diff --git a/drivers/Kconfig b/drivers/Kconfig
index 145f9f7d35..eead0d8c2e 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -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"
diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep
index de294701c4..0940945c31 100644
--- a/drivers/Makefile.dep
+++ b/drivers/Makefile.dep
@@ -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
diff --git a/drivers/include/pcf857x.h b/drivers/include/pcf857x.h
new file mode 100644
index 0000000000..e93f0c89c1
--- /dev/null
+++ b/drivers/include/pcf857x.h
@@ -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:
+ *
+ *
+ * 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`
+ *
+ *
+ * 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:
+ *
+ * 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
+ *
+ *
+ * @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:
+ *
+ *
+ * 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
+ *
+ *
+ * ## 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` 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=...
+ *
+ *
+ * @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.
+ * 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_`. 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
+ * @file
+ */
+
+#ifndef PCF857X_H
+#define PCF857X_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include
+#include
+
+#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 */
+/** @} */
diff --git a/drivers/pcf857x/Kconfig b/drivers/pcf857x/Kconfig
new file mode 100644
index 0000000000..f60a2ab858
--- /dev/null
+++ b/drivers/pcf857x/Kconfig
@@ -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
diff --git a/drivers/pcf857x/Makefile b/drivers/pcf857x/Makefile
new file mode 100644
index 0000000000..48422e909a
--- /dev/null
+++ b/drivers/pcf857x/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/drivers/pcf857x/Makefile.dep b/drivers/pcf857x/Makefile.dep
new file mode 100644
index 0000000000..090294112c
--- /dev/null
+++ b/drivers/pcf857x/Makefile.dep
@@ -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_ 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
diff --git a/drivers/pcf857x/Makefile.include b/drivers/pcf857x/Makefile.include
new file mode 100644
index 0000000000..bddc48997f
--- /dev/null
+++ b/drivers/pcf857x/Makefile.include
@@ -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)
diff --git a/drivers/pcf857x/include/pcf857x_params.h b/drivers/pcf857x/include/pcf857x_params.h
new file mode 100644
index 0000000000..b260cd0247
--- /dev/null
+++ b/drivers/pcf857x/include/pcf857x_params.h
@@ -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
+ * @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 */
+/** @} */
diff --git a/drivers/pcf857x/pcf857x.c b/drivers/pcf857x/pcf857x.c
new file mode 100644
index 0000000000..f7f4e8b224
--- /dev/null
+++ b/drivers/pcf857x/pcf857x.c
@@ -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
+ * @file
+ * @{
+ */
+
+#include
+#include
+
+#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;
+}
diff --git a/drivers/pcf857x/pcf857x_saul.c b/drivers/pcf857x/pcf857x_saul.c
new file mode 100644
index 0000000000..84edc7f2a9
--- /dev/null
+++ b/drivers/pcf857x/pcf857x_saul.c
@@ -0,0 +1,57 @@
+/*
+ * 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 PCF857X adaption to the RIOT actuator/sensor interface
+ * @author Gunar Schorcht
+ * @file
+ */
+#if MODULE_SAUL_GPIO
+
+#include
+
+#include "saul.h"
+#include "pcf857x.h"
+
+extern pcf857x_t pcf857x_devs[];
+
+static int read(const void *dev, phydat_t *res)
+{
+ const pcf857x_saul_gpio_params_t *p = (const pcf857x_saul_gpio_params_t *)dev;
+ int inverted = (p->gpio.flags & SAUL_GPIO_INVERTED);
+
+ res->val[0] = (pcf857x_gpio_read(&pcf857x_devs[p->dev],
+ p->gpio.pin)) ? !inverted : inverted;
+ res->unit = UNIT_BOOL;
+ res->scale = 0;
+ return 1;
+}
+
+static int write(const void *dev, phydat_t *state)
+{
+ const pcf857x_saul_gpio_params_t *p = (const pcf857x_saul_gpio_params_t *)dev;
+ int inverted = (p->gpio.flags & SAUL_GPIO_INVERTED);
+ int value = (state->val[0] ? !inverted : inverted);
+
+ pcf857x_gpio_write(&pcf857x_devs[p->dev], p->gpio.pin, value);
+ return 1;
+}
+
+const saul_driver_t pcf857x_gpio_out_saul_driver = {
+ .read = read,
+ .write = write,
+ .type = SAUL_ACT_SWITCH
+};
+
+const saul_driver_t pcf857x_gpio_in_saul_driver = {
+ .read = read,
+ .write = saul_notsup,
+ .type = SAUL_SENSE_BTN
+};
+#endif /* MODULE_SAUL_GPIO */
diff --git a/drivers/saul/init_devs/auto_init_pcf857x.c b/drivers/saul/init_devs/auto_init_pcf857x.c
new file mode 100644
index 0000000000..9044e8fdd9
--- /dev/null
+++ b/drivers/saul/init_devs/auto_init_pcf857x.c
@@ -0,0 +1,105 @@
+/*
+ * 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
+ * @ingroup sys_auto_init_saul
+ * @brief Auto initialization of Texas Instruments PCF857X I2C I/O expanders
+ * @author Gunar Schorcht
+ * @file
+ * @{
+ */
+
+#if MODULE_PCF857X && MODULE_SAUL_GPIO
+
+#include "assert.h"
+#include "log.h"
+
+#include "saul_reg.h"
+#include "saul/periph.h"
+
+#include "pcf857x.h"
+#include "pcf857x_params.h"
+
+/**
+ * @brief Number of configured PCF857X I/O expander devices
+ */
+#define PCF857X_NUM ARRAY_SIZE(pcf857x_params)
+
+/**
+ * @brief Number of configured SAUL PCF857X I/O pins
+ */
+#define PCF857X_SAUL_GPIO_NUMOF ARRAY_SIZE(pcf857x_saul_gpio_params)
+
+/**
+ * @brief Number of saul info
+ */
+#define PCF857X_INFO_NUM ARRAY_SIZE(pcf857x_saul_info)
+
+/**
+ * @brief Allocate the memory for the PCF857X I/O expander device descriptors
+ */
+pcf857x_t pcf857x_devs[PCF857X_NUM];
+
+/**
+ * @brief Allocate the memory for PCF857X I/O expander SAUL registry entries
+ */
+static saul_reg_t pcf857x_saul_reg_entries[PCF857X_SAUL_GPIO_NUMOF];
+
+/**
+ * @brief Reference the PCF857X I/O expander input mode driver struct
+ */
+extern saul_driver_t pcf857x_gpio_in_saul_driver;
+
+/**
+ * @brief Reference to the PCF857X I/O expander output mode driver struct
+ */
+extern saul_driver_t pcf857x_gpio_out_saul_driver;
+
+void auto_init_pcf857x(void)
+{
+ for (unsigned int i = 0; i < PCF857X_NUM; i++) {
+ LOG_DEBUG("[auto_init_saul] initializing PCF857X I/O expander dev #%u\n", i);
+ pcf857x_init(&pcf857x_devs[i], &pcf857x_params[i]);
+ }
+
+ for (unsigned int i = 0; i < PCF857X_SAUL_GPIO_NUMOF; i++) {
+ const pcf857x_saul_gpio_params_t *p = &pcf857x_saul_gpio_params[i];
+
+ LOG_DEBUG("[auto_init_saul] initializing PCF857X GPIO #%u\n", i);
+
+ /* check the PCF857X device index */
+ assert(p->dev < PCF857X_NUM);
+ /* check the PCF857X device index */
+ assert(p->gpio.pin < PCF857X_GPIO_PIN_NUM);
+
+ pcf857x_saul_reg_entries[i].dev = (void *)p;
+ pcf857x_saul_reg_entries[i].name = p->gpio.name;
+ if ((p->gpio.mode == GPIO_IN) ||
+ (p->gpio.mode == GPIO_IN_PD) ||
+ (p->gpio.mode == GPIO_IN_PU)) {
+ pcf857x_saul_reg_entries[i].driver = &pcf857x_gpio_in_saul_driver;
+ }
+ else {
+ pcf857x_saul_reg_entries[i].driver = &pcf857x_gpio_out_saul_driver;
+ }
+ /* initialize the PCF857X pin */
+ pcf857x_gpio_init(&pcf857x_devs[p->dev], p->gpio.pin, p->gpio.mode);
+ /* set initial pin PCF857X state if configured */
+ if (p->gpio.flags & (SAUL_GPIO_INIT_CLEAR | SAUL_GPIO_INIT_SET)) {
+ phydat_t s;
+ s.val[0] = (p->gpio.flags & SAUL_GPIO_INIT_SET);
+ pcf857x_saul_reg_entries[i].driver->write(p, &s);
+ }
+ /* add to registry */
+ saul_reg_add(&(pcf857x_saul_reg_entries[i]));
+ }
+}
+#else
+typedef int dont_be_pedantic;
+#endif /* MODULE_PCF857X && MODULE_SAUL_GPIO */
diff --git a/drivers/saul/init_devs/init.c b/drivers/saul/init_devs/init.c
index ebacd494e8..e052c8410c 100644
--- a/drivers/saul/init_devs/init.c
+++ b/drivers/saul/init_devs/init.c
@@ -223,6 +223,10 @@ void saul_init_devs(void)
extern void auto_init_pca9685(void);
auto_init_pca9685();
}
+ if (IS_USED(MODULE_PCF857X)) {
+ extern void auto_init_pcf857x(void);
+ auto_init_pcf857x();
+ }
if (IS_USED(MODULE_PH_OEM)) {
extern void auto_init_ph_oem(void);
auto_init_ph_oem();
diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk
index 753b1708f7..15bae104e2 100644
--- a/makefiles/pseudomodules.inc.mk
+++ b/makefiles/pseudomodules.inc.mk
@@ -174,6 +174,12 @@ NO_PSEUDOMODULES += netdev_ieee802154_submac
# print ascii representation in function od_hex_dump()
PSEUDOMODULES += od_string
+# include variants of PCF857X drivers as pseudo modules
+PSEUDOMODULES += pcf857x_irq
+PSEUDOMODULES += pcf8574
+PSEUDOMODULES += pcf8574a
+PSEUDOMODULES += pcf8575
+
# add all pseudo random number generator variants as pseudomodules
PSEUDOMODULES += prng_%
diff --git a/tests/driver_pcf857x/Makefile b/tests/driver_pcf857x/Makefile
new file mode 100644
index 0000000000..1e785fac83
--- /dev/null
+++ b/tests/driver_pcf857x/Makefile
@@ -0,0 +1,13 @@
+INCLUDES += -I$(APPDIR)
+
+include ../Makefile.tests_common
+
+ifeq (,$(filter pcf857%,$(USEMODULE)))
+ # default expander module
+ USEMODULE += pcf8575
+endif
+
+USEMODULE += shell
+USEMODULE += benchmark
+
+include $(RIOTBASE)/Makefile.include
diff --git a/tests/driver_pcf857x/Makefile.ci b/tests/driver_pcf857x/Makefile.ci
new file mode 100644
index 0000000000..1152ca53bc
--- /dev/null
+++ b/tests/driver_pcf857x/Makefile.ci
@@ -0,0 +1,8 @@
+BOARD_INSUFFICIENT_MEMORY := \
+ arduino-duemilanove \
+ arduino-leonardo \
+ arduino-nano \
+ arduino-uno \
+ atmega328p \
+ atmega328p-xplained-mini \
+ #
diff --git a/tests/driver_pcf857x/README.md b/tests/driver_pcf857x/README.md
new file mode 100644
index 0000000000..39f7f7e6c8
--- /dev/null
+++ b/tests/driver_pcf857x/README.md
@@ -0,0 +1,70 @@
+# Texas Instruments PCF857X I2C I/O expanders test application
+
+## Overview
+
+This test appliation demonstrates the usage of the PCF857X driver interface
+and can be used to test each PCF857X expander I/O pin with shell commands.
+
+## Compilation
+
+To use the test application, compile it with one or more of the pseudomodules
+`pcf8574`, `pcf8574a` or `pcf8575` to enable the driver for your
+expander modules. Please check the default configuration parameters in
+`$(RIOTBASE)/drivers/pcf857x/include/pcf857x_params.h` and modify them
+if necessary. Alternatively, a modified version of this file could be
+placed in the directory of this test application to override it.
+```
+USEMODULE=pcf8575 make -C tests/driver_pcf857x BOARD=...
+```
+**Please note:** When no pseudomodule is given, `pcf8575` is used by default.
+
+To use external interrupts with the expander I/O pins, the PCF857X
+low-active open-drain interrupt signal has to be enabled. Add module
+`pcf857x_irq` for this purpose and define the MCU interrupt pin by
+parameter `PCF857X_PARAM_INT_PIN`, e.g.
+```
+CFLAGS="-DPCF857X_PARAM_INT_PIN=\(GPIO_PIN\(0,6\)\)" \
+USEMODULE="pcf8575 pcf857x_irq" make -C tests/driver_pcf857x BOARD=...
+```
+**Please note:** Since interrupts are handled in the context of a separate
+event thread, enabling interrupts requires more RAM.
+
+## Usage
+
+The test allows to use commands as known from GPIO test application for
+PCF857X expanders:
+```
+> help
+Command Description
+---------------------------------------
+init_out init as output (push-pull mode)
+init_in init as input w/o pull resistor
+init_in_pu init as input with pull-up
+init_od init as output (open-drain without pull resistor)
+init_od_pu init as output (open-drain with pull-up)
+init_int init as external INT w/o pull resistor
+enable_int enable or disable gpio interrupt
+read read pin status
+set set pin to HIGH
+clear set pin to LOW
+toggle toggle pin
+bench run a set of predefined benchmarks
+
+```
+The number of the first PCF857X expander port used by the test application
+is defined by the macro `PCF857X_PORT_0`, which is 16 by default. This value
+can be overridden during compilation, e.g.:
+```
+CFLAGS="-DPCF857X_PORT_0=8" \
+USEMODULE=pcf8575 make -C tests/driver_pcf857x BOARD=...
+```
+Using the port number defined by `PCF857X_PORT_0` and the following port
+numbers, you can apply the command to the PCF857X expander ports. For
+example, the following command initializes I/O pin 7 of the first PCF857X
+expander:
+```
+init_out 16 7
+```
+Commands with port numbers less than `PCF857X_PORT_0` refer to GPIO
+peripheral ports. Thus, both the I/O pins of the PCF857X expanders as well
+as the GPIO peripheral pins of the MCU can be addressed by all commands.
diff --git a/tests/driver_pcf857x/app.config.test b/tests/driver_pcf857x/app.config.test
new file mode 100644
index 0000000000..6366f1bfcf
--- /dev/null
+++ b/tests/driver_pcf857x/app.config.test
@@ -0,0 +1,7 @@
+# this file enables modules defined in Kconfig. Do not use this file for
+# application configuration. This is only needed during migration.
+CONFIG_MODULE_PCF857X=y
+
+CONFIG_MODULE_SHELL=y
+CONFIG_MODULE_BENCHMARK=y
+CONFIG_ZTIMER_USEC=y
diff --git a/tests/driver_pcf857x/main.c b/tests/driver_pcf857x/main.c
new file mode 100644
index 0000000000..89a6c12610
--- /dev/null
+++ b/tests/driver_pcf857x/main.c
@@ -0,0 +1,434 @@
+/*
+ * 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 tests
+ * @brief Test application for Texas Instruments PCF857X I2C I/O expanders
+ * @author Gunar Schorcht
+ * @file
+ *
+ * ## Overview
+ *
+ * This test appliation demonstrates the usage of the PCF857X driver interface
+ * and can be used to test each PCF857X expander I/O pin with shell commands.
+ *
+ * The application bases on the test application for GPIO peripheral drivers
+ * which is under following copyright:
+ *
+ * Copyright (C) 2014,2017 Freie Universität Berlin
+ * @author Hauke Petersen
+ *
+ * ## Compilation
+ *
+ * To use the test application, compile it with one or more of the pseudomodules
+ * `pcf8574`, `pcf8574a` or `pcf8575` to enable the driver for your
+ * expander modules. Please check the default configuration parameters in
+ * `$(RIOTBASE)/drivers/pcf857x/include/pcf857x_params.h` and modify them
+ * if necessary. Alternatively, a modified version of this file could be
+ * placed in the directory of this test application to override it.
+ * ```
+ * USEMODULE=pcf8575 make -C tests/driver_pcf857x BOARD=...
+ * ```
+ * @note When no pseudomodule is given, `pcf8575` is used by default.
+ *
+ * To use external interrupts with the expander I/O pins, the PCF857X
+ * low-active open-drain interrupt signal has to be enabled. Add module
+ * `pcf857x_irq` for this purpose and define the MCU interrupt pin by
+ * parameter `PCF857X_PARAM_INT_PIN`, e.g.
+ * ```
+ * CFLAGS="-DPCF857X_PARAM_INT_PIN=\(GPIO_PIN\(0,6\)\)" \
+ * USEMODULE="pcf8575 pcf857x_irq" make -C tests/driver_pcf857x BOARD=...
+ * ```
+ * @note Since interrupts are handled in the context of a separate event thread,
+ * enabling interrupts requires more RAM.
+ *
+ * ## Usage
+ *
+ * The test allows to use commands as known from GPIO test application for
+ * PCF857X expanders:
+ * ```
+ * > help
+ * Command Description
+ * ---------------------------------------
+ * init_out init as output (push-pull mode)
+ * init_in init as input w/o pull resistor
+ * init_in_pu init as input with pull-up
+ * init_od init as output (open-drain without pull resistor)
+ * init_od_pu init as output (open-drain with pull-up)
+ * init_int init as external INT w/o pull resistor
+ * enable_int enable or disable gpio interrupt
+ * read read pin status
+ * set set pin to HIGH
+ * clear set pin to LOW
+ * toggle toggle pin
+ * bench run a set of predefined benchmarks
+ * ```
+ * The number of the first PCF857X expander port used by the test application
+ * is defined by the macro `PCF857X_PORT_0`, which is 16 by default. This value
+ * can be overridden during compilation, e.g.:
+ * ```
+ * CFLAGS="-DPCF857X_PORT_0=8" \
+ * USEMODULE=pcf8575 make -C tests/driver_pcf857x BOARD=...
+ * ```
+ * Using the port number defined by `PCF857X_PORT_0` and the following port
+ * numbers, you can apply the command to the PCF857X expander ports. For
+ * example, the following command initializes I/O pin 7 of the first PCF857X
+ * expander:
+ * ```
+ * init_out 16 7
+ * ```
+ * Commands with port numbers less than `PCF857X_PORT_0` refer to GPIO
+ * peripheral ports. Thus, both the I/O pins of the PCF857X expanders as well
+ * as the GPIO peripheral pins of the MCU can be addressed by all commands.
+ */
+
+#include
+#include
+
+#include "pcf857x.h"
+#include "pcf857x_params.h"
+
+#include "irq.h"
+#include "shell.h"
+#include "benchmark.h"
+
+#define BENCH_RUNS_DEFAULT (100UL * 100)
+
+/* Number of configured PCF857X I/O expander devices */
+#define PCF857X_NUM ARRAY_SIZE(pcf857x_params)
+
+/* Port number of the first PCF857X I/O expander device */
+#ifndef PCF857X_PORT_0
+#define PCF857X_PORT_0 (16)
+#endif
+
+/* PCF857X devices allocation */
+pcf857x_t pcf857x_dev[PCF857X_NUM];
+
+#ifdef MODULE_PCF857X_IRQ
+static void cb(void *arg)
+{
+ printf("INT: external interrupt from pin %i\n", (int)arg);
+}
+#endif
+
+static int init_pin(int argc, char **argv, gpio_mode_t mode)
+{
+ if (argc < 3) {
+ printf("usage: %s \n", argv[0]);
+ return 1;
+ }
+
+ int po = atoi(argv[1]);
+ int pi = atoi(argv[2]);
+
+ if (po < PCF857X_PORT_0) {
+ gpio_init(GPIO_PIN(po, pi), mode);
+ }
+ else if (pcf857x_gpio_init(&pcf857x_dev[po - PCF857X_PORT_0],
+ pi, mode) < 0) {
+ printf("error: init PCF857X pin (dev %i, pin %02i) failed\n", po, pi);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int init_out(int argc, char **argv)
+{
+ return init_pin(argc, argv, GPIO_OUT);
+}
+
+static int init_in(int argc, char **argv)
+{
+ return init_pin(argc, argv, GPIO_IN);
+}
+
+static int init_in_pu(int argc, char **argv)
+{
+ return init_pin(argc, argv, GPIO_IN_PU);
+}
+
+static int init_od(int argc, char **argv)
+{
+ return init_pin(argc, argv, GPIO_OD);
+}
+
+static int init_od_pu(int argc, char **argv)
+{
+ return init_pin(argc, argv, GPIO_OD_PU);
+}
+
+#ifdef MODULE_PCF857X_IRQ
+static int init_int(int argc, char **argv)
+{
+ gpio_mode_t mode = GPIO_IN;
+ gpio_flank_t flank;
+ int fl;
+
+ if (argc < 4) {
+ printf("usage: %s \n", argv[0]);
+ puts("\tflank:\n"
+ "\t0: falling\n"
+ "\t1: rising\n"
+ "\t2: both\n");
+ return 1;
+ }
+
+ int po = atoi(argv[1]);
+ int pi = atoi(argv[2]);
+
+ fl = atoi(argv[3]);
+ switch (fl) {
+ case 0:
+ flank = GPIO_FALLING;
+ break;
+ case 1:
+ flank = GPIO_RISING;
+ break;
+ case 2:
+ flank = GPIO_BOTH;
+ break;
+ default:
+ puts("error: invalid value for active flank");
+ return 1;
+ }
+
+ if (po < PCF857X_PORT_0) {
+ gpio_init_int(GPIO_PIN(po, pi), mode, flank, cb, (void *)pi);
+ }
+ else if (pcf857x_gpio_init_int(&pcf857x_dev[po - PCF857X_PORT_0], pi,
+ mode, flank, cb, (void *)pi) < 0) {
+ printf("error: init_int PCF857X pin (dev %i, pin %02i) failed\n",
+ po, pi);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int enable_int(int argc, char **argv)
+{
+ int status;
+
+ if (argc < 4) {
+ printf("usage: %s \n", argv[0]);
+ puts("\tstatus:\n"
+ "\t0: disable\n"
+ "\t1: enable\n");
+ return 1;
+ }
+
+ int po = atoi(argv[1]);
+ int pi = atoi(argv[2]);
+
+ status = atoi(argv[3]);
+
+ switch (status) {
+ case 0:
+ puts("disabling GPIO interrupt");
+ if (po < PCF857X_PORT_0) {
+ gpio_irq_disable(GPIO_PIN(po, pi));
+ }
+ else {
+ pcf857x_gpio_irq_disable(&pcf857x_dev[po - PCF857X_PORT_0], pi);
+ }
+ break;
+ case 1:
+ puts("enabling GPIO interrupt");
+ if (po < PCF857X_PORT_0) {
+ gpio_irq_enable(GPIO_PIN(po, pi));
+ }
+ else {
+ pcf857x_gpio_irq_enable(&pcf857x_dev[po - PCF857X_PORT_0], pi);
+ }
+ break;
+ default:
+ puts("error: invalid status");
+ return 1;
+ }
+
+ return 0;
+}
+#endif /* MODULE_PCF857X_IRQ */
+
+static int read(int argc, char **argv)
+{
+ if (argc < 3) {
+ printf("usage: %s \n", argv[0]);
+ return 1;
+ }
+
+ int po = atoi(argv[1]);
+ int pi = atoi(argv[2]);
+
+ if (po < PCF857X_PORT_0) {
+ if (gpio_read(GPIO_PIN(po, pi))) {
+ printf("GPIO pin (port %i, pin %02i) is HIGH\n", po, pi);
+ }
+ else {
+ printf("GPIO pin (port %i, pin %02i) is LOW\n", po, pi);
+ } }
+ else {
+ if (pcf857x_gpio_read(&pcf857x_dev[po - PCF857X_PORT_0], pi)) {
+ printf("PCF857X pin (dev %i, pin %02i) is HIGH\n", po, pi);
+ }
+ else {
+ printf("PCF857X pin (dev %i, pin %02i) is LOW\n", po, pi);
+ }
+ }
+ return 0;
+}
+
+static int set(int argc, char **argv)
+{
+ if (argc < 3) {
+ printf("usage: %s \n", argv[0]);
+ return 1;
+ }
+
+ int po = atoi(argv[1]);
+ int pi = atoi(argv[2]);
+
+ if (po < PCF857X_PORT_0) {
+ gpio_set(GPIO_PIN(po, pi));
+ }
+ else {
+ pcf857x_gpio_set(&pcf857x_dev[po - PCF857X_PORT_0], pi);
+ }
+ return 0;
+}
+
+static int clear(int argc, char **argv)
+{
+ if (argc < 3) {
+ printf("usage: %s \n", argv[0]);
+ return 1;
+ }
+
+ int po = atoi(argv[1]);
+ int pi = atoi(argv[2]);
+
+ if (po < PCF857X_PORT_0) {
+ gpio_clear(GPIO_PIN(po, pi));
+ }
+ else {
+ pcf857x_gpio_clear(&pcf857x_dev[po - PCF857X_PORT_0], pi);
+ }
+ return 0;
+}
+
+static int toggle(int argc, char **argv)
+{
+ if (argc < 3) {
+ printf("usage: %s \n", argv[0]);
+ return 1;
+ }
+
+ int po = atoi(argv[1]);
+ int pi = atoi(argv[2]);
+
+ if (po < PCF857X_PORT_0) {
+ gpio_toggle(GPIO_PIN(po, pi));
+ }
+ else {
+ pcf857x_gpio_toggle(&pcf857x_dev[po - PCF857X_PORT_0], pi);
+ }
+ return 0;
+}
+
+static int bench(int argc, char **argv)
+{
+ if (argc < 3) {
+ printf("usage: %s [# of runs]\n", argv[0]);
+ return 1;
+ }
+
+ int po = atoi(argv[1]);
+ int pi = atoi(argv[2]);
+
+ unsigned long runs = BENCH_RUNS_DEFAULT;
+ if (argc > 3) {
+ runs = (unsigned long)atol(argv[3]);
+ }
+
+ puts("\nGPIO driver run-time performance benchmark\n");
+
+ if (po < PCF857X_PORT_0) {
+ BENCHMARK_FUNC("nop loop", runs, __asm__ volatile("nop"));
+ gpio_init(GPIO_PIN(po, pi), GPIO_OUT);
+ BENCHMARK_FUNC("gpio_set", runs, gpio_set(GPIO_PIN(po, pi)));
+ BENCHMARK_FUNC("gpio_clear", runs, gpio_clear(GPIO_PIN(po, pi)));
+ BENCHMARK_FUNC("gpio_toggle", runs, gpio_toggle(GPIO_PIN(po, pi)));
+ gpio_init(GPIO_PIN(po, pi), GPIO_IN);
+ BENCHMARK_FUNC("gpio_read", runs, (void)gpio_read(GPIO_PIN(po, pi)));
+ gpio_init(GPIO_PIN(po, pi), GPIO_OUT);
+ BENCHMARK_FUNC("gpio_write", runs, gpio_write(GPIO_PIN(po, pi), 1));
+ }
+ else {
+ pcf857x_t* dev = &pcf857x_dev[po - PCF857X_PORT_0];
+ BENCHMARK_FUNC("nop loop", runs, __asm__ volatile("nop"));
+ pcf857x_gpio_init(dev, pi, GPIO_OUT);
+ BENCHMARK_FUNC("gpio_set", runs, pcf857x_gpio_set(dev, pi));
+ BENCHMARK_FUNC("gpio_clear", runs, pcf857x_gpio_clear(dev, pi));
+ BENCHMARK_FUNC("gpio_toggle", runs, pcf857x_gpio_toggle(dev, pi));
+ pcf857x_gpio_init(dev, pi, GPIO_IN);
+ BENCHMARK_FUNC("gpio_read", runs, (void)pcf857x_gpio_read(dev, pi));
+ pcf857x_gpio_init(dev, pi, GPIO_OUT);
+ BENCHMARK_FUNC("gpio_write", runs, pcf857x_gpio_write(dev, pi, 1));
+ puts("\n --- DONE ---");
+ }
+ return 0;
+}
+
+static const shell_command_t shell_commands[] = {
+ { "init_out", "init as output (push-pull mode)", init_out },
+ { "init_in", "init as input w/o pull resistor", init_in },
+ { "init_in_pu", "init as input with pull-up", init_in_pu },
+ { "init_od", "init as output (open-drain without pull resistor)", init_od },
+ { "init_od_pu", "init as output (open-drain with pull-up)", init_od_pu },
+#ifdef MODULE_PCF857X_IRQ
+ { "init_int", "init as external INT w/o pull resistor", init_int },
+ { "enable_int", "enable or disable gpio interrupt", enable_int },
+#endif
+ { "read", "read pin status", read },
+ { "set", "set pin to HIGH", set },
+ { "clear", "set pin to LOW", clear },
+ { "toggle", "toggle pin", toggle },
+ { "bench", "run a set of predefined benchmarks", bench },
+ { NULL, NULL, NULL }
+};
+
+int main(void)
+{
+ puts("PCF857X I/O expander GPIO peripheral driver test\n");
+ puts("Initializing PCF857X");
+
+ /* initialize configured PCF857X devices */
+ for (unsigned i = 0; i < PCF857X_NUM; i++) {
+ if (pcf857x_init(&pcf857x_dev[i], &pcf857x_params[i]) != PCF857X_OK) {
+ puts("[Failed]");
+ return 1;
+ }
+ }
+ puts("[OK]\n");
+
+ printf("In this test, pins are specified by integer port and pin numbers.\n"
+ "PCF8574 has 8 I/O pins labeled P00...P07.\n"
+ "PCF8575 has 16 I/O pins labeled P00...P07 and P10...P17\n"
+ "Use port %d and pin 0...15 in all commands to access them.\n\n"
+ "NOTE: make sure the values you use exist! The\n"
+ " behavior for not existing ports/pins is not defined!\n",
+ PCF857X_PORT_0);
+
+ /* start the shell */
+ char line_buf[SHELL_DEFAULT_BUFSIZE];
+ shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
+
+ return 0;
+}