cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

axp288_adc.c (8490B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * axp288_adc.c - X-Powers AXP288 PMIC ADC Driver
      4 *
      5 * Copyright (C) 2014 Intel Corporation
      6 *
      7 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      8 */
      9
     10#include <linux/dmi.h>
     11#include <linux/module.h>
     12#include <linux/kernel.h>
     13#include <linux/device.h>
     14#include <linux/regmap.h>
     15#include <linux/mfd/axp20x.h>
     16#include <linux/platform_device.h>
     17
     18#include <linux/iio/iio.h>
     19#include <linux/iio/machine.h>
     20#include <linux/iio/driver.h>
     21
     22/*
     23 * This mask enables all ADCs except for the battery temp-sensor (TS), that is
     24 * left as-is to avoid breaking charging on devices without a temp-sensor.
     25 */
     26#define AXP288_ADC_EN_MASK				0xF0
     27#define AXP288_ADC_TS_ENABLE				0x01
     28
     29#define AXP288_ADC_TS_BIAS_MASK				GENMASK(5, 4)
     30#define AXP288_ADC_TS_BIAS_20UA				(0 << 4)
     31#define AXP288_ADC_TS_BIAS_40UA				(1 << 4)
     32#define AXP288_ADC_TS_BIAS_60UA				(2 << 4)
     33#define AXP288_ADC_TS_BIAS_80UA				(3 << 4)
     34#define AXP288_ADC_TS_CURRENT_ON_OFF_MASK		GENMASK(1, 0)
     35#define AXP288_ADC_TS_CURRENT_OFF			(0 << 0)
     36#define AXP288_ADC_TS_CURRENT_ON_WHEN_CHARGING		(1 << 0)
     37#define AXP288_ADC_TS_CURRENT_ON_ONDEMAND		(2 << 0)
     38#define AXP288_ADC_TS_CURRENT_ON			(3 << 0)
     39
     40enum axp288_adc_id {
     41	AXP288_ADC_TS,
     42	AXP288_ADC_PMIC,
     43	AXP288_ADC_GP,
     44	AXP288_ADC_BATT_CHRG_I,
     45	AXP288_ADC_BATT_DISCHRG_I,
     46	AXP288_ADC_BATT_V,
     47	AXP288_ADC_NR_CHAN,
     48};
     49
     50struct axp288_adc_info {
     51	int irq;
     52	struct regmap *regmap;
     53	bool ts_enabled;
     54};
     55
     56static const struct iio_chan_spec axp288_adc_channels[] = {
     57	{
     58		.indexed = 1,
     59		.type = IIO_TEMP,
     60		.channel = 0,
     61		.address = AXP288_TS_ADC_H,
     62		.datasheet_name = "TS_PIN",
     63		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
     64	}, {
     65		.indexed = 1,
     66		.type = IIO_TEMP,
     67		.channel = 1,
     68		.address = AXP288_PMIC_ADC_H,
     69		.datasheet_name = "PMIC_TEMP",
     70		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
     71	}, {
     72		.indexed = 1,
     73		.type = IIO_TEMP,
     74		.channel = 2,
     75		.address = AXP288_GP_ADC_H,
     76		.datasheet_name = "GPADC",
     77		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
     78	}, {
     79		.indexed = 1,
     80		.type = IIO_CURRENT,
     81		.channel = 3,
     82		.address = AXP20X_BATT_CHRG_I_H,
     83		.datasheet_name = "BATT_CHG_I",
     84		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
     85	}, {
     86		.indexed = 1,
     87		.type = IIO_CURRENT,
     88		.channel = 4,
     89		.address = AXP20X_BATT_DISCHRG_I_H,
     90		.datasheet_name = "BATT_DISCHRG_I",
     91		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
     92	}, {
     93		.indexed = 1,
     94		.type = IIO_VOLTAGE,
     95		.channel = 5,
     96		.address = AXP20X_BATT_V_H,
     97		.datasheet_name = "BATT_V",
     98		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
     99	},
    100};
    101
    102/* for consumer drivers */
    103static struct iio_map axp288_adc_default_maps[] = {
    104	IIO_MAP("TS_PIN", "axp288-batt", "axp288-batt-temp"),
    105	IIO_MAP("PMIC_TEMP", "axp288-pmic", "axp288-pmic-temp"),
    106	IIO_MAP("GPADC", "axp288-gpadc", "axp288-system-temp"),
    107	IIO_MAP("BATT_CHG_I", "axp288-chrg", "axp288-chrg-curr"),
    108	IIO_MAP("BATT_DISCHRG_I", "axp288-chrg", "axp288-chrg-d-curr"),
    109	IIO_MAP("BATT_V", "axp288-batt", "axp288-batt-volt"),
    110	{},
    111};
    112
    113static int axp288_adc_read_channel(int *val, unsigned long address,
    114				struct regmap *regmap)
    115{
    116	u8 buf[2];
    117
    118	if (regmap_bulk_read(regmap, address, buf, 2))
    119		return -EIO;
    120	*val = (buf[0] << 4) + ((buf[1] >> 4) & 0x0F);
    121
    122	return IIO_VAL_INT;
    123}
    124
    125/*
    126 * The current-source used for the battery temp-sensor (TS) is shared
    127 * with the GPADC. For proper fuel-gauge and charger operation the TS
    128 * current-source needs to be permanently on. But to read the GPADC we
    129 * need to temporary switch the TS current-source to ondemand, so that
    130 * the GPADC can use it, otherwise we will always read an all 0 value.
    131 */
    132static int axp288_adc_set_ts(struct axp288_adc_info *info,
    133			     unsigned int mode, unsigned long address)
    134{
    135	int ret;
    136
    137	/* No need to switch the current-source if the TS pin is disabled */
    138	if (!info->ts_enabled)
    139		return 0;
    140
    141	/* Channels other than GPADC do not need the current source */
    142	if (address != AXP288_GP_ADC_H)
    143		return 0;
    144
    145	ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
    146				 AXP288_ADC_TS_CURRENT_ON_OFF_MASK, mode);
    147	if (ret)
    148		return ret;
    149
    150	/* When switching to the GPADC pin give things some time to settle */
    151	if (mode == AXP288_ADC_TS_CURRENT_ON_ONDEMAND)
    152		usleep_range(6000, 10000);
    153
    154	return 0;
    155}
    156
    157static int axp288_adc_read_raw(struct iio_dev *indio_dev,
    158			struct iio_chan_spec const *chan,
    159			int *val, int *val2, long mask)
    160{
    161	int ret;
    162	struct axp288_adc_info *info = iio_priv(indio_dev);
    163
    164	mutex_lock(&indio_dev->mlock);
    165	switch (mask) {
    166	case IIO_CHAN_INFO_RAW:
    167		if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON_ONDEMAND,
    168					chan->address)) {
    169			dev_err(&indio_dev->dev, "GPADC mode\n");
    170			ret = -EINVAL;
    171			break;
    172		}
    173		ret = axp288_adc_read_channel(val, chan->address, info->regmap);
    174		if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON,
    175						chan->address))
    176			dev_err(&indio_dev->dev, "TS pin restore\n");
    177		break;
    178	default:
    179		ret = -EINVAL;
    180	}
    181	mutex_unlock(&indio_dev->mlock);
    182
    183	return ret;
    184}
    185
    186/*
    187 * We rely on the machine's firmware to correctly setup the TS pin bias current
    188 * at boot. This lists systems with broken fw where we need to set it ourselves.
    189 */
    190static const struct dmi_system_id axp288_adc_ts_bias_override[] = {
    191	{
    192		/* Lenovo Ideapad 100S (11 inch) */
    193		.matches = {
    194		  DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
    195		  DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 100S-11IBY"),
    196		},
    197		.driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA,
    198	},
    199	{
    200		/* Nuvision Solo 10 Draw */
    201		.matches = {
    202		  DMI_MATCH(DMI_SYS_VENDOR, "TMAX"),
    203		  DMI_MATCH(DMI_PRODUCT_NAME, "TM101W610L"),
    204		},
    205		.driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA,
    206	},
    207	{}
    208};
    209
    210static int axp288_adc_initialize(struct axp288_adc_info *info)
    211{
    212	const struct dmi_system_id *bias_override;
    213	int ret, adc_enable_val;
    214
    215	bias_override = dmi_first_match(axp288_adc_ts_bias_override);
    216	if (bias_override) {
    217		ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
    218					 AXP288_ADC_TS_BIAS_MASK,
    219					 (uintptr_t)bias_override->driver_data);
    220		if (ret)
    221			return ret;
    222	}
    223
    224	/*
    225	 * Determine if the TS pin is enabled and set the TS current-source
    226	 * accordingly.
    227	 */
    228	ret = regmap_read(info->regmap, AXP20X_ADC_EN1, &adc_enable_val);
    229	if (ret)
    230		return ret;
    231
    232	if (adc_enable_val & AXP288_ADC_TS_ENABLE) {
    233		info->ts_enabled = true;
    234		ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
    235					 AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
    236					 AXP288_ADC_TS_CURRENT_ON);
    237	} else {
    238		info->ts_enabled = false;
    239		ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
    240					 AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
    241					 AXP288_ADC_TS_CURRENT_OFF);
    242	}
    243	if (ret)
    244		return ret;
    245
    246	/* Turn on the ADC for all channels except TS, leave TS as is */
    247	return regmap_update_bits(info->regmap, AXP20X_ADC_EN1,
    248				  AXP288_ADC_EN_MASK, AXP288_ADC_EN_MASK);
    249}
    250
    251static const struct iio_info axp288_adc_iio_info = {
    252	.read_raw = &axp288_adc_read_raw,
    253};
    254
    255static int axp288_adc_probe(struct platform_device *pdev)
    256{
    257	int ret;
    258	struct axp288_adc_info *info;
    259	struct iio_dev *indio_dev;
    260	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
    261
    262	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
    263	if (!indio_dev)
    264		return -ENOMEM;
    265
    266	info = iio_priv(indio_dev);
    267	info->irq = platform_get_irq(pdev, 0);
    268	if (info->irq < 0)
    269		return info->irq;
    270
    271	info->regmap = axp20x->regmap;
    272	/*
    273	 * Set ADC to enabled state at all time, including system suspend.
    274	 * otherwise internal fuel gauge functionality may be affected.
    275	 */
    276	ret = axp288_adc_initialize(info);
    277	if (ret) {
    278		dev_err(&pdev->dev, "unable to enable ADC device\n");
    279		return ret;
    280	}
    281
    282	indio_dev->name = pdev->name;
    283	indio_dev->channels = axp288_adc_channels;
    284	indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels);
    285	indio_dev->info = &axp288_adc_iio_info;
    286	indio_dev->modes = INDIO_DIRECT_MODE;
    287
    288	ret = devm_iio_map_array_register(&pdev->dev, indio_dev, axp288_adc_default_maps);
    289	if (ret < 0)
    290		return ret;
    291
    292	return devm_iio_device_register(&pdev->dev, indio_dev);
    293}
    294
    295static const struct platform_device_id axp288_adc_id_table[] = {
    296	{ .name = "axp288_adc" },
    297	{},
    298};
    299
    300static struct platform_driver axp288_adc_driver = {
    301	.probe = axp288_adc_probe,
    302	.id_table = axp288_adc_id_table,
    303	.driver = {
    304		.name = "axp288_adc",
    305	},
    306};
    307
    308MODULE_DEVICE_TABLE(platform, axp288_adc_id_table);
    309
    310module_platform_driver(axp288_adc_driver);
    311
    312MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
    313MODULE_DESCRIPTION("X-Powers AXP288 ADC Driver");
    314MODULE_LICENSE("GPL");