#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: umcpmio_spi.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/spi/spivar.h>
#include <dev/usb/umcpmio.h>
#include <dev/usb/umcpmio_hid_reports.h>
#include <dev/usb/umcpmio_transport.h>
#include <dev/usb/umcpmio_spi.h>
#include <dev/usb/umcpmio_gpio.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 int
mcp2210_get_spi_sram(struct umcpmio_softc *sc,
struct mcp2210_get_spi_sram_res *res)
{
struct mcp2210_get_spi_sram_req req;
int err = 0;
memset(&req, 0, MCP2210_REQ_BUFFER_SIZE);
req.cmd = MCP2210_CMD_GET_SPI_SRAM;
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) & req, MCP2210_REQ_BUFFER_SIZE,
"mcp2210_get_spi_sram req");
mutex_enter(&sc->sc_action_mutex);
err = umcpmio_send_report(sc,
(uint8_t *) & req, MCP2210_REQ_BUFFER_SIZE,
(uint8_t *) res, sc->sc_cv_wait);
mutex_exit(&sc->sc_action_mutex);
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) res, MCP2210_RES_BUFFER_SIZE,
"mcp2210_get_spi_sram res");
err = mcp2210_decode_errors(req.cmd, err, res->completion);
return err;
}
static int
mcp2210_set_spi_sram(struct umcpmio_softc *sc,
struct mcp2210_set_spi_sram_req *req, struct mcp2210_set_spi_sram_res *res)
{
int err = 0;
req->cmd = MCP2210_CMD_SET_SPI_SRAM;
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) req, MCP2210_REQ_BUFFER_SIZE,
"mcp2210_set_spi_sram req");
mutex_enter(&sc->sc_action_mutex);
err = umcpmio_send_report(sc,
(uint8_t *) req, MCP2210_REQ_BUFFER_SIZE,
(uint8_t *) res, sc->sc_cv_wait);
mutex_exit(&sc->sc_action_mutex);
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) res, MCP2210_RES_BUFFER_SIZE,
"mcp2210_set_spi_sram res");
err = mcp2210_decode_errors(req->cmd, err, res->completion);
return err;
}
static int
mcp2210_do_spi_transfer(struct umcpmio_softc *sc,
struct mcp2210_spi_transfer_req *req, struct mcp2210_spi_transfer_res *res)
{
int err = 0;
req->cmd = MCP2210_CMD_SPI_TRANSFER;
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) req, MCP2210_REQ_BUFFER_SIZE,
"mcp2210_do_spi_transfer req");
mutex_enter(&sc->sc_action_mutex);
err = umcpmio_send_report(sc,
(uint8_t *) req, MCP2210_REQ_BUFFER_SIZE,
(uint8_t *) res, sc->sc_cv_wait);
mutex_exit(&sc->sc_action_mutex);
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) res, MCP2210_RES_BUFFER_SIZE,
"mcp2210_do_spi_transfer res");
err = mcp2210_decode_errors(req->cmd, err, res->completion);
return err;
}
static int
mcp2210_spi_configure(void *cookie, int slave, int mode, int speed)
{
struct umcpmio_softc *sc = cookie;
uint8_t desired_mode = 0;
struct umcpmio_spi_received *r;
int err = 0;
if (slave >= sc->sc_spi.sct_nslaves)
return EINVAL;
if (speed < 1500) {
aprint_error_dev(sc->sc_dev,
"Speed %d is too low and not supported.", speed);
return EINVAL;
}
mutex_enter(&sc->sc_spi_mutex);
umcpmio_gpio_pin_ctl(sc, slave, GPIO_PIN_ALT0);
switch (mode) {
case SPI_MODE_0:
desired_mode = MCP2210_SPI_MODE_0;
break;
case SPI_MODE_1:
desired_mode = MCP2210_SPI_MODE_1;
break;
case SPI_MODE_2:
desired_mode = MCP2210_SPI_MODE_2;
break;
case SPI_MODE_3:
desired_mode = MCP2210_SPI_MODE_3;
break;
default:
err = EINVAL;
}
if (!err) {
sc->sc_slave_configs[slave].bit_rate = (uint32_t) speed;
sc->sc_slave_configs[slave].mode = desired_mode;
if (!SIMPLEQ_EMPTY(&sc->sc_received[slave])) {
while ((r = SIMPLEQ_FIRST(&sc->sc_received[slave])) != NULL) {
SIMPLEQ_REMOVE_HEAD(&sc->sc_received[slave], umcpmio_spi_received_q);
kmem_free(r, sizeof(struct umcpmio_spi_received));
}
}
}
mutex_exit(&sc->sc_spi_mutex);
return err;
}
static int
mcp2210_spi_set_slave_config(struct umcpmio_softc *sc,
uint16_t max_transfer_size, int slave)
{
struct mcp2210_get_spi_sram_res get_res;
struct mcp2210_set_spi_sram_req set_req;
struct mcp2210_set_spi_sram_res set_res;
uint16_t idle;
uint16_t active;
int err = 0;
err = mcp2210_get_spi_sram(sc, &get_res);
if (err)
goto out;
memset(&set_res, 0, MCP2210_RES_BUFFER_SIZE);
memcpy(&set_req, &get_res, MCP2210_REQ_BUFFER_SIZE);
idle = 0xffff;
active = ~(1 << slave);
set_req.idle_cs_value_lsb = (uint8_t)(idle & 0xff);
set_req.idle_cs_value_msb = (uint8_t)(idle >> 8);
set_req.active_cs_value_lsb = (uint8_t)(active & 0xff);
set_req.active_cs_value_msb = (uint8_t)(active >> 8);
set_req.spi_mode = sc->sc_slave_configs[slave].mode;
uint32_t s = sc->sc_slave_configs[slave].bit_rate;
set_req.bit_rate_byte_3 = (0x000000ff & s);
set_req.bit_rate_byte_2 |= (0x000000ff & (s >> 8));
set_req.bit_rate_byte_1 |= (0x000000ff & (s >> 16));
set_req.bit_rate_byte_0 |= (0x000000ff & (s >> 24));
set_req.bytes_per_spi_transaction_lsb = (uint8_t)(max_transfer_size & 0xff);
set_req.bytes_per_spi_transaction_msb = (uint8_t)(max_transfer_size >> 8);
err = mcp2210_set_spi_sram(sc, &set_req, &set_res);
out:
DPRINTF(("mcp2210_spi_set_slave exit: err=%d\n", err));
return err;
}
static void
mcp2210_spi_tx_fill(struct umcpmio_softc *sc,
struct mcp2210_spi_transfer_req *req)
{
struct spi_chunk *chunk = sc->sc_wchunk;
size_t len;
if (chunk == NULL)
return;
if (sc->sc_dying)
return;
len = MIN(MCP2210_MAX_SPI_BYTES, chunk->chunk_wresid);
DPRINTF(("mcp2210_spi_tx_fill: len=%zd; chunk_wresid=%d\n",
len, chunk->chunk_wresid));
chunk->chunk_wresid -= len;
req->num_send_bytes += len;
if (chunk->chunk_wptr)
memcpy(&req->send_bytes[0], chunk->chunk_wptr, len);
DPRINTF(("mcp2210_spi_tx_fill: checking sending values.. req->num_send_bytes=%d, chunk_wresid=%d\n",
req->num_send_bytes, chunk->chunk_wresid));
if (chunk->chunk_wresid == 0) {
DPRINTF(("mcp2210_spi_tx_fill: moving onto the next chunk\n"));
sc->sc_wchunk = sc->sc_wchunk->chunk_next;
if (sc->sc_wchunk)
DPRINTF(("mcp2210_spi_tx_fill: new chunk EXISTS\n"));
else
DPRINTF(("mcp2210_spi_tx_fill: new chunk does not EXIST, NULL\n"));
}
}
static int
mcp2210_spi_rx_drain(struct umcpmio_softc *sc, int slave,
struct mcp2210_spi_transfer_res *res)
{
struct spi_chunk *chunk = sc->sc_rchunk;
size_t len;
struct umcpmio_spi_received *r;
int err = 0;
if (chunk == NULL)
return err;
if (sc->sc_dying)
return err;
len = MIN(res->num_receive_bytes, chunk->chunk_rresid);
chunk->chunk_rresid -= len;
DPRINTF(("mcp2210_spi_rx_drain: ENTER: len=%zd; res->num_receive_bytes=%d; chunk_rresid=%d\n",
len, res->num_receive_bytes, chunk->chunk_rresid));
if (chunk->chunk_rptr)
memcpy(chunk->chunk_rptr, &res->receive_bytes[0], len);
if (chunk->chunk_rresid == 0) {
DPRINTF(("mcp2210_spi_rx_drain: moving onto the next chunk\n"));
sc->sc_rchunk = sc->sc_rchunk->chunk_next;
if (sc->sc_rchunk)
DPRINTF(("mcp2210_spi_rx_drain: new chunk EXISTS\n"));
else
DPRINTF(("mcp2210_spi_rx_drain: new chunk does not EXIST, NULL\n"));
}
DPRINTF(("mcp2210_spi_rx_drain: checking received values.. res->num_received_bytes=%d\n",
res->num_receive_bytes));
if (len < res->num_receive_bytes) {
DPRINTF(("mcp2210_spi_rx_drain: did not consume all that was received, Q it up.... res->num_received_bytes=%d\n",
res->num_receive_bytes));
r = kmem_alloc(sizeof(struct umcpmio_spi_received), KM_NOSLEEP);
if (r != NULL) {
memcpy(&r->receive_bytes[0], &res->receive_bytes[len], res->num_receive_bytes - len);
r->num_receive_bytes = res->num_receive_bytes - len;
SIMPLEQ_INSERT_HEAD(&sc->sc_received[slave], r, umcpmio_spi_received_q);
} else {
err = ENOMEM;
}
}
return err;
}
static int
mcp2210_spi_rx_q(struct umcpmio_softc *sc, int slave,
struct umcpmio_spi_received *r)
{
struct spi_chunk *chunk = sc->sc_rchunk;
size_t len;
struct umcpmio_spi_received *mr;
int err = 0;
if (chunk == NULL)
return err;
if (sc->sc_dying)
return err;
len = MIN(r->num_receive_bytes, chunk->chunk_rresid);
chunk->chunk_rresid -= len;
DPRINTF(("mcp2210_spi_rx_q: ENTER: len=%zd; r->num_receive_bytes=%d; chunk_rresid=%d\n",
len, r->num_receive_bytes, chunk->chunk_rresid));
if (chunk->chunk_rptr)
memcpy(chunk->chunk_rptr, &r->receive_bytes[0], len);
if (chunk->chunk_rresid == 0) {
DPRINTF(("mcp2210_spi_rx_q: moving onto the next chunk\n"));
sc->sc_rchunk = sc->sc_rchunk->chunk_next;
if (sc->sc_rchunk)
DPRINTF(("mcp2210_spi_rx_q: new chunk EXISTS\n"));
else
DPRINTF(("mcp2210_spi_rx_q: new chunk does not EXIST, NULL\n"));
}
DPRINTF(("mcp2210_spi_rx_q: checking received values.. r->num_received_bytes=%d\n",
r->num_receive_bytes));
if (len < r->num_receive_bytes) {
DPRINTF(("mcp2210_spi_rx_q: did not consume all that was received, Q it up... r->num_received_bytes=%d\n",
r->num_receive_bytes));
mr = kmem_alloc(sizeof(struct umcpmio_spi_received), KM_NOSLEEP);
if (mr != NULL) {
memcpy(&mr->receive_bytes[0], &r->receive_bytes[len], r->num_receive_bytes - len);
mr->num_receive_bytes = r->num_receive_bytes - len;
SIMPLEQ_INSERT_HEAD(&sc->sc_received[slave], mr, umcpmio_spi_received_q);
} else {
err = ENOMEM;
}
}
return err;
}
static int
mcp2210_spi_rx_drainchip(struct umcpmio_softc *sc, int slave,
struct mcp2210_spi_transfer_res *res)
{
int err = 0;
struct umcpmio_spi_received *r;
r = kmem_alloc(sizeof(struct umcpmio_spi_received), KM_NOSLEEP);
if (r == NULL)
return ENOMEM;
memcpy(&r->receive_bytes[0], &res->receive_bytes[0], res->num_receive_bytes);
r->num_receive_bytes = res->num_receive_bytes;
SIMPLEQ_INSERT_TAIL(&sc->sc_received[slave], r, umcpmio_spi_received_q);
return err;
}
static int
mcp2210_spi_txrx(struct umcpmio_softc *sc, int slave)
{
struct mcp2210_spi_transfer_req req;
struct mcp2210_spi_transfer_res res;
struct umcpmio_spi_received *r;
int err = 0;
int wretry = sc->sc_retry_busy_write;
DPRINTF(("mcp2210_spi_txrx: ENTER\n"));
if (sc->sc_dying)
return err;
if (!SIMPLEQ_EMPTY(&sc->sc_received[slave]) &&
sc->sc_rchunk != NULL) {
DPRINTF(("mcp2210_spi_txrx: previous received bytes to send to upstream\n"));
while ((r = SIMPLEQ_FIRST(&sc->sc_received[slave])) != NULL) {
SIMPLEQ_REMOVE_HEAD(&sc->sc_received[slave], umcpmio_spi_received_q);
err = mcp2210_spi_rx_q(sc, slave, r);
kmem_free(r, sizeof(struct umcpmio_spi_received));
if (err)
break;
}
goto out;
}
if (sc->sc_dying)
return err;
memset(&req, 0, UMCPMIO_REQ_BUFFER_SIZE);
if (sc->sc_wchunk != NULL)
DPRINTF(("mcp2210_spi_txrx: filling up req for write transfer; chunk_wresid=%d\n", sc->sc_wchunk->chunk_wresid));
else
DPRINTF(("mcp2210_spi_txrx: filling up req for write transfer; wchunk is NULL\n"));
mcp2210_spi_tx_fill(sc, &req);
retry:
memset(&res, 0, UMCPMIO_RES_BUFFER_SIZE);
if (sc->sc_dying)
return err;
err = mcp2210_do_spi_transfer(sc, &req, &res);
if (err)
if (err != EBUSY)
goto out;
DPRINTF(("mcp2210_spi_txrx: did transfer res.completion=%02x ;"
"res.spi_engine_status=%02x, res.num_receive_bytes=%d\n",
res.completion, res.spi_engine_status, res.num_receive_bytes));
if (!err) {
if (res.spi_engine_status == MCP2210_SPI_STATUS_DATA &&
res.num_receive_bytes == 0) {
err = ECANCELED;
}
}
if (err == EBUSY) {
DPRINTF(("mcp2210_spi_txrx: transfer is busy\n"));
wretry--;
if (wretry > 0) {
WAITMS(sc->sc_busy_delay);
goto retry;
} else {
err = EIO;
goto out;
}
}
if (res.spi_engine_status == MCP2210_SPI_STATUS_NO_DATA_YET) {
DPRINTF(("mcp2210_spi_txrx: transfer is NO DATA YET\n"));
wretry--;
if (wretry > 0) {
WAITMS(sc->sc_busy_delay);
memset(&req, 0, UMCPMIO_REQ_BUFFER_SIZE);
goto retry;
} else {
err = EIO;
goto out;
}
}
DPRINTF(("mcp2210_spi_txrx: DOING RECEIVE: res.num_receive_bytes=%d\n",
res.num_receive_bytes));
if (res.num_receive_bytes > 0) {
if (sc->sc_rchunk != NULL &&
sc->sc_rchunk->chunk_rptr) {
DPRINTF(("mcp2210_spi_txrx: send data to upstream\n"));
err = mcp2210_spi_rx_drain(sc, slave, &res);
} else {
DPRINTF(("mcp2210_spi_txrx: drain chip, send data to Q\n"));
err = mcp2210_spi_rx_drainchip(sc, slave, &res);
}
}
out:
DPRINTF(("mcp2210_spi_txrx exit. err=%d\n", err));
return err;
}
int
mcp2210_cancel_spi_transfer(struct umcpmio_softc *sc,
struct mcp2210_cancel_spi_res *res)
{
struct mcp2210_cancel_spi_req req;
int err = 0;
KASSERT(mutex_owned(&sc->sc_action_mutex));
memset(&req, 0, MCP2210_REQ_BUFFER_SIZE);
req.cmd = MCP2210_CMD_SPI_CANCEL;
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) & req, MCP2210_REQ_BUFFER_SIZE,
"mcp2210_cancel_spi_transfer req");
err = umcpmio_send_report(sc,
(uint8_t *) & req, MCP2210_REQ_BUFFER_SIZE,
(uint8_t *) res, sc->sc_cv_wait);
umcpmio_dump_buffer(sc->sc_dumpbuffer,
(uint8_t *) res, MCP2210_RES_BUFFER_SIZE,
"mcp2210_get_spi_sram res");
err = mcp2210_decode_errors(req.cmd, err, res->completion);
return err;
}
static void
mcp2210_spi_poll(struct umcpmio_softc *sc,
int slave, int *stuckcount)
{
struct spi_transfer *st;
struct mcp2210_cancel_spi_res cancel_res;
int err = 0;
err = mcp2210_spi_txrx(sc, slave);
DPRINTF(("mcp2210_spi_poll: err from transfer: err=%d\n", err));
if (err == ECANCELED) {
int s;
s = *stuckcount;
s--;
DPRINTF(("mcp2210_spi_poll: STUCK s=%d\n", s));
if (s > 0)
err = 0;
*stuckcount = s;
}
if (err) {
if (err == ECANCELED) {
if (sc->sc_spi_verbose)
device_printf(sc->sc_dev, "The SPI transfer may be stuck. Canceling.\n");
mutex_enter(&sc->sc_action_mutex);
mcp2210_cancel_spi_transfer(sc, &cancel_res);
mutex_exit(&sc->sc_action_mutex);
}
sc->sc_rchunk = sc->sc_wchunk = NULL;
}
if (sc->sc_rchunk == NULL &&
sc->sc_wchunk == NULL) {
st = sc->sc_transfer;
sc->sc_transfer = NULL;
KASSERT(st != NULL);
spi_done(st, 0);
sc->sc_running = false;
}
return;
}
static void
mcp2210_spi_start(struct umcpmio_softc *sc)
{
struct spi_transfer *st;
struct spi_chunk *chunk;
uint16_t max_transfer_size = 0;
int transfer_size = 0;
int err = 0;
int stuckcount = 4;
struct mcp2210_cancel_spi_res cancel_res;
while ((st = spi_transq_first(&sc->sc_q)) != NULL) {
spi_transq_dequeue(&sc->sc_q);
KASSERT(sc->sc_transfer == NULL);
sc->sc_transfer = st;
sc->sc_rchunk = sc->sc_wchunk = st->st_chunks;
sc->sc_running = true;
KASSERT(st->st_slave < sc->sc_spi.sct_nslaves);
chunk = st->st_chunks;
while (chunk != NULL) {
if (chunk->chunk_wresid > transfer_size)
transfer_size = chunk->chunk_wresid;
if (chunk->chunk_rresid > transfer_size)
transfer_size = chunk->chunk_rresid;
chunk = chunk->chunk_next;
}
KASSERT(transfer_size > 0);
if (transfer_size > 65534) {
if (sc->sc_spi_verbose)
device_printf(sc->sc_dev, "Canceling transfer. "
"The size of the requested transfer is too big. "
"transfer_size=%d\n",
transfer_size);
st = sc->sc_transfer;
sc->sc_transfer = NULL;
spi_done(st, 0);
sc->sc_running = false;
return;
}
max_transfer_size = (uint16_t) transfer_size + 1;
DPRINTF(("mcp2210_spi_start: transfer_size=%d; max_transfer_size=%d\n", transfer_size, max_transfer_size));
err = mcp2210_spi_set_slave_config(sc, max_transfer_size, st->st_slave);
if (err == EBUSY) {
mutex_enter(&sc->sc_action_mutex);
mcp2210_cancel_spi_transfer(sc, &cancel_res);
mutex_exit(&sc->sc_action_mutex);
err = mcp2210_spi_set_slave_config(sc, max_transfer_size, st->st_slave);
}
if (err) {
if (sc->sc_spi_verbose)
device_printf(sc->sc_dev, "Canceling transfer. Error setting up slave config. error=%d\n", err);
st = sc->sc_transfer;
sc->sc_transfer = NULL;
spi_done(st, 0);
sc->sc_running = false;
return;
}
for (;;) {
mcp2210_spi_poll(sc, st->st_slave, &stuckcount);
if (ISSET(st->st_flags, SPI_F_DONE))
break;
if (sc->sc_dying)
break;
}
}
sc->sc_running = false;
return;
}
static int
mcp2210_spi_transfer(void *cookie, struct spi_transfer *st)
{
struct umcpmio_softc *sc = cookie;
mutex_enter(&sc->sc_spi_mutex);
spi_transq_enqueue(&sc->sc_q, st);
if (sc->sc_running == false) {
mcp2210_spi_start(sc);
}
mutex_exit(&sc->sc_spi_mutex);
return 0;
}
int
umcpmio_spi_attach(struct umcpmio_softc *sc)
{
struct mcp2210_get_spi_sram_res get_res;
struct mcp2210_set_spi_sram_req set_req;
struct mcp2210_set_spi_sram_res set_res;
struct mcp2210_cancel_spi_res cancel_res;
int err = 0;
sc->sc_spi_verbose = true;
sc->sc_busy_delay = 0;
sc->sc_retry_busy_read = 50;
sc->sc_retry_busy_write = 50;
sc->sc_running = false;
DPRINTF(("umcpmio_spi_attach: sc->sc_chipinfo->num_spi_slaves=%d\n", sc->sc_chipinfo->num_spi_slaves));
SIMPLEQ_INIT(&sc->sc_q);
for (int i = 0; i < sc->sc_chipinfo->num_spi_slaves; i++){
SIMPLEQ_INIT(&sc->sc_received[i]);
sc->sc_slave_configs[i].bit_rate = (uint32_t) SPI_FREQ_MHz(1);
sc->sc_slave_configs[i].mode = MCP2210_SPI_MODE_0;
}
err = mcp2210_get_spi_sram(sc, &get_res);
if (err) {
aprint_error_dev(sc->sc_dev,
"%s: unable to read SPI sram: %d\n",
__func__, err);
goto out;
}
memset(&set_res, 0, MCP2210_RES_BUFFER_SIZE);
memcpy(&set_req, &get_res, MCP2210_REQ_BUFFER_SIZE);
set_req.idle_cs_value_lsb = set_req.idle_cs_value_msb = 0xff;
set_req.active_cs_value_lsb = set_req.active_cs_value_msb = 0x00;
err = mcp2210_set_spi_sram(sc, &set_req, &set_res);
if (err == EBUSY) {
mutex_enter(&sc->sc_action_mutex);
mcp2210_cancel_spi_transfer(sc, &cancel_res);
mutex_exit(&sc->sc_action_mutex);
err = mcp2210_set_spi_sram(sc, &set_req, &set_res);
}
if (err) {
aprint_error_dev(sc->sc_dev,
"%s: unable to set SPI sram: %d\n",
__func__, err);
goto out;
}
sc->sc_spi.sct_cookie = sc;
sc->sc_spi.sct_configure = mcp2210_spi_configure;
sc->sc_spi.sct_transfer = mcp2210_spi_transfer;
sc->sc_spi.sct_nslaves = sc->sc_chipinfo->num_spi_slaves;
spibus_attach(sc->sc_dev, &sc->sc_spi);
out:
return err;
}