Gregory Nutt 5f9ee79298 I/O Expander: Remove hard-coded PCA9555 fields from ioexpander.h definitons. Add support for an attach() method that may be used when any subset of pin interrupts occur.
PCA9555 Driver:  Replace the signalling logic with a simple callback using the new definitons of ioexpander.h.  This repartitioning of functionality is necessary because (1) the I/O expander driver is the lower-lower part of any driver that uses GPIOs (include the GPIO driver itself) and should not be interacting directly with the much higher level application layer.  And (2) in order to be compatible with the GPIO driver (and any arbitrary upper half driver), the PCA9555 should not directly signal, but should call back into the upper half.  The upper half driver that interacts directly with the application is the appropriate place to be generating signal.
2016-07-31 11:09:47 -06:00

843 lines
22 KiB
C

/****************************************************************************
* drivers/ioexpander/pca9555.c
*
* Copyright (C) 2015, 2016 Gregory Nutt. All rights reserved.
* Author: Sebastien Lorquet <sebastien@lorquet.fr>
*
* References:
* "16-bit I2C-bus and SMBus I/O port with interrupt product datasheet",
* Rev. 08 - 22 October 2009, NXP
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <semaphore.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/irq.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/kmalloc.h>
#include <nuttx/ioexpander/ioexpander.h>
#include "pca9555.h"
#if defined(CONFIG_IOEXPANDER_PCA9555)
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifndef CONFIG_I2C
# warning I2C support is required (CONFIG_I2C)
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static inline int pca9555_write(FAR struct pca9555_dev_s *pca,
FAR const uint8_t *wbuffer, int wbuflen);
static inline int pca9555_writeread(FAR struct pca9555_dev_s *pca,
FAR const uint8_t *wbuffer, int wbuflen, FAR uint8_t *rbuffer,
int rbuflen);
static int pca9555_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin,
int dir);
static int pca9555_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,
int opt, void *val);
static int pca9555_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
bool value);
static int pca9555_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
FAR bool *value);
static int pca9555_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin,
FAR bool *value);
#ifdef CONFIG_IOEXPANDER_MULTIPIN
static int pca9555_multiwritepin(FAR struct ioexpander_dev_s *dev,
FAR uint8_t *pins, FAR bool *values, int count);
static int pca9555_multireadpin(FAR struct ioexpander_dev_s *dev,
FAR uint8_t *pins, FAR bool *values, int count);
static int pca9555_multireadbuf(FAR struct ioexpander_dev_s *dev,
FAR uint8_t *pins, FAR bool *values, int count);
#endif
#ifdef CONFIG_IOEXPANDER_INT_ENABLE
static int pca9555_attach(FAR struct ioexpander_dev_s *dev,
ioe_pinset_t pinset, ioe_callback_t callback);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
#ifndef CONFIG_PCA9555_MULTIPLE
/* If only a single PCA9555 device is supported, then the driver state
* structure may as well be pre-allocated.
*/
static struct pca9555_dev_s g_pca9555;
/* Otherwise, we will need to maintain allocated driver instances in a list */
#else
static struct pca9555_dev_s *g_pca9555list;
#endif
/* I/O expander vtable */
static const struct ioexpander_ops_s g_pca9555_ops =
{
pca9555_direction,
pca9555_option,
pca9555_writepin,
pca9555_readpin,
pca9555_readbuf
#ifdef CONFIG_IOEXPANDER_MULTIPIN
, pca9555_multiwritepin
, pca9555_multireadpin
, pca9555_multireadbuf
#endif
#ifdef CONFIG_IOEXPANDER_INT_ENABLE
, pca9555_attach
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: pca9555_lock
*
* Description:
* Get exclusive access to the PCA9555
*
****************************************************************************/
static void pca9555_lock(FAR struct pca9555_dev_s *pca)
{
while (sem_wait(&pca->exclsem) < 0)
{
/* EINTR is the only expected error from sem_wait() */
DEBUGASSERT(errno == EINTR);
}
}
#define pca9555_unlock(p) sem_post(&(p)->exclsem)
/****************************************************************************
* Name: pca9555_write
*
* Description:
* Write to the I2C device.
*
****************************************************************************/
static inline int pca9555_write(FAR struct pca9555_dev_s *pca,
FAR const uint8_t *wbuffer, int wbuflen)
{
struct i2c_msg_s msg;
/* Setup for the transfer */
msg.frequency = pca->config->frequency;
msg.addr = pca->config->address;
msg.flags = 0;
msg.buffer = (FAR uint8_t *)wbuffer; /* Override const */
msg.length = wbuflen;
/* Then perform the transfer. */
return I2C_TRANSFER(pca->i2c, &msg, 1);
}
/****************************************************************************
* Name: pca9555_writeread
*
* Description:
* Write to then read from the I2C device.
*
****************************************************************************/
static inline int pca9555_writeread(FAR struct pca9555_dev_s *pca,
FAR const uint8_t *wbuffer, int wbuflen,
FAR uint8_t *rbuffer, int rbuflen)
{
struct i2c_config_s config;
/* Set up the configuration and perform the write-read operation */
config.frequency = pca->config->frequency;
config.address = pca->config->address;
config.addrlen = 7;
return i2c_writeread(pca->i2c, &config, wbuffer, wbuflen, rbuffer, rbuflen);
}
/****************************************************************************
* Name: pca9555_setbit
*
* Description:
* Write a bit in a register pair
*
****************************************************************************/
static int pca9555_setbit(FAR struct pca9555_dev_s *pca, uint8_t addr,
uint8_t pin, int bitval)
{
uint8_t buf[2];
int ret;
if (pin > 15)
{
return -ENXIO;
}
else if (pin > 7)
{
addr++;
pin -= 8;
}
buf[0] = addr;
#ifdef CONFIG_IOEXPANDER_SHADOW_MODE
/* Get the shadowed register value */
buf[1] = pca->sreg[addr];
#else
/* Get the register value from the IO-Expander */
ret = pca9555_writeread(pca, &buf[0], 1, &buf[1], 1);
if (ret < 0)
{
return ret;
}
#endif
if (bitval)
{
buf[1] |= (1 << pin);
}
else
{
buf[1] &= ~(1 << pin);
}
#ifdef CONFIG_IOEXPANDER_SHADOW_MODE
/* Save the new register value in the shadow register */
pca->sreg[addr] = buf[1];
#endif
ret = pca9555_write(pca, buf, 2);
#ifdef CONFIG_IOEXPANDER_RETRY
if (ret != OK)
{
/* Try again (only once) */
ret = pca9555_write(pca, buf, 2);
}
#endif
return ret;
}
/****************************************************************************
* Name: pca9555_getbit
*
* Description:
* Get a bit from a register pair
*
****************************************************************************/
static int pca9555_getbit(FAR struct pca9555_dev_s *pca, uint8_t addr,
uint8_t pin, FAR bool *val)
{
uint8_t buf;
int ret;
if (pin > 15)
{
return -ENXIO;
}
else if (pin > 7)
{
addr += 1;
pin -= 8;
}
ret = pca9555_writeread(pca, &addr, 1, &buf, 1);
if (ret < 0)
{
return ret;
}
#ifdef CONFIG_IOEXPANDER_SHADOW_MODE
/* Save the new register value in the shadow register */
pca->sreg[addr] = buf;
#endif
*val = (buf >> pin) & 1;
return OK;
}
/****************************************************************************
* Name: pca9555_direction
*
* Description:
* See include/nuttx/ioexpander/ioexpander.h
*
****************************************************************************/
static int pca9555_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin,
int direction)
{
FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s *)dev;
int ret;
/* Get exclusive access to the PCA555 */
pca9555_lock(pca);
ret = pca9555_setbit(pca, PCA9555_REG_CONFIG, pin,
(direction == IOEXPANDER_DIRECTION_IN));
pca9555_unlock(pca);
return ret;
}
/****************************************************************************
* Name: pca9555_option
*
* Description:
* See include/nuttx/ioexpander/ioexpander.h
*
****************************************************************************/
static int pca9555_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,
int opt, FAR void *val)
{
FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s *)dev;
int ival = (int)val;
int ret = -EINVAL;
if (opt == IOEXPANDER_OPTION_INVERT)
{
/* Get exclusive access to the PCA555 */
pca9555_lock(pca);
ret = pca9555_setbit(pca, PCA9555_REG_POLINV, pin, ival);
pca9555_unlock(pca);
}
return ret;
}
/****************************************************************************
* Name: pca9555_writepin
*
* Description:
* See include/nuttx/ioexpander/ioexpander.h
*
****************************************************************************/
static int pca9555_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
bool value)
{
FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s *)dev;
int ret;
/* Get exclusive access to the PCA555 */
pca9555_lock(pca);
ret = pca9555_setbit(pca, PCA9555_REG_OUTPUT, pin, value);
pca9555_unlock(pca);
return ret;
}
/****************************************************************************
* Name: pca9555_readpin
*
* Description:
* See include/nuttx/ioexpander/ioexpander.h
*
****************************************************************************/
static int pca9555_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
FAR bool *value)
{
FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s *)dev;
int ret;
/* Get exclusive access to the PCA555 */
pca9555_lock(pca);
ret = pca9555_getbit(pca, PCA9555_REG_INPUT, pin, value);
pca9555_unlock(pca);
return ret;
}
/****************************************************************************
* Name: pca9555_readbuf
*
* Description:
* See include/nuttx/ioexpander/ioexpander.h
*
****************************************************************************/
static int pca9555_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin,
FAR bool *value)
{
FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s *)dev;
int ret;
/* Get exclusive access to the PCA555 */
pca9555_lock(pca);
ret = pca9555_getbit(pca, PCA9555_REG_OUTPUT, pin, value);
pca9555_unlock(pca);
return ret;
}
#ifdef CONFIG_IOEXPANDER_MULTIPIN
/****************************************************************************
* Name: pca9555_getmultibits
*
* Description:
* Read multiple bits from PCA9555 registers.
*
****************************************************************************/
static int pca9555_getmultibits(FAR struct pca9555_dev_s *pca, uint8_t addr,
FAR uint8_t *pins, FAR bool *values,
int count)
{
uint8_t buf[2];
int ret = OK;
int i;
int index;
int pin;
ret = pca9555_writeread(pca, &addr, 1, buf, 2);
if (ret < 0)
{
return ret;
}
#ifdef CONFIG_IOEXPANDER_SHADOW_MODE
/* Save the new register value in the shadow register */
pca->sreg[addr] = buf[0];
pca->sreg[addr+1] = buf[1];
#endif
/* Read the requested bits */
for (i = 0; i < count; i++)
{
index = 0;
pin = pins[i];
if (pin > 15)
{
return -ENXIO;
}
else if (pin > 7)
{
index = 1;
pin -= 8;
}
values[i] = (buf[index] >> pin) & 1;
}
return OK;
}
/****************************************************************************
* Name: pca9555_multiwritepin
*
* Description:
* See include/nuttx/ioexpander/ioexpander.h
*
****************************************************************************/
static int pca9555_multiwritepin(FAR struct ioexpander_dev_s *dev,
FAR uint8_t *pins, FAR bool *values,
int count)
{
FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s *)dev;
uint8_t addr = PCA9555_REG_OUTPUT;
uint8_t buf[3];
int ret;
int i;
int index;
int pin;
/* Get exclusive access to the PCA555 */
pca9555_lock(pca);
/* Start by reading both registers, whatever the pins to change. We could
* attempt to read one port only if all pins were on the same port, but
* this would not save much.
*/
#ifndef CONFIG_IOEXPANDER_SHADOW_MODE
ret = pca9555_writeread(pca, &addr, 1, &buf[1], 2);
if (ret < 0)
{
pca9555_unlock(pca);
return ret;
}
#else
/* In Shadow-Mode we "read" the pin status from the shadow registers */
buf[1] = pca->sreg[addr];
buf[2] = pca->sreg[addr+1];
#endif
/* Apply the user defined changes */
for (i = 0; i < count; i++)
{
index = 1;
pin = pins[i];
if (pin > 15)
{
pca9555_unlock(pca);
return -ENXIO;
}
else if (pin > 7)
{
index = 2;
pin -= 8;
}
if (values[i])
{
buf[index] |= (1 << pin);
}
else
{
buf[index] &= ~(1 << pin);
}
}
/* Now write back the new pins states */
buf[0] = addr;
#ifdef CONFIG_IOEXPANDER_SHADOW_MODE
/* Save the new register values in the shadow register */
pca->sreg[addr] = buf[1];
pca->sreg[addr+1] = buf[2];
#endif
ret = pca9555_write(pca, buf, 3);
pca9555_unlock(pca);
return ret;
}
/****************************************************************************
* Name: pca9555_multireadpin
*
* Description:
* See include/nuttx/ioexpander/ioexpander.h
*
****************************************************************************/
static int pca9555_multireadpin(FAR struct ioexpander_dev_s *dev,
FAR uint8_t *pins, FAR bool *values,
int count)
{
FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s *)dev;
int ret;
/* Get exclusive access to the PCA555 */
pca9555_lock(pca);
ret = pca9555_getmultibits(pca, PCA9555_REG_INPUT,
pins, values, count);
pca9555_unlock(pca);
return ret;
}
/****************************************************************************
* Name: pca9555_multireadbuf
*
* Description:
* See include/nuttx/ioexpander/ioexpander.h
*
****************************************************************************/
static int pca9555_multireadbuf(FAR struct ioexpander_dev_s *dev,
FAR uint8_t *pins, FAR bool *values,
int count)
{
FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s *)dev;
int ret;
/* Get exclusive access to the PCA555 */
pca9555_lock(pca);
ret = pca9555_getmultibits(pca, PCA9555_REG_OUTPUT,
pins, values, count);
pca9555_unlock(pca);
return ret;
}
#endif
#ifdef CONFIG_PCA9555_INT_ENABLE
/****************************************************************************
* Name: pca9555_attach
*
* Description:
* Attach a pin interrupt callback function.
*
* Input Parameters:
* dev - Device-specific state data
* pinset - The set of pin events that will generate the callback
* callback - The pointer to callback function. NULL will detach the
* callback.
*
* Returned Value:
* 0 on success, else a negative error code
*
****************************************************************************/
static int pca9555_attach(FAR struct ioexpander_dev_s *dev,
ioe_pinset_t pinset, ioe_callback_t callback)
{
FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s *)dev;
int ret;
int i;
/* Get exclusive access to the PCA555 */
pca9555_lock(pca);
/* Find and available in entry in the callback table */
ret = -ENOSPC;
for (i = 0; i < CONFIG_PCA9555_INT_NCALLBACKS; i++)
{
/* Is this entry available (i.e., no callback attached) */
if (pca->cb[i].cbfunc == NULL)
{
/* Yes.. use this entry */
pca->cb[i].pinset = pinset;
pca->cb[i].cbfunc = callback;
ret = OK;
}
}
/* Add this callback to the table */
pca9555_unlock(pca);
return ret;
}
/****************************************************************************
* Name: pca9555_irqworker
*
* Description:
* Handle GPIO interrupt events (this function actually executes in the
* context of the worker thread).
*
****************************************************************************/
static void pca9555_irqworker(void *arg)
{
FAR struct pca9555_dev_s *pca = (FAR struct pca9555_dev_s *)arg;
uint8_t addr = PCA9555_REG_INPUT;
uint8_t buf[2];
ioe_pinset_t pinset;
int ret;
int i;
/* Read inputs */
ret = pca9555_writeread(pca, &addr, 1, buf, 2);
if (ret == OK)
{
#ifdef CONFIG_IOEXPANDER_SHADOW_MODE
/* Don't forget to update the shadow registers at this point */
pca->sreg[addr] = buf[0];
pca->sreg[addr+1] = buf[1];
#endif
/* Create a 16-bit pinset */
pinset = ((unsigned int)buf[0] << 8) | buf[1];
/* Perform pin interrupt callbacks */
for (i = 0; i < CONFIG_PCA9555_INT_NCALLBACKS; i++)
{
/* Is this entry valid (i.e., callback attached)? If so, did
* any of the requested pin interrupts occur?
*/
if (pca->cb[i].cbfunc != NULL)
{
/* Did any of the requested pin interrupts occur? */
ioe_pinset_t match = pinset & pca->cb[i].pinset;
if (match != 0)
{
/* Yes.. perform the callback */
(void)pca->cb[i].cbfunc(&pca->dev, match);
}
}
}
}
/* Re-enable interrupts */
pca->config->enable(pca->config, TRUE);
}
/****************************************************************************
* Name: pca9555_interrupt
*
* Description:
* Handle GPIO interrupt events (this function executes in the
* context of the interrupt).
*
****************************************************************************/
static int pca9555_interrupt(int irq, FAR void *context)
{
#ifdef CONFIG_PCA9555_MULTIPLE
/* To support multiple devices,
* retrieve the pca structure using the irq number.
*/
# warning Missing logic
#else
register FAR struct pca9555_dev_s *pca = &g_pca9555;
#endif
/* In complex environments, we cannot do I2C transfers from the interrupt
* handler because semaphores are probably used to lock the I2C bus. In
* this case, we will defer processing to the worker thread. This is also
* much kinder in the use of system resources and is, therefore, probably
* a good thing to do in any event.
*/
/* Notice that further GPIO interrupts are disabled until the work is
* actually performed. This is to prevent overrun of the worker thread.
* Interrupts are re-enabled in pca9555_irqworker() when the work is
* completed.
*/
if (work_available(&pca->work))
{
pca->config->enable(pca->config, FALSE);
work_queue(HPWORK, &pca->work, pca9555_irqworker,
(FAR void *)pca, 0);
}
return OK;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: pca9555_initialize
*
* Description:
* Initialize a PCA9555 I2C device.
*
* TODO: Actually support more than one device.
*
****************************************************************************/
FAR struct ioexpander_dev_s *pca9555_initialize(FAR struct i2c_master_s *i2cdev,
FAR struct pca9555_config_s *config)
{
FAR struct pca9555_dev_s *pcadev;
DEBUGASSERT(i2cdev != NULL && config != NULL);
#ifdef CONFIG_PCA9555_MULTIPLE
/* Allocate the device state structure */
pcadev = (FAR struct pca9555_dev_s *)kmm_zalloc(sizeof(struct pca9555_dev_s));
if (!pcadev)
{
return NULL;
}
/* And save the device structure in the list of PCA9555 so that we can
* find it later.
*/
pcadev->flink = g_pca9555list;
g_pca9555list = pcadev;
#else
/* Use the one-and-only PCA9555 driver instance */
pcadev = &g_pca9555;
#endif
/* Initialize the device state structure */
pcadev->i2c = i2cdev;
pcadev->dev.ops = &g_pca9555_ops;
pcadev->config = config;
#ifdef CONFIG_PCA9555_INT_ENABLE
pcadev->config->attach(pcadev->config, pca9555_interrupt);
pcadev->config->enable(pcadev->config, TRUE);
#endif
sem_init(&pcadev->exclsem, 0, 1);
return &pcadev->dev;
}
#endif /* CONFIG_IOEXPANDER_PCA9555 */