| @@ -0,0 +1,393 @@ |
| | 1 | // SPDX-License-Identifier: GPL-2.0-only |
| | 2 | /* |
| | 3 | * Copyright 2025, Duje Mihanović <duje@dujemihanovic.xyz> |
| | 4 | */ |
| | 5 | |
| | 6 | #include <linux/bits.h> |
| | 7 | #include <linux/bug.h> |
| | 8 | #include <linux/delay.h> |
| | 9 | #include <linux/device.h> |
| | 10 | #include <linux/err.h> |
| | 11 | #include <linux/i2c.h> |
| | 12 | #include <linux/math.h> |
| | 13 | #include <linux/mod_devicetable.h> |
| | 14 | #include <linux/module.h> |
| | 15 | #include <linux/platform_device.h> |
| | 16 | #include <linux/pm_runtime.h> |
| | 17 | #include <linux/regmap.h> |
| | 18 | #include <linux/types.h> |
| | 19 | #include <linux/units.h> |
| | 20 | |
| | 21 | #include <asm/byteorder.h> |
| | 22 | |
| | 23 | #include <linux/iio/iio.h> |
| | 24 | #include <linux/iio/types.h> |
| | 25 | |
| | 26 | #include <linux/mfd/88pm886.h> |
| | 27 | |
| | 28 | struct pm886_gpadc { |
| | 29 | struct regmap *map; |
| | 30 | }; |
| | 31 | |
| | 32 | enum pm886_gpadc_channel { |
| | 33 | VSC_CHAN, |
| | 34 | VCHG_PWR_CHAN, |
| | 35 | VCF_OUT_CHAN, |
| | 36 | VBAT_CHAN, |
| | 37 | VBAT_SLP_CHAN, |
| | 38 | VBUS_CHAN, |
| | 39 | |
| | 40 | GPADC0_CHAN, |
| | 41 | GPADC1_CHAN, |
| | 42 | GPADC2_CHAN, |
| | 43 | GPADC3_CHAN, |
| | 44 | |
| | 45 | GND_DET1_CHAN, |
| | 46 | GND_DET2_CHAN, |
| | 47 | MIC_DET_CHAN, |
| | 48 | |
| | 49 | TINT_CHAN, |
| | 50 | }; |
| | 51 | |
| | 52 | static const int pm886_gpadc_regs[] = { |
| | 53 | [VSC_CHAN] = PM886_REG_GPADC_VSC, |
| | 54 | [VCHG_PWR_CHAN] = PM886_REG_GPADC_VCHG_PWR, |
| | 55 | [VCF_OUT_CHAN] = PM886_REG_GPADC_VCF_OUT, |
| | 56 | [VBAT_CHAN] = PM886_REG_GPADC_VBAT, |
| | 57 | [VBAT_SLP_CHAN] = PM886_REG_GPADC_VBAT_SLP, |
| | 58 | [VBUS_CHAN] = PM886_REG_GPADC_VBUS, |
| | 59 | |
| | 60 | [GPADC0_CHAN] = PM886_REG_GPADC_GPADC0, |
| | 61 | [GPADC1_CHAN] = PM886_REG_GPADC_GPADC1, |
| | 62 | [GPADC2_CHAN] = PM886_REG_GPADC_GPADC2, |
| | 63 | [GPADC3_CHAN] = PM886_REG_GPADC_GPADC3, |
| | 64 | |
| | 65 | [GND_DET1_CHAN] = PM886_REG_GPADC_GND_DET1, |
| | 66 | [GND_DET2_CHAN] = PM886_REG_GPADC_GND_DET2, |
| | 67 | [MIC_DET_CHAN] = PM886_REG_GPADC_MIC_DET, |
| | 68 | |
| | 69 | [TINT_CHAN] = PM886_REG_GPADC_TINT, |
| | 70 | }; |
| | 71 | |
| | 72 | #define ADC_CHANNEL_VOLTAGE(index, lsb, name) \ |
| | 73 | { \ |
| | 74 | .type = IIO_VOLTAGE, \ |
| | 75 | .indexed = 1, \ |
| | 76 | .channel = index, \ |
| | 77 | .address = lsb, \ |
| | 78 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ |
| | 79 | BIT(IIO_CHAN_INFO_SCALE), \ |
| | 80 | .datasheet_name = name, \ |
| | 81 | } |
| | 82 | |
| | 83 | #define ADC_CHANNEL_RESISTANCE(index, lsb, name) \ |
| | 84 | { \ |
| | 85 | .type = IIO_RESISTANCE, \ |
| | 86 | .indexed = 1, \ |
| | 87 | .channel = index, \ |
| | 88 | .address = lsb, \ |
| | 89 | .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ |
| | 90 | .datasheet_name = name, \ |
| | 91 | } |
| | 92 | |
| | 93 | #define ADC_CHANNEL_TEMPERATURE(index, lsb, name) \ |
| | 94 | { \ |
| | 95 | .type = IIO_TEMP, \ |
| | 96 | .indexed = 1, \ |
| | 97 | .channel = index, \ |
| | 98 | .address = lsb, \ |
| | 99 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ |
| | 100 | BIT(IIO_CHAN_INFO_SCALE) | \ |
| | 101 | BIT(IIO_CHAN_INFO_OFFSET), \ |
| | 102 | .datasheet_name = name, \ |
| | 103 | } |
| | 104 | |
| | 105 | static const struct iio_chan_spec pm886_gpadc_channels[] = { |
| | 106 | ADC_CHANNEL_VOLTAGE(VSC_CHAN, 1367, "vsc"), |
| | 107 | ADC_CHANNEL_VOLTAGE(VCHG_PWR_CHAN, 1709, "vchg_pwr"), |
| | 108 | ADC_CHANNEL_VOLTAGE(VCF_OUT_CHAN, 1367, "vcf_out"), |
| | 109 | ADC_CHANNEL_VOLTAGE(VBAT_CHAN, 1367, "vbat"), |
| | 110 | ADC_CHANNEL_VOLTAGE(VBAT_SLP_CHAN, 1367, "vbat_slp"), |
| | 111 | ADC_CHANNEL_VOLTAGE(VBUS_CHAN, 1709, "vbus"), |
| | 112 | |
| | 113 | ADC_CHANNEL_RESISTANCE(GPADC0_CHAN, 342, "gpadc0"), |
| | 114 | ADC_CHANNEL_RESISTANCE(GPADC1_CHAN, 342, "gpadc1"), |
| | 115 | ADC_CHANNEL_RESISTANCE(GPADC2_CHAN, 342, "gpadc2"), |
| | 116 | ADC_CHANNEL_RESISTANCE(GPADC3_CHAN, 342, "gpadc3"), |
| | 117 | |
| | 118 | ADC_CHANNEL_VOLTAGE(GND_DET1_CHAN, 342, "gnddet1"), |
| | 119 | ADC_CHANNEL_VOLTAGE(GND_DET2_CHAN, 342, "gnddet2"), |
| | 120 | ADC_CHANNEL_VOLTAGE(MIC_DET_CHAN, 1367, "mic_det"), |
| | 121 | |
| | 122 | ADC_CHANNEL_TEMPERATURE(TINT_CHAN, 104, "tint"), |
| | 123 | }; |
| | 124 | |
| | 125 | static const struct regmap_config pm886_gpadc_regmap_config = { |
| | 126 | .reg_bits = 8, |
| | 127 | .val_bits = 8, |
| | 128 | .max_register = PM886_GPADC_MAX_REGISTER, |
| | 129 | }; |
| | 130 | |
| | 131 | static int gpadc_get_raw(struct iio_dev *iio, enum pm886_gpadc_channel chan) |
| | 132 | { |
| | 133 | struct pm886_gpadc *gpadc = iio_priv(iio); |
| | 134 | __be16 buf; |
| | 135 | int ret; |
| | 136 | |
| | 137 | ret = regmap_bulk_read(gpadc->map, pm886_gpadc_regs[chan], &buf, sizeof(buf)); |
| | 138 | if (ret) |
| | 139 | return ret; |
| | 140 | |
| | 141 | return be16_to_cpu(buf) >> 4; |
| | 142 | } |
| | 143 | |
| | 144 | static int |
| | 145 | gpadc_set_bias(struct pm886_gpadc *gpadc, enum pm886_gpadc_channel chan, bool on) |
| | 146 | { |
| | 147 | unsigned int gpadc_num = chan - GPADC0_CHAN; |
| | 148 | unsigned int bits = BIT(gpadc_num + 4) | BIT(gpadc_num); |
| | 149 | |
| | 150 | return regmap_assign_bits(gpadc->map, PM886_REG_GPADC_CONFIG(0x14), bits, on); |
| | 151 | } |
| | 152 | |
| | 153 | static int |
| | 154 | gpadc_find_bias_current(struct iio_dev *iio, struct iio_chan_spec const *chan, |
| | 155 | unsigned int *raw_uV, unsigned int *raw_uA) |
| | 156 | { |
| | 157 | struct pm886_gpadc *gpadc = iio_priv(iio); |
| | 158 | unsigned int gpadc_num = chan->channel - GPADC0_CHAN; |
| | 159 | unsigned int reg = PM886_REG_GPADC_CONFIG(0xb + gpadc_num); |
| | 160 | unsigned long lsb = chan->address; |
| | 161 | int ret; |
| | 162 | |
| | 163 | for (unsigned int i = 0; i < PM886_GPADC_BIAS_LEVELS; i++) { |
| | 164 | ret = regmap_update_bits(gpadc->map, reg, GENMASK(3, 0), i); |
| | 165 | if (ret) |
| | 166 | return ret; |
| | 167 | |
| | 168 | /* Wait for the new bias level to apply. */ |
| | 169 | fsleep(5 * USEC_PER_MSEC); |
| | 170 | |
| | 171 | *raw_uA = PM886_GPADC_INDEX_TO_BIAS_uA(i); |
| | 172 | *raw_uV = gpadc_get_raw(iio, chan->channel) * lsb; |
| | 173 | |
| | 174 | /* |
| | 175 | * Vendor kernel errors out above 1.25 V, but testing shows |
| | 176 | * that the resistance of the battery detection channel (GPADC2 |
| | 177 | * on coreprimevelte) reaches about 1.4 MΩ when the battery is |
| | 178 | * removed, which can't be measured with such a low upper |
| | 179 | * limit. Therefore, to be able to detect the battery without |
| | 180 | * ugly externs as used in the vendor fuel gauge driver, |
| | 181 | * increase this limit a bit. |
| | 182 | */ |
| | 183 | if (WARN_ON(*raw_uV > 1500 * (MICRO / MILLI))) |
| | 184 | return -EIO; |
| | 185 | |
| | 186 | /* |
| | 187 | * Vendor kernel errors out under 300 mV, but for the same |
| | 188 | * reason as above (except the channel hovers around 3.5 kΩ |
| | 189 | * with battery present) reduce this limit. |
| | 190 | */ |
| | 191 | if (*raw_uV < 200 * (MICRO / MILLI)) { |
| | 192 | dev_dbg(&iio->dev, "bad bias for chan %d: %d uA @ %d uV\n", |
| | 193 | chan->channel, *raw_uA, *raw_uV); |
| | 194 | continue; |
| | 195 | } |
| | 196 | |
| | 197 | dev_dbg(&iio->dev, "good bias for chan %d: %d uA @ %d uV\n", |
| | 198 | chan->channel, *raw_uA, *raw_uV); |
| | 199 | return 0; |
| | 200 | } |
| | 201 | |
| | 202 | dev_err(&iio->dev, "failed to find good bias for chan %d\n", chan->channel); |
| | 203 | return -EINVAL; |
| | 204 | } |
| | 205 | |
| | 206 | static int |
| | 207 | gpadc_get_resistance_ohm(struct iio_dev *iio, struct iio_chan_spec const *chan) |
| | 208 | { |
| | 209 | struct pm886_gpadc *gpadc = iio_priv(iio); |
| | 210 | unsigned int raw_uV, raw_uA; |
| | 211 | int ret; |
| | 212 | |
| | 213 | ret = gpadc_set_bias(gpadc, chan->channel, true); |
| | 214 | if (ret) |
| | 215 | goto out; |
| | 216 | |
| | 217 | ret = gpadc_find_bias_current(iio, chan, &raw_uV, &raw_uA); |
| | 218 | if (ret) |
| | 219 | goto out; |
| | 220 | |
| | 221 | ret = DIV_ROUND_CLOSEST(raw_uV, raw_uA); |
| | 222 | out: |
| | 223 | gpadc_set_bias(gpadc, chan->channel, false); |
| | 224 | return ret; |
| | 225 | } |
| | 226 | |
| | 227 | static int |
| | 228 | __pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, |
| | 229 | int *val, int *val2, long mask) |
| | 230 | { |
| | 231 | unsigned long lsb = chan->address; |
| | 232 | |
| | 233 | switch (mask) { |
| | 234 | case IIO_CHAN_INFO_RAW: |
| | 235 | *val = gpadc_get_raw(iio, chan->channel); |
| | 236 | if (*val < 0) |
| | 237 | return *val; |
| | 238 | |
| | 239 | return IIO_VAL_INT; |
| | 240 | case IIO_CHAN_INFO_SCALE: |
| | 241 | *val = lsb; |
| | 242 | |
| | 243 | if (chan->type == IIO_VOLTAGE) { |
| | 244 | *val2 = MILLI; |
| | 245 | return IIO_VAL_FRACTIONAL; |
| | 246 | } else { |
| | 247 | return IIO_VAL_INT; |
| | 248 | } |
| | 249 | case IIO_CHAN_INFO_OFFSET: |
| | 250 | /* Raw value is 104 millikelvin/LSB, convert it to 104 millicelsius/LSB */ |
| | 251 | *val = ABSOLUTE_ZERO_MILLICELSIUS; |
| | 252 | *val2 = lsb; |
| | 253 | return IIO_VAL_FRACTIONAL; |
| | 254 | case IIO_CHAN_INFO_PROCESSED: |
| | 255 | *val = gpadc_get_resistance_ohm(iio, chan); |
| | 256 | if (*val < 0) |
| | 257 | return *val; |
| | 258 | |
| | 259 | return IIO_VAL_INT; |
| | 260 | default: |
| | 261 | return -EINVAL; |
| | 262 | } |
| | 263 | } |
| | 264 | |
| | 265 | static int pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, |
| | 266 | int *val, int *val2, long mask) |
| | 267 | { |
| | 268 | struct device *dev = iio->dev.parent; |
| | 269 | int ret; |
| | 270 | |
| | 271 | ret = pm_runtime_resume_and_get(dev); |
| | 272 | if (ret) |
| | 273 | return ret; |
| | 274 | |
| | 275 | ret = __pm886_gpadc_read_raw(iio, chan, val, val2, mask); |
| | 276 | |
| | 277 | pm_runtime_put_autosuspend(dev); |
| | 278 | return ret; |
| | 279 | } |
| | 280 | |
| | 281 | static int pm886_gpadc_hw_enable(struct regmap *map) |
| | 282 | { |
| | 283 | const u8 config[] = { |
| | 284 | PM886_GPADC_CONFIG1_EN_ALL, |
| | 285 | PM886_GPADC_CONFIG2_EN_ALL, |
| | 286 | PM886_GPADC_GND_DET2_EN, |
| | 287 | }; |
| | 288 | int ret; |
| | 289 | |
| | 290 | /* Enable the ADC block. */ |
| | 291 | ret = regmap_set_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0)); |
| | 292 | if (ret) |
| | 293 | return ret; |
| | 294 | |
| | 295 | /* Enable all channels. */ |
| | 296 | return regmap_bulk_write(map, PM886_REG_GPADC_CONFIG(0x1), config, ARRAY_SIZE(config)); |
| | 297 | } |
| | 298 | |
| | 299 | static int pm886_gpadc_hw_disable(struct regmap *map) |
| | 300 | { |
| | 301 | return regmap_clear_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0)); |
| | 302 | } |
| | 303 | |
| | 304 | static const struct iio_info pm886_gpadc_iio_info = { |
| | 305 | .read_raw = pm886_gpadc_read_raw, |
| | 306 | }; |
| | 307 | |
| | 308 | static int pm886_gpadc_probe(struct platform_device *pdev) |
| | 309 | { |
| | 310 | struct device *dev = &pdev->dev; |
| | 311 | struct pm886_chip *chip = dev_get_drvdata(dev->parent); |
| | 312 | struct i2c_client *client = chip->client; |
| | 313 | struct pm886_gpadc *gpadc; |
| | 314 | struct i2c_client *page; |
| | 315 | struct iio_dev *iio; |
| | 316 | int ret; |
| | 317 | |
| | 318 | iio = devm_iio_device_alloc(dev, sizeof(*gpadc)); |
| | 319 | if (!iio) |
| | 320 | return -ENOMEM; |
| | 321 | |
| | 322 | gpadc = iio_priv(iio); |
| | 323 | dev_set_drvdata(dev, iio); |
| | 324 | |
| | 325 | page = devm_i2c_new_dummy_device(dev, client->adapter, |
| | 326 | client->addr + PM886_PAGE_OFFSET_GPADC); |
| | 327 | if (IS_ERR(page)) |
| | 328 | return dev_err_probe(dev, PTR_ERR(page), "Failed to initialize GPADC page\n"); |
| | 329 | |
| | 330 | gpadc->map = devm_regmap_init_i2c(page, &pm886_gpadc_regmap_config); |
| | 331 | if (IS_ERR(gpadc->map)) |
| | 332 | return dev_err_probe(dev, PTR_ERR(gpadc->map), |
| | 333 | "Failed to initialize GPADC regmap\n"); |
| | 334 | |
| | 335 | iio->name = "88pm886-gpadc"; |
| | 336 | iio->modes = INDIO_DIRECT_MODE; |
| | 337 | iio->info = &pm886_gpadc_iio_info; |
| | 338 | iio->channels = pm886_gpadc_channels; |
| | 339 | iio->num_channels = ARRAY_SIZE(pm886_gpadc_channels); |
| | 340 | device_set_node(&iio->dev, dev_fwnode(dev->parent)); |
| | 341 | |
| | 342 | ret = devm_pm_runtime_enable(dev); |
| | 343 | if (ret) |
| | 344 | return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); |
| | 345 | |
| | 346 | pm_runtime_set_autosuspend_delay(dev, 50); |
| | 347 | pm_runtime_use_autosuspend(dev); |
| | 348 | ret = devm_iio_device_register(dev, iio); |
| | 349 | if (ret) |
| | 350 | return dev_err_probe(dev, ret, "Failed to register ADC\n"); |
| | 351 | |
| | 352 | return 0; |
| | 353 | } |
| | 354 | |
| | 355 | static int pm886_gpadc_runtime_resume(struct device *dev) |
| | 356 | { |
| | 357 | struct iio_dev *iio = dev_get_drvdata(dev); |
| | 358 | struct pm886_gpadc *gpadc = iio_priv(iio); |
| | 359 | |
| | 360 | return pm886_gpadc_hw_enable(gpadc->map); |
| | 361 | } |
| | 362 | |
| | 363 | static int pm886_gpadc_runtime_suspend(struct device *dev) |
| | 364 | { |
| | 365 | struct iio_dev *iio = dev_get_drvdata(dev); |
| | 366 | struct pm886_gpadc *gpadc = iio_priv(iio); |
| | 367 | |
| | 368 | return pm886_gpadc_hw_disable(gpadc->map); |
| | 369 | } |
| | 370 | |
| | 371 | static DEFINE_RUNTIME_DEV_PM_OPS(pm886_gpadc_pm_ops, |
| | 372 | pm886_gpadc_runtime_suspend, |
| | 373 | pm886_gpadc_runtime_resume, NULL); |
| | 374 | |
| | 375 | static const struct platform_device_id pm886_gpadc_id[] = { |
| | 376 | { "88pm886-gpadc" }, |
| | 377 | { } |
| | 378 | }; |
| | 379 | MODULE_DEVICE_TABLE(platform, pm886_gpadc_id); |
| | 380 | |
| | 381 | static struct platform_driver pm886_gpadc_driver = { |
| | 382 | .driver = { |
| | 383 | .name = "88pm886-gpadc", |
| | 384 | .pm = pm_ptr(&pm886_gpadc_pm_ops), |
| | 385 | }, |
| | 386 | .probe = pm886_gpadc_probe, |
| | 387 | .id_table = pm886_gpadc_id, |
| | 388 | }; |
| | 389 | module_platform_driver(pm886_gpadc_driver); |
| | 390 | |
| | 391 | MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>"); |
| | 392 | MODULE_DESCRIPTION("Marvell 88PM886 GPADC driver"); |
| | 393 | MODULE_LICENSE("GPL"); |