#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/debug.h>
#include <sys/i2c/client.h>
#include <sys/sensors.h>
#define TS_R_TYPE_MSB 0
#define TS_R_TYPE_511X 0x51
#define TS_R_TYPE_521X 0x52
#define TS_R_TYPE_LSB 1
#define TS_R_TYPE_LSB_GRADE_A 0x11
#define TS_R_TYPE_LSB_GRADE_B 0x10
#define TS_R_REV 2
#define TS_R_REV_GET_MAJ(v) bitx8(v, 5, 4)
#define TS_R_REV_GET_MIN(v) bitx8(v, 3, 1)
#define TS_R_VID0 3
#define TS_R_VID1 4
#define TS_R_HIGH_LIMIT_LSB 0x1c
#define TS_R_HIGH_LIMIT_MSB 0x1d
#define TS_R_LOW_LIMIT_LSB 0x1e
#define TS_R_LOW_LIMIT_MSB 0x1f
#define TS_R_HIGH_CRIT_LSB 0x20
#define TS_R_HIGH_CRIT_MSB 0x21
#define TS_R_LOW_CRIT_LSB 0x22
#define TS_R_LOW_CRIT_MSB 0x23
#define TS_R_TEMP_LSB 0x31
#define TS_R_TEMP_LSB_GET_TEMP(v) bitx8(v, 7, 2)
#define TS_R_TEMP_MSB 0x32
#define TS_R_TEMP_MSB_GET_TEMP(v) bitx8(v, 3, 0)
#define TS_R_TEMP_MSB_GET_SIGN(v) bitx8(v, 4, 4)
#define TS_R_TEMP_MSB_SHIFT 6
#define TS_R_SN0 0x50
#define TS_R_SN1 0x51
#define TS_R_SN2 0x52
#define TS_R_SN3 0x53
#define TS_R_SN4 0x54
#define TS_R_MAX 0xFF
#define TS_TEMP_RES 4
typedef enum ts511x_type {
TS_TYPE_511X,
TS_TYPE_521X
} ts511x_type_t;
typedef struct ts511x {
dev_info_t *ts_dip;
i2c_client_t *ts_client;
i2c_reg_hdl_t *ts_regs;
ts511x_type_t ts_type;
id_t ts_ksensor;
uint8_t ts_vid[2];
uint8_t ts_rev;
uint8_t ts_sn[5];
kmutex_t ts_mutex;
uint8_t ts_raw[2];
int64_t ts_temp;
} ts511x_t;
static const i2c_reg_acc_attr_t ts511x_reg_attr = {
.i2cacc_version = I2C_REG_ACC_ATTR_V0,
.i2cacc_addr_len = 1,
.i2cacc_reg_len = 1,
.i2cacc_addr_max = TS_R_MAX
};
static int
ts511x_temp_read(void *arg, sensor_ioctl_scalar_t *scalar)
{
ts511x_t *ts = arg;
uint8_t val[2];
i2c_error_t err;
if (!i2c_reg_get(NULL, ts->ts_regs, TS_R_TEMP_LSB, val, sizeof (val),
&err)) {
dev_err(ts->ts_dip, CE_WARN, "!failed to read temp registers: "
"0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (EIO);
}
mutex_enter(&ts->ts_mutex);
bcopy(val, ts->ts_raw, sizeof (val));
uint64_t u64 = TS_R_TEMP_LSB_GET_TEMP(ts->ts_raw[0]) |
(TS_R_TEMP_MSB_GET_TEMP(ts->ts_raw[1]) << TS_R_TEMP_MSB_SHIFT);
if (TS_R_TEMP_MSB_GET_SIGN(ts->ts_raw[1]) == 1) {
u64 |= UINT64_MAX & ~((1 << 10) - 1);
}
ts->ts_temp = (int64_t)u64;
scalar->sis_value = ts->ts_temp;
scalar->sis_unit = SENSOR_UNIT_CELSIUS;
scalar->sis_gran = TS_TEMP_RES;
int64_t prec_temp = scalar->sis_value / TS_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;
}
mutex_exit(&ts->ts_mutex);
return (0);
}
static const ksensor_ops_t ts511x_temp_ops = {
.kso_kind = ksensor_kind_temperature,
.kso_scalar = ts511x_temp_read
};
static bool
ts511x_i2c_init(ts511x_t *ts)
{
i2c_errno_t err;
if ((err = i2c_client_init(ts->ts_dip, 0, &ts->ts_client)) !=
I2C_CORE_E_OK) {
dev_err(ts->ts_dip, CE_WARN, "failed to create i2c client: "
"0x%x", err);
return (false);
}
if ((err = i2c_reg_handle_init(ts->ts_client, &ts511x_reg_attr,
&ts->ts_regs)) != I2C_CORE_E_OK) {
dev_err(ts->ts_dip, CE_WARN, "failed to create register "
"handle: %s (0x%x)", i2c_client_errtostr(ts->ts_client,
err), err);
return (false);
}
return (true);
}
static bool
ts511x_ident(ts511x_t *ts)
{
uint8_t type;
i2c_error_t err;
if (!i2c_reg_get(NULL, ts->ts_regs, TS_R_TYPE_MSB, &type, sizeof (type),
&err)) {
dev_err(ts->ts_dip, CE_WARN, "!failed to read type register: "
"0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
switch (type) {
case TS_R_TYPE_511X:
ts->ts_type = TS_TYPE_511X;
break;
case TS_R_TYPE_521X:
ts->ts_type = TS_TYPE_521X;
break;
default:
dev_err(ts->ts_dip, CE_WARN, "encountered unsupported device "
"type: 0x%x", type);
return (false);
}
if (!i2c_reg_get(NULL, ts->ts_regs, TS_R_VID0, ts->ts_vid,
sizeof (ts->ts_vid), &err)) {
dev_err(ts->ts_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, ts->ts_regs, TS_R_REV, &ts->ts_rev,
sizeof (ts->ts_rev), &err)) {
dev_err(ts->ts_dip, CE_WARN, "!failed to read rev register: "
"0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
if (ts->ts_type != TS_TYPE_521X) {
return (true);
}
if (!i2c_reg_get(NULL, ts->ts_regs, TS_R_REV, ts->ts_sn,
sizeof (ts->ts_sn), &err)) {
dev_err(ts->ts_dip, CE_WARN, "!failed to read sn registers: "
"0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
return (true);
}
static void
ts511x_cleanup(ts511x_t *ts)
{
(void) ksensor_remove(ts->ts_dip, KSENSOR_ALL_IDS);
i2c_reg_handle_destroy(ts->ts_regs);
i2c_client_destroy(ts->ts_client);
mutex_destroy(&ts->ts_mutex);
ddi_set_driver_private(ts->ts_dip, NULL);
ts->ts_dip = NULL;
kmem_free(ts, sizeof (ts511x_t));
}
static int
ts511x_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int ret;
ts511x_t *ts;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
ts = kmem_zalloc(sizeof (ts511x_t), KM_SLEEP);
ts->ts_dip = dip;
ddi_set_driver_private(dip, ts);
mutex_init(&ts->ts_mutex, NULL, MUTEX_DRIVER, NULL);
if (!ts511x_i2c_init(ts))
goto cleanup;
if (!ts511x_ident(ts))
goto cleanup;
if ((ret = i2c_client_ksensor_create_scalar(ts->ts_client,
SENSOR_KIND_TEMPERATURE, &ts511x_temp_ops, ts, "temp",
&ts->ts_ksensor)) != 0) {
dev_err(ts->ts_dip, CE_WARN, "failed to create ksensor: %d",
ret);
goto cleanup;
}
return (DDI_SUCCESS);
cleanup:
ts511x_cleanup(ts);
return (DDI_FAILURE);
}
static int
ts511x_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
ts511x_t *ts;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
ts = ddi_get_driver_private(dip);
if (ts == NULL) {
dev_err(dip, CE_WARN, "asked to detach, but missing private "
"data");
return (DDI_FAILURE);
}
VERIFY3P(ts->ts_dip, ==, dip);
ts511x_cleanup(ts);
return (DDI_SUCCESS);
}
static struct dev_ops ts511x_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = ts511x_attach,
.devo_detach = ts511x_detach,
.devo_reset = nodev,
.devo_quiesce = ddi_quiesce_not_needed
};
static struct modldrv ts511x_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "TS511X/TS521X driver",
.drv_dev_ops = &ts511x_dev_ops
};
static struct modlinkage ts511x_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &ts511x_modldrv, NULL }
};
int
_init(void)
{
return (mod_install(&ts511x_modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&ts511x_modlinkage, modinfop));
}
int
_fini(void)
{
return (mod_remove(&ts511x_modlinkage));
}