#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: umcpmio_iic.c,v 1.2 2025/12/12 17:49:35 andvar Exp $");
#ifdef _KERNEL_OPT
#include "opt_usb.h"
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <dev/i2c/i2cvar.h>
#include <dev/usb/umcpmio.h>
#include <dev/usb/umcpmio_hid_reports.h>
#include <dev/usb/umcpmio_transport.h>
#include <dev/usb/umcpmio_iic.h>
#include <dev/usb/umcpmio_subr.h>
#define UMCPMIO_DEBUG 1
#ifdef UMCPMIO_DEBUG
#define DPRINTF(x) do { if (umcpmiodebug) printf x; } while (0)
#define DPRINTFN(n, x) do { if (umcpmiodebug > (n)) printf x; } while (0)
extern int umcpmiodebug;
#else
#define DPRINTF(x) __nothing
#define DPRINTFN(n, x) __nothing
#endif
static void
mcp2221_set_i2c_speed(struct mcp2221_status_req *req, int flags)
{
int i2cbaud = MCP2221_DEFAULT_I2C_SPEED;
if (flags & I2C_F_SPEED)
i2cbaud = 400000;
req->set_i2c_speed = MCP2221_I2C_SET_SPEED;
if (i2cbaud <= 0)
i2cbaud = MCP2221_DEFAULT_I2C_SPEED;
req->i2c_clock_divider = (MCP2221_INTERNAL_CLOCK / i2cbaud) - 3;
}
static int
mcp2221_set_i2c_speed_one(struct umcpmio_softc *sc, int flags)
{
int err = 0;
struct mcp2221_status_req req;
struct mcp2221_status_res res;
KASSERT(mutex_owned(&sc->sc_action_mutex));
memset(&req, 0, MCP2221_REQ_BUFFER_SIZE);
mcp2221_set_i2c_speed(&req, flags);
err = mcp2221_put_status(sc, &req, &res);
if (err)
goto out;
if (res.set_i2c_speed == MCP2221_I2C_SPEED_BUSY)
err = EBUSY;
out:
return err;
}
static int
mcp2221_i2c_clear(struct umcpmio_softc *sc)
{
int err = 0;
struct mcp2221_status_req status_req;
struct mcp2221_status_res status_res;
KASSERT(mutex_owned(&sc->sc_action_mutex));
memset(&status_req, 0, MCP2221_REQ_BUFFER_SIZE);
status_req.cmd = MCP2221_CMD_STATUS;
status_req.cancel_transfer = MCP2221_I2C_DO_CANCEL;
err = umcpmio_send_report(sc,
(uint8_t *) & status_req, MCP2221_REQ_BUFFER_SIZE,
(uint8_t *) & status_res, sc->sc_cv_wait);
if (err) {
err = EIO;
goto out;
}
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) & status_res, MCP2221_RES_BUFFER_SIZE,
"mcp2221_i2c_clear buffer copy");
if (status_res.cmd != MCP2221_CMD_STATUS &&
status_res.completion != MCP2221_CMD_COMPLETE_OK) {
err = EIO;
goto out;
}
umcpmio_dump_buffer(true,
(uint8_t *) & status_res, MCP2221_RES_BUFFER_SIZE,
"mcp2221_i2c_clear res buffer");
out:
return err;
}
static int
mcp2221_acquire_bus(void *v, int flags)
{
return 0;
}
static void
mcp2221_release_bus(void *v, int flags)
{
return;
}
static bool
mcp2221_i2c_fatal(uint8_t state)
{
int r = false;
if (state == MCP2221_ENGINE_ADDRNACK ||
state == MCP2221_ENGINE_STARTTIMEOUT ||
state == MCP2221_ENGINE_REPSTARTTIMEOUT ||
state == MCP2221_ENGINE_STOPTIMEOUT ||
state == MCP2221_ENGINE_READTIMEOUT ||
state == MCP2221_ENGINE_WRITETIMEOUT ||
state == MCP2221_ENGINE_ADDRTIMEOUT)
r = true;
return r;
}
static int
mcp2221_i2c_write(struct umcpmio_softc *sc, i2c_op_t op, i2c_addr_t addr,
const void *cmdbuf, size_t cmdlen, void *databuf, size_t datalen,
int flags)
{
struct mcp2221_i2c_req i2c_req;
struct mcp2221_i2c_res i2c_res;
struct mcp2221_status_res status_res;
int remaining;
int err = 0;
uint8_t cmd;
size_t totallen = 0;
int wretry = sc->sc_retry_busy_write;
int wsretry = sc->sc_retry_busy_write;
mutex_enter(&sc->sc_action_mutex);
err = mcp2221_get_status(sc, &status_res);
mutex_exit(&sc->sc_action_mutex);
if (err)
goto out;
if (status_res.internal_i2c_state != 0) {
DPRINTF(("mcp2221_i2c_write: internal state not zero,"
" clearing. internal_i2c_state=%02x\n",
status_res.internal_i2c_state));
mutex_enter(&sc->sc_action_mutex);
err = mcp2221_i2c_clear(sc);
mutex_exit(&sc->sc_action_mutex);
}
if (err)
goto out;
if (cmdbuf != NULL)
totallen += cmdlen;
if (databuf != NULL)
totallen += datalen;
again:
memset(&i2c_req, 0, MCP2221_REQ_BUFFER_SIZE);
cmd = MCP2221_I2C_WRITE_DATA_NS;
if (I2C_OP_STOP_P(op))
cmd = MCP2221_I2C_WRITE_DATA;
i2c_req.cmd = cmd;
i2c_req.lsblen = totallen;
i2c_req.msblen = 0;
i2c_req.slaveaddr = addr << 1;
remaining = 0;
if (cmdbuf != NULL) {
memcpy(&i2c_req.data[0], cmdbuf, cmdlen);
remaining = cmdlen;
}
if (databuf != NULL)
memcpy(&i2c_req.data[remaining], databuf, datalen);
DPRINTF(("mcp2221_i2c_write: I2C WRITE: cmd: %02x\n", cmd));
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) & i2c_req, MCP2221_REQ_BUFFER_SIZE,
"mcp2221_i2c_write: write req buffer copy");
mutex_enter(&sc->sc_action_mutex);
err = umcpmio_send_report(sc,
(uint8_t *) & i2c_req, MCP2221_REQ_BUFFER_SIZE,
(uint8_t *) & i2c_res, sc->sc_cv_wait);
mutex_exit(&sc->sc_action_mutex);
if (err) {
err = EIO;
goto out;
}
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) & i2c_res, MCP2221_RES_BUFFER_SIZE,
"mcp2221_i2c_write: write res buffer copy");
if (i2c_res.cmd == cmd &&
i2c_res.completion == MCP2221_CMD_COMPLETE_OK) {
} else if (i2c_res.cmd == cmd &&
i2c_res.completion == MCP2221_I2C_ENGINE_BUSY) {
DPRINTF(("mcp2221_i2c_write:"
" I2C engine busy\n"));
if (mcp2221_i2c_fatal(i2c_res.internal_i2c_state)) {
err = EIO;
goto out;
}
wretry--;
if (wretry > 0) {
WAITMS(sc->sc_busy_delay);
goto again;
} else {
err = EBUSY;
goto out;
}
} else {
err = EIO;
goto out;
}
while (wsretry > 0) {
wsretry--;
DPRINTF(("mcp2221_i2c_write: checking status loop:"
" wcretry=%d\n", wsretry));
mutex_enter(&sc->sc_action_mutex);
err = mcp2221_get_status(sc, &status_res);
mutex_exit(&sc->sc_action_mutex);
if (err) {
err = EIO;
break;
}
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) & status_res,
MCP2221_RES_BUFFER_SIZE,
"mcp2221_i2c_write post check status");
if (status_res.internal_i2c_state20 &
MCP2221_ENGINE_T1_MASK_NACK) {
DPRINTF(("mcp2221_i2c_write post check:"
" engine internal state T1 says NACK\n"));
err = EIO;
break;
}
if (status_res.internal_i2c_state == 0) {
DPRINTF(("mcp2221_i2c_write post check:"
" engine internal state is ZERO\n"));
err = 0;
break;
}
if (status_res.internal_i2c_state ==
MCP2221_ENGINE_WRITINGNOSTOP &&
cmd == MCP2221_I2C_WRITE_DATA_NS) {
DPRINTF(("mcp2221_i2c_write post check:"
" engine internal state is WRITINGNOSTOP\n"));
err = 0;
break;
}
if (mcp2221_i2c_fatal(status_res.internal_i2c_state)) {
DPRINTF(("mcp2221_i2c_write post check:"
" engine internal state is fatal: %02x\n",
status_res.internal_i2c_state));
err = EIO;
break;
}
WAITMS(sc->sc_busy_delay);
}
out:
return err;
}
static int
mcp2221_i2c_read(struct umcpmio_softc *sc, i2c_op_t op, i2c_addr_t addr,
const void *cmdbuf, size_t cmdlen, void *databuf, size_t datalen, int
flags)
{
struct mcp2221_i2c_req i2c_req;
struct mcp2221_i2c_res i2c_res;
struct mcp2221_i2c_fetch_req i2c_fetch_req;
struct mcp2221_i2c_fetch_res i2c_fetch_res;
struct mcp2221_status_res status_res;
int err = 0;
uint8_t cmd;
int rretry = sc->sc_retry_busy_read;
if (cmdbuf != NULL) {
DPRINTF(("mcp2221_i2c_read: has a cmdbuf, doing write first:"
" addr=%02x\n", addr));
err = mcp2221_i2c_write(sc, I2C_OP_WRITE, addr, cmdbuf, cmdlen,
NULL, 0, flags);
}
if (err)
goto out;
mutex_enter(&sc->sc_action_mutex);
err = mcp2221_get_status(sc, &status_res);
mutex_exit(&sc->sc_action_mutex);
if (err)
goto out;
if (status_res.internal_i2c_state != 0 &&
status_res.internal_i2c_state != MCP2221_ENGINE_WRITINGNOSTOP) {
DPRINTF(("mcp2221_i2c_read:"
" internal state not zero and not WRITINGNOSTOP,"
" clearing. internal_i2c_state=%02x\n",
status_res.internal_i2c_state));
mutex_enter(&sc->sc_action_mutex);
err = mcp2221_i2c_clear(sc);
mutex_exit(&sc->sc_action_mutex);
}
if (err)
goto out;
memset(&i2c_req, 0, MCP2221_REQ_BUFFER_SIZE);
if (cmdbuf == NULL &&
status_res.internal_i2c_state != MCP2221_ENGINE_WRITINGNOSTOP) {
cmd = MCP2221_I2C_READ_DATA;
} else {
cmd = MCP2221_I2C_READ_DATA_RS;
}
if (!I2C_OP_STOP_P(op) && sc->sc_reportreadnostop) {
device_printf(sc->sc_dev,
"mcp2221_i2c_read: ************ called with READ"
" without STOP ***************\n");
}
i2c_req.cmd = cmd;
i2c_req.lsblen = datalen;
i2c_req.msblen = 0;
i2c_req.slaveaddr = (addr << 1) | 0x01;
DPRINTF(("mcp2221_i2c_read: I2C READ normal read:"
" cmd=%02x, addr=%02x\n", cmd, addr));
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) & i2c_req, MCP2221_RES_BUFFER_SIZE,
"mcp2221_i2c_read normal read req buffer copy");
mutex_enter(&sc->sc_action_mutex);
err = umcpmio_send_report(sc,
(uint8_t *) & i2c_req, MCP2221_REQ_BUFFER_SIZE,
(uint8_t *) & i2c_res, sc->sc_cv_wait);
mutex_exit(&sc->sc_action_mutex);
if (err) {
err = EIO;
goto out;
}
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) & i2c_res, MCP2221_RES_BUFFER_SIZE,
"mcp2221_i2c_read read-request response buffer copy");
while (rretry > 0) {
rretry--;
DPRINTF(("mcp2221_i2c_read: fetch loop: rretry=%d\n", rretry));
err = 0;
memset(&i2c_fetch_req, 0, MCP2221_REQ_BUFFER_SIZE);
i2c_fetch_req.cmd = MCP2221_CMD_I2C_FETCH_READ_DATA;
mutex_enter(&sc->sc_action_mutex);
err = umcpmio_send_report(sc,
(uint8_t *) & i2c_fetch_req, MCP2221_REQ_BUFFER_SIZE,
(uint8_t *) & i2c_fetch_res, sc->sc_cv_wait);
mutex_exit(&sc->sc_action_mutex);
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) & i2c_fetch_req, MCP2221_RES_BUFFER_SIZE,
"mcp2221_i2c_read fetch res buffer copy");
if (i2c_fetch_res.cmd != MCP2221_CMD_I2C_FETCH_READ_DATA) {
err = EIO;
break;
}
if (i2c_fetch_res.completion ==
MCP2221_FETCH_READ_PARTIALDATA ||
i2c_fetch_res.fetchlen == MCP2221_FETCH_READERROR) {
DPRINTF(("mcp2221_i2c_read: fetch loop:"
" partial data or read error:"
" completion=%02x, fetchlen=%02x\n",
i2c_fetch_res.completion,
i2c_fetch_res.fetchlen));
WAITMS(sc->sc_busy_delay);
err = EAGAIN;
continue;
}
if (i2c_fetch_res.internal_i2c_state ==
MCP2221_ENGINE_ADDRNACK) {
DPRINTF(("mcp2221_i2c_read:"
" fetch loop: engine NACK\n"));
err = EIO;
break;
}
if (i2c_fetch_res.internal_i2c_state == 0 &&
i2c_fetch_res.fetchlen == 0) {
DPRINTF(("mcp2221_i2c_read: fetch loop:"
" internal state and fetch len are ZERO\n"));
err = 0;
break;
}
if (i2c_fetch_res.internal_i2c_state ==
MCP2221_ENGINE_READPARTIAL ||
i2c_fetch_res.internal_i2c_state ==
MCP2221_ENGINE_READCOMPLETE) {
DPRINTF(("mcp2221_i2c_read:"
" fetch loop: read partial or"
" read complete: internal_i2c_state=%02x\n",
i2c_fetch_res.internal_i2c_state));
err = 0;
break;
}
}
if (err == EAGAIN)
err = ETIMEDOUT;
if (err)
goto out;
if (databuf == NULL ||
i2c_fetch_res.fetchlen == MCP2221_FETCH_READERROR) {
DPRINTF(("mcp2221_i2c_read: copy data:"
" databuf is NULL\n"));
goto out;
}
const int size = uimin(i2c_fetch_res.fetchlen, datalen);
DPRINTF(("mcp2221_i2c_read: copy data: size=%d, fetchlen=%d\n",
size, i2c_fetch_res.fetchlen));
if (size > 0) {
memcpy(databuf, &i2c_fetch_res.data[0], size);
}
out:
return err;
}
static int
mcp2221_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 umcpmio_softc *sc = v;
size_t totallen = 0;
int err = 0;
if (sc->sc_dying)
return EIO;
if (addr > 0x7f)
return ENOTSUP;
if (cmdbuf != NULL)
totallen += cmdlen;
if (databuf != NULL)
totallen += datalen;
if (totallen > 60)
return ENOTSUP;
if (I2C_OP_WRITE_P(op)) {
err = mcp2221_i2c_write(sc, op, addr, cmdbuf, cmdlen,
databuf, datalen, flags);
DPRINTF(("umcpmio_exec: I2C WRITE: err=%d\n", err));
} else {
err = mcp2221_i2c_read(sc, op, addr, cmdbuf, cmdlen,
databuf, datalen, flags);
DPRINTF(("umcpmio_exec: I2C READ: err=%d\n", err));
}
return err;
}
int
umcpmio_i2c_attach(struct umcpmio_softc *sc)
{
sc->sc_reportreadnostop = true;
sc->sc_busy_delay = 1;
sc->sc_retry_busy_read = 50;
sc->sc_retry_busy_write = 50;
int err = 0;
int i2cspeed;
for (i2cspeed = 0; i2cspeed < 3; i2cspeed++) {
mutex_enter(&sc->sc_action_mutex);
err = mcp2221_set_i2c_speed_one(sc, I2C_SPEED_SM);
mutex_exit(&sc->sc_action_mutex);
if (err) {
aprint_error_dev(sc->sc_dev, "umcpmio_i2c_attach:"
" set I2C speed: err=%d\n",
err);
delay(300);
}
break;
}
iic_tag_init(&sc->sc_i2c_tag);
sc->sc_i2c_tag.ic_cookie = sc;
sc->sc_i2c_tag.ic_acquire_bus = mcp2221_acquire_bus;
sc->sc_i2c_tag.ic_release_bus = mcp2221_release_bus;
sc->sc_i2c_tag.ic_exec = mcp2221_i2c_exec;
sc->sc_i2c_dev = iicbus_attach(sc->sc_dev, &sc->sc_i2c_tag);
return err;
}