Commit | Line | Data |
---|---|---|
6df2e98c MP |
1 | /* |
2 | * Copyright (C) 2014-2015 Pengutronix, Markus Pargmann <mpa@pengutronix.de> | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify it under | |
5 | * the terms of the GNU General Public License version 2 as published by the | |
6 | * Free Software Foundation. | |
7 | * | |
8 | * This is the driver for the imx25 GCQ (Generic Conversion Queue) | |
9 | * connected to the imx25 ADC. | |
10 | */ | |
11 | ||
12 | #include <dt-bindings/iio/adc/fsl-imx25-gcq.h> | |
13 | #include <linux/clk.h> | |
14 | #include <linux/iio/iio.h> | |
15 | #include <linux/interrupt.h> | |
16 | #include <linux/mfd/imx25-tsadc.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/of.h> | |
19 | #include <linux/platform_device.h> | |
20 | #include <linux/regmap.h> | |
21 | #include <linux/regulator/consumer.h> | |
22 | ||
23 | #define MX25_GCQ_TIMEOUT (msecs_to_jiffies(2000)) | |
24 | ||
25 | static const char * const driver_name = "mx25-gcq"; | |
26 | ||
27 | enum mx25_gcq_cfgs { | |
28 | MX25_CFG_XP = 0, | |
29 | MX25_CFG_YP, | |
30 | MX25_CFG_XN, | |
31 | MX25_CFG_YN, | |
32 | MX25_CFG_WIPER, | |
33 | MX25_CFG_INAUX0, | |
34 | MX25_CFG_INAUX1, | |
35 | MX25_CFG_INAUX2, | |
36 | MX25_NUM_CFGS, | |
37 | }; | |
38 | ||
39 | struct mx25_gcq_priv { | |
40 | struct regmap *regs; | |
41 | struct completion completed; | |
42 | struct clk *clk; | |
43 | int irq; | |
44 | struct regulator *vref[4]; | |
45 | u32 channel_vref_mv[MX25_NUM_CFGS]; | |
46 | }; | |
47 | ||
48 | #define MX25_CQG_CHAN(chan, id) {\ | |
49 | .type = IIO_VOLTAGE,\ | |
50 | .indexed = 1,\ | |
51 | .channel = chan,\ | |
52 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ | |
53 | BIT(IIO_CHAN_INFO_SCALE),\ | |
54 | .datasheet_name = id,\ | |
55 | } | |
56 | ||
57 | static const struct iio_chan_spec mx25_gcq_channels[MX25_NUM_CFGS] = { | |
58 | MX25_CQG_CHAN(MX25_CFG_XP, "xp"), | |
59 | MX25_CQG_CHAN(MX25_CFG_YP, "yp"), | |
60 | MX25_CQG_CHAN(MX25_CFG_XN, "xn"), | |
61 | MX25_CQG_CHAN(MX25_CFG_YN, "yn"), | |
62 | MX25_CQG_CHAN(MX25_CFG_WIPER, "wiper"), | |
63 | MX25_CQG_CHAN(MX25_CFG_INAUX0, "inaux0"), | |
64 | MX25_CQG_CHAN(MX25_CFG_INAUX1, "inaux1"), | |
65 | MX25_CQG_CHAN(MX25_CFG_INAUX2, "inaux2"), | |
66 | }; | |
67 | ||
68 | static const char * const mx25_gcq_refp_names[] = { | |
69 | [MX25_ADC_REFP_YP] = "yp", | |
70 | [MX25_ADC_REFP_XP] = "xp", | |
71 | [MX25_ADC_REFP_INT] = "int", | |
72 | [MX25_ADC_REFP_EXT] = "ext", | |
73 | }; | |
74 | ||
75 | static irqreturn_t mx25_gcq_irq(int irq, void *data) | |
76 | { | |
77 | struct mx25_gcq_priv *priv = data; | |
78 | u32 stats; | |
79 | ||
80 | regmap_read(priv->regs, MX25_ADCQ_SR, &stats); | |
81 | ||
82 | if (stats & MX25_ADCQ_SR_EOQ) { | |
83 | regmap_update_bits(priv->regs, MX25_ADCQ_MR, | |
84 | MX25_ADCQ_MR_EOQ_IRQ, MX25_ADCQ_MR_EOQ_IRQ); | |
85 | complete(&priv->completed); | |
86 | } | |
87 | ||
88 | /* Disable conversion queue run */ | |
89 | regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS, 0); | |
90 | ||
91 | /* Acknowledge all possible irqs */ | |
92 | regmap_write(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_FRR | | |
93 | MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR | | |
94 | MX25_ADCQ_SR_EOQ | MX25_ADCQ_SR_PD); | |
95 | ||
96 | return IRQ_HANDLED; | |
97 | } | |
98 | ||
99 | static int mx25_gcq_get_raw_value(struct device *dev, | |
100 | struct iio_chan_spec const *chan, | |
101 | struct mx25_gcq_priv *priv, | |
102 | int *val) | |
103 | { | |
104 | long timeout; | |
105 | u32 data; | |
106 | ||
107 | /* Setup the configuration we want to use */ | |
108 | regmap_write(priv->regs, MX25_ADCQ_ITEM_7_0, | |
109 | MX25_ADCQ_ITEM(0, chan->channel)); | |
110 | ||
111 | regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_EOQ_IRQ, 0); | |
112 | ||
113 | /* Trigger queue for one run */ | |
114 | regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS, | |
115 | MX25_ADCQ_CR_FQS); | |
116 | ||
117 | timeout = wait_for_completion_interruptible_timeout( | |
118 | &priv->completed, MX25_GCQ_TIMEOUT); | |
119 | if (timeout < 0) { | |
120 | dev_err(dev, "ADC wait for measurement failed\n"); | |
121 | return timeout; | |
122 | } else if (timeout == 0) { | |
123 | dev_err(dev, "ADC timed out\n"); | |
124 | return -ETIMEDOUT; | |
125 | } | |
126 | ||
127 | regmap_read(priv->regs, MX25_ADCQ_FIFO, &data); | |
128 | ||
129 | *val = MX25_ADCQ_FIFO_DATA(data); | |
130 | ||
131 | return IIO_VAL_INT; | |
132 | } | |
133 | ||
134 | static int mx25_gcq_read_raw(struct iio_dev *indio_dev, | |
135 | struct iio_chan_spec const *chan, int *val, | |
136 | int *val2, long mask) | |
137 | { | |
138 | struct mx25_gcq_priv *priv = iio_priv(indio_dev); | |
139 | int ret; | |
140 | ||
141 | switch (mask) { | |
142 | case IIO_CHAN_INFO_RAW: | |
143 | mutex_lock(&indio_dev->mlock); | |
144 | ret = mx25_gcq_get_raw_value(&indio_dev->dev, chan, priv, val); | |
145 | mutex_unlock(&indio_dev->mlock); | |
146 | return ret; | |
147 | ||
148 | case IIO_CHAN_INFO_SCALE: | |
149 | *val = priv->channel_vref_mv[chan->channel]; | |
150 | *val2 = 12; | |
151 | return IIO_VAL_FRACTIONAL_LOG2; | |
152 | ||
153 | default: | |
154 | return -EINVAL; | |
155 | } | |
156 | } | |
157 | ||
158 | static const struct iio_info mx25_gcq_iio_info = { | |
159 | .read_raw = mx25_gcq_read_raw, | |
160 | }; | |
161 | ||
162 | static const struct regmap_config mx25_gcq_regconfig = { | |
163 | .max_register = 0x5c, | |
164 | .reg_bits = 32, | |
165 | .val_bits = 32, | |
166 | .reg_stride = 4, | |
167 | }; | |
168 | ||
169 | static int mx25_gcq_setup_cfgs(struct platform_device *pdev, | |
170 | struct mx25_gcq_priv *priv) | |
171 | { | |
172 | struct device_node *np = pdev->dev.of_node; | |
173 | struct device_node *child; | |
174 | struct device *dev = &pdev->dev; | |
175 | unsigned int refp_used[4] = {}; | |
176 | int ret, i; | |
177 | ||
178 | /* | |
179 | * Setup all configurations registers with a default conversion | |
180 | * configuration for each input | |
181 | */ | |
182 | for (i = 0; i < MX25_NUM_CFGS; ++i) | |
183 | regmap_write(priv->regs, MX25_ADCQ_CFG(i), | |
184 | MX25_ADCQ_CFG_YPLL_OFF | | |
185 | MX25_ADCQ_CFG_XNUR_OFF | | |
186 | MX25_ADCQ_CFG_XPUL_OFF | | |
187 | MX25_ADCQ_CFG_REFP_INT | | |
188 | MX25_ADCQ_CFG_IN(i) | | |
189 | MX25_ADCQ_CFG_REFN_NGND2); | |
190 | ||
191 | /* | |
192 | * First get all regulators to store them in channel_vref_mv if | |
193 | * necessary. Later we use that information for proper IIO scale | |
194 | * information. | |
195 | */ | |
196 | priv->vref[MX25_ADC_REFP_INT] = NULL; | |
197 | priv->vref[MX25_ADC_REFP_EXT] = | |
198 | devm_regulator_get_optional(&pdev->dev, "vref-ext"); | |
199 | priv->vref[MX25_ADC_REFP_XP] = | |
200 | devm_regulator_get_optional(&pdev->dev, "vref-xp"); | |
201 | priv->vref[MX25_ADC_REFP_YP] = | |
202 | devm_regulator_get_optional(&pdev->dev, "vref-yp"); | |
203 | ||
204 | for_each_child_of_node(np, child) { | |
205 | u32 reg; | |
206 | u32 refp = MX25_ADCQ_CFG_REFP_INT; | |
207 | u32 refn = MX25_ADCQ_CFG_REFN_NGND2; | |
208 | ||
209 | ret = of_property_read_u32(child, "reg", ®); | |
210 | if (ret) { | |
211 | dev_err(dev, "Failed to get reg property\n"); | |
212 | return ret; | |
213 | } | |
214 | ||
215 | if (reg >= MX25_NUM_CFGS) { | |
216 | dev_err(dev, | |
217 | "reg value is greater than the number of available configuration registers\n"); | |
218 | return -EINVAL; | |
219 | } | |
220 | ||
221 | of_property_read_u32(child, "fsl,adc-refp", &refp); | |
222 | of_property_read_u32(child, "fsl,adc-refn", &refn); | |
223 | ||
224 | switch (refp) { | |
225 | case MX25_ADC_REFP_EXT: | |
226 | case MX25_ADC_REFP_XP: | |
227 | case MX25_ADC_REFP_YP: | |
228 | if (IS_ERR(priv->vref[refp])) { | |
229 | dev_err(dev, "Error, trying to use external voltage reference without a vref-%s regulator.", | |
230 | mx25_gcq_refp_names[refp]); | |
231 | return PTR_ERR(priv->vref[refp]); | |
232 | } | |
233 | priv->channel_vref_mv[reg] = | |
234 | regulator_get_voltage(priv->vref[refp]); | |
235 | /* Conversion from uV to mV */ | |
07086775 | 236 | priv->channel_vref_mv[reg] /= 1000; |
6df2e98c MP |
237 | break; |
238 | case MX25_ADC_REFP_INT: | |
239 | priv->channel_vref_mv[reg] = 2500; | |
240 | break; | |
241 | default: | |
242 | dev_err(dev, "Invalid positive reference %d\n", refp); | |
243 | return -EINVAL; | |
244 | } | |
245 | ||
246 | ++refp_used[refp]; | |
247 | ||
248 | /* | |
249 | * Shift the read values to the correct positions within the | |
250 | * register. | |
251 | */ | |
252 | refp = MX25_ADCQ_CFG_REFP(refp); | |
253 | refn = MX25_ADCQ_CFG_REFN(refn); | |
254 | ||
255 | if ((refp & MX25_ADCQ_CFG_REFP_MASK) != refp) { | |
256 | dev_err(dev, "Invalid fsl,adc-refp property value\n"); | |
257 | return -EINVAL; | |
258 | } | |
259 | if ((refn & MX25_ADCQ_CFG_REFN_MASK) != refn) { | |
260 | dev_err(dev, "Invalid fsl,adc-refn property value\n"); | |
261 | return -EINVAL; | |
262 | } | |
263 | ||
264 | regmap_update_bits(priv->regs, MX25_ADCQ_CFG(reg), | |
265 | MX25_ADCQ_CFG_REFP_MASK | | |
266 | MX25_ADCQ_CFG_REFN_MASK, | |
267 | refp | refn); | |
268 | } | |
269 | regmap_update_bits(priv->regs, MX25_ADCQ_CR, | |
270 | MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST, | |
271 | MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST); | |
272 | ||
273 | regmap_write(priv->regs, MX25_ADCQ_CR, | |
274 | MX25_ADCQ_CR_PDMSK | MX25_ADCQ_CR_QSM_FQS); | |
275 | ||
276 | /* Remove unused regulators */ | |
277 | for (i = 0; i != 4; ++i) { | |
278 | if (!refp_used[i]) { | |
279 | if (!IS_ERR_OR_NULL(priv->vref[i])) | |
280 | devm_regulator_put(priv->vref[i]); | |
281 | priv->vref[i] = NULL; | |
282 | } | |
283 | } | |
284 | ||
285 | return 0; | |
286 | } | |
287 | ||
288 | static int mx25_gcq_probe(struct platform_device *pdev) | |
289 | { | |
290 | struct iio_dev *indio_dev; | |
291 | struct mx25_gcq_priv *priv; | |
292 | struct mx25_tsadc *tsadc = dev_get_drvdata(pdev->dev.parent); | |
293 | struct device *dev = &pdev->dev; | |
294 | struct resource *res; | |
295 | void __iomem *mem; | |
296 | int ret; | |
297 | int i; | |
298 | ||
299 | indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv)); | |
300 | if (!indio_dev) | |
301 | return -ENOMEM; | |
302 | ||
303 | priv = iio_priv(indio_dev); | |
304 | ||
305 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
306 | mem = devm_ioremap_resource(dev, res); | |
307 | if (IS_ERR(mem)) | |
308 | return PTR_ERR(mem); | |
309 | ||
310 | priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_gcq_regconfig); | |
311 | if (IS_ERR(priv->regs)) { | |
312 | dev_err(dev, "Failed to initialize regmap\n"); | |
313 | return PTR_ERR(priv->regs); | |
314 | } | |
315 | ||
316 | init_completion(&priv->completed); | |
317 | ||
318 | ret = mx25_gcq_setup_cfgs(pdev, priv); | |
319 | if (ret) | |
320 | return ret; | |
321 | ||
322 | for (i = 0; i != 4; ++i) { | |
323 | if (!priv->vref[i]) | |
324 | continue; | |
325 | ||
326 | ret = regulator_enable(priv->vref[i]); | |
327 | if (ret) | |
328 | goto err_regulator_disable; | |
329 | } | |
330 | ||
331 | priv->clk = tsadc->clk; | |
332 | ret = clk_prepare_enable(priv->clk); | |
333 | if (ret) { | |
334 | dev_err(dev, "Failed to enable clock\n"); | |
335 | goto err_vref_disable; | |
336 | } | |
337 | ||
338 | priv->irq = platform_get_irq(pdev, 0); | |
339 | if (priv->irq <= 0) { | |
340 | dev_err(dev, "Failed to get IRQ\n"); | |
341 | ret = priv->irq; | |
342 | if (!ret) | |
343 | ret = -ENXIO; | |
344 | goto err_clk_unprepare; | |
345 | } | |
346 | ||
347 | ret = request_irq(priv->irq, mx25_gcq_irq, 0, pdev->name, priv); | |
348 | if (ret) { | |
349 | dev_err(dev, "Failed requesting IRQ\n"); | |
350 | goto err_clk_unprepare; | |
351 | } | |
352 | ||
353 | indio_dev->dev.parent = &pdev->dev; | |
354 | indio_dev->channels = mx25_gcq_channels; | |
355 | indio_dev->num_channels = ARRAY_SIZE(mx25_gcq_channels); | |
356 | indio_dev->info = &mx25_gcq_iio_info; | |
357 | indio_dev->name = driver_name; | |
358 | ||
359 | ret = iio_device_register(indio_dev); | |
360 | if (ret) { | |
361 | dev_err(dev, "Failed to register iio device\n"); | |
362 | goto err_irq_free; | |
363 | } | |
364 | ||
365 | platform_set_drvdata(pdev, indio_dev); | |
366 | ||
367 | return 0; | |
368 | ||
369 | err_irq_free: | |
370 | free_irq(priv->irq, priv); | |
371 | err_clk_unprepare: | |
372 | clk_disable_unprepare(priv->clk); | |
373 | err_vref_disable: | |
374 | i = 4; | |
375 | err_regulator_disable: | |
376 | for (; i-- > 0;) { | |
377 | if (priv->vref[i]) | |
378 | regulator_disable(priv->vref[i]); | |
379 | } | |
380 | return ret; | |
381 | } | |
382 | ||
383 | static int mx25_gcq_remove(struct platform_device *pdev) | |
384 | { | |
385 | struct iio_dev *indio_dev = platform_get_drvdata(pdev); | |
386 | struct mx25_gcq_priv *priv = iio_priv(indio_dev); | |
387 | int i; | |
388 | ||
389 | iio_device_unregister(indio_dev); | |
390 | free_irq(priv->irq, priv); | |
391 | clk_disable_unprepare(priv->clk); | |
392 | for (i = 4; i-- > 0;) { | |
393 | if (priv->vref[i]) | |
394 | regulator_disable(priv->vref[i]); | |
395 | } | |
396 | ||
397 | return 0; | |
398 | } | |
399 | ||
400 | static const struct of_device_id mx25_gcq_ids[] = { | |
401 | { .compatible = "fsl,imx25-gcq", }, | |
402 | { /* Sentinel */ } | |
403 | }; | |
404 | ||
405 | static struct platform_driver mx25_gcq_driver = { | |
406 | .driver = { | |
407 | .name = "mx25-gcq", | |
408 | .of_match_table = mx25_gcq_ids, | |
409 | }, | |
410 | .probe = mx25_gcq_probe, | |
411 | .remove = mx25_gcq_remove, | |
412 | }; | |
413 | module_platform_driver(mx25_gcq_driver); | |
414 | ||
415 | MODULE_DESCRIPTION("ADC driver for Freescale mx25"); | |
416 | MODULE_AUTHOR("Markus Pargmann <mpa@pengutronix.de>"); | |
417 | MODULE_LICENSE("GPL v2"); |