nuttx/drivers/spi/spi_slave_driver.c
2024-09-13 12:27:31 +08:00

823 lines
23 KiB
C

/****************************************************************************
* 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/param.h>
#include <sys/types.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <fcntl.h>
#include <poll.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/fs/fs.h>
#include <nuttx/semaphore.h>
#include <nuttx/spi/slave.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define DEVNAME_FMT "/dev/spislv%d"
#define DEVNAME_FMTLEN (11 + 3 + 1)
#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 */
FAR struct spi_slave_ctrlr_s *ctrlr;
/* The poll waiter */
FAR struct pollfd *fds;
/* The semphore reader */
sem_t wait;
/* Receive buffer */
uint8_t rx_buffer[CONFIG_SPI_SLAVE_DRIVER_BUFFER_SIZE];
uint32_t rx_length; /* Location of next RX value */
/* Transmit buffer */
#ifdef CONFIG_SPI_SLAVE_DRIVER_COLORIZE_TX_BUFFER
uint8_t tx_buffer[CONFIG_SPI_SLAVE_DRIVER_COLORIZE_NUM_BYTES];
#endif
mutex_t lock; /* Mutual exclusion */
int16_t crefs; /* Number of open references */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
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_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int spi_slave_unlink(FAR struct inode *inode);
#endif
/* SPI Slave driver methods */
static void spi_slave_select(FAR struct spi_slave_dev_s *dev,
bool selected);
static void spi_slave_cmddata(FAR struct spi_slave_dev_s *dev,
bool data);
static size_t spi_slave_getdata(FAR struct spi_slave_dev_s *dev,
FAR const void **data);
static size_t spi_slave_receive(FAR struct spi_slave_dev_s *dev,
FAR const void *data, size_t nwords);
static void spi_slave_notify(FAR struct spi_slave_dev_s *dev,
spi_slave_state_t state);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_spislavefops =
{
spi_slave_open, /* open */
spi_slave_close, /* close */
spi_slave_read, /* read */
spi_slave_write, /* write */
NULL, /* seek */
NULL, /* ioctl */
NULL, /* mmap */
NULL, /* truncate */
spi_slave_poll /* 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 */
spi_slave_notify, /* notify */
};
/****************************************************************************
* 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.
*
****************************************************************************/
static int spi_slave_open(FAR struct file *filep)
{
FAR struct spi_slave_driver_s *priv;
int ret;
/* Sanity check */
DEBUGASSERT(filep->f_inode->i_private != NULL);
spiinfo("filep: %p\n", filep);
/* Get our private data structure */
priv = filep->f_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 */
if (priv->crefs == 0)
{
SPIS_CTRLR_BIND(priv->ctrlr, (FAR struct spi_slave_dev_s *)priv,
CONFIG_SPI_SLAVE_DRIVER_MODE,
CONFIG_SPI_SLAVE_DRIVER_WIDTH);
}
priv->crefs++;
DEBUGASSERT(priv->crefs > 0);
nxmutex_unlock(&priv->lock);
return OK;
}
/****************************************************************************
* 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.
*
****************************************************************************/
static int spi_slave_close(FAR struct file *filep)
{
FAR struct spi_slave_driver_s *priv;
int ret;
/* Sanity check */
DEBUGASSERT(filep->f_inode->i_private != NULL);
spiinfo("filep: %p\n", filep);
/* Get our private data structure */
priv = filep->f_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 (priv->crefs == 0)
{
SPIS_CTRLR_UNBIND(priv->ctrlr);
}
/* If the count has decremented to zero and the driver has been already
* unlinked, then dispose of the driver resources.
*/
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
if (priv->crefs <= 0 && priv->unlinked)
#else
if (priv->crefs <= 0)
#endif
{
nxmutex_destroy(&priv->lock);
kmm_free(priv);
filep->f_inode->i_private = NULL;
return OK;
}
nxmutex_unlock(&priv->lock);
return OK;
}
/****************************************************************************
* 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 spi_slave_driver_s *priv;
size_t read_bytes;
size_t remaining_words;
int ret;
/* Sanity check */
DEBUGASSERT(filep->f_inode->i_private != NULL);
spiinfo("filep=%p buffer=%p buflen=%zu\n", filep, buffer, buflen);
/* Get our private data structure */
priv = filep->f_inode->i_private;
if (buffer == NULL)
{
spierr("ERROR: Buffer is NULL\n");
return -ENOBUFS;
}
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
spierr("Failed to get exclusive access: %d\n", ret);
return ret;
}
while (priv->rx_length == 0)
{
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);
}
if (priv->rx_length == 0)
{
nxmutex_unlock(&priv->lock);
if (filep->f_oflags & O_NONBLOCK)
{
return -EAGAIN;
}
ret = nxsem_wait(&priv->wait);
if (ret < 0)
{
return ret;
}
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
spierr("Failed to get exclusive access: %d\n", ret);
return ret;
}
}
}
read_bytes = MIN(buflen, priv->rx_length);
memcpy(buffer, priv->rx_buffer, read_bytes);
priv->rx_length -= read_bytes;
nxmutex_unlock(&priv->lock);
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 spi_slave_driver_s *priv;
size_t enqueued_bytes;
int ret;
/* Sanity check */
DEBUGASSERT(filep->f_inode->i_private != NULL);
spiinfo("filep=%p buffer=%p buflen=%zu\n", filep, buffer, buflen);
/* Get our private data structure */
priv = filep->f_inode->i_private;
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
spierr("Failed to get exclusive access: %d\n", ret);
return ret;
}
if (SPIS_CTRLR_QFULL(priv->ctrlr))
{
nxmutex_unlock(&priv->lock);
if (filep->f_oflags & O_NONBLOCK)
{
return -EAGAIN;
}
ret = nxsem_wait(&priv->wait);
if (ret < 0)
{
return ret;
}
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
spierr("Failed to get exclusive access: %d\n", ret);
return ret;
}
}
enqueued_bytes = WORDS2BYTES(SPIS_CTRLR_ENQUEUE(priv->ctrlr,
buffer,
BYTES2WORDS(buflen)));
spiinfo("%zu bytes enqueued\n", enqueued_bytes);
nxmutex_unlock(&priv->lock);
return (ssize_t)enqueued_bytes;
}
/****************************************************************************
* Name: spi_slave_poll
****************************************************************************/
static int spi_slave_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct spi_slave_driver_s *priv;
int ret;
/* Sanity check */
DEBUGASSERT(filep->f_inode->i_private != NULL);
/* Get our private data structure */
priv = filep->f_inode->i_private;
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
spierr("Failed to get exclusive access: %d\n", ret);
return ret;
}
if (setup)
{
pollevent_t eventset = 0;
if (priv->fds == NULL)
{
priv->fds = fds;
fds->priv = &priv->fds;
}
else
{
ret = -EBUSY;
}
SPIS_CTRLR_QPOLL(priv->ctrlr);
if (priv->rx_length > 0)
{
eventset |= POLLIN;
}
if (!SPIS_CTRLR_QFULL(priv->ctrlr))
{
eventset |= POLLOUT;
}
poll_notify(&fds, 1, eventset);
}
else if (fds->priv != NULL)
{
priv->fds = NULL;
fds->priv = NULL;
}
nxmutex_unlock(&priv->lock);
return ret;
}
/****************************************************************************
* 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;
/* Sanity check */
DEBUGASSERT(inode->i_private != NULL);
/* Get our private data structure */
priv = 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)
{
SPIS_CTRLR_UNBIND(priv->ctrlr);
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)
{
#ifdef CONFIG_SPI_SLAVE_DRIVER_COLORIZE_TX_BUFFER
FAR struct spi_slave_driver_s *priv = (FAR struct spi_slave_driver_s *)dev;
*data = priv->tx_buffer;
return BYTES2WORDS(CONFIG_SPI_SLAVE_DRIVER_COLORIZE_NUM_BYTES);
#else
*data = NULL;
return 0;
#endif
}
/****************************************************************************
* 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
* nwords - 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 nwords)
{
FAR struct spi_slave_driver_s *priv = (FAR struct spi_slave_driver_s *)dev;
size_t recv_bytes = MIN(WORDS2BYTES(nwords), sizeof(priv->rx_buffer));
memcpy(priv->rx_buffer, data, recv_bytes);
priv->rx_length = recv_bytes;
return BYTES2WORDS(recv_bytes);
}
/****************************************************************************
* Name: spi_slave_notify
*
* Description:
* This is a SPI device callback that is used when the SPI controller
* receives and sends complete or fail to notify spi slave upper half.
* And this callback can call in interrupt handler.
*
* Input Parameters:
* dev - SPI Slave device interface instance
* state - The Receive and send state, type of state is spi_slave_state_t
*
****************************************************************************/
static void spi_slave_notify(FAR struct spi_slave_dev_s *dev,
spi_slave_state_t state)
{
FAR struct spi_slave_driver_s *priv = (FAR struct spi_slave_driver_s *)dev;
int semcnt;
/* POLLOUT is used to notify the upper layer that data can be written,
* POLLPRI is used to notify the upper layer that the data written
* has been read
*/
if (state == SPISLAVE_TX_COMPLETE)
{
poll_notify(&priv->fds, 1, POLLOUT | POLLPRI);
}
else if (state == SPISLAVE_RX_COMPLETE)
{
poll_notify(&priv->fds, 1, POLLIN);
}
else
{
poll_notify(&priv->fds, 1, POLLERR);
}
while (nxsem_get_value(&priv->wait, &semcnt) == 0 && semcnt <= 0)
{
nxsem_post(&priv->wait);
}
}
/****************************************************************************
* 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;
nxsem_init(&priv->wait, 0, 0);
#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);
#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);
nxsem_destroy(&priv->wait);
nxmutex_destroy(&priv->lock);
kmm_free(priv);
}
spiinfo("SPI Slave driver loaded successfully!\n");
return ret;
}