drivers/spi: Add SPI Slave character device driver

This commit is contained in:
Gustavo Henrique Nihei 2021-05-14 22:41:15 -03:00 committed by Alan Carvalho de Assis
parent e29da149e3
commit 57723eaedf
4 changed files with 728 additions and 4 deletions

View File

@ -26,21 +26,81 @@ menuconfig SPI
if SPI
config SPI_SLAVE
bool "SPI slave"
bool "SPI Slave"
default n
---help---
Enable support for SPI slave features
Enable support for SPI Slave features
if SPI_SLAVE
config SPI_SLAVE_DRIVER
bool "SPI Slave character driver"
default n
---help---
Built-in support for a character driver at /dev/spislv[N] that may be
used to perform SPI bus transfers from applications.
The intent of this driver is to support SPI Slave testing.
if SPI_SLAVE_DRIVER
config SPI_SLAVE_DRIVER_MODE
int "SPI Slave character driver default mode"
default 0
---help---
Default SPI Slave character driver mode, where:
0 = CPOL=0, CPHA=0
1 = CPOL=0, CPHA=1
2 = CPOL=1, CPHA=0
3 = CPOL=1, CPHA=1
config SPI_SLAVE_DRIVER_WIDTH
int "SPI Slave character driver default bit width"
default 8
---help---
Number of bits per SPI Slave transfer (default 8).
config SPI_SLAVE_DRIVER_BUFFER_SIZE
int "SPI Slave character driver TX and RX buffer sizes"
default 128
---help---
Size of the internal TX and RX buffers of the SPI Slave
character driver.
config SPI_SLAVE_DRIVER_COLORIZE_TX_BUFFER
bool "SPI Slave character driver colorize TX buffer"
default n
---help---
Initialize entries of the TX buffer with a given pattern.
If the SPI Slave controller performs a call to "getdata" API during
the "bind" operation, the colorized buffer may be sent as part of the
first TX transfer of the SPI Slave controller.
This feature might be useful for a quick communication test between
Master and Slave.
config SPI_SLAVE_DRIVER_COLORIZE_PATTERN
hex "SPI Slave character driver colorize pattern"
default 0xa5
depends on SPI_SLAVE_DRIVER_COLORIZE_TX_BUFFER
---help---
Pattern to be used as the coloration of the TX buffer.
config SPI_SLAVE_DRIVER_COLORIZE_NUM_BYTES
int "SPI Slave character driver colorize number of bytes"
default 4
depends on SPI_SLAVE_DRIVER_COLORIZE_TX_BUFFER
---help---
Number of bytes of the TX buffer to be colorized.
endif # SPI_SLAVE_DRIVER
config SPI_SLAVE_DMA
bool "SPI slave DMA"
bool "SPI Slave DMA"
default n
depends on ARCH_DMA && EXPERIMENTAL
---help---
Enable support for DMA data transfers (not yet implemented).
endif
endif # SPI_SLAVE
config SPI_EXCHANGE
bool "SPI exchange"

View File

@ -29,6 +29,10 @@ ifeq ($(CONFIG_SPI_EXCHANGE),y)
endif
endif
ifeq ($(CONFIG_SPI_SLAVE_DRIVER),y)
CSRCS += spi_slave_driver.c
endif
# Include the selected SPI drivers
ifeq ($(CONFIG_SPI_BITBANG),y)

View File

@ -0,0 +1,629 @@
/****************************************************************************
* 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 <debug.h>
#include <nuttx/kmalloc.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 spislv_driver_s
{
/* Externally visible part of the SPI Slave device interface */
struct spi_sdev_s dev;
/* Reference to SPI Slave controller interface */
struct spi_sctrlr_s *sctrlr;
/* 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
sem_t exclsem; /* 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 spislv_open(FAR struct file *filep);
static int spislv_close(FAR struct file *filep);
static ssize_t spislv_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t spislv_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static int spislv_unlink(FAR struct inode *inode);
/* SPI Slave driver methods */
static void spislv_select(FAR struct spi_sdev_s *sdev, bool selected);
static void spislv_cmddata(FAR struct spi_sdev_s *sdev, bool data);
static size_t spislv_getdata(FAR struct spi_sdev_s *sdev,
FAR const void **data);
static size_t spislv_receive(FAR struct spi_sdev_s *sdev,
FAR const void *data, size_t nwords);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_spislvfops =
{
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
spislv_open, /* open */
spislv_close, /* close */
#else
NULL, /* open */
NULL, /* close */
#endif
spislv_read, /* read */
spislv_write, /* write */
NULL, /* seek */
NULL, /* ioctl */
NULL /* poll */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
, spislv_unlink /* unlink */
#endif
};
static const struct spi_sdevops_s g_spisdev_ops =
{
spislv_select, /* select */
spislv_cmddata, /* cmddata */
spislv_getdata, /* getdata */
spislv_receive, /* receive */
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: spislv_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 spislv_open(FAR struct file *filep)
{
FAR struct inode *inode;
FAR struct spislv_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 spislv_driver_s *)inode->i_private;
/* Get exclusive access to the SPI Slave driver state structure */
ret = nxsem_wait(&priv->exclsem);
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);
nxsem_post(&priv->exclsem);
return OK;
}
#endif
/****************************************************************************
* Name: spislv_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 spislv_close(FAR struct file *filep)
{
FAR struct inode *inode;
FAR struct spislv_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 spislv_driver_s *)inode->i_private;
/* Get exclusive access to the SPI Slave driver state structure */
ret = nxsem_wait(&priv->exclsem);
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)
{
nxsem_destroy(&priv->exclsem);
kmm_free(priv);
inode->i_private = NULL;
return OK;
}
nxsem_post(&priv->exclsem);
return OK;
}
#endif
/****************************************************************************
* Name: spislv_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 spislv_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
FAR struct inode *inode;
FAR struct spislv_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 spislv_driver_s *)inode->i_private;
if (buffer == NULL)
{
spierr("ERROR: Buffer is NULL\n");
return -ENOBUFS;
}
remaining_words = SPI_SCTRLR_QPOLL(priv->sctrlr);
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: spislv_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 spislv_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen)
{
FAR struct inode *inode;
FAR struct spislv_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 spislv_driver_s *)inode->i_private;
memcpy(priv->tx_buffer, buffer, buflen);
priv->tx_length = buflen;
num_words = BYTES2WORDS(priv->tx_length);
enqueued_bytes = WORDS2BYTES(SPI_SCTRLR_ENQUEUE(priv->sctrlr,
priv->tx_buffer,
num_words));
spiinfo("%zu bytes enqueued\n", enqueued_bytes);
return (ssize_t)enqueued_bytes;
}
/****************************************************************************
* Name: spislv_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 spislv_unlink(FAR struct inode *inode)
{
FAR struct spislv_driver_s *priv;
int ret;
DEBUGASSERT(inode != NULL);
DEBUGASSERT(inode->i_private != NULL);
/* Get our private data structure */
priv = (FAR struct spislv_driver_s *)inode->i_private;
/* Get exclusive access to the SPI Slave driver state structure */
ret = nxsem_wait(&priv->exclsem);
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)
{
nxsem_destroy(&priv->exclsem);
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;
nxsem_post(&priv->exclsem);
return ret;
}
#endif
/****************************************************************************
* Name: spislv_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:
* sdev - SPI 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 spislv_select(FAR struct spi_sdev_s *sdev, bool selected)
{
spiinfo("sdev: %p CS: %s\n", sdev, selected ? "select" : "free");
}
/****************************************************************************
* Name: spislv_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:
* sdev - SPI 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 spislv_cmddata(FAR struct spi_sdev_s *sdev, bool data)
{
spiinfo("sdev: %p CMD: %s\n", sdev, data ? "data" : "command");
}
/****************************************************************************
* Name: spislv_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:
* sdev - SPI 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 spislv_getdata(FAR struct spi_sdev_s *sdev,
FAR const void **data)
{
FAR struct spislv_driver_s *priv = (FAR struct spislv_driver_s *)sdev;
*data = priv->tx_buffer;
return BYTES2WORDS(priv->tx_length);
}
/****************************************************************************
* Name: spislv_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:
* sdev - SPI 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 spislv_receive(FAR struct spi_sdev_s *sdev,
FAR const void *data, size_t len)
{
FAR struct spislv_driver_s *priv = (FAR struct spislv_driver_s *)sdev;
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: spislv_register
*
* Description:
* Register the SPI Slave character device as 'devpath'.
*
* Input Parameters:
* sctrlr - 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 spislv_register(FAR struct spi_sctrlr_s *sctrlr, int bus)
{
FAR struct spislv_driver_s *priv;
char devname[DEVNAME_FMTLEN];
int ret;
/* Sanity check */
DEBUGASSERT(sctrlr != NULL && (unsigned int)bus < 1000);
/* Initialize the SPI Slave device structure */
priv = (FAR struct spislv_driver_s *)
kmm_zalloc(sizeof(struct spislv_driver_s));
if (!priv)
{
spierr("ERROR: Failed to allocate instance\n");
return -ENOMEM;
}
priv->dev.ops = &g_spisdev_ops;
priv->sctrlr = sctrlr;
#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
nxsem_init(&priv->exclsem, 0, 1);
#endif
/* Create the character device name */
snprintf(devname, DEVNAME_FMTLEN, DEVNAME_FMT, bus);
/* Register the character driver */
ret = register_driver(devname, &g_spislvfops, 0666, priv);
if (ret < 0)
{
spierr("ERROR: Failed to register driver: %d\n", ret);
kmm_free(priv);
}
SPI_SCTRLR_BIND(priv->sctrlr, (FAR struct spi_sdev_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 */

View File

@ -534,6 +534,36 @@ struct spi_sdev_s
* Public Data
****************************************************************************/
/****************************************************************************
* Public Types
****************************************************************************/
/****************************************************************************
* Public Functions Definitions
****************************************************************************/
/****************************************************************************
* Name: spislv_register
*
* Description:
* Register the SPI Slave echo character device as 'devpath'.
*
* Input Parameters:
* sctrlr - An instance of the SPI Slave interface to use to communicate
* with the SPI Slave echo 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.
*
****************************************************************************/
#ifdef CONFIG_SPI_SLAVE_DRIVER
int spislv_register(FAR struct spi_sctrlr_s *sctrlr, int bus);
#endif /* CONFIG_SPI_SLAVE_DRIVER */
#undef EXTERN
#if defined(__cplusplus)
#define EXTERN extern "C"
@ -547,4 +577,5 @@ extern "C"
#if defined(__cplusplus)
}
#endif
#endif /* __INCLUDE_NUTTX_SPI_SLAVE_H */