From bff6abf5be9b740a2b103ba7442200edf349a282 Mon Sep 17 00:00:00 2001 From: Sarah Renkhoff Date: Thu, 13 Jun 2024 13:02:02 +0200 Subject: [PATCH 1/7] drivers: add SC18IS604 MFD core driver This driver manages the MFD pseudo-bus on which child devices can be placed, and which itself is placed on an SPI bus. Child devices can register their own callbacks for interrupt handling, which are fired directly from the ISR context. Asynchronous transfer functions are necessary for correct interrupt handling of child devices in cases where that handling itself required bus communication. These functions are realized through a separate work queue per driver instance, since the particular communication needs of the SC18IS604 (very large, but exact time frame delays during SPI transfers) make it impossible to use asynchronous SPI transfers. Signed-off-by: Sarah Renkhoff Signed-off-by: Stephan Linz --- drivers/mfd/CMakeLists.txt | 4 + drivers/mfd/Kconfig | 1 + drivers/mfd/Kconfig.sc18is604 | 36 ++ drivers/mfd/mfd_sc18is604.c | 388 +++++++++++++++++++++ drivers/mfd/mfd_sc18is604.h | 90 +++++ drivers/mfd/mfd_sc18is604_transfer.c | 115 ++++++ drivers/mfd/mfd_sc18is604_transfer_async.c | 135 +++++++ dts/bindings/mfd/nxp,sc18is604.yaml | 70 ++++ include/zephyr/drivers/mfd/sc18is604.h | 244 +++++++++++++ 9 files changed, 1083 insertions(+) create mode 100644 drivers/mfd/Kconfig.sc18is604 create mode 100644 drivers/mfd/mfd_sc18is604.c create mode 100644 drivers/mfd/mfd_sc18is604.h create mode 100644 drivers/mfd/mfd_sc18is604_transfer.c create mode 100644 drivers/mfd/mfd_sc18is604_transfer_async.c create mode 100644 dts/bindings/mfd/nxp,sc18is604.yaml create mode 100644 include/zephyr/drivers/mfd/sc18is604.h diff --git a/drivers/mfd/CMakeLists.txt b/drivers/mfd/CMakeLists.txt index 9916467089..f1830874f3 100644 --- a/drivers/mfd/CMakeLists.txt +++ b/drivers/mfd/CMakeLists.txt @@ -10,3 +10,7 @@ zephyr_library_sources_ifdef(CONFIG_MFD_DS3231 mfd_ds3231.c) zephyr_library_sources_ifdef(CONFIG_MFD_SIPOMUXGP mfd_sipomuxgp.c mfd_sipomuxgp_workq.c) zephyr_library_sources_ifdef(CONFIG_MFD_SIPOMUXGP_SPI mfd_sipomuxgp_spi.c) + +zephyr_library_sources_ifdef(CONFIG_MFD_SC18IS604 mfd_sc18is604.c) +zephyr_library_sources_ifdef(CONFIG_MFD_SC18IS604 mfd_sc18is604_transfer.c) +zephyr_library_sources_ifdef(CONFIG_MFD_SC18IS604_ASYNC mfd_sc18is604_transfer_async.c) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index ba219baf68..c844a35394 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -4,6 +4,7 @@ if MFD rsource "Kconfig.ds3231" +rsource "Kconfig.sc18is604" rsource "Kconfig.sipomuxgp" endif # MFD diff --git a/drivers/mfd/Kconfig.sc18is604 b/drivers/mfd/Kconfig.sc18is604 new file mode 100644 index 0000000000..da9e8c4680 --- /dev/null +++ b/drivers/mfd/Kconfig.sc18is604 @@ -0,0 +1,36 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +menuconfig MFD_SC18IS604 + bool "SC18IM604 SPI to I2C & GPIO bridge" + default y + depends on DT_HAS_NXP_SC18IS604_ENABLED + depends on SPI + help + Enable driver for SC18IM604 bridge. + +config MFD_SC18IS604_INIT_PRIORITY + int "Init priority" + default 55 if I2C_SC18IS604 + default MFD_INIT_PRIORITY + depends on MFD_SC18IS604 + help + Device driver initialization priority. + +config MFD_SC18IS604_ASYNC + bool "Enable asynchronous transfer functions over SC18IS604 bus" + default n + depends on POLL + depends on DYNAMIC_THREAD + +if MFD_SC18IS604_ASYNC + + config MFD_SC18IS604_WORKQUEUE_STACK_SIZE + int + default 512 + + config HEAP_MEM_POOL_ADD_SIZE_MFD_SC18IS604 + int + default 256 + +endif # MFD_SC18IS604_ASYNC diff --git a/drivers/mfd/mfd_sc18is604.c b/drivers/mfd/mfd_sc18is604.c new file mode 100644 index 0000000000..53b4b8f082 --- /dev/null +++ b/drivers/mfd/mfd_sc18is604.c @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_sc18is604 + +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "mfd_sc18is604.h" + +#include +LOG_MODULE_REGISTER(mfd_sc18is604, CONFIG_MFD_LOG_LEVEL); + +#if defined(CONFIG_MFD_SC18IS604_ASYNC) \ + && (!CONFIG_DYNAMIC_THREAD_POOL_SIZE && !defined(CONFIG_DYNAMIC_THREAD_ALLOC)) +#error "SC18IS604 MFD driver requires either CONFIG_DYNAMIC_THREAD_POOL_SIZE>0" \ + "or CONFIG_DYNAMIC_THREAD_ALLOC" +#endif + +int mfd_sc18is604_add_callback(const struct device *dev, + struct gpio_callback *callback) +{ + mfd_sc18is604_data_t * const data = dev->data; + + return gpio_manage_callback(&data->child_callbacks, callback, true); +} + +int mfd_sc18is604_remove_callback(const struct device *dev, + struct gpio_callback *callback) +{ + mfd_sc18is604_data_t * const data = dev->data; + + return gpio_manage_callback(&data->child_callbacks, callback, false); +} + +int mfd_sc18is604_claim(const struct device *dev, k_timeout_t timeout) +{ + mfd_sc18is604_data_t * const data = dev->data; + + return k_sem_take(&data->lock, timeout); +} + +void mfd_sc18is604_release(const struct device *dev) +{ + mfd_sc18is604_data_t * const data = dev->data; + + k_sem_give(&data->lock); +} + +/** + * @brief Request device version string. The string will be placed in the + * internal buffer. The device interrupt is set once the string is ready + * to be read from the buffer. + * + * @param dev An SC18IS604 MFD device. + * + * @return A value from mfd_sc18is604_transfer(). + */ +static int mfd_sc18is604_request_version_string(const struct device *dev) +{ + uint8_t cmd[] = {SC18IS604_CMD_VERSION_STRING}; + + return mfd_sc18is604_transfer(dev, cmd, ARRAY_SIZE(cmd), + NULL, 0, NULL, 0); +} + +/** + * @brief Set up GPIO pin. + * + * @param dev An SC18IS604 MFD device. + * @param gpio The GPIO specification from devicetree. + * @param flags Additional GPIO flags. + * + * @retval 0 On success. + * @return Negative error code on failure. + */ +static int mfd_sc18is604_configure_gpio_pin(const struct device *dev, + const struct gpio_dt_spec *gpio, + const gpio_flags_t flags) +{ + int ret = 0; + + if (gpio->port != NULL) { + LOG_DBG("%s: configure GPIO port %s, pin %d", + dev->name, gpio->port->name, gpio->pin); + + if (!gpio_is_ready_dt(gpio)) { + LOG_ERR("%s: GPIO port %s not ready", + dev->name, gpio->port->name); + return -ENODEV; + } + + ret = gpio_pin_configure_dt(gpio, flags); + if (ret != 0) { + LOG_ERR("%s: couldn't configure GPIO pin %d", + dev->name, gpio->pin); + return ret; + } + } else { + return -EINVAL; + } + + return 0; +} + +/** + * @brief Reset device. + * + * @param dev An SC18IS604 MFD device. + * + * @retval 0 On success. + * @return Negative error code on failure. + */ +static int mfd_sc18is604_chip_reset(const struct device *dev) +{ + const mfd_sc18is604_config_t * const config = dev->config; + int ret = 0; + + LOG_DBG("%s: resetting device", dev->name); + if (config->reset.port != NULL) { + ret = gpio_pin_set_dt(&config->reset, 1); + if (ret != 0) + goto end; + + /* Should suffice to pull for reset acceptance time. */ + k_busy_wait(MFD_SC18IS604_RESET_SETUP_USEC); + + ret = gpio_pin_set_dt(&config->reset, 0); + if (ret != 0) + goto end; + + /* + * Device needs some time before becoming + * responsive again after reset. + */ + k_busy_wait(MFD_SC18IS604_RESET_TIME_USEC); + } + +end: + return ret; +} + +static int mfd_sc18is604_clear_interrupt_source(const struct device *dev) +{ + uint8_t buf = 0; + /* Reset interrupt by reading from I2CStat register. */ + int ret = READ_SC18IS604_REG(dev, I2C_STATUS, &buf); + + return ret; +} + +static void mfd_sc18is604_interrupt_callback(const struct device *dev, + struct gpio_callback *cb, + gpio_port_pins_t pins) +{ + mfd_sc18is604_data_t * const data = CONTAINER_OF(cb, + mfd_sc18is604_data_t, + interrupt_callback); + + /* Raise internal signal */ + k_sem_give(&data->interrupt_signal); + + /* Fire registered callbacks */ + gpio_fire_callbacks(&data->child_callbacks, dev, pins); +} + +static int mfd_sc18is604_check_chipid(const struct device *dev, + k_timeout_t timeout) +{ + mfd_sc18is604_data_t * const data = dev->data; + int ret = 0; + + /* Lock device */ + ret = mfd_sc18is604_claim(dev, timeout); + if (ret != 0) { + return -ETIMEDOUT; + } + + /* Prepare signal for interrupt signaling */ + k_sem_reset(&data->interrupt_signal); + + /* Send command to place ID string in device buffer */ + mfd_sc18is604_request_version_string(dev); + + /* Await interrupt signaling completion */ + ret = k_sem_take(&data->interrupt_signal, timeout); + if (ret != 0) { + ret = -ETIMEDOUT; + goto release_and_return; + } + + /* Clear hardware interrupt signal */ + mfd_sc18is604_clear_interrupt_source(dev); + + /* Read generated string from device buffer */ + char id[16] = {0}; + + ret = mfd_sc18is604_read_buffer(dev, (uint8_t *) id, 16); + if (ret != 0) { + ret = -EIO; + goto release_and_return; + } + + /* Compare with expected value */ + if (strncmp(MFD_SC18IS604_VERSION_CHIP, id, + strlen(MFD_SC18IS604_VERSION_CHIP)) != 0) { + LOG_ERR("%s: chip mismatch: expected %s, got %s", + dev->name, MFD_SC18IS604_VERSION_CHIP, id); + ret = -ENODEV; + goto release_and_return; + } + +release_and_return: + mfd_sc18is604_release(dev); + return ret; +} + +/** + * @brief Set up interrupt handling. + * + * @param dev An SC18IS604 MFD device. + * @param gpio The GPIO specification from devicetree. + * @param flags Additional GPIO flags. + * @retval 0 On success. + * @return Negative error code on failure. + */ +static int mfd_sc18is604_bind_interrupt(const struct device *dev, + const struct gpio_dt_spec *gpio, + const gpio_flags_t flags) +{ + mfd_sc18is604_data_t * const data = dev->data; + int ret = 0; + + if (gpio->port == NULL) { + return -EINVAL; + } + + LOG_DBG("%s: bind GPIO port %s, pin %d to interrupt handling", + dev->name, gpio->port->name, gpio->pin); + + ret = gpio_pin_interrupt_configure_dt(gpio, flags); + if (ret != 0) { + LOG_ERR("%s: couldn't enable interrupt on GPIO pin %d", + dev->name, gpio->pin); + return ret; + } + + gpio_init_callback(&data->interrupt_callback, + mfd_sc18is604_interrupt_callback, + BIT(gpio->pin)); + + ret = gpio_add_callback_dt(gpio, &data->interrupt_callback); + if (ret != 0) { + LOG_ERR("%s: couldn't register callback on GPIO port %s", + dev->name, gpio->port->name); + return ret; + } + + return 0; +} + +static int mfd_sc18is604_init(const struct device *dev) +{ + const mfd_sc18is604_config_t * const config = dev->config; + mfd_sc18is604_data_t * const data = dev->data; + int ret = 0; + + /* Check parent bus readiness for bridge. */ + if (!spi_is_ready_dt(&config->spi)) { + LOG_ERR("%s: SPI device %s not ready", + dev->name, config->spi.bus->name); + return -ENODEV; + } + + /* Set driver instance back reference */ + data->dev = dev; + + /* Initialize bus lock (initially open) */ + k_sem_init(&data->lock, 1, 1); + + /* Initialize interrupt signal (initially open) */ + k_sem_init(&data->interrupt_signal, 0, 1); + +#ifdef CONFIG_MFD_SC18IS604_ASYNC + /* Initialize private work queue. */ + size_t work_queue_stack_size = CONFIG_MFD_SC18IS604_WORKQUEUE_STACK_SIZE; + + k_work_queue_init(&data->work_queue); + data->work_queue_stack = k_thread_stack_alloc(work_queue_stack_size, 0); + k_work_queue_start(&data->work_queue, data->work_queue_stack, + work_queue_stack_size, K_HIGHEST_THREAD_PRIO, NULL); +#endif /* CONFIG_MFD_SC18IS604_ASYNC */ + + /* Configure interrupt input pin. */ + ret = mfd_sc18is604_configure_gpio_pin(dev, &config->interrupt, + GPIO_INPUT); + if (ret != 0) { + LOG_ERR("%s: interrupt GPIO port not given", dev->name); + return -EINVAL; + } + + /* Set up interrupt handling */ + ret = mfd_sc18is604_bind_interrupt(dev, &config->interrupt, + GPIO_INT_EDGE_TO_ACTIVE); + if (ret != 0) { + LOG_ERR("%s: interrupt callback binding failed", dev->name); + goto end; + } + + /* Configure reset output pin. */ + ret = mfd_sc18is604_configure_gpio_pin(dev, &config->reset, + GPIO_OUTPUT_INACTIVE); + if (ret != 0) { + LOG_WRN("%s: reset GPIO port not given (work w/o chip reset)", + dev->name); + } + + /* Reset device. */ + ret = mfd_sc18is604_chip_reset(dev); + if (ret != 0) { + LOG_ERR("%s: couldn't reset device", dev->name); + goto end; + } + + /* Check Chip ID */ + ret = mfd_sc18is604_check_chipid(dev, K_SECONDS(5)); + if (ret != 0) { + return ret; + } + +end: + return ret; +} + +#ifdef CONFIG_PM_DEVICE +static int mfd_sc18is604_pm_device_pm_action(const struct device *dev, + enum pm_device_action action) +{ + ARG_UNUSED(dev); + ARG_UNUSED(action); + + return 0; +} +#endif + +#define MFD_SC18IS604_DEFINE(inst) \ + \ + static const mfd_sc18is604_config_t mfd_sc18is604_config_##inst = \ + { \ + .spi = SPI_DT_SPEC_INST_GET(inst, SPI_OP_MODE_MASTER | \ + SPI_TRANSFER_MSB | \ + SPI_HOLD_ON_CS | \ + SPI_LOCK_ON | \ + SPI_MODE_CPOL | \ + SPI_MODE_CPHA | \ + SPI_WORD_SET(8), 0), \ + .interrupt = GPIO_DT_SPEC_INST_GET(inst, interrupt_gpios), \ + .reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, { 0 }), \ + }; \ + BUILD_ASSERT(DT_INST_PROP(inst, spi_max_frequency) \ + >= MFD_SC18IS604_SPI_HZ_MIN, \ + "SPI bus clock too low"); \ + BUILD_ASSERT(DT_INST_PROP(inst, spi_max_frequency) \ + <= MFD_SC18IS604_SPI_HZ_MAX, \ + "SPI bus clock too high"); \ + \ + static mfd_sc18is604_data_t mfd_sc18is604_data_##inst = { 0 }; \ + \ + PM_DEVICE_DT_INST_DEFINE(inst, mfd_sc18is604_pm_device_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(inst, mfd_sc18is604_init, \ + PM_DEVICE_DT_INST_GET(inst), \ + &mfd_sc18is604_data_##inst, \ + &mfd_sc18is604_config_##inst, POST_KERNEL, \ + CONFIG_MFD_SC18IS604_INIT_PRIORITY, NULL); + +DT_INST_FOREACH_STATUS_OKAY(MFD_SC18IS604_DEFINE); diff --git a/drivers/mfd/mfd_sc18is604.h b/drivers/mfd/mfd_sc18is604.h new file mode 100644 index 0000000000..1434b106bf --- /dev/null +++ b/drivers/mfd/mfd_sc18is604.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_MFD_SC18IS604_H_ +#define ZEPHYR_DRIVERS_MFD_SC18IS604_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include + +/* + * Values in Hz, intentionally to be comparable with the spi-max-frequency + * property from DT bindings in spi-device.yaml. + */ +#define MFD_SC18IS604_SPI_HZ_MIN 100000 +#define MFD_SC18IS604_SPI_HZ_MAX 1200000 + +#define MFD_SC18IS604_SPI_DELAY_USEC 8 /* between two SPI bytes */ +#define MFD_SC18IS604_RESET_SETUP_USEC 1 /* >= 125ns acceptance time */ +#define MFD_SC18IS604_RESET_TIME_USEC 500 + +/* timeout for about two times a maximum command (three byte) at 100kHz */ +#define MFD_SC18IS604_CMD_MAX_TOUT_USEC 200 + +/* expected chip in version string */ +#define MFD_SC18IS604_VERSION_CHIP "SC18IS604" +BUILD_ASSERT( + ARRAY_SIZE(MFD_SC18IS604_VERSION_CHIP) <= SC18IS604_VERSION_STRING_SIZE, + "Chip name too long" +); + +/** + * @brief SC18IM604 MFD configuration data + * + * @a interrupt must be set to a valid input pin with interrupt capability. + * @a reset must be set to a valid output pin. + * + * This structure contains all of the state for a given SC18IM604 MFD + * controller as well as the binding to related SPI device. + */ +typedef struct mfd_sc18is604_config { + /** Specs of the underlying SPI bus. */ + const struct spi_dt_spec spi; + /** Specs of this device's interrupt pin. */ + const struct gpio_dt_spec interrupt; + /** Specs of this device's reset pin. */ + const struct gpio_dt_spec reset; +} mfd_sc18is604_config_t; + +/** + * @brief SC18IM604 MFD data + * + * This structure contains data structures used by a SC18IM604 MFD. + * + * Interrupt propagation is handled by @a k_sem. Changes to + * @ref mfd_sc18is604_data and @ref mfd_sc18is604_config are + * synchronized using @a k_mutex. + */ +typedef struct mfd_sc18is604_data { + /** Backreference to driver instance. */ + const struct device *dev; + /** Lock for ensuring uninterrupted access to the MFD bus. */ + struct k_sem lock; + /** Interrupt callback of this driver instance. */ + struct gpio_callback interrupt_callback; + /** Semaphore used for MFD-internal signaling of interrupts. */ + struct k_sem interrupt_signal; + /** Interrupt callbacks registered by child devices. */ + sys_slist_t child_callbacks; +#ifdef CONFIG_MFD_SC18IS604_ASYNC + /** Private work queue for offloading blocking work. */ + struct k_work_q work_queue; + /** Stack area used by this driver instance's work queue. */ + k_thread_stack_t *work_queue_stack; +#endif /* CONFIG_MFD_SC18IS604_ASYNC */ +} mfd_sc18is604_data_t; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_MFD_SC18IS604_H_ */ diff --git a/drivers/mfd/mfd_sc18is604_transfer.c b/drivers/mfd/mfd_sc18is604_transfer.c new file mode 100644 index 0000000000..a1147c1793 --- /dev/null +++ b/drivers/mfd/mfd_sc18is604_transfer.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#include "mfd_sc18is604.h" + +int mfd_sc18is604_transfer(const struct device *dev, + uint8_t *cmd, size_t cmd_len, + uint8_t *tx_data, size_t tx_len, + uint8_t *rx_data, size_t rx_len) +{ + const mfd_sc18is604_config_t * const config = dev->config; + int ret = 0; + + /* + * The SC18IS604 requires significant delay between each byte + * sent via SPI. We achieve this by sending bytes in individual + * transactions, but keeping the CS line set, and the device + * locked, until we explicitly release it (see the flags + * SPI_HOLD_ON_CS and SPI_LOCK_ON set in device init). + */ + + struct spi_buf buffer = { + .buf = NULL, + .len = 1, + }; + + const struct spi_buf_set buf_set = { + .buffers = &buffer, + .count = 1 + }; + + /* Write command sequence */ + if (cmd != NULL) { + for (int i = 0; i < cmd_len; i++) { + buffer.buf = &cmd[i]; + + ret = spi_write_dt(&config->spi, &buf_set); + if (ret != 0) { + goto end; + } + + k_usleep(MFD_SC18IS604_SPI_DELAY_USEC); + } + } + + /* Write data */ + if (tx_data != NULL) { + for (int i = 0; i < tx_len; i++) { + buffer.buf = &tx_data[i]; + + ret = spi_write_dt(&config->spi, &buf_set); + if (ret != 0) { + goto end; + } + + k_usleep(MFD_SC18IS604_SPI_DELAY_USEC); + } + } + + /* Read data */ + if (rx_data != NULL) { + for (int i = 0; i < rx_len; i++) { + buffer.buf = &rx_data[i]; + + ret = spi_read_dt(&config->spi, &buf_set); + if (ret != 0) { + goto end; + } + + k_usleep(MFD_SC18IS604_SPI_DELAY_USEC); + } + } + +end: + /* + * Releases the SPI bus itself, which we locked with + * our first spi_write_dt call. + */ + spi_release_dt(&config->spi); + + return ret; +} + +int mfd_sc18is604_read_register(const struct device *dev, + uint8_t reg, uint8_t *val) +{ + uint8_t cmd[] = {SC18IS604_CMD_READ_REGISTER, reg}; + + return mfd_sc18is604_transfer(dev, cmd, ARRAY_SIZE(cmd), + NULL, 0, val, 1); +} + +int mfd_sc18is604_write_register(const struct device *dev, + uint8_t reg, uint8_t val) +{ + uint8_t cmd[] = {SC18IS604_CMD_WRITE_REGISTER, reg}; + + return mfd_sc18is604_transfer(dev, cmd, ARRAY_SIZE(cmd), + &val, 1, NULL, 0); +} + +int mfd_sc18is604_read_buffer(const struct device *dev, + uint8_t *data, size_t len) +{ + uint8_t cmd[] = {SC18IS604_CMD_READ_BUFFER}; + + return mfd_sc18is604_transfer(dev, cmd, ARRAY_SIZE(cmd), + NULL, 0, data, len); +} diff --git a/drivers/mfd/mfd_sc18is604_transfer_async.c b/drivers/mfd/mfd_sc18is604_transfer_async.c new file mode 100644 index 0000000000..6713900d2c --- /dev/null +++ b/drivers/mfd/mfd_sc18is604_transfer_async.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#include "mfd_sc18is604.h" + +/** + * @brief Structure tracking an asynchronous transfer. + */ +struct mfd_sc18is604_transfer_work { + /** The work item performing the transfer. */ + struct k_work work; + /** The device on which the transfer is performed. */ + const struct device *dev; + /** The command sequence to be sent. */ + uint8_t *cmd; + /** Length of the command sequence. */ + size_t cmd_len; + /** Data to be sent to the device. */ + uint8_t *tx_data; + /** Length of tx_data. */ + size_t tx_len; + /** Buffer for receiving data from the device. */ + uint8_t *rx_data; + /** Length of rx_data */ + size_t rx_len; + /** Signal to be raised once the transfer is complete (or has failed). */ + struct k_poll_signal *signal; +}; + +/* + * Work item that performs the actual transfers on the underlying SPI bus. Must + * be submitted to the work queue owned by the driver instance . + */ +static void mfd_sc18is604_transfer_work_fn(struct k_work *work) +{ + /* Access bundled data */ + struct mfd_sc18is604_transfer_work *transfer_work = CONTAINER_OF(work, + struct mfd_sc18is604_transfer_work, work); + const struct device *dev = transfer_work->dev; + int ret = 0; + + /* Call blocking transfer */ + ret = mfd_sc18is604_transfer(dev, transfer_work->cmd, + transfer_work->cmd_len, + transfer_work->tx_data, + transfer_work->tx_len, + transfer_work->rx_data, + transfer_work->rx_len); + + /* Report result through signal */ + k_poll_signal_raise(transfer_work->signal, ret); + + /* Clean up this work item */ + k_free(transfer_work->cmd); + k_free(transfer_work); +} + +int mfd_sc18is604_transfer_signal(const struct device *dev, + uint8_t *cmd, size_t cmd_len, + uint8_t *tx_data, size_t tx_len, + uint8_t *rx_data, size_t rx_len, + struct k_poll_signal *signal) +{ + mfd_sc18is604_data_t * const data = dev->data; + int ret = 0; + + /* Create work item to manage transfers */ + struct mfd_sc18is604_transfer_work *transfer_work = k_malloc( + sizeof(struct mfd_sc18is604_transfer_work)); + if (transfer_work == NULL) { + return -ENOMEM; + } + + *transfer_work = (struct mfd_sc18is604_transfer_work) { + .dev = dev, + .cmd_len = cmd_len, + .tx_data = tx_data, + .tx_len = tx_len, + .rx_data = rx_data, + .rx_len = rx_len, + .signal = signal, + }; + k_work_init(&transfer_work->work, mfd_sc18is604_transfer_work_fn); + + /* + * We can expect the TX and RX buffer to stay live until + * transfer completion, but the command sequence was + * stack-allocated inbetween. To make sure it stays valid, + * we create an allocated copy attached to the transfer work. + */ + + transfer_work->cmd = k_malloc(cmd_len); + if (transfer_work->cmd == NULL) { + k_free(transfer_work); + return -ENOMEM; + } + memcpy(transfer_work->cmd, cmd, cmd_len); + + /* Submit work item to driver owned queue */ + ret = k_work_submit_to_queue(&data->work_queue, &transfer_work->work); + if (ret != 1) { + k_free(transfer_work->cmd); + k_free(transfer_work); + return -EAGAIN; + } + + return 0; +} + +int mfd_sc18is604_read_register_signal(const struct device *dev, + uint8_t reg, uint8_t *val, + struct k_poll_signal *signal) +{ + uint8_t cmd[] = { SC18IS604_CMD_READ_REGISTER, reg }; + + return mfd_sc18is604_transfer_signal(dev, cmd, ARRAY_SIZE(cmd), + NULL, 0, val, 1, signal); +} + +int mfd_sc18is604_read_buffer_signal(const struct device *dev, + uint8_t *data, size_t len, + struct k_poll_signal *signal) +{ + uint8_t cmd[] = { SC18IS604_CMD_READ_BUFFER }; + + return mfd_sc18is604_transfer_signal(dev, cmd, ARRAY_SIZE(cmd), + NULL, 0, data, len, signal); +} diff --git a/dts/bindings/mfd/nxp,sc18is604.yaml b/dts/bindings/mfd/nxp,sc18is604.yaml new file mode 100644 index 0000000000..e560ac599e --- /dev/null +++ b/dts/bindings/mfd/nxp,sc18is604.yaml @@ -0,0 +1,70 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +description: | + NXP SC18IS604 SPI to I2C/GPIO bridge. + + The SC18IS604 acts as both an external I2C and GPIO controller. These + controllers have to be added to the Device Tree as children, while the + device itself has to be a child of an SPI controller. + + An example configuration: + + #include + #include + #include + + &spi0 { + status = "okay"; + pinctrl-0 = <&spi0_default>; + pinctrl-names = "default"; + cs-gpios = <&gpio0 16 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; + + sc18is604: sc18is604 { + compatible = "nxp,sc18is604"; + status = "okay"; + reg = <0>; + spi-max-frequency = ; + reset-gpios = <&gpio0 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; + interrupt-gpios = <&gpio0 15 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; + + i2c_ext: sc18is604-i2c { + compatible = "nxp,sc18is604-i2c"; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + clock-frequency = ; + }; + + gpio_ext: sc18is604-gpio { + compatible = "nxp,sc18is604-gpio"; + status = "okay"; + gpio-controller; + #gpio-cells = <2>; + ngpios = <5>; + }; + }; + }; + +compatible: "nxp,sc18is604" + +include: [spi-device.yaml] + +properties: + + reset-gpios: + type: phandle-array + description: | + Optional GPIO pin for chip reset. + Example usage: + reset-gpios = <&gpio0 14 GPIO_ACTIVE_LOW>; + + interrupt-gpios: + required: true + type: phandle-array + description: | + GPIO pin for interrupt request (mandatory for I2C controller). + Example usage: + interrupt-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>; + +bus: nxp,sc18is604 diff --git a/include/zephyr/drivers/mfd/sc18is604.h b/include/zephyr/drivers/mfd/sc18is604.h new file mode 100644 index 0000000000..331e7205fd --- /dev/null +++ b/include/zephyr/drivers/mfd/sc18is604.h @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_MFD_SC18IS604_H_ +#define ZEPHYR_INCLUDE_DRIVERS_MFD_SC18IS604_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include +#include + +#define SC18IS604_CMD_WRITE_I2C 0x00 +#define SC18IS604_CMD_READ_I2C 0x01 +#define SC18IS604_CMD_READ_AFTER_WRITE_I2C 0x02 +#define SC18IS604_CMD_WRITE_AFTER_WRITE_I2C 0x03 +#define SC18IS604_CMD_READ_BUFFER 0x06 +#define SC18IS604_CMD_SPI_CONFIG 0x18 +#define SC18IS604_CMD_WRITE_REGISTER 0x20 +#define SC18IS604_CMD_READ_REGISTER 0x21 +#define SC18IS604_CMD_POWER_DOWN 0x30 +#define SC18IS604_CMD_POWER_DOWN_KEY1 0x5A +#define SC18IS604_CMD_POWER_DOWN_KEY2 0xA5 +#define SC18IS604_CMD_VERSION_STRING 0xFE + +#define SC18IS604_VERSION_STRING_SIZE 16 + +#define SC18IS604_REG_IO_CONFIG 0x00 +#define SC18IS604_REG_IO_STATE 0x01 +#define SC18IS604_REG_I2C_CLOCK 0x02 +#define SC18IS604_REG_I2C_TIMEOUT 0x03 +#define SC18IS604_REG_I2C_STATUS 0x04 +#define SC18IS604_REG_I2C_ADDRESS 0x05 + +#define SC18IS604_IO_NUM_PINS_INOUT 4 +#define SC18IS604_IO_NUM_PINS_IONLY 1 +#define SC18IS604_IO_NUM_PINS_MAX (SC18IS604_IO_NUM_PINS_INOUT \ + + SC18IS604_IO_NUM_PINS_IONLY) + +#define SC18IS604_IO_CONFIG_INPUT 0b00 +#define SC18IS604_IO_CONFIG_PUSH_PULL 0b10 +#define SC18IS604_IO_CONFIG_OPEN_DRAIN 0b11 + +#define SC18IS604_I2C_STATUS_SUCCESS 0xF0 +#define SC18IS604_I2C_STATUS_DEVICE_ADDR_NACK 0xF1 +#define SC18IS604_I2C_STATUS_BYTE_NACK 0xF2 +#define SC18IS604_I2C_STATUS_BUSY 0xF3 + +#define SC18IS604_I2C_CLOCK_99KHZ 19 +#define SC18IS604_I2C_CLOCK_375KHZ 5 + +/** + * @defgroup mfd_interface_sc18is604 MFD SC18IM604 Interface + * @ingroup mfd_interfaces + * @{ + */ + +/** + * @brief Perform a data transfer to and from an SC18IS604 device. + * + * @param dev An SC18IS604 MFD device. + * @param cmd A command sequence sent before the TX data. Can be NULL, in which + * case no command sequence is sent). + * @param cmd_len Length of the command sequence. + * @param tx_data Data to be sent to the device. Can be NULL, in which case no + * data is send). + * @param tx_len Length of the TX data buffer. + * @param[out] rx_data Buffer to hold data received from the device. Can be + * NULL, in which case no data is received. + * @param rx_len Length of the RX data buffer. + * + * @retval 0 On success. + * @return Negative error code on failure. + */ +int mfd_sc18is604_transfer(const struct device *dev, + uint8_t *cmd, size_t cmd_len, + uint8_t *tx_data, size_t tx_len, + uint8_t *rx_data, size_t rx_len); + + +/** + * @brief Write to an internal register. + * + * @param dev An SC18IS604 MFD device. + * @param reg Register address to write to. + * @param value Value to write into the register. + * + * @return A value from mfd_sc18is604_transfer(). + */ +int mfd_sc18is604_read_register(const struct device *dev, + uint8_t reg, uint8_t *val); + +#define READ_SC18IS604_REG(dev, reg, val) \ + mfd_sc18is604_read_register((dev), SC18IS604_REG_##reg, (val)); + +/** + * @brief Read from an internal register. + * + * @param dev An SC18IS604 MFD device. + * @param reg Register address to read from. + * @param[out] value Value read from the register. + * + * @return A value from mfd_sc18is604_transfer(). + */ +int mfd_sc18is604_write_register(const struct device *dev, + uint8_t reg, uint8_t val); + +#define WRITE_SC18IS604_REG(dev, reg, val) \ + mfd_sc18is604_write_register((dev), SC18IS604_REG_##reg, (val)); + +/** + * @brief Read data from the internal buffer of an SC18IS604. + * + * @param dev An SC18IS604 MFD device. + * @param[out] data Data read from the buffer. + * @param len Number of bytes to read from the buffer. + * + * @return A value from mfd_sc18is604_transfer(). + */ +int mfd_sc18is604_read_buffer(const struct device *dev, + uint8_t *data, size_t len); + +/** + * @brief Register an interrupt callback. + * + * @param dev An SC18IS604 device + * @param callback The callback to add. + * + * @retval 0 On success. + * @return Negative error code on failure. + */ +int mfd_sc18is604_add_callback(const struct device *dev, + struct gpio_callback *callback); + +/** + * @brief Remove a previously registered interrupt callback. + * + * @param dev An SC18IS604 device + * @param callback The callback to remove. + * + * @retval 0 On success. + * @return Negative error code on failure. + */ +int mfd_sc18is604_remove_callback(const struct device *dev, + struct gpio_callback *callback); + +/** + * @brief Claim a lock on an SC18IS604 device, preventing other users from + * accessing the device until the lock is released. + * + * @param dev The SC18IS604 MFD device to be locked. + * @param timeout Timeout after which trying to claim the device is aborted. + * + * @return A value from k_sem_take. + */ +int mfd_sc18is604_claim(const struct device *dev, k_timeout_t timeout); + +/** + * @brief Release a previously acquired lock on an SC18IS604 device. + * + * @param dev The SC18IS604 MFD device to be unlocked. + */ +void mfd_sc18is604_release(const struct device *dev); + +#ifdef CONFIG_MFD_SC18IS604_ASYNC + +/** + * @brief Asynchronously transfer data to and from an SC18IS604 device. This is + * the most general transfer routine, all other transfer variants are + * implemented in terms of this one. + * + * @param dev An SC18IS604 MFD device. + * @param cmd A command sequence sent before the TX data. Can be NULL, in which + * case no command sequence is sent). + * @param cmd_len Length of the command sequence. + * @param tx_data Data to be sent to the device. Can be NULL, in which case no + * data is send). + * @param tx_len Length of the TX data buffer. + * @param[out] rx_data Buffer to hold data received from the device. Can be + * NULL, in which case no data is received. + * @param rx_len Length of the RX data buffer. + * @param signal Signal to be raised once the transfer is complete. + * + * @retval 0 If the transfer was successfully started. + * @return Negative error code on failure. + */ +int mfd_sc18is604_transfer_signal(const struct device *dev, + uint8_t *cmd, size_t cmd_len, + uint8_t *tx_data, size_t tx_len, + uint8_t *rx_data, size_t rx_len, + struct k_poll_signal *signal); + +/** + * @brief Read from an internal register asychronously. + * + * @param dev An SC18IS604 MFD device. + * @param reg Register address to read from. + * @param[out] value Value read from the register. Pointer must remain valid + * until the transfer is complete. + * @param signal Signal that will be raised on transfer completion. Pointer + * must remain valid until the transfer is complete. + * + * @return A value from mfd_sc18is604_transfer_signal(). + */ +int mfd_sc18is604_read_register_signal(const struct device *dev, + uint8_t reg, uint8_t *val, + struct k_poll_signal *signal); + +#define READ_SC18IS604_REG_SIGNAL(dev, reg, val, signal) \ + mfd_sc18is604_read_register_signal((dev), SC18IS604_REG_##reg, \ + (val), (signal)); + +/** + * @brief Read data from the internal buffer of an SC18IS604 asynchronously. + * + * @param dev An SC18IS604 MFD device. + * @param[out] data Data read from the buffer. Pointer must remain valid until + * the transfer is complete. + * @param len Number of bytes to read from the buffer. + * @param signal Signal that will be raised on transfer completion. Pointer + * must remain valid until the transfer is complete. + * + * @return A value from mfd_sc18is604_transfer(). + */ +int mfd_sc18is604_read_buffer_signal(const struct device *dev, + uint8_t *data, size_t len, + struct k_poll_signal *signal); + +#endif /* CONFIG_MFD_SC18IS604_ASYNC */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_MFD_SC18IS604_H_ */ From 72b9c10246a5619fffb55c2cde6fd7ee4f7d7863 Mon Sep 17 00:00:00 2001 From: Sarah Renkhoff Date: Tue, 11 Jun 2024 13:24:50 +0200 Subject: [PATCH 2/7] drivers: add SC18IS604 GPIO controller driver This driver manages the GPIO controller part of an SC18IS604 MFD. It doesn't provide interrupt functionality, since the SC18IS604 doesn't generate interrupt signals for changes on its GPIO pins. Signed-off-by: Sarah Renkhoff Signed-off-by: Stephan Linz --- drivers/gpio/CMakeLists.txt | 9 +- drivers/gpio/Kconfig | 3 +- drivers/gpio/Kconfig.sc18is604 | 17 + drivers/gpio/gpio_sc18is604.c | 380 ++++++++++++++++++++++ dts/bindings/gpio/nxp,sc18is604-gpio.yaml | 22 ++ 5 files changed, 426 insertions(+), 5 deletions(-) create mode 100644 drivers/gpio/Kconfig.sc18is604 create mode 100644 drivers/gpio/gpio_sc18is604.c create mode 100644 dts/bindings/gpio/nxp,sc18is604-gpio.yaml diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt index d10b46d790..9e8a407e8b 100644 --- a/drivers/gpio/CMakeLists.txt +++ b/drivers/gpio/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2023 TiaC Systems +# Copyright (c) 2021-2024 TiaC Systems # SPDX-License-Identifier: Apache-2.0 # Add to the existing gpio library from zephyr instead of defining our own. @@ -6,6 +6,7 @@ # build errors can occur! zephyr_library_amend() -zephyr_library_sources_ifdef(CONFIG_GPIO_PCA9554 gpio_pca9554.c) -zephyr_library_sources_ifdef(CONFIG_GPIO_PCA9555 gpio_pca9555.c) -zephyr_library_sources_ifdef(CONFIG_GPIO_SIPOMUX gpio_sipomux.c) +zephyr_library_sources_ifdef(CONFIG_GPIO_PCA9554 gpio_pca9554.c) +zephyr_library_sources_ifdef(CONFIG_GPIO_PCA9555 gpio_pca9555.c) +zephyr_library_sources_ifdef(CONFIG_GPIO_SC18IS604 gpio_sc18is604.c) +zephyr_library_sources_ifdef(CONFIG_GPIO_SIPOMUX gpio_sipomux.c) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index fcd5f7a6e8..df977c92cb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1,10 +1,11 @@ -# Copyright (c) 2021-2023 TiaC Systems +# Copyright (c) 2021-2024 TiaC Systems # SPDX-License-Identifier: Apache-2.0 if GPIO rsource "Kconfig.pca9554" rsource "Kconfig.pca9555" +rsource "Kconfig.sc18is604" rsource "Kconfig.sipomux" endif # GPIO diff --git a/drivers/gpio/Kconfig.sc18is604 b/drivers/gpio/Kconfig.sc18is604 new file mode 100644 index 0000000000..94a8391901 --- /dev/null +++ b/drivers/gpio/Kconfig.sc18is604 @@ -0,0 +1,17 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +menuconfig GPIO_SC18IS604 + bool "GPIO controller part of an SC18IM604 bridge" + default y + depends on DT_HAS_NXP_SC18IS604_GPIO_ENABLED + depends on MFD_SC18IS604 + help + Enable driver for GPIO controller part of an SC18IM604 bridge. + +config GPIO_SC18IS604_INIT_PRIORITY + int "Init priority" + default 85 + depends on GPIO_SC18IS604 + help + Device driver initialization priority. diff --git a/drivers/gpio/gpio_sc18is604.c b/drivers/gpio/gpio_sc18is604.c new file mode 100644 index 0000000000..93530e2228 --- /dev/null +++ b/drivers/gpio/gpio_sc18is604.c @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_sc18is604_gpio + +#include +#include +#include + +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(gpio_sc18is604, CONFIG_GPIO_LOG_LEVEL); + +/** + * @brief SC18IM604 GPIO controller configuration data + * + * @a num_pins must be in the range [1, @ref SC18IS604_IO_NUM_PINS_MAX]. + * + * This structure contains all of the state for a given SC18IM604 GPIO + * controller as well as the binding to related MFD device. + */ +typedef struct gpio_sc18is604_config { + /** Common @ref gpio_driver_config (needs to be first) */ + struct gpio_driver_config common; + /** Number of pins available in the given GPIO controller instance */ + const gpio_pin_t num_pins; + /** Backend MFD (bridge) device for real operations on hardware */ + const struct device *bridge; +} gpio_sc18is604_config_t; + +/** + * @brief SC18IM604 GPIO controller data + * + * This structure contains data structures used by a SC18IM604 GPIO + * controller. + * + * Changes to @ref gpio_sc18is604_data and @ref gpio_sc18is604_config are + * synchronized using @a k_sem. + */ +typedef struct gpio_sc18is604_data { + /** Common @ref gpio_driver_data (needs to be first) */ + struct gpio_driver_data common; + /** Semaphore to synchronize accesses to driver data and config */ + struct k_sem lock; + /** GPIO pin direction and driver configuration */ + uint8_t pin_config; + /** GPIO pin level state */ + uint8_t pin_state; +} gpio_sc18is604_data_t; + +static int gpio_sc18is604_pin_write_config(const struct device *dev, + uint8_t val) +{ + const gpio_sc18is604_config_t * const config = dev->config; + gpio_sc18is604_data_t * const data = dev->data; + int ret = 0; + + ret = WRITE_SC18IS604_REG(config->bridge, IO_CONFIG, val); + if (ret != 0) { + goto end; + } + + k_sem_take(&data->lock, K_FOREVER); + + data->pin_config = val; + + k_sem_give(&data->lock); + +end: + return ret; +} + +static int gpio_sc18is604_pin_read_config(const struct device *dev, + uint8_t *val) +{ + const gpio_sc18is604_config_t * const config = dev->config; + gpio_sc18is604_data_t * const data = dev->data; + int ret = 0; + + ret = READ_SC18IS604_REG(config->bridge, IO_CONFIG, val); + if (ret != 0) { + goto end; + } + + k_sem_take(&data->lock, K_FOREVER); + + data->pin_config = *val; + + k_sem_give(&data->lock); + +end: + return ret; +} + +static int gpio_sc18is604_pin_write_state(const struct device *dev, + uint8_t val) +{ + const gpio_sc18is604_config_t * const config = dev->config; + gpio_sc18is604_data_t * const data = dev->data; + int ret = 0; + + ret = WRITE_SC18IS604_REG(config->bridge, IO_STATE, val); + if (ret != 0) { + goto end; + } + + k_sem_take(&data->lock, K_FOREVER); + + data->pin_state = val; + + k_sem_give(&data->lock); + +end: + return ret; +} + +static int gpio_sc18is604_pin_read_state(const struct device *dev, + uint8_t *val) +{ + const gpio_sc18is604_config_t * const config = dev->config; + gpio_sc18is604_data_t * const data = dev->data; + int ret = 0; + + ret = READ_SC18IS604_REG(config->bridge, IO_STATE, val); + if (ret != 0) { + goto end; + } + + k_sem_take(&data->lock, K_FOREVER); + + data->pin_state = *val; + + k_sem_give(&data->lock); + +end: + return ret; +} + +static int gpio_sc18is604_pin_configure(const struct device *dev, + gpio_pin_t pin, gpio_flags_t flags) +{ + gpio_sc18is604_data_t * const data = dev->data; + uint8_t shift, bits, pin_config, pin_state; + int ret = 0; + + /* Can't do bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + /* Can only configure SC18IS604_IO_NUM_PINS_INOUT pins */ + if (pin >= SC18IS604_IO_NUM_PINS_INOUT) { + + /* Can only handle SC18IS604_IO_NUM_PINS_MAX pins */ + if (pin >= SC18IS604_IO_NUM_PINS_MAX) { + return -ENOTSUP; + + /* SC18IS604_IO_NUM_PINS_MAX - SC18IS604_IO_NUM_PINS_INOUT */ + } else if ((flags & GPIO_INPUT) == GPIO_INPUT) { + + /* + * Do nothing for the highest pin numbers, + * input is the default and can not change. + */ + goto end; + + } else { + return -ENOTSUP; + } + } + + /* Does not support disconnected pin */ + if ((flags & (GPIO_INPUT | GPIO_OUTPUT)) == GPIO_DISCONNECTED) { + return -ENOTSUP; + } + + k_sem_take(&data->lock, K_FOREVER); + + pin_config = data->pin_config; + pin_state = data->pin_state; + + k_sem_give(&data->lock); + + /* Prepare new configuration for target pin */ + shift = 2 * pin; /* Each pin has two configuration bits */ + bits = 0b00; + + if ((flags & GPIO_INPUT) == GPIO_INPUT) { + bits = SC18IS604_IO_CONFIG_INPUT; + } else if ((flags & GPIO_OUTPUT) == GPIO_OUTPUT) { + if ((flags & GPIO_OPEN_DRAIN) == GPIO_OPEN_DRAIN) { + bits = SC18IS604_IO_CONFIG_OPEN_DRAIN; + } else { + bits = SC18IS604_IO_CONFIG_PUSH_PULL; + } + } + + pin_config &= ~(0b11 << shift); /* Clear config bits */ + pin_config |= (bits << shift); /* Set new config */ + + /* Apply configuration */ + ret = gpio_sc18is604_pin_write_config(dev, pin_config); + if (ret != 0) { + goto end; + } + + /* For output pins, state might also be configured */ + if ((flags & GPIO_OUTPUT) == GPIO_OUTPUT) { + if ((flags & GPIO_OUTPUT_INIT_HIGH) == GPIO_OUTPUT_INIT_HIGH) { + pin_state |= BIT(pin); + } else if ((flags & GPIO_OUTPUT_INIT_LOW) == GPIO_OUTPUT_INIT_LOW) { + pin_state &= ~BIT(pin); + } + + /* Apply pin state */ + ret = gpio_sc18is604_pin_write_state(dev, pin_state); + if (ret != 0) { + goto end; + } + } + +end: + return ret; +} + +static int gpio_sc18is604_get_raw(const struct device *dev, uint32_t *value) +{ + uint8_t buf = 0x00; + int ret = 0; + + /* Can't do bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + /* Read current state from device */ + ret = gpio_sc18is604_pin_read_state(dev, &buf); + if (ret != 0) { + goto end; + } + + /* Return through pointer */ + *value = buf; + +end: + return ret; +} + +static int gpio_sc18is604_set_masked_raw(const struct device *dev, + uint32_t mask, uint32_t value) +{ + gpio_sc18is604_data_t * const data = dev->data; + uint8_t pin_state; + + /* Can't do operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&data->lock, K_FOREVER); + + /* Get (cached) state, and set bits to value according to mask */ + pin_state = data->pin_state; + pin_state = (pin_state & ~mask) | (mask & value); /* BYTE macro ? */ + + k_sem_give(&data->lock); + + /* Apply new pin state */ + return gpio_sc18is604_pin_write_state(dev, pin_state); +} + +static int gpio_sc18is604_set_bits_raw(const struct device *dev, uint32_t pins) +{ + return gpio_sc18is604_set_masked_raw(dev, pins, pins); +} + +static int gpio_sc18is604_clear_bits_raw(const struct device *dev, uint32_t pins) +{ + return gpio_sc18is604_set_masked_raw(dev, pins, 0U); +} + +static int gpio_sc18is604_toggle_bits(const struct device *dev, uint32_t pins) +{ + gpio_sc18is604_data_t * const data = dev->data; + uint8_t pin_state; + + /* Can't do operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&data->lock, K_FOREVER); + + /* Get (cached) state, and toggle bits according to mask */ + pin_state = data->pin_state; + pin_state ^= pins; /* BYTE macro ? */ + + k_sem_give(&data->lock); + + /* Apply new pin state */ + return gpio_sc18is604_pin_write_state(dev, pin_state); +} + +static const struct gpio_driver_api gpio_sc18is604_api = { + .pin_configure = gpio_sc18is604_pin_configure, + .port_get_raw = gpio_sc18is604_get_raw, + .port_set_masked_raw = gpio_sc18is604_set_masked_raw, + .port_set_bits_raw = gpio_sc18is604_set_bits_raw, + .port_clear_bits_raw = gpio_sc18is604_clear_bits_raw, + .port_toggle_bits = gpio_sc18is604_toggle_bits, +}; + +static int gpio_sc18is604_init(const struct device *dev) +{ + const gpio_sc18is604_config_t * const config = dev->config; + gpio_sc18is604_data_t * const data = dev->data; + uint8_t buf; + + /* Check MFD (bridge) readiness */ + if (!device_is_ready(config->bridge)) { + LOG_ERR("%s: bridge device %s not ready", + dev->name, config->bridge->name); + return -ENODEV; + } + + k_sem_init(&data->lock, 1, 1); + + /* Initialize register cache */ + gpio_sc18is604_pin_read_state(dev, &buf); + gpio_sc18is604_pin_read_config(dev, &buf); + + LOG_DBG("%s: ready for %u pins with bridge over %s!", dev->name, + config->num_pins, config->bridge->name); + return 0; +} + +#ifdef CONFIG_PM_DEVICE +static int gpio_sc18is604_pm_device_pm_action(const struct device *dev, + enum pm_device_action action) +{ + ARG_UNUSED(dev); + ARG_UNUSED(action); + + return 0; +} +#endif + +#define GPIO_SC18IS604_DEFINE(inst) \ + \ + static const gpio_sc18is604_config_t gpio_sc18is604_config_##inst = \ + { \ + .common = { \ + .port_pin_mask = \ + GPIO_PORT_PIN_MASK_FROM_DT_INST(inst), \ + }, \ + .num_pins = DT_INST_PROP(inst, ngpios), \ + .bridge = DEVICE_DT_GET(DT_INST_BUS(inst)), \ + }; \ + BUILD_ASSERT( \ + DT_INST_PROP(inst, ngpios) <= SC18IS604_IO_NUM_PINS_MAX, \ + "Too many ngpios"); \ + \ + static gpio_sc18is604_data_t gpio_sc18is604_data_##inst; \ + \ + PM_DEVICE_DT_INST_DEFINE(inst, gpio_sc18is604_pm_device_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(inst, gpio_sc18is604_init, \ + PM_DEVICE_DT_INST_GET(inst), \ + &gpio_sc18is604_data_##inst, \ + &gpio_sc18is604_config_##inst, POST_KERNEL,\ + CONFIG_GPIO_SC18IS604_INIT_PRIORITY, \ + &gpio_sc18is604_api); + +DT_INST_FOREACH_STATUS_OKAY(GPIO_SC18IS604_DEFINE); diff --git a/dts/bindings/gpio/nxp,sc18is604-gpio.yaml b/dts/bindings/gpio/nxp,sc18is604-gpio.yaml new file mode 100644 index 0000000000..9c46224735 --- /dev/null +++ b/dts/bindings/gpio/nxp,sc18is604-gpio.yaml @@ -0,0 +1,22 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +description: GPIO controller part of an SC18IM604 bridge + +compatible: "nxp,sc18is604-gpio" + +include: [gpio-controller.yaml] + +properties: + "#gpio-cells": + const: 2 + + ngpios: + required: true + const: 5 + +gpio-cells: + - pin + - flags + +on-bus: nxp,sc18is604 From fa23befd38864f1c364c9cc4acf323b546878c70 Mon Sep 17 00:00:00 2001 From: Sarah Renkhoff Date: Tue, 11 Jun 2024 13:51:36 +0200 Subject: [PATCH 3/7] drivers: add SC18IS604 I2C controller driver This driver manages the I2C controller part of an SC18IS604 MFD, and relies on the asynchronous transfer functions provided by the MFD driver to handle interrupts through non-blocking system work queue items. The asynchronous API function is implemented via fully non-blocking work items running on the system work queue. Tracking each in-flight transfer requires ~100 bytes of heap allocation, so the driver requests some heap space by default. Signed-off-by: Sarah Renkhoff Signed-off-by: Stephan Linz --- drivers/CMakeLists.txt | 1 + drivers/Kconfig | 1 + drivers/i2c/CMakeLists.txt | 10 + drivers/i2c/Kconfig | 8 + drivers/i2c/Kconfig.sc18is604 | 29 ++ drivers/i2c/i2c_sc18is604.c | 440 ++++++++++++++++++++++++ drivers/i2c/i2c_sc18is604.h | 94 +++++ drivers/i2c/i2c_sc18is604_callback.c | 382 ++++++++++++++++++++ drivers/mfd/Kconfig.sc18is604 | 1 + dts/bindings/i2c/nxp,sc18is604-i2c.yaml | 10 + 10 files changed, 976 insertions(+) create mode 100644 drivers/i2c/CMakeLists.txt create mode 100644 drivers/i2c/Kconfig create mode 100644 drivers/i2c/Kconfig.sc18is604 create mode 100644 drivers/i2c/i2c_sc18is604.c create mode 100644 drivers/i2c/i2c_sc18is604.h create mode 100644 drivers/i2c/i2c_sc18is604_callback.c create mode 100644 dts/bindings/i2c/nxp,sc18is604-i2c.yaml diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 0f721e757e..56a4fb07fc 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory_ifdef(CONFIG_DISPLAY display) add_subdirectory_ifdef(CONFIG_GPIO gpio) +add_subdirectory_ifdef(CONFIG_I2C i2c) add_subdirectory_ifdef(CONFIG_MFD mfd) add_subdirectory_ifdef(CONFIG_RTC rtc) add_subdirectory_ifdef(CONFIG_SENSOR sensor) diff --git a/drivers/Kconfig b/drivers/Kconfig index 6a3b62babc..76563505d1 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -6,6 +6,7 @@ menu "Device Drivers" rsource "display/Kconfig" rsource "gpio/Kconfig" +rsource "i2c/Kconfig" rsource "mfd/Kconfig" rsource "rtc/Kconfig" rsource "sensor/Kconfig" diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt new file mode 100644 index 0000000000..28cd99b284 --- /dev/null +++ b/drivers/i2c/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +# Add to the existing i2c library from zephyr instead of defining our own. +# It is actually required to add I2C drivers to that library, otherwise +# build errors can occur! +zephyr_library_amend() + +zephyr_library_sources_ifdef(CONFIG_I2C_SC18IS604 i2c_sc18is604.c) +zephyr_library_sources_ifdef(CONFIG_I2C_CALLBACK i2c_sc18is604_callback.c) diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig new file mode 100644 index 0000000000..375a15d50c --- /dev/null +++ b/drivers/i2c/Kconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +if I2C + +rsource "Kconfig.sc18is604" + +endif # I2C diff --git a/drivers/i2c/Kconfig.sc18is604 b/drivers/i2c/Kconfig.sc18is604 new file mode 100644 index 0000000000..b07f937c4f --- /dev/null +++ b/drivers/i2c/Kconfig.sc18is604 @@ -0,0 +1,29 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +menuconfig I2C_SC18IS604 + bool "I2C controller part of an SC18IM604 bridge" + default y + depends on DT_HAS_NXP_SC18IS604_I2C_ENABLED + depends on MFD_SC18IS604 + select MFD_SC18IS604_ASYNC + select POLL # dependency of MFD_SC18IS604_ASYNC + select DYNAMIC_THREAD # dependency of MFD_SC18IS604_ASYNC + select THREAD_STACK_INFO # dependency of DYNAMIC_THREAD + help + Enable driver for I2C controller part of an SC18IM604 bridge. + +config I2C_SC18IS604_INIT_PRIORITY + int "Init priority" + default 60 + depends on I2C_SC18IS604 + help + Device driver initialization priority. + +if I2C_CALLBACK + + config HEAP_MEM_POOL_ADD_SIZE_I2C_SC18IS604 + int + default 512 # Enough for 5 in-flight async transfers + +endif diff --git a/drivers/i2c/i2c_sc18is604.c b/drivers/i2c/i2c_sc18is604.c new file mode 100644 index 0000000000..031dc4ebe9 --- /dev/null +++ b/drivers/i2c/i2c_sc18is604.c @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef CONFIG_MFD_SC18IS604_ASYNC +#error "CONFIG_MFD_SC18IS604_ASYNC must be enabled for I2C controller to function." +#endif /* CONFIG_MFD_SC18IS604_ASYNC */ + +#define DT_DRV_COMPAT nxp_sc18is604_i2c + +#include +#include +#include + +#include + +#include + +#include "i2c_sc18is604.h" + +#include +LOG_MODULE_REGISTER(sc18is604_i2c, CONFIG_I2C_LOG_LEVEL); + +int await_signal(struct k_poll_signal *signal, int *result, k_timeout_t timeout) +{ + struct k_poll_event events[1] = { + K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + signal), + }; + + k_poll(events, 1, timeout); + + int signaled = 0; + + k_poll_signal_check(signal, &signaled, result); + + return signaled; +} + +/* Initialize reading out the interrupt source. */ +static void i2c_sc18is604_interrupt_work_fn_initial(struct k_work *work) +{ + i2c_sc18is604_data_t * const data = CONTAINER_OF(work, + i2c_sc18is604_data_t, + interrupt_work_initial); + const struct device *dev = data->dev; + const i2c_sc18is604_config_t * const config = dev->config; + int ret = 0; + + /* Soft spinning on interrupt handling lock */ + ret = k_sem_take(&data->interrupt_lock, K_NO_WAIT); + if (ret != 0) { + k_work_submit(work); + return; + } + + /* + * Begin asynchronous readout of i2c status register (this resets the + * hardware interrupt signal). + */ + k_poll_signal_reset(&data->interrupt_handling_data.signal); + ret = READ_SC18IS604_REG_SIGNAL(config->parent_dev, I2C_STATUS, + &data->interrupt_handling_data.i2cstat, + &data->interrupt_handling_data.signal); + if (ret != 0) { + /* Failed to read out I2C status, retry */ + k_work_submit(work); + k_sem_give(&data->interrupt_lock); + return; + } + + /* Submit work item for awaiting result */ + ret = k_work_schedule(&data->interrupt_work_final, K_NO_WAIT); + if (ret != 0 && ret != 1) { + /* + * ret == 0 shouldn't happen, but is permissible: at least we + * know the item will run. Other return values mean we can't + * rely on the item to run, so we must release the lock here to + * prevent a deadlock on the next interrupt. + */ + k_sem_give(&data->interrupt_lock); + } +} + +/* Await the result of reading out the interrupt source and act on it. */ +static void i2c_sc18is604_interrupt_work_fn_final(struct k_work *work) +{ + struct k_work_delayable *work_delayable = CONTAINER_OF(work, struct k_work_delayable, work); + i2c_sc18is604_data_t * const data = CONTAINER_OF(work_delayable, + i2c_sc18is604_data_t, + interrupt_work_final); + + /* Check result of i2c status readout */ + int result = 0; + int signaled = await_signal(&data->interrupt_handling_data.signal, + &result, + K_NO_WAIT); + + if (!signaled) { + /* Transfer not complete, keep spinning */ + k_work_schedule(work_delayable, K_MSEC(1)); + return; + } + + /* If bus transfer failed, try again */ + if (result != 0) { + k_work_submit(&data->interrupt_work_initial); + goto end; + } + + /* Otherwise, propagate the received status via signal */ + k_poll_signal_raise(&data->interrupt_signal, + data->interrupt_handling_data.i2cstat); + +end: + /* Release interrupt handling lock */ + k_sem_give(&data->interrupt_lock); +} + +/* + * Interrupt callback that is registered with the parent MFD which runs in ISR + * context. + */ +static void i2c_sc18is604_interrupt_callback(const struct device *dev, + struct gpio_callback *cb, + gpio_port_pins_t pins) +{ + i2c_sc18is604_data_t * const data = CONTAINER_OF(cb, + i2c_sc18is604_data_t, + interrupt_callback); + + /* + * Reading out the interrupt source requires bus communication, so we do + * it via work queue items + */ + k_work_submit(&data->interrupt_work_initial); +} + +static inline int i2c_sc18is604_set_clock_speed(const struct device *dev, + uint32_t speed) +{ + const i2c_sc18is604_config_t * const config = dev->config; + uint8_t value; + + switch (speed) { + case I2C_SPEED_STANDARD: + value = SC18IS604_I2C_CLOCK_99KHZ; + break; + case I2C_SPEED_FAST: + value = SC18IS604_I2C_CLOCK_375KHZ; + break; + default: + return -EINVAL; + } + + return WRITE_SC18IS604_REG(config->parent_dev, I2C_CLOCK, value); +} + +static int i2c_sc18is604_write_message(const struct device *dev, + uint8_t *data, size_t len, + uint16_t addr) +{ + const i2c_sc18is604_config_t * const config = dev->config; + i2c_sc18is604_data_t * const drv_data = dev->data; + int ret; + + uint8_t cmd[] = { + SC18IS604_CMD_WRITE_I2C, + len, (uint8_t) addr + }; + + /* Reset interrupt signal */ + k_poll_signal_reset(&drv_data->interrupt_signal); + + /* Send 'I2C WRITE' command with data attached */ + ret = mfd_sc18is604_transfer(config->parent_dev, + cmd, ARRAY_SIZE(cmd), + data, len, NULL, 0); + if (ret != 0) { + return ret; + } + /* Await interrupt signal */ + int result = 0; + int signaled = await_signal(&drv_data->interrupt_signal, &result, K_FOREVER); + + if (!signaled) { + return -EIO; + } + + if (result != SC18IS604_I2C_STATUS_SUCCESS) { + return -EIO; + } + + return 0; +} + +static int i2c_sc18is604_read_message(const struct device *dev, + uint8_t *data, size_t len, + uint16_t addr) +{ + const i2c_sc18is604_config_t * const config = dev->config; + i2c_sc18is604_data_t * const drv_data = dev->data; + int ret = 0; + + uint8_t cmd[] = { + SC18IS604_CMD_READ_I2C, + len, (uint8_t) addr + }; + + /* Claim lock on MFD */ + mfd_sc18is604_claim(config->parent_dev, K_FOREVER); + + /* Reset interrupt signal */ + k_poll_signal_reset(&drv_data->interrupt_signal); + + /* Send 'I2C READ' command */ + ret = mfd_sc18is604_transfer(config->parent_dev, + cmd, ARRAY_SIZE(cmd), + NULL, 0, NULL, 0); + if (ret != 0) { + goto release_and_return; + } + + /* Await interrupt signal */ + int result = 0; + int signaled = await_signal(&drv_data->interrupt_signal, &result, K_FOREVER); + + if (!signaled) { + ret = -EIO; + goto release_and_return; + } + + if (result != SC18IS604_I2C_STATUS_SUCCESS) { + ret = -EIO; + goto release_and_return; + } + + /* Read out the received data */ + ret = mfd_sc18is604_read_buffer(config->parent_dev, data, len); + if (ret != 0) { + ret = -EIO; + goto release_and_return; + } + +release_and_return: + mfd_sc18is604_release(config->parent_dev); + return ret; +} + +static int i2c_sc18is604_configure(const struct device *dev, uint32_t config) +{ + i2c_sc18is604_data_t * const data = dev->data; + + /* Device can only act as controller */ + if ((config & I2C_MODE_CONTROLLER) != I2C_MODE_CONTROLLER) { + return -EINVAL; + } + + /* Controller doesn't support 10bit addressing */ + if ((config & I2C_ADDR_10_BITS) == I2C_ADDR_10_BITS) { + return -EINVAL; + } + + /* Adjust bus speed if necessary */ + uint32_t speed = I2C_SPEED_GET(config); + + if (speed != I2C_SPEED_GET(data->i2c_config)) { + + /* + * Controller only supports 'standard' (100kHz) and 'fast' + * (400kHz) speeds. + */ + if ((speed == I2C_SPEED_STANDARD) || + (speed == I2C_SPEED_FAST)) { + + int ret = i2c_sc18is604_set_clock_speed(dev, speed); + + if (ret != 0) { + return ret; + } + + } else { + return -EINVAL; + } + } + + /* Store new config */ + data->i2c_config = config; + + return 0; +} + +static int i2c_sc18is604_get_config(const struct device *dev, uint32_t *config) +{ + const i2c_sc18is604_data_t * const data = dev->data; + *config = data->i2c_config; + return 0; +} + +static int i2c_sc18is604_transfer_msg(const struct device *dev, + struct i2c_msg *msg, uint16_t addr) +{ + /* Device doesn't support 10bit addressing */ + if ((msg->flags & I2C_MSG_ADDR_10_BITS) == I2C_MSG_ADDR_10_BITS) { + return -EINVAL; + } + + /* Add RW bit to address */ + addr <<= 1; + addr |= (msg->flags & I2C_MSG_RW_MASK); + + if ((msg->flags & I2C_MSG_READ) == I2C_MSG_READ) { + return i2c_sc18is604_read_message(dev, msg->buf, + msg->len, addr); + } else { + return i2c_sc18is604_write_message(dev, msg->buf, + msg->len, addr); + } + + /* Should be unreachable */ + return -EINVAL; +} + +static int i2c_sc18is604_transfer(const struct device *dev, + struct i2c_msg *msgs, + uint8_t num_msgs, + uint16_t addr) +{ + i2c_sc18is604_data_t * const data = dev->data; + int ret; + + if (num_msgs == 0) { + return 0; + } + + /* Lock driver instance before multi-transfer transaction */ + k_sem_take(&data->lock, K_FOREVER); + + /* Handle messages one-by-one */ + for (int i = 0; i < num_msgs; i++) { + ret = i2c_sc18is604_transfer_msg(dev, &msgs[i], addr); + if (ret != 0) { + break; + } + } + + /* Release lock */ + k_sem_give(&data->lock); + + return ret; +} + +static const struct i2c_driver_api i2c_sc18is604_api = { + .configure = i2c_sc18is604_configure, + .get_config = i2c_sc18is604_get_config, + .transfer = i2c_sc18is604_transfer, +#ifdef CONFIG_I2C_CALLBACK + .transfer_cb = i2c_sc18is604_transfer_cb, +#endif /* CONFIG_I2C_CALLBACK */ +}; + +static int i2c_sc18is604_init(const struct device *dev) +{ + const i2c_sc18is604_config_t * const config = dev->config; + i2c_sc18is604_data_t * const data = dev->data; + int ret = 0; + + /* Check parent bus readiness */ + if (!device_is_ready(config->parent_dev)) { + LOG_ERR("%s: MFD device %s not ready", + dev->name, config->parent_dev->name); + return -ENODEV; + } + + /* Set back-reference */ + data->dev = dev; + + /* Initialize locks (both initially open) */ + k_sem_init(&data->lock, 1, 1); + k_sem_init(&data->interrupt_lock, 1, 1); + + /* Initialize interrupt signal */ + k_poll_signal_init(&data->interrupt_signal); + + /* Register interrupt callback with parent MFD */ + gpio_init_callback(&data->interrupt_callback, + i2c_sc18is604_interrupt_callback, + 0xffffff); + ret = mfd_sc18is604_add_callback(config->parent_dev, &data->interrupt_callback); + if (ret != 0) { + LOG_ERR("Failed to register interrupt callback for %s with %s", + config->parent_dev->name, + dev->name); + return ret; + } + + /* Set up work items for interrupt handling */ + k_work_init(&data->interrupt_work_initial, i2c_sc18is604_interrupt_work_fn_initial); + k_work_init_delayable(&data->interrupt_work_final, i2c_sc18is604_interrupt_work_fn_final); + + /* Initialize data used by interrupt handling work items */ + k_poll_signal_init(&data->interrupt_handling_data.signal); + + LOG_DBG("%s: ready over %s!", dev->name, + config->parent_dev->name); + return 0; +} + +#ifdef CONFIG_PM_DEVICE +static int i2c_sc18is604_pm_device_pm_action(const struct device *dev, + enum pm_device_action action) +{ + ARG_UNUSED(dev); + ARG_UNUSED(action); + + return 0; +} +#endif + +#define I2C_SC18IS604_DEFINE(inst) \ + \ + static const i2c_sc18is604_config_t i2c_sc18is604_config_##inst = \ + { \ + .parent_dev = DEVICE_DT_GET(DT_INST_BUS(inst)), \ + }; \ + \ + static i2c_sc18is604_data_t i2c_sc18is604_data_##inst = { }; \ + \ + PM_DEVICE_DT_INST_DEFINE(inst, i2c_sc18is604_pm_device_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(inst, i2c_sc18is604_init, \ + PM_DEVICE_DT_INST_GET(inst), \ + &i2c_sc18is604_data_##inst, \ + &i2c_sc18is604_config_##inst, POST_KERNEL, \ + CONFIG_I2C_SC18IS604_INIT_PRIORITY, \ + &i2c_sc18is604_api); + +DT_INST_FOREACH_STATUS_OKAY(I2C_SC18IS604_DEFINE); diff --git a/drivers/i2c/i2c_sc18is604.h b/drivers/i2c/i2c_sc18is604.h new file mode 100644 index 0000000000..d2591de170 --- /dev/null +++ b/drivers/i2c/i2c_sc18is604.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_I2C_SC18IS604_H_ +#define ZEPHYR_DRIVERS_I2C_SC18IS604_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + + +/** + * @brief SC18IM604 I2C controller configuration data + * + * This structure contains all of the state for a given SC18IM604 + * I2C controller as well as the binding to related MFD device. + */ +typedef struct i2c_sc18is604_config { + /** Parent MFD device for real operations on hardware. */ + const struct device *parent_dev; +} i2c_sc18is604_config_t; + +/** + * @brief SC18IS604 I2C controller data + * + * This structure contains data structures used by a SC18IM604 I2C controller. + * + */ +typedef struct i2c_sc18is604_data { + /** Back-reference to driver instance. */ + const struct device *dev; + /** I2C bus configuration flags. */ + uint32_t i2c_config; + /** Lock for transactions. */ + struct k_sem lock; + /** Interrupt handling callback */ + struct gpio_callback interrupt_callback; + /** Lock for ongoing interrupt handling. */ + struct k_sem interrupt_lock; + /** Signal for waiting on interrupts. */ + struct k_poll_signal interrupt_signal; + /** Work items for interrupt handling */ + struct k_work interrupt_work_initial; + struct k_work_delayable interrupt_work_final; + /** Struct for passing data between interrupt handling work items. */ + struct sc18is604_interrupt_handling_data { + uint8_t i2cstat; + struct k_poll_signal signal; + } interrupt_handling_data; +} i2c_sc18is604_data_t; + +/** + * + * @brief Await a signal being raised. + * + * @param signal The signal to await. + * @param[out] result The result attached to the raised signal. + * @param timeout Timeout before awaiting the signal is aborted. + * + * @retval 1 If the signal was raised. + * @retval 0 If the signal was not raised within the timeout. + * @return Negative error code if some failure occurs during k_poll(). + */ +int await_signal(struct k_poll_signal *signal, + int *result, k_timeout_t timeout); + +/** + * @brief Transfer I2C messages asynchronously. + * + * @param dev An SC18IS604 I2C controller. + * @param msgs I2C messages to be transmitted. Pointer must remain valid + * for the duration of the transfer. + * @param num_msgs Number of messages to be transmitted. + * @param addr I2C address of the target device. + * @param cb Callback to be invoked on transfer completion (or failure). + * @param userdata User data passed to callback. + */ +int i2c_sc18is604_transfer_cb(const struct device *dev, + struct i2c_msg *msgs, uint8_t num_msgs, + uint16_t addr, + i2c_callback_t cb, void *userdata); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_I2C_SC18IS604_H_ */ diff --git a/drivers/i2c/i2c_sc18is604_callback.c b/drivers/i2c/i2c_sc18is604_callback.c new file mode 100644 index 0000000000..b3be02c4e3 --- /dev/null +++ b/drivers/i2c/i2c_sc18is604_callback.c @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#include "i2c_sc18is604.h" + +/* + * Asynchronous message transfers are processed by a tracking structure + * being passed between 3 separate work items: One for initiating transfers, + * one for performing RX buffer readouts, and one for awaiting completion of + * one message being processed. + * + * These work items can both re-schedule themselves to wait on data or locks + * without blocking, and they schedule each other to progress message + * processing. + * + * On completion, or if unrecoverable errors occur, each work item can call + * out to a shared completion function, which propagates the final return + * value to the user provided callback. + * + * A driver instance wide lock ensures that only one packet of messages given + * to `i2c_transfer_cb` is processed at the same time. Multiple calls can still + * be made in succession, but it is not guaranteed that they will be processed + * in the order they are submitted. + */ + +/** Data structure for tracking a asynchronous I2C transfer. */ +typedef struct i2c_sc18is604_transfer_cb_work { + /** Work items performing the transfer. */ + struct k_work work_init_msg; + struct k_work work_buffer_readout; + struct k_work work_finish_msg; + /** Device performing this transfer. */ + const struct device *dev; + /** Whether this transfer holds the transaction lock. */ + bool owns_lock; + /** Signal used during processing to await individual message transfers. */ + struct k_poll_signal signal; + /** Index of the message currently being processed. */ + uint8_t msg_index; + /** Messages to be processed. */ + struct i2c_msg *msgs; + /** Number of messages to be processed. */ + uint8_t num_msgs; + /** I2C bus address the messages should be sent to. */ + uint16_t addr; + /** User callback to be called once all messages are processed. */ + i2c_callback_t cb; + /** User provided data for the callback. */ + void *userdata; +} transfer_work_t; + +/* + * Finish an asynchronous transfer with some result, + * cleaning up locks and resources. + */ +static void i2c_sc18is604_transfer_cb_process_done( + transfer_work_t *transfer_work, + int result) +{ + const struct device *dev = transfer_work->dev; + i2c_sc18is604_data_t * const data = dev->data; + + /* Release driver lock */ + k_sem_give(&data->lock); + + /* Free this work item */ + i2c_callback_t cb = transfer_work->cb; + void *userdata = transfer_work->userdata; + + k_free(transfer_work); + + /* Fire user callback */ + cb(dev, result, userdata); +} + +static void i2c_sc18is604_init_msg_read(transfer_work_t *transfer_work, + uint16_t addr, struct i2c_msg *msg) +{ + const struct device *dev = transfer_work->dev; + const i2c_sc18is604_config_t * const config = dev->config; + int ret = 0; + + /* Call asynchronous transfer function with our internal signal. */ + uint8_t cmd[] = {SC18IS604_CMD_READ_I2C, msg->len, (uint8_t) addr}; + + ret = mfd_sc18is604_transfer_signal(config->parent_dev, + cmd, ARRAY_SIZE(cmd), + NULL, 0, NULL, 0, + &transfer_work->signal); + if (ret != 0) { + mfd_sc18is604_release(config->parent_dev); + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } + + /* Schedule next work item to continue processing this message */ + ret = k_work_submit(&transfer_work->work_buffer_readout); + if (ret != 1 && ret != 0) { + mfd_sc18is604_release(config->parent_dev); + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } +} + +static void i2c_sc18is604_init_msg_write(transfer_work_t *transfer_work, + uint16_t addr, struct i2c_msg *msg) +{ + const struct device *dev = transfer_work->dev; + const i2c_sc18is604_config_t * const config = dev->config; + int ret = 0; + + uint8_t cmd[] = {SC18IS604_CMD_WRITE_I2C, msg->len, (uint8_t) addr}; + + /* Call asynchronous transfer function with our internal signal. */ + ret = mfd_sc18is604_transfer_signal(config->parent_dev, + cmd, ARRAY_SIZE(cmd), + msg->buf, msg->len, + NULL, 0, + &transfer_work->signal); + if (ret != 0) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } + + /* Schedule final work item to finish processing this message */ + ret = k_work_submit(&transfer_work->work_finish_msg); + if (ret != 1 && ret != 0) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } +} + +static void i2c_sc18is604_init_msg_work_fn(struct k_work *work) +{ + transfer_work_t *transfer_work = CONTAINER_OF(work, + transfer_work_t, work_init_msg); + const struct device *dev = transfer_work->dev; + i2c_sc18is604_data_t * const data = dev->data; + int ret = 0; + + /* Get device lock, or reschedule to retry later */ + if (!transfer_work->owns_lock) { + ret = k_sem_take(&data->lock, K_NO_WAIT); + if (ret != 0) { + ret = k_work_submit(work); + if (ret != 1 && ret != 0) { + i2c_sc18is604_transfer_cb_process_done( + transfer_work, -EIO); + return; + } + return; + } + } + + /* Make sure there is a message to transfer */ + uint8_t index = transfer_work->msg_index; + + if (index >= transfer_work->num_msgs) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, 0); + } + + /* + * Reset signals + */ + + /* Signals completion of communication *with* the device */ + k_poll_signal_reset(&transfer_work->signal); + /* Signals comletion of the command *by* the device */ + k_poll_signal_reset(&data->interrupt_signal); + + /* Begin processing the i-th message */ + struct i2c_msg *msg = &transfer_work->msgs[index]; + + /* Device doesn't support 10bit addressing */ + if ((msg->flags & I2C_MSG_ADDR_10_BITS) == I2C_MSG_ADDR_10_BITS) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EINVAL); + return; + } + + /* Add RW bit to address */ + uint16_t addr = transfer_work->addr; + + addr <<= 1; + addr |= (msg->flags & I2C_MSG_RW_MASK); + + /* Process message depending on type */ + if ((msg->flags & I2C_MSG_READ) == I2C_MSG_READ) { + i2c_sc18is604_init_msg_read(transfer_work, addr, msg); + return; + } + + /* If it's not a 'read' message, it must be a 'write' message */ + i2c_sc18is604_init_msg_write(transfer_work, addr, msg); +} + +static void i2c_sc18is604_buffer_readout_work_fn(struct k_work *work) +{ + transfer_work_t *transfer_work = CONTAINER_OF(work, + transfer_work_t, work_buffer_readout); + const struct device *dev = transfer_work->dev; + const i2c_sc18is604_config_t * const config = dev->config; + i2c_sc18is604_data_t * const data = dev->data; + int ret = 0; + + /* Await completion of the previous transfer */ + int result = 0; + int signaled = await_signal(&transfer_work->signal, &result, K_NO_WAIT); + + if (!signaled) { + ret = k_work_submit(work); + if (ret != 1 && ret != 0) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } + return; + } + + if (result != 0) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } + + /* Await interrupt signal */ + signaled = await_signal(&data->interrupt_signal, &result, K_NO_WAIT); + if (!signaled) { + /* + * We didn't reset the transfer signal, so we'll fall through + * to awaiting the interrupt again next time. + */ + ret = k_work_submit(work); + if (ret != 1 && ret != 0) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } + return; + } + + /* + * Check contents of I2C_STATUS register (propagated through + * interrupt signal) + */ + if (result != SC18IS604_I2C_STATUS_SUCCESS) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } + + /* Reset signal */ + k_poll_signal_reset(&transfer_work->signal); + + /* Begin readout of the received data from device buffer */ + uint8_t index = transfer_work->msg_index; + struct i2c_msg *msg = &transfer_work->msgs[index]; + + ret = mfd_sc18is604_read_buffer_signal(config->parent_dev, + msg->buf, msg->len, + &transfer_work->signal); + if (ret != 0) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } + + /* Schedule final work item to finish processing this message */ + ret = k_work_submit(&transfer_work->work_finish_msg); + if (ret != 1 && ret != 0) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } +} + +static void i2c_sc18is604_finish_msg_work_fn(struct k_work *work) +{ + transfer_work_t *transfer_work = CONTAINER_OF(work, + transfer_work_t, work_finish_msg); + const struct device *dev = transfer_work->dev; + i2c_sc18is604_data_t * const data = dev->data; + int ret = 0; + + int result = 0; + int signaled = await_signal(&transfer_work->signal, &result, K_NO_WAIT); + + if (!signaled) { + ret = k_work_submit(work); + if (ret != 1 && ret != 0) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } + return; + } + + /* + * For I2C_WRITE messages, we also need to await the interrupt signal. + * For I2C_READ messages, we're awaiting completion of an RX buffer + * readout, which doesn't require confirmation via interrupt. + */ + + uint8_t index = transfer_work->msg_index; + struct i2c_msg *msg = &transfer_work->msgs[index]; + + if ((msg->flags & I2C_MSG_READ) != I2C_MSG_READ) { + signaled = await_signal(&data->interrupt_signal, &result, K_NO_WAIT); + if (!signaled) { + ret = k_work_submit(work); + if (ret != 1 && ret != 0) { + i2c_sc18is604_transfer_cb_process_done( + transfer_work, -EIO); + return; + } + return; + } + + /* + * Check status read from I2C_STATUS (returned through interrupt + * signal). + */ + if (result != SC18IS604_I2C_STATUS_SUCCESS) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } + } + + /* We've finished processing this message. Check if there are more. */ + transfer_work->msg_index += 1; + if (transfer_work->msg_index >= transfer_work->num_msgs) { + /* We've successfully processed all messages */ + i2c_sc18is604_transfer_cb_process_done(transfer_work, 0); + return; + } + + /* Schedule work item to begin processing the next message */ + ret = k_work_submit(&transfer_work->work_init_msg); + if (ret != 1 && ret != 0) { + i2c_sc18is604_transfer_cb_process_done(transfer_work, -EIO); + return; + } +} + +int i2c_sc18is604_transfer_cb(const struct device *dev, + struct i2c_msg *msgs, uint8_t num_msgs, + uint16_t addr, + i2c_callback_t cb, void *userdata) +{ + int ret = 0; + + /* Create work item for tracking this transfer */ + transfer_work_t *transfer_work = k_calloc(1, sizeof(transfer_work_t)); + + if (transfer_work == NULL) { + return -ENOMEM; + } + + *transfer_work = (transfer_work_t) { + .dev = dev, + .owns_lock = false, + .msg_index = 0, + .msgs = msgs, + .num_msgs = num_msgs, + .addr = addr, + .cb = cb, + .userdata = userdata, + }; + k_work_init(&transfer_work->work_init_msg, + i2c_sc18is604_init_msg_work_fn); + k_work_init(&transfer_work->work_buffer_readout, + i2c_sc18is604_buffer_readout_work_fn); + k_work_init(&transfer_work->work_finish_msg, + i2c_sc18is604_finish_msg_work_fn); + k_poll_signal_init(&transfer_work->signal); + + ret = k_work_submit(&transfer_work->work_init_msg); + if (ret != 1 && ret != 0) { + return -EAGAIN; + } + + return 0; +} diff --git a/drivers/mfd/Kconfig.sc18is604 b/drivers/mfd/Kconfig.sc18is604 index da9e8c4680..2a36cd9144 100644 --- a/drivers/mfd/Kconfig.sc18is604 +++ b/drivers/mfd/Kconfig.sc18is604 @@ -19,6 +19,7 @@ config MFD_SC18IS604_INIT_PRIORITY config MFD_SC18IS604_ASYNC bool "Enable asynchronous transfer functions over SC18IS604 bus" + default y if I2C_SC18IS604 default n depends on POLL depends on DYNAMIC_THREAD diff --git a/dts/bindings/i2c/nxp,sc18is604-i2c.yaml b/dts/bindings/i2c/nxp,sc18is604-i2c.yaml new file mode 100644 index 0000000000..1b9f52966d --- /dev/null +++ b/dts/bindings/i2c/nxp,sc18is604-i2c.yaml @@ -0,0 +1,10 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +description: I2C controller part of an SC18IM604 bridge + +compatible: "nxp,sc18is604-i2c" + +include: [i2c-controller.yaml] + +on-bus: nxp,sc18is604 From d20266f840b66c01a9b38c4daad6839a9ee9ad50 Mon Sep 17 00:00:00 2001 From: Stephan Linz Date: Sat, 27 Jul 2024 18:40:56 +0200 Subject: [PATCH 4/7] snippets: tests: all drivers: mfd: add SC18IS604 Support the SC18IS604 chip on build all MFD driver test. Signed-off-by: Stephan Linz --- .../tstdrv-bldall-mfd-adj.conf | 2 ++ .../tstdrv-bldall-mfd-adj.overlay | 28 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/snippets/tstdrv-bldall-mfd-adj/tstdrv-bldall-mfd-adj.conf b/snippets/tstdrv-bldall-mfd-adj/tstdrv-bldall-mfd-adj.conf index e7683e6f37..c400b86a23 100644 --- a/snippets/tstdrv-bldall-mfd-adj/tstdrv-bldall-mfd-adj.conf +++ b/snippets/tstdrv-bldall-mfd-adj/tstdrv-bldall-mfd-adj.conf @@ -5,3 +5,5 @@ CONFIG_MFD=y CONFIG_I2C=y CONFIG_SPI=y CONFIG_SERIAL=y + +CONFIG_DYNAMIC_THREAD_POOL_SIZE=2 diff --git a/snippets/tstdrv-bldall-mfd-adj/tstdrv-bldall-mfd-adj.overlay b/snippets/tstdrv-bldall-mfd-adj/tstdrv-bldall-mfd-adj.overlay index 9e74169a11..042d5da043 100644 --- a/snippets/tstdrv-bldall-mfd-adj/tstdrv-bldall-mfd-adj.overlay +++ b/snippets/tstdrv-bldall-mfd-adj/tstdrv-bldall-mfd-adj.overlay @@ -59,7 +59,8 @@ clock-frequency = <2000000>; /* one entry for every devices */ - cs-gpios = <&test_gpio_adj 0 0>; + cs-gpios = <&test_gpio_adj 0 0>, + <&test_gpio_adj 0 0>; test_spi_sipo_mux_gp: sipo-mux-gp@0 { compatible = "sipo-mux-gp-spi"; @@ -94,6 +95,31 @@ offset = <32>; }; }; + + test_spi_sc18is604: sc18is604-spi@1 { + compatible = "nxp,sc18is604"; + status = "okay"; + reg = <0x1>; + spi-max-frequency = <650000>; + reset-gpios = <&test_gpio_adj 0 0>; + interrupt-gpios = <&test_gpio_adj 0 0>; + + test_spi_sc18is604_gpio: sc18is604-spi-gpio { + compatible = "nxp,sc18is604-gpio"; + status = "okay"; + gpio-controller; + #gpio-cells = <2>; + ngpios = <5>; + }; + + test_spi_sc18is604_i2c: sc18is604-spi-i2c { + compatible = "nxp,sc18is604-i2c"; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + clock-frequency = <100000>; + }; + }; }; test_uart_adj: uart@eeeeffff { From f97cda59a38598758e789474ae334fcdd49bcd21 Mon Sep 17 00:00:00 2001 From: Stephan Linz Date: Sat, 27 Jul 2024 19:01:49 +0200 Subject: [PATCH 5/7] snippets: tests: all drivers: gpio: add SC18IS604 Support the SC18IS604 chip on build all GPIO driver test. Signed-off-by: Stephan Linz --- .../tstdrv-bldall-gpio-adj.overlay | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/snippets/tstdrv-bldall-gpio-adj/tstdrv-bldall-gpio-adj.overlay b/snippets/tstdrv-bldall-gpio-adj/tstdrv-bldall-gpio-adj.overlay index c36315adad..7bd591c5e2 100644 --- a/snippets/tstdrv-bldall-gpio-adj/tstdrv-bldall-gpio-adj.overlay +++ b/snippets/tstdrv-bldall-gpio-adj/tstdrv-bldall-gpio-adj.overlay @@ -62,6 +62,7 @@ /* one entry for every devices */ cs-gpios = <&test_gpio_adj 0 0>, + <&test_gpio_adj 0 0>, <&test_gpio_adj 0 0>, <&test_gpio_adj 0 0>, <&test_gpio_adj 0 0>; @@ -243,6 +244,23 @@ offset = <24>; }; }; + + test_spi_sc18is604: sc18is604-spi@4 { + compatible = "nxp,sc18is604"; + status = "okay"; + reg = <0x4>; + spi-max-frequency = <650000>; + reset-gpios = <&test_gpio_adj 0 0>; + interrupt-gpios = <&test_gpio_adj 0 0>; + + test_spi_sc18is604_gpio: sc18is604-spi-gpio { + compatible = "nxp,sc18is604-gpio"; + status = "okay"; + gpio-controller; + #gpio-cells = <2>; + ngpios = <5>; + }; + }; }; test_uart_adj: uart@eeeeffff { From 3596523a4380fb88549c56023a0faef0c6c36750 Mon Sep 17 00:00:00 2001 From: Stephan Linz Date: Sat, 27 Jul 2024 19:02:23 +0200 Subject: [PATCH 6/7] snippets: tests: all drivers: i2c: add SC18IS604 Support the SC18IS604 chip on build all I2C driver test. Signed-off-by: Stephan Linz --- .../tstdrv-bldall-i2c-adj.conf | 7 +++++++ .../tstdrv-bldall-i2c-adj.overlay | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/snippets/tstdrv-bldall-i2c-adj/tstdrv-bldall-i2c-adj.conf b/snippets/tstdrv-bldall-i2c-adj/tstdrv-bldall-i2c-adj.conf index e9ca22677a..6c678ce6b9 100644 --- a/snippets/tstdrv-bldall-i2c-adj/tstdrv-bldall-i2c-adj.conf +++ b/snippets/tstdrv-bldall-i2c-adj/tstdrv-bldall-i2c-adj.conf @@ -1,2 +1,9 @@ # Copyright (c) 2024 TiaC Systems # SPDX-License-Identifier: Apache-2.0 + +CONFIG_MFD=y +CONFIG_I2C=y +CONFIG_SPI=y +CONFIG_GPIO=y + +CONFIG_DYNAMIC_THREAD_POOL_SIZE=2 diff --git a/snippets/tstdrv-bldall-i2c-adj/tstdrv-bldall-i2c-adj.overlay b/snippets/tstdrv-bldall-i2c-adj/tstdrv-bldall-i2c-adj.overlay index deff50c02c..aafa041233 100644 --- a/snippets/tstdrv-bldall-i2c-adj/tstdrv-bldall-i2c-adj.overlay +++ b/snippets/tstdrv-bldall-i2c-adj/tstdrv-bldall-i2c-adj.overlay @@ -44,7 +44,24 @@ clock-frequency = <2000000>; /* one entry for every devices */ - /* cs-gpios = <&test_gpio_adj 0 0>; */ + cs-gpios = <&test_gpio_adj 0 0>; + + test_spi_sc18is604: sc18is604-spi@0 { + compatible = "nxp,sc18is604"; + status = "okay"; + reg = <0x0>; + spi-max-frequency = <650000>; + reset-gpios = <&test_gpio_adj 0 0>; + interrupt-gpios = <&test_gpio_adj 0 0>; + + test_spi_sc18is604_i2c: sc18is604-spi-i2c { + compatible = "nxp,sc18is604-i2c"; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + clock-frequency = <100000>; + }; + }; }; test_uart_adj: uart@ddddeeee { From 8b08123d6e240e887a5b0fc68af6688516520974 Mon Sep 17 00:00:00 2001 From: Sarah Renkhoff Date: Tue, 9 Jul 2024 11:33:51 +0200 Subject: [PATCH 7/7] doc: update release notes Add github issue resolved by the previous commits. Signed-off-by: Sarah Renkhoff Signed-off-by: Stephan Linz --- doc/bridle/releases/release-notes-3.7.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/bridle/releases/release-notes-3.7.0.rst b/doc/bridle/releases/release-notes-3.7.0.rst index 0ab40c4d28..fea36f58bc 100644 --- a/doc/bridle/releases/release-notes-3.7.0.rst +++ b/doc/bridle/releases/release-notes-3.7.0.rst @@ -240,6 +240,7 @@ These GitHub issues were addressed since project bootstrapping: * :github:`254` - [FCR] Bump to Zephyr v3.7 * :github:`252` - [FCR] Upgrade to Zephyr SDK 0.16.8 +* :github:`247` - [HW] NXP SC18IS604 SPI to I2C bridge * :github:`244` - [HW] Spotpear Raspberry Pi Pico LCD Modules as Shields * :github:`242` - [HW] 52Pi (GeeekPi) Pico Breadboard Kit -/Plus (EP-0164/0172) * :github:`239` - [HW] PiMoroni Raspberry Pi Pico LCD Modules as Shields