diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt index 4e1f72bb71d..cf71fff980b 100644 --- a/drivers/dac/CMakeLists.txt +++ b/drivers/dac/CMakeLists.txt @@ -10,6 +10,7 @@ zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_DAC32 dac_mcux_dac32.c) zephyr_library_sources_ifdef(CONFIG_DAC_STM32 dac_stm32.c) 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_DACX0501 dac_dacx0501.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) diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig index 97edb2c0bcf..4f1d95d5485 100644 --- a/drivers/dac/Kconfig +++ b/drivers/dac/Kconfig @@ -37,6 +37,8 @@ source "drivers/dac/Kconfig.sam" source "drivers/dac/Kconfig.sam0" +source "drivers/dac/Kconfig.dacx0501" + source "drivers/dac/Kconfig.dacx0508" source "drivers/dac/Kconfig.dacx3608" diff --git a/drivers/dac/Kconfig.dacx0501 b/drivers/dac/Kconfig.dacx0501 new file mode 100644 index 00000000000..0481816e46e --- /dev/null +++ b/drivers/dac/Kconfig.dacx0501 @@ -0,0 +1,23 @@ +# DAC configuration options + +# Copyright (c) 2023 Google, LLC. +# +# SPDX-License-Identifier: Apache-2.0 + +config DAC_DACX0501 + bool "TI DACx0501 DAC driver" + default y + depends on DT_HAS_TI_DACX0501_ENABLED + select I2C + help + Enable the driver for the TI DACx0501. + +if DAC_DACX0501 + +config DAC_DACX0501_INIT_PRIORITY + int "Init priority" + default 80 + help + TI DACx0501 DAC device driver initialization priority. Must be greater than I2C_INIT_PRIORITY. + +endif # DAC_DACX0501 diff --git a/drivers/dac/dac_dacx0501.c b/drivers/dac/dac_dacx0501.c new file mode 100644 index 00000000000..f8ee999dee1 --- /dev/null +++ b/drivers/dac/dac_dacx0501.c @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2024 Google LLC. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief Driver for Texas Instruments DACx0501 series + * + * Device driver for the Texas Instruments DACx0501 series of devices: DAC60501, DAC70501 and + * DAC80501: Digital to Analog Converters with a single channel output and with 12, 14 and 16 + * bits resolution respectively. Data sheet can be found here: + * https://www.ti.com/lit/ds/symlink/dac80501.pdf + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(dac_dacx0501, CONFIG_DAC_LOG_LEVEL); + +#define DACX0501_REG_DEVICE_ID 0x01U +#define DACX0501_REG_SYNC 0x02U +#define DACX0501_REG_CONFIG 0x03U +#define DACX0501_REG_GAIN 0x04U +#define DACX0501_REG_TRIGGER 0x05U +#define DACX0501_REG_STATUS 0x07U +#define DACX0501_REG_DAC 0x08U + +#define DACX0501_MASK_DEVICE_ID_RES GENMASK(14, 12) +#define DACX0501_MASK_CONFIG_REF_PWDWN BIT(8) +#define DACX0501_MASK_CONFIG_DAC_PWDWN BIT(0) +#define DACX0501_MASK_GAIN_BUFF_GAIN BIT(0) +#define DACX0501_MASK_GAIN_REFDIV_EN BIT(8) +#define DACX0501_MASK_TRIGGER_SOFT_RESET (BIT(1) | BIT(3)) +#define DACX0501_MASK_STATUS_REF_ALM BIT(0) + +/* Specifies the source of the reference voltage. */ +enum voltage_reference_source { + REF_INTERNAL, /* Internal 2.5V voltage reference. */ + REF_EXTERNAL, /* External pin voltage reference. */ +}; + +/* Specifies the reference voltage multiplier. */ +enum output_gain { + VM_MUL2, /* Multiplies by 2. */ + VM_MUL1, /* Multiplies by 1. */ + VM_DIV2, /* Multiplies by 0.5 */ +}; + +struct dacx0501_config { + struct i2c_dt_spec i2c_spec; + enum voltage_reference_source voltage_reference; + enum output_gain output_gain; +}; + +struct dacx0501_data { + /* Number of bits in the DAC register: Either 12, 14 or 16. */ + uint8_t resolution; +}; + +static int dacx0501_reg_read(const struct device *dev, const uint8_t addr, uint16_t *data) +{ + const struct dacx0501_config *config = dev->config; + uint8_t raw_data[2]; + int status; + + status = i2c_write_read_dt(&config->i2c_spec, &addr, sizeof(addr), raw_data, + sizeof(raw_data)); + if (status != 0) { + return status; + } + + /* DAC is big endian. */ + *data = sys_get_be16(raw_data); + return 0; +} + +static int dacx0501_reg_write(const struct device *dev, uint8_t addr, uint16_t data) +{ + const struct dacx0501_config *config = dev->config; + uint8_t write_cmd[3] = {addr}; + + /* DAC is big endian. */ + sys_put_be16(data, write_cmd + 1); + + return i2c_write_dt(&config->i2c_spec, write_cmd, sizeof(write_cmd)); +} + +static int dacx0501_channel_setup(const struct device *dev, + const struct dac_channel_cfg *channel_cfg) +{ + struct dacx0501_data *data = dev->data; + + /* DACx0501 series only has a single output channel. */ + if (channel_cfg->channel_id != 0) { + LOG_ERR("Unsupported channel %d", channel_cfg->channel_id); + return -ENOTSUP; + } + + if (channel_cfg->resolution != data->resolution) { + LOG_ERR("Unsupported resolution %d. Actual: %d", channel_cfg->resolution, + data->resolution); + return -ENOTSUP; + } + + return 0; +} + +static int dacx0501_write_value(const struct device *dev, uint8_t channel, uint32_t value) +{ + struct dacx0501_data *data = dev->data; + + if (channel != 0) { + LOG_ERR("dacx0501: Unsupported channel %d", channel); + return -ENOTSUP; + } + + if (value >= (1 << data->resolution)) { + LOG_ERR("dacx0501: Value %d out of range", value); + return -EINVAL; + } + + value <<= (16 - data->resolution); + + return dacx0501_reg_write(dev, DACX0501_REG_DAC, value); +} + +static int dacx0501_init(const struct device *dev) +{ + const struct dacx0501_config *config = dev->config; + struct dacx0501_data *data = dev->data; + uint16_t device_id; + int status; + + if (!i2c_is_ready_dt(&config->i2c_spec)) { + LOG_ERR("I2C bus %s not ready", config->i2c_spec.bus->name); + return -ENODEV; + } + + status = dacx0501_reg_read(dev, DACX0501_REG_DEVICE_ID, &device_id); + if (status != 0) { + LOG_ERR("read DEVICE_ID register failed"); + return status; + } + + /* See DEVICE_ID register RES field in the data sheet. */ + data->resolution = 16 - 2 * FIELD_GET(DACX0501_MASK_DEVICE_ID_RES, device_id); + + status = dacx0501_reg_write(dev, DACX0501_REG_CONFIG, + FIELD_PREP(DACX0501_MASK_CONFIG_REF_PWDWN, + config->voltage_reference == REF_EXTERNAL)); + if (status != 0) { + LOG_ERR("write CONFIG register failed"); + return status; + } + + status = dacx0501_reg_write( + dev, DACX0501_REG_GAIN, + FIELD_PREP(DACX0501_MASK_GAIN_REFDIV_EN, config->output_gain == VM_DIV2) | + FIELD_PREP(DACX0501_MASK_GAIN_BUFF_GAIN, config->output_gain == VM_MUL2)); + if (status != 0) { + LOG_ERR("GAIN Register update failed"); + return status; + } + + return 0; +} + +static const struct dac_driver_api dacx0501_driver_api = { + .channel_setup = dacx0501_channel_setup, + .write_value = dacx0501_write_value, +}; + +#define DT_DRV_COMPAT ti_dacx0501 + +#define DACX0501_DEFINE(n) \ + static struct dacx0501_data dacx0501_data_##n = {}; \ + static const struct dacx0501_config dacx0501_config_##n = { \ + .i2c_spec = I2C_DT_SPEC_INST_GET(n), \ + .voltage_reference = \ + _CONCAT(REF_, DT_STRING_UPPER_TOKEN(DT_DRV_INST(n), voltage_reference)), \ + .output_gain = _CONCAT(VM_, DT_STRING_UPPER_TOKEN(DT_DRV_INST(n), output_gain)), \ + }; \ + DEVICE_DT_INST_DEFINE(n, &dacx0501_init, NULL, &dacx0501_data_##n, &dacx0501_config_##n, \ + POST_KERNEL, CONFIG_DAC_DACX0501_INIT_PRIORITY, \ + &dacx0501_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(DACX0501_DEFINE) diff --git a/dts/bindings/dac/ti,dacx0501.yaml b/dts/bindings/dac/ti,dacx0501.yaml new file mode 100644 index 00000000000..53251001271 --- /dev/null +++ b/dts/bindings/dac/ti,dacx0501.yaml @@ -0,0 +1,30 @@ +# Copyright (c) 2024 Google, LLC. +# SPDX-License-Identifier: Apache-2.0 + +include: base.yaml + +description: TI DACx0501 12 to 16 bit DAC series for DAC60501, DAC70501 and DAC80501 devices. + +compatible: "ti,dacx0501" + +properties: + voltage-reference: + type: string + required: true + enum: + - "internal" + - "external" + description: | + DAC voltage reference select: either internal (2.5 V) or external + + output-gain: + type: string + required: true + enum: + - "mul2" + - "mul1" + - "div2" + description: | + This setting can be used to control the output voltage range within the supported bit + resolution. mul2 will double the output range but lower the resolution, while div2 will + lower the range but double the resolution. diff --git a/tests/drivers/build_all/dac/app.overlay b/tests/drivers/build_all/dac/app.overlay index 8f7c91819ac..ea90be65121 100644 --- a/tests/drivers/build_all/dac/app.overlay +++ b/tests/drivers/build_all/dac/app.overlay @@ -68,6 +68,14 @@ voltage_reference = <0>; power_down_mode = <0>; }; + + test_i2c_dacx0501:dacx0501@62 { + compatible = "ti,dacx0501"; + status = "okay"; + reg = <0x62>; + voltage-reference = "internal"; + output-gain = "mul2"; + }; }; test_spi: spi@33334444 {