#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ds28e17iic.c,v 1.2 2025/09/15 13:23:03 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <dev/onewire/onewiredevs.h>
#include <dev/onewire/onewirereg.h>
#include <dev/onewire/onewirevar.h>
#include <dev/i2c/i2cvar.h>
#include <dev/onewire/ds28e17iicreg.h>
#include <dev/onewire/ds28e17iicvar.h>
static int ds28e17iic_match(device_t, cfdata_t, void *);
static void ds28e17iic_attach(device_t, device_t, void *);
static int ds28e17iic_detach(device_t, int);
static int ds28e17iic_activate(device_t, enum devact);
static int ds28e17iic_verify_sysctl(SYSCTLFN_ARGS);
#define DS28E17IIC_DEBUG
#ifdef DS28E17IIC_DEBUG
#define DPRINTF(s, l, x) \
do { \
if (l <= s->sc_ds28e17iicdebug) \
printf x; \
} while (0)
#else
#define DPRINTF(s, l, x)
#endif
CFATTACH_DECL_NEW(ds28e17iic, sizeof(struct ds28e17iic_softc),
ds28e17iic_match, ds28e17iic_attach, ds28e17iic_detach, ds28e17iic_activate);
extern struct cfdriver ds28e17iic_cd;
static const struct onewire_matchfam ds28e17iic_fams[] = {
{ ONEWIRE_FAMILY_DS28E17 },
};
#define READY_DELAY(d) if (d > 0) delay(d)
static uint16_t
ds28e17iic_crc16_bit(uint16_t icrc)
{
for (size_t i = 0; i < 8; i++) {
if (icrc & 0x01) {
icrc >>= 1;
icrc ^= 0xA001;
} else {
icrc >>= 1;
}
}
return(icrc);
}
static void
ds28e17iic_crc16(uint8_t crc16[], uint8_t cmd, uint8_t i2c_addr, uint8_t len, uint8_t *data, uint8_t len2)
{
uint16_t crc = 0;
crc ^= cmd;
crc = ds28e17iic_crc16_bit(crc);
if (i2c_addr != 0xff) {
crc ^= i2c_addr;
crc = ds28e17iic_crc16_bit(crc);
}
crc ^= len;
crc = ds28e17iic_crc16_bit(crc);
if (data != NULL) {
for (size_t j = 0; j < len; j++) {
crc ^= data[j];
crc = ds28e17iic_crc16_bit(crc);
}
}
if (len2 > 0) {
crc ^= len2;
crc = ds28e17iic_crc16_bit(crc);
}
crc = crc ^ 0xffff;
crc16[1] = crc >> 8;
crc16[0] = crc & 0xff;
}
int
ds28e17iic_verify_sysctl(SYSCTLFN_ARGS)
{
int error, t;
struct sysctlnode node;
node = *rnode;
t = *(int *)rnode->sysctl_data;
node.sysctl_data = &t;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (t < 0)
return EINVAL;
*(int *)rnode->sysctl_data = t;
return 0;
}
static int
ds28e17iic_acquire_bus(void *v, int flags)
{
return(0);
}
static void
ds28e17iic_release_bus(void *v, int flags)
{
return;
}
static int
ds28e17iic_ow_i2c_transaction(struct ds28e17iic_softc *sc, const char *what, uint8_t device_cmd,
uint8_t i2c_addr, uint8_t len1, uint8_t *buf1, uint8_t len2,
uint8_t crc16[2], uint8_t *i2c_status)
{
int err = 0;
int readycount;
uint8_t ready;
if (onewire_reset(sc->sc_onewire) != 0) {
err = EIO;
} else {
ds28e17iic_crc16(crc16,device_cmd,i2c_addr,len1,buf1,len2);
onewire_matchrom(sc->sc_onewire, sc->sc_rom);
onewire_write_byte(sc->sc_onewire,device_cmd);
if (i2c_addr != 0xff)
onewire_write_byte(sc->sc_onewire,i2c_addr);
onewire_write_byte(sc->sc_onewire,len1);
if (buf1 != NULL)
onewire_write_block(sc->sc_onewire,buf1,len1);
if (len2 > 0)
onewire_write_byte(sc->sc_onewire,len2);
onewire_write_block(sc->sc_onewire,crc16,2);
readycount=0;
do {
READY_DELAY(sc->sc_readydelay);
ready = onewire_read_bit(sc->sc_onewire);
readycount++;
if (readycount > sc->sc_readycount)
err = EAGAIN;
} while (ready != 0 && !err);
DPRINTF(sc, 3, ("%s: readycount=%d, err=%d\n",
what, readycount, err));
*i2c_status = onewire_read_byte(sc->sc_onewire);
}
return(err);
}
static int
ds28e17iic_i2c_write(struct ds28e17iic_softc *sc, i2c_op_t op, i2c_addr_t addr,
const void *cmdbuf, size_t cmdlen, void *databuf, size_t datalen, int flags)
{
uint8_t crc16[2];
uint8_t dcmd;
uint8_t i2c_status;
uint8_t i2c_write_status;
uint8_t *buf;
size_t len;
uint8_t maddr;
int err = 0;
if (sc->sc_dying)
return(EIO);
if (cmdbuf != NULL &&
cmdlen > 256)
return(ENOTSUP);
if (databuf != NULL &&
datalen > 256)
return(ENOTSUP);
if (cmdbuf == NULL &&
databuf == NULL)
return(err);
maddr = addr << 1;
onewire_lock(sc->sc_onewire);
if (cmdbuf != NULL &&
databuf != NULL) {
if (cmdlen == 0 ||
datalen == 0) {
if (sc->sc_reportzerolen)
device_printf(sc->sc_dv,"ds28e17iic_i2c_write: ************ called with zero length read: cmdlen: %zu, datalen: %zu ***************\n",
cmdlen, datalen);
}
err = ds28e17iic_ow_i2c_transaction(sc, "ds28e17iic_i2c_write cmd and data 1",
DS28E17IIC_DC_WD, maddr, cmdlen, __UNCONST(cmdbuf), 0, crc16, &i2c_status);
if (! err) {
i2c_write_status = onewire_read_byte(sc->sc_onewire);
DPRINTF(sc, 2, ("ds28e17iic_i2c_write: both cmd and data 1: i2c_status=%02x, i2c_write_status=%02x\n",
i2c_status, i2c_write_status));
if (i2c_status == 0 &&
i2c_write_status == 0) {
if (I2C_OP_STOP_P(op))
dcmd = DS28E17IIC_DC_WD_ONLY_WITH_STOP;
else
dcmd = DS28E17IIC_DC_WD_ONLY;
err = ds28e17iic_ow_i2c_transaction(sc, "ds28e17iic_i2c_write cmd and data 2",
dcmd, 0xff, datalen, databuf, 0, crc16, &i2c_status);
if (! err) {
i2c_write_status = onewire_read_byte(sc->sc_onewire);
DPRINTF(sc, 2, ("ds28e17iic_i2c_write: both cmd and data 2: dcmd=%02x, i2c_status=%02x, i2c_write_status=%02x\n",
dcmd, i2c_status, i2c_write_status));
if (i2c_status != 0 ||
i2c_write_status != 0)
err = EIO;
} else {
DPRINTF(sc, 2, ("ds28e17iic_i2c_write: cmd and data 2: err=%d\n", err));
}
} else {
err = EIO;
}
} else {
DPRINTF(sc, 2, ("ds28e17iic_i2c_write: cmd and data 1: err=%d\n", err));
}
} else {
if (cmdbuf != NULL) {
buf = __UNCONST(cmdbuf);
len = cmdlen;
} else {
buf = databuf;
len = datalen;
}
if (I2C_OP_STOP_P(op))
dcmd = DS28E17IIC_DC_WD_WITH_STOP;
else
dcmd = DS28E17IIC_DC_WD;
if (len == 0) {
if (sc->sc_reportzerolen)
device_printf(sc->sc_dv,"ds28e17iic_i2c_write: ************ called with zero length read ***************\n");
}
err = ds28e17iic_ow_i2c_transaction(sc, "ds28e17iic_i2c_write cmd or data",
dcmd, maddr, len, buf, 0, crc16, &i2c_status);
if (! err) {
i2c_write_status = onewire_read_byte(sc->sc_onewire);
DPRINTF(sc, 2, ("ds28e17iic_i2c_write: cmd or data: dcmd=%02x, i2c_status=%02x, i2c_write_status=%02x\n",
dcmd, i2c_status, i2c_write_status));
if (i2c_status != 0 ||
i2c_write_status != 0)
err = EIO;
} else {
DPRINTF(sc, 2, ("ds28e17iic_i2c_write: cmd or data: err=%d\n", err));
}
}
onewire_unlock(sc->sc_onewire);
return(err);
}
static int
ds28e17iic_i2c_read(struct ds28e17iic_softc *sc, i2c_op_t op, i2c_addr_t addr,
void *databuf, size_t datalen, int flags)
{
uint8_t crc16[2];
uint8_t i2c_status;
uint8_t maddr;
int err = 0;
if (sc->sc_dying)
return(EIO);
if (databuf != NULL &&
datalen > 256)
return(ENOTSUP);
if (databuf == NULL)
return(err);
maddr = (addr << 1) | 0x01;
onewire_lock(sc->sc_onewire);
if (datalen == 0)
if (sc->sc_reportzerolen)
device_printf(sc->sc_dv,"ds28e17iic_i2c_read: ************ called with zero length read ***************\n");
if (!I2C_OP_STOP_P(op) &&
sc->sc_reportreadnostop)
device_printf(sc->sc_dv,"ds28e17iic_i2c_read: ************ called with READ without STOP ***************\n");
err = ds28e17iic_ow_i2c_transaction(sc, "ds28e17iic_i2c_read",
DS28E17IIC_DC_RD_WITH_STOP, maddr, datalen,
NULL, 0, crc16, &i2c_status);
if (! err) {
DPRINTF(sc, 2, ("ds28e17iic_i2c_read: i2c_status=%02x\n", i2c_status));
if (i2c_status == 0) {
onewire_read_block(sc->sc_onewire, databuf, datalen);
} else {
err = EIO;
}
} else {
DPRINTF(sc, 2, ("ds28e17iic_i2c_read: err=%d\n", err));
}
onewire_unlock(sc->sc_onewire);
return(err);
}
static int
ds28e17iic_i2c_write_read(struct ds28e17iic_softc *sc, i2c_op_t op, i2c_addr_t addr,
const void *cmdbuf, size_t cmdlen, void *databuf, size_t datalen, int flags)
{
uint8_t crc16[2];
uint8_t i2c_status;
uint8_t i2c_write_status;
uint8_t maddr;
int err = 0;
if (sc->sc_dying)
return(EIO);
if (cmdbuf != NULL &&
cmdlen > 256)
return(ENOTSUP);
if (databuf != NULL &&
datalen > 256)
return(ENOTSUP);
if (cmdbuf == NULL &&
databuf == NULL)
return(err);
maddr = addr << 1;
if (cmdlen == 0 ||
datalen == 0) {
if (sc->sc_reportzerolen)
device_printf(sc->sc_dv,"ds28e17iic_i2c_write_read: ************ called with zero length read: cmdlen: %zu, datalen: %zu ***************\n",
cmdlen, datalen);
}
if (!I2C_OP_STOP_P(op) &&
sc->sc_reportreadnostop)
device_printf(sc->sc_dv,"ds28e17iic_i2c_write_read: ************ called with READ without STOP ***************\n");
onewire_lock(sc->sc_onewire);
err = ds28e17iic_ow_i2c_transaction(sc, "ds28e17iic_i2c_write_read",
DS28E17IIC_DC_WD_RD_WITH_STOP, maddr, cmdlen,
__UNCONST(cmdbuf), datalen, crc16, &i2c_status);
if (! err) {
i2c_write_status = onewire_read_byte(sc->sc_onewire);
DPRINTF(sc, 2, ("ds28e17iic_i2c_write_read: i2c_status=%02x, i2c_write_status=%02x\n",
i2c_status, i2c_write_status));
if (i2c_status == 0 &&
i2c_write_status == 0) {
onewire_read_block(sc->sc_onewire, databuf, datalen);
} else {
err = EIO;
}
} else {
DPRINTF(sc, 2, ("ds28e17iic_i2c_write_read: err=%d\n", err));
}
onewire_unlock(sc->sc_onewire);
return(err);
}
static int
ds28e17iic_i2c_exec(void *v, i2c_op_t op, i2c_addr_t addr, const void *cmdbuf,
size_t cmdlen, void *databuf, size_t datalen, int flags)
{
struct ds28e17iic_softc *sc = v;
int err = 0;
if (sc->sc_dying)
return(EIO);
if (addr > 0x7f)
return (ENOTSUP);
if (I2C_OP_WRITE_P(op)) {
err = ds28e17iic_i2c_write(sc, op, addr, cmdbuf, cmdlen, databuf, datalen, flags);
DPRINTF(sc, 2, ("ds28e17iic_exec: I2C WRITE: addr=%02x, err=%d\n", addr, err));
} else {
if (cmdbuf != NULL)
err = ds28e17iic_i2c_write_read(sc, op, addr, cmdbuf, cmdlen, databuf, datalen, flags);
else
err = ds28e17iic_i2c_read(sc, op, addr, databuf, datalen, flags);
DPRINTF(sc, 2, ("ds28e17iic_exec: I2C %s addr=%02x, err=%d\n",
cmdbuf != NULL ? "WRITE-READ" : "READ", addr, err));
}
return(err);
}
static int
ds28e17iic_sysctl_init(struct ds28e17iic_softc *sc)
{
int error;
const struct sysctlnode *cnode;
int sysctlroot_num;
if ((error = sysctl_createv(&sc->sc_ds28e17iiclog, 0, NULL, &cnode,
0, CTLTYPE_NODE, device_xname(sc->sc_dv),
SYSCTL_DESCR("DS28E17IIC controls"), NULL, 0, NULL, 0, CTL_HW,
CTL_CREATE, CTL_EOL)) != 0)
return error;
sysctlroot_num = cnode->sysctl_num;
#ifdef DS28E17IIC_DEBUG
if ((error = sysctl_createv(&sc->sc_ds28e17iiclog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_INT, "debug",
SYSCTL_DESCR("Debug level"), ds28e17iic_verify_sysctl, 0,
&sc->sc_ds28e17iicdebug, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
CTL_EOL)) != 0)
return error;
#endif
if ((error = sysctl_createv(&sc->sc_ds28e17iiclog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_INT, "readycount",
SYSCTL_DESCR("How many times to wait for the I2C transaction to finish"), ds28e17iic_verify_sysctl, 0,
&sc->sc_readycount, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_ds28e17iiclog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_INT, "readydelay",
SYSCTL_DESCR("Delay in microseconds before checking the I2C transaction"), ds28e17iic_verify_sysctl, 0,
&sc->sc_readydelay, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_ds28e17iiclog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_BOOL, "reportreadnostop",
SYSCTL_DESCR("Report that a READ without STOP was attempted by a device"),
NULL, 0, &sc->sc_reportreadnostop, 0, CTL_HW, sysctlroot_num,
CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_ds28e17iiclog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_BOOL, "reportzerolen",
SYSCTL_DESCR("Report that an attempt to read or write zero length was attempted"),
NULL, 0, &sc->sc_reportzerolen, 0, CTL_HW, sysctlroot_num,
CTL_CREATE, CTL_EOL)) != 0)
return error;
return 0;
}
static int
ds28e17iic_match(device_t parent, cfdata_t match, void *aux)
{
return (onewire_matchbyfam(aux, ds28e17iic_fams,
__arraycount(ds28e17iic_fams)));
}
static void
ds28e17iic_attach(device_t parent, device_t self, void *aux)
{
struct ds28e17iic_softc *sc = device_private(self);
struct onewire_attach_args *oa = aux;
uint8_t hardware_rev;
uint8_t i2c_speed[2];
int err = 0;
aprint_normal("\n");
sc->sc_dv = self;
sc->sc_onewire = oa->oa_onewire;
sc->sc_rom = oa->oa_rom;
sc->sc_reportreadnostop = true;
sc->sc_reportzerolen = true;
sc->sc_ds28e17iicdebug = 0;
sc->sc_readycount=20;
sc->sc_readydelay = 10;
if ((err = ds28e17iic_sysctl_init(sc)) != 0) {
aprint_error_dev(self, "Can't setup sysctl tree (%d)\n", err);
return;
}
onewire_lock(sc->sc_onewire);
if (onewire_reset(sc->sc_onewire) != 0) {
aprint_error_dev(sc->sc_dv, "Could not reset the onewire bus for hardware version\n");
onewire_unlock(sc->sc_onewire);
return;
}
onewire_matchrom(sc->sc_onewire, sc->sc_rom);
onewire_write_byte(sc->sc_onewire, DS28E17IIC_DC_DEV_REVISION);
hardware_rev = onewire_read_byte(sc->sc_onewire);
aprint_normal_dev(sc->sc_dv, "Hardware revision: %d\n",
hardware_rev);
if (onewire_reset(sc->sc_onewire) != 0) {
aprint_error_dev(sc->sc_dv, "Could not reset the onewire bus to set I2C speed\n");
onewire_unlock(sc->sc_onewire);
return;
}
onewire_matchrom(sc->sc_onewire, sc->sc_rom);
i2c_speed[0] = DS28E17IIC_DC_WRITE_CONFIG;
i2c_speed[1] = DS28E17IIC_SPEED_100KHZ;
onewire_write_block(sc->sc_onewire, i2c_speed, 2);
onewire_unlock(sc->sc_onewire);
iic_tag_init(&sc->sc_i2c_tag);
sc->sc_i2c_tag.ic_cookie = sc;
sc->sc_i2c_tag.ic_acquire_bus = ds28e17iic_acquire_bus;
sc->sc_i2c_tag.ic_release_bus = ds28e17iic_release_bus;
sc->sc_i2c_tag.ic_exec = ds28e17iic_i2c_exec;
sc->sc_i2c_dev = iicbus_attach(self, &sc->sc_i2c_tag);
}
static int
ds28e17iic_detach(device_t self, int flags)
{
struct ds28e17iic_softc *sc = device_private(self);
int err = 0;
sc->sc_dying = 1;
err = config_detach_children(self, flags);
if (err)
return err;
sysctl_teardown(&sc->sc_ds28e17iiclog);
return 0;
}
static int
ds28e17iic_activate(device_t self, enum devact act)
{
struct ds28e17iic_softc *sc = device_private(self);
switch (act) {
case DVACT_DEACTIVATE:
sc->sc_dying = 1;
return 0;
default:
return EOPNOTSUPP;
}
}
MODULE(MODULE_CLASS_DRIVER, ds28e17iic, "onewire,iic");
#ifdef _MODULE
#include "ioconf.c"
#endif
static int
ds28e17iic_modcmd(modcmd_t cmd, void *opaque)
{
int error;
error = 0;
switch (cmd) {
case MODULE_CMD_INIT:
#ifdef _MODULE
error = config_init_component(cfdriver_ioconf_ds28e17iic,
cfattach_ioconf_ds28e17iic, cfdata_ioconf_ds28e17iic);
if (error)
aprint_error("%s: unable to init component\n",
ds28e17iic_cd.cd_name);
#endif
break;
case MODULE_CMD_FINI:
#ifdef _MODULE
config_fini_component(cfdriver_ioconf_ds28e17iic,
cfattach_ioconf_ds28e17iic, cfdata_ioconf_ds28e17iic);
#endif
break;
default:
error = ENOTTY;
}
return error;
}