// SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2025, Duje Mihanović */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct pm886_gpadc { struct regmap *map; }; enum pm886_gpadc_channel { VSC_CHAN, VCHG_PWR_CHAN, VCF_OUT_CHAN, VBAT_CHAN, VBAT_SLP_CHAN, VBUS_CHAN, GPADC0_CHAN, GPADC1_CHAN, GPADC2_CHAN, GPADC3_CHAN, GND_DET1_CHAN, GND_DET2_CHAN, MIC_DET_CHAN, TINT_CHAN, }; static const int pm886_gpadc_regs[] = { [VSC_CHAN] = PM886_REG_GPADC_VSC, [VCHG_PWR_CHAN] = PM886_REG_GPADC_VCHG_PWR, [VCF_OUT_CHAN] = PM886_REG_GPADC_VCF_OUT, [VBAT_CHAN] = PM886_REG_GPADC_VBAT, [VBAT_SLP_CHAN] = PM886_REG_GPADC_VBAT_SLP, [VBUS_CHAN] = PM886_REG_GPADC_VBUS, [GPADC0_CHAN] = PM886_REG_GPADC_GPADC0, [GPADC1_CHAN] = PM886_REG_GPADC_GPADC1, [GPADC2_CHAN] = PM886_REG_GPADC_GPADC2, [GPADC3_CHAN] = PM886_REG_GPADC_GPADC3, [GND_DET1_CHAN] = PM886_REG_GPADC_GND_DET1, [GND_DET2_CHAN] = PM886_REG_GPADC_GND_DET2, [MIC_DET_CHAN] = PM886_REG_GPADC_MIC_DET, [TINT_CHAN] = PM886_REG_GPADC_TINT, }; #define ADC_CHANNEL_VOLTAGE(index, lsb, name) \ { \ .type = IIO_VOLTAGE, \ .indexed = 1, \ .channel = index, \ .address = lsb, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ BIT(IIO_CHAN_INFO_SCALE), \ .datasheet_name = name, \ } #define ADC_CHANNEL_RESISTANCE(index, lsb, name) \ { \ .type = IIO_RESISTANCE, \ .indexed = 1, \ .channel = index, \ .address = lsb, \ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ .datasheet_name = name, \ } #define ADC_CHANNEL_TEMPERATURE(index, lsb, name) \ { \ .type = IIO_TEMP, \ .indexed = 1, \ .channel = index, \ .address = lsb, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ BIT(IIO_CHAN_INFO_SCALE) | \ BIT(IIO_CHAN_INFO_OFFSET), \ .datasheet_name = name, \ } static const struct iio_chan_spec pm886_gpadc_channels[] = { ADC_CHANNEL_VOLTAGE(VSC_CHAN, 1367, "vsc"), ADC_CHANNEL_VOLTAGE(VCHG_PWR_CHAN, 1709, "vchg_pwr"), ADC_CHANNEL_VOLTAGE(VCF_OUT_CHAN, 1367, "vcf_out"), ADC_CHANNEL_VOLTAGE(VBAT_CHAN, 1367, "vbat"), ADC_CHANNEL_VOLTAGE(VBAT_SLP_CHAN, 1367, "vbat_slp"), ADC_CHANNEL_VOLTAGE(VBUS_CHAN, 1709, "vbus"), ADC_CHANNEL_RESISTANCE(GPADC0_CHAN, 342, "gpadc0"), ADC_CHANNEL_RESISTANCE(GPADC1_CHAN, 342, "gpadc1"), ADC_CHANNEL_RESISTANCE(GPADC2_CHAN, 342, "gpadc2"), ADC_CHANNEL_RESISTANCE(GPADC3_CHAN, 342, "gpadc3"), ADC_CHANNEL_VOLTAGE(GND_DET1_CHAN, 342, "gnddet1"), ADC_CHANNEL_VOLTAGE(GND_DET2_CHAN, 342, "gnddet2"), ADC_CHANNEL_VOLTAGE(MIC_DET_CHAN, 1367, "mic_det"), ADC_CHANNEL_TEMPERATURE(TINT_CHAN, 104, "tint"), }; static const struct regmap_config pm886_gpadc_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = PM886_GPADC_MAX_REGISTER, }; static int gpadc_get_raw(struct iio_dev *iio, enum pm886_gpadc_channel chan) { struct pm886_gpadc *gpadc = iio_priv(iio); __be16 buf; int ret; ret = regmap_bulk_read(gpadc->map, pm886_gpadc_regs[chan], &buf, sizeof(buf)); if (ret) return ret; return be16_to_cpu(buf) >> 4; } static int gpadc_set_bias(struct pm886_gpadc *gpadc, enum pm886_gpadc_channel chan, bool on) { unsigned int gpadc_num = chan - GPADC0_CHAN; unsigned int bits = BIT(gpadc_num + 4) | BIT(gpadc_num); return regmap_assign_bits(gpadc->map, PM886_REG_GPADC_CONFIG(0x14), bits, on); } static int gpadc_find_bias_current(struct iio_dev *iio, struct iio_chan_spec const *chan, unsigned int *raw_uV, unsigned int *raw_uA) { struct pm886_gpadc *gpadc = iio_priv(iio); unsigned int gpadc_num = chan->channel - GPADC0_CHAN; unsigned int reg = PM886_REG_GPADC_CONFIG(0xb + gpadc_num); unsigned long lsb = chan->address; int ret; for (unsigned int i = 0; i < PM886_GPADC_BIAS_LEVELS; i++) { ret = regmap_update_bits(gpadc->map, reg, GENMASK(3, 0), i); if (ret) return ret; /* Wait for the new bias level to apply. */ fsleep(5 * USEC_PER_MSEC); *raw_uA = PM886_GPADC_INDEX_TO_BIAS_uA(i); *raw_uV = gpadc_get_raw(iio, chan->channel) * lsb; /* * Vendor kernel errors out above 1.25 V, but testing shows * that the resistance of the battery detection channel (GPADC2 * on coreprimevelte) reaches about 1.4 MΩ when the battery is * removed, which can't be measured with such a low upper * limit. Therefore, to be able to detect the battery without * ugly externs as used in the vendor fuel gauge driver, * increase this limit a bit. */ if (WARN_ON(*raw_uV > 1500 * (MICRO / MILLI))) return -EIO; /* * Vendor kernel errors out under 300 mV, but for the same * reason as above (except the channel hovers around 3.5 kΩ * with battery present) reduce this limit. */ if (*raw_uV < 200 * (MICRO / MILLI)) { dev_dbg(&iio->dev, "bad bias for chan %d: %d uA @ %d uV\n", chan->channel, *raw_uA, *raw_uV); continue; } dev_dbg(&iio->dev, "good bias for chan %d: %d uA @ %d uV\n", chan->channel, *raw_uA, *raw_uV); return 0; } dev_err(&iio->dev, "failed to find good bias for chan %d\n", chan->channel); return -EINVAL; } static int gpadc_get_resistance_ohm(struct iio_dev *iio, struct iio_chan_spec const *chan) { struct pm886_gpadc *gpadc = iio_priv(iio); unsigned int raw_uV, raw_uA; int ret; ret = gpadc_set_bias(gpadc, chan->channel, true); if (ret) goto out; ret = gpadc_find_bias_current(iio, chan, &raw_uV, &raw_uA); if (ret) goto out; ret = DIV_ROUND_CLOSEST(raw_uV, raw_uA); out: gpadc_set_bias(gpadc, chan->channel, false); return ret; } static int __pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { unsigned long lsb = chan->address; switch (mask) { case IIO_CHAN_INFO_RAW: *val = gpadc_get_raw(iio, chan->channel); if (*val < 0) return *val; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = lsb; if (chan->type == IIO_VOLTAGE) { *val2 = MILLI; return IIO_VAL_FRACTIONAL; } else { return IIO_VAL_INT; } case IIO_CHAN_INFO_OFFSET: /* Raw value is 104 millikelvin/LSB, convert it to 104 millicelsius/LSB */ *val = ABSOLUTE_ZERO_MILLICELSIUS; *val2 = lsb; return IIO_VAL_FRACTIONAL; case IIO_CHAN_INFO_PROCESSED: *val = gpadc_get_resistance_ohm(iio, chan); if (*val < 0) return *val; return IIO_VAL_INT; default: return -EINVAL; } } static int pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { struct device *dev = iio->dev.parent; int ret; ret = pm_runtime_resume_and_get(dev); if (ret) return ret; ret = __pm886_gpadc_read_raw(iio, chan, val, val2, mask); pm_runtime_put_autosuspend(dev); return ret; } static int pm886_gpadc_hw_enable(struct regmap *map) { const u8 config[] = { PM886_GPADC_CONFIG1_EN_ALL, PM886_GPADC_CONFIG2_EN_ALL, PM886_GPADC_GND_DET2_EN, }; int ret; /* Enable the ADC block. */ ret = regmap_set_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0)); if (ret) return ret; /* Enable all channels. */ return regmap_bulk_write(map, PM886_REG_GPADC_CONFIG(0x1), config, ARRAY_SIZE(config)); } static int pm886_gpadc_hw_disable(struct regmap *map) { return regmap_clear_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0)); } static const struct iio_info pm886_gpadc_iio_info = { .read_raw = pm886_gpadc_read_raw, }; static int pm886_gpadc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct pm886_chip *chip = dev_get_drvdata(dev->parent); struct i2c_client *client = chip->client; struct pm886_gpadc *gpadc; struct i2c_client *page; struct iio_dev *iio; int ret; iio = devm_iio_device_alloc(dev, sizeof(*gpadc)); if (!iio) return -ENOMEM; gpadc = iio_priv(iio); dev_set_drvdata(dev, iio); page = devm_i2c_new_dummy_device(dev, client->adapter, client->addr + PM886_PAGE_OFFSET_GPADC); if (IS_ERR(page)) return dev_err_probe(dev, PTR_ERR(page), "Failed to initialize GPADC page\n"); gpadc->map = devm_regmap_init_i2c(page, &pm886_gpadc_regmap_config); if (IS_ERR(gpadc->map)) return dev_err_probe(dev, PTR_ERR(gpadc->map), "Failed to initialize GPADC regmap\n"); iio->name = "88pm886-gpadc"; iio->modes = INDIO_DIRECT_MODE; iio->info = &pm886_gpadc_iio_info; iio->channels = pm886_gpadc_channels; iio->num_channels = ARRAY_SIZE(pm886_gpadc_channels); device_set_node(&iio->dev, dev_fwnode(dev->parent)); ret = devm_pm_runtime_enable(dev); if (ret) return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); pm_runtime_set_autosuspend_delay(dev, 50); pm_runtime_use_autosuspend(dev); ret = devm_iio_device_register(dev, iio); if (ret) return dev_err_probe(dev, ret, "Failed to register ADC\n"); return 0; } static int pm886_gpadc_runtime_resume(struct device *dev) { struct iio_dev *iio = dev_get_drvdata(dev); struct pm886_gpadc *gpadc = iio_priv(iio); return pm886_gpadc_hw_enable(gpadc->map); } static int pm886_gpadc_runtime_suspend(struct device *dev) { struct iio_dev *iio = dev_get_drvdata(dev); struct pm886_gpadc *gpadc = iio_priv(iio); return pm886_gpadc_hw_disable(gpadc->map); } static DEFINE_RUNTIME_DEV_PM_OPS(pm886_gpadc_pm_ops, pm886_gpadc_runtime_suspend, pm886_gpadc_runtime_resume, NULL); static const struct platform_device_id pm886_gpadc_id[] = { { "88pm886-gpadc" }, { } }; MODULE_DEVICE_TABLE(platform, pm886_gpadc_id); static struct platform_driver pm886_gpadc_driver = { .driver = { .name = "88pm886-gpadc", .pm = pm_ptr(&pm886_gpadc_pm_ops), }, .probe = pm886_gpadc_probe, .id_table = pm886_gpadc_id, }; module_platform_driver(pm886_gpadc_driver); MODULE_AUTHOR("Duje Mihanović "); MODULE_DESCRIPTION("Marvell 88PM886 GPADC driver"); MODULE_LICENSE("GPL");