/**************************************************************************** * drivers/spi/spi_slave_driver.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 <nuttx/config.h> #include <semaphore.h> #include <sys/types.h> #include <stdbool.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <assert.h> #include <debug.h> #include <nuttx/kmalloc.h> #include <nuttx/mutex.h> #include <nuttx/fs/fs.h> #include <nuttx/semaphore.h> #include <nuttx/spi/slave.h> #ifdef CONFIG_SPI_SLAVE_DRIVER /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define DEVNAME_FMT "/dev/spislv%d" #define DEVNAME_FMTLEN (11 + 3 + 1) #ifndef MIN # define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif #define WORDS2BYTES(_wn) ((_wn) * (CONFIG_SPI_SLAVE_DRIVER_WIDTH / 8)) #define BYTES2WORDS(_bn) ((_bn) / (CONFIG_SPI_SLAVE_DRIVER_WIDTH / 8)) /**************************************************************************** * Private Types ****************************************************************************/ struct spi_slave_driver_s { /* Externally visible part of the SPI Slave device interface */ struct spi_slave_dev_s dev; /* Reference to SPI Slave controller interface */ struct spi_slave_ctrlr_s *ctrlr; /* Receive buffer */ uint8_t rx_buffer[CONFIG_SPI_SLAVE_DRIVER_BUFFER_SIZE]; uint32_t rx_length; /* Location of next RX value */ /* Transmit buffer */ uint8_t tx_buffer[CONFIG_SPI_SLAVE_DRIVER_BUFFER_SIZE]; uint32_t tx_length; /* Location of next TX value */ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS mutex_t lock; /* Mutual exclusion */ int16_t crefs; /* Number of open references */ bool unlinked; /* Indicates if the driver has been unlinked */ #endif }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Character driver methods */ static int spi_slave_open(FAR struct file *filep); static int spi_slave_close(FAR struct file *filep); static ssize_t spi_slave_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t spi_slave_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); static int spi_slave_unlink(FAR struct inode *inode); /* SPI Slave driver methods */ static void spi_slave_select(FAR struct spi_slave_dev_s *sdev, bool selected); static void spi_slave_cmddata(FAR struct spi_slave_dev_s *sdev, bool data); static size_t spi_slave_getdata(FAR struct spi_slave_dev_s *sdev, FAR const void **data); static size_t spi_slave_receive(FAR struct spi_slave_dev_s *sdev, FAR const void *data, size_t nwords); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations g_spislavefops = { #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS spi_slave_open, /* open */ spi_slave_close, /* close */ #else NULL, /* open */ NULL, /* close */ #endif spi_slave_read, /* read */ spi_slave_write, /* write */ NULL, /* seek */ NULL, /* ioctl */ NULL /* poll */ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS , spi_slave_unlink /* unlink */ #endif }; static const struct spi_slave_devops_s g_spisdev_ops = { spi_slave_select, /* select */ spi_slave_cmddata, /* cmddata */ spi_slave_getdata, /* getdata */ spi_slave_receive, /* receive */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: spi_slave_open * * Description: * This function is called whenever the SPI Slave device is opened. * * Input Parameters: * filep - File structure instance * * Returned Value: * Zero (OK) is returned on success. A negated errno value is returned on * any failure to indicate the nature of the failure. * ****************************************************************************/ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int spi_slave_open(FAR struct file *filep) { FAR struct inode *inode; FAR struct spi_slave_driver_s *priv; int ret; DEBUGASSERT(filep != NULL); DEBUGASSERT(filep->f_inode != NULL); DEBUGASSERT(filep->f_inode->i_private != NULL); spiinfo("filep: %p\n", filep); /* Get our private data structure */ inode = filep->f_inode; priv = (FAR struct spi_slave_driver_s *)inode->i_private; /* Get exclusive access to the SPI Slave driver state structure */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { spierr("Failed to get exclusive access to the driver: %d\n", ret); return ret; } /* Increment the count of open references on the driver */ priv->crefs++; DEBUGASSERT(priv->crefs > 0); nxmutex_unlock(&priv->lock); return OK; } #endif /**************************************************************************** * Name: spi_slave_close * * Description: * This routine is called when the SPI Slave device is closed. * * Input Parameters: * filep - File structure instance * * Returned Value: * Zero (OK) is returned on success; A negated errno value is returned on * any failure to indicate the nature of the failure. * ****************************************************************************/ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int spi_slave_close(FAR struct file *filep) { FAR struct inode *inode; FAR struct spi_slave_driver_s *priv; int ret; DEBUGASSERT(filep != NULL); DEBUGASSERT(filep->f_inode != NULL); DEBUGASSERT(filep->f_inode->i_private != NULL); spiinfo("filep: %p\n", filep); /* Get our private data structure */ inode = filep->f_inode; priv = (FAR struct spi_slave_driver_s *)inode->i_private; /* Get exclusive access to the SPI Slave driver state structure */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { spierr("Failed to get exclusive access to the driver: %d\n", ret); return ret; } /* Decrement the count of open references on the driver */ DEBUGASSERT(priv->crefs > 0); priv->crefs--; /* If the count has decremented to zero and the driver has been already * unlinked, then dispose of the driver resources. */ if (priv->crefs <= 0 && priv->unlinked) { nxmutex_destroy(&priv->lock); kmm_free(priv); inode->i_private = NULL; return OK; } nxmutex_unlock(&priv->lock); return OK; } #endif /**************************************************************************** * Name: spi_slave_read * * Description: * This routine is called when the application requires to read the data * received by the SPI Slave device. * * Input Parameters: * filep - File structure instance * buffer - User-provided to save the data * buflen - The maximum size of the user-provided buffer * * Returned Value: * The positive non-zero number of bytes read on success, 0 on if an * end-of-file condition, or a negated errno value on any failure. * ****************************************************************************/ static ssize_t spi_slave_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct inode *inode; FAR struct spi_slave_driver_s *priv; size_t read_bytes; size_t remaining_words; spiinfo("filep=%p buffer=%p buflen=%zu\n", filep, buffer, buflen); /* Get our private data structure */ inode = filep->f_inode; priv = (FAR struct spi_slave_driver_s *)inode->i_private; if (buffer == NULL) { spierr("ERROR: Buffer is NULL\n"); return -ENOBUFS; } remaining_words = SPIS_CTRLR_QPOLL(priv->ctrlr); if (remaining_words == 0) { spiinfo("All words retrieved!\n"); } else { spiinfo("%zu words left in the buffer\n", remaining_words); } read_bytes = MIN(buflen, priv->rx_length); memcpy(buffer, priv->rx_buffer, read_bytes); return (ssize_t)read_bytes; } /**************************************************************************** * Name: spi_slave_write * * Description: * This routine is called when the application needs to enqueue data to be * transferred at the next leading clock edge of the SPI Slave controller. * * Input Parameters: * filep - Instance of struct file to use with the write * buffer - Data to write * buflen - Length of data to write in bytes * * Returned Value: * On success, the number of bytes written are returned (zero indicates * nothing was written). On any failure, a negated errno value is returned. * ****************************************************************************/ static ssize_t spi_slave_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) { FAR struct inode *inode; FAR struct spi_slave_driver_s *priv; size_t num_words; size_t enqueued_bytes; spiinfo("filep=%p buffer=%p buflen=%zu\n", filep, buffer, buflen); /* Get our private data structure */ inode = filep->f_inode; priv = (FAR struct spi_slave_driver_s *)inode->i_private; memcpy(priv->tx_buffer, buffer, buflen); priv->tx_length = buflen; num_words = BYTES2WORDS(priv->tx_length); enqueued_bytes = WORDS2BYTES(SPIS_CTRLR_ENQUEUE(priv->ctrlr, priv->tx_buffer, num_words)); spiinfo("%zu bytes enqueued\n", enqueued_bytes); return (ssize_t)enqueued_bytes; } /**************************************************************************** * Name: spi_slave_unlink * * Description: * This routine is called when the SPI Slave device is unlinked. * * Input Parameters: * inode - The inode associated with the SPI Slave device * * Returned Value: * Zero is returned on success; a negated value is returned on any failure. * ****************************************************************************/ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS static int spi_slave_unlink(FAR struct inode *inode) { FAR struct spi_slave_driver_s *priv; int ret; DEBUGASSERT(inode != NULL); DEBUGASSERT(inode->i_private != NULL); /* Get our private data structure */ priv = (FAR struct spi_slave_driver_s *)inode->i_private; /* Get exclusive access to the SPI Slave driver state structure */ ret = nxmutex_lock(&priv->lock); if (ret < 0) { spierr("Failed to get exclusive access to the driver: %d\n", ret); return ret; } /* Are there open references to the driver data structure? */ if (priv->crefs <= 0) { nxmutex_destroy(&priv->lock); kmm_free(priv); inode->i_private = NULL; return OK; } /* No... just mark the driver as unlinked and free the resources when the * last client closes their reference to the driver. */ priv->unlinked = true; nxmutex_unlock(&priv->lock); return ret; } #endif /**************************************************************************** * Name: spi_slave_select * * Description: * This is a SPI device callback that is used when the SPI controller * driver detects any change in the chip select pin. * * Input Parameters: * dev - SPI Slave device interface instance * selected - Indicates whether the chip select is in active state * * Returned Value: * None. * * Assumptions: * May be called from an interrupt handler. Processing should be as * brief as possible. * ****************************************************************************/ static void spi_slave_select(FAR struct spi_slave_dev_s *dev, bool selected) { spiinfo("sdev: %p CS: %s\n", dev, selected ? "select" : "free"); } /**************************************************************************** * Name: spi_slave_cmddata * * Description: * This is a SPI device callback that is used when the SPI controller * driver detects any change in the command/data condition. * * Normally only LCD devices distinguish command and data. For devices * that do not distinguish between command and data, this method may be * a stub. For devices that do make that distinction, they should treat * all subsequent calls to getdata() or receive() appropriately for the * current command/data selection. * * Input Parameters: * dev - SPI Slave device interface instance * data - True: Data is selected * * Returned Value: * None. * * Assumptions: * May be called from an interrupt handler. Processing should be as * brief as possible. * ****************************************************************************/ static void spi_slave_cmddata(FAR struct spi_slave_dev_s *dev, bool data) { spiinfo("sdev: %p CMD: %s\n", dev, data ? "data" : "command"); } /**************************************************************************** * Name: spi_slave_getdata * * Description: * This is a SPI device callback that is used when the SPI controller * requires data be shifted out at the next leading clock edge. This * is necessary to "prime the pump" so that the SPI controller driver * can keep pace with the shifted-in data. * * The SPI controller driver will prime for both command and data * transfers as determined by a preceding call to the device cmddata() * method. Normally only LCD devices distinguish command and data. * * Input Parameters: * dev - SPI Slave device interface instance * data - Pointer to the data buffer pointer to be shifed out. * The device will set the data buffer pointer to the actual data * * Returned Value: * The number of data units to be shifted out from the data buffer. * * Assumptions: * May be called from an interrupt handler and the response is usually * time-critical. * ****************************************************************************/ static size_t spi_slave_getdata(FAR struct spi_slave_dev_s *dev, FAR const void **data) { FAR struct spi_slave_driver_s *priv = (FAR struct spi_slave_driver_s *)dev; *data = priv->tx_buffer; return BYTES2WORDS(priv->tx_length); } /**************************************************************************** * Name: spi_slave_receive * * Description: * This is a SPI device callback that is used when the SPI controller * receives a new value shifted in. Notice that these values may be out of * synchronization by several words. * * Input Parameters: * dev - SPI Slave device interface instance * data - Pointer to the new data that has been shifted in * len - Length of the new data in units of nbits wide, * nbits being the data width previously provided to the bind() * method. * * Returned Value: * Number of units accepted by the device. In other words, * number of units to be removed from controller's receive queue. * * Assumptions: * May be called from an interrupt handler and in time-critical * circumstances. A good implementation might just add the newly * received word to a queue, post a processing task, and return as * quickly as possible to avoid any data overrun problems. * ****************************************************************************/ static size_t spi_slave_receive(FAR struct spi_slave_dev_s *dev, FAR const void *data, size_t len) { FAR struct spi_slave_driver_s *priv = (FAR struct spi_slave_driver_s *)dev; size_t recv_bytes = MIN(len, sizeof(priv->rx_buffer)); memcpy(priv->rx_buffer, data, recv_bytes); priv->rx_length = recv_bytes; return BYTES2WORDS(recv_bytes); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: spi_slave_register * * Description: * Register the SPI Slave character device driver as 'devpath'. * * Input Parameters: * ctrlr - An instance of the SPI Slave interface to use to communicate * with the SPI Slave device * bus - The SPI Slave bus number. This will be used as the SPI device * minor number. The SPI Slave character device will be * registered as /dev/spislvN where N is the minor number * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ int spi_slave_register(FAR struct spi_slave_ctrlr_s *ctrlr, int bus) { FAR struct spi_slave_driver_s *priv; char devname[DEVNAME_FMTLEN]; int ret; /* Sanity check */ DEBUGASSERT(ctrlr != NULL && (unsigned int)bus < 1000); /* Initialize the SPI Slave device structure */ priv = (FAR struct spi_slave_driver_s *) kmm_zalloc(sizeof(struct spi_slave_driver_s)); if (!priv) { spierr("ERROR: Failed to allocate instance\n"); return -ENOMEM; } priv->dev.ops = &g_spisdev_ops; priv->ctrlr = ctrlr; #ifdef CONFIG_SPI_SLAVE_DRIVER_COLORIZE_TX_BUFFER memset(priv->tx_buffer, CONFIG_SPI_SLAVE_DRIVER_COLORIZE_PATTERN, CONFIG_SPI_SLAVE_DRIVER_COLORIZE_NUM_BYTES); priv->tx_length = CONFIG_SPI_SLAVE_DRIVER_COLORIZE_NUM_BYTES; #endif #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS nxmutex_init(&priv->lock); #endif /* Create the character device name */ snprintf(devname, sizeof(devname), DEVNAME_FMT, bus); /* Register the character driver */ ret = register_driver(devname, &g_spislavefops, 0666, priv); if (ret < 0) { spierr("ERROR: Failed to register driver: %d\n", ret); nxmutex_destroy(&priv->lock); kmm_free(priv); } SPIS_CTRLR_BIND(priv->ctrlr, (FAR struct spi_slave_dev_s *)priv, CONFIG_SPI_SLAVE_DRIVER_MODE, CONFIG_SPI_SLAVE_DRIVER_WIDTH); spiinfo("SPI Slave driver loaded successfully!\n"); return ret; } #endif /* CONFIG_SPI_SLAVE_DRIVER */