From 2aeed81202934ccf5a683af4ba49fe05edf15885 Mon Sep 17 00:00:00 2001 From: Kwon Tae-young Date: Thu, 4 Jun 2020 12:24:40 +0900 Subject: [PATCH] drivers: dac: Added driver for TI DACx0508 TI's DACx0508 is a DAC chip that supports SPI. Gain and Reference can be set through the register. Signed-off-by: Kwon Tae-young --- drivers/dac/CMakeLists.txt | 1 + drivers/dac/Kconfig | 2 + drivers/dac/Kconfig.dacx0508 | 21 ++ drivers/dac/dac_dacx0508.c | 455 +++++++++++++++++++++++++ dts/bindings/dac/ti,dac60508.yaml | 8 + dts/bindings/dac/ti,dac70508.yaml | 8 + dts/bindings/dac/ti,dac80508.yaml | 8 + dts/bindings/dac/ti,dacx0508-base.yaml | 83 +++++ include/dt-bindings/dac/dacx0508.h | 17 + 9 files changed, 603 insertions(+) create mode 100644 drivers/dac/Kconfig.dacx0508 create mode 100644 drivers/dac/dac_dacx0508.c create mode 100644 dts/bindings/dac/ti,dac60508.yaml create mode 100644 dts/bindings/dac/ti,dac70508.yaml create mode 100644 dts/bindings/dac/ti,dac80508.yaml create mode 100644 dts/bindings/dac/ti,dacx0508-base.yaml create mode 100644 include/dt-bindings/dac/dacx0508.h diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt index ed605f3bfe2..0b5cee37d6e 100644 --- a/drivers/dac/CMakeLists.txt +++ b/drivers/dac/CMakeLists.txt @@ -6,5 +6,6 @@ zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_DAC dac_mcux_dac.c) 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_SHELL dac_shell.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE dac_handlers.c) diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig index c3dfeb54eb8..f30b3b4032b 100644 --- a/drivers/dac/Kconfig +++ b/drivers/dac/Kconfig @@ -30,4 +30,6 @@ source "drivers/dac/Kconfig.stm32" source "drivers/dac/Kconfig.sam0" +source "drivers/dac/Kconfig.dacx0508" + endif # DAC diff --git a/drivers/dac/Kconfig.dacx0508 b/drivers/dac/Kconfig.dacx0508 new file mode 100644 index 00000000000..db09d4d55d0 --- /dev/null +++ b/drivers/dac/Kconfig.dacx0508 @@ -0,0 +1,21 @@ +# DAC configuration options + +# Copyright (c) 2020 M2I Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +config DAC_DACX0508 + bool "TI DACx0508 DAC driver" + depends on SPI + help + Enable the driver for the TI DACx0508. + +if DAC_DACX0508 + +config DAC_DACX0508_INIT_PRIORITY + int "Init priority" + default 80 + help + DACx0508 DAC device driver initialization priority. + +endif # DAC_DACX0508 diff --git a/drivers/dac/dac_dacx0508.c b/drivers/dac/dac_dacx0508.c new file mode 100644 index 00000000000..66957b7168e --- /dev/null +++ b/drivers/dac/dac_dacx0508.c @@ -0,0 +1,455 @@ +/* + * Copyright (c) 2020 M2I Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(dac_dacx0508, CONFIG_DAC_LOG_LEVEL); + +#define DACX0508_REG_DEVICE_ID 0x01U +#define DACX0508_REG_CONFIG 0x03U +#define DACX0508_REG_GAIN 0x04U +#define DACX0508_REG_TRIGGER 0x05U +#define DACX0508_REG_STATUS 0x07U +#define DACX0508_REG_DAC0 0x08U + +#define DACX0508_MASK_DEVICE_ID_8CH BIT(11) +#define DACX0508_MASK_CONFIG_REF_PWDWN BIT(8) +#define DACX0508_MASK_GAIN_BUFF_GAIN(x) BIT(x) +#define DACX0508_MASK_GAIN_REFDIV_EN BIT(8) +#define DACX0508_MASK_TRIGGER_SOFT_RESET (BIT(1) | BIT(3)) +#define DACX0508_MASK_STATUS_REF_ALM BIT(0) + +#define DACX0508_READ_CMD 0x80 +#define DACX0508_POR_DELAY 250 +#define DACX0508_MAX_CHANNEL 8 + +struct dacx0508_config { + const char *spi_dev_name; + const char *spi_cs_dev_name; + gpio_pin_t spi_cs_pin; + gpio_dt_flags_t spi_cs_dt_flags; + struct spi_config spi_cfg; + uint8_t resolution; + uint8_t reference; + uint8_t gain[8]; +}; + +struct dacx0508_data { + struct device *spi_dev; + struct spi_cs_control spi_cs; + struct spi_config spi_cfg; + uint8_t configured; +}; + +static int dacx0508_reg_read(struct device *dev, uint8_t addr, uint8_t *data) +{ + struct dacx0508_data *dev_data = dev->data; + const struct spi_buf buf[2] = { + { + .buf = &addr, + .len = sizeof(addr) + }, + { + .buf = data, + .len = 2 + } + }; + struct spi_buf_set tx = { + .buffers = buf, + .count = ARRAY_SIZE(buf), + }; + struct spi_buf_set rx = { + .buffers = buf, + .count = ARRAY_SIZE(buf) + }; + uint8_t tmp; + int ret; + + if (k_is_in_isr()) { + /* Prevent SPI transactions from an ISR */ + return -EWOULDBLOCK; + } + + tmp = addr |= DACX0508_READ_CMD; + + ret = spi_write(dev_data->spi_dev, &dev_data->spi_cfg, &tx); + if (ret) { + return ret; + } + + ret = spi_read(dev_data->spi_dev, &dev_data->spi_cfg, &rx); + if (ret) { + return ret; + } + + if (addr != tmp) { + return -EIO; + } + + return 0; +} + +static int dacx0508_reg_write(struct device *dev, uint8_t addr, uint8_t *data) +{ + struct dacx0508_data *dev_data = dev->data; + const struct spi_buf buf[2] = { + { + .buf = &addr, + .len = sizeof(addr) + }, + { + .buf = data, + .len = 2 + } + }; + struct spi_buf_set tx = { + .buffers = buf, + .count = ARRAY_SIZE(buf), + }; + + if (k_is_in_isr()) { + /* Prevent SPI transactions from an ISR */ + return -EWOULDBLOCK; + } + + return spi_write(dev_data->spi_dev, &dev_data->spi_cfg, &tx); +} + +int dacx0508_reg_update(struct device *dev, uint8_t addr, + uint16_t mask, bool setting) +{ + uint8_t regval[2] = {0, }; + uint16_t tmp; + int ret; + + ret = dacx0508_reg_read(dev, addr, regval); + if (ret < 0) { + return ret; + } + tmp = (regval[0] << 8) | regval[1]; + + if (setting) { + tmp |= mask; + } else { + tmp &= ~mask; + } + + regval[0] = tmp >> 8; + regval[1] = tmp & 0xFF; + + ret = dacx0508_reg_write(dev, addr, regval); + if (ret) { + return ret; + } + + return 0; +} + +static int dacx0508_channel_setup(struct device *dev, + const struct dac_channel_cfg *channel_cfg) +{ + const struct dacx0508_config *config = dev->config; + struct dacx0508_data *data = dev->data; + + if (channel_cfg->channel_id > DACX0508_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; + } + + data->configured |= BIT(channel_cfg->channel_id); + + return 0; +} + +static int dacx0508_write_value(struct device *dev, uint8_t channel, + uint32_t value) +{ + const struct dacx0508_config *config = dev->config; + struct dacx0508_data *data = dev->data; + uint8_t regval[2]; + int ret; + + if (channel > DACX0508_MAX_CHANNEL - 1) { + LOG_ERR("unsupported channel %d", channel); + return -ENOTSUP; + } + + if (!(data->configured & BIT(channel))) { + LOG_ERR("Channel not initialized"); + return -EINVAL; + } + + if (value >= (1 << config->resolution)) { + LOG_ERR("Value %d out of range", value); + return -EINVAL; + } + + value <<= (16 - config->resolution); + regval[0] = value >> 8; + regval[1] = value & 0xFF; + + ret = dacx0508_reg_write(dev, DACX0508_REG_DAC0 + channel, regval); + if (ret) { + return -EIO; + } + + return 0; +} + +static int dacx0508_soft_reset(struct device *dev) +{ + uint8_t regval[2] = {0, DACX0508_MASK_TRIGGER_SOFT_RESET}; + int ret; + + ret = dacx0508_reg_write(dev, DACX0508_REG_TRIGGER, regval); + if (ret) { + return -EIO; + } + k_usleep(DACX0508_POR_DELAY); + + return 0; +} + +static int dacx0508_device_id_check(struct device *dev) +{ + const struct dacx0508_config *config = dev->config; + uint8_t regval[2] = {0, }; + uint8_t resolution; + uint16_t dev_id; + int ret; + + ret = dacx0508_reg_read(dev, DACX0508_REG_DEVICE_ID, regval); + if (ret) { + LOG_ERR("Unable to read Device ID"); + return -EIO; + } + dev_id = (regval[0] << 8) | regval[1]; + + resolution = dev_id >> 12; + if (resolution != (16 - config->resolution) >> 1) { + LOG_ERR("Not match chip resolution"); + return -EINVAL; + } + + if ((dev_id & DACX0508_MASK_DEVICE_ID_8CH) != + DACX0508_MASK_DEVICE_ID_8CH) { + LOG_ERR("Support channels mismatch"); + return -EINVAL; + } + + return 0; +} + +static int dacx0508_setup(struct device *dev) +{ + const struct dacx0508_config *config = dev->config; + uint8_t regval[2] = {0, }, tmp = 0; + bool ref_pwdwn, refdiv_en; + int ret; + + switch (config->reference) { + case DACX0508_REF_INTERNAL_1: + ref_pwdwn = false; + refdiv_en = false; + break; + case DACX0508_REF_INTERNAL_1_2: + ref_pwdwn = false; + refdiv_en = true; + break; + case DACX0508_REF_EXTERNAL_1: + ref_pwdwn = true; + refdiv_en = false; + break; + case DACX0508_REF_EXTERNAL_1_2: + ref_pwdwn = true; + refdiv_en = true; + break; + default: + LOG_ERR("unsupported channel reference type '%d'", + config->reference); + return -ENOTSUP; + } + + ret = dacx0508_reg_update(dev, DACX0508_REG_CONFIG, + DACX0508_MASK_CONFIG_REF_PWDWN, ref_pwdwn); + if (ret) { + LOG_ERR("GAIN Register update failed"); + return -EIO; + } + + ret = dacx0508_reg_update(dev, DACX0508_REG_GAIN, + DACX0508_MASK_GAIN_REFDIV_EN, refdiv_en); + if (ret) { + LOG_ERR("GAIN Register update failed"); + return -EIO; + } + + + for (int i = 0; i < 8; i++) { + tmp |= config->gain[i] << i; + } + + ret = dacx0508_reg_read(dev, DACX0508_REG_GAIN, regval); + if (ret) { + LOG_ERR("Unable to read GAIN Register"); + return -EIO; + } + + regval[1] = tmp; + ret = dacx0508_reg_write(dev, DACX0508_REG_GAIN, regval); + if (ret) { + LOG_ERR("Unable to write GAIN Register"); + return -EIO; + } + + ret = dacx0508_reg_read(dev, DACX0508_REG_STATUS, regval); + if (ret) { + LOG_ERR("Unable to read STATUS Register"); + return -EIO; + } + if ((regval[1] & DACX0508_MASK_STATUS_REF_ALM) == + DACX0508_MASK_STATUS_REF_ALM) { + LOG_ERR("Difference between VREF/DIV and VDD is " + "below the required minimum analog threshold"); + return -EIO; + } + + return 0; +} + +static int dacx0508_init(struct device *dev) +{ + const struct dacx0508_config *config = dev->config; + struct dacx0508_data *data = dev->data; + int ret; + + data->spi_dev = device_get_binding(config->spi_dev_name); + if (!data->spi_dev) { + LOG_ERR("Cannot get pointer to %s device", + config->spi_dev_name); + return -EINVAL; + } + + if (config->spi_cs_dev_name) { + data->spi_cs.gpio_dev = + device_get_binding(config->spi_cs_dev_name); + if (!data->spi_cs.gpio_dev) { + LOG_ERR("Cannot get pointer to %s device", + config->spi_cs_dev_name); + return -EINVAL; + } + data->spi_cs.gpio_pin = config->spi_cs_pin; + data->spi_cs.gpio_dt_flags = config->spi_cs_dt_flags; + data->spi_cfg = config->spi_cfg; + data->spi_cfg.cs = &data->spi_cs; + } + + ret = dacx0508_soft_reset(dev); + if (ret) { + LOG_ERR("Soft-reset failed"); + return ret; + } + + ret = dacx0508_device_id_check(dev); + if (ret) { + return ret; + } + + ret = dacx0508_setup(dev); + if (ret) { + return ret; + } + + data->configured = 0; + + return 0; +} + +static const struct dac_driver_api dacx0508_driver_api = { + .channel_setup = dacx0508_channel_setup, + .write_value = dacx0508_write_value, +}; + +#define INST_DT_DACX0508(inst, t) DT_INST(inst, ti_dac##t) + +#define DACX0508_DEVICE(t, n, res) \ + static struct dacx0508_data dac##t##_data_##n; \ + static const struct dacx0508_config dac##t##_config_##n = { \ + .spi_dev_name = DT_BUS_LABEL(INST_DT_DACX0508(n, t)), \ + .spi_cs_dev_name = \ + UTIL_AND( \ + DT_SPI_DEV_HAS_CS_GPIOS(INST_DT_DACX0508(n, t)), \ + DT_SPI_DEV_CS_GPIOS_LABEL(INST_DT_DACX0508(n, t)) \ + ), \ + .spi_cs_pin = \ + UTIL_AND( \ + DT_SPI_DEV_HAS_CS_GPIOS(INST_DT_DACX0508(n, t)), \ + DT_SPI_DEV_CS_GPIOS_PIN(INST_DT_DACX0508(n, t)) \ + ), \ + .spi_cs_dt_flags = UTIL_AND( \ + DT_SPI_DEV_HAS_CS_GPIOS(INST_DT_DACX0508(n, t)), \ + DT_SPI_DEV_CS_GPIOS_FLAGS(INST_DT_DACX0508(n, t)) \ + ), \ + .spi_cfg = { \ + .operation = (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | \ + SPI_WORD_SET(8) | SPI_MODE_CPHA), \ + .frequency = DT_PROP(INST_DT_DACX0508(n, t), \ + spi_max_frequency), \ + .slave = DT_REG_ADDR(INST_DT_DACX0508(n, t)), \ + .cs = &dac##t##_data_##n.spi_cs, \ + }, \ + .resolution = res, \ + .reference = DT_PROP(INST_DT_DACX0508(n, t), \ + voltage_reference), \ + .gain[0] = DT_PROP(INST_DT_DACX0508(n, t), channel0_gain), \ + .gain[1] = DT_PROP(INST_DT_DACX0508(n, t), channel1_gain), \ + .gain[2] = DT_PROP(INST_DT_DACX0508(n, t), channel2_gain), \ + .gain[3] = DT_PROP(INST_DT_DACX0508(n, t), channel3_gain), \ + .gain[4] = DT_PROP(INST_DT_DACX0508(n, t), channel4_gain), \ + .gain[5] = DT_PROP(INST_DT_DACX0508(n, t), channel5_gain), \ + .gain[6] = DT_PROP(INST_DT_DACX0508(n, t), channel6_gain), \ + .gain[7] = DT_PROP(INST_DT_DACX0508(n, t), channel7_gain), \ + }; \ + DEVICE_AND_API_INIT(dac##t##_##n, \ + DT_LABEL(INST_DT_DACX0508(n, t)), \ + &dacx0508_init, &dac##t##_data_##n, \ + &dac##t##_config_##n, POST_KERNEL, \ + CONFIG_DAC_DACX0508_INIT_PRIORITY, \ + &dacx0508_driver_api) + +/* + * DAC60508: 12-bit + */ +#define DAC60508_DEVICE(n) DACX0508_DEVICE(60508, n, 12) + +/* + * DAC70508: 14-bit + */ +#define DAC70508_DEVICE(n) DACX0508_DEVICE(70508, n, 14) + +/* + * DAC80508: 16-bit + */ +#define DAC80508_DEVICE(n) DACX0508_DEVICE(80508, n, 16) + +#define CALL_WITH_ARG(arg, expr) expr(arg) + +#define INST_DT_DACX0508_FOREACH(t, inst_expr) \ + UTIL_LISTIFY(DT_NUM_INST_STATUS_OKAY(ti_dac##t), \ + CALL_WITH_ARG, inst_expr) + +INST_DT_DACX0508_FOREACH(60508, DAC60508_DEVICE); +INST_DT_DACX0508_FOREACH(70508, DAC70508_DEVICE); +INST_DT_DACX0508_FOREACH(80508, DAC80508_DEVICE); diff --git a/dts/bindings/dac/ti,dac60508.yaml b/dts/bindings/dac/ti,dac60508.yaml new file mode 100644 index 00000000000..469cbaa8f52 --- /dev/null +++ b/dts/bindings/dac/ti,dac60508.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2020 M2I Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: TI DAC60508 12-bit DAC + +compatible: "ti,dac60508" + +include: ti,dacx0508-base.yaml diff --git a/dts/bindings/dac/ti,dac70508.yaml b/dts/bindings/dac/ti,dac70508.yaml new file mode 100644 index 00000000000..e7a5c258a64 --- /dev/null +++ b/dts/bindings/dac/ti,dac70508.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2020 M2I Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: TI DAC70508 14-bit DAC + +compatible: "ti,dac70508" + +include: ti,dacx0508-base.yaml diff --git a/dts/bindings/dac/ti,dac80508.yaml b/dts/bindings/dac/ti,dac80508.yaml new file mode 100644 index 00000000000..1b79996a132 --- /dev/null +++ b/dts/bindings/dac/ti,dac80508.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2020 M2I Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: TI DAC80508 16-bit DAC + +compatible: "ti,dac80508" + +include: ti,dacx0508-base.yaml diff --git a/dts/bindings/dac/ti,dacx0508-base.yaml b/dts/bindings/dac/ti,dacx0508-base.yaml new file mode 100644 index 00000000000..6899dc90e0e --- /dev/null +++ b/dts/bindings/dac/ti,dacx0508-base.yaml @@ -0,0 +1,83 @@ +# Copyright (c) 2020 M2I Corporation +# SPDX-License-Identifier: Apache-2.0 + +include: [dac-controller.yaml, spi-device.yaml] + +properties: + voltage-reference: + type: int + required: true + description: | + DAC voltage reference select + + See constants in dt-bindings/dac/dacx0508.h. + + channel0-gain: + type: int + required: true + description: | + Channel 0 gain select + + See constants in dt-bindings/dac/dacx0508.h. + + channel1-gain: + type: int + required: true + description: | + Channel 1 gain select + + See constants in dt-bindings/dac/dacx0508.h. + + channel2-gain: + type: int + required: true + description: | + Channel 2 gain select + + See constants in dt-bindings/dac/dacx0508.h. + + channel3-gain: + type: int + required: true + description: | + Channel 3 gain select + + See constants in dt-bindings/dac/dacx0508.h. + + channel4-gain: + type: int + required: true + description: | + Channel 4 gain select + + See constants in dt-bindings/dac/dacx0508.h. + + channel5-gain: + type: int + required: true + description: | + Channel 5 gain select + + See constants in dt-bindings/dac/dacx0508.h. + + channel6-gain: + type: int + required: true + description: | + Channel 6 gain select + + See constants in dt-bindings/dac/dacx0508.h. + + channel7-gain: + type: int + required: true + description: | + Channel 7 gain select + + See constants in dt-bindings/dac/dacx0508.h. + + "#io-channel-cells": + const: 1 + +io-channel-cells: + - output diff --git a/include/dt-bindings/dac/dacx0508.h b/include/dt-bindings/dac/dacx0508.h new file mode 100644 index 00000000000..15ce7cb1447 --- /dev/null +++ b/include/dt-bindings/dac/dacx0508.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2020 M2I Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_DAC_DACX0508_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_DAC_DACX0508_H_ + +#define DACX0508_REF_INTERNAL_1 0x00 +#define DACX0508_REF_INTERNAL_1_2 0x01 +#define DACX0508_REF_EXTERNAL_1 0x02 +#define DACX0508_REF_EXTERNAL_1_2 0x03 + +#define DACX0508_CHANNEL_GAIN_1 0x00 +#define DACX0508_CHANNEL_GAIN_2 0x01 + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_DAC_DACX0508_H_ */