12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421 |
- /****************************************************************************
- * drivers/ioexpander/tca64xx.h
- * Supports the following parts: TCA6408, TCA6416, TCA6424
- *
- * Copyright (C) 2016-2017 Gregory Nutt. All rights reserved.
- * Author: Gregory Nutt <gnutt@nuttx.org>
- *
- * This header file derives, in part, from the Project Ara TCA64xx driver
- * which has this copyright:
- *
- * Copyright (c) 2014-2015 Google Inc.
- * All rights reserved.
- * Author: Patrick Titiano, Jean Pihet
- *
- * 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 of the copyright holder 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 HOLDER 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/kmalloc.h>
- #include <nuttx/wdog.h>
- #include <nuttx/ioexpander/ioexpander.h>
- #include <nuttx/ioexpander/tca64xx.h>
- #include "tca64xx.h"
- #ifdef CONFIG_IOEXPANDER_TCA64XX
- /****************************************************************************
- * Pre-processor Definitions
- ****************************************************************************/
- #ifndef MAX
- # define MAX(a,b) (((a) > (b)) ? (a) : (b))
- #endif
- #ifndef MIN
- # define MIN(a,b) (((a) < (b)) ? (a) : (b))
- #endif
- /****************************************************************************
- * Private Function Prototypes
- ****************************************************************************/
- /* TCA64xx Helpers */
- static void tca64_lock(FAR struct tca64_dev_s *priv);
- static FAR const struct tca64_part_s *tca64_getpart(FAR struct tca64_dev_s *priv);
- static uint8_t tca64_ngpios(FAR struct tca64_dev_s *priv);
- static uint8_t tca64_input_reg(FAR struct tca64_dev_s *priv, uint8_t pin);
- static uint8_t tca64_output_reg(FAR struct tca64_dev_s *priv, uint8_t pin);
- static uint8_t tca64_polarity_reg(FAR struct tca64_dev_s *priv, uint8_t pin);
- static uint8_t tca64_config_reg(FAR struct tca64_dev_s *priv, uint8_t pin);
- static int tca64_getreg(FAR struct tca64_dev_s *priv, uint8_t regaddr,
- FAR uint8_t *regval, unsigned int count);
- static int tca64_putreg(struct tca64_dev_s *priv, uint8_t regaddr,
- FAR uint8_t *regval, unsigned int count);
- /* I/O Expander Methods */
- static int tca64_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin,
- int dir);
- static int tca64_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,
- int opt, void *regval);
- static int tca64_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
- bool value);
- static int tca64_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
- FAR bool *value);
- #ifdef CONFIG_IOEXPANDER_MULTIPIN
- static int tca64_multiwritepin(FAR struct ioexpander_dev_s *dev,
- FAR uint8_t *pins, FAR bool *values, int count);
- static int tca64_multireadpin(FAR struct ioexpander_dev_s *dev,
- FAR uint8_t *pins, FAR bool *values, int count);
- #endif
- #ifdef CONFIG_IOEXPANDER_INT_ENABLE
- static FAR void *tca64_attach(FAR struct ioexpander_dev_s *dev,
- ioe_pinset_t pinset, ioe_callback_t callback, FAR void *arg);
- static int tca64_detach(FAR struct ioexpander_dev_s *dev, FAR void *handle);
- #endif
- #ifdef CONFIG_TCA64XX_INT_ENABLE
- static void tca64_int_update(FAR struct tca64_dev_s *priv,
- ioe_pinset_t input, ioe_pinset_t mask);
- static void tca64_register_update(FAR struct tca64_dev_s *priv);
- static void tca64_irqworker(void *arg);
- static void tca64_interrupt(FAR void *arg);
- #ifdef CONFIG_TCA64XX_INT_POLL
- static void tca64_poll_expiry(int argc, wdparm_t arg1, ...);
- #endif
- #endif
- /****************************************************************************
- * Private Data
- ****************************************************************************/
- #ifndef CONFIG_TCA64XX_MULTIPLE
- /* If only a single device is supported, then the driver state structure may
- * as well be pre-allocated.
- */
- static struct tca64_dev_s g_tca64;
- #endif
- /* I/O expander vtable */
- static const struct ioexpander_ops_s g_tca64_ops =
- {
- tca64_direction,
- tca64_option,
- tca64_writepin,
- tca64_readpin,
- tca64_readpin
- #ifdef CONFIG_IOEXPANDER_MULTIPIN
- , tca64_multiwritepin
- , tca64_multireadpin
- , tca64_multireadpin
- #endif
- #ifdef CONFIG_IOEXPANDER_INT_ENABLE
- , tca64_attach
- , tca64_detach
- #endif
- };
- /* TCA64 part data */
- static const struct tca64_part_s g_tca64_parts[TCA64_NPARTS] =
- {
- {
- TCA6408_PART,
- MIN(TCA6408_NR_GPIOS, CONFIG_IOEXPANDER_NPINS),
- TCA6408_INPUT_REG,
- TCA6408_OUTPUT_REG,
- TCA6408_POLARITY_REG,
- TCA6408_CONFIG_REG,
- },
- {
- TCA6416_PART,
- MIN(TCA6416_NR_GPIOS, CONFIG_IOEXPANDER_NPINS),
- TCA6416_INPUT0_REG,
- TCA6416_OUTPUT0_REG,
- TCA6416_POLARITY0_REG,
- TCA6416_CONFIG0_REG,
- },
- {
- TCA6424_PART,
- MIN(TCA6424_NR_GPIOS, CONFIG_IOEXPANDER_NPINS),
- TCA6424_INPUT0_REG,
- TCA6424_OUTPUT0_REG,
- TCA6424_POLARITY0_REG,
- TCA6424_CONFIG0_REG,
- },
- };
- /****************************************************************************
- * Private Functions
- ****************************************************************************/
- /****************************************************************************
- * Name: tca64_lock
- *
- * Description:
- * Get exclusive access to the I/O Expander
- *
- ****************************************************************************/
- static void tca64_lock(FAR struct tca64_dev_s *priv)
- {
- int ret;
- do
- {
- /* Take the semaphore (perhaps waiting) */
- ret = nxsem_wait(&priv->exclsem);
- /* The only case that an error should occur here is if the wait was
- * awakened by a signal.
- */
- DEBUGASSERT(ret == OK || ret == -EINTR);
- }
- while (ret == -EINTR);
- }
- #define tca64_unlock(p) nxsem_post(&(p)->exclsem)
- /****************************************************************************
- * Name: tca64_getpart
- *
- * Description:
- * Look up information for the selected part
- *
- ****************************************************************************/
- static FAR const struct tca64_part_s *tca64_getpart(FAR struct tca64_dev_s *priv)
- {
- DEBUGASSERT(priv != NULL && priv->config != NULL &&
- priv->config->part < TCA64_NPARTS);
- return &g_tca64_parts[priv->config->part];
- }
- /****************************************************************************
- * Name: tca64_ngpios
- *
- * Description:
- * Return the number of GPIOs supported by the selected part
- *
- ****************************************************************************/
- static uint8_t tca64_ngpios(FAR struct tca64_dev_s *priv)
- {
- FAR const struct tca64_part_s *part = tca64_getpart(priv);
- return part->tp_ngpios;
- }
- /****************************************************************************
- * Name: tca64_input_reg
- *
- * Description:
- * Return the address of the input register for the specified pin.
- *
- ****************************************************************************/
- static uint8_t tca64_input_reg(FAR struct tca64_dev_s *priv, uint8_t pin)
- {
- FAR const struct tca64_part_s *part = tca64_getpart(priv);
- uint8_t reg = part->tp_input;
- DEBUGASSERT(pin <= part->tp_ngpios);
- return reg + (pin >> 3);
- }
- /****************************************************************************
- * Name: tca64_output_reg
- *
- * Description:
- * Return the address of the output register for the specified pin.
- *
- ****************************************************************************/
- static uint8_t tca64_output_reg(FAR struct tca64_dev_s *priv, uint8_t pin)
- {
- FAR const struct tca64_part_s *part = tca64_getpart(priv);
- uint8_t reg = part->tp_output;
- DEBUGASSERT(pin <= part->tp_ngpios);
- return reg + (pin >> 3);
- }
- /****************************************************************************
- * Name: tca64_polarity_reg
- *
- * Description:
- * Return the address of the polarity register for the specified pin.
- *
- ****************************************************************************/
- static uint8_t tca64_polarity_reg(FAR struct tca64_dev_s *priv, uint8_t pin)
- {
- FAR const struct tca64_part_s *part = tca64_getpart(priv);
- uint8_t reg = part->tp_output;
- DEBUGASSERT(pin <= part->tp_ngpios);
- return reg + (pin >> 3);
- }
- /****************************************************************************
- * Name: tca64_config_reg
- *
- * Description:
- * Return the address of the configuration register for the specified pin.
- *
- ****************************************************************************/
- static uint8_t tca64_config_reg(FAR struct tca64_dev_s *priv, uint8_t pin)
- {
- FAR const struct tca64_part_s *part = tca64_getpart(priv);
- uint8_t reg = part->tp_config;
- DEBUGASSERT(pin <= part->tp_ngpios);
- return reg + (pin >> 3);
- }
- /****************************************************************************
- * Name: tca64_getreg
- *
- * Description:
- * Read an 8-bit value from a TCA64xx register
- *
- ****************************************************************************/
- static int tca64_getreg(FAR struct tca64_dev_s *priv, uint8_t regaddr,
- FAR uint8_t *regval, unsigned int count)
- {
- struct i2c_msg_s msg[2];
- int ret;
- DEBUGASSERT(priv != NULL && priv->i2c != NULL && priv->config != NULL);
- /* Set up for the transfer */
- msg[0].frequency = TCA64XX_I2C_MAXFREQUENCY,
- msg[0].addr = priv->config->address,
- msg[0].flags = 0,
- msg[0].buffer = ®addr,
- msg[0].length = 1,
- msg[1].frequency = TCA64XX_I2C_MAXFREQUENCY,
- msg[1].addr = priv->config->address,
- msg[1].flags = I2C_M_READ,
- msg[1].buffer = regval,
- msg[1].length = count,
- /* Perform the transfer */
- ret = I2C_TRANSFER(priv->i2c, msg, 2);
- if (ret < 0)
- {
- gpioerr("ERROR: I2C addr=%02x regaddr=%02x: failed, ret=%d!\n",
- priv->config->address, regaddr, ret);
- return ret;
- }
- else
- {
- gpioinfo("I2C addr=%02x regaddr=%02x: read %02x\n",
- priv->config->address, regaddr, *regval);
- return OK;
- }
- }
- /****************************************************************************
- * Name: tca64_putreg
- *
- * Description:
- * Write an 8-bit value to a TCA64xx register
- *
- ****************************************************************************/
- static int tca64_putreg(struct tca64_dev_s *priv, uint8_t regaddr,
- FAR uint8_t *regval, unsigned int count)
- {
- struct i2c_msg_s msg[1];
- uint8_t cmd[2];
- int ret;
- int i;
- DEBUGASSERT(priv != NULL && priv->i2c != NULL && priv->config != NULL);
- /* Set up for the transfer */
- cmd[0] = regaddr;
- for (i = 0; i < count; i++)
- {
- cmd[i+1] = regval[i];
- }
- msg[0].frequency = TCA64XX_I2C_MAXFREQUENCY,
- msg[0].addr = priv->config->address,
- msg[0].flags = 0,
- msg[0].buffer = cmd,
- msg[0].length = count + 1,
- ret = I2C_TRANSFER(priv->i2c, msg, 1);
- if (ret < 0)
- {
- gpioerr("ERROR: claddr=%02x, regaddr=%02x: failed, ret=%d!\n",
- priv->config->address, regaddr, ret);
- return ret;
- }
- else
- {
- gpioinfo("claddr=%02x, regaddr=%02x, regval=%02x\n",
- priv->config->address, regaddr, regval);
- return OK;
- }
- }
- /****************************************************************************
- * Name: tca64_direction
- *
- * Description:
- * Set the direction of an ioexpander pin. Required.
- *
- * Input Parameters:
- * dev - Device-specific state data
- * pin - The index of the pin to alter in this call
- * dir - One of the IOEXPANDER_DIRECTION_ macros
- *
- * Returned Value:
- * 0 on success, else a negative error code
- *
- ****************************************************************************/
- static int tca64_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin,
- int direction)
- {
- FAR struct tca64_dev_s *priv = (FAR struct tca64_dev_s *)dev;
- uint8_t regaddr;
- uint8_t regval;
- int ret;
- DEBUGASSERT(priv != NULL && priv->config != NULL &&
- pin < CONFIG_IOEXPANDER_NPINS &&
- (direction == IOEXPANDER_DIRECTION_IN ||
- direction == IOEXPANDER_DIRECTION_OUT));
- gpioinfo("I2C addr=%02x pin=%u direction=%s\n",
- priv->config->address, pin,
- (direction == IOEXPANDER_DIRECTION_IN) ? "IN" : "OUT");
- /* Get exclusive access to the I/O Expander */
- tca64_lock(priv);
- /* Read the Configuration Register associated with this pin. The
- * Configuration Register configures the direction of the I/O pins.
- */
- regaddr = tca64_config_reg(priv, pin);
- ret = tca64_getreg(priv, regaddr, ®val, 1);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to read config register at %u: %d\n",
- regaddr, ret);
- goto errout_with_lock;
- }
- /* Set the pin direction in the I/O Expander */
- if (direction == IOEXPANDER_DIRECTION_IN)
- {
- /* Configure pin as input. If a bit in the configuration register is
- * set to 1, the corresponding port pin is enabled as an input with a
- * high-impedance output driver.
- */
- regval |= (1 << (pin & 7));
- }
- else /* if (direction == IOEXPANDER_DIRECTION_OUT) */
- {
- /* Configure pin as output. If a bit in this register is cleared to
- * 0, the corresponding port pin is enabled as an output.
- *
- * REVISIT: The value of output has not been selected! This might
- * put a glitch on the output.
- */
- regval &= ~(1 << (pin & 7));
- }
- /* Write back the modified register content */
- ret = tca64_putreg(priv, regaddr, ®val, 1);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to write config register at %u: %d\n",
- regaddr, ret);
- }
- errout_with_lock:
- tca64_unlock(priv);
- return ret;
- }
- /****************************************************************************
- * Name: tca64_option
- *
- * Description:
- * Set pin options. Required.
- * Since all IO expanders have various pin options, this API allows setting
- * pin options in a flexible way.
- *
- * Input Parameters:
- * dev - Device-specific state data
- * pin - The index of the pin to alter in this call
- * opt - One of the IOEXPANDER_OPTION_ macros
- * val - The option's value
- *
- * Returned Value:
- * 0 on success, else a negative error code
- *
- ****************************************************************************/
- static int tca64_option(FAR struct ioexpander_dev_s *dev, uint8_t pin,
- int opt, FAR void *value)
- {
- FAR struct tca64_dev_s *priv = (FAR struct tca64_dev_s *)dev;
- int ret = -ENOSYS;
- DEBUGASSERT(priv != NULL && priv->config != NULL);
- gpioinfo("I2C addr=%02x pin=%u option=%u\n",
- priv->config->address, pin, opt);
- /* Check for pin polarity inversion. The Polarity Inversion Register
- * allows polarity inversion of pins defined as inputs by the
- * Configuration Register. If a bit in this register is set, the
- * corresponding port pin's polarity is inverted. If a bit in this
- * register is cleared, the corresponding port pin's original polarity
- * is retained.
- */
- if (opt == IOEXPANDER_OPTION_INVERT)
- {
- unsigned int ival = (unsigned int)((uintptr_t)value);
- uint8_t regaddr;
- uint8_t polarity;
- /* Get exclusive access to the I/O Expander */
- tca64_lock(priv);
- /* Read the polarity register */
- regaddr = tca64_polarity_reg(priv, pin);
- ret = tca64_getreg(priv, regaddr, &polarity, 1);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to read polarity register at %u: %d\n",
- regaddr, ret);
- tca64_unlock(priv);
- return ret;
- }
- /* Set/clear the pin option */
- if (ival == IOEXPANDER_OPTION_INVERT)
- {
- polarity |= (1 << (pin & 7));
- }
- else
- {
- polarity &= ~(1 << (pin & 7));
- }
- /* Write back the modified register */
- ret = tca64_putreg(priv, regaddr, &polarity, 1);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to read polarity register at %u: %d\n",
- regaddr, ret);
- }
- tca64_unlock(priv);
- }
- #ifdef CONFIG_TCA64XX_INT_ENABLE
- /* Interrupt configuration */
- else if (opt == IOEXPANDER_OPTION_INTCFG)
- {
- unsigned int ival = (unsigned int)((uintptr_t)value);
- ioe_pinset_t bit = ((ioe_pinset_t)1 << pin);
- ret = OK;
- tca64_lock(priv);
- switch (ival)
- {
- case IOEXPANDER_VAL_HIGH: /* Interrupt on high level */
- priv->trigger &= ~bit;
- priv->level[0] |= bit;
- priv->level[1] &= ~bit;
- break;
- case IOEXPANDER_VAL_LOW: /* Interrupt on low level */
- priv->trigger &= ~bit;
- priv->level[0] &= ~bit;
- priv->level[1] |= bit;
- break;
- case IOEXPANDER_VAL_RISING: /* Interrupt on rising edge */
- priv->trigger |= bit;
- priv->level[0] |= bit;
- priv->level[1] &= ~bit;
- break;
- case IOEXPANDER_VAL_FALLING: /* Interrupt on falling edge */
- priv->trigger |= bit;
- priv->level[0] &= ~bit;
- priv->level[1] |= bit;
- break;
- case IOEXPANDER_VAL_BOTH: /* Interrupt on both edges */
- priv->trigger |= bit;
- priv->level[0] |= bit;
- priv->level[1] |= bit;
- break;
- case IOEXPANDER_VAL_DISABLE:
- break;
- default:
- ret = -EINVAL;
- }
- tca64_unlock(priv);
- }
- #endif
- return ret;
- }
- /****************************************************************************
- * Name: tca64_writepin
- *
- * Description:
- * Set the pin level. Required.
- *
- * Input Parameters:
- * dev - Device-specific state data
- * pin - The index of the pin to alter in this call
- * val - The pin level. Usually TRUE will set the pin high,
- * except if OPTION_INVERT has been set on this pin.
- *
- * Returned Value:
- * 0 on success, else a negative error code
- *
- ****************************************************************************/
- static int tca64_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
- bool value)
- {
- FAR struct tca64_dev_s *priv = (FAR struct tca64_dev_s *)dev;
- uint8_t regaddr;
- uint8_t regval;
- int ret;
- DEBUGASSERT(priv != NULL && priv->config != NULL &&
- pin < CONFIG_IOEXPANDER_NPINS);
- gpioinfo("I2C addr=%02x pin=%u value=%u\n",
- priv->config->address, pin, value);
- /* Get exclusive access to the I/O Expander */
- tca64_lock(priv);
- /* Read the output register. */
- regaddr = tca64_output_reg(priv, pin);
- ret = tca64_getreg(priv, regaddr, ®val, 1);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to read output register at %u: %d\n",
- regaddr, ret);
- goto errout_with_lock;
- }
- /* Set output pins default value (before configuring it as output) The
- * Output Port Register shows the outgoing logic levels of the pins
- * defined as outputs by the Configuration Register.
- */
- if (value != 0)
- {
- regval |= (1 << (pin & 7));
- }
- else
- {
- regval &= ~(1 << (pin & 7));
- }
- /* Write the modified output register value */
- ret = tca64_putreg(priv, regaddr, ®val, 1);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to write output register at %u: %d\n",
- regaddr, ret);
- }
- errout_with_lock:
- tca64_unlock(priv);
- return ret;
- }
- /****************************************************************************
- * Name: tca64_readpin
- *
- * Description:
- * Read the actual PIN level. This can be different from the last value written
- * to this pin. Required.
- *
- * Input Parameters:
- * dev - Device-specific state data
- * pin - The index of the pin
- * valptr - Pointer to a buffer where the pin level is stored. Usually TRUE
- * if the pin is high, except if OPTION_INVERT has been set on this pin.
- *
- * Returned Value:
- * 0 on success, else a negative error code
- *
- ****************************************************************************/
- static int tca64_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin,
- FAR bool *value)
- {
- FAR struct tca64_dev_s *priv = (FAR struct tca64_dev_s *)dev;
- uint8_t regaddr;
- uint8_t regval;
- int ret;
- DEBUGASSERT(priv != NULL && priv->config != NULL &&
- pin < CONFIG_IOEXPANDER_NPINS && value != NULL);
- gpioinfo("I2C addr=%02x, pin=%u\n", priv->config->address, pin);
- /* Get exclusive access to the I/O Expander */
- tca64_lock(priv);
- /* Read the input register for this pin
- *
- * The Input Port Register reflects the incoming logic levels of the pins,
- * regardless of whether the pin is defined as an input or an output by
- * the Configuration Register. They act only on read operation.
- */
- regaddr = tca64_input_reg(priv, pin);
- ret = tca64_getreg(priv, regaddr, ®val, 1);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to read input register at %u: %d\n",
- regaddr, ret);
- goto errout_with_lock;
- }
- #ifdef CONFIG_TCA64XX_INT_ENABLE
- /* Update the input status with the 8 bits read from the expander */
- tca64_int_update(priv, (ioe_pinset_t)regval << (pin & ~7),
- (ioe_pinset_t)0xff << (pin & ~7));
- #endif
- /* Return 0 or 1 to indicate the state of pin */
- *value = (bool)((regval >> (pin & 7)) & 1);
- ret = OK;
- errout_with_lock:
- tca64_unlock(priv);
- return ret;
- }
- /****************************************************************************
- * Name: tca64_multiwritepin
- *
- * Description:
- * Set the pin level for multiple pins. This routine may be faster than
- * individual pin accesses. Optional.
- *
- * Input Parameters:
- * dev - Device-specific state data
- * pins - The list of pin indexes to alter in this call
- * val - The list of pin levels.
- *
- * Returned Value:
- * 0 on success, else a negative error code
- *
- ****************************************************************************/
- #ifdef CONFIG_IOEXPANDER_MULTIPIN
- static int tca64_multiwritepin(FAR struct ioexpander_dev_s *dev,
- FAR uint8_t *pins, FAR bool *values,
- int count)
- {
- FAR struct tca64_dev_s *priv = (FAR struct tca64_dev_s *)dev;
- ioe_pinset_t pinset;
- uint8_t regaddr;
- uint8_t ngpios;
- uint8_t nregs;
- uint8_t pin;
- int ret;
- int i;
- /* Get exclusive access to the I/O Expander */
- tca64_lock(priv);
- /* Read the output registers for pin 0 through the number of supported
- * pins.
- */
- ngpios = tca64_ngpios(priv);
- nregs = (ngpios + 7) >> 3;
- pinset = 0;
- regaddr = tca64_output_reg(priv, 0);
- ret = tca64_getreg(priv, regaddr, (FAR uint8_t *)&pinset, nregs);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to read %u output registers at %u: %d\n",
- nregs, regaddr, ret);
- goto errout_with_lock;
- }
- /* Apply the user defined changes */
- for (i = 0; i < count; i++)
- {
- pin = pins[i];
- DEBUGASSERT(pin < CONFIG_IOEXPANDER_NPINS);
- if (values[i])
- {
- pinset |= (1 << pin);
- }
- else
- {
- pinset &= ~(1 << pin);
- }
- }
- /* Now write back the new pins states */
- ret = tca64_putreg(priv, regaddr, (FAR uint8_t *)&pinset, nregs);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to write %u output registers at %u: %d\n",
- nregs, regaddr, ret);
- }
- errout_with_lock:
- tca64_unlock(priv);
- return ret;
- }
- #endif
- /****************************************************************************
- * Name: tca64_multireadpin
- *
- * Description:
- * Read the actual level for multiple pins. This routine may be faster than
- * individual pin accesses. Optional.
- *
- * Input Parameters:
- * dev - Device-specific state data
- * pin - The list of pin indexes to read
- * valptr - Pointer to a buffer where the pin levels are stored.
- *
- * Returned Value:
- * 0 on success, else a negative error code
- *
- ****************************************************************************/
- #ifdef CONFIG_IOEXPANDER_MULTIPIN
- static int tca64_multireadpin(FAR struct ioexpander_dev_s *dev,
- FAR uint8_t *pins, FAR bool *values,
- int count)
- {
- FAR struct tca64_dev_s *priv = (FAR struct tca64_dev_s *)dev;
- ioe_pinset_t pinset;
- uint8_t regaddr;
- uint8_t ngpios;
- uint8_t nregs;
- uint8_t pin;
- int ret;
- int i;
- DEBUGASSERT(priv != NULL && priv->config != NULL && pins != NULL &&
- values != NULL && count > 0);
- gpioinfo("I2C addr=%02x, count=%u\n", priv->config->address, count);
- /* Get exclusive access to the I/O Expander */
- tca64_lock(priv);
- /* Read the input register for pin 0 through the number of supported pins.
- *
- * The Input Port Register reflects the incoming logic levels of the pins,
- * regardless of whether the pin is defined as an input or an output by
- * the Configuration Register. They act only on read operation.
- */
- ngpios = tca64_ngpios(priv);
- nregs = (ngpios + 7) >> 3;
- pinset = 0;
- regaddr = tca64_input_reg(priv, 0);
- ret = tca64_getreg(priv, regaddr, (FAR uint8_t *)&pinset, nregs);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to read input %u registers at %u: %d\n",
- nregs, regaddr, ret);
- goto errout_with_lock;
- }
- /* Update the input status with the 8 bits read from the expander */
- for (i = 0; i < count; i++)
- {
- pin = pins[i];
- DEBUGASSERT(pin < CONFIG_IOEXPANDER_NPINS);
- values[i] = ((pinset & (1 << pin)) != 0);
- }
- #ifdef CONFIG_TCA64XX_INT_ENABLE
- /* Update the input status with the 32 bits read from the expander */
- tca64_int_update(priv, pinset, PINSET_ALL);
- #endif
- errout_with_lock:
- tca64_unlock(priv);
- return ret;
- }
- #endif
- /****************************************************************************
- * Name: tca64_attach
- *
- * Description:
- * Attach and enable 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.
- * arg - User-provided callback argument
- *
- * Returned Value:
- * A non-NULL handle value is returned on success. This handle may be
- * used later to detach and disable the pin interrupt.
- *
- ****************************************************************************/
- #ifdef CONFIG_TCA64XX_INT_ENABLE
- static FAR void *tca64_attach(FAR struct ioexpander_dev_s *dev,
- ioe_pinset_t pinset, ioe_callback_t callback,
- FAR void *arg)
- {
- FAR struct tca64_dev_s *priv = (FAR struct tca64_dev_s *)dev;
- FAR void *handle = NULL;
- int i;
- /* Get exclusive access to the I/O Expander */
- tca64_lock(priv);
- /* Find and available in entry in the callback table */
- for (i = 0; i < CONFIG_TCA64XX_INT_NCALLBACKS; i++)
- {
- /* Is this entry available (i.e., no callback attached) */
- if (priv->cb[i].cbfunc == NULL)
- {
- /* Yes.. use this entry */
- priv->cb[i].pinset = pinset;
- priv->cb[i].cbfunc = callback;
- priv->cb[i].cbarg = arg;
- handle = &priv->cb[i];
- break;
- }
- }
- tca64_unlock(priv);
- return handle;
- }
- /****************************************************************************
- * Name: tca64_detach
- *
- * Description:
- * Detach and disable a pin interrupt callback function.
- *
- * Input Parameters:
- * dev - Device-specific state data
- * handle - The non-NULL opaque value return by tca64_attch()
- *
- * Returned Value:
- * 0 on success, else a negative error code
- *
- ****************************************************************************/
- static int tca64_detach(FAR struct ioexpander_dev_s *dev, FAR void *handle)
- {
- FAR struct tca64_dev_s *priv = (FAR struct tca64_dev_s *)dev;
- FAR struct tca64_callback_s *cb = (FAR struct tca64_callback_s *)handle;
- DEBUGASSERT(priv != NULL && cb != NULL);
- DEBUGASSERT((uintptr_t)cb >= (uintptr_t)&priv->cb[0] &&
- (uintptr_t)cb <= (uintptr_t)&priv->cb[CONFIG_TCA64XX_INT_NCALLBACKS-1]);
- UNUSED(priv);
- cb->pinset = 0;
- cb->cbfunc = NULL;
- cb->cbarg = NULL;
- return OK;
- }
- #endif
- /****************************************************************************
- * Name: tca64_int_update
- *
- * Description:
- * Check for pending interrupts.
- *
- ****************************************************************************/
- #ifdef CONFIG_TCA64XX_INT_ENABLE
- static void tca64_int_update(FAR struct tca64_dev_s *priv, ioe_pinset_t input,
- ioe_pinset_t mask)
- {
- ioe_pinset_t diff;
- irqstate_t flags;
- int ngios = tca64_ngpios(priv);
- int pin;
- flags = enter_critical_section();
- /* Check the changed bits from last read */
- input = (priv->input & ~mask) | (input & mask);
- diff = priv->input ^ input;
- priv->input = input;
- /* TCA64XX doesn't support irq trigger, we have to do this in software. */
- for (pin = 0; pin < ngios; pin++)
- {
- if (TCA64_EDGE_SENSITIVE(priv, pin))
- {
- /* Edge triggered. Was there a change in the level? */
- if ((diff & 1) != 0)
- {
- /* Set interrupt as a function of edge type */
- if (((input & 1) == 0 && TCA64_EDGE_FALLING(priv, pin)) ||
- ((input & 1) != 0 && TCA64_EDGE_RISING(priv, pin)))
- {
- priv->intstat |= 1 << pin;
- }
- }
- }
- else /* if (TCA64_LEVEL_SENSITIVE(priv, pin)) */
- {
- /* Level triggered. Set intstat bit if match in level type. */
- if (((input & 1) != 0 && TCA64_LEVEL_HIGH(priv, pin)) ||
- ((input & 1) == 0 && TCA64_LEVEL_LOW(priv, pin)))
- {
- priv->intstat |= 1 << pin;
- }
- }
- diff >>= 1;
- input >>= 1;
- }
- leave_critical_section(flags);
- }
- #endif
- /****************************************************************************
- * Name: tc64_update_registers
- *
- * Description:
- * Read all pin states and update pending interrupts.
- *
- * Input Parameters:
- * dev - Device-specific state data
- * pins - The list of pin indexes to alter in this call
- * val - The list of pin levels.
- *
- * Returned Value:
- * 0 on success, else a negative error code
- *
- ****************************************************************************/
- #ifdef CONFIG_TCA64XX_INT_ENABLE
- static void tca64_register_update(FAR struct tca64_dev_s *priv)
- {
- ioe_pinset_t pinset;
- uint8_t regaddr;
- uint8_t ngpios;
- uint8_t nregs;
- int ret;
- /* Read the input register for pin 0 through the number of supported pins.
- *
- * The Input Port Register reflects the incoming logic levels of the pins,
- * regardless of whether the pin is defined as an input or an output by
- * the Configuration Register. They act only on read operation.
- */
- ngpios = tca64_ngpios(priv);
- nregs = (ngpios + 7) >> 3;
- pinset = 0;
- regaddr = tca64_input_reg(priv, 0);
- ret = tca64_getreg(priv, regaddr, (FAR uint8_t *)&pinset, nregs);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to read input %u registers at %u: %d\n",
- nregs, regaddr, ret);
- return;
- }
- /* Update the input status with the 32 bits read from the expander */
- tca64_int_update(priv, pinset, PINSET_ALL);
- }
- #endif
- /****************************************************************************
- * Name: tca64_irqworker
- *
- * Description:
- * Handle GPIO interrupt events (this function actually executes in the
- * context of the worker thread).
- *
- ****************************************************************************/
- #ifdef CONFIG_TCA64XX_INT_ENABLE
- static void tca64_irqworker(void *arg)
- {
- FAR struct tca64_dev_s *priv = (FAR struct tca64_dev_s *)arg;
- ioe_pinset_t pinset;
- uint8_t regaddr;
- uint8_t ngpios;
- uint8_t nregs;
- int ret;
- int i;
- DEBUGASSERT(priv != NULL && priv->config != NULL);
- /* Get exclusive access to read inputs and assess pending interrupts. */
- tca64_lock(priv);
- /* Read the input register for pin 0 through the number of supported pins.
- *
- * The Input Port Register reflects the incoming logic levels of the pins,
- * regardless of whether the pin is defined as an input or an output by
- * the Configuration Register. They act only on read operation.
- */
- ngpios = tca64_ngpios(priv);
- nregs = (ngpios + 7) >> 3;
- pinset = 0;
- regaddr = tca64_input_reg(priv, 0);
- ret = tca64_getreg(priv, regaddr, (FAR uint8_t *)&pinset, nregs);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to read input %u registers at %u: %d\n",
- nregs, regaddr, ret);
- tca64_unlock(priv);
- goto errout_with_restart;
- }
- /* Update the input status with the 32 bits read from the expander */
- tca64_int_update(priv, pinset, PINSET_ALL);
- /* Sample and clear the pending interrupts. */
- pinset = priv->intstat;
- priv->intstat = 0;
- tca64_unlock(priv);
- /* Perform pin interrupt callbacks */
- for (i = 0; i < CONFIG_TCA64XX_INT_NCALLBACKS; i++)
- {
- /* Is this entry valid (i.e., callback attached)? */
- if (priv->cb[i].cbfunc != NULL)
- {
- /* Did any of the requested pin interrupts occur? */
- ioe_pinset_t match = pinset & priv->cb[i].pinset;
- if (match != 0)
- {
- /* Yes.. perform the callback */
- (void)priv->cb[i].cbfunc(&priv->dev, match,
- priv->cb[i].cbarg);
- }
- }
- }
- errout_with_restart:
- #ifdef CONFIG_TCA64XX_INT_POLL
- /* Check for pending interrupts */
- tca64_register_update(priv);
- /* Re-start the poll timer */
- sched_lock();
- ret = wd_start(priv->wdog, TCA64XX_POLLDELAY, (wdentry_t)tca64_poll_expiry,
- 1, (wdparm_t)priv);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to start poll timer\n");
- }
- #endif
- /* Re-enable interrupts */
- priv->config->enable(priv->config, true);
- #ifdef CONFIG_TCA64XX_INT_POLL
- sched_unlock();
- #endif
- }
- #endif
- /****************************************************************************
- * Name: tca64_interrupt
- *
- * Description:
- * Handle GPIO interrupt events (this function executes in the
- * context of the interrupt).
- *
- ****************************************************************************/
- #ifdef CONFIG_TCA64XX_INT_ENABLE
- static void tca64_interrupt(FAR void *arg)
- {
- FAR struct tca64_dev_s *priv = (FAR struct tca64_dev_s *)arg;
- DEBUGASSERT(priv != NULL && priv->config != NULL);
- /* Defer interrupt processing to the worker thread. This is not only
- * much kinder in the use of system resources but is probably necessary
- * to access the I/O expander device.
- *
- * 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 tca64_irqworker() when the work is
- * completed.
- */
- if (work_available(&priv->work))
- {
- #ifdef CONFIG_TCA64XX_INT_POLL
- /* Cancel the poll timer */
- (void)wd_cancel(priv->wdog);
- #endif
- /* Disable interrupts */
- priv->config->enable(priv->config, false);
- /* Schedule interrupt related work on the high priority worker thread. */
- work_queue(HPWORK, &priv->work, tca64_irqworker,
- (FAR void *)priv, 0);
- }
- }
- #endif
- /****************************************************************************
- * Name: tca64_poll_expiry
- *
- * Description:
- * The poll timer has expired; check for missed interrupts
- *
- * Input Parameters:
- * Standard wdog expiration arguments.
- *
- ****************************************************************************/
- #if defined(CONFIG_TCA64XX_INT_ENABLE) && defined(CONFIG_TCA64XX_INT_POLL)
- static void tca64_poll_expiry(int argc, wdparm_t arg1, ...)
- {
- FAR struct tca64_dev_s *priv;
- DEBUGASSERT(argc == 1);
- priv = (FAR struct tca64_dev_s *)arg1;
- DEBUGASSERT(priv != NULL && priv->config != NULL);
- /* Defer interrupt processing to the worker thread. This is not only
- * much kinder in the use of system resources but is probably necessary
- * to access the I/O expander device.
- *
- * 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 tca64_irqworker() when the work is
- * completed.
- */
- if (work_available(&priv->work))
- {
- /* Disable interrupts */
- priv->config->enable(priv->config, false);
- /* Schedule interrupt related work on the high priority worker thread. */
- work_queue(HPWORK, &priv->work, tca64_irqworker,
- (FAR void *)priv, 0);
- }
- }
- #endif
- /****************************************************************************
- * Public Functions
- ****************************************************************************/
- /****************************************************************************
- * Name: tca64_initialize
- *
- * Description:
- * Instantiate and configure the TCA64xx device driver to use the provided
- * I2C device instance.
- *
- * Input Parameters:
- * i2c - An I2C driver instance
- * minor - The device i2c address
- * config - Persistent board configuration data
- *
- * Returned Value:
- * an ioexpander_dev_s instance on success, NULL on failure.
- *
- ****************************************************************************/
- FAR struct ioexpander_dev_s *tca64_initialize(FAR struct i2c_master_s *i2c,
- FAR struct tca64_config_s *config)
- {
- FAR struct tca64_dev_s *priv;
- int ret;
- #ifdef CONFIG_TCA64XX_MULTIPLE
- /* Allocate the device state structure */
- priv = (FAR struct tca64_dev_s *)kmm_zalloc(sizeof(struct tca64_dev_s));
- if (!priv)
- {
- gpioerr("ERROR: Failed to allocate driver instance\n");
- return NULL;
- }
- #else
- /* Use the one-and-only I/O Expander driver instance */
- priv = &g_tca64;
- #endif
- /* Initialize the device state structure */
- priv->dev.ops = &g_tca64_ops;
- priv->i2c = i2c;
- priv->config = config;
- #ifdef CONFIG_TCA64XX_INT_ENABLE
- /* Initial interrupt state: Edge triggered on both edges */
- priv->trigger = PINSET_ALL; /* All edge triggered */
- priv->level[0] = PINSET_ALL; /* All rising edge */
- priv->level[1] = PINSET_ALL; /* All falling edge */
- #ifdef CONFIG_TCA64XX_INT_POLL
- /* Set up a timer to poll for missed interrupts */
- priv->wdog = wd_create();
- DEBUGASSERT(priv->wdog != NULL);
- ret = wd_start(priv->wdog, TCA64XX_POLLDELAY, (wdentry_t)tca64_poll_expiry,
- 1, (wdparm_t)priv);
- if (ret < 0)
- {
- gpioerr("ERROR: Failed to start poll timer\n");
- }
- #endif
- /* Attach the I/O expander interrupt handler and enable interrupts */
- priv->config->attach(config, tca64_interrupt, priv);
- priv->config->enable(config, true);
- #endif
- nxsem_init(&priv->exclsem, 0, 1);
- return &priv->dev;
- }
- #endif /* CONFIG_IOEXPANDER_TCA64XX */
|