Re: [PATCH v3 2/3] iio: adc: mt6397-auxadc: add mt6397 PMIC AUXADC driver
From: AngeloGioacchino Del Regno
Date: Mon Sep 21 2026 - 06:31:06 EST
On 9/20/26 07:31, Ryan Brue wrote:
The mt6397 AUXADC is a 10-bit ADC behind the SoC's PMIC wrapper. On boards
built around this PMIC it is the only way to read the battery: the SoC's
AUXADC is wired to board thermistors and the charger ICs these boards use
have no ADC of their own.
Add a driver exposing the battery voltage and battery temperature
channels. Only those two are described, so a channel ID in the device tree
indexes the driver's channel array rather than the PMIC's channel number,
as mt6323-auxadc does. The ready bit lives in a channel's raw result
register, but the value comes from the chip's factory-calibrated copy.
The battery voltage is measured through ISENSE, because a board with a
switching charger in the power path leaves BATSNS on the charger's system
rail instead of on the pack. The thermistor only reads correctly with the
PMIC's battery-detect bias and input buffer enabled, which take 20 ms to
settle. Both are switched back off afterwards.
Reads average sixteen conversions in software; the chip's sample
accumulator makes no measurable difference at any setting, so it is left
at one sample per conversion.
Assisted-by: LLM
Signed-off-by: Ryan Brue <ryanbrue.dev@xxxxxxxxx>
---
A new driver was created here, instead of modifying an existing driver such
as mt6323-auxadc or mt6359-auxadc, for the following reasons:
- Both mt6323-auxadc and mt6359-auxadc select channels through a request
register (1 bit per channel), while mt6397 uses a 4-bit numeric field
CHSEL in CON1 (10:7), and then pulses a START bit (CON1 bit 0).
- For mt6323-auxadc, which is the closest I could find to the mt6397
(CON0..CON27), it has 13 more registers than the mt6397 (CON0..CON14).
It uses CON22 for its request register, and reads the result value
from the same register as the ready bit. We don't do that - the mt6397
has a factory-calibrated value for each channel at 0x16 higher than the
raw value. mt6323 also has a 1800 mV / 15 bit scale / resolution while
we have 1200 mV / 10 bits. We also have some per-channel preparation
that we have to do before the burst, that the mt6323 doesn't have to
do.
- For mt6359-auxadc, it has a more generic framework for describing the
AUXADC, but it assumes requests are channel-per-bit, and so we would
have to basically ignore req_idx, req_mask, rdy_idx, and rdy_mask.
- We also have our own software sampling, which the vendor does too
(Amazon Fire OS based on Linux 3.18). We'd have to have our own
sampling callback to do it.
---
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 11 ++
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/mt6397-auxadc.c | 391 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 404 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 41d7b3a67415..9b254f98b5d7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16717,6 +16717,7 @@ MEDIATEK MT6397 PMIC AUXADC DRIVER
M: Ryan Brue <ryanbrue.dev@xxxxxxxxx>
L: linux-iio@xxxxxxxxxxxxxxx
S: Maintained
+F: drivers/iio/adc/mt6397-auxadc.c
F: include/dt-bindings/iio/adc/mediatek,mt6397-auxadc.h
MEDIATEK MT6735 CLOCK & RESET DRIVERS
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 415e519ad4eb..9a1d36ed53cb 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1233,6 +1233,17 @@ config MEDIATEK_MT6370_ADC
This driver can also be built as a module. If so, the module
will be called "mt6370-adc".
+config MEDIATEK_MT6397_AUXADC
+ tristate "MediaTek MT6397 PMIC AUXADC driver"
+ depends on MFD_MT6397
+ help
+ Say yes here to enable support for MediaTek MT6397 PMIC Auxiliary ADC.
+ This driver provides the battery voltage and battery temperature
+ channels used by the fuel gauge on boards built around this PMIC.
+
+ This driver can also be built as a module. If so, the module will be
+ called mt6397-auxadc.
+
config MEDIATEK_MT6577_AUXADC
tristate "MediaTek AUXADC driver"
depends on ARCH_MEDIATEK || COMPILE_TEST
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index dcec0abb03b7..13cb4206bdb7 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -105,6 +105,7 @@ obj-$(CONFIG_MEDIATEK_MT6323_AUXADC) += mt6323-auxadc.o
obj-$(CONFIG_MEDIATEK_MT6359_AUXADC) += mt6359-auxadc.o
obj-$(CONFIG_MEDIATEK_MT6360_ADC) += mt6360-adc.o
obj-$(CONFIG_MEDIATEK_MT6370_ADC) += mt6370-adc.o
+obj-$(CONFIG_MEDIATEK_MT6397_AUXADC) += mt6397-auxadc.o
obj-$(CONFIG_MEDIATEK_MT6577_AUXADC) += mt6577_auxadc.o
obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
diff --git a/drivers/iio/adc/mt6397-auxadc.c b/drivers/iio/adc/mt6397-auxadc.c
new file mode 100644
index 000000000000..d860cd3a8922
--- /dev/null
+++ b/drivers/iio/adc/mt6397-auxadc.c
@@ -0,0 +1,391 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MediaTek MT6397 PMIC AUXADC IIO driver
+ *
+ * Copyright (c) 2026 Ryan Brue <ryanbrue.dev@xxxxxxxxx>
+ *
+ * Based on drivers/iio/adc/mt6323-auxadc.c
+ */
+
..snip..
+
+static int mt6397_auxadc_battemp_bias_on(struct mt6397_auxadc *adc)
+{
+ struct regmap *map = adc->regmap;
+ int ret;
+
+ ret = regmap_set_bits(map, MT6397_AUXADC_CON0,
+ MT6397_AUXADC_CON0_BUF_PWD_ON);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(map, MT6397_AUXADC_CON0,
+ MT6397_AUXADC_CON0_BUF_PWD_B);
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(map, MT6397_CHR_CON7,
+ MT6397_CHR_CON7_BATON_TDET_EN);
+}
+
+static int mt6397_auxadc_battemp_bias_off(struct mt6397_auxadc *adc)
+{
+ struct regmap *map = adc->regmap;
+ int ret;
+
+ ret = regmap_clear_bits(map, MT6397_CHR_CON7, MT6397_CHR_CON7_BATON_TDET_EN);
+ if (ret)
+ return ret;
+
+ ret = regmap_clear_bits(map, MT6397_AUXADC_CON0, MT6397_AUXADC_CON0_BUF_PWD_B);
+ if (ret)
+ return ret;
If this fails, and the internal buffers are kept enabled, are you sure that
the AUXADC won't lock up?
Should it lock up, I imagine that the best thing that could happen is a power
leakage, but can be as bad as a system lockup.
Should we unconditionally also clear the MT6397_AUXADC_CON0_BUF_PWD_ON bit
even if MT6397_AUXADC_CON0_BUF_PWD_B clearing fails?
+
+ return regmap_clear_bits(map, MT6397_AUXADC_CON0,
+ MT6397_AUXADC_CON0_BUF_PWD_ON);
+}
+
+static int mt6397_auxadc_isense_enable(struct mt6397_auxadc *adc)
+{
+ struct regmap *map = adc->regmap;
+ int ret;
+
+ /*
+ * Two of the five independent input enables, not a select field:
+ * BATSNS is cleared first so that the two inputs are never enabled
+ * onto the battery channel at the same time.
+ */
+ ret = regmap_clear_bits(map, MT6397_CHR_CON16, MT6397_CHR_CON16_ADCIN_VBAT_EN);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(map, MT6397_CHR_CON16, MT6397_CHR_CON16_ADCIN_VSEN_EN);
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(map, MT6397_AUXADC_CON14,
+ MT6397_AUXADC_CON14_CH0_NORM_SEL |
+ MT6397_AUXADC_CON14_CH0_LBAT_SEL);
+}
+
+static int mt6397_auxadc_isense_disable(struct mt6397_auxadc *adc)
+{
+ struct regmap *map = adc->regmap;
+ int ret;
+
+ ret = regmap_clear_bits(map, MT6397_AUXADC_CON14,
+ MT6397_AUXADC_CON14_CH0_NORM_SEL |
+ MT6397_AUXADC_CON14_CH0_LBAT_SEL);
+ if (ret)
+ return ret;
+
+ return regmap_clear_bits(map, MT6397_CHR_CON16,
+ MT6397_CHR_CON16_ADCIN_VSEN_EN |
+ MT6397_CHR_CON16_ADCIN_VBAT_EN);
Same here, but makes even more sense: if clearing the channel selector
fails, you still want to disable the ADCIN in an effort to avoid power
leakage.
Everything else looks good, so I'm sure that you're getting my R-b on
version 4.
Cheers,
Angelo