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