Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

iio: adc: Add Intel Dollar Cove TI PMIC ADC driver

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

Reviewed-by: Andy Shevchenko <andy@kernel.org>
Signed-off-by: Hans de Goede <hansg@kernel.org>
Link: https://patch.msgid.link/20250831104825.15097-7-hansg@kernel.org
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

authored by

Hans de Goede and committed by
Jonathan Cameron
28e4b859 c732e60e

+340
+11
drivers/iio/adc/Kconfig
··· 767 767 This driver can also be built as a module. If so, the module will be 768 768 called ingenic_adc. 769 769 770 + config INTEL_DC_TI_ADC 771 + tristate "Intel Bay Trail / Cherry Trail Dollar Cove TI ADC driver" 772 + depends on INTEL_SOC_PMIC_CHTDC_TI 773 + help 774 + Say yes here to have support for the Dollar Cove TI PMIC ADC device. 775 + Depending on platform configuration, this general purpose ADC can be 776 + used for sensors such as battery voltage and thermal resistors. 777 + 778 + To compile this driver as a module, choose M here: the module will be 779 + called intel_dc_ti_adc. 780 + 770 781 config INTEL_MRFLD_ADC 771 782 tristate "Intel Merrifield Basin Cove ADC driver" 772 783 depends on INTEL_SOC_PMIC_MRFLD
+1
drivers/iio/adc/Makefile
··· 70 70 obj-$(CONFIG_IMX93_ADC) += imx93_adc.o 71 71 obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o 72 72 obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o 73 + obj-$(CONFIG_INTEL_DC_TI_ADC) += intel_dc_ti_adc.o 73 74 obj-$(CONFIG_INTEL_MRFLD_ADC) += intel_mrfld_adc.o 74 75 obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o 75 76 obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
+328
drivers/iio/adc/intel_dc_ti_adc.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Intel Dollar Cove TI PMIC GPADC Driver 4 + * 5 + * Copyright (C) 2014 Intel Corporation (Ramakrishna Pallala <ramakrishna.pallala@intel.com>) 6 + * Copyright (C) 2024 - 2025 Hans de Goede <hansg@kernel.org> 7 + */ 8 + 9 + #include <linux/bits.h> 10 + #include <linux/bitfield.h> 11 + #include <linux/bitops.h> 12 + #include <linux/cleanup.h> 13 + #include <linux/delay.h> 14 + #include <linux/device.h> 15 + #include <linux/interrupt.h> 16 + #include <linux/mfd/intel_soc_pmic.h> 17 + #include <linux/mod_devicetable.h> 18 + #include <linux/module.h> 19 + #include <linux/mutex.h> 20 + #include <linux/platform_device.h> 21 + #include <linux/regmap.h> 22 + #include <linux/wait.h> 23 + 24 + #include <linux/iio/driver.h> 25 + #include <linux/iio/iio.h> 26 + #include <linux/iio/machine.h> 27 + 28 + #define DC_TI_ADC_CNTL_REG 0x50 29 + #define DC_TI_ADC_START BIT(0) 30 + #define DC_TI_ADC_CH_SEL GENMASK(2, 1) 31 + #define DC_TI_ADC_EN BIT(5) 32 + #define DC_TI_ADC_EN_EXT_BPTH_BIAS BIT(6) 33 + 34 + #define DC_TI_VBAT_ZSE_GE_REG 0x53 35 + #define DC_TI_VBAT_GE GENMASK(3, 0) 36 + #define DC_TI_VBAT_ZSE GENMASK(7, 4) 37 + 38 + /* VBAT GE gain correction is in 0.0015 increments, ZSE is in 1.0 increments */ 39 + #define DC_TI_VBAT_GE_STEP 15 40 + #define DC_TI_VBAT_GE_DIV 10000 41 + 42 + #define DC_TI_ADC_DATA_REG_CH(x) (0x54 + 2 * (x)) 43 + 44 + enum dc_ti_adc_id { 45 + DC_TI_ADC_VBAT, 46 + DC_TI_ADC_PMICTEMP, 47 + DC_TI_ADC_BATTEMP, 48 + DC_TI_ADC_SYSTEMP0, 49 + }; 50 + 51 + struct dc_ti_adc_info { 52 + struct mutex lock; /* Protects against concurrent accesses to the ADC */ 53 + wait_queue_head_t wait; 54 + struct device *dev; 55 + struct regmap *regmap; 56 + int vbat_zse; 57 + int vbat_ge; 58 + bool conversion_done; 59 + }; 60 + 61 + static const struct iio_chan_spec dc_ti_adc_channels[] = { 62 + { 63 + .indexed = 1, 64 + .type = IIO_VOLTAGE, 65 + .channel = DC_TI_ADC_VBAT, 66 + .address = DC_TI_ADC_DATA_REG_CH(0), 67 + .datasheet_name = "CH0", 68 + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | 69 + BIT(IIO_CHAN_INFO_SCALE) | 70 + BIT(IIO_CHAN_INFO_PROCESSED), 71 + }, { 72 + .indexed = 1, 73 + .type = IIO_TEMP, 74 + .channel = DC_TI_ADC_PMICTEMP, 75 + .address = DC_TI_ADC_DATA_REG_CH(1), 76 + .datasheet_name = "CH1", 77 + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 78 + }, { 79 + .indexed = 1, 80 + .type = IIO_TEMP, 81 + .channel = DC_TI_ADC_BATTEMP, 82 + .address = DC_TI_ADC_DATA_REG_CH(2), 83 + .datasheet_name = "CH2", 84 + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 85 + }, { 86 + .indexed = 1, 87 + .type = IIO_TEMP, 88 + .channel = DC_TI_ADC_SYSTEMP0, 89 + .address = DC_TI_ADC_DATA_REG_CH(3), 90 + .datasheet_name = "CH3", 91 + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), 92 + } 93 + }; 94 + 95 + static struct iio_map dc_ti_adc_default_maps[] = { 96 + IIO_MAP("CH0", "chtdc_ti_battery", "VBAT"), 97 + IIO_MAP("CH1", "chtdc_ti_battery", "PMICTEMP"), 98 + IIO_MAP("CH2", "chtdc_ti_battery", "BATTEMP"), 99 + IIO_MAP("CH3", "chtdc_ti_battery", "SYSTEMP0"), 100 + { } 101 + }; 102 + 103 + static irqreturn_t dc_ti_adc_isr(int irq, void *data) 104 + { 105 + struct dc_ti_adc_info *info = data; 106 + 107 + info->conversion_done = true; 108 + wake_up(&info->wait); 109 + return IRQ_HANDLED; 110 + } 111 + 112 + static int dc_ti_adc_scale(struct dc_ti_adc_info *info, 113 + struct iio_chan_spec const *chan, 114 + int *val, int *val2) 115 + { 116 + if (chan->channel != DC_TI_ADC_VBAT) 117 + return -EINVAL; 118 + 119 + /* Vbat ADC scale is 4.6875 mV / unit */ 120 + *val = 4; 121 + *val2 = 687500; 122 + 123 + return IIO_VAL_INT_PLUS_MICRO; 124 + } 125 + 126 + static int dc_ti_adc_raw_to_processed(struct dc_ti_adc_info *info, 127 + struct iio_chan_spec const *chan, 128 + int raw, int *val, int *val2) 129 + { 130 + if (chan->channel != DC_TI_ADC_VBAT) 131 + return -EINVAL; 132 + 133 + /* Apply calibration */ 134 + raw -= info->vbat_zse; 135 + raw = raw * (DC_TI_VBAT_GE_DIV - info->vbat_ge * DC_TI_VBAT_GE_STEP) / 136 + DC_TI_VBAT_GE_DIV; 137 + /* Vbat ADC scale is 4.6875 mV / unit */ 138 + raw *= 46875; 139 + 140 + /* raw is now in 10000 units / mV, convert to milli + milli/1e6 */ 141 + *val = raw / 10000; 142 + *val2 = (raw % 10000) * 100; 143 + 144 + return IIO_VAL_INT_PLUS_MICRO; 145 + } 146 + 147 + static int dc_ti_adc_sample(struct dc_ti_adc_info *info, 148 + struct iio_chan_spec const *chan, int *val) 149 + { 150 + int ret, ch = chan->channel; 151 + __be16 buf; 152 + 153 + info->conversion_done = false; 154 + 155 + /* 156 + * As per TI (PMIC Vendor), the ADC enable and ADC start commands should 157 + * not be sent together. Hence send the commands separately. 158 + */ 159 + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, DC_TI_ADC_EN); 160 + if (ret) 161 + return ret; 162 + 163 + ret = regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG, 164 + DC_TI_ADC_CH_SEL, 165 + FIELD_PREP(DC_TI_ADC_CH_SEL, ch)); 166 + if (ret) 167 + return ret; 168 + 169 + /* 170 + * As per PMIC Vendor, a minimum of 50 ųs delay is required between ADC 171 + * Enable and ADC START commands. This is also recommended by Intel 172 + * Hardware team after the timing analysis of GPADC signals. Since the 173 + * I2C Write transaction to set the channel number also imparts 25 ųs 174 + * delay, we need to wait for another 25 ųs before issuing ADC START. 175 + */ 176 + fsleep(25); 177 + 178 + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, 179 + DC_TI_ADC_START); 180 + if (ret) 181 + return ret; 182 + 183 + /* TI (PMIC Vendor) recommends 5 s timeout for conversion */ 184 + ret = wait_event_timeout(info->wait, info->conversion_done, 5 * HZ); 185 + if (ret == 0) { 186 + ret = -ETIMEDOUT; 187 + goto disable_adc; 188 + } 189 + 190 + ret = regmap_bulk_read(info->regmap, chan->address, &buf, sizeof(buf)); 191 + if (ret) 192 + goto disable_adc; 193 + 194 + /* The ADC values are 10 bits wide */ 195 + *val = be16_to_cpu(buf) & GENMASK(9, 0); 196 + 197 + disable_adc: 198 + regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG, 199 + DC_TI_ADC_START | DC_TI_ADC_EN); 200 + return ret; 201 + } 202 + 203 + static int dc_ti_adc_read_raw(struct iio_dev *indio_dev, 204 + struct iio_chan_spec const *chan, 205 + int *val, int *val2, long mask) 206 + { 207 + struct dc_ti_adc_info *info = iio_priv(indio_dev); 208 + int ret; 209 + 210 + if (mask == IIO_CHAN_INFO_SCALE) 211 + return dc_ti_adc_scale(info, chan, val, val2); 212 + 213 + guard(mutex)(&info->lock); 214 + 215 + /* 216 + * If channel BPTHERM has been selected, first enable the BPTHERM BIAS 217 + * which provides the VREF Voltage reference to convert BPTHERM Input 218 + * voltage to temperature. 219 + */ 220 + if (chan->channel == DC_TI_ADC_BATTEMP) { 221 + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, 222 + DC_TI_ADC_EN_EXT_BPTH_BIAS); 223 + if (ret) 224 + return ret; 225 + /* 226 + * As per PMIC Vendor specifications, BPTHERM BIAS should be 227 + * enabled 35 ms before ADC_EN command. 228 + */ 229 + msleep(35); 230 + } 231 + 232 + ret = dc_ti_adc_sample(info, chan, val); 233 + 234 + if (chan->channel == DC_TI_ADC_BATTEMP) 235 + regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG, 236 + DC_TI_ADC_EN_EXT_BPTH_BIAS); 237 + 238 + if (ret) 239 + return ret; 240 + 241 + switch (mask) { 242 + case IIO_CHAN_INFO_RAW: 243 + return IIO_VAL_INT; 244 + case IIO_CHAN_INFO_PROCESSED: 245 + return dc_ti_adc_raw_to_processed(info, chan, *val, val, val2); 246 + } 247 + 248 + return -EINVAL; 249 + } 250 + 251 + static const struct iio_info dc_ti_adc_iio_info = { 252 + .read_raw = dc_ti_adc_read_raw, 253 + }; 254 + 255 + static int dc_ti_adc_probe(struct platform_device *pdev) 256 + { 257 + struct device *dev = &pdev->dev; 258 + struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent); 259 + struct dc_ti_adc_info *info; 260 + struct iio_dev *indio_dev; 261 + unsigned int val; 262 + int irq, ret; 263 + 264 + irq = platform_get_irq(pdev, 0); 265 + if (irq < 0) 266 + return irq; 267 + 268 + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); 269 + if (!indio_dev) 270 + return -ENOMEM; 271 + 272 + info = iio_priv(indio_dev); 273 + 274 + ret = devm_mutex_init(dev, &info->lock); 275 + if (ret) 276 + return ret; 277 + 278 + init_waitqueue_head(&info->wait); 279 + 280 + info->dev = dev; 281 + info->regmap = pmic->regmap; 282 + 283 + indio_dev->name = "dc_ti_adc"; 284 + indio_dev->channels = dc_ti_adc_channels; 285 + indio_dev->num_channels = ARRAY_SIZE(dc_ti_adc_channels); 286 + indio_dev->info = &dc_ti_adc_iio_info; 287 + indio_dev->modes = INDIO_DIRECT_MODE; 288 + 289 + ret = regmap_read(info->regmap, DC_TI_VBAT_ZSE_GE_REG, &val); 290 + if (ret) 291 + return ret; 292 + 293 + info->vbat_zse = sign_extend32(FIELD_GET(DC_TI_VBAT_ZSE, val), 3); 294 + info->vbat_ge = sign_extend32(FIELD_GET(DC_TI_VBAT_GE, val), 3); 295 + 296 + dev_dbg(dev, "vbat-zse %d vbat-ge %d\n", info->vbat_zse, info->vbat_ge); 297 + 298 + ret = devm_iio_map_array_register(dev, indio_dev, dc_ti_adc_default_maps); 299 + if (ret) 300 + return ret; 301 + 302 + ret = devm_request_threaded_irq(dev, irq, NULL, dc_ti_adc_isr, 303 + IRQF_ONESHOT, indio_dev->name, info); 304 + if (ret) 305 + return ret; 306 + 307 + return devm_iio_device_register(dev, indio_dev); 308 + } 309 + 310 + static const struct platform_device_id dc_ti_adc_ids[] = { 311 + { .name = "chtdc_ti_adc" }, 312 + { } 313 + }; 314 + MODULE_DEVICE_TABLE(platform, dc_ti_adc_ids); 315 + 316 + static struct platform_driver dc_ti_adc_driver = { 317 + .driver = { 318 + .name = "dc_ti_adc", 319 + }, 320 + .probe = dc_ti_adc_probe, 321 + .id_table = dc_ti_adc_ids, 322 + }; 323 + module_platform_driver(dc_ti_adc_driver); 324 + 325 + MODULE_AUTHOR("Ramakrishna Pallala (Intel)"); 326 + MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); 327 + MODULE_DESCRIPTION("Intel Dollar Cove (TI) GPADC Driver"); 328 + MODULE_LICENSE("GPL");