#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/sysmacros.h>
#include <sys/i2c/client.h>
#include <sys/sensors.h>
typedef enum {
TMP43X_R_TEMP_LOCAL = 0,
TMP43X_R_TEMP_REM1,
TMP43X_R_TEMP_REM2,
TMP43X_R_CFG1,
TMP43X_R_NFACTOR1,
TMP43X_R_NFACTOR2,
TMP43X_R_BETA1,
TMP43X_R_BETA2,
TMP43X_R_RES
} tmp43x_reg_t;
#define TMP43X_NSESNORS 3
typedef enum {
TMP43X_REG_TYPE_TEMP,
TMP43X_REG_TYPE_1B
} tmp43x_reg_type_t;
typedef struct tmp43x_reg_info {
tmp43x_reg_t tri_reg;
bool tri_ro;
uint8_t tri_read;
uint8_t tri_write;
} tmp43x_reg_info_t;
static const tmp43x_reg_info_t tmp43x_regs_401[] = {
{ TMP43X_R_TEMP_LOCAL, true, 0x0, 0x0 },
{ TMP43X_R_TEMP_REM1, true, 0x1, 0x1 },
{ TMP43X_R_CFG1, false, 0x3, 0x9 },
{ TMP43X_R_RES, false, 0x1a, 0x1a }
};
static const tmp43x_reg_info_t tmp43x_regs_411[] = {
{ TMP43X_R_TEMP_LOCAL, true, 0x0, 0x0 },
{ TMP43X_R_TEMP_REM1, true, 0x1, 0x1 },
{ TMP43X_R_CFG1, false, 0x3, 0x9 },
{ TMP43X_R_RES, false, 0x1a, 0x1a },
{ TMP43X_R_NFACTOR1, false, 0x18, 0x18 }
};
static const tmp43x_reg_info_t tmp43x_regs_431_435[] = {
{ TMP43X_R_TEMP_LOCAL, true, 0x0, 0x0 },
{ TMP43X_R_TEMP_REM1, true, 0x1, 0x1 },
{ TMP43X_R_CFG1, false, 0x3, 0x9 },
{ TMP43X_R_NFACTOR1, false, 0x18, 0x18 },
{ TMP43X_R_BETA1, false, 0x25, 0x25 },
};
static const tmp43x_reg_info_t tmp43x_regs_432[] = {
{ TMP43X_R_TEMP_LOCAL, true, 0x0, 0x0 },
{ TMP43X_R_TEMP_REM1, true, 0x1, 0x1 },
{ TMP43X_R_TEMP_REM2, true, 0x23, 0x23 },
{ TMP43X_R_CFG1, false, 0x3, 0x9 },
{ TMP43X_R_NFACTOR1, false, 0x27, 0x27 },
{ TMP43X_R_NFACTOR2, false, 0x28, 0x28 },
{ TMP43X_R_BETA1, false, 0x25, 0x25 },
{ TMP43X_R_BETA2, false, 0x26, 0x26 },
};
#define TMP43X_R_MFGID 0xfe
#define TMP43X_MFG_TI 0x55
#define TMP43X_R_DEVID 0xff
typedef enum {
TMP43X_DEV_401 = 0x11,
TMP43X_DEV_411A = 0x12,
TMP43X_DEV_411B = 0x13,
TMP43X_DEV_411C = 0x10,
TMP43X_DEV_431 = 0x31,
TMP43X_DEV_432 = 0x32,
TMP43X_DEV_435 = 0x35
} tmp43x_dev_t;
#define TMP43X_TEMP_GRAN 16
#define TMP43X_TEMP_GET_VAL(r) bitx16(r, 15, 4)
#define TMP43X_TEMP_EXTD_ADJ (-65 * TMP43X_TEMP_GRAN)
#define TMP43X_TEMP_PREC (TMP43X_TEMP_GRAN * 1)
#define TMP43X_CFG1_GET_SD(r) bitx8(r, 6, 6)
#define TMP43X_CFG1_SET_SD(r, v) bitset8(r, 6, 6, v)
#define TMP43X_CFG1_SD_RUN 0
#define TMP43X_CFG1_SD_STOP 1
#define TMP43X_CFG1_GET_RANGE(r) bitx8(r, 2, 2)
#define TMP43X_CFG1_RANGE_STD 0
#define TMP43X_CFG1_RANGE_EXT 1
#define TMP43X_RES_SET_RES(r, v) bitset8(r, 1, 0, v)
#define TMP43X_RES_RES_9B 0
#define TMP43X_RES_RES_10B 1
#define TMP43X_RES_RES_11B 2
#define TMP43X_RES_RES_12B 3
#define TMP43X_BETA_GET_RANGE(r) bitx8(r, 3, 0)
#define TMP43X_BETA_SET_RANGE(r, v) bitset8(r, 3, 0, v)
typedef enum {
TMP43X_F_NFACTOR = 1 << 0,
TMP43X_F_BETA = 1 << 1,
TMP43X_F_LRES = 1 << 2,
TMP43X_F_MINMAX = 1 << 3,
TMP43X_F_REM2 = 1 << 4,
TMP43X_F_EXT_TEMP = 1 << 5
} tmp43x_flags_t;
typedef struct tmp43x {
dev_info_t *tmp_dip;
i2c_client_t *tmp_client;
i2c_reg_hdl_t *tmp_regs;
tmp43x_dev_t tmp_dev;
tmp43x_flags_t tmp_flags;
uint8_t tmp_nrem;
const tmp43x_reg_info_t *tmp_rinfo;
size_t tmp_nrinfo;
id_t tmp_ksensor[TMP43X_NSESNORS];
kmutex_t tmp_mutex;
uint16_t tmp_raw[TMP43X_NSESNORS];
int64_t tmp_temp[TMP43X_NSESNORS];
} tmp43x_t;
static tmp43x_reg_type_t
tmp43x_reg_type(tmp43x_reg_t reg)
{
switch (reg) {
case TMP43X_R_TEMP_LOCAL:
case TMP43X_R_TEMP_REM1:
case TMP43X_R_TEMP_REM2:
return (TMP43X_REG_TYPE_TEMP);
case TMP43X_R_CFG1:
case TMP43X_R_NFACTOR1:
case TMP43X_R_NFACTOR2:
case TMP43X_R_BETA1:
case TMP43X_R_BETA2:
case TMP43X_R_RES:
return (TMP43X_REG_TYPE_1B);
default:
panic("tmp43x programmer error: unknown register 0x%x", reg);
}
}
static bool
tmp43x_write_ctl(tmp43x_t *tmp, tmp43x_reg_t reg, uint8_t val)
{
const tmp43x_reg_info_t *info = NULL;
i2c_error_t err;
for (size_t i = 0; i < tmp->tmp_nrinfo; i++) {
if (tmp->tmp_rinfo[i].tri_reg == reg) {
info = &tmp->tmp_rinfo[i];
break;
}
}
VERIFY3P(info, !=, NULL);
VERIFY3B(info->tri_ro, ==, false);
VERIFY3U(tmp43x_reg_type(reg), ==, TMP43X_REG_TYPE_1B);
if (!smbus_client_write_u8(NULL, tmp->tmp_client, info->tri_write, val,
&err)) {
dev_err(tmp->tmp_dip, CE_WARN, "!failed to write register "
"0x%x: 0x%x/0x%x", info->tri_read, err.i2c_error,
err.i2c_ctrl);
return (false);
}
return (true);
}
static bool
tmp43x_read_ctl(tmp43x_t *tmp, tmp43x_reg_t reg, uint8_t *valp)
{
const tmp43x_reg_info_t *info = NULL;
i2c_error_t err;
for (size_t i = 0; i < tmp->tmp_nrinfo; i++) {
if (tmp->tmp_rinfo[i].tri_reg == reg) {
info = &tmp->tmp_rinfo[i];
break;
}
}
VERIFY3P(info, !=, NULL);
VERIFY3U(tmp43x_reg_type(reg), ==, TMP43X_REG_TYPE_1B);
if (!smbus_client_read_u8(NULL, tmp->tmp_client, info->tri_read, valp,
&err)) {
dev_err(tmp->tmp_dip, CE_WARN, "!failed to read register 0x%x: "
"0x%x/0x%x", info->tri_read, err.i2c_error, err.i2c_ctrl);
return (false);
}
return (true);
}
static int
tmp43x_temp_read(tmp43x_t *tmp, tmp43x_reg_t reg, sensor_ioctl_scalar_t *scalar)
{
const tmp43x_reg_info_t *info = NULL;
i2c_error_t err;
uint16_t val;
for (size_t i = 0; i < tmp->tmp_nrinfo; i++) {
if (tmp->tmp_rinfo[i].tri_reg == reg) {
info = &tmp->tmp_rinfo[i];
break;
}
}
VERIFY3P(info, !=, NULL);
VERIFY3U(tmp43x_reg_type(reg), ==, TMP43X_REG_TYPE_TEMP);
mutex_enter(&tmp->tmp_mutex);
if (!i2c_reg_get(NULL, tmp->tmp_regs, info->tri_read, &val,
sizeof (val), &err)) {
dev_err(tmp->tmp_dip, CE_WARN, "!failed to read temperature "
"register 0x%x: 0x%x/0x%x", info->tri_read, err.i2c_error,
err.i2c_ctrl);
mutex_exit(&tmp->tmp_mutex);
return (EIO);
}
tmp->tmp_raw[reg] = val;
int64_t temp = TMP43X_TEMP_GET_VAL(val);
if ((tmp->tmp_flags & TMP43X_F_EXT_TEMP) != 0) {
temp += TMP43X_TEMP_EXTD_ADJ;
}
tmp->tmp_temp[reg] = temp;
scalar->sis_value = temp;
scalar->sis_gran = TMP43X_TEMP_GRAN;
scalar->sis_prec = TMP43X_TEMP_PREC;
scalar->sis_unit = SENSOR_UNIT_CELSIUS;
mutex_exit(&tmp->tmp_mutex);
return (0);
}
static int
tmp43x_temp_read_local(void *arg, sensor_ioctl_scalar_t *scalar)
{
tmp43x_t *tmp = arg;
return (tmp43x_temp_read(tmp, TMP43X_R_TEMP_LOCAL, scalar));
}
static int
tmp43x_temp_read_rem1(void *arg, sensor_ioctl_scalar_t *scalar)
{
tmp43x_t *tmp = arg;
return (tmp43x_temp_read(tmp, TMP43X_R_TEMP_REM1, scalar));
}
static int
tmp43x_temp_read_rem2(void *arg, sensor_ioctl_scalar_t *scalar)
{
tmp43x_t *tmp = arg;
return (tmp43x_temp_read(tmp, TMP43X_R_TEMP_REM2, scalar));
}
static const ksensor_ops_t tmp43x_local_temp_ops = {
.kso_kind = ksensor_kind_temperature,
.kso_scalar = tmp43x_temp_read_local
};
static const ksensor_ops_t tmp43x_rem1_temp_ops = {
.kso_kind = ksensor_kind_temperature,
.kso_scalar = tmp43x_temp_read_rem1
};
static const ksensor_ops_t tmp43x_rem2_temp_ops = {
.kso_kind = ksensor_kind_temperature,
.kso_scalar = tmp43x_temp_read_rem2
};
static const i2c_reg_acc_attr_t tmp43x_reg_attr = {
.i2cacc_version = I2C_REG_ACC_ATTR_V0,
.i2cacc_addr_len = 1,
.i2cacc_reg_len = 2,
.i2cacc_reg_endian = DDI_STRUCTURE_BE_ACC,
.i2cacc_addr_max = UINT8_MAX
};
static bool
tmp43x_i2c_init(tmp43x_t *tmp)
{
i2c_errno_t err;
if ((err = i2c_client_init(tmp->tmp_dip, 0, &tmp->tmp_client)) !=
I2C_CORE_E_OK) {
dev_err(tmp->tmp_dip, CE_WARN, "failed to create i2c client: "
"0x%x", err);
return (false);
}
if ((err = i2c_reg_handle_init(tmp->tmp_client, &tmp43x_reg_attr,
&tmp->tmp_regs)) != I2C_CORE_E_OK) {
dev_err(tmp->tmp_dip, CE_WARN, "failed to create register "
"handle: %s (0x%x)", i2c_client_errtostr(tmp->tmp_client,
err), err);
return (false);
}
return (true);
}
static bool
tmp43x_ident(tmp43x_t *tmp)
{
uint8_t val;
i2c_error_t err;
if (!smbus_client_read_u8(NULL, tmp->tmp_client, TMP43X_R_MFGID, &val,
&err)) {
dev_err(tmp->tmp_dip, CE_WARN, "!failed to read mfg register: "
"0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
if (val != TMP43X_MFG_TI) {
dev_err(tmp->tmp_dip, CE_WARN, "encountered unsupported vendor "
"id: 0x%x", val);
return (false);
}
if (!smbus_client_read_u8(NULL, tmp->tmp_client, TMP43X_R_DEVID, &val,
&err)) {
dev_err(tmp->tmp_dip, CE_WARN, "!failed to read device "
"id register: 0x%x/0x%x", err.i2c_error, err.i2c_ctrl);
return (false);
}
switch (val) {
case TMP43X_DEV_401:
tmp->tmp_flags = TMP43X_F_LRES;
tmp->tmp_rinfo = tmp43x_regs_401;
tmp->tmp_nrinfo = ARRAY_SIZE(tmp43x_regs_401);
break;
case TMP43X_DEV_411A:
case TMP43X_DEV_411B:
case TMP43X_DEV_411C:
tmp->tmp_flags = TMP43X_F_LRES | TMP43X_F_MINMAX |
TMP43X_F_NFACTOR;
tmp->tmp_rinfo = tmp43x_regs_411;
tmp->tmp_nrinfo = ARRAY_SIZE(tmp43x_regs_411);
break;
case TMP43X_DEV_431:
case TMP43X_DEV_435:
tmp->tmp_flags = TMP43X_F_NFACTOR | TMP43X_F_BETA;
tmp->tmp_rinfo = tmp43x_regs_431_435;
tmp->tmp_nrinfo = ARRAY_SIZE(tmp43x_regs_431_435);
break;
case TMP43X_DEV_432:
tmp->tmp_flags = TMP43X_F_NFACTOR | TMP43X_F_BETA |
TMP43X_F_REM2;
tmp->tmp_rinfo = tmp43x_regs_432;
tmp->tmp_nrinfo = ARRAY_SIZE(tmp43x_regs_432);
break;
default:
dev_err(tmp->tmp_dip, CE_WARN, "encountered unsupported device "
"id: 0x%x", val);
return (false);
}
tmp->tmp_dev = val;
return (true);
}
static bool
tmp43x_start(tmp43x_t *tmp)
{
uint8_t cfg1;
if (!tmp43x_read_ctl(tmp, TMP43X_R_CFG1, &cfg1)) {
return (false);
}
if (TMP43X_CFG1_GET_RANGE(cfg1) == TMP43X_CFG1_RANGE_EXT) {
tmp->tmp_flags |= TMP43X_F_EXT_TEMP;
}
if (TMP43X_CFG1_GET_SD(cfg1) == TMP43X_CFG1_SD_STOP) {
cfg1 = TMP43X_CFG1_SET_SD(cfg1, TMP43X_CFG1_SD_RUN);
if (!tmp43x_write_ctl(tmp, TMP43X_R_CFG1, cfg1)) {
return (false);
}
}
if ((tmp->tmp_flags & TMP43X_F_LRES) != 0) {
uint8_t res, nres;
if (!tmp43x_read_ctl(tmp, TMP43X_R_RES, &res)) {
return (false);
}
nres = TMP43X_RES_SET_RES(res, TMP43X_RES_RES_12B);
if (res != nres && !tmp43x_write_ctl(tmp, TMP43X_R_RES, nres)) {
return (false);
}
}
return (true);
}
static bool
tmp43x_ksensor_init(tmp43x_t *tmp)
{
int ret;
if ((ret = i2c_client_ksensor_create_scalar(tmp->tmp_client,
SENSOR_KIND_TEMPERATURE, &tmp43x_local_temp_ops, tmp, "local",
&tmp->tmp_ksensor[0])) != 0) {
dev_err(tmp->tmp_dip, CE_WARN, "failed to create ksensor: %d",
ret);
return (false);
}
if ((ret = i2c_client_ksensor_create_scalar(tmp->tmp_client,
SENSOR_KIND_TEMPERATURE, &tmp43x_rem1_temp_ops, tmp, "remote1",
&tmp->tmp_ksensor[1])) != 0) {
dev_err(tmp->tmp_dip, CE_WARN, "failed to create ksensor: %d",
ret);
return (false);
}
if ((tmp->tmp_flags & TMP43X_F_REM2) == 0)
return (true);
if ((ret = i2c_client_ksensor_create_scalar(tmp->tmp_client,
SENSOR_KIND_TEMPERATURE, &tmp43x_rem2_temp_ops, tmp, "remote2",
&tmp->tmp_ksensor[2])) != 0) {
dev_err(tmp->tmp_dip, CE_WARN, "failed to create ksensor: %d",
ret);
return (false);
}
return (true);
}
static void
tmp43x_cleanup(tmp43x_t *tmp)
{
(void) ksensor_remove(tmp->tmp_dip, KSENSOR_ALL_IDS);
i2c_reg_handle_destroy(tmp->tmp_regs);
i2c_client_destroy(tmp->tmp_client);
mutex_destroy(&tmp->tmp_mutex);
ddi_set_driver_private(tmp->tmp_dip, NULL);
tmp->tmp_dip = NULL;
kmem_free(tmp, sizeof (tmp43x_t));
}
static int
tmp43x_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
tmp43x_t *tmp;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
tmp = kmem_zalloc(sizeof (tmp43x_t), KM_SLEEP);
tmp->tmp_dip = dip;
ddi_set_driver_private(dip, tmp);
mutex_init(&tmp->tmp_mutex, NULL, MUTEX_DRIVER, NULL);
if (!tmp43x_i2c_init(tmp))
goto cleanup;
if (!tmp43x_ident(tmp))
goto cleanup;
if (!tmp43x_start(tmp))
goto cleanup;
if (!tmp43x_ksensor_init(tmp))
goto cleanup;
return (DDI_SUCCESS);
cleanup:
tmp43x_cleanup(tmp);
return (DDI_FAILURE);
}
static int
tmp43x_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
tmp43x_t *tmp;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
tmp = ddi_get_driver_private(dip);
if (tmp == NULL) {
dev_err(dip, CE_WARN, "asked to detach, but missing private "
"data");
return (DDI_FAILURE);
}
VERIFY3P(tmp->tmp_dip, ==, dip);
tmp43x_cleanup(tmp);
return (DDI_SUCCESS);
}
static struct dev_ops tmp43x_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = tmp43x_attach,
.devo_detach = tmp43x_detach,
.devo_reset = nodev,
.devo_quiesce = ddi_quiesce_not_needed
};
static struct modldrv tmp43x_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "TMP43X driver",
.drv_dev_ops = &tmp43x_dev_ops
};
static struct modlinkage tmp43x_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &tmp43x_modldrv, NULL }
};
int
_init(void)
{
return (mod_install(&tmp43x_modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&tmp43x_modlinkage, modinfop));
}
int
_fini(void)
{
return (mod_remove(&tmp43x_modlinkage));
}