43b38b348b
Since this DAC is connected via SPI bus the init priority value must be higher than the default 50 so it can be initialized later than the bus itself so add a dedicated init config symbol for that. Signed-off-by: Bartosz Bilas <b.bilas@grinn-global.com>
415 lines
9 KiB
C
415 lines
9 KiB
C
/*
|
|
* Copyright (c) 2020 M2I Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/zephyr.h>
|
|
#include <zephyr/drivers/spi.h>
|
|
#include <zephyr/drivers/dac.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/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 {
|
|
struct spi_dt_spec bus;
|
|
uint8_t resolution;
|
|
uint8_t reference;
|
|
uint8_t gain[8];
|
|
};
|
|
|
|
struct dacx0508_data {
|
|
uint8_t configured;
|
|
};
|
|
|
|
static int dacx0508_reg_read(const struct device *dev, uint8_t addr,
|
|
uint8_t *data)
|
|
{
|
|
const struct dacx0508_config *config = dev->config;
|
|
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_dt(&config->bus, &tx);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = spi_read_dt(&config->bus, &rx);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
if (addr != tmp) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dacx0508_reg_write(const struct device *dev, uint8_t addr,
|
|
uint8_t *data)
|
|
{
|
|
const struct dacx0508_config *config = dev->config;
|
|
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_dt(&config->bus, &tx);
|
|
}
|
|
|
|
int dacx0508_reg_update(const 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(const 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(const 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(const 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(const 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(const 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(const struct device *dev)
|
|
{
|
|
const struct dacx0508_config *config = dev->config;
|
|
struct dacx0508_data *data = dev->data;
|
|
int ret;
|
|
|
|
if (!spi_is_ready(&config->bus)) {
|
|
LOG_ERR("SPI bus %s not ready", config->bus.bus->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
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 = { \
|
|
.bus = SPI_DT_SPEC_GET(INST_DT_DACX0508(n, t), \
|
|
SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | \
|
|
SPI_WORD_SET(8) | SPI_MODE_CPHA, 0), \
|
|
.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_DT_DEFINE(INST_DT_DACX0508(n, t), \
|
|
&dacx0508_init, NULL, \
|
|
&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) \
|
|
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);
|