|  | /* | 
|  | * Copyright (c), 2023 Basalte bv | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT nxp_sc18im704_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/logging/log.h> | 
|  | LOG_MODULE_REGISTER(gpio_sc18im, CONFIG_GPIO_LOG_LEVEL); | 
|  |  | 
|  | #include "i2c/i2c_sc18im704.h" | 
|  |  | 
|  | #define GPIO_SC18IM_MAX_PINS		8 | 
|  |  | 
|  | /* After reset the GPIO config registers are 0x55 */ | 
|  | #define GPIO_SC18IM_DEFAULT_CONF	0x55 | 
|  |  | 
|  | #define GPIO_SC18IM_CONF_INPUT		0x01 | 
|  | #define GPIO_SC18IM_CONF_PUSH_PULL	0x02 | 
|  | #define GPIO_SC18IM_CONF_OPEN_DRAIN	0x03 | 
|  | #define GPIO_SC18IM_CONF_MASK		0x03 | 
|  |  | 
|  | struct gpio_sc18im_config { | 
|  | /* gpio_driver_config needs to be first */ | 
|  | struct gpio_driver_config common; | 
|  |  | 
|  | const struct device *bridge; | 
|  | }; | 
|  |  | 
|  | struct gpio_sc18im_data { | 
|  | /* gpio_driver_data needs to be first */ | 
|  | struct gpio_driver_data common; | 
|  |  | 
|  | uint8_t conf1; | 
|  | uint8_t conf2; | 
|  | uint8_t output_state; | 
|  | }; | 
|  |  | 
|  | static int gpio_sc18im_port_set_raw(const struct device *port, | 
|  | uint8_t mask, uint8_t value, uint8_t toggle) | 
|  | { | 
|  | const struct gpio_sc18im_config *cfg = port->config; | 
|  | struct gpio_sc18im_data *data = port->data; | 
|  | uint8_t buf[] = { | 
|  | SC18IM704_CMD_WRITE_GPIO, | 
|  | data->output_state, | 
|  | SC18IM704_CMD_STOP, | 
|  | }; | 
|  | int ret; | 
|  |  | 
|  | if (k_is_in_isr()) { | 
|  | return -EWOULDBLOCK; | 
|  | } | 
|  |  | 
|  | buf[1] &= ~mask; | 
|  | buf[1] |= (value & mask); | 
|  | buf[1] ^= toggle; | 
|  |  | 
|  | ret = sc18im704_transfer(cfg->bridge, buf, sizeof(buf), NULL, 0); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("Failed to write GPIO state (%d)", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | data->output_state = buf[1]; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gpio_sc18im_pin_configure(const struct device *port, gpio_pin_t pin, | 
|  | gpio_flags_t flags) | 
|  | { | 
|  | const struct gpio_sc18im_config *cfg = port->config; | 
|  | struct gpio_sc18im_data *data = port->data; | 
|  | uint8_t pin_conf; | 
|  | int ret; | 
|  | uint8_t buf[] = { | 
|  | SC18IM704_CMD_WRITE_REG, | 
|  | 0x00, | 
|  | 0x00, | 
|  | SC18IM704_CMD_STOP, | 
|  | }; | 
|  |  | 
|  | if (pin >= GPIO_SC18IM_MAX_PINS) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | if (flags & GPIO_INPUT) { | 
|  | pin_conf = GPIO_SC18IM_CONF_INPUT; | 
|  | } else if (flags & GPIO_OUTPUT) { | 
|  | if (flags & GPIO_SINGLE_ENDED) { | 
|  | if (flags & GPIO_LINE_OPEN_DRAIN) { | 
|  | pin_conf = GPIO_SC18IM_CONF_OPEN_DRAIN; | 
|  | } else { | 
|  | /* Open-drain is the only supported single-ended mode */ | 
|  | return -ENOTSUP; | 
|  | } | 
|  | } else { | 
|  | /* Default to push/pull */ | 
|  | pin_conf = GPIO_SC18IM_CONF_PUSH_PULL; | 
|  | } | 
|  | } else { | 
|  | /* Neither input nor output mode is selected */ | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | ret = sc18im704_claim(cfg->bridge); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("Failed to claim bridge (%d)", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (pin < 4) { | 
|  | /* Shift the config to the pin offset */ | 
|  | data->conf1 &= ~(GPIO_SC18IM_CONF_MASK << (pin * 2)); | 
|  | data->conf1 |= pin_conf << (pin * 2); | 
|  |  | 
|  | buf[1] = SC18IM704_REG_GPIO_CONF1; | 
|  | buf[2] = data->conf1; | 
|  | } else { | 
|  | /* Shift the config to the pin offset */ | 
|  | data->conf2 &= ~(GPIO_SC18IM_CONF_MASK << ((pin - 4) * 2)); | 
|  | data->conf2 |= pin_conf << ((pin - 4) * 2); | 
|  |  | 
|  | buf[1] = SC18IM704_REG_GPIO_CONF2; | 
|  | buf[2] = data->conf2; | 
|  | } | 
|  |  | 
|  | ret = sc18im704_transfer(cfg->bridge, buf, sizeof(buf), NULL, 0); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("Failed to configure GPIO (%d)", ret); | 
|  | } | 
|  |  | 
|  | if (ret == 0 && flags & GPIO_OUTPUT) { | 
|  | if (flags & GPIO_OUTPUT_INIT_HIGH) { | 
|  | gpio_sc18im_port_set_raw(port, BIT(pin), BIT(pin), 0); | 
|  | } | 
|  | if (flags & GPIO_OUTPUT_INIT_LOW) { | 
|  | gpio_sc18im_port_set_raw(port, BIT(pin), 0, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | sc18im704_release(cfg->bridge); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_GPIO_GET_CONFIG | 
|  | static int gpio_sc18im_pin_get_config(const struct device *port, gpio_pin_t pin, | 
|  | gpio_flags_t *flags) | 
|  | { | 
|  | struct gpio_sc18im_data *data = port->data; | 
|  | uint8_t conf; | 
|  |  | 
|  | if (pin >= GPIO_SC18IM_MAX_PINS) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (pin < 4) { | 
|  | conf = (data->conf1 >> (2 * pin)) & GPIO_SC18IM_CONF_MASK; | 
|  | } else { | 
|  | conf = (data->conf2 >> (2 * (pin - 4))) & GPIO_SC18IM_CONF_MASK; | 
|  | } | 
|  |  | 
|  | switch (conf) { | 
|  | case GPIO_SC18IM_CONF_PUSH_PULL: | 
|  | *flags = GPIO_OUTPUT | GPIO_PUSH_PULL; | 
|  | break; | 
|  | case GPIO_SC18IM_CONF_OPEN_DRAIN: | 
|  | *flags = GPIO_OUTPUT | GPIO_SINGLE_ENDED | GPIO_LINE_OPEN_DRAIN; | 
|  | break; | 
|  | case GPIO_SC18IM_CONF_INPUT: | 
|  | default: | 
|  | *flags = GPIO_INPUT; | 
|  | break; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static int gpio_sc18im_port_get_raw(const struct device *port, gpio_port_value_t *value) | 
|  | { | 
|  | const struct gpio_sc18im_config *cfg = port->config; | 
|  |  | 
|  | uint8_t buf[] = { | 
|  | SC18IM704_CMD_READ_GPIO, | 
|  | SC18IM704_CMD_STOP, | 
|  | }; | 
|  | uint8_t data; | 
|  | int ret; | 
|  |  | 
|  | if (k_is_in_isr()) { | 
|  | return -EWOULDBLOCK; | 
|  | } | 
|  |  | 
|  | ret = sc18im704_transfer(cfg->bridge, buf, sizeof(buf), &data, 1); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("Failed to read GPIO state (%d)", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | *value = data; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gpio_sc18im_port_set_masked_raw(const struct device *port, | 
|  | gpio_port_pins_t mask, | 
|  | gpio_port_value_t value) | 
|  | { | 
|  | return gpio_sc18im_port_set_raw(port, (uint8_t)mask, (uint8_t)value, 0); | 
|  | } | 
|  |  | 
|  | static int gpio_sc18im_port_set_bits_raw(const struct device *port, gpio_port_pins_t pins) | 
|  | { | 
|  | return gpio_sc18im_port_set_raw(port, (uint8_t)pins, (uint8_t)pins, 0); | 
|  | } | 
|  |  | 
|  | static int gpio_sc18im_port_clear_bits_raw(const struct device *port, gpio_port_pins_t pins) | 
|  | { | 
|  | return gpio_sc18im_port_set_raw(port, (uint8_t)pins, 0, 0); | 
|  | } | 
|  |  | 
|  | static int gpio_sc18im_port_toggle_bits(const struct device *port, gpio_port_pins_t pins) | 
|  | { | 
|  | return gpio_sc18im_port_set_raw(port, 0, 0, (uint8_t)pins); | 
|  | } | 
|  |  | 
|  | static int gpio_sc18im_init(const struct device *dev) | 
|  | { | 
|  | const struct gpio_sc18im_config *cfg = dev->config; | 
|  |  | 
|  | if (!device_is_ready(cfg->bridge)) { | 
|  | LOG_ERR("Parent device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct gpio_driver_api gpio_sc18im_driver_api = { | 
|  | .pin_configure = gpio_sc18im_pin_configure, | 
|  | #ifdef CONFIG_GPIO_GET_CONFIG | 
|  | .pin_get_config = gpio_sc18im_pin_get_config, | 
|  | #endif | 
|  | .port_get_raw = gpio_sc18im_port_get_raw, | 
|  | .port_set_masked_raw = gpio_sc18im_port_set_masked_raw, | 
|  | .port_set_bits_raw = gpio_sc18im_port_set_bits_raw, | 
|  | .port_clear_bits_raw = gpio_sc18im_port_clear_bits_raw, | 
|  | .port_toggle_bits = gpio_sc18im_port_toggle_bits, | 
|  | }; | 
|  |  | 
|  | #define CHECK_COMPAT(node)									\ | 
|  | COND_CODE_1(DT_NODE_HAS_COMPAT(node, nxp_sc18im704_i2c), (DEVICE_DT_GET(node)), ()) | 
|  |  | 
|  | #define GPIO_SC18IM704_I2C_SIBLING(n)								\ | 
|  | DT_FOREACH_CHILD_STATUS_OKAY(DT_INST_PARENT(n), CHECK_COMPAT) | 
|  |  | 
|  | #define GPIO_SC18IM704_DEFINE(n)								\ | 
|  | static const struct gpio_sc18im_config gpio_sc18im_config_##n = {			\ | 
|  | .common = {									\ | 
|  | .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n),			\ | 
|  | },										\ | 
|  | .bridge = GPIO_SC18IM704_I2C_SIBLING(n),					\ | 
|  | };											\ | 
|  | static struct gpio_sc18im_data gpio_sc18im_data_##n = {					\ | 
|  | .conf1 = GPIO_SC18IM_DEFAULT_CONF,						\ | 
|  | .conf2 = GPIO_SC18IM_DEFAULT_CONF,						\ | 
|  | };											\ | 
|  | \ | 
|  | DEVICE_DT_INST_DEFINE(n, gpio_sc18im_init, NULL,					\ | 
|  | &gpio_sc18im_data_##n, &gpio_sc18im_config_##n,			\ | 
|  | POST_KERNEL, CONFIG_GPIO_SC18IM704_INIT_PRIORITY,			\ | 
|  | &gpio_sc18im_driver_api); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(GPIO_SC18IM704_DEFINE); |