summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/Kconfig14
-rw-r--r--drivers/power/Makefile2
-rw-r--r--drivers/power/power_supply_sysfs.c112
-rw-r--r--drivers/power/qcom/Kconfig66
-rw-r--r--drivers/power/qcom/Makefile3
-rw-r--r--drivers/power/qcom/apm.c1059
-rw-r--r--drivers/power/qcom/debug_core.c330
-rw-r--r--drivers/power/qcom/lpm-stats.c871
-rw-r--r--drivers/power/qcom/msm-core.c1133
-rw-r--r--drivers/power/reset/Kconfig36
-rw-r--r--drivers/power/reset/Makefile4
-rw-r--r--drivers/power/reset/msm-poweroff.c636
-rw-r--r--drivers/power/reset/reboot-mode.c140
-rw-r--r--drivers/power/reset/reboot-mode.h14
-rw-r--r--drivers/power/reset/syscon-reboot-mode.c99
-rw-r--r--drivers/power/supply/Kconfig1
-rw-r--r--drivers/power/supply/Makefile1
-rw-r--r--drivers/power/supply/qcom/Kconfig85
-rw-r--r--drivers/power/supply/qcom/Makefile9
-rw-r--r--drivers/power/supply/qcom/battery.c1100
-rw-r--r--drivers/power/supply/qcom/battery.h17
-rw-r--r--drivers/power/supply/qcom/battery_current_limit.c1842
-rw-r--r--drivers/power/supply/qcom/batterydata-lib.c493
-rw-r--r--drivers/power/supply/qcom/bcl_peripheral.c1367
-rw-r--r--drivers/power/supply/qcom/fg-core.h502
-rw-r--r--drivers/power/supply/qcom/fg-memif.c780
-rw-r--r--drivers/power/supply/qcom/fg-reg.h329
-rw-r--r--drivers/power/supply/qcom/fg-util.c960
-rw-r--r--drivers/power/supply/qcom/msm_bcl.c374
-rw-r--r--drivers/power/supply/qcom/pmic-voter.c695
-rw-r--r--drivers/power/supply/qcom/qpnp-fg-gen3.c5002
-rw-r--r--drivers/power/supply/qcom/qpnp-qnovo.c1726
-rw-r--r--drivers/power/supply/qcom/qpnp-smb2.c2464
-rw-r--r--drivers/power/supply/qcom/smb-lib.c4856
-rw-r--r--drivers/power/supply/qcom/smb-lib.h523
-rw-r--r--drivers/power/supply/qcom/smb-reg.h1028
-rw-r--r--drivers/power/supply/qcom/smb1351-charger.c3344
-rw-r--r--drivers/power/supply/qcom/smb135x-charger.c4584
-rw-r--r--drivers/power/supply/qcom/smb138x-charger.c1655
-rw-r--r--drivers/power/supply/qcom/step-chg-jeita.c498
-rw-r--r--drivers/power/supply/qcom/step-chg-jeita.h17
-rw-r--r--drivers/power/supply/qcom/storm-watch.c76
-rw-r--r--drivers/power/supply/qcom/storm-watch.h41
43 files changed, 38861 insertions, 27 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 237d7aa73e8c..91fdeaf67037 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -449,6 +449,18 @@ config CHARGER_SMB347
Say Y to include support for Summit Microelectronics SMB347
Battery Charger.
+config BATTERY_BQ28400
+ tristate "BQ28400 battery driver"
+ depends on I2C
+ default n
+ help
+ Say Y here to enable support for batteries with BQ28400 (I2C) chips.
+ The bq28400 Texas Instruments Inc device monitors the battery
+ charging/discharging status via Rsens resistor, typically 10 mohm.
+ It monitors the battery temperature via Thermistor.
+ The device monitors the battery level (Relative-State-Of-Charge).
+ The device is SBS compliant, providing battery info over I2C.
+
config CHARGER_TPS65090
tristate "TPS65090 battery charger driver"
depends on MFD_TPS65090
@@ -509,7 +521,9 @@ config AXP20X_POWER
AXP20x PMIC.
source "drivers/power/reset/Kconfig"
+source "drivers/power/supply/Kconfig"
endif # POWER_SUPPLY
source "drivers/power/avs/Kconfig"
+source "drivers/power/qcom/Kconfig"
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index b656638f8b39..f7adecea0a70 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -72,3 +72,5 @@ obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
obj-$(CONFIG_POWER_RESET) += reset/
obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o
+obj-$(CONFIG_ARCH_QCOM) += qcom/
+obj-$(CONFIG_POWER_SUPPLY) += supply/
diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c
index 280018d59d5a..a45a51490817 100644
--- a/drivers/power/power_supply_sysfs.c
+++ b/drivers/power/power_supply_sysfs.c
@@ -44,19 +44,23 @@ static ssize_t power_supply_show_property(struct device *dev,
struct device_attribute *attr,
char *buf) {
static char *type_text[] = {
- "Unknown", "Battery", "UPS", "Mains", "USB",
- "USB_DCP", "USB_CDP", "USB_ACA"
+ "Unknown", "Battery", "UPS", "Mains", "USB", "USB_DCP",
+ "USB_CDP", "USB_ACA", "USB_HVDCP", "USB_HVDCP_3", "USB_PD",
+ "Wireless", "USB_FLOAT", "BMS", "Parallel", "Main", "Wipower",
+ "TYPEC", "TYPEC_UFP", "TYPEC_DFP"
};
static char *status_text[] = {
"Unknown", "Charging", "Discharging", "Not charging", "Full"
};
static char *charge_type[] = {
- "Unknown", "N/A", "Trickle", "Fast"
+ "Unknown", "N/A", "Trickle", "Fast",
+ "Taper"
};
static char *health_text[] = {
"Unknown", "Good", "Overheat", "Dead", "Over voltage",
"Unspecified failure", "Cold", "Watchdog timer expire",
- "Safety timer expire"
+ "Safety timer expire",
+ "Warm", "Cool", "Hot"
};
static char *technology_text[] = {
"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
@@ -68,6 +72,17 @@ static ssize_t power_supply_show_property(struct device *dev,
static char *scope_text[] = {
"Unknown", "System", "Device"
};
+ static char *typec_text[] = {
+ "Nothing attached", "Sink attached", "Powered cable w/ sink",
+ "Debug Accessory", "Audio Adapter", "Powered cable w/o sink",
+ "Source attached (default current)",
+ "Source attached (medium current)",
+ "Source attached (high current)",
+ "Non compliant",
+ };
+ static char *typec_pr_text[] = {
+ "none", "dual power role", "sink", "source"
+ };
ssize_t ret = 0;
struct power_supply *psy = dev_get_drvdata(dev);
const ptrdiff_t off = attr - power_supply_attrs;
@@ -99,10 +114,19 @@ static ssize_t power_supply_show_property(struct device *dev,
return sprintf(buf, "%s\n", technology_text[value.intval]);
else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL)
return sprintf(buf, "%s\n", capacity_level_text[value.intval]);
- else if (off == POWER_SUPPLY_PROP_TYPE)
+ else if (off == POWER_SUPPLY_PROP_TYPE ||
+ off == POWER_SUPPLY_PROP_REAL_TYPE)
return sprintf(buf, "%s\n", type_text[value.intval]);
else if (off == POWER_SUPPLY_PROP_SCOPE)
return sprintf(buf, "%s\n", scope_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_TYPEC_MODE)
+ return sprintf(buf, "%s\n", typec_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_TYPEC_POWER_ROLE)
+ return sprintf(buf, "%s\n", typec_pr_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_DIE_HEALTH)
+ return sprintf(buf, "%s\n", health_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_CONNECTOR_HEALTH)
+ return sprintf(buf, "%s\n", health_text[value.intval]);
else if (off >= POWER_SUPPLY_PROP_MODEL_NAME)
return sprintf(buf, "%s\n", value.strval);
@@ -165,6 +189,8 @@ static struct device_attribute power_supply_attrs[] = {
POWER_SUPPLY_ATTR(charge_full),
POWER_SUPPLY_ATTR(charge_empty),
POWER_SUPPLY_ATTR(charge_now),
+ POWER_SUPPLY_ATTR(charge_now_raw),
+ POWER_SUPPLY_ATTR(charge_now_error),
POWER_SUPPLY_ATTR(charge_avg),
POWER_SUPPLY_ATTR(charge_counter),
POWER_SUPPLY_ATTR(constant_charge_current),
@@ -184,6 +210,7 @@ static struct device_attribute power_supply_attrs[] = {
POWER_SUPPLY_ATTR(capacity_alert_min),
POWER_SUPPLY_ATTR(capacity_alert_max),
POWER_SUPPLY_ATTR(capacity_level),
+ POWER_SUPPLY_ATTR(capacity_raw),
POWER_SUPPLY_ATTR(temp),
POWER_SUPPLY_ATTR(temp_max),
POWER_SUPPLY_ATTR(temp_min),
@@ -203,13 +230,86 @@ static struct device_attribute power_supply_attrs[] = {
/* Local extensions */
POWER_SUPPLY_ATTR(usb_hc),
POWER_SUPPLY_ATTR(usb_otg),
- POWER_SUPPLY_ATTR(charge_enabled),
+ POWER_SUPPLY_ATTR(battery_charging_enabled),
+ POWER_SUPPLY_ATTR(charging_enabled),
+ POWER_SUPPLY_ATTR(step_charging_enabled),
+ POWER_SUPPLY_ATTR(step_charging_step),
+ POWER_SUPPLY_ATTR(pin_enabled),
+ POWER_SUPPLY_ATTR(input_suspend),
+ POWER_SUPPLY_ATTR(input_voltage_regulation),
+ POWER_SUPPLY_ATTR(input_current_max),
+ POWER_SUPPLY_ATTR(input_current_trim),
+ POWER_SUPPLY_ATTR(input_current_settled),
+ POWER_SUPPLY_ATTR(input_voltage_settled),
+ POWER_SUPPLY_ATTR(bypass_vchg_loop_debouncer),
+ POWER_SUPPLY_ATTR(charge_counter_shadow),
+ POWER_SUPPLY_ATTR(hi_power),
+ POWER_SUPPLY_ATTR(low_power),
+ POWER_SUPPLY_ATTR(temp_cool),
+ POWER_SUPPLY_ATTR(temp_warm),
+ POWER_SUPPLY_ATTR(system_temp_level),
+ POWER_SUPPLY_ATTR(resistance),
+ POWER_SUPPLY_ATTR(resistance_capacitive),
+ POWER_SUPPLY_ATTR(resistance_id),
+ POWER_SUPPLY_ATTR(resistance_now),
+ POWER_SUPPLY_ATTR(flash_current_max),
+ POWER_SUPPLY_ATTR(update_now),
+ POWER_SUPPLY_ATTR(esr_count),
+ POWER_SUPPLY_ATTR(buck_freq),
+ POWER_SUPPLY_ATTR(boost_current),
+ POWER_SUPPLY_ATTR(safety_timer_enabled),
+ POWER_SUPPLY_ATTR(charge_done),
+ POWER_SUPPLY_ATTR(flash_active),
+ POWER_SUPPLY_ATTR(flash_trigger),
+ POWER_SUPPLY_ATTR(force_tlim),
+ POWER_SUPPLY_ATTR(dp_dm),
+ POWER_SUPPLY_ATTR(input_current_limited),
+ POWER_SUPPLY_ATTR(input_current_now),
+ POWER_SUPPLY_ATTR(charge_qnovo_enable),
+ POWER_SUPPLY_ATTR(current_qnovo),
+ POWER_SUPPLY_ATTR(voltage_qnovo),
+ POWER_SUPPLY_ATTR(rerun_aicl),
+ POWER_SUPPLY_ATTR(cycle_count_id),
+ POWER_SUPPLY_ATTR(safety_timer_expired),
+ POWER_SUPPLY_ATTR(restricted_charging),
+ POWER_SUPPLY_ATTR(current_capability),
+ POWER_SUPPLY_ATTR(typec_mode),
+ POWER_SUPPLY_ATTR(typec_cc_orientation),
+ POWER_SUPPLY_ATTR(typec_power_role),
+ POWER_SUPPLY_ATTR(pd_allowed),
+ POWER_SUPPLY_ATTR(pd_active),
+ POWER_SUPPLY_ATTR(pd_in_hard_reset),
+ POWER_SUPPLY_ATTR(pd_current_max),
+ POWER_SUPPLY_ATTR(pd_usb_suspend_supported),
+ POWER_SUPPLY_ATTR(charger_temp),
+ POWER_SUPPLY_ATTR(charger_temp_max),
+ POWER_SUPPLY_ATTR(parallel_disable),
+ POWER_SUPPLY_ATTR(pe_start),
+ POWER_SUPPLY_ATTR(set_ship_mode),
+ POWER_SUPPLY_ATTR(soc_reporting_ready),
+ POWER_SUPPLY_ATTR(debug_battery),
+ POWER_SUPPLY_ATTR(fcc_delta),
+ POWER_SUPPLY_ATTR(icl_reduction),
+ POWER_SUPPLY_ATTR(parallel_mode),
+ POWER_SUPPLY_ATTR(die_health),
+ POWER_SUPPLY_ATTR(connector_health),
+ POWER_SUPPLY_ATTR(ctm_current_max),
+ POWER_SUPPLY_ATTR(hw_current_max),
+ POWER_SUPPLY_ATTR(real_type),
+ POWER_SUPPLY_ATTR(pr_swap),
+ POWER_SUPPLY_ATTR(cc_step),
+ POWER_SUPPLY_ATTR(cc_step_sel),
+ POWER_SUPPLY_ATTR(sw_jeita_enabled),
+ POWER_SUPPLY_ATTR(pd_voltage_max),
+ POWER_SUPPLY_ATTR(pd_voltage_min),
+ POWER_SUPPLY_ATTR(sdp_current_max),
/* Local extensions of type int64_t */
POWER_SUPPLY_ATTR(charge_counter_ext),
/* Properties of type `const char *' */
POWER_SUPPLY_ATTR(model_name),
POWER_SUPPLY_ATTR(manufacturer),
POWER_SUPPLY_ATTR(serial_number),
+ POWER_SUPPLY_ATTR(battery_type),
};
static struct attribute *
diff --git a/drivers/power/qcom/Kconfig b/drivers/power/qcom/Kconfig
new file mode 100644
index 000000000000..efb9dd9628bb
--- /dev/null
+++ b/drivers/power/qcom/Kconfig
@@ -0,0 +1,66 @@
+config MSM_PM
+ depends on PM
+ select MSM_IDLE_STATS if DEBUG_FS
+ select CPU_IDLE_MULTIPLE_DRIVERS
+ bool "Qualcomm platform specific PM driver"
+ help
+ Platform specific power driver to manage cores and l2
+ low power modes. It interface with various system
+ driver and put the cores into low power modes.
+
+config MSM_NOPM
+ default y if !PM
+ bool
+ help
+ This enables bare minimum support of power management at platform level.
+ i.e WFI
+
+config APSS_CORE_EA
+ depends on CPU_FREQ && PM_OPP
+ bool "Qualcomm Technology Inc specific power aware driver"
+ help
+ Platform specific power aware driver to provide power
+ and temperature information to the scheduler.
+
+config MSM_APM
+ bool "Qualcomm Technologies, Inc. platform specific APM driver"
+ help
+ Platform specific driver to manage the power source of
+ memory arrays. Interfaces with regulator drivers to ensure
+ SRAM Vmin requirements are met across different performance
+ levels.
+
+if MSM_PM
+menuconfig MSM_IDLE_STATS
+ bool "Collect idle statistics"
+ help
+ Collect cores various low power mode idle statistics
+ and export them in proc/msm_pm_stats. User can read
+ this data and determine what low power modes and how
+ many times cores have entered into LPM modes.
+
+if MSM_IDLE_STATS
+
+config MSM_IDLE_STATS_FIRST_BUCKET
+ int "First bucket time"
+ default 62500
+ help
+ Upper time limit in nanoseconds of first bucket.
+
+config MSM_IDLE_STATS_BUCKET_SHIFT
+ int "Bucket shift"
+ default 2
+
+config MSM_IDLE_STATS_BUCKET_COUNT
+ int "Bucket count"
+ default 10
+
+config MSM_SUSPEND_STATS_FIRST_BUCKET
+ int "First bucket time for suspend"
+ default 1000000000
+ help
+ Upper time limit in nanoseconds of first bucket of the
+ histogram. This is for collecting statistics on suspend.
+
+endif # MSM_IDLE_STATS
+endif # MSM_PM
diff --git a/drivers/power/qcom/Makefile b/drivers/power/qcom/Makefile
new file mode 100644
index 000000000000..8e1ce14e384c
--- /dev/null
+++ b/drivers/power/qcom/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_MSM_IDLE_STATS) += lpm-stats.o
+obj-$(CONFIG_APSS_CORE_EA) += msm-core.o debug_core.o
+obj-$(CONFIG_MSM_APM) += apm.o
diff --git a/drivers/power/qcom/apm.c b/drivers/power/qcom/apm.c
new file mode 100644
index 000000000000..9455468f1734
--- /dev/null
+++ b/drivers/power/qcom/apm.c
@@ -0,0 +1,1059 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/power/qcom/apm.h>
+#include <soc/qcom/scm.h>
+#include <linux/arm-smccc.h>
+
+/*
+ * VDD_APCC
+ * =============================================================
+ * | VDD_MX | |
+ * | ==========================|============= |
+ * ___|___ ___|___ ___|___ ___|___ ___|___ ___|___
+ * | | | | | | | | | | | |
+ * | APCC | | MX HS | | MX HS | | APCC | | MX HS | | APCC |
+ * | HS | | | | | | HS | | | | HS |
+ * |_______| |_______| |_______| |_______| |_______| |_______|
+ * |_________| |_________| |__________|
+ * | | |
+ * ______|_____ ______|_____ _______|_____
+ * | | | | | |
+ * | | | | | |
+ * | CPU MEM | | L2 MEM | | L3 MEM |
+ * | Arrays | | Arrays | | Arrays |
+ * | | | | | |
+ * |____________| |____________| |_____________|
+ *
+ */
+
+/* Register value definitions */
+#define APCS_GFMUXA_SEL_VAL 0x13
+#define APCS_GFMUXA_DESEL_VAL 0x03
+#define MSM_APM_MX_MODE_VAL 0x00
+#define MSM_APM_APCC_MODE_VAL 0x10
+#define MSM_APM_MX_DONE_VAL 0x00
+#define MSM_APM_APCC_DONE_VAL 0x03
+#define MSM_APM_OVERRIDE_SEL_VAL 0xb0
+#define MSM_APM_SEC_CLK_SEL_VAL 0x30
+#define SPM_EVENT_SET_VAL 0x01
+#define SPM_EVENT_CLEAR_VAL 0x00
+
+/* Register bit mask definitions */
+#define MSM_APM_CTL_STS_MASK 0x0f
+
+/* Register offset definitions */
+#define APCC_APM_MODE 0x00000098
+#define APCC_APM_CTL_STS 0x000000a8
+#define APCS_SPARE 0x00000068
+#define APCS_VERSION 0x00000fd0
+
+#define HMSS_VERSION_1P2 0x10020000
+
+#define MSM_APM_SWITCH_TIMEOUT_US 10
+#define SPM_WAKEUP_DELAY_US 2
+#define SPM_EVENT_NUM 6
+
+#define MSM_APM_DRIVER_NAME "qcom,msm-apm"
+
+
+enum {
+ CLOCK_ASSERT_ENABLE,
+ CLOCK_ASSERT_DISABLE,
+ CLOCK_ASSERT_TOGGLE,
+};
+
+enum {
+ MSM8996_ID,
+ MSM8996PRO_ID,
+ MSM8953_ID,
+};
+
+struct msm_apm_ctrl_dev {
+ struct list_head list;
+ struct device *dev;
+ enum msm_apm_supply supply;
+ spinlock_t lock;
+ void __iomem *reg_base;
+ void __iomem *apcs_csr_base;
+ void __iomem **apcs_spm_events_addr;
+ void __iomem *apc0_pll_ctl_addr;
+ void __iomem *apc1_pll_ctl_addr;
+ bool clk_src_override;
+ u32 version;
+ struct dentry *debugfs;
+ u32 msm_id;
+};
+
+#if defined(CONFIG_DEBUG_FS)
+static struct dentry *apm_debugfs_base;
+#endif
+
+static DEFINE_MUTEX(apm_ctrl_list_mutex);
+static LIST_HEAD(apm_ctrl_list);
+
+/*
+ * Get the resources associated with the APM controller from device tree
+ * and remap all I/O addresses that are relevant to this HW revision.
+ */
+static int msm_apm_ctrl_devm_ioremap(struct platform_device *pdev,
+ struct msm_apm_ctrl_dev *ctrl)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ static const char *res_name[SPM_EVENT_NUM] = {
+ "apc0-l2-spm",
+ "apc1-l2-spm",
+ "apc0-cpu0-spm",
+ "apc0-cpu1-spm",
+ "apc1-cpu0-spm",
+ "apc1-cpu1-spm"
+ };
+ int i, ret = 0;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pm-apcc-glb");
+ if (!res) {
+ dev_err(dev, "Missing PM APCC Global register physical address");
+ return -EINVAL;
+ }
+ ctrl->reg_base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!ctrl->reg_base) {
+ dev_err(dev, "Failed to map PM APCC Global registers\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "apcs-csr");
+ if (!res) {
+ dev_err(dev, "Missing APCS CSR physical base address");
+ return -EINVAL;
+ }
+ ctrl->apcs_csr_base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!ctrl->apcs_csr_base) {
+ dev_err(dev, "Failed to map APCS CSR registers\n");
+ return -ENOMEM;
+ }
+
+ ctrl->clk_src_override = of_property_read_bool(dev->of_node,
+ "qcom,clock-source-override");
+
+ if (ctrl->clk_src_override)
+ dev_info(dev, "overriding clock sources across APM switch\n");
+
+ ctrl->version = readl_relaxed(ctrl->apcs_csr_base + APCS_VERSION);
+
+ if (ctrl->version >= HMSS_VERSION_1P2)
+ return ret;
+
+ ctrl->apcs_spm_events_addr = devm_kzalloc(&pdev->dev,
+ SPM_EVENT_NUM
+ * sizeof(void __iomem *),
+ GFP_KERNEL);
+ if (!ctrl->apcs_spm_events_addr) {
+ dev_err(dev, "Failed to allocate memory for APCS SPM event registers\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < SPM_EVENT_NUM; i++) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ res_name[i]);
+ if (!res) {
+ dev_err(dev, "Missing address for %s\n", res_name[i]);
+ ret = -EINVAL;
+ goto free_events;
+ }
+
+ ctrl->apcs_spm_events_addr[i] = devm_ioremap(dev, res->start,
+ resource_size(res));
+ if (!ctrl->apcs_spm_events_addr[i]) {
+ dev_err(dev, "Failed to map %s\n", res_name[i]);
+ ret = -ENOMEM;
+ goto free_events;
+ }
+
+ dev_dbg(dev, "%s event phys: %pa virt:0x%p\n", res_name[i],
+ &res->start, ctrl->apcs_spm_events_addr[i]);
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "apc0-pll-ctl");
+ if (!res) {
+ dev_err(dev, "Missing APC0 PLL CTL physical address\n");
+ ret = -EINVAL;
+ goto free_events;
+ }
+
+ ctrl->apc0_pll_ctl_addr = devm_ioremap(dev,
+ res->start,
+ resource_size(res));
+ if (!ctrl->apc0_pll_ctl_addr) {
+ dev_err(dev, "Failed to map APC0 PLL CTL register\n");
+ ret = -ENOMEM;
+ goto free_events;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "apc1-pll-ctl");
+ if (!res) {
+ dev_err(dev, "Missing APC1 PLL CTL physical address\n");
+ ret = -EINVAL;
+ goto free_events;
+ }
+
+ ctrl->apc1_pll_ctl_addr = devm_ioremap(dev,
+ res->start,
+ resource_size(res));
+ if (!ctrl->apc1_pll_ctl_addr) {
+ dev_err(dev, "Failed to map APC1 PLL CTL register\n");
+ ret = -ENOMEM;
+ goto free_events;
+ }
+
+ return ret;
+
+free_events:
+ devm_kfree(dev, ctrl->apcs_spm_events_addr);
+ return ret;
+}
+
+/* MSM8953 register offset definition */
+#define MSM8953_APM_DLY_CNTR 0x2ac
+
+/* Register field shift definitions */
+#define APM_CTL_SEL_SWITCH_DLY_SHIFT 0
+#define APM_CTL_RESUME_CLK_DLY_SHIFT 8
+#define APM_CTL_HALT_CLK_DLY_SHIFT 16
+#define APM_CTL_POST_HALT_DLY_SHIFT 24
+
+/* Register field mask definitions */
+#define APM_CTL_SEL_SWITCH_DLY_MASK GENMASK(7, 0)
+#define APM_CTL_RESUME_CLK_DLY_MASK GENMASK(15, 8)
+#define APM_CTL_HALT_CLK_DLY_MASK GENMASK(23, 16)
+#define APM_CTL_POST_HALT_DLY_MASK GENMASK(31, 24)
+
+/*
+ * Get the resources associated with the MSM8953 APM controller from
+ * device tree, remap all I/O addresses, and program the initial
+ * register configuration required for the MSM8953 APM controller device.
+ */
+static int msm8953_apm_ctrl_init(struct platform_device *pdev,
+ struct msm_apm_ctrl_dev *ctrl)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ u32 delay_counter, val = 0, regval = 0;
+ int rc = 0;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pm-apcc-glb");
+ if (!res) {
+ dev_err(dev, "Missing PM APCC Global register physical address\n");
+ return -ENODEV;
+ }
+ ctrl->reg_base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!ctrl->reg_base) {
+ dev_err(dev, "Failed to map PM APCC Global registers\n");
+ return -ENOMEM;
+ }
+
+ /*
+ * Initial APM register configuration required before starting
+ * APM HW controller.
+ */
+ regval = readl_relaxed(ctrl->reg_base + MSM8953_APM_DLY_CNTR);
+ val = regval;
+
+ if (of_find_property(dev->of_node, "qcom,apm-post-halt-delay", NULL)) {
+ rc = of_property_read_u32(dev->of_node,
+ "qcom,apm-post-halt-delay", &delay_counter);
+ if (rc < 0) {
+ dev_err(dev, "apm-post-halt-delay read failed, rc = %d",
+ rc);
+ return rc;
+ }
+
+ val &= ~APM_CTL_POST_HALT_DLY_MASK;
+ val |= (delay_counter << APM_CTL_POST_HALT_DLY_SHIFT)
+ & APM_CTL_POST_HALT_DLY_MASK;
+ }
+
+ if (of_find_property(dev->of_node, "qcom,apm-halt-clk-delay", NULL)) {
+ rc = of_property_read_u32(dev->of_node,
+ "qcom,apm-halt-clk-delay", &delay_counter);
+ if (rc < 0) {
+ dev_err(dev, "apm-halt-clk-delay read failed, rc = %d",
+ rc);
+ return rc;
+ }
+
+ val &= ~APM_CTL_HALT_CLK_DLY_MASK;
+ val |= (delay_counter << APM_CTL_HALT_CLK_DLY_SHIFT)
+ & APM_CTL_HALT_CLK_DLY_MASK;
+ }
+
+ if (of_find_property(dev->of_node, "qcom,apm-resume-clk-delay", NULL)) {
+ rc = of_property_read_u32(dev->of_node,
+ "qcom,apm-resume-clk-delay", &delay_counter);
+ if (rc < 0) {
+ dev_err(dev, "apm-resume-clk-delay read failed, rc = %d",
+ rc);
+ return rc;
+ }
+
+ val &= ~APM_CTL_RESUME_CLK_DLY_MASK;
+ val |= (delay_counter << APM_CTL_RESUME_CLK_DLY_SHIFT)
+ & APM_CTL_RESUME_CLK_DLY_MASK;
+ }
+
+ if (of_find_property(dev->of_node, "qcom,apm-sel-switch-delay", NULL)) {
+ rc = of_property_read_u32(dev->of_node,
+ "qcom,apm-sel-switch-delay", &delay_counter);
+ if (rc < 0) {
+ dev_err(dev, "apm-sel-switch-delay read failed, rc = %d",
+ rc);
+ return rc;
+ }
+
+ val &= ~APM_CTL_SEL_SWITCH_DLY_MASK;
+ val |= (delay_counter << APM_CTL_SEL_SWITCH_DLY_SHIFT)
+ & APM_CTL_SEL_SWITCH_DLY_MASK;
+ }
+
+ if (val != regval) {
+ writel_relaxed(val, ctrl->reg_base + MSM8953_APM_DLY_CNTR);
+ /* make sure write completes before return */
+ mb();
+ }
+
+ return rc;
+}
+
+static int msm_apm_secure_clock_source_override(
+ struct msm_apm_ctrl_dev *ctrl_dev, bool enable)
+{
+ int ret;
+
+ if (ctrl_dev->clk_src_override) {
+ ret = __invoke_psci_fn_smc(0xC4000020, 3, enable ?
+ CLOCK_ASSERT_ENABLE :
+ CLOCK_ASSERT_DISABLE, 0);
+ if (ret)
+ dev_err(ctrl_dev->dev, "PSCI request to switch to %s clock source failed\n",
+ enable ? "GPLL0" : "original");
+ }
+
+ return 0;
+}
+
+static int msm8996_apm_wait_for_switch(struct msm_apm_ctrl_dev *ctrl_dev,
+ u32 done_val)
+{
+ int timeout = MSM_APM_SWITCH_TIMEOUT_US;
+ u32 regval;
+
+ while (timeout > 0) {
+ regval = readl_relaxed(ctrl_dev->reg_base + APCC_APM_CTL_STS);
+ if ((regval & MSM_APM_CTL_STS_MASK) == done_val)
+ break;
+
+ udelay(1);
+ timeout--;
+ }
+
+ if (timeout == 0) {
+ dev_err(ctrl_dev->dev, "%s switch timed out. APCC_APM_CTL_STS=0x%x\n",
+ done_val == MSM_APM_MX_DONE_VAL
+ ? "APCC to MX" : "MX to APCC",
+ regval);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int msm8996_apm_switch_to_mx(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ unsigned long flags;
+ int i, ret;
+
+ mutex_lock(&scm_lmh_lock);
+ spin_lock_irqsave(&ctrl_dev->lock, flags);
+
+ ret = msm_apm_secure_clock_source_override(ctrl_dev, true);
+ if (ret)
+ goto done;
+
+ /* Perform revision-specific programming steps */
+ if (ctrl_dev->version < HMSS_VERSION_1P2) {
+ /* Clear SPM events */
+ for (i = 0; i < SPM_EVENT_NUM; i++)
+ writel_relaxed(SPM_EVENT_CLEAR_VAL,
+ ctrl_dev->apcs_spm_events_addr[i]);
+
+ udelay(SPM_WAKEUP_DELAY_US);
+
+ /* Switch APC/CBF to GPLL0 clock */
+ writel_relaxed(APCS_GFMUXA_SEL_VAL,
+ ctrl_dev->apcs_csr_base + APCS_SPARE);
+ ndelay(200);
+ writel_relaxed(MSM_APM_OVERRIDE_SEL_VAL,
+ ctrl_dev->apc0_pll_ctl_addr);
+ ndelay(200);
+ writel_relaxed(MSM_APM_OVERRIDE_SEL_VAL,
+ ctrl_dev->apc1_pll_ctl_addr);
+
+ /* Ensure writes complete before proceeding */
+ mb();
+ }
+
+ /* Switch arrays to MX supply and wait for its completion */
+ writel_relaxed(MSM_APM_MX_MODE_VAL, ctrl_dev->reg_base +
+ APCC_APM_MODE);
+
+ /* Ensure write above completes before delaying */
+ mb();
+
+ ret = msm8996_apm_wait_for_switch(ctrl_dev, MSM_APM_MX_DONE_VAL);
+
+ /* Perform revision-specific programming steps */
+ if (ctrl_dev->version < HMSS_VERSION_1P2) {
+ /* Switch APC/CBF clocks to original source */
+ writel_relaxed(APCS_GFMUXA_DESEL_VAL,
+ ctrl_dev->apcs_csr_base + APCS_SPARE);
+ ndelay(200);
+ writel_relaxed(MSM_APM_SEC_CLK_SEL_VAL,
+ ctrl_dev->apc0_pll_ctl_addr);
+ ndelay(200);
+ writel_relaxed(MSM_APM_SEC_CLK_SEL_VAL,
+ ctrl_dev->apc1_pll_ctl_addr);
+
+ /* Complete clock source switch before SPM event sequence */
+ mb();
+
+ /* Set SPM events */
+ for (i = 0; i < SPM_EVENT_NUM; i++)
+ writel_relaxed(SPM_EVENT_SET_VAL,
+ ctrl_dev->apcs_spm_events_addr[i]);
+ }
+
+ /*
+ * Ensure that HMSS v1.0/v1.1 register writes are completed before
+ * bailing out in the case of a switching time out.
+ */
+ if (ret)
+ goto done;
+
+ ret = msm_apm_secure_clock_source_override(ctrl_dev, false);
+ if (ret)
+ goto done;
+
+ ctrl_dev->supply = MSM_APM_SUPPLY_MX;
+ dev_dbg(ctrl_dev->dev, "APM supply switched to MX\n");
+
+done:
+ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
+ mutex_unlock(&scm_lmh_lock);
+
+ return ret;
+}
+
+static int msm8996_apm_switch_to_apcc(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ unsigned long flags;
+ int i, ret;
+
+ mutex_lock(&scm_lmh_lock);
+ spin_lock_irqsave(&ctrl_dev->lock, flags);
+
+ ret = msm_apm_secure_clock_source_override(ctrl_dev, true);
+ if (ret)
+ goto done;
+
+ /* Perform revision-specific programming steps */
+ if (ctrl_dev->version < HMSS_VERSION_1P2) {
+ /* Clear SPM events */
+ for (i = 0; i < SPM_EVENT_NUM; i++)
+ writel_relaxed(SPM_EVENT_CLEAR_VAL,
+ ctrl_dev->apcs_spm_events_addr[i]);
+
+ udelay(SPM_WAKEUP_DELAY_US);
+
+ /* Switch APC/CBF to GPLL0 clock */
+ writel_relaxed(APCS_GFMUXA_SEL_VAL,
+ ctrl_dev->apcs_csr_base + APCS_SPARE);
+ ndelay(200);
+ writel_relaxed(MSM_APM_OVERRIDE_SEL_VAL,
+ ctrl_dev->apc0_pll_ctl_addr);
+ ndelay(200);
+ writel_relaxed(MSM_APM_OVERRIDE_SEL_VAL,
+ ctrl_dev->apc1_pll_ctl_addr);
+
+ /* Ensure previous writes complete before proceeding */
+ mb();
+ }
+
+ /* Switch arrays to APCC supply and wait for its completion */
+ writel_relaxed(MSM_APM_APCC_MODE_VAL, ctrl_dev->reg_base +
+ APCC_APM_MODE);
+
+ /* Ensure write above completes before delaying */
+ mb();
+
+ ret = msm8996_apm_wait_for_switch(ctrl_dev, MSM_APM_APCC_DONE_VAL);
+
+ /* Perform revision-specific programming steps */
+ if (ctrl_dev->version < HMSS_VERSION_1P2) {
+ /* Set SPM events */
+ for (i = 0; i < SPM_EVENT_NUM; i++)
+ writel_relaxed(SPM_EVENT_SET_VAL,
+ ctrl_dev->apcs_spm_events_addr[i]);
+
+ /* Complete SPM event sequence before clock source switch */
+ mb();
+
+ /* Switch APC/CBF clocks to original source */
+ writel_relaxed(APCS_GFMUXA_DESEL_VAL,
+ ctrl_dev->apcs_csr_base + APCS_SPARE);
+ ndelay(200);
+ writel_relaxed(MSM_APM_SEC_CLK_SEL_VAL,
+ ctrl_dev->apc0_pll_ctl_addr);
+ ndelay(200);
+ writel_relaxed(MSM_APM_SEC_CLK_SEL_VAL,
+ ctrl_dev->apc1_pll_ctl_addr);
+ }
+
+ /*
+ * Ensure that HMSS v1.0/v1.1 register writes are completed before
+ * bailing out in the case of a switching time out.
+ */
+ if (ret)
+ goto done;
+
+ ret = msm_apm_secure_clock_source_override(ctrl_dev, false);
+ if (ret)
+ goto done;
+
+ ctrl_dev->supply = MSM_APM_SUPPLY_APCC;
+ dev_dbg(ctrl_dev->dev, "APM supply switched to APCC\n");
+
+done:
+ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
+ mutex_unlock(&scm_lmh_lock);
+
+ return ret;
+}
+
+static int msm8996pro_apm_switch_to_mx(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&ctrl_dev->lock, flags);
+
+ /* Switch arrays to MX supply and wait for its completion */
+ writel_relaxed(MSM_APM_MX_MODE_VAL, ctrl_dev->reg_base +
+ APCC_APM_MODE);
+
+ /* Ensure write above completes before delaying */
+ mb();
+
+ ret = msm8996_apm_wait_for_switch(ctrl_dev, MSM_APM_MX_DONE_VAL);
+ if (ret)
+ goto done;
+
+ ctrl_dev->supply = MSM_APM_SUPPLY_MX;
+ dev_dbg(ctrl_dev->dev, "APM supply switched to MX\n");
+
+done:
+ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
+
+ return ret;
+}
+
+static int msm8996pro_apm_switch_to_apcc(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&ctrl_dev->lock, flags);
+
+ /* Switch arrays to APCC supply and wait for its completion */
+ writel_relaxed(MSM_APM_APCC_MODE_VAL, ctrl_dev->reg_base +
+ APCC_APM_MODE);
+
+ /* Ensure write above completes before delaying */
+ mb();
+
+ ret = msm8996_apm_wait_for_switch(ctrl_dev, MSM_APM_APCC_DONE_VAL);
+ if (ret)
+ goto done;
+
+ ctrl_dev->supply = MSM_APM_SUPPLY_APCC;
+ dev_dbg(ctrl_dev->dev, "APM supply switched to APCC\n");
+
+done:
+ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
+
+ return ret;
+}
+
+/* MSM8953 register value definitions */
+#define MSM8953_APM_MX_MODE_VAL 0x00
+#define MSM8953_APM_APCC_MODE_VAL 0x02
+#define MSM8953_APM_MX_DONE_VAL 0x00
+#define MSM8953_APM_APCC_DONE_VAL 0x03
+
+/* MSM8953 register offset definitions */
+#define MSM8953_APCC_APM_MODE 0x000002a8
+#define MSM8953_APCC_APM_CTL_STS 0x000002b0
+
+/* 8953 constants */
+#define MSM8953_APM_SWITCH_TIMEOUT_US 500
+
+/* Register bit mask definitions */
+#define MSM8953_APM_CTL_STS_MASK 0x1f
+
+static int msm8953_apm_switch_to_mx(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ int timeout = MSM8953_APM_SWITCH_TIMEOUT_US;
+ u32 regval;
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctrl_dev->lock, flags);
+
+ /* Switch arrays to MX supply and wait for its completion */
+ writel_relaxed(MSM8953_APM_MX_MODE_VAL, ctrl_dev->reg_base +
+ MSM8953_APCC_APM_MODE);
+
+ /* Ensure write above completes before delaying */
+ mb();
+
+ while (timeout > 0) {
+ regval = readl_relaxed(ctrl_dev->reg_base +
+ MSM8953_APCC_APM_CTL_STS);
+ if ((regval & MSM8953_APM_CTL_STS_MASK) ==
+ MSM8953_APM_MX_DONE_VAL)
+ break;
+
+ udelay(1);
+ timeout--;
+ }
+
+ if (timeout == 0) {
+ ret = -ETIMEDOUT;
+ dev_err(ctrl_dev->dev, "APCC to MX APM switch timed out. APCC_APM_CTL_STS=0x%x\n",
+ regval);
+ } else {
+ ctrl_dev->supply = MSM_APM_SUPPLY_MX;
+ dev_dbg(ctrl_dev->dev, "APM supply switched to MX\n");
+ }
+
+ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
+
+ return ret;
+}
+
+static int msm8953_apm_switch_to_apcc(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ int timeout = MSM8953_APM_SWITCH_TIMEOUT_US;
+ u32 regval;
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctrl_dev->lock, flags);
+
+ /* Switch arrays to APCC supply and wait for its completion */
+ writel_relaxed(MSM8953_APM_APCC_MODE_VAL, ctrl_dev->reg_base +
+ MSM8953_APCC_APM_MODE);
+
+ /* Ensure write above completes before delaying */
+ mb();
+
+ while (timeout > 0) {
+ regval = readl_relaxed(ctrl_dev->reg_base +
+ MSM8953_APCC_APM_CTL_STS);
+ if ((regval & MSM8953_APM_CTL_STS_MASK) ==
+ MSM8953_APM_APCC_DONE_VAL)
+ break;
+
+ udelay(1);
+ timeout--;
+ }
+
+ if (timeout == 0) {
+ ret = -ETIMEDOUT;
+ dev_err(ctrl_dev->dev, "MX to APCC APM switch timed out. APCC_APM_CTL_STS=0x%x\n",
+ regval);
+ } else {
+ ctrl_dev->supply = MSM_APM_SUPPLY_APCC;
+ dev_dbg(ctrl_dev->dev, "APM supply switched to APCC\n");
+ }
+
+ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
+
+ return ret;
+}
+
+static int msm_apm_switch_to_mx(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ int ret = 0;
+
+ switch (ctrl_dev->msm_id) {
+ case MSM8996_ID:
+ ret = msm8996_apm_switch_to_mx(ctrl_dev);
+ break;
+ case MSM8996PRO_ID:
+ ret = msm8996pro_apm_switch_to_mx(ctrl_dev);
+ break;
+ case MSM8953_ID:
+ ret = msm8953_apm_switch_to_mx(ctrl_dev);
+ break;
+ }
+
+ return ret;
+}
+
+static int msm_apm_switch_to_apcc(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ int ret = 0;
+
+ switch (ctrl_dev->msm_id) {
+ case MSM8996_ID:
+ ret = msm8996_apm_switch_to_apcc(ctrl_dev);
+ break;
+ case MSM8996PRO_ID:
+ ret = msm8996pro_apm_switch_to_apcc(ctrl_dev);
+ break;
+ case MSM8953_ID:
+ ret = msm8953_apm_switch_to_apcc(ctrl_dev);
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * msm_apm_get_supply() - Returns the supply that is currently
+ * powering the memory arrays
+ * @ctrl_dev: Pointer to an MSM APM controller device
+ *
+ * Returns the supply currently selected by the APM.
+ */
+int msm_apm_get_supply(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ return ctrl_dev->supply;
+}
+EXPORT_SYMBOL(msm_apm_get_supply);
+
+/**
+ * msm_apm_set_supply() - Perform the necessary steps to switch the voltage
+ * source of the memory arrays to a given supply
+ * @ctrl_dev: Pointer to an MSM APM controller device
+ * @supply: Power rail to use as supply for the memory
+ * arrays
+ *
+ * Returns 0 on success, -ETIMEDOUT on APM switch timeout, or -EPERM if
+ * the supply is not supported.
+ */
+int msm_apm_set_supply(struct msm_apm_ctrl_dev *ctrl_dev,
+ enum msm_apm_supply supply)
+{
+ int ret;
+
+ switch (supply) {
+ case MSM_APM_SUPPLY_APCC:
+ ret = msm_apm_switch_to_apcc(ctrl_dev);
+ break;
+ case MSM_APM_SUPPLY_MX:
+ ret = msm_apm_switch_to_mx(ctrl_dev);
+ break;
+ default:
+ ret = -EPERM;
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(msm_apm_set_supply);
+
+/**
+ * msm_apm_ctrl_dev_get() - get a handle to the MSM APM controller linked to
+ * the device in device tree
+ * @dev: Pointer to the device
+ *
+ * The device must specify "qcom,apm-ctrl" property in its device tree
+ * node which points to an MSM APM controller device node.
+ *
+ * Returns an MSM APM controller handle if successful or ERR_PTR on any error.
+ * If the APM controller device hasn't probed yet, ERR_PTR(-EPROBE_DEFER) is
+ * returned.
+ */
+struct msm_apm_ctrl_dev *msm_apm_ctrl_dev_get(struct device *dev)
+{
+ struct msm_apm_ctrl_dev *ctrl_dev = NULL;
+ struct msm_apm_ctrl_dev *dev_found = ERR_PTR(-EPROBE_DEFER);
+ struct device_node *ctrl_node;
+
+ if (!dev || !dev->of_node) {
+ pr_err("Invalid device node\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ ctrl_node = of_parse_phandle(dev->of_node, "qcom,apm-ctrl", 0);
+ if (!ctrl_node) {
+ pr_err("Could not find qcom,apm-ctrl property in %s\n",
+ dev->of_node->full_name);
+ return ERR_PTR(-ENXIO);
+ }
+
+ mutex_lock(&apm_ctrl_list_mutex);
+ list_for_each_entry(ctrl_dev, &apm_ctrl_list, list) {
+ if (ctrl_dev->dev && ctrl_dev->dev->of_node == ctrl_node) {
+ dev_found = ctrl_dev;
+ break;
+ }
+ }
+ mutex_unlock(&apm_ctrl_list_mutex);
+
+ of_node_put(ctrl_node);
+ return dev_found;
+}
+EXPORT_SYMBOL(msm_apm_ctrl_dev_get);
+
+#if defined(CONFIG_DEBUG_FS)
+
+static int apm_supply_dbg_open(struct inode *inode, struct file *filep)
+{
+ filep->private_data = inode->i_private;
+
+ return 0;
+}
+
+static ssize_t apm_supply_dbg_read(struct file *filep, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct msm_apm_ctrl_dev *ctrl_dev = filep->private_data;
+ char buf[10];
+ int len;
+
+ if (!ctrl_dev) {
+ pr_err("invalid apm ctrl handle\n");
+ return -ENODEV;
+ }
+
+ if (ctrl_dev->supply == MSM_APM_SUPPLY_APCC)
+ len = snprintf(buf, sizeof(buf), "APCC\n");
+ else if (ctrl_dev->supply == MSM_APM_SUPPLY_MX)
+ len = snprintf(buf, sizeof(buf), "MX\n");
+ else
+ len = snprintf(buf, sizeof(buf), "ERR\n");
+
+ return simple_read_from_buffer(ubuf, count, ppos, buf, len);
+}
+
+static const struct file_operations apm_supply_fops = {
+ .open = apm_supply_dbg_open,
+ .read = apm_supply_dbg_read,
+};
+
+static void apm_debugfs_base_init(void)
+{
+ apm_debugfs_base = debugfs_create_dir("msm-apm", NULL);
+
+ if (IS_ERR_OR_NULL(apm_debugfs_base))
+ pr_err("msm-apm debugfs base directory creation failed\n");
+}
+
+static void apm_debugfs_init(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ struct dentry *temp;
+
+ if (IS_ERR_OR_NULL(apm_debugfs_base)) {
+ pr_err("Base directory missing, cannot create apm debugfs nodes\n");
+ return;
+ }
+
+ ctrl_dev->debugfs = debugfs_create_dir(dev_name(ctrl_dev->dev),
+ apm_debugfs_base);
+ if (IS_ERR_OR_NULL(ctrl_dev->debugfs)) {
+ pr_err("%s debugfs directory creation failed\n",
+ dev_name(ctrl_dev->dev));
+ return;
+ }
+
+ temp = debugfs_create_file("supply", 0444, ctrl_dev->debugfs,
+ ctrl_dev, &apm_supply_fops);
+ if (IS_ERR_OR_NULL(temp)) {
+ pr_err("supply mode creation failed\n");
+ return;
+ }
+}
+
+static void apm_debugfs_deinit(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ if (!IS_ERR_OR_NULL(ctrl_dev->debugfs))
+ debugfs_remove_recursive(ctrl_dev->debugfs);
+}
+
+static void apm_debugfs_base_remove(void)
+{
+ debugfs_remove_recursive(apm_debugfs_base);
+}
+#else
+
+static void apm_debugfs_base_init(void)
+{}
+
+static void apm_debugfs_init(struct msm_apm_ctrl_dev *ctrl_dev)
+{}
+
+static void apm_debugfs_deinit(struct msm_apm_ctrl_dev *ctrl_dev)
+{}
+
+static void apm_debugfs_base_remove(void)
+{}
+
+#endif
+
+static const struct of_device_id msm_apm_match_table[] = {
+ {
+ .compatible = "qcom,msm-apm",
+ .data = (void *)(uintptr_t)MSM8996_ID,
+ },
+ {
+ .compatible = "qcom,msm8996pro-apm",
+ .data = (void *)(uintptr_t)MSM8996PRO_ID,
+ },
+ {
+ .compatible = "qcom,msm8953-apm",
+ .data = (void *)(uintptr_t)MSM8953_ID,
+ },
+ {}
+};
+
+static int msm_apm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct msm_apm_ctrl_dev *ctrl;
+ const struct of_device_id *match;
+ int ret = 0;
+
+ dev_dbg(dev, "probing MSM Array Power Mux driver\n");
+
+ if (!dev->of_node) {
+ dev_err(dev, "Device tree node is missing\n");
+ return -ENODEV;
+ }
+
+ match = of_match_device(msm_apm_match_table, dev);
+ if (!match)
+ return -ENODEV;
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&ctrl->list);
+ spin_lock_init(&ctrl->lock);
+ ctrl->dev = dev;
+ ctrl->msm_id = (uintptr_t)match->data;
+ platform_set_drvdata(pdev, ctrl);
+
+ switch (ctrl->msm_id) {
+ case MSM8996_ID:
+ case MSM8996PRO_ID:
+ ret = msm_apm_ctrl_devm_ioremap(pdev, ctrl);
+ if (ret) {
+ dev_err(dev, "Failed to add APM controller device\n");
+ return ret;
+ }
+ break;
+ case MSM8953_ID:
+ ret = msm8953_apm_ctrl_init(pdev, ctrl);
+ if (ret) {
+ dev_err(dev, "Failed to initialize APM controller device: ret=%d\n",
+ ret);
+ return ret;
+ }
+ break;
+ default:
+ dev_err(dev, "unable to add APM controller device for msm_id:%d\n",
+ ctrl->msm_id);
+ return -ENODEV;
+ }
+
+ apm_debugfs_init(ctrl);
+ mutex_lock(&apm_ctrl_list_mutex);
+ list_add_tail(&ctrl->list, &apm_ctrl_list);
+ mutex_unlock(&apm_ctrl_list_mutex);
+
+ dev_dbg(dev, "MSM Array Power Mux driver probe successful");
+
+ return ret;
+}
+
+static int msm_apm_remove(struct platform_device *pdev)
+{
+ struct msm_apm_ctrl_dev *ctrl_dev;
+
+ ctrl_dev = platform_get_drvdata(pdev);
+ if (ctrl_dev) {
+ mutex_lock(&apm_ctrl_list_mutex);
+ list_del(&ctrl_dev->list);
+ mutex_unlock(&apm_ctrl_list_mutex);
+ apm_debugfs_deinit(ctrl_dev);
+ }
+
+ return 0;
+}
+
+static struct platform_driver msm_apm_driver = {
+ .driver = {
+ .name = MSM_APM_DRIVER_NAME,
+ .of_match_table = msm_apm_match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = msm_apm_probe,
+ .remove = msm_apm_remove,
+};
+
+static int __init msm_apm_init(void)
+{
+ apm_debugfs_base_init();
+ return platform_driver_register(&msm_apm_driver);
+}
+
+static void __exit msm_apm_exit(void)
+{
+ platform_driver_unregister(&msm_apm_driver);
+ apm_debugfs_base_remove();
+}
+
+arch_initcall(msm_apm_init);
+module_exit(msm_apm_exit);
+
+MODULE_DESCRIPTION("MSM Array Power Mux driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/qcom/debug_core.c b/drivers/power/qcom/debug_core.c
new file mode 100644
index 000000000000..51b6d63fe994
--- /dev/null
+++ b/drivers/power/qcom/debug_core.c
@@ -0,0 +1,330 @@
+/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/string.h>
+#include <linux/debugfs.h>
+#include <linux/ctype.h>
+#include <linux/cpu.h>
+#include "soc/qcom/msm-core.h"
+
+#define MAX_PSTATES 50
+#define NUM_OF_PENTRY 3 /* number of variables for ptable node */
+#define NUM_OF_EENTRY 2 /* number of variables for enable node */
+
+enum arg_offset {
+ CPU_OFFSET,
+ FREQ_OFFSET,
+ POWER_OFFSET,
+};
+
+struct core_debug {
+ int cpu;
+ struct cpu_pstate_pwr *head;
+ int enabled;
+ int len;
+ struct cpu_pwr_stats *ptr;
+ struct cpu_pstate_pwr *driver_data;
+ int driver_len;
+};
+
+static DEFINE_PER_CPU(struct core_debug, c_dgfs);
+static struct cpu_pwr_stats *msm_core_data;
+static struct debugfs_blob_wrapper help_msg = {
+ .data =
+"MSM CORE Debug-FS Support\n"
+"\n"
+"Hierarchy schema\n"
+"/sys/kernel/debug/msm_core\n"
+" /help - Static help text\n"
+" /ptable - write to p-state table\n"
+" /enable - enable the written p-state table\n"
+" /ptable_dump - Dump the debug ptable\n"
+"\n"
+"Usage\n"
+" Input test frequency and power information in ptable:\n"
+" echo \"0 300000 120\" > ptable\n"
+" format: <cpu> <frequency in khz> <power>\n"
+"\n"
+" Enable the ptable for the cpu:\n"
+" echo \"0 1\" > enable\n"
+" format: <cpu> <1 to enable, 0 to disable>\n"
+" Note: Writing 0 to disable will reset/clear the ptable\n"
+"\n"
+" Dump the entire ptable:\n"
+" cat ptable\n"
+" ----- CPU0 - Enabled ---------\n"
+" Freq Power\n"
+" 700000 120\n"
+"----- CPU0 - Live numbers -----\n"
+" Freq Power\n"
+" 300000 218\n"
+" ----- CPU1 - Written ---------\n"
+" Freq Power\n"
+" 700000 120\n"
+" Ptable dump will dump the status of the table as well\n"
+" It shows:\n"
+" Enabled -> for a cpu that debug ptable enabled\n"
+" Written -> for a cpu that has debug ptable values written\n"
+" but not enabled\n"
+"\n",
+
+};
+
+static void add_to_ptable(unsigned int *arg)
+{
+ struct core_debug *node;
+ int i, cpu = arg[CPU_OFFSET];
+ uint32_t freq = arg[FREQ_OFFSET];
+ uint32_t power = arg[POWER_OFFSET];
+
+ if (!cpu_possible(cpu))
+ return;
+
+ if ((freq == 0) || (power == 0)) {
+ pr_warn("Incorrect power data\n");
+ return;
+ }
+
+ node = &per_cpu(c_dgfs, cpu);
+
+ if (node->len >= MAX_PSTATES) {
+ pr_warn("Dropped ptable update - no space left.\n");
+ return;
+ }
+
+ if (!node->head) {
+ node->head = kzalloc(sizeof(struct cpu_pstate_pwr) *
+ (MAX_PSTATES + 1),
+ GFP_KERNEL);
+ if (!node->head)
+ return;
+ }
+
+ for (i = 0; i < node->len; i++) {
+ if (node->head[i].freq == freq) {
+ node->head[i].power = power;
+ return;
+ }
+ }
+
+ /* Insert a new frequency (may need to move things around to
+ keep in ascending order). */
+ for (i = MAX_PSTATES - 1; i > 0; i--) {
+ if (node->head[i-1].freq > freq) {
+ node->head[i].freq = node->head[i-1].freq;
+ node->head[i].power = node->head[i-1].power;
+ } else if (node->head[i-1].freq != 0) {
+ break;
+ }
+ }
+
+ if (node->len < MAX_PSTATES) {
+ node->head[i].freq = freq;
+ node->head[i].power = power;
+ node->len++;
+ }
+
+ if (node->ptr)
+ node->ptr->len = node->len;
+}
+
+static int split_ptable_args(char *line, unsigned int *arg, uint32_t n)
+{
+ char *args;
+ int i;
+ int ret = 0;
+
+ for (i = 0; i < n; i++) {
+ if (!line)
+ break;
+ args = strsep(&line, " ");
+ ret = kstrtouint(args, 10, &arg[i]);
+ if (ret)
+ return ret;
+ }
+ return ret;
+}
+
+static ssize_t msm_core_ptable_write(struct file *file,
+ const char __user *ubuf, size_t len, loff_t *offp)
+{
+ char *kbuf;
+ int ret;
+ unsigned int arg[3];
+
+ if (len == 0)
+ return 0;
+
+ kbuf = kzalloc(len + 1, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ if (copy_from_user(kbuf, ubuf, len)) {
+ ret = -EFAULT;
+ goto done;
+ }
+ kbuf[len] = '\0';
+ ret = split_ptable_args(kbuf, arg, NUM_OF_PENTRY);
+ if (!ret) {
+ add_to_ptable(arg);
+ ret = len;
+ }
+done:
+ kfree(kbuf);
+ return ret;
+}
+
+static void print_table(struct seq_file *m, struct cpu_pstate_pwr *c_n,
+ int len)
+{
+ int i;
+
+ seq_puts(m, " Freq Power\n");
+ for (i = 0; i < len; i++)
+ seq_printf(m, " %d %u\n", c_n[i].freq,
+ c_n[i].power);
+
+}
+
+static int msm_core_ptable_read(struct seq_file *m, void *data)
+{
+ int cpu;
+ struct core_debug *node;
+
+ for_each_possible_cpu(cpu) {
+ node = &per_cpu(c_dgfs, cpu);
+ if (node->head) {
+ seq_printf(m, "----- CPU%d - %s - Debug -------\n",
+ cpu, node->enabled == 1 ? "Enabled" : "Written");
+ print_table(m, node->head, node->len);
+ }
+ if (msm_core_data[cpu].ptable) {
+ seq_printf(m, "--- CPU%d - Live numbers at %ldC---\n",
+ cpu, node->ptr->temp);
+ print_table(m, msm_core_data[cpu].ptable,
+ node->driver_len);
+ }
+ }
+ return 0;
+}
+
+static ssize_t msm_core_enable_write(struct file *file,
+ const char __user *ubuf, size_t len, loff_t *offp)
+{
+ char *kbuf;
+ int ret;
+ unsigned int arg[3];
+ int cpu;
+
+ if (len == 0)
+ return 0;
+
+ kbuf = kzalloc(len + 1, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ if (copy_from_user(kbuf, ubuf, len)) {
+ ret = -EFAULT;
+ goto done;
+ }
+ kbuf[len] = '\0';
+ ret = split_ptable_args(kbuf, arg, NUM_OF_EENTRY);
+ if (ret)
+ goto done;
+ cpu = arg[CPU_OFFSET];
+
+ if (cpu_possible(cpu)) {
+ struct core_debug *node = &per_cpu(c_dgfs, cpu);
+
+ if (arg[FREQ_OFFSET]) {
+ msm_core_data[cpu].ptable = node->head;
+ msm_core_data[cpu].len = node->len;
+ } else {
+ msm_core_data[cpu].ptable = node->driver_data;
+ msm_core_data[cpu].len = node->driver_len;
+ node->len = 0;
+ }
+ node->enabled = arg[FREQ_OFFSET];
+ }
+ ret = len;
+ blocking_notifier_call_chain(
+ get_power_update_notifier(), cpu, NULL);
+
+done:
+ kfree(kbuf);
+ return ret;
+}
+
+static const struct file_operations msm_core_enable_ops = {
+ .write = msm_core_enable_write,
+};
+
+static int msm_core_dump_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, msm_core_ptable_read, inode->i_private);
+}
+
+static const struct file_operations msm_core_ptable_ops = {
+ .open = msm_core_dump_open,
+ .read = seq_read,
+ .write = msm_core_ptable_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+int msm_core_debug_init(void)
+{
+ struct dentry *dir = NULL;
+ struct dentry *file = NULL;
+ int i;
+
+ msm_core_data = get_cpu_pwr_stats();
+ if (!msm_core_data)
+ goto fail;
+
+ dir = debugfs_create_dir("msm_core", NULL);
+ if (IS_ERR_OR_NULL(dir))
+ return PTR_ERR(dir);
+
+ file = debugfs_create_file("enable",
+ S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP, dir, NULL,
+ &msm_core_enable_ops);
+ if (IS_ERR_OR_NULL(file))
+ goto fail;
+
+ file = debugfs_create_file("ptable",
+ S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP, dir, NULL,
+ &msm_core_ptable_ops);
+ if (IS_ERR_OR_NULL(file))
+ goto fail;
+
+ help_msg.size = strlen(help_msg.data);
+ file = debugfs_create_blob("help", S_IRUGO, dir, &help_msg);
+ if (IS_ERR_OR_NULL(file))
+ goto fail;
+
+ for (i = 0; i < num_possible_cpus(); i++) {
+ per_cpu(c_dgfs, i).ptr = &msm_core_data[i];
+ per_cpu(c_dgfs, i).driver_data = msm_core_data[i].ptable;
+ per_cpu(c_dgfs, i).driver_len = msm_core_data[i].len;
+ }
+ return 0;
+fail:
+ debugfs_remove(dir);
+ return PTR_ERR(file);
+}
+late_initcall(msm_core_debug_init);
diff --git a/drivers/power/qcom/lpm-stats.c b/drivers/power/qcom/lpm-stats.c
new file mode 100644
index 000000000000..d3cafc411a77
--- /dev/null
+++ b/drivers/power/qcom/lpm-stats.c
@@ -0,0 +1,871 @@
+/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <soc/qcom/spm.h>
+#include <soc/qcom/pm.h>
+#include <soc/qcom/lpm-stats.h>
+
+#define MAX_STR_LEN 256
+#define MAX_TIME_LEN 20
+const char *lpm_stats_reset = "reset";
+const char *lpm_stats_suspend = "suspend";
+
+struct lpm_sleep_time {
+ struct kobj_attribute ts_attr;
+ unsigned int cpu;
+};
+
+struct level_stats {
+ const char *name;
+ struct lpm_stats *owner;
+ int64_t first_bucket_time;
+ int bucket[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
+ int64_t min_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
+ int64_t max_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
+ int success_count;
+ int failed_count;
+ int64_t total_time;
+ uint64_t enter_time;
+};
+
+static struct level_stats suspend_time_stats;
+
+static DEFINE_PER_CPU_SHARED_ALIGNED(struct lpm_stats, cpu_stats);
+
+static uint64_t get_total_sleep_time(unsigned int cpu_id)
+{
+ struct lpm_stats *stats = &per_cpu(cpu_stats, cpu_id);
+ int i;
+ uint64_t ret = 0;
+
+ for (i = 0; i < stats->num_levels; i++)
+ ret += stats->time_stats[i].total_time;
+
+ return ret;
+}
+
+static void update_level_stats(struct level_stats *stats, uint64_t t,
+ bool success)
+{
+ uint64_t bt;
+ int i;
+
+ if (!success) {
+ stats->failed_count++;
+ return;
+ }
+
+ stats->success_count++;
+ stats->total_time += t;
+ bt = t;
+ do_div(bt, stats->first_bucket_time);
+
+ if (bt < 1ULL << (CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT *
+ (CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1)))
+ i = DIV_ROUND_UP(fls((uint32_t)bt),
+ CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT);
+ else
+ i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
+
+ if (i >= CONFIG_MSM_IDLE_STATS_BUCKET_COUNT)
+ i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
+
+ stats->bucket[i]++;
+
+ if (t < stats->min_time[i] || !stats->max_time[i])
+ stats->min_time[i] = t;
+ if (t > stats->max_time[i])
+ stats->max_time[i] = t;
+ return;
+}
+
+static void level_stats_print(struct seq_file *m, struct level_stats *stats)
+{
+ int i = 0;
+ int64_t bucket_time = 0;
+ char seqs[MAX_STR_LEN] = {0};
+ int64_t s = stats->total_time;
+ uint32_t ns = do_div(s, NSEC_PER_SEC);
+
+ snprintf(seqs, MAX_STR_LEN,
+ "[%s] %s:\n"
+ " success count: %7d\n"
+ " total success time: %lld.%09u\n",
+ stats->owner->name,
+ stats->name,
+ stats->success_count,
+ s, ns);
+ seq_puts(m, seqs);
+
+ if (stats->failed_count) {
+ snprintf(seqs, MAX_STR_LEN, " failed count: %7d\n",
+ stats->failed_count);
+ seq_puts(m, seqs);
+ }
+
+ bucket_time = stats->first_bucket_time;
+ for (i = 0;
+ i < CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
+ i++) {
+ s = bucket_time;
+ ns = do_div(s, NSEC_PER_SEC);
+ snprintf(seqs, MAX_STR_LEN,
+ "\t<%6lld.%09u: %7d (%lld-%lld)\n",
+ s, ns, stats->bucket[i],
+ stats->min_time[i],
+ stats->max_time[i]);
+ seq_puts(m, seqs);
+ bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT;
+ }
+ snprintf(seqs, MAX_STR_LEN,
+ "\t>=%5lld.%09u:%8d (%lld-%lld)\n",
+ s, ns, stats->bucket[i],
+ stats->min_time[i],
+ stats->max_time[i]);
+ seq_puts(m, seqs);
+}
+
+static int level_stats_file_show(struct seq_file *m, void *v)
+{
+ struct level_stats *stats = NULL;
+
+ if (!m->private)
+ return -EINVAL;
+
+ stats = (struct level_stats *) m->private;
+
+ level_stats_print(m, stats);
+
+ return 0;
+}
+
+static int level_stats_file_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, level_stats_file_show, inode->i_private);
+}
+
+static void level_stats_print_all(struct seq_file *m, struct lpm_stats *stats)
+{
+ struct list_head *centry = NULL;
+ struct lpm_stats *pos = NULL;
+ int i = 0;
+
+ for (i = 0; i < stats->num_levels; i++)
+ level_stats_print(m, &stats->time_stats[i]);
+
+ if (list_empty(&stats->child))
+ return;
+
+ centry = &stats->child;
+ list_for_each_entry(pos, centry, sibling) {
+ level_stats_print_all(m, pos);
+ }
+}
+
+static void level_stats_reset(struct level_stats *stats)
+{
+ memset(stats->bucket, 0, sizeof(stats->bucket));
+ memset(stats->min_time, 0, sizeof(stats->min_time));
+ memset(stats->max_time, 0, sizeof(stats->max_time));
+ stats->success_count = 0;
+ stats->failed_count = 0;
+ stats->total_time = 0;
+}
+
+static void level_stats_reset_all(struct lpm_stats *stats)
+{
+ struct list_head *centry = NULL;
+ struct lpm_stats *pos = NULL;
+ int i = 0;
+
+ for (i = 0; i < stats->num_levels; i++)
+ level_stats_reset(&stats->time_stats[i]);
+
+ if (list_empty(&stats->child))
+ return;
+
+ centry = &stats->child;
+ list_for_each_entry(pos, centry, sibling) {
+ level_stats_reset_all(pos);
+ }
+}
+
+static int lpm_stats_file_show(struct seq_file *m, void *v)
+{
+ struct lpm_stats *stats = (struct lpm_stats *)m->private;
+
+ if (!m->private) {
+ pr_err("%s: Invalid pdata, Cannot print stats\n", __func__);
+ return -EINVAL;
+ }
+
+ level_stats_print_all(m, stats);
+ level_stats_print(m, &suspend_time_stats);
+
+ return 0;
+}
+
+static int lpm_stats_file_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, lpm_stats_file_show, inode->i_private);
+}
+
+static ssize_t level_stats_file_write(struct file *file,
+ const char __user *buffer, size_t count, loff_t *off)
+{
+ char buf[MAX_STR_LEN] = {0};
+ struct inode *in = file->f_inode;
+ struct level_stats *stats = (struct level_stats *)in->i_private;
+ size_t len = strnlen(lpm_stats_reset, MAX_STR_LEN);
+
+ if (!stats)
+ return -EINVAL;
+
+ if (count != len+1)
+ return -EINVAL;
+
+ if (copy_from_user(buf, buffer, len))
+ return -EFAULT;
+
+ if (strcmp(buf, lpm_stats_reset))
+ return -EINVAL;
+
+ level_stats_reset(stats);
+
+ return count;
+}
+
+static ssize_t lpm_stats_file_write(struct file *file,
+ const char __user *buffer, size_t count, loff_t *off)
+{
+ char buf[MAX_STR_LEN] = {0};
+ struct inode *in = file->f_inode;
+ struct lpm_stats *stats = (struct lpm_stats *)in->i_private;
+ size_t len = strnlen(lpm_stats_reset, MAX_STR_LEN);
+
+ if (!stats)
+ return -EINVAL;
+
+ if (count != len+1)
+ return -EINVAL;
+
+ if (copy_from_user(buf, buffer, len))
+ return -EFAULT;
+
+ if (strcmp(buf, lpm_stats_reset))
+ return -EINVAL;
+
+ level_stats_reset_all(stats);
+
+ return count;
+}
+
+int lifo_stats_file_show(struct seq_file *m, void *v)
+{
+ struct lpm_stats *stats = NULL;
+ struct list_head *centry = NULL;
+ struct lpm_stats *pos = NULL;
+ char seqs[MAX_STR_LEN] = {0};
+
+ if (!m->private)
+ return -EINVAL;
+
+ stats = (struct lpm_stats *)m->private;
+
+ if (list_empty(&stats->child)) {
+ pr_err("%s: ERROR: Lifo level with no children.\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ centry = &stats->child;
+ list_for_each_entry(pos, centry, sibling) {
+ snprintf(seqs, MAX_STR_LEN,
+ "%s:\n"
+ "\tLast-In:%u\n"
+ "\tFirst-Out:%u\n",
+ pos->name,
+ pos->lifo.last_in,
+ pos->lifo.first_out);
+ seq_puts(m, seqs);
+ }
+ return 0;
+}
+
+static int lifo_stats_file_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, lifo_stats_file_show, inode->i_private);
+}
+
+static void lifo_stats_reset_all(struct lpm_stats *stats)
+{
+ struct list_head *centry = NULL;
+ struct lpm_stats *pos = NULL;
+
+ centry = &stats->child;
+ list_for_each_entry(pos, centry, sibling) {
+ pos->lifo.last_in = 0;
+ pos->lifo.first_out = 0;
+ if (!list_empty(&pos->child))
+ lifo_stats_reset_all(pos);
+ }
+}
+
+static ssize_t lifo_stats_file_write(struct file *file,
+ const char __user *buffer, size_t count, loff_t *off)
+{
+ char buf[MAX_STR_LEN] = {0};
+ struct inode *in = file->f_inode;
+ struct lpm_stats *stats = (struct lpm_stats *)in->i_private;
+ size_t len = strnlen(lpm_stats_reset, MAX_STR_LEN);
+
+ if (!stats)
+ return -EINVAL;
+
+ if (count != len+1)
+ return -EINVAL;
+
+ if (copy_from_user(buf, buffer, len))
+ return -EFAULT;
+
+ if (strcmp(buf, lpm_stats_reset))
+ return -EINVAL;
+
+ lifo_stats_reset_all(stats);
+
+ return count;
+}
+
+static const struct file_operations level_stats_fops = {
+ .owner = THIS_MODULE,
+ .open = level_stats_file_open,
+ .read = seq_read,
+ .release = single_release,
+ .llseek = no_llseek,
+ .write = level_stats_file_write,
+};
+
+static const struct file_operations lpm_stats_fops = {
+ .owner = THIS_MODULE,
+ .open = lpm_stats_file_open,
+ .read = seq_read,
+ .release = single_release,
+ .llseek = no_llseek,
+ .write = lpm_stats_file_write,
+};
+
+static const struct file_operations lifo_stats_fops = {
+ .owner = THIS_MODULE,
+ .open = lifo_stats_file_open,
+ .read = seq_read,
+ .release = single_release,
+ .llseek = no_llseek,
+ .write = lifo_stats_file_write,
+};
+
+static void update_last_in_stats(struct lpm_stats *stats)
+{
+ struct list_head *centry = NULL;
+ struct lpm_stats *pos = NULL;
+
+ if (list_empty(&stats->child))
+ return;
+
+ centry = &stats->child;
+ list_for_each_entry(pos, centry, sibling) {
+ if (cpumask_test_cpu(smp_processor_id(), &pos->mask)) {
+ pos->lifo.last_in++;
+ return;
+ }
+ }
+ WARN(1, "Should not reach here\n");
+}
+
+static void update_first_out_stats(struct lpm_stats *stats)
+{
+ struct list_head *centry = NULL;
+ struct lpm_stats *pos = NULL;
+
+ if (list_empty(&stats->child))
+ return;
+
+ centry = &stats->child;
+ list_for_each_entry(pos, centry, sibling) {
+ if (cpumask_test_cpu(smp_processor_id(), &pos->mask)) {
+ pos->lifo.first_out++;
+ return;
+ }
+ }
+ WARN(1, "Should not reach here\n");
+}
+
+static inline void update_exit_stats(struct lpm_stats *stats, uint32_t index,
+ bool success)
+{
+ uint64_t exit_time = 0;
+
+ /* Update time stats only when exit is preceded by enter */
+ exit_time = stats->sleep_time;
+ update_level_stats(&stats->time_stats[index], exit_time,
+ success);
+}
+
+static int config_level(const char *name, const char **levels,
+ int num_levels, struct lpm_stats *parent, struct lpm_stats *stats)
+{
+ int i = 0;
+ struct dentry *directory = NULL;
+ const char *rootname = "lpm_stats";
+ const char *dirname = rootname;
+
+ strlcpy(stats->name, name, MAX_STR_LEN);
+ stats->num_levels = num_levels;
+ stats->parent = parent;
+ INIT_LIST_HEAD(&stats->sibling);
+ INIT_LIST_HEAD(&stats->child);
+
+ stats->time_stats = kzalloc(sizeof(struct level_stats) *
+ num_levels, GFP_KERNEL);
+ if (!stats->time_stats) {
+ pr_err("%s: Insufficient memory for %s level time stats\n",
+ __func__, name);
+ return -ENOMEM;
+ }
+
+ if (parent) {
+ list_add_tail(&stats->sibling, &parent->child);
+ directory = parent->directory;
+ dirname = name;
+ }
+
+ stats->directory = debugfs_create_dir(dirname, directory);
+ if (!stats->directory) {
+ pr_err("%s: Unable to create %s debugfs directory\n",
+ __func__, dirname);
+ kfree(stats->time_stats);
+ return -EPERM;
+ }
+
+ for (i = 0; i < num_levels; i++) {
+ stats->time_stats[i].name = levels[i];
+ stats->time_stats[i].owner = stats;
+ stats->time_stats[i].first_bucket_time =
+ CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
+ stats->time_stats[i].enter_time = 0;
+
+ if (!debugfs_create_file(stats->time_stats[i].name, S_IRUGO,
+ stats->directory, (void *)&stats->time_stats[i],
+ &level_stats_fops)) {
+ pr_err("%s: Unable to create %s %s level-stats file\n",
+ __func__, stats->name,
+ stats->time_stats[i].name);
+ kfree(stats->time_stats);
+ return -EPERM;
+ }
+ }
+
+ if (!debugfs_create_file("stats", S_IRUGO, stats->directory,
+ (void *)stats, &lpm_stats_fops)) {
+ pr_err("%s: Unable to create %s's overall 'stats' file\n",
+ __func__, stats->name);
+ kfree(stats->time_stats);
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+static ssize_t total_sleep_time_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct lpm_sleep_time *cpu_sleep_time = container_of(attr,
+ struct lpm_sleep_time, ts_attr);
+ unsigned int cpu = cpu_sleep_time->cpu;
+ uint64_t total_time = get_total_sleep_time(cpu);
+
+ return snprintf(buf, MAX_TIME_LEN, "%llu.%09u\n", total_time,
+ do_div(total_time, NSEC_PER_SEC));
+}
+
+static struct kobject *local_module_kobject(void)
+{
+ struct kobject *kobj;
+
+ kobj = kset_find_obj(module_kset, KBUILD_MODNAME);
+
+ if (!kobj) {
+ int err;
+ struct module_kobject *mk;
+
+ mk = kzalloc(sizeof(*mk), GFP_KERNEL);
+ if (!mk)
+ return ERR_PTR(-ENOMEM);
+
+ mk->mod = THIS_MODULE;
+ mk->kobj.kset = module_kset;
+
+ err = kobject_init_and_add(&mk->kobj, &module_ktype, NULL,
+ "%s", KBUILD_MODNAME);
+
+ if (err) {
+ kobject_put(&mk->kobj);
+ kfree(mk);
+ pr_err("%s: cannot create kobject for %s\n",
+ __func__, KBUILD_MODNAME);
+ return ERR_PTR(err);
+ }
+
+ kobject_get(&mk->kobj);
+ kobj = &mk->kobj;
+ }
+
+ return kobj;
+}
+
+static int create_sysfs_node(unsigned int cpu, struct lpm_stats *stats)
+{
+ struct kobject *cpu_kobj = NULL;
+ struct lpm_sleep_time *ts = NULL;
+ struct kobject *stats_kobj;
+ char cpu_name[] = "cpuXX";
+ int ret = -ENOMEM;
+
+ stats_kobj = local_module_kobject();
+
+ if (IS_ERR_OR_NULL(stats_kobj))
+ return PTR_ERR(stats_kobj);
+
+ snprintf(cpu_name, sizeof(cpu_name), "cpu%u", cpu);
+ cpu_kobj = kobject_create_and_add(cpu_name, stats_kobj);
+ if (!cpu_kobj)
+ return -ENOMEM;
+
+ ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ goto failed;
+
+ sysfs_attr_init(&ts->ts_attr.attr);
+ ts->ts_attr.attr.name = "total_sleep_time_secs";
+ ts->ts_attr.attr.mode = 0444;
+ ts->ts_attr.show = total_sleep_time_show;
+ ts->ts_attr.store = NULL;
+ ts->cpu = cpu;
+
+ ret = sysfs_create_file(cpu_kobj, &ts->ts_attr.attr);
+ if (ret)
+ goto failed;
+
+ return 0;
+
+failed:
+ kfree(ts);
+ kobject_put(cpu_kobj);
+ return ret;
+}
+
+static struct lpm_stats *config_cpu_level(const char *name,
+ const char **levels, int num_levels, struct lpm_stats *parent,
+ struct cpumask *mask)
+{
+ int cpu = 0;
+ struct lpm_stats *pstats = NULL;
+ struct lpm_stats *stats = NULL;
+
+ for (pstats = parent; pstats; pstats = pstats->parent)
+ cpumask_or(&pstats->mask, &pstats->mask, mask);
+
+ for_each_cpu(cpu, mask) {
+ int ret = 0;
+ char cpu_name[MAX_STR_LEN] = {0};
+
+ stats = &per_cpu(cpu_stats, cpu);
+ snprintf(cpu_name, MAX_STR_LEN, "%s%d", name, cpu);
+ cpumask_set_cpu(cpu, &stats->mask);
+
+ stats->is_cpu = true;
+
+ ret = config_level(cpu_name, levels, num_levels, parent,
+ stats);
+ if (ret) {
+ pr_err("%s: Unable to create %s stats\n",
+ __func__, cpu_name);
+ return ERR_PTR(ret);
+ }
+
+ ret = create_sysfs_node(cpu, stats);
+
+ if (ret) {
+ pr_err("Could not create the sysfs node\n");
+ return ERR_PTR(ret);
+ }
+ }
+
+ return stats;
+}
+
+static void config_suspend_level(struct lpm_stats *stats)
+{
+ suspend_time_stats.name = lpm_stats_suspend;
+ suspend_time_stats.owner = stats;
+ suspend_time_stats.first_bucket_time =
+ CONFIG_MSM_SUSPEND_STATS_FIRST_BUCKET;
+ suspend_time_stats.enter_time = 0;
+ suspend_time_stats.success_count = 0;
+ suspend_time_stats.failed_count = 0;
+
+ if (!debugfs_create_file(suspend_time_stats.name, S_IRUGO,
+ stats->directory, (void *)&suspend_time_stats,
+ &level_stats_fops))
+ pr_err("%s: Unable to create %s Suspend stats file\n",
+ __func__, stats->name);
+}
+
+static struct lpm_stats *config_cluster_level(const char *name,
+ const char **levels, int num_levels, struct lpm_stats *parent)
+{
+ struct lpm_stats *stats = NULL;
+ int ret = 0;
+
+ stats = kzalloc(sizeof(struct lpm_stats), GFP_KERNEL);
+ if (!stats) {
+ pr_err("%s: Insufficient memory for %s stats\n",
+ __func__, name);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ stats->is_cpu = false;
+
+ ret = config_level(name, levels, num_levels, parent, stats);
+ if (ret) {
+ pr_err("%s: Unable to create %s stats\n", __func__,
+ name);
+ kfree(stats);
+ return ERR_PTR(ret);
+ }
+
+ if (!debugfs_create_file("lifo", S_IRUGO, stats->directory,
+ (void *)stats, &lifo_stats_fops)) {
+ pr_err("%s: Unable to create %s lifo stats file\n",
+ __func__, stats->name);
+ kfree(stats);
+ return ERR_PTR(-EPERM);
+ }
+
+ if (!parent)
+ config_suspend_level(stats);
+
+ return stats;
+}
+
+static void cleanup_stats(struct lpm_stats *stats)
+{
+ struct list_head *centry = NULL;
+ struct lpm_stats *pos = NULL;
+
+ centry = &stats->child;
+ list_for_each_entry_reverse(pos, centry, sibling) {
+ if (!list_empty(&pos->child))
+ cleanup_stats(pos);
+
+ list_del_init(&pos->child);
+
+ kfree(pos->time_stats);
+ if (!pos->is_cpu)
+ kfree(pos);
+ }
+ kfree(stats->time_stats);
+ kfree(stats);
+}
+
+static void lpm_stats_cleanup(struct lpm_stats *stats)
+{
+ struct lpm_stats *pstats = stats;
+
+ if (!pstats)
+ return;
+
+ while (pstats->parent)
+ pstats = pstats->parent;
+
+ debugfs_remove_recursive(pstats->directory);
+
+ cleanup_stats(pstats);
+}
+
+/**
+ * lpm_stats_config_level() - API to configure levels stats.
+ *
+ * @name: Name of the cluster/cpu.
+ * @levels: Low power mode level names.
+ * @num_levels: Number of leves supported.
+ * @parent: Pointer to the parent's lpm_stats object.
+ * @mask: cpumask, if configuring cpu stats, else NULL.
+ *
+ * Function to communicate the low power mode levels supported by
+ * cpus or a cluster.
+ *
+ * Return: Pointer to the lpm_stats object or ERR_PTR(-ERRNO)
+ */
+struct lpm_stats *lpm_stats_config_level(const char *name,
+ const char **levels, int num_levels, struct lpm_stats *parent,
+ struct cpumask *mask)
+{
+ struct lpm_stats *stats = NULL;
+
+ if (!levels || num_levels <= 0 || IS_ERR(parent)) {
+ pr_err("%s: Invalid input\n\t\tlevels = %p\n\t\t"
+ "num_levels = %d\n\t\tparent = %ld\n",
+ __func__, levels, num_levels, PTR_ERR(parent));
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (mask)
+ stats = config_cpu_level(name, levels, num_levels, parent,
+ mask);
+ else
+ stats = config_cluster_level(name, levels, num_levels,
+ parent);
+
+ if (IS_ERR(stats)) {
+ lpm_stats_cleanup(parent);
+ return stats;
+ }
+
+ return stats;
+}
+EXPORT_SYMBOL(lpm_stats_config_level);
+
+/**
+ * lpm_stats_cluster_enter() - API to communicate the lpm level a cluster
+ * is prepared to enter.
+ *
+ * @stats: Pointer to the cluster's lpm_stats object.
+ * @index: Index of the lpm level that the cluster is going to enter.
+ *
+ * Function to communicate the low power mode level that the cluster is
+ * prepared to enter.
+ */
+void lpm_stats_cluster_enter(struct lpm_stats *stats, uint32_t index)
+{
+ if (IS_ERR_OR_NULL(stats))
+ return;
+
+ update_last_in_stats(stats);
+}
+EXPORT_SYMBOL(lpm_stats_cluster_enter);
+
+/**
+ * lpm_stats_cluster_exit() - API to communicate the lpm level a cluster
+ * exited.
+ *
+ * @stats: Pointer to the cluster's lpm_stats object.
+ * @index: Index of the cluster lpm level.
+ * @success: Success/Failure of the low power mode execution.
+ *
+ * Function to communicate the low power mode level that the cluster
+ * exited.
+ */
+void lpm_stats_cluster_exit(struct lpm_stats *stats, uint32_t index,
+ bool success)
+{
+ if (IS_ERR_OR_NULL(stats))
+ return;
+
+ update_exit_stats(stats, index, success);
+
+ update_first_out_stats(stats);
+}
+EXPORT_SYMBOL(lpm_stats_cluster_exit);
+
+/**
+ * lpm_stats_cpu_enter() - API to communicate the lpm level a cpu
+ * is prepared to enter.
+ *
+ * @index: cpu's lpm level index.
+ *
+ * Function to communicate the low power mode level that the cpu is
+ * prepared to enter.
+ */
+void lpm_stats_cpu_enter(uint32_t index, uint64_t time)
+{
+ struct lpm_stats *stats = &(*this_cpu_ptr(&(cpu_stats)));
+
+ stats->sleep_time = time;
+
+ if (!stats->time_stats)
+ return;
+
+}
+EXPORT_SYMBOL(lpm_stats_cpu_enter);
+
+/**
+ * lpm_stats_cpu_exit() - API to communicate the lpm level that the cpu exited.
+ *
+ * @index: cpu's lpm level index.
+ * @success: Success/Failure of the low power mode execution.
+ *
+ * Function to communicate the low power mode level that the cpu exited.
+ */
+void lpm_stats_cpu_exit(uint32_t index, uint64_t time, bool success)
+{
+ struct lpm_stats *stats = &(*this_cpu_ptr(&(cpu_stats)));
+
+ if (!stats->time_stats)
+ return;
+
+ stats->sleep_time = time - stats->sleep_time;
+
+ update_exit_stats(stats, index, success);
+}
+EXPORT_SYMBOL(lpm_stats_cpu_exit);
+
+/**
+ * lpm_stats_suspend_enter() - API to communicate system entering suspend.
+ *
+ * Function to communicate that the system is ready to enter suspend.
+ */
+void lpm_stats_suspend_enter(void)
+{
+ struct timespec ts;
+
+ getnstimeofday(&ts);
+ suspend_time_stats.enter_time = timespec_to_ns(&ts);
+}
+EXPORT_SYMBOL(lpm_stats_suspend_enter);
+
+/**
+ * lpm_stats_suspend_exit() - API to communicate system exiting suspend.
+ *
+ * Function to communicate that the system exited suspend.
+ */
+void lpm_stats_suspend_exit(void)
+{
+ struct timespec ts;
+ uint64_t exit_time = 0;
+
+ getnstimeofday(&ts);
+ exit_time = timespec_to_ns(&ts) - suspend_time_stats.enter_time;
+ update_level_stats(&suspend_time_stats, exit_time, true);
+}
+EXPORT_SYMBOL(lpm_stats_suspend_exit);
diff --git a/drivers/power/qcom/msm-core.c b/drivers/power/qcom/msm-core.c
new file mode 100644
index 000000000000..825c27e7a4c1
--- /dev/null
+++ b/drivers/power/qcom/msm-core.c
@@ -0,0 +1,1133 @@
+/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kthread.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/msm-core-interface.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/pm_opp.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/uio_driver.h>
+#include <asm/smp_plat.h>
+#include <stdbool.h>
+#define CREATE_TRACE_POINTS
+#include <trace/events/trace_msm_core.h>
+
+#define TEMP_BASE_POINT 35
+#define TEMP_MAX_POINT 95
+#define CPU_HOTPLUG_LIMIT 80
+#define CPU_BIT_MASK(cpu) BIT(cpu)
+#define DEFAULT_TEMP 40
+#define DEFAULT_LOW_HYST_TEMP 10
+#define DEFAULT_HIGH_HYST_TEMP 5
+#define CLUSTER_OFFSET_FOR_MPIDR 8
+#define MAX_CORES_PER_CLUSTER 4
+#define MAX_NUM_OF_CLUSTERS 2
+#define NUM_OF_CORNERS 10
+#define DEFAULT_SCALING_FACTOR 1
+
+#define ALLOCATE_2D_ARRAY(type)\
+static type **allocate_2d_array_##type(int idx)\
+{\
+ int i;\
+ type **ptr = NULL;\
+ if (!idx) \
+ return ERR_PTR(-EINVAL);\
+ ptr = kzalloc(sizeof(*ptr) * TEMP_DATA_POINTS, \
+ GFP_KERNEL);\
+ if (!ptr) { \
+ return ERR_PTR(-ENOMEM); \
+ } \
+ for (i = 0; i < TEMP_DATA_POINTS; i++) { \
+ ptr[i] = kzalloc(sizeof(*ptr[i]) * \
+ idx, GFP_KERNEL);\
+ if (!ptr[i]) {\
+ goto done;\
+ } \
+ } \
+ return ptr;\
+done:\
+ for (i = 0; i < TEMP_DATA_POINTS; i++) \
+ kfree(ptr[i]);\
+ kfree(ptr);\
+ return ERR_PTR(-ENOMEM);\
+}
+
+struct cpu_activity_info {
+ int cpu;
+ int mpidr;
+ long temp;
+ int sensor_id;
+ struct sensor_threshold hi_threshold;
+ struct sensor_threshold low_threshold;
+ struct cpu_static_info *sp;
+};
+
+struct cpu_static_info {
+ uint32_t **power;
+ cpumask_t mask;
+ struct cpufreq_frequency_table *table;
+ uint32_t *voltage;
+ uint32_t num_of_freqs;
+};
+
+static DEFINE_MUTEX(policy_update_mutex);
+static DEFINE_MUTEX(kthread_update_mutex);
+static DEFINE_SPINLOCK(update_lock);
+static struct delayed_work sampling_work;
+static struct completion sampling_completion;
+static struct task_struct *sampling_task;
+static int low_hyst_temp;
+static int high_hyst_temp;
+static struct platform_device *msm_core_pdev;
+static struct cpu_activity_info activity[NR_CPUS];
+DEFINE_PER_CPU(struct cpu_pstate_pwr *, ptable);
+static struct cpu_pwr_stats cpu_stats[NR_CPUS];
+static uint32_t scaling_factor;
+ALLOCATE_2D_ARRAY(uint32_t);
+
+static int poll_ms;
+module_param_named(polling_interval, poll_ms, int,
+ S_IRUGO | S_IWUSR | S_IWGRP);
+
+static int disabled;
+module_param_named(disabled, disabled, int,
+ S_IRUGO | S_IWUSR | S_IWGRP);
+static bool in_suspend;
+static bool activate_power_table;
+static int max_throttling_temp = 80; /* in C */
+module_param_named(throttling_temp, max_throttling_temp, int,
+ S_IRUGO | S_IWUSR | S_IWGRP);
+
+/*
+ * Cannot be called from an interrupt context
+ */
+static void set_and_activate_threshold(uint32_t sensor_id,
+ struct sensor_threshold *threshold)
+{
+ if (sensor_set_trip(sensor_id, threshold)) {
+ pr_err("%s: Error in setting trip %d\n",
+ KBUILD_MODNAME, threshold->trip);
+ return;
+ }
+
+ if (sensor_activate_trip(sensor_id, threshold, true)) {
+ sensor_cancel_trip(sensor_id, threshold);
+ pr_err("%s: Error in enabling trip %d\n",
+ KBUILD_MODNAME, threshold->trip);
+ return;
+ }
+}
+
+static void set_threshold(struct cpu_activity_info *cpu_node)
+{
+ if (cpu_node->sensor_id < 0)
+ return;
+
+ /*
+ * Before operating on the threshold structure which is used by
+ * thermal core ensure that the sensor is disabled to prevent
+ * incorrect operations on the sensor list maintained by thermal code.
+ */
+ sensor_activate_trip(cpu_node->sensor_id,
+ &cpu_node->hi_threshold, false);
+ sensor_activate_trip(cpu_node->sensor_id,
+ &cpu_node->low_threshold, false);
+
+ cpu_node->hi_threshold.temp = (cpu_node->temp + high_hyst_temp) *
+ scaling_factor;
+ cpu_node->low_threshold.temp = (cpu_node->temp - low_hyst_temp) *
+ scaling_factor;
+
+ /*
+ * Set the threshold only if we are below the hotplug limit
+ * Adding more work at this high temperature range, seems to
+ * fail hotplug notifications.
+ */
+ if (cpu_node->hi_threshold.temp < (CPU_HOTPLUG_LIMIT * scaling_factor))
+ set_and_activate_threshold(cpu_node->sensor_id,
+ &cpu_node->hi_threshold);
+
+ set_and_activate_threshold(cpu_node->sensor_id,
+ &cpu_node->low_threshold);
+}
+
+static void samplequeue_handle(struct work_struct *work)
+{
+ complete(&sampling_completion);
+}
+
+/* May be called from an interrupt context */
+static void core_temp_notify(enum thermal_trip_type type,
+ int temp, void *data)
+{
+ struct cpu_activity_info *cpu_node =
+ (struct cpu_activity_info *) data;
+
+ temp /= scaling_factor;
+
+ trace_temp_notification(cpu_node->sensor_id,
+ type, temp, cpu_node->temp);
+
+ cpu_node->temp = temp;
+
+ complete(&sampling_completion);
+}
+
+static void repopulate_stats(int cpu)
+{
+ int i;
+ struct cpu_activity_info *cpu_node = &activity[cpu];
+ int temp_point;
+ struct cpu_pstate_pwr *pt = per_cpu(ptable, cpu);
+
+ if (!pt)
+ return;
+
+ if (cpu_node->temp < TEMP_BASE_POINT)
+ temp_point = 0;
+ else if (cpu_node->temp > TEMP_MAX_POINT)
+ temp_point = TEMP_DATA_POINTS - 1;
+ else
+ temp_point = (cpu_node->temp - TEMP_BASE_POINT) / 5;
+
+ cpu_stats[cpu].temp = cpu_node->temp;
+ for (i = 0; i < cpu_node->sp->num_of_freqs; i++)
+ pt[i].power = cpu_node->sp->power[temp_point][i];
+
+ trace_cpu_stats(cpu, cpu_stats[cpu].temp, pt[0].power,
+ pt[cpu_node->sp->num_of_freqs-1].power);
+};
+
+void trigger_cpu_pwr_stats_calc(void)
+{
+ int cpu;
+ static long prev_temp[NR_CPUS];
+ struct cpu_activity_info *cpu_node;
+ int temp;
+
+ if (disabled)
+ return;
+
+ spin_lock(&update_lock);
+
+ for_each_online_cpu(cpu) {
+ cpu_node = &activity[cpu];
+ if (cpu_node->sensor_id < 0)
+ continue;
+
+ if (cpu_node->temp == prev_temp[cpu]) {
+ sensor_get_temp(cpu_node->sensor_id, &temp);
+ cpu_node->temp = temp / scaling_factor;
+ }
+
+ prev_temp[cpu] = cpu_node->temp;
+
+ /*
+ * Do not populate/update stats before policy and ptable have
+ * been updated.
+ */
+ if (activate_power_table && cpu_stats[cpu].ptable
+ && cpu_node->sp->table)
+ repopulate_stats(cpu);
+ }
+ spin_unlock(&update_lock);
+}
+EXPORT_SYMBOL(trigger_cpu_pwr_stats_calc);
+
+void set_cpu_throttled(cpumask_t *mask, bool throttling)
+{
+ int cpu;
+
+ if (!mask)
+ return;
+
+ spin_lock(&update_lock);
+ for_each_cpu(cpu, mask)
+ cpu_stats[cpu].throttling = throttling;
+ spin_unlock(&update_lock);
+}
+EXPORT_SYMBOL(set_cpu_throttled);
+
+static void update_related_freq_table(struct cpufreq_policy *policy)
+{
+ int cpu, num_of_freqs;
+ struct cpufreq_frequency_table *table;
+
+ table = cpufreq_frequency_get_table(policy->cpu);
+ if (!table) {
+ pr_err("Couldn't get freq table for cpu%d\n",
+ policy->cpu);
+ return;
+ }
+
+ for (num_of_freqs = 0; table[num_of_freqs].frequency !=
+ CPUFREQ_TABLE_END;)
+ num_of_freqs++;
+
+ /*
+ * Synchronous cores within cluster have the same
+ * policy. Since these cores do not have the cpufreq
+ * table initialized for all of them, copy the same
+ * table to all the related cpus.
+ */
+ for_each_cpu(cpu, policy->related_cpus) {
+ activity[cpu].sp->table = table;
+ activity[cpu].sp->num_of_freqs = num_of_freqs;
+ }
+}
+
+static __ref int do_sampling(void *data)
+{
+ int cpu;
+ struct cpu_activity_info *cpu_node;
+ static int prev_temp[NR_CPUS];
+
+ while (!kthread_should_stop()) {
+ wait_for_completion(&sampling_completion);
+ cancel_delayed_work(&sampling_work);
+
+ mutex_lock(&kthread_update_mutex);
+ if (in_suspend)
+ goto unlock;
+
+ trigger_cpu_pwr_stats_calc();
+
+ for_each_online_cpu(cpu) {
+ cpu_node = &activity[cpu];
+ if (prev_temp[cpu] != cpu_node->temp) {
+ prev_temp[cpu] = cpu_node->temp;
+ set_threshold(cpu_node);
+ trace_temp_threshold(cpu, cpu_node->temp,
+ cpu_node->hi_threshold.temp /
+ scaling_factor,
+ cpu_node->low_threshold.temp /
+ scaling_factor);
+ }
+ }
+ if (!poll_ms)
+ goto unlock;
+
+ schedule_delayed_work(&sampling_work,
+ msecs_to_jiffies(poll_ms));
+unlock:
+ mutex_unlock(&kthread_update_mutex);
+ }
+ return 0;
+}
+
+static void clear_static_power(struct cpu_static_info *sp)
+{
+ int i;
+
+ if (!sp)
+ return;
+
+ if (cpumask_first(&sp->mask) < num_possible_cpus())
+ return;
+
+ for (i = 0; i < TEMP_DATA_POINTS; i++)
+ kfree(sp->power[i]);
+ kfree(sp->power);
+ kfree(sp);
+}
+
+BLOCKING_NOTIFIER_HEAD(msm_core_stats_notifier_list);
+
+struct blocking_notifier_head *get_power_update_notifier(void)
+{
+ return &msm_core_stats_notifier_list;
+}
+
+int register_cpu_pwr_stats_ready_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&msm_core_stats_notifier_list,
+ nb);
+}
+
+static int update_userspace_power(struct sched_params __user *argp)
+{
+ int i;
+ int ret;
+ int cpu = -1;
+ struct cpu_activity_info *node;
+ struct cpu_static_info *sp, *clear_sp;
+ int cpumask, cluster, mpidr;
+ bool pdata_valid[NR_CPUS] = {0};
+
+ get_user(cpumask, &argp->cpumask);
+ get_user(cluster, &argp->cluster);
+ mpidr = cluster << 8;
+
+ pr_debug("%s: cpumask %d, cluster: %d\n", __func__, cpumask,
+ cluster);
+ for (i = 0; i < MAX_CORES_PER_CLUSTER; i++, cpumask >>= 1) {
+ if (!(cpumask & 0x01))
+ continue;
+
+ mpidr |= i;
+ for_each_possible_cpu(cpu) {
+ if (cpu_logical_map(cpu) == mpidr)
+ break;
+ }
+ }
+
+ if ((cpu < 0) || (cpu >= num_possible_cpus()))
+ return -EINVAL;
+
+ node = &activity[cpu];
+ /* Allocate new memory to copy cpumask specific power
+ * information.
+ */
+ sp = kzalloc(sizeof(*sp), GFP_KERNEL);
+ if (!sp)
+ return -ENOMEM;
+
+
+ sp->power = allocate_2d_array_uint32_t(node->sp->num_of_freqs);
+ if (IS_ERR_OR_NULL(sp->power)) {
+ ret = PTR_ERR(sp->power);
+ kfree(sp);
+ return ret;
+ }
+ sp->num_of_freqs = node->sp->num_of_freqs;
+ sp->voltage = node->sp->voltage;
+ sp->table = node->sp->table;
+
+ for (i = 0; i < TEMP_DATA_POINTS; i++) {
+ ret = copy_from_user(sp->power[i], &argp->power[i][0],
+ sizeof(sp->power[i][0]) * node->sp->num_of_freqs);
+ if (ret)
+ goto failed;
+ }
+
+ /* Copy the same power values for all the cpus in the cpumask
+ * argp->cpumask within the cluster (argp->cluster)
+ */
+ get_user(cpumask, &argp->cpumask);
+ spin_lock(&update_lock);
+ for (i = 0; i < MAX_CORES_PER_CLUSTER; i++, cpumask >>= 1) {
+ if (!(cpumask & 0x01))
+ continue;
+ mpidr = (cluster << CLUSTER_OFFSET_FOR_MPIDR);
+ mpidr |= i;
+ for_each_possible_cpu(cpu) {
+ if (!(cpu_logical_map(cpu) == mpidr))
+ continue;
+
+ node = &activity[cpu];
+ clear_sp = node->sp;
+ node->sp = sp;
+ cpumask_set_cpu(cpu, &sp->mask);
+ if (clear_sp) {
+ cpumask_clear_cpu(cpu, &clear_sp->mask);
+ clear_static_power(clear_sp);
+ }
+ cpu_stats[cpu].ptable = per_cpu(ptable, cpu);
+ repopulate_stats(cpu);
+ pdata_valid[cpu] = true;
+ }
+ }
+ spin_unlock(&update_lock);
+
+ for_each_possible_cpu(cpu) {
+ if (!pdata_valid[cpu])
+ continue;
+
+ blocking_notifier_call_chain(
+ &msm_core_stats_notifier_list, cpu, NULL);
+ }
+
+ activate_power_table = true;
+ return 0;
+
+failed:
+ for (i = 0; i < TEMP_DATA_POINTS; i++)
+ kfree(sp->power[i]);
+ kfree(sp->power);
+ kfree(sp);
+ return ret;
+}
+
+static long msm_core_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ long ret = 0;
+ struct cpu_activity_info *node = NULL;
+ struct sched_params __user *argp = (struct sched_params __user *)arg;
+ int i, cpu = num_possible_cpus();
+ int mpidr, cluster, cpumask;
+
+ if (!argp)
+ return -EINVAL;
+
+ get_user(cluster, &argp->cluster);
+ mpidr = (cluster << (MAX_CORES_PER_CLUSTER *
+ MAX_NUM_OF_CLUSTERS));
+ get_user(cpumask, &argp->cpumask);
+
+ switch (cmd) {
+ case EA_LEAKAGE:
+ ret = update_userspace_power(argp);
+ if (ret)
+ pr_err("Userspace power update failed with %ld\n", ret);
+ break;
+ case EA_VOLT:
+ for (i = 0; cpumask > 0; i++, cpumask >>= 1) {
+ for_each_possible_cpu(cpu) {
+ if (cpu_logical_map(cpu) == (mpidr | i))
+ break;
+ }
+ }
+ if (cpu >= num_possible_cpus())
+ break;
+
+ mutex_lock(&policy_update_mutex);
+ node = &activity[cpu];
+ if (!node->sp->table) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+ ret = copy_to_user((void __user *)&argp->voltage[0],
+ node->sp->voltage,
+ sizeof(uint32_t) * node->sp->num_of_freqs);
+ if (ret)
+ break;
+ for (i = 0; i < node->sp->num_of_freqs; i++) {
+ ret = copy_to_user((void __user *)&argp->freq[i],
+ &node->sp->table[i].frequency,
+ sizeof(uint32_t));
+ if (ret)
+ break;
+ }
+unlock:
+ mutex_unlock(&policy_update_mutex);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_COMPAT
+static long msm_core_compat_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ arg = (unsigned long)compat_ptr(arg);
+ return msm_core_ioctl(file, cmd, arg);
+}
+#endif
+
+static int msm_core_open(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static int msm_core_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static inline void init_sens_threshold(struct sensor_threshold *threshold,
+ enum thermal_trip_type trip, long temp,
+ void *data)
+{
+ threshold->trip = trip;
+ threshold->temp = temp;
+ threshold->data = data;
+ threshold->notify = (void *)core_temp_notify;
+}
+
+static int msm_core_stats_init(struct device *dev, int cpu)
+{
+ int i;
+ struct cpu_activity_info *cpu_node;
+ struct cpu_pstate_pwr *pstate = NULL;
+
+ cpu_node = &activity[cpu];
+ cpu_stats[cpu].cpu = cpu;
+ cpu_stats[cpu].temp = cpu_node->temp;
+ cpu_stats[cpu].throttling = false;
+
+ cpu_stats[cpu].len = cpu_node->sp->num_of_freqs;
+ pstate = devm_kzalloc(dev,
+ sizeof(*pstate) * cpu_node->sp->num_of_freqs,
+ GFP_KERNEL);
+ if (!pstate)
+ return -ENOMEM;
+
+ for (i = 0; i < cpu_node->sp->num_of_freqs; i++)
+ pstate[i].freq = cpu_node->sp->table[i].frequency;
+
+ per_cpu(ptable, cpu) = pstate;
+
+ return 0;
+}
+
+static int msm_core_task_init(struct device *dev)
+{
+ init_completion(&sampling_completion);
+ sampling_task = kthread_run(do_sampling, NULL, "msm-core:sampling");
+ if (IS_ERR(sampling_task)) {
+ pr_err("Failed to create do_sampling err: %ld\n",
+ PTR_ERR(sampling_task));
+ return PTR_ERR(sampling_task);
+ }
+ return 0;
+}
+
+struct cpu_pwr_stats *get_cpu_pwr_stats(void)
+{
+ return cpu_stats;
+}
+EXPORT_SYMBOL(get_cpu_pwr_stats);
+
+static int msm_get_power_values(int cpu, struct cpu_static_info *sp)
+{
+ int i = 0, j;
+ int ret = 0;
+ uint64_t power;
+
+ /* Calculate dynamic power spent for every frequency using formula:
+ * Power = V * V * f
+ * where V = voltage for frequency
+ * f = frequency
+ * */
+ sp->power = allocate_2d_array_uint32_t(sp->num_of_freqs);
+ if (IS_ERR_OR_NULL(sp->power))
+ return PTR_ERR(sp->power);
+
+ for (i = 0; i < TEMP_DATA_POINTS; i++) {
+ for (j = 0; j < sp->num_of_freqs; j++) {
+ power = sp->voltage[j] *
+ sp->table[j].frequency;
+ do_div(power, 1000);
+ do_div(power, 1000);
+ power *= sp->voltage[j];
+ do_div(power, 1000);
+ sp->power[i][j] = power;
+ }
+ }
+ return ret;
+}
+
+static int msm_get_voltage_levels(struct device *dev, int cpu,
+ struct cpu_static_info *sp)
+{
+ unsigned int *voltage;
+ int i;
+ int corner;
+ struct dev_pm_opp *opp;
+ struct device *cpu_dev = get_cpu_device(cpu);
+ /*
+ * Convert cpr corner voltage to average voltage of both
+ * a53 and a57 votlage value
+ */
+ int average_voltage[NUM_OF_CORNERS] = {0, 746, 841, 843, 940, 953, 976,
+ 1024, 1090, 1100};
+
+ if (!cpu_dev)
+ return -ENODEV;
+
+ voltage = devm_kzalloc(dev,
+ sizeof(*voltage) * sp->num_of_freqs, GFP_KERNEL);
+
+ if (!voltage)
+ return -ENOMEM;
+
+ rcu_read_lock();
+ for (i = 0; i < sp->num_of_freqs; i++) {
+ opp = dev_pm_opp_find_freq_exact(cpu_dev,
+ sp->table[i].frequency * 1000, true);
+ corner = dev_pm_opp_get_voltage(opp);
+
+ if (corner > 400000)
+ voltage[i] = corner / 1000;
+ else if (corner > 0 && corner < ARRAY_SIZE(average_voltage))
+ voltage[i] = average_voltage[corner];
+ else
+ voltage[i]
+ = average_voltage[ARRAY_SIZE(average_voltage) - 1];
+ }
+ rcu_read_unlock();
+
+ sp->voltage = voltage;
+ return 0;
+}
+
+static int msm_core_dyn_pwr_init(struct platform_device *pdev,
+ int cpu)
+{
+ int ret = 0;
+
+ if (!activity[cpu].sp->table)
+ return 0;
+
+ ret = msm_get_voltage_levels(&pdev->dev, cpu, activity[cpu].sp);
+ if (ret)
+ return ret;
+
+ ret = msm_get_power_values(cpu, activity[cpu].sp);
+
+ return ret;
+}
+
+static int msm_core_tsens_init(struct device_node *node, int cpu)
+{
+ int ret = 0;
+ char *key = NULL;
+ struct device_node *phandle;
+ const char *sensor_type = NULL;
+ struct cpu_activity_info *cpu_node = &activity[cpu];
+ int temp;
+
+ if (!node)
+ return -ENODEV;
+
+ key = "sensor";
+ phandle = of_parse_phandle(node, key, 0);
+ if (!phandle) {
+ pr_info("%s: No sensor mapping found for the core\n",
+ __func__);
+ /* Do not treat this as error as some targets might have
+ * temperature notification only in userspace.
+ * Use default temperature for the core. Userspace might
+ * update the temperature once it is up.
+ */
+ cpu_node->sensor_id = -ENODEV;
+ cpu_node->temp = DEFAULT_TEMP;
+ return 0;
+ }
+
+ key = "qcom,sensor-name";
+ ret = of_property_read_string(phandle, key,
+ &sensor_type);
+ if (ret) {
+ pr_err("%s: Cannot read tsens id\n", __func__);
+ return ret;
+ }
+
+ cpu_node->sensor_id = sensor_get_id((char *)sensor_type);
+ if (cpu_node->sensor_id < 0)
+ return cpu_node->sensor_id;
+
+ key = "qcom,scaling-factor";
+ ret = of_property_read_u32(phandle, key,
+ &scaling_factor);
+ if (ret) {
+ pr_info("%s: Cannot read tsens scaling factor\n", __func__);
+ scaling_factor = DEFAULT_SCALING_FACTOR;
+ }
+
+ ret = sensor_get_temp(cpu_node->sensor_id, &temp);
+ if (ret)
+ return ret;
+
+ cpu_node->temp = temp / scaling_factor;
+
+ init_sens_threshold(&cpu_node->hi_threshold,
+ THERMAL_TRIP_CONFIGURABLE_HI,
+ (cpu_node->temp + high_hyst_temp) * scaling_factor,
+ (void *)cpu_node);
+ init_sens_threshold(&cpu_node->low_threshold,
+ THERMAL_TRIP_CONFIGURABLE_LOW,
+ (cpu_node->temp - low_hyst_temp) * scaling_factor,
+ (void *)cpu_node);
+
+ return ret;
+}
+
+static int msm_core_mpidr_init(struct device_node *phandle)
+{
+ int ret = 0;
+ char *key = NULL;
+ int mpidr;
+
+ key = "reg";
+ ret = of_property_read_u32(phandle, key,
+ &mpidr);
+ if (ret) {
+ pr_err("%s: Cannot read mpidr\n", __func__);
+ return ret;
+ }
+ return mpidr;
+}
+
+static int msm_core_cpu_policy_handler(struct notifier_block *nb,
+ unsigned long val, void *data)
+{
+ struct cpufreq_policy *policy = data;
+ struct cpu_activity_info *cpu_info = &activity[policy->cpu];
+ int cpu;
+ int ret;
+
+ if (cpu_info->sp->table)
+ return NOTIFY_OK;
+
+ switch (val) {
+ case CPUFREQ_CREATE_POLICY:
+ mutex_lock(&policy_update_mutex);
+ update_related_freq_table(policy);
+
+ for_each_cpu(cpu, policy->related_cpus) {
+ ret = msm_core_dyn_pwr_init(msm_core_pdev, cpu);
+ if (ret)
+ pr_debug("voltage-pwr table update failed\n");
+
+ ret = msm_core_stats_init(&msm_core_pdev->dev, cpu);
+ if (ret)
+ pr_debug("Stats table update failed\n");
+ }
+ mutex_unlock(&policy_update_mutex);
+ break;
+ default:
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+struct notifier_block cpu_policy = {
+ .notifier_call = msm_core_cpu_policy_handler
+};
+
+static int system_suspend_handler(struct notifier_block *nb,
+ unsigned long val, void *data)
+{
+ int cpu;
+
+ mutex_lock(&kthread_update_mutex);
+ switch (val) {
+ case PM_POST_HIBERNATION:
+ case PM_POST_SUSPEND:
+ case PM_POST_RESTORE:
+ /*
+ * Set completion event to read temperature and repopulate
+ * stats
+ */
+ in_suspend = 0;
+ complete(&sampling_completion);
+ break;
+ case PM_HIBERNATION_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ /*
+ * cancel delayed work to be able to restart immediately
+ * after system resume
+ */
+ in_suspend = 1;
+ cancel_delayed_work(&sampling_work);
+ /*
+ * cancel TSENS interrupts as we do not want to wake up from
+ * suspend to take care of repopulate stats while the system is
+ * in suspend
+ */
+ for_each_possible_cpu(cpu) {
+ if (activity[cpu].sensor_id < 0)
+ continue;
+
+ sensor_activate_trip(activity[cpu].sensor_id,
+ &activity[cpu].hi_threshold, false);
+ sensor_activate_trip(activity[cpu].sensor_id,
+ &activity[cpu].low_threshold, false);
+ }
+ break;
+ default:
+ break;
+ }
+ mutex_unlock(&kthread_update_mutex);
+
+ return NOTIFY_OK;
+}
+
+static int msm_core_freq_init(void)
+{
+ int cpu;
+ struct cpufreq_policy *policy;
+
+ for_each_possible_cpu(cpu) {
+ activity[cpu].sp = kzalloc(sizeof(*(activity[cpu].sp)),
+ GFP_KERNEL);
+ if (!activity[cpu].sp)
+ return -ENOMEM;
+ }
+
+ for_each_online_cpu(cpu) {
+ if (activity[cpu].sp->table)
+ continue;
+
+ policy = cpufreq_cpu_get(cpu);
+ if (!policy)
+ continue;
+
+ update_related_freq_table(policy);
+ cpufreq_cpu_put(policy);
+ }
+
+ return 0;
+}
+
+static int msm_core_params_init(struct platform_device *pdev)
+{
+ int ret = 0;
+ unsigned long cpu = 0;
+ struct device_node *child_node = NULL;
+ struct device_node *ea_node = NULL;
+ char *key = NULL;
+ int mpidr;
+
+ for_each_possible_cpu(cpu) {
+ child_node = of_get_cpu_node(cpu, NULL);
+
+ if (!child_node)
+ continue;
+
+ mpidr = msm_core_mpidr_init(child_node);
+ if (mpidr < 0)
+ return mpidr;
+
+ if (cpu >= num_possible_cpus())
+ continue;
+
+ activity[cpu].mpidr = mpidr;
+
+ key = "qcom,ea";
+ ea_node = of_parse_phandle(child_node, key, 0);
+ if (!ea_node) {
+ pr_err("%s Couldn't find the ea_node for cpu%lu\n",
+ __func__, cpu);
+ return -ENODEV;
+ }
+
+ ret = msm_core_tsens_init(ea_node, cpu);
+ if (ret)
+ return ret;
+
+ if (!activity[cpu].sp->table)
+ continue;
+
+ ret = msm_core_dyn_pwr_init(msm_core_pdev, cpu);
+ if (ret)
+ pr_debug("voltage-pwr table update failed\n");
+
+ ret = msm_core_stats_init(&msm_core_pdev->dev, cpu);
+ if (ret)
+ pr_debug("Stats table update failed\n");
+ }
+
+ return 0;
+}
+
+static const struct file_operations msm_core_ops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = msm_core_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = msm_core_compat_ioctl,
+#endif
+ .open = msm_core_open,
+ .release = msm_core_release,
+};
+
+static struct miscdevice msm_core_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "pta",
+ .fops = &msm_core_ops
+};
+
+static void free_dyn_memory(void)
+{
+ int i, cpu;
+
+ for_each_possible_cpu(cpu) {
+ if (activity[cpu].sp) {
+ for (i = 0; i < TEMP_DATA_POINTS; i++) {
+ if (!activity[cpu].sp->power)
+ break;
+
+ kfree(activity[cpu].sp->power[i]);
+ }
+ }
+ kfree(activity[cpu].sp);
+ }
+}
+
+static int uio_init(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct uio_info *info = NULL;
+ struct resource *clnt_res = NULL;
+ u32 ea_mem_size = 0;
+ phys_addr_t ea_mem_pyhsical = 0;
+
+ clnt_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!clnt_res) {
+ pr_err("resource not found\n");
+ return -ENODEV;
+ }
+
+ info = devm_kzalloc(&pdev->dev, sizeof(struct uio_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ ea_mem_size = resource_size(clnt_res);
+ ea_mem_pyhsical = clnt_res->start;
+
+ if (ea_mem_size == 0) {
+ pr_err("msm-core: memory size is zero");
+ return -EINVAL;
+ }
+
+ /* Setup device */
+ info->name = clnt_res->name;
+ info->version = "1.0";
+ info->mem[0].addr = ea_mem_pyhsical;
+ info->mem[0].size = ea_mem_size;
+ info->mem[0].memtype = UIO_MEM_PHYS;
+
+ ret = uio_register_device(&pdev->dev, info);
+ if (ret) {
+ pr_err("uio register failed ret=%d", ret);
+ return ret;
+ }
+ dev_set_drvdata(&pdev->dev, info);
+
+ return 0;
+}
+
+static int msm_core_dev_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ char *key = NULL;
+ struct device_node *node;
+ int cpu;
+ struct uio_info *info;
+
+ if (!pdev)
+ return -ENODEV;
+
+ msm_core_pdev = pdev;
+ node = pdev->dev.of_node;
+ if (!node)
+ return -ENODEV;
+
+ key = "qcom,low-hyst-temp";
+ ret = of_property_read_u32(node, key, &low_hyst_temp);
+ if (ret)
+ low_hyst_temp = DEFAULT_LOW_HYST_TEMP;
+
+ key = "qcom,high-hyst-temp";
+ ret = of_property_read_u32(node, key, &high_hyst_temp);
+ if (ret)
+ high_hyst_temp = DEFAULT_HIGH_HYST_TEMP;
+
+ key = "qcom,polling-interval";
+ ret = of_property_read_u32(node, key, &poll_ms);
+ if (ret)
+ pr_info("msm-core initialized without polling period\n");
+
+ key = "qcom,throttling-temp";
+ ret = of_property_read_u32(node, key, &max_throttling_temp);
+
+ ret = uio_init(pdev);
+ if (ret)
+ return ret;
+
+ ret = msm_core_freq_init();
+ if (ret)
+ goto failed;
+
+ ret = misc_register(&msm_core_device);
+ if (ret) {
+ pr_err("%s: Error registering device %d\n", __func__, ret);
+ goto failed;
+ }
+
+ ret = msm_core_params_init(pdev);
+ if (ret)
+ goto failed;
+
+ INIT_DEFERRABLE_WORK(&sampling_work, samplequeue_handle);
+ ret = msm_core_task_init(&pdev->dev);
+ if (ret)
+ goto failed;
+
+ for_each_possible_cpu(cpu)
+ set_threshold(&activity[cpu]);
+
+ schedule_delayed_work(&sampling_work, msecs_to_jiffies(0));
+ cpufreq_register_notifier(&cpu_policy, CPUFREQ_POLICY_NOTIFIER);
+ pm_notifier(system_suspend_handler, 0);
+ return 0;
+failed:
+ info = dev_get_drvdata(&pdev->dev);
+ uio_unregister_device(info);
+ free_dyn_memory();
+ return ret;
+}
+
+static int msm_core_remove(struct platform_device *pdev)
+{
+ int cpu;
+ struct uio_info *info = dev_get_drvdata(&pdev->dev);
+
+ uio_unregister_device(info);
+
+ for_each_possible_cpu(cpu) {
+ if (activity[cpu].sensor_id < 0)
+ continue;
+
+ sensor_cancel_trip(activity[cpu].sensor_id,
+ &activity[cpu].hi_threshold);
+ sensor_cancel_trip(activity[cpu].sensor_id,
+ &activity[cpu].low_threshold);
+ }
+ free_dyn_memory();
+ misc_deregister(&msm_core_device);
+ return 0;
+}
+
+static struct of_device_id msm_core_match_table[] = {
+ {.compatible = "qcom,apss-core-ea"},
+ {},
+};
+
+static struct platform_driver msm_core_driver = {
+ .probe = msm_core_dev_probe,
+ .driver = {
+ .name = "msm_core",
+ .owner = THIS_MODULE,
+ .of_match_table = msm_core_match_table,
+ },
+ .remove = msm_core_remove,
+};
+
+static int __init msm_core_init(void)
+{
+ return platform_driver_register(&msm_core_driver);
+}
+late_initcall(msm_core_init);
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 1131cf75acc6..7569e35b59e0 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -80,12 +80,6 @@ config POWER_RESET_IMX
say N here or disable in dts to make sure pm_power_off never be
overwrote wrongly by this driver.
-config POWER_RESET_MSM
- bool "Qualcomm MSM power-off driver"
- depends on ARCH_QCOM
- help
- Power off and restart support for Qualcomm boards.
-
config POWER_RESET_LTC2952
bool "LTC2952 PowerPath power-off driver"
depends on OF_GPIO
@@ -93,6 +87,22 @@ config POWER_RESET_LTC2952
This driver supports an external powerdown trigger and board power
down via the LTC2952. Bindings are made in the device tree.
+config POWER_RESET_QCOM
+ bool "Qualcomm MSM power-off driver"
+ depends on ARCH_MSM || ARCH_QCOM
+ depends on POWER_RESET
+ help
+ Power off and restart support for Qualcomm boards.
+
+config QCOM_DLOAD_MODE
+ bool "Qualcomm download mode"
+ depends on POWER_RESET_QCOM
+ help
+ This makes the SoC enter download mode when it resets
+ due to a kernel panic. Note that this doesn't by itself
+ make the kernel reboot on a kernel panic - that must be
+ enabled via another mechanism.
+
config POWER_RESET_QNAP
bool "QNAP power-off driver"
depends on OF_GPIO && PLAT_ORION
@@ -173,5 +183,19 @@ config POWER_RESET_ZX
help
Reboot support for ZTE SoCs.
+config REBOOT_MODE
+ tristate
+
+config SYSCON_REBOOT_MODE
+ tristate "Generic SYSCON regmap reboot mode driver"
+ depends on OF
+ select REBOOT_MODE
+ select MFD_SYSCON
+ help
+ Say y here will enable reboot mode driver. This will
+ get reboot mode arguments and store it in SYSCON mapped
+ register, then the bootloader can read it to take different
+ action according to the mode.
+
endif
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 096fa67047f6..66568c4497a4 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -7,7 +7,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
obj-$(CONFIG_POWER_RESET_IMX) += imx-snvs-poweroff.o
-obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
+obj-$(CONFIG_POWER_RESET_QCOM) += msm-poweroff.o
obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o
obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
@@ -20,3 +20,5 @@ obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o
obj-$(CONFIG_POWER_RESET_SYSCON_POWEROFF) += syscon-poweroff.o
obj-$(CONFIG_POWER_RESET_RMOBILE) += rmobile-reset.o
obj-$(CONFIG_POWER_RESET_ZX) += zx-reboot.o
+obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
+obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c
index 4702efdfe466..209263ccced7 100644
--- a/drivers/power/reset/msm-poweroff.c
+++ b/drivers/power/reset/msm-poweroff.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -15,49 +15,657 @@
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
+
#include <linux/io.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/pm.h>
+#include <linux/delay.h>
+#include <linux/input/qpnp-power-on.h>
+#include <linux/of_address.h>
+
+#include <asm/cacheflush.h>
+#include <asm/system_misc.h>
+#include <asm/memory.h>
+
+#include <soc/qcom/scm.h>
+#include <soc/qcom/restart.h>
+#include <soc/qcom/watchdog.h>
+#include <soc/qcom/minidump.h>
+
+#define EMERGENCY_DLOAD_MAGIC1 0x322A4F99
+#define EMERGENCY_DLOAD_MAGIC2 0xC67E4350
+#define EMERGENCY_DLOAD_MAGIC3 0x77777777
+#define EMMC_DLOAD_TYPE 0x2
+#define SCM_IO_DISABLE_PMIC_ARBITER 1
+#define SCM_IO_DEASSERT_PS_HOLD 2
+#define SCM_WDOG_DEBUG_BOOT_PART 0x9
+#define SCM_DLOAD_FULLDUMP 0X10
+#define SCM_EDLOAD_MODE 0X01
+#define SCM_DLOAD_CMD 0x10
+#define SCM_DLOAD_MINIDUMP 0X20
+#define SCM_DLOAD_BOTHDUMPS (SCM_DLOAD_MINIDUMP | SCM_DLOAD_FULLDUMP)
+
+static int restart_mode;
+static void *restart_reason;
+static bool scm_pmic_arbiter_disable_supported;
+static bool scm_deassert_ps_hold_supported;
+/* Download mode master kill-switch */
static void __iomem *msm_ps_hold;
-static int do_msm_restart(struct notifier_block *nb, unsigned long action,
- void *data)
-{
- writel(0, msm_ps_hold);
- mdelay(10000);
+static phys_addr_t tcsr_boot_misc_detect;
+static void scm_disable_sdi(void);
+
+/* Runtime could be only changed value once.
+ * There is no API from TZ to re-enable the registers.
+ * So the SDI cannot be re-enabled when it already by-passed.
+*/
+
+#ifdef CONFIG_QCOM_DLOAD_MODE
+#define EDL_MODE_PROP "qcom,msm-imem-emergency_download_mode"
+#define DL_MODE_PROP "qcom,msm-imem-download_mode"
+#ifdef CONFIG_RANDOMIZE_BASE
+#define KASLR_OFFSET_PROP "qcom,msm-imem-kaslr_offset"
+#endif
+static int in_panic;
+static int dload_type = SCM_DLOAD_FULLDUMP;
+static int download_mode = 1;
+static struct kobject dload_kobj;
+static void *dload_mode_addr, *dload_type_addr;
+static bool dload_mode_enabled;
+static void *emergency_dload_mode_addr;
+#ifdef CONFIG_RANDOMIZE_BASE
+static void *kaslr_imem_addr;
+#endif
+static bool scm_dload_supported;
+
+static int dload_set(const char *val, struct kernel_param *kp);
+/* interface for exporting attributes */
+struct reset_attribute {
+ struct attribute attr;
+ ssize_t (*show)(struct kobject *kobj, struct attribute *attr,
+ char *buf);
+ size_t (*store)(struct kobject *kobj, struct attribute *attr,
+ const char *buf, size_t count);
+};
+#define to_reset_attr(_attr) \
+ container_of(_attr, struct reset_attribute, attr)
+#define RESET_ATTR(_name, _mode, _show, _store) \
+ static struct reset_attribute reset_attr_##_name = \
+ __ATTR(_name, _mode, _show, _store)
+
+module_param_call(download_mode, dload_set, param_get_int,
+ &download_mode, 0644);
+
+static int panic_prep_restart(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ in_panic = 1;
return NOTIFY_DONE;
}
-static struct notifier_block restart_nb = {
- .notifier_call = do_msm_restart,
- .priority = 128,
+static struct notifier_block panic_blk = {
+ .notifier_call = panic_prep_restart,
};
+int scm_set_dload_mode(int arg1, int arg2)
+{
+ struct scm_desc desc = {
+ .args[0] = arg1,
+ .args[1] = arg2,
+ .arginfo = SCM_ARGS(2),
+ };
+
+ if (!scm_dload_supported) {
+ if (tcsr_boot_misc_detect)
+ return scm_io_write(tcsr_boot_misc_detect, arg1);
+
+ return 0;
+ }
+
+ if (!is_scm_armv8())
+ return scm_call_atomic2(SCM_SVC_BOOT, SCM_DLOAD_CMD, arg1,
+ arg2);
+
+ return scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_BOOT, SCM_DLOAD_CMD),
+ &desc);
+}
+
+static void set_dload_mode(int on)
+{
+ int ret;
+
+ if (dload_mode_addr) {
+ __raw_writel(on ? 0xE47B337D : 0, dload_mode_addr);
+ __raw_writel(on ? 0xCE14091A : 0,
+ dload_mode_addr + sizeof(unsigned int));
+ mb();
+ }
+
+ ret = scm_set_dload_mode(on ? dload_type : 0, 0);
+ if (ret)
+ pr_err("Failed to set secure DLOAD mode: %d\n", ret);
+
+ dload_mode_enabled = on;
+}
+
+static bool get_dload_mode(void)
+{
+ return dload_mode_enabled;
+}
+
+static void enable_emergency_dload_mode(void)
+{
+ int ret;
+
+ if (emergency_dload_mode_addr) {
+ __raw_writel(EMERGENCY_DLOAD_MAGIC1,
+ emergency_dload_mode_addr);
+ __raw_writel(EMERGENCY_DLOAD_MAGIC2,
+ emergency_dload_mode_addr +
+ sizeof(unsigned int));
+ __raw_writel(EMERGENCY_DLOAD_MAGIC3,
+ emergency_dload_mode_addr +
+ (2 * sizeof(unsigned int)));
+
+ /* Need disable the pmic wdt, then the emergency dload mode
+ * will not auto reset. */
+ qpnp_pon_wd_config(0);
+ mb();
+ }
+
+ ret = scm_set_dload_mode(SCM_EDLOAD_MODE, 0);
+ if (ret)
+ pr_err("Failed to set secure EDLOAD mode: %d\n", ret);
+}
+
+static int dload_set(const char *val, struct kernel_param *kp)
+{
+ int ret;
+ int old_val = download_mode;
+
+ ret = param_set_int(val, kp);
+ if (ret)
+ return ret;
+
+ /* If download_mode is not zero or one, ignore. */
+ if (download_mode >> 1) {
+ download_mode = old_val;
+ return -EINVAL;
+ }
+
+ set_dload_mode(download_mode);
+
+ return 0;
+}
+#else
+static void set_dload_mode(int on)
+{
+ return;
+}
+
+static void enable_emergency_dload_mode(void)
+{
+ pr_err("dload mode is not enabled on target\n");
+}
+
+static bool get_dload_mode(void)
+{
+ return false;
+}
+#endif
+
+static void scm_disable_sdi(void)
+{
+ int ret;
+ struct scm_desc desc = {
+ .args[0] = 1,
+ .args[1] = 0,
+ .arginfo = SCM_ARGS(2),
+ };
+
+ /* Needed to bypass debug image on some chips */
+ if (!is_scm_armv8())
+ ret = scm_call_atomic2(SCM_SVC_BOOT,
+ SCM_WDOG_DEBUG_BOOT_PART, 1, 0);
+ else
+ ret = scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_BOOT,
+ SCM_WDOG_DEBUG_BOOT_PART), &desc);
+ if (ret)
+ pr_err("Failed to disable secure wdog debug: %d\n", ret);
+}
+
+void msm_set_restart_mode(int mode)
+{
+ restart_mode = mode;
+}
+EXPORT_SYMBOL(msm_set_restart_mode);
+
+/*
+ * Force the SPMI PMIC arbiter to shutdown so that no more SPMI transactions
+ * are sent from the MSM to the PMIC. This is required in order to avoid an
+ * SPMI lockup on certain PMIC chips if PS_HOLD is lowered in the middle of
+ * an SPMI transaction.
+ */
+static void halt_spmi_pmic_arbiter(void)
+{
+ struct scm_desc desc = {
+ .args[0] = 0,
+ .arginfo = SCM_ARGS(1),
+ };
+
+ if (scm_pmic_arbiter_disable_supported) {
+ pr_crit("Calling SCM to disable SPMI PMIC arbiter\n");
+ if (!is_scm_armv8())
+ scm_call_atomic1(SCM_SVC_PWR,
+ SCM_IO_DISABLE_PMIC_ARBITER, 0);
+ else
+ scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_PWR,
+ SCM_IO_DISABLE_PMIC_ARBITER), &desc);
+ }
+}
+
+static void msm_restart_prepare(const char *cmd)
+{
+ bool need_warm_reset = false;
+
+#ifdef CONFIG_QCOM_DLOAD_MODE
+
+ /* Write download mode flags if we're panic'ing
+ * Write download mode flags if restart_mode says so
+ * Kill download mode if master-kill switch is set
+ */
+
+ set_dload_mode(download_mode &&
+ (in_panic || restart_mode == RESTART_DLOAD));
+#endif
+
+ if (qpnp_pon_check_hard_reset_stored()) {
+ /* Set warm reset as true when device is in dload mode */
+ if (get_dload_mode() ||
+ ((cmd != NULL && cmd[0] != '\0') &&
+ !strcmp(cmd, "edl")))
+ need_warm_reset = true;
+ } else {
+ need_warm_reset = (get_dload_mode() ||
+ (cmd != NULL && cmd[0] != '\0'));
+ }
+
+ /* Hard reset the PMIC unless memory contents must be maintained. */
+ if (need_warm_reset) {
+ qpnp_pon_system_pwr_off(PON_POWER_OFF_WARM_RESET);
+ } else {
+ qpnp_pon_system_pwr_off(PON_POWER_OFF_HARD_RESET);
+ }
+
+ if (cmd != NULL) {
+ if (!strncmp(cmd, "bootloader", 10)) {
+ qpnp_pon_set_restart_reason(
+ PON_RESTART_REASON_BOOTLOADER);
+ __raw_writel(0x77665500, restart_reason);
+ } else if (!strncmp(cmd, "recovery", 8)) {
+ qpnp_pon_set_restart_reason(
+ PON_RESTART_REASON_RECOVERY);
+ __raw_writel(0x77665502, restart_reason);
+ } else if (!strcmp(cmd, "rtc")) {
+ qpnp_pon_set_restart_reason(
+ PON_RESTART_REASON_RTC);
+ __raw_writel(0x77665503, restart_reason);
+ } else if (!strcmp(cmd, "dm-verity device corrupted")) {
+ qpnp_pon_set_restart_reason(
+ PON_RESTART_REASON_DMVERITY_CORRUPTED);
+ __raw_writel(0x77665508, restart_reason);
+ } else if (!strcmp(cmd, "dm-verity enforcing")) {
+ qpnp_pon_set_restart_reason(
+ PON_RESTART_REASON_DMVERITY_ENFORCE);
+ __raw_writel(0x77665509, restart_reason);
+ } else if (!strcmp(cmd, "keys clear")) {
+ qpnp_pon_set_restart_reason(
+ PON_RESTART_REASON_KEYS_CLEAR);
+ __raw_writel(0x7766550a, restart_reason);
+ } else if (!strncmp(cmd, "oem-", 4)) {
+ unsigned long code;
+ int ret;
+ ret = kstrtoul(cmd + 4, 16, &code);
+ if (!ret)
+ __raw_writel(0x6f656d00 | (code & 0xff),
+ restart_reason);
+ } else if (!strncmp(cmd, "edl", 3)) {
+ enable_emergency_dload_mode();
+ } else {
+ __raw_writel(0x77665501, restart_reason);
+ }
+ }
+
+ flush_cache_all();
+
+ /*outer_flush_all is not supported by 64bit kernel*/
+#ifndef CONFIG_ARM64
+ outer_flush_all();
+#endif
+
+}
+
+/*
+ * Deassert PS_HOLD to signal the PMIC that we are ready to power down or reset.
+ * Do this by calling into the secure environment, if available, or by directly
+ * writing to a hardware register.
+ *
+ * This function should never return.
+ */
+static void deassert_ps_hold(void)
+{
+ struct scm_desc desc = {
+ .args[0] = 0,
+ .arginfo = SCM_ARGS(1),
+ };
+
+ if (scm_deassert_ps_hold_supported) {
+ /* This call will be available on ARMv8 only */
+ scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_PWR,
+ SCM_IO_DEASSERT_PS_HOLD), &desc);
+ }
+
+ /* Fall-through to the direct write in case the scm_call "returns" */
+ __raw_writel(0, msm_ps_hold);
+}
+
+static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd)
+{
+ pr_notice("Going down for restart now\n");
+
+ msm_restart_prepare(cmd);
+
+#ifdef CONFIG_QCOM_DLOAD_MODE
+ /*
+ * Trigger a watchdog bite here and if this fails,
+ * device will take the usual restart path.
+ */
+
+ if (WDOG_BITE_ON_PANIC && in_panic)
+ msm_trigger_wdog_bite();
+#endif
+
+ scm_disable_sdi();
+ halt_spmi_pmic_arbiter();
+ deassert_ps_hold();
+
+ mdelay(10000);
+}
+
static void do_msm_poweroff(void)
{
- /* TODO: Add poweroff capability */
- do_msm_restart(&restart_nb, 0, NULL);
+ pr_notice("Powering off the SoC\n");
+
+ set_dload_mode(0);
+ scm_disable_sdi();
+ qpnp_pon_system_pwr_off(PON_POWER_OFF_SHUTDOWN);
+
+ halt_spmi_pmic_arbiter();
+ deassert_ps_hold();
+
+ mdelay(10000);
+ pr_err("Powering off has failed\n");
+ return;
+}
+
+#ifdef CONFIG_QCOM_DLOAD_MODE
+static ssize_t attr_show(struct kobject *kobj, struct attribute *attr,
+ char *buf)
+{
+ struct reset_attribute *reset_attr = to_reset_attr(attr);
+ ssize_t ret = -EIO;
+
+ if (reset_attr->show)
+ ret = reset_attr->show(kobj, attr, buf);
+
+ return ret;
+}
+
+static ssize_t attr_store(struct kobject *kobj, struct attribute *attr,
+ const char *buf, size_t count)
+{
+ struct reset_attribute *reset_attr = to_reset_attr(attr);
+ ssize_t ret = -EIO;
+
+ if (reset_attr->store)
+ ret = reset_attr->store(kobj, attr, buf, count);
+
+ return ret;
+}
+
+static const struct sysfs_ops reset_sysfs_ops = {
+ .show = attr_show,
+ .store = attr_store,
+};
+
+static struct kobj_type reset_ktype = {
+ .sysfs_ops = &reset_sysfs_ops,
+};
+
+static ssize_t show_emmc_dload(struct kobject *kobj, struct attribute *attr,
+ char *buf)
+{
+ uint32_t read_val, show_val;
+
+ read_val = __raw_readl(dload_type_addr);
+ if (read_val == EMMC_DLOAD_TYPE)
+ show_val = 1;
+ else
+ show_val = 0;
+
+ return scnprintf(buf, sizeof(show_val), "%u\n", show_val);
}
+static size_t store_emmc_dload(struct kobject *kobj, struct attribute *attr,
+ const char *buf, size_t count)
+{
+ uint32_t enabled;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &enabled);
+ if (ret < 0)
+ return ret;
+
+ if (!((enabled == 0) || (enabled == 1)))
+ return -EINVAL;
+
+ if (enabled == 1)
+ __raw_writel(EMMC_DLOAD_TYPE, dload_type_addr);
+ else
+ __raw_writel(0, dload_type_addr);
+
+ return count;
+}
+
+#ifdef CONFIG_QCOM_MINIDUMP
+
+static DEFINE_MUTEX(tcsr_lock);
+
+static ssize_t show_dload_mode(struct kobject *kobj, struct attribute *attr,
+ char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "DLOAD dump type: %s\n",
+ (dload_type == SCM_DLOAD_BOTHDUMPS) ? "both" :
+ ((dload_type == SCM_DLOAD_MINIDUMP) ? "mini" : "full"));
+}
+
+static size_t store_dload_mode(struct kobject *kobj, struct attribute *attr,
+ const char *buf, size_t count)
+{
+ if (sysfs_streq(buf, "full")) {
+ dload_type = SCM_DLOAD_FULLDUMP;
+ } else if (sysfs_streq(buf, "mini")) {
+ if (!minidump_enabled) {
+ pr_err("Minidump is not enabled\n");
+ return -ENODEV;
+ }
+ dload_type = SCM_DLOAD_MINIDUMP;
+ } else if (sysfs_streq(buf, "both")) {
+ if (!minidump_enabled) {
+ pr_err("Minidump not enabled, setting fulldump only\n");
+ dload_type = SCM_DLOAD_FULLDUMP;
+ return count;
+ }
+ dload_type = SCM_DLOAD_BOTHDUMPS;
+ } else{
+ pr_err("Invalid Dump setup request..\n");
+ pr_err("Supported dumps:'full', 'mini', or 'both'\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&tcsr_lock);
+ /*Overwrite TCSR reg*/
+ set_dload_mode(dload_type);
+ mutex_unlock(&tcsr_lock);
+ return count;
+}
+RESET_ATTR(dload_mode, 0644, show_dload_mode, store_dload_mode);
+#endif
+
+RESET_ATTR(emmc_dload, 0644, show_emmc_dload, store_emmc_dload);
+
+static struct attribute *reset_attrs[] = {
+ &reset_attr_emmc_dload.attr,
+#ifdef CONFIG_QCOM_MINIDUMP
+ &reset_attr_dload_mode.attr,
+#endif
+ NULL
+};
+
+static struct attribute_group reset_attr_group = {
+ .attrs = reset_attrs,
+};
+#endif
+
static int msm_restart_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *mem;
+ struct device_node *np;
+ int ret = 0;
+
+#ifdef CONFIG_QCOM_DLOAD_MODE
+ if (scm_is_call_available(SCM_SVC_BOOT, SCM_DLOAD_CMD) > 0)
+ scm_dload_supported = true;
+
+ atomic_notifier_chain_register(&panic_notifier_list, &panic_blk);
+ np = of_find_compatible_node(NULL, NULL, DL_MODE_PROP);
+ if (!np) {
+ pr_err("unable to find DT imem DLOAD mode node\n");
+ } else {
+ dload_mode_addr = of_iomap(np, 0);
+ if (!dload_mode_addr)
+ pr_err("unable to map imem DLOAD offset\n");
+ }
- mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ np = of_find_compatible_node(NULL, NULL, EDL_MODE_PROP);
+ if (!np) {
+ pr_err("unable to find DT imem EDLOAD mode node\n");
+ } else {
+ emergency_dload_mode_addr = of_iomap(np, 0);
+ if (!emergency_dload_mode_addr)
+ pr_err("unable to map imem EDLOAD mode offset\n");
+ }
+
+#ifdef CONFIG_RANDOMIZE_BASE
+#define KASLR_OFFSET_BIT_MASK 0x00000000FFFFFFFF
+ np = of_find_compatible_node(NULL, NULL, KASLR_OFFSET_PROP);
+ if (!np) {
+ pr_err("unable to find DT imem KASLR_OFFSET node\n");
+ } else {
+ kaslr_imem_addr = of_iomap(np, 0);
+ if (!kaslr_imem_addr)
+ pr_err("unable to map imem KASLR offset\n");
+ }
+
+ if (kaslr_imem_addr) {
+ __raw_writel(0xdead4ead, kaslr_imem_addr);
+ __raw_writel(KASLR_OFFSET_BIT_MASK &
+ (kimage_vaddr - KIMAGE_VADDR), kaslr_imem_addr + 4);
+ __raw_writel(KASLR_OFFSET_BIT_MASK &
+ ((kimage_vaddr - KIMAGE_VADDR) >> 32),
+ kaslr_imem_addr + 8);
+ iounmap(kaslr_imem_addr);
+ }
+#endif
+
+ np = of_find_compatible_node(NULL, NULL,
+ "qcom,msm-imem-dload-type");
+ if (!np) {
+ pr_err("unable to find DT imem dload-type node\n");
+ goto skip_sysfs_create;
+ } else {
+ dload_type_addr = of_iomap(np, 0);
+ if (!dload_type_addr) {
+ pr_err("unable to map imem dload-type offset\n");
+ goto skip_sysfs_create;
+ }
+ }
+
+ ret = kobject_init_and_add(&dload_kobj, &reset_ktype,
+ kernel_kobj, "%s", "dload");
+ if (ret) {
+ pr_err("%s:Error in creation kobject_add\n", __func__);
+ kobject_put(&dload_kobj);
+ goto skip_sysfs_create;
+ }
+
+ ret = sysfs_create_group(&dload_kobj, &reset_attr_group);
+ if (ret) {
+ pr_err("%s:Error in creation sysfs_create_group\n", __func__);
+ kobject_del(&dload_kobj);
+ }
+skip_sysfs_create:
+#endif
+ np = of_find_compatible_node(NULL, NULL,
+ "qcom,msm-imem-restart_reason");
+ if (!np) {
+ pr_err("unable to find DT imem restart reason node\n");
+ } else {
+ restart_reason = of_iomap(np, 0);
+ if (!restart_reason) {
+ pr_err("unable to map imem restart reason offset\n");
+ ret = -ENOMEM;
+ goto err_restart_reason;
+ }
+ }
+
+ mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pshold-base");
msm_ps_hold = devm_ioremap_resource(dev, mem);
if (IS_ERR(msm_ps_hold))
return PTR_ERR(msm_ps_hold);
- register_restart_handler(&restart_nb);
+ mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "tcsr-boot-misc-detect");
+ if (mem)
+ tcsr_boot_misc_detect = mem->start;
pm_power_off = do_msm_poweroff;
+ arm_pm_restart = do_msm_restart;
+
+ if (scm_is_call_available(SCM_SVC_PWR, SCM_IO_DISABLE_PMIC_ARBITER) > 0)
+ scm_pmic_arbiter_disable_supported = true;
+ if (scm_is_call_available(SCM_SVC_PWR, SCM_IO_DEASSERT_PS_HOLD) > 0)
+ scm_deassert_ps_hold_supported = true;
+
+#ifdef CONFIG_QCOM_DLOAD_MODE
+ set_dload_mode(download_mode);
+ if (!download_mode)
+ scm_disable_sdi();
+#endif
return 0;
+
+err_restart_reason:
+#ifdef CONFIG_QCOM_DLOAD_MODE
+ iounmap(emergency_dload_mode_addr);
+ iounmap(dload_mode_addr);
+#endif
+ return ret;
}
static const struct of_device_id of_msm_restart_match[] = {
@@ -78,4 +686,4 @@ static int __init msm_restart_init(void)
{
return platform_driver_register(&msm_restart_driver);
}
-device_initcall(msm_restart_init);
+pure_initcall(msm_restart_init);
diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c
new file mode 100644
index 000000000000..2dfbbce0f817
--- /dev/null
+++ b/drivers/power/reset/reboot-mode.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/reboot.h>
+#include "reboot-mode.h"
+
+#define PREFIX "mode-"
+
+struct mode_info {
+ const char *mode;
+ u32 magic;
+ struct list_head list;
+};
+
+static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
+ const char *cmd)
+{
+ const char *normal = "normal";
+ int magic = 0;
+ struct mode_info *info;
+
+ if (!cmd)
+ cmd = normal;
+
+ list_for_each_entry(info, &reboot->head, list) {
+ if (!strcmp(info->mode, cmd)) {
+ magic = info->magic;
+ break;
+ }
+ }
+
+ return magic;
+}
+
+static int reboot_mode_notify(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct reboot_mode_driver *reboot;
+ unsigned int magic;
+
+ reboot = container_of(this, struct reboot_mode_driver, reboot_notifier);
+ magic = get_reboot_mode_magic(reboot, cmd);
+ if (magic)
+ reboot->write(reboot, magic);
+
+ return NOTIFY_DONE;
+}
+
+/**
+ * reboot_mode_register - register a reboot mode driver
+ * @reboot: reboot mode driver
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int reboot_mode_register(struct reboot_mode_driver *reboot)
+{
+ struct mode_info *info;
+ struct property *prop;
+ struct device_node *np = reboot->dev->of_node;
+ size_t len = strlen(PREFIX);
+ int ret;
+
+ INIT_LIST_HEAD(&reboot->head);
+
+ for_each_property_of_node(np, prop) {
+ if (strncmp(prop->name, PREFIX, len))
+ continue;
+
+ info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ if (of_property_read_u32(np, prop->name, &info->magic)) {
+ dev_err(reboot->dev, "reboot mode %s without magic number\n",
+ info->mode);
+ devm_kfree(reboot->dev, info);
+ continue;
+ }
+
+ info->mode = kstrdup_const(prop->name + len, GFP_KERNEL);
+ if (!info->mode) {
+ ret = -ENOMEM;
+ goto error;
+ } else if (info->mode[0] == '\0') {
+ kfree_const(info->mode);
+ ret = -EINVAL;
+ dev_err(reboot->dev, "invalid mode name(%s): too short!\n",
+ prop->name);
+ goto error;
+ }
+
+ list_add_tail(&info->list, &reboot->head);
+ }
+
+ reboot->reboot_notifier.notifier_call = reboot_mode_notify;
+ register_reboot_notifier(&reboot->reboot_notifier);
+
+ return 0;
+
+error:
+ list_for_each_entry(info, &reboot->head, list)
+ kfree_const(info->mode);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(reboot_mode_register);
+
+/**
+ * reboot_mode_unregister - unregister a reboot mode driver
+ * @reboot: reboot mode driver
+ */
+int reboot_mode_unregister(struct reboot_mode_driver *reboot)
+{
+ struct mode_info *info;
+
+ unregister_reboot_notifier(&reboot->reboot_notifier);
+
+ list_for_each_entry(info, &reboot->head, list)
+ kfree_const(info->mode);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(reboot_mode_unregister);
+
+MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com");
+MODULE_DESCRIPTION("System reboot mode core library");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/reboot-mode.h b/drivers/power/reset/reboot-mode.h
new file mode 100644
index 000000000000..2491bb71f591
--- /dev/null
+++ b/drivers/power/reset/reboot-mode.h
@@ -0,0 +1,14 @@
+#ifndef __REBOOT_MODE_H__
+#define __REBOOT_MODE_H__
+
+struct reboot_mode_driver {
+ struct device *dev;
+ struct list_head head;
+ int (*write)(struct reboot_mode_driver *reboot, unsigned int magic);
+ struct notifier_block reboot_notifier;
+};
+
+int reboot_mode_register(struct reboot_mode_driver *reboot);
+int reboot_mode_unregister(struct reboot_mode_driver *reboot);
+
+#endif
diff --git a/drivers/power/reset/syscon-reboot-mode.c b/drivers/power/reset/syscon-reboot-mode.c
new file mode 100644
index 000000000000..9e1cba5dd58e
--- /dev/null
+++ b/drivers/power/reset/syscon-reboot-mode.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include "reboot-mode.h"
+
+struct syscon_reboot_mode {
+ struct regmap *map;
+ struct reboot_mode_driver reboot;
+ u32 offset;
+ u32 mask;
+};
+
+static int syscon_reboot_mode_write(struct reboot_mode_driver *reboot,
+ unsigned int magic)
+{
+ struct syscon_reboot_mode *syscon_rbm;
+ int ret;
+
+ syscon_rbm = container_of(reboot, struct syscon_reboot_mode, reboot);
+
+ ret = regmap_update_bits(syscon_rbm->map, syscon_rbm->offset,
+ syscon_rbm->mask, magic);
+ if (ret < 0)
+ dev_err(reboot->dev, "update reboot mode bits failed\n");
+
+ return ret;
+}
+
+static int syscon_reboot_mode_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct syscon_reboot_mode *syscon_rbm;
+
+ syscon_rbm = devm_kzalloc(&pdev->dev, sizeof(*syscon_rbm), GFP_KERNEL);
+ if (!syscon_rbm)
+ return -ENOMEM;
+
+ syscon_rbm->reboot.dev = &pdev->dev;
+ syscon_rbm->reboot.write = syscon_reboot_mode_write;
+ syscon_rbm->mask = 0xffffffff;
+
+ dev_set_drvdata(&pdev->dev, syscon_rbm);
+
+ syscon_rbm->map = syscon_node_to_regmap(pdev->dev.parent->of_node);
+ if (IS_ERR(syscon_rbm->map))
+ return PTR_ERR(syscon_rbm->map);
+
+ if (of_property_read_u32(pdev->dev.of_node, "offset",
+ &syscon_rbm->offset))
+ return -EINVAL;
+
+ of_property_read_u32(pdev->dev.of_node, "mask", &syscon_rbm->mask);
+
+ ret = reboot_mode_register(&syscon_rbm->reboot);
+ if (ret)
+ dev_err(&pdev->dev, "can't register reboot mode\n");
+
+ return ret;
+}
+
+static int syscon_reboot_mode_remove(struct platform_device *pdev)
+{
+ struct syscon_reboot_mode *syscon_rbm = dev_get_drvdata(&pdev->dev);
+
+ return reboot_mode_unregister(&syscon_rbm->reboot);
+}
+
+static const struct of_device_id syscon_reboot_mode_of_match[] = {
+ { .compatible = "syscon-reboot-mode" },
+ {}
+};
+
+static struct platform_driver syscon_reboot_mode_driver = {
+ .probe = syscon_reboot_mode_probe,
+ .remove = syscon_reboot_mode_remove,
+ .driver = {
+ .name = "syscon-reboot-mode",
+ .of_match_table = syscon_reboot_mode_of_match,
+ },
+};
+module_platform_driver(syscon_reboot_mode_driver);
+
+MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com");
+MODULE_DESCRIPTION("SYSCON reboot mode driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
new file mode 100644
index 000000000000..ec128bf6a93a
--- /dev/null
+++ b/drivers/power/supply/Kconfig
@@ -0,0 +1 @@
+source "drivers/power/supply/qcom/Kconfig"
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
new file mode 100644
index 000000000000..c8f025f309e7
--- /dev/null
+++ b/drivers/power/supply/Makefile
@@ -0,0 +1 @@
+obj-y += qcom/
diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig
new file mode 100644
index 000000000000..47b201738672
--- /dev/null
+++ b/drivers/power/supply/qcom/Kconfig
@@ -0,0 +1,85 @@
+menu "Qualcomm Technologies Inc Charger and Fuel Gauge support"
+
+config QPNP_FG_GEN3
+ tristate "QPNP GEN3 fuel gauge driver"
+ depends on MFD_SPMI_PMIC
+ help
+ Say Y here to enable the GEN3 Fuel Gauge driver. This adds support
+ for battery fuel gauging and state of charge of battery connected to
+ the fuel gauge. The state of charge is reported through a BMS power
+ supply property and also sends uevents when the capacity is updated.
+
+config SMB135X_CHARGER
+ tristate "SMB135X Battery Charger"
+ depends on I2C
+ help
+ Say Y to include support for SMB135X Battery Charger.
+ SMB135X is a dual path switching mode charger capable of charging
+ the battery with 3Amps of current.
+ The driver supports charger enable/disable.
+ The driver reports the charger status via the power supply framework.
+ A charger status change triggers an IRQ via the device STAT pin.
+
+config SMB1351_USB_CHARGER
+ tristate "smb1351 usb charger (with VBUS detection)"
+ depends on I2C
+ help
+ Say Y to enable support for the SMB1351 switching mode based charger.
+ The driver supports charging control (enable/disable) and
+ charge-current limiting. It also provides USB VBUS detection and
+ notification support. The driver controls SMB1351 via I2C and
+ supports device-tree interface.
+
+config MSM_BCL_CTL
+ bool "BCL Framework driver"
+ help
+ Say Y here to enable this BCL Framework driver. This driver provides
+ interface, which can be used by the BCL h/w drivers to implement the
+ basic functionalities. This framework abstracts the underlying
+ hardware for the top level modules.
+
+config MSM_BCL_PERIPHERAL_CTL
+ bool "BCL driver to control the PMIC BCL peripheral"
+ depends on SPMI
+ depends on MSM_BCL_CTL
+ help
+ Say Y here to enable this BCL PMIC peripheral driver. This driver
+ provides routines to configure and monitor the BCL
+ PMIC peripheral.
+
+config BATTERY_BCL
+ tristate "Battery Current Limit driver"
+ depends on THERMAL_MONITOR
+ help
+ Say Y here to enable support for battery current limit
+ device. The BCL driver will poll BMS if
+ thermal daemon enables BCL.
+ It will notify thermal daemon if IBat crosses Imax threshold.
+
+config QPNP_SMB2
+ tristate "SMB2 Battery Charger"
+ depends on MFD_SPMI_PMIC
+ help
+ Enables support for the SMB2 charging peripheral
+
+config SMB138X_CHARGER
+ tristate "SMB138X Battery Charger"
+ depends on MFD_I2C_PMIC
+ help
+ Say Y to include support for SMB138X Battery Charger.
+ SMB1380 is a dual phase 6A battery charger, and SMB1381 is a single
+ phase 5A battery charger.
+ The driver supports charger enable/disable.
+ The driver reports the charger status via the power supply framework.
+ A charger status change triggers an IRQ via the device STAT pin.
+
+config QPNP_QNOVO
+ bool "QPNP QNOVO driver"
+ depends on MFD_SPMI_PMIC
+ help
+ Say Y here to enable the Qnovo pulse charging engine. Qnovo driver
+ accepts pulse parameters via sysfs entries and programs the hardware
+ module. It also allows userspace code to read diagnostics of voltage
+ and current measured during certain phases of the pulses.
+
+endmenu
diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile
new file mode 100644
index 000000000000..87ab2b24175f
--- /dev/null
+++ b/drivers/power/supply/qcom/Makefile
@@ -0,0 +1,9 @@
+obj-$(CONFIG_QPNP_FG_GEN3) += qpnp-fg-gen3.o fg-memif.o fg-util.o
+obj-$(CONFIG_SMB135X_CHARGER) += smb135x-charger.o pmic-voter.o
+obj-$(CONFIG_SMB1351_USB_CHARGER) += battery.o smb1351-charger.o pmic-voter.o
+obj-$(CONFIG_MSM_BCL_CTL) += msm_bcl.o
+obj-$(CONFIG_MSM_BCL_PERIPHERAL_CTL) += bcl_peripheral.o
+obj-$(CONFIG_BATTERY_BCL) += battery_current_limit.o
+obj-$(CONFIG_QPNP_SMB2) += step-chg-jeita.o battery.o qpnp-smb2.o smb-lib.o pmic-voter.o storm-watch.o
+obj-$(CONFIG_SMB138X_CHARGER) += battery.o smb138x-charger.o smb-lib.o pmic-voter.o storm-watch.o
+obj-$(CONFIG_QPNP_QNOVO) += battery.o qpnp-qnovo.o
diff --git a/drivers/power/supply/qcom/battery.c b/drivers/power/supply/qcom/battery.c
new file mode 100644
index 000000000000..5e8cc84fbfbf
--- /dev/null
+++ b/drivers/power/supply/qcom/battery.c
@@ -0,0 +1,1100 @@
+/* Copyright (c) 2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "QCOM-BATT: %s: " fmt, __func__
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/printk.h>
+#include <linux/pm_wakeup.h>
+#include <linux/slab.h>
+#include <linux/pmic-voter.h>
+
+#define DRV_MAJOR_VERSION 1
+#define DRV_MINOR_VERSION 0
+
+#define CHG_STATE_VOTER "CHG_STATE_VOTER"
+#define TAPER_END_VOTER "TAPER_END_VOTER"
+#define PL_TAPER_EARLY_BAD_VOTER "PL_TAPER_EARLY_BAD_VOTER"
+#define PARALLEL_PSY_VOTER "PARALLEL_PSY_VOTER"
+#define PL_HW_ABSENT_VOTER "PL_HW_ABSENT_VOTER"
+#define PL_VOTER "PL_VOTER"
+#define RESTRICT_CHG_VOTER "RESTRICT_CHG_VOTER"
+#define ICL_CHANGE_VOTER "ICL_CHANGE_VOTER"
+#define PL_INDIRECT_VOTER "PL_INDIRECT_VOTER"
+#define USBIN_I_VOTER "USBIN_I_VOTER"
+
+struct pl_data {
+ int pl_mode;
+ int slave_pct;
+ int taper_pct;
+ int slave_fcc_ua;
+ int restricted_current;
+ bool restricted_charging_enabled;
+ struct votable *fcc_votable;
+ struct votable *fv_votable;
+ struct votable *pl_disable_votable;
+ struct votable *pl_awake_votable;
+ struct votable *hvdcp_hw_inov_dis_votable;
+ struct votable *usb_icl_votable;
+ struct votable *pl_enable_votable_indirect;
+ struct delayed_work status_change_work;
+ struct work_struct pl_disable_forever_work;
+ struct delayed_work pl_taper_work;
+ struct power_supply *main_psy;
+ struct power_supply *pl_psy;
+ struct power_supply *batt_psy;
+ struct power_supply *usb_psy;
+ int charge_type;
+ int total_settled_ua;
+ int pl_settled_ua;
+ struct class qcom_batt_class;
+ struct wakeup_source *pl_ws;
+ struct notifier_block nb;
+};
+
+struct pl_data *the_chip;
+
+enum print_reason {
+ PR_PARALLEL = BIT(0),
+};
+
+static int debug_mask;
+module_param_named(debug_mask, debug_mask, int, S_IRUSR | S_IWUSR);
+
+#define pl_dbg(chip, reason, fmt, ...) \
+ do { \
+ if (debug_mask & (reason)) \
+ pr_info(fmt, ##__VA_ARGS__); \
+ else \
+ pr_debug(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+enum {
+ VER = 0,
+ SLAVE_PCT,
+ RESTRICT_CHG_ENABLE,
+ RESTRICT_CHG_CURRENT,
+};
+
+/*******
+ * ICL *
+********/
+static void split_settled(struct pl_data *chip)
+{
+ int slave_icl_pct, total_current_ua;
+ int slave_ua = 0, main_settled_ua = 0;
+ union power_supply_propval pval = {0, };
+ int rc, total_settled_ua = 0;
+
+ if ((chip->pl_mode != POWER_SUPPLY_PL_USBIN_USBIN)
+ && (chip->pl_mode != POWER_SUPPLY_PL_USBIN_USBIN_EXT))
+ return;
+
+ if (!chip->main_psy)
+ return;
+
+ if (!get_effective_result_locked(chip->pl_disable_votable)) {
+ /* read the aicl settled value */
+ rc = power_supply_get_property(chip->main_psy,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get aicl settled value rc=%d\n", rc);
+ return;
+ }
+ main_settled_ua = pval.intval;
+ /* slave gets 10 percent points less for ICL */
+ slave_icl_pct = max(0, chip->slave_pct - 10);
+ slave_ua = ((main_settled_ua + chip->pl_settled_ua)
+ * slave_icl_pct) / 100;
+ total_settled_ua = main_settled_ua + chip->pl_settled_ua;
+ }
+
+ total_current_ua = get_effective_result_locked(chip->usb_icl_votable);
+ if (total_current_ua < 0) {
+ if (!chip->usb_psy)
+ chip->usb_psy = power_supply_get_by_name("usb");
+ if (!chip->usb_psy) {
+ pr_err("Couldn't get usbpsy while splitting settled\n");
+ return;
+ }
+ /* no client is voting, so get the total current from charger */
+ rc = power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_HW_CURRENT_MAX, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get max current rc=%d\n", rc);
+ return;
+ }
+ total_current_ua = pval.intval;
+ }
+
+ pval.intval = total_current_ua - slave_ua;
+ /* Set ICL on main charger */
+ rc = power_supply_set_property(chip->main_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't change slave suspend state rc=%d\n", rc);
+ return;
+ }
+
+ /* set parallel's ICL could be 0mA when pl is disabled */
+ pval.intval = slave_ua;
+ rc = power_supply_set_property(chip->pl_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't set parallel icl, rc=%d\n", rc);
+ return;
+ }
+
+ chip->total_settled_ua = total_settled_ua;
+ chip->pl_settled_ua = slave_ua;
+
+ pl_dbg(chip, PR_PARALLEL,
+ "Split total_current_ua=%d main_settled_ua=%d slave_ua=%d\n",
+ total_current_ua, main_settled_ua, slave_ua);
+}
+
+static ssize_t version_show(struct class *c, struct class_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d.%d\n",
+ DRV_MAJOR_VERSION, DRV_MINOR_VERSION);
+}
+
+/*************
+* SLAVE PCT *
+**************/
+static ssize_t slave_pct_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ struct pl_data *chip = container_of(c, struct pl_data,
+ qcom_batt_class);
+
+ return snprintf(ubuf, PAGE_SIZE, "%d\n", chip->slave_pct);
+}
+
+static ssize_t slave_pct_store(struct class *c, struct class_attribute *attr,
+ const char *ubuf, size_t count)
+{
+ struct pl_data *chip = container_of(c, struct pl_data,
+ qcom_batt_class);
+ unsigned long val;
+
+ if (kstrtoul(ubuf, 10, &val))
+ return -EINVAL;
+
+ chip->slave_pct = val;
+ rerun_election(chip->fcc_votable);
+ rerun_election(chip->fv_votable);
+ split_settled(chip);
+
+ return count;
+}
+
+/**********************
+* RESTICTED CHARGIGNG *
+***********************/
+static ssize_t restrict_chg_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ struct pl_data *chip = container_of(c, struct pl_data,
+ qcom_batt_class);
+
+ return snprintf(ubuf, PAGE_SIZE, "%d\n",
+ chip->restricted_charging_enabled);
+}
+
+static ssize_t restrict_chg_store(struct class *c, struct class_attribute *attr,
+ const char *ubuf, size_t count)
+{
+ struct pl_data *chip = container_of(c, struct pl_data,
+ qcom_batt_class);
+ unsigned long val;
+
+ if (kstrtoul(ubuf, 10, &val))
+ return -EINVAL;
+
+ if (chip->restricted_charging_enabled == !!val)
+ goto no_change;
+
+ chip->restricted_charging_enabled = !!val;
+
+ /* disable parallel charger in case of restricted charging */
+ vote(chip->pl_disable_votable, RESTRICT_CHG_VOTER,
+ chip->restricted_charging_enabled, 0);
+
+ vote(chip->fcc_votable, RESTRICT_CHG_VOTER,
+ chip->restricted_charging_enabled,
+ chip->restricted_current);
+
+no_change:
+ return count;
+}
+
+static ssize_t restrict_cur_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ struct pl_data *chip = container_of(c, struct pl_data,
+ qcom_batt_class);
+
+ return snprintf(ubuf, PAGE_SIZE, "%d\n", chip->restricted_current);
+}
+
+static ssize_t restrict_cur_store(struct class *c, struct class_attribute *attr,
+ const char *ubuf, size_t count)
+{
+ struct pl_data *chip = container_of(c, struct pl_data,
+ qcom_batt_class);
+ unsigned long val;
+
+ if (kstrtoul(ubuf, 10, &val))
+ return -EINVAL;
+
+ chip->restricted_current = val;
+
+ vote(chip->fcc_votable, RESTRICT_CHG_VOTER,
+ chip->restricted_charging_enabled,
+ chip->restricted_current);
+
+ return count;
+}
+
+static struct class_attribute pl_attributes[] = {
+ [VER] = __ATTR_RO(version),
+ [SLAVE_PCT] = __ATTR(parallel_pct, S_IRUGO | S_IWUSR,
+ slave_pct_show, slave_pct_store),
+ [RESTRICT_CHG_ENABLE] = __ATTR(restricted_charging, S_IRUGO | S_IWUSR,
+ restrict_chg_show, restrict_chg_store),
+ [RESTRICT_CHG_CURRENT] = __ATTR(restricted_current, S_IRUGO | S_IWUSR,
+ restrict_cur_show, restrict_cur_store),
+ __ATTR_NULL,
+};
+
+/***********
+ * TAPER *
+************/
+#define MINIMUM_PARALLEL_FCC_UA 500000
+#define PL_TAPER_WORK_DELAY_MS 100
+#define TAPER_RESIDUAL_PCT 75
+static void pl_taper_work(struct work_struct *work)
+{
+ struct pl_data *chip = container_of(work, struct pl_data,
+ pl_taper_work.work);
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ /* exit immediately if parallel is disabled */
+ if (get_effective_result(chip->pl_disable_votable)) {
+ pl_dbg(chip, PR_PARALLEL, "terminating parallel not in progress\n");
+ goto done;
+ }
+
+ pl_dbg(chip, PR_PARALLEL, "entering parallel taper work slave_fcc = %d\n",
+ chip->slave_fcc_ua);
+ if (chip->slave_fcc_ua < MINIMUM_PARALLEL_FCC_UA) {
+ pl_dbg(chip, PR_PARALLEL, "terminating parallel's share lower than 500mA\n");
+ vote(chip->pl_disable_votable, TAPER_END_VOTER, true, 0);
+ goto done;
+ }
+
+ rc = power_supply_get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get batt charge type rc=%d\n", rc);
+ goto done;
+ }
+
+ chip->charge_type = pval.intval;
+ if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) {
+ pl_dbg(chip, PR_PARALLEL, "master is taper charging; reducing slave FCC\n");
+
+ vote(chip->pl_awake_votable, TAPER_END_VOTER, true, 0);
+ /* Reduce the taper percent by 25 percent */
+ chip->taper_pct = chip->taper_pct * TAPER_RESIDUAL_PCT / 100;
+ rerun_election(chip->fcc_votable);
+ pl_dbg(chip, PR_PARALLEL, "taper entry scheduling work after %d ms\n",
+ PL_TAPER_WORK_DELAY_MS);
+ schedule_delayed_work(&chip->pl_taper_work,
+ msecs_to_jiffies(PL_TAPER_WORK_DELAY_MS));
+ return;
+ }
+
+ /*
+ * Master back to Fast Charge, get out of this round of taper reduction
+ */
+ pl_dbg(chip, PR_PARALLEL, "master is fast charging; waiting for next taper\n");
+
+done:
+ vote(chip->pl_awake_votable, TAPER_END_VOTER, false, 0);
+}
+
+/*********
+ * FCC *
+**********/
+#define EFFICIENCY_PCT 80
+static void split_fcc(struct pl_data *chip, int total_ua,
+ int *master_ua, int *slave_ua)
+{
+ int rc, effective_total_ua, slave_limited_ua, hw_cc_delta_ua = 0,
+ icl_ua, adapter_uv, bcl_ua;
+ union power_supply_propval pval = {0, };
+
+ rc = power_supply_get_property(chip->main_psy,
+ POWER_SUPPLY_PROP_FCC_DELTA, &pval);
+ if (rc < 0)
+ hw_cc_delta_ua = 0;
+ else
+ hw_cc_delta_ua = pval.intval;
+
+ bcl_ua = INT_MAX;
+ if (chip->pl_mode == POWER_SUPPLY_PL_USBMID_USBMID) {
+ rc = power_supply_get_property(chip->main_psy,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get aicl settled value rc=%d\n", rc);
+ return;
+ }
+ icl_ua = pval.intval;
+
+ rc = power_supply_get_property(chip->main_psy,
+ POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get adaptive voltage rc=%d\n", rc);
+ return;
+ }
+ adapter_uv = pval.intval;
+
+ bcl_ua = div64_s64((s64)icl_ua * adapter_uv * EFFICIENCY_PCT,
+ (s64)get_effective_result(chip->fv_votable) * 100);
+ }
+
+ effective_total_ua = max(0, total_ua + hw_cc_delta_ua);
+ slave_limited_ua = min(effective_total_ua, bcl_ua);
+ *slave_ua = (slave_limited_ua * chip->slave_pct) / 100;
+ *slave_ua = (*slave_ua * chip->taper_pct) / 100;
+ /*
+ * In USBIN_USBIN configuration with internal rsense parallel
+ * charger's current goes through main charger's BATFET, keep
+ * the main charger's FCC to the votable result.
+ */
+ if (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+ *master_ua = max(0, total_ua);
+ else
+ *master_ua = max(0, total_ua - *slave_ua);
+}
+
+static int pl_fcc_vote_callback(struct votable *votable, void *data,
+ int total_fcc_ua, const char *client)
+{
+ struct pl_data *chip = data;
+ union power_supply_propval pval = {0, };
+ int rc, master_fcc_ua = total_fcc_ua, slave_fcc_ua = 0;
+
+ if (total_fcc_ua < 0)
+ return 0;
+
+ if (!chip->main_psy)
+ return 0;
+
+ if (chip->pl_mode == POWER_SUPPLY_PL_NONE
+ || get_effective_result_locked(chip->pl_disable_votable)) {
+ pval.intval = total_fcc_ua;
+ rc = power_supply_set_property(chip->main_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ &pval);
+ if (rc < 0)
+ pr_err("Couldn't set main fcc, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chip->pl_mode != POWER_SUPPLY_PL_NONE) {
+ split_fcc(chip, total_fcc_ua, &master_fcc_ua, &slave_fcc_ua);
+
+ pval.intval = slave_fcc_ua;
+ rc = power_supply_set_property(chip->pl_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ &pval);
+ if (rc < 0) {
+ pr_err("Couldn't set parallel fcc, rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->slave_fcc_ua = slave_fcc_ua;
+
+ pval.intval = master_fcc_ua;
+ rc = power_supply_set_property(chip->main_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ &pval);
+ if (rc < 0) {
+ pr_err("Could not set main fcc, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ pl_dbg(chip, PR_PARALLEL, "master_fcc=%d slave_fcc=%d distribution=(%d/%d)\n",
+ master_fcc_ua, slave_fcc_ua,
+ (master_fcc_ua * 100) / total_fcc_ua,
+ (slave_fcc_ua * 100) / total_fcc_ua);
+
+ return 0;
+}
+
+#define PARALLEL_FLOAT_VOLTAGE_DELTA_UV 50000
+static int pl_fv_vote_callback(struct votable *votable, void *data,
+ int fv_uv, const char *client)
+{
+ struct pl_data *chip = data;
+ union power_supply_propval pval = {0, };
+ int rc = 0;
+
+ if (fv_uv < 0)
+ return 0;
+
+ if (!chip->main_psy)
+ return 0;
+
+ pval.intval = fv_uv;
+
+ rc = power_supply_set_property(chip->main_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't set main fv, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chip->pl_mode != POWER_SUPPLY_PL_NONE) {
+ pval.intval += PARALLEL_FLOAT_VOLTAGE_DELTA_UV;
+ rc = power_supply_set_property(chip->pl_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't set float on parallel rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+#define ICL_STEP_UA 25000
+#define PL_DELAY_MS 3000
+static int usb_icl_vote_callback(struct votable *votable, void *data,
+ int icl_ua, const char *client)
+{
+ int rc;
+ struct pl_data *chip = data;
+ union power_supply_propval pval = {0, };
+ bool rerun_aicl = false;
+
+ if (!chip->main_psy)
+ return 0;
+
+ if (client == NULL)
+ icl_ua = INT_MAX;
+
+ /*
+ * Disable parallel for new ICL vote - the call to split_settled will
+ * ensure that all the input current limit gets assigned to the main
+ * charger.
+ */
+ vote(chip->pl_disable_votable, ICL_CHANGE_VOTER, true, 0);
+
+ /*
+ * if (ICL < 1400)
+ * disable parallel charger using USBIN_I_VOTER
+ * else
+ * instead of re-enabling here rely on status_changed_work
+ * (triggered via AICL completed or scheduled from here to
+ * unvote USBIN_I_VOTER) the status_changed_work enables
+ * USBIN_I_VOTER based on settled current.
+ */
+ if (icl_ua <= 1400000)
+ vote(chip->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0);
+ else
+ schedule_delayed_work(&chip->status_change_work,
+ msecs_to_jiffies(PL_DELAY_MS));
+
+ /* rerun AICL */
+ /* get the settled current */
+ rc = power_supply_get_property(chip->main_psy,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED,
+ &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get aicl settled value rc=%d\n", rc);
+ return rc;
+ }
+
+ /* rerun AICL if new ICL is above settled ICL */
+ if (icl_ua > pval.intval)
+ rerun_aicl = true;
+
+ if (rerun_aicl) {
+ /* set a lower ICL */
+ pval.intval = max(pval.intval - ICL_STEP_UA, ICL_STEP_UA);
+ power_supply_set_property(chip->main_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ &pval);
+ }
+
+ /* set the effective ICL */
+ pval.intval = icl_ua;
+ power_supply_set_property(chip->main_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ &pval);
+
+ vote(chip->pl_disable_votable, ICL_CHANGE_VOTER, false, 0);
+
+ return 0;
+}
+
+static void pl_disable_forever_work(struct work_struct *work)
+{
+ struct pl_data *chip = container_of(work,
+ struct pl_data, pl_disable_forever_work);
+
+ /* Disable Parallel charger forever */
+ vote(chip->pl_disable_votable, PL_HW_ABSENT_VOTER, true, 0);
+
+ /* Re-enable autonomous mode */
+ if (chip->hvdcp_hw_inov_dis_votable)
+ vote(chip->hvdcp_hw_inov_dis_votable, PL_VOTER, false, 0);
+}
+
+static int pl_disable_vote_callback(struct votable *votable,
+ void *data, int pl_disable, const char *client)
+{
+ struct pl_data *chip = data;
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ chip->taper_pct = 100;
+ chip->total_settled_ua = 0;
+ chip->pl_settled_ua = 0;
+
+ if (!pl_disable) { /* enable */
+ rc = power_supply_get_property(chip->pl_psy,
+ POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
+ if (rc == -ENODEV) {
+ /*
+ * -ENODEV is returned only if parallel chip
+ * is not present in the system.
+ * Disable parallel charger forever.
+ */
+ schedule_work(&chip->pl_disable_forever_work);
+ return rc;
+ }
+
+ rerun_election(chip->fv_votable);
+ rerun_election(chip->fcc_votable);
+ /*
+ * Enable will be called with a valid pl_psy always. The
+ * PARALLEL_PSY_VOTER keeps it disabled unless a pl_psy
+ * is seen.
+ */
+ pval.intval = 0;
+ rc = power_supply_set_property(chip->pl_psy,
+ POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval);
+ if (rc < 0)
+ pr_err("Couldn't change slave suspend state rc=%d\n",
+ rc);
+
+ if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+ || (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT))
+ split_settled(chip);
+ /*
+ * we could have been enabled while in taper mode,
+ * start the taper work if so
+ */
+ rc = power_supply_get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get batt charge type rc=%d\n", rc);
+ } else {
+ if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) {
+ pl_dbg(chip, PR_PARALLEL,
+ "pl enabled in Taper scheduing work\n");
+ schedule_delayed_work(&chip->pl_taper_work, 0);
+ }
+ }
+ } else {
+ if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+ || (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT))
+ split_settled(chip);
+
+ /* pl_psy may be NULL while in the disable branch */
+ if (chip->pl_psy) {
+ pval.intval = 1;
+ rc = power_supply_set_property(chip->pl_psy,
+ POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval);
+ if (rc < 0)
+ pr_err("Couldn't change slave suspend state rc=%d\n",
+ rc);
+ }
+ rerun_election(chip->fcc_votable);
+ rerun_election(chip->fv_votable);
+ }
+
+ pl_dbg(chip, PR_PARALLEL, "parallel charging %s\n",
+ pl_disable ? "disabled" : "enabled");
+
+ return 0;
+}
+
+static int pl_enable_indirect_vote_callback(struct votable *votable,
+ void *data, int pl_enable, const char *client)
+{
+ struct pl_data *chip = data;
+
+ vote(chip->pl_disable_votable, PL_INDIRECT_VOTER, !pl_enable, 0);
+
+ return 0;
+}
+
+static int pl_awake_vote_callback(struct votable *votable,
+ void *data, int awake, const char *client)
+{
+ struct pl_data *chip = data;
+
+ if (awake)
+ __pm_stay_awake(chip->pl_ws);
+ else
+ __pm_relax(chip->pl_ws);
+
+ pr_debug("client: %s awake: %d\n", client, awake);
+ return 0;
+}
+
+static bool is_main_available(struct pl_data *chip)
+{
+ if (chip->main_psy)
+ return true;
+
+ chip->main_psy = power_supply_get_by_name("main");
+
+ return !!chip->main_psy;
+}
+
+static bool is_batt_available(struct pl_data *chip)
+{
+ if (!chip->batt_psy)
+ chip->batt_psy = power_supply_get_by_name("battery");
+
+ if (!chip->batt_psy)
+ return false;
+
+ return true;
+}
+
+static bool is_parallel_available(struct pl_data *chip)
+{
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ if (chip->pl_psy)
+ return true;
+
+ chip->pl_psy = power_supply_get_by_name("parallel");
+ if (!chip->pl_psy)
+ return false;
+
+ rc = power_supply_get_property(chip->pl_psy,
+ POWER_SUPPLY_PROP_PARALLEL_MODE, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get parallel mode from parallel rc=%d\n",
+ rc);
+ return false;
+ }
+ /*
+ * Note that pl_mode will be updated to anything other than a _NONE
+ * only after pl_psy is found. IOW pl_mode != _NONE implies that
+ * pl_psy is present and valid.
+ */
+ chip->pl_mode = pval.intval;
+
+ /* Disable autonomous votage increments for USBIN-USBIN */
+ if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+ || (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT)) {
+ if (!chip->hvdcp_hw_inov_dis_votable)
+ chip->hvdcp_hw_inov_dis_votable =
+ find_votable("HVDCP_HW_INOV_DIS");
+ if (chip->hvdcp_hw_inov_dis_votable)
+ /* Read current pulse count */
+ vote(chip->hvdcp_hw_inov_dis_votable, PL_VOTER,
+ true, 0);
+ else
+ return false;
+ }
+
+ vote(chip->pl_disable_votable, PARALLEL_PSY_VOTER, false, 0);
+
+ return true;
+}
+
+static void handle_main_charge_type(struct pl_data *chip)
+{
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ rc = power_supply_get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get batt charge type rc=%d\n", rc);
+ return;
+ }
+
+ /* not fast/not taper state to disables parallel */
+ if ((pval.intval != POWER_SUPPLY_CHARGE_TYPE_FAST)
+ && (pval.intval != POWER_SUPPLY_CHARGE_TYPE_TAPER)) {
+ vote(chip->pl_disable_votable, CHG_STATE_VOTER, true, 0);
+ chip->taper_pct = 100;
+ vote(chip->pl_disable_votable, TAPER_END_VOTER, false, 0);
+ vote(chip->pl_disable_votable, PL_TAPER_EARLY_BAD_VOTER,
+ false, 0);
+ chip->charge_type = pval.intval;
+ return;
+ }
+
+ /* handle taper charge entry */
+ if (chip->charge_type == POWER_SUPPLY_CHARGE_TYPE_FAST
+ && (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER)) {
+ chip->charge_type = pval.intval;
+ pl_dbg(chip, PR_PARALLEL, "taper entry scheduling work\n");
+ schedule_delayed_work(&chip->pl_taper_work, 0);
+ return;
+ }
+
+ /* handle fast/taper charge entry */
+ if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER
+ || pval.intval == POWER_SUPPLY_CHARGE_TYPE_FAST) {
+ pl_dbg(chip, PR_PARALLEL, "chg_state enabling parallel\n");
+ vote(chip->pl_disable_votable, CHG_STATE_VOTER, false, 0);
+ chip->charge_type = pval.intval;
+ return;
+ }
+
+ /* remember the new state only if it isn't any of the above */
+ chip->charge_type = pval.intval;
+}
+
+#define MIN_ICL_CHANGE_DELTA_UA 300000
+static void handle_settled_icl_change(struct pl_data *chip)
+{
+ union power_supply_propval pval = {0, };
+ int new_total_settled_ua;
+ int rc;
+ int main_settled_ua;
+ int main_limited;
+ int total_current_ua;
+
+ total_current_ua = get_effective_result_locked(chip->usb_icl_votable);
+
+ /*
+ * call aicl split only when USBIN_USBIN and enabled
+ * and if aicl changed
+ */
+ rc = power_supply_get_property(chip->main_psy,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED,
+ &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get aicl settled value rc=%d\n", rc);
+ return;
+ }
+ main_settled_ua = pval.intval;
+
+ rc = power_supply_get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+ &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get aicl settled value rc=%d\n", rc);
+ return;
+ }
+ main_limited = pval.intval;
+
+ if ((main_limited && (main_settled_ua + chip->pl_settled_ua) < 1400000)
+ || (main_settled_ua == 0)
+ || ((total_current_ua >= 0) &&
+ (total_current_ua <= 1400000)))
+ vote(chip->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0);
+ else
+ vote(chip->pl_enable_votable_indirect, USBIN_I_VOTER, true, 0);
+
+
+ if (get_effective_result(chip->pl_disable_votable))
+ return;
+
+ if (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN
+ || chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT) {
+ /*
+ * call aicl split only when USBIN_USBIN and enabled
+ * and if settled current has changed by more than 300mA
+ */
+
+ new_total_settled_ua = main_settled_ua + chip->pl_settled_ua;
+ pl_dbg(chip, PR_PARALLEL,
+ "total_settled_ua=%d settled_ua=%d new_total_settled_ua=%d\n",
+ chip->total_settled_ua, pval.intval,
+ new_total_settled_ua);
+
+ /* If ICL change is small skip splitting */
+ if (abs(new_total_settled_ua - chip->total_settled_ua)
+ > MIN_ICL_CHANGE_DELTA_UA)
+ split_settled(chip);
+ } else {
+ rerun_election(chip->fcc_votable);
+ }
+}
+
+static void handle_parallel_in_taper(struct pl_data *chip)
+{
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ if (get_effective_result_locked(chip->pl_disable_votable))
+ return;
+
+ if (!chip->pl_psy)
+ return;
+
+ rc = power_supply_get_property(chip->pl_psy,
+ POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't get pl charge type rc=%d\n", rc);
+ return;
+ }
+
+ /*
+ * if parallel is seen in taper mode ever, that is an anomaly and
+ * we disable parallel charger
+ */
+ if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) {
+ vote(chip->pl_disable_votable, PL_TAPER_EARLY_BAD_VOTER,
+ true, 0);
+ return;
+ }
+}
+
+static void status_change_work(struct work_struct *work)
+{
+ struct pl_data *chip = container_of(work,
+ struct pl_data, status_change_work.work);
+
+ if (!chip->main_psy && is_main_available(chip)) {
+ /*
+ * re-run election for FCC/FV/ICL once main_psy
+ * is available to ensure all votes are reflected
+ * on hardware
+ */
+ rerun_election(chip->usb_icl_votable);
+ rerun_election(chip->fcc_votable);
+ rerun_election(chip->fv_votable);
+ }
+
+ if (!chip->main_psy)
+ return;
+
+ if (!is_batt_available(chip))
+ return;
+
+ is_parallel_available(chip);
+
+ handle_main_charge_type(chip);
+ handle_settled_icl_change(chip);
+ handle_parallel_in_taper(chip);
+}
+
+static int pl_notifier_call(struct notifier_block *nb,
+ unsigned long ev, void *v)
+{
+ struct power_supply *psy = v;
+ struct pl_data *chip = container_of(nb, struct pl_data, nb);
+
+ if (ev != PSY_EVENT_PROP_CHANGED)
+ return NOTIFY_OK;
+
+ if ((strcmp(psy->desc->name, "parallel") == 0)
+ || (strcmp(psy->desc->name, "battery") == 0)
+ || (strcmp(psy->desc->name, "main") == 0))
+ schedule_delayed_work(&chip->status_change_work, 0);
+
+ return NOTIFY_OK;
+}
+
+static int pl_register_notifier(struct pl_data *chip)
+{
+ int rc;
+
+ chip->nb.notifier_call = pl_notifier_call;
+ rc = power_supply_reg_notifier(&chip->nb);
+ if (rc < 0) {
+ pr_err("Couldn't register psy notifier rc = %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int pl_determine_initial_status(struct pl_data *chip)
+{
+ status_change_work(&chip->status_change_work.work);
+ return 0;
+}
+
+#define DEFAULT_RESTRICTED_CURRENT_UA 1000000
+int qcom_batt_init(void)
+{
+ struct pl_data *chip;
+ int rc = 0;
+
+ /* initialize just once */
+ if (the_chip) {
+ pr_err("was initialized earlier Failing now\n");
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+ chip->slave_pct = 50;
+ chip->restricted_current = DEFAULT_RESTRICTED_CURRENT_UA;
+
+ chip->pl_ws = wakeup_source_register("qcom-battery");
+ if (!chip->pl_ws)
+ goto cleanup;
+
+ chip->fcc_votable = create_votable("FCC", VOTE_MIN,
+ pl_fcc_vote_callback,
+ chip);
+ if (IS_ERR(chip->fcc_votable)) {
+ rc = PTR_ERR(chip->fcc_votable);
+ goto release_wakeup_source;
+ }
+
+ chip->fv_votable = create_votable("FV", VOTE_MAX,
+ pl_fv_vote_callback,
+ chip);
+ if (IS_ERR(chip->fv_votable)) {
+ rc = PTR_ERR(chip->fv_votable);
+ goto destroy_votable;
+ }
+
+ chip->usb_icl_votable = create_votable("USB_ICL", VOTE_MIN,
+ usb_icl_vote_callback,
+ chip);
+ if (IS_ERR(chip->usb_icl_votable)) {
+ rc = PTR_ERR(chip->usb_icl_votable);
+ goto destroy_votable;
+ }
+
+ chip->pl_disable_votable = create_votable("PL_DISABLE", VOTE_SET_ANY,
+ pl_disable_vote_callback,
+ chip);
+ if (IS_ERR(chip->pl_disable_votable)) {
+ rc = PTR_ERR(chip->pl_disable_votable);
+ goto destroy_votable;
+ }
+ vote(chip->pl_disable_votable, CHG_STATE_VOTER, true, 0);
+ vote(chip->pl_disable_votable, TAPER_END_VOTER, false, 0);
+ vote(chip->pl_disable_votable, PARALLEL_PSY_VOTER, true, 0);
+
+ chip->pl_awake_votable = create_votable("PL_AWAKE", VOTE_SET_ANY,
+ pl_awake_vote_callback,
+ chip);
+ if (IS_ERR(chip->pl_awake_votable)) {
+ rc = PTR_ERR(chip->pl_disable_votable);
+ goto destroy_votable;
+ }
+
+ chip->pl_enable_votable_indirect = create_votable("PL_ENABLE_INDIRECT",
+ VOTE_SET_ANY,
+ pl_enable_indirect_vote_callback,
+ chip);
+ if (IS_ERR(chip->pl_enable_votable_indirect)) {
+ rc = PTR_ERR(chip->pl_enable_votable_indirect);
+ return rc;
+ }
+
+ vote(chip->pl_disable_votable, PL_INDIRECT_VOTER, true, 0);
+
+ INIT_DELAYED_WORK(&chip->status_change_work, status_change_work);
+ INIT_DELAYED_WORK(&chip->pl_taper_work, pl_taper_work);
+ INIT_WORK(&chip->pl_disable_forever_work, pl_disable_forever_work);
+
+ rc = pl_register_notifier(chip);
+ if (rc < 0) {
+ pr_err("Couldn't register psy notifier rc = %d\n", rc);
+ goto unreg_notifier;
+ }
+
+ rc = pl_determine_initial_status(chip);
+ if (rc < 0) {
+ pr_err("Couldn't determine initial status rc=%d\n", rc);
+ goto unreg_notifier;
+ }
+
+ chip->qcom_batt_class.name = "qcom-battery",
+ chip->qcom_batt_class.owner = THIS_MODULE,
+ chip->qcom_batt_class.class_attrs = pl_attributes;
+
+ rc = class_register(&chip->qcom_batt_class);
+ if (rc < 0) {
+ pr_err("couldn't register pl_data sysfs class rc = %d\n", rc);
+ goto unreg_notifier;
+ }
+
+ the_chip = chip;
+
+ return 0;
+
+unreg_notifier:
+ power_supply_unreg_notifier(&chip->nb);
+destroy_votable:
+ destroy_votable(chip->pl_enable_votable_indirect);
+ destroy_votable(chip->pl_awake_votable);
+ destroy_votable(chip->pl_disable_votable);
+ destroy_votable(chip->fv_votable);
+ destroy_votable(chip->fcc_votable);
+ destroy_votable(chip->usb_icl_votable);
+release_wakeup_source:
+ wakeup_source_unregister(chip->pl_ws);
+cleanup:
+ kfree(chip);
+ return rc;
+}
+
+void qcom_batt_deinit(void)
+{
+ struct pl_data *chip = the_chip;
+
+ if (chip == NULL)
+ return;
+
+ cancel_delayed_work_sync(&chip->status_change_work);
+ cancel_delayed_work_sync(&chip->pl_taper_work);
+ cancel_work_sync(&chip->pl_disable_forever_work);
+
+ power_supply_unreg_notifier(&chip->nb);
+ destroy_votable(chip->pl_enable_votable_indirect);
+ destroy_votable(chip->pl_awake_votable);
+ destroy_votable(chip->pl_disable_votable);
+ destroy_votable(chip->fv_votable);
+ destroy_votable(chip->fcc_votable);
+ wakeup_source_unregister(chip->pl_ws);
+ the_chip = NULL;
+ kfree(chip);
+}
diff --git a/drivers/power/supply/qcom/battery.h b/drivers/power/supply/qcom/battery.h
new file mode 100644
index 000000000000..38626e733a09
--- /dev/null
+++ b/drivers/power/supply/qcom/battery.h
@@ -0,0 +1,17 @@
+/* Copyright (c) 2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 __BATTERY_H
+#define __BATTERY_H
+int qcom_batt_init(void);
+void qcom_batt_deinit(void);
+#endif /* __BATTERY_H */
diff --git a/drivers/power/supply/qcom/battery_current_limit.c b/drivers/power/supply/qcom/battery_current_limit.c
new file mode 100644
index 000000000000..410e64321ba6
--- /dev/null
+++ b/drivers/power/supply/qcom/battery_current_limit.c
@@ -0,0 +1,1842 @@
+/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/power_supply.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/cpufreq.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/cpu.h>
+#include <linux/msm_bcl.h>
+#include <linux/power_supply.h>
+#include <linux/cpumask.h>
+#include <linux/msm_thermal.h>
+
+#define CREATE_TRACE_POINTS
+#define _BCL_SW_TRACE
+#include <trace/trace_thermal.h>
+
+#define BCL_DEV_NAME "battery_current_limit"
+#define BCL_NAME_LENGTH 20
+/*
+ * Default BCL poll interval 1000 msec
+ */
+#define BCL_POLL_INTERVAL 1000
+/*
+ * Mininum BCL poll interval 10 msec
+ */
+#define MIN_BCL_POLL_INTERVAL 10
+#define BATTERY_VOLTAGE_MIN 3400
+#define BTM_8084_FREQ_MITIG_LIMIT 1958400
+#define MAX_CPU_NAME 10
+
+#define BCL_FETCH_DT_U32(_dev, _key, _search_str, _ret, _out) do { \
+ _key = _search_str; \
+ _ret = of_property_read_u32(_dev, _key, &_out); \
+ } while (0)
+
+/*
+ * Battery Current Limit Enable or Not
+ */
+enum bcl_device_mode {
+ BCL_DEVICE_DISABLED = 0,
+ BCL_DEVICE_ENABLED,
+};
+
+/*
+ * Battery Current Limit Iavail Threshold Mode set
+ */
+enum bcl_iavail_threshold_mode {
+ BCL_IAVAIL_THRESHOLD_DISABLED = 0,
+ BCL_IAVAIL_THRESHOLD_ENABLED,
+};
+
+/*
+ * Battery Current Limit Iavail Threshold Mode
+ */
+enum bcl_iavail_threshold_type {
+ BCL_LOW_THRESHOLD_TYPE = 0,
+ BCL_HIGH_THRESHOLD_TYPE,
+ BCL_THRESHOLD_TYPE_MAX,
+};
+
+enum bcl_monitor_type {
+ BCL_IAVAIL_MONITOR_TYPE,
+ BCL_IBAT_MONITOR_TYPE,
+ BCL_IBAT_PERIPH_MONITOR_TYPE,
+ BCL_MONITOR_TYPE_MAX,
+};
+
+enum bcl_adc_monitor_mode {
+ BCL_MONITOR_DISABLED,
+ BCL_VPH_MONITOR_MODE,
+ BCL_IBAT_MONITOR_MODE,
+ BCL_IBAT_HIGH_LOAD_MODE,
+ BCL_MONITOR_MODE_MAX,
+};
+
+static const char *bcl_type[BCL_MONITOR_TYPE_MAX] = {"bcl", "btm",
+ "bcl_peripheral"};
+int adc_timer_val_usec[] = {
+ [ADC_MEAS1_INTERVAL_0MS] = 0,
+ [ADC_MEAS1_INTERVAL_1P0MS] = 1000,
+ [ADC_MEAS1_INTERVAL_2P0MS] = 2000,
+ [ADC_MEAS1_INTERVAL_3P9MS] = 3900,
+ [ADC_MEAS1_INTERVAL_7P8MS] = 7800,
+ [ADC_MEAS1_INTERVAL_15P6MS] = 15600,
+ [ADC_MEAS1_INTERVAL_31P3MS] = 31300,
+ [ADC_MEAS1_INTERVAL_62P5MS] = 62500,
+ [ADC_MEAS1_INTERVAL_125MS] = 125000,
+ [ADC_MEAS1_INTERVAL_250MS] = 250000,
+ [ADC_MEAS1_INTERVAL_500MS] = 500000,
+ [ADC_MEAS1_INTERVAL_1S] = 1000000,
+ [ADC_MEAS1_INTERVAL_2S] = 2000000,
+ [ADC_MEAS1_INTERVAL_4S] = 4000000,
+ [ADC_MEAS1_INTERVAL_8S] = 8000000,
+ [ADC_MEAS1_INTERVAL_16S] = 16000000,
+};
+
+/**
+ * BCL control block
+ *
+ */
+struct bcl_context {
+ /* BCL device */
+ struct device *dev;
+
+ /* BCL related config parameter */
+ /* BCL mode enable or not */
+ enum bcl_device_mode bcl_mode;
+ /* BCL monitoring Iavail or Ibat */
+ enum bcl_monitor_type bcl_monitor_type;
+ /* BCL Iavail Threshold Activate or Not */
+ enum bcl_iavail_threshold_mode
+ bcl_threshold_mode[BCL_THRESHOLD_TYPE_MAX];
+ /* BCL Iavail Threshold value in milli Amp */
+ int bcl_threshold_value_ma[BCL_THRESHOLD_TYPE_MAX];
+ /* BCL Type */
+ char bcl_type[BCL_NAME_LENGTH];
+ /* BCL poll in msec */
+ int bcl_poll_interval_msec;
+
+ /* BCL realtime value based on poll */
+ /* BCL realtime vbat in mV*/
+ int bcl_vbat_mv;
+ /* BCL realtime rbat in mOhms*/
+ int bcl_rbat_mohm;
+ /*BCL realtime iavail in milli Amp*/
+ int bcl_iavail;
+ /*BCL vbatt min in mV*/
+ int bcl_vbat_min;
+ /* BCL period poll delay work structure */
+ struct delayed_work bcl_iavail_work;
+ /* For non-bms target */
+ bool bcl_no_bms;
+ /* The max CPU frequency the BTM restricts during high load */
+ uint32_t btm_freq_max;
+ /* Indicates whether there is a high load */
+ enum bcl_adc_monitor_mode btm_mode;
+ /* battery current high load clr threshold */
+ int btm_low_threshold_uv;
+ /* battery current high load threshold */
+ int btm_high_threshold_uv;
+ /* ADC battery current polling timer interval */
+ enum qpnp_adc_meas_timer_1 btm_adc_interval;
+ /* Ibat ADC config parameters */
+ struct qpnp_adc_tm_chip *btm_adc_tm_dev;
+ struct qpnp_vadc_chip *btm_vadc_dev;
+ int btm_ibat_chan;
+ struct qpnp_adc_tm_btm_param btm_ibat_adc_param;
+ uint32_t btm_uv_to_ua_numerator;
+ uint32_t btm_uv_to_ua_denominator;
+ /* Vph ADC config parameters */
+ int btm_vph_chan;
+ uint32_t btm_vph_high_thresh;
+ uint32_t btm_vph_low_thresh;
+ struct qpnp_adc_tm_btm_param btm_vph_adc_param;
+ /* Low temp min freq limit requested by thermal */
+ uint32_t thermal_freq_limit;
+ /* state of charge notifier */
+ struct notifier_block psy_nb;
+ struct work_struct soc_mitig_work;
+
+ /* BCL Peripheral monitor parameters */
+ struct bcl_threshold ibat_high_thresh;
+ struct bcl_threshold ibat_low_thresh;
+ struct bcl_threshold vbat_high_thresh;
+ struct bcl_threshold vbat_low_thresh;
+ uint32_t bcl_p_freq_max;
+ struct workqueue_struct *bcl_hotplug_wq;
+ struct device_clnt_data *hotplug_handle;
+ struct device_clnt_data *cpufreq_handle[NR_CPUS];
+};
+
+enum bcl_threshold_state {
+ BCL_LOW_THRESHOLD = 0,
+ BCL_HIGH_THRESHOLD,
+ BCL_THRESHOLD_DISABLED,
+};
+
+static struct bcl_context *gbcl;
+static enum bcl_threshold_state bcl_vph_state = BCL_THRESHOLD_DISABLED,
+ bcl_ibat_state = BCL_THRESHOLD_DISABLED,
+ bcl_soc_state = BCL_THRESHOLD_DISABLED;
+static DEFINE_MUTEX(bcl_notify_mutex);
+static uint32_t bcl_hotplug_request, bcl_hotplug_mask, bcl_soc_hotplug_mask;
+static uint32_t bcl_frequency_mask;
+static struct work_struct bcl_hotplug_work;
+static DEFINE_MUTEX(bcl_hotplug_mutex);
+static DEFINE_MUTEX(bcl_cpufreq_mutex);
+static bool bcl_hotplug_enabled;
+static uint32_t battery_soc_val = 100;
+static uint32_t soc_low_threshold;
+static const char bcl_psy_name[] = "bcl";
+
+static void bcl_handle_hotplug(struct work_struct *work)
+{
+ int ret = 0, cpu = 0;
+ union device_request curr_req;
+
+ trace_bcl_sw_mitigation_event("start hotplug mitigation");
+ mutex_lock(&bcl_hotplug_mutex);
+
+ if (bcl_soc_state == BCL_LOW_THRESHOLD
+ || bcl_vph_state == BCL_LOW_THRESHOLD)
+ bcl_hotplug_request = bcl_soc_hotplug_mask;
+ else if (bcl_ibat_state == BCL_HIGH_THRESHOLD)
+ bcl_hotplug_request = bcl_hotplug_mask;
+ else
+ bcl_hotplug_request = 0;
+
+ cpumask_clear(&curr_req.offline_mask);
+ for_each_possible_cpu(cpu) {
+ if (bcl_hotplug_request & BIT(cpu))
+ cpumask_set_cpu(cpu, &curr_req.offline_mask);
+ }
+ trace_bcl_sw_mitigation("Start hotplug CPU", bcl_hotplug_request);
+ ret = devmgr_client_request_mitigation(
+ gbcl->hotplug_handle,
+ HOTPLUG_MITIGATION_REQ,
+ &curr_req);
+ if (ret) {
+ pr_err("hotplug request failed. err:%d\n", ret);
+ goto handle_hotplug_exit;
+ }
+
+handle_hotplug_exit:
+ mutex_unlock(&bcl_hotplug_mutex);
+ trace_bcl_sw_mitigation_event("stop hotplug mitigation");
+}
+
+static void update_cpu_freq(void)
+{
+ int cpu, ret = 0;
+ union device_request cpufreq_req;
+
+ trace_bcl_sw_mitigation_event("Start Frequency Mitigate");
+ mutex_lock(&bcl_cpufreq_mutex);
+ cpufreq_req.freq.max_freq = UINT_MAX;
+ cpufreq_req.freq.min_freq = CPUFREQ_MIN_NO_MITIGATION;
+
+ if (bcl_vph_state == BCL_LOW_THRESHOLD
+ || bcl_ibat_state == BCL_HIGH_THRESHOLD
+ || bcl_soc_state == BCL_LOW_THRESHOLD) {
+ cpufreq_req.freq.max_freq = (gbcl->bcl_monitor_type
+ == BCL_IBAT_MONITOR_TYPE) ? gbcl->btm_freq_max
+ : gbcl->bcl_p_freq_max;
+ }
+
+ for_each_possible_cpu(cpu) {
+ if (!(bcl_frequency_mask & BIT(cpu)))
+ continue;
+ pr_debug("Requesting Max freq:%u for CPU%d\n",
+ cpufreq_req.freq.max_freq, cpu);
+ trace_bcl_sw_mitigation("Frequency Mitigate CPU", cpu);
+ ret = devmgr_client_request_mitigation(
+ gbcl->cpufreq_handle[cpu],
+ CPUFREQ_MITIGATION_REQ, &cpufreq_req);
+ if (ret)
+ pr_err("Error updating freq for CPU%d. ret:%d\n",
+ cpu, ret);
+ }
+ mutex_unlock(&bcl_cpufreq_mutex);
+ trace_bcl_sw_mitigation_event("End Frequency Mitigation");
+}
+
+static void soc_mitigate(struct work_struct *work)
+{
+ if (bcl_hotplug_enabled)
+ queue_work(gbcl->bcl_hotplug_wq, &bcl_hotplug_work);
+ update_cpu_freq();
+}
+
+static int get_and_evaluate_battery_soc(void)
+{
+ static struct power_supply *batt_psy;
+ union power_supply_propval ret = {0,};
+ int battery_percentage;
+ enum bcl_threshold_state prev_soc_state;
+
+ if (!batt_psy)
+ batt_psy = power_supply_get_by_name("battery");
+ if (batt_psy) {
+ battery_percentage = power_supply_get_property(batt_psy,
+ POWER_SUPPLY_PROP_CAPACITY, &ret);
+ battery_percentage = ret.intval;
+ battery_soc_val = battery_percentage;
+ pr_debug("Battery SOC reported:%d", battery_soc_val);
+ trace_bcl_sw_mitigation("SoC reported", battery_soc_val);
+ prev_soc_state = bcl_soc_state;
+ bcl_soc_state = (battery_soc_val <= soc_low_threshold) ?
+ BCL_LOW_THRESHOLD : BCL_HIGH_THRESHOLD;
+ if (bcl_soc_state == prev_soc_state)
+ return NOTIFY_OK;
+ trace_bcl_sw_mitigation_event(
+ (bcl_soc_state == BCL_LOW_THRESHOLD)
+ ? "trigger SoC mitigation"
+ : "clear SoC mitigation");
+ schedule_work(&gbcl->soc_mitig_work);
+ }
+ return NOTIFY_OK;
+}
+
+static int power_supply_callback(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct power_supply *psy = data;
+
+ if (gbcl->bcl_mode != BCL_DEVICE_ENABLED) {
+ pr_debug("BCL is not enabled\n");
+ return NOTIFY_OK;
+ }
+
+ if (strcmp(psy->desc->name, "battery"))
+ return NOTIFY_OK;
+
+ return get_and_evaluate_battery_soc();
+}
+
+static int bcl_get_battery_voltage(int *vbatt_mv)
+{
+ static struct power_supply *psy;
+ union power_supply_propval ret = {0,};
+
+ if (psy == NULL) {
+ psy = power_supply_get_by_name("battery");
+ if (psy == NULL) {
+ pr_err("failed to get ps battery\n");
+ return -EINVAL;
+ }
+ }
+
+ if (power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &ret))
+ return -EINVAL;
+
+ if (ret.intval <= 0)
+ return -EINVAL;
+
+ *vbatt_mv = ret.intval / 1000;
+ return 0;
+}
+
+
+static int bcl_get_resistance(int *rbatt_mohm)
+{
+ static struct power_supply *psy;
+ union power_supply_propval ret = {0,};
+
+ if (psy == NULL) {
+ psy =
+ power_supply_get_by_name(gbcl->bcl_no_bms ? "battery" : "bms");
+ if (psy == NULL) {
+ pr_err("failed to get ps %s\n",
+ gbcl->bcl_no_bms ? "battery" : "bms");
+ return -EINVAL;
+ }
+ }
+ if (power_supply_get_property(psy, POWER_SUPPLY_PROP_RESISTANCE, &ret))
+ return -EINVAL;
+
+ if (ret.intval < 1000)
+ return -EINVAL;
+
+ *rbatt_mohm = ret.intval / 1000;
+
+ return 0;
+}
+
+/*
+ * BCL iavail calculation and trigger notification to user space
+ * if iavail cross threshold
+ */
+static void bcl_calculate_iavail_trigger(void)
+{
+ int iavail_ma = 0;
+ int vbatt_mv;
+ int rbatt_mohm;
+ bool threshold_cross = false;
+
+ if (!gbcl) {
+ pr_err("called before initialization\n");
+ return;
+ }
+
+ if (bcl_get_battery_voltage(&vbatt_mv))
+ return;
+
+ if (bcl_get_resistance(&rbatt_mohm))
+ return;
+
+ iavail_ma = (vbatt_mv - gbcl->bcl_vbat_min) * 1000 / rbatt_mohm;
+
+ gbcl->bcl_rbat_mohm = rbatt_mohm;
+ gbcl->bcl_vbat_mv = vbatt_mv;
+ gbcl->bcl_iavail = iavail_ma;
+
+ pr_debug("iavail %d, vbatt %d rbatt %d\n", iavail_ma, vbatt_mv,
+ rbatt_mohm);
+
+ if ((gbcl->bcl_threshold_mode[BCL_HIGH_THRESHOLD_TYPE] ==
+ BCL_IAVAIL_THRESHOLD_ENABLED)
+ && (iavail_ma >=
+ gbcl->bcl_threshold_value_ma[BCL_HIGH_THRESHOLD_TYPE]))
+ threshold_cross = true;
+ else if ((gbcl->bcl_threshold_mode[BCL_LOW_THRESHOLD_TYPE]
+ == BCL_IAVAIL_THRESHOLD_ENABLED)
+ && (iavail_ma <=
+ gbcl->bcl_threshold_value_ma[BCL_LOW_THRESHOLD_TYPE]))
+ threshold_cross = true;
+
+ if (threshold_cross)
+ sysfs_notify(&gbcl->dev->kobj, NULL, "type");
+}
+
+/*
+ * BCL iavail work
+ */
+static void bcl_iavail_work(struct work_struct *work)
+{
+ struct bcl_context *bcl = container_of(work,
+ struct bcl_context, bcl_iavail_work.work);
+
+ if (gbcl->bcl_mode == BCL_DEVICE_ENABLED) {
+ bcl_calculate_iavail_trigger();
+ /* restart the delay work for caculating imax */
+ schedule_delayed_work(&bcl->bcl_iavail_work,
+ msecs_to_jiffies(bcl->bcl_poll_interval_msec));
+ }
+}
+
+static void bcl_ibat_notify(enum bcl_threshold_state thresh_type)
+{
+ bcl_ibat_state = thresh_type;
+ if (bcl_hotplug_enabled)
+ queue_work(gbcl->bcl_hotplug_wq, &bcl_hotplug_work);
+ update_cpu_freq();
+}
+
+static void bcl_vph_notify(enum bcl_threshold_state thresh_type)
+{
+ bcl_vph_state = thresh_type;
+ if (bcl_hotplug_enabled)
+ queue_work(gbcl->bcl_hotplug_wq, &bcl_hotplug_work);
+ update_cpu_freq();
+}
+
+int bcl_voltage_notify(bool is_high_thresh)
+{
+ int ret = 0;
+
+ if (!gbcl) {
+ pr_err("BCL Driver not configured\n");
+ return -EINVAL;
+ }
+ if (gbcl->bcl_mode == BCL_DEVICE_ENABLED) {
+ pr_err("BCL Driver is enabled\n");
+ return -EINVAL;
+ }
+
+ trace_bcl_sw_mitigation_event((is_high_thresh)
+ ? "vbat High trip notify"
+ : "vbat Low trip notify");
+ bcl_vph_notify((is_high_thresh) ? BCL_HIGH_THRESHOLD
+ : BCL_LOW_THRESHOLD);
+ return ret;
+}
+EXPORT_SYMBOL(bcl_voltage_notify);
+
+int bcl_current_notify(bool is_high_thresh)
+{
+ int ret = 0;
+
+ if (!gbcl) {
+ pr_err("BCL Driver not configured\n");
+ return -EINVAL;
+ }
+ if (gbcl->bcl_mode == BCL_DEVICE_ENABLED) {
+ pr_err("BCL Driver is enabled\n");
+ return -EINVAL;
+ }
+
+ trace_bcl_sw_mitigation_event((is_high_thresh)
+ ? "ibat High trip notify"
+ : "ibat Low trip notify");
+ bcl_ibat_notify((is_high_thresh) ? BCL_HIGH_THRESHOLD
+ : BCL_LOW_THRESHOLD);
+ return ret;
+}
+EXPORT_SYMBOL(bcl_current_notify);
+
+static void bcl_ibat_notification(enum qpnp_tm_state state, void *ctx);
+static void bcl_vph_notification(enum qpnp_tm_state state, void *ctx);
+static int bcl_config_ibat_adc(struct bcl_context *bcl,
+ enum bcl_iavail_threshold_type thresh_type);
+static int bcl_config_vph_adc(struct bcl_context *bcl,
+ enum bcl_iavail_threshold_type thresh_type)
+{
+ int ret = 0;
+
+ if (bcl->bcl_mode == BCL_DEVICE_DISABLED
+ || bcl->bcl_monitor_type != BCL_IBAT_MONITOR_TYPE)
+ return -EINVAL;
+
+ switch (thresh_type) {
+ case BCL_HIGH_THRESHOLD_TYPE:
+ bcl->btm_vph_adc_param.state_request = ADC_TM_HIGH_THR_ENABLE;
+ break;
+ case BCL_LOW_THRESHOLD_TYPE:
+ bcl->btm_vph_adc_param.state_request = ADC_TM_LOW_THR_ENABLE;
+ break;
+ default:
+ pr_err("Invalid threshold type:%d\n", thresh_type);
+ return -EINVAL;
+ }
+ bcl->btm_vph_adc_param.low_thr = bcl->btm_vph_low_thresh;
+ bcl->btm_vph_adc_param.high_thr = bcl->btm_vph_high_thresh;
+ bcl->btm_vph_adc_param.timer_interval =
+ adc_timer_val_usec[ADC_MEAS1_INTERVAL_1S];
+ bcl->btm_vph_adc_param.btm_ctx = bcl;
+ bcl->btm_vph_adc_param.threshold_notification = bcl_vph_notification;
+ bcl->btm_vph_adc_param.channel = bcl->btm_vph_chan;
+
+ ret = qpnp_adc_tm_channel_measure(bcl->btm_adc_tm_dev,
+ &bcl->btm_vph_adc_param);
+ if (ret < 0)
+ pr_err("Error configuring BTM for Vph. ret:%d\n", ret);
+ else
+ pr_debug("Vph config. poll:%d high_uv:%d(%s) low_uv:%d(%s)\n",
+ bcl->btm_vph_adc_param.timer_interval,
+ bcl->btm_vph_adc_param.high_thr,
+ (bcl->btm_vph_adc_param.state_request ==
+ ADC_TM_HIGH_THR_ENABLE) ? "enabled" : "disabled",
+ bcl->btm_vph_adc_param.low_thr,
+ (bcl->btm_vph_adc_param.state_request ==
+ ADC_TM_LOW_THR_ENABLE) ? "enabled" : "disabled");
+
+ return ret;
+}
+
+static int current_to_voltage(struct bcl_context *bcl, int ua)
+{
+ return DIV_ROUND_CLOSEST(ua * bcl->btm_uv_to_ua_denominator,
+ bcl->btm_uv_to_ua_numerator);
+}
+
+static int voltage_to_current(struct bcl_context *bcl, int uv)
+{
+ return DIV_ROUND_CLOSEST(uv * bcl->btm_uv_to_ua_numerator,
+ bcl->btm_uv_to_ua_denominator);
+}
+
+static int adc_time_to_uSec(struct bcl_context *bcl,
+ enum qpnp_adc_meas_timer_1 t)
+{
+ return adc_timer_val_usec[t];
+}
+
+static int uSec_to_adc_time(struct bcl_context *bcl, int us)
+{
+ int i;
+
+ for (i = ARRAY_SIZE(adc_timer_val_usec) - 1;
+ i >= 0 && adc_timer_val_usec[i] > us; i--)
+ ;
+
+ /* disallow continuous mode */
+ if (i <= 0)
+ return -EINVAL;
+
+ return i;
+}
+
+static int vph_disable(void)
+{
+ int ret = 0;
+
+ ret = qpnp_adc_tm_disable_chan_meas(gbcl->btm_adc_tm_dev,
+ &gbcl->btm_vph_adc_param);
+ if (ret) {
+ pr_err("Error disabling ADC. err:%d\n", ret);
+ gbcl->bcl_mode = BCL_DEVICE_ENABLED;
+ gbcl->btm_mode = BCL_VPH_MONITOR_MODE;
+ goto vph_disable_exit;
+ }
+ bcl_vph_notify(BCL_THRESHOLD_DISABLED);
+ gbcl->btm_mode = BCL_MONITOR_DISABLED;
+
+vph_disable_exit:
+ return ret;
+}
+
+static int ibat_disable(void)
+{
+ int ret = 0;
+
+ ret = qpnp_adc_tm_disable_chan_meas(gbcl->btm_adc_tm_dev,
+ &gbcl->btm_ibat_adc_param);
+ if (ret) {
+ pr_err("Error disabling ADC. err:%d\n", ret);
+ gbcl->bcl_mode = BCL_DEVICE_ENABLED;
+ gbcl->btm_mode = BCL_IBAT_MONITOR_MODE;
+ goto ibat_disable_exit;
+ }
+ bcl_ibat_notify(BCL_THRESHOLD_DISABLED);
+
+ibat_disable_exit:
+ return ret;
+}
+
+static void bcl_periph_ibat_notify(enum bcl_trip_type type, int trip_temp,
+ void *data)
+{
+ if (type == BCL_HIGH_TRIP)
+ bcl_ibat_notify(BCL_HIGH_THRESHOLD);
+ else
+ bcl_ibat_notify(BCL_LOW_THRESHOLD);
+}
+
+static void bcl_periph_vbat_notify(enum bcl_trip_type type, int trip_temp,
+ void *data)
+{
+ if (type == BCL_HIGH_TRIP)
+ bcl_vph_notify(BCL_HIGH_THRESHOLD);
+ else
+ bcl_vph_notify(BCL_LOW_THRESHOLD);
+}
+
+static void bcl_periph_mode_set(enum bcl_device_mode mode)
+{
+ int ret = 0;
+
+ if (mode == BCL_DEVICE_ENABLED) {
+ /*
+ * Power supply monitor wont send a callback till the
+ * power state changes. Make sure we read the current SoC
+ * and mitigate.
+ */
+ get_and_evaluate_battery_soc();
+ ret = power_supply_reg_notifier(&gbcl->psy_nb);
+ if (ret < 0) {
+ pr_err("Unable to register soc notifier rc = %d\n",
+ ret);
+ return;
+ }
+ ret = msm_bcl_set_threshold(BCL_PARAM_CURRENT, BCL_HIGH_TRIP,
+ &gbcl->ibat_high_thresh);
+ if (ret) {
+ pr_err("Error setting Ibat high threshold. err:%d\n",
+ ret);
+ return;
+ }
+ ret = msm_bcl_set_threshold(BCL_PARAM_CURRENT, BCL_LOW_TRIP,
+ &gbcl->ibat_low_thresh);
+ if (ret) {
+ pr_err("Error setting Ibat low threshold. err:%d\n",
+ ret);
+ return;
+ }
+ ret = msm_bcl_set_threshold(BCL_PARAM_VOLTAGE, BCL_LOW_TRIP,
+ &gbcl->vbat_low_thresh);
+ if (ret) {
+ pr_err("Error setting Vbat low threshold. err:%d\n",
+ ret);
+ return;
+ }
+ ret = msm_bcl_set_threshold(BCL_PARAM_VOLTAGE, BCL_HIGH_TRIP,
+ &gbcl->vbat_high_thresh);
+ if (ret) {
+ pr_err("Error setting Vbat high threshold. err:%d\n",
+ ret);
+ return;
+ }
+ ret = msm_bcl_enable();
+ if (ret) {
+ pr_err("Error enabling BCL\n");
+ return;
+ }
+ gbcl->btm_mode = BCL_VPH_MONITOR_MODE;
+ } else {
+ power_supply_unreg_notifier(&gbcl->psy_nb);
+ ret = msm_bcl_disable();
+ if (ret) {
+ pr_err("Error disabling BCL\n");
+ return;
+ }
+ gbcl->btm_mode = BCL_MONITOR_DISABLED;
+ bcl_soc_state = BCL_THRESHOLD_DISABLED;
+ bcl_vph_notify(BCL_HIGH_THRESHOLD);
+ bcl_ibat_notify(BCL_LOW_THRESHOLD);
+ bcl_handle_hotplug(NULL);
+ }
+}
+
+static void ibat_mode_set(enum bcl_device_mode mode)
+{
+ int ret = 0;
+
+ if (mode == BCL_DEVICE_ENABLED) {
+ gbcl->btm_mode = BCL_VPH_MONITOR_MODE;
+ ret = bcl_config_vph_adc(gbcl, BCL_LOW_THRESHOLD_TYPE);
+ if (ret) {
+ pr_err("Vph config error. ret:%d\n", ret);
+ gbcl->bcl_mode = BCL_DEVICE_DISABLED;
+ gbcl->btm_mode = BCL_MONITOR_DISABLED;
+ return;
+ }
+ } else {
+ switch (gbcl->btm_mode) {
+ case BCL_IBAT_MONITOR_MODE:
+ case BCL_IBAT_HIGH_LOAD_MODE:
+ ret = ibat_disable();
+ if (ret)
+ return;
+ ret = vph_disable();
+ if (ret)
+ return;
+ break;
+ case BCL_VPH_MONITOR_MODE:
+ ret = vph_disable();
+ if (ret)
+ return;
+ break;
+ case BCL_MONITOR_DISABLED:
+ default:
+ break;
+ }
+ gbcl->btm_mode = BCL_MONITOR_DISABLED;
+ }
+}
+
+static void bcl_vph_notification(enum qpnp_tm_state state, void *ctx)
+{
+ struct bcl_context *bcl = ctx;
+ int ret = 0;
+
+ mutex_lock(&bcl_notify_mutex);
+ if (bcl->btm_mode == BCL_MONITOR_DISABLED)
+ goto unlock_and_exit;
+
+ switch (state) {
+ case ADC_TM_LOW_STATE:
+ if (bcl->btm_mode != BCL_VPH_MONITOR_MODE) {
+ pr_err("Low thresh received with invalid btm mode:%d\n",
+ bcl->btm_mode);
+ ibat_mode_set(BCL_DEVICE_DISABLED);
+ goto unlock_and_exit;
+ }
+ pr_debug("Initiating Ibat current monitoring\n");
+ bcl_vph_notify(BCL_LOW_THRESHOLD);
+ bcl_config_ibat_adc(gbcl, BCL_HIGH_THRESHOLD_TYPE);
+ bcl_config_vph_adc(gbcl, BCL_HIGH_THRESHOLD_TYPE);
+ bcl->btm_mode = BCL_IBAT_MONITOR_MODE;
+ break;
+ case ADC_TM_HIGH_STATE:
+ if (bcl->btm_mode != BCL_IBAT_MONITOR_MODE
+ && bcl->btm_mode != BCL_IBAT_HIGH_LOAD_MODE) {
+ pr_err("High thresh received with invalid btm mode:%d\n"
+ , bcl->btm_mode);
+ ibat_mode_set(BCL_DEVICE_DISABLED);
+ goto unlock_and_exit;
+ }
+ pr_debug("Exiting Ibat current monitoring\n");
+ bcl->btm_mode = BCL_VPH_MONITOR_MODE;
+ ret = ibat_disable();
+ if (ret) {
+ pr_err("Error disabling ibat ADC. err:%d\n", ret);
+ goto unlock_and_exit;
+ }
+ bcl_vph_notify(BCL_HIGH_THRESHOLD);
+ bcl_config_vph_adc(gbcl, BCL_LOW_THRESHOLD_TYPE);
+ break;
+ default:
+ goto set_thresh;
+ }
+unlock_and_exit:
+ mutex_unlock(&bcl_notify_mutex);
+ return;
+
+set_thresh:
+ mutex_unlock(&bcl_notify_mutex);
+ bcl_config_vph_adc(gbcl, BCL_HIGH_THRESHOLD_TYPE);
+}
+
+/*
+ * Set BCL mode
+ */
+static void bcl_mode_set(enum bcl_device_mode mode)
+{
+ if (!gbcl)
+ return;
+ if (gbcl->bcl_mode == mode)
+ return;
+
+ gbcl->bcl_mode = mode;
+ switch (gbcl->bcl_monitor_type) {
+ case BCL_IAVAIL_MONITOR_TYPE:
+ if (mode == BCL_DEVICE_ENABLED)
+ schedule_delayed_work(&gbcl->bcl_iavail_work, 0);
+ else
+ cancel_delayed_work_sync(&(gbcl->bcl_iavail_work));
+ break;
+ case BCL_IBAT_MONITOR_TYPE:
+ ibat_mode_set(mode);
+ break;
+ case BCL_IBAT_PERIPH_MONITOR_TYPE:
+ bcl_periph_mode_set(mode);
+ break;
+ default:
+ pr_err("Invalid monitor type:%d\n", gbcl->bcl_monitor_type);
+ break;
+ }
+}
+
+#define show_bcl(name, variable, format) \
+static ssize_t \
+name##_show(struct device *dev, struct device_attribute *attr, char *buf) \
+{ \
+ if (gbcl) \
+ return snprintf(buf, PAGE_SIZE, format, variable); \
+ else \
+ return -EPERM; \
+}
+
+show_bcl(type, gbcl->bcl_type, "%s\n")
+show_bcl(vbat, gbcl->bcl_vbat_mv, "%d\n")
+show_bcl(rbat, gbcl->bcl_rbat_mohm, "%d\n")
+show_bcl(iavail, gbcl->bcl_iavail, "%d\n")
+show_bcl(vbat_min, gbcl->bcl_vbat_min, "%d\n")
+show_bcl(poll_interval, gbcl->bcl_poll_interval_msec, "%d\n")
+show_bcl(high_ua, (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE) ?
+ voltage_to_current(gbcl, gbcl->btm_high_threshold_uv)
+ : gbcl->ibat_high_thresh.trip_value, "%d\n")
+show_bcl(low_ua, (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE) ?
+ voltage_to_current(gbcl, gbcl->btm_low_threshold_uv)
+ : gbcl->ibat_low_thresh.trip_value, "%d\n")
+show_bcl(adc_interval_us, (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE) ?
+ adc_time_to_uSec(gbcl, gbcl->btm_adc_interval) : 0, "%d\n")
+show_bcl(freq_max, (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE) ?
+ gbcl->btm_freq_max : gbcl->bcl_p_freq_max, "%u\n")
+show_bcl(vph_high, (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE) ?
+ gbcl->btm_vph_high_thresh : gbcl->vbat_high_thresh.trip_value, "%d\n")
+show_bcl(vph_low, (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE) ?
+ gbcl->btm_vph_low_thresh : gbcl->vbat_low_thresh.trip_value, "%d\n")
+show_bcl(freq_limit, gbcl->thermal_freq_limit, "%u\n")
+show_bcl(vph_state, bcl_vph_state, "%d\n")
+show_bcl(ibat_state, bcl_ibat_state, "%d\n")
+show_bcl(hotplug_mask, bcl_hotplug_mask, "%d\n")
+show_bcl(hotplug_soc_mask, bcl_soc_hotplug_mask, "%d\n")
+show_bcl(hotplug_status, bcl_hotplug_request, "%d\n")
+show_bcl(soc_low_thresh, soc_low_threshold, "%d\n")
+
+static ssize_t
+mode_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ if (!gbcl)
+ return -EPERM;
+
+ return snprintf(buf, PAGE_SIZE, "%s\n",
+ gbcl->bcl_mode == BCL_DEVICE_ENABLED ? "enabled"
+ : "disabled");
+}
+
+static ssize_t
+mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ if (!gbcl)
+ return -EPERM;
+
+ if (!strcmp(buf, "enable")) {
+ bcl_mode_set(BCL_DEVICE_ENABLED);
+ pr_info("bcl enabled\n");
+ } else if (!strcmp(buf, "disable")) {
+ bcl_mode_set(BCL_DEVICE_DISABLED);
+ pr_info("bcl disabled\n");
+ } else {
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t
+poll_interval_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int value = 0, ret = 0;
+
+ if (!gbcl)
+ return -EPERM;
+
+ ret = kstrtoint(buf, 10, &value);
+ if (!ret)
+ return ret;
+
+ if (value < MIN_BCL_POLL_INTERVAL)
+ return -EINVAL;
+
+ gbcl->bcl_poll_interval_msec = value;
+
+ return count;
+}
+
+static ssize_t vbat_min_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int value = 0;
+ int ret = 0;
+
+ if (!gbcl)
+ return -EPERM;
+
+ ret = kstrtoint(buf, 10, &value);
+
+ if (ret || (value < 0)) {
+ pr_err("Incorrect vbatt min value\n");
+ return -EINVAL;
+ }
+
+ gbcl->bcl_vbat_min = value;
+ return count;
+}
+
+static ssize_t iavail_low_threshold_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if (!gbcl)
+ return -EPERM;
+
+ return snprintf(buf, PAGE_SIZE, "%s\n",
+ gbcl->bcl_threshold_mode[BCL_LOW_THRESHOLD_TYPE]
+ == BCL_IAVAIL_THRESHOLD_ENABLED ? "enabled" : "disabled");
+}
+
+static ssize_t iavail_low_threshold_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ if (!gbcl)
+ return -EPERM;
+
+ if (!strcmp(buf, "enable"))
+ gbcl->bcl_threshold_mode[BCL_LOW_THRESHOLD_TYPE]
+ = BCL_IAVAIL_THRESHOLD_ENABLED;
+ else if (!strcmp(buf, "disable"))
+ gbcl->bcl_threshold_mode[BCL_LOW_THRESHOLD_TYPE]
+ = BCL_IAVAIL_THRESHOLD_DISABLED;
+ else
+ return -EINVAL;
+
+ return count;
+}
+static ssize_t iavail_high_threshold_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if (!gbcl)
+ return -EPERM;
+
+ return snprintf(buf, PAGE_SIZE, "%s\n",
+ gbcl->bcl_threshold_mode[BCL_HIGH_THRESHOLD_TYPE]
+ == BCL_IAVAIL_THRESHOLD_ENABLED ? "enabled" : "disabled");
+}
+
+static ssize_t iavail_high_threshold_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ if (!gbcl)
+ return -EPERM;
+
+ if (!strcmp(buf, "enable"))
+ gbcl->bcl_threshold_mode[BCL_HIGH_THRESHOLD_TYPE]
+ = BCL_IAVAIL_THRESHOLD_ENABLED;
+ else if (!strcmp(buf, "disable"))
+ gbcl->bcl_threshold_mode[BCL_HIGH_THRESHOLD_TYPE]
+ = BCL_IAVAIL_THRESHOLD_DISABLED;
+ else
+ return -EINVAL;
+
+ return count;
+}
+
+static ssize_t iavail_low_threshold_value_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if (!gbcl)
+ return -EPERM;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ gbcl->bcl_threshold_value_ma[BCL_LOW_THRESHOLD_TYPE]);
+}
+
+
+static ssize_t iavail_low_threshold_value_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+ int ret = 0;
+
+ if (!gbcl)
+ return -EPERM;
+
+ ret = kstrtoint(buf, 10, &val);
+
+ if (ret || (val < 0)) {
+ pr_err("Incorrect available current threshold value\n");
+ return -EINVAL;
+ }
+
+ gbcl->bcl_threshold_value_ma[BCL_LOW_THRESHOLD_TYPE] = val;
+
+ return count;
+}
+static ssize_t iavail_high_threshold_value_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if (!gbcl)
+ return -EPERM;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ gbcl->bcl_threshold_value_ma[BCL_HIGH_THRESHOLD_TYPE]);
+}
+
+static ssize_t iavail_high_threshold_value_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+ int ret = 0;
+
+ if (!gbcl)
+ return -EPERM;
+ ret = kstrtoint(buf, 10, &val);
+
+ if (ret || (val < 0)) {
+ pr_err("Incorrect available current threshold value\n");
+ return -EINVAL;
+ }
+
+ gbcl->bcl_threshold_value_ma[BCL_HIGH_THRESHOLD_TYPE] = val;
+
+ return count;
+}
+
+static int convert_to_int(const char *buf, int *val)
+{
+ int ret = 0;
+
+ if (!gbcl)
+ return -EPERM;
+ if (gbcl->bcl_mode != BCL_DEVICE_DISABLED) {
+ pr_err("BCL is not disabled\n");
+ return -EINVAL;
+ }
+
+ ret = kstrtoint(buf, 10, val);
+ if (ret || (*val < 0)) {
+ pr_err("Invalid high threshold %s val:%d ret:%d\n", buf, *val,
+ ret);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static ssize_t high_ua_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+ int ret = 0;
+
+ ret = convert_to_int(buf, &val);
+ if (ret)
+ return ret;
+
+ if (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE)
+ gbcl->btm_high_threshold_uv = current_to_voltage(gbcl, val);
+ else
+ gbcl->ibat_high_thresh.trip_value = val;
+
+ return count;
+}
+
+static ssize_t low_ua_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+ int ret = 0;
+
+ ret = convert_to_int(buf, &val);
+ if (ret)
+ return ret;
+
+ if (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE)
+ gbcl->btm_low_threshold_uv = current_to_voltage(gbcl, val);
+ else
+ gbcl->ibat_low_thresh.trip_value = val;
+
+ return count;
+}
+
+static ssize_t freq_max_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+ int ret = 0;
+ uint32_t *freq_lim = NULL;
+
+ ret = convert_to_int(buf, &val);
+ if (ret)
+ return ret;
+ freq_lim = (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE) ?
+ &gbcl->btm_freq_max : &gbcl->bcl_p_freq_max;
+ *freq_lim = max_t(uint32_t, val, gbcl->thermal_freq_limit);
+
+ return count;
+}
+
+static ssize_t vph_low_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+ int ret = 0;
+ int *thresh = NULL;
+
+ ret = convert_to_int(buf, &val);
+ if (ret)
+ return ret;
+
+ thresh = (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE)
+ ? (int *)&gbcl->btm_vph_low_thresh
+ : &gbcl->vbat_low_thresh.trip_value;
+ *thresh = val;
+
+ return count;
+}
+
+static ssize_t vph_high_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val = 0;
+ int ret = 0;
+ int *thresh = NULL;
+
+ ret = convert_to_int(buf, &val);
+ if (ret)
+ return ret;
+
+ thresh = (gbcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE)
+ ? (int *)&gbcl->btm_vph_high_thresh
+ : &gbcl->vbat_high_thresh.trip_value;
+ *thresh = val;
+
+ return count;
+}
+
+static ssize_t hotplug_mask_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = 0, val = 0;
+
+ ret = convert_to_int(buf, &val);
+ if (ret)
+ return ret;
+
+ bcl_hotplug_mask = val;
+ pr_info("bcl hotplug mask updated to %d\n", bcl_hotplug_mask);
+
+ if (!bcl_hotplug_mask && !bcl_soc_hotplug_mask)
+ bcl_hotplug_enabled = false;
+ else
+ bcl_hotplug_enabled = true;
+
+ return count;
+}
+
+static ssize_t hotplug_soc_mask_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = 0, val = 0;
+
+ ret = convert_to_int(buf, &val);
+ if (ret)
+ return ret;
+
+ bcl_soc_hotplug_mask = val;
+ pr_info("bcl soc hotplug mask updated to %d\n", bcl_soc_hotplug_mask);
+
+ if (!bcl_hotplug_mask && !bcl_soc_hotplug_mask)
+ bcl_hotplug_enabled = false;
+ else
+ bcl_hotplug_enabled = true;
+
+ return count;
+}
+
+static ssize_t soc_low_thresh_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int val = 0;
+ int ret = 0;
+
+ ret = convert_to_int(buf, &val);
+ if (ret)
+ return ret;
+
+ soc_low_threshold = val;
+ pr_info("bcl soc low threshold updated to %d\n", soc_low_threshold);
+
+ return count;
+}
+
+/*
+ * BCL device attributes
+ */
+static struct device_attribute bcl_dev_attr[] = {
+ __ATTR(type, 0444, type_show, NULL),
+ __ATTR(iavail, 0444, iavail_show, NULL),
+ __ATTR(vbat_min, 0644, vbat_min_show, vbat_min_store),
+ __ATTR(vbat, 0444, vbat_show, NULL),
+ __ATTR(rbat, 0444, rbat_show, NULL),
+ __ATTR(mode, 0644, mode_show, mode_store),
+ __ATTR(poll_interval, 0644,
+ poll_interval_show, poll_interval_store),
+ __ATTR(iavail_low_threshold_mode, 0644,
+ iavail_low_threshold_mode_show,
+ iavail_low_threshold_mode_store),
+ __ATTR(iavail_high_threshold_mode, 0644,
+ iavail_high_threshold_mode_show,
+ iavail_high_threshold_mode_store),
+ __ATTR(iavail_low_threshold_value, 0644,
+ iavail_low_threshold_value_show,
+ iavail_low_threshold_value_store),
+ __ATTR(iavail_high_threshold_value, 0644,
+ iavail_high_threshold_value_show,
+ iavail_high_threshold_value_store),
+};
+
+static struct device_attribute btm_dev_attr[] = {
+ __ATTR(type, 0444, type_show, NULL),
+ __ATTR(mode, 0644, mode_show, mode_store),
+ __ATTR(vph_state, 0444, vph_state_show, NULL),
+ __ATTR(ibat_state, 0444, ibat_state_show, NULL),
+ __ATTR(high_threshold_ua, 0644, high_ua_show, high_ua_store),
+ __ATTR(low_threshold_ua, 0644, low_ua_show, low_ua_store),
+ __ATTR(adc_interval_us, 0444, adc_interval_us_show, NULL),
+ __ATTR(freq_max, 0644, freq_max_show, freq_max_store),
+ __ATTR(vph_high_thresh_uv, 0644, vph_high_show, vph_high_store),
+ __ATTR(vph_low_thresh_uv, 0644, vph_low_show, vph_low_store),
+ __ATTR(thermal_freq_limit, 0444, freq_limit_show, NULL),
+ __ATTR(hotplug_status, 0444, hotplug_status_show, NULL),
+ __ATTR(hotplug_mask, 0644, hotplug_mask_show, hotplug_mask_store),
+ __ATTR(hotplug_soc_mask, 0644, hotplug_soc_mask_show,
+ hotplug_soc_mask_store),
+ __ATTR(soc_low_thresh, 0644, soc_low_thresh_show, soc_low_thresh_store),
+};
+
+static int create_bcl_sysfs(struct bcl_context *bcl)
+{
+ int result = 0, num_attr = 0, i;
+ struct device_attribute *attr_ptr = NULL;
+
+ switch (bcl->bcl_monitor_type) {
+ case BCL_IAVAIL_MONITOR_TYPE:
+ num_attr = sizeof(bcl_dev_attr)/sizeof(struct device_attribute);
+ attr_ptr = bcl_dev_attr;
+ break;
+ case BCL_IBAT_MONITOR_TYPE:
+ case BCL_IBAT_PERIPH_MONITOR_TYPE:
+ num_attr = sizeof(btm_dev_attr)/sizeof(struct device_attribute);
+ attr_ptr = btm_dev_attr;
+ break;
+ default:
+ pr_err("Invalid monitor type:%d\n", bcl->bcl_monitor_type);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < num_attr; i++) {
+ result = device_create_file(bcl->dev, &attr_ptr[i]);
+ if (result < 0)
+ return result;
+ }
+
+ return result;
+}
+
+static void remove_bcl_sysfs(struct bcl_context *bcl)
+{
+ int num_attr = 0, i;
+ struct device_attribute *attr_ptr = NULL;
+
+ switch (bcl->bcl_monitor_type) {
+ case BCL_IAVAIL_MONITOR_TYPE:
+ num_attr = sizeof(bcl_dev_attr)/sizeof(struct device_attribute);
+ attr_ptr = bcl_dev_attr;
+ break;
+ case BCL_IBAT_MONITOR_TYPE:
+ num_attr = sizeof(btm_dev_attr)/sizeof(struct device_attribute);
+ attr_ptr = btm_dev_attr;
+ break;
+ default:
+ pr_err("Invalid monitor type:%d\n", bcl->bcl_monitor_type);
+ return;
+ }
+
+ for (i = 0; i < num_attr; i++)
+ device_remove_file(bcl->dev, &attr_ptr[i]);
+}
+
+static int bcl_config_ibat_adc(struct bcl_context *bcl,
+ enum bcl_iavail_threshold_type thresh_type)
+{
+ int ret = 0;
+
+ if (bcl->bcl_mode == BCL_DEVICE_DISABLED
+ || bcl->bcl_monitor_type != BCL_IBAT_MONITOR_TYPE)
+ return -EINVAL;
+
+ switch (thresh_type) {
+ case BCL_HIGH_THRESHOLD_TYPE:
+ bcl->btm_ibat_adc_param.state_request = ADC_TM_HIGH_THR_ENABLE;
+ break;
+ case BCL_LOW_THRESHOLD_TYPE:
+ bcl->btm_ibat_adc_param.state_request = ADC_TM_LOW_THR_ENABLE;
+ break;
+ default:
+ pr_err("Invalid threshold type:%d\n", thresh_type);
+ return -EINVAL;
+ }
+
+ bcl->btm_ibat_adc_param.low_thr = bcl->btm_low_threshold_uv;
+ bcl->btm_ibat_adc_param.high_thr = bcl->btm_high_threshold_uv;
+ bcl->btm_ibat_adc_param.timer_interval = bcl->btm_adc_interval;
+ bcl->btm_ibat_adc_param.btm_ctx = bcl;
+ bcl->btm_ibat_adc_param.threshold_notification = bcl_ibat_notification;
+ bcl->btm_ibat_adc_param.channel = bcl->btm_ibat_chan;
+
+ ret = qpnp_adc_tm_channel_measure(bcl->btm_adc_tm_dev,
+ &bcl->btm_ibat_adc_param);
+ if (ret < 0)
+ pr_err("Error configuring BTM. ret:%d\n", ret);
+ else
+ pr_debug("BTM config. poll:%d high_uv:%d(%s) low_uv:%d(%s)\n",
+ bcl->btm_adc_interval,
+ bcl->btm_ibat_adc_param.high_thr,
+ (bcl->btm_ibat_adc_param.state_request ==
+ ADC_TM_HIGH_THR_ENABLE) ? "enabled" : "disabled",
+ bcl->btm_ibat_adc_param.low_thr,
+ (bcl->btm_ibat_adc_param.state_request ==
+ ADC_TM_LOW_THR_ENABLE) ? "enabled" : "disabled");
+ return ret;
+}
+
+static void bcl_ibat_notification(enum qpnp_tm_state state, void *ctx)
+{
+ struct bcl_context *bcl = ctx;
+ int ret = 0;
+
+ mutex_lock(&bcl_notify_mutex);
+ if (bcl->btm_mode == BCL_MONITOR_DISABLED ||
+ bcl->btm_mode == BCL_VPH_MONITOR_MODE)
+ goto unlock_and_return;
+
+ switch (state) {
+ case ADC_TM_LOW_STATE:
+ if (bcl->btm_mode != BCL_IBAT_HIGH_LOAD_MODE)
+ goto set_ibat_threshold;
+ pr_debug("ibat low load enter\n");
+ bcl->btm_mode = BCL_IBAT_MONITOR_MODE;
+ bcl_ibat_notify(BCL_LOW_THRESHOLD);
+ break;
+ case ADC_TM_HIGH_STATE:
+ if (bcl->btm_mode != BCL_IBAT_MONITOR_MODE)
+ goto set_ibat_threshold;
+ pr_debug("ibat high load enter\n");
+ bcl->btm_mode = BCL_IBAT_HIGH_LOAD_MODE;
+ bcl_ibat_notify(BCL_HIGH_THRESHOLD);
+ break;
+ default:
+ pr_err("Invalid threshold state:%d\n", state);
+ bcl_config_ibat_adc(bcl, BCL_HIGH_THRESHOLD_TYPE);
+ goto unlock_and_return;
+ }
+
+set_ibat_threshold:
+ ret = bcl_config_ibat_adc(bcl, (state == ADC_TM_LOW_STATE) ?
+ BCL_HIGH_THRESHOLD_TYPE : BCL_LOW_THRESHOLD_TYPE);
+ if (ret < 0)
+ pr_err("Error configuring %s thresh. err:%d\n",
+ (state == ADC_TM_LOW_STATE) ? "high" : "low", ret);
+unlock_and_return:
+ mutex_unlock(&bcl_notify_mutex);
+}
+
+static int bcl_suspend(struct device *dev)
+{
+ int ret = 0;
+ struct bcl_context *bcl = dev_get_drvdata(dev);
+
+ if (bcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE &&
+ bcl->bcl_mode == BCL_DEVICE_ENABLED) {
+ switch (bcl->btm_mode) {
+ case BCL_IBAT_MONITOR_MODE:
+ case BCL_IBAT_HIGH_LOAD_MODE:
+ ret = ibat_disable();
+ if (!ret)
+ vph_disable();
+ break;
+ case BCL_VPH_MONITOR_MODE:
+ vph_disable();
+ break;
+ case BCL_MONITOR_DISABLED:
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+static int bcl_resume(struct device *dev)
+{
+ struct bcl_context *bcl = dev_get_drvdata(dev);
+
+ if (bcl->bcl_monitor_type == BCL_IBAT_MONITOR_TYPE &&
+ bcl->bcl_mode == BCL_DEVICE_ENABLED) {
+ bcl->btm_mode = BCL_VPH_MONITOR_MODE;
+ bcl_config_vph_adc(bcl, BCL_LOW_THRESHOLD_TYPE);
+ }
+ return 0;
+}
+
+static void get_vdd_rstr_freq(struct bcl_context *bcl,
+ struct device_node *ibat_node)
+{
+ int ret = 0;
+ struct device_node *phandle = NULL;
+ char *key = NULL;
+
+ key = "qcom,thermal-handle";
+ phandle = of_parse_phandle(ibat_node, key, 0);
+ if (!phandle) {
+ pr_err("Thermal handle not present\n");
+ ret = -ENODEV;
+ goto vdd_rstr_exit;
+ }
+ key = "qcom,levels";
+ ret = of_property_read_u32_index(phandle, key, 0,
+ &bcl->thermal_freq_limit);
+ if (ret) {
+ pr_err("Error reading property %s. ret:%d\n", key, ret);
+ goto vdd_rstr_exit;
+ }
+
+vdd_rstr_exit:
+ if (ret)
+ bcl->thermal_freq_limit = BTM_8084_FREQ_MITIG_LIMIT;
+}
+
+static int probe_bcl_periph_prop(struct bcl_context *bcl)
+{
+ int ret = 0;
+ struct device_node *ibat_node = NULL, *dev_node = bcl->dev->of_node;
+ char *key = NULL;
+
+ key = "qcom,ibat-monitor";
+ ibat_node = of_find_node_by_name(dev_node, key);
+ if (!ibat_node) {
+ ret = -ENODEV;
+ goto ibat_probe_exit;
+ }
+
+ BCL_FETCH_DT_U32(ibat_node, key, "qcom,low-threshold-uamp", ret,
+ bcl->ibat_low_thresh.trip_value);
+ if (ret)
+ goto ibat_probe_exit;
+ BCL_FETCH_DT_U32(ibat_node, key, "qcom,high-threshold-uamp", ret,
+ bcl->ibat_high_thresh.trip_value);
+ if (ret)
+ goto ibat_probe_exit;
+
+ BCL_FETCH_DT_U32(ibat_node, key, "qcom,vph-high-threshold-uv", ret,
+ bcl->vbat_high_thresh.trip_value);
+ if (ret)
+ goto ibat_probe_exit;
+ BCL_FETCH_DT_U32(ibat_node, key, "qcom,vph-low-threshold-uv", ret,
+ bcl->vbat_low_thresh.trip_value);
+ if (ret)
+ goto ibat_probe_exit;
+ BCL_FETCH_DT_U32(ibat_node, key, "qcom,soc-low-threshold", ret,
+ soc_low_threshold);
+ if (ret)
+ goto ibat_probe_exit;
+
+ bcl->vbat_high_thresh.trip_notify
+ = bcl->vbat_low_thresh.trip_notify = bcl_periph_vbat_notify;
+ bcl->vbat_high_thresh.trip_data
+ = bcl->vbat_low_thresh.trip_data = (void *) bcl;
+ bcl->ibat_high_thresh.trip_notify
+ = bcl->ibat_low_thresh.trip_notify = bcl_periph_ibat_notify;
+ bcl->ibat_high_thresh.trip_data
+ = bcl->ibat_low_thresh.trip_data = (void *) bcl;
+
+ if (bcl_frequency_mask) {
+ BCL_FETCH_DT_U32(ibat_node, key, "qcom,mitigation-freq-khz",
+ ret, bcl->bcl_p_freq_max);
+ if (ret)
+ goto ibat_probe_exit;
+ get_vdd_rstr_freq(bcl, ibat_node);
+ } else {
+ bcl->bcl_p_freq_max = UINT_MAX;
+ bcl->thermal_freq_limit = 0;
+ }
+
+ bcl->bcl_p_freq_max = max(bcl->bcl_p_freq_max, bcl->thermal_freq_limit);
+
+ bcl->btm_mode = BCL_MONITOR_DISABLED;
+ bcl->bcl_monitor_type = BCL_IBAT_PERIPH_MONITOR_TYPE;
+ snprintf(bcl->bcl_type, BCL_NAME_LENGTH, "%s",
+ bcl_type[BCL_IBAT_PERIPH_MONITOR_TYPE]);
+
+ibat_probe_exit:
+ if (ret && ret != -EPROBE_DEFER)
+ dev_info(bcl->dev, "%s:%s Error reading key:%s. ret = %d\n",
+ KBUILD_MODNAME, __func__, key, ret);
+
+ return ret;
+}
+
+static int probe_btm_properties(struct bcl_context *bcl)
+{
+ int ret = 0, curr_ua = 0;
+ int adc_interval_us;
+ struct device_node *ibat_node = NULL, *dev_node = bcl->dev->of_node;
+ char *key = NULL;
+
+ key = "qcom,ibat-monitor";
+ ibat_node = of_find_node_by_name(dev_node, key);
+ if (!ibat_node) {
+ ret = -ENODEV;
+ goto btm_probe_exit;
+ }
+
+ key = "qcom,uv-to-ua-numerator";
+ ret = of_property_read_u32(ibat_node, key,
+ &bcl->btm_uv_to_ua_numerator);
+ if (ret < 0)
+ goto btm_probe_exit;
+
+ key = "qcom,uv-to-ua-denominator";
+ ret = of_property_read_u32(ibat_node, key,
+ &bcl->btm_uv_to_ua_denominator);
+ if (ret < 0)
+ goto btm_probe_exit;
+
+ key = "qcom,low-threshold-uamp";
+ ret = of_property_read_u32(ibat_node, key, &curr_ua);
+ if (ret < 0)
+ goto btm_probe_exit;
+ bcl->btm_low_threshold_uv = current_to_voltage(bcl, curr_ua);
+
+ key = "qcom,high-threshold-uamp";
+ ret = of_property_read_u32(ibat_node, key, &curr_ua);
+ if (ret < 0)
+ goto btm_probe_exit;
+ bcl->btm_high_threshold_uv = current_to_voltage(bcl, curr_ua);
+
+ key = "qcom,ibat-channel";
+ ret = of_property_read_u32(ibat_node, key, &bcl->btm_ibat_chan);
+ if (ret < 0)
+ goto btm_probe_exit;
+
+ key = "qcom,adc-interval-usec";
+ ret = of_property_read_u32(ibat_node, key, &adc_interval_us);
+ if (ret < 0)
+ goto btm_probe_exit;
+ bcl->btm_adc_interval = uSec_to_adc_time(bcl, adc_interval_us);
+
+ key = "qcom,vph-channel";
+ ret = of_property_read_u32(ibat_node, key, &bcl->btm_vph_chan);
+ if (ret < 0)
+ goto btm_probe_exit;
+
+ key = "qcom,vph-high-threshold-uv";
+ ret = of_property_read_u32(ibat_node, key, &bcl->btm_vph_high_thresh);
+ if (ret < 0)
+ goto btm_probe_exit;
+
+ key = "qcom,vph-low-threshold-uv";
+ ret = of_property_read_u32(ibat_node, key, &bcl->btm_vph_low_thresh);
+ if (ret < 0)
+ goto btm_probe_exit;
+
+ key = "ibat-threshold";
+ bcl->btm_adc_tm_dev = qpnp_get_adc_tm(bcl->dev, key);
+ if (IS_ERR(bcl->btm_adc_tm_dev)) {
+ ret = PTR_ERR(bcl->btm_adc_tm_dev);
+ goto btm_probe_exit;
+ }
+
+ key = "ibat";
+ bcl->btm_vadc_dev = qpnp_get_vadc(bcl->dev, key);
+ if (IS_ERR(bcl->btm_vadc_dev)) {
+ ret = PTR_ERR(bcl->btm_vadc_dev);
+ goto btm_probe_exit;
+ }
+
+ if (bcl_frequency_mask) {
+ key = "qcom,mitigation-freq-khz";
+ ret = of_property_read_u32(ibat_node, key, &bcl->btm_freq_max);
+ if (ret < 0)
+ goto btm_probe_exit;
+ get_vdd_rstr_freq(bcl, ibat_node);
+ } else {
+ bcl->btm_freq_max = UINT_MAX;
+ bcl->thermal_freq_limit = 0;
+ }
+ bcl->btm_freq_max = max(bcl->btm_freq_max, bcl->thermal_freq_limit);
+
+ bcl->btm_mode = BCL_MONITOR_DISABLED;
+ bcl->bcl_monitor_type = BCL_IBAT_MONITOR_TYPE;
+ snprintf(bcl->bcl_type, BCL_NAME_LENGTH, "%s",
+ bcl_type[BCL_IBAT_MONITOR_TYPE]);
+
+btm_probe_exit:
+ if (ret && ret != -EPROBE_DEFER)
+ dev_info(bcl->dev, "%s:%s Error reading key:%s. ret = %d\n",
+ KBUILD_MODNAME, __func__, key, ret);
+
+ return ret;
+}
+
+static uint32_t get_mask_from_core_handle(struct platform_device *pdev,
+ const char *key)
+{
+ struct device_node *core_phandle = NULL;
+ int i = 0, cpu = 0;
+ uint32_t mask = 0;
+
+ core_phandle = of_parse_phandle(pdev->dev.of_node,
+ key, i++);
+ while (core_phandle) {
+ for_each_possible_cpu(cpu) {
+ if (of_get_cpu_node(cpu, NULL) == core_phandle) {
+ mask |= BIT(cpu);
+ break;
+ }
+ }
+ core_phandle = of_parse_phandle(pdev->dev.of_node,
+ key, i++);
+ }
+
+ return mask;
+}
+
+static int bcl_probe(struct platform_device *pdev)
+{
+ struct bcl_context *bcl = NULL;
+ int ret = 0;
+ enum bcl_device_mode bcl_mode = BCL_DEVICE_DISABLED;
+ char cpu_str[MAX_CPU_NAME];
+ int cpu;
+
+ bcl = devm_kzalloc(&pdev->dev, sizeof(struct bcl_context), GFP_KERNEL);
+ if (!bcl)
+ return -ENOMEM;
+
+ /* For BCL */
+ /* Init default BCL params */
+ if (of_property_read_bool(pdev->dev.of_node, "qcom,bcl-enable"))
+ bcl_mode = BCL_DEVICE_ENABLED;
+ else
+ bcl_mode = BCL_DEVICE_DISABLED;
+ bcl->bcl_mode = BCL_DEVICE_DISABLED;
+ bcl->dev = &pdev->dev;
+ bcl->bcl_monitor_type = BCL_IAVAIL_MONITOR_TYPE;
+ bcl->bcl_threshold_mode[BCL_LOW_THRESHOLD_TYPE] =
+ BCL_IAVAIL_THRESHOLD_DISABLED;
+ bcl->bcl_threshold_mode[BCL_HIGH_THRESHOLD_TYPE] =
+ BCL_IAVAIL_THRESHOLD_DISABLED;
+ bcl->bcl_threshold_value_ma[BCL_LOW_THRESHOLD_TYPE] = 0;
+ bcl->bcl_threshold_value_ma[BCL_HIGH_THRESHOLD_TYPE] = 0;
+ bcl->bcl_vbat_min = BATTERY_VOLTAGE_MIN;
+ snprintf(bcl->bcl_type, BCL_NAME_LENGTH, "%s",
+ bcl_type[BCL_IAVAIL_MONITOR_TYPE]);
+ bcl->bcl_poll_interval_msec = BCL_POLL_INTERVAL;
+
+ if (of_property_read_bool(pdev->dev.of_node, "qcom,bcl-no-bms"))
+ bcl->bcl_no_bms = true;
+ else
+ bcl->bcl_no_bms = false;
+
+ bcl_frequency_mask = get_mask_from_core_handle(pdev,
+ "qcom,bcl-freq-control-list");
+ bcl_hotplug_mask = get_mask_from_core_handle(pdev,
+ "qcom,bcl-hotplug-list");
+ bcl_soc_hotplug_mask = get_mask_from_core_handle(pdev,
+ "qcom,bcl-soc-hotplug-list");
+
+ if (!bcl_hotplug_mask && !bcl_soc_hotplug_mask)
+ bcl_hotplug_enabled = false;
+ else
+ bcl_hotplug_enabled = true;
+
+ if (of_property_read_bool(pdev->dev.of_node,
+ "qcom,bcl-framework-interface"))
+ ret = probe_bcl_periph_prop(bcl);
+ else
+ ret = probe_btm_properties(bcl);
+
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ ret = create_bcl_sysfs(bcl);
+ if (ret < 0) {
+ pr_err("Cannot create bcl sysfs\n");
+ return ret;
+ }
+ INIT_WORK(&bcl->soc_mitig_work, soc_mitigate);
+ bcl->psy_nb.notifier_call = power_supply_callback;
+ bcl->bcl_hotplug_wq = alloc_workqueue("bcl_hotplug_wq", WQ_HIGHPRI, 0);
+ if (!bcl->bcl_hotplug_wq) {
+ pr_err("Workqueue alloc failed\n");
+ return -ENOMEM;
+ }
+
+ /* Initialize mitigation KTM interface */
+ if (num_possible_cpus() > 1) {
+ bcl->hotplug_handle = devmgr_register_mitigation_client(
+ &pdev->dev, HOTPLUG_DEVICE, NULL);
+ if (IS_ERR(bcl->hotplug_handle)) {
+ ret = PTR_ERR(bcl->hotplug_handle);
+ pr_err("Error registering for hotplug. ret:%d\n", ret);
+ return ret;
+ }
+ }
+ for_each_possible_cpu(cpu) {
+ if (!(bcl_frequency_mask & BIT(cpu)))
+ continue;
+ snprintf(cpu_str, MAX_CPU_NAME, "cpu%d", cpu);
+ bcl->cpufreq_handle[cpu] = devmgr_register_mitigation_client(
+ &pdev->dev, cpu_str, NULL);
+ if (IS_ERR(bcl->cpufreq_handle[cpu])) {
+ ret = PTR_ERR(bcl->cpufreq_handle[cpu]);
+ pr_err("Error registering for cpufreq. ret:%d\n", ret);
+ return ret;
+ }
+ }
+
+ gbcl = bcl;
+ platform_set_drvdata(pdev, bcl);
+ INIT_DEFERRABLE_WORK(&bcl->bcl_iavail_work, bcl_iavail_work);
+ INIT_WORK(&bcl_hotplug_work, bcl_handle_hotplug);
+ if (bcl_mode == BCL_DEVICE_ENABLED)
+ bcl_mode_set(bcl_mode);
+
+ return 0;
+}
+
+static int bcl_remove(struct platform_device *pdev)
+{
+ int cpu;
+
+ /* De-register KTM handle */
+ power_supply_unreg_notifier(&gbcl->psy_nb);
+ if (gbcl->hotplug_handle)
+ devmgr_unregister_mitigation_client(&pdev->dev,
+ gbcl->hotplug_handle);
+ for_each_possible_cpu(cpu) {
+ if (gbcl->cpufreq_handle[cpu])
+ devmgr_unregister_mitigation_client(&pdev->dev,
+ gbcl->cpufreq_handle[cpu]);
+ }
+ remove_bcl_sysfs(gbcl);
+ if (gbcl->bcl_hotplug_wq)
+ destroy_workqueue(gbcl->bcl_hotplug_wq);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static const struct of_device_id bcl_match_table[] = {
+ {.compatible = "qcom,bcl"},
+ {},
+};
+
+static const struct dev_pm_ops bcl_pm_ops = {
+ .resume = bcl_resume,
+ .suspend = bcl_suspend,
+};
+
+static struct platform_driver bcl_driver = {
+ .probe = bcl_probe,
+ .remove = bcl_remove,
+ .driver = {
+ .name = BCL_DEV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = bcl_match_table,
+ .pm = &bcl_pm_ops,
+ },
+};
+
+static int __init bcl_init(void)
+{
+ return platform_driver_register(&bcl_driver);
+}
+
+static void __exit bcl_exit(void)
+{
+ platform_driver_unregister(&bcl_driver);
+}
+
+late_initcall(bcl_init);
+module_exit(bcl_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("battery current limit driver");
+MODULE_ALIAS("platform:" BCL_DEV_NAME);
diff --git a/drivers/power/supply/qcom/batterydata-lib.c b/drivers/power/supply/qcom/batterydata-lib.c
new file mode 100644
index 000000000000..226581468fda
--- /dev/null
+++ b/drivers/power/supply/qcom/batterydata-lib.c
@@ -0,0 +1,493 @@
+/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/batterydata-lib.h>
+
+int linear_interpolate(int y0, int x0, int y1, int x1, int x)
+{
+ if (y0 == y1 || x == x0)
+ return y0;
+ if (x1 == x0 || x == x1)
+ return y1;
+
+ return y0 + ((y1 - y0) * (x - x0) / (x1 - x0));
+}
+
+static int interpolate_single_lut_scaled(struct single_row_lut *lut,
+ int x, int scale)
+{
+ int i, result;
+
+ if (x < lut->x[0] * scale) {
+ pr_debug("x %d less than known range return y = %d lut = %pS\n",
+ x, lut->y[0], lut);
+ return lut->y[0];
+ }
+ if (x > lut->x[lut->cols - 1] * scale) {
+ pr_debug("x %d more than known range return y = %d lut = %pS\n",
+ x, lut->y[lut->cols - 1], lut);
+ return lut->y[lut->cols - 1];
+ }
+
+ for (i = 0; i < lut->cols; i++)
+ if (x <= lut->x[i] * scale)
+ break;
+ if (x == lut->x[i] * scale) {
+ result = lut->y[i];
+ } else {
+ result = linear_interpolate(
+ lut->y[i - 1],
+ lut->x[i - 1] * scale,
+ lut->y[i],
+ lut->x[i] * scale,
+ x);
+ }
+ return result;
+}
+
+int interpolate_fcc(struct single_row_lut *fcc_temp_lut, int batt_temp)
+{
+ return interpolate_single_lut_scaled(fcc_temp_lut,
+ batt_temp,
+ DEGC_SCALE);
+}
+
+int interpolate_scalingfactor_fcc(struct single_row_lut *fcc_sf_lut,
+ int cycles)
+{
+ /*
+ * sf table could be null when no battery aging data is available, in
+ * that case return 100%
+ */
+ if (fcc_sf_lut)
+ return interpolate_single_lut_scaled(fcc_sf_lut, cycles, 1);
+ else
+ return 100;
+}
+
+int interpolate_scalingfactor(struct sf_lut *sf_lut, int row_entry, int pc)
+{
+ int i, scalefactorrow1, scalefactorrow2, scalefactor, rows, cols;
+ int row1 = 0;
+ int row2 = 0;
+
+ /*
+ * sf table could be null when no battery aging data is available, in
+ * that case return 100%
+ */
+ if (!sf_lut)
+ return 100;
+
+ rows = sf_lut->rows;
+ cols = sf_lut->cols;
+ if (pc > sf_lut->percent[0]) {
+ pr_debug("pc %d greater than known pc ranges for sfd\n", pc);
+ row1 = 0;
+ row2 = 0;
+ } else if (pc < sf_lut->percent[rows - 1]) {
+ pr_debug("pc %d less than known pc ranges for sf\n", pc);
+ row1 = rows - 1;
+ row2 = rows - 1;
+ } else {
+ for (i = 0; i < rows; i++) {
+ if (pc == sf_lut->percent[i]) {
+ row1 = i;
+ row2 = i;
+ break;
+ }
+ if (pc > sf_lut->percent[i]) {
+ row1 = i - 1;
+ row2 = i;
+ break;
+ }
+ }
+ }
+
+ if (row_entry < sf_lut->row_entries[0] * DEGC_SCALE)
+ row_entry = sf_lut->row_entries[0] * DEGC_SCALE;
+ if (row_entry > sf_lut->row_entries[cols - 1] * DEGC_SCALE)
+ row_entry = sf_lut->row_entries[cols - 1] * DEGC_SCALE;
+
+ for (i = 0; i < cols; i++)
+ if (row_entry <= sf_lut->row_entries[i] * DEGC_SCALE)
+ break;
+ if (row_entry == sf_lut->row_entries[i] * DEGC_SCALE) {
+ scalefactor = linear_interpolate(
+ sf_lut->sf[row1][i],
+ sf_lut->percent[row1],
+ sf_lut->sf[row2][i],
+ sf_lut->percent[row2],
+ pc);
+ return scalefactor;
+ }
+
+ scalefactorrow1 = linear_interpolate(
+ sf_lut->sf[row1][i - 1],
+ sf_lut->row_entries[i - 1] * DEGC_SCALE,
+ sf_lut->sf[row1][i],
+ sf_lut->row_entries[i] * DEGC_SCALE,
+ row_entry);
+
+ scalefactorrow2 = linear_interpolate(
+ sf_lut->sf[row2][i - 1],
+ sf_lut->row_entries[i - 1] * DEGC_SCALE,
+ sf_lut->sf[row2][i],
+ sf_lut->row_entries[i] * DEGC_SCALE,
+ row_entry);
+
+ scalefactor = linear_interpolate(
+ scalefactorrow1,
+ sf_lut->percent[row1],
+ scalefactorrow2,
+ sf_lut->percent[row2],
+ pc);
+
+ return scalefactor;
+}
+
+/* get ocv given a soc -- reverse lookup */
+int interpolate_ocv(struct pc_temp_ocv_lut *pc_temp_ocv,
+ int batt_temp, int pc)
+{
+ int i, ocvrow1, ocvrow2, ocv, rows, cols;
+ int row1 = 0;
+ int row2 = 0;
+
+ rows = pc_temp_ocv->rows;
+ cols = pc_temp_ocv->cols;
+ if (pc > pc_temp_ocv->percent[0]) {
+ pr_debug("pc %d greater than known pc ranges for sfd\n", pc);
+ row1 = 0;
+ row2 = 0;
+ } else if (pc < pc_temp_ocv->percent[rows - 1]) {
+ pr_debug("pc %d less than known pc ranges for sf\n", pc);
+ row1 = rows - 1;
+ row2 = rows - 1;
+ } else {
+ for (i = 0; i < rows; i++) {
+ if (pc == pc_temp_ocv->percent[i]) {
+ row1 = i;
+ row2 = i;
+ break;
+ }
+ if (pc > pc_temp_ocv->percent[i]) {
+ row1 = i - 1;
+ row2 = i;
+ break;
+ }
+ }
+ }
+
+ if (batt_temp < pc_temp_ocv->temp[0] * DEGC_SCALE)
+ batt_temp = pc_temp_ocv->temp[0] * DEGC_SCALE;
+ if (batt_temp > pc_temp_ocv->temp[cols - 1] * DEGC_SCALE)
+ batt_temp = pc_temp_ocv->temp[cols - 1] * DEGC_SCALE;
+
+ for (i = 0; i < cols; i++)
+ if (batt_temp <= pc_temp_ocv->temp[i] * DEGC_SCALE)
+ break;
+ if (batt_temp == pc_temp_ocv->temp[i] * DEGC_SCALE) {
+ ocv = linear_interpolate(
+ pc_temp_ocv->ocv[row1][i],
+ pc_temp_ocv->percent[row1],
+ pc_temp_ocv->ocv[row2][i],
+ pc_temp_ocv->percent[row2],
+ pc);
+ return ocv;
+ }
+
+ ocvrow1 = linear_interpolate(
+ pc_temp_ocv->ocv[row1][i - 1],
+ pc_temp_ocv->temp[i - 1] * DEGC_SCALE,
+ pc_temp_ocv->ocv[row1][i],
+ pc_temp_ocv->temp[i] * DEGC_SCALE,
+ batt_temp);
+
+ ocvrow2 = linear_interpolate(
+ pc_temp_ocv->ocv[row2][i - 1],
+ pc_temp_ocv->temp[i - 1] * DEGC_SCALE,
+ pc_temp_ocv->ocv[row2][i],
+ pc_temp_ocv->temp[i] * DEGC_SCALE,
+ batt_temp);
+
+ ocv = linear_interpolate(
+ ocvrow1,
+ pc_temp_ocv->percent[row1],
+ ocvrow2,
+ pc_temp_ocv->percent[row2],
+ pc);
+
+ return ocv;
+}
+
+int interpolate_pc(struct pc_temp_ocv_lut *pc_temp_ocv,
+ int batt_temp, int ocv)
+{
+ int i, j, pcj, pcj_minus_one, pc;
+ int rows = pc_temp_ocv->rows;
+ int cols = pc_temp_ocv->cols;
+
+ if (batt_temp < pc_temp_ocv->temp[0] * DEGC_SCALE) {
+ pr_debug("batt_temp %d < known temp range\n", batt_temp);
+ batt_temp = pc_temp_ocv->temp[0] * DEGC_SCALE;
+ }
+
+ if (batt_temp > pc_temp_ocv->temp[cols - 1] * DEGC_SCALE) {
+ pr_debug("batt_temp %d > known temp range\n", batt_temp);
+ batt_temp = pc_temp_ocv->temp[cols - 1] * DEGC_SCALE;
+ }
+
+ for (j = 0; j < cols; j++)
+ if (batt_temp <= pc_temp_ocv->temp[j] * DEGC_SCALE)
+ break;
+ if (batt_temp == pc_temp_ocv->temp[j] * DEGC_SCALE) {
+ /* found an exact match for temp in the table */
+ if (ocv >= pc_temp_ocv->ocv[0][j])
+ return pc_temp_ocv->percent[0];
+ if (ocv <= pc_temp_ocv->ocv[rows - 1][j])
+ return pc_temp_ocv->percent[rows - 1];
+ for (i = 0; i < rows; i++) {
+ if (ocv >= pc_temp_ocv->ocv[i][j]) {
+ if (ocv == pc_temp_ocv->ocv[i][j])
+ return pc_temp_ocv->percent[i];
+ pc = linear_interpolate(
+ pc_temp_ocv->percent[i],
+ pc_temp_ocv->ocv[i][j],
+ pc_temp_ocv->percent[i - 1],
+ pc_temp_ocv->ocv[i - 1][j],
+ ocv);
+ return pc;
+ }
+ }
+ }
+
+ /*
+ * batt_temp is within temperature for
+ * column j-1 and j
+ */
+ if (ocv >= pc_temp_ocv->ocv[0][j])
+ return pc_temp_ocv->percent[0];
+ if (ocv <= pc_temp_ocv->ocv[rows - 1][j - 1])
+ return pc_temp_ocv->percent[rows - 1];
+
+ pcj_minus_one = 0;
+ pcj = 0;
+ for (i = 0; i < rows-1; i++) {
+ if (pcj == 0
+ && is_between(pc_temp_ocv->ocv[i][j],
+ pc_temp_ocv->ocv[i+1][j], ocv)) {
+ pcj = linear_interpolate(
+ pc_temp_ocv->percent[i],
+ pc_temp_ocv->ocv[i][j],
+ pc_temp_ocv->percent[i + 1],
+ pc_temp_ocv->ocv[i+1][j],
+ ocv);
+ }
+
+ if (pcj_minus_one == 0
+ && is_between(pc_temp_ocv->ocv[i][j-1],
+ pc_temp_ocv->ocv[i+1][j-1], ocv)) {
+ pcj_minus_one = linear_interpolate(
+ pc_temp_ocv->percent[i],
+ pc_temp_ocv->ocv[i][j-1],
+ pc_temp_ocv->percent[i + 1],
+ pc_temp_ocv->ocv[i+1][j-1],
+ ocv);
+ }
+
+ if (pcj && pcj_minus_one) {
+ pc = linear_interpolate(
+ pcj_minus_one,
+ pc_temp_ocv->temp[j-1] * DEGC_SCALE,
+ pcj,
+ pc_temp_ocv->temp[j] * DEGC_SCALE,
+ batt_temp);
+ return pc;
+ }
+ }
+
+ if (pcj)
+ return pcj;
+
+ if (pcj_minus_one)
+ return pcj_minus_one;
+
+ pr_debug("%d ocv wasn't found for temp %d in the LUT returning 100%%\n",
+ ocv, batt_temp);
+ return 100;
+}
+
+int interpolate_slope(struct pc_temp_ocv_lut *pc_temp_ocv,
+ int batt_temp, int pc)
+{
+ int i, ocvrow1, ocvrow2, rows, cols;
+ int row1 = 0;
+ int row2 = 0;
+ int slope;
+
+ rows = pc_temp_ocv->rows;
+ cols = pc_temp_ocv->cols;
+ if (pc >= pc_temp_ocv->percent[0]) {
+ pr_debug("pc %d >= max pc range - use the slope at pc=%d\n",
+ pc, pc_temp_ocv->percent[0]);
+ row1 = 0;
+ row2 = 1;
+ } else if (pc <= pc_temp_ocv->percent[rows - 1]) {
+ pr_debug("pc %d is <= min pc range - use the slope at pc=%d\n",
+ pc, pc_temp_ocv->percent[rows - 1]);
+ row1 = rows - 2;
+ row2 = rows - 1;
+ } else {
+ for (i = 0; i < rows; i++) {
+ if (pc == pc_temp_ocv->percent[i]) {
+ row1 = i - 1;
+ row2 = i;
+ break;
+ }
+ if (pc > pc_temp_ocv->percent[i]) {
+ row1 = i - 1;
+ row2 = i;
+ break;
+ }
+ }
+ }
+
+ if (batt_temp < pc_temp_ocv->temp[0] * DEGC_SCALE)
+ batt_temp = pc_temp_ocv->temp[0] * DEGC_SCALE;
+ if (batt_temp > pc_temp_ocv->temp[cols - 1] * DEGC_SCALE)
+ batt_temp = pc_temp_ocv->temp[cols - 1] * DEGC_SCALE;
+
+ for (i = 0; i < cols; i++)
+ if (batt_temp <= pc_temp_ocv->temp[i] * DEGC_SCALE)
+ break;
+
+ if (batt_temp == pc_temp_ocv->temp[i] * DEGC_SCALE) {
+ slope = (pc_temp_ocv->ocv[row1][i] -
+ pc_temp_ocv->ocv[row2][i]);
+ if (slope <= 0) {
+ pr_warn("Slope=%d for pc=%d, using 1\n", slope, pc);
+ slope = 1;
+ }
+ slope *= 1000;
+ slope /= (pc_temp_ocv->percent[row1] -
+ pc_temp_ocv->percent[row2]);
+ return slope;
+ }
+ ocvrow1 = linear_interpolate(
+ pc_temp_ocv->ocv[row1][i - 1],
+ pc_temp_ocv->temp[i - 1] * DEGC_SCALE,
+ pc_temp_ocv->ocv[row1][i],
+ pc_temp_ocv->temp[i] * DEGC_SCALE,
+ batt_temp);
+
+ ocvrow2 = linear_interpolate(
+ pc_temp_ocv->ocv[row2][i - 1],
+ pc_temp_ocv->temp[i - 1] * DEGC_SCALE,
+ pc_temp_ocv->ocv[row2][i],
+ pc_temp_ocv->temp[i] * DEGC_SCALE,
+ batt_temp);
+
+ slope = (ocvrow1 - ocvrow2);
+ if (slope <= 0) {
+ pr_warn("Slope=%d for pc=%d, using 1\n", slope, pc);
+ slope = 1;
+ }
+ slope *= 1000;
+ slope /= (pc_temp_ocv->percent[row1] - pc_temp_ocv->percent[row2]);
+
+ return slope;
+}
+
+
+int interpolate_acc(struct ibat_temp_acc_lut *ibat_acc_lut,
+ int batt_temp, int ibat)
+{
+ int i, accrow1, accrow2, rows, cols;
+ int row1 = 0;
+ int row2 = 0;
+ int acc;
+
+ rows = ibat_acc_lut->rows;
+ cols = ibat_acc_lut->cols;
+
+ if (ibat > ibat_acc_lut->ibat[rows - 1]) {
+ pr_debug("ibatt(%d) > max range(%d)\n", ibat,
+ ibat_acc_lut->ibat[rows - 1]);
+ row1 = rows - 1;
+ row2 = rows - 2;
+ } else if (ibat < ibat_acc_lut->ibat[0]) {
+ pr_debug("ibatt(%d) < max range(%d)\n", ibat,
+ ibat_acc_lut->ibat[0]);
+ row1 = 0;
+ row2 = 0;
+ } else {
+ for (i = 0; i < rows; i++) {
+ if (ibat == ibat_acc_lut->ibat[i]) {
+ row1 = i;
+ row2 = i;
+ break;
+ }
+ if (ibat < ibat_acc_lut->ibat[i]) {
+ row1 = i;
+ row2 = i - 1;
+ break;
+ }
+ }
+ }
+
+ if (batt_temp < ibat_acc_lut->temp[0] * DEGC_SCALE)
+ batt_temp = ibat_acc_lut->temp[0] * DEGC_SCALE;
+ if (batt_temp > ibat_acc_lut->temp[cols - 1] * DEGC_SCALE)
+ batt_temp = ibat_acc_lut->temp[cols - 1] * DEGC_SCALE;
+
+ for (i = 0; i < cols; i++)
+ if (batt_temp <= ibat_acc_lut->temp[i] * DEGC_SCALE)
+ break;
+
+ if (batt_temp == (ibat_acc_lut->temp[i] * DEGC_SCALE)) {
+ acc = linear_interpolate(
+ ibat_acc_lut->acc[row1][i],
+ ibat_acc_lut->ibat[row1],
+ ibat_acc_lut->acc[row2][i],
+ ibat_acc_lut->ibat[row2],
+ ibat);
+ return acc;
+ }
+
+ accrow1 = linear_interpolate(
+ ibat_acc_lut->acc[row1][i - 1],
+ ibat_acc_lut->temp[i - 1] * DEGC_SCALE,
+ ibat_acc_lut->acc[row1][i],
+ ibat_acc_lut->temp[i] * DEGC_SCALE,
+ batt_temp);
+
+ accrow2 = linear_interpolate(
+ ibat_acc_lut->acc[row2][i - 1],
+ ibat_acc_lut->temp[i - 1] * DEGC_SCALE,
+ ibat_acc_lut->acc[row2][i],
+ ibat_acc_lut->temp[i] * DEGC_SCALE,
+ batt_temp);
+
+ acc = linear_interpolate(accrow1,
+ ibat_acc_lut->ibat[row1],
+ accrow2,
+ ibat_acc_lut->ibat[row2],
+ ibat);
+
+ if (acc < 0)
+ acc = 0;
+
+ return acc;
+}
diff --git a/drivers/power/supply/qcom/bcl_peripheral.c b/drivers/power/supply/qcom/bcl_peripheral.c
new file mode 100644
index 000000000000..2d237f27b062
--- /dev/null
+++ b/drivers/power/supply/qcom/bcl_peripheral.c
@@ -0,0 +1,1367 @@
+/*
+ * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/msm_bcl.h>
+#include <linux/power_supply.h>
+#include <soc/qcom/scm.h>
+#include <linux/slab.h>
+#include <asm/cacheflush.h>
+
+#define CREATE_TRACE_POINTS
+#define _BCL_HW_TRACE
+#include <trace/trace_thermal.h>
+
+#define BCL_DRIVER_NAME "bcl_peripheral"
+#define BCL_VBAT_INT_NAME "bcl-low-vbat-int"
+#define BCL_IBAT_INT_NAME "bcl-high-ibat-int"
+#define BCL_PARAM_MAX_ATTR 3
+
+#define BCL_MONITOR_EN 0x46
+#define BCL_VBAT_VALUE 0x54
+#define BCL_IBAT_VALUE 0x55
+#define BCL_VBAT_MIN 0x58
+#define BCL_IBAT_MAX 0x59
+#define BCL_V_GAIN_BAT 0x60
+#define BCL_I_GAIN_RSENSE 0x61
+#define BCL_I_OFFSET_RSENSE 0x62
+#define BCL_I_GAIN_BATFET 0x63
+#define BCL_I_OFFSET_BATFET 0x64
+#define BCL_I_SENSE_SRC 0x65
+#define BCL_VBAT_MIN_CLR 0x66
+#define BCL_IBAT_MAX_CLR 0x67
+#define BCL_VBAT_TRIP 0x68
+#define BCL_IBAT_TRIP 0x69
+
+#define BCL_8998_VBAT_VALUE 0x58
+#define BCL_8998_IBAT_VALUE 0x59
+#define BCL_8998_VBAT_MIN 0x5C
+#define BCL_8998_IBAT_MAX 0x5D
+#define BCL_8998_MAX_MIN_CLR 0x48
+#define BCL_8998_IBAT_MAX_CLR 3
+#define BCL_8998_VBAT_MIN_CLR 2
+#define BCL_8998_VBAT_ADC_LOW 0x72
+#define BCL_8998_VBAT_COMP_LOW 0x75
+#define BCL_8998_VBAT_COMP_TLOW 0x76
+#define BCL_8998_IBAT_HIGH 0x78
+#define BCL_8998_IBAT_TOO_HIGH 0x79
+#define BCL_8998_LMH_CFG 0xA3
+#define BCL_8998_BCL_CFG 0x6A
+#define LMH_8998_INT_POL_HIGH 0x12
+#define LMH_8998_INT_EN 0x15
+
+#define BCL_8998_VBAT_SCALING 39000
+#define BCL_8998_IBAT_SCALING 80000
+#define BCL_VBAT_LOW_THRESHOLD 0x7 /* 3.1V */
+#define BCL_VBAT_TLOW_THRESHOLD 0x5 /* 2.9v */
+#define BCL_IBAT_HIGH_THRESH_UA 4300000
+#define BCL_LMH_CFG_VAL 0x3
+#define BCL_CFG_VAL 0x81
+#define LMH_INT_VAL 0x7
+
+#define BCL_CONSTANT_NUM 32
+#define BCL_READ_RETRY_LIMIT 3
+#define VAL_CP_REG_BUF_LEN 3
+#define VAL_REG_BUF_OFFSET 0
+#define VAL_CP_REG_BUF_OFFSET 2
+#define PON_SPARE_FULL_CURRENT 0x0
+#define PON_SPARE_DERATED_CURRENT 0x1
+
+#define LMH_DCVSH 0x10
+#define LMH_NODE_DCVS 0x44435653 /* DCVS */
+#define LMH_SUB_FN_BCL 0x42434C00 /* BCL */
+#define LMH_CLUSTER_0 0x6370302D /* cpAG */
+#define LMH_CLUSTER_1 0x6370312D /* cpAU */
+#define LMH_ALGO_ENABLE 0x454E424C /* ENBL */
+
+#define READ_CONV_FACTOR(_node, _key, _val, _ret, _dest) do { \
+ _ret = of_property_read_u32(_node, _key, &_val); \
+ if (_ret) { \
+ pr_err("Error reading key:%s. err:%d\n", _key, _ret); \
+ goto bcl_dev_exit; \
+ } \
+ _dest = _val; \
+ } while (0)
+
+#define READ_OPTIONAL_PROP(_node, _key, _val, _ret, _dest) do { \
+ _ret = of_property_read_u32(_node, _key, &_val); \
+ if (_ret && _ret != -EINVAL) { \
+ pr_err("Error reading key:%s. err:%d\n", _key, _ret); \
+ goto bcl_dev_exit; \
+ } else if (!_ret) { \
+ _dest = _val; \
+ } \
+ } while (0)
+
+enum bcl_monitor_state {
+ BCL_PARAM_INACTIVE,
+ BCL_PARAM_MONITOR,
+ BCL_PARAM_POLLING,
+};
+
+enum bcl_hw_type {
+ BCL_PMI8994,
+ BCL_PMI8998,
+ BCL_VERSION_MAX,
+};
+
+struct bcl_peripheral_data {
+ struct bcl_param_data *param_data;
+ struct bcl_driver_ops ops;
+ enum bcl_monitor_state state;
+ struct delayed_work poll_work;
+ int irq_num;
+ int high_trip;
+ int low_trip;
+ int trip_val;
+ int scaling_factor;
+ int offset_factor_num;
+ int offset_factor_den;
+ int offset;
+ int gain_factor_num;
+ int gain_factor_den;
+ int gain;
+ uint32_t polling_delay_ms;
+ int inhibit_derating_ua;
+ int (*read_max) (int *adc_value);
+ int (*clear_max) (void);
+ struct mutex state_trans_lock;
+};
+
+struct bcl_device {
+ bool enabled;
+ struct device *dev;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ uint16_t base_addr;
+ uint16_t pon_spare_addr;
+ uint16_t fg_lmh_addr;
+ int i_src;
+ struct bcl_peripheral_data param[BCL_PARAM_MAX];
+};
+
+static struct bcl_device *bcl_perph;
+static struct power_supply_desc bcl_psy_d;
+static struct power_supply *bcl_psy;
+static const char bcl_psy_name[] = "fg_adc";
+static bool calibration_done;
+static DEFINE_MUTEX(bcl_access_mutex);
+static DEFINE_MUTEX(bcl_enable_mutex);
+static enum bcl_hw_type bcl_perph_version;
+
+static int bcl_read_multi_register(int16_t reg_offset, uint8_t *data, int len)
+{
+ int ret = 0, trace_len = 0;
+
+ if (!bcl_perph) {
+ pr_err("BCL device not initialized\n");
+ return -EINVAL;
+ }
+ ret = regmap_bulk_read(bcl_perph->regmap,
+ (bcl_perph->base_addr + reg_offset), data, len);
+ if (ret < 0) {
+ pr_err("Error reading register %d. err:%d", reg_offset, ret);
+ return ret;
+ }
+ while (trace_len < len) {
+ trace_bcl_hw_reg_access("Read",
+ bcl_perph->base_addr + reg_offset + trace_len,
+ data[trace_len]);
+ trace_len++;
+ }
+
+ return ret;
+}
+
+static int bcl_read_register(int16_t reg_offset, uint8_t *data)
+{
+ return bcl_read_multi_register(reg_offset, data, 1);
+}
+
+static int bcl_write_general_register(int16_t reg_offset,
+ uint16_t base, uint8_t data)
+{
+ int ret = 0;
+ uint8_t *write_buf = &data;
+
+ if (!bcl_perph) {
+ pr_err("BCL device not initialized\n");
+ return -EINVAL;
+ }
+ ret = regmap_write(bcl_perph->regmap, (base + reg_offset), *write_buf);
+ if (ret < 0) {
+ pr_err("Error reading register %d. err:%d", reg_offset, ret);
+ return ret;
+ }
+ pr_debug("wrote 0x%02x to 0x%04x\n", data, base + reg_offset);
+ trace_bcl_hw_reg_access("write", base + reg_offset, data);
+
+ return ret;
+}
+
+static int bcl_write_register(int16_t reg_offset, uint8_t data)
+{
+ return bcl_write_general_register(reg_offset,
+ bcl_perph->base_addr, data);
+}
+
+static void convert_vbat_to_adc_val(int *val)
+{
+ struct bcl_peripheral_data *perph_data = NULL;
+
+ switch (bcl_perph_version) {
+ case BCL_PMI8994:
+ if (!bcl_perph)
+ return;
+ perph_data = &bcl_perph->param[BCL_PARAM_VOLTAGE];
+ *val = (*val * 100
+ / (100 + (perph_data->gain_factor_num
+ * perph_data->gain) * BCL_CONSTANT_NUM
+ / perph_data->gain_factor_den))
+ / perph_data->scaling_factor;
+ break;
+ case BCL_PMI8998:
+ *val = *val / BCL_8998_VBAT_SCALING;
+ break;
+ default:
+ break;
+ }
+
+ return;
+}
+
+static void convert_adc_to_vbat_val(int *val)
+{
+ struct bcl_peripheral_data *perph_data = NULL;
+
+ switch (bcl_perph_version) {
+ case BCL_PMI8994:
+ if (!bcl_perph)
+ return;
+ perph_data = &bcl_perph->param[BCL_PARAM_VOLTAGE];
+ *val = ((*val + 2) * perph_data->scaling_factor)
+ * (100 + (perph_data->gain_factor_num
+ * perph_data->gain)
+ * BCL_CONSTANT_NUM / perph_data->gain_factor_den)
+ / 100;
+ break;
+ case BCL_PMI8998:
+ *val = *val * BCL_8998_VBAT_SCALING;
+ break;
+ default:
+ break;
+ }
+
+ return;
+}
+
+static void convert_ibat_to_adc_val(int *val)
+{
+ struct bcl_peripheral_data *perph_data = NULL;
+
+ switch (bcl_perph_version) {
+ case BCL_PMI8994:
+ if (!bcl_perph)
+ return;
+ perph_data = &bcl_perph->param[BCL_PARAM_CURRENT];
+ *val = (*val * 100
+ / (100 + (perph_data->gain_factor_num
+ * perph_data->gain)
+ * BCL_CONSTANT_NUM / perph_data->gain_factor_den)
+ - (perph_data->offset_factor_num * perph_data->offset)
+ / perph_data->offset_factor_den)
+ / perph_data->scaling_factor;
+ break;
+ case BCL_PMI8998:
+ *val = *val / BCL_8998_IBAT_SCALING;
+ break;
+ default:
+ break;
+ }
+
+ return;
+}
+
+static void convert_adc_to_ibat_val(int *val)
+{
+ struct bcl_peripheral_data *perph_data = NULL;
+
+ switch (bcl_perph_version) {
+ case BCL_PMI8994:
+ if (!bcl_perph)
+ return;
+ perph_data = &bcl_perph->param[BCL_PARAM_CURRENT];
+ *val = (*val * perph_data->scaling_factor
+ + (perph_data->offset_factor_num * perph_data->offset)
+ / perph_data->offset_factor_den)
+ * (100 + (perph_data->gain_factor_num
+ * perph_data->gain) * BCL_CONSTANT_NUM /
+ perph_data->gain_factor_den) / 100;
+ break;
+ case BCL_PMI8998:
+ *val = *val * BCL_8998_IBAT_SCALING;
+ break;
+ default:
+ break;
+ }
+
+ return;
+}
+
+static int bcl_set_high_vbat(int thresh_value)
+{
+ bcl_perph->param[BCL_PARAM_VOLTAGE].high_trip = thresh_value;
+ return 0;
+}
+
+static int bcl_set_low_ibat(int thresh_value)
+{
+ bcl_perph->param[BCL_PARAM_CURRENT].low_trip = thresh_value;
+ return 0;
+}
+
+static int bcl_set_high_ibat(int thresh_value)
+{
+ int ret = 0, ibat_ua;
+ int8_t val = 0;
+ uint32_t too_high_thresh = BCL_IBAT_HIGH_THRESH_UA;
+
+ ibat_ua = thresh_value;
+ convert_ibat_to_adc_val(&thresh_value);
+ pr_debug("Setting Ibat high trip:%d. ADC_val:%d\n", ibat_ua,
+ thresh_value);
+ val = (int8_t)thresh_value;
+ ret = bcl_write_register((bcl_perph_version == BCL_PMI8994) ?
+ BCL_IBAT_TRIP : BCL_8998_IBAT_HIGH, val);
+ if (ret) {
+ pr_err("Error accessing BCL peripheral. err:%d\n", ret);
+ return ret;
+ }
+ bcl_perph->param[BCL_PARAM_CURRENT].high_trip = thresh_value;
+ if (bcl_perph_version == BCL_PMI8998) {
+ convert_ibat_to_adc_val(&too_high_thresh);
+ pr_debug("Setting Ibat too high trip:%d. ADC_val:%d\n",
+ BCL_IBAT_HIGH_THRESH_UA, too_high_thresh);
+ val = (int8_t)too_high_thresh;
+ ret = bcl_write_register(BCL_8998_IBAT_TOO_HIGH, val);
+ if (ret) {
+ pr_err("Error accessing BCL peripheral. err:%d\n", ret);
+ return ret;
+ }
+ }
+
+ if (bcl_perph->param[BCL_PARAM_CURRENT].inhibit_derating_ua == 0
+ || bcl_perph->pon_spare_addr == 0)
+ return ret;
+
+ ret = bcl_write_general_register(bcl_perph->pon_spare_addr,
+ PON_SPARE_FULL_CURRENT, val);
+ if (ret) {
+ pr_debug("Error accessing PON register. err:%d\n", ret);
+ return ret;
+ }
+ thresh_value = ibat_ua
+ - bcl_perph->param[BCL_PARAM_CURRENT].inhibit_derating_ua;
+ convert_ibat_to_adc_val(&thresh_value);
+ val = (int8_t)thresh_value;
+ ret = bcl_write_general_register(bcl_perph->pon_spare_addr,
+ PON_SPARE_DERATED_CURRENT, val);
+ if (ret) {
+ pr_debug("Error accessing PON register. err:%d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int bcl_set_low_vbat(int thresh_value)
+{
+ int ret = 0, vbat_uv;
+ int8_t val = 0;
+
+ vbat_uv = thresh_value;
+ convert_vbat_to_adc_val(&thresh_value);
+ pr_debug("Setting Vbat low trip:%d. ADC_val:%d\n", vbat_uv,
+ thresh_value);
+ val = (int8_t)thresh_value;
+ ret = bcl_write_register((bcl_perph_version == BCL_PMI8994)
+ ? BCL_VBAT_TRIP : BCL_8998_VBAT_ADC_LOW, val);
+ if (ret) {
+ pr_err("Error accessing BCL peripheral. err:%d\n", ret);
+ return ret;
+ }
+ if (bcl_perph_version == BCL_PMI8998) {
+ ret = bcl_write_register(BCL_8998_VBAT_COMP_LOW,
+ BCL_VBAT_LOW_THRESHOLD);
+ if (ret) {
+ pr_err("Error accessing BCL peripheral. err:%d\n", ret);
+ return ret;
+ }
+ pr_debug("Setting Vbat low comparator threshold:0x%x.\n",
+ BCL_VBAT_LOW_THRESHOLD);
+ ret = bcl_write_register(BCL_8998_VBAT_COMP_TLOW,
+ BCL_VBAT_TLOW_THRESHOLD);
+ if (ret) {
+ pr_err("Error accessing BCL peripheral. err:%d\n", ret);
+ return ret;
+ }
+ pr_debug("Setting Vbat too low comparator threshold:0x%x.\n",
+ BCL_VBAT_TLOW_THRESHOLD);
+ }
+ bcl_perph->param[BCL_PARAM_VOLTAGE].low_trip = thresh_value;
+
+ return ret;
+}
+
+static void bcl_lmh_dcvs_enable(void)
+{
+ struct scm_desc desc_arg;
+ uint32_t *payload = NULL;
+
+ payload = kzalloc(sizeof(uint32_t) * 5, GFP_KERNEL);
+ if (!payload)
+ return;
+
+ payload[0] = LMH_SUB_FN_BCL;
+ payload[1] = 0; /* unused sub-algorithm */
+ payload[2] = LMH_ALGO_ENABLE;
+ payload[3] = 1; /* number of values */
+ payload[4] = 1;
+
+ desc_arg.args[0] = SCM_BUFFER_PHYS(payload);
+ desc_arg.args[1] = sizeof(uint32_t) * 5;
+ desc_arg.args[2] = LMH_NODE_DCVS;
+ desc_arg.args[3] = LMH_CLUSTER_0;
+ desc_arg.args[4] = 0; /* version */
+ desc_arg.arginfo = SCM_ARGS(5, SCM_RO, SCM_VAL, SCM_VAL,
+ SCM_VAL, SCM_VAL);
+
+ dmac_flush_range(payload, (void *)payload + 5 * (sizeof(uint32_t)));
+ if (scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DCVSH),
+ &desc_arg))
+ pr_err("Error enabling LMH BCL monitoringfor cluster0\n");
+
+ desc_arg.args[3] = LMH_CLUSTER_1;
+ if (scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DCVSH),
+ &desc_arg))
+ pr_err("Error enabling LMH BCL monitoringfor cluster1\n");
+
+ kfree(payload);
+}
+
+static int bcl_access_monitor_enable(bool enable)
+{
+ int ret = 0, i = 0;
+ struct bcl_peripheral_data *perph_data = NULL;
+ static bool hw_enabled;
+
+ mutex_lock(&bcl_enable_mutex);
+ if (enable == bcl_perph->enabled)
+ goto access_exit;
+
+ if ((bcl_perph_version == BCL_PMI8998) && !hw_enabled && enable) {
+ bcl_lmh_dcvs_enable();
+ hw_enabled = true;
+ }
+
+ for (; i < BCL_PARAM_MAX; i++) {
+ perph_data = &bcl_perph->param[i];
+ mutex_lock(&perph_data->state_trans_lock);
+ if (enable) {
+ switch (perph_data->state) {
+ case BCL_PARAM_INACTIVE:
+ trace_bcl_hw_state_event(
+ (i == BCL_PARAM_VOLTAGE)
+ ? "Voltage Inactive to Monitor"
+ : "Current Inactive to Monitor",
+ 0);
+ enable_irq(perph_data->irq_num);
+ break;
+ case BCL_PARAM_POLLING:
+ case BCL_PARAM_MONITOR:
+ default:
+ break;
+ }
+ perph_data->state = BCL_PARAM_MONITOR;
+ } else {
+ switch (perph_data->state) {
+ case BCL_PARAM_MONITOR:
+ trace_bcl_hw_state_event(
+ (i == BCL_PARAM_VOLTAGE)
+ ? "Voltage Monitor to Inactive"
+ : "Current Monitor to Inactive",
+ 0);
+ disable_irq_nosync(perph_data->irq_num);
+ /* Fall through to clear the poll work */
+ case BCL_PARAM_INACTIVE:
+ case BCL_PARAM_POLLING:
+ cancel_delayed_work_sync(
+ &perph_data->poll_work);
+ break;
+ default:
+ break;
+ }
+ perph_data->state = BCL_PARAM_INACTIVE;
+ }
+ mutex_unlock(&perph_data->state_trans_lock);
+ }
+ bcl_perph->enabled = enable;
+
+access_exit:
+ mutex_unlock(&bcl_enable_mutex);
+ return ret;
+}
+
+static int bcl_monitor_enable(void)
+{
+ trace_bcl_hw_event("BCL Enable");
+ return bcl_access_monitor_enable(true);
+}
+
+static int bcl_monitor_disable(void)
+{
+ trace_bcl_hw_event("BCL Disable");
+ return bcl_access_monitor_enable(false);
+}
+
+static int bcl_read_ibat_high_trip(int *thresh_value)
+{
+ int ret = 0;
+ int8_t val = 0;
+
+ *thresh_value = (int)val;
+ ret = bcl_read_register((bcl_perph_version == BCL_PMI8994) ?
+ BCL_IBAT_TRIP : BCL_8998_IBAT_HIGH, &val);
+ if (ret) {
+ pr_err("BCL register read error. err:%d\n", ret);
+ ret = 0;
+ val = bcl_perph->param[BCL_PARAM_CURRENT].high_trip;
+ *thresh_value = (int)val;
+ } else {
+ *thresh_value = (int)val;
+ convert_adc_to_ibat_val(thresh_value);
+ pr_debug("Reading Ibat high trip:%d. ADC_val:%d\n",
+ *thresh_value, val);
+ }
+
+ return ret;
+}
+
+static int bcl_read_ibat_low_trip(int *thresh_value)
+{
+ *thresh_value = bcl_perph->param[BCL_PARAM_CURRENT].low_trip;
+ return 0;
+}
+
+static int bcl_read_vbat_low_trip(int *thresh_value)
+{
+ int ret = 0;
+ int8_t val = 0;
+
+ *thresh_value = (int)val;
+ ret = bcl_read_register((bcl_perph_version == BCL_PMI8994)
+ ? BCL_VBAT_TRIP : BCL_8998_VBAT_ADC_LOW,
+ &val);
+ if (ret) {
+ pr_err("BCL register read error. err:%d\n", ret);
+ ret = 0;
+ *thresh_value = bcl_perph->param[BCL_PARAM_VOLTAGE].low_trip;
+ } else {
+ *thresh_value = (int)val;
+ convert_adc_to_vbat_val(thresh_value);
+ pr_debug("Reading Ibat high trip:%d. ADC_val:%d\n",
+ *thresh_value, val);
+ }
+
+ return ret;
+}
+
+static int bcl_read_vbat_high_trip(int *thresh_value)
+{
+ *thresh_value = bcl_perph->param[BCL_PARAM_VOLTAGE].high_trip;
+ return 0;
+}
+
+static int bcl_clear_vbat_min(void)
+{
+ int ret = 0;
+
+ if (bcl_perph_version == BCL_PMI8994)
+ ret = bcl_write_register(BCL_VBAT_MIN_CLR, BIT(7));
+ else
+ ret = bcl_write_register(BCL_8998_MAX_MIN_CLR,
+ BIT(BCL_8998_VBAT_MIN_CLR));
+ if (ret)
+ pr_err("Error in clearing vbat min reg. err:%d", ret);
+
+ return ret;
+}
+
+static int bcl_clear_ibat_max(void)
+{
+ int ret = 0;
+
+ if (bcl_perph_version == BCL_PMI8994)
+ ret = bcl_write_register(BCL_IBAT_MAX_CLR, BIT(7));
+ else
+ ret = bcl_write_register(BCL_8998_MAX_MIN_CLR,
+ BIT(BCL_8998_IBAT_MAX_CLR));
+ if (ret)
+ pr_err("Error in clearing ibat max reg. err:%d", ret);
+
+ return ret;
+}
+
+static int bcl_read_ibat_max(int *adc_value)
+{
+ int ret = 0, timeout = 0;
+ int8_t val[VAL_CP_REG_BUF_LEN] = {0};
+
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ do {
+ ret = bcl_read_multi_register(
+ (bcl_perph_version == BCL_PMI8994) ? BCL_IBAT_MAX
+ : BCL_8998_IBAT_MAX, val,
+ VAL_CP_REG_BUF_LEN);
+ if (ret) {
+ pr_err("BCL register read error. err:%d\n", ret);
+ goto bcl_read_exit;
+ }
+ } while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]
+ && timeout++ < BCL_READ_RETRY_LIMIT);
+ if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) {
+ ret = -ENODEV;
+ goto bcl_read_exit;
+ }
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ convert_adc_to_ibat_val(adc_value);
+ pr_debug("Ibat Max:%d. ADC_val:%d\n", *adc_value,
+ val[VAL_REG_BUF_OFFSET]);
+ trace_bcl_hw_sensor_reading("Ibat Max[uA]", *adc_value);
+
+bcl_read_exit:
+ return ret;
+}
+
+static int bcl_read_vbat_min(int *adc_value)
+{
+ int ret = 0, timeout = 0;
+ int8_t val[VAL_CP_REG_BUF_LEN] = {0};
+
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ do {
+ ret = bcl_read_multi_register(
+ (bcl_perph_version == BCL_PMI8994) ? BCL_VBAT_MIN
+ : BCL_8998_VBAT_MIN, val,
+ VAL_CP_REG_BUF_LEN);
+ if (ret) {
+ pr_err("BCL register read error. err:%d\n", ret);
+ goto bcl_read_exit;
+ }
+ } while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]
+ && timeout++ < BCL_READ_RETRY_LIMIT);
+ if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) {
+ ret = -ENODEV;
+ goto bcl_read_exit;
+ }
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ convert_adc_to_vbat_val(adc_value);
+ pr_debug("Vbat Min:%d. ADC_val:%d\n", *adc_value,
+ val[VAL_REG_BUF_OFFSET]);
+ trace_bcl_hw_sensor_reading("vbat Min[uV]", *adc_value);
+
+bcl_read_exit:
+ return ret;
+}
+
+static int bcl_read_ibat(int *adc_value)
+{
+ int ret = 0, timeout = 0;
+ int8_t val[VAL_CP_REG_BUF_LEN] = {0};
+
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ do {
+ ret = bcl_read_multi_register(
+ (bcl_perph_version == BCL_PMI8994) ? BCL_IBAT_VALUE
+ : BCL_8998_IBAT_VALUE, val,
+ VAL_CP_REG_BUF_LEN);
+ if (ret) {
+ pr_err("BCL register read error. err:%d\n", ret);
+ goto bcl_read_exit;
+ }
+ } while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]
+ && timeout++ < BCL_READ_RETRY_LIMIT);
+ if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) {
+ ret = -ENODEV;
+ goto bcl_read_exit;
+ }
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ convert_adc_to_ibat_val(adc_value);
+ pr_debug("Read Ibat:%d. ADC_val:%d\n", *adc_value,
+ val[VAL_REG_BUF_OFFSET]);
+ trace_bcl_hw_sensor_reading("ibat[uA]", *adc_value);
+
+bcl_read_exit:
+ return ret;
+}
+
+static int bcl_read_vbat(int *adc_value)
+{
+ int ret = 0, timeout = 0;
+ int8_t val[VAL_CP_REG_BUF_LEN] = {0};
+
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ do {
+ ret = bcl_read_multi_register(
+ (bcl_perph_version == BCL_PMI8994) ? BCL_VBAT_VALUE :
+ BCL_8998_VBAT_VALUE, val,
+ VAL_CP_REG_BUF_LEN);
+ if (ret) {
+ pr_err("BCL register read error. err:%d\n", ret);
+ goto bcl_read_exit;
+ }
+ } while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]
+ && timeout++ < BCL_READ_RETRY_LIMIT);
+ if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) {
+ ret = -ENODEV;
+ goto bcl_read_exit;
+ }
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ convert_adc_to_vbat_val(adc_value);
+ pr_debug("Read Vbat:%d. ADC_val:%d\n", *adc_value,
+ val[VAL_REG_BUF_OFFSET]);
+ trace_bcl_hw_sensor_reading("vbat[uV]", *adc_value);
+
+bcl_read_exit:
+ return ret;
+}
+
+static void bcl_poll_ibat_low(struct work_struct *work)
+{
+ int ret = 0, val = 0;
+ struct bcl_peripheral_data *perph_data =
+ &bcl_perph->param[BCL_PARAM_CURRENT];
+
+ trace_bcl_hw_event("ibat poll low. Enter");
+ mutex_lock(&perph_data->state_trans_lock);
+ if (perph_data->state != BCL_PARAM_POLLING) {
+ pr_err("Invalid ibat state %d\n", perph_data->state);
+ goto exit_ibat;
+ }
+
+ ret = perph_data->read_max(&val);
+ if (ret) {
+ pr_err("Error in reading ibat. err:%d", ret);
+ goto reschedule_ibat;
+ }
+ ret = perph_data->clear_max();
+ if (ret)
+ pr_err("Error clearing max ibat reg. err:%d\n", ret);
+ if (val <= perph_data->low_trip) {
+ pr_debug("Ibat reached low clear trip. ibat:%d\n", val);
+ trace_bcl_hw_state_event("Polling to Monitor. Ibat[uA]:", val);
+ trace_bcl_hw_mitigation("Ibat low trip. Ibat[uA]", val);
+ perph_data->ops.notify(perph_data->param_data, val,
+ BCL_LOW_TRIP);
+ perph_data->state = BCL_PARAM_MONITOR;
+ enable_irq(perph_data->irq_num);
+ } else {
+ goto reschedule_ibat;
+ }
+
+exit_ibat:
+ mutex_unlock(&perph_data->state_trans_lock);
+ trace_bcl_hw_event("ibat poll low. Exit");
+ return;
+
+reschedule_ibat:
+ mutex_unlock(&perph_data->state_trans_lock);
+ schedule_delayed_work(&perph_data->poll_work,
+ msecs_to_jiffies(perph_data->polling_delay_ms));
+ trace_bcl_hw_event("ibat poll low. Exit");
+ return;
+}
+
+static void bcl_poll_vbat_high(struct work_struct *work)
+{
+ int ret = 0, val = 0;
+ struct bcl_peripheral_data *perph_data =
+ &bcl_perph->param[BCL_PARAM_VOLTAGE];
+
+ trace_bcl_hw_event("vbat poll high. Enter");
+ mutex_lock(&perph_data->state_trans_lock);
+ if (perph_data->state != BCL_PARAM_POLLING) {
+ pr_err("Invalid vbat state %d\n", perph_data->state);
+ goto exit_vbat;
+ }
+
+ ret = perph_data->read_max(&val);
+ if (ret) {
+ pr_err("Error in reading vbat. err:%d", ret);
+ goto reschedule_vbat;
+ }
+ ret = perph_data->clear_max();
+ if (ret)
+ pr_err("Error clearing min vbat reg. err:%d\n", ret);
+ if (val >= perph_data->high_trip) {
+ pr_debug("Vbat reached high clear trip. vbat:%d\n", val);
+ trace_bcl_hw_state_event("Polling to Monitor. vbat[uV]:", val);
+ trace_bcl_hw_mitigation("vbat high trip. vbat[uV]", val);
+ perph_data->ops.notify(perph_data->param_data, val,
+ BCL_HIGH_TRIP);
+ perph_data->state = BCL_PARAM_MONITOR;
+ enable_irq(perph_data->irq_num);
+ } else {
+ goto reschedule_vbat;
+ }
+
+exit_vbat:
+ mutex_unlock(&perph_data->state_trans_lock);
+ trace_bcl_hw_event("vbat poll high. Exit");
+ return;
+
+reschedule_vbat:
+ mutex_unlock(&perph_data->state_trans_lock);
+ schedule_delayed_work(&perph_data->poll_work,
+ msecs_to_jiffies(perph_data->polling_delay_ms));
+ trace_bcl_hw_event("vbat poll high. Exit");
+ return;
+}
+
+static irqreturn_t bcl_handle_ibat(int irq, void *data)
+{
+ int thresh_value = 0, ret = 0;
+ struct bcl_peripheral_data *perph_data =
+ (struct bcl_peripheral_data *)data;
+
+ trace_bcl_hw_mitigation_event("Ibat interrupted");
+ mutex_lock(&perph_data->state_trans_lock);
+ if (perph_data->state == BCL_PARAM_MONITOR) {
+ ret = perph_data->read_max(&perph_data->trip_val);
+ if (ret) {
+ pr_err("Error reading max/min reg. err:%d\n", ret);
+ goto exit_intr;
+ }
+ ret = perph_data->clear_max();
+ if (ret)
+ pr_err("Error clearing max/min reg. err:%d\n", ret);
+ thresh_value = perph_data->high_trip;
+ convert_adc_to_ibat_val(&thresh_value);
+ /* Account threshold trip from PBS threshold for dead time */
+ thresh_value -= perph_data->inhibit_derating_ua;
+ if (perph_data->trip_val < thresh_value) {
+ pr_debug("False Ibat high trip. ibat:%d ibat_thresh_val:%d\n",
+ perph_data->trip_val, thresh_value);
+ trace_bcl_hw_event("Ibat invalid interrupt");
+ goto exit_intr;
+ }
+ pr_debug("Ibat reached high trip. ibat:%d\n",
+ perph_data->trip_val);
+ trace_bcl_hw_state_event("Monitor to Polling. ibat[uA]:",
+ perph_data->trip_val);
+ disable_irq_nosync(perph_data->irq_num);
+ perph_data->state = BCL_PARAM_POLLING;
+ trace_bcl_hw_mitigation("ibat high trip. ibat[uA]",
+ perph_data->trip_val);
+ perph_data->ops.notify(perph_data->param_data,
+ perph_data->trip_val, BCL_HIGH_TRIP);
+ schedule_delayed_work(&perph_data->poll_work,
+ msecs_to_jiffies(perph_data->polling_delay_ms));
+ } else {
+ pr_debug("Ignoring interrupt\n");
+ trace_bcl_hw_event("Ibat Ignoring interrupt");
+ }
+
+exit_intr:
+ mutex_unlock(&perph_data->state_trans_lock);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bcl_handle_vbat(int irq, void *data)
+{
+ int thresh_value = 0, ret = 0;
+ struct bcl_peripheral_data *perph_data =
+ (struct bcl_peripheral_data *)data;
+
+ trace_bcl_hw_mitigation_event("Vbat Interrupted");
+ mutex_lock(&perph_data->state_trans_lock);
+ if (perph_data->state == BCL_PARAM_MONITOR) {
+ ret = perph_data->read_max(&perph_data->trip_val);
+ if (ret) {
+ pr_err("Error reading max/min reg. err:%d\n", ret);
+ goto exit_intr;
+ }
+ ret = perph_data->clear_max();
+ if (ret)
+ pr_err("Error clearing max/min reg. err:%d\n", ret);
+ thresh_value = perph_data->low_trip;
+ convert_adc_to_vbat_val(&thresh_value);
+ if (perph_data->trip_val > thresh_value) {
+ pr_debug("False vbat min trip. vbat:%d vbat_thresh_val:%d\n",
+ perph_data->trip_val, thresh_value);
+ trace_bcl_hw_event("Vbat Invalid interrupt");
+ goto exit_intr;
+ }
+ pr_debug("Vbat reached Low trip. vbat:%d\n",
+ perph_data->trip_val);
+ trace_bcl_hw_state_event("Monitor to Polling. vbat[uV]:",
+ perph_data->trip_val);
+ disable_irq_nosync(perph_data->irq_num);
+ perph_data->state = BCL_PARAM_POLLING;
+ trace_bcl_hw_mitigation("vbat low trip. vbat[uV]",
+ perph_data->trip_val);
+ perph_data->ops.notify(perph_data->param_data,
+ perph_data->trip_val, BCL_LOW_TRIP);
+ schedule_delayed_work(&perph_data->poll_work,
+ msecs_to_jiffies(perph_data->polling_delay_ms));
+ } else {
+ pr_debug("Ignoring interrupt\n");
+ trace_bcl_hw_event("Vbat Ignoring interrupt");
+ }
+
+exit_intr:
+ mutex_unlock(&perph_data->state_trans_lock);
+ return IRQ_HANDLED;
+}
+
+static int bcl_get_devicetree_data(struct platform_device *pdev)
+{
+ int ret = 0, irq_num = 0, temp_val = 0;
+ char *key = NULL;
+ const __be32 *prop = NULL;
+ struct device_node *dev_node = pdev->dev.of_node;
+
+ prop = of_get_address_by_name(dev_node, "fg_user_adc", 0, 0);
+ if (prop) {
+ bcl_perph->base_addr = be32_to_cpu(*prop);
+ pr_debug("fg_user_adc@%04x\n", bcl_perph->base_addr);
+ } else {
+ dev_err(&pdev->dev, "No fg_user_adc registers found\n");
+ return -EINVAL;
+ }
+
+ prop = of_get_address_by_name(dev_node,
+ "pon_spare", 0, 0);
+ if (prop) {
+ bcl_perph->pon_spare_addr = be32_to_cpu(*prop);
+ pr_debug("pon_spare@%04x\n", bcl_perph->pon_spare_addr);
+ }
+
+ /* Register SPMI peripheral interrupt */
+ irq_num = platform_get_irq_byname(pdev, BCL_VBAT_INT_NAME);
+ if (irq_num < 0) {
+ pr_err("Invalid vbat IRQ\n");
+ ret = -ENXIO;
+ goto bcl_dev_exit;
+ }
+ bcl_perph->param[BCL_PARAM_VOLTAGE].irq_num = irq_num;
+ irq_num = platform_get_irq_byname(pdev, BCL_IBAT_INT_NAME);
+ if (irq_num < 0) {
+ pr_err("Invalid ibat IRQ\n");
+ ret = -ENXIO;
+ goto bcl_dev_exit;
+ }
+ bcl_perph->param[BCL_PARAM_CURRENT].irq_num = irq_num;
+
+ if (bcl_perph_version == BCL_PMI8994) {
+ /* Get VADC and IADC scaling factor */
+ key = "qcom,vbat-scaling-factor";
+ READ_CONV_FACTOR(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_VOLTAGE].scaling_factor);
+ key = "qcom,vbat-gain-numerator";
+ READ_CONV_FACTOR(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_VOLTAGE].gain_factor_num);
+ key = "qcom,vbat-gain-denominator";
+ READ_CONV_FACTOR(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_VOLTAGE].gain_factor_den);
+ key = "qcom,ibat-scaling-factor";
+ READ_CONV_FACTOR(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_CURRENT].scaling_factor);
+ key = "qcom,ibat-offset-numerator";
+ READ_CONV_FACTOR(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_CURRENT].offset_factor_num);
+ key = "qcom,ibat-offset-denominator";
+ READ_CONV_FACTOR(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_CURRENT].offset_factor_den);
+ key = "qcom,ibat-gain-numerator";
+ READ_CONV_FACTOR(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_CURRENT].gain_factor_num);
+ key = "qcom,ibat-gain-denominator";
+ READ_CONV_FACTOR(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_CURRENT].gain_factor_den);
+ key = "qcom,inhibit-derating-ua";
+ READ_OPTIONAL_PROP(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_CURRENT].
+ inhibit_derating_ua);
+ } else {
+ prop = of_get_address_by_name(dev_node,
+ "fg_lmh", 0, 0);
+ if (prop) {
+ bcl_perph->fg_lmh_addr = be32_to_cpu(*prop);
+ pr_debug("fg_lmh@%04x\n", bcl_perph->fg_lmh_addr);
+ } else {
+ return -ENODEV;
+ }
+ }
+ key = "qcom,vbat-polling-delay-ms";
+ READ_CONV_FACTOR(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_VOLTAGE].polling_delay_ms);
+ key = "qcom,ibat-polling-delay-ms";
+ READ_CONV_FACTOR(dev_node, key, temp_val, ret,
+ bcl_perph->param[BCL_PARAM_CURRENT].polling_delay_ms);
+
+bcl_dev_exit:
+ return ret;
+}
+
+static int bcl_calibrate(void)
+{
+ int ret = 0;
+ int8_t i_src = 0, val = 0;
+
+ ret = bcl_read_register(BCL_I_SENSE_SRC, &i_src);
+ if (ret) {
+ pr_err("Error reading current sense reg. err:%d\n", ret);
+ goto bcl_cal_exit;
+ }
+
+ ret = bcl_read_register((i_src & 0x01) ? BCL_I_GAIN_RSENSE
+ : BCL_I_GAIN_BATFET, &val);
+ if (ret) {
+ pr_err("Error reading %s current gain. err:%d\n",
+ (i_src & 0x01) ? "rsense" : "batfet", ret);
+ goto bcl_cal_exit;
+ }
+ bcl_perph->param[BCL_PARAM_CURRENT].gain = val;
+ ret = bcl_read_register((i_src & 0x01) ? BCL_I_OFFSET_RSENSE
+ : BCL_I_OFFSET_BATFET, &val);
+ if (ret) {
+ pr_err("Error reading %s current offset. err:%d\n",
+ (i_src & 0x01) ? "rsense" : "batfet", ret);
+ goto bcl_cal_exit;
+ }
+ bcl_perph->param[BCL_PARAM_CURRENT].offset = val;
+ ret = bcl_read_register(BCL_V_GAIN_BAT, &val);
+ if (ret) {
+ pr_err("Error reading vbat offset. err:%d\n", ret);
+ goto bcl_cal_exit;
+ }
+ bcl_perph->param[BCL_PARAM_VOLTAGE].gain = val;
+
+ if (((i_src & 0x01) != bcl_perph->i_src)
+ && (bcl_perph->enabled)) {
+ bcl_set_low_vbat(bcl_perph->param[BCL_PARAM_VOLTAGE]
+ .low_trip);
+ bcl_set_high_ibat(bcl_perph->param[BCL_PARAM_CURRENT]
+ .high_trip);
+ bcl_perph->i_src = i_src;
+ }
+
+bcl_cal_exit:
+ return ret;
+}
+
+static void power_supply_callback(struct power_supply *psy)
+{
+ static struct power_supply *bms_psy;
+ int ret = 0;
+
+ if (calibration_done)
+ return;
+
+ if (!bms_psy)
+ bms_psy = power_supply_get_by_name("bms");
+ if (bms_psy) {
+ calibration_done = true;
+ trace_bcl_hw_event("Recalibrate callback");
+ ret = bcl_calibrate();
+ if (ret)
+ pr_err("Could not read calibration values. err:%d",
+ ret);
+ }
+}
+
+static int bcl_psy_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ return 0;
+}
+static int bcl_psy_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ return -EINVAL;
+}
+
+static int bcl_update_data(void)
+{
+ int ret = 0;
+
+ bcl_perph->param[BCL_PARAM_VOLTAGE].ops.read = bcl_read_vbat;
+ bcl_perph->param[BCL_PARAM_VOLTAGE].ops.get_high_trip
+ = bcl_read_vbat_high_trip;
+ bcl_perph->param[BCL_PARAM_VOLTAGE].ops.get_low_trip
+ = bcl_read_vbat_low_trip;
+ bcl_perph->param[BCL_PARAM_VOLTAGE].ops.set_high_trip
+ = bcl_set_high_vbat;
+ bcl_perph->param[BCL_PARAM_VOLTAGE].ops.set_low_trip
+ = bcl_set_low_vbat;
+ bcl_perph->param[BCL_PARAM_VOLTAGE].ops.enable
+ = bcl_monitor_enable;
+ bcl_perph->param[BCL_PARAM_VOLTAGE].ops.disable
+ = bcl_monitor_disable;
+ bcl_perph->param[BCL_PARAM_VOLTAGE].read_max
+ = bcl_read_vbat_min;
+ bcl_perph->param[BCL_PARAM_VOLTAGE].clear_max
+ = bcl_clear_vbat_min;
+
+ bcl_perph->param[BCL_PARAM_CURRENT].ops.read = bcl_read_ibat;
+ bcl_perph->param[BCL_PARAM_CURRENT].ops.get_high_trip
+ = bcl_read_ibat_high_trip;
+ bcl_perph->param[BCL_PARAM_CURRENT].ops.get_low_trip
+ = bcl_read_ibat_low_trip;
+ bcl_perph->param[BCL_PARAM_CURRENT].ops.set_high_trip
+ = bcl_set_high_ibat;
+ bcl_perph->param[BCL_PARAM_CURRENT].ops.set_low_trip
+ = bcl_set_low_ibat;
+ bcl_perph->param[BCL_PARAM_CURRENT].ops.enable
+ = bcl_monitor_enable;
+ bcl_perph->param[BCL_PARAM_CURRENT].ops.disable
+ = bcl_monitor_disable;
+ bcl_perph->param[BCL_PARAM_CURRENT].read_max
+ = bcl_read_ibat_max;
+ bcl_perph->param[BCL_PARAM_CURRENT].clear_max
+ = bcl_clear_ibat_max;
+
+ bcl_perph->param[BCL_PARAM_VOLTAGE].param_data = msm_bcl_register_param(
+ BCL_PARAM_VOLTAGE, &bcl_perph->param[BCL_PARAM_VOLTAGE].ops,
+ "vbat");
+ if (!bcl_perph->param[BCL_PARAM_VOLTAGE].param_data) {
+ pr_err("register Vbat failed.\n");
+ ret = -ENODEV;
+ goto update_data_exit;
+ }
+ bcl_perph->param[BCL_PARAM_CURRENT].param_data = msm_bcl_register_param(
+ BCL_PARAM_CURRENT, &bcl_perph->param[BCL_PARAM_CURRENT].ops,
+ "ibat");
+ if (!bcl_perph->param[BCL_PARAM_CURRENT].param_data) {
+ pr_err("register Ibat failed.\n");
+ ret = -ENODEV;
+ goto update_data_exit;
+ }
+ INIT_DELAYED_WORK(&bcl_perph->param[BCL_PARAM_VOLTAGE].poll_work,
+ bcl_poll_vbat_high);
+ INIT_DELAYED_WORK(&bcl_perph->param[BCL_PARAM_CURRENT].poll_work,
+ bcl_poll_ibat_low);
+ mutex_init(&bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock);
+ mutex_init(&bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock);
+
+update_data_exit:
+ return ret;
+}
+
+static int bcl_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct power_supply_config bcl_psy_cfg = {};
+
+ bcl_perph = devm_kzalloc(&pdev->dev, sizeof(struct bcl_device),
+ GFP_KERNEL);
+ if (!bcl_perph) {
+ pr_err("Memory alloc failed\n");
+ return -ENOMEM;
+ }
+ bcl_perph->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!bcl_perph->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+ bcl_perph->pdev = pdev;
+ bcl_perph->dev = &(pdev->dev);
+
+ ret = bcl_get_devicetree_data(pdev);
+ if (ret) {
+ pr_err("Device tree data fetch error. err:%d", ret);
+ goto bcl_probe_exit;
+ }
+ if (bcl_perph_version == BCL_PMI8994) {
+ ret = bcl_calibrate();
+ if (ret) {
+ pr_debug("Could not read calibration values. err:%d",
+ ret);
+ goto bcl_probe_exit;
+ }
+ bcl_psy_d.name = bcl_psy_name;
+ bcl_psy_d.type = POWER_SUPPLY_TYPE_BMS;
+ bcl_psy_d.get_property = bcl_psy_get_property;
+ bcl_psy_d.set_property = bcl_psy_set_property;
+ bcl_psy_d.num_properties = 0;
+ bcl_psy_d.external_power_changed = power_supply_callback;
+
+ bcl_psy_cfg.num_supplicants = 0;
+ bcl_psy_cfg.drv_data = bcl_perph;
+
+ bcl_psy = devm_power_supply_register(&pdev->dev, &bcl_psy_d,
+ &bcl_psy_cfg);
+ if (IS_ERR(bcl_psy)) {
+ pr_err("Unable to register bcl_psy rc = %ld\n",
+ PTR_ERR(bcl_psy));
+ return ret;
+ }
+ } else {
+ bcl_write_register(BCL_8998_LMH_CFG, BCL_LMH_CFG_VAL);
+ bcl_write_register(BCL_8998_BCL_CFG, BCL_CFG_VAL);
+ bcl_write_general_register(LMH_8998_INT_POL_HIGH,
+ bcl_perph->fg_lmh_addr, LMH_INT_VAL);
+ bcl_write_general_register(LMH_8998_INT_EN,
+ bcl_perph->fg_lmh_addr, LMH_INT_VAL);
+ }
+
+ ret = bcl_update_data();
+ if (ret) {
+ pr_err("Update data failed. err:%d", ret);
+ goto bcl_probe_exit;
+ }
+ mutex_lock(&bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock);
+ ret = devm_request_threaded_irq(&pdev->dev,
+ bcl_perph->param[BCL_PARAM_VOLTAGE].irq_num,
+ NULL, bcl_handle_vbat,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "bcl_vbat_interrupt",
+ &bcl_perph->param[BCL_PARAM_VOLTAGE]);
+ if (ret) {
+ dev_err(&pdev->dev, "Error requesting VBAT irq. err:%d", ret);
+ mutex_unlock(
+ &bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock);
+ goto bcl_probe_exit;
+ }
+ /*
+ * BCL is enabled by default in hardware.
+ * Disable BCL monitoring till a valid threshold is set by APPS
+ */
+ disable_irq_nosync(bcl_perph->param[BCL_PARAM_VOLTAGE].irq_num);
+ mutex_unlock(&bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock);
+
+ mutex_lock(&bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock);
+ ret = devm_request_threaded_irq(&pdev->dev,
+ bcl_perph->param[BCL_PARAM_CURRENT].irq_num,
+ NULL, bcl_handle_ibat,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "bcl_ibat_interrupt",
+ &bcl_perph->param[BCL_PARAM_CURRENT]);
+ if (ret) {
+ dev_err(&pdev->dev, "Error requesting IBAT irq. err:%d", ret);
+ mutex_unlock(
+ &bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock);
+ goto bcl_probe_exit;
+ }
+ disable_irq_nosync(bcl_perph->param[BCL_PARAM_CURRENT].irq_num);
+ mutex_unlock(&bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock);
+
+ dev_set_drvdata(&pdev->dev, bcl_perph);
+ ret = bcl_write_register(BCL_MONITOR_EN, BIT(7));
+ if (ret) {
+ pr_err("Error accessing BCL peripheral. err:%d\n", ret);
+ goto bcl_probe_exit;
+ }
+
+ return 0;
+
+bcl_probe_exit:
+ bcl_perph = NULL;
+ return ret;
+}
+
+static int bcl_remove(struct platform_device *pdev)
+{
+ int ret = 0, i = 0;
+
+ ret = bcl_monitor_disable();
+ if (ret)
+ pr_err("Error disabling BCL. err:%d\n", ret);
+
+ for (; i < BCL_PARAM_MAX; i++) {
+ if (!bcl_perph->param[i].param_data)
+ continue;
+
+ ret = msm_bcl_unregister_param(bcl_perph->param[i].param_data);
+ if (ret)
+ pr_err("Error unregistering with Framework. err:%d\n",
+ ret);
+ }
+
+ return 0;
+}
+
+static struct of_device_id bcl_match[] = {
+ { .compatible = "qcom,msm-bcl",
+ .data = (void *) BCL_PMI8994,
+ },
+ { .compatible = "qcom,msm-bcl-lmh",
+ .data = (void *) BCL_PMI8998,
+ },
+ {},
+};
+
+static struct platform_driver bcl_driver = {
+ .probe = bcl_probe,
+ .remove = bcl_remove,
+ .driver = {
+ .name = BCL_DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = bcl_match,
+ },
+};
+
+static int __init bcl_perph_init(void)
+{
+ struct device_node *comp_node;
+
+ comp_node = of_find_matching_node(NULL, bcl_match);
+ bcl_perph_version = BCL_PMI8994;
+ if (comp_node) {
+ const struct of_device_id *match = of_match_node(bcl_match,
+ comp_node);
+ if (!match) {
+ pr_err("Couldnt find a match\n");
+ goto plt_register;
+ }
+ bcl_perph_version = (enum bcl_hw_type)match->data;
+ of_node_put(comp_node);
+ }
+
+plt_register:
+ return platform_driver_register(&bcl_driver);
+}
+
+static void __exit bcl_perph_exit(void)
+{
+ platform_driver_unregister(&bcl_driver);
+}
+fs_initcall(bcl_perph_init);
+module_exit(bcl_perph_exit);
+MODULE_ALIAS("platform:" BCL_DRIVER_NAME);
diff --git a/drivers/power/supply/qcom/fg-core.h b/drivers/power/supply/qcom/fg-core.h
new file mode 100644
index 000000000000..88dcdd8fd7be
--- /dev/null
+++ b/drivers/power/supply/qcom/fg-core.h
@@ -0,0 +1,502 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 __FG_CORE_H__
+#define __FG_CORE_H__
+
+#include <linux/atomic.h>
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string_helpers.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/pmic-voter.h>
+
+#define fg_dbg(chip, reason, fmt, ...) \
+ do { \
+ if (*chip->debug_mask & (reason)) \
+ pr_info(fmt, ##__VA_ARGS__); \
+ else \
+ pr_debug(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define is_between(left, right, value) \
+ (((left) >= (right) && (left) >= (value) \
+ && (value) >= (right)) \
+ || ((left) <= (right) && (left) <= (value) \
+ && (value) <= (right)))
+
+/* Awake votable reasons */
+#define SRAM_READ "fg_sram_read"
+#define SRAM_WRITE "fg_sram_write"
+#define PROFILE_LOAD "fg_profile_load"
+#define TTF_PRIMING "fg_ttf_priming"
+
+/* Delta BSOC irq votable reasons */
+#define DELTA_BSOC_IRQ_VOTER "fg_delta_bsoc_irq"
+
+/* Battery missing irq votable reasons */
+#define BATT_MISS_IRQ_VOTER "fg_batt_miss_irq"
+
+#define DEBUG_PRINT_BUFFER_SIZE 64
+/* 3 byte address + 1 space character */
+#define ADDR_LEN 4
+/* Format is 'XX ' */
+#define CHARS_PER_ITEM 3
+/* 4 data items per line */
+#define ITEMS_PER_LINE 4
+#define MAX_LINE_LENGTH (ADDR_LEN + (ITEMS_PER_LINE * \
+ CHARS_PER_ITEM) + 1) \
+
+#define FG_SRAM_ADDRESS_MAX 255
+#define FG_SRAM_LEN 504
+#define PROFILE_LEN 224
+#define PROFILE_COMP_LEN 148
+#define BUCKET_COUNT 8
+#define BUCKET_SOC_PCT (256 / BUCKET_COUNT)
+
+#define KI_COEFF_MAX 62200
+#define KI_COEFF_SOC_LEVELS 3
+
+#define SLOPE_LIMIT_COEFF_MAX 31
+
+#define BATT_THERM_NUM_COEFFS 3
+
+#define MAX_CC_STEPS 20
+
+/* Debug flag definitions */
+enum fg_debug_flag {
+ FG_IRQ = BIT(0), /* Show interrupts */
+ FG_STATUS = BIT(1), /* Show FG status changes */
+ FG_POWER_SUPPLY = BIT(2), /* Show POWER_SUPPLY */
+ FG_SRAM_WRITE = BIT(3), /* Show SRAM writes */
+ FG_SRAM_READ = BIT(4), /* Show SRAM reads */
+ FG_BUS_WRITE = BIT(5), /* Show REGMAP writes */
+ FG_BUS_READ = BIT(6), /* Show REGMAP reads */
+ FG_CAP_LEARN = BIT(7), /* Show capacity learning */
+ FG_TTF = BIT(8), /* Show time to full */
+};
+
+/* SRAM access */
+enum sram_access_flags {
+ FG_IMA_DEFAULT = 0,
+ FG_IMA_ATOMIC = BIT(0),
+ FG_IMA_NO_WLOCK = BIT(1),
+};
+
+/* JEITA */
+enum {
+ JEITA_COLD = 0,
+ JEITA_COOL,
+ JEITA_WARM,
+ JEITA_HOT,
+ NUM_JEITA_LEVELS,
+};
+
+/* FG irqs */
+enum fg_irq_index {
+ MSOC_FULL_IRQ = 0,
+ MSOC_HIGH_IRQ,
+ MSOC_EMPTY_IRQ,
+ MSOC_LOW_IRQ,
+ MSOC_DELTA_IRQ,
+ BSOC_DELTA_IRQ,
+ SOC_READY_IRQ,
+ SOC_UPDATE_IRQ,
+ BATT_TEMP_DELTA_IRQ,
+ BATT_MISSING_IRQ,
+ ESR_DELTA_IRQ,
+ VBATT_LOW_IRQ,
+ VBATT_PRED_DELTA_IRQ,
+ DMA_GRANT_IRQ,
+ MEM_XCP_IRQ,
+ IMA_RDY_IRQ,
+ FG_IRQ_MAX,
+};
+
+/*
+ * List of FG_SRAM parameters. Please add a parameter only if it is an entry
+ * that will be used either to configure an entity (e.g. termination current)
+ * which might need some encoding (or) it is an entry that will be read from
+ * SRAM and decoded (e.g. CC_SOC_SW) for SW to use at various places. For
+ * generic read/writes to SRAM registers, please use fg_sram_read/write APIs
+ * directly without adding an entry here.
+ */
+enum fg_sram_param_id {
+ FG_SRAM_BATT_SOC = 0,
+ FG_SRAM_FULL_SOC,
+ FG_SRAM_VOLTAGE_PRED,
+ FG_SRAM_OCV,
+ FG_SRAM_ESR,
+ FG_SRAM_RSLOW,
+ FG_SRAM_ALG_FLAGS,
+ FG_SRAM_CC_SOC,
+ FG_SRAM_CC_SOC_SW,
+ FG_SRAM_ACT_BATT_CAP,
+ FG_SRAM_TIMEBASE,
+ /* Entries below here are configurable during initialization */
+ FG_SRAM_CUTOFF_VOLT,
+ FG_SRAM_EMPTY_VOLT,
+ FG_SRAM_VBATT_LOW,
+ FG_SRAM_FLOAT_VOLT,
+ FG_SRAM_VBATT_FULL,
+ FG_SRAM_ESR_TIMER_DISCHG_MAX,
+ FG_SRAM_ESR_TIMER_DISCHG_INIT,
+ FG_SRAM_ESR_TIMER_CHG_MAX,
+ FG_SRAM_ESR_TIMER_CHG_INIT,
+ FG_SRAM_ESR_PULSE_THRESH,
+ FG_SRAM_SYS_TERM_CURR,
+ FG_SRAM_CHG_TERM_CURR,
+ FG_SRAM_CHG_TERM_BASE_CURR,
+ FG_SRAM_DELTA_MSOC_THR,
+ FG_SRAM_DELTA_BSOC_THR,
+ FG_SRAM_RECHARGE_SOC_THR,
+ FG_SRAM_RECHARGE_VBATT_THR,
+ FG_SRAM_KI_COEFF_MED_DISCHG,
+ FG_SRAM_KI_COEFF_HI_DISCHG,
+ FG_SRAM_KI_COEFF_FULL_SOC,
+ FG_SRAM_ESR_TIGHT_FILTER,
+ FG_SRAM_ESR_BROAD_FILTER,
+ FG_SRAM_SLOPE_LIMIT,
+ FG_SRAM_MAX,
+};
+
+struct fg_sram_param {
+ u16 addr_word;
+ int addr_byte;
+ u8 len;
+ int value;
+ int numrtr;
+ int denmtr;
+ int offset;
+ void (*encode)(struct fg_sram_param *sp, enum fg_sram_param_id id,
+ int val, u8 *buf);
+ int (*decode)(struct fg_sram_param *sp, enum fg_sram_param_id id,
+ int val);
+};
+
+enum fg_alg_flag_id {
+ ALG_FLAG_SOC_LT_OTG_MIN = 0,
+ ALG_FLAG_SOC_LT_RECHARGE,
+ ALG_FLAG_IBATT_LT_ITERM,
+ ALG_FLAG_IBATT_GT_HPM,
+ ALG_FLAG_IBATT_GT_UPM,
+ ALG_FLAG_VBATT_LT_RECHARGE,
+ ALG_FLAG_VBATT_GT_VFLOAT,
+ ALG_FLAG_MAX,
+};
+
+struct fg_alg_flag {
+ char *name;
+ u8 bit;
+ bool invalid;
+};
+
+enum wa_flags {
+ PMI8998_V1_REV_WA = BIT(0),
+ PM660_TSMC_OSC_WA = BIT(1),
+};
+
+enum slope_limit_status {
+ LOW_TEMP_DISCHARGE = 0,
+ LOW_TEMP_CHARGE,
+ HIGH_TEMP_DISCHARGE,
+ HIGH_TEMP_CHARGE,
+ SLOPE_LIMIT_NUM_COEFFS,
+};
+
+enum esr_timer_config {
+ TIMER_RETRY = 0,
+ TIMER_MAX,
+ NUM_ESR_TIMERS,
+};
+
+enum ttf_mode {
+ TTF_MODE_NORMAL = 0,
+ TTF_MODE_QNOVO,
+};
+
+/* DT parameters for FG device */
+struct fg_dt_props {
+ bool force_load_profile;
+ bool hold_soc_while_full;
+ bool auto_recharge_soc;
+ int cutoff_volt_mv;
+ int empty_volt_mv;
+ int vbatt_low_thr_mv;
+ int chg_term_curr_ma;
+ int chg_term_base_curr_ma;
+ int sys_term_curr_ma;
+ int delta_soc_thr;
+ int recharge_soc_thr;
+ int recharge_volt_thr_mv;
+ int rsense_sel;
+ int esr_timer_charging[NUM_ESR_TIMERS];
+ int esr_timer_awake[NUM_ESR_TIMERS];
+ int esr_timer_asleep[NUM_ESR_TIMERS];
+ int rconn_mohms;
+ int esr_clamp_mohms;
+ int cl_start_soc;
+ int cl_max_temp;
+ int cl_min_temp;
+ int cl_max_cap_inc;
+ int cl_max_cap_dec;
+ int cl_max_cap_limit;
+ int cl_min_cap_limit;
+ int jeita_hyst_temp;
+ int batt_temp_delta;
+ int esr_flt_switch_temp;
+ int esr_tight_flt_upct;
+ int esr_broad_flt_upct;
+ int esr_tight_lt_flt_upct;
+ int esr_broad_lt_flt_upct;
+ int slope_limit_temp;
+ int esr_pulse_thresh_ma;
+ int esr_meas_curr_ma;
+ int jeita_thresholds[NUM_JEITA_LEVELS];
+ int ki_coeff_soc[KI_COEFF_SOC_LEVELS];
+ int ki_coeff_med_dischg[KI_COEFF_SOC_LEVELS];
+ int ki_coeff_hi_dischg[KI_COEFF_SOC_LEVELS];
+ int slope_limit_coeffs[SLOPE_LIMIT_NUM_COEFFS];
+ u8 batt_therm_coeffs[BATT_THERM_NUM_COEFFS];
+};
+
+/* parameters from battery profile */
+struct fg_batt_props {
+ const char *batt_type_str;
+ char *batt_profile;
+ int float_volt_uv;
+ int vbatt_full_mv;
+ int fastchg_curr_ma;
+};
+
+struct fg_cyc_ctr_data {
+ bool en;
+ bool started[BUCKET_COUNT];
+ u16 count[BUCKET_COUNT];
+ u8 last_soc[BUCKET_COUNT];
+ int id;
+ struct mutex lock;
+};
+
+struct fg_cap_learning {
+ bool active;
+ int init_cc_soc_sw;
+ int64_t nom_cap_uah;
+ int64_t init_cc_uah;
+ int64_t final_cc_uah;
+ int64_t learned_cc_uah;
+ struct mutex lock;
+};
+
+struct fg_irq_info {
+ const char *name;
+ const irq_handler_t handler;
+ bool wakeable;
+ int irq;
+};
+
+struct fg_circ_buf {
+ int arr[10];
+ int size;
+ int head;
+};
+
+struct fg_cc_step_data {
+ int arr[MAX_CC_STEPS];
+ int sel;
+};
+
+struct fg_pt {
+ s32 x;
+ s32 y;
+};
+
+struct ttf {
+ struct fg_circ_buf ibatt;
+ struct fg_circ_buf vbatt;
+ struct fg_cc_step_data cc_step;
+ struct mutex lock;
+ int mode;
+ int last_ttf;
+ s64 last_ms;
+};
+
+static const struct fg_pt fg_ln_table[] = {
+ { 1000, 0 },
+ { 2000, 693 },
+ { 4000, 1386 },
+ { 6000, 1792 },
+ { 8000, 2079 },
+ { 16000, 2773 },
+ { 32000, 3466 },
+ { 64000, 4159 },
+ { 128000, 4852 },
+};
+
+/* each tuple is - <temperature in degC, Timebase> */
+static const struct fg_pt fg_tsmc_osc_table[] = {
+ { -20, 395064 },
+ { -10, 398114 },
+ { 0, 401669 },
+ { 10, 404641 },
+ { 20, 408856 },
+ { 25, 412449 },
+ { 30, 416532 },
+ { 40, 420289 },
+ { 50, 425020 },
+ { 60, 430160 },
+ { 70, 434175 },
+ { 80, 439475 },
+ { 90, 444992 },
+};
+
+struct fg_chip {
+ struct device *dev;
+ struct pmic_revid_data *pmic_rev_id;
+ struct regmap *regmap;
+ struct dentry *dfs_root;
+ struct power_supply *fg_psy;
+ struct power_supply *batt_psy;
+ struct power_supply *usb_psy;
+ struct power_supply *dc_psy;
+ struct power_supply *parallel_psy;
+ struct power_supply *pc_port_psy;
+ struct iio_channel *batt_id_chan;
+ struct iio_channel *die_temp_chan;
+ struct fg_memif *sram;
+ struct fg_irq_info *irqs;
+ struct votable *awake_votable;
+ struct votable *delta_bsoc_irq_en_votable;
+ struct votable *batt_miss_irq_en_votable;
+ struct fg_sram_param *sp;
+ struct fg_alg_flag *alg_flags;
+ int *debug_mask;
+ char batt_profile[PROFILE_LEN];
+ struct fg_dt_props dt;
+ struct fg_batt_props bp;
+ struct fg_cyc_ctr_data cyc_ctr;
+ struct notifier_block nb;
+ struct fg_cap_learning cl;
+ struct ttf ttf;
+ struct mutex bus_lock;
+ struct mutex sram_rw_lock;
+ struct mutex charge_full_lock;
+ u32 batt_soc_base;
+ u32 batt_info_base;
+ u32 mem_if_base;
+ u32 rradc_base;
+ u32 wa_flags;
+ int batt_id_ohms;
+ int ki_coeff_full_soc;
+ int charge_status;
+ int prev_charge_status;
+ int charge_done;
+ int charge_type;
+ int online_status;
+ int last_soc;
+ int last_batt_temp;
+ int health;
+ int maint_soc;
+ int delta_soc;
+ int last_msoc;
+ int last_recharge_volt_mv;
+ int esr_timer_charging_default[NUM_ESR_TIMERS];
+ enum slope_limit_status slope_limit_sts;
+ bool profile_available;
+ bool profile_loaded;
+ bool battery_missing;
+ bool fg_restarting;
+ bool charge_full;
+ bool recharge_soc_adjusted;
+ bool ki_coeff_dischg_en;
+ bool esr_fcc_ctrl_en;
+ bool soc_reporting_ready;
+ bool esr_flt_cold_temp_en;
+ bool slope_limit_en;
+ bool use_ima_single_mode;
+ struct completion soc_update;
+ struct completion soc_ready;
+ struct delayed_work profile_load_work;
+ struct work_struct status_change_work;
+ struct delayed_work ttf_work;
+ struct delayed_work sram_dump_work;
+};
+
+/* Debugfs data structures are below */
+
+/* Log buffer */
+struct fg_log_buffer {
+ size_t rpos;
+ size_t wpos;
+ size_t len;
+ char data[0];
+};
+
+/* transaction parameters */
+struct fg_trans {
+ struct fg_chip *chip;
+ struct mutex fg_dfs_lock; /* Prevent thread concurrency */
+ struct fg_log_buffer *log;
+ u32 cnt;
+ u16 addr;
+ u32 offset;
+ u8 *data;
+};
+
+struct fg_dbgfs {
+ struct debugfs_blob_wrapper help_msg;
+ struct fg_chip *chip;
+ struct dentry *root;
+ u32 cnt;
+ u32 addr;
+};
+
+extern int fg_sram_write(struct fg_chip *chip, u16 address, u8 offset,
+ u8 *val, int len, int flags);
+extern int fg_sram_read(struct fg_chip *chip, u16 address, u8 offset,
+ u8 *val, int len, int flags);
+extern int fg_sram_masked_write(struct fg_chip *chip, u16 address, u8 offset,
+ u8 mask, u8 val, int flags);
+extern int fg_interleaved_mem_read(struct fg_chip *chip, u16 address,
+ u8 offset, u8 *val, int len);
+extern int fg_interleaved_mem_write(struct fg_chip *chip, u16 address,
+ u8 offset, u8 *val, int len, bool atomic_access);
+extern int fg_read(struct fg_chip *chip, int addr, u8 *val, int len);
+extern int fg_write(struct fg_chip *chip, int addr, u8 *val, int len);
+extern int fg_masked_write(struct fg_chip *chip, int addr, u8 mask, u8 val);
+extern int fg_ima_init(struct fg_chip *chip);
+extern int fg_clear_ima_errors_if_any(struct fg_chip *chip, bool check_hw_sts);
+extern int fg_clear_dma_errors_if_any(struct fg_chip *chip);
+extern int fg_debugfs_create(struct fg_chip *chip);
+extern void fill_string(char *str, size_t str_len, u8 *buf, int buf_len);
+extern void dump_sram(u8 *buf, int addr, int len);
+extern int64_t twos_compliment_extend(int64_t val, int s_bit_pos);
+extern s64 fg_float_decode(u16 val);
+extern bool is_input_present(struct fg_chip *chip);
+extern bool is_qnovo_en(struct fg_chip *chip);
+extern void fg_circ_buf_add(struct fg_circ_buf *, int);
+extern void fg_circ_buf_clr(struct fg_circ_buf *);
+extern int fg_circ_buf_avg(struct fg_circ_buf *, int *);
+extern int fg_circ_buf_median(struct fg_circ_buf *, int *);
+extern int fg_lerp(const struct fg_pt *, size_t, s32, s32 *);
+#endif
diff --git a/drivers/power/supply/qcom/fg-memif.c b/drivers/power/supply/qcom/fg-memif.c
new file mode 100644
index 000000000000..8a949bfe61d0
--- /dev/null
+++ b/drivers/power/supply/qcom/fg-memif.c
@@ -0,0 +1,780 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "FG: %s: " fmt, __func__
+
+#include "fg-core.h"
+#include "fg-reg.h"
+
+/* Generic definitions */
+#define RETRY_COUNT 3
+#define BYTES_PER_SRAM_WORD 4
+
+enum {
+ FG_READ = 0,
+ FG_WRITE,
+};
+
+static int fg_set_address(struct fg_chip *chip, u16 address)
+{
+ u8 buffer[2];
+ int rc;
+
+ buffer[0] = address & 0xFF;
+ /* MSB has to be written zero */
+ buffer[1] = 0;
+
+ rc = fg_write(chip, MEM_IF_ADDR_LSB(chip), buffer, 2);
+ if (rc < 0) {
+ pr_err("failed to write to 0x%04X, rc=%d\n",
+ MEM_IF_ADDR_LSB(chip), rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int fg_config_access_mode(struct fg_chip *chip, bool access, bool burst)
+{
+ int rc;
+ u8 intf_ctl = 0;
+
+ fg_dbg(chip, FG_SRAM_READ | FG_SRAM_WRITE, "access: %d burst: %d\n",
+ access, burst);
+
+ WARN_ON(burst && chip->use_ima_single_mode);
+ intf_ctl = ((access == FG_WRITE) ? IMA_WR_EN_BIT : 0) |
+ (burst ? MEM_ACS_BURST_BIT : 0);
+
+ rc = fg_masked_write(chip, MEM_IF_IMA_CTL(chip), IMA_CTL_MASK,
+ intf_ctl);
+ if (rc < 0) {
+ pr_err("failed to write to 0x%04x, rc=%d\n",
+ MEM_IF_IMA_CTL(chip), rc);
+ return -EIO;
+ }
+
+ return rc;
+}
+
+static int fg_run_iacs_clear_sequence(struct fg_chip *chip)
+{
+ u8 val, hw_sts, exp_sts;
+ int rc, tries = 250;
+
+ /*
+ * Values to write for running IACS clear sequence comes from
+ * hardware documentation.
+ */
+ rc = fg_masked_write(chip, MEM_IF_IMA_CFG(chip),
+ IACS_CLR_BIT | STATIC_CLK_EN_BIT,
+ IACS_CLR_BIT | STATIC_CLK_EN_BIT);
+ if (rc < 0) {
+ pr_err("failed to write 0x%04x, rc=%d\n", MEM_IF_IMA_CFG(chip),
+ rc);
+ return rc;
+ }
+
+ rc = fg_config_access_mode(chip, FG_READ, false);
+ if (rc < 0) {
+ pr_err("failed to write to 0x%04x, rc=%d\n",
+ MEM_IF_IMA_CTL(chip), rc);
+ return rc;
+ }
+
+ rc = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+ MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT,
+ MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT);
+ if (rc < 0) {
+ pr_err("failed to set ima_req_access bit rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Delay for the clock to reach FG */
+ usleep_range(35, 40);
+
+ while (1) {
+ val = 0;
+ rc = fg_write(chip, MEM_IF_ADDR_MSB(chip), &val, 1);
+ if (rc < 0) {
+ pr_err("failed to write 0x%04x, rc=%d\n",
+ MEM_IF_ADDR_MSB(chip), rc);
+ return rc;
+ }
+
+ val = 0;
+ rc = fg_write(chip, MEM_IF_WR_DATA3(chip), &val, 1);
+ if (rc < 0) {
+ pr_err("failed to write 0x%04x, rc=%d\n",
+ MEM_IF_WR_DATA3(chip), rc);
+ return rc;
+ }
+
+ rc = fg_read(chip, MEM_IF_RD_DATA3(chip), &val, 1);
+ if (rc < 0) {
+ pr_err("failed to read 0x%04x, rc=%d\n",
+ MEM_IF_RD_DATA3(chip), rc);
+ return rc;
+ }
+
+ /* Delay for IMA hardware to clear */
+ usleep_range(35, 40);
+
+ rc = fg_read(chip, MEM_IF_IMA_HW_STS(chip), &hw_sts, 1);
+ if (rc < 0) {
+ pr_err("failed to read ima_hw_sts rc=%d\n", rc);
+ return rc;
+ }
+
+ if (hw_sts != 0)
+ continue;
+
+ rc = fg_read(chip, MEM_IF_IMA_EXP_STS(chip), &exp_sts, 1);
+ if (rc < 0) {
+ pr_err("failed to read ima_exp_sts rc=%d\n", rc);
+ return rc;
+ }
+
+ if (exp_sts == 0 || !(--tries))
+ break;
+ }
+
+ if (!tries)
+ pr_err("Failed to clear the error? hw_sts: %x exp_sts: %d\n",
+ hw_sts, exp_sts);
+
+ rc = fg_masked_write(chip, MEM_IF_IMA_CFG(chip), IACS_CLR_BIT, 0);
+ if (rc < 0) {
+ pr_err("failed to write 0x%04x, rc=%d\n", MEM_IF_IMA_CFG(chip),
+ rc);
+ return rc;
+ }
+
+ udelay(5);
+
+ rc = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+ MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT, 0);
+ if (rc < 0) {
+ pr_err("failed to write to 0x%04x, rc=%d\n",
+ MEM_IF_MEM_INTF_CFG(chip), rc);
+ return rc;
+ }
+
+ /* Delay before next transaction is attempted */
+ usleep_range(35, 40);
+ fg_dbg(chip, FG_SRAM_READ | FG_SRAM_WRITE, "IACS clear sequence complete\n");
+ return rc;
+}
+
+int fg_clear_dma_errors_if_any(struct fg_chip *chip)
+{
+ int rc;
+ u8 dma_sts;
+ bool error_present;
+
+ rc = fg_read(chip, MEM_IF_DMA_STS(chip), &dma_sts, 1);
+ if (rc < 0) {
+ pr_err("failed to read addr=0x%04x, rc=%d\n",
+ MEM_IF_DMA_STS(chip), rc);
+ return rc;
+ }
+ fg_dbg(chip, FG_STATUS, "dma_sts: %x\n", dma_sts);
+
+ error_present = dma_sts & (DMA_WRITE_ERROR_BIT | DMA_READ_ERROR_BIT);
+ rc = fg_masked_write(chip, MEM_IF_DMA_CTL(chip), DMA_CLEAR_LOG_BIT,
+ error_present ? DMA_CLEAR_LOG_BIT : 0);
+ if (rc < 0) {
+ pr_err("failed to write addr=0x%04x, rc=%d\n",
+ MEM_IF_DMA_CTL(chip), rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+int fg_clear_ima_errors_if_any(struct fg_chip *chip, bool check_hw_sts)
+{
+ int rc = 0;
+ u8 err_sts, exp_sts = 0, hw_sts = 0;
+ bool run_err_clr_seq = false;
+
+ rc = fg_read(chip, MEM_IF_IMA_EXP_STS(chip), &exp_sts, 1);
+ if (rc < 0) {
+ pr_err("failed to read ima_exp_sts rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_read(chip, MEM_IF_IMA_HW_STS(chip), &hw_sts, 1);
+ if (rc < 0) {
+ pr_err("failed to read ima_hw_sts rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_read(chip, MEM_IF_IMA_ERR_STS(chip), &err_sts, 1);
+ if (rc < 0) {
+ pr_err("failed to read ima_err_sts rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_dbg(chip, FG_SRAM_READ | FG_SRAM_WRITE, "ima_err_sts=%x ima_exp_sts=%x ima_hw_sts=%x\n",
+ err_sts, exp_sts, hw_sts);
+
+ if (check_hw_sts) {
+ /*
+ * Lower nibble should be equal to upper nibble before SRAM
+ * transactions begins from SW side. If they are unequal, then
+ * the error clear sequence should be run irrespective of IMA
+ * exception errors.
+ */
+ if ((hw_sts & 0x0F) != hw_sts >> 4) {
+ pr_err("IMA HW not in correct state, hw_sts=%x\n",
+ hw_sts);
+ run_err_clr_seq = true;
+ }
+ }
+
+ if (exp_sts & (IACS_ERR_BIT | XCT_TYPE_ERR_BIT | DATA_RD_ERR_BIT |
+ DATA_WR_ERR_BIT | ADDR_BURST_WRAP_BIT | ADDR_STABLE_ERR_BIT)) {
+ pr_err("IMA exception bit set, exp_sts=%x\n", exp_sts);
+ run_err_clr_seq = true;
+ }
+
+ if (run_err_clr_seq) {
+ /* clear the error */
+ rc = fg_run_iacs_clear_sequence(chip);
+ if (rc < 0) {
+ pr_err("failed to run iacs clear sequence rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Retry again as there was an error in the transaction */
+ return -EAGAIN;
+ }
+
+ return rc;
+}
+
+static int fg_check_iacs_ready(struct fg_chip *chip)
+{
+ int rc = 0, tries = 250;
+ u8 ima_opr_sts = 0;
+
+ /*
+ * Additional delay to make sure IACS ready bit is set after
+ * Read/Write operation.
+ */
+
+ usleep_range(30, 35);
+ while (1) {
+ rc = fg_read(chip, MEM_IF_IMA_OPR_STS(chip), &ima_opr_sts, 1);
+ if (rc < 0) {
+ pr_err("failed to read 0x%04x, rc=%d\n",
+ MEM_IF_IMA_OPR_STS(chip), rc);
+ return rc;
+ }
+
+ if (ima_opr_sts & IACS_RDY_BIT)
+ break;
+
+ if (!(--tries))
+ break;
+
+ /* delay for iacs_ready to be asserted */
+ usleep_range(5000, 7000);
+ }
+
+ if (!tries) {
+ pr_err("IACS_RDY not set, opr_sts: %d\n", ima_opr_sts);
+ /* check for error condition */
+ rc = fg_clear_ima_errors_if_any(chip, false);
+ if (rc < 0) {
+ if (rc != -EAGAIN)
+ pr_err("Failed to check for ima errors rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int __fg_interleaved_mem_write(struct fg_chip *chip, u16 address,
+ int offset, u8 *val, int len)
+{
+ int rc = 0, i;
+ u8 *ptr = val, byte_enable = 0, num_bytes = 0;
+
+ fg_dbg(chip, FG_SRAM_WRITE, "length %d addr=%02X offset=%d\n", len,
+ address, offset);
+
+ while (len > 0) {
+ num_bytes = (offset + len) > BYTES_PER_SRAM_WORD ?
+ (BYTES_PER_SRAM_WORD - offset) : len;
+
+ /* write to byte_enable */
+ for (i = offset; i < (offset + num_bytes); i++)
+ byte_enable |= BIT(i);
+
+ rc = fg_write(chip, MEM_IF_IMA_BYTE_EN(chip), &byte_enable, 1);
+ if (rc < 0) {
+ pr_err("Unable to write to byte_en_reg rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* write data */
+ rc = fg_write(chip, MEM_IF_WR_DATA0(chip) + offset, ptr,
+ num_bytes);
+ if (rc < 0) {
+ pr_err("failed to write to 0x%04x, rc=%d\n",
+ MEM_IF_WR_DATA0(chip) + offset, rc);
+ return rc;
+ }
+
+ /*
+ * The last-byte WR_DATA3 starts the write transaction.
+ * Write a dummy value to WR_DATA3 if it does not have
+ * valid data. This dummy data is not written to the
+ * SRAM as byte_en for WR_DATA3 is not set.
+ */
+ if (!(byte_enable & BIT(3))) {
+ u8 dummy_byte = 0x0;
+
+ rc = fg_write(chip, MEM_IF_WR_DATA3(chip), &dummy_byte,
+ 1);
+ if (rc < 0) {
+ pr_err("failed to write dummy-data to WR_DATA3 rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* check for error condition */
+ rc = fg_clear_ima_errors_if_any(chip, false);
+ if (rc < 0) {
+ if (rc == -EAGAIN)
+ pr_err("IMA error cleared, address [%d %d] len %d\n",
+ address, offset, len);
+ else
+ pr_err("Failed to check for ima errors rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ ptr += num_bytes;
+ len -= num_bytes;
+ offset = byte_enable = 0;
+
+ if (chip->use_ima_single_mode && len) {
+ address++;
+ rc = fg_set_address(chip, address);
+ if (rc < 0) {
+ pr_err("failed to set address rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ rc = fg_check_iacs_ready(chip);
+ if (rc < 0) {
+ pr_debug("IACS_RDY failed rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int __fg_interleaved_mem_read(struct fg_chip *chip, u16 address,
+ int offset, u8 *val, int len)
+{
+ int rc = 0, total_len;
+ u8 *rd_data = val, num_bytes;
+ char str[DEBUG_PRINT_BUFFER_SIZE];
+
+ fg_dbg(chip, FG_SRAM_READ, "length %d addr=%02X\n", len, address);
+
+ total_len = len;
+ while (len > 0) {
+ num_bytes = (offset + len) > BYTES_PER_SRAM_WORD ?
+ (BYTES_PER_SRAM_WORD - offset) : len;
+ rc = fg_read(chip, MEM_IF_RD_DATA0(chip) + offset, rd_data,
+ num_bytes);
+ if (rc < 0) {
+ pr_err("failed to read 0x%04x, rc=%d\n",
+ MEM_IF_RD_DATA0(chip) + offset, rc);
+ return rc;
+ }
+
+ rd_data += num_bytes;
+ len -= num_bytes;
+ offset = 0;
+
+ /* check for error condition */
+ rc = fg_clear_ima_errors_if_any(chip, false);
+ if (rc < 0) {
+ if (rc == -EAGAIN)
+ pr_err("IMA error cleared, address [%d %d] len %d\n",
+ address, offset, len);
+ else
+ pr_err("Failed to check for ima errors rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (chip->use_ima_single_mode) {
+ if (len) {
+ address++;
+ rc = fg_set_address(chip, address);
+ if (rc < 0) {
+ pr_err("failed to set address rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+ } else {
+ if (len && len < BYTES_PER_SRAM_WORD) {
+ /*
+ * Move to single mode. Changing address is not
+ * required here as it must be in burst mode.
+ * Address will get incremented internally by FG
+ * HW once the MSB of RD_DATA is read.
+ */
+ rc = fg_config_access_mode(chip, FG_READ,
+ false);
+ if (rc < 0) {
+ pr_err("failed to move to single mode rc=%d\n",
+ rc);
+ return -EIO;
+ }
+ }
+ }
+
+ rc = fg_check_iacs_ready(chip);
+ if (rc < 0) {
+ pr_debug("IACS_RDY failed rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (*chip->debug_mask & FG_SRAM_READ) {
+ fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, total_len);
+ pr_info("data read: %s\n", str);
+ }
+
+ return rc;
+}
+
+static int fg_get_mem_access_status(struct fg_chip *chip, bool *status)
+{
+ int rc;
+ u8 mem_if_sts;
+
+ rc = fg_read(chip, MEM_IF_MEM_INTF_CFG(chip), &mem_if_sts, 1);
+ if (rc < 0) {
+ pr_err("failed to read rif_mem status rc=%d\n", rc);
+ return rc;
+ }
+
+ *status = mem_if_sts & MEM_ACCESS_REQ_BIT;
+ return 0;
+}
+
+static bool is_mem_access_available(struct fg_chip *chip, int access)
+{
+ bool rif_mem_sts = true;
+ int rc, time_count = 0;
+
+ while (1) {
+ rc = fg_get_mem_access_status(chip, &rif_mem_sts);
+ if (rc < 0)
+ return rc;
+
+ /* This is an inverting logic */
+ if (!rif_mem_sts)
+ break;
+
+ fg_dbg(chip, FG_SRAM_READ | FG_SRAM_WRITE, "MEM_ACCESS_REQ is not clear yet for IMA_%s\n",
+ access ? "write" : "read");
+
+ /*
+ * Try this no more than 4 times. If MEM_ACCESS_REQ is not
+ * clear, then return an error instead of waiting for it again.
+ */
+ if (time_count > 4) {
+ pr_err("Tried 4 times(~16ms) polling MEM_ACCESS_REQ\n");
+ return false;
+ }
+
+ /* Wait for 4ms before reading MEM_ACCESS_REQ again */
+ usleep_range(4000, 4100);
+ time_count++;
+ }
+ return true;
+}
+
+static int fg_interleaved_mem_config(struct fg_chip *chip, u8 *val,
+ u16 address, int offset, int len, bool access)
+{
+ int rc = 0;
+ bool burst_mode = false;
+
+ if (!is_mem_access_available(chip, access))
+ return -EBUSY;
+
+ /* configure for IMA access */
+ rc = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+ MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT,
+ MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT);
+ if (rc < 0) {
+ pr_err("failed to set ima_req_access bit rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure for the read/write, single/burst mode */
+ burst_mode = chip->use_ima_single_mode ? false : ((offset + len) > 4);
+ rc = fg_config_access_mode(chip, access, burst_mode);
+ if (rc < 0) {
+ pr_err("failed to set memory access rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = fg_check_iacs_ready(chip);
+ if (rc < 0) {
+ pr_err_ratelimited("IACS_RDY failed rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_set_address(chip, address);
+ if (rc < 0) {
+ pr_err("failed to set address rc = %d\n", rc);
+ return rc;
+ }
+
+ if (access == FG_READ) {
+ rc = fg_check_iacs_ready(chip);
+ if (rc < 0) {
+ pr_debug("IACS_RDY failed rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int fg_get_beat_count(struct fg_chip *chip, u8 *count)
+{
+ int rc;
+
+ rc = fg_read(chip, MEM_IF_FG_BEAT_COUNT(chip), count, 1);
+ *count &= BEAT_COUNT_MASK;
+ return rc;
+}
+
+int fg_interleaved_mem_read(struct fg_chip *chip, u16 address, u8 offset,
+ u8 *val, int len)
+{
+ int rc = 0, ret;
+ u8 start_beat_count, end_beat_count, count = 0;
+ bool retry = false;
+
+ if (offset > 3) {
+ pr_err("offset too large %d\n", offset);
+ return -EINVAL;
+ }
+
+retry:
+ if (count >= RETRY_COUNT) {
+ pr_err("Tried %d times\n", RETRY_COUNT);
+ retry = false;
+ goto out;
+ }
+
+ rc = fg_interleaved_mem_config(chip, val, address, offset, len,
+ FG_READ);
+ if (rc < 0) {
+ pr_err("failed to configure SRAM for IMA rc = %d\n", rc);
+ count++;
+ retry = true;
+ goto out;
+ }
+
+ /* read the start beat count */
+ rc = fg_get_beat_count(chip, &start_beat_count);
+ if (rc < 0) {
+ pr_err("failed to read beat count rc=%d\n", rc);
+ count++;
+ retry = true;
+ goto out;
+ }
+
+ /* read data */
+ rc = __fg_interleaved_mem_read(chip, address, offset, val, len);
+ if (rc < 0) {
+ count++;
+ if (rc == -EAGAIN) {
+ pr_err("IMA read failed retry_count = %d\n", count);
+ goto retry;
+ }
+ pr_err("failed to read SRAM address rc = %d\n", rc);
+ retry = true;
+ goto out;
+ }
+
+ /* read the end beat count */
+ rc = fg_get_beat_count(chip, &end_beat_count);
+ if (rc < 0) {
+ pr_err("failed to read beat count rc=%d\n", rc);
+ count++;
+ retry = true;
+ goto out;
+ }
+
+ fg_dbg(chip, FG_SRAM_READ, "Start beat_count = %x End beat_count = %x\n",
+ start_beat_count, end_beat_count);
+
+ if (start_beat_count != end_beat_count) {
+ fg_dbg(chip, FG_SRAM_READ, "Beat count(%d/%d) do not match - retry transaction\n",
+ start_beat_count, end_beat_count);
+ count++;
+ retry = true;
+ }
+out:
+ /* Release IMA access */
+ ret = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+ MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT, 0);
+ if (rc < 0 && ret < 0) {
+ pr_err("failed to reset IMA access bit ret = %d\n", ret);
+ return ret;
+ }
+
+ if (retry) {
+ retry = false;
+ goto retry;
+ }
+
+ return rc;
+}
+
+int fg_interleaved_mem_write(struct fg_chip *chip, u16 address, u8 offset,
+ u8 *val, int len, bool atomic_access)
+{
+ int rc = 0, ret;
+ u8 start_beat_count, end_beat_count, count = 0;
+ bool retry = false;
+
+ if (offset > 3) {
+ pr_err("offset too large %d\n", offset);
+ return -EINVAL;
+ }
+
+retry:
+ if (count >= RETRY_COUNT) {
+ pr_err("Tried %d times\n", RETRY_COUNT);
+ retry = false;
+ goto out;
+ }
+
+ rc = fg_interleaved_mem_config(chip, val, address, offset, len,
+ FG_WRITE);
+ if (rc < 0) {
+ pr_err("failed to configure SRAM for IMA rc = %d\n", rc);
+ count++;
+ retry = true;
+ goto out;
+ }
+
+ /* read the start beat count */
+ rc = fg_get_beat_count(chip, &start_beat_count);
+ if (rc < 0) {
+ pr_err("failed to read beat count rc=%d\n", rc);
+ count++;
+ retry = true;
+ goto out;
+ }
+
+ /* write data */
+ rc = __fg_interleaved_mem_write(chip, address, offset, val, len);
+ if (rc < 0) {
+ count++;
+ if (rc == -EAGAIN) {
+ pr_err("IMA write failed retry_count = %d\n", count);
+ goto retry;
+ }
+ pr_err("failed to write SRAM address rc = %d\n", rc);
+ retry = true;
+ goto out;
+ }
+
+ /* read the end beat count */
+ rc = fg_get_beat_count(chip, &end_beat_count);
+ if (rc < 0) {
+ pr_err("failed to read beat count rc=%d\n", rc);
+ count++;
+ retry = true;
+ goto out;
+ }
+
+ if (atomic_access && start_beat_count != end_beat_count)
+ pr_err("Start beat_count = %x End beat_count = %x\n",
+ start_beat_count, end_beat_count);
+out:
+ /* Release IMA access */
+ ret = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+ MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT, 0);
+ if (rc < 0 && ret < 0) {
+ pr_err("failed to reset IMA access bit ret = %d\n", ret);
+ return ret;
+ }
+
+ if (retry) {
+ retry = false;
+ goto retry;
+ }
+
+ /* Return the error we got before releasing memory access */
+ return rc;
+}
+
+int fg_ima_init(struct fg_chip *chip)
+{
+ int rc;
+
+ /*
+ * 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.
+ */
+ rc = fg_masked_write(chip, MEM_IF_IMA_CFG(chip), IACS_INTR_SRC_SLCT_BIT,
+ IACS_INTR_SRC_SLCT_BIT);
+ if (rc < 0) {
+ pr_err("failed to configure interrupt source %d\n", rc);
+ return rc;
+ }
+
+ /* Clear DMA errors if any before clearing IMA errors */
+ rc = fg_clear_dma_errors_if_any(chip);
+ if (rc < 0) {
+ pr_err("Error in checking DMA errors rc:%d\n", rc);
+ return rc;
+ }
+
+ /* Clear IMA errors if any before SRAM transactions can begin */
+ rc = fg_clear_ima_errors_if_any(chip, true);
+ if (rc < 0 && rc != -EAGAIN) {
+ pr_err("Error in checking IMA errors rc:%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/drivers/power/supply/qcom/fg-reg.h b/drivers/power/supply/qcom/fg-reg.h
new file mode 100644
index 000000000000..cd0b2fb4391f
--- /dev/null
+++ b/drivers/power/supply/qcom/fg-reg.h
@@ -0,0 +1,329 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 __FG_REG_H__
+#define __FG_REG_H__
+
+/* FG_ADC_RR register definitions used only for READ */
+#define ADC_RR_FAKE_BATT_LOW_LSB(chip) (chip->rradc_base + 0x58)
+#define ADC_RR_FAKE_BATT_HIGH_LSB(chip) (chip->rradc_base + 0x5A)
+
+/* FG_BATT_SOC register definitions */
+#define BATT_SOC_FG_ALG_STS(chip) (chip->batt_soc_base + 0x06)
+#define BATT_SOC_FG_ALG_AUX_STS0(chip) (chip->batt_soc_base + 0x07)
+#define BATT_SOC_SLEEP_SHUTDOWN_STS(chip) (chip->batt_soc_base + 0x08)
+#define BATT_SOC_FG_MONOTONIC_SOC(chip) (chip->batt_soc_base + 0x09)
+#define BATT_SOC_FG_MONOTONIC_SOC_CP(chip) (chip->batt_soc_base + 0x0A)
+#define BATT_SOC_INT_RT_STS(chip) (chip->batt_soc_base + 0x10)
+#define BATT_SOC_EN_CTL(chip) (chip->batt_soc_base + 0x46)
+#define BATT_SOC_RESTART(chip) (chip->batt_soc_base + 0x48)
+#define BATT_SOC_STS_CLR(chip) (chip->batt_soc_base + 0x4A)
+#define BATT_SOC_LOW_PWR_CFG(chip) (chip->batt_soc_base + 0x52)
+#define BATT_SOC_LOW_PWR_STS(chip) (chip->batt_soc_base + 0x56)
+
+/* BATT_SOC_INT_RT_STS */
+#define MSOC_EMPTY_BIT BIT(5)
+
+/* BATT_SOC_EN_CTL */
+#define FG_ALGORITHM_EN_BIT BIT(7)
+
+/* BATT_SOC_RESTART */
+#define RESTART_GO_BIT BIT(0)
+
+/* FG_BATT_INFO register definitions */
+#define BATT_INFO_BATT_TEMP_STS(chip) (chip->batt_info_base + 0x06)
+#define BATT_INFO_SYS_BATT(chip) (chip->batt_info_base + 0x07)
+#define BATT_INFO_FG_STS(chip) (chip->batt_info_base + 0x09)
+#define BATT_INFO_INT_RT_STS(chip) (chip->batt_info_base + 0x10)
+#define BATT_INFO_BATT_REM_LATCH(chip) (chip->batt_info_base + 0x4F)
+#define BATT_INFO_BATT_TEMP_LSB(chip) (chip->batt_info_base + 0x50)
+#define BATT_INFO_BATT_TEMP_MSB(chip) (chip->batt_info_base + 0x51)
+#define BATT_INFO_BATT_TEMP_CFG(chip) (chip->batt_info_base + 0x56)
+#define BATT_INFO_BATT_TMPR_INTR(chip) (chip->batt_info_base + 0x59)
+#define BATT_INFO_THERM_C1(chip) (chip->batt_info_base + 0x5C)
+#define BATT_INFO_THERM_C2(chip) (chip->batt_info_base + 0x5D)
+#define BATT_INFO_THERM_C3(chip) (chip->batt_info_base + 0x5E)
+#define BATT_INFO_THERM_HALF_RANGE(chip) (chip->batt_info_base + 0x5F)
+#define BATT_INFO_JEITA_CTLS(chip) (chip->batt_info_base + 0x61)
+#define BATT_INFO_JEITA_TOO_COLD(chip) (chip->batt_info_base + 0x62)
+#define BATT_INFO_JEITA_COLD(chip) (chip->batt_info_base + 0x63)
+#define BATT_INFO_JEITA_HOT(chip) (chip->batt_info_base + 0x64)
+#define BATT_INFO_JEITA_TOO_HOT(chip) (chip->batt_info_base + 0x65)
+
+/* only for v1.1 */
+#define BATT_INFO_ESR_CFG(chip) (chip->batt_info_base + 0x69)
+/* starting from v2.0 */
+#define BATT_INFO_ESR_GENERAL_CFG(chip) (chip->batt_info_base + 0x68)
+#define BATT_INFO_ESR_PULL_DN_CFG(chip) (chip->batt_info_base + 0x69)
+#define BATT_INFO_ESR_FAST_CRG_CFG(chip) (chip->batt_info_base + 0x6A)
+
+#define BATT_INFO_BATT_MISS_CFG(chip) (chip->batt_info_base + 0x6B)
+#define BATT_INFO_WATCHDOG_COUNT(chip) (chip->batt_info_base + 0x70)
+#define BATT_INFO_WATCHDOG_CFG(chip) (chip->batt_info_base + 0x71)
+#define BATT_INFO_IBATT_SENSING_CFG(chip) (chip->batt_info_base + 0x73)
+#define BATT_INFO_QNOVO_CFG(chip) (chip->batt_info_base + 0x74)
+#define BATT_INFO_QNOVO_SCALER(chip) (chip->batt_info_base + 0x75)
+
+/* starting from v2.0 */
+#define BATT_INFO_CRG_SERVICES(chip) (chip->batt_info_base + 0x90)
+
+/* Following LSB/MSB address are for v2.0 and above; v1.1 have them swapped */
+#define BATT_INFO_VBATT_LSB(chip) (chip->batt_info_base + 0xA0)
+#define BATT_INFO_VBATT_MSB(chip) (chip->batt_info_base + 0xA1)
+#define BATT_INFO_IBATT_LSB(chip) (chip->batt_info_base + 0xA2)
+#define BATT_INFO_IBATT_MSB(chip) (chip->batt_info_base + 0xA3)
+#define BATT_INFO_ESR_LSB(chip) (chip->batt_info_base + 0xA4)
+#define BATT_INFO_ESR_MSB(chip) (chip->batt_info_base + 0xA5)
+#define BATT_INFO_VBATT_LSB_CP(chip) (chip->batt_info_base + 0xA6)
+#define BATT_INFO_VBATT_MSB_CP(chip) (chip->batt_info_base + 0xA7)
+#define BATT_INFO_IBATT_LSB_CP(chip) (chip->batt_info_base + 0xA8)
+#define BATT_INFO_IBATT_MSB_CP(chip) (chip->batt_info_base + 0xA9)
+#define BATT_INFO_ESR_LSB_CP(chip) (chip->batt_info_base + 0xAA)
+#define BATT_INFO_ESR_MSB_CP(chip) (chip->batt_info_base + 0xAB)
+#define BATT_INFO_VADC_LSB(chip) (chip->batt_info_base + 0xAC)
+#define BATT_INFO_VADC_MSB(chip) (chip->batt_info_base + 0xAD)
+#define BATT_INFO_IADC_LSB(chip) (chip->batt_info_base + 0xAE)
+#define BATT_INFO_IADC_MSB(chip) (chip->batt_info_base + 0xAF)
+#define BATT_INFO_TM_MISC(chip) (chip->batt_info_base + 0xE5)
+#define BATT_INFO_TM_MISC1(chip) (chip->batt_info_base + 0xE6)
+
+/* BATT_INFO_BATT_TEMP_STS */
+#define JEITA_TOO_HOT_STS_BIT BIT(7)
+#define JEITA_HOT_STS_BIT BIT(6)
+#define JEITA_COLD_STS_BIT BIT(5)
+#define JEITA_TOO_COLD_STS_BIT BIT(4)
+#define BATT_TEMP_DELTA_BIT BIT(1)
+#define BATT_TEMP_AVAIL_BIT BIT(0)
+
+/* BATT_INFO_SYS_BATT */
+#define BATT_REM_LATCH_STS_BIT BIT(4)
+#define BATT_MISSING_HW_BIT BIT(2)
+#define BATT_MISSING_ALG_BIT BIT(1)
+#define BATT_MISSING_CMP_BIT BIT(0)
+
+/* BATT_INFO_FG_STS */
+#define FG_WD_RESET_BIT BIT(7)
+/* This bit is not present in v1.1 */
+#define FG_CRG_TRM_BIT BIT(0)
+
+/* BATT_INFO_INT_RT_STS */
+#define BT_TMPR_DELTA_BIT BIT(6)
+#define WDOG_EXP_BIT BIT(5)
+#define BT_ATTN_BIT BIT(4)
+#define BT_MISS_BIT BIT(3)
+#define ESR_DELTA_BIT BIT(2)
+#define VBT_LOW_BIT BIT(1)
+#define VBT_PRD_DELTA_BIT BIT(0)
+
+/* BATT_INFO_INT_RT_STS */
+#define BATT_REM_LATCH_CLR_BIT BIT(7)
+
+/* BATT_INFO_BATT_TEMP_LSB/MSB */
+#define BATT_TEMP_LSB_MASK GENMASK(7, 0)
+#define BATT_TEMP_MSB_MASK GENMASK(2, 0)
+
+/* BATT_INFO_BATT_TEMP_CFG */
+#define JEITA_TEMP_HYST_MASK GENMASK(5, 4)
+#define JEITA_TEMP_HYST_SHIFT 4
+#define JEITA_TEMP_NO_HYST 0x0
+#define JEITA_TEMP_HYST_1C 0x1
+#define JEITA_TEMP_HYST_2C 0x2
+#define JEITA_TEMP_HYST_3C 0x3
+
+/* BATT_INFO_BATT_TMPR_INTR */
+#define CHANGE_THOLD_MASK GENMASK(1, 0)
+#define BTEMP_DELTA_2K 0x0
+#define BTEMP_DELTA_4K 0x1
+#define BTEMP_DELTA_6K 0x2
+#define BTEMP_DELTA_10K 0x3
+
+/* BATT_INFO_THERM_C1/C2/C3 */
+#define BATT_INFO_THERM_COEFF_MASK GENMASK(7, 0)
+
+/* BATT_INFO_THERM_HALF_RANGE */
+#define BATT_INFO_THERM_TEMP_MASK GENMASK(7, 0)
+
+/* BATT_INFO_JEITA_CTLS */
+#define JEITA_STS_CLEAR_BIT BIT(0)
+
+/* BATT_INFO_JEITA_TOO_COLD/COLD/HOT/TOO_HOT */
+#define JEITA_THOLD_MASK GENMASK(7, 0)
+
+/* BATT_INFO_ESR_CFG */
+#define CFG_ACTIVE_PD_MASK GENMASK(2, 1)
+#define CFG_FCC_DEC_MASK GENMASK(4, 3)
+
+/* BATT_INFO_ESR_GENERAL_CFG */
+#define ESR_DEEP_TAPER_EN_BIT BIT(0)
+
+/* BATT_INFO_ESR_PULL_DN_CFG */
+#define ESR_PULL_DOWN_IVAL_MASK GENMASK(3, 2)
+#define ESR_PULL_DOWN_IVAL_SHIFT 2
+#define ESR_MEAS_CUR_60MA 0x0
+#define ESR_MEAS_CUR_120MA 0x1
+#define ESR_MEAS_CUR_180MA 0x2
+#define ESR_MEAS_CUR_240MA 0x3
+#define ESR_PULL_DOWN_MODE_MASK GENMASK(1, 0)
+#define ESR_NO_PULL_DOWN 0x0
+#define ESR_STATIC_PULL_DOWN 0x1
+#define ESR_CRG_DSC_PULL_DOWN 0x2
+#define ESR_DSC_PULL_DOWN 0x3
+
+/* BATT_INFO_ESR_FAST_CRG_CFG */
+#define ESR_FAST_CRG_IVAL_MASK GENMASK(3, 1)
+#define ESR_FCC_300MA 0x0
+#define ESR_FCC_600MA 0x1
+#define ESR_FCC_1A 0x2
+#define ESR_FCC_2A 0x3
+#define ESR_FCC_3A 0x4
+#define ESR_FCC_4A 0x5
+#define ESR_FCC_5A 0x6
+#define ESR_FCC_6A 0x7
+#define ESR_FAST_CRG_CTL_EN_BIT BIT(0)
+
+/* BATT_INFO_BATT_MISS_CFG */
+#define BM_THERM_TH_MASK GENMASK(5, 4)
+#define RES_TH_0P75_MOHM 0x0
+#define RES_TH_1P00_MOHM 0x1
+#define RES_TH_1P50_MOHM 0x2
+#define RES_TH_3P00_MOHM 0x3
+#define BM_BATT_ID_TH_MASK GENMASK(3, 2)
+#define BM_FROM_THERM_BIT BIT(1)
+#define BM_FROM_BATT_ID_BIT BIT(0)
+
+/* BATT_INFO_WATCHDOG_COUNT */
+#define WATCHDOG_COUNTER GENMASK(7, 0)
+
+/* BATT_INFO_WATCHDOG_CFG */
+#define RESET_CAPABLE_BIT BIT(2)
+#define PET_CTRL_BIT BIT(1)
+#define ENABLE_CTRL_BIT BIT(0)
+
+/* BATT_INFO_IBATT_SENSING_CFG */
+#define ADC_BITSTREAM_INV_BIT BIT(4)
+#define SOURCE_SELECT_MASK GENMASK(1, 0)
+#define SRC_SEL_BATFET 0x0
+#define SRC_SEL_BATFET_SMB 0x2
+#define SRC_SEL_RESERVED 0x3
+
+/* BATT_INFO_QNOVO_CFG */
+#define LD_REG_FORCE_CTL_BIT BIT(2)
+#define LD_REG_CTRL_FORCE_HIGH LD_REG_FORCE_CTL_BIT
+#define LD_REG_CTRL_FORCE_LOW 0
+#define LD_REG_CTRL_BIT BIT(1)
+#define LD_REG_CTRL_REGISTER LD_REG_CTRL_BIT
+#define LD_REG_CTRL_LOGIC 0
+#define BIT_STREAM_CFG_BIT BIT(0)
+
+/* BATT_INFO_QNOVO_SCALER */
+#define QNOVO_SCALER_MASK GENMASK(7, 0)
+
+/* BATT_INFO_CRG_SERVICES */
+#define FG_CRC_TRM_EN_BIT BIT(0)
+
+/* BATT_INFO_VBATT_LSB/MSB */
+#define VBATT_MASK GENMASK(7, 0)
+
+/* BATT_INFO_IBATT_LSB/MSB */
+#define IBATT_MASK GENMASK(7, 0)
+
+/* BATT_INFO_ESR_LSB/MSB */
+#define ESR_LSB_MASK GENMASK(7, 0)
+#define ESR_MSB_MASK GENMASK(5, 0)
+
+/* BATT_INFO_VADC_LSB/MSB */
+#define VADC_LSB_MASK GENMASK(7, 0)
+#define VADC_MSB_MASK GENMASK(6, 0)
+
+/* BATT_INFO_IADC_LSB/MSB */
+#define IADC_LSB_MASK GENMASK(7, 0)
+#define IADC_MSB_MASK GENMASK(6, 0)
+
+/* BATT_INFO_TM_MISC */
+#define FORCE_SEQ_RESP_TOGGLE_BIT BIT(6)
+#define ALG_DIRECT_VALID_DATA_BIT BIT(5)
+#define ALG_DIRECT_MODE_EN_BIT BIT(4)
+#define BATT_VADC_CONV_BIT BIT(3)
+#define BATT_IADC_CONV_BIT BIT(2)
+#define ADC_ENABLE_REG_CTRL_BIT BIT(1)
+#define WDOG_FORCE_EXP_BIT BIT(0)
+/* only for v1.1 */
+#define ESR_PULSE_FORCE_CTRL_BIT BIT(7)
+
+/* BATT_INFO_TM_MISC1 */
+/* for v2.0 and above */
+#define ESR_REQ_CTL_BIT BIT(1)
+#define ESR_REQ_CTL_EN_BIT BIT(0)
+
+/* FG_MEM_IF register and bit definitions */
+#define MEM_IF_INT_RT_STS(chip) ((chip->mem_if_base) + 0x10)
+#define MEM_IF_MEM_INTF_CFG(chip) ((chip->mem_if_base) + 0x50)
+#define MEM_IF_IMA_CTL(chip) ((chip->mem_if_base) + 0x51)
+#define MEM_IF_IMA_CFG(chip) ((chip->mem_if_base) + 0x52)
+#define MEM_IF_IMA_OPR_STS(chip) ((chip->mem_if_base) + 0x54)
+#define MEM_IF_IMA_EXP_STS(chip) ((chip->mem_if_base) + 0x55)
+#define MEM_IF_IMA_HW_STS(chip) ((chip->mem_if_base) + 0x56)
+#define MEM_IF_FG_BEAT_COUNT(chip) ((chip->mem_if_base) + 0x57)
+#define MEM_IF_IMA_ERR_STS(chip) ((chip->mem_if_base) + 0x5F)
+#define MEM_IF_IMA_BYTE_EN(chip) ((chip->mem_if_base) + 0x60)
+#define MEM_IF_ADDR_LSB(chip) ((chip->mem_if_base) + 0x61)
+#define MEM_IF_ADDR_MSB(chip) ((chip->mem_if_base) + 0x62)
+#define MEM_IF_WR_DATA0(chip) ((chip->mem_if_base) + 0x63)
+#define MEM_IF_WR_DATA3(chip) ((chip->mem_if_base) + 0x66)
+#define MEM_IF_RD_DATA0(chip) ((chip->mem_if_base) + 0x67)
+#define MEM_IF_RD_DATA3(chip) ((chip->mem_if_base) + 0x6A)
+#define MEM_IF_DMA_STS(chip) ((chip->mem_if_base) + 0x70)
+#define MEM_IF_DMA_CTL(chip) ((chip->mem_if_base) + 0x71)
+
+/* MEM_IF_INT_RT_STS */
+#define MEM_XCP_BIT BIT(1)
+
+/* MEM_IF_MEM_INTF_CFG */
+#define MEM_ACCESS_REQ_BIT BIT(7)
+#define IACS_SLCT_BIT BIT(5)
+
+/* MEM_IF_IMA_CTL */
+#define MEM_ACS_BURST_BIT BIT(7)
+#define IMA_WR_EN_BIT BIT(6)
+#define IMA_CTL_MASK GENMASK(7, 6)
+
+/* MEM_IF_IMA_CFG */
+#define IACS_CLR_BIT BIT(2)
+#define IACS_INTR_SRC_SLCT_BIT BIT(3)
+#define STATIC_CLK_EN_BIT BIT(4)
+
+/* MEM_IF_IMA_OPR_STS */
+#define IACS_RDY_BIT BIT(1)
+
+/* MEM_IF_IMA_EXP_STS */
+#define IACS_ERR_BIT BIT(0)
+#define XCT_TYPE_ERR_BIT BIT(1)
+#define DATA_RD_ERR_BIT BIT(3)
+#define DATA_WR_ERR_BIT BIT(4)
+#define ADDR_BURST_WRAP_BIT BIT(5)
+#define ADDR_STABLE_ERR_BIT BIT(7)
+
+/* MEM_IF_IMA_ERR_STS */
+#define ADDR_STBL_ERR_BIT BIT(7)
+#define WR_ACS_ERR_BIT BIT(6)
+#define RD_ACS_ERR_BIT BIT(5)
+
+/* MEM_IF_FG_BEAT_COUNT */
+#define BEAT_COUNT_MASK GENMASK(3, 0)
+
+/* MEM_IF_DMA_STS */
+#define DMA_WRITE_ERROR_BIT BIT(1)
+#define DMA_READ_ERROR_BIT BIT(2)
+
+/* MEM_IF_DMA_CTL */
+#define DMA_CLEAR_LOG_BIT BIT(0)
+#endif
diff --git a/drivers/power/supply/qcom/fg-util.c b/drivers/power/supply/qcom/fg-util.c
new file mode 100644
index 000000000000..0cb1dea7113b
--- /dev/null
+++ b/drivers/power/supply/qcom/fg-util.c
@@ -0,0 +1,960 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/sort.h>
+#include "fg-core.h"
+
+void fg_circ_buf_add(struct fg_circ_buf *buf, int val)
+{
+ buf->arr[buf->head] = val;
+ buf->head = (buf->head + 1) % ARRAY_SIZE(buf->arr);
+ buf->size = min(++buf->size, (int)ARRAY_SIZE(buf->arr));
+}
+
+void fg_circ_buf_clr(struct fg_circ_buf *buf)
+{
+ memset(buf, 0, sizeof(*buf));
+}
+
+int fg_circ_buf_avg(struct fg_circ_buf *buf, int *avg)
+{
+ s64 result = 0;
+ int i;
+
+ if (buf->size == 0)
+ return -ENODATA;
+
+ for (i = 0; i < buf->size; i++)
+ result += buf->arr[i];
+
+ *avg = div_s64(result, buf->size);
+ return 0;
+}
+
+static int cmp_int(const void *a, const void *b)
+{
+ return *(int *)a - *(int *)b;
+}
+
+int fg_circ_buf_median(struct fg_circ_buf *buf, int *median)
+{
+ int *temp;
+
+ if (buf->size == 0)
+ return -ENODATA;
+
+ if (buf->size == 1) {
+ *median = buf->arr[0];
+ return 0;
+ }
+
+ temp = kmalloc_array(buf->size, sizeof(*temp), GFP_KERNEL);
+ if (!temp)
+ return -ENOMEM;
+
+ memcpy(temp, buf->arr, buf->size * sizeof(*temp));
+ sort(temp, buf->size, sizeof(*temp), cmp_int, NULL);
+
+ if (buf->size % 2)
+ *median = temp[buf->size / 2];
+ else
+ *median = (temp[buf->size / 2 - 1] + temp[buf->size / 2]) / 2;
+
+ kfree(temp);
+ return 0;
+}
+
+int fg_lerp(const struct fg_pt *pts, size_t tablesize, s32 input, s32 *output)
+{
+ int i;
+ s64 temp;
+
+ if (pts == NULL) {
+ pr_err("Table is NULL\n");
+ return -EINVAL;
+ }
+
+ if (tablesize < 1) {
+ pr_err("Table has no entries\n");
+ return -ENOENT;
+ }
+
+ if (tablesize == 1) {
+ *output = pts[0].y;
+ return 0;
+ }
+
+ if (pts[0].x > pts[1].x) {
+ pr_err("Table is not in acending order\n");
+ return -EINVAL;
+ }
+
+ if (input <= pts[0].x) {
+ *output = pts[0].y;
+ return 0;
+ }
+
+ if (input >= pts[tablesize - 1].x) {
+ *output = pts[tablesize - 1].y;
+ return 0;
+ }
+
+ for (i = 1; i < tablesize; i++) {
+ if (input >= pts[i].x)
+ continue;
+
+ temp = (s64)(pts[i].y - pts[i - 1].y) *
+ (s64)(input - pts[i - 1].x);
+ temp = div_s64(temp, pts[i].x - pts[i - 1].x);
+ *output = temp + pts[i - 1].y;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static struct fg_dbgfs dbgfs_data = {
+ .help_msg = {
+ .data =
+ "FG Debug-FS support\n"
+ "\n"
+ "Hierarchy schema:\n"
+ "/sys/kernel/debug/fg_sram\n"
+ " /help -- Static help text\n"
+ " /address -- Starting register address for reads or writes\n"
+ " /count -- Number of registers to read (only used for reads)\n"
+ " /data -- Initiates the SRAM read (formatted output)\n"
+ "\n",
+ },
+};
+
+static bool is_usb_present(struct fg_chip *chip)
+{
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ if (!chip->usb_psy)
+ chip->usb_psy = power_supply_get_by_name("usb");
+
+ if (!chip->usb_psy)
+ return false;
+
+ rc = power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT, &pval);
+ if (rc < 0)
+ return false;
+
+ return pval.intval != 0;
+}
+
+static bool is_dc_present(struct fg_chip *chip)
+{
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ if (!chip->dc_psy)
+ chip->dc_psy = power_supply_get_by_name("dc");
+
+ if (!chip->dc_psy)
+ return false;
+
+ rc = power_supply_get_property(chip->dc_psy,
+ POWER_SUPPLY_PROP_PRESENT, &pval);
+ if (rc < 0)
+ return false;
+
+ return pval.intval != 0;
+}
+
+bool is_input_present(struct fg_chip *chip)
+{
+ return is_usb_present(chip) || is_dc_present(chip);
+}
+
+bool is_qnovo_en(struct fg_chip *chip)
+{
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ if (!chip->batt_psy)
+ chip->batt_psy = power_supply_get_by_name("battery");
+
+ if (!chip->batt_psy)
+ return false;
+
+ rc = power_supply_get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE, &pval);
+ if (rc < 0)
+ return false;
+
+ return pval.intval != 0;
+}
+
+#define EXPONENT_SHIFT 11
+#define EXPONENT_OFFSET -9
+#define MANTISSA_SIGN_BIT 10
+#define MICRO_UNIT 1000000
+s64 fg_float_decode(u16 val)
+{
+ s8 exponent;
+ s32 mantissa;
+
+ /* mantissa bits are shifted out during sign extension */
+ exponent = ((s16)val >> EXPONENT_SHIFT) + EXPONENT_OFFSET;
+ /* exponent bits are shifted out during sign extension */
+ mantissa = sign_extend32(val, MANTISSA_SIGN_BIT) * MICRO_UNIT;
+
+ if (exponent < 0)
+ return (s64)mantissa >> -exponent;
+
+ return (s64)mantissa << exponent;
+}
+
+void fill_string(char *str, size_t str_len, u8 *buf, int buf_len)
+{
+ int pos = 0;
+ int i;
+
+ for (i = 0; i < buf_len; i++) {
+ pos += scnprintf(str + pos, str_len - pos, "%02x", buf[i]);
+ if (i < buf_len - 1)
+ pos += scnprintf(str + pos, str_len - pos, " ");
+ }
+}
+
+void dump_sram(u8 *buf, int addr, int len)
+{
+ int i;
+ char str[16];
+
+ /*
+ * Length passed should be in multiple of 4 as each FG SRAM word
+ * holds 4 bytes. To keep this simple, even if a length which is
+ * not a multiple of 4 bytes or less than 4 bytes is passed, SRAM
+ * registers dumped will be always in multiple of 4 bytes.
+ */
+ for (i = 0; i < len; i += 4) {
+ str[0] = '\0';
+ fill_string(str, sizeof(str), buf + i, 4);
+ pr_info("%03d %s\n", addr + (i / 4), str);
+ }
+}
+
+static inline bool fg_sram_address_valid(u16 address, int len)
+{
+ if (address > FG_SRAM_ADDRESS_MAX)
+ return false;
+
+ if ((address + DIV_ROUND_UP(len, 4)) > FG_SRAM_ADDRESS_MAX + 1)
+ return false;
+
+ return true;
+}
+
+#define SOC_UPDATE_WAIT_MS 1500
+int fg_sram_write(struct fg_chip *chip, u16 address, u8 offset,
+ u8 *val, int len, int flags)
+{
+ int rc = 0;
+ bool tried_again = false;
+ bool atomic_access = false;
+
+ if (!chip)
+ return -ENXIO;
+
+ if (chip->battery_missing)
+ return -ENODATA;
+
+ if (!fg_sram_address_valid(address, len))
+ return -EFAULT;
+
+ if (!(flags & FG_IMA_NO_WLOCK))
+ vote(chip->awake_votable, SRAM_WRITE, true, 0);
+ mutex_lock(&chip->sram_rw_lock);
+
+ if ((flags & FG_IMA_ATOMIC) && chip->irqs[SOC_UPDATE_IRQ].irq) {
+ /*
+ * This interrupt need to be enabled only when it is
+ * required. It will be kept disabled other times.
+ */
+ reinit_completion(&chip->soc_update);
+ enable_irq(chip->irqs[SOC_UPDATE_IRQ].irq);
+ atomic_access = true;
+ } else {
+ flags = FG_IMA_DEFAULT;
+ }
+wait:
+ /*
+ * Atomic access mean waiting upon SOC_UPDATE interrupt from
+ * FG_ALG and do the transaction after that. This is to make
+ * sure that there will be no SOC update happening when an
+ * IMA write is happening. SOC_UPDATE interrupt fires every
+ * FG cycle (~1.47 seconds).
+ */
+ if (atomic_access) {
+ /* Wait for SOC_UPDATE completion */
+ rc = wait_for_completion_interruptible_timeout(
+ &chip->soc_update,
+ msecs_to_jiffies(SOC_UPDATE_WAIT_MS));
+
+ /* If we were interrupted wait again one more time. */
+ if (rc == -ERESTARTSYS && !tried_again) {
+ tried_again = true;
+ goto wait;
+ } else if (rc <= 0) {
+ pr_err("wait for soc_update timed out rc=%d\n", rc);
+ goto out;
+ }
+ }
+
+ rc = fg_interleaved_mem_write(chip, address, offset, val, len,
+ atomic_access);
+ if (rc < 0)
+ pr_err("Error in writing SRAM address 0x%x[%d], rc=%d\n",
+ address, offset, rc);
+out:
+ if (atomic_access)
+ disable_irq_nosync(chip->irqs[SOC_UPDATE_IRQ].irq);
+
+ mutex_unlock(&chip->sram_rw_lock);
+ if (!(flags & FG_IMA_NO_WLOCK))
+ vote(chip->awake_votable, SRAM_WRITE, false, 0);
+ return rc;
+}
+
+int fg_sram_read(struct fg_chip *chip, u16 address, u8 offset,
+ u8 *val, int len, int flags)
+{
+ int rc = 0;
+
+ if (!chip)
+ return -ENXIO;
+
+ if (chip->battery_missing)
+ return -ENODATA;
+
+ if (!fg_sram_address_valid(address, len))
+ return -EFAULT;
+
+ if (!(flags & FG_IMA_NO_WLOCK))
+ vote(chip->awake_votable, SRAM_READ, true, 0);
+ mutex_lock(&chip->sram_rw_lock);
+
+ rc = fg_interleaved_mem_read(chip, address, offset, val, len);
+ if (rc < 0)
+ pr_err("Error in reading SRAM address 0x%x[%d], rc=%d\n",
+ address, offset, rc);
+
+ mutex_unlock(&chip->sram_rw_lock);
+ if (!(flags & FG_IMA_NO_WLOCK))
+ vote(chip->awake_votable, SRAM_READ, false, 0);
+ return rc;
+}
+
+int fg_sram_masked_write(struct fg_chip *chip, u16 address, u8 offset,
+ u8 mask, u8 val, int flags)
+{
+ int rc = 0;
+ u8 buf[4];
+
+ rc = fg_sram_read(chip, address, 0, buf, 4, flags);
+ if (rc < 0) {
+ pr_err("sram read failed: address=%03X, rc=%d\n", address, rc);
+ return rc;
+ }
+
+ buf[offset] &= ~mask;
+ buf[offset] |= val & mask;
+
+ rc = fg_sram_write(chip, address, 0, buf, 4, flags);
+ if (rc < 0) {
+ pr_err("sram write failed: address=%03X, rc=%d\n", address, rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+int fg_read(struct fg_chip *chip, int addr, u8 *val, int len)
+{
+ int rc, i;
+
+ if (!chip || !chip->regmap)
+ return -ENXIO;
+
+ rc = regmap_bulk_read(chip->regmap, addr, val, len);
+
+ if (rc < 0) {
+ dev_err(chip->dev, "regmap_read failed for address %04x rc=%d\n",
+ addr, rc);
+ return rc;
+ }
+
+ if (*chip->debug_mask & FG_BUS_READ) {
+ pr_info("length %d addr=%04x\n", len, addr);
+ for (i = 0; i < len; i++)
+ pr_info("val[%d]: %02x\n", i, val[i]);
+ }
+
+ return 0;
+}
+
+int fg_write(struct fg_chip *chip, int addr, u8 *val, int len)
+{
+ int rc, i;
+ bool sec_access = false;
+
+ if (!chip || !chip->regmap)
+ return -ENXIO;
+
+ mutex_lock(&chip->bus_lock);
+ sec_access = (addr & 0x00FF) > 0xD0;
+ if (sec_access) {
+ rc = regmap_write(chip->regmap, (addr & 0xFF00) | 0xD0, 0xA5);
+ if (rc < 0) {
+ dev_err(chip->dev, "regmap_write failed for address %x rc=%d\n",
+ addr, rc);
+ goto out;
+ }
+ }
+
+ if (len > 1)
+ rc = regmap_bulk_write(chip->regmap, addr, val, len);
+ else
+ rc = regmap_write(chip->regmap, addr, *val);
+
+ if (rc < 0) {
+ dev_err(chip->dev, "regmap_write failed for address %04x rc=%d\n",
+ addr, rc);
+ goto out;
+ }
+
+ if (*chip->debug_mask & FG_BUS_WRITE) {
+ pr_info("length %d addr=%04x\n", len, addr);
+ for (i = 0; i < len; i++)
+ pr_info("val[%d]: %02x\n", i, val[i]);
+ }
+out:
+ mutex_unlock(&chip->bus_lock);
+ return rc;
+}
+
+int fg_masked_write(struct fg_chip *chip, int addr, u8 mask, u8 val)
+{
+ int rc;
+ bool sec_access = false;
+
+ if (!chip || !chip->regmap)
+ return -ENXIO;
+
+ mutex_lock(&chip->bus_lock);
+ sec_access = (addr & 0x00FF) > 0xD0;
+ if (sec_access) {
+ rc = regmap_write(chip->regmap, (addr & 0xFF00) | 0xD0, 0xA5);
+ if (rc < 0) {
+ dev_err(chip->dev, "regmap_write failed for address %x rc=%d\n",
+ addr, rc);
+ goto out;
+ }
+ }
+
+ rc = regmap_update_bits(chip->regmap, addr, mask, val);
+ if (rc < 0) {
+ dev_err(chip->dev, "regmap_update_bits failed for address %04x rc=%d\n",
+ addr, rc);
+ goto out;
+ }
+
+ fg_dbg(chip, FG_BUS_WRITE, "addr=%04x mask: %02x val: %02x\n", addr,
+ mask, val);
+out:
+ mutex_unlock(&chip->bus_lock);
+ return rc;
+}
+
+int64_t twos_compliment_extend(int64_t val, int sign_bit_pos)
+{
+ int i, nbytes = DIV_ROUND_UP(sign_bit_pos, 8);
+ int64_t mask, val_out;
+
+ val_out = val;
+ mask = 1 << sign_bit_pos;
+ if (val & mask) {
+ for (i = 8; i > nbytes; i--) {
+ mask = 0xFFLL << ((i - 1) * 8);
+ val_out |= mask;
+ }
+
+ if ((nbytes * 8) - 1 > sign_bit_pos) {
+ mask = 1 << sign_bit_pos;
+ for (i = 1; i <= (nbytes * 8) - sign_bit_pos; i++)
+ val_out |= mask << i;
+ }
+ }
+
+ pr_debug("nbytes: %d val: %llx val_out: %llx\n", nbytes, val, val_out);
+ return val_out;
+}
+
+/* All the debugfs related functions are defined below */
+static int fg_sram_dfs_open(struct inode *inode, struct file *file)
+{
+ struct fg_log_buffer *log;
+ struct fg_trans *trans;
+ u8 *data_buf;
+
+ size_t logbufsize = SZ_4K;
+ size_t databufsize = SZ_4K;
+
+ if (!dbgfs_data.chip) {
+ pr_err("Not initialized data\n");
+ return -EINVAL;
+ }
+
+ /* Per file "transaction" data */
+ trans = devm_kzalloc(dbgfs_data.chip->dev, sizeof(*trans), GFP_KERNEL);
+ if (!trans)
+ return -ENOMEM;
+
+ /* Allocate log buffer */
+ log = devm_kzalloc(dbgfs_data.chip->dev, logbufsize, GFP_KERNEL);
+ if (!log)
+ return -ENOMEM;
+
+ log->rpos = 0;
+ log->wpos = 0;
+ log->len = logbufsize - sizeof(*log);
+
+ /* Allocate data buffer */
+ data_buf = devm_kzalloc(dbgfs_data.chip->dev, databufsize, GFP_KERNEL);
+ if (!data_buf)
+ return -ENOMEM;
+
+ trans->log = log;
+ trans->data = data_buf;
+ trans->cnt = dbgfs_data.cnt;
+ trans->addr = dbgfs_data.addr;
+ trans->chip = dbgfs_data.chip;
+ trans->offset = trans->addr;
+ mutex_init(&trans->fg_dfs_lock);
+
+ file->private_data = trans;
+ return 0;
+}
+
+static int fg_sram_dfs_close(struct inode *inode, struct file *file)
+{
+ struct fg_trans *trans = file->private_data;
+
+ if (trans && trans->log && trans->data) {
+ file->private_data = NULL;
+ mutex_destroy(&trans->fg_dfs_lock);
+ devm_kfree(trans->chip->dev, trans->log);
+ devm_kfree(trans->chip->dev, trans->data);
+ devm_kfree(trans->chip->dev, trans);
+ }
+
+ return 0;
+}
+
+/**
+ * print_to_log: format a string and place into the log buffer
+ * @log: The log buffer to place the result into.
+ * @fmt: The format string to use.
+ * @...: The arguments for the format string.
+ *
+ * The return value is the number of characters written to @log buffer
+ * not including the trailing '\0'.
+ */
+static int print_to_log(struct fg_log_buffer *log, const char *fmt, ...)
+{
+ va_list args;
+ int cnt;
+ char *buf = &log->data[log->wpos];
+ size_t size = log->len - log->wpos;
+
+ va_start(args, fmt);
+ cnt = vscnprintf(buf, size, fmt, args);
+ va_end(args);
+
+ log->wpos += cnt;
+ return cnt;
+}
+
+/**
+ * write_next_line_to_log: Writes a single "line" of data into the log buffer
+ * @trans: Pointer to SRAM transaction data.
+ * @offset: SRAM address offset to start reading from.
+ * @pcnt: Pointer to 'cnt' variable. Indicates the number of bytes to read.
+ *
+ * The 'offset' is a 12-bit SRAM address.
+ *
+ * On a successful read, the pcnt is decremented by the number of data
+ * bytes read from the SRAM. When the cnt reaches 0, all requested bytes have
+ * been read.
+ */
+static int write_next_line_to_log(struct fg_trans *trans, int offset,
+ size_t *pcnt)
+{
+ int i;
+ u8 data[ITEMS_PER_LINE];
+ u16 address;
+ struct fg_log_buffer *log = trans->log;
+ int cnt = 0;
+ int items_to_read = min(ARRAY_SIZE(data), *pcnt);
+ int items_to_log = min(ITEMS_PER_LINE, items_to_read);
+
+ /* Buffer needs enough space for an entire line */
+ if ((log->len - log->wpos) < MAX_LINE_LENGTH)
+ goto done;
+
+ memcpy(data, trans->data + (offset - trans->addr), items_to_read);
+ *pcnt -= items_to_read;
+
+ /* address is in word now and it increments by 1. */
+ address = trans->addr + ((offset - trans->addr) / ITEMS_PER_LINE);
+ cnt = print_to_log(log, "%3.3d ", address & 0xfff);
+ if (cnt == 0)
+ goto done;
+
+ /* Log the data items */
+ for (i = 0; i < items_to_log; ++i) {
+ cnt = print_to_log(log, "%2.2X ", data[i]);
+ if (cnt == 0)
+ goto done;
+ }
+
+ /* If the last character was a space, then replace it with a newline */
+ if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
+ log->data[log->wpos - 1] = '\n';
+
+done:
+ return cnt;
+}
+
+/**
+ * get_log_data - reads data from SRAM and saves to the log buffer
+ * @trans: Pointer to SRAM transaction data.
+ *
+ * Returns the number of "items" read or SPMI error code for read failures.
+ */
+static int get_log_data(struct fg_trans *trans)
+{
+ int cnt, rc;
+ int last_cnt;
+ int items_read;
+ int total_items_read = 0;
+ u32 offset = trans->offset;
+ size_t item_cnt = trans->cnt;
+ struct fg_log_buffer *log = trans->log;
+
+ if (item_cnt == 0)
+ return 0;
+
+ if (item_cnt > SZ_4K) {
+ pr_err("Reading too many bytes\n");
+ return -EINVAL;
+ }
+
+ pr_debug("addr: %d offset: %d count: %d\n", trans->addr, trans->offset,
+ trans->cnt);
+ rc = fg_sram_read(trans->chip, trans->addr, 0,
+ trans->data, trans->cnt, 0);
+ if (rc < 0) {
+ pr_err("SRAM read failed: rc = %d\n", rc);
+ return rc;
+ }
+ /* Reset the log buffer 'pointers' */
+ log->wpos = log->rpos = 0;
+
+ /* Keep reading data until the log is full */
+ do {
+ last_cnt = item_cnt;
+ cnt = write_next_line_to_log(trans, offset, &item_cnt);
+ items_read = last_cnt - item_cnt;
+ offset += items_read;
+ total_items_read += items_read;
+ } while (cnt && item_cnt > 0);
+
+ /* Adjust the transaction offset and count */
+ trans->cnt = item_cnt;
+ trans->offset += total_items_read;
+
+ return total_items_read;
+}
+
+/**
+ * fg_sram_dfs_reg_read: reads value(s) from SRAM and fills user's buffer a
+ * byte array (coded as string)
+ * @file: file pointer
+ * @buf: where to put the result
+ * @count: maximum space available in @buf
+ * @ppos: starting position
+ * @return number of user bytes read, or negative error value
+ */
+static ssize_t fg_sram_dfs_reg_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct fg_trans *trans = file->private_data;
+ struct fg_log_buffer *log = trans->log;
+ size_t ret;
+ size_t len;
+
+ mutex_lock(&trans->fg_dfs_lock);
+ /* Is the the log buffer empty */
+ if (log->rpos >= log->wpos) {
+ if (get_log_data(trans) <= 0) {
+ len = 0;
+ goto unlock_mutex;
+ }
+ }
+
+ len = min(count, log->wpos - log->rpos);
+
+ ret = copy_to_user(buf, &log->data[log->rpos], len);
+ if (ret == len) {
+ pr_err("error copy sram register values to user\n");
+ len = -EFAULT;
+ goto unlock_mutex;
+ }
+
+ /* 'ret' is the number of bytes not copied */
+ len -= ret;
+
+ *ppos += len;
+ log->rpos += len;
+
+unlock_mutex:
+ mutex_unlock(&trans->fg_dfs_lock);
+ return len;
+}
+
+/**
+ * fg_sram_dfs_reg_write: write user's byte array (coded as string) to SRAM.
+ * @file: file pointer
+ * @buf: user data to be written.
+ * @count: maximum space available in @buf
+ * @ppos: starting position
+ * @return number of user byte written, or negative error value
+ */
+static ssize_t fg_sram_dfs_reg_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int bytes_read;
+ int data;
+ int pos = 0;
+ int cnt = 0;
+ u8 *values;
+ char *kbuf;
+ size_t ret = 0;
+ struct fg_trans *trans = file->private_data;
+ u32 address = trans->addr;
+
+ mutex_lock(&trans->fg_dfs_lock);
+ /* Make a copy of the user data */
+ kbuf = kmalloc(count + 1, GFP_KERNEL);
+ if (!kbuf) {
+ ret = -ENOMEM;
+ goto unlock_mutex;
+ }
+
+ ret = copy_from_user(kbuf, buf, count);
+ if (ret == count) {
+ pr_err("failed to copy data from user\n");
+ ret = -EFAULT;
+ goto free_buf;
+ }
+
+ count -= ret;
+ *ppos += count;
+ kbuf[count] = '\0';
+
+ /* Override the text buffer with the raw data */
+ values = kbuf;
+
+ /* Parse the data in the buffer. It should be a string of numbers */
+ while ((pos < count) &&
+ sscanf(kbuf + pos, "%i%n", &data, &bytes_read) == 1) {
+ /*
+ * We shouldn't be receiving a string of characters that
+ * exceeds a size of 5 to keep this functionally correct.
+ * Also, we should make sure that pos never gets overflowed
+ * beyond the limit.
+ */
+ if (bytes_read > 5 || bytes_read > INT_MAX - pos) {
+ cnt = 0;
+ ret = -EINVAL;
+ break;
+ }
+ pos += bytes_read;
+ values[cnt++] = data & 0xff;
+ }
+
+ if (!cnt)
+ goto free_buf;
+
+ pr_debug("address %d, count %d\n", address, cnt);
+ /* Perform the write(s) */
+
+ ret = fg_sram_write(trans->chip, address, 0, values, cnt, 0);
+ if (ret) {
+ pr_err("SRAM write failed, err = %zu\n", ret);
+ } else {
+ ret = count;
+ trans->offset += cnt > 4 ? 4 : cnt;
+ }
+
+free_buf:
+ kfree(kbuf);
+unlock_mutex:
+ mutex_unlock(&trans->fg_dfs_lock);
+ return ret;
+}
+
+static const struct file_operations fg_sram_dfs_reg_fops = {
+ .open = fg_sram_dfs_open,
+ .release = fg_sram_dfs_close,
+ .read = fg_sram_dfs_reg_read,
+ .write = fg_sram_dfs_reg_write,
+};
+
+/*
+ * fg_debugfs_create: adds new fg_sram debugfs entry
+ * @return zero on success
+ */
+static int fg_sram_debugfs_create(struct fg_chip *chip)
+{
+ struct dentry *dfs_sram;
+ struct dentry *file;
+ mode_t dfs_mode = S_IRUSR | S_IWUSR;
+
+ pr_debug("Creating FG_SRAM debugfs file-system\n");
+ dfs_sram = debugfs_create_dir("sram", chip->dfs_root);
+ if (!dfs_sram) {
+ pr_err("error creating fg sram dfs rc=%ld\n",
+ (long)dfs_sram);
+ return -ENOMEM;
+ }
+
+ dbgfs_data.help_msg.size = strlen(dbgfs_data.help_msg.data);
+ file = debugfs_create_blob("help", S_IRUGO, dfs_sram,
+ &dbgfs_data.help_msg);
+ if (!file) {
+ pr_err("error creating help entry\n");
+ goto err_remove_fs;
+ }
+
+ dbgfs_data.chip = chip;
+
+ file = debugfs_create_u32("count", dfs_mode, dfs_sram,
+ &(dbgfs_data.cnt));
+ if (!file) {
+ pr_err("error creating 'count' entry\n");
+ goto err_remove_fs;
+ }
+
+ file = debugfs_create_x32("address", dfs_mode, dfs_sram,
+ &(dbgfs_data.addr));
+ if (!file) {
+ pr_err("error creating 'address' entry\n");
+ goto err_remove_fs;
+ }
+
+ file = debugfs_create_file("data", dfs_mode, dfs_sram, &dbgfs_data,
+ &fg_sram_dfs_reg_fops);
+ if (!file) {
+ pr_err("error creating 'data' entry\n");
+ goto err_remove_fs;
+ }
+
+ return 0;
+
+err_remove_fs:
+ debugfs_remove_recursive(dfs_sram);
+ return -ENOMEM;
+}
+
+static int fg_alg_flags_open(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return 0;
+}
+
+static ssize_t fg_alg_flags_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct fg_chip *chip = file->private_data;
+ char buf[512];
+ u8 alg_flags = 0;
+ int rc, i, len;
+
+ rc = fg_sram_read(chip, chip->sp[FG_SRAM_ALG_FLAGS].addr_word,
+ chip->sp[FG_SRAM_ALG_FLAGS].addr_byte, &alg_flags, 1,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("failed to read algorithm flags rc=%d\n", rc);
+ return -EFAULT;
+ }
+
+ len = 0;
+ for (i = 0; i < ALG_FLAG_MAX; ++i) {
+ if (len > ARRAY_SIZE(buf) - 1)
+ return -EFAULT;
+ if (chip->alg_flags[i].invalid)
+ continue;
+
+ len += snprintf(buf + len, sizeof(buf) - sizeof(*buf) * len,
+ "%s = %d\n", chip->alg_flags[i].name,
+ (bool)(alg_flags & chip->alg_flags[i].bit));
+ }
+
+ return simple_read_from_buffer(userbuf, count, ppos, buf, len);
+}
+
+static const struct file_operations fg_alg_flags_fops = {
+ .open = fg_alg_flags_open,
+ .read = fg_alg_flags_read,
+};
+
+int fg_debugfs_create(struct fg_chip *chip)
+{
+ int rc;
+
+ pr_debug("Creating debugfs file-system\n");
+ chip->dfs_root = debugfs_create_dir("fg", NULL);
+ if (IS_ERR_OR_NULL(chip->dfs_root)) {
+ if (PTR_ERR(chip->dfs_root) == -ENODEV)
+ pr_err("debugfs is not enabled in the kernel\n");
+ else
+ pr_err("error creating fg dfs root rc=%ld\n",
+ (long)chip->dfs_root);
+ return -ENODEV;
+ }
+
+ rc = fg_sram_debugfs_create(chip);
+ if (rc < 0) {
+ pr_err("failed to create sram dfs rc=%d\n", rc);
+ goto err_remove_fs;
+ }
+
+ if (!debugfs_create_file("alg_flags", S_IRUSR, chip->dfs_root, chip,
+ &fg_alg_flags_fops)) {
+ pr_err("failed to create alg_flags file\n");
+ goto err_remove_fs;
+ }
+
+ return 0;
+
+err_remove_fs:
+ debugfs_remove_recursive(chip->dfs_root);
+ return -ENOMEM;
+}
diff --git a/drivers/power/supply/qcom/msm_bcl.c b/drivers/power/supply/qcom/msm_bcl.c
new file mode 100644
index 000000000000..aea3f4645897
--- /dev/null
+++ b/drivers/power/supply/qcom/msm_bcl.c
@@ -0,0 +1,374 @@
+/* Copyright (c) 2014, 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/sysfs.h>
+#include <linux/mutex.h>
+#include <linux/msm_bcl.h>
+#include <linux/slab.h>
+
+#define BCL_PARAM_MAX_ATTR 3
+
+#define BCL_DEFINE_RO_PARAM(_attr, _name, _attr_gp, _index) \
+ _attr.attr.name = __stringify(_name); \
+ _attr.attr.mode = 0444; \
+ _attr.show = _name##_show; \
+ _attr_gp.attrs[_index] = &_attr.attr;
+
+static struct bcl_param_data *bcl[BCL_PARAM_MAX];
+
+static ssize_t high_trip_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int val = 0, ret = 0;
+ struct bcl_param_data *dev_param = container_of(attr,
+ struct bcl_param_data, high_trip_attr);
+
+ if (!dev_param->registered)
+ return -ENODEV;
+
+ ret = dev_param->ops->get_high_trip(&val);
+ if (ret) {
+ pr_err("High trip value read failed. err:%d\n", ret);
+ return ret;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t low_trip_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int val = 0, ret = 0;
+ struct bcl_param_data *dev_param = container_of(attr,
+ struct bcl_param_data, low_trip_attr);
+
+ if (!dev_param->registered)
+ return -ENODEV;
+
+ ret = dev_param->ops->get_low_trip(&val);
+ if (ret) {
+ pr_err("Low trip value read failed. err:%d\n", ret);
+ return ret;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int32_t val = 0, ret = 0;
+ struct bcl_param_data *dev_param = container_of(attr,
+ struct bcl_param_data, val_attr);
+
+ if (!dev_param->registered)
+ return -ENODEV;
+
+ ret = dev_param->ops->read(&val);
+ if (ret) {
+ pr_err("Value read failed. err:%d\n", ret);
+ return ret;
+ }
+ dev_param->last_read_val = val;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+int msm_bcl_set_threshold(enum bcl_param param_type,
+ enum bcl_trip_type trip_type, struct bcl_threshold *inp_thresh)
+{
+ int ret = 0;
+
+ if (param_type >= BCL_PARAM_MAX || !bcl[param_type]
+ || !bcl[param_type]->registered) {
+ pr_err("BCL not initialized\n");
+ return -EINVAL;
+ }
+ if ((!inp_thresh)
+ || (inp_thresh->trip_value < 0)
+ || (!inp_thresh->trip_notify)
+ || (trip_type >= BCL_TRIP_MAX)) {
+ pr_err("Invalid Input\n");
+ return -EINVAL;
+ }
+
+ bcl[param_type]->thresh[trip_type] = inp_thresh;
+ if (trip_type == BCL_HIGH_TRIP) {
+ bcl[param_type]->high_trip = inp_thresh->trip_value;
+ ret = bcl[param_type]->ops->set_high_trip(
+ inp_thresh->trip_value);
+ } else {
+ bcl[param_type]->low_trip = inp_thresh->trip_value;
+ ret = bcl[param_type]->ops->set_low_trip(
+ inp_thresh->trip_value);
+ }
+ if (ret) {
+ pr_err("Error setting trip%d for param%d. err:%d\n", trip_type,
+ param_type, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int bcl_thresh_notify(struct bcl_param_data *param_data, int val,
+ enum bcl_trip_type trip_type)
+{
+ if (!param_data || trip_type >= BCL_TRIP_MAX
+ || !param_data->registered) {
+ pr_err("Invalid input\n");
+ return -EINVAL;
+ }
+
+ param_data->thresh[trip_type]->trip_notify(trip_type, val,
+ param_data->thresh[trip_type]->trip_data);
+
+ return 0;
+}
+
+static int bcl_add_sysfs_nodes(enum bcl_param param_type);
+struct bcl_param_data *msm_bcl_register_param(enum bcl_param param_type,
+ struct bcl_driver_ops *param_ops, char *name)
+{
+ int ret = 0;
+
+ if (param_type >= BCL_PARAM_MAX
+ || !bcl[param_type] || !param_ops || !name
+ || !param_ops->read || !param_ops->set_high_trip
+ || !param_ops->get_high_trip || !param_ops->set_low_trip
+ || !param_ops->get_low_trip || !param_ops->enable
+ || !param_ops->disable) {
+ pr_err("Invalid input\n");
+ return NULL;
+ }
+ if (bcl[param_type]->registered) {
+ pr_err("param%d already initialized\n", param_type);
+ return NULL;
+ }
+
+ ret = bcl_add_sysfs_nodes(param_type);
+ if (ret) {
+ pr_err("Error creating sysfs nodes. err:%d\n", ret);
+ return NULL;
+ }
+ bcl[param_type]->ops = param_ops;
+ bcl[param_type]->registered = true;
+ strlcpy(bcl[param_type]->name, name, BCL_NAME_MAX_LEN);
+ param_ops->notify = bcl_thresh_notify;
+
+ return bcl[param_type];
+}
+
+int msm_bcl_unregister_param(struct bcl_param_data *param_data)
+{
+ int i = 0, ret = -EINVAL;
+
+ if (!bcl[i] || !param_data) {
+ pr_err("Invalid input\n");
+ return ret;
+ }
+ for (; i < BCL_PARAM_MAX; i++) {
+ if (param_data != bcl[i])
+ continue;
+ bcl[i]->ops->disable();
+ bcl[i]->registered = false;
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+int msm_bcl_disable(void)
+{
+ int ret = 0, i = 0;
+
+ if (!bcl[i]) {
+ pr_err("BCL not initialized\n");
+ return -EINVAL;
+ }
+
+ for (; i < BCL_PARAM_MAX; i++) {
+ if (!bcl[i]->registered)
+ continue;
+ ret = bcl[i]->ops->disable();
+ if (ret) {
+ pr_err("Error in disabling interrupt. param:%d err%d\n",
+ i, ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+int msm_bcl_enable(void)
+{
+ int ret = 0, i = 0;
+ struct bcl_param_data *param_data = NULL;
+
+ if (!bcl[i] || !bcl[BCL_PARAM_VOLTAGE]->thresh
+ || !bcl[BCL_PARAM_CURRENT]->thresh) {
+ pr_err("BCL not initialized\n");
+ return -EINVAL;
+ }
+
+ for (; i < BCL_PARAM_MAX; i++) {
+ if (!bcl[i]->registered)
+ continue;
+ param_data = bcl[i];
+ ret = param_data->ops->set_high_trip(param_data->high_trip);
+ if (ret) {
+ pr_err("Error setting high trip. param:%d. err:%d",
+ i, ret);
+ return ret;
+ }
+ ret = param_data->ops->set_low_trip(param_data->low_trip);
+ if (ret) {
+ pr_err("Error setting low trip. param:%d. err:%d",
+ i, ret);
+ return ret;
+ }
+ ret = param_data->ops->enable();
+ if (ret) {
+ pr_err("Error enabling interrupt. param:%d. err:%d",
+ i, ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+int msm_bcl_read(enum bcl_param param_type, int *value)
+{
+ int ret = 0;
+
+ if (!value || param_type >= BCL_PARAM_MAX) {
+ pr_err("Invalid input\n");
+ return -EINVAL;
+ }
+ if (!bcl[param_type] || !bcl[param_type]->registered) {
+ pr_err("BCL driver not initialized\n");
+ return -ENOSYS;
+ }
+
+ ret = bcl[param_type]->ops->read(value);
+ if (ret) {
+ pr_err("Error reading param%d. err:%d\n", param_type, ret);
+ return ret;
+ }
+ bcl[param_type]->last_read_val = *value;
+
+ return ret;
+}
+
+static struct class msm_bcl_class = {
+ .name = "msm_bcl",
+};
+
+static int bcl_add_sysfs_nodes(enum bcl_param param_type)
+{
+ char *param_name[BCL_PARAM_MAX] = {"voltage", "current"};
+ int ret = 0;
+
+ bcl[param_type]->device.class = &msm_bcl_class;
+ dev_set_name(&bcl[param_type]->device, "%s", param_name[param_type]);
+ ret = device_register(&bcl[param_type]->device);
+ if (ret) {
+ pr_err("Error registering device %s. err:%d\n",
+ param_name[param_type], ret);
+ return ret;
+ }
+ bcl[param_type]->bcl_attr_gp.attrs = kzalloc(sizeof(struct attribute *)
+ * (BCL_PARAM_MAX_ATTR + 1), GFP_KERNEL);
+ if (!bcl[param_type]->bcl_attr_gp.attrs) {
+ pr_err("Sysfs attribute create failed.\n");
+ ret = -ENOMEM;
+ goto add_sysfs_exit;
+ }
+ BCL_DEFINE_RO_PARAM(bcl[param_type]->val_attr, value,
+ bcl[param_type]->bcl_attr_gp, 0);
+ BCL_DEFINE_RO_PARAM(bcl[param_type]->high_trip_attr, high_trip,
+ bcl[param_type]->bcl_attr_gp, 1);
+ BCL_DEFINE_RO_PARAM(bcl[param_type]->low_trip_attr, low_trip,
+ bcl[param_type]->bcl_attr_gp, 2);
+ bcl[param_type]->bcl_attr_gp.attrs[BCL_PARAM_MAX_ATTR] = NULL;
+
+ ret = sysfs_create_group(&bcl[param_type]->device.kobj,
+ &bcl[param_type]->bcl_attr_gp);
+ if (ret) {
+ pr_err("Failure to create sysfs nodes. err:%d", ret);
+ goto add_sysfs_exit;
+ }
+
+add_sysfs_exit:
+ return ret;
+}
+
+static int msm_bcl_init(void)
+{
+ int ret = 0, i = 0;
+
+ for (; i < BCL_PARAM_MAX; i++) {
+ bcl[i] = kzalloc(sizeof(struct bcl_param_data),
+ GFP_KERNEL);
+ if (!bcl[i]) {
+ pr_err("kzalloc failed\n");
+ while ((--i) >= 0)
+ kfree(bcl[i]);
+ return -ENOMEM;
+ }
+ }
+
+ return ret;
+}
+
+
+static int __init msm_bcl_init_driver(void)
+{
+ int ret = 0;
+
+ ret = msm_bcl_init();
+ if (ret) {
+ pr_err("msm bcl init failed. err:%d\n", ret);
+ return ret;
+ }
+ return class_register(&msm_bcl_class);
+}
+
+static void __exit bcl_exit(void)
+{
+ int i = 0;
+
+ for (; i < BCL_PARAM_MAX; i++) {
+ sysfs_remove_group(&bcl[i]->device.kobj,
+ &bcl[i]->bcl_attr_gp);
+ kfree(bcl[i]->bcl_attr_gp.attrs);
+ kfree(bcl[i]);
+ }
+ class_unregister(&msm_bcl_class);
+}
+
+fs_initcall(msm_bcl_init_driver);
+module_exit(bcl_exit);
diff --git a/drivers/power/supply/qcom/pmic-voter.c b/drivers/power/supply/qcom/pmic-voter.c
new file mode 100644
index 000000000000..3d0a71844608
--- /dev/null
+++ b/drivers/power/supply/qcom/pmic-voter.c
@@ -0,0 +1,695 @@
+/* Copyright (c) 2015-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/bitops.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <linux/pmic-voter.h>
+
+#define NUM_MAX_CLIENTS 16
+#define DEBUG_FORCE_CLIENT "DEBUG_FORCE_CLIENT"
+
+static DEFINE_SPINLOCK(votable_list_slock);
+static LIST_HEAD(votable_list);
+
+static struct dentry *debug_root;
+
+struct client_vote {
+ bool enabled;
+ int value;
+};
+
+struct votable {
+ const char *name;
+ struct list_head list;
+ struct client_vote votes[NUM_MAX_CLIENTS];
+ int num_clients;
+ int type;
+ int effective_client_id;
+ int effective_result;
+ struct mutex vote_lock;
+ void *data;
+ int (*callback)(struct votable *votable,
+ void *data,
+ int effective_result,
+ const char *effective_client);
+ char *client_strs[NUM_MAX_CLIENTS];
+ bool voted_on;
+ struct dentry *root;
+ struct dentry *status_ent;
+ u32 force_val;
+ struct dentry *force_val_ent;
+ bool force_active;
+ struct dentry *force_active_ent;
+};
+
+/**
+ * vote_set_any()
+ * @votable: votable object
+ * @client_id: client number of the latest voter
+ * @eff_res: sets 0 or 1 based on the voting
+ * @eff_id: Always returns the client_id argument
+ *
+ * Note that for SET_ANY voter, the value is always same as enabled. There is
+ * no idea of a voter abstaining from the election. Hence there is never a
+ * situation when the effective_id will be invalid, during election.
+ *
+ * Context:
+ * Must be called with the votable->lock held
+ */
+static void vote_set_any(struct votable *votable, int client_id,
+ int *eff_res, int *eff_id)
+{
+ int i;
+
+ *eff_res = 0;
+
+ for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++)
+ *eff_res |= votable->votes[i].enabled;
+
+ *eff_id = client_id;
+}
+
+/**
+ * vote_min() -
+ * @votable: votable object
+ * @client_id: client number of the latest voter
+ * @eff_res: sets this to the min. of all the values amongst enabled voters.
+ * If there is no enabled client, this is set to INT_MAX
+ * @eff_id: sets this to the client id that has the min value amongst all
+ * the enabled clients. If there is no enabled client, sets this
+ * to -EINVAL
+ *
+ * Context:
+ * Must be called with the votable->lock held
+ */
+static void vote_min(struct votable *votable, int client_id,
+ int *eff_res, int *eff_id)
+{
+ int i;
+
+ *eff_res = INT_MAX;
+ *eff_id = -EINVAL;
+ for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) {
+ if (votable->votes[i].enabled
+ && *eff_res > votable->votes[i].value) {
+ *eff_res = votable->votes[i].value;
+ *eff_id = i;
+ }
+ }
+ if (*eff_id == -EINVAL)
+ *eff_res = -EINVAL;
+}
+
+/**
+ * vote_max() -
+ * @votable: votable object
+ * @client_id: client number of the latest voter
+ * @eff_res: sets this to the max. of all the values amongst enabled voters.
+ * If there is no enabled client, this is set to -EINVAL
+ * @eff_id: sets this to the client id that has the max value amongst all
+ * the enabled clients. If there is no enabled client, sets this to
+ * -EINVAL
+ *
+ * Context:
+ * Must be called with the votable->lock held
+ */
+static void vote_max(struct votable *votable, int client_id,
+ int *eff_res, int *eff_id)
+{
+ int i;
+
+ *eff_res = INT_MIN;
+ *eff_id = -EINVAL;
+ for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) {
+ if (votable->votes[i].enabled &&
+ *eff_res < votable->votes[i].value) {
+ *eff_res = votable->votes[i].value;
+ *eff_id = i;
+ }
+ }
+ if (*eff_id == -EINVAL)
+ *eff_res = -EINVAL;
+}
+
+static int get_client_id(struct votable *votable, const char *client_str)
+{
+ int i;
+
+ for (i = 0; i < votable->num_clients; i++) {
+ if (votable->client_strs[i]
+ && (strcmp(votable->client_strs[i], client_str) == 0))
+ return i;
+ }
+
+ /* new client */
+ for (i = 0; i < votable->num_clients; i++) {
+ if (!votable->client_strs[i]) {
+ votable->client_strs[i]
+ = kstrdup(client_str, GFP_KERNEL);
+ if (!votable->client_strs[i])
+ return -ENOMEM;
+ return i;
+ }
+ }
+ return -EINVAL;
+}
+
+static char *get_client_str(struct votable *votable, int client_id)
+{
+ if (client_id == -EINVAL)
+ return NULL;
+
+ return votable->client_strs[client_id];
+}
+
+void lock_votable(struct votable *votable)
+{
+ mutex_lock(&votable->vote_lock);
+}
+
+void unlock_votable(struct votable *votable)
+{
+ mutex_unlock(&votable->vote_lock);
+}
+
+/**
+ * is_client_vote_enabled() -
+ * is_client_vote_enabled_locked() -
+ * The unlocked and locked variants of getting whether a client's
+ vote is enabled.
+ * @votable: the votable object
+ * @client_str: client of interest
+ *
+ * Returns:
+ * True if the client's vote is enabled; false otherwise.
+ */
+bool is_client_vote_enabled_locked(struct votable *votable,
+ const char *client_str)
+{
+ int client_id = get_client_id(votable, client_str);
+
+ if (client_id < 0)
+ return false;
+
+ return votable->votes[client_id].enabled;
+}
+
+bool is_client_vote_enabled(struct votable *votable, const char *client_str)
+{
+ bool enabled;
+
+ lock_votable(votable);
+ enabled = is_client_vote_enabled_locked(votable, client_str);
+ unlock_votable(votable);
+ return enabled;
+}
+
+/**
+ * get_client_vote() -
+ * get_client_vote_locked() -
+ * The unlocked and locked variants of getting a client's voted
+ * value.
+ * @votable: the votable object
+ * @client_str: client of interest
+ *
+ * Returns:
+ * The value the client voted for. -EINVAL is returned if the client
+ * is not enabled or the client is not found.
+ */
+int get_client_vote_locked(struct votable *votable, const char *client_str)
+{
+ int client_id = get_client_id(votable, client_str);
+
+ if (client_id < 0)
+ return -EINVAL;
+
+ if ((votable->type != VOTE_SET_ANY)
+ && !votable->votes[client_id].enabled)
+ return -EINVAL;
+
+ return votable->votes[client_id].value;
+}
+
+int get_client_vote(struct votable *votable, const char *client_str)
+{
+ int value;
+
+ lock_votable(votable);
+ value = get_client_vote_locked(votable, client_str);
+ unlock_votable(votable);
+ return value;
+}
+
+/**
+ * get_effective_result() -
+ * get_effective_result_locked() -
+ * The unlocked and locked variants of getting the effective value
+ * amongst all the enabled voters.
+ *
+ * @votable: the votable object
+ *
+ * Returns:
+ * The effective result.
+ * For MIN and MAX votable, returns -EINVAL when the votable
+ * object has been created but no clients have casted their votes or
+ * the last enabled client disables its vote.
+ * For SET_ANY votable it returns 0 when no clients have casted their votes
+ * because for SET_ANY there is no concept of abstaining from election. The
+ * votes for all the clients of SET_ANY votable is defaulted to false.
+ */
+int get_effective_result_locked(struct votable *votable)
+{
+ if (votable->force_active)
+ return votable->force_val;
+
+ return votable->effective_result;
+}
+
+int get_effective_result(struct votable *votable)
+{
+ int value;
+
+ lock_votable(votable);
+ value = get_effective_result_locked(votable);
+ unlock_votable(votable);
+ return value;
+}
+
+/**
+ * get_effective_client() -
+ * get_effective_client_locked() -
+ * The unlocked and locked variants of getting the effective client
+ * amongst all the enabled voters.
+ *
+ * @votable: the votable object
+ *
+ * Returns:
+ * The effective client.
+ * For MIN and MAX votable, returns NULL when the votable
+ * object has been created but no clients have casted their votes or
+ * the last enabled client disables its vote.
+ * For SET_ANY votable it returns NULL too when no clients have casted
+ * their votes. But for SET_ANY since there is no concept of abstaining
+ * from election, the only client that casts a vote or the client that
+ * caused the result to change is returned.
+ */
+const char *get_effective_client_locked(struct votable *votable)
+{
+ if (votable->force_active)
+ return DEBUG_FORCE_CLIENT;
+
+ return get_client_str(votable, votable->effective_client_id);
+}
+
+const char *get_effective_client(struct votable *votable)
+{
+ const char *client_str;
+
+ lock_votable(votable);
+ client_str = get_effective_client_locked(votable);
+ unlock_votable(votable);
+ return client_str;
+}
+
+/**
+ * vote() -
+ *
+ * @votable: the votable object
+ * @client_str: the voting client
+ * @enabled: This provides a means for the client to exclude himself from
+ * election. This clients val (the next argument) will be
+ * considered only when he has enabled his participation.
+ * Note that this takes a differnt meaning for SET_ANY type, as
+ * there is no concept of abstaining from participation.
+ * Enabled is treated as the boolean value the client is voting.
+ * @val: The vote value. This is ignored for SET_ANY votable types.
+ * For MIN, MAX votable types this value is used as the
+ * clients vote value when the enabled is true, this value is
+ * ignored if enabled is false.
+ *
+ * The callback is called only when there is a change in the election results or
+ * if it is the first time someone is voting.
+ *
+ * Returns:
+ * The return from the callback when present and needs to be called
+ * or zero.
+ */
+int vote(struct votable *votable, const char *client_str, bool enabled, int val)
+{
+ int effective_id = -EINVAL;
+ int effective_result;
+ int client_id;
+ int rc = 0;
+ bool similar_vote = false;
+
+ lock_votable(votable);
+
+ client_id = get_client_id(votable, client_str);
+ if (client_id < 0) {
+ rc = client_id;
+ goto out;
+ }
+
+ /*
+ * for SET_ANY the val is to be ignored, set it
+ * to enabled so that the election still works based on
+ * value regardless of the type
+ */
+ if (votable->type == VOTE_SET_ANY)
+ val = enabled;
+
+ if ((votable->votes[client_id].enabled == enabled) &&
+ (votable->votes[client_id].value == val)) {
+ pr_debug("%s: %s,%d same vote %s of val=%d\n",
+ votable->name,
+ client_str, client_id,
+ enabled ? "on" : "off",
+ val);
+ similar_vote = true;
+ }
+
+ votable->votes[client_id].enabled = enabled;
+ votable->votes[client_id].value = val;
+
+ if (similar_vote && votable->voted_on) {
+ pr_debug("%s: %s,%d Ignoring similar vote %s of val=%d\n",
+ votable->name,
+ client_str, client_id, enabled ? "on" : "off", val);
+ goto out;
+ }
+
+ pr_debug("%s: %s,%d voting %s of val=%d\n",
+ votable->name,
+ client_str, client_id, enabled ? "on" : "off", val);
+ switch (votable->type) {
+ case VOTE_MIN:
+ vote_min(votable, client_id, &effective_result, &effective_id);
+ break;
+ case VOTE_MAX:
+ vote_max(votable, client_id, &effective_result, &effective_id);
+ break;
+ case VOTE_SET_ANY:
+ vote_set_any(votable, client_id,
+ &effective_result, &effective_id);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Note that the callback is called with a NULL string and -EINVAL
+ * result when there are no enabled votes
+ */
+ if (!votable->voted_on
+ || (effective_result != votable->effective_result)) {
+ votable->effective_client_id = effective_id;
+ votable->effective_result = effective_result;
+ pr_debug("%s: effective vote is now %d voted by %s,%d\n",
+ votable->name, effective_result,
+ get_client_str(votable, effective_id),
+ effective_id);
+ if (votable->callback && !votable->force_active)
+ rc = votable->callback(votable, votable->data,
+ effective_result,
+ get_client_str(votable, effective_id));
+ }
+
+ votable->voted_on = true;
+out:
+ unlock_votable(votable);
+ return rc;
+}
+
+int rerun_election(struct votable *votable)
+{
+ int rc = 0;
+ int effective_result;
+
+ lock_votable(votable);
+ effective_result = get_effective_result_locked(votable);
+ if (votable->callback)
+ rc = votable->callback(votable,
+ votable->data,
+ effective_result,
+ get_client_str(votable, votable->effective_client_id));
+ unlock_votable(votable);
+ return rc;
+}
+
+struct votable *find_votable(const char *name)
+{
+ unsigned long flags;
+ struct votable *v;
+ bool found = false;
+
+ spin_lock_irqsave(&votable_list_slock, flags);
+ if (list_empty(&votable_list))
+ goto out;
+
+ list_for_each_entry(v, &votable_list, list) {
+ if (strcmp(v->name, name) == 0) {
+ found = true;
+ break;
+ }
+ }
+out:
+ spin_unlock_irqrestore(&votable_list_slock, flags);
+
+ if (found)
+ return v;
+ else
+ return NULL;
+}
+
+static int force_active_get(void *data, u64 *val)
+{
+ struct votable *votable = data;
+
+ *val = votable->force_active;
+
+ return 0;
+}
+
+static int force_active_set(void *data, u64 val)
+{
+ struct votable *votable = data;
+ int rc = 0;
+
+ lock_votable(votable);
+ votable->force_active = !!val;
+
+ if (!votable->callback)
+ goto out;
+
+ if (votable->force_active) {
+ rc = votable->callback(votable, votable->data,
+ votable->force_val,
+ DEBUG_FORCE_CLIENT);
+ } else {
+ rc = votable->callback(votable, votable->data,
+ votable->effective_result,
+ get_client_str(votable, votable->effective_client_id));
+ }
+out:
+ unlock_votable(votable);
+ return rc;
+}
+DEFINE_SIMPLE_ATTRIBUTE(votable_force_ops, force_active_get, force_active_set,
+ "%lld\n");
+
+static int show_votable_clients(struct seq_file *m, void *data)
+{
+ struct votable *votable = m->private;
+ int i;
+ char *type_str = "Unkonwn";
+ const char *effective_client_str;
+
+ lock_votable(votable);
+
+ for (i = 0; i < votable->num_clients; i++) {
+ if (votable->client_strs[i]) {
+ seq_printf(m, "%s: %s:\t\t\ten=%d v=%d\n",
+ votable->name,
+ votable->client_strs[i],
+ votable->votes[i].enabled,
+ votable->votes[i].value);
+ }
+ }
+
+ switch (votable->type) {
+ case VOTE_MIN:
+ type_str = "Min";
+ break;
+ case VOTE_MAX:
+ type_str = "Max";
+ break;
+ case VOTE_SET_ANY:
+ type_str = "Set_any";
+ break;
+ }
+
+ effective_client_str = get_effective_client_locked(votable);
+ seq_printf(m, "%s: effective=%s type=%s v=%d\n",
+ votable->name,
+ effective_client_str ? effective_client_str : "none",
+ type_str,
+ get_effective_result_locked(votable));
+ unlock_votable(votable);
+
+ return 0;
+}
+
+static int votable_status_open(struct inode *inode, struct file *file)
+{
+ struct votable *votable = inode->i_private;
+
+ return single_open(file, show_votable_clients, votable);
+}
+
+static const struct file_operations votable_status_ops = {
+ .owner = THIS_MODULE,
+ .open = votable_status_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+struct votable *create_votable(const char *name,
+ int votable_type,
+ int (*callback)(struct votable *votable,
+ void *data,
+ int effective_result,
+ const char *effective_client),
+ void *data)
+{
+ struct votable *votable;
+ unsigned long flags;
+
+ votable = find_votable(name);
+ if (votable)
+ return ERR_PTR(-EEXIST);
+
+ if (debug_root == NULL) {
+ debug_root = debugfs_create_dir("pmic-votable", NULL);
+ if (!debug_root) {
+ pr_err("Couldn't create debug dir\n");
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+
+ if (votable_type >= NUM_VOTABLE_TYPES) {
+ pr_err("Invalid votable_type specified for voter\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ votable = kzalloc(sizeof(struct votable), GFP_KERNEL);
+ if (!votable)
+ return ERR_PTR(-ENOMEM);
+
+ votable->name = kstrdup(name, GFP_KERNEL);
+ if (!votable->name) {
+ kfree(votable);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ votable->num_clients = NUM_MAX_CLIENTS;
+ votable->callback = callback;
+ votable->type = votable_type;
+ votable->data = data;
+ mutex_init(&votable->vote_lock);
+
+ /*
+ * Because effective_result and client states are invalid
+ * before the first vote, initialize them to -EINVAL
+ */
+ votable->effective_result = -EINVAL;
+ if (votable->type == VOTE_SET_ANY)
+ votable->effective_result = 0;
+ votable->effective_client_id = -EINVAL;
+
+ spin_lock_irqsave(&votable_list_slock, flags);
+ list_add(&votable->list, &votable_list);
+ spin_unlock_irqrestore(&votable_list_slock, flags);
+
+ votable->root = debugfs_create_dir(name, debug_root);
+ if (!votable->root) {
+ pr_err("Couldn't create debug dir %s\n", name);
+ kfree(votable->name);
+ kfree(votable);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ votable->status_ent = debugfs_create_file("status", S_IFREG | S_IRUGO,
+ votable->root, votable,
+ &votable_status_ops);
+ if (!votable->status_ent) {
+ pr_err("Couldn't create status dbg file for %s\n", name);
+ debugfs_remove_recursive(votable->root);
+ kfree(votable->name);
+ kfree(votable);
+ return ERR_PTR(-EEXIST);
+ }
+
+ votable->force_val_ent = debugfs_create_u32("force_val",
+ S_IFREG | S_IWUSR | S_IRUGO,
+ votable->root,
+ &(votable->force_val));
+
+ if (!votable->force_val_ent) {
+ pr_err("Couldn't create force_val dbg file for %s\n", name);
+ debugfs_remove_recursive(votable->root);
+ kfree(votable->name);
+ kfree(votable);
+ return ERR_PTR(-EEXIST);
+ }
+
+ votable->force_active_ent = debugfs_create_file("force_active",
+ S_IFREG | S_IRUGO,
+ votable->root, votable,
+ &votable_force_ops);
+ if (!votable->force_active_ent) {
+ pr_err("Couldn't create force_active dbg file for %s\n", name);
+ debugfs_remove_recursive(votable->root);
+ kfree(votable->name);
+ kfree(votable);
+ return ERR_PTR(-EEXIST);
+ }
+
+ return votable;
+}
+
+void destroy_votable(struct votable *votable)
+{
+ unsigned long flags;
+ int i;
+
+ if (!votable)
+ return;
+
+ spin_lock_irqsave(&votable_list_slock, flags);
+ list_del(&votable->list);
+ spin_unlock_irqrestore(&votable_list_slock, flags);
+
+ debugfs_remove_recursive(votable->root);
+
+ for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++)
+ kfree(votable->client_strs[i]);
+
+ kfree(votable->name);
+ kfree(votable);
+}
diff --git a/drivers/power/supply/qcom/qpnp-fg-gen3.c b/drivers/power/supply/qcom/qpnp-fg-gen3.c
new file mode 100644
index 000000000000..361efd4fbbbd
--- /dev/null
+++ b/drivers/power/supply/qcom/qpnp-fg-gen3.c
@@ -0,0 +1,5002 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "FG: %s: " fmt, __func__
+
+#include <linux/ktime.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/of_batterydata.h>
+#include <linux/platform_device.h>
+#include <linux/iio/consumer.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include "fg-core.h"
+#include "fg-reg.h"
+
+#define FG_GEN3_DEV_NAME "qcom,fg-gen3"
+
+#define PERPH_SUBTYPE_REG 0x05
+#define FG_BATT_SOC_PMI8998 0x10
+#define FG_BATT_INFO_PMI8998 0x11
+#define FG_MEM_INFO_PMI8998 0x0D
+
+/* SRAM address and offset in ascending order */
+#define ESR_PULSE_THRESH_WORD 2
+#define ESR_PULSE_THRESH_OFFSET 3
+#define SLOPE_LIMIT_WORD 3
+#define SLOPE_LIMIT_OFFSET 0
+#define CUTOFF_VOLT_WORD 5
+#define CUTOFF_VOLT_OFFSET 0
+#define SYS_TERM_CURR_WORD 6
+#define SYS_TERM_CURR_OFFSET 0
+#define VBATT_FULL_WORD 7
+#define VBATT_FULL_OFFSET 0
+#define ESR_FILTER_WORD 8
+#define ESR_UPD_TIGHT_OFFSET 0
+#define ESR_UPD_BROAD_OFFSET 1
+#define ESR_UPD_TIGHT_LOW_TEMP_OFFSET 2
+#define ESR_UPD_BROAD_LOW_TEMP_OFFSET 3
+#define KI_COEFF_MED_DISCHG_WORD 9
+#define TIMEBASE_OFFSET 1
+#define KI_COEFF_MED_DISCHG_OFFSET 3
+#define KI_COEFF_HI_DISCHG_WORD 10
+#define KI_COEFF_HI_DISCHG_OFFSET 0
+#define KI_COEFF_LOW_DISCHG_WORD 10
+#define KI_COEFF_LOW_DISCHG_OFFSET 2
+#define KI_COEFF_FULL_SOC_WORD 12
+#define KI_COEFF_FULL_SOC_OFFSET 2
+#define DELTA_MSOC_THR_WORD 12
+#define DELTA_MSOC_THR_OFFSET 3
+#define DELTA_BSOC_THR_WORD 13
+#define DELTA_BSOC_THR_OFFSET 2
+#define RECHARGE_SOC_THR_WORD 14
+#define RECHARGE_SOC_THR_OFFSET 0
+#define CHG_TERM_CURR_WORD 14
+#define CHG_TERM_CURR_OFFSET 1
+#define EMPTY_VOLT_WORD 15
+#define EMPTY_VOLT_OFFSET 0
+#define VBATT_LOW_WORD 15
+#define VBATT_LOW_OFFSET 1
+#define ESR_TIMER_DISCHG_MAX_WORD 17
+#define ESR_TIMER_DISCHG_MAX_OFFSET 0
+#define ESR_TIMER_DISCHG_INIT_WORD 17
+#define ESR_TIMER_DISCHG_INIT_OFFSET 2
+#define ESR_TIMER_CHG_MAX_WORD 18
+#define ESR_TIMER_CHG_MAX_OFFSET 0
+#define ESR_TIMER_CHG_INIT_WORD 18
+#define ESR_TIMER_CHG_INIT_OFFSET 2
+#define ESR_EXTRACTION_ENABLE_WORD 19
+#define ESR_EXTRACTION_ENABLE_OFFSET 0
+#define PROFILE_LOAD_WORD 24
+#define PROFILE_LOAD_OFFSET 0
+#define ESR_RSLOW_DISCHG_WORD 34
+#define ESR_RSLOW_DISCHG_OFFSET 0
+#define ESR_RSLOW_CHG_WORD 51
+#define ESR_RSLOW_CHG_OFFSET 0
+#define NOM_CAP_WORD 58
+#define NOM_CAP_OFFSET 0
+#define ACT_BATT_CAP_BKUP_WORD 74
+#define ACT_BATT_CAP_BKUP_OFFSET 0
+#define CYCLE_COUNT_WORD 75
+#define CYCLE_COUNT_OFFSET 0
+#define PROFILE_INTEGRITY_WORD 79
+#define SW_CONFIG_OFFSET 0
+#define PROFILE_INTEGRITY_OFFSET 3
+#define BATT_SOC_WORD 91
+#define BATT_SOC_OFFSET 0
+#define FULL_SOC_WORD 93
+#define FULL_SOC_OFFSET 2
+#define MONOTONIC_SOC_WORD 94
+#define MONOTONIC_SOC_OFFSET 2
+#define CC_SOC_WORD 95
+#define CC_SOC_OFFSET 0
+#define CC_SOC_SW_WORD 96
+#define CC_SOC_SW_OFFSET 0
+#define VOLTAGE_PRED_WORD 97
+#define VOLTAGE_PRED_OFFSET 0
+#define OCV_WORD 97
+#define OCV_OFFSET 2
+#define ESR_WORD 99
+#define ESR_OFFSET 0
+#define RSLOW_WORD 101
+#define RSLOW_OFFSET 0
+#define ACT_BATT_CAP_WORD 117
+#define ACT_BATT_CAP_OFFSET 0
+#define LAST_BATT_SOC_WORD 119
+#define LAST_BATT_SOC_OFFSET 0
+#define LAST_MONOTONIC_SOC_WORD 119
+#define LAST_MONOTONIC_SOC_OFFSET 2
+#define ALG_FLAGS_WORD 120
+#define ALG_FLAGS_OFFSET 1
+
+/* v2 SRAM address and offset in ascending order */
+#define KI_COEFF_LOW_DISCHG_v2_WORD 9
+#define KI_COEFF_LOW_DISCHG_v2_OFFSET 3
+#define KI_COEFF_MED_DISCHG_v2_WORD 10
+#define KI_COEFF_MED_DISCHG_v2_OFFSET 0
+#define KI_COEFF_HI_DISCHG_v2_WORD 10
+#define KI_COEFF_HI_DISCHG_v2_OFFSET 1
+#define DELTA_BSOC_THR_v2_WORD 12
+#define DELTA_BSOC_THR_v2_OFFSET 3
+#define DELTA_MSOC_THR_v2_WORD 13
+#define DELTA_MSOC_THR_v2_OFFSET 0
+#define RECHARGE_SOC_THR_v2_WORD 14
+#define RECHARGE_SOC_THR_v2_OFFSET 1
+#define CHG_TERM_CURR_v2_WORD 15
+#define CHG_TERM_BASE_CURR_v2_OFFSET 0
+#define CHG_TERM_CURR_v2_OFFSET 1
+#define EMPTY_VOLT_v2_WORD 15
+#define EMPTY_VOLT_v2_OFFSET 3
+#define VBATT_LOW_v2_WORD 16
+#define VBATT_LOW_v2_OFFSET 0
+#define RECHARGE_VBATT_THR_v2_WORD 16
+#define RECHARGE_VBATT_THR_v2_OFFSET 1
+#define FLOAT_VOLT_v2_WORD 16
+#define FLOAT_VOLT_v2_OFFSET 2
+
+static int fg_decode_voltage_15b(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int val);
+static int fg_decode_value_16b(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int val);
+static int fg_decode_default(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int val);
+static int fg_decode_cc_soc(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int value);
+static void fg_encode_voltage(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int val_mv, u8 *buf);
+static void fg_encode_current(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int val_ma, u8 *buf);
+static void fg_encode_default(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int val, u8 *buf);
+
+static struct fg_irq_info fg_irqs[FG_IRQ_MAX];
+
+#define PARAM(_id, _addr_word, _addr_byte, _len, _num, _den, _offset, \
+ _enc, _dec) \
+ [FG_SRAM_##_id] = { \
+ .addr_word = _addr_word, \
+ .addr_byte = _addr_byte, \
+ .len = _len, \
+ .numrtr = _num, \
+ .denmtr = _den, \
+ .offset = _offset, \
+ .encode = _enc, \
+ .decode = _dec, \
+ } \
+
+static struct fg_sram_param pmi8998_v1_sram_params[] = {
+ PARAM(BATT_SOC, BATT_SOC_WORD, BATT_SOC_OFFSET, 4, 1, 1, 0, NULL,
+ fg_decode_default),
+ PARAM(FULL_SOC, FULL_SOC_WORD, FULL_SOC_OFFSET, 2, 1, 1, 0, NULL,
+ fg_decode_default),
+ PARAM(VOLTAGE_PRED, VOLTAGE_PRED_WORD, VOLTAGE_PRED_OFFSET, 2, 1000,
+ 244141, 0, NULL, fg_decode_voltage_15b),
+ PARAM(OCV, OCV_WORD, OCV_OFFSET, 2, 1000, 244141, 0, NULL,
+ fg_decode_voltage_15b),
+ PARAM(ESR, ESR_WORD, ESR_OFFSET, 2, 1000, 244141, 0, fg_encode_default,
+ fg_decode_value_16b),
+ PARAM(RSLOW, RSLOW_WORD, RSLOW_OFFSET, 2, 1000, 244141, 0, NULL,
+ fg_decode_value_16b),
+ PARAM(ALG_FLAGS, ALG_FLAGS_WORD, ALG_FLAGS_OFFSET, 1, 1, 1, 0, NULL,
+ fg_decode_default),
+ PARAM(CC_SOC, CC_SOC_WORD, CC_SOC_OFFSET, 4, 1, 1, 0, NULL,
+ fg_decode_cc_soc),
+ PARAM(CC_SOC_SW, CC_SOC_SW_WORD, CC_SOC_SW_OFFSET, 4, 1, 1, 0, NULL,
+ fg_decode_cc_soc),
+ PARAM(ACT_BATT_CAP, ACT_BATT_CAP_BKUP_WORD, ACT_BATT_CAP_BKUP_OFFSET, 2,
+ 1, 1, 0, NULL, fg_decode_default),
+ /* Entries below here are configurable during initialization */
+ PARAM(CUTOFF_VOLT, CUTOFF_VOLT_WORD, CUTOFF_VOLT_OFFSET, 2, 1000000,
+ 244141, 0, fg_encode_voltage, NULL),
+ PARAM(EMPTY_VOLT, EMPTY_VOLT_WORD, EMPTY_VOLT_OFFSET, 1, 100000, 390625,
+ -2500, fg_encode_voltage, NULL),
+ PARAM(VBATT_LOW, VBATT_LOW_WORD, VBATT_LOW_OFFSET, 1, 100000, 390625,
+ -2500, fg_encode_voltage, NULL),
+ PARAM(VBATT_FULL, VBATT_FULL_WORD, VBATT_FULL_OFFSET, 2, 1000,
+ 244141, 0, fg_encode_voltage, fg_decode_voltage_15b),
+ PARAM(SYS_TERM_CURR, SYS_TERM_CURR_WORD, SYS_TERM_CURR_OFFSET, 3,
+ 1000000, 122070, 0, fg_encode_current, NULL),
+ PARAM(CHG_TERM_CURR, CHG_TERM_CURR_WORD, CHG_TERM_CURR_OFFSET, 1,
+ 100000, 390625, 0, fg_encode_current, NULL),
+ PARAM(DELTA_MSOC_THR, DELTA_MSOC_THR_WORD, DELTA_MSOC_THR_OFFSET, 1,
+ 2048, 100, 0, fg_encode_default, NULL),
+ PARAM(DELTA_BSOC_THR, DELTA_BSOC_THR_WORD, DELTA_BSOC_THR_OFFSET, 1,
+ 2048, 100, 0, fg_encode_default, NULL),
+ PARAM(RECHARGE_SOC_THR, RECHARGE_SOC_THR_WORD, RECHARGE_SOC_THR_OFFSET,
+ 1, 256, 100, 0, fg_encode_default, NULL),
+ PARAM(ESR_TIMER_DISCHG_MAX, ESR_TIMER_DISCHG_MAX_WORD,
+ ESR_TIMER_DISCHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default,
+ NULL),
+ PARAM(ESR_TIMER_DISCHG_INIT, ESR_TIMER_DISCHG_INIT_WORD,
+ ESR_TIMER_DISCHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default,
+ NULL),
+ PARAM(ESR_TIMER_CHG_MAX, ESR_TIMER_CHG_MAX_WORD,
+ ESR_TIMER_CHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL),
+ PARAM(ESR_TIMER_CHG_INIT, ESR_TIMER_CHG_INIT_WORD,
+ ESR_TIMER_CHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL),
+ PARAM(ESR_PULSE_THRESH, ESR_PULSE_THRESH_WORD, ESR_PULSE_THRESH_OFFSET,
+ 1, 100000, 390625, 0, fg_encode_default, NULL),
+ PARAM(KI_COEFF_MED_DISCHG, KI_COEFF_MED_DISCHG_WORD,
+ KI_COEFF_MED_DISCHG_OFFSET, 1, 1000, 244141, 0,
+ fg_encode_default, NULL),
+ PARAM(KI_COEFF_HI_DISCHG, KI_COEFF_HI_DISCHG_WORD,
+ KI_COEFF_HI_DISCHG_OFFSET, 1, 1000, 244141, 0,
+ fg_encode_default, NULL),
+ PARAM(KI_COEFF_FULL_SOC, KI_COEFF_FULL_SOC_WORD,
+ KI_COEFF_FULL_SOC_OFFSET, 1, 1000, 244141, 0,
+ fg_encode_default, NULL),
+ PARAM(ESR_TIGHT_FILTER, ESR_FILTER_WORD, ESR_UPD_TIGHT_OFFSET,
+ 1, 512, 1000000, 0, fg_encode_default, NULL),
+ PARAM(ESR_BROAD_FILTER, ESR_FILTER_WORD, ESR_UPD_BROAD_OFFSET,
+ 1, 512, 1000000, 0, fg_encode_default, NULL),
+ PARAM(SLOPE_LIMIT, SLOPE_LIMIT_WORD, SLOPE_LIMIT_OFFSET, 1, 8192, 1000,
+ 0, fg_encode_default, NULL),
+};
+
+static struct fg_sram_param pmi8998_v2_sram_params[] = {
+ PARAM(BATT_SOC, BATT_SOC_WORD, BATT_SOC_OFFSET, 4, 1, 1, 0, NULL,
+ fg_decode_default),
+ PARAM(FULL_SOC, FULL_SOC_WORD, FULL_SOC_OFFSET, 2, 1, 1, 0, NULL,
+ fg_decode_default),
+ PARAM(VOLTAGE_PRED, VOLTAGE_PRED_WORD, VOLTAGE_PRED_OFFSET, 2, 1000,
+ 244141, 0, NULL, fg_decode_voltage_15b),
+ PARAM(OCV, OCV_WORD, OCV_OFFSET, 2, 1000, 244141, 0, NULL,
+ fg_decode_voltage_15b),
+ PARAM(ESR, ESR_WORD, ESR_OFFSET, 2, 1000, 244141, 0, fg_encode_default,
+ fg_decode_value_16b),
+ PARAM(RSLOW, RSLOW_WORD, RSLOW_OFFSET, 2, 1000, 244141, 0, NULL,
+ fg_decode_value_16b),
+ PARAM(ALG_FLAGS, ALG_FLAGS_WORD, ALG_FLAGS_OFFSET, 1, 1, 1, 0, NULL,
+ fg_decode_default),
+ PARAM(CC_SOC, CC_SOC_WORD, CC_SOC_OFFSET, 4, 1, 1, 0, NULL,
+ fg_decode_cc_soc),
+ PARAM(CC_SOC_SW, CC_SOC_SW_WORD, CC_SOC_SW_OFFSET, 4, 1, 1, 0, NULL,
+ fg_decode_cc_soc),
+ PARAM(ACT_BATT_CAP, ACT_BATT_CAP_BKUP_WORD, ACT_BATT_CAP_BKUP_OFFSET, 2,
+ 1, 1, 0, NULL, fg_decode_default),
+ PARAM(TIMEBASE, KI_COEFF_MED_DISCHG_WORD, TIMEBASE_OFFSET, 2, 1000,
+ 61000, 0, fg_encode_default, NULL),
+ /* Entries below here are configurable during initialization */
+ PARAM(CUTOFF_VOLT, CUTOFF_VOLT_WORD, CUTOFF_VOLT_OFFSET, 2, 1000000,
+ 244141, 0, fg_encode_voltage, NULL),
+ PARAM(EMPTY_VOLT, EMPTY_VOLT_v2_WORD, EMPTY_VOLT_v2_OFFSET, 1, 1000,
+ 15625, -2000, fg_encode_voltage, NULL),
+ PARAM(VBATT_LOW, VBATT_LOW_v2_WORD, VBATT_LOW_v2_OFFSET, 1, 1000,
+ 15625, -2000, fg_encode_voltage, NULL),
+ PARAM(FLOAT_VOLT, FLOAT_VOLT_v2_WORD, FLOAT_VOLT_v2_OFFSET, 1, 1000,
+ 15625, -2000, fg_encode_voltage, NULL),
+ PARAM(VBATT_FULL, VBATT_FULL_WORD, VBATT_FULL_OFFSET, 2, 1000,
+ 244141, 0, fg_encode_voltage, fg_decode_voltage_15b),
+ PARAM(SYS_TERM_CURR, SYS_TERM_CURR_WORD, SYS_TERM_CURR_OFFSET, 3,
+ 1000000, 122070, 0, fg_encode_current, NULL),
+ PARAM(CHG_TERM_CURR, CHG_TERM_CURR_v2_WORD, CHG_TERM_CURR_v2_OFFSET, 1,
+ 100000, 390625, 0, fg_encode_current, NULL),
+ PARAM(CHG_TERM_BASE_CURR, CHG_TERM_CURR_v2_WORD,
+ CHG_TERM_BASE_CURR_v2_OFFSET, 1, 1024, 1000, 0,
+ fg_encode_current, NULL),
+ PARAM(DELTA_MSOC_THR, DELTA_MSOC_THR_v2_WORD, DELTA_MSOC_THR_v2_OFFSET,
+ 1, 2048, 100, 0, fg_encode_default, NULL),
+ PARAM(DELTA_BSOC_THR, DELTA_BSOC_THR_v2_WORD, DELTA_BSOC_THR_v2_OFFSET,
+ 1, 2048, 100, 0, fg_encode_default, NULL),
+ PARAM(RECHARGE_SOC_THR, RECHARGE_SOC_THR_v2_WORD,
+ RECHARGE_SOC_THR_v2_OFFSET, 1, 256, 100, 0, fg_encode_default,
+ NULL),
+ PARAM(RECHARGE_VBATT_THR, RECHARGE_VBATT_THR_v2_WORD,
+ RECHARGE_VBATT_THR_v2_OFFSET, 1, 1000, 15625, -2000,
+ fg_encode_voltage, NULL),
+ PARAM(ESR_TIMER_DISCHG_MAX, ESR_TIMER_DISCHG_MAX_WORD,
+ ESR_TIMER_DISCHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default,
+ NULL),
+ PARAM(ESR_TIMER_DISCHG_INIT, ESR_TIMER_DISCHG_INIT_WORD,
+ ESR_TIMER_DISCHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default,
+ NULL),
+ PARAM(ESR_TIMER_CHG_MAX, ESR_TIMER_CHG_MAX_WORD,
+ ESR_TIMER_CHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL),
+ PARAM(ESR_TIMER_CHG_INIT, ESR_TIMER_CHG_INIT_WORD,
+ ESR_TIMER_CHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL),
+ PARAM(ESR_PULSE_THRESH, ESR_PULSE_THRESH_WORD, ESR_PULSE_THRESH_OFFSET,
+ 1, 100000, 390625, 0, fg_encode_default, NULL),
+ PARAM(KI_COEFF_MED_DISCHG, KI_COEFF_MED_DISCHG_v2_WORD,
+ KI_COEFF_MED_DISCHG_v2_OFFSET, 1, 1000, 244141, 0,
+ fg_encode_default, NULL),
+ PARAM(KI_COEFF_HI_DISCHG, KI_COEFF_HI_DISCHG_v2_WORD,
+ KI_COEFF_HI_DISCHG_v2_OFFSET, 1, 1000, 244141, 0,
+ fg_encode_default, NULL),
+ PARAM(KI_COEFF_FULL_SOC, KI_COEFF_FULL_SOC_WORD,
+ KI_COEFF_FULL_SOC_OFFSET, 1, 1000, 244141, 0,
+ fg_encode_default, NULL),
+ PARAM(ESR_TIGHT_FILTER, ESR_FILTER_WORD, ESR_UPD_TIGHT_OFFSET,
+ 1, 512, 1000000, 0, fg_encode_default, NULL),
+ PARAM(ESR_BROAD_FILTER, ESR_FILTER_WORD, ESR_UPD_BROAD_OFFSET,
+ 1, 512, 1000000, 0, fg_encode_default, NULL),
+ PARAM(SLOPE_LIMIT, SLOPE_LIMIT_WORD, SLOPE_LIMIT_OFFSET, 1, 8192, 1000,
+ 0, fg_encode_default, NULL),
+};
+
+static struct fg_alg_flag pmi8998_v1_alg_flags[] = {
+ [ALG_FLAG_SOC_LT_OTG_MIN] = {
+ .name = "SOC_LT_OTG_MIN",
+ .bit = BIT(0),
+ },
+ [ALG_FLAG_SOC_LT_RECHARGE] = {
+ .name = "SOC_LT_RECHARGE",
+ .bit = BIT(1),
+ },
+ [ALG_FLAG_IBATT_LT_ITERM] = {
+ .name = "IBATT_LT_ITERM",
+ .bit = BIT(2),
+ },
+ [ALG_FLAG_IBATT_GT_HPM] = {
+ .name = "IBATT_GT_HPM",
+ .bit = BIT(3),
+ },
+ [ALG_FLAG_IBATT_GT_UPM] = {
+ .name = "IBATT_GT_UPM",
+ .bit = BIT(4),
+ },
+ [ALG_FLAG_VBATT_LT_RECHARGE] = {
+ .name = "VBATT_LT_RECHARGE",
+ .bit = BIT(5),
+ },
+ [ALG_FLAG_VBATT_GT_VFLOAT] = {
+ .invalid = true,
+ },
+};
+
+static struct fg_alg_flag pmi8998_v2_alg_flags[] = {
+ [ALG_FLAG_SOC_LT_OTG_MIN] = {
+ .name = "SOC_LT_OTG_MIN",
+ .bit = BIT(0),
+ },
+ [ALG_FLAG_SOC_LT_RECHARGE] = {
+ .name = "SOC_LT_RECHARGE",
+ .bit = BIT(1),
+ },
+ [ALG_FLAG_IBATT_LT_ITERM] = {
+ .name = "IBATT_LT_ITERM",
+ .bit = BIT(2),
+ },
+ [ALG_FLAG_IBATT_GT_HPM] = {
+ .name = "IBATT_GT_HPM",
+ .bit = BIT(4),
+ },
+ [ALG_FLAG_IBATT_GT_UPM] = {
+ .name = "IBATT_GT_UPM",
+ .bit = BIT(5),
+ },
+ [ALG_FLAG_VBATT_LT_RECHARGE] = {
+ .name = "VBATT_LT_RECHARGE",
+ .bit = BIT(6),
+ },
+ [ALG_FLAG_VBATT_GT_VFLOAT] = {
+ .name = "VBATT_GT_VFLOAT",
+ .bit = BIT(7),
+ },
+};
+
+static int fg_gen3_debug_mask;
+module_param_named(
+ debug_mask, fg_gen3_debug_mask, int, S_IRUSR | S_IWUSR
+);
+
+static bool fg_profile_dump;
+module_param_named(
+ profile_dump, fg_profile_dump, bool, S_IRUSR | S_IWUSR
+);
+
+static int fg_sram_dump_period_ms = 20000;
+module_param_named(
+ sram_dump_period_ms, fg_sram_dump_period_ms, int, S_IRUSR | S_IWUSR
+);
+
+static int fg_restart;
+static bool fg_sram_dump;
+
+/* All getters HERE */
+
+#define VOLTAGE_15BIT_MASK GENMASK(14, 0)
+static int fg_decode_voltage_15b(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int value)
+{
+ value &= VOLTAGE_15BIT_MASK;
+ sp[id].value = div_u64((u64)value * sp[id].denmtr, sp[id].numrtr);
+ pr_debug("id: %d raw value: %x decoded value: %x\n", id, value,
+ sp[id].value);
+ return sp[id].value;
+}
+
+static int fg_decode_cc_soc(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int value)
+{
+ sp[id].value = div_s64((s64)value * sp[id].denmtr, sp[id].numrtr);
+ sp[id].value = sign_extend32(sp[id].value, 31);
+ pr_debug("id: %d raw value: %x decoded value: %x\n", id, value,
+ sp[id].value);
+ return sp[id].value;
+}
+
+static int fg_decode_value_16b(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int value)
+{
+ sp[id].value = div_u64((u64)(u16)value * sp[id].denmtr, sp[id].numrtr);
+ pr_debug("id: %d raw value: %x decoded value: %x\n", id, value,
+ sp[id].value);
+ return sp[id].value;
+}
+
+static int fg_decode_default(struct fg_sram_param *sp, enum fg_sram_param_id id,
+ int value)
+{
+ sp[id].value = value;
+ return sp[id].value;
+}
+
+static int fg_decode(struct fg_sram_param *sp, enum fg_sram_param_id id,
+ int value)
+{
+ if (!sp[id].decode) {
+ pr_err("No decoding function for parameter %d\n", id);
+ return -EINVAL;
+ }
+
+ return sp[id].decode(sp, id, value);
+}
+
+static void fg_encode_voltage(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int val_mv, u8 *buf)
+{
+ int i, mask = 0xff;
+ int64_t temp;
+
+ val_mv += sp[id].offset;
+ temp = (int64_t)div_u64((u64)val_mv * sp[id].numrtr, sp[id].denmtr);
+ pr_debug("temp: %llx id: %d, val_mv: %d, buf: [ ", temp, id, val_mv);
+ for (i = 0; i < sp[id].len; i++) {
+ buf[i] = temp & mask;
+ temp >>= 8;
+ pr_debug("%x ", buf[i]);
+ }
+ pr_debug("]\n");
+}
+
+static void fg_encode_current(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int val_ma, u8 *buf)
+{
+ int i, mask = 0xff;
+ int64_t temp;
+ s64 current_ma;
+
+ current_ma = val_ma;
+ temp = (int64_t)div_s64(current_ma * sp[id].numrtr, sp[id].denmtr);
+ pr_debug("temp: %llx id: %d, val: %d, buf: [ ", temp, id, val_ma);
+ for (i = 0; i < sp[id].len; i++) {
+ buf[i] = temp & mask;
+ temp >>= 8;
+ pr_debug("%x ", buf[i]);
+ }
+ pr_debug("]\n");
+}
+
+static void fg_encode_default(struct fg_sram_param *sp,
+ enum fg_sram_param_id id, int val, u8 *buf)
+{
+ int i, mask = 0xff;
+ int64_t temp;
+
+ temp = (int64_t)div_s64((s64)val * sp[id].numrtr, sp[id].denmtr);
+ pr_debug("temp: %llx id: %d, val: %d, buf: [ ", temp, id, val);
+ for (i = 0; i < sp[id].len; i++) {
+ buf[i] = temp & mask;
+ temp >>= 8;
+ pr_debug("%x ", buf[i]);
+ }
+ pr_debug("]\n");
+}
+
+static void fg_encode(struct fg_sram_param *sp, enum fg_sram_param_id id,
+ int val, u8 *buf)
+{
+ if (!sp[id].encode) {
+ pr_err("No encoding function for parameter %d\n", id);
+ return;
+ }
+
+ sp[id].encode(sp, id, val, buf);
+}
+
+/*
+ * Please make sure *_sram_params table has the entry for the parameter
+ * obtained through this function. In addition to address, offset,
+ * length from where this SRAM parameter is read, a decode function
+ * need to be specified.
+ */
+static int fg_get_sram_prop(struct fg_chip *chip, enum fg_sram_param_id id,
+ int *val)
+{
+ int temp, rc, i;
+ u8 buf[4];
+
+ if (id < 0 || id > FG_SRAM_MAX || chip->sp[id].len > sizeof(buf))
+ return -EINVAL;
+
+ if (chip->battery_missing)
+ return -ENODATA;
+
+ rc = fg_sram_read(chip, chip->sp[id].addr_word, chip->sp[id].addr_byte,
+ buf, chip->sp[id].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error reading address 0x%04x[%d] rc=%d\n",
+ chip->sp[id].addr_word, chip->sp[id].addr_byte, rc);
+ return rc;
+ }
+
+ for (i = 0, temp = 0; i < chip->sp[id].len; i++)
+ temp |= buf[i] << (8 * i);
+
+ *val = fg_decode(chip->sp, id, temp);
+ return 0;
+}
+
+#define CC_SOC_30BIT GENMASK(29, 0)
+static int fg_get_charge_raw(struct fg_chip *chip, int *val)
+{
+ int rc, cc_soc;
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC, &cc_soc);
+ if (rc < 0) {
+ pr_err("Error in getting CC_SOC, rc=%d\n", rc);
+ return rc;
+ }
+
+ *val = div_s64(cc_soc * chip->cl.nom_cap_uah, CC_SOC_30BIT);
+ return 0;
+}
+
+static int fg_get_charge_counter(struct fg_chip *chip, int *val)
+{
+ int rc, cc_soc;
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC_SW, &cc_soc);
+ if (rc < 0) {
+ pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc);
+ return rc;
+ }
+
+ *val = div_s64(cc_soc * chip->cl.learned_cc_uah, CC_SOC_30BIT);
+ return 0;
+}
+
+#define BATT_TEMP_NUMR 1
+#define BATT_TEMP_DENR 1
+static int fg_get_battery_temp(struct fg_chip *chip, int *val)
+{
+ int rc = 0, temp;
+ u8 buf[2];
+
+ rc = fg_read(chip, BATT_INFO_BATT_TEMP_LSB(chip), buf, 2);
+ if (rc < 0) {
+ pr_err("failed to read addr=0x%04x, rc=%d\n",
+ BATT_INFO_BATT_TEMP_LSB(chip), rc);
+ return rc;
+ }
+
+ temp = ((buf[1] & BATT_TEMP_MSB_MASK) << 8) |
+ (buf[0] & BATT_TEMP_LSB_MASK);
+ temp = DIV_ROUND_CLOSEST(temp, 4);
+
+ /* Value is in Kelvin; Convert it to deciDegC */
+ temp = (temp - 273) * 10;
+ *val = temp;
+ return 0;
+}
+
+static int fg_get_battery_resistance(struct fg_chip *chip, int *val)
+{
+ int rc, esr_uohms, rslow_uohms;
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_ESR, &esr_uohms);
+ if (rc < 0) {
+ pr_err("failed to get ESR, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_RSLOW, &rslow_uohms);
+ if (rc < 0) {
+ pr_err("failed to get Rslow, rc=%d\n", rc);
+ return rc;
+ }
+
+ *val = esr_uohms + rslow_uohms;
+ return 0;
+}
+
+#define BATT_CURRENT_NUMR 488281
+#define BATT_CURRENT_DENR 1000
+static int fg_get_battery_current(struct fg_chip *chip, int *val)
+{
+ int rc = 0;
+ int64_t temp = 0;
+ u8 buf[2];
+
+ rc = fg_read(chip, BATT_INFO_IBATT_LSB(chip), buf, 2);
+ if (rc < 0) {
+ pr_err("failed to read addr=0x%04x, rc=%d\n",
+ BATT_INFO_IBATT_LSB(chip), rc);
+ return rc;
+ }
+
+ if (chip->wa_flags & PMI8998_V1_REV_WA)
+ temp = buf[0] << 8 | buf[1];
+ else
+ temp = buf[1] << 8 | buf[0];
+
+ pr_debug("buf: %x %x temp: %llx\n", buf[0], buf[1], temp);
+ /* Sign bit is bit 15 */
+ temp = twos_compliment_extend(temp, 15);
+ *val = div_s64((s64)temp * BATT_CURRENT_NUMR, BATT_CURRENT_DENR);
+ return 0;
+}
+
+#define BATT_VOLTAGE_NUMR 122070
+#define BATT_VOLTAGE_DENR 1000
+static int fg_get_battery_voltage(struct fg_chip *chip, int *val)
+{
+ int rc = 0;
+ u16 temp = 0;
+ u8 buf[2];
+
+ rc = fg_read(chip, BATT_INFO_VBATT_LSB(chip), buf, 2);
+ if (rc < 0) {
+ pr_err("failed to read addr=0x%04x, rc=%d\n",
+ BATT_INFO_VBATT_LSB(chip), rc);
+ return rc;
+ }
+
+ if (chip->wa_flags & PMI8998_V1_REV_WA)
+ temp = buf[0] << 8 | buf[1];
+ else
+ temp = buf[1] << 8 | buf[0];
+
+ pr_debug("buf: %x %x temp: %x\n", buf[0], buf[1], temp);
+ *val = div_u64((u64)temp * BATT_VOLTAGE_NUMR, BATT_VOLTAGE_DENR);
+ return 0;
+}
+
+#define MAX_TRIES_SOC 5
+static int fg_get_msoc_raw(struct fg_chip *chip, int *val)
+{
+ u8 cap[2];
+ int rc, tries = 0;
+
+ while (tries < MAX_TRIES_SOC) {
+ rc = fg_read(chip, BATT_SOC_FG_MONOTONIC_SOC(chip), cap, 2);
+ if (rc < 0) {
+ pr_err("failed to read addr=0x%04x, rc=%d\n",
+ BATT_SOC_FG_MONOTONIC_SOC(chip), rc);
+ return rc;
+ }
+
+ if (cap[0] == cap[1])
+ break;
+
+ tries++;
+ }
+
+ if (tries == MAX_TRIES_SOC) {
+ pr_err("shadow registers do not match\n");
+ return -EINVAL;
+ }
+
+ fg_dbg(chip, FG_POWER_SUPPLY, "raw: 0x%02x\n", cap[0]);
+ *val = cap[0];
+ return 0;
+}
+
+#define FULL_CAPACITY 100
+#define FULL_SOC_RAW 255
+static int fg_get_msoc(struct fg_chip *chip, int *msoc)
+{
+ int rc;
+
+ rc = fg_get_msoc_raw(chip, msoc);
+ if (rc < 0)
+ return rc;
+
+ *msoc = DIV_ROUND_CLOSEST(*msoc * FULL_CAPACITY, FULL_SOC_RAW);
+ return 0;
+}
+
+static bool is_batt_empty(struct fg_chip *chip)
+{
+ u8 status;
+ int rc, vbatt_uv, msoc;
+
+ rc = fg_read(chip, BATT_SOC_INT_RT_STS(chip), &status, 1);
+ if (rc < 0) {
+ pr_err("failed to read addr=0x%04x, rc=%d\n",
+ BATT_SOC_INT_RT_STS(chip), rc);
+ return false;
+ }
+
+ if (!(status & MSOC_EMPTY_BIT))
+ return false;
+
+ rc = fg_get_battery_voltage(chip, &vbatt_uv);
+ if (rc < 0) {
+ pr_err("failed to get battery voltage, rc=%d\n", rc);
+ return false;
+ }
+
+ rc = fg_get_msoc(chip, &msoc);
+ if (!rc)
+ pr_warn("batt_soc_rt_sts: %x vbatt: %d uV msoc:%d\n", status,
+ vbatt_uv, msoc);
+
+ return ((vbatt_uv < chip->dt.cutoff_volt_mv * 1000) ? true : false);
+}
+
+static int fg_get_debug_batt_id(struct fg_chip *chip, int *batt_id)
+{
+ int rc;
+ u64 temp;
+ u8 buf[2];
+
+ rc = fg_read(chip, ADC_RR_FAKE_BATT_LOW_LSB(chip), buf, 2);
+ if (rc < 0) {
+ pr_err("failed to read addr=0x%04x, rc=%d\n",
+ ADC_RR_FAKE_BATT_LOW_LSB(chip), rc);
+ return rc;
+ }
+
+ /*
+ * Fake battery threshold is encoded in the following format.
+ * Threshold (code) = (battery_id in Ohms) * 0.00015 * 2^10 / 2.5
+ */
+ temp = (buf[1] << 8 | buf[0]) * 2500000;
+ do_div(temp, 150 * 1024);
+ batt_id[0] = temp;
+ rc = fg_read(chip, ADC_RR_FAKE_BATT_HIGH_LSB(chip), buf, 2);
+ if (rc < 0) {
+ pr_err("failed to read addr=0x%04x, rc=%d\n",
+ ADC_RR_FAKE_BATT_HIGH_LSB(chip), rc);
+ return rc;
+ }
+
+ temp = (buf[1] << 8 | buf[0]) * 2500000;
+ do_div(temp, 150 * 1024);
+ batt_id[1] = temp;
+ pr_debug("debug batt_id range: [%d %d]\n", batt_id[0], batt_id[1]);
+ return 0;
+}
+
+static bool is_debug_batt_id(struct fg_chip *chip)
+{
+ int debug_batt_id[2], rc;
+
+ if (!chip->batt_id_ohms)
+ return false;
+
+ rc = fg_get_debug_batt_id(chip, debug_batt_id);
+ if (rc < 0) {
+ pr_err("Failed to get debug batt_id, rc=%d\n", rc);
+ return false;
+ }
+
+ if (is_between(debug_batt_id[0], debug_batt_id[1],
+ chip->batt_id_ohms)) {
+ fg_dbg(chip, FG_POWER_SUPPLY, "Debug battery id: %dohms\n",
+ chip->batt_id_ohms);
+ return true;
+ }
+
+ return false;
+}
+
+#define DEBUG_BATT_SOC 67
+#define BATT_MISS_SOC 50
+#define EMPTY_SOC 0
+static int fg_get_prop_capacity(struct fg_chip *chip, int *val)
+{
+ int rc, msoc;
+
+ if (is_debug_batt_id(chip)) {
+ *val = DEBUG_BATT_SOC;
+ return 0;
+ }
+
+ if (chip->fg_restarting) {
+ *val = chip->last_soc;
+ return 0;
+ }
+
+ if (chip->battery_missing) {
+ *val = BATT_MISS_SOC;
+ return 0;
+ }
+
+ if (is_batt_empty(chip)) {
+ *val = EMPTY_SOC;
+ return 0;
+ }
+
+ if (chip->charge_full) {
+ *val = FULL_CAPACITY;
+ return 0;
+ }
+
+ rc = fg_get_msoc(chip, &msoc);
+ if (rc < 0)
+ return rc;
+
+ if (chip->delta_soc > 0)
+ *val = chip->maint_soc;
+ else
+ *val = msoc;
+ return 0;
+}
+
+#define DEFAULT_BATT_TYPE "Unknown Battery"
+#define MISSING_BATT_TYPE "Missing Battery"
+#define LOADING_BATT_TYPE "Loading Battery"
+static const char *fg_get_battery_type(struct fg_chip *chip)
+{
+ if (chip->battery_missing)
+ return MISSING_BATT_TYPE;
+
+ if (chip->bp.batt_type_str) {
+ if (chip->profile_loaded)
+ return chip->bp.batt_type_str;
+ else if (chip->profile_available)
+ return LOADING_BATT_TYPE;
+ }
+
+ return DEFAULT_BATT_TYPE;
+}
+
+static int fg_batt_missing_config(struct fg_chip *chip, bool enable)
+{
+ int rc;
+
+ rc = fg_masked_write(chip, BATT_INFO_BATT_MISS_CFG(chip),
+ BM_FROM_BATT_ID_BIT, enable ? BM_FROM_BATT_ID_BIT : 0);
+ if (rc < 0)
+ pr_err("Error in writing to %04x, rc=%d\n",
+ BATT_INFO_BATT_MISS_CFG(chip), rc);
+ return rc;
+}
+
+static int fg_get_batt_id(struct fg_chip *chip)
+{
+ int rc, ret, batt_id = 0;
+
+ if (!chip->batt_id_chan)
+ return -EINVAL;
+
+ rc = fg_batt_missing_config(chip, false);
+ if (rc < 0) {
+ pr_err("Error in disabling BMD, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = iio_read_channel_processed(chip->batt_id_chan, &batt_id);
+ if (rc < 0) {
+ pr_err("Error in reading batt_id channel, rc:%d\n", rc);
+ goto out;
+ }
+
+ /* Wait for 200ms before enabling BMD again */
+ msleep(200);
+
+ fg_dbg(chip, FG_STATUS, "batt_id: %d\n", batt_id);
+ chip->batt_id_ohms = batt_id;
+out:
+ ret = fg_batt_missing_config(chip, true);
+ if (ret < 0) {
+ pr_err("Error in enabling BMD, ret=%d\n", ret);
+ return ret;
+ }
+
+ vote(chip->batt_miss_irq_en_votable, BATT_MISS_IRQ_VOTER, true, 0);
+ return rc;
+}
+
+static int fg_get_batt_profile(struct fg_chip *chip)
+{
+ struct device_node *node = chip->dev->of_node;
+ struct device_node *batt_node, *profile_node;
+ const char *data;
+ int rc, len;
+
+ batt_node = of_find_node_by_name(node, "qcom,battery-data");
+ if (!batt_node) {
+ pr_err("Batterydata not available\n");
+ return -ENXIO;
+ }
+
+ profile_node = of_batterydata_get_best_profile(batt_node,
+ chip->batt_id_ohms / 1000, NULL);
+ if (IS_ERR(profile_node))
+ return PTR_ERR(profile_node);
+
+ if (!profile_node) {
+ pr_err("couldn't find profile handle\n");
+ return -ENODATA;
+ }
+
+ rc = of_property_read_string(profile_node, "qcom,battery-type",
+ &chip->bp.batt_type_str);
+ if (rc < 0) {
+ pr_err("battery type unavailable, rc:%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv",
+ &chip->bp.float_volt_uv);
+ if (rc < 0) {
+ pr_err("battery float voltage unavailable, rc:%d\n", rc);
+ chip->bp.float_volt_uv = -EINVAL;
+ }
+
+ rc = of_property_read_u32(profile_node, "qcom,fastchg-current-ma",
+ &chip->bp.fastchg_curr_ma);
+ if (rc < 0) {
+ pr_err("battery fastchg current unavailable, rc:%d\n", rc);
+ chip->bp.fastchg_curr_ma = -EINVAL;
+ }
+
+ rc = of_property_read_u32(profile_node, "qcom,fg-cc-cv-threshold-mv",
+ &chip->bp.vbatt_full_mv);
+ if (rc < 0) {
+ pr_err("battery cc_cv threshold unavailable, rc:%d\n", rc);
+ chip->bp.vbatt_full_mv = -EINVAL;
+ }
+
+ data = of_get_property(profile_node, "qcom,fg-profile-data", &len);
+ if (!data) {
+ pr_err("No profile data available\n");
+ return -ENODATA;
+ }
+
+ if (len != PROFILE_LEN) {
+ pr_err("battery profile incorrect size: %d\n", len);
+ return -EINVAL;
+ }
+
+ chip->profile_available = true;
+ memcpy(chip->batt_profile, data, len);
+
+ return 0;
+}
+
+static inline void get_temp_setpoint(int threshold, u8 *val)
+{
+ /* Resolution is 0.5C. Base is -30C. */
+ *val = DIV_ROUND_CLOSEST((threshold + 30) * 10, 5);
+}
+
+static inline void get_batt_temp_delta(int delta, u8 *val)
+{
+ switch (delta) {
+ case 2:
+ *val = BTEMP_DELTA_2K;
+ break;
+ case 4:
+ *val = BTEMP_DELTA_4K;
+ break;
+ case 6:
+ *val = BTEMP_DELTA_6K;
+ break;
+ case 10:
+ *val = BTEMP_DELTA_10K;
+ break;
+ default:
+ *val = BTEMP_DELTA_2K;
+ break;
+ };
+}
+
+static inline void get_esr_meas_current(int curr_ma, u8 *val)
+{
+ switch (curr_ma) {
+ case 60:
+ *val = ESR_MEAS_CUR_60MA;
+ break;
+ case 120:
+ *val = ESR_MEAS_CUR_120MA;
+ break;
+ case 180:
+ *val = ESR_MEAS_CUR_180MA;
+ break;
+ case 240:
+ *val = ESR_MEAS_CUR_240MA;
+ break;
+ default:
+ *val = ESR_MEAS_CUR_120MA;
+ break;
+ };
+
+ *val <<= ESR_PULL_DOWN_IVAL_SHIFT;
+}
+
+static int fg_set_esr_timer(struct fg_chip *chip, int cycles_init,
+ int cycles_max, bool charging, int flags)
+{
+ u8 buf[2];
+ int rc, timer_max, timer_init;
+
+ if (cycles_init < 0 || cycles_max < 0)
+ return 0;
+
+ if (charging) {
+ timer_max = FG_SRAM_ESR_TIMER_CHG_MAX;
+ timer_init = FG_SRAM_ESR_TIMER_CHG_INIT;
+ } else {
+ timer_max = FG_SRAM_ESR_TIMER_DISCHG_MAX;
+ timer_init = FG_SRAM_ESR_TIMER_DISCHG_INIT;
+ }
+
+ fg_encode(chip->sp, timer_max, cycles_max, buf);
+ rc = fg_sram_write(chip,
+ chip->sp[timer_max].addr_word,
+ chip->sp[timer_max].addr_byte, buf,
+ chip->sp[timer_max].len, flags);
+ if (rc < 0) {
+ pr_err("Error in writing esr_timer_dischg_max, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ fg_encode(chip->sp, timer_init, cycles_init, buf);
+ rc = fg_sram_write(chip,
+ chip->sp[timer_init].addr_word,
+ chip->sp[timer_init].addr_byte, buf,
+ chip->sp[timer_init].len, flags);
+ if (rc < 0) {
+ pr_err("Error in writing esr_timer_dischg_init, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ fg_dbg(chip, FG_STATUS, "esr_%s_timer set to %d/%d\n",
+ charging ? "charging" : "discharging", cycles_init, cycles_max);
+ return 0;
+}
+
+/* Other functions HERE */
+
+static void fg_notify_charger(struct fg_chip *chip)
+{
+ union power_supply_propval prop = {0, };
+ int rc;
+
+ if (!chip->batt_psy)
+ return;
+
+ if (!chip->profile_available)
+ return;
+
+ prop.intval = chip->bp.float_volt_uv;
+ rc = power_supply_set_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop);
+ if (rc < 0) {
+ pr_err("Error in setting voltage_max property on batt_psy, rc=%d\n",
+ rc);
+ return;
+ }
+
+ prop.intval = chip->bp.fastchg_curr_ma * 1000;
+ rc = power_supply_set_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &prop);
+ if (rc < 0) {
+ pr_err("Error in setting constant_charge_current_max property on batt_psy, rc=%d\n",
+ rc);
+ return;
+ }
+
+ fg_dbg(chip, FG_STATUS, "Notified charger on float voltage and FCC\n");
+}
+
+static int fg_batt_miss_irq_en_cb(struct votable *votable, void *data,
+ int enable, const char *client)
+{
+ struct fg_chip *chip = data;
+
+ if (!chip->irqs[BATT_MISSING_IRQ].irq)
+ return 0;
+
+ if (enable) {
+ enable_irq(chip->irqs[BATT_MISSING_IRQ].irq);
+ enable_irq_wake(chip->irqs[BATT_MISSING_IRQ].irq);
+ } else {
+ disable_irq_wake(chip->irqs[BATT_MISSING_IRQ].irq);
+ disable_irq(chip->irqs[BATT_MISSING_IRQ].irq);
+ }
+
+ return 0;
+}
+
+static int fg_delta_bsoc_irq_en_cb(struct votable *votable, void *data,
+ int enable, const char *client)
+{
+ struct fg_chip *chip = data;
+
+ if (!chip->irqs[BSOC_DELTA_IRQ].irq)
+ return 0;
+
+ if (enable) {
+ enable_irq(chip->irqs[BSOC_DELTA_IRQ].irq);
+ enable_irq_wake(chip->irqs[BSOC_DELTA_IRQ].irq);
+ } else {
+ disable_irq_wake(chip->irqs[BSOC_DELTA_IRQ].irq);
+ disable_irq(chip->irqs[BSOC_DELTA_IRQ].irq);
+ }
+
+ return 0;
+}
+
+static int fg_awake_cb(struct votable *votable, void *data, int awake,
+ const char *client)
+{
+ struct fg_chip *chip = data;
+
+ if (awake)
+ pm_stay_awake(chip->dev);
+ else
+ pm_relax(chip->dev);
+
+ pr_debug("client: %s awake: %d\n", client, awake);
+ return 0;
+}
+
+static bool batt_psy_initialized(struct fg_chip *chip)
+{
+ if (chip->batt_psy)
+ return true;
+
+ chip->batt_psy = power_supply_get_by_name("battery");
+ if (!chip->batt_psy)
+ return false;
+
+ /* batt_psy is initialized, set the fcc and fv */
+ fg_notify_charger(chip);
+
+ return true;
+}
+
+static bool usb_psy_initialized(struct fg_chip *chip)
+{
+ if (chip->usb_psy)
+ return true;
+
+ chip->usb_psy = power_supply_get_by_name("usb");
+ if (!chip->usb_psy)
+ return false;
+
+ return true;
+}
+
+static bool pc_port_psy_initialized(struct fg_chip *chip)
+{
+ if (chip->pc_port_psy)
+ return true;
+
+ chip->pc_port_psy = power_supply_get_by_name("pc_port");
+ if (!chip->pc_port_psy)
+ return false;
+
+ return true;
+}
+
+static bool dc_psy_initialized(struct fg_chip *chip)
+{
+ if (chip->dc_psy)
+ return true;
+
+ chip->dc_psy = power_supply_get_by_name("dc");
+ if (!chip->dc_psy)
+ return false;
+
+ return true;
+}
+
+static bool is_parallel_charger_available(struct fg_chip *chip)
+{
+ if (!chip->parallel_psy)
+ chip->parallel_psy = power_supply_get_by_name("parallel");
+
+ if (!chip->parallel_psy)
+ return false;
+
+ return true;
+}
+
+static int fg_save_learned_cap_to_sram(struct fg_chip *chip)
+{
+ int16_t cc_mah;
+ int rc;
+
+ if (chip->battery_missing || !chip->cl.learned_cc_uah)
+ return -EPERM;
+
+ cc_mah = div64_s64(chip->cl.learned_cc_uah, 1000);
+ /* Write to a backup register to use across reboot */
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_ACT_BATT_CAP].addr_word,
+ chip->sp[FG_SRAM_ACT_BATT_CAP].addr_byte, (u8 *)&cc_mah,
+ chip->sp[FG_SRAM_ACT_BATT_CAP].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing act_batt_cap_bkup, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Write to actual capacity register for coulomb counter operation */
+ rc = fg_sram_write(chip, ACT_BATT_CAP_WORD, ACT_BATT_CAP_OFFSET,
+ (u8 *)&cc_mah, chip->sp[FG_SRAM_ACT_BATT_CAP].len,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing act_batt_cap, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_dbg(chip, FG_CAP_LEARN, "learned capacity %llduah/%dmah stored\n",
+ chip->cl.learned_cc_uah, cc_mah);
+ return 0;
+}
+
+#define CAPACITY_DELTA_DECIPCT 500
+static int fg_load_learned_cap_from_sram(struct fg_chip *chip)
+{
+ int rc, act_cap_mah;
+ int64_t delta_cc_uah, pct_nom_cap_uah;
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_ACT_BATT_CAP, &act_cap_mah);
+ if (rc < 0) {
+ pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->cl.learned_cc_uah = act_cap_mah * 1000;
+
+ if (chip->cl.learned_cc_uah != chip->cl.nom_cap_uah) {
+ if (chip->cl.learned_cc_uah == 0)
+ chip->cl.learned_cc_uah = chip->cl.nom_cap_uah;
+
+ delta_cc_uah = abs(chip->cl.learned_cc_uah -
+ chip->cl.nom_cap_uah);
+ pct_nom_cap_uah = div64_s64((int64_t)chip->cl.nom_cap_uah *
+ CAPACITY_DELTA_DECIPCT, 1000);
+ /*
+ * If the learned capacity is out of range by 50% from the
+ * nominal capacity, then overwrite the learned capacity with
+ * the nominal capacity.
+ */
+ if (chip->cl.nom_cap_uah && delta_cc_uah > pct_nom_cap_uah) {
+ fg_dbg(chip, FG_CAP_LEARN, "learned_cc_uah: %lld is higher than expected, capping it to nominal: %lld\n",
+ chip->cl.learned_cc_uah, chip->cl.nom_cap_uah);
+ chip->cl.learned_cc_uah = chip->cl.nom_cap_uah;
+ }
+
+ rc = fg_save_learned_cap_to_sram(chip);
+ if (rc < 0)
+ pr_err("Error in saving learned_cc_uah, rc=%d\n", rc);
+ }
+
+ fg_dbg(chip, FG_CAP_LEARN, "learned_cc_uah:%lld nom_cap_uah: %lld\n",
+ chip->cl.learned_cc_uah, chip->cl.nom_cap_uah);
+ return 0;
+}
+
+static bool is_temp_valid_cap_learning(struct fg_chip *chip)
+{
+ int rc, batt_temp;
+
+ rc = fg_get_battery_temp(chip, &batt_temp);
+ if (rc < 0) {
+ pr_err("Error in getting batt_temp\n");
+ return false;
+ }
+
+ if (batt_temp > chip->dt.cl_max_temp ||
+ batt_temp < chip->dt.cl_min_temp) {
+ fg_dbg(chip, FG_CAP_LEARN, "batt temp %d out of range [%d %d]\n",
+ batt_temp, chip->dt.cl_min_temp, chip->dt.cl_max_temp);
+ return false;
+ }
+
+ return true;
+}
+
+#define QNOVO_CL_SKEW_DECIPCT -30
+static void fg_cap_learning_post_process(struct fg_chip *chip)
+{
+ int64_t max_inc_val, min_dec_val, old_cap;
+ int rc;
+
+ if (is_qnovo_en(chip)) {
+ fg_dbg(chip, FG_CAP_LEARN, "applying skew %d on current learnt capacity %lld\n",
+ QNOVO_CL_SKEW_DECIPCT, chip->cl.final_cc_uah);
+ chip->cl.final_cc_uah = chip->cl.final_cc_uah *
+ (1000 + QNOVO_CL_SKEW_DECIPCT);
+ do_div(chip->cl.final_cc_uah, 1000);
+ }
+
+ max_inc_val = chip->cl.learned_cc_uah
+ * (1000 + chip->dt.cl_max_cap_inc);
+ do_div(max_inc_val, 1000);
+
+ min_dec_val = chip->cl.learned_cc_uah
+ * (1000 - chip->dt.cl_max_cap_dec);
+ do_div(min_dec_val, 1000);
+
+ old_cap = chip->cl.learned_cc_uah;
+ if (chip->cl.final_cc_uah > max_inc_val)
+ chip->cl.learned_cc_uah = max_inc_val;
+ else if (chip->cl.final_cc_uah < min_dec_val)
+ chip->cl.learned_cc_uah = min_dec_val;
+ else
+ chip->cl.learned_cc_uah =
+ chip->cl.final_cc_uah;
+
+ if (chip->dt.cl_max_cap_limit) {
+ max_inc_val = (int64_t)chip->cl.nom_cap_uah * (1000 +
+ chip->dt.cl_max_cap_limit);
+ do_div(max_inc_val, 1000);
+ if (chip->cl.final_cc_uah > max_inc_val) {
+ fg_dbg(chip, FG_CAP_LEARN, "learning capacity %lld goes above max limit %lld\n",
+ chip->cl.final_cc_uah, max_inc_val);
+ chip->cl.learned_cc_uah = max_inc_val;
+ }
+ }
+
+ if (chip->dt.cl_min_cap_limit) {
+ min_dec_val = (int64_t)chip->cl.nom_cap_uah * (1000 -
+ chip->dt.cl_min_cap_limit);
+ do_div(min_dec_val, 1000);
+ if (chip->cl.final_cc_uah < min_dec_val) {
+ fg_dbg(chip, FG_CAP_LEARN, "learning capacity %lld goes below min limit %lld\n",
+ chip->cl.final_cc_uah, min_dec_val);
+ chip->cl.learned_cc_uah = min_dec_val;
+ }
+ }
+
+ rc = fg_save_learned_cap_to_sram(chip);
+ if (rc < 0)
+ pr_err("Error in saving learned_cc_uah, rc=%d\n", rc);
+
+ fg_dbg(chip, FG_CAP_LEARN, "final cc_uah = %lld, learned capacity %lld -> %lld uah\n",
+ chip->cl.final_cc_uah, old_cap, chip->cl.learned_cc_uah);
+}
+
+static int fg_cap_learning_process_full_data(struct fg_chip *chip)
+{
+ int rc, cc_soc_sw, cc_soc_delta_pct;
+ int64_t delta_cc_uah;
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC_SW, &cc_soc_sw);
+ if (rc < 0) {
+ pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc);
+ return rc;
+ }
+
+ cc_soc_delta_pct =
+ div64_s64((int64_t)(cc_soc_sw - chip->cl.init_cc_soc_sw) * 100,
+ CC_SOC_30BIT);
+
+ /* If the delta is < 50%, then skip processing full data */
+ if (cc_soc_delta_pct < 50) {
+ pr_err("cc_soc_delta_pct: %d\n", cc_soc_delta_pct);
+ return -ERANGE;
+ }
+
+ delta_cc_uah = div64_s64(chip->cl.learned_cc_uah * cc_soc_delta_pct,
+ 100);
+ chip->cl.final_cc_uah = chip->cl.init_cc_uah + delta_cc_uah;
+ fg_dbg(chip, FG_CAP_LEARN, "Current cc_soc=%d cc_soc_delta_pct=%d total_cc_uah=%lld\n",
+ cc_soc_sw, cc_soc_delta_pct, chip->cl.final_cc_uah);
+ return 0;
+}
+
+#define BATT_SOC_32BIT GENMASK(31, 0)
+static int fg_cap_learning_begin(struct fg_chip *chip, u32 batt_soc)
+{
+ int rc, cc_soc_sw, batt_soc_msb;
+
+ batt_soc_msb = batt_soc >> 24;
+ if (DIV_ROUND_CLOSEST(batt_soc_msb * 100, FULL_SOC_RAW) >
+ chip->dt.cl_start_soc) {
+ fg_dbg(chip, FG_CAP_LEARN, "Battery SOC %d is high!, not starting\n",
+ batt_soc_msb);
+ return -EINVAL;
+ }
+
+ chip->cl.init_cc_uah = div64_s64(chip->cl.learned_cc_uah * batt_soc_msb,
+ FULL_SOC_RAW);
+
+ /* Prime cc_soc_sw with battery SOC when capacity learning begins */
+ cc_soc_sw = div64_s64((int64_t)batt_soc * CC_SOC_30BIT,
+ BATT_SOC_32BIT);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_CC_SOC_SW].addr_word,
+ chip->sp[FG_SRAM_CC_SOC_SW].addr_byte, (u8 *)&cc_soc_sw,
+ chip->sp[FG_SRAM_CC_SOC_SW].len, FG_IMA_ATOMIC);
+ if (rc < 0) {
+ pr_err("Error in writing cc_soc_sw, rc=%d\n", rc);
+ goto out;
+ }
+
+ chip->cl.init_cc_soc_sw = cc_soc_sw;
+ chip->cl.active = true;
+ fg_dbg(chip, FG_CAP_LEARN, "Capacity learning started @ battery SOC %d init_cc_soc_sw:%d\n",
+ batt_soc_msb, chip->cl.init_cc_soc_sw);
+out:
+ return rc;
+}
+
+static int fg_cap_learning_done(struct fg_chip *chip)
+{
+ int rc, cc_soc_sw;
+
+ rc = fg_cap_learning_process_full_data(chip);
+ if (rc < 0) {
+ pr_err("Error in processing cap learning full data, rc=%d\n",
+ rc);
+ goto out;
+ }
+
+ /* Write a FULL value to cc_soc_sw */
+ cc_soc_sw = CC_SOC_30BIT;
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_CC_SOC_SW].addr_word,
+ chip->sp[FG_SRAM_CC_SOC_SW].addr_byte, (u8 *)&cc_soc_sw,
+ chip->sp[FG_SRAM_CC_SOC_SW].len, FG_IMA_ATOMIC);
+ if (rc < 0) {
+ pr_err("Error in writing cc_soc_sw, rc=%d\n", rc);
+ goto out;
+ }
+
+ fg_cap_learning_post_process(chip);
+out:
+ return rc;
+}
+
+static void fg_cap_learning_update(struct fg_chip *chip)
+{
+ int rc, batt_soc, batt_soc_msb;
+ bool input_present = is_input_present(chip);
+
+ mutex_lock(&chip->cl.lock);
+
+ if (!is_temp_valid_cap_learning(chip) || !chip->cl.learned_cc_uah ||
+ chip->battery_missing) {
+ fg_dbg(chip, FG_CAP_LEARN, "Aborting cap_learning %lld\n",
+ chip->cl.learned_cc_uah);
+ chip->cl.active = false;
+ chip->cl.init_cc_uah = 0;
+ goto out;
+ }
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_BATT_SOC, &batt_soc);
+ if (rc < 0) {
+ pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc);
+ goto out;
+ }
+
+ batt_soc_msb = (u32)batt_soc >> 24;
+ fg_dbg(chip, FG_CAP_LEARN, "Chg_status: %d cl_active: %d batt_soc: %d\n",
+ chip->charge_status, chip->cl.active, batt_soc_msb);
+
+ /* Initialize the starting point of learning capacity */
+ if (!chip->cl.active) {
+ if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) {
+ rc = fg_cap_learning_begin(chip, batt_soc);
+ chip->cl.active = (rc == 0);
+ }
+
+ } else {
+ if (chip->charge_done) {
+ rc = fg_cap_learning_done(chip);
+ if (rc < 0)
+ pr_err("Error in completing capacity learning, rc=%d\n",
+ rc);
+
+ chip->cl.active = false;
+ chip->cl.init_cc_uah = 0;
+ }
+
+ if (chip->charge_status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ if (!input_present) {
+ fg_dbg(chip, FG_CAP_LEARN, "Capacity learning aborted @ battery SOC %d\n",
+ batt_soc_msb);
+ chip->cl.active = false;
+ chip->cl.init_cc_uah = 0;
+ }
+ }
+
+ if (chip->charge_status == POWER_SUPPLY_STATUS_NOT_CHARGING) {
+ if (is_qnovo_en(chip) && input_present) {
+ /*
+ * Don't abort the capacity learning when qnovo
+ * is enabled and input is present where the
+ * charging status can go to "not charging"
+ * intermittently.
+ */
+ } else {
+ fg_dbg(chip, FG_CAP_LEARN, "Capacity learning aborted @ battery SOC %d\n",
+ batt_soc_msb);
+ chip->cl.active = false;
+ chip->cl.init_cc_uah = 0;
+ }
+ }
+ }
+
+out:
+ mutex_unlock(&chip->cl.lock);
+}
+
+#define KI_COEFF_MED_DISCHG_DEFAULT 1500
+#define KI_COEFF_HI_DISCHG_DEFAULT 2200
+static int fg_adjust_ki_coeff_dischg(struct fg_chip *chip)
+{
+ int rc, i, msoc;
+ int ki_coeff_med = KI_COEFF_MED_DISCHG_DEFAULT;
+ int ki_coeff_hi = KI_COEFF_HI_DISCHG_DEFAULT;
+ u8 val;
+
+ if (!chip->ki_coeff_dischg_en)
+ return 0;
+
+ rc = fg_get_prop_capacity(chip, &msoc);
+ if (rc < 0) {
+ pr_err("Error in getting capacity, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chip->charge_status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ for (i = KI_COEFF_SOC_LEVELS - 1; i >= 0; i--) {
+ if (msoc < chip->dt.ki_coeff_soc[i]) {
+ ki_coeff_med = chip->dt.ki_coeff_med_dischg[i];
+ ki_coeff_hi = chip->dt.ki_coeff_hi_dischg[i];
+ }
+ }
+ }
+
+ fg_encode(chip->sp, FG_SRAM_KI_COEFF_MED_DISCHG, ki_coeff_med, &val);
+ rc = fg_sram_write(chip,
+ chip->sp[FG_SRAM_KI_COEFF_MED_DISCHG].addr_word,
+ chip->sp[FG_SRAM_KI_COEFF_MED_DISCHG].addr_byte, &val,
+ chip->sp[FG_SRAM_KI_COEFF_MED_DISCHG].len,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing ki_coeff_med, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_encode(chip->sp, FG_SRAM_KI_COEFF_HI_DISCHG, ki_coeff_hi, &val);
+ rc = fg_sram_write(chip,
+ chip->sp[FG_SRAM_KI_COEFF_HI_DISCHG].addr_word,
+ chip->sp[FG_SRAM_KI_COEFF_HI_DISCHG].addr_byte, &val,
+ chip->sp[FG_SRAM_KI_COEFF_HI_DISCHG].len,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing ki_coeff_hi, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_dbg(chip, FG_STATUS, "Wrote ki_coeff_med %d ki_coeff_hi %d\n",
+ ki_coeff_med, ki_coeff_hi);
+ return 0;
+}
+
+#define KI_COEFF_FULL_SOC_DEFAULT 733
+static int fg_adjust_ki_coeff_full_soc(struct fg_chip *chip, int batt_temp)
+{
+ int rc, ki_coeff_full_soc;
+ u8 val;
+
+ if (batt_temp < 0)
+ ki_coeff_full_soc = 0;
+ else
+ ki_coeff_full_soc = KI_COEFF_FULL_SOC_DEFAULT;
+
+ if (chip->ki_coeff_full_soc == ki_coeff_full_soc)
+ return 0;
+
+ fg_encode(chip->sp, FG_SRAM_KI_COEFF_FULL_SOC, ki_coeff_full_soc, &val);
+ rc = fg_sram_write(chip,
+ chip->sp[FG_SRAM_KI_COEFF_FULL_SOC].addr_word,
+ chip->sp[FG_SRAM_KI_COEFF_FULL_SOC].addr_byte, &val,
+ chip->sp[FG_SRAM_KI_COEFF_FULL_SOC].len,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing ki_coeff_full_soc, rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->ki_coeff_full_soc = ki_coeff_full_soc;
+ fg_dbg(chip, FG_STATUS, "Wrote ki_coeff_full_soc %d\n",
+ ki_coeff_full_soc);
+ return 0;
+}
+
+static int fg_set_recharge_voltage(struct fg_chip *chip, int voltage_mv)
+{
+ u8 buf;
+ int rc;
+
+ if (chip->dt.auto_recharge_soc)
+ return 0;
+
+ /* This configuration is available only for pmicobalt v2.0 and above */
+ if (chip->wa_flags & PMI8998_V1_REV_WA)
+ return 0;
+
+ if (voltage_mv == chip->last_recharge_volt_mv)
+ return 0;
+
+ fg_dbg(chip, FG_STATUS, "Setting recharge voltage to %dmV\n",
+ voltage_mv);
+ fg_encode(chip->sp, FG_SRAM_RECHARGE_VBATT_THR, voltage_mv, &buf);
+ rc = fg_sram_write(chip,
+ chip->sp[FG_SRAM_RECHARGE_VBATT_THR].addr_word,
+ chip->sp[FG_SRAM_RECHARGE_VBATT_THR].addr_byte,
+ &buf, chip->sp[FG_SRAM_RECHARGE_VBATT_THR].len,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing recharge_vbatt_thr, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ chip->last_recharge_volt_mv = voltage_mv;
+ return 0;
+}
+
+#define AUTO_RECHG_VOLT_LOW_LIMIT_MV 3700
+static int fg_charge_full_update(struct fg_chip *chip)
+{
+ union power_supply_propval prop = {0, };
+ int rc, msoc, bsoc, recharge_soc, msoc_raw;
+ u8 full_soc[2] = {0xFF, 0xFF};
+
+ if (!chip->dt.hold_soc_while_full)
+ return 0;
+
+ if (!batt_psy_initialized(chip))
+ return 0;
+
+ mutex_lock(&chip->charge_full_lock);
+ vote(chip->delta_bsoc_irq_en_votable, DELTA_BSOC_IRQ_VOTER,
+ chip->charge_done, 0);
+ rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_HEALTH,
+ &prop);
+ if (rc < 0) {
+ pr_err("Error in getting battery health, rc=%d\n", rc);
+ goto out;
+ }
+
+ chip->health = prop.intval;
+ recharge_soc = chip->dt.recharge_soc_thr;
+ recharge_soc = DIV_ROUND_CLOSEST(recharge_soc * FULL_SOC_RAW,
+ FULL_CAPACITY);
+ rc = fg_get_sram_prop(chip, FG_SRAM_BATT_SOC, &bsoc);
+ if (rc < 0) {
+ pr_err("Error in getting BATT_SOC, rc=%d\n", rc);
+ goto out;
+ }
+
+ /* We need 2 most significant bytes here */
+ bsoc = (u32)bsoc >> 16;
+ rc = fg_get_msoc(chip, &msoc);
+ if (rc < 0) {
+ pr_err("Error in getting msoc, rc=%d\n", rc);
+ goto out;
+ }
+ msoc_raw = DIV_ROUND_CLOSEST(msoc * FULL_SOC_RAW, FULL_CAPACITY);
+
+ fg_dbg(chip, FG_STATUS, "msoc: %d bsoc: %x health: %d status: %d full: %d\n",
+ msoc, bsoc, chip->health, chip->charge_status,
+ chip->charge_full);
+ if (chip->charge_done && !chip->charge_full) {
+ if (msoc >= 99 && chip->health == POWER_SUPPLY_HEALTH_GOOD) {
+ fg_dbg(chip, FG_STATUS, "Setting charge_full to true\n");
+ chip->charge_full = true;
+ /*
+ * Lower the recharge voltage so that VBAT_LT_RECHG
+ * signal will not be asserted soon.
+ */
+ rc = fg_set_recharge_voltage(chip,
+ AUTO_RECHG_VOLT_LOW_LIMIT_MV);
+ if (rc < 0) {
+ pr_err("Error in reducing recharge voltage, rc=%d\n",
+ rc);
+ goto out;
+ }
+ } else {
+ fg_dbg(chip, FG_STATUS, "Terminated charging @ SOC%d\n",
+ msoc);
+ }
+ } else if (msoc_raw < recharge_soc && chip->charge_full) {
+ chip->delta_soc = FULL_CAPACITY - msoc;
+
+ /*
+ * We're spreading out the delta SOC over every 10% change
+ * in monotonic SOC. We cannot spread more than 9% in the
+ * range of 0-100 skipping the first 10%.
+ */
+ if (chip->delta_soc > 9) {
+ chip->delta_soc = 0;
+ chip->maint_soc = 0;
+ } else {
+ chip->maint_soc = FULL_CAPACITY;
+ chip->last_msoc = msoc;
+ }
+
+ chip->charge_full = false;
+
+ /*
+ * Raise the recharge voltage so that VBAT_LT_RECHG signal
+ * will be asserted soon as battery SOC had dropped below
+ * the recharge SOC threshold.
+ */
+ rc = fg_set_recharge_voltage(chip,
+ chip->dt.recharge_volt_thr_mv);
+ if (rc < 0) {
+ pr_err("Error in setting recharge voltage, rc=%d\n",
+ rc);
+ goto out;
+ }
+ fg_dbg(chip, FG_STATUS, "msoc_raw = %d bsoc: %d recharge_soc: %d delta_soc: %d\n",
+ msoc_raw, bsoc >> 8, recharge_soc, chip->delta_soc);
+ } else {
+ goto out;
+ }
+
+ if (!chip->charge_full)
+ goto out;
+
+ /*
+ * During JEITA conditions, charge_full can happen early. FULL_SOC
+ * and MONOTONIC_SOC needs to be updated to reflect the same. Write
+ * battery SOC to FULL_SOC and write a full value to MONOTONIC_SOC.
+ */
+ rc = fg_sram_write(chip, FULL_SOC_WORD, FULL_SOC_OFFSET, (u8 *)&bsoc, 2,
+ FG_IMA_ATOMIC);
+ if (rc < 0) {
+ pr_err("failed to write full_soc rc=%d\n", rc);
+ goto out;
+ }
+
+ rc = fg_sram_write(chip, MONOTONIC_SOC_WORD, MONOTONIC_SOC_OFFSET,
+ full_soc, 2, FG_IMA_ATOMIC);
+ if (rc < 0) {
+ pr_err("failed to write monotonic_soc rc=%d\n", rc);
+ goto out;
+ }
+
+ fg_dbg(chip, FG_STATUS, "Set charge_full to true @ soc %d\n", msoc);
+out:
+ mutex_unlock(&chip->charge_full_lock);
+ return rc;
+}
+
+#define RCONN_CONFIG_BIT BIT(0)
+static int fg_rconn_config(struct fg_chip *chip)
+{
+ int rc, esr_uohms;
+ u64 scaling_factor;
+ u32 val = 0;
+
+ if (!chip->dt.rconn_mohms)
+ return 0;
+
+ rc = fg_sram_read(chip, PROFILE_INTEGRITY_WORD,
+ SW_CONFIG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in reading SW_CONFIG_OFFSET, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (val & RCONN_CONFIG_BIT) {
+ fg_dbg(chip, FG_STATUS, "Rconn already configured: %x\n", val);
+ return 0;
+ }
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_ESR, &esr_uohms);
+ if (rc < 0) {
+ pr_err("failed to get ESR, rc=%d\n", rc);
+ return rc;
+ }
+
+ scaling_factor = div64_u64((u64)esr_uohms * 1000,
+ esr_uohms + (chip->dt.rconn_mohms * 1000));
+
+ rc = fg_sram_read(chip, ESR_RSLOW_CHG_WORD,
+ ESR_RSLOW_CHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in reading ESR_RSLOW_CHG_OFFSET, rc=%d\n", rc);
+ return rc;
+ }
+
+ val *= scaling_factor;
+ do_div(val, 1000);
+ rc = fg_sram_write(chip, ESR_RSLOW_CHG_WORD,
+ ESR_RSLOW_CHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing ESR_RSLOW_CHG_OFFSET, rc=%d\n", rc);
+ return rc;
+ }
+ fg_dbg(chip, FG_STATUS, "esr_rslow_chg modified to %x\n", val & 0xFF);
+
+ rc = fg_sram_read(chip, ESR_RSLOW_DISCHG_WORD,
+ ESR_RSLOW_DISCHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in reading ESR_RSLOW_DISCHG_OFFSET, rc=%d\n", rc);
+ return rc;
+ }
+
+ val *= scaling_factor;
+ do_div(val, 1000);
+ rc = fg_sram_write(chip, ESR_RSLOW_DISCHG_WORD,
+ ESR_RSLOW_DISCHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing ESR_RSLOW_DISCHG_OFFSET, rc=%d\n", rc);
+ return rc;
+ }
+ fg_dbg(chip, FG_STATUS, "esr_rslow_dischg modified to %x\n",
+ val & 0xFF);
+
+ val = RCONN_CONFIG_BIT;
+ rc = fg_sram_write(chip, PROFILE_INTEGRITY_WORD,
+ SW_CONFIG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing SW_CONFIG_OFFSET, rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int fg_set_constant_chg_voltage(struct fg_chip *chip, int volt_uv)
+{
+ u8 buf[2];
+ int rc;
+
+ if (volt_uv <= 0 || volt_uv > 15590000) {
+ pr_err("Invalid voltage %d\n", volt_uv);
+ return -EINVAL;
+ }
+
+ fg_encode(chip->sp, FG_SRAM_VBATT_FULL, volt_uv, buf);
+
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_VBATT_FULL].addr_word,
+ chip->sp[FG_SRAM_VBATT_FULL].addr_byte, buf,
+ chip->sp[FG_SRAM_VBATT_FULL].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing vbatt_full, rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int fg_set_recharge_soc(struct fg_chip *chip, int recharge_soc)
+{
+ u8 buf;
+ int rc;
+
+ if (!chip->dt.auto_recharge_soc)
+ return 0;
+
+ if (recharge_soc < 0 || recharge_soc > FULL_CAPACITY)
+ return 0;
+
+ fg_encode(chip->sp, FG_SRAM_RECHARGE_SOC_THR, recharge_soc, &buf);
+ rc = fg_sram_write(chip,
+ chip->sp[FG_SRAM_RECHARGE_SOC_THR].addr_word,
+ chip->sp[FG_SRAM_RECHARGE_SOC_THR].addr_byte, &buf,
+ chip->sp[FG_SRAM_RECHARGE_SOC_THR].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing recharge_soc_thr, rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int fg_adjust_recharge_soc(struct fg_chip *chip)
+{
+ int rc, msoc, recharge_soc, new_recharge_soc = 0;
+ bool recharge_soc_status;
+
+ if (!chip->dt.auto_recharge_soc)
+ return 0;
+
+ recharge_soc = chip->dt.recharge_soc_thr;
+ recharge_soc_status = chip->recharge_soc_adjusted;
+ /*
+ * If the input is present and charging had been terminated, adjust
+ * the recharge SOC threshold based on the monotonic SOC at which
+ * the charge termination had happened.
+ */
+ if (is_input_present(chip)) {
+ if (chip->charge_done) {
+ if (!chip->recharge_soc_adjusted) {
+ /* Get raw monotonic SOC for calculation */
+ rc = fg_get_msoc(chip, &msoc);
+ if (rc < 0) {
+ pr_err("Error in getting msoc, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* Adjust the recharge_soc threshold */
+ new_recharge_soc = msoc - (FULL_CAPACITY -
+ recharge_soc);
+ chip->recharge_soc_adjusted = true;
+ } else {
+ /* adjusted already, do nothing */
+ return 0;
+ }
+ } else {
+ /* Charging, do nothing */
+ return 0;
+ }
+ } else {
+ /* Restore the default value */
+ new_recharge_soc = recharge_soc;
+ chip->recharge_soc_adjusted = false;
+ }
+
+ rc = fg_set_recharge_soc(chip, new_recharge_soc);
+ if (rc < 0) {
+ chip->recharge_soc_adjusted = recharge_soc_status;
+ pr_err("Couldn't set resume SOC for FG, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_dbg(chip, FG_STATUS, "resume soc set to %d\n", new_recharge_soc);
+ return 0;
+}
+
+static int fg_adjust_recharge_voltage(struct fg_chip *chip)
+{
+ int rc, recharge_volt_mv;
+
+ if (chip->dt.auto_recharge_soc)
+ return 0;
+
+ fg_dbg(chip, FG_STATUS, "health: %d chg_status: %d chg_done: %d\n",
+ chip->health, chip->charge_status, chip->charge_done);
+
+ recharge_volt_mv = chip->dt.recharge_volt_thr_mv;
+
+ /* Lower the recharge voltage in soft JEITA */
+ if (chip->health == POWER_SUPPLY_HEALTH_WARM ||
+ chip->health == POWER_SUPPLY_HEALTH_COOL)
+ recharge_volt_mv -= 200;
+
+ rc = fg_set_recharge_voltage(chip, recharge_volt_mv);
+ if (rc < 0) {
+ pr_err("Error in setting recharge_voltage, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int fg_slope_limit_config(struct fg_chip *chip, int batt_temp)
+{
+ enum slope_limit_status status;
+ int rc;
+ u8 buf;
+
+ if (!chip->slope_limit_en)
+ return 0;
+
+ if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING ||
+ chip->charge_status == POWER_SUPPLY_STATUS_FULL) {
+ if (batt_temp < chip->dt.slope_limit_temp)
+ status = LOW_TEMP_CHARGE;
+ else
+ status = HIGH_TEMP_CHARGE;
+ } else {
+ if (batt_temp < chip->dt.slope_limit_temp)
+ status = LOW_TEMP_DISCHARGE;
+ else
+ status = HIGH_TEMP_DISCHARGE;
+ }
+
+ if (chip->slope_limit_sts == status)
+ return 0;
+
+ fg_encode(chip->sp, FG_SRAM_SLOPE_LIMIT,
+ chip->dt.slope_limit_coeffs[status], &buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_SLOPE_LIMIT].addr_word,
+ chip->sp[FG_SRAM_SLOPE_LIMIT].addr_byte, &buf,
+ chip->sp[FG_SRAM_SLOPE_LIMIT].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in configuring slope_limit coefficient, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ chip->slope_limit_sts = status;
+ fg_dbg(chip, FG_STATUS, "Slope limit status: %d value: %x\n", status,
+ buf);
+ return 0;
+}
+
+static int fg_esr_filter_config(struct fg_chip *chip, int batt_temp)
+{
+ u8 esr_tight_lt_flt, esr_broad_lt_flt;
+ bool cold_temp = false;
+ int rc;
+
+ /*
+ * If the battery temperature is lower than -20 C, then skip modifying
+ * ESR filter.
+ */
+ if (batt_temp < -210)
+ return 0;
+
+ /*
+ * If battery temperature is lesser than 10 C (default), then apply the
+ * ESR low temperature tight and broad filter values to ESR room
+ * temperature tight and broad filters. If battery temperature is higher
+ * than 10 C, then apply back the room temperature ESR filter
+ * coefficients to ESR room temperature tight and broad filters.
+ */
+ if (batt_temp > chip->dt.esr_flt_switch_temp
+ && chip->esr_flt_cold_temp_en) {
+ fg_encode(chip->sp, FG_SRAM_ESR_TIGHT_FILTER,
+ chip->dt.esr_tight_flt_upct, &esr_tight_lt_flt);
+ fg_encode(chip->sp, FG_SRAM_ESR_BROAD_FILTER,
+ chip->dt.esr_broad_flt_upct, &esr_broad_lt_flt);
+ } else if (batt_temp <= chip->dt.esr_flt_switch_temp
+ && !chip->esr_flt_cold_temp_en) {
+ fg_encode(chip->sp, FG_SRAM_ESR_TIGHT_FILTER,
+ chip->dt.esr_tight_lt_flt_upct, &esr_tight_lt_flt);
+ fg_encode(chip->sp, FG_SRAM_ESR_BROAD_FILTER,
+ chip->dt.esr_broad_lt_flt_upct, &esr_broad_lt_flt);
+ cold_temp = true;
+ } else {
+ return 0;
+ }
+
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_word,
+ chip->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_byte,
+ &esr_tight_lt_flt,
+ chip->sp[FG_SRAM_ESR_TIGHT_FILTER].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing ESR LT tight filter, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR_BROAD_FILTER].addr_word,
+ chip->sp[FG_SRAM_ESR_BROAD_FILTER].addr_byte,
+ &esr_broad_lt_flt,
+ chip->sp[FG_SRAM_ESR_BROAD_FILTER].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing ESR LT broad filter, rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->esr_flt_cold_temp_en = cold_temp;
+ fg_dbg(chip, FG_STATUS, "applied %s ESR filter values\n",
+ cold_temp ? "cold" : "normal");
+ return 0;
+}
+
+static int fg_esr_fcc_config(struct fg_chip *chip)
+{
+ union power_supply_propval prop = {0, };
+ int rc;
+ bool parallel_en = false, qnovo_en;
+
+ if (is_parallel_charger_available(chip)) {
+ rc = power_supply_get_property(chip->parallel_psy,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED, &prop);
+ if (rc < 0) {
+ pr_err("Error in reading charging_enabled from parallel_psy, rc=%d\n",
+ rc);
+ return rc;
+ }
+ parallel_en = prop.intval;
+ }
+
+ qnovo_en = is_qnovo_en(chip);
+
+ fg_dbg(chip, FG_POWER_SUPPLY, "chg_sts: %d par_en: %d qnov_en: %d esr_fcc_ctrl_en: %d\n",
+ chip->charge_status, parallel_en, qnovo_en,
+ chip->esr_fcc_ctrl_en);
+
+ if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING &&
+ (parallel_en || qnovo_en)) {
+ if (chip->esr_fcc_ctrl_en)
+ return 0;
+
+ /*
+ * When parallel charging or Qnovo is enabled, configure ESR
+ * FCC to 300mA to trigger an ESR pulse. Without this, FG can
+ * request the main charger to increase FCC when it is supposed
+ * to decrease it.
+ */
+ rc = fg_masked_write(chip, BATT_INFO_ESR_FAST_CRG_CFG(chip),
+ ESR_FAST_CRG_IVAL_MASK |
+ ESR_FAST_CRG_CTL_EN_BIT,
+ ESR_FCC_300MA | ESR_FAST_CRG_CTL_EN_BIT);
+ if (rc < 0) {
+ pr_err("Error in writing to %04x, rc=%d\n",
+ BATT_INFO_ESR_FAST_CRG_CFG(chip), rc);
+ return rc;
+ }
+
+ chip->esr_fcc_ctrl_en = true;
+ } else {
+ if (!chip->esr_fcc_ctrl_en)
+ return 0;
+
+ /*
+ * If we're here, then it means either the device is not in
+ * charging state or parallel charging / Qnovo is disabled.
+ * Disable ESR fast charge current control in SW.
+ */
+ rc = fg_masked_write(chip, BATT_INFO_ESR_FAST_CRG_CFG(chip),
+ ESR_FAST_CRG_CTL_EN_BIT, 0);
+ if (rc < 0) {
+ pr_err("Error in writing to %04x, rc=%d\n",
+ BATT_INFO_ESR_FAST_CRG_CFG(chip), rc);
+ return rc;
+ }
+
+ chip->esr_fcc_ctrl_en = false;
+ }
+
+ fg_dbg(chip, FG_STATUS, "esr_fcc_ctrl_en set to %d\n",
+ chip->esr_fcc_ctrl_en);
+ return 0;
+}
+
+static int fg_esr_timer_config(struct fg_chip *chip, bool sleep)
+{
+ int rc, cycles_init, cycles_max;
+ bool end_of_charge = false;
+
+ end_of_charge = is_input_present(chip) && chip->charge_done;
+ fg_dbg(chip, FG_STATUS, "sleep: %d eoc: %d\n", sleep, end_of_charge);
+
+ /* ESR discharging timer configuration */
+ cycles_init = sleep ? chip->dt.esr_timer_asleep[TIMER_RETRY] :
+ chip->dt.esr_timer_awake[TIMER_RETRY];
+ if (end_of_charge)
+ cycles_init = 0;
+
+ cycles_max = sleep ? chip->dt.esr_timer_asleep[TIMER_MAX] :
+ chip->dt.esr_timer_awake[TIMER_MAX];
+
+ rc = fg_set_esr_timer(chip, cycles_init, cycles_max, false,
+ sleep ? FG_IMA_NO_WLOCK : FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in setting ESR timer, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* ESR charging timer configuration */
+ cycles_init = cycles_max = -EINVAL;
+ if (end_of_charge || sleep) {
+ cycles_init = chip->dt.esr_timer_charging[TIMER_RETRY];
+ cycles_max = chip->dt.esr_timer_charging[TIMER_MAX];
+ } else if (is_input_present(chip)) {
+ cycles_init = chip->esr_timer_charging_default[TIMER_RETRY];
+ cycles_max = chip->esr_timer_charging_default[TIMER_MAX];
+ }
+
+ rc = fg_set_esr_timer(chip, cycles_init, cycles_max, true,
+ sleep ? FG_IMA_NO_WLOCK : FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in setting ESR timer, rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static void fg_ttf_update(struct fg_chip *chip)
+{
+ int rc;
+ int delay_ms;
+ union power_supply_propval prop = {0, };
+ int online = 0;
+
+ if (usb_psy_initialized(chip)) {
+ rc = power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_ONLINE, &prop);
+ if (rc < 0) {
+ pr_err("Couldn't read usb ONLINE prop rc=%d\n", rc);
+ return;
+ }
+
+ online = online || prop.intval;
+ }
+
+ if (pc_port_psy_initialized(chip)) {
+ rc = power_supply_get_property(chip->pc_port_psy,
+ POWER_SUPPLY_PROP_ONLINE, &prop);
+ if (rc < 0) {
+ pr_err("Couldn't read pc_port ONLINE prop rc=%d\n", rc);
+ return;
+ }
+
+ online = online || prop.intval;
+ }
+
+ if (dc_psy_initialized(chip)) {
+ rc = power_supply_get_property(chip->dc_psy,
+ POWER_SUPPLY_PROP_ONLINE, &prop);
+ if (rc < 0) {
+ pr_err("Couldn't read dc ONLINE prop rc=%d\n", rc);
+ return;
+ }
+
+ online = online || prop.intval;
+ }
+
+
+ if (chip->online_status == online)
+ return;
+
+ chip->online_status = online;
+ if (online)
+ /* wait 35 seconds for the input to settle */
+ delay_ms = 35000;
+ else
+ /* wait 5 seconds for current to settle during discharge */
+ delay_ms = 5000;
+
+ vote(chip->awake_votable, TTF_PRIMING, true, 0);
+ cancel_delayed_work_sync(&chip->ttf_work);
+ mutex_lock(&chip->ttf.lock);
+ fg_circ_buf_clr(&chip->ttf.ibatt);
+ fg_circ_buf_clr(&chip->ttf.vbatt);
+ chip->ttf.last_ttf = 0;
+ chip->ttf.last_ms = 0;
+ mutex_unlock(&chip->ttf.lock);
+ schedule_delayed_work(&chip->ttf_work, msecs_to_jiffies(delay_ms));
+}
+
+static void restore_cycle_counter(struct fg_chip *chip)
+{
+ int rc = 0, i;
+ u8 data[2];
+
+ if (!chip->cyc_ctr.en)
+ return;
+
+ mutex_lock(&chip->cyc_ctr.lock);
+ for (i = 0; i < BUCKET_COUNT; i++) {
+ rc = fg_sram_read(chip, CYCLE_COUNT_WORD + (i / 2),
+ CYCLE_COUNT_OFFSET + (i % 2) * 2, data, 2,
+ FG_IMA_DEFAULT);
+ if (rc < 0)
+ pr_err("failed to read bucket %d rc=%d\n", i, rc);
+ else
+ chip->cyc_ctr.count[i] = data[0] | data[1] << 8;
+ }
+ mutex_unlock(&chip->cyc_ctr.lock);
+}
+
+static void clear_cycle_counter(struct fg_chip *chip)
+{
+ int rc = 0, i;
+
+ if (!chip->cyc_ctr.en)
+ return;
+
+ mutex_lock(&chip->cyc_ctr.lock);
+ memset(chip->cyc_ctr.count, 0, sizeof(chip->cyc_ctr.count));
+ for (i = 0; i < BUCKET_COUNT; i++) {
+ chip->cyc_ctr.started[i] = false;
+ chip->cyc_ctr.last_soc[i] = 0;
+ }
+ rc = fg_sram_write(chip, CYCLE_COUNT_WORD, CYCLE_COUNT_OFFSET,
+ (u8 *)&chip->cyc_ctr.count,
+ sizeof(chip->cyc_ctr.count) / sizeof(u8 *),
+ FG_IMA_DEFAULT);
+ if (rc < 0)
+ pr_err("failed to clear cycle counter rc=%d\n", rc);
+
+ mutex_unlock(&chip->cyc_ctr.lock);
+}
+
+static int fg_inc_store_cycle_ctr(struct fg_chip *chip, int bucket)
+{
+ int rc = 0;
+ u16 cyc_count;
+ u8 data[2];
+
+ if (bucket < 0 || (bucket > BUCKET_COUNT - 1))
+ return 0;
+
+ cyc_count = chip->cyc_ctr.count[bucket];
+ cyc_count++;
+ data[0] = cyc_count & 0xFF;
+ data[1] = cyc_count >> 8;
+
+ rc = fg_sram_write(chip, CYCLE_COUNT_WORD + (bucket / 2),
+ CYCLE_COUNT_OFFSET + (bucket % 2) * 2, data, 2,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("failed to write BATT_CYCLE[%d] rc=%d\n",
+ bucket, rc);
+ return rc;
+ }
+
+ chip->cyc_ctr.count[bucket] = cyc_count;
+ fg_dbg(chip, FG_STATUS, "Stored count %d in bucket %d\n", cyc_count,
+ bucket);
+
+ return rc;
+}
+
+static void fg_cycle_counter_update(struct fg_chip *chip)
+{
+ int rc = 0, bucket, i, batt_soc;
+
+ if (!chip->cyc_ctr.en)
+ return;
+
+ mutex_lock(&chip->cyc_ctr.lock);
+ rc = fg_get_sram_prop(chip, FG_SRAM_BATT_SOC, &batt_soc);
+ if (rc < 0) {
+ pr_err("Failed to read battery soc rc: %d\n", rc);
+ goto out;
+ }
+
+ /* We need only the most significant byte here */
+ batt_soc = (u32)batt_soc >> 24;
+
+ /* Find out which bucket the SOC falls in */
+ bucket = batt_soc / BUCKET_SOC_PCT;
+
+ if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) {
+ if (!chip->cyc_ctr.started[bucket]) {
+ chip->cyc_ctr.started[bucket] = true;
+ chip->cyc_ctr.last_soc[bucket] = batt_soc;
+ }
+ } else if (chip->charge_done || !is_input_present(chip)) {
+ for (i = 0; i < BUCKET_COUNT; i++) {
+ if (chip->cyc_ctr.started[i] &&
+ batt_soc > chip->cyc_ctr.last_soc[i] + 2) {
+ rc = fg_inc_store_cycle_ctr(chip, i);
+ if (rc < 0)
+ pr_err("Error in storing cycle_ctr rc: %d\n",
+ rc);
+ chip->cyc_ctr.last_soc[i] = 0;
+ chip->cyc_ctr.started[i] = false;
+ }
+ }
+ }
+
+ fg_dbg(chip, FG_STATUS, "batt_soc: %d bucket: %d chg_status: %d\n",
+ batt_soc, bucket, chip->charge_status);
+out:
+ mutex_unlock(&chip->cyc_ctr.lock);
+}
+
+static int fg_get_cycle_count(struct fg_chip *chip)
+{
+ int count;
+
+ if (!chip->cyc_ctr.en)
+ return 0;
+
+ if ((chip->cyc_ctr.id <= 0) || (chip->cyc_ctr.id > BUCKET_COUNT))
+ return -EINVAL;
+
+ mutex_lock(&chip->cyc_ctr.lock);
+ count = chip->cyc_ctr.count[chip->cyc_ctr.id - 1];
+ mutex_unlock(&chip->cyc_ctr.lock);
+ return count;
+}
+
+static void status_change_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip, status_change_work);
+ union power_supply_propval prop = {0, };
+ int rc, batt_temp;
+
+ if (!batt_psy_initialized(chip)) {
+ fg_dbg(chip, FG_STATUS, "Charger not available?!\n");
+ goto out;
+ }
+
+ rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS,
+ &prop);
+ if (rc < 0) {
+ pr_err("Error in getting charging status, rc=%d\n", rc);
+ goto out;
+ }
+
+ chip->prev_charge_status = chip->charge_status;
+ chip->charge_status = prop.intval;
+ rc = power_supply_get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_CHARGE_TYPE, &prop);
+ if (rc < 0) {
+ pr_err("Error in getting charge type, rc=%d\n", rc);
+ goto out;
+ }
+
+ chip->charge_type = prop.intval;
+ rc = power_supply_get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_CHARGE_DONE, &prop);
+ if (rc < 0) {
+ pr_err("Error in getting charge_done, rc=%d\n", rc);
+ goto out;
+ }
+
+ chip->charge_done = prop.intval;
+ fg_cycle_counter_update(chip);
+ fg_cap_learning_update(chip);
+
+ rc = fg_charge_full_update(chip);
+ if (rc < 0)
+ pr_err("Error in charge_full_update, rc=%d\n", rc);
+
+ rc = fg_adjust_recharge_soc(chip);
+ if (rc < 0)
+ pr_err("Error in adjusting recharge_soc, rc=%d\n", rc);
+
+ rc = fg_adjust_recharge_voltage(chip);
+ if (rc < 0)
+ pr_err("Error in adjusting recharge_voltage, rc=%d\n", rc);
+
+ rc = fg_adjust_ki_coeff_dischg(chip);
+ if (rc < 0)
+ pr_err("Error in adjusting ki_coeff_dischg, rc=%d\n", rc);
+
+ rc = fg_esr_fcc_config(chip);
+ if (rc < 0)
+ pr_err("Error in adjusting FCC for ESR, rc=%d\n", rc);
+
+ rc = fg_get_battery_temp(chip, &batt_temp);
+ if (!rc) {
+ rc = fg_slope_limit_config(chip, batt_temp);
+ if (rc < 0)
+ pr_err("Error in configuring slope limiter rc:%d\n",
+ rc);
+
+ rc = fg_adjust_ki_coeff_full_soc(chip, batt_temp);
+ if (rc < 0)
+ pr_err("Error in configuring ki_coeff_full_soc rc:%d\n",
+ rc);
+ }
+
+ fg_ttf_update(chip);
+
+out:
+ fg_dbg(chip, FG_POWER_SUPPLY, "charge_status:%d charge_type:%d charge_done:%d\n",
+ chip->charge_status, chip->charge_type, chip->charge_done);
+ pm_relax(chip->dev);
+}
+
+static int fg_bp_params_config(struct fg_chip *chip)
+{
+ int rc = 0;
+ u8 buf;
+
+ /* This SRAM register is only present in v2.0 and above */
+ if (!(chip->wa_flags & PMI8998_V1_REV_WA) &&
+ chip->bp.float_volt_uv > 0) {
+ fg_encode(chip->sp, FG_SRAM_FLOAT_VOLT,
+ chip->bp.float_volt_uv / 1000, &buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_FLOAT_VOLT].addr_word,
+ chip->sp[FG_SRAM_FLOAT_VOLT].addr_byte, &buf,
+ chip->sp[FG_SRAM_FLOAT_VOLT].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing float_volt, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (chip->bp.vbatt_full_mv > 0) {
+ rc = fg_set_constant_chg_voltage(chip,
+ chip->bp.vbatt_full_mv * 1000);
+ if (rc < 0)
+ return rc;
+ }
+
+ return rc;
+}
+
+#define PROFILE_LOAD_BIT BIT(0)
+#define BOOTLOADER_LOAD_BIT BIT(1)
+#define BOOTLOADER_RESTART_BIT BIT(2)
+#define HLOS_RESTART_BIT BIT(3)
+static bool is_profile_load_required(struct fg_chip *chip)
+{
+ u8 buf[PROFILE_COMP_LEN], val;
+ bool profiles_same = false;
+ int rc;
+
+ rc = fg_sram_read(chip, PROFILE_INTEGRITY_WORD,
+ PROFILE_INTEGRITY_OFFSET, &val, 1, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("failed to read profile integrity rc=%d\n", rc);
+ return false;
+ }
+
+ /* Check if integrity bit is set */
+ if (val & PROFILE_LOAD_BIT) {
+ fg_dbg(chip, FG_STATUS, "Battery profile integrity bit is set\n");
+
+ /* Whitelist the values */
+ val &= ~PROFILE_LOAD_BIT;
+ if (val != HLOS_RESTART_BIT && val != BOOTLOADER_LOAD_BIT &&
+ val != (BOOTLOADER_LOAD_BIT | BOOTLOADER_RESTART_BIT)) {
+ val |= PROFILE_LOAD_BIT;
+ pr_warn("Garbage value in profile integrity word: 0x%x\n",
+ val);
+ return true;
+ }
+
+ rc = fg_sram_read(chip, PROFILE_LOAD_WORD, PROFILE_LOAD_OFFSET,
+ buf, PROFILE_COMP_LEN, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in reading battery profile, rc:%d\n", rc);
+ return false;
+ }
+ profiles_same = memcmp(chip->batt_profile, buf,
+ PROFILE_COMP_LEN) == 0;
+ if (profiles_same) {
+ fg_dbg(chip, FG_STATUS, "Battery profile is same, not loading it\n");
+ return false;
+ }
+
+ if (!chip->dt.force_load_profile) {
+ pr_warn("Profiles doesn't match, skipping loading it since force_load_profile is disabled\n");
+ if (fg_profile_dump) {
+ pr_info("FG: loaded profile:\n");
+ dump_sram(buf, PROFILE_LOAD_WORD,
+ PROFILE_COMP_LEN);
+ pr_info("FG: available profile:\n");
+ dump_sram(chip->batt_profile, PROFILE_LOAD_WORD,
+ PROFILE_LEN);
+ }
+ return false;
+ }
+
+ fg_dbg(chip, FG_STATUS, "Profiles are different, loading the correct one\n");
+ } else {
+ fg_dbg(chip, FG_STATUS, "Profile integrity bit is not set\n");
+ if (fg_profile_dump) {
+ pr_info("FG: profile to be loaded:\n");
+ dump_sram(chip->batt_profile, PROFILE_LOAD_WORD,
+ PROFILE_LEN);
+ }
+ }
+ return true;
+}
+
+static void clear_battery_profile(struct fg_chip *chip)
+{
+ u8 val = 0;
+ int rc;
+
+ rc = fg_sram_write(chip, PROFILE_INTEGRITY_WORD,
+ PROFILE_INTEGRITY_OFFSET, &val, 1, FG_IMA_DEFAULT);
+ if (rc < 0)
+ pr_err("failed to write profile integrity rc=%d\n", rc);
+}
+
+#define SOC_READY_WAIT_MS 2000
+static int __fg_restart(struct fg_chip *chip)
+{
+ int rc, msoc;
+ bool tried_again = false;
+
+ rc = fg_get_prop_capacity(chip, &msoc);
+ if (rc < 0) {
+ pr_err("Error in getting capacity, rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->last_soc = msoc;
+ chip->fg_restarting = true;
+ reinit_completion(&chip->soc_ready);
+ rc = fg_masked_write(chip, BATT_SOC_RESTART(chip), RESTART_GO_BIT,
+ RESTART_GO_BIT);
+ if (rc < 0) {
+ pr_err("Error in writing to %04x, rc=%d\n",
+ BATT_SOC_RESTART(chip), rc);
+ goto out;
+ }
+
+wait:
+ rc = wait_for_completion_interruptible_timeout(&chip->soc_ready,
+ msecs_to_jiffies(SOC_READY_WAIT_MS));
+
+ /* If we were interrupted wait again one more time. */
+ if (rc == -ERESTARTSYS && !tried_again) {
+ tried_again = true;
+ goto wait;
+ } else if (rc <= 0) {
+ pr_err("wait for soc_ready timed out rc=%d\n", rc);
+ }
+
+ rc = fg_masked_write(chip, BATT_SOC_RESTART(chip), RESTART_GO_BIT, 0);
+ if (rc < 0) {
+ pr_err("Error in writing to %04x, rc=%d\n",
+ BATT_SOC_RESTART(chip), rc);
+ goto out;
+ }
+out:
+ chip->fg_restarting = false;
+ return rc;
+}
+
+static void profile_load_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ profile_load_work.work);
+ u8 buf[2], val;
+ int rc;
+
+ vote(chip->awake_votable, PROFILE_LOAD, true, 0);
+
+ rc = fg_get_batt_id(chip);
+ if (rc < 0) {
+ pr_err("Error in getting battery id, rc:%d\n", rc);
+ goto out;
+ }
+
+ rc = fg_get_batt_profile(chip);
+ if (rc < 0) {
+ pr_warn("profile for batt_id=%dKOhms not found..using OTP, rc:%d\n",
+ chip->batt_id_ohms / 1000, rc);
+ goto out;
+ }
+
+ if (!chip->profile_available)
+ goto out;
+
+ if (!is_profile_load_required(chip))
+ goto done;
+
+ clear_cycle_counter(chip);
+ mutex_lock(&chip->cl.lock);
+ chip->cl.learned_cc_uah = 0;
+ chip->cl.active = false;
+ mutex_unlock(&chip->cl.lock);
+
+ fg_dbg(chip, FG_STATUS, "profile loading started\n");
+ rc = fg_masked_write(chip, BATT_SOC_RESTART(chip), RESTART_GO_BIT, 0);
+ if (rc < 0) {
+ pr_err("Error in writing to %04x, rc=%d\n",
+ BATT_SOC_RESTART(chip), rc);
+ goto out;
+ }
+
+ /* load battery profile */
+ rc = fg_sram_write(chip, PROFILE_LOAD_WORD, PROFILE_LOAD_OFFSET,
+ chip->batt_profile, PROFILE_LEN, FG_IMA_ATOMIC);
+ if (rc < 0) {
+ pr_err("Error in writing battery profile, rc:%d\n", rc);
+ goto out;
+ }
+
+ rc = __fg_restart(chip);
+ if (rc < 0) {
+ pr_err("Error in restarting FG, rc=%d\n", rc);
+ goto out;
+ }
+
+ fg_dbg(chip, FG_STATUS, "SOC is ready\n");
+
+ /* Set the profile integrity bit */
+ val = HLOS_RESTART_BIT | PROFILE_LOAD_BIT;
+ rc = fg_sram_write(chip, PROFILE_INTEGRITY_WORD,
+ PROFILE_INTEGRITY_OFFSET, &val, 1, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("failed to write profile integrity rc=%d\n", rc);
+ goto out;
+ }
+
+done:
+ rc = fg_bp_params_config(chip);
+ if (rc < 0)
+ pr_err("Error in configuring battery profile params, rc:%d\n",
+ rc);
+
+ rc = fg_sram_read(chip, NOM_CAP_WORD, NOM_CAP_OFFSET, buf, 2,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in reading %04x[%d] rc=%d\n", NOM_CAP_WORD,
+ NOM_CAP_OFFSET, rc);
+ } else {
+ chip->cl.nom_cap_uah = (int)(buf[0] | buf[1] << 8) * 1000;
+ rc = fg_load_learned_cap_from_sram(chip);
+ if (rc < 0)
+ pr_err("Error in loading capacity learning data, rc:%d\n",
+ rc);
+ }
+
+ batt_psy_initialized(chip);
+ fg_notify_charger(chip);
+ chip->profile_loaded = true;
+ fg_dbg(chip, FG_STATUS, "profile loaded successfully");
+out:
+ chip->soc_reporting_ready = true;
+ vote(chip->awake_votable, PROFILE_LOAD, false, 0);
+}
+
+static void sram_dump_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work, struct fg_chip,
+ sram_dump_work.work);
+ u8 buf[FG_SRAM_LEN];
+ int rc;
+ s64 timestamp_ms, quotient;
+ s32 remainder;
+
+ rc = fg_sram_read(chip, 0, 0, buf, FG_SRAM_LEN, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in reading FG SRAM, rc:%d\n", rc);
+ goto resched;
+ }
+
+ timestamp_ms = ktime_to_ms(ktime_get_boottime());
+ quotient = div_s64_rem(timestamp_ms, 1000, &remainder);
+ fg_dbg(chip, FG_STATUS, "SRAM Dump Started at %lld.%d\n",
+ quotient, remainder);
+ dump_sram(buf, 0, FG_SRAM_LEN);
+ timestamp_ms = ktime_to_ms(ktime_get_boottime());
+ quotient = div_s64_rem(timestamp_ms, 1000, &remainder);
+ fg_dbg(chip, FG_STATUS, "SRAM Dump done at %lld.%d\n",
+ quotient, remainder);
+resched:
+ schedule_delayed_work(&chip->sram_dump_work,
+ msecs_to_jiffies(fg_sram_dump_period_ms));
+}
+
+static int fg_sram_dump_sysfs(const char *val, const struct kernel_param *kp)
+{
+ int rc;
+ struct power_supply *bms_psy;
+ struct fg_chip *chip;
+ bool old_val = fg_sram_dump;
+
+ rc = param_set_bool(val, kp);
+ if (rc) {
+ pr_err("Unable to set fg_sram_dump: %d\n", rc);
+ return rc;
+ }
+
+ if (fg_sram_dump == old_val)
+ return 0;
+
+ bms_psy = power_supply_get_by_name("bms");
+ if (!bms_psy) {
+ pr_err("bms psy not found\n");
+ return -ENODEV;
+ }
+
+ chip = power_supply_get_drvdata(bms_psy);
+ if (fg_sram_dump)
+ schedule_delayed_work(&chip->sram_dump_work,
+ msecs_to_jiffies(fg_sram_dump_period_ms));
+ else
+ cancel_delayed_work_sync(&chip->sram_dump_work);
+
+ return 0;
+}
+
+static struct kernel_param_ops fg_sram_dump_ops = {
+ .set = fg_sram_dump_sysfs,
+ .get = param_get_bool,
+};
+
+module_param_cb(sram_dump_en, &fg_sram_dump_ops, &fg_sram_dump, 0644);
+
+static int fg_restart_sysfs(const char *val, const struct kernel_param *kp)
+{
+ int rc;
+ struct power_supply *bms_psy;
+ struct fg_chip *chip;
+
+ rc = param_set_int(val, kp);
+ if (rc) {
+ pr_err("Unable to set fg_restart: %d\n", rc);
+ return rc;
+ }
+
+ if (fg_restart != 1) {
+ pr_err("Bad value %d\n", fg_restart);
+ return -EINVAL;
+ }
+
+ bms_psy = power_supply_get_by_name("bms");
+ if (!bms_psy) {
+ pr_err("bms psy not found\n");
+ return 0;
+ }
+
+ chip = power_supply_get_drvdata(bms_psy);
+ rc = __fg_restart(chip);
+ if (rc < 0) {
+ pr_err("Error in restarting FG, rc=%d\n", rc);
+ return rc;
+ }
+
+ pr_info("FG restart done\n");
+ return rc;
+}
+
+static struct kernel_param_ops fg_restart_ops = {
+ .set = fg_restart_sysfs,
+ .get = param_get_int,
+};
+
+module_param_cb(restart, &fg_restart_ops, &fg_restart, 0644);
+
+#define HOURS_TO_SECONDS 3600
+#define OCV_SLOPE_UV 10869
+#define MILLI_UNIT 1000
+#define MICRO_UNIT 1000000
+#define NANO_UNIT 1000000000
+static int fg_get_time_to_full_locked(struct fg_chip *chip, int *val)
+{
+ int rc, ibatt_avg, vbatt_avg, rbatt, msoc, full_soc, act_cap_mah,
+ i_cc2cv, soc_cc2cv, tau, divisor, iterm, ttf_mode,
+ i, soc_per_step, msoc_this_step, msoc_next_step,
+ ibatt_this_step, t_predicted_this_step, ttf_slope,
+ t_predicted_cv, t_predicted = 0;
+ s64 delta_ms;
+
+ if (chip->bp.float_volt_uv <= 0) {
+ pr_err("battery profile is not loaded\n");
+ return -ENODATA;
+ }
+
+ if (!batt_psy_initialized(chip)) {
+ fg_dbg(chip, FG_TTF, "charger is not available\n");
+ return -ENODATA;
+ }
+
+ rc = fg_get_prop_capacity(chip, &msoc);
+ if (rc < 0) {
+ pr_err("failed to get msoc rc=%d\n", rc);
+ return rc;
+ }
+ fg_dbg(chip, FG_TTF, "msoc=%d\n", msoc);
+
+ /* the battery is considered full if the SOC is 100% */
+ if (msoc >= 100) {
+ *val = 0;
+ return 0;
+ }
+
+ if (is_qnovo_en(chip))
+ ttf_mode = TTF_MODE_QNOVO;
+ else
+ ttf_mode = TTF_MODE_NORMAL;
+
+ /* when switching TTF algorithms the TTF needs to be reset */
+ if (chip->ttf.mode != ttf_mode) {
+ fg_circ_buf_clr(&chip->ttf.ibatt);
+ fg_circ_buf_clr(&chip->ttf.vbatt);
+ chip->ttf.last_ttf = 0;
+ chip->ttf.last_ms = 0;
+ chip->ttf.mode = ttf_mode;
+ }
+
+ /* at least 10 samples are required to produce a stable IBATT */
+ if (chip->ttf.ibatt.size < 10) {
+ *val = -1;
+ return 0;
+ }
+
+ rc = fg_circ_buf_median(&chip->ttf.ibatt, &ibatt_avg);
+ if (rc < 0) {
+ pr_err("failed to get IBATT AVG rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_circ_buf_median(&chip->ttf.vbatt, &vbatt_avg);
+ if (rc < 0) {
+ pr_err("failed to get VBATT AVG rc=%d\n", rc);
+ return rc;
+ }
+
+ ibatt_avg = -ibatt_avg / MILLI_UNIT;
+ vbatt_avg /= MILLI_UNIT;
+
+ /* clamp ibatt_avg to iterm */
+ if (ibatt_avg < abs(chip->dt.sys_term_curr_ma))
+ ibatt_avg = abs(chip->dt.sys_term_curr_ma);
+
+ fg_dbg(chip, FG_TTF, "ibatt_avg=%d\n", ibatt_avg);
+ fg_dbg(chip, FG_TTF, "vbatt_avg=%d\n", vbatt_avg);
+
+ rc = fg_get_battery_resistance(chip, &rbatt);
+ if (rc < 0) {
+ pr_err("failed to get battery resistance rc=%d\n", rc);
+ return rc;
+ }
+
+ rbatt /= MILLI_UNIT;
+ fg_dbg(chip, FG_TTF, "rbatt=%d\n", rbatt);
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_ACT_BATT_CAP, &act_cap_mah);
+ if (rc < 0) {
+ pr_err("failed to get ACT_BATT_CAP rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_FULL_SOC, &full_soc);
+ if (rc < 0) {
+ pr_err("failed to get full soc rc=%d\n", rc);
+ return rc;
+ }
+ full_soc = DIV_ROUND_CLOSEST(((u16)full_soc >> 8) * FULL_CAPACITY,
+ FULL_SOC_RAW);
+ act_cap_mah = full_soc * act_cap_mah / 100;
+ fg_dbg(chip, FG_TTF, "act_cap_mah=%d\n", act_cap_mah);
+
+ /* estimated battery current at the CC to CV transition */
+ switch (chip->ttf.mode) {
+ case TTF_MODE_NORMAL:
+ i_cc2cv = ibatt_avg * vbatt_avg /
+ max(MILLI_UNIT, chip->bp.float_volt_uv / MILLI_UNIT);
+ break;
+ case TTF_MODE_QNOVO:
+ i_cc2cv = min(
+ chip->ttf.cc_step.arr[MAX_CC_STEPS - 1] / MILLI_UNIT,
+ ibatt_avg * vbatt_avg /
+ max(MILLI_UNIT, chip->bp.float_volt_uv / MILLI_UNIT));
+ break;
+ default:
+ pr_err("TTF mode %d is not supported\n", chip->ttf.mode);
+ break;
+ }
+ fg_dbg(chip, FG_TTF, "i_cc2cv=%d\n", i_cc2cv);
+
+ /* if we are already in CV state then we can skip estimating CC */
+ if (chip->charge_type == POWER_SUPPLY_CHARGE_TYPE_TAPER)
+ goto cv_estimate;
+
+ /* estimated SOC at the CC to CV transition */
+ soc_cc2cv = DIV_ROUND_CLOSEST(rbatt * i_cc2cv, OCV_SLOPE_UV);
+ soc_cc2cv = 100 - soc_cc2cv;
+ fg_dbg(chip, FG_TTF, "soc_cc2cv=%d\n", soc_cc2cv);
+
+ switch (chip->ttf.mode) {
+ case TTF_MODE_NORMAL:
+ if (soc_cc2cv - msoc <= 0)
+ goto cv_estimate;
+
+ divisor = max(100, (ibatt_avg + i_cc2cv) / 2 * 100);
+ t_predicted = div_s64((s64)act_cap_mah * (soc_cc2cv - msoc) *
+ HOURS_TO_SECONDS, divisor);
+ break;
+ case TTF_MODE_QNOVO:
+ soc_per_step = 100 / MAX_CC_STEPS;
+ for (i = msoc / soc_per_step; i < MAX_CC_STEPS - 1; ++i) {
+ msoc_next_step = (i + 1) * soc_per_step;
+ if (i == msoc / soc_per_step)
+ msoc_this_step = msoc;
+ else
+ msoc_this_step = i * soc_per_step;
+
+ /* scale ibatt by 85% to account for discharge pulses */
+ ibatt_this_step = min(
+ chip->ttf.cc_step.arr[i] / MILLI_UNIT,
+ ibatt_avg) * 85 / 100;
+ divisor = max(100, ibatt_this_step * 100);
+ t_predicted_this_step = div_s64((s64)act_cap_mah *
+ (msoc_next_step - msoc_this_step) *
+ HOURS_TO_SECONDS, divisor);
+ t_predicted += t_predicted_this_step;
+ fg_dbg(chip, FG_TTF, "[%d, %d] ma=%d t=%d\n",
+ msoc_this_step, msoc_next_step,
+ ibatt_this_step, t_predicted_this_step);
+ }
+ break;
+ default:
+ pr_err("TTF mode %d is not supported\n", chip->ttf.mode);
+ break;
+ }
+
+cv_estimate:
+ fg_dbg(chip, FG_TTF, "t_predicted_cc=%d\n", t_predicted);
+
+ iterm = max(100, abs(chip->dt.sys_term_curr_ma) + 200);
+ fg_dbg(chip, FG_TTF, "iterm=%d\n", iterm);
+
+ if (chip->charge_type == POWER_SUPPLY_CHARGE_TYPE_TAPER)
+ tau = max(MILLI_UNIT, ibatt_avg * MILLI_UNIT / iterm);
+ else
+ tau = max(MILLI_UNIT, i_cc2cv * MILLI_UNIT / iterm);
+
+ rc = fg_lerp(fg_ln_table, ARRAY_SIZE(fg_ln_table), tau, &tau);
+ if (rc < 0) {
+ pr_err("failed to interpolate tau rc=%d\n", rc);
+ return rc;
+ }
+
+ /* tau is scaled linearly from 95% to 100% SOC */
+ if (msoc >= 95)
+ tau = tau * 2 * (100 - msoc) / 10;
+
+ fg_dbg(chip, FG_TTF, "tau=%d\n", tau);
+ t_predicted_cv = div_s64((s64)act_cap_mah * rbatt * tau *
+ HOURS_TO_SECONDS, NANO_UNIT);
+ fg_dbg(chip, FG_TTF, "t_predicted_cv=%d\n", t_predicted_cv);
+ t_predicted += t_predicted_cv;
+
+ fg_dbg(chip, FG_TTF, "t_predicted_prefilter=%d\n", t_predicted);
+ if (chip->ttf.last_ms != 0) {
+ delta_ms = ktime_ms_delta(ktime_get_boottime(),
+ ms_to_ktime(chip->ttf.last_ms));
+ if (delta_ms > 10000) {
+ ttf_slope = div64_s64(
+ (s64)(t_predicted - chip->ttf.last_ttf) *
+ MICRO_UNIT, delta_ms);
+ if (ttf_slope > -100)
+ ttf_slope = -100;
+ else if (ttf_slope < -2000)
+ ttf_slope = -2000;
+
+ t_predicted = div_s64(
+ (s64)ttf_slope * delta_ms, MICRO_UNIT) +
+ chip->ttf.last_ttf;
+ fg_dbg(chip, FG_TTF, "ttf_slope=%d\n", ttf_slope);
+ } else {
+ t_predicted = chip->ttf.last_ttf;
+ }
+ }
+
+ /* clamp the ttf to 0 */
+ if (t_predicted < 0)
+ t_predicted = 0;
+
+ fg_dbg(chip, FG_TTF, "t_predicted_postfilter=%d\n", t_predicted);
+ *val = t_predicted;
+ return 0;
+}
+
+static int fg_get_time_to_full(struct fg_chip *chip, int *val)
+{
+ int rc;
+
+ mutex_lock(&chip->ttf.lock);
+ rc = fg_get_time_to_full_locked(chip, val);
+ mutex_unlock(&chip->ttf.lock);
+ return rc;
+}
+
+#define CENTI_ICORRECT_C0 105
+#define CENTI_ICORRECT_C1 20
+static int fg_get_time_to_empty(struct fg_chip *chip, int *val)
+{
+ int rc, ibatt_avg, msoc, full_soc, act_cap_mah, divisor;
+
+ rc = fg_circ_buf_median(&chip->ttf.ibatt, &ibatt_avg);
+ if (rc < 0) {
+ /* try to get instantaneous current */
+ rc = fg_get_battery_current(chip, &ibatt_avg);
+ if (rc < 0) {
+ pr_err("failed to get battery current, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ ibatt_avg /= MILLI_UNIT;
+ /* clamp ibatt_avg to 100mA */
+ if (ibatt_avg < 100)
+ ibatt_avg = 100;
+
+ rc = fg_get_prop_capacity(chip, &msoc);
+ if (rc < 0) {
+ pr_err("Error in getting capacity, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_ACT_BATT_CAP, &act_cap_mah);
+ if (rc < 0) {
+ pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_FULL_SOC, &full_soc);
+ if (rc < 0) {
+ pr_err("failed to get full soc rc=%d\n", rc);
+ return rc;
+ }
+ full_soc = DIV_ROUND_CLOSEST(((u16)full_soc >> 8) * FULL_CAPACITY,
+ FULL_SOC_RAW);
+ act_cap_mah = full_soc * act_cap_mah / 100;
+
+ divisor = CENTI_ICORRECT_C0 * 100 + CENTI_ICORRECT_C1 * msoc;
+ divisor = ibatt_avg * divisor / 100;
+ divisor = max(100, divisor);
+ *val = act_cap_mah * msoc * HOURS_TO_SECONDS / divisor;
+ return 0;
+}
+
+static int fg_update_maint_soc(struct fg_chip *chip)
+{
+ int rc = 0, msoc;
+
+ mutex_lock(&chip->charge_full_lock);
+ if (chip->delta_soc <= 0)
+ goto out;
+
+ rc = fg_get_msoc(chip, &msoc);
+ if (rc < 0) {
+ pr_err("Error in getting msoc, rc=%d\n", rc);
+ goto out;
+ }
+
+ if (msoc > chip->maint_soc) {
+ /*
+ * When the monotonic SOC goes above maintenance SOC, we should
+ * stop showing the maintenance SOC.
+ */
+ chip->delta_soc = 0;
+ chip->maint_soc = 0;
+ } else if (msoc <= chip->last_msoc) {
+ /* MSOC is decreasing. Decrease maintenance SOC as well */
+ chip->maint_soc -= 1;
+ if (!(msoc % 10)) {
+ /*
+ * Reduce the maintenance SOC additionally by 1 whenever
+ * it crosses a SOC multiple of 10.
+ */
+ chip->maint_soc -= 1;
+ chip->delta_soc -= 1;
+ }
+ }
+
+ fg_dbg(chip, FG_IRQ, "msoc: %d last_msoc: %d maint_soc: %d delta_soc: %d\n",
+ msoc, chip->last_msoc, chip->maint_soc, chip->delta_soc);
+ chip->last_msoc = msoc;
+out:
+ mutex_unlock(&chip->charge_full_lock);
+ return rc;
+}
+
+static int fg_esr_validate(struct fg_chip *chip)
+{
+ int rc, esr_uohms;
+ u8 buf[2];
+
+ if (chip->dt.esr_clamp_mohms <= 0)
+ return 0;
+
+ rc = fg_get_sram_prop(chip, FG_SRAM_ESR, &esr_uohms);
+ if (rc < 0) {
+ pr_err("failed to get ESR, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (esr_uohms >= chip->dt.esr_clamp_mohms * 1000) {
+ pr_debug("ESR %d is > ESR_clamp\n", esr_uohms);
+ return 0;
+ }
+
+ esr_uohms = chip->dt.esr_clamp_mohms * 1000;
+ fg_encode(chip->sp, FG_SRAM_ESR, esr_uohms, buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR].addr_word,
+ chip->sp[FG_SRAM_ESR].addr_byte, buf,
+ chip->sp[FG_SRAM_ESR].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing ESR, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_dbg(chip, FG_STATUS, "ESR clamped to %duOhms\n", esr_uohms);
+ return 0;
+}
+
+static int fg_force_esr_meas(struct fg_chip *chip)
+{
+ int rc;
+ int esr_uohms;
+
+ /* force esr extraction enable */
+ rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD,
+ ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), BIT(0),
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("failed to enable esr extn rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip),
+ LD_REG_CTRL_BIT, 0);
+ if (rc < 0) {
+ pr_err("Error in configuring qnovo_cfg rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_masked_write(chip, BATT_INFO_TM_MISC1(chip),
+ ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT,
+ ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT);
+ if (rc < 0) {
+ pr_err("Error in configuring force ESR rc=%d\n", rc);
+ return rc;
+ }
+
+ /* wait 1.5 seconds for hw to measure ESR */
+ msleep(1500);
+ rc = fg_masked_write(chip, BATT_INFO_TM_MISC1(chip),
+ ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT,
+ 0);
+ if (rc < 0) {
+ pr_err("Error in restoring force ESR rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip),
+ LD_REG_CTRL_BIT, LD_REG_CTRL_BIT);
+ if (rc < 0) {
+ pr_err("Error in restoring qnovo_cfg rc=%d\n", rc);
+ return rc;
+ }
+
+ /* force esr extraction disable */
+ rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD,
+ ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), 0,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("failed to disable esr extn rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_get_battery_resistance(chip, &esr_uohms);
+ fg_dbg(chip, FG_STATUS, "ESR uohms = %d\n", esr_uohms);
+
+ return rc;
+}
+
+static int fg_prepare_for_qnovo(struct fg_chip *chip, int qnovo_enable)
+{
+ int rc;
+
+ /* force esr extraction disable when qnovo enables */
+ rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD,
+ ESR_EXTRACTION_ENABLE_OFFSET,
+ BIT(0), qnovo_enable ? 0 : BIT(0),
+ FG_IMA_DEFAULT);
+ if (rc < 0)
+ pr_err("Error in configuring esr extraction rc=%d\n", rc);
+
+ rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip),
+ LD_REG_CTRL_BIT,
+ qnovo_enable ? LD_REG_CTRL_BIT : 0);
+ if (rc < 0) {
+ pr_err("Error in configuring qnovo_cfg rc=%d\n", rc);
+ return rc;
+ }
+ fg_dbg(chip, FG_STATUS, "Prepared for Qnovo\n");
+ return 0;
+}
+
+static void ttf_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work, struct fg_chip,
+ ttf_work.work);
+ int rc, ibatt_now, vbatt_now, ttf;
+ ktime_t ktime_now;
+
+ mutex_lock(&chip->ttf.lock);
+ if (chip->charge_status != POWER_SUPPLY_STATUS_CHARGING &&
+ chip->charge_status != POWER_SUPPLY_STATUS_DISCHARGING)
+ goto end_work;
+
+ rc = fg_get_battery_current(chip, &ibatt_now);
+ if (rc < 0) {
+ pr_err("failed to get battery current, rc=%d\n", rc);
+ goto end_work;
+ }
+
+ rc = fg_get_battery_voltage(chip, &vbatt_now);
+ if (rc < 0) {
+ pr_err("failed to get battery voltage, rc=%d\n", rc);
+ goto end_work;
+ }
+
+ fg_circ_buf_add(&chip->ttf.ibatt, ibatt_now);
+ fg_circ_buf_add(&chip->ttf.vbatt, vbatt_now);
+
+ if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) {
+ rc = fg_get_time_to_full_locked(chip, &ttf);
+ if (rc < 0) {
+ pr_err("failed to get ttf, rc=%d\n", rc);
+ goto end_work;
+ }
+
+ /* keep the wake lock and prime the IBATT and VBATT buffers */
+ if (ttf < 0) {
+ /* delay for one FG cycle */
+ schedule_delayed_work(&chip->ttf_work,
+ msecs_to_jiffies(1500));
+ mutex_unlock(&chip->ttf.lock);
+ return;
+ }
+
+ /* update the TTF reference point every minute */
+ ktime_now = ktime_get_boottime();
+ if (ktime_ms_delta(ktime_now,
+ ms_to_ktime(chip->ttf.last_ms)) > 60000 ||
+ chip->ttf.last_ms == 0) {
+ chip->ttf.last_ttf = ttf;
+ chip->ttf.last_ms = ktime_to_ms(ktime_now);
+ }
+ }
+
+ /* recurse every 10 seconds */
+ schedule_delayed_work(&chip->ttf_work, msecs_to_jiffies(10000));
+end_work:
+ vote(chip->awake_votable, TTF_PRIMING, false, 0);
+ mutex_unlock(&chip->ttf.lock);
+}
+
+/* PSY CALLBACKS STAY HERE */
+
+static int fg_psy_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *pval)
+{
+ struct fg_chip *chip = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CAPACITY:
+ rc = fg_get_prop_capacity(chip, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (chip->battery_missing)
+ pval->intval = 3700000;
+ else
+ rc = fg_get_battery_voltage(chip, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ rc = fg_get_battery_current(chip, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ rc = fg_get_battery_temp(chip, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_RESISTANCE:
+ rc = fg_get_battery_resistance(chip, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ rc = fg_get_sram_prop(chip, FG_SRAM_OCV, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ pval->intval = chip->cl.nom_cap_uah;
+ break;
+ case POWER_SUPPLY_PROP_RESISTANCE_ID:
+ pval->intval = chip->batt_id_ohms;
+ break;
+ case POWER_SUPPLY_PROP_BATTERY_TYPE:
+ pval->strval = fg_get_battery_type(chip);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ pval->intval = chip->bp.float_volt_uv;
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ pval->intval = fg_get_cycle_count(chip);
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT_ID:
+ pval->intval = chip->cyc_ctr.id;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW_RAW:
+ rc = fg_get_charge_raw(chip, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ pval->intval = chip->cl.init_cc_uah;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ pval->intval = chip->cl.learned_cc_uah;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ rc = fg_get_charge_counter(chip, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+ rc = fg_get_time_to_full(chip, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ rc = fg_get_time_to_empty(chip, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_SOC_REPORTING_READY:
+ pval->intval = chip->soc_reporting_ready;
+ break;
+ case POWER_SUPPLY_PROP_DEBUG_BATTERY:
+ pval->intval = is_debug_batt_id(chip);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ rc = fg_get_sram_prop(chip, FG_SRAM_VBATT_FULL, &pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CC_STEP:
+ if ((chip->ttf.cc_step.sel >= 0) &&
+ (chip->ttf.cc_step.sel < MAX_CC_STEPS)) {
+ pval->intval =
+ chip->ttf.cc_step.arr[chip->ttf.cc_step.sel];
+ } else {
+ pr_err("cc_step_sel is out of bounds [0, %d]\n",
+ chip->ttf.cc_step.sel);
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CC_STEP_SEL:
+ pval->intval = chip->ttf.cc_step.sel;
+ break;
+ default:
+ pr_err("unsupported property %d\n", psp);
+ rc = -EINVAL;
+ break;
+ }
+
+ if (rc < 0)
+ return -ENODATA;
+
+ return 0;
+}
+
+static int fg_psy_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *pval)
+{
+ struct fg_chip *chip = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CYCLE_COUNT_ID:
+ if ((pval->intval > 0) && (pval->intval <= BUCKET_COUNT)) {
+ chip->cyc_ctr.id = pval->intval;
+ } else {
+ pr_err("rejecting invalid cycle_count_id = %d\n",
+ pval->intval);
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ rc = fg_set_constant_chg_voltage(chip, pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_RESISTANCE:
+ rc = fg_force_esr_meas(chip);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE:
+ rc = fg_prepare_for_qnovo(chip, pval->intval);
+ break;
+ case POWER_SUPPLY_PROP_CC_STEP:
+ if ((chip->ttf.cc_step.sel >= 0) &&
+ (chip->ttf.cc_step.sel < MAX_CC_STEPS)) {
+ chip->ttf.cc_step.arr[chip->ttf.cc_step.sel] =
+ pval->intval;
+ } else {
+ pr_err("cc_step_sel is out of bounds [0, %d]\n",
+ chip->ttf.cc_step.sel);
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CC_STEP_SEL:
+ if ((pval->intval >= 0) && (pval->intval < MAX_CC_STEPS)) {
+ chip->ttf.cc_step.sel = pval->intval;
+ } else {
+ pr_err("cc_step_sel is out of bounds [0, %d]\n",
+ pval->intval);
+ return -EINVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+static int fg_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CYCLE_COUNT_ID:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ case POWER_SUPPLY_PROP_CC_STEP:
+ case POWER_SUPPLY_PROP_CC_STEP_SEL:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void fg_external_power_changed(struct power_supply *psy)
+{
+ pr_debug("power supply changed\n");
+}
+
+static int fg_notifier_cb(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct power_supply *psy = data;
+ struct fg_chip *chip = container_of(nb, struct fg_chip, nb);
+
+ if (event != PSY_EVENT_PROP_CHANGED)
+ return NOTIFY_OK;
+
+ if (work_pending(&chip->status_change_work))
+ return NOTIFY_OK;
+
+ if ((strcmp(psy->desc->name, "battery") == 0)
+ || (strcmp(psy->desc->name, "usb") == 0)) {
+ /*
+ * We cannot vote for awake votable here as that takes
+ * a mutex lock and this is executed in an atomic context.
+ */
+ pm_stay_awake(chip->dev);
+ schedule_work(&chip->status_change_work);
+ }
+
+ return NOTIFY_OK;
+}
+
+static enum power_supply_property fg_psy_props[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_RESISTANCE_ID,
+ POWER_SUPPLY_PROP_RESISTANCE,
+ POWER_SUPPLY_PROP_BATTERY_TYPE,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_CYCLE_COUNT_ID,
+ POWER_SUPPLY_PROP_CHARGE_NOW_RAW,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_SOC_REPORTING_READY,
+ POWER_SUPPLY_PROP_DEBUG_BATTERY,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ POWER_SUPPLY_PROP_CC_STEP,
+ POWER_SUPPLY_PROP_CC_STEP_SEL,
+};
+
+static const struct power_supply_desc fg_psy_desc = {
+ .name = "bms",
+ .type = POWER_SUPPLY_TYPE_BMS,
+ .properties = fg_psy_props,
+ .num_properties = ARRAY_SIZE(fg_psy_props),
+ .get_property = fg_psy_get_property,
+ .set_property = fg_psy_set_property,
+ .external_power_changed = fg_external_power_changed,
+ .property_is_writeable = fg_property_is_writeable,
+};
+
+/* INIT FUNCTIONS STAY HERE */
+
+#define DEFAULT_ESR_CHG_TIMER_RETRY 8
+#define DEFAULT_ESR_CHG_TIMER_MAX 16
+static int fg_hw_init(struct fg_chip *chip)
+{
+ int rc;
+ u8 buf[4], val;
+
+ fg_encode(chip->sp, FG_SRAM_CUTOFF_VOLT, chip->dt.cutoff_volt_mv, buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_CUTOFF_VOLT].addr_word,
+ chip->sp[FG_SRAM_CUTOFF_VOLT].addr_byte, buf,
+ chip->sp[FG_SRAM_CUTOFF_VOLT].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing cutoff_volt, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_encode(chip->sp, FG_SRAM_EMPTY_VOLT, chip->dt.empty_volt_mv, buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_EMPTY_VOLT].addr_word,
+ chip->sp[FG_SRAM_EMPTY_VOLT].addr_byte, buf,
+ chip->sp[FG_SRAM_EMPTY_VOLT].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing empty_volt, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_encode(chip->sp, FG_SRAM_CHG_TERM_CURR, chip->dt.chg_term_curr_ma,
+ buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_CHG_TERM_CURR].addr_word,
+ chip->sp[FG_SRAM_CHG_TERM_CURR].addr_byte, buf,
+ chip->sp[FG_SRAM_CHG_TERM_CURR].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing chg_term_curr, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_encode(chip->sp, FG_SRAM_SYS_TERM_CURR, chip->dt.sys_term_curr_ma,
+ buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_SYS_TERM_CURR].addr_word,
+ chip->sp[FG_SRAM_SYS_TERM_CURR].addr_byte, buf,
+ chip->sp[FG_SRAM_SYS_TERM_CURR].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing sys_term_curr, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (!(chip->wa_flags & PMI8998_V1_REV_WA)) {
+ fg_encode(chip->sp, FG_SRAM_CHG_TERM_BASE_CURR,
+ chip->dt.chg_term_base_curr_ma, buf);
+ rc = fg_sram_write(chip,
+ chip->sp[FG_SRAM_CHG_TERM_BASE_CURR].addr_word,
+ chip->sp[FG_SRAM_CHG_TERM_BASE_CURR].addr_byte,
+ buf, chip->sp[FG_SRAM_CHG_TERM_BASE_CURR].len,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing chg_term_base_curr, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (chip->dt.vbatt_low_thr_mv > 0) {
+ fg_encode(chip->sp, FG_SRAM_VBATT_LOW,
+ chip->dt.vbatt_low_thr_mv, buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_VBATT_LOW].addr_word,
+ chip->sp[FG_SRAM_VBATT_LOW].addr_byte, buf,
+ chip->sp[FG_SRAM_VBATT_LOW].len,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing vbatt_low_thr, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (chip->dt.delta_soc_thr > 0 && chip->dt.delta_soc_thr < 100) {
+ fg_encode(chip->sp, FG_SRAM_DELTA_MSOC_THR,
+ chip->dt.delta_soc_thr, buf);
+ rc = fg_sram_write(chip,
+ chip->sp[FG_SRAM_DELTA_MSOC_THR].addr_word,
+ chip->sp[FG_SRAM_DELTA_MSOC_THR].addr_byte,
+ buf, chip->sp[FG_SRAM_DELTA_MSOC_THR].len,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing delta_msoc_thr, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_encode(chip->sp, FG_SRAM_DELTA_BSOC_THR,
+ chip->dt.delta_soc_thr, buf);
+ rc = fg_sram_write(chip,
+ chip->sp[FG_SRAM_DELTA_BSOC_THR].addr_word,
+ chip->sp[FG_SRAM_DELTA_BSOC_THR].addr_byte,
+ buf, chip->sp[FG_SRAM_DELTA_BSOC_THR].len,
+ FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing delta_bsoc_thr, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ /*
+ * configure battery thermal coefficients c1,c2,c3
+ * if its value is not zero.
+ */
+ if (chip->dt.batt_therm_coeffs[0] > 0) {
+ rc = fg_write(chip, BATT_INFO_THERM_C1(chip),
+ chip->dt.batt_therm_coeffs, BATT_THERM_NUM_COEFFS);
+ if (rc < 0) {
+ pr_err("Error in writing battery thermal coefficients, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+
+ if (chip->dt.recharge_soc_thr > 0 && chip->dt.recharge_soc_thr < 100) {
+ rc = fg_set_recharge_soc(chip, chip->dt.recharge_soc_thr);
+ if (rc < 0) {
+ pr_err("Error in setting recharge_soc, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (chip->dt.recharge_volt_thr_mv > 0) {
+ rc = fg_set_recharge_voltage(chip,
+ chip->dt.recharge_volt_thr_mv);
+ if (rc < 0) {
+ pr_err("Error in setting recharge_voltage, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (chip->dt.rsense_sel >= SRC_SEL_BATFET &&
+ chip->dt.rsense_sel < SRC_SEL_RESERVED) {
+ rc = fg_masked_write(chip, BATT_INFO_IBATT_SENSING_CFG(chip),
+ SOURCE_SELECT_MASK, chip->dt.rsense_sel);
+ if (rc < 0) {
+ pr_err("Error in writing rsense_sel, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ get_temp_setpoint(chip->dt.jeita_thresholds[JEITA_COLD], &val);
+ rc = fg_write(chip, BATT_INFO_JEITA_TOO_COLD(chip), &val, 1);
+ if (rc < 0) {
+ pr_err("Error in writing jeita_cold, rc=%d\n", rc);
+ return rc;
+ }
+
+ get_temp_setpoint(chip->dt.jeita_thresholds[JEITA_COOL], &val);
+ rc = fg_write(chip, BATT_INFO_JEITA_COLD(chip), &val, 1);
+ if (rc < 0) {
+ pr_err("Error in writing jeita_cool, rc=%d\n", rc);
+ return rc;
+ }
+
+ get_temp_setpoint(chip->dt.jeita_thresholds[JEITA_WARM], &val);
+ rc = fg_write(chip, BATT_INFO_JEITA_HOT(chip), &val, 1);
+ if (rc < 0) {
+ pr_err("Error in writing jeita_warm, rc=%d\n", rc);
+ return rc;
+ }
+
+ get_temp_setpoint(chip->dt.jeita_thresholds[JEITA_HOT], &val);
+ rc = fg_write(chip, BATT_INFO_JEITA_TOO_HOT(chip), &val, 1);
+ if (rc < 0) {
+ pr_err("Error in writing jeita_hot, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chip->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE) {
+ chip->esr_timer_charging_default[TIMER_RETRY] =
+ DEFAULT_ESR_CHG_TIMER_RETRY;
+ chip->esr_timer_charging_default[TIMER_MAX] =
+ DEFAULT_ESR_CHG_TIMER_MAX;
+ } else {
+ /* We don't need this for pm660 at present */
+ chip->esr_timer_charging_default[TIMER_RETRY] = -EINVAL;
+ chip->esr_timer_charging_default[TIMER_MAX] = -EINVAL;
+ }
+
+ rc = fg_set_esr_timer(chip, chip->dt.esr_timer_charging[TIMER_RETRY],
+ chip->dt.esr_timer_charging[TIMER_MAX], true, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in setting ESR timer, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_set_esr_timer(chip, chip->dt.esr_timer_awake[TIMER_RETRY],
+ chip->dt.esr_timer_awake[TIMER_MAX], false, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in setting ESR timer, rc=%d\n", rc);
+ return rc;
+ }
+
+ restore_cycle_counter(chip);
+
+ if (chip->dt.jeita_hyst_temp >= 0) {
+ val = chip->dt.jeita_hyst_temp << JEITA_TEMP_HYST_SHIFT;
+ rc = fg_masked_write(chip, BATT_INFO_BATT_TEMP_CFG(chip),
+ JEITA_TEMP_HYST_MASK, val);
+ if (rc < 0) {
+ pr_err("Error in writing batt_temp_cfg, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ get_batt_temp_delta(chip->dt.batt_temp_delta, &val);
+ rc = fg_masked_write(chip, BATT_INFO_BATT_TMPR_INTR(chip),
+ CHANGE_THOLD_MASK, val);
+ if (rc < 0) {
+ pr_err("Error in writing batt_temp_delta, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_rconn_config(chip);
+ if (rc < 0) {
+ pr_err("Error in configuring Rconn, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_encode(chip->sp, FG_SRAM_ESR_TIGHT_FILTER,
+ chip->dt.esr_tight_flt_upct, buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_word,
+ chip->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_byte, buf,
+ chip->sp[FG_SRAM_ESR_TIGHT_FILTER].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing ESR tight filter, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_encode(chip->sp, FG_SRAM_ESR_BROAD_FILTER,
+ chip->dt.esr_broad_flt_upct, buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR_BROAD_FILTER].addr_word,
+ chip->sp[FG_SRAM_ESR_BROAD_FILTER].addr_byte, buf,
+ chip->sp[FG_SRAM_ESR_BROAD_FILTER].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing ESR broad filter, rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_encode(chip->sp, FG_SRAM_ESR_PULSE_THRESH,
+ chip->dt.esr_pulse_thresh_ma, buf);
+ rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR_PULSE_THRESH].addr_word,
+ chip->sp[FG_SRAM_ESR_PULSE_THRESH].addr_byte, buf,
+ chip->sp[FG_SRAM_ESR_PULSE_THRESH].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing esr_pulse_thresh_ma, rc=%d\n", rc);
+ return rc;
+ }
+
+ get_esr_meas_current(chip->dt.esr_meas_curr_ma, &val);
+ rc = fg_masked_write(chip, BATT_INFO_ESR_PULL_DN_CFG(chip),
+ ESR_PULL_DOWN_IVAL_MASK, val);
+ if (rc < 0) {
+ pr_err("Error in writing esr_meas_curr_ma, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (is_debug_batt_id(chip)) {
+ val = ESR_NO_PULL_DOWN;
+ rc = fg_masked_write(chip, BATT_INFO_ESR_PULL_DN_CFG(chip),
+ ESR_PULL_DOWN_MODE_MASK, val);
+ if (rc < 0) {
+ pr_err("Error in writing esr_pull_down, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int fg_memif_init(struct fg_chip *chip)
+{
+ return fg_ima_init(chip);
+}
+
+static int fg_adjust_timebase(struct fg_chip *chip)
+{
+ int rc = 0, die_temp;
+ s32 time_base = 0;
+ u8 buf[2] = {0};
+
+ if ((chip->wa_flags & PM660_TSMC_OSC_WA) && chip->die_temp_chan) {
+ rc = iio_read_channel_processed(chip->die_temp_chan, &die_temp);
+ if (rc < 0) {
+ pr_err("Error in reading die_temp, rc:%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_lerp(fg_tsmc_osc_table, ARRAY_SIZE(fg_tsmc_osc_table),
+ die_temp / 1000, &time_base);
+ if (rc < 0) {
+ pr_err("Error to lookup fg_tsmc_osc_table rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_encode(chip->sp, FG_SRAM_TIMEBASE, time_base, buf);
+ rc = fg_sram_write(chip,
+ chip->sp[FG_SRAM_TIMEBASE].addr_word,
+ chip->sp[FG_SRAM_TIMEBASE].addr_byte, buf,
+ chip->sp[FG_SRAM_TIMEBASE].len, FG_IMA_DEFAULT);
+ if (rc < 0) {
+ pr_err("Error in writing timebase, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/* INTERRUPT HANDLERS STAY HERE */
+
+static irqreturn_t fg_mem_xcp_irq_handler(int irq, void *data)
+{
+ struct fg_chip *chip = data;
+ u8 status;
+ int rc;
+
+ rc = fg_read(chip, MEM_IF_INT_RT_STS(chip), &status, 1);
+ if (rc < 0) {
+ pr_err("failed to read addr=0x%04x, rc=%d\n",
+ MEM_IF_INT_RT_STS(chip), rc);
+ return IRQ_HANDLED;
+ }
+
+ fg_dbg(chip, FG_IRQ, "irq %d triggered, status:%d\n", irq, status);
+
+ mutex_lock(&chip->sram_rw_lock);
+ rc = fg_clear_dma_errors_if_any(chip);
+ if (rc < 0)
+ pr_err("Error in clearing DMA error, rc=%d\n", rc);
+
+ if (status & MEM_XCP_BIT) {
+ rc = fg_clear_ima_errors_if_any(chip, true);
+ if (rc < 0 && rc != -EAGAIN)
+ pr_err("Error in checking IMA errors rc:%d\n", rc);
+ }
+
+ mutex_unlock(&chip->sram_rw_lock);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_vbatt_low_irq_handler(int irq, void *data)
+{
+ struct fg_chip *chip = data;
+
+ fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_batt_missing_irq_handler(int irq, void *data)
+{
+ struct fg_chip *chip = data;
+ u8 status;
+ int rc;
+
+ rc = fg_read(chip, BATT_INFO_INT_RT_STS(chip), &status, 1);
+ if (rc < 0) {
+ pr_err("failed to read addr=0x%04x, rc=%d\n",
+ BATT_INFO_INT_RT_STS(chip), rc);
+ return IRQ_HANDLED;
+ }
+
+ fg_dbg(chip, FG_IRQ, "irq %d triggered sts:%d\n", irq, status);
+ chip->battery_missing = (status & BT_MISS_BIT);
+
+ if (chip->battery_missing) {
+ chip->profile_available = false;
+ chip->profile_loaded = false;
+ chip->soc_reporting_ready = false;
+ return IRQ_HANDLED;
+ }
+
+ clear_battery_profile(chip);
+ schedule_delayed_work(&chip->profile_load_work, 0);
+
+ if (chip->fg_psy)
+ power_supply_changed(chip->fg_psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_delta_batt_temp_irq_handler(int irq, void *data)
+{
+ struct fg_chip *chip = data;
+ union power_supply_propval prop = {0, };
+ int rc, batt_temp;
+
+ fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+ rc = fg_get_battery_temp(chip, &batt_temp);
+ if (rc < 0) {
+ pr_err("Error in getting batt_temp\n");
+ return IRQ_HANDLED;
+ }
+
+ rc = fg_esr_filter_config(chip, batt_temp);
+ if (rc < 0)
+ pr_err("Error in configuring ESR filter rc:%d\n", rc);
+
+ rc = fg_slope_limit_config(chip, batt_temp);
+ if (rc < 0)
+ pr_err("Error in configuring slope limiter rc:%d\n", rc);
+
+ rc = fg_adjust_ki_coeff_full_soc(chip, batt_temp);
+ if (rc < 0)
+ pr_err("Error in configuring ki_coeff_full_soc rc:%d\n", rc);
+
+ if (!batt_psy_initialized(chip)) {
+ chip->last_batt_temp = batt_temp;
+ return IRQ_HANDLED;
+ }
+
+ power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_HEALTH,
+ &prop);
+ chip->health = prop.intval;
+
+ if (chip->last_batt_temp != batt_temp) {
+ rc = fg_adjust_timebase(chip);
+ if (rc < 0)
+ pr_err("Error in adjusting timebase, rc=%d\n", rc);
+
+ rc = fg_adjust_recharge_voltage(chip);
+ if (rc < 0)
+ pr_err("Error in adjusting recharge_voltage, rc=%d\n",
+ rc);
+
+ chip->last_batt_temp = batt_temp;
+ power_supply_changed(chip->batt_psy);
+ }
+
+ if (abs(chip->last_batt_temp - batt_temp) > 30)
+ pr_warn("Battery temperature last:%d current: %d\n",
+ chip->last_batt_temp, batt_temp);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_first_est_irq_handler(int irq, void *data)
+{
+ struct fg_chip *chip = data;
+
+ fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+ complete_all(&chip->soc_ready);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_soc_update_irq_handler(int irq, void *data)
+{
+ struct fg_chip *chip = data;
+
+ fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+ complete_all(&chip->soc_update);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_delta_bsoc_irq_handler(int irq, void *data)
+{
+ struct fg_chip *chip = data;
+ int rc;
+
+ fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+ rc = fg_charge_full_update(chip);
+ if (rc < 0)
+ pr_err("Error in charge_full_update, rc=%d\n", rc);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_delta_msoc_irq_handler(int irq, void *data)
+{
+ struct fg_chip *chip = data;
+ int rc;
+
+ fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+ fg_cycle_counter_update(chip);
+
+ if (chip->cl.active)
+ fg_cap_learning_update(chip);
+
+ rc = fg_charge_full_update(chip);
+ if (rc < 0)
+ pr_err("Error in charge_full_update, rc=%d\n", rc);
+
+ rc = fg_adjust_ki_coeff_dischg(chip);
+ if (rc < 0)
+ pr_err("Error in adjusting ki_coeff_dischg, rc=%d\n", rc);
+
+ rc = fg_update_maint_soc(chip);
+ if (rc < 0)
+ pr_err("Error in updating maint_soc, rc=%d\n", rc);
+
+ rc = fg_esr_validate(chip);
+ if (rc < 0)
+ pr_err("Error in validating ESR, rc=%d\n", rc);
+
+ rc = fg_adjust_timebase(chip);
+ if (rc < 0)
+ pr_err("Error in adjusting timebase, rc=%d\n", rc);
+
+ if (batt_psy_initialized(chip))
+ power_supply_changed(chip->batt_psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_empty_soc_irq_handler(int irq, void *data)
+{
+ struct fg_chip *chip = data;
+
+ fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+ if (batt_psy_initialized(chip))
+ power_supply_changed(chip->batt_psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_soc_irq_handler(int irq, void *data)
+{
+ struct fg_chip *chip = data;
+
+ fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_dummy_irq_handler(int irq, void *data)
+{
+ pr_debug("irq %d triggered\n", irq);
+ return IRQ_HANDLED;
+}
+
+static struct fg_irq_info fg_irqs[FG_IRQ_MAX] = {
+ /* BATT_SOC irqs */
+ [MSOC_FULL_IRQ] = {
+ .name = "msoc-full",
+ .handler = fg_soc_irq_handler,
+ },
+ [MSOC_HIGH_IRQ] = {
+ .name = "msoc-high",
+ .handler = fg_soc_irq_handler,
+ .wakeable = true,
+ },
+ [MSOC_EMPTY_IRQ] = {
+ .name = "msoc-empty",
+ .handler = fg_empty_soc_irq_handler,
+ .wakeable = true,
+ },
+ [MSOC_LOW_IRQ] = {
+ .name = "msoc-low",
+ .handler = fg_soc_irq_handler,
+ .wakeable = true,
+ },
+ [MSOC_DELTA_IRQ] = {
+ .name = "msoc-delta",
+ .handler = fg_delta_msoc_irq_handler,
+ .wakeable = true,
+ },
+ [BSOC_DELTA_IRQ] = {
+ .name = "bsoc-delta",
+ .handler = fg_delta_bsoc_irq_handler,
+ .wakeable = true,
+ },
+ [SOC_READY_IRQ] = {
+ .name = "soc-ready",
+ .handler = fg_first_est_irq_handler,
+ .wakeable = true,
+ },
+ [SOC_UPDATE_IRQ] = {
+ .name = "soc-update",
+ .handler = fg_soc_update_irq_handler,
+ },
+ /* BATT_INFO irqs */
+ [BATT_TEMP_DELTA_IRQ] = {
+ .name = "batt-temp-delta",
+ .handler = fg_delta_batt_temp_irq_handler,
+ .wakeable = true,
+ },
+ [BATT_MISSING_IRQ] = {
+ .name = "batt-missing",
+ .handler = fg_batt_missing_irq_handler,
+ .wakeable = true,
+ },
+ [ESR_DELTA_IRQ] = {
+ .name = "esr-delta",
+ .handler = fg_dummy_irq_handler,
+ },
+ [VBATT_LOW_IRQ] = {
+ .name = "vbatt-low",
+ .handler = fg_vbatt_low_irq_handler,
+ .wakeable = true,
+ },
+ [VBATT_PRED_DELTA_IRQ] = {
+ .name = "vbatt-pred-delta",
+ .handler = fg_dummy_irq_handler,
+ },
+ /* MEM_IF irqs */
+ [DMA_GRANT_IRQ] = {
+ .name = "dma-grant",
+ .handler = fg_dummy_irq_handler,
+ },
+ [MEM_XCP_IRQ] = {
+ .name = "mem-xcp",
+ .handler = fg_mem_xcp_irq_handler,
+ },
+ [IMA_RDY_IRQ] = {
+ .name = "ima-rdy",
+ .handler = fg_dummy_irq_handler,
+ },
+};
+
+static int fg_get_irq_index_byname(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fg_irqs); i++) {
+ if (strcmp(fg_irqs[i].name, name) == 0)
+ return i;
+ }
+
+ pr_err("%s is not in irq list\n", name);
+ return -ENOENT;
+}
+
+static int fg_register_interrupts(struct fg_chip *chip)
+{
+ struct device_node *child, *node = chip->dev->of_node;
+ struct property *prop;
+ const char *name;
+ int rc, irq, irq_index;
+
+ for_each_available_child_of_node(node, child) {
+ of_property_for_each_string(child, "interrupt-names", prop,
+ name) {
+ irq = of_irq_get_byname(child, name);
+ if (irq < 0) {
+ dev_err(chip->dev, "failed to get irq %s irq:%d\n",
+ name, irq);
+ return irq;
+ }
+
+ irq_index = fg_get_irq_index_byname(name);
+ if (irq_index < 0)
+ return irq_index;
+
+ rc = devm_request_threaded_irq(chip->dev, irq, NULL,
+ fg_irqs[irq_index].handler,
+ IRQF_ONESHOT, name, chip);
+ if (rc < 0) {
+ dev_err(chip->dev, "failed to register irq handler for %s rc:%d\n",
+ name, rc);
+ return rc;
+ }
+
+ fg_irqs[irq_index].irq = irq;
+ if (fg_irqs[irq_index].wakeable)
+ enable_irq_wake(fg_irqs[irq_index].irq);
+ }
+ }
+
+ return 0;
+}
+
+static int fg_parse_dt_property_u32_array(struct device_node *node,
+ const char *prop_name, int *buf, int len)
+{
+ int rc;
+
+ rc = of_property_count_elems_of_size(node, prop_name, sizeof(u32));
+ if (rc < 0) {
+ if (rc == -EINVAL)
+ return 0;
+ else
+ return rc;
+ } else if (rc != len) {
+ pr_err("Incorrect length %d for %s, rc=%d\n", len, prop_name,
+ rc);
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32_array(node, prop_name, buf, len);
+ if (rc < 0) {
+ pr_err("Error in reading %s, rc=%d\n", prop_name, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int fg_parse_slope_limit_coefficients(struct fg_chip *chip)
+{
+ struct device_node *node = chip->dev->of_node;
+ int rc, i;
+
+ rc = of_property_read_u32(node, "qcom,slope-limit-temp-threshold",
+ &chip->dt.slope_limit_temp);
+ if (rc < 0)
+ return 0;
+
+ rc = fg_parse_dt_property_u32_array(node, "qcom,slope-limit-coeffs",
+ chip->dt.slope_limit_coeffs, SLOPE_LIMIT_NUM_COEFFS);
+ if (rc < 0)
+ return rc;
+
+ for (i = 0; i < SLOPE_LIMIT_NUM_COEFFS; i++) {
+ if (chip->dt.slope_limit_coeffs[i] > SLOPE_LIMIT_COEFF_MAX ||
+ chip->dt.slope_limit_coeffs[i] < 0) {
+ pr_err("Incorrect slope limit coefficient\n");
+ return -EINVAL;
+ }
+ }
+
+ chip->slope_limit_en = true;
+ return 0;
+}
+
+static int fg_parse_ki_coefficients(struct fg_chip *chip)
+{
+ struct device_node *node = chip->dev->of_node;
+ int rc, i;
+
+ rc = fg_parse_dt_property_u32_array(node, "qcom,ki-coeff-soc-dischg",
+ chip->dt.ki_coeff_soc, KI_COEFF_SOC_LEVELS);
+ if (rc < 0)
+ return rc;
+
+ rc = fg_parse_dt_property_u32_array(node, "qcom,ki-coeff-med-dischg",
+ chip->dt.ki_coeff_med_dischg, KI_COEFF_SOC_LEVELS);
+ if (rc < 0)
+ return rc;
+
+ rc = fg_parse_dt_property_u32_array(node, "qcom,ki-coeff-hi-dischg",
+ chip->dt.ki_coeff_hi_dischg, KI_COEFF_SOC_LEVELS);
+ if (rc < 0)
+ return rc;
+
+ for (i = 0; i < KI_COEFF_SOC_LEVELS; i++) {
+ if (chip->dt.ki_coeff_soc[i] < 0 ||
+ chip->dt.ki_coeff_soc[i] > FULL_CAPACITY) {
+ pr_err("Error in ki_coeff_soc_dischg values\n");
+ return -EINVAL;
+ }
+
+ if (chip->dt.ki_coeff_med_dischg[i] < 0 ||
+ chip->dt.ki_coeff_med_dischg[i] > KI_COEFF_MAX) {
+ pr_err("Error in ki_coeff_med_dischg values\n");
+ return -EINVAL;
+ }
+
+ if (chip->dt.ki_coeff_med_dischg[i] < 0 ||
+ chip->dt.ki_coeff_med_dischg[i] > KI_COEFF_MAX) {
+ pr_err("Error in ki_coeff_med_dischg values\n");
+ return -EINVAL;
+ }
+ }
+ chip->ki_coeff_dischg_en = true;
+ return 0;
+}
+
+#define DEFAULT_CUTOFF_VOLT_MV 3200
+#define DEFAULT_EMPTY_VOLT_MV 2850
+#define DEFAULT_RECHARGE_VOLT_MV 4250
+#define DEFAULT_CHG_TERM_CURR_MA 100
+#define DEFAULT_CHG_TERM_BASE_CURR_MA 75
+#define DEFAULT_SYS_TERM_CURR_MA -125
+#define DEFAULT_DELTA_SOC_THR 1
+#define DEFAULT_RECHARGE_SOC_THR 95
+#define DEFAULT_BATT_TEMP_COLD 0
+#define DEFAULT_BATT_TEMP_COOL 5
+#define DEFAULT_BATT_TEMP_WARM 45
+#define DEFAULT_BATT_TEMP_HOT 50
+#define DEFAULT_CL_START_SOC 15
+#define DEFAULT_CL_MIN_TEMP_DECIDEGC 150
+#define DEFAULT_CL_MAX_TEMP_DECIDEGC 450
+#define DEFAULT_CL_MAX_INC_DECIPERC 5
+#define DEFAULT_CL_MAX_DEC_DECIPERC 100
+#define DEFAULT_CL_MIN_LIM_DECIPERC 0
+#define DEFAULT_CL_MAX_LIM_DECIPERC 0
+#define BTEMP_DELTA_LOW 2
+#define BTEMP_DELTA_HIGH 10
+#define DEFAULT_ESR_FLT_TEMP_DECIDEGC 100
+#define DEFAULT_ESR_TIGHT_FLT_UPCT 3907
+#define DEFAULT_ESR_BROAD_FLT_UPCT 99610
+#define DEFAULT_ESR_TIGHT_LT_FLT_UPCT 48829
+#define DEFAULT_ESR_BROAD_LT_FLT_UPCT 148438
+#define DEFAULT_ESR_CLAMP_MOHMS 20
+#define DEFAULT_ESR_PULSE_THRESH_MA 110
+#define DEFAULT_ESR_MEAS_CURR_MA 120
+static int fg_parse_dt(struct fg_chip *chip)
+{
+ struct device_node *child, *revid_node, *node = chip->dev->of_node;
+ u32 base, temp;
+ u8 subtype;
+ int rc;
+
+ if (!node) {
+ dev_err(chip->dev, "device tree node missing\n");
+ return -ENXIO;
+ }
+
+ revid_node = of_parse_phandle(node, "qcom,pmic-revid", 0);
+ if (!revid_node) {
+ pr_err("Missing qcom,pmic-revid property - driver failed\n");
+ return -EINVAL;
+ }
+
+ chip->pmic_rev_id = get_revid_data(revid_node);
+ if (IS_ERR_OR_NULL(chip->pmic_rev_id)) {
+ pr_err("Unable to get pmic_revid rc=%ld\n",
+ PTR_ERR(chip->pmic_rev_id));
+ /*
+ * the revid peripheral must be registered, any failure
+ * here only indicates that the rev-id module has not
+ * probed yet.
+ */
+ return -EPROBE_DEFER;
+ }
+
+ pr_debug("PMIC subtype %d Digital major %d\n",
+ chip->pmic_rev_id->pmic_subtype, chip->pmic_rev_id->rev4);
+
+ switch (chip->pmic_rev_id->pmic_subtype) {
+ case PMI8998_SUBTYPE:
+ if (chip->pmic_rev_id->rev4 < PMI8998_V2P0_REV4) {
+ chip->sp = pmi8998_v1_sram_params;
+ chip->alg_flags = pmi8998_v1_alg_flags;
+ chip->wa_flags |= PMI8998_V1_REV_WA;
+ } else if (chip->pmic_rev_id->rev4 == PMI8998_V2P0_REV4) {
+ chip->sp = pmi8998_v2_sram_params;
+ chip->alg_flags = pmi8998_v2_alg_flags;
+ } else {
+ return -EINVAL;
+ }
+ break;
+ case PM660_SUBTYPE:
+ chip->sp = pmi8998_v2_sram_params;
+ chip->alg_flags = pmi8998_v2_alg_flags;
+ chip->use_ima_single_mode = true;
+ if (chip->pmic_rev_id->fab_id == PM660_FAB_ID_TSMC)
+ chip->wa_flags |= PM660_TSMC_OSC_WA;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (of_get_available_child_count(node) == 0) {
+ dev_err(chip->dev, "No child nodes specified!\n");
+ return -ENXIO;
+ }
+
+ for_each_available_child_of_node(node, child) {
+ rc = of_property_read_u32(child, "reg", &base);
+ if (rc < 0) {
+ dev_err(chip->dev, "reg not specified in node %s, rc=%d\n",
+ child->full_name, rc);
+ return rc;
+ }
+
+ rc = fg_read(chip, base + PERPH_SUBTYPE_REG, &subtype, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read subtype for base %d, rc=%d\n",
+ base, rc);
+ return rc;
+ }
+
+ switch (subtype) {
+ case FG_BATT_SOC_PMI8998:
+ chip->batt_soc_base = base;
+ break;
+ case FG_BATT_INFO_PMI8998:
+ chip->batt_info_base = base;
+ break;
+ case FG_MEM_INFO_PMI8998:
+ chip->mem_if_base = base;
+ break;
+ default:
+ dev_err(chip->dev, "Invalid peripheral subtype 0x%x\n",
+ subtype);
+ return -ENXIO;
+ }
+ }
+
+ rc = of_property_read_u32(node, "qcom,rradc-base", &base);
+ if (rc < 0) {
+ dev_err(chip->dev, "rradc-base not specified, rc=%d\n", rc);
+ return rc;
+ }
+ chip->rradc_base = base;
+
+ /* Read all the optional properties below */
+ rc = of_property_read_u32(node, "qcom,fg-cutoff-voltage", &temp);
+ if (rc < 0)
+ chip->dt.cutoff_volt_mv = DEFAULT_CUTOFF_VOLT_MV;
+ else
+ chip->dt.cutoff_volt_mv = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-empty-voltage", &temp);
+ if (rc < 0)
+ chip->dt.empty_volt_mv = DEFAULT_EMPTY_VOLT_MV;
+ else
+ chip->dt.empty_volt_mv = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-vbatt-low-thr", &temp);
+ if (rc < 0)
+ chip->dt.vbatt_low_thr_mv = -EINVAL;
+ else
+ chip->dt.vbatt_low_thr_mv = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-chg-term-current", &temp);
+ if (rc < 0)
+ chip->dt.chg_term_curr_ma = DEFAULT_CHG_TERM_CURR_MA;
+ else
+ chip->dt.chg_term_curr_ma = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-sys-term-current", &temp);
+ if (rc < 0)
+ chip->dt.sys_term_curr_ma = DEFAULT_SYS_TERM_CURR_MA;
+ else
+ chip->dt.sys_term_curr_ma = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-chg-term-base-current", &temp);
+ if (rc < 0)
+ chip->dt.chg_term_base_curr_ma = DEFAULT_CHG_TERM_BASE_CURR_MA;
+ else
+ chip->dt.chg_term_base_curr_ma = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-delta-soc-thr", &temp);
+ if (rc < 0)
+ chip->dt.delta_soc_thr = DEFAULT_DELTA_SOC_THR;
+ else
+ chip->dt.delta_soc_thr = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-recharge-soc-thr", &temp);
+ if (rc < 0)
+ chip->dt.recharge_soc_thr = DEFAULT_RECHARGE_SOC_THR;
+ else
+ chip->dt.recharge_soc_thr = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-recharge-voltage", &temp);
+ if (rc < 0)
+ chip->dt.recharge_volt_thr_mv = DEFAULT_RECHARGE_VOLT_MV;
+ else
+ chip->dt.recharge_volt_thr_mv = temp;
+
+ chip->dt.auto_recharge_soc = of_property_read_bool(node,
+ "qcom,fg-auto-recharge-soc");
+
+ rc = of_property_read_u32(node, "qcom,fg-rsense-sel", &temp);
+ if (rc < 0)
+ chip->dt.rsense_sel = SRC_SEL_BATFET_SMB;
+ else
+ chip->dt.rsense_sel = (u8)temp & SOURCE_SELECT_MASK;
+
+ chip->dt.jeita_thresholds[JEITA_COLD] = DEFAULT_BATT_TEMP_COLD;
+ chip->dt.jeita_thresholds[JEITA_COOL] = DEFAULT_BATT_TEMP_COOL;
+ chip->dt.jeita_thresholds[JEITA_WARM] = DEFAULT_BATT_TEMP_WARM;
+ chip->dt.jeita_thresholds[JEITA_HOT] = DEFAULT_BATT_TEMP_HOT;
+ if (of_property_count_elems_of_size(node, "qcom,fg-jeita-thresholds",
+ sizeof(u32)) == NUM_JEITA_LEVELS) {
+ rc = of_property_read_u32_array(node,
+ "qcom,fg-jeita-thresholds",
+ chip->dt.jeita_thresholds, NUM_JEITA_LEVELS);
+ if (rc < 0)
+ pr_warn("Error reading Jeita thresholds, default values will be used rc:%d\n",
+ rc);
+ }
+
+ if (of_property_count_elems_of_size(node,
+ "qcom,battery-thermal-coefficients",
+ sizeof(u8)) == BATT_THERM_NUM_COEFFS) {
+ rc = of_property_read_u8_array(node,
+ "qcom,battery-thermal-coefficients",
+ chip->dt.batt_therm_coeffs,
+ BATT_THERM_NUM_COEFFS);
+ if (rc < 0)
+ pr_warn("Error reading battery thermal coefficients, rc:%d\n",
+ rc);
+ }
+
+ rc = fg_parse_dt_property_u32_array(node, "qcom,fg-esr-timer-charging",
+ chip->dt.esr_timer_charging, NUM_ESR_TIMERS);
+ if (rc < 0) {
+ chip->dt.esr_timer_charging[TIMER_RETRY] = -EINVAL;
+ chip->dt.esr_timer_charging[TIMER_MAX] = -EINVAL;
+ }
+
+ rc = fg_parse_dt_property_u32_array(node, "qcom,fg-esr-timer-awake",
+ chip->dt.esr_timer_awake, NUM_ESR_TIMERS);
+ if (rc < 0) {
+ chip->dt.esr_timer_awake[TIMER_RETRY] = -EINVAL;
+ chip->dt.esr_timer_awake[TIMER_MAX] = -EINVAL;
+ }
+
+ rc = fg_parse_dt_property_u32_array(node, "qcom,fg-esr-timer-asleep",
+ chip->dt.esr_timer_asleep, NUM_ESR_TIMERS);
+ if (rc < 0) {
+ chip->dt.esr_timer_asleep[TIMER_RETRY] = -EINVAL;
+ chip->dt.esr_timer_asleep[TIMER_MAX] = -EINVAL;
+ }
+
+ chip->cyc_ctr.en = of_property_read_bool(node, "qcom,cycle-counter-en");
+ if (chip->cyc_ctr.en)
+ chip->cyc_ctr.id = 1;
+
+ chip->dt.force_load_profile = of_property_read_bool(node,
+ "qcom,fg-force-load-profile");
+
+ rc = of_property_read_u32(node, "qcom,cl-start-capacity", &temp);
+ if (rc < 0)
+ chip->dt.cl_start_soc = DEFAULT_CL_START_SOC;
+ else
+ chip->dt.cl_start_soc = temp;
+
+ rc = of_property_read_u32(node, "qcom,cl-min-temp", &temp);
+ if (rc < 0)
+ chip->dt.cl_min_temp = DEFAULT_CL_MIN_TEMP_DECIDEGC;
+ else
+ chip->dt.cl_min_temp = temp;
+
+ rc = of_property_read_u32(node, "qcom,cl-max-temp", &temp);
+ if (rc < 0)
+ chip->dt.cl_max_temp = DEFAULT_CL_MAX_TEMP_DECIDEGC;
+ else
+ chip->dt.cl_max_temp = temp;
+
+ rc = of_property_read_u32(node, "qcom,cl-max-increment", &temp);
+ if (rc < 0)
+ chip->dt.cl_max_cap_inc = DEFAULT_CL_MAX_INC_DECIPERC;
+ else
+ chip->dt.cl_max_cap_inc = temp;
+
+ rc = of_property_read_u32(node, "qcom,cl-max-decrement", &temp);
+ if (rc < 0)
+ chip->dt.cl_max_cap_dec = DEFAULT_CL_MAX_DEC_DECIPERC;
+ else
+ chip->dt.cl_max_cap_dec = temp;
+
+ rc = of_property_read_u32(node, "qcom,cl-min-limit", &temp);
+ if (rc < 0)
+ chip->dt.cl_min_cap_limit = DEFAULT_CL_MIN_LIM_DECIPERC;
+ else
+ chip->dt.cl_min_cap_limit = temp;
+
+ rc = of_property_read_u32(node, "qcom,cl-max-limit", &temp);
+ if (rc < 0)
+ chip->dt.cl_max_cap_limit = DEFAULT_CL_MAX_LIM_DECIPERC;
+ else
+ chip->dt.cl_max_cap_limit = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-jeita-hyst-temp", &temp);
+ if (rc < 0)
+ chip->dt.jeita_hyst_temp = -EINVAL;
+ else
+ chip->dt.jeita_hyst_temp = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-batt-temp-delta", &temp);
+ if (rc < 0)
+ chip->dt.batt_temp_delta = -EINVAL;
+ else if (temp > BTEMP_DELTA_LOW && temp <= BTEMP_DELTA_HIGH)
+ chip->dt.batt_temp_delta = temp;
+
+ chip->dt.hold_soc_while_full = of_property_read_bool(node,
+ "qcom,hold-soc-while-full");
+
+ rc = fg_parse_ki_coefficients(chip);
+ if (rc < 0)
+ pr_err("Error in parsing Ki coefficients, rc=%d\n", rc);
+
+ rc = of_property_read_u32(node, "qcom,fg-rconn-mohms", &temp);
+ if (!rc)
+ chip->dt.rconn_mohms = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-esr-filter-switch-temp",
+ &temp);
+ if (rc < 0)
+ chip->dt.esr_flt_switch_temp = DEFAULT_ESR_FLT_TEMP_DECIDEGC;
+ else
+ chip->dt.esr_flt_switch_temp = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-esr-tight-filter-micro-pct",
+ &temp);
+ if (rc < 0)
+ chip->dt.esr_tight_flt_upct = DEFAULT_ESR_TIGHT_FLT_UPCT;
+ else
+ chip->dt.esr_tight_flt_upct = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-esr-broad-filter-micro-pct",
+ &temp);
+ if (rc < 0)
+ chip->dt.esr_broad_flt_upct = DEFAULT_ESR_BROAD_FLT_UPCT;
+ else
+ chip->dt.esr_broad_flt_upct = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-esr-tight-lt-filter-micro-pct",
+ &temp);
+ if (rc < 0)
+ chip->dt.esr_tight_lt_flt_upct = DEFAULT_ESR_TIGHT_LT_FLT_UPCT;
+ else
+ chip->dt.esr_tight_lt_flt_upct = temp;
+
+ rc = of_property_read_u32(node, "qcom,fg-esr-broad-lt-filter-micro-pct",
+ &temp);
+ if (rc < 0)
+ chip->dt.esr_broad_lt_flt_upct = DEFAULT_ESR_BROAD_LT_FLT_UPCT;
+ else
+ chip->dt.esr_broad_lt_flt_upct = temp;
+
+ rc = fg_parse_slope_limit_coefficients(chip);
+ if (rc < 0)
+ pr_err("Error in parsing slope limit coeffs, rc=%d\n", rc);
+
+ rc = of_property_read_u32(node, "qcom,fg-esr-clamp-mohms", &temp);
+ if (rc < 0)
+ chip->dt.esr_clamp_mohms = DEFAULT_ESR_CLAMP_MOHMS;
+ else
+ chip->dt.esr_clamp_mohms = temp;
+
+ chip->dt.esr_pulse_thresh_ma = DEFAULT_ESR_PULSE_THRESH_MA;
+ rc = of_property_read_u32(node, "qcom,fg-esr-pulse-thresh-ma", &temp);
+ if (!rc) {
+ /* ESR pulse qualification threshold range is 1-997 mA */
+ if (temp > 0 && temp < 997)
+ chip->dt.esr_pulse_thresh_ma = temp;
+ }
+
+ chip->dt.esr_meas_curr_ma = DEFAULT_ESR_MEAS_CURR_MA;
+ rc = of_property_read_u32(node, "qcom,fg-esr-meas-curr-ma", &temp);
+ if (!rc) {
+ /* ESR measurement current range is 60-240 mA */
+ if (temp >= 60 || temp <= 240)
+ chip->dt.esr_meas_curr_ma = temp;
+ }
+
+ return 0;
+}
+
+static void fg_cleanup(struct fg_chip *chip)
+{
+ power_supply_unreg_notifier(&chip->nb);
+ debugfs_remove_recursive(chip->dfs_root);
+ if (chip->awake_votable)
+ destroy_votable(chip->awake_votable);
+
+ if (chip->delta_bsoc_irq_en_votable)
+ destroy_votable(chip->delta_bsoc_irq_en_votable);
+
+ if (chip->batt_miss_irq_en_votable)
+ destroy_votable(chip->batt_miss_irq_en_votable);
+
+ if (chip->batt_id_chan)
+ iio_channel_release(chip->batt_id_chan);
+
+ dev_set_drvdata(chip->dev, NULL);
+}
+
+static int fg_gen3_probe(struct platform_device *pdev)
+{
+ struct fg_chip *chip;
+ struct power_supply_config fg_psy_cfg;
+ int rc, msoc, volt_uv, batt_temp;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = &pdev->dev;
+ chip->debug_mask = &fg_gen3_debug_mask;
+ chip->irqs = fg_irqs;
+ chip->charge_status = -EINVAL;
+ chip->prev_charge_status = -EINVAL;
+ chip->ki_coeff_full_soc = -EINVAL;
+ chip->online_status = -EINVAL;
+ chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
+ if (!chip->regmap) {
+ dev_err(chip->dev, "Parent regmap is unavailable\n");
+ return -ENXIO;
+ }
+
+ chip->batt_id_chan = iio_channel_get(chip->dev, "rradc_batt_id");
+ if (IS_ERR(chip->batt_id_chan)) {
+ if (PTR_ERR(chip->batt_id_chan) != -EPROBE_DEFER)
+ pr_err("batt_id_chan unavailable %ld\n",
+ PTR_ERR(chip->batt_id_chan));
+ rc = PTR_ERR(chip->batt_id_chan);
+ chip->batt_id_chan = NULL;
+ return rc;
+ }
+
+ rc = of_property_match_string(chip->dev->of_node,
+ "io-channel-names", "rradc_die_temp");
+ if (rc >= 0) {
+ chip->die_temp_chan = iio_channel_get(chip->dev,
+ "rradc_die_temp");
+ if (IS_ERR(chip->die_temp_chan)) {
+ if (PTR_ERR(chip->die_temp_chan) != -EPROBE_DEFER)
+ pr_err("rradc_die_temp unavailable %ld\n",
+ PTR_ERR(chip->die_temp_chan));
+ rc = PTR_ERR(chip->die_temp_chan);
+ chip->die_temp_chan = NULL;
+ return rc;
+ }
+ }
+
+ chip->awake_votable = create_votable("FG_WS", VOTE_SET_ANY, fg_awake_cb,
+ chip);
+ if (IS_ERR(chip->awake_votable)) {
+ rc = PTR_ERR(chip->awake_votable);
+ chip->awake_votable = NULL;
+ goto exit;
+ }
+
+ chip->delta_bsoc_irq_en_votable = create_votable("FG_DELTA_BSOC_IRQ",
+ VOTE_SET_ANY,
+ fg_delta_bsoc_irq_en_cb, chip);
+ if (IS_ERR(chip->delta_bsoc_irq_en_votable)) {
+ rc = PTR_ERR(chip->delta_bsoc_irq_en_votable);
+ chip->delta_bsoc_irq_en_votable = NULL;
+ goto exit;
+ }
+
+ chip->batt_miss_irq_en_votable = create_votable("FG_BATT_MISS_IRQ",
+ VOTE_SET_ANY,
+ fg_batt_miss_irq_en_cb, chip);
+ if (IS_ERR(chip->batt_miss_irq_en_votable)) {
+ rc = PTR_ERR(chip->batt_miss_irq_en_votable);
+ chip->batt_miss_irq_en_votable = NULL;
+ goto exit;
+ }
+
+ rc = fg_parse_dt(chip);
+ if (rc < 0) {
+ dev_err(chip->dev, "Error in reading DT parameters, rc:%d\n",
+ rc);
+ goto exit;
+ }
+
+ mutex_init(&chip->bus_lock);
+ mutex_init(&chip->sram_rw_lock);
+ mutex_init(&chip->cyc_ctr.lock);
+ mutex_init(&chip->cl.lock);
+ mutex_init(&chip->ttf.lock);
+ mutex_init(&chip->charge_full_lock);
+ init_completion(&chip->soc_update);
+ init_completion(&chip->soc_ready);
+ INIT_DELAYED_WORK(&chip->profile_load_work, profile_load_work);
+ INIT_WORK(&chip->status_change_work, status_change_work);
+ INIT_DELAYED_WORK(&chip->ttf_work, ttf_work);
+ INIT_DELAYED_WORK(&chip->sram_dump_work, sram_dump_work);
+
+ rc = fg_memif_init(chip);
+ if (rc < 0) {
+ dev_err(chip->dev, "Error in initializing FG_MEMIF, rc:%d\n",
+ rc);
+ goto exit;
+ }
+
+ rc = fg_hw_init(chip);
+ if (rc < 0) {
+ dev_err(chip->dev, "Error in initializing FG hardware, rc:%d\n",
+ rc);
+ goto exit;
+ }
+
+ platform_set_drvdata(pdev, chip);
+
+ /* Register the power supply */
+ fg_psy_cfg.drv_data = chip;
+ fg_psy_cfg.of_node = NULL;
+ fg_psy_cfg.supplied_to = NULL;
+ fg_psy_cfg.num_supplicants = 0;
+ chip->fg_psy = devm_power_supply_register(chip->dev, &fg_psy_desc,
+ &fg_psy_cfg);
+ if (IS_ERR(chip->fg_psy)) {
+ pr_err("failed to register fg_psy rc = %ld\n",
+ PTR_ERR(chip->fg_psy));
+ goto exit;
+ }
+
+ chip->nb.notifier_call = fg_notifier_cb;
+ rc = power_supply_reg_notifier(&chip->nb);
+ if (rc < 0) {
+ pr_err("Couldn't register psy notifier rc = %d\n", rc);
+ goto exit;
+ }
+
+ rc = fg_register_interrupts(chip);
+ if (rc < 0) {
+ dev_err(chip->dev, "Error in registering interrupts, rc:%d\n",
+ rc);
+ goto exit;
+ }
+
+ /* Keep SOC_UPDATE_IRQ disabled until we require it */
+ if (fg_irqs[SOC_UPDATE_IRQ].irq)
+ disable_irq_nosync(fg_irqs[SOC_UPDATE_IRQ].irq);
+
+ /* Keep BSOC_DELTA_IRQ disabled until we require it */
+ vote(chip->delta_bsoc_irq_en_votable, DELTA_BSOC_IRQ_VOTER, false, 0);
+
+ /* Keep BATT_MISSING_IRQ disabled until we require it */
+ vote(chip->batt_miss_irq_en_votable, BATT_MISS_IRQ_VOTER, false, 0);
+
+ rc = fg_debugfs_create(chip);
+ if (rc < 0) {
+ dev_err(chip->dev, "Error in creating debugfs entries, rc:%d\n",
+ rc);
+ goto exit;
+ }
+
+ rc = fg_get_battery_voltage(chip, &volt_uv);
+ if (!rc)
+ rc = fg_get_prop_capacity(chip, &msoc);
+
+ if (!rc)
+ rc = fg_get_battery_temp(chip, &batt_temp);
+
+ if (!rc) {
+ pr_info("battery SOC:%d voltage: %duV temp: %d id: %dKOhms\n",
+ msoc, volt_uv, batt_temp, chip->batt_id_ohms / 1000);
+ rc = fg_esr_filter_config(chip, batt_temp);
+ if (rc < 0)
+ pr_err("Error in configuring ESR filter rc:%d\n", rc);
+ }
+
+ device_init_wakeup(chip->dev, true);
+ schedule_delayed_work(&chip->profile_load_work, 0);
+
+ pr_debug("FG GEN3 driver probed successfully\n");
+ return 0;
+exit:
+ fg_cleanup(chip);
+ return rc;
+}
+
+static int fg_gen3_suspend(struct device *dev)
+{
+ struct fg_chip *chip = dev_get_drvdata(dev);
+ int rc;
+
+ rc = fg_esr_timer_config(chip, true);
+ if (rc < 0)
+ pr_err("Error in configuring ESR timer, rc=%d\n", rc);
+
+ cancel_delayed_work_sync(&chip->ttf_work);
+ if (fg_sram_dump)
+ cancel_delayed_work_sync(&chip->sram_dump_work);
+ return 0;
+}
+
+static int fg_gen3_resume(struct device *dev)
+{
+ struct fg_chip *chip = dev_get_drvdata(dev);
+ int rc;
+
+ rc = fg_esr_timer_config(chip, false);
+ if (rc < 0)
+ pr_err("Error in configuring ESR timer, rc=%d\n", rc);
+
+ schedule_delayed_work(&chip->ttf_work, 0);
+ if (fg_sram_dump)
+ schedule_delayed_work(&chip->sram_dump_work,
+ msecs_to_jiffies(fg_sram_dump_period_ms));
+ return 0;
+}
+
+static const struct dev_pm_ops fg_gen3_pm_ops = {
+ .suspend = fg_gen3_suspend,
+ .resume = fg_gen3_resume,
+};
+
+static int fg_gen3_remove(struct platform_device *pdev)
+{
+ struct fg_chip *chip = dev_get_drvdata(&pdev->dev);
+
+ fg_cleanup(chip);
+ return 0;
+}
+
+static const struct of_device_id fg_gen3_match_table[] = {
+ {.compatible = FG_GEN3_DEV_NAME},
+ {},
+};
+
+static struct platform_driver fg_gen3_driver = {
+ .driver = {
+ .name = FG_GEN3_DEV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = fg_gen3_match_table,
+ .pm = &fg_gen3_pm_ops,
+ },
+ .probe = fg_gen3_probe,
+ .remove = fg_gen3_remove,
+};
+
+static int __init fg_gen3_init(void)
+{
+ return platform_driver_register(&fg_gen3_driver);
+}
+
+static void __exit fg_gen3_exit(void)
+{
+ return platform_driver_unregister(&fg_gen3_driver);
+}
+
+module_init(fg_gen3_init);
+module_exit(fg_gen3_exit);
+
+MODULE_DESCRIPTION("QPNP Fuel gauge GEN3 driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" FG_GEN3_DEV_NAME);
diff --git a/drivers/power/supply/qcom/qpnp-qnovo.c b/drivers/power/supply/qcom/qpnp-qnovo.c
new file mode 100644
index 000000000000..b20807990efc
--- /dev/null
+++ b/drivers/power/supply/qcom/qpnp-qnovo.c
@@ -0,0 +1,1726 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/pmic-voter.h>
+#include <linux/delay.h>
+
+#define QNOVO_REVISION1 0x00
+#define QNOVO_REVISION2 0x01
+#define QNOVO_PERPH_TYPE 0x04
+#define QNOVO_PERPH_SUBTYPE 0x05
+#define QNOVO_PTTIME_STS 0x07
+#define QNOVO_PTRAIN_STS 0x08
+#define QNOVO_ERROR_STS 0x09
+#define QNOVO_ERROR_BIT BIT(0)
+#define QNOVO_ERROR_STS2 0x0A
+#define QNOVO_ERROR_CHARGING_DISABLED BIT(1)
+#define QNOVO_INT_RT_STS 0x10
+#define QNOVO_INT_SET_TYPE 0x11
+#define QNOVO_INT_POLARITY_HIGH 0x12
+#define QNOVO_INT_POLARITY_LOW 0x13
+#define QNOVO_INT_LATCHED_CLR 0x14
+#define QNOVO_INT_EN_SET 0x15
+#define QNOVO_INT_EN_CLR 0x16
+#define QNOVO_INT_LATCHED_STS 0x18
+#define QNOVO_INT_PENDING_STS 0x19
+#define QNOVO_INT_MID_SEL 0x1A
+#define QNOVO_INT_PRIORITY 0x1B
+#define QNOVO_PE_CTRL 0x40
+#define QNOVO_PREST1_CTRL 0x41
+#define QNOVO_PPULS1_LSB_CTRL 0x42
+#define QNOVO_PPULS1_MSB_CTRL 0x43
+#define QNOVO_NREST1_CTRL 0x44
+#define QNOVO_NPULS1_CTRL 0x45
+#define QNOVO_PPCNT_CTRL 0x46
+#define QNOVO_VLIM1_LSB_CTRL 0x47
+#define QNOVO_VLIM1_MSB_CTRL 0x48
+#define QNOVO_PTRAIN_EN 0x49
+#define QNOVO_PTRAIN_EN_BIT BIT(0)
+#define QNOVO_PE_CTRL2 0x4A
+#define QNOVO_PREST2_LSB_CTRL 0x50
+#define QNOVO_PREST2_MSB_CTRL 0x51
+#define QNOVO_PPULS2_LSB_CTRL 0x52
+#define QNOVO_PPULS2_MSB_CTRL 0x53
+#define QNOVO_NREST2_CTRL 0x54
+#define QNOVO_NPULS2_CTRL 0x55
+#define QNOVO_VLIM2_LSB_CTRL 0x56
+#define QNOVO_VLIM2_MSB_CTRL 0x57
+#define QNOVO_PVOLT1_LSB 0x60
+#define QNOVO_PVOLT1_MSB 0x61
+#define QNOVO_PCUR1_LSB 0x62
+#define QNOVO_PCUR1_MSB 0x63
+#define QNOVO_PVOLT2_LSB 0x70
+#define QNOVO_PVOLT2_MSB 0x71
+#define QNOVO_RVOLT2_LSB 0x72
+#define QNOVO_RVOLT2_MSB 0x73
+#define QNOVO_PCUR2_LSB 0x74
+#define QNOVO_PCUR2_MSB 0x75
+#define QNOVO_SCNT 0x80
+#define QNOVO_VMAX_LSB 0x90
+#define QNOVO_VMAX_MSB 0x91
+#define QNOVO_SNUM 0x92
+
+/* Registers ending in 0 imply external rsense */
+#define QNOVO_IADC_OFFSET_0 0xA0
+#define QNOVO_IADC_OFFSET_1 0xA1
+#define QNOVO_IADC_GAIN_0 0xA2
+#define QNOVO_IADC_GAIN_1 0xA3
+#define QNOVO_VADC_OFFSET 0xA4
+#define QNOVO_VADC_GAIN 0xA5
+#define QNOVO_IADC_GAIN_2 0xA6
+#define QNOVO_SPARE 0xA7
+#define QNOVO_STRM_CTRL 0xA8
+#define QNOVO_IADC_OFFSET_OVR_VAL 0xA9
+#define QNOVO_IADC_OFFSET_OVR 0xAA
+
+#define QNOVO_DISABLE_CHARGING 0xAB
+#define ERR_SWITCHER_DISABLED BIT(7)
+#define ERR_JEITA_SOFT_CONDITION BIT(6)
+#define ERR_BAT_OV BIT(5)
+#define ERR_CV_MODE BIT(4)
+#define ERR_BATTERY_MISSING BIT(3)
+#define ERR_SAFETY_TIMER_EXPIRED BIT(2)
+#define ERR_CHARGING_DISABLED BIT(1)
+#define ERR_JEITA_HARD_CONDITION BIT(0)
+
+#define QNOVO_TR_IADC_OFFSET_0 0xF1
+#define QNOVO_TR_IADC_OFFSET_1 0xF2
+
+#define DRV_MAJOR_VERSION 1
+#define DRV_MINOR_VERSION 0
+
+#define IADC_LSB_NA 2441400
+#define VADC_LSB_NA 1220700
+#define GAIN_LSB_FACTOR 976560
+
+#define USER_VOTER "user_voter"
+#define OK_TO_QNOVO_VOTER "ok_to_qnovo_voter"
+
+#define QNOVO_VOTER "qnovo_voter"
+#define FG_AVAILABLE_VOTER "FG_AVAILABLE_VOTER"
+#define QNOVO_OVERALL_VOTER "QNOVO_OVERALL_VOTER"
+#define QNI_PT_VOTER "QNI_PT_VOTER"
+#define ESR_VOTER "ESR_VOTER"
+
+#define HW_OK_TO_QNOVO_VOTER "HW_OK_TO_QNOVO_VOTER"
+#define CHG_READY_VOTER "CHG_READY_VOTER"
+#define USB_READY_VOTER "USB_READY_VOTER"
+#define DC_READY_VOTER "DC_READY_VOTER"
+
+#define PT_RESTART_VOTER "PT_RESTART_VOTER"
+
+struct qnovo_dt_props {
+ bool external_rsense;
+ struct device_node *revid_dev_node;
+};
+
+struct qnovo {
+ int base;
+ struct mutex write_lock;
+ struct regmap *regmap;
+ struct qnovo_dt_props dt;
+ struct device *dev;
+ struct votable *disable_votable;
+ struct votable *pt_dis_votable;
+ struct votable *not_ok_to_qnovo_votable;
+ struct votable *chg_ready_votable;
+ struct votable *awake_votable;
+ struct class qnovo_class;
+ struct pmic_revid_data *pmic_rev_id;
+ u32 wa_flags;
+ s64 external_offset_nA;
+ s64 internal_offset_nA;
+ s64 offset_nV;
+ s64 external_i_gain_mega;
+ s64 internal_i_gain_mega;
+ s64 v_gain_mega;
+ struct notifier_block nb;
+ struct power_supply *batt_psy;
+ struct power_supply *bms_psy;
+ struct power_supply *usb_psy;
+ struct power_supply *dc_psy;
+ struct work_struct status_change_work;
+ int fv_uV_request;
+ int fcc_uA_request;
+ int usb_present;
+ int dc_present;
+ struct delayed_work usb_debounce_work;
+ struct delayed_work dc_debounce_work;
+
+ struct delayed_work ptrain_restart_work;
+};
+
+static int debug_mask;
+module_param_named(debug_mask, debug_mask, int, 0600);
+
+#define qnovo_dbg(chip, reason, fmt, ...) \
+ do { \
+ if (debug_mask & (reason)) \
+ dev_info(chip->dev, fmt, ##__VA_ARGS__); \
+ else \
+ dev_dbg(chip->dev, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+static bool is_secure(struct qnovo *chip, int addr)
+{
+ /* assume everything above 0x40 is secure */
+ return (bool)(addr >= 0x40);
+}
+
+static int qnovo_read(struct qnovo *chip, u16 addr, u8 *buf, int len)
+{
+ return regmap_bulk_read(chip->regmap, chip->base + addr, buf, len);
+}
+
+static int qnovo_masked_write(struct qnovo *chip, u16 addr, u8 mask, u8 val)
+{
+ int rc = 0;
+
+ mutex_lock(&chip->write_lock);
+ if (is_secure(chip, addr)) {
+ rc = regmap_write(chip->regmap,
+ ((chip->base + addr) & ~(0xFF)) | 0xD0, 0xA5);
+ if (rc < 0)
+ goto unlock;
+ }
+
+ rc = regmap_update_bits(chip->regmap, chip->base + addr, mask, val);
+
+unlock:
+ mutex_unlock(&chip->write_lock);
+ return rc;
+}
+
+static int qnovo_write(struct qnovo *chip, u16 addr, u8 *buf, int len)
+{
+ int i, rc = 0;
+ bool is_start_secure, is_end_secure;
+
+ is_start_secure = is_secure(chip, addr);
+ is_end_secure = is_secure(chip, addr + len);
+
+ if (!is_start_secure && !is_end_secure) {
+ mutex_lock(&chip->write_lock);
+ rc = regmap_bulk_write(chip->regmap, chip->base + addr,
+ buf, len);
+ goto unlock;
+ }
+
+ mutex_lock(&chip->write_lock);
+ for (i = addr; i < addr + len; i++) {
+ if (is_secure(chip, i)) {
+ rc = regmap_write(chip->regmap,
+ ((chip->base + i) & ~(0xFF)) | 0xD0, 0xA5);
+ if (rc < 0)
+ goto unlock;
+ }
+ rc = regmap_write(chip->regmap, chip->base + i, buf[i - addr]);
+ if (rc < 0)
+ goto unlock;
+ }
+
+unlock:
+ mutex_unlock(&chip->write_lock);
+ return rc;
+}
+
+static bool is_batt_available(struct qnovo *chip)
+{
+ if (!chip->batt_psy)
+ chip->batt_psy = power_supply_get_by_name("battery");
+
+ if (!chip->batt_psy)
+ return false;
+
+ return true;
+}
+
+static bool is_fg_available(struct qnovo *chip)
+{
+ if (!chip->bms_psy)
+ chip->bms_psy = power_supply_get_by_name("bms");
+
+ if (!chip->bms_psy)
+ return false;
+
+ return true;
+}
+
+static bool is_usb_available(struct qnovo *chip)
+{
+ if (!chip->usb_psy)
+ chip->usb_psy = power_supply_get_by_name("usb");
+
+ if (!chip->usb_psy)
+ return false;
+
+ return true;
+}
+
+static bool is_dc_available(struct qnovo *chip)
+{
+ if (!chip->dc_psy)
+ chip->dc_psy = power_supply_get_by_name("dc");
+
+ if (!chip->dc_psy)
+ return false;
+
+ return true;
+}
+
+static int qnovo_batt_psy_update(struct qnovo *chip, bool disable)
+{
+ union power_supply_propval pval = {0};
+ int rc = 0;
+
+ if (!is_batt_available(chip))
+ return -EINVAL;
+
+ if (chip->fv_uV_request != -EINVAL) {
+ pval.intval = disable ? -EINVAL : chip->fv_uV_request;
+ rc = power_supply_set_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_QNOVO,
+ &pval);
+ if (rc < 0) {
+ pr_err("Couldn't set prop qnovo_fv rc = %d\n", rc);
+ return -EINVAL;
+ }
+ }
+
+ if (chip->fcc_uA_request != -EINVAL) {
+ pval.intval = disable ? -EINVAL : chip->fcc_uA_request;
+ rc = power_supply_set_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_CURRENT_QNOVO,
+ &pval);
+ if (rc < 0) {
+ pr_err("Couldn't set prop qnovo_fcc rc = %d\n", rc);
+ return -EINVAL;
+ }
+ }
+
+ return rc;
+}
+
+static int qnovo_disable_cb(struct votable *votable, void *data, int disable,
+ const char *client)
+{
+ struct qnovo *chip = data;
+ union power_supply_propval pval = {0};
+ int rc;
+
+ if (!is_batt_available(chip))
+ return -EINVAL;
+
+ pval.intval = !disable;
+ rc = power_supply_set_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE,
+ &pval);
+ if (rc < 0) {
+ pr_err("Couldn't set prop qnovo_enable rc = %d\n", rc);
+ return -EINVAL;
+ }
+
+ /*
+ * fg must be available for enable FG_AVAILABLE_VOTER
+ * won't enable it otherwise
+ */
+
+ if (is_fg_available(chip))
+ power_supply_set_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE,
+ &pval);
+
+ vote(chip->pt_dis_votable, QNOVO_OVERALL_VOTER, disable, 0);
+ rc = qnovo_batt_psy_update(chip, disable);
+ return rc;
+}
+
+static int pt_dis_votable_cb(struct votable *votable, void *data, int disable,
+ const char *client)
+{
+ struct qnovo *chip = data;
+ int rc;
+
+ if (disable) {
+ cancel_delayed_work_sync(&chip->ptrain_restart_work);
+ vote(chip->awake_votable, PT_RESTART_VOTER, false, 0);
+ }
+
+ rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT,
+ (bool)disable ? 0 : QNOVO_PTRAIN_EN_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't %s pulse train rc=%d\n",
+ (bool)disable ? "disable" : "enable", rc);
+ return rc;
+ }
+
+ if (!disable) {
+ vote(chip->awake_votable, PT_RESTART_VOTER, true, 0);
+ schedule_delayed_work(&chip->ptrain_restart_work,
+ msecs_to_jiffies(20));
+ }
+
+ return 0;
+}
+
+static int not_ok_to_qnovo_cb(struct votable *votable, void *data,
+ int not_ok_to_qnovo,
+ const char *client)
+{
+ struct qnovo *chip = data;
+
+ vote(chip->disable_votable, OK_TO_QNOVO_VOTER, not_ok_to_qnovo, 0);
+ if (not_ok_to_qnovo)
+ vote(chip->disable_votable, USER_VOTER, true, 0);
+
+ kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
+ return 0;
+}
+
+static int chg_ready_cb(struct votable *votable, void *data, int ready,
+ const char *client)
+{
+ struct qnovo *chip = data;
+
+ vote(chip->not_ok_to_qnovo_votable, CHG_READY_VOTER, !ready, 0);
+
+ return 0;
+}
+
+static int awake_cb(struct votable *votable, void *data, int awake,
+ const char *client)
+{
+ struct qnovo *chip = data;
+
+ if (awake)
+ pm_stay_awake(chip->dev);
+ else
+ pm_relax(chip->dev);
+
+ return 0;
+}
+
+static int qnovo_parse_dt(struct qnovo *chip)
+{
+ struct device_node *node = chip->dev->of_node;
+ int rc;
+
+ if (!node) {
+ pr_err("device tree node missing\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(node, "reg", &chip->base);
+ if (rc < 0) {
+ pr_err("Couldn't read base rc = %d\n", rc);
+ return rc;
+ }
+
+ chip->dt.external_rsense = of_property_read_bool(node,
+ "qcom,external-rsense");
+
+ chip->dt.revid_dev_node = of_parse_phandle(node, "qcom,pmic-revid", 0);
+ if (!chip->dt.revid_dev_node) {
+ pr_err("Missing qcom,pmic-revid property - driver failed\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+enum {
+ VER = 0,
+ OK_TO_QNOVO,
+ QNOVO_ENABLE,
+ PT_ENABLE,
+ FV_REQUEST,
+ FCC_REQUEST,
+ PE_CTRL_REG,
+ PE_CTRL2_REG,
+ PTRAIN_STS_REG,
+ INT_RT_STS_REG,
+ ERR_STS2_REG,
+ PREST1,
+ PPULS1,
+ NREST1,
+ NPULS1,
+ PPCNT,
+ VLIM1,
+ PVOLT1,
+ PCUR1,
+ PTTIME,
+ PREST2,
+ PPULS2,
+ NREST2,
+ NPULS2,
+ VLIM2,
+ PVOLT2,
+ RVOLT2,
+ PCUR2,
+ SCNT,
+ VMAX,
+ SNUM,
+ VBATT,
+ IBATT,
+ BATTTEMP,
+ BATTSOC,
+};
+
+struct param_info {
+ char *name;
+ int start_addr;
+ int num_regs;
+ int reg_to_unit_multiplier;
+ int reg_to_unit_divider;
+ int reg_to_unit_offset;
+ int min_val;
+ int max_val;
+ char *units_str;
+};
+
+static struct param_info params[] = {
+ [PT_ENABLE] = {
+ .name = "PT_ENABLE",
+ .start_addr = QNOVO_PTRAIN_EN,
+ .num_regs = 1,
+ .units_str = "",
+ },
+ [FV_REQUEST] = {
+ .units_str = "uV",
+ },
+ [FCC_REQUEST] = {
+ .units_str = "uA",
+ },
+ [PE_CTRL_REG] = {
+ .name = "CTRL_REG",
+ .start_addr = QNOVO_PE_CTRL,
+ .num_regs = 1,
+ .units_str = "",
+ },
+ [PE_CTRL2_REG] = {
+ .name = "PE_CTRL2_REG",
+ .start_addr = QNOVO_PE_CTRL2,
+ .num_regs = 1,
+ .units_str = "",
+ },
+ [PTRAIN_STS_REG] = {
+ .name = "PTRAIN_STS",
+ .start_addr = QNOVO_PTRAIN_STS,
+ .num_regs = 1,
+ .units_str = "",
+ },
+ [INT_RT_STS_REG] = {
+ .name = "INT_RT_STS",
+ .start_addr = QNOVO_INT_RT_STS,
+ .num_regs = 1,
+ .units_str = "",
+ },
+ [ERR_STS2_REG] = {
+ .name = "RAW_CHGR_ERR",
+ .start_addr = QNOVO_ERROR_STS2,
+ .num_regs = 1,
+ .units_str = "",
+ },
+ [PREST1] = {
+ .name = "PREST1",
+ .start_addr = QNOVO_PREST1_CTRL,
+ .num_regs = 1,
+ .reg_to_unit_multiplier = 5,
+ .reg_to_unit_divider = 1,
+ .min_val = 5,
+ .max_val = 255,
+ .units_str = "mS",
+ },
+ [PPULS1] = {
+ .name = "PPULS1",
+ .start_addr = QNOVO_PPULS1_LSB_CTRL,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 1600, /* converts to uC */
+ .reg_to_unit_divider = 1,
+ .min_val = 30000,
+ .max_val = 65535000,
+ .units_str = "uC",
+ },
+ [NREST1] = {
+ .name = "NREST1",
+ .start_addr = QNOVO_NREST1_CTRL,
+ .num_regs = 1,
+ .reg_to_unit_multiplier = 5,
+ .reg_to_unit_divider = 1,
+ .min_val = 5,
+ .max_val = 255,
+ .units_str = "mS",
+ },
+ [NPULS1] = {
+ .name = "NPULS1",
+ .start_addr = QNOVO_NPULS1_CTRL,
+ .num_regs = 1,
+ .reg_to_unit_multiplier = 5,
+ .reg_to_unit_divider = 1,
+ .min_val = 0,
+ .max_val = 255,
+ .units_str = "mS",
+ },
+ [PPCNT] = {
+ .name = "PPCNT",
+ .start_addr = QNOVO_PPCNT_CTRL,
+ .num_regs = 1,
+ .reg_to_unit_multiplier = 1,
+ .reg_to_unit_divider = 1,
+ .min_val = 1,
+ .max_val = 255,
+ .units_str = "pulses",
+ },
+ [VLIM1] = {
+ .name = "VLIM1",
+ .start_addr = QNOVO_VLIM1_LSB_CTRL,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 610350, /* converts to nV */
+ .reg_to_unit_divider = 1,
+ .min_val = 2200000,
+ .max_val = 4500000,
+ .units_str = "uV",
+ },
+ [PVOLT1] = {
+ .name = "PVOLT1",
+ .start_addr = QNOVO_PVOLT1_LSB,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 610350, /* converts to nV */
+ .reg_to_unit_divider = 1,
+ .units_str = "uV",
+ },
+ [PCUR1] = {
+ .name = "PCUR1",
+ .start_addr = QNOVO_PCUR1_LSB,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 1220700, /* converts to nA */
+ .reg_to_unit_divider = 1,
+ .units_str = "uA",
+ },
+ [PTTIME] = {
+ .name = "PTTIME",
+ .start_addr = QNOVO_PTTIME_STS,
+ .num_regs = 1,
+ .reg_to_unit_multiplier = 2,
+ .reg_to_unit_divider = 1,
+ .units_str = "S",
+ },
+ [PREST2] = {
+ .name = "PREST2",
+ .start_addr = QNOVO_PREST2_LSB_CTRL,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 5,
+ .reg_to_unit_divider = 1,
+ .min_val = 5,
+ .max_val = 65535,
+ .units_str = "mS",
+ },
+ [PPULS2] = {
+ .name = "PPULS2",
+ .start_addr = QNOVO_PPULS2_LSB_CTRL,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 1600, /* converts to uC */
+ .reg_to_unit_divider = 1,
+ .min_val = 30000,
+ .max_val = 65535000,
+ .units_str = "uC",
+ },
+ [NREST2] = {
+ .name = "NREST2",
+ .start_addr = QNOVO_NREST2_CTRL,
+ .num_regs = 1,
+ .reg_to_unit_multiplier = 5,
+ .reg_to_unit_divider = 1,
+ .reg_to_unit_offset = -5,
+ .min_val = 5,
+ .max_val = 255,
+ .units_str = "mS",
+ },
+ [NPULS2] = {
+ .name = "NPULS2",
+ .start_addr = QNOVO_NPULS2_CTRL,
+ .num_regs = 1,
+ .reg_to_unit_multiplier = 5,
+ .reg_to_unit_divider = 1,
+ .min_val = 0,
+ .max_val = 255,
+ .units_str = "mS",
+ },
+ [VLIM2] = {
+ .name = "VLIM2",
+ .start_addr = QNOVO_VLIM2_LSB_CTRL,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 610350, /* converts to nV */
+ .reg_to_unit_divider = 1,
+ .min_val = 2200000,
+ .max_val = 4500000,
+ .units_str = "uV",
+ },
+ [PVOLT2] = {
+ .name = "PVOLT2",
+ .start_addr = QNOVO_PVOLT2_LSB,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 610350, /* converts to nV */
+ .reg_to_unit_divider = 1,
+ .units_str = "uV",
+ },
+ [RVOLT2] = {
+ .name = "RVOLT2",
+ .start_addr = QNOVO_RVOLT2_LSB,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 610350,
+ .reg_to_unit_divider = 1,
+ .units_str = "uV",
+ },
+ [PCUR2] = {
+ .name = "PCUR2",
+ .start_addr = QNOVO_PCUR2_LSB,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 1220700, /* converts to nA */
+ .reg_to_unit_divider = 1,
+ .units_str = "uA",
+ },
+ [SCNT] = {
+ .name = "SCNT",
+ .start_addr = QNOVO_SCNT,
+ .num_regs = 1,
+ .reg_to_unit_multiplier = 1,
+ .reg_to_unit_divider = 1,
+ .min_val = 0,
+ .max_val = 255,
+ .units_str = "pulses",
+ },
+ [VMAX] = {
+ .name = "VMAX",
+ .start_addr = QNOVO_VMAX_LSB,
+ .num_regs = 2,
+ .reg_to_unit_multiplier = 814000, /* converts to nV */
+ .reg_to_unit_divider = 1,
+ .units_str = "uV",
+ },
+ [SNUM] = {
+ .name = "SNUM",
+ .start_addr = QNOVO_SNUM,
+ .num_regs = 1,
+ .reg_to_unit_multiplier = 1,
+ .reg_to_unit_divider = 1,
+ .units_str = "pulses",
+ },
+ [VBATT] = {
+ .name = "POWER_SUPPLY_PROP_VOLTAGE_NOW",
+ .start_addr = POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ .units_str = "uV",
+ },
+ [IBATT] = {
+ .name = "POWER_SUPPLY_PROP_CURRENT_NOW",
+ .start_addr = POWER_SUPPLY_PROP_CURRENT_NOW,
+ .units_str = "uA",
+ },
+ [BATTTEMP] = {
+ .name = "POWER_SUPPLY_PROP_TEMP",
+ .start_addr = POWER_SUPPLY_PROP_TEMP,
+ .units_str = "uV",
+ },
+ [BATTSOC] = {
+ .name = "POWER_SUPPLY_PROP_CAPACITY",
+ .start_addr = POWER_SUPPLY_PROP_CAPACITY,
+ .units_str = "%",
+ },
+};
+
+static struct class_attribute qnovo_attributes[];
+
+static ssize_t version_show(struct class *c, struct class_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d.%d\n",
+ DRV_MAJOR_VERSION, DRV_MINOR_VERSION);
+}
+
+static ssize_t ok_to_qnovo_show(struct class *c, struct class_attribute *attr,
+ char *buf)
+{
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ int val = get_effective_result(chip->not_ok_to_qnovo_votable);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", !val);
+}
+
+static ssize_t qnovo_enable_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ int val = get_effective_result(chip->disable_votable);
+
+ return snprintf(ubuf, PAGE_SIZE, "%d\n", !val);
+}
+
+static ssize_t qnovo_enable_store(struct class *c, struct class_attribute *attr,
+ const char *ubuf, size_t count)
+{
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ unsigned long val;
+
+ if (kstrtoul(ubuf, 0, &val))
+ return -EINVAL;
+
+ vote(chip->disable_votable, USER_VOTER, !val, 0);
+
+ return count;
+}
+
+static ssize_t pt_enable_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ int val = get_effective_result(chip->pt_dis_votable);
+
+ return snprintf(ubuf, PAGE_SIZE, "%d\n", !val);
+}
+
+static ssize_t pt_enable_store(struct class *c, struct class_attribute *attr,
+ const char *ubuf, size_t count)
+{
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ unsigned long val;
+
+ if (kstrtoul(ubuf, 0, &val))
+ return -EINVAL;
+
+ /* val being 0, userspace wishes to disable pt so vote true */
+ vote(chip->pt_dis_votable, QNI_PT_VOTER, val ? false : true, 0);
+
+ return count;
+}
+
+static ssize_t val_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ int i = attr - qnovo_attributes;
+ int val = 0;
+
+ if (i == FV_REQUEST)
+ val = chip->fv_uV_request;
+
+ if (i == FCC_REQUEST)
+ val = chip->fcc_uA_request;
+
+ return snprintf(ubuf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t val_store(struct class *c, struct class_attribute *attr,
+ const char *ubuf, size_t count)
+{
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ int i = attr - qnovo_attributes;
+ unsigned long val;
+
+ if (kstrtoul(ubuf, 0, &val))
+ return -EINVAL;
+
+ if (i == FV_REQUEST)
+ chip->fv_uV_request = val;
+
+ if (i == FCC_REQUEST)
+ chip->fcc_uA_request = val;
+
+ if (!get_effective_result(chip->disable_votable))
+ qnovo_batt_psy_update(chip, false);
+
+ return count;
+}
+
+static ssize_t reg_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ int i = attr - qnovo_attributes;
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ u8 buf[2] = {0, 0};
+ u16 regval;
+ int rc;
+
+ rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
+ if (rc < 0) {
+ pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
+ return -EINVAL;
+ }
+ regval = buf[1] << 8 | buf[0];
+
+ return snprintf(ubuf, PAGE_SIZE, "0x%04x\n", regval);
+}
+
+static ssize_t reg_store(struct class *c, struct class_attribute *attr,
+ const char *ubuf, size_t count)
+{
+ int i = attr - qnovo_attributes;
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ u8 buf[2] = {0, 0};
+ unsigned long val;
+ int rc;
+
+ if (kstrtoul(ubuf, 0, &val))
+ return -EINVAL;
+
+ buf[0] = val & 0xFF;
+ buf[1] = (val >> 8) & 0xFF;
+
+ rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs);
+ if (rc < 0) {
+ pr_err("Couldn't write %s rc = %d\n", params[i].name, rc);
+ return -EINVAL;
+ }
+ return count;
+}
+
+static ssize_t time_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ int i = attr - qnovo_attributes;
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ u8 buf[2] = {0, 0};
+ u16 regval;
+ int val;
+ int rc;
+
+ rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
+ if (rc < 0) {
+ pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
+ return -EINVAL;
+ }
+ regval = buf[1] << 8 | buf[0];
+
+ val = ((regval * params[i].reg_to_unit_multiplier)
+ / params[i].reg_to_unit_divider)
+ - params[i].reg_to_unit_offset;
+
+ return snprintf(ubuf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t time_store(struct class *c, struct class_attribute *attr,
+ const char *ubuf, size_t count)
+{
+ int i = attr - qnovo_attributes;
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ u8 buf[2] = {0, 0};
+ u16 regval;
+ unsigned long val;
+ int rc;
+
+ if (kstrtoul(ubuf, 0, &val))
+ return -EINVAL;
+
+ if (val < params[i].min_val || val > params[i].max_val) {
+ pr_err("Out of Range %d%s for %s\n", (int)val,
+ params[i].units_str,
+ params[i].name);
+ return -ERANGE;
+ }
+
+ regval = (((int)val + params[i].reg_to_unit_offset)
+ * params[i].reg_to_unit_divider)
+ / params[i].reg_to_unit_multiplier;
+ buf[0] = regval & 0xFF;
+ buf[1] = (regval >> 8) & 0xFF;
+
+ rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs);
+ if (rc < 0) {
+ pr_err("Couldn't write %s rc = %d\n", params[i].name, rc);
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t current_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ int i = attr - qnovo_attributes;
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ u8 buf[2] = {0, 0};
+ int rc;
+ int comp_val_uA;
+ s64 regval_nA;
+ s64 gain, offset_nA, comp_val_nA;
+
+ rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
+ if (rc < 0) {
+ pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
+ return -EINVAL;
+ }
+
+ if (buf[1] & BIT(5))
+ buf[1] |= GENMASK(7, 6);
+
+ regval_nA = (s16)(buf[1] << 8 | buf[0]);
+ regval_nA = div_s64(regval_nA * params[i].reg_to_unit_multiplier,
+ params[i].reg_to_unit_divider)
+ - params[i].reg_to_unit_offset;
+
+ if (chip->dt.external_rsense) {
+ offset_nA = chip->external_offset_nA;
+ gain = chip->external_i_gain_mega;
+ } else {
+ offset_nA = chip->internal_offset_nA;
+ gain = chip->internal_i_gain_mega;
+ }
+
+ comp_val_nA = div_s64(regval_nA * gain, 1000000) - offset_nA;
+ comp_val_uA = div_s64(comp_val_nA, 1000);
+
+ return snprintf(ubuf, PAGE_SIZE, "%d\n", comp_val_uA);
+}
+
+static ssize_t voltage_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ int i = attr - qnovo_attributes;
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ u8 buf[2] = {0, 0};
+ int rc;
+ int comp_val_uV;
+ s64 regval_nV;
+ s64 gain, offset_nV, comp_val_nV;
+
+ rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
+ if (rc < 0) {
+ pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
+ return -EINVAL;
+ }
+ regval_nV = buf[1] << 8 | buf[0];
+ regval_nV = div_s64(regval_nV * params[i].reg_to_unit_multiplier,
+ params[i].reg_to_unit_divider)
+ - params[i].reg_to_unit_offset;
+
+ offset_nV = chip->offset_nV;
+ gain = chip->v_gain_mega;
+
+ comp_val_nV = div_s64(regval_nV * gain, 1000000) + offset_nV;
+ comp_val_uV = div_s64(comp_val_nV, 1000);
+
+ return snprintf(ubuf, PAGE_SIZE, "%d\n", comp_val_uV);
+}
+
+static ssize_t voltage_store(struct class *c, struct class_attribute *attr,
+ const char *ubuf, size_t count)
+{
+ int i = attr - qnovo_attributes;
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ u8 buf[2] = {0, 0};
+ int rc;
+ unsigned long val_uV;
+ s64 regval_nV;
+ s64 gain, offset_nV;
+
+ if (kstrtoul(ubuf, 0, &val_uV))
+ return -EINVAL;
+
+ if (val_uV < params[i].min_val || val_uV > params[i].max_val) {
+ pr_err("Out of Range %d%s for %s\n", (int)val_uV,
+ params[i].units_str,
+ params[i].name);
+ return -ERANGE;
+ }
+
+ offset_nV = chip->offset_nV;
+ gain = chip->v_gain_mega;
+
+ regval_nV = (s64)val_uV * 1000 - offset_nV;
+ regval_nV = div_s64(regval_nV * 1000000, gain);
+
+ regval_nV = div_s64((regval_nV + params[i].reg_to_unit_offset)
+ * params[i].reg_to_unit_divider,
+ params[i].reg_to_unit_multiplier);
+ buf[0] = regval_nV & 0xFF;
+ buf[1] = ((u64)regval_nV >> 8) & 0xFF;
+
+ rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs);
+ if (rc < 0) {
+ pr_err("Couldn't write %s rc = %d\n", params[i].name, rc);
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t coulomb_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ int i = attr - qnovo_attributes;
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ u8 buf[2] = {0, 0};
+ int rc;
+ int comp_val_uC;
+ s64 regval_uC, gain;
+
+ rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
+ if (rc < 0) {
+ pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
+ return -EINVAL;
+ }
+ regval_uC = buf[1] << 8 | buf[0];
+ regval_uC = div_s64(regval_uC * params[i].reg_to_unit_multiplier,
+ params[i].reg_to_unit_divider)
+ - params[i].reg_to_unit_offset;
+
+ if (chip->dt.external_rsense)
+ gain = chip->external_i_gain_mega;
+ else
+ gain = chip->internal_i_gain_mega;
+
+ comp_val_uC = div_s64(regval_uC * gain, 1000000);
+ return snprintf(ubuf, PAGE_SIZE, "%d\n", comp_val_uC);
+}
+
+static ssize_t coulomb_store(struct class *c, struct class_attribute *attr,
+ const char *ubuf, size_t count)
+{
+ int i = attr - qnovo_attributes;
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ u8 buf[2] = {0, 0};
+ int rc;
+ unsigned long val_uC;
+ s64 regval;
+ s64 gain;
+
+ if (kstrtoul(ubuf, 0, &val_uC))
+ return -EINVAL;
+
+ if (val_uC < params[i].min_val || val_uC > params[i].max_val) {
+ pr_err("Out of Range %d%s for %s\n", (int)val_uC,
+ params[i].units_str,
+ params[i].name);
+ return -ERANGE;
+ }
+
+ if (chip->dt.external_rsense)
+ gain = chip->external_i_gain_mega;
+ else
+ gain = chip->internal_i_gain_mega;
+
+ regval = div_s64((s64)val_uC * 1000000, gain);
+
+ regval = div_s64((regval + params[i].reg_to_unit_offset)
+ * params[i].reg_to_unit_divider,
+ params[i].reg_to_unit_multiplier);
+
+ buf[0] = regval & 0xFF;
+ buf[1] = ((u64)regval >> 8) & 0xFF;
+
+ rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs);
+ if (rc < 0) {
+ pr_err("Couldn't write %s rc = %d\n", params[i].name, rc);
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t batt_prop_show(struct class *c, struct class_attribute *attr,
+ char *ubuf)
+{
+ int i = attr - qnovo_attributes;
+ struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+ int rc = -EINVAL;
+ int prop = params[i].start_addr;
+ union power_supply_propval pval = {0};
+
+ if (!is_batt_available(chip))
+ return -EINVAL;
+
+ rc = power_supply_get_property(chip->batt_psy, prop, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't read battery prop %s rc = %d\n",
+ params[i].name, rc);
+ return -EINVAL;
+ }
+
+ return snprintf(ubuf, PAGE_SIZE, "%d\n", pval.intval);
+}
+
+static struct class_attribute qnovo_attributes[] = {
+ [VER] = __ATTR_RO(version),
+ [OK_TO_QNOVO] = __ATTR_RO(ok_to_qnovo),
+ [QNOVO_ENABLE] = __ATTR_RW(qnovo_enable),
+ [PT_ENABLE] = __ATTR_RW(pt_enable),
+ [FV_REQUEST] = __ATTR(fv_uV_request, 0644,
+ val_show, val_store),
+ [FCC_REQUEST] = __ATTR(fcc_uA_request, 0644,
+ val_show, val_store),
+ [PE_CTRL_REG] = __ATTR(PE_CTRL_REG, 0644,
+ reg_show, reg_store),
+ [PE_CTRL2_REG] = __ATTR(PE_CTRL2_REG, 0644,
+ reg_show, reg_store),
+ [PTRAIN_STS_REG] = __ATTR(PTRAIN_STS_REG, 0444,
+ reg_show, NULL),
+ [INT_RT_STS_REG] = __ATTR(INT_RT_STS_REG, 0444,
+ reg_show, NULL),
+ [ERR_STS2_REG] = __ATTR(ERR_STS2_REG, 0444,
+ reg_show, NULL),
+ [PREST1] = __ATTR(PREST1_mS, 0644,
+ time_show, time_store),
+ [PPULS1] = __ATTR(PPULS1_uC, 0644,
+ coulomb_show, coulomb_store),
+ [NREST1] = __ATTR(NREST1_mS, 0644,
+ time_show, time_store),
+ [NPULS1] = __ATTR(NPULS1_mS, 0644,
+ time_show, time_store),
+ [PPCNT] = __ATTR(PPCNT, 0644,
+ time_show, time_store),
+ [VLIM1] = __ATTR(VLIM1_uV, 0644,
+ voltage_show, voltage_store),
+ [PVOLT1] = __ATTR(PVOLT1_uV, 0444,
+ voltage_show, NULL),
+ [PCUR1] = __ATTR(PCUR1_uA, 0444,
+ current_show, NULL),
+ [PTTIME] = __ATTR(PTTIME_S, 0444,
+ time_show, NULL),
+ [PREST2] = __ATTR(PREST2_mS, 0644,
+ time_show, time_store),
+ [PPULS2] = __ATTR(PPULS2_uC, 0644,
+ coulomb_show, coulomb_store),
+ [NREST2] = __ATTR(NREST2_mS, 0644,
+ time_show, time_store),
+ [NPULS2] = __ATTR(NPULS2_mS, 0644,
+ time_show, time_store),
+ [VLIM2] = __ATTR(VLIM2_uV, 0644,
+ voltage_show, voltage_store),
+ [PVOLT2] = __ATTR(PVOLT2_uV, 0444,
+ voltage_show, NULL),
+ [RVOLT2] = __ATTR(RVOLT2_uV, 0444,
+ voltage_show, NULL),
+ [PCUR2] = __ATTR(PCUR2_uA, 0444,
+ current_show, NULL),
+ [SCNT] = __ATTR(SCNT, 0644,
+ time_show, time_store),
+ [VMAX] = __ATTR(VMAX_uV, 0444,
+ voltage_show, NULL),
+ [SNUM] = __ATTR(SNUM, 0444,
+ time_show, NULL),
+ [VBATT] = __ATTR(VBATT_uV, 0444,
+ batt_prop_show, NULL),
+ [IBATT] = __ATTR(IBATT_uA, 0444,
+ batt_prop_show, NULL),
+ [BATTTEMP] = __ATTR(BATTTEMP_deciDegC, 0444,
+ batt_prop_show, NULL),
+ [BATTSOC] = __ATTR(BATTSOC, 0444,
+ batt_prop_show, NULL),
+ __ATTR_NULL,
+};
+
+static int qnovo_update_status(struct qnovo *chip)
+{
+ u8 val = 0;
+ int rc;
+ bool hw_ok_to_qnovo;
+
+ rc = qnovo_read(chip, QNOVO_ERROR_STS2, &val, 1);
+ if (rc < 0) {
+ pr_err("Couldn't read error sts rc = %d\n", rc);
+ hw_ok_to_qnovo = false;
+ } else {
+ /*
+ * For CV mode keep qnovo enabled, userspace is expected to
+ * disable it after few runs
+ */
+ hw_ok_to_qnovo = (val == ERR_CV_MODE || val == 0) ?
+ true : false;
+ }
+
+ vote(chip->not_ok_to_qnovo_votable, HW_OK_TO_QNOVO_VOTER,
+ !hw_ok_to_qnovo, 0);
+ return 0;
+}
+
+static void usb_debounce_work(struct work_struct *work)
+{
+ struct qnovo *chip = container_of(work,
+ struct qnovo, usb_debounce_work.work);
+
+ vote(chip->chg_ready_votable, USB_READY_VOTER, true, 0);
+ vote(chip->awake_votable, USB_READY_VOTER, false, 0);
+}
+
+static void dc_debounce_work(struct work_struct *work)
+{
+ struct qnovo *chip = container_of(work,
+ struct qnovo, dc_debounce_work.work);
+
+ vote(chip->chg_ready_votable, DC_READY_VOTER, true, 0);
+ vote(chip->awake_votable, DC_READY_VOTER, false, 0);
+}
+
+#define DEBOUNCE_MS 15000 /* 15 seconds */
+static void status_change_work(struct work_struct *work)
+{
+ struct qnovo *chip = container_of(work,
+ struct qnovo, status_change_work);
+ union power_supply_propval pval;
+ bool usb_present = false, dc_present = false;
+ int rc;
+
+ if (is_fg_available(chip))
+ vote(chip->disable_votable, FG_AVAILABLE_VOTER, false, 0);
+
+ if (is_usb_available(chip)) {
+ rc = power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT, &pval);
+ usb_present = (rc < 0) ? 0 : pval.intval;
+ }
+
+ if (chip->usb_present && !usb_present) {
+ /* removal */
+ chip->usb_present = 0;
+ cancel_delayed_work_sync(&chip->usb_debounce_work);
+ vote(chip->awake_votable, USB_READY_VOTER, false, 0);
+ vote(chip->chg_ready_votable, USB_READY_VOTER, false, 0);
+ } else if (!chip->usb_present && usb_present) {
+ /* insertion */
+ chip->usb_present = 1;
+ vote(chip->awake_votable, USB_READY_VOTER, true, 0);
+ schedule_delayed_work(&chip->usb_debounce_work,
+ msecs_to_jiffies(DEBOUNCE_MS));
+ }
+
+ if (is_dc_available(chip)) {
+ rc = power_supply_get_property(chip->dc_psy,
+ POWER_SUPPLY_PROP_PRESENT,
+ &pval);
+ dc_present = (rc < 0) ? 0 : pval.intval;
+ }
+
+ if (usb_present)
+ dc_present = 0;
+
+ if (chip->dc_present && !dc_present) {
+ /* removal */
+ chip->dc_present = 0;
+ cancel_delayed_work_sync(&chip->dc_debounce_work);
+ vote(chip->awake_votable, DC_READY_VOTER, false, 0);
+ vote(chip->chg_ready_votable, DC_READY_VOTER, false, 0);
+ } else if (!chip->dc_present && dc_present) {
+ /* insertion */
+ chip->dc_present = 1;
+ vote(chip->awake_votable, DC_READY_VOTER, true, 0);
+ schedule_delayed_work(&chip->dc_debounce_work,
+ msecs_to_jiffies(DEBOUNCE_MS));
+ }
+
+ qnovo_update_status(chip);
+}
+
+static void ptrain_restart_work(struct work_struct *work)
+{
+ struct qnovo *chip = container_of(work,
+ struct qnovo, ptrain_restart_work.work);
+ u8 pt_t1, pt_t2;
+ int rc;
+
+ rc = qnovo_read(chip, QNOVO_PTTIME_STS, &pt_t1, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read QNOVO_PTTIME_STS rc = %d\n",
+ rc);
+ goto clean_up;
+ }
+
+ /* pttime increments every 2 seconds */
+ msleep(2100);
+
+ rc = qnovo_read(chip, QNOVO_PTTIME_STS, &pt_t2, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read QNOVO_PTTIME_STS rc = %d\n",
+ rc);
+ goto clean_up;
+ }
+
+ if (pt_t1 != pt_t2)
+ goto clean_up;
+
+ /* Toggle pt enable to restart pulse train */
+ rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't disable pulse train rc=%d\n", rc);
+ goto clean_up;
+ }
+ msleep(1000);
+ rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT,
+ QNOVO_PTRAIN_EN_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't enable pulse train rc=%d\n", rc);
+ goto clean_up;
+ }
+
+clean_up:
+ vote(chip->awake_votable, PT_RESTART_VOTER, false, 0);
+}
+
+static int qnovo_notifier_call(struct notifier_block *nb,
+ unsigned long ev, void *v)
+{
+ struct power_supply *psy = v;
+ struct qnovo *chip = container_of(nb, struct qnovo, nb);
+
+ if (ev != PSY_EVENT_PROP_CHANGED)
+ return NOTIFY_OK;
+
+ if (strcmp(psy->desc->name, "battery") == 0
+ || strcmp(psy->desc->name, "bms") == 0
+ || strcmp(psy->desc->name, "usb") == 0
+ || strcmp(psy->desc->name, "dc") == 0)
+ schedule_work(&chip->status_change_work);
+
+ return NOTIFY_OK;
+}
+
+static irqreturn_t handle_ptrain_done(int irq, void *data)
+{
+ struct qnovo *chip = data;
+ union power_supply_propval pval = {0};
+
+ /*
+ * In some cases (esp shutting down) the userspace would disable by
+ * setting qnovo_enable=0. Also charger could be removed or there is
+ * an error (i.e. its not okay to run qnovo)-
+ * skip taking ESR measurement in such situations
+ */
+
+ if (get_client_vote(chip->disable_votable, USER_VOTER)
+ || get_effective_result(chip->not_ok_to_qnovo_votable) > 0)
+ return IRQ_HANDLED;
+
+ /*
+ * hw resets pt_en bit once ptrain_done triggers.
+ * vote on behalf of QNI to disable it such that
+ * once QNI enables it, the votable state changes
+ * and the callback that sets it is indeed invoked
+ */
+ vote(chip->pt_dis_votable, QNI_PT_VOTER, true, 0);
+
+ vote(chip->pt_dis_votable, ESR_VOTER, true, 0);
+ if (is_fg_available(chip))
+ power_supply_set_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_RESISTANCE,
+ &pval);
+
+ vote(chip->pt_dis_votable, ESR_VOTER, false, 0);
+ qnovo_update_status(chip);
+ kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
+ return IRQ_HANDLED;
+}
+
+static int qnovo_hw_init(struct qnovo *chip)
+{
+ int rc;
+ u8 iadc_offset_external, iadc_offset_internal;
+ u8 iadc_gain_external, iadc_gain_internal;
+ u8 vadc_offset, vadc_gain;
+ u8 val;
+
+ vote(chip->disable_votable, USER_VOTER, true, 0);
+ vote(chip->disable_votable, FG_AVAILABLE_VOTER, true, 0);
+
+ vote(chip->pt_dis_votable, QNI_PT_VOTER, true, 0);
+ vote(chip->pt_dis_votable, QNOVO_OVERALL_VOTER, true, 0);
+ vote(chip->pt_dis_votable, ESR_VOTER, false, 0);
+
+ val = 0;
+ rc = qnovo_write(chip, QNOVO_STRM_CTRL, &val, 1);
+ if (rc < 0) {
+ pr_err("Couldn't write iadc bitstream control rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = qnovo_read(chip, QNOVO_IADC_OFFSET_0, &iadc_offset_external, 1);
+ if (rc < 0) {
+ pr_err("Couldn't read iadc exernal offset rc = %d\n", rc);
+ return rc;
+ }
+
+ /* stored as an 8 bit 2's complement signed integer */
+ val = -1 * iadc_offset_external;
+ rc = qnovo_write(chip, QNOVO_TR_IADC_OFFSET_0, &val, 1);
+ if (rc < 0) {
+ pr_err("Couldn't write iadc offset rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = qnovo_read(chip, QNOVO_IADC_OFFSET_1, &iadc_offset_internal, 1);
+ if (rc < 0) {
+ pr_err("Couldn't read iadc internal offset rc = %d\n", rc);
+ return rc;
+ }
+
+ /* stored as an 8 bit 2's complement signed integer */
+ val = -1 * iadc_offset_internal;
+ rc = qnovo_write(chip, QNOVO_TR_IADC_OFFSET_1, &val, 1);
+ if (rc < 0) {
+ pr_err("Couldn't write iadc offset rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = qnovo_read(chip, QNOVO_IADC_GAIN_0, &iadc_gain_external, 1);
+ if (rc < 0) {
+ pr_err("Couldn't read iadc external gain rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = qnovo_read(chip, QNOVO_IADC_GAIN_1, &iadc_gain_internal, 1);
+ if (rc < 0) {
+ pr_err("Couldn't read iadc internal gain rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = qnovo_read(chip, QNOVO_VADC_OFFSET, &vadc_offset, 1);
+ if (rc < 0) {
+ pr_err("Couldn't read vadc offset rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = qnovo_read(chip, QNOVO_VADC_GAIN, &vadc_gain, 1);
+ if (rc < 0) {
+ pr_err("Couldn't read vadc external gain rc = %d\n", rc);
+ return rc;
+ }
+
+ chip->external_offset_nA = (s64)(s8)iadc_offset_external * IADC_LSB_NA;
+ chip->internal_offset_nA = (s64)(s8)iadc_offset_internal * IADC_LSB_NA;
+ chip->offset_nV = (s64)(s8)vadc_offset * VADC_LSB_NA;
+ chip->external_i_gain_mega
+ = 1000000000 + (s64)(s8)iadc_gain_external * GAIN_LSB_FACTOR;
+ chip->external_i_gain_mega
+ = div_s64(chip->external_i_gain_mega, 1000);
+ chip->internal_i_gain_mega
+ = 1000000000 + (s64)(s8)iadc_gain_internal * GAIN_LSB_FACTOR;
+ chip->internal_i_gain_mega
+ = div_s64(chip->internal_i_gain_mega, 1000);
+ chip->v_gain_mega = 1000000000 + (s64)(s8)vadc_gain * GAIN_LSB_FACTOR;
+ chip->v_gain_mega = div_s64(chip->v_gain_mega, 1000);
+
+ /* allow charger error conditions to disable qnovo, CV mode excluded */
+ val = ERR_SWITCHER_DISABLED | ERR_JEITA_SOFT_CONDITION | ERR_BAT_OV |
+ ERR_BATTERY_MISSING | ERR_SAFETY_TIMER_EXPIRED |
+ ERR_CHARGING_DISABLED | ERR_JEITA_HARD_CONDITION;
+ rc = qnovo_write(chip, QNOVO_DISABLE_CHARGING, &val, 1);
+ if (rc < 0) {
+ pr_err("Couldn't write QNOVO_DISABLE_CHARGING rc = %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qnovo_register_notifier(struct qnovo *chip)
+{
+ int rc;
+
+ chip->nb.notifier_call = qnovo_notifier_call;
+ rc = power_supply_reg_notifier(&chip->nb);
+ if (rc < 0) {
+ pr_err("Couldn't register psy notifier rc = %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qnovo_determine_initial_status(struct qnovo *chip)
+{
+ status_change_work(&chip->status_change_work);
+ return 0;
+}
+
+static int qnovo_request_interrupts(struct qnovo *chip)
+{
+ int rc = 0;
+ int irq_ptrain_done = of_irq_get_byname(chip->dev->of_node,
+ "ptrain-done");
+
+ rc = devm_request_threaded_irq(chip->dev, irq_ptrain_done, NULL,
+ handle_ptrain_done,
+ IRQF_ONESHOT, "ptrain-done", chip);
+ if (rc < 0) {
+ pr_err("Couldn't request irq %d rc = %d\n",
+ irq_ptrain_done, rc);
+ return rc;
+ }
+
+ enable_irq_wake(irq_ptrain_done);
+
+ return rc;
+}
+
+static int qnovo_probe(struct platform_device *pdev)
+{
+ struct qnovo *chip;
+ int rc = 0;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->fv_uV_request = -EINVAL;
+ chip->fcc_uA_request = -EINVAL;
+ chip->dev = &pdev->dev;
+ mutex_init(&chip->write_lock);
+
+ chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
+ if (!chip->regmap) {
+ pr_err("parent regmap is missing\n");
+ return -EINVAL;
+ }
+
+ rc = qnovo_parse_dt(chip);
+ if (rc < 0) {
+ pr_err("Couldn't parse device tree rc=%d\n", rc);
+ return rc;
+ }
+
+ /* set driver data before resources request it */
+ platform_set_drvdata(pdev, chip);
+
+ chip->disable_votable = create_votable("QNOVO_DISABLE", VOTE_SET_ANY,
+ qnovo_disable_cb, chip);
+ if (IS_ERR(chip->disable_votable)) {
+ rc = PTR_ERR(chip->disable_votable);
+ goto cleanup;
+ }
+
+ chip->pt_dis_votable = create_votable("QNOVO_PT_DIS", VOTE_SET_ANY,
+ pt_dis_votable_cb, chip);
+ if (IS_ERR(chip->pt_dis_votable)) {
+ rc = PTR_ERR(chip->pt_dis_votable);
+ goto destroy_disable_votable;
+ }
+
+ chip->not_ok_to_qnovo_votable = create_votable("QNOVO_NOT_OK",
+ VOTE_SET_ANY,
+ not_ok_to_qnovo_cb, chip);
+ if (IS_ERR(chip->not_ok_to_qnovo_votable)) {
+ rc = PTR_ERR(chip->not_ok_to_qnovo_votable);
+ goto destroy_pt_dis_votable;
+ }
+
+ chip->chg_ready_votable = create_votable("QNOVO_CHG_READY",
+ VOTE_SET_ANY,
+ chg_ready_cb, chip);
+ if (IS_ERR(chip->chg_ready_votable)) {
+ rc = PTR_ERR(chip->chg_ready_votable);
+ goto destroy_not_ok_to_qnovo_votable;
+ }
+
+ chip->awake_votable = create_votable("QNOVO_AWAKE", VOTE_SET_ANY,
+ awake_cb, chip);
+ if (IS_ERR(chip->awake_votable)) {
+ rc = PTR_ERR(chip->awake_votable);
+ goto destroy_chg_ready_votable;
+ }
+
+ INIT_WORK(&chip->status_change_work, status_change_work);
+ INIT_DELAYED_WORK(&chip->dc_debounce_work, dc_debounce_work);
+ INIT_DELAYED_WORK(&chip->usb_debounce_work, usb_debounce_work);
+ INIT_DELAYED_WORK(&chip->ptrain_restart_work, ptrain_restart_work);
+
+ rc = qnovo_hw_init(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize hardware rc=%d\n", rc);
+ goto destroy_awake_votable;
+ }
+
+ rc = qnovo_register_notifier(chip);
+ if (rc < 0) {
+ pr_err("Couldn't register psy notifier rc = %d\n", rc);
+ goto unreg_notifier;
+ }
+
+ rc = qnovo_determine_initial_status(chip);
+ if (rc < 0) {
+ pr_err("Couldn't determine initial status rc=%d\n", rc);
+ goto unreg_notifier;
+ }
+
+ rc = qnovo_request_interrupts(chip);
+ if (rc < 0) {
+ pr_err("Couldn't request interrupts rc=%d\n", rc);
+ goto unreg_notifier;
+ }
+ chip->qnovo_class.name = "qnovo",
+ chip->qnovo_class.owner = THIS_MODULE,
+ chip->qnovo_class.class_attrs = qnovo_attributes;
+
+ rc = class_register(&chip->qnovo_class);
+ if (rc < 0) {
+ pr_err("couldn't register qnovo sysfs class rc = %d\n", rc);
+ goto unreg_notifier;
+ }
+
+ device_init_wakeup(chip->dev, true);
+
+ return rc;
+
+unreg_notifier:
+ power_supply_unreg_notifier(&chip->nb);
+destroy_awake_votable:
+ destroy_votable(chip->awake_votable);
+destroy_chg_ready_votable:
+ destroy_votable(chip->chg_ready_votable);
+destroy_not_ok_to_qnovo_votable:
+ destroy_votable(chip->not_ok_to_qnovo_votable);
+destroy_pt_dis_votable:
+ destroy_votable(chip->pt_dis_votable);
+destroy_disable_votable:
+ destroy_votable(chip->disable_votable);
+cleanup:
+ platform_set_drvdata(pdev, NULL);
+ return rc;
+}
+
+static int qnovo_remove(struct platform_device *pdev)
+{
+ struct qnovo *chip = platform_get_drvdata(pdev);
+
+ class_unregister(&chip->qnovo_class);
+ power_supply_unreg_notifier(&chip->nb);
+ destroy_votable(chip->chg_ready_votable);
+ destroy_votable(chip->not_ok_to_qnovo_votable);
+ destroy_votable(chip->pt_dis_votable);
+ destroy_votable(chip->disable_votable);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static const struct of_device_id match_table[] = {
+ { .compatible = "qcom,qpnp-qnovo", },
+ { },
+};
+
+static struct platform_driver qnovo_driver = {
+ .driver = {
+ .name = "qcom,qnovo-driver",
+ .owner = THIS_MODULE,
+ .of_match_table = match_table,
+ },
+ .probe = qnovo_probe,
+ .remove = qnovo_remove,
+};
+module_platform_driver(qnovo_driver);
+
+MODULE_DESCRIPTION("QPNP Qnovo Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/qcom/qpnp-smb2.c b/drivers/power/supply/qcom/qpnp-smb2.c
new file mode 100644
index 000000000000..c085256a794a
--- /dev/null
+++ b/drivers/power/supply/qcom/qpnp-smb2.c
@@ -0,0 +1,2464 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/log2.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include "smb-reg.h"
+#include "smb-lib.h"
+#include "storm-watch.h"
+#include <linux/pmic-voter.h>
+
+#define SMB2_DEFAULT_WPWR_UW 8000000
+
+static struct smb_params v1_params = {
+ .fcc = {
+ .name = "fast charge current",
+ .reg = FAST_CHARGE_CURRENT_CFG_REG,
+ .min_u = 0,
+ .max_u = 4500000,
+ .step_u = 25000,
+ },
+ .fv = {
+ .name = "float voltage",
+ .reg = FLOAT_VOLTAGE_CFG_REG,
+ .min_u = 3487500,
+ .max_u = 4920000,
+ .step_u = 7500,
+ },
+ .usb_icl = {
+ .name = "usb input current limit",
+ .reg = USBIN_CURRENT_LIMIT_CFG_REG,
+ .min_u = 0,
+ .max_u = 4800000,
+ .step_u = 25000,
+ },
+ .icl_stat = {
+ .name = "input current limit status",
+ .reg = ICL_STATUS_REG,
+ .min_u = 0,
+ .max_u = 4800000,
+ .step_u = 25000,
+ },
+ .otg_cl = {
+ .name = "usb otg current limit",
+ .reg = OTG_CURRENT_LIMIT_CFG_REG,
+ .min_u = 250000,
+ .max_u = 2000000,
+ .step_u = 250000,
+ },
+ .dc_icl = {
+ .name = "dc input current limit",
+ .reg = DCIN_CURRENT_LIMIT_CFG_REG,
+ .min_u = 0,
+ .max_u = 6000000,
+ .step_u = 25000,
+ },
+ .dc_icl_pt_lv = {
+ .name = "dc icl PT <8V",
+ .reg = ZIN_ICL_PT_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .dc_icl_pt_hv = {
+ .name = "dc icl PT >8V",
+ .reg = ZIN_ICL_PT_HV_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .dc_icl_div2_lv = {
+ .name = "dc icl div2 <5.5V",
+ .reg = ZIN_ICL_LV_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .dc_icl_div2_mid_lv = {
+ .name = "dc icl div2 5.5-6.5V",
+ .reg = ZIN_ICL_MID_LV_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .dc_icl_div2_mid_hv = {
+ .name = "dc icl div2 6.5-8.0V",
+ .reg = ZIN_ICL_MID_HV_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .dc_icl_div2_hv = {
+ .name = "dc icl div2 >8.0V",
+ .reg = ZIN_ICL_HV_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .jeita_cc_comp = {
+ .name = "jeita fcc reduction",
+ .reg = JEITA_CCCOMP_CFG_REG,
+ .min_u = 0,
+ .max_u = 1575000,
+ .step_u = 25000,
+ },
+ .freq_buck = {
+ .name = "buck switching frequency",
+ .reg = CFG_BUCKBOOST_FREQ_SELECT_BUCK_REG,
+ .min_u = 600,
+ .max_u = 2000,
+ .step_u = 200,
+ },
+ .freq_boost = {
+ .name = "boost switching frequency",
+ .reg = CFG_BUCKBOOST_FREQ_SELECT_BOOST_REG,
+ .min_u = 600,
+ .max_u = 2000,
+ .step_u = 200,
+ },
+};
+
+static struct smb_params pm660_params = {
+ .freq_buck = {
+ .name = "buck switching frequency",
+ .reg = FREQ_CLK_DIV_REG,
+ .min_u = 600,
+ .max_u = 1600,
+ .set_proc = smblib_set_chg_freq,
+ },
+ .freq_boost = {
+ .name = "boost switching frequency",
+ .reg = FREQ_CLK_DIV_REG,
+ .min_u = 600,
+ .max_u = 1600,
+ .set_proc = smblib_set_chg_freq,
+ },
+};
+
+struct smb_dt_props {
+ int usb_icl_ua;
+ int dc_icl_ua;
+ int boost_threshold_ua;
+ int wipower_max_uw;
+ int min_freq_khz;
+ int max_freq_khz;
+ struct device_node *revid_dev_node;
+ int float_option;
+ int chg_inhibit_thr_mv;
+ bool no_battery;
+ bool hvdcp_disable;
+ bool auto_recharge_soc;
+ int wd_bark_time;
+};
+
+struct smb2 {
+ struct smb_charger chg;
+ struct dentry *dfs_root;
+ struct smb_dt_props dt;
+ bool bad_part;
+};
+
+static int __debug_mask;
+module_param_named(
+ debug_mask, __debug_mask, int, S_IRUSR | S_IWUSR
+);
+
+static int __weak_chg_icl_ua = 500000;
+module_param_named(
+ weak_chg_icl_ua, __weak_chg_icl_ua, int, S_IRUSR | S_IWUSR);
+
+#define MICRO_1P5A 1500000
+#define MICRO_P1A 100000
+#define OTG_DEFAULT_DEGLITCH_TIME_MS 50
+#define MIN_WD_BARK_TIME 16
+#define DEFAULT_WD_BARK_TIME 64
+#define BITE_WDOG_TIMEOUT_8S 0x3
+#define BARK_WDOG_TIMEOUT_MASK GENMASK(3, 2)
+#define BARK_WDOG_TIMEOUT_SHIFT 2
+static int smb2_parse_dt(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct device_node *node = chg->dev->of_node;
+ int rc, byte_len;
+
+ if (!node) {
+ pr_err("device tree node missing\n");
+ return -EINVAL;
+ }
+
+ chg->step_chg_enabled = of_property_read_bool(node,
+ "qcom,step-charging-enable");
+
+ chg->sw_jeita_enabled = of_property_read_bool(node,
+ "qcom,sw-jeita-enable");
+
+ rc = of_property_read_u32(node, "qcom,wd-bark-time-secs",
+ &chip->dt.wd_bark_time);
+ if (rc < 0 || chip->dt.wd_bark_time < MIN_WD_BARK_TIME)
+ chip->dt.wd_bark_time = DEFAULT_WD_BARK_TIME;
+
+ chip->dt.no_battery = of_property_read_bool(node,
+ "qcom,batteryless-platform");
+
+ rc = of_property_read_u32(node,
+ "qcom,fcc-max-ua", &chg->batt_profile_fcc_ua);
+ if (rc < 0)
+ chg->batt_profile_fcc_ua = -EINVAL;
+
+ rc = of_property_read_u32(node,
+ "qcom,fv-max-uv", &chg->batt_profile_fv_uv);
+ if (rc < 0)
+ chg->batt_profile_fv_uv = -EINVAL;
+
+ rc = of_property_read_u32(node,
+ "qcom,usb-icl-ua", &chip->dt.usb_icl_ua);
+ if (rc < 0)
+ chip->dt.usb_icl_ua = -EINVAL;
+
+ rc = of_property_read_u32(node,
+ "qcom,otg-cl-ua", &chg->otg_cl_ua);
+ if (rc < 0)
+ chg->otg_cl_ua = MICRO_1P5A;
+
+ rc = of_property_read_u32(node,
+ "qcom,dc-icl-ua", &chip->dt.dc_icl_ua);
+ if (rc < 0)
+ chip->dt.dc_icl_ua = -EINVAL;
+
+ rc = of_property_read_u32(node,
+ "qcom,boost-threshold-ua",
+ &chip->dt.boost_threshold_ua);
+ if (rc < 0)
+ chip->dt.boost_threshold_ua = MICRO_P1A;
+
+ rc = of_property_read_u32(node,
+ "qcom,min-freq-khz",
+ &chip->dt.min_freq_khz);
+ if (rc < 0)
+ chip->dt.min_freq_khz = -EINVAL;
+
+ rc = of_property_read_u32(node,
+ "qcom,max-freq-khz",
+ &chip->dt.max_freq_khz);
+ if (rc < 0)
+ chip->dt.max_freq_khz = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,wipower-max-uw",
+ &chip->dt.wipower_max_uw);
+ if (rc < 0)
+ chip->dt.wipower_max_uw = -EINVAL;
+
+ if (of_find_property(node, "qcom,thermal-mitigation", &byte_len)) {
+ chg->thermal_mitigation = devm_kzalloc(chg->dev, byte_len,
+ GFP_KERNEL);
+
+ if (chg->thermal_mitigation == NULL)
+ return -ENOMEM;
+
+ chg->thermal_levels = byte_len / sizeof(u32);
+ rc = of_property_read_u32_array(node,
+ "qcom,thermal-mitigation",
+ chg->thermal_mitigation,
+ chg->thermal_levels);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't read threm limits rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ of_property_read_u32(node, "qcom,float-option", &chip->dt.float_option);
+ if (chip->dt.float_option < 0 || chip->dt.float_option > 4) {
+ pr_err("qcom,float-option is out of range [0, 4]\n");
+ return -EINVAL;
+ }
+
+ chip->dt.hvdcp_disable = of_property_read_bool(node,
+ "qcom,hvdcp-disable");
+
+ of_property_read_u32(node, "qcom,chg-inhibit-threshold-mv",
+ &chip->dt.chg_inhibit_thr_mv);
+ if ((chip->dt.chg_inhibit_thr_mv < 0 ||
+ chip->dt.chg_inhibit_thr_mv > 300)) {
+ pr_err("qcom,chg-inhibit-threshold-mv is incorrect\n");
+ return -EINVAL;
+ }
+
+ chip->dt.auto_recharge_soc = of_property_read_bool(node,
+ "qcom,auto-recharge-soc");
+
+ chg->micro_usb_mode = of_property_read_bool(node, "qcom,micro-usb");
+
+ chg->dcp_icl_ua = chip->dt.usb_icl_ua;
+
+ chg->suspend_input_on_debug_batt = of_property_read_bool(node,
+ "qcom,suspend-input-on-debug-batt");
+
+ rc = of_property_read_u32(node, "qcom,otg-deglitch-time-ms",
+ &chg->otg_delay_ms);
+ if (rc < 0)
+ chg->otg_delay_ms = OTG_DEFAULT_DEGLITCH_TIME_MS;
+
+ return 0;
+}
+
+/************************
+ * USB PSY REGISTRATION *
+ ************************/
+
+static enum power_supply_property smb2_usb_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_PD_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_TYPE,
+ POWER_SUPPLY_PROP_TYPEC_MODE,
+ POWER_SUPPLY_PROP_TYPEC_POWER_ROLE,
+ POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION,
+ POWER_SUPPLY_PROP_PD_ALLOWED,
+ POWER_SUPPLY_PROP_PD_ACTIVE,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_NOW,
+ POWER_SUPPLY_PROP_BOOST_CURRENT,
+ POWER_SUPPLY_PROP_PE_START,
+ POWER_SUPPLY_PROP_CTM_CURRENT_MAX,
+ POWER_SUPPLY_PROP_HW_CURRENT_MAX,
+ POWER_SUPPLY_PROP_REAL_TYPE,
+ POWER_SUPPLY_PROP_PR_SWAP,
+ POWER_SUPPLY_PROP_PD_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_PD_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_SDP_CURRENT_MAX,
+};
+
+static int smb2_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (chip->bad_part)
+ val->intval = 1;
+ else
+ rc = smblib_get_prop_usb_present(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ rc = smblib_get_prop_usb_online(chg, val);
+ if (!val->intval)
+ break;
+
+ if ((chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT ||
+ chg->micro_usb_mode) &&
+ chg->real_charger_type == POWER_SUPPLY_TYPE_USB)
+ val->intval = 0;
+ else
+ val->intval = 1;
+ if (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN)
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smblib_get_prop_usb_voltage_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ rc = smblib_get_prop_usb_voltage_now(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_CURRENT_MAX:
+ val->intval = get_client_vote(chg->usb_icl_votable, PD_VOTER);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_get_prop_input_current_settled(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_TYPE:
+ val->intval = POWER_SUPPLY_TYPE_USB_PD;
+ break;
+ case POWER_SUPPLY_PROP_REAL_TYPE:
+ if (chip->bad_part)
+ val->intval = POWER_SUPPLY_TYPE_USB_PD;
+ else
+ val->intval = chg->real_charger_type;
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_MODE:
+ if (chg->micro_usb_mode)
+ val->intval = POWER_SUPPLY_TYPEC_NONE;
+ else if (chip->bad_part)
+ val->intval = POWER_SUPPLY_TYPEC_SOURCE_DEFAULT;
+ else
+ val->intval = chg->typec_mode;
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+ if (chg->micro_usb_mode)
+ val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
+ else
+ rc = smblib_get_prop_typec_power_role(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION:
+ if (chg->micro_usb_mode)
+ val->intval = 0;
+ else
+ rc = smblib_get_prop_typec_cc_orientation(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_ALLOWED:
+ rc = smblib_get_prop_pd_allowed(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_ACTIVE:
+ val->intval = chg->pd_active;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED:
+ rc = smblib_get_prop_input_current_settled(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_NOW:
+ rc = smblib_get_prop_usb_current_now(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_BOOST_CURRENT:
+ val->intval = chg->boost_current_ua;
+ break;
+ case POWER_SUPPLY_PROP_PD_IN_HARD_RESET:
+ rc = smblib_get_prop_pd_in_hard_reset(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED:
+ val->intval = chg->system_suspend_supported;
+ break;
+ case POWER_SUPPLY_PROP_PE_START:
+ rc = smblib_get_pe_start(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CTM_CURRENT_MAX:
+ val->intval = get_client_vote(chg->usb_icl_votable, CTM_VOTER);
+ break;
+ case POWER_SUPPLY_PROP_HW_CURRENT_MAX:
+ rc = smblib_get_charge_current(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_PR_SWAP:
+ rc = smblib_get_prop_pr_swap_in_progress(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_VOLTAGE_MAX:
+ val->intval = chg->voltage_max_uv;
+ break;
+ case POWER_SUPPLY_PROP_PD_VOLTAGE_MIN:
+ val->intval = chg->voltage_min_uv;
+ break;
+ case POWER_SUPPLY_PROP_SDP_CURRENT_MAX:
+ val->intval = get_client_vote(chg->usb_icl_votable,
+ USB_PSY_VOTER);
+ break;
+ default:
+ pr_err("get prop %d is not supported in usb\n", psp);
+ rc = -EINVAL;
+ break;
+ }
+ if (rc < 0) {
+ pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+ return -ENODATA;
+ }
+ return 0;
+}
+
+static int smb2_usb_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ mutex_lock(&chg->lock);
+ if (!chg->typec_present) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PD_CURRENT_MAX:
+ rc = smblib_set_prop_pd_current_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+ rc = smblib_set_prop_typec_power_role(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_ACTIVE:
+ rc = smblib_set_prop_pd_active(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_IN_HARD_RESET:
+ rc = smblib_set_prop_pd_in_hard_reset(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED:
+ chg->system_suspend_supported = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_BOOST_CURRENT:
+ rc = smblib_set_prop_boost_current(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CTM_CURRENT_MAX:
+ rc = vote(chg->usb_icl_votable, CTM_VOTER,
+ val->intval >= 0, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_PR_SWAP:
+ rc = smblib_set_prop_pr_swap_in_progress(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_VOLTAGE_MAX:
+ rc = smblib_set_prop_pd_voltage_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_VOLTAGE_MIN:
+ rc = smblib_set_prop_pd_voltage_min(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_SDP_CURRENT_MAX:
+ rc = smblib_set_prop_sdp_current_max(chg, val);
+ break;
+ default:
+ pr_err("set prop %d is not supported\n", psp);
+ rc = -EINVAL;
+ break;
+ }
+
+unlock:
+ mutex_unlock(&chg->lock);
+ return rc;
+}
+
+static int smb2_usb_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CTM_CURRENT_MAX:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int smb2_init_usb_psy(struct smb2 *chip)
+{
+ struct power_supply_config usb_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+
+ chg->usb_psy_desc.name = "usb";
+ chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_PD;
+ chg->usb_psy_desc.properties = smb2_usb_props;
+ chg->usb_psy_desc.num_properties = ARRAY_SIZE(smb2_usb_props);
+ chg->usb_psy_desc.get_property = smb2_usb_get_prop;
+ chg->usb_psy_desc.set_property = smb2_usb_set_prop;
+ chg->usb_psy_desc.property_is_writeable = smb2_usb_prop_is_writeable;
+
+ usb_cfg.drv_data = chip;
+ usb_cfg.of_node = chg->dev->of_node;
+ chg->usb_psy = power_supply_register(chg->dev,
+ &chg->usb_psy_desc,
+ &usb_cfg);
+ if (IS_ERR(chg->usb_psy)) {
+ pr_err("Couldn't register USB power supply\n");
+ return PTR_ERR(chg->usb_psy);
+ }
+
+ return 0;
+}
+
+/********************************
+ * USB PC_PORT PSY REGISTRATION *
+ ********************************/
+static enum power_supply_property smb2_usb_port_props[] = {
+ POWER_SUPPLY_PROP_TYPE,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static int smb2_usb_port_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TYPE:
+ val->intval = POWER_SUPPLY_TYPE_USB;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ rc = smblib_get_prop_usb_online(chg, val);
+ if (!val->intval)
+ break;
+
+ if ((chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT ||
+ chg->micro_usb_mode) &&
+ chg->real_charger_type == POWER_SUPPLY_TYPE_USB)
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = 5000000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_get_prop_input_current_settled(chg, val);
+ break;
+ default:
+ pr_err_ratelimited("Get prop %d is not supported in pc_port\n",
+ psp);
+ return -EINVAL;
+ }
+
+ if (rc < 0) {
+ pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+ return -ENODATA;
+ }
+
+ return 0;
+}
+
+static int smb2_usb_port_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+
+ switch (psp) {
+ default:
+ pr_err_ratelimited("Set prop %d is not supported in pc_port\n",
+ psp);
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static const struct power_supply_desc usb_port_psy_desc = {
+ .name = "pc_port",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = smb2_usb_port_props,
+ .num_properties = ARRAY_SIZE(smb2_usb_port_props),
+ .get_property = smb2_usb_port_get_prop,
+ .set_property = smb2_usb_port_set_prop,
+};
+
+static int smb2_init_usb_port_psy(struct smb2 *chip)
+{
+ struct power_supply_config usb_port_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+
+ usb_port_cfg.drv_data = chip;
+ usb_port_cfg.of_node = chg->dev->of_node;
+ chg->usb_port_psy = power_supply_register(chg->dev,
+ &usb_port_psy_desc,
+ &usb_port_cfg);
+ if (IS_ERR(chg->usb_port_psy)) {
+ pr_err("Couldn't register USB pc_port power supply\n");
+ return PTR_ERR(chg->usb_port_psy);
+ }
+
+ return 0;
+}
+
+/*****************************
+ * USB MAIN PSY REGISTRATION *
+ *****************************/
+
+static enum power_supply_property smb2_usb_main_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_TYPE,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED,
+ POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED,
+ POWER_SUPPLY_PROP_FCC_DELTA,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ /*
+ * TODO move the TEMP and TEMP_MAX properties here,
+ * and update the thermal balancer to look here
+ */
+};
+
+static int smb2_usb_main_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smblib_get_charge_param(chg, &chg->param.fv, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ rc = smblib_get_charge_param(chg, &chg->param.fcc,
+ &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TYPE:
+ val->intval = POWER_SUPPLY_TYPE_MAIN;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED:
+ rc = smblib_get_prop_input_current_settled(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED:
+ rc = smblib_get_prop_input_voltage_settled(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_FCC_DELTA:
+ rc = smblib_get_prop_fcc_delta(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_get_icl_current(chg, &val->intval);
+ break;
+ default:
+ pr_debug("get prop %d is not supported in usb-main\n", psp);
+ rc = -EINVAL;
+ break;
+ }
+ if (rc < 0) {
+ pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+ return -ENODATA;
+ }
+ return 0;
+}
+
+static int smb2_usb_main_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smblib_set_charge_param(chg, &chg->param.fv, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ rc = smblib_set_charge_param(chg, &chg->param.fcc, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_set_icl_current(chg, val->intval);
+ break;
+ default:
+ pr_err("set prop %d is not supported\n", psp);
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static const struct power_supply_desc usb_main_psy_desc = {
+ .name = "main",
+ .type = POWER_SUPPLY_TYPE_MAIN,
+ .properties = smb2_usb_main_props,
+ .num_properties = ARRAY_SIZE(smb2_usb_main_props),
+ .get_property = smb2_usb_main_get_prop,
+ .set_property = smb2_usb_main_set_prop,
+};
+
+static int smb2_init_usb_main_psy(struct smb2 *chip)
+{
+ struct power_supply_config usb_main_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+
+ usb_main_cfg.drv_data = chip;
+ usb_main_cfg.of_node = chg->dev->of_node;
+ chg->usb_main_psy = power_supply_register(chg->dev,
+ &usb_main_psy_desc,
+ &usb_main_cfg);
+ if (IS_ERR(chg->usb_main_psy)) {
+ pr_err("Couldn't register USB main power supply\n");
+ return PTR_ERR(chg->usb_main_psy);
+ }
+
+ return 0;
+}
+
+/*************************
+ * DC PSY REGISTRATION *
+ *************************/
+
+static enum power_supply_property smb2_dc_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_REAL_TYPE,
+};
+
+static int smb2_dc_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ rc = smblib_get_prop_dc_present(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ rc = smblib_get_prop_dc_online(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_get_prop_dc_current_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_REAL_TYPE:
+ val->intval = POWER_SUPPLY_TYPE_WIPOWER;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (rc < 0) {
+ pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+ return -ENODATA;
+ }
+ return 0;
+}
+
+static int smb2_dc_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_set_prop_dc_current_max(chg, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb2_dc_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ int rc;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = 1;
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+
+ return rc;
+}
+
+static const struct power_supply_desc dc_psy_desc = {
+ .name = "dc",
+ .type = POWER_SUPPLY_TYPE_WIRELESS,
+ .properties = smb2_dc_props,
+ .num_properties = ARRAY_SIZE(smb2_dc_props),
+ .get_property = smb2_dc_get_prop,
+ .set_property = smb2_dc_set_prop,
+ .property_is_writeable = smb2_dc_prop_is_writeable,
+};
+
+static int smb2_init_dc_psy(struct smb2 *chip)
+{
+ struct power_supply_config dc_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+
+ dc_cfg.drv_data = chip;
+ dc_cfg.of_node = chg->dev->of_node;
+ chg->dc_psy = power_supply_register(chg->dev,
+ &dc_psy_desc,
+ &dc_cfg);
+ if (IS_ERR(chg->dc_psy)) {
+ pr_err("Couldn't register USB power supply\n");
+ return PTR_ERR(chg->dc_psy);
+ }
+
+ return 0;
+}
+
+/*************************
+ * BATT PSY REGISTRATION *
+ *************************/
+
+static enum power_supply_property smb2_batt_props[] = {
+ POWER_SUPPLY_PROP_INPUT_SUSPEND,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
+ POWER_SUPPLY_PROP_CHARGER_TEMP,
+ POWER_SUPPLY_PROP_CHARGER_TEMP_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_QNOVO,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_QNOVO,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_SW_JEITA_ENABLED,
+ POWER_SUPPLY_PROP_CHARGE_DONE,
+ POWER_SUPPLY_PROP_PARALLEL_DISABLE,
+ POWER_SUPPLY_PROP_SET_SHIP_MODE,
+ POWER_SUPPLY_PROP_DIE_HEALTH,
+ POWER_SUPPLY_PROP_RERUN_AICL,
+ POWER_SUPPLY_PROP_DP_DM,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+};
+
+static int smb2_batt_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb_charger *chg = power_supply_get_drvdata(psy);
+ int rc = 0;
+ union power_supply_propval pval = {0, };
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ rc = smblib_get_prop_batt_status(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ rc = smblib_get_prop_batt_health(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ rc = smblib_get_prop_batt_present(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smblib_get_prop_input_suspend(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ rc = smblib_get_prop_batt_charge_type(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ rc = smblib_get_prop_batt_capacity(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+ rc = smblib_get_prop_system_temp_level(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGER_TEMP:
+ /* do not query RRADC if charger is not present */
+ rc = smblib_get_prop_usb_present(chg, &pval);
+ if (rc < 0)
+ pr_err("Couldn't get usb present rc=%d\n", rc);
+
+ rc = -ENODATA;
+ if (pval.intval)
+ rc = smblib_get_prop_charger_temp(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX:
+ rc = smblib_get_prop_charger_temp_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ rc = smblib_get_prop_input_current_limited(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED:
+ val->intval = chg->step_chg_enabled;
+ break;
+ case POWER_SUPPLY_PROP_SW_JEITA_ENABLED:
+ val->intval = chg->sw_jeita_enabled;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ rc = smblib_get_prop_batt_voltage_now(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = get_client_vote(chg->fv_votable,
+ BATT_PROFILE_VOTER);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE:
+ rc = smblib_get_prop_charge_qnovo_enable(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_QNOVO:
+ val->intval = get_client_vote_locked(chg->fv_votable,
+ QNOVO_VOTER);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ rc = smblib_get_prop_batt_current_now(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_QNOVO:
+ val->intval = get_client_vote_locked(chg->fcc_votable,
+ QNOVO_VOTER);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = get_client_vote(chg->fcc_votable,
+ BATT_PROFILE_VOTER);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ rc = smblib_get_prop_batt_temp(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_DONE:
+ rc = smblib_get_prop_batt_charge_done(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PARALLEL_DISABLE:
+ val->intval = get_client_vote(chg->pl_disable_votable,
+ USER_VOTER);
+ break;
+ case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+ /* Not in ship mode as long as device is active */
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_DIE_HEALTH:
+ rc = smblib_get_prop_die_health(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_DP_DM:
+ val->intval = chg->pulse_cnt;
+ break;
+ case POWER_SUPPLY_PROP_RERUN_AICL:
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ rc = smblib_get_prop_batt_charge_counter(chg, val);
+ break;
+ default:
+ pr_err("batt power supply prop %d not supported\n", psp);
+ return -EINVAL;
+ }
+
+ if (rc < 0) {
+ pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+ return -ENODATA;
+ }
+
+ return 0;
+}
+
+static int smb2_batt_set_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+ struct smb_charger *chg = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smblib_set_prop_input_suspend(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+ rc = smblib_set_prop_system_temp_level(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ rc = smblib_set_prop_batt_capacity(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PARALLEL_DISABLE:
+ vote(chg->pl_disable_votable, USER_VOTER, (bool)val->intval, 0);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ chg->batt_profile_fv_uv = val->intval;
+ vote(chg->fv_votable, BATT_PROFILE_VOTER, true, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE:
+ rc = smblib_set_prop_charge_qnovo_enable(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_QNOVO:
+ if (val->intval == -EINVAL) {
+ vote(chg->fv_votable, BATT_PROFILE_VOTER,
+ true, chg->batt_profile_fv_uv);
+ vote(chg->fv_votable, QNOVO_VOTER, false, 0);
+ } else {
+ vote(chg->fv_votable, QNOVO_VOTER, true, val->intval);
+ vote(chg->fv_votable, BATT_PROFILE_VOTER, false, 0);
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_QNOVO:
+ vote(chg->pl_disable_votable, PL_QNOVO_VOTER,
+ val->intval != -EINVAL && val->intval < 2000000, 0);
+ if (val->intval == -EINVAL) {
+ vote(chg->fcc_votable, BATT_PROFILE_VOTER,
+ true, chg->batt_profile_fcc_ua);
+ vote(chg->fcc_votable, QNOVO_VOTER, false, 0);
+ } else {
+ vote(chg->fcc_votable, QNOVO_VOTER, true, val->intval);
+ vote(chg->fcc_votable, BATT_PROFILE_VOTER, false, 0);
+ }
+ break;
+ case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED:
+ chg->step_chg_enabled = !!val->intval;
+ break;
+ case POWER_SUPPLY_PROP_SW_JEITA_ENABLED:
+ if (chg->sw_jeita_enabled != (!!val->intval)) {
+ rc = smblib_disable_hw_jeita(chg, !!val->intval);
+ if (rc == 0)
+ chg->sw_jeita_enabled = !!val->intval;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ chg->batt_profile_fcc_ua = val->intval;
+ vote(chg->fcc_votable, BATT_PROFILE_VOTER, true, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+ /* Not in ship mode as long as the device is active */
+ if (!val->intval)
+ break;
+ if (chg->pl.psy)
+ power_supply_set_property(chg->pl.psy,
+ POWER_SUPPLY_PROP_SET_SHIP_MODE, val);
+ rc = smblib_set_prop_ship_mode(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_RERUN_AICL:
+ rc = smblib_rerun_aicl(chg);
+ break;
+ case POWER_SUPPLY_PROP_DP_DM:
+ rc = smblib_dp_dm(chg, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ rc = smblib_set_prop_input_current_limited(chg, val);
+ break;
+ default:
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb2_batt_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ case POWER_SUPPLY_PROP_PARALLEL_DISABLE:
+ case POWER_SUPPLY_PROP_DP_DM:
+ case POWER_SUPPLY_PROP_RERUN_AICL:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_SW_JEITA_ENABLED:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct power_supply_desc batt_psy_desc = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = smb2_batt_props,
+ .num_properties = ARRAY_SIZE(smb2_batt_props),
+ .get_property = smb2_batt_get_prop,
+ .set_property = smb2_batt_set_prop,
+ .property_is_writeable = smb2_batt_prop_is_writeable,
+};
+
+static int smb2_init_batt_psy(struct smb2 *chip)
+{
+ struct power_supply_config batt_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ batt_cfg.drv_data = chg;
+ batt_cfg.of_node = chg->dev->of_node;
+ chg->batt_psy = power_supply_register(chg->dev,
+ &batt_psy_desc,
+ &batt_cfg);
+ if (IS_ERR(chg->batt_psy)) {
+ pr_err("Couldn't register battery power supply\n");
+ return PTR_ERR(chg->batt_psy);
+ }
+
+ return rc;
+}
+
+/******************************
+ * VBUS REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb2_vbus_reg_ops = {
+ .enable = smblib_vbus_regulator_enable,
+ .disable = smblib_vbus_regulator_disable,
+ .is_enabled = smblib_vbus_regulator_is_enabled,
+};
+
+static int smb2_init_vbus_regulator(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct regulator_config cfg = {};
+ int rc = 0;
+
+ chg->vbus_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vbus_vreg),
+ GFP_KERNEL);
+ if (!chg->vbus_vreg)
+ return -ENOMEM;
+
+ cfg.dev = chg->dev;
+ cfg.driver_data = chip;
+
+ chg->vbus_vreg->rdesc.owner = THIS_MODULE;
+ chg->vbus_vreg->rdesc.type = REGULATOR_VOLTAGE;
+ chg->vbus_vreg->rdesc.ops = &smb2_vbus_reg_ops;
+ chg->vbus_vreg->rdesc.of_match = "qcom,smb2-vbus";
+ chg->vbus_vreg->rdesc.name = "qcom,smb2-vbus";
+
+ chg->vbus_vreg->rdev = devm_regulator_register(chg->dev,
+ &chg->vbus_vreg->rdesc, &cfg);
+ if (IS_ERR(chg->vbus_vreg->rdev)) {
+ rc = PTR_ERR(chg->vbus_vreg->rdev);
+ chg->vbus_vreg->rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't register VBUS regualtor rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+/******************************
+ * VCONN REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb2_vconn_reg_ops = {
+ .enable = smblib_vconn_regulator_enable,
+ .disable = smblib_vconn_regulator_disable,
+ .is_enabled = smblib_vconn_regulator_is_enabled,
+};
+
+static int smb2_init_vconn_regulator(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct regulator_config cfg = {};
+ int rc = 0;
+
+ if (chg->micro_usb_mode)
+ return 0;
+
+ chg->vconn_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vconn_vreg),
+ GFP_KERNEL);
+ if (!chg->vconn_vreg)
+ return -ENOMEM;
+
+ cfg.dev = chg->dev;
+ cfg.driver_data = chip;
+
+ chg->vconn_vreg->rdesc.owner = THIS_MODULE;
+ chg->vconn_vreg->rdesc.type = REGULATOR_VOLTAGE;
+ chg->vconn_vreg->rdesc.ops = &smb2_vconn_reg_ops;
+ chg->vconn_vreg->rdesc.of_match = "qcom,smb2-vconn";
+ chg->vconn_vreg->rdesc.name = "qcom,smb2-vconn";
+
+ chg->vconn_vreg->rdev = devm_regulator_register(chg->dev,
+ &chg->vconn_vreg->rdesc, &cfg);
+ if (IS_ERR(chg->vconn_vreg->rdev)) {
+ rc = PTR_ERR(chg->vconn_vreg->rdev);
+ chg->vconn_vreg->rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't register VCONN regualtor rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+/***************************
+ * HARDWARE INITIALIZATION *
+ ***************************/
+static int smb2_config_wipower_input_power(struct smb2 *chip, int uw)
+{
+ int rc;
+ int ua;
+ struct smb_charger *chg = &chip->chg;
+ s64 nw = (s64)uw * 1000;
+
+ if (uw < 0)
+ return 0;
+
+ ua = div_s64(nw, ZIN_ICL_PT_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_pt_lv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_pt_lv rc = %d\n", rc);
+ return rc;
+ }
+
+ ua = div_s64(nw, ZIN_ICL_PT_HV_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_pt_hv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_pt_hv rc = %d\n", rc);
+ return rc;
+ }
+
+ ua = div_s64(nw, ZIN_ICL_LV_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_lv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_div2_lv rc = %d\n", rc);
+ return rc;
+ }
+
+ ua = div_s64(nw, ZIN_ICL_MID_LV_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_mid_lv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_div2_mid_lv rc = %d\n", rc);
+ return rc;
+ }
+
+ ua = div_s64(nw, ZIN_ICL_MID_HV_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_mid_hv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_div2_mid_hv rc = %d\n", rc);
+ return rc;
+ }
+
+ ua = div_s64(nw, ZIN_ICL_HV_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_hv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_div2_hv rc = %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int smb2_configure_typec(struct smb_charger *chg)
+{
+ int rc;
+
+ /*
+ * trigger the usb-typec-change interrupt only when the CC state
+ * changes
+ */
+ rc = smblib_write(chg, TYPE_C_INTRPT_ENB_REG,
+ TYPEC_CCSTATE_CHANGE_INT_EN_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure Type-C interrupts rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * disable Type-C factory mode and stay in Attached.SRC state when VCONN
+ * over-current happens
+ */
+ rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
+ FACTORY_MODE_DETECTION_EN_BIT | VCONN_OC_CFG_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure Type-C rc=%d\n", rc);
+ return rc;
+ }
+
+ /* increase VCONN softstart */
+ rc = smblib_masked_write(chg, TYPE_C_CFG_2_REG,
+ VCONN_SOFTSTART_CFG_MASK, VCONN_SOFTSTART_CFG_MASK);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't increase VCONN softstart rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* disable try.SINK mode and legacy cable IRQs */
+ rc = smblib_masked_write(chg, TYPE_C_CFG_3_REG, EN_TRYSINK_MODE_BIT |
+ TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN_BIT |
+ TYPEC_LEGACY_CABLE_INT_EN_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't set Type-C config rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smb2_disable_typec(struct smb_charger *chg)
+{
+ int rc;
+
+ /* Move to typeC mode */
+ /* configure FSM in idle state and disable UFP_ENABLE bit */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_DISABLE_CMD_BIT | UFP_EN_CMD_BIT,
+ TYPEC_DISABLE_CMD_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't put FSM in idle rc=%d\n", rc);
+ return rc;
+ }
+
+ /* wait for FSM to enter idle state */
+ msleep(200);
+ /* configure TypeC mode */
+ rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
+ TYPE_C_OR_U_USB_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't enable micro USB mode rc=%d\n", rc);
+ return rc;
+ }
+
+ /* wait for mode change before enabling FSM */
+ usleep_range(10000, 11000);
+ /* release FSM from idle state */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_DISABLE_CMD_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't release FSM rc=%d\n", rc);
+ return rc;
+ }
+
+ /* wait for FSM to start */
+ msleep(100);
+ /* move to uUSB mode */
+ /* configure FSM in idle state */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_DISABLE_CMD_BIT, TYPEC_DISABLE_CMD_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't put FSM in idle rc=%d\n", rc);
+ return rc;
+ }
+
+ /* wait for FSM to enter idle state */
+ msleep(200);
+ /* configure micro USB mode */
+ rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
+ TYPE_C_OR_U_USB_BIT, TYPE_C_OR_U_USB_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't enable micro USB mode rc=%d\n", rc);
+ return rc;
+ }
+
+ /* wait for mode change before enabling FSM */
+ usleep_range(10000, 11000);
+ /* release FSM from idle state */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_DISABLE_CMD_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't release FSM rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smb2_init_hw(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc;
+ u8 stat, val;
+
+ if (chip->dt.no_battery)
+ chg->fake_capacity = 50;
+
+ if (chg->batt_profile_fcc_ua < 0)
+ smblib_get_charge_param(chg, &chg->param.fcc,
+ &chg->batt_profile_fcc_ua);
+
+ if (chg->batt_profile_fv_uv < 0)
+ smblib_get_charge_param(chg, &chg->param.fv,
+ &chg->batt_profile_fv_uv);
+
+ smblib_get_charge_param(chg, &chg->param.usb_icl,
+ &chg->default_icl_ua);
+ if (chip->dt.usb_icl_ua < 0)
+ chip->dt.usb_icl_ua = chg->default_icl_ua;
+
+ if (chip->dt.dc_icl_ua < 0)
+ smblib_get_charge_param(chg, &chg->param.dc_icl,
+ &chip->dt.dc_icl_ua);
+
+ if (chip->dt.min_freq_khz > 0) {
+ chg->param.freq_buck.min_u = chip->dt.min_freq_khz;
+ chg->param.freq_boost.min_u = chip->dt.min_freq_khz;
+ }
+
+ if (chip->dt.max_freq_khz > 0) {
+ chg->param.freq_buck.max_u = chip->dt.max_freq_khz;
+ chg->param.freq_boost.max_u = chip->dt.max_freq_khz;
+ }
+
+ /* set a slower soft start setting for OTG */
+ rc = smblib_masked_write(chg, DC_ENG_SSUPPLY_CFG2_REG,
+ ENG_SSUPPLY_IVREF_OTG_SS_MASK, OTG_SS_SLOW);
+ if (rc < 0) {
+ pr_err("Couldn't set otg soft start rc=%d\n", rc);
+ return rc;
+ }
+
+ /* set OTG current limit */
+ rc = smblib_set_charge_param(chg, &chg->param.otg_cl,
+ (chg->wa_flags & OTG_WA) ?
+ chg->param.otg_cl.min_u : chg->otg_cl_ua);
+ if (rc < 0) {
+ pr_err("Couldn't set otg current limit rc=%d\n", rc);
+ return rc;
+ }
+
+ chg->boost_threshold_ua = chip->dt.boost_threshold_ua;
+
+ rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat);
+ if (rc < 0) {
+ pr_err("Couldn't read APSD_RESULT_STATUS rc=%d\n", rc);
+ return rc;
+ }
+
+ smblib_rerun_apsd_if_required(chg);
+
+ /* clear the ICL override if it is set */
+ if (smblib_icl_override(chg, false) < 0) {
+ pr_err("Couldn't disable ICL override rc=%d\n", rc);
+ return rc;
+ }
+
+ /* votes must be cast before configuring software control */
+ /* vote 0mA on usb_icl for non battery platforms */
+ vote(chg->usb_icl_votable,
+ DEFAULT_VOTER, chip->dt.no_battery, 0);
+ vote(chg->dc_suspend_votable,
+ DEFAULT_VOTER, chip->dt.no_battery, 0);
+ vote(chg->fcc_votable,
+ BATT_PROFILE_VOTER, true, chg->batt_profile_fcc_ua);
+ vote(chg->fv_votable,
+ BATT_PROFILE_VOTER, true, chg->batt_profile_fv_uv);
+ vote(chg->dc_icl_votable,
+ DEFAULT_VOTER, true, chip->dt.dc_icl_ua);
+ vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER,
+ true, 0);
+ vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER,
+ true, 0);
+ vote(chg->hvdcp_disable_votable_indirect, DEFAULT_VOTER,
+ chip->dt.hvdcp_disable, 0);
+ vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER,
+ true, 0);
+ vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+ true, 0);
+ vote(chg->pd_disallowed_votable_indirect, MICRO_USB_VOTER,
+ chg->micro_usb_mode, 0);
+ vote(chg->hvdcp_enable_votable, MICRO_USB_VOTER,
+ chg->micro_usb_mode, 0);
+
+ /*
+ * AICL configuration:
+ * start from min and AICL ADC disable
+ */
+ rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG,
+ USBIN_AICL_START_AT_MAX_BIT
+ | USBIN_AICL_ADC_EN_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure AICL rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Configure charge enable for software control; active high */
+ rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+ CHG_EN_POLARITY_BIT |
+ CHG_EN_SRC_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure charger rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable the charging path */
+ rc = vote(chg->chg_disable_votable, DEFAULT_VOTER, false, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't enable charging rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chg->micro_usb_mode)
+ rc = smb2_disable_typec(chg);
+ else
+ rc = smb2_configure_typec(chg);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure Type-C interrupts rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure VCONN for software control */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ VCONN_EN_SRC_BIT | VCONN_EN_VALUE_BIT,
+ VCONN_EN_SRC_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure VCONN for SW control rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure VBUS for software control */
+ rc = smblib_masked_write(chg, OTG_CFG_REG, OTG_EN_SRC_CFG_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure VBUS for SW control rc=%d\n", rc);
+ return rc;
+ }
+
+ val = (ilog2(chip->dt.wd_bark_time / 16) << BARK_WDOG_TIMEOUT_SHIFT) &
+ BARK_WDOG_TIMEOUT_MASK;
+ val |= BITE_WDOG_TIMEOUT_8S;
+ rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG,
+ BITE_WDOG_DISABLE_CHARGING_CFG_BIT |
+ BARK_WDOG_TIMEOUT_MASK | BITE_WDOG_TIMEOUT_MASK,
+ val);
+ if (rc) {
+ pr_err("Couldn't configue WD config rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable WD BARK and enable it on plugin */
+ rc = smblib_masked_write(chg, WD_CFG_REG,
+ WATCHDOG_TRIGGER_AFP_EN_BIT |
+ WDOG_TIMER_EN_ON_PLUGIN_BIT |
+ BARK_WDOG_INT_EN_BIT,
+ WDOG_TIMER_EN_ON_PLUGIN_BIT |
+ BARK_WDOG_INT_EN_BIT);
+ if (rc) {
+ pr_err("Couldn't configue WD config rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure wipower watts */
+ rc = smb2_config_wipower_input_power(chip, chip->dt.wipower_max_uw);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure wipower rc=%d\n", rc);
+ return rc;
+ }
+
+ /* disable SW STAT override */
+ rc = smblib_masked_write(chg, STAT_CFG_REG,
+ STAT_SW_OVERRIDE_CFG_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't disable SW STAT override rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* disable h/w autonomous parallel charging control */
+ rc = smblib_masked_write(chg, MISC_CFG_REG,
+ STAT_PARALLEL_1400MA_EN_CFG_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't disable h/w autonomous parallel control rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* configure float charger options */
+ switch (chip->dt.float_option) {
+ case 1:
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+ FLOAT_OPTIONS_MASK, 0);
+ break;
+ case 2:
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+ FLOAT_OPTIONS_MASK, FORCE_FLOAT_SDP_CFG_BIT);
+ break;
+ case 3:
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+ FLOAT_OPTIONS_MASK, FLOAT_DIS_CHGING_CFG_BIT);
+ break;
+ case 4:
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+ FLOAT_OPTIONS_MASK, SUSPEND_FLOAT_CFG_BIT);
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure float charger options rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smblib_read(chg, USBIN_OPTIONS_2_CFG_REG, &chg->float_cfg);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't read float charger options rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ switch (chip->dt.chg_inhibit_thr_mv) {
+ case 50:
+ rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+ CHARGE_INHIBIT_THRESHOLD_MASK,
+ CHARGE_INHIBIT_THRESHOLD_50MV);
+ break;
+ case 100:
+ rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+ CHARGE_INHIBIT_THRESHOLD_MASK,
+ CHARGE_INHIBIT_THRESHOLD_100MV);
+ break;
+ case 200:
+ rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+ CHARGE_INHIBIT_THRESHOLD_MASK,
+ CHARGE_INHIBIT_THRESHOLD_200MV);
+ break;
+ case 300:
+ rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+ CHARGE_INHIBIT_THRESHOLD_MASK,
+ CHARGE_INHIBIT_THRESHOLD_300MV);
+ break;
+ case 0:
+ rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+ CHARGER_INHIBIT_BIT, 0);
+ default:
+ break;
+ }
+
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure charge inhibit threshold rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (chip->dt.auto_recharge_soc) {
+ rc = smblib_masked_write(chg, FG_UPDATE_CFG_2_SEL_REG,
+ SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
+ VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
+ VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure FG_UPDATE_CFG2_SEL_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+ } else {
+ rc = smblib_masked_write(chg, FG_UPDATE_CFG_2_SEL_REG,
+ SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
+ VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
+ SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure FG_UPDATE_CFG2_SEL_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (chg->sw_jeita_enabled) {
+ rc = smblib_disable_hw_jeita(chg, true);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't set hw jeita rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int smb2_post_init(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc;
+
+ /* In case the usb path is suspended, we would have missed disabling
+ * the icl change interrupt because the interrupt could have been
+ * not requested
+ */
+ rerun_election(chg->usb_icl_votable);
+
+ /* configure power role for dual-role */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_POWER_ROLE_CMD_MASK, 0);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure power role for DRP rc=%d\n", rc);
+ return rc;
+ }
+
+ rerun_election(chg->usb_irq_enable_votable);
+
+ return 0;
+}
+
+static int smb2_chg_config_init(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct pmic_revid_data *pmic_rev_id;
+ struct device_node *revid_dev_node;
+
+ revid_dev_node = of_parse_phandle(chip->chg.dev->of_node,
+ "qcom,pmic-revid", 0);
+ if (!revid_dev_node) {
+ pr_err("Missing qcom,pmic-revid property\n");
+ return -EINVAL;
+ }
+
+ pmic_rev_id = get_revid_data(revid_dev_node);
+ if (IS_ERR_OR_NULL(pmic_rev_id)) {
+ /*
+ * the revid peripheral must be registered, any failure
+ * here only indicates that the rev-id module has not
+ * probed yet.
+ */
+ return -EPROBE_DEFER;
+ }
+
+ switch (pmic_rev_id->pmic_subtype) {
+ case PMI8998_SUBTYPE:
+ chip->chg.smb_version = PMI8998_SUBTYPE;
+ chip->chg.wa_flags |= BOOST_BACK_WA | QC_AUTH_INTERRUPT_WA_BIT;
+ if (pmic_rev_id->rev4 == PMI8998_V1P1_REV4) /* PMI rev 1.1 */
+ chg->wa_flags |= QC_CHARGER_DETECTION_WA_BIT;
+ if (pmic_rev_id->rev4 == PMI8998_V2P0_REV4) /* PMI rev 2.0 */
+ chg->wa_flags |= TYPEC_CC2_REMOVAL_WA_BIT;
+ chg->chg_freq.freq_5V = 600;
+ chg->chg_freq.freq_6V_8V = 800;
+ chg->chg_freq.freq_9V = 1000;
+ chg->chg_freq.freq_12V = 1200;
+ chg->chg_freq.freq_removal = 1000;
+ chg->chg_freq.freq_below_otg_threshold = 2000;
+ chg->chg_freq.freq_above_otg_threshold = 800;
+ break;
+ case PM660_SUBTYPE:
+ chip->chg.smb_version = PM660_SUBTYPE;
+ chip->chg.wa_flags |= BOOST_BACK_WA | OTG_WA;
+ chg->param.freq_buck = pm660_params.freq_buck;
+ chg->param.freq_boost = pm660_params.freq_boost;
+ chg->chg_freq.freq_5V = 650;
+ chg->chg_freq.freq_6V_8V = 850;
+ chg->chg_freq.freq_9V = 1050;
+ chg->chg_freq.freq_12V = 1200;
+ chg->chg_freq.freq_removal = 1050;
+ chg->chg_freq.freq_below_otg_threshold = 1600;
+ chg->chg_freq.freq_above_otg_threshold = 800;
+ break;
+ default:
+ pr_err("PMIC subtype %d not supported\n",
+ pmic_rev_id->pmic_subtype);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/****************************
+ * DETERMINE INITIAL STATUS *
+ ****************************/
+
+static int smb2_determine_initial_status(struct smb2 *chip)
+{
+ struct smb_irq_data irq_data = {chip, "determine-initial-status"};
+ struct smb_charger *chg = &chip->chg;
+
+ if (chg->bms_psy)
+ smblib_suspend_on_debug_battery(chg);
+ smblib_handle_usb_plugin(0, &irq_data);
+ smblib_handle_usb_typec_change(0, &irq_data);
+ smblib_handle_usb_source_change(0, &irq_data);
+ smblib_handle_chg_state_change(0, &irq_data);
+ smblib_handle_icl_change(0, &irq_data);
+ smblib_handle_batt_temp_changed(0, &irq_data);
+ smblib_handle_wdog_bark(0, &irq_data);
+
+ return 0;
+}
+
+/**************************
+ * INTERRUPT REGISTRATION *
+ **************************/
+
+static struct smb_irq_info smb2_irqs[] = {
+/* CHARGER IRQs */
+ [CHG_ERROR_IRQ] = {
+ .name = "chg-error",
+ .handler = smblib_handle_debug,
+ },
+ [CHG_STATE_CHANGE_IRQ] = {
+ .name = "chg-state-change",
+ .handler = smblib_handle_chg_state_change,
+ .wake = true,
+ },
+ [STEP_CHG_STATE_CHANGE_IRQ] = {
+ .name = "step-chg-state-change",
+ .handler = NULL,
+ },
+ [STEP_CHG_SOC_UPDATE_FAIL_IRQ] = {
+ .name = "step-chg-soc-update-fail",
+ .handler = NULL,
+ },
+ [STEP_CHG_SOC_UPDATE_REQ_IRQ] = {
+ .name = "step-chg-soc-update-request",
+ .handler = NULL,
+ },
+/* OTG IRQs */
+ [OTG_FAIL_IRQ] = {
+ .name = "otg-fail",
+ .handler = smblib_handle_debug,
+ },
+ [OTG_OVERCURRENT_IRQ] = {
+ .name = "otg-overcurrent",
+ .handler = smblib_handle_otg_overcurrent,
+ },
+ [OTG_OC_DIS_SW_STS_IRQ] = {
+ .name = "otg-oc-dis-sw-sts",
+ .handler = smblib_handle_debug,
+ },
+ [TESTMODE_CHANGE_DET_IRQ] = {
+ .name = "testmode-change-detect",
+ .handler = smblib_handle_debug,
+ },
+/* BATTERY IRQs */
+ [BATT_TEMP_IRQ] = {
+ .name = "bat-temp",
+ .handler = smblib_handle_batt_temp_changed,
+ .wake = true,
+ },
+ [BATT_OCP_IRQ] = {
+ .name = "bat-ocp",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ [BATT_OV_IRQ] = {
+ .name = "bat-ov",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ [BATT_LOW_IRQ] = {
+ .name = "bat-low",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ [BATT_THERM_ID_MISS_IRQ] = {
+ .name = "bat-therm-or-id-missing",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ [BATT_TERM_MISS_IRQ] = {
+ .name = "bat-terminal-missing",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+/* USB INPUT IRQs */
+ [USBIN_COLLAPSE_IRQ] = {
+ .name = "usbin-collapse",
+ .handler = smblib_handle_debug,
+ },
+ [USBIN_LT_3P6V_IRQ] = {
+ .name = "usbin-lt-3p6v",
+ .handler = smblib_handle_debug,
+ },
+ [USBIN_UV_IRQ] = {
+ .name = "usbin-uv",
+ .handler = smblib_handle_usbin_uv,
+ },
+ [USBIN_OV_IRQ] = {
+ .name = "usbin-ov",
+ .handler = smblib_handle_debug,
+ },
+ [USBIN_PLUGIN_IRQ] = {
+ .name = "usbin-plugin",
+ .handler = smblib_handle_usb_plugin,
+ .wake = true,
+ },
+ [USBIN_SRC_CHANGE_IRQ] = {
+ .name = "usbin-src-change",
+ .handler = smblib_handle_usb_source_change,
+ .wake = true,
+ },
+ [USBIN_ICL_CHANGE_IRQ] = {
+ .name = "usbin-icl-change",
+ .handler = smblib_handle_icl_change,
+ .wake = true,
+ },
+ [TYPE_C_CHANGE_IRQ] = {
+ .name = "type-c-change",
+ .handler = smblib_handle_usb_typec_change,
+ .wake = true,
+ },
+/* DC INPUT IRQs */
+ [DCIN_COLLAPSE_IRQ] = {
+ .name = "dcin-collapse",
+ .handler = smblib_handle_debug,
+ },
+ [DCIN_LT_3P6V_IRQ] = {
+ .name = "dcin-lt-3p6v",
+ .handler = smblib_handle_debug,
+ },
+ [DCIN_UV_IRQ] = {
+ .name = "dcin-uv",
+ .handler = smblib_handle_debug,
+ },
+ [DCIN_OV_IRQ] = {
+ .name = "dcin-ov",
+ .handler = smblib_handle_debug,
+ },
+ [DCIN_PLUGIN_IRQ] = {
+ .name = "dcin-plugin",
+ .handler = smblib_handle_dc_plugin,
+ .wake = true,
+ },
+ [DIV2_EN_DG_IRQ] = {
+ .name = "div2-en-dg",
+ .handler = smblib_handle_debug,
+ },
+ [DCIN_ICL_CHANGE_IRQ] = {
+ .name = "dcin-icl-change",
+ .handler = smblib_handle_debug,
+ },
+/* MISCELLANEOUS IRQs */
+ [WDOG_SNARL_IRQ] = {
+ .name = "wdog-snarl",
+ .handler = NULL,
+ },
+ [WDOG_BARK_IRQ] = {
+ .name = "wdog-bark",
+ .handler = smblib_handle_wdog_bark,
+ .wake = true,
+ },
+ [AICL_FAIL_IRQ] = {
+ .name = "aicl-fail",
+ .handler = smblib_handle_debug,
+ },
+ [AICL_DONE_IRQ] = {
+ .name = "aicl-done",
+ .handler = smblib_handle_debug,
+ },
+ [HIGH_DUTY_CYCLE_IRQ] = {
+ .name = "high-duty-cycle",
+ .handler = smblib_handle_high_duty_cycle,
+ .wake = true,
+ },
+ [INPUT_CURRENT_LIMIT_IRQ] = {
+ .name = "input-current-limiting",
+ .handler = smblib_handle_debug,
+ },
+ [TEMPERATURE_CHANGE_IRQ] = {
+ .name = "temperature-change",
+ .handler = smblib_handle_debug,
+ },
+ [SWITCH_POWER_OK_IRQ] = {
+ .name = "switcher-power-ok",
+ .handler = smblib_handle_switcher_power_ok,
+ .storm_data = {true, 1000, 8},
+ },
+};
+
+static int smb2_get_irq_index_byname(const char *irq_name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(smb2_irqs); i++) {
+ if (strcmp(smb2_irqs[i].name, irq_name) == 0)
+ return i;
+ }
+
+ return -ENOENT;
+}
+
+static int smb2_request_interrupt(struct smb2 *chip,
+ struct device_node *node, const char *irq_name)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc, irq, irq_index;
+ struct smb_irq_data *irq_data;
+
+ irq = of_irq_get_byname(node, irq_name);
+ if (irq < 0) {
+ pr_err("Couldn't get irq %s byname\n", irq_name);
+ return irq;
+ }
+
+ irq_index = smb2_get_irq_index_byname(irq_name);
+ if (irq_index < 0) {
+ pr_err("%s is not a defined irq\n", irq_name);
+ return irq_index;
+ }
+
+ if (!smb2_irqs[irq_index].handler)
+ return 0;
+
+ irq_data = devm_kzalloc(chg->dev, sizeof(*irq_data), GFP_KERNEL);
+ if (!irq_data)
+ return -ENOMEM;
+
+ irq_data->parent_data = chip;
+ irq_data->name = irq_name;
+ irq_data->storm_data = smb2_irqs[irq_index].storm_data;
+ mutex_init(&irq_data->storm_data.storm_lock);
+
+ rc = devm_request_threaded_irq(chg->dev, irq, NULL,
+ smb2_irqs[irq_index].handler,
+ IRQF_ONESHOT, irq_name, irq_data);
+ if (rc < 0) {
+ pr_err("Couldn't request irq %d\n", irq);
+ return rc;
+ }
+
+ smb2_irqs[irq_index].irq = irq;
+ smb2_irqs[irq_index].irq_data = irq_data;
+ if (smb2_irqs[irq_index].wake)
+ enable_irq_wake(irq);
+
+ return rc;
+}
+
+static int smb2_request_interrupts(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct device_node *node = chg->dev->of_node;
+ struct device_node *child;
+ int rc = 0;
+ const char *name;
+ struct property *prop;
+
+ for_each_available_child_of_node(node, child) {
+ of_property_for_each_string(child, "interrupt-names",
+ prop, name) {
+ rc = smb2_request_interrupt(chip, child, name);
+ if (rc < 0)
+ return rc;
+ }
+ }
+ if (chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq)
+ chg->usb_icl_change_irq_enabled = true;
+
+ return rc;
+}
+
+static void smb2_free_interrupts(struct smb_charger *chg)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(smb2_irqs); i++) {
+ if (smb2_irqs[i].irq > 0) {
+ if (smb2_irqs[i].wake)
+ disable_irq_wake(smb2_irqs[i].irq);
+
+ devm_free_irq(chg->dev, smb2_irqs[i].irq,
+ smb2_irqs[i].irq_data);
+ }
+ }
+}
+
+static void smb2_disable_interrupts(struct smb_charger *chg)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(smb2_irqs); i++) {
+ if (smb2_irqs[i].irq > 0)
+ disable_irq(smb2_irqs[i].irq);
+ }
+}
+
+#if defined(CONFIG_DEBUG_FS)
+
+static int force_batt_psy_update_write(void *data, u64 val)
+{
+ struct smb_charger *chg = data;
+
+ power_supply_changed(chg->batt_psy);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_batt_psy_update_ops, NULL,
+ force_batt_psy_update_write, "0x%02llx\n");
+
+static int force_usb_psy_update_write(void *data, u64 val)
+{
+ struct smb_charger *chg = data;
+
+ power_supply_changed(chg->usb_psy);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_usb_psy_update_ops, NULL,
+ force_usb_psy_update_write, "0x%02llx\n");
+
+static int force_dc_psy_update_write(void *data, u64 val)
+{
+ struct smb_charger *chg = data;
+
+ power_supply_changed(chg->dc_psy);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_dc_psy_update_ops, NULL,
+ force_dc_psy_update_write, "0x%02llx\n");
+
+static void smb2_create_debugfs(struct smb2 *chip)
+{
+ struct dentry *file;
+
+ chip->dfs_root = debugfs_create_dir("charger", NULL);
+ if (IS_ERR_OR_NULL(chip->dfs_root)) {
+ pr_err("Couldn't create charger debugfs rc=%ld\n",
+ (long)chip->dfs_root);
+ return;
+ }
+
+ file = debugfs_create_file("force_batt_psy_update", S_IRUSR | S_IWUSR,
+ chip->dfs_root, chip, &force_batt_psy_update_ops);
+ if (IS_ERR_OR_NULL(file))
+ pr_err("Couldn't create force_batt_psy_update file rc=%ld\n",
+ (long)file);
+
+ file = debugfs_create_file("force_usb_psy_update", S_IRUSR | S_IWUSR,
+ chip->dfs_root, chip, &force_usb_psy_update_ops);
+ if (IS_ERR_OR_NULL(file))
+ pr_err("Couldn't create force_usb_psy_update file rc=%ld\n",
+ (long)file);
+
+ file = debugfs_create_file("force_dc_psy_update", S_IRUSR | S_IWUSR,
+ chip->dfs_root, chip, &force_dc_psy_update_ops);
+ if (IS_ERR_OR_NULL(file))
+ pr_err("Couldn't create force_dc_psy_update file rc=%ld\n",
+ (long)file);
+}
+
+#else
+
+static void smb2_create_debugfs(struct smb2 *chip)
+{}
+
+#endif
+
+static int smb2_probe(struct platform_device *pdev)
+{
+ struct smb2 *chip;
+ struct smb_charger *chg;
+ int rc = 0;
+ union power_supply_propval val;
+ int usb_present, batt_present, batt_health, batt_charge_type;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chg = &chip->chg;
+ chg->dev = &pdev->dev;
+ chg->param = v1_params;
+ chg->debug_mask = &__debug_mask;
+ chg->weak_chg_icl_ua = &__weak_chg_icl_ua;
+ chg->mode = PARALLEL_MASTER;
+ chg->irq_info = smb2_irqs;
+ chg->name = "PMI";
+
+ chg->regmap = dev_get_regmap(chg->dev->parent, NULL);
+ if (!chg->regmap) {
+ pr_err("parent regmap is missing\n");
+ return -EINVAL;
+ }
+
+ rc = smb2_chg_config_init(chip);
+ if (rc < 0) {
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't setup chg_config rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb2_parse_dt(chip);
+ if (rc < 0) {
+ pr_err("Couldn't parse device tree rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smblib_init(chg);
+ if (rc < 0) {
+ pr_err("Smblib_init failed rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ /* set driver data before resources request it */
+ platform_set_drvdata(pdev, chip);
+
+ rc = smb2_init_vbus_regulator(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize vbus regulator rc=%d\n",
+ rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_vconn_regulator(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize vconn regulator rc=%d\n",
+ rc);
+ goto cleanup;
+ }
+
+ /* extcon registration */
+ chg->extcon = devm_extcon_dev_allocate(chg->dev, smblib_extcon_cable);
+ if (IS_ERR(chg->extcon)) {
+ rc = PTR_ERR(chg->extcon);
+ dev_err(chg->dev, "failed to allocate extcon device rc=%d\n",
+ rc);
+ goto cleanup;
+ }
+
+ rc = devm_extcon_dev_register(chg->dev, chg->extcon);
+ if (rc < 0) {
+ dev_err(chg->dev, "failed to register extcon device rc=%d\n",
+ rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_hw(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize hardware rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_dc_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize dc psy rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_usb_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize usb psy rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_usb_main_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize usb main psy rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_usb_port_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize usb pc_port psy rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_batt_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize batt psy rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_determine_initial_status(chip);
+ if (rc < 0) {
+ pr_err("Couldn't determine initial status rc=%d\n",
+ rc);
+ goto cleanup;
+ }
+
+ rc = smb2_request_interrupts(chip);
+ if (rc < 0) {
+ pr_err("Couldn't request interrupts rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_post_init(chip);
+ if (rc < 0) {
+ pr_err("Failed in post init rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ smb2_create_debugfs(chip);
+
+ rc = smblib_get_prop_usb_present(chg, &val);
+ if (rc < 0) {
+ pr_err("Couldn't get usb present rc=%d\n", rc);
+ goto cleanup;
+ }
+ usb_present = val.intval;
+
+ rc = smblib_get_prop_batt_present(chg, &val);
+ if (rc < 0) {
+ pr_err("Couldn't get batt present rc=%d\n", rc);
+ goto cleanup;
+ }
+ batt_present = val.intval;
+
+ rc = smblib_get_prop_batt_health(chg, &val);
+ if (rc < 0) {
+ pr_err("Couldn't get batt health rc=%d\n", rc);
+ val.intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+ batt_health = val.intval;
+
+ rc = smblib_get_prop_batt_charge_type(chg, &val);
+ if (rc < 0) {
+ pr_err("Couldn't get batt charge type rc=%d\n", rc);
+ goto cleanup;
+ }
+ batt_charge_type = val.intval;
+
+ device_init_wakeup(chg->dev, true);
+
+ pr_info("QPNP SMB2 probed successfully usb:present=%d type=%d batt:present = %d health = %d charge = %d\n",
+ usb_present, chg->real_charger_type,
+ batt_present, batt_health, batt_charge_type);
+ return rc;
+
+cleanup:
+ smb2_free_interrupts(chg);
+ if (chg->batt_psy)
+ power_supply_unregister(chg->batt_psy);
+ if (chg->usb_main_psy)
+ power_supply_unregister(chg->usb_main_psy);
+ if (chg->usb_psy)
+ power_supply_unregister(chg->usb_psy);
+ if (chg->usb_port_psy)
+ power_supply_unregister(chg->usb_port_psy);
+ if (chg->dc_psy)
+ power_supply_unregister(chg->dc_psy);
+ if (chg->vconn_vreg && chg->vconn_vreg->rdev)
+ devm_regulator_unregister(chg->dev, chg->vconn_vreg->rdev);
+ if (chg->vbus_vreg && chg->vbus_vreg->rdev)
+ devm_regulator_unregister(chg->dev, chg->vbus_vreg->rdev);
+
+ smblib_deinit(chg);
+
+ platform_set_drvdata(pdev, NULL);
+ return rc;
+}
+
+static int smb2_remove(struct platform_device *pdev)
+{
+ struct smb2 *chip = platform_get_drvdata(pdev);
+ struct smb_charger *chg = &chip->chg;
+
+ power_supply_unregister(chg->batt_psy);
+ power_supply_unregister(chg->usb_psy);
+ power_supply_unregister(chg->usb_port_psy);
+ regulator_unregister(chg->vconn_vreg->rdev);
+ regulator_unregister(chg->vbus_vreg->rdev);
+
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static void smb2_shutdown(struct platform_device *pdev)
+{
+ struct smb2 *chip = platform_get_drvdata(pdev);
+ struct smb_charger *chg = &chip->chg;
+
+ /* disable all interrupts */
+ smb2_disable_interrupts(chg);
+
+ /* configure power role for UFP */
+ smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_POWER_ROLE_CMD_MASK, UFP_EN_CMD_BIT);
+
+ /* force HVDCP to 5V */
+ smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+ HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT, 0);
+ smblib_write(chg, CMD_HVDCP_2_REG, FORCE_5V_BIT);
+
+ /* force enable APSD */
+ smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+ AUTO_SRC_DETECT_BIT, AUTO_SRC_DETECT_BIT);
+}
+
+static const struct of_device_id match_table[] = {
+ { .compatible = "qcom,qpnp-smb2", },
+ { },
+};
+
+static struct platform_driver smb2_driver = {
+ .driver = {
+ .name = "qcom,qpnp-smb2",
+ .owner = THIS_MODULE,
+ .of_match_table = match_table,
+ },
+ .probe = smb2_probe,
+ .remove = smb2_remove,
+ .shutdown = smb2_shutdown,
+};
+module_platform_driver(smb2_driver);
+
+MODULE_DESCRIPTION("QPNP SMB2 Charger Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/qcom/smb-lib.c b/drivers/power/supply/qcom/smb-lib.c
new file mode 100644
index 000000000000..f9d35ea7775b
--- /dev/null
+++ b/drivers/power/supply/qcom/smb-lib.c
@@ -0,0 +1,4856 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/iio/consumer.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/driver.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/input/qpnp-power-on.h>
+#include <linux/irq.h>
+#include <linux/pmic-voter.h>
+#include "smb-lib.h"
+#include "smb-reg.h"
+#include "battery.h"
+#include "step-chg-jeita.h"
+#include "storm-watch.h"
+
+#define smblib_err(chg, fmt, ...) \
+ pr_err("%s: %s: " fmt, chg->name, \
+ __func__, ##__VA_ARGS__) \
+
+#define smblib_dbg(chg, reason, fmt, ...) \
+ do { \
+ if (*chg->debug_mask & (reason)) \
+ pr_info("%s: %s: " fmt, chg->name, \
+ __func__, ##__VA_ARGS__); \
+ else \
+ pr_debug("%s: %s: " fmt, chg->name, \
+ __func__, ##__VA_ARGS__); \
+ } while (0)
+
+static bool is_secure(struct smb_charger *chg, int addr)
+{
+ if (addr == SHIP_MODE_REG || addr == FREQ_CLK_DIV_REG)
+ return true;
+ /* assume everything above 0xA0 is secure */
+ return (bool)((addr & 0xFF) >= 0xA0);
+}
+
+int smblib_read(struct smb_charger *chg, u16 addr, u8 *val)
+{
+ unsigned int temp;
+ int rc = 0;
+
+ rc = regmap_read(chg->regmap, addr, &temp);
+ if (rc >= 0)
+ *val = (u8)temp;
+
+ return rc;
+}
+
+int smblib_multibyte_read(struct smb_charger *chg, u16 addr, u8 *val,
+ int count)
+{
+ return regmap_bulk_read(chg->regmap, addr, val, count);
+}
+
+int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val)
+{
+ int rc = 0;
+
+ mutex_lock(&chg->write_lock);
+ if (is_secure(chg, addr)) {
+ rc = regmap_write(chg->regmap, (addr & 0xFF00) | 0xD0, 0xA5);
+ if (rc < 0)
+ goto unlock;
+ }
+
+ rc = regmap_update_bits(chg->regmap, addr, mask, val);
+
+unlock:
+ mutex_unlock(&chg->write_lock);
+ return rc;
+}
+
+int smblib_write(struct smb_charger *chg, u16 addr, u8 val)
+{
+ int rc = 0;
+
+ mutex_lock(&chg->write_lock);
+
+ if (is_secure(chg, addr)) {
+ rc = regmap_write(chg->regmap, (addr & ~(0xFF)) | 0xD0, 0xA5);
+ if (rc < 0)
+ goto unlock;
+ }
+
+ rc = regmap_write(chg->regmap, addr, val);
+
+unlock:
+ mutex_unlock(&chg->write_lock);
+ return rc;
+}
+
+static int smblib_get_jeita_cc_delta(struct smb_charger *chg, int *cc_delta_ua)
+{
+ int rc, cc_minus_ua;
+ u8 stat;
+
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (!(stat & BAT_TEMP_STATUS_SOFT_LIMIT_MASK)) {
+ *cc_delta_ua = 0;
+ return 0;
+ }
+
+ rc = smblib_get_charge_param(chg, &chg->param.jeita_cc_comp,
+ &cc_minus_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n", rc);
+ return rc;
+ }
+
+ *cc_delta_ua = -cc_minus_ua;
+ return 0;
+}
+
+int smblib_icl_override(struct smb_charger *chg, bool override)
+{
+ int rc;
+
+ rc = smblib_masked_write(chg, USBIN_LOAD_CFG_REG,
+ ICL_OVERRIDE_AFTER_APSD_BIT,
+ override ? ICL_OVERRIDE_AFTER_APSD_BIT : 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't override ICL rc=%d\n", rc);
+
+ return rc;
+}
+
+/********************
+ * REGISTER GETTERS *
+ ********************/
+
+int smblib_get_charge_param(struct smb_charger *chg,
+ struct smb_chg_param *param, int *val_u)
+{
+ int rc = 0;
+ u8 val_raw;
+
+ rc = smblib_read(chg, param->reg, &val_raw);
+ if (rc < 0) {
+ smblib_err(chg, "%s: Couldn't read from 0x%04x rc=%d\n",
+ param->name, param->reg, rc);
+ return rc;
+ }
+
+ if (param->get_proc)
+ *val_u = param->get_proc(param, val_raw);
+ else
+ *val_u = val_raw * param->step_u + param->min_u;
+ smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n",
+ param->name, *val_u, val_raw);
+
+ return rc;
+}
+
+int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend)
+{
+ int rc = 0;
+ u8 temp;
+
+ rc = smblib_read(chg, USBIN_CMD_IL_REG, &temp);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read USBIN_CMD_IL rc=%d\n", rc);
+ return rc;
+ }
+ *suspend = temp & USBIN_SUSPEND_BIT;
+
+ return rc;
+}
+
+struct apsd_result {
+ const char * const name;
+ const u8 bit;
+ const enum power_supply_type pst;
+};
+
+enum {
+ UNKNOWN,
+ SDP,
+ CDP,
+ DCP,
+ OCP,
+ FLOAT,
+ HVDCP2,
+ HVDCP3,
+ MAX_TYPES
+};
+
+static const struct apsd_result const smblib_apsd_results[] = {
+ [UNKNOWN] = {
+ .name = "UNKNOWN",
+ .bit = 0,
+ .pst = POWER_SUPPLY_TYPE_UNKNOWN
+ },
+ [SDP] = {
+ .name = "SDP",
+ .bit = SDP_CHARGER_BIT,
+ .pst = POWER_SUPPLY_TYPE_USB
+ },
+ [CDP] = {
+ .name = "CDP",
+ .bit = CDP_CHARGER_BIT,
+ .pst = POWER_SUPPLY_TYPE_USB_CDP
+ },
+ [DCP] = {
+ .name = "DCP",
+ .bit = DCP_CHARGER_BIT,
+ .pst = POWER_SUPPLY_TYPE_USB_DCP
+ },
+ [OCP] = {
+ .name = "OCP",
+ .bit = OCP_CHARGER_BIT,
+ .pst = POWER_SUPPLY_TYPE_USB_DCP
+ },
+ [FLOAT] = {
+ .name = "FLOAT",
+ .bit = FLOAT_CHARGER_BIT,
+ .pst = POWER_SUPPLY_TYPE_USB_FLOAT
+ },
+ [HVDCP2] = {
+ .name = "HVDCP2",
+ .bit = DCP_CHARGER_BIT | QC_2P0_BIT,
+ .pst = POWER_SUPPLY_TYPE_USB_HVDCP
+ },
+ [HVDCP3] = {
+ .name = "HVDCP3",
+ .bit = DCP_CHARGER_BIT | QC_3P0_BIT,
+ .pst = POWER_SUPPLY_TYPE_USB_HVDCP_3,
+ },
+};
+
+static const struct apsd_result *smblib_get_apsd_result(struct smb_charger *chg)
+{
+ int rc, i;
+ u8 apsd_stat, stat;
+ const struct apsd_result *result = &smblib_apsd_results[UNKNOWN];
+
+ rc = smblib_read(chg, APSD_STATUS_REG, &apsd_stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc);
+ return result;
+ }
+ smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", apsd_stat);
+
+ if (!(apsd_stat & APSD_DTC_STATUS_DONE_BIT))
+ return result;
+
+ rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read APSD_RESULT_STATUS rc=%d\n",
+ rc);
+ return result;
+ }
+ stat &= APSD_RESULT_STATUS_MASK;
+
+ for (i = 0; i < ARRAY_SIZE(smblib_apsd_results); i++) {
+ if (smblib_apsd_results[i].bit == stat)
+ result = &smblib_apsd_results[i];
+ }
+
+ if (apsd_stat & QC_CHARGER_BIT) {
+ /* since its a qc_charger, either return HVDCP3 or HVDCP2 */
+ if (result != &smblib_apsd_results[HVDCP3])
+ result = &smblib_apsd_results[HVDCP2];
+ }
+
+ return result;
+}
+
+/********************
+ * REGISTER SETTERS *
+ ********************/
+
+static int chg_freq_list[] = {
+ 9600, 9600, 6400, 4800, 3800, 3200, 2700, 2400, 2100, 1900, 1700,
+ 1600, 1500, 1400, 1300, 1200,
+};
+
+int smblib_set_chg_freq(struct smb_chg_param *param,
+ int val_u, u8 *val_raw)
+{
+ u8 i;
+
+ if (val_u > param->max_u || val_u < param->min_u)
+ return -EINVAL;
+
+ /* Charger FSW is the configured freqency / 2 */
+ val_u *= 2;
+ for (i = 0; i < ARRAY_SIZE(chg_freq_list); i++) {
+ if (chg_freq_list[i] == val_u)
+ break;
+ }
+ if (i == ARRAY_SIZE(chg_freq_list)) {
+ pr_err("Invalid frequency %d Hz\n", val_u / 2);
+ return -EINVAL;
+ }
+
+ *val_raw = i;
+
+ return 0;
+}
+
+static int smblib_set_opt_freq_buck(struct smb_charger *chg, int fsw_khz)
+{
+ union power_supply_propval pval = {0, };
+ int rc = 0;
+
+ rc = smblib_set_charge_param(chg, &chg->param.freq_buck, fsw_khz);
+ if (rc < 0)
+ dev_err(chg->dev, "Error in setting freq_buck rc=%d\n", rc);
+
+ if (chg->mode == PARALLEL_MASTER && chg->pl.psy) {
+ pval.intval = fsw_khz;
+ /*
+ * Some parallel charging implementations may not have
+ * PROP_BUCK_FREQ property - they could be running
+ * with a fixed frequency
+ */
+ power_supply_set_property(chg->pl.psy,
+ POWER_SUPPLY_PROP_BUCK_FREQ, &pval);
+ }
+
+ return rc;
+}
+
+int smblib_set_charge_param(struct smb_charger *chg,
+ struct smb_chg_param *param, int val_u)
+{
+ int rc = 0;
+ u8 val_raw;
+
+ if (param->set_proc) {
+ rc = param->set_proc(param, val_u, &val_raw);
+ if (rc < 0)
+ return -EINVAL;
+ } else {
+ if (val_u > param->max_u || val_u < param->min_u) {
+ smblib_err(chg, "%s: %d is out of range [%d, %d]\n",
+ param->name, val_u, param->min_u, param->max_u);
+ return -EINVAL;
+ }
+
+ val_raw = (val_u - param->min_u) / param->step_u;
+ }
+
+ rc = smblib_write(chg, param->reg, val_raw);
+ if (rc < 0) {
+ smblib_err(chg, "%s: Couldn't write 0x%02x to 0x%04x rc=%d\n",
+ param->name, val_raw, param->reg, rc);
+ return rc;
+ }
+
+ smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n",
+ param->name, val_u, val_raw);
+
+ return rc;
+}
+
+int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend)
+{
+ int rc = 0;
+ int irq = chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq;
+
+ if (suspend && irq) {
+ if (chg->usb_icl_change_irq_enabled) {
+ disable_irq_nosync(irq);
+ chg->usb_icl_change_irq_enabled = false;
+ }
+ }
+
+ rc = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT,
+ suspend ? USBIN_SUSPEND_BIT : 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't write %s to USBIN_SUSPEND_BIT rc=%d\n",
+ suspend ? "suspend" : "resume", rc);
+
+ if (!suspend && irq) {
+ if (!chg->usb_icl_change_irq_enabled) {
+ enable_irq(irq);
+ chg->usb_icl_change_irq_enabled = true;
+ }
+ }
+
+ return rc;
+}
+
+int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend)
+{
+ int rc = 0;
+
+ rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_SUSPEND_BIT,
+ suspend ? DCIN_SUSPEND_BIT : 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't write %s to DCIN_SUSPEND_BIT rc=%d\n",
+ suspend ? "suspend" : "resume", rc);
+
+ return rc;
+}
+
+static int smblib_set_adapter_allowance(struct smb_charger *chg,
+ u8 allowed_voltage)
+{
+ int rc = 0;
+
+ /* PM660 only support max. 9V */
+ if (chg->smb_version == PM660_SUBTYPE) {
+ switch (allowed_voltage) {
+ case USBIN_ADAPTER_ALLOW_12V:
+ case USBIN_ADAPTER_ALLOW_9V_TO_12V:
+ allowed_voltage = USBIN_ADAPTER_ALLOW_9V;
+ break;
+ case USBIN_ADAPTER_ALLOW_5V_OR_12V:
+ case USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V:
+ allowed_voltage = USBIN_ADAPTER_ALLOW_5V_OR_9V;
+ break;
+ case USBIN_ADAPTER_ALLOW_5V_TO_12V:
+ allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_9V;
+ break;
+ }
+ }
+
+ rc = smblib_write(chg, USBIN_ADAPTER_ALLOW_CFG_REG, allowed_voltage);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't write 0x%02x to USBIN_ADAPTER_ALLOW_CFG rc=%d\n",
+ allowed_voltage, rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+#define MICRO_5V 5000000
+#define MICRO_9V 9000000
+#define MICRO_12V 12000000
+static int smblib_set_usb_pd_allowed_voltage(struct smb_charger *chg,
+ int min_allowed_uv, int max_allowed_uv)
+{
+ int rc;
+ u8 allowed_voltage;
+
+ if (min_allowed_uv == MICRO_5V && max_allowed_uv == MICRO_5V) {
+ allowed_voltage = USBIN_ADAPTER_ALLOW_5V;
+ smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_5V);
+ } else if (min_allowed_uv == MICRO_9V && max_allowed_uv == MICRO_9V) {
+ allowed_voltage = USBIN_ADAPTER_ALLOW_9V;
+ smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_9V);
+ } else if (min_allowed_uv == MICRO_12V && max_allowed_uv == MICRO_12V) {
+ allowed_voltage = USBIN_ADAPTER_ALLOW_12V;
+ smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_12V);
+ } else if (min_allowed_uv < MICRO_9V && max_allowed_uv <= MICRO_9V) {
+ allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_9V;
+ } else if (min_allowed_uv < MICRO_9V && max_allowed_uv <= MICRO_12V) {
+ allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_12V;
+ } else if (min_allowed_uv < MICRO_12V && max_allowed_uv <= MICRO_12V) {
+ allowed_voltage = USBIN_ADAPTER_ALLOW_9V_TO_12V;
+ } else {
+ smblib_err(chg, "invalid allowed voltage [%d, %d]\n",
+ min_allowed_uv, max_allowed_uv);
+ return -EINVAL;
+ }
+
+ rc = smblib_set_adapter_allowance(chg, allowed_voltage);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't configure adapter allowance rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+/********************
+ * HELPER FUNCTIONS *
+ ********************/
+static int smblib_request_dpdm(struct smb_charger *chg, bool enable)
+{
+ int rc = 0;
+
+ /* fetch the DPDM regulator */
+ if (!chg->dpdm_reg && of_get_property(chg->dev->of_node,
+ "dpdm-supply", NULL)) {
+ chg->dpdm_reg = devm_regulator_get(chg->dev, "dpdm");
+ if (IS_ERR(chg->dpdm_reg)) {
+ rc = PTR_ERR(chg->dpdm_reg);
+ smblib_err(chg, "Couldn't get dpdm regulator rc=%d\n",
+ rc);
+ chg->dpdm_reg = NULL;
+ return rc;
+ }
+ }
+
+ if (enable) {
+ if (chg->dpdm_reg && !regulator_is_enabled(chg->dpdm_reg)) {
+ smblib_dbg(chg, PR_MISC, "enabling DPDM regulator\n");
+ rc = regulator_enable(chg->dpdm_reg);
+ if (rc < 0)
+ smblib_err(chg,
+ "Couldn't enable dpdm regulator rc=%d\n",
+ rc);
+ }
+ } else {
+ if (chg->dpdm_reg && regulator_is_enabled(chg->dpdm_reg)) {
+ smblib_dbg(chg, PR_MISC, "disabling DPDM regulator\n");
+ rc = regulator_disable(chg->dpdm_reg);
+ if (rc < 0)
+ smblib_err(chg,
+ "Couldn't disable dpdm regulator rc=%d\n",
+ rc);
+ }
+ }
+
+ return rc;
+}
+
+static void smblib_rerun_apsd(struct smb_charger *chg)
+{
+ int rc;
+
+ smblib_dbg(chg, PR_MISC, "re-running APSD\n");
+ if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
+ rc = smblib_masked_write(chg,
+ USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
+ AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't enable HVDCP auth IRQ rc=%d\n",
+ rc);
+ }
+
+ rc = smblib_masked_write(chg, CMD_APSD_REG,
+ APSD_RERUN_BIT, APSD_RERUN_BIT);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't re-run APSD rc=%d\n", rc);
+}
+
+static const struct apsd_result *smblib_update_usb_type(struct smb_charger *chg)
+{
+ const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
+
+ /* if PD is active, APSD is disabled so won't have a valid result */
+ if (chg->pd_active) {
+ chg->real_charger_type = POWER_SUPPLY_TYPE_USB_PD;
+ } else {
+ /*
+ * Update real charger type only if its not FLOAT
+ * detected as as SDP
+ */
+ if (!(apsd_result->pst == POWER_SUPPLY_TYPE_USB_FLOAT &&
+ chg->real_charger_type == POWER_SUPPLY_TYPE_USB))
+ chg->real_charger_type = apsd_result->pst;
+ }
+
+ smblib_dbg(chg, PR_MISC, "APSD=%s PD=%d\n",
+ apsd_result->name, chg->pd_active);
+ return apsd_result;
+}
+
+static int smblib_notifier_call(struct notifier_block *nb,
+ unsigned long ev, void *v)
+{
+ struct power_supply *psy = v;
+ struct smb_charger *chg = container_of(nb, struct smb_charger, nb);
+
+ if (!strcmp(psy->desc->name, "bms")) {
+ if (!chg->bms_psy)
+ chg->bms_psy = psy;
+ if (ev == PSY_EVENT_PROP_CHANGED)
+ schedule_work(&chg->bms_update_work);
+ }
+
+ if (!chg->pl.psy && !strcmp(psy->desc->name, "parallel"))
+ chg->pl.psy = psy;
+
+ return NOTIFY_OK;
+}
+
+static int smblib_register_notifier(struct smb_charger *chg)
+{
+ int rc;
+
+ chg->nb.notifier_call = smblib_notifier_call;
+ rc = power_supply_reg_notifier(&chg->nb);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't register psy notifier rc = %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+int smblib_mapping_soc_from_field_value(struct smb_chg_param *param,
+ int val_u, u8 *val_raw)
+{
+ if (val_u > param->max_u || val_u < param->min_u)
+ return -EINVAL;
+
+ *val_raw = val_u << 1;
+
+ return 0;
+}
+
+int smblib_mapping_cc_delta_to_field_value(struct smb_chg_param *param,
+ u8 val_raw)
+{
+ int val_u = val_raw * param->step_u + param->min_u;
+
+ if (val_u > param->max_u)
+ val_u -= param->max_u * 2;
+
+ return val_u;
+}
+
+int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param,
+ int val_u, u8 *val_raw)
+{
+ if (val_u > param->max_u || val_u < param->min_u - param->max_u)
+ return -EINVAL;
+
+ val_u += param->max_u * 2 - param->min_u;
+ val_u %= param->max_u * 2;
+ *val_raw = val_u / param->step_u;
+
+ return 0;
+}
+
+static void smblib_uusb_removal(struct smb_charger *chg)
+{
+ int rc;
+ struct smb_irq_data *data;
+ struct storm_watch *wdata;
+
+ cancel_delayed_work_sync(&chg->pl_enable_work);
+
+ rc = smblib_request_dpdm(chg, false);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't to disable DPDM rc=%d\n", rc);
+
+ if (chg->wa_flags & BOOST_BACK_WA) {
+ data = chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data;
+ if (data) {
+ wdata = &data->storm_data;
+ update_storm_count(wdata, WEAK_CHG_STORM_COUNT);
+ vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0);
+ vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER,
+ false, 0);
+ }
+ }
+ vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0);
+ vote(chg->awake_votable, PL_DELAY_VOTER, false, 0);
+
+ /* reset both usbin current and voltage votes */
+ vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0);
+ vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0);
+ vote(chg->usb_icl_votable, SW_QC3_VOTER, false, 0);
+
+ cancel_delayed_work_sync(&chg->hvdcp_detect_work);
+
+ if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
+ /* re-enable AUTH_IRQ_EN_CFG_BIT */
+ rc = smblib_masked_write(chg,
+ USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
+ AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT);
+ if (rc < 0)
+ smblib_err(chg,
+ "Couldn't enable QC auth setting rc=%d\n", rc);
+ }
+
+ /* reconfigure allowed voltage for HVDCP */
+ rc = smblib_set_adapter_allowance(chg,
+ USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n",
+ rc);
+
+ chg->voltage_min_uv = MICRO_5V;
+ chg->voltage_max_uv = MICRO_5V;
+ chg->usb_icl_delta_ua = 0;
+ chg->pulse_cnt = 0;
+ chg->uusb_apsd_rerun_done = false;
+
+ /* clear USB ICL vote for USB_PSY_VOTER */
+ rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't un-vote for USB ICL rc=%d\n", rc);
+
+ /* clear USB ICL vote for DCP_VOTER */
+ rc = vote(chg->usb_icl_votable, DCP_VOTER, false, 0);
+ if (rc < 0)
+ smblib_err(chg,
+ "Couldn't un-vote DCP from USB ICL rc=%d\n", rc);
+}
+
+void smblib_suspend_on_debug_battery(struct smb_charger *chg)
+{
+ int rc;
+ union power_supply_propval val;
+
+ if (!chg->suspend_input_on_debug_batt)
+ return;
+
+ rc = power_supply_get_property(chg->bms_psy,
+ POWER_SUPPLY_PROP_DEBUG_BATTERY, &val);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get debug battery prop rc=%d\n", rc);
+ return;
+ }
+
+ vote(chg->usb_icl_votable, DEBUG_BOARD_VOTER, val.intval, 0);
+ vote(chg->dc_suspend_votable, DEBUG_BOARD_VOTER, val.intval, 0);
+ if (val.intval)
+ pr_info("Input suspended: Fake battery\n");
+}
+
+int smblib_rerun_apsd_if_required(struct smb_charger *chg)
+{
+ union power_supply_propval val;
+ int rc;
+
+ rc = smblib_get_prop_usb_present(chg, &val);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get usb present rc = %d\n", rc);
+ return rc;
+ }
+
+ if (!val.intval)
+ return 0;
+
+ rc = smblib_request_dpdm(chg, true);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc);
+
+ chg->uusb_apsd_rerun_done = true;
+ smblib_rerun_apsd(chg);
+
+ return 0;
+}
+
+static int smblib_get_hw_pulse_cnt(struct smb_charger *chg, int *count)
+{
+ int rc;
+ u8 val[2];
+
+ switch (chg->smb_version) {
+ case PMI8998_SUBTYPE:
+ rc = smblib_read(chg, QC_PULSE_COUNT_STATUS_REG, val);
+ if (rc) {
+ pr_err("failed to read QC_PULSE_COUNT_STATUS_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+ *count = val[0] & QC_PULSE_COUNT_MASK;
+ break;
+ case PM660_SUBTYPE:
+ rc = smblib_multibyte_read(chg,
+ QC_PULSE_COUNT_STATUS_1_REG, val, 2);
+ if (rc) {
+ pr_err("failed to read QC_PULSE_COUNT_STATUS_1_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+ *count = (val[1] << 8) | val[0];
+ break;
+ default:
+ smblib_dbg(chg, PR_PARALLEL, "unknown SMB chip %d\n",
+ chg->smb_version);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int smblib_get_pulse_cnt(struct smb_charger *chg, int *count)
+{
+ int rc;
+
+ /* Use software based pulse count if HW INOV is disabled */
+ if (get_effective_result(chg->hvdcp_hw_inov_dis_votable) > 0) {
+ *count = chg->pulse_cnt;
+ return 0;
+ }
+
+ /* Use h/w pulse count if autonomous mode is enabled */
+ rc = smblib_get_hw_pulse_cnt(chg, count);
+ if (rc < 0)
+ smblib_err(chg, "failed to read h/w pulse count rc=%d\n", rc);
+
+ return rc;
+}
+
+#define USBIN_25MA 25000
+#define USBIN_100MA 100000
+#define USBIN_150MA 150000
+#define USBIN_500MA 500000
+#define USBIN_900MA 900000
+
+static int set_sdp_current(struct smb_charger *chg, int icl_ua)
+{
+ int rc;
+ u8 icl_options;
+ const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
+
+ /* power source is SDP */
+ switch (icl_ua) {
+ case USBIN_100MA:
+ /* USB 2.0 100mA */
+ icl_options = 0;
+ break;
+ case USBIN_150MA:
+ /* USB 3.0 150mA */
+ icl_options = CFG_USB3P0_SEL_BIT;
+ break;
+ case USBIN_500MA:
+ /* USB 2.0 500mA */
+ icl_options = USB51_MODE_BIT;
+ break;
+ case USBIN_900MA:
+ /* USB 3.0 900mA */
+ icl_options = CFG_USB3P0_SEL_BIT | USB51_MODE_BIT;
+ break;
+ default:
+ smblib_err(chg, "ICL %duA isn't supported for SDP\n", icl_ua);
+ return -EINVAL;
+ }
+
+ if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB &&
+ apsd_result->pst == POWER_SUPPLY_TYPE_USB_FLOAT) {
+ /*
+ * change the float charger configuration to SDP, if this
+ * is the case of SDP being detected as FLOAT
+ */
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+ FORCE_FLOAT_SDP_CFG_BIT, FORCE_FLOAT_SDP_CFG_BIT);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set float ICL options rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG,
+ CFG_USB3P0_SEL_BIT | USB51_MODE_BIT, icl_options);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set ICL options rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int get_sdp_current(struct smb_charger *chg, int *icl_ua)
+{
+ int rc;
+ u8 icl_options;
+ bool usb3 = false;
+
+ rc = smblib_read(chg, USBIN_ICL_OPTIONS_REG, &icl_options);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get ICL options rc=%d\n", rc);
+ return rc;
+ }
+
+ usb3 = (icl_options & CFG_USB3P0_SEL_BIT);
+
+ if (icl_options & USB51_MODE_BIT)
+ *icl_ua = usb3 ? USBIN_900MA : USBIN_500MA;
+ else
+ *icl_ua = usb3 ? USBIN_150MA : USBIN_100MA;
+
+ return rc;
+}
+
+int smblib_set_icl_current(struct smb_charger *chg, int icl_ua)
+{
+ int rc = 0;
+ bool override;
+
+ /* suspend and return if 25mA or less is requested */
+ if (icl_ua < USBIN_25MA)
+ return smblib_set_usb_suspend(chg, true);
+
+ if (icl_ua == INT_MAX)
+ goto override_suspend_config;
+
+ /* configure current */
+ if (chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT
+ && (chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) {
+ rc = set_sdp_current(chg, icl_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set SDP ICL rc=%d\n", rc);
+ goto enable_icl_changed_interrupt;
+ }
+ } else {
+ set_sdp_current(chg, 100000);
+ rc = smblib_set_charge_param(chg, &chg->param.usb_icl, icl_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set HC ICL rc=%d\n", rc);
+ goto enable_icl_changed_interrupt;
+ }
+ }
+
+override_suspend_config:
+ /* determine if override needs to be enforced */
+ override = true;
+ if (icl_ua == INT_MAX) {
+ /* remove override if no voters - hw defaults is desired */
+ override = false;
+ } else if (chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) {
+ if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB)
+ /* For std cable with type = SDP never override */
+ override = false;
+ else if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_CDP
+ && icl_ua == 1500000)
+ /*
+ * For std cable with type = CDP override only if
+ * current is not 1500mA
+ */
+ override = false;
+ }
+
+ /* enforce override */
+ rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG,
+ USBIN_MODE_CHG_BIT, override ? USBIN_MODE_CHG_BIT : 0);
+
+ rc = smblib_icl_override(chg, override);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set ICL override rc=%d\n", rc);
+ goto enable_icl_changed_interrupt;
+ }
+
+ /* unsuspend after configuring current and override */
+ rc = smblib_set_usb_suspend(chg, false);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't resume input rc=%d\n", rc);
+ goto enable_icl_changed_interrupt;
+ }
+
+enable_icl_changed_interrupt:
+ return rc;
+}
+
+int smblib_get_icl_current(struct smb_charger *chg, int *icl_ua)
+{
+ int rc = 0;
+ u8 load_cfg;
+ bool override;
+
+ if ((chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT
+ || chg->micro_usb_mode)
+ && (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB)) {
+ rc = get_sdp_current(chg, icl_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get SDP ICL rc=%d\n", rc);
+ return rc;
+ }
+ } else {
+ rc = smblib_read(chg, USBIN_LOAD_CFG_REG, &load_cfg);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get load cfg rc=%d\n", rc);
+ return rc;
+ }
+ override = load_cfg & ICL_OVERRIDE_AFTER_APSD_BIT;
+ if (!override)
+ return INT_MAX;
+
+ /* override is set */
+ rc = smblib_get_charge_param(chg, &chg->param.usb_icl, icl_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get HC ICL rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/*********************
+ * VOTABLE CALLBACKS *
+ *********************/
+
+static int smblib_dc_suspend_vote_callback(struct votable *votable, void *data,
+ int suspend, const char *client)
+{
+ struct smb_charger *chg = data;
+
+ /* resume input if suspend is invalid */
+ if (suspend < 0)
+ suspend = 0;
+
+ return smblib_set_dc_suspend(chg, (bool)suspend);
+}
+
+static int smblib_dc_icl_vote_callback(struct votable *votable, void *data,
+ int icl_ua, const char *client)
+{
+ struct smb_charger *chg = data;
+ int rc = 0;
+ bool suspend;
+
+ if (icl_ua < 0) {
+ smblib_dbg(chg, PR_MISC, "No Voter hence suspending\n");
+ icl_ua = 0;
+ }
+
+ suspend = (icl_ua < USBIN_25MA);
+ if (suspend)
+ goto suspend;
+
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set DC input current limit rc=%d\n",
+ rc);
+ return rc;
+ }
+
+suspend:
+ rc = vote(chg->dc_suspend_votable, USER_VOTER, suspend, 0);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't vote to %s DC rc=%d\n",
+ suspend ? "suspend" : "resume", rc);
+ return rc;
+ }
+ return rc;
+}
+
+static int smblib_pd_disallowed_votable_indirect_callback(
+ struct votable *votable, void *data, int disallowed, const char *client)
+{
+ struct smb_charger *chg = data;
+ int rc;
+
+ rc = vote(chg->pd_allowed_votable, PD_DISALLOWED_INDIRECT_VOTER,
+ !disallowed, 0);
+
+ return rc;
+}
+
+static int smblib_awake_vote_callback(struct votable *votable, void *data,
+ int awake, const char *client)
+{
+ struct smb_charger *chg = data;
+
+ if (awake)
+ pm_stay_awake(chg->dev);
+ else
+ pm_relax(chg->dev);
+
+ return 0;
+}
+
+static int smblib_chg_disable_vote_callback(struct votable *votable, void *data,
+ int chg_disable, const char *client)
+{
+ struct smb_charger *chg = data;
+ int rc;
+
+ rc = smblib_masked_write(chg, CHARGING_ENABLE_CMD_REG,
+ CHARGING_ENABLE_CMD_BIT,
+ chg_disable ? 0 : CHARGING_ENABLE_CMD_BIT);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't %s charging rc=%d\n",
+ chg_disable ? "disable" : "enable", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int smblib_hvdcp_enable_vote_callback(struct votable *votable,
+ void *data,
+ int hvdcp_enable, const char *client)
+{
+ struct smb_charger *chg = data;
+ int rc;
+ u8 val = HVDCP_AUTH_ALG_EN_CFG_BIT | HVDCP_EN_BIT;
+ u8 stat;
+
+ /* vote to enable/disable HW autonomous INOV */
+ vote(chg->hvdcp_hw_inov_dis_votable, client, !hvdcp_enable, 0);
+
+ /*
+ * Disable the autonomous bit and auth bit for disabling hvdcp.
+ * This ensures only qc 2.0 detection runs but no vbus
+ * negotiation happens.
+ */
+ if (!hvdcp_enable)
+ val = HVDCP_EN_BIT;
+
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+ HVDCP_EN_BIT | HVDCP_AUTH_ALG_EN_CFG_BIT,
+ val);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't %s hvdcp rc=%d\n",
+ hvdcp_enable ? "enable" : "disable", rc);
+ return rc;
+ }
+
+ rc = smblib_read(chg, APSD_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read APSD status rc=%d\n", rc);
+ return rc;
+ }
+
+ /* re-run APSD if HVDCP was detected */
+ if (stat & QC_CHARGER_BIT)
+ smblib_rerun_apsd(chg);
+
+ return 0;
+}
+
+static int smblib_hvdcp_disable_indirect_vote_callback(struct votable *votable,
+ void *data, int hvdcp_disable, const char *client)
+{
+ struct smb_charger *chg = data;
+
+ vote(chg->hvdcp_enable_votable, HVDCP_INDIRECT_VOTER,
+ !hvdcp_disable, 0);
+
+ return 0;
+}
+
+static int smblib_apsd_disable_vote_callback(struct votable *votable,
+ void *data,
+ int apsd_disable, const char *client)
+{
+ struct smb_charger *chg = data;
+ int rc;
+
+ if (apsd_disable) {
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+ AUTO_SRC_DETECT_BIT,
+ 0);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't disable APSD rc=%d\n", rc);
+ return rc;
+ }
+ } else {
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+ AUTO_SRC_DETECT_BIT,
+ AUTO_SRC_DETECT_BIT);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't enable APSD rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int smblib_hvdcp_hw_inov_dis_vote_callback(struct votable *votable,
+ void *data, int disable, const char *client)
+{
+ struct smb_charger *chg = data;
+ int rc;
+
+ if (disable) {
+ /*
+ * the pulse count register get zeroed when autonomous mode is
+ * disabled. Track that in variables before disabling
+ */
+ rc = smblib_get_hw_pulse_cnt(chg, &chg->pulse_cnt);
+ if (rc < 0) {
+ pr_err("failed to read QC_PULSE_COUNT_STATUS_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+ HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT,
+ disable ? 0 : HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't %s hvdcp rc=%d\n",
+ disable ? "disable" : "enable", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smblib_usb_irq_enable_vote_callback(struct votable *votable,
+ void *data, int enable, const char *client)
+{
+ struct smb_charger *chg = data;
+
+ if (!chg->irq_info[INPUT_CURRENT_LIMIT_IRQ].irq ||
+ !chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq)
+ return 0;
+
+ if (enable) {
+ enable_irq(chg->irq_info[INPUT_CURRENT_LIMIT_IRQ].irq);
+ enable_irq(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq);
+ } else {
+ disable_irq(chg->irq_info[INPUT_CURRENT_LIMIT_IRQ].irq);
+ disable_irq(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq);
+ }
+
+ return 0;
+}
+
+static int smblib_typec_irq_disable_vote_callback(struct votable *votable,
+ void *data, int disable, const char *client)
+{
+ struct smb_charger *chg = data;
+
+ if (!chg->irq_info[TYPE_C_CHANGE_IRQ].irq)
+ return 0;
+
+ if (disable)
+ disable_irq_nosync(chg->irq_info[TYPE_C_CHANGE_IRQ].irq);
+ else
+ enable_irq(chg->irq_info[TYPE_C_CHANGE_IRQ].irq);
+
+ return 0;
+}
+
+/*******************
+ * VCONN REGULATOR *
+ * *****************/
+
+#define MAX_OTG_SS_TRIES 2
+static int _smblib_vconn_regulator_enable(struct regulator_dev *rdev)
+{
+ struct smb_charger *chg = rdev_get_drvdata(rdev);
+ int rc = 0;
+ u8 val;
+
+ /*
+ * When enabling VCONN using the command register the CC pin must be
+ * selected. VCONN should be supplied to the inactive CC pin hence using
+ * the opposite of the CC_ORIENTATION_BIT.
+ */
+ smblib_dbg(chg, PR_OTG, "enabling VCONN\n");
+ val = chg->typec_status[3] &
+ CC_ORIENTATION_BIT ? 0 : VCONN_EN_ORIENTATION_BIT;
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ VCONN_EN_VALUE_BIT | VCONN_EN_ORIENTATION_BIT,
+ VCONN_EN_VALUE_BIT | val);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't enable vconn setting rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+int smblib_vconn_regulator_enable(struct regulator_dev *rdev)
+{
+ struct smb_charger *chg = rdev_get_drvdata(rdev);
+ int rc = 0;
+
+ mutex_lock(&chg->vconn_oc_lock);
+ if (chg->vconn_en)
+ goto unlock;
+
+ rc = _smblib_vconn_regulator_enable(rdev);
+ if (rc >= 0)
+ chg->vconn_en = true;
+
+unlock:
+ mutex_unlock(&chg->vconn_oc_lock);
+ return rc;
+}
+
+static int _smblib_vconn_regulator_disable(struct regulator_dev *rdev)
+{
+ struct smb_charger *chg = rdev_get_drvdata(rdev);
+ int rc = 0;
+
+ smblib_dbg(chg, PR_OTG, "disabling VCONN\n");
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ VCONN_EN_VALUE_BIT, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't disable vconn regulator rc=%d\n", rc);
+
+ return rc;
+}
+
+int smblib_vconn_regulator_disable(struct regulator_dev *rdev)
+{
+ struct smb_charger *chg = rdev_get_drvdata(rdev);
+ int rc = 0;
+
+ mutex_lock(&chg->vconn_oc_lock);
+ if (!chg->vconn_en)
+ goto unlock;
+
+ rc = _smblib_vconn_regulator_disable(rdev);
+ if (rc >= 0)
+ chg->vconn_en = false;
+
+unlock:
+ mutex_unlock(&chg->vconn_oc_lock);
+ return rc;
+}
+
+int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct smb_charger *chg = rdev_get_drvdata(rdev);
+ int ret;
+
+ mutex_lock(&chg->vconn_oc_lock);
+ ret = chg->vconn_en;
+ mutex_unlock(&chg->vconn_oc_lock);
+ return ret;
+}
+
+/*****************
+ * OTG REGULATOR *
+ *****************/
+#define MAX_RETRY 15
+#define MIN_DELAY_US 2000
+#define MAX_DELAY_US 9000
+static int otg_current[] = {250000, 500000, 1000000, 1500000};
+static int smblib_enable_otg_wa(struct smb_charger *chg)
+{
+ u8 stat;
+ int rc, i, retry_count = 0, min_delay = MIN_DELAY_US;
+
+ for (i = 0; i < ARRAY_SIZE(otg_current); i++) {
+ smblib_dbg(chg, PR_OTG, "enabling OTG with %duA\n",
+ otg_current[i]);
+ rc = smblib_set_charge_param(chg, &chg->param.otg_cl,
+ otg_current[i]);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set otg limit rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smblib_write(chg, CMD_OTG_REG, OTG_EN_BIT);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't enable OTG rc=%d\n", rc);
+ return rc;
+ }
+
+ retry_count = 0;
+ min_delay = MIN_DELAY_US;
+ do {
+ usleep_range(min_delay, min_delay + 100);
+ rc = smblib_read(chg, OTG_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read OTG status rc=%d\n",
+ rc);
+ goto out;
+ }
+
+ if (stat & BOOST_SOFTSTART_DONE_BIT) {
+ rc = smblib_set_charge_param(chg,
+ &chg->param.otg_cl, chg->otg_cl_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set otg limit rc=%d\n",
+ rc);
+ goto out;
+ }
+ break;
+ }
+ /* increase the delay for following iterations */
+ if (retry_count > 5)
+ min_delay = MAX_DELAY_US;
+
+ } while (retry_count++ < MAX_RETRY);
+
+ if (retry_count >= MAX_RETRY) {
+ smblib_dbg(chg, PR_OTG, "OTG enable failed with %duA\n",
+ otg_current[i]);
+ rc = smblib_write(chg, CMD_OTG_REG, 0);
+ if (rc < 0) {
+ smblib_err(chg, "disable OTG rc=%d\n", rc);
+ goto out;
+ }
+ } else {
+ smblib_dbg(chg, PR_OTG, "OTG enabled\n");
+ return 0;
+ }
+ }
+
+ if (i == ARRAY_SIZE(otg_current)) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ return 0;
+out:
+ smblib_write(chg, CMD_OTG_REG, 0);
+ return rc;
+}
+
+static int _smblib_vbus_regulator_enable(struct regulator_dev *rdev)
+{
+ struct smb_charger *chg = rdev_get_drvdata(rdev);
+ int rc;
+
+ smblib_dbg(chg, PR_OTG, "halt 1 in 8 mode\n");
+ rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG,
+ ENG_BUCKBOOST_HALT1_8_MODE_BIT,
+ ENG_BUCKBOOST_HALT1_8_MODE_BIT);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ smblib_dbg(chg, PR_OTG, "enabling OTG\n");
+
+ if (chg->wa_flags & OTG_WA) {
+ rc = smblib_enable_otg_wa(chg);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't enable OTG rc=%d\n", rc);
+ } else {
+ rc = smblib_write(chg, CMD_OTG_REG, OTG_EN_BIT);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't enable OTG rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+int smblib_vbus_regulator_enable(struct regulator_dev *rdev)
+{
+ struct smb_charger *chg = rdev_get_drvdata(rdev);
+ int rc = 0;
+
+ mutex_lock(&chg->otg_oc_lock);
+ if (chg->otg_en)
+ goto unlock;
+
+ if (!chg->usb_icl_votable) {
+ chg->usb_icl_votable = find_votable("USB_ICL");
+
+ if (!chg->usb_icl_votable)
+ return -EINVAL;
+ }
+ vote(chg->usb_icl_votable, USBIN_USBIN_BOOST_VOTER, true, 0);
+
+ rc = _smblib_vbus_regulator_enable(rdev);
+ if (rc >= 0)
+ chg->otg_en = true;
+
+unlock:
+ mutex_unlock(&chg->otg_oc_lock);
+ return rc;
+}
+
+static int _smblib_vbus_regulator_disable(struct regulator_dev *rdev)
+{
+ struct smb_charger *chg = rdev_get_drvdata(rdev);
+ int rc;
+
+ if (chg->wa_flags & OTG_WA) {
+ /* set OTG current limit to minimum value */
+ rc = smblib_set_charge_param(chg, &chg->param.otg_cl,
+ chg->param.otg_cl.min_u);
+ if (rc < 0) {
+ smblib_err(chg,
+ "Couldn't set otg current limit rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ smblib_dbg(chg, PR_OTG, "disabling OTG\n");
+ rc = smblib_write(chg, CMD_OTG_REG, 0);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc);
+ return rc;
+ }
+
+ smblib_dbg(chg, PR_OTG, "start 1 in 8 mode\n");
+ rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG,
+ ENG_BUCKBOOST_HALT1_8_MODE_BIT, 0);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+int smblib_vbus_regulator_disable(struct regulator_dev *rdev)
+{
+ struct smb_charger *chg = rdev_get_drvdata(rdev);
+ int rc = 0;
+
+ mutex_lock(&chg->otg_oc_lock);
+ if (!chg->otg_en)
+ goto unlock;
+
+ rc = _smblib_vbus_regulator_disable(rdev);
+ if (rc >= 0)
+ chg->otg_en = false;
+
+ if (chg->usb_icl_votable)
+ vote(chg->usb_icl_votable, USBIN_USBIN_BOOST_VOTER, false, 0);
+unlock:
+ mutex_unlock(&chg->otg_oc_lock);
+ return rc;
+}
+
+int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct smb_charger *chg = rdev_get_drvdata(rdev);
+ int ret;
+
+ mutex_lock(&chg->otg_oc_lock);
+ ret = chg->otg_en;
+ mutex_unlock(&chg->otg_oc_lock);
+ return ret;
+}
+
+/********************
+ * BATT PSY GETTERS *
+ ********************/
+
+int smblib_get_prop_input_suspend(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ val->intval
+ = (get_client_vote(chg->usb_icl_votable, USER_VOTER) == 0)
+ && get_client_vote(chg->dc_suspend_votable, USER_VOTER);
+ return 0;
+}
+
+int smblib_get_prop_batt_present(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+ u8 stat;
+
+ rc = smblib_read(chg, BATIF_BASE + INT_RT_STS_OFFSET, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read BATIF_INT_RT_STS rc=%d\n", rc);
+ return rc;
+ }
+
+ val->intval = !(stat & (BAT_THERM_OR_ID_MISSING_RT_STS_BIT
+ | BAT_TERMINAL_MISSING_RT_STS_BIT));
+
+ return rc;
+}
+
+int smblib_get_prop_batt_capacity(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc = -EINVAL;
+
+ if (chg->fake_capacity >= 0) {
+ val->intval = chg->fake_capacity;
+ return 0;
+ }
+
+ if (chg->bms_psy)
+ rc = power_supply_get_property(chg->bms_psy,
+ POWER_SUPPLY_PROP_CAPACITY, val);
+ return rc;
+}
+
+int smblib_get_prop_batt_status(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ union power_supply_propval pval = {0, };
+ bool usb_online, dc_online, qnovo_en;
+ u8 stat, pt_en_cmd;
+ int rc;
+
+ rc = smblib_get_prop_usb_online(chg, &pval);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get usb online property rc=%d\n",
+ rc);
+ return rc;
+ }
+ usb_online = (bool)pval.intval;
+
+ rc = smblib_get_prop_dc_online(chg, &pval);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get dc online property rc=%d\n",
+ rc);
+ return rc;
+ }
+ dc_online = (bool)pval.intval;
+
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+ rc);
+ return rc;
+ }
+ stat = stat & BATTERY_CHARGER_STATUS_MASK;
+
+ if (!usb_online && !dc_online) {
+ switch (stat) {
+ case TERMINATE_CHARGE:
+ case INHIBIT_CHARGE:
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ }
+ return rc;
+ }
+
+ switch (stat) {
+ case TRICKLE_CHARGE:
+ case PRE_CHARGE:
+ case FAST_CHARGE:
+ case FULLON_CHARGE:
+ case TAPER_CHARGE:
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case TERMINATE_CHARGE:
+ case INHIBIT_CHARGE:
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case DISABLE_CHARGE:
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ if (val->intval != POWER_SUPPLY_STATUS_CHARGING)
+ return 0;
+
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_7_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ stat &= ENABLE_TRICKLE_BIT | ENABLE_PRE_CHARGING_BIT |
+ ENABLE_FAST_CHARGING_BIT | ENABLE_FULLON_MODE_BIT;
+
+ rc = smblib_read(chg, QNOVO_PT_ENABLE_CMD_REG, &pt_en_cmd);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read QNOVO_PT_ENABLE_CMD_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ qnovo_en = (bool)(pt_en_cmd & QNOVO_PT_ENABLE_CMD_BIT);
+
+ /* ignore stat7 when qnovo is enabled */
+ if (!qnovo_en && !stat)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ return 0;
+}
+
+int smblib_get_prop_batt_charge_type(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+ u8 stat;
+
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ switch (stat & BATTERY_CHARGER_STATUS_MASK) {
+ case TRICKLE_CHARGE:
+ case PRE_CHARGE:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case FAST_CHARGE:
+ case FULLON_CHARGE:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case TAPER_CHARGE:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TAPER;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ }
+
+ return rc;
+}
+
+int smblib_get_prop_batt_health(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ union power_supply_propval pval;
+ int rc;
+ int effective_fv_uv;
+ u8 stat;
+
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
+ rc);
+ return rc;
+ }
+ smblib_dbg(chg, PR_REGISTER, "BATTERY_CHARGER_STATUS_2 = 0x%02x\n",
+ stat);
+
+ if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
+ rc = smblib_get_prop_batt_voltage_now(chg, &pval);
+ if (!rc) {
+ /*
+ * If Vbatt is within 40mV above Vfloat, then don't
+ * treat it as overvoltage.
+ */
+ effective_fv_uv = get_effective_result(chg->fv_votable);
+ if (pval.intval >= effective_fv_uv + 40000) {
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ smblib_err(chg, "battery over-voltage vbat_fg = %duV, fv = %duV\n",
+ pval.intval, effective_fv_uv);
+ goto done;
+ }
+ }
+ }
+
+ if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT)
+ val->intval = POWER_SUPPLY_HEALTH_COLD;
+ else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (stat & BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT)
+ val->intval = POWER_SUPPLY_HEALTH_COOL;
+ else if (stat & BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT)
+ val->intval = POWER_SUPPLY_HEALTH_WARM;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+
+done:
+ return rc;
+}
+
+int smblib_get_prop_system_temp_level(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ val->intval = chg->system_temp_level;
+ return 0;
+}
+
+int smblib_get_prop_input_current_limited(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ u8 stat;
+ int rc;
+
+ if (chg->fake_input_current_limited >= 0) {
+ val->intval = chg->fake_input_current_limited;
+ return 0;
+ }
+
+ rc = smblib_read(chg, AICL_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", rc);
+ return rc;
+ }
+ val->intval = (stat & SOFT_ILIMIT_BIT) || chg->is_hdc;
+ return 0;
+}
+
+int smblib_get_prop_batt_voltage_now(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+
+ if (!chg->bms_psy)
+ return -EINVAL;
+
+ rc = power_supply_get_property(chg->bms_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, val);
+ return rc;
+}
+
+int smblib_get_prop_batt_current_now(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+
+ if (!chg->bms_psy)
+ return -EINVAL;
+
+ rc = power_supply_get_property(chg->bms_psy,
+ POWER_SUPPLY_PROP_CURRENT_NOW, val);
+ return rc;
+}
+
+int smblib_get_prop_batt_temp(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+
+ if (!chg->bms_psy)
+ return -EINVAL;
+
+ rc = power_supply_get_property(chg->bms_psy,
+ POWER_SUPPLY_PROP_TEMP, val);
+ return rc;
+}
+
+int smblib_get_prop_batt_charge_done(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+ u8 stat;
+
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ stat = stat & BATTERY_CHARGER_STATUS_MASK;
+ val->intval = (stat == TERMINATE_CHARGE);
+ return 0;
+}
+
+int smblib_get_prop_charge_qnovo_enable(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+ u8 stat;
+
+ rc = smblib_read(chg, QNOVO_PT_ENABLE_CMD_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read QNOVO_PT_ENABLE_CMD rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ val->intval = (bool)(stat & QNOVO_PT_ENABLE_CMD_BIT);
+ return 0;
+}
+
+int smblib_get_prop_batt_charge_counter(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+
+ if (!chg->bms_psy)
+ return -EINVAL;
+
+ rc = power_supply_get_property(chg->bms_psy,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER, val);
+ return rc;
+}
+
+/***********************
+ * BATTERY PSY SETTERS *
+ ***********************/
+
+int smblib_set_prop_input_suspend(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc;
+
+ /* vote 0mA when suspended */
+ rc = vote(chg->usb_icl_votable, USER_VOTER, (bool)val->intval, 0);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't vote to %s USB rc=%d\n",
+ (bool)val->intval ? "suspend" : "resume", rc);
+ return rc;
+ }
+
+ rc = vote(chg->dc_suspend_votable, USER_VOTER, (bool)val->intval, 0);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't vote to %s DC rc=%d\n",
+ (bool)val->intval ? "suspend" : "resume", rc);
+ return rc;
+ }
+
+ power_supply_changed(chg->batt_psy);
+ return rc;
+}
+
+int smblib_set_prop_batt_capacity(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ chg->fake_capacity = val->intval;
+
+ power_supply_changed(chg->batt_psy);
+
+ return 0;
+}
+
+int smblib_set_prop_system_temp_level(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ if (val->intval < 0)
+ return -EINVAL;
+
+ if (chg->thermal_levels <= 0)
+ return -EINVAL;
+
+ if (val->intval > chg->thermal_levels)
+ return -EINVAL;
+
+ chg->system_temp_level = val->intval;
+ /* disable parallel charge in case of system temp level */
+ vote(chg->pl_disable_votable, THERMAL_DAEMON_VOTER,
+ chg->system_temp_level ? true : false, 0);
+
+ if (chg->system_temp_level == chg->thermal_levels)
+ return vote(chg->chg_disable_votable,
+ THERMAL_DAEMON_VOTER, true, 0);
+
+ vote(chg->chg_disable_votable, THERMAL_DAEMON_VOTER, false, 0);
+ if (chg->system_temp_level == 0)
+ return vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, false, 0);
+
+ vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, true,
+ chg->thermal_mitigation[chg->system_temp_level]);
+ return 0;
+}
+
+int smblib_set_prop_charge_qnovo_enable(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+
+ rc = smblib_masked_write(chg, QNOVO_PT_ENABLE_CMD_REG,
+ QNOVO_PT_ENABLE_CMD_BIT,
+ val->intval ? QNOVO_PT_ENABLE_CMD_BIT : 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't enable qnovo rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+int smblib_set_prop_input_current_limited(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ chg->fake_input_current_limited = val->intval;
+ return 0;
+}
+
+int smblib_rerun_aicl(struct smb_charger *chg)
+{
+ int rc, settled_icl_ua;
+ u8 stat;
+
+ rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* USB is suspended so skip re-running AICL */
+ if (stat & USBIN_SUSPEND_STS_BIT)
+ return rc;
+
+ smblib_dbg(chg, PR_MISC, "re-running AICL\n");
+ rc = smblib_get_charge_param(chg, &chg->param.icl_stat,
+ &settled_icl_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get settled ICL rc=%d\n", rc);
+ return rc;
+ }
+
+ vote(chg->usb_icl_votable, AICL_RERUN_VOTER, true,
+ max(settled_icl_ua - chg->param.usb_icl.step_u,
+ chg->param.usb_icl.step_u));
+ vote(chg->usb_icl_votable, AICL_RERUN_VOTER, false, 0);
+
+ return 0;
+}
+
+static int smblib_dp_pulse(struct smb_charger *chg)
+{
+ int rc;
+
+ /* QC 3.0 increment */
+ rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_INCREMENT_BIT,
+ SINGLE_INCREMENT_BIT);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n",
+ rc);
+
+ return rc;
+}
+
+static int smblib_dm_pulse(struct smb_charger *chg)
+{
+ int rc;
+
+ /* QC 3.0 decrement */
+ rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_DECREMENT_BIT,
+ SINGLE_DECREMENT_BIT);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n",
+ rc);
+
+ return rc;
+}
+
+int smblib_dp_dm(struct smb_charger *chg, int val)
+{
+ int target_icl_ua, rc = 0;
+ union power_supply_propval pval;
+
+ switch (val) {
+ case POWER_SUPPLY_DP_DM_DP_PULSE:
+ rc = smblib_dp_pulse(chg);
+ if (!rc)
+ chg->pulse_cnt++;
+ smblib_dbg(chg, PR_PARALLEL, "DP_DM_DP_PULSE rc=%d cnt=%d\n",
+ rc, chg->pulse_cnt);
+ break;
+ case POWER_SUPPLY_DP_DM_DM_PULSE:
+ rc = smblib_dm_pulse(chg);
+ if (!rc && chg->pulse_cnt)
+ chg->pulse_cnt--;
+ smblib_dbg(chg, PR_PARALLEL, "DP_DM_DM_PULSE rc=%d cnt=%d\n",
+ rc, chg->pulse_cnt);
+ break;
+ case POWER_SUPPLY_DP_DM_ICL_DOWN:
+ target_icl_ua = get_effective_result(chg->usb_icl_votable);
+ if (target_icl_ua < 0) {
+ /* no client vote, get the ICL from charger */
+ rc = power_supply_get_property(chg->usb_psy,
+ POWER_SUPPLY_PROP_HW_CURRENT_MAX,
+ &pval);
+ if (rc < 0) {
+ smblib_err(chg,
+ "Couldn't get max current rc=%d\n",
+ rc);
+ return rc;
+ }
+ target_icl_ua = pval.intval;
+ }
+
+ /*
+ * Check if any other voter voted on USB_ICL in case of
+ * voter other than SW_QC3_VOTER reset and restart reduction
+ * again.
+ */
+ if (target_icl_ua != get_client_vote(chg->usb_icl_votable,
+ SW_QC3_VOTER))
+ chg->usb_icl_delta_ua = 0;
+
+ chg->usb_icl_delta_ua += 100000;
+ vote(chg->usb_icl_votable, SW_QC3_VOTER, true,
+ target_icl_ua - 100000);
+ smblib_dbg(chg, PR_PARALLEL, "ICL DOWN ICL=%d reduction=%d\n",
+ target_icl_ua, chg->usb_icl_delta_ua);
+ break;
+ case POWER_SUPPLY_DP_DM_ICL_UP:
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+int smblib_disable_hw_jeita(struct smb_charger *chg, bool disable)
+{
+ int rc;
+ u8 mask;
+
+ /*
+ * Disable h/w base JEITA compensation if s/w JEITA is enabled
+ */
+ mask = JEITA_EN_COLD_SL_FCV_BIT
+ | JEITA_EN_HOT_SL_FCV_BIT
+ | JEITA_EN_HOT_SL_CCC_BIT
+ | JEITA_EN_COLD_SL_CCC_BIT,
+ rc = smblib_masked_write(chg, JEITA_EN_CFG_REG, mask,
+ disable ? 0 : mask);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure s/w jeita rc=%d\n",
+ rc);
+ return rc;
+ }
+ return 0;
+}
+
+/*******************
+ * DC PSY GETTERS *
+ *******************/
+
+int smblib_get_prop_dc_present(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+ u8 stat;
+
+ rc = smblib_read(chg, DCIN_BASE + INT_RT_STS_OFFSET, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read DCIN_RT_STS rc=%d\n", rc);
+ return rc;
+ }
+
+ val->intval = (bool)(stat & DCIN_PLUGIN_RT_STS_BIT);
+ return 0;
+}
+
+int smblib_get_prop_dc_online(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc = 0;
+ u8 stat;
+
+ if (get_client_vote(chg->dc_suspend_votable, USER_VOTER)) {
+ val->intval = false;
+ return rc;
+ }
+
+ rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n",
+ rc);
+ return rc;
+ }
+ smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n",
+ stat);
+
+ val->intval = (stat & USE_DCIN_BIT) &&
+ (stat & VALID_INPUT_POWER_SOURCE_STS_BIT);
+
+ return rc;
+}
+
+int smblib_get_prop_dc_current_max(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ val->intval = get_effective_result_locked(chg->dc_icl_votable);
+ return 0;
+}
+
+/*******************
+ * DC PSY SETTERS *
+ * *****************/
+
+int smblib_set_prop_dc_current_max(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc;
+
+ rc = vote(chg->dc_icl_votable, USER_VOTER, true, val->intval);
+ return rc;
+}
+
+/*******************
+ * USB PSY GETTERS *
+ *******************/
+
+int smblib_get_prop_usb_present(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+ u8 stat;
+
+ rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read USBIN_RT_STS rc=%d\n", rc);
+ return rc;
+ }
+
+ val->intval = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT);
+ return 0;
+}
+
+int smblib_get_prop_usb_online(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc = 0;
+ u8 stat;
+
+ if (get_client_vote_locked(chg->usb_icl_votable, USER_VOTER) == 0) {
+ val->intval = false;
+ return rc;
+ }
+
+ rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n",
+ rc);
+ return rc;
+ }
+ smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n",
+ stat);
+
+ val->intval = (stat & USE_USBIN_BIT) &&
+ (stat & VALID_INPUT_POWER_SOURCE_STS_BIT);
+ return rc;
+}
+
+int smblib_get_prop_usb_voltage_max(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ switch (chg->real_charger_type) {
+ case POWER_SUPPLY_TYPE_USB_HVDCP:
+ case POWER_SUPPLY_TYPE_USB_PD:
+ if (chg->smb_version == PM660_SUBTYPE)
+ val->intval = MICRO_9V;
+ else
+ val->intval = MICRO_12V;
+ break;
+ default:
+ val->intval = MICRO_5V;
+ break;
+ }
+
+ return 0;
+}
+
+int smblib_get_prop_usb_voltage_now(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc = 0;
+
+ rc = smblib_get_prop_usb_present(chg, val);
+ if (rc < 0 || !val->intval)
+ return rc;
+
+ if (!chg->iio.usbin_v_chan ||
+ PTR_ERR(chg->iio.usbin_v_chan) == -EPROBE_DEFER)
+ chg->iio.usbin_v_chan = iio_channel_get(chg->dev, "usbin_v");
+
+ if (IS_ERR(chg->iio.usbin_v_chan))
+ return PTR_ERR(chg->iio.usbin_v_chan);
+
+ return iio_read_channel_processed(chg->iio.usbin_v_chan, &val->intval);
+}
+
+int smblib_get_prop_usb_current_now(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc = 0;
+
+ rc = smblib_get_prop_usb_present(chg, val);
+ if (rc < 0 || !val->intval)
+ return rc;
+
+ if (!chg->iio.usbin_i_chan ||
+ PTR_ERR(chg->iio.usbin_i_chan) == -EPROBE_DEFER)
+ chg->iio.usbin_i_chan = iio_channel_get(chg->dev, "usbin_i");
+
+ if (IS_ERR(chg->iio.usbin_i_chan))
+ return PTR_ERR(chg->iio.usbin_i_chan);
+
+ return iio_read_channel_processed(chg->iio.usbin_i_chan, &val->intval);
+}
+
+int smblib_get_prop_charger_temp(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+
+ if (!chg->iio.temp_chan ||
+ PTR_ERR(chg->iio.temp_chan) == -EPROBE_DEFER)
+ chg->iio.temp_chan = iio_channel_get(chg->dev, "charger_temp");
+
+ if (IS_ERR(chg->iio.temp_chan))
+ return PTR_ERR(chg->iio.temp_chan);
+
+ rc = iio_read_channel_processed(chg->iio.temp_chan, &val->intval);
+ val->intval /= 100;
+ return rc;
+}
+
+int smblib_get_prop_charger_temp_max(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+
+ if (!chg->iio.temp_max_chan ||
+ PTR_ERR(chg->iio.temp_max_chan) == -EPROBE_DEFER)
+ chg->iio.temp_max_chan = iio_channel_get(chg->dev,
+ "charger_temp_max");
+ if (IS_ERR(chg->iio.temp_max_chan))
+ return PTR_ERR(chg->iio.temp_max_chan);
+
+ rc = iio_read_channel_processed(chg->iio.temp_max_chan, &val->intval);
+ val->intval /= 100;
+ return rc;
+}
+
+int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ if (chg->typec_status[3] & CC_ATTACHED_BIT)
+ val->intval =
+ (bool)(chg->typec_status[3] & CC_ORIENTATION_BIT) + 1;
+ else
+ val->intval = 0;
+
+ return 0;
+}
+
+static const char * const smblib_typec_mode_name[] = {
+ [POWER_SUPPLY_TYPEC_NONE] = "NONE",
+ [POWER_SUPPLY_TYPEC_SOURCE_DEFAULT] = "SOURCE_DEFAULT",
+ [POWER_SUPPLY_TYPEC_SOURCE_MEDIUM] = "SOURCE_MEDIUM",
+ [POWER_SUPPLY_TYPEC_SOURCE_HIGH] = "SOURCE_HIGH",
+ [POWER_SUPPLY_TYPEC_NON_COMPLIANT] = "NON_COMPLIANT",
+ [POWER_SUPPLY_TYPEC_SINK] = "SINK",
+ [POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE] = "SINK_POWERED_CABLE",
+ [POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY] = "SINK_DEBUG_ACCESSORY",
+ [POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER] = "SINK_AUDIO_ADAPTER",
+ [POWER_SUPPLY_TYPEC_POWERED_CABLE_ONLY] = "POWERED_CABLE_ONLY",
+};
+
+static int smblib_get_prop_ufp_mode(struct smb_charger *chg)
+{
+ switch (chg->typec_status[0]) {
+ case UFP_TYPEC_RDSTD_BIT:
+ return POWER_SUPPLY_TYPEC_SOURCE_DEFAULT;
+ case UFP_TYPEC_RD1P5_BIT:
+ return POWER_SUPPLY_TYPEC_SOURCE_MEDIUM;
+ case UFP_TYPEC_RD3P0_BIT:
+ return POWER_SUPPLY_TYPEC_SOURCE_HIGH;
+ default:
+ break;
+ }
+
+ return POWER_SUPPLY_TYPEC_NONE;
+}
+
+static int smblib_get_prop_dfp_mode(struct smb_charger *chg)
+{
+ switch (chg->typec_status[1] & DFP_TYPEC_MASK) {
+ case DFP_RA_RA_BIT:
+ return POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER;
+ case DFP_RD_RD_BIT:
+ return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY;
+ case DFP_RD_RA_VCONN_BIT:
+ return POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE;
+ case DFP_RD_OPEN_BIT:
+ return POWER_SUPPLY_TYPEC_SINK;
+ default:
+ break;
+ }
+
+ return POWER_SUPPLY_TYPEC_NONE;
+}
+
+static int smblib_get_prop_typec_mode(struct smb_charger *chg)
+{
+ if (chg->typec_status[3] & UFP_DFP_MODE_STATUS_BIT)
+ return smblib_get_prop_dfp_mode(chg);
+ else
+ return smblib_get_prop_ufp_mode(chg);
+}
+
+int smblib_get_prop_typec_power_role(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc = 0;
+ u8 ctrl;
+
+ rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, &ctrl);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
+ rc);
+ return rc;
+ }
+ smblib_dbg(chg, PR_REGISTER, "TYPE_C_INTRPT_ENB_SOFTWARE_CTRL = 0x%02x\n",
+ ctrl);
+
+ if (ctrl & TYPEC_DISABLE_CMD_BIT) {
+ val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
+ return rc;
+ }
+
+ switch (ctrl & (DFP_EN_CMD_BIT | UFP_EN_CMD_BIT)) {
+ case 0:
+ val->intval = POWER_SUPPLY_TYPEC_PR_DUAL;
+ break;
+ case DFP_EN_CMD_BIT:
+ val->intval = POWER_SUPPLY_TYPEC_PR_SOURCE;
+ break;
+ case UFP_EN_CMD_BIT:
+ val->intval = POWER_SUPPLY_TYPEC_PR_SINK;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
+ smblib_err(chg, "unsupported power role 0x%02lx\n",
+ ctrl & (DFP_EN_CMD_BIT | UFP_EN_CMD_BIT));
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+int smblib_get_prop_pd_allowed(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ val->intval = get_effective_result(chg->pd_allowed_votable);
+ return 0;
+}
+
+int smblib_get_prop_input_current_settled(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ return smblib_get_charge_param(chg, &chg->param.icl_stat, &val->intval);
+}
+
+#define HVDCP3_STEP_UV 200000
+int smblib_get_prop_input_voltage_settled(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc, pulses;
+
+ switch (chg->real_charger_type) {
+ case POWER_SUPPLY_TYPE_USB_HVDCP_3:
+ rc = smblib_get_pulse_cnt(chg, &pulses);
+ if (rc < 0) {
+ smblib_err(chg,
+ "Couldn't read QC_PULSE_COUNT rc=%d\n", rc);
+ return 0;
+ }
+ val->intval = MICRO_5V + HVDCP3_STEP_UV * pulses;
+ break;
+ case POWER_SUPPLY_TYPE_USB_PD:
+ val->intval = chg->voltage_min_uv;
+ break;
+ default:
+ val->intval = MICRO_5V;
+ break;
+ }
+
+ return 0;
+}
+
+int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ val->intval = chg->pd_hard_reset;
+ return 0;
+}
+
+int smblib_get_pe_start(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ /*
+ * hvdcp timeout voter is the last one to allow pd. Use its vote
+ * to indicate start of pe engine
+ */
+ val->intval
+ = !get_client_vote_locked(chg->pd_disallowed_votable_indirect,
+ HVDCP_TIMEOUT_VOTER);
+ return 0;
+}
+
+int smblib_get_prop_die_health(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc;
+ u8 stat;
+
+ rc = smblib_read(chg, TEMP_RANGE_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read TEMP_RANGE_STATUS_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* TEMP_RANGE bits are mutually exclusive */
+ switch (stat & TEMP_RANGE_MASK) {
+ case TEMP_BELOW_RANGE_BIT:
+ val->intval = POWER_SUPPLY_HEALTH_COOL;
+ break;
+ case TEMP_WITHIN_RANGE_BIT:
+ val->intval = POWER_SUPPLY_HEALTH_WARM;
+ break;
+ case TEMP_ABOVE_RANGE_BIT:
+ val->intval = POWER_SUPPLY_HEALTH_HOT;
+ break;
+ case ALERT_LEVEL_BIT:
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+
+ return 0;
+}
+
+#define SDP_CURRENT_UA 500000
+#define CDP_CURRENT_UA 1500000
+#define DCP_CURRENT_UA 1500000
+#define HVDCP_CURRENT_UA 3000000
+#define TYPEC_DEFAULT_CURRENT_UA 900000
+#define TYPEC_MEDIUM_CURRENT_UA 1500000
+#define TYPEC_HIGH_CURRENT_UA 3000000
+static int get_rp_based_dcp_current(struct smb_charger *chg, int typec_mode)
+{
+ int rp_ua;
+
+ switch (typec_mode) {
+ case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
+ rp_ua = TYPEC_HIGH_CURRENT_UA;
+ break;
+ case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
+ case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
+ /* fall through */
+ default:
+ rp_ua = DCP_CURRENT_UA;
+ }
+
+ return rp_ua;
+}
+
+/*******************
+ * USB PSY SETTERS *
+ * *****************/
+
+int smblib_set_prop_pd_current_max(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc;
+
+ if (chg->pd_active)
+ rc = vote(chg->usb_icl_votable, PD_VOTER, true, val->intval);
+ else
+ rc = -EPERM;
+
+ return rc;
+}
+
+static int smblib_handle_usb_current(struct smb_charger *chg,
+ int usb_current)
+{
+ int rc = 0, rp_ua, typec_mode;
+
+ if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_FLOAT) {
+ if (usb_current == -ETIMEDOUT) {
+ /*
+ * Valid FLOAT charger, report the current based
+ * of Rp
+ */
+ typec_mode = smblib_get_prop_typec_mode(chg);
+ rp_ua = get_rp_based_dcp_current(chg, typec_mode);
+ rc = vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER,
+ true, rp_ua);
+ if (rc < 0)
+ return rc;
+ } else {
+ /*
+ * FLOAT charger detected as SDP by USB driver,
+ * charge with the requested current and update the
+ * real_charger_type
+ */
+ chg->real_charger_type = POWER_SUPPLY_TYPE_USB;
+ rc = vote(chg->usb_icl_votable, USB_PSY_VOTER,
+ true, usb_current);
+ if (rc < 0)
+ return rc;
+ rc = vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER,
+ false, 0);
+ if (rc < 0)
+ return rc;
+ }
+ } else {
+ rc = vote(chg->usb_icl_votable, USB_PSY_VOTER,
+ true, usb_current);
+ }
+
+ return rc;
+}
+
+int smblib_set_prop_sdp_current_max(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+
+ if (!chg->pd_active) {
+ rc = smblib_handle_usb_current(chg, val->intval);
+ } else if (chg->system_suspend_supported) {
+ if (val->intval <= USBIN_25MA)
+ rc = vote(chg->usb_icl_votable,
+ PD_SUSPEND_SUPPORTED_VOTER, true, val->intval);
+ else
+ rc = vote(chg->usb_icl_votable,
+ PD_SUSPEND_SUPPORTED_VOTER, false, 0);
+ }
+ return rc;
+}
+
+int smblib_set_prop_boost_current(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+
+ rc = smblib_set_charge_param(chg, &chg->param.freq_boost,
+ val->intval <= chg->boost_threshold_ua ?
+ chg->chg_freq.freq_below_otg_threshold :
+ chg->chg_freq.freq_above_otg_threshold);
+ if (rc < 0) {
+ dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc);
+ return rc;
+ }
+
+ chg->boost_current_ua = val->intval;
+ return rc;
+}
+
+int smblib_set_prop_typec_power_role(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+ u8 power_role;
+
+ switch (val->intval) {
+ case POWER_SUPPLY_TYPEC_PR_NONE:
+ power_role = TYPEC_DISABLE_CMD_BIT;
+ break;
+ case POWER_SUPPLY_TYPEC_PR_DUAL:
+ power_role = 0;
+ break;
+ case POWER_SUPPLY_TYPEC_PR_SINK:
+ power_role = UFP_EN_CMD_BIT;
+ break;
+ case POWER_SUPPLY_TYPEC_PR_SOURCE:
+ power_role = DFP_EN_CMD_BIT;
+ break;
+ default:
+ smblib_err(chg, "power role %d not supported\n", val->intval);
+ return -EINVAL;
+ }
+
+ if (power_role == UFP_EN_CMD_BIT) {
+ /* disable PBS workaround when forcing sink mode */
+ rc = smblib_write(chg, TM_IO_DTEST4_SEL, 0x0);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't write to TM_IO_DTEST4_SEL rc=%d\n",
+ rc);
+ }
+ } else {
+ /* restore it back to 0xA5 */
+ rc = smblib_write(chg, TM_IO_DTEST4_SEL, 0xA5);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't write to TM_IO_DTEST4_SEL rc=%d\n",
+ rc);
+ }
+ }
+
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_POWER_ROLE_CMD_MASK, power_role);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
+ power_role, rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+int smblib_set_prop_pd_voltage_min(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc, min_uv;
+
+ min_uv = min(val->intval, chg->voltage_max_uv);
+ rc = smblib_set_usb_pd_allowed_voltage(chg, min_uv,
+ chg->voltage_max_uv);
+ if (rc < 0) {
+ smblib_err(chg, "invalid max voltage %duV rc=%d\n",
+ val->intval, rc);
+ return rc;
+ }
+
+ chg->voltage_min_uv = min_uv;
+ power_supply_changed(chg->usb_main_psy);
+ return rc;
+}
+
+int smblib_set_prop_pd_voltage_max(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc, max_uv;
+
+ max_uv = max(val->intval, chg->voltage_min_uv);
+ rc = smblib_set_usb_pd_allowed_voltage(chg, chg->voltage_min_uv,
+ max_uv);
+ if (rc < 0) {
+ smblib_err(chg, "invalid min voltage %duV rc=%d\n",
+ val->intval, rc);
+ return rc;
+ }
+
+ chg->voltage_max_uv = max_uv;
+ return rc;
+}
+
+int smblib_set_prop_pd_active(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc;
+ bool orientation, sink_attached, hvdcp;
+ u8 stat;
+
+ if (!get_effective_result(chg->pd_allowed_votable))
+ return -EINVAL;
+
+ chg->pd_active = val->intval;
+ if (chg->pd_active) {
+ vote(chg->apsd_disable_votable, PD_VOTER, true, 0);
+ vote(chg->pd_allowed_votable, PD_VOTER, true, 0);
+ vote(chg->usb_irq_enable_votable, PD_VOTER, true, 0);
+
+ /*
+ * VCONN_EN_ORIENTATION_BIT controls whether to use CC1 or CC2
+ * line when TYPEC_SPARE_CFG_BIT (CC pin selection s/w override)
+ * is set or when VCONN_EN_VALUE_BIT is set.
+ */
+ orientation = chg->typec_status[3] & CC_ORIENTATION_BIT;
+ rc = smblib_masked_write(chg,
+ TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ VCONN_EN_ORIENTATION_BIT,
+ orientation ? 0 : VCONN_EN_ORIENTATION_BIT);
+ if (rc < 0)
+ smblib_err(chg,
+ "Couldn't enable vconn on CC line rc=%d\n", rc);
+
+ /* SW controlled CC_OUT */
+ rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG,
+ TYPEC_SPARE_CFG_BIT, TYPEC_SPARE_CFG_BIT);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't enable SW cc_out rc=%d\n",
+ rc);
+
+ /*
+ * Enforce 500mA for PD until the real vote comes in later.
+ * It is guaranteed that pd_active is set prior to
+ * pd_current_max
+ */
+ rc = vote(chg->usb_icl_votable, PD_VOTER, true, USBIN_500MA);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't vote for USB ICL rc=%d\n",
+ rc);
+
+ /* since PD was found the cable must be non-legacy */
+ vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, false, 0);
+
+ /* clear USB ICL vote for DCP_VOTER */
+ rc = vote(chg->usb_icl_votable, DCP_VOTER, false, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't un-vote DCP from USB ICL rc=%d\n",
+ rc);
+
+ /* remove USB_PSY_VOTER */
+ rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't unvote USB_PSY rc=%d\n", rc);
+ } else {
+ rc = smblib_read(chg, APSD_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read APSD status rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ hvdcp = stat & QC_CHARGER_BIT;
+ vote(chg->apsd_disable_votable, PD_VOTER, false, 0);
+ vote(chg->pd_allowed_votable, PD_VOTER, true, 0);
+ vote(chg->usb_irq_enable_votable, PD_VOTER, true, 0);
+ vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER,
+ false, 0);
+
+ /* HW controlled CC_OUT */
+ rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG,
+ TYPEC_SPARE_CFG_BIT, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't enable HW cc_out rc=%d\n",
+ rc);
+
+ /*
+ * This WA should only run for HVDCP. Non-legacy SDP/CDP could
+ * draw more, but this WA will remove Rd causing VBUS to drop,
+ * and data could be interrupted. Non-legacy DCP could also draw
+ * more, but it may impact compliance.
+ */
+ sink_attached = chg->typec_status[3] & UFP_DFP_MODE_STATUS_BIT;
+ if (!chg->typec_legacy_valid && !sink_attached && hvdcp)
+ schedule_work(&chg->legacy_detection_work);
+ }
+
+ smblib_update_usb_type(chg);
+ power_supply_changed(chg->usb_psy);
+ return rc;
+}
+
+int smblib_set_prop_ship_mode(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc;
+
+ smblib_dbg(chg, PR_MISC, "Set ship mode: %d!!\n", !!val->intval);
+
+ rc = smblib_masked_write(chg, SHIP_MODE_REG, SHIP_MODE_EN_BIT,
+ !!val->intval ? SHIP_MODE_EN_BIT : 0);
+ if (rc < 0)
+ dev_err(chg->dev, "Couldn't %s ship mode, rc=%d\n",
+ !!val->intval ? "enable" : "disable", rc);
+
+ return rc;
+}
+
+int smblib_reg_block_update(struct smb_charger *chg,
+ struct reg_info *entry)
+{
+ int rc = 0;
+
+ while (entry && entry->reg) {
+ rc = smblib_read(chg, entry->reg, &entry->bak);
+ if (rc < 0) {
+ dev_err(chg->dev, "Error in reading %s rc=%d\n",
+ entry->desc, rc);
+ break;
+ }
+ entry->bak &= entry->mask;
+
+ rc = smblib_masked_write(chg, entry->reg,
+ entry->mask, entry->val);
+ if (rc < 0) {
+ dev_err(chg->dev, "Error in writing %s rc=%d\n",
+ entry->desc, rc);
+ break;
+ }
+ entry++;
+ }
+
+ return rc;
+}
+
+int smblib_reg_block_restore(struct smb_charger *chg,
+ struct reg_info *entry)
+{
+ int rc = 0;
+
+ while (entry && entry->reg) {
+ rc = smblib_masked_write(chg, entry->reg,
+ entry->mask, entry->bak);
+ if (rc < 0) {
+ dev_err(chg->dev, "Error in writing %s rc=%d\n",
+ entry->desc, rc);
+ break;
+ }
+ entry++;
+ }
+
+ return rc;
+}
+
+static struct reg_info cc2_detach_settings[] = {
+ {
+ .reg = TYPE_C_CFG_2_REG,
+ .mask = TYPE_C_UFP_MODE_BIT | EN_TRY_SOURCE_MODE_BIT,
+ .val = TYPE_C_UFP_MODE_BIT,
+ .desc = "TYPE_C_CFG_2_REG",
+ },
+ {
+ .reg = TYPE_C_CFG_3_REG,
+ .mask = EN_TRYSINK_MODE_BIT,
+ .val = 0,
+ .desc = "TYPE_C_CFG_3_REG",
+ },
+ {
+ .reg = TAPER_TIMER_SEL_CFG_REG,
+ .mask = TYPEC_SPARE_CFG_BIT,
+ .val = TYPEC_SPARE_CFG_BIT,
+ .desc = "TAPER_TIMER_SEL_CFG_REG",
+ },
+ {
+ .reg = TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ .mask = VCONN_EN_ORIENTATION_BIT,
+ .val = 0,
+ .desc = "TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG",
+ },
+ {
+ .reg = MISC_CFG_REG,
+ .mask = TCC_DEBOUNCE_20MS_BIT,
+ .val = TCC_DEBOUNCE_20MS_BIT,
+ .desc = "Tccdebounce time"
+ },
+ {
+ },
+};
+
+static int smblib_cc2_sink_removal_enter(struct smb_charger *chg)
+{
+ int rc, ccout, ufp_mode;
+ u8 stat;
+
+ if ((chg->wa_flags & TYPEC_CC2_REMOVAL_WA_BIT) == 0)
+ return 0;
+
+ if (chg->cc2_detach_wa_active)
+ return 0;
+
+ rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
+ return rc;
+ }
+
+ ccout = (stat & CC_ATTACHED_BIT) ?
+ (!!(stat & CC_ORIENTATION_BIT) + 1) : 0;
+ ufp_mode = (stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT) ?
+ !(stat & UFP_DFP_MODE_STATUS_BIT) : 0;
+
+ if (ccout != 2)
+ return 0;
+
+ if (!ufp_mode)
+ return 0;
+
+ chg->cc2_detach_wa_active = true;
+ /* The CC2 removal WA will cause a type-c-change IRQ storm */
+ smblib_reg_block_update(chg, cc2_detach_settings);
+ schedule_work(&chg->rdstd_cc2_detach_work);
+ return rc;
+}
+
+static int smblib_cc2_sink_removal_exit(struct smb_charger *chg)
+{
+ if ((chg->wa_flags & TYPEC_CC2_REMOVAL_WA_BIT) == 0)
+ return 0;
+
+ if (!chg->cc2_detach_wa_active)
+ return 0;
+
+ chg->cc2_detach_wa_active = false;
+ cancel_work_sync(&chg->rdstd_cc2_detach_work);
+ smblib_reg_block_restore(chg, cc2_detach_settings);
+ return 0;
+}
+
+int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+
+ if (chg->pd_hard_reset == val->intval)
+ return rc;
+
+ chg->pd_hard_reset = val->intval;
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ EXIT_SNK_BASED_ON_CC_BIT,
+ (chg->pd_hard_reset) ? EXIT_SNK_BASED_ON_CC_BIT : 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't set EXIT_SNK_BASED_ON_CC rc=%d\n",
+ rc);
+
+ vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER,
+ chg->pd_hard_reset, 0);
+
+ return rc;
+}
+
+static int smblib_recover_from_soft_jeita(struct smb_charger *chg)
+{
+ u8 stat_1, stat_2;
+ int rc;
+
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat_1);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat_2);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if ((chg->jeita_status && !(stat_2 & BAT_TEMP_STATUS_SOFT_LIMIT_MASK) &&
+ ((stat_1 & BATTERY_CHARGER_STATUS_MASK) == TERMINATE_CHARGE))) {
+ /*
+ * We are moving from JEITA soft -> Normal and charging
+ * is terminated
+ */
+ rc = smblib_write(chg, CHARGING_ENABLE_CMD_REG, 0);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't disable charging rc=%d\n",
+ rc);
+ return rc;
+ }
+ rc = smblib_write(chg, CHARGING_ENABLE_CMD_REG,
+ CHARGING_ENABLE_CMD_BIT);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't enable charging rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ chg->jeita_status = stat_2 & BAT_TEMP_STATUS_SOFT_LIMIT_MASK;
+
+ return 0;
+}
+
+/***********************
+* USB MAIN PSY GETTERS *
+*************************/
+int smblib_get_prop_fcc_delta(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ int rc, jeita_cc_delta_ua = 0;
+
+ rc = smblib_get_jeita_cc_delta(chg, &jeita_cc_delta_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get jeita cc delta rc=%d\n", rc);
+ jeita_cc_delta_ua = 0;
+ }
+
+ val->intval = jeita_cc_delta_ua;
+ return 0;
+}
+
+/***********************
+* USB MAIN PSY SETTERS *
+*************************/
+int smblib_get_charge_current(struct smb_charger *chg,
+ int *total_current_ua)
+{
+ const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
+ union power_supply_propval val = {0, };
+ int rc = 0, typec_source_rd, current_ua;
+ bool non_compliant;
+ u8 stat5;
+
+ if (chg->pd_active) {
+ *total_current_ua =
+ get_client_vote_locked(chg->usb_icl_votable, PD_VOTER);
+ return rc;
+ }
+
+ rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat5);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc);
+ return rc;
+ }
+ non_compliant = stat5 & TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT;
+
+ /* get settled ICL */
+ rc = smblib_get_prop_input_current_settled(chg, &val);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get settled ICL rc=%d\n", rc);
+ return rc;
+ }
+
+ typec_source_rd = smblib_get_prop_ufp_mode(chg);
+
+ /* QC 2.0/3.0 adapter */
+ if (apsd_result->bit & (QC_3P0_BIT | QC_2P0_BIT)) {
+ *total_current_ua = HVDCP_CURRENT_UA;
+ return 0;
+ }
+
+ if (non_compliant) {
+ switch (apsd_result->bit) {
+ case CDP_CHARGER_BIT:
+ current_ua = CDP_CURRENT_UA;
+ break;
+ case DCP_CHARGER_BIT:
+ case OCP_CHARGER_BIT:
+ case FLOAT_CHARGER_BIT:
+ current_ua = DCP_CURRENT_UA;
+ break;
+ default:
+ current_ua = 0;
+ break;
+ }
+
+ *total_current_ua = max(current_ua, val.intval);
+ return 0;
+ }
+
+ switch (typec_source_rd) {
+ case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
+ switch (apsd_result->bit) {
+ case CDP_CHARGER_BIT:
+ current_ua = CDP_CURRENT_UA;
+ break;
+ case DCP_CHARGER_BIT:
+ case OCP_CHARGER_BIT:
+ case FLOAT_CHARGER_BIT:
+ current_ua = chg->default_icl_ua;
+ break;
+ default:
+ current_ua = 0;
+ break;
+ }
+ break;
+ case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
+ current_ua = TYPEC_MEDIUM_CURRENT_UA;
+ break;
+ case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
+ current_ua = TYPEC_HIGH_CURRENT_UA;
+ break;
+ case POWER_SUPPLY_TYPEC_NON_COMPLIANT:
+ case POWER_SUPPLY_TYPEC_NONE:
+ default:
+ current_ua = 0;
+ break;
+ }
+
+ *total_current_ua = max(current_ua, val.intval);
+ return 0;
+}
+
+/************************
+ * PARALLEL PSY GETTERS *
+ ************************/
+
+int smblib_get_prop_slave_current_now(struct smb_charger *chg,
+ union power_supply_propval *pval)
+{
+ if (IS_ERR_OR_NULL(chg->iio.batt_i_chan))
+ chg->iio.batt_i_chan = iio_channel_get(chg->dev, "batt_i");
+
+ if (IS_ERR(chg->iio.batt_i_chan))
+ return PTR_ERR(chg->iio.batt_i_chan);
+
+ return iio_read_channel_processed(chg->iio.batt_i_chan, &pval->intval);
+}
+
+/**********************
+ * INTERRUPT HANDLERS *
+ **********************/
+
+irqreturn_t smblib_handle_debug(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+ return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_otg_overcurrent(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+ int rc;
+ u8 stat;
+
+ rc = smblib_read(chg, OTG_BASE + INT_RT_STS_OFFSET, &stat);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't read OTG_INT_RT_STS rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+
+ if (chg->wa_flags & OTG_WA) {
+ if (stat & OTG_OC_DIS_SW_STS_RT_STS_BIT)
+ smblib_err(chg, "OTG disabled by hw\n");
+
+ /* not handling software based hiccups for PM660 */
+ return IRQ_HANDLED;
+ }
+
+ if (stat & OTG_OVERCURRENT_RT_STS_BIT)
+ schedule_work(&chg->otg_oc_work);
+
+ return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_chg_state_change(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+ u8 stat;
+ int rc;
+
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+ rc);
+ return IRQ_HANDLED;
+ }
+
+ stat = stat & BATTERY_CHARGER_STATUS_MASK;
+ power_supply_changed(chg->batt_psy);
+ return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_batt_temp_changed(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+ int rc;
+
+ rc = smblib_recover_from_soft_jeita(chg);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't recover chg from soft jeita rc=%d\n",
+ rc);
+ return IRQ_HANDLED;
+ }
+
+ rerun_election(chg->fcc_votable);
+ power_supply_changed(chg->batt_psy);
+ return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_batt_psy_changed(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+ power_supply_changed(chg->batt_psy);
+ return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_usb_psy_changed(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+ power_supply_changed(chg->usb_psy);
+ return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_usbin_uv(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+ struct storm_watch *wdata;
+
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+ if (!chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data)
+ return IRQ_HANDLED;
+
+ wdata = &chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data->storm_data;
+ reset_storm_count(wdata);
+ return IRQ_HANDLED;
+}
+
+static void smblib_micro_usb_plugin(struct smb_charger *chg, bool vbus_rising)
+{
+ if (vbus_rising) {
+ /* use the typec flag even though its not typec */
+ chg->typec_present = 1;
+ } else {
+ chg->typec_present = 0;
+ smblib_update_usb_type(chg);
+ extcon_set_cable_state_(chg->extcon, EXTCON_USB, false);
+ smblib_uusb_removal(chg);
+ }
+}
+
+void smblib_usb_plugin_hard_reset_locked(struct smb_charger *chg)
+{
+ int rc;
+ u8 stat;
+ bool vbus_rising;
+ struct smb_irq_data *data;
+ struct storm_watch *wdata;
+
+ rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc);
+ return;
+ }
+
+ vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT);
+
+ if (vbus_rising) {
+ smblib_cc2_sink_removal_exit(chg);
+ } else {
+ smblib_cc2_sink_removal_enter(chg);
+ if (chg->wa_flags & BOOST_BACK_WA) {
+ data = chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data;
+ if (data) {
+ wdata = &data->storm_data;
+ update_storm_count(wdata,
+ WEAK_CHG_STORM_COUNT);
+ vote(chg->usb_icl_votable, BOOST_BACK_VOTER,
+ false, 0);
+ vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER,
+ false, 0);
+ }
+ }
+ }
+
+ power_supply_changed(chg->usb_psy);
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n",
+ vbus_rising ? "attached" : "detached");
+}
+
+#define PL_DELAY_MS 30000
+void smblib_usb_plugin_locked(struct smb_charger *chg)
+{
+ int rc;
+ u8 stat;
+ bool vbus_rising;
+ struct smb_irq_data *data;
+ struct storm_watch *wdata;
+
+ rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc);
+ return;
+ }
+
+ vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT);
+ smblib_set_opt_freq_buck(chg, vbus_rising ? chg->chg_freq.freq_5V :
+ chg->chg_freq.freq_removal);
+
+ if (vbus_rising) {
+ rc = smblib_request_dpdm(chg, true);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc);
+
+ /* Schedule work to enable parallel charger */
+ vote(chg->awake_votable, PL_DELAY_VOTER, true, 0);
+ schedule_delayed_work(&chg->pl_enable_work,
+ msecs_to_jiffies(PL_DELAY_MS));
+ } else {
+ if (chg->wa_flags & BOOST_BACK_WA) {
+ data = chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data;
+ if (data) {
+ wdata = &data->storm_data;
+ update_storm_count(wdata,
+ WEAK_CHG_STORM_COUNT);
+ vote(chg->usb_icl_votable, BOOST_BACK_VOTER,
+ false, 0);
+ vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER,
+ false, 0);
+ }
+ }
+
+ rc = smblib_request_dpdm(chg, false);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc);
+ }
+
+ if (chg->micro_usb_mode)
+ smblib_micro_usb_plugin(chg, vbus_rising);
+
+ power_supply_changed(chg->usb_psy);
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n",
+ vbus_rising ? "attached" : "detached");
+}
+
+irqreturn_t smblib_handle_usb_plugin(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+
+ mutex_lock(&chg->lock);
+ if (chg->pd_hard_reset)
+ smblib_usb_plugin_hard_reset_locked(chg);
+ else
+ smblib_usb_plugin_locked(chg);
+ mutex_unlock(&chg->lock);
+ return IRQ_HANDLED;
+}
+
+#define USB_WEAK_INPUT_UA 1400000
+#define ICL_CHANGE_DELAY_MS 1000
+irqreturn_t smblib_handle_icl_change(int irq, void *data)
+{
+ u8 stat;
+ int rc, settled_ua, delay = ICL_CHANGE_DELAY_MS;
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+
+ if (chg->mode == PARALLEL_MASTER) {
+ rc = smblib_read(chg, AICL_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n",
+ rc);
+ return IRQ_HANDLED;
+ }
+
+ rc = smblib_get_charge_param(chg, &chg->param.icl_stat,
+ &settled_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+
+ /* If AICL settled then schedule work now */
+ if ((settled_ua == get_effective_result(chg->usb_icl_votable))
+ || (stat & AICL_DONE_BIT))
+ delay = 0;
+
+ cancel_delayed_work_sync(&chg->icl_change_work);
+ schedule_delayed_work(&chg->icl_change_work,
+ msecs_to_jiffies(delay));
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void smblib_handle_slow_plugin_timeout(struct smb_charger *chg,
+ bool rising)
+{
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: slow-plugin-timeout %s\n",
+ rising ? "rising" : "falling");
+}
+
+static void smblib_handle_sdp_enumeration_done(struct smb_charger *chg,
+ bool rising)
+{
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: sdp-enumeration-done %s\n",
+ rising ? "rising" : "falling");
+}
+
+#define QC3_PULSES_FOR_6V 5
+#define QC3_PULSES_FOR_9V 20
+#define QC3_PULSES_FOR_12V 35
+static void smblib_hvdcp_adaptive_voltage_change(struct smb_charger *chg)
+{
+ int rc;
+ u8 stat;
+ int pulses;
+
+ power_supply_changed(chg->usb_main_psy);
+ if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_HVDCP) {
+ rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg,
+ "Couldn't read QC_CHANGE_STATUS rc=%d\n", rc);
+ return;
+ }
+
+ switch (stat & QC_2P0_STATUS_MASK) {
+ case QC_5V_BIT:
+ smblib_set_opt_freq_buck(chg,
+ chg->chg_freq.freq_5V);
+ break;
+ case QC_9V_BIT:
+ smblib_set_opt_freq_buck(chg,
+ chg->chg_freq.freq_9V);
+ break;
+ case QC_12V_BIT:
+ smblib_set_opt_freq_buck(chg,
+ chg->chg_freq.freq_12V);
+ break;
+ default:
+ smblib_set_opt_freq_buck(chg,
+ chg->chg_freq.freq_removal);
+ break;
+ }
+ }
+
+ if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_HVDCP_3) {
+ rc = smblib_get_pulse_cnt(chg, &pulses);
+ if (rc < 0) {
+ smblib_err(chg,
+ "Couldn't read QC_PULSE_COUNT rc=%d\n", rc);
+ return;
+ }
+
+ if (pulses < QC3_PULSES_FOR_6V)
+ smblib_set_opt_freq_buck(chg,
+ chg->chg_freq.freq_5V);
+ else if (pulses < QC3_PULSES_FOR_9V)
+ smblib_set_opt_freq_buck(chg,
+ chg->chg_freq.freq_6V_8V);
+ else if (pulses < QC3_PULSES_FOR_12V)
+ smblib_set_opt_freq_buck(chg,
+ chg->chg_freq.freq_9V);
+ else
+ smblib_set_opt_freq_buck(chg,
+ chg->chg_freq.freq_12V);
+ }
+}
+
+/* triggers when HVDCP 3.0 authentication has finished */
+static void smblib_handle_hvdcp_3p0_auth_done(struct smb_charger *chg,
+ bool rising)
+{
+ const struct apsd_result *apsd_result;
+ int rc;
+
+ if (!rising)
+ return;
+
+ if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
+ /*
+ * Disable AUTH_IRQ_EN_CFG_BIT to receive adapter voltage
+ * change interrupt.
+ */
+ rc = smblib_masked_write(chg,
+ USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
+ AUTH_IRQ_EN_CFG_BIT, 0);
+ if (rc < 0)
+ smblib_err(chg,
+ "Couldn't enable QC auth setting rc=%d\n", rc);
+ }
+
+ if (chg->mode == PARALLEL_MASTER)
+ vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, true, 0);
+
+ /* the APSD done handler will set the USB supply type */
+ apsd_result = smblib_get_apsd_result(chg);
+ if (get_effective_result(chg->hvdcp_hw_inov_dis_votable)) {
+ if (apsd_result->pst == POWER_SUPPLY_TYPE_USB_HVDCP) {
+ /* force HVDCP2 to 9V if INOV is disabled */
+ rc = smblib_masked_write(chg, CMD_HVDCP_2_REG,
+ FORCE_9V_BIT, FORCE_9V_BIT);
+ if (rc < 0)
+ smblib_err(chg,
+ "Couldn't force 9V HVDCP rc=%d\n", rc);
+ }
+ }
+
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-3p0-auth-done rising; %s detected\n",
+ apsd_result->name);
+}
+
+static void smblib_handle_hvdcp_check_timeout(struct smb_charger *chg,
+ bool rising, bool qc_charger)
+{
+ const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
+
+ /* Hold off PD only until hvdcp 2.0 detection timeout */
+ if (rising) {
+ vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+ false, 0);
+
+ /* enable HDC and ICL irq for QC2/3 charger */
+ if (qc_charger)
+ vote(chg->usb_irq_enable_votable, QC_VOTER, true, 0);
+
+ /*
+ * HVDCP detection timeout done
+ * If adapter is not QC2.0/QC3.0 - it is a plain old DCP.
+ */
+ if (!qc_charger && (apsd_result->bit & DCP_CHARGER_BIT))
+ /* enforce DCP ICL if specified */
+ vote(chg->usb_icl_votable, DCP_VOTER,
+ chg->dcp_icl_ua != -EINVAL, chg->dcp_icl_ua);
+ }
+
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: smblib_handle_hvdcp_check_timeout %s\n",
+ rising ? "rising" : "falling");
+}
+
+/* triggers when HVDCP is detected */
+static void smblib_handle_hvdcp_detect_done(struct smb_charger *chg,
+ bool rising)
+{
+ if (!rising)
+ return;
+
+ /* the APSD done handler will set the USB supply type */
+ cancel_delayed_work_sync(&chg->hvdcp_detect_work);
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-detect-done %s\n",
+ rising ? "rising" : "falling");
+}
+
+static void smblib_force_legacy_icl(struct smb_charger *chg, int pst)
+{
+ int typec_mode;
+ int rp_ua;
+
+ /* while PD is active it should have complete ICL control */
+ if (chg->pd_active)
+ return;
+
+ switch (pst) {
+ case POWER_SUPPLY_TYPE_USB:
+ /*
+ * USB_PSY will vote to increase the current to 500/900mA once
+ * enumeration is done. Ensure that USB_PSY has at least voted
+ * for 100mA before releasing the LEGACY_UNKNOWN vote
+ */
+ if (!is_client_vote_enabled(chg->usb_icl_votable,
+ USB_PSY_VOTER))
+ vote(chg->usb_icl_votable, USB_PSY_VOTER, true, 100000);
+ vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, false, 0);
+ break;
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 1500000);
+ break;
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ typec_mode = smblib_get_prop_typec_mode(chg);
+ rp_ua = get_rp_based_dcp_current(chg, typec_mode);
+ vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, rp_ua);
+ break;
+ case POWER_SUPPLY_TYPE_USB_FLOAT:
+ /*
+ * limit ICL to 100mA, the USB driver will enumerate to check
+ * if this is a SDP and appropriately set the current
+ */
+ vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 100000);
+ break;
+ case POWER_SUPPLY_TYPE_USB_HVDCP:
+ case POWER_SUPPLY_TYPE_USB_HVDCP_3:
+ vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 3000000);
+ break;
+ default:
+ smblib_err(chg, "Unknown APSD %d; forcing 500mA\n", pst);
+ vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 500000);
+ break;
+ }
+}
+
+static void smblib_notify_extcon_props(struct smb_charger *chg)
+{
+ union power_supply_propval val;
+
+ smblib_get_prop_typec_cc_orientation(chg, &val);
+ extcon_set_cable_state_(chg->extcon, EXTCON_USB_CC,
+ (val.intval == 2) ? 1 : 0);
+ extcon_set_cable_state_(chg->extcon, EXTCON_USB_SPEED, true);
+}
+
+static void smblib_notify_device_mode(struct smb_charger *chg, bool enable)
+{
+ if (enable)
+ smblib_notify_extcon_props(chg);
+
+ extcon_set_cable_state_(chg->extcon, EXTCON_USB, enable);
+}
+
+static void smblib_notify_usb_host(struct smb_charger *chg, bool enable)
+{
+ if (enable)
+ smblib_notify_extcon_props(chg);
+
+ extcon_set_cable_state_(chg->extcon, EXTCON_USB_HOST, enable);
+}
+
+#define HVDCP_DET_MS 2500
+static void smblib_handle_apsd_done(struct smb_charger *chg, bool rising)
+{
+ const struct apsd_result *apsd_result;
+
+ if (!rising)
+ return;
+
+ apsd_result = smblib_update_usb_type(chg);
+
+ if (!chg->typec_legacy_valid)
+ smblib_force_legacy_icl(chg, apsd_result->pst);
+
+ switch (apsd_result->bit) {
+ case SDP_CHARGER_BIT:
+ case CDP_CHARGER_BIT:
+ if (chg->micro_usb_mode)
+ extcon_set_cable_state_(chg->extcon, EXTCON_USB,
+ true);
+ if (chg->use_extcon)
+ smblib_notify_device_mode(chg, true);
+ case OCP_CHARGER_BIT:
+ case FLOAT_CHARGER_BIT:
+ /* if not DCP then no hvdcp timeout happens, Enable pd here. */
+ vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+ false, 0);
+ break;
+ case DCP_CHARGER_BIT:
+ if (chg->wa_flags & QC_CHARGER_DETECTION_WA_BIT)
+ schedule_delayed_work(&chg->hvdcp_detect_work,
+ msecs_to_jiffies(HVDCP_DET_MS));
+ break;
+ default:
+ break;
+ }
+
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: apsd-done rising; %s detected\n",
+ apsd_result->name);
+}
+
+irqreturn_t smblib_handle_usb_source_change(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+ int rc = 0;
+ u8 stat;
+
+ rc = smblib_read(chg, APSD_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+ smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", stat);
+
+ if (chg->micro_usb_mode && (stat & APSD_DTC_STATUS_DONE_BIT)
+ && !chg->uusb_apsd_rerun_done) {
+ /*
+ * Force re-run APSD to handle slow insertion related
+ * charger-mis-detection.
+ */
+ chg->uusb_apsd_rerun_done = true;
+ smblib_rerun_apsd(chg);
+ return IRQ_HANDLED;
+ }
+
+ smblib_handle_apsd_done(chg,
+ (bool)(stat & APSD_DTC_STATUS_DONE_BIT));
+
+ smblib_handle_hvdcp_detect_done(chg,
+ (bool)(stat & QC_CHARGER_BIT));
+
+ smblib_handle_hvdcp_check_timeout(chg,
+ (bool)(stat & HVDCP_CHECK_TIMEOUT_BIT),
+ (bool)(stat & QC_CHARGER_BIT));
+
+ smblib_handle_hvdcp_3p0_auth_done(chg,
+ (bool)(stat & QC_AUTH_DONE_STATUS_BIT));
+
+ smblib_handle_sdp_enumeration_done(chg,
+ (bool)(stat & ENUMERATION_DONE_BIT));
+
+ smblib_handle_slow_plugin_timeout(chg,
+ (bool)(stat & SLOW_PLUGIN_TIMEOUT_BIT));
+
+ smblib_hvdcp_adaptive_voltage_change(chg);
+
+ power_supply_changed(chg->usb_psy);
+
+ rc = smblib_read(chg, APSD_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+ smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", stat);
+
+ return IRQ_HANDLED;
+}
+
+static void typec_sink_insertion(struct smb_charger *chg)
+{
+ /* when a sink is inserted we should not wait on hvdcp timeout to
+ * enable pd
+ */
+ vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+ false, 0);
+ if (chg->use_extcon) {
+ smblib_notify_usb_host(chg, true);
+ chg->otg_present = true;
+ }
+}
+
+static void typec_sink_removal(struct smb_charger *chg)
+{
+ smblib_set_charge_param(chg, &chg->param.freq_boost,
+ chg->chg_freq.freq_above_otg_threshold);
+ chg->boost_current_ua = 0;
+}
+
+static void smblib_handle_typec_removal(struct smb_charger *chg)
+{
+ int rc;
+ struct smb_irq_data *data;
+ struct storm_watch *wdata;
+
+ chg->cc2_detach_wa_active = false;
+
+ rc = smblib_request_dpdm(chg, false);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc);
+
+ if (chg->wa_flags & BOOST_BACK_WA) {
+ data = chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data;
+ if (data) {
+ wdata = &data->storm_data;
+ update_storm_count(wdata, WEAK_CHG_STORM_COUNT);
+ vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0);
+ vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER,
+ false, 0);
+ }
+ }
+
+ /* reset APSD voters */
+ vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, false, 0);
+ vote(chg->apsd_disable_votable, PD_VOTER, false, 0);
+
+ cancel_delayed_work_sync(&chg->pl_enable_work);
+ cancel_delayed_work_sync(&chg->hvdcp_detect_work);
+
+ /* reset input current limit voters */
+ vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 100000);
+ vote(chg->usb_icl_votable, PD_VOTER, false, 0);
+ vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
+ vote(chg->usb_icl_votable, DCP_VOTER, false, 0);
+ vote(chg->usb_icl_votable, PL_USBIN_USBIN_VOTER, false, 0);
+ vote(chg->usb_icl_votable, SW_QC3_VOTER, false, 0);
+
+ /* reset hvdcp voters */
+ vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER, true, 0);
+ vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER, true, 0);
+
+ /* reset power delivery voters */
+ vote(chg->pd_allowed_votable, PD_VOTER, false, 0);
+ vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, true, 0);
+ vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, true, 0);
+
+ /* reset usb irq voters */
+ vote(chg->usb_irq_enable_votable, PD_VOTER, false, 0);
+ vote(chg->usb_irq_enable_votable, QC_VOTER, false, 0);
+
+ /* reset parallel voters */
+ vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0);
+ vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0);
+ vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0);
+ vote(chg->awake_votable, PL_DELAY_VOTER, false, 0);
+
+ chg->vconn_attempts = 0;
+ chg->otg_attempts = 0;
+ chg->pulse_cnt = 0;
+ chg->usb_icl_delta_ua = 0;
+ chg->voltage_min_uv = MICRO_5V;
+ chg->voltage_max_uv = MICRO_5V;
+ chg->pd_active = 0;
+ chg->pd_hard_reset = 0;
+ chg->typec_legacy_valid = false;
+
+ /* write back the default FLOAT charger configuration */
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+ (u8)FLOAT_OPTIONS_MASK, chg->float_cfg);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't write float charger options rc=%d\n",
+ rc);
+
+ /* reset back to 120mS tCC debounce */
+ rc = smblib_masked_write(chg, MISC_CFG_REG, TCC_DEBOUNCE_20MS_BIT, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't set 120mS tCC debounce rc=%d\n", rc);
+
+ /* enable APSD CC trigger for next insertion */
+ rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
+ APSD_START_ON_CC_BIT, APSD_START_ON_CC_BIT);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't enable APSD_START_ON_CC rc=%d\n", rc);
+
+ if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
+ /* re-enable AUTH_IRQ_EN_CFG_BIT */
+ rc = smblib_masked_write(chg,
+ USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
+ AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT);
+ if (rc < 0)
+ smblib_err(chg,
+ "Couldn't enable QC auth setting rc=%d\n", rc);
+ }
+
+ /* reconfigure allowed voltage for HVDCP */
+ rc = smblib_set_adapter_allowance(chg,
+ USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n",
+ rc);
+
+ /* enable DRP */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_POWER_ROLE_CMD_MASK, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't enable DRP rc=%d\n", rc);
+
+ /* HW controlled CC_OUT */
+ rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG,
+ TYPEC_SPARE_CFG_BIT, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't enable HW cc_out rc=%d\n", rc);
+
+ /* restore crude sensor */
+ rc = smblib_write(chg, TM_IO_DTEST4_SEL, 0xA5);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't restore crude sensor rc=%d\n", rc);
+
+ mutex_lock(&chg->vconn_oc_lock);
+ if (!chg->vconn_en)
+ goto unlock;
+
+ smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ VCONN_EN_VALUE_BIT, 0);
+ chg->vconn_en = false;
+
+unlock:
+ mutex_unlock(&chg->vconn_oc_lock);
+
+ /* clear exit sink based on cc */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ EXIT_SNK_BASED_ON_CC_BIT, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't clear exit_sink_based_on_cc rc=%d\n",
+ rc);
+
+ typec_sink_removal(chg);
+ smblib_update_usb_type(chg);
+
+ if (chg->use_extcon) {
+ if (chg->otg_present)
+ smblib_notify_usb_host(chg, false);
+ else
+ smblib_notify_device_mode(chg, false);
+ }
+ chg->otg_present = false;
+}
+
+static void smblib_handle_typec_insertion(struct smb_charger *chg)
+{
+ int rc;
+
+ vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, false, 0);
+
+ /* disable APSD CC trigger since CC is attached */
+ rc = smblib_masked_write(chg, TYPE_C_CFG_REG, APSD_START_ON_CC_BIT, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't disable APSD_START_ON_CC rc=%d\n",
+ rc);
+
+ if (chg->typec_status[3] & UFP_DFP_MODE_STATUS_BIT) {
+ typec_sink_insertion(chg);
+ } else {
+ rc = smblib_request_dpdm(chg, true);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc);
+ typec_sink_removal(chg);
+ }
+}
+
+static void smblib_handle_rp_change(struct smb_charger *chg, int typec_mode)
+{
+ int rp_ua;
+ const struct apsd_result *apsd = smblib_get_apsd_result(chg);
+
+ if ((apsd->pst != POWER_SUPPLY_TYPE_USB_DCP)
+ && (apsd->pst != POWER_SUPPLY_TYPE_USB_FLOAT))
+ return;
+
+ /*
+ * if APSD indicates FLOAT and the USB stack had detected SDP,
+ * do not respond to Rp changes as we do not confirm that its
+ * a legacy cable
+ */
+ if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB)
+ return;
+ /*
+ * We want the ICL vote @ 100mA for a FLOAT charger
+ * until the detection by the USB stack is complete.
+ * Ignore the Rp changes unless there is a
+ * pre-existing valid vote.
+ */
+ if (apsd->pst == POWER_SUPPLY_TYPE_USB_FLOAT &&
+ get_client_vote(chg->usb_icl_votable,
+ LEGACY_UNKNOWN_VOTER) <= 100000)
+ return;
+
+ /*
+ * handle Rp change for DCP/FLOAT/OCP.
+ * Update the current only if the Rp is different from
+ * the last Rp value.
+ */
+ smblib_dbg(chg, PR_MISC, "CC change old_mode=%d new_mode=%d\n",
+ chg->typec_mode, typec_mode);
+
+ rp_ua = get_rp_based_dcp_current(chg, typec_mode);
+ vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, rp_ua);
+}
+
+static void smblib_handle_typec_cc_state_change(struct smb_charger *chg)
+{
+ int typec_mode;
+
+ if (chg->pr_swap_in_progress)
+ return;
+
+ typec_mode = smblib_get_prop_typec_mode(chg);
+ if (chg->typec_present && (typec_mode != chg->typec_mode))
+ smblib_handle_rp_change(chg, typec_mode);
+
+ chg->typec_mode = typec_mode;
+
+ if (!chg->typec_present && chg->typec_mode != POWER_SUPPLY_TYPEC_NONE) {
+ chg->typec_present = true;
+ smblib_dbg(chg, PR_MISC, "TypeC %s insertion\n",
+ smblib_typec_mode_name[chg->typec_mode]);
+ smblib_handle_typec_insertion(chg);
+ } else if (chg->typec_present &&
+ chg->typec_mode == POWER_SUPPLY_TYPEC_NONE) {
+ chg->typec_present = false;
+ smblib_dbg(chg, PR_MISC, "TypeC removal\n");
+ smblib_handle_typec_removal(chg);
+ }
+
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: cc-state-change; Type-C %s detected\n",
+ smblib_typec_mode_name[chg->typec_mode]);
+}
+
+static void smblib_usb_typec_change(struct smb_charger *chg)
+{
+ int rc;
+
+ rc = smblib_multibyte_read(chg, TYPE_C_STATUS_1_REG,
+ chg->typec_status, 5);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't cache USB Type-C status rc=%d\n", rc);
+ return;
+ }
+
+ smblib_handle_typec_cc_state_change(chg);
+
+ if (chg->typec_status[3] & TYPEC_VBUS_ERROR_STATUS_BIT)
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: vbus-error\n");
+
+ if (chg->typec_status[3] & TYPEC_VCONN_OVERCURR_STATUS_BIT)
+ schedule_work(&chg->vconn_oc_work);
+
+ power_supply_changed(chg->usb_psy);
+}
+
+irqreturn_t smblib_handle_usb_typec_change(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+
+ if (chg->micro_usb_mode) {
+ cancel_delayed_work_sync(&chg->uusb_otg_work);
+ vote(chg->awake_votable, OTG_DELAY_VOTER, true, 0);
+ smblib_dbg(chg, PR_INTERRUPT, "Scheduling OTG work\n");
+ schedule_delayed_work(&chg->uusb_otg_work,
+ msecs_to_jiffies(chg->otg_delay_ms));
+ return IRQ_HANDLED;
+ }
+
+ if (chg->cc2_detach_wa_active || chg->typec_en_dis_active) {
+ smblib_dbg(chg, PR_INTERRUPT, "Ignoring since %s active\n",
+ chg->cc2_detach_wa_active ?
+ "cc2_detach_wa" : "typec_en_dis");
+ return IRQ_HANDLED;
+ }
+
+ mutex_lock(&chg->lock);
+ smblib_usb_typec_change(chg);
+ mutex_unlock(&chg->lock);
+ return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_dc_plugin(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+
+ power_supply_changed(chg->dc_psy);
+ return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_high_duty_cycle(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+
+ chg->is_hdc = true;
+ schedule_delayed_work(&chg->clear_hdc_work, msecs_to_jiffies(60));
+
+ return IRQ_HANDLED;
+}
+
+static void smblib_bb_removal_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ bb_removal_work.work);
+
+ vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0);
+ vote(chg->awake_votable, BOOST_BACK_VOTER, false, 0);
+}
+
+#define BOOST_BACK_UNVOTE_DELAY_MS 750
+#define BOOST_BACK_STORM_COUNT 3
+#define WEAK_CHG_STORM_COUNT 8
+irqreturn_t smblib_handle_switcher_power_ok(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+ struct storm_watch *wdata = &irq_data->storm_data;
+ int rc, usb_icl;
+ u8 stat;
+
+ if (!(chg->wa_flags & BOOST_BACK_WA))
+ return IRQ_HANDLED;
+
+ rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+
+ /* skip suspending input if its already suspended by some other voter */
+ usb_icl = get_effective_result(chg->usb_icl_votable);
+ if ((stat & USE_USBIN_BIT) && usb_icl >= 0 && usb_icl < USBIN_25MA)
+ return IRQ_HANDLED;
+
+ if (stat & USE_DCIN_BIT)
+ return IRQ_HANDLED;
+
+ if (is_storming(&irq_data->storm_data)) {
+ /* This could be a weak charger reduce ICL */
+ if (!is_client_vote_enabled(chg->usb_icl_votable,
+ WEAK_CHARGER_VOTER)) {
+ smblib_err(chg,
+ "Weak charger detected: voting %dmA ICL\n",
+ *chg->weak_chg_icl_ua / 1000);
+ vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER,
+ true, *chg->weak_chg_icl_ua);
+ /*
+ * reset storm data and set the storm threshold
+ * to 3 for reverse boost detection.
+ */
+ update_storm_count(wdata, BOOST_BACK_STORM_COUNT);
+ } else {
+ smblib_err(chg,
+ "Reverse boost detected: voting 0mA to suspend input\n");
+ vote(chg->usb_icl_votable, BOOST_BACK_VOTER, true, 0);
+ vote(chg->awake_votable, BOOST_BACK_VOTER, true, 0);
+ /*
+ * Remove the boost-back vote after a delay, to avoid
+ * permanently suspending the input if the boost-back
+ * condition is unintentionally hit.
+ */
+ schedule_delayed_work(&chg->bb_removal_work,
+ msecs_to_jiffies(BOOST_BACK_UNVOTE_DELAY_MS));
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_wdog_bark(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb_charger *chg = irq_data->parent_data;
+ int rc;
+
+ smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+
+ rc = smblib_write(chg, BARK_BITE_WDOG_PET_REG, BARK_BITE_WDOG_PET_BIT);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't pet the dog rc=%d\n", rc);
+
+ if (chg->step_chg_enabled || chg->sw_jeita_enabled)
+ power_supply_changed(chg->batt_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**************
+ * Additional USB PSY getters/setters
+ * that call interrupt functions
+***************/
+
+int smblib_get_prop_pr_swap_in_progress(struct smb_charger *chg,
+ union power_supply_propval *val)
+{
+ val->intval = chg->pr_swap_in_progress;
+ return 0;
+}
+
+int smblib_set_prop_pr_swap_in_progress(struct smb_charger *chg,
+ const union power_supply_propval *val)
+{
+ int rc;
+
+ chg->pr_swap_in_progress = val->intval;
+ /*
+ * call the cc changed irq to handle real removals while
+ * PR_SWAP was in progress
+ */
+ smblib_usb_typec_change(chg);
+ rc = smblib_masked_write(chg, MISC_CFG_REG, TCC_DEBOUNCE_20MS_BIT,
+ val->intval ? TCC_DEBOUNCE_20MS_BIT : 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't set tCC debounce rc=%d\n", rc);
+ return 0;
+}
+
+/***************
+ * Work Queues *
+ ***************/
+static void smblib_uusb_otg_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ uusb_otg_work.work);
+ int rc;
+ u8 stat;
+ bool otg;
+
+ rc = smblib_read(chg, TYPE_C_STATUS_3_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read TYPE_C_STATUS_3 rc=%d\n", rc);
+ goto out;
+ }
+
+ otg = !!(stat & (U_USB_GND_NOVBUS_BIT | U_USB_GND_BIT));
+ extcon_set_cable_state_(chg->extcon, EXTCON_USB_HOST, otg);
+ smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_3 = 0x%02x OTG=%d\n",
+ stat, otg);
+ power_supply_changed(chg->usb_psy);
+
+out:
+ vote(chg->awake_votable, OTG_DELAY_VOTER, false, 0);
+}
+
+
+static void smblib_hvdcp_detect_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ hvdcp_detect_work.work);
+
+ vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+ false, 0);
+ power_supply_changed(chg->usb_psy);
+}
+
+static void bms_update_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ bms_update_work);
+
+ smblib_suspend_on_debug_battery(chg);
+
+ if (chg->batt_psy)
+ power_supply_changed(chg->batt_psy);
+}
+
+static void clear_hdc_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ clear_hdc_work.work);
+
+ chg->is_hdc = 0;
+}
+
+static void rdstd_cc2_detach_work(struct work_struct *work)
+{
+ int rc;
+ u8 stat4, stat5;
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ rdstd_cc2_detach_work);
+
+ if (!chg->cc2_detach_wa_active)
+ return;
+
+ /*
+ * WA steps -
+ * 1. Enable both UFP and DFP, wait for 10ms.
+ * 2. Disable DFP, wait for 30ms.
+ * 3. Removal detected if both TYPEC_DEBOUNCE_DONE_STATUS
+ * and TIMER_STAGE bits are gone, otherwise repeat all by
+ * work rescheduling.
+ * Note, work will be cancelled when USB_PLUGIN rises.
+ */
+
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ UFP_EN_CMD_BIT | DFP_EN_CMD_BIT,
+ UFP_EN_CMD_BIT | DFP_EN_CMD_BIT);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't write TYPE_C_CTRL_REG rc=%d\n", rc);
+ return;
+ }
+
+ usleep_range(10000, 11000);
+
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ UFP_EN_CMD_BIT | DFP_EN_CMD_BIT,
+ UFP_EN_CMD_BIT);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't write TYPE_C_CTRL_REG rc=%d\n", rc);
+ return;
+ }
+
+ usleep_range(30000, 31000);
+
+ rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat4);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
+ return;
+ }
+
+ rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat5);
+ if (rc < 0) {
+ smblib_err(chg,
+ "Couldn't read TYPE_C_STATUS_5_REG rc=%d\n", rc);
+ return;
+ }
+
+ if ((stat4 & TYPEC_DEBOUNCE_DONE_STATUS_BIT)
+ || (stat5 & TIMER_STAGE_2_BIT)) {
+ smblib_dbg(chg, PR_MISC, "rerunning DD=%d TS2BIT=%d\n",
+ (int)(stat4 & TYPEC_DEBOUNCE_DONE_STATUS_BIT),
+ (int)(stat5 & TIMER_STAGE_2_BIT));
+ goto rerun;
+ }
+
+ smblib_dbg(chg, PR_MISC, "Bingo CC2 Removal detected\n");
+ chg->cc2_detach_wa_active = false;
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ EXIT_SNK_BASED_ON_CC_BIT, 0);
+ smblib_reg_block_restore(chg, cc2_detach_settings);
+ mutex_lock(&chg->lock);
+ smblib_usb_typec_change(chg);
+ mutex_unlock(&chg->lock);
+ return;
+
+rerun:
+ schedule_work(&chg->rdstd_cc2_detach_work);
+}
+
+static void smblib_otg_oc_exit(struct smb_charger *chg, bool success)
+{
+ int rc;
+
+ chg->otg_attempts = 0;
+ if (!success) {
+ smblib_err(chg, "OTG soft start failed\n");
+ chg->otg_en = false;
+ }
+
+ smblib_dbg(chg, PR_OTG, "enabling VBUS < 1V check\n");
+ rc = smblib_masked_write(chg, OTG_CFG_REG,
+ QUICKSTART_OTG_FASTROLESWAP_BIT, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't enable VBUS < 1V check rc=%d\n", rc);
+}
+
+#define MAX_OC_FALLING_TRIES 10
+static void smblib_otg_oc_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ otg_oc_work);
+ int rc, i;
+ u8 stat;
+
+ if (!chg->vbus_vreg || !chg->vbus_vreg->rdev)
+ return;
+
+ smblib_err(chg, "over-current detected on VBUS\n");
+ mutex_lock(&chg->otg_oc_lock);
+ if (!chg->otg_en)
+ goto unlock;
+
+ smblib_dbg(chg, PR_OTG, "disabling VBUS < 1V check\n");
+ smblib_masked_write(chg, OTG_CFG_REG,
+ QUICKSTART_OTG_FASTROLESWAP_BIT,
+ QUICKSTART_OTG_FASTROLESWAP_BIT);
+
+ /*
+ * If 500ms has passed and another over-current interrupt has not
+ * triggered then it is likely that the software based soft start was
+ * successful and the VBUS < 1V restriction should be re-enabled.
+ */
+ schedule_delayed_work(&chg->otg_ss_done_work, msecs_to_jiffies(500));
+
+ rc = _smblib_vbus_regulator_disable(chg->vbus_vreg->rdev);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't disable VBUS rc=%d\n", rc);
+ goto unlock;
+ }
+
+ if (++chg->otg_attempts > OTG_MAX_ATTEMPTS) {
+ cancel_delayed_work_sync(&chg->otg_ss_done_work);
+ smblib_err(chg, "OTG failed to enable after %d attempts\n",
+ chg->otg_attempts - 1);
+ smblib_otg_oc_exit(chg, false);
+ goto unlock;
+ }
+
+ /*
+ * The real time status should go low within 10ms. Poll every 1-2ms to
+ * minimize the delay when re-enabling OTG.
+ */
+ for (i = 0; i < MAX_OC_FALLING_TRIES; ++i) {
+ usleep_range(1000, 2000);
+ rc = smblib_read(chg, OTG_BASE + INT_RT_STS_OFFSET, &stat);
+ if (rc >= 0 && !(stat & OTG_OVERCURRENT_RT_STS_BIT))
+ break;
+ }
+
+ if (i >= MAX_OC_FALLING_TRIES) {
+ cancel_delayed_work_sync(&chg->otg_ss_done_work);
+ smblib_err(chg, "OTG OC did not fall after %dms\n",
+ 2 * MAX_OC_FALLING_TRIES);
+ smblib_otg_oc_exit(chg, false);
+ goto unlock;
+ }
+
+ smblib_dbg(chg, PR_OTG, "OTG OC fell after %dms\n", 2 * i + 1);
+ rc = _smblib_vbus_regulator_enable(chg->vbus_vreg->rdev);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't enable VBUS rc=%d\n", rc);
+ goto unlock;
+ }
+
+unlock:
+ mutex_unlock(&chg->otg_oc_lock);
+}
+
+static void smblib_vconn_oc_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ vconn_oc_work);
+ int rc, i;
+ u8 stat;
+
+ if (chg->micro_usb_mode)
+ return;
+
+ smblib_err(chg, "over-current detected on VCONN\n");
+ if (!chg->vconn_vreg || !chg->vconn_vreg->rdev)
+ return;
+
+ mutex_lock(&chg->vconn_oc_lock);
+ rc = _smblib_vconn_regulator_disable(chg->vconn_vreg->rdev);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't disable VCONN rc=%d\n", rc);
+ goto unlock;
+ }
+
+ if (++chg->vconn_attempts > VCONN_MAX_ATTEMPTS) {
+ smblib_err(chg, "VCONN failed to enable after %d attempts\n",
+ chg->otg_attempts - 1);
+ chg->vconn_en = false;
+ chg->vconn_attempts = 0;
+ goto unlock;
+ }
+
+ /*
+ * The real time status should go low within 10ms. Poll every 1-2ms to
+ * minimize the delay when re-enabling OTG.
+ */
+ for (i = 0; i < MAX_OC_FALLING_TRIES; ++i) {
+ usleep_range(1000, 2000);
+ rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat);
+ if (rc >= 0 && !(stat & TYPEC_VCONN_OVERCURR_STATUS_BIT))
+ break;
+ }
+
+ if (i >= MAX_OC_FALLING_TRIES) {
+ smblib_err(chg, "VCONN OC did not fall after %dms\n",
+ 2 * MAX_OC_FALLING_TRIES);
+ chg->vconn_en = false;
+ chg->vconn_attempts = 0;
+ goto unlock;
+ }
+
+ smblib_dbg(chg, PR_OTG, "VCONN OC fell after %dms\n", 2 * i + 1);
+ if (++chg->vconn_attempts > VCONN_MAX_ATTEMPTS) {
+ smblib_err(chg, "VCONN failed to enable after %d attempts\n",
+ chg->vconn_attempts - 1);
+ chg->vconn_en = false;
+ goto unlock;
+ }
+
+ rc = _smblib_vconn_regulator_enable(chg->vconn_vreg->rdev);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't enable VCONN rc=%d\n", rc);
+ goto unlock;
+ }
+
+unlock:
+ mutex_unlock(&chg->vconn_oc_lock);
+}
+
+static void smblib_otg_ss_done_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ otg_ss_done_work.work);
+ int rc;
+ bool success = false;
+ u8 stat;
+
+ mutex_lock(&chg->otg_oc_lock);
+ rc = smblib_read(chg, OTG_STATUS_REG, &stat);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't read OTG status rc=%d\n", rc);
+ else if (stat & BOOST_SOFTSTART_DONE_BIT)
+ success = true;
+
+ smblib_otg_oc_exit(chg, success);
+ mutex_unlock(&chg->otg_oc_lock);
+}
+
+static void smblib_icl_change_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ icl_change_work.work);
+ int rc, settled_ua;
+
+ rc = smblib_get_charge_param(chg, &chg->param.icl_stat, &settled_ua);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc);
+ return;
+ }
+
+ power_supply_changed(chg->usb_main_psy);
+
+ smblib_dbg(chg, PR_INTERRUPT, "icl_settled=%d\n", settled_ua);
+}
+
+static void smblib_pl_enable_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ pl_enable_work.work);
+
+ smblib_dbg(chg, PR_PARALLEL, "timer expired, enabling parallel\n");
+ vote(chg->pl_disable_votable, PL_DELAY_VOTER, false, 0);
+ vote(chg->awake_votable, PL_DELAY_VOTER, false, 0);
+}
+
+static void smblib_legacy_detection_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ legacy_detection_work);
+ int rc;
+ u8 stat;
+ bool legacy, rp_high;
+
+ mutex_lock(&chg->lock);
+ chg->typec_en_dis_active = 1;
+ smblib_dbg(chg, PR_MISC, "running legacy unknown workaround\n");
+ rc = smblib_masked_write(chg,
+ TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_DISABLE_CMD_BIT,
+ TYPEC_DISABLE_CMD_BIT);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't disable type-c rc=%d\n", rc);
+
+ /* wait for the adapter to turn off VBUS */
+ msleep(500);
+
+ rc = smblib_masked_write(chg,
+ TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_DISABLE_CMD_BIT, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't enable type-c rc=%d\n", rc);
+
+ /* wait for type-c detection to complete */
+ msleep(100);
+
+ rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't read typec stat5 rc = %d\n", rc);
+ goto unlock;
+ }
+
+ chg->typec_legacy_valid = true;
+ vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, false, 0);
+ legacy = stat & TYPEC_LEGACY_CABLE_STATUS_BIT;
+ rp_high = chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_HIGH;
+ if (!legacy || !rp_high)
+ vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER,
+ false, 0);
+
+unlock:
+ chg->typec_en_dis_active = 0;
+ smblib_usb_typec_change(chg);
+ mutex_unlock(&chg->lock);
+}
+
+static int smblib_create_votables(struct smb_charger *chg)
+{
+ int rc = 0;
+
+ chg->fcc_votable = find_votable("FCC");
+ if (chg->fcc_votable == NULL) {
+ rc = -EINVAL;
+ smblib_err(chg, "Couldn't find FCC votable rc=%d\n", rc);
+ return rc;
+ }
+
+ chg->fv_votable = find_votable("FV");
+ if (chg->fv_votable == NULL) {
+ rc = -EINVAL;
+ smblib_err(chg, "Couldn't find FV votable rc=%d\n", rc);
+ return rc;
+ }
+
+ chg->usb_icl_votable = find_votable("USB_ICL");
+ if (!chg->usb_icl_votable) {
+ rc = -EINVAL;
+ smblib_err(chg, "Couldn't find USB_ICL votable rc=%d\n", rc);
+ return rc;
+ }
+
+ chg->pl_disable_votable = find_votable("PL_DISABLE");
+ if (chg->pl_disable_votable == NULL) {
+ rc = -EINVAL;
+ smblib_err(chg, "Couldn't find votable PL_DISABLE rc=%d\n", rc);
+ return rc;
+ }
+
+ chg->pl_enable_votable_indirect = find_votable("PL_ENABLE_INDIRECT");
+ if (chg->pl_enable_votable_indirect == NULL) {
+ rc = -EINVAL;
+ smblib_err(chg,
+ "Couldn't find votable PL_ENABLE_INDIRECT rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0);
+
+ chg->dc_suspend_votable = create_votable("DC_SUSPEND", VOTE_SET_ANY,
+ smblib_dc_suspend_vote_callback,
+ chg);
+ if (IS_ERR(chg->dc_suspend_votable)) {
+ rc = PTR_ERR(chg->dc_suspend_votable);
+ return rc;
+ }
+
+ chg->dc_icl_votable = create_votable("DC_ICL", VOTE_MIN,
+ smblib_dc_icl_vote_callback,
+ chg);
+ if (IS_ERR(chg->dc_icl_votable)) {
+ rc = PTR_ERR(chg->dc_icl_votable);
+ return rc;
+ }
+
+ chg->pd_disallowed_votable_indirect
+ = create_votable("PD_DISALLOWED_INDIRECT", VOTE_SET_ANY,
+ smblib_pd_disallowed_votable_indirect_callback, chg);
+ if (IS_ERR(chg->pd_disallowed_votable_indirect)) {
+ rc = PTR_ERR(chg->pd_disallowed_votable_indirect);
+ return rc;
+ }
+
+ chg->pd_allowed_votable = create_votable("PD_ALLOWED",
+ VOTE_SET_ANY, NULL, NULL);
+ if (IS_ERR(chg->pd_allowed_votable)) {
+ rc = PTR_ERR(chg->pd_allowed_votable);
+ return rc;
+ }
+
+ chg->awake_votable = create_votable("AWAKE", VOTE_SET_ANY,
+ smblib_awake_vote_callback,
+ chg);
+ if (IS_ERR(chg->awake_votable)) {
+ rc = PTR_ERR(chg->awake_votable);
+ return rc;
+ }
+
+ chg->chg_disable_votable = create_votable("CHG_DISABLE", VOTE_SET_ANY,
+ smblib_chg_disable_vote_callback,
+ chg);
+ if (IS_ERR(chg->chg_disable_votable)) {
+ rc = PTR_ERR(chg->chg_disable_votable);
+ return rc;
+ }
+
+
+ chg->hvdcp_disable_votable_indirect = create_votable(
+ "HVDCP_DISABLE_INDIRECT",
+ VOTE_SET_ANY,
+ smblib_hvdcp_disable_indirect_vote_callback,
+ chg);
+ if (IS_ERR(chg->hvdcp_disable_votable_indirect)) {
+ rc = PTR_ERR(chg->hvdcp_disable_votable_indirect);
+ return rc;
+ }
+
+ chg->hvdcp_enable_votable = create_votable("HVDCP_ENABLE",
+ VOTE_SET_ANY,
+ smblib_hvdcp_enable_vote_callback,
+ chg);
+ if (IS_ERR(chg->hvdcp_enable_votable)) {
+ rc = PTR_ERR(chg->hvdcp_enable_votable);
+ return rc;
+ }
+
+ chg->apsd_disable_votable = create_votable("APSD_DISABLE",
+ VOTE_SET_ANY,
+ smblib_apsd_disable_vote_callback,
+ chg);
+ if (IS_ERR(chg->apsd_disable_votable)) {
+ rc = PTR_ERR(chg->apsd_disable_votable);
+ return rc;
+ }
+
+ chg->hvdcp_hw_inov_dis_votable = create_votable("HVDCP_HW_INOV_DIS",
+ VOTE_SET_ANY,
+ smblib_hvdcp_hw_inov_dis_vote_callback,
+ chg);
+ if (IS_ERR(chg->hvdcp_hw_inov_dis_votable)) {
+ rc = PTR_ERR(chg->hvdcp_hw_inov_dis_votable);
+ return rc;
+ }
+
+ chg->usb_irq_enable_votable = create_votable("USB_IRQ_DISABLE",
+ VOTE_SET_ANY,
+ smblib_usb_irq_enable_vote_callback,
+ chg);
+ if (IS_ERR(chg->usb_irq_enable_votable)) {
+ rc = PTR_ERR(chg->usb_irq_enable_votable);
+ return rc;
+ }
+
+ chg->typec_irq_disable_votable = create_votable("TYPEC_IRQ_DISABLE",
+ VOTE_SET_ANY,
+ smblib_typec_irq_disable_vote_callback,
+ chg);
+ if (IS_ERR(chg->typec_irq_disable_votable)) {
+ rc = PTR_ERR(chg->typec_irq_disable_votable);
+ return rc;
+ }
+
+ return rc;
+}
+
+static void smblib_destroy_votables(struct smb_charger *chg)
+{
+ if (chg->dc_suspend_votable)
+ destroy_votable(chg->dc_suspend_votable);
+ if (chg->usb_icl_votable)
+ destroy_votable(chg->usb_icl_votable);
+ if (chg->dc_icl_votable)
+ destroy_votable(chg->dc_icl_votable);
+ if (chg->pd_disallowed_votable_indirect)
+ destroy_votable(chg->pd_disallowed_votable_indirect);
+ if (chg->pd_allowed_votable)
+ destroy_votable(chg->pd_allowed_votable);
+ if (chg->awake_votable)
+ destroy_votable(chg->awake_votable);
+ if (chg->chg_disable_votable)
+ destroy_votable(chg->chg_disable_votable);
+ if (chg->apsd_disable_votable)
+ destroy_votable(chg->apsd_disable_votable);
+ if (chg->hvdcp_hw_inov_dis_votable)
+ destroy_votable(chg->hvdcp_hw_inov_dis_votable);
+ if (chg->typec_irq_disable_votable)
+ destroy_votable(chg->typec_irq_disable_votable);
+}
+
+static void smblib_iio_deinit(struct smb_charger *chg)
+{
+ if (!IS_ERR_OR_NULL(chg->iio.temp_chan))
+ iio_channel_release(chg->iio.temp_chan);
+ if (!IS_ERR_OR_NULL(chg->iio.temp_max_chan))
+ iio_channel_release(chg->iio.temp_max_chan);
+ if (!IS_ERR_OR_NULL(chg->iio.usbin_i_chan))
+ iio_channel_release(chg->iio.usbin_i_chan);
+ if (!IS_ERR_OR_NULL(chg->iio.usbin_v_chan))
+ iio_channel_release(chg->iio.usbin_v_chan);
+ if (!IS_ERR_OR_NULL(chg->iio.batt_i_chan))
+ iio_channel_release(chg->iio.batt_i_chan);
+}
+
+int smblib_init(struct smb_charger *chg)
+{
+ int rc = 0;
+
+ mutex_init(&chg->lock);
+ mutex_init(&chg->write_lock);
+ mutex_init(&chg->otg_oc_lock);
+ mutex_init(&chg->vconn_oc_lock);
+ INIT_WORK(&chg->bms_update_work, bms_update_work);
+ INIT_WORK(&chg->rdstd_cc2_detach_work, rdstd_cc2_detach_work);
+ INIT_DELAYED_WORK(&chg->hvdcp_detect_work, smblib_hvdcp_detect_work);
+ INIT_DELAYED_WORK(&chg->clear_hdc_work, clear_hdc_work);
+ INIT_WORK(&chg->otg_oc_work, smblib_otg_oc_work);
+ INIT_WORK(&chg->vconn_oc_work, smblib_vconn_oc_work);
+ INIT_DELAYED_WORK(&chg->otg_ss_done_work, smblib_otg_ss_done_work);
+ INIT_DELAYED_WORK(&chg->icl_change_work, smblib_icl_change_work);
+ INIT_DELAYED_WORK(&chg->pl_enable_work, smblib_pl_enable_work);
+ INIT_WORK(&chg->legacy_detection_work, smblib_legacy_detection_work);
+ INIT_DELAYED_WORK(&chg->uusb_otg_work, smblib_uusb_otg_work);
+ INIT_DELAYED_WORK(&chg->bb_removal_work, smblib_bb_removal_work);
+ chg->fake_capacity = -EINVAL;
+ chg->fake_input_current_limited = -EINVAL;
+
+ switch (chg->mode) {
+ case PARALLEL_MASTER:
+ rc = qcom_batt_init();
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't init qcom_batt_init rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = qcom_step_chg_init(chg->step_chg_enabled,
+ chg->sw_jeita_enabled);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't init qcom_step_chg_init rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smblib_create_votables(chg);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't create votables rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smblib_register_notifier(chg);
+ if (rc < 0) {
+ smblib_err(chg,
+ "Couldn't register notifier rc=%d\n", rc);
+ return rc;
+ }
+
+ chg->bms_psy = power_supply_get_by_name("bms");
+ chg->pl.psy = power_supply_get_by_name("parallel");
+ break;
+ case PARALLEL_SLAVE:
+ break;
+ default:
+ smblib_err(chg, "Unsupported mode %d\n", chg->mode);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+int smblib_deinit(struct smb_charger *chg)
+{
+ switch (chg->mode) {
+ case PARALLEL_MASTER:
+ cancel_work_sync(&chg->bms_update_work);
+ cancel_work_sync(&chg->rdstd_cc2_detach_work);
+ cancel_delayed_work_sync(&chg->hvdcp_detect_work);
+ cancel_delayed_work_sync(&chg->clear_hdc_work);
+ cancel_work_sync(&chg->otg_oc_work);
+ cancel_work_sync(&chg->vconn_oc_work);
+ cancel_delayed_work_sync(&chg->otg_ss_done_work);
+ cancel_delayed_work_sync(&chg->icl_change_work);
+ cancel_delayed_work_sync(&chg->pl_enable_work);
+ cancel_work_sync(&chg->legacy_detection_work);
+ cancel_delayed_work_sync(&chg->uusb_otg_work);
+ cancel_delayed_work_sync(&chg->bb_removal_work);
+ power_supply_unreg_notifier(&chg->nb);
+ smblib_destroy_votables(chg);
+ qcom_step_chg_deinit();
+ qcom_batt_deinit();
+ break;
+ case PARALLEL_SLAVE:
+ break;
+ default:
+ smblib_err(chg, "Unsupported mode %d\n", chg->mode);
+ return -EINVAL;
+ }
+
+ smblib_iio_deinit(chg);
+
+ return 0;
+}
diff --git a/drivers/power/supply/qcom/smb-lib.h b/drivers/power/supply/qcom/smb-lib.h
new file mode 100644
index 000000000000..a89d09711ec8
--- /dev/null
+++ b/drivers/power/supply/qcom/smb-lib.h
@@ -0,0 +1,523 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 __SMB2_CHARGER_H
+#define __SMB2_CHARGER_H
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/consumer.h>
+#include <linux/extcon.h>
+#include "storm-watch.h"
+
+enum print_reason {
+ PR_INTERRUPT = BIT(0),
+ PR_REGISTER = BIT(1),
+ PR_MISC = BIT(2),
+ PR_PARALLEL = BIT(3),
+ PR_OTG = BIT(4),
+};
+
+#define DEFAULT_VOTER "DEFAULT_VOTER"
+#define USER_VOTER "USER_VOTER"
+#define PD_VOTER "PD_VOTER"
+#define DCP_VOTER "DCP_VOTER"
+#define QC_VOTER "QC_VOTER"
+#define PL_USBIN_USBIN_VOTER "PL_USBIN_USBIN_VOTER"
+#define USB_PSY_VOTER "USB_PSY_VOTER"
+#define PL_TAPER_WORK_RUNNING_VOTER "PL_TAPER_WORK_RUNNING_VOTER"
+#define PL_QNOVO_VOTER "PL_QNOVO_VOTER"
+#define USBIN_V_VOTER "USBIN_V_VOTER"
+#define CHG_STATE_VOTER "CHG_STATE_VOTER"
+#define TYPEC_SRC_VOTER "TYPEC_SRC_VOTER"
+#define TAPER_END_VOTER "TAPER_END_VOTER"
+#define THERMAL_DAEMON_VOTER "THERMAL_DAEMON_VOTER"
+#define CC_DETACHED_VOTER "CC_DETACHED_VOTER"
+#define HVDCP_TIMEOUT_VOTER "HVDCP_TIMEOUT_VOTER"
+#define PD_DISALLOWED_INDIRECT_VOTER "PD_DISALLOWED_INDIRECT_VOTER"
+#define PD_HARD_RESET_VOTER "PD_HARD_RESET_VOTER"
+#define VBUS_CC_SHORT_VOTER "VBUS_CC_SHORT_VOTER"
+#define PD_INACTIVE_VOTER "PD_INACTIVE_VOTER"
+#define BOOST_BACK_VOTER "BOOST_BACK_VOTER"
+#define USBIN_USBIN_BOOST_VOTER "USBIN_USBIN_BOOST_VOTER"
+#define HVDCP_INDIRECT_VOTER "HVDCP_INDIRECT_VOTER"
+#define MICRO_USB_VOTER "MICRO_USB_VOTER"
+#define DEBUG_BOARD_VOTER "DEBUG_BOARD_VOTER"
+#define PD_SUSPEND_SUPPORTED_VOTER "PD_SUSPEND_SUPPORTED_VOTER"
+#define PL_DELAY_VOTER "PL_DELAY_VOTER"
+#define CTM_VOTER "CTM_VOTER"
+#define SW_QC3_VOTER "SW_QC3_VOTER"
+#define AICL_RERUN_VOTER "AICL_RERUN_VOTER"
+#define LEGACY_UNKNOWN_VOTER "LEGACY_UNKNOWN_VOTER"
+#define CC2_WA_VOTER "CC2_WA_VOTER"
+#define QNOVO_VOTER "QNOVO_VOTER"
+#define BATT_PROFILE_VOTER "BATT_PROFILE_VOTER"
+#define OTG_DELAY_VOTER "OTG_DELAY_VOTER"
+#define USBIN_I_VOTER "USBIN_I_VOTER"
+#define WEAK_CHARGER_VOTER "WEAK_CHARGER_VOTER"
+
+#define VCONN_MAX_ATTEMPTS 3
+#define OTG_MAX_ATTEMPTS 3
+#define BOOST_BACK_STORM_COUNT 3
+#define WEAK_CHG_STORM_COUNT 8
+
+enum smb_mode {
+ PARALLEL_MASTER = 0,
+ PARALLEL_SLAVE,
+ NUM_MODES,
+};
+
+enum {
+ QC_CHARGER_DETECTION_WA_BIT = BIT(0),
+ BOOST_BACK_WA = BIT(1),
+ TYPEC_CC2_REMOVAL_WA_BIT = BIT(2),
+ QC_AUTH_INTERRUPT_WA_BIT = BIT(3),
+ OTG_WA = BIT(4),
+};
+
+enum smb_irq_index {
+ CHG_ERROR_IRQ = 0,
+ CHG_STATE_CHANGE_IRQ,
+ STEP_CHG_STATE_CHANGE_IRQ,
+ STEP_CHG_SOC_UPDATE_FAIL_IRQ,
+ STEP_CHG_SOC_UPDATE_REQ_IRQ,
+ OTG_FAIL_IRQ,
+ OTG_OVERCURRENT_IRQ,
+ OTG_OC_DIS_SW_STS_IRQ,
+ TESTMODE_CHANGE_DET_IRQ,
+ BATT_TEMP_IRQ,
+ BATT_OCP_IRQ,
+ BATT_OV_IRQ,
+ BATT_LOW_IRQ,
+ BATT_THERM_ID_MISS_IRQ,
+ BATT_TERM_MISS_IRQ,
+ USBIN_COLLAPSE_IRQ,
+ USBIN_LT_3P6V_IRQ,
+ USBIN_UV_IRQ,
+ USBIN_OV_IRQ,
+ USBIN_PLUGIN_IRQ,
+ USBIN_SRC_CHANGE_IRQ,
+ USBIN_ICL_CHANGE_IRQ,
+ TYPE_C_CHANGE_IRQ,
+ DCIN_COLLAPSE_IRQ,
+ DCIN_LT_3P6V_IRQ,
+ DCIN_UV_IRQ,
+ DCIN_OV_IRQ,
+ DCIN_PLUGIN_IRQ,
+ DIV2_EN_DG_IRQ,
+ DCIN_ICL_CHANGE_IRQ,
+ WDOG_SNARL_IRQ,
+ WDOG_BARK_IRQ,
+ AICL_FAIL_IRQ,
+ AICL_DONE_IRQ,
+ HIGH_DUTY_CYCLE_IRQ,
+ INPUT_CURRENT_LIMIT_IRQ,
+ TEMPERATURE_CHANGE_IRQ,
+ SWITCH_POWER_OK_IRQ,
+ SMB_IRQ_MAX,
+};
+
+struct smb_irq_info {
+ const char *name;
+ const irq_handler_t handler;
+ const bool wake;
+ const struct storm_watch storm_data;
+ struct smb_irq_data *irq_data;
+ int irq;
+};
+
+static const unsigned int smblib_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_USB_CC,
+ EXTCON_USB_SPEED,
+ EXTCON_NONE,
+};
+
+/* EXTCON_USB and EXTCON_USB_HOST are mutually exclusive */
+static const u32 smblib_extcon_exclusive[] = {0x3, 0};
+
+struct smb_regulator {
+ struct regulator_dev *rdev;
+ struct regulator_desc rdesc;
+};
+
+struct smb_irq_data {
+ void *parent_data;
+ const char *name;
+ struct storm_watch storm_data;
+};
+
+struct smb_chg_param {
+ const char *name;
+ u16 reg;
+ int min_u;
+ int max_u;
+ int step_u;
+ int (*get_proc)(struct smb_chg_param *param,
+ u8 val_raw);
+ int (*set_proc)(struct smb_chg_param *param,
+ int val_u,
+ u8 *val_raw);
+};
+
+struct smb_chg_freq {
+ unsigned int freq_5V;
+ unsigned int freq_6V_8V;
+ unsigned int freq_9V;
+ unsigned int freq_12V;
+ unsigned int freq_removal;
+ unsigned int freq_below_otg_threshold;
+ unsigned int freq_above_otg_threshold;
+};
+
+struct smb_params {
+ struct smb_chg_param fcc;
+ struct smb_chg_param fv;
+ struct smb_chg_param usb_icl;
+ struct smb_chg_param icl_stat;
+ struct smb_chg_param otg_cl;
+ struct smb_chg_param dc_icl;
+ struct smb_chg_param dc_icl_pt_lv;
+ struct smb_chg_param dc_icl_pt_hv;
+ struct smb_chg_param dc_icl_div2_lv;
+ struct smb_chg_param dc_icl_div2_mid_lv;
+ struct smb_chg_param dc_icl_div2_mid_hv;
+ struct smb_chg_param dc_icl_div2_hv;
+ struct smb_chg_param jeita_cc_comp;
+ struct smb_chg_param freq_buck;
+ struct smb_chg_param freq_boost;
+};
+
+struct parallel_params {
+ struct power_supply *psy;
+};
+
+struct smb_iio {
+ struct iio_channel *temp_chan;
+ struct iio_channel *temp_max_chan;
+ struct iio_channel *usbin_i_chan;
+ struct iio_channel *usbin_v_chan;
+ struct iio_channel *batt_i_chan;
+ struct iio_channel *connector_temp_chan;
+ struct iio_channel *connector_temp_thr1_chan;
+ struct iio_channel *connector_temp_thr2_chan;
+ struct iio_channel *connector_temp_thr3_chan;
+};
+
+struct reg_info {
+ u16 reg;
+ u8 mask;
+ u8 val;
+ u8 bak;
+ const char *desc;
+};
+
+struct smb_charger {
+ struct device *dev;
+ char *name;
+ struct regmap *regmap;
+ struct smb_irq_info *irq_info;
+ struct smb_params param;
+ struct smb_iio iio;
+ int *debug_mask;
+ enum smb_mode mode;
+ struct smb_chg_freq chg_freq;
+ int smb_version;
+ int otg_delay_ms;
+ int *weak_chg_icl_ua;
+
+ /* locks */
+ struct mutex lock;
+ struct mutex write_lock;
+ struct mutex ps_change_lock;
+ struct mutex otg_oc_lock;
+ struct mutex vconn_oc_lock;
+
+ /* power supplies */
+ struct power_supply *batt_psy;
+ struct power_supply *usb_psy;
+ struct power_supply *dc_psy;
+ struct power_supply *bms_psy;
+ struct power_supply_desc usb_psy_desc;
+ struct power_supply *usb_main_psy;
+ struct power_supply *usb_port_psy;
+ enum power_supply_type real_charger_type;
+
+ /* notifiers */
+ struct notifier_block nb;
+
+ /* parallel charging */
+ struct parallel_params pl;
+
+ /* regulators */
+ struct smb_regulator *vbus_vreg;
+ struct smb_regulator *vconn_vreg;
+ struct regulator *dpdm_reg;
+
+ /* votables */
+ struct votable *dc_suspend_votable;
+ struct votable *fcc_votable;
+ struct votable *fv_votable;
+ struct votable *usb_icl_votable;
+ struct votable *dc_icl_votable;
+ struct votable *pd_disallowed_votable_indirect;
+ struct votable *pd_allowed_votable;
+ struct votable *awake_votable;
+ struct votable *pl_disable_votable;
+ struct votable *chg_disable_votable;
+ struct votable *pl_enable_votable_indirect;
+ struct votable *hvdcp_disable_votable_indirect;
+ struct votable *hvdcp_enable_votable;
+ struct votable *apsd_disable_votable;
+ struct votable *hvdcp_hw_inov_dis_votable;
+ struct votable *usb_irq_enable_votable;
+ struct votable *typec_irq_disable_votable;
+
+ /* work */
+ struct work_struct bms_update_work;
+ struct work_struct rdstd_cc2_detach_work;
+ struct delayed_work hvdcp_detect_work;
+ struct delayed_work ps_change_timeout_work;
+ struct delayed_work clear_hdc_work;
+ struct work_struct otg_oc_work;
+ struct work_struct vconn_oc_work;
+ struct delayed_work otg_ss_done_work;
+ struct delayed_work icl_change_work;
+ struct delayed_work pl_enable_work;
+ struct work_struct legacy_detection_work;
+ struct delayed_work uusb_otg_work;
+ struct delayed_work bb_removal_work;
+
+ /* cached status */
+ int voltage_min_uv;
+ int voltage_max_uv;
+ int pd_active;
+ bool system_suspend_supported;
+ int boost_threshold_ua;
+ int system_temp_level;
+ int thermal_levels;
+ int *thermal_mitigation;
+ int dcp_icl_ua;
+ int fake_capacity;
+ bool step_chg_enabled;
+ bool sw_jeita_enabled;
+ bool is_hdc;
+ bool chg_done;
+ bool micro_usb_mode;
+ bool otg_en;
+ bool vconn_en;
+ bool suspend_input_on_debug_batt;
+ int otg_attempts;
+ int vconn_attempts;
+ int default_icl_ua;
+ int otg_cl_ua;
+ bool uusb_apsd_rerun_done;
+ bool pd_hard_reset;
+ bool typec_present;
+ u8 typec_status[5];
+ bool typec_legacy_valid;
+ int fake_input_current_limited;
+ bool pr_swap_in_progress;
+ int typec_mode;
+ int usb_icl_change_irq_enabled;
+ u32 jeita_status;
+ u8 float_cfg;
+ bool use_extcon;
+ bool otg_present;
+
+ /* workaround flag */
+ u32 wa_flags;
+ bool cc2_detach_wa_active;
+ bool typec_en_dis_active;
+ int boost_current_ua;
+ int temp_speed_reading_count;
+
+ /* extcon for VBUS / ID notification to USB for uUSB */
+ struct extcon_dev *extcon;
+
+ /* battery profile */
+ int batt_profile_fcc_ua;
+ int batt_profile_fv_uv;
+
+ /* qnovo */
+ int usb_icl_delta_ua;
+ int pulse_cnt;
+};
+
+int smblib_read(struct smb_charger *chg, u16 addr, u8 *val);
+int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val);
+int smblib_write(struct smb_charger *chg, u16 addr, u8 val);
+
+int smblib_get_charge_param(struct smb_charger *chg,
+ struct smb_chg_param *param, int *val_u);
+int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend);
+
+int smblib_enable_charging(struct smb_charger *chg, bool enable);
+int smblib_set_charge_param(struct smb_charger *chg,
+ struct smb_chg_param *param, int val_u);
+int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend);
+int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend);
+
+int smblib_mapping_soc_from_field_value(struct smb_chg_param *param,
+ int val_u, u8 *val_raw);
+int smblib_mapping_cc_delta_to_field_value(struct smb_chg_param *param,
+ u8 val_raw);
+int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param,
+ int val_u, u8 *val_raw);
+int smblib_set_chg_freq(struct smb_chg_param *param,
+ int val_u, u8 *val_raw);
+
+int smblib_vbus_regulator_enable(struct regulator_dev *rdev);
+int smblib_vbus_regulator_disable(struct regulator_dev *rdev);
+int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev);
+
+int smblib_vconn_regulator_enable(struct regulator_dev *rdev);
+int smblib_vconn_regulator_disable(struct regulator_dev *rdev);
+int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev);
+
+irqreturn_t smblib_handle_debug(int irq, void *data);
+irqreturn_t smblib_handle_otg_overcurrent(int irq, void *data);
+irqreturn_t smblib_handle_chg_state_change(int irq, void *data);
+irqreturn_t smblib_handle_batt_temp_changed(int irq, void *data);
+irqreturn_t smblib_handle_batt_psy_changed(int irq, void *data);
+irqreturn_t smblib_handle_usb_psy_changed(int irq, void *data);
+irqreturn_t smblib_handle_usbin_uv(int irq, void *data);
+irqreturn_t smblib_handle_usb_plugin(int irq, void *data);
+irqreturn_t smblib_handle_usb_source_change(int irq, void *data);
+irqreturn_t smblib_handle_icl_change(int irq, void *data);
+irqreturn_t smblib_handle_usb_typec_change(int irq, void *data);
+irqreturn_t smblib_handle_dc_plugin(int irq, void *data);
+irqreturn_t smblib_handle_high_duty_cycle(int irq, void *data);
+irqreturn_t smblib_handle_switcher_power_ok(int irq, void *data);
+irqreturn_t smblib_handle_wdog_bark(int irq, void *data);
+
+int smblib_get_prop_input_suspend(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_batt_present(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_batt_capacity(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_batt_status(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_batt_charge_type(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_batt_charge_done(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_batt_health(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_system_temp_level(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_input_current_limited(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_batt_voltage_now(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_batt_current_now(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_batt_temp(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_batt_charge_counter(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_set_prop_input_suspend(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_batt_capacity(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_system_temp_level(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_input_current_limited(struct smb_charger *chg,
+ const union power_supply_propval *val);
+
+int smblib_get_prop_dc_present(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_dc_online(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_dc_current_max(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_set_prop_dc_current_max(struct smb_charger *chg,
+ const union power_supply_propval *val);
+
+int smblib_get_prop_usb_present(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_usb_online(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_usb_suspend(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_usb_voltage_max(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_usb_voltage_now(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_usb_current_now(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_typec_power_role(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_pd_allowed(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_input_current_settled(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_input_voltage_settled(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_pe_start(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_charger_temp(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_charger_temp_max(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_die_health(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_get_prop_charge_qnovo_enable(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_set_prop_pd_current_max(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_sdp_current_max(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_pd_voltage_max(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_pd_voltage_min(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_boost_current(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_typec_power_role(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_pd_active(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_get_prop_slave_current_now(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_set_prop_ship_mode(struct smb_charger *chg,
+ const union power_supply_propval *val);
+int smblib_set_prop_charge_qnovo_enable(struct smb_charger *chg,
+ const union power_supply_propval *val);
+void smblib_suspend_on_debug_battery(struct smb_charger *chg);
+int smblib_rerun_apsd_if_required(struct smb_charger *chg);
+int smblib_get_prop_fcc_delta(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_icl_override(struct smb_charger *chg, bool override);
+int smblib_dp_dm(struct smb_charger *chg, int val);
+int smblib_disable_hw_jeita(struct smb_charger *chg, bool disable);
+int smblib_rerun_aicl(struct smb_charger *chg);
+int smblib_set_icl_current(struct smb_charger *chg, int icl_ua);
+int smblib_get_icl_current(struct smb_charger *chg, int *icl_ua);
+int smblib_get_charge_current(struct smb_charger *chg, int *total_current_ua);
+int smblib_get_prop_pr_swap_in_progress(struct smb_charger *chg,
+ union power_supply_propval *val);
+int smblib_set_prop_pr_swap_in_progress(struct smb_charger *chg,
+ const union power_supply_propval *val);
+
+int smblib_init(struct smb_charger *chg);
+int smblib_deinit(struct smb_charger *chg);
+#endif /* __SMB2_CHARGER_H */
diff --git a/drivers/power/supply/qcom/smb-reg.h b/drivers/power/supply/qcom/smb-reg.h
new file mode 100644
index 000000000000..d8671ab1fd06
--- /dev/null
+++ b/drivers/power/supply/qcom/smb-reg.h
@@ -0,0 +1,1028 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 __SMB2_CHARGER_REG_H
+#define __SMB2_CHARGER_REG_H
+
+#include <linux/bitops.h>
+
+#define CHGR_BASE 0x1000
+#define OTG_BASE 0x1100
+#define BATIF_BASE 0x1200
+#define USBIN_BASE 0x1300
+#define DCIN_BASE 0x1400
+#define MISC_BASE 0x1600
+#define CHGR_FREQ_BASE 0x1900
+
+#define PERPH_TYPE_OFFSET 0x04
+#define TYPE_MASK GENMASK(7, 0)
+#define PERPH_SUBTYPE_OFFSET 0x05
+#define SUBTYPE_MASK GENMASK(7, 0)
+#define INT_RT_STS_OFFSET 0x10
+
+/* CHGR Peripheral Registers */
+#define BATTERY_CHARGER_STATUS_1_REG (CHGR_BASE + 0x06)
+#define BVR_INITIAL_RAMP_BIT BIT(7)
+#define CC_SOFT_TERMINATE_BIT BIT(6)
+#define STEP_CHARGING_STATUS_SHIFT 3
+#define STEP_CHARGING_STATUS_MASK GENMASK(5, 3)
+#define BATTERY_CHARGER_STATUS_MASK GENMASK(2, 0)
+enum {
+ TRICKLE_CHARGE = 0,
+ PRE_CHARGE,
+ FAST_CHARGE,
+ FULLON_CHARGE,
+ TAPER_CHARGE,
+ TERMINATE_CHARGE,
+ INHIBIT_CHARGE,
+ DISABLE_CHARGE,
+};
+
+#define BATTERY_CHARGER_STATUS_2_REG (CHGR_BASE + 0x07)
+#define INPUT_CURRENT_LIMITED_BIT BIT(7)
+#define CHARGER_ERROR_STATUS_SFT_EXPIRE_BIT BIT(6)
+#define CHARGER_ERROR_STATUS_BAT_OV_BIT BIT(5)
+#define CHARGER_ERROR_STATUS_BAT_TERM_MISSING_BIT BIT(4)
+#define BAT_TEMP_STATUS_MASK GENMASK(3, 0)
+#define BAT_TEMP_STATUS_SOFT_LIMIT_MASK GENMASK(3, 2)
+#define BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT BIT(3)
+#define BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT BIT(2)
+#define BAT_TEMP_STATUS_TOO_HOT_BIT BIT(1)
+#define BAT_TEMP_STATUS_TOO_COLD_BIT BIT(0)
+
+#define CHG_OPTION_REG (CHGR_BASE + 0x08)
+#define PIN_BIT BIT(7)
+
+#define BATTERY_CHARGER_STATUS_3_REG (CHGR_BASE + 0x09)
+#define FV_POST_JEITA_MASK GENMASK(7, 0)
+
+#define BATTERY_CHARGER_STATUS_4_REG (CHGR_BASE + 0x0A)
+#define CHARGE_CURRENT_POST_JEITA_MASK GENMASK(7, 0)
+
+#define BATTERY_CHARGER_STATUS_5_REG (CHGR_BASE + 0x0B)
+#define VALID_INPUT_POWER_SOURCE_BIT BIT(7)
+#define DISABLE_CHARGING_BIT BIT(6)
+#define FORCE_ZERO_CHARGE_CURRENT_BIT BIT(5)
+#define CHARGING_ENABLE_BIT BIT(4)
+#define TAPER_BIT BIT(3)
+#define ENABLE_CHG_SENSORS_BIT BIT(2)
+#define ENABLE_TAPER_SENSOR_BIT BIT(1)
+#define TAPER_REGION_BIT BIT(0)
+
+#define BATTERY_CHARGER_STATUS_6_REG (CHGR_BASE + 0x0C)
+#define GF_BATT_OV_BIT BIT(7)
+#define DROP_IN_BATTERY_VOLTAGE_REFERENCE_BIT BIT(6)
+#define VBATT_LTET_RECHARGE_BIT BIT(5)
+#define VBATT_GTET_INHIBIT_BIT BIT(4)
+#define VBATT_GTET_FLOAT_VOLTAGE_BIT BIT(3)
+#define BATT_GT_PRE_TO_FAST_BIT BIT(2)
+#define BATT_GT_FULL_ON_BIT BIT(1)
+#define VBATT_LT_2V_BIT BIT(0)
+
+#define BATTERY_CHARGER_STATUS_7_REG (CHGR_BASE + 0x0D)
+#define ENABLE_TRICKLE_BIT BIT(7)
+#define ENABLE_PRE_CHARGING_BIT BIT(6)
+#define ENABLE_FAST_CHARGING_BIT BIT(5)
+#define ENABLE_FULLON_MODE_BIT BIT(4)
+#define TOO_COLD_ADC_BIT BIT(3)
+#define TOO_HOT_ADC_BIT BIT(2)
+#define HOT_SL_ADC_BIT BIT(1)
+#define COLD_SL_ADC_BIT BIT(0)
+
+#define BATTERY_CHARGER_STATUS_8_REG (CHGR_BASE + 0x0E)
+#define PRE_FAST_BIT BIT(7)
+#define PRE_FULLON_BIT BIT(6)
+#define PRE_RCHG_BIT BIT(5)
+#define PRE_INHIBIT_BIT BIT(4)
+#define PRE_OVRV_BIT BIT(3)
+#define PRE_TERM_BIT BIT(2)
+#define BAT_ID_BMISS_CMP_BIT BIT(1)
+#define THERM_CMP_BIT BIT(0)
+
+/* CHGR Interrupt Bits */
+#define CHGR_7_RT_STS_BIT BIT(7)
+#define CHGR_6_RT_STS_BIT BIT(6)
+#define FG_FVCAL_QUALIFIED_RT_STS_BIT BIT(5)
+#define STEP_CHARGING_SOC_UPDATE_REQUEST_RT_STS_BIT BIT(4)
+#define STEP_CHARGING_SOC_UPDATE_FAIL_RT_STS_BIT BIT(3)
+#define STEP_CHARGING_STATE_CHANGE_RT_STS_BIT BIT(2)
+#define CHARGING_STATE_CHANGE_RT_STS_BIT BIT(1)
+#define CHGR_ERROR_RT_STS_BIT BIT(0)
+
+#define STEP_CHG_SOC_VBATT_V_REG (CHGR_BASE + 0x40)
+#define STEP_CHG_SOC_VBATT_V_MASK GENMASK(7, 0)
+
+#define STEP_CHG_SOC_VBATT_V_UPDATE_REG (CHGR_BASE + 0x41)
+#define STEP_CHG_SOC_VBATT_V_UPDATE_BIT BIT(0)
+
+#define CHARGING_ENABLE_CMD_REG (CHGR_BASE + 0x42)
+#define CHARGING_ENABLE_CMD_BIT BIT(0)
+
+#define ALLOW_FAST_CHARGING_CMD_REG (CHGR_BASE + 0x43)
+#define ALLOW_FAST_CHARGING_CMD_BIT BIT(0)
+
+#define QNOVO_PT_ENABLE_CMD_REG (CHGR_BASE + 0x44)
+#define QNOVO_PT_ENABLE_CMD_BIT BIT(0)
+
+#define CHGR_CFG1_REG (CHGR_BASE + 0x50)
+#define INCREASE_RCHG_TIMEOUT_CFG_BIT BIT(1)
+#define LOAD_BAT_BIT BIT(0)
+
+#define CHGR_CFG2_REG (CHGR_BASE + 0x51)
+#define CHG_EN_SRC_BIT BIT(7)
+#define CHG_EN_POLARITY_BIT BIT(6)
+#define PRETOFAST_TRANSITION_CFG_BIT BIT(5)
+#define BAT_OV_ECC_BIT BIT(4)
+#define I_TERM_BIT BIT(3)
+#define AUTO_RECHG_BIT BIT(2)
+#define EN_ANALOG_DROP_IN_VBATT_BIT BIT(1)
+#define CHARGER_INHIBIT_BIT BIT(0)
+
+#define CHARGER_ENABLE_CFG_REG (CHGR_BASE + 0x52)
+#define CHG_ENB_TIMEOUT_SETTING_BIT BIT(1)
+#define FORCE_ZERO_CFG_BIT BIT(0)
+
+#define CFG_REG (CHGR_BASE + 0x53)
+#define CHG_OPTION_PIN_TRIM_BIT BIT(7)
+#define BATN_SNS_CFG_BIT BIT(4)
+#define CFG_TAPER_DIS_AFVC_BIT BIT(3)
+#define BATFET_SHUTDOWN_CFG_BIT BIT(2)
+#define VDISCHG_EN_CFG_BIT BIT(1)
+#define VCHG_EN_CFG_BIT BIT(0)
+
+#define CHARGER_SPARE_REG (CHGR_BASE + 0x54)
+#define CHARGER_SPARE_MASK GENMASK(5, 0)
+
+#define PRE_CHARGE_CURRENT_CFG_REG (CHGR_BASE + 0x60)
+#define PRE_CHARGE_CURRENT_SETTING_MASK GENMASK(5, 0)
+
+#define FAST_CHARGE_CURRENT_CFG_REG (CHGR_BASE + 0x61)
+#define FAST_CHARGE_CURRENT_SETTING_MASK GENMASK(7, 0)
+
+#define CHARGE_CURRENT_TERMINATION_CFG_REG (CHGR_BASE + 0x62)
+#define ANALOG_CHARGE_CURRENT_TERMINATION_SETTING_MASK GENMASK(2, 0)
+
+#define TCCC_CHARGE_CURRENT_TERMINATION_CFG_REG (CHGR_BASE + 0x63)
+#define TCCC_CHARGE_CURRENT_TERMINATION_SETTING_MASK GENMASK(3, 0)
+
+#define CHARGE_CURRENT_SOFTSTART_SETTING_CFG_REG (CHGR_BASE + 0x64)
+#define CHARGE_CURRENT_SOFTSTART_SETTING_MASK GENMASK(1, 0)
+
+#define FLOAT_VOLTAGE_CFG_REG (CHGR_BASE + 0x70)
+#define FLOAT_VOLTAGE_SETTING_MASK GENMASK(7, 0)
+
+#define AUTO_FLOAT_VOLTAGE_COMPENSATION_CFG_REG (CHGR_BASE + 0x71)
+#define AUTO_FLOAT_VOLTAGE_COMPENSATION_MASK GENMASK(2, 0)
+
+#define CHARGE_INHIBIT_THRESHOLD_CFG_REG (CHGR_BASE + 0x72)
+#define CHARGE_INHIBIT_THRESHOLD_MASK GENMASK(1, 0)
+#define CHARGE_INHIBIT_THRESHOLD_50MV 0
+#define CHARGE_INHIBIT_THRESHOLD_100MV 1
+#define CHARGE_INHIBIT_THRESHOLD_200MV 2
+#define CHARGE_INHIBIT_THRESHOLD_300MV 3
+
+#define RECHARGE_THRESHOLD_CFG_REG (CHGR_BASE + 0x73)
+#define RECHARGE_THRESHOLD_MASK GENMASK(1, 0)
+
+#define PRE_TO_FAST_CHARGE_THRESHOLD_CFG_REG (CHGR_BASE + 0x74)
+#define PRE_TO_FAST_CHARGE_THRESHOLD_MASK GENMASK(1, 0)
+
+#define FV_HYSTERESIS_CFG_REG (CHGR_BASE + 0x75)
+#define FV_DROP_HYSTERESIS_CFG_MASK GENMASK(7, 4)
+#define THRESH_HYSTERESIS_CFG_MASK GENMASK(3, 0)
+
+#define FVC_CHARGE_INHIBIT_THRESHOLD_CFG_REG (CHGR_BASE + 0x80)
+#define FVC_CHARGE_INHIBIT_THRESHOLD_MASK GENMASK(5, 0)
+
+#define FVC_RECHARGE_THRESHOLD_CFG_REG (CHGR_BASE + 0x81)
+#define FVC_RECHARGE_THRESHOLD_MASK GENMASK(7, 0)
+
+#define FVC_PRE_TO_FAST_CHARGE_THRESHOLD_CFG_REG (CHGR_BASE + 0x82)
+#define FVC_PRE_TO_FAST_CHARGE_THRESHOLD_MASK GENMASK(7, 0)
+
+#define FVC_FULL_ON_THRESHOLD_CFG_REG (CHGR_BASE + 0x83)
+#define FVC_FULL_ON_THRESHOLD_MASK GENMASK(7, 0)
+
+#define FVC_CC_MODE_GLITCH_FILTER_SEL_CFG_REG (CHGR_BASE + 0x84)
+#define FVC_CC_MODE_GLITCH_FILTER_SEL_MASK GENMASK(1, 0)
+
+#define FVC_TERMINATION_GLITCH_FILTER_SEL_CFG_REG (CHGR_BASE + 0x85)
+#define FVC_TERMINATION_GLITCH_FILTER_SEL_MASK GENMASK(1, 0)
+
+#define JEITA_EN_CFG_REG (CHGR_BASE + 0x90)
+#define JEITA_EN_HARDLIMIT_BIT BIT(4)
+#define JEITA_EN_HOT_SL_FCV_BIT BIT(3)
+#define JEITA_EN_COLD_SL_FCV_BIT BIT(2)
+#define JEITA_EN_HOT_SL_CCC_BIT BIT(1)
+#define JEITA_EN_COLD_SL_CCC_BIT BIT(0)
+
+#define JEITA_FVCOMP_CFG_REG (CHGR_BASE + 0x91)
+#define JEITA_FVCOMP_MASK GENMASK(7, 0)
+
+#define JEITA_CCCOMP_CFG_REG (CHGR_BASE + 0x92)
+#define JEITA_CCCOMP_MASK GENMASK(7, 0)
+
+#define FV_CAL_CFG_REG (CHGR_BASE + 0x76)
+#define FV_CALIBRATION_CFG_MASK GENMASK(2, 0)
+
+#define FV_ADJUST_REG (CHGR_BASE + 0x77)
+#define FLOAT_VOLTAGE_ADJUSTMENT_MASK GENMASK(4, 0)
+
+#define FG_VADC_DISQ_THRESH_REG (CHGR_BASE + 0x78)
+#define VADC_DISQUAL_THRESH_MASK GENMASK(7, 0)
+
+#define FG_IADC_DISQ_THRESH_REG (CHGR_BASE + 0x79)
+#define IADC_DISQUAL_THRESH_MASK GENMASK(7, 0)
+
+#define FG_UPDATE_CFG_1_REG (CHGR_BASE + 0x7A)
+#define BT_TMPR_TCOLD_BIT BIT(7)
+#define BT_TMPR_COLD_BIT BIT(6)
+#define BT_TMPR_HOT_BIT BIT(5)
+#define BT_TMPR_THOT_BIT BIT(4)
+#define CHG_DIE_TMPR_HOT_BIT BIT(3)
+#define CHG_DIE_TMPR_THOT_BIT BIT(2)
+#define SKIN_TMPR_HOT_BIT BIT(1)
+#define SKIN_TMPR_THOT_BIT BIT(0)
+
+#define FG_UPDATE_CFG_1_SEL_REG (CHGR_BASE + 0x7B)
+#define BT_TMPR_TCOLD_SEL_BIT BIT(7)
+#define BT_TMPR_COLD_SEL_BIT BIT(6)
+#define BT_TMPR_HOT_SEL_BIT BIT(5)
+#define BT_TMPR_THOT_SEL_BIT BIT(4)
+#define CHG_DIE_TMPR_HOT_SEL_BIT BIT(3)
+#define CHG_DIE_TMPR_THOT_SEL_BIT BIT(2)
+#define SKIN_TMPR_HOT_SEL_BIT BIT(1)
+#define SKIN_TMPR_THOT_SEL_BIT BIT(0)
+
+#define FG_UPDATE_CFG_2_REG (CHGR_BASE + 0x7C)
+#define SOC_LT_OTG_THRESH_BIT BIT(3)
+#define SOC_LT_CHG_RECHARGE_THRESH_BIT BIT(2)
+#define VBT_LT_CHG_RECHARGE_THRESH_BIT BIT(1)
+#define IBT_LT_CHG_TERM_THRESH_BIT BIT(0)
+
+#define FG_UPDATE_CFG_2_SEL_REG (CHGR_BASE + 0x7D)
+#define SOC_LT_OTG_THRESH_SEL_BIT BIT(3)
+#define SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT BIT(2)
+#define VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT BIT(1)
+#define IBT_LT_CHG_TERM_THRESH_SEL_BIT BIT(0)
+
+#define FG_CHG_INTERFACE_CFG_REG (CHGR_BASE + 0x7E)
+#define ESR_ISINK_CFG_MASK GENMASK(7, 6)
+#define ESR_FASTCHG_DECR_CFG_MASK GENMASK(5, 4)
+#define FG_CHARGER_INHIBIT_BIT BIT(3)
+#define FG_BATFET_BIT BIT(2)
+#define IADC_SYNC_CNV_BIT BIT(1)
+#define VADC_SYNC_CNV_BIT BIT(0)
+
+#define FG_CHG_INTERFACE_CFG_SEL_REG (CHGR_BASE + 0x7F)
+#define ESR_ISINK_CFG_SEL_BIT BIT(5)
+#define ESR_FASTCHG_DECR_CFG_SEL_BIT BIT(4)
+#define FG_CHARGER_INHIBIT_SEL_BIT BIT(3)
+#define FG_BATFET_SEL_BIT BIT(2)
+#define IADC_SYNC_CNV_SEL_BIT BIT(1)
+#define VADC_SYNC_CNV_SEL_BIT BIT(0)
+
+#define CHGR_STEP_CHG_MODE_CFG_REG (CHGR_BASE + 0xB0)
+#define STEP_CHARGING_SOC_FAIL_OPTION_BIT BIT(3)
+#define STEP_CHARGING_MODE_SELECT_BIT BIT(2)
+#define STEP_CHARGING_SOURCE_SELECT_BIT BIT(1)
+#define STEP_CHARGING_ENABLE_BIT BIT(0)
+
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_CFG_REG (CHGR_BASE + 0xB1)
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_CFG_MASK GENMASK(0, 1)
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_5S 0
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_10S 1
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_20S 2
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_40S 3
+
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_CFG_REG (CHGR_BASE + 0xB2)
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_CFG_MASK GENMASK(0, 1)
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_10S 0
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_30S 1
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_60S 2
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_120S 3
+
+#define STEP_CHG_SOC_OR_BATT_V_TH1_REG (CHGR_BASE + 0xB3)
+#define STEP_CHG_SOC_OR_BATT_V_TH2_REG (CHGR_BASE + 0xB4)
+#define STEP_CHG_SOC_OR_BATT_V_TH3_REG (CHGR_BASE + 0xB5)
+#define STEP_CHG_SOC_OR_BATT_V_TH4_REG (CHGR_BASE + 0xB6)
+#define STEP_CHG_CURRENT_DELTA1_REG (CHGR_BASE + 0xB7)
+#define STEP_CHG_CURRENT_DELTA2_REG (CHGR_BASE + 0xB8)
+#define STEP_CHG_CURRENT_DELTA3_REG (CHGR_BASE + 0xB9)
+#define STEP_CHG_CURRENT_DELTA4_REG (CHGR_BASE + 0xBA)
+#define STEP_CHG_CURRENT_DELTA5_REG (CHGR_BASE + 0xBB)
+
+/* OTG Peripheral Registers */
+#define RID_CC_CONTROL_23_16_REG (OTG_BASE + 0x06)
+#define RID_CC_CONTROL_23_BIT BIT(7)
+#define VCONN_SOFTSTART_EN_BIT BIT(6)
+#define VCONN_SFTST_CFG_MASK GENMASK(5, 4)
+#define CONNECT_RIDCC_SENSOR_TO_CC_MASK GENMASK(3, 2)
+#define EN_CC_1P1CLAMP_BIT BIT(1)
+#define ENABLE_CRUDESEN_CC_1_BIT BIT(0)
+
+#define RID_CC_CONTROL_15_8_REG (OTG_BASE + 0x07)
+#define ENABLE_CRUDESEN_CC_0_BIT BIT(7)
+#define EN_FMB_2P5UA_CC_MASK GENMASK(6, 5)
+#define EN_ISRC_180UA_BIT BIT(4)
+#define ENABLE_CURRENTSOURCE_CC_MASK GENMASK(3, 2)
+#define EN_BANDGAP_RID_C_DET_BIT BIT(1)
+#define ENABLE_RD_CC_1_BIT BIT(0)
+
+#define RID_CC_CONTROL_7_0_REG (OTG_BASE + 0x08)
+#define ENABLE_RD_CC_0_BIT BIT(7)
+#define VCONN_ILIM500MA_BIT BIT(6)
+#define EN_MICRO_USB_MODE_BIT BIT(5)
+#define UFP_DFP_MODE_BIT BIT(4)
+#define VCONN_EN_CC_MASK GENMASK(3, 2)
+#define VREF_SEL_RIDCC_SENSOR_MASK GENMASK(1, 0)
+
+#define OTG_STATUS_REG (OTG_BASE + 0x09)
+#define BOOST_SOFTSTART_DONE_BIT BIT(3)
+#define OTG_STATE_MASK GENMASK(2, 0)
+#define OTG_STATE_ENABLED 0x2
+
+/* OTG Interrupt Bits */
+#define TESTMODE_CHANGE_DETECT_RT_STS_BIT BIT(3)
+#define OTG_OC_DIS_SW_STS_RT_STS_BIT BIT(2)
+#define OTG_OVERCURRENT_RT_STS_BIT BIT(1)
+#define OTG_FAIL_RT_STS_BIT BIT(0)
+
+#define CMD_OTG_REG (OTG_BASE + 0x40)
+#define OTG_EN_BIT BIT(0)
+
+#define BAT_UVLO_THRESHOLD_CFG_REG (OTG_BASE + 0x51)
+#define BAT_UVLO_THRESHOLD_MASK GENMASK(1, 0)
+
+#define OTG_CURRENT_LIMIT_CFG_REG (OTG_BASE + 0x52)
+#define OTG_CURRENT_LIMIT_MASK GENMASK(2, 0)
+
+#define OTG_CFG_REG (OTG_BASE + 0x53)
+#define OTG_RESERVED_MASK GENMASK(7, 6)
+#define DIS_OTG_ON_TLIM_BIT BIT(5)
+#define QUICKSTART_OTG_FASTROLESWAP_BIT BIT(4)
+#define INCREASE_DFP_TIME_BIT BIT(3)
+#define ENABLE_OTG_IN_DEBUG_MODE_BIT BIT(2)
+#define OTG_EN_SRC_CFG_BIT BIT(1)
+#define CONCURRENT_MODE_CFG_BIT BIT(0)
+
+#define OTG_ENG_OTG_CFG_REG (OTG_BASE + 0xC0)
+#define ENG_BUCKBOOST_HALT1_8_MODE_BIT BIT(0)
+
+/* BATIF Peripheral Registers */
+/* BATIF Interrupt Bits */
+#define BAT_7_RT_STS_BIT BIT(7)
+#define BAT_6_RT_STS_BIT BIT(6)
+#define BAT_TERMINAL_MISSING_RT_STS_BIT BIT(5)
+#define BAT_THERM_OR_ID_MISSING_RT_STS_BIT BIT(4)
+#define BAT_LOW_RT_STS_BIT BIT(3)
+#define BAT_OV_RT_STS_BIT BIT(2)
+#define BAT_OCP_RT_STS_BIT BIT(1)
+#define BAT_TEMP_RT_STS_BIT BIT(0)
+
+#define SHIP_MODE_REG (BATIF_BASE + 0x40)
+#define SHIP_MODE_EN_BIT BIT(0)
+
+#define BATOCP_THRESHOLD_CFG_REG (BATIF_BASE + 0x50)
+#define BATOCP_ENABLE_CFG_BIT BIT(3)
+#define BATOCP_THRESHOLD_MASK GENMASK(2, 0)
+
+#define BATOCP_INTRPT_DELAY_TMR_CFG_REG (BATIF_BASE + 0x51)
+#define BATOCP_INTRPT_TIMEOUT_MASK GENMASK(5, 3)
+#define BATOCP_DELAY_TIMEOUT_MASK GENMASK(2, 0)
+
+#define BATOCP_RESET_TMR_CFG_REG (BATIF_BASE + 0x52)
+#define EN_BATOCP_RESET_TMR_BIT BIT(3)
+#define BATOCP_RESET_TIMEOUT_MASK GENMASK(2, 0)
+
+#define LOW_BATT_DETECT_EN_CFG_REG (BATIF_BASE + 0x60)
+#define LOW_BATT_DETECT_EN_BIT BIT(0)
+
+#define LOW_BATT_THRESHOLD_CFG_REG (BATIF_BASE + 0x61)
+#define LOW_BATT_THRESHOLD_MASK GENMASK(3, 0)
+
+#define BAT_FET_CFG_REG (BATIF_BASE + 0x62)
+#define BAT_FET_CFG_BIT BIT(0)
+
+#define BAT_MISS_SRC_CFG_REG (BATIF_BASE + 0x70)
+#define BAT_MISS_ALG_EN_BIT BIT(2)
+#define BAT_MISS_RESERVED_BIT BIT(1)
+#define BAT_MISS_PIN_SRC_EN_BIT BIT(0)
+
+#define BAT_MISS_ALG_OPTIONS_CFG_REG (BATIF_BASE + 0x71)
+#define BAT_MISS_INPUT_PLUGIN_BIT BIT(2)
+#define BAT_MISS_TMR_START_OPTION_BIT BIT(1)
+#define BAT_MISS_POLL_EN_BIT BIT(0)
+
+#define BAT_MISS_PIN_GF_CFG_REG (BATIF_BASE + 0x72)
+#define BAT_MISS_PIN_GF_MASK GENMASK(1, 0)
+
+/* USBIN Peripheral Registers */
+#define USBIN_INPUT_STATUS_REG (USBIN_BASE + 0x06)
+#define USBIN_INPUT_STATUS_7_BIT BIT(7)
+#define USBIN_INPUT_STATUS_6_BIT BIT(6)
+#define USBIN_12V_BIT BIT(5)
+#define USBIN_9V_TO_12V_BIT BIT(4)
+#define USBIN_9V_BIT BIT(3)
+#define USBIN_5V_TO_12V_BIT BIT(2)
+#define USBIN_5V_TO_9V_BIT BIT(1)
+#define USBIN_5V_BIT BIT(0)
+#define QC_2P0_STATUS_MASK GENMASK(2, 0)
+
+#define APSD_STATUS_REG (USBIN_BASE + 0x07)
+#define APSD_STATUS_7_BIT BIT(7)
+#define HVDCP_CHECK_TIMEOUT_BIT BIT(6)
+#define SLOW_PLUGIN_TIMEOUT_BIT BIT(5)
+#define ENUMERATION_DONE_BIT BIT(4)
+#define VADP_CHANGE_DONE_AFTER_AUTH_BIT BIT(3)
+#define QC_AUTH_DONE_STATUS_BIT BIT(2)
+#define QC_CHARGER_BIT BIT(1)
+#define APSD_DTC_STATUS_DONE_BIT BIT(0)
+
+#define APSD_RESULT_STATUS_REG (USBIN_BASE + 0x08)
+#define ICL_OVERRIDE_LATCH_BIT BIT(7)
+#define APSD_RESULT_STATUS_MASK GENMASK(6, 0)
+#define QC_3P0_BIT BIT(6)
+#define QC_2P0_BIT BIT(5)
+#define FLOAT_CHARGER_BIT BIT(4)
+#define DCP_CHARGER_BIT BIT(3)
+#define CDP_CHARGER_BIT BIT(2)
+#define OCP_CHARGER_BIT BIT(1)
+#define SDP_CHARGER_BIT BIT(0)
+
+#define QC_CHANGE_STATUS_REG (USBIN_BASE + 0x09)
+#define QC_CHANGE_STATUS_7_BIT BIT(7)
+#define QC_CHANGE_STATUS_6_BIT BIT(6)
+#define QC_9V_TO_12V_REASON_BIT BIT(5)
+#define QC_5V_TO_9V_REASON_BIT BIT(4)
+#define QC_CONTINUOUS_BIT BIT(3)
+#define QC_12V_BIT BIT(2)
+#define QC_9V_BIT BIT(1)
+#define QC_5V_BIT BIT(0)
+
+#define QC_PULSE_COUNT_STATUS_REG (USBIN_BASE + 0x0A)
+#define QC_PULSE_COUNT_STATUS_7_BIT BIT(7)
+#define QC_PULSE_COUNT_STATUS_6_BIT BIT(6)
+#define QC_PULSE_COUNT_MASK GENMASK(5, 0)
+
+#define TYPE_C_STATUS_1_REG (USBIN_BASE + 0x0B)
+#define UFP_TYPEC_MASK GENMASK(7, 5)
+#define UFP_TYPEC_RDSTD_BIT BIT(7)
+#define UFP_TYPEC_RD1P5_BIT BIT(6)
+#define UFP_TYPEC_RD3P0_BIT BIT(5)
+#define UFP_TYPEC_FMB_255K_BIT BIT(4)
+#define UFP_TYPEC_FMB_301K_BIT BIT(3)
+#define UFP_TYPEC_FMB_523K_BIT BIT(2)
+#define UFP_TYPEC_FMB_619K_BIT BIT(1)
+#define UFP_TYPEC_OPEN_OPEN_BIT BIT(0)
+
+#define TYPE_C_STATUS_2_REG (USBIN_BASE + 0x0C)
+#define DFP_RA_OPEN_BIT BIT(7)
+#define TIMER_STAGE_BIT BIT(6)
+#define EXIT_UFP_MODE_BIT BIT(5)
+#define EXIT_DFP_MODE_BIT BIT(4)
+#define DFP_TYPEC_MASK GENMASK(3, 0)
+#define DFP_RD_OPEN_BIT BIT(3)
+#define DFP_RD_RA_VCONN_BIT BIT(2)
+#define DFP_RD_RD_BIT BIT(1)
+#define DFP_RA_RA_BIT BIT(0)
+
+#define TYPE_C_STATUS_3_REG (USBIN_BASE + 0x0D)
+#define ENABLE_BANDGAP_BIT BIT(7)
+#define U_USB_GND_NOVBUS_BIT BIT(6)
+#define U_USB_FLOAT_NOVBUS_BIT BIT(5)
+#define U_USB_GND_BIT BIT(4)
+#define U_USB_FMB1_BIT BIT(3)
+#define U_USB_FLOAT1_BIT BIT(2)
+#define U_USB_FMB2_BIT BIT(1)
+#define U_USB_FLOAT2_BIT BIT(0)
+
+#define TYPE_C_STATUS_4_REG (USBIN_BASE + 0x0E)
+#define UFP_DFP_MODE_STATUS_BIT BIT(7)
+#define TYPEC_VBUS_STATUS_BIT BIT(6)
+#define TYPEC_VBUS_ERROR_STATUS_BIT BIT(5)
+#define TYPEC_DEBOUNCE_DONE_STATUS_BIT BIT(4)
+#define TYPEC_UFP_AUDIO_ADAPT_STATUS_BIT BIT(3)
+#define TYPEC_VCONN_OVERCURR_STATUS_BIT BIT(2)
+#define CC_ORIENTATION_BIT BIT(1)
+#define CC_ATTACHED_BIT BIT(0)
+
+#define TYPE_C_STATUS_5_REG (USBIN_BASE + 0x0F)
+#define TRY_SOURCE_FAILED_BIT BIT(6)
+#define TRY_SINK_FAILED_BIT BIT(5)
+#define TIMER_STAGE_2_BIT BIT(4)
+#define TYPEC_LEGACY_CABLE_STATUS_BIT BIT(3)
+#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT BIT(2)
+#define TYPEC_TRYSOURCE_DETECT_STATUS_BIT BIT(1)
+#define TYPEC_TRYSINK_DETECT_STATUS_BIT BIT(0)
+
+/* USBIN Interrupt Bits */
+#define TYPE_C_CHANGE_RT_STS_BIT BIT(7)
+#define USBIN_ICL_CHANGE_RT_STS_BIT BIT(6)
+#define USBIN_SOURCE_CHANGE_RT_STS_BIT BIT(5)
+#define USBIN_PLUGIN_RT_STS_BIT BIT(4)
+#define USBIN_OV_RT_STS_BIT BIT(3)
+#define USBIN_UV_RT_STS_BIT BIT(2)
+#define USBIN_LT_3P6V_RT_STS_BIT BIT(1)
+#define USBIN_COLLAPSE_RT_STS_BIT BIT(0)
+
+#define QC_PULSE_COUNT_STATUS_1_REG (USBIN_BASE + 0x30)
+
+#define USBIN_CMD_IL_REG (USBIN_BASE + 0x40)
+#define BAT_2_SYS_FET_DIS_BIT BIT(1)
+#define USBIN_SUSPEND_BIT BIT(0)
+
+#define CMD_APSD_REG (USBIN_BASE + 0x41)
+#define ICL_OVERRIDE_BIT BIT(1)
+#define APSD_RERUN_BIT BIT(0)
+
+#define CMD_HVDCP_2_REG (USBIN_BASE + 0x43)
+#define RESTART_AICL_BIT BIT(7)
+#define TRIGGER_AICL_BIT BIT(6)
+#define FORCE_12V_BIT BIT(5)
+#define FORCE_9V_BIT BIT(4)
+#define FORCE_5V_BIT BIT(3)
+#define IDLE_BIT BIT(2)
+#define SINGLE_DECREMENT_BIT BIT(1)
+#define SINGLE_INCREMENT_BIT BIT(0)
+
+#define USB_MISC2_REG (USBIN_BASE + 0x57)
+#define USB_MISC2_MASK GENMASK(1, 0)
+
+#define TYPE_C_CFG_REG (USBIN_BASE + 0x58)
+#define APSD_START_ON_CC_BIT BIT(7)
+#define WAIT_FOR_APSD_BIT BIT(6)
+#define FACTORY_MODE_DETECTION_EN_BIT BIT(5)
+#define FACTORY_MODE_ICL_3A_4A_BIT BIT(4)
+#define FACTORY_MODE_DIS_CHGING_CFG_BIT BIT(3)
+#define SUSPEND_NON_COMPLIANT_CFG_BIT BIT(2)
+#define VCONN_OC_CFG_BIT BIT(1)
+#define TYPE_C_OR_U_USB_BIT BIT(0)
+
+#define TYPE_C_CFG_2_REG (USBIN_BASE + 0x59)
+#define TYPE_C_DFP_CURRSRC_MODE_BIT BIT(7)
+#define VCONN_ILIM500MA_CFG_BIT BIT(6)
+#define VCONN_SOFTSTART_CFG_MASK GENMASK(5, 4)
+#define EN_TRY_SOURCE_MODE_BIT BIT(3)
+#define USB_FACTORY_MODE_ENABLE_BIT BIT(2)
+#define TYPE_C_UFP_MODE_BIT BIT(1)
+#define EN_80UA_180UA_CUR_SOURCE_BIT BIT(0)
+
+#define TYPE_C_CFG_3_REG (USBIN_BASE + 0x5A)
+#define TVBUS_DEBOUNCE_BIT BIT(7)
+#define TYPEC_LEGACY_CABLE_INT_EN_BIT BIT(6)
+#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN_BIT BIT(5)
+#define TYPEC_TRYSOURCE_DETECT_INT_EN_BIT BIT(4)
+#define TYPEC_TRYSINK_DETECT_INT_EN_BIT BIT(3)
+#define EN_TRYSINK_MODE_BIT BIT(2)
+#define EN_LEGACY_CABLE_DETECTION_BIT BIT(1)
+#define ALLOW_PD_DRING_UFP_TCCDB_BIT BIT(0)
+
+#define USBIN_ADAPTER_ALLOW_CFG_REG (USBIN_BASE + 0x60)
+#define USBIN_ADAPTER_ALLOW_MASK GENMASK(3, 0)
+enum {
+ USBIN_ADAPTER_ALLOW_5V = 0,
+ USBIN_ADAPTER_ALLOW_9V = 2,
+ USBIN_ADAPTER_ALLOW_5V_OR_9V = 3,
+ USBIN_ADAPTER_ALLOW_12V = 4,
+ USBIN_ADAPTER_ALLOW_5V_OR_12V = 5,
+ USBIN_ADAPTER_ALLOW_9V_TO_12V = 6,
+ USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V = 7,
+ USBIN_ADAPTER_ALLOW_5V_TO_9V = 8,
+ USBIN_ADAPTER_ALLOW_5V_TO_12V = 12,
+};
+
+#define USBIN_OPTIONS_1_CFG_REG (USBIN_BASE + 0x62)
+#define CABLE_R_SEL_BIT BIT(7)
+#define HVDCP_AUTH_ALG_EN_CFG_BIT BIT(6)
+#define HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT BIT(5)
+#define INPUT_PRIORITY_BIT BIT(4)
+#define AUTO_SRC_DETECT_BIT BIT(3)
+#define HVDCP_EN_BIT BIT(2)
+#define VADP_INCREMENT_VOLTAGE_LIMIT_BIT BIT(1)
+#define VADP_TAPER_TIMER_EN_BIT BIT(0)
+
+#define USBIN_OPTIONS_2_CFG_REG (USBIN_BASE + 0x63)
+#define WIPWR_RST_EUD_CFG_BIT BIT(7)
+#define SWITCHER_START_CFG_BIT BIT(6)
+#define DCD_TIMEOUT_SEL_BIT BIT(5)
+#define OCD_CURRENT_SEL_BIT BIT(4)
+#define SLOW_PLUGIN_TIMER_EN_CFG_BIT BIT(3)
+#define FLOAT_OPTIONS_MASK GENMASK(2, 0)
+#define FLOAT_DIS_CHGING_CFG_BIT BIT(2)
+#define SUSPEND_FLOAT_CFG_BIT BIT(1)
+#define FORCE_FLOAT_SDP_CFG_BIT BIT(0)
+
+#define TAPER_TIMER_SEL_CFG_REG (USBIN_BASE + 0x64)
+#define TYPEC_SPARE_CFG_BIT BIT(7)
+#define TAPER_TIMER_SEL_MASK GENMASK(1, 0)
+
+#define USBIN_LOAD_CFG_REG (USBIN_BASE + 0x65)
+#define USBIN_OV_CH_LOAD_OPTION_BIT BIT(7)
+#define ICL_OVERRIDE_AFTER_APSD_BIT BIT(4)
+
+#define USBIN_ICL_OPTIONS_REG (USBIN_BASE + 0x66)
+#define CFG_USB3P0_SEL_BIT BIT(2)
+#define USB51_MODE_BIT BIT(1)
+#define USBIN_MODE_CHG_BIT BIT(0)
+
+#define TYPE_C_INTRPT_ENB_REG (USBIN_BASE + 0x67)
+#define TYPEC_CCOUT_DETACH_INT_EN_BIT BIT(7)
+#define TYPEC_CCOUT_ATTACH_INT_EN_BIT BIT(6)
+#define TYPEC_VBUS_ERROR_INT_EN_BIT BIT(5)
+#define TYPEC_UFP_AUDIOADAPT_INT_EN_BIT BIT(4)
+#define TYPEC_DEBOUNCE_DONE_INT_EN_BIT BIT(3)
+#define TYPEC_CCSTATE_CHANGE_INT_EN_BIT BIT(2)
+#define TYPEC_VBUS_DEASSERT_INT_EN_BIT BIT(1)
+#define TYPEC_VBUS_ASSERT_INT_EN_BIT BIT(0)
+
+#define TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG (USBIN_BASE + 0x68)
+#define EXIT_SNK_BASED_ON_CC_BIT BIT(7)
+#define VCONN_EN_ORIENTATION_BIT BIT(6)
+#define TYPEC_VCONN_OVERCURR_INT_EN_BIT BIT(5)
+#define VCONN_EN_SRC_BIT BIT(4)
+#define VCONN_EN_VALUE_BIT BIT(3)
+#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0)
+#define UFP_EN_CMD_BIT BIT(2)
+#define DFP_EN_CMD_BIT BIT(1)
+#define TYPEC_DISABLE_CMD_BIT BIT(0)
+
+#define USBIN_SOURCE_CHANGE_INTRPT_ENB_REG (USBIN_BASE + 0x69)
+#define SLOW_IRQ_EN_CFG_BIT BIT(5)
+#define ENUMERATION_IRQ_EN_CFG_BIT BIT(4)
+#define VADP_IRQ_EN_CFG_BIT BIT(3)
+#define AUTH_IRQ_EN_CFG_BIT BIT(2)
+#define HVDCP_IRQ_EN_CFG_BIT BIT(1)
+#define APSD_IRQ_EN_CFG_BIT BIT(0)
+
+#define USBIN_CURRENT_LIMIT_CFG_REG (USBIN_BASE + 0x70)
+#define USBIN_CURRENT_LIMIT_MASK GENMASK(7, 0)
+
+#define USBIN_AICL_OPTIONS_CFG_REG (USBIN_BASE + 0x80)
+#define SUSPEND_ON_COLLAPSE_USBIN_BIT BIT(7)
+#define USBIN_AICL_HDC_EN_BIT BIT(6)
+#define USBIN_AICL_START_AT_MAX_BIT BIT(5)
+#define USBIN_AICL_RERUN_EN_BIT BIT(4)
+#define USBIN_AICL_ADC_EN_BIT BIT(3)
+#define USBIN_AICL_EN_BIT BIT(2)
+#define USBIN_HV_COLLAPSE_RESPONSE_BIT BIT(1)
+#define USBIN_LV_COLLAPSE_RESPONSE_BIT BIT(0)
+
+#define USBIN_5V_AICL_THRESHOLD_CFG_REG (USBIN_BASE + 0x81)
+#define USBIN_5V_AICL_THRESHOLD_CFG_MASK GENMASK(2, 0)
+
+#define USBIN_9V_AICL_THRESHOLD_CFG_REG (USBIN_BASE + 0x82)
+#define USBIN_9V_AICL_THRESHOLD_CFG_MASK GENMASK(2, 0)
+
+#define USBIN_12V_AICL_THRESHOLD_CFG_REG (USBIN_BASE + 0x83)
+#define USBIN_12V_AICL_THRESHOLD_CFG_MASK GENMASK(2, 0)
+
+#define USBIN_CONT_AICL_THRESHOLD_CFG_REG (USBIN_BASE + 0x84)
+#define USBIN_CONT_AICL_THRESHOLD_CFG_MASK GENMASK(5, 0)
+
+/* DCIN Peripheral Registers */
+#define DCIN_INPUT_STATUS_REG (DCIN_BASE + 0x06)
+#define DCIN_INPUT_STATUS_7_BIT BIT(7)
+#define DCIN_INPUT_STATUS_6_BIT BIT(6)
+#define DCIN_12V_BIT BIT(5)
+#define DCIN_9V_TO_12V_BIT BIT(4)
+#define DCIN_9V_BIT BIT(3)
+#define DCIN_5V_TO_12V_BIT BIT(2)
+#define DCIN_5V_TO_9V_BIT BIT(1)
+#define DCIN_5V_BIT BIT(0)
+
+#define WIPWR_STATUS_REG (DCIN_BASE + 0x07)
+#define WIPWR_STATUS_7_BIT BIT(7)
+#define WIPWR_STATUS_6_BIT BIT(6)
+#define WIPWR_STATUS_5_BIT BIT(5)
+#define DCIN_WIPWR_OV_DG_BIT BIT(4)
+#define DIV2_EN_DG_BIT BIT(3)
+#define SHUTDOWN_N_LATCH_BIT BIT(2)
+#define CHG_OK_PIN_BIT BIT(1)
+#define WIPWR_CHARGING_ENABLED_BIT BIT(0)
+
+#define WIPWR_RANGE_STATUS_REG (DCIN_BASE + 0x08)
+#define WIPWR_RANGE_STATUS_MASK GENMASK(4, 0)
+
+/* DCIN Interrupt Bits */
+#define WIPWR_VOLTAGE_RANGE_RT_STS_BIT BIT(7)
+#define DCIN_ICL_CHANGE_RT_STS_BIT BIT(6)
+#define DIV2_EN_DG_RT_STS_BIT BIT(5)
+#define DCIN_PLUGIN_RT_STS_BIT BIT(4)
+#define DCIN_OV_RT_STS_BIT BIT(3)
+#define DCIN_UV_RT_STS_BIT BIT(2)
+#define DCIN_LT_3P6V_RT_STS_BIT BIT(1)
+#define DCIN_COLLAPSE_RT_STS_BIT BIT(0)
+
+#define DCIN_CMD_IL_REG (DCIN_BASE + 0x40)
+#define WIRELESS_CHG_DIS_BIT BIT(3)
+#define SHDN_N_CLEAR_CMD_BIT BIT(2)
+#define SHDN_N_SET_CMD_BIT BIT(1)
+#define DCIN_SUSPEND_BIT BIT(0)
+
+#define DC_SPARE_REG (DCIN_BASE + 0x58)
+#define DC_SPARE_MASK GENMASK(3, 0)
+
+#define DCIN_ADAPTER_ALLOW_CFG_REG (DCIN_BASE + 0x60)
+#define DCIN_ADAPTER_ALLOW_MASK GENMASK(3, 0)
+
+#define DCIN_LOAD_CFG_REG (DCIN_BASE + 0x65)
+#define DCIN_OV_CH_LOAD_OPTION_BIT BIT(7)
+
+#define DCIN_CURRENT_LIMIT_CFG_REG (DCIN_BASE + 0x70)
+#define DCIN_CURRENT_LIMIT_MASK GENMASK(7, 0)
+
+#define DCIN_AICL_OPTIONS_CFG_REG (DCIN_BASE + 0x80)
+#define SUSPEND_ON_COLLAPSE_DCIN_BIT BIT(7)
+#define DCIN_AICL_HDC_EN_BIT BIT(6)
+#define DCIN_AICL_START_AT_MAX_BIT BIT(5)
+#define DCIN_AICL_RERUN_EN_BIT BIT(4)
+#define DCIN_AICL_ADC_EN_BIT BIT(3)
+#define DCIN_AICL_EN_BIT BIT(2)
+#define DCIN_HV_COLLAPSE_RESPONSE_BIT BIT(1)
+#define DCIN_LV_COLLAPSE_RESPONSE_BIT BIT(0)
+
+#define DCIN_AICL_REF_SEL_CFG_REG (DCIN_BASE + 0x81)
+#define DCIN_CONT_AICL_THRESHOLD_CFG_MASK GENMASK(5, 0)
+
+#define DCIN_ICL_START_CFG_REG (DCIN_BASE + 0x82)
+#define DCIN_ICL_START_CFG_BIT BIT(0)
+
+#define DIV2_EN_GF_TIME_CFG_REG (DCIN_BASE + 0x90)
+#define DIV2_EN_GF_TIME_CFG_MASK GENMASK(1, 0)
+
+#define WIPWR_IRQ_TMR_CFG_REG (DCIN_BASE + 0x91)
+#define WIPWR_IRQ_TMR_MASK GENMASK(2, 0)
+
+#define ZIN_ICL_PT_REG (DCIN_BASE + 0x92)
+#define ZIN_ICL_PT_MASK GENMASK(7, 0)
+
+#define ZIN_ICL_LV_REG (DCIN_BASE + 0x93)
+#define ZIN_ICL_LV_MASK GENMASK(7, 0)
+
+#define ZIN_ICL_HV_REG (DCIN_BASE + 0x94)
+#define ZIN_ICL_HV_MASK GENMASK(7, 0)
+
+#define WI_PWR_OPTIONS_REG (DCIN_BASE + 0x95)
+#define CHG_OK_BIT BIT(7)
+#define WIPWR_UVLO_IRQ_OPT_BIT BIT(6)
+#define BUCK_HOLDOFF_ENABLE_BIT BIT(5)
+#define CHG_OK_HW_SW_SELECT_BIT BIT(4)
+#define WIPWR_RST_ENABLE_BIT BIT(3)
+#define DCIN_WIPWR_IRQ_SELECT_BIT BIT(2)
+#define AICL_SWITCH_ENABLE_BIT BIT(1)
+#define ZIN_ICL_ENABLE_BIT BIT(0)
+
+#define ZIN_ICL_PT_HV_REG (DCIN_BASE + 0x96)
+#define ZIN_ICL_PT_HV_MASK GENMASK(7, 0)
+
+#define ZIN_ICL_MID_LV_REG (DCIN_BASE + 0x97)
+#define ZIN_ICL_MID_LV_MASK GENMASK(7, 0)
+
+#define ZIN_ICL_MID_HV_REG (DCIN_BASE + 0x98)
+#define ZIN_ICL_MID_HV_MASK GENMASK(7, 0)
+
+enum {
+ ZIN_ICL_PT_MAX_MV = 8000,
+ ZIN_ICL_PT_HV_MAX_MV = 9000,
+ ZIN_ICL_LV_MAX_MV = 5500,
+ ZIN_ICL_MID_LV_MAX_MV = 6500,
+ ZIN_ICL_MID_HV_MAX_MV = 8000,
+ ZIN_ICL_HV_MAX_MV = 11000,
+};
+
+#define DC_ENG_SSUPPLY_CFG2_REG (DCIN_BASE + 0xC1)
+#define ENG_SSUPPLY_IVREF_OTG_SS_MASK GENMASK(2, 0)
+#define OTG_SS_SLOW 0x3
+
+#define DC_ENG_SSUPPLY_CFG3_REG (DCIN_BASE + 0xC2)
+#define ENG_SSUPPLY_HI_CAP_BIT BIT(6)
+#define ENG_SSUPPLY_HI_RES_BIT BIT(5)
+#define ENG_SSUPPLY_CFG_SKIP_TH_V0P2_BIT BIT(3)
+#define ENG_SSUPPLY_CFG_SYSOV_TH_4P8_BIT BIT(2)
+#define ENG_SSUPPLY_5V_OV_OPT_BIT BIT(0)
+
+/* MISC Peripheral Registers */
+#define REVISION1_REG (MISC_BASE + 0x00)
+#define DIG_MINOR_MASK GENMASK(7, 0)
+
+#define REVISION2_REG (MISC_BASE + 0x01)
+#define DIG_MAJOR_MASK GENMASK(7, 0)
+
+#define REVISION3_REG (MISC_BASE + 0x02)
+#define ANA_MINOR_MASK GENMASK(7, 0)
+
+#define REVISION4_REG (MISC_BASE + 0x03)
+#define ANA_MAJOR_MASK GENMASK(7, 0)
+
+#define TEMP_RANGE_STATUS_REG (MISC_BASE + 0x06)
+#define TEMP_RANGE_STATUS_7_BIT BIT(7)
+#define THERM_REG_ACTIVE_BIT BIT(6)
+#define TLIM_BIT BIT(5)
+#define TEMP_RANGE_MASK GENMASK(4, 1)
+#define ALERT_LEVEL_BIT BIT(4)
+#define TEMP_ABOVE_RANGE_BIT BIT(3)
+#define TEMP_WITHIN_RANGE_BIT BIT(2)
+#define TEMP_BELOW_RANGE_BIT BIT(1)
+#define THERMREG_DISABLED_BIT BIT(0)
+
+#define ICL_STATUS_REG (MISC_BASE + 0x07)
+#define INPUT_CURRENT_LIMIT_MASK GENMASK(7, 0)
+
+#define ADAPTER_5V_ICL_STATUS_REG (MISC_BASE + 0x08)
+#define ADAPTER_5V_ICL_MASK GENMASK(7, 0)
+
+#define ADAPTER_9V_ICL_STATUS_REG (MISC_BASE + 0x09)
+#define ADAPTER_9V_ICL_MASK GENMASK(7, 0)
+
+#define AICL_STATUS_REG (MISC_BASE + 0x0A)
+#define AICL_STATUS_7_BIT BIT(7)
+#define SOFT_ILIMIT_BIT BIT(6)
+#define HIGHEST_DC_BIT BIT(5)
+#define USBIN_CH_COLLAPSE_BIT BIT(4)
+#define DCIN_CH_COLLAPSE_BIT BIT(3)
+#define ICL_IMIN_BIT BIT(2)
+#define AICL_FAIL_BIT BIT(1)
+#define AICL_DONE_BIT BIT(0)
+
+#define POWER_PATH_STATUS_REG (MISC_BASE + 0x0B)
+#define INPUT_SS_DONE_BIT BIT(7)
+#define USBIN_SUSPEND_STS_BIT BIT(6)
+#define DCIN_SUSPEND_STS_BIT BIT(5)
+#define USE_USBIN_BIT BIT(4)
+#define USE_DCIN_BIT BIT(3)
+#define POWER_PATH_MASK GENMASK(2, 1)
+#define VALID_INPUT_POWER_SOURCE_STS_BIT BIT(0)
+
+#define WDOG_STATUS_REG (MISC_BASE + 0x0C)
+#define WDOG_STATUS_7_BIT BIT(7)
+#define WDOG_STATUS_6_BIT BIT(6)
+#define WDOG_STATUS_5_BIT BIT(5)
+#define WDOG_STATUS_4_BIT BIT(4)
+#define WDOG_STATUS_3_BIT BIT(3)
+#define WDOG_STATUS_2_BIT BIT(2)
+#define WDOG_STATUS_1_BIT BIT(1)
+#define BARK_BITE_STATUS_BIT BIT(0)
+
+#define SYSOK_REASON_STATUS_REG (MISC_BASE + 0x0D)
+#define SYSOK_REASON_DCIN_BIT BIT(1)
+#define SYSOK_REASON_USBIN_BIT BIT(0)
+
+/* MISC Interrupt Bits */
+#define SWITCHER_POWER_OK_RT_STS_BIT BIT(7)
+#define TEMPERATURE_CHANGE_RT_STS_BIT BIT(6)
+#define INPUT_CURRENT_LIMITING_RT_STS_BIT BIT(5)
+#define HIGH_DUTY_CYCLE_RT_STS_BIT BIT(4)
+#define AICL_DONE_RT_STS_BIT BIT(3)
+#define AICL_FAIL_RT_STS_BIT BIT(2)
+#define WDOG_BARK_RT_STS_BIT BIT(1)
+#define WDOG_SNARL_RT_STS_BIT BIT(0)
+
+#define WDOG_RST_REG (MISC_BASE + 0x40)
+#define WDOG_RST_BIT BIT(0)
+
+#define AFP_MODE_REG (MISC_BASE + 0x41)
+#define AFP_MODE_EN_BIT BIT(0)
+
+#define GSM_PA_ON_ADJ_EN_REG (MISC_BASE + 0x42)
+#define GSM_PA_ON_ADJ_EN_BIT BIT(0)
+
+#define BARK_BITE_WDOG_PET_REG (MISC_BASE + 0x43)
+#define BARK_BITE_WDOG_PET_BIT BIT(0)
+
+#define PHYON_CMD_REG (MISC_BASE + 0x44)
+#define PHYON_CMD_BIT BIT(0)
+
+#define SHDN_CMD_REG (MISC_BASE + 0x45)
+#define SHDN_CMD_BIT BIT(0)
+
+#define FINISH_COPY_COMMAND_REG (MISC_BASE + 0x4F)
+#define START_COPY_BIT BIT(0)
+
+#define WD_CFG_REG (MISC_BASE + 0x51)
+#define WATCHDOG_TRIGGER_AFP_EN_BIT BIT(7)
+#define BARK_WDOG_INT_EN_BIT BIT(6)
+#define BITE_WDOG_INT_EN_BIT BIT(5)
+#define SFT_AFTER_WDOG_IRQ_MASK GENMASK(4, 3)
+#define WDOG_IRQ_SFT_BIT BIT(2)
+#define WDOG_TIMER_EN_ON_PLUGIN_BIT BIT(1)
+#define WDOG_TIMER_EN_BIT BIT(0)
+
+#define MISC_CFG_REG (MISC_BASE + 0x52)
+#define GSM_PA_ON_ADJ_SEL_BIT BIT(0)
+#define STAT_PARALLEL_1400MA_EN_CFG_BIT BIT(3)
+#define TCC_DEBOUNCE_20MS_BIT BIT(5)
+
+#define SNARL_BARK_BITE_WD_CFG_REG (MISC_BASE + 0x53)
+#define BITE_WDOG_DISABLE_CHARGING_CFG_BIT BIT(7)
+#define SNARL_WDOG_TIMEOUT_MASK GENMASK(6, 4)
+#define BARK_WDOG_TIMEOUT_MASK GENMASK(3, 2)
+#define BITE_WDOG_TIMEOUT_MASK GENMASK(1, 0)
+
+#define PHYON_CFG_REG (MISC_BASE + 0x54)
+#define USBPHYON_PUSHPULL_CFG_BIT BIT(1)
+#define PHYON_SW_SEL_BIT BIT(0)
+
+#define CHGR_TRIM_OPTIONS_7_0_REG (MISC_BASE + 0x55)
+#define TLIM_DIS_TBIT_BIT BIT(0)
+
+#define CH_OV_OPTION_CFG_REG (MISC_BASE + 0x56)
+#define OV_OPTION_TBIT_BIT BIT(0)
+
+#define AICL_CFG_REG (MISC_BASE + 0x60)
+#define TREG_ALLOW_DECREASE_BIT BIT(1)
+#define AICL_HIGH_DC_INC_BIT BIT(0)
+
+#define AICL_RERUN_TIME_CFG_REG (MISC_BASE + 0x61)
+#define AICL_RERUN_TIME_MASK GENMASK(1, 0)
+
+#define AICL_RERUN_TEMP_TIME_CFG_REG (MISC_BASE + 0x62)
+#define AICL_RERUN_TEMP_TIME_MASK GENMASK(1, 0)
+
+#define THERMREG_SRC_CFG_REG (MISC_BASE + 0x70)
+#define SKIN_ADC_CFG_BIT BIT(3)
+#define THERMREG_SKIN_ADC_SRC_EN_BIT BIT(2)
+#define THERMREG_DIE_ADC_SRC_EN_BIT BIT(1)
+#define THERMREG_DIE_CMP_SRC_EN_BIT BIT(0)
+
+#define TREG_DIE_CMP_INC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x71)
+#define TREG_DIE_CMP_INC_CYCLE_TIME_MASK GENMASK(1, 0)
+
+#define TREG_DIE_CMP_DEC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x72)
+#define TREG_DIE_CMP_DEC_CYCLE_TIME_MASK GENMASK(1, 0)
+
+#define TREG_DIE_ADC_INC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x73)
+#define TREG_DIE_ADC_INC_CYCLE_TIME_MASK GENMASK(1, 0)
+
+#define TREG_DIE_ADC_DEC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x74)
+#define TREG_DIE_ADC_DEC_CYCLE_TIME_MASK GENMASK(1, 0)
+
+#define TREG_SKIN_ADC_INC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x75)
+#define TREG_SKIN_ADC_INC_CYCLE_TIME_MASK GENMASK(1, 0)
+
+#define TREG_SKIN_ADC_DEC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x76)
+#define TREG_SKIN_ADC_DEC_CYCLE_TIME_MASK GENMASK(1, 0)
+
+#define BUCK_OPTIONS_CFG_REG (MISC_BASE + 0x80)
+#define CHG_EN_PIN_SUSPEND_CFG_BIT BIT(6)
+#define HICCUP_OPTIONS_MASK GENMASK(5, 4)
+#define INPUT_CURRENT_LIMIT_SOFTSTART_EN_BIT BIT(3)
+#define HV_HIGH_DUTY_CYCLE_PROTECT_EN_BIT BIT(2)
+#define BUCK_OC_PROTECT_EN_BIT BIT(1)
+#define INPUT_MISS_POLL_EN_BIT BIT(0)
+
+#define ICL_SOFTSTART_RATE_CFG_REG (MISC_BASE + 0x81)
+#define ICL_SOFTSTART_RATE_MASK GENMASK(1, 0)
+
+#define ICL_SOFTSTOP_RATE_CFG_REG (MISC_BASE + 0x82)
+#define ICL_SOFTSTOP_RATE_MASK GENMASK(1, 0)
+
+#define VSYS_MIN_SEL_CFG_REG (MISC_BASE + 0x83)
+#define VSYS_MIN_SEL_MASK GENMASK(1, 0)
+
+#define TRACKING_VOLTAGE_SEL_CFG_REG (MISC_BASE + 0x84)
+#define TRACKING_VOLTAGE_SEL_BIT BIT(0)
+
+#define STAT_CFG_REG (MISC_BASE + 0x90)
+#define STAT_SW_OVERRIDE_VALUE_BIT BIT(7)
+#define STAT_SW_OVERRIDE_CFG_BIT BIT(6)
+#define STAT_PARALLEL_OFF_DG_CFG_MASK GENMASK(5, 4)
+#define STAT_POLARITY_CFG_BIT BIT(3)
+#define STAT_PARALLEL_CFG_BIT BIT(2)
+#define STAT_FUNCTION_CFG_BIT BIT(1)
+#define STAT_IRQ_PULSING_EN_BIT BIT(0)
+
+#define LBC_EN_CFG_REG (MISC_BASE + 0x91)
+#define LBC_DURING_CHARGING_CFG_BIT BIT(1)
+#define LBC_EN_BIT BIT(0)
+
+#define LBC_PERIOD_CFG_REG (MISC_BASE + 0x92)
+#define LBC_PERIOD_MASK GENMASK(2, 0)
+
+#define LBC_DUTY_CYCLE_CFG_REG (MISC_BASE + 0x93)
+#define LBC_DUTY_CYCLE_MASK GENMASK(2, 0)
+
+#define SYSOK_CFG_REG (MISC_BASE + 0x94)
+#define SYSOK_PUSHPULL_CFG_BIT BIT(5)
+#define SYSOK_B_OR_C_SEL_BIT BIT(4)
+#define SYSOK_POL_BIT BIT(3)
+#define SYSOK_OPTIONS_MASK GENMASK(2, 0)
+
+#define CFG_BUCKBOOST_FREQ_SELECT_BUCK_REG (MISC_BASE + 0xA0)
+#define CFG_BUCKBOOST_FREQ_SELECT_BOOST_REG (MISC_BASE + 0xA1)
+
+#define TM_IO_DTEST4_SEL (MISC_BASE + 0xE9)
+
+/* CHGR FREQ Peripheral registers */
+#define FREQ_CLK_DIV_REG (CHGR_FREQ_BASE + 0x50)
+
+#endif /* __SMB2_CHARGER_REG_H */
diff --git a/drivers/power/supply/qcom/smb1351-charger.c b/drivers/power/supply/qcom/smb1351-charger.c
new file mode 100644
index 000000000000..f5c8252b5e41
--- /dev/null
+++ b/drivers/power/supply/qcom/smb1351-charger.c
@@ -0,0 +1,3344 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/i2c.h>
+#include <linux/debugfs.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include <linux/of.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/pinctrl/consumer.h>
+
+/* Mask/Bit helpers */
+#define _SMB1351_MASK(BITS, POS) \
+ ((unsigned char)(((1 << (BITS)) - 1) << (POS)))
+#define SMB1351_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \
+ _SMB1351_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \
+ (RIGHT_BIT_POS))
+
+/* Configuration registers */
+#define CHG_CURRENT_CTRL_REG 0x0
+#define FAST_CHG_CURRENT_MASK SMB1351_MASK(7, 4)
+#define AC_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(3, 0)
+
+#define CHG_OTH_CURRENT_CTRL_REG 0x1
+#define PRECHG_CURRENT_MASK SMB1351_MASK(7, 5)
+#define ITERM_MASK SMB1351_MASK(4, 2)
+#define USB_2_3_MODE_SEL_BIT BIT(1)
+#define USB_2_3_MODE_SEL_BY_I2C 0
+#define USB_2_3_MODE_SEL_BY_PIN 0x2
+#define USB_5_1_CMD_POLARITY_BIT BIT(0)
+#define USB_CMD_POLARITY_500_1_100_0 0
+#define USB_CMD_POLARITY_500_0_100_1 0x1
+
+#define VARIOUS_FUNC_REG 0x2
+#define SUSPEND_MODE_CTRL_BIT BIT(7)
+#define SUSPEND_MODE_CTRL_BY_PIN 0
+#define SUSPEND_MODE_CTRL_BY_I2C 0x80
+#define BATT_TO_SYS_POWER_CTRL_BIT BIT(6)
+#define MAX_SYS_VOLTAGE BIT(5)
+#define AICL_EN_BIT BIT(4)
+#define AICL_DET_TH_BIT BIT(3)
+#define APSD_EN_BIT BIT(2)
+#define BATT_OV_BIT BIT(1)
+#define VCHG_FUNC_BIT BIT(0)
+
+#define VFLOAT_REG 0x3
+#define PRECHG_TO_FAST_VOLTAGE_CFG_MASK SMB1351_MASK(7, 6)
+#define VFLOAT_MASK SMB1351_MASK(5, 0)
+
+#define CHG_CTRL_REG 0x4
+#define AUTO_RECHG_BIT BIT(7)
+#define AUTO_RECHG_ENABLE 0
+#define AUTO_RECHG_DISABLE 0x80
+#define ITERM_EN_BIT BIT(6)
+#define ITERM_ENABLE 0
+#define ITERM_DISABLE 0x40
+#define MAPPED_AC_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(5, 4)
+#define AUTO_RECHG_TH_BIT BIT(3)
+#define AUTO_RECHG_TH_50MV 0
+#define AUTO_RECHG_TH_100MV 0x8
+#define AFCV_MASK SMB1351_MASK(2, 0)
+
+#define CHG_STAT_TIMERS_CTRL_REG 0x5
+#define STAT_OUTPUT_POLARITY_BIT BIT(7)
+#define STAT_OUTPUT_MODE_BIT BIT(6)
+#define STAT_OUTPUT_CTRL_BIT BIT(5)
+#define OTH_CHG_IL_BIT BIT(4)
+#define COMPLETE_CHG_TIMEOUT_MASK SMB1351_MASK(3, 2)
+#define PRECHG_TIMEOUT_MASK SMB1351_MASK(1, 0)
+
+#define CHG_PIN_EN_CTRL_REG 0x6
+#define LED_BLINK_FUNC_BIT BIT(7)
+#define EN_PIN_CTRL_MASK SMB1351_MASK(6, 5)
+#define EN_BY_I2C_0_DISABLE 0
+#define EN_BY_I2C_0_ENABLE 0x20
+#define EN_BY_PIN_HIGH_ENABLE 0x40
+#define EN_BY_PIN_LOW_ENABLE 0x60
+#define USBCS_CTRL_BIT BIT(4)
+#define USBCS_CTRL_BY_I2C 0
+#define USBCS_CTRL_BY_PIN 0x10
+#define USBCS_INPUT_STATE_BIT BIT(3)
+#define CHG_ERR_BIT BIT(2)
+#define APSD_DONE_BIT BIT(1)
+#define USB_FAIL_BIT BIT(0)
+
+#define THERM_A_CTRL_REG 0x7
+#define MIN_SYS_VOLTAGE_MASK SMB1351_MASK(7, 6)
+#define LOAD_BATT_10MA_FVC_BIT BIT(5)
+#define THERM_MONITOR_BIT BIT(4)
+#define THERM_MONITOR_EN 0
+#define SOFT_COLD_TEMP_LIMIT_MASK SMB1351_MASK(3, 2)
+#define SOFT_HOT_TEMP_LIMIT_MASK SMB1351_MASK(1, 0)
+
+#define WDOG_SAFETY_TIMER_CTRL_REG 0x8
+#define AICL_FAIL_OPTION_BIT BIT(7)
+#define AICL_FAIL_TO_SUSPEND 0
+#define AICL_FAIL_TO_150_MA 0x80
+#define WDOG_TIMEOUT_MASK SMB1351_MASK(6, 5)
+#define WDOG_IRQ_SAFETY_TIMER_MASK SMB1351_MASK(4, 3)
+#define WDOG_IRQ_SAFETY_TIMER_EN_BIT BIT(2)
+#define WDOG_OPTION_BIT BIT(1)
+#define WDOG_TIMER_EN_BIT BIT(0)
+
+#define OTG_USBIN_AICL_CTRL_REG 0x9
+#define OTG_ID_PIN_CTRL_MASK SMB1351_MASK(7, 6)
+#define OTG_PIN_POLARITY_BIT BIT(5)
+#define DCIN_IC_GLITCH_FILTER_HV_ADAPTER_MASK SMB1351_MASK(4, 3)
+#define DCIN_IC_GLITCH_FILTER_LV_ADAPTER_BIT BIT(2)
+#define USBIN_AICL_CFG1_BIT BIT(1)
+#define USBIN_AICL_CFG0_BIT BIT(0)
+
+#define OTG_TLIM_CTRL_REG 0xA
+#define SWITCH_FREQ_MASK SMB1351_MASK(7, 6)
+#define THERM_LOOP_TEMP_SEL_MASK SMB1351_MASK(5, 4)
+#define OTG_OC_LIMIT_MASK SMB1351_MASK(3, 2)
+#define OTG_BATT_UVLO_TH_MASK SMB1351_MASK(1, 0)
+
+#define HARD_SOFT_LIMIT_CELL_TEMP_REG 0xB
+#define HARD_LIMIT_COLD_TEMP_ALARM_TRIP_MASK SMB1351_MASK(7, 6)
+#define HARD_LIMIT_HOT_TEMP_ALARM_TRIP_MASK SMB1351_MASK(5, 4)
+#define SOFT_LIMIT_COLD_TEMP_ALARM_TRIP_MASK SMB1351_MASK(3, 2)
+#define SOFT_LIMIT_HOT_TEMP_ALARM_TRIP_MASK SMB1351_MASK(1, 0)
+
+#define FAULT_INT_REG 0xC
+#define HOT_COLD_HARD_LIMIT_BIT BIT(7)
+#define HOT_COLD_SOFT_LIMIT_BIT BIT(6)
+#define BATT_UVLO_IN_OTG_BIT BIT(5)
+#define OTG_OC_BIT BIT(4)
+#define INPUT_OVLO_BIT BIT(3)
+#define INPUT_UVLO_BIT BIT(2)
+#define AICL_DONE_FAIL_BIT BIT(1)
+#define INTERNAL_OVER_TEMP_BIT BIT(0)
+
+#define STATUS_INT_REG 0xD
+#define CHG_OR_PRECHG_TIMEOUT_BIT BIT(7)
+#define RID_CHANGE_BIT BIT(6)
+#define BATT_OVP_BIT BIT(5)
+#define FAST_TERM_TAPER_RECHG_INHIBIT_BIT BIT(4)
+#define WDOG_TIMER_BIT BIT(3)
+#define POK_BIT BIT(2)
+#define BATT_MISSING_BIT BIT(1)
+#define BATT_LOW_BIT BIT(0)
+
+#define VARIOUS_FUNC_2_REG 0xE
+#define CHG_HOLD_OFF_TIMER_AFTER_PLUGIN_BIT BIT(7)
+#define CHG_INHIBIT_BIT BIT(6)
+#define FAST_CHG_CC_IN_BATT_SOFT_LIMIT_MODE_BIT BIT(5)
+#define FVCL_IN_BATT_SOFT_LIMIT_MODE_MASK SMB1351_MASK(4, 3)
+#define HARD_TEMP_LIMIT_BEHAVIOR_BIT BIT(2)
+#define PRECHG_TO_FASTCHG_BIT BIT(1)
+#define STAT_PIN_CONFIG_BIT BIT(0)
+
+#define FLEXCHARGER_REG 0x10
+#define AFVC_IRQ_BIT BIT(7)
+#define CHG_CONFIG_MASK SMB1351_MASK(6, 4)
+#define LOW_BATT_VOLTAGE_DET_TH_MASK SMB1351_MASK(3, 0)
+
+#define VARIOUS_FUNC_3_REG 0x11
+#define SAFETY_TIMER_EN_MASK SMB1351_MASK(7, 6)
+#define BLOCK_SUSPEND_DURING_VBATT_LOW_BIT BIT(5)
+#define TIMEOUT_SEL_FOR_APSD_BIT BIT(4)
+#define SDP_SUSPEND_BIT BIT(3)
+#define QC_2P1_AUTO_INCREMENT_MODE_BIT BIT(2)
+#define QC_2P1_AUTH_ALGO_BIT BIT(1)
+#define DCD_EN_BIT BIT(0)
+
+#define HVDCP_BATT_MISSING_CTRL_REG 0x12
+#define HVDCP_ADAPTER_SEL_MASK SMB1351_MASK(7, 6)
+#define HVDCP_EN_BIT BIT(5)
+#define HVDCP_AUTO_INCREMENT_LIMIT_BIT BIT(4)
+#define BATT_MISSING_ON_INPUT_PLUGIN_BIT BIT(3)
+#define BATT_MISSING_2P6S_POLLER_BIT BIT(2)
+#define BATT_MISSING_ALGO_BIT BIT(1)
+#define BATT_MISSING_THERM_PIN_SOURCE_BIT BIT(0)
+
+#define PON_OPTIONS_REG 0x13
+#define SYSOK_INOK_POLARITY_BIT BIT(7)
+#define SYSOK_OPTIONS_MASK SMB1351_MASK(6, 4)
+#define INPUT_MISSING_POLLER_CONFIG_BIT BIT(3)
+#define VBATT_LOW_DISABLED_OR_RESET_STATE_BIT BIT(2)
+#define QC_2P1_AUTH_ALGO_IRQ_EN_BIT BIT(0)
+
+#define OTG_MODE_POWER_OPTIONS_REG 0x14
+#define ADAPTER_CONFIG_MASK SMB1351_MASK(7, 6)
+#define MAP_HVDCP_BIT BIT(5)
+#define SDP_LOW_BATT_FORCE_USB5_OVER_USB1_BIT BIT(4)
+#define OTG_HICCUP_MODE_BIT BIT(2)
+#define INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(1, 0)
+
+#define CHARGER_I2C_CTRL_REG 0x15
+#define FULLON_MODE_EN_BIT BIT(7)
+#define I2C_HS_MODE_EN_BIT BIT(6)
+#define SYSON_LDO_OUTPUT_SEL_BIT BIT(5)
+#define VBATT_TRACKING_VOLTAGE_DIFF_BIT BIT(4)
+#define DISABLE_AFVC_WHEN_ENTER_TAPER_BIT BIT(3)
+#define VCHG_IINV_BIT BIT(2)
+#define AFVC_OVERRIDE_BIT BIT(1)
+#define SYSOK_PIN_CONFIG_BIT BIT(0)
+
+#define VERSION_REG 0x2E
+#define VERSION_MASK BIT(1)
+
+/* Command registers */
+#define CMD_I2C_REG 0x30
+#define CMD_RELOAD_BIT BIT(7)
+#define CMD_BQ_CFG_ACCESS_BIT BIT(6)
+
+#define CMD_INPUT_LIMIT_REG 0x31
+#define CMD_OVERRIDE_BIT BIT(7)
+#define CMD_SUSPEND_MODE_BIT BIT(6)
+#define CMD_INPUT_CURRENT_MODE_BIT BIT(3)
+#define CMD_INPUT_CURRENT_MODE_APSD 0
+#define CMD_INPUT_CURRENT_MODE_CMD 0x08
+#define CMD_USB_2_3_SEL_BIT BIT(2)
+#define CMD_USB_2_MODE 0
+#define CMD_USB_3_MODE 0x4
+#define CMD_USB_1_5_AC_CTRL_MASK SMB1351_MASK(1, 0)
+#define CMD_USB_100_MODE 0
+#define CMD_USB_500_MODE 0x2
+#define CMD_USB_AC_MODE 0x1
+
+#define CMD_CHG_REG 0x32
+#define CMD_DISABLE_THERM_MONITOR_BIT BIT(4)
+#define CMD_TURN_OFF_STAT_PIN_BIT BIT(3)
+#define CMD_PRE_TO_FAST_EN_BIT BIT(2)
+#define CMD_CHG_EN_BIT BIT(1)
+#define CMD_CHG_DISABLE 0
+#define CMD_CHG_ENABLE 0x2
+#define CMD_OTG_EN_BIT BIT(0)
+
+#define CMD_DEAD_BATT_REG 0x33
+#define CMD_STOP_DEAD_BATT_TIMER_MASK SMB1351_MASK(7, 0)
+
+#define CMD_HVDCP_REG 0x34
+#define CMD_APSD_RE_RUN_BIT BIT(7)
+#define CMD_FORCE_HVDCP_2P0_BIT BIT(5)
+#define CMD_HVDCP_MODE_MASK SMB1351_MASK(5, 0)
+
+/* Status registers */
+#define STATUS_0_REG 0x36
+#define STATUS_AICL_BIT BIT(7)
+#define STATUS_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(6, 5)
+#define STATUS_DCIN_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(4, 0)
+
+#define STATUS_1_REG 0x37
+#define STATUS_INPUT_RANGE_MASK SMB1351_MASK(7, 4)
+#define STATUS_INPUT_USB_BIT BIT(0)
+
+#define STATUS_2_REG 0x38
+#define STATUS_FAST_CHG_BIT BIT(7)
+#define STATUS_HARD_LIMIT_BIT BIT(6)
+#define STATUS_FLOAT_VOLTAGE_MASK SMB1351_MASK(5, 0)
+
+#define STATUS_3_REG 0x39
+#define STATUS_CHG_BIT BIT(7)
+#define STATUS_PRECHG_CURRENT_MASK SMB1351_MASK(6, 4)
+#define STATUS_FAST_CHG_CURRENT_MASK SMB1351_MASK(3, 0)
+
+#define STATUS_4_REG 0x3A
+#define STATUS_OTG_BIT BIT(7)
+#define STATUS_AFVC_BIT BIT(6)
+#define STATUS_DONE_BIT BIT(5)
+#define STATUS_BATT_LESS_THAN_2V_BIT BIT(4)
+#define STATUS_HOLD_OFF_BIT BIT(3)
+#define STATUS_CHG_MASK SMB1351_MASK(2, 1)
+#define STATUS_NO_CHARGING 0
+#define STATUS_FAST_CHARGING 0x4
+#define STATUS_PRE_CHARGING 0x2
+#define STATUS_TAPER_CHARGING 0x6
+#define STATUS_CHG_EN_STATUS_BIT BIT(0)
+
+#define STATUS_5_REG 0x3B
+#define STATUS_SOURCE_DETECTED_MASK SMB1351_MASK(7, 0)
+#define STATUS_PORT_CDP 0x80
+#define STATUS_PORT_DCP 0x40
+#define STATUS_PORT_OTHER 0x20
+#define STATUS_PORT_SDP 0x10
+#define STATUS_PORT_ACA_A 0x8
+#define STATUS_PORT_ACA_B 0x4
+#define STATUS_PORT_ACA_C 0x2
+#define STATUS_PORT_ACA_DOCK 0x1
+
+#define STATUS_6_REG 0x3C
+#define STATUS_DCD_TIMEOUT_BIT BIT(7)
+#define STATUS_DCD_GOOD_DG_BIT BIT(6)
+#define STATUS_OCD_GOOD_DG_BIT BIT(5)
+#define STATUS_RID_ABD_DG_BIT BIT(4)
+#define STATUS_RID_FLOAT_STATE_MACHINE_BIT BIT(3)
+#define STATUS_RID_A_STATE_MACHINE_BIT BIT(2)
+#define STATUS_RID_B_STATE_MACHINE_BIT BIT(1)
+#define STATUS_RID_C_STATE_MACHINE_BIT BIT(0)
+
+#define STATUS_7_REG 0x3D
+#define STATUS_HVDCP_MASK SMB1351_MASK(7, 0)
+
+#define STATUS_8_REG 0x3E
+#define STATUS_USNIN_HV_INPUT_SEL_BIT BIT(5)
+#define STATUS_USBIN_LV_UNDER_INPUT_SEL_BIT BIT(4)
+#define STATUS_USBIN_LV_INPUT_SEL_BIT BIT(3)
+
+/* Revision register */
+#define CHG_REVISION_REG 0x3F
+#define GUI_REVISION_MASK SMB1351_MASK(7, 4)
+#define DEVICE_REVISION_MASK SMB1351_MASK(3, 0)
+
+/* IRQ status registers */
+#define IRQ_A_REG 0x40
+#define IRQ_HOT_HARD_BIT BIT(6)
+#define IRQ_COLD_HARD_BIT BIT(4)
+#define IRQ_HOT_SOFT_BIT BIT(2)
+#define IRQ_COLD_SOFT_BIT BIT(0)
+
+#define IRQ_B_REG 0x41
+#define IRQ_BATT_TERMINAL_REMOVED_BIT BIT(6)
+#define IRQ_BATT_MISSING_BIT BIT(4)
+#define IRQ_LOW_BATT_VOLTAGE_BIT BIT(2)
+#define IRQ_INTERNAL_TEMP_LIMIT_BIT BIT(0)
+
+#define IRQ_C_REG 0x42
+#define IRQ_PRE_TO_FAST_VOLTAGE_BIT BIT(6)
+#define IRQ_RECHG_BIT BIT(4)
+#define IRQ_TAPER_BIT BIT(2)
+#define IRQ_TERM_BIT BIT(0)
+
+#define IRQ_D_REG 0x43
+#define IRQ_BATT_OV_BIT BIT(6)
+#define IRQ_CHG_ERROR_BIT BIT(4)
+#define IRQ_CHG_TIMEOUT_BIT BIT(2)
+#define IRQ_PRECHG_TIMEOUT_BIT BIT(0)
+
+#define IRQ_E_REG 0x44
+#define IRQ_USBIN_OV_BIT BIT(6)
+#define IRQ_USBIN_UV_BIT BIT(4)
+#define IRQ_AFVC_BIT BIT(2)
+#define IRQ_POWER_OK_BIT BIT(0)
+
+#define IRQ_F_REG 0x45
+#define IRQ_OTG_OVER_CURRENT_BIT BIT(6)
+#define IRQ_OTG_FAIL_BIT BIT(4)
+#define IRQ_RID_BIT BIT(2)
+#define IRQ_OTG_OC_RETRY_BIT BIT(0)
+
+#define IRQ_G_REG 0x46
+#define IRQ_SOURCE_DET_BIT BIT(6)
+#define IRQ_AICL_DONE_BIT BIT(4)
+#define IRQ_AICL_FAIL_BIT BIT(2)
+#define IRQ_CHG_INHIBIT_BIT BIT(0)
+
+#define IRQ_H_REG 0x47
+#define IRQ_IC_LIMIT_STATUS_BIT BIT(5)
+#define IRQ_HVDCP_2P1_STATUS_BIT BIT(4)
+#define IRQ_HVDCP_AUTH_DONE_BIT BIT(2)
+#define IRQ_WDOG_TIMEOUT_BIT BIT(0)
+
+/* constants */
+#define USB2_MIN_CURRENT_MA 100
+#define USB2_MAX_CURRENT_MA 500
+#define USB3_MIN_CURRENT_MA 150
+#define USB3_MAX_CURRENT_MA 900
+#define SMB1351_IRQ_REG_COUNT 8
+#define SMB1351_CHG_PRE_MIN_MA 100
+#define SMB1351_CHG_FAST_MIN_MA 1000
+#define SMB1351_CHG_FAST_MAX_MA 4500
+#define SMB1351_CHG_PRE_SHIFT 5
+#define SMB1351_CHG_FAST_SHIFT 4
+#define DEFAULT_BATT_CAPACITY 50
+#define DEFAULT_BATT_TEMP 250
+#define SUSPEND_CURRENT_MA 2
+
+#define CHG_ITERM_200MA 0x0
+#define CHG_ITERM_300MA 0x04
+#define CHG_ITERM_400MA 0x08
+#define CHG_ITERM_500MA 0x0C
+#define CHG_ITERM_600MA 0x10
+#define CHG_ITERM_700MA 0x14
+
+#define ADC_TM_WARM_COOL_THR_ENABLE ADC_TM_HIGH_LOW_THR_ENABLE
+
+enum reason {
+ USER = BIT(0),
+ THERMAL = BIT(1),
+ CURRENT = BIT(2),
+ SOC = BIT(3),
+};
+
+static char *pm_batt_supplied_to[] = {
+ "bms",
+};
+
+struct smb1351_regulator {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+};
+
+enum chip_version {
+ SMB_UNKNOWN = 0,
+ SMB1350,
+ SMB1351,
+ SMB_MAX_TYPE,
+};
+
+static const char *smb1351_version_str[SMB_MAX_TYPE] = {
+ [SMB_UNKNOWN] = "Unknown",
+ [SMB1350] = "SMB1350",
+ [SMB1351] = "SMB1351",
+};
+
+struct smb1351_charger {
+ struct i2c_client *client;
+ struct device *dev;
+
+ bool recharge_disabled;
+ int recharge_mv;
+ bool iterm_disabled;
+ int iterm_ma;
+ int vfloat_mv;
+ int chg_present;
+ int fake_battery_soc;
+ bool chg_autonomous_mode;
+ bool disable_apsd;
+ bool using_pmic_therm;
+ bool jeita_supported;
+ bool battery_missing;
+ const char *bms_psy_name;
+ bool resume_completed;
+ bool irq_waiting;
+ struct delayed_work chg_remove_work;
+ struct delayed_work hvdcp_det_work;
+
+ /* status tracking */
+ bool batt_full;
+ bool batt_hot;
+ bool batt_cold;
+ bool batt_warm;
+ bool batt_cool;
+
+ int battchg_disabled_status;
+ int usb_suspended_status;
+ int target_fastchg_current_max_ma;
+ int fastchg_current_max_ma;
+ int workaround_flags;
+
+ int parallel_pin_polarity_setting;
+ int parallel_mode;
+ bool parallel_charger;
+ bool parallel_charger_suspended;
+ bool bms_controlled_charging;
+ bool apsd_rerun;
+ bool usbin_ov;
+ bool chg_remove_work_scheduled;
+ bool force_hvdcp_2p0;
+ enum chip_version version;
+
+ /* psy */
+ struct power_supply *usb_psy;
+ int usb_psy_ma;
+ struct power_supply *bms_psy;
+ struct power_supply_desc batt_psy_d;
+ struct power_supply *batt_psy;
+ struct power_supply *parallel_psy;
+ struct power_supply_desc parallel_psy_d;
+
+ struct smb1351_regulator otg_vreg;
+ struct mutex irq_complete;
+
+ struct dentry *debug_root;
+ u32 peek_poke_address;
+
+ /* adc_tm parameters */
+ struct qpnp_vadc_chip *vadc_dev;
+ struct qpnp_adc_tm_chip *adc_tm_dev;
+ struct qpnp_adc_tm_btm_param adc_param;
+
+ /* jeita parameters */
+ int batt_hot_decidegc;
+ int batt_cold_decidegc;
+ int batt_warm_decidegc;
+ int batt_cool_decidegc;
+ int batt_missing_decidegc;
+ unsigned int batt_warm_ma;
+ unsigned int batt_warm_mv;
+ unsigned int batt_cool_ma;
+ unsigned int batt_cool_mv;
+
+ /* pinctrl parameters */
+ const char *pinctrl_state_name;
+ struct pinctrl *smb_pinctrl;
+};
+
+struct smb_irq_info {
+ const char *name;
+ int (*smb_irq)(struct smb1351_charger *chip, u8 rt_stat);
+ int high;
+ int low;
+};
+
+struct irq_handler_info {
+ u8 stat_reg;
+ u8 val;
+ u8 prev_val;
+ struct smb_irq_info irq_info[4];
+};
+
+/* USB input charge current */
+static int usb_chg_current[] = {
+ 500, 685, 1000, 1100, 1200, 1300, 1500, 1600,
+ 1700, 1800, 2000, 2200, 2500, 3000,
+};
+
+static int fast_chg_current[] = {
+ 1000, 1200, 1400, 1600, 1800, 2000, 2200,
+ 2400, 2600, 2800, 3000, 3400, 3600, 3800,
+ 4000, 4640,
+};
+
+static int pre_chg_current[] = {
+ 200, 300, 400, 500, 600, 700,
+};
+
+struct battery_status {
+ bool batt_hot;
+ bool batt_warm;
+ bool batt_cool;
+ bool batt_cold;
+ bool batt_present;
+};
+
+enum {
+ BATT_HOT = 0,
+ BATT_WARM,
+ BATT_NORMAL,
+ BATT_COOL,
+ BATT_COLD,
+ BATT_MISSING,
+ BATT_STATUS_MAX,
+};
+
+static struct battery_status batt_s[] = {
+ [BATT_HOT] = {1, 0, 0, 0, 1},
+ [BATT_WARM] = {0, 1, 0, 0, 1},
+ [BATT_NORMAL] = {0, 0, 0, 0, 1},
+ [BATT_COOL] = {0, 0, 1, 0, 1},
+ [BATT_COLD] = {0, 0, 0, 1, 1},
+ [BATT_MISSING] = {0, 0, 0, 1, 0},
+};
+
+static int smb1351_read_reg(struct smb1351_charger *chip, int reg, u8 *val)
+{
+ s32 ret;
+
+ pm_stay_awake(chip->dev);
+ ret = i2c_smbus_read_byte_data(chip->client, reg);
+ if (ret < 0) {
+ pr_err("i2c read fail: can't read from %02x: %d\n", reg, ret);
+ pm_relax(chip->dev);
+ return ret;
+ } else {
+ *val = ret;
+ }
+ pm_relax(chip->dev);
+ pr_debug("Reading 0x%02x=0x%02x\n", reg, *val);
+ return 0;
+}
+
+static int smb1351_write_reg(struct smb1351_charger *chip, int reg, u8 val)
+{
+ s32 ret;
+
+ pm_stay_awake(chip->dev);
+ ret = i2c_smbus_write_byte_data(chip->client, reg, val);
+ if (ret < 0) {
+ pr_err("i2c write fail: can't write %02x to %02x: %d\n",
+ val, reg, ret);
+ pm_relax(chip->dev);
+ return ret;
+ }
+ pm_relax(chip->dev);
+ pr_debug("Writing 0x%02x=0x%02x\n", reg, val);
+ return 0;
+}
+
+static int smb1351_masked_write(struct smb1351_charger *chip, int reg,
+ u8 mask, u8 val)
+{
+ s32 rc;
+ u8 temp;
+
+ rc = smb1351_read_reg(chip, reg, &temp);
+ if (rc) {
+ pr_err("read failed: reg=%03X, rc=%d\n", reg, rc);
+ return rc;
+ }
+ temp &= ~mask;
+ temp |= val & mask;
+ rc = smb1351_write_reg(chip, reg, temp);
+ if (rc) {
+ pr_err("write failed: reg=%03X, rc=%d\n", reg, rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int smb1351_enable_volatile_writes(struct smb1351_charger *chip)
+{
+ int rc;
+
+ rc = smb1351_masked_write(chip, CMD_I2C_REG, CMD_BQ_CFG_ACCESS_BIT,
+ CMD_BQ_CFG_ACCESS_BIT);
+ if (rc)
+ pr_err("Couldn't write CMD_BQ_CFG_ACCESS_BIT rc=%d\n", rc);
+
+ return rc;
+}
+
+static int smb1351_usb_suspend(struct smb1351_charger *chip, int reason,
+ bool suspend)
+{
+ int rc = 0;
+ int suspended;
+
+ suspended = chip->usb_suspended_status;
+
+ pr_debug("reason = %d requested_suspend = %d suspended_status = %d\n",
+ reason, suspend, suspended);
+
+ if (suspend == false)
+ suspended &= ~reason;
+ else
+ suspended |= reason;
+
+ pr_debug("new suspended_status = %d\n", suspended);
+
+ rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG,
+ CMD_SUSPEND_MODE_BIT,
+ suspended ? CMD_SUSPEND_MODE_BIT : 0);
+ if (rc)
+ pr_err("Couldn't suspend rc = %d\n", rc);
+ else
+ chip->usb_suspended_status = suspended;
+
+ return rc;
+}
+
+static int smb1351_battchg_disable(struct smb1351_charger *chip,
+ int reason, int disable)
+{
+ int rc = 0;
+ int disabled;
+
+ if (chip->chg_autonomous_mode) {
+ pr_debug("Charger in autonomous mode\n");
+ return 0;
+ }
+
+ disabled = chip->battchg_disabled_status;
+
+ pr_debug("reason = %d requested_disable = %d disabled_status = %d\n",
+ reason, disable, disabled);
+ if (disable == true)
+ disabled |= reason;
+ else
+ disabled &= ~reason;
+
+ pr_debug("new disabled_status = %d\n", disabled);
+
+ rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_CHG_EN_BIT,
+ disabled ? 0 : CMD_CHG_ENABLE);
+ if (rc)
+ pr_err("Couldn't %s charging rc=%d\n",
+ disable ? "disable" : "enable", rc);
+ else
+ chip->battchg_disabled_status = disabled;
+
+ return rc;
+}
+
+static int smb1351_fastchg_current_set(struct smb1351_charger *chip,
+ unsigned int fastchg_current)
+{
+ int i, rc;
+ bool is_pre_chg = false;
+
+
+ if ((fastchg_current < SMB1351_CHG_PRE_MIN_MA) ||
+ (fastchg_current > SMB1351_CHG_FAST_MAX_MA)) {
+ pr_err("bad pre_fastchg current mA=%d asked to set\n",
+ fastchg_current);
+ return -EINVAL;
+ }
+
+ /*
+ * fast chg current could not support less than 1000mA
+ * use pre chg to instead for the parallel charging
+ */
+ if (fastchg_current < SMB1351_CHG_FAST_MIN_MA) {
+ is_pre_chg = true;
+ pr_debug("is_pre_chg true, current is %d\n", fastchg_current);
+ }
+
+ if (is_pre_chg) {
+ /* set prechg current */
+ for (i = ARRAY_SIZE(pre_chg_current) - 1; i >= 0; i--) {
+ if (pre_chg_current[i] <= fastchg_current)
+ break;
+ }
+ if (i < 0)
+ i = 0;
+ chip->fastchg_current_max_ma = pre_chg_current[i];
+ pr_debug("prechg setting %02x\n", i);
+
+ i = i << SMB1351_CHG_PRE_SHIFT;
+
+ rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG,
+ PRECHG_CURRENT_MASK, i);
+ if (rc)
+ pr_err("Couldn't write CHG_OTH_CURRENT_CTRL_REG rc=%d\n",
+ rc);
+
+ return smb1351_masked_write(chip, VARIOUS_FUNC_2_REG,
+ PRECHG_TO_FASTCHG_BIT, PRECHG_TO_FASTCHG_BIT);
+ } else {
+ if (chip->version == SMB_UNKNOWN)
+ return -EINVAL;
+
+ /* SMB1350 supports FCC upto 2600 mA */
+ if (chip->version == SMB1350 && fastchg_current > 2600)
+ fastchg_current = 2600;
+
+ /* set fastchg current */
+ for (i = ARRAY_SIZE(fast_chg_current) - 1; i >= 0; i--) {
+ if (fast_chg_current[i] <= fastchg_current)
+ break;
+ }
+ if (i < 0)
+ i = 0;
+ chip->fastchg_current_max_ma = fast_chg_current[i];
+
+ i = i << SMB1351_CHG_FAST_SHIFT;
+ pr_debug("fastchg limit=%d setting %02x\n",
+ chip->fastchg_current_max_ma, i);
+
+ /* make sure pre chg mode is disabled */
+ rc = smb1351_masked_write(chip, VARIOUS_FUNC_2_REG,
+ PRECHG_TO_FASTCHG_BIT, 0);
+ if (rc)
+ pr_err("Couldn't write VARIOUS_FUNC_2_REG rc=%d\n", rc);
+
+ return smb1351_masked_write(chip, CHG_CURRENT_CTRL_REG,
+ FAST_CHG_CURRENT_MASK, i);
+ }
+}
+
+#define MIN_FLOAT_MV 3500
+#define MAX_FLOAT_MV 4500
+#define VFLOAT_STEP_MV 20
+
+static int smb1351_float_voltage_set(struct smb1351_charger *chip,
+ int vfloat_mv)
+{
+ u8 temp;
+
+ if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) {
+ pr_err("bad float voltage mv =%d asked to set\n", vfloat_mv);
+ return -EINVAL;
+ }
+
+ temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV;
+
+ return smb1351_masked_write(chip, VFLOAT_REG, VFLOAT_MASK, temp);
+}
+
+static int smb1351_iterm_set(struct smb1351_charger *chip, int iterm_ma)
+{
+ int rc;
+ u8 reg;
+
+ if (iterm_ma <= 200)
+ reg = CHG_ITERM_200MA;
+ else if (iterm_ma <= 300)
+ reg = CHG_ITERM_300MA;
+ else if (iterm_ma <= 400)
+ reg = CHG_ITERM_400MA;
+ else if (iterm_ma <= 500)
+ reg = CHG_ITERM_500MA;
+ else if (iterm_ma <= 600)
+ reg = CHG_ITERM_600MA;
+ else
+ reg = CHG_ITERM_700MA;
+
+ rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG,
+ ITERM_MASK, reg);
+ if (rc) {
+ pr_err("Couldn't set iterm rc = %d\n", rc);
+ return rc;
+ }
+ /* enable the iterm */
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ ITERM_EN_BIT, ITERM_ENABLE);
+ if (rc) {
+ pr_err("Couldn't enable iterm rc = %d\n", rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int smb1351_chg_otg_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct smb1351_charger *chip = rdev_get_drvdata(rdev);
+
+ rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT,
+ CMD_OTG_EN_BIT);
+ if (rc)
+ pr_err("Couldn't enable OTG mode rc=%d\n", rc);
+ return rc;
+}
+
+static int smb1351_chg_otg_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct smb1351_charger *chip = rdev_get_drvdata(rdev);
+
+ rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT, 0);
+ if (rc)
+ pr_err("Couldn't disable OTG mode rc=%d\n", rc);
+ return rc;
+}
+
+static int smb1351_chg_otg_regulator_is_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ u8 reg = 0;
+ struct smb1351_charger *chip = rdev_get_drvdata(rdev);
+
+ rc = smb1351_read_reg(chip, CMD_CHG_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read OTG enable bit rc=%d\n", rc);
+ return rc;
+ }
+
+ return (reg & CMD_OTG_EN_BIT) ? 1 : 0;
+}
+
+struct regulator_ops smb1351_chg_otg_reg_ops = {
+ .enable = smb1351_chg_otg_regulator_enable,
+ .disable = smb1351_chg_otg_regulator_disable,
+ .is_enabled = smb1351_chg_otg_regulator_is_enable,
+};
+
+static int smb1351_regulator_init(struct smb1351_charger *chip)
+{
+ int rc = 0;
+ struct regulator_config cfg = {};
+
+ chip->otg_vreg.rdesc.owner = THIS_MODULE;
+ chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
+ chip->otg_vreg.rdesc.ops = &smb1351_chg_otg_reg_ops;
+ chip->otg_vreg.rdesc.name =
+ chip->dev->of_node->name;
+ chip->otg_vreg.rdesc.of_match =
+ chip->dev->of_node->name;
+
+ cfg.dev = chip->dev;
+ cfg.driver_data = chip;
+
+ chip->otg_vreg.rdev = regulator_register(
+ &chip->otg_vreg.rdesc, &cfg);
+ if (IS_ERR(chip->otg_vreg.rdev)) {
+ rc = PTR_ERR(chip->otg_vreg.rdev);
+ chip->otg_vreg.rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ pr_err("OTG reg failed, rc=%d\n", rc);
+ }
+ return rc;
+}
+
+static int smb_chip_get_version(struct smb1351_charger *chip)
+{
+ u8 ver;
+ int rc = 0;
+
+ if (chip->version == SMB_UNKNOWN) {
+ rc = smb1351_read_reg(chip, VERSION_REG, &ver);
+ if (rc) {
+ pr_err("Couldn't read version rc=%d\n", rc);
+ return rc;
+ }
+
+ /* If bit 1 is set, it is SMB1350 */
+ if (ver & VERSION_MASK)
+ chip->version = SMB1350;
+ else
+ chip->version = SMB1351;
+ }
+
+ return rc;
+}
+
+static int smb1351_hw_init(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg = 0, mask = 0;
+
+ /* configure smb_pinctrl to enable irqs */
+ if (chip->pinctrl_state_name) {
+ chip->smb_pinctrl = pinctrl_get_select(chip->dev,
+ chip->pinctrl_state_name);
+ if (IS_ERR(chip->smb_pinctrl)) {
+ pr_err("Could not get/set %s pinctrl state rc = %ld\n",
+ chip->pinctrl_state_name,
+ PTR_ERR(chip->smb_pinctrl));
+ return PTR_ERR(chip->smb_pinctrl);
+ }
+ }
+
+ /*
+ * If the charger is pre-configured for autonomous operation,
+ * do not apply additional settings
+ */
+ if (chip->chg_autonomous_mode) {
+ pr_debug("Charger configured for autonomous mode\n");
+ return 0;
+ }
+
+ rc = smb_chip_get_version(chip);
+ if (rc) {
+ pr_err("Couldn't get version rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = smb1351_enable_volatile_writes(chip);
+ if (rc) {
+ pr_err("Couldn't configure volatile writes rc=%d\n", rc);
+ return rc;
+ }
+
+ /* setup battery missing source */
+ reg = BATT_MISSING_THERM_PIN_SOURCE_BIT;
+ mask = BATT_MISSING_THERM_PIN_SOURCE_BIT;
+ rc = smb1351_masked_write(chip, HVDCP_BATT_MISSING_CTRL_REG,
+ mask, reg);
+ if (rc) {
+ pr_err("Couldn't set HVDCP_BATT_MISSING_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+ /* setup defaults for CHG_PIN_EN_CTRL_REG */
+ reg = EN_BY_I2C_0_DISABLE | USBCS_CTRL_BY_I2C | CHG_ERR_BIT |
+ APSD_DONE_BIT | LED_BLINK_FUNC_BIT;
+ mask = EN_PIN_CTRL_MASK | USBCS_CTRL_BIT | CHG_ERR_BIT |
+ APSD_DONE_BIT | LED_BLINK_FUNC_BIT;
+ rc = smb1351_masked_write(chip, CHG_PIN_EN_CTRL_REG, mask, reg);
+ if (rc) {
+ pr_err("Couldn't set CHG_PIN_EN_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+ /* setup USB 2.0/3.0 detection and USB 500/100 command polarity */
+ reg = USB_2_3_MODE_SEL_BY_I2C | USB_CMD_POLARITY_500_1_100_0;
+ mask = USB_2_3_MODE_SEL_BIT | USB_5_1_CMD_POLARITY_BIT;
+ rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, mask, reg);
+ if (rc) {
+ pr_err("Couldn't set CHG_OTH_CURRENT_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+ /* setup USB suspend, AICL and APSD */
+ reg = SUSPEND_MODE_CTRL_BY_I2C | AICL_EN_BIT;
+ if (!chip->disable_apsd)
+ reg |= APSD_EN_BIT;
+ mask = SUSPEND_MODE_CTRL_BIT | AICL_EN_BIT | APSD_EN_BIT;
+ rc = smb1351_masked_write(chip, VARIOUS_FUNC_REG, mask, reg);
+ if (rc) {
+ pr_err("Couldn't set VARIOUS_FUNC_REG rc=%d\n", rc);
+ return rc;
+ }
+ /* Fault and Status IRQ configuration */
+ reg = HOT_COLD_HARD_LIMIT_BIT | HOT_COLD_SOFT_LIMIT_BIT
+ | INPUT_OVLO_BIT | INPUT_UVLO_BIT | AICL_DONE_FAIL_BIT;
+ rc = smb1351_write_reg(chip, FAULT_INT_REG, reg);
+ if (rc) {
+ pr_err("Couldn't set FAULT_INT_REG rc=%d\n", rc);
+ return rc;
+ }
+ reg = CHG_OR_PRECHG_TIMEOUT_BIT | BATT_OVP_BIT |
+ FAST_TERM_TAPER_RECHG_INHIBIT_BIT |
+ BATT_MISSING_BIT | BATT_LOW_BIT;
+ rc = smb1351_write_reg(chip, STATUS_INT_REG, reg);
+ if (rc) {
+ pr_err("Couldn't set STATUS_INT_REG rc=%d\n", rc);
+ return rc;
+ }
+ /* setup THERM Monitor */
+ if (!chip->using_pmic_therm) {
+ rc = smb1351_masked_write(chip, THERM_A_CTRL_REG,
+ THERM_MONITOR_BIT, THERM_MONITOR_EN);
+ if (rc) {
+ pr_err("Couldn't set THERM_A_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+ }
+ /* set the fast charge current limit */
+ rc = smb1351_fastchg_current_set(chip,
+ chip->target_fastchg_current_max_ma);
+ if (rc) {
+ pr_err("Couldn't set fastchg current rc=%d\n", rc);
+ return rc;
+ }
+
+ /* set the float voltage */
+ if (chip->vfloat_mv != -EINVAL) {
+ rc = smb1351_float_voltage_set(chip, chip->vfloat_mv);
+ if (rc) {
+ pr_err("Couldn't set float voltage rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ /* set iterm */
+ if (chip->iterm_ma != -EINVAL) {
+ if (chip->iterm_disabled) {
+ pr_err("Error: Both iterm_disabled and iterm_ma set\n");
+ return -EINVAL;
+ } else {
+ rc = smb1351_iterm_set(chip, chip->iterm_ma);
+ if (rc) {
+ pr_err("Couldn't set iterm rc = %d\n", rc);
+ return rc;
+ }
+ }
+ } else if (chip->iterm_disabled) {
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ ITERM_EN_BIT, ITERM_DISABLE);
+ if (rc) {
+ pr_err("Couldn't set iterm rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ /* set recharge-threshold */
+ if (chip->recharge_mv != -EINVAL) {
+ if (chip->recharge_disabled) {
+ pr_err("Error: Both recharge_disabled and recharge_mv set\n");
+ return -EINVAL;
+ } else {
+ reg = AUTO_RECHG_ENABLE;
+ if (chip->recharge_mv > 50)
+ reg |= AUTO_RECHG_TH_100MV;
+ else
+ reg |= AUTO_RECHG_TH_50MV;
+
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ AUTO_RECHG_BIT |
+ AUTO_RECHG_TH_BIT, reg);
+ if (rc) {
+ pr_err("Couldn't set rechg-cfg rc = %d\n", rc);
+ return rc;
+ }
+ }
+ } else if (chip->recharge_disabled) {
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ AUTO_RECHG_BIT,
+ AUTO_RECHG_DISABLE);
+ if (rc) {
+ pr_err("Couldn't disable auto-rechg rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ /* enable/disable charging by suspending usb */
+ rc = smb1351_usb_suspend(chip, USER, chip->usb_suspended_status);
+ if (rc) {
+ pr_err("Unable to %s battery charging. rc=%d\n",
+ chip->usb_suspended_status ? "disable" : "enable",
+ rc);
+ }
+
+ return rc;
+}
+
+static enum power_supply_property smb1351_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int smb1351_get_prop_batt_status(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg = 0;
+
+ if (chip->batt_full)
+ return POWER_SUPPLY_STATUS_FULL;
+
+ rc = smb1351_read_reg(chip, STATUS_4_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read STATUS_4 rc = %d\n", rc);
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ pr_debug("STATUS_4_REG(0x3A)=%x\n", reg);
+
+ if (reg & STATUS_HOLD_OFF_BIT)
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ if (reg & STATUS_CHG_MASK)
+ return POWER_SUPPLY_STATUS_CHARGING;
+
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+}
+
+static int smb1351_get_prop_batt_present(struct smb1351_charger *chip)
+{
+ return !chip->battery_missing;
+}
+
+static int smb1351_get_prop_batt_capacity(struct smb1351_charger *chip)
+{
+ union power_supply_propval ret = {0, };
+
+ if (chip->fake_battery_soc >= 0)
+ return chip->fake_battery_soc;
+
+ if (chip->bms_psy) {
+ power_supply_get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_CAPACITY, &ret);
+ return ret.intval;
+ }
+ pr_debug("return DEFAULT_BATT_CAPACITY\n");
+ return DEFAULT_BATT_CAPACITY;
+}
+
+static int smb1351_get_prop_batt_temp(struct smb1351_charger *chip)
+{
+ union power_supply_propval ret = {0, };
+ int rc = 0;
+ struct qpnp_vadc_result results;
+
+ if (chip->bms_psy) {
+ power_supply_get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_TEMP, &ret);
+ return ret.intval;
+ }
+ if (chip->vadc_dev) {
+ rc = qpnp_vadc_read(chip->vadc_dev,
+ LR_MUX1_BATT_THERM, &results);
+ if (rc)
+ pr_debug("Unable to read adc batt temp rc=%d\n", rc);
+ else
+ return (int)results.physical;
+ }
+
+ pr_debug("return default temperature\n");
+ return DEFAULT_BATT_TEMP;
+}
+
+static int smb1351_get_prop_charge_type(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg = 0;
+
+ rc = smb1351_read_reg(chip, STATUS_4_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read STATUS_4 rc = %d\n", rc);
+ return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ }
+
+ pr_debug("STATUS_4_REG(0x3A)=%x\n", reg);
+
+ reg &= STATUS_CHG_MASK;
+
+ if (reg == STATUS_FAST_CHARGING)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else if (reg == STATUS_TAPER_CHARGING)
+ return POWER_SUPPLY_CHARGE_TYPE_TAPER;
+ else if (reg == STATUS_PRE_CHARGING)
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ else
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+static int smb1351_get_prop_batt_health(struct smb1351_charger *chip)
+{
+ union power_supply_propval ret = {0, };
+
+ if (chip->batt_hot)
+ ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (chip->batt_cold)
+ ret.intval = POWER_SUPPLY_HEALTH_COLD;
+ else if (chip->batt_warm)
+ ret.intval = POWER_SUPPLY_HEALTH_WARM;
+ else if (chip->batt_cool)
+ ret.intval = POWER_SUPPLY_HEALTH_COOL;
+ else
+ ret.intval = POWER_SUPPLY_HEALTH_GOOD;
+
+ return ret.intval;
+}
+
+static int smb1351_set_usb_chg_current(struct smb1351_charger *chip,
+ int current_ma)
+{
+ int i, rc = 0;
+ u8 reg = 0, mask = 0;
+
+ pr_debug("USB current_ma = %d\n", current_ma);
+
+ if (chip->chg_autonomous_mode) {
+ pr_debug("Charger in autonomous mode\n");
+ return 0;
+ }
+
+ /* set suspend bit when urrent_ma <= 2 */
+ if (current_ma <= SUSPEND_CURRENT_MA) {
+ smb1351_usb_suspend(chip, CURRENT, true);
+ pr_debug("USB suspend\n");
+ return 0;
+ }
+
+ if (current_ma > SUSPEND_CURRENT_MA &&
+ current_ma < USB2_MIN_CURRENT_MA)
+ current_ma = USB2_MIN_CURRENT_MA;
+
+ if (current_ma == USB2_MIN_CURRENT_MA) {
+ /* USB 2.0 - 100mA */
+ reg = CMD_USB_2_MODE | CMD_USB_100_MODE;
+ } else if (current_ma == USB3_MIN_CURRENT_MA) {
+ /* USB 3.0 - 150mA */
+ reg = CMD_USB_3_MODE | CMD_USB_100_MODE;
+ } else if (current_ma == USB2_MAX_CURRENT_MA) {
+ /* USB 2.0 - 500mA */
+ reg = CMD_USB_2_MODE | CMD_USB_500_MODE;
+ } else if (current_ma == USB3_MAX_CURRENT_MA) {
+ /* USB 3.0 - 900mA */
+ reg = CMD_USB_3_MODE | CMD_USB_500_MODE;
+ } else if (current_ma > USB2_MAX_CURRENT_MA) {
+ /* HC mode - if none of the above */
+ reg = CMD_USB_AC_MODE;
+
+ for (i = ARRAY_SIZE(usb_chg_current) - 1; i >= 0; i--) {
+ if (usb_chg_current[i] <= current_ma)
+ break;
+ }
+ if (i < 0)
+ i = 0;
+ rc = smb1351_masked_write(chip, CHG_CURRENT_CTRL_REG,
+ AC_INPUT_CURRENT_LIMIT_MASK, i);
+ if (rc) {
+ pr_err("Couldn't set input mA rc=%d\n", rc);
+ return rc;
+ }
+ }
+ /* control input current mode by command */
+ reg |= CMD_INPUT_CURRENT_MODE_CMD;
+ mask = CMD_INPUT_CURRENT_MODE_BIT | CMD_USB_2_3_SEL_BIT |
+ CMD_USB_1_5_AC_CTRL_MASK;
+ rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, mask, reg);
+ if (rc) {
+ pr_err("Couldn't set charging mode rc = %d\n", rc);
+ return rc;
+ }
+
+ /* unset the suspend bit here */
+ smb1351_usb_suspend(chip, CURRENT, false);
+
+ return rc;
+}
+
+static int smb1351_batt_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int smb1351_battery_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int rc;
+ struct smb1351_charger *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!chip->bms_controlled_charging)
+ return -EINVAL;
+ switch (val->intval) {
+ case POWER_SUPPLY_STATUS_FULL:
+ rc = smb1351_battchg_disable(chip, SOC, true);
+ if (rc) {
+ pr_err("Couldn't disable charging rc = %d\n",
+ rc);
+ } else {
+ chip->batt_full = true;
+ pr_debug("status = FULL, batt_full = %d\n",
+ chip->batt_full);
+ }
+ break;
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ chip->batt_full = false;
+ power_supply_changed(chip->batt_psy);
+ pr_debug("status = DISCHARGING, batt_full = %d\n",
+ chip->batt_full);
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ rc = smb1351_battchg_disable(chip, SOC, false);
+ if (rc) {
+ pr_err("Couldn't enable charging rc = %d\n",
+ rc);
+ } else {
+ chip->batt_full = false;
+ pr_debug("status = CHARGING, batt_full = %d\n",
+ chip->batt_full);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ smb1351_usb_suspend(chip, USER, !val->intval);
+ break;
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ smb1351_battchg_disable(chip, USER, !val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ chip->fake_battery_soc = val->intval;
+ power_supply_changed(chip->batt_psy);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int smb1351_battery_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb1351_charger *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = smb1351_get_prop_batt_status(chip);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = smb1351_get_prop_batt_present(chip);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = smb1351_get_prop_batt_capacity(chip);
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ val->intval = !chip->usb_suspended_status;
+ break;
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ val->intval = !chip->battchg_disabled_status;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = smb1351_get_prop_charge_type(chip);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = smb1351_get_prop_batt_health(chip);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = smb1351_get_prop_batt_temp(chip);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = "smb1351";
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property smb1351_parallel_properties[] = {
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_PARALLEL_MODE,
+ POWER_SUPPLY_PROP_INPUT_SUSPEND,
+};
+
+static int smb1351_parallel_set_chg_suspend(struct smb1351_charger *chip,
+ int suspend)
+{
+ int rc;
+ u8 reg, mask = 0;
+
+ if (chip->parallel_charger_suspended == suspend) {
+ pr_debug("Skip same state request suspended = %d suspend=%d\n",
+ chip->parallel_charger_suspended, !suspend);
+ return 0;
+ }
+
+ if (!suspend) {
+ rc = smb_chip_get_version(chip);
+ if (rc) {
+ pr_err("Couldn't get version rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = smb1351_enable_volatile_writes(chip);
+ if (rc) {
+ pr_err("Couldn't configure for volatile rc = %d\n", rc);
+ return rc;
+ }
+
+ /* set the float voltage */
+ if (chip->vfloat_mv != -EINVAL) {
+ rc = smb1351_float_voltage_set(chip, chip->vfloat_mv);
+ if (rc) {
+ pr_err("Couldn't set float voltage rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* set recharge-threshold and enable auto recharge */
+ if (chip->recharge_mv != -EINVAL) {
+ reg = AUTO_RECHG_ENABLE;
+ if (chip->recharge_mv > 50)
+ reg |= AUTO_RECHG_TH_100MV;
+ else
+ reg |= AUTO_RECHG_TH_50MV;
+
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ AUTO_RECHG_BIT |
+ AUTO_RECHG_TH_BIT, reg);
+ if (rc) {
+ pr_err("Couldn't set rechg-cfg rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ /* control USB suspend via command bits */
+ rc = smb1351_masked_write(chip, VARIOUS_FUNC_REG,
+ APSD_EN_BIT | SUSPEND_MODE_CTRL_BIT,
+ SUSPEND_MODE_CTRL_BY_I2C);
+ if (rc) {
+ pr_err("Couldn't set USB suspend rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * When present is being set force USB suspend, start charging
+ * only when POWER_SUPPLY_PROP_CURRENT_MAX is set.
+ */
+ rc = smb1351_usb_suspend(chip, CURRENT, true);
+ if (rc) {
+ pr_err("failed to suspend rc=%d\n", rc);
+ return rc;
+ }
+ chip->usb_psy_ma = SUSPEND_CURRENT_MA;
+
+ /* set chg en by pin active low */
+ reg = chip->parallel_pin_polarity_setting | USBCS_CTRL_BY_I2C;
+ rc = smb1351_masked_write(chip, CHG_PIN_EN_CTRL_REG,
+ EN_PIN_CTRL_MASK | USBCS_CTRL_BIT, reg);
+ if (rc) {
+ pr_err("Couldn't set en pin rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * setup USB 2.0/3.0 detection and USB 500/100
+ * command polarity
+ */
+ reg = USB_2_3_MODE_SEL_BY_I2C | USB_CMD_POLARITY_500_1_100_0;
+ mask = USB_2_3_MODE_SEL_BIT | USB_5_1_CMD_POLARITY_BIT;
+ rc = smb1351_masked_write(chip,
+ CHG_OTH_CURRENT_CTRL_REG, mask, reg);
+ if (rc) {
+ pr_err("Couldn't set CHG_OTH_CURRENT_CTRL_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smb1351_fastchg_current_set(chip,
+ chip->target_fastchg_current_max_ma);
+ if (rc) {
+ pr_err("Couldn't set fastchg current rc=%d\n", rc);
+ return rc;
+ }
+ chip->parallel_charger_suspended = false;
+ } else {
+ rc = smb1351_usb_suspend(chip, CURRENT, true);
+ if (rc)
+ pr_debug("failed to suspend rc=%d\n", rc);
+
+ chip->usb_psy_ma = SUSPEND_CURRENT_MA;
+ chip->parallel_charger_suspended = true;
+ }
+
+ return 0;
+}
+
+static int smb1351_get_closest_usb_setpoint(int val)
+{
+ int i;
+
+ for (i = ARRAY_SIZE(usb_chg_current) - 1; i >= 0; i--) {
+ if (usb_chg_current[i] <= val)
+ break;
+ }
+ if (i < 0)
+ i = 0;
+
+ if (i >= ARRAY_SIZE(usb_chg_current) - 1)
+ return ARRAY_SIZE(usb_chg_current) - 1;
+
+ /* check what is closer, i or i + 1 */
+ if (abs(usb_chg_current[i] - val) < abs(usb_chg_current[i + 1] - val))
+ return i;
+ else
+ return i + 1;
+}
+
+static bool smb1351_is_input_current_limited(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smb1351_read_reg(chip, IRQ_H_REG, &reg);
+ if (rc) {
+ pr_err("Failed to read IRQ_H_REG for ICL status: %d\n", rc);
+ return false;
+ }
+
+ return !!(reg & IRQ_IC_LIMIT_STATUS_BIT);
+}
+
+static bool smb1351_is_usb_present(struct smb1351_charger *chip)
+{
+ int rc;
+ union power_supply_propval val = {0, };
+
+ if (!chip->usb_psy)
+ chip->usb_psy = power_supply_get_by_name("usb");
+ if (!chip->usb_psy) {
+ pr_err("USB psy not found\n");
+ return false;
+ }
+
+ rc = power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_ONLINE, &val);
+ if (rc < 0) {
+ pr_err("Failed to get present property rc=%d\n", rc);
+ return false;
+ }
+
+ if (val.intval)
+ return true;
+
+ return false;
+}
+
+static int smb1351_parallel_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int rc = 0, index;
+ struct smb1351_charger *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ /*
+ *CHG EN is controlled by pin in the parallel charging.
+ *Use suspend if disable charging by command.
+ */
+ if (!chip->parallel_charger_suspended)
+ rc = smb1351_usb_suspend(chip, USER, !val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smb1351_parallel_set_chg_suspend(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ chip->target_fastchg_current_max_ma =
+ val->intval / 1000;
+ if (!chip->parallel_charger_suspended)
+ rc = smb1351_fastchg_current_set(chip,
+ chip->target_fastchg_current_max_ma);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ index = smb1351_get_closest_usb_setpoint(val->intval / 1000);
+ chip->usb_psy_ma = usb_chg_current[index];
+ if (!chip->parallel_charger_suspended)
+ rc = smb1351_set_usb_chg_current(chip,
+ chip->usb_psy_ma);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ chip->vfloat_mv = val->intval / 1000;
+ if (!chip->parallel_charger_suspended)
+ rc = smb1351_float_voltage_set(chip, val->intval);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return rc;
+}
+
+static int smb1351_parallel_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int smb1351_parallel_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb1351_charger *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ val->intval = !chip->parallel_charger_suspended;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (!chip->parallel_charger_suspended)
+ val->intval = chip->usb_psy_ma * 1000;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (!chip->parallel_charger_suspended)
+ val->intval = chip->vfloat_mv;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ /* Check if SMB1351 is present */
+ if (smb1351_is_usb_present(chip)) {
+ val->intval = smb1351_get_prop_charge_type(chip);
+ if (val->intval == POWER_SUPPLY_CHARGE_TYPE_UNKNOWN) {
+ pr_debug("Failed to charge type, charger may be absent\n");
+ return -ENODEV;
+ }
+ }
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ if (!chip->parallel_charger_suspended)
+ val->intval = chip->fastchg_current_max_ma * 1000;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!chip->parallel_charger_suspended)
+ val->intval = smb1351_get_prop_batt_status(chip);
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ if (!chip->parallel_charger_suspended)
+ val->intval =
+ smb1351_is_input_current_limited(chip) ? 1 : 0;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_PARALLEL_MODE:
+ val->intval = chip->parallel_mode;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ val->intval = chip->parallel_charger_suspended;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void smb1351_chg_set_appropriate_battery_current(
+ struct smb1351_charger *chip)
+{
+ int rc;
+ unsigned int current_max = chip->target_fastchg_current_max_ma;
+
+ if (chip->batt_cool)
+ current_max = min(current_max, chip->batt_cool_ma);
+ if (chip->batt_warm)
+ current_max = min(current_max, chip->batt_warm_ma);
+
+ pr_debug("setting %dmA", current_max);
+
+ rc = smb1351_fastchg_current_set(chip, current_max);
+ if (rc)
+ pr_err("Couldn't set charging current rc = %d\n", rc);
+}
+
+static void smb1351_chg_set_appropriate_vddmax(struct smb1351_charger *chip)
+{
+ int rc;
+ unsigned int vddmax = chip->vfloat_mv;
+
+ if (chip->batt_cool)
+ vddmax = min(vddmax, chip->batt_cool_mv);
+ if (chip->batt_warm)
+ vddmax = min(vddmax, chip->batt_warm_mv);
+
+ pr_debug("setting %dmV\n", vddmax);
+
+ rc = smb1351_float_voltage_set(chip, vddmax);
+ if (rc)
+ pr_err("Couldn't set float voltage rc = %d\n", rc);
+}
+
+static void smb1351_chg_ctrl_in_jeita(struct smb1351_charger *chip)
+{
+ union power_supply_propval ret = {0, };
+ int rc;
+
+ /* enable the iterm to prevent the reverse boost */
+ if (chip->iterm_disabled) {
+ if (chip->batt_cool || chip->batt_warm) {
+ rc = smb1351_iterm_set(chip, 100);
+ pr_debug("set the iterm due to JEITA\n");
+ } else {
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ ITERM_EN_BIT, ITERM_DISABLE);
+ pr_debug("disable the iterm when exits warm/cool\n");
+ }
+ if (rc) {
+ pr_err("Couldn't set iterm rc = %d\n", rc);
+ return;
+ }
+ }
+ /*
+ * When JEITA back to normal, the charging maybe disabled due to
+ * the current termination. So re-enable the charging if the soc
+ * is less than 100 in the normal mode. A 200ms delay is requred
+ * before the disabe and enable operation.
+ */
+ if (chip->bms_psy) {
+ rc = power_supply_get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_CAPACITY, &ret);
+ if (rc) {
+ pr_err("Couldn't read the bms capacity rc = %d\n",
+ rc);
+ return;
+ }
+ if (!chip->batt_cool && !chip->batt_warm
+ && !chip->batt_cold && !chip->batt_hot
+ && ret.intval < 100) {
+ rc = smb1351_battchg_disable(chip, THERMAL, true);
+ if (rc) {
+ pr_err("Couldn't disable charging rc = %d\n",
+ rc);
+ return;
+ }
+ /* delay for resetting the charging */
+ msleep(200);
+ rc = smb1351_battchg_disable(chip, THERMAL, false);
+ if (rc) {
+ pr_err("Couldn't enable charging rc = %d\n",
+ rc);
+ return;
+ } else {
+ chip->batt_full = false;
+ pr_debug("re-enable charging, batt_full = %d\n",
+ chip->batt_full);
+ }
+ pr_debug("batt psy changed\n");
+ power_supply_changed(chip->batt_psy);
+ }
+ }
+}
+
+#define HYSTERESIS_DECIDEGC 20
+static void smb1351_chg_adc_notification(enum qpnp_tm_state state, void *ctx)
+{
+ struct smb1351_charger *chip = ctx;
+ struct battery_status *cur = NULL;
+ int temp;
+
+ if (state >= ADC_TM_STATE_NUM) {
+ pr_err("invalid state parameter %d\n", state);
+ return;
+ }
+
+ temp = smb1351_get_prop_batt_temp(chip);
+
+ pr_debug("temp = %d state = %s\n", temp,
+ state == ADC_TM_WARM_STATE ? "hot" : "cold");
+
+ /* reset the adc status request */
+ chip->adc_param.state_request = ADC_TM_WARM_COOL_THR_ENABLE;
+
+ /* temp from low to high */
+ if (state == ADC_TM_WARM_STATE) {
+ /* WARM -> HOT */
+ if (temp >= chip->batt_hot_decidegc) {
+ cur = &batt_s[BATT_HOT];
+ chip->adc_param.low_temp =
+ chip->batt_hot_decidegc - HYSTERESIS_DECIDEGC;
+ chip->adc_param.state_request = ADC_TM_COOL_THR_ENABLE;
+ /* NORMAL -> WARM */
+ } else if (temp >= chip->batt_warm_decidegc &&
+ chip->jeita_supported) {
+ cur = &batt_s[BATT_WARM];
+ chip->adc_param.low_temp =
+ chip->batt_warm_decidegc - HYSTERESIS_DECIDEGC;
+ chip->adc_param.high_temp = chip->batt_hot_decidegc;
+ /* COOL -> NORMAL */
+ } else if (temp >= chip->batt_cool_decidegc &&
+ chip->jeita_supported) {
+ cur = &batt_s[BATT_NORMAL];
+ chip->adc_param.low_temp =
+ chip->batt_cool_decidegc - HYSTERESIS_DECIDEGC;
+ chip->adc_param.high_temp = chip->batt_warm_decidegc;
+ /* COLD -> COOL */
+ } else if (temp >= chip->batt_cold_decidegc) {
+ cur = &batt_s[BATT_COOL];
+ chip->adc_param.low_temp =
+ chip->batt_cold_decidegc - HYSTERESIS_DECIDEGC;
+ if (chip->jeita_supported)
+ chip->adc_param.high_temp =
+ chip->batt_cool_decidegc;
+ else
+ chip->adc_param.high_temp =
+ chip->batt_hot_decidegc;
+ /* MISSING -> COLD */
+ } else if (temp >= chip->batt_missing_decidegc) {
+ cur = &batt_s[BATT_COLD];
+ chip->adc_param.high_temp = chip->batt_cold_decidegc;
+ chip->adc_param.low_temp = chip->batt_missing_decidegc
+ - HYSTERESIS_DECIDEGC;
+ }
+ /* temp from high to low */
+ } else {
+ /* COLD -> MISSING */
+ if (temp <= chip->batt_missing_decidegc) {
+ cur = &batt_s[BATT_MISSING];
+ chip->adc_param.high_temp = chip->batt_missing_decidegc
+ + HYSTERESIS_DECIDEGC;
+ chip->adc_param.state_request = ADC_TM_WARM_THR_ENABLE;
+ /* COOL -> COLD */
+ } else if (temp <= chip->batt_cold_decidegc) {
+ cur = &batt_s[BATT_COLD];
+ chip->adc_param.high_temp =
+ chip->batt_cold_decidegc + HYSTERESIS_DECIDEGC;
+ /* add low_temp to enable batt present check */
+ chip->adc_param.low_temp = chip->batt_missing_decidegc;
+ /* NORMAL -> COOL */
+ } else if (temp <= chip->batt_cool_decidegc &&
+ chip->jeita_supported) {
+ cur = &batt_s[BATT_COOL];
+ chip->adc_param.high_temp =
+ chip->batt_cool_decidegc + HYSTERESIS_DECIDEGC;
+ chip->adc_param.low_temp = chip->batt_cold_decidegc;
+ /* WARM -> NORMAL */
+ } else if (temp <= chip->batt_warm_decidegc &&
+ chip->jeita_supported) {
+ cur = &batt_s[BATT_NORMAL];
+ chip->adc_param.high_temp =
+ chip->batt_warm_decidegc + HYSTERESIS_DECIDEGC;
+ chip->adc_param.low_temp = chip->batt_cool_decidegc;
+ /* HOT -> WARM */
+ } else if (temp <= chip->batt_hot_decidegc) {
+ cur = &batt_s[BATT_WARM];
+ if (chip->jeita_supported)
+ chip->adc_param.low_temp =
+ chip->batt_warm_decidegc;
+ else
+ chip->adc_param.low_temp =
+ chip->batt_cold_decidegc;
+ chip->adc_param.high_temp =
+ chip->batt_hot_decidegc + HYSTERESIS_DECIDEGC;
+ }
+ }
+
+ if (!cur) {
+ pr_debug("Couldn't choose batt state, adc state=%d and temp=%d\n",
+ state, temp);
+ return;
+ }
+
+ if (cur->batt_present)
+ chip->battery_missing = false;
+ else
+ chip->battery_missing = true;
+
+ if (cur->batt_hot ^ chip->batt_hot ||
+ cur->batt_cold ^ chip->batt_cold) {
+ chip->batt_hot = cur->batt_hot;
+ chip->batt_cold = cur->batt_cold;
+ /* stop charging explicitly since we use PMIC thermal pin*/
+ if (cur->batt_hot || cur->batt_cold ||
+ chip->battery_missing)
+ smb1351_battchg_disable(chip, THERMAL, 1);
+ else
+ smb1351_battchg_disable(chip, THERMAL, 0);
+ }
+
+ if ((chip->batt_warm ^ cur->batt_warm ||
+ chip->batt_cool ^ cur->batt_cool)
+ && chip->jeita_supported) {
+ chip->batt_warm = cur->batt_warm;
+ chip->batt_cool = cur->batt_cool;
+ smb1351_chg_set_appropriate_battery_current(chip);
+ smb1351_chg_set_appropriate_vddmax(chip);
+ smb1351_chg_ctrl_in_jeita(chip);
+ }
+
+ pr_debug("hot %d, cold %d, warm %d, cool %d, soft jeita supported %d, missing %d, low = %d deciDegC, high = %d deciDegC\n",
+ chip->batt_hot, chip->batt_cold, chip->batt_warm,
+ chip->batt_cool, chip->jeita_supported,
+ chip->battery_missing, chip->adc_param.low_temp,
+ chip->adc_param.high_temp);
+ if (qpnp_adc_tm_channel_measure(chip->adc_tm_dev, &chip->adc_param))
+ pr_err("request ADC error\n");
+}
+
+static int rerun_apsd(struct smb1351_charger *chip)
+{
+ int rc;
+
+ pr_debug("Reruning APSD\nDisabling APSD\n");
+
+ rc = smb1351_masked_write(chip, CMD_HVDCP_REG, CMD_APSD_RE_RUN_BIT,
+ CMD_APSD_RE_RUN_BIT);
+ if (rc)
+ pr_err("Couldn't re-run APSD algo\n");
+
+ return 0;
+}
+
+static void smb1351_hvdcp_det_work(struct work_struct *work)
+{
+ int rc;
+ u8 reg;
+ union power_supply_propval pval = {0, };
+ struct smb1351_charger *chip = container_of(work,
+ struct smb1351_charger,
+ hvdcp_det_work.work);
+
+ rc = smb1351_read_reg(chip, STATUS_7_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read STATUS_7_REG rc == %d\n", rc);
+ goto end;
+ }
+ pr_debug("STATUS_7_REG = 0x%02X\n", reg);
+
+ if (reg) {
+ pr_debug("HVDCP detected; notifying USB PSY\n");
+ pval.intval = POWER_SUPPLY_TYPE_USB_HVDCP;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &pval);
+ }
+end:
+ pm_relax(chip->dev);
+}
+
+#define HVDCP_NOTIFY_MS 2500
+static int smb1351_apsd_complete_handler(struct smb1351_charger *chip,
+ u8 status)
+{
+ int rc;
+ u8 reg = 0;
+ union power_supply_propval prop = {0, };
+ enum power_supply_type type = POWER_SUPPLY_TYPE_UNKNOWN;
+
+ /*
+ * If apsd is disabled, charger detection is done by
+ * USB phy driver.
+ */
+ if (chip->disable_apsd || chip->usbin_ov) {
+ pr_debug("APSD %s, status = %d\n",
+ chip->disable_apsd ? "disabled" : "enabled", !!status);
+ pr_debug("USBIN ov, status = %d\n", chip->usbin_ov);
+ return 0;
+ }
+
+ rc = smb1351_read_reg(chip, STATUS_5_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read STATUS_5 rc = %d\n", rc);
+ return rc;
+ }
+
+ pr_debug("STATUS_5_REG(0x3B)=%x\n", reg);
+
+ switch (reg) {
+ case STATUS_PORT_ACA_DOCK:
+ case STATUS_PORT_ACA_C:
+ case STATUS_PORT_ACA_B:
+ case STATUS_PORT_ACA_A:
+ type = POWER_SUPPLY_TYPE_USB_ACA;
+ break;
+ case STATUS_PORT_CDP:
+ type = POWER_SUPPLY_TYPE_USB_CDP;
+ break;
+ case STATUS_PORT_DCP:
+ type = POWER_SUPPLY_TYPE_USB_DCP;
+ break;
+ case STATUS_PORT_SDP:
+ type = POWER_SUPPLY_TYPE_USB;
+ break;
+ case STATUS_PORT_OTHER:
+ type = POWER_SUPPLY_TYPE_USB_DCP;
+ break;
+ default:
+ type = POWER_SUPPLY_TYPE_USB;
+ break;
+ }
+
+ if (status) {
+ chip->chg_present = true;
+ pr_debug("APSD complete. USB type detected=%d chg_present=%d\n",
+ type, chip->chg_present);
+ if (!chip->battery_missing && !chip->apsd_rerun) {
+ if (type == POWER_SUPPLY_TYPE_USB) {
+ pr_debug("Setting usb psy dp=f dm=f SDP and rerun\n");
+ prop.intval = POWER_SUPPLY_DP_DM_DPF_DMF;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_DP_DM, &prop);
+ chip->apsd_rerun = true;
+ rerun_apsd(chip);
+ return 0;
+ }
+ pr_debug("Set usb psy dp=f dm=f DCP and no rerun\n");
+ prop.intval = POWER_SUPPLY_DP_DM_DPF_DMF;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_DP_DM, &prop);
+ }
+ /*
+ * If defined force hvdcp 2p0 property,
+ * we force to hvdcp 2p0 in the APSD handler.
+ */
+ if (chip->force_hvdcp_2p0) {
+ pr_debug("Force set to HVDCP 2.0 mode\n");
+ smb1351_masked_write(chip, VARIOUS_FUNC_3_REG,
+ QC_2P1_AUTH_ALGO_BIT, 0);
+ smb1351_masked_write(chip, CMD_HVDCP_REG,
+ CMD_FORCE_HVDCP_2P0_BIT,
+ CMD_FORCE_HVDCP_2P0_BIT);
+ type = POWER_SUPPLY_TYPE_USB_HVDCP;
+ } else if (type == POWER_SUPPLY_TYPE_USB_DCP) {
+ pr_debug("schedule hvdcp detection worker\n");
+ pm_stay_awake(chip->dev);
+ schedule_delayed_work(&chip->hvdcp_det_work,
+ msecs_to_jiffies(HVDCP_NOTIFY_MS));
+ }
+
+ prop.intval = type;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &prop);
+ /*
+ * SMB is now done sampling the D+/D- lines,
+ * indicate USB driver
+ */
+ pr_debug("updating usb_psy present=%d\n", chip->chg_present);
+ prop.intval = chip->chg_present;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT,
+ &prop);
+ chip->apsd_rerun = false;
+ } else if (!chip->apsd_rerun) {
+ /* Handle Charger removal */
+ prop.intval = POWER_SUPPLY_TYPE_UNKNOWN;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &prop);
+
+ chip->chg_present = false;
+ prop.intval = chip->chg_present;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT,
+ &prop);
+
+ pr_debug("Set usb psy dm=r df=r\n");
+ prop.intval = POWER_SUPPLY_DP_DM_DPR_DMR;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_DP_DM, &prop);
+ }
+
+ return 0;
+}
+
+/*
+ * As source detect interrupt is not triggered on the falling edge,
+ * we need to schedule a work for checking source detect status after
+ * charger UV interrupt fired.
+ */
+#define FIRST_CHECK_DELAY 100
+#define SECOND_CHECK_DELAY 1000
+static void smb1351_chg_remove_work(struct work_struct *work)
+{
+ int rc;
+ u8 reg;
+ struct smb1351_charger *chip = container_of(work,
+ struct smb1351_charger, chg_remove_work.work);
+
+ rc = smb1351_read_reg(chip, IRQ_G_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read IRQ_G_REG rc = %d\n", rc);
+ goto end;
+ }
+
+ if (!(reg & IRQ_SOURCE_DET_BIT)) {
+ pr_debug("chg removed\n");
+ smb1351_apsd_complete_handler(chip, 0);
+ } else if (!chip->chg_remove_work_scheduled) {
+ chip->chg_remove_work_scheduled = true;
+ goto reschedule;
+ } else {
+ pr_debug("charger is present\n");
+ }
+end:
+ chip->chg_remove_work_scheduled = false;
+ pm_relax(chip->dev);
+ return;
+
+reschedule:
+ pr_debug("reschedule after 1s\n");
+ schedule_delayed_work(&chip->chg_remove_work,
+ msecs_to_jiffies(SECOND_CHECK_DELAY));
+}
+
+static int smb1351_usbin_uv_handler(struct smb1351_charger *chip, u8 status)
+{
+ union power_supply_propval pval = {0, };
+
+ /* use this to detect USB insertion only if !apsd */
+ if (chip->disable_apsd) {
+ /*
+ * If APSD is disabled, src det interrupt won't trigger.
+ * Hence use usbin_uv for removal and insertion notification
+ */
+ if (status == 0) {
+ chip->chg_present = true;
+ pr_debug("updating usb_psy present=%d\n",
+ chip->chg_present);
+ pval.intval = POWER_SUPPLY_TYPE_USB;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &pval);
+
+ pval.intval = chip->chg_present;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT,
+ &pval);
+ } else {
+ chip->chg_present = false;
+
+ pval.intval = POWER_SUPPLY_TYPE_UNKNOWN;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &pval);
+
+ pr_debug("updating usb_psy present=%d\n",
+ chip->chg_present);
+ pval.intval = chip->chg_present;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT,
+ &pval);
+ }
+ return 0;
+ }
+
+ if (status) {
+ cancel_delayed_work_sync(&chip->hvdcp_det_work);
+ pm_relax(chip->dev);
+ pr_debug("schedule charger remove worker\n");
+ schedule_delayed_work(&chip->chg_remove_work,
+ msecs_to_jiffies(FIRST_CHECK_DELAY));
+ pm_stay_awake(chip->dev);
+ }
+
+ pr_debug("chip->chg_present = %d\n", chip->chg_present);
+
+ return 0;
+}
+
+static int smb1351_usbin_ov_handler(struct smb1351_charger *chip, u8 status)
+{
+ int rc;
+ u8 reg;
+ union power_supply_propval pval = {0, };
+
+ rc = smb1351_read_reg(chip, IRQ_E_REG, &reg);
+ if (rc)
+ pr_err("Couldn't read IRQ_E rc = %d\n", rc);
+
+ if (status != 0) {
+ chip->chg_present = false;
+ chip->usbin_ov = true;
+
+ pval.intval = POWER_SUPPLY_TYPE_UNKNOWN;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &pval);
+
+ pval.intval = chip->chg_present;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT,
+ &pval);
+ } else {
+ chip->usbin_ov = false;
+ if (reg & IRQ_USBIN_UV_BIT)
+ pr_debug("Charger unplugged from OV\n");
+ else
+ smb1351_apsd_complete_handler(chip, 1);
+ }
+
+ if (chip->usb_psy) {
+ pval.intval = status ? POWER_SUPPLY_HEALTH_OVERVOLTAGE
+ : POWER_SUPPLY_HEALTH_GOOD;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_HEALTH, &pval);
+ pr_debug("chip ov status is %d\n", pval.intval);
+ }
+ pr_debug("chip->chg_present = %d\n", chip->chg_present);
+
+ return 0;
+}
+
+static int smb1351_fast_chg_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("enter\n");
+ return 0;
+}
+
+static int smb1351_chg_term_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("enter\n");
+ if (!chip->bms_controlled_charging)
+ chip->batt_full = !!status;
+ return 0;
+}
+
+static int smb1351_safety_timeout_handler(struct smb1351_charger *chip,
+ u8 status)
+{
+ pr_debug("safety_timeout triggered\n");
+ return 0;
+}
+
+static int smb1351_aicl_done_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("aicl_done triggered\n");
+ return 0;
+}
+
+static int smb1351_hot_hard_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("status = 0x%02x\n", status);
+ chip->batt_hot = !!status;
+ return 0;
+}
+static int smb1351_cold_hard_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("status = 0x%02x\n", status);
+ chip->batt_cold = !!status;
+ return 0;
+}
+static int smb1351_hot_soft_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("status = 0x%02x\n", status);
+ chip->batt_warm = !!status;
+ return 0;
+}
+static int smb1351_cold_soft_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("status = 0x%02x\n", status);
+ chip->batt_cool = !!status;
+ return 0;
+}
+
+static int smb1351_battery_missing_handler(struct smb1351_charger *chip,
+ u8 status)
+{
+ if (status)
+ chip->battery_missing = true;
+ else
+ chip->battery_missing = false;
+
+ return 0;
+}
+
+static struct irq_handler_info handlers[] = {
+ [0] = {
+ .stat_reg = IRQ_A_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "cold_soft",
+ .smb_irq = smb1351_cold_soft_handler,
+ },
+ { .name = "hot_soft",
+ .smb_irq = smb1351_hot_soft_handler,
+ },
+ { .name = "cold_hard",
+ .smb_irq = smb1351_cold_hard_handler,
+ },
+ { .name = "hot_hard",
+ .smb_irq = smb1351_hot_hard_handler,
+ },
+ },
+ },
+ [1] = {
+ .stat_reg = IRQ_B_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "internal_temp_limit",
+ },
+ { .name = "vbatt_low",
+ },
+ { .name = "battery_missing",
+ .smb_irq = smb1351_battery_missing_handler,
+ },
+ { .name = "batt_therm_removed",
+ },
+ },
+ },
+ [2] = {
+ .stat_reg = IRQ_C_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "chg_term",
+ .smb_irq = smb1351_chg_term_handler,
+ },
+ { .name = "taper",
+ },
+ { .name = "recharge",
+ },
+ { .name = "fast_chg",
+ .smb_irq = smb1351_fast_chg_handler,
+ },
+ },
+ },
+ [3] = {
+ .stat_reg = IRQ_D_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "prechg_timeout",
+ },
+ { .name = "safety_timeout",
+ .smb_irq = smb1351_safety_timeout_handler,
+ },
+ { .name = "chg_error",
+ },
+ { .name = "batt_ov",
+ },
+ },
+ },
+ [4] = {
+ .stat_reg = IRQ_E_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "power_ok",
+ },
+ { .name = "afvc",
+ },
+ { .name = "usbin_uv",
+ .smb_irq = smb1351_usbin_uv_handler,
+ },
+ { .name = "usbin_ov",
+ .smb_irq = smb1351_usbin_ov_handler,
+ },
+ },
+ },
+ [5] = {
+ .stat_reg = IRQ_F_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "otg_oc_retry",
+ },
+ { .name = "rid",
+ },
+ { .name = "otg_fail",
+ },
+ { .name = "otg_oc",
+ },
+ },
+ },
+ [6] = {
+ .stat_reg = IRQ_G_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "chg_inhibit",
+ },
+ { .name = "aicl_fail",
+ },
+ { .name = "aicl_done",
+ .smb_irq = smb1351_aicl_done_handler,
+ },
+ { .name = "apsd_complete",
+ .smb_irq = smb1351_apsd_complete_handler,
+ },
+ },
+ },
+ [7] = {
+ .stat_reg = IRQ_H_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "wdog_timeout",
+ },
+ { .name = "hvdcp_auth_done",
+ },
+ },
+ },
+};
+
+#define IRQ_LATCHED_MASK 0x02
+#define IRQ_STATUS_MASK 0x01
+#define BITS_PER_IRQ 2
+static irqreturn_t smb1351_chg_stat_handler(int irq, void *dev_id)
+{
+ struct smb1351_charger *chip = dev_id;
+ int i, j;
+ u8 triggered;
+ u8 changed;
+ u8 rt_stat, prev_rt_stat;
+ int rc;
+ int handler_count = 0;
+
+ mutex_lock(&chip->irq_complete);
+
+ chip->irq_waiting = true;
+ if (!chip->resume_completed) {
+ pr_debug("IRQ triggered before device-resume\n");
+ disable_irq_nosync(irq);
+ mutex_unlock(&chip->irq_complete);
+ return IRQ_HANDLED;
+ }
+ chip->irq_waiting = false;
+
+ for (i = 0; i < ARRAY_SIZE(handlers); i++) {
+ rc = smb1351_read_reg(chip, handlers[i].stat_reg,
+ &handlers[i].val);
+ if (rc) {
+ pr_err("Couldn't read %d rc = %d\n",
+ handlers[i].stat_reg, rc);
+ continue;
+ }
+
+ for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) {
+ triggered = handlers[i].val
+ & (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ));
+ rt_stat = handlers[i].val
+ & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
+ prev_rt_stat = handlers[i].prev_val
+ & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
+ changed = prev_rt_stat ^ rt_stat;
+
+ if (triggered || changed)
+ rt_stat ? handlers[i].irq_info[j].high++ :
+ handlers[i].irq_info[j].low++;
+
+ if ((triggered || changed)
+ && handlers[i].irq_info[j].smb_irq != NULL) {
+ handler_count++;
+ rc = handlers[i].irq_info[j].smb_irq(chip,
+ rt_stat);
+ if (rc)
+ pr_err("Couldn't handle %d irq for reg 0x%02x rc = %d\n",
+ j, handlers[i].stat_reg, rc);
+ }
+ }
+ handlers[i].prev_val = handlers[i].val;
+ }
+
+ pr_debug("handler count = %d\n", handler_count);
+ if (handler_count) {
+ pr_debug("batt psy changed\n");
+ power_supply_changed(chip->batt_psy);
+ }
+
+ mutex_unlock(&chip->irq_complete);
+
+ return IRQ_HANDLED;
+}
+
+static void smb1351_external_power_changed(struct power_supply *psy)
+{
+ struct smb1351_charger *chip = power_supply_get_drvdata(psy);
+ union power_supply_propval prop = {0,};
+ int rc, current_limit = 0, online = 0;
+
+ if (chip->bms_psy_name)
+ chip->bms_psy =
+ power_supply_get_by_name((char *)chip->bms_psy_name);
+
+ rc = power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_ONLINE, &prop);
+ if (rc)
+ pr_err("Couldn't read USB online property, rc=%d\n", rc);
+ else
+ online = prop.intval;
+
+ rc = power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
+ if (rc)
+ pr_err("Couldn't read USB current_max property, rc=%d\n", rc);
+ else
+ current_limit = prop.intval / 1000;
+
+ pr_debug("online = %d, current_limit = %d\n", online, current_limit);
+
+ smb1351_enable_volatile_writes(chip);
+ smb1351_set_usb_chg_current(chip, current_limit);
+
+ pr_debug("updating batt psy\n");
+}
+
+#define LAST_CNFG_REG 0x16
+static int show_cnfg_regs(struct seq_file *m, void *data)
+{
+ struct smb1351_charger *chip = m->private;
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (!rc)
+ seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ return 0;
+}
+
+static int cnfg_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb1351_charger *chip = inode->i_private;
+
+ return single_open(file, show_cnfg_regs, chip);
+}
+
+static const struct file_operations cnfg_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = cnfg_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#define FIRST_CMD_REG 0x30
+#define LAST_CMD_REG 0x34
+static int show_cmd_regs(struct seq_file *m, void *data)
+{
+ struct smb1351_charger *chip = m->private;
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (!rc)
+ seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ return 0;
+}
+
+static int cmd_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb1351_charger *chip = inode->i_private;
+
+ return single_open(file, show_cmd_regs, chip);
+}
+
+static const struct file_operations cmd_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = cmd_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#define FIRST_STATUS_REG 0x36
+#define LAST_STATUS_REG 0x3F
+static int show_status_regs(struct seq_file *m, void *data)
+{
+ struct smb1351_charger *chip = m->private;
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (!rc)
+ seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ return 0;
+}
+
+static int status_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb1351_charger *chip = inode->i_private;
+
+ return single_open(file, show_status_regs, chip);
+}
+
+static const struct file_operations status_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = status_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int show_irq_count(struct seq_file *m, void *data)
+{
+ int i, j, total = 0;
+
+ for (i = 0; i < ARRAY_SIZE(handlers); i++)
+ for (j = 0; j < 4; j++) {
+ seq_printf(m, "%s=%d\t(high=%d low=%d)\n",
+ handlers[i].irq_info[j].name,
+ handlers[i].irq_info[j].high
+ + handlers[i].irq_info[j].low,
+ handlers[i].irq_info[j].high,
+ handlers[i].irq_info[j].low);
+ total += (handlers[i].irq_info[j].high
+ + handlers[i].irq_info[j].low);
+ }
+
+ seq_printf(m, "\n\tTotal = %d\n", total);
+
+ return 0;
+}
+
+static int irq_count_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb1351_charger *chip = inode->i_private;
+
+ return single_open(file, show_irq_count, chip);
+}
+
+static const struct file_operations irq_count_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = irq_count_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int get_reg(void *data, u64 *val)
+{
+ struct smb1351_charger *chip = data;
+ int rc;
+ u8 temp;
+
+ rc = smb1351_read_reg(chip, chip->peek_poke_address, &temp);
+ if (rc) {
+ pr_err("Couldn't read reg %x rc = %d\n",
+ chip->peek_poke_address, rc);
+ return -EAGAIN;
+ }
+ *val = temp;
+ return 0;
+}
+
+static int set_reg(void *data, u64 val)
+{
+ struct smb1351_charger *chip = data;
+ int rc;
+ u8 temp;
+
+ temp = (u8) val;
+ rc = smb1351_write_reg(chip, chip->peek_poke_address, temp);
+ if (rc) {
+ pr_err("Couldn't write 0x%02x to 0x%02x rc= %d\n",
+ temp, chip->peek_poke_address, rc);
+ return -EAGAIN;
+ }
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n");
+
+static int force_irq_set(void *data, u64 val)
+{
+ struct smb1351_charger *chip = data;
+
+ smb1351_chg_stat_handler(chip->client->irq, data);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n");
+
+#ifdef DEBUG
+static void dump_regs(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (rc)
+ pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc);
+ else
+ pr_debug("0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (rc)
+ pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc);
+ else
+ pr_debug("0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (rc)
+ pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc);
+ else
+ pr_debug("0x%02x = 0x%02x\n", addr, reg);
+ }
+}
+#else
+static void dump_regs(struct smb1351_charger *chip)
+{
+}
+#endif
+
+static int smb1351_parse_dt(struct smb1351_charger *chip)
+{
+ int rc;
+ struct device_node *node = chip->dev->of_node;
+
+ if (!node) {
+ pr_err("device tree info. missing\n");
+ return -EINVAL;
+ }
+
+ chip->usb_suspended_status = of_property_read_bool(node,
+ "qcom,charging-disabled");
+
+ chip->chg_autonomous_mode = of_property_read_bool(node,
+ "qcom,chg-autonomous-mode");
+
+ chip->disable_apsd = of_property_read_bool(node, "qcom,disable-apsd");
+
+ chip->using_pmic_therm = of_property_read_bool(node,
+ "qcom,using-pmic-therm");
+ chip->bms_controlled_charging = of_property_read_bool(node,
+ "qcom,bms-controlled-charging");
+ chip->force_hvdcp_2p0 = of_property_read_bool(node,
+ "qcom,force-hvdcp-2p0");
+
+ rc = of_property_read_string(node, "qcom,bms-psy-name",
+ &chip->bms_psy_name);
+ if (rc)
+ chip->bms_psy_name = NULL;
+
+ rc = of_property_read_u32(node, "qcom,fastchg-current-max-ma",
+ &chip->target_fastchg_current_max_ma);
+ if (rc)
+ chip->target_fastchg_current_max_ma = SMB1351_CHG_FAST_MAX_MA;
+
+ chip->iterm_disabled = of_property_read_bool(node,
+ "qcom,iterm-disabled");
+
+ rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma);
+ if (rc)
+ chip->iterm_ma = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,float-voltage-mv",
+ &chip->vfloat_mv);
+ if (rc)
+ chip->vfloat_mv = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,recharge-mv",
+ &chip->recharge_mv);
+ if (rc)
+ chip->recharge_mv = -EINVAL;
+
+ chip->recharge_disabled = of_property_read_bool(node,
+ "qcom,recharge-disabled");
+
+ /* thermal and jeita support */
+ rc = of_property_read_u32(node, "qcom,batt-cold-decidegc",
+ &chip->batt_cold_decidegc);
+ if (rc < 0)
+ chip->batt_cold_decidegc = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,batt-hot-decidegc",
+ &chip->batt_hot_decidegc);
+ if (rc < 0)
+ chip->batt_hot_decidegc = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,batt-warm-decidegc",
+ &chip->batt_warm_decidegc);
+
+ rc |= of_property_read_u32(node, "qcom,batt-cool-decidegc",
+ &chip->batt_cool_decidegc);
+
+ if (!rc) {
+ rc = of_property_read_u32(node, "qcom,batt-cool-mv",
+ &chip->batt_cool_mv);
+
+ rc |= of_property_read_u32(node, "qcom,batt-warm-mv",
+ &chip->batt_warm_mv);
+
+ rc |= of_property_read_u32(node, "qcom,batt-cool-ma",
+ &chip->batt_cool_ma);
+
+ rc |= of_property_read_u32(node, "qcom,batt-warm-ma",
+ &chip->batt_warm_ma);
+ if (rc)
+ chip->jeita_supported = false;
+ else
+ chip->jeita_supported = true;
+ }
+
+ pr_debug("jeita_supported = %d\n", chip->jeita_supported);
+
+ rc = of_property_read_u32(node, "qcom,batt-missing-decidegc",
+ &chip->batt_missing_decidegc);
+
+ chip->pinctrl_state_name = of_get_property(node, "pinctrl-names", NULL);
+
+ return 0;
+}
+
+static int smb1351_determine_initial_state(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg = 0;
+
+ /*
+ * It is okay to read the interrupt status here since
+ * interrupts aren't requested. Reading interrupt status
+ * clears the interrupt so be careful to read interrupt
+ * status only in interrupt handling code
+ */
+
+ rc = smb1351_read_reg(chip, IRQ_B_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read IRQ_B rc = %d\n", rc);
+ goto fail_init_status;
+ }
+
+ chip->battery_missing = (reg & IRQ_BATT_MISSING_BIT) ? true : false;
+
+ rc = smb1351_read_reg(chip, IRQ_C_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read IRQ_C rc = %d\n", rc);
+ goto fail_init_status;
+ }
+ chip->batt_full = (reg & IRQ_TERM_BIT) ? true : false;
+
+ rc = smb1351_read_reg(chip, IRQ_A_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read irq A rc = %d\n", rc);
+ return rc;
+ }
+
+ if (reg & IRQ_HOT_HARD_BIT)
+ chip->batt_hot = true;
+ if (reg & IRQ_COLD_HARD_BIT)
+ chip->batt_cold = true;
+ if (reg & IRQ_HOT_SOFT_BIT)
+ chip->batt_warm = true;
+ if (reg & IRQ_COLD_SOFT_BIT)
+ chip->batt_cool = true;
+
+ rc = smb1351_read_reg(chip, IRQ_E_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read IRQ_E rc = %d\n", rc);
+ goto fail_init_status;
+ }
+
+ if (reg & IRQ_USBIN_UV_BIT) {
+ smb1351_usbin_uv_handler(chip, 1);
+ } else {
+ smb1351_usbin_uv_handler(chip, 0);
+ smb1351_apsd_complete_handler(chip, 1);
+ }
+
+ rc = smb1351_read_reg(chip, IRQ_G_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read IRQ_G rc = %d\n", rc);
+ goto fail_init_status;
+ }
+
+ if (reg & IRQ_SOURCE_DET_BIT)
+ smb1351_apsd_complete_handler(chip, 1);
+
+ return 0;
+
+fail_init_status:
+ pr_err("Couldn't determine initial status\n");
+ return rc;
+}
+
+static int is_parallel_charger(struct i2c_client *client)
+{
+ struct device_node *node = client->dev.of_node;
+
+ return of_property_read_bool(node, "qcom,parallel-charger");
+}
+
+static int create_debugfs_entries(struct smb1351_charger *chip)
+{
+ struct dentry *ent;
+
+ chip->debug_root = debugfs_create_dir("smb1351", NULL);
+ if (!chip->debug_root) {
+ pr_err("Couldn't create debug dir\n");
+ } else {
+ ent = debugfs_create_file("config_registers", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &cnfg_debugfs_ops);
+ if (!ent)
+ pr_err("Couldn't create cnfg debug file\n");
+
+ ent = debugfs_create_file("status_registers", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &status_debugfs_ops);
+ if (!ent)
+ pr_err("Couldn't create status debug file\n");
+
+ ent = debugfs_create_file("cmd_registers", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &cmd_debugfs_ops);
+ if (!ent)
+ pr_err("Couldn't create cmd debug file\n");
+
+ ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root,
+ &(chip->peek_poke_address));
+ if (!ent)
+ pr_err("Couldn't create address debug file\n");
+
+ ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root, chip,
+ &poke_poke_debug_ops);
+ if (!ent)
+ pr_err("Couldn't create data debug file\n");
+
+ ent = debugfs_create_file("force_irq",
+ S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root, chip,
+ &force_irq_ops);
+ if (!ent)
+ pr_err("Couldn't create data debug file\n");
+
+ ent = debugfs_create_file("irq_count", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &irq_count_debugfs_ops);
+ if (!ent)
+ pr_err("Couldn't create count debug file\n");
+ }
+ return 0;
+}
+
+static int smb1351_main_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int rc;
+ struct smb1351_charger *chip;
+ struct power_supply *usb_psy;
+ struct power_supply_config batt_psy_cfg = {};
+ u8 reg = 0;
+
+ usb_psy = power_supply_get_by_name("usb");
+ if (!usb_psy) {
+ pr_debug("USB psy not found; deferring probe\n");
+ return -EPROBE_DEFER;
+ }
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ pr_err("Couldn't allocate memory\n");
+ return -ENOMEM;
+ }
+
+ chip->client = client;
+ chip->dev = &client->dev;
+ chip->usb_psy = usb_psy;
+ chip->fake_battery_soc = -EINVAL;
+ INIT_DELAYED_WORK(&chip->chg_remove_work, smb1351_chg_remove_work);
+ INIT_DELAYED_WORK(&chip->hvdcp_det_work, smb1351_hvdcp_det_work);
+ device_init_wakeup(chip->dev, true);
+
+ /* probe the device to check if its actually connected */
+ rc = smb1351_read_reg(chip, CHG_REVISION_REG, &reg);
+ if (rc) {
+ pr_err("Failed to detect smb1351, device may be absent\n");
+ return -ENODEV;
+ }
+ pr_debug("smb1351 chip revision is %d\n", reg);
+
+ rc = smb1351_parse_dt(chip);
+ if (rc) {
+ pr_err("Couldn't parse DT nodes rc=%d\n", rc);
+ return rc;
+ }
+
+ /* using vadc and adc_tm for implementing pmic therm */
+ if (chip->using_pmic_therm) {
+ chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg");
+ if (IS_ERR(chip->vadc_dev)) {
+ rc = PTR_ERR(chip->vadc_dev);
+ if (rc != -EPROBE_DEFER)
+ pr_err("vadc property missing\n");
+ return rc;
+ }
+ chip->adc_tm_dev = qpnp_get_adc_tm(chip->dev, "chg");
+ if (IS_ERR(chip->adc_tm_dev)) {
+ rc = PTR_ERR(chip->adc_tm_dev);
+ if (rc != -EPROBE_DEFER)
+ pr_err("adc_tm property missing\n");
+ return rc;
+ }
+ }
+
+ i2c_set_clientdata(client, chip);
+
+ chip->batt_psy_d.name = "battery";
+ chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY;
+ chip->batt_psy_d.get_property = smb1351_battery_get_property;
+ chip->batt_psy_d.set_property = smb1351_battery_set_property;
+ chip->batt_psy_d.property_is_writeable =
+ smb1351_batt_property_is_writeable;
+ chip->batt_psy_d.properties = smb1351_battery_properties;
+ chip->batt_psy_d.num_properties =
+ ARRAY_SIZE(smb1351_battery_properties);
+ chip->batt_psy_d.external_power_changed =
+ smb1351_external_power_changed;
+
+ chip->resume_completed = true;
+ mutex_init(&chip->irq_complete);
+
+ batt_psy_cfg.drv_data = chip;
+ batt_psy_cfg.supplied_to = pm_batt_supplied_to;
+ batt_psy_cfg.num_supplicants = ARRAY_SIZE(pm_batt_supplied_to);
+ chip->batt_psy = devm_power_supply_register(chip->dev,
+ &chip->batt_psy_d,
+ &batt_psy_cfg);
+ if (IS_ERR(chip->batt_psy)) {
+ pr_err("Couldn't register batt psy rc=%ld\n",
+ PTR_ERR(chip->batt_psy));
+ return rc;
+ }
+
+ dump_regs(chip);
+
+ rc = smb1351_regulator_init(chip);
+ if (rc) {
+ pr_err("Couldn't initialize smb1351 ragulator rc=%d\n", rc);
+ goto fail_smb1351_regulator_init;
+ }
+
+ rc = smb1351_hw_init(chip);
+ if (rc) {
+ pr_err("Couldn't intialize hardware rc=%d\n", rc);
+ goto fail_smb1351_hw_init;
+ }
+
+ rc = smb1351_determine_initial_state(chip);
+ if (rc) {
+ pr_err("Couldn't determine initial state rc=%d\n", rc);
+ goto fail_smb1351_hw_init;
+ }
+
+ /* STAT irq configuration */
+ if (client->irq) {
+ rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ smb1351_chg_stat_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "smb1351_chg_stat_irq", chip);
+ if (rc) {
+ pr_err("Failed STAT irq=%d request rc = %d\n",
+ client->irq, rc);
+ goto fail_smb1351_hw_init;
+ }
+ enable_irq_wake(client->irq);
+ }
+
+ if (chip->using_pmic_therm) {
+ if (!chip->jeita_supported) {
+ /* add hot/cold temperature monitor */
+ chip->adc_param.low_temp = chip->batt_cold_decidegc;
+ chip->adc_param.high_temp = chip->batt_hot_decidegc;
+ } else {
+ chip->adc_param.low_temp = chip->batt_cool_decidegc;
+ chip->adc_param.high_temp = chip->batt_warm_decidegc;
+ }
+ chip->adc_param.timer_interval = ADC_MEAS1_INTERVAL_500MS;
+ chip->adc_param.state_request = ADC_TM_WARM_COOL_THR_ENABLE;
+ chip->adc_param.btm_ctx = chip;
+ chip->adc_param.threshold_notification =
+ smb1351_chg_adc_notification;
+ chip->adc_param.channel = LR_MUX1_BATT_THERM;
+
+ rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev,
+ &chip->adc_param);
+ if (rc) {
+ pr_err("requesting ADC error %d\n", rc);
+ goto fail_smb1351_hw_init;
+ }
+ }
+
+ create_debugfs_entries(chip);
+
+ dump_regs(chip);
+
+ pr_info("smb1351 successfully probed. charger=%d, batt=%d version=%s\n",
+ chip->chg_present,
+ smb1351_get_prop_batt_present(chip),
+ smb1351_version_str[chip->version]);
+ return 0;
+
+fail_smb1351_hw_init:
+ regulator_unregister(chip->otg_vreg.rdev);
+fail_smb1351_regulator_init:
+ return rc;
+}
+
+static int smb1351_parallel_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int rc;
+ struct smb1351_charger *chip;
+ struct device_node *node = client->dev.of_node;
+ struct power_supply_config parallel_psy_cfg = {};
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ pr_err("Couldn't allocate memory\n");
+ return -ENOMEM;
+ }
+
+ chip->client = client;
+ chip->dev = &client->dev;
+ chip->parallel_charger = true;
+ chip->parallel_charger_suspended = true;
+
+ chip->usb_suspended_status = of_property_read_bool(node,
+ "qcom,charging-disabled");
+ rc = of_property_read_u32(node, "qcom,float-voltage-mv",
+ &chip->vfloat_mv);
+ if (rc)
+ chip->vfloat_mv = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,recharge-mv",
+ &chip->recharge_mv);
+ if (rc)
+ chip->recharge_mv = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,parallel-en-pin-polarity",
+ &chip->parallel_pin_polarity_setting);
+ if (rc)
+ chip->parallel_pin_polarity_setting = EN_BY_PIN_LOW_ENABLE;
+ else
+ chip->parallel_pin_polarity_setting =
+ chip->parallel_pin_polarity_setting ?
+ EN_BY_PIN_HIGH_ENABLE : EN_BY_PIN_LOW_ENABLE;
+
+ if (of_property_read_bool(node,
+ "qcom,parallel-external-current-sense"))
+ chip->parallel_mode = POWER_SUPPLY_PL_USBIN_USBIN_EXT;
+ else
+ chip->parallel_mode = POWER_SUPPLY_PL_USBIN_USBIN;
+
+ i2c_set_clientdata(client, chip);
+
+ chip->parallel_psy_d.name = "parallel";
+ chip->parallel_psy_d.type = POWER_SUPPLY_TYPE_PARALLEL;
+ chip->parallel_psy_d.get_property = smb1351_parallel_get_property;
+ chip->parallel_psy_d.set_property = smb1351_parallel_set_property;
+ chip->parallel_psy_d.properties = smb1351_parallel_properties;
+ chip->parallel_psy_d.property_is_writeable
+ = smb1351_parallel_is_writeable;
+ chip->parallel_psy_d.num_properties
+ = ARRAY_SIZE(smb1351_parallel_properties);
+
+ parallel_psy_cfg.drv_data = chip;
+ parallel_psy_cfg.num_supplicants = 0;
+ chip->parallel_psy = devm_power_supply_register(chip->dev,
+ &chip->parallel_psy_d,
+ &parallel_psy_cfg);
+ if (IS_ERR(chip->parallel_psy)) {
+ pr_err("Couldn't register parallel psy rc=%ld\n",
+ PTR_ERR(chip->parallel_psy));
+ return rc;
+ }
+
+ chip->resume_completed = true;
+ mutex_init(&chip->irq_complete);
+
+ create_debugfs_entries(chip);
+
+ pr_info("smb1351 parallel successfully probed.\n");
+
+ return 0;
+}
+
+static int smb1351_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ if (is_parallel_charger(client))
+ return smb1351_parallel_charger_probe(client, id);
+ else
+ return smb1351_main_charger_probe(client, id);
+}
+
+static int smb1351_charger_remove(struct i2c_client *client)
+{
+ struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+ cancel_delayed_work_sync(&chip->chg_remove_work);
+
+ mutex_destroy(&chip->irq_complete);
+ debugfs_remove_recursive(chip->debug_root);
+ return 0;
+}
+
+static int smb1351_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+ /* no suspend resume activities for parallel charger */
+ if (chip->parallel_charger)
+ return 0;
+
+ mutex_lock(&chip->irq_complete);
+ chip->resume_completed = false;
+ mutex_unlock(&chip->irq_complete);
+
+ return 0;
+}
+
+static int smb1351_suspend_noirq(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+ /* no suspend resume activities for parallel charger */
+ if (chip->parallel_charger)
+ return 0;
+
+ if (chip->irq_waiting) {
+ pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int smb1351_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+ /* no suspend resume activities for parallel charger */
+ if (chip->parallel_charger)
+ return 0;
+
+ mutex_lock(&chip->irq_complete);
+ chip->resume_completed = true;
+ if (chip->irq_waiting) {
+ mutex_unlock(&chip->irq_complete);
+ smb1351_chg_stat_handler(client->irq, chip);
+ enable_irq(client->irq);
+ } else {
+ mutex_unlock(&chip->irq_complete);
+ }
+ return 0;
+}
+
+static const struct dev_pm_ops smb1351_pm_ops = {
+ .suspend = smb1351_suspend,
+ .suspend_noirq = smb1351_suspend_noirq,
+ .resume = smb1351_resume,
+};
+
+static struct of_device_id smb1351_match_table[] = {
+ { .compatible = "qcom,smb1351-charger",},
+ { },
+};
+
+static const struct i2c_device_id smb1351_charger_id[] = {
+ {"smb1351-charger", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, smb1351_charger_id);
+
+static struct i2c_driver smb1351_charger_driver = {
+ .driver = {
+ .name = "smb1351-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = smb1351_match_table,
+ .pm = &smb1351_pm_ops,
+ },
+ .probe = smb1351_charger_probe,
+ .remove = smb1351_charger_remove,
+ .id_table = smb1351_charger_id,
+};
+
+module_i2c_driver(smb1351_charger_driver);
+
+MODULE_DESCRIPTION("smb1351 Charger");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("i2c:smb1351-charger");
diff --git a/drivers/power/supply/qcom/smb135x-charger.c b/drivers/power/supply/qcom/smb135x-charger.c
new file mode 100644
index 000000000000..08af01544590
--- /dev/null
+++ b/drivers/power/supply/qcom/smb135x-charger.c
@@ -0,0 +1,4584 @@
+/* Copyright (c) 2013-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/i2c.h>
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/bitops.h>
+#include <linux/mutex.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include <linux/pinctrl/consumer.h>
+
+#define SMB135X_BITS_PER_REG 8
+
+/* Mask/Bit helpers */
+#define _SMB135X_MASK(BITS, POS) \
+ ((unsigned char)(((1 << (BITS)) - 1) << (POS)))
+#define SMB135X_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \
+ _SMB135X_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \
+ (RIGHT_BIT_POS))
+
+/* Config registers */
+#define CFG_3_REG 0x03
+#define CHG_ITERM_50MA 0x08
+#define CHG_ITERM_100MA 0x10
+#define CHG_ITERM_150MA 0x18
+#define CHG_ITERM_200MA 0x20
+#define CHG_ITERM_250MA 0x28
+#define CHG_ITERM_300MA 0x00
+#define CHG_ITERM_500MA 0x30
+#define CHG_ITERM_600MA 0x38
+#define CHG_ITERM_MASK SMB135X_MASK(5, 3)
+
+#define CFG_4_REG 0x04
+#define CHG_INHIBIT_MASK SMB135X_MASK(7, 6)
+#define CHG_INHIBIT_50MV_VAL 0x00
+#define CHG_INHIBIT_100MV_VAL 0x40
+#define CHG_INHIBIT_200MV_VAL 0x80
+#define CHG_INHIBIT_300MV_VAL 0xC0
+
+#define CFG_5_REG 0x05
+#define RECHARGE_200MV_BIT BIT(2)
+#define USB_2_3_BIT BIT(5)
+
+#define CFG_A_REG 0x0A
+#define DCIN_INPUT_MASK SMB135X_MASK(4, 0)
+
+#define CFG_C_REG 0x0C
+#define USBIN_INPUT_MASK SMB135X_MASK(4, 0)
+#define USBIN_ADAPTER_ALLOWANCE_MASK SMB135X_MASK(7, 5)
+#define ALLOW_5V_ONLY 0x00
+#define ALLOW_5V_OR_9V 0x20
+#define ALLOW_5V_TO_9V 0x40
+#define ALLOW_9V_ONLY 0x60
+
+#define CFG_D_REG 0x0D
+
+#define CFG_E_REG 0x0E
+#define POLARITY_100_500_BIT BIT(2)
+#define USB_CTRL_BY_PIN_BIT BIT(1)
+#define HVDCP_5_9_BIT BIT(4)
+
+#define CFG_11_REG 0x11
+#define PRIORITY_BIT BIT(7)
+#define AUTO_SRC_DET_EN_BIT BIT(0)
+
+#define USBIN_DCIN_CFG_REG 0x12
+#define USBIN_SUSPEND_VIA_COMMAND_BIT BIT(6)
+
+#define CFG_14_REG 0x14
+#define CHG_EN_BY_PIN_BIT BIT(7)
+#define CHG_EN_ACTIVE_LOW_BIT BIT(6)
+#define CHG_EN_ACTIVE_HIGH_BIT 0x0
+#define PRE_TO_FAST_REQ_CMD_BIT BIT(5)
+#define DISABLE_CURRENT_TERM_BIT BIT(3)
+#define DISABLE_AUTO_RECHARGE_BIT BIT(2)
+#define EN_CHG_INHIBIT_BIT BIT(0)
+
+#define CFG_16_REG 0x16
+#define SAFETY_TIME_EN_BIT BIT(5)
+#define SAFETY_TIME_EN_SHIFT 5
+#define SAFETY_TIME_MINUTES_MASK SMB135X_MASK(3, 2)
+#define SAFETY_TIME_MINUTES_SHIFT 2
+
+#define CFG_17_REG 0x17
+#define CHG_STAT_DISABLE_BIT BIT(0)
+#define CHG_STAT_ACTIVE_HIGH_BIT BIT(1)
+#define CHG_STAT_IRQ_ONLY_BIT BIT(4)
+
+#define CFG_19_REG 0x19
+#define BATT_MISSING_ALGO_BIT BIT(2)
+#define BATT_MISSING_THERM_BIT BIT(1)
+
+#define CFG_1A_REG 0x1A
+#define HOT_SOFT_VFLOAT_COMP_EN_BIT BIT(3)
+#define COLD_SOFT_VFLOAT_COMP_EN_BIT BIT(2)
+#define HOT_SOFT_CURRENT_COMP_EN_BIT BIT(1)
+#define COLD_SOFT_CURRENT_COMP_EN_BIT BIT(0)
+
+#define CFG_1B_REG 0x1B
+#define COLD_HARD_MASK SMB135X_MASK(7, 6)
+#define COLD_HARD_SHIFT 6
+#define HOT_HARD_MASK SMB135X_MASK(5, 4)
+#define HOT_HARD_SHIFT 4
+#define COLD_SOFT_MASK SMB135X_MASK(3, 2)
+#define COLD_SOFT_SHIFT 2
+#define HOT_SOFT_MASK SMB135X_MASK(1, 0)
+#define HOT_SOFT_SHIFT 0
+
+#define VFLOAT_REG 0x1E
+
+#define VERSION1_REG 0x2A
+#define VERSION1_MASK SMB135X_MASK(7, 6)
+#define VERSION1_SHIFT 6
+#define VERSION2_REG 0x32
+#define VERSION2_MASK SMB135X_MASK(1, 0)
+#define VERSION3_REG 0x34
+
+/* Irq Config registers */
+#define IRQ_CFG_REG 0x07
+#define IRQ_BAT_HOT_COLD_HARD_BIT BIT(7)
+#define IRQ_BAT_HOT_COLD_SOFT_BIT BIT(6)
+#define IRQ_OTG_OVER_CURRENT_BIT BIT(4)
+#define IRQ_USBIN_UV_BIT BIT(2)
+#define IRQ_INTERNAL_TEMPERATURE_BIT BIT(0)
+
+#define IRQ2_CFG_REG 0x08
+#define IRQ2_SAFETY_TIMER_BIT BIT(7)
+#define IRQ2_CHG_ERR_BIT BIT(6)
+#define IRQ2_CHG_PHASE_CHANGE_BIT BIT(4)
+#define IRQ2_CHG_INHIBIT_BIT BIT(3)
+#define IRQ2_POWER_OK_BIT BIT(2)
+#define IRQ2_BATT_MISSING_BIT BIT(1)
+#define IRQ2_VBAT_LOW_BIT BIT(0)
+
+#define IRQ3_CFG_REG 0x09
+#define IRQ3_RID_DETECT_BIT BIT(4)
+#define IRQ3_SRC_DETECT_BIT BIT(2)
+#define IRQ3_DCIN_UV_BIT BIT(0)
+
+#define USBIN_OTG_REG 0x0F
+#define OTG_CNFG_MASK SMB135X_MASK(3, 2)
+#define OTG_CNFG_PIN_CTRL 0x04
+#define OTG_CNFG_COMMAND_CTRL 0x08
+#define OTG_CNFG_AUTO_CTRL 0x0C
+
+/* Command Registers */
+#define CMD_I2C_REG 0x40
+#define ALLOW_VOLATILE_BIT BIT(6)
+
+#define CMD_INPUT_LIMIT 0x41
+#define USB_SHUTDOWN_BIT BIT(6)
+#define DC_SHUTDOWN_BIT BIT(5)
+#define USE_REGISTER_FOR_CURRENT BIT(2)
+#define USB_100_500_AC_MASK SMB135X_MASK(1, 0)
+#define USB_100_VAL 0x02
+#define USB_500_VAL 0x00
+#define USB_AC_VAL 0x01
+
+#define CMD_CHG_REG 0x42
+#define CMD_CHG_EN BIT(1)
+#define OTG_EN BIT(0)
+
+/* Status registers */
+#define STATUS_1_REG 0x47
+#define USING_USB_BIT BIT(1)
+#define USING_DC_BIT BIT(0)
+
+#define STATUS_2_REG 0x48
+#define HARD_LIMIT_STS_BIT BIT(6)
+
+#define STATUS_4_REG 0x4A
+#define BATT_NET_CHG_CURRENT_BIT BIT(7)
+#define BATT_LESS_THAN_2V BIT(4)
+#define CHG_HOLD_OFF_BIT BIT(3)
+#define CHG_TYPE_MASK SMB135X_MASK(2, 1)
+#define CHG_TYPE_SHIFT 1
+#define BATT_NOT_CHG_VAL 0x0
+#define BATT_PRE_CHG_VAL 0x1
+#define BATT_FAST_CHG_VAL 0x2
+#define BATT_TAPER_CHG_VAL 0x3
+#define CHG_EN_BIT BIT(0)
+
+#define STATUS_5_REG 0x4B
+#define CDP_BIT BIT(7)
+#define DCP_BIT BIT(6)
+#define OTHER_BIT BIT(5)
+#define SDP_BIT BIT(4)
+#define ACA_A_BIT BIT(3)
+#define ACA_B_BIT BIT(2)
+#define ACA_C_BIT BIT(1)
+#define ACA_DOCK_BIT BIT(0)
+
+#define STATUS_6_REG 0x4C
+#define RID_FLOAT_BIT BIT(3)
+#define RID_A_BIT BIT(2)
+#define RID_B_BIT BIT(1)
+#define RID_C_BIT BIT(0)
+
+#define STATUS_7_REG 0x4D
+
+#define STATUS_8_REG 0x4E
+#define USBIN_9V BIT(5)
+#define USBIN_UNREG BIT(4)
+#define USBIN_LV BIT(3)
+#define DCIN_9V BIT(2)
+#define DCIN_UNREG BIT(1)
+#define DCIN_LV BIT(0)
+
+#define STATUS_9_REG 0x4F
+#define REV_MASK SMB135X_MASK(3, 0)
+
+/* Irq Status registers */
+#define IRQ_A_REG 0x50
+#define IRQ_A_HOT_HARD_BIT BIT(6)
+#define IRQ_A_COLD_HARD_BIT BIT(4)
+#define IRQ_A_HOT_SOFT_BIT BIT(2)
+#define IRQ_A_COLD_SOFT_BIT BIT(0)
+
+#define IRQ_B_REG 0x51
+#define IRQ_B_BATT_TERMINAL_BIT BIT(6)
+#define IRQ_B_BATT_MISSING_BIT BIT(4)
+#define IRQ_B_VBAT_LOW_BIT BIT(2)
+#define IRQ_B_TEMPERATURE_BIT BIT(0)
+
+#define IRQ_C_REG 0x52
+#define IRQ_C_TERM_BIT BIT(0)
+#define IRQ_C_FASTCHG_BIT BIT(6)
+
+#define IRQ_D_REG 0x53
+#define IRQ_D_TIMEOUT_BIT BIT(2)
+
+#define IRQ_E_REG 0x54
+#define IRQ_E_DC_OV_BIT BIT(6)
+#define IRQ_E_DC_UV_BIT BIT(4)
+#define IRQ_E_USB_OV_BIT BIT(2)
+#define IRQ_E_USB_UV_BIT BIT(0)
+
+#define IRQ_F_REG 0x55
+#define IRQ_F_POWER_OK_BIT BIT(0)
+
+#define IRQ_G_REG 0x56
+#define IRQ_G_SRC_DETECT_BIT BIT(6)
+
+enum {
+ WRKARND_USB100_BIT = BIT(0),
+ WRKARND_APSD_FAIL = BIT(1),
+};
+
+enum {
+ REV_1 = 1, /* Rev 1.0 */
+ REV_1_1 = 2, /* Rev 1.1 */
+ REV_2 = 3, /* Rev 2 */
+ REV_2_1 = 5, /* Rev 2.1 */
+ REV_MAX,
+};
+
+static char *revision_str[] = {
+ [REV_1] = "rev1",
+ [REV_1_1] = "rev1.1",
+ [REV_2] = "rev2",
+ [REV_2_1] = "rev2.1",
+};
+
+enum {
+ V_SMB1356,
+ V_SMB1357,
+ V_SMB1358,
+ V_SMB1359,
+ V_MAX,
+};
+
+static int version_data[] = {
+ [V_SMB1356] = V_SMB1356,
+ [V_SMB1357] = V_SMB1357,
+ [V_SMB1358] = V_SMB1358,
+ [V_SMB1359] = V_SMB1359,
+};
+
+static char *version_str[] = {
+ [V_SMB1356] = "smb1356",
+ [V_SMB1357] = "smb1357",
+ [V_SMB1358] = "smb1358",
+ [V_SMB1359] = "smb1359",
+};
+
+enum {
+ USER = BIT(0),
+ THERMAL = BIT(1),
+ CURRENT = BIT(2),
+};
+
+enum path_type {
+ USB,
+ DC,
+};
+
+static int chg_time[] = {
+ 192,
+ 384,
+ 768,
+ 1536,
+};
+
+static char *pm_batt_supplied_to[] = {
+ "bms",
+};
+
+struct smb135x_regulator {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+};
+
+struct smb135x_chg {
+ struct i2c_client *client;
+ struct device *dev;
+ struct mutex read_write_lock;
+
+ u8 revision;
+ int version;
+
+ bool chg_enabled;
+ bool chg_disabled_permanently;
+
+ bool usb_present;
+ bool dc_present;
+ bool usb_slave_present;
+ bool dc_ov;
+
+ bool bmd_algo_disabled;
+ bool iterm_disabled;
+ int iterm_ma;
+ int vfloat_mv;
+ int safety_time;
+ int resume_delta_mv;
+ int fake_battery_soc;
+ struct dentry *debug_root;
+ int usb_current_arr_size;
+ int *usb_current_table;
+ int dc_current_arr_size;
+ int *dc_current_table;
+ bool inhibit_disabled;
+ int fastchg_current_arr_size;
+ int *fastchg_current_table;
+ int fastchg_ma;
+ u8 irq_cfg_mask[3];
+ int otg_oc_count;
+ struct delayed_work reset_otg_oc_count_work;
+ struct mutex otg_oc_count_lock;
+ struct delayed_work hvdcp_det_work;
+
+ bool parallel_charger;
+ bool parallel_charger_present;
+ bool bms_controlled_charging;
+ u32 parallel_pin_polarity_setting;
+
+ /* psy */
+ struct power_supply *usb_psy;
+ int usb_psy_ma;
+ int real_usb_psy_ma;
+ struct power_supply_desc batt_psy_d;
+ struct power_supply *batt_psy;
+ struct power_supply_desc dc_psy_d;
+ struct power_supply *dc_psy;
+ struct power_supply_desc parallel_psy_d;
+ struct power_supply *parallel_psy;
+ struct power_supply *bms_psy;
+ int dc_psy_type;
+ int dc_psy_ma;
+ const char *bms_psy_name;
+
+ /* status tracking */
+ bool chg_done_batt_full;
+ bool batt_present;
+ bool batt_hot;
+ bool batt_cold;
+ bool batt_warm;
+ bool batt_cool;
+
+ bool resume_completed;
+ bool irq_waiting;
+ u32 usb_suspended;
+ u32 dc_suspended;
+ struct mutex path_suspend_lock;
+
+ u32 peek_poke_address;
+ struct smb135x_regulator otg_vreg;
+ int skip_writes;
+ int skip_reads;
+ u32 workaround_flags;
+ bool soft_vfloat_comp_disabled;
+ bool soft_current_comp_disabled;
+ struct mutex irq_complete;
+ struct regulator *therm_bias_vreg;
+ struct regulator *usb_pullup_vreg;
+ struct delayed_work wireless_insertion_work;
+
+ unsigned int thermal_levels;
+ unsigned int therm_lvl_sel;
+ unsigned int *thermal_mitigation;
+ unsigned int gamma_setting_num;
+ unsigned int *gamma_setting;
+ struct mutex current_change_lock;
+
+ const char *pinctrl_state_name;
+ struct pinctrl *smb_pinctrl;
+
+ bool apsd_rerun;
+ bool id_line_not_connected;
+};
+
+#define RETRY_COUNT 5
+int retry_sleep_ms[RETRY_COUNT] = {
+ 10, 20, 30, 40, 50
+};
+
+static int __smb135x_read(struct smb135x_chg *chip, int reg,
+ u8 *val)
+{
+ s32 ret;
+ int retry_count = 0;
+
+retry:
+ ret = i2c_smbus_read_byte_data(chip->client, reg);
+ if (ret < 0 && retry_count < RETRY_COUNT) {
+ /* sleep for few ms before retrying */
+ msleep(retry_sleep_ms[retry_count++]);
+ goto retry;
+ }
+ if (ret < 0) {
+ dev_err(chip->dev,
+ "i2c read fail: can't read from %02x: %d\n", reg, ret);
+ return ret;
+ } else {
+ *val = ret;
+ }
+
+ return 0;
+}
+
+static int __smb135x_write(struct smb135x_chg *chip, int reg,
+ u8 val)
+{
+ s32 ret;
+ int retry_count = 0;
+
+retry:
+ ret = i2c_smbus_write_byte_data(chip->client, reg, val);
+ if (ret < 0 && retry_count < RETRY_COUNT) {
+ /* sleep for few ms before retrying */
+ msleep(retry_sleep_ms[retry_count++]);
+ goto retry;
+ }
+ if (ret < 0) {
+ dev_err(chip->dev,
+ "i2c write fail: can't write %02x to %02x: %d\n",
+ val, reg, ret);
+ return ret;
+ }
+ pr_debug("Writing 0x%02x=0x%02x\n", reg, val);
+ return 0;
+}
+
+static int smb135x_read(struct smb135x_chg *chip, int reg,
+ u8 *val)
+{
+ int rc;
+
+ if (chip->skip_reads) {
+ *val = 0;
+ return 0;
+ }
+ mutex_lock(&chip->read_write_lock);
+ pm_stay_awake(chip->dev);
+ rc = __smb135x_read(chip, reg, val);
+ pm_relax(chip->dev);
+ mutex_unlock(&chip->read_write_lock);
+
+ return rc;
+}
+
+static int smb135x_write(struct smb135x_chg *chip, int reg,
+ u8 val)
+{
+ int rc;
+
+ if (chip->skip_writes)
+ return 0;
+
+ mutex_lock(&chip->read_write_lock);
+ pm_stay_awake(chip->dev);
+ rc = __smb135x_write(chip, reg, val);
+ pm_relax(chip->dev);
+ mutex_unlock(&chip->read_write_lock);
+
+ return rc;
+}
+
+static int smb135x_masked_write(struct smb135x_chg *chip, int reg,
+ u8 mask, u8 val)
+{
+ s32 rc;
+ u8 temp;
+
+ if (chip->skip_writes || chip->skip_reads)
+ return 0;
+
+ mutex_lock(&chip->read_write_lock);
+ rc = __smb135x_read(chip, reg, &temp);
+ if (rc < 0) {
+ dev_err(chip->dev, "read failed: reg=%03X, rc=%d\n", reg, rc);
+ goto out;
+ }
+ temp &= ~mask;
+ temp |= val & mask;
+ rc = __smb135x_write(chip, reg, temp);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "write failed: reg=%03X, rc=%d\n", reg, rc);
+ }
+out:
+ mutex_unlock(&chip->read_write_lock);
+ return rc;
+}
+
+static int read_revision(struct smb135x_chg *chip, u8 *revision)
+{
+ int rc;
+ u8 reg;
+
+ rc = smb135x_read(chip, STATUS_9_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read status 9 rc = %d\n", rc);
+ return rc;
+ }
+ *revision = (reg & REV_MASK);
+ return 0;
+}
+
+static int read_version1(struct smb135x_chg *chip, u8 *version)
+{
+ int rc;
+ u8 reg;
+
+ rc = smb135x_read(chip, VERSION1_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read version 1 rc = %d\n", rc);
+ return rc;
+ }
+ *version = (reg & VERSION1_MASK) >> VERSION1_SHIFT;
+ return 0;
+}
+
+static int read_version2(struct smb135x_chg *chip, u8 *version)
+{
+ int rc;
+ u8 reg;
+
+ rc = smb135x_read(chip, VERSION2_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read version 2 rc = %d\n", rc);
+ return rc;
+ }
+ *version = (reg & VERSION2_MASK);
+ return 0;
+}
+
+static int read_version3(struct smb135x_chg *chip, u8 *version)
+{
+ int rc;
+ u8 reg;
+
+ rc = smb135x_read(chip, VERSION3_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read version 3 rc = %d\n", rc);
+ return rc;
+ }
+ *version = reg;
+ return 0;
+}
+
+#define TRIM_23_REG 0x23
+#define CHECK_USB100_GOOD_BIT BIT(1)
+static bool is_usb100_broken(struct smb135x_chg *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smb135x_read(chip, TRIM_23_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read status 9 rc = %d\n", rc);
+ return rc;
+ }
+ return !!(reg & CHECK_USB100_GOOD_BIT);
+}
+
+static bool is_usb_slave_present(struct smb135x_chg *chip)
+{
+ bool usb_slave_present;
+ u8 reg;
+ int rc;
+
+ if (chip->id_line_not_connected)
+ return false;
+
+ rc = smb135x_read(chip, STATUS_6_REG, &reg);
+ if (rc < 0) {
+ pr_err("Couldn't read stat 6 rc = %d\n", rc);
+ return false;
+ }
+
+ if ((reg & (RID_FLOAT_BIT | RID_A_BIT | RID_B_BIT | RID_C_BIT)) == 0)
+ usb_slave_present = 1;
+ else
+ usb_slave_present = 0;
+
+ pr_debug("stat6= 0x%02x slave_present = %d\n", reg, usb_slave_present);
+ return usb_slave_present;
+}
+
+static char *usb_type_str[] = {
+ "ACA_DOCK", /* bit 0 */
+ "ACA_C", /* bit 1 */
+ "ACA_B", /* bit 2 */
+ "ACA_A", /* bit 3 */
+ "SDP", /* bit 4 */
+ "OTHER", /* bit 5 */
+ "DCP", /* bit 6 */
+ "CDP", /* bit 7 */
+ "NONE", /* bit 8 error case */
+};
+
+/* helper to return the string of USB type */
+static char *get_usb_type_name(u8 stat_5)
+{
+ unsigned long stat = stat_5;
+
+ return usb_type_str[find_first_bit(&stat, SMB135X_BITS_PER_REG)];
+}
+
+static enum power_supply_type usb_type_enum[] = {
+ POWER_SUPPLY_TYPE_USB_ACA, /* bit 0 */
+ POWER_SUPPLY_TYPE_USB_ACA, /* bit 1 */
+ POWER_SUPPLY_TYPE_USB_ACA, /* bit 2 */
+ POWER_SUPPLY_TYPE_USB_ACA, /* bit 3 */
+ POWER_SUPPLY_TYPE_USB, /* bit 4 */
+ POWER_SUPPLY_TYPE_UNKNOWN, /* bit 5 */
+ POWER_SUPPLY_TYPE_USB_DCP, /* bit 6 */
+ POWER_SUPPLY_TYPE_USB_CDP, /* bit 7 */
+ POWER_SUPPLY_TYPE_UNKNOWN, /* bit 8 error case, report UNKNWON */
+};
+
+/* helper to return enum power_supply_type of USB type */
+static enum power_supply_type get_usb_supply_type(u8 stat_5)
+{
+ unsigned long stat = stat_5;
+
+ return usb_type_enum[find_first_bit(&stat, SMB135X_BITS_PER_REG)];
+}
+
+static enum power_supply_property smb135x_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
+};
+
+static int smb135x_get_prop_batt_status(struct smb135x_chg *chip)
+{
+ int rc;
+ int status = POWER_SUPPLY_STATUS_DISCHARGING;
+ u8 reg = 0;
+ u8 chg_type;
+
+ if (chip->chg_done_batt_full)
+ return POWER_SUPPLY_STATUS_FULL;
+
+ rc = smb135x_read(chip, STATUS_4_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Unable to read STATUS_4_REG rc = %d\n", rc);
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ if (reg & CHG_HOLD_OFF_BIT) {
+ /*
+ * when chg hold off happens the battery is
+ * not charging
+ */
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ goto out;
+ }
+
+ chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT;
+
+ if (chg_type == BATT_NOT_CHG_VAL)
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_CHARGING;
+out:
+ pr_debug("STATUS_4_REG=%x\n", reg);
+ return status;
+}
+
+static int smb135x_get_prop_batt_present(struct smb135x_chg *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smb135x_read(chip, STATUS_4_REG, &reg);
+ if (rc < 0)
+ return 0;
+
+ /* treat battery gone if less than 2V */
+ if (reg & BATT_LESS_THAN_2V)
+ return 0;
+
+ return chip->batt_present;
+}
+
+static int smb135x_get_prop_charge_type(struct smb135x_chg *chip)
+{
+ int rc;
+ u8 reg;
+ u8 chg_type;
+
+ rc = smb135x_read(chip, STATUS_4_REG, &reg);
+ if (rc < 0)
+ return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+
+ chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT;
+ if (chg_type == BATT_NOT_CHG_VAL)
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+ else if (chg_type == BATT_FAST_CHG_VAL)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else if (chg_type == BATT_PRE_CHG_VAL)
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ else if (chg_type == BATT_TAPER_CHG_VAL)
+ return POWER_SUPPLY_CHARGE_TYPE_TAPER;
+
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+#define DEFAULT_BATT_CAPACITY 50
+static int smb135x_get_prop_batt_capacity(struct smb135x_chg *chip)
+{
+ union power_supply_propval ret = {0, };
+
+ if (chip->fake_battery_soc >= 0)
+ return chip->fake_battery_soc;
+ if (chip->bms_psy) {
+ power_supply_get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_CAPACITY, &ret);
+ return ret.intval;
+ }
+
+ return DEFAULT_BATT_CAPACITY;
+}
+
+static int smb135x_get_prop_batt_health(struct smb135x_chg *chip)
+{
+ union power_supply_propval ret = {0, };
+
+ if (chip->batt_hot)
+ ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (chip->batt_cold)
+ ret.intval = POWER_SUPPLY_HEALTH_COLD;
+ else if (chip->batt_warm)
+ ret.intval = POWER_SUPPLY_HEALTH_WARM;
+ else if (chip->batt_cool)
+ ret.intval = POWER_SUPPLY_HEALTH_COOL;
+ else
+ ret.intval = POWER_SUPPLY_HEALTH_GOOD;
+
+ return ret.intval;
+}
+
+static int smb135x_enable_volatile_writes(struct smb135x_chg *chip)
+{
+ int rc;
+
+ rc = smb135x_masked_write(chip, CMD_I2C_REG,
+ ALLOW_VOLATILE_BIT, ALLOW_VOLATILE_BIT);
+ if (rc < 0)
+ dev_err(chip->dev,
+ "Couldn't set VOLATILE_W_PERM_BIT rc=%d\n", rc);
+
+ return rc;
+}
+
+static int usb_current_table_smb1356[] = {
+ 180,
+ 240,
+ 270,
+ 285,
+ 300,
+ 330,
+ 360,
+ 390,
+ 420,
+ 540,
+ 570,
+ 600,
+ 660,
+ 720,
+ 840,
+ 900,
+ 960,
+ 1080,
+ 1110,
+ 1128,
+ 1146,
+ 1170,
+ 1182,
+ 1200,
+ 1230,
+ 1260,
+ 1380,
+ 1440,
+ 1560,
+ 1620,
+ 1680,
+ 1800
+};
+
+static int fastchg_current_table[] = {
+ 300,
+ 400,
+ 450,
+ 475,
+ 500,
+ 550,
+ 600,
+ 650,
+ 700,
+ 900,
+ 950,
+ 1000,
+ 1100,
+ 1200,
+ 1400,
+ 2700,
+ 1500,
+ 1600,
+ 1800,
+ 1850,
+ 1880,
+ 1910,
+ 2800,
+ 1950,
+ 1970,
+ 2000,
+ 2050,
+ 2100,
+ 2300,
+ 2400,
+ 2500,
+ 3000
+};
+
+static int usb_current_table_smb1357_smb1358[] = {
+ 300,
+ 400,
+ 450,
+ 475,
+ 500,
+ 550,
+ 600,
+ 650,
+ 700,
+ 900,
+ 950,
+ 1000,
+ 1100,
+ 1200,
+ 1400,
+ 1450,
+ 1500,
+ 1600,
+ 1800,
+ 1850,
+ 1880,
+ 1910,
+ 1930,
+ 1950,
+ 1970,
+ 2000,
+ 2050,
+ 2100,
+ 2300,
+ 2400,
+ 2500,
+ 3000
+};
+
+static int usb_current_table_smb1359[] = {
+ 300,
+ 400,
+ 450,
+ 475,
+ 500,
+ 550,
+ 600,
+ 650,
+ 700,
+ 900,
+ 950,
+ 1000,
+ 1100,
+ 1200,
+ 1400,
+ 1450,
+ 1500,
+ 1600,
+ 1800,
+ 1850,
+ 1880,
+ 1910,
+ 1930,
+ 1950,
+ 1970,
+ 2000,
+ 2050,
+ 2100,
+ 2300,
+ 2400,
+ 2500
+};
+
+static int dc_current_table_smb1356[] = {
+ 180,
+ 240,
+ 270,
+ 285,
+ 300,
+ 330,
+ 360,
+ 390,
+ 420,
+ 540,
+ 570,
+ 600,
+ 660,
+ 720,
+ 840,
+ 870,
+ 900,
+ 960,
+ 1080,
+ 1110,
+ 1128,
+ 1146,
+ 1158,
+ 1170,
+ 1182,
+ 1200,
+};
+
+static int dc_current_table[] = {
+ 300,
+ 400,
+ 450,
+ 475,
+ 500,
+ 550,
+ 600,
+ 650,
+ 700,
+ 900,
+ 950,
+ 1000,
+ 1100,
+ 1200,
+ 1400,
+ 1450,
+ 1500,
+ 1600,
+ 1800,
+ 1850,
+ 1880,
+ 1910,
+ 1930,
+ 1950,
+ 1970,
+ 2000,
+};
+
+#define CURRENT_100_MA 100
+#define CURRENT_150_MA 150
+#define CURRENT_500_MA 500
+#define CURRENT_900_MA 900
+#define SUSPEND_CURRENT_MA 2
+
+static int __smb135x_usb_suspend(struct smb135x_chg *chip, bool suspend)
+{
+ int rc;
+
+ rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+ USB_SHUTDOWN_BIT, suspend ? USB_SHUTDOWN_BIT : 0);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc);
+ return rc;
+}
+
+static int __smb135x_dc_suspend(struct smb135x_chg *chip, bool suspend)
+{
+ int rc = 0;
+
+ rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+ DC_SHUTDOWN_BIT, suspend ? DC_SHUTDOWN_BIT : 0);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc);
+ return rc;
+}
+
+static int smb135x_path_suspend(struct smb135x_chg *chip, enum path_type path,
+ int reason, bool suspend)
+{
+ int rc = 0;
+ int suspended;
+ int *path_suspended;
+ int (*func)(struct smb135x_chg *chip, bool suspend);
+
+ mutex_lock(&chip->path_suspend_lock);
+ if (path == USB) {
+ suspended = chip->usb_suspended;
+ path_suspended = &chip->usb_suspended;
+ func = __smb135x_usb_suspend;
+ } else {
+ suspended = chip->dc_suspended;
+ path_suspended = &chip->dc_suspended;
+ func = __smb135x_dc_suspend;
+ }
+
+ if (suspend == false)
+ suspended &= ~reason;
+ else
+ suspended |= reason;
+
+ if (*path_suspended && !suspended)
+ rc = func(chip, 0);
+ if (!(*path_suspended) && suspended)
+ rc = func(chip, 1);
+
+ if (rc)
+ dev_err(chip->dev, "Couldn't set/unset suspend for %s path rc = %d\n",
+ path == USB ? "usb" : "dc",
+ rc);
+ else
+ *path_suspended = suspended;
+
+ mutex_unlock(&chip->path_suspend_lock);
+ return rc;
+}
+
+static int smb135x_get_usb_chg_current(struct smb135x_chg *chip)
+{
+ if (chip->usb_suspended)
+ return SUSPEND_CURRENT_MA;
+ else
+ return chip->real_usb_psy_ma;
+}
+#define FCC_MASK SMB135X_MASK(4, 0)
+#define CFG_1C_REG 0x1C
+static int smb135x_get_fastchg_current(struct smb135x_chg *chip)
+{
+ u8 reg;
+ int rc;
+
+ rc = smb135x_read(chip, CFG_1C_REG, &reg);
+ if (rc < 0) {
+ pr_debug("cannot read 1c rc = %d\n", rc);
+ return 0;
+ }
+ reg &= FCC_MASK;
+ if (reg < 0 || chip->fastchg_current_arr_size == 0
+ || reg > chip->fastchg_current_table[
+ chip->fastchg_current_arr_size - 1]) {
+ dev_err(chip->dev, "Current table out of range\n");
+ return -EINVAL;
+ }
+ return chip->fastchg_current_table[reg];
+}
+
+static int smb135x_set_fastchg_current(struct smb135x_chg *chip,
+ int current_ma)
+{
+ int i, rc, diff, best, best_diff;
+ u8 reg;
+
+ /*
+ * if there is no array loaded or if the smallest current limit is
+ * above the requested current, then do nothing
+ */
+ if (chip->fastchg_current_arr_size == 0) {
+ dev_err(chip->dev, "no table loaded\n");
+ return -EINVAL;
+ } else if ((current_ma - chip->fastchg_current_table[0]) < 0) {
+ dev_err(chip->dev, "invalid current requested\n");
+ return -EINVAL;
+ }
+
+ /* use the closest setting under the requested current */
+ best = 0;
+ best_diff = current_ma - chip->fastchg_current_table[best];
+
+ for (i = 1; i < chip->fastchg_current_arr_size; i++) {
+ diff = current_ma - chip->fastchg_current_table[i];
+ if (diff >= 0 && diff < best_diff) {
+ best_diff = diff;
+ best = i;
+ }
+ }
+ i = best;
+
+ reg = i & FCC_MASK;
+ rc = smb135x_masked_write(chip, CFG_1C_REG, FCC_MASK, reg);
+ if (rc < 0)
+ dev_err(chip->dev, "cannot write to config c rc = %d\n", rc);
+ pr_debug("fastchg current set to %dma\n",
+ chip->fastchg_current_table[i]);
+ return rc;
+}
+
+static int smb135x_set_high_usb_chg_current(struct smb135x_chg *chip,
+ int current_ma)
+{
+ int i, rc;
+ u8 usb_cur_val;
+
+ for (i = chip->usb_current_arr_size - 1; i >= 0; i--) {
+ if (current_ma >= chip->usb_current_table[i])
+ break;
+ }
+ if (i < 0) {
+ dev_err(chip->dev,
+ "Cannot find %dma current_table using %d\n",
+ current_ma, CURRENT_150_MA);
+ rc = smb135x_masked_write(chip, CFG_5_REG,
+ USB_2_3_BIT, USB_2_3_BIT);
+ rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+ USB_100_500_AC_MASK, USB_100_VAL);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set %dmA rc=%d\n",
+ CURRENT_150_MA, rc);
+ else
+ chip->real_usb_psy_ma = CURRENT_150_MA;
+ return rc;
+ }
+
+ usb_cur_val = i & USBIN_INPUT_MASK;
+ rc = smb135x_masked_write(chip, CFG_C_REG,
+ USBIN_INPUT_MASK, usb_cur_val);
+ if (rc < 0) {
+ dev_err(chip->dev, "cannot write to config c rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+ USB_100_500_AC_MASK, USB_AC_VAL);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't write cfg 5 rc = %d\n", rc);
+ else
+ chip->real_usb_psy_ma = chip->usb_current_table[i];
+ return rc;
+}
+
+#define MAX_VERSION 0xF
+#define USB_100_PROBLEM_VERSION 0x2
+/* if APSD results are used
+ * if SDP is detected it will look at 500mA setting
+ * if set it will draw 500mA
+ * if unset it will draw 100mA
+ * if CDP/DCP it will look at 0x0C setting
+ * i.e. values in 0x41[1, 0] does not matter
+ */
+static int smb135x_set_usb_chg_current(struct smb135x_chg *chip,
+ int current_ma)
+{
+ int rc;
+
+ pr_debug("USB current_ma = %d\n", current_ma);
+
+ if (chip->workaround_flags & WRKARND_USB100_BIT) {
+ pr_info("USB requested = %dmA using %dmA\n", current_ma,
+ CURRENT_500_MA);
+ current_ma = CURRENT_500_MA;
+ }
+
+ if (current_ma == 0)
+ /* choose the lowest available value of 100mA */
+ current_ma = CURRENT_100_MA;
+
+ if (current_ma == SUSPEND_CURRENT_MA) {
+ /* force suspend bit */
+ rc = smb135x_path_suspend(chip, USB, CURRENT, true);
+ chip->real_usb_psy_ma = SUSPEND_CURRENT_MA;
+ goto out;
+ }
+ if (current_ma < CURRENT_150_MA) {
+ /* force 100mA */
+ rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, 0);
+ rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+ USB_100_500_AC_MASK, USB_100_VAL);
+ rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
+ chip->real_usb_psy_ma = CURRENT_100_MA;
+ goto out;
+ }
+ /* specific current values */
+ if (current_ma == CURRENT_150_MA) {
+ rc = smb135x_masked_write(chip, CFG_5_REG,
+ USB_2_3_BIT, USB_2_3_BIT);
+ rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+ USB_100_500_AC_MASK, USB_100_VAL);
+ rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
+ chip->real_usb_psy_ma = CURRENT_150_MA;
+ goto out;
+ }
+ if (current_ma == CURRENT_500_MA) {
+ rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, 0);
+ rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+ USB_100_500_AC_MASK, USB_500_VAL);
+ rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
+ chip->real_usb_psy_ma = CURRENT_500_MA;
+ goto out;
+ }
+ if (current_ma == CURRENT_900_MA) {
+ rc = smb135x_masked_write(chip, CFG_5_REG,
+ USB_2_3_BIT, USB_2_3_BIT);
+ rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+ USB_100_500_AC_MASK, USB_500_VAL);
+ rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
+ chip->real_usb_psy_ma = CURRENT_900_MA;
+ goto out;
+ }
+
+ rc = smb135x_set_high_usb_chg_current(chip, current_ma);
+ rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
+out:
+ if (rc < 0)
+ dev_err(chip->dev,
+ "Couldn't set %dmA rc = %d\n", current_ma, rc);
+ return rc;
+}
+
+static int smb135x_set_dc_chg_current(struct smb135x_chg *chip,
+ int current_ma)
+{
+ int i, rc;
+ u8 dc_cur_val;
+
+ for (i = chip->dc_current_arr_size - 1; i >= 0; i--) {
+ if (chip->dc_psy_ma >= chip->dc_current_table[i])
+ break;
+ }
+ dc_cur_val = i & DCIN_INPUT_MASK;
+ rc = smb135x_masked_write(chip, CFG_A_REG,
+ DCIN_INPUT_MASK, dc_cur_val);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set dc charge current rc = %d\n",
+ rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int smb135x_set_appropriate_current(struct smb135x_chg *chip,
+ enum path_type path)
+{
+ int therm_ma, current_ma;
+ int path_current = (path == USB) ? chip->usb_psy_ma : chip->dc_psy_ma;
+ int (*func)(struct smb135x_chg *chip, int current_ma);
+ int rc = 0;
+
+ if (!chip->usb_psy && path == USB)
+ return 0;
+
+ /*
+ * If battery is absent do not modify the current at all, these
+ * would be some appropriate values set by the bootloader or default
+ * configuration and since it is the only source of power we should
+ * not change it
+ */
+ if (!chip->batt_present) {
+ pr_debug("ignoring current request since battery is absent\n");
+ return 0;
+ }
+
+ if (path == USB) {
+ path_current = chip->usb_psy_ma;
+ func = smb135x_set_usb_chg_current;
+ } else {
+ path_current = chip->dc_psy_ma;
+ func = smb135x_set_dc_chg_current;
+ if (chip->dc_psy_type == -EINVAL)
+ func = NULL;
+ }
+
+ if (chip->therm_lvl_sel > 0
+ && chip->therm_lvl_sel < (chip->thermal_levels - 1))
+ /*
+ * consider thermal limit only when it is active and not at
+ * the highest level
+ */
+ therm_ma = chip->thermal_mitigation[chip->therm_lvl_sel];
+ else
+ therm_ma = path_current;
+
+ current_ma = min(therm_ma, path_current);
+ if (func != NULL)
+ rc = func(chip, current_ma);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set %s current to min(%d, %d)rc = %d\n",
+ path == USB ? "usb" : "dc",
+ therm_ma, path_current,
+ rc);
+ return rc;
+}
+
+static int smb135x_charging_enable(struct smb135x_chg *chip, int enable)
+{
+ int rc;
+
+ rc = smb135x_masked_write(chip, CMD_CHG_REG,
+ CMD_CHG_EN, enable ? CMD_CHG_EN : 0);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set CHG_ENABLE_BIT enable = %d rc = %d\n",
+ enable, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int __smb135x_charging(struct smb135x_chg *chip, int enable)
+{
+ int rc = 0;
+
+ pr_debug("charging enable = %d\n", enable);
+
+ if (chip->chg_disabled_permanently) {
+ pr_debug("charging is disabled permanetly\n");
+ return -EINVAL;
+ }
+
+ rc = smb135x_charging_enable(chip, enable);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't %s charging rc = %d\n",
+ enable ? "enable" : "disable", rc);
+ return rc;
+ }
+ chip->chg_enabled = enable;
+
+ /* set the suspended status */
+ rc = smb135x_path_suspend(chip, DC, USER, !enable);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set dc suspend to %d rc = %d\n",
+ enable, rc);
+ return rc;
+ }
+ rc = smb135x_path_suspend(chip, USB, USER, !enable);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set usb suspend to %d rc = %d\n",
+ enable, rc);
+ return rc;
+ }
+
+ pr_debug("charging %s\n",
+ enable ? "enabled" : "disabled running from batt");
+ return rc;
+}
+
+static int smb135x_charging(struct smb135x_chg *chip, int enable)
+{
+ int rc = 0;
+
+ pr_debug("charging enable = %d\n", enable);
+
+ __smb135x_charging(chip, enable);
+
+ if (chip->usb_psy) {
+ pr_debug("usb psy changed\n");
+ power_supply_changed(chip->usb_psy);
+ }
+ if (chip->dc_psy_type != -EINVAL) {
+ pr_debug("dc psy changed\n");
+ power_supply_changed(chip->dc_psy);
+ }
+ pr_debug("charging %s\n",
+ enable ? "enabled" : "disabled running from batt");
+ return rc;
+}
+
+static int smb135x_system_temp_level_set(struct smb135x_chg *chip,
+ int lvl_sel)
+{
+ int rc = 0;
+ int prev_therm_lvl;
+
+ if (!chip->thermal_mitigation) {
+ pr_err("Thermal mitigation not supported\n");
+ return -EINVAL;
+ }
+
+ if (lvl_sel < 0) {
+ pr_err("Unsupported level selected %d\n", lvl_sel);
+ return -EINVAL;
+ }
+
+ if (lvl_sel >= chip->thermal_levels) {
+ pr_err("Unsupported level selected %d forcing %d\n", lvl_sel,
+ chip->thermal_levels - 1);
+ lvl_sel = chip->thermal_levels - 1;
+ }
+
+ if (lvl_sel == chip->therm_lvl_sel)
+ return 0;
+
+ mutex_lock(&chip->current_change_lock);
+ prev_therm_lvl = chip->therm_lvl_sel;
+ chip->therm_lvl_sel = lvl_sel;
+ if (chip->therm_lvl_sel == (chip->thermal_levels - 1)) {
+ /*
+ * Disable charging if highest value selected by
+ * setting the DC and USB path in suspend
+ */
+ rc = smb135x_path_suspend(chip, DC, THERMAL, true);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set dc suspend rc %d\n", rc);
+ goto out;
+ }
+ rc = smb135x_path_suspend(chip, USB, THERMAL, true);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set usb suspend rc %d\n", rc);
+ goto out;
+ }
+ goto out;
+ }
+
+ smb135x_set_appropriate_current(chip, USB);
+ smb135x_set_appropriate_current(chip, DC);
+
+ if (prev_therm_lvl == chip->thermal_levels - 1) {
+ /*
+ * If previously highest value was selected charging must have
+ * been disabed. Enable charging by taking the DC and USB path
+ * out of suspend.
+ */
+ rc = smb135x_path_suspend(chip, DC, THERMAL, false);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set dc suspend rc %d\n", rc);
+ goto out;
+ }
+ rc = smb135x_path_suspend(chip, USB, THERMAL, false);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set usb suspend rc %d\n", rc);
+ goto out;
+ }
+ }
+out:
+ mutex_unlock(&chip->current_change_lock);
+ return rc;
+}
+
+static int smb135x_battery_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int rc = 0, update_psy = 0;
+ struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!chip->bms_controlled_charging) {
+ rc = -EINVAL;
+ break;
+ }
+ switch (val->intval) {
+ case POWER_SUPPLY_STATUS_FULL:
+ rc = smb135x_charging_enable(chip, false);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't disable charging rc = %d\n",
+ rc);
+ } else {
+ chip->chg_done_batt_full = true;
+ update_psy = 1;
+ dev_dbg(chip->dev, "status = FULL chg_done_batt_full = %d",
+ chip->chg_done_batt_full);
+ }
+ break;
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ chip->chg_done_batt_full = false;
+ update_psy = 1;
+ dev_dbg(chip->dev, "status = DISCHARGING chg_done_batt_full = %d",
+ chip->chg_done_batt_full);
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ rc = smb135x_charging_enable(chip, true);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't enable charging rc = %d\n",
+ rc);
+ } else {
+ chip->chg_done_batt_full = false;
+ dev_dbg(chip->dev, "status = CHARGING chg_done_batt_full = %d",
+ chip->chg_done_batt_full);
+ }
+ break;
+ default:
+ update_psy = 0;
+ rc = -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ smb135x_charging(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ chip->fake_battery_soc = val->intval;
+ update_psy = 1;
+ break;
+ case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+ smb135x_system_temp_level_set(chip, val->intval);
+ break;
+ default:
+ rc = -EINVAL;
+ }
+
+ if (!rc && update_psy)
+ power_supply_changed(chip->batt_psy);
+ return rc;
+}
+
+static int smb135x_battery_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ int rc;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+ rc = 1;
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+ return rc;
+}
+
+static int smb135x_battery_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = smb135x_get_prop_batt_status(chip);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = smb135x_get_prop_batt_present(chip);
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ val->intval = chip->chg_enabled;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = smb135x_get_prop_charge_type(chip);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = smb135x_get_prop_batt_capacity(chip);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = smb135x_get_prop_batt_health(chip);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+ val->intval = chip->therm_lvl_sel;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property smb135x_dc_properties[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
+static int smb135x_dc_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = chip->dc_present;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = chip->chg_enabled ? chip->dc_present : 0;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = chip->dc_present;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#define MIN_FLOAT_MV 3600
+#define MAX_FLOAT_MV 4500
+
+#define MID_RANGE_FLOAT_MV_MIN 3600
+#define MID_RANGE_FLOAT_MIN_VAL 0x05
+#define MID_RANGE_FLOAT_STEP_MV 20
+
+#define HIGH_RANGE_FLOAT_MIN_MV 4340
+#define HIGH_RANGE_FLOAT_MIN_VAL 0x2A
+#define HIGH_RANGE_FLOAT_STEP_MV 10
+
+#define VHIGH_RANGE_FLOAT_MIN_MV 4400
+#define VHIGH_RANGE_FLOAT_MIN_VAL 0x2E
+#define VHIGH_RANGE_FLOAT_STEP_MV 20
+static int smb135x_float_voltage_set(struct smb135x_chg *chip, int vfloat_mv)
+{
+ u8 temp;
+
+ if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) {
+ dev_err(chip->dev, "bad float voltage mv =%d asked to set\n",
+ vfloat_mv);
+ return -EINVAL;
+ }
+
+ if (vfloat_mv <= HIGH_RANGE_FLOAT_MIN_MV) {
+ /* mid range */
+ temp = MID_RANGE_FLOAT_MIN_VAL
+ + (vfloat_mv - MID_RANGE_FLOAT_MV_MIN)
+ / MID_RANGE_FLOAT_STEP_MV;
+ } else if (vfloat_mv < VHIGH_RANGE_FLOAT_MIN_MV) {
+ /* high range */
+ temp = HIGH_RANGE_FLOAT_MIN_VAL
+ + (vfloat_mv - HIGH_RANGE_FLOAT_MIN_MV)
+ / HIGH_RANGE_FLOAT_STEP_MV;
+ } else {
+ /* very high range */
+ temp = VHIGH_RANGE_FLOAT_MIN_VAL
+ + (vfloat_mv - VHIGH_RANGE_FLOAT_MIN_MV)
+ / VHIGH_RANGE_FLOAT_STEP_MV;
+ }
+
+ return smb135x_write(chip, VFLOAT_REG, temp);
+}
+
+static int smb135x_set_resume_threshold(struct smb135x_chg *chip,
+ int resume_delta_mv)
+{
+ int rc;
+ u8 reg;
+
+ if (!chip->inhibit_disabled) {
+ if (resume_delta_mv < 100)
+ reg = CHG_INHIBIT_50MV_VAL;
+ else if (resume_delta_mv < 200)
+ reg = CHG_INHIBIT_100MV_VAL;
+ else if (resume_delta_mv < 300)
+ reg = CHG_INHIBIT_200MV_VAL;
+ else
+ reg = CHG_INHIBIT_300MV_VAL;
+
+ rc = smb135x_masked_write(chip, CFG_4_REG, CHG_INHIBIT_MASK,
+ reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set inhibit val rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (resume_delta_mv < 200)
+ reg = 0;
+ else
+ reg = RECHARGE_200MV_BIT;
+
+ rc = smb135x_masked_write(chip, CFG_5_REG, RECHARGE_200MV_BIT, reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set recharge rc = %d\n", rc);
+ return rc;
+ }
+ return 0;
+}
+
+static enum power_supply_property smb135x_parallel_properties[] = {
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+};
+
+static bool smb135x_is_input_current_limited(struct smb135x_chg *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smb135x_read(chip, STATUS_2_REG, &reg);
+ if (rc) {
+ pr_debug("Couldn't read _REG for ICL status rc = %d\n", rc);
+ return false;
+ }
+
+ return !!(reg & HARD_LIMIT_STS_BIT);
+}
+
+static int smb135x_parallel_set_chg_present(struct smb135x_chg *chip,
+ int present)
+{
+ u8 val;
+ int rc;
+
+ if (present == chip->parallel_charger_present) {
+ pr_debug("present %d -> %d, skipping\n",
+ chip->parallel_charger_present, present);
+ return 0;
+ }
+
+ if (present) {
+ /* Check if SMB135x is present */
+ rc = smb135x_read(chip, VERSION1_REG, &val);
+ if (rc) {
+ pr_debug("Failed to detect smb135x-parallel charger may be absent\n");
+ return -ENODEV;
+ }
+
+ rc = smb135x_enable_volatile_writes(chip);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't configure for volatile rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ /* set the float voltage */
+ if (chip->vfloat_mv != -EINVAL) {
+ rc = smb135x_float_voltage_set(chip, chip->vfloat_mv);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set float voltage rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* resume threshold */
+ if (chip->resume_delta_mv != -EINVAL) {
+ smb135x_set_resume_threshold(chip,
+ chip->resume_delta_mv);
+ }
+
+ rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+ USE_REGISTER_FOR_CURRENT,
+ USE_REGISTER_FOR_CURRENT);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set input limit cmd rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* set chg en by pin active low and enable auto recharge */
+ rc = smb135x_masked_write(chip, CFG_14_REG,
+ CHG_EN_BY_PIN_BIT | CHG_EN_ACTIVE_LOW_BIT
+ | DISABLE_AUTO_RECHARGE_BIT,
+ CHG_EN_BY_PIN_BIT |
+ chip->parallel_pin_polarity_setting);
+
+ /* set bit 0 = 100mA bit 1 = 500mA and set register control */
+ rc = smb135x_masked_write(chip, CFG_E_REG,
+ POLARITY_100_500_BIT | USB_CTRL_BY_PIN_BIT,
+ POLARITY_100_500_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set usbin cfg rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* control USB suspend via command bits */
+ rc = smb135x_masked_write(chip, USBIN_DCIN_CFG_REG,
+ USBIN_SUSPEND_VIA_COMMAND_BIT,
+ USBIN_SUSPEND_VIA_COMMAND_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set cfg rc=%d\n", rc);
+ return rc;
+ }
+
+ /* set the fastchg_current to the lowest setting */
+ if (chip->fastchg_current_arr_size > 0)
+ rc = smb135x_set_fastchg_current(chip,
+ chip->fastchg_current_table[0]);
+
+ /*
+ * enforce chip->chg_enabled since this could be the first
+ * time we have i2c access to the charger after
+ * chip->chg_enabled has been modified
+ */
+ smb135x_charging(chip, chip->chg_enabled);
+ }
+
+ chip->parallel_charger_present = present;
+ /*
+ * When present is being set force USB suspend, start charging
+ * only when CURRENT_MAX is set.
+ *
+ * Usually the chip will be shutdown (no i2c access to the chip)
+ * when USB is removed, however there could be situations when
+ * it is not. To cover for USB reinsetions in such situations
+ * force USB suspend when present is being unset.
+ * It is likely that i2c access could fail here - do not return error.
+ * (It is not possible to detect whether the chip is in shutdown state
+ * or not except for the i2c error).
+ */
+ chip->usb_psy_ma = SUSPEND_CURRENT_MA;
+ rc = smb135x_path_suspend(chip, USB, CURRENT, true);
+
+ if (present) {
+ if (rc) {
+ dev_err(chip->dev,
+ "Couldn't set usb suspend to true rc = %d\n",
+ rc);
+ return rc;
+ }
+ /* Check if the USB is configured for suspend. If not, do it */
+ mutex_lock(&chip->path_suspend_lock);
+ rc = smb135x_read(chip, CMD_INPUT_LIMIT, &val);
+ if (rc) {
+ dev_err(chip->dev,
+ "Couldn't read 0x%02x rc:%d\n", CMD_INPUT_LIMIT,
+ rc);
+ mutex_unlock(&chip->path_suspend_lock);
+ return rc;
+ } else if (!(val & BIT(6))) {
+ rc = __smb135x_usb_suspend(chip, 1);
+ }
+ mutex_unlock(&chip->path_suspend_lock);
+ if (rc) {
+ dev_err(chip->dev,
+ "Couldn't set usb to suspend rc:%d\n", rc);
+ return rc;
+ }
+ } else {
+ chip->real_usb_psy_ma = SUSPEND_CURRENT_MA;
+ }
+ return 0;
+}
+
+static int smb135x_parallel_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+ struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ if (chip->parallel_charger_present)
+ smb135x_charging(chip, val->intval);
+ else
+ chip->chg_enabled = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ rc = smb135x_parallel_set_chg_present(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ if (chip->parallel_charger_present) {
+ rc = smb135x_set_fastchg_current(chip,
+ val->intval / 1000);
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (chip->parallel_charger_present) {
+ chip->usb_psy_ma = val->intval / 1000;
+ rc = smb135x_set_usb_chg_current(chip,
+ chip->usb_psy_ma);
+ }
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (chip->parallel_charger_present &&
+ (chip->vfloat_mv != val->intval)) {
+ rc = smb135x_float_voltage_set(chip, val->intval);
+ if (!rc)
+ chip->vfloat_mv = val->intval;
+ } else {
+ chip->vfloat_mv = val->intval;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return rc;
+}
+
+static int smb135x_parallel_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ int rc;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ rc = 1;
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+ return rc;
+}
+static int smb135x_parallel_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ val->intval = chip->chg_enabled;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (chip->parallel_charger_present)
+ val->intval = smb135x_get_usb_chg_current(chip) * 1000;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = chip->vfloat_mv;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = chip->parallel_charger_present;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ if (chip->parallel_charger_present)
+ val->intval = smb135x_get_fastchg_current(chip) * 1000;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (chip->parallel_charger_present)
+ val->intval = smb135x_get_prop_batt_status(chip);
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ if (chip->parallel_charger_present)
+ val->intval = smb135x_is_input_current_limited(chip);
+ else
+ val->intval = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void smb135x_external_power_changed(struct power_supply *psy)
+{
+ struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+ union power_supply_propval prop = {0,};
+ int rc, current_limit = 0;
+
+ if (!chip->usb_psy)
+ return;
+
+ if (chip->bms_psy_name)
+ chip->bms_psy =
+ power_supply_get_by_name((char *)chip->bms_psy_name);
+
+ rc = power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
+ if (rc < 0)
+ dev_err(chip->dev,
+ "could not read USB current_max property, rc=%d\n", rc);
+ else
+ current_limit = prop.intval / 1000;
+
+ pr_debug("current_limit = %d\n", current_limit);
+
+ if (chip->usb_psy_ma != current_limit) {
+ mutex_lock(&chip->current_change_lock);
+ chip->usb_psy_ma = current_limit;
+ rc = smb135x_set_appropriate_current(chip, USB);
+ mutex_unlock(&chip->current_change_lock);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set usb current rc = %d\n",
+ rc);
+ }
+
+ rc = power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_ONLINE, &prop);
+ if (rc < 0)
+ dev_err(chip->dev,
+ "could not read USB ONLINE property, rc=%d\n", rc);
+
+ /* update online property */
+ rc = 0;
+ if (chip->usb_present && chip->chg_enabled && chip->usb_psy_ma != 0) {
+ if (prop.intval == 0) {
+ prop.intval = 1;
+ rc = power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_ONLINE, &prop);
+ }
+ } else {
+ if (prop.intval == 1) {
+ prop.intval = 0;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_ONLINE, &prop);
+ }
+ }
+ if (rc < 0)
+ dev_err(chip->dev, "could not set usb online, rc=%d\n", rc);
+}
+
+static bool elapsed_msec_greater(struct timeval *start_time,
+ struct timeval *end_time, int ms)
+{
+ int msec_elapsed;
+
+ msec_elapsed = (end_time->tv_sec - start_time->tv_sec) * 1000 +
+ DIV_ROUND_UP(end_time->tv_usec - start_time->tv_usec, 1000);
+
+ return (msec_elapsed > ms);
+}
+
+#define MAX_STEP_MS 10
+static int smb135x_chg_otg_enable(struct smb135x_chg *chip)
+{
+ int rc = 0;
+ int restart_count = 0;
+ struct timeval time_a, time_b, time_c, time_d;
+ u8 reg;
+
+ if (chip->revision == REV_2) {
+ /*
+ * Workaround for a hardware bug where the OTG needs to be
+ * enabled disabled and enabled for it to be actually enabled.
+ * The time between each step should be atmost MAX_STEP_MS
+ *
+ * Note that if enable-disable executes within the timeframe
+ * but the final enable takes more than MAX_STEP_ME, we treat
+ * it as the first enable and try disabling again. We don't
+ * want to issue enable back to back.
+ *
+ * Notice the instances when time is captured and the
+ * successive steps.
+ * timeA-enable-timeC-disable-timeB-enable-timeD.
+ * When
+ * (timeB - timeA) < MAX_STEP_MS AND
+ * (timeC - timeD) < MAX_STEP_MS
+ * then it is guaranteed that the successive steps
+ * must have executed within MAX_STEP_MS
+ */
+ do_gettimeofday(&time_a);
+restart_from_enable:
+ /* first step - enable otg */
+ rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
+ rc);
+ return rc;
+ }
+
+restart_from_disable:
+ /* second step - disable otg */
+ do_gettimeofday(&time_c);
+ rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
+ rc);
+ return rc;
+ }
+ do_gettimeofday(&time_b);
+
+ if (elapsed_msec_greater(&time_a, &time_b, MAX_STEP_MS)) {
+ restart_count++;
+ if (restart_count > 10) {
+ dev_err(chip->dev,
+ "Couldn't enable OTG restart_count=%d\n",
+ restart_count);
+ return -EAGAIN;
+ }
+ time_a = time_b;
+ pr_debug("restarting from first enable\n");
+ goto restart_from_enable;
+ }
+
+ /* third step (first step in case of a failure) - enable otg */
+ time_a = time_b;
+ rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
+ rc);
+ return rc;
+ }
+ do_gettimeofday(&time_d);
+
+ if (elapsed_msec_greater(&time_c, &time_d, MAX_STEP_MS)) {
+ restart_count++;
+ if (restart_count > 10) {
+ dev_err(chip->dev,
+ "Couldn't enable OTG restart_count=%d\n",
+ restart_count);
+ return -EAGAIN;
+ }
+ pr_debug("restarting from disable\n");
+ goto restart_from_disable;
+ }
+ } else {
+ rc = smb135x_read(chip, CMD_CHG_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read cmd reg rc=%d\n",
+ rc);
+ return rc;
+ }
+ if (reg & OTG_EN) {
+ /* if it is set, disable it before re-enabling it */
+ rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int smb135x_chg_otg_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct smb135x_chg *chip = rdev_get_drvdata(rdev);
+
+ chip->otg_oc_count = 0;
+ rc = smb135x_chg_otg_enable(chip);
+ if (rc)
+ dev_err(chip->dev, "Couldn't enable otg regulator rc=%d\n", rc);
+
+ return rc;
+}
+
+static int smb135x_chg_otg_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct smb135x_chg *chip = rdev_get_drvdata(rdev);
+
+ mutex_lock(&chip->otg_oc_count_lock);
+ cancel_delayed_work_sync(&chip->reset_otg_oc_count_work);
+ mutex_unlock(&chip->otg_oc_count_lock);
+ rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", rc);
+ return rc;
+}
+
+static int smb135x_chg_otg_regulator_is_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ u8 reg = 0;
+ struct smb135x_chg *chip = rdev_get_drvdata(rdev);
+
+ rc = smb135x_read(chip, CMD_CHG_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't read OTG enable bit rc=%d\n", rc);
+ return rc;
+ }
+
+ return (reg & OTG_EN) ? 1 : 0;
+}
+
+struct regulator_ops smb135x_chg_otg_reg_ops = {
+ .enable = smb135x_chg_otg_regulator_enable,
+ .disable = smb135x_chg_otg_regulator_disable,
+ .is_enabled = smb135x_chg_otg_regulator_is_enable,
+};
+
+static int smb135x_set_current_tables(struct smb135x_chg *chip)
+{
+ switch (chip->version) {
+ case V_SMB1356:
+ chip->usb_current_table = usb_current_table_smb1356;
+ chip->usb_current_arr_size
+ = ARRAY_SIZE(usb_current_table_smb1356);
+ chip->dc_current_table = dc_current_table_smb1356;
+ chip->dc_current_arr_size
+ = ARRAY_SIZE(dc_current_table_smb1356);
+ chip->fastchg_current_table = NULL;
+ chip->fastchg_current_arr_size = 0;
+ break;
+ case V_SMB1357:
+ chip->usb_current_table = usb_current_table_smb1357_smb1358;
+ chip->usb_current_arr_size
+ = ARRAY_SIZE(usb_current_table_smb1357_smb1358);
+ chip->dc_current_table = dc_current_table;
+ chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table);
+ chip->fastchg_current_table = fastchg_current_table;
+ chip->fastchg_current_arr_size
+ = ARRAY_SIZE(fastchg_current_table);
+ break;
+ case V_SMB1358:
+ chip->usb_current_table = usb_current_table_smb1357_smb1358;
+ chip->usb_current_arr_size
+ = ARRAY_SIZE(usb_current_table_smb1357_smb1358);
+ chip->dc_current_table = dc_current_table;
+ chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table);
+ chip->fastchg_current_table = fastchg_current_table;
+ chip->fastchg_current_arr_size
+ = ARRAY_SIZE(fastchg_current_table);
+ break;
+ case V_SMB1359:
+ chip->usb_current_table = usb_current_table_smb1359;
+ chip->usb_current_arr_size
+ = ARRAY_SIZE(usb_current_table_smb1359);
+ chip->dc_current_table = dc_current_table;
+ chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table);
+ chip->fastchg_current_table = NULL;
+ chip->fastchg_current_arr_size = 0;
+ break;
+ }
+ return 0;
+}
+
+#define SMB1356_VERSION3_BIT BIT(7)
+#define SMB1357_VERSION1_VAL 0x01
+#define SMB1358_VERSION1_VAL 0x02
+#define SMB1359_VERSION1_VAL 0x00
+#define SMB1357_VERSION2_VAL 0x01
+#define SMB1358_VERSION2_VAL 0x02
+#define SMB1359_VERSION2_VAL 0x00
+static int smb135x_chip_version_and_revision(struct smb135x_chg *chip)
+{
+ int rc;
+ u8 version1, version2, version3;
+
+ /* read the revision */
+ rc = read_revision(chip, &chip->revision);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read revision rc = %d\n", rc);
+ return rc;
+ }
+
+ if (chip->revision >= REV_MAX || revision_str[chip->revision] == NULL) {
+ dev_err(chip->dev, "Bad revision found = %d\n", chip->revision);
+ return -EINVAL;
+ }
+
+ /* check if it is smb1356 */
+ rc = read_version3(chip, &version3);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read version3 rc = %d\n", rc);
+ return rc;
+ }
+
+ if (version3 & SMB1356_VERSION3_BIT) {
+ chip->version = V_SMB1356;
+ goto wrkarnd_and_input_current_values;
+ }
+
+ /* check if it is smb1357, smb1358 or smb1359 based on revision */
+ if (chip->revision <= REV_1_1) {
+ rc = read_version1(chip, &version1);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't read version 1 rc = %d\n", rc);
+ return rc;
+ }
+ switch (version1) {
+ case SMB1357_VERSION1_VAL:
+ chip->version = V_SMB1357;
+ break;
+ case SMB1358_VERSION1_VAL:
+ chip->version = V_SMB1358;
+ break;
+ case SMB1359_VERSION1_VAL:
+ chip->version = V_SMB1359;
+ break;
+ default:
+ dev_err(chip->dev,
+ "Unknown version 1 = 0x%02x rc = %d\n",
+ version1, rc);
+ return rc;
+ }
+ } else {
+ rc = read_version2(chip, &version2);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't read version 2 rc = %d\n", rc);
+ return rc;
+ }
+ switch (version2) {
+ case SMB1357_VERSION2_VAL:
+ chip->version = V_SMB1357;
+ break;
+ case SMB1358_VERSION2_VAL:
+ chip->version = V_SMB1358;
+ break;
+ case SMB1359_VERSION2_VAL:
+ chip->version = V_SMB1359;
+ break;
+ default:
+ dev_err(chip->dev,
+ "Unknown version 2 = 0x%02x rc = %d\n",
+ version2, rc);
+ return rc;
+ }
+ }
+
+wrkarnd_and_input_current_values:
+ if (is_usb100_broken(chip))
+ chip->workaround_flags |= WRKARND_USB100_BIT;
+ /*
+ * Rev v1.0 and v1.1 of SMB135x fails charger type detection
+ * (apsd) due to interference on the D+/- lines by the USB phy.
+ * Set the workaround flag to disable charger type reporting
+ * for this revision.
+ */
+ if (chip->revision <= REV_1_1)
+ chip->workaround_flags |= WRKARND_APSD_FAIL;
+
+ pr_debug("workaround_flags = %x\n", chip->workaround_flags);
+
+ return smb135x_set_current_tables(chip);
+}
+
+static int smb135x_regulator_init(struct smb135x_chg *chip)
+{
+ int rc = 0;
+ struct regulator_config cfg = {};
+
+ chip->otg_vreg.rdesc.owner = THIS_MODULE;
+ chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
+ chip->otg_vreg.rdesc.ops = &smb135x_chg_otg_reg_ops;
+ chip->otg_vreg.rdesc.name = chip->dev->of_node->name;
+ chip->otg_vreg.rdesc.of_match = chip->dev->of_node->name;
+ cfg.dev = chip->dev;
+ cfg.driver_data = chip;
+
+ chip->otg_vreg.rdev = regulator_register(&chip->otg_vreg.rdesc, &cfg);
+ if (IS_ERR(chip->otg_vreg.rdev)) {
+ rc = PTR_ERR(chip->otg_vreg.rdev);
+ chip->otg_vreg.rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ dev_err(chip->dev,
+ "OTG reg failed, rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+static void smb135x_regulator_deinit(struct smb135x_chg *chip)
+{
+ if (chip->otg_vreg.rdev)
+ regulator_unregister(chip->otg_vreg.rdev);
+}
+
+static void wireless_insertion_work(struct work_struct *work)
+{
+ struct smb135x_chg *chip =
+ container_of(work, struct smb135x_chg,
+ wireless_insertion_work.work);
+
+ /* unsuspend dc */
+ smb135x_path_suspend(chip, DC, CURRENT, false);
+}
+
+static int hot_hard_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+ chip->batt_hot = !!rt_stat;
+ return 0;
+}
+static int cold_hard_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+ chip->batt_cold = !!rt_stat;
+ return 0;
+}
+static int hot_soft_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+ chip->batt_warm = !!rt_stat;
+ return 0;
+}
+static int cold_soft_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+ chip->batt_cool = !!rt_stat;
+ return 0;
+}
+static int battery_missing_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+ chip->batt_present = !rt_stat;
+ return 0;
+}
+static int vbat_low_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_warn("vbat low\n");
+ return 0;
+}
+static int chg_hot_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_warn("chg hot\n");
+ return 0;
+}
+static int chg_term_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+
+ /*
+ * This handler gets called even when the charger based termination
+ * is disabled (due to change in RT status). However, in a bms
+ * controlled design the battery status should not be updated.
+ */
+ if (!chip->iterm_disabled)
+ chip->chg_done_batt_full = !!rt_stat;
+ return 0;
+}
+
+static int taper_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+ return 0;
+}
+
+static int fast_chg_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+
+ if (rt_stat & IRQ_C_FASTCHG_BIT)
+ chip->chg_done_batt_full = false;
+
+ return 0;
+}
+
+static int recharge_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ int rc;
+
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+
+ if (chip->bms_controlled_charging) {
+ rc = smb135x_charging_enable(chip, true);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't enable charging rc = %d\n",
+ rc);
+ }
+
+ return 0;
+}
+
+static int safety_timeout_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_warn("safety timeout rt_stat = 0x%02x\n", rt_stat);
+ return 0;
+}
+
+/**
+ * power_ok_handler() - called when the switcher turns on or turns off
+ * @chip: pointer to smb135x_chg chip
+ * @rt_stat: the status bit indicating switcher turning on or off
+ */
+static int power_ok_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+ return 0;
+}
+
+static int rid_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ bool usb_slave_present;
+ union power_supply_propval pval = {0, };
+
+ usb_slave_present = is_usb_slave_present(chip);
+
+ if (chip->usb_slave_present ^ usb_slave_present) {
+ chip->usb_slave_present = usb_slave_present;
+ if (chip->usb_psy) {
+ pr_debug("setting usb psy usb_otg = %d\n",
+ chip->usb_slave_present);
+ pval.intval = chip->usb_slave_present;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_USB_OTG, &pval);
+ }
+ }
+ return 0;
+}
+
+#define RESET_OTG_OC_COUNT_MS 100
+static void reset_otg_oc_count_work(struct work_struct *work)
+{
+ struct smb135x_chg *chip =
+ container_of(work, struct smb135x_chg,
+ reset_otg_oc_count_work.work);
+
+ mutex_lock(&chip->otg_oc_count_lock);
+ pr_debug("It has been %dmS since OverCurrent interrupt resetting the count\n",
+ RESET_OTG_OC_COUNT_MS);
+ chip->otg_oc_count = 0;
+ mutex_unlock(&chip->otg_oc_count_lock);
+}
+
+#define MAX_OTG_RETRY 3
+static int otg_oc_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ int rc;
+
+ mutex_lock(&chip->otg_oc_count_lock);
+ cancel_delayed_work_sync(&chip->reset_otg_oc_count_work);
+ ++chip->otg_oc_count;
+ if (chip->otg_oc_count < MAX_OTG_RETRY) {
+ rc = smb135x_chg_otg_enable(chip);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
+ rc);
+ } else {
+ pr_warn_ratelimited("Tried enabling OTG %d times, the USB slave is nonconformant.\n",
+ chip->otg_oc_count);
+ }
+
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+ schedule_delayed_work(&chip->reset_otg_oc_count_work,
+ msecs_to_jiffies(RESET_OTG_OC_COUNT_MS));
+ mutex_unlock(&chip->otg_oc_count_lock);
+ return 0;
+}
+
+static int handle_dc_removal(struct smb135x_chg *chip)
+{
+ union power_supply_propval prop;
+
+ if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) {
+ cancel_delayed_work_sync(&chip->wireless_insertion_work);
+ smb135x_path_suspend(chip, DC, CURRENT, true);
+ }
+ if (chip->dc_psy_type != -EINVAL) {
+ prop.intval = chip->dc_present;
+ power_supply_set_property(chip->dc_psy,
+ POWER_SUPPLY_PROP_ONLINE, &prop);
+ }
+ return 0;
+}
+
+#define DCIN_UNSUSPEND_DELAY_MS 1000
+static int handle_dc_insertion(struct smb135x_chg *chip)
+{
+ union power_supply_propval prop;
+
+ if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS)
+ schedule_delayed_work(&chip->wireless_insertion_work,
+ msecs_to_jiffies(DCIN_UNSUSPEND_DELAY_MS));
+ if (chip->dc_psy_type != -EINVAL) {
+ prop.intval = chip->dc_present;
+ power_supply_set_property(chip->dc_psy,
+ POWER_SUPPLY_PROP_ONLINE, &prop);
+ }
+ return 0;
+}
+/**
+ * dcin_uv_handler() - called when the dc voltage crosses the uv threshold
+ * @chip: pointer to smb135x_chg chip
+ * @rt_stat: the status bit indicating whether dc voltage is uv
+ */
+static int dcin_uv_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ /*
+ * rt_stat indicates if dc is undervolted. If so dc_present
+ * should be marked removed
+ */
+ bool dc_present = !rt_stat;
+
+ pr_debug("chip->dc_present = %d dc_present = %d\n",
+ chip->dc_present, dc_present);
+
+ if (chip->dc_present && !dc_present) {
+ /* dc removed */
+ chip->dc_present = dc_present;
+ handle_dc_removal(chip);
+ }
+
+ if (!chip->dc_present && dc_present) {
+ /* dc inserted */
+ chip->dc_present = dc_present;
+ handle_dc_insertion(chip);
+ }
+
+ return 0;
+}
+
+static int dcin_ov_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ /*
+ * rt_stat indicates if dc is overvolted. If so dc_present
+ * should be marked removed
+ */
+ bool dc_present = !rt_stat;
+
+ pr_debug("chip->dc_present = %d dc_present = %d\n",
+ chip->dc_present, dc_present);
+
+ chip->dc_ov = !!rt_stat;
+
+ if (chip->dc_present && !dc_present) {
+ /* dc removed */
+ chip->dc_present = dc_present;
+ handle_dc_removal(chip);
+ }
+
+ if (!chip->dc_present && dc_present) {
+ /* dc inserted */
+ chip->dc_present = dc_present;
+ handle_dc_insertion(chip);
+ }
+ return 0;
+}
+
+static int handle_usb_removal(struct smb135x_chg *chip)
+{
+ union power_supply_propval pval = {0,};
+
+ if (chip->usb_psy) {
+ cancel_delayed_work_sync(&chip->hvdcp_det_work);
+ pm_relax(chip->dev);
+ pr_debug("setting usb psy type = %d\n",
+ POWER_SUPPLY_TYPE_UNKNOWN);
+ pval.intval = POWER_SUPPLY_TYPE_UNKNOWN;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &pval);
+
+ pr_debug("setting usb psy present = %d\n", chip->usb_present);
+ pval.intval = chip->usb_present;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT,
+ &pval);
+
+ pr_debug("Setting usb psy dp=r dm=r\n");
+ pval.intval = POWER_SUPPLY_DP_DM_DPR_DMR;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_DP_DM,
+ &pval);
+ }
+ return 0;
+}
+
+static int rerun_apsd(struct smb135x_chg *chip)
+{
+ int rc;
+
+ pr_debug("Reruning APSD\nDisabling APSD\n");
+ rc = smb135x_masked_write(chip, CFG_11_REG, AUTO_SRC_DET_EN_BIT, 0);
+ if (rc) {
+ dev_err(chip->dev, "Couldn't Disable APSD rc=%d\n", rc);
+ return rc;
+ }
+ pr_debug("Allow only 9V chargers\n");
+ rc = smb135x_masked_write(chip, CFG_C_REG,
+ USBIN_ADAPTER_ALLOWANCE_MASK, ALLOW_9V_ONLY);
+ if (rc)
+ dev_err(chip->dev, "Couldn't Allow 9V rc=%d\n", rc);
+ pr_debug("Enabling APSD\n");
+ rc = smb135x_masked_write(chip, CFG_11_REG, AUTO_SRC_DET_EN_BIT, 1);
+ if (rc)
+ dev_err(chip->dev, "Couldn't Enable APSD rc=%d\n", rc);
+ pr_debug("Allow 5V-9V\n");
+ rc = smb135x_masked_write(chip, CFG_C_REG,
+ USBIN_ADAPTER_ALLOWANCE_MASK, ALLOW_5V_TO_9V);
+ if (rc)
+ dev_err(chip->dev, "Couldn't Allow 5V-9V rc=%d\n", rc);
+ return rc;
+}
+
+static void smb135x_hvdcp_det_work(struct work_struct *work)
+{
+ int rc;
+ u8 reg;
+ struct smb135x_chg *chip = container_of(work, struct smb135x_chg,
+ hvdcp_det_work.work);
+ union power_supply_propval pval = {0,};
+
+ rc = smb135x_read(chip, STATUS_7_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read STATUS_7_REG rc == %d\n", rc);
+ goto end;
+ }
+ pr_debug("STATUS_7_REG = 0x%02X\n", reg);
+
+ if (reg) {
+ pr_debug("HVDCP detected; notifying USB PSY\n");
+ pval.intval = POWER_SUPPLY_TYPE_USB_HVDCP;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &pval);
+ }
+end:
+ pm_relax(chip->dev);
+}
+
+#define HVDCP_NOTIFY_MS 2500
+static int handle_usb_insertion(struct smb135x_chg *chip)
+{
+ u8 reg;
+ int rc;
+ char *usb_type_name = "null";
+ enum power_supply_type usb_supply_type;
+ union power_supply_propval pval = {0,};
+
+ /* usb inserted */
+ rc = smb135x_read(chip, STATUS_5_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc);
+ return rc;
+ }
+ /*
+ * Report the charger type as UNKNOWN if the
+ * apsd-fail flag is set. This nofifies the USB driver
+ * to initiate a s/w based charger type detection.
+ */
+ if (chip->workaround_flags & WRKARND_APSD_FAIL)
+ reg = 0;
+
+ usb_type_name = get_usb_type_name(reg);
+ usb_supply_type = get_usb_supply_type(reg);
+ pr_debug("inserted %s, usb psy type = %d stat_5 = 0x%02x apsd_rerun = %d\n",
+ usb_type_name, usb_supply_type, reg, chip->apsd_rerun);
+
+ if (chip->batt_present && !chip->apsd_rerun && chip->usb_psy) {
+ if (usb_supply_type == POWER_SUPPLY_TYPE_USB) {
+ pr_debug("Setting usb psy dp=f dm=f SDP and rerun\n");
+ pval.intval = POWER_SUPPLY_DP_DM_DPF_DMF;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_DP_DM,
+ &pval);
+ chip->apsd_rerun = true;
+ rerun_apsd(chip);
+ /* rising edge of src detect will happen in few mS */
+ return 0;
+ } else {
+ pr_debug("Set usb psy dp=f dm=f DCP and no rerun\n");
+ pval.intval = POWER_SUPPLY_DP_DM_DPF_DMF;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_DP_DM,
+ &pval);
+ }
+ }
+
+ if (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP) {
+ pr_debug("schedule hvdcp detection worker\n");
+ pm_stay_awake(chip->dev);
+ schedule_delayed_work(&chip->hvdcp_det_work,
+ msecs_to_jiffies(HVDCP_NOTIFY_MS));
+ }
+
+ if (chip->usb_psy) {
+ if (chip->bms_controlled_charging) {
+ /* enable charging on USB insertion */
+ rc = smb135x_charging_enable(chip, true);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't enable charging rc = %d\n",
+ rc);
+ }
+ pr_debug("setting usb psy type = %d\n", usb_supply_type);
+ pval.intval = usb_supply_type;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &pval);
+
+ pr_debug("setting usb psy present = %d\n", chip->usb_present);
+ pval.intval = chip->usb_present;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT,
+ &pval);
+ }
+ chip->apsd_rerun = false;
+ return 0;
+}
+
+/**
+ * usbin_uv_handler()
+ * @chip: pointer to smb135x_chg chip
+ * @rt_stat: the status bit indicating chg insertion/removal
+ */
+static int usbin_uv_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ /*
+ * rt_stat indicates if usb is undervolted
+ */
+ bool usb_present = !rt_stat;
+
+ pr_debug("chip->usb_present = %d usb_present = %d\n",
+ chip->usb_present, usb_present);
+
+ return 0;
+}
+
+static int usbin_ov_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ union power_supply_propval pval = {0, };
+ /*
+ * rt_stat indicates if usb is overvolted. If so usb_present
+ * should be marked removed
+ */
+ bool usb_present = !rt_stat;
+
+ pr_debug("chip->usb_present = %d usb_present = %d\n",
+ chip->usb_present, usb_present);
+ if (chip->usb_present && !usb_present) {
+ /* USB removed */
+ chip->usb_present = usb_present;
+ handle_usb_removal(chip);
+ } else if (!chip->usb_present && usb_present) {
+ /* USB inserted */
+ chip->usb_present = usb_present;
+ handle_usb_insertion(chip);
+ }
+
+ if (chip->usb_psy) {
+ pval.intval = rt_stat ? POWER_SUPPLY_HEALTH_OVERVOLTAGE
+ : POWER_SUPPLY_HEALTH_GOOD;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_HEALTH, &pval);
+ }
+
+ return 0;
+}
+
+/**
+ * src_detect_handler() - this is called on rising edge when USB
+ * charger type is detected and on falling edge when
+ * USB voltage falls below the coarse detect voltage
+ * (1V), use it for handling USB charger insertion
+ * and removal.
+ * @chip: pointer to smb135x_chg chip
+ * @rt_stat: the status bit indicating chg insertion/removal
+ */
+static int src_detect_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ bool usb_present = !!rt_stat;
+
+ pr_debug("chip->usb_present = %d usb_present = %d\n",
+ chip->usb_present, usb_present);
+
+ if (!chip->usb_present && usb_present) {
+ /* USB inserted */
+ chip->usb_present = usb_present;
+ handle_usb_insertion(chip);
+ } else if (usb_present && chip->apsd_rerun) {
+ handle_usb_insertion(chip);
+ } else if (chip->usb_present && !usb_present) {
+ chip->usb_present = !chip->usb_present;
+ handle_usb_removal(chip);
+ }
+
+ return 0;
+}
+
+static int chg_inhibit_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+ /*
+ * charger is inserted when the battery voltage is high
+ * so h/w won't start charging just yet. Treat this as
+ * battery full
+ */
+ pr_debug("rt_stat = 0x%02x\n", rt_stat);
+
+ if (!chip->inhibit_disabled)
+ chip->chg_done_batt_full = !!rt_stat;
+ return 0;
+}
+
+struct smb_irq_info {
+ const char *name;
+ int (*smb_irq)(struct smb135x_chg *chip,
+ u8 rt_stat);
+ int high;
+ int low;
+};
+
+struct irq_handler_info {
+ u8 stat_reg;
+ u8 val;
+ u8 prev_val;
+ struct smb_irq_info irq_info[4];
+};
+
+static struct irq_handler_info handlers[] = {
+ {IRQ_A_REG, 0, 0,
+ {
+ {
+ .name = "cold_soft",
+ .smb_irq = cold_soft_handler,
+ },
+ {
+ .name = "hot_soft",
+ .smb_irq = hot_soft_handler,
+ },
+ {
+ .name = "cold_hard",
+ .smb_irq = cold_hard_handler,
+ },
+ {
+ .name = "hot_hard",
+ .smb_irq = hot_hard_handler,
+ },
+ },
+ },
+ {IRQ_B_REG, 0, 0,
+ {
+ {
+ .name = "chg_hot",
+ .smb_irq = chg_hot_handler,
+ },
+ {
+ .name = "vbat_low",
+ .smb_irq = vbat_low_handler,
+ },
+ {
+ .name = "battery_missing",
+ .smb_irq = battery_missing_handler,
+ },
+ {
+ .name = "battery_missing",
+ .smb_irq = battery_missing_handler,
+ },
+ },
+ },
+ {IRQ_C_REG, 0, 0,
+ {
+ {
+ .name = "chg_term",
+ .smb_irq = chg_term_handler,
+ },
+ {
+ .name = "taper",
+ .smb_irq = taper_handler,
+ },
+ {
+ .name = "recharge",
+ .smb_irq = recharge_handler,
+ },
+ {
+ .name = "fast_chg",
+ .smb_irq = fast_chg_handler,
+ },
+ },
+ },
+ {IRQ_D_REG, 0, 0,
+ {
+ {
+ .name = "prechg_timeout",
+ },
+ {
+ .name = "safety_timeout",
+ .smb_irq = safety_timeout_handler,
+ },
+ {
+ .name = "aicl_done",
+ },
+ {
+ .name = "battery_ov",
+ },
+ },
+ },
+ {IRQ_E_REG, 0, 0,
+ {
+ {
+ .name = "usbin_uv",
+ .smb_irq = usbin_uv_handler,
+ },
+ {
+ .name = "usbin_ov",
+ .smb_irq = usbin_ov_handler,
+ },
+ {
+ .name = "dcin_uv",
+ .smb_irq = dcin_uv_handler,
+ },
+ {
+ .name = "dcin_ov",
+ .smb_irq = dcin_ov_handler,
+ },
+ },
+ },
+ {IRQ_F_REG, 0, 0,
+ {
+ {
+ .name = "power_ok",
+ .smb_irq = power_ok_handler,
+ },
+ {
+ .name = "rid",
+ .smb_irq = rid_handler,
+ },
+ {
+ .name = "otg_fail",
+ },
+ {
+ .name = "otg_oc",
+ .smb_irq = otg_oc_handler,
+ },
+ },
+ },
+ {IRQ_G_REG, 0, 0,
+ {
+ {
+ .name = "chg_inhibit",
+ .smb_irq = chg_inhibit_handler,
+ },
+ {
+ .name = "chg_error",
+ },
+ {
+ .name = "wd_timeout",
+ },
+ {
+ .name = "src_detect",
+ .smb_irq = src_detect_handler,
+ },
+ },
+ },
+};
+
+static int smb135x_irq_read(struct smb135x_chg *chip)
+{
+ int rc, i;
+
+ /*
+ * When dcin path is suspended the irq triggered status is not cleared
+ * causing a storm. To prevent this situation unsuspend dcin path while
+ * reading interrupts and restore its status back.
+ */
+ mutex_lock(&chip->path_suspend_lock);
+
+ if (chip->dc_suspended)
+ __smb135x_dc_suspend(chip, false);
+
+ for (i = 0; i < ARRAY_SIZE(handlers); i++) {
+ rc = smb135x_read(chip, handlers[i].stat_reg,
+ &handlers[i].val);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read %d rc = %d\n",
+ handlers[i].stat_reg, rc);
+ handlers[i].val = 0;
+ continue;
+ }
+ }
+
+ if (chip->dc_suspended)
+ __smb135x_dc_suspend(chip, true);
+
+ mutex_unlock(&chip->path_suspend_lock);
+
+ return rc;
+}
+#define IRQ_LATCHED_MASK 0x02
+#define IRQ_STATUS_MASK 0x01
+#define BITS_PER_IRQ 2
+static irqreturn_t smb135x_chg_stat_handler(int irq, void *dev_id)
+{
+ struct smb135x_chg *chip = dev_id;
+ int i, j;
+ u8 triggered;
+ u8 changed;
+ u8 rt_stat, prev_rt_stat;
+ int rc;
+ int handler_count = 0;
+
+ mutex_lock(&chip->irq_complete);
+ chip->irq_waiting = true;
+ if (!chip->resume_completed) {
+ dev_dbg(chip->dev, "IRQ triggered before device-resume\n");
+ disable_irq_nosync(irq);
+ mutex_unlock(&chip->irq_complete);
+ return IRQ_HANDLED;
+ }
+ chip->irq_waiting = false;
+
+ smb135x_irq_read(chip);
+ for (i = 0; i < ARRAY_SIZE(handlers); i++) {
+ for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) {
+ triggered = handlers[i].val
+ & (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ));
+ rt_stat = handlers[i].val
+ & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
+ prev_rt_stat = handlers[i].prev_val
+ & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
+ changed = prev_rt_stat ^ rt_stat;
+
+ if (triggered || changed)
+ rt_stat ? handlers[i].irq_info[j].high++ :
+ handlers[i].irq_info[j].low++;
+
+ if ((triggered || changed)
+ && handlers[i].irq_info[j].smb_irq != NULL) {
+ handler_count++;
+ rc = handlers[i].irq_info[j].smb_irq(chip,
+ rt_stat);
+ if (rc < 0)
+ dev_err(chip->dev,
+ "Couldn't handle %d irq for reg 0x%02x rc = %d\n",
+ j, handlers[i].stat_reg, rc);
+ }
+ }
+ handlers[i].prev_val = handlers[i].val;
+ }
+
+ pr_debug("handler count = %d\n", handler_count);
+ if (handler_count) {
+ pr_debug("batt psy changed\n");
+ power_supply_changed(chip->batt_psy);
+ if (chip->usb_psy) {
+ pr_debug("usb psy changed\n");
+ power_supply_changed(chip->usb_psy);
+ }
+ if (chip->dc_psy_type != -EINVAL) {
+ pr_debug("dc psy changed\n");
+ power_supply_changed(chip->dc_psy);
+ }
+ }
+
+ mutex_unlock(&chip->irq_complete);
+
+ return IRQ_HANDLED;
+}
+
+#define LAST_CNFG_REG 0x1F
+static int show_cnfg_regs(struct seq_file *m, void *data)
+{
+ struct smb135x_chg *chip = m->private;
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
+ rc = smb135x_read(chip, addr, &reg);
+ if (!rc)
+ seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ return 0;
+}
+
+static int cnfg_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb135x_chg *chip = inode->i_private;
+
+ return single_open(file, show_cnfg_regs, chip);
+}
+
+static const struct file_operations cnfg_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = cnfg_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#define FIRST_CMD_REG 0x40
+#define LAST_CMD_REG 0x42
+static int show_cmd_regs(struct seq_file *m, void *data)
+{
+ struct smb135x_chg *chip = m->private;
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
+ rc = smb135x_read(chip, addr, &reg);
+ if (!rc)
+ seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ return 0;
+}
+
+static int cmd_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb135x_chg *chip = inode->i_private;
+
+ return single_open(file, show_cmd_regs, chip);
+}
+
+static const struct file_operations cmd_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = cmd_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#define FIRST_STATUS_REG 0x46
+#define LAST_STATUS_REG 0x56
+static int show_status_regs(struct seq_file *m, void *data)
+{
+ struct smb135x_chg *chip = m->private;
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
+ rc = smb135x_read(chip, addr, &reg);
+ if (!rc)
+ seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ return 0;
+}
+
+static int status_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb135x_chg *chip = inode->i_private;
+
+ return single_open(file, show_status_regs, chip);
+}
+
+static const struct file_operations status_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = status_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int show_irq_count(struct seq_file *m, void *data)
+{
+ int i, j, total = 0;
+
+ for (i = 0; i < ARRAY_SIZE(handlers); i++)
+ for (j = 0; j < 4; j++) {
+ seq_printf(m, "%s=%d\t(high=%d low=%d)\n",
+ handlers[i].irq_info[j].name,
+ handlers[i].irq_info[j].high
+ + handlers[i].irq_info[j].low,
+ handlers[i].irq_info[j].high,
+ handlers[i].irq_info[j].low);
+ total += (handlers[i].irq_info[j].high
+ + handlers[i].irq_info[j].low);
+ }
+
+ seq_printf(m, "\n\tTotal = %d\n", total);
+
+ return 0;
+}
+
+static int irq_count_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb135x_chg *chip = inode->i_private;
+
+ return single_open(file, show_irq_count, chip);
+}
+
+static const struct file_operations irq_count_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = irq_count_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int get_reg(void *data, u64 *val)
+{
+ struct smb135x_chg *chip = data;
+ int rc;
+ u8 temp;
+
+ rc = smb135x_read(chip, chip->peek_poke_address, &temp);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't read reg %x rc = %d\n",
+ chip->peek_poke_address, rc);
+ return -EAGAIN;
+ }
+ *val = temp;
+ return 0;
+}
+
+static int set_reg(void *data, u64 val)
+{
+ struct smb135x_chg *chip = data;
+ int rc;
+ u8 temp;
+
+ temp = (u8) val;
+ rc = smb135x_write(chip, chip->peek_poke_address, temp);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't write 0x%02x to 0x%02x rc= %d\n",
+ chip->peek_poke_address, temp, rc);
+ return -EAGAIN;
+ }
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n");
+
+static int force_irq_set(void *data, u64 val)
+{
+ struct smb135x_chg *chip = data;
+
+ smb135x_chg_stat_handler(chip->client->irq, data);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n");
+
+static int force_rechg_set(void *data, u64 val)
+{
+ int rc = 0;
+ struct smb135x_chg *chip = data;
+
+ if (!chip->chg_enabled) {
+ pr_debug("Charging Disabled force recharge not allowed\n");
+ return -EINVAL;
+ }
+
+ if (!chip->inhibit_disabled) {
+ rc = smb135x_masked_write(chip, CFG_14_REG, EN_CHG_INHIBIT_BIT,
+ 0);
+ if (rc)
+ dev_err(chip->dev,
+ "Couldn't disable charge-inhibit rc=%d\n", rc);
+
+ /* delay for charge-inhibit to take affect */
+ msleep(500);
+ }
+
+ rc |= smb135x_charging(chip, false);
+ rc |= smb135x_charging(chip, true);
+
+ if (!chip->inhibit_disabled) {
+ rc |= smb135x_masked_write(chip, CFG_14_REG,
+ EN_CHG_INHIBIT_BIT, EN_CHG_INHIBIT_BIT);
+ if (rc)
+ dev_err(chip->dev,
+ "Couldn't enable charge-inhibit rc=%d\n", rc);
+ }
+
+ return rc;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_rechg_ops, NULL, force_rechg_set, "0x%02llx\n");
+
+#ifdef DEBUG
+static void dump_regs(struct smb135x_chg *chip)
+{
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
+ rc = smb135x_read(chip, addr, &reg);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
+ addr, rc);
+ else
+ pr_debug("0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
+ rc = smb135x_read(chip, addr, &reg);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
+ addr, rc);
+ else
+ pr_debug("0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
+ rc = smb135x_read(chip, addr, &reg);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
+ addr, rc);
+ else
+ pr_debug("0x%02x = 0x%02x\n", addr, reg);
+ }
+}
+#else
+static void dump_regs(struct smb135x_chg *chip)
+{
+}
+#endif
+static int determine_initial_status(struct smb135x_chg *chip)
+{
+ union power_supply_propval pval = {0, };
+ int rc;
+ u8 reg;
+
+ /*
+ * It is okay to read the interrupt status here since
+ * interrupts aren't requested. reading interrupt status
+ * clears the interrupt so be careful to read interrupt
+ * status only in interrupt handling code
+ */
+
+ chip->batt_present = true;
+ rc = smb135x_read(chip, IRQ_B_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read irq b rc = %d\n", rc);
+ return rc;
+ }
+ if (reg & IRQ_B_BATT_TERMINAL_BIT || reg & IRQ_B_BATT_MISSING_BIT)
+ chip->batt_present = false;
+ rc = smb135x_read(chip, STATUS_4_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read status 4 rc = %d\n", rc);
+ return rc;
+ }
+ /* treat battery gone if less than 2V */
+ if (reg & BATT_LESS_THAN_2V)
+ chip->batt_present = false;
+
+ rc = smb135x_read(chip, IRQ_A_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc);
+ return rc;
+ }
+
+ if (reg & IRQ_A_HOT_HARD_BIT)
+ chip->batt_hot = true;
+ if (reg & IRQ_A_COLD_HARD_BIT)
+ chip->batt_cold = true;
+ if (reg & IRQ_A_HOT_SOFT_BIT)
+ chip->batt_warm = true;
+ if (reg & IRQ_A_COLD_SOFT_BIT)
+ chip->batt_cool = true;
+
+ rc = smb135x_read(chip, IRQ_C_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc);
+ return rc;
+ }
+ if (reg & IRQ_C_TERM_BIT)
+ chip->chg_done_batt_full = true;
+
+ rc = smb135x_read(chip, IRQ_E_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read irq E rc = %d\n", rc);
+ return rc;
+ }
+ chip->usb_present = !(reg & IRQ_E_USB_OV_BIT)
+ && !(reg & IRQ_E_USB_UV_BIT);
+ chip->dc_present = !(reg & IRQ_E_DC_OV_BIT) && !(reg & IRQ_E_DC_UV_BIT);
+
+ if (chip->usb_present)
+ handle_usb_insertion(chip);
+ else
+ handle_usb_removal(chip);
+
+ if (chip->dc_psy_type != -EINVAL) {
+ if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) {
+ /*
+ * put the dc path in suspend state if it is powered
+ * by wireless charger
+ */
+ if (chip->dc_present)
+ smb135x_path_suspend(chip, DC, CURRENT, false);
+ else
+ smb135x_path_suspend(chip, DC, CURRENT, true);
+ }
+ }
+
+ chip->usb_slave_present = is_usb_slave_present(chip);
+ if (chip->usb_psy && !chip->id_line_not_connected) {
+ pr_debug("setting usb psy usb_otg = %d\n",
+ chip->usb_slave_present);
+ pval.intval = chip->usb_slave_present;
+ power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_USB_OTG, &pval);
+ }
+ return 0;
+}
+
+static int smb135x_hw_init(struct smb135x_chg *chip)
+{
+ int rc;
+ int i;
+ u8 reg, mask;
+
+ if (chip->pinctrl_state_name) {
+ chip->smb_pinctrl = pinctrl_get_select(chip->dev,
+ chip->pinctrl_state_name);
+ if (IS_ERR(chip->smb_pinctrl)) {
+ pr_err("Could not get/set %s pinctrl state rc = %ld\n",
+ chip->pinctrl_state_name,
+ PTR_ERR(chip->smb_pinctrl));
+ return PTR_ERR(chip->smb_pinctrl);
+ }
+ }
+
+ if (chip->therm_bias_vreg) {
+ rc = regulator_enable(chip->therm_bias_vreg);
+ if (rc) {
+ pr_err("Couldn't enable therm-bias rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ /*
+ * Enable USB data line pullup regulator this is needed for the D+
+ * line to be at proper voltage for HVDCP charger detection.
+ */
+ if (chip->usb_pullup_vreg) {
+ rc = regulator_enable(chip->usb_pullup_vreg);
+ if (rc) {
+ pr_err("Unable to enable data line pull-up regulator rc=%d\n",
+ rc);
+ if (chip->therm_bias_vreg)
+ regulator_disable(chip->therm_bias_vreg);
+ return rc;
+ }
+ }
+
+ rc = smb135x_enable_volatile_writes(chip);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't configure for volatile rc = %d\n",
+ rc);
+ goto free_regulator;
+ }
+
+ /*
+ * force using current from the register i.e. ignore auto
+ * power source detect (APSD) mA ratings
+ */
+ mask = USE_REGISTER_FOR_CURRENT;
+
+ if (chip->workaround_flags & WRKARND_USB100_BIT)
+ reg = 0;
+ else
+ /* this ignores APSD results */
+ reg = USE_REGISTER_FOR_CURRENT;
+
+ rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, mask, reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc);
+ goto free_regulator;
+ }
+
+ /* set bit 0 = 100mA bit 1 = 500mA and set register control */
+ rc = smb135x_masked_write(chip, CFG_E_REG,
+ POLARITY_100_500_BIT | USB_CTRL_BY_PIN_BIT,
+ POLARITY_100_500_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set usbin cfg rc=%d\n", rc);
+ goto free_regulator;
+ }
+
+ /*
+ * set chg en by cmd register, set chg en by writing bit 1,
+ * enable auto pre to fast, enable current termination, enable
+ * auto recharge, enable chg inhibition based on the dt flag
+ */
+ if (chip->inhibit_disabled)
+ reg = 0;
+ else
+ reg = EN_CHG_INHIBIT_BIT;
+
+ rc = smb135x_masked_write(chip, CFG_14_REG,
+ CHG_EN_BY_PIN_BIT | CHG_EN_ACTIVE_LOW_BIT
+ | PRE_TO_FAST_REQ_CMD_BIT | DISABLE_AUTO_RECHARGE_BIT
+ | EN_CHG_INHIBIT_BIT, reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set cfg 14 rc=%d\n", rc);
+ goto free_regulator;
+ }
+
+ /* control USB suspend via command bits */
+ rc = smb135x_masked_write(chip, USBIN_DCIN_CFG_REG,
+ USBIN_SUSPEND_VIA_COMMAND_BIT, USBIN_SUSPEND_VIA_COMMAND_BIT);
+
+ /* set the float voltage */
+ if (chip->vfloat_mv != -EINVAL) {
+ rc = smb135x_float_voltage_set(chip, chip->vfloat_mv);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set float voltage rc = %d\n", rc);
+ goto free_regulator;
+ }
+ }
+
+ /* set iterm */
+ if (chip->iterm_ma != -EINVAL) {
+ if (chip->iterm_disabled) {
+ dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n");
+ rc = -EINVAL;
+ goto free_regulator;
+ } else {
+ if (chip->iterm_ma <= 50)
+ reg = CHG_ITERM_50MA;
+ else if (chip->iterm_ma <= 100)
+ reg = CHG_ITERM_100MA;
+ else if (chip->iterm_ma <= 150)
+ reg = CHG_ITERM_150MA;
+ else if (chip->iterm_ma <= 200)
+ reg = CHG_ITERM_200MA;
+ else if (chip->iterm_ma <= 250)
+ reg = CHG_ITERM_250MA;
+ else if (chip->iterm_ma <= 300)
+ reg = CHG_ITERM_300MA;
+ else if (chip->iterm_ma <= 500)
+ reg = CHG_ITERM_500MA;
+ else
+ reg = CHG_ITERM_600MA;
+
+ rc = smb135x_masked_write(chip, CFG_3_REG,
+ CHG_ITERM_MASK, reg);
+ if (rc) {
+ dev_err(chip->dev,
+ "Couldn't set iterm rc = %d\n", rc);
+ goto free_regulator;
+ }
+
+ rc = smb135x_masked_write(chip, CFG_14_REG,
+ DISABLE_CURRENT_TERM_BIT, 0);
+ if (rc) {
+ dev_err(chip->dev,
+ "Couldn't enable iterm rc = %d\n", rc);
+ goto free_regulator;
+ }
+ }
+ } else if (chip->iterm_disabled) {
+ rc = smb135x_masked_write(chip, CFG_14_REG,
+ DISABLE_CURRENT_TERM_BIT,
+ DISABLE_CURRENT_TERM_BIT);
+ if (rc) {
+ dev_err(chip->dev, "Couldn't set iterm rc = %d\n",
+ rc);
+ goto free_regulator;
+ }
+ }
+
+ /* set the safety time voltage */
+ if (chip->safety_time != -EINVAL) {
+ if (chip->safety_time == 0) {
+ /* safety timer disabled */
+ reg = 1 << SAFETY_TIME_EN_SHIFT;
+ rc = smb135x_masked_write(chip, CFG_16_REG,
+ SAFETY_TIME_EN_BIT, reg);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't disable safety timer rc = %d\n",
+ rc);
+ goto free_regulator;
+ }
+ } else {
+ for (i = 0; i < ARRAY_SIZE(chg_time); i++) {
+ if (chip->safety_time <= chg_time[i]) {
+ reg = i << SAFETY_TIME_MINUTES_SHIFT;
+ break;
+ }
+ }
+ rc = smb135x_masked_write(chip, CFG_16_REG,
+ SAFETY_TIME_EN_BIT | SAFETY_TIME_MINUTES_MASK,
+ reg);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set safety timer rc = %d\n",
+ rc);
+ goto free_regulator;
+ }
+ }
+ }
+
+ /* battery missing detection */
+ rc = smb135x_masked_write(chip, CFG_19_REG,
+ BATT_MISSING_ALGO_BIT | BATT_MISSING_THERM_BIT,
+ chip->bmd_algo_disabled ? BATT_MISSING_THERM_BIT :
+ BATT_MISSING_ALGO_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set batt_missing config = %d\n",
+ rc);
+ goto free_regulator;
+ }
+
+ /* set maximum fastchg current */
+ if (chip->fastchg_ma != -EINVAL) {
+ rc = smb135x_set_fastchg_current(chip, chip->fastchg_ma);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set fastchg current = %d\n",
+ rc);
+ goto free_regulator;
+ }
+ }
+
+ if (chip->usb_pullup_vreg) {
+ /* enable 9V HVDCP adapter support */
+ rc = smb135x_masked_write(chip, CFG_E_REG, HVDCP_5_9_BIT,
+ HVDCP_5_9_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't request for 5 or 9V rc=%d\n", rc);
+ goto free_regulator;
+ }
+ }
+
+ if (chip->gamma_setting) {
+ rc = smb135x_masked_write(chip, CFG_1B_REG, COLD_HARD_MASK,
+ chip->gamma_setting[0] << COLD_HARD_SHIFT);
+
+ rc |= smb135x_masked_write(chip, CFG_1B_REG, HOT_HARD_MASK,
+ chip->gamma_setting[1] << HOT_HARD_SHIFT);
+
+ rc |= smb135x_masked_write(chip, CFG_1B_REG, COLD_SOFT_MASK,
+ chip->gamma_setting[2] << COLD_SOFT_SHIFT);
+
+ rc |= smb135x_masked_write(chip, CFG_1B_REG, HOT_SOFT_MASK,
+ chip->gamma_setting[3] << HOT_SOFT_SHIFT);
+ if (rc < 0)
+ goto free_regulator;
+ }
+
+ __smb135x_charging(chip, chip->chg_enabled);
+
+ /* interrupt enabling - active low */
+ if (chip->client->irq) {
+ mask = CHG_STAT_IRQ_ONLY_BIT | CHG_STAT_ACTIVE_HIGH_BIT
+ | CHG_STAT_DISABLE_BIT;
+ reg = CHG_STAT_IRQ_ONLY_BIT;
+ rc = smb135x_masked_write(chip, CFG_17_REG, mask, reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set irq config rc = %d\n",
+ rc);
+ goto free_regulator;
+ }
+
+ /* enabling only interesting interrupts */
+ rc = smb135x_write(chip, IRQ_CFG_REG,
+ IRQ_BAT_HOT_COLD_HARD_BIT
+ | IRQ_BAT_HOT_COLD_SOFT_BIT
+ | IRQ_OTG_OVER_CURRENT_BIT
+ | IRQ_INTERNAL_TEMPERATURE_BIT
+ | IRQ_USBIN_UV_BIT);
+
+ rc |= smb135x_write(chip, IRQ2_CFG_REG,
+ IRQ2_SAFETY_TIMER_BIT
+ | IRQ2_CHG_ERR_BIT
+ | IRQ2_CHG_PHASE_CHANGE_BIT
+ | IRQ2_POWER_OK_BIT
+ | IRQ2_BATT_MISSING_BIT
+ | IRQ2_VBAT_LOW_BIT);
+
+ rc |= smb135x_write(chip, IRQ3_CFG_REG, IRQ3_SRC_DETECT_BIT
+ | IRQ3_DCIN_UV_BIT | IRQ3_RID_DETECT_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set irq enable rc = %d\n",
+ rc);
+ goto free_regulator;
+ }
+ }
+
+ /* resume threshold */
+ if (chip->resume_delta_mv != -EINVAL) {
+ smb135x_set_resume_threshold(chip, chip->resume_delta_mv);
+ }
+
+ /* DC path current settings */
+ if (chip->dc_psy_type != -EINVAL) {
+ rc = smb135x_set_dc_chg_current(chip, chip->dc_psy_ma);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set dc charge current rc = %d\n",
+ rc);
+ goto free_regulator;
+ }
+ }
+
+ /*
+ * on some devices the battery is powered via external sources which
+ * could raise its voltage above the float voltage. smb135x chips go
+ * in to reverse boost in such a situation and the workaround is to
+ * disable float voltage compensation (note that the battery will appear
+ * hot/cold when powered via external source).
+ */
+
+ if (chip->soft_vfloat_comp_disabled) {
+ mask = HOT_SOFT_VFLOAT_COMP_EN_BIT
+ | COLD_SOFT_VFLOAT_COMP_EN_BIT;
+ rc = smb135x_masked_write(chip, CFG_1A_REG, mask, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't disable soft vfloat rc = %d\n",
+ rc);
+ goto free_regulator;
+ }
+ }
+
+ if (chip->soft_current_comp_disabled) {
+ mask = HOT_SOFT_CURRENT_COMP_EN_BIT
+ | COLD_SOFT_CURRENT_COMP_EN_BIT;
+ rc = smb135x_masked_write(chip, CFG_1A_REG, mask, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't disable soft current rc = %d\n",
+ rc);
+ goto free_regulator;
+ }
+ }
+
+ /*
+ * Command mode for OTG control. This gives us RID interrupts but keeps
+ * enabling the 5V OTG via i2c register control
+ */
+ rc = smb135x_masked_write(chip, USBIN_OTG_REG, OTG_CNFG_MASK,
+ OTG_CNFG_COMMAND_CTRL);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't write to otg cfg reg rc = %d\n",
+ rc);
+ goto free_regulator;
+ }
+ return 0;
+
+free_regulator:
+ if (chip->therm_bias_vreg)
+ regulator_disable(chip->therm_bias_vreg);
+ if (chip->usb_pullup_vreg)
+ regulator_disable(chip->usb_pullup_vreg);
+ return rc;
+}
+
+static struct of_device_id smb135x_match_table[] = {
+ {
+ .compatible = "qcom,smb1356-charger",
+ .data = &version_data[V_SMB1356],
+ },
+ {
+ .compatible = "qcom,smb1357-charger",
+ .data = &version_data[V_SMB1357],
+ },
+ {
+ .compatible = "qcom,smb1358-charger",
+ .data = &version_data[V_SMB1358],
+ },
+ {
+ .compatible = "qcom,smb1359-charger",
+ .data = &version_data[V_SMB1359],
+ },
+ { },
+};
+
+#define DC_MA_MIN 300
+#define DC_MA_MAX 2000
+#define NUM_GAMMA_VALUES 4
+static int smb_parse_dt(struct smb135x_chg *chip)
+{
+ int rc;
+ struct device_node *node = chip->dev->of_node;
+ const char *dc_psy_type;
+
+ if (!node) {
+ dev_err(chip->dev, "device tree info. missing\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(node, "qcom,float-voltage-mv",
+ &chip->vfloat_mv);
+ if (rc < 0)
+ chip->vfloat_mv = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,charging-timeout",
+ &chip->safety_time);
+ if (rc < 0)
+ chip->safety_time = -EINVAL;
+
+ if (!rc &&
+ (chip->safety_time > chg_time[ARRAY_SIZE(chg_time) - 1])) {
+ dev_err(chip->dev, "Bad charging-timeout %d\n",
+ chip->safety_time);
+ return -EINVAL;
+ }
+
+ chip->bmd_algo_disabled = of_property_read_bool(node,
+ "qcom,bmd-algo-disabled");
+
+ chip->dc_psy_type = -EINVAL;
+ dc_psy_type = of_get_property(node, "qcom,dc-psy-type", NULL);
+ if (dc_psy_type) {
+ if (strcmp(dc_psy_type, "Mains") == 0)
+ chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS;
+ else if (strcmp(dc_psy_type, "Wireless") == 0)
+ chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS;
+ }
+
+ if (chip->dc_psy_type != -EINVAL) {
+ rc = of_property_read_u32(node, "qcom,dc-psy-ma",
+ &chip->dc_psy_ma);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "no mA current for dc rc = %d\n", rc);
+ return rc;
+ }
+
+ if (chip->dc_psy_ma < DC_MA_MIN
+ || chip->dc_psy_ma > DC_MA_MAX) {
+ dev_err(chip->dev, "Bad dc mA %d\n", chip->dc_psy_ma);
+ return -EINVAL;
+ }
+ }
+
+ rc = of_property_read_u32(node, "qcom,recharge-thresh-mv",
+ &chip->resume_delta_mv);
+ if (rc < 0)
+ chip->resume_delta_mv = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma);
+ if (rc < 0)
+ chip->iterm_ma = -EINVAL;
+
+ chip->iterm_disabled = of_property_read_bool(node,
+ "qcom,iterm-disabled");
+
+ chip->chg_disabled_permanently = (of_property_read_bool(node,
+ "qcom,charging-disabled"));
+ chip->chg_enabled = !chip->chg_disabled_permanently;
+
+ chip->inhibit_disabled = of_property_read_bool(node,
+ "qcom,inhibit-disabled");
+
+ chip->bms_controlled_charging = of_property_read_bool(node,
+ "qcom,bms-controlled-charging");
+
+ rc = of_property_read_string(node, "qcom,bms-psy-name",
+ &chip->bms_psy_name);
+ if (rc)
+ chip->bms_psy_name = NULL;
+
+ rc = of_property_read_u32(node, "qcom,fastchg-ma", &chip->fastchg_ma);
+ if (rc < 0)
+ chip->fastchg_ma = -EINVAL;
+
+ chip->soft_vfloat_comp_disabled = of_property_read_bool(node,
+ "qcom,soft-vfloat-comp-disabled");
+
+ chip->soft_current_comp_disabled = of_property_read_bool(node,
+ "qcom,soft-current-comp-disabled");
+
+ if (of_find_property(node, "therm-bias-supply", NULL)) {
+ /* get the thermistor bias regulator */
+ chip->therm_bias_vreg = devm_regulator_get(chip->dev,
+ "therm-bias");
+ if (IS_ERR(chip->therm_bias_vreg))
+ return PTR_ERR(chip->therm_bias_vreg);
+ }
+
+ /*
+ * Gamma value indicates the ratio of the pull up resistors and NTC
+ * resistor in battery pack. There are 4 options, refer to the graphic
+ * user interface and choose the right one.
+ */
+ if (of_find_property(node, "qcom,gamma-setting",
+ &chip->gamma_setting_num)) {
+ chip->gamma_setting_num = chip->gamma_setting_num /
+ sizeof(chip->gamma_setting_num);
+ if (NUM_GAMMA_VALUES != chip->gamma_setting_num) {
+ pr_err("Gamma setting not correct!\n");
+ return -EINVAL;
+ }
+
+ chip->gamma_setting = devm_kzalloc(chip->dev,
+ chip->gamma_setting_num *
+ sizeof(chip->gamma_setting_num), GFP_KERNEL);
+ if (!chip->gamma_setting) {
+ pr_err("gamma setting kzalloc failed!\n");
+ return -ENOMEM;
+ }
+
+ rc = of_property_read_u32_array(node,
+ "qcom,gamma-setting",
+ chip->gamma_setting, chip->gamma_setting_num);
+ if (rc) {
+ pr_err("Couldn't read gamma setting, rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ if (of_find_property(node, "qcom,thermal-mitigation",
+ &chip->thermal_levels)) {
+ chip->thermal_mitigation = devm_kzalloc(chip->dev,
+ chip->thermal_levels,
+ GFP_KERNEL);
+
+ if (chip->thermal_mitigation == NULL) {
+ pr_err("thermal mitigation kzalloc() failed.\n");
+ return -ENOMEM;
+ }
+
+ chip->thermal_levels /= sizeof(int);
+ rc = of_property_read_u32_array(node,
+ "qcom,thermal-mitigation",
+ chip->thermal_mitigation, chip->thermal_levels);
+ if (rc) {
+ pr_err("Couldn't read threm limits rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ if (of_find_property(node, "usb-pullup-supply", NULL)) {
+ /* get the data line pull-up regulator */
+ chip->usb_pullup_vreg = devm_regulator_get(chip->dev,
+ "usb-pullup");
+ if (IS_ERR(chip->usb_pullup_vreg))
+ return PTR_ERR(chip->usb_pullup_vreg);
+ }
+
+ chip->pinctrl_state_name = of_get_property(node, "pinctrl-names", NULL);
+
+ chip->id_line_not_connected = of_property_read_bool(node,
+ "qcom,id-line-not-connected");
+ return 0;
+}
+
+static int create_debugfs_entries(struct smb135x_chg *chip)
+{
+ chip->debug_root = debugfs_create_dir("smb135x", NULL);
+ if (!chip->debug_root)
+ dev_err(chip->dev, "Couldn't create debug dir\n");
+
+ if (chip->debug_root) {
+ struct dentry *ent;
+
+ ent = debugfs_create_file("config_registers", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &cnfg_debugfs_ops);
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create cnfg debug file\n");
+
+ ent = debugfs_create_file("status_registers", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &status_debugfs_ops);
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create status debug file\n");
+
+ ent = debugfs_create_file("cmd_registers", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &cmd_debugfs_ops);
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create cmd debug file\n");
+
+ ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root,
+ &(chip->peek_poke_address));
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create address debug file\n");
+
+ ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root, chip,
+ &poke_poke_debug_ops);
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create data debug file\n");
+
+ ent = debugfs_create_file("force_irq",
+ S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root, chip,
+ &force_irq_ops);
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create force_irq debug file\n");
+
+ ent = debugfs_create_x32("skip_writes",
+ S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root,
+ &(chip->skip_writes));
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create skip writes debug file\n");
+
+ ent = debugfs_create_x32("skip_reads",
+ S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root,
+ &(chip->skip_reads));
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create skip reads debug file\n");
+
+ ent = debugfs_create_file("irq_count", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &irq_count_debugfs_ops);
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create irq_count debug file\n");
+
+ ent = debugfs_create_file("force_recharge",
+ S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root, chip,
+ &force_rechg_ops);
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create force recharge debug file\n");
+
+ ent = debugfs_create_x32("usb_suspend_votes",
+ S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root,
+ &(chip->usb_suspended));
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create usb_suspend_votes file\n");
+
+ ent = debugfs_create_x32("dc_suspend_votes",
+ S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root,
+ &(chip->dc_suspended));
+ if (!ent)
+ dev_err(chip->dev,
+ "Couldn't create dc_suspend_votes file\n");
+ }
+ return 0;
+}
+
+static int is_parallel_charger(struct i2c_client *client)
+{
+ struct device_node *node = client->dev.of_node;
+
+ return of_property_read_bool(node, "qcom,parallel-charger");
+}
+
+static int smb135x_main_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int rc;
+ struct smb135x_chg *chip;
+ struct power_supply *usb_psy;
+ struct power_supply_config batt_psy_cfg = {};
+ struct power_supply_config dc_psy_cfg = {};
+ u8 reg = 0;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ dev_err(&client->dev, "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ chip->client = client;
+ chip->dev = &client->dev;
+
+ rc = smb_parse_dt(chip);
+ if (rc < 0) {
+ dev_err(&client->dev, "Unable to parse DT nodes\n");
+ return rc;
+ }
+
+ usb_psy = power_supply_get_by_name("usb");
+ if (!usb_psy && chip->chg_enabled) {
+ dev_dbg(&client->dev, "USB supply not found; defer probe\n");
+ return -EPROBE_DEFER;
+ }
+ chip->usb_psy = usb_psy;
+
+ chip->fake_battery_soc = -EINVAL;
+
+ INIT_DELAYED_WORK(&chip->wireless_insertion_work,
+ wireless_insertion_work);
+
+ INIT_DELAYED_WORK(&chip->reset_otg_oc_count_work,
+ reset_otg_oc_count_work);
+ INIT_DELAYED_WORK(&chip->hvdcp_det_work, smb135x_hvdcp_det_work);
+ mutex_init(&chip->path_suspend_lock);
+ mutex_init(&chip->current_change_lock);
+ mutex_init(&chip->read_write_lock);
+ mutex_init(&chip->otg_oc_count_lock);
+ device_init_wakeup(chip->dev, true);
+ /* probe the device to check if its actually connected */
+ rc = smb135x_read(chip, CFG_4_REG, &reg);
+ if (rc) {
+ pr_err("Failed to detect SMB135x, device may be absent\n");
+ return -ENODEV;
+ }
+
+ i2c_set_clientdata(client, chip);
+
+ rc = smb135x_chip_version_and_revision(chip);
+ if (rc) {
+ dev_err(&client->dev,
+ "Couldn't detect version/revision rc=%d\n", rc);
+ return rc;
+ }
+
+ dump_regs(chip);
+
+ rc = smb135x_regulator_init(chip);
+ if (rc) {
+ dev_err(&client->dev,
+ "Couldn't initialize regulator rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb135x_hw_init(chip);
+ if (rc < 0) {
+ dev_err(&client->dev,
+ "Unable to intialize hardware rc = %d\n", rc);
+ goto free_regulator;
+ }
+
+ rc = determine_initial_status(chip);
+ if (rc < 0) {
+ dev_err(&client->dev,
+ "Unable to determine init status rc = %d\n", rc);
+ goto free_regulator;
+ }
+
+ chip->batt_psy_d.name = "battery";
+ chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY;
+ chip->batt_psy_d.get_property = smb135x_battery_get_property;
+ chip->batt_psy_d.set_property = smb135x_battery_set_property;
+ chip->batt_psy_d.properties = smb135x_battery_properties;
+ chip->batt_psy_d.num_properties
+ = ARRAY_SIZE(smb135x_battery_properties);
+ chip->batt_psy_d.external_power_changed
+ = smb135x_external_power_changed;
+ chip->batt_psy_d.property_is_writeable = smb135x_battery_is_writeable;
+
+ batt_psy_cfg.drv_data = chip;
+ batt_psy_cfg.num_supplicants = 0;
+ if (chip->bms_controlled_charging) {
+ batt_psy_cfg.supplied_to = pm_batt_supplied_to;
+ batt_psy_cfg.num_supplicants
+ = ARRAY_SIZE(pm_batt_supplied_to);
+ }
+ chip->batt_psy = devm_power_supply_register(chip->dev,
+ &chip->batt_psy_d, &batt_psy_cfg);
+ if (IS_ERR(chip->batt_psy)) {
+ dev_err(&client->dev, "Unable to register batt_psy rc = %ld\n",
+ PTR_ERR(chip->batt_psy));
+ goto free_regulator;
+ }
+
+ if (chip->dc_psy_type != -EINVAL) {
+ chip->dc_psy_d.name = "dc";
+ chip->dc_psy_d.type = chip->dc_psy_type;
+ chip->dc_psy_d.get_property = smb135x_dc_get_property;
+ chip->dc_psy_d.properties = smb135x_dc_properties;
+ chip->dc_psy_d.num_properties
+ = ARRAY_SIZE(smb135x_dc_properties);
+
+ dc_psy_cfg.drv_data = chip;
+ dc_psy_cfg.num_supplicants = 0;
+ chip->dc_psy = devm_power_supply_register(chip->dev,
+ &chip->dc_psy_d,
+ &dc_psy_cfg);
+
+ if (IS_ERR(chip->dc_psy)) {
+ dev_err(&client->dev,
+ "Unable to register dc_psy rc = %ld\n",
+ PTR_ERR(chip->dc_psy));
+ goto free_regulator;
+ }
+ }
+
+ chip->resume_completed = true;
+ mutex_init(&chip->irq_complete);
+
+ /* STAT irq configuration */
+ if (client->irq) {
+ rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ smb135x_chg_stat_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "smb135x_chg_stat_irq", chip);
+ if (rc < 0) {
+ dev_err(&client->dev,
+ "request_irq for irq=%d failed rc = %d\n",
+ client->irq, rc);
+ goto free_regulator;
+ }
+ enable_irq_wake(client->irq);
+ }
+
+ create_debugfs_entries(chip);
+ dev_info(chip->dev, "SMB135X version = %s revision = %s successfully probed batt=%d dc = %d usb = %d\n",
+ version_str[chip->version],
+ revision_str[chip->revision],
+ smb135x_get_prop_batt_present(chip),
+ chip->dc_present, chip->usb_present);
+ return 0;
+
+free_regulator:
+ smb135x_regulator_deinit(chip);
+ return rc;
+}
+
+static int smb135x_parallel_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int rc;
+ struct smb135x_chg *chip;
+ const struct of_device_id *match;
+ struct device_node *node = client->dev.of_node;
+ struct power_supply_config parallel_psy_cfg = {};
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ dev_err(&client->dev, "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ chip->client = client;
+ chip->dev = &client->dev;
+ chip->parallel_charger = true;
+ chip->dc_psy_type = -EINVAL;
+
+ chip->chg_enabled = !(of_property_read_bool(node,
+ "qcom,charging-disabled"));
+
+ rc = of_property_read_u32(node, "qcom,recharge-thresh-mv",
+ &chip->resume_delta_mv);
+ if (rc < 0)
+ chip->resume_delta_mv = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,float-voltage-mv",
+ &chip->vfloat_mv);
+ if (rc < 0)
+ chip->vfloat_mv = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,parallel-en-pin-polarity",
+ &chip->parallel_pin_polarity_setting);
+ if (rc)
+ chip->parallel_pin_polarity_setting = CHG_EN_ACTIVE_LOW_BIT;
+ else
+ chip->parallel_pin_polarity_setting =
+ chip->parallel_pin_polarity_setting ?
+ CHG_EN_ACTIVE_HIGH_BIT : CHG_EN_ACTIVE_LOW_BIT;
+
+ mutex_init(&chip->path_suspend_lock);
+ mutex_init(&chip->current_change_lock);
+ mutex_init(&chip->read_write_lock);
+
+ match = of_match_node(smb135x_match_table, node);
+ if (match == NULL) {
+ dev_err(chip->dev, "device tree match not found\n");
+ return -EINVAL;
+ }
+
+ chip->version = *(int *)match->data;
+ smb135x_set_current_tables(chip);
+
+ i2c_set_clientdata(client, chip);
+
+ chip->parallel_psy_d.name = "parallel";
+ chip->parallel_psy_d.type = POWER_SUPPLY_TYPE_PARALLEL;
+ chip->parallel_psy_d.get_property = smb135x_parallel_get_property;
+ chip->parallel_psy_d.set_property = smb135x_parallel_set_property;
+ chip->parallel_psy_d.properties = smb135x_parallel_properties;
+ chip->parallel_psy_d.property_is_writeable
+ = smb135x_parallel_is_writeable;
+ chip->parallel_psy_d.num_properties
+ = ARRAY_SIZE(smb135x_parallel_properties);
+
+ parallel_psy_cfg.drv_data = chip;
+ parallel_psy_cfg.num_supplicants = 0;
+ chip->parallel_psy = devm_power_supply_register(chip->dev,
+ &chip->parallel_psy_d,
+ &parallel_psy_cfg);
+ if (IS_ERR(chip->parallel_psy)) {
+ dev_err(&client->dev,
+ "Unable to register parallel_psy rc = %ld\n",
+ PTR_ERR(chip->parallel_psy));
+ return rc;
+ }
+
+ chip->resume_completed = true;
+ mutex_init(&chip->irq_complete);
+
+ create_debugfs_entries(chip);
+
+ dev_info(chip->dev, "SMB135X USB PARALLEL CHARGER version = %s successfully probed\n",
+ version_str[chip->version]);
+ return 0;
+}
+
+static int smb135x_chg_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ if (is_parallel_charger(client))
+ return smb135x_parallel_charger_probe(client, id);
+ else
+ return smb135x_main_charger_probe(client, id);
+}
+
+static int smb135x_chg_remove(struct i2c_client *client)
+{
+ int rc;
+ struct smb135x_chg *chip = i2c_get_clientdata(client);
+
+ debugfs_remove_recursive(chip->debug_root);
+
+ if (chip->parallel_charger)
+ goto mutex_destroy;
+
+ if (chip->therm_bias_vreg) {
+ rc = regulator_disable(chip->therm_bias_vreg);
+ if (rc)
+ pr_err("Couldn't disable therm-bias rc = %d\n", rc);
+ }
+
+ if (chip->usb_pullup_vreg) {
+ rc = regulator_disable(chip->usb_pullup_vreg);
+ if (rc)
+ pr_err("Couldn't disable data-pullup rc = %d\n", rc);
+ }
+
+ smb135x_regulator_deinit(chip);
+
+mutex_destroy:
+ mutex_destroy(&chip->irq_complete);
+ return 0;
+}
+
+static int smb135x_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smb135x_chg *chip = i2c_get_clientdata(client);
+ int i, rc;
+
+ /* no suspend resume activities for parallel charger */
+ if (chip->parallel_charger)
+ return 0;
+
+ /* Save the current IRQ config */
+ for (i = 0; i < 3; i++) {
+ rc = smb135x_read(chip, IRQ_CFG_REG + i,
+ &chip->irq_cfg_mask[i]);
+ if (rc)
+ dev_err(chip->dev,
+ "Couldn't save irq cfg regs rc=%d\n", rc);
+ }
+
+ /* enable only important IRQs */
+ rc = smb135x_write(chip, IRQ_CFG_REG, IRQ_USBIN_UV_BIT);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set irq_cfg rc = %d\n", rc);
+
+ rc = smb135x_write(chip, IRQ2_CFG_REG, IRQ2_BATT_MISSING_BIT
+ | IRQ2_VBAT_LOW_BIT
+ | IRQ2_POWER_OK_BIT);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set irq2_cfg rc = %d\n", rc);
+
+ rc = smb135x_write(chip, IRQ3_CFG_REG, IRQ3_SRC_DETECT_BIT
+ | IRQ3_DCIN_UV_BIT | IRQ3_RID_DETECT_BIT);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set irq3_cfg rc = %d\n", rc);
+
+ mutex_lock(&chip->irq_complete);
+ chip->resume_completed = false;
+ mutex_unlock(&chip->irq_complete);
+
+ return 0;
+}
+
+static int smb135x_suspend_noirq(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smb135x_chg *chip = i2c_get_clientdata(client);
+
+ /* no suspend resume activities for parallel charger */
+ if (chip->parallel_charger)
+ return 0;
+
+ if (chip->irq_waiting) {
+ pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int smb135x_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smb135x_chg *chip = i2c_get_clientdata(client);
+ int i, rc;
+
+ /* no suspend resume activities for parallel charger */
+ if (chip->parallel_charger)
+ return 0;
+ /* Restore the IRQ config */
+ for (i = 0; i < 3; i++) {
+ rc = smb135x_write(chip, IRQ_CFG_REG + i,
+ chip->irq_cfg_mask[i]);
+ if (rc)
+ dev_err(chip->dev,
+ "Couldn't restore irq cfg regs rc=%d\n", rc);
+ }
+ mutex_lock(&chip->irq_complete);
+ chip->resume_completed = true;
+ if (chip->irq_waiting) {
+ mutex_unlock(&chip->irq_complete);
+ smb135x_chg_stat_handler(client->irq, chip);
+ enable_irq(client->irq);
+ } else {
+ mutex_unlock(&chip->irq_complete);
+ }
+ return 0;
+}
+
+static const struct dev_pm_ops smb135x_pm_ops = {
+ .resume = smb135x_resume,
+ .suspend_noirq = smb135x_suspend_noirq,
+ .suspend = smb135x_suspend,
+};
+
+static const struct i2c_device_id smb135x_chg_id[] = {
+ {"smb135x-charger", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, smb135x_chg_id);
+
+static void smb135x_shutdown(struct i2c_client *client)
+{
+ int rc;
+ struct smb135x_chg *chip = i2c_get_clientdata(client);
+
+ if (chip->usb_pullup_vreg) {
+ /*
+ * switch to 5V adapter to prevent any errorneous request of 12V
+ * when USB D+ line pull-up regulator turns off.
+ */
+ rc = smb135x_masked_write(chip, CFG_E_REG, HVDCP_5_9_BIT, 0);
+ if (rc < 0)
+ dev_err(chip->dev,
+ "Couldn't request for 5V rc=%d\n", rc);
+ }
+}
+
+static struct i2c_driver smb135x_chg_driver = {
+ .driver = {
+ .name = "smb135x-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = smb135x_match_table,
+ .pm = &smb135x_pm_ops,
+ },
+ .probe = smb135x_chg_probe,
+ .remove = smb135x_chg_remove,
+ .id_table = smb135x_chg_id,
+ .shutdown = smb135x_shutdown,
+};
+
+module_i2c_driver(smb135x_chg_driver);
+
+MODULE_DESCRIPTION("SMB135x Charger");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("i2c:smb135x-charger");
diff --git a/drivers/power/supply/qcom/smb138x-charger.c b/drivers/power/supply/qcom/smb138x-charger.c
new file mode 100644
index 000000000000..e7ca1e3fb108
--- /dev/null
+++ b/drivers/power/supply/qcom/smb138x-charger.c
@@ -0,0 +1,1655 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "SMB138X: %s: " fmt, __func__
+
+#include <linux/device.h>
+#include <linux/iio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.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/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include "smb-reg.h"
+#include "smb-lib.h"
+#include "storm-watch.h"
+#include <linux/pmic-voter.h>
+
+#define SMB138X_DEFAULT_FCC_UA 1000000
+#define SMB138X_DEFAULT_ICL_UA 1500000
+
+/* Registers that are not common to be mentioned in smb-reg.h */
+#define SMB2CHG_MISC_ENG_SDCDC_CFG2 (MISC_BASE + 0xC1)
+#define ENG_SDCDC_SEL_OOB_VTH_BIT BIT(0)
+
+#define SMB2CHG_MISC_ENG_SDCDC_CFG6 (MISC_BASE + 0xC5)
+#define DEAD_TIME_MASK GENMASK(7, 4)
+#define HIGH_DEAD_TIME_MASK GENMASK(7, 4)
+
+#define SMB2CHG_DC_TM_SREFGEN (DCIN_BASE + 0xE2)
+#define STACKED_DIODE_EN_BIT BIT(2)
+
+#define TDIE_AVG_COUNT 10
+#define MAX_SPEED_READING_TIMES 5
+
+enum {
+ OOB_COMP_WA_BIT = BIT(0),
+};
+
+static struct smb_params v1_params = {
+ .fcc = {
+ .name = "fast charge current",
+ .reg = FAST_CHARGE_CURRENT_CFG_REG,
+ .min_u = 0,
+ .max_u = 6000000,
+ .step_u = 25000,
+ },
+ .fv = {
+ .name = "float voltage",
+ .reg = FLOAT_VOLTAGE_CFG_REG,
+ .min_u = 2450000,
+ .max_u = 4950000,
+ .step_u = 10000,
+ },
+ .usb_icl = {
+ .name = "usb input current limit",
+ .reg = USBIN_CURRENT_LIMIT_CFG_REG,
+ .min_u = 0,
+ .max_u = 6000000,
+ .step_u = 25000,
+ },
+ .dc_icl = {
+ .name = "dc input current limit",
+ .reg = DCIN_CURRENT_LIMIT_CFG_REG,
+ .min_u = 0,
+ .max_u = 6000000,
+ .step_u = 25000,
+ },
+ .freq_buck = {
+ .name = "buck switching frequency",
+ .reg = CFG_BUCKBOOST_FREQ_SELECT_BUCK_REG,
+ .min_u = 500,
+ .max_u = 2000,
+ .step_u = 100,
+ },
+};
+
+struct smb_dt_props {
+ bool suspend_input;
+ int fcc_ua;
+ int usb_icl_ua;
+ int dc_icl_ua;
+ int chg_temp_max_mdegc;
+ int connector_temp_max_mdegc;
+ int pl_mode;
+};
+
+struct smb138x {
+ struct smb_charger chg;
+ struct smb_dt_props dt;
+ struct power_supply *parallel_psy;
+ u32 wa_flags;
+};
+
+static int __debug_mask;
+module_param_named(
+ debug_mask, __debug_mask, int, S_IRUSR | S_IWUSR
+);
+
+irqreturn_t smb138x_handle_slave_chg_state_change(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb138x *chip = irq_data->parent_data;
+
+ if (chip->parallel_psy)
+ power_supply_changed(chip->parallel_psy);
+
+ return IRQ_HANDLED;
+}
+
+static int smb138x_get_prop_charger_temp(struct smb138x *chip,
+ union power_supply_propval *val)
+{
+ union power_supply_propval pval;
+ int rc = 0, avg = 0, i;
+ struct smb_charger *chg = &chip->chg;
+ int die_avg_count;
+
+ if (chg->temp_speed_reading_count < MAX_SPEED_READING_TIMES) {
+ chg->temp_speed_reading_count++;
+ die_avg_count = 1;
+ } else {
+ die_avg_count = TDIE_AVG_COUNT;
+ }
+
+ for (i = 0; i < die_avg_count; i++) {
+ pval.intval = 0;
+ rc = smblib_get_prop_charger_temp(chg, &pval);
+ if (rc < 0) {
+ pr_err("Couldnt read chg temp at %dth iteration rc = %d\n",
+ i + 1, rc);
+ return rc;
+ }
+ avg += pval.intval;
+ }
+ val->intval = avg / die_avg_count;
+ return rc;
+}
+
+static int smb138x_parse_dt(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct device_node *node = chg->dev->of_node;
+ int rc;
+
+ if (!node) {
+ pr_err("device tree node missing\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(node,
+ "qcom,parallel-mode", &chip->dt.pl_mode);
+ if (rc < 0)
+ chip->dt.pl_mode = POWER_SUPPLY_PL_USBMID_USBMID;
+
+ chip->dt.suspend_input = of_property_read_bool(node,
+ "qcom,suspend-input");
+
+ rc = of_property_read_u32(node,
+ "qcom,fcc-max-ua", &chip->dt.fcc_ua);
+ if (rc < 0)
+ chip->dt.fcc_ua = SMB138X_DEFAULT_FCC_UA;
+
+ rc = of_property_read_u32(node,
+ "qcom,usb-icl-ua", &chip->dt.usb_icl_ua);
+ if (rc < 0)
+ chip->dt.usb_icl_ua = SMB138X_DEFAULT_ICL_UA;
+
+ rc = of_property_read_u32(node,
+ "qcom,dc-icl-ua", &chip->dt.dc_icl_ua);
+ if (rc < 0)
+ chip->dt.dc_icl_ua = SMB138X_DEFAULT_ICL_UA;
+
+ rc = of_property_read_u32(node,
+ "qcom,charger-temp-max-mdegc",
+ &chip->dt.chg_temp_max_mdegc);
+ if (rc < 0)
+ chip->dt.chg_temp_max_mdegc = 80000;
+
+ rc = of_property_read_u32(node,
+ "qcom,connector-temp-max-mdegc",
+ &chip->dt.connector_temp_max_mdegc);
+ if (rc < 0)
+ chip->dt.connector_temp_max_mdegc = 105000;
+
+ return 0;
+}
+
+/************************
+ * USB PSY REGISTRATION *
+ ************************/
+
+static enum power_supply_property smb138x_usb_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_TYPE,
+ POWER_SUPPLY_PROP_TYPEC_MODE,
+ POWER_SUPPLY_PROP_TYPEC_POWER_ROLE,
+ POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION,
+ POWER_SUPPLY_PROP_SDP_CURRENT_MAX,
+};
+
+static int smb138x_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb138x *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ rc = smblib_get_prop_usb_present(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ rc = smblib_get_prop_usb_online(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = chg->voltage_min_uv;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = chg->voltage_max_uv;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ rc = smblib_get_prop_usb_voltage_now(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = get_effective_result(chg->usb_icl_votable);
+ break;
+ case POWER_SUPPLY_PROP_TYPE:
+ val->intval = chg->usb_psy_desc.type;
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_MODE:
+ val->intval = chg->typec_mode;
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+ rc = smblib_get_prop_typec_power_role(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION:
+ rc = smblib_get_prop_typec_cc_orientation(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_SDP_CURRENT_MAX:
+ val->intval = get_client_vote(chg->usb_icl_votable,
+ USB_PSY_VOTER);
+ break;
+ default:
+ pr_err("get prop %d is not supported\n", prop);
+ return -EINVAL;
+ }
+
+ if (rc < 0) {
+ pr_debug("Couldn't get prop %d rc = %d\n", prop, rc);
+ return -ENODATA;
+ }
+
+ return rc;
+}
+
+static int smb138x_usb_set_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct smb138x *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+ rc = smblib_set_prop_typec_power_role(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_SDP_CURRENT_MAX:
+ rc = smblib_set_prop_sdp_current_max(chg, val);
+ break;
+ default:
+ pr_err("set prop %d is not supported\n", prop);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb138x_usb_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ return 0;
+}
+
+static int smb138x_init_usb_psy(struct smb138x *chip)
+{
+ struct power_supply_config usb_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+
+ chg->usb_psy_desc.name = "usb";
+ chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
+ chg->usb_psy_desc.properties = smb138x_usb_props;
+ chg->usb_psy_desc.num_properties = ARRAY_SIZE(smb138x_usb_props);
+ chg->usb_psy_desc.get_property = smb138x_usb_get_prop;
+ chg->usb_psy_desc.set_property = smb138x_usb_set_prop;
+ chg->usb_psy_desc.property_is_writeable = smb138x_usb_prop_is_writeable;
+
+ usb_cfg.drv_data = chip;
+ usb_cfg.of_node = chg->dev->of_node;
+ chg->usb_psy = devm_power_supply_register(chg->dev,
+ &chg->usb_psy_desc,
+ &usb_cfg);
+ if (IS_ERR(chg->usb_psy)) {
+ pr_err("Couldn't register USB power supply\n");
+ return PTR_ERR(chg->usb_psy);
+ }
+
+ return 0;
+}
+
+/*************************
+ * BATT PSY REGISTRATION *
+ *************************/
+
+static enum power_supply_property smb138x_batt_props[] = {
+ POWER_SUPPLY_PROP_INPUT_SUSPEND,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGER_TEMP,
+ POWER_SUPPLY_PROP_CHARGER_TEMP_MAX,
+ POWER_SUPPLY_PROP_SET_SHIP_MODE,
+};
+
+static int smb138x_batt_get_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb138x *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ rc = smblib_get_prop_batt_status(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ rc = smblib_get_prop_batt_health(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ rc = smblib_get_prop_batt_present(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smblib_get_prop_input_suspend(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ rc = smblib_get_prop_batt_charge_type(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ rc = smblib_get_prop_batt_capacity(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGER_TEMP:
+ rc = smb138x_get_prop_charger_temp(chip, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX:
+ rc = smblib_get_prop_charger_temp_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+ /* Not in ship mode as long as device is active */
+ val->intval = 0;
+ break;
+ default:
+ pr_err("batt power supply get prop %d not supported\n", prop);
+ return -EINVAL;
+ }
+
+ if (rc < 0) {
+ pr_debug("Couldn't get prop %d rc = %d\n", prop, rc);
+ return -ENODATA;
+ }
+
+ return rc;
+}
+
+static int smb138x_batt_set_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct smb138x *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smblib_set_prop_input_suspend(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ rc = smblib_set_prop_batt_capacity(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+ /* Not in ship mode as long as the device is active */
+ if (!val->intval)
+ break;
+ rc = smblib_set_prop_ship_mode(chg, val);
+ break;
+ default:
+ pr_err("batt power supply set prop %d not supported\n", prop);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb138x_batt_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct power_supply_desc batt_psy_desc = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = smb138x_batt_props,
+ .num_properties = ARRAY_SIZE(smb138x_batt_props),
+ .get_property = smb138x_batt_get_prop,
+ .set_property = smb138x_batt_set_prop,
+ .property_is_writeable = smb138x_batt_prop_is_writeable,
+};
+
+static int smb138x_init_batt_psy(struct smb138x *chip)
+{
+ struct power_supply_config batt_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ batt_cfg.drv_data = chip;
+ batt_cfg.of_node = chg->dev->of_node;
+ chg->batt_psy = devm_power_supply_register(chg->dev,
+ &batt_psy_desc,
+ &batt_cfg);
+ if (IS_ERR(chg->batt_psy)) {
+ pr_err("Couldn't register battery power supply\n");
+ return PTR_ERR(chg->batt_psy);
+ }
+
+ return rc;
+}
+
+/*****************************
+ * PARALLEL PSY REGISTRATION *
+ *****************************/
+
+static int smb138x_get_prop_connector_health(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc, lb_mdegc, ub_mdegc, rst_mdegc, connector_mdegc;
+
+ if (!chg->iio.connector_temp_chan ||
+ PTR_ERR(chg->iio.connector_temp_chan) == -EPROBE_DEFER)
+ chg->iio.connector_temp_chan = iio_channel_get(chg->dev,
+ "connector_temp");
+
+ if (IS_ERR(chg->iio.connector_temp_chan))
+ return POWER_SUPPLY_HEALTH_UNKNOWN;
+
+ rc = iio_read_channel_processed(chg->iio.connector_temp_thr1_chan,
+ &lb_mdegc);
+ if (rc < 0) {
+ pr_err("Couldn't read connector lower bound rc=%d\n", rc);
+ return POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+
+ rc = iio_read_channel_processed(chg->iio.connector_temp_thr2_chan,
+ &ub_mdegc);
+ if (rc < 0) {
+ pr_err("Couldn't read connector upper bound rc=%d\n", rc);
+ return POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+
+ rc = iio_read_channel_processed(chg->iio.connector_temp_thr3_chan,
+ &rst_mdegc);
+ if (rc < 0) {
+ pr_err("Couldn't read connector reset bound rc=%d\n", rc);
+ return POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+
+ rc = iio_read_channel_processed(chg->iio.connector_temp_chan,
+ &connector_mdegc);
+ if (rc < 0) {
+ pr_err("Couldn't read connector temperature rc=%d\n", rc);
+ return POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+
+ if (connector_mdegc < lb_mdegc)
+ return POWER_SUPPLY_HEALTH_COOL;
+ else if (connector_mdegc < ub_mdegc)
+ return POWER_SUPPLY_HEALTH_WARM;
+ else if (connector_mdegc < rst_mdegc)
+ return POWER_SUPPLY_HEALTH_HOT;
+
+ return POWER_SUPPLY_HEALTH_OVERHEAT;
+}
+
+static enum power_supply_property smb138x_parallel_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_PIN_ENABLED,
+ POWER_SUPPLY_PROP_INPUT_SUSPEND,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CHARGER_TEMP,
+ POWER_SUPPLY_PROP_CHARGER_TEMP_MAX,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_PARALLEL_MODE,
+ POWER_SUPPLY_PROP_CONNECTOR_HEALTH,
+ POWER_SUPPLY_PROP_SET_SHIP_MODE,
+};
+
+static int smb138x_parallel_get_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb138x *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+ u8 temp;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ rc = smblib_get_prop_batt_charge_type(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_5_REG,
+ &temp);
+ if (rc >= 0)
+ val->intval = (bool)(temp & CHARGING_ENABLE_BIT);
+ break;
+ case POWER_SUPPLY_PROP_PIN_ENABLED:
+ rc = smblib_read(chg, BATTERY_CHARGER_STATUS_5_REG,
+ &temp);
+ if (rc >= 0)
+ val->intval = !(temp & DISABLE_CHARGING_BIT);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smblib_get_usb_suspend(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ if ((chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+ || (chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT))
+ rc = smblib_get_prop_input_current_limited(chg, val);
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if ((chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+ || (chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT))
+ rc = smblib_get_charge_param(chg, &chg->param.usb_icl,
+ &val->intval);
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smblib_get_charge_param(chg, &chg->param.fv, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ rc = smblib_get_charge_param(chg, &chg->param.fcc,
+ &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ rc = smblib_get_prop_slave_current_now(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGER_TEMP:
+ rc = smb138x_get_prop_charger_temp(chip, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX:
+ rc = smblib_get_prop_charger_temp_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = "smb138x";
+ break;
+ case POWER_SUPPLY_PROP_PARALLEL_MODE:
+ val->intval = chip->dt.pl_mode;
+ break;
+ case POWER_SUPPLY_PROP_CONNECTOR_HEALTH:
+ val->intval = smb138x_get_prop_connector_health(chip);
+ break;
+ case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+ /* Not in ship mode as long as device is active */
+ val->intval = 0;
+ break;
+ default:
+ pr_err("parallel power supply get prop %d not supported\n",
+ prop);
+ return -EINVAL;
+ }
+
+ if (rc < 0) {
+ pr_debug("Couldn't get prop %d rc = %d\n", prop, rc);
+ return -ENODATA;
+ }
+
+ return rc;
+}
+
+static int smb138x_set_parallel_suspend(struct smb138x *chip, bool suspend)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ rc = smblib_masked_write(chg, WD_CFG_REG, WDOG_TIMER_EN_BIT,
+ suspend ? 0 : WDOG_TIMER_EN_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't %s watchdog rc=%d\n",
+ suspend ? "disable" : "enable", rc);
+ suspend = true;
+ }
+
+ rc = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT,
+ suspend ? USBIN_SUSPEND_BIT : 0);
+ if (rc < 0) {
+ pr_err("Couldn't %s parallel charger rc=%d\n",
+ suspend ? "suspend" : "resume", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smb138x_parallel_set_prop(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct smb138x *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+ rc = smb138x_set_parallel_suspend(chip, (bool)val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if ((chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+ || (chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT))
+ rc = smblib_set_charge_param(chg, &chg->param.usb_icl,
+ val->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smblib_set_charge_param(chg, &chg->param.fv, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ rc = smblib_set_charge_param(chg, &chg->param.fcc, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+ /* Not in ship mode as long as the device is active */
+ if (!val->intval)
+ break;
+ rc = smblib_set_prop_ship_mode(chg, val);
+ break;
+ default:
+ pr_debug("parallel power supply set prop %d not supported\n",
+ prop);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb138x_parallel_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ return 0;
+}
+
+static const struct power_supply_desc parallel_psy_desc = {
+ .name = "parallel",
+ .type = POWER_SUPPLY_TYPE_PARALLEL,
+ .properties = smb138x_parallel_props,
+ .num_properties = ARRAY_SIZE(smb138x_parallel_props),
+ .get_property = smb138x_parallel_get_prop,
+ .set_property = smb138x_parallel_set_prop,
+ .property_is_writeable = smb138x_parallel_prop_is_writeable,
+};
+
+static int smb138x_init_parallel_psy(struct smb138x *chip)
+{
+ struct power_supply_config parallel_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+
+ parallel_cfg.drv_data = chip;
+ parallel_cfg.of_node = chg->dev->of_node;
+ chip->parallel_psy = devm_power_supply_register(chg->dev,
+ &parallel_psy_desc,
+ &parallel_cfg);
+ if (IS_ERR(chip->parallel_psy)) {
+ pr_err("Couldn't register parallel power supply\n");
+ return PTR_ERR(chip->parallel_psy);
+ }
+
+ return 0;
+}
+
+/******************************
+ * VBUS REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb138x_vbus_reg_ops = {
+ .enable = smblib_vbus_regulator_enable,
+ .disable = smblib_vbus_regulator_disable,
+ .is_enabled = smblib_vbus_regulator_is_enabled,
+};
+
+static int smb138x_init_vbus_regulator(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct regulator_config cfg = {};
+ int rc = 0;
+
+ chg->vbus_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vbus_vreg),
+ GFP_KERNEL);
+ if (!chg->vbus_vreg)
+ return -ENOMEM;
+
+ cfg.dev = chg->dev;
+ cfg.driver_data = chip;
+
+ chg->vbus_vreg->rdesc.owner = THIS_MODULE;
+ chg->vbus_vreg->rdesc.type = REGULATOR_VOLTAGE;
+ chg->vbus_vreg->rdesc.ops = &smb138x_vbus_reg_ops;
+ chg->vbus_vreg->rdesc.of_match = "qcom,smb138x-vbus";
+ chg->vbus_vreg->rdesc.name = "qcom,smb138x-vbus";
+
+ chg->vbus_vreg->rdev = devm_regulator_register(chg->dev,
+ &chg->vbus_vreg->rdesc, &cfg);
+ if (IS_ERR(chg->vbus_vreg->rdev)) {
+ rc = PTR_ERR(chg->vbus_vreg->rdev);
+ chg->vbus_vreg->rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't register VBUS regualtor rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+/******************************
+ * VCONN REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb138x_vconn_reg_ops = {
+ .enable = smblib_vconn_regulator_enable,
+ .disable = smblib_vconn_regulator_disable,
+ .is_enabled = smblib_vconn_regulator_is_enabled,
+};
+
+static int smb138x_init_vconn_regulator(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct regulator_config cfg = {};
+ int rc = 0;
+
+ chg->vconn_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vconn_vreg),
+ GFP_KERNEL);
+ if (!chg->vconn_vreg)
+ return -ENOMEM;
+
+ cfg.dev = chg->dev;
+ cfg.driver_data = chip;
+
+ chg->vconn_vreg->rdesc.owner = THIS_MODULE;
+ chg->vconn_vreg->rdesc.type = REGULATOR_VOLTAGE;
+ chg->vconn_vreg->rdesc.ops = &smb138x_vconn_reg_ops;
+ chg->vconn_vreg->rdesc.of_match = "qcom,smb138x-vconn";
+ chg->vconn_vreg->rdesc.name = "qcom,smb138x-vconn";
+
+ chg->vconn_vreg->rdev = devm_regulator_register(chg->dev,
+ &chg->vconn_vreg->rdesc, &cfg);
+ if (IS_ERR(chg->vconn_vreg->rdev)) {
+ rc = PTR_ERR(chg->vconn_vreg->rdev);
+ chg->vconn_vreg->rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't register VCONN regualtor rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+/***************************
+ * HARDWARE INITIALIZATION *
+ ***************************/
+
+#define MDEGC_3 3000
+#define MDEGC_15 15000
+static int smb138x_init_slave_hw(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc;
+
+ if (chip->wa_flags & OOB_COMP_WA_BIT) {
+ rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG2,
+ ENG_SDCDC_SEL_OOB_VTH_BIT,
+ ENG_SDCDC_SEL_OOB_VTH_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't configure the OOB comp threshold rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG6,
+ DEAD_TIME_MASK, HIGH_DEAD_TIME_MASK);
+ if (rc < 0) {
+ pr_err("Couldn't configure the sdcdc cfg 6 reg rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* configure to a fixed 700khz freq to avoid tdie errors */
+ rc = smblib_set_charge_param(chg, &chg->param.freq_buck, 700);
+ if (rc < 0) {
+ pr_err("Couldn't configure 700Khz switch freq rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable watchdog bark and bite interrupts, and disable the watchdog */
+ rc = smblib_masked_write(chg, WD_CFG_REG, WDOG_TIMER_EN_BIT
+ | WDOG_TIMER_EN_ON_PLUGIN_BIT | BITE_WDOG_INT_EN_BIT
+ | BARK_WDOG_INT_EN_BIT,
+ BITE_WDOG_INT_EN_BIT | BARK_WDOG_INT_EN_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't configure the watchdog rc=%d\n", rc);
+ return rc;
+ }
+
+ /* disable charging when watchdog bites */
+ rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG,
+ BITE_WDOG_DISABLE_CHARGING_CFG_BIT,
+ BITE_WDOG_DISABLE_CHARGING_CFG_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't configure the watchdog bite rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Disable OTG */
+ rc = smblib_masked_write(chg, CMD_OTG_REG, OTG_EN_BIT, 0);
+ if (rc < 0) {
+ pr_err("Couldn't disable OTG rc=%d\n", rc);
+ return rc;
+ }
+
+ /* suspend parallel charging */
+ rc = smb138x_set_parallel_suspend(chip, true);
+ if (rc < 0) {
+ pr_err("Couldn't suspend parallel charging rc=%d\n", rc);
+ return rc;
+ }
+
+ /* initialize FCC to 0 */
+ rc = smblib_set_charge_param(chg, &chg->param.fcc, 0);
+ if (rc < 0) {
+ pr_err("Couldn't set 0 FCC rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable the charging path */
+ rc = smblib_masked_write(chg, CHARGING_ENABLE_CMD_REG,
+ CHARGING_ENABLE_CMD_BIT,
+ CHARGING_ENABLE_CMD_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't enable charging rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure charge enable for software control; active high */
+ rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+ CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT, 0);
+ if (rc < 0) {
+ pr_err("Couldn't configure charge enable source rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* enable parallel current sensing */
+ rc = smblib_masked_write(chg, CFG_REG,
+ VCHG_EN_CFG_BIT, VCHG_EN_CFG_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't enable parallel current sensing rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* enable stacked diode */
+ rc = smblib_write(chg, SMB2CHG_DC_TM_SREFGEN, STACKED_DIODE_EN_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't enable stacked diode rc=%d\n", rc);
+ return rc;
+ }
+
+ /* initialize charger temperature threshold */
+ rc = iio_write_channel_processed(chg->iio.temp_max_chan,
+ chip->dt.chg_temp_max_mdegc);
+ if (rc < 0) {
+ pr_err("Couldn't set charger temp threshold rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = iio_write_channel_processed(chg->iio.connector_temp_thr1_chan,
+ chip->dt.connector_temp_max_mdegc);
+ if (rc < 0) {
+ pr_err("Couldn't set connector temp threshold1 rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = iio_write_channel_processed(chg->iio.connector_temp_thr2_chan,
+ chip->dt.connector_temp_max_mdegc + MDEGC_3);
+ if (rc < 0) {
+ pr_err("Couldn't set connector temp threshold2 rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = iio_write_channel_processed(chg->iio.connector_temp_thr3_chan,
+ chip->dt.connector_temp_max_mdegc + MDEGC_15);
+ if (rc < 0) {
+ pr_err("Couldn't set connector temp threshold3 rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int smb138x_init_hw(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ /* votes must be cast before configuring software control */
+ vote(chg->dc_suspend_votable,
+ DEFAULT_VOTER, chip->dt.suspend_input, 0);
+ vote(chg->fcc_votable,
+ DEFAULT_VOTER, true, chip->dt.fcc_ua);
+ vote(chg->usb_icl_votable,
+ DCP_VOTER, true, chip->dt.usb_icl_ua);
+ vote(chg->dc_icl_votable,
+ DEFAULT_VOTER, true, chip->dt.dc_icl_ua);
+
+ chg->dcp_icl_ua = chip->dt.usb_icl_ua;
+
+ /* Disable OTG */
+ rc = smblib_masked_write(chg, CMD_OTG_REG, OTG_EN_BIT, 0);
+ if (rc < 0) {
+ pr_err("Couldn't disable OTG rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Unsuspend USB input */
+ rc = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT, 0);
+ if (rc < 0) {
+ pr_err("Couldn't unsuspend USB, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure to a fixed 700khz freq to avoid tdie errors */
+ rc = smblib_set_charge_param(chg, &chg->param.freq_buck, 700);
+ if (rc < 0) {
+ pr_err("Couldn't configure 700Khz switch freq rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure charge enable for software control; active high */
+ rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+ CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT, 0);
+ if (rc < 0) {
+ pr_err("Couldn't configure charge enable source rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable the charging path */
+ rc = vote(chg->chg_disable_votable, DEFAULT_VOTER, false, 0);
+ if (rc < 0) {
+ pr_err("Couldn't enable charging rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * trigger the usb-typec-change interrupt only when the CC state
+ * changes, or there was a VBUS error
+ */
+ rc = smblib_write(chg, TYPE_C_INTRPT_ENB_REG,
+ TYPEC_CCSTATE_CHANGE_INT_EN_BIT
+ | TYPEC_VBUS_ERROR_INT_EN_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't configure Type-C interrupts rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure VCONN for software control */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ VCONN_EN_SRC_BIT | VCONN_EN_VALUE_BIT,
+ VCONN_EN_SRC_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't configure VCONN for SW control rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure VBUS for software control */
+ rc = smblib_masked_write(chg, OTG_CFG_REG, OTG_EN_SRC_CFG_BIT, 0);
+ if (rc < 0) {
+ pr_err("Couldn't configure VBUS for SW control rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure power role for dual-role */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_POWER_ROLE_CMD_MASK, 0);
+ if (rc < 0) {
+ pr_err("Couldn't configure power role for DRP rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chip->wa_flags & OOB_COMP_WA_BIT) {
+ rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG2,
+ ENG_SDCDC_SEL_OOB_VTH_BIT,
+ ENG_SDCDC_SEL_OOB_VTH_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't configure the OOB comp threshold rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG6,
+ DEAD_TIME_MASK, HIGH_DEAD_TIME_MASK);
+ if (rc < 0) {
+ pr_err("Couldn't configure the sdcdc cfg 6 reg rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int smb138x_setup_wa_flags(struct smb138x *chip)
+{
+ struct pmic_revid_data *pmic_rev_id;
+ struct device_node *revid_dev_node;
+
+ revid_dev_node = of_parse_phandle(chip->chg.dev->of_node,
+ "qcom,pmic-revid", 0);
+ if (!revid_dev_node) {
+ pr_err("Missing qcom,pmic-revid property\n");
+ return -EINVAL;
+ }
+
+ pmic_rev_id = get_revid_data(revid_dev_node);
+ if (IS_ERR_OR_NULL(pmic_rev_id)) {
+ /*
+ * the revid peripheral must be registered, any failure
+ * here only indicates that the rev-id module has not
+ * probed yet.
+ */
+ return -EPROBE_DEFER;
+ }
+
+ switch (pmic_rev_id->pmic_subtype) {
+ case SMB1381_SUBTYPE:
+ if (pmic_rev_id->rev4 < 2) /* SMB1381 rev 1.0 */
+ chip->wa_flags |= OOB_COMP_WA_BIT;
+ break;
+ default:
+ pr_err("PMIC subtype %d not supported\n",
+ pmic_rev_id->pmic_subtype);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/****************************
+ * DETERMINE INITIAL STATUS *
+ ****************************/
+
+static irqreturn_t smb138x_handle_temperature_change(int irq, void *data)
+{
+ struct smb_irq_data *irq_data = data;
+ struct smb138x *chip = irq_data->parent_data;
+
+ power_supply_changed(chip->parallel_psy);
+ return IRQ_HANDLED;
+}
+
+static int smb138x_determine_initial_slave_status(struct smb138x *chip)
+{
+ struct smb_irq_data irq_data = {chip, "determine-initial-status"};
+
+ smb138x_handle_temperature_change(0, &irq_data);
+ return 0;
+}
+
+static int smb138x_determine_initial_status(struct smb138x *chip)
+{
+ struct smb_irq_data irq_data = {chip, "determine-initial-status"};
+
+ smblib_handle_usb_plugin(0, &irq_data);
+ smblib_handle_usb_typec_change(0, &irq_data);
+ smblib_handle_usb_source_change(0, &irq_data);
+ return 0;
+}
+
+/**************************
+ * INTERRUPT REGISTRATION *
+ **************************/
+
+static struct smb_irq_info smb138x_irqs[] = {
+/* CHARGER IRQs */
+ [CHG_ERROR_IRQ] = {
+ .name = "chg-error",
+ .handler = smblib_handle_debug,
+ },
+ [CHG_STATE_CHANGE_IRQ] = {
+ .name = "chg-state-change",
+ .handler = smb138x_handle_slave_chg_state_change,
+ .wake = true,
+ },
+ [STEP_CHG_STATE_CHANGE_IRQ] = {
+ .name = "step-chg-state-change",
+ .handler = smblib_handle_debug,
+ },
+ [STEP_CHG_SOC_UPDATE_FAIL_IRQ] = {
+ .name = "step-chg-soc-update-fail",
+ .handler = smblib_handle_debug,
+ },
+ [STEP_CHG_SOC_UPDATE_REQ_IRQ] = {
+ .name = "step-chg-soc-update-request",
+ .handler = smblib_handle_debug,
+ },
+/* OTG IRQs */
+ [OTG_FAIL_IRQ] = {
+ .name = "otg-fail",
+ .handler = smblib_handle_debug,
+ },
+ [OTG_OVERCURRENT_IRQ] = {
+ .name = "otg-overcurrent",
+ .handler = smblib_handle_debug,
+ },
+ [OTG_OC_DIS_SW_STS_IRQ] = {
+ .name = "otg-oc-dis-sw-sts",
+ .handler = smblib_handle_debug,
+ },
+ [TESTMODE_CHANGE_DET_IRQ] = {
+ .name = "testmode-change-detect",
+ .handler = smblib_handle_debug,
+ },
+/* BATTERY IRQs */
+ [BATT_TEMP_IRQ] = {
+ .name = "bat-temp",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ [BATT_OCP_IRQ] = {
+ .name = "bat-ocp",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ [BATT_OV_IRQ] = {
+ .name = "bat-ov",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ [BATT_LOW_IRQ] = {
+ .name = "bat-low",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ [BATT_THERM_ID_MISS_IRQ] = {
+ .name = "bat-therm-or-id-missing",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ [BATT_TERM_MISS_IRQ] = {
+ .name = "bat-terminal-missing",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+/* USB INPUT IRQs */
+ [USBIN_COLLAPSE_IRQ] = {
+ .name = "usbin-collapse",
+ .handler = smblib_handle_debug,
+ },
+ [USBIN_LT_3P6V_IRQ] = {
+ .name = "usbin-lt-3p6v",
+ .handler = smblib_handle_debug,
+ },
+ [USBIN_UV_IRQ] = {
+ .name = "usbin-uv",
+ .handler = smblib_handle_debug,
+ },
+ [USBIN_OV_IRQ] = {
+ .name = "usbin-ov",
+ .handler = smblib_handle_debug,
+ },
+ [USBIN_PLUGIN_IRQ] = {
+ .name = "usbin-plugin",
+ .handler = smblib_handle_usb_plugin,
+ },
+ [USBIN_SRC_CHANGE_IRQ] = {
+ .name = "usbin-src-change",
+ .handler = smblib_handle_usb_source_change,
+ },
+ [USBIN_ICL_CHANGE_IRQ] = {
+ .name = "usbin-icl-change",
+ .handler = smblib_handle_debug,
+ },
+ [TYPE_C_CHANGE_IRQ] = {
+ .name = "type-c-change",
+ .handler = smblib_handle_usb_typec_change,
+ },
+/* DC INPUT IRQs */
+ [DCIN_COLLAPSE_IRQ] = {
+ .name = "dcin-collapse",
+ .handler = smblib_handle_debug,
+ },
+ [DCIN_LT_3P6V_IRQ] = {
+ .name = "dcin-lt-3p6v",
+ .handler = smblib_handle_debug,
+ },
+ [DCIN_UV_IRQ] = {
+ .name = "dcin-uv",
+ .handler = smblib_handle_debug,
+ },
+ [DCIN_OV_IRQ] = {
+ .name = "dcin-ov",
+ .handler = smblib_handle_debug,
+ },
+ [DCIN_PLUGIN_IRQ] = {
+ .name = "dcin-plugin",
+ .handler = smblib_handle_debug,
+ },
+ [DIV2_EN_DG_IRQ] = {
+ .name = "div2-en-dg",
+ .handler = smblib_handle_debug,
+ },
+ [DCIN_ICL_CHANGE_IRQ] = {
+ .name = "dcin-icl-change",
+ .handler = smblib_handle_debug,
+ },
+/* MISCELLANEOUS IRQs */
+ [WDOG_SNARL_IRQ] = {
+ .name = "wdog-snarl",
+ .handler = smblib_handle_debug,
+ },
+ [WDOG_BARK_IRQ] = {
+ .name = "wdog-bark",
+ .handler = smblib_handle_wdog_bark,
+ .wake = true,
+ },
+ [AICL_FAIL_IRQ] = {
+ .name = "aicl-fail",
+ .handler = smblib_handle_debug,
+ },
+ [AICL_DONE_IRQ] = {
+ .name = "aicl-done",
+ .handler = smblib_handle_debug,
+ },
+ [HIGH_DUTY_CYCLE_IRQ] = {
+ .name = "high-duty-cycle",
+ .handler = smblib_handle_debug,
+ },
+ [INPUT_CURRENT_LIMIT_IRQ] = {
+ .name = "input-current-limiting",
+ .handler = smblib_handle_debug,
+ },
+ [TEMPERATURE_CHANGE_IRQ] = {
+ .name = "temperature-change",
+ .handler = smb138x_handle_temperature_change,
+ },
+ [SWITCH_POWER_OK_IRQ] = {
+ .name = "switcher-power-ok",
+ .handler = smblib_handle_debug,
+ },
+};
+
+static int smb138x_get_irq_index_byname(const char *irq_name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(smb138x_irqs); i++) {
+ if (strcmp(smb138x_irqs[i].name, irq_name) == 0)
+ return i;
+ }
+
+ return -ENOENT;
+}
+
+static int smb138x_request_interrupt(struct smb138x *chip,
+ struct device_node *node,
+ const char *irq_name)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0, irq, irq_index;
+ struct smb_irq_data *irq_data;
+
+ irq = of_irq_get_byname(node, irq_name);
+ if (irq < 0) {
+ pr_err("Couldn't get irq %s byname\n", irq_name);
+ return irq;
+ }
+
+ irq_index = smb138x_get_irq_index_byname(irq_name);
+ if (irq_index < 0) {
+ pr_err("%s is not a defined irq\n", irq_name);
+ return irq_index;
+ }
+
+ if (!smb138x_irqs[irq_index].handler)
+ return 0;
+
+ irq_data = devm_kzalloc(chg->dev, sizeof(*irq_data), GFP_KERNEL);
+ if (!irq_data)
+ return -ENOMEM;
+
+ irq_data->parent_data = chip;
+ irq_data->name = irq_name;
+ irq_data->storm_data = smb138x_irqs[irq_index].storm_data;
+ mutex_init(&irq_data->storm_data.storm_lock);
+
+ rc = devm_request_threaded_irq(chg->dev, irq, NULL,
+ smb138x_irqs[irq_index].handler,
+ IRQF_ONESHOT, irq_name, irq_data);
+ if (rc < 0) {
+ pr_err("Couldn't request irq %d\n", irq);
+ return rc;
+ }
+
+ if (smb138x_irqs[irq_index].wake)
+ enable_irq_wake(irq);
+
+ return rc;
+}
+
+static int smb138x_request_interrupts(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct device_node *node = chg->dev->of_node;
+ struct device_node *child;
+ int rc = 0;
+ const char *name;
+ struct property *prop;
+
+ for_each_available_child_of_node(node, child) {
+ of_property_for_each_string(child, "interrupt-names",
+ prop, name) {
+ rc = smb138x_request_interrupt(chip, child, name);
+ if (rc < 0) {
+ pr_err("Couldn't request interrupt %s rc=%d\n",
+ name, rc);
+ return rc;
+ }
+ }
+ }
+
+ return rc;
+}
+
+/*********
+ * PROBE *
+ *********/
+
+static int smb138x_master_probe(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ chg->param = v1_params;
+
+ rc = smblib_init(chg);
+ if (rc < 0) {
+ pr_err("Couldn't initialize smblib rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_parse_dt(chip);
+ if (rc < 0) {
+ pr_err("Couldn't parse device tree rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_init_vbus_regulator(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize vbus regulator rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smb138x_init_vconn_regulator(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize vconn regulator rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smb138x_init_usb_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize usb psy rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_init_batt_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize batt psy rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_init_hw(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize hardware rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb138x_determine_initial_status(chip);
+ if (rc < 0) {
+ pr_err("Couldn't determine initial status rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smb138x_request_interrupts(chip);
+ if (rc < 0) {
+ pr_err("Couldn't request interrupts rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smb138x_slave_probe(struct smb138x *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ chg->param = v1_params;
+
+ rc = smblib_init(chg);
+ if (rc < 0) {
+ pr_err("Couldn't initialize smblib rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ chg->iio.temp_max_chan = iio_channel_get(chg->dev, "charger_temp_max");
+ if (IS_ERR(chg->iio.temp_max_chan)) {
+ rc = PTR_ERR(chg->iio.temp_max_chan);
+ goto cleanup;
+ }
+
+ chg->iio.connector_temp_thr1_chan = iio_channel_get(chg->dev,
+ "connector_temp_thr1");
+ if (IS_ERR(chg->iio.connector_temp_thr1_chan)) {
+ rc = PTR_ERR(chg->iio.connector_temp_thr1_chan);
+ goto cleanup;
+ }
+
+ chg->iio.connector_temp_thr2_chan = iio_channel_get(chg->dev,
+ "connector_temp_thr2");
+ if (IS_ERR(chg->iio.connector_temp_thr2_chan)) {
+ rc = PTR_ERR(chg->iio.connector_temp_thr2_chan);
+ goto cleanup;
+ }
+
+ chg->iio.connector_temp_thr3_chan = iio_channel_get(chg->dev,
+ "connector_temp_thr3");
+ if (IS_ERR(chg->iio.connector_temp_thr3_chan)) {
+ rc = PTR_ERR(chg->iio.connector_temp_thr3_chan);
+ goto cleanup;
+ }
+
+ rc = smb138x_parse_dt(chip);
+ if (rc < 0) {
+ pr_err("Couldn't parse device tree rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb138x_init_slave_hw(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize hardware rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ if ((chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+ || (chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT)) {
+ rc = smb138x_init_vbus_regulator(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize vbus regulator rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = smb138x_init_parallel_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize parallel psy rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb138x_determine_initial_slave_status(chip);
+ if (rc < 0) {
+ pr_err("Couldn't determine initial status rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb138x_request_interrupts(chip);
+ if (rc < 0) {
+ pr_err("Couldn't request interrupts rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ return rc;
+
+cleanup:
+ smblib_deinit(chg);
+ if (chip->parallel_psy)
+ power_supply_unregister(chip->parallel_psy);
+ if (chg->vbus_vreg && chg->vbus_vreg->rdev)
+ regulator_unregister(chg->vbus_vreg->rdev);
+ return rc;
+}
+
+static const struct of_device_id match_table[] = {
+ {
+ .compatible = "qcom,smb138x-charger",
+ .data = (void *) PARALLEL_MASTER
+ },
+ {
+ .compatible = "qcom,smb138x-parallel-slave",
+ .data = (void *) PARALLEL_SLAVE
+ },
+ { },
+};
+
+static int smb138x_probe(struct platform_device *pdev)
+{
+ struct smb138x *chip;
+ const struct of_device_id *id;
+ int rc = 0;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->chg.dev = &pdev->dev;
+ chip->chg.debug_mask = &__debug_mask;
+ chip->chg.irq_info = smb138x_irqs;
+ chip->chg.name = "SMB";
+
+ chip->chg.regmap = dev_get_regmap(chip->chg.dev->parent, NULL);
+ if (!chip->chg.regmap) {
+ pr_err("parent regmap is missing\n");
+ return -EINVAL;
+ }
+
+ id = of_match_device(of_match_ptr(match_table), chip->chg.dev);
+ if (!id) {
+ pr_err("Couldn't find a matching device\n");
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, chip);
+
+ rc = smb138x_setup_wa_flags(chip);
+ if (rc < 0) {
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't setup wa flags rc = %d\n", rc);
+ return rc;
+ }
+
+ chip->chg.mode = (enum smb_mode) id->data;
+ switch (chip->chg.mode) {
+ case PARALLEL_MASTER:
+ rc = smb138x_master_probe(chip);
+ break;
+ case PARALLEL_SLAVE:
+ rc = smb138x_slave_probe(chip);
+ break;
+ default:
+ pr_err("Couldn't find a matching mode %d\n", chip->chg.mode);
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ if (rc < 0) {
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't probe SMB138X rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ pr_info("SMB138X probed successfully mode=%d\n", chip->chg.mode);
+ return rc;
+
+cleanup:
+ platform_set_drvdata(pdev, NULL);
+ return rc;
+}
+
+static int smb138x_remove(struct platform_device *pdev)
+{
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static void smb138x_shutdown(struct platform_device *pdev)
+{
+ struct smb138x *chip = platform_get_drvdata(pdev);
+ struct smb_charger *chg = &chip->chg;
+ int rc;
+
+ /* Suspend charging */
+ rc = smb138x_set_parallel_suspend(chip, true);
+ if (rc < 0)
+ pr_err("Couldn't suspend charging rc=%d\n", rc);
+
+ /* Disable OTG */
+ rc = smblib_masked_write(chg, CMD_OTG_REG, OTG_EN_BIT, 0);
+ if (rc < 0)
+ pr_err("Couldn't disable OTG rc=%d\n", rc);
+
+}
+
+static struct platform_driver smb138x_driver = {
+ .driver = {
+ .name = "qcom,smb138x-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = match_table,
+ },
+ .probe = smb138x_probe,
+ .remove = smb138x_remove,
+ .shutdown = smb138x_shutdown,
+};
+module_platform_driver(smb138x_driver);
+
+MODULE_DESCRIPTION("QPNP SMB138X Charger Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/qcom/step-chg-jeita.c b/drivers/power/supply/qcom/step-chg-jeita.c
new file mode 100644
index 000000000000..5b41a456c6db
--- /dev/null
+++ b/drivers/power/supply/qcom/step-chg-jeita.c
@@ -0,0 +1,498 @@
+/* Copyright (c) 2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+#define pr_fmt(fmt) "QCOM-STEPCHG: %s: " fmt, __func__
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/pmic-voter.h>
+#include "step-chg-jeita.h"
+
+#define MAX_STEP_CHG_ENTRIES 8
+#define STEP_CHG_VOTER "STEP_CHG_VOTER"
+#define JEITA_VOTER "JEITA_VOTER"
+
+#define is_between(left, right, value) \
+ (((left) >= (right) && (left) >= (value) \
+ && (value) >= (right)) \
+ || ((left) <= (right) && (left) <= (value) \
+ && (value) <= (right)))
+
+struct range_data {
+ u32 low_threshold;
+ u32 high_threshold;
+ u32 value;
+};
+
+struct step_chg_cfg {
+ u32 psy_prop;
+ char *prop_name;
+ int hysteresis;
+ struct range_data fcc_cfg[MAX_STEP_CHG_ENTRIES];
+};
+
+struct jeita_fcc_cfg {
+ u32 psy_prop;
+ char *prop_name;
+ int hysteresis;
+ struct range_data fcc_cfg[MAX_STEP_CHG_ENTRIES];
+};
+
+struct jeita_fv_cfg {
+ u32 psy_prop;
+ char *prop_name;
+ int hysteresis;
+ struct range_data fv_cfg[MAX_STEP_CHG_ENTRIES];
+};
+
+struct step_chg_info {
+ ktime_t step_last_update_time;
+ ktime_t jeita_last_update_time;
+ bool step_chg_enable;
+ bool sw_jeita_enable;
+ int jeita_fcc_index;
+ int jeita_fv_index;
+ int step_index;
+
+ struct votable *fcc_votable;
+ struct votable *fv_votable;
+ struct wakeup_source *step_chg_ws;
+ struct power_supply *batt_psy;
+ struct delayed_work status_change_work;
+ struct notifier_block nb;
+};
+
+static struct step_chg_info *the_chip;
+
+#define STEP_CHG_HYSTERISIS_DELAY_US 5000000 /* 5 secs */
+
+/*
+ * Step Charging Configuration
+ * Update the table based on the battery profile
+ * Supports VBATT and SOC based source
+ * range data must be in increasing ranges and shouldn't overlap
+ */
+static struct step_chg_cfg step_chg_config = {
+ .psy_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ .prop_name = "VBATT",
+ .hysteresis = 100000, /* 100mV */
+ .fcc_cfg = {
+ /* VBAT_LOW VBAT_HIGH FCC */
+ {3600000, 4000000, 3000000},
+ {4001000, 4200000, 2800000},
+ {4201000, 4400000, 2000000},
+ },
+ /*
+ * SOC STEP-CHG configuration example.
+ *
+ * .psy_prop = POWER_SUPPLY_PROP_CAPACITY,
+ * .prop_name = "SOC",
+ * .fcc_cfg = {
+ * //SOC_LOW SOC_HIGH FCC
+ * {20, 70, 3000000},
+ * {70, 90, 2750000},
+ * {90, 100, 2500000},
+ * },
+ */
+};
+
+/*
+ * Jeita Charging Configuration
+ * Update the table based on the battery profile
+ * Please ensure that the TEMP ranges are programmed in the hw so that
+ * an interrupt is issued and a consequent psy changed will cause us to
+ * react immediately.
+ * range data must be in increasing ranges and shouldn't overlap.
+ * Gaps are okay
+ */
+static struct jeita_fcc_cfg jeita_fcc_config = {
+ .psy_prop = POWER_SUPPLY_PROP_TEMP,
+ .prop_name = "BATT_TEMP",
+ .hysteresis = 10, /* 1degC hysteresis */
+ .fcc_cfg = {
+ /* TEMP_LOW TEMP_HIGH FCC */
+ {0, 100, 600000},
+ {101, 200, 2000000},
+ {201, 450, 3000000},
+ {451, 550, 600000},
+ },
+};
+
+static struct jeita_fv_cfg jeita_fv_config = {
+ .psy_prop = POWER_SUPPLY_PROP_TEMP,
+ .prop_name = "BATT_TEMP",
+ .hysteresis = 10, /* 1degC hysteresis */
+ .fv_cfg = {
+ /* TEMP_LOW TEMP_HIGH FCC */
+ {0, 100, 4200000},
+ {101, 450, 4400000},
+ {451, 550, 4200000},
+ },
+};
+
+static bool is_batt_available(struct step_chg_info *chip)
+{
+ if (!chip->batt_psy)
+ chip->batt_psy = power_supply_get_by_name("battery");
+
+ if (!chip->batt_psy)
+ return false;
+
+ return true;
+}
+
+static int get_val(struct range_data *range, int hysteresis, int current_index,
+ int threshold,
+ int *new_index, int *val)
+{
+ int i;
+
+ *new_index = -EINVAL;
+ /* first find the matching index without hysteresis */
+ for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++)
+ if (is_between(range[i].low_threshold,
+ range[i].high_threshold, threshold)) {
+ *new_index = i;
+ *val = range[i].value;
+ }
+
+ /* if nothing was found, return -ENODATA */
+ if (*new_index == -EINVAL)
+ return -ENODATA;
+ /*
+ * If we don't have a current_index return this
+ * newfound value. There is no hysterisis from out of range
+ * to in range transition
+ */
+ if (current_index == -EINVAL)
+ return 0;
+
+ /*
+ * Check for hysteresis if it in the neighbourhood
+ * of our current index.
+ */
+ if (*new_index == current_index + 1) {
+ if (threshold < range[*new_index].low_threshold + hysteresis) {
+ /*
+ * Stay in the current index, threshold is not higher
+ * by hysteresis amount
+ */
+ *new_index = current_index;
+ *val = range[current_index].value;
+ }
+ } else if (*new_index == current_index - 1) {
+ if (threshold > range[*new_index].high_threshold - hysteresis) {
+ /*
+ * stay in the current index, threshold is not lower
+ * by hysteresis amount
+ */
+ *new_index = current_index;
+ *val = range[current_index].value;
+ }
+ }
+ return 0;
+}
+
+static int handle_step_chg_config(struct step_chg_info *chip)
+{
+ union power_supply_propval pval = {0, };
+ int rc = 0, fcc_ua = 0;
+ u64 elapsed_us;
+
+ elapsed_us = ktime_us_delta(ktime_get(), chip->step_last_update_time);
+ if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US)
+ goto reschedule;
+
+ rc = power_supply_get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED, &pval);
+ if (rc < 0)
+ chip->step_chg_enable = 0;
+ else
+ chip->step_chg_enable = pval.intval;
+
+ if (!chip->step_chg_enable) {
+ if (chip->fcc_votable)
+ vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0);
+ goto update_time;
+ }
+
+ rc = power_supply_get_property(chip->batt_psy,
+ step_chg_config.psy_prop, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't read %s property rc=%d\n",
+ step_chg_config.prop_name, rc);
+ return rc;
+ }
+
+ rc = get_val(step_chg_config.fcc_cfg, step_chg_config.hysteresis,
+ chip->step_index,
+ pval.intval,
+ &chip->step_index,
+ &fcc_ua);
+ if (rc < 0) {
+ /* remove the vote if no step-based fcc is found */
+ if (chip->fcc_votable)
+ vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0);
+ goto update_time;
+ }
+
+ if (!chip->fcc_votable)
+ chip->fcc_votable = find_votable("FCC");
+ if (!chip->fcc_votable)
+ return -EINVAL;
+
+ vote(chip->fcc_votable, STEP_CHG_VOTER, true, fcc_ua);
+
+ pr_debug("%s = %d Step-FCC = %duA\n",
+ step_chg_config.prop_name, pval.intval, fcc_ua);
+
+update_time:
+ chip->step_last_update_time = ktime_get();
+ return 0;
+
+reschedule:
+ /* reschedule 1000uS after the remaining time */
+ return (STEP_CHG_HYSTERISIS_DELAY_US - elapsed_us + 1000);
+}
+
+static int handle_jeita(struct step_chg_info *chip)
+{
+ union power_supply_propval pval = {0, };
+ int rc = 0, fcc_ua = 0, fv_uv = 0;
+ u64 elapsed_us;
+
+ rc = power_supply_get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_SW_JEITA_ENABLED, &pval);
+ if (rc < 0)
+ chip->sw_jeita_enable = 0;
+ else
+ chip->sw_jeita_enable = pval.intval;
+
+ if (!chip->sw_jeita_enable) {
+ if (chip->fcc_votable)
+ vote(chip->fcc_votable, JEITA_VOTER, false, 0);
+ if (chip->fv_votable)
+ vote(chip->fv_votable, JEITA_VOTER, false, 0);
+ return 0;
+ }
+
+ elapsed_us = ktime_us_delta(ktime_get(), chip->jeita_last_update_time);
+ if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US)
+ goto reschedule;
+
+ rc = power_supply_get_property(chip->batt_psy,
+ jeita_fcc_config.psy_prop, &pval);
+ if (rc < 0) {
+ pr_err("Couldn't read %s property rc=%d\n",
+ step_chg_config.prop_name, rc);
+ return rc;
+ }
+
+ rc = get_val(jeita_fcc_config.fcc_cfg, jeita_fcc_config.hysteresis,
+ chip->jeita_fcc_index,
+ pval.intval,
+ &chip->jeita_fcc_index,
+ &fcc_ua);
+ if (rc < 0) {
+ /* remove the vote if no step-based fcc is found */
+ if (chip->fcc_votable)
+ vote(chip->fcc_votable, JEITA_VOTER, false, 0);
+ goto update_time;
+ }
+
+ if (!chip->fcc_votable)
+ chip->fcc_votable = find_votable("FCC");
+ if (!chip->fcc_votable)
+ /* changing FCC is a must */
+ return -EINVAL;
+
+ vote(chip->fcc_votable, JEITA_VOTER, true, fcc_ua);
+
+ rc = get_val(jeita_fv_config.fv_cfg, jeita_fv_config.hysteresis,
+ chip->jeita_fv_index,
+ pval.intval,
+ &chip->jeita_fv_index,
+ &fv_uv);
+ if (rc < 0) {
+ /* remove the vote if no step-based fcc is found */
+ if (chip->fv_votable)
+ vote(chip->fv_votable, JEITA_VOTER, false, 0);
+ goto update_time;
+ }
+
+ chip->fv_votable = find_votable("FV");
+ if (!chip->fv_votable)
+ goto update_time;
+
+ vote(chip->fv_votable, JEITA_VOTER, true, fv_uv);
+
+ pr_debug("%s = %d FCC = %duA FV = %duV\n",
+ step_chg_config.prop_name, pval.intval, fcc_ua, fv_uv);
+
+update_time:
+ chip->jeita_last_update_time = ktime_get();
+ return 0;
+
+reschedule:
+ /* reschedule 1000uS after the remaining time */
+ return (STEP_CHG_HYSTERISIS_DELAY_US - elapsed_us + 1000);
+}
+
+static void status_change_work(struct work_struct *work)
+{
+ struct step_chg_info *chip = container_of(work,
+ struct step_chg_info, status_change_work.work);
+ int rc = 0;
+ int reschedule_us;
+ int reschedule_jeita_work_us = 0;
+ int reschedule_step_work_us = 0;
+
+ if (!is_batt_available(chip))
+ return;
+
+ /* skip elapsed_us debounce for handling battery temperature */
+ rc = handle_jeita(chip);
+ if (rc > 0)
+ reschedule_jeita_work_us = rc;
+ else if (rc < 0)
+ pr_err("Couldn't handle sw jeita rc = %d\n", rc);
+
+ rc = handle_step_chg_config(chip);
+ if (rc > 0)
+ reschedule_step_work_us = rc;
+ if (rc < 0)
+ pr_err("Couldn't handle step rc = %d\n", rc);
+
+ reschedule_us = min(reschedule_jeita_work_us, reschedule_step_work_us);
+ if (reschedule_us == 0)
+ __pm_relax(chip->step_chg_ws);
+ else
+ schedule_delayed_work(&chip->status_change_work,
+ usecs_to_jiffies(reschedule_us));
+}
+
+static int step_chg_notifier_call(struct notifier_block *nb,
+ unsigned long ev, void *v)
+{
+ struct power_supply *psy = v;
+ struct step_chg_info *chip = container_of(nb, struct step_chg_info, nb);
+
+ if (ev != PSY_EVENT_PROP_CHANGED)
+ return NOTIFY_OK;
+
+ if ((strcmp(psy->desc->name, "battery") == 0)) {
+ __pm_stay_awake(chip->step_chg_ws);
+ schedule_delayed_work(&chip->status_change_work, 0);
+ }
+
+ return NOTIFY_OK;
+}
+
+static int step_chg_register_notifier(struct step_chg_info *chip)
+{
+ int rc;
+
+ chip->nb.notifier_call = step_chg_notifier_call;
+ rc = power_supply_reg_notifier(&chip->nb);
+ if (rc < 0) {
+ pr_err("Couldn't register psy notifier rc = %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+int qcom_step_chg_init(bool step_chg_enable, bool sw_jeita_enable)
+{
+ int rc;
+ struct step_chg_info *chip;
+
+ if (the_chip) {
+ pr_err("Already initialized\n");
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->step_chg_ws = wakeup_source_register("qcom-step-chg");
+ if (!chip->step_chg_ws) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ chip->step_chg_enable = step_chg_enable;
+ chip->sw_jeita_enable = sw_jeita_enable;
+
+ chip->step_index = -EINVAL;
+ chip->jeita_fcc_index = -EINVAL;
+ chip->jeita_fv_index = -EINVAL;
+
+ if (step_chg_enable && (!step_chg_config.psy_prop ||
+ !step_chg_config.prop_name)) {
+ /* fail if step-chg configuration is invalid */
+ pr_err("Step-chg configuration not defined - fail\n");
+ return -ENODATA;
+ }
+
+ if (sw_jeita_enable && (!jeita_fcc_config.psy_prop ||
+ !jeita_fcc_config.prop_name)) {
+ /* fail if step-chg configuration is invalid */
+ pr_err("Jeita TEMP configuration not defined - fail\n");
+ return -ENODATA;
+ }
+
+ if (sw_jeita_enable && (!jeita_fv_config.psy_prop ||
+ !jeita_fv_config.prop_name)) {
+ /* fail if step-chg configuration is invalid */
+ pr_err("Jeita TEMP configuration not defined - fail\n");
+ return -ENODATA;
+ }
+
+ INIT_DELAYED_WORK(&chip->status_change_work, status_change_work);
+
+ rc = step_chg_register_notifier(chip);
+ if (rc < 0) {
+ pr_err("Couldn't register psy notifier rc = %d\n", rc);
+ goto release_wakeup_source;
+ }
+
+ the_chip = chip;
+
+ if (step_chg_enable)
+ pr_info("Step charging enabled. Using %s source\n",
+ step_chg_config.prop_name);
+
+ return 0;
+
+release_wakeup_source:
+ wakeup_source_unregister(chip->step_chg_ws);
+cleanup:
+ kfree(chip);
+ return rc;
+}
+
+void qcom_step_chg_deinit(void)
+{
+ struct step_chg_info *chip = the_chip;
+
+ if (!chip)
+ return;
+
+ cancel_delayed_work_sync(&chip->status_change_work);
+ power_supply_unreg_notifier(&chip->nb);
+ wakeup_source_unregister(chip->step_chg_ws);
+ the_chip = NULL;
+ kfree(chip);
+}
diff --git a/drivers/power/supply/qcom/step-chg-jeita.h b/drivers/power/supply/qcom/step-chg-jeita.h
new file mode 100644
index 000000000000..53335c3c2c70
--- /dev/null
+++ b/drivers/power/supply/qcom/step-chg-jeita.h
@@ -0,0 +1,17 @@
+/* Copyright (c) 2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 __STEP_CHG_H__
+#define __STEP_CHG_H__
+int qcom_step_chg_init(bool, bool);
+void qcom_step_chg_deinit(void);
+#endif /* __STEP_CHG_H__ */
diff --git a/drivers/power/supply/qcom/storm-watch.c b/drivers/power/supply/qcom/storm-watch.c
new file mode 100644
index 000000000000..21ac669f2ec9
--- /dev/null
+++ b/drivers/power/supply/qcom/storm-watch.c
@@ -0,0 +1,76 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include "storm-watch.h"
+
+/**
+ * is_storming(): Check if an event is storming
+ *
+ * @data: Data for tracking an event storm
+ *
+ * The return value will be true if a storm has been detected and
+ * false if a storm was not detected.
+ */
+bool is_storming(struct storm_watch *data)
+{
+ ktime_t curr_kt, delta_kt;
+ bool is_storming = false;
+
+ if (!data)
+ return false;
+
+ if (!data->enabled)
+ return false;
+
+ /* max storm count must be greater than 0 */
+ if (data->max_storm_count <= 0)
+ return false;
+
+ /* the period threshold must be greater than 0ms */
+ if (data->storm_period_ms <= 0)
+ return false;
+
+ mutex_lock(&data->storm_lock);
+ curr_kt = ktime_get_boottime();
+ delta_kt = ktime_sub(curr_kt, data->last_kt);
+
+ if (ktime_to_ms(delta_kt) < data->storm_period_ms)
+ data->storm_count++;
+ else
+ data->storm_count = 0;
+
+ if (data->storm_count > data->max_storm_count) {
+ is_storming = true;
+ data->storm_count = 0;
+ }
+
+ data->last_kt = curr_kt;
+ mutex_unlock(&data->storm_lock);
+ return is_storming;
+}
+
+void reset_storm_count(struct storm_watch *data)
+{
+ mutex_lock(&data->storm_lock);
+ data->storm_count = 0;
+ mutex_unlock(&data->storm_lock);
+}
+
+void update_storm_count(struct storm_watch *data, int max_count)
+{
+ if (!data)
+ return;
+
+ mutex_lock(&data->storm_lock);
+ data->max_storm_count = max_count;
+ mutex_unlock(&data->storm_lock);
+}
diff --git a/drivers/power/supply/qcom/storm-watch.h b/drivers/power/supply/qcom/storm-watch.h
new file mode 100644
index 000000000000..5275d73613d4
--- /dev/null
+++ b/drivers/power/supply/qcom/storm-watch.h
@@ -0,0 +1,41 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 __STORM_WATCH_H
+#define __STORM_WATCH_H
+#include <linux/ktime.h>
+#include <linux/mutex.h>
+
+/**
+ * Data used to track an event storm.
+ *
+ * @storm_period_ms: The maximum time interval between two events. If this limit
+ * is exceeded then the event chain will be broken and removed
+ * from consideration for a storm.
+ * @max_storm_count: The number of chained events required to trigger a storm.
+ * @storm_count: The current number of chained events.
+ * @last_kt: Kernel time of the last event seen.
+ * @storm_lock: Mutex lock to protect storm_watch data.
+ */
+struct storm_watch {
+ bool enabled;
+ int storm_period_ms;
+ int max_storm_count;
+ int storm_count;
+ ktime_t last_kt;
+ struct mutex storm_lock;
+};
+
+bool is_storming(struct storm_watch *data);
+void reset_storm_count(struct storm_watch *data);
+void update_storm_count(struct storm_watch *data, int max_count);
+#endif