|  | /* | 
|  | * Copyright (c) 2020 Matija Tudan | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <zephyr.h> | 
|  | #include <kernel.h> | 
|  | #include <drivers/i2c.h> | 
|  | #include <drivers/dac.h> | 
|  | #include <sys/util.h> | 
|  | #include <sys/byteorder.h> | 
|  | #include <sys/__assert.h> | 
|  | #include <logging/log.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(dac_dacx3608, CONFIG_DAC_LOG_LEVEL); | 
|  |  | 
|  | /* Register addresses */ | 
|  | #define DACX3608_REG_DEVICE_CONFIG  0x01U | 
|  | #define DACX3608_REG_STATUS_TRIGGER 0x02U | 
|  | #define DACX3608_REG_BRDCAST        0x03U | 
|  | #define DACX3608_REG_DACA_DATA      0x08U | 
|  |  | 
|  | #define DAC43608_DEVICE_ID      0x500	/* STATUS_TRIGGER[DEVICE_ID] */ | 
|  | #define DAC53608_DEVICE_ID      0x300	/* STATUS_TRIGGER[DEVICE_ID] */ | 
|  | #define DACX3608_SW_RST         0x0A	/* STATUS_TRIGGER[SW_RST] */ | 
|  | #define DACX3608_POR_DELAY      5 | 
|  | #define DACX3608_MAX_CHANNEL    8 | 
|  |  | 
|  | struct dacx3608_config { | 
|  | const char *i2c_bus; | 
|  | uint16_t i2c_addr; | 
|  | uint8_t resolution; | 
|  | }; | 
|  |  | 
|  | struct dacx3608_data { | 
|  | const struct device *i2c; | 
|  | uint8_t configured; | 
|  | }; | 
|  |  | 
|  | static int dacx3608_reg_read(const struct device *dev, uint8_t reg, | 
|  | uint16_t *val) | 
|  | { | 
|  | struct dacx3608_data *data = dev->data; | 
|  | const struct dacx3608_config *cfg = dev->config; | 
|  |  | 
|  | if (i2c_burst_read(data->i2c, cfg->i2c_addr, | 
|  | reg, (uint8_t *) val, 2) < 0) { | 
|  | LOG_ERR("I2C read failed"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | *val = sys_be16_to_cpu(*val); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int dacx3608_reg_write(const struct device *dev, uint8_t reg, | 
|  | uint16_t val) | 
|  | { | 
|  | struct dacx3608_data *data = dev->data; | 
|  | const struct dacx3608_config *cfg = dev->config; | 
|  | uint8_t buf[3] = {reg, val >> 8, val & 0xFF}; | 
|  |  | 
|  | return i2c_write(data->i2c, buf, sizeof(buf), cfg->i2c_addr); | 
|  | } | 
|  |  | 
|  | int dacx3608_reg_update(const struct device *dev, uint8_t reg, | 
|  | uint16_t mask, bool setting) | 
|  | { | 
|  | uint16_t regval; | 
|  | int ret; | 
|  |  | 
|  | ret = dacx3608_reg_read(dev, reg, ®val); | 
|  | if (ret) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (setting) { | 
|  | regval |= mask; | 
|  | } else { | 
|  | regval &= ~mask; | 
|  | } | 
|  |  | 
|  | ret = dacx3608_reg_write(dev, reg, regval); | 
|  | if (ret) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int dacx3608_channel_setup(const struct device *dev, | 
|  | const struct dac_channel_cfg *channel_cfg) | 
|  | { | 
|  | const struct dacx3608_config *config = dev->config; | 
|  | struct dacx3608_data *data = dev->data; | 
|  | bool setting = false; | 
|  | int ret; | 
|  |  | 
|  | if (channel_cfg->channel_id > DACX3608_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; | 
|  | } | 
|  |  | 
|  | if (data->configured & BIT(channel_cfg->channel_id)) { | 
|  | LOG_DBG("Channel %d already configured", channel_cfg->channel_id); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Clear PDNn bit */ | 
|  | ret = dacx3608_reg_update(dev, DACX3608_REG_DEVICE_CONFIG, | 
|  | BIT(channel_cfg->channel_id), setting); | 
|  | if (ret) { | 
|  | LOG_ERR("Unable to update DEVICE_CONFIG register"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | data->configured |= BIT(channel_cfg->channel_id); | 
|  |  | 
|  | LOG_DBG("Channel %d initialized", channel_cfg->channel_id); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int dacx3608_write_value(const struct device *dev, uint8_t channel, | 
|  | uint32_t value) | 
|  | { | 
|  | const struct dacx3608_config *config = dev->config; | 
|  | struct dacx3608_data *data = dev->data; | 
|  | uint16_t regval; | 
|  | int ret; | 
|  |  | 
|  | if (channel > DACX3608_MAX_CHANNEL - 1) { | 
|  | LOG_ERR("Unsupported channel %d", channel); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | if (!(data->configured & BIT(channel))) { | 
|  | LOG_ERR("Channel %d not initialized", channel); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (value >= (1 << (config->resolution))) { | 
|  | LOG_ERR("Value %d out of range", value); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Shift passed value two times left because first two bits are Don't Care | 
|  | * | 
|  | * DACn_DATA register format: | 
|  | * | 
|  | * | 15 14 13 12 |      11 10 9 8 7 6 5 4 3 2      |    1 0     | | 
|  | * |-------------|---------------------------------|------------| | 
|  | * | Don't Care  |  DAC53608[9:0] / DAC43608[7:0]  | Don't Care | | 
|  | */ | 
|  | regval = value << 2; | 
|  | regval &= 0xFFFF; | 
|  |  | 
|  | ret = dacx3608_reg_write(dev, DACX3608_REG_DACA_DATA + channel, regval); | 
|  | if (ret) { | 
|  | LOG_ERR("Unable to set value %d on channel %d", value, channel); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int dacx3608_soft_reset(const struct device *dev) | 
|  | { | 
|  | uint16_t regval = DACX3608_SW_RST; | 
|  | int ret; | 
|  |  | 
|  | ret = dacx3608_reg_write(dev, DACX3608_REG_STATUS_TRIGGER, regval); | 
|  | if (ret) { | 
|  | return -EIO; | 
|  | } | 
|  | k_msleep(DACX3608_POR_DELAY); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int dacx3608_device_id_check(const struct device *dev) | 
|  | { | 
|  | uint16_t dev_id; | 
|  | int ret; | 
|  |  | 
|  | ret = dacx3608_reg_read(dev, DACX3608_REG_STATUS_TRIGGER, &dev_id); | 
|  | if (ret) { | 
|  | LOG_ERR("Unable to read device ID"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | switch (dev_id) { | 
|  | case DAC43608_DEVICE_ID: | 
|  | case DAC53608_DEVICE_ID: | 
|  | LOG_DBG("Device ID %#4x", dev_id); | 
|  | break; | 
|  | default: | 
|  | LOG_ERR("Unknown Device ID %#4x", dev_id); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int dacx3608_init(const struct device *dev) | 
|  | { | 
|  | const struct dacx3608_config *config = dev->config; | 
|  | struct dacx3608_data *data = dev->data; | 
|  | int ret; | 
|  |  | 
|  | data->i2c = device_get_binding(config->i2c_bus); | 
|  | if (!data->i2c) { | 
|  | LOG_ERR("Could not find I2C device"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | ret = dacx3608_soft_reset(dev); | 
|  | if (ret) { | 
|  | LOG_ERR("Soft-reset failed"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = dacx3608_device_id_check(dev); | 
|  | if (ret) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | data->configured = 0; | 
|  |  | 
|  | LOG_DBG("Init complete"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct dac_driver_api dacx3608_driver_api = { | 
|  | .channel_setup = dacx3608_channel_setup, | 
|  | .write_value = dacx3608_write_value, | 
|  | }; | 
|  |  | 
|  | #define INST_DT_DACX3608(inst, t) DT_INST(inst, ti_dac##t) | 
|  |  | 
|  | #define DACX3608_DEVICE(t, n, res) \ | 
|  | static struct dacx3608_data dac##t##_data_##n; \ | 
|  | static const struct dacx3608_config dac##t##_config_##n = { \ | 
|  | .i2c_bus = DT_BUS_LABEL(INST_DT_DACX3608(n, t)), \ | 
|  | .i2c_addr = DT_REG_ADDR(INST_DT_DACX3608(n, t)), \ | 
|  | .resolution = res, \ | 
|  | }; \ | 
|  | DEVICE_DT_DEFINE(INST_DT_DACX3608(n, t), \ | 
|  | &dacx3608_init, NULL, \ | 
|  | &dac##t##_data_##n, \ | 
|  | &dac##t##_config_##n, POST_KERNEL, \ | 
|  | CONFIG_DAC_DACX3608_INIT_PRIORITY, \ | 
|  | &dacx3608_driver_api) | 
|  |  | 
|  | /* | 
|  | * DAC43608: 8-bit | 
|  | */ | 
|  | #define DAC43608_DEVICE(n) DACX3608_DEVICE(43608, n, 8) | 
|  |  | 
|  | /* | 
|  | * DAC53608: 10-bit | 
|  | */ | 
|  | #define DAC53608_DEVICE(n) DACX3608_DEVICE(53608, n, 10) | 
|  |  | 
|  | #define CALL_WITH_ARG(arg, expr) expr(arg) | 
|  |  | 
|  | #define INST_DT_DACX3608_FOREACH(t, inst_expr) \ | 
|  | UTIL_LISTIFY(DT_NUM_INST_STATUS_OKAY(ti_dac##t), \ | 
|  | CALL_WITH_ARG, inst_expr) | 
|  |  | 
|  | INST_DT_DACX3608_FOREACH(43608, DAC43608_DEVICE); | 
|  | INST_DT_DACX3608_FOREACH(53608, DAC53608_DEVICE); |