diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt index 10b72060d64b21e..f577fd8960a1746 100644 --- a/drivers/gpio/CMakeLists.txt +++ b/drivers/gpio/CMakeLists.txt @@ -42,6 +42,7 @@ zephyr_library_sources_ifdef(CONFIG_GPIO_LMP90XXX gpio_lmp90xxx.c) zephyr_library_sources_ifdef(CONFIG_GPIO_LPC11U6X gpio_lpc11u6x.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MAX14906 gpio_max14906.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MAX14916 gpio_max14916.c) +zephyr_library_sources_ifdef(CONFIG_GPIO_MAX22017 gpio_max22017.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MAX32 gpio_max32.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MCHP_MSS gpio_mchp_mss.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MCP230XX gpio_mcp230xx.c) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 81b20ea68c64041..5f32729a3e4349b 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -96,6 +96,7 @@ config GPIO_ENABLE_DISABLE_INTERRUPT # zephyr-keep-sorted-start source "drivers/gpio/Kconfig.ad559x" +source "drivers/gpio/Kconfig.adi_max22017" source "drivers/gpio/Kconfig.adp5585" source "drivers/gpio/Kconfig.ads114s0x" source "drivers/gpio/Kconfig.altera" diff --git a/drivers/gpio/Kconfig.adi_max22017 b/drivers/gpio/Kconfig.adi_max22017 new file mode 100644 index 000000000000000..110600da8a51bec --- /dev/null +++ b/drivers/gpio/Kconfig.adi_max22017 @@ -0,0 +1,33 @@ +# Copyright (c) 2024 Analog Devices Inc. +# Copyright (c) 2024 BayLibre SAS +# SPDX-License-Identifier: Apache-2.0 + +config GPIO_MAX22017 + bool "Analog Devices MAX22017 GPIO support" + default y + depends on DT_HAS_ADI_MAX22017_GPIO_ENABLED + select MFD + help + Enable GPIO support for the Analog Devices MAX22017 + +if GPIO_MAX22017 + +config GPIO_MAX22017_INIT_PRIORITY + int "Init priority" + default 81 + help + Analog Devices MAX22017 gpio device driver initialization priority. + +config GPIO_MAX22017_INT_QUIRK + bool "MAX22017 GPIO Interrupt quirk" + default n + help + The GPIO controller will not report any new GPI interrupt as long as its interrupt status + register hasn't been read. + Reading the interrupt status register happens on a falling edge of the INT pin. + There seems to be a condition when the GPIO controller detects an interrupt but it's INT + pin stays high which masks any subsequent interrupts. + To avoid being stuck in that state, fire a timer to periodically check the interrupt status + register. + +endif # GPIO_MAX22017 diff --git a/drivers/gpio/gpio_max22017.c b/drivers/gpio/gpio_max22017.c new file mode 100644 index 000000000000000..8b56f6a407397c6 --- /dev/null +++ b/drivers/gpio/gpio_max22017.c @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2024 Analog Devices Inc. + * Copyright (c) 2024 Baylibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define DT_DRV_COMPAT adi_max22017_gpio + +#include +LOG_MODULE_REGISTER(gpio_max22017, CONFIG_GPIO_LOG_LEVEL); + +#include + +#include + +struct gpio_adi_max22017_config { + /* gpio_driver_config needs to be first */ + struct gpio_driver_config common; + const struct device *parent; +}; + +struct gpio_adi_max22017_data { + /* gpio_driver_data needs to be first */ + struct gpio_driver_data common; +#ifdef CONFIG_GPIO_MAX22017_INT_QUIRK + struct k_timer int_quirk_timer; +#endif +}; + +#ifdef CONFIG_GPIO_MAX22017_INT_QUIRK +void isr_quirk_handler(struct k_timer *int_quirk_timer) +{ + int ret; + struct max22017_data *data = k_timer_user_data_get(int_quirk_timer); + k_mutex_lock(&data->lock, K_FOREVER); + + ret = k_work_submit(&data->int_work); + if (ret < 0) { + LOG_WRN("Could not submit int work: %d", ret); + } + + k_mutex_unlock(&data->lock); +} +#endif + +static int adi_max22017_gpio_set_output(const struct device *dev, uint8_t pin, bool initial_value) +{ + int ret; + uint16_t gpio_data, gpio_ctrl; + struct max22017_data *data = dev->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = max22017_reg_read(dev, MAX22017_GEN_GPIO_DATA_OFF, &gpio_data); + if (ret) { + goto fail; + } + + ret = max22017_reg_read(dev, MAX22017_GEN_GPIO_CTRL_OFF, &gpio_ctrl); + if (ret) { + goto fail; + } + + if (initial_value) { + gpio_data |= FIELD_PREP(MAX22017_GEN_GPIO_DATA_GPO_DATA, BIT(pin)); + } else { + gpio_data &= ~FIELD_PREP(MAX22017_GEN_GPIO_DATA_GPO_DATA, BIT(pin)); + } + + gpio_ctrl |= FIELD_PREP(MAX22017_GEN_GPIO_CTRL_GPIO_EN, BIT(pin)) | + FIELD_PREP(MAX22017_GEN_GPIO_CTRL_GPIO_DIR, BIT(pin)); + + ret = max22017_reg_write(dev, MAX22017_GEN_GPIO_DATA_OFF, gpio_data); + if (ret) { + goto fail; + } + + ret = max22017_reg_write(dev, MAX22017_GEN_GPIO_CTRL_OFF, gpio_ctrl); + +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +static int adi_max22017_gpio_set_input(const struct device *dev, uint8_t pin) +{ + int ret; + uint16_t gpio_ctrl; + struct max22017_data *data = dev->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = max22017_reg_read(dev, MAX22017_GEN_GPIO_CTRL_OFF, &gpio_ctrl); + if (ret) { + goto fail; + } + + gpio_ctrl |= FIELD_PREP(MAX22017_GEN_GPIO_CTRL_GPIO_EN, BIT(pin)); + gpio_ctrl &= ~FIELD_PREP(MAX22017_GEN_GPIO_CTRL_GPIO_DIR, BIT(pin)); + + ret = max22017_reg_write(dev, MAX22017_GEN_GPIO_CTRL_OFF, gpio_ctrl); +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +int adi_max22017_gpio_deconfigure(const struct device *dev, uint8_t pin) +{ + int ret; + uint16_t gpio_ctrl; + struct max22017_data *data = dev->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = max22017_reg_read(dev, MAX22017_GEN_GPIO_CTRL_OFF, &gpio_ctrl); + if (ret) { + goto fail; + } + + gpio_ctrl &= ~FIELD_PREP(MAX22017_GEN_GPIO_CTRL_GPIO_EN, BIT(pin)); + + ret = max22017_reg_write(dev, MAX22017_GEN_GPIO_CTRL_OFF, gpio_ctrl); +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +int adi_max22017_gpio_set_pin_value(const struct device *dev, uint8_t pin, bool value) +{ + int ret; + uint16_t gpio_data; + struct max22017_data *data = dev->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = max22017_reg_read(dev, MAX22017_GEN_GPIO_DATA_OFF, &gpio_data); + if (ret) { + goto fail; + } + + if (value) { + gpio_data |= FIELD_PREP(MAX22017_GEN_GPIO_DATA_GPO_DATA, BIT(pin)); + } else { + gpio_data &= ~FIELD_PREP(MAX22017_GEN_GPIO_DATA_GPO_DATA, BIT(pin)); + } + + ret = max22017_reg_write(dev, MAX22017_GEN_GPIO_DATA_OFF, gpio_data); +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +int adi_max22017_gpio_get_pin_value(const struct device *dev, uint8_t pin, bool *value) +{ + int ret; + uint16_t gpio_data; + struct max22017_data *data = dev->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = max22017_reg_read(dev, MAX22017_GEN_GPIO_DATA_OFF, &gpio_data); + if (ret) { + goto fail; + } + + *value = FIELD_GET(MAX22017_GEN_GPIO_DATA_GPI_DATA, gpio_data) & BIT(pin); + +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +static int adi_max22017_gpio_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask, + gpio_port_value_t value) +{ + int ret; + uint16_t gpio_data, tmp_val; + struct max22017_data *data = dev->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = max22017_reg_read(dev, MAX22017_GEN_GPIO_DATA_OFF, &gpio_data); + if (ret) { + goto fail; + } + + tmp_val = FIELD_GET(MAX22017_GEN_GPIO_DATA_GPO_DATA, gpio_data); + tmp_val = (tmp_val & ~mask) | (value & mask); + gpio_data = FIELD_PREP(MAX22017_GEN_GPIO_DATA_GPO_DATA, tmp_val) | + FIELD_PREP(MAX22017_GEN_GPIO_DATA_GPI_DATA, + FIELD_GET(MAX22017_GEN_GPIO_DATA_GPI_DATA, gpio_data)); + + ret = max22017_reg_write(dev, MAX22017_GEN_GPIO_DATA_OFF, gpio_data); +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +static int gpio_adi_max22017_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) +{ + const struct gpio_adi_max22017_config *config = dev->config; + int err = -EINVAL; + + if ((flags & (GPIO_INPUT | GPIO_OUTPUT)) == GPIO_DISCONNECTED) { + return adi_max22017_gpio_deconfigure(config->parent, pin); + } + + if ((flags & GPIO_SINGLE_ENDED) != 0) { + return -ENOTSUP; + } + + if ((flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0) { + return -ENOTSUP; + } + + switch (flags & GPIO_DIR_MASK) { + case GPIO_INPUT: + err = adi_max22017_gpio_set_input(config->parent, pin); + break; + case GPIO_OUTPUT: + err = adi_max22017_gpio_set_output(config->parent, pin, + (flags & GPIO_OUTPUT_INIT_HIGH) != 0); + break; + default: + return -ENOTSUP; + } + return err; +} + +static int gpio_adi_max22017_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, + enum gpio_int_mode mode, + enum gpio_int_trig trig) +{ + int ret; + uint16_t gpio_int, gen_int_en; + const struct gpio_adi_max22017_config *config = dev->config; + const struct device *parent = config->parent; + struct max22017_data *data = parent->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + if (mode == GPIO_INT_MODE_DISABLED) { + ret = -ENOTSUP; + goto fail; + } + + ret = max22017_reg_read(parent, MAX22017_GEN_GPI_INT_OFF, &gpio_int); + if (ret) { + goto fail; + } + + if (mode & GPIO_INT_EDGE_RISING) { + gpio_int |= FIELD_PREP(MAX22017_GEN_GPI_INT_GPI_POS_EDGE_INT, BIT(pin)); + } + if (mode & GPIO_INT_EDGE_FALLING) { + gpio_int |= FIELD_PREP(MAX22017_GEN_GPI_INT_GPI_NEG_EDGE_INT, BIT(pin)); + } + + ret = max22017_reg_write(parent, MAX22017_GEN_GPI_INT_OFF, gpio_int); + if (ret) { + goto fail; + } + + ret = max22017_reg_read(parent, MAX22017_GEN_INTEN_OFF, &gen_int_en); + if (ret) { + goto fail; + } + + ret = max22017_reg_write(parent, MAX22017_GEN_INTEN_OFF, + gen_int_en | FIELD_PREP(MAX22017_GEN_INTEN_GPI_INTEN, 1)); +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +static int gpio_adi_max22017_port_get_raw(const struct device *dev, gpio_port_value_t *value) +{ + int ret; + const struct gpio_adi_max22017_config *config = dev->config; + const struct device *parent = config->parent; + struct max22017_data *data = parent->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = max22017_reg_read(parent, MAX22017_GEN_GPIO_DATA_OFF, (uint16_t *)value); + if (ret) { + goto fail; + } + + *value = FIELD_GET(MAX22017_GEN_GPIO_DATA_GPI_DATA, *value); + +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +static int gpio_adi_max22017_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask, + gpio_port_value_t value) +{ + const struct gpio_adi_max22017_config *config = dev->config; + + return adi_max22017_gpio_port_set_masked_raw(config->parent, mask, value); +} + +static int gpio_adi_max22017_port_set_bits_raw(const struct device *dev, gpio_port_pins_t pins) +{ + const struct gpio_adi_max22017_config *config = dev->config; + + return adi_max22017_gpio_port_set_masked_raw(config->parent, pins, pins); +} + +static int gpio_adi_max22017_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t pins) +{ + const struct gpio_adi_max22017_config *config = dev->config; + + return adi_max22017_gpio_port_set_masked_raw(config->parent, pins, 0); +} + +static int gpio_adi_max22017_port_toggle_bits(const struct device *dev, gpio_port_pins_t pins) +{ + int ret; + uint16_t gpio_data, tmp_val; + const struct gpio_adi_max22017_config *config = dev->config; + const struct device *parent = config->parent; + struct max22017_data *data = parent->data; + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = max22017_reg_read(parent, MAX22017_GEN_GPIO_DATA_OFF, &gpio_data); + if (ret) { + goto fail; + } + + tmp_val = FIELD_GET(MAX22017_GEN_GPIO_DATA_GPO_DATA, gpio_data); + tmp_val = (tmp_val ^ pins); + gpio_data = FIELD_PREP(MAX22017_GEN_GPIO_DATA_GPO_DATA, tmp_val) | + FIELD_PREP(MAX22017_GEN_GPIO_DATA_GPI_DATA, + FIELD_GET(MAX22017_GEN_GPIO_DATA_GPI_DATA, gpio_data)); + + ret = max22017_reg_write(parent, MAX22017_GEN_GPIO_DATA_OFF, gpio_data); +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +static int gpio_adi_max22017_manage_cb(const struct device *dev, struct gpio_callback *callback, + bool set) +{ + int ret; + const struct gpio_adi_max22017_config *config = dev->config; + struct max22017_data *data = config->parent->data; + + k_mutex_lock(&data->lock, K_FOREVER); + ret = gpio_manage_callback(&data->callbacks_gpi, callback, set); + k_mutex_unlock(&data->lock); + + return ret; +} + +static int gpio_adi_max22017_init(const struct device *dev) +{ + const struct gpio_adi_max22017_config *config = dev->config; + const struct device *parent = config->parent; + + if (!device_is_ready(parent)) { + LOG_ERR("parent adi_max22017 MFD device '%s' not ready", config->parent->name); + return -EINVAL; + } + +#ifdef CONFIG_GPIO_MAX22017_INT_QUIRK + struct gpio_adi_max22017_data *data = dev->data; + struct k_timer *t = &data->int_quirk_timer; + k_timer_init(t, isr_quirk_handler, NULL); + k_timer_user_data_set(t, parent->data); + k_timer_start(t, K_MSEC(25), K_MSEC(25)); +#endif + return 0; +} + +static const struct gpio_driver_api gpio_adi_max22017_api = { + .pin_configure = gpio_adi_max22017_configure, + .port_set_masked_raw = gpio_adi_max22017_port_set_masked_raw, + .port_set_bits_raw = gpio_adi_max22017_port_set_bits_raw, + .port_clear_bits_raw = gpio_adi_max22017_port_clear_bits_raw, + .port_toggle_bits = gpio_adi_max22017_port_toggle_bits, + .port_get_raw = gpio_adi_max22017_port_get_raw, + .pin_interrupt_configure = gpio_adi_max22017_pin_interrupt_configure, + .manage_callback = gpio_adi_max22017_manage_cb, +}; + +#define GPIO_MAX22017_DEVICE(id) \ + static const struct gpio_adi_max22017_config gpio_adi_max22017_##id##_cfg = { \ + .common = \ + { \ + .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(id), \ + }, \ + .parent = DEVICE_DT_GET(DT_INST_PARENT(id)), \ + }; \ + static struct gpio_adi_max22017_data gpio_adi_max22017_##id##_data; \ + DEVICE_DT_INST_DEFINE(id, gpio_adi_max22017_init, NULL, &gpio_adi_max22017_##id##_data, \ + &gpio_adi_max22017_##id##_cfg, POST_KERNEL, \ + CONFIG_GPIO_MAX22017_INIT_PRIORITY, &gpio_adi_max22017_api); + +DT_INST_FOREACH_STATUS_OKAY(GPIO_MAX22017_DEVICE) diff --git a/dts/bindings/gpio/adi,max22017-gpio.yaml b/dts/bindings/gpio/adi,max22017-gpio.yaml new file mode 100644 index 000000000000000..81a243ea7af22a1 --- /dev/null +++ b/dts/bindings/gpio/adi,max22017-gpio.yaml @@ -0,0 +1,17 @@ +# Copyright (c) 2024 Analog Devices Inc. +# Copyright (c) 2024 BayLibre SAS +# SPDX-License-Identifier: Apache-2.0 + +description: MAX22017 GPIO + +compatible: "adi,max22017-gpio" + +include: gpio-controller.yaml + +properties: + "#gpio-cells": + const: 2 + +gpio-cells: + - pin + - flags diff --git a/tests/drivers/build_all/gpio/app.overlay b/tests/drivers/build_all/gpio/app.overlay index e023736fe339a82..bcaf133d5500262 100644 --- a/tests/drivers/build_all/gpio/app.overlay +++ b/tests/drivers/build_all/gpio/app.overlay @@ -480,7 +480,22 @@ fault-gpios = <&test_gpio 0 0>; sync-gpios = <&test_gpio 0 0>; en-gpios = <&test_gpio 0 0>; + + test_spi_max22017: max22017_mfd@8 { + compatible = "adi,max22017"; + status = "okay"; + reg = <0x8>; + spi-max-frequency = <0>; + + max22017_gpio0: max22017_gpio { + compatible = "adi,max22017-gpio"; + status = "okay"; + gpio-controller; + ngpios = <5>; + #gpio-cells = <2>; + }; }; + }; }; };