1418 lines
37 KiB
Diff
1418 lines
37 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Teguh Sobirin <teguh@sobir.in>
|
|
Date: Wed, 20 Nov 2024 23:00:52 +0700
|
|
Subject: power: supply: Driver for Qualcomm FG
|
|
|
|
support for pm8150 taken from:
|
|
https://github.com/map220v/sm8150-mainline
|
|
Signed-off-by: Teguh Sobirin <teguh@sobir.in>
|
|
---
|
|
drivers/power/supply/Kconfig | 8 +
|
|
drivers/power/supply/Makefile | 1 +
|
|
drivers/power/supply/qcom_fg.c | 1364 ++++++++++
|
|
3 files changed, 1373 insertions(+)
|
|
|
|
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
|
|
index 111111111111..222222222222 100644
|
|
--- a/drivers/power/supply/Kconfig
|
|
+++ b/drivers/power/supply/Kconfig
|
|
@@ -1017,6 +1017,14 @@ config CHARGER_QCOM_SMB5
|
|
adds support for the SMB5 switch mode battery charger found
|
|
in PM8150B and related PMICs.
|
|
|
|
+config BATTERY_QCOM_FG
|
|
+ tristate "Qualcomm PMIC fuel gauge driver"
|
|
+ depends on MFD_SPMI_PMIC
|
|
+ help
|
|
+ Say Y here to enable the Qualcomm PMIC Fuel Gauge driver. This
|
|
+ adds support for battery fuel gauging and state of charge of
|
|
+ battery connected to the fuel gauge.
|
|
+
|
|
config FUEL_GAUGE_MM8013
|
|
tristate "Mitsumi MM8013 fuel gauge driver"
|
|
depends on I2C
|
|
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
|
|
index 111111111111..222222222222 100644
|
|
--- a/drivers/power/supply/Makefile
|
|
+++ b/drivers/power/supply/Makefile
|
|
@@ -117,4 +117,5 @@ obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
|
|
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
|
|
obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_pmi8998_charger.o
|
|
obj-$(CONFIG_CHARGER_QCOM_SMB5) += qcom_pm8150b_charger.o
|
|
+obj-$(CONFIG_BATTERY_QCOM_FG) += qcom_fg.o
|
|
obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o
|
|
diff --git a/drivers/power/supply/qcom_fg.c b/drivers/power/supply/qcom_fg.c
|
|
new file mode 100644
|
|
index 000000000000..111111111111
|
|
--- /dev/null
|
|
+++ b/drivers/power/supply/qcom_fg.c
|
|
@@ -0,0 +1,1364 @@
|
|
+// 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
|
|
+
|
|
+/* RRADC */
|
|
+#define ADC_RR_BATT_TEMP_LSB 0x288
|
|
+
|
|
+/* MEMIF */
|
|
+#define MEM_INTF_STS(chip) (chip->ops->memif_base + 0x10)
|
|
+#define MEM_INTF_CFG(chip) (chip->ops->memif_base + 0x50)
|
|
+#define MEM_INTF_CTL(chip) (chip->ops->memif_base + 0x51)
|
|
+#define MEM_INTF_IMA_CFG(chip) (chip->ops->memif_base + 0x52)
|
|
+#define MEM_INTF_IMA_EXP_STS(chip) (chip->ops->memif_base + 0x55)
|
|
+#define MEM_INTF_IMA_HW_STS(chip) (chip->ops->memif_base + 0x56)
|
|
+#define MEM_INTF_IMA_ERR_STS(chip) (chip->ops->memif_base + 0x5f)
|
|
+#define MEM_INTF_IMA_BYTE_EN(chip) (chip->ops->memif_base + 0x60)
|
|
+#define MEM_INTF_ADDR_LSB(chip) (chip->ops->memif_base + 0x61)
|
|
+#define MEM_INTF_RD_DATA0(chip) (chip->ops->memif_base + 0x67)
|
|
+#define MEM_INTF_WR_DATA0(chip) (chip->ops->memif_base + 0x63)
|
|
+#define MEM_IF_DMA_STS(chip) (chip->ops->memif_base + 0x70)
|
|
+#define MEM_IF_DMA_CTL(chip) (chip->ops->memif_base + 0x71)
|
|
+
|
|
+/* SRAM addresses */
|
|
+#define TEMP_THRESHOLD 0x454
|
|
+#define BATT_TEMP 0x550
|
|
+#define BATT_VOLTAGE_CURRENT 0x5cc
|
|
+
|
|
+#define BATT_TEMP_LSB_MASK GENMASK(7, 0)
|
|
+#define BATT_TEMP_MSB_MASK GENMASK(2, 0)
|
|
+
|
|
+#define BATT_TEMP_JEITA_COLD 100
|
|
+#define BATT_TEMP_JEITA_COOL 50
|
|
+#define BATT_TEMP_JEITA_WARM 400
|
|
+#define BATT_TEMP_JEITA_HOT 450
|
|
+
|
|
+#define MEM_INTF_AVAIL BIT(0)
|
|
+#define MEM_INTF_CTL_BURST BIT(7)
|
|
+#define MEM_INTF_CTL_WR_EN BIT(6)
|
|
+#define RIF_MEM_ACCESS_REQ BIT(7)
|
|
+
|
|
+#define MEM_IF_TIMEOUT_MS 5000
|
|
+#define SRAM_ACCESS_RELEASE_DELAY_MS 500
|
|
+
|
|
+struct qcom_fg_chip;
|
|
+
|
|
+struct qcom_fg_ops {
|
|
+ int (*get_capacity)(struct qcom_fg_chip *chip, int *);
|
|
+ int (*get_temperature)(struct qcom_fg_chip *chip, int *);
|
|
+ int (*get_current)(struct qcom_fg_chip *chip, int *);
|
|
+ int (*get_voltage)(struct qcom_fg_chip *chip, int *);
|
|
+ int (*get_temp_threshold)(struct qcom_fg_chip *chip,
|
|
+ enum power_supply_property psp, int *);
|
|
+ int (*set_temp_threshold)(struct qcom_fg_chip *chip,
|
|
+ enum power_supply_property psp, int);
|
|
+
|
|
+ short memif_base;
|
|
+};
|
|
+
|
|
+struct qcom_fg_chip {
|
|
+ struct device *dev;
|
|
+ unsigned int base;
|
|
+ struct regmap *regmap;
|
|
+ const struct qcom_fg_ops *ops;
|
|
+ struct notifier_block nb;
|
|
+
|
|
+ struct power_supply *batt_psy;
|
|
+ struct power_supply_battery_info *batt_info;
|
|
+ struct power_supply *chg_psy;
|
|
+ int status;
|
|
+ struct delayed_work status_changed_work;
|
|
+
|
|
+ struct completion sram_access_granted;
|
|
+ struct completion sram_access_revoked;
|
|
+ struct workqueue_struct *sram_wq;
|
|
+ struct delayed_work sram_release_access_work;
|
|
+ spinlock_t sram_request_lock;
|
|
+ spinlock_t sram_rw_lock;
|
|
+ int sram_requests;
|
|
+};
|
|
+
|
|
+/************************
|
|
+ * IO FUNCTIONS
|
|
+ * **********************/
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_read() - Read multiple registers with regmap_bulk_read
|
|
+ *
|
|
+ * @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 qcom_fg_read(struct qcom_fg_chip *chip, u8 *val, u16 addr, int len)
|
|
+{
|
|
+ if (((chip->base + addr) & 0xff00) == 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ dev_vdbg(chip->dev, "%s: Reading 0x%x bytes from 0x%x", __func__, len, addr);
|
|
+
|
|
+ return regmap_bulk_read(chip->regmap, chip->base + addr, val, len);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_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 qcom_fg_write(struct qcom_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);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return regmap_bulk_write(chip->regmap, chip->base + addr, val, len);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_masked_write() - like qcom_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 qcom_fg_masked_write(struct qcom_fg_chip *chip, u16 addr, u8 mask, u8 val)
|
|
+{
|
|
+ u8 reg;
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_fg_read(chip, ®, addr, 1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ reg &= ~mask;
|
|
+ reg |= val & mask;
|
|
+
|
|
+ return qcom_fg_write(chip, ®, addr, 1);
|
|
+}
|
|
+
|
|
+/************************
|
|
+ * SRAM FUNCTIONS
|
|
+ * **********************/
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_sram_check_access() - Check if SRAM is accessible
|
|
+ *
|
|
+ * @param chip Pointer to chip
|
|
+ * @return bool true if accessible, false otherwise
|
|
+ */
|
|
+static bool qcom_fg_sram_check_access(struct qcom_fg_chip *chip)
|
|
+{
|
|
+ u8 mem_if_status;
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_fg_read(chip, &mem_if_status,
|
|
+ MEM_INTF_STS(chip), 1);
|
|
+
|
|
+ if (ret || !(mem_if_status & MEM_INTF_AVAIL))
|
|
+ return false;
|
|
+
|
|
+ ret = qcom_fg_read(chip, &mem_if_status,
|
|
+ MEM_INTF_CFG(chip), 1);
|
|
+
|
|
+ if (ret)
|
|
+ return false;
|
|
+
|
|
+ return !!(mem_if_status & RIF_MEM_ACCESS_REQ);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_sram_request_access() - Request access to SRAM and wait for it
|
|
+ *
|
|
+ * @param chip Pointer to chip
|
|
+ * @return int 0 on success, negative errno on error
|
|
+ */
|
|
+static int qcom_fg_sram_request_access(struct qcom_fg_chip *chip)
|
|
+{
|
|
+ bool sram_accessible;
|
|
+ int ret;
|
|
+
|
|
+ spin_lock(&chip->sram_request_lock);
|
|
+
|
|
+ sram_accessible = qcom_fg_sram_check_access(chip);
|
|
+
|
|
+ dev_vdbg(chip->dev, "Requesting SRAM access, current state: %d, requests: %d\n",
|
|
+ sram_accessible, chip->sram_requests);
|
|
+
|
|
+ if (!sram_accessible && chip->sram_requests == 0) {
|
|
+ ret = qcom_fg_masked_write(chip, MEM_INTF_CFG(chip),
|
|
+ RIF_MEM_ACCESS_REQ, RIF_MEM_ACCESS_REQ);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to set SRAM access request bit: %d\n", ret);
|
|
+
|
|
+ spin_unlock(&chip->sram_request_lock);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ chip->sram_requests++;
|
|
+
|
|
+ spin_unlock(&chip->sram_request_lock);
|
|
+
|
|
+ /* Wait to get access to SRAM, and try again if interrupted */
|
|
+ do {
|
|
+ ret = wait_for_completion_interruptible_timeout(
|
|
+ &chip->sram_access_granted,
|
|
+ msecs_to_jiffies(MEM_IF_TIMEOUT_MS));
|
|
+ } while(ret == -ERESTARTSYS);
|
|
+
|
|
+ if (ret <= 0) {
|
|
+ ret = -ETIMEDOUT;
|
|
+
|
|
+ spin_lock(&chip->sram_request_lock);
|
|
+ chip->sram_requests--;
|
|
+ spin_unlock(&chip->sram_request_lock);
|
|
+ } else {
|
|
+ ret = 0;
|
|
+
|
|
+ reinit_completion(&chip->sram_access_revoked);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_sram_release_access() - Release access to SRAM
|
|
+ *
|
|
+ * @param chip Pointer to chip
|
|
+ * @return int 0 on success, negative errno on error
|
|
+ */
|
|
+static void qcom_fg_sram_release_access(struct qcom_fg_chip *chip)
|
|
+{
|
|
+ spin_lock(&chip->sram_request_lock);
|
|
+
|
|
+ chip->sram_requests--;
|
|
+
|
|
+ if(WARN(chip->sram_requests < 0,
|
|
+ "sram_requests=%d, cannot be negative! resetting to 0.\n",
|
|
+ chip->sram_requests))
|
|
+ chip->sram_requests = 0;
|
|
+
|
|
+ if(chip->sram_requests == 0)
|
|
+ /* Schedule access release */
|
|
+ queue_delayed_work(chip->sram_wq, &chip->sram_release_access_work,
|
|
+ msecs_to_jiffies(SRAM_ACCESS_RELEASE_DELAY_MS));
|
|
+
|
|
+ spin_unlock(&chip->sram_request_lock);
|
|
+}
|
|
+
|
|
+static void qcom_fg_sram_release_access_worker(struct work_struct *work)
|
|
+{
|
|
+ struct qcom_fg_chip *chip;
|
|
+ bool wait = false;
|
|
+ int ret;
|
|
+
|
|
+ chip = container_of(work, struct qcom_fg_chip, sram_release_access_work.work);
|
|
+
|
|
+ spin_lock(&chip->sram_request_lock);
|
|
+
|
|
+ /* Request access release if there are still no access requests */
|
|
+ if(chip->sram_requests == 0) {
|
|
+ qcom_fg_masked_write(chip, MEM_INTF_CFG(chip), RIF_MEM_ACCESS_REQ, 0);
|
|
+ wait = true;
|
|
+ }
|
|
+
|
|
+ spin_unlock(&chip->sram_request_lock);
|
|
+
|
|
+ if(!wait)
|
|
+ return;
|
|
+
|
|
+ /* Wait for SRAM access to be released, and try again if interrupted */
|
|
+ do {
|
|
+ ret = wait_for_completion_interruptible_timeout(
|
|
+ &chip->sram_access_revoked,
|
|
+ msecs_to_jiffies(MEM_IF_TIMEOUT_MS));
|
|
+ } while(ret == -ERESTARTSYS);
|
|
+
|
|
+ reinit_completion(&chip->sram_access_granted);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_sram_config_access() - Configure access to SRAM
|
|
+ *
|
|
+ * @param chip Pointer to chip
|
|
+ * @param write 0 for read access, 1 for write access
|
|
+ * @param burst 1 to access mutliple addresses successively
|
|
+ * @return int 0 on success, negative errno on error
|
|
+ */
|
|
+static int qcom_fg_sram_config_access(struct qcom_fg_chip *chip,
|
|
+ bool write, bool burst)
|
|
+{
|
|
+ u8 intf_ctl;
|
|
+ int ret;
|
|
+
|
|
+ intf_ctl = (write ? MEM_INTF_CTL_WR_EN : 0)
|
|
+ | (burst ? MEM_INTF_CTL_BURST : 0);
|
|
+
|
|
+ ret = qcom_fg_write(chip, &intf_ctl,
|
|
+ MEM_INTF_CTL(chip), 1);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to configure SRAM access: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_sram_read() - Read data from SRAM
|
|
+ *
|
|
+ * @param chip Pointer to chip
|
|
+ * @param val Pointer to read values into
|
|
+ * @param addr Address to read from
|
|
+ * @param len Number of bytes to read
|
|
+ * @return int 0 on success, negative errno on error
|
|
+ */
|
|
+static int qcom_fg_sram_read(struct qcom_fg_chip *chip,
|
|
+ u8 *val, u16 addr, int len, int offset)
|
|
+{
|
|
+ u8 *rd_data = val;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = qcom_fg_sram_request_access(chip);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to request SRAM access: %d", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ spin_lock(&chip->sram_rw_lock);
|
|
+
|
|
+ dev_vdbg(chip->dev,
|
|
+ "Reading address 0x%x with offset %d of length %d from SRAM",
|
|
+ addr, len, offset);
|
|
+
|
|
+ ret = qcom_fg_sram_config_access(chip, 0, (len > 4));
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to configure SRAM access: %d", ret);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ while(len > 0) {
|
|
+ /* Set SRAM address register */
|
|
+ ret = qcom_fg_write(chip, (u8 *) &addr,
|
|
+ MEM_INTF_ADDR_LSB(chip), 2);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to set SRAM address: %d", ret);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ ret = qcom_fg_read(chip, rd_data,
|
|
+ MEM_INTF_RD_DATA0(chip) + offset, len);
|
|
+
|
|
+ addr += 4;
|
|
+
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ rd_data += 4 - offset;
|
|
+ len -= 4 - offset;
|
|
+ offset = 0;
|
|
+ }
|
|
+out:
|
|
+ spin_unlock(&chip->sram_rw_lock);
|
|
+ qcom_fg_sram_release_access(chip);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_sram_write() - Write data to SRAM
|
|
+ *
|
|
+ * @param chip Pointer to chip
|
|
+ * @param val Pointer to write values from
|
|
+ * @param addr Address to write to
|
|
+ * @param len Number of bytes to write
|
|
+ * @return int 0 on success, negative errno on error
|
|
+ */
|
|
+static int qcom_fg_sram_write(struct qcom_fg_chip *chip,
|
|
+ u8 *val, u16 addr, int len, int offset)
|
|
+{
|
|
+ u8 *wr_data = val;
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_fg_sram_request_access(chip);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to request SRAM access: %d", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ spin_lock(&chip->sram_rw_lock);
|
|
+
|
|
+ dev_vdbg(chip->dev,
|
|
+ "Wrtiting address 0x%x with offset %d of length %d to SRAM",
|
|
+ addr, len, offset);
|
|
+
|
|
+ ret = qcom_fg_sram_config_access(chip, 1, (len > 4));
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to configure SRAM access: %d", ret);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ while(len > 0) {
|
|
+ /* Set SRAM address register */
|
|
+ ret = qcom_fg_write(chip, (u8 *) &addr,
|
|
+ MEM_INTF_ADDR_LSB(chip), 2);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to set SRAM address: %d", ret);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ ret = qcom_fg_write(chip, wr_data,
|
|
+ MEM_INTF_WR_DATA0(chip) + offset, len);
|
|
+
|
|
+ addr += 4;
|
|
+
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ wr_data += 4 - offset;
|
|
+ len -= 4 - offset;
|
|
+ offset = 0;
|
|
+ }
|
|
+out:
|
|
+ spin_unlock(&chip->sram_rw_lock);
|
|
+ qcom_fg_sram_release_access(chip);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/*************************
|
|
+ * BATTERY STATUS
|
|
+ * ***********************/
|
|
+
|
|
+/**
|
|
+ * @brief qcom_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 qcom_fg_get_capacity(struct qcom_fg_chip *chip, int *val)
|
|
+{
|
|
+ u8 cap[2];
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_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 qcom_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 qcom_fg_get_temperature(struct qcom_fg_chip *chip, int *val)
|
|
+{
|
|
+ int temp;
|
|
+ u8 readval[2];
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_fg_sram_read(chip, readval, BATT_TEMP, 2, 2);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to read temperature: %d", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ temp = readval[1] << 8 | readval[0];
|
|
+ *val = temp * 625 / 1000 - 2730;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_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 qcom_fg_get_current(struct qcom_fg_chip *chip, int *val)
|
|
+{
|
|
+ s16 temp;
|
|
+ u8 readval[2];
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_fg_sram_read(chip, readval, BATT_VOLTAGE_CURRENT, 2, 3);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to read current: %d", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ temp = (s16)(readval[1] << 8 | readval[0]);
|
|
+ *val = div_s64((s64)temp * 152587, 1000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_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 qcom_fg_get_voltage(struct qcom_fg_chip *chip, int *val)
|
|
+{
|
|
+ int temp;
|
|
+ u8 readval[2];
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_fg_sram_read(chip, readval, BATT_VOLTAGE_CURRENT, 2, 1);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to read voltage: %d", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ temp = readval[1] << 8 | readval[0];
|
|
+ *val = div_u64((u64)temp * 152587, 1000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_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 qcom_fg_get_temp_threshold(struct qcom_fg_chip *chip,
|
|
+ enum power_supply_property psp, int *val)
|
|
+{
|
|
+ u8 temp;
|
|
+ int offset;
|
|
+ int ret;
|
|
+
|
|
+ switch (psp) {
|
|
+ case POWER_SUPPLY_PROP_TEMP_MIN:
|
|
+ offset = 0;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_TEMP_MAX:
|
|
+ offset = 1;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
|
|
+ offset = 2;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
|
|
+ offset = 3;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = qcom_fg_sram_read(chip, &temp, TEMP_THRESHOLD, 1, offset);
|
|
+ if (ret < 0) {
|
|
+ dev_err(chip->dev, "Failed to read JEITA property %d level: %d\n", psp, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ *val = (temp - 30) * 10;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_set_temp_threshold() - Configure temperature thresholds
|
|
+ *
|
|
+ * @param chip Pointer to chip
|
|
+ * @param psp Power supply property of temperature limit
|
|
+ * @param val Pointer to get value from
|
|
+ * @return int 0 on success, negative errno on error
|
|
+ */
|
|
+static int qcom_fg_set_temp_threshold(struct qcom_fg_chip *chip,
|
|
+ enum power_supply_property psp, int val)
|
|
+{
|
|
+ u8 temp;
|
|
+ int offset;
|
|
+ int ret;
|
|
+
|
|
+ switch (psp) {
|
|
+ case POWER_SUPPLY_PROP_TEMP_MIN:
|
|
+ offset = 0;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_TEMP_MAX:
|
|
+ offset = 1;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
|
|
+ offset = 2;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
|
|
+ offset = 3;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ temp = val / 10 + 30;
|
|
+
|
|
+ ret = qcom_fg_sram_write(chip, &temp, TEMP_THRESHOLD, 1, offset);
|
|
+ if (ret < 0) {
|
|
+ dev_err(chip->dev, "Failed to write JEITA property %d level: %d\n", psp, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*************************
|
|
+ * BATTERY STATUS, GEN3
|
|
+ * ***********************/
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_gen3_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 qcom_fg_gen3_get_temperature(struct qcom_fg_chip *chip, int *val)
|
|
+{
|
|
+ int temp;
|
|
+ u8 readval[2];
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_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 qcom_fg_gen3_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 qcom_fg_gen3_get_current(struct qcom_fg_chip *chip, int *val)
|
|
+{
|
|
+ s16 temp;
|
|
+ u8 readval[2];
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_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);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_gen3_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 qcom_fg_gen3_get_voltage(struct qcom_fg_chip *chip, int *val)
|
|
+{
|
|
+ int temp;
|
|
+ u8 readval[2];
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_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 qcom_fg_gen3_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 qcom_fg_gen3_get_temp_threshold(struct qcom_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 = qcom_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 STATUS, GEN4
|
|
+ * ***********************/
|
|
+
|
|
+/**
|
|
+ * @brief qcom_fg_gen4_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 qcom_fg_gen4_get_temperature(struct qcom_fg_chip *chip, int *val)
|
|
+{
|
|
+ int temp;
|
|
+ u8 readval[2];
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_fg_read(chip, readval, ADC_RR_BATT_TEMP_LSB, 2);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to read temperature: %d", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ temp = readval[1] << 8 | readval[0];
|
|
+ *val = temp * 10;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/************************
|
|
+ * BATTERY POWER SUPPLY
|
|
+ * **********************/
|
|
+
|
|
+/* Pre-Gen3 fuel gauge. PMI8996 and older */
|
|
+static const struct qcom_fg_ops ops_fg = {
|
|
+ .get_capacity = qcom_fg_get_capacity,
|
|
+ .get_temperature = qcom_fg_get_temperature,
|
|
+ .get_current = qcom_fg_get_current,
|
|
+ .get_voltage = qcom_fg_get_voltage,
|
|
+ .get_temp_threshold = qcom_fg_get_temp_threshold,
|
|
+ .set_temp_threshold = qcom_fg_set_temp_threshold,
|
|
+ .memif_base = 0x400,
|
|
+};
|
|
+
|
|
+/* Gen3 fuel gauge. PMI8998 and newer */
|
|
+static const struct qcom_fg_ops ops_fg_gen3 = {
|
|
+ .get_capacity = qcom_fg_get_capacity,
|
|
+ .get_temperature = qcom_fg_gen3_get_temperature,
|
|
+ .get_current = qcom_fg_gen3_get_current,
|
|
+ .get_voltage = qcom_fg_gen3_get_voltage,
|
|
+ .get_temp_threshold = qcom_fg_gen3_get_temp_threshold,
|
|
+ .memif_base = 0x400,
|
|
+};
|
|
+
|
|
+/* Gen4 fuel gauge. PM8150B and newer */
|
|
+static const struct qcom_fg_ops ops_fg_gen4 = {
|
|
+ .get_capacity = qcom_fg_get_capacity,
|
|
+ .get_temperature = qcom_fg_gen4_get_temperature,
|
|
+ .get_current = qcom_fg_gen3_get_current,
|
|
+ .get_voltage = qcom_fg_gen3_get_voltage,
|
|
+ .get_temp_threshold = qcom_fg_gen3_get_temp_threshold,
|
|
+ .memif_base = 0x300,
|
|
+};
|
|
+
|
|
+static enum power_supply_property qcom_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_VOLTAGE_MIN_DESIGN,
|
|
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
|
|
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
|
|
+ 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 qcom_fg_get_property(struct power_supply *psy,
|
|
+ enum power_supply_property psp,
|
|
+ union power_supply_propval *val)
|
|
+{
|
|
+ struct qcom_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;
|
|
+ break;
|
|
+ } else {
|
|
+ /*
|
|
+ * Fall back to capacity and current-based
|
|
+ * status checking
|
|
+ */
|
|
+ ret = chip->ops->get_capacity(chip, &temp);
|
|
+ if (ret) {
|
|
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
+ break;
|
|
+ }
|
|
+ if (temp == 100) {
|
|
+ val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ ret = chip->ops->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 = chip->ops->get_capacity(chip, &val->intval);
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
+ ret = chip->ops->get_current(chip, &val->intval);
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
+ ret = chip->ops->get_voltage(chip, &val->intval);
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
+ val->intval = chip->batt_info->voltage_min_design_uv;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
|
+ val->intval = chip->batt_info->voltage_max_design_uv;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
|
|
+ val->intval = chip->batt_info->charge_full_design_uah;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_PRESENT:
|
|
+ val->intval = 1;
|
|
+ break;
|
|
+ case POWER_SUPPLY_PROP_TEMP:
|
|
+ ret = chip->ops->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 = chip->ops->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 = qcom_fg_props,
|
|
+ .num_properties = ARRAY_SIZE(qcom_fg_props),
|
|
+ .get_property = qcom_fg_get_property,
|
|
+};
|
|
+
|
|
+/********************
|
|
+ * INIT FUNCTIONS
|
|
+ * ******************/
|
|
+
|
|
+static int qcom_fg_iacs_clear_sequence(struct qcom_fg_chip *chip)
|
|
+{
|
|
+ u8 temp;
|
|
+ int ret;
|
|
+
|
|
+ /* clear the error */
|
|
+ ret = qcom_fg_masked_write(chip, MEM_INTF_IMA_CFG(chip), BIT(2), BIT(2));
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to write IMA_CFG: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ temp = 0x4;
|
|
+ ret = qcom_fg_write(chip, &temp, MEM_INTF_ADDR_LSB(chip) + 1, 1);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to write MEM_INTF_ADDR_MSB: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ temp = 0x0;
|
|
+ ret = qcom_fg_write(chip, &temp, MEM_INTF_WR_DATA0(chip) + 3, 1);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to write WR_DATA3: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = qcom_fg_read(chip, &temp, MEM_INTF_RD_DATA0(chip) + 3, 1);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to write RD_DATA3: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = qcom_fg_masked_write(chip, MEM_INTF_IMA_CFG(chip), BIT(2), 0);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to write IMA_CFG: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int qcom_fg_clear_ima(struct qcom_fg_chip *chip,
|
|
+ bool check_hw_sts)
|
|
+{
|
|
+ u8 err_sts, exp_sts, hw_sts;
|
|
+ bool run_err_clr_seq = false;
|
|
+ int ret;
|
|
+
|
|
+ ret = qcom_fg_read(chip, &err_sts,
|
|
+ MEM_INTF_IMA_ERR_STS(chip), 1);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to read IMA_ERR_STS: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = qcom_fg_read(chip, &exp_sts,
|
|
+ MEM_INTF_IMA_EXP_STS(chip), 1);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Failed to read IMA_EXP_STS: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (check_hw_sts) {
|
|
+ ret = qcom_fg_read(chip, &hw_sts,
|
|
+ MEM_INTF_IMA_HW_STS(chip), 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))) {
|
|
+ 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 = qcom_fg_iacs_clear_sequence(chip);
|
|
+ if (!ret)
|
|
+ return -EAGAIN;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static irqreturn_t qcom_fg_handle_soc_delta(int irq, void *data)
|
|
+{
|
|
+ struct qcom_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 irqreturn_t qcom_fg_handle_mem_avail(int irq, void *data)
|
|
+{
|
|
+ struct qcom_fg_chip *chip = data;
|
|
+
|
|
+ if (qcom_fg_sram_check_access(chip)) {
|
|
+ complete_all(&chip->sram_access_granted);
|
|
+ dev_dbg(chip->dev, "SRAM access granted");
|
|
+ } else {
|
|
+ complete_all(&chip->sram_access_revoked);
|
|
+ dev_dbg(chip->dev, "SRAM access revoked");
|
|
+ }
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void qcom_fg_status_changed_worker(struct work_struct *work)
|
|
+{
|
|
+ struct qcom_fg_chip *chip = container_of(work, struct qcom_fg_chip,
|
|
+ status_changed_work.work);
|
|
+
|
|
+ power_supply_changed(chip->batt_psy);
|
|
+}
|
|
+
|
|
+static int qcom_fg_notifier_call(struct notifier_block *nb,
|
|
+ unsigned long val, void *v)
|
|
+{
|
|
+ struct qcom_fg_chip *chip = container_of(nb, struct qcom_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 qcom_fg_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct power_supply_config supply_config = {};
|
|
+ struct qcom_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->ops = of_device_get_match_data(&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 = qcom_fg_masked_write(chip,
|
|
+ MEM_INTF_IMA_CFG(chip), BIT(3), BIT(3));
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to configure interrupt sourete: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = qcom_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 */
|
|
+ ret = qcom_fg_read(chip, &dma_status, MEM_IF_DMA_STS(chip), 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 = qcom_fg_masked_write(chip, MEM_IF_DMA_CTL(chip), 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.of_node = pdev->dev.of_node;
|
|
+
|
|
+ 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);
|
|
+
|
|
+ ret = power_supply_get_battery_info(chip->batt_psy, &chip->batt_info);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Failed to get battery info: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Initialize SRAM */
|
|
+ if (of_device_is_compatible(pdev->dev.of_node, "qcom,pmi8994-fg")) {
|
|
+ irq = of_irq_get_byname(pdev->dev.of_node, "mem-avail");
|
|
+ if (irq < 0) {
|
|
+ dev_err(&pdev->dev, "Failed to get irq mem-avail byname: %d\n",
|
|
+ irq);
|
|
+ return irq;
|
|
+ }
|
|
+
|
|
+ init_completion(&chip->sram_access_granted);
|
|
+ init_completion(&chip->sram_access_revoked);
|
|
+
|
|
+ chip->sram_wq = create_singlethread_workqueue("qcom_fg");
|
|
+ INIT_DELAYED_WORK(&chip->sram_release_access_work,
|
|
+ qcom_fg_sram_release_access_worker);
|
|
+
|
|
+ ret = devm_request_threaded_irq(chip->dev, irq, NULL,
|
|
+ qcom_fg_handle_mem_avail,
|
|
+ IRQF_ONESHOT, "mem-avail", chip);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&pdev->dev, "Failed to request mem-avail IRQ: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ spin_lock_init(&chip->sram_request_lock);
|
|
+ spin_lock_init(&chip->sram_rw_lock);
|
|
+ chip->sram_requests = 0;
|
|
+ }
|
|
+
|
|
+ /* Set default temperature thresholds */
|
|
+ if (chip->ops->set_temp_threshold) {
|
|
+ ret = chip->ops->set_temp_threshold(chip,
|
|
+ POWER_SUPPLY_PROP_TEMP_MIN,
|
|
+ BATT_TEMP_JEITA_COLD);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to set cold threshold: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = chip->ops->set_temp_threshold(chip,
|
|
+ POWER_SUPPLY_PROP_TEMP_MAX,
|
|
+ BATT_TEMP_JEITA_WARM);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to set warm threshold: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = chip->ops->set_temp_threshold(chip,
|
|
+ POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
|
|
+ BATT_TEMP_JEITA_COOL);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to set cool threshold: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = chip->ops->set_temp_threshold(chip,
|
|
+ POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
|
|
+ BATT_TEMP_JEITA_HOT);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev,
|
|
+ "Failed to set hot threshold: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* 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,
|
|
+ qcom_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_phandle(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,
|
|
+ qcom_fg_status_changed_worker);
|
|
+
|
|
+ chip->nb.notifier_call = qcom_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 void qcom_fg_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct qcom_fg_chip *chip = platform_get_drvdata(pdev);
|
|
+
|
|
+ power_supply_put_battery_info(chip->batt_psy, chip->batt_info);
|
|
+
|
|
+ if(chip->sram_wq)
|
|
+ destroy_workqueue(chip->sram_wq);
|
|
+}
|
|
+
|
|
+static const struct of_device_id fg_match_id_table[] = {
|
|
+ { .compatible = "qcom,pmi8994-fg", .data = &ops_fg },
|
|
+ { .compatible = "qcom,pmi8998-fg", .data = &ops_fg_gen3 },
|
|
+ { .compatible = "qcom,pm8150b-fg", .data = &ops_fg_gen4 },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, fg_match_id_table);
|
|
+
|
|
+static struct platform_driver qcom_fg_driver = {
|
|
+ .probe = qcom_fg_probe,
|
|
+ .remove_new = qcom_fg_remove,
|
|
+ .driver = {
|
|
+ .name = "qcom-fg",
|
|
+ .of_match_table = fg_match_id_table,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(qcom_fg_driver);
|
|
+
|
|
+MODULE_AUTHOR("Caleb Connolly <caleb@connolly.tech>");
|
|
+MODULE_AUTHOR("Joel Selvaraj <jo@jsfamily.in>");
|
|
+MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>");
|
|
+MODULE_DESCRIPTION("Qualcomm PMIC Fuel Gauge Driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
\ No newline at end of file
|
|
--
|
|
Armbian
|
|
|