#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/cmn_err.h>
#include <sys/stat.h>
#include <sys/sensors.h>
#define PCHTEMP_RNUMBER 1
#define PCHTEMP_TEMP_RESOLUTION 2
#define PCHTEMP_TEMP_OFFSET (50 << 1)
#define PCHTEMP_REG_TEMP 0x00
#define PCHTEMP_REG_TEMP_TSR 0x00ff
#define PCHTEMP_REG_TSEL 0x08
#define PCHTEMP_REG_TSEL_ETS 0x01
#define PCHTEMP_REG_TSEL_PLDB 0x80
#define PCHTEMP_REG_CTT 0x10
#define PCHTEMP_REG_TAHV 0x14
#define PCHTEMP_REG_TALV 0x18
typedef struct pchtemp {
dev_info_t *pcht_dip;
int pcht_fm_caps;
caddr_t pcht_base;
ddi_acc_handle_t pcht_handle;
id_t pcht_ksensor;
kmutex_t pcht_mutex;
uint16_t pcht_temp_raw;
uint8_t pcht_tsel_raw;
uint16_t pcht_ctt_raw;
uint16_t pcht_tahv_raw;
uint16_t pcht_talv_raw;
int64_t pcht_temp;
} pchtemp_t;
void *pchtemp_state;
static int
pchtemp_read_check(pchtemp_t *pch)
{
ddi_fm_error_t de;
if (!DDI_FM_ACC_ERR_CAP(pch->pcht_fm_caps)) {
return (DDI_FM_OK);
}
ddi_fm_acc_err_get(pch->pcht_handle, &de, DDI_FME_VERSION);
ddi_fm_acc_err_clear(pch->pcht_handle, DDI_FME_VERSION);
return (de.fme_status);
}
static int
pchtemp_read(void *arg, sensor_ioctl_scalar_t *scalar)
{
uint16_t temp, ctt, tahv, talv;
uint8_t tsel;
pchtemp_t *pch = arg;
mutex_enter(&pch->pcht_mutex);
temp = ddi_get16(pch->pcht_handle,
(uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TEMP));
tsel = ddi_get8(pch->pcht_handle,
(uint8_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TSEL));
ctt = ddi_get16(pch->pcht_handle,
(uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_CTT));
tahv = ddi_get16(pch->pcht_handle,
(uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TAHV));
talv = ddi_get16(pch->pcht_handle,
(uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TALV));
if (pchtemp_read_check(pch) != DDI_FM_OK) {
mutex_exit(&pch->pcht_mutex);
dev_err(pch->pcht_dip, CE_WARN, "failed to read temperature "
"data due to FM device error");
return (EIO);
}
pch->pcht_temp_raw = temp;
pch->pcht_tsel_raw = tsel;
pch->pcht_ctt_raw = ctt;
pch->pcht_tahv_raw = tahv;
pch->pcht_talv_raw = talv;
if ((tsel & PCHTEMP_REG_TSEL_ETS) == 0) {
mutex_exit(&pch->pcht_mutex);
return (ENXIO);
}
pch->pcht_temp = (temp & PCHTEMP_REG_TEMP_TSR) - PCHTEMP_TEMP_OFFSET;
scalar->sis_unit = SENSOR_UNIT_CELSIUS;
scalar->sis_gran = PCHTEMP_TEMP_RESOLUTION;
scalar->sis_value = pch->pcht_temp;
mutex_exit(&pch->pcht_mutex);
return (0);
}
static const ksensor_ops_t pchtemp_temp_ops = {
.kso_kind = ksensor_kind_temperature,
.kso_scalar = pchtemp_read
};
static void
pchtemp_cleanup(pchtemp_t *pch)
{
int inst;
ASSERT3P(pch->pcht_dip, !=, NULL);
inst = ddi_get_instance(pch->pcht_dip);
(void) ksensor_remove(pch->pcht_dip, KSENSOR_ALL_IDS);
if (pch->pcht_handle != NULL) {
ddi_regs_map_free(&pch->pcht_handle);
}
if (pch->pcht_fm_caps != DDI_FM_NOT_CAPABLE) {
ddi_fm_fini(pch->pcht_dip);
}
mutex_destroy(&pch->pcht_mutex);
ddi_soft_state_free(pchtemp_state, inst);
}
static int
pchtemp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int inst, ret;
pchtemp_t *pch;
off_t memsize;
ddi_device_acc_attr_t da;
ddi_iblock_cookie_t iblk;
char name[1024];
switch (cmd) {
case DDI_RESUME:
return (DDI_SUCCESS);
case DDI_ATTACH:
break;
default:
return (DDI_FAILURE);
}
inst = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(pchtemp_state, inst) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "failed to allocate soft state entry %d",
inst);
return (DDI_FAILURE);
}
pch = ddi_get_soft_state(pchtemp_state, inst);
if (pch == NULL) {
dev_err(dip, CE_WARN, "failed to retrieve soft state entry %d",
inst);
return (DDI_FAILURE);
}
pch->pcht_dip = dip;
pch->pcht_fm_caps = DDI_FM_ACCCHK_CAPABLE;
ddi_fm_init(dip, &pch->pcht_fm_caps, &iblk);
mutex_init(&pch->pcht_mutex, NULL, MUTEX_DRIVER, NULL);
if (ddi_dev_regsize(dip, PCHTEMP_RNUMBER, &memsize) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "failed to obtain register size for "
"register set %d", PCHTEMP_RNUMBER);
goto err;
}
bzero(&da, sizeof (ddi_device_acc_attr_t));
da.devacc_attr_version = DDI_DEVICE_ATTR_V0;
da.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
da.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (DDI_FM_ACC_ERR_CAP(pch->pcht_fm_caps)) {
da.devacc_attr_access = DDI_FLAGERR_ACC;
} else {
da.devacc_attr_access = DDI_DEFAULT_ACC;
}
if ((ret = ddi_regs_map_setup(dip, PCHTEMP_RNUMBER, &pch->pcht_base,
0, memsize, &da, &pch->pcht_handle)) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "failed to map register set %d: %d",
PCHTEMP_RNUMBER, ret);
goto err;
}
if (snprintf(name, sizeof (name), "ts.%d", inst) >= sizeof (name)) {
dev_err(dip, CE_WARN, "failed to construct minor node name, "
"name too long");
goto err;
}
if ((ret = ksensor_create(pch->pcht_dip, &pchtemp_temp_ops, pch, name,
DDI_NT_SENSOR_TEMP_PCH, &pch->pcht_ksensor)) != 0) {
dev_err(dip, CE_WARN, "failed to create minor node %s", name);
goto err;
}
return (DDI_SUCCESS);
err:
pchtemp_cleanup(pch);
return (DDI_FAILURE);
}
static int
pchtemp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int inst;
pchtemp_t *pch;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
inst = ddi_get_instance(dip);
pch = ddi_get_soft_state(pchtemp_state, inst);
if (pch == NULL) {
dev_err(dip, CE_WARN, "asked to detached instance %d, but "
"it does not exist in soft state", inst);
return (DDI_FAILURE);
}
pchtemp_cleanup(pch);
return (DDI_SUCCESS);
}
static struct dev_ops pchtemp_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_getinfo = nodev,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = pchtemp_attach,
.devo_detach = pchtemp_detach,
.devo_reset = nodev,
.devo_quiesce = ddi_quiesce_not_needed
};
static struct modldrv pchtemp_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "Intel PCH Thermal Sensor",
.drv_dev_ops = &pchtemp_dev_ops
};
static struct modlinkage pchtemp_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &pchtemp_modldrv, NULL }
};
int
_init(void)
{
int ret;
if (ddi_soft_state_init(&pchtemp_state, sizeof (pchtemp_t), 1) !=
DDI_SUCCESS) {
return (ENOMEM);
}
if ((ret = mod_install(&pchtemp_modlinkage)) != 0) {
ddi_soft_state_fini(&pchtemp_state);
return (ret);
}
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&pchtemp_modlinkage, modinfop));
}
int
_fini(void)
{
int ret;
if ((ret = mod_remove(&pchtemp_modlinkage)) != 0) {
return (ret);
}
ddi_soft_state_fini(&pchtemp_state);
return (ret);
}