|  | /* | 
|  | * Copyright (c) 2023 SILA Embedded Solutions GmbH | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <stdint.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/drivers/gpio.h> | 
|  | #include <zephyr/drivers/spi.h> | 
|  | #include <zephyr/drivers/dac.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/sys/byteorder.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(dac_ad56xx, CONFIG_DAC_LOG_LEVEL); | 
|  |  | 
|  | /* | 
|  | * These values are actually all way less than 1us, but we can only | 
|  | * wait with 1us precision. | 
|  | * | 
|  | * This should be checked when new types of this series are added to | 
|  | * this implementation. | 
|  | */ | 
|  | #define DAC_AD56XX_MINIMUM_PULSE_WIDTH_LOW_IN_US 1 | 
|  | #define DAC_AD56XX_PULSE_ACTIVATION_TIME_IN_US   1 | 
|  |  | 
|  | enum ad56xx_command { | 
|  | AD56XX_CMD_WRITE_UPDATE_CHANNEL = 3, | 
|  | AD56XX_CMD_SOFTWARE_RESET = 6, | 
|  | }; | 
|  |  | 
|  | struct ad56xx_config { | 
|  | struct spi_dt_spec bus; | 
|  | const struct gpio_dt_spec gpio_reset; | 
|  | uint8_t resolution; | 
|  |  | 
|  | const uint8_t *channel_addresses; | 
|  | size_t channel_count; | 
|  | }; | 
|  |  | 
|  | struct ad56xx_data { | 
|  | }; | 
|  |  | 
|  | static int ad56xx_write_command(const struct device *dev, enum ad56xx_command command, | 
|  | uint8_t address, uint16_t value) | 
|  | { | 
|  | const struct ad56xx_config *config = dev->config; | 
|  | uint8_t buffer_tx[3]; | 
|  | uint8_t buffer_rx[ARRAY_SIZE(buffer_tx)]; | 
|  | const struct spi_buf tx_buf[] = {{ | 
|  | .buf = buffer_tx, | 
|  | .len = ARRAY_SIZE(buffer_tx), | 
|  | }}; | 
|  | const struct spi_buf rx_buf[] = {{ | 
|  | .buf = buffer_rx, | 
|  | .len = ARRAY_SIZE(buffer_rx), | 
|  | }}; | 
|  | const struct spi_buf_set tx = { | 
|  | .buffers = tx_buf, | 
|  | .count = ARRAY_SIZE(tx_buf), | 
|  | }; | 
|  | const struct spi_buf_set rx = { | 
|  | .buffers = rx_buf, | 
|  | .count = ARRAY_SIZE(rx_buf), | 
|  | }; | 
|  |  | 
|  | buffer_tx[0] = (command << 4) | address; | 
|  | value = value << (16 - config->resolution); | 
|  | sys_put_be16(value, buffer_tx + 1); | 
|  |  | 
|  | LOG_DBG("sending to DAC %s command 0x%02X, address 0x%02X and value 0x%04X", dev->name, | 
|  | command, address, value); | 
|  | int result = spi_transceive_dt(&config->bus, &tx, &rx); | 
|  |  | 
|  | if (result != 0) { | 
|  | LOG_ERR("spi_transceive failed with error %i", result); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ad56xx_channel_setup(const struct device *dev, const struct dac_channel_cfg *channel_cfg) | 
|  | { | 
|  | const struct ad56xx_config *config = dev->config; | 
|  |  | 
|  | if (channel_cfg->channel_id >= config->channel_count) { | 
|  | LOG_ERR("invalid channel %i", channel_cfg->channel_id); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (channel_cfg->resolution != config->resolution) { | 
|  | LOG_ERR("invalid resolution %i", channel_cfg->resolution); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ad56xx_write_value(const struct device *dev, uint8_t channel, uint32_t value) | 
|  | { | 
|  | const struct ad56xx_config *config = dev->config; | 
|  |  | 
|  | if (value > BIT(config->resolution) - 1) { | 
|  | LOG_ERR("invalid value %i", value); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (channel > config->channel_count) { | 
|  | LOG_ERR("invalid channel %i", channel); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return ad56xx_write_command(dev, AD56XX_CMD_WRITE_UPDATE_CHANNEL, | 
|  | config->channel_addresses[channel], value); | 
|  | } | 
|  |  | 
|  | static int ad56xx_init(const struct device *dev) | 
|  | { | 
|  | const struct ad56xx_config *config = dev->config; | 
|  | int result; | 
|  |  | 
|  | if (!spi_is_ready_dt(&config->bus)) { | 
|  | LOG_ERR("SPI bus %s not ready", config->bus.bus->name); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | if (config->gpio_reset.port != NULL) { | 
|  | LOG_DBG("reset %s with GPIO", dev->name); | 
|  | result = gpio_pin_configure_dt(&config->gpio_reset, GPIO_OUTPUT_ACTIVE); | 
|  | if (result != 0) { | 
|  | LOG_ERR("failed to initialize GPIO for reset"); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | k_busy_wait(DAC_AD56XX_MINIMUM_PULSE_WIDTH_LOW_IN_US); | 
|  | gpio_pin_set_dt(&config->gpio_reset, 0); | 
|  | } else { | 
|  | LOG_DBG("reset %s with command", dev->name); | 
|  | result = ad56xx_write_command(dev, AD56XX_CMD_SOFTWARE_RESET, 0, 0); | 
|  | if (result != 0) { | 
|  | LOG_ERR("failed to send reset command"); | 
|  | return result; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The pulse activation time is actually defined to start together | 
|  | * with the pulse start. To be on the safe side we add the wait time | 
|  | * on top of the actual pulse. | 
|  | */ | 
|  | k_busy_wait(DAC_AD56XX_PULSE_ACTIVATION_TIME_IN_US); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct dac_driver_api ad56xx_driver_api = { | 
|  | .channel_setup = ad56xx_channel_setup, | 
|  | .write_value = ad56xx_write_value, | 
|  | }; | 
|  |  | 
|  | BUILD_ASSERT(CONFIG_DAC_AD56XX_INIT_PRIORITY > CONFIG_SPI_INIT_PRIORITY, | 
|  | "CONFIG_DAC_AD56XX_INIT_PRIORITY must be higher than CONFIG_SPI_INIT_PRIORITY"); | 
|  |  | 
|  | #define DAC_AD56XX_INST_DEFINE(index, name, res, channels, channels_count)                         \ | 
|  | static struct ad56xx_data data_##name##_##index;                                           \ | 
|  | static const struct ad56xx_config config_##name##_##index = {                              \ | 
|  | .bus = SPI_DT_SPEC_INST_GET(                                                       \ | 
|  | index, SPI_OP_MODE_MASTER | SPI_MODE_CPHA | SPI_WORD_SET(8), 0),           \ | 
|  | .resolution = res,                                                                 \ | 
|  | .gpio_reset = GPIO_DT_SPEC_INST_GET_OR(index, reset_gpios, {0}),                   \ | 
|  | .channel_addresses = channels,                                                     \ | 
|  | .channel_count = channels_count,                                                   \ | 
|  | };                                                                                         \ | 
|  | DEVICE_DT_INST_DEFINE(index, ad56xx_init, NULL, &data_##name##_##index,                    \ | 
|  | &config_##name##_##index, POST_KERNEL,                               \ | 
|  | CONFIG_DAC_AD56XX_INIT_PRIORITY, &ad56xx_driver_api); | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5628 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5628_channels[] = { | 
|  | 0, 1, 2, 3, 4, 5, 6, 7, | 
|  | }; | 
|  | #define DAC_AD5628_RESOLUTION    12 | 
|  | #define DAC_AD5628_CHANNELS      ad5628_channels | 
|  | #define DAC_AD5628_CHANNEL_COUNT ARRAY_SIZE(ad5628_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5628_RESOLUTION, | 
|  | DAC_AD5628_CHANNELS, DAC_AD5628_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5648 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5648_channels[] = { | 
|  | 0, 1, 2, 3, 4, 5, 6, 7, | 
|  | }; | 
|  | #define DAC_AD5648_RESOLUTION    14 | 
|  | #define DAC_AD5648_CHANNELS      ad5648_channels | 
|  | #define DAC_AD5648_CHANNEL_COUNT ARRAY_SIZE(ad5648_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5648_RESOLUTION, | 
|  | DAC_AD5648_CHANNELS, DAC_AD5648_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5668 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5668_channels[] = { | 
|  | 0, 1, 2, 3, 4, 5, 6, 7, | 
|  | }; | 
|  | #define DAC_AD5668_RESOLUTION    16 | 
|  | #define DAC_AD5668_CHANNELS      ad5668_channels | 
|  | #define DAC_AD5668_CHANNEL_COUNT ARRAY_SIZE(ad5668_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5668_RESOLUTION, | 
|  | DAC_AD5668_CHANNELS, DAC_AD5668_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5672 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5672_channels[] = { | 
|  | 0, 1, 2, 3, 4, 5, 6, 7, | 
|  | }; | 
|  | #define DAC_AD5672_RESOLUTION    12 | 
|  | #define DAC_AD5672_CHANNELS      ad5672_channels | 
|  | #define DAC_AD5672_CHANNEL_COUNT ARRAY_SIZE(ad5672_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5672_RESOLUTION, | 
|  | DAC_AD5672_CHANNELS, DAC_AD5672_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5674 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5674_channels[] = { | 
|  | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | 
|  | }; | 
|  | #define DAC_AD5674_RESOLUTION    12 | 
|  | #define DAC_AD5674_CHANNELS      ad5674_channels | 
|  | #define DAC_AD5674_CHANNEL_COUNT ARRAY_SIZE(ad5674_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5674_RESOLUTION, | 
|  | DAC_AD5674_CHANNELS, DAC_AD5674_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5676 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5676_channels[] = { | 
|  | 0, 1, 2, 3, 4, 5, 6, 7, | 
|  | }; | 
|  | #define DAC_AD5676_RESOLUTION    16 | 
|  | #define DAC_AD5676_CHANNELS      ad5676_channels | 
|  | #define DAC_AD5676_CHANNEL_COUNT ARRAY_SIZE(ad5676_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5676_RESOLUTION, | 
|  | DAC_AD5676_CHANNELS, DAC_AD5676_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5679 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5679_channels[] = { | 
|  | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | 
|  | }; | 
|  | #define DAC_AD5679_RESOLUTION    16 | 
|  | #define DAC_AD5679_CHANNELS      ad5679_channels | 
|  | #define DAC_AD5679_CHANNEL_COUNT ARRAY_SIZE(ad5679_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5679_RESOLUTION, | 
|  | DAC_AD5679_CHANNELS, DAC_AD5679_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5684 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5684_channels[] = { | 
|  | 1, | 
|  | 2, | 
|  | 4, | 
|  | 8, | 
|  | }; | 
|  | #define DAC_AD5684_RESOLUTION    12 | 
|  | #define DAC_AD5684_CHANNELS      ad5684_channels | 
|  | #define DAC_AD5684_CHANNEL_COUNT ARRAY_SIZE(ad5684_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5684_RESOLUTION, | 
|  | DAC_AD5684_CHANNELS, DAC_AD5684_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5686 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5686_channels[] = { | 
|  | 1, | 
|  | 2, | 
|  | 4, | 
|  | 8, | 
|  | }; | 
|  | #define DAC_AD5686_RESOLUTION    16 | 
|  | #define DAC_AD5686_CHANNELS      ad5686_channels | 
|  | #define DAC_AD5686_CHANNEL_COUNT ARRAY_SIZE(ad5686_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5686_RESOLUTION, | 
|  | DAC_AD5686_CHANNELS, DAC_AD5686_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5687 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5687_channels[] = { | 
|  | 1, | 
|  | 8, | 
|  | }; | 
|  | #define DAC_AD5687_RESOLUTION    12 | 
|  | #define DAC_AD5687_CHANNELS      ad5687_channels | 
|  | #define DAC_AD5687_CHANNEL_COUNT ARRAY_SIZE(ad5687_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5687_RESOLUTION, | 
|  | DAC_AD5687_CHANNELS, DAC_AD5687_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT | 
|  |  | 
|  | #define DT_DRV_COMPAT adi_ad5689 | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) | 
|  | static const uint8_t ad5689_channels[] = { | 
|  | 1, | 
|  | 8, | 
|  | }; | 
|  | #define DAC_AD5689_RESOLUTION    16 | 
|  | #define DAC_AD5689_CHANNELS      ad5689_channels | 
|  | #define DAC_AD5689_CHANNEL_COUNT ARRAY_SIZE(ad5689_channels) | 
|  | DT_INST_FOREACH_STATUS_OKAY_VARGS(DAC_AD56XX_INST_DEFINE, DT_DRV_COMPAT, DAC_AD5689_RESOLUTION, | 
|  | DAC_AD5689_CHANNELS, DAC_AD5689_CHANNEL_COUNT) | 
|  | #endif | 
|  | #undef DT_DRV_COMPAT |