From 1489038f3beb5d5925f2ec42fdbd28d420ef3bdc Mon Sep 17 00:00:00 2001 From: Robert Budai Date: Mon, 17 Feb 2025 08:06:15 +0200 Subject: [PATCH] drivers: gpio: add max14917 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MAX14917 is an eight high-side switch, specified to deliver up to 700mA (min) continuous current per channel. The high-side switches have on-resistance of 120mΩ (typ) at 25°C ambient temperature Signed-off-by: Robert Budai --- drivers/gpio/CMakeLists.txt | 1 + drivers/gpio/Kconfig | 1 + drivers/gpio/Kconfig.max14917 | 20 ++ drivers/gpio/gpio_max14917.c | 358 +++++++++++++++++++++++ drivers/gpio/gpio_max14917.h | 46 +++ dts/bindings/gpio/adi,max14917-gpio.yaml | 59 ++++ tests/drivers/build_all/gpio/app.overlay | 17 ++ 7 files changed, 502 insertions(+) create mode 100644 drivers/gpio/Kconfig.max14917 create mode 100644 drivers/gpio/gpio_max14917.c create mode 100644 drivers/gpio/gpio_max14917.h create mode 100644 dts/bindings/gpio/adi,max14917-gpio.yaml diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt index 9a5bf207935..7723b93ad7e 100644 --- a/drivers/gpio/CMakeLists.txt +++ b/drivers/gpio/CMakeLists.txt @@ -45,6 +45,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_MAX14917 gpio_max14917.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MAX22017 gpio_max22017.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MAX22190 gpio_max22190.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MAX32 gpio_max32.c) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 00aab689e11..a3094299cd9 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -135,6 +135,7 @@ source "drivers/gpio/Kconfig.lmp90xxx" source "drivers/gpio/Kconfig.lpc11u6x" source "drivers/gpio/Kconfig.max14906" source "drivers/gpio/Kconfig.max14916" +source "drivers/gpio/Kconfig.max14917" source "drivers/gpio/Kconfig.max22017" source "drivers/gpio/Kconfig.max22190" source "drivers/gpio/Kconfig.max32" diff --git a/drivers/gpio/Kconfig.max14917 b/drivers/gpio/Kconfig.max14917 new file mode 100644 index 00000000000..cd7f1af878d --- /dev/null +++ b/drivers/gpio/Kconfig.max14917 @@ -0,0 +1,20 @@ +# Copyright (c) 2025 Analog Devices Inc. +# SPDX-License-Identifier: Apache-2.0 + +# MAX14917 GPIO configuration options + +menuconfig GPIO_MAX14917 + bool "MAX14917 GPIO driver" + default y + select CRC + depends on DT_HAS_ADI_MAX14917_GPIO_ENABLED && SPI + help + Enable MAX14917 octal industrial digital + output with diagnostics + +config GPIO_MAX14917_INIT_PRIORITY + int "Driver init priority" + default 99 + depends on GPIO_MAX14917 + help + Device driver initialization priority. diff --git a/drivers/gpio/gpio_max14917.c b/drivers/gpio/gpio_max14917.c new file mode 100644 index 00000000000..74c87f45688 --- /dev/null +++ b/drivers/gpio/gpio_max14917.c @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL +#include + +LOG_MODULE_REGISTER(gpio_max14917); + +#include + +#include "gpio_max14917.h" + +#define DT_DRV_COMPAT adi_max14917_gpio + +static int max14917_reg_trans_spi_diag(const struct device *dev) +{ + int ret = 0; + uint8_t crc; + + uint8_t local_tx_buff[MAX14917_MAX_PKT_SIZE] = {0}; + uint8_t local_rx_buff[MAX14917_MAX_PKT_SIZE] = {0}; + + struct max14917_data *data = dev->data; + const struct max14917_config *config = dev->config; + + struct spi_buf tx_buf = { + .buf = &local_tx_buff, + .len = config->pkt_size, + }; + const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1}; + + struct spi_buf rx_buf = { + .buf = &local_rx_buff, + .len = config->pkt_size, + }; + const struct spi_buf_set rx = {.buffers = &rx_buf, .count = 1}; + + local_tx_buff[0] = data->gpios_ON; + + /* If CRC enabled calculate it */ + if (config->crc_en) { + crc = crc8(&local_tx_buff[0], 1, MAX14917_CRC_POLY, MAX14917_CRC_INI_VAL, false); + local_tx_buff[1] = (crc & MAX14917_CRC_MASK); + } + + /* Perform SPI transaction */ + ret = spi_transceive_dt(&config->spi, &tx, &rx); + if (ret) { + LOG_ERR("SPI transfer failed"); + return ret; + } + + /* If CRC enabled check it */ + if (config->crc_en) { + crc = crc8(&local_tx_buff[0], 1, MAX14917_CRC_POLY, MAX14917_CRC_INI_VAL, false); + crc = (crc & MAX14917_CRC_MASK); + if (crc != (local_rx_buff[1] & 0x1F)) { + LOG_ERR("READ CRC ERR (%d)-(%d)\n", crc, (local_rx_buff[1] & 0x1F)); + return -EINVAL; + } + /* Set error flags in device data */ + data->comm_err = (local_rx_buff[1] & MAX14917_COMM_ERR); + data->verr = (local_rx_buff[1] & MAX14917_VERR); + data->therm_err = (local_rx_buff[1] & MAX14917_THERM_ERR); + } + + data->gpios_fault = local_rx_buff[0]; + + return 0; +} + +static int max14917_fault_check(const struct device *dev) +{ + int ret; + const struct max14917_data *data = dev->data; + const struct max14917_config *config = dev->config; + + if (gpio_pin_get_dt(&config->fault_gpio)) { + LOG_DBG("FAULT GPIO is high"); + } + + /* Update error flags */ + ret = max14917_reg_trans_spi_diag(dev); + if (ret) { + return ret; + } + + if (data->comm_err) { + LOG_DBG("COMMERR flag is active"); + } + if (data->verr) { + LOG_DBG("VERR flag is active"); + } + if (data->therm_err) { + LOG_DBG("THERMERR flag is active"); + } + + for (int i = 0; i < MAX14917_CHANNELS; i++) { + if (data->gpios_fault & BIT(i)) { + LOG_DBG("Channel %d has a fault", i); + } + } + + return 0; +} + +static int gpio_max14917_init(const struct device *dev) +{ + struct max14917_data *data = dev->data; + const struct max14917_config *config = dev->config; + int err = 0; + + LOG_DBG(" --- GPIO max14917 init IN ---"); + + if (!spi_is_ready_dt(&config->spi)) { + LOG_ERR("SPI bus is not ready\n"); + return -ENODEV; + } + + /* Output GPIOS */ + /* setup EN gpio - normal low */ + if (!gpio_is_ready_dt(&config->en_gpio)) { + LOG_ERR("EN GPIO device not ready"); + return -ENODEV; + } + + err = gpio_pin_configure_dt(&config->en_gpio, GPIO_OUTPUT); + if (err < 0) { + LOG_ERR("Failed to configure EN GPIO"); + return err; + } + + /* setup SYNC gpio - normal low */ + if (!gpio_is_ready_dt(&config->sync_gpio)) { + LOG_ERR("SYNC GPIO device not ready"); + return -ENODEV; + } + + err = gpio_pin_configure_dt(&config->sync_gpio, GPIO_OUTPUT); + if (err < 0) { + LOG_ERR("Failed to configure SYNC GPIO"); + return err; + } + /* setup CRCEN gpio - normal low */ + if (!gpio_is_ready_dt(&config->crcen_gpio)) { + LOG_ERR("CRCEN GPIO device not ready"); + return -ENODEV; + } + + err = gpio_pin_configure_dt(&config->crcen_gpio, GPIO_OUTPUT); + if (err < 0) { + LOG_ERR("Failed to configure CRCEN GPIO"); + return err; + } + /* Input GPIOS */ + /* setup VDDOK gpio - normal low */ + if (!gpio_is_ready_dt(&config->vddok_gpio)) { + LOG_ERR("VDDOK GPIO device not ready"); + return -ENODEV; + } + + err = gpio_pin_configure_dt(&config->vddok_gpio, GPIO_INPUT); + if (err < 0) { + LOG_ERR("Failed to configure VDDOK GPIO"); + return err; + } + /* setup READY gpio - normal low */ + if (!gpio_is_ready_dt(&config->ready_gpio)) { + LOG_ERR("VDDOK READY device not ready"); + return -ENODEV; + } + + err = gpio_pin_configure_dt(&config->ready_gpio, GPIO_INPUT); + if (err < 0) { + LOG_ERR("Failed to configure READY GPIO"); + return err; + } + /* setup COMERR gpio - normal low */ + if (!gpio_is_ready_dt(&config->comerr_gpio)) { + LOG_ERR("COMERR GPIO device not ready"); + return -ENODEV; + } + + err = gpio_pin_configure_dt(&config->comerr_gpio, GPIO_INPUT); + if (err < 0) { + LOG_ERR("Failed to configure COMERR GPIO"); + return err; + } + /* setup FAULT gpio - normal low */ + if (!gpio_is_ready_dt(&config->fault_gpio)) { + LOG_ERR("FAULT GPIO device not ready"); + return -ENODEV; + } + + err = gpio_pin_configure_dt(&config->fault_gpio, GPIO_INPUT); + if (err < 0) { + LOG_ERR("Failed to configure FAULT GPIO"); + return err; + } + + err = gpio_pin_set_dt(&config->en_gpio, 1); + if (err) { + return err; + } + err = gpio_pin_set_dt(&config->sync_gpio, 1); + if (err) { + return err; + } + + if (config->crc_en) { + err = gpio_pin_set_dt(&config->crcen_gpio, 1); + } else { + err = gpio_pin_set_dt(&config->crcen_gpio, 0); + } + + if (err) { + return err; + } + + /* Initialize satus and fault flags to 0 */ + data->gpios_ON = 0; + data->gpios_fault = 0; + + err = max14917_fault_check(dev); + + return err; +} + +static int gpio_max14917_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) +{ + int err = 0; + + if ((flags & (GPIO_INPUT | GPIO_OUTPUT)) == GPIO_DISCONNECTED) { + return -ENOTSUP; + } + + if ((flags & GPIO_SINGLE_ENDED) != 0) { + return -ENOTSUP; + } + + if ((flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0) { + return -ENOTSUP; + } + + if (flags & GPIO_INT_ENABLE) { + return -ENOTSUP; + } + + switch (flags & GPIO_DIR_MASK) { + case GPIO_OUTPUT: + break; + case GPIO_INPUT: + default: + LOG_ERR("NOT SUPPORTED OPTION!"); + return -ENOTSUP; + } + + return err; +} + +static int gpio_max14917_port_get_raw(const struct device *dev, gpio_port_value_t *value) +{ + int ret; + const struct max14917_data *data = dev->data; + + ret = max14917_fault_check(dev); + if (ret) { + return ret; + } + + *value = data->gpios_ON; + + return 0; +} + +static int gpio_max14917_port_set_bits_raw(const struct device *dev, gpio_port_pins_t pins) +{ + int ret; + struct max14917_data *data = dev->data; + + ret = max14917_fault_check(dev); + if (ret) { + return ret; + } + + data->gpios_ON = data->gpios_ON | pins; + + return max14917_reg_trans_spi_diag(dev); +} + +static int gpio_max14917_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t pins) +{ + int ret; + struct max14917_data *data = dev->data; + + ret = max14917_fault_check(dev); + if (ret) { + return ret; + } + + data->gpios_ON = data->gpios_ON & ~pins; + + return max14917_reg_trans_spi_diag(dev); +} + +static int gpio_max14917_port_toggle_bits(const struct device *dev, gpio_port_pins_t pins) +{ + int ret; + struct max14917_data *data = dev->data; + + ret = max14917_fault_check(dev); + if (ret) { + return ret; + } + + data->gpios_ON ^= pins; + + return max14917_reg_trans_spi_diag(dev); +} + +static DEVICE_API(gpio, gpio_max14917_api) = { + .pin_configure = gpio_max14917_config, + .port_get_raw = gpio_max14917_port_get_raw, + .port_set_bits_raw = gpio_max14917_port_set_bits_raw, + .port_clear_bits_raw = gpio_max14917_port_clear_bits_raw, + .port_toggle_bits = gpio_max14917_port_toggle_bits, +}; + +#define GPIO_MAX14917_DEVICE(id) \ + static const struct max14917_config max14917_##id##_cfg = { \ + .spi = SPI_DT_SPEC_INST_GET(id, SPI_OP_MODE_MASTER | SPI_WORD_SET(8U), 0U), \ + .vddok_gpio = GPIO_DT_SPEC_INST_GET(id, vddok_gpios), \ + .ready_gpio = GPIO_DT_SPEC_INST_GET(id, ready_gpios), \ + .comerr_gpio = GPIO_DT_SPEC_INST_GET(id, comerr_gpios), \ + .fault_gpio = GPIO_DT_SPEC_INST_GET(id, fault_gpios), \ + .en_gpio = GPIO_DT_SPEC_INST_GET(id, en_gpios), \ + .sync_gpio = GPIO_DT_SPEC_INST_GET(id, sync_gpios), \ + .crcen_gpio = GPIO_DT_SPEC_INST_GET(id, crcen_gpios), \ + .crc_en = DT_INST_PROP(id, crc_en), \ + .pkt_size = (DT_INST_PROP(id, crc_en) & 0x1) ? 2 : 1, \ + }; \ + \ + static struct max14917_data max14917_##id##_data; \ + \ + DEVICE_DT_INST_DEFINE(id, &gpio_max14917_init, NULL, &max14917_##id##_data, \ + &max14917_##id##_cfg, POST_KERNEL, \ + CONFIG_GPIO_MAX14917_INIT_PRIORITY, &gpio_max14917_api); + +DT_INST_FOREACH_STATUS_OKAY(GPIO_MAX14917_DEVICE) diff --git a/drivers/gpio/gpio_max14917.h b/drivers/gpio/gpio_max14917.h new file mode 100644 index 00000000000..f23e1a14a9b --- /dev/null +++ b/drivers/gpio/gpio_max14917.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_GPIO_GPIO_MAX14917_H_ +#define ZEPHYR_DRIVERS_GPIO_GPIO_MAX14917_H_ + +#define MAX14917_CHANNELS 8 + +#define MAX14917_MAX_PKT_SIZE 2 + +#define MAX14917_COMM_ERR BIT(7) +#define MAX14917_VERR BIT(6) +#define MAX14917_THERM_ERR BIT(5) + +#define MAX14917_CRC_POLY 0x15 +#define MAX14917_CRC_INI_VAL 0x1F +#define MAX14917_CRC_EXTRA_BYTE 0x00 +#define MAX14917_CRC_MASK 0x1F + +struct max14917_config { + struct spi_dt_spec spi; + /* Input gpios */ + struct gpio_dt_spec vddok_gpio; + struct gpio_dt_spec ready_gpio; + struct gpio_dt_spec comerr_gpio; + struct gpio_dt_spec fault_gpio; + /* Output gpios */ + struct gpio_dt_spec en_gpio; + struct gpio_dt_spec sync_gpio; + struct gpio_dt_spec crcen_gpio; + bool crc_en; + uint8_t pkt_size; +}; + +struct max14917_data { + uint8_t gpios_ON; /* GPIO states */ + uint8_t gpios_fault; + bool comm_err; + bool verr; + bool therm_err; +}; + +#endif diff --git a/dts/bindings/gpio/adi,max14917-gpio.yaml b/dts/bindings/gpio/adi,max14917-gpio.yaml new file mode 100644 index 00000000000..d94b486e7c7 --- /dev/null +++ b/dts/bindings/gpio/adi,max14917-gpio.yaml @@ -0,0 +1,59 @@ +# Copyright (c) 2025 Analog Devices Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: ADI MAX14917 is octal industrial output + +compatible: "adi,max14917-gpio" + +properties: + "#gpio-cells": + const: 2 + ngpios: + type: int + required: true + const: 8 + description: Number of gpios supported + vddok-gpios: + description: | + High-Side Open-Drain Output. VDDOK is passive low when the internal + logic supply is higher than the UVLO threshold, indicating that the + registers have adequate supply voltage. + type: phandle-array + ready-gpios: + description: | + Ready pin indicates when the device is ready to be used. + type: phandle-array + comerr-gpios: + description: | + Communication Error pin indicates when there is a communication error + on the SPI bus. + type: phandle-array + fault-gpios: + description: | + Fault pin indicates cases of overload or undervoltage conditions + type: phandle-array + en-gpios: + description: | + Enable Pin. Drive the EN pin high to enable the outputs. + Drive EN low to disable/three-state all outputs. + type: phandle-array + sync-gpios: + description: | + All eight output switches are updated simultaneously on the rising edge + of SYNCH, as determined by the content of the SPI command + type: phandle-array + crcen-gpios: + description: | + Drive CRCEN high to enable CRC generation and error detection on the + serial data. + type: phandle-array + crc-en: + description: | + Notify driver if crc pin is enabled. + type: boolean + +gpio-cells: + - pin + - flags + +include: [gpio-controller.yaml, spi-device.yaml] diff --git a/tests/drivers/build_all/gpio/app.overlay b/tests/drivers/build_all/gpio/app.overlay index 746b7548296..8f3d01d8632 100644 --- a/tests/drivers/build_all/gpio/app.overlay +++ b/tests/drivers/build_all/gpio/app.overlay @@ -626,6 +626,23 @@ sync-gpios = <&test_gpio 0 0>; en-gpios = <&test_gpio 0 0>; }; + + test_spi_max14917: max14917@d { + compatible = "adi,max14917-gpio"; + status = "okay"; + reg = <0x0d>; + spi-max-frequency = <0>; + gpio-controller; + #gpio-cells = <2>; + ngpios = <8>; + vddok-gpios = <&test_gpio 0 0>; + ready-gpios = <&test_gpio 0 0>; + comerr-gpios = <&test_gpio 0 0>; + fault-gpios = <&test_gpio 0 0>; + en-gpios = <&test_gpio 0 0>; + sync-gpios = <&test_gpio 0 0>; + crcen-gpios = <&test_gpio 0 0>; + }; }; }; };