/**************************************************************************** * drivers/rpmsg/rpmsg_port_spi_slave.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include "rpmsg_port.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifdef CONFIG_RPMSG_PORT_SPI_CRC # define rpmsg_port_spi_crc16(hdr) crc16((FAR uint8_t *)&(hdr)->cmd, \ (hdr)->len - sizeof((hdr)->crc)) #else # define rpmsg_port_spi_crc16(hdr) 0 #endif #define BYTES2WORDS(s,b) ((b) / ((s)->nbits >> 3)) /**************************************************************************** * Private Types ****************************************************************************/ enum rpmsg_port_spi_cmd_e { RPMSG_PORT_SPI_CMD_CONNECT = 0x01, RPMSG_PORT_SPI_CMD_AVAIL, RPMSG_PORT_SPI_CMD_DATA, }; struct rpmsg_port_spi_s { struct rpmsg_port_s port; FAR struct spi_slave_ctrlr_s *spictrlr; struct spi_slave_dev_s spislv; FAR struct ioexpander_dev_s *ioe; /* GPIOs used for handshake */ uint8_t mreq; uint8_t sreq; /* SPI devices' configuration */ int nbits; /* Reserved for cmd send */ FAR struct rpmsg_port_header_s *cmdhdr; /* Used for sync data state between mreq_handler and * rpmsg_port_spi_slave_notify */ FAR struct rpmsg_port_header_s *txhdr; FAR struct rpmsg_port_header_s *rxhdr; rpmsg_port_rx_cb_t rxcb; bool connected; /* Used for flow control */ uint16_t txavail; uint16_t rxavail; uint16_t rxthres; atomic_int transferring; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static void rpmsg_port_spi_notify_tx_ready(FAR struct rpmsg_port_s *port); static void rpmsg_port_spi_notify_rx_free(FAR struct rpmsg_port_s *port); static void rpmsg_port_spi_register_cb(FAR struct rpmsg_port_s *port, rpmsg_port_rx_cb_t callback); static void rpmsg_port_spi_slave_select(FAR struct spi_slave_dev_s *dev, bool selected); static void rpmsg_port_spi_slave_cmddata(FAR struct spi_slave_dev_s *dev, bool data); static size_t rpmsg_port_spi_slave_getdata(FAR struct spi_slave_dev_s *dev, FAR const void **data); static size_t rpmsg_port_spi_slave_receive(FAR struct spi_slave_dev_s *dev, FAR const void *data, size_t nwords); static void rpmsg_port_spi_slave_notify(FAR struct spi_slave_dev_s *dev, spi_slave_state_t state); /**************************************************************************** * Private Data ****************************************************************************/ static const struct rpmsg_port_ops_s g_rpmsg_port_spi_ops = { rpmsg_port_spi_notify_tx_ready, rpmsg_port_spi_notify_rx_free, rpmsg_port_spi_register_cb, }; static const struct spi_slave_devops_s g_rpmsg_port_spi_slave_ops = { rpmsg_port_spi_slave_select, /* select */ rpmsg_port_spi_slave_cmddata, /* cmddata */ rpmsg_port_spi_slave_getdata, /* getdata */ rpmsg_port_spi_slave_receive, /* receive */ rpmsg_port_spi_slave_notify, /* notify */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: rpmsg_port_spi_drop_packets ****************************************************************************/ static void rpmsg_port_spi_drop_packets(FAR struct rpmsg_port_spi_s *rpspi) { FAR struct rpmsg_port_header_s *hdr; while (!!(hdr = rpmsg_port_queue_get_buffer(&rpspi->port.txq, false))) { rpmsg_port_queue_return_buffer(&rpspi->port.txq, hdr); } while (!!(hdr = rpmsg_port_queue_get_buffer(&rpspi->port.rxq, false))) { rpmsg_port_queue_return_buffer(&rpspi->port.rxq, hdr); } } /**************************************************************************** * Name: rpmsg_port_spi_exchange ****************************************************************************/ static void rpmsg_port_spi_exchange(FAR struct rpmsg_port_spi_s *rpspi) { FAR struct rpmsg_port_header_s *txhdr; if (atomic_fetch_add(&rpspi->transferring, 1)) { return; } if (!rpspi->connected) { txhdr = rpspi->cmdhdr; txhdr->cmd = RPMSG_PORT_SPI_CMD_CONNECT; strlcpy((FAR char *)(txhdr + 1), rpspi->port.cpuname, RPMSG_NAME_SIZE); } else if (rpspi->txavail > 0 && rpmsg_port_queue_nused(&rpspi->port.txq) > 0) { txhdr = rpmsg_port_queue_get_buffer(&rpspi->port.txq, false); DEBUGASSERT(txhdr != NULL); txhdr->cmd = RPMSG_PORT_SPI_CMD_DATA; rpspi->txhdr = txhdr; } else { txhdr = rpspi->cmdhdr; txhdr->cmd = RPMSG_PORT_SPI_CMD_AVAIL; } txhdr->avail = rpmsg_port_queue_navail(&rpspi->port.rxq); txhdr->avail = txhdr->avail > 1 ? txhdr->avail - 1 : 0; txhdr->crc = rpmsg_port_spi_crc16(txhdr); rpmsginfo("send cmd:%u avail:%u\n", txhdr->cmd, txhdr->avail); SPIS_CTRLR_ENQUEUE(rpspi->spictrlr, txhdr, BYTES2WORDS(rpspi, rpspi->cmdhdr->len)); IOEXP_WRITEPIN(rpspi->ioe, rpspi->sreq, 1); rpspi->rxavail = txhdr->avail; } /**************************************************************************** * Name: rpmsg_port_spi_notify_tx_ready ****************************************************************************/ static void rpmsg_port_spi_notify_tx_ready(FAR struct rpmsg_port_s *port) { FAR struct rpmsg_port_spi_s *rpspi = container_of(port, struct rpmsg_port_spi_s, port); if (rpspi->connected) { rpmsg_port_spi_exchange(rpspi); } else { rpmsg_port_spi_drop_packets(rpspi); } } /**************************************************************************** * Name: rpmsg_port_spi_notify_rx_free ****************************************************************************/ static void rpmsg_port_spi_notify_rx_free(FAR struct rpmsg_port_s *port) { FAR struct rpmsg_port_spi_s *rpspi = container_of(port, struct rpmsg_port_spi_s, port); if (rpmsg_port_queue_navail(&port->rxq) - rpspi->rxavail >= rpspi->rxthres) { rpmsg_port_spi_exchange(rpspi); } } /**************************************************************************** * Name: rpmsg_port_spi_register_cb ****************************************************************************/ static void rpmsg_port_spi_register_cb(FAR struct rpmsg_port_s *port, rpmsg_port_rx_cb_t callback) { FAR struct rpmsg_port_spi_s *rpspi = container_of(port, struct rpmsg_port_spi_s, port); rpspi->rxcb = callback; } /**************************************************************************** * Name: rpmsg_port_spi_slave_select ****************************************************************************/ static void rpmsg_port_spi_slave_select(FAR struct spi_slave_dev_s *dev, bool selected) { rpmsginfo("sdev: %p CS: %s\n", dev, selected ? "select" : "free"); } /**************************************************************************** * Name: rpmsg_port_spi_slave_cmddata ****************************************************************************/ static void rpmsg_port_spi_slave_cmddata(FAR struct spi_slave_dev_s *dev, bool data) { rpmsginfo("sdev: %p CMD: %s\n", dev, data ? "data" : "command"); } /**************************************************************************** * Name: rpmsg_port_spi_slave_getdata ****************************************************************************/ static size_t rpmsg_port_spi_slave_getdata(FAR struct spi_slave_dev_s *dev, FAR const void **data) { FAR struct rpmsg_port_spi_s *rpspi = container_of(dev, struct rpmsg_port_spi_s, spislv); *data = rpspi->rxhdr; return BYTES2WORDS(rpspi, rpspi->cmdhdr->len); } /**************************************************************************** * Name: rpmsg_port_spi_slave_receive ****************************************************************************/ static size_t rpmsg_port_spi_slave_receive(FAR struct spi_slave_dev_s *dev, FAR const void *data, size_t nwords) { return nwords; } /**************************************************************************** * Name: rpmsg_port_spi_slave_notify ****************************************************************************/ static void rpmsg_port_spi_slave_notify(FAR struct spi_slave_dev_s *dev, spi_slave_state_t state) { FAR struct rpmsg_port_spi_s *rpspi = container_of(dev, struct rpmsg_port_spi_s, spislv); IOEXP_WRITEPIN(rpspi->ioe, rpspi->sreq, 0); SPIS_CTRLR_QPOLL(rpspi->spictrlr); rpmsginfo("received cmd:%u avail:%u\n", rpspi->rxhdr->cmd, rpspi->rxhdr->avail); if (rpspi->txhdr != NULL) { rpmsg_port_queue_return_buffer(&rpspi->port.txq, rpspi->txhdr); rpspi->txhdr = NULL; } if (rpspi->rxhdr->crc != 0) { uint16_t crc = rpmsg_port_spi_crc16(rpspi->rxhdr); if (crc != 0 && rpspi->rxhdr->crc != crc) { rpmsgerr("crc check fail received: %u calculated: %u\n", rpspi->rxhdr->crc, crc); goto out; } } /* Skip any data received when connection is not established until a * connect req data packet has been received. */ if (!rpspi->connected) { if (rpspi->rxhdr->cmd != RPMSG_PORT_SPI_CMD_CONNECT) { goto out; } rpspi->txavail = rpspi->rxhdr->avail; rpspi->connected = true; } else { rpspi->txavail = rpspi->rxhdr->avail; if (rpspi->rxhdr->cmd == RPMSG_PORT_SPI_CMD_CONNECT) { rpspi->connected = false; rpmsg_port_spi_drop_packets(rpspi); } } if (rpspi->rxhdr->cmd != RPMSG_PORT_SPI_CMD_AVAIL) { rpmsg_port_queue_add_buffer(&rpspi->port.rxq, rpspi->rxhdr); rpspi->rxhdr = rpmsg_port_queue_get_available_buffer( &rpspi->port.rxq, false); DEBUGASSERT(rpspi->rxhdr != NULL); } out: if (atomic_exchange(&rpspi->transferring, 0) > 1 || (rpspi->txavail > 0 && rpmsg_port_queue_nused(&rpspi->port.txq) > 0)) { rpmsg_port_spi_exchange(rpspi); } } /**************************************************************************** * Name: rpmsg_port_spi_mreq_handler ****************************************************************************/ static int rpmsg_port_spi_mreq_handler(FAR struct ioexpander_dev_s *dev, ioe_pinset_t pinset, FAR void *arg) { FAR struct rpmsg_port_spi_s *rpspi = arg; rpmsginfo("received a mreq\n"); rpmsg_port_spi_exchange(rpspi); return 0; } /**************************************************************************** * Name: rpmsg_port_spi_connect ****************************************************************************/ static inline void rpmsg_port_spi_connect(FAR struct rpmsg_port_spi_s *rpspi) { rpmsg_port_spi_exchange(rpspi); } /**************************************************************************** * Name: rpmsg_port_spi_process_packet ****************************************************************************/ static void rpmsg_port_spi_process_packet(FAR struct rpmsg_port_spi_s *rpspi, FAR struct rpmsg_port_header_s *rxhdr) { rpmsginfo("received cmd: %u avail: %u", rxhdr->cmd, rxhdr->avail); switch (rxhdr->cmd) { case RPMSG_PORT_SPI_CMD_CONNECT: if (!rpspi->connected) { rpmsg_port_unregister(&rpspi->port); rpmsg_port_spi_connect(rpspi); } else { rpmsg_port_register(&rpspi->port, (FAR const char *)(rxhdr + 1)); } rpmsg_port_queue_return_buffer(&rpspi->port.rxq, rxhdr); break; case RPMSG_PORT_SPI_CMD_DATA: rpspi->rxcb(&rpspi->port, rxhdr); break; default: rpmsgerr("received a unexpected frame, dropped\n"); rpmsg_port_queue_return_buffer(&rpspi->port.rxq, rxhdr); break; } } /**************************************************************************** * Name: rpmsg_port_spi_thread ****************************************************************************/ static int rpmsg_port_spi_thread(int argc, FAR char *argv[]) { FAR struct rpmsg_port_spi_s *rpspi = (FAR struct rpmsg_port_spi_s *)((uintptr_t)strtoul(argv[2], NULL, 16)); FAR struct rpmsg_port_queue_s *queue = &rpspi->port.rxq; FAR struct rpmsg_port_header_s *rxhdr; rpmsg_port_spi_connect(rpspi); for (; ; ) { while ((rxhdr = rpmsg_port_queue_get_buffer(queue, true)) != NULL) { rpmsg_port_spi_process_packet(rpspi, rxhdr); } } return 0; } /**************************************************************************** * Name: rpmsg_port_spi_gpio_init ****************************************************************************/ static int rpmsg_port_spi_init_gpio(FAR struct ioexpander_dev_s *ioe, FAR uint8_t *gpio, uint8_t pin, int invert, ioe_callback_t callback, FAR void *args) { int direction = callback ? IOEXPANDER_DIRECTION_IN_PULLDOWN : IOEXPANDER_DIRECTION_OUT; int ret; ret = IOEXP_SETOPTION(ioe, pin, IOEXPANDER_OPTION_INVERT, (FAR void *)invert); if (ret < 0) { rpmsgerr("gpio set invert option error: %d\n", ret); return ret; } ret = IOEXP_SETDIRECTION(ioe, pin, direction); if (ret < 0) { rpmsgerr("gpio set direction %d error: %d\n", direction, ret); return ret; } if (direction == IOEXPANDER_DIRECTION_IN_PULLDOWN) { int intcfg = invert == IOEXPANDER_VAL_INVERT ? IOEXPANDER_VAL_FALLING : IOEXPANDER_VAL_RISING; FAR void *ptr; ret = IOEXP_SETOPTION(ioe, pin, IOEXPANDER_OPTION_INTCFG, (FAR void *)intcfg); if (ret < 0) { rpmsgerr("gpio set int option %d error: %d\n", intcfg, ret); return ret; } ptr = IOEP_ATTACH(ioe, pin, callback, args); if (ptr == NULL) { rpmsgerr("gpio attach error: %d\n", ret); return -EINVAL; } } *gpio = pin; return ret; } /**************************************************************************** * Name: rpmsg_port_spi_init_hardware ****************************************************************************/ static int rpmsg_port_spi_init_hardware(FAR struct rpmsg_port_spi_s *rpspi, FAR const struct rpmsg_port_spi_config_s *spicfg, FAR struct spi_slave_ctrlr_s *spictrlr, FAR struct ioexpander_dev_s *ioe) { int ret; if (spictrlr == NULL || ioe == NULL || spicfg == NULL) { rpmsgerr("invalid params\n"); return -EINVAL; } /* Init mreq gpio */ ret = rpmsg_port_spi_init_gpio(ioe, &rpspi->mreq, spicfg->mreq_pin, spicfg->mreq_invert, rpmsg_port_spi_mreq_handler, rpspi); if (ret < 0) { rpmsgerr("mreq init failed\n"); return ret; } /* Init sreq gpio */ ret = rpmsg_port_spi_init_gpio(ioe, &rpspi->sreq, spicfg->sreq_pin, spicfg->sreq_invert, NULL, NULL); if (ret < 0) { rpmsgerr("sreq init failed\n"); return ret; } rpspi->ioe = ioe; rpspi->spictrlr = spictrlr; rpspi->spislv.ops = &g_rpmsg_port_spi_slave_ops; rpspi->nbits = spicfg->nbits; SPIS_CTRLR_BIND(spictrlr, &rpspi->spislv, spicfg->mode, spicfg->nbits); return 0; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: rpmsg_port_spi_slave_initialize ****************************************************************************/ int rpmsg_port_spi_slave_initialize(FAR const struct rpmsg_port_config_s *cfg, FAR const struct rpmsg_port_spi_config_s *spicfg, FAR struct spi_slave_ctrlr_s *spictrlr, FAR struct ioexpander_dev_s *ioe) { FAR struct rpmsg_port_spi_s *rpspi; FAR char *argv[3]; char arg1[32]; int ret; rpspi = kmm_zalloc(sizeof(*rpspi)); if (rpspi == NULL) { rpmsgerr("malloc rpmsg spi failed\n"); return -ENOMEM; } DEBUGASSERT(cfg->txlen == cfg->rxlen); ret = rpmsg_port_initialize(&rpspi->port, cfg, &g_rpmsg_port_spi_ops); if (ret < 0) { rpmsgerr("rpmsg port initialize failed\n"); goto rpmsg_err; } /* Always reserve one buffer for sending/receiving cmd packet */ rpspi->cmdhdr = rpmsg_port_queue_get_available_buffer( &rpspi->port.txq, true); rpspi->rxhdr = rpmsg_port_queue_get_available_buffer( &rpspi->port.rxq, true); DEBUGASSERT(rpspi->cmdhdr != NULL && rpspi->rxhdr != NULL); rpspi->rxthres = rpmsg_port_queue_navail(&rpspi->port.rxq) * CONFIG_RPMSG_PORT_SPI_RX_THRESHOLD / 100; ret = rpmsg_port_spi_init_hardware(rpspi, spicfg, spictrlr, ioe); if (ret < 0) { rpmsgerr("rpmsg port spi hardware init failed\n"); goto out; } snprintf(arg1, sizeof(arg1), "%p", rpspi); argv[0] = (FAR char *)cfg->remotecpu; argv[1] = arg1; argv[2] = NULL; ret = kthread_create("rpmsg-spi-slv", CONFIG_RPMSG_PORT_SPI_THREAD_PRIORITY, CONFIG_RPMSG_PORT_SPI_THREAD_STACKSIZE, rpmsg_port_spi_thread, argv); if (ret < 0) { rpmsgerr("rpmsg port spi create thread failed\n"); goto out; } return 0; out: rpmsg_port_uninitialize(&rpspi->port); rpmsg_err: kmm_free(rpspi); return ret; }