From 1463596205698542d5a8ff41be021c4cf147e392 Mon Sep 17 00:00:00 2001 From: Matija Tudan Date: Wed, 16 Dec 2020 21:57:28 +0100 Subject: [PATCH] drivers: dac: added driver for TI DACx3608 The DAC53608 and DAC43608 (DACx3608) are lowpower, eight-channel, voltage-output, 10-bit or 8-bit digital-to-analog converters (DACs) respectively. They support I2C with a wide power supply range from 1.8 V to 5.5 V, and a full scale output voltage range of 1.8 V to 5.5 V. The DACx3608 also includes per channel, user programmable, power down registers. Signed-off-by: Matija Tudan --- drivers/dac/CMakeLists.txt | 1 + drivers/dac/Kconfig | 2 + drivers/dac/Kconfig.dacx3608 | 18 ++ drivers/dac/dac_dacx3608.c | 281 +++++++++++++++++++++++++ dts/bindings/dac/ti,dac43608.yaml | 8 + dts/bindings/dac/ti,dac53608.yaml | 8 + dts/bindings/dac/ti,dacx3608-base.yaml | 11 + 7 files changed, 329 insertions(+) create mode 100644 drivers/dac/Kconfig.dacx3608 create mode 100644 drivers/dac/dac_dacx3608.c create mode 100644 dts/bindings/dac/ti,dac43608.yaml create mode 100644 dts/bindings/dac/ti,dac53608.yaml create mode 100644 dts/bindings/dac/ti,dacx3608-base.yaml diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt index 0b5cee37d6e..0e5fdf34981 100644 --- a/drivers/dac/CMakeLists.txt +++ b/drivers/dac/CMakeLists.txt @@ -7,5 +7,6 @@ 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_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_SHELL dac_shell.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE dac_handlers.c) diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig index f30b3b4032b..254e2456b88 100644 --- a/drivers/dac/Kconfig +++ b/drivers/dac/Kconfig @@ -32,4 +32,6 @@ source "drivers/dac/Kconfig.sam0" source "drivers/dac/Kconfig.dacx0508" +source "drivers/dac/Kconfig.dacx3608" + endif # DAC diff --git a/drivers/dac/Kconfig.dacx3608 b/drivers/dac/Kconfig.dacx3608 new file mode 100644 index 00000000000..de8f218bee3 --- /dev/null +++ b/drivers/dac/Kconfig.dacx3608 @@ -0,0 +1,18 @@ +# DAC configuration options + +# Copyright (c) 2020 Matija Tudan +# +# SPDX-License-Identifier: Apache-2.0 + +config DAC_DACX3608 + bool "TI DACX3608 DAC driver" + depends on I2C + help + Enable the driver for the TI DACX3608. + +config DAC_DACX3608_INIT_PRIORITY + int "Init priority" + depends on DAC_DACX3608 + default 80 + help + DACX3608 DAC device driver initialization priority. diff --git a/drivers/dac/dac_dacx3608.c b/drivers/dac/dac_dacx3608.c new file mode 100644 index 00000000000..d0e36a1876e --- /dev/null +++ b/drivers/dac/dac_dacx3608.c @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2020 Matija Tudan + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(dac_dacx3608, CONFIG_DAC_LOG_LEVEL); + +/* Register addresses */ +#define DACX3608_REG_DEVICE_CONFIG 0x01U +#define DACX3608_REG_STATUS_TRIGGER 0x02U +#define DACX3608_REG_BRDCAST 0x03U +#define DACX3608_REG_DACA_DATA 0x08U + +#define DAC43608_DEVICE_ID 0x500 /* STATUS_TRIGGER[DEVICE_ID] */ +#define DAC53608_DEVICE_ID 0x300 /* STATUS_TRIGGER[DEVICE_ID] */ +#define DACX3608_SW_RST 0x0A /* STATUS_TRIGGER[SW_RST] */ +#define DACX3608_POR_DELAY 5 +#define DACX3608_MAX_CHANNEL 8 + +struct dacx3608_config { + const char *i2c_bus; + uint16_t i2c_addr; + uint8_t resolution; +}; + +struct dacx3608_data { + const struct device *i2c; + uint8_t configured; +}; + +static int dacx3608_reg_read(const struct device *dev, uint8_t reg, + uint16_t *val) +{ + struct dacx3608_data *data = dev->data; + const struct dacx3608_config *cfg = dev->config; + + if (i2c_burst_read(data->i2c, cfg->i2c_addr, + reg, (uint8_t *) val, 2) < 0) { + LOG_ERR("I2C read failed"); + return -EIO; + } + + *val = sys_be16_to_cpu(*val); + + return 0; +} + +static int dacx3608_reg_write(const struct device *dev, uint8_t reg, + uint16_t val) +{ + struct dacx3608_data *data = dev->data; + const struct dacx3608_config *cfg = dev->config; + uint8_t buf[3] = {reg, val >> 8, val & 0xFF}; + + return i2c_write(data->i2c, buf, sizeof(buf), cfg->i2c_addr); +} + +int dacx3608_reg_update(const struct device *dev, uint8_t reg, + uint16_t mask, bool setting) +{ + uint16_t regval; + int ret; + + ret = dacx3608_reg_read(dev, reg, ®val); + if (ret) { + return -EIO; + } + + if (setting) { + regval |= mask; + } else { + regval &= ~mask; + } + + ret = dacx3608_reg_write(dev, reg, regval); + if (ret) { + return ret; + } + + return 0; +} + +static int dacx3608_channel_setup(const struct device *dev, + const struct dac_channel_cfg *channel_cfg) +{ + const struct dacx3608_config *config = dev->config; + struct dacx3608_data *data = dev->data; + bool setting = false; + int ret; + + if (channel_cfg->channel_id > DACX3608_MAX_CHANNEL - 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; + } + + if (data->configured & BIT(channel_cfg->channel_id)) { + LOG_DBG("Channel %d already configured", channel_cfg->channel_id); + return 0; + } + + /* Clear PDNn bit */ + ret = dacx3608_reg_update(dev, DACX3608_REG_DEVICE_CONFIG, + BIT(channel_cfg->channel_id), setting); + if (ret) { + LOG_ERR("Unable to update DEVICE_CONFIG register"); + return -EIO; + } + + data->configured |= BIT(channel_cfg->channel_id); + + LOG_DBG("Channel %d initialized", channel_cfg->channel_id); + + return 0; +} + +static int dacx3608_write_value(const struct device *dev, uint8_t channel, + uint32_t value) +{ + const struct dacx3608_config *config = dev->config; + struct dacx3608_data *data = dev->data; + uint16_t regval; + int ret; + + if (channel > DACX3608_MAX_CHANNEL - 1) { + LOG_ERR("Unsupported channel %d", channel); + return -ENOTSUP; + } + + if (!(data->configured & BIT(channel))) { + LOG_ERR("Channel %d not initialized", channel); + return -EINVAL; + } + + if (value >= (1 << (config->resolution))) { + LOG_ERR("Value %d out of range", value); + return -EINVAL; + } + + /* + * Shift passed value two times left because first two bits are Don't Care + * + * DACn_DATA register format: + * + * | 15 14 13 12 | 11 10 9 8 7 6 5 4 3 2 | 1 0 | + * |-------------|---------------------------------|------------| + * | Don't Care | DAC53608[9:0] / DAC43608[7:0] | Don't Care | + */ + regval = value << 2; + regval &= 0xFFFF; + + ret = dacx3608_reg_write(dev, DACX3608_REG_DACA_DATA + channel, regval); + if (ret) { + LOG_ERR("Unable to set value %d on channel %d", value, channel); + return -EIO; + } + + return 0; +} + +static int dacx3608_soft_reset(const struct device *dev) +{ + uint16_t regval = DACX3608_SW_RST; + int ret; + + ret = dacx3608_reg_write(dev, DACX3608_REG_STATUS_TRIGGER, regval); + if (ret) { + return -EIO; + } + k_msleep(DACX3608_POR_DELAY); + + return 0; +} + +static int dacx3608_device_id_check(const struct device *dev) +{ + uint16_t dev_id; + int ret; + + ret = dacx3608_reg_read(dev, DACX3608_REG_STATUS_TRIGGER, dev_id); + if (ret) { + LOG_ERR("Unable to read device ID"); + return -EIO; + } + + switch (dev_id) { + case DAC43608_DEVICE_ID: + case DAC53608_DEVICE_ID: + LOG_DBG("Device ID %#4x", dev_id); + break; + default: + LOG_ERR("Unknown Device ID %#4x", dev_id); + return -EIO; + } + + return 0; +} + +static int dacx3608_init(const struct device *dev) +{ + const struct dacx3608_config *config = dev->config; + struct dacx3608_data *data = dev->data; + int ret; + + data->i2c = device_get_binding(config->i2c_bus); + if (!data->i2c) { + LOG_ERR("Could not find I2C device"); + return -EINVAL; + } + + ret = dacx3608_soft_reset(dev); + if (ret) { + LOG_ERR("Soft-reset failed"); + return ret; + } + + ret = dacx3608_device_id_check(dev); + if (ret) { + return ret; + } + + data->configured = 0; + + LOG_DBG("Init complete"); + + return 0; +} + +static const struct dac_driver_api dacx3608_driver_api = { + .channel_setup = dacx3608_channel_setup, + .write_value = dacx3608_write_value, +}; + +#define INST_DT_DACX3608(inst, t) DT_INST(inst, ti_dac##t) + +#define DACX3608_DEVICE(t, n, res) \ + static struct dacx3608_data dac##t##_data_##n; \ + static const struct dacx3608_config dac##t##_config_##n = { \ + .i2c_bus = DT_BUS_LABEL(INST_DT_DACX3608(n, t)), \ + .i2c_addr = DT_REG_ADDR(INST_DT_DACX3608(n, t)), \ + .resolution = res, \ + }; \ + DEVICE_DT_DEFINE(INST_DT_DACX3608(n, t), \ + &dacx3608_init, device_pm_control_nop, \ + &dac##t##_data_##n, \ + &dac##t##_config_##n, POST_KERNEL, \ + CONFIG_DAC_DACX3608_INIT_PRIORITY, \ + &dacx3608_driver_api) + +/* + * DAC43608: 8-bit + */ +#define DAC43608_DEVICE(n) DACX3608_DEVICE(43608, n, 8) + +/* + * DAC53608: 10-bit + */ +#define DAC53608_DEVICE(n) DACX3608_DEVICE(53608, n, 10) + +#define CALL_WITH_ARG(arg, expr) expr(arg) + +#define INST_DT_DACX3608_FOREACH(t, inst_expr) \ + UTIL_LISTIFY(DT_NUM_INST_STATUS_OKAY(ti_dac##t), \ + CALL_WITH_ARG, inst_expr) + +INST_DT_DACX3608_FOREACH(43608, DAC43608_DEVICE); +INST_DT_DACX3608_FOREACH(53608, DAC53608_DEVICE); diff --git a/dts/bindings/dac/ti,dac43608.yaml b/dts/bindings/dac/ti,dac43608.yaml new file mode 100644 index 00000000000..37f362c095d --- /dev/null +++ b/dts/bindings/dac/ti,dac43608.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2020 Matija Tudan +# SPDX-License-Identifier: Apache-2.0 + +description: TI DAC43608 8-bit 8 channel DAC + +compatible: "ti,dac43608" + +include: ti,dacx3608-base.yaml diff --git a/dts/bindings/dac/ti,dac53608.yaml b/dts/bindings/dac/ti,dac53608.yaml new file mode 100644 index 00000000000..6ddc2621818 --- /dev/null +++ b/dts/bindings/dac/ti,dac53608.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2020 Matija Tudan +# SPDX-License-Identifier: Apache-2.0 + +description: TI DAC53608 10-bit 8 channel DAC + +compatible: "ti,dac53608" + +include: ti,dacx3608-base.yaml diff --git a/dts/bindings/dac/ti,dacx3608-base.yaml b/dts/bindings/dac/ti,dacx3608-base.yaml new file mode 100644 index 00000000000..660b75d94ce --- /dev/null +++ b/dts/bindings/dac/ti,dacx3608-base.yaml @@ -0,0 +1,11 @@ +# Copyright (c) 2020 Matija Tudan +# SPDX-License-Identifier: Apache-2.0 + +include: [dac-controller.yaml, i2c-device.yaml] + +properties: + "#io-channel-cells": + const: 1 + +io-channel-cells: + - output