xref: /linux/drivers/iio/adc/ingenic-adc.c (revision d2912cb15bdda8ba4a5dd73396ad62641af2f520)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * ADC driver for the Ingenic JZ47xx SoCs
4  * Copyright (c) 2019 Artur Rojek <contact@artur-rojek.eu>
5  *
6  * based on drivers/mfd/jz4740-adc.c
7  */
8 
9 #include <dt-bindings/iio/adc/ingenic,adc.h>
10 #include <linux/clk.h>
11 #include <linux/iio/iio.h>
12 #include <linux/io.h>
13 #include <linux/iopoll.h>
14 #include <linux/module.h>
15 #include <linux/mutex.h>
16 #include <linux/platform_device.h>
17 
18 #define JZ_ADC_REG_ENABLE		0x00
19 #define JZ_ADC_REG_CFG			0x04
20 #define JZ_ADC_REG_CTRL			0x08
21 #define JZ_ADC_REG_STATUS		0x0c
22 #define JZ_ADC_REG_ADTCH		0x18
23 #define JZ_ADC_REG_ADBDAT		0x1c
24 #define JZ_ADC_REG_ADSDAT		0x20
25 
26 #define JZ_ADC_REG_CFG_BAT_MD		BIT(4)
27 
28 #define JZ_ADC_AUX_VREF				3300
29 #define JZ_ADC_AUX_VREF_BITS			12
30 #define JZ_ADC_BATTERY_LOW_VREF			2500
31 #define JZ_ADC_BATTERY_LOW_VREF_BITS		12
32 #define JZ4725B_ADC_BATTERY_HIGH_VREF		7500
33 #define JZ4725B_ADC_BATTERY_HIGH_VREF_BITS	10
34 #define JZ4740_ADC_BATTERY_HIGH_VREF		(7500 * 0.986)
35 #define JZ4740_ADC_BATTERY_HIGH_VREF_BITS	12
36 
37 struct ingenic_adc_soc_data {
38 	unsigned int battery_high_vref;
39 	unsigned int battery_high_vref_bits;
40 	const int *battery_raw_avail;
41 	size_t battery_raw_avail_size;
42 	const int *battery_scale_avail;
43 	size_t battery_scale_avail_size;
44 };
45 
46 struct ingenic_adc {
47 	void __iomem *base;
48 	struct clk *clk;
49 	struct mutex lock;
50 	const struct ingenic_adc_soc_data *soc_data;
51 	bool low_vref_mode;
52 };
53 
54 static void ingenic_adc_set_config(struct ingenic_adc *adc,
55 				   uint32_t mask,
56 				   uint32_t val)
57 {
58 	uint32_t cfg;
59 
60 	clk_enable(adc->clk);
61 	mutex_lock(&adc->lock);
62 
63 	cfg = readl(adc->base + JZ_ADC_REG_CFG) & ~mask;
64 	cfg |= val;
65 	writel(cfg, adc->base + JZ_ADC_REG_CFG);
66 
67 	mutex_unlock(&adc->lock);
68 	clk_disable(adc->clk);
69 }
70 
71 static void ingenic_adc_enable(struct ingenic_adc *adc,
72 			       int engine,
73 			       bool enabled)
74 {
75 	u8 val;
76 
77 	mutex_lock(&adc->lock);
78 	val = readb(adc->base + JZ_ADC_REG_ENABLE);
79 
80 	if (enabled)
81 		val |= BIT(engine);
82 	else
83 		val &= ~BIT(engine);
84 
85 	writeb(val, adc->base + JZ_ADC_REG_ENABLE);
86 	mutex_unlock(&adc->lock);
87 }
88 
89 static int ingenic_adc_capture(struct ingenic_adc *adc,
90 			       int engine)
91 {
92 	u8 val;
93 	int ret;
94 
95 	ingenic_adc_enable(adc, engine, true);
96 	ret = readb_poll_timeout(adc->base + JZ_ADC_REG_ENABLE, val,
97 				 !(val & BIT(engine)), 250, 1000);
98 	if (ret)
99 		ingenic_adc_enable(adc, engine, false);
100 
101 	return ret;
102 }
103 
104 static int ingenic_adc_write_raw(struct iio_dev *iio_dev,
105 				 struct iio_chan_spec const *chan,
106 				 int val,
107 				 int val2,
108 				 long m)
109 {
110 	struct ingenic_adc *adc = iio_priv(iio_dev);
111 
112 	switch (m) {
113 	case IIO_CHAN_INFO_SCALE:
114 		switch (chan->channel) {
115 		case INGENIC_ADC_BATTERY:
116 			if (val > JZ_ADC_BATTERY_LOW_VREF) {
117 				ingenic_adc_set_config(adc,
118 						       JZ_ADC_REG_CFG_BAT_MD,
119 						       0);
120 				adc->low_vref_mode = false;
121 			} else {
122 				ingenic_adc_set_config(adc,
123 						       JZ_ADC_REG_CFG_BAT_MD,
124 						       JZ_ADC_REG_CFG_BAT_MD);
125 				adc->low_vref_mode = true;
126 			}
127 			return 0;
128 		default:
129 			return -EINVAL;
130 		}
131 	default:
132 		return -EINVAL;
133 	}
134 }
135 
136 static const int jz4725b_adc_battery_raw_avail[] = {
137 	0, 1, (1 << JZ_ADC_BATTERY_LOW_VREF_BITS) - 1,
138 };
139 
140 static const int jz4725b_adc_battery_scale_avail[] = {
141 	JZ4725B_ADC_BATTERY_HIGH_VREF, JZ4725B_ADC_BATTERY_HIGH_VREF_BITS,
142 	JZ_ADC_BATTERY_LOW_VREF, JZ_ADC_BATTERY_LOW_VREF_BITS,
143 };
144 
145 static const int jz4740_adc_battery_raw_avail[] = {
146 	0, 1, (1 << JZ_ADC_BATTERY_LOW_VREF_BITS) - 1,
147 };
148 
149 static const int jz4740_adc_battery_scale_avail[] = {
150 	JZ4740_ADC_BATTERY_HIGH_VREF, JZ4740_ADC_BATTERY_HIGH_VREF_BITS,
151 	JZ_ADC_BATTERY_LOW_VREF, JZ_ADC_BATTERY_LOW_VREF_BITS,
152 };
153 
154 static const struct ingenic_adc_soc_data jz4725b_adc_soc_data = {
155 	.battery_high_vref = JZ4725B_ADC_BATTERY_HIGH_VREF,
156 	.battery_high_vref_bits = JZ4725B_ADC_BATTERY_HIGH_VREF_BITS,
157 	.battery_raw_avail = jz4725b_adc_battery_raw_avail,
158 	.battery_raw_avail_size = ARRAY_SIZE(jz4725b_adc_battery_raw_avail),
159 	.battery_scale_avail = jz4725b_adc_battery_scale_avail,
160 	.battery_scale_avail_size = ARRAY_SIZE(jz4725b_adc_battery_scale_avail),
161 };
162 
163 static const struct ingenic_adc_soc_data jz4740_adc_soc_data = {
164 	.battery_high_vref = JZ4740_ADC_BATTERY_HIGH_VREF,
165 	.battery_high_vref_bits = JZ4740_ADC_BATTERY_HIGH_VREF_BITS,
166 	.battery_raw_avail = jz4740_adc_battery_raw_avail,
167 	.battery_raw_avail_size = ARRAY_SIZE(jz4740_adc_battery_raw_avail),
168 	.battery_scale_avail = jz4740_adc_battery_scale_avail,
169 	.battery_scale_avail_size = ARRAY_SIZE(jz4740_adc_battery_scale_avail),
170 };
171 
172 static int ingenic_adc_read_avail(struct iio_dev *iio_dev,
173 				  struct iio_chan_spec const *chan,
174 				  const int **vals,
175 				  int *type,
176 				  int *length,
177 				  long m)
178 {
179 	struct ingenic_adc *adc = iio_priv(iio_dev);
180 
181 	switch (m) {
182 	case IIO_CHAN_INFO_RAW:
183 		*type = IIO_VAL_INT;
184 		*length = adc->soc_data->battery_raw_avail_size;
185 		*vals = adc->soc_data->battery_raw_avail;
186 		return IIO_AVAIL_RANGE;
187 	case IIO_CHAN_INFO_SCALE:
188 		*type = IIO_VAL_FRACTIONAL_LOG2;
189 		*length = adc->soc_data->battery_scale_avail_size;
190 		*vals = adc->soc_data->battery_scale_avail;
191 		return IIO_AVAIL_LIST;
192 	default:
193 		return -EINVAL;
194 	};
195 }
196 
197 static int ingenic_adc_read_raw(struct iio_dev *iio_dev,
198 				struct iio_chan_spec const *chan,
199 				int *val,
200 				int *val2,
201 				long m)
202 {
203 	struct ingenic_adc *adc = iio_priv(iio_dev);
204 	int ret;
205 
206 	switch (m) {
207 	case IIO_CHAN_INFO_RAW:
208 		clk_enable(adc->clk);
209 		ret = ingenic_adc_capture(adc, chan->channel);
210 		if (ret) {
211 			clk_disable(adc->clk);
212 			return ret;
213 		}
214 
215 		switch (chan->channel) {
216 		case INGENIC_ADC_AUX:
217 			*val = readw(adc->base + JZ_ADC_REG_ADSDAT);
218 			break;
219 		case INGENIC_ADC_BATTERY:
220 			*val = readw(adc->base + JZ_ADC_REG_ADBDAT);
221 			break;
222 		}
223 
224 		clk_disable(adc->clk);
225 
226 		return IIO_VAL_INT;
227 	case IIO_CHAN_INFO_SCALE:
228 		switch (chan->channel) {
229 		case INGENIC_ADC_AUX:
230 			*val = JZ_ADC_AUX_VREF;
231 			*val2 = JZ_ADC_AUX_VREF_BITS;
232 			break;
233 		case INGENIC_ADC_BATTERY:
234 			if (adc->low_vref_mode) {
235 				*val = JZ_ADC_BATTERY_LOW_VREF;
236 				*val2 = JZ_ADC_BATTERY_LOW_VREF_BITS;
237 			} else {
238 				*val = adc->soc_data->battery_high_vref;
239 				*val2 = adc->soc_data->battery_high_vref_bits;
240 			}
241 			break;
242 		}
243 
244 		return IIO_VAL_FRACTIONAL_LOG2;
245 	default:
246 		return -EINVAL;
247 	}
248 }
249 
250 static void ingenic_adc_clk_cleanup(void *data)
251 {
252 	clk_unprepare(data);
253 }
254 
255 static const struct iio_info ingenic_adc_info = {
256 	.write_raw = ingenic_adc_write_raw,
257 	.read_raw = ingenic_adc_read_raw,
258 	.read_avail = ingenic_adc_read_avail,
259 };
260 
261 static const struct iio_chan_spec ingenic_channels[] = {
262 	{
263 		.extend_name = "aux",
264 		.type = IIO_VOLTAGE,
265 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
266 				      BIT(IIO_CHAN_INFO_SCALE),
267 		.indexed = 1,
268 		.channel = INGENIC_ADC_AUX,
269 	},
270 	{
271 		.extend_name = "battery",
272 		.type = IIO_VOLTAGE,
273 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
274 				      BIT(IIO_CHAN_INFO_SCALE),
275 		.info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW) |
276 						BIT(IIO_CHAN_INFO_SCALE),
277 		.indexed = 1,
278 		.channel = INGENIC_ADC_BATTERY,
279 	},
280 };
281 
282 static int ingenic_adc_probe(struct platform_device *pdev)
283 {
284 	struct device *dev = &pdev->dev;
285 	struct iio_dev *iio_dev;
286 	struct ingenic_adc *adc;
287 	struct resource *mem_base;
288 	const struct ingenic_adc_soc_data *soc_data;
289 	int ret;
290 
291 	soc_data = device_get_match_data(dev);
292 	if (!soc_data)
293 		return -EINVAL;
294 
295 	iio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
296 	if (!iio_dev)
297 		return -ENOMEM;
298 
299 	adc = iio_priv(iio_dev);
300 	mutex_init(&adc->lock);
301 	adc->soc_data = soc_data;
302 
303 	mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
304 	adc->base = devm_ioremap_resource(dev, mem_base);
305 	if (IS_ERR(adc->base))
306 		return PTR_ERR(adc->base);
307 
308 	adc->clk = devm_clk_get(dev, "adc");
309 	if (IS_ERR(adc->clk)) {
310 		dev_err(dev, "Unable to get clock\n");
311 		return PTR_ERR(adc->clk);
312 	}
313 
314 	ret = clk_prepare_enable(adc->clk);
315 	if (ret) {
316 		dev_err(dev, "Failed to enable clock\n");
317 		return ret;
318 	}
319 
320 	/* Put hardware in a known passive state. */
321 	writeb(0x00, adc->base + JZ_ADC_REG_ENABLE);
322 	writeb(0xff, adc->base + JZ_ADC_REG_CTRL);
323 	clk_disable(adc->clk);
324 
325 	ret = devm_add_action_or_reset(dev, ingenic_adc_clk_cleanup, adc->clk);
326 	if (ret) {
327 		dev_err(dev, "Unable to add action\n");
328 		return ret;
329 	}
330 
331 	iio_dev->dev.parent = dev;
332 	iio_dev->name = "jz-adc";
333 	iio_dev->modes = INDIO_DIRECT_MODE;
334 	iio_dev->channels = ingenic_channels;
335 	iio_dev->num_channels = ARRAY_SIZE(ingenic_channels);
336 	iio_dev->info = &ingenic_adc_info;
337 
338 	ret = devm_iio_device_register(dev, iio_dev);
339 	if (ret)
340 		dev_err(dev, "Unable to register IIO device\n");
341 
342 	return ret;
343 }
344 
345 #ifdef CONFIG_OF
346 static const struct of_device_id ingenic_adc_of_match[] = {
347 	{ .compatible = "ingenic,jz4725b-adc", .data = &jz4725b_adc_soc_data, },
348 	{ .compatible = "ingenic,jz4740-adc", .data = &jz4740_adc_soc_data, },
349 	{ },
350 };
351 MODULE_DEVICE_TABLE(of, ingenic_adc_of_match);
352 #endif
353 
354 static struct platform_driver ingenic_adc_driver = {
355 	.driver = {
356 		.name = "ingenic-adc",
357 		.of_match_table = of_match_ptr(ingenic_adc_of_match),
358 	},
359 	.probe = ingenic_adc_probe,
360 };
361 module_platform_driver(ingenic_adc_driver);
362 MODULE_LICENSE("GPL v2");
363