mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
drivers: add PCF857X I/O expander driver
This commit is contained in:
parent
97f7b67b49
commit
7eb599e765
@ -42,6 +42,7 @@ rsource "ds3231/Kconfig"
|
||||
rsource "ds3234/Kconfig"
|
||||
rsource "edbg_eui/Kconfig"
|
||||
rsource "io1_xplained/Kconfig"
|
||||
rsource "pcf857x/Kconfig"
|
||||
rsource "tps6274x/Kconfig"
|
||||
rsource "uart_half_duplex/Kconfig"
|
||||
rsource "usbdev_mock/Kconfig"
|
||||
|
@ -113,6 +113,10 @@ ifneq (,$(filter nrf24l01p_ng_%,$(USEMODULE)))
|
||||
USEMODULE += nrf24l01p_ng
|
||||
endif
|
||||
|
||||
ifneq (,$(filter pcf857%,$(USEMODULE)))
|
||||
USEMODULE += pcf857x
|
||||
endif
|
||||
|
||||
ifneq (,$(filter periph_ptp_timer periph_ptp_speed_adjustment,$(FEATURES_USED)))
|
||||
FEATURES_REQUIRED += periph_ptp
|
||||
endif
|
||||
|
600
drivers/include/pcf857x.h
Normal file
600
drivers/include/pcf857x.h
Normal file
@ -0,0 +1,600 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Gunar Schorcht
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup drivers_pcf857x PCF857X I2C I/O expanders
|
||||
* @ingroup drivers_misc
|
||||
* @ingroup drivers_saul
|
||||
* @brief Device driver for Texas Instruments PCF857X I2C I/O expanders
|
||||
*
|
||||
*
|
||||
* \section Device driver for Texas Instruments PCF857X I2C I/O expanders
|
||||
*
|
||||
* ## Overview
|
||||
*
|
||||
* Texas Instruments PCF857X I2C I/O expanders provide general purpose I/O
|
||||
* extension via I2C bus. The driver supports the following PCF857X I2C I/O
|
||||
* expander variants:
|
||||
*
|
||||
* <center>
|
||||
* Expander | Type | Pseudomodule to be used
|
||||
* :--------|:------------------------|:-----------------------
|
||||
* PCF8574 | 8-bit I2C I/O expander | `pcf8574`
|
||||
* PCF8574A | 8-bit I2C I/O expander | `pcf8574a`
|
||||
* PCF8575 | 16-bit I2C I/O expander | `pcf8575`
|
||||
* </center><br>
|
||||
*
|
||||
* For each of these PCF857X I2C I/O expanders variants, the driver defines
|
||||
* a separate pseudomodule. Multiple PCF857X I2C I/O expanders and different
|
||||
* variants can be used at the same time. Either the board definition or the
|
||||
* application must specify used PCF857X I/O expander variants by a list of
|
||||
* used pseudomodules. For example, to use a PCF8574A and a PCF8575 I/O
|
||||
* expander in one application, the make command would be:
|
||||
*
|
||||
* USEMODULE="pcf8574a pcf8575" make -C tests/driver_pcf857x BOARD=...
|
||||
*
|
||||
* At least one PCF857X I2C I/O expander variant has to be specified. The
|
||||
* driver module `pcf857x` is then enabled implicitly.
|
||||
*
|
||||
* @note While PCF8575 is working in I2C fast mode with up to 400 kHz clock
|
||||
* frequency, PCF8574 and PCF8574A are only specified for I2C normal mode
|
||||
* with up to 100 kHz clock frequency. However, they seem also to work at
|
||||
* 400 kHz clock frequency.
|
||||
*
|
||||
* The driver interface is kept as compatible as possible with the peripheral
|
||||
* GPIO interface. The only differences are that
|
||||
*
|
||||
* - functions have the prefix `pcf857x_` and
|
||||
* - functions require an additional parameter, the pointer to the expander
|
||||
* device of type #pcf857x_t.
|
||||
*
|
||||
* ## Defined pseudomodules
|
||||
*
|
||||
* The functionality of the driver is controlled by the use of pseudomodules.
|
||||
* The following pseudomodules are defined:
|
||||
* <center>
|
||||
* Pseudomoule | Functionality
|
||||
* :--------------------|:---------------------
|
||||
* `pcf8574` | support of PCF8574 enabled
|
||||
* `pcf8574a` | support of PCF8574A enabled
|
||||
* `pcf8575` | support of PCF8575 enabled
|
||||
* `pcf857x_irq` | support of interrupts enabled with medium event priority
|
||||
* `pcf857x_irq_low` | support of interrupts enabled with low event priority
|
||||
* `pcf857x_irq_medium` | support of interrupts enabled with medium event priority
|
||||
* `pcf857x_irq_low` | support of interrupts enabled with high event priority
|
||||
* </center><br>
|
||||
*
|
||||
* @note At least one of the modules `pcf8574`, `pcf8574a` and `pcf8575` has to
|
||||
* be used.
|
||||
*
|
||||
* ## Expander GPIOs
|
||||
*
|
||||
* The PCF857X expander devices provide a GPIO expansion over the I2C
|
||||
* interface with either
|
||||
*
|
||||
* - 1 port with 8 quasi-parallel input/output (I/O) pins (PCF8574, PCF8574A) or
|
||||
* - 2 ports with 8 quasi-parallel input/output (I/O) pins each (PCF8575).
|
||||
*
|
||||
* Each quasi-bidirectional expander I/O pin can be used as an input or output
|
||||
* without the use of a data-direction control signal. Output pins are latched
|
||||
* and have high-current drive capability for directly driving LEDs.
|
||||
* The quasi-bidirectional expander I/O pins without direction control
|
||||
* work as follows:
|
||||
*
|
||||
* - INPUT:
|
||||
* Writing 1 to an expander pin configures the pin as an input, which is
|
||||
* pulled up to HIGH by a very weak 100 μA pull-up. When reading the pin,
|
||||
* its value then depends on the actual voltage. This corresponds to the
|
||||
* behavior of the #GPIO_IN_PU mode.
|
||||
* - OUTPUT:
|
||||
* Writing 0 to an expander pin configures the pin as an output and
|
||||
* actively drives the pin to LOW. This corresponds to the behavior of
|
||||
* the #GPIO_OD_PU mode.
|
||||
*
|
||||
* @note Since the expander I/O pins are quasi-bidirectional without direction
|
||||
* control, the only actively driven level is the output LOW. Therefore the
|
||||
* driver physically supports only the modes #GPIO_IN_PU and #GPIO_OD_PU.
|
||||
* The other logically identical modes #GPIO_IN, #GPIO_OUT and #GPIO_OD are
|
||||
* emulated. Please keep this in mind when connecting these pins to other
|
||||
* open-drain output pins that do not generate active signals. The #GPIO_IN_PD
|
||||
* mode is not supported.
|
||||
*
|
||||
* After the initialization with the #pcf857x_init function, all
|
||||
* expander I/O pins are in input mode and pulled-up to HIGH.
|
||||
*
|
||||
* The expander I/O pins can be addressed as GPIO pins using the following
|
||||
* scheme:
|
||||
*
|
||||
* <center>
|
||||
* PCF857X pin label | Port | Pin | RIOT symbol | Remark
|
||||
* ----------------- |:----:|:---:|:--------------------------|:-----------------
|
||||
* P00 | 0 | 0 | `PCF857X_GPIO_PIN(0, 0)` | PCF8574, PCF8574A and PCF8575
|
||||
* P01 | 0 | 1 | `PCF857X_GPIO_PIN(0, 1)` | PCF8574, PCF8574A and PCF8575
|
||||
* ... | ... | ... | ... | ...
|
||||
* P07 | 0 | 7 | `PCF857X_GPIO_PIN(0, 7)` | PCF8574, PCF8574A and PCF8575
|
||||
* P10 | 0 | 8 | `PCF857X_GPIO_PIN(0, 8)` | PCF8575 only
|
||||
* P11 | 0 | 9 | `PCF857X_GPIO_PIN(0, 9)` | PCF8575 only
|
||||
* ... | ... | ... | ... | ...
|
||||
* P17 | 0 | 15 | `PCF857X_GPIO_PIN(0, 15)` | PCF8575 only
|
||||
* </center>
|
||||
*
|
||||
* ## Interrupts
|
||||
*
|
||||
* PCF857X expanders have an open-drain, low-active interrupt (INT) signal,
|
||||
* which generates an interrupt by any rising or falling edge of the expander
|
||||
* pins in the input mode. Using this expander interrupt signal, the following
|
||||
* features become available:
|
||||
*
|
||||
* - An interrupt service function can be attached to an expander input pin with
|
||||
* the #pcf857x_gpio_init_int function. This interrupt service function is
|
||||
* then called on any rising and/or falling edge of the expander input pin.
|
||||
*
|
||||
* - In addition, the driver uses the interrupt on changes of an expander
|
||||
* input pin to internally maintain the current state of all expander
|
||||
* input pins. Using this internal current state of the expander input
|
||||
* pins avoids reading all expander input pins via I2C every time the input
|
||||
* value of a single expander GPIO pin is read with #pcf857x_gpio_read.
|
||||
*
|
||||
* Since interrupts are handled in the context of a separate event thread (see
|
||||
* section [The Interrupt Context Problem](#pcf857x_interrupt_context_problem))
|
||||
* enabling interrupts requires more RAM. Therefore interrupts have to be
|
||||
* explicitly enabled with the module `pcf857x_irq_<priority>`.
|
||||
* `priority` can be one of `low`, `medium` or `highest`, which correspond to
|
||||
* the priority of the event thread that processes the interrupts. If only the
|
||||
* module `pcf857x_irq` is used without specifying the priority, the interrupt
|
||||
* handling is enabled with a medium priority of the event thread. For more
|
||||
* information on the priorities check the @ref sys_event module.
|
||||
*
|
||||
* Furthermore, the MCU GPIO pin to which the PCF857X `INT` signal is
|
||||
* connected has to be defined by the default configuration parameter
|
||||
* #PCF857X_PARAM_INT_PIN (pcf857x_params_t::int_pin) either in the
|
||||
* configuration parameter file or at the command line, for example:
|
||||
*
|
||||
* CFLAGS="-DPCF857X_PARAM_INT_PIN=\(GPIO_PIN\(0,6\)\)" \
|
||||
* USEMODULE="pcf8575 pcf857x_irq_medium" make -C tests/driver_pcf857x BOARD=...
|
||||
*
|
||||
* <br>
|
||||
* @note If an output of the expander is connected to an input of the same
|
||||
* expander, there is no interrupt triggered by the input when the
|
||||
* output changes. Therefore, a write operation to an output with
|
||||
* #pcf857x_gpio_write, #pcf857x_gpio_clear, #pcf857x_gpio_set or
|
||||
* #pcf857x_gpio_toggle leads to an additional read-after-write operation,
|
||||
* if interrupts are used.<br><br>
|
||||
* The use of interrupts therefore increases the read performance considerably,
|
||||
* since I2C read operations are required only when the inputs change. But the
|
||||
* write performance is reduced to the half.
|
||||
*
|
||||
* ## The Interrupt Context Problem {#pcf857x_interrupt_context_problem}
|
||||
*
|
||||
* Handling an interrupt of a PCF857x expander requires the driver to access
|
||||
* the device directly via I2C. However, the mutex-based synchronization of
|
||||
* I2C accesses does not work in the interrupt context. Therefore the ISR must
|
||||
* not access the PCF857x expander device directly. Rather, the ISR must only
|
||||
* indicate the occurrence of the interrupt which has to be handled
|
||||
* asynchronously in thread context.
|
||||
*
|
||||
* For this purpose an event thread module is used when interrupts are
|
||||
* enabled by the module `pcf857x_irq_<priority>`. The driver then
|
||||
* handles the interrupts in the context of the event thread with given
|
||||
* `priority`. For more information on the priorities check
|
||||
* the @ref sys_event module.
|
||||
*
|
||||
* ## SAUL Capabilities
|
||||
*
|
||||
* The driver provides SAUL capabilities that are compatible to the SAUL
|
||||
* capabilities of peripheral GPIOs. Each PCF857X expander I/O pin can be
|
||||
* mapped directly to SAUL by defining an according entry in
|
||||
* \c PCF857X_SAUL_GPIO_PARAMS. Please refer file
|
||||
* `$RIOTBASE/drivers/pcf857x/include/pcf857x_params.h` for an example.
|
||||
*
|
||||
* @note Module `saul_gpio` has to be added to the
|
||||
* project to enable SAUL capabilities of the PCF857X driver, e.g.:
|
||||
*
|
||||
* USEMODULE="pcf8575 saul_gpio" make -C tests/saul BOARD=...
|
||||
*
|
||||
* ## Using Multiple Devices
|
||||
*
|
||||
* It is possible to use multiple devices and different variants of PCF857X
|
||||
* I/O expanders at the same time. Either the board definition or the
|
||||
* application must specify used PCF857X I/O expander variants by a list of
|
||||
* used pseudomodules. For example, to use a PCF8574A and a PCF8575 I/O
|
||||
* expander in one application, the make command would be:
|
||||
*
|
||||
* USEMODULE="pcf8574a pcf8575" make -C tests/driver_pcf857x BOARD=...
|
||||
*
|
||||
* Furthermore, used devices have to be configured by defining the
|
||||
* configuration parameter set `pcf857x_params` of type #pcf857x_params_t.
|
||||
* The default configuration for one device is defined in
|
||||
* `drivers/pcf857x/pcf857x_params.h`. Either the board definition or the
|
||||
* application can override it by placing a file `pcf857x_params.h` in the
|
||||
* board definition directory or the application directory `$APPDIR`.
|
||||
* For example, the definition of the configuration parameter array for the
|
||||
* two devices above could be:
|
||||
*
|
||||
* static const pcf857x_params_t pcf857x_params[] = {
|
||||
* {
|
||||
* .dev = I2C_DEV(0),
|
||||
* .addr = 0,
|
||||
* .exp = PCF857X_EXP_PCF8574A,
|
||||
* .int_pin = GPIO_PIN(0,1),
|
||||
* },
|
||||
* {
|
||||
* .dev = I2C_DEV(0),
|
||||
* .addr = 0,
|
||||
* .exp = PCF857X_EXP_PCF8575,
|
||||
* .int_pin = GPIO_PIN(0,2),
|
||||
* },
|
||||
* };
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @author Gunar Schorcht <gunar@schorcht.net>
|
||||
* @file
|
||||
*/
|
||||
|
||||
#ifndef PCF857X_H
|
||||
#define PCF857X_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "kernel_defines.h"
|
||||
#include "periph/gpio.h"
|
||||
#include "periph/i2c.h"
|
||||
|
||||
#if IS_USED(MODULE_SAUL_GPIO) || DOXYGEN
|
||||
#include "saul/periph.h"
|
||||
#endif /* MODULE_SAUL_GPIO */
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN
|
||||
#include "event.h"
|
||||
#endif /* MODULE_PCF857X_IRQ */
|
||||
|
||||
#if !IS_USED(MODULE_PCF8574) && !IS_USED(MODULE_PCF8574A) && !IS_USED(MODULE_PCF8575)
|
||||
#error "Please provide a list of pcf857x variants used by the application (pcf8574, pcf8574a or pcf8575)"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name PCF857X I2C slave addresses
|
||||
*
|
||||
* PCF857X I2C slave addresses are defined as an offset to a base address,
|
||||
* which depends on the expander used. The address offset is in the range
|
||||
* of 0 to 7.
|
||||
* @{
|
||||
*/
|
||||
#define PCF8575_BASE_ADDR (0x20) /**< PCF8575 I2C slave base address.
|
||||
Addresses are then in range from
|
||||
0x20 to 0x27 */
|
||||
#define PCF8574_BASE_ADDR (0x20) /**< PCF8574 I2C slave base address.
|
||||
Addresses are then in range from
|
||||
0x20 to 0x27 */
|
||||
#define PCF8574A_BASE_ADDR (0x38) /**< PCF8574A I2C slave base address.
|
||||
Addresses are then in range from
|
||||
0x38 to 0x3f */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name PCF857X I/O expander pin number
|
||||
* @{
|
||||
*/
|
||||
#define PCF8575_GPIO_PIN_NUM (16) /**< PCF8575 has 16 I/O pins */
|
||||
#define PCF8574_GPIO_PIN_NUM (8) /**< PCF8574 has 8 I/O pins */
|
||||
#define PCF8574A_GPIO_PIN_NUM (8) /**< PCF8574A has 8 I/O pins */
|
||||
/** @} */
|
||||
|
||||
/** conversion of (port x : pin y) to a pin number */
|
||||
#define PCF857X_GPIO_PIN(x,y) ((gpio_t)((x << 3) | y))
|
||||
|
||||
/**
|
||||
* @name Module dependent definitions and declarations
|
||||
* @{
|
||||
*/
|
||||
#if IS_USED(MODULE_PCF8575) || DOXYGEN
|
||||
|
||||
/**
|
||||
* @brief Maximum number of GPIO pins
|
||||
*
|
||||
* Defines the maximum number of GPIO pins of all PCF857X I/O expanders
|
||||
* used. If a PCF8575 is used, the maximum number is 16 I/O pins.
|
||||
*/
|
||||
#define PCF857X_GPIO_PIN_NUM (16)
|
||||
|
||||
/**
|
||||
* @brief Data type that can mask all expander pins
|
||||
*
|
||||
* If a PCF8575 is used, the 16 I/O pins have to be masked.
|
||||
*/
|
||||
typedef uint16_t pcf857x_data_t;
|
||||
|
||||
#else /* MODULE_PCF8575 || DOXYGEN */
|
||||
|
||||
#define PCF857X_GPIO_PIN_NUM (8) /**< PCF8574, PCF8574 provide 8 I/O pins */
|
||||
typedef uint8_t pcf857x_data_t; /**< type that can mask all expander pins */
|
||||
|
||||
#endif /* MODULE_PCF8575 || DOXYGEN */
|
||||
/** @} */
|
||||
|
||||
/** Definition of PCF857X driver error codes */
|
||||
typedef enum {
|
||||
PCF857X_OK, /**< success */
|
||||
PCF857X_ERROR_I2C, /**< I2C communication error */
|
||||
PCF857X_ERROR_INV_EXP, /**< invalid expander variant */
|
||||
PCF857X_ERROR_INV_MODE, /**< invalid pin mode */
|
||||
PCF857X_ERROR_INV_FLANK, /**< invalid interrupt flank */
|
||||
PCF857X_ERROR_INT_PIN, /**< interrupt pin initialization failed */
|
||||
} pcf857x_error_codes_t;
|
||||
|
||||
/**
|
||||
* @brief Definition of PCF857X expander variants
|
||||
*
|
||||
* It is used in configuration parameters to specify the PCF857X expander
|
||||
* used by device.
|
||||
*
|
||||
* @note Expander variants known by the driver depend on enabled pseudomodules
|
||||
* `pcf8574`, `pcf8574a` and `pcf8575`.
|
||||
*/
|
||||
typedef enum {
|
||||
#if IS_USED(MODULE_PCF8574) || DOXYGEN
|
||||
PCF857X_EXP_PCF8574, /**< PCF8574 8 bit I/O expander used */
|
||||
#endif
|
||||
#if IS_USED(MODULE_PCF8574A) || DOXYGEN
|
||||
PCF857X_EXP_PCF8574A, /**< PCF8574A 8 bit I/O expander */
|
||||
#endif
|
||||
#if IS_USED(MODULE_PCF8575) || DOXYGEN
|
||||
PCF857X_EXP_PCF8575, /**< PCF8575 16 bit I/O expander */
|
||||
#endif
|
||||
PCF857X_EXP_MAX,
|
||||
} pcf857x_exp_t;
|
||||
|
||||
/**
|
||||
* @brief PCF857X device initialization parameters
|
||||
*/
|
||||
typedef struct {
|
||||
|
||||
i2c_t dev; /**< I2C device (default I2C_DEV(0)) */
|
||||
uint16_t addr; /**< I2C slave address offset to the PCF7857X base
|
||||
address (default 0) */
|
||||
pcf857x_exp_t exp; /**< PCF857X expander variant used by the device
|
||||
(default depends on used pseudomodules */
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN
|
||||
gpio_t int_pin; /**< MCU GPIO pin or #GPIO_UNDEF if not used (default).
|
||||
Using interrupt pin has the advantage that inputs
|
||||
have to be read from expander only if any input
|
||||
value changes.
|
||||
@note To use interrupts for expander inputs, this
|
||||
pin has to be defined. */
|
||||
#endif /* MODULE_PCF857X_IRQ */
|
||||
} pcf857x_params_t;
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN
|
||||
/**
|
||||
* @brief IRQ event type
|
||||
*
|
||||
* Handling an interrupt of a PCF857x expander requires the driver to access
|
||||
* the device directly via I2C. However, the mutex-based synchronization of
|
||||
* I2C accesses does not work in the interrupt context. Therefore the ISR must
|
||||
* not access the PCF857x expander device directly. Rather, the ISR must only
|
||||
* indicate the occurrence of the interrupt which has to be handled
|
||||
* asynchronously in the thread context.
|
||||
*
|
||||
* The type defines the data structure that is part of each device data
|
||||
* structure to indicate that an interrupt of the device occurred. Since there
|
||||
* is only one interrupt source, only one interrupt can be pending per device.
|
||||
* Thus, only one object of this type per device is required.
|
||||
*/
|
||||
typedef struct {
|
||||
event_t event; /**< inherited event data structure */
|
||||
void *dev; /**< PCF857X device reference */
|
||||
} pcf857x_irq_event_t;
|
||||
|
||||
#endif /* MODULE_PCF857X_IRQ */
|
||||
|
||||
/**
|
||||
* @brief PCF857X device data structure type
|
||||
*/
|
||||
typedef struct {
|
||||
pcf857x_params_t params; /**< device initialization parameters */
|
||||
|
||||
uint8_t pin_num; /**< number of I/O pins, depends on used expander
|
||||
variant */
|
||||
pcf857x_data_t modes; /**< expander pin modes */
|
||||
pcf857x_data_t in; /**< expander input pin values */
|
||||
pcf857x_data_t out; /**< expander output pin values */
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN
|
||||
gpio_isr_ctx_t isr[PCF857X_GPIO_PIN_NUM]; /**< ISR with arg for each expander pin */
|
||||
gpio_flank_t flank[PCF857X_GPIO_PIN_NUM]; /**< interrupt flank for each expander pin */
|
||||
bool enabled[PCF857X_GPIO_PIN_NUM]; /**< enabled flag for each expander pin */
|
||||
pcf857x_irq_event_t irq_event; /**< IRQ event object used for the device */
|
||||
#endif /* MODULE_PCF857X_IRQ */
|
||||
|
||||
} pcf857x_t;
|
||||
|
||||
#if IS_USED(MODULE_SAUL_GPIO) || DOXYGEN
|
||||
/**
|
||||
* @brief PCF857X configuration structure for mapping expander pins to SAUL
|
||||
*
|
||||
* This data structure is an extension of the GPIO configuration structure for
|
||||
* mapping GPIOs to SAUL. The only additional information required is a
|
||||
* reference to the according PCF857X device.
|
||||
*
|
||||
* @note To use PCF857X with SAUL, module `saul_gpio` has to be added to the
|
||||
* project.
|
||||
*/
|
||||
typedef struct {
|
||||
unsigned int dev; /**< PCF857X device index */
|
||||
saul_gpio_params_t gpio; /**< GPIO configuration for mapping to SAUL */
|
||||
} pcf857x_saul_gpio_params_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the PCF857X I/O expander
|
||||
*
|
||||
* All expander pins are set to be input and are pulled up.
|
||||
*
|
||||
* @param[in] dev descriptor of PCF857X I/O expander device
|
||||
* @param[in] params configuration parameters, see #pcf857x_params_t
|
||||
*
|
||||
* @pre If the interrupt handling is enabled by one of the modules
|
||||
* `pcf857x_irq*`, the MCU GPIO pin for the interrupt signal
|
||||
* has to be defined by the default configuration parameter
|
||||
* #PCF857X_PARAM_INT_PIN (pcf857x_params_t::int_pin).
|
||||
*
|
||||
* @retval PCF857X_OK on success
|
||||
* @retval PCF857X_ERROR_* a negative error code on error,
|
||||
* see #pcf857x_error_codes_t
|
||||
*/
|
||||
int pcf857x_init(pcf857x_t *dev, const pcf857x_params_t *params);
|
||||
|
||||
/**
|
||||
* @brief Initialize a PCF857X pin
|
||||
*
|
||||
* @param[in] dev descriptor of PCF857X I/O expander device
|
||||
* @param[in] pin pin to initialize, use PCF857X_GPIO_PIN(x,y) to specify
|
||||
* @param[in] mode mode of the pin, see #gpio_t
|
||||
*
|
||||
* @note
|
||||
* - Since the expander I/O pins are quasi-bidirectional without direction
|
||||
* control, the only actively driven level is the output LOW. Therefore
|
||||
* the driver physically supports only the modes #GPIO_IN_PU and
|
||||
* #GPIO_OD_PU. The other logically identical modes #GPIO_IN, #GPIO_OUT
|
||||
* and #GPIO_OD are emulated. For the #GPIO_IN_PU mode the function returns
|
||||
* with #PCF857X_ERROR_INV_MODE.
|
||||
* - After initialization in #GPIO_OUT mode the pin is actively driven LOW,
|
||||
* after initialization in all other modes the pin is pulled-up to HIGH.
|
||||
*
|
||||
* @retval PCF857X_OK on success
|
||||
* @retval PCF857X_ERROR_* a negative error code on error,
|
||||
* see #pcf857x_error_codes_t
|
||||
*/
|
||||
int pcf857x_gpio_init(pcf857x_t *dev, gpio_t pin, gpio_mode_t mode);
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN
|
||||
/**
|
||||
* @brief Initialize a PCF857X pin for external interrupt usage
|
||||
*
|
||||
* The registered callback function will be called in interrupt context every
|
||||
* time the defined flank(s) are detected. Therefore, it MUST NOT be blocking
|
||||
* or time-consuming.
|
||||
*
|
||||
* The interrupt is activated automatically after the initialization.
|
||||
*
|
||||
* @pre The MCU GPIO pin for the interrupt signal has to be defined by the
|
||||
* default configuration parameter #PCF857X_PARAM_INT_PIN
|
||||
* (pcf857x_params_t::int_pin).
|
||||
*
|
||||
* @note
|
||||
* - This function is only available if interrupt handling is enabled by one
|
||||
* of the modules `pcf857x_irq*`
|
||||
* - Since the expander I/O pins are quasi-bidirectional without direction
|
||||
* control, the only actively driven level is the output LOW. Therefore
|
||||
* the driver physically supports only the modes #GPIO_IN_PU and
|
||||
* #GPIO_OD_PU. The other logically identical modes #GPIO_IN, #GPIO_OUT
|
||||
* and #GPIO_OD are emulated. For the #GPIO_IN_PU mode the function returns
|
||||
* with #PCF857X_ERROR_INV_MODE.
|
||||
* - After initialization in #GPIO_OUT mode the pin is actively driven LOW,
|
||||
* after initialization in all other modes the pin is pulled-up to HIGH.
|
||||
*
|
||||
* @param[in] dev descriptor of PCF857X I/O expander device
|
||||
* @param[in] pin pin to initialize, use PCF857X_GPIO_PIN(x,y) to specify
|
||||
* @param[in] mode mode of the pin, see #gpio_t
|
||||
* @param[in] flank define the active flanks, see #gpio_flank_t
|
||||
* @param[in] isr ISR that is called back from interrupt context
|
||||
* @param[in] arg optional argument passed to the callback
|
||||
*
|
||||
* @retval PCF857X_OK on success
|
||||
* @retval PCF857X_ERROR_* a negative error code on error,
|
||||
* see #pcf857x_error_codes_t
|
||||
*/
|
||||
int pcf857x_gpio_init_int(pcf857x_t *dev, gpio_t pin,
|
||||
gpio_mode_t mode,
|
||||
gpio_flank_t flank,
|
||||
gpio_cb_t isr,
|
||||
void *arg);
|
||||
#endif /* MODULE_PCF857X_IRQ || DOXYGEN */
|
||||
|
||||
/**
|
||||
* @brief Get the value from PCF857X input pin
|
||||
*
|
||||
* @note If the PCF857X interrupt is used, the read operation does not perform
|
||||
* an I2C read operation since the last input pin value is already read.
|
||||
*
|
||||
* @param[in] dev descriptor of PCF857X I/O expander device
|
||||
* @param[in] pin pin to read, use PCF857X_GPIO_PIN(x,y) to specify
|
||||
*/
|
||||
int pcf857x_gpio_read(pcf857x_t *dev, gpio_t pin);
|
||||
|
||||
/**
|
||||
* @brief Write the value to PCF857X input pin
|
||||
*
|
||||
* @param[in] dev descriptor of PCF857X I/O expander device
|
||||
* @param[in] pin pin to write, use PCF857X_GPIO_PIN(x,y) to specify
|
||||
* @param[in] value value to write
|
||||
*/
|
||||
void pcf857x_gpio_write(pcf857x_t *dev, gpio_t pin, int value);
|
||||
|
||||
/**
|
||||
* @brief Clear the PCF857X output pin
|
||||
*
|
||||
* @param[in] dev descriptor of PCF857X I/O expander device
|
||||
* @param[in] pin pin to clear, use PCF857X_GPIO_PIN(x,y) to specify
|
||||
*/
|
||||
void pcf857x_gpio_clear(pcf857x_t *dev, gpio_t pin);
|
||||
|
||||
/**
|
||||
* @brief Set the PCF857X output pin
|
||||
*
|
||||
* @param[in] dev descriptor of PCF857X I/O expander device
|
||||
* @param[in] pin pin to set, use PCF857X_GPIO_PIN(x,y) to specify
|
||||
*/
|
||||
void pcf857x_gpio_set(pcf857x_t *dev, gpio_t pin);
|
||||
|
||||
/**
|
||||
* @brief Toggle the value of the PCF857X output pin
|
||||
*
|
||||
* @param[in] dev descriptor of PCF857X I/O expander device
|
||||
* @param[in] pin pin to toggle, use PCF857X_GPIO_PIN(x,y) to specify
|
||||
*/
|
||||
void pcf857x_gpio_toggle(pcf857x_t *dev, gpio_t pin);
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN
|
||||
/**
|
||||
* @brief Enable pin interrupt
|
||||
*
|
||||
* @note This function is only available if interrupt handling is enabled
|
||||
* by one of the modules `pcf857x_irq*`
|
||||
*
|
||||
* @param[in] dev descriptor of PCF857X I/O expander device
|
||||
* @param[in] pin pin to enable the interrupt for
|
||||
*/
|
||||
void pcf857x_gpio_irq_enable(pcf857x_t *dev, gpio_t pin);
|
||||
|
||||
/**
|
||||
* @brief Disable pin interrupt
|
||||
*
|
||||
* @note This function is only available if interrupt handling is enabled
|
||||
* by one of the modules `pcf857x_irq*`
|
||||
*
|
||||
* @param[in] dev descriptor of PCF857X I/O expander device
|
||||
* @param[in] pin pin to enable the interrupt for
|
||||
*/
|
||||
void pcf857x_gpio_irq_disable(pcf857x_t *dev, gpio_t pin);
|
||||
#endif /* MODULE_PCF857X_IRQ || DOXYGEN */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PCF857X_H */
|
||||
/** @} */
|
65
drivers/pcf857x/Kconfig
Normal file
65
drivers/pcf857x/Kconfig
Normal file
@ -0,0 +1,65 @@
|
||||
# Copyright (c) 2021 Gunar Schorcht
|
||||
#
|
||||
# This file is subject to the terms and conditions of the GNU Lesser
|
||||
# General Public License v2.1. See the file LICENSE in the top level
|
||||
# directory for more details.
|
||||
#
|
||||
|
||||
menuconfig MODULE_PCF857X
|
||||
bool "PCF857x Remote I/O Expander for I2C Bus"
|
||||
depends on HAS_PERIPH_GPIO
|
||||
depends on HAS_PERIPH_I2C
|
||||
depends on TEST_KCONFIG
|
||||
select MODULE_PERIPH_GPIO
|
||||
select MODULE_PERIPH_I2C
|
||||
help
|
||||
Driver for Texas Instruments PCF857X I2C I/O expanders.
|
||||
The driver supports the PCF8574, PCF8574A, and PCF8575 variants.
|
||||
Select the variants used by your application.
|
||||
|
||||
if MODULE_PCF857X
|
||||
|
||||
config MODULE_PCF8574
|
||||
bool "PCF8574 Remote 8-Bit I/O is used"
|
||||
|
||||
config MODULE_PCF8574A
|
||||
bool "PCF8574A Remote 8-Bit I/O is used"
|
||||
|
||||
config MODULE_PCF8575
|
||||
bool "PCF8575 Remote 16-Bit I/O is used"
|
||||
default y
|
||||
|
||||
config MODULE_PCF857X_IRQ
|
||||
bool "Interrupt support for PCF857x I/O Expander pins"
|
||||
depends on MODULE_PCF857X
|
||||
depends on HAS_PERIPH_GPIO_IRQ
|
||||
select MODULE_PERIPH_GPIO_IRQ
|
||||
select MODULE_EVENT
|
||||
select MODULE_EVENT_THREAD
|
||||
help
|
||||
To use the IRQs the MODULE_EVENT_THREAD symbol should be set.
|
||||
|
||||
choice
|
||||
bool "Thread priority"
|
||||
depends on MODULE_PCF857X_IRQ
|
||||
default MODULE_PCF857X_IRQ_MEDIUM
|
||||
help
|
||||
To process IRQs an event thread is used. The MODULE_EVENT_THREAD
|
||||
symbol should be set. Choose a priority for the thread that
|
||||
processes the IRQs. The default is medium priority.
|
||||
|
||||
config MODULE_PCF857X_IRQ_LOW
|
||||
bool "Low"
|
||||
select MODULE_EVENT_THREAD_LOW
|
||||
|
||||
config MODULE_PCF857X_IRQ_MEDIUM
|
||||
bool "Medium"
|
||||
select MODULE_EVENT_THREAD_MEDIUM
|
||||
|
||||
config MODULE_PCF857X_IRQ_HIGHEST
|
||||
bool "Highest"
|
||||
select MODULE_EVENT_THREAD_HIGHEST
|
||||
|
||||
endchoice
|
||||
|
||||
endif # MODULE_PCF857X
|
1
drivers/pcf857x/Makefile
Normal file
1
drivers/pcf857x/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
19
drivers/pcf857x/Makefile.dep
Normal file
19
drivers/pcf857x/Makefile.dep
Normal file
@ -0,0 +1,19 @@
|
||||
FEATURES_REQUIRED += periph_gpio
|
||||
FEATURES_REQUIRED += periph_i2c
|
||||
|
||||
_PCF857X_IRQ_MODULE := $(filter pcf857x_irq_%,$(USEMODULE))
|
||||
ifneq (,$(_PCF857X_IRQ_MODULE))
|
||||
# pull in the correspondant event_thread_<priority> module
|
||||
USEMODULE += $(_PCF857X_IRQ_MODULE:pcf857x_irq_%=event_thread_%)
|
||||
USEMODULE += pcf857x_irq
|
||||
else
|
||||
ifneq (,$(filter pcf857x_irq,$(USEMODULE)))
|
||||
# pull in the pcf857x_irq_medium module as default if pcf857x_irq is used.
|
||||
USEMODULE += pcf857x_irq_medium
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(filter pcf857x_irq,$(USEMODULE)))
|
||||
FEATURES_REQUIRED += periph_gpio_irq
|
||||
USEMODULE += pcf857x
|
||||
endif
|
11
drivers/pcf857x/Makefile.include
Normal file
11
drivers/pcf857x/Makefile.include
Normal file
@ -0,0 +1,11 @@
|
||||
# include variants of PCF857X drivers as pseudo modules
|
||||
PSEUDOMODULES += pcf8574
|
||||
PSEUDOMODULES += pcf8574a
|
||||
PSEUDOMODULES += pcf8575
|
||||
PSEUDOMODULES += pcf857x_irq
|
||||
PSEUDOMODULES += pcf857x_irq_low
|
||||
PSEUDOMODULES += pcf857x_irq_medium
|
||||
PSEUDOMODULES += pcf857x_irq_highest
|
||||
|
||||
USEMODULE_INCLUDES_pcf857x := $(LAST_MAKEFILEDIR)/include
|
||||
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_pcf857x)
|
123
drivers/pcf857x/include/pcf857x_params.h
Normal file
123
drivers/pcf857x/include/pcf857x_params.h
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Gunar Schorcht
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup drivers_pcf857x
|
||||
* @brief Default configuration for Texas Instruments PCF857X I2C I/O expanders
|
||||
* @author Gunar Schorcht <gunar@schorcht.net>
|
||||
* @file
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifndef PCF857X_PARAMS_H
|
||||
#define PCF857X_PARAMS_H
|
||||
|
||||
#include "board.h"
|
||||
#include "pcf857x.h"
|
||||
#include "saul_reg.h"
|
||||
#include "saul/periph.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name Set default configuration parameters
|
||||
* @{
|
||||
*/
|
||||
#ifndef PCF857X_PARAM_DEV
|
||||
/** device is I2C_DEV(0) */
|
||||
#define PCF857X_PARAM_DEV I2C_DEV(0)
|
||||
#endif
|
||||
|
||||
#ifndef PCF857X_PARAM_ADDR
|
||||
/** I2C slave address offset is 0 */
|
||||
#define PCF857X_PARAM_ADDR (0)
|
||||
#endif
|
||||
|
||||
#ifndef PCF857X_PARAM_EXP
|
||||
/** PCF857X expander variant used depends on enabled pseudomodules*/
|
||||
#if IS_USED(MODULE_PCF8575) || DOXYGEN
|
||||
#define PCF857X_PARAM_EXP (PCF857X_EXP_PCF8575)
|
||||
#elif IS_USED(MODULE_PCF8574)
|
||||
#define PCF857X_PARAM_EXP (PCF857X_EXP_PCF8574)
|
||||
#elif IS_USED(MODULE_PCF8574A)
|
||||
#define PCF857X_PARAM_EXP (PCF857X_EXP_PCF8574A)
|
||||
#endif
|
||||
#endif /* PCF857X_PARAM_EXP */
|
||||
|
||||
#ifndef PCF857X_PARAM_INT_PIN
|
||||
/** MCU interrupt pin */
|
||||
#define PCF857X_PARAM_INT_PIN (GPIO_UNDEF)
|
||||
#endif
|
||||
|
||||
#ifndef PCF857X_PARAMS
|
||||
#if IS_USED(MODULE_PCF857X_IRQ) || DOXYGEN
|
||||
/** Default configuration parameter set */
|
||||
#define PCF857X_PARAMS { \
|
||||
.dev = PCF857X_PARAM_DEV, \
|
||||
.addr = PCF857X_PARAM_ADDR, \
|
||||
.exp = PCF857X_PARAM_EXP, \
|
||||
.int_pin = PCF857X_PARAM_INT_PIN, \
|
||||
},
|
||||
#else
|
||||
#define PCF857X_PARAMS { \
|
||||
.dev = PCF857X_PARAM_DEV, \
|
||||
.addr = PCF857X_PARAM_ADDR, \
|
||||
.exp = PCF857X_PARAM_EXP, \
|
||||
},
|
||||
#endif
|
||||
#endif /* PCF857X_PARAMS */
|
||||
|
||||
#ifndef PCF857X_SAUL_GPIO_PARAMS
|
||||
/** Example for mapping expander pins to SAUL */
|
||||
#define PCF857X_SAUL_GPIO_PARAMS { \
|
||||
.dev = 0, \
|
||||
.gpio = { \
|
||||
.name = "P00 Output", \
|
||||
.pin = PCF857X_GPIO_PIN(0, 0), \
|
||||
.mode = GPIO_OUT, \
|
||||
.flags = SAUL_GPIO_INIT_CLEAR, \
|
||||
} \
|
||||
}, \
|
||||
{ \
|
||||
.dev = 0, \
|
||||
.gpio = { \
|
||||
.name = "P01 Input", \
|
||||
.pin = PCF857X_GPIO_PIN(0, 1), \
|
||||
.mode = GPIO_IN, \
|
||||
.flags = 0, \
|
||||
} \
|
||||
},
|
||||
#endif
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @brief Allocate some memory to store the actual configuration
|
||||
*/
|
||||
static const pcf857x_params_t pcf857x_params[] =
|
||||
{
|
||||
PCF857X_PARAMS
|
||||
};
|
||||
|
||||
#if IS_USED(MODULE_SAUL_GPIO) || DOXYGEN
|
||||
/**
|
||||
* @brief Additional meta information to keep in the SAUL registry
|
||||
*/
|
||||
static const pcf857x_saul_gpio_params_t pcf857x_saul_gpio_params[] =
|
||||
{
|
||||
PCF857X_SAUL_GPIO_PARAMS
|
||||
};
|
||||
#endif /* MODULE_SAUL_GPIO || DOXYGEN */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PCF857X_PARAMS_H */
|
||||
/** @} */
|
476
drivers/pcf857x/pcf857x.c
Normal file
476
drivers/pcf857x/pcf857x.c
Normal file
@ -0,0 +1,476 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Gunar Schorcht
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup drivers_pcf857x
|
||||
* @brief Device driver for Texas Instruments PCF857X I2C I/O expanders
|
||||
* @author Gunar Schorcht <gunar@schorcht.net>
|
||||
* @file
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "pcf857x.h"
|
||||
|
||||
#include "irq.h"
|
||||
#include "log.h"
|
||||
#include "thread.h"
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ)
|
||||
#include "event/thread.h"
|
||||
#endif
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
#if ENABLE_DEBUG
|
||||
|
||||
#define DEBUG_DEV(f, d, ...) \
|
||||
DEBUG("[pcf857x] %s i2c dev=%d addr=%02x: " f "\n", \
|
||||
__func__, d->params.dev, d->params.addr, ## __VA_ARGS__)
|
||||
|
||||
#else /* ENABLE_DEBUG */
|
||||
|
||||
#define DEBUG_DEV(f, d, ...)
|
||||
|
||||
#endif /* ENABLE_DEBUG */
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ_LOW)
|
||||
#define PCF857X_EVENT_PRIO EVENT_PRIO_LOWEST
|
||||
#elif IS_USED(MODULE_PCF857X_IRQ_MEDIUM)
|
||||
#define PCF857X_EVENT_PRIO EVENT_PRIO_MEDIUM
|
||||
#elif IS_USED(MODULE_PCF857X_IRQ_HIGHEST)
|
||||
#define PCF857X_EVENT_PRIO EVENT_PRIO_HIGHEST
|
||||
#endif
|
||||
|
||||
/** Forward declaration of functions for internal use */
|
||||
|
||||
static inline void _acquire(const pcf857x_t *dev);
|
||||
static inline void _release(const pcf857x_t *dev);
|
||||
static int _read(const pcf857x_t *dev, pcf857x_data_t *data);
|
||||
static int _write(const pcf857x_t *dev, pcf857x_data_t data);
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ)
|
||||
|
||||
/* interrutp service routine for IRQs */
|
||||
static void _irq_isr(void *arg);
|
||||
|
||||
/* declaration of IRQ handler function */
|
||||
static void _irq_handler(event_t *event);
|
||||
|
||||
/* internal update function */
|
||||
static void _update_state(pcf857x_t* dev);
|
||||
|
||||
#endif /* MODULE_PCF857X_IRQ */
|
||||
|
||||
int pcf857x_init(pcf857x_t *dev, const pcf857x_params_t *params)
|
||||
{
|
||||
/* some parameter sanity checks */
|
||||
assert(dev != NULL);
|
||||
assert(params != NULL);
|
||||
assert(params->exp < PCF857X_EXP_MAX);
|
||||
#if IS_USED(MODULE_PCF857X_IRQ)
|
||||
assert(gpio_is_valid(params->int_pin));
|
||||
#endif
|
||||
|
||||
DEBUG_DEV("params=%p", dev, params);
|
||||
|
||||
/* init device data structure */
|
||||
dev->params = *params;
|
||||
|
||||
switch (params->exp) {
|
||||
#if IS_USED(MODULE_PCF8574)
|
||||
/**< PCF8574 8 bit I/O expander used */
|
||||
case PCF857X_EXP_PCF8574: dev->pin_num = PCF8574_GPIO_PIN_NUM;
|
||||
dev->params.addr += PCF8574_BASE_ADDR;
|
||||
break;
|
||||
#endif
|
||||
#if IS_USED(MODULE_PCF8574A)
|
||||
/**< PCF8574A 8 bit I/O expander */
|
||||
case PCF857X_EXP_PCF8574A: dev->pin_num = PCF8574A_GPIO_PIN_NUM;
|
||||
dev->params.addr += PCF8574A_BASE_ADDR;
|
||||
break;
|
||||
#endif
|
||||
#if IS_USED(MODULE_PCF8575)
|
||||
/**< PCF8575 16 bit I/O expander */
|
||||
case PCF857X_EXP_PCF8575: dev->pin_num = PCF8575_GPIO_PIN_NUM;
|
||||
dev->params.addr += PCF8575_BASE_ADDR;
|
||||
break;
|
||||
#endif
|
||||
default: return -PCF857X_ERROR_INV_EXP;
|
||||
}
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ)
|
||||
/* initialize the IRQ event object used for delaying interrupts */
|
||||
dev->irq_event.event.handler = _irq_handler;
|
||||
dev->irq_event.dev = dev;
|
||||
|
||||
for (unsigned i = 0; i < dev->pin_num; i++) {
|
||||
dev->isr[i].cb = NULL;
|
||||
dev->isr[i].arg = NULL;
|
||||
dev->enabled[i] = false;
|
||||
}
|
||||
|
||||
/* initialize the interrupt pin */
|
||||
if (gpio_init_int(dev->params.int_pin,
|
||||
GPIO_IN_PU, GPIO_FALLING, _irq_isr, (void*)dev)) {
|
||||
return -PCF857X_ERROR_INT_PIN;
|
||||
}
|
||||
#endif /* MODULE_PCF857X_IRQ */
|
||||
|
||||
int res = PCF857X_OK;
|
||||
|
||||
_acquire(dev);
|
||||
|
||||
/* write 1 to all pins to switch them to INPUTS pulled up to HIGH */
|
||||
dev->out = ~0;
|
||||
res |= _write(dev, dev->out);
|
||||
|
||||
/* initial read all pins */
|
||||
res |= _read(dev, &dev->in);
|
||||
|
||||
/* set all pin modes to INPUT and set internal output data to 1 (HIGH) */
|
||||
dev->modes = ~0;
|
||||
|
||||
_release(dev);
|
||||
|
||||
return PCF857X_OK;
|
||||
}
|
||||
|
||||
int pcf857x_gpio_init(pcf857x_t *dev, gpio_t pin, gpio_mode_t mode)
|
||||
{
|
||||
/* some parameter sanity checks */
|
||||
assert(dev != NULL);
|
||||
assert(pin < dev->pin_num);
|
||||
|
||||
DEBUG_DEV("pin=%u mode=%u", dev, pin, mode);
|
||||
|
||||
/*
|
||||
* Since the LOW output is the only actively driven level possible with
|
||||
* this expander, only in the case of GPIO_OUT we write a 0 to the pin
|
||||
* to configure the pin as an output and actively drive it LOW. In all
|
||||
* other modes, the pin is configured as an input and pulled-up to HIGH
|
||||
* with the weak pull-up to emulate them.
|
||||
*/
|
||||
switch (mode) {
|
||||
case GPIO_IN_PD: DEBUG_DEV("gpio mode GPIO_IN_PD not supported", dev, mode);
|
||||
return -PCF857X_ERROR_INV_MODE;
|
||||
case GPIO_OUT: dev->modes &= ~(1 << pin); /* set mode bit to 0 */
|
||||
dev->out &= ~(1 << pin); /* set output bit to 0 */
|
||||
break;
|
||||
default: dev->modes |= (1 << pin); /* set mode bit to 1 */
|
||||
dev->out |= (1 << pin); /* set output bit to 1 */
|
||||
break;
|
||||
}
|
||||
|
||||
int res;
|
||||
|
||||
/* write the mode */
|
||||
pcf857x_data_t data = dev->modes | dev->out;
|
||||
_acquire(dev);
|
||||
if ((res = _write(dev, data)) != PCF857X_OK) {
|
||||
_release(dev);
|
||||
return res;
|
||||
}
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ)
|
||||
/* reset the callback in case the port used external interrupts before */
|
||||
dev->isr[pin].cb = NULL;
|
||||
dev->isr[pin].arg = NULL;
|
||||
dev->enabled[pin] = false;
|
||||
|
||||
/*
|
||||
* If an output of the expander is connected to an input of the same
|
||||
* expander, there is no interrupt triggered by the input when the
|
||||
* output changes.
|
||||
* Therefore, we have to read input pins after the write operation to
|
||||
* update the input pin state in the device data structure and to trigger
|
||||
* an ISR if necessary.
|
||||
*
|
||||
* @note _update_state releases the bus.
|
||||
*/
|
||||
_update_state(dev);
|
||||
#else
|
||||
/* read to update the internal input state */
|
||||
res = _read(dev, &dev->in);
|
||||
_release(dev);
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ)
|
||||
int pcf857x_gpio_init_int(pcf857x_t *dev, gpio_t pin,
|
||||
gpio_mode_t mode,
|
||||
gpio_flank_t flank,
|
||||
gpio_cb_t isr,
|
||||
void *arg)
|
||||
{
|
||||
int res = PCF857X_OK;
|
||||
|
||||
/* initialize the pin */
|
||||
if ((res = pcf857x_gpio_init(dev, pin, mode)) != PCF857X_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
switch (flank) {
|
||||
case GPIO_FALLING:
|
||||
case GPIO_RISING:
|
||||
case GPIO_BOTH: dev->isr[pin].cb = isr;
|
||||
dev->isr[pin].arg = arg;
|
||||
dev->flank[pin] = flank;
|
||||
dev->enabled[pin] = true;
|
||||
break;
|
||||
default: DEBUG_DEV("invalid flank %d for pin %d", dev, flank, pin);
|
||||
return -PCF857X_ERROR_INV_FLANK;
|
||||
}
|
||||
|
||||
return PCF857X_OK;
|
||||
}
|
||||
|
||||
void pcf857x_gpio_irq_enable(pcf857x_t *dev, gpio_t pin)
|
||||
{
|
||||
/* some parameter sanity checks */
|
||||
assert(dev != NULL);
|
||||
assert(pin < dev->pin_num);
|
||||
|
||||
DEBUG_DEV("pin=%u", dev, pin);
|
||||
dev->enabled[pin] = true;
|
||||
}
|
||||
|
||||
void pcf857x_gpio_irq_disable(pcf857x_t *dev, gpio_t pin)
|
||||
{
|
||||
/* some parameter sanity checks */
|
||||
assert(dev != NULL);
|
||||
assert(pin < dev->pin_num);
|
||||
|
||||
DEBUG_DEV("pin=%u", dev, pin);
|
||||
dev->enabled[pin] = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
int pcf857x_gpio_read(pcf857x_t *dev, gpio_t pin)
|
||||
{
|
||||
/* some parameter sanity checks */
|
||||
assert(dev != NULL);
|
||||
assert(pin < dev->pin_num);
|
||||
|
||||
DEBUG_DEV("pin=%u", dev, pin);
|
||||
|
||||
/*
|
||||
* If we use the interrupt, we always have an up-to-date input snapshot
|
||||
* stored in the device data structure and which can be used directly.
|
||||
* Otherwise we have to read the pins first.
|
||||
*/
|
||||
#if !IS_USED(MODULE_PCF857X_IRQ)
|
||||
_acquire(dev);
|
||||
_read(dev, &dev->in);
|
||||
_release(dev);
|
||||
#endif
|
||||
return (dev->in &(1 << pin)) ? 1 : 0;
|
||||
}
|
||||
|
||||
void pcf857x_gpio_write(pcf857x_t *dev, gpio_t pin, int value)
|
||||
{
|
||||
/* some parameter sanity checks */
|
||||
assert(dev != NULL);
|
||||
assert(pin < dev->pin_num);
|
||||
|
||||
DEBUG_DEV("pin=%u value=%d", dev, pin, value);
|
||||
|
||||
/* set pin bit value */
|
||||
if (value) {
|
||||
dev->out |= (1 << pin);
|
||||
}
|
||||
else {
|
||||
dev->out &= ~(1 << pin);
|
||||
}
|
||||
|
||||
/* update pin values */
|
||||
pcf857x_data_t data = dev->modes | dev->out;
|
||||
_acquire(dev);
|
||||
_write(dev, data);
|
||||
#if IS_USED(MODULE_PCF857X_IRQ)
|
||||
/*
|
||||
* If an output of the expander is connected to an input of the same
|
||||
* expander, there is no interrupt triggered by the input when the
|
||||
* output changes.
|
||||
* Therefore, we have to read input pins after the write operation to
|
||||
* update the input pin state in the device data structure and to trigger
|
||||
* an ISR if necessary.
|
||||
*
|
||||
* @note _update_state releases the bus.
|
||||
*/
|
||||
_update_state(dev);
|
||||
#else
|
||||
_release(dev);
|
||||
#endif
|
||||
}
|
||||
|
||||
void pcf857x_gpio_clear(pcf857x_t *dev, gpio_t pin)
|
||||
{
|
||||
DEBUG_DEV("pin=%u", dev, pin);
|
||||
return pcf857x_gpio_write(dev, pin, 0);
|
||||
}
|
||||
|
||||
void pcf857x_gpio_set(pcf857x_t *dev, gpio_t pin)
|
||||
{
|
||||
DEBUG_DEV("pin=%u", dev, pin);
|
||||
return pcf857x_gpio_write(dev, pin, 1);
|
||||
}
|
||||
|
||||
void pcf857x_gpio_toggle(pcf857x_t *dev, gpio_t pin)
|
||||
{
|
||||
DEBUG_DEV("pin=%u", dev, pin);
|
||||
return pcf857x_gpio_write(dev, pin, (dev->out & (1 << pin)) ? 0 : 1);
|
||||
}
|
||||
|
||||
/** Functions for internal use only */
|
||||
|
||||
#if IS_USED(MODULE_PCF857X_IRQ)
|
||||
|
||||
/* interrupt service routine for IRQs */
|
||||
static void _irq_isr(void *arg)
|
||||
{
|
||||
assert(arg != NULL);
|
||||
|
||||
/* just indicate that an interrupt occurred and return */
|
||||
event_post(PCF857X_EVENT_PRIO, (event_t*)&((pcf857x_t*)arg)->irq_event);
|
||||
}
|
||||
|
||||
/* handle one IRQ event of device referenced by the event */
|
||||
static void _irq_handler(event_t* event)
|
||||
{
|
||||
pcf857x_irq_event_t* irq_event = (pcf857x_irq_event_t*)event;
|
||||
|
||||
assert(irq_event != NULL);
|
||||
_acquire(irq_event->dev);
|
||||
/* _update_state releases the bus */
|
||||
_update_state(irq_event->dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* @warning: It is expected that the I2C bus is already acquired when the
|
||||
* function is called. However, it is released by this function
|
||||
* before the function returns.
|
||||
*/
|
||||
static void _update_state(pcf857x_t* dev)
|
||||
{
|
||||
assert(dev != NULL);
|
||||
DEBUG_DEV("", dev);
|
||||
|
||||
/* save old input values */
|
||||
pcf857x_data_t old_in = dev->in;
|
||||
pcf857x_data_t new_in;
|
||||
|
||||
/* read in new input values and release the bus */
|
||||
if (_read(dev, &dev->in)) {
|
||||
_release(dev);
|
||||
return;
|
||||
}
|
||||
_release(dev);
|
||||
|
||||
new_in = dev->in;
|
||||
|
||||
/* iterate over all pins to check whether ISR has to be called */
|
||||
for (unsigned i = 0; i < dev->pin_num; i++) {
|
||||
pcf857x_data_t mask = 1 << i;
|
||||
|
||||
/*
|
||||
* if pin is input, interrupt is enabled, has an ISR registered
|
||||
* and the input value changed
|
||||
*/
|
||||
if (((dev->modes & mask) != 0) && dev->enabled[i] &&
|
||||
(dev->isr[i].cb != NULL) && ((old_in ^ new_in) & mask)) {
|
||||
/* check for the flank and the activated flank mode */
|
||||
if ((dev->flank[i] == GPIO_BOTH) || /* no matter what flank */
|
||||
((new_in & mask) == 0 && /* falling flank */
|
||||
(dev->flank[i] == GPIO_FALLING)) ||
|
||||
((new_in & mask) == mask && /* rising flank */
|
||||
(dev->flank[i] == GPIO_RISING))) {
|
||||
|
||||
/* call the ISR */
|
||||
dev->isr[i].cb(dev->isr[i].arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* MODULE_PCF857X_IRQ */
|
||||
|
||||
static inline void _acquire(const pcf857x_t *dev)
|
||||
{
|
||||
assert(dev != NULL);
|
||||
i2c_acquire(dev->params.dev);
|
||||
}
|
||||
|
||||
static inline void _release(const pcf857x_t *dev)
|
||||
{
|
||||
assert(dev != NULL);
|
||||
i2c_release(dev->params.dev);
|
||||
}
|
||||
|
||||
static int _read(const pcf857x_t *dev, pcf857x_data_t *data)
|
||||
{
|
||||
assert(dev != NULL);
|
||||
assert(data != NULL);
|
||||
|
||||
uint8_t bytes[2];
|
||||
size_t len = (dev->pin_num == 8) ? 1 : 2;
|
||||
|
||||
int res = i2c_read_bytes(dev->params.dev, dev->params.addr, bytes, len, 0);
|
||||
|
||||
if (res != 0) {
|
||||
DEBUG_DEV("could not read data, reason %d (%s)",
|
||||
dev, res, strerror(res * -1));
|
||||
return -PCF857X_ERROR_I2C;
|
||||
}
|
||||
|
||||
if (dev->pin_num == 8) {
|
||||
*data = bytes[0];
|
||||
DEBUG_DEV("data=%02x", dev, *data);
|
||||
}
|
||||
else {
|
||||
*data = (bytes[1] << 8) | bytes[0];
|
||||
DEBUG_DEV("data=%04x", dev, *data);
|
||||
}
|
||||
|
||||
return PCF857X_OK;
|
||||
}
|
||||
|
||||
static int _write(const pcf857x_t *dev, pcf857x_data_t data)
|
||||
{
|
||||
assert(dev != NULL);
|
||||
|
||||
uint8_t bytes[2];
|
||||
size_t len;
|
||||
|
||||
if (dev->pin_num == 8) {
|
||||
DEBUG_DEV("data=%02x", dev, data & 0xff);
|
||||
|
||||
bytes[0] = data & 0xff;
|
||||
len = 1;
|
||||
}
|
||||
else {
|
||||
DEBUG_DEV("data=%04x", dev, data);
|
||||
|
||||
bytes[0] = data & 0xff;
|
||||
bytes[1] = data >> 8;
|
||||
len = 2;
|
||||
}
|
||||
|
||||
int res = i2c_write_bytes(dev->params.dev, dev->params.addr, bytes, len, 0);
|
||||
|
||||
if (res != 0) {
|
||||
DEBUG_DEV("could not write data, reason %d (%s)",
|
||||
dev, res, strerror(res * -1));
|
||||
return -PCF857X_ERROR_I2C;
|
||||
}
|
||||
|
||||
return PCF857X_OK;
|
||||
}
|
Loading…
Reference in New Issue
Block a user