#include <sys/param.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/taskqueue.h>
#include <machine/bus.h>
#include <dev/asmc/asmcvar.h>
#include <dev/asmc/asmcmmio.h>
static int
asmc_mmio_wait(device_t dev)
{
struct asmc_softc *sc = device_get_softc(dev);
int i;
uint8_t status;
int delay_us = 10;
for (i = 0; i < ASMC_MMIO_MAX_WAIT; i++) {
status = bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS);
if (status & ASMC_MMIO_STATUS_READY)
return (0);
DELAY(delay_us);
if (delay_us < 3200)
delay_us *= 2;
}
return (ETIMEDOUT);
}
int
asmc_mmio_key_read(device_t dev, const char *key, uint8_t *buf, uint8_t len)
{
struct asmc_softc *sc = device_get_softc(dev);
uint32_t key_int;
int error, i;
uint8_t cmd_result, rlen;
if (len > ASMC_MAXVAL)
return (EINVAL);
mtx_lock_spin(&sc->sc_mtx);
if (bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS))
bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0);
memcpy(&key_int, key, 4);
bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, key_int);
bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0);
bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDREAD);
error = asmc_mmio_wait(dev);
if (error != 0) {
uint8_t st = bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS);
uint8_t cm = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD);
mtx_unlock_spin(&sc->sc_mtx);
device_printf(dev,
"%s: timeout key %.4s status=0x%02x cmd=0x%02x\n",
__func__, key, st, cm);
return (error);
}
cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD);
if (cmd_result != 0) {
mtx_unlock_spin(&sc->sc_mtx);
device_printf(dev,
"%s: key %.4s cmd error 0x%02x\n",
__func__, key, cmd_result);
return (EIO);
}
rlen = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA_LEN);
rlen = MIN(rlen, len);
for (i = rlen; i < len; i++)
buf[i] = 0;
for (i = 0; i < rlen; i++)
buf[i] = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA + i);
mtx_unlock_spin(&sc->sc_mtx);
return (0);
}
int
asmc_mmio_key_write(device_t dev, const char *key, uint8_t *buf, uint8_t len)
{
struct asmc_softc *sc = device_get_softc(dev);
uint32_t key_int;
int error, i;
uint8_t cmd_result;
if (len > ASMC_MAXVAL)
return (EINVAL);
mtx_lock_spin(&sc->sc_mtx);
bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0);
for (i = 0; i < len; i++)
bus_write_1(sc->sc_iomem, ASMC_MMIO_DATA + i, buf[i]);
memcpy(&key_int, key, 4);
bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, key_int);
bus_write_1(sc->sc_iomem, ASMC_MMIO_DATA_LEN, len);
bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0);
bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDWRITE);
error = asmc_mmio_wait(dev);
if (error != 0) {
mtx_unlock_spin(&sc->sc_mtx);
device_printf(dev, "%s: timeout writing key %.4s\n",
__func__, key);
return (error);
}
cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD);
mtx_unlock_spin(&sc->sc_mtx);
return (cmd_result == 0 ? 0 : EIO);
}
int
asmc_mmio_key_getinfo(device_t dev, const char *key, uint8_t *len, char *type)
{
struct asmc_softc *sc = device_get_softc(dev);
uint32_t key_int;
int error, i;
uint8_t cmd_result;
mtx_lock_spin(&sc->sc_mtx);
bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0);
memcpy(&key_int, key, 4);
bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, key_int);
bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0);
bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDGETINFO);
error = asmc_mmio_wait(dev);
if (error != 0) {
mtx_unlock_spin(&sc->sc_mtx);
return (error);
}
cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD);
if (cmd_result != 0) {
mtx_unlock_spin(&sc->sc_mtx);
return (EIO);
}
if (type != NULL) {
for (i = 0; i < ASMC_TYPELEN; i++)
type[i] = bus_read_1(sc->sc_iomem,
ASMC_MMIO_DATA + i);
type[ASMC_TYPELEN] = '\0';
}
if (len != NULL)
*len = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA + 5);
mtx_unlock_spin(&sc->sc_mtx);
return (0);
}
int
asmc_mmio_key_getbyindex(device_t dev, int index, char *key)
{
struct asmc_softc *sc = device_get_softc(dev);
uint32_t idx_val;
int error, i;
uint8_t cmd_result;
mtx_lock_spin(&sc->sc_mtx);
bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0);
idx_val = htobe32(index);
bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, idx_val);
bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0);
bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDGETBYINDEX);
error = asmc_mmio_wait(dev);
if (error != 0) {
mtx_unlock_spin(&sc->sc_mtx);
return (error);
}
cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD);
if (cmd_result != 0) {
mtx_unlock_spin(&sc->sc_mtx);
return (EIO);
}
for (i = 0; i < ASMC_KEYLEN; i++)
key[i] = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA + i);
key[ASMC_KEYLEN] = '\0';
mtx_unlock_spin(&sc->sc_mtx);
return (0);
}
int
asmc_mmio_probe(device_t dev)
{
struct asmc_softc *sc = device_get_softc(dev);
rman_res_t size;
uint8_t status, ldkn;
int error;
size = rman_get_size(sc->sc_iomem);
if (size < ASMC_MMIO_MIN_SIZE) {
device_printf(dev, "MMIO region too small (%jd < %d)\n",
(intmax_t)size, ASMC_MMIO_MIN_SIZE);
return (ENXIO);
}
status = bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS);
if (status == 0xFF) {
device_printf(dev, "MMIO status register reads 0xFF\n");
return (ENXIO);
}
if (!mtx_initialized(&sc->sc_mtx))
mtx_init(&sc->sc_mtx, "asmc", NULL, MTX_SPIN);
error = asmc_mmio_key_read(dev, ASMC_KEY_LDKN, &ldkn, 1);
if (error != 0) {
device_printf(dev, "MMIO: failed to read LDKN key\n");
return (ENXIO);
}
if (ldkn < 2) {
device_printf(dev, "MMIO: LDKN=%d (need >= 2)\n", ldkn);
return (ENXIO);
}
device_printf(dev, "MMIO: LDKN=%d, T2 SMC detected\n", ldkn);
sc->sc_is_t2 = 1;
return (0);
}
void
asmc_mmio_detach(device_t dev, struct asmc_softc *sc)
{
if (sc->sc_iomem != NULL) {
bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_rid_mem,
sc->sc_iomem);
sc->sc_iomem = NULL;
}
sc->sc_is_mmio = 0;
sc->sc_is_t2 = 0;
}
uint32_t
asmc_float_to_u32(uint32_t d)
{
int32_t exp;
uint32_t fr;
if (d == 0 || (d >> 31) != 0)
return (0);
exp = (int32_t)((d >> 23) & 0xff) - 0x7f;
fr = d & 0x7fffff;
if (exp < 0)
return (0);
if (exp > 23) {
if (exp > 30)
return (0xffffffffu);
return ((1u << exp) | (fr << (exp - 23)));
}
return ((1u << exp) + (fr >> (23 - exp)));
}
uint32_t
asmc_u32_to_float(uint32_t d)
{
uint32_t dc, bc, exp;
if (d == 0)
return (0);
dc = d;
bc = 0;
while (dc >>= 1)
++bc;
bc = MIN(bc, 30);
exp = 0x7f + bc;
if (bc >= 23)
return ((exp << 23) | ((d >> (bc - 23)) & 0x7fffff));
else
return ((exp << 23) | ((d << (23 - bc)) & 0x7fffff));
}
int
asmc_bclm_sysctl(SYSCTL_HANDLER_ARGS)
{
device_t dev = (device_t)arg1;
uint8_t bclm;
int val, error;
error = asmc_mmio_key_read(dev, ASMC_KEY_BCLM, &bclm, 1);
if (error != 0)
return (EIO);
val = (int)bclm;
error = sysctl_handle_int(oidp, &val, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (val < 0 || val > 100)
return (EINVAL);
bclm = (uint8_t)val;
error = asmc_mmio_key_write(dev, ASMC_KEY_BCLM, &bclm, 1);
return (error != 0 ? EIO : 0);
}