1 /*
2 * Copyright (c) 2024 Google LLC.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /**
8 * @brief Driver for Texas Instruments DACx0501 series
9 *
10 * Device driver for the Texas Instruments DACx0501 series of devices: DAC60501, DAC70501 and
11 * DAC80501: Digital to Analog Converters with a single channel output and with 12, 14 and 16
12 * bits resolution respectively. Data sheet can be found here:
13 * https://www.ti.com/lit/ds/symlink/dac80501.pdf
14 */
15
16 #include <zephyr/kernel.h>
17 #include <zephyr/drivers/i2c.h>
18 #include <zephyr/drivers/dac.h>
19 #include <zephyr/logging/log.h>
20 #include <zephyr/sys/byteorder.h>
21
22 LOG_MODULE_REGISTER(dac_dacx0501, CONFIG_DAC_LOG_LEVEL);
23
24 #define DACX0501_REG_DEVICE_ID 0x01U
25 #define DACX0501_REG_SYNC 0x02U
26 #define DACX0501_REG_CONFIG 0x03U
27 #define DACX0501_REG_GAIN 0x04U
28 #define DACX0501_REG_TRIGGER 0x05U
29 #define DACX0501_REG_STATUS 0x07U
30 #define DACX0501_REG_DAC 0x08U
31
32 #define DACX0501_MASK_DEVICE_ID_RES GENMASK(14, 12)
33 #define DACX0501_MASK_CONFIG_REF_PWDWN BIT(8)
34 #define DACX0501_MASK_CONFIG_DAC_PWDWN BIT(0)
35 #define DACX0501_MASK_GAIN_BUFF_GAIN BIT(0)
36 #define DACX0501_MASK_GAIN_REFDIV_EN BIT(8)
37 #define DACX0501_MASK_TRIGGER_SOFT_RESET (BIT(1) | BIT(3))
38 #define DACX0501_MASK_STATUS_REF_ALM BIT(0)
39
40 /* Specifies the source of the reference voltage. */
41 enum voltage_reference_source {
42 REF_INTERNAL, /* Internal 2.5V voltage reference. */
43 REF_EXTERNAL, /* External pin voltage reference. */
44 };
45
46 /* Specifies the reference voltage multiplier. */
47 enum output_gain {
48 VM_MUL2, /* Multiplies by 2. */
49 VM_MUL1, /* Multiplies by 1. */
50 VM_DIV2, /* Multiplies by 0.5 */
51 };
52
53 struct dacx0501_config {
54 struct i2c_dt_spec i2c_spec;
55 enum voltage_reference_source voltage_reference;
56 enum output_gain output_gain;
57 };
58
59 struct dacx0501_data {
60 /* Number of bits in the DAC register: Either 12, 14 or 16. */
61 uint8_t resolution;
62 };
63
dacx0501_reg_read(const struct device * dev,const uint8_t addr,uint16_t * data)64 static int dacx0501_reg_read(const struct device *dev, const uint8_t addr, uint16_t *data)
65 {
66 const struct dacx0501_config *config = dev->config;
67 uint8_t raw_data[2];
68 int status;
69
70 status = i2c_write_read_dt(&config->i2c_spec, &addr, sizeof(addr), raw_data,
71 sizeof(raw_data));
72 if (status != 0) {
73 return status;
74 }
75
76 /* DAC is big endian. */
77 *data = sys_get_be16(raw_data);
78 return 0;
79 }
80
dacx0501_reg_write(const struct device * dev,uint8_t addr,uint16_t data)81 static int dacx0501_reg_write(const struct device *dev, uint8_t addr, uint16_t data)
82 {
83 const struct dacx0501_config *config = dev->config;
84 uint8_t write_cmd[3] = {addr};
85
86 /* DAC is big endian. */
87 sys_put_be16(data, write_cmd + 1);
88
89 return i2c_write_dt(&config->i2c_spec, write_cmd, sizeof(write_cmd));
90 }
91
dacx0501_channel_setup(const struct device * dev,const struct dac_channel_cfg * channel_cfg)92 static int dacx0501_channel_setup(const struct device *dev,
93 const struct dac_channel_cfg *channel_cfg)
94 {
95 struct dacx0501_data *data = dev->data;
96
97 /* DACx0501 series only has a single output channel. */
98 if (channel_cfg->channel_id != 0) {
99 LOG_ERR("Unsupported channel %d", channel_cfg->channel_id);
100 return -ENOTSUP;
101 }
102
103 if (channel_cfg->resolution != data->resolution) {
104 LOG_ERR("Unsupported resolution %d. Actual: %d", channel_cfg->resolution,
105 data->resolution);
106 return -ENOTSUP;
107 }
108
109 if (channel_cfg->internal) {
110 LOG_ERR("Internal channels not supported");
111 return -ENOTSUP;
112 }
113
114 return 0;
115 }
116
dacx0501_write_value(const struct device * dev,uint8_t channel,uint32_t value)117 static int dacx0501_write_value(const struct device *dev, uint8_t channel, uint32_t value)
118 {
119 struct dacx0501_data *data = dev->data;
120
121 if (channel != 0) {
122 LOG_ERR("dacx0501: Unsupported channel %d", channel);
123 return -ENOTSUP;
124 }
125
126 if (value >= (1 << data->resolution)) {
127 LOG_ERR("dacx0501: Value %d out of range", value);
128 return -EINVAL;
129 }
130
131 value <<= (16 - data->resolution);
132
133 return dacx0501_reg_write(dev, DACX0501_REG_DAC, value);
134 }
135
dacx0501_init(const struct device * dev)136 static int dacx0501_init(const struct device *dev)
137 {
138 const struct dacx0501_config *config = dev->config;
139 struct dacx0501_data *data = dev->data;
140 uint16_t device_id;
141 int status;
142
143 if (!i2c_is_ready_dt(&config->i2c_spec)) {
144 LOG_ERR("I2C bus %s not ready", config->i2c_spec.bus->name);
145 return -ENODEV;
146 }
147
148 status = dacx0501_reg_read(dev, DACX0501_REG_DEVICE_ID, &device_id);
149 if (status != 0) {
150 LOG_ERR("read DEVICE_ID register failed");
151 return status;
152 }
153
154 /* See DEVICE_ID register RES field in the data sheet. */
155 data->resolution = 16 - 2 * FIELD_GET(DACX0501_MASK_DEVICE_ID_RES, device_id);
156
157 status = dacx0501_reg_write(dev, DACX0501_REG_CONFIG,
158 FIELD_PREP(DACX0501_MASK_CONFIG_REF_PWDWN,
159 config->voltage_reference == REF_EXTERNAL));
160 if (status != 0) {
161 LOG_ERR("write CONFIG register failed");
162 return status;
163 }
164
165 status = dacx0501_reg_write(
166 dev, DACX0501_REG_GAIN,
167 FIELD_PREP(DACX0501_MASK_GAIN_REFDIV_EN, config->output_gain == VM_DIV2) |
168 FIELD_PREP(DACX0501_MASK_GAIN_BUFF_GAIN, config->output_gain == VM_MUL2));
169 if (status != 0) {
170 LOG_ERR("GAIN Register update failed");
171 return status;
172 }
173
174 return 0;
175 }
176
177 static DEVICE_API(dac, dacx0501_driver_api) = {
178 .channel_setup = dacx0501_channel_setup,
179 .write_value = dacx0501_write_value,
180 };
181
182 #define DT_DRV_COMPAT ti_dacx0501
183
184 #define DACX0501_DEFINE(n) \
185 static struct dacx0501_data dacx0501_data_##n = {}; \
186 static const struct dacx0501_config dacx0501_config_##n = { \
187 .i2c_spec = I2C_DT_SPEC_INST_GET(n), \
188 .voltage_reference = \
189 _CONCAT(REF_, DT_STRING_UPPER_TOKEN(DT_DRV_INST(n), voltage_reference)), \
190 .output_gain = _CONCAT(VM_, DT_STRING_UPPER_TOKEN(DT_DRV_INST(n), output_gain)), \
191 }; \
192 DEVICE_DT_INST_DEFINE(n, &dacx0501_init, NULL, &dacx0501_data_##n, &dacx0501_config_##n, \
193 POST_KERNEL, CONFIG_DAC_DACX0501_INIT_PRIORITY, \
194 &dacx0501_driver_api);
195
196 DT_INST_FOREACH_STATUS_OKAY(DACX0501_DEFINE)
197