]>
Commit | Line | Data |
---|---|---|
59c23eab MH |
1 | /* |
2 | * AD5504, AD5501 High Voltage Digital to Analog Converter | |
3 | * | |
4 | * Copyright 2011 Analog Devices Inc. | |
5 | * | |
6 | * Licensed under the GPL-2. | |
7 | */ | |
8 | ||
9 | #include <linux/interrupt.h> | |
59c23eab MH |
10 | #include <linux/fs.h> |
11 | #include <linux/device.h> | |
12 | #include <linux/kernel.h> | |
13 | #include <linux/spi/spi.h> | |
14 | #include <linux/slab.h> | |
15 | #include <linux/sysfs.h> | |
16 | #include <linux/regulator/consumer.h> | |
99c97852 | 17 | #include <linux/module.h> |
a476bc02 | 18 | #include <linux/bitops.h> |
59c23eab | 19 | |
06458e27 JC |
20 | #include <linux/iio/iio.h> |
21 | #include <linux/iio/sysfs.h> | |
22 | #include <linux/iio/events.h> | |
dbdc025b | 23 | #include <linux/iio/dac/ad5504.h> |
59c23eab | 24 | |
a476bc02 PM |
25 | #define AD5504_RES_MASK GENMASK(11, 0) |
26 | #define AD5504_CMD_READ BIT(15) | |
27 | #define AD5504_CMD_WRITE 0 | |
a98348b7 LPC |
28 | #define AD5504_ADDR(addr) ((addr) << 12) |
29 | ||
30 | /* Registers */ | |
31 | #define AD5504_ADDR_NOOP 0 | |
32 | #define AD5504_ADDR_DAC(x) ((x) + 1) | |
33 | #define AD5504_ADDR_ALL_DAC 5 | |
34 | #define AD5504_ADDR_CTRL 7 | |
35 | ||
36 | /* Control Register */ | |
37 | #define AD5504_DAC_PWR(ch) ((ch) << 2) | |
38 | #define AD5504_DAC_PWRDWN_MODE(mode) ((mode) << 6) | |
39 | #define AD5504_DAC_PWRDN_20K 0 | |
40 | #define AD5504_DAC_PWRDN_3STATE 1 | |
41 | ||
42 | /** | |
43 | * struct ad5446_state - driver instance specific data | |
a476bc02 | 44 | * @spi: spi_device |
a98348b7 LPC |
45 | * @reg: supply regulator |
46 | * @vref_mv: actual reference voltage used | |
47 | * @pwr_down_mask power down mask | |
48 | * @pwr_down_mode current power down mode | |
0dbe59c7 | 49 | * @data: transfer buffer |
a98348b7 | 50 | */ |
a98348b7 LPC |
51 | struct ad5504_state { |
52 | struct spi_device *spi; | |
53 | struct regulator *reg; | |
54 | unsigned short vref_mv; | |
55 | unsigned pwr_down_mask; | |
56 | unsigned pwr_down_mode; | |
0dbe59c7 LPC |
57 | |
58 | __be16 data[2] ____cacheline_aligned; | |
a98348b7 LPC |
59 | }; |
60 | ||
61 | /** | |
62 | * ad5504_supported_device_ids: | |
63 | */ | |
64 | ||
65 | enum ad5504_supported_device_ids { | |
66 | ID_AD5504, | |
67 | ID_AD5501, | |
68 | }; | |
69 | ||
0dbe59c7 | 70 | static int ad5504_spi_write(struct ad5504_state *st, u8 addr, u16 val) |
59c23eab | 71 | { |
0dbe59c7 | 72 | st->data[0] = cpu_to_be16(AD5504_CMD_WRITE | AD5504_ADDR(addr) | |
59c23eab MH |
73 | (val & AD5504_RES_MASK)); |
74 | ||
0dbe59c7 | 75 | return spi_write(st->spi, &st->data[0], 2); |
59c23eab MH |
76 | } |
77 | ||
0dbe59c7 | 78 | static int ad5504_spi_read(struct ad5504_state *st, u8 addr) |
59c23eab | 79 | { |
59c23eab | 80 | int ret; |
0dbe59c7 LPC |
81 | struct spi_transfer t = { |
82 | .tx_buf = &st->data[0], | |
83 | .rx_buf = &st->data[1], | |
84 | .len = 2, | |
85 | }; | |
86 | ||
87 | st->data[0] = cpu_to_be16(AD5504_CMD_READ | AD5504_ADDR(addr)); | |
88 | ret = spi_sync_transfer(st->spi, &t, 1); | |
a7b15288 LPC |
89 | if (ret < 0) |
90 | return ret; | |
59c23eab | 91 | |
0dbe59c7 | 92 | return be16_to_cpu(st->data[1]) & AD5504_RES_MASK; |
59c23eab MH |
93 | } |
94 | ||
a7b15288 LPC |
95 | static int ad5504_read_raw(struct iio_dev *indio_dev, |
96 | struct iio_chan_spec const *chan, | |
97 | int *val, | |
98 | int *val2, | |
99 | long m) | |
59c23eab | 100 | { |
a3684ded | 101 | struct ad5504_state *st = iio_priv(indio_dev); |
59c23eab MH |
102 | int ret; |
103 | ||
a7b15288 | 104 | switch (m) { |
09f4eb40 | 105 | case IIO_CHAN_INFO_RAW: |
0dbe59c7 | 106 | ret = ad5504_spi_read(st, chan->address); |
a7b15288 LPC |
107 | if (ret < 0) |
108 | return ret; | |
59c23eab | 109 | |
a7b15288 LPC |
110 | *val = ret; |
111 | ||
112 | return IIO_VAL_INT; | |
113 | case IIO_CHAN_INFO_SCALE: | |
54ea1433 LPC |
114 | *val = st->vref_mv; |
115 | *val2 = chan->scan_type.realbits; | |
116 | return IIO_VAL_FRACTIONAL_LOG2; | |
a7b15288 LPC |
117 | } |
118 | return -EINVAL; | |
59c23eab MH |
119 | } |
120 | ||
a7b15288 LPC |
121 | static int ad5504_write_raw(struct iio_dev *indio_dev, |
122 | struct iio_chan_spec const *chan, | |
123 | int val, | |
124 | int val2, | |
125 | long mask) | |
59c23eab | 126 | { |
a3684ded | 127 | struct ad5504_state *st = iio_priv(indio_dev); |
59c23eab | 128 | |
a7b15288 | 129 | switch (mask) { |
09f4eb40 | 130 | case IIO_CHAN_INFO_RAW: |
a7b15288 LPC |
131 | if (val >= (1 << chan->scan_type.realbits) || val < 0) |
132 | return -EINVAL; | |
59c23eab | 133 | |
0dbe59c7 | 134 | return ad5504_spi_write(st, chan->address, val); |
a7b15288 | 135 | default: |
0d3a9b2d | 136 | return -EINVAL; |
a7b15288 | 137 | } |
59c23eab MH |
138 | } |
139 | ||
8d05d777 LPC |
140 | static const char * const ad5504_powerdown_modes[] = { |
141 | "20kohm_to_gnd", | |
142 | "three_state", | |
143 | }; | |
144 | ||
145 | static int ad5504_get_powerdown_mode(struct iio_dev *indio_dev, | |
146 | const struct iio_chan_spec *chan) | |
59c23eab | 147 | { |
a3684ded | 148 | struct ad5504_state *st = iio_priv(indio_dev); |
59c23eab | 149 | |
8d05d777 | 150 | return st->pwr_down_mode; |
59c23eab MH |
151 | } |
152 | ||
8d05d777 LPC |
153 | static int ad5504_set_powerdown_mode(struct iio_dev *indio_dev, |
154 | const struct iio_chan_spec *chan, unsigned int mode) | |
59c23eab | 155 | { |
a3684ded | 156 | struct ad5504_state *st = iio_priv(indio_dev); |
59c23eab | 157 | |
8d05d777 | 158 | st->pwr_down_mode = mode; |
59c23eab | 159 | |
8d05d777 | 160 | return 0; |
59c23eab MH |
161 | } |
162 | ||
8d05d777 LPC |
163 | static const struct iio_enum ad5504_powerdown_mode_enum = { |
164 | .items = ad5504_powerdown_modes, | |
165 | .num_items = ARRAY_SIZE(ad5504_powerdown_modes), | |
166 | .get = ad5504_get_powerdown_mode, | |
167 | .set = ad5504_set_powerdown_mode, | |
168 | }; | |
169 | ||
170 | static ssize_t ad5504_read_dac_powerdown(struct iio_dev *indio_dev, | |
171 | uintptr_t private, const struct iio_chan_spec *chan, char *buf) | |
59c23eab | 172 | { |
a3684ded | 173 | struct ad5504_state *st = iio_priv(indio_dev); |
59c23eab MH |
174 | |
175 | return sprintf(buf, "%d\n", | |
8d05d777 | 176 | !(st->pwr_down_mask & (1 << chan->channel))); |
59c23eab MH |
177 | } |
178 | ||
8d05d777 LPC |
179 | static ssize_t ad5504_write_dac_powerdown(struct iio_dev *indio_dev, |
180 | uintptr_t private, const struct iio_chan_spec *chan, const char *buf, | |
181 | size_t len) | |
59c23eab | 182 | { |
3bbbf150 | 183 | bool pwr_down; |
59c23eab | 184 | int ret; |
a3684ded | 185 | struct ad5504_state *st = iio_priv(indio_dev); |
59c23eab | 186 | |
3bbbf150 | 187 | ret = strtobool(buf, &pwr_down); |
59c23eab MH |
188 | if (ret) |
189 | return ret; | |
190 | ||
3bbbf150 | 191 | if (pwr_down) |
8d05d777 | 192 | st->pwr_down_mask |= (1 << chan->channel); |
59c23eab | 193 | else |
3bbbf150 | 194 | st->pwr_down_mask &= ~(1 << chan->channel); |
59c23eab | 195 | |
0dbe59c7 | 196 | ret = ad5504_spi_write(st, AD5504_ADDR_CTRL, |
59c23eab MH |
197 | AD5504_DAC_PWRDWN_MODE(st->pwr_down_mode) | |
198 | AD5504_DAC_PWR(st->pwr_down_mask)); | |
199 | ||
200 | /* writes to the CTRL register must be followed by a NOOP */ | |
0dbe59c7 | 201 | ad5504_spi_write(st, AD5504_ADDR_NOOP, 0); |
59c23eab MH |
202 | |
203 | return ret ? ret : len; | |
204 | } | |
205 | ||
59c23eab MH |
206 | static IIO_CONST_ATTR(temp0_thresh_rising_value, "110000"); |
207 | static IIO_CONST_ATTR(temp0_thresh_rising_en, "1"); | |
208 | ||
209 | static struct attribute *ad5504_ev_attributes[] = { | |
210 | &iio_const_attr_temp0_thresh_rising_value.dev_attr.attr, | |
211 | &iio_const_attr_temp0_thresh_rising_en.dev_attr.attr, | |
212 | NULL, | |
213 | }; | |
214 | ||
e36020fd | 215 | static const struct attribute_group ad5504_ev_attribute_group = { |
59c23eab MH |
216 | .attrs = ad5504_ev_attributes, |
217 | }; | |
218 | ||
ce298d40 | 219 | static irqreturn_t ad5504_event_handler(int irq, void *private) |
59c23eab | 220 | { |
5aa96188 | 221 | iio_push_event(private, |
c4b14d99 | 222 | IIO_UNMOD_EVENT_CODE(IIO_TEMP, |
ce298d40 JC |
223 | 0, |
224 | IIO_EV_TYPE_THRESH, | |
225 | IIO_EV_DIR_RISING), | |
4d925083 | 226 | iio_get_time_ns(private)); |
ce298d40 JC |
227 | |
228 | return IRQ_HANDLED; | |
59c23eab MH |
229 | } |
230 | ||
6fe8135f | 231 | static const struct iio_info ad5504_info = { |
a7b15288 LPC |
232 | .write_raw = ad5504_write_raw, |
233 | .read_raw = ad5504_read_raw, | |
6fe8135f | 234 | .event_attrs = &ad5504_ev_attribute_group, |
6fe8135f JC |
235 | }; |
236 | ||
8d05d777 LPC |
237 | static const struct iio_chan_spec_ext_info ad5504_ext_info[] = { |
238 | { | |
239 | .name = "powerdown", | |
240 | .read = ad5504_read_dac_powerdown, | |
241 | .write = ad5504_write_dac_powerdown, | |
3704432f | 242 | .shared = IIO_SEPARATE, |
8d05d777 | 243 | }, |
3704432f JC |
244 | IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, |
245 | &ad5504_powerdown_mode_enum), | |
8d05d777 LPC |
246 | IIO_ENUM_AVAILABLE("powerdown_mode", &ad5504_powerdown_mode_enum), |
247 | { }, | |
248 | }; | |
249 | ||
250 | #define AD5504_CHANNEL(_chan) { \ | |
251 | .type = IIO_VOLTAGE, \ | |
252 | .indexed = 1, \ | |
253 | .output = 1, \ | |
254 | .channel = (_chan), \ | |
f3ec5a2d JC |
255 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
256 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ | |
8d05d777 | 257 | .address = AD5504_ADDR_DAC(_chan), \ |
73d3a775 JC |
258 | .scan_type = { \ |
259 | .sign = 'u', \ | |
260 | .realbits = 12, \ | |
261 | .storagebits = 16, \ | |
262 | }, \ | |
8d05d777 LPC |
263 | .ext_info = ad5504_ext_info, \ |
264 | } | |
265 | ||
266 | static const struct iio_chan_spec ad5504_channels[] = { | |
267 | AD5504_CHANNEL(0), | |
268 | AD5504_CHANNEL(1), | |
269 | AD5504_CHANNEL(2), | |
270 | AD5504_CHANNEL(3), | |
6fe8135f JC |
271 | }; |
272 | ||
fc52692c | 273 | static int ad5504_probe(struct spi_device *spi) |
59c23eab MH |
274 | { |
275 | struct ad5504_platform_data *pdata = spi->dev.platform_data; | |
a3684ded | 276 | struct iio_dev *indio_dev; |
59c23eab | 277 | struct ad5504_state *st; |
a3684ded | 278 | struct regulator *reg; |
59c23eab MH |
279 | int ret, voltage_uv = 0; |
280 | ||
8571ebf7 SK |
281 | indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); |
282 | if (!indio_dev) | |
283 | return -ENOMEM; | |
284 | reg = devm_regulator_get(&spi->dev, "vcc"); | |
a3684ded JC |
285 | if (!IS_ERR(reg)) { |
286 | ret = regulator_enable(reg); | |
59c23eab | 287 | if (ret) |
8571ebf7 | 288 | return ret; |
59c23eab | 289 | |
0ce2fdaa AL |
290 | ret = regulator_get_voltage(reg); |
291 | if (ret < 0) | |
292 | goto error_disable_reg; | |
293 | ||
294 | voltage_uv = ret; | |
59c23eab MH |
295 | } |
296 | ||
a3684ded JC |
297 | spi_set_drvdata(spi, indio_dev); |
298 | st = iio_priv(indio_dev); | |
59c23eab MH |
299 | if (voltage_uv) |
300 | st->vref_mv = voltage_uv / 1000; | |
301 | else if (pdata) | |
302 | st->vref_mv = pdata->vref_mv; | |
303 | else | |
304 | dev_warn(&spi->dev, "reference voltage unspecified\n"); | |
305 | ||
a3684ded | 306 | st->reg = reg; |
59c23eab | 307 | st->spi = spi; |
a3684ded JC |
308 | indio_dev->dev.parent = &spi->dev; |
309 | indio_dev->name = spi_get_device_id(st->spi)->name; | |
8d05d777 LPC |
310 | indio_dev->info = &ad5504_info; |
311 | if (spi_get_device_id(st->spi)->driver_data == ID_AD5501) | |
a7b15288 | 312 | indio_dev->num_channels = 1; |
8d05d777 | 313 | else |
a7b15288 | 314 | indio_dev->num_channels = 4; |
a7b15288 | 315 | indio_dev->channels = ad5504_channels; |
a3684ded | 316 | indio_dev->modes = INDIO_DIRECT_MODE; |
59c23eab | 317 | |
59c23eab | 318 | if (spi->irq) { |
8571ebf7 | 319 | ret = devm_request_threaded_irq(&spi->dev, spi->irq, |
ce298d40 JC |
320 | NULL, |
321 | &ad5504_event_handler, | |
322 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | |
323 | spi_get_device_id(st->spi)->name, | |
a3684ded | 324 | indio_dev); |
59c23eab | 325 | if (ret) |
26d25ae3 | 326 | goto error_disable_reg; |
59c23eab MH |
327 | } |
328 | ||
26d25ae3 JC |
329 | ret = iio_device_register(indio_dev); |
330 | if (ret) | |
8571ebf7 | 331 | goto error_disable_reg; |
26d25ae3 | 332 | |
59c23eab MH |
333 | return 0; |
334 | ||
59c23eab | 335 | error_disable_reg: |
a3684ded | 336 | if (!IS_ERR(reg)) |
0cbb2b53 | 337 | regulator_disable(reg); |
59c23eab | 338 | |
59c23eab MH |
339 | return ret; |
340 | } | |
341 | ||
fc52692c | 342 | static int ad5504_remove(struct spi_device *spi) |
59c23eab | 343 | { |
a3684ded JC |
344 | struct iio_dev *indio_dev = spi_get_drvdata(spi); |
345 | struct ad5504_state *st = iio_priv(indio_dev); | |
26d25ae3 | 346 | |
d2fffd6c | 347 | iio_device_unregister(indio_dev); |
59c23eab | 348 | |
8571ebf7 | 349 | if (!IS_ERR(st->reg)) |
26d25ae3 | 350 | regulator_disable(st->reg); |
26d25ae3 | 351 | |
59c23eab MH |
352 | return 0; |
353 | } | |
354 | ||
355 | static const struct spi_device_id ad5504_id[] = { | |
356 | {"ad5504", ID_AD5504}, | |
357 | {"ad5501", ID_AD5501}, | |
358 | {} | |
359 | }; | |
55e4390c | 360 | MODULE_DEVICE_TABLE(spi, ad5504_id); |
59c23eab MH |
361 | |
362 | static struct spi_driver ad5504_driver = { | |
363 | .driver = { | |
364 | .name = "ad5504", | |
59c23eab MH |
365 | }, |
366 | .probe = ad5504_probe, | |
fc52692c | 367 | .remove = ad5504_remove, |
59c23eab MH |
368 | .id_table = ad5504_id, |
369 | }; | |
ae6ae6fe | 370 | module_spi_driver(ad5504_driver); |
59c23eab | 371 | |
9920ed25 | 372 | MODULE_AUTHOR("Michael Hennerich <[email protected]>"); |
59c23eab MH |
373 | MODULE_DESCRIPTION("Analog Devices AD5501/AD5501 DAC"); |
374 | MODULE_LICENSE("GPL v2"); |