diff --git a/drivers/rpmsg/CMakeLists.txt b/drivers/rpmsg/CMakeLists.txt index a049dcc73a4..bde43757cc6 100644 --- a/drivers/rpmsg/CMakeLists.txt +++ b/drivers/rpmsg/CMakeLists.txt @@ -33,6 +33,10 @@ if(CONFIG_RPMSG) PRIVATE ${NUTTX_DIR}/openamp/open-amp/lib) endif() + if(CONFIG_RPMSG_PORT_SPI) + list(APPEND SRCS rpmsg_port_spi.c) + endif() + if(CONFIG_RPMSG_VIRTIO) list(APPEND SRCS rpmsg_virtio.c) endif() diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig index 6d9bf2b990d..4868df30cae 100644 --- a/drivers/rpmsg/Kconfig +++ b/drivers/rpmsg/Kconfig @@ -27,6 +27,34 @@ config RPMSG_PORT ---help--- Rpmsg port transport layer used for cross chip communication. +config RPMSG_PORT_SPI + bool "Rpmsg SPI Port Driver Support" + default n + select RPMSG_PORT + ---help--- + Rpmsg SPI Port driver used for cross chip communication. + +if RPMSG_PORT_SPI + +config RPMSG_PORT_SPI_THREAD_PRIORITY + int "Rpmsg SPI Port Thread Priority" + default 224 + +config RPMSG_PORT_SPI_THREAD_STACKSIZE + int "Rpmsg SPI Port Stack Size" + default DEFAULT_TASK_STACKSIZE + +config RPMSG_PORT_SPI_CRC + bool "Rpmsg SPI Port Use CRC Check" + default n + +config RPMSG_PORT_SPI_RX_THRESHOLD + int "Rpmsg SPI Port Rx Buffer Threshold" + default 50 + range 0 100 + +endif # RPMSG_PORT_SPI + endif # RPMSG config RPMSG_VIRTIO diff --git a/drivers/rpmsg/Make.defs b/drivers/rpmsg/Make.defs index 52ddcab5d1e..5499c7db8b6 100644 --- a/drivers/rpmsg/Make.defs +++ b/drivers/rpmsg/Make.defs @@ -33,6 +33,10 @@ CSRCS += rpmsg_port.c CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)openamp$(DELIM)open-amp$(DELIM)lib endif +ifeq ($(CONFIG_RPMSG_PORT_SPI),y) +CSRCS += rpmsg_port_spi.c +endif + ifeq ($(CONFIG_RPMSG_VIRTIO),y) CSRCS += rpmsg_virtio.c CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)openamp$(DELIM)open-amp$(DELIM)lib diff --git a/drivers/rpmsg/rpmsg_port_spi.c b/drivers/rpmsg/rpmsg_port_spi.c new file mode 100644 index 00000000000..6d66bd8eab8 --- /dev/null +++ b/drivers/rpmsg/rpmsg_port_spi.c @@ -0,0 +1,545 @@ +/**************************************************************************** + * drivers/rpmsg/rpmsg_port_spi.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 "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 RPMSG_SPI_PORT_UNCONNECTED UINT16_MAX + +/**************************************************************************** + * 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_dev_s *spi; + FAR struct ioexpander_dev_s *ioe; + + /* GPIOs used for handshake */ + + uint8_t mreq; + uint8_t sreq; + + /* SPI devices' configuration */ + + uint32_t devid; + + /* Reserved for cmd send */ + + FAR struct rpmsg_port_header_s *cmdhdr; + + /* Used for sync data state between sreq_handler and complete_handler */ + + FAR struct rpmsg_port_header_s *txhdr; + FAR struct rpmsg_port_header_s *rxhdr; + + rpmsg_port_rx_cb_t rxcb; + + /* Used for flow control */ + + uint16_t txavail; + uint16_t rxavail; + uint16_t rxthres; +}; + +/**************************************************************************** + * 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); + +/**************************************************************************** + * 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, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * 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); + + IOEXP_WRITEPIN(rpspi->ioe, rpspi->mreq, 1); +} + +/**************************************************************************** + * 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) + { + IOEXP_WRITEPIN(rpspi->ioe, rpspi->mreq, 1); + } +} + +/**************************************************************************** + * 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_complete_handler + ****************************************************************************/ + +static void rpmsg_port_spi_complete_handler(FAR void *arg) +{ + FAR struct rpmsg_port_spi_s *rpspi = arg; + uint16_t avail = rpspi->rxhdr->avail; + + SPI_SELECT(rpspi->spi, rpspi->devid, false); + + rpmsginfo("received cmd:%u avail:%u\n", rpspi->rxhdr->cmd, 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 (rpspi->rxhdr->crc != crc) + { + rpmsgerr("crc check fail received: %u calculated: %u\n", + rpspi->rxhdr->crc, crc); + + /* Length may have been destroyed by a wrong data packet. we + * should reassign it, since it is used by SPI_EXCHANGE. + */ + + rpspi->rxhdr->len = rpspi->cmdhdr->len; + return; + } + } + + /* Skip any data received when connection is not established until a + * connect req data packet has been received. + */ + + if (rpspi->txavail == RPMSG_SPI_PORT_UNCONNECTED && + rpspi->rxhdr->cmd != RPMSG_PORT_SPI_CMD_CONNECT) + { + rpspi->rxhdr->len = rpspi->cmdhdr->len; + return; + } + + 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); + } + + /* Do nothing when two sides are not connected. */ + + if (rpspi->txavail == RPMSG_SPI_PORT_UNCONNECTED) + { + return; + } + + /* Drop invaild avail value in case of a wrong data packet received. */ + + if (avail != RPMSG_SPI_PORT_UNCONNECTED) + { + rpspi->txavail = avail; + } + + if (rpspi->txavail > 0 && rpmsg_port_queue_nused(&rpspi->port.txq) > 0) + { + IOEXP_WRITEPIN(rpspi->ioe, rpspi->mreq, 1); + } +} + +/**************************************************************************** + * Name: rpmsg_port_spi_sreq_handler + ****************************************************************************/ + +static int rpmsg_port_spi_sreq_handler(FAR struct ioexpander_dev_s *dev, + ioe_pinset_t pinset, FAR void *arg) +{ + FAR struct rpmsg_port_spi_s *rpspi = arg; + FAR struct rpmsg_port_header_s *txhdr; + + rpmsginfo("sreq enter\n"); + + IOEXP_WRITEPIN(rpspi->ioe, rpspi->mreq, 0); + + if (rpspi->txavail == RPMSG_SPI_PORT_UNCONNECTED) + { + txhdr = rpspi->cmdhdr; + txhdr->cmd = RPMSG_PORT_SPI_CMD_CONNECT; + } + 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) - 1; + txhdr->crc = rpmsg_port_spi_crc16(txhdr); + + rpmsginfo("irq send cmd:%u avail:%u\n", txhdr->cmd, txhdr->avail); + + SPI_SELECT(rpspi->spi, rpspi->devid, true); + SPI_EXCHANGE(rpspi->spi, txhdr, rpspi->rxhdr, rpspi->rxhdr->len); + + rpspi->rxavail = txhdr->avail; + return 0; +} + +/**************************************************************************** + * Name: rpmsg_port_spi_connect + ****************************************************************************/ + +static void rpmsg_port_spi_connect(FAR struct rpmsg_port_spi_s *rpspi) +{ + bool val; + + IOEXP_READPIN(rpspi->ioe, rpspi->sreq, &val); + rpmsginfo("sreq level is %d\n", val); + + /* If sreq gpio is high, that means peer has sent connect req and receive + * the connect data packet directly. if req gpio is low, then current + * side send the req. + */ + + if (val) + { + rpmsg_port_spi_sreq_handler(NULL, 0, rpspi); + } + else + { + IOEXP_WRITEPIN(rpspi->ioe, rpspi->mreq, 1); + } +} + +/**************************************************************************** + * 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->txavail != RPMSG_SPI_PORT_UNCONNECTED) + { + rpmsg_port_unregister(&rpspi->port); + rpspi->txavail = RPMSG_SPI_PORT_UNCONNECTED; + } + else + { + rpspi->txavail = rxhdr->avail; + rpmsg_port_register(&rpspi->port); + } + + 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, 0)); + 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_dev_s *spi, FAR struct ioexpander_dev_s *ioe) +{ + int ret; + + /* Init mreq gpio */ + + ret = rpmsg_port_spi_init_gpio(ioe, &rpspi->mreq, spicfg->mreq_pin, + spicfg->mreq_invert, NULL, NULL); + 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, + rpmsg_port_spi_sreq_handler, rpspi); + if (ret < 0) + { + rpmsgerr("sreq init failed\n"); + return ret; + } + + SPI_SETBITS(spi, 8); + SPI_SETMODE(spi, spicfg->mode); + SPI_SETFREQUENCY(spi, spicfg->freq); + SPI_REGISTERCALLBACK(spi, rpmsg_port_spi_complete_handler, rpspi); + + rpspi->spi = spi; + rpspi->ioe = ioe; + rpspi->devid = spicfg->devid; + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rpmsg_port_spi_initialize + ****************************************************************************/ + +int +rpmsg_port_spi_initialize(FAR const struct rpmsg_port_config_s *cfg, + FAR const struct rpmsg_port_spi_config_s *spicfg, + FAR struct spi_dev_s *spi, + FAR struct ioexpander_dev_s *ioe) +{ + FAR struct rpmsg_port_spi_s *rpspi; + FAR char *argv[3]; + char arg1[32]; + int ret; + + if (spi == NULL || ioe == NULL || spicfg == NULL) + { + rpmsgerr("invalid params\n"); + return -EINVAL; + } + + rpspi = kmm_zalloc(sizeof(*rpspi)); + if (rpspi == NULL) + { + rpmsgerr("malloc rpmsg spi failed\n"); + return -ENOMEM; + } + + ret = rpmsg_port_spi_init_hardware(rpspi, spicfg, spi, ioe); + if (ret < 0) + { + rpmsgerr("rpmsg port spi hardware init failed\n"); + goto rpmsg_err; + } + + 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->txavail = RPMSG_SPI_PORT_UNCONNECTED; + rpspi->rxthres = rpmsg_port_queue_navail(&rpspi->port.rxq) * + CONFIG_RPMSG_PORT_SPI_RX_THRESHOLD / 100; + + snprintf(arg1, sizeof(arg1), "%p", rpspi); + argv[0] = (FAR void *)cfg->remotecpu; + argv[1] = arg1; + argv[2] = NULL; + ret = kthread_create("rpmsg-spi", + 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 thread_err; + } + + return 0; + +thread_err: + rpmsg_port_uninitialize(&rpspi->port); +rpmsg_err: + kmm_free(rpspi); + return ret; +} diff --git a/include/nuttx/rpmsg/rpmsg_port.h b/include/nuttx/rpmsg/rpmsg_port.h index c7f8e388572..a6a8f4d8f2f 100644 --- a/include/nuttx/rpmsg/rpmsg_port.h +++ b/include/nuttx/rpmsg/rpmsg_port.h @@ -28,6 +28,9 @@ #include #include +#include +#include + #ifdef CONFIG_RPMSG_PORT /**************************************************************************** @@ -50,5 +53,79 @@ struct rpmsg_port_config_s FAR void *rxbuf; }; +#ifdef CONFIG_RPMSG_PORT_SPI + +/* There are two gpios used for communication between two chips. At the SPI + * master side, mreq is an output gpio pin which is used to notify the + * slave side there is a data packet to be sent. it actually transfers the + * data only when it receives an interrupt from sreq pin. and at the SPI + * slave side, it prepares the data to be sent, and activates the sreq to + * the master side, master will initiate a transfer immediately when it + * receives an interrupt from sreq pin to receive the data. + * + * If IOEXPANDER_OPTION_INVERT option of pin is set to be 0, then it will + * be triggered an interrupt at the rising edge. or it will be triggered + * at the falling edge. + */ + +struct rpmsg_port_spi_config_s +{ + /* GPIO configurations of pins used for communication between two chips. */ + + uint8_t mreq_pin; + uint8_t sreq_pin; + int mreq_invert; + int sreq_invert; /* Pin options described in ioexpander.h */ + + enum spi_mode_e mode; + uint32_t devid; /* Device ID of enum spi_devtype_e */ + uint32_t freq; /* SPI frequency (Hz) */ +}; + +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +#ifdef CONFIG_RPMSG_PORT_SPI + +/**************************************************************************** + * Name: rpmsg_port_spi_initialize + * + * Description: + * Initialize a rpmsg_port_spi device to communicate between two chips. + * + * Input Parameters: + * cfg - Configuration of buffers needed for communication. + * spicfg - SPI device's configuration. + * spi - SPI device used for transfer data between two chips. + * ioe - ioexpander used to config gpios. + * + * Returned Value: + * Zero on success or an negative value on failure. + * + ****************************************************************************/ + +int +rpmsg_port_spi_initialize(FAR const struct rpmsg_port_config_s *cfg, + FAR const struct rpmsg_port_spi_config_s *spicfg, + FAR struct spi_dev_s *spi, + FAR struct ioexpander_dev_s *ioe); + +#endif + +#ifdef __cplusplus +} +#endif + #endif /* CONFIG_RPMSG_PORT */ #endif /* __INCLUDE_NUTTX_RPMSG_RPMSG_PORT_H */