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