From 10818f75e2672d6b9460ec9713be31719ce1f90f Mon Sep 17 00:00:00 2001 From: Marcus Folkesson Date: Wed, 5 Apr 2023 14:16:13 +0200 Subject: [PATCH] drivers: dac: add support for ltc1660/ltc1665 LTC1665/LTC1660 is a 8/10-bit Digital-to-Analog Converter (DAC) with eight individual channels. Signed-off-by: Marcus Folkesson --- drivers/dac/CMakeLists.txt | 1 + drivers/dac/Kconfig | 2 + drivers/dac/Kconfig.ltc166x | 23 ++++++ drivers/dac/dac_ltc166x.c | 141 ++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 drivers/dac/Kconfig.ltc166x create mode 100644 drivers/dac/dac_ltc166x.c diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt index b0e86e3bd4..800bc895fd 100644 --- a/drivers/dac/CMakeLists.txt +++ b/drivers/dac/CMakeLists.txt @@ -9,6 +9,7 @@ zephyr_library_sources_ifdef(CONFIG_DAC_SAM dac_sam.c) zephyr_library_sources_ifdef(CONFIG_DAC_SAM0 dac_sam0.c) zephyr_library_sources_ifdef(CONFIG_DAC_DACX0508 dac_dacx0508.c) zephyr_library_sources_ifdef(CONFIG_DAC_DACX3608 dac_dacx3608.c) +zephyr_library_sources_ifdef(CONFIG_DAC_LTC166X dac_ltc166x.c) zephyr_library_sources_ifdef(CONFIG_DAC_SHELL dac_shell.c) zephyr_library_sources_ifdef(CONFIG_DAC_MCP4725 dac_mcp4725.c) zephyr_library_sources_ifdef(CONFIG_DAC_MCP4728 dac_mcp4728.c) diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig index 7b54572146..77b0db902b 100644 --- a/drivers/dac/Kconfig +++ b/drivers/dac/Kconfig @@ -42,6 +42,8 @@ source "drivers/dac/Kconfig.dacx0508" source "drivers/dac/Kconfig.dacx3608" +source "drivers/dac/Kconfig.ltc166x" + source "drivers/dac/Kconfig.mcp4725" source "drivers/dac/Kconfig.mcp4728" diff --git a/drivers/dac/Kconfig.ltc166x b/drivers/dac/Kconfig.ltc166x new file mode 100644 index 0000000000..66bfcfb89a --- /dev/null +++ b/drivers/dac/Kconfig.ltc166x @@ -0,0 +1,23 @@ +# DAC configuration options + +# Copyright (C) 2023 Marcus Folkesson +# +# SPDX-License-Identifier: Apache-2.0 + +config DAC_LTC166X + bool "Linear Technology LTC166X DAC" + default y + select SPI + depends on DT_HAS_LLTC_LTC1660_ENABLED || DT_HAS_LLTC_LTC1665_ENABLED + help + Enable the driver for the Linear Technology LTC166X DAC + +if DAC_LTC166X + +config DAC_LTC166X_INIT_PRIORITY + int "Init priority" + default 80 + help + Linear Technology LTC166X DAC device driver initialization priority. + +endif # DAC_LTC166X diff --git a/drivers/dac/dac_ltc166x.c b/drivers/dac/dac_ltc166x.c new file mode 100644 index 0000000000..81d579949c --- /dev/null +++ b/drivers/dac/dac_ltc166x.c @@ -0,0 +1,141 @@ +/* + * Driver for Linear Technology LTC1660/LTC1665 DAC + * + * Copyright (C) 2023 Marcus Folkesson + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(dac_ltc166x, CONFIG_DAC_LOG_LEVEL); + +#define LTC166X_REG_MASK GENMASK(15, 12) +#define LTC166X_DATA8_MASK GENMASK(11, 4) +#define LTC166X_DATA10_MASK GENMASK(12, 2) + +struct ltc166x_config { + struct spi_dt_spec bus; + uint8_t resolution; + uint8_t nchannels; +}; + +static int ltc166x_reg_write(const struct device *dev, uint8_t addr, + uint32_t data) +{ + const struct ltc166x_config *config = dev->config; + uint16_t regval; + + regval = FIELD_PREP(LTC166X_REG_MASK, addr); + + if (config->resolution == 10) { + regval |= FIELD_PREP(LTC166X_DATA10_MASK, data); + } else { + regval |= FIELD_PREP(LTC166X_DATA8_MASK, data); + } + + const struct spi_buf buf = { + .buf = ®val, + .len = sizeof(regval), + }; + + struct spi_buf_set tx = { + .buffers = &buf, + .count = 1, + }; + + return spi_write_dt(&config->bus, &tx); +} + + +static int ltc166x_channel_setup(const struct device *dev, + const struct dac_channel_cfg *channel_cfg) +{ + const struct ltc166x_config *config = dev->config; + + if (channel_cfg->channel_id > config->nchannels - 1) { + LOG_ERR("Unsupported channel %d", channel_cfg->channel_id); + return -ENOTSUP; + } + + if (channel_cfg->resolution != config->resolution) { + LOG_ERR("Unsupported resolution %d", channel_cfg->resolution); + return -ENOTSUP; + } + + return 0; +} + +static int ltc166x_write_value(const struct device *dev, uint8_t channel, + uint32_t value) +{ + const struct ltc166x_config *config = dev->config; + + 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; + } + + return ltc166x_reg_write(dev, channel + 1, value); +} + +static int ltc166x_init(const struct device *dev) +{ + const struct ltc166x_config *config = dev->config; + + if (!spi_is_ready_dt(&config->bus)) { + LOG_ERR("SPI bus %s not ready", config->bus.bus->name); + return -ENODEV; + } + return 0; +} + +static const struct dac_driver_api ltc166x_driver_api = { + .channel_setup = ltc166x_channel_setup, + .write_value = ltc166x_write_value, +}; + + +#define INST_DT_LTC166X(inst, t) DT_INST(inst, lltc_ltc##t) + +#define LTC166X_DEVICE(t, n, res, nchan) \ + static const struct ltc166x_config ltc##t##_config_##n = { \ + .bus = SPI_DT_SPEC_GET(INST_DT_LTC166X(n, t), \ + SPI_OP_MODE_MASTER | \ + SPI_WORD_SET(8), 0), \ + .resolution = res, \ + .nchannels = nchan, \ + }; \ + DEVICE_DT_DEFINE(INST_DT_LTC166X(n, t), \ + <c166x_init, NULL, \ + NULL, \ + <c##t##_config_##n, POST_KERNEL, \ + CONFIG_DAC_LTC166X_INIT_PRIORITY, \ + <c166x_driver_api) + +/* + * LTC1660: 10-bit + */ +#define LTC1660_DEVICE(n) LTC166X_DEVICE(1660, n, 10, 8) + +/* + * LTC1665: 8-bit + */ +#define LTC1665_DEVICE(n) LTC166X_DEVICE(1665, n, 8, 8) + +#define CALL_WITH_ARG(arg, expr) expr(arg) + +#define INST_DT_LTC166X_FOREACH(t, inst_expr) \ + LISTIFY(DT_NUM_INST_STATUS_OKAY(lltc_ltc##t), \ + CALL_WITH_ARG, (), inst_expr) + +INST_DT_LTC166X_FOREACH(1660, LTC1660_DEVICE); +INST_DT_LTC166X_FOREACH(1665, LTC1665_DEVICE);