Browse Source

drivers: dac: Add TI DAC161S997 driver

Initial DAC driver for TI DAC161S997. This is a 1 channel 16 bit
DAC designed for 4-20 mA loops.

Signed-off-by: Jeppe Odgaard <jeppe.odgaard@prevas.dk>
pull/88042/head
Jeppe Odgaard 5 months ago committed by Benjamin Cabé
parent
commit
0dffe7cc7e
  1. 1
      drivers/dac/CMakeLists.txt
  2. 2
      drivers/dac/Kconfig
  3. 11
      drivers/dac/Kconfig.dac161s997
  4. 309
      drivers/dac/dac_dac161s997.c
  5. 14
      dts/bindings/dac/ti,dac161s997.yaml
  6. 77
      include/zephyr/drivers/dac/dac161s997.h
  7. 8
      tests/drivers/build_all/dac/app.overlay

1
drivers/dac/CMakeLists.txt

@ -10,6 +10,7 @@ zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_DAC32 dac_mcux_dac32.c) @@ -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_DAC161S997 dac_dac161s997.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)

2
drivers/dac/Kconfig

@ -37,6 +37,8 @@ source "drivers/dac/Kconfig.sam" @@ -37,6 +37,8 @@ source "drivers/dac/Kconfig.sam"
source "drivers/dac/Kconfig.sam0"
source "drivers/dac/Kconfig.dac161s997"
source "drivers/dac/Kconfig.dacx0501"
source "drivers/dac/Kconfig.dacx0508"

11
drivers/dac/Kconfig.dac161s997

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
# Copyright (c) 2025 Vitrolife A/S
#
# SPDX-License-Identifier: Apache-2.0
config DAC_DAC161S997
bool "TI DAC161S997 DAC driver"
default y
select SPI
depends on DT_HAS_TI_DAC161S997_ENABLED
help
Enable the driver for the TI DAC161S997.

309
drivers/dac/dac_dac161s997.c

@ -0,0 +1,309 @@ @@ -0,0 +1,309 @@
/*
* Copyright (c) 2025 Vitrolife A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ti_dac161s997
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/dac.h>
#include <zephyr/drivers/dac/dac161s997.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util_macro.h>
LOG_MODULE_REGISTER(dac_dac161s997, CONFIG_DAC_LOG_LEVEL);
#define DAC161S997_CHANNELS 1
#define DAC161S997_RESOLUTION 16
enum dac161s997_reg {
DAC161S997_REG_XFER = 1,
DAC161S997_REG_NOP,
DAC161S997_REG_WR_MODE,
DAC161S997_REG_DACCODE,
DAC161S997_REG_ERR_CONFIG,
DAC161S997_REG_ERR_LOW,
DAC161S997_REG_ERR_HIGH,
DAC161S997_REG_RESET,
DAC161S997_REG_STATUS,
};
struct dac161s997_config {
struct spi_dt_spec bus;
const struct gpio_dt_spec gpio_errb;
};
struct dac161s997_data {
const struct device *dev;
struct k_sem lock;
struct gpio_callback gpio_errb_cb;
struct k_work gpio_errb_work;
dac161s997_error_callback_t error_cb;
};
int dac161s997_set_error_callback(const struct device *dev, dac161s997_error_callback_t cb)
{
const struct dac161s997_config *config = dev->config;
struct dac161s997_data *data = dev->data;
int ret;
ret = k_sem_take(&data->lock, K_FOREVER);
if (ret != 0) {
return ret;
}
if (config->gpio_errb.port != NULL) {
data->error_cb = cb;
} else {
ret = -ENOTSUP;
}
k_sem_give(&data->lock);
return ret;
}
static int dac161s997_read_reg(const struct device *dev, enum dac161s997_reg reg, uint16_t *val)
{
const struct dac161s997_config *config = dev->config;
int res;
uint8_t reg_read = BIT(7) | reg;
uint8_t tx_buf[3] = {reg_read};
const struct spi_buf tx_buffers[] = {{.buf = tx_buf, .len = ARRAY_SIZE(tx_buf)}};
const struct spi_buf_set tx_bufs = {.buffers = tx_buffers, .count = ARRAY_SIZE(tx_buffers)};
uint8_t rx_buf[3];
const struct spi_buf rx_buffers[] = {{.buf = rx_buf, .len = ARRAY_SIZE(rx_buf)}};
const struct spi_buf_set rx_bufs = {.buffers = rx_buffers, .count = ARRAY_SIZE(rx_buffers)};
res = spi_write_dt(&config->bus, &tx_bufs);
if (res != 0) {
LOG_ERR("Read 0x%02x setup failed: %d", reg, res);
return res;
}
tx_buf[0] = DAC161S997_REG_NOP;
res = spi_transceive_dt(&config->bus, &tx_bufs, &rx_bufs);
if (res != 0) {
LOG_ERR("Read from 0x%02x failed: %d", reg, res);
return res;
}
if (reg_read != rx_buf[0]) {
LOG_ERR("Read 0x%02x addr mismatch: 0x%02x", reg_read, rx_buf[0]);
return -EIO;
}
*val = sys_get_be16(&rx_buf[1]);
LOG_DBG("Reg 0x%02x: 0x%02x", reg, *val);
return 0;
}
static int dac161s997_write_reg(const struct device *dev, enum dac161s997_reg reg, uint16_t val)
{
const struct dac161s997_config *config = dev->config;
uint8_t tx_buf[3] = {reg, val >> 8, val};
const struct spi_buf tx_buffers[] = {{.buf = tx_buf, .len = ARRAY_SIZE(tx_buf)}};
const struct spi_buf_set tx_bufs = {.buffers = tx_buffers, .count = ARRAY_SIZE(tx_buffers)};
int ret = spi_write_dt(&config->bus, &tx_bufs);
if (ret != 0) {
LOG_ERR("Write to reg 0x%02x failed: %i", reg, ret);
return ret;
}
return 0;
}
static int dac161s997_channel_setup(const struct device *dev,
const struct dac_channel_cfg *channel_cfg)
{
if (channel_cfg->channel_id >= DAC161S997_CHANNELS) {
LOG_ERR("Channel %d is not valid", channel_cfg->channel_id);
return -EINVAL;
}
if (channel_cfg->resolution != DAC161S997_RESOLUTION) {
LOG_ERR("Only %d bit resolution is supported", DAC161S997_RESOLUTION);
return -ENOTSUP;
}
if (channel_cfg->internal) {
LOG_ERR("Internal channels not supported");
return -ENOTSUP;
}
return 0;
}
static int dac161s997_write_value(const struct device *dev, uint8_t channel, uint32_t value)
{
struct dac161s997_data *data = dev->data;
int ret;
if (channel >= DAC161S997_CHANNELS) {
LOG_ERR("Channel %d is not valid", channel);
return -EINVAL;
}
if (value > BIT(DAC161S997_RESOLUTION) - 1) {
LOG_ERR("Value %d out of range", value);
return -EINVAL;
}
ret = k_sem_take(&data->lock, K_FOREVER);
if (ret != 0) {
LOG_WRN("Write value lock failed: %d", ret);
return ret;
}
ret = dac161s997_write_reg(dev, DAC161S997_REG_DACCODE, value);
k_sem_give(&data->lock);
return ret;
}
static int dac161s997_read_status(const struct device *dev, union dac161s997_status *status)
{
int ret;
uint16_t tmp;
ret = dac161s997_read_reg(dev, DAC161S997_REG_STATUS, &tmp);
if (ret == 0) {
status->raw = tmp;
}
return ret;
}
static void dac161s997_gpio_errb_work_handler(struct k_work *work)
{
struct dac161s997_data *data = CONTAINER_OF(work, struct dac161s997_data, gpio_errb_work);
const struct device *dev = data->dev;
union dac161s997_status status;
int ret;
ret = k_sem_take(&data->lock, K_FOREVER);
if (ret != 0) {
LOG_WRN("ERRB handler take lock failed: %d", ret);
return;
}
ret = dac161s997_read_status(dev, &status);
if (data->error_cb != NULL) {
data->error_cb(dev, ret == 0 ? &status : NULL);
}
k_sem_give(&data->lock);
}
static void dac161s997_gpio_errb_cb(const struct device *dev, struct gpio_callback *cb,
gpio_port_pins_t pins)
{
struct dac161s997_data *data = CONTAINER_OF(cb, struct dac161s997_data, gpio_errb_cb);
int ret;
ret = k_work_submit(&data->gpio_errb_work);
if (ret != 1) {
LOG_WRN("ERRB work not queued: %d", ret);
}
}
static int dac161s997_init(const struct device *dev)
{
const struct dac161s997_config *config = dev->config;
struct dac161s997_data *data = dev->data;
union dac161s997_status status;
int ret;
data->dev = dev;
if (!spi_is_ready_dt(&config->bus)) {
LOG_ERR("SPI bus %s not ready", config->bus.bus->name);
return -ENODEV;
}
k_sem_init(&data->lock, 1, 1);
ret = dac161s997_write_reg(dev, DAC161S997_REG_RESET, 0xc33c);
if (ret != 0) {
return ret;
}
ret = dac161s997_write_reg(dev, DAC161S997_REG_NOP, 0);
if (ret != 0) {
return ret;
}
/* Read status to clear any sticky error caused during boot or reboot */
ret = dac161s997_read_status(dev, &status);
if (ret != 0) {
return ret;
}
/* Check that DAC_RES bits are all set */
if (status.dac_resolution != 0x7) {
LOG_ERR("Unexpected DAC resolution value: 0x%02x", status.dac_resolution);
return ret;
}
if (config->gpio_errb.port != NULL) {
if (!gpio_is_ready_dt(&config->gpio_errb)) {
LOG_ERR("ERRB GPIO is not ready");
return -ENODEV;
}
k_work_init(&data->gpio_errb_work, dac161s997_gpio_errb_work_handler);
gpio_init_callback(&data->gpio_errb_cb, dac161s997_gpio_errb_cb,
BIT(config->gpio_errb.pin));
ret = gpio_pin_configure_dt(&config->gpio_errb, GPIO_INPUT);
if (ret != 0) {
LOG_ERR("Configure ERRB GPIO failed: %d", ret);
return ret;
}
ret = gpio_pin_interrupt_configure_dt(&config->gpio_errb, GPIO_INT_EDGE_TO_ACTIVE);
if (ret) {
LOG_ERR("Configure ERRB interrupt failed: %d", ret);
return ret;
}
ret = gpio_add_callback_dt(&config->gpio_errb, &data->gpio_errb_cb);
if (ret != 0) {
LOG_ERR("Configure ERRB callback failed: %d", ret);
return ret;
}
}
return 0;
}
static DEVICE_API(dac, dac161s997_driver_api) = {
.channel_setup = dac161s997_channel_setup,
.write_value = dac161s997_write_value,
};
#define DAC_DAC161S997_INIT(n) \
static const struct dac161s997_config dac161s997_config_##n = { \
.bus = SPI_DT_SPEC_INST_GET(n, SPI_TRANSFER_MSB | SPI_WORD_SET(8), 0), \
.gpio_errb = GPIO_DT_SPEC_INST_GET_OR(n, errb_gpios, {0}), \
}; \
\
static struct dac161s997_data dac161s997_data_##n; \
\
DEVICE_DT_INST_DEFINE(n, dac161s997_init, NULL, &dac161s997_data_##n, \
&dac161s997_config_##n, POST_KERNEL, CONFIG_DAC_INIT_PRIORITY, \
&dac161s997_driver_api);
DT_INST_FOREACH_STATUS_OKAY(DAC_DAC161S997_INIT);

14
dts/bindings/dac/ti,dac161s997.yaml

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
# Copyright (C) 2025 Vitrolife A/S
# SPDX-License-Identifier: Apache-2.0
description: TI DAC161S997 16-bit 1 channel SPI DAC for 4-20 mA loops
compatible: "ti,dac161s997"
include: [dac-controller.yaml, spi-device.yaml]
properties:
errb-gpios:
type: phandle-array
description: |
DAC error signal output. If set a callback can be set to read what caused the error.

77
include/zephyr/drivers/dac/dac161s997.h

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
/*
* Copyright 2025 Vitrolife A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_DAC_DAC161S997_H_
#define ZEPHYR_INCLUDE_DRIVERS_DAC_DAC161S997_H_
#include <stdint.h>
#include <zephyr/device.h>
#ifdef __cplusplus
extern "C" {
#endif
union dac161s997_status {
uint8_t raw;
struct {
/**
* True if the DAC161S997 is unable to maintain the output current.
*/
bool current_loop_status: 1;
/**
* Identical to current_loop_status except this bit is sticky.
*/
bool loop_status: 1;
/**
* True if a SPI command has not been received within SPI timeout period (default
* 100 ms). If this error occurs, it is cleared with a properly formatted write
* command to a valid address.
*/
bool spi_timeout_error: 1;
/**
* A frame error is caused by an incorrect number of clocks during a register write.
* A register write without an integer multiple of 24 clock cycles will cause a
* Frame error.
*/
bool frame_status: 1;
/**
* Returns the state of the ERR_LVL pin.
*/
bool error_level_pin_state: 1;
/**
* DAC resolution register. Always returns 0x7.
*/
uint8_t dac_resolution: 3;
} __packed;
};
/**
* @typedef dac161s997_error_callback_t
* @brief Callback to invoke when an error is triggered
*
* @param dev Pointer to the device
* @param status NULL if read was not possible otherwise pointer to status.
*/
typedef void (*dac161s997_error_callback_t)(const struct device *dev,
const union dac161s997_status *status);
/**
* @brief Set callback to invoke when an error is triggered
*
* The callback runs in a work queue context and the device is locked while it runs.
*
* @param dev Pointer to the device
* @param cb Callback to invoke when an error is triggered
*
* @returns 0 on success and -errno otherwise.
*/
int dac161s997_set_error_callback(const struct device *dev, dac161s997_error_callback_t cb);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_DRIVERS_DAC_DAC161S997_H_ */

8
tests/drivers/build_all/dac/app.overlay

@ -302,6 +302,14 @@ @@ -302,6 +302,14 @@
status = "okay";
};
};
test_spi_dac161s997: dac161s997@12 {
compatible = "ti,dac161s997";
reg = <0x12>;
spi-max-frequency = <0>;
#io-channel-cells = <1>;
errb-gpios = <&test_gpio 0 0>;
};
};
};
};

Loading…
Cancel
Save