From 60e8c17076b6fd278a13038310b59a2f98b3f5de Mon Sep 17 00:00:00 2001 From: Guillaume Ranquet Date: Thu, 4 Jul 2024 13:57:43 +0200 Subject: [PATCH] drivers: dac: Add support for MAX22017 DAC The MAX22017 is a two-channel industrial-grade software-configurable analog output device that can be used in either voltage or current output mode. Signed-off-by: Guillaume Ranquet --- drivers/dac/CMakeLists.txt | 1 + drivers/dac/Kconfig | 2 + drivers/dac/Kconfig.max22017 | 21 +++ drivers/dac/dac_max22017.c | 237 ++++++++++++++++++++++++ dts/bindings/dac/adi,max22017.yaml | 72 +++++++ tests/drivers/build_all/dac/app.overlay | 14 ++ 6 files changed, 347 insertions(+) create mode 100644 drivers/dac/Kconfig.max22017 create mode 100644 drivers/dac/dac_max22017.c create mode 100644 dts/bindings/dac/adi,max22017.yaml diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt index eec91be800a218d..a8761a6b53bc805 100644 --- a/drivers/dac/CMakeLists.txt +++ b/drivers/dac/CMakeLists.txt @@ -25,3 +25,4 @@ zephyr_library_sources_ifdef(CONFIG_DAC_AD569X dac_ad569x.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE dac_handlers.c) zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_GAU dac_mcux_gau.c) zephyr_library_sources_ifdef(CONFIG_DAC_TEST dac_test.c) +zephyr_library_sources_ifdef(CONFIG_DAC_MAX22017 dac_max22017.c) diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig index 26b17f1a31d57fc..74def829954887a 100644 --- a/drivers/dac/Kconfig +++ b/drivers/dac/Kconfig @@ -61,4 +61,6 @@ source "drivers/dac/Kconfig.ad569x" source "drivers/dac/Kconfig.test" +source "drivers/dac/Kconfig.max22017" + endif # DAC diff --git a/drivers/dac/Kconfig.max22017 b/drivers/dac/Kconfig.max22017 new file mode 100644 index 000000000000000..d7a46229f02bb25 --- /dev/null +++ b/drivers/dac/Kconfig.max22017 @@ -0,0 +1,21 @@ +# Copyright (c) 2024 Analog Devices Inc. +# Copyright (c) 2024 BayLibre SAS +# SPDX-License-Identifier: Apache-2.0 + +config DAC_MAX22017 + bool "Analog Devices MAX22017 DAC" + default y + depends on DT_HAS_ADI_MAX22017_DAC_ENABLED + select MFD + help + Enable the driver for the Analog Devices MAX22017 DAC + +if DAC_MAX22017 + +config DAC_MAX22017_INIT_PRIORITY + int "Init priority" + default 80 + help + Analog Devices MAX22017 DAC device driver initialization priority. + +endif # DAC_MAX22017 diff --git a/drivers/dac/dac_max22017.c b/drivers/dac/dac_max22017.c new file mode 100644 index 000000000000000..3c971e7a8408bb5 --- /dev/null +++ b/drivers/dac/dac_max22017.c @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2024 Analog Devices Inc. + * Copyright (c) 2024 Baylibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include + +#define DT_DRV_COMPAT adi_max22017_dac +LOG_MODULE_REGISTER(dac_max22017, CONFIG_DAC_LOG_LEVEL); + +struct dac_adi_max22017_config { + const struct device *parent; + uint8_t resolution; + uint8_t nchannels; + const struct gpio_dt_spec gpio_ldac; + const struct gpio_dt_spec gpio_busy; + uint8_t latch_mode[MAX22017_MAX_CHANNEL]; + uint8_t polarity_mode[MAX22017_MAX_CHANNEL]; + uint8_t dac_mode[MAX22017_MAX_CHANNEL]; + uint8_t ovc_mode[MAX22017_MAX_CHANNEL]; + uint16_t timeout; +}; + +static int max22017_channel_setup(const struct device *dev, + const struct dac_channel_cfg *channel_cfg) +{ + int ret; + uint16_t ao_cnfg, gen_cnfg; + uint8_t chan = channel_cfg->channel_id; + const struct dac_adi_max22017_config *config = dev->config; + const struct device *parent = config->parent; + struct max22017_data *data = parent->data; + + if (chan > config->nchannels - 1) { + LOG_ERR("Unsupported channel %d", chan); + return -ENOTSUP; + } + + if (channel_cfg->resolution != config->resolution) { + LOG_ERR("Unsupported resolution %d", chan); + return -ENOTSUP; + } + + k_mutex_lock(&data->lock, K_FOREVER); + ret = max22017_reg_read(parent, MAX22017_AO_CNFG_OFF, &ao_cnfg); + if (ret) { + goto fail; + } + + ao_cnfg |= FIELD_PREP(MAX22017_AO_CNFG_AO_EN, BIT(chan)); + + if (!config->latch_mode[chan]) { + ao_cnfg |= FIELD_PREP(MAX22017_AO_CNFG_AO_LD_CNFG, BIT(chan)); + } + + if (config->polarity_mode[chan]) { + ao_cnfg |= FIELD_PREP(MAX22017_AO_CNFG_AO_UNI, BIT(chan)); + } + + if (config->dac_mode[chan]) { + ao_cnfg |= FIELD_PREP(MAX22017_AO_CNFG_AO_MODE, BIT(chan)); + } + + ret = max22017_reg_write(parent, MAX22017_AO_CNFG_OFF, ao_cnfg); + if (ret) { + goto fail; + } + + ret = max22017_reg_read(parent, MAX22017_GEN_CNFG_OFF, &gen_cnfg); + if (ret) { + goto fail; + } + + if (config->ovc_mode[chan]) { + gen_cnfg |= FIELD_PREP(MAX22017_GEN_CNFG_OVC_CNFG, BIT(chan)); + /* Over current shutdown mode */ + if (config->ovc_mode[chan] == 2) { + gen_cnfg |= FIELD_PREP(MAX22017_GEN_CNFG_OVC_SHDN_CNFG, BIT(chan)); + } + } + + ret = max22017_reg_write(parent, MAX22017_GEN_CNFG_OFF, gen_cnfg); +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +static int max22017_write_value(const struct device *dev, uint8_t channel, uint32_t value) +{ + int ret; + uint16_t ao_sta; + const struct dac_adi_max22017_config *config = dev->config; + const struct device *parent = config->parent; + struct max22017_data *data = parent->data; + + if (channel > config->nchannels - 1) { + LOG_ERR("unsupported channel %d", channel); + return ENOTSUP; + } + + if (value >= (1 << config->resolution)) { + LOG_ERR("Value %d out of range", value); + return -EINVAL; + } + + k_mutex_lock(&data->lock, K_FOREVER); + if (config->gpio_busy.port) { + if (gpio_pin_get_dt(&config->gpio_busy)) { + ret = -EBUSY; + goto fail; + } + } else { + ret = max22017_reg_read(parent, MAX22017_AO_STA_OFF, &ao_sta); + if (ret) { + goto fail; + } + if (FIELD_GET(MAX22017_AO_STA_BUSY_STA, ao_sta)) { + ret = -EBUSY; + goto fail; + } + } + + ret = max22017_reg_write(parent, MAX22017_AO_DATA_CHn_OFF(channel), + FIELD_PREP(MAX22017_AO_DATA_CHn_AO_DATA_CH, value)); + if (ret) { + goto fail; + } + + if (config->latch_mode[channel]) { + if (config->gpio_ldac.port) { + gpio_pin_set_dt(&config->gpio_ldac, false); + k_sleep(K_USEC(MAX22017_LDAC_TOGGLE_TIME)); + gpio_pin_set_dt(&config->gpio_ldac, true); + } else { + ret = max22017_reg_write( + parent, MAX22017_AO_CMD_OFF, + FIELD_PREP(MAX22017_AO_CMD_AO_LD_CTRL, BIT(channel))); + } + } +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +static int max22017_init(const struct device *dev) +{ + int ret; + uint16_t gen_cnfg = 0, gen_int_en = 0; + const struct dac_adi_max22017_config *config = dev->config; + const struct device *parent = config->parent; + struct max22017_data *data = config->parent->data; + + if (!device_is_ready(config->parent)) { + LOG_ERR("parent adi_max22017 MFD device '%s' not ready", config->parent->name); + return -EINVAL; + } + + k_mutex_lock(&data->lock, K_FOREVER); + + ret = max22017_reg_read(parent, MAX22017_GEN_CNFG_OFF, &gen_cnfg); + if (ret) { + goto fail; + } + + ret = max22017_reg_read(parent, MAX22017_GEN_INTEN_OFF, &gen_int_en); + if (ret) { + goto fail; + } + + if (config->timeout) { + gen_cnfg |= FIELD_PREP(MAX22017_GEN_CNFG_TMOUT_EN, 1) | + FIELD_PREP(MAX22017_GEN_CNFG_TMOUT_SEL, (config->timeout / 100) - 1); + gen_int_en |= FIELD_PREP(MAX22017_GEN_INTEN_TMOUT_INTEN, 1); + } + + ret = max22017_reg_write(parent, MAX22017_GEN_CNFG_OFF, gen_cnfg); + if (ret) { + goto fail; + } + + ret = max22017_reg_write(parent, MAX22017_GEN_INTEN_OFF, gen_int_en); + if (ret) { + goto fail; + } + + if (config->gpio_ldac.port) { + ret = gpio_pin_configure_dt(&config->gpio_ldac, GPIO_OUTPUT_ACTIVE); + if (ret) { + LOG_ERR("failed to initialize GPIO ldac pin"); + goto fail; + } + } + + if (config->gpio_busy.port) { + ret = gpio_pin_configure_dt(&config->gpio_busy, GPIO_INPUT); + if (ret) { + LOG_ERR("failed to initialize GPIO busy pin"); + goto fail; + } + } + +fail: + k_mutex_unlock(&data->lock); + return ret; +} + +static const struct dac_driver_api max22017_driver_api = { + .channel_setup = max22017_channel_setup, + .write_value = max22017_write_value, +}; + +#define DAC_MAX22017_DEVICE(id) \ + static const struct dac_adi_max22017_config dac_adi_max22017_config_##id = { \ + .parent = DEVICE_DT_GET(DT_INST_PARENT(id)), \ + .resolution = DT_INST_PROP_OR(id, resolution, 16), \ + .nchannels = DT_INST_PROP_OR(id, num_channels, 2), \ + .gpio_busy = GPIO_DT_SPEC_INST_GET_OR(id, busy_gpios, {0}), \ + .gpio_ldac = GPIO_DT_SPEC_INST_GET_OR(id, ldac_gpios, {0}), \ + .latch_mode = DT_INST_PROP_OR(id, latch_mode, {0}), \ + .polarity_mode = DT_INST_PROP_OR(id, polarity_mode, {0}), \ + .dac_mode = DT_INST_PROP_OR(id, dac_mode, {0}), \ + .ovc_mode = DT_INST_PROP_OR(id, overcurrent_mode, {0}), \ + .timeout = DT_INST_PROP_OR(id, timeout, 0), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(id, max22017_init, NULL, NULL, &dac_adi_max22017_config_##id, \ + POST_KERNEL, CONFIG_DAC_MAX22017_INIT_PRIORITY, \ + &max22017_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(DAC_MAX22017_DEVICE); diff --git a/dts/bindings/dac/adi,max22017.yaml b/dts/bindings/dac/adi,max22017.yaml new file mode 100644 index 000000000000000..77f0589510edf9d --- /dev/null +++ b/dts/bindings/dac/adi,max22017.yaml @@ -0,0 +1,72 @@ +# Copyright (c) 2024 Analog Devices Inc. +# Copyright (c) 2024 BayLibre SAS +# SPDX-License-Identifier: Apache-2.0 + +include: dac-controller.yaml + +description: Analog Devices MAX22017 16bit DAC + +properties: + "#io-channel-cells": + const: 2 + + num-channels: + type: int + description: Number of DAC output channels. + + resolution: + type: int + description: DAC resolution. + + busy-gpios: + description: Busy line indicating the DAC is calculating next sample. + type: phandle-array + + ldac-gpios: + description: Load both DAC latches at the same time. + type: phandle-array + + polarity-mode: + description: | + Unipolar/bipolar mode selection for channels. + 0 Indicates bipolar mode and 1 unipolar mode. + The default settings to bipolar here align with the default mode of the device. + default: [0, 0] + type: uint8-array + + dac-mode: + description: | + Voltage/current mode selection for channels. + 0 indicates voltage mode and 1 indicates current mode. + The default settings to voltage mode here align with the default mode of the device. + default: [0, 0] + type: uint8-array + + latch-mode: + description: | + Latch mode selection for channels. + 0 means the channel is not latched, 1 means latched. + The default settings to non latched should be more straightforward to use than the latched + mode. The latch mode can be used eitheir with the ldac-gpios to load both channels at the + same time or if no ldac-gpios property is set, latching will be done per channel with a + register write. + default: [0, 0] + type: uint8-array + + overcurrent-mode: + description: | + Overcurrent mode selection for channels. + 0 for current limiting mode + 1 for short circuit protection auto power up mode + 2 for short circuit protection shutdown mode + The default setting to current limiting mode here aligns with the default mode of the device. + default: [0, 0] + type: uint8-array + + timeout: + description: | + Timeout in ms. + The value should be between 100 and 1600ms in increments of 100ms. + type: int + +compatible: "adi,max22017-dac" diff --git a/tests/drivers/build_all/dac/app.overlay b/tests/drivers/build_all/dac/app.overlay index c3c9fec38ed3c2b..e9f0777190d1572 100644 --- a/tests/drivers/build_all/dac/app.overlay +++ b/tests/drivers/build_all/dac/app.overlay @@ -124,6 +124,7 @@ <&test_gpio 0 0>, <&test_gpio 0 0>, <&test_gpio 0 0>, + <&test_gpio 0 0>, <&test_gpio 0 0>; test_spi_dac60508: dac60508@0 { @@ -288,6 +289,19 @@ #io-channel-cells = <1>; }; }; + + test_spi_max22017_mfd: max22017_mfd@11 { + compatible = "adi,max22017"; + status = "okay"; + spi-max-frequency = <1000000>; + reg = <0x11>; + + test_spi_max22017_dac0: dac-controller { + compatible = "adi,max22017-dac"; + #io-channel-cells = <2>; + status = "okay"; + }; + }; }; }; };