mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 14:41:39 +00:00
d3be83244c
This patch applies the semantic patch: @@ expression I, P, SP; @@ I = devm_iio_device_alloc(P, SP); ... - I->dev.parent = P; It updates 302 files and does 307 deletions. This semantic patch also removes some comments like '/* Establish that the iio_dev is a child of the i2c device */' But this is is only done in case where the block is left empty. The patch does not seem to cover all cases. It looks like in some cases a different variable is used in some cases to assign the parent, but it points to the same reference. In other cases, the block covered by ... may be just too big to be covered by the semantic patch. However, this looks pretty good as well, as it does cover a big bulk of the drivers that should remove the parent assignment. Signed-off-by: Alexandru Ardelean <alexandru.ardelean@analog.com> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
327 lines
8.5 KiB
C
327 lines
8.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* axp288_adc.c - X-Powers AXP288 PMIC ADC Driver
|
|
*
|
|
* Copyright (C) 2014 Intel Corporation
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*/
|
|
|
|
#include <linux/dmi.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/mfd/axp20x.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/machine.h>
|
|
#include <linux/iio/driver.h>
|
|
|
|
/*
|
|
* This mask enables all ADCs except for the battery temp-sensor (TS), that is
|
|
* left as-is to avoid breaking charging on devices without a temp-sensor.
|
|
*/
|
|
#define AXP288_ADC_EN_MASK 0xF0
|
|
#define AXP288_ADC_TS_ENABLE 0x01
|
|
|
|
#define AXP288_ADC_TS_BIAS_MASK GENMASK(5, 4)
|
|
#define AXP288_ADC_TS_BIAS_20UA (0 << 4)
|
|
#define AXP288_ADC_TS_BIAS_40UA (1 << 4)
|
|
#define AXP288_ADC_TS_BIAS_60UA (2 << 4)
|
|
#define AXP288_ADC_TS_BIAS_80UA (3 << 4)
|
|
#define AXP288_ADC_TS_CURRENT_ON_OFF_MASK GENMASK(1, 0)
|
|
#define AXP288_ADC_TS_CURRENT_OFF (0 << 0)
|
|
#define AXP288_ADC_TS_CURRENT_ON_WHEN_CHARGING (1 << 0)
|
|
#define AXP288_ADC_TS_CURRENT_ON_ONDEMAND (2 << 0)
|
|
#define AXP288_ADC_TS_CURRENT_ON (3 << 0)
|
|
|
|
enum axp288_adc_id {
|
|
AXP288_ADC_TS,
|
|
AXP288_ADC_PMIC,
|
|
AXP288_ADC_GP,
|
|
AXP288_ADC_BATT_CHRG_I,
|
|
AXP288_ADC_BATT_DISCHRG_I,
|
|
AXP288_ADC_BATT_V,
|
|
AXP288_ADC_NR_CHAN,
|
|
};
|
|
|
|
struct axp288_adc_info {
|
|
int irq;
|
|
struct regmap *regmap;
|
|
bool ts_enabled;
|
|
};
|
|
|
|
static const struct iio_chan_spec axp288_adc_channels[] = {
|
|
{
|
|
.indexed = 1,
|
|
.type = IIO_TEMP,
|
|
.channel = 0,
|
|
.address = AXP288_TS_ADC_H,
|
|
.datasheet_name = "TS_PIN",
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
|
}, {
|
|
.indexed = 1,
|
|
.type = IIO_TEMP,
|
|
.channel = 1,
|
|
.address = AXP288_PMIC_ADC_H,
|
|
.datasheet_name = "PMIC_TEMP",
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
|
}, {
|
|
.indexed = 1,
|
|
.type = IIO_TEMP,
|
|
.channel = 2,
|
|
.address = AXP288_GP_ADC_H,
|
|
.datasheet_name = "GPADC",
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
|
}, {
|
|
.indexed = 1,
|
|
.type = IIO_CURRENT,
|
|
.channel = 3,
|
|
.address = AXP20X_BATT_CHRG_I_H,
|
|
.datasheet_name = "BATT_CHG_I",
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
|
}, {
|
|
.indexed = 1,
|
|
.type = IIO_CURRENT,
|
|
.channel = 4,
|
|
.address = AXP20X_BATT_DISCHRG_I_H,
|
|
.datasheet_name = "BATT_DISCHRG_I",
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
|
}, {
|
|
.indexed = 1,
|
|
.type = IIO_VOLTAGE,
|
|
.channel = 5,
|
|
.address = AXP20X_BATT_V_H,
|
|
.datasheet_name = "BATT_V",
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
|
},
|
|
};
|
|
|
|
/* for consumer drivers */
|
|
static struct iio_map axp288_adc_default_maps[] = {
|
|
IIO_MAP("TS_PIN", "axp288-batt", "axp288-batt-temp"),
|
|
IIO_MAP("PMIC_TEMP", "axp288-pmic", "axp288-pmic-temp"),
|
|
IIO_MAP("GPADC", "axp288-gpadc", "axp288-system-temp"),
|
|
IIO_MAP("BATT_CHG_I", "axp288-chrg", "axp288-chrg-curr"),
|
|
IIO_MAP("BATT_DISCHRG_I", "axp288-chrg", "axp288-chrg-d-curr"),
|
|
IIO_MAP("BATT_V", "axp288-batt", "axp288-batt-volt"),
|
|
{},
|
|
};
|
|
|
|
static int axp288_adc_read_channel(int *val, unsigned long address,
|
|
struct regmap *regmap)
|
|
{
|
|
u8 buf[2];
|
|
|
|
if (regmap_bulk_read(regmap, address, buf, 2))
|
|
return -EIO;
|
|
*val = (buf[0] << 4) + ((buf[1] >> 4) & 0x0F);
|
|
|
|
return IIO_VAL_INT;
|
|
}
|
|
|
|
/*
|
|
* The current-source used for the battery temp-sensor (TS) is shared
|
|
* with the GPADC. For proper fuel-gauge and charger operation the TS
|
|
* current-source needs to be permanently on. But to read the GPADC we
|
|
* need to temporary switch the TS current-source to ondemand, so that
|
|
* the GPADC can use it, otherwise we will always read an all 0 value.
|
|
*/
|
|
static int axp288_adc_set_ts(struct axp288_adc_info *info,
|
|
unsigned int mode, unsigned long address)
|
|
{
|
|
int ret;
|
|
|
|
/* No need to switch the current-source if the TS pin is disabled */
|
|
if (!info->ts_enabled)
|
|
return 0;
|
|
|
|
/* Channels other than GPADC do not need the current source */
|
|
if (address != AXP288_GP_ADC_H)
|
|
return 0;
|
|
|
|
ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
|
|
AXP288_ADC_TS_CURRENT_ON_OFF_MASK, mode);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* When switching to the GPADC pin give things some time to settle */
|
|
if (mode == AXP288_ADC_TS_CURRENT_ON_ONDEMAND)
|
|
usleep_range(6000, 10000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int axp288_adc_read_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan,
|
|
int *val, int *val2, long mask)
|
|
{
|
|
int ret;
|
|
struct axp288_adc_info *info = iio_priv(indio_dev);
|
|
|
|
mutex_lock(&indio_dev->mlock);
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON_ONDEMAND,
|
|
chan->address)) {
|
|
dev_err(&indio_dev->dev, "GPADC mode\n");
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
ret = axp288_adc_read_channel(val, chan->address, info->regmap);
|
|
if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON,
|
|
chan->address))
|
|
dev_err(&indio_dev->dev, "TS pin restore\n");
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
mutex_unlock(&indio_dev->mlock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* We rely on the machine's firmware to correctly setup the TS pin bias current
|
|
* at boot. This lists systems with broken fw where we need to set it ourselves.
|
|
*/
|
|
static const struct dmi_system_id axp288_adc_ts_bias_override[] = {
|
|
{
|
|
/* Lenovo Ideapad 100S (11 inch) */
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
|
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 100S-11IBY"),
|
|
},
|
|
.driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA,
|
|
},
|
|
{}
|
|
};
|
|
|
|
static int axp288_adc_initialize(struct axp288_adc_info *info)
|
|
{
|
|
const struct dmi_system_id *bias_override;
|
|
int ret, adc_enable_val;
|
|
|
|
bias_override = dmi_first_match(axp288_adc_ts_bias_override);
|
|
if (bias_override) {
|
|
ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
|
|
AXP288_ADC_TS_BIAS_MASK,
|
|
(uintptr_t)bias_override->driver_data);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Determine if the TS pin is enabled and set the TS current-source
|
|
* accordingly.
|
|
*/
|
|
ret = regmap_read(info->regmap, AXP20X_ADC_EN1, &adc_enable_val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (adc_enable_val & AXP288_ADC_TS_ENABLE) {
|
|
info->ts_enabled = true;
|
|
ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
|
|
AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
|
|
AXP288_ADC_TS_CURRENT_ON);
|
|
} else {
|
|
info->ts_enabled = false;
|
|
ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL,
|
|
AXP288_ADC_TS_CURRENT_ON_OFF_MASK,
|
|
AXP288_ADC_TS_CURRENT_OFF);
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Turn on the ADC for all channels except TS, leave TS as is */
|
|
return regmap_update_bits(info->regmap, AXP20X_ADC_EN1,
|
|
AXP288_ADC_EN_MASK, AXP288_ADC_EN_MASK);
|
|
}
|
|
|
|
static const struct iio_info axp288_adc_iio_info = {
|
|
.read_raw = &axp288_adc_read_raw,
|
|
};
|
|
|
|
static int axp288_adc_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
struct axp288_adc_info *info;
|
|
struct iio_dev *indio_dev;
|
|
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
|
|
|
|
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
|
|
if (!indio_dev)
|
|
return -ENOMEM;
|
|
|
|
info = iio_priv(indio_dev);
|
|
info->irq = platform_get_irq(pdev, 0);
|
|
if (info->irq < 0)
|
|
return info->irq;
|
|
platform_set_drvdata(pdev, indio_dev);
|
|
info->regmap = axp20x->regmap;
|
|
/*
|
|
* Set ADC to enabled state at all time, including system suspend.
|
|
* otherwise internal fuel gauge functionality may be affected.
|
|
*/
|
|
ret = axp288_adc_initialize(info);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "unable to enable ADC device\n");
|
|
return ret;
|
|
}
|
|
|
|
indio_dev->name = pdev->name;
|
|
indio_dev->channels = axp288_adc_channels;
|
|
indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels);
|
|
indio_dev->info = &axp288_adc_iio_info;
|
|
indio_dev->modes = INDIO_DIRECT_MODE;
|
|
ret = iio_map_array_register(indio_dev, axp288_adc_default_maps);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = iio_device_register(indio_dev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "unable to register iio device\n");
|
|
goto err_array_unregister;
|
|
}
|
|
return 0;
|
|
|
|
err_array_unregister:
|
|
iio_map_array_unregister(indio_dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int axp288_adc_remove(struct platform_device *pdev)
|
|
{
|
|
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
|
|
|
iio_device_unregister(indio_dev);
|
|
iio_map_array_unregister(indio_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct platform_device_id axp288_adc_id_table[] = {
|
|
{ .name = "axp288_adc" },
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver axp288_adc_driver = {
|
|
.probe = axp288_adc_probe,
|
|
.remove = axp288_adc_remove,
|
|
.id_table = axp288_adc_id_table,
|
|
.driver = {
|
|
.name = "axp288_adc",
|
|
},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(platform, axp288_adc_id_table);
|
|
|
|
module_platform_driver(axp288_adc_driver);
|
|
|
|
MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
|
|
MODULE_DESCRIPTION("X-Powers AXP288 ADC Driver");
|
|
MODULE_LICENSE("GPL");
|