1de89bd7fSJacob Pan /* 2de89bd7fSJacob Pan * axp288_adc.c - X-Powers AXP288 PMIC ADC Driver 3de89bd7fSJacob Pan * 4de89bd7fSJacob Pan * Copyright (C) 2014 Intel Corporation 5de89bd7fSJacob Pan * 6de89bd7fSJacob Pan * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7de89bd7fSJacob Pan * 8de89bd7fSJacob Pan * This program is free software; you can redistribute it and/or modify 9de89bd7fSJacob Pan * it under the terms of the GNU General Public License as published by 10de89bd7fSJacob Pan * the Free Software Foundation; version 2 of the License. 11de89bd7fSJacob Pan * 12de89bd7fSJacob Pan * This program is distributed in the hope that it will be useful, but 13de89bd7fSJacob Pan * WITHOUT ANY WARRANTY; without even the implied warranty of 14de89bd7fSJacob Pan * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15de89bd7fSJacob Pan * General Public License for more details. 16de89bd7fSJacob Pan * 17de89bd7fSJacob Pan */ 18de89bd7fSJacob Pan 19de89bd7fSJacob Pan #include <linux/module.h> 20de89bd7fSJacob Pan #include <linux/kernel.h> 21de89bd7fSJacob Pan #include <linux/device.h> 22de89bd7fSJacob Pan #include <linux/regmap.h> 23de89bd7fSJacob Pan #include <linux/mfd/axp20x.h> 24de89bd7fSJacob Pan #include <linux/platform_device.h> 25de89bd7fSJacob Pan 26de89bd7fSJacob Pan #include <linux/iio/iio.h> 27de89bd7fSJacob Pan #include <linux/iio/machine.h> 28de89bd7fSJacob Pan #include <linux/iio/driver.h> 29de89bd7fSJacob Pan 30de89bd7fSJacob Pan #define AXP288_ADC_EN_MASK 0xF1 31de89bd7fSJacob Pan #define AXP288_ADC_TS_PIN_GPADC 0xF2 32de89bd7fSJacob Pan #define AXP288_ADC_TS_PIN_ON 0xF3 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; 47de89bd7fSJacob Pan }; 48de89bd7fSJacob Pan 49de89bd7fSJacob Pan static const struct iio_chan_spec const axp288_adc_channels[] = { 50de89bd7fSJacob Pan { 51de89bd7fSJacob Pan .indexed = 1, 52de89bd7fSJacob Pan .type = IIO_TEMP, 53de89bd7fSJacob Pan .channel = 0, 54de89bd7fSJacob Pan .address = AXP288_TS_ADC_H, 55de89bd7fSJacob Pan .datasheet_name = "TS_PIN", 56d0716b0eSJacob Pan .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 57de89bd7fSJacob Pan }, { 58de89bd7fSJacob Pan .indexed = 1, 59de89bd7fSJacob Pan .type = IIO_TEMP, 60de89bd7fSJacob Pan .channel = 1, 61de89bd7fSJacob Pan .address = AXP288_PMIC_ADC_H, 62de89bd7fSJacob Pan .datasheet_name = "PMIC_TEMP", 63d0716b0eSJacob Pan .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 64de89bd7fSJacob Pan }, { 65de89bd7fSJacob Pan .indexed = 1, 66de89bd7fSJacob Pan .type = IIO_TEMP, 67de89bd7fSJacob Pan .channel = 2, 68de89bd7fSJacob Pan .address = AXP288_GP_ADC_H, 69de89bd7fSJacob Pan .datasheet_name = "GPADC", 70d0716b0eSJacob Pan .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 71de89bd7fSJacob Pan }, { 72de89bd7fSJacob Pan .indexed = 1, 73de89bd7fSJacob Pan .type = IIO_CURRENT, 74de89bd7fSJacob Pan .channel = 3, 75de89bd7fSJacob Pan .address = AXP20X_BATT_CHRG_I_H, 76de89bd7fSJacob Pan .datasheet_name = "BATT_CHG_I", 77d0716b0eSJacob Pan .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 78de89bd7fSJacob Pan }, { 79de89bd7fSJacob Pan .indexed = 1, 80de89bd7fSJacob Pan .type = IIO_CURRENT, 81de89bd7fSJacob Pan .channel = 4, 82de89bd7fSJacob Pan .address = AXP20X_BATT_DISCHRG_I_H, 83de89bd7fSJacob Pan .datasheet_name = "BATT_DISCHRG_I", 84d0716b0eSJacob Pan .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 85de89bd7fSJacob Pan }, { 86de89bd7fSJacob Pan .indexed = 1, 87de89bd7fSJacob Pan .type = IIO_VOLTAGE, 88de89bd7fSJacob Pan .channel = 5, 89de89bd7fSJacob Pan .address = AXP20X_BATT_V_H, 90de89bd7fSJacob Pan .datasheet_name = "BATT_V", 91d0716b0eSJacob Pan .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 92de89bd7fSJacob Pan }, 93de89bd7fSJacob Pan }; 94de89bd7fSJacob Pan 95de89bd7fSJacob Pan #define AXP288_ADC_MAP(_adc_channel_label, _consumer_dev_name, \ 96de89bd7fSJacob Pan _consumer_channel) \ 97de89bd7fSJacob Pan { \ 98de89bd7fSJacob Pan .adc_channel_label = _adc_channel_label, \ 99de89bd7fSJacob Pan .consumer_dev_name = _consumer_dev_name, \ 100de89bd7fSJacob Pan .consumer_channel = _consumer_channel, \ 101de89bd7fSJacob Pan } 102de89bd7fSJacob Pan 103de89bd7fSJacob Pan /* for consumer drivers */ 104de89bd7fSJacob Pan static struct iio_map axp288_adc_default_maps[] = { 105de89bd7fSJacob Pan AXP288_ADC_MAP("TS_PIN", "axp288-batt", "axp288-batt-temp"), 106de89bd7fSJacob Pan AXP288_ADC_MAP("PMIC_TEMP", "axp288-pmic", "axp288-pmic-temp"), 107de89bd7fSJacob Pan AXP288_ADC_MAP("GPADC", "axp288-gpadc", "axp288-system-temp"), 108de89bd7fSJacob Pan AXP288_ADC_MAP("BATT_CHG_I", "axp288-chrg", "axp288-chrg-curr"), 109de89bd7fSJacob Pan AXP288_ADC_MAP("BATT_DISCHRG_I", "axp288-chrg", "axp288-chrg-d-curr"), 110de89bd7fSJacob Pan AXP288_ADC_MAP("BATT_V", "axp288-batt", "axp288-batt-volt"), 111de89bd7fSJacob Pan {}, 112de89bd7fSJacob Pan }; 113de89bd7fSJacob Pan 114de89bd7fSJacob Pan static int axp288_adc_read_channel(int *val, unsigned long address, 115de89bd7fSJacob Pan struct regmap *regmap) 116de89bd7fSJacob Pan { 117de89bd7fSJacob Pan u8 buf[2]; 118de89bd7fSJacob Pan 119de89bd7fSJacob Pan if (regmap_bulk_read(regmap, address, buf, 2)) 120de89bd7fSJacob Pan return -EIO; 121de89bd7fSJacob Pan *val = (buf[0] << 4) + ((buf[1] >> 4) & 0x0F); 122de89bd7fSJacob Pan 123de89bd7fSJacob Pan return IIO_VAL_INT; 124de89bd7fSJacob Pan } 125de89bd7fSJacob Pan 126de89bd7fSJacob Pan static int axp288_adc_set_ts(struct regmap *regmap, unsigned int mode, 127de89bd7fSJacob Pan unsigned long address) 128de89bd7fSJacob Pan { 129de89bd7fSJacob Pan /* channels other than GPADC do not need to switch TS pin */ 130de89bd7fSJacob Pan if (address != AXP288_GP_ADC_H) 131de89bd7fSJacob Pan return 0; 132de89bd7fSJacob Pan 133de89bd7fSJacob Pan return regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, mode); 134de89bd7fSJacob Pan } 135de89bd7fSJacob Pan 136de89bd7fSJacob Pan static int axp288_adc_read_raw(struct iio_dev *indio_dev, 137de89bd7fSJacob Pan struct iio_chan_spec const *chan, 138de89bd7fSJacob Pan int *val, int *val2, long mask) 139de89bd7fSJacob Pan { 140de89bd7fSJacob Pan int ret; 141de89bd7fSJacob Pan struct axp288_adc_info *info = iio_priv(indio_dev); 142de89bd7fSJacob Pan 143de89bd7fSJacob Pan mutex_lock(&indio_dev->mlock); 144de89bd7fSJacob Pan switch (mask) { 145de89bd7fSJacob Pan case IIO_CHAN_INFO_RAW: 146de89bd7fSJacob Pan if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_GPADC, 147de89bd7fSJacob Pan chan->address)) { 148de89bd7fSJacob Pan dev_err(&indio_dev->dev, "GPADC mode\n"); 149de89bd7fSJacob Pan ret = -EINVAL; 150de89bd7fSJacob Pan break; 151de89bd7fSJacob Pan } 152de89bd7fSJacob Pan ret = axp288_adc_read_channel(val, chan->address, info->regmap); 153de89bd7fSJacob Pan if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_ON, 154de89bd7fSJacob Pan chan->address)) 155de89bd7fSJacob Pan dev_err(&indio_dev->dev, "TS pin restore\n"); 156de89bd7fSJacob Pan break; 157de89bd7fSJacob Pan default: 158de89bd7fSJacob Pan ret = -EINVAL; 159de89bd7fSJacob Pan } 160de89bd7fSJacob Pan mutex_unlock(&indio_dev->mlock); 161de89bd7fSJacob Pan 162de89bd7fSJacob Pan return ret; 163de89bd7fSJacob Pan } 164de89bd7fSJacob Pan 165de89bd7fSJacob Pan static int axp288_adc_set_state(struct regmap *regmap) 166de89bd7fSJacob Pan { 167de89bd7fSJacob Pan /* ADC should be always enabled for internal FG to function */ 168de89bd7fSJacob Pan if (regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, AXP288_ADC_TS_PIN_ON)) 169de89bd7fSJacob Pan return -EIO; 170de89bd7fSJacob Pan 171de89bd7fSJacob Pan return regmap_write(regmap, AXP20X_ADC_EN1, AXP288_ADC_EN_MASK); 172de89bd7fSJacob Pan } 173de89bd7fSJacob Pan 174de89bd7fSJacob Pan static const struct iio_info axp288_adc_iio_info = { 175de89bd7fSJacob Pan .read_raw = &axp288_adc_read_raw, 176de89bd7fSJacob Pan .driver_module = THIS_MODULE, 177de89bd7fSJacob Pan }; 178de89bd7fSJacob Pan 179de89bd7fSJacob Pan static int axp288_adc_probe(struct platform_device *pdev) 180de89bd7fSJacob Pan { 181de89bd7fSJacob Pan int ret; 182de89bd7fSJacob Pan struct axp288_adc_info *info; 183de89bd7fSJacob Pan struct iio_dev *indio_dev; 184de89bd7fSJacob Pan struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); 185de89bd7fSJacob Pan 186de89bd7fSJacob Pan indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); 187de89bd7fSJacob Pan if (!indio_dev) 188de89bd7fSJacob Pan return -ENOMEM; 189de89bd7fSJacob Pan 190de89bd7fSJacob Pan info = iio_priv(indio_dev); 191de89bd7fSJacob Pan info->irq = platform_get_irq(pdev, 0); 192de89bd7fSJacob Pan if (info->irq < 0) { 193de89bd7fSJacob Pan dev_err(&pdev->dev, "no irq resource?\n"); 194de89bd7fSJacob Pan return info->irq; 195de89bd7fSJacob Pan } 196de89bd7fSJacob Pan platform_set_drvdata(pdev, indio_dev); 197de89bd7fSJacob Pan info->regmap = axp20x->regmap; 198de89bd7fSJacob Pan /* 199de89bd7fSJacob Pan * Set ADC to enabled state at all time, including system suspend. 200de89bd7fSJacob Pan * otherwise internal fuel gauge functionality may be affected. 201de89bd7fSJacob Pan */ 202de89bd7fSJacob Pan ret = axp288_adc_set_state(axp20x->regmap); 203de89bd7fSJacob Pan if (ret) { 204de89bd7fSJacob Pan dev_err(&pdev->dev, "unable to enable ADC device\n"); 205de89bd7fSJacob Pan return ret; 206de89bd7fSJacob Pan } 207de89bd7fSJacob Pan 208de89bd7fSJacob Pan indio_dev->dev.parent = &pdev->dev; 209de89bd7fSJacob Pan indio_dev->name = pdev->name; 210de89bd7fSJacob Pan indio_dev->channels = axp288_adc_channels; 211de89bd7fSJacob Pan indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels); 212de89bd7fSJacob Pan indio_dev->info = &axp288_adc_iio_info; 213de89bd7fSJacob Pan indio_dev->modes = INDIO_DIRECT_MODE; 214de89bd7fSJacob Pan ret = iio_map_array_register(indio_dev, axp288_adc_default_maps); 215de89bd7fSJacob Pan if (ret < 0) 216de89bd7fSJacob Pan return ret; 217de89bd7fSJacob Pan 218de89bd7fSJacob Pan ret = iio_device_register(indio_dev); 219de89bd7fSJacob Pan if (ret < 0) { 220de89bd7fSJacob Pan dev_err(&pdev->dev, "unable to register iio device\n"); 221de89bd7fSJacob Pan goto err_array_unregister; 222de89bd7fSJacob Pan } 223de89bd7fSJacob Pan return 0; 224de89bd7fSJacob Pan 225de89bd7fSJacob Pan err_array_unregister: 226de89bd7fSJacob Pan iio_map_array_unregister(indio_dev); 227de89bd7fSJacob Pan 228de89bd7fSJacob Pan return ret; 229de89bd7fSJacob Pan } 230de89bd7fSJacob Pan 231de89bd7fSJacob Pan static int axp288_adc_remove(struct platform_device *pdev) 232de89bd7fSJacob Pan { 233de89bd7fSJacob Pan struct iio_dev *indio_dev = platform_get_drvdata(pdev); 234de89bd7fSJacob Pan 235de89bd7fSJacob Pan iio_device_unregister(indio_dev); 236de89bd7fSJacob Pan iio_map_array_unregister(indio_dev); 237de89bd7fSJacob Pan 238de89bd7fSJacob Pan return 0; 239de89bd7fSJacob Pan } 240de89bd7fSJacob Pan 24129ec0a25SAaron Lu static struct platform_device_id axp288_adc_id_table[] = { 24229ec0a25SAaron Lu { .name = "axp288_adc" }, 24329ec0a25SAaron Lu {}, 24429ec0a25SAaron Lu }; 24529ec0a25SAaron Lu 246de89bd7fSJacob Pan static struct platform_driver axp288_adc_driver = { 247de89bd7fSJacob Pan .probe = axp288_adc_probe, 248de89bd7fSJacob Pan .remove = axp288_adc_remove, 24929ec0a25SAaron Lu .id_table = axp288_adc_id_table, 250de89bd7fSJacob Pan .driver = { 251de89bd7fSJacob Pan .name = "axp288_adc", 252de89bd7fSJacob Pan }, 253de89bd7fSJacob Pan }; 254de89bd7fSJacob Pan 25529ec0a25SAaron Lu MODULE_DEVICE_TABLE(platform, axp288_adc_id_table); 25629ec0a25SAaron Lu 257de89bd7fSJacob Pan module_platform_driver(axp288_adc_driver); 258de89bd7fSJacob Pan 259de89bd7fSJacob Pan MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>"); 260de89bd7fSJacob Pan MODULE_DESCRIPTION("X-Powers AXP288 ADC Driver"); 261de89bd7fSJacob Pan MODULE_LICENSE("GPL"); 262