From nobody Thu Oct 2 21:41:02 2025 Received: from mail-wr1-f41.google.com (mail-wr1-f41.google.com [209.85.221.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 93CEC320CDE for ; Thu, 11 Sep 2025 12:43:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757594635; cv=none; b=fKV0BtftRyhX0iwSyKQewHenjz3MdWstaPGaJmyEdqanW3U4UYFDQZ64gvQyznLeXFjz91rYPyJxB4UTcf4SL3AGgGCrRuLGEOBG4/iDZpAIHoBA6SvxHe+0WEz78eSyFs7houL/aphMPJODB8tZCrAXBbkEKMU4HuNXeAKERyo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757594635; c=relaxed/simple; bh=xicKlN7T01FzFwAQgVi+LIJY9t+qAB9UvjZBPPhlc9k=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=CQCFi2wuHLeXFe5wQkc62wLL4qdOJ57RILf6pfdA1Vh5+49FpWDGxmCyGAAidGPPECFKSD8eRF1RGOCn+2Nh8ju6ltpy5A2VwaEetlYUhK2Egoly7C9fObP+ierVCFqYQa0QbdIPDoq/PrYwlsDVPjcjKrmMUQizpW3IMXbFGno= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=IfoK9ezO; arc=none smtp.client-ip=209.85.221.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="IfoK9ezO" Received: by mail-wr1-f41.google.com with SMTP id ffacd0b85a97d-3dae49b1293so411865f8f.1 for ; Thu, 11 Sep 2025 05:43:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1757594632; x=1758199432; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=TxNYsQ5yPcRKxXQ8SxCGsgJHWwOT3fEp+q/uqHvkv3k=; b=IfoK9ezOCEkR/NhPDsFcMCVBpt86r57GUVnueUOf8so3BTNIPpE3W3sEpvaj9xNSH9 RWuHclfyAjFGnB802X8Rbqd802uUNc6iPjJ6Bal/FJXxUudHakAW8fWxxxmRIsrrE99P FJaBRC+GFG1ea0eMv3X4xBxTgSdpr7mzgK4lYyZwKc8DOebssvix9W1747baKiQpvr44 +BTGIDpPsvuJAidUOJ2rgeol43DIKefWJstcnqp5eAEImJN0Z+pTdhsHUwImc5wa/Jwy x57Qsw281b/n0dDhU0GDk8NapCNsHQ+skNkZDiNkoREJDWh2DjsEjJHHkc2u5kDoAigs +jrg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757594632; x=1758199432; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=TxNYsQ5yPcRKxXQ8SxCGsgJHWwOT3fEp+q/uqHvkv3k=; b=nv6WqJxX3SWp6LgwY3K/ptd1+Le9MMu5+zIb4uuZZDBAmAKiQuSngMtH5OnK+Po6b7 l5g227arZ/Ni95gisuPh+bQ4Ghz+2H/behRYeQ31cN2zXjVbZtX8MB0yhvmYB9PaqGwR uZ+bGM/z8m7GjE0uk7g1CJq9wTWOX+1tO9wRcmA5t7SOR7IzRdR2ANkACHtUcdrp2W2s pAfw/Z4IaOgrrVxE/VYcu945j5ewZtxrnCzwdayJc7nfZJ4e63iKV2wMV4vJHxwRMfUx FJeR3o2xxhumFZD5Y1fSgwoWOu92DgFGIbh/duiKlg8iyuj1blbBGo7MRmMNev9qcyyO i3Og== X-Forwarded-Encrypted: i=1; AJvYcCXjGVWInwTKSuGsIQz2WXaPADpuOahDy2mO0kqNi/iKcb4f5PyqqmHj9KANyF5rCb3SiFmzMHhN7Pav2lk=@vger.kernel.org X-Gm-Message-State: AOJu0YzSsnLnmB7PsI2gQ0yrTaBnoAjFTKXGDDJaATxVe+e74JefWIWp hfjwH5GvF0Aik7HiXXC3pVSKldjxRJuWf05oE98B4AfeQIePhlq2JKel X-Gm-Gg: ASbGncvDVQ12N0wvdO7gpLZvQIWypPtvp5RqdHNyMStcB9lJFAoHDFJi92MGyDYXFEw UB1i3kaN6MbB5Ukci76S8/OzUiPMweR83ap5YDcV8dppW/I2lIyvtBz5KLux8GmM0si4CKN8vgL 9AylU7CWx/qHoFiNYvWRlNBp3lbebqBCu4skMkzrlonhNrccG7CuFCCQDKQhYmEXJxGSYUWgOBN mpIkr1+JAFLLirVCGsYDdHjAplXSRycvh4asxIH2GXkkD2SHZ4QHNBB2tQC/HRwdi/uu2Znsuwa k5cV5gqGj69kEKrozz/XiB9/pvMzOUNnc90N4g+k6Vy6WMz3avTbV0GRLAW7YebCHfFszKf7VKb ekRlUi5G/UY63pBFbAEGT7EL988RJUcxNEYRjOo4izTqaE3kTRwfU6gqT5uEpkzKOfsYB8omls5 J9ddls7RQLcEY= X-Google-Smtp-Source: AGHT+IH8M+tPu4NHT1O7jHhUkteo1nPDOTBBWd3Hpd20boEnWMProEatZByjxFmaKpTH8w8FO1Gyiw== X-Received: by 2002:a05:6000:647:b0:3e7:1f63:6e81 with SMTP id ffacd0b85a97d-3e71f6373c5mr16936549f8f.16.1757594631636; Thu, 11 Sep 2025 05:43:51 -0700 (PDT) Received: from Radijator.localdomain (93-143-13-188.adsl.net.t-com.hr. [93.143.13.188]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45e03729c76sm22591495e9.6.2025.09.11.05.43.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 11 Sep 2025 05:43:50 -0700 (PDT) From: =?utf-8?q?Duje_Mihanovi=C4=87?= Date: Thu, 11 Sep 2025 14:43:45 +0200 Subject: [PATCH v4 2/3] iio: adc: Add driver for Marvell 88PM886 PMIC ADC Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250911-88pm886-gpadc-v4-2-60452710d3a0@dujemihanovic.xyz> References: <20250911-88pm886-gpadc-v4-0-60452710d3a0@dujemihanovic.xyz> In-Reply-To: <20250911-88pm886-gpadc-v4-0-60452710d3a0@dujemihanovic.xyz> To: Jonathan Cameron , David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko , Karel Balej , Lee Jones , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: David Wronek , phone-devel@vger.kernel.org, ~postmarketos/upstreaming@lists.sr.ht, linux-kernel@vger.kernel.org, linux-iio@vger.kernel.org, devicetree@vger.kernel.org, =?utf-8?q?Duje_Mihanovi=C4=87?= X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=17398; i=duje@dujemihanovic.xyz; s=20240706; h=from:subject:message-id; bh=0LQF9vrI/sTMRpnoRYb2UWoaVl7IGUrBBrscrObtRLQ=; b=owGbwMvMwCW21nBykGv/WmbG02pJDBmHjjAa8FVOeBZ+p2XOubUbeeoPvrvjLrSmLOCXfOPpy 7o2Cfp/O0pZGMS4GGTFFFly/zte4/0ssnV79jIDmDmsTCBDGLg4BWAiqq8ZGfpucqnWlnUGGC9R aleQT43eUH+jxcrmwhzLU6vzJ4eFSjL8lSiS5JzHuqX2u+JtRrG/7yon1h4w9lfbllXUf/69vGc BDwA= X-Developer-Key: i=duje@dujemihanovic.xyz; a=openpgp; fpr=6DFF41D60DF314B5B76BA630AD319352458FAD03 From: Duje Mihanovi=C4=87 Marvell's 88PM886 PMIC has a so-called General Purpose ADC used for monitoring various system voltages and temperatures. Add the relevant register definitions to the MFD header and a driver for the ADC. Acked-by: Karel Balej # for the PMIC Signed-off-by: Duje Mihanovi=C4=87 --- v4: - Add some missing headers - Fix unit symbols v3: - Add indices to pm886_gpadc_regs[] table - Tidy up macro definitions - Define MAX_REGISTER - Drop some ternary operators - raw_u{v,a} -> raw_u{V,A} - Reorder gpadc_get_resistance_ohm() - Split pm886_gpadc_setup() into hw_{en,dis}able() - Set firmware node the right way v2: - default MFD_88PM886_PMIC - u8[2] -> __be16 - Drop kernel.h include - Add pm886_gpadc struct - Reorder channel enum - Drop GPADC voltage channels - Drop unnecessary masking in gpadc_get_raw() - Extend gpadc_enable_bias() to allow disabling bias - usleep_range() -> fsleep() - PM wrapper for pm886_gpadc_read_raw() - Proper channel info: voltage is RAW | SCALE, temperature is RAW | OFFSET | SCALE, resistance is PROCESSED - Explicitly define channels to en/disable in pm886_gpadc_setup() - Don't explicitly set iio->dev.parent - Miscellaneous style changes --- MAINTAINERS | 5 + drivers/iio/adc/88pm886-gpadc.c | 393 ++++++++++++++++++++++++++++++++++++= ++++ drivers/iio/adc/Kconfig | 13 ++ drivers/iio/adc/Makefile | 1 + include/linux/mfd/88pm886.h | 58 ++++++ 5 files changed, 470 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index cd7ff55b5d321752ac44c91d2d7e74de28e08960..9d94eb3761d492ff7ba4da3238a= a195dfb43e70f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14711,6 +14711,11 @@ F: drivers/regulator/88pm886-regulator.c F: drivers/rtc/rtc-88pm886.c F: include/linux/mfd/88pm886.h =20 +MARVELL 88PM886 PMIC GPADC DRIVER +M: Duje Mihanovi=C4=87 +S: Maintained +F: drivers/iio/adc/88pm886-gpadc.c + MARVELL ARMADA 3700 PHY DRIVERS M: Miquel Raynal S: Maintained diff --git a/drivers/iio/adc/88pm886-gpadc.c b/drivers/iio/adc/88pm886-gpad= c.c new file mode 100644 index 0000000000000000000000000000000000000000..cffe35136685e1814b5b74d1c6f= cfe743125b82c --- /dev/null +++ b/drivers/iio/adc/88pm886-gpadc.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2025, Duje Mihanovi=C4=87 + */ + +#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[] =3D { + [VSC_CHAN] =3D PM886_REG_GPADC_VSC, + [VCHG_PWR_CHAN] =3D PM886_REG_GPADC_VCHG_PWR, + [VCF_OUT_CHAN] =3D PM886_REG_GPADC_VCF_OUT, + [VBAT_CHAN] =3D PM886_REG_GPADC_VBAT, + [VBAT_SLP_CHAN] =3D PM886_REG_GPADC_VBAT_SLP, + [VBUS_CHAN] =3D PM886_REG_GPADC_VBUS, + + [GPADC0_CHAN] =3D PM886_REG_GPADC_GPADC0, + [GPADC1_CHAN] =3D PM886_REG_GPADC_GPADC1, + [GPADC2_CHAN] =3D PM886_REG_GPADC_GPADC2, + [GPADC3_CHAN] =3D PM886_REG_GPADC_GPADC3, + + [GND_DET1_CHAN] =3D PM886_REG_GPADC_GND_DET1, + [GND_DET2_CHAN] =3D PM886_REG_GPADC_GND_DET2, + [MIC_DET_CHAN] =3D PM886_REG_GPADC_MIC_DET, + + [TINT_CHAN] =3D PM886_REG_GPADC_TINT, +}; + +#define ADC_CHANNEL_VOLTAGE(index, lsb, name) \ +{ \ + .type =3D IIO_VOLTAGE, \ + .indexed =3D 1, \ + .channel =3D index, \ + .address =3D lsb, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name =3D name, \ +} + +#define ADC_CHANNEL_RESISTANCE(index, lsb, name) \ +{ \ + .type =3D IIO_RESISTANCE, \ + .indexed =3D 1, \ + .channel =3D index, \ + .address =3D lsb, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_PROCESSED), \ + .datasheet_name =3D name, \ +} + +#define ADC_CHANNEL_TEMPERATURE(index, lsb, name) \ +{ \ + .type =3D IIO_TEMP, \ + .indexed =3D 1, \ + .channel =3D index, \ + .address =3D lsb, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .datasheet_name =3D name, \ +} + +static const struct iio_chan_spec pm886_gpadc_channels[] =3D { + 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 =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D PM886_GPADC_MAX_REGISTER, +}; + +static int gpadc_get_raw(struct iio_dev *iio, enum pm886_gpadc_channel cha= n) +{ + struct pm886_gpadc *gpadc =3D iio_priv(iio); + __be16 buf; + int ret; + + ret =3D 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, b= ool on) +{ + unsigned int gpadc_num =3D chan - GPADC0_CHAN; + unsigned int bits =3D 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 *c= han, + unsigned int *raw_uV, unsigned int *raw_uA) +{ + struct pm886_gpadc *gpadc =3D iio_priv(iio); + unsigned int gpadc_num =3D chan->channel - GPADC0_CHAN; + unsigned int reg =3D PM886_REG_GPADC_CONFIG(0xb + gpadc_num); + unsigned long lsb =3D chan->address; + int ret; + + for (unsigned int i =3D 0; i < PM886_GPADC_BIAS_LEVELS; i++) { + ret =3D 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 =3D PM886_GPADC_INDEX_TO_BIAS_uA(i); + *raw_uV =3D 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=CE=A9 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=CE=A9 + * 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->channe= l); + return -EINVAL; +} + +static int +gpadc_get_resistance_ohm(struct iio_dev *iio, struct iio_chan_spec const *= chan) +{ + struct pm886_gpadc *gpadc =3D iio_priv(iio); + unsigned int raw_uV, raw_uA; + int ret; + + ret =3D gpadc_set_bias(gpadc, chan->channel, true); + if (ret) + goto out; + + ret =3D gpadc_find_bias_current(iio, chan, &raw_uV, &raw_uA); + if (ret) + goto out; + + ret =3D 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 *ch= an, + int *val, int *val2, long mask) +{ + unsigned long lsb =3D chan->address; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val =3D gpadc_get_raw(iio, chan->channel); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val =3D lsb; + + if (chan->type =3D=3D IIO_VOLTAGE) { + *val2 =3D 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 =3D ABSOLUTE_ZERO_MILLICELSIUS; + *val2 =3D lsb; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_PROCESSED: + *val =3D 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 =3D iio->dev.parent; + int ret; + + ret =3D pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret =3D __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[] =3D { + PM886_GPADC_CONFIG1_EN_ALL, + PM886_GPADC_CONFIG2_EN_ALL, + PM886_GPADC_GND_DET2_EN, + }; + int ret; + + /* Enable the ADC block. */ + ret =3D 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 =3D { + .read_raw =3D pm886_gpadc_read_raw, +}; + +static int pm886_gpadc_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct pm886_chip *chip =3D dev_get_drvdata(dev->parent); + struct i2c_client *client =3D chip->client; + struct pm886_gpadc *gpadc; + struct i2c_client *page; + struct iio_dev *iio; + int ret; + + iio =3D devm_iio_device_alloc(dev, sizeof(*gpadc)); + if (!iio) + return -ENOMEM; + + gpadc =3D iio_priv(iio); + dev_set_drvdata(dev, iio); + + page =3D 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 pag= e\n"); + + gpadc->map =3D 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 =3D "88pm886-gpadc"; + iio->modes =3D INDIO_DIRECT_MODE; + iio->info =3D &pm886_gpadc_iio_info; + iio->channels =3D pm886_gpadc_channels; + iio->num_channels =3D ARRAY_SIZE(pm886_gpadc_channels); + device_set_node(&iio->dev, dev_fwnode(dev->parent)); + + ret =3D 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 =3D 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 =3D dev_get_drvdata(dev); + struct pm886_gpadc *gpadc =3D iio_priv(iio); + + return pm886_gpadc_hw_enable(gpadc->map); +} + +static int pm886_gpadc_runtime_suspend(struct device *dev) +{ + struct iio_dev *iio =3D dev_get_drvdata(dev); + struct pm886_gpadc *gpadc =3D 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[] =3D { + { "88pm886-gpadc" }, + { } +}; +MODULE_DEVICE_TABLE(platform, pm886_gpadc_id); + +static struct platform_driver pm886_gpadc_driver =3D { + .driver =3D { + .name =3D "88pm886-gpadc", + .pm =3D pm_ptr(&pm886_gpadc_pm_ops), + }, + .probe =3D pm886_gpadc_probe, + .id_table =3D pm886_gpadc_id, +}; +module_platform_driver(pm886_gpadc_driver); + +MODULE_AUTHOR("Duje Mihanovi=C4=87 "); +MODULE_DESCRIPTION("Marvell 88PM886 GPADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 24f2572c487ea3db2abec3283ebd93357c08baab..04c8478ff707dd16ec943674ac7= f01f33249acf1 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -9,6 +9,19 @@ menu "Analog to digital converters" config IIO_ADC_HELPER tristate =20 +config 88PM886_GPADC + tristate "Marvell 88PM886 GPADC driver" + depends on MFD_88PM886_PMIC + default MFD_88PM886_PMIC + help + Say Y here to enable support for the GPADC (General Purpose ADC) + found on the Marvell 88PM886 PMIC. The GPADC measures various + internal voltages and temperatures, including (but not limited to) + system, battery and USB Vbus. + + To compile this driver as a module, choose M here: the module will be + called 88pm886-gpadc. + config AB8500_GPADC bool "ST-Ericsson AB8500 GPADC driver" depends on AB8500_CORE && REGULATOR_AB8500 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 1c6ca5fd4b6db8c4c40a351b231ba0892e8cd70e..64854907bf3bef7da39f95247e4= e502d01232af3 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_IIO_ADC_HELPER) +=3D industrialio-adc.o =20 # When adding new entries keep the list in alphabetical order +obj-$(CONFIG_88PM886_GPADC) +=3D 88pm886-gpadc.o obj-$(CONFIG_AB8500_GPADC) +=3D ab8500-gpadc.o obj-$(CONFIG_AD_SIGMA_DELTA) +=3D ad_sigma_delta.o obj-$(CONFIG_AD4000) +=3D ad4000.o diff --git a/include/linux/mfd/88pm886.h b/include/linux/mfd/88pm886.h index 85eca44f39ab58ba4cb9ec4216118ee9604d021f..38892ba7b8a42bbecb53621a891= a52a2fd70fd43 100644 --- a/include/linux/mfd/88pm886.h +++ b/include/linux/mfd/88pm886.h @@ -10,6 +10,7 @@ #define PM886_IRQ_ONKEY 0 =20 #define PM886_PAGE_OFFSET_REGULATORS 1 +#define PM886_PAGE_OFFSET_GPADC 2 =20 #define PM886_REG_ID 0x00 =20 @@ -70,6 +71,63 @@ #define PM886_LDO_VSEL_MASK 0x0f #define PM886_BUCK_VSEL_MASK 0x7f =20 +/* GPADC enable/disable registers */ +#define PM886_REG_GPADC_CONFIG(n) (n) + +#define PM886_GPADC_VSC_EN BIT(0) +#define PM886_GPADC_VBAT_EN BIT(1) +#define PM886_GPADC_GNDDET1_EN BIT(3) +#define PM886_GPADC_VBUS_EN BIT(4) +#define PM886_GPADC_VCHG_PWR_EN BIT(5) +#define PM886_GPADC_VCF_OUT_EN BIT(6) +#define PM886_GPADC_CONFIG1_EN_ALL \ + (PM886_GPADC_VSC_EN | \ + PM886_GPADC_VBAT_EN | \ + PM886_GPADC_GNDDET1_EN | \ + PM886_GPADC_VBUS_EN | \ + PM886_GPADC_VCHG_PWR_EN | \ + PM886_GPADC_VCF_OUT_EN) + +#define PM886_GPADC_TINT_EN BIT(0) +#define PM886_GPADC_PMODE_EN BIT(1) +#define PM886_GPADC_GPADC0_EN BIT(2) +#define PM886_GPADC_GPADC1_EN BIT(3) +#define PM886_GPADC_GPADC2_EN BIT(4) +#define PM886_GPADC_GPADC3_EN BIT(5) +#define PM886_GPADC_MIC_DET_EN BIT(6) +#define PM886_GPADC_CONFIG2_EN_ALL \ + (PM886_GPADC_TINT_EN | \ + PM886_GPADC_GPADC0_EN | \ + PM886_GPADC_GPADC1_EN | \ + PM886_GPADC_GPADC2_EN | \ + PM886_GPADC_GPADC3_EN | \ + PM886_GPADC_MIC_DET_EN) + +/* No CONFIG3_EN_ALL because this is the only bit there. */ +#define PM886_GPADC_GND_DET2_EN BIT(0) + +/* GPADC channel registers */ +#define PM886_REG_GPADC_VSC 0x40 +#define PM886_REG_GPADC_VCHG_PWR 0x4c +#define PM886_REG_GPADC_VCF_OUT 0x4e +#define PM886_REG_GPADC_TINT 0x50 +#define PM886_REG_GPADC_GPADC0 0x54 +#define PM886_REG_GPADC_GPADC1 0x56 +#define PM886_REG_GPADC_GPADC2 0x58 +#define PM886_REG_GPADC_VBAT 0xa0 +#define PM886_REG_GPADC_GND_DET1 0xa4 +#define PM886_REG_GPADC_GND_DET2 0xa6 +#define PM886_REG_GPADC_VBUS 0xa8 +#define PM886_REG_GPADC_GPADC3 0xaa +#define PM886_REG_GPADC_MIC_DET 0xac +#define PM886_REG_GPADC_VBAT_SLP 0xb0 + +/* VBAT_SLP is the last register and is 2 bytes wide like other channels. = */ +#define PM886_GPADC_MAX_REGISTER (PM886_REG_GPADC_VBAT_SLP + 1) + +#define PM886_GPADC_BIAS_LEVELS 16 +#define PM886_GPADC_INDEX_TO_BIAS_uA(i) (1 + (i) * 5) + struct pm886_chip { struct i2c_client *client; unsigned int chip_id; --=20 2.51.0