brintos

brintos / linux-shallow public Read only

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