From 21958ff531e2619c3c3f037bb48f317bcce7c93b Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Thu, 20 Feb 2020 05:05:22 +0100 Subject: [PATCH 1/3] drivers/periph/i2c: add periph_i2c_reconfigure feature It is often desireable to re-configure the pins of a bus back to GPIO mode, either to save power when an external peripheral is turned off and current would leak, or because a device may need a non-standard in-band signal to be generated on the bus lines. To serve those use cases, this patch introduces four new functions to the I2C API: - `i2c_init_pins()` equivalent to spi_init_pins(), restores I2C pin configuration - `i2c_deinit_pins()` to switch the configuration of the I2C pins back to GPIO mode and block access to the bus. - `i2c_pin_sda()` to get the data pin for a given bus - `i2c_pin_scl()` to get the clock pin for a given bus Since it's unreasonable to expect having implementations for all platforms already, those functions are only availiable when the periph_i2c_reconfigure feature is availiable. Applications should use FEATURES_REQUIRED += periph_i2c_reconfigure or FEATURES_OPTIONAL if they want to make use of those functions. --- drivers/include/periph/i2c.h | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/drivers/include/periph/i2c.h b/drivers/include/periph/i2c.h index 69824f8905..b8ee3bf9f0 100644 --- a/drivers/include/periph/i2c.h +++ b/drivers/include/periph/i2c.h @@ -215,6 +215,67 @@ typedef enum { */ void i2c_init(i2c_t dev); +/** + * @brief Initialize the used I2C bus pins + * + * After calling i2c_init, the pins must be initialized (i.e. spi_init is + * calling this function internally). In normal cases, this function will not be + * used. But there are some devices (e.g. ATECC608A), that use I2C bus lines also + * for other purposes and need the option to dynamically re-configure one or + * more of the used pins. + * + * @param[in] dev I2C device the pins are configure for + */ +void i2c_init_pins(i2c_t dev); + +/** + * @brief Change the pins of the given I2C bus back to plain GPIO functionality + * + * The pin mux of the SDA and SCL pins of the bus will be changed back to + * default (GPIO) mode and the I2C bus is powered off. + * This allows to use the I2C pins for another function and return to I2C + * functionality again by calling i2c_init_pins() + * + * If you want the pin to be in a defined state, call gpio_init() on it. + * + * The bus MUST not be acquired before initializing it, as this is handled + * internally by the i2c_deinit function! + * + * Calls to i2c_acquire() will block until i2c_init_pins() is called. + * + * @note Until this is implemented on all platforms, this requires the + * periph_i2c_reconfigure feature to be used. + * + * @param[in] dev the device to de-initialize + */ +void i2c_deinit_pins(i2c_t dev); + +#if DOXYGEN /* functions to be implemented as `#define` in `periph_cpu.h` */ +/** + * @brief Get the SDA pin of the given I2C bus. + * + * @param[in] dev The device to query + * + * @note Until this is implemented on all platforms, this requires the + * periph_i2c_reconfigure feature to be used. + * + * @return The GPIO used for the I2C data line. + */ +gpio_t i2c_pin_sda(i2c_t dev); + +/** + * @brief Get the SCL pin of the given I2C bus. + * + * @param[in] dev The device to query + * + * @note Until this is implemented on all platforms, this requires the + * periph_i2c_reconfigure feature to be used. + * + * @return The GPIO used for the I2C clock line. + */ +gpio_t i2c_pin_scl(i2c_t dev); +#endif /* DOXYGEN */ + /** * @brief Get mutually exclusive access to the given I2C bus * From 8c502322f43fe866f04ab6665164c6ed8461aa06 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 19 Feb 2020 19:42:22 +0100 Subject: [PATCH 2/3] cpu/sam0_common: i2c: implement the periph_i2c_reconfigure feature This adds sam0 implementations for - i2c_init_pins() - i2c_deinit_pins() - i2c_pin_sda() - i2c_pin_scl() --- cpu/sam0_common/Makefile.features | 1 + cpu/sam0_common/include/periph_cpu_common.h | 9 +++++++ cpu/sam0_common/periph/i2c.c | 26 +++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/cpu/sam0_common/Makefile.features b/cpu/sam0_common/Makefile.features index 38f83ce8b7..78e90ed0a0 100644 --- a/cpu/sam0_common/Makefile.features +++ b/cpu/sam0_common/Makefile.features @@ -3,6 +3,7 @@ FEATURES_PROVIDED += periph_flashpage FEATURES_PROVIDED += periph_flashpage_raw FEATURES_PROVIDED += periph_flashpage_rwee FEATURES_PROVIDED += periph_gpio periph_gpio_irq +FEATURES_PROVIDED += periph_i2c_reconfigure FEATURES_PROVIDED += periph_uart_modecfg FEATURES_PROVIDED += periph_uart_nonblocking FEATURES_PROVIDED += periph_wdt periph_wdt_cb diff --git a/cpu/sam0_common/include/periph_cpu_common.h b/cpu/sam0_common/include/periph_cpu_common.h index 9efb0e8aea..28c994844a 100644 --- a/cpu/sam0_common/include/periph_cpu_common.h +++ b/cpu/sam0_common/include/periph_cpu_common.h @@ -311,6 +311,15 @@ typedef enum { I2C_SPEED_HIGH = 3400000U, /**< high speed mode: ~3.4Mbit/s */ } i2c_speed_t; /** @} */ + +/** + * @name I2C pin getter functions + * @{ + */ +#define i2c_pin_sda(dev) i2c_config[dev].sda_pin +#define i2c_pin_scl(dev) i2c_config[dev].scl_pin +/** @} */ + #endif /* ndef DOXYGEN */ /** diff --git a/cpu/sam0_common/periph/i2c.c b/cpu/sam0_common/periph/i2c.c index 10ac2baa63..c9cc275469 100644 --- a/cpu/sam0_common/periph/i2c.c +++ b/cpu/sam0_common/periph/i2c.c @@ -79,6 +79,7 @@ void i2c_init(i2c_t dev) int32_t tmp_baud; assert(dev < I2C_NUMOF); + /* Initialize mutex */ mutex_init(&locks[dev]); /* DISABLE I2C MASTER */ @@ -171,6 +172,31 @@ void i2c_release(i2c_t dev) mutex_unlock(&locks[dev]); } +#ifdef MODULE_PERIPH_I2C_RECONFIGURE +void i2c_init_pins(i2c_t dev) +{ + assert(dev < I2C_NUMOF); + + _i2c_poweron(dev); + + gpio_init_mux(i2c_config[dev].scl_pin, i2c_config[dev].mux); + gpio_init_mux(i2c_config[dev].sda_pin, i2c_config[dev].mux); + + mutex_unlock(&locks[dev]); +} + +void i2c_deinit_pins(i2c_t dev) +{ + assert(dev < I2C_NUMOF); + + mutex_lock(&locks[dev]); + _i2c_poweroff(dev); + + gpio_disable_mux(i2c_config[dev].sda_pin); + gpio_disable_mux(i2c_config[dev].scl_pin); +} +#endif + int i2c_read_bytes(i2c_t dev, uint16_t addr, void *data, size_t len, uint8_t flags) { From 896f9db71fcd4283c984982cfa6d0730e35a8b12 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Thu, 20 Feb 2020 13:34:37 +0100 Subject: [PATCH 3/3] tests/periph_i2c: add tests for periph_i2c_reconfigure features Add a test to re-configure the I2C pins to GPIO functionality and use them as output. A small delay is added to allow for observing the change in power draw. --- tests/periph_i2c/Makefile | 2 ++ tests/periph_i2c/main.c | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/tests/periph_i2c/Makefile b/tests/periph_i2c/Makefile index 1aa079ca54..77e10cbeac 100644 --- a/tests/periph_i2c/Makefile +++ b/tests/periph_i2c/Makefile @@ -2,7 +2,9 @@ BOARD ?= samr21-xpro include ../Makefile.tests_common FEATURES_REQUIRED = periph_i2c +FEATURES_OPTIONAL = periph_i2c_reconfigure USEMODULE += shell +USEMODULE += xtimer include $(RIOTBASE)/Makefile.include diff --git a/tests/periph_i2c/main.c b/tests/periph_i2c/main.c index e6ab7e6110..0b5f4f87a5 100644 --- a/tests/periph_i2c/main.c +++ b/tests/periph_i2c/main.c @@ -24,9 +24,12 @@ #include #include "periph_conf.h" +#include "periph/gpio.h" #include "periph/i2c.h" #include "shell.h" +#include + #ifndef I2C_ACK #define I2C_ACK (0) #endif @@ -160,6 +163,47 @@ int cmd_i2c_release(int argc, char **argv) return 0; } +#ifdef MODULE_PERIPH_I2C_RECONFIGURE +int cmd_i2c_gpio(int argc, char **argv) +{ + int dev; + + dev = _check_param(argc, argv, 1, 1, "DEV"); + if (dev == ARG_ERROR) { + return 1; + } + + gpio_t sda_pin = i2c_pin_sda(dev); + gpio_t scl_pin = i2c_pin_scl(dev); + + printf("Command: i2c_deinit_pins(%i)\n", dev); + i2c_deinit_pins(dev); + + gpio_init(sda_pin, GPIO_OUT); + gpio_init(scl_pin, GPIO_OUT); + + xtimer_sleep(1); + + printf("Command: gpio_set()\n"); + gpio_set(sda_pin); + gpio_set(scl_pin); + + xtimer_sleep(1); + + printf("Command: gpio_clear()\n"); + gpio_clear(sda_pin); + gpio_clear(scl_pin); + + xtimer_sleep(1); + + printf("Command: i2c_init_pins(%i)\n", dev); + i2c_init_pins(dev); + + printf("Success: i2c_%i re-init\n", dev); + return 0; +} +#endif + int cmd_i2c_read_reg(int argc, char **argv) { int res; @@ -444,6 +488,9 @@ int cmd_i2c_get_id(int argc, char **argv) static const shell_command_t shell_commands[] = { { "i2c_acquire", "Get access to the I2C bus", cmd_i2c_acquire }, { "i2c_release", "Release to the I2C bus", cmd_i2c_release }, +#ifdef MODULE_PERIPH_I2C_RECONFIGURE + { "i2c_gpio", "Re-configures I2C pins to GPIO mode and back.", cmd_i2c_gpio }, +#endif { "i2c_read_reg", "Read byte from register", cmd_i2c_read_reg }, { "i2c_read_regs", "Read bytes from registers", cmd_i2c_read_regs }, { "i2c_read_byte", "Read byte from the I2C device", cmd_i2c_read_byte },