mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-17 18:43:50 +01:00
623 lines
18 KiB
C
623 lines
18 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup drivers_mcp23x17
|
|
* @brief Device driver implementation for Microchip MCP23x17 I/O expanders
|
|
* @author Gunar Schorcht <gunar@schorcht.net>
|
|
* @file
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "assert.h"
|
|
#include "periph/i2c.h"
|
|
#include "ztimer.h"
|
|
|
|
#include "mcp23x17.h"
|
|
#include "mcp23x17_regs.h"
|
|
|
|
#if IS_USED(MODULE_MCP23X17_IRQ)
|
|
#include "event/thread.h"
|
|
#endif
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
#if ENABLE_DEBUG
|
|
|
|
#include <string.h>
|
|
|
|
#define DEBUG_DEV(m, d, ...) \
|
|
DEBUG("[mcp23x17] %s dev=%" PRIxPTR " addr=%02x: " m "\n", \
|
|
__func__, (unsigned int)d, d->params.addr, ## __VA_ARGS__)
|
|
|
|
#else /* ENABLE_DEBUG */
|
|
|
|
#define DEBUG_DEV(f, d, ...)
|
|
|
|
#endif /* ENABLE_DEBUG */
|
|
|
|
#define _ADDR (MCP23X17_BASE_ADDR + dev->params.addr)
|
|
|
|
#if IS_USED(MODULE_MCP23X17_SPI)
|
|
#define _SPI_DEV (dev->params.if_params.spi.dev)
|
|
#define _SPI_CS (dev->params.if_params.spi.cs)
|
|
#define _SPI_CLK (dev->params.if_params.spi.clk)
|
|
#endif
|
|
|
|
#if IS_USED(MODULE_MCP23X17_I2C)
|
|
#define _I2C_DEV (dev->params.if_params.i2c.dev)
|
|
#endif
|
|
|
|
#if IS_USED(MODULE_MCP23X17_IRQ)
|
|
|
|
#if IS_USED(MODULE_MCP23X17_IRQ_MEDIUM)
|
|
#define MCP23X17_EVENT_PRIO EVENT_PRIO_MEDIUM
|
|
#elif IS_USED(MODULE_MCP23X17_IRQ_HIGHEST)
|
|
#define MCP23X17_EVENT_PRIO EVENT_PRIO_HIGHEST
|
|
#endif
|
|
|
|
/* interrupt service routine for IRQs */
|
|
static void _irq_isr(void *arg);
|
|
|
|
/* declaration of IRQ handler function */
|
|
static void _irq_handler(event_t *event);
|
|
|
|
#endif /* MODULE_MCP23X17_IRQ */
|
|
|
|
/* forward declarations of internal functions */
|
|
static void _acquire(const mcp23x17_t *dev);
|
|
static void _release(const mcp23x17_t *dev);
|
|
static int _read(const mcp23x17_t *dev, uint8_t reg, uint8_t *data, size_t len);
|
|
static int _write(const mcp23x17_t *dev, uint8_t reg, const uint8_t *data, size_t len);
|
|
static int _update_pin(const mcp23x17_t *dev, uint8_t reg, gpio_t pin, int value);
|
|
|
|
/* static power on reset configuration */
|
|
static const uint8_t _reset_conf[] =
|
|
{
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
int mcp23x17_init(mcp23x17_t *dev, const mcp23x17_params_t* params)
|
|
{
|
|
assert(dev);
|
|
assert(params);
|
|
|
|
dev->params = *params;
|
|
dev->od_pins = 0;
|
|
|
|
DEBUG_DEV("params %p", dev, params);
|
|
|
|
int res = MCP23X17_OK;
|
|
|
|
#if IS_USED(MODULE_MCP23X17_SPI)
|
|
if (params->if_params.type == MCP23X17_SPI) {
|
|
/* CS pin has to be defined and has to be initialized */
|
|
assert(gpio_is_valid(_SPI_CS));
|
|
if (spi_init_cs(_SPI_DEV, _SPI_CS) != SPI_OK) {
|
|
DEBUG_DEV("CS pin defined but could not be initialized\n", dev);
|
|
return -MCP23X17_ERROR_NO_DEV;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if IS_USED(MODULE_MCP23X17_RESET)
|
|
/* GPIO pin for the RESET signal has to be define and initialized */
|
|
assert(gpio_is_valid(params->reset_pin));
|
|
|
|
/* initialize the low active RESET pin */
|
|
if (gpio_init(params->reset_pin, GPIO_OUT)) {
|
|
DEBUG_DEV("RESET pin defined but could not be initialized", dev);
|
|
return -MCP23X17_ERROR_RESET_PIN;
|
|
}
|
|
/* hardware reset impuls for at least 10 us is required, we use 1 ms */
|
|
gpio_clear(params->reset_pin);
|
|
ztimer_sleep(ZTIMER_MSEC, 1);
|
|
gpio_set(params->reset_pin);
|
|
#endif
|
|
|
|
#if IS_USED(MODULE_MCP23X17_IRQ)
|
|
/* GPIO pin for combined interrupt signal INTA/INTB has to be defined */
|
|
assert(gpio_is_valid(params->int_pin));
|
|
|
|
/* 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 < MCP23X17_GPIO_PIN_NUM; i++) {
|
|
dev->isr[i].cb = NULL;
|
|
dev->isr[i].arg = NULL;
|
|
}
|
|
|
|
/* GPIO for interrupt signal has to be initialized */
|
|
if (gpio_init_int(dev->params.int_pin, GPIO_IN, GPIO_FALLING,
|
|
_irq_isr, (void*)dev)) {
|
|
DEBUG_DEV("INT pin defined but could not be initialized", dev);
|
|
return -MCP23X17_ERROR_INT_PIN;
|
|
}
|
|
#endif /* MODULE_MCP23X17_IRQ */
|
|
|
|
_acquire(dev);
|
|
|
|
/*
|
|
* After power on reset or hardware reset, the default BANK mode is 0
|
|
* i.e., the A/B registers are paired and in the same bank with
|
|
* subsequent ascending addresses.
|
|
* Since the BANK mode is never changed, we can rely on the addressing
|
|
* scheme as define in mcp23x17_regs.h.
|
|
*/
|
|
|
|
uint8_t iocon; /* configuration register is the same for port A and B */
|
|
|
|
/* read the configuration registers to see whether device is reachable */
|
|
if (_read(dev, MCP23X17_REG_IOCONA, &iocon, 1) ||
|
|
_read(dev, MCP23X17_REG_IOCONB, &iocon, 1)) {
|
|
DEBUG_DEV("error reading IOCON registers", dev);
|
|
_release(dev);
|
|
return -MCP23X17_ERROR_NO_DEV;
|
|
}
|
|
|
|
/*
|
|
* After power on reset or hardware reset, the configuration of GPIOs is:
|
|
*
|
|
* - GPIO pins are defined as inputs
|
|
* - GPIO registers reflects the same logic state of the input pin
|
|
* - GPIO pin value is compared against previous pin value for
|
|
* interrupt-on-change
|
|
* - GPIO input pins are disabled for interrupts on change
|
|
* - GPIO pull-ups are disabled
|
|
* - GPIO default output values are 0
|
|
*
|
|
* If hardware reset is not used, we have to restore this configuration
|
|
* after system reboots
|
|
*/
|
|
res |= _write(dev, MCP23X17_REG_IODIR, _reset_conf, ARRAY_SIZE(_reset_conf));
|
|
|
|
#if IS_USED(MODULE_MCP23X17_IRQ)
|
|
/* INT is configured as push/pull and is active low */
|
|
iocon &= ~MCP23X17_IOCON_ODR;
|
|
iocon &= ~MCP23X17_IOCON_INTPOL;
|
|
|
|
/*
|
|
* Since we use only one pin for INTA and INTB signal, we have to use
|
|
* the MIRROR mode, i.e., an interrupt on either port will cause both
|
|
* interrupt pins to activate.
|
|
*/
|
|
iocon |= MCP23X17_IOCON_MIRROR;
|
|
|
|
/*
|
|
* Reset all interrupt-on-change control bits to 0, that is, pin values
|
|
* are compared against the previous value for interrupt-on-change.
|
|
* Disable all interrupts.
|
|
*
|
|
* Since this corresponds to the power on reset configuration written
|
|
* before, we have not execute it here.
|
|
*/
|
|
#if 0
|
|
uint8_t zero = 0;
|
|
res |= _write(dev, MCP23X17_REG_INTCONA, &zero, 1);
|
|
res |= _write(dev, MCP23X17_REG_INTCONB, &zero, 1);
|
|
res |= _write(dev, MCP23X17_REG_GPINTENA, &zero, 1);
|
|
res |= _write(dev, MCP23X17_REG_GPINTENB, &zero, 1);
|
|
#endif
|
|
|
|
#endif /* MODULE_MCP23X17_IRQ */
|
|
|
|
iocon &= ~MCP23X17_IOCON_SEQOP; /* sequential operation mode enabled */
|
|
iocon &= ~MCP23X17_IOCON_DISSLW; /* slew rate control enabled */
|
|
iocon |= MCP23X17_IOCON_HAEN; /* hardware addressing enabled */
|
|
|
|
/* write back configuration registers */
|
|
res |= _write(dev, MCP23X17_REG_IOCONA, &iocon, 1);
|
|
res |= _write(dev, MCP23X17_REG_IOCONB, &iocon, 1);
|
|
|
|
_release(dev);
|
|
|
|
return res;
|
|
}
|
|
|
|
int mcp23x17_gpio_init(mcp23x17_t *dev, gpio_t pin, gpio_mode_t mode)
|
|
{
|
|
assert(dev);
|
|
assert(pin < MCP23X17_GPIO_PIN_NUM);
|
|
|
|
DEBUG_DEV("pin %u, mode %d", dev, pin, mode);
|
|
|
|
uint8_t port = pin >> 3;
|
|
uint8_t pin_bit = 1 << (pin & 0x07);
|
|
|
|
uint8_t iodir;
|
|
uint8_t gppu;
|
|
uint8_t gpinten;
|
|
int res;
|
|
|
|
_acquire(dev);
|
|
/* read the I/O direction configuration and GPIO pull-up register */
|
|
if ((res = _read(dev, MCP23X17_REG_IODIR + port, &iodir, 1)) ||
|
|
(res = _read(dev, MCP23X17_REG_GPPU + port, &gppu, 1)) ||
|
|
(res = _read(dev, MCP23X17_REG_GPINTEN + port, &gpinten, 1))) {
|
|
DEBUG_DEV("error reading IODIR, GPPU or GPINTEN register", dev);
|
|
_release(dev);
|
|
return res;
|
|
}
|
|
|
|
/* set default configuration first */
|
|
iodir |= pin_bit; /* input pin */
|
|
gppu &= ~pin_bit; /* no pull-up */
|
|
gpinten &= ~pin_bit; /* interrupt disabled */
|
|
dev->od_pins &= ~(1 << pin); /* open-drain flag not set */
|
|
|
|
/* override only non default settings */
|
|
switch (mode) {
|
|
case GPIO_OUT: iodir &= ~pin_bit; /* change direction to output */
|
|
break;
|
|
case GPIO_OD_PU: gppu |= pin_bit; /* enable pull-up */
|
|
/* intentionally falls through */
|
|
case GPIO_OD: dev->od_pins |= (1 << pin); /* set open-drain flag */
|
|
_update_pin(dev, MCP23X17_REG_GPIO, pin, 0); /* clear pin */
|
|
break;
|
|
case GPIO_IN_PU: gppu |= pin_bit; /* enable pull-up */
|
|
/* intentionally falls through */
|
|
case GPIO_IN: break;
|
|
|
|
default: DEBUG_DEV("invalid pin mode", dev);
|
|
_release(dev);
|
|
return -MCP23X17_ERROR_INV_MODE;
|
|
}
|
|
|
|
/* write back the I/O direction configuration and GPIO pull-up register */
|
|
if ((res = _write(dev, MCP23X17_REG_GPINTEN + port, &gpinten, 1)) ||
|
|
(res = _write(dev, MCP23X17_REG_IODIR + port, &iodir, 1)) ||
|
|
(res = _write(dev, MCP23X17_REG_GPPU + port, &gppu, 1))) {
|
|
DEBUG_DEV("error writing IODIR, GPPU or GPINTEN register", dev);
|
|
_release(dev);
|
|
return res;
|
|
}
|
|
|
|
_release(dev);
|
|
return MCP23X17_OK;
|
|
}
|
|
|
|
int mcp23x17_gpio_read(mcp23x17_t *dev, gpio_t pin)
|
|
{
|
|
assert(dev);
|
|
assert(pin < MCP23X17_GPIO_PIN_NUM);
|
|
|
|
DEBUG_DEV("pin %u", dev, pin);
|
|
|
|
uint8_t port = pin >> 3;
|
|
pin -= (port << 3);
|
|
|
|
uint8_t gpio;
|
|
|
|
/* read the GPIO port register */
|
|
_acquire(dev);
|
|
int res = _read(dev, MCP23X17_REG_GPIO + port, &gpio, 1);
|
|
_release(dev);
|
|
|
|
if (res) {
|
|
DEBUG_DEV("error reading GPIO register", dev);
|
|
return res;
|
|
}
|
|
|
|
return (gpio & (1 << pin)) ? 1 : 0;
|
|
}
|
|
|
|
void mcp23x17_gpio_write(mcp23x17_t *dev, gpio_t pin, int value)
|
|
{
|
|
assert(dev);
|
|
assert(pin < MCP23X17_GPIO_PIN_NUM);
|
|
|
|
DEBUG_DEV("pin %u, value %d", dev, pin, value);
|
|
|
|
_acquire(dev);
|
|
|
|
/* check whether it is an emulated OD pin */
|
|
if (dev->od_pins & (1 << pin)) {
|
|
if (value) {
|
|
/* simply set direction to input */
|
|
_update_pin(dev, MCP23X17_REG_IODIR, pin, 1);
|
|
}
|
|
else {
|
|
/* set direction to output, was cleared during initialization */
|
|
_update_pin(dev, MCP23X17_REG_IODIR, pin, 0);
|
|
}
|
|
}
|
|
else {
|
|
_update_pin(dev, MCP23X17_REG_GPIO, pin, value);
|
|
}
|
|
|
|
_release(dev);
|
|
}
|
|
|
|
void mcp23x17_gpio_set(mcp23x17_t *dev, gpio_t pin)
|
|
{
|
|
mcp23x17_gpio_write(dev, pin, 1);
|
|
}
|
|
|
|
void mcp23x17_gpio_clear(mcp23x17_t *dev, gpio_t pin)
|
|
{
|
|
mcp23x17_gpio_write(dev, pin, 0);
|
|
}
|
|
|
|
void mcp23x17_gpio_toggle(mcp23x17_t *dev, gpio_t pin)
|
|
{
|
|
DEBUG_DEV("pin %u", dev, pin);
|
|
mcp23x17_gpio_write(dev, pin, mcp23x17_gpio_read(dev, pin) ? 0 : 1);
|
|
}
|
|
|
|
#if IS_USED(MODULE_MCP23X17_IRQ)
|
|
|
|
int mcp23x17_gpio_init_int(mcp23x17_t *dev, gpio_t pin,
|
|
gpio_mode_t mode,
|
|
gpio_flank_t flank,
|
|
gpio_cb_t isr,
|
|
void *arg)
|
|
{
|
|
assert(dev);
|
|
assert(pin < MCP23X17_GPIO_PIN_NUM);
|
|
assert(isr != NULL);
|
|
|
|
DEBUG_DEV("pin %u, mode %d, flank %d, isr %p, arg %p",
|
|
dev, pin, mode, flank, isr, arg);
|
|
|
|
/* initialize the pin */
|
|
int res = mcp23x17_gpio_init(dev, pin, mode);
|
|
if (res != MCP23X17_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;
|
|
break;
|
|
default: DEBUG_DEV("invalid flank %d for pin %d", dev, flank, pin);
|
|
return -MCP23X17_ERROR_INV_FLANK;
|
|
}
|
|
|
|
/* enable the interrupt */
|
|
mcp23x17_gpio_irq_enable(dev, pin);
|
|
|
|
return MCP23X17_OK;
|
|
}
|
|
|
|
void mcp23x17_gpio_irq_enable(mcp23x17_t *dev, gpio_t pin)
|
|
{
|
|
/* some parameter sanity checks */
|
|
assert(dev != NULL);
|
|
assert(pin < MCP23X17_GPIO_PIN_NUM);
|
|
|
|
DEBUG_DEV("pin %u", dev, pin);
|
|
|
|
_acquire(dev);
|
|
|
|
/* delete pending interrupts */
|
|
uint8_t regs[4];
|
|
/* read the GPIO port register */
|
|
if (_read(dev, MCP23X17_REG_INTF, regs, ARRAY_SIZE(regs))) {
|
|
DEBUG_DEV("error reading INTF and INTCAP registers", dev);
|
|
_release(dev);
|
|
return;
|
|
}
|
|
|
|
_update_pin(dev, MCP23X17_REG_GPINTEN, pin, 1);
|
|
_release(dev);
|
|
|
|
}
|
|
|
|
void mcp23x17_gpio_irq_disable(mcp23x17_t *dev, gpio_t pin)
|
|
{
|
|
/* some parameter sanity checks */
|
|
assert(dev != NULL);
|
|
assert(pin < MCP23X17_GPIO_PIN_NUM);
|
|
|
|
DEBUG_DEV("pin %u", dev, pin);
|
|
|
|
_acquire(dev);
|
|
_update_pin(dev, MCP23X17_REG_GPINTEN, pin, 0);
|
|
_release(dev);
|
|
}
|
|
|
|
/* interrupt service routine for IRQs */
|
|
static void _irq_isr(void *arg)
|
|
{
|
|
/* some parameter sanity checks */
|
|
assert(arg != NULL);
|
|
|
|
/* just indicate that an interrupt occurred and return */
|
|
event_post(MCP23X17_EVENT_PRIO, (event_t*)&((mcp23x17_t*)arg)->irq_event);
|
|
}
|
|
|
|
/* handle one IRQ event of device referenced by the event */
|
|
static void _irq_handler(event_t* event)
|
|
{
|
|
mcp23x17_irq_event_t* irq_event = (mcp23x17_irq_event_t*)event;
|
|
|
|
assert(irq_event != NULL);
|
|
assert(irq_event->dev);
|
|
|
|
mcp23x17_t *dev = irq_event->dev;
|
|
|
|
DEBUG_DEV("event %p", dev, event);
|
|
|
|
uint8_t regs[4];
|
|
|
|
/* read the GPIO port register */
|
|
_acquire(dev);
|
|
if (_read(dev, MCP23X17_REG_INTF, regs, ARRAY_SIZE(regs))) {
|
|
DEBUG_DEV("error reading INTF and INTCAP registers", dev);
|
|
_release(dev);
|
|
return;
|
|
}
|
|
_release(dev);
|
|
|
|
/* iterate over all pins to check whether ISR has to be called */
|
|
for (unsigned i = 0; i < MCP23X17_GPIO_PIN_NUM; i++) {
|
|
uint8_t port = i >> 3;
|
|
uint8_t pin_bit = 1 << (i & 0x07);
|
|
|
|
/* test whether interrupt flag is set and cb is defined for the pin */
|
|
if ((regs[port] & pin_bit) && dev->isr[i].cb != NULL) {
|
|
|
|
/* check the flank and the activated flank mode */
|
|
if (dev->flank[i] == GPIO_BOTH || /* no matter what flank */
|
|
((regs[2 + port] & pin_bit) == 0 && /* new value is 0 -> falling flank */
|
|
(dev->flank[i] == GPIO_FALLING)) ||
|
|
((regs[2 + port] & pin_bit) && /* new value is 1 -> rising flank */
|
|
(dev->flank[i] == GPIO_RISING))) {
|
|
/* call the ISR */
|
|
dev->isr[i].cb(dev->isr[i].arg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif /* MODULE_MCP23X17_IRQ */
|
|
|
|
/* internal functions */
|
|
|
|
static void _acquire(const mcp23x17_t *dev)
|
|
{
|
|
#if IS_USED(MODULE_MCP23X17_SPI)
|
|
if (dev->params.if_params.type == MCP23X17_SPI) {
|
|
spi_acquire(_SPI_DEV, _SPI_CS, SPI_MODE_0, _SPI_CLK);
|
|
}
|
|
#endif
|
|
#if IS_USED(MODULE_MCP23X17_I2C)
|
|
if (dev->params.if_params.type == MCP23X17_I2C) {
|
|
i2c_acquire(_I2C_DEV);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void _release(const mcp23x17_t *dev)
|
|
{
|
|
#if IS_USED(MODULE_MCP23X17_SPI)
|
|
if (dev->params.if_params.type == MCP23X17_SPI) {
|
|
spi_release(_SPI_DEV);
|
|
}
|
|
#endif
|
|
#if IS_USED(MODULE_MCP23X17_I2C)
|
|
if (dev->params.if_params.type == MCP23X17_I2C) {
|
|
i2c_release(_I2C_DEV);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int _read(const mcp23x17_t *dev, uint8_t reg, uint8_t *data, size_t len)
|
|
{
|
|
DEBUG_DEV("reg=%02x data=%p len=%d", dev, reg, data, len);
|
|
|
|
int res = MCP23X17_OK;
|
|
|
|
#if IS_USED(MODULE_MCP23X17_SPI)
|
|
if (dev->params.if_params.type == MCP23X17_SPI) {
|
|
spi_transfer_byte(_SPI_DEV, _SPI_CS, true, (_ADDR << 1) | 1);
|
|
spi_transfer_byte(_SPI_DEV, _SPI_CS, true, reg);
|
|
spi_transfer_bytes(_SPI_DEV, _SPI_CS, false, NULL, data, len);
|
|
}
|
|
#endif
|
|
|
|
#if IS_USED(MODULE_MCP23X17_I2C)
|
|
if (dev->params.if_params.type == MCP23X17_I2C) {
|
|
int res = i2c_read_regs(_I2C_DEV, _ADDR, reg, data, len, 0);
|
|
if (res) {
|
|
DEBUG_DEV("I2C read error: %d (%s)", dev, res, strerror(res * -1));
|
|
return -MCP23X17_ERROR_I2C;
|
|
}
|
|
else {
|
|
return MCP23X17_OK;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (ENABLE_DEBUG) {
|
|
printf("[mcp23x17] %s dev=%" PRIxPTR " addr=%02x reg=%02x read: ",
|
|
__func__, (uintptr_t)dev, dev->params.addr, reg);
|
|
for (uint8_t i = 1; i < len + 1; i++) {
|
|
printf("%02x ", data[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static int _write(const mcp23x17_t *dev, uint8_t reg,
|
|
const uint8_t *data, size_t len)
|
|
{
|
|
DEBUG_DEV("reg=%02x data=%p len=%d", dev, reg, data, len);
|
|
|
|
if (ENABLE_DEBUG) {
|
|
printf("[mcp23x17] %s dev=%" PRIxPTR " addr=%02x reg=%02x write: ",
|
|
__func__, (uintptr_t)dev, dev->params.addr, reg);
|
|
for (uint8_t i = 1; i < len + 1; i++) {
|
|
printf("%02x ", data[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
#if IS_USED(MODULE_MCP23X17_SPI)
|
|
if (dev->params.if_params.type == MCP23X17_SPI) {
|
|
spi_transfer_byte(_SPI_DEV, _SPI_CS, true, (_ADDR << 1));
|
|
spi_transfer_byte(_SPI_DEV, _SPI_CS, true, reg);
|
|
spi_transfer_bytes(_SPI_DEV, _SPI_CS, false, data, NULL, len);
|
|
return MCP23X17_OK;
|
|
}
|
|
#endif
|
|
|
|
#if IS_USED(MODULE_MCP23X17_I2C)
|
|
if (dev->params.if_params.type == MCP23X17_I2C) {
|
|
int res = i2c_write_regs(_I2C_DEV, _ADDR, reg, data, len, 0);
|
|
if (res) {
|
|
DEBUG_DEV("I2C write error: %d (%s)", dev, res, strerror(res * -1));
|
|
return -MCP23X17_ERROR_I2C;
|
|
}
|
|
else {
|
|
return MCP23X17_OK;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return -MCP23X17_ERROR_NO_DEV;
|
|
}
|
|
|
|
static int _update_pin(const mcp23x17_t *dev, uint8_t reg, gpio_t pin, int value)
|
|
{
|
|
/* some parameter sanity checks */
|
|
assert(dev != NULL);
|
|
assert(pin < MCP23X17_GPIO_PIN_NUM);
|
|
|
|
DEBUG_DEV("pin %d, value %d, reg %02x", dev, pin, value, reg);
|
|
|
|
uint8_t data;
|
|
uint8_t port = pin >> 3;
|
|
pin -= (port << 3);
|
|
|
|
/* read the register */
|
|
int res = _read(dev, reg + port, &data, 1);
|
|
|
|
if (value) {
|
|
data |= (1 << pin);
|
|
}
|
|
else {
|
|
data &= ~(1 << pin);
|
|
}
|
|
|
|
/* write back the register */
|
|
res |= _write(dev, reg + port, &data, 1);
|
|
|
|
return res;
|
|
}
|