Commit 2aeed812 authored by Kwon Tae-young's avatar Kwon Tae-young Committed by Carles Cufi
Browse files

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: default avatarKwon Tae-young <tykwon@m2i.co.kr>
parent 94721b31
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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)
+2 −0
Original line number Diff line number Diff line
@@ -30,4 +30,6 @@ source "drivers/dac/Kconfig.stm32"

source "drivers/dac/Kconfig.sam0"

source "drivers/dac/Kconfig.dacx0508"

endif # DAC
+21 −0
Original line number Diff line number Diff line
# 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
+455 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2020 M2I Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <drivers/spi.h>
#include <drivers/dac.h>
#include <logging/log.h>
#include <dt-bindings/dac/dacx0508.h>

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);
+8 −0
Original line number Diff line number Diff line
# 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
Loading