Commit | Line | Data |
---|---|---|
9bf85fbc TM |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2019 Nuvoton Technology corporation. | |
3 | ||
4 | #include <linux/clk.h> | |
5 | #include <linux/device.h> | |
6 | #include <linux/mfd/syscon.h> | |
7 | #include <linux/io.h> | |
8 | #include <linux/iio/iio.h> | |
9 | #include <linux/interrupt.h> | |
10 | #include <linux/kernel.h> | |
074d68c1 | 11 | #include <linux/mod_devicetable.h> |
9bf85fbc TM |
12 | #include <linux/module.h> |
13 | #include <linux/platform_device.h> | |
3ccb2524 | 14 | #include <linux/property.h> |
9bf85fbc TM |
15 | #include <linux/regmap.h> |
16 | #include <linux/regulator/consumer.h> | |
17 | #include <linux/spinlock.h> | |
18 | #include <linux/uaccess.h> | |
db6bcb8c | 19 | #include <linux/reset.h> |
9bf85fbc | 20 | |
3ccb2524 TM |
21 | struct npcm_adc_info { |
22 | u32 data_mask; | |
23 | u32 internal_vref; | |
24 | u32 res_bits; | |
25 | }; | |
26 | ||
9bf85fbc TM |
27 | struct npcm_adc { |
28 | bool int_status; | |
29 | u32 adc_sample_hz; | |
30 | struct device *dev; | |
31 | void __iomem *regs; | |
32 | struct clk *adc_clk; | |
33 | wait_queue_head_t wq; | |
34 | struct regulator *vref; | |
db6bcb8c | 35 | struct reset_control *reset; |
08dfc6f8 SC |
36 | /* |
37 | * Lock to protect the device state during a potential concurrent | |
38 | * read access from userspace. Reading a raw value requires a sequence | |
39 | * of register writes, then a wait for a event and finally a register | |
40 | * read, during which userspace could issue another read request. | |
41 | * This lock protects a read access from ocurring before another one | |
42 | * has finished. | |
43 | */ | |
44 | struct mutex lock; | |
3ccb2524 | 45 | const struct npcm_adc_info *data; |
9bf85fbc TM |
46 | }; |
47 | ||
9bf85fbc TM |
48 | /* ADC registers */ |
49 | #define NPCM_ADCCON 0x00 | |
50 | #define NPCM_ADCDATA 0x04 | |
51 | ||
52 | /* ADCCON Register Bits */ | |
53 | #define NPCM_ADCCON_ADC_INT_EN BIT(21) | |
54 | #define NPCM_ADCCON_REFSEL BIT(19) | |
55 | #define NPCM_ADCCON_ADC_INT_ST BIT(18) | |
56 | #define NPCM_ADCCON_ADC_EN BIT(17) | |
57 | #define NPCM_ADCCON_ADC_RST BIT(16) | |
58 | #define NPCM_ADCCON_ADC_CONV BIT(13) | |
59 | ||
60 | #define NPCM_ADCCON_CH_MASK GENMASK(27, 24) | |
61 | #define NPCM_ADCCON_CH(x) ((x) << 24) | |
62 | #define NPCM_ADCCON_DIV_SHIFT 1 | |
63 | #define NPCM_ADCCON_DIV_MASK GENMASK(8, 1) | |
9bf85fbc TM |
64 | |
65 | #define NPCM_ADC_ENABLE (NPCM_ADCCON_ADC_EN | NPCM_ADCCON_ADC_INT_EN) | |
66 | ||
67 | /* ADC General Definition */ | |
3ccb2524 TM |
68 | static const struct npcm_adc_info npxm7xx_adc_info = { |
69 | .data_mask = GENMASK(9, 0), | |
70 | .internal_vref = 2048, | |
71 | .res_bits = 10, | |
72 | }; | |
73 | ||
74 | static const struct npcm_adc_info npxm8xx_adc_info = { | |
75 | .data_mask = GENMASK(11, 0), | |
76 | .internal_vref = 1229, | |
77 | .res_bits = 12, | |
78 | }; | |
9bf85fbc TM |
79 | |
80 | #define NPCM_ADC_CHAN(ch) { \ | |
81 | .type = IIO_VOLTAGE, \ | |
82 | .indexed = 1, \ | |
83 | .channel = ch, \ | |
84 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ | |
85 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ | |
86 | BIT(IIO_CHAN_INFO_SAMP_FREQ), \ | |
87 | } | |
88 | ||
89 | static const struct iio_chan_spec npcm_adc_iio_channels[] = { | |
90 | NPCM_ADC_CHAN(0), | |
91 | NPCM_ADC_CHAN(1), | |
92 | NPCM_ADC_CHAN(2), | |
93 | NPCM_ADC_CHAN(3), | |
94 | NPCM_ADC_CHAN(4), | |
95 | NPCM_ADC_CHAN(5), | |
96 | NPCM_ADC_CHAN(6), | |
97 | NPCM_ADC_CHAN(7), | |
98 | }; | |
99 | ||
100 | static irqreturn_t npcm_adc_isr(int irq, void *data) | |
101 | { | |
102 | u32 regtemp; | |
103 | struct iio_dev *indio_dev = data; | |
104 | struct npcm_adc *info = iio_priv(indio_dev); | |
105 | ||
106 | regtemp = ioread32(info->regs + NPCM_ADCCON); | |
107 | if (regtemp & NPCM_ADCCON_ADC_INT_ST) { | |
108 | iowrite32(regtemp, info->regs + NPCM_ADCCON); | |
109 | wake_up_interruptible(&info->wq); | |
110 | info->int_status = true; | |
111 | } | |
112 | ||
113 | return IRQ_HANDLED; | |
114 | } | |
115 | ||
116 | static int npcm_adc_read(struct npcm_adc *info, int *val, u8 channel) | |
117 | { | |
118 | int ret; | |
119 | u32 regtemp; | |
120 | ||
121 | /* Select ADC channel */ | |
122 | regtemp = ioread32(info->regs + NPCM_ADCCON); | |
123 | regtemp &= ~NPCM_ADCCON_CH_MASK; | |
124 | info->int_status = false; | |
125 | iowrite32(regtemp | NPCM_ADCCON_CH(channel) | | |
126 | NPCM_ADCCON_ADC_CONV, info->regs + NPCM_ADCCON); | |
127 | ||
128 | ret = wait_event_interruptible_timeout(info->wq, info->int_status, | |
129 | msecs_to_jiffies(10)); | |
130 | if (ret == 0) { | |
131 | regtemp = ioread32(info->regs + NPCM_ADCCON); | |
db6bcb8c | 132 | if (regtemp & NPCM_ADCCON_ADC_CONV) { |
9bf85fbc | 133 | /* if conversion failed - reset ADC module */ |
db6bcb8c | 134 | reset_control_assert(info->reset); |
9bf85fbc | 135 | msleep(100); |
db6bcb8c | 136 | reset_control_deassert(info->reset); |
9bf85fbc TM |
137 | msleep(100); |
138 | ||
139 | /* Enable ADC and start conversion module */ | |
140 | iowrite32(NPCM_ADC_ENABLE | NPCM_ADCCON_ADC_CONV, | |
141 | info->regs + NPCM_ADCCON); | |
142 | dev_err(info->dev, "RESET ADC Complete\n"); | |
143 | } | |
144 | return -ETIMEDOUT; | |
145 | } | |
146 | if (ret < 0) | |
147 | return ret; | |
148 | ||
3ccb2524 TM |
149 | *val = ioread32(info->regs + NPCM_ADCDATA); |
150 | *val &= info->data->data_mask; | |
9bf85fbc TM |
151 | |
152 | return 0; | |
153 | } | |
154 | ||
155 | static int npcm_adc_read_raw(struct iio_dev *indio_dev, | |
156 | struct iio_chan_spec const *chan, int *val, | |
157 | int *val2, long mask) | |
158 | { | |
159 | int ret; | |
160 | int vref_uv; | |
161 | struct npcm_adc *info = iio_priv(indio_dev); | |
162 | ||
163 | switch (mask) { | |
164 | case IIO_CHAN_INFO_RAW: | |
08dfc6f8 | 165 | mutex_lock(&info->lock); |
9bf85fbc | 166 | ret = npcm_adc_read(info, val, chan->channel); |
08dfc6f8 | 167 | mutex_unlock(&info->lock); |
9bf85fbc TM |
168 | if (ret) { |
169 | dev_err(info->dev, "NPCM ADC read failed\n"); | |
170 | return ret; | |
171 | } | |
172 | return IIO_VAL_INT; | |
173 | case IIO_CHAN_INFO_SCALE: | |
4e63ed6b | 174 | if (!IS_ERR(info->vref)) { |
9bf85fbc TM |
175 | vref_uv = regulator_get_voltage(info->vref); |
176 | *val = vref_uv / 1000; | |
177 | } else { | |
3ccb2524 | 178 | *val = info->data->internal_vref; |
9bf85fbc | 179 | } |
3ccb2524 | 180 | *val2 = info->data->res_bits; |
9bf85fbc TM |
181 | return IIO_VAL_FRACTIONAL_LOG2; |
182 | case IIO_CHAN_INFO_SAMP_FREQ: | |
183 | *val = info->adc_sample_hz; | |
184 | return IIO_VAL_INT; | |
185 | default: | |
186 | return -EINVAL; | |
187 | } | |
188 | ||
189 | return 0; | |
190 | } | |
191 | ||
192 | static const struct iio_info npcm_adc_iio_info = { | |
193 | .read_raw = &npcm_adc_read_raw, | |
194 | }; | |
195 | ||
196 | static const struct of_device_id npcm_adc_match[] = { | |
3ccb2524 TM |
197 | { .compatible = "nuvoton,npcm750-adc", .data = &npxm7xx_adc_info}, |
198 | { .compatible = "nuvoton,npcm845-adc", .data = &npxm8xx_adc_info}, | |
9bf85fbc TM |
199 | { /* sentinel */ } |
200 | }; | |
201 | MODULE_DEVICE_TABLE(of, npcm_adc_match); | |
202 | ||
203 | static int npcm_adc_probe(struct platform_device *pdev) | |
204 | { | |
205 | int ret; | |
206 | int irq; | |
207 | u32 div; | |
208 | u32 reg_con; | |
9bf85fbc TM |
209 | struct npcm_adc *info; |
210 | struct iio_dev *indio_dev; | |
211 | struct device *dev = &pdev->dev; | |
9bf85fbc TM |
212 | |
213 | indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); | |
214 | if (!indio_dev) | |
215 | return -ENOMEM; | |
216 | info = iio_priv(indio_dev); | |
217 | ||
3ccb2524 TM |
218 | info->data = device_get_match_data(dev); |
219 | if (!info->data) | |
220 | return -EINVAL; | |
221 | ||
08dfc6f8 SC |
222 | mutex_init(&info->lock); |
223 | ||
9bf85fbc TM |
224 | info->dev = &pdev->dev; |
225 | ||
08cf48c7 | 226 | info->regs = devm_platform_ioremap_resource(pdev, 0); |
9bf85fbc TM |
227 | if (IS_ERR(info->regs)) |
228 | return PTR_ERR(info->regs); | |
229 | ||
db6bcb8c TM |
230 | info->reset = devm_reset_control_get(&pdev->dev, NULL); |
231 | if (IS_ERR(info->reset)) | |
232 | return PTR_ERR(info->reset); | |
233 | ||
9bf85fbc TM |
234 | info->adc_clk = devm_clk_get(&pdev->dev, NULL); |
235 | if (IS_ERR(info->adc_clk)) { | |
236 | dev_warn(&pdev->dev, "ADC clock failed: can't read clk\n"); | |
237 | return PTR_ERR(info->adc_clk); | |
238 | } | |
239 | ||
240 | /* calculate ADC clock sample rate */ | |
241 | reg_con = ioread32(info->regs + NPCM_ADCCON); | |
242 | div = reg_con & NPCM_ADCCON_DIV_MASK; | |
243 | div = div >> NPCM_ADCCON_DIV_SHIFT; | |
244 | info->adc_sample_hz = clk_get_rate(info->adc_clk) / ((div + 1) * 2); | |
245 | ||
9bf85fbc TM |
246 | irq = platform_get_irq(pdev, 0); |
247 | if (irq <= 0) { | |
9bf85fbc TM |
248 | ret = -EINVAL; |
249 | goto err_disable_clk; | |
250 | } | |
251 | ||
252 | ret = devm_request_irq(&pdev->dev, irq, npcm_adc_isr, 0, | |
253 | "NPCM_ADC", indio_dev); | |
254 | if (ret < 0) { | |
255 | dev_err(dev, "failed requesting interrupt\n"); | |
256 | goto err_disable_clk; | |
257 | } | |
258 | ||
259 | reg_con = ioread32(info->regs + NPCM_ADCCON); | |
260 | info->vref = devm_regulator_get_optional(&pdev->dev, "vref"); | |
261 | if (!IS_ERR(info->vref)) { | |
262 | ret = regulator_enable(info->vref); | |
263 | if (ret) { | |
264 | dev_err(&pdev->dev, "Can't enable ADC reference voltage\n"); | |
265 | goto err_disable_clk; | |
266 | } | |
267 | ||
268 | iowrite32(reg_con & ~NPCM_ADCCON_REFSEL, | |
269 | info->regs + NPCM_ADCCON); | |
270 | } else { | |
271 | /* | |
272 | * Any error which is not ENODEV indicates the regulator | |
273 | * has been specified and so is a failure case. | |
274 | */ | |
275 | if (PTR_ERR(info->vref) != -ENODEV) { | |
276 | ret = PTR_ERR(info->vref); | |
277 | goto err_disable_clk; | |
278 | } | |
279 | ||
280 | /* Use internal reference */ | |
281 | iowrite32(reg_con | NPCM_ADCCON_REFSEL, | |
282 | info->regs + NPCM_ADCCON); | |
283 | } | |
284 | ||
285 | init_waitqueue_head(&info->wq); | |
286 | ||
287 | reg_con = ioread32(info->regs + NPCM_ADCCON); | |
288 | reg_con |= NPCM_ADC_ENABLE; | |
289 | ||
290 | /* Enable the ADC Module */ | |
291 | iowrite32(reg_con, info->regs + NPCM_ADCCON); | |
292 | ||
293 | /* Start ADC conversion */ | |
294 | iowrite32(reg_con | NPCM_ADCCON_ADC_CONV, info->regs + NPCM_ADCCON); | |
295 | ||
296 | platform_set_drvdata(pdev, indio_dev); | |
297 | indio_dev->name = dev_name(&pdev->dev); | |
9bf85fbc TM |
298 | indio_dev->info = &npcm_adc_iio_info; |
299 | indio_dev->modes = INDIO_DIRECT_MODE; | |
300 | indio_dev->channels = npcm_adc_iio_channels; | |
301 | indio_dev->num_channels = ARRAY_SIZE(npcm_adc_iio_channels); | |
302 | ||
303 | ret = iio_device_register(indio_dev); | |
304 | if (ret) { | |
305 | dev_err(&pdev->dev, "Couldn't register the device.\n"); | |
306 | goto err_iio_register; | |
307 | } | |
308 | ||
309 | pr_info("NPCM ADC driver probed\n"); | |
310 | ||
311 | return 0; | |
312 | ||
313 | err_iio_register: | |
314 | iowrite32(reg_con & ~NPCM_ADCCON_ADC_EN, info->regs + NPCM_ADCCON); | |
315 | if (!IS_ERR(info->vref)) | |
316 | regulator_disable(info->vref); | |
317 | err_disable_clk: | |
318 | clk_disable_unprepare(info->adc_clk); | |
319 | ||
320 | return ret; | |
321 | } | |
322 | ||
323 | static int npcm_adc_remove(struct platform_device *pdev) | |
324 | { | |
325 | struct iio_dev *indio_dev = platform_get_drvdata(pdev); | |
326 | struct npcm_adc *info = iio_priv(indio_dev); | |
327 | u32 regtemp; | |
328 | ||
329 | iio_device_unregister(indio_dev); | |
330 | ||
331 | regtemp = ioread32(info->regs + NPCM_ADCCON); | |
332 | iowrite32(regtemp & ~NPCM_ADCCON_ADC_EN, info->regs + NPCM_ADCCON); | |
333 | if (!IS_ERR(info->vref)) | |
334 | regulator_disable(info->vref); | |
335 | clk_disable_unprepare(info->adc_clk); | |
336 | ||
337 | return 0; | |
338 | } | |
339 | ||
340 | static struct platform_driver npcm_adc_driver = { | |
341 | .probe = npcm_adc_probe, | |
342 | .remove = npcm_adc_remove, | |
343 | .driver = { | |
344 | .name = "npcm_adc", | |
345 | .of_match_table = npcm_adc_match, | |
346 | }, | |
347 | }; | |
348 | ||
349 | module_platform_driver(npcm_adc_driver); | |
350 | ||
351 | MODULE_DESCRIPTION("Nuvoton NPCM ADC Driver"); | |
352 | MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>"); | |
353 | MODULE_LICENSE("GPL v2"); |