#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sysmacros.h>
#include <sys/bitext.h>
#include <sys/i2c/client.h>
#include <eedev.h>
#define EE1004_GET_PAGE_ADDR 0x36
#define EE1004_SET_PAGE0_ADDR 0x36
#define EE1004_SET_PAGE1_ADDR 0x37
#define EE1004_LEN 512
#define EE1004_SEG 256
typedef struct ee100x {
dev_info_t *ee_dip;
i2c_client_t *ee_mem;
i2c_reg_hdl_t *ee_mem_hdl;
i2c_client_t *ee_spa0;
i2c_client_t *ee_spa1;
eedev_hdl_t *ee_devhdl;
uint8_t ee_buf[I2C_REQ_MAX];
} ee100x_t;
static void *ee100x_state;
static kmutex_t ee100x_spa_mutex;
static int
ee100x_read(void *arg, struct uio *uio, uint32_t page, uint32_t pageoff,
uint32_t nbytes)
{
int ret = 0;
ee100x_t *ee = arg;
i2c_client_t *client;
i2c_txn_t *txn;
i2c_error_t err;
if (page == 0) {
client = ee->ee_spa0;
} else {
client = ee->ee_spa1;
}
mutex_enter(&ee100x_spa_mutex);
if (i2c_bus_lock(client, 0, &txn) != I2C_CORE_E_OK) {
mutex_exit(&ee100x_spa_mutex);
return (EINTR);
}
if (!smbus_client_write_u8(txn, client, 0, 0, &err)) {
dev_err(ee->ee_dip, CE_WARN, "!failed to select page %u: "
"0x%x/0x%x", page, err.i2c_error, err.i2c_ctrl);
ret = EIO;
goto done;
}
if (i2c_reg_get(txn, ee->ee_mem_hdl, pageoff, ee->ee_buf, nbytes,
&err)) {
ret = uiomove(ee->ee_buf, nbytes, UIO_READ, uio);
} else {
dev_err(ee->ee_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(&ee100x_spa_mutex);
return (ret);
}
static const eedev_ops_t ee100x_eedev_ops = {
.eo_read = ee100x_read
};
static bool
ee100x_eedev_init(ee100x_t *ee)
{
int ret;
eedev_reg_t reg;
bzero(®, sizeof (reg));
reg.ereg_vers = EEDEV_REG_VERS;
reg.ereg_size = EE1004_LEN;
reg.ereg_seg = EE1004_SEG;
reg.ereg_read_gran = 1;
reg.ereg_ro = true;
reg.ereg_dip = ee->ee_dip;
reg.ereg_driver = ee;
reg.ereg_name = NULL;
reg.ereg_ops = &ee100x_eedev_ops;
reg.ereg_max_read = MIN(i2c_reg_max_read(ee->ee_mem_hdl),
I2C_REQ_MAX / 2);
if ((ret = eedev_create(®, &ee->ee_devhdl)) != 0) {
dev_err(ee->ee_dip, CE_WARN, "failed to create eedev device: "
"%d", ret);
return (false);
}
return (true);
}
static void
ee100x_cleanup(ee100x_t *ee)
{
eedev_fini(ee->ee_devhdl);
i2c_client_destroy(ee->ee_spa1);
i2c_client_destroy(ee->ee_spa0);
i2c_reg_handle_destroy(ee->ee_mem_hdl);
i2c_client_destroy(ee->ee_mem);
ddi_soft_state_free(ee100x_state, ddi_get_instance(ee->ee_dip));
}
static int
ee100x_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
i2c_errno_t ret;
i2c_addr_t addr;
i2c_reg_acc_attr_t attr;
ee100x_t *ee;
if (cmd == DDI_RESUME) {
return (DDI_SUCCESS);
} else if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
if (ddi_soft_state_zalloc(ee100x_state, ddi_get_instance(dip)) !=
DDI_SUCCESS) {
dev_err(dip, CE_WARN, "failed to alocate soft state");
return (DDI_FAILURE);
}
ee = ddi_get_soft_state(ee100x_state, ddi_get_instance(dip));
if (ee == NULL) {
dev_err(dip, CE_WARN, "failed to obtain soft state after "
"alloc");
return (DDI_FAILURE);
}
ee->ee_dip = dip;
if ((ret = i2c_client_init(ee->ee_dip, 0, &ee->ee_mem)) !=
I2C_CORE_E_OK) {
dev_err(dip, CE_WARN, "failed to initialize memory i2c "
"client: 0x%x", ret);
goto err;
}
bzero(&attr, sizeof (attr));
attr.i2cacc_version = I2C_REG_ACC_ATTR_V0;
attr.i2cacc_addr_len = 1;
attr.i2cacc_reg_len = 1;
attr.i2cacc_addr_max = UINT8_MAX;
if ((ret = i2c_reg_handle_init(ee->ee_mem, &attr, &ee->ee_mem_hdl)) !=
I2C_CORE_E_OK) {
dev_err(dip, CE_WARN, "failed to initialize client handle: "
"0x%x", ret);
goto err;
}
addr.ia_type = I2C_ADDR_7BIT;
addr.ia_addr = EE1004_SET_PAGE0_ADDR;
if ((ret = i2c_client_claim_addr(ee->ee_dip, &addr, I2C_CLAIM_F_SHARED,
&ee->ee_spa0)) != I2C_CORE_E_OK) {
dev_err(dip, CE_WARN, "failed to claim address 0x%x: 0x%x",
addr.ia_addr, ret);
goto err;
}
addr.ia_type = I2C_ADDR_7BIT;
addr.ia_addr = EE1004_SET_PAGE1_ADDR;
if ((ret = i2c_client_claim_addr(ee->ee_dip, &addr, I2C_CLAIM_F_SHARED,
&ee->ee_spa1)) != I2C_CORE_E_OK) {
dev_err(dip, CE_WARN, "failed to claim address 0x%x: 0x%x",
addr.ia_addr, ret);
goto err;
}
if (!ee100x_eedev_init(ee))
goto err;
return (DDI_SUCCESS);
err:
ee100x_cleanup(ee);
return (DDI_FAILURE);
}
static int
ee100x_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **outp)
{
ee100x_t *ee;
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
ee = ddi_get_soft_state(ee100x_state, getminor((dev_t)arg));
if (ee == NULL) {
return (DDI_FAILURE);
}
*outp = ee->ee_dip;
break;
case DDI_INFO_DEVT2INSTANCE:
ee = ddi_get_soft_state(ee100x_state, getminor((dev_t)arg));
if (ee == NULL) {
return (DDI_FAILURE);
}
*outp = (void *)(uintptr_t)ddi_get_instance(ee->ee_dip);
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
ee100x_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
ee100x_t *ee;
if (cmd == DDI_SUSPEND) {
return (DDI_SUCCESS);
} else if (cmd != DDI_DETACH) {
return (DDI_FAILURE);
}
ee = ddi_get_soft_state(ee100x_state, ddi_get_instance(dip));
if (ee == NULL) {
dev_err(dip, CE_WARN, "cannot detach: failed to obtain soft "
"state");
return (DDI_FAILURE);
}
ee100x_cleanup(ee);
return (DDI_SUCCESS);
}
static struct dev_ops ee100x_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_getinfo = ee100x_getinfo,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = ee100x_attach,
.devo_detach = ee100x_detach,
.devo_reset = nodev,
.devo_quiesce = ddi_quiesce_not_needed
};
static struct modldrv ee100x_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "EE1004 Driver",
.drv_dev_ops = &ee100x_dev_ops
};
static struct modlinkage ee100x_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &ee100x_modldrv, NULL }
};
static void
ee100x_globals_fini(void)
{
ddi_soft_state_fini(&ee100x_state);
mutex_destroy(&ee100x_spa_mutex);
}
static int
ee100x_globals_init(void)
{
int ret;
if ((ret = ddi_soft_state_init(&ee100x_state, sizeof (ee100x_t), 0)) !=
0) {
return (ret);
}
mutex_init(&ee100x_spa_mutex, NULL, MUTEX_DRIVER, NULL);
return (0);
}
int
_init(void)
{
int ret;
if ((ret = ee100x_globals_init()) != 0) {
return (ret);
}
if ((ret = mod_install(&ee100x_modlinkage)) != 0) {
ee100x_globals_fini();
}
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&ee100x_modlinkage, modinfop));
}
int
_fini(void)
{
int ret;
if ((ret = mod_remove(&ee100x_modlinkage)) == 0) {
ee100x_globals_fini();
}
return (ret);
}