From: Dzmitry Sankouski <dsankouski@gmail.com>
To: "Sebastian Reichel" <sre@kernel.org>,
"Bjorn Andersson" <andersson@kernel.org>,
"Michael Turquette" <mturquette@baylibre.com>,
"Stephen Boyd" <sboyd@kernel.org>,
"Neil Armstrong" <neil.armstrong@linaro.org>,
"Jessica Zhang" <quic_jesszhan@quicinc.com>,
"Sam Ravnborg" <sam@ravnborg.org>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"David Airlie" <airlied@gmail.com>,
"Daniel Vetter" <daniel@ffwll.ch>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Lee Jones" <lee@kernel.org>,
"Dmitry Torokhov" <dmitry.torokhov@gmail.com>,
"Pavel Machek" <pavel@ucw.cz>,
"Liam Girdwood" <lgirdwood@gmail.com>,
"Mark Brown" <broonie@kernel.org>,
"Uwe Kleine-König" <ukleinek@kernel.org>,
"Krzysztof Kozlowski" <krzk@kernel.org>,
"Konrad Dybcio" <konrad.dybcio@linaro.org>,
"Chanwoo Choi" <cw00.choi@samsung.com>,
phone-devel@vger.kernel.org
Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-arm-msm@vger.kernel.org, linux-clk@vger.kernel.org,
dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
linux-input@vger.kernel.org, linux-leds@vger.kernel.org,
linux-pwm@vger.kernel.org, linux-samsung-soc@vger.kernel.org,
Dzmitry Sankouski <dsankouski@gmail.com>
Subject: [PATCH v3 15/23] power: supply: max77705: Add fuel gauge driver for Maxim 77705
Date: Tue, 18 Jun 2024 16:59:49 +0300 [thread overview]
Message-ID: <20240618-starqltechn_integration_upstream-v3-15-e3f6662017ac@gmail.com> (raw)
In-Reply-To: <20240618-starqltechn_integration_upstream-v3-0-e3f6662017ac@gmail.com>
Add driver for Maxim 77705 fuel gauge (part of max77705
MFD driver) providing power supply class information to userspace.
The driver is configured through DTS (battery and system related
settings).
Signed-off-by: Dzmitry Sankouski <dsankouski@gmail.com>
---
drivers/power/supply/Kconfig | 7 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/max77705_fuelgauge.c | 624 ++++++++++++++++++++++++++++++
include/linux/power/max77705_fuelgauge.h | 107 +++++
4 files changed, 739 insertions(+)
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 47ca8cc00a80..a3fdfcb5aa64 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -558,6 +558,13 @@ config CHARGER_MAX77705
help
Say Y to enable support for the Maxim MAX77705 battery charger.
+config FUEL_GAUGE_MAX77705
+ tristate "MAX77705 fuel gauge driver"
+ depends on MFD_MAX77705
+ default n
+ help
+ Say Y to enable support for MAXIM MAX77705 fuel gauge driver.
+
config CHARGER_MAX77976
tristate "Maxim MAX77976 battery charger driver"
depends on I2C
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index dbec648c78c9..e1abad8b6b75 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -78,6 +78,7 @@ obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o
obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
obj-$(CONFIG_CHARGER_MAX77705) += max77705_charger.o
+obj-$(CONFIG_FUEL_GAUGE_MAX77705) += max77705_fuelgauge.o
obj-$(CONFIG_CHARGER_MAX77976) += max77976_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
diff --git a/drivers/power/supply/max77705_fuelgauge.c b/drivers/power/supply/max77705_fuelgauge.c
new file mode 100644
index 000000000000..108a829421d7
--- /dev/null
+++ b/drivers/power/supply/max77705_fuelgauge.c
@@ -0,0 +1,624 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * max77705_fuelgauge.c
+ * Samsung max77705 Fuel Gauge Driver
+ *
+ * Copyright (C) 2015 Samsung Electronics
+ * Copyright (C) 2024 Dzmitry Sankouski <dsankouski@gmail.com>
+ */
+
+#include <linux/of_gpio.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/power/max77705_fuelgauge.h>
+#include <linux/mfd/max77705-private.h>
+
+static const char *max77705_fuelgauge_model = "max77705";
+static const char *max77705_fuelgauge_manufacturer = "Maxim Integrated";
+static struct dentry *debugfs_file;
+
+static enum power_supply_property max77705_fuelgauge_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+};
+
+static int max77705_fg_read_vcell(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2];
+ u16 w_data;
+
+ if (regmap_noinc_read(regmap, VCELL_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read VCELL_REG\n", __func__);
+ return -1;
+ }
+
+ w_data = (data[1] << 8) | data[0];
+
+ return max77705_fg_vs_convert(w_data);
+}
+
+static int max77705_fg_read_vfocv(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2];
+ u16 w_data;
+
+ if (regmap_noinc_read(regmap, VFOCV_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read VFOCV_REG\n", __func__);
+ return -1;
+ }
+
+ w_data = (data[1] << 8) | data[0];
+
+ return max77705_fg_vs_convert(w_data);
+}
+
+static int max77705_fg_read_avg_vcell(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2];
+ u16 w_data;
+
+ if (regmap_noinc_read(regmap, AVR_VCELL_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read AVR_VCELL_REG\n", __func__);
+ return -1;
+ }
+
+ w_data = (data[1] << 8) | data[0];
+ return max77705_fg_vs_convert(w_data);
+}
+
+static int max77705_fg_check_battery_present(struct max77705_fuelgauge_data
+ *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 status_data[2];
+
+ if (regmap_noinc_read(regmap, STATUS_REG, status_data, sizeof(status_data)) < 0) {
+ dev_err(fuelgauge->dev, "Failed to read STATUS_REG\n");
+ return 0;
+ }
+
+ return !(status_data[0] & MAX77705_BAT_ABSENT_MASK);
+}
+
+static int max77705_fg_read_temp(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2] = { 0, 0 };
+ int temper = 0;
+
+ if (regmap_noinc_read(regmap, TEMPERATURE_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read TEMPERATURE_REG\n", __func__);
+ return -1;
+ }
+
+ if (data[1] & BIT(7))
+ temper = ((~(data[1])) & 0xFF) + 1;
+ else
+ temper = data[1] & 0x7f;
+
+ temper *= 10;
+ temper += data[0] * 10 / 256;
+
+ return temper;
+}
+
+static int max77705_fg_read_socrep(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2];
+ int soc;
+
+ if (regmap_noinc_read(regmap, SOCREP_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read SOCREP_REG\n", __func__);
+ return -1;
+ }
+
+ soc = data[1];
+
+ return min(soc, 100);
+}
+
+static int max77705_fg_read_fullcaprep(struct max77705_fuelgauge_data
+ *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2];
+ int ret;
+
+ if (regmap_noinc_read(regmap, FULLCAP_REP_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read FULLCAP_REP_REG\n", __func__);
+ return -1;
+ }
+
+ ret = (data[1] << 8) + data[0];
+
+ return ret * 1000;
+}
+
+static int max77705_fg_read_repcap(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2];
+ int ret;
+
+ if (regmap_noinc_read(regmap, REMCAP_REP_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read REMCAP_REP_REG\n", __func__);
+ return -1;
+ }
+
+ ret = (data[1] << 8) + data[0];
+
+ return ret * 1000;
+}
+
+static int max77705_fg_read_charge_design(struct max77705_fuelgauge_data
+ *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2];
+ int reg;
+
+ if (regmap_noinc_read(regmap, DESIGNCAP_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read DESIGNCAP_REG\n", __func__);
+ return -1;
+ }
+
+ reg = (data[1] << 8) | (data[0]);
+
+ return reg * 1000;
+}
+
+static int max77705_fg_set_charge_design(struct regmap *regmap, int value)
+{
+ u8 data[2];
+ int value_mah;
+
+ value_mah = value / 1000;
+ data[0] = value_mah & 0xFF;
+ data[1] = (value_mah >> 8) & 0xFF;
+
+ if (regmap_noinc_write(regmap, DESIGNCAP_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to write DESIGNCAP_REG\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int max77705_fg_read_current(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 reg_data[2];
+ s16 reg_value;
+ s32 i_current;
+
+ if (regmap_noinc_read(regmap, CURRENT_REG, reg_data, sizeof(reg_data)) < 0) {
+ pr_err("%s: Failed to read CURRENT\n", __func__);
+ return -1;
+ }
+
+ reg_value = ((reg_data[1] << 8) | reg_data[0]);
+ i_current = max77705_fg_cs_convert(reg_value, fuelgauge->rsense_conductance);
+
+ return i_current;
+}
+
+static int max77705_fg_read_avg_current(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 reg_data[2];
+ u32 reg_value;
+ s32 avg_current;
+
+ if (regmap_noinc_read(regmap, AVG_CURRENT_REG, reg_data, sizeof(reg_data)) < 0) {
+ pr_err("%s: Failed to read AVG_CURRENT_REG\n", __func__);
+ return -1;
+ }
+
+ reg_value = ((reg_data[1] << 8) | reg_data[0]);
+ avg_current = max77705_fg_cs_convert(reg_value, fuelgauge->rsense_conductance);
+
+ return avg_current;
+}
+
+static int max77705_fg_read_tte(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2];
+ int reg;
+
+ if (regmap_noinc_read(regmap, TIME_TO_EMPTY_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read TIME_TO_EMPTY_REG\n", __func__);
+ return -1;
+ }
+
+ reg = (data[1] << 8) | (data[0]);
+
+ return reg;
+}
+
+static int max77705_fg_read_ttf(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2];
+ int reg;
+
+ if (regmap_noinc_read(regmap, TIME_TO_FULL_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read TIME_TO_FULL_REG\n", __func__);
+ return -1;
+ }
+
+ reg = (data[1] << 8) | (data[0]);
+
+ return reg;
+}
+
+static int max77705_fg_read_cycle(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2];
+ int ret;
+
+ if (regmap_noinc_read(regmap, CYCLES_REG, data, sizeof(data)) < 0) {
+ pr_err("%s: Failed to read CYCLES_REG\n", __func__);
+ return -1;
+ }
+
+ ret = (data[1] << 8) + data[0];
+
+ return ret;
+}
+
+static int max77705_battery_get_status(struct max77705_fuelgauge_data *fuelgauge)
+{
+ int current_now;
+ int am_i_supplied;
+
+ am_i_supplied = power_supply_am_i_supplied(fuelgauge->psy_fg);
+ if (am_i_supplied) {
+ if (am_i_supplied == -ENODEV)
+ dev_err(fuelgauge->dev,
+ "power supply not found, fall back to current-based status checking\n");
+ else
+ return POWER_SUPPLY_STATUS_CHARGING;
+ }
+ if (max77705_fg_read_socrep(fuelgauge) < 100) {
+ current_now = max77705_fg_read_current(fuelgauge);
+ if (current_now > 0)
+ return POWER_SUPPLY_STATUS_CHARGING;
+ else if (current_now < 0)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ return POWER_SUPPLY_STATUS_FULL;
+ }
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+}
+
+static bool max77705_fg_init(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ u8 data[2] = { 0, 0 };
+ u32 volt_threshold = 0;
+ u32 temp_threshold = 0;
+
+ if (fuelgauge->auto_discharge_en) {
+ /* Auto discharge EN & Alert Enable */
+ regmap_noinc_read(regmap, CONFIG2_REG, data, sizeof(data));
+ data[1] |= MAX77705_AUTO_DISCHARGE_EN_MASK >> 8;
+ regmap_noinc_write(regmap, CONFIG2_REG, data, sizeof(data));
+
+ /* Set Auto Discharge temperature & Voltage threshold */
+ volt_threshold =
+ fuelgauge->discharge_volt_threshold < 3900 ? 0x0 :
+ fuelgauge->discharge_volt_threshold > 4540 ? 0x20 :
+ (fuelgauge->discharge_volt_threshold - 3900) / 20;
+
+ temp_threshold =
+ fuelgauge->discharge_temp_threshold < 470 ? 0x0 :
+ fuelgauge->discharge_temp_threshold > 630 ? 0x20 :
+ (fuelgauge->discharge_temp_threshold - 470) / 5;
+
+ regmap_noinc_read(regmap, DISCHARGE_THRESHOLD_REG, data, sizeof(data));
+ data[1] &= ~MAX77705_AUTO_DISCHARGE_VALUE_MASK;
+ data[1] |= volt_threshold << MAX77705_AUTO_DISCHARGE_VALUE_SHIFT;
+
+ data[0] &= ~MAX77705_AUTO_DISCHARGE_VALUE_MASK;
+ data[0] |= temp_threshold << MAX77705_AUTO_DISCHARGE_VALUE_SHIFT;
+
+ regmap_noinc_write(regmap, DISCHARGE_THRESHOLD_REG, data, sizeof(data));
+
+ pr_info("%s: DISCHARGE_THRESHOLD Value : 0x%x\n",
+ __func__, (data[1] << 8) | data[0]);
+ }
+
+ return true;
+}
+
+static int max77705_fg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max77705_fuelgauge_data *fuelgauge =
+ power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = max77705_battery_get_status(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = max77705_fg_check_battery_present(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = max77705_fg_read_vcell(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ val->intval = max77705_fg_read_vfocv(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ val->intval = max77705_fg_read_avg_vcell(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = max77705_fg_read_current(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = max77705_fg_read_avg_current(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = max77705_fg_read_repcap(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = max77705_fg_read_fullcaprep(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = max77705_fg_read_charge_design(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = max77705_fg_read_socrep(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = max77705_fg_read_temp(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ val->intval = max77705_fg_read_tte(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ val->intval = max77705_fg_read_ttf(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ val->intval = max77705_fg_read_cycle(fuelgauge);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = max77705_fuelgauge_model;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = max77705_fuelgauge_manufacturer;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max77705_fuelgauge_debugfs_show(struct seq_file *s, void *data)
+{
+ struct max77705_fuelgauge_data *fuelgauge = s->private;
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+ int i;
+ u16 reg_data;
+ int regs[] = { 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x09, 0x11, 0x13, 0x0A,
+ 0x0B, 0x0D, 0x0E, 0x0F, 0x10, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20,
+ 0x1D, 0x1E, 0x1F, 0x23, 0x28, 0x29, 0x2B, 0x32, 0x35, 0x38, 0x3A,
+ 0x3D, 0x40, 0x42, 0x43, 0x45, 0x46, 0x4B, 0x4D, 0xB1, 0xB2, 0xB3,
+ 0xBB, 0xD0, 0xEE, 0xFB, 0xFF, };
+
+ seq_puts(s, "MAX77705 FUELGAUGE IC :\n");
+ seq_puts(s, "===================\n");
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ regmap_noinc_read(regmap, regs[i], ®_data, 2);
+ seq_printf(s, "0x%02x:\t0x%02x\n", regs[i], reg_data);
+ }
+ seq_puts(s, "\n");
+ return 0;
+}
+
+static int max77705_fuelgauge_debugfs_open(struct inode *inode,
+ struct file *file)
+{
+ return single_open(file, max77705_fuelgauge_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations max77705_fuelgauge_debugfs_fops = {
+ .open = max77705_fuelgauge_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void max77705_battery_settings(struct max77705_fuelgauge_data *fuelgauge)
+{
+ struct power_supply_battery_info *info;
+ struct regmap *regmap = fuelgauge->max77705->regmap_fg;
+
+ if (power_supply_get_battery_info(fuelgauge->psy_fg, &info) < 0)
+ return;
+
+ fuelgauge->bat_info = info;
+
+ if (!regmap) {
+ dev_warn(fuelgauge->dev, "data memory update not supported for chip\n");
+ return;
+ }
+
+ if (info->energy_full_design_uwh != info->charge_full_design_uah) {
+ if (info->charge_full_design_uah == -EINVAL)
+ dev_warn(fuelgauge->dev, "missing battery:charge-full-design-microamp-hours\n");
+ max77705_fg_set_charge_design(regmap, info->charge_full_design_uah);
+ }
+}
+
+static int max77705_fuelgauge_parse_dt(struct max77705_fuelgauge_data
+ *fuelgauge)
+{
+ struct device *dev = fuelgauge->dev;
+ struct device_node *np = dev->of_node;
+ unsigned int rsense;
+
+ if (!np) {
+ dev_err(dev, "no fuelgauge OF node\n");
+ return -EINVAL;
+ }
+
+ int ret;
+
+ if (np == NULL) {
+ pr_err("%s np NULL\n", __func__);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(np, "rsense",
+ &rsense);
+ if (ret < 0) {
+ pr_err("%s error reading rsense %d\n",
+ __func__, ret);
+ fuelgauge->rsense_conductance = 100;
+ } else
+ fuelgauge->rsense_conductance = 1000 / rsense; /* rsense in Ohm^-1 */
+
+ fuelgauge->auto_discharge_en = of_property_read_bool(np,
+ "auto_discharge_en");
+ if (fuelgauge->auto_discharge_en) {
+ ret = of_property_read_u32(np,
+ "discharge_temp_threshold",
+ &fuelgauge->discharge_temp_threshold);
+ if (ret < 0) {
+ dev_err(dev, "error reading rsense_conductance %d\n", ret);
+ fuelgauge->discharge_temp_threshold = 600;
+ }
+
+ ret = of_property_read_u32(np,
+ "discharge_volt_threshold",
+ &fuelgauge->discharge_volt_threshold);
+ if (ret < 0)
+ fuelgauge->discharge_volt_threshold = 4200;
+ }
+
+ return 0;
+}
+
+static const struct power_supply_desc max77705_fuelgauge_power_supply_desc = {
+ .name = "max77705-fuelgauge",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = max77705_fuelgauge_props,
+ .num_properties = ARRAY_SIZE(max77705_fuelgauge_props),
+ .get_property = max77705_fg_get_property,
+};
+
+static int max77705_fuelgauge_probe(struct platform_device *pdev)
+{
+ struct max77705_dev *max77705 = dev_get_drvdata(pdev->dev.parent);
+ struct max77705_platform_data *pdata = dev_get_platdata(max77705->dev);
+ struct max77705_fuelgauge_data *fuelgauge;
+ struct power_supply_config fuelgauge_cfg = { };
+ int ret = 0;
+
+ fuelgauge = kzalloc(sizeof(*fuelgauge), GFP_KERNEL);
+ if (!fuelgauge)
+ return -ENOMEM;
+
+ mutex_init(&fuelgauge->fg_lock);
+
+ fuelgauge->dev = &pdev->dev;
+ fuelgauge->max77705 = max77705;
+ fuelgauge->max77705_pdata = pdata;
+
+ ret = max77705_fuelgauge_parse_dt(fuelgauge);
+ if (ret < 0)
+ pr_err("%s not found charger dt! ret[%d]\n", __func__, ret);
+
+ platform_set_drvdata(pdev, fuelgauge);
+
+
+ debugfs_file = debugfs_create_file("max77705-fuelgauge-regs",
+ 0664, NULL, (void *)fuelgauge,
+ &max77705_fuelgauge_debugfs_fops);
+ if (!debugfs_file)
+ dev_err(fuelgauge->dev, "Failed to create debugfs file\n");
+
+ fuelgauge_cfg.drv_data = fuelgauge;
+ fuelgauge_cfg.of_node = fuelgauge->dev->of_node;
+
+ fuelgauge->psy_fg =
+ devm_power_supply_register(&pdev->dev,
+ &max77705_fuelgauge_power_supply_desc,
+ &fuelgauge_cfg);
+
+ if (IS_ERR(fuelgauge->psy_fg)) {
+ pr_err("%s: Failed to Register psy_fg\n", __func__);
+ goto err_data_free;
+ }
+
+ fuelgauge->fg_irq = max77705->irq_base + MAX77705_FG_IRQ_ALERT;
+ pr_info("[%s]IRQ_BASE(%d) FG_IRQ(%d)\n",
+ __func__, max77705->irq_base, fuelgauge->fg_irq);
+
+ if (!max77705_fg_init(fuelgauge)) {
+ pr_err("%s: Failed to Initialize Fuelgauge\n", __func__);
+ goto err_supply_unreg;
+ }
+
+ max77705_battery_settings(fuelgauge);
+
+ return 0;
+
+err_supply_unreg:
+ power_supply_unregister(fuelgauge->psy_fg);
+ kfree(fuelgauge->bat_info);
+err_data_free:
+ mutex_destroy(&fuelgauge->fg_lock);
+
+ return ret;
+}
+
+static void max77705_fuelgauge_remove(struct platform_device *pdev)
+{
+ if (debugfs_file)
+ debugfs_remove(debugfs_file);
+}
+static const struct platform_device_id max77705_fuelgauge_id[] = {
+ { "max77705-fuelgauge", 0, },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, max77705_fuelgauge_id);
+
+static struct platform_driver max77705_fuelgauge_driver = {
+ .driver = {
+ .name = "max77705-fuelgauge",
+ },
+ .probe = max77705_fuelgauge_probe,
+ .remove_new = max77705_fuelgauge_remove,
+ .id_table = max77705_fuelgauge_id,
+};
+module_platform_driver(max77705_fuelgauge_driver);
+
+MODULE_DESCRIPTION("Samsung max77705 Fuel Gauge Driver");
+MODULE_AUTHOR("Samsung Electronics");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/power/max77705_fuelgauge.h b/include/linux/power/max77705_fuelgauge.h
new file mode 100644
index 000000000000..718cd47bc7b6
--- /dev/null
+++ b/include/linux/power/max77705_fuelgauge.h
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * max77705_fuelgauge.h
+ * Samsung max77705 Fuel Gauge Header
+ *
+ * Copyright (C) 2015 Samsung Electronics, Inc.
+ *
+ * This software is 77854 under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __MAX77705_FUELGAUGE_H
+#define __MAX77705_FUELGAUGE_H __FILE__
+
+#include <linux/mfd/core.h>
+#include <linux/mfd/max77705.h>
+#include <linux/mfd/max77705-private.h>
+#include <linux/regulator/machine.h>
+
+#define ALERT_EN 0x04
+#define CAPACITY_SCALE_DEFAULT_CURRENT 1000
+#define CAPACITY_SCALE_HV_CURRENT 600
+/*
+ * Current and capacity values are displayed as a voltage
+ * and must be divided by the sense resistor to determine Amps or Amp-hours.
+ * This should be applied to all current, charge, energy registers,
+ * except ModelGauge m5 Algorithm related ones.
+ */
+/* current sense resolution */
+#define MAX77705_FG_CS_ADC_RESOLUTION 15625 /* 1.5625 microvolts */
+/* voltage sense resolution */
+#define MAX77705_FG_VS_ADC_RESOLUTION 78125 /* 78.125 microvolts */
+/* CONFIG register */
+#define MAX77705_SOC_ALERT_EN_MASK BIT(2)
+/* When set to 1, external temperature measurements should be written from the host */
+#define MAX77705_TEX_MASK BIT(8)
+/* Enable Thermistor */
+#define MAX77705_ETHRM_MASK BIT(5)
+/* CONFIG2 register */
+#define MAX77705_AUTO_DISCHARGE_EN_MASK BIT(9)
+/* DISCHARGE register*/
+#define MAX77705_AUTO_DISCHARGE_VALUE_SHIFT 3
+#define MAX77705_AUTO_DISCHARGE_VALUE_MASK 0xF8
+
+#define MAX77705_FG_CS_MASK BIT(15)
+/* MISCCFG register */
+#define MAX77705_AUTO_DISCHARGE_VALUE_MASK 0xF8
+
+/* adc resolution for voltage sensing is 78.125 microvolts */
+inline u64 max77705_fg_vs_convert(u16 reg_val)
+{
+ u64 result = (u64)reg_val * MAX77705_FG_VS_ADC_RESOLUTION;
+
+ return result / 1000;
+}
+
+/* adc resolution for current sensing is 1.5625 microvolts */
+inline s32 max77705_fg_cs_convert(s16 reg_val, u32 rsense_conductance)
+{
+ s64 result = (s64)reg_val * rsense_conductance * MAX77705_FG_CS_ADC_RESOLUTION;
+
+ return result / 10000;
+}
+
+struct max77705_fuelgauge_data {
+ struct device *dev;
+ struct i2c_client *i2c;
+ struct i2c_client *pmic;
+ struct mutex fuelgauge_mutex;
+ struct max77705_dev *max77705;
+ struct max77705_platform_data *max77705_pdata;
+ struct power_supply *psy_fg;
+ struct delayed_work isr_work;
+
+ int cable_type;
+ bool is_charging;
+
+ struct power_supply_battery_info *bat_info;
+
+ struct mutex fg_lock;
+
+ /* register programming */
+ int reg_addr;
+ u8 reg_data[2];
+
+ unsigned int pre_soc;
+ int fg_irq;
+
+ int temperature;
+ int low_temp_limit;
+
+ bool auto_discharge_en;
+ u32 discharge_temp_threshold;
+ u32 discharge_volt_threshold;
+
+ u32 rsense_conductance;
+ u32 fuel_alert_soc;
+};
+
+#endif /* __MAX77705_FUELGAUGE_H */
--
2.39.2
next prev parent reply other threads:[~2024-06-18 14:00 UTC|newest]
Thread overview: 66+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-06-18 13:59 [PATCH v3 00/23] This is continued work on Samsung S9(SM-9600) starqltechn Dzmitry Sankouski
2024-06-18 13:59 ` [PATCH v3 01/23] power: supply: add undervoltage health status property Dzmitry Sankouski
2024-06-18 13:59 ` [PATCH v3 02/23] gcc-sdm845: Add rates to the GP clocks Dzmitry Sankouski
2024-06-18 17:50 ` Dmitry Baryshkov
2024-06-18 18:50 ` Konrad Dybcio
2024-06-18 18:55 ` Dmitry Baryshkov
2024-06-18 19:11 ` Konrad Dybcio
2024-06-19 6:31 ` Dmitry Baryshkov
2024-07-19 9:01 ` Dzmitry Sankouski
2024-06-18 13:59 ` [PATCH v3 03/23] dt-bindings: panel: add Samsung s6e3ha8 Dzmitry Sankouski
2024-06-20 15:44 ` Krzysztof Kozlowski
2024-06-18 13:59 ` [PATCH v3 04/23] dt-bindings: mfd: add maxim,max77705 Dzmitry Sankouski
2024-06-18 16:53 ` Rob Herring (Arm)
2024-06-20 15:45 ` Krzysztof Kozlowski
2024-09-13 14:51 ` Dzmitry Sankouski
2024-09-13 16:47 ` Conor Dooley
2024-06-18 13:59 ` [PATCH v3 05/23] dt-bindings: input: add maxim,max77705-haptic Dzmitry Sankouski
2024-06-20 15:47 ` Krzysztof Kozlowski
2024-06-18 13:59 ` [PATCH v3 06/23] dt-bindings: power: supply: add maxim,max77705 charger Dzmitry Sankouski
2024-06-20 15:47 ` Krzysztof Kozlowski
2024-06-18 13:59 ` [PATCH v3 07/23] dt-bindings: power: supply: add maxim,max77705 Dzmitry Sankouski
2024-06-18 16:53 ` Rob Herring (Arm)
2024-06-20 16:14 ` Krzysztof Kozlowski
2024-06-18 13:59 ` [PATCH v3 08/23] dt-bindings: led: add maxim,max77705-leds Dzmitry Sankouski
2024-06-18 13:59 ` [PATCH v3 09/23] dt-bindings: mfd: add samsung,s2dos05 Dzmitry Sankouski
2024-06-18 16:53 ` Rob Herring (Arm)
2024-06-20 15:49 ` Krzysztof Kozlowski
2024-06-18 13:59 ` [PATCH v3 10/23] dt-bindings: regulator: " Dzmitry Sankouski
2024-06-20 15:51 ` Krzysztof Kozlowski
2024-06-18 13:59 ` [PATCH v3 11/23] drm/panel: Add support for S6E3HA8 panel driver Dzmitry Sankouski
2024-06-18 18:39 ` Dmitry Baryshkov
2024-06-19 13:27 ` Dzmitry Sankouski
2024-06-20 20:13 ` Dmitry Baryshkov
2024-06-18 13:59 ` [PATCH v3 12/23] mfd: Add new driver for MAX77705 PMIC Dzmitry Sankouski
2024-06-20 16:02 ` Krzysztof Kozlowski
2024-06-21 16:16 ` kernel test robot
2024-06-21 23:51 ` kernel test robot
2024-06-18 13:59 ` [PATCH v3 13/23] input: add max77705 haptic driver Dzmitry Sankouski
2024-06-20 16:04 ` Krzysztof Kozlowski
2024-06-18 13:59 ` [PATCH v3 14/23] power: supply: max77705: Add charger driver for Maxim 77705 Dzmitry Sankouski
2024-06-20 16:06 ` Krzysztof Kozlowski
2024-06-18 13:59 ` Dzmitry Sankouski [this message]
2024-06-18 13:59 ` [PATCH v3 16/23] leds: max77705: Add LEDs support Dzmitry Sankouski
2024-06-20 20:14 ` Krzysztof Kozlowski
2024-06-18 13:59 ` [PATCH v3 17/23] mfd: add s2dos series core driver Dzmitry Sankouski
2024-06-20 16:07 ` Krzysztof Kozlowski
2024-06-21 20:33 ` kernel test robot
2024-06-18 13:59 ` [PATCH v3 18/23] regulator: add s2dos05 regulator support Dzmitry Sankouski
2024-06-18 14:08 ` Mark Brown
2024-06-19 15:49 ` Dzmitry Sankouski
2024-06-19 15:52 ` Mark Brown
2024-06-19 13:26 ` kernel test robot
2024-06-20 16:08 ` Krzysztof Kozlowski
2024-06-18 13:59 ` [PATCH v3 19/23] power: supply: s2dos05: Add fuel gauge driver for s2dos05 Dzmitry Sankouski
2024-06-20 16:11 ` Krzysztof Kozlowski
2024-06-18 13:59 ` [PATCH v3 20/23] arm64: dts: qcom: starqltechn: remove wifi Dzmitry Sankouski
2024-06-18 14:06 ` Konrad Dybcio
2024-06-18 13:59 ` [PATCH v3 21/23] arm64: dts: qcom: starqltechn: remove framebuffer Dzmitry Sankouski
2024-06-18 14:06 ` Konrad Dybcio
2024-06-18 13:59 ` [PATCH v3 22/23] arm64: dts: qcom: starqltechn: fix usb regulator mistake Dzmitry Sankouski
2024-06-18 14:07 ` Konrad Dybcio
2024-06-18 14:16 ` Dzmitry Sankouski
2024-06-18 13:59 ` [PATCH v3 23/23] arm64: dts: qcom: starqltechn: add new features Dzmitry Sankouski
2024-06-18 14:12 ` Konrad Dybcio
2024-07-08 15:54 ` Dzmitry Sankouski
2024-07-08 18:08 ` Konrad Dybcio
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240618-starqltechn_integration_upstream-v3-15-e3f6662017ac@gmail.com \
--to=dsankouski@gmail.com \
--cc=airlied@gmail.com \
--cc=andersson@kernel.org \
--cc=broonie@kernel.org \
--cc=conor+dt@kernel.org \
--cc=cw00.choi@samsung.com \
--cc=daniel@ffwll.ch \
--cc=devicetree@vger.kernel.org \
--cc=dmitry.torokhov@gmail.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=konrad.dybcio@linaro.org \
--cc=krzk+dt@kernel.org \
--cc=krzk@kernel.org \
--cc=lee@kernel.org \
--cc=lgirdwood@gmail.com \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-clk@vger.kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-leds@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=linux-pwm@vger.kernel.org \
--cc=linux-samsung-soc@vger.kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=mturquette@baylibre.com \
--cc=neil.armstrong@linaro.org \
--cc=pavel@ucw.cz \
--cc=phone-devel@vger.kernel.org \
--cc=quic_jesszhan@quicinc.com \
--cc=robh@kernel.org \
--cc=sam@ravnborg.org \
--cc=sboyd@kernel.org \
--cc=sre@kernel.org \
--cc=tzimmermann@suse.de \
--cc=ukleinek@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).