[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <jddpwqo4f4irukwc725b3c5dmsitjgupcvupvbe7fom7vx2p6r@ilfmls4fzrn7>
Date: Wed, 26 Nov 2025 01:13:59 +0200
From: Dmitry Baryshkov <dmitry.baryshkov@....qualcomm.com>
To: david@...t.cz
Cc: Sebastian Reichel <sre@...nel.org>, Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>,
Casey Connolly <casey.connolly@...aro.org>,
Casey Connolly <casey@...nolly.tech>,
Joel Selvaraj <foss@...lselvaraj.com>,
Yassine Oudjana <y.oudjana@...tonmail.com>,
Bjorn Andersson <andersson@...nel.org>,
Konrad Dybcio <konradybcio@...nel.org>,
Alexander Martinz <amartinz@...ftphones.com>,
Barnabás Czémán <barnabas.czeman@...nlining.org>,
Richard Acayan <mailingradian@...il.com>,
Alexey Minnekhanov <alexeymin@...tmarketos.org>,
linux-pm@...r.kernel.org, devicetree@...r.kernel.org,
linux-kernel@...r.kernel.org, linux-arm-msm@...r.kernel.org,
phone-devel@...r.kernel.org
Subject: Re: [PATCH RFC 2/8] power: supply: Add driver for Qualcomm PMI8998
fuel gauge
On Mon, Nov 24, 2025 at 10:53:35PM +0100, David Heidelberg via B4 Relay wrote:
> From: Joel Selvaraj <foss@...lselvaraj.com>
>
> Ths driver supports the fuel gauge hardware available on PMICs known as
> 3rd generation fuel gauge hardware available on PMI8998.
>
> Co-developed-by: Casey Connolly <casey@...nolly.tech>
> Co-developed-by: Barnabás Czémán <barnabas.czeman@...nlining.org>
> Signed-off-by: Barnabás Czémán <barnabas.czeman@...nlining.org>
> Co-developed-by: Yassine Oudjana <y.oudjana@...tonmail.com>
> Signed-off-by: Yassine Oudjana <y.oudjana@...tonmail.com>
> Signed-off-by: David Heidelberg <david@...t.cz>
This can't be applied, there is no author's (Joel Selvaraj) SoB.
> ---
> drivers/power/supply/Kconfig | 8 +
> drivers/power/supply/Makefile | 1 +
> drivers/power/supply/pmi8998_fg.c | 687 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 696 insertions(+)
>
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index 92f9f7aae92f2..4024c6fe3fef2 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -746,6 +746,14 @@ config CHARGER_PM8916_LBC
> To compile this driver as module, choose M here: the
> module will be called pm8916_lbc.
>
> +config BATTERY_PMI8998_FG
> + tristate "Qualcomm PMI8998 PMIC fuel gauge driver"
> + depends on MFD_SPMI_PMIC
> + help
> + Say Y here to enable the Qualcomm PMI8998 PMIC Fuel Gauge driver.
> + This adds support for battery fuel gauging and state of charge of
> + battery connected to the fuel gauge.
> +
> config CHARGER_BQ2415X
> tristate "TI BQ2415x battery charger driver"
> depends on I2C
> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> index 4b79d5abc49a7..03584efa2b1b0 100644
> --- a/drivers/power/supply/Makefile
> +++ b/drivers/power/supply/Makefile
> @@ -96,6 +96,7 @@ obj-$(CONFIG_CHARGER_MT6370) += mt6370-charger.o
> obj-$(CONFIG_CHARGER_QCOM_SMBB) += qcom_smbb.o
> obj-$(CONFIG_BATTERY_PM8916_BMS_VM) += pm8916_bms_vm.o
> obj-$(CONFIG_CHARGER_PM8916_LBC) += pm8916_lbc.o
> +obj-$(CONFIG_BATTERY_PMI8998_FG) += pmi8998_fg.o
> obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
> obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o
> obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o
> diff --git a/drivers/power/supply/pmi8998_fg.c b/drivers/power/supply/pmi8998_fg.c
> new file mode 100644
> index 0000000000000..d5fccd16a013b
> --- /dev/null
> +++ b/drivers/power/supply/pmi8998_fg.c
> @@ -0,0 +1,687 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */
> +
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/math64.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/power_supply.h>
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +#include <linux/workqueue.h>
> +
> +/* SOC */
> +#define BATT_MONOTONIC_SOC 0x009
> +
> +/* BATT */
> +#define PARAM_ADDR_BATT_TEMP 0x150
> +#define BATT_INFO_JEITA_COLD 0x162
> +#define BATT_INFO_JEITA_COOL 0x163
> +#define BATT_INFO_JEITA_WARM 0x164
> +#define BATT_INFO_JEITA_HOT 0x165
> +#define PARAM_ADDR_BATT_VOLTAGE 0x1a0
> +#define PARAM_ADDR_BATT_CURRENT 0x1a2
> +
> +/* MEMIF */
> +#define MEM_INTF_IMA_CFG 0x452
> +#define MEM_INTF_IMA_EXP_STS 0x455
> +#define MEM_INTF_IMA_HW_STS 0x456
> +#define MEM_INTF_IMA_ERR_STS 0x45f
> +#define MEM_INTF_ADDR_LSB 0x461
> +#define MEM_INTF_RD_DATA0 0x467
> +#define MEM_INTF_WR_DATA0 0x463
> +#define MEM_IF_DMA_STS 0x470
> +#define MEM_IF_DMA_CTL 0x471
> +
> +#define BATT_TEMP_LSB_MASK GENMASK(7, 0)
> +#define BATT_TEMP_MSB_MASK GENMASK(2, 0)
> +
> +struct pmi8998_fg_chip {
> + struct device *dev;
> + unsigned int base;
> + struct regmap *regmap;
> + struct notifier_block nb;
> +
> + struct power_supply *batt_psy;
> + struct power_supply *chg_psy;
> + int status;
> + struct delayed_work status_changed_work;
> +};
> +
> +/*
> + * IO functions
> + */
> +
> +/**
> + * @brief pmi8998_fg_read() - Read multiple registers with regmap_bulk_read
Please see Documentation/doc-guide and reforman kerneldoc
appropriately.
> + *
> + * @param chip Pointer to chip
> + * @param val Pointer to read values into
> + * @param addr Address to read from
> + * @param len Number of registers (bytes) to read
> + * @return int 0 on success, negative errno on error
> + */
> +static int pmi8998_fg_read(struct pmi8998_fg_chip *chip, u8 *val, u16 addr, int len)
> +{
> + if (((chip->base + addr) & 0xff00) == 0)
> + return -EINVAL;
A comment would be apprciated
> +
> + dev_vdbg(chip->dev, "%s: Reading 0x%x bytes from 0x%x", __func__, len, addr);
No need to, regmap already has debugging / tracing functionality.
> +
> + return regmap_bulk_read(chip->regmap, chip->base + addr, val, len);
> +}
> +
> +/**
> + * @brief pmi8998_fg_write() - Write multiple registers with regmap_bulk_write
> + *
> + * @param chip Pointer to chip
> + * @param val Pointer to write values from
> + * @param addr Address to write to
> + * @param len Number of registers (bytes) to write
> + * @return int 0 on success, negative errno on error
> + */
> +static int pmi8998_fg_write(struct pmi8998_fg_chip *chip, u8 *val, u16 addr, int len)
> +{
> + bool sec_access = (addr & 0xff) > 0xd0;
> + u8 sec_addr_val = 0xa5;
> + int ret;
> +
> + if (((chip->base + addr) & 0xff00) == 0)
> + return -EINVAL;
> +
> + dev_vdbg(chip->dev, "%s: Writing 0x%x to 0x%x", __func__, *val, addr);
> +
> + if (sec_access) {
> + ret = regmap_bulk_write(chip->regmap,
> + ((chip->base + addr) & 0xff00) | 0xd0,
> + &sec_addr_val, 1);
Isn't it required to lock it again?
> + if (ret)
> + return ret;
> + }
> +
> + return regmap_bulk_write(chip->regmap, chip->base + addr, val, len);
> +}
> +
> +/**
> + * @brief pmi8998_fg_masked_write() - like pmi8998_fg_write but applies
> + * a mask first.
> + *
> + * @param chip Pointer to chip
> + * @param val Pointer to write values from
> + * @param addr Address to write to
> + * @param len Number of registers (bytes) to write
> + * @return int 0 on success, negative errno on error
> + */
> +static int pmi8998_fg_masked_write(struct pmi8998_fg_chip *chip, u16 addr, u8 mask, u8 val)
> +{
> + u8 reg;
> + int ret;
> +
> + ret = pmi8998_fg_read(chip, ®, addr, 1);
> + if (ret)
> + return ret;
> +
> + reg &= ~mask;
> + reg |= val & mask;
> +
> + return pmi8998_fg_write(chip, ®, addr, 1);
regmap_update_bits? Or at least a lock around?
> +}
> +
> +/*
> + * Battery status
> + */
> +
> +/**
> + * @brief pmi8998_fg_get_capacity() - Get remaining capacity of battery
> + *
> + * @param chip Pointer to chip
> + * @param val Pointer to store value at
> + * @return int 0 on success, negative errno on error
> + */
> +static int pmi8998_fg_get_capacity(struct pmi8998_fg_chip *chip, int *val)
> +{
> + u8 cap[2];
> + int ret;
> +
> + ret = pmi8998_fg_read(chip, cap, BATT_MONOTONIC_SOC, 2);
> + if (ret) {
> + dev_err(chip->dev, "Failed to read capacity: %d", ret);
> + return ret;
> + }
> +
> + if (cap[0] != cap[1])
> + cap[0] = cap[0] < cap[1] ? cap[0] : cap[1];
> +
> + *val = DIV_ROUND_CLOSEST((cap[0] - 1) * 98, 0xff - 2) + 1;
> +
> + return 0;
> +}
> +
> +/**
> + * @brief pmi8998_fg_get_temperature() - Get temperature of battery
> + *
> + * @param chip Pointer to chip
> + * @param val Pointer to store value at
> + * @return int 0 on success, negative errno on error
> + */
> +static int pmi8998_fg_get_temperature(struct pmi8998_fg_chip *chip, int *val)
> +{
> + int ret, temp;
> + u8 readval[2];
> +
> + ret = pmi8998_fg_read(chip, readval, PARAM_ADDR_BATT_TEMP, 2);
> + if (ret) {
> + dev_err(chip->dev, "Failed to read temperature: %d\n", ret);
> + return ret;
> + }
> +
> + temp = ((readval[1] & BATT_TEMP_MSB_MASK) << 8) |
> + (readval[0] & BATT_TEMP_LSB_MASK);
> + temp = DIV_ROUND_CLOSEST(temp * 10, 4);
> +
> + *val = temp - 2730;
> +
> + return 0;
> +}
> +
> +/**
> + * @brief pmi8998_fg_get_current() - Get current being drawn from battery
> + *
> + * @param chip Pointer to chip
> + * @param val Pointer to store value at
> + * @return int 0 on success, negative errno on error
> + */
> +static int pmi8998_fg_get_current(struct pmi8998_fg_chip *chip, int *val)
> +{
> + s16 temp;
> + u8 readval[2];
> + int ret;
> +
> + ret = pmi8998_fg_read(chip, readval, PARAM_ADDR_BATT_CURRENT, 2);
> + if (ret) {
> + dev_err(chip->dev, "Failed to read current: %d\n", ret);
> + return ret;
> + }
> +
> + /* handle rev 1 too */
> + temp = (s16)(readval[1] << 8 | readval[0]);
> + *val = div_s64((s64)temp * 488281, 1000);
> +
> + /*
> + * PSY API expects charging batteries to report a positive current, which is inverted
> + * to what the PMIC reports.
> + */
> + *val = -*val;
> +
> + return 0;
> +}
> +
> +/**
> + * @brief pmi8998_fg_get_voltage() - Get voltage of battery
> + *
> + * @param chip Pointer to chip
> + * @param val Pointer to store value at
> + * @return int 0 on success, negative errno on error
> + */
> +static int pmi8998_fg_get_voltage(struct pmi8998_fg_chip *chip, int *val)
> +{
> + int temp;
> + u8 readval[2];
> + int ret;
> +
> + ret = pmi8998_fg_read(chip, readval, PARAM_ADDR_BATT_VOLTAGE, 2);
> + if (ret) {
> + dev_err(chip->dev, "Failed to read voltage: %d\n", ret);
> + return ret;
> + }
> +
> + /* handle rev 1 too */
> + temp = readval[1] << 8 | readval[0];
> + *val = div_u64((u64)temp * 122070, 1000);
> +
> + return 0;
> +}
> +
> +/**
> + * @brief pmi8998_fg_get_temp_threshold() - Get configured temperature thresholds
> + *
> + * @param chip Pointer to chip
> + * @param psp Power supply property of temperature limit
> + * @param val Pointer to store value at
> + * @return int 0 on success, negative errno on error
> + */
> +static int pmi8998_fg_get_temp_threshold(struct pmi8998_fg_chip *chip,
> + enum power_supply_property psp, int *val)
> +{
> + u8 temp;
> + u16 reg;
> + int ret;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_TEMP_MIN:
> + reg = BATT_INFO_JEITA_COLD;
> + break;
> + case POWER_SUPPLY_PROP_TEMP_MAX:
> + reg = BATT_INFO_JEITA_HOT;
> + break;
> + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
> + reg = BATT_INFO_JEITA_COOL;
> + break;
> + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
> + reg = BATT_INFO_JEITA_WARM;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + ret = pmi8998_fg_read(chip, &temp, reg, 1);
> + if (ret < 0) {
> + dev_err(chip->dev, "Failed to read JEITA property %d level: %d\n", psp, ret);
> + return ret;
> + }
> +
> + /* Resolution is 0.5C. Base is -30C. */
> + *val = (((5 * temp) / 10) - 30) * 10;
> +
> + return 0;
> +}
> +
> +/*
> + * Battery power supply
> + */
> +
> +static enum power_supply_property pmi8998_fg_props[] = {
> + POWER_SUPPLY_PROP_STATUS,
> + POWER_SUPPLY_PROP_TECHNOLOGY,
> + POWER_SUPPLY_PROP_CAPACITY,
> + POWER_SUPPLY_PROP_CURRENT_NOW,
> + POWER_SUPPLY_PROP_VOLTAGE_NOW,
> + POWER_SUPPLY_PROP_PRESENT,
> + POWER_SUPPLY_PROP_TEMP,
> + POWER_SUPPLY_PROP_TEMP_MIN,
> + POWER_SUPPLY_PROP_TEMP_MAX,
> + POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
> + POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
> +};
> +
> +static int pmi8998_fg_get_property(struct power_supply *psy,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + struct pmi8998_fg_chip *chip = power_supply_get_drvdata(psy);
> + int temp, ret = 0;
> +
> + dev_dbg(chip->dev, "Getting property: %d", psp);
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_STATUS:
> + /* Get status from charger if available */
> + if (chip->chg_psy &&
> + chip->status != POWER_SUPPLY_STATUS_UNKNOWN) {
> + val->intval = chip->status;
return 0;
drop else
> + } else {
> + /*
> + * Fall back to capacity and current-based
> + * status checking
> + */
> + ret = pmi8998_fg_get_capacity(chip, &temp);
> + if (ret) {
> + val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
> + break;
> + }
> + if (temp == 100) {
> + val->intval = POWER_SUPPLY_STATUS_FULL;
> + break;
> + }
> +
> + ret = pmi8998_fg_get_current(chip, &temp);
> + if (ret) {
> + val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
> + break;
> + }
> + if (temp < 0)
> + val->intval = POWER_SUPPLY_STATUS_CHARGING;
> + else if (temp > 0)
> + val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
> + else
> + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
> + }
> +
> + break;
> + case POWER_SUPPLY_PROP_TECHNOLOGY:
> + val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
> + break;
> + case POWER_SUPPLY_PROP_CAPACITY:
> + ret = pmi8998_fg_get_capacity(chip, &val->intval);
return pmi8998_fg_get_capacity();
etc.
> + break;
> + case POWER_SUPPLY_PROP_CURRENT_NOW:
> + ret = pmi8998_fg_get_current(chip, &val->intval);
> + break;
> + case POWER_SUPPLY_PROP_VOLTAGE_NOW:
> + ret = pmi8998_fg_get_voltage(chip, &val->intval);
> + break;
> + case POWER_SUPPLY_PROP_PRESENT:
> + val->intval = 1;
> + break;
> + case POWER_SUPPLY_PROP_TEMP:
> + ret = pmi8998_fg_get_temperature(chip, &val->intval);
> + break;
> + case POWER_SUPPLY_PROP_TEMP_MIN:
> + case POWER_SUPPLY_PROP_TEMP_MAX:
> + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
> + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
> + ret = pmi8998_fg_get_temp_threshold(chip, psp, &val->intval);
> + break;
> + default:
> + dev_err(chip->dev, "invalid property: %d\n", psp);
> + return -EINVAL;
> + }
> +
> + return ret;
> +}
> +
> +static const struct power_supply_desc batt_psy_desc = {
> + .name = "qcom-battery",
> + .type = POWER_SUPPLY_TYPE_BATTERY,
> + .properties = pmi8998_fg_props,
> + .num_properties = ARRAY_SIZE(pmi8998_fg_props),
> + .get_property = pmi8998_fg_get_property,
> +};
> +
> +/*
> + * Init functions
> + */
> +
> +static int pmi8998_fg_iacs_clear_sequence(struct pmi8998_fg_chip *chip)
> +{
> + u8 temp;
> + int ret;
> +
> + /* clear the error */
> + ret = pmi8998_fg_masked_write(chip, MEM_INTF_IMA_CFG, BIT(2), BIT(2));
> + if (ret) {
> + dev_err(chip->dev, "Failed to write IMA_CFG: %d\n", ret);
> + return ret;
> + }
> +
> + temp = 0x4;
> + ret = pmi8998_fg_write(chip, &temp, MEM_INTF_ADDR_LSB + 1, 1);
> + if (ret) {
> + dev_err(chip->dev, "Failed to write MEM_INTF_ADDR_MSB: %d\n", ret);
> + return ret;
> + }
> +
> + temp = 0x0;
> + ret = pmi8998_fg_write(chip, &temp, MEM_INTF_WR_DATA0 + 3, 1);
> + if (ret) {
> + dev_err(chip->dev, "Failed to write WR_DATA3: %d\n", ret);
> + return ret;
> + }
> +
> + ret = pmi8998_fg_read(chip, &temp, MEM_INTF_RD_DATA0 + 3, 1);
> + if (ret) {
> + dev_err(chip->dev, "Failed to write RD_DATA3: %d\n", ret);
> + return ret;
> + }
> +
> + ret = pmi8998_fg_masked_write(chip, MEM_INTF_IMA_CFG, BIT(2), 0);
> + if (ret) {
> + dev_err(chip->dev, "Failed to write IMA_CFG: %d\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int pmi8998_fg_clear_ima(struct pmi8998_fg_chip *chip, bool check_hw_sts)
> +{
> + u8 err_sts, exp_sts, hw_sts;
> + bool run_err_clr_seq = false;
> + int ret;
> +
> + ret = pmi8998_fg_read(chip, &err_sts, MEM_INTF_IMA_ERR_STS, 1);
> + if (ret) {
> + dev_err(chip->dev, "Failed to read IMA_ERR_STS: %d\n", ret);
> + return ret;
> + }
> +
> + ret = pmi8998_fg_read(chip, &exp_sts,
> + MEM_INTF_IMA_EXP_STS, 1);
> + if (ret) {
> + dev_err(chip->dev, "Failed to read IMA_EXP_STS: %d\n", ret);
> + return ret;
> + }
> +
> + if (check_hw_sts) {
> + ret = pmi8998_fg_read(chip, &hw_sts,
> + MEM_INTF_IMA_HW_STS, 1);
> + if (ret) {
> + dev_err(chip->dev, "Failed to read IMA_HW_STS: %d\n", ret);
> + return ret;
> + }
> + /*
> + * Lower nibble should be equal to upper nibble before SRAM
> + * transactions begins from SW side.
> + */
> + if ((hw_sts & 0x0f) != hw_sts >> 4) {
> + dev_dbg(chip->dev, "IMA HW not in correct state, hw_sts=%x\n",
> + hw_sts);
> + run_err_clr_seq = true;
> + }
> + }
> +
> + if (exp_sts & (BIT(0) | BIT(1) | BIT(3) |
> + BIT(4) | BIT(5) | BIT(6) |
> + BIT(7))) {
Wrong indentation. Please #define the mask if possible.
> + dev_dbg(chip->dev, "IMA exception bit set, exp_sts=%x\n", exp_sts);
> + run_err_clr_seq = true;
> + }
> +
> + if (run_err_clr_seq) {
> + ret = pmi8998_fg_iacs_clear_sequence(chip);
> + if (!ret)
> + return -EAGAIN;
> + }
> +
> + return 0;
> +}
> +
> +static irqreturn_t pmi8998_fg_handle_soc_delta(int irq, void *data)
> +{
> + struct pmi8998_fg_chip *chip = data;
> +
> + /* Signal change in state of charge */
> + power_supply_changed(chip->batt_psy);
> + dev_dbg(chip->dev, "SOC changed");
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void pmi8998_fg_status_changed_worker(struct work_struct *work)
> +{
> + struct pmi8998_fg_chip *chip = container_of(work, struct pmi8998_fg_chip,
> + status_changed_work.work);
> +
> + power_supply_changed(chip->batt_psy);
> +}
> +
> +static int pmi8998_fg_notifier_call(struct notifier_block *nb, unsigned long val, void *v)
> +{
> + struct pmi8998_fg_chip *chip = container_of(nb, struct pmi8998_fg_chip, nb);
> + struct power_supply *psy = v;
> + union power_supply_propval propval;
> + int ret;
> +
> + if (psy == chip->chg_psy) {
> + ret = power_supply_get_property(psy,
> + POWER_SUPPLY_PROP_STATUS, &propval);
> + if (ret)
> + chip->status = POWER_SUPPLY_STATUS_UNKNOWN;
> +
> + chip->status = propval.intval;
> +
> + power_supply_changed(chip->batt_psy);
> +
> + if (chip->status == POWER_SUPPLY_STATUS_UNKNOWN) {
> + /*
> + * REVISIT: Find better solution or remove current-based
> + * status checking once checking is properly implemented
> + * in charger drivers
> +
> + * Sometimes it take a while for current to stabilize,
> + * so signal property change again later to make sure
> + * current-based status is properly detected.
> + */
> + cancel_delayed_work_sync(&chip->status_changed_work);
> + schedule_delayed_work(&chip->status_changed_work,
> + msecs_to_jiffies(1000));
> + }
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +static int pmi8998_fg_probe(struct platform_device *pdev)
> +{
> + struct power_supply_config supply_config = {};
> + struct pmi8998_fg_chip *chip;
> + const __be32 *prop_addr;
> + int irq;
> + u8 dma_status;
> + bool error_present;
> + int ret;
> +
> + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
> + if (!chip)
> + return -ENOMEM;
> +
> + chip->dev = &pdev->dev;
> +
> + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
> + if (!chip->regmap) {
> + dev_err(chip->dev, "Failed to locate the regmap\n");
> + return -ENODEV;
> + }
> +
> + /* Get base address */
> + prop_addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL);
> + if (!prop_addr) {
> + dev_err(chip->dev, "Failed to read SOC base address from dt\n");
> + return -EINVAL;
> + }
> + chip->base = be32_to_cpu(*prop_addr);
> +
> + /*
> + * Change the FG_MEM_INT interrupt to track IACS_READY
> + * condition instead of end-of-transaction. This makes sure
> + * that the next transaction starts only after the hw is ready.
> + * IACS_INTR_SRC_SLCT is BIT(3)
> + */
> + ret = pmi8998_fg_masked_write(chip, MEM_INTF_IMA_CFG, BIT(3), BIT(3));
> + if (ret) {
> + dev_err(chip->dev,
> + "Failed to configure interrupt sourete: %d\n", ret);
> + return ret;
> + }
> +
> + ret = pmi8998_fg_clear_ima(chip, true);
> + if (ret && ret != -EAGAIN) {
> + dev_err(chip->dev, "Failed to clear IMA exception: %d\n", ret);
> + return ret;
> + }
> +
> + /* Check and clear DMA errors */
DMA?
> + ret = pmi8998_fg_read(chip, &dma_status, MEM_IF_DMA_STS, 1);
> + if (ret < 0) {
> + dev_err(chip->dev, "Failed to read dma_status: %d\n", ret);
> + return ret;
> + }
> +
> + error_present = dma_status & (BIT(1) | BIT(2));
> + ret = pmi8998_fg_masked_write(chip, MEM_IF_DMA_CTL, BIT(0),
> + error_present ? BIT(0) : 0);
> + if (ret < 0) {
> + dev_err(chip->dev, "Failed to write dma_ctl: %d\n", ret);
> + return ret;
> + }
> +
> + supply_config.drv_data = chip;
> + supply_config.fwnode = dev_fwnode(&pdev->dev);
> +
> + chip->batt_psy = devm_power_supply_register(chip->dev,
> + &batt_psy_desc, &supply_config);
> + if (IS_ERR(chip->batt_psy)) {
> + if (PTR_ERR(chip->batt_psy) != -EPROBE_DEFER)
> + dev_err(&pdev->dev, "Failed to register battery\n");
> + return PTR_ERR(chip->batt_psy);
> + }
> +
> + platform_set_drvdata(pdev, chip);
> +
> + /* Get soc-delta IRQ */
> + irq = of_irq_get_byname(pdev->dev.of_node, "soc-delta");
> + if (irq < 0) {
> + dev_err(&pdev->dev, "Failed to get irq soc-delta byname: %d\n",
> + irq);
> + return irq;
> + }
> +
> + ret = devm_request_threaded_irq(chip->dev, irq, NULL,
> + pmi8998_fg_handle_soc_delta,
> + IRQF_ONESHOT, "soc-delta", chip);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "Failed to request soc-delta IRQ: %d\n", ret);
> + return ret;
> + }
> +
> + /* Optional: Get charger power supply for status checking */
> + chip->chg_psy = power_supply_get_by_reference(of_fwnode_handle(chip->dev->of_node),
> + "power-supplies");
> + if (IS_ERR(chip->chg_psy)) {
> + ret = PTR_ERR(chip->chg_psy);
> + dev_warn(chip->dev, "Failed to get charger supply: %d\n", ret);
> + chip->chg_psy = NULL;
> + }
> +
> + if (chip->chg_psy) {
> + INIT_DELAYED_WORK(&chip->status_changed_work,
> + pmi8998_fg_status_changed_worker);
> +
> + chip->nb.notifier_call = pmi8998_fg_notifier_call;
> + ret = power_supply_reg_notifier(&chip->nb);
> + if (ret) {
> + dev_err(chip->dev,
> + "Failed to register notifier: %d\n", ret);
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static const struct of_device_id fg_match_id_table[] = {
> + { .compatible = "qcom,pmi8998-fg" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, fg_match_id_table);
> +
> +static struct platform_driver pmi8998_fg_driver = {
> + .probe = pmi8998_fg_probe,
> + .driver = {
> + .name = "pmi8998-fg",
> + .of_match_table = fg_match_id_table,
> + },
> +};
> +
> +module_platform_driver(pmi8998_fg_driver);
> +
> +MODULE_AUTHOR("Casey Connolly <casey@...nolly.tech>");
> +MODULE_AUTHOR("Joel Selvaraj <foss@...lselvaraj.com>");
> +MODULE_AUTHOR("Yassine Oudjana <y.oudjana@...tonmail.com>");
> +MODULE_DESCRIPTION("Qualcomm PMI8998 Fuel Gauge Driver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.51.0
>
>
--
With best wishes
Dmitry
Powered by blists - more mailing lists