diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt index 27dd96534ed43..1cf62e8a528bc 100644 --- a/drivers/dac/CMakeLists.txt +++ b/drivers/dac/CMakeLists.txt @@ -34,4 +34,5 @@ zephyr_library_sources_ifdef(CONFIG_DAC_SAMD5X dac_samd5x.c) zephyr_library_sources_ifdef(CONFIG_DAC_SILABS_VDAC dac_silabs_vdac.c) zephyr_library_sources_ifdef(CONFIG_DAC_STM32 dac_stm32.c) zephyr_library_sources_ifdef(CONFIG_DAC_TEST dac_test.c) +zephyr_library_sources_ifdef(CONFIG_DAC_TX311 dac_tx311.c) # zephyr-keep-sorted-stop diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig index 99f447c6136cf..efef5abb22224 100644 --- a/drivers/dac/Kconfig +++ b/drivers/dac/Kconfig @@ -52,6 +52,7 @@ source "drivers/dac/Kconfig.samd5x" source "drivers/dac/Kconfig.silabs" source "drivers/dac/Kconfig.stm32" source "drivers/dac/Kconfig.test" +source "drivers/dac/Kconfig.tx311" # zephyr-keep-sorted-stop endif # DAC diff --git a/drivers/dac/Kconfig.tx311 b/drivers/dac/Kconfig.tx311 new file mode 100644 index 0000000000000..5166b1f3ef73b --- /dev/null +++ b/drivers/dac/Kconfig.tx311 @@ -0,0 +1,14 @@ +# DAC configuration options +# +# Copyright (c) 2025 Andreas Wolf +# +# SPDX-License-Identifier: Apache-2.0 + +config DAC_TX311 + bool "TI DACx311 DAC driver" + default y + depends on DT_HAS_TI_DAC8311_ENABLED || DT_HAS_TI_DAC7311_ENABLED || \ + DT_HAS_TI_DAC6311_ENABLED || DT_HAS_TI_DAC5311_ENABLED + select SPI + help + Enable the driver for TI DACx311 chip. diff --git a/drivers/dac/dac_tx311.c b/drivers/dac/dac_tx311.c new file mode 100644 index 0000000000000..10f716b19c062 --- /dev/null +++ b/drivers/dac/dac_tx311.c @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2025 Andreas Wolf + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file dac_tx311.c + * @brief Driver for the TI x311 single channel DAC chips. + * + * This driver supports multiple variants of the Texas Instrument DAC chip. + * + * DAC5311 : 8-bit resolution + * DAC6311 : 10-bit resolution + * DAC7311 : 12-bit resolution + * DAC8311 : 14-bit resolution + * + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(dac_tx311, CONFIG_DAC_LOG_LEVEL); + +#define DACX311_MIN_RESOLUTION 8U +#define DACX311_MAX_RESOLUTION 14U + +#define DAC8311_RESOLUTION 14U +#define DAC7311_RESOLUTION 12U +#define DAC6311_RESOLUTION 10U +#define DAC5311_RESOLUTION 8U + +#define DACX311_MAX_CHANNEL 1U + +#define DACX311_SPI_HZ_MAX (5*1000*1000) + +struct dacx311_config { + struct spi_dt_spec bus; + uint8_t resolution; + uint8_t power_down_mode; +}; + +struct dacx311_data { + uint8_t resolution; + uint16_t power_down_mode; + uint8_t configured; +}; + +static int dacx311_reg_write(const struct device *dev, uint16_t val) +{ + const struct dacx311_config *cfg = dev->config; + uint8_t tx_bytes[2]; + + /* Construct write buffer for SPI API */ + const struct spi_buf tx_buf[1] = { + { + .buf = tx_bytes, + .len = sizeof(tx_bytes) + } + }; + const struct spi_buf_set tx = { + .buffers = tx_buf, + .count = ARRAY_SIZE(tx_buf) + }; + + /* Set register bits */ + tx_bytes[0] = val >> 8; + tx_bytes[1] = val & 0xFF; + + /* Write to bus */ + return spi_write_dt(&cfg->bus, &tx); +} + +static int dacx311_channel_setup(const struct device *dev, + const struct dac_channel_cfg *channel_cfg) +{ + struct dacx311_data *data = dev->data; + + /* Validate configuration */ + if (channel_cfg->channel_id > (DACX311_MAX_CHANNEL - 1)) { + LOG_ERR("Unsupported channel %d", channel_cfg->channel_id); + return -ENOTSUP; + } + + if (channel_cfg->internal) { + LOG_ERR("Internal channels not supported"); + return -ENOTSUP; + } + + if (data->configured & BIT(channel_cfg->channel_id)) { + LOG_DBG("Channel %d already configured", channel_cfg->channel_id); + return 0; + } + + /* Mark channel as configured */ + data->configured |= BIT(channel_cfg->channel_id); + + LOG_DBG("Channel %d initialized", channel_cfg->channel_id); + + return 0; +} + +static int dacx311_write_value(const struct device *dev, uint8_t channel, + uint32_t value) +{ + struct dacx311_data *data = dev->data; + uint16_t regval; + uint8_t shift; + int ret; + + const bool brdcast = (channel == DAC_CHANNEL_BROADCAST) ? 1 : 0; + + if (!brdcast && (channel > (DACX311_MAX_CHANNEL - 1))) { + LOG_ERR("Unsupported channel %d", channel); + return -ENOTSUP; + } + + /* + * Check if channel is initialized + * If broadcast channel is used, check if any channel is initialized + */ + if ((brdcast && !data->configured) || + (channel < DACX311_MAX_CHANNEL && !(data->configured & BIT(channel)))) { + LOG_ERR("Channel %d not initialized", channel); + return -EINVAL; + } + + if (value >= (1 << (data->resolution))) { + LOG_ERR("Value %d out of range", value); + return -EINVAL; + } + + /* + * (See https://www.ti.com/document-viewer/dac6311/datasheet) + * + * Shift given value to align MSB bit position to register bit 13. + * + * DAC output register format: + * + * | 15 14 | 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | + * |-------|---------------------------------------------------| + * | Mode | 8311[13:0] / 7311[13:2] / 6311[13:4] / 5311[13:6] | + */ + shift = DACX311_MAX_RESOLUTION - data->resolution; + regval = value << shift; + + /* + * Set mode bits to value taken from configuration. + * + * MODE = 0 0 -> Normal Operation + * 0 1 -> Output 1 kΩ to GND + * 1 0 -> Output 100 kΩ to GND + * 1 1 -> High-Z + */ + regval &= 0x3FFF; + regval |= data->power_down_mode; + + /* Write to output */ + ret = dacx311_reg_write(dev, regval); + if (ret) { + LOG_ERR("Unable to set value %d on channel %d", value, channel); + return -EIO; + } + + return 0; +} + +static int dacx311_init(const struct device *dev) +{ + const struct dacx311_config *config = dev->config; + struct dacx311_data *data = dev->data; + + /* Set bit resolution for this chip variant */ + data->resolution = config->resolution; + + /* Set the power mode (shifted to upper bits) */ + data->power_down_mode = FIELD_PREP(BIT_MASK(2) << DACX311_MAX_RESOLUTION, + config->power_down_mode); + + return 0; +} + +static DEVICE_API(dac, dacx311_driver_api) = { + .channel_setup = dacx311_channel_setup, + .write_value = dacx311_write_value +}; + +#define INST_DT_DACX311(inst, t) DT_INST(inst, ti_dac##t) + +#define DACX311_DEVICE(t, n, res) \ + BUILD_ASSERT(DT_INST_ENUM_IDX(n, power_down_mode) <= 3, \ + "Invalid power down mode");\ + BUILD_ASSERT(DT_PROP(INST_DT_DACX311(n, t), spi_max_frequency) \ + <= DACX311_SPI_HZ_MAX, "Invalid SPI frequency");\ + static struct dacx311_data dac##t##_data_##n; \ + static const struct dacx311_config dac##t##_config_##n = { \ + .bus = SPI_DT_SPEC_GET(INST_DT_DACX311(n, t), \ + SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | \ + SPI_MODE_CPHA | \ + SPI_WORD_SET(16), 0), \ + .resolution = res, \ + .power_down_mode = DT_INST_ENUM_IDX(n, power_down_mode), \ + }; \ + DEVICE_DT_DEFINE(INST_DT_DACX311(n, t), \ + dacx311_init, NULL, \ + &dac##t##_data_##n, \ + &dac##t##_config_##n, POST_KERNEL, \ + CONFIG_DAC_INIT_PRIORITY, \ + &dacx311_driver_api) + +/* + * DAC8311: 14-bit + */ +#define DAC8311_DEVICE(n) DACX311_DEVICE(8311, n, DAC8311_RESOLUTION) +/* + * DAC7311: 12-bit + */ +#define DAC7311_DEVICE(n) DACX311_DEVICE(7311, n, DAC7311_RESOLUTION) +/* + * DAC6311: 10-bit + */ +#define DAC6311_DEVICE(n) DACX311_DEVICE(6311, n, DAC6311_RESOLUTION) +/* + * DAC5311: 8-bit + */ +#define DAC5311_DEVICE(n) DACX311_DEVICE(5311, n, DAC5311_RESOLUTION) + +#define CALL_WITH_ARG(arg, expr) expr(arg) + +#define INST_DT_DACX311_FOREACH(t, inst_expr) \ + LISTIFY(DT_NUM_INST_STATUS_OKAY(ti_dac##t), \ + CALL_WITH_ARG, (), inst_expr) + +#define DT_DRV_COMPAT ti_dac8311 +INST_DT_DACX311_FOREACH(8311, DAC8311_DEVICE); +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT ti_dac7311 +INST_DT_DACX311_FOREACH(7311, DAC7311_DEVICE); +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT ti_dac6311 +INST_DT_DACX311_FOREACH(6311, DAC6311_DEVICE); +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT ti_dac5311 +INST_DT_DACX311_FOREACH(5311, DAC5311_DEVICE); +#undef DT_DRV_COMPAT diff --git a/dts/bindings/dac/ti,dac5311.yaml b/dts/bindings/dac/ti,dac5311.yaml new file mode 100644 index 0000000000000..f2496d3781a8b --- /dev/null +++ b/dts/bindings/dac/ti,dac5311.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Andreas Wolf +# SPDX-License-Identifier: Apache-2.0 + +description: Texas Instrument 8-Bit DAC 5311 + +compatible: "ti,dac5311" + +include: ti,dacx311-base.yaml diff --git a/dts/bindings/dac/ti,dac6311.yaml b/dts/bindings/dac/ti,dac6311.yaml new file mode 100644 index 0000000000000..fce9c5d4914bf --- /dev/null +++ b/dts/bindings/dac/ti,dac6311.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Andreas Wolf +# SPDX-License-Identifier: Apache-2.0 + +description: Texas Instrument 10-Bit DAC 6311 + +compatible: "ti,dac6311" + +include: ti,dacx311-base.yaml diff --git a/dts/bindings/dac/ti,dac7311.yaml b/dts/bindings/dac/ti,dac7311.yaml new file mode 100644 index 0000000000000..be92f838acf7b --- /dev/null +++ b/dts/bindings/dac/ti,dac7311.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Andreas Wolf +# SPDX-License-Identifier: Apache-2.0 + +description: Texas Instrument 12-Bit DAC 7311 + +compatible: "ti,dac7311" + +include: ti,dacx311-base.yaml diff --git a/dts/bindings/dac/ti,dac8311.yaml b/dts/bindings/dac/ti,dac8311.yaml new file mode 100644 index 0000000000000..d897489dc9297 --- /dev/null +++ b/dts/bindings/dac/ti,dac8311.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Andreas Wolf +# SPDX-License-Identifier: Apache-2.0 + +description: Texas Instrument 14-Bit DAC 8311 + +compatible: "ti,dac8311" + +include: ti,dacx311-base.yaml diff --git a/dts/bindings/dac/ti,dacx311-base.yaml b/dts/bindings/dac/ti,dacx311-base.yaml new file mode 100644 index 0000000000000..e92de892d67a5 --- /dev/null +++ b/dts/bindings/dac/ti,dacx311-base.yaml @@ -0,0 +1,27 @@ +# Copyright (c) 2025 Andreas Wolf +# SPDX-License-Identifier: Apache-2.0 + +include: [dac-controller.yaml, spi-device.yaml] + +properties: + "#io-channel-cells": + const: 1 + + power-down-mode: + type: string + default: "normal" + enum: + - "normal" + - "power-down-1k" + - "power-down-100k" + - "power-down-3-state" + description: | + Power-down mode select. + - Normal mode (reg: 0). + - 1 kOhm output impedance (reg: 1). + - 100 kOhm output impedance (reg: 2). + - Three-state output impedance (reg: 3). + The default corresponds to the reset value of the register field. + +io-channel-cells: + - output diff --git a/tests/drivers/build_all/dac/app.overlay b/tests/drivers/build_all/dac/app.overlay index 03fc52b3e4ec3..50a0de9906ad9 100644 --- a/tests/drivers/build_all/dac/app.overlay +++ b/tests/drivers/build_all/dac/app.overlay @@ -125,6 +125,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 { @@ -331,6 +332,13 @@ spi-max-frequency = <0>; #io-channel-cells = <1>; }; + + test_spi_dac6311: dac6311@16 { + compatible = "ti,dac6311"; + reg = <0x16>; + spi-max-frequency = <0>; + #io-channel-cells = <1>; + }; }; }; };