#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/bitext.h>
#include <sys/sysmacros.h>
#include <sys/i2c/client.h>
#include <eedev.h>
#define HUB_R_TYPE_MSB 0x00
#define HUB_R_TYPE_LSB 0x01
#define HUB_R_REV 0x02
#define HUB_R_VID0 0x03
#define HUB_R_VID1 0x04
#define HUB_R_CAP 0x05
#define HUB_R_CAP_GET_TS_SUP(r) bitx8(r, 1, 1)
#define HUB_R_CAP_GET_HUB(r) bitx8(r, 0, 0)
#define HUB_R_I2C_CFG 0x0b
#define HUB_R_I2C_CFG_GET_MODE(r) bitx8(r, 3, 3)
#define HUB_R_I2C_CFG_GET_PAGE(r) bitx8(r, 2, 0)
#define HUB_R_I2C_CFG_SET_PAGE(r, v) bitset8(r, 2, 0, v)
#define HUB_R_TEMP_LSB 0x31
#define HUB_R_TEMP_LSB_GET_TEMP(v) bitx8(v, 7, 2)
#define HUB_R_TEMP_MSB 0x32
#define HUB_R_TEMP_MSB_GET_TEMP(v) bitx8(v, 3, 0)
#define HUB_R_TEMP_MSB_GET_SIGN(v) bitx8(v, 4, 4)
#define HUB_R_TEMP_MSB_SHIFT 6
#define HUB_R_NVM_BASE 0x80
#define HUB_R_REG_MAX UINT8_MAX
#define HUB_TEMP_RES 4
#define HUB_NVM_NPAGES 8
#define HUB_NVM_PAGE_SIZE 128
typedef struct spd5118 {
dev_info_t *spd_dip;
i2c_client_t *spd_client;
i2c_reg_hdl_t *spd_regs;
uint8_t spd_vid[2];
uint8_t spd_rev;
uint8_t spd_cap;
eedev_hdl_t *spd_eehdl;
id_t spd_ksensor;
kmutex_t spd_mutex;
uint8_t spd_buf[I2C_REQ_MAX];
uint8_t spd_raw[2];
int64_t spd_temp;
} spd5118_t;
static const i2c_reg_acc_attr_t spd5118_reg_attr = {
.i2cacc_version = I2C_REG_ACC_ATTR_V0,
.i2cacc_addr_len = 1,
.i2cacc_reg_len = 1,
.i2cacc_addr_max = HUB_R_REG_MAX
};
static bool
spd5118_page_change(i2c_txn_t *txn, spd5118_t *spd, uint32_t page)
{
uint8_t cfg;
i2c_error_t err;
VERIFY(MUTEX_HELD(&spd->spd_mutex));
if (!i2c_reg_get(txn, spd->spd_regs, HUB_R_I2C_CFG, &cfg, sizeof (cfg),
&err)) {
dev_err(spd->spd_dip, CE_WARN, "!failed to read cap register: "
"0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
if (HUB_R_I2C_CFG_GET_PAGE(cfg) != page) {
cfg = HUB_R_I2C_CFG_SET_PAGE(cfg, page);
if (!i2c_reg_put(txn, spd->spd_regs, HUB_R_I2C_CFG, &cfg,
sizeof (cfg), &err)) {
dev_err(spd->spd_dip, CE_WARN, "!failed to write cap "
"register: 0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
}
return (true);
}
static int
spd5118_temp_read(void *arg, sensor_ioctl_scalar_t *scalar)
{
int ret;
uint8_t val[2];
i2c_txn_t *txn;
i2c_error_t err;
spd5118_t *spd = arg;
mutex_enter(&spd->spd_mutex);
if (i2c_bus_lock(spd->spd_client, 0, &txn) != I2C_CORE_E_OK) {
mutex_exit(&spd->spd_mutex);
return (EINTR);
}
if (!spd5118_page_change(txn, spd, 0)) {
ret = EIO;
goto done;
}
if (!i2c_reg_get(txn, spd->spd_regs, HUB_R_TEMP_LSB, val, sizeof (val),
&err)) {
dev_err(spd->spd_dip, CE_WARN, "!failed to read temp "
"registers: 0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (EIO);
}
bcopy(val, spd->spd_raw, sizeof (val));
uint64_t u64 = HUB_R_TEMP_LSB_GET_TEMP(spd->spd_raw[0]) |
(HUB_R_TEMP_MSB_GET_TEMP(spd->spd_raw[1]) << HUB_R_TEMP_MSB_SHIFT);
if (HUB_R_TEMP_MSB_GET_SIGN(spd->spd_raw[1]) == 1) {
u64 |= UINT64_MAX & ~((1 << 10) - 1);
}
spd->spd_temp = (int64_t)u64;
scalar->sis_value = spd->spd_temp;
scalar->sis_unit = SENSOR_UNIT_CELSIUS;
scalar->sis_gran = HUB_TEMP_RES;
int64_t prec_temp = scalar->sis_value / HUB_TEMP_RES;
if (75 <= prec_temp && prec_temp <= 95) {
scalar->sis_prec = 1 * scalar->sis_gran;
} else if (40 <= prec_temp && prec_temp <= 125) {
scalar->sis_prec = 2 * scalar->sis_gran;
} else {
scalar->sis_prec = 3 * scalar->sis_gran;
}
ret = 0;
done:
i2c_bus_unlock(txn);
mutex_exit(&spd->spd_mutex);
return (ret);
}
static const ksensor_ops_t spd5118_temp_ops = {
.kso_kind = ksensor_kind_temperature,
.kso_scalar = spd5118_temp_read
};
static int
spd5118_read(void *arg, struct uio *uio, uint32_t page, uint32_t pageoff,
uint32_t nbytes)
{
int ret;
i2c_txn_t *txn;
i2c_error_t err;
spd5118_t *spd = arg;
VERIFY3U(page, <, HUB_NVM_NPAGES);
VERIFY3U(pageoff, <, HUB_NVM_PAGE_SIZE);
mutex_enter(&spd->spd_mutex);
if (i2c_bus_lock(spd->spd_client, 0, &txn) != I2C_CORE_E_OK) {
mutex_exit(&spd->spd_mutex);
return (EINTR);
}
if (!spd5118_page_change(txn, spd, page)) {
ret = EIO;
goto done;
}
pageoff += HUB_R_NVM_BASE;
if (i2c_reg_get(txn, spd->spd_regs, pageoff, spd->spd_buf, nbytes,
&err)) {
ret = uiomove(spd->spd_buf, nbytes, UIO_READ, uio);
} else {
dev_err(spd->spd_dip, CE_WARN, "!failed to read %u bytes of "
"NVM at 0x%x on page %u: 0x%x/0x%x", nbytes, pageoff, page,
err.i2c_error, err.i2c_ctrl);
ret = EIO;
}
done:
i2c_bus_unlock(txn);
mutex_exit(&spd->spd_mutex);
return (ret);
}
static const eedev_ops_t spd5118_eedev_ops = {
.eo_read = spd5118_read
};
static bool
spd5118_i2c_init(spd5118_t *spd)
{
i2c_errno_t err;
if ((err = i2c_client_init(spd->spd_dip, 0, &spd->spd_client)) !=
I2C_CORE_E_OK) {
dev_err(spd->spd_dip, CE_WARN, "failed to create i2c client: "
"0x%x", err);
return (false);
}
if ((err = i2c_reg_handle_init(spd->spd_client, &spd5118_reg_attr,
&spd->spd_regs)) != I2C_CORE_E_OK) {
dev_err(spd->spd_dip, CE_WARN, "failed to create register "
"handle: %s (0x%x)", i2c_client_errtostr(spd->spd_client,
err), err);
return (false);
}
return (true);
}
static bool
spd5118_ident(spd5118_t *spd)
{
uint8_t type[2];
i2c_error_t err;
if (!i2c_reg_get(NULL, spd->spd_regs, HUB_R_TYPE_MSB, type,
sizeof (type), &err)) {
dev_err(spd->spd_dip, CE_WARN, "!failed to read type "
"registers: 0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
if (type[0] == 0 && type[1] == 0) {
mutex_enter(&spd->spd_mutex);
if (!spd5118_page_change(NULL, spd, 0)) {
mutex_exit(&spd->spd_mutex);
return (false);
}
mutex_exit(&spd->spd_mutex);
if (!i2c_reg_get(NULL, spd->spd_regs, HUB_R_TYPE_MSB, type,
sizeof (type), &err)) {
dev_err(spd->spd_dip, CE_WARN, "!failed to read type "
"registers: 0x%x/0x%x", err.i2c_error,
err.i2c_ctrl);
return (false);
}
}
if (type[0] != 0x51 || type[1] != 0x18) {
dev_err(spd->spd_dip, CE_WARN, "encountered unsupported device "
"type: 0x%x/0x%x", type[0], type[1]);
return (false);
}
if (!i2c_reg_get(NULL, spd->spd_regs, HUB_R_VID0, spd->spd_vid,
sizeof (spd->spd_vid), &err)) {
dev_err(spd->spd_dip, CE_WARN, "!failed to read vid registers: "
"0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
if (!i2c_reg_get(NULL, spd->spd_regs, HUB_R_REV, &spd->spd_rev,
sizeof (spd->spd_rev), &err)) {
dev_err(spd->spd_dip, CE_WARN, "!failed to read rev register: "
"0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
if (!i2c_reg_get(NULL, spd->spd_regs, HUB_R_CAP, &spd->spd_cap,
sizeof (spd->spd_cap), &err)) {
dev_err(spd->spd_dip, CE_WARN, "!failed to read cap register: "
"0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
return (true);
}
static bool
spd5118_eedev_init(spd5118_t *spd)
{
int ret;
eedev_reg_t reg;
bzero(®, sizeof (reg));
reg.ereg_vers = EEDEV_REG_VERS;
reg.ereg_size = HUB_NVM_NPAGES * HUB_NVM_PAGE_SIZE;
reg.ereg_seg = HUB_NVM_PAGE_SIZE;
reg.ereg_read_gran = 1;
reg.ereg_ro = true;
reg.ereg_dip = spd->spd_dip;
reg.ereg_driver = spd;
reg.ereg_name = NULL;
reg.ereg_ops = &spd5118_eedev_ops;
reg.ereg_max_read = MIN(i2c_reg_max_read(spd->spd_regs),
I2C_REQ_MAX / 2);
if ((ret = eedev_create(®, &spd->spd_eehdl)) != 0) {
dev_err(spd->spd_dip, CE_WARN, "failed to create eedev device: "
"%d", ret);
return (false);
}
return (true);
}
static void
spd5118_cleanup(spd5118_t *spd)
{
(void) ksensor_remove(spd->spd_dip, KSENSOR_ALL_IDS);
eedev_fini(spd->spd_eehdl);
i2c_reg_handle_destroy(spd->spd_regs);
i2c_client_destroy(spd->spd_client);
mutex_destroy(&spd->spd_mutex);
ddi_set_driver_private(spd->spd_dip, NULL);
spd->spd_dip = NULL;
kmem_free(spd, sizeof (spd5118_t));
}
static int
spd5118_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int ret;
spd5118_t *spd;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
spd = kmem_zalloc(sizeof (spd5118_t), KM_SLEEP);
spd->spd_dip = dip;
ddi_set_driver_private(dip, spd);
mutex_init(&spd->spd_mutex, NULL, MUTEX_DRIVER, NULL);
if (!spd5118_i2c_init(spd))
goto cleanup;
if (!spd5118_ident(spd))
goto cleanup;
if (!spd5118_eedev_init(spd))
goto cleanup;
if ((ret = i2c_client_ksensor_create_scalar(spd->spd_client,
SENSOR_KIND_TEMPERATURE, &spd5118_temp_ops, spd, "temp",
&spd->spd_ksensor)) != 0) {
dev_err(spd->spd_dip, CE_WARN, "failed to create ksensor: %d",
ret);
goto cleanup;
}
return (DDI_SUCCESS);
cleanup:
spd5118_cleanup(spd);
return (DDI_FAILURE);
}
static int
spd5118_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
spd5118_t *spd;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
spd = ddi_get_driver_private(dip);
if (spd == NULL) {
dev_err(dip, CE_WARN, "asked to detach, but missing private "
"data");
return (DDI_FAILURE);
}
VERIFY3P(spd->spd_dip, ==, dip);
spd5118_cleanup(spd);
return (DDI_SUCCESS);
}
static struct dev_ops spd5118_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = spd5118_attach,
.devo_detach = spd5118_detach,
.devo_reset = nodev,
.devo_quiesce = ddi_quiesce_not_needed
};
static struct modldrv spd5118_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "SPD5118 driver",
.drv_dev_ops = &spd5118_dev_ops
};
static struct modlinkage spd5118_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &spd5118_modldrv, NULL }
};
int
_init(void)
{
return (mod_install(&spd5118_modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&spd5118_modlinkage, modinfop));
}
int
_fini(void)
{
return (mod_remove(&spd5118_modlinkage));
}