History

iio: adc: Add driver for Marvell 88PM886 PMIC ADC

Marvell's 88PM886 PMIC has a so-called General Purpose ADC used for monitoring various system voltages and temperatures. Add the relevant register definitions to the MFD header and a driver for the ADC.

Duje Mihanović committed 1 year ago

Commit 3422b4b1 parent 0f06e285 files · +470 −0Browse files

Unified · Split

5 files
  1. MAINTAINERS +5 −0
  2. drivers/iio/adc/88pm886-gpadc.c +393 −0
  3. drivers/iio/adc/Kconfig +13 −0
  4. drivers/iio/adc/Makefile +1 −0
  5. include/linux/mfd/88pm886.h +58 −0
MAINTAINERS+5−0
@@ -14714,6 +14714,11 @@ F: drivers/regulator/88pm886-regulator.c · 14713 unmodified lines
14714F: drivers/rtc/rtc-88pm886.c14714F: drivers/rtc/rtc-88pm886.c
14715F: include/linux/mfd/88pm886.h14715F: include/linux/mfd/88pm886.h
14716 14716
14717MARVELL 88PM886 PMIC GPADC DRIVER
14718M: Duje Mihanović <duje@dujemihanovic.xyz>
14719S: Maintained
14720F: drivers/iio/adc/88pm886-gpadc.c
14721
14717MARVELL ARMADA 3700 PHY DRIVERS14722MARVELL ARMADA 3700 PHY DRIVERS
14718M: Miquel Raynal <miquel.raynal@bootlin.com>14723M: Miquel Raynal <miquel.raynal@bootlin.com>
14719S: Maintained14724S: Maintained
drivers/iio/adc/88pm886-gpadc.c+393−0
@@ -0,0 +1,393 @@
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright 2025, Duje Mihanović <duje@dujemihanovic.xyz>
4 */
5
6#include <linux/bits.h>
7#include <linux/bug.h>
8#include <linux/delay.h>
9#include <linux/device.h>
10#include <linux/err.h>
11#include <linux/i2c.h>
12#include <linux/math.h>
13#include <linux/mod_devicetable.h>
14#include <linux/module.h>
15#include <linux/platform_device.h>
16#include <linux/pm_runtime.h>
17#include <linux/regmap.h>
18#include <linux/types.h>
19#include <linux/units.h>
20
21#include <asm/byteorder.h>
22
23#include <linux/iio/iio.h>
24#include <linux/iio/types.h>
25
26#include <linux/mfd/88pm886.h>
27
28struct pm886_gpadc {
29 struct regmap *map;
30};
31
32enum pm886_gpadc_channel {
33 VSC_CHAN,
34 VCHG_PWR_CHAN,
35 VCF_OUT_CHAN,
36 VBAT_CHAN,
37 VBAT_SLP_CHAN,
38 VBUS_CHAN,
39
40 GPADC0_CHAN,
41 GPADC1_CHAN,
42 GPADC2_CHAN,
43 GPADC3_CHAN,
44
45 GND_DET1_CHAN,
46 GND_DET2_CHAN,
47 MIC_DET_CHAN,
48
49 TINT_CHAN,
50};
51
52static const int pm886_gpadc_regs[] = {
53 [VSC_CHAN] = PM886_REG_GPADC_VSC,
54 [VCHG_PWR_CHAN] = PM886_REG_GPADC_VCHG_PWR,
55 [VCF_OUT_CHAN] = PM886_REG_GPADC_VCF_OUT,
56 [VBAT_CHAN] = PM886_REG_GPADC_VBAT,
57 [VBAT_SLP_CHAN] = PM886_REG_GPADC_VBAT_SLP,
58 [VBUS_CHAN] = PM886_REG_GPADC_VBUS,
59
60 [GPADC0_CHAN] = PM886_REG_GPADC_GPADC0,
61 [GPADC1_CHAN] = PM886_REG_GPADC_GPADC1,
62 [GPADC2_CHAN] = PM886_REG_GPADC_GPADC2,
63 [GPADC3_CHAN] = PM886_REG_GPADC_GPADC3,
64
65 [GND_DET1_CHAN] = PM886_REG_GPADC_GND_DET1,
66 [GND_DET2_CHAN] = PM886_REG_GPADC_GND_DET2,
67 [MIC_DET_CHAN] = PM886_REG_GPADC_MIC_DET,
68
69 [TINT_CHAN] = PM886_REG_GPADC_TINT,
70};
71
72#define ADC_CHANNEL_VOLTAGE(index, lsb, name) \
73{ \
74 .type = IIO_VOLTAGE, \
75 .indexed = 1, \
76 .channel = index, \
77 .address = lsb, \
78 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
79 BIT(IIO_CHAN_INFO_SCALE), \
80 .datasheet_name = name, \
81}
82
83#define ADC_CHANNEL_RESISTANCE(index, lsb, name) \
84{ \
85 .type = IIO_RESISTANCE, \
86 .indexed = 1, \
87 .channel = index, \
88 .address = lsb, \
89 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
90 .datasheet_name = name, \
91}
92
93#define ADC_CHANNEL_TEMPERATURE(index, lsb, name) \
94{ \
95 .type = IIO_TEMP, \
96 .indexed = 1, \
97 .channel = index, \
98 .address = lsb, \
99 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
100 BIT(IIO_CHAN_INFO_SCALE) | \
101 BIT(IIO_CHAN_INFO_OFFSET), \
102 .datasheet_name = name, \
103}
104
105static const struct iio_chan_spec pm886_gpadc_channels[] = {
106 ADC_CHANNEL_VOLTAGE(VSC_CHAN, 1367, "vsc"),
107 ADC_CHANNEL_VOLTAGE(VCHG_PWR_CHAN, 1709, "vchg_pwr"),
108 ADC_CHANNEL_VOLTAGE(VCF_OUT_CHAN, 1367, "vcf_out"),
109 ADC_CHANNEL_VOLTAGE(VBAT_CHAN, 1367, "vbat"),
110 ADC_CHANNEL_VOLTAGE(VBAT_SLP_CHAN, 1367, "vbat_slp"),
111 ADC_CHANNEL_VOLTAGE(VBUS_CHAN, 1709, "vbus"),
112
113 ADC_CHANNEL_RESISTANCE(GPADC0_CHAN, 342, "gpadc0"),
114 ADC_CHANNEL_RESISTANCE(GPADC1_CHAN, 342, "gpadc1"),
115 ADC_CHANNEL_RESISTANCE(GPADC2_CHAN, 342, "gpadc2"),
116 ADC_CHANNEL_RESISTANCE(GPADC3_CHAN, 342, "gpadc3"),
117
118 ADC_CHANNEL_VOLTAGE(GND_DET1_CHAN, 342, "gnddet1"),
119 ADC_CHANNEL_VOLTAGE(GND_DET2_CHAN, 342, "gnddet2"),
120 ADC_CHANNEL_VOLTAGE(MIC_DET_CHAN, 1367, "mic_det"),
121
122 ADC_CHANNEL_TEMPERATURE(TINT_CHAN, 104, "tint"),
123};
124
125static const struct regmap_config pm886_gpadc_regmap_config = {
126 .reg_bits = 8,
127 .val_bits = 8,
128 .max_register = PM886_GPADC_MAX_REGISTER,
129};
130
131static int gpadc_get_raw(struct iio_dev *iio, enum pm886_gpadc_channel chan)
132{
133 struct pm886_gpadc *gpadc = iio_priv(iio);
134 __be16 buf;
135 int ret;
136
137 ret = regmap_bulk_read(gpadc->map, pm886_gpadc_regs[chan], &buf, sizeof(buf));
138 if (ret)
139 return ret;
140
141 return be16_to_cpu(buf) >> 4;
142}
143
144static int
145gpadc_set_bias(struct pm886_gpadc *gpadc, enum pm886_gpadc_channel chan, bool on)
146{
147 unsigned int gpadc_num = chan - GPADC0_CHAN;
148 unsigned int bits = BIT(gpadc_num + 4) | BIT(gpadc_num);
149
150 return regmap_assign_bits(gpadc->map, PM886_REG_GPADC_CONFIG(0x14), bits, on);
151}
152
153static int
154gpadc_find_bias_current(struct iio_dev *iio, struct iio_chan_spec const *chan,
155 unsigned int *raw_uV, unsigned int *raw_uA)
156{
157 struct pm886_gpadc *gpadc = iio_priv(iio);
158 unsigned int gpadc_num = chan->channel - GPADC0_CHAN;
159 unsigned int reg = PM886_REG_GPADC_CONFIG(0xb + gpadc_num);
160 unsigned long lsb = chan->address;
161 int ret;
162
163 for (unsigned int i = 0; i < PM886_GPADC_BIAS_LEVELS; i++) {
164 ret = regmap_update_bits(gpadc->map, reg, GENMASK(3, 0), i);
165 if (ret)
166 return ret;
167
168 /* Wait for the new bias level to apply. */
169 fsleep(5 * USEC_PER_MSEC);
170
171 *raw_uA = PM886_GPADC_INDEX_TO_BIAS_uA(i);
172 *raw_uV = gpadc_get_raw(iio, chan->channel) * lsb;
173
174 /*
175 * Vendor kernel errors out above 1.25 V, but testing shows
176 * that the resistance of the battery detection channel (GPADC2
177 * on coreprimevelte) reaches about 1.4 MΩ when the battery is
178 * removed, which can't be measured with such a low upper
179 * limit. Therefore, to be able to detect the battery without
180 * ugly externs as used in the vendor fuel gauge driver,
181 * increase this limit a bit.
182 */
183 if (WARN_ON(*raw_uV > 1500 * (MICRO / MILLI)))
184 return -EIO;
185
186 /*
187 * Vendor kernel errors out under 300 mV, but for the same
188 * reason as above (except the channel hovers around 3.5 kΩ
189 * with battery present) reduce this limit.
190 */
191 if (*raw_uV < 200 * (MICRO / MILLI)) {
192 dev_dbg(&iio->dev, "bad bias for chan %d: %d uA @ %d uV\n",
193 chan->channel, *raw_uA, *raw_uV);
194 continue;
195 }
196
197 dev_dbg(&iio->dev, "good bias for chan %d: %d uA @ %d uV\n",
198 chan->channel, *raw_uA, *raw_uV);
199 return 0;
200 }
201
202 dev_err(&iio->dev, "failed to find good bias for chan %d\n", chan->channel);
203 return -EINVAL;
204}
205
206static int
207gpadc_get_resistance_ohm(struct iio_dev *iio, struct iio_chan_spec const *chan)
208{
209 struct pm886_gpadc *gpadc = iio_priv(iio);
210 unsigned int raw_uV, raw_uA;
211 int ret;
212
213 ret = gpadc_set_bias(gpadc, chan->channel, true);
214 if (ret)
215 goto out;
216
217 ret = gpadc_find_bias_current(iio, chan, &raw_uV, &raw_uA);
218 if (ret)
219 goto out;
220
221 ret = DIV_ROUND_CLOSEST(raw_uV, raw_uA);
222out:
223 gpadc_set_bias(gpadc, chan->channel, false);
224 return ret;
225}
226
227static int
228__pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan,
229 int *val, int *val2, long mask)
230{
231 unsigned long lsb = chan->address;
232
233 switch (mask) {
234 case IIO_CHAN_INFO_RAW:
235 *val = gpadc_get_raw(iio, chan->channel);
236 if (*val < 0)
237 return *val;
238
239 return IIO_VAL_INT;
240 case IIO_CHAN_INFO_SCALE:
241 *val = lsb;
242
243 if (chan->type == IIO_VOLTAGE) {
244 *val2 = MILLI;
245 return IIO_VAL_FRACTIONAL;
246 } else {
247 return IIO_VAL_INT;
248 }
249 case IIO_CHAN_INFO_OFFSET:
250 /* Raw value is 104 millikelvin/LSB, convert it to 104 millicelsius/LSB */
251 *val = ABSOLUTE_ZERO_MILLICELSIUS;
252 *val2 = lsb;
253 return IIO_VAL_FRACTIONAL;
254 case IIO_CHAN_INFO_PROCESSED:
255 *val = gpadc_get_resistance_ohm(iio, chan);
256 if (*val < 0)
257 return *val;
258
259 return IIO_VAL_INT;
260 default:
261 return -EINVAL;
262 }
263}
264
265static int pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan,
266 int *val, int *val2, long mask)
267{
268 struct device *dev = iio->dev.parent;
269 int ret;
270
271 ret = pm_runtime_resume_and_get(dev);
272 if (ret)
273 return ret;
274
275 ret = __pm886_gpadc_read_raw(iio, chan, val, val2, mask);
276
277 pm_runtime_put_autosuspend(dev);
278 return ret;
279}
280
281static int pm886_gpadc_hw_enable(struct regmap *map)
282{
283 const u8 config[] = {
284 PM886_GPADC_CONFIG1_EN_ALL,
285 PM886_GPADC_CONFIG2_EN_ALL,
286 PM886_GPADC_GND_DET2_EN,
287 };
288 int ret;
289
290 /* Enable the ADC block. */
291 ret = regmap_set_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0));
292 if (ret)
293 return ret;
294
295 /* Enable all channels. */
296 return regmap_bulk_write(map, PM886_REG_GPADC_CONFIG(0x1), config, ARRAY_SIZE(config));
297}
298
299static int pm886_gpadc_hw_disable(struct regmap *map)
300{
301 return regmap_clear_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0));
302}
303
304static const struct iio_info pm886_gpadc_iio_info = {
305 .read_raw = pm886_gpadc_read_raw,
306};
307
308static int pm886_gpadc_probe(struct platform_device *pdev)
309{
310 struct device *dev = &pdev->dev;
311 struct pm886_chip *chip = dev_get_drvdata(dev->parent);
312 struct i2c_client *client = chip->client;
313 struct pm886_gpadc *gpadc;
314 struct i2c_client *page;
315 struct iio_dev *iio;
316 int ret;
317
318 iio = devm_iio_device_alloc(dev, sizeof(*gpadc));
319 if (!iio)
320 return -ENOMEM;
321
322 gpadc = iio_priv(iio);
323 dev_set_drvdata(dev, iio);
324
325 page = devm_i2c_new_dummy_device(dev, client->adapter,
326 client->addr + PM886_PAGE_OFFSET_GPADC);
327 if (IS_ERR(page))
328 return dev_err_probe(dev, PTR_ERR(page), "Failed to initialize GPADC page\n");
329
330 gpadc->map = devm_regmap_init_i2c(page, &pm886_gpadc_regmap_config);
331 if (IS_ERR(gpadc->map))
332 return dev_err_probe(dev, PTR_ERR(gpadc->map),
333 "Failed to initialize GPADC regmap\n");
334
335 iio->name = "88pm886-gpadc";
336 iio->modes = INDIO_DIRECT_MODE;
337 iio->info = &pm886_gpadc_iio_info;
338 iio->channels = pm886_gpadc_channels;
339 iio->num_channels = ARRAY_SIZE(pm886_gpadc_channels);
340 device_set_node(&iio->dev, dev_fwnode(dev->parent));
341
342 ret = devm_pm_runtime_enable(dev);
343 if (ret)
344 return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
345
346 pm_runtime_set_autosuspend_delay(dev, 50);
347 pm_runtime_use_autosuspend(dev);
348 ret = devm_iio_device_register(dev, iio);
349 if (ret)
350 return dev_err_probe(dev, ret, "Failed to register ADC\n");
351
352 return 0;
353}
354
355static int pm886_gpadc_runtime_resume(struct device *dev)
356{
357 struct iio_dev *iio = dev_get_drvdata(dev);
358 struct pm886_gpadc *gpadc = iio_priv(iio);
359
360 return pm886_gpadc_hw_enable(gpadc->map);
361}
362
363static int pm886_gpadc_runtime_suspend(struct device *dev)
364{
365 struct iio_dev *iio = dev_get_drvdata(dev);
366 struct pm886_gpadc *gpadc = iio_priv(iio);
367
368 return pm886_gpadc_hw_disable(gpadc->map);
369}
370
371static DEFINE_RUNTIME_DEV_PM_OPS(pm886_gpadc_pm_ops,
372 pm886_gpadc_runtime_suspend,
373 pm886_gpadc_runtime_resume, NULL);
374
375static const struct platform_device_id pm886_gpadc_id[] = {
376 { "88pm886-gpadc" },
377 { }
378};
379MODULE_DEVICE_TABLE(platform, pm886_gpadc_id);
380
381static struct platform_driver pm886_gpadc_driver = {
382 .driver = {
383 .name = "88pm886-gpadc",
384 .pm = pm_ptr(&pm886_gpadc_pm_ops),
385 },
386 .probe = pm886_gpadc_probe,
387 .id_table = pm886_gpadc_id,
388};
389module_platform_driver(pm886_gpadc_driver);
390
391MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>");
392MODULE_DESCRIPTION("Marvell 88PM886 GPADC driver");
393MODULE_LICENSE("GPL");
drivers/iio/adc/Kconfig+13−0
@@ -9,6 +9,19 @@ menu "Analog to digital converters" · 8 unmodified lines
9config IIO_ADC_HELPER9config IIO_ADC_HELPER
10 tristate10 tristate
11 11
12config 88PM886_GPADC
13 tristate "Marvell 88PM886 GPADC driver"
14 depends on MFD_88PM886_PMIC
15 default MFD_88PM886_PMIC
16 help
17 Say Y here to enable support for the GPADC (General Purpose ADC)
18 found on the Marvell 88PM886 PMIC. The GPADC measures various
19 internal voltages and temperatures, including (but not limited to)
20 system, battery and USB Vbus.
21
22 To compile this driver as a module, choose M here: the module will be
23 called 88pm886-gpadc.
24
12config AB8500_GPADC25config AB8500_GPADC
13 bool "ST-Ericsson AB8500 GPADC driver"26 bool "ST-Ericsson AB8500 GPADC driver"
14 depends on AB8500_CORE && REGULATOR_AB850027 depends on AB8500_CORE && REGULATOR_AB8500
drivers/iio/adc/Makefile+1−0
@@ -6,6 +6,7 @@ · 5 unmodified lines
6obj-$(CONFIG_IIO_ADC_HELPER) += industrialio-adc.o6obj-$(CONFIG_IIO_ADC_HELPER) += industrialio-adc.o
7 7
8# When adding new entries keep the list in alphabetical order8# When adding new entries keep the list in alphabetical order
9obj-$(CONFIG_88PM886_GPADC) += 88pm886-gpadc.o
9obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o10obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o
10obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o11obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o
11obj-$(CONFIG_AD4000) += ad4000.o12obj-$(CONFIG_AD4000) += ad4000.o
include/linux/mfd/88pm886.h+58−0
@@ -10,6 +10,7 @@ · 9 unmodified lines
10#define PM886_IRQ_ONKEY 010#define PM886_IRQ_ONKEY 0
11 11
12#define PM886_PAGE_OFFSET_REGULATORS 112#define PM886_PAGE_OFFSET_REGULATORS 1
13#define PM886_PAGE_OFFSET_GPADC 2
13 14
14#define PM886_REG_ID 0x0015#define PM886_REG_ID 0x00
15 16
@@ -70,6 +71,63 @@ · 54 unmodified lines
70#define PM886_LDO_VSEL_MASK 0x0f71#define PM886_LDO_VSEL_MASK 0x0f
71#define PM886_BUCK_VSEL_MASK 0x7f72#define PM886_BUCK_VSEL_MASK 0x7f
72 73
74/* GPADC enable/disable registers */
75#define PM886_REG_GPADC_CONFIG(n) (n)
76
77#define PM886_GPADC_VSC_EN BIT(0)
78#define PM886_GPADC_VBAT_EN BIT(1)
79#define PM886_GPADC_GNDDET1_EN BIT(3)
80#define PM886_GPADC_VBUS_EN BIT(4)
81#define PM886_GPADC_VCHG_PWR_EN BIT(5)
82#define PM886_GPADC_VCF_OUT_EN BIT(6)
83#define PM886_GPADC_CONFIG1_EN_ALL \
84 (PM886_GPADC_VSC_EN | \
85 PM886_GPADC_VBAT_EN | \
86 PM886_GPADC_GNDDET1_EN | \
87 PM886_GPADC_VBUS_EN | \
88 PM886_GPADC_VCHG_PWR_EN | \
89 PM886_GPADC_VCF_OUT_EN)
90
91#define PM886_GPADC_TINT_EN BIT(0)
92#define PM886_GPADC_PMODE_EN BIT(1)
93#define PM886_GPADC_GPADC0_EN BIT(2)
94#define PM886_GPADC_GPADC1_EN BIT(3)
95#define PM886_GPADC_GPADC2_EN BIT(4)
96#define PM886_GPADC_GPADC3_EN BIT(5)
97#define PM886_GPADC_MIC_DET_EN BIT(6)
98#define PM886_GPADC_CONFIG2_EN_ALL \
99 (PM886_GPADC_TINT_EN | \
100 PM886_GPADC_GPADC0_EN | \
101 PM886_GPADC_GPADC1_EN | \
102 PM886_GPADC_GPADC2_EN | \
103 PM886_GPADC_GPADC3_EN | \
104 PM886_GPADC_MIC_DET_EN)
105
106/* No CONFIG3_EN_ALL because this is the only bit there. */
107#define PM886_GPADC_GND_DET2_EN BIT(0)
108
109/* GPADC channel registers */
110#define PM886_REG_GPADC_VSC 0x40
111#define PM886_REG_GPADC_VCHG_PWR 0x4c
112#define PM886_REG_GPADC_VCF_OUT 0x4e
113#define PM886_REG_GPADC_TINT 0x50
114#define PM886_REG_GPADC_GPADC0 0x54
115#define PM886_REG_GPADC_GPADC1 0x56
116#define PM886_REG_GPADC_GPADC2 0x58
117#define PM886_REG_GPADC_VBAT 0xa0
118#define PM886_REG_GPADC_GND_DET1 0xa4
119#define PM886_REG_GPADC_GND_DET2 0xa6
120#define PM886_REG_GPADC_VBUS 0xa8
121#define PM886_REG_GPADC_GPADC3 0xaa
122#define PM886_REG_GPADC_MIC_DET 0xac
123#define PM886_REG_GPADC_VBAT_SLP 0xb0
124
125/* VBAT_SLP is the last register and is 2 bytes wide like other channels. */
126#define PM886_GPADC_MAX_REGISTER (PM886_REG_GPADC_VBAT_SLP + 1)
127
128#define PM886_GPADC_BIAS_LEVELS 16
129#define PM886_GPADC_INDEX_TO_BIAS_uA(i) (1 + (i) * 5)
130
73struct pm886_chip {131struct pm886_chip {
74 struct i2c_client *client;132 struct i2c_client *client;
75 unsigned int chip_id;133 unsigned int chip_id;