diff options
Diffstat (limited to 'drivers/power')
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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, + ¶llel_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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, + ¶llel_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, + ¶llel_psy_desc, + ¶llel_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 |
