diff --git a/doc/bridle/releases/release-notes-3.7.0.rst b/doc/bridle/releases/release-notes-3.7.0.rst index fea36f58bc..78f516d0ec 100644 --- a/doc/bridle/releases/release-notes-3.7.0.rst +++ b/doc/bridle/releases/release-notes-3.7.0.rst @@ -241,6 +241,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:`246` - [HW] NXP SC16IS75x series I2C/SPI to UART/GPIO 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 diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 56a4fb07fc..3ea1bab476 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -8,3 +8,4 @@ add_subdirectory_ifdef(CONFIG_I2C i2c) add_subdirectory_ifdef(CONFIG_MFD mfd) add_subdirectory_ifdef(CONFIG_RTC rtc) add_subdirectory_ifdef(CONFIG_SENSOR sensor) +add_subdirectory_ifdef(CONFIG_SERIAL serial) diff --git a/drivers/Kconfig b/drivers/Kconfig index 76563505d1..a974a270ad 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -10,5 +10,6 @@ rsource "i2c/Kconfig" rsource "mfd/Kconfig" rsource "rtc/Kconfig" rsource "sensor/Kconfig" +rsource "serial/Kconfig" endmenu diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt index 9e8a407e8b..f2a9e2e4e2 100644 --- a/drivers/gpio/CMakeLists.txt +++ b/drivers/gpio/CMakeLists.txt @@ -8,5 +8,6 @@ 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_SC16IS75X gpio_sc16is75x.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 df977c92cb..da81870188 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -5,6 +5,7 @@ if GPIO rsource "Kconfig.pca9554" rsource "Kconfig.pca9555" +rsource "Kconfig.sc16is75x" rsource "Kconfig.sc18is604" rsource "Kconfig.sipomux" diff --git a/drivers/gpio/Kconfig.sc16is75x b/drivers/gpio/Kconfig.sc16is75x new file mode 100644 index 0000000000..afc027a377 --- /dev/null +++ b/drivers/gpio/Kconfig.sc16is75x @@ -0,0 +1,27 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +menuconfig GPIO_SC16IS75X + bool "NXP SC16IS75x GPIO driver" + default y + depends on DT_HAS_NXP_SC16IS75X_GPIO_ENABLED + depends on MFD_SC16IS75X + help + Enable driver for GPIO controller part of an SC16IS75x bridge. + +if GPIO_SC16IS75X + +config GPIO_SC16IS75X_INIT_PRIORITY + int "Init priority" + default 85 + help + Device driver initialization priority. + +config GPIO_SC16IS75X_INTERRUPTS + bool "Interrupt support" + default y + depends on MFD_SC16IS75X_INTERRUPTS + help + Enable interrupt support on SC16IS75x GPIO controllers. + +endif # GPIO_SC16IS75X diff --git a/drivers/gpio/gpio_sc16is75x.c b/drivers/gpio/gpio_sc16is75x.c new file mode 100644 index 0000000000..fd4b8a3a1d --- /dev/null +++ b/drivers/gpio/gpio_sc16is75x.c @@ -0,0 +1,579 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_sc16is75x_gpio + +#include +#include +#include + +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(gpio_sc16is75x, CONFIG_GPIO_LOG_LEVEL); + +/** + * @brief SC16IS75X GPIO controller configuration data + * + * @a num_pins must be in the range [1, @ref SC16IS75X_IO_NUM_PINS_MAX]. + * + * This structure contains all of the state for a given SC16IS75X GPIO + * controller as well as the binding to related MFD device. + */ +struct gpio_sc16is75x_config { + /** Common @ref gpio_driver_config (needs to be first) */ + struct gpio_driver_config common; + /** Backend MFD (bridge) device for real operations on hardware */ + const struct device *bus; + /** Number of pins available in the given GPIO controller instance */ + const gpio_pin_t num_pins; +}; + +#ifdef CONFIG_GPIO_SC16IS75X_INTERRUPTS + +/** + * @brief SC16IS75X cache data for interrupt configuration and emulation + * + * The SC16IS75X can only detect signal changes, level-controlled + * interrupts are therefore not possible. The driver emulates these + * in order to support all possible types of interrupt configuration + * from the GPIO API. This requires internal cached data per GPIO pin + * for the calculation of edge and level triggered events. + */ +struct gpio_sc16is75x_interrupts { + uint8_t enabled; /**< pin is event source */ + uint8_t edge_rising; /**< pin should trigger on rising edge */ + uint8_t edge_falling; /**< pin should trigger on falling edge */ + uint8_t level_high; /**< pin should trigger on high level */ + uint8_t level_low; /**< pin should trigger on low level */ +}; + +#endif /* CONFIG_GPIO_SC16IS75X_INTERRUPTS */ + +/** + * @brief SC16IS75X GPIO controller data + * + * This structure contains data structures used by a SC16IS75X GPIO controller. + * + * Changes to @ref gpio_sc16is75x_data and @ref gpio_sc16is75x_config and also + * multi-transfer bus transactions are synchronized using @a k_mutex. + */ +struct gpio_sc16is75x_data { + /** Common @ref gpio_driver_data (needs to be first) */ + struct gpio_driver_data common; + /** Device self-reference for the interruption treatment */ + const struct device *self; + /** MFD bridge interrupt callback */ + struct gpio_callback interrupt_cb; + /** MFD bridge interrupt worker */ + struct k_work interrupt_work; + /** Lock for synchronizing accesses to driver data and config */ + struct k_mutex lock; + /** GPIO pin direction */ + uint8_t pin_dir; + /** GPIO pin level state */ + uint8_t pin_state; +#ifdef CONFIG_GPIO_SC16IS75X_INTERRUPTS + /** GPIO pin interrupt trigger */ + struct gpio_sc16is75x_interrupts pin_irq; + /** GPIO port interrupt callback list */ + sys_slist_t callbacks; +#endif /* CONFIG_GPIO_SC16IS75X_INTERRUPTS */ +}; + +static inline uint8_t gpio_sc16is75x_cached_pin_dir(const struct device *dev) +{ + struct gpio_sc16is75x_data * const data = dev->data; + uint8_t pin_dir; + + k_mutex_lock(&data->lock, K_FOREVER); + pin_dir = data->pin_dir; + k_mutex_unlock(&data->lock); + + return pin_dir; +} + +static inline int gpio_sc16is75x_write_pin_dir(const struct device *dev, + const uint8_t dir) +{ + const struct gpio_sc16is75x_config * const config = dev->config; + struct gpio_sc16is75x_data * const data = dev->data; + int ret = 0; + + k_mutex_lock(&data->lock, K_FOREVER); + ret = WRITE_SC16IS75X_REG(config->bus, IODIR, dir); + if (ret == 0) { + data->pin_dir = dir; + } + k_mutex_unlock(&data->lock); + + return ret; +} + +static inline int gpio_sc16is75x_read_pin_dir(const struct device *dev, + uint8_t *dir) +{ + const struct gpio_sc16is75x_config * const config = dev->config; + struct gpio_sc16is75x_data * const data = dev->data; + int ret = 0; + + k_mutex_lock(&data->lock, K_FOREVER); + ret = READ_SC16IS75X_REG(config->bus, IODIR, dir); + if (ret == 0) { + data->pin_dir = *dir; + } + k_mutex_unlock(&data->lock); + + return ret; +} + +static inline uint8_t gpio_sc16is75x_cached_pin_state(const struct device *dev) +{ + struct gpio_sc16is75x_data * const data = dev->data; + uint8_t pin_state; + + k_mutex_lock(&data->lock, K_FOREVER); + pin_state = data->pin_state; + k_mutex_unlock(&data->lock); + + return pin_state; +} + +static inline int gpio_sc16is75x_write_pin_state(const struct device *dev, + uint8_t state) +{ + const struct gpio_sc16is75x_config * const config = dev->config; + struct gpio_sc16is75x_data * const data = dev->data; + int ret = 0; + + k_mutex_lock(&data->lock, K_FOREVER); + ret = WRITE_SC16IS75X_REG(config->bus, IOSTATE, state); + if (ret == 0) { + data->pin_state = state; + } + k_mutex_unlock(&data->lock); + + return ret; +} + +static inline int gpio_sc16is75x_read_pin_state(const struct device *dev, + uint8_t *state) +{ + const struct gpio_sc16is75x_config * const config = dev->config; + struct gpio_sc16is75x_data * const data = dev->data; + int ret = 0; + + k_mutex_lock(&data->lock, K_FOREVER); + ret = READ_SC16IS75X_REG(config->bus, IOSTATE, state); + if (ret == 0) { + data->pin_state = *state; + } + k_mutex_unlock(&data->lock); + + return ret; +} + + +static int gpio_sc16is75x_pin_configure(const struct device *dev, + gpio_pin_t pin, gpio_flags_t flags) +{ + const struct gpio_sc16is75x_config * const config = dev->config; + uint8_t dir, state; + int ret = 0; + + /* Can't do bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + /* Check for invalid pin number */ + if ((BIT(pin) & config->common.port_pin_mask) == 0) { + return -EINVAL; + } + + /* Does not support open source/drain pin */ + if (flags & (GPIO_OPEN_SOURCE | GPIO_OPEN_DRAIN)) { + return -ENOTSUP; + } + /* Does not support pull-up/pull-down pin resistor */ + if (flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) { + return -ENOTSUP; + } + + /* Does not support disconnected pin */ + if ((flags & (GPIO_INPUT | GPIO_OUTPUT)) == GPIO_DISCONNECTED) { + return -ENOTSUP; + } + + /* Get (cached) port direction and state */ + dir = gpio_sc16is75x_cached_pin_dir(dev); + state = gpio_sc16is75x_cached_pin_state(dev); + + /* Calculate new pin direction and state */ + if (flags & GPIO_OUTPUT) { + dir |= BIT(pin); /* set direction bit */ + if (flags & GPIO_OUTPUT_INIT_HIGH) { + state |= BIT(pin); /* set state bit */ + } else if (flags & GPIO_OUTPUT_INIT_LOW) { + state &= ~BIT(pin); /* clear state bit */ + } + } else if (flags & GPIO_INPUT) { + dir &= ~BIT(pin); /* clear direction bit */ + } else { + return -EINVAL; + } + + /* Set (cached) port direction to new value */ + if (gpio_sc16is75x_cached_pin_dir(dev) != dir) { + ret = gpio_sc16is75x_write_pin_dir(dev, dir); + if (ret != 0) { + goto end; + } + } + + /* Set (cached) port state to new value */ + if (gpio_sc16is75x_cached_pin_state(dev) != state) { + ret = gpio_sc16is75x_write_pin_state(dev, state); + if (ret != 0) { + goto end; + } + } + +end: + return ret; +} + +static int gpio_sc16is75x_port_get_raw(const struct device *dev, + gpio_port_value_t *value) +{ + uint8_t * const state = (uint8_t *)value; + + /* Can't do bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + /* Read current state from device */ + return gpio_sc16is75x_read_pin_state(dev, state); +} + +static int gpio_sc16is75x_port_set_masked_raw(const struct device *dev, + const gpio_port_pins_t mask, + const gpio_port_value_t value) +{ + uint8_t state = gpio_sc16is75x_cached_pin_state(dev); + + /* Can't do bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + /* Get (cached) port state, and set bits according to pin mask */ + state = (state & ~mask) | (mask & value); + + /* Apply the new pin state */ + return gpio_sc16is75x_write_pin_state(dev, state); +} + +static int gpio_sc16is75x_port_set_bits_raw(const struct device *dev, + const gpio_port_pins_t pins) +{ + return gpio_sc16is75x_port_set_masked_raw(dev, pins, pins); +} + +static int gpio_sc16is75x_port_clear_bits_raw(const struct device *dev, + const gpio_port_pins_t pins) +{ + return gpio_sc16is75x_port_set_masked_raw(dev, pins, 0); +} + +static int gpio_sc16is75x_port_toggle_bits(const struct device *dev, + const gpio_port_pins_t pins) +{ + uint8_t state = gpio_sc16is75x_cached_pin_state(dev); + + /* Can't do bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + /* Get (cached) state, and toggle bits according to mask */ + state ^= pins; + + /* Apply new pin state */ + return gpio_sc16is75x_write_pin_state(dev, state); +} + +#ifdef CONFIG_GPIO_SC16IS75X_INTERRUPTS + +static inline int gpio_sc16is75x_write_pin_irq(const struct device *dev, + const gpio_pin_t pin, + const enum gpio_int_mode mode, + const enum gpio_int_trig trig) +{ + const struct gpio_sc16is75x_config * const config = dev->config; + struct gpio_sc16is75x_data * const data = dev->data; + struct gpio_sc16is75x_interrupts * const irq = &data->pin_irq; + int ret = 0; + + /* Update interrupt masks */ + bool enabled = !(mode & GPIO_INT_MODE_DISABLED); + bool edge = (mode == GPIO_INT_MODE_EDGE); + bool level = (mode == GPIO_INT_MODE_LEVEL); + bool high = ((trig & GPIO_INT_TRIG_HIGH) == GPIO_INT_TRIG_HIGH); + bool low = ((trig & GPIO_INT_TRIG_LOW) == GPIO_INT_TRIG_LOW); + + k_mutex_lock(&data->lock, K_FOREVER); + WRITE_BIT(irq->enabled, pin, enabled); + WRITE_BIT(irq->edge_rising, pin, enabled && edge && high); + WRITE_BIT(irq->edge_falling, pin, enabled && edge && low); + WRITE_BIT(irq->level_high, pin, enabled && level && high); + WRITE_BIT(irq->level_low, pin, enabled && level && low); + ret = WRITE_SC16IS75X_REG(config->bus, IOINTENA, irq->enabled); + k_mutex_unlock(&data->lock); + + return ret; +} + +static inline gpio_port_pins_t gpio_sc16is75x_trig_edge( + const struct device *dev, + const gpio_port_pins_t changed_pins, + const gpio_port_pins_t new_state) +{ + struct gpio_sc16is75x_data * const data = dev->data; + struct gpio_sc16is75x_interrupts * const irq = &data->pin_irq; + gpio_port_pins_t trig_edge; + + k_mutex_lock(&data->lock, K_FOREVER); + trig_edge = (changed_pins & new_state & irq->edge_rising) + | (changed_pins & ~new_state & irq->edge_falling); + k_mutex_unlock(&data->lock); + + return trig_edge; +} + +static inline gpio_port_pins_t gpio_sc16is75x_trig_level( + const struct device *dev, + const gpio_port_pins_t changed_pins, + const gpio_port_pins_t new_state) +{ + struct gpio_sc16is75x_data * const data = dev->data; + struct gpio_sc16is75x_interrupts * const irq = &data->pin_irq; + gpio_port_pins_t trig_level; + + k_mutex_lock(&data->lock, K_FOREVER); + trig_level = (changed_pins & new_state & irq->level_high) + | (changed_pins & ~new_state & irq->level_low); + k_mutex_unlock(&data->lock); + + return trig_level; +} + +static void gpio_sc16is75x_interrupt_work_fn(struct k_work *work) +{ + struct gpio_sc16is75x_data * const data = CONTAINER_OF(work, + struct gpio_sc16is75x_data, interrupt_work); + const struct device * const dev = data->self; + gpio_port_pins_t changed_pins, triggered_pins; + gpio_port_pins_t triggered_edge, triggered_level; + uint8_t cur_state, new_state; + int ret = 0; + + /* + * Read out input values and compare with previously cached values. + * Note that reading the inputs clears the interrupt. + */ + cur_state = gpio_sc16is75x_cached_pin_state(dev); + new_state = 0; + + ret = gpio_sc16is75x_read_pin_state(dev, &new_state); + if (ret != 0) { + return; + } + + changed_pins = (cur_state ^ new_state); + + /* Calculate trigger sources */ + triggered_edge = gpio_sc16is75x_trig_edge(dev, changed_pins, new_state); + triggered_level = gpio_sc16is75x_trig_level(dev, changed_pins, new_state); + + /* Propagate triggered pins */ + triggered_pins = (triggered_edge | triggered_level); + if (triggered_pins) { + gpio_fire_callbacks(&data->callbacks, data->self, triggered_pins); + } + + /* Emulate level triggering */ + if (triggered_level) { + k_work_submit(&data->interrupt_work); /* Reschedule self */ + } +} + +static void gpio_sc16is75x_interrupt_callback(const struct device *dev, + struct gpio_callback *cb, + gpio_port_pins_t event_pins) +{ + /* + * The emulation of level-triggered interrupts requires + * an automatic rescheduling in the worker queue. The work + * item is therefore scheduled immediately for all entries. + */ + struct gpio_sc16is75x_data * const data = CONTAINER_OF(cb, + struct gpio_sc16is75x_data, interrupt_cb); + + k_work_submit(&data->interrupt_work); +} + + +static int gpio_sc16is75x_pin_interrupt_configure(const struct device *dev, + const gpio_pin_t pin, + const enum gpio_int_mode mode, + const enum gpio_int_trig trig) +{ + const struct gpio_sc16is75x_config * const config = dev->config; + + /* Check for invalid pin number */ + if ((BIT(pin) & config->common.port_pin_mask) == 0) { + return -EINVAL; + } + + /* Confirm that specific pin is configured as input */ + if (BIT(pin) & gpio_sc16is75x_cached_pin_dir(dev)) { + /* setted bit means output, thus not supported */ + return -ENOTSUP; + } + + return gpio_sc16is75x_write_pin_irq(dev, pin, mode, trig); +} + +static int gpio_sc16is75x_manage_callback(const struct device *dev, + struct gpio_callback *callback, + const bool set) +{ + struct gpio_sc16is75x_data * const data = dev->data; + + return gpio_manage_callback(&data->callbacks, callback, set); +} + +#endif /* CONFIG_GPIO_SC16IS75X_INTERRUPTS */ + +static const struct gpio_driver_api gpio_sc16is75x_api = { + .pin_configure = gpio_sc16is75x_pin_configure, + .port_get_raw = gpio_sc16is75x_port_get_raw, + .port_set_masked_raw = gpio_sc16is75x_port_set_masked_raw, + .port_set_bits_raw = gpio_sc16is75x_port_set_bits_raw, + .port_clear_bits_raw = gpio_sc16is75x_port_clear_bits_raw, + .port_toggle_bits = gpio_sc16is75x_port_toggle_bits, +#ifdef CONFIG_GPIO_SC16IS75X_INTERRUPTS + .pin_interrupt_configure = gpio_sc16is75x_pin_interrupt_configure, + .manage_callback = gpio_sc16is75x_manage_callback, +#endif /* CONFIG_GPIO_SC16IS75X_INTERRUPTS */ +}; + + +static int gpio_sc16is75x_init(const struct device *dev) +{ + const struct gpio_sc16is75x_config * const config = dev->config; + struct gpio_sc16is75x_data * const data = dev->data; + int ret = 0; + + /* Confirm (MFD) bridge readiness */ + if (!device_is_ready(config->bus)) { + LOG_ERR("%s: bridge device %s not ready", + dev->name, config->bus->name); + return -ENODEV; + } + + /* Initialize synchronization lock */ + k_mutex_init(&data->lock); + + /* Initialize register cache for interrupt handling */ + ret = gpio_sc16is75x_read_pin_dir(dev, &data->pin_dir); + if (ret != 0) { + LOG_ERR("%s: read pin directions failed: %d", + dev->name, ret); + goto end; + } + + ret = gpio_sc16is75x_read_pin_state(dev, &data->pin_state); + if (ret != 0) { + LOG_ERR("%s: read pin state failed: %d", + dev->name, ret); + goto end; + } + +#ifdef CONFIG_GPIO_SC16IS75X_INTERRUPTS + + /* Set up interrupt handling */ + + /* Create back-reference to acces device from handler */ + data->self = dev; + + /* Set up handler work item */ + k_work_init(&data->interrupt_work, gpio_sc16is75x_interrupt_work_fn); + + /* Set up and register our own callback on our parent device */ + gpio_init_callback(&data->interrupt_cb, + gpio_sc16is75x_interrupt_callback, + BIT(SC16IS75X_EVENT_IO0_STATE) + | BIT(SC16IS75X_EVENT_IO1_STATE)); + + ret = mfd_sc16is75x_add_callback(config->bus, &data->interrupt_cb); + if (ret != 0) { + LOG_ERR("%s: register interrupt callback failed: %d", + dev->name, ret); + goto end; + } + +#endif /* CONFIG_GPIO_SC16IS75X_INTERRUPTS */ + + LOG_DBG("%s: ready for %u pins with bridge backend over %s!", + dev->name, config->num_pins, config->bus->name); + +end: + return ret; +} + +#ifdef CONFIG_PM_DEVICE +static int gpio_sc16is75x_pm_device_pm_action(const struct device *dev, + enum pm_device_action action) +{ + ARG_UNUSED(dev); + ARG_UNUSED(action); + + return 0; +} +#endif + +#define GPIO_SC16IS75X_DEFINE(inst) \ + \ + static struct gpio_sc16is75x_config gpio_sc16is75x_config_##inst = \ + { \ + .common = { \ + .port_pin_mask = \ + GPIO_PORT_PIN_MASK_FROM_DT_INST(inst), \ + }, \ + .bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \ + .num_pins = DT_INST_PROP(inst, ngpios), \ + }; \ + BUILD_ASSERT( \ + DT_INST_PROP(inst, ngpios) <= SC16IS75X_IO_NUM_PINS_MAX, \ + "Too many ngpios"); \ + \ + static struct gpio_sc16is75x_data gpio_sc16is75x_data_##inst; \ + \ + PM_DEVICE_DT_INST_DEFINE(inst, gpio_sc16is75x_pm_device_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(inst, gpio_sc16is75x_init, \ + PM_DEVICE_DT_INST_GET(inst), \ + &gpio_sc16is75x_data_##inst, \ + &gpio_sc16is75x_config_##inst, POST_KERNEL, \ + CONFIG_GPIO_SC16IS75X_INIT_PRIORITY, \ + &gpio_sc16is75x_api); + +DT_INST_FOREACH_STATUS_OKAY(GPIO_SC16IS75X_DEFINE); diff --git a/drivers/mfd/CMakeLists.txt b/drivers/mfd/CMakeLists.txt index f1830874f3..34e1b11dc0 100644 --- a/drivers/mfd/CMakeLists.txt +++ b/drivers/mfd/CMakeLists.txt @@ -7,6 +7,10 @@ zephyr_library_amend() zephyr_library_sources_ifdef(CONFIG_MFD_DS3231 mfd_ds3231.c) +zephyr_library_sources_ifdef(CONFIG_MFD_SC16IS75X mfd_sc16is75x.c) +zephyr_library_sources_ifdef(CONFIG_MFD_SC16IS75X_SPI mfd_sc16is75x_spi.c) +zephyr_library_sources_ifdef(CONFIG_MFD_SC16IS75X_I2C mfd_sc16is75x_i2c.c) +zephyr_library_sources_ifdef(CONFIG_MFD_SC16IS75X_ASWQ mfd_sc16is75x_async.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) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c844a35394..1b6bfb12ec 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -4,6 +4,7 @@ if MFD rsource "Kconfig.ds3231" +rsource "Kconfig.sc16is75x" rsource "Kconfig.sc18is604" rsource "Kconfig.sipomuxgp" diff --git a/drivers/mfd/Kconfig.sc16is75x b/drivers/mfd/Kconfig.sc16is75x new file mode 100644 index 0000000000..98a610a0d5 --- /dev/null +++ b/drivers/mfd/Kconfig.sc16is75x @@ -0,0 +1,108 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +menuconfig MFD_SC16IS75X + bool "NXP SC16IS75x SPI/I2C to UART & GPIO bridge" + default y + depends on DT_HAS_NXP_SC16IS75X_ENABLED + depends on MFD + help + Enable the NXP SC16IS75x bridge multi-function device driver. + +if MFD_SC16IS75X + +# Workaround for not being able to have commas in macro arguments +DT_COMPAT_NXP_SC16IS75X := nxp,sc16is75x +DT_NXP_SC16IS75X_HAS_IRQ := $(dt_compat_any_has_prop,$(DT_COMPAT_NXP_SC16IS75X),interrupt-gpios) + +config MFD_SC16IS75X_ASYNC_NEEDS_WORKQUEUE + bool + default y if MFD_SC16IS75X_SPI && !SPI_ASYNC + default y if MFD_SC16IS75X_I2C && !I2C_CALLBACK + default n + +if MFD_SC16IS75X_ASYNC_NEEDS_WORKQUEUE + +config MFD_SC16IS75X_ASYNC + bool + default n + depends on POLL + depends on DYNAMIC_THREAD + +config MFD_SC16IS75X_INTERRUPTS + bool "Interrupt support" + default y if $(DT_NXP_SC16IS75X_HAS_IRQ) + default n + depends on GPIO + select MFD_SC16IS75X_ASYNC + select POLL # dependency of MFD_SC16IS75X_ASYNC + select DYNAMIC_THREAD # dependency of MFD_SC16IS75X_ASWQ + select THREAD_STACK_INFO # dependency of DYNAMIC_THREAD + help + Enable interrupts on SC16IS75x bridges. + +endif # MFD_SC16IS75X_ASYNC_NEEDS_WORKQUEUE + +if !MFD_SC16IS75X_ASYNC_NEEDS_WORKQUEUE + +config MFD_SC16IS75X_ASYNC + bool + default n + depends on POLL + +config MFD_SC16IS75X_INTERRUPTS + bool "Interrupt support" + default y if $(DT_NXP_SC16IS75X_HAS_IRQ) + default n + depends on GPIO + select MFD_SC16IS75X_ASYNC + select POLL # dependency of MFD_SC16IS75X_ASYNC + help + Enable interrupts on SC16IS75x bridges. + +endif # !MFD_SC16IS75X_ASYNC_NEEDS_WORKQUEUE + +config MFD_SC16IS75X_SPI + bool "NXP SC16IS75x SPI bus support" + default y + depends on $(dt_compat_on_bus,$(DT_COMPAT_NXP_SC16IS75X),spi) + depends on SPI + help + Enable the SPI bus transfer functions for SC16IS75x. + +config MFD_SC16IS75X_I2C + bool "NXP SC16IS75x I2C bus support" + default y + depends on $(dt_compat_on_bus,$(DT_COMPAT_NXP_SC16IS75X),i2c) + depends on I2C + help + Enable the I2C bus transfer functions for SC16IS75x. + +config MFD_SC16IS75X_INIT_PRIORITY + int "Init priority" + default 55 if UART_SC16IS75X + default MFD_INIT_PRIORITY + help + Multi-function device initialization priority for SC16IS75x. + +if MFD_SC16IS75X_ASYNC + +config HEAP_MEM_POOL_ADD_SIZE_MFD_SC16IS75X + int + default 128 # Enough for 16 concurrent transfers (k_calloc) + depends on MFD_SC16IS75X_ASYNC + +config MFD_SC16IS75X_ASWQ + bool + default y if MFD_SC16IS75X_ASYNC && MFD_SC16IS75X_ASYNC_NEEDS_WORKQUEUE + default n + depends on DYNAMIC_THREAD + +config MFD_SC16IS75X_WORKQUEUE_STACK_SIZE + int + default 512 + depends on MFD_SC16IS75X_ASWQ + +endif # MFD_SC16IS75X_ASYNC + +endif # MFD_SC16IS75X diff --git a/drivers/mfd/mfd_sc16is75x.c b/drivers/mfd/mfd_sc16is75x.c new file mode 100644 index 0000000000..445919ed8d --- /dev/null +++ b/drivers/mfd/mfd_sc16is75x.c @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include + +#include "mfd_sc16is75x.h" + +#include +LOG_MODULE_REGISTER(mfd_sc16is75x, CONFIG_MFD_LOG_LEVEL); + +#if defined(CONFIG_MFD_SC16IS75X_ASWQ) \ + && (!CONFIG_DYNAMIC_THREAD_POOL_SIZE && !defined(CONFIG_DYNAMIC_THREAD_ALLOC)) +#error "SC16IS75x MFD driver requires either CONFIG_DYNAMIC_THREAD_POOL_SIZE>0" \ + "or CONFIG_DYNAMIC_THREAD_ALLOC" +#endif + +#define SC16IS75X_SA_RD true +#define SC16IS75X_SA_WR false + +static inline int mfd_sc16is75x_sub_address(const bool read, + const uint8_t reg, + const uint8_t channel, + uint8_t *sub_address) +{ + /* + * Subaddress scheme: [ R/W, A3, A2, A1, A0, CH1, Ch0, X] + * R/W: 1: Read, 0: Write (SPI only) + * A[3:0]: Register address + * CH1, CH0: Select serial channel + * X: Unused + */ + + if ((reg & BIT_MASK(4)) > BIT_MASK(4)) { + return -EINVAL; + } + + if ((channel & BIT_MASK(2)) > BIT_MASK(2)) { + return -EINVAL; + } + + *sub_address = 0; + *sub_address |= (channel & BIT_MASK(2)) << 1; + *sub_address |= (reg & BIT_MASK(4)) << 3; + WRITE_BIT(*sub_address, 7, read ? 1 : 0); + + return 0; +} + +static int mfd_sc16is75x_read(const struct device *dev, + const uint8_t channel, const uint8_t reg, + uint8_t *buf, const size_t len) +{ + struct mfd_sc16is75x_data * const data = dev->data; + uint8_t sub_address = 0; + int ret = 0; + + ret = mfd_sc16is75x_sub_address(SC16IS75X_SA_RD, reg, channel, + &sub_address); + if (ret != 0) { + return ret; + } + + return data->transfer_function->read_raw(dev, sub_address, buf, len); +} + +#define READ_SC16IS75X_CHANNEL(dev, ch, reg, buf, len) \ + mfd_sc16is75x_read((dev), (ch), SC16IS75X_REG_##reg, (buf), (len)); + +static int mfd_sc16is75x_write(const struct device *dev, + const uint8_t channel, const uint8_t reg, + const uint8_t *buf, const size_t len) +{ + struct mfd_sc16is75x_data * const data = dev->data; + uint8_t sub_address = 0; + int ret = 0; + + ret = mfd_sc16is75x_sub_address(SC16IS75X_SA_WR, reg, channel, + &sub_address); + if (ret != 0) { + return ret; + } + + return data->transfer_function->write_raw(dev, sub_address, buf, len); +} + +#define WRITE_SC16IS75X_CHANNEL(dev, ch, reg, buf, len) \ + mfd_sc16is75x_write((dev), (ch), SC16IS75X_REG_##reg, (buf), (len)); + +int mfd_sc16is75x_read_register(const struct device *dev, + const uint8_t channel, + const uint8_t reg, uint8_t *value) +{ + return mfd_sc16is75x_read(dev, channel, reg, value, 1); +} + +int mfd_sc16is75x_write_register(const struct device *dev, + const uint8_t channel, + const uint8_t reg, const uint8_t value) +{ + return mfd_sc16is75x_write(dev, channel, reg, &value, 1); +} + +int mfd_sc16is75x_set_register_bit(const struct device *dev, + const uint8_t channel, const uint8_t reg, + const uint8_t bit, const bool value) +{ + struct mfd_sc16is75x_data * const data = dev->data; + uint8_t reg_val = 0; + int ret = 0; + + /* Lock device before multi-transfer transaction */ + k_mutex_lock(&data->transaction_lock, K_FOREVER); + + /* Read out current value */ + ret = mfd_sc16is75x_read(dev, channel, reg, ®_val, 1); + if (ret != 0) { + goto end; + } + + /* Set selected bit */ + WRITE_BIT(reg_val, bit, value); + + /* Write back modified value */ + ret = mfd_sc16is75x_write(dev, channel, reg, ®_val, 1); + if (ret != 0) { + goto end; + } + +end: + k_mutex_unlock(&data->transaction_lock); + return ret; +} + +int mfd_sc16is75x_read_fifo(const struct device *dev, const uint8_t channel, + uint8_t *buf, const size_t len) +{ + return READ_SC16IS75X_CHANNEL(dev, channel, RHR, buf, len); +} + +int mfd_sc16is75x_write_fifo(const struct device *dev, const uint8_t channel, + const uint8_t *buf, const size_t len) +{ + return WRITE_SC16IS75X_CHANNEL(dev, channel, THR, buf, len); +} + +#ifdef CONFIG_MFD_SC16IS75X_ASYNC + +static int mfd_sc16is75x_read_signal(const struct device *dev, + const uint8_t channel, const uint8_t reg, + uint8_t *buf, const size_t len, + struct k_poll_signal *signal) +{ + struct mfd_sc16is75x_data * const data = dev->data; + uint8_t sub_address = 0; + int ret = 0; + + ret = mfd_sc16is75x_sub_address(SC16IS75X_SA_RD, reg, channel, + &sub_address); + if (ret != 0) { + return ret; + } + + return data->transfer_function->read_raw_signal(dev, sub_address, + buf, len, signal); +} + +int mfd_sc16is75x_read_register_signal(const struct device *dev, + const uint8_t channel, + const uint8_t reg, uint8_t *value, + struct k_poll_signal *signal) +{ + return mfd_sc16is75x_read_signal(dev, channel, reg, value, 1, signal); +} + +#endif /* CONFIG_MFD_SC16IS75X_ASYNC */ + +#ifdef CONFIG_MFD_SC16IS75X_INTERRUPTS + +int mfd_sc16is75x_add_callback(const struct device *dev, + struct gpio_callback *callback) +{ + struct mfd_sc16is75x_data * const data = dev->data; + + return gpio_manage_callback(&data->callbacks, callback, true); +} + +int mfd_sc16is75x_remove_callback(const struct device *dev, + struct gpio_callback *callback) +{ + struct mfd_sc16is75x_data * const data = dev->data; + + return gpio_manage_callback(&data->callbacks, callback, false); +} + +static void mfd_sc16is75x_interrupt_work_fn_init(struct k_work *work) +{ + struct mfd_sc16is75x_data * const data = CONTAINER_OF(work, + struct mfd_sc16is75x_data, interrupt_work_init); + const struct device * const dev = data->self; + const struct mfd_sc16is75x_config * const config = dev->config; + int ret = 0; + + /* + * Attempt to get interrupt handling lock. If it's taken, that means + * a handling transaction is already active. In that case, reschedule + * self. + */ + ret = k_sem_take(&data->interrupt_lock, K_NO_WAIT); + if (ret != 0) { + k_work_submit(work); + return; + } + + /* Initiate reading from IIRs */ + for (int i = 0; i < config->n_channels; i++) { + data->interrupt_data.iir[i] = BIT(SC16IS75X_BIT_IIR_PENDING); + k_poll_signal_init(&data->interrupt_data.signals[i]); + uint8_t ch = config->channels[i]; + + ret = READ_SC16IS75X_CHREG_SIGNAL(dev, ch, IIR, + &data->interrupt_data.iir[i], + &data->interrupt_data.signals[i]); + if (ret != 0) { + /* + * Propagate a failure to start the transfer the same + * as a failure to complete it: Have the finalizing + * work item retry the whole handling process. To do + * this, raise the signal ourselves with a generic + * error. + */ + k_poll_signal_raise(&data->interrupt_data.signals[i], -1); + } + } + + /* Schedule finalizing work item. */ + ret = k_work_schedule(&data->interrupt_work_final, K_USEC(100)); + if (ret != 0 && ret != 1) { + /* + * Result 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); + } +} + +static void mfd_sc16is75x_interrupt_work_fn_final(struct k_work *work) +{ + struct k_work_delayable *work_delayable = (struct k_work_delayable *) work; + struct mfd_sc16is75x_data * const data = CONTAINER_OF(work_delayable, + struct mfd_sc16is75x_data, interrupt_work_final); + const struct device * const dev = data->self; + const struct mfd_sc16is75x_config * const config = dev->config; + enum mfd_sc16is75x_event event; + bool retry = false; + + struct k_poll_event poll_events[config->n_channels]; + int results[config->n_channels]; + + /* Check for completion of running transfers */ + for (int i = 0; i < config->n_channels; i++) { + poll_events[i] = (struct k_poll_event)K_POLL_EVENT_INITIALIZER( + K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &data->interrupt_data.signals[i] + ); + } + + k_poll(poll_events, config->n_channels, K_NO_WAIT); + for (int i = 0; i < config->n_channels; i++) { + int signaled = 0; + + k_poll_signal_check(&data->interrupt_data.signals[i], + &signaled, &results[i]); + if (!signaled) { + /* + * At least one transfer isn't done yet, + * continue spinning. + */ + k_work_schedule(work_delayable, K_USEC(100)); + return; + } + } + + /* + * Once all transfers are complete, check return values. If any + * transfers failed, retry the interrupt handling precedure. + */ + for (int i = 0; i < config->n_channels; i++) { + if (results[i] != 0) { + retry = true; + break; + } + } + + /* Sorting out and triggering callbacks for the various types */ + for (int i = 0; i < config->n_channels; i++) { + uint8_t type = GET_FIELD(data->interrupt_data.iir[i], + SC16IS75X_BIT_IIR_TYPE); + + /* Transfer for this channel failed, no data to check */ + if (results[i] != 0) { + continue; + } + + /* channel has not fired (0: pending, 1: not pending) */ + if (IS_BIT_SET(data->interrupt_data.iir[i], + SC16IS75X_BIT_IIR_PENDING)) { + continue; + }; + + uint8_t ch = config->channels[i]; + + /* reset event number for each channel iterration */ + event = SC16IS75X_EVENT_MAX; + + if (type == SC16IS75X_INT_RXLSE) { /* priority 1 */ + retry = true; + event = (ch == 0) ? SC16IS75X_EVENT_UART0_RXLSE + : (ch == 1) ? SC16IS75X_EVENT_UART1_RXLSE + : SC16IS75X_EVENT_MAX; + } else if (type == SC16IS75X_INT_RXTO) { /* priority 2 */ + retry = true; + event = (ch == 0) ? SC16IS75X_EVENT_UART0_RXTO + : (ch == 1) ? SC16IS75X_EVENT_UART1_RXTO + : SC16IS75X_EVENT_MAX; + } else if (type == SC16IS75X_INT_RHRI) { /* priority 2 */ + retry = true; + event = (ch == 0) ? SC16IS75X_EVENT_UART0_RHRI + : (ch == 1) ? SC16IS75X_EVENT_UART1_RHRI + : SC16IS75X_EVENT_MAX; + } else if (type == SC16IS75X_INT_THRI) { /* priority 3 */ + retry = true; + event = (ch == 0) ? SC16IS75X_EVENT_UART0_THRI + : (ch == 1) ? SC16IS75X_EVENT_UART1_THRI + : SC16IS75X_EVENT_MAX; + } else if (type == SC16IS75X_INT_MSI) { /* priority 4 */ + retry = true; + event = (ch == 0) ? SC16IS75X_EVENT_UART0_MSI + : (ch == 1) ? SC16IS75X_EVENT_UART1_MSI + : SC16IS75X_EVENT_MAX; + } else if (type == SC16IS75X_INT_IO) { /* priority 5 */ + retry = true; + event = (ch == 0) ? SC16IS75X_EVENT_IO0_STATE + : (ch == 1) ? SC16IS75X_EVENT_IO1_STATE + : SC16IS75X_EVENT_MAX; + } else if (type == SC16IS75X_INT_XOFF) { /* priority 6 */ + retry = true; + event = (ch == 0) ? SC16IS75X_EVENT_UART0_XOFF + : (ch == 1) ? SC16IS75X_EVENT_UART1_XOFF + : SC16IS75X_EVENT_MAX; + } else if (type == SC16IS75X_INT_HWFL) { /* priority 7 */ + retry = true; + event = (ch == 0) ? SC16IS75X_EVENT_UART0_HWFL + : (ch == 1) ? SC16IS75X_EVENT_UART1_HWFL + : SC16IS75X_EVENT_MAX; + } + + if (event < SC16IS75X_EVENT_MAX) { + gpio_fire_callbacks(&data->callbacks, data->self, + BIT(event)); + } + } + + /* + * Resubmit handler to queue to look for further pending interrupts, + * or if interrupt pin is still active. + */ + if (retry || gpio_pin_get_dt(&config->interrupt) != 0) { + k_work_submit(&data->interrupt_work_init); + }; + + /* Release lock */ + k_sem_give(&data->interrupt_lock); +} + +static void mfd_sc16is75x_interrupt_callback(const struct device *dev, + struct gpio_callback *cb, + gpio_port_pins_t pins) +{ + /* + * Handling interrupts requires bus operations, which we can't + * do from an ISR. Schedule a work item to do it instead. + */ + struct mfd_sc16is75x_data * const data = CONTAINER_OF(cb, + struct mfd_sc16is75x_data, interrupt_cb); + + k_work_submit(&data->interrupt_work_init); +} + +#endif /* CONFIG_MFD_SC16IS75X_INTERRUPTS */ + +static int mfd_sc16is75x_init(const struct device *dev) +{ + const struct mfd_sc16is75x_config * const config = dev->config; + struct mfd_sc16is75x_data * const data = dev->data; + int ret = 0; + + /* Create back-reference for interrupt handling */ + data->self = dev; + + /* Initialize transaction lock */ + k_mutex_init(&data->transaction_lock); + + /* Configure bus specific transfer functions (SPI or I2C) */ + ret = config->bus_init(dev); + if (ret != 0) { + LOG_ERR("%s: bus initialization failed: %d", dev->name, ret); + goto end; + } + + /* Configure reset GPIO pin, confirm device readiness first */ + if (gpio_is_ready_dt(&config->reset)) { + ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT); + if (ret != 0) { + LOG_ERR("%s: configure reset pin failed: %d", + dev->name, ret); + goto end; + } + } else { + LOG_ERR("%s: GPIO device %s with reset pin not ready", + dev->name, config->reset.port->name); + return -ENODEV; + } + + /* + * Pull reset for a few µs, then wait another + * few µs for device startup. + */ + gpio_pin_set_dt(&config->reset, 1); /* assert */ + k_usleep(5); + gpio_pin_set_dt(&config->reset, 0); /* deassert */ + k_usleep(5); + +#ifdef CONFIG_MFD_SC16IS75X_ASWQ + /* Initialize private work queue. */ + size_t work_queue_stack_size = CONFIG_MFD_SC16IS75X_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_SC16IS75X_ASWQ */ + +#ifdef CONFIG_MFD_SC16IS75X_INTERRUPTS + + /* Initialize interrupt handling lock as open */ + k_sem_init(&data->interrupt_lock, 1, 1); + + /* Configure interrupt GPIO pin, confirm device readiness first */ + if (gpio_is_ready_dt(&config->interrupt)) { + ret = gpio_pin_configure_dt(&config->interrupt, GPIO_INPUT); + if (ret != 0) { + LOG_ERR("%s: configure interrupt pin failed: %d", + dev->name, ret); + goto end; + } + } else { + LOG_ERR("%s: GPIO device %s with interrupt pin not ready", + dev->name, config->interrupt.port->name); + return -ENODEV; + } + + /* Set up interrupt handling */ + k_work_init(&data->interrupt_work_init, + mfd_sc16is75x_interrupt_work_fn_init); + k_work_init_delayable(&data->interrupt_work_final, + mfd_sc16is75x_interrupt_work_fn_final); + + gpio_init_callback(&data->interrupt_cb, + mfd_sc16is75x_interrupt_callback, + BIT(config->interrupt.pin)); + + ret = gpio_add_callback_dt(&config->interrupt, &data->interrupt_cb); + if (ret < 0) { + goto end; + } + + /* Enable interrupts */ + ret = gpio_pin_interrupt_configure_dt(&config->interrupt, + GPIO_INT_EDGE_TO_ACTIVE); + if (ret != 0) { + LOG_ERR("%s: enable interrupt on interrupt pin failed: %d", + dev->name, ret); + goto end; + } + +#endif /* CONFIG_MFD_SC16IS75X_INTERRUPTS */ + +end: + return ret; +} + +#ifdef CONFIG_PM_DEVICE +static int mfd_sc16is75x_pm_device_pm_action(const struct device *dev, + enum pm_device_action action) +{ + ARG_UNUSED(dev); + ARG_UNUSED(action); + + return 0; +} +#endif + +/** + * @brief For a given child node, if it's a UART controller: return the + * channel id (`reg` property), with a comma. If the child is not + * a UART controller, return nothing. + * + * Note that since the property is technically an array, we take the 0th entry + * to avoid extra braces. + */ +#define MFD_SC16IS75X_CHILD_CHANNEL(child) \ + COND_CODE_1(DT_NODE_HAS_COMPAT(child, nxp_sc16is75x_uart), \ + (DT_PROP_BY_IDX(child, reg, 0),), \ + () \ + ) + +/** + * @brief Construct a bracketed list of all child UART controllers' channel id + * (== `reg` property) + */ +#define MFD_SC16IS75X_UART_CHANNELS(inst) { \ + DT_INST_FOREACH_CHILD_STATUS_OKAY(inst, \ + MFD_SC16IS75X_CHILD_CHANNEL) \ +} + +/** + * @brief Construct struct initializer entries for an SPI bus configuration. + */ +#define MFD_SC16IS75X_DEFINE_SPI_BUS(inst) \ + .spi = SPI_DT_SPEC_INST_GET(inst, SPI_OP_MODE_MASTER | \ + SPI_WORD_SET(8), 0), \ + .bus_init = mfd_sc16is75x_spi_init + +/** + * @brief Construct struct initializer entries for an I2C bus configuration. + */ +#define MFD_SC16IS75X_DEFINE_I2C_BUS(inst) \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + .bus_init = mfd_sc16is75x_i2c_init + +/** + * @brief Return one of the two bus initializer lists above, selecting the + * correct bus based on the devicetree. + */ +#define MFD_SC16IS75X_DEFINE_BUS(inst) \ + COND_CODE_1(DT_INST_ON_BUS(inst, spi), \ + (MFD_SC16IS75X_DEFINE_SPI_BUS(inst)), \ + (MFD_SC16IS75X_DEFINE_I2C_BUS(inst))) + +/** + * @brief Initializer macro for a device driver instance. + * + * In order to count the number of channels, we create an instance of the + * channels list. Unfortunately, GCC doesn't like us using this same instance + * for initialization, so we have to invoke the list construction macro again to + * get a naked initializer list later. + */ +#define MFD_SC16IS75X_DEFINE(inst) \ + \ + static uint8_t mfd_sc16is75x_uart_channels_##inst[] = \ + MFD_SC16IS75X_UART_CHANNELS(inst); \ + \ + static struct mfd_sc16is75x_config mfd_sc16is75x_config_##inst = \ + { \ + MFD_SC16IS75X_DEFINE_BUS(inst), \ + .reset = GPIO_DT_SPEC_INST_GET(inst, reset_gpios), \ + .n_channels = ARRAY_SIZE(mfd_sc16is75x_uart_channels_##inst),\ + .channels = MFD_SC16IS75X_UART_CHANNELS(inst), \ + COND_CODE_1(CONFIG_MFD_SC16IS75X_INTERRUPTS, \ + (.interrupt = GPIO_DT_SPEC_INST_GET(inst, \ + interrupt_gpios),), \ + ()) \ + }; \ + \ + static struct mfd_sc16is75x_data mfd_sc16is75x_data_##inst; \ + \ + PM_DEVICE_DT_INST_DEFINE(inst, mfd_sc16is75x_pm_device_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(inst, mfd_sc16is75x_init, \ + PM_DEVICE_DT_INST_GET(inst), \ + &mfd_sc16is75x_data_##inst, \ + &mfd_sc16is75x_config_##inst, POST_KERNEL, \ + CONFIG_MFD_SC16IS75X_INIT_PRIORITY, NULL); + +DT_INST_FOREACH_STATUS_OKAY(MFD_SC16IS75X_DEFINE); diff --git a/drivers/mfd/mfd_sc16is75x.h b/drivers/mfd/mfd_sc16is75x.h new file mode 100644 index 0000000000..9a7c6e54db --- /dev/null +++ b/drivers/mfd/mfd_sc16is75x.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_MFD_SC16IS75X_H_ +#define ZEPHYR_DRIVERS_MFD_SC16IS75X_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define DT_DRV_COMPAT nxp_sc16is75x + +#include +#include +#include +#include +#include + +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) +#include +#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) */ + +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) +#include +#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */ + +/** + * @brief SC16IS75X 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 SC16IS75X MFD + * controller as well as the binding to related SPI or I2C device. + */ +struct mfd_sc16is75x_config { +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) + struct spi_dt_spec spi; +#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) */ +#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) + struct i2c_dt_spec i2c; +#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */ + /** bus specific initialization function (SPI or I2C) */ + int (*bus_init)(const struct device *dev); + /** GPIO pin for chip reset */ + struct gpio_dt_spec reset; + /** Number of UART channels provided by this device. */ + uint8_t n_channels; + /** UART channel numbers. */ + uint8_t channels[SC16IS75X_UART_CHANNELS_MAX]; +#ifdef CONFIG_MFD_SC16IS75X_INTERRUPTS + /** GPIO pin for interrupt requests */ + struct gpio_dt_spec interrupt; +#endif /* CONFIG_MFD_SC16IS75X_INTERRUPTS */ +}; + +/** + * @brief SC16IS75X MFD bus transfer functions + * + * The SC16IS75X supports either SPI or I2C bus communiation. Depending + * on the device tree definitions, the driver automatically selects the + * correct transfer functions for reading and writing raw data. + */ +struct mfd_sc16is75x_transfer_function { + /** read raw data */ + int (*read_raw)(const struct device *dev, const uint8_t sub_address, + uint8_t *buf, const size_t len); + /** write raw data */ + int (*write_raw)(const struct device *dev, const uint8_t sub_address, + const uint8_t *buf, const size_t len); +#ifdef CONFIG_MFD_SC16IS75X_ASYNC + int (*read_raw_signal)(const struct device *dev, const uint8_t sub_address, + uint8_t *buf, const size_t len, + struct k_poll_signal *signal); +#endif /* CONFIG_MFD_SC16IS75X_ASYNC */ +}; + +#ifdef CONFIG_MFD_SC16IS75X_ASWQ + +/** + * @brief Bus agnostic asynchronous read function. + */ +int mfd_sc16is75x_read_raw_signal(const struct device *dev, + const uint8_t sub_address, + uint8_t *buf, const size_t len, + struct k_poll_signal *signal); + +#endif /* CONFIG_MFD_SC16IS75X_ASWQ */ + +#ifdef CONFIG_MFD_SC16IS75X_INTERRUPTS + +/** + * @brief SC16IS75X MFD data for asynchronous interrupt handling + */ +struct sc16is75x_interrupt_data { + /** IIR value per channels */ + uint8_t iir[SC16IS75X_UART_CHANNELS_MAX]; + /** asynchronous notification signal per channel */ + struct k_poll_signal signals[SC16IS75X_UART_CHANNELS_MAX]; +}; + +#endif /* CONFIG_MFD_SC16IS75X_INTERRUPTS */ + +/** + * @brief SC16IS75X MFD data + * + * This structure contains data structures used by a SC16IS75X MFD. + * + * Combined register access sequences, either multiple read, multiple write + * or mixed read and write, are synchronized using @a k_mutex. + */ +struct mfd_sc16is75x_data { + /** Back refernce to driver instance */ + const struct device *self; + /** bus specific transfer functions (SPI or I2C) */ + const struct mfd_sc16is75x_transfer_function *transfer_function; + /** Mutex to allow locking across multiple transactions */ + struct k_mutex transaction_lock; +#ifdef CONFIG_MFD_SC16IS75X_ASWQ + /** 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_SC16IS75X_ASWQ */ +#ifdef CONFIG_MFD_SC16IS75X_INTERRUPTS + /** Lock for interrupt handling */ + struct k_sem interrupt_lock; + /** GPIO port interrupt callback */ + struct gpio_callback interrupt_cb; + /** GPIO port interrupt worker */ + struct k_work interrupt_work_init; + struct k_work_delayable interrupt_work_final; + /** Struct for passing data between interrupt handling work items */ + struct sc16is75x_interrupt_data interrupt_data; + /** Child callbacks for interrupt handling */ + sys_slist_t callbacks; +#endif /* CONFIG_MFD_SC16IS75X_INTERRUPTS */ +}; + +int mfd_sc16is75x_spi_init(const struct device *dev); +int mfd_sc16is75x_i2c_init(const struct device *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_MFD_SC16IS75X_H_*/ diff --git a/drivers/mfd/mfd_sc16is75x_async.c b/drivers/mfd/mfd_sc16is75x_async.c new file mode 100644 index 0000000000..bfd3159a65 --- /dev/null +++ b/drivers/mfd/mfd_sc16is75x_async.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "mfd_sc16is75x.h" + +#include +LOG_MODULE_REGISTER(mfd_sc16is75x_async, CONFIG_MFD_LOG_LEVEL); + +struct mfd_sc16is75x_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 subaddress being read from. */ + uint8_t sub_address; + /** 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_sc16is75x_transfer_work_fn(struct k_work *work) +{ + /* Access bundled data */ + struct mfd_sc16is75x_transfer_work *transfer_work = CONTAINER_OF(work, + struct mfd_sc16is75x_transfer_work, work); + const struct device *dev = transfer_work->dev; + struct mfd_sc16is75x_data * const data = dev->data; + int ret = 0; + + /* Call blocking transfer */ + ret = data->transfer_function->read_raw(dev, + transfer_work->sub_address, + 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); +} + + +int mfd_sc16is75x_read_raw_signal(const struct device *dev, + const uint8_t sub_address, + uint8_t *buf, const size_t len, + struct k_poll_signal *signal) +{ + struct mfd_sc16is75x_data * const data = dev->data; + int ret = 0; + + /* Create work item to manage transfers */ + struct mfd_sc16is75x_transfer_work *transfer_work = k_malloc( + sizeof(struct mfd_sc16is75x_transfer_work)); + if (transfer_work == NULL) { + return -ENOMEM; + } + + *transfer_work = (struct mfd_sc16is75x_transfer_work) { + .dev = dev, + .sub_address = sub_address, + .rx_data = buf, + .rx_len = len, + .signal = signal, + }; + k_work_init(&transfer_work->work, mfd_sc16is75x_transfer_work_fn); + + /* 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); + return -EAGAIN; + } + + return 0; +} diff --git a/drivers/mfd/mfd_sc16is75x_i2c.c b/drivers/mfd/mfd_sc16is75x_i2c.c new file mode 100644 index 0000000000..0991c0bd5c --- /dev/null +++ b/drivers/mfd/mfd_sc16is75x_i2c.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +#include "mfd_sc16is75x.h" + +#include +LOG_MODULE_REGISTER(mfd_sc16is75x_i2c, CONFIG_MFD_LOG_LEVEL); + +static int mfd_sc16is75x_i2c_read_raw(const struct device *dev, + const uint8_t sub_address, + uint8_t *buf, const size_t len) +{ + const struct mfd_sc16is75x_config * const config = dev->config; + int ret = 0; + + ret = i2c_write_read_dt(&config->i2c, &sub_address, 1, buf, len); + return ret; +} + +static int mfd_sc16is75x_i2c_write_raw(const struct device *dev, + const uint8_t sub_address, + const uint8_t *buf, const size_t len) +{ + const struct mfd_sc16is75x_config * const config = dev->config; + uint8_t buffer[SC16IS75X_FIFO_CAPACITY + 1] = {0}; + int ret = 0; + + /* More than SC16IS75X_FIFO_CAPACITY is useless */ + if (len > SC16IS75X_FIFO_CAPACITY) { + return -EINVAL; + } + + buffer[0] = sub_address; + memcpy(buffer + 1, buf, len); + + ret = i2c_write_dt(&config->i2c, buffer, len + 1); + return ret; +} + +#if defined(CONFIG_MFD_SC16IS75X_ASYNC) && defined(CONFIG_I2C_CALLBACK) + +struct mfd_sc16is75x_i2c_xfer_data { + uint8_t sub_address; + struct k_poll_signal *signal; +}; + +static void mfd_sc16is75x_i2c_xfer_complete(const struct device *dev, + int result, void *data) +{ + struct mfd_sc16is75x_i2c_xfer_data *xfer_data = data; + + k_poll_signal_raise(xfer_data->signal, result); + k_free(xfer_data); +} + +static int mfd_sc16is75x_i2c_read_raw_signal(const struct device *dev, + const uint8_t sub_address, + uint8_t *buf, const size_t len, + struct k_poll_signal *signal) +{ + const struct mfd_sc16is75x_config * const config = dev->config; + struct mfd_sc16is75x_i2c_xfer_data *xfer_data; + int ret = 0; + + /* + * We need to keep a live pointer to the value of sub_address + * throughout the transfer. + */ + xfer_data = k_calloc(1, sizeof(struct mfd_sc16is75x_i2c_xfer_data)); + if (xfer_data == NULL) { + return -ENOMEM; + } + + /* Fill in value of sub_address and signal reference */ + *xfer_data = (struct mfd_sc16is75x_i2c_xfer_data){ + .sub_address = sub_address, + .signal = signal, + }; + + /* Setup transfer data buffers */ + struct i2c_msg msgs[] = { + { + .buf = &xfer_data->sub_address, + .len = 1, + .flags = I2C_MSG_WRITE, + }, + { + .buf = buf, + .len = len, + .flags = I2C_MSG_RESTART | I2C_MSG_READ | I2C_MSG_STOP, + }, + }; + + ret = i2c_transfer_cb_dt(&config->i2c, msgs, ARRAY_SIZE(msgs), + mfd_sc16is75x_i2c_xfer_complete, + (void *)xfer_data); + if (ret != 0) { + k_free(xfer_data); + } + + return ret; +} + +#endif /* defined(CONFIG_MFD_SC16IS75X_ASYNC) && defined(CONFIG_I2C_CALLBACK) */ + +static const struct mfd_sc16is75x_transfer_function +mfd_sc16is75x_i2c_init_transfer_function = { + .read_raw = mfd_sc16is75x_i2c_read_raw, + .write_raw = mfd_sc16is75x_i2c_write_raw, +#ifdef CONFIG_MFD_SC16IS75X_ASYNC +#ifdef CONFIG_I2C_CALLBACK + .read_raw_signal = mfd_sc16is75x_i2c_read_raw_signal, +#else /* CONFIG_I2C_CALLBACK */ + .read_raw_signal = mfd_sc16is75x_read_raw_signal, +#endif /* CONFIG_I2C_CALLBACK */ +#endif /* CONFIG_MFD_SC16IS75X_ASYNC */ +}; + +int mfd_sc16is75x_i2c_init(const struct device *dev) +{ + const struct mfd_sc16is75x_config * const config = dev->config; + struct mfd_sc16is75x_data * const data = dev->data; + + data->transfer_function = &mfd_sc16is75x_i2c_init_transfer_function; + + /* Confirm device readiness */ + if (!i2c_is_ready_dt(&config->i2c)) { + LOG_ERR("%s: I2C device %s not ready", + dev->name, config->i2c.bus->name); + return -ENODEV; + } + + LOG_DBG("%s: ready over %s!", dev->name, config->i2c.bus->name); + + return 0; +} diff --git a/drivers/mfd/mfd_sc16is75x_spi.c b/drivers/mfd/mfd_sc16is75x_spi.c new file mode 100644 index 0000000000..920d95d6ca --- /dev/null +++ b/drivers/mfd/mfd_sc16is75x_spi.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +#include "mfd_sc16is75x.h" + +#include +LOG_MODULE_REGISTER(mfd_sc16is75x_spi, CONFIG_MFD_LOG_LEVEL); + +static int mfd_sc16is75x_spi_read_raw(const struct device *dev, + const uint8_t sub_address, + uint8_t *buf, const size_t len) +{ + const struct mfd_sc16is75x_config * const config = dev->config; + int ret = 0; + + const struct spi_buf tx_buffers[] = { { .buf = (void *)&sub_address, .len = 1, }, + { .buf = NULL, .len = len, } }; + const struct spi_buf rx_buffers[] = { { .buf = NULL, .len = 1, }, + { .buf = buf, .len = len, } }; + struct spi_buf_set tx = { + .buffers = tx_buffers, + .count = ARRAY_SIZE(tx_buffers), + }; + struct spi_buf_set rx = { + .buffers = rx_buffers, + .count = ARRAY_SIZE(rx_buffers), + }; + + ret = spi_transceive_dt(&config->spi, &tx, &rx); + return ret; +} + +static int mfd_sc16is75x_spi_write_raw(const struct device *dev, + const uint8_t sub_address, + const uint8_t *buf, const size_t len) +{ + const struct mfd_sc16is75x_config * const config = dev->config; + int ret = 0; + + const struct spi_buf tx_buffers[] = { { .buf = (void *)&sub_address, .len = 1, }, + { .buf = (void *)buf, .len = len, } }; + const struct spi_buf_set tx = { + .buffers = tx_buffers, + .count = ARRAY_SIZE(tx_buffers), + }; + + ret = spi_write_dt(&config->spi, &tx); + return ret; +} + +#if defined(CONFIG_MFD_SC16IS75X_ASYNC) && defined(CONFIG_SPI_ASYNC) + +struct mfd_sc16is75x_spi_xfer_data { + uint8_t sub_address; + struct k_poll_signal *signal; +}; + +static void mfd_sc16is75x_spi_xfer_complete(const struct device *dev, + int result, void *data) +{ + struct mfd_sc16is75x_spi_xfer_data *xfer_data = data; + + k_poll_signal_raise(xfer_data->signal, result); + k_free(xfer_data); +} + +static int mfd_sc16is75x_spi_read_raw_signal(const struct device *dev, + const uint8_t sub_address, + uint8_t *buf, const size_t len, + struct k_poll_signal *signal) +{ + const struct mfd_sc16is75x_config * const config = dev->config; + struct mfd_sc16is75x_spi_xfer_data *xfer_data; + int ret = 0; + + /* + * We need to keep a live pointer to the value of sub_address + * throughout the transfer. + */ + xfer_data = k_calloc(1, sizeof(struct mfd_sc16is75x_spi_xfer_data)); + if (xfer_data == NULL) { + return -ENOMEM; + } + + /* Fill in value of sub_address and signal reference */ + *xfer_data = (struct mfd_sc16is75x_spi_xfer_data){ + .sub_address = sub_address, + .signal = signal, + }; + + /* Setup transfer data buffers */ + const struct spi_buf tx_buffers[] = { { .buf = &xfer_data->sub_address, .len = 1, }, + { .buf = NULL, .len = len, } }; + const struct spi_buf rx_buffers[] = { { .buf = NULL, .len = 1, }, + { .buf = buf, .len = len, } }; + const struct spi_buf_set tx = { + .buffers = tx_buffers, + .count = ARRAY_SIZE(tx_buffers), + }; + const struct spi_buf_set rx = { + .buffers = rx_buffers, + .count = ARRAY_SIZE(rx_buffers), + }; + + ret = spi_transceive_cb(config->spi.bus, &config->spi.config, &tx, &rx, + mfd_sc16is75x_spi_xfer_complete, + (void *)xfer_data); + if (ret != 0) { + k_free(xfer_data); + } + + return ret; +} + +#endif /* defined(CONFIG_MFD_SC16IS75X_ASYNC) && defined(CONFIG_SPI_ASYNC) */ + +static const struct mfd_sc16is75x_transfer_function +mfd_sc16is75x_spi_init_transfer_function = { + .read_raw = mfd_sc16is75x_spi_read_raw, + .write_raw = mfd_sc16is75x_spi_write_raw, +#ifdef CONFIG_MFD_SC16IS75X_ASYNC +#ifdef CONFIG_SPI_ASYNC + .read_raw_signal = mfd_sc16is75x_spi_read_raw_signal, +#else /* CONFIG_SPI_ASYNC */ + .read_raw_signal = mfd_sc16is75x_read_raw_signal, +#endif /* CONFIG_SPI_ASYNC */ +#endif /* CONFIG_MFD_SC16IS75X_ASYNC */ +}; + +int mfd_sc16is75x_spi_init(const struct device *dev) +{ + const struct mfd_sc16is75x_config * const config = dev->config; + struct mfd_sc16is75x_data * const data = dev->data; + + data->transfer_function = &mfd_sc16is75x_spi_init_transfer_function; + + /* Confirm device readiness */ + if (!spi_is_ready_dt(&config->spi)) { + LOG_ERR("%s: SPI device %s not ready", + dev->name, config->spi.bus->name); + return -ENODEV; + } + + LOG_DBG("%s: ready over %s!", dev->name, config->spi.bus->name); + + return 0; +} diff --git a/drivers/serial/CMakeLists.txt b/drivers/serial/CMakeLists.txt new file mode 100644 index 0000000000..b658fe8f6e --- /dev/null +++ b/drivers/serial/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library_amend() + +zephyr_library_sources_ifdef(CONFIG_UART_SC16IS75X uart_sc16is75x.c) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig new file mode 100644 index 0000000000..65ba801288 --- /dev/null +++ b/drivers/serial/Kconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +if SERIAL + +rsource "Kconfig.sc16is75x" + +endif # SERIAL diff --git a/drivers/serial/Kconfig.sc16is75x b/drivers/serial/Kconfig.sc16is75x new file mode 100644 index 0000000000..1d17dd7766 --- /dev/null +++ b/drivers/serial/Kconfig.sc16is75x @@ -0,0 +1,36 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +menuconfig UART_SC16IS75X + bool "NXP SC16IS75x UART driver" + default y + depends on DT_HAS_NXP_SC16IS75X_UART_ENABLED + depends on MFD_SC16IS75X + help + Enable driver for UART controller part of an SC16IS75x bridge. + +if UART_SC16IS75X + +config UART_SC16IS75X_LOOPBACK + bool "Enable internal loopback" + help + Enable the NXP SC16IS75x local UART loopback mode (internal). + + In this mode the RTS and DTR signals are looped back to the + CTS and DSR and the TX output is looped back to the RX input + internally for all channels. + +config UART_SC16IS75X_INIT_PRIORITY + int "Init priority" + default 60 + help + Device driver initialization priority. + +config UART_SC16IS75X_INTERRUPT_DRIVEN + bool + default y if UART_INTERRUPT_DRIVEN + default n + depends on MFD_SC16IS75X_INTERRUPTS + depends on MFD_SC16IS75X_ASYNC + +endif # UART_SC16IS75X diff --git a/drivers/serial/uart_sc16is75x.c b/drivers/serial/uart_sc16is75x.c new file mode 100644 index 0000000000..e1a7cd88ec --- /dev/null +++ b/drivers/serial/uart_sc16is75x.c @@ -0,0 +1,1230 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_sc16is75x_uart + +#include +#include +#include + +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(uart_sc16is75x, CONFIG_UART_LOG_LEVEL); + +struct uart_sc16is75x_config { + const struct device *bus; + uint8_t channel; + uint32_t clock_frequency; +}; + +struct uart_sc16is75x_data { + /* Mutex to allow locking across multiple transactions */ + struct k_mutex transaction_lock; + struct uart_config uart_config; +#ifdef CONFIG_UART_SC16IS75X_INTERRUPT_DRIVEN + const struct device *self; + struct gpio_callback interrupt_cb; + uart_irq_callback_user_data_t callback; + void *cb_data; +#endif /* CONFIG_UART_SC16IS75X_INTERRUPT_DRIVEN */ +}; + +static bool uart_sc16is75x_rx_available(const struct device *dev) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t lsr = 0; + int ret = 0; + + /* Line Status Register */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, LSR, &lsr); + if (ret != 0) { + return false; + } + + /* + * NOTE: if needed, this would be a good place to detect and + * handle lost characters due to SC16IS75X_BIT_LSR_RX_OVERRUN + * or other communication conditions (break, framing or parity + * error, or anything else) in polling mode. + */ + + if (IS_BIT_SET(lsr, SC16IS75X_BIT_LSR_RX_OVERRUN)) { + LOG_WRN("%s: overrun error has occurred", dev->name); + } + + return IS_BIT_SET(lsr, SC16IS75X_BIT_LSR_RX_DATA); +} + +static bool uart_sc16is75x_tx_possible(const struct device *dev) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t txlvl = 0; + int ret = 0; + + /* Line Status Register */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, TXLVL, &txlvl); + if (ret != 0) { + return false; + } + + /* + * NOTE: if needed, this would be a good place to detect more + * then just the number of spaces available in the transmit + * FIFO in polling mode, e.g. the transmit empty indicators: + * SC16IS75X_BIT_LSR_THREMPTY or SC16IS75X_BIT_LSR_THRTSREMPTY. + */ + + return (GET_FIELD(txlvl, SC16IS75X_BIT_TXLVL_SP) >= 1); +} + +#ifdef UART_SC16IS75X_LOOPBACK + +static int uart_sc16is75x_enable_loopback(const struct device *dev, bool enable) +{ + const struct uart_sc16is75x_config * const config = dev->config; + + /* Internal loopback is enabled by setting MCR[4] to 1 */ + return SETBIT_SC16IS75X_CHREG(config->bus, config->channel, MCR, + SC16IS75X_BIT_MCR_LOOPBACK, enable); +} + +#endif /* UART_SC16IS75X_LOOPBACK */ + +static int uart_sc16is75x_enable_fifo(const struct device *dev, bool enable) +{ + const struct uart_sc16is75x_config * const config = dev->config; + + /* FIFO is enabled by setting FCR[0] to 1 */ + return SETBIT_SC16IS75X_CHREG(config->bus, config->channel, FCR, + SC16IS75X_BIT_FCR_FIFOENA, enable); +} + +static int uart_sc16is75x_reset_fifos(const struct device *dev) +{ + const struct uart_sc16is75x_config * const config = dev->config; + + /* FIFO reset is controlled by FCR[1] (RX) and FCR[2] (TX) */ + uint8_t fcr = (BIT(SC16IS75X_BIT_FCR_RXFIFORST) + | BIT(SC16IS75X_BIT_FCR_TXFIFORST)); + + return WRITE_SC16IS75X_CHREG(config->bus, config->channel, FCR, fcr); +} + +static int uart_sc16is75x_poll_in(const struct device *dev, + unsigned char *p_char) +{ + const struct uart_sc16is75x_config * const config = dev->config; + + if (!uart_sc16is75x_rx_available(dev)) { + return -1; + } + + return READ_SC16IS75X_CHREG(config->bus, config->channel, RHR, p_char); +} + +static void uart_sc16is75x_poll_out(const struct device *dev, + unsigned char out_char) +{ + const struct uart_sc16is75x_config * const config = dev->config; + + if (uart_sc16is75x_tx_possible(dev)) { + WRITE_SC16IS75X_CHREG(config->bus, config->channel, + THR, out_char); + } +} + +#ifdef CONFIG_UART_WIDE_DATA + +static int uart_sc16is75x_poll_in_u16(const struct device *dev, + uint16_t *p_u16) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t rxlvl = 0; + uint8_t buf = 0; + int ret = 0; + + /* Check whether at least 2 bytes are available */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, RXLVL, &rxlvl); + if (ret != 0) { + return ret; + } + + if (GET_FIELD(rxlvl, SC16IS75X_BIT_RXLVL_CH) < 2) { + return -1; + } + + /* Read out data byte-wise */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, RHR, &buf); + if (ret != 0) { + return ret; + } + + *p_u16 = buf << 8; + + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, RHR, &buf); + if (ret != 0) { + return ret; + } + + *p_u16 |= buf; + + return 0; +} + +static void uart_sc16is75x_poll_out_u16(const struct device *dev, + uint16_t out_u16) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t txlvl = 0; + uint8_t buf = 0; + int ret = 0; + + /* Check whether at least 2 bytes are free */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, TXLVL, &txlvl); + if (ret != 0) { + return; + } + + if (GET_FIELD(txlvl, SC16IS75X_BIT_TXLVL_SP) >= 2) { + + /* Upper byte first */ + buf = (uint8_t)((out_u16 >> 8) & BIT_MASK(8)); + WRITE_SC16IS75X_CHREG(config->bus, config->channel, THR, buf); + + /* Lower byte second */ + buf = (uint8_t)(out_u16 & BIT_MASK(8)); + WRITE_SC16IS75X_CHREG(config->bus, config->channel, THR, buf); + } +} + +#endif /* CONFIG_UART_WIDE_DATA */ + +static int uart_sc16is75x_err_check(const struct device *dev) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t lsr = 0; + int errors = 0; + int ret = 0; + + /* Check Line Status Register for errors in the FIFO. */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, LSR, &lsr); + if (ret != 0) { + return ret; + } + + /* LSR[7] is 1 if there is at least one error present. */ + if (!IS_BIT_SET(lsr, SC16IS75X_BIT_LSR_RX_FIFO)) { + return 0; + } + + /* Assemble error flags */ + /* LSR[1] ^= Overrun error */ + if (IS_BIT_SET(lsr, SC16IS75X_BIT_LSR_RX_OVERRUN)) { + errors |= UART_ERROR_OVERRUN; + } + + /* LSR[2] ^= Parity error */ + if (IS_BIT_SET(lsr, SC16IS75X_BIT_LSR_RX_PARITY)) { + errors |= UART_ERROR_PARITY; + } + + /* LSR[3] ^= Framing error */ + if (IS_BIT_SET(lsr, SC16IS75X_BIT_LSR_RX_FRAMING)) { + errors |= UART_ERROR_FRAMING; + } + + /* LSR[4] ^= Break interrupt */ + if (IS_BIT_SET(lsr, SC16IS75X_BIT_LSR_RX_LINE_BREAK)) { + errors |= UART_BREAK; + } + + return errors; +} + +static int uart_sc16is75x_set_baudrate(const struct device *dev, + uint32_t baudrate) +{ + const struct uart_sc16is75x_config * const config = dev->config; + struct uart_sc16is75x_data * const data = dev->data; + bool dlena = false; + int ret = 0; + + /* Compute divisor value */ + uint16_t divisor = config->clock_frequency / (baudrate * 16); + uint8_t divisor_lsb = divisor; + uint8_t divisor_msb = divisor >> 8; + + /* Lock device before multi-transfer transaction */ + k_mutex_lock(&data->transaction_lock, K_FOREVER); + + /* Set LCR[7] to 1 to enable access to DLL and DLH registers */ + ret = SETBIT_SC16IS75X_CHREG(config->bus, config->channel, LCR, + SC16IS75X_BIT_LCR_DLENA, true); + if (ret != 0) { + goto end; + } + + dlena = true; + + /* Write values to device. */ + ret = WRITE_SC16IS75X_CHREG(config->bus, config->channel, + DLL, divisor_lsb); + if (ret != 0) { + goto end; + } + ret = WRITE_SC16IS75X_CHREG(config->bus, config->channel, + DLH, divisor_msb); + if (ret != 0) { + goto end; + } + + /* Unset LCR[7] to access normal registers again */ + ret = SETBIT_SC16IS75X_CHREG(config->bus, config->channel, LCR, + SC16IS75X_BIT_LCR_DLENA, false); + if (ret != 0) { + goto end; + } + + dlena = false; + + /* Update cached setting */ + data->uart_config.baudrate = baudrate; + +end: + /* Unlock device after transaction, and possibly do cleanup */ + if (dlena) { + ret = SETBIT_SC16IS75X_CHREG(config->bus, config->channel, LCR, + SC16IS75X_BIT_LCR_DLENA, false); + } + + k_mutex_unlock(&data->transaction_lock); + return ret; +} + +static int uart_sc16is75x_set_hw_flow_ctrl(const struct device *dev, + bool enable) +{ + const struct uart_sc16is75x_config * const config = dev->config; + struct uart_sc16is75x_data * const data = dev->data; + bool enhanced_reg_enabled = false; + uint8_t lcr = 0; + uint8_t efr = 0; + int ret = 0; + + /* + * To access enhanced register set, LCR must be set to 0xbf. + * Save current value to restore later. + */ + + /* Lock device for multi-transfer transaction */ + k_mutex_lock(&data->transaction_lock, K_FOREVER); + + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, LCR, &lcr); + if (ret != 0) { + goto end; + } + + ret = WRITE_SC16IS75X_CHREG(config->bus, config->channel, + LCR, SC16IS75X_BIT_LCR_ACCESS_EFR); + if (ret != 0) { + goto end; + } + + enhanced_reg_enabled = true; + + /* Get value of EFR register */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, + EFR, &efr); + if (ret != 0) { + goto end; + } + + /* Set Auto-RTS and Auto-CTS */ + WRITE_BIT(efr, SC16IS75X_BIT_EFR_RTSENA, enable); /* Auto-RTS */ + WRITE_BIT(efr, SC16IS75X_BIT_EFR_CTSENA, enable); /* Auto-CTS */ + + /* Write back modified value */ + ret = WRITE_SC16IS75X_CHREG(config->bus, config->channel, + EFR, efr); + if (ret != 0) { + goto end; + } + +end: + /* Unlock device after transaction, and possibly do restore */ + if (enhanced_reg_enabled) { + ret = WRITE_SC16IS75X_CHREG(config->bus, config->channel, + LCR, lcr); + } + + k_mutex_unlock(&data->transaction_lock); + return ret; +} + +static int uart_sc16is75x_use_modem_pins(const struct device *dev, + bool enable) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t bit; + + /* + * IOControl[1] enables extra modem pins for channel 0 (A), + * IOControl[2] enables extra modem pins for channel 1 (B). + */ + + if (config->channel == 0) { + bit = SC16IS75X_BIT_IOCONTROL_MODEM_PINS_A; + } else if (config->channel == 1) { + bit = SC16IS75X_BIT_IOCONTROL_MODEM_PINS_B; + } else { + return -EINVAL; + } + + return SETBIT_SC16IS75X_REG(config->bus, IOCONTROL, bit, enable); +} + +static int uart_sc16is75x_set_rs485_flow_ctrl(const struct device *dev, + bool enable) +{ + const struct uart_sc16is75x_config * const config = dev->config; + + /* EFCR[0] controls RS-485 (9-bit) mode */ + return SETBIT_SC16IS75X_CHREG(config->bus, config->channel, EFCR, + SC16IS75X_BIT_EFCR_RS485, enable); +} + +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE + +static int uart_sc16is75x_configure(const struct device *dev, + const struct uart_config *cfg) +{ + const struct uart_sc16is75x_config * const config = dev->config; + struct uart_sc16is75x_data * const data = dev->data; + uint8_t lcr = 0; + uint8_t parity = 0; + uint8_t stop_bits = 0; + uint8_t data_bits = 0; + int ret = 0; + + /* + * Set parity, stop bits and data bits + */ + + /* Parity */ + switch (cfg->parity) { + case UART_CFG_PARITY_NONE: + parity = SC16IS75X_PARITY_NONE; + break; + case UART_CFG_PARITY_ODD: + parity = SC16IS75X_PARITY_ODD; + break; + case UART_CFG_PARITY_EVEN: + parity = SC16IS75X_PARITY_EVEN; + break; + case UART_CFG_PARITY_MARK: + parity = SC16IS75X_PARITY_MARK; + break; + case UART_CFG_PARITY_SPACE: + parity = SC16IS75X_PARITY_SPACE; + break; + default: + return -EINVAL; + } + + /* Stop bits */ + if (cfg->stop_bits == UART_CFG_STOP_BITS_1) { + stop_bits = SC16IS75X_STOP_LEN_1; + } else if ((cfg->stop_bits == UART_CFG_STOP_BITS_1_5) + && (cfg->data_bits == UART_CFG_DATA_BITS_5)) { + stop_bits = SC16IS75X_STOP_LEN_1_5; + } else if ((cfg->stop_bits == UART_CFG_STOP_BITS_2) + && ((cfg->data_bits == UART_CFG_DATA_BITS_6) + || (cfg->data_bits == UART_CFG_DATA_BITS_7) + || (cfg->data_bits == UART_CFG_DATA_BITS_8))) { + stop_bits = SC16IS75X_STOP_LEN_2; + } else { + return -ENOTSUP; + } + + /* Data bits */ + switch (cfg->data_bits) { + case UART_CFG_DATA_BITS_5: + data_bits = SC16IS75X_WORD_LEN_5; + break; + case UART_CFG_DATA_BITS_6: + data_bits = SC16IS75X_WORD_LEN_6; + break; + case UART_CFG_DATA_BITS_7: + data_bits = SC16IS75X_WORD_LEN_7; + break; + case UART_CFG_DATA_BITS_8: + data_bits = SC16IS75X_WORD_LEN_8; + break; + default: + return -EINVAL; + } + + /* Lock device before multi-transfer transaction */ + k_mutex_lock(&data->transaction_lock, K_FOREVER); + + /* Read line control register value */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, LCR, &lcr); + if (ret != 0) { + goto end; + } + + /* Insert new values */ + SET_FIELD(lcr, SC16IS75X_BIT_LCR_PARITY, parity); + SET_FIELD(lcr, SC16IS75X_BIT_LCR_STOP_LEN, stop_bits); + SET_FIELD(lcr, SC16IS75X_BIT_LCR_WORD_LEN, data_bits); + + /* Write back updated register value */ + ret = WRITE_SC16IS75X_CHREG(config->bus, config->channel, LCR, lcr); + if (ret != 0) { + goto end; + } + + data->uart_config.parity = cfg->parity; + data->uart_config.stop_bits = cfg->stop_bits; + data->uart_config.data_bits = cfg->data_bits; + + /* Set baud rate */ + ret = uart_sc16is75x_set_baudrate(dev, cfg->baudrate); + if (ret != 0) { + goto end; + } + + /* Set flow control */ + switch (cfg->flow_ctrl) { + case UART_CFG_FLOW_CTRL_NONE: + ret = uart_sc16is75x_set_hw_flow_ctrl(dev, false); + if (ret != 0) { + goto end; + } + ret = uart_sc16is75x_use_modem_pins(dev, false); + if (ret != 0) { + goto end; + } + ret = uart_sc16is75x_set_rs485_flow_ctrl(dev, false); + if (ret != 0) { + goto end; + } + break; + + case UART_CFG_FLOW_CTRL_RTS_CTS: + ret = uart_sc16is75x_set_hw_flow_ctrl(dev, true); + if (ret != 0) { + goto end; + } + ret = uart_sc16is75x_use_modem_pins(dev, false); + if (ret != 0) { + goto end; + } + ret = uart_sc16is75x_set_rs485_flow_ctrl(dev, false); + if (ret != 0) { + goto end; + } + break; + + case UART_CFG_FLOW_CTRL_DTR_DSR: + ret = -ENOTSUP; + goto end; + + case UART_CFG_FLOW_CTRL_RS485: + ret = uart_sc16is75x_set_hw_flow_ctrl(dev, false); + if (ret != 0) { + goto end; + } + ret = uart_sc16is75x_use_modem_pins(dev, false); + if (ret != 0) { + goto end; + } + ret = uart_sc16is75x_set_rs485_flow_ctrl(dev, true); + if (ret != 0) { + goto end; + } + break; + + default: + ret = -EINVAL; + goto end; + } + +end: + k_mutex_unlock(&data->transaction_lock); + return ret; +} + +#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ + +static int uart_sc16is75x_config_get(const struct device *dev, + struct uart_config *cfg) +{ + struct uart_sc16is75x_data * const data = dev->data; + + *cfg = data->uart_config; + + return 0; +} + +#ifdef CONFIG_UART_LINE_CTRL + +static int uart_sc16is75x_line_ctrl_set(const struct device *dev, + uint32_t ctrl, uint32_t val) +{ + const struct uart_sc16is75x_config * const config = dev->config; + struct uart_sc16is75x_data * const data = dev->data; + uint8_t reg_val = 0; + uint8_t reg = 0; + uint8_t bit = 0; + int ret = 0; + + if (ctrl == UART_LINE_CTRL_BAUD_RATE) { + return uart_sc16is75x_set_baudrate(dev, val); + } + + switch (ctrl) { + case UART_LINE_CTRL_RTS: + /* RTS is in MCR[1] */ + reg = SC16IS75X_REG_MCR; + bit = SC16IS75X_BIT_MCR_RTS; + break; + + case UART_LINE_CTRL_DTR: + /* DTR is in MCR[0] */ + reg = SC16IS75X_REG_MCR; + bit = SC16IS75X_BIT_MCR_DTR; + break; + + case UART_LINE_CTRL_DCD: + /* CD is read-only */ + return -EINVAL; + + case UART_LINE_CTRL_DSR: + /* DSR is read-only */ + return -EINVAL; + + default: + return -EINVAL; + } + + /* Lock device before multi-transfer transaction */ + k_mutex_lock(&data->transaction_lock, K_FOREVER); + + ret = mfd_sc16is75x_read_register(config->bus, config->channel, + reg, ®_val); + if (ret != 0) { + goto end; + } + + WRITE_BIT(reg_val, bit, val); + + ret = mfd_sc16is75x_write_register(config->bus, config->channel, + reg, reg_val); + if (ret != 0) { + goto end; + } + +end: + k_mutex_unlock(&data->transaction_lock); + return ret; +} + +static int uart_sc16is75x_line_ctrl_get(const struct device *dev, + uint32_t ctrl, uint32_t *val) +{ + const struct uart_sc16is75x_config * const config = dev->config; + struct uart_sc16is75x_data * const data = dev->data; + uint8_t reg_val = 0; + uint8_t reg = 0; + uint8_t bit = 0; + int ret = 0; + + if (ctrl == UART_LINE_CTRL_BAUD_RATE) { + *val = data->uart_config.baudrate; + goto end; + } + + switch (ctrl) { + case UART_LINE_CTRL_RTS: + /* RTS is in MCR[1] */ + reg = SC16IS75X_REG_MCR; + bit = SC16IS75X_BIT_MCR_RTS; + break; + + case UART_LINE_CTRL_DTR: + /* DTR is in MCR[0] */ + reg = SC16IS75X_REG_MCR; + bit = SC16IS75X_BIT_MCR_DTR; + break; + + case UART_LINE_CTRL_DCD: + /* CD is in MSR[7] */ + reg = SC16IS75X_REG_MSR; + bit = SC16IS75X_BIT_MSR_CD; + break; + + case UART_LINE_CTRL_DSR: + /* DSR is in MSR[5] */ + reg = SC16IS75X_REG_MSR; + bit = SC16IS75X_BIT_MSR_DSR; + break; + + default: + return -EINVAL; + } + + ret = mfd_sc16is75x_read_register(config->bus, config->channel, + reg, ®_val); + if (ret != 0) { + goto end; + } + + *val = IS_BIT_SET(reg_val, bit); + +end: + return ret; +} + +#endif /* CONFIG_UART_LINE_CTRL */ + +#ifdef CONFIG_UART_SC16IS75X_INTERRUPT_DRIVEN + +static int uart_sc16is75x_fifo_fill(const struct device *dev, + const uint8_t *tx_data, int len) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t data_mut[SC16IS75X_FIFO_CAPACITY] = { 0 }; + uint8_t txlvl = 0; + int ret = 0; + + /* Check current FIFO fill level (number of bytes available) */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, TXLVL, &txlvl); + if (ret != 0) { + return ret; + } + + /* Calculate how many bytes to write */ + if (len > txlvl) { + len = txlvl; + } + + /* Copy data to respect pointer const-ness */ + memcpy(data_mut, tx_data, len); + + /* Write that many bytes to FIFO */ + ret = mfd_sc16is75x_write_fifo(config->bus, config->channel, + data_mut, len); + if (ret != 0) { + return ret; + } + + return len; +} + +static int uart_sc16is75x_fifo_read(const struct device *dev, + uint8_t *rx_data, const int size) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t rxlvl = 0; + int bytes_to_read; + int ret = 0; + + /* Check FIFO fill level */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, RXLVL, &rxlvl); + if (ret != 0) { + return ret; + } + + /* Calculate how many bytes we can read */ + bytes_to_read = rxlvl; + if (bytes_to_read > size) { + bytes_to_read = size; + } + + if (bytes_to_read == 0) { + return 0; + } + + /* Read that many bytes from FIFO */ + ret = mfd_sc16is75x_read_fifo(config->bus, config->channel, + rx_data, bytes_to_read); + if (ret != 0) { + return ret; + } + + return bytes_to_read; +} + +#ifdef CONFIG_UART_WIDE_DATA + +static int uart_sc16is75x_fifo_fill_u16(const struct device *dev, + const uint16_t *tx_data, int len) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t data_mut[SC16IS75X_FIFO_CAPACITY] = { 0 }; + uint8_t txlvl = 0; + int txlvl_words; + int len_bytes; + int ret = 0; + + /* Check curretn FIFO fill level (number of bytes available) */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, TXLVL, &txlvl); + if (ret != 0) { + return ret; + } + + /* Calculate how many words to write */ + txlvl_words = txlvl / 2; + + if (len > txlvl_words) { + len = txlvl_words; + } + + len_bytes = len / 2; + + /* + * Copy data to respect const-ness. + * Also cast pointer at this opportunity. + */ + memcpy(data_mut, (uint8_t *)tx_data, len_bytes); + + /* Write that many words to FIFO */ + ret = mfd_sc16is75x_write_fifo(config->bus, config->channel, + data_mut, len_bytes); + if (ret != 0) { + return ret; + } + + return len; +} + +static int uart_sc16is75x_fifo_read_u16(const struct device *dev, + uint16_t *rx_data, const int size) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t rxlvl = 0; + int rxlvl_words; + int words_to_read; + int bytes_to_read; + int ret = 0; + + /* Check FIFO fill level */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, RXLVL, &rxlvl); + if (ret != 0) { + return ret; + } + + /* Calculate how many words we can read */ + rxlvl_words = rxlvl / 2; + words_to_read = rxlvl_words; + + if (words_to_read > size) { + words_to_read = size; + } + + bytes_to_read = 2 * words_to_read; + + /* Read that many words from FIFO */ + ret = mfd_sc16is75x_read_fifo(config->bus, config->channel, + (uint8_t *)rx_data, bytes_to_read); + if (ret != 0) { + return ret; + } + + return words_to_read; +} + +#endif /* CONFIG_UART_WIDE_DATA */ + +static inline void uart_sc16is75x_irq_tx_set(const struct device *dev, + bool enable) +{ + const struct uart_sc16is75x_config * const config = dev->config; + + /* IER[1] enables THR interrupt */ + SETBIT_SC16IS75X_CHREG(config->bus, config->channel, IER, + SC16IS75X_BIT_IER_TXHSENA, enable); +} + +static void uart_sc16is75x_irq_tx_enable(const struct device *dev) +{ + uart_sc16is75x_irq_tx_set(dev, true); +} + +static void uart_sc16is75x_irq_tx_disable(const struct device *dev) +{ + uart_sc16is75x_irq_tx_set(dev, false); +} + +static int uart_sc16is75x_irq_tx_ready(const struct device *dev) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t txlvl = 0; + int ret = 0; + + /* TXLVL register holds number of bytes free in TX FIFO */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, TXLVL, &txlvl); + if (ret != 0) { + return 0; + } + + return (txlvl > 0); +} + +static int uart_sc16is75x_irq_tx_complete(const struct device *dev) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t lsr = 0; + int ret = 0; + + /* LSR[5] holds THR status (0: not empty, 1: empty) */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, LSR, &lsr); + if (ret != 0) { + return 0; + } + + return IS_BIT_SET(lsr, SC16IS75X_BIT_LSR_THREMPTY); +} + +static inline void uart_sc16is75x_irq_rx_set(const struct device *dev, + bool enable) +{ + const struct uart_sc16is75x_config * const config = dev->config; + + /* IER[0] enables RHR interrupt */ + SETBIT_SC16IS75X_CHREG(config->bus, config->channel, IER, + SC16IS75X_BIT_IER_RXHSENA, enable); +} + +static void uart_sc16is75x_irq_rx_enable(const struct device *dev) +{ + uart_sc16is75x_irq_rx_set(dev, true); +} + +static void uart_sc16is75x_irq_rx_disable(const struct device *dev) +{ + uart_sc16is75x_irq_rx_set(dev, false); +} + +static int uart_sc16is75x_irq_rx_ready(const struct device *dev) +{ + return uart_sc16is75x_rx_available(dev); +} + +static inline void uart_sc16is75x_irq_err_set(const struct device *dev, + bool enable) +{ + const struct uart_sc16is75x_config * const config = dev->config; + + /* IER[2] enables LSR interrupt */ + SETBIT_SC16IS75X_CHREG(config->bus, config->channel, IER, + SC16IS75X_BIT_IER_RXLSENA, enable); +} + +static void uart_sc16is75x_irq_err_enable(const struct device *dev) +{ + uart_sc16is75x_irq_err_set(dev, true); +} + +static void uart_sc16is75x_irq_err_disable(const struct device *dev) +{ + uart_sc16is75x_irq_err_set(dev, false); +} + +static int uart_sc16is75x_irq_is_pending(const struct device *dev) +{ + const struct uart_sc16is75x_config * const config = dev->config; + uint8_t iir = 0; + int ret = 0; + + /* + * It is only partially possible to read the IRQ pending bit from + * the read-only Interrupt Identification Register (IIR) without + * performing an immediate interrupt handling. If this were not + * done, interrupts would inevitably be lost. + */ + + /* Read out IIR */ + ret = READ_SC16IS75X_CHREG(config->bus, config->channel, IIR, &iir); + if (ret != 0) { + return 0; + } + + /* IIR[0] holds interrupt status (0: pending, 1: not pending) */ + return !IS_BIT_SET(iir, SC16IS75X_BIT_IIR_PENDING); +} + +static int uart_sc16is75x_irq_update(const struct device *dev) +{ + return 1; +} + +static void uart_sc16is75x_irq_callback_set(const struct device *dev, + uart_irq_callback_user_data_t cb, + void *user_data) +{ + struct uart_sc16is75x_data * const data = dev->data; + + data->callback = cb; + data->cb_data = user_data; +} + +static void uart_sc16is75x_interrupt_callback(const struct device *bus, + struct gpio_callback *cb, + gpio_port_pins_t event_pins) +{ + struct uart_sc16is75x_data * const data = CONTAINER_OF(cb, + struct uart_sc16is75x_data, interrupt_cb); + const struct device *dev = data->self; + + /* Call registered UART IRQ callback, if there is one */ + if (data->callback) { + data->callback(dev, data->cb_data); + } +} + +#endif /* CONFIG_UART_SC16IS75X_INTERRUPT_DRIVEN */ + +#ifdef CONFIG_UART_ASYNC_API + +static int uart_sc16is75x_callback_set(const struct device *dev, + uart_callback_t callback, + void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(callback); + ARG_UNUSED(user_data); + + return -ENOTSUP; +} + +#endif /* CONFIG_UART_ASYNC_API */ + +#ifdef CONFIG_UART_DRV_CMD + +static int uart_sc16is75x_drv_cmd(const struct device *dev, + uint32_t cmd, uint32_t p) +{ + ARG_UNUSED(dev); + ARG_UNUSED(cmd); + ARG_UNUSED(p); + + return -ENOTSUP; +} + +#endif /* CONFIG_UART_DRV_CMD */ + +static const struct uart_driver_api uart_sc16is75x_api = { + .poll_in = uart_sc16is75x_poll_in, + .poll_out = uart_sc16is75x_poll_out, +#ifdef CONFIG_UART_WIDE_DATA + .poll_in_u16 = uart_sc16is75x_poll_in_u16, + .poll_out_u16 = uart_sc16is75x_poll_out_u16, +#endif /* CONFIG_UART_WIDE_DATA */ + .err_check = uart_sc16is75x_err_check, +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE + .configure = uart_sc16is75x_configure, +#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ + .config_get = uart_sc16is75x_config_get, +#ifdef CONFIG_UART_LINE_CTRL + .line_ctrl_set = uart_sc16is75x_line_ctrl_set, + .line_ctrl_get = uart_sc16is75x_line_ctrl_get, +#endif /* CONFIG_UART_LINE_CTRL */ + +#ifdef CONFIG_UART_SC16IS75X_INTERRUPT_DRIVEN + .fifo_fill = uart_sc16is75x_fifo_fill, + .fifo_read = uart_sc16is75x_fifo_read, +#ifdef CONFIG_UART_WIDE_DATA + .fifo_fill_u16 = uart_sc16is75x_fifo_fill_u16, + .fifo_read_u16 = uart_sc16is75x_fifo_read_u16, +#endif /* CONFIG_UART_WIDE_DATA */ + .irq_tx_enable = uart_sc16is75x_irq_tx_enable, + .irq_tx_disable = uart_sc16is75x_irq_tx_disable, + .irq_tx_ready = uart_sc16is75x_irq_tx_ready, + .irq_tx_complete = uart_sc16is75x_irq_tx_complete, + .irq_rx_enable = uart_sc16is75x_irq_rx_enable, + .irq_rx_disable = uart_sc16is75x_irq_rx_disable, + .irq_rx_ready = uart_sc16is75x_irq_rx_ready, + .irq_err_enable = uart_sc16is75x_irq_err_enable, + .irq_err_disable = uart_sc16is75x_irq_err_disable, + .irq_is_pending = uart_sc16is75x_irq_is_pending, + .irq_update = uart_sc16is75x_irq_update, + .irq_callback_set = uart_sc16is75x_irq_callback_set, +#endif /* CONFIG_UART_SC16IS75X_INTERRUPT_DRIVEN */ + +#ifdef CONFIG_UART_ASYNC_API + .callback_set = uart_sc16is75x_callback_set, /* Not supported */ + .tx = NULL, + .tx_abort = NULL, + .rx_enable = NULL, + .rx_buf_rsp = NULL, + .rx_disable = NULL, +#ifdef CONFIG_UART_WIDE_DATA + .tx_u16 = NULL, + .rx_enable_u16 = NULL, + .rx_buf_rsp_u16 = NULL, +#endif /* CONFIG_UART_WIDE_DATA */ +#endif /* CONFIG_UART_ASYNC_API */ + +#ifdef CONFIG_UART_DRV_CMD + .uart_sc16is75x_drv_cmd = uart_sc16is75x_drv_cmd, +#endif /* CONFIG_UART_DRV_CMD */ + +}; + +static int uart_sc16is75x_init(const struct device *dev) +{ + const struct uart_sc16is75x_config * const config = dev->config; + struct uart_sc16is75x_data * const data = dev->data; +#ifdef CONFIG_UART_SC16IS75X_INTERRUPT_DRIVEN + gpio_port_pins_t event_pins; +#endif /* CONFIG_UART_SC16IS75X_INTERRUPT_DRIVEN */ + int ret = 0; + + /* Confirm (MFD) bridge readiness */ + if (!device_is_ready(config->bus)) { + LOG_ERR("%s: bridge device %s not ready", + dev->name, config->bus->name); + return -ENODEV; + } + + /* Initialize transaction lock */ + k_mutex_init(&data->transaction_lock); + + /* Apply initial config */ + ret = uart_sc16is75x_configure(dev, &data->uart_config); + if (ret != 0) { + LOG_ERR("%s: configure device failed: %d", dev->name, ret); + goto end; + } + + /* Enable internal FIFO */ + ret = uart_sc16is75x_reset_fifos(dev); + if (ret != 0) { + LOG_ERR("%s: reset FIFO failed: %d", dev->name, ret); + goto end; + } + ret = uart_sc16is75x_enable_fifo(dev, true); + if (ret != 0) { + LOG_ERR("%s: enable FIFO failed: %d", dev->name, ret); + goto end; + } + +#ifdef CONFIG_UART_SC16IS75X_INTERRUPT_DRIVEN + + /* Create back-reference for interrupt handling */ + data->self = dev; + + /* Set up interrupt handling */ + switch (config->channel) { + case 0: + event_pins = BIT(SC16IS75X_EVENT_UART0_RXLSE) + | BIT(SC16IS75X_EVENT_UART0_RXTO) + | BIT(SC16IS75X_EVENT_UART0_RHRI) + | BIT(SC16IS75X_EVENT_UART0_THRI); + /* no modem signals: BIT(SC16IS75X_EVENT_UART0_MSI) */ + /* no Xoff/Xon trigger: BIT(SC16IS75X_EVENT_UART0_XOFF) */ + /* no RTS/CTS trigger: BIT(SC16IS75X_EVENT_UART0_HWFL) */ + break; + case 1: + event_pins = BIT(SC16IS75X_EVENT_UART1_RXLSE) + | BIT(SC16IS75X_EVENT_UART1_RXTO) + | BIT(SC16IS75X_EVENT_UART1_RHRI) + | BIT(SC16IS75X_EVENT_UART1_THRI); + /* no modem signals: BIT(SC16IS75X_EVENT_UART1_MSI) */ + /* no Xoff/Xon trigger: BIT(SC16IS75X_EVENT_UART1_XOFF) */ + /* no RTS/CTS trigger: BIT(SC16IS75X_EVENT_UART1_HWFL) */ + break; + default: + return -ENODEV; + } + + gpio_init_callback(&data->interrupt_cb, + uart_sc16is75x_interrupt_callback, event_pins); + + ret = mfd_sc16is75x_add_callback(config->bus, &data->interrupt_cb); + if (ret != 0) { + LOG_ERR("%s: register interrupt callback failed: %d", + dev->name, ret); + goto end; + } + +#endif /* CONFIG_UART_SC16IS75X_INTERRUPT_DRIVEN */ + +#ifdef UART_SC16IS75X_LOOPBACK + /* Enable internal loopback */ + ret = uart_sc16is75x_enable_loopback(dev, true); + if (ret != 0) { + LOG_ERR("%s: enable internal loopback failed: %d", + dev->name, ret); + goto end; + } +#endif /* UART_SC16IS75X_LOOPBACK */ + + LOG_DBG("%s: ready for %u bps with bridge backend over %s!", + dev->name, data->uart_config.baudrate, config->bus->name); + +end: + return ret; +} + +#ifdef CONFIG_PM_DEVICE +static int uart_sc16is75x_pm_device_pm_action(const struct device * const dev, + enum pm_device_action action) +{ + ARG_UNUSED(dev); + ARG_UNUSED(action); + + return 0; +} +#endif + +#define UART_SC16IS75X_DEFINE(inst) \ + \ + static struct uart_sc16is75x_config uart_sc16is75x_config_##inst = \ + { \ + .bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \ + .channel = DT_INST_REG_ADDR(inst), \ + .clock_frequency = DT_PROP_BY_PHANDLE(DT_INST_PARENT(inst), \ + clock, \ + clock_frequency), \ + }; \ + BUILD_ASSERT( \ + DT_INST_REG_ADDR(inst) < SC16IS75X_UART_CHANNELS_MAX, \ + "Too many channels"); \ + \ + static struct uart_sc16is75x_data uart_sc16is75x_data_##inst = \ + { \ + .uart_config = { \ + .baudrate = DT_INST_PROP_OR(inst, \ + current_speed, 9600), \ + .parity = DT_INST_ENUM_IDX_OR(inst, \ + parity, UART_CFG_PARITY_EVEN), \ + .stop_bits = DT_INST_ENUM_IDX_OR(inst, \ + stop_bits, UART_CFG_STOP_BITS_2), \ + .data_bits = DT_INST_ENUM_IDX_OR(inst, \ + data_bits, UART_CFG_DATA_BITS_6), \ + .flow_ctrl = COND_CODE_1(DT_INST_PROP_OR(inst, \ + hw_flow_control, false), \ + (UART_CFG_FLOW_CTRL_RTS_CTS), \ + (UART_CFG_FLOW_CTRL_NONE)), \ + }, \ + }; \ + \ + PM_DEVICE_DT_INST_DEFINE(inst, uart_sc16is75x_pm_device_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(inst, uart_sc16is75x_init, \ + PM_DEVICE_DT_INST_GET(inst), \ + &uart_sc16is75x_data_##inst, \ + &uart_sc16is75x_config_##inst, POST_KERNEL, \ + CONFIG_UART_SC16IS75X_INIT_PRIORITY, \ + &uart_sc16is75x_api); + +DT_INST_FOREACH_STATUS_OKAY(UART_SC16IS75X_DEFINE); diff --git a/dts/bindings/gpio/nxp,sc16is75x-gpio.yaml b/dts/bindings/gpio/nxp,sc16is75x-gpio.yaml new file mode 100644 index 0000000000..5c64e8e429 --- /dev/null +++ b/dts/bindings/gpio/nxp,sc16is75x-gpio.yaml @@ -0,0 +1,22 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +description: GPIO controller part of an NXP SC16IS75x chip + +compatible: "nxp,sc16is75x-gpio" + +include: [gpio-controller.yaml] + +properties: + "#gpio-cells": + const: 2 + + ngpios: + required: true + const: 8 + +gpio-cells: + - pin + - flags + +on-bus: nxp,sc16is75x diff --git a/dts/bindings/mfd/nxp,sc16is75x-common.yaml b/dts/bindings/mfd/nxp,sc16is75x-common.yaml new file mode 100644 index 0000000000..f684cc12b1 --- /dev/null +++ b/dts/bindings/mfd/nxp,sc16is75x-common.yaml @@ -0,0 +1,31 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +description: NXP SC16IS75x UART/GPIO chip common properties + +include: base.yaml + +properties: + + reset-gpios: + required: true + type: phandle-array + description: Reset pin. + + interrupt-gpios: + type: phandle-array + description: Interrupt pin. + + clock: + required: true + type: phandle + + "#address-cells": + required: true + const: 1 + + "#size-cells": + required: true + const: 0 + +bus: nxp,sc16is75x diff --git a/dts/bindings/mfd/nxp,sc16is75x-i2c.yaml b/dts/bindings/mfd/nxp,sc16is75x-i2c.yaml new file mode 100644 index 0000000000..141301b944 --- /dev/null +++ b/dts/bindings/mfd/nxp,sc16is75x-i2c.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +description: NXP SC16IS75x UART/GPIO chip via I2C bus + +compatible: "nxp,sc16is75x" + +include: ["i2c-device.yaml", "nxp,sc16is75x-common.yaml"] diff --git a/dts/bindings/mfd/nxp,sc16is75x-spi.yaml b/dts/bindings/mfd/nxp,sc16is75x-spi.yaml new file mode 100644 index 0000000000..4b424ff0b9 --- /dev/null +++ b/dts/bindings/mfd/nxp,sc16is75x-spi.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +description: NXP SC16IS75x UART/GPIO chip via SPI bus + +compatible: "nxp,sc16is75x" + +include: ["spi-device.yaml", "nxp,sc16is75x-common.yaml"] diff --git a/dts/bindings/serial/nxp,sc16is75x.yaml b/dts/bindings/serial/nxp,sc16is75x.yaml new file mode 100644 index 0000000000..fd75081ec8 --- /dev/null +++ b/dts/bindings/serial/nxp,sc16is75x.yaml @@ -0,0 +1,10 @@ +# Copyright (c) 2024 TiaC Systems +# SPDX-License-Identifier: Apache-2.0 + +description: UART controller part of an NXP SC16IS75x chip + +compatible: "nxp,sc16is75x-uart" + +include: [uart-controller.yaml] + +on-bus: nxp,sc16is75x diff --git a/include/zephyr/drivers/mfd/sc16is75x.h b/include/zephyr/drivers/mfd/sc16is75x.h new file mode 100644 index 0000000000..eb9ea98c34 --- /dev/null +++ b/include/zephyr/drivers/mfd/sc16is75x.h @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2024 TiaC Systems + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_MFD_SC16IS75X_H_ +#define ZEPHYR_INCLUDE_DRIVERS_MFD_SC16IS75X_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include +#include + +#include + +/* + * SC16IS75X register bit/field access operations + */ + +#define IS_BIT_SET(reg, bit) (((reg >> bit) & (0x1)) != 0) + +#define FIELD_POS(field) field##_POS +#define FIELD_SIZE(field) field##_SIZE + +#define GET_FIELD(reg, field) \ + _GET_FIELD_(reg, FIELD_POS(field), FIELD_SIZE(field)) +#define _GET_FIELD_(reg, f_pos, f_size) (((reg) >> (f_pos)) & ((1 << (f_size)) - 1)) + +#define SET_FIELD(reg, field, value) \ + _SET_FIELD_(reg, FIELD_POS(field), FIELD_SIZE(field), value) +#define _SET_FIELD_(reg, f_pos, f_size, value) \ + ((reg) = ((reg) & (~(((1 << (f_size)) - 1) << (f_pos)))) \ + | ((value) << (f_pos))) + +#define GET_FIELD_POS(field) \ + _GET_FIELD_POS_(FIELD_POS(field)) +#define _GET_FIELD_POS_(f_ops) f_ops + +#define GET_FIELD_SZ(field) \ + _GET_FIELD_SZ_(FIELD_SIZE(field)) +#define _GET_FIELD_SZ_(f_ops) f_ops + +/* + * General registers + */ + +/* Receive Holding Register (RHR), read only */ +#define SC16IS75X_REG_RHR 0x00 + +/* Transmit Holding Register (THR), write only */ +#define SC16IS75X_REG_THR 0x00 + +/* Interrupt Enable Register (IER) */ +#define SC16IS75X_REG_IER 0x01 +#define SC16IS75X_BIT_IER_RXHSENA 0 +#define SC16IS75X_BIT_IER_TXHSENA 1 +#define SC16IS75X_BIT_IER_RXLSENA 2 +#define SC16IS75X_BIT_IER_MSENA 3 +#define SC16IS75X_BIT_IER_SLEEPENA 4 +#define SC16IS75X_BIT_IER_XOFFENA 5 +#define SC16IS75X_BIT_IER_RTSENA 6 +#define SC16IS75X_BIT_IER_CTSENA 7 + +/* FIFO Control Register (FCR), write only */ +#define SC16IS75X_REG_FCR 0x02 +#define SC16IS75X_BIT_FCR_FIFOENA 0 +#define SC16IS75X_BIT_FCR_RXFIFORST 1 +#define SC16IS75X_BIT_FCR_TXFIFORST 2 + /* reserved 3 */ +#define SC16IS75X_BIT_FCR_TXFIFOTRIG 4 +#define SC16IS75X_TXFIFOTRIG_8SP 0b00 +#define SC16IS75X_TXFIFOTRIG_16SP 0b01 +#define SC16IS75X_TXFIFOTRIG_32SP 0b10 +#define SC16IS75X_TXFIFOTRIG_56SP 0b11 +#define SC16IS75X_BIT_FCR_RXFIFOTRIG 6 +#define SC16IS75X_RXFIFOTRIG_8CH 0b00 +#define SC16IS75X_RXFIFOTRIG_16CH 0b01 +#define SC16IS75X_RXFIFOTRIG_56CH 0b10 +#define SC16IS75X_RXFIFOTRIG_60CH 0b11 + +/* Interrupt Identification Register (IIR), read only */ +#define SC16IS75X_REG_IIR 0x02 +#define SC16IS75X_BIT_IIR_PENDING 0 +#define SC16IS75X_BIT_IIR_TYPE_POS 1 +#define SC16IS75X_BIT_IIR_TYPE_SIZE 5 +#define SC16IS75X_INT_RXLSE 0b00011 +#define SC16IS75X_INT_RXTO 0b00110 +#define SC16IS75X_INT_RHRI 0b00010 +#define SC16IS75X_INT_THRI 0b00001 +#define SC16IS75X_INT_MSI 0b00000 +#define SC16IS75X_INT_IO 0b11000 +#define SC16IS75X_INT_XOFF 0b01000 +#define SC16IS75X_INT_HWFL 0b10000 +#define SC16IS75X_BIT_IIR_MIRROR_FIFOENA_POS 6 +#define SC16IS75X_BIT_IIR_MIRROR_FIFOENA_SIZE 2 +#define SC16IS75X_INT_FIFOENA 0b11 + +/* Line Control Register (LCR) */ +#define SC16IS75X_REG_LCR 0x03 + +#define SC16IS75X_BIT_LCR_WORD_LEN_POS 0 +#define SC16IS75X_BIT_LCR_WORD_LEN_SIZE 2 +#define SC16IS75X_WORD_LEN_5 0b00 +#define SC16IS75X_WORD_LEN_6 0b01 +#define SC16IS75X_WORD_LEN_7 0b10 +#define SC16IS75X_WORD_LEN_8 0b11 +#define SC16IS75X_BIT_LCR_STOP_LEN_POS 2 +#define SC16IS75X_BIT_LCR_STOP_LEN_SIZE 1 +#define SC16IS75X_STOP_LEN_1 0b0 +#define SC16IS75X_STOP_LEN_1_5 0b1 +#define SC16IS75X_STOP_LEN_2 0b1 +#define SC16IS75X_BIT_LCR_PARITY_POS 3 +#define SC16IS75X_BIT_LCR_PARITY_SIZE 3 +#define SC16IS75X_PARITY_NONE 0b000 +#define SC16IS75X_PARITY_ODD 0b001 +#define SC16IS75X_PARITY_EVEN 0b011 +#define SC16IS75X_PARITY_MARK 0b101 +#define SC16IS75X_PARITY_SPACE 0b111 +#define SC16IS75X_BIT_LCR_TX_LINE_BREAK 6 +#define SC16IS75X_BIT_LCR_DLENA 7 +#define SC16IS75X_BIT_LCR_ACCESS_EFR 0b10111111 /* 0xBF */ + +/* Modem Control Register (MCR) */ +#define SC16IS75X_REG_MCR 0x04 +#define SC16IS75X_BIT_MCR_DTR 0 +#define SC16IS75X_BIT_MCR_RTS 1 +#define SC16IS75X_BIT_MCR_TCRTLRENA 2 + /* reserved 3 */ +#define SC16IS75X_BIT_MCR_LOOPBACK 4 +#define SC16IS75X_BIT_MCR_XONANY 5 +#define SC16IS75X_BIT_MCR_IRDAENA 6 +#define SC16IS75X_BIT_MCR_CLKDIV 7 + +/* Line Status Register (LSR), read only */ +#define SC16IS75X_REG_LSR 0x05 +#define SC16IS75X_BIT_LSR_RX_DATA 0 +#define SC16IS75X_BIT_LSR_RX_OVERRUN 1 +#define SC16IS75X_BIT_LSR_RX_PARITY 2 +#define SC16IS75X_BIT_LSR_RX_FRAMING 3 +#define SC16IS75X_BIT_LSR_RX_LINE_BREAK 4 +#define SC16IS75X_BIT_LSR_THREMPTY 5 +#define SC16IS75X_BIT_LSR_THRTSREMPTY 6 +#define SC16IS75X_BIT_LSR_RX_FIFO 7 + +/* Modem Status Register (MSR), read only */ +#define SC16IS75X_REG_MSR 0x06 +#define SC16IS75X_BIT_MSR_CTS_CHANGED 0 +#define SC16IS75X_BIT_MSR_DSR_CHANGED 1 +#define SC16IS75X_BIT_MSR_RI_CHANGED 2 +#define SC16IS75X_BIT_MSR_CD_CHANGED 3 +#define SC16IS75X_BIT_MSR_CTS 4 +#define SC16IS75X_BIT_MSR_DSR 5 +#define SC16IS75X_BIT_MSR_RI 6 +#define SC16IS75X_BIT_MSR_CD 7 + +/* Scratchpad Register (SPR) */ +#define SC16IS75X_REG_SPR 0x07 + +/* Transmission Control Register (TCR) */ +#define SC16IS75X_REG_TCR 0x06 + +/* Trigger Level Register (TLR) */ +#define SC16IS75X_REG_TLR 0x07 + +/* Transmitter FIFO Level register (TXLVL), read only */ +#define SC16IS75X_REG_TXLVL 0x08 +#define SC16IS75X_BIT_TXLVL_SP_POS 0 +#define SC16IS75X_BIT_TXLVL_SP_SIZE 7 + +/* Receiver FIFO Level register (RXLVL), read only */ +#define SC16IS75X_REG_RXLVL 0x09 +#define SC16IS75X_BIT_RXLVL_CH_POS 0 +#define SC16IS75X_BIT_RXLVL_CH_SIZE 7 + +/* Programmable I/O pins Direction register (IODir), all channels */ +#define SC16IS75X_REG_IODIR 0x0A + +/* Programmable I/O pins State register (IOState), all channels */ +#define SC16IS75X_REG_IOSTATE 0x0B + +/* Programmable I/O Interrupt Enable register (IOIntEna), all channels */ +#define SC16IS75X_REG_IOINTENA 0x0C + +/* Programmable I/O Control register (IOControl), all channels */ +#define SC16IS75X_REG_IOCONTROL 0x0E +#define SC16IS75X_BIT_IOCONTROL_IOLATCH 0 +#define SC16IS75X_BIT_IOCONTROL_MODEM_PINS_A 1 +#define SC16IS75X_BIT_IOCONTROL_MODEM_PINS_B 2 +#define SC16IS75X_BIT_IOCONTROL_SRESET 3 + /* reserved 4:7 */ + +/* Extra Features Control Register (EFCR) */ +#define SC16IS75X_REG_EFCR 0x0F +#define SC16IS75X_BIT_EFCR_RS485 0 +#define SC16IS75X_BIT_EFCR_RXDISABLE 1 +#define SC16IS75X_BIT_EFCR_TXDISABLE 2 + /* reserved 3 */ +#define SC16IS75X_BIT_EFCR_RTSCON 4 +#define SC16IS75X_BIT_EFCR_RTSINVER 5 + /* reserved 6 */ +#define SC16IS75X_BIT_EFCR_IRDA 7 + +/* + * Special registers + */ + +/* Division registers (DLL, DLH) */ +#define SC16IS75X_REG_DLL 0x00 +#define SC16IS75X_REG_DLH 0x01 + +/* + * Enhanced registers + */ + +/* Enhanced Features Register (EFR) */ +#define SC16IS75X_REG_EFR 0x02 +#define SC16IS75X_BIT_EFR_RX_SWFLOWCTRL_POS 0 +#define SC16IS75X_BIT_EFR_RX_SWFLOWCTRL_SIZE 2 +#define SC16IS75X_BIT_EFR_TX_SWFLOWCTRL_POS 2 +#define SC16IS75X_BIT_EFR_TX_SWFLOWCTRL_SIZE 2 +#define SC16IS75X_SWFLOWCTRL_NONE 0b00 +#define SC16IS75X_SWFLOWCTRL_XONOFF1 0b10 +#define SC16IS75X_SWFLOWCTRL_XONOFF2 0b01 +#define SC16IS75X_SWFLOWCTRL_XONOFF12 0b11 +#define SC16IS75X_BIT_EFR_EFENA 4 +#define SC16IS75X_BIT_EFR_XOFF2ENA 5 +#define SC16IS75X_BIT_EFR_RTSENA 6 +#define SC16IS75X_BIT_EFR_CTSENA 7 + +/* Xon1 Special Character Register (XON1) */ +#define SC16IS75X_REG_XON1 0x04 + +/* Xoff2 Special Character Register (XOFF2) */ +#define SC16IS75X_REG_XON2 0x05 + +/* Xon1 Special Character Register (XON1) */ +#define SC16IS75X_REG_XOFF1 0x06 + +/* Xoff2 Special Character Register (XOFF2) */ +#define SC16IS75X_REG_XOFF2 0x07 + +/** + * @defgroup mfd_interface_sc16is75x MFD SC16IS75X Interface + * @ingroup mfd_interfaces + * @{ + */ + +/** FIFO capacity in byte */ +#define SC16IS75X_FIFO_CAPACITY 64 + +/** Maximum programmable I/O pins */ +#define SC16IS75X_IO_NUM_PINS_MAX 8 + +/** Maximum UART channels */ +#define SC16IS75X_UART_CHANNELS_MAX 2 + +/** + * @brief Read from an internal register. + * + * @param dev An SC16IS75x device. + * @param channel Channel to access. + * @param reg Register address to read from. + * @param[out] value Value of that register. + * @retval 0 On succes. + * @return Negative error code on failure. + */ +int mfd_sc16is75x_read_register(const struct device *dev, + const uint8_t channel, + const uint8_t reg, uint8_t *value); + +#define READ_SC16IS75X_REG(dev, reg, val) \ + mfd_sc16is75x_read_register((dev), 0, SC16IS75X_REG_##reg, (val)); + +#define READ_SC16IS75X_CHREG(dev, ch, reg, val) \ + mfd_sc16is75x_read_register((dev), (ch), SC16IS75X_REG_##reg, (val)); + +/** + * @brief Write to an internal register. + * + * @param dev An SC16IS75x device. + * @param channel Channel to access. + * @param reg Register address to write to. + * @param value Value to be written. + * @retval 0 On success. + * @return Negative error code on failure. + */ +int mfd_sc16is75x_write_register(const struct device *dev, + const uint8_t channel, + const uint8_t reg, const uint8_t value); + +#define WRITE_SC16IS75X_REG(dev, reg, val) \ + mfd_sc16is75x_write_register((dev), 0, SC16IS75X_REG_##reg, (val)); + +#define WRITE_SC16IS75X_CHREG(dev, ch, reg, val) \ + mfd_sc16is75x_write_register((dev), (ch), SC16IS75X_REG_##reg, (val)); + +/** + * @brief Enable or disable a bit in an internal register. + * + * @param dev An SC16IS75x device. + * @param channel Channel to access. + * @param reg Register address to change the bit. + * @param bit Bit number to change. + * @param value Value to be written (true = set to one, false = set to zero). + * @retval 0 On success. + * @return Negative error code on failure. + */ +int mfd_sc16is75x_set_register_bit(const struct device *dev, + const uint8_t channel, const uint8_t reg, + const uint8_t bit, const bool value); + +#define SETBIT_SC16IS75X_REG(dev, reg, bit, val) \ + mfd_sc16is75x_set_register_bit((dev), 0, SC16IS75X_REG_##reg, \ + (bit), (val)); + +#define SETBIT_SC16IS75X_CHREG(dev, ch, reg, bit, val) \ + mfd_sc16is75x_set_register_bit((dev), (ch), SC16IS75X_REG_##reg, \ + (bit), (val)); + +/** + * @brief Read data from FIFO. + * + * @param dev An SC16IS75x device. + * @param channel Channel whose FIFO to read. + * @param[out] buf Data read from FIFO. + * @param len Number of bytes to read. + * @retval 0 On success. + * @return Negative error code on failure. + */ +int mfd_sc16is75x_read_fifo(const struct device *dev, const uint8_t channel, + uint8_t *buf, const size_t len); + +/** + * @brief Write data to FIFO. + * + * @param dev An SC16IS75x device. + * @param channel Channel whose FIFO to read. + * @param buf Data to be written. + * @param len Number of bytes to write. + * @retval 0 On success. + * @return Negative error code on failure. + */ +int mfd_sc16is75x_write_fifo(const struct device *dev, const uint8_t channel, + const uint8_t *buf, const size_t len); + +#ifdef CONFIG_MFD_SC16IS75X_ASYNC + +/** + * @brief Read from an internal register asynchronously. + * + * @param dev An SC16IS75x device. + * @param channel Channel to access. + * @param reg Register address to read from. + * @param[out] value Value of that register. + * @retval 0 On succes. + * @return Negative error code on failure. + */ +int mfd_sc16is75x_read_register_signal(const struct device *dev, + const uint8_t channel, + const uint8_t reg, uint8_t *value, + struct k_poll_signal *signal); + +#define READ_SC16IS75X_CHREG_SIGNAL(dev, ch, reg, val, signal) \ + mfd_sc16is75x_read_register_signal((dev), (ch), SC16IS75X_REG_##reg, \ + (val), (signal)); + +#endif /* CONFIG_MFD_SC16IS75X_ASYNC */ + +#ifdef CONFIG_MFD_SC16IS75X_INTERRUPTS + +/** + * @brief Emitted event bits. + * + * These values should be used with the @ref event callback functions. + * + * - @c UART0 and @c UART1 means UART channel 0/1 (A/B). + * - @c IO0 and @c IO1 means GPIO, the underlying GPIO driver should + * decide for itself whether it needs this channel separation or not. + */ +enum mfd_sc16is75x_event { + /* channel 0 (A) */ + SC16IS75X_EVENT_UART0_RXLSE, /**< RX0 Line Status Error in LSR0 */ + SC16IS75X_EVENT_UART0_RXTO, /**< RX0 Break/Time-Out, TLR0 underrun */ + SC16IS75X_EVENT_UART0_RHRI, /**< RX0 FIFO (RHR0), RXLVL0 > TLR0 */ + SC16IS75X_EVENT_UART0_THRI, /**< TX0 FIFO (THR0), TXLVL0 < TLR0 */ + SC16IS75X_EVENT_UART0_MSI, /**< Modem Status in MSR0 */ + SC16IS75X_EVENT_IO0_STATE, /**< I/O Pin in IOSTATE0 */ + SC16IS75X_EVENT_UART0_XOFF, /**< Xoff char from XOFF0 seen */ + SC16IS75X_EVENT_UART0_HWFL, /**< RTS0 or CTS0 change seen */ + /* channel 1 (B) */ + SC16IS75X_EVENT_UART1_RXLSE, /**< RX1 Line Status Error in LSR1 */ + SC16IS75X_EVENT_UART1_RXTO, /**< RX1 Break/Time-Out, TLR1 underrun */ + SC16IS75X_EVENT_UART1_RHRI, /**< RX1 FIFO (RHR1), RXLVL1 > TLR1 */ + SC16IS75X_EVENT_UART1_THRI, /**< TX1 FIFO (THR1), TXLVL1 < TLR1 */ + SC16IS75X_EVENT_UART1_MSI, /**< Modem Status in MSR1 */ + SC16IS75X_EVENT_IO1_STATE, /**< I/O Pin in IOSTATE1 */ + SC16IS75X_EVENT_UART1_XOFF, /**< Xoff char from XOFF1 seen */ + SC16IS75X_EVENT_UART1_HWFL, /**< RTS1 or CTS1 change seen */ + /* must be last entry */ + SC16IS75X_EVENT_MAX +}; + +/** + * @brief Add an event callback + * + * @param dev An SC16IS75x device. + * @param callback The child callback to add. + * @retval 0 On success. + * @return Negative error code on failure. + */ +int mfd_sc16is75x_add_callback(const struct device *dev, + struct gpio_callback *callback); + +/** + * @brief Remove an event callback + * + * @param dev An SC16IS75x device. + * @param callback The child callback to remove. + * @retval 0 On success. + * @return Negative error code on failure. + */ +int mfd_sc16is75x_remove_callback(const struct device *dev, + struct gpio_callback *callback); + + +#endif /* CONFIG_MFD_SC16IS75X_INTERRUPTS */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_MFD_SC16IS75X_H_ */ diff --git a/snippets/tstdrv-bldall-display-adj/tstdrv-bldall-display-adj.overlay b/snippets/tstdrv-bldall-display-adj/tstdrv-bldall-display-adj.overlay index c0e1f0c14e..7765581388 100644 --- a/snippets/tstdrv-bldall-display-adj/tstdrv-bldall-display-adj.overlay +++ b/snippets/tstdrv-bldall-display-adj/tstdrv-bldall-display-adj.overlay @@ -34,6 +34,14 @@ clock-frequency = <100000>; }; + test_i3c_adj: i3c@bbbbaaaa { + compatible = "vnd,i3c"; + status = "okay"; + reg = <0xbbbbaaaa 0x1000>; + #address-cells = <3>; + #size-cells = <0>; + }; + test_spi_adj: spi@ccccdddd { compatible = "vnd,spi"; status = "okay"; @@ -189,6 +197,15 @@ reg = <0xeeeeffff 0x1000>; }; + test_w1_adj: w1@ffffeeee { + compatible = "vnd,w1"; + status = "okay"; + reg = <0xffffeeee 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + ranges = <0 0>; + }; + test_mipi_dbi_adj { compatible = "zephyr,mipi-dbi-spi"; status = "okay"; @@ -198,7 +215,7 @@ dc-gpios = <&test_gpio_adj 0 0>; spi-dev = <&test_spi_adj>; - test_mipi_dbi_ili9488: ili9342c@10 { + test_mipi_dbi_ili9488: ili9342c@a { compatible = "ilitek,ili9488"; status = "okay"; reg = <10>; @@ -220,7 +237,7 @@ dc-gpios = <&test_gpio_adj 0 0>; spi-dev = <&test_spi_adj>; - test_mipi_dbi_xfr_16bit_ili9488: ili9342c@20 { + test_mipi_dbi_xfr_16bit_ili9488: ili9342c@14 { compatible = "ilitek,ili9488"; status = "okay"; reg = <20>; diff --git a/snippets/tstdrv-bldall-gpio-adj/tstdrv-bldall-gpio-adj.conf b/snippets/tstdrv-bldall-gpio-adj/tstdrv-bldall-gpio-adj.conf index 9d004a8d0c..9fdb845ab6 100644 --- a/snippets/tstdrv-bldall-gpio-adj/tstdrv-bldall-gpio-adj.conf +++ b/snippets/tstdrv-bldall-gpio-adj/tstdrv-bldall-gpio-adj.conf @@ -4,3 +4,5 @@ CONFIG_I2C=y CONFIG_GPIO_PCA9554_INTERRUPT=y CONFIG_GPIO_PCA9555_INTERRUPT=y + +CONFIG_DYNAMIC_THREAD_POOL_SIZE=2 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 7bd591c5e2..114296b2c7 100644 --- a/snippets/tstdrv-bldall-gpio-adj/tstdrv-bldall-gpio-adj.overlay +++ b/snippets/tstdrv-bldall-gpio-adj/tstdrv-bldall-gpio-adj.overlay @@ -15,6 +15,13 @@ }; &test { + test_fixed_clk_adj: test-fixed-clk-adj { + compatible = "fixed-clock"; + status = "okay"; + #clock-cells = <0>; + clock-frequency = <0>; + }; + test_gpio_adj: gpio@feedface { compatible = "vnd,gpio"; status = "okay"; @@ -50,6 +57,34 @@ interrupt-gpios = <&test_gpio 0 0>; rst-dflts; }; + + test_i2c_sc16is75x: sc16is75x-i2c@3 { + compatible = "nxp,sc16is75x"; + status = "okay"; + reg = <0x3>; + #address-cells = <1>; + #size-cells = <0>; + ranges; + clock = <&test_fixed_clk_adj>; + reset-gpios = <&test_gpio_adj 0 0>; + interrupt-gpios = <&test_gpio_adj 0 0>; + + test_i2c_sc16is75x_gpio: sc16is75x-i2c-gpio { + compatible = "nxp,sc16is75x-gpio"; + status = "okay"; + gpio-controller; + #gpio-cells = <2>; + ngpios = <8>; + }; + }; + }; + + test_i3c_adj: i3c@bbbbaaaa { + compatible = "vnd,i3c"; + status = "okay"; + reg = <0xbbbbaaaa 0x1000>; + #address-cells = <3>; + #size-cells = <0>; }; test_spi_adj: spi@ccccdddd { @@ -65,6 +100,7 @@ <&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>; test_spi_sipo_mux_gp_08: sipo-mux-gp-08@0 { @@ -261,6 +297,27 @@ ngpios = <5>; }; }; + + test_spi_sc16is75x: sc16is75x-spi@5 { + compatible = "nxp,sc16is75x"; + status = "okay"; + reg = <0x5>; + spi-max-frequency = <0>; + #address-cells = <1>; + #size-cells = <0>; + ranges; + clock = <&test_fixed_clk_adj>; + reset-gpios = <&test_gpio_adj 0 0>; + interrupt-gpios = <&test_gpio_adj 0 0>; + + test_spi_sc16is75x_gpio: sc16is75x-spi-gpio { + compatible = "nxp,sc16is75x-gpio"; + status = "okay"; + gpio-controller; + #gpio-cells = <2>; + ngpios = <8>; + }; + }; }; test_uart_adj: uart@eeeeffff { @@ -268,4 +325,13 @@ status = "okay"; reg = <0xeeeeffff 0x1000>; }; + + test_w1_adj: w1@ffffeeee { + compatible = "vnd,w1"; + status = "okay"; + reg = <0xffffeeee 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + ranges = <0 0>; + }; }; 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 aafa041233..6945895549 100644 --- a/snippets/tstdrv-bldall-i2c-adj/tstdrv-bldall-i2c-adj.overlay +++ b/snippets/tstdrv-bldall-i2c-adj/tstdrv-bldall-i2c-adj.overlay @@ -35,7 +35,15 @@ clock-frequency = <100000>; }; - test_spi_adj: spi@bbbbcccc { + test_i3c_adj: i3c@bbbbaaaa { + compatible = "vnd,i3c"; + status = "okay"; + reg = <0xbbbbaaaa 0x1000>; + #address-cells = <3>; + #size-cells = <0>; + }; + + test_spi_adj: spi@ccccdddd { compatible = "vnd,spi"; status = "okay"; reg = <0xccccdddd 0x1000>; @@ -64,9 +72,18 @@ }; }; - test_uart_adj: uart@ddddeeee { + test_uart_adj: uart@eeeeffff { compatible = "vnd,serial"; status = "okay"; - reg = <0xddddeeee 0x1000>; + reg = <0xeeeeffff 0x1000>; + }; + + test_w1_adj: w1@ffffeeee { + compatible = "vnd,w1"; + status = "okay"; + reg = <0xffffeeee 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + ranges = <0 0>; }; }; 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 042d5da043..99d88022f7 100644 --- a/snippets/tstdrv-bldall-mfd-adj/tstdrv-bldall-mfd-adj.overlay +++ b/snippets/tstdrv-bldall-mfd-adj/tstdrv-bldall-mfd-adj.overlay @@ -15,6 +15,13 @@ }; &test { + test_fixed_clk_adj: test-fixed-clk-adj { + compatible = "fixed-clock"; + status = "okay"; + #clock-cells = <0>; + clock-frequency = <0>; + }; + test_gpio_adj: gpio@feedface { compatible = "vnd,gpio"; status = "okay"; @@ -48,6 +55,45 @@ status = "okay"; }; }; + + test_i2c_sc16is75x: sc16is75x-i2c@2 { + compatible = "nxp,sc16is75x"; + status = "okay"; + reg = <0x2>; + #address-cells = <1>; + #size-cells = <0>; + clock = <&test_fixed_clk_adj>; + reset-gpios = <&test_gpio_adj 0 0>; + interrupt-gpios = <&test_gpio_adj 0 0>; + + test_i2c_sc16is75x_gpio: sc16is75x-i2c-gpio { + compatible = "nxp,sc16is75x-gpio"; + status = "okay"; + gpio-controller; + #gpio-cells = <2>; + ngpios = <8>; + }; + + test_i2c_sc16is75x_uart_0: sc16is75x-i2c-uart@0 { + compatible = "nxp,sc16is75x-uart"; + status = "okay"; + reg = <0>; + }; + + test_i2c_sc16is75x_uart_1: sc16is75x-i2c-uart@1 { + compatible = "nxp,sc16is75x-uart"; + status = "okay"; + reg = <1>; + }; + }; + }; + + test_i3c_adj: i3c@bbbbaaaa { + compatible = "vnd,i3c"; + status = "okay"; + reg = <0xbbbbaaaa 0x1000>; + #address-cells = <3>; + #size-cells = <0>; }; test_spi_adj: spi@ccccdddd { @@ -60,6 +106,7 @@ /* one entry for every devices */ cs-gpios = <&test_gpio_adj 0 0>, + <&test_gpio_adj 0 0>, <&test_gpio_adj 0 0>; test_spi_sipo_mux_gp: sipo-mux-gp@0 { @@ -120,6 +167,38 @@ clock-frequency = <100000>; }; }; + + test_spi_sc16is75x: sc16is75x-spi@2 { + compatible = "nxp,sc16is75x"; + status = "okay"; + reg = <0x2>; + spi-max-frequency = <0>; + #address-cells = <1>; + #size-cells = <0>; + clock = <&test_fixed_clk_adj>; + reset-gpios = <&test_gpio_adj 0 0>; + interrupt-gpios = <&test_gpio_adj 0 0>; + + test_spi_sc16is75x_gpio: sc16is75x-spi-gpio { + compatible = "nxp,sc16is75x-gpio"; + status = "okay"; + gpio-controller; + #gpio-cells = <2>; + ngpios = <8>; + }; + + test_spi_sc16is75x_uart_0: sc16is75x-spi-uart@0 { + compatible = "nxp,sc16is75x-uart"; + status = "okay"; + reg = <0>; + }; + + test_spi_sc16is75x_uart_1: sc16is75x-spi-uart@1 { + compatible = "nxp,sc16is75x-uart"; + status = "okay"; + reg = <1>; + }; + }; }; test_uart_adj: uart@eeeeffff { @@ -127,4 +206,13 @@ status = "okay"; reg = <0xeeeeffff 0x1000>; }; + + test_w1_adj: w1@ffffeeee { + compatible = "vnd,w1"; + status = "okay"; + reg = <0xffffeeee 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + ranges = <0 0>; + }; }; diff --git a/snippets/tstdrv-bldall-rtc-adj/tstdrv-bldall-rtc-adj.overlay b/snippets/tstdrv-bldall-rtc-adj/tstdrv-bldall-rtc-adj.overlay index 957b2efe96..12afd8a126 100644 --- a/snippets/tstdrv-bldall-rtc-adj/tstdrv-bldall-rtc-adj.overlay +++ b/snippets/tstdrv-bldall-rtc-adj/tstdrv-bldall-rtc-adj.overlay @@ -48,7 +48,15 @@ }; }; - test_spi_adj: spi@bbbbcccc { + test_i3c_adj: i3c@bbbbaaaa { + compatible = "vnd,i3c"; + status = "okay"; + reg = <0xbbbbaaaa 0x1000>; + #address-cells = <3>; + #size-cells = <0>; + }; + + test_spi_adj: spi@ccccdddd { compatible = "vnd,spi"; status = "okay"; reg = <0xccccdddd 0x1000>; @@ -60,9 +68,18 @@ /* cs-gpios = <&test_gpio_adj 0 0>; */ }; - test_uart_adj: uart@ddddeeee { + test_uart_adj: uart@eeeeffff { compatible = "vnd,serial"; status = "okay"; - reg = <0xddddeeee 0x1000>; + reg = <0xeeeeffff 0x1000>; + }; + + test_w1_adj: w1@ffffeeee { + compatible = "vnd,w1"; + status = "okay"; + reg = <0xffffeeee 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + ranges = <0 0>; }; }; diff --git a/snippets/tstdrv-bldall-sensor-adj/tstdrv-bldall-sensor-adj.overlay b/snippets/tstdrv-bldall-sensor-adj/tstdrv-bldall-sensor-adj.overlay index 8ebef41b4d..d4b94f14c8 100644 --- a/snippets/tstdrv-bldall-sensor-adj/tstdrv-bldall-sensor-adj.overlay +++ b/snippets/tstdrv-bldall-sensor-adj/tstdrv-bldall-sensor-adj.overlay @@ -113,5 +113,29 @@ reg = <0xffffeeee 0x1000>; #address-cells = <1>; #size-cells = <0>; + ranges = <0 0>; + }; +}; + +/* FIX: /test/w1@66660000: unnecessary #address-cells/#size-cells + * without "ranges" or child "reg" property + * OR: /test/w1@66660000:ranges: empty "ranges" property + * but its #size-cells (0) differs from /test (1) + */ +&test_w1 { + ranges = <0 0>; +}; + +/* FIX: /test/i2c@11112222/lis2dux12@8a: duplicate unit-address + * (also used in node /test/i2c@11112222/iis328dq@8a) + */ +/delete-node/ &test_i2c_iis328dq; +&test_i2c_adj { + test_i2c_iis328dq: iis328dq@8a { + compatible = "st,iis328dq"; + status = "okay"; + reg = <0x8a>; + int2-gpios = <&test_gpio 0 0>; + threshold-int-pad = <2>; }; }; diff --git a/snippets/tstdrv-bldall-uart-adj/tstdrv-bldall-uart-adj.conf b/snippets/tstdrv-bldall-uart-adj/tstdrv-bldall-uart-adj.conf index e9ca22677a..48df856265 100644 --- a/snippets/tstdrv-bldall-uart-adj/tstdrv-bldall-uart-adj.conf +++ b/snippets/tstdrv-bldall-uart-adj/tstdrv-bldall-uart-adj.conf @@ -1,2 +1,12 @@ # Copyright (c) 2024 TiaC Systems # SPDX-License-Identifier: Apache-2.0 + +CONFIG_MFD=y +CONFIG_I2C=y +CONFIG_SPI=y +CONFIG_GPIO=y +CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_UART_ASYNC_API=y + +CONFIG_DYNAMIC_THREAD_POOL_SIZE=2 diff --git a/snippets/tstdrv-bldall-uart-adj/tstdrv-bldall-uart-adj.overlay b/snippets/tstdrv-bldall-uart-adj/tstdrv-bldall-uart-adj.overlay index deff50c02c..1889af0831 100644 --- a/snippets/tstdrv-bldall-uart-adj/tstdrv-bldall-uart-adj.overlay +++ b/snippets/tstdrv-bldall-uart-adj/tstdrv-bldall-uart-adj.overlay @@ -18,6 +18,13 @@ }; &test { + test_fixed_clk_adj: test-fixed-clk-adj { + compatible = "fixed-clock"; + status = "okay"; + #clock-cells = <0>; + clock-frequency = <0>; + }; + test_gpio_adj: gpio@feedface { compatible = "vnd,gpio"; status = "okay"; @@ -33,9 +40,40 @@ #address-cells = <1>; #size-cells = <0>; clock-frequency = <100000>; + + test_i2c_sc16is75x: sc16is75x-i2c@2 { + compatible = "nxp,sc16is75x"; + status = "okay"; + reg = <0x2>; + #address-cells = <1>; + #size-cells = <0>; + clock = <&test_fixed_clk_adj>; + reset-gpios = <&test_gpio_adj 0 0>; + interrupt-gpios = <&test_gpio_adj 0 0>; + + test_i2c_sc16is75x_uart_0: sc16is75x-i2c-uart@0 { + compatible = "nxp,sc16is75x-uart"; + status = "okay"; + reg = <0>; + }; + + test_i2c_sc16is75x_uart_1: sc16is75x-i2c-uart@1 { + compatible = "nxp,sc16is75x-uart"; + status = "okay"; + reg = <1>; + }; + }; + }; + + test_i3c_adj: i3c@bbbbaaaa { + compatible = "vnd,i3c"; + status = "okay"; + reg = <0xbbbbaaaa 0x1000>; + #address-cells = <3>; + #size-cells = <0>; }; - test_spi_adj: spi@bbbbcccc { + test_spi_adj: spi@ccccdddd { compatible = "vnd,spi"; status = "okay"; reg = <0xccccdddd 0x1000>; @@ -44,12 +82,45 @@ clock-frequency = <2000000>; /* one entry for every devices */ - /* cs-gpios = <&test_gpio_adj 0 0>; */ + cs-gpios = <&test_gpio_adj 0 0>; + + test_spi_sc16is75x: sc16is75x-spi@0 { + compatible = "nxp,sc16is75x"; + status = "okay"; + reg = <0x0>; + spi-max-frequency = <0>; + #address-cells = <1>; + #size-cells = <0>; + clock = <&test_fixed_clk_adj>; + reset-gpios = <&test_gpio_adj 0 0>; + interrupt-gpios = <&test_gpio_adj 0 0>; + + test_spi_sc16is75x_uart_0: sc16is75x-spi-uart@0 { + compatible = "nxp,sc16is75x-uart"; + status = "okay"; + reg = <0>; + }; + + test_spi_sc16is75x_uart_1: sc16is75x-spi-uart@1 { + compatible = "nxp,sc16is75x-uart"; + status = "okay"; + reg = <1>; + }; + }; }; - test_uart_adj: uart@ddddeeee { + test_uart_adj: uart@eeeeffff { compatible = "vnd,serial"; status = "okay"; - reg = <0xddddeeee 0x1000>; + reg = <0xeeeeffff 0x1000>; + }; + + test_w1_adj: w1@ffffeeee { + compatible = "vnd,w1"; + status = "okay"; + reg = <0xffffeeee 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + ranges = <0 0>; }; };