xref: /openbmc/linux/drivers/iio/adc/axp288_adc.c (revision ed3aa671)
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 
1097291741SHans de Goede #include <linux/dmi.h>
11de89bd7fSJacob Pan #include <linux/module.h>
12*ed3aa671SNuno Sá #include <linux/mutex.h>
13de89bd7fSJacob Pan #include <linux/kernel.h>
14de89bd7fSJacob Pan #include <linux/device.h>
15de89bd7fSJacob Pan #include <linux/regmap.h>
16de89bd7fSJacob Pan #include <linux/mfd/axp20x.h>
17de89bd7fSJacob Pan #include <linux/platform_device.h>
18de89bd7fSJacob Pan 
19de89bd7fSJacob Pan #include <linux/iio/iio.h>
20de89bd7fSJacob Pan #include <linux/iio/machine.h>
21de89bd7fSJacob Pan #include <linux/iio/driver.h>
22de89bd7fSJacob Pan 
239bcf15f7SHans de Goede /*
249bcf15f7SHans de Goede  * This mask enables all ADCs except for the battery temp-sensor (TS), that is
259bcf15f7SHans de Goede  * left as-is to avoid breaking charging on devices without a temp-sensor.
269bcf15f7SHans de Goede  */
279bcf15f7SHans de Goede #define AXP288_ADC_EN_MASK				0xF0
289bcf15f7SHans de Goede #define AXP288_ADC_TS_ENABLE				0x01
299bcf15f7SHans de Goede 
3097291741SHans de Goede #define AXP288_ADC_TS_BIAS_MASK				GENMASK(5, 4)
3197291741SHans de Goede #define AXP288_ADC_TS_BIAS_20UA				(0 << 4)
3297291741SHans de Goede #define AXP288_ADC_TS_BIAS_40UA				(1 << 4)
3397291741SHans de Goede #define AXP288_ADC_TS_BIAS_60UA				(2 << 4)
3497291741SHans de Goede #define AXP288_ADC_TS_BIAS_80UA				(3 << 4)
359bcf15f7SHans de Goede #define AXP288_ADC_TS_CURRENT_ON_OFF_MASK		GENMASK(1, 0)
369bcf15f7SHans de Goede #define AXP288_ADC_TS_CURRENT_OFF			(0 << 0)
379bcf15f7SHans de Goede #define AXP288_ADC_TS_CURRENT_ON_WHEN_CHARGING		(1 << 0)
389bcf15f7SHans de Goede #define AXP288_ADC_TS_CURRENT_ON_ONDEMAND		(2 << 0)
399bcf15f7SHans de Goede #define AXP288_ADC_TS_CURRENT_ON			(3 << 0)
40de89bd7fSJacob Pan 
41de89bd7fSJacob Pan enum axp288_adc_id {
42de89bd7fSJacob Pan 	AXP288_ADC_TS,
43de89bd7fSJacob Pan 	AXP288_ADC_PMIC,
44de89bd7fSJacob Pan 	AXP288_ADC_GP,
45de89bd7fSJacob Pan 	AXP288_ADC_BATT_CHRG_I,
46de89bd7fSJacob Pan 	AXP288_ADC_BATT_DISCHRG_I,
47de89bd7fSJacob Pan 	AXP288_ADC_BATT_V,
48de89bd7fSJacob Pan 	AXP288_ADC_NR_CHAN,
49de89bd7fSJacob Pan };
50de89bd7fSJacob Pan 
51de89bd7fSJacob Pan struct axp288_adc_info {
52de89bd7fSJacob Pan 	int irq;
53de89bd7fSJacob Pan 	struct regmap *regmap;
54*ed3aa671SNuno Sá 	/* lock to protect against multiple access to the device */
55*ed3aa671SNuno Sá 	struct mutex lock;
569bcf15f7SHans de Goede 	bool ts_enabled;
57de89bd7fSJacob Pan };
58de89bd7fSJacob Pan 
597ca6574aSColin Ian King static const struct iio_chan_spec axp288_adc_channels[] = {
60de89bd7fSJacob Pan 	{
61de89bd7fSJacob Pan 		.indexed = 1,
62de89bd7fSJacob Pan 		.type = IIO_TEMP,
63de89bd7fSJacob Pan 		.channel = 0,
64de89bd7fSJacob Pan 		.address = AXP288_TS_ADC_H,
65de89bd7fSJacob Pan 		.datasheet_name = "TS_PIN",
66d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
67de89bd7fSJacob Pan 	}, {
68de89bd7fSJacob Pan 		.indexed = 1,
69de89bd7fSJacob Pan 		.type = IIO_TEMP,
70de89bd7fSJacob Pan 		.channel = 1,
71de89bd7fSJacob Pan 		.address = AXP288_PMIC_ADC_H,
72de89bd7fSJacob Pan 		.datasheet_name = "PMIC_TEMP",
73d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
74de89bd7fSJacob Pan 	}, {
75de89bd7fSJacob Pan 		.indexed = 1,
76de89bd7fSJacob Pan 		.type = IIO_TEMP,
77de89bd7fSJacob Pan 		.channel = 2,
78de89bd7fSJacob Pan 		.address = AXP288_GP_ADC_H,
79de89bd7fSJacob Pan 		.datasheet_name = "GPADC",
80d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
81de89bd7fSJacob Pan 	}, {
82de89bd7fSJacob Pan 		.indexed = 1,
83de89bd7fSJacob Pan 		.type = IIO_CURRENT,
84de89bd7fSJacob Pan 		.channel = 3,
85de89bd7fSJacob Pan 		.address = AXP20X_BATT_CHRG_I_H,
86de89bd7fSJacob Pan 		.datasheet_name = "BATT_CHG_I",
87d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
88de89bd7fSJacob Pan 	}, {
89de89bd7fSJacob Pan 		.indexed = 1,
90de89bd7fSJacob Pan 		.type = IIO_CURRENT,
91de89bd7fSJacob Pan 		.channel = 4,
92de89bd7fSJacob Pan 		.address = AXP20X_BATT_DISCHRG_I_H,
93de89bd7fSJacob Pan 		.datasheet_name = "BATT_DISCHRG_I",
94d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
95de89bd7fSJacob Pan 	}, {
96de89bd7fSJacob Pan 		.indexed = 1,
97de89bd7fSJacob Pan 		.type = IIO_VOLTAGE,
98de89bd7fSJacob Pan 		.channel = 5,
99de89bd7fSJacob Pan 		.address = AXP20X_BATT_V_H,
100de89bd7fSJacob Pan 		.datasheet_name = "BATT_V",
101d0716b0eSJacob Pan 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
102de89bd7fSJacob Pan 	},
103de89bd7fSJacob Pan };
104de89bd7fSJacob Pan 
105de89bd7fSJacob Pan /* for consumer drivers */
106de89bd7fSJacob Pan static struct iio_map axp288_adc_default_maps[] = {
1078d05ffd2SLukas Wunner 	IIO_MAP("TS_PIN", "axp288-batt", "axp288-batt-temp"),
1088d05ffd2SLukas Wunner 	IIO_MAP("PMIC_TEMP", "axp288-pmic", "axp288-pmic-temp"),
1098d05ffd2SLukas Wunner 	IIO_MAP("GPADC", "axp288-gpadc", "axp288-system-temp"),
1108d05ffd2SLukas Wunner 	IIO_MAP("BATT_CHG_I", "axp288-chrg", "axp288-chrg-curr"),
1118d05ffd2SLukas Wunner 	IIO_MAP("BATT_DISCHRG_I", "axp288-chrg", "axp288-chrg-d-curr"),
1128d05ffd2SLukas Wunner 	IIO_MAP("BATT_V", "axp288-batt", "axp288-batt-volt"),
113de89bd7fSJacob Pan 	{},
114de89bd7fSJacob Pan };
115de89bd7fSJacob Pan 
axp288_adc_read_channel(int * val,unsigned long address,struct regmap * regmap)116de89bd7fSJacob Pan static int axp288_adc_read_channel(int *val, unsigned long address,
117de89bd7fSJacob Pan 				struct regmap *regmap)
118de89bd7fSJacob Pan {
119de89bd7fSJacob Pan 	u8 buf[2];
120de89bd7fSJacob Pan 
121de89bd7fSJacob Pan 	if (regmap_bulk_read(regmap, address, buf, 2))
122de89bd7fSJacob Pan 		return -EIO;
123de89bd7fSJacob Pan 	*val = (buf[0] << 4) + ((buf[1] >> 4) & 0x0F);
124de89bd7fSJacob Pan 
125de89bd7fSJacob Pan 	return IIO_VAL_INT;
126de89bd7fSJacob Pan }
127de89bd7fSJacob Pan 
1289bcf15f7SHans de Goede /*
1299bcf15f7SHans de Goede  * The current-source used for the battery temp-sensor (TS) is shared
1309bcf15f7SHans de Goede  * with the GPADC. For proper fuel-gauge and charger operation the TS
1319bcf15f7SHans de Goede  * current-source needs to be permanently on. But to read the GPADC we
1329bcf15f7SHans de Goede  * need to temporary switch the TS current-source to ondemand, so that
1339bcf15f7SHans de Goede  * the GPADC can use it, otherwise we will always read an all 0 value.
1349bcf15f7SHans de Goede  */
axp288_adc_set_ts(struct axp288_adc_info * info,unsigned int mode,unsigned long address)1359bcf15f7SHans de Goede static int axp288_adc_set_ts(struct axp288_adc_info *info,
1369bcf15f7SHans de Goede 			     unsigned int mode, unsigned long address)
137631b010aSHans de Goede {
1383091141dSHans de Goede 	int ret;
1393091141dSHans de Goede 
1409bcf15f7SHans de Goede 	/* No need to switch the current-source if the TS pin is disabled */
1419bcf15f7SHans de Goede 	if (!info->ts_enabled)
1429bcf15f7SHans de Goede 		return 0;
1439bcf15f7SHans de Goede 
1449bcf15f7SHans de Goede 	/* Channels other than GPADC do not need the current source */
145631b010aSHans de Goede 	if (address != AXP288_GP_ADC_H)
146631b010aSHans de Goede 		return 0;
147631b010aSHans de Goede 
1489bcf15f7SHans de Goede 	ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
1499bcf15f7SHans de Goede 				 AXP288_ADC_TS_CURRENT_ON_OFF_MASK, mode);
1503091141dSHans de Goede 	if (ret)
1513091141dSHans de Goede 		return ret;
1523091141dSHans de Goede 
1533091141dSHans de Goede 	/* When switching to the GPADC pin give things some time to settle */
1549bcf15f7SHans de Goede 	if (mode == AXP288_ADC_TS_CURRENT_ON_ONDEMAND)
1553091141dSHans de Goede 		usleep_range(6000, 10000);
1563091141dSHans de Goede 
1573091141dSHans de Goede 	return 0;
158631b010aSHans de Goede }
159631b010aSHans de Goede 
axp288_adc_read_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int * val,int * val2,long mask)160de89bd7fSJacob Pan static int axp288_adc_read_raw(struct iio_dev *indio_dev,
161de89bd7fSJacob Pan 			struct iio_chan_spec const *chan,
162de89bd7fSJacob Pan 			int *val, int *val2, long mask)
163de89bd7fSJacob Pan {
164de89bd7fSJacob Pan 	int ret;
165de89bd7fSJacob Pan 	struct axp288_adc_info *info = iio_priv(indio_dev);
166de89bd7fSJacob Pan 
167*ed3aa671SNuno Sá 	mutex_lock(&info->lock);
168de89bd7fSJacob Pan 	switch (mask) {
169de89bd7fSJacob Pan 	case IIO_CHAN_INFO_RAW:
1709bcf15f7SHans de Goede 		if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON_ONDEMAND,
171631b010aSHans de Goede 					chan->address)) {
172631b010aSHans de Goede 			dev_err(&indio_dev->dev, "GPADC mode\n");
173631b010aSHans de Goede 			ret = -EINVAL;
174631b010aSHans de Goede 			break;
175631b010aSHans de Goede 		}
176de89bd7fSJacob Pan 		ret = axp288_adc_read_channel(val, chan->address, info->regmap);
1779bcf15f7SHans de Goede 		if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON,
178631b010aSHans de Goede 						chan->address))
179631b010aSHans de Goede 			dev_err(&indio_dev->dev, "TS pin restore\n");
180de89bd7fSJacob Pan 		break;
181de89bd7fSJacob Pan 	default:
182de89bd7fSJacob Pan 		ret = -EINVAL;
183de89bd7fSJacob Pan 	}
184*ed3aa671SNuno Sá 	mutex_unlock(&info->lock);
185de89bd7fSJacob Pan 
186de89bd7fSJacob Pan 	return ret;
187de89bd7fSJacob Pan }
188de89bd7fSJacob Pan 
18997291741SHans de Goede /*
19097291741SHans de Goede  * We rely on the machine's firmware to correctly setup the TS pin bias current
19197291741SHans de Goede  * at boot. This lists systems with broken fw where we need to set it ourselves.
19297291741SHans de Goede  */
19397291741SHans de Goede static const struct dmi_system_id axp288_adc_ts_bias_override[] = {
19497291741SHans de Goede 	{
19597291741SHans de Goede 		/* Lenovo Ideapad 100S (11 inch) */
19697291741SHans de Goede 		.matches = {
19797291741SHans de Goede 		  DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
19897291741SHans de Goede 		  DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 100S-11IBY"),
19997291741SHans de Goede 		},
20097291741SHans de Goede 		.driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA,
20197291741SHans de Goede 	},
20204805839SHans de Goede 	{
20304805839SHans de Goede 		/* Nuvision Solo 10 Draw */
20404805839SHans de Goede 		.matches = {
20504805839SHans de Goede 		  DMI_MATCH(DMI_SYS_VENDOR, "TMAX"),
20604805839SHans de Goede 		  DMI_MATCH(DMI_PRODUCT_NAME, "TM101W610L"),
20704805839SHans de Goede 		},
20804805839SHans de Goede 		.driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA,
20904805839SHans de Goede 	},
21097291741SHans de Goede 	{}
21197291741SHans de Goede };
21297291741SHans de Goede 
axp288_adc_initialize(struct axp288_adc_info * info)2139bcf15f7SHans de Goede static int axp288_adc_initialize(struct axp288_adc_info *info)
214631b010aSHans de Goede {
21597291741SHans de Goede 	const struct dmi_system_id *bias_override;
2169bcf15f7SHans de Goede 	int ret, adc_enable_val;
217631b010aSHans de Goede 
21897291741SHans de Goede 	bias_override = dmi_first_match(axp288_adc_ts_bias_override);
21997291741SHans de Goede 	if (bias_override) {
22097291741SHans de Goede 		ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
22197291741SHans de Goede 					 AXP288_ADC_TS_BIAS_MASK,
22297291741SHans de Goede 					 (uintptr_t)bias_override->driver_data);
22397291741SHans de Goede 		if (ret)
22497291741SHans de Goede 			return ret;
22597291741SHans de Goede 	}
22697291741SHans de Goede 
2279bcf15f7SHans de Goede 	/*
2289bcf15f7SHans de Goede 	 * Determine if the TS pin is enabled and set the TS current-source
2299bcf15f7SHans de Goede 	 * accordingly.
2309bcf15f7SHans de Goede 	 */
2319bcf15f7SHans de Goede 	ret = regmap_read(info->regmap, AXP20X_ADC_EN1, &adc_enable_val);
2329bcf15f7SHans de Goede 	if (ret)
2339bcf15f7SHans de Goede 		return ret;
2349bcf15f7SHans de Goede 
2359bcf15f7SHans de Goede 	if (adc_enable_val & AXP288_ADC_TS_ENABLE) {
2369bcf15f7SHans de Goede 		info->ts_enabled = true;
2379bcf15f7SHans de Goede 		ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
2389bcf15f7SHans de Goede 					 AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
2399bcf15f7SHans de Goede 					 AXP288_ADC_TS_CURRENT_ON);
2409bcf15f7SHans de Goede 	} else {
2419bcf15f7SHans de Goede 		info->ts_enabled = false;
2429bcf15f7SHans de Goede 		ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
2439bcf15f7SHans de Goede 					 AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
2449bcf15f7SHans de Goede 					 AXP288_ADC_TS_CURRENT_OFF);
2459bcf15f7SHans de Goede 	}
2469bcf15f7SHans de Goede 	if (ret)
2479bcf15f7SHans de Goede 		return ret;
2489bcf15f7SHans de Goede 
2499bcf15f7SHans de Goede 	/* Turn on the ADC for all channels except TS, leave TS as is */
2509bcf15f7SHans de Goede 	return regmap_update_bits(info->regmap, AXP20X_ADC_EN1,
2519bcf15f7SHans de Goede 				  AXP288_ADC_EN_MASK, AXP288_ADC_EN_MASK);
252631b010aSHans de Goede }
253631b010aSHans de Goede 
254de89bd7fSJacob Pan static const struct iio_info axp288_adc_iio_info = {
255de89bd7fSJacob Pan 	.read_raw = &axp288_adc_read_raw,
256de89bd7fSJacob Pan };
257de89bd7fSJacob Pan 
axp288_adc_probe(struct platform_device * pdev)258de89bd7fSJacob Pan static int axp288_adc_probe(struct platform_device *pdev)
259de89bd7fSJacob Pan {
260de89bd7fSJacob Pan 	int ret;
261de89bd7fSJacob Pan 	struct axp288_adc_info *info;
262de89bd7fSJacob Pan 	struct iio_dev *indio_dev;
263de89bd7fSJacob Pan 	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
264de89bd7fSJacob Pan 
265de89bd7fSJacob Pan 	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
266de89bd7fSJacob Pan 	if (!indio_dev)
267de89bd7fSJacob Pan 		return -ENOMEM;
268de89bd7fSJacob Pan 
269de89bd7fSJacob Pan 	info = iio_priv(indio_dev);
270de89bd7fSJacob Pan 	info->irq = platform_get_irq(pdev, 0);
2717c279229SStephen Boyd 	if (info->irq < 0)
272de89bd7fSJacob Pan 		return info->irq;
273298fdedcSAlexandru Ardelean 
274de89bd7fSJacob Pan 	info->regmap = axp20x->regmap;
275de89bd7fSJacob Pan 	/*
276de89bd7fSJacob Pan 	 * Set ADC to enabled state at all time, including system suspend.
277de89bd7fSJacob Pan 	 * otherwise internal fuel gauge functionality may be affected.
278de89bd7fSJacob Pan 	 */
2799bcf15f7SHans de Goede 	ret = axp288_adc_initialize(info);
280de89bd7fSJacob Pan 	if (ret) {
281de89bd7fSJacob Pan 		dev_err(&pdev->dev, "unable to enable ADC device\n");
282de89bd7fSJacob Pan 		return ret;
283de89bd7fSJacob Pan 	}
284de89bd7fSJacob Pan 
285de89bd7fSJacob Pan 	indio_dev->name = pdev->name;
286de89bd7fSJacob Pan 	indio_dev->channels = axp288_adc_channels;
287de89bd7fSJacob Pan 	indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels);
288de89bd7fSJacob Pan 	indio_dev->info = &axp288_adc_iio_info;
289de89bd7fSJacob Pan 	indio_dev->modes = INDIO_DIRECT_MODE;
290298fdedcSAlexandru Ardelean 
291298fdedcSAlexandru Ardelean 	ret = devm_iio_map_array_register(&pdev->dev, indio_dev, axp288_adc_default_maps);
292de89bd7fSJacob Pan 	if (ret < 0)
293de89bd7fSJacob Pan 		return ret;
294de89bd7fSJacob Pan 
295*ed3aa671SNuno Sá 	mutex_init(&info->lock);
296*ed3aa671SNuno Sá 
297298fdedcSAlexandru Ardelean 	return devm_iio_device_register(&pdev->dev, indio_dev);
298de89bd7fSJacob Pan }
299de89bd7fSJacob Pan 
300e682173fSKrzysztof Kozlowski static const struct platform_device_id axp288_adc_id_table[] = {
30129ec0a25SAaron Lu 	{ .name = "axp288_adc" },
30229ec0a25SAaron Lu 	{},
30329ec0a25SAaron Lu };
30429ec0a25SAaron Lu 
305de89bd7fSJacob Pan static struct platform_driver axp288_adc_driver = {
306de89bd7fSJacob Pan 	.probe = axp288_adc_probe,
30729ec0a25SAaron Lu 	.id_table = axp288_adc_id_table,
308de89bd7fSJacob Pan 	.driver = {
309de89bd7fSJacob Pan 		.name = "axp288_adc",
310de89bd7fSJacob Pan 	},
311de89bd7fSJacob Pan };
312de89bd7fSJacob Pan 
31329ec0a25SAaron Lu MODULE_DEVICE_TABLE(platform, axp288_adc_id_table);
31429ec0a25SAaron Lu 
315de89bd7fSJacob Pan module_platform_driver(axp288_adc_driver);
316de89bd7fSJacob Pan 
317de89bd7fSJacob Pan MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
318de89bd7fSJacob Pan MODULE_DESCRIPTION("X-Powers AXP288 ADC Driver");
319de89bd7fSJacob Pan MODULE_LICENSE("GPL");
320