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