diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index d3f32ac006..b365491fee 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -30,6 +30,10 @@ if(CONFIG_I2C) list(APPEND SRCS i2c_bitbang.c) endif() + if(CONFIG_I2C_SLAVE_DRIVER) + list(APPEND SRCS i2c_slave_driver.c) + endif() + # Include the selected I2C multiplexer drivers if(CONFIG_I2CMULTIPLEXER_PCA9540BDP) diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 764fc7473d..a0883f5a20 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -24,6 +24,30 @@ config I2C_SLAVE bool "I2C Slave" default n +menu "I2C Slave Support" + +config I2C_SLAVE_DRIVER + bool "I2C Slave driver" + default n + ---help--- + Enable I2C Slave driver + +config I2C_SLAVE_READBUFSIZE + int "I2C Slave driver read buffer size" + default 32 + depends on I2C_SLAVE_DRIVER + ---help--- + I2C Slave read buffer size + +config I2C_SLAVE_WRITEBUFSIZE + int "I2C Slave driver write buffer size" + default 32 + depends on I2C_SLAVE_DRIVER + ---help--- + I2C Slave write buffer size + +endmenu # I2C Slave Support + config I2C_POLLED bool "Polled I2C (no interrupts)" default n diff --git a/drivers/i2c/Make.defs b/drivers/i2c/Make.defs index d069ad80dc..e823327de0 100644 --- a/drivers/i2c/Make.defs +++ b/drivers/i2c/Make.defs @@ -32,6 +32,10 @@ ifeq ($(CONFIG_I2C_BITBANG),y) CSRCS += i2c_bitbang.c endif +ifeq ($(CONFIG_I2C_SLAVE_DRIVER),y) +CSRCS += i2c_slave_driver.c +endif + # Include the selected I2C multiplexer drivers ifeq ($(CONFIG_I2CMULTIPLEXER_PCA9540BDP),y) diff --git a/drivers/i2c/i2c_slave_driver.c b/drivers/i2c/i2c_slave_driver.c new file mode 100644 index 0000000000..1cb71262e1 --- /dev/null +++ b/drivers/i2c/i2c_slave_driver.c @@ -0,0 +1,523 @@ +/**************************************************************************** + * drivers/i2c/i2c_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 + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define DEVNAME_FMT "/dev/i2cslv%d" +#define DEVNAME_FMTLEN (11 + 3 + 1) + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int i2c_slave_open(FAR struct file *filep); +static int i2c_slave_close(FAR struct file *filep); +static ssize_t i2c_slave_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t i2c_slave_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen); +static int i2c_slave_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup); + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS +static int i2c_slave_unlink(FAR struct inode *inode); +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct i2c_slave_driver_s +{ + /* Slave private data */ + + FAR struct i2c_slave_s *dev; + + /* Slave send buffer */ + + uint8_t write_buffer[CONFIG_I2C_SLAVE_WRITEBUFSIZE]; + + /* Slave receive buffer */ + + uint8_t read_buffer[CONFIG_I2C_SLAVE_READBUFSIZE]; + + /* Slave receive buffer length */ + + size_t read_length; + + /* Read buffer index */ + + size_t read_index; + + /* Wait for transfer to complete */ + + sem_t wait; + + /* Mutual exclusion */ + + mutex_t lock; + + /* The poll waiter */ + + FAR struct pollfd *fds; + + /* Number of open references */ + + int16_t crefs; + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + bool unlinked; /* Indicates if the driver has been unlinked */ +#endif +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_i2cslavefops = +{ + i2c_slave_open, /* open */ + i2c_slave_close, /* close */ + i2c_slave_read, /* read */ + i2c_slave_write, /* write */ + NULL, /* seek */ + NULL, /* ioctl */ + NULL, /* mmap */ + NULL, /* truncate */ + i2c_slave_poll /* poll */ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + , i2c_slave_unlink /* unlink */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: i2c_slave_open + * + * Description: + * This function is called whenever the I2C 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 i2c_slave_open(FAR struct file *filep) +{ + FAR struct i2c_slave_driver_s *priv; + + DEBUGASSERT(filep->f_inode->i_private != NULL); + + /* Get our private data structure */ + + priv = filep->f_inode->i_private; + + /* Get exclusive access to the I2C Slave driver state structure */ + + nxmutex_lock(&priv->lock); + + /* Increment the count of open references on the driver */ + + priv->crefs++; + DEBUGASSERT(priv->crefs > 0); + + nxmutex_unlock(&priv->lock); + return OK; +} + +/**************************************************************************** + * Name: i2c_slave_close + * + * Description: + * This routine is called when the I2C 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 i2c_slave_close(FAR struct file *filep) +{ + FAR struct i2c_slave_driver_s *priv; + + DEBUGASSERT(filep->f_inode->i_private != NULL); + + /* Get our private data structure */ + + priv = filep->f_inode->i_private; + + /* Get exclusive access to the I2C slave driver state structure */ + + nxmutex_lock(&priv->lock); + + /* Decrement the count of open references on the driver */ + + DEBUGASSERT(priv->crefs > 0); + priv->crefs--; +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + if (priv->crefs <= 0 && priv->unlinked) + { + nxmutex_destroy(&priv->lock); + kmm_free(priv); + filep->f_inode->i_private = NULL; + return OK; + } +#endif + + nxmutex_unlock(&priv->lock); + return OK; +} + +/**************************************************************************** + * Name: i2c_slave_read + * + * Description: + * This routine is called when the application requires to read the data + * received by the I2C 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 i2c_slave_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct i2c_slave_driver_s *priv; + int ret; + + DEBUGASSERT(filep->f_inode->i_private != NULL); + + /* Get our private data structure */ + + priv = filep->f_inode->i_private; + + /* Get exclusive access to the I2C Slave driver state structure */ + + nxmutex_lock(&priv->lock); + + while (priv->read_length == 0) + { + nxmutex_unlock(&priv->lock); + if (filep->f_oflags & O_NONBLOCK) + { + return -EAGAIN; + } + + ret = nxsem_wait(&priv->wait); + if (ret < 0) + { + return ret; + } + + nxmutex_lock(&priv->lock); + } + + buflen = MIN(buflen, priv->read_length); + memcpy(buffer, priv->read_buffer + priv->read_index, buflen); + priv->read_index += buflen; + priv->read_length -= buflen; + nxmutex_unlock(&priv->lock); + return buflen; +} + +/**************************************************************************** + * Name: i2c_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 I2C 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 i2c_slave_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen) +{ + FAR struct i2c_slave_driver_s *priv; + size_t write_bytes; + int ret; + + DEBUGASSERT(filep->f_inode->i_private != NULL); + + /* Get our private data structure */ + + priv = filep->f_inode->i_private; + + /* Get exclusive access to the I2C Slave driver state structure */ + + nxmutex_lock(&priv->lock); + + write_bytes = MIN(buflen, CONFIG_I2C_SLAVE_WRITEBUFSIZE); + memcpy(priv->write_buffer, buffer, write_bytes); + ret = I2CS_WRITE(priv->dev, priv->write_buffer, write_bytes); + nxmutex_unlock(&priv->lock); + return ret < 0 ? ret : write_bytes; +} + +/**************************************************************************** + * Name: i2c_slave_poll + ****************************************************************************/ + +static int i2c_slave_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct i2c_slave_driver_s *priv; + int ret; + + DEBUGASSERT(filep->f_inode->i_private != NULL); + + /* Get our private data structure */ + + priv = filep->f_inode->i_private; + + /* Get exclusive access to the I2C Slave driver state structure */ + + nxmutex_lock(&priv->lock); + if (setup) + { + pollevent_t eventset = 0; + + if (priv->fds == NULL) + { + priv->fds = fds; + fds->priv = &priv->fds; + } + else + { + ret = -EBUSY; + goto out; + } + + if (priv->read_length > 0) + { + eventset |= POLLIN; + } + + poll_notify(&priv->fds, 1, eventset); + } + else if (fds->priv != NULL) + { + priv->fds = NULL; + fds->priv = NULL; + } + +out: + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: i2c_slave_unlink + * + * Description: + * This routine is called when the I2C Slave device is unlinked. + * + * Input Parameters: + * inode - The inode associated with the I2C Slave device + * + * Returned Value: + * Zero is returned on success; a negated value is returned on any failure. + * + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS +static int i2c_slave_unlink(FAR struct inode *inode) +{ + FAR struct i2c_slave_driver_s *priv; + + DEBUGASSERT(inode->i_private != NULL); + + /* Get our private data structure */ + + priv = inode->i_private; + + /* Get exclusive access to the I2C Slave driver state structure */ + + nxmutex_lock(&priv->lock); + + /* 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 OK; +} +#endif + +static int i2c_slave_callback(FAR void *arg, size_t length) +{ + FAR struct i2c_slave_driver_s *priv = arg; + int semcount; + + /* Get exclusive access to the I2C Slave driver state structure */ + + nxmutex_lock(&priv->lock); + + priv->read_index = 0; + priv->read_length = length; + + while (nxsem_get_value(&priv->wait, &semcount) >= 0 && semcount <= 1) + { + nxsem_post(&priv->wait); + } + + nxmutex_unlock(&priv->lock); + + poll_notify(&priv->fds, 1, POLLIN); + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: i2c_slave_register + * + * Description: + * Register the I2C Slave character device driver as 'devpath'. + * + * Input Parameters: + * dev - An instance of the I2C Slave interface to use to communicate + * with the I2C Slave device + * bus - The I2C Slave bus number. This will be used as the I2C device + * minor number. The I2C Slave character device will be + * registered as /dev/i2cslvN where N is the minor number + * addr - I2C Slave address + * nbits - The number of address bits provided (7 or 10) + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int i2c_slave_register(FAR struct i2c_slave_s *dev, int bus, int addr, + int nbits) +{ + FAR struct i2c_slave_driver_s *priv; + char devname[DEVNAME_FMTLEN]; + int ret; + + /* Sanity check */ + + DEBUGASSERT(dev != NULL && (unsigned int)bus < 1000); + + priv = kmm_zalloc(sizeof(struct i2c_slave_driver_s)); + if (priv == NULL) + { + return -ENOMEM; + } + + snprintf(devname, DEVNAME_FMTLEN, DEVNAME_FMT, bus); + ret = register_driver(devname, &g_i2cslavefops, 0666, priv); + if (ret < 0) + { + kmm_free(priv); + return ret; + } + + nxsem_init(&priv->wait, 0, 0); + nxmutex_init(&priv->lock); + priv->dev = dev; + + ret = I2CS_SETOWNADDRESS(priv->dev, addr, nbits); + if (ret < 0) + { + goto out; + } + + ret = I2CS_READ(priv->dev, priv->read_buffer, + CONFIG_I2C_SLAVE_READBUFSIZE); + if (ret < 0) + { + goto out; + } + + ret = I2CS_REGISTERCALLBACK(priv->dev, i2c_slave_callback, priv); + if (ret > 0) + { + return OK; + } + +out: + nxmutex_destroy(&priv->lock); + nxsem_destroy(&priv->wait); + unregister_driver(devname); + kmm_free(priv); + return ret; +} diff --git a/include/nuttx/i2c/i2c_slave.h b/include/nuttx/i2c/i2c_slave.h index ef29ab6ced..ee33157958 100644 --- a/include/nuttx/i2c/i2c_slave.h +++ b/include/nuttx/i2c/i2c_slave.h @@ -223,6 +223,33 @@ extern "C" #define EXTERN extern #endif +#ifdef CONFIG_I2C_SLAVE_DRIVER + +/**************************************************************************** + * Name: i2c_slave_register + * + * Description: + * Register the I2C Slave character device driver as 'devpath'. + * + * Input Parameters: + * dev - An instance of the I2C Slave interface to use to communicate + * with the I2C Slave device + * bus - The I2C Slave bus number. This will be used as the I2C device + * minor number. The I2C Slave character device will be + * registered as /dev/i2cslvN where N is the minor number + * addr - I2C Slave address + * nbits - The number of address bits provided (7 or 10) + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int i2c_slave_register(FAR struct i2c_slave_s *dev, int bus, int addr, + int nbit); + +#endif + #undef EXTERN #if defined(__cplusplus) }