Commit | Line | Data |
---|---|---|
a7118662 VP |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * ADC driver for Basin Cove PMIC | |
4 | * | |
5 | * Copyright (C) 2012 Intel Corporation | |
6 | * Author: Bin Yang <bin.yang@intel.com> | |
7 | * | |
8 | * Rewritten for upstream by: | |
9 | * Vincent Pelletier <plr.vincent@gmail.com> | |
10 | * Andy Shevchenko <andriy.shevchenko@linux.intel.com> | |
11 | */ | |
12 | ||
13 | #include <linux/bitops.h> | |
14 | #include <linux/completion.h> | |
15 | #include <linux/interrupt.h> | |
16 | #include <linux/mfd/intel_soc_pmic.h> | |
17 | #include <linux/mfd/intel_soc_pmic_mrfld.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/mutex.h> | |
20 | #include <linux/platform_device.h> | |
21 | #include <linux/regmap.h> | |
22 | ||
23 | #include <linux/iio/driver.h> | |
24 | #include <linux/iio/iio.h> | |
25 | #include <linux/iio/machine.h> | |
26 | ||
27 | #include <asm/unaligned.h> | |
28 | ||
29 | #define BCOVE_GPADCREQ 0xDC | |
30 | #define BCOVE_GPADCREQ_BUSY BIT(0) | |
31 | #define BCOVE_GPADCREQ_IRQEN BIT(1) | |
32 | ||
33 | #define BCOVE_ADCIRQ_ALL ( \ | |
34 | BCOVE_ADCIRQ_BATTEMP | \ | |
35 | BCOVE_ADCIRQ_SYSTEMP | \ | |
36 | BCOVE_ADCIRQ_BATTID | \ | |
37 | BCOVE_ADCIRQ_VIBATT | \ | |
38 | BCOVE_ADCIRQ_CCTICK) | |
39 | ||
40 | #define BCOVE_ADC_TIMEOUT msecs_to_jiffies(1000) | |
41 | ||
42 | static const u8 mrfld_adc_requests[] = { | |
43 | BCOVE_ADCIRQ_VIBATT, | |
44 | BCOVE_ADCIRQ_BATTID, | |
45 | BCOVE_ADCIRQ_VIBATT, | |
46 | BCOVE_ADCIRQ_SYSTEMP, | |
47 | BCOVE_ADCIRQ_BATTEMP, | |
48 | BCOVE_ADCIRQ_BATTEMP, | |
49 | BCOVE_ADCIRQ_SYSTEMP, | |
50 | BCOVE_ADCIRQ_SYSTEMP, | |
51 | BCOVE_ADCIRQ_SYSTEMP, | |
52 | }; | |
53 | ||
54 | struct mrfld_adc { | |
55 | struct regmap *regmap; | |
56 | struct completion completion; | |
57 | /* Lock to protect the IPC transfers */ | |
58 | struct mutex lock; | |
59 | }; | |
60 | ||
61 | static irqreturn_t mrfld_adc_thread_isr(int irq, void *data) | |
62 | { | |
63 | struct iio_dev *indio_dev = data; | |
64 | struct mrfld_adc *adc = iio_priv(indio_dev); | |
65 | ||
66 | complete(&adc->completion); | |
67 | return IRQ_HANDLED; | |
68 | } | |
69 | ||
70 | static int mrfld_adc_single_conv(struct iio_dev *indio_dev, | |
71 | struct iio_chan_spec const *chan, | |
72 | int *result) | |
73 | { | |
74 | struct mrfld_adc *adc = iio_priv(indio_dev); | |
75 | struct regmap *regmap = adc->regmap; | |
76 | unsigned int req; | |
77 | long timeout; | |
78 | u8 buf[2]; | |
79 | int ret; | |
80 | ||
81 | reinit_completion(&adc->completion); | |
82 | ||
83 | regmap_update_bits(regmap, BCOVE_MADCIRQ, BCOVE_ADCIRQ_ALL, 0); | |
84 | regmap_update_bits(regmap, BCOVE_MIRQLVL1, BCOVE_LVL1_ADC, 0); | |
85 | ||
86 | ret = regmap_read_poll_timeout(regmap, BCOVE_GPADCREQ, req, | |
87 | !(req & BCOVE_GPADCREQ_BUSY), | |
88 | 2000, 1000000); | |
89 | if (ret) | |
90 | goto done; | |
91 | ||
92 | req = mrfld_adc_requests[chan->channel]; | |
93 | ret = regmap_write(regmap, BCOVE_GPADCREQ, BCOVE_GPADCREQ_IRQEN | req); | |
94 | if (ret) | |
95 | goto done; | |
96 | ||
97 | timeout = wait_for_completion_interruptible_timeout(&adc->completion, | |
98 | BCOVE_ADC_TIMEOUT); | |
99 | if (timeout < 0) { | |
100 | ret = timeout; | |
101 | goto done; | |
102 | } | |
103 | if (timeout == 0) { | |
104 | ret = -ETIMEDOUT; | |
105 | goto done; | |
106 | } | |
107 | ||
108 | ret = regmap_bulk_read(regmap, chan->address, buf, 2); | |
109 | if (ret) | |
110 | goto done; | |
111 | ||
112 | *result = get_unaligned_be16(buf); | |
113 | ret = IIO_VAL_INT; | |
114 | ||
115 | done: | |
116 | regmap_update_bits(regmap, BCOVE_MIRQLVL1, BCOVE_LVL1_ADC, 0xff); | |
117 | regmap_update_bits(regmap, BCOVE_MADCIRQ, BCOVE_ADCIRQ_ALL, 0xff); | |
118 | ||
119 | return ret; | |
120 | } | |
121 | ||
122 | static int mrfld_adc_read_raw(struct iio_dev *indio_dev, | |
123 | struct iio_chan_spec const *chan, | |
124 | int *val, int *val2, long mask) | |
125 | { | |
126 | struct mrfld_adc *adc = iio_priv(indio_dev); | |
127 | int ret; | |
128 | ||
129 | switch (mask) { | |
130 | case IIO_CHAN_INFO_RAW: | |
131 | mutex_lock(&adc->lock); | |
132 | ret = mrfld_adc_single_conv(indio_dev, chan, val); | |
133 | mutex_unlock(&adc->lock); | |
134 | return ret; | |
135 | default: | |
136 | return -EINVAL; | |
137 | } | |
138 | } | |
139 | ||
140 | static const struct iio_info mrfld_adc_iio_info = { | |
141 | .read_raw = &mrfld_adc_read_raw, | |
142 | }; | |
143 | ||
144 | #define BCOVE_ADC_CHANNEL(_type, _channel, _datasheet_name, _address) \ | |
145 | { \ | |
146 | .indexed = 1, \ | |
147 | .type = _type, \ | |
148 | .channel = _channel, \ | |
149 | .address = _address, \ | |
150 | .datasheet_name = _datasheet_name, \ | |
151 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ | |
152 | } | |
153 | ||
154 | static const struct iio_chan_spec mrfld_adc_channels[] = { | |
155 | BCOVE_ADC_CHANNEL(IIO_VOLTAGE, 0, "CH0", 0xE9), | |
156 | BCOVE_ADC_CHANNEL(IIO_RESISTANCE, 1, "CH1", 0xEB), | |
157 | BCOVE_ADC_CHANNEL(IIO_CURRENT, 2, "CH2", 0xED), | |
158 | BCOVE_ADC_CHANNEL(IIO_TEMP, 3, "CH3", 0xCC), | |
159 | BCOVE_ADC_CHANNEL(IIO_TEMP, 4, "CH4", 0xC8), | |
160 | BCOVE_ADC_CHANNEL(IIO_TEMP, 5, "CH5", 0xCA), | |
161 | BCOVE_ADC_CHANNEL(IIO_TEMP, 6, "CH6", 0xC2), | |
162 | BCOVE_ADC_CHANNEL(IIO_TEMP, 7, "CH7", 0xC4), | |
163 | BCOVE_ADC_CHANNEL(IIO_TEMP, 8, "CH8", 0xC6), | |
164 | }; | |
165 | ||
166 | static struct iio_map iio_maps[] = { | |
167 | IIO_MAP("CH0", "bcove-battery", "VBATRSLT"), | |
168 | IIO_MAP("CH1", "bcove-battery", "BATTID"), | |
169 | IIO_MAP("CH2", "bcove-battery", "IBATRSLT"), | |
170 | IIO_MAP("CH3", "bcove-temp", "PMICTEMP"), | |
171 | IIO_MAP("CH4", "bcove-temp", "BATTEMP0"), | |
172 | IIO_MAP("CH5", "bcove-temp", "BATTEMP1"), | |
173 | IIO_MAP("CH6", "bcove-temp", "SYSTEMP0"), | |
174 | IIO_MAP("CH7", "bcove-temp", "SYSTEMP1"), | |
175 | IIO_MAP("CH8", "bcove-temp", "SYSTEMP2"), | |
176 | {} | |
177 | }; | |
178 | ||
179 | static int mrfld_adc_probe(struct platform_device *pdev) | |
180 | { | |
181 | struct device *dev = &pdev->dev; | |
182 | struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent); | |
183 | struct iio_dev *indio_dev; | |
184 | struct mrfld_adc *adc; | |
185 | int irq; | |
186 | int ret; | |
187 | ||
1f310f77 | 188 | indio_dev = devm_iio_device_alloc(dev, sizeof(struct mrfld_adc)); |
a7118662 VP |
189 | if (!indio_dev) |
190 | return -ENOMEM; | |
191 | ||
192 | adc = iio_priv(indio_dev); | |
193 | ||
194 | mutex_init(&adc->lock); | |
195 | init_completion(&adc->completion); | |
196 | adc->regmap = pmic->regmap; | |
197 | ||
198 | irq = platform_get_irq(pdev, 0); | |
199 | if (irq < 0) | |
200 | return irq; | |
201 | ||
202 | ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_adc_thread_isr, | |
203 | IRQF_ONESHOT | IRQF_SHARED, pdev->name, | |
204 | indio_dev); | |
205 | if (ret) | |
206 | return ret; | |
207 | ||
208 | platform_set_drvdata(pdev, indio_dev); | |
209 | ||
210 | indio_dev->dev.parent = dev; | |
211 | indio_dev->name = pdev->name; | |
212 | ||
213 | indio_dev->channels = mrfld_adc_channels; | |
214 | indio_dev->num_channels = ARRAY_SIZE(mrfld_adc_channels); | |
215 | indio_dev->info = &mrfld_adc_iio_info; | |
216 | indio_dev->modes = INDIO_DIRECT_MODE; | |
217 | ||
218 | ret = iio_map_array_register(indio_dev, iio_maps); | |
219 | if (ret) | |
220 | return ret; | |
221 | ||
222 | ret = devm_iio_device_register(dev, indio_dev); | |
223 | if (ret < 0) | |
224 | goto err_array_unregister; | |
225 | ||
226 | return 0; | |
227 | ||
228 | err_array_unregister: | |
229 | iio_map_array_unregister(indio_dev); | |
230 | return ret; | |
231 | } | |
232 | ||
233 | static int mrfld_adc_remove(struct platform_device *pdev) | |
234 | { | |
235 | struct iio_dev *indio_dev = platform_get_drvdata(pdev); | |
236 | ||
237 | iio_map_array_unregister(indio_dev); | |
238 | ||
239 | return 0; | |
240 | } | |
241 | ||
242 | static const struct platform_device_id mrfld_adc_id_table[] = { | |
243 | { .name = "mrfld_bcove_adc" }, | |
244 | {} | |
245 | }; | |
246 | MODULE_DEVICE_TABLE(platform, mrfld_adc_id_table); | |
247 | ||
248 | static struct platform_driver mrfld_adc_driver = { | |
249 | .driver = { | |
250 | .name = "mrfld_bcove_adc", | |
251 | }, | |
252 | .probe = mrfld_adc_probe, | |
253 | .remove = mrfld_adc_remove, | |
254 | .id_table = mrfld_adc_id_table, | |
255 | }; | |
256 | module_platform_driver(mrfld_adc_driver); | |
257 | ||
258 | MODULE_AUTHOR("Bin Yang <bin.yang@intel.com>"); | |
259 | MODULE_AUTHOR("Vincent Pelletier <plr.vincent@gmail.com>"); | |
260 | MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>"); | |
261 | MODULE_DESCRIPTION("ADC driver for Basin Cove PMIC"); | |
262 | MODULE_LICENSE("GPL v2"); |