]>
Commit | Line | Data |
---|---|---|
3c4b0f4d | 1 | // SPDX-License-Identifier: GPL-2.0 |
7fde1484 PR |
2 | /* |
3 | * IIO DAC emulation driver using a digital potentiometer | |
4 | * | |
5 | * Copyright (C) 2016 Axentia Technologies AB | |
6 | * | |
7 | * Author: Peter Rosin <[email protected]> | |
7fde1484 PR |
8 | */ |
9 | ||
10 | /* | |
11 | * It is assumed that the dpot is used as a voltage divider between the | |
12 | * current dpot wiper setting and the maximum resistance of the dpot. The | |
13 | * divided voltage is provided by a vref regulator. | |
14 | * | |
15 | * .------. | |
16 | * .-----------. | | | |
17 | * | vref |--' .---. | |
18 | * | regulator |--. | | | |
19 | * '-----------' | | d | | |
20 | * | | p | | |
21 | * | | o | wiper | |
22 | * | | t |<---------+ | |
23 | * | | | | |
24 | * | '---' dac output voltage | |
25 | * | | | |
26 | * '------+------------+ | |
27 | */ | |
28 | ||
29 | #include <linux/err.h> | |
30 | #include <linux/iio/consumer.h> | |
31 | #include <linux/iio/iio.h> | |
32 | #include <linux/module.h> | |
33 | #include <linux/of.h> | |
34 | #include <linux/platform_device.h> | |
35 | #include <linux/regulator/consumer.h> | |
36 | ||
37 | struct dpot_dac { | |
38 | struct regulator *vref; | |
39 | struct iio_channel *dpot; | |
40 | u32 max_ohms; | |
41 | }; | |
42 | ||
43 | static const struct iio_chan_spec dpot_dac_iio_channel = { | |
44 | .type = IIO_VOLTAGE, | |
45 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
46 | | BIT(IIO_CHAN_INFO_SCALE), | |
47 | .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), | |
48 | .output = 1, | |
49 | .indexed = 1, | |
50 | }; | |
51 | ||
52 | static int dpot_dac_read_raw(struct iio_dev *indio_dev, | |
53 | struct iio_chan_spec const *chan, | |
54 | int *val, int *val2, long mask) | |
55 | { | |
56 | struct dpot_dac *dac = iio_priv(indio_dev); | |
57 | int ret; | |
58 | unsigned long long tmp; | |
59 | ||
60 | switch (mask) { | |
61 | case IIO_CHAN_INFO_RAW: | |
62 | return iio_read_channel_raw(dac->dpot, val); | |
63 | ||
64 | case IIO_CHAN_INFO_SCALE: | |
65 | ret = iio_read_channel_scale(dac->dpot, val, val2); | |
66 | switch (ret) { | |
67 | case IIO_VAL_FRACTIONAL_LOG2: | |
68 | tmp = *val * 1000000000LL; | |
69 | do_div(tmp, dac->max_ohms); | |
70 | tmp *= regulator_get_voltage(dac->vref) / 1000; | |
71 | do_div(tmp, 1000000000LL); | |
72 | *val = tmp; | |
73 | return ret; | |
74 | case IIO_VAL_INT: | |
75 | /* | |
76 | * Convert integer scale to fractional scale by | |
c65a0d84 | 77 | * setting the denominator (val2) to one, and... |
7fde1484 PR |
78 | */ |
79 | *val2 = 1; | |
80 | ret = IIO_VAL_FRACTIONAL; | |
c65a0d84 | 81 | /* fall through */ |
7fde1484 PR |
82 | case IIO_VAL_FRACTIONAL: |
83 | *val *= regulator_get_voltage(dac->vref) / 1000; | |
84 | *val2 *= dac->max_ohms; | |
85 | break; | |
86 | } | |
87 | ||
88 | return ret; | |
89 | } | |
90 | ||
91 | return -EINVAL; | |
92 | } | |
93 | ||
94 | static int dpot_dac_read_avail(struct iio_dev *indio_dev, | |
95 | struct iio_chan_spec const *chan, | |
96 | const int **vals, int *type, int *length, | |
97 | long mask) | |
98 | { | |
99 | struct dpot_dac *dac = iio_priv(indio_dev); | |
100 | ||
101 | switch (mask) { | |
102 | case IIO_CHAN_INFO_RAW: | |
103 | *type = IIO_VAL_INT; | |
104 | return iio_read_avail_channel_raw(dac->dpot, vals, length); | |
105 | } | |
106 | ||
107 | return -EINVAL; | |
108 | } | |
109 | ||
110 | static int dpot_dac_write_raw(struct iio_dev *indio_dev, | |
111 | struct iio_chan_spec const *chan, | |
112 | int val, int val2, long mask) | |
113 | { | |
114 | struct dpot_dac *dac = iio_priv(indio_dev); | |
115 | ||
116 | switch (mask) { | |
117 | case IIO_CHAN_INFO_RAW: | |
118 | return iio_write_channel_raw(dac->dpot, val); | |
119 | } | |
120 | ||
121 | return -EINVAL; | |
122 | } | |
123 | ||
124 | static const struct iio_info dpot_dac_info = { | |
125 | .read_raw = dpot_dac_read_raw, | |
126 | .read_avail = dpot_dac_read_avail, | |
127 | .write_raw = dpot_dac_write_raw, | |
7fde1484 PR |
128 | }; |
129 | ||
130 | static int dpot_dac_channel_max_ohms(struct iio_dev *indio_dev) | |
131 | { | |
132 | struct device *dev = &indio_dev->dev; | |
133 | struct dpot_dac *dac = iio_priv(indio_dev); | |
134 | unsigned long long tmp; | |
135 | int ret; | |
136 | int val; | |
137 | int val2; | |
138 | int max; | |
139 | ||
140 | ret = iio_read_max_channel_raw(dac->dpot, &max); | |
141 | if (ret < 0) { | |
142 | dev_err(dev, "dpot does not indicate its raw maximum value\n"); | |
143 | return ret; | |
144 | } | |
145 | ||
146 | switch (iio_read_channel_scale(dac->dpot, &val, &val2)) { | |
147 | case IIO_VAL_INT: | |
148 | return max * val; | |
149 | case IIO_VAL_FRACTIONAL: | |
150 | tmp = (unsigned long long)max * val; | |
151 | do_div(tmp, val2); | |
152 | return tmp; | |
153 | case IIO_VAL_FRACTIONAL_LOG2: | |
154 | tmp = val * 1000000000LL * max >> val2; | |
155 | do_div(tmp, 1000000000LL); | |
156 | return tmp; | |
157 | default: | |
158 | dev_err(dev, "dpot has a scale that is too weird\n"); | |
159 | } | |
160 | ||
161 | return -EINVAL; | |
162 | } | |
163 | ||
164 | static int dpot_dac_probe(struct platform_device *pdev) | |
165 | { | |
166 | struct device *dev = &pdev->dev; | |
167 | struct iio_dev *indio_dev; | |
168 | struct dpot_dac *dac; | |
169 | enum iio_chan_type type; | |
170 | int ret; | |
171 | ||
172 | indio_dev = devm_iio_device_alloc(dev, sizeof(*dac)); | |
173 | if (!indio_dev) | |
174 | return -ENOMEM; | |
175 | ||
176 | platform_set_drvdata(pdev, indio_dev); | |
177 | dac = iio_priv(indio_dev); | |
178 | ||
179 | indio_dev->name = dev_name(dev); | |
180 | indio_dev->dev.parent = dev; | |
181 | indio_dev->info = &dpot_dac_info; | |
182 | indio_dev->modes = INDIO_DIRECT_MODE; | |
183 | indio_dev->channels = &dpot_dac_iio_channel; | |
184 | indio_dev->num_channels = 1; | |
185 | ||
186 | dac->vref = devm_regulator_get(dev, "vref"); | |
187 | if (IS_ERR(dac->vref)) { | |
188 | if (PTR_ERR(dac->vref) != -EPROBE_DEFER) | |
189 | dev_err(&pdev->dev, "failed to get vref regulator\n"); | |
190 | return PTR_ERR(dac->vref); | |
191 | } | |
192 | ||
193 | dac->dpot = devm_iio_channel_get(dev, "dpot"); | |
194 | if (IS_ERR(dac->dpot)) { | |
195 | if (PTR_ERR(dac->dpot) != -EPROBE_DEFER) | |
196 | dev_err(dev, "failed to get dpot input channel\n"); | |
197 | return PTR_ERR(dac->dpot); | |
198 | } | |
199 | ||
200 | ret = iio_get_channel_type(dac->dpot, &type); | |
201 | if (ret < 0) | |
202 | return ret; | |
203 | ||
204 | if (type != IIO_RESISTANCE) { | |
205 | dev_err(dev, "dpot is of the wrong type\n"); | |
206 | return -EINVAL; | |
207 | } | |
208 | ||
209 | ret = dpot_dac_channel_max_ohms(indio_dev); | |
210 | if (ret < 0) | |
211 | return ret; | |
212 | dac->max_ohms = ret; | |
213 | ||
214 | ret = regulator_enable(dac->vref); | |
215 | if (ret) { | |
216 | dev_err(dev, "failed to enable the vref regulator\n"); | |
217 | return ret; | |
218 | } | |
219 | ||
220 | ret = iio_device_register(indio_dev); | |
221 | if (ret) { | |
222 | dev_err(dev, "failed to register iio device\n"); | |
223 | goto disable_reg; | |
224 | } | |
225 | ||
226 | return 0; | |
227 | ||
228 | disable_reg: | |
229 | regulator_disable(dac->vref); | |
230 | return ret; | |
231 | } | |
232 | ||
233 | static int dpot_dac_remove(struct platform_device *pdev) | |
234 | { | |
235 | struct iio_dev *indio_dev = platform_get_drvdata(pdev); | |
236 | struct dpot_dac *dac = iio_priv(indio_dev); | |
237 | ||
238 | iio_device_unregister(indio_dev); | |
239 | regulator_disable(dac->vref); | |
240 | ||
241 | return 0; | |
242 | } | |
243 | ||
244 | static const struct of_device_id dpot_dac_match[] = { | |
245 | { .compatible = "dpot-dac" }, | |
246 | { /* sentinel */ } | |
247 | }; | |
248 | MODULE_DEVICE_TABLE(of, dpot_dac_match); | |
249 | ||
250 | static struct platform_driver dpot_dac_driver = { | |
251 | .probe = dpot_dac_probe, | |
252 | .remove = dpot_dac_remove, | |
253 | .driver = { | |
254 | .name = "iio-dpot-dac", | |
255 | .of_match_table = dpot_dac_match, | |
256 | }, | |
257 | }; | |
258 | module_platform_driver(dpot_dac_driver); | |
259 | ||
260 | MODULE_DESCRIPTION("DAC emulation driver using a digital potentiometer"); | |
261 | MODULE_AUTHOR("Peter Rosin <[email protected]>"); | |
262 | MODULE_LICENSE("GPL v2"); |