xref: /openbmc/linux/drivers/iio/adc/axp288_adc.c (revision 7c279229)
18e8e69d6SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2de89bd7fSJacob Pan /*
3de89bd7fSJacob Pan  * axp288_adc.c - X-Powers AXP288 PMIC ADC Driver
4de89bd7fSJacob Pan  *
5de89bd7fSJacob Pan  * Copyright (C) 2014 Intel Corporation
6de89bd7fSJacob Pan  *
7de89bd7fSJacob Pan  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8de89bd7fSJacob Pan  */
9de89bd7fSJacob Pan 
10de89bd7fSJacob Pan #include <linux/module.h>
11de89bd7fSJacob Pan #include <linux/kernel.h>
12de89bd7fSJacob Pan #include <linux/device.h>
13de89bd7fSJacob Pan #include <linux/regmap.h>
14de89bd7fSJacob Pan #include <linux/mfd/axp20x.h>
15de89bd7fSJacob Pan #include <linux/platform_device.h>
16de89bd7fSJacob Pan 
17de89bd7fSJacob Pan #include <linux/iio/iio.h>
18de89bd7fSJacob Pan #include <linux/iio/machine.h>
19de89bd7fSJacob Pan #include <linux/iio/driver.h>
20de89bd7fSJacob Pan 
219bcf15f7SHans de Goede /*
229bcf15f7SHans de Goede  * This mask enables all ADCs except for the battery temp-sensor (TS), that is
239bcf15f7SHans de Goede  * left as-is to avoid breaking charging on devices without a temp-sensor.
249bcf15f7SHans de Goede  */
259bcf15f7SHans de Goede #define AXP288_ADC_EN_MASK				0xF0
269bcf15f7SHans de Goede #define AXP288_ADC_TS_ENABLE				0x01
279bcf15f7SHans de Goede 
289bcf15f7SHans de Goede #define AXP288_ADC_TS_CURRENT_ON_OFF_MASK		GENMASK(1, 0)
299bcf15f7SHans de Goede #define AXP288_ADC_TS_CURRENT_OFF			(0 << 0)
309bcf15f7SHans de Goede #define AXP288_ADC_TS_CURRENT_ON_WHEN_CHARGING		(1 << 0)
319bcf15f7SHans de Goede #define AXP288_ADC_TS_CURRENT_ON_ONDEMAND		(2 << 0)
329bcf15f7SHans de Goede #define AXP288_ADC_TS_CURRENT_ON			(3 << 0)
33de89bd7fSJacob Pan 
34de89bd7fSJacob Pan enum axp288_adc_id {
35de89bd7fSJacob Pan 	AXP288_ADC_TS,
36de89bd7fSJacob Pan 	AXP288_ADC_PMIC,
37de89bd7fSJacob Pan 	AXP288_ADC_GP,
38de89bd7fSJacob Pan 	AXP288_ADC_BATT_CHRG_I,
39de89bd7fSJacob Pan 	AXP288_ADC_BATT_DISCHRG_I,
40de89bd7fSJacob Pan 	AXP288_ADC_BATT_V,
41de89bd7fSJacob Pan 	AXP288_ADC_NR_CHAN,
42de89bd7fSJacob Pan };
43de89bd7fSJacob Pan 
44de89bd7fSJacob Pan struct axp288_adc_info {
45de89bd7fSJacob Pan 	int irq;
46de89bd7fSJacob Pan 	struct regmap *regmap;
479bcf15f7SHans de Goede 	bool ts_enabled;
48de89bd7fSJacob Pan };
49de89bd7fSJacob Pan 
507ca6574aSColin Ian King static const struct iio_chan_spec axp288_adc_channels[] = {
51de89bd7fSJacob Pan 	{
52de89bd7fSJacob Pan 		.indexed = 1,
53de89bd7fSJacob Pan 		.type = IIO_TEMP,
54de89bd7fSJacob Pan 		.channel = 0,
55de89bd7fSJacob Pan 		.address = AXP288_TS_ADC_H,
56de89bd7fSJacob Pan 		.datasheet_name = "TS_PIN",
57d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
58de89bd7fSJacob Pan 	}, {
59de89bd7fSJacob Pan 		.indexed = 1,
60de89bd7fSJacob Pan 		.type = IIO_TEMP,
61de89bd7fSJacob Pan 		.channel = 1,
62de89bd7fSJacob Pan 		.address = AXP288_PMIC_ADC_H,
63de89bd7fSJacob Pan 		.datasheet_name = "PMIC_TEMP",
64d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
65de89bd7fSJacob Pan 	}, {
66de89bd7fSJacob Pan 		.indexed = 1,
67de89bd7fSJacob Pan 		.type = IIO_TEMP,
68de89bd7fSJacob Pan 		.channel = 2,
69de89bd7fSJacob Pan 		.address = AXP288_GP_ADC_H,
70de89bd7fSJacob Pan 		.datasheet_name = "GPADC",
71d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
72de89bd7fSJacob Pan 	}, {
73de89bd7fSJacob Pan 		.indexed = 1,
74de89bd7fSJacob Pan 		.type = IIO_CURRENT,
75de89bd7fSJacob Pan 		.channel = 3,
76de89bd7fSJacob Pan 		.address = AXP20X_BATT_CHRG_I_H,
77de89bd7fSJacob Pan 		.datasheet_name = "BATT_CHG_I",
78d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
79de89bd7fSJacob Pan 	}, {
80de89bd7fSJacob Pan 		.indexed = 1,
81de89bd7fSJacob Pan 		.type = IIO_CURRENT,
82de89bd7fSJacob Pan 		.channel = 4,
83de89bd7fSJacob Pan 		.address = AXP20X_BATT_DISCHRG_I_H,
84de89bd7fSJacob Pan 		.datasheet_name = "BATT_DISCHRG_I",
85d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
86de89bd7fSJacob Pan 	}, {
87de89bd7fSJacob Pan 		.indexed = 1,
88de89bd7fSJacob Pan 		.type = IIO_VOLTAGE,
89de89bd7fSJacob Pan 		.channel = 5,
90de89bd7fSJacob Pan 		.address = AXP20X_BATT_V_H,
91de89bd7fSJacob Pan 		.datasheet_name = "BATT_V",
92d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
93de89bd7fSJacob Pan 	},
94de89bd7fSJacob Pan };
95de89bd7fSJacob Pan 
96de89bd7fSJacob Pan /* for consumer drivers */
97de89bd7fSJacob Pan static struct iio_map axp288_adc_default_maps[] = {
988d05ffd2SLukas Wunner 	IIO_MAP("TS_PIN", "axp288-batt", "axp288-batt-temp"),
998d05ffd2SLukas Wunner 	IIO_MAP("PMIC_TEMP", "axp288-pmic", "axp288-pmic-temp"),
1008d05ffd2SLukas Wunner 	IIO_MAP("GPADC", "axp288-gpadc", "axp288-system-temp"),
1018d05ffd2SLukas Wunner 	IIO_MAP("BATT_CHG_I", "axp288-chrg", "axp288-chrg-curr"),
1028d05ffd2SLukas Wunner 	IIO_MAP("BATT_DISCHRG_I", "axp288-chrg", "axp288-chrg-d-curr"),
1038d05ffd2SLukas Wunner 	IIO_MAP("BATT_V", "axp288-batt", "axp288-batt-volt"),
104de89bd7fSJacob Pan 	{},
105de89bd7fSJacob Pan };
106de89bd7fSJacob Pan 
107de89bd7fSJacob Pan static int axp288_adc_read_channel(int *val, unsigned long address,
108de89bd7fSJacob Pan 				struct regmap *regmap)
109de89bd7fSJacob Pan {
110de89bd7fSJacob Pan 	u8 buf[2];
111de89bd7fSJacob Pan 
112de89bd7fSJacob Pan 	if (regmap_bulk_read(regmap, address, buf, 2))
113de89bd7fSJacob Pan 		return -EIO;
114de89bd7fSJacob Pan 	*val = (buf[0] << 4) + ((buf[1] >> 4) & 0x0F);
115de89bd7fSJacob Pan 
116de89bd7fSJacob Pan 	return IIO_VAL_INT;
117de89bd7fSJacob Pan }
118de89bd7fSJacob Pan 
1199bcf15f7SHans de Goede /*
1209bcf15f7SHans de Goede  * The current-source used for the battery temp-sensor (TS) is shared
1219bcf15f7SHans de Goede  * with the GPADC. For proper fuel-gauge and charger operation the TS
1229bcf15f7SHans de Goede  * current-source needs to be permanently on. But to read the GPADC we
1239bcf15f7SHans de Goede  * need to temporary switch the TS current-source to ondemand, so that
1249bcf15f7SHans de Goede  * the GPADC can use it, otherwise we will always read an all 0 value.
1259bcf15f7SHans de Goede  */
1269bcf15f7SHans de Goede static int axp288_adc_set_ts(struct axp288_adc_info *info,
1279bcf15f7SHans de Goede 			     unsigned int mode, unsigned long address)
128631b010aSHans de Goede {
1293091141dSHans de Goede 	int ret;
1303091141dSHans de Goede 
1319bcf15f7SHans de Goede 	/* No need to switch the current-source if the TS pin is disabled */
1329bcf15f7SHans de Goede 	if (!info->ts_enabled)
1339bcf15f7SHans de Goede 		return 0;
1349bcf15f7SHans de Goede 
1359bcf15f7SHans de Goede 	/* Channels other than GPADC do not need the current source */
136631b010aSHans de Goede 	if (address != AXP288_GP_ADC_H)
137631b010aSHans de Goede 		return 0;
138631b010aSHans de Goede 
1399bcf15f7SHans de Goede 	ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
1409bcf15f7SHans de Goede 				 AXP288_ADC_TS_CURRENT_ON_OFF_MASK, mode);
1413091141dSHans de Goede 	if (ret)
1423091141dSHans de Goede 		return ret;
1433091141dSHans de Goede 
1443091141dSHans de Goede 	/* When switching to the GPADC pin give things some time to settle */
1459bcf15f7SHans de Goede 	if (mode == AXP288_ADC_TS_CURRENT_ON_ONDEMAND)
1463091141dSHans de Goede 		usleep_range(6000, 10000);
1473091141dSHans de Goede 
1483091141dSHans de Goede 	return 0;
149631b010aSHans de Goede }
150631b010aSHans de Goede 
151de89bd7fSJacob Pan static int axp288_adc_read_raw(struct iio_dev *indio_dev,
152de89bd7fSJacob Pan 			struct iio_chan_spec const *chan,
153de89bd7fSJacob Pan 			int *val, int *val2, long mask)
154de89bd7fSJacob Pan {
155de89bd7fSJacob Pan 	int ret;
156de89bd7fSJacob Pan 	struct axp288_adc_info *info = iio_priv(indio_dev);
157de89bd7fSJacob Pan 
158de89bd7fSJacob Pan 	mutex_lock(&indio_dev->mlock);
159de89bd7fSJacob Pan 	switch (mask) {
160de89bd7fSJacob Pan 	case IIO_CHAN_INFO_RAW:
1619bcf15f7SHans de Goede 		if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON_ONDEMAND,
162631b010aSHans de Goede 					chan->address)) {
163631b010aSHans de Goede 			dev_err(&indio_dev->dev, "GPADC mode\n");
164631b010aSHans de Goede 			ret = -EINVAL;
165631b010aSHans de Goede 			break;
166631b010aSHans de Goede 		}
167de89bd7fSJacob Pan 		ret = axp288_adc_read_channel(val, chan->address, info->regmap);
1689bcf15f7SHans de Goede 		if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON,
169631b010aSHans de Goede 						chan->address))
170631b010aSHans de Goede 			dev_err(&indio_dev->dev, "TS pin restore\n");
171de89bd7fSJacob Pan 		break;
172de89bd7fSJacob Pan 	default:
173de89bd7fSJacob Pan 		ret = -EINVAL;
174de89bd7fSJacob Pan 	}
175de89bd7fSJacob Pan 	mutex_unlock(&indio_dev->mlock);
176de89bd7fSJacob Pan 
177de89bd7fSJacob Pan 	return ret;
178de89bd7fSJacob Pan }
179de89bd7fSJacob Pan 
1809bcf15f7SHans de Goede static int axp288_adc_initialize(struct axp288_adc_info *info)
181631b010aSHans de Goede {
1829bcf15f7SHans de Goede 	int ret, adc_enable_val;
183631b010aSHans de Goede 
1849bcf15f7SHans de Goede 	/*
1859bcf15f7SHans de Goede 	 * Determine if the TS pin is enabled and set the TS current-source
1869bcf15f7SHans de Goede 	 * accordingly.
1879bcf15f7SHans de Goede 	 */
1889bcf15f7SHans de Goede 	ret = regmap_read(info->regmap, AXP20X_ADC_EN1, &adc_enable_val);
1899bcf15f7SHans de Goede 	if (ret)
1909bcf15f7SHans de Goede 		return ret;
1919bcf15f7SHans de Goede 
1929bcf15f7SHans de Goede 	if (adc_enable_val & AXP288_ADC_TS_ENABLE) {
1939bcf15f7SHans de Goede 		info->ts_enabled = true;
1949bcf15f7SHans de Goede 		ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
1959bcf15f7SHans de Goede 					 AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
1969bcf15f7SHans de Goede 					 AXP288_ADC_TS_CURRENT_ON);
1979bcf15f7SHans de Goede 	} else {
1989bcf15f7SHans de Goede 		info->ts_enabled = false;
1999bcf15f7SHans de Goede 		ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
2009bcf15f7SHans de Goede 					 AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
2019bcf15f7SHans de Goede 					 AXP288_ADC_TS_CURRENT_OFF);
2029bcf15f7SHans de Goede 	}
2039bcf15f7SHans de Goede 	if (ret)
2049bcf15f7SHans de Goede 		return ret;
2059bcf15f7SHans de Goede 
2069bcf15f7SHans de Goede 	/* Turn on the ADC for all channels except TS, leave TS as is */
2079bcf15f7SHans de Goede 	return regmap_update_bits(info->regmap, AXP20X_ADC_EN1,
2089bcf15f7SHans de Goede 				  AXP288_ADC_EN_MASK, AXP288_ADC_EN_MASK);
209631b010aSHans de Goede }
210631b010aSHans de Goede 
211de89bd7fSJacob Pan static const struct iio_info axp288_adc_iio_info = {
212de89bd7fSJacob Pan 	.read_raw = &axp288_adc_read_raw,
213de89bd7fSJacob Pan };
214de89bd7fSJacob Pan 
215de89bd7fSJacob Pan static int axp288_adc_probe(struct platform_device *pdev)
216de89bd7fSJacob Pan {
217de89bd7fSJacob Pan 	int ret;
218de89bd7fSJacob Pan 	struct axp288_adc_info *info;
219de89bd7fSJacob Pan 	struct iio_dev *indio_dev;
220de89bd7fSJacob Pan 	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
221de89bd7fSJacob Pan 
222de89bd7fSJacob Pan 	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
223de89bd7fSJacob Pan 	if (!indio_dev)
224de89bd7fSJacob Pan 		return -ENOMEM;
225de89bd7fSJacob Pan 
226de89bd7fSJacob Pan 	info = iio_priv(indio_dev);
227de89bd7fSJacob Pan 	info->irq = platform_get_irq(pdev, 0);
2287c279229SStephen Boyd 	if (info->irq < 0)
229de89bd7fSJacob Pan 		return info->irq;
230de89bd7fSJacob Pan 	platform_set_drvdata(pdev, indio_dev);
231de89bd7fSJacob Pan 	info->regmap = axp20x->regmap;
232de89bd7fSJacob Pan 	/*
233de89bd7fSJacob Pan 	 * Set ADC to enabled state at all time, including system suspend.
234de89bd7fSJacob Pan 	 * otherwise internal fuel gauge functionality may be affected.
235de89bd7fSJacob Pan 	 */
2369bcf15f7SHans de Goede 	ret = axp288_adc_initialize(info);
237de89bd7fSJacob Pan 	if (ret) {
238de89bd7fSJacob Pan 		dev_err(&pdev->dev, "unable to enable ADC device\n");
239de89bd7fSJacob Pan 		return ret;
240de89bd7fSJacob Pan 	}
241de89bd7fSJacob Pan 
242de89bd7fSJacob Pan 	indio_dev->dev.parent = &pdev->dev;
243de89bd7fSJacob Pan 	indio_dev->name = pdev->name;
244de89bd7fSJacob Pan 	indio_dev->channels = axp288_adc_channels;
245de89bd7fSJacob Pan 	indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels);
246de89bd7fSJacob Pan 	indio_dev->info = &axp288_adc_iio_info;
247de89bd7fSJacob Pan 	indio_dev->modes = INDIO_DIRECT_MODE;
248de89bd7fSJacob Pan 	ret = iio_map_array_register(indio_dev, axp288_adc_default_maps);
249de89bd7fSJacob Pan 	if (ret < 0)
250de89bd7fSJacob Pan 		return ret;
251de89bd7fSJacob Pan 
252de89bd7fSJacob Pan 	ret = iio_device_register(indio_dev);
253de89bd7fSJacob Pan 	if (ret < 0) {
254de89bd7fSJacob Pan 		dev_err(&pdev->dev, "unable to register iio device\n");
255de89bd7fSJacob Pan 		goto err_array_unregister;
256de89bd7fSJacob Pan 	}
257de89bd7fSJacob Pan 	return 0;
258de89bd7fSJacob Pan 
259de89bd7fSJacob Pan err_array_unregister:
260de89bd7fSJacob Pan 	iio_map_array_unregister(indio_dev);
261de89bd7fSJacob Pan 
262de89bd7fSJacob Pan 	return ret;
263de89bd7fSJacob Pan }
264de89bd7fSJacob Pan 
265de89bd7fSJacob Pan static int axp288_adc_remove(struct platform_device *pdev)
266de89bd7fSJacob Pan {
267de89bd7fSJacob Pan 	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
268de89bd7fSJacob Pan 
269de89bd7fSJacob Pan 	iio_device_unregister(indio_dev);
270de89bd7fSJacob Pan 	iio_map_array_unregister(indio_dev);
271de89bd7fSJacob Pan 
272de89bd7fSJacob Pan 	return 0;
273de89bd7fSJacob Pan }
274de89bd7fSJacob Pan 
275e682173fSKrzysztof Kozlowski static const struct platform_device_id axp288_adc_id_table[] = {
27629ec0a25SAaron Lu 	{ .name = "axp288_adc" },
27729ec0a25SAaron Lu 	{},
27829ec0a25SAaron Lu };
27929ec0a25SAaron Lu 
280de89bd7fSJacob Pan static struct platform_driver axp288_adc_driver = {
281de89bd7fSJacob Pan 	.probe = axp288_adc_probe,
282de89bd7fSJacob Pan 	.remove = axp288_adc_remove,
28329ec0a25SAaron Lu 	.id_table = axp288_adc_id_table,
284de89bd7fSJacob Pan 	.driver = {
285de89bd7fSJacob Pan 		.name = "axp288_adc",
286de89bd7fSJacob Pan 	},
287de89bd7fSJacob Pan };
288de89bd7fSJacob Pan 
28929ec0a25SAaron Lu MODULE_DEVICE_TABLE(platform, axp288_adc_id_table);
29029ec0a25SAaron Lu 
291de89bd7fSJacob Pan module_platform_driver(axp288_adc_driver);
292de89bd7fSJacob Pan 
293de89bd7fSJacob Pan MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
294de89bd7fSJacob Pan MODULE_DESCRIPTION("X-Powers AXP288 ADC Driver");
295de89bd7fSJacob Pan MODULE_LICENSE("GPL");
296