Intel has 2 completely different "Dollar Cove" PMICs for its Bay Trail / Cherry Trail SoCs. One is made by X-Powers and is called the AXP288. The AXP288's GPADC is already supported by the X-Powers AXP288 ADC driver. The other "Dollar Cove" PMIC is made by TI and does not have any clear TI denomination, its MFD driver calls it the "Intel Dollar Cove TI PMIC". Add a driver for the Intel Dollar Cove TI PMIC's general purpose ADC, binding to the "chtdc_ti_adc" MFD cell of the MFD driver. The "cht" in the cell name comes from Cherry Trail, but it turns out that just like the AXP288 the Intel Dollar Cove TI PMIC is also used with both Intel Bay Trail and Intel Cherry Trail SoCs, so this new ADC driver does not include the cht part in its name. This is loosely based on kernel/drivers/iio/adc/iio_dc_ti_gpadc.c from the Acer A1-840 Android kernel source-code archive named: "App. Guide_Acer_20151221_A_A.zip" which is distributed by Acer from the Acer A1-840 support page: https://www.acer.com/us-en/support/product-support/A1-840/downloads Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx> --- drivers/iio/adc/Kconfig | 11 ++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/intel_dc_ti_adc.c | 258 ++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 drivers/iio/adc/intel_dc_ti_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 849c90203071..8fd69edb057c 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -678,6 +678,17 @@ config INTEL_MRFLD_ADC To compile this driver as a module, choose M here: the module will be called intel_mrfld_adc. +config INTEL_DC_TI_ADC + tristate "Intel Bay / Cherry Trail Dollar Cove TI ADC driver" + depends on INTEL_SOC_PMIC_CHTDC_TI + help + Say yes here to have support for the Dollar Cove TI PMIC ADC device. + Depending on platform configuration, this general purpose ADC can be + used for sensors such as battery voltage and thermal resistors. + + To compile this driver as a module, choose M here: the module will be + called intel_dc_ti_adc. + config IMX7D_ADC tristate "Freescale IMX7D ADC driver" depends on ARCH_MXC || COMPILE_TEST diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index ee19afba62b7..607d13c93c76 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_IMX93_ADC) += imx93_adc.o obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o obj-$(CONFIG_INTEL_MRFLD_ADC) += intel_mrfld_adc.o +obj-$(CONFIG_INTEL_DC_TI_ADC) += intel_dc_ti_adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o diff --git a/drivers/iio/adc/intel_dc_ti_adc.c b/drivers/iio/adc/intel_dc_ti_adc.c new file mode 100644 index 000000000000..c286f72cfb08 --- /dev/null +++ b/drivers/iio/adc/intel_dc_ti_adc.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Dollar Cove TI PMIC GPADC Driver + * + * Copyright (C) 2014 Intel Corporation (Ramakrishna Pallala <ramakrishna.pallala@xxxxxxxxx>) + * Copyright (C) 2024 Hans de Goede <hansg@xxxxxxxxxx> + */ + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/wait.h> + +#include <linux/iio/driver.h> +#include <linux/iio/iio.h> +#include <linux/iio/machine.h> + +#define DC_TI_ADC_CNTL_REG 0x50 +#define DC_TI_ADC_START BIT(0) +#define DC_TI_ADC_CH_SEL GENMASK(2, 1) +#define DC_TI_ADC_EN BIT(5) +#define DC_TI_ADC_EN_EXT_BPTH_BIAS BIT(6) + +#define DC_TI_ADC_CH0_DATAH_REG 0x54 +#define DC_TI_ADC_CH0_DATAL_REG 0x55 +#define DC_TI_ADC_CH1_DATAH_REG 0x56 +#define DC_TI_ADC_CH1_DATAL_REG 0x57 +#define DC_TI_ADC_CH2_DATAH_REG 0x58 +#define DC_TI_ADC_CH2_DATAL_REG 0x59 +#define DC_TI_ADC_CH3_DATAH_REG 0x5A +#define DC_TI_ADC_CH3_DATAL_REG 0x5B + +#define DEV_NAME "chtdc_ti_adc" + +enum dc_ti_adc_id { + DC_TI_ADC_VBAT, + DC_TI_ADC_PMICTEMP, + DC_TI_ADC_BATTEMP, + DC_TI_ADC_SYSTEMP0, +}; + +struct dc_ti_adc_info { + struct mutex lock; + wait_queue_head_t wait; + struct device *dev; + struct regmap *regmap; + bool conversion_done; +}; + +static const struct iio_chan_spec dc_ti_adc_channels[] = { + { + .indexed = 1, + .type = IIO_VOLTAGE, + .channel = DC_TI_ADC_VBAT, + .address = DC_TI_ADC_CH0_DATAH_REG, + .datasheet_name = "CH0", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_PMICTEMP, + .address = DC_TI_ADC_CH1_DATAH_REG, + .datasheet_name = "CH1", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_BATTEMP, + .address = DC_TI_ADC_CH2_DATAH_REG, + .datasheet_name = "CH2", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_SYSTEMP0, + .address = DC_TI_ADC_CH3_DATAH_REG, + .datasheet_name = "CH3", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static struct iio_map dc_ti_adc_default_maps[] = { + IIO_MAP("CH0", "chtdc_ti_battery", "VBAT"), + IIO_MAP("CH1", "chtdc_ti_battery", "PMICTEMP"), + IIO_MAP("CH2", "chtdc_ti_battery", "BATTEMP"), + IIO_MAP("CH3", "chtdc_ti_battery", "SYSTEMP0"), + {} +}; + +static irqreturn_t dc_ti_adc_isr(int irq, void *data) +{ + struct dc_ti_adc_info *info = data; + + info->conversion_done = true; + wake_up(&info->wait); + return IRQ_HANDLED; +} + +static int dc_ti_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dc_ti_adc_info *info = iio_priv(indio_dev); + int ret, ch = chan->channel; + unsigned int lsb, msb; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + guard(mutex)(&info->lock); + + info->conversion_done = false; + + /* + * If channel BPTHERM has been selected, first enable the BPTHERM BIAS + * which provides the VREFT Voltage reference to convert BPTHERM Input + * voltage to temperature. + * As per PMIC Vendor specifications, BPTHERM BIAS should be enabled + * 35 ms before ADC_EN command. + */ + if (ch == DC_TI_ADC_BATTEMP) { + ret = regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_EN_EXT_BPTH_BIAS, + DC_TI_ADC_EN_EXT_BPTH_BIAS); + if (ret < 0) + return ret; + msleep(35); + } + + /* + * As per TI (PMIC Vendor), the ADC enable and ADC start commands should + * not be sent together. Hence send the commands separately + */ + ret = regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_EN, DC_TI_ADC_EN); + if (ret < 0) + goto disable_adc; + + ret = regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_CH_SEL, FIELD_PREP(DC_TI_ADC_CH_SEL, ch)); + if (ret < 0) + goto disable_adc; + + /* + * As per PMIC Vendor, a minimum of 50 micro seconds delay is required + * between ADC Enable and ADC START commands. This is also recommended + * by Intel Hardware team after the timing analysis of GPADC signals. + * Since the I2C Write transaction to set the channel number also + * imparts 25 micro seconds of delay, so we need to wait for another + * 25 micro seconds before issuing ADC START command. + */ + usleep_range(25, 40); + + ret = regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_START, DC_TI_ADC_START); + if (ret < 0) + goto disable_adc; + + /* TI (PMIC Vendor) recommends 5 sec timeout for conversion */ + ret = wait_event_timeout(info->wait, info->conversion_done, 5 * HZ); + if (ret == 0) { + dev_err(info->dev, "Error sample timeout\n"); + ret = -ETIMEDOUT; + goto disable_adc; + } + + ret = regmap_read(info->regmap, chan->address, &msb); + if (ret) + goto disable_adc; + + ret = regmap_read(info->regmap, chan->address + 1, &lsb); + if (ret) + goto disable_adc; + + *val = ((msb << 8) | lsb) & 0x3ff; + ret = IIO_VAL_INT; + +disable_adc: + regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_START | DC_TI_ADC_EN, 0); + + if (ch == DC_TI_ADC_BATTEMP) + regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_EN_EXT_BPTH_BIAS, 0); + + return ret; +} + +static const struct iio_info dc_ti_adc_iio_info = { + .read_raw = dc_ti_adc_read_raw, +}; + +static int dc_ti_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent); + struct dc_ti_adc_info *info; + struct iio_dev *indio_dev; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + ret = devm_mutex_init(dev, &info->lock); + if (ret) + return ret; + + init_waitqueue_head(&info->wait); + + info->dev = dev; + info->regmap = pmic->regmap; + + indio_dev->name = pdev->name; + indio_dev->channels = dc_ti_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(dc_ti_adc_channels); + indio_dev->info = &dc_ti_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_map_array_register(dev, indio_dev, dc_ti_adc_default_maps); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, dc_ti_adc_isr, + IRQF_ONESHOT, DEV_NAME, info); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static struct platform_driver dc_ti_adc_driver = { + .probe = dc_ti_adc_probe, + .driver = { + .name = DEV_NAME, + }, +}; + +module_platform_driver(dc_ti_adc_driver); + +MODULE_ALIAS("platform:" DEV_NAME); +MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@xxxxxxxxx>"); +MODULE_DESCRIPTION("Intel Dollar Cove (TI) GPADC Driver"); +MODULE_LICENSE("GPL"); -- 2.47.1