/* $NetBSD: umcpmio_spi.c,v 1.1 2025/11/29 18:39:14 brad Exp $ */ /* * Copyright (c) 2025 Brad Spencer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __KERNEL_RCSID(0, "$NetBSD: umcpmio_spi.c,v 1.1 2025/11/29 18:39:14 brad Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" #endif #include #include #include #include #include #include #include #include #include #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 /* Stuff required for the SPI part of the MCP2210 */ 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; } /* Most configuration is defered until a tranfer actually happens. The * exception is setting the pin that is to be used as a CS to ALT0. * * There is no attempt to reset the mode of the pin used for the CS, * as there is no concept of a session with the SPI framework. That is, * you would never know when to do it. Further, it will cause pin * flapping of all of the other pins if the CS is set to INPUT * or OUTPUT. Of course, that will happen when the CS mode (ALT0) * is set, but only the first time the pin is used as a CS. * * It is adviseable to make use of the NVRAM to set up the pin mode * upon boot up of the chip. * */ 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; /* The speed is in bps and the MCP2210 uses bps, but is not suppose to * be able to go below 1500 bps. There is also a upper bound of 12Mbps, * but the chip will not do anything wrong if you try to go faster as it * will cap the speed. */ 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; } /* Remove any queued up data. This is a choice, but it seems the * right now. If you call configure() you are probably resetting * the SPI port. */ 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; } /* Set all of the stuff about the slave device that is needed * to do a transfer. */ 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); /* The MCP2210 supports the notion of a high active for * the CS, but spi(4) doesn't so, just use the normal * low active behavior all of the time. * * The chip also appears to support more than one CS * being active at the same time, but that is really * an unusual desire to have. */ 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)); /* The MCP2210 does best when the tranfer size is set to the * same max amount that a transfer incoming might have. */ 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; } /* Fill up the outgoing tranfer buffers. It is generally assumed that the state * of the req buffer is set coming into this function. It should either be * zero filled or a repeat buffer in a retry situation. */ 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")); } } /* Drain the incoming buffer into the upstream structure. If there is no * upstream to drain into, then queue up the result for later. */ 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; } /* Very simular to receiving from the chip buffer, except from this * is from the local queue. Requeue if needed. */ 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; } /* Drain the chip buffers into the local queue. No upstream involved. */ 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; } /* Service a tranfer. We do the local queue first if there is something, then * send a tranfer, with an optional amount of data, and then process any * incoming chip data. */ 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; /* There was local queue data and an upstream to receive it */ 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; /* Set up a transfer and fill it once */ 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)); /* This error situation of MCP2210_SPI_STATUS_DATA (0x30) and * zero received data means either the chip is stuck because * upstream didn't send enough data, or the chip is working * on receiving some data. We only let this condution go on * a certain number of times before passing the error up. */ if (!err) { if (res.spi_engine_status == MCP2210_SPI_STATUS_DATA && res.num_receive_bytes == 0) { err = ECANCELED; } } /* A normal busy caused by the SPI bus being busy or the USB * bus being busy. Try the whole transfer again. */ 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 the chip knows that there is data coming, but has not seen * any yet you get this error. Retry the transfer with a zero * length outgoing buffer. */ 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 we actually received some data, drain it somewhere */ 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; } /* Cancel a SPI transfer that might be in progress, or at * least the chip thinks is in progress. */ 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; } /* As long as there are outgoing chunks to send, * or incoming chunks to receive, poll the SPI port. */ 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)); /* Give up if we exceed the stuck count. It is ok for this to happen * a couple of times during a transfer, but if it persists, something is * going wrong. */ 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; } /* Finialize the transfer */ 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; } /* Start a SPI transfer up and running */ 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; /* XXX - maybe this should be a sysctl */ 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); /* Figure out the maximum size for this transfer. * It doesn't matter if it is a incoming or outgoing * chunk. */ 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); /* The chip can do up to 65535 bytes in a transfer. We subtract * 1 so the slave device can send back a end byte. There is, * apparently some was to do larger transfers, but I could * not find documentation on how to do that. */ 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)); /* Set all of the defered setting now that we are commited to trying * to do the transfer. */ err = mcp2210_spi_set_slave_config(sc, max_transfer_size, st->st_slave); /* If we get EBUSY here, then it probably means that there was a incomplete * transfer. Cancel it, and try one more time. */ 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; } /* Set all of the active and idle CS directions here. */ 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 there is a EBUSY here, then a transfer was incomplete * in the past. That can happen on attach if the chip stays * powered up, but is removed from the USB port and * reinserted. Cancel the transfer and try one more time. */ 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; }