Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions drivers/gpio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,8 @@ if(CONFIG_GPIO_SC18IM704)
zephyr_library_include_directories(${ZEPHYR_BASE}/drivers)
zephyr_library_sources(gpio_sc18im704.c)
endif()

if(CONFIG_GPIO_SC18IS606)
zephyr_library_include_directories(${ZEPHYR_BASE}/drivers)
zephyr_library_sources(gpio_sc18is606.c)
endif()
1 change: 1 addition & 0 deletions drivers/gpio/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ source "drivers/gpio/Kconfig.rzt2m"
source "drivers/gpio/Kconfig.sam"
source "drivers/gpio/Kconfig.sam0"
source "drivers/gpio/Kconfig.sc18im704"
source "drivers/gpio/Kconfig.sc18is606"
source "drivers/gpio/Kconfig.sedi"
source "drivers/gpio/Kconfig.sf32lb"
source "drivers/gpio/Kconfig.si32"
Expand Down
19 changes: 19 additions & 0 deletions drivers/gpio/Kconfig.sc18is606
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) 2025 tinyvision.ai
# SPDX-License-Identifier: Apache-2.0

config GPIO_SC18IS606
bool "NXP SC18IS606 GPIO Controller driver"
default y
depends on SPI_SC18IS606
depends on DT_HAS_NXP_SC18IS606_GPIO_ENABLED
help
Enables NXP SC18IS606 gpio controller driver

config GPIO_SC18IS606_INIT_PRIORITY
int "SC18IS606 GPIO Init Priority"
default 66
depends on GPIO_SC18IS606
help
SC18IS606 GPIO Controller Init priority

Note: Has to be greater than the parent SC18IS606 bridge init priority
252 changes: 252 additions & 0 deletions drivers/gpio/gpio_sc18is606.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* Copyright (c), 2025 tinyvision.ai
*
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT nxp_sc18is606_gpio

#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_utils.h>
#include <zephyr/sys/util.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(nxp_sc18is606_gpio, CONFIG_GPIO_LOG_LEVEL);

#include "spi/spi_sc18is606.h"

#define SC18IS606_GPIO_MAX_PINS 3

#define SC18IS606_GPIO_WRITE 0xF4
#define SC18IS606_GPIO_READ 0xF5
#define SC18IS606_GPIO_ENABLE 0xF6
#define SC18IS606_GPIO_CONF 0xF7

#define SC18IS606_GPIO_CONF_INPUT 0x00
#define SC18IS606_GPIO_CONF_PUSH_PULL 0x01
#define SC18IS606_GPIO_CONF_OPEN_DRAIN 0x03
#define SC18IS606_GPIO_CONF_MASK 0x03

#define SC18IS606_GPIO_ENABLE_MASK GENMASK(2, 0)

struct gpio_sc18is606_config {
struct gpio_driver_config common;

const struct device *bridge;
};

struct gpio_sc18is606_data {
struct gpio_driver_data sc18is606_data;

/* current port state */
uint8_t output_state;

/* current port pin config */
uint8_t conf;
};

static int gpio_sc18is606_port_set_raw(const struct device *port, uint8_t mask, uint8_t value,
uint8_t toggle)
{
const struct gpio_sc18is606_config *cfg = port->config;
struct gpio_sc18is606_data *data = port->data;

uint8_t buf[] = {
SC18IS606_GPIO_WRITE,
data->output_state,
};
int ret;

if (k_is_in_isr()) {
return -EWOULDBLOCK;
}

buf[1] &= ~mask;
buf[1] |= (value & mask);
buf[1] ^= toggle;

ret = nxp_sc18is606_transfer(cfg->bridge, buf, sizeof(buf), NULL, 0, NULL);

if (ret < 0) {
LOG_ERR("Failed to write to GPIO (%d)", ret);
return ret;
}

data->output_state = buf[1];
Comment on lines +68 to +79
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential race condition: The output_state is read, modified, and written without proper synchronization. If multiple threads call port_set_raw concurrently, they could overwrite each other's updates since they all read the same initial value of data->output_state before applying their changes.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kartben I don't know that it would be worth adding threading here either, would this be necessary?


return 0;
}

static int gpio_sc18is606_pin_configure(const struct device *port, gpio_pin_t pin,
gpio_flags_t flags)
{
const struct gpio_sc18is606_config *cfg = port->config;
struct gpio_sc18is606_data *data = port->data;
uint8_t pin_conf;
uint8_t pin_enable;
int ret;

uint8_t buf[] = {
SC18IS606_GPIO_CONF,
0x00,
};

uint8_t enable_buf[] = {
SC18IS606_GPIO_ENABLE,
0x00,
};

if (pin >= SC18IS606_GPIO_MAX_PINS) {
return -EINVAL;
}

if (flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) {
return -ENOTSUP;
}

if (flags & GPIO_INPUT) {
pin_conf = SC18IS606_GPIO_CONF_INPUT;
} else if (flags & GPIO_OUTPUT) {
if (flags & GPIO_SINGLE_ENDED) {
if (flags & GPIO_LINE_OPEN_DRAIN) {
pin_conf = SC18IS606_GPIO_CONF_OPEN_DRAIN;
} else {
return -ENOTSUP;
}
} else {
pin_conf = SC18IS606_GPIO_CONF_PUSH_PULL;
}
} else {
/* Impossible option */
return -ENOTSUP;
}

ret = nxp_sc18is606_claim(cfg->bridge);
if (ret < 0) {
LOG_ERR("Failed to claim the bridge (%d)", ret);
return ret;
}

pin_enable = FIELD_PREP(SC18IS606_GPIO_ENABLE_MASK, (1 << pin));

enable_buf[1] = pin_enable;

ret = nxp_sc18is606_transfer(cfg->bridge, enable_buf, sizeof(enable_buf), NULL, 0, NULL);
if (ret < 0) {
LOG_ERR("Failed to enable GPIO (%d)", ret);
}
Comment on lines +138 to +141
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bridge is claimed but not released if the enable GPIO transfer fails. This creates a resource leak where the bridge remains locked. The release should occur in all error paths, or error handling should properly unwind the claim.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

@zacck zacck Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kartben unless I am mistaken, this is indeed handled in the implementation of transfer


data->conf &= ~(SC18IS606_GPIO_CONF_MASK << (pin * 2));
data->conf |= (pin_conf & SC18IS606_GPIO_CONF_MASK) << (pin * 2);
buf[1] = data->conf;

ret = nxp_sc18is606_transfer(cfg->bridge, buf, sizeof(buf), NULL, 0, NULL);
if (ret < 0) {
LOG_ERR("Failed to configure GPIO (%d)", ret);
}
Comment on lines +147 to +150
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bridge is not released if the configure GPIO transfer fails. This creates a resource leak where the bridge remains locked. The release at line 160 will only execute if this function completes normally, but not if an early return occurs due to error.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kartben so is this


if (ret == 0 && flags & GPIO_OUTPUT) {
if (flags & GPIO_OUTPUT_INIT_HIGH) {
gpio_sc18is606_port_set_raw(port, BIT(pin), BIT(pin), 0);
} else if (flags & GPIO_OUTPUT_INIT_LOW) {
gpio_sc18is606_port_set_raw(port, BIT(pin), 0, 0);
}
}

nxp_sc18is606_release(cfg->bridge);

return ret;
}

static int gpio_sc18is606_port_get_raw(const struct device *port, gpio_port_value_t *value)
{
const struct gpio_sc18is606_config *cfg = port->config;

uint8_t buf[] = {
SC18IS606_GPIO_READ,
};
uint8_t data;
int ret;

if (k_is_in_isr()) {
return -EWOULDBLOCK;
}

ret = nxp_sc18is606_transfer(cfg->bridge, buf, sizeof(buf), &data, 1, NULL);
if (ret < 0) {
LOG_ERR("Failed to read GPIO state (%d)", ret);
return ret;
}

*value = data;

return 0;
}

static int gpio_sc18is606_port_set_masked_raw(const struct device *port, gpio_port_pins_t mask,
gpio_port_value_t value)
{
return gpio_sc18is606_port_set_raw(port, (uint8_t)mask, (uint8_t)value, 0);
}

static int gpio_sc18is606_port_set_bits_raw(const struct device *port, gpio_port_pins_t pins)
{
return gpio_sc18is606_port_set_raw(port, (uint8_t)pins, (uint8_t)pins, 0);
}

static int gpio_sc18is606_port_clear_bits_raw(const struct device *port, gpio_port_pins_t pins)
{
return gpio_sc18is606_port_set_raw(port, (uint8_t)pins, 0, 0);
}
static int gpio_sc18is606_port_toggle_bits(const struct device *port, gpio_port_pins_t pins)
{
return gpio_sc18is606_port_set_raw(port, 0, 0, (uint8_t)pins);
}

static int gpio_sc18is606_init(const struct device *dev)
{
const struct gpio_sc18is606_config *cfg = dev->config;

if (!device_is_ready(cfg->bridge)) {
LOG_ERR("Parent device not ready");
return -ENODEV;
}

return 0;
}

static DEVICE_API(gpio, gpio_sc18is606_driver_api) = {
.pin_configure = gpio_sc18is606_pin_configure,
.port_get_raw = gpio_sc18is606_port_get_raw,
.port_set_masked_raw = gpio_sc18is606_port_set_masked_raw,
.port_set_bits_raw = gpio_sc18is606_port_set_bits_raw,
.port_clear_bits_raw = gpio_sc18is606_port_clear_bits_raw,
.port_toggle_bits = gpio_sc18is606_port_toggle_bits,
};

#define CHECK_COMPAT(node) \
COND_CODE_1(DT_NODE_HAS_COMPAT(node, nxp_sc18is606_spi), (DEVICE_DT_GET(node)), ())

#define GPIO_SC18IS606_SPI_SIBLING(n) DT_FOREACH_CHILD_STATUS_OKAY(DT_INST_PARENT(n), CHECK_COMPAT)

#define GPIO_SC18IS606_DEFINE(inst) \
static const struct gpio_sc18is606_config gpio_sc18is606_config##inst = { \
.common = \
{ \
.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst), \
}, \
.bridge = GPIO_SC18IS606_SPI_SIBLING(inst), \
}; \
static struct gpio_sc18is606_data gpio_sc18is606_data##inst = { \
.conf = 0x00, \
}; \
\
DEVICE_DT_INST_DEFINE(inst, gpio_sc18is606_init, NULL, &gpio_sc18is606_data##inst, \
&gpio_sc18is606_config##inst, POST_KERNEL, \
CONFIG_GPIO_SC18IS606_INIT_PRIORITY, &gpio_sc18is606_driver_api);

DT_INST_FOREACH_STATUS_OKAY(GPIO_SC18IS606_DEFINE);
24 changes: 24 additions & 0 deletions dts/bindings/gpio/nxp,sc18is606-gpio.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c), 2025 tinyvision.ai
# SPDX-License-Identifier: Apache-2.0


description: GPIO Controller part for the SC18IS606 bridge.

compatible: "nxp,sc18is606-gpio"

include: gpio-controller.yaml

properties:
"#gpio-cells":
required: true
const: 2

ngpios:
required: true
const: 3

gpio-cells:
- pin
- flags

on-bus: nxp,sc18is606
8 changes: 8 additions & 0 deletions dts/bindings/mfd/nxp,sc18is606.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ description: |
#address-cells = <1>;
#size-cells = <0>;
};

gpio_ext: sc18is606_gpio {
compatible = "nxp,sc18is606-gpio";
status = "okay";
gpio-controller;
#gpio-cells = <2>;
ngpios = <3>;
};
};
};

Expand Down
8 changes: 8 additions & 0 deletions tests/drivers/build_all/mfd/app.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
#size-cells = <0>;
frequency = <1875>;
};

gpio_ext: sc18is606_gpio {
compatible = "nxp,sc18is606-gpio";
status = "okay";
gpio-controller;
#gpio-cells = <2>;
ngpios = <3>;
};
};
};

Expand Down