From 6c2bbf291417e1c23abbbc363341555f824f437d Mon Sep 17 00:00:00 2001 From: yanyh2 Date: Mon, 24 Feb 2020 12:18:25 +0800 Subject: [PATCH] dlkm: charger driver for Moto common features As a common charger driver mmi_charger inherits Motorola legacy battery charging features, including Demo charging, Step charging, Flash Fast charging, and common features for factory test support. More importantly, it provides a cross-platform solution to support various battery charging design, not only Qualcomm SoC but also Samsung, MTK, even remote system over network, and especially suite multiple battery packs and multiple charger ICs in one system. To achieve this goal, besides mmi_charger driver, there need a low level charger driver for each battery charging solution(a couple of charger IC & battery pack) to register to mmi_charger driver with implementation of required interfaces depending on their own HW design. With mmi_charger driver, Motorola common features have been stripped from the HW dependent charger drivers(older Motorola charger drivers), in which all the common features have to be implemented by each driver of its own. So a low level charger driver can focus on HW specific features and function development. Motorola charger driver architecture: ---------------------------------------------------------------------- Userspace Common Interfaces for Charging Features and Factory Test ---------------------------------------------------------------------- Kernel mmi_charger (Motorola demo, step, flash-fast charging, / | \ factory and debugging interfaces) / | \ qti_glink_charger x_charger N_charger (basic charging functions HW | | | specific factory debugging interfaces) | | | ------------glink----i2c------spmi------------------------------------ | | | | | | ADSP i2c chip PMIC Benefit from this new Motorola charger driver design: 1. Easy upgrade, migration, and maintenance, as common driver is cross-platform and HW independent. 2. Reduce develop workload, as developers can pay more energy and efforts on HW or platform specific business logic. 3. Easy to support complex battery charging solution, as it can support N couples of charger IC & battery pack in one system at the same time theoretically. Change-Id: Ie44a22a2c65ea01fd3930879aeb9aae1c44341e0 Signed-off-by: yanyh2 Reviewed-on: https://gerrit.mot.com/1502333 SME-Granted: SME Approvals Granted SLTApproved: Slta Waiver Tested-by: Jira Key Reviewed-by: Wentao Xu Reviewed-by: Jianqi Yang Submit-Approved: Jira Key --- drivers/power/mmi_charger/Android.mk | 11 + drivers/power/mmi_charger/Kbuild | 5 + drivers/power/mmi_charger/Makefile | 10 + drivers/power/mmi_charger/README | 544 ++++++ drivers/power/mmi_charger/mmi_charger.c | 2239 +++++++++++++++++++++++ drivers/power/mmi_charger/mmi_charger.h | 117 ++ 6 files changed, 2926 insertions(+) create mode 100755 drivers/power/mmi_charger/Android.mk create mode 100755 drivers/power/mmi_charger/Kbuild create mode 100755 drivers/power/mmi_charger/Makefile create mode 100644 drivers/power/mmi_charger/README create mode 100644 drivers/power/mmi_charger/mmi_charger.c create mode 100644 drivers/power/mmi_charger/mmi_charger.h diff --git a/drivers/power/mmi_charger/Android.mk b/drivers/power/mmi_charger/Android.mk new file mode 100755 index 000000000000..bb3f6314254c --- /dev/null +++ b/drivers/power/mmi_charger/Android.mk @@ -0,0 +1,11 @@ +DLKM_DIR := motorola/kernel/modules +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := mmi_charger.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +KBUILD_OPTIONS_GKI += GKI_OBJ_MODULE_DIR=gki +include $(DLKM_DIR)/AndroidKernelModule.mk + +include $(call first-makefiles-under,$(LOCAL_PATH)) diff --git a/drivers/power/mmi_charger/Kbuild b/drivers/power/mmi_charger/Kbuild new file mode 100755 index 000000000000..420e219dffd4 --- /dev/null +++ b/drivers/power/mmi_charger/Kbuild @@ -0,0 +1,5 @@ +# add -Wall to try to catch everything we can. +EXTRA_CFLAGS += -Wall +EXTRA_CFLAGS += -I$(ANDROID_BUILD_TOP)/motorola/kernel/modules/drivers/power/mmi_charger + +obj-m += mmi_charger.o diff --git a/drivers/power/mmi_charger/Makefile b/drivers/power/mmi_charger/Makefile new file mode 100755 index 000000000000..34b09dd815a7 --- /dev/null +++ b/drivers/power/mmi_charger/Makefile @@ -0,0 +1,10 @@ +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) modules $(KBUILD_OPTIONS) + +modules_install: + $(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(shell pwd) modules_install + +clean: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean \ No newline at end of file diff --git a/drivers/power/mmi_charger/README b/drivers/power/mmi_charger/README new file mode 100644 index 000000000000..d45eb86cdbd2 --- /dev/null +++ b/drivers/power/mmi_charger/README @@ -0,0 +1,544 @@ +Motorola Mobility. MMI Charger Specific Bindings + +As a common charger driver mmi_charger inherits Motorola legacy battery +charging features, including Demo charging, Step charging, Flash Fast +charging, and common features for factory test support. + +More importantly, it provides a cross-platform solution to support +various battery charging design, not only Qualcomm SoC but also Samsung, +MTK, even remote system over network, and especially suite multiple +battery packs and multiple charger ICs in one system. + +To achieve this goal, besides mmi_charger driver, there need a low level +charger driver for each battery charging solution(a couple of charger IC +& battery pack) to register to mmi_charger driver with implementation of +required interfaces depending on their own HW design. + +With mmi_charger driver, Motorola common features have been stripped +from the HW dependent charger drivers(older Motorola charger drivers), +in which all the common features have to be implemented by each driver +of its own. So a low level charger driver can focus on HW specific +features and function development. + +Motorola charger driver architecture: +----------------------------------------------------------------------------------------------------------- +Userspace Common Interfaces for Charging Features and Factory Test +----------------------------------------------------------------------------------------------------------- +Kernel mmi_charger (Motorola demo, step, flash-fast charging, + / | \ factory and debugging interfaces) + / | \ + qti_glink_charger x_charger N_charger (basic charging functions HW + | | | specific factory debugging interfaces) + | | | +------------glink----i2c------spmi-------------------------------------------------------------------------- + | | | + | | | + ADSP i2c chip PMIC + +Benefit from this new Motorola charger driver design: +1. Easy upgrade, migration, and maintenance, as common driver is + cross-platform and HW independent. +2. Reduce develop workload, as developers can pay more energy and + efforts on HW or platform specific business logic. +3. Easy to support complex battery charging solution, as it can support + N couples of charger IC & battery pack in one system at the same time + theoretically. + +Motorola common charger driver is built as DLKM: +Module Name: mmi_charger.ko +Log Tag: mmi_charger +IPC Log: /d/ipc_logging/charger/log +Support Platform: platform independent + +Motorola low level charger driver is built as DLKM: +Module Name: qti_glink_charger.ko +Log Tag: qti_glink_charger +IPC Log: /d/ipc_logging/charger/log +Support Platform: SM8350 or similar architecture(adsp and HLOS connection over glink) + +devicetree properties description: +mmi_charger: +- mmi,enable-charging-limit + Usage: optional + Value Type: + Definition: Boolean flag to set capacity limits to battery when plugged on +a charger and is executing a Factory Image. It will use upper and lower +capacities defined by the Device Tree. + +- mmi,enable-factory-poweroff + Usage: optional + Value Type: + Definition: Boolean flag which enables Factory Kill when executing under a +Factory Image. By default, Factory Kill is a feature only in Factory Mode, +which turns the device off when charger is removed. + +- mmi,upper-limit-capacity + Usage: optional + Value Type: + Definition: Limits minimum battery SoC (in percentage). In other words, starts +charging when it reaches this value. Especially useful for factory procedures, +which needs to limit SoC. Requires qcom,enable-charging-limit. If not defined, +defaults to 100. + +- mmi,lower-limit-capacity + Usage: optional + Value Type: + Definition: Limits maximum battery SoC (in percentage). In other words, stop +charging when it reaches this value. Especially useful for factory procedures, +which needs to limit SoC. Requires qcom,enable-charging-limit. If not defined, +defaults to 0. + +- mmi,dcp-power-max + Usage: optional + Value Type: + Definition: Set this value to allow power in mW draw from DC charger, used + when a normal DCP charger is detected. If not set, 7.5W power is allowed. + +- mmi,hvdcp-power-max + Usage: optional + Value Type: + Definition: Set this value to allow power in mW draw from QC3.0 charger, used + when a QC3.0 charger is detected. If not set, 15W power is allowed. + +- mmi,heartbeat-interval +Usage: optional +Value Type: +Definition: Set the heartbeat interval of in ms, 60s by default if not set. + +Example: +mmi_charger: mmi,charger { + compatible = "mmi,mmi-charger"; + mmi,enable-charging-limit; + mmi,enable-factory-poweroff; + mmi,upper-limit-capacity = <75>; + mmi,lower-limit-capacity = <60>; + mmi,heartbeat-interval = <60000>; + mmi,dcp-power-max = <10000>; + mmi,hvdcp-power-max = <200000>; +}; + +qti_glink_charger: +- mmi,mmi-temp-zones + Usage: optional + Value Type: Array of tuples of + Definition: Unlimited array of arrays. Each internal array needs to have a set +of 4 bytes, each one representing this sequence: Temperature (in degC), Step +"Normalized" Voltage (in millivolts), Full Rate "FCC" Current (in milliamps), +and Taper Rate "FCC Normalized" Current (in milliamps). These values are +related to charging limitations based on temperature specifications. Note that +setting a voltage to '0' means that it doesn't matter the battery voltage on +that defined temperature range to perform necessary tapering. To understand how +Those values are used, please check the "IEEE Charging Compliance" section below. +In order to support charging current limit based on battery voltage in the same +temperature zone, one zone allows to have multiple lines with ascending sequence +by float voltage. First find the temperature zone, and then find the right one +among the same zone according to current battery voltage. + +- mmi,df-serialnum + Usage: optional + Value Type: + Definition: The default battery serial number for charging profile selection +and battery identification. + +- mmi,chrg-iterm-ma + Usage: optional + Value Type: + Definition: It will define taper current on battery (in milliamps). If charging +current becomes below this value, it indicates FULL charging. If not defined, +defaults to 300. + +- mmi,fg-iterm-ma + Usage: optional + Value Type: + Definition: It will define charging current on battery (in milliamps) as the +threshold for the fuel gauge to decide reporting FULL capacity. If not defined, +defaults to 350. + +- mmi,vfloat-comp-uv + Usage: optional + Value Type: + Definition: This is a tolerance, in uV, for battery floating voltage when +charging is being limited. Such situation can happen when either battery is +close to full or when there's thermal mitigation on charging. If not set, +defaults to 0. + +- mmi,max-fv-mv + Usage: optional + Value Type: + Definition: It will define the maximum battery charging voltage (in milliamps). +This value is used as the max float voltage for battery charging. If not +defined, defaults to 4400. + +- mmi,max-fcc-ma + Usage: optional + Value Type: + Definition: It will define maximum charging current on battery (in milliamps) +If not defined, defaults to 4000. + +- mmi,demo-fv-mv + Usage: optional + Value Type: + Definition: It will define the maximum battery charging voltage (in milliamps) +in demo mode. This value is used as the max float voltage for battery charging in +demo mode. If not defined, defaults to 4000. + +- mmi,mmi-ffc-zones + Usage: optional + Value Type: Array of tuples of + Definition: Unlimited array of arrays. Each internal array needs to have a set +of 4 data members, each one representing this sequence: Temperature (in degC), +maximum Float Voltage (in millivolts), charging terminated current(in milliamps), +and Fuel Gauge FULL terminated current (in milliamps). These values are +related to charging limitations based on temperature specifications when the +battery supports Flash Fast Charging. + +- mmi,profile-data-block-size + Usage: optional + Value Type: + Definition: It will define the profile data member count each block line +in the profile data array. If not defined, defaults to 4. + +- mmi,profile-data + Usage: optional + Value Type: Array of tuples of + Definition: Unlimited array of arrays. Each internal array needs to have a set +with member count specified by property "mmi,profile-data-block-size". The content +for each line is defined by the target user of the profile data. + +Example: +qti_glink_charger: mmi,qti-glink-charger { + compatible = "mmi,qti-glink-charger"; + mmi,df-serialnum = "unknown-sn"; + mmi,chrg-iterm-ma = <300>; + mmi,fg-iterm-ma = <350>; + mmi,max-fv-mv = <4400>; + mmi,max-fcc-ma = <4000>; + mmi,demo-fv-mv = <4000>; + mmi,vfloat-comp-uv = <20000>; + mmi,mmi-temp-zones = + <0 4200 880 0>, + <10 4200 2185 880>, + <15 0 2185 2185>, + <45 4000 3600 3600>, + <45 4250 3600 3000>, + <45 4350 3000 2000>, + <60 4200 2185 0>; + mmi,mmi-ffc-zones = + <0 4450 243 500>, + <23 4480 670 980>, + <35 4480 770 1045>, + <45 4480 840 1120>, + <60 4450 243 500>; + + mmi,profile-data-block-size = <4>; + mmi,profile-data = + <0 4200 880 0>, + <10 4200 2185 880>, + <15 0 2185 2185>, + <45 4000 3600 3600>, + <45 4250 3600 3000>, + <45 4350 3000 2000>, + <60 4200 2185 0>; +}; + +========================== + IEEE Charging Compliance +========================== + +In order to be compliant to IEEE rules, there are a series of limitations on +how much a battery can be charged, based on temperature. That will limit how +much current can be drawn, or maximum battery voltage to be achieved during +charging. + +Current is always tapered when voltage achieves maximum voltage specification +(which is generally 4.4V, but may have a different value, depending a product +battery specs). It is tapered to some small value, possibly around 100mA, +which is based on product specifications and capacity testing, defined by +EE (Electrical Engineering) Team/Hardware. + +If temperature is below -20 degC or above 60 degC, it should automatically +stop charging the device. + +If temperature is in between 15 and 45 deg C, it can achieve maximum charging +current, and such value varies across products. Generally, if it supports PD, +this will be a much higher current than non-PD products, which will also have +a much higher current than dual-battery products. Important to note that this +is a rule-of-thumb, so always check product specifications. + +If temperature is between 0 and 10 deg C, but battery voltage is above 4.20V, +current should be tapered to 1000mA. + +Finally, if temperature is in between -20 and 0 deg C, current should be +tapered to 1000mA. + +NOTE1: whenever dealing with IEEE certification, most documentation and +variable notation across the bootloader and kernel may refer to JEITA. This is a +convention on such standards and values to which temperature, voltage and +current levels are accepted in different scenarios for Li-ion batteries. So +JEITA can be seen as a general standard, but IEEE enforces it as well as +provide ways to certify a product according to it (is mandatory to certify +any commercial product that has a lithium-ion battery embedded or plugged to +it). + +NOTE2: taper current mostly refers to when battery is above maximum allowed +voltage on a specific temperature region, based on rules above. So be aware +that the rest of this document may refer as "taper" to a current that is +limiting the maximum value, but before achieving a stage in which current +is limited to something around 100mA (so an intermediate stage). Also +"taper voltage" will refer to a voltage level in which this mentioned +"taper" current (intermediate current level) is triggered. + + +========================================= + How to define mmi,mmi-temp-zones array +========================================= + +When defining qcom,mmi-temp-zones array, it is important to always have in +mind the IEEE rules described above, as well the battery specifications. + +Each tuple in that array represents a different value, as shown here: + + +"temperature" defines maximum temperature value. So setting "35" means a +definition to anything below 35 degC. + +"taper_volt" means maximum allowed battery voltage in that temperature +range. So setting "4200" means that tapering will happen once battery +is above 4.2V and is still charging. + +"max_cur_below_taper_volt" defines maximum allowed current value in case +the battery is below "taper_volt" value. So setting it to "700" means that +current won't go above 700mA, as long as battery is below "taper_volt" +voltage. + +"max_cur_above_taper_volt" defines maximum allowed current value in case +battery is above "taper_volt" value. So setting it to "0" means that it +won't charge in case the battery is above "taper_volt" voltage. + +Here's an example from Dubai: + mmi,mmi-temp-zones = + <0 4200 880 0>, + <10 4200 2185 880>, + <15 0 2185 2185>, + <45 4000 3600 3600>, + <45 4250 3600 3000>, + <45 4350 3000 2000>, + <60 4200 2185 0>; +The first line states "If temperature is below 0 degC, and the battery voltage +is below 4.2V, device can be charged up to 880mA. But if the battery voltage +is above 4.2V, device cannot be charged". + +The third line states "If temperature is below 15 degC, the battery can be charged +up to 2185mA, no matter the battery voltage". + +The fourth to sixth line states that there are three lines in the same zone with +different taper_volt by ascending sequence, the charging current is limited by +both temperature and battery voltage. + +================================================= + Understanding the IEEE algorithm implementation +================================================= + +Now that IEEE rules were understood, as well as how qcom,mmi-temp-zones is set, +just to stress out this driver implementation, the thermal charging limitation +implementation will be described. + +mmi_basic_charge_sm() or mmi_dual_charge_sm() are the functions responsible +to enforce such rules. In that way, before we check the implementation, here +are some conventions: + +STEP_NONE: no charger present +STEP_STOP: in zone hot or zone cold, which limits charging considerably +STEP_NORM: limits current to some value +STEP_MAX: allows maximum current value, when in between 0 and 45 degC +STEP_FULL: battery is almost or completely full, tapering to a small current +STEP_FLOAT: TODO + +Important to note that even when moving from one state to another, temperature +may change. Hence, taper current and voltage will no longer correspond to such +state, and so is always necessary to check on every state in which temperature +range device is. + +"batt_ma" refers to battery current (equivalent to current_now on battery +sysfs). +"taper_ma" is "fcc_norm_ma" in case is in STATE_MAX, which means battery is +above taper voltage. If in any other state, it will refer to "chrg_iterm", +which will depend on device specifications (4.4V batteries have 97mA as +current standards, and 4.45V batteries have 121mA as taper current). + +Now, let's check the algorithms for a bit: + +----------------------------------------------------- +current-step: STEP_NONE | STEP_STOP + if norm_mv AND (batt_mv + 50 >= norm_mv) // (1) + if fcc_norm_ma // (2) + -goto STEP_NORM // (2) + else + -goto STEP_STOP // (2) + else + -goto STEP_MAX // (1) + +(1) are we in a range which has "norm_mv" defined, and battery is above this +value? +This corresponds to most temperature ranges, except in between 15 and 45 degC. +If that's the case, allow charging up to maximum current defined in product +spec. + +(2) is "fcc_norm_ma" a value different from zero? +This happens if temperature is in range between 0 and 45 degC. Due to previous +consideration, it is only missing range 0 to 15 degC here. If that's the case, +limit maximum current. Otherwise, battery is above "norm_mv" (which probably +is defined as 4.2V), and at that voltage and temperature, limiting charging +considerably. + +----------------------------------------------------- +current-step: STEP_MAX + if !norm_mv // (1) + taper_cnt = 0 + if (batt_mv + 50) > max_fv_mv // (2) + -goto STEP_NORM + else + -goto STEP_MAX + else if (batt_mv + 50) < norm_mv // (1) + taper_cnt = 0 + -goto STEP_MAX + else if !fcc_norm_ma // (3) + -goto STEP_FLOAT + else if current_has_tapered // (4) + taper_cnt = 0 + if charge_halted + vote chg_dis_votable + -goto STEP_NORM + +(1) are we in a range which has "norm_mv" NOT defined, or below taper voltage? +This is only for the 15 to 45 degC region, which has no current tapering. + +(2) are we above taper voltage? +This checks whether taper voltage was achieved, making current to taper to +defined values, depending on which temperature region battery is at this +moment. + +(3) at current temperature, is taper current defined? +If there is a taper current value to current temperature region (which is +usually in between 0 and 15 degC as defined in current implementation), allow +TODO STEP_FLOAT + +(4) has current been limited due to other thermal rules? +Function mmi_has_current_tapered() requests to power supply votable module +what is the allowed maximum current. Then such value will be compared to taper +current value, and the minimum will be chosen. Additionally, it checks whether +there's still a charger plugged to device or if charging is disabled, and if +any, mark a flag on the system displaying no charging is going on. Finally, move +to taper the current state (STEP_NORM). + +----------------------------------------------------- +current-step: STEP_NORM + if !fcc_norm_ma // (1) + -goto STEP_FLOAT + else if batt_soc < 100 or (batt_mv + 50) < max_fv_mv // (2) + taper_cnt = 0 + -goto STEP_NORM + else if current_has_tapered // (2) + -goto STEP_FULL + +(1) although device is in STEP_NORM, is there a defined taper current for +current temperature region device? +It may happen that temperature has changed, and device no longer needs to +be tapered. Hence, move to STEP_FLOAT. + +(2) is the battery NOT full or below taper voltage? +Stay in this state, because tapering is still happening. Otherwise, it may +be the case that battery is above taper voltage, or device is already fully +charged, forcing it to prepare to stop charging (STEP_FULL). + +----------------------------------------------------- +current-step: STEP_FULL + if batt_soc < 99 // (1) + taper_cnt = 0 + goto STEP_NORM + +(1) Is the battery no longer full? +It may happen that the battery device was previously full, but no longer is. So, +first move to a limited current state for safety, and then in such state, it +may happen that maximum current levels can be achieved. + +----------------------------------------------------- +current-step: STEP_FLOAT + if fcc_norm_ma or (batt_mv + 50) < norm_mv // (1) + goto STEP_MAX + if current_has_tapered // (2) + goto STEP_STOP + +(1) does battery temperature allow current to be tapered to something above +0mA, or battery is below taper voltage? +This means that temperature is in between 0 and 15 degC. If so, try to achieve +maximum current value (STEP_MAX). + +(2) does current need to be tapered? +In case mmi_has_current_tapered() needs to decrease current, it will request +charger to stop charging. + +----------------------------------------------------- +Setting current values based on state machine: + +STEP_FLOAT +STEP_MAX + target_fcc = fcc_max_ma + target_fv: + if !norm_mv + target_fv = max_fv_mv + chip->vfloat_comp_mv + else + target_fv = norm_mv + chip->vfloat_comp_mv + +STEP_FULL: + target_fcc = -EINVAL + target_fv = max_fv_mv + +STEP_NORM: + target_fcc = fcc_norm_ma + target_fv = max_fv_mv + chip->vfloat_comp_mv + +STEP_NONE: + target_fv = max_fv_mv + target_fcc = fcc_norm_ma + +========================================= + How to define mmi,mmi-ffc-zones array +========================================= + +When defining mmi,mmi-ffc-zones array, it is important to always have in +mind the rules described in the battery specifications, only the battery +support Flash Fast Charging can use it, otherwise battery damage or over +voltage protection may happen. + +Each tuple in that array represents a different value, as shown here: + + +"temperature" defines maximum temperature value. So setting "35" means a +definition to anything below 35 degC. + +"max_fv_volt" means maximum allowed battery voltage in that temperature +range. So setting "4480" means that the maximum float voltage is limited +to 4480. + +"charging_term_cur" means charging terminated current threshold in that +temperature range. Once the charging current becomes below the threshold, +the charging will be terminated. + +"fg_term_cur" means charging current threshold for FG to report full +charged in that temperature range. Once the charging current becomes +below the threshold, the FG will report FULL state of charge. + +Here's an example from Dubai: + mmi,mmi-ffc-zones = + <0 4450 243 500>, + <23 4480 670 980>, + <35 4480 770 1045>, + <45 4480 840 1120>, + <60 4450 243 500>; + +The third line states "If temperature is below 35 degC, and the maximum float voltage +is 4.48V, charging terminated current sets to 770mA, Fuel Gauge terminated current +sets to 1045mA. + + diff --git a/drivers/power/mmi_charger/mmi_charger.c b/drivers/power/mmi_charger/mmi_charger.c new file mode 100644 index 000000000000..58eb338d3a18 --- /dev/null +++ b/drivers/power/mmi_charger/mmi_charger.c @@ -0,0 +1,2239 @@ +/* + * Copyright (C) 2020 Motorola Mobility LLC + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mmi_charger.h" + +#define HYST_STEP_MV 50 +#define DEMO_MODE_HYS_SOC 5 +#define WARM_TEMP 45 +#define COOL_TEMP 0 + +#define HEARTBEAT_DELAY_MS 60000 +#define HEARTBEAT_FACTORY_MS 1000 +#define HEARTBEAT_WAKEUP_INTRVAL_NS 70000000000 + +static bool debug_enabled; +module_param(debug_enabled, bool, 0600); +MODULE_PARM_DESC(debug_enabled, "Enable debug for mmi charger driver"); + +static int factory_kill_disable; +module_param(factory_kill_disable, int, 0644); +static int suspend_wakeups; +module_param(suspend_wakeups, int, 0644); + +static struct mmi_charger_chip *this_chip = NULL; + +enum { + POWER_SUPPLY_CHARGE_RATE_NONE = 0, + POWER_SUPPLY_CHARGE_RATE_NORMAL, + POWER_SUPPLY_CHARGE_RATE_WEAK, + POWER_SUPPLY_CHARGE_RATE_TURBO, + POWER_SUPPLY_CHARGE_RATE_HYPER, +}; + +static char *charge_rate[] = { + "None", "Normal", "Weak", "Turbo", "Hyper" +}; + +#define MAX_NUM_TEMP_ZONE 10 +enum mmi_temp_zones { + ZONE_FIRST = 0, + /* states 0-9 are reserved for zones */ + ZONE_LAST = MAX_NUM_TEMP_ZONE + ZONE_FIRST - 1, + ZONE_HOT, + ZONE_COLD, + ZONE_NONE, +}; + +struct mmi_temp_zone { + int temp_c; + int norm_mv; + int fcc_max_ma; + int fcc_norm_ma; +}; + +enum mmi_chrg_step { + STEP_MAX, + STEP_NORM, + STEP_FULL, + STEP_FLOAT, + STEP_DEMO, + STEP_STOP, + STEP_NONE, +}; + +static char *stepchg_str[] = { + [STEP_MAX] = "MAX", + [STEP_NORM] = "NORMAL", + [STEP_FULL] = "FULL", + [STEP_FLOAT] = "FLOAT", + [STEP_DEMO] = "DEMO", + [STEP_STOP] = "STOP", + [STEP_NONE] = "NONE", +}; + +struct mmi_ffc_zone { + int temp; + int fv; + int chrg_iterm; + int fg_iterm; +}; + +enum charging_limit_modes { + CHARGING_LIMIT_OFF, + CHARGING_LIMIT_RUN, + CHARGING_LIMIT_UNKNOWN, +}; + +struct mmi_battery_pack { + int health; + int status; + int age; + int cycles; + int pending; + int reference; + int charger_rate; + int soc_cycles_start; + struct mmi_battery_info *info; + char sn[MMI_BATT_SN_LEN]; + struct list_head list; +}; + +struct mmi_charger_profile { + int fg_iterm; + int chrg_iterm; + int max_fv_mv; + int max_fcc_ma; + int vfloat_comp_mv; + int demo_fv_mv; + int num_temp_zones; + struct mmi_temp_zone *temp_zones; + int num_ffc_zones; + struct mmi_ffc_zone *ffc_zones; +}; + +struct mmi_charger_status { + int demo_full_soc; + bool demo_chrg_suspend; + struct mmi_temp_zone *temp_zone; + enum mmi_temp_zones pres_temp_zone; + enum mmi_chrg_step pres_chrg_step; + enum charging_limit_modes charging_limit_modes; +}; + +struct mmi_charger { + struct mmi_charger_profile profile; + struct mmi_charger_status status; + struct mmi_charger_driver *driver; + struct mmi_battery_info batt_info; + struct mmi_charger_info chg_info; + struct mmi_charger_cfg cfg; + struct mmi_battery_pack *battery; + struct list_head list; +}; + +#define IS_SUSPENDED BIT(0) +#define WAS_SUSPENDED BIT(1) + +struct mmi_charger_chip { + char *name; + struct device *dev; + struct list_head charger_list; + struct mutex charger_lock; + struct list_head battery_list; + struct mutex battery_lock; + + struct power_supply *mmi_psy; + struct power_supply *batt_psy; + char *batt_uenvp[2]; + int combo_cycles; + int combo_soc; + int combo_age; + int combo_status; + int charge_full; + int charge_full_design; + int init_cycles; + int max_charger_rate; + + int suspended; + int demo_mode; + bool factory_mode; + bool factory_version; + bool factory_kill_armed; + bool force_charger_disabled; + + int dcp_pmax; + int hvdcp_pmax; + int max_chrg_temp; + bool enable_charging_limit; + bool enable_factory_poweroff; + int upper_limit_capacity; + int lower_limit_capacity; + + struct wakeup_source *mmi_hb_wake_source; + struct alarm heartbeat_alarm; + int heartbeat_interval; + struct notifier_block mmi_reboot; + struct notifier_block mmi_psy_notifier; + struct delayed_work heartbeat_work; + + bool *debug_enabled; + void *ipc_log; +}; + +#define CHARGER_POWER_7P5W 7500 +#define CHARGER_POWER_10W 10000 +#define CHARGER_POWER_15W 15000 +#define CHARGER_POWER_18W 18000 +#define CHARGER_POWER_20W 20000 +static ssize_t dcp_pmax_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long pmax; + + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + r = kstrtoul(buf, 0, &pmax); + if (r) { + mmi_err(this_chip, "Invalid dcp pmax value = %lu\n", pmax); + return -EINVAL; + } + + if (this_chip->dcp_pmax != pmax && + (pmax > 0 && pmax <= CHARGER_POWER_10W)) { + this_chip->dcp_pmax = pmax; + cancel_delayed_work(&this_chip->heartbeat_work); + schedule_delayed_work(&this_chip->heartbeat_work, + msecs_to_jiffies(0)); + } + + return r ? r : count; +} + +static ssize_t dcp_pmax_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", this_chip->dcp_pmax); +} + +static DEVICE_ATTR(dcp_pmax, 0644, + dcp_pmax_show, + dcp_pmax_store); + +static ssize_t hvdcp_pmax_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long pmax; + + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + r = kstrtoul(buf, 0, &pmax); + if (r) { + mmi_err(this_chip, "Invalid hvdcp pmax value = %lu\n", pmax); + return -EINVAL; + } + + if (this_chip->hvdcp_pmax != pmax && + (pmax >= CHARGER_POWER_7P5W && pmax <= CHARGER_POWER_20W)) { + this_chip->hvdcp_pmax = pmax; + cancel_delayed_work(&this_chip->heartbeat_work); + schedule_delayed_work(&this_chip->heartbeat_work, + msecs_to_jiffies(0)); + } + + return r ? r : count; +} + +static ssize_t hvdcp_pmax_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", this_chip->hvdcp_pmax); +} + +static DEVICE_ATTR(hvdcp_pmax, 0644, + hvdcp_pmax_show, + hvdcp_pmax_store); + +static ssize_t factory_image_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long mode; + + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + r = kstrtoul(buf, 0, &mode); + if (r) { + mmi_err(this_chip, "Invalid factory image mode value = %lu\n", mode); + return -EINVAL; + } + + this_chip->factory_version = (mode) ? true : false; + + return r ? r : count; +} + +static ssize_t factory_image_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int state; + + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + state = (this_chip->factory_version) ? 1 : 0; + + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state); +} + +static DEVICE_ATTR(factory_image_mode, 0644, + factory_image_mode_show, + factory_image_mode_store); + +static ssize_t factory_charge_upper_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int state; + + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + state = this_chip->upper_limit_capacity; + + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state); +} + +static DEVICE_ATTR(factory_charge_upper, 0444, + factory_charge_upper_show, + NULL); + +#define MMI_CHIP_MODE_LOWER_LIMIT 35 +#define MMI_CHIP_MODE_UPPER_LIMIT 80 +static ssize_t force_demo_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long mode; + + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + r = kstrtoul(buf, 0, &mode); + if (r) { + mmi_err(this_chip, "Invalid demo mode value = %lu\n", mode); + return -EINVAL; + } + + if ((mode >= MMI_CHIP_MODE_LOWER_LIMIT) && + (mode <= MMI_CHIP_MODE_UPPER_LIMIT)) + this_chip->demo_mode = mode; + else + this_chip->demo_mode = MMI_CHIP_MODE_LOWER_LIMIT; + + return r ? r : count; +} + +static ssize_t force_demo_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int state; + + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + state = this_chip->demo_mode; + + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state); +} + +static DEVICE_ATTR(force_demo_mode, 0644, + force_demo_mode_show, + force_demo_mode_store); + +#define MIN_TEMP_C -20 +#define MAX_TEMP_C 60 +#define MIN_MAX_TEMP_C 47 +#define HYSTERESIS_DEGC 2 +static ssize_t force_max_chrg_temp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long r; + unsigned long mode; + + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + r = kstrtoul(buf, 0, &mode); + if (r) { + mmi_err(this_chip, "Invalid max temp value = %lu\n", mode); + return -EINVAL; + } + + if ((mode >= MIN_MAX_TEMP_C) && (mode <= MAX_TEMP_C)) + this_chip->max_chrg_temp = mode; + else + this_chip->max_chrg_temp = MAX_TEMP_C; + + return r ? r : count; +} + +static ssize_t force_max_chrg_temp_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int state; + + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + state = this_chip->max_chrg_temp; + + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state); +} + +static DEVICE_ATTR(force_max_chrg_temp, 0644, + force_max_chrg_temp_show, + force_max_chrg_temp_store); + +static ssize_t charge_rate_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%s\n", + charge_rate[this_chip->max_charger_rate]); +} +static DEVICE_ATTR(charge_rate, S_IRUGO, charge_rate_show, NULL); + +static ssize_t age_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + if (!this_chip) { + pr_err("mmi_charger: chip is invalid\n"); + return -ENODEV; + } + + return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", this_chip->combo_age); +} +static DEVICE_ATTR(age, S_IRUGO, age_show, NULL); + +static struct attribute * mmi_g[] = { + &dev_attr_charge_rate.attr, + &dev_attr_age.attr, + NULL, +}; + +static const struct attribute_group power_supply_mmi_attr_group = { + .attrs = mmi_g, +}; + +static int mmi_get_charger_profile(struct mmi_charger_chip *chip, + struct mmi_charger *charger) +{ + int rc; + int i; + int byte_len; + struct device_node *node; + + if (!charger->driver || !charger->driver->dev) { + mmi_err(chip, "mmi charger driver is invalid\n"); + return -EINVAL; + } + + node = charger->driver->dev->of_node; + rc = of_property_read_u32(node, "mmi,chrg-iterm-ma", + &charger->profile.chrg_iterm); + if (rc) + charger->profile.chrg_iterm = 300; + + rc = of_property_read_u32(node, "mmi,fg-iterm-ma", + &charger->profile.fg_iterm); + if (rc) + charger->profile.fg_iterm = charger->profile.chrg_iterm + 50; + + rc = of_property_read_u32(node, "mmi,vfloat-comp-uv", + &charger->profile.vfloat_comp_mv); + if (rc) + charger->profile.vfloat_comp_mv = 0; + charger->profile.vfloat_comp_mv /= 1000; + + rc = of_property_read_u32(node, "mmi,max-fv-mv", + &charger->profile.max_fv_mv); + if (rc) + charger->profile.max_fv_mv = 4400; + + rc = of_property_read_u32(node, "mmi,max-fcc-ma", + &charger->profile.max_fcc_ma); + if (rc) + charger->profile.max_fcc_ma = 4400; + + rc = of_property_read_u32(node, "mmi,demo-fv-mv", + &charger->profile.demo_fv_mv); + if (rc) + charger->profile.demo_fv_mv = 4000; + + if (of_find_property(node, "mmi,mmi-temp-zones", &byte_len)) { + if ((byte_len / sizeof(u32)) % 4) { + mmi_err(chip, "[C:%s]: DT error wrong mmi temp zones\n", + charger->driver->name); + return -ENODEV; + } + + charger->profile.temp_zones = (struct mmi_temp_zone *) + devm_kzalloc(chip->dev, byte_len, GFP_KERNEL); + + if (charger->profile.temp_zones == NULL) + return -ENOMEM; + + charger->profile.num_temp_zones = + byte_len / sizeof(struct mmi_temp_zone); + + rc = of_property_read_u32_array(node, + "mmi,mmi-temp-zones", + (u32 *)charger->profile.temp_zones, + byte_len / sizeof(u32)); + if (rc < 0) { + mmi_err(chip, "[C:%s]: Couldn't read mmi temp zones rc = %d\n", + charger->driver->name, rc); + devm_kfree(chip->dev, charger->profile.temp_zones); + charger->profile.temp_zones = NULL; + return rc; + } + + mmi_info(chip, "[C:%s]: mmi temp zones: Num: %d\n", + charger->driver->name, + charger->profile.num_temp_zones); + for (i = 0; i < charger->profile.num_temp_zones; i++) { + mmi_info(chip, "[C:%s]: mmi temp zones: Zone %d, Temp %d C, " \ + "Step Volt %d mV, Full Rate %d mA, " \ + "Taper Rate %d mA\n", charger->driver->name, i, + charger->profile.temp_zones[i].temp_c, + charger->profile.temp_zones[i].norm_mv, + charger->profile.temp_zones[i].fcc_max_ma, + charger->profile.temp_zones[i].fcc_norm_ma); + } + } + + if (of_find_property(node, "mmi,mmi-ffc-zones", &byte_len)) { + if ((byte_len / sizeof(u32)) % 4) { + mmi_err(chip, "[C:%s]: DT error wrong mmi ffc zones\n", + charger->driver->name); + return -ENODEV; + } + + charger->profile.ffc_zones = (struct mmi_ffc_zone *) + devm_kzalloc(chip->dev, byte_len, GFP_KERNEL); + + if (charger->profile.ffc_zones == NULL) + return -ENOMEM; + + charger->profile.num_ffc_zones = + byte_len / sizeof(struct mmi_ffc_zone); + + rc = of_property_read_u32_array(node, + "mmi,mmi-ffc-zones", + (u32 *)charger->profile.ffc_zones, + byte_len / sizeof(u32)); + if (rc < 0) { + mmi_err(chip, "[C:%s]: Couldn't read mmi ffc zones rc = %d\n", + charger->driver->name, rc); + devm_kfree(chip->dev, charger->profile.ffc_zones); + charger->profile.ffc_zones = NULL; + return rc; + } + + mmi_info(chip, "[C:%s]: mmi ffc zones: Num: %d\n", + charger->driver->name, + charger->profile.num_ffc_zones); + for (i = 0; i < charger->profile.num_ffc_zones; i++) { + mmi_info(chip, "[C:%s]: mmi ffc zones: Zone %d, Temp %d C, " \ + "FV %d mV, Chg Iterm %d mA, Fg Iterm %d mA\n", + charger->driver->name, i, + charger->profile.ffc_zones[i].temp, + charger->profile.ffc_zones[i].fv, + charger->profile.ffc_zones[i].chrg_iterm, + charger->profile.ffc_zones[i].fg_iterm); + } + } + + return 0; +} + +static void mmi_update_charger_profile(struct mmi_charger_chip *chip, + struct mmi_charger *charger) +{ + int i = 0; + int temp; + int num_zones; + struct mmi_ffc_zone *zones; + + if (!chip) { + pr_err("called before chg valid!\n"); + return; + } + + temp = charger->batt_info.batt_temp; + num_zones = charger->profile.num_ffc_zones; + zones = charger->profile.ffc_zones; + while (i < num_zones && temp > zones[i++].temp); + zones = i > 0? &zones[i - 1] : NULL; + if (zones) { + charger->profile.max_fv_mv = zones->fv; + charger->profile.fg_iterm = zones->fg_iterm; + charger->profile.chrg_iterm = zones->chrg_iterm; + mmi_dbg(chip, "[C:%s]: Max FV:%d, FG Iterm:%d, Chrg Iterm:%d\n", + charger->driver->name, + charger->profile.max_fv_mv, + charger->profile.fg_iterm, + charger->profile.chrg_iterm); + } +} + +static int mmi_find_colder_temp_zone(int pres_zone, int vbat, + struct mmi_temp_zone *zones, + int num_zones) +{ + int i; + int colder_zone; + int target_zone; + + if (pres_zone == ZONE_HOT) + colder_zone = num_zones - 1; + else if (pres_zone == ZONE_COLD || + zones[pres_zone].temp_c == zones[ZONE_FIRST].temp_c) + return ZONE_COLD; + else { + for (i = pres_zone - 1; i >= ZONE_FIRST; i--) { + if (zones[pres_zone].temp_c > zones[i].temp_c) { + colder_zone = i; + break; + } + } + if (i < 0) + return ZONE_COLD; + } + + target_zone = colder_zone; + for (i = ZONE_FIRST; i < colder_zone; i++) { + if (zones[colder_zone].temp_c == zones[i].temp_c) { + target_zone = i; + if (vbat < zones[i].norm_mv) + break; + } + } + + return target_zone; +} + +static int mmi_find_hotter_temp_zone(int pres_zone, int vbat, + struct mmi_temp_zone *zones, + int num_zones) +{ + int i; + int hotter_zone; + int target_zone; + + if (pres_zone == ZONE_COLD) + hotter_zone = ZONE_FIRST; + else if (pres_zone == ZONE_HOT || + zones[pres_zone].temp_c == zones[num_zones - 1].temp_c) + return ZONE_HOT; + else { + for (i = pres_zone + 1; i < num_zones; i++) { + if (zones[pres_zone].temp_c < zones[i].temp_c) { + hotter_zone = i; + break; + } + } + if (i >= num_zones) + return ZONE_HOT; + } + + target_zone = hotter_zone; + for (i = hotter_zone; i < num_zones; i++) { + if (zones[hotter_zone].temp_c == zones[i].temp_c) { + target_zone = i; + if (vbat < zones[i].norm_mv) + break; + } + } + return target_zone; +} + +static int mmi_refresh_temp_zone(int pres_zone, int vbat, + struct mmi_temp_zone *zones, + int num_zones) +{ + int i; + int target_zone; + + if (pres_zone == ZONE_COLD || pres_zone == ZONE_HOT) + return pres_zone; + + target_zone = pres_zone; + for (i = ZONE_FIRST; i < num_zones; i++) { + if (zones[pres_zone].temp_c == zones[i].temp_c) { + target_zone = i; + if (vbat < zones[i].norm_mv) + break; + } + } + return target_zone; +} + +static void mmi_get_temp_zone(struct mmi_charger_chip *chip, + struct mmi_charger *charger) +{ + int i; + int temp_c; + int vbat_mv; + int max_temp; + int prev_zone, num_zones; + int hotter_zone, colder_zone; + struct mmi_temp_zone *zones; + int hotter_t, hotter_fcc; + int colder_t, colder_fcc; + + if (!chip) { + pr_err("called before chg valid!\n"); + return; + } + + temp_c = charger->batt_info.batt_temp; + vbat_mv = charger->batt_info.batt_mv; + prev_zone = charger->status.pres_temp_zone; + num_zones = charger->profile.num_temp_zones; + if (!charger->profile.temp_zones) { + zones = NULL; + num_zones = 0; + max_temp = MAX_TEMP_C; + } else { + zones = charger->profile.temp_zones; + if (chip->max_chrg_temp >= MIN_MAX_TEMP_C) + max_temp = chip->max_chrg_temp; + else + max_temp = zones[num_zones - 1].temp_c; + } + + if (prev_zone == ZONE_NONE && zones) { + for (i = num_zones - 1; i >= 0; i--) { + if (temp_c >= zones[i].temp_c) { + charger->status.pres_temp_zone = + mmi_find_hotter_temp_zone(i, + vbat_mv, + zones, + num_zones); + goto exit; + } + } + if (temp_c < MIN_TEMP_C) + charger->status.pres_temp_zone = ZONE_COLD; + else + charger->status.pres_temp_zone = + mmi_find_hotter_temp_zone(ZONE_COLD, + vbat_mv, + zones, + num_zones); + goto exit; + } + + if (prev_zone == ZONE_COLD) { + if (temp_c >= MIN_TEMP_C + HYSTERESIS_DEGC) { + if (!num_zones) + charger->status.pres_temp_zone = ZONE_FIRST; + else + charger->status.pres_temp_zone = + mmi_find_hotter_temp_zone(prev_zone, + vbat_mv, + zones, + num_zones); + } + } else if (prev_zone == ZONE_HOT) { + if (temp_c <= max_temp - HYSTERESIS_DEGC) { + if (!num_zones) + charger->status.pres_temp_zone = ZONE_FIRST; + else + charger->status.pres_temp_zone = + mmi_find_colder_temp_zone(prev_zone, + vbat_mv, + zones, + num_zones); + } + } else if (zones) { + hotter_zone = mmi_find_hotter_temp_zone(prev_zone, + vbat_mv, + zones, + num_zones); + colder_zone = mmi_find_colder_temp_zone(prev_zone, + vbat_mv, + zones, + num_zones); + if (hotter_zone == ZONE_HOT) { + hotter_fcc = 0; + hotter_t = zones[prev_zone].temp_c; + } else { + hotter_fcc = zones[hotter_zone].fcc_max_ma; + hotter_t = zones[prev_zone].temp_c; + } + + if (colder_zone == ZONE_COLD) { + colder_fcc = 0; + colder_t = MIN_TEMP_C; + } else { + colder_fcc = zones[colder_zone].fcc_max_ma; + colder_t = zones[colder_zone].temp_c; + } + + if (zones[prev_zone].fcc_max_ma < hotter_fcc) + hotter_t += HYSTERESIS_DEGC; + + if (zones[prev_zone].fcc_max_ma < colder_fcc) + colder_t -= HYSTERESIS_DEGC; + + if (temp_c < MIN_TEMP_C) + charger->status.pres_temp_zone = ZONE_COLD; + else if (temp_c >= max_temp) + charger->status.pres_temp_zone = ZONE_HOT; + else if (temp_c >= hotter_t) + charger->status.pres_temp_zone = hotter_zone; + else if (temp_c < colder_t) + charger->status.pres_temp_zone = colder_zone; + else + charger->status.pres_temp_zone = + mmi_refresh_temp_zone(prev_zone, + vbat_mv, + zones, + num_zones); + } else { + if (temp_c < MIN_TEMP_C) + charger->status.pres_temp_zone = ZONE_COLD; + else if (temp_c >= max_temp) + charger->status.pres_temp_zone = ZONE_HOT; + else + charger->status.pres_temp_zone = ZONE_FIRST; + } + +exit: + if (!zones || + charger->status.pres_temp_zone == ZONE_COLD || + charger->status.pres_temp_zone == ZONE_HOT) { + charger->status.temp_zone = NULL; + } else { + charger->status.temp_zone = &zones[charger->status.pres_temp_zone]; + } + if (prev_zone != charger->status.pres_temp_zone) { + mmi_info(chip, "[C:%s]: temp zone switch %x -> %x\n", + charger->driver->name, + prev_zone, + charger->status.pres_temp_zone); + } +} + +static void mmi_get_charger_info(struct mmi_charger_chip *chip, + struct mmi_charger *charger) +{ + struct mmi_battery_info *batt_info = &charger->batt_info; + struct mmi_charger_info *chg_info = &charger->chg_info; + + charger->driver->get_batt_info(charger->driver->data, batt_info); + charger->driver->get_chg_info(charger->driver->data, chg_info); + mmi_info(chip, "[C:%s]: batt_mv %d, batt_ma %d, batt_soc %d," + " batt_temp %d, batt_status %d, batt_sn %s," + " chrg_present %d, chrg_type %d, chrg_pmax_mw %d," + " chrg_mv %d, chrg_ma %d\n", + charger->driver->name, + batt_info->batt_mv, + batt_info->batt_ma, + batt_info->batt_soc, + batt_info->batt_temp, + batt_info->batt_status, + batt_info->batt_sn, + chg_info->chrg_present, + chg_info->chrg_type, + chg_info->chrg_pmax_mw, + chg_info->chrg_mv, + chg_info->chrg_ma); +} + +static void mmi_update_charger_status(struct mmi_charger_chip *chip, + struct mmi_charger *charger) +{ + bool voltage_full; + enum charging_limit_modes charging_limit_modes; + struct mmi_charger_profile *profile = &charger->profile; + struct mmi_charger_status *status = &charger->status; + struct mmi_battery_info *batt_info = &charger->batt_info; + struct mmi_charger_info *chg_info = &charger->chg_info; + struct mmi_charger_cfg *cfg = &charger->cfg; + + if (chip->enable_charging_limit && chip->factory_version) { + charging_limit_modes = status->charging_limit_modes; + if ((charging_limit_modes != CHARGING_LIMIT_RUN) + && (batt_info->batt_soc >= chip->upper_limit_capacity)) + charging_limit_modes = CHARGING_LIMIT_RUN; + else if ((charging_limit_modes != CHARGING_LIMIT_OFF) + && (batt_info->batt_soc <= chip->lower_limit_capacity)) + charging_limit_modes = CHARGING_LIMIT_OFF; + + if (charging_limit_modes != charger->status.charging_limit_modes) { + status->charging_limit_modes = charging_limit_modes; + if (status->charging_limit_modes == CHARGING_LIMIT_RUN) + mmi_warn(chip, "Factory Mode/Image so Limiting Charging!!!\n"); + } + } + + mmi_get_temp_zone(chip, charger); + if (!chg_info->chrg_present) { + status->pres_chrg_step = STEP_NONE; + } else if ((status->pres_temp_zone == ZONE_HOT) || + (status->pres_temp_zone == ZONE_COLD) || + (status->charging_limit_modes == CHARGING_LIMIT_RUN)) { + status->pres_chrg_step = STEP_STOP; + } else if (chip->demo_mode) { /* Demo Mode */ + status->pres_chrg_step = STEP_DEMO; + voltage_full = ((status->demo_chrg_suspend == false) && + ((batt_info->batt_mv + HYST_STEP_MV) >= profile->demo_fv_mv) && + charger->driver->is_charge_tapered(charger->driver->data, profile->chrg_iterm)); + + if ((status->demo_chrg_suspend == false) && + ((batt_info->batt_soc >= chip->demo_mode) || voltage_full)) { + status->demo_full_soc = batt_info->batt_soc; + cfg->charger_suspend = true; + status->demo_chrg_suspend = true; + } else if (status->demo_chrg_suspend == true && + (batt_info->batt_soc <= (status->demo_full_soc - DEMO_MODE_HYS_SOC))) { + status->demo_chrg_suspend = false; + cfg->taper_kickoff = true; + cfg->charger_suspend = false; + } + } else if (!status->temp_zone) { + status->pres_chrg_step = STEP_MAX; + /* Skip for empty temperature zone */ + } else if ((status->pres_chrg_step == STEP_NONE) || + (status->pres_chrg_step == STEP_STOP)) { + if (status->temp_zone->norm_mv && + ((batt_info->batt_mv + HYST_STEP_MV) >= status->temp_zone->norm_mv)) { + if (status->temp_zone->fcc_norm_ma) + status->pres_chrg_step = STEP_NORM; + else + status->pres_chrg_step = STEP_STOP; + } else + status->pres_chrg_step = STEP_MAX; + } else if (status->pres_chrg_step == STEP_MAX) { + if (!status->temp_zone->norm_mv) { + /* No Step in this Zone */ + cfg->taper_kickoff = true; + if ((batt_info->batt_mv + HYST_STEP_MV) >= profile->max_fv_mv) + status->pres_chrg_step = STEP_NORM; + else + status->pres_chrg_step = STEP_MAX; + } else if ((batt_info->batt_mv + HYST_STEP_MV) < status->temp_zone->norm_mv) { + cfg->taper_kickoff = true; + status->pres_chrg_step = STEP_MAX; + } else if (!status->temp_zone->fcc_norm_ma) + status->pres_chrg_step = STEP_FLOAT; + else if (charger->driver->is_charge_tapered(charger->driver->data, status->temp_zone->fcc_norm_ma)) { + cfg->taper_kickoff = true; + if (charger->driver->is_charge_halt && + charger->driver->is_charge_halt(charger->driver->data)) { + cfg->charging_reset = true; + mmi_warn(chip, "[C:%s]: Charge Halt..Toggle\n", + charger->driver->name); + } + status->pres_chrg_step = STEP_NORM; + } + } else if (status->pres_chrg_step == STEP_NORM) { + if (!status->temp_zone->fcc_norm_ma) + status->pres_chrg_step = STEP_FLOAT; + else if ((batt_info->batt_soc < 100) || + (batt_info->batt_mv + HYST_STEP_MV) < profile->max_fv_mv) { + cfg->taper_kickoff = true; + status->pres_chrg_step = STEP_NORM; + } else if (charger->driver->is_charge_tapered(charger->driver->data, profile->chrg_iterm)) { + status->pres_chrg_step = STEP_FULL; + } + } else if (status->pres_chrg_step == STEP_FULL) { + if ((batt_info->batt_soc <= 99) || + batt_info->batt_mv < (profile->max_fv_mv - HYST_STEP_MV * 2)) { + cfg->taper_kickoff = true; + status->pres_chrg_step = STEP_NORM; + } + } else if (status->pres_chrg_step == STEP_FLOAT) { + if ((status->temp_zone->fcc_norm_ma) || + ((batt_info->batt_mv + HYST_STEP_MV) < status->temp_zone->norm_mv)) + status->pres_chrg_step = STEP_MAX; + else if (charger->driver->is_charge_tapered(charger->driver->data, profile->chrg_iterm)) + status->pres_chrg_step = STEP_STOP; + } + + charger->battery->pending++; + + mmi_info(chip, "[C:%s]: StepChg: %s, TempZone: %d, LimitMode: %d, DemoSuspend: %d\n", + charger->driver->name, + stepchg_str[(int)status->pres_chrg_step], + status->pres_temp_zone, + status->charging_limit_modes, + status->demo_chrg_suspend); +} + +static void mmi_reset_charger_configure(struct mmi_charger_chip *chip, + struct mmi_charger *charger) +{ + charger->cfg.target_fv = charger->profile.max_fv_mv; + charger->cfg.target_fcc = -EINVAL; + charger->cfg.chrg_iterm = charger->profile.chrg_iterm; + charger->cfg.fg_iterm = charger->profile.fg_iterm; + charger->cfg.taper_kickoff = false; + charger->cfg.charging_reset = false; + charger->cfg.full_charged = false; + charger->cfg.charging_disable = false; + charger->cfg.charger_suspend = false; + charger->cfg.demo_mode = chip->demo_mode; + charger->cfg.factory_version = chip->factory_version; + charger->cfg.factory_mode = chip->factory_mode; + charger->cfg.dcp_pmax = chip->dcp_pmax; + charger->cfg.hvdcp_pmax = chip->hvdcp_pmax; +} + +static void mmi_configure_charger(struct mmi_charger_chip *chip, + struct mmi_charger *charger) +{ + struct mmi_charger_profile *profile = &charger->profile; + struct mmi_charger_status *status = &charger->status; + struct mmi_charger_cfg *cfg = &charger->cfg; + + switch (status->pres_chrg_step) { + case STEP_FLOAT: + case STEP_MAX: + if (!status->temp_zone || !status->temp_zone->norm_mv) + cfg->target_fv = profile->max_fv_mv + profile->vfloat_comp_mv; + else + cfg->target_fv = status->temp_zone->norm_mv + profile->vfloat_comp_mv; + if (!status->temp_zone) + cfg->target_fcc = profile->max_fcc_ma; + else + cfg->target_fcc = status->temp_zone->fcc_max_ma; + break; + case STEP_FULL: + cfg->target_fv = profile->max_fv_mv; + cfg->target_fcc = -EINVAL; + cfg->full_charged = true; + break; + case STEP_NORM: + cfg->target_fv = profile->max_fv_mv + profile->vfloat_comp_mv; + if (!status->temp_zone) + cfg->target_fcc = profile->max_fcc_ma; + else + cfg->target_fcc = status->temp_zone->fcc_norm_ma; + break; + case STEP_NONE: + cfg->target_fv = profile->max_fv_mv; + if (!status->temp_zone) + cfg->target_fcc = profile->max_fcc_ma; + else + cfg->target_fcc = status->temp_zone->fcc_norm_ma; + break; + case STEP_STOP: + cfg->target_fv = profile->max_fv_mv; + cfg->target_fcc = -EINVAL; + break; + case STEP_DEMO: + cfg->target_fv = profile->demo_fv_mv; + if (!status->temp_zone) + cfg->target_fcc = profile->max_fcc_ma; + else + cfg->target_fcc = status->temp_zone->fcc_norm_ma; + break; + default: + cfg->target_fv = profile->max_fv_mv; + if (!status->temp_zone) + cfg->target_fcc = profile->max_fcc_ma; + else + cfg->target_fcc = status->temp_zone->fcc_norm_ma; + break; + } + + if (cfg->target_fcc < 0) + cfg->charging_disable = true; + else + cfg->charging_disable = false; + + if (chip->factory_mode) { + cfg->target_fv = 4400; + cfg->target_fcc = 3000; + cfg->charging_disable = true; + cfg->taper_kickoff = false; + cfg->charging_reset = false; + } + + if (chip->force_charger_disabled) + cfg->charger_suspend = true; + + charger->driver->config_charge(charger->driver->data, cfg); + + mmi_info(chip, "[C:%s]: FV=%d, FCC=%d, CDIS=%d," + " CSUS=%d, CRES=%d, CFULL=%d\n", + charger->driver->name, + cfg->target_fv, + cfg->target_fcc, + cfg->charging_disable, + cfg->charger_suspend, + cfg->charging_reset, + cfg->full_charged); +} + +static void mmi_notify_charger_rate(struct mmi_charger_chip *chip, int rate) +{ + int rc; + char *chrg_rate_string = NULL; + + if (rate < POWER_SUPPLY_CHARGE_RATE_NONE || + rate > POWER_SUPPLY_CHARGE_RATE_HYPER) { + mmi_err(chip, "Invalid charger rate %d\n", rate); + return; + } + + if (!chip->batt_psy) { + chip->batt_psy = power_supply_get_by_name("battery"); + if (chip->batt_psy) { + rc = sysfs_create_group(&chip->batt_psy->dev.kobj, + &power_supply_mmi_attr_group); + if (rc) + mmi_err(chip, "failed: attr create\n"); + } else { + mmi_err(chip, "No battery supply found\n"); + return; + } + } + + chrg_rate_string = kmalloc(CHG_SHOW_MAX_SIZE, GFP_KERNEL); + if (chrg_rate_string) { + scnprintf(chrg_rate_string, CHG_SHOW_MAX_SIZE, + "POWER_SUPPLY_CHARGE_RATE=%s", + charge_rate[rate]); + if (chip->batt_uenvp[0]) { + kfree(chip->batt_uenvp[0]); + chip->batt_uenvp[0] = NULL; + } + chip->batt_uenvp[0] = chrg_rate_string; + chip->batt_uenvp[1] = NULL; + } + kobject_uevent_env(&chip->batt_psy->dev.kobj, KOBJ_CHANGE, + chip->batt_uenvp); +} + +#define WEAK_CHRG_THRSH_MW 2500 +#define TURBO_CHRG_THRSH_MW 12500 +#define HYPER_CHRG_THRSH_MW 40000 +int mmi_get_battery_charger_rate(struct mmi_charger *charger) +{ + int rate; + struct mmi_charger_info *info; + + info = &charger->chg_info; + if (!info->chrg_present) + rate = POWER_SUPPLY_CHARGE_RATE_NONE; + else if (info->chrg_pmax_mw < WEAK_CHRG_THRSH_MW) + rate = POWER_SUPPLY_CHARGE_RATE_WEAK; + else if (info->chrg_pmax_mw < TURBO_CHRG_THRSH_MW) + rate = POWER_SUPPLY_CHARGE_RATE_NORMAL; + else if (info->chrg_pmax_mw < HYPER_CHRG_THRSH_MW) + rate = POWER_SUPPLY_CHARGE_RATE_TURBO; + else + rate = POWER_SUPPLY_CHARGE_RATE_HYPER; + + return rate; +} + +static int mmi_get_battery_health(struct mmi_charger *charger) +{ + int batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; + struct mmi_charger_status *status = &charger->status; + struct mmi_battery_info *batt_info = &charger->batt_info; + + if (status->pres_temp_zone == ZONE_HOT) { + batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + } else if (status->pres_temp_zone == ZONE_COLD) { + batt_health = POWER_SUPPLY_HEALTH_COLD; + } else if (batt_info->batt_temp >= WARM_TEMP) { + if (status->pres_chrg_step == STEP_STOP) + batt_health = POWER_SUPPLY_HEALTH_OVERHEAT; + else + batt_health = POWER_SUPPLY_HEALTH_GOOD; + } else if (batt_info->batt_temp <= COOL_TEMP) { + if (status->pres_chrg_step == STEP_STOP) + batt_health = POWER_SUPPLY_HEALTH_COLD; + else + batt_health = POWER_SUPPLY_HEALTH_GOOD; + } else + batt_health = POWER_SUPPLY_HEALTH_GOOD; + + return batt_health; +} + +static int mmi_combine_battery_soc(struct mmi_charger_chip *chip) +{ + int soc = 0; + int remain = 0, full = 0; + struct mmi_battery_pack *battery = NULL; + + list_for_each_entry(battery, &chip->battery_list, list) { + remain += battery->info->batt_soc * battery->info->batt_full_uah; + full += battery->info->batt_full_uah; + } + + if (full > 0 && remain > 0) { + soc = remain / full; + if (soc > 100) + soc = 100; + } else + soc = -1; + + return soc; +} + +static int mmi_combine_battery_status(struct mmi_charger_chip *chip) +{ + int status = POWER_SUPPLY_STATUS_DISCHARGING; + struct mmi_battery_pack *battery = NULL; + + list_for_each_entry(battery, &chip->battery_list, list) { + if (battery->status == POWER_SUPPLY_STATUS_CHARGING) { + status = POWER_SUPPLY_STATUS_CHARGING; + break; + } + if (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING || + status == POWER_SUPPLY_STATUS_NOT_CHARGING) { + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + continue; + } + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING || + status == POWER_SUPPLY_STATUS_DISCHARGING) { + status = POWER_SUPPLY_STATUS_DISCHARGING; + continue; + } + status = battery->status; + } + + return status; +} + +static int mmi_combine_battery_cycles(struct mmi_charger_chip *chip) +{ + int cycles = 0; + int full = 0; + struct mmi_battery_pack *battery = NULL; + + list_for_each_entry(battery, &chip->battery_list, list) { + cycles += battery->cycles * battery->info->batt_full_uah; + full += battery->info->batt_full_uah; + } + + if (full > 0 && cycles >= 0) + cycles /= full; + else + cycles = -1; + + return cycles; +} + +static int mmi_combine_battery_age(struct mmi_charger_chip *chip) +{ + int age = 100; + int full = 0; + int full_design = 0; + struct mmi_battery_pack *battery = NULL; + + list_for_each_entry(battery, &chip->battery_list, list) { + full += battery->info->batt_full_uah; + full_design += battery->info->batt_design_uah; + } + chip->charge_full = full; + chip->charge_full_design = full_design; + + if (full_design > 0 && full > 0) { + age = full * 100 / full_design; + if (age > 100) + age = 100; + } else + age = -1; + + return age; +} + +static void mmi_update_battery_status(struct mmi_charger_chip *chip) +{ + int soc; + int age; + int status; + int cycles; + bool mmi_changed = false; + int charger_rate = POWER_SUPPLY_CHARGE_RATE_NONE; + int max_charger_rate = POWER_SUPPLY_CHARGE_RATE_NONE; + struct mmi_charger *charger = NULL; + struct mmi_battery_pack *battery = NULL; + struct mmi_battery_info *batt_info = NULL; + + mutex_lock(&chip->battery_lock); + list_for_each_entry(charger, &chip->charger_list, list) { + battery = charger->battery; + batt_info = &charger->batt_info; + + /* Do it only for the first charger */ + if (battery->pending == battery->reference) { + /* init for going through all chargers */ + battery->status = POWER_SUPPLY_STATUS_DISCHARGING; + battery->charger_rate = POWER_SUPPLY_CHARGE_RATE_NONE; + + /* update only at the first charger */ + battery->health = mmi_get_battery_health(charger); + if (batt_info->batt_design_uah > 0) + battery->age = batt_info->batt_full_uah * 100 \ + / batt_info->batt_design_uah; + if (battery->soc_cycles_start < batt_info->batt_soc) + battery->cycles += \ + batt_info->batt_soc - battery->soc_cycles_start; + battery->soc_cycles_start = batt_info->batt_soc; + battery->info = batt_info; + } + + /* update charger rate */ + charger_rate = mmi_get_battery_charger_rate(charger); + if (battery->charger_rate < charger_rate) + battery->charger_rate = charger_rate; + + /* update charging status */ + if (batt_info->batt_status == POWER_SUPPLY_STATUS_FULL || + battery->status == POWER_SUPPLY_STATUS_FULL || + charger->status.pres_chrg_step == STEP_FULL) + battery->status = POWER_SUPPLY_STATUS_FULL; + else if (batt_info->batt_status == POWER_SUPPLY_STATUS_CHARGING || + battery->status == POWER_SUPPLY_STATUS_CHARGING) + battery->status = POWER_SUPPLY_STATUS_CHARGING; + else if (batt_info->batt_status == POWER_SUPPLY_STATUS_NOT_CHARGING || + battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING) + battery->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + battery->status = POWER_SUPPLY_STATUS_DISCHARGING; + + battery->pending--; + /* Do it only for the last charger */ + if (battery->pending <= 0) { + if (max_charger_rate < battery->charger_rate) + max_charger_rate = battery->charger_rate; + battery->pending = 0; + mmi_dbg(chip, "[B:%s]: health:%d, status:%d," + " age:%d, cycles:%d, rate:%s\n", + battery->sn, + battery->health, + battery->status, + battery->age, + battery->cycles, + charge_rate[battery->charger_rate]); + } + } + + soc = mmi_combine_battery_soc(chip); + status = mmi_combine_battery_status(chip); + cycles = mmi_combine_battery_cycles(chip); + age = mmi_combine_battery_age(chip); + if (soc >= 0 && chip->combo_soc != soc) { + mmi_changed = true; + chip->combo_soc = soc; + } + if (chip->combo_status != status) { + mmi_changed = true; + chip->combo_status = status; + } + if (cycles >= 0 && + (chip->combo_cycles / 100) != (cycles / 100)) { + mmi_changed = true; + chip->combo_cycles = cycles; + } + if (age > 0 && chip->combo_age != age) { + mmi_changed = true; + chip->combo_age = age; + } + if (chip->max_charger_rate != max_charger_rate) { + mmi_changed = true; + chip->max_charger_rate = max_charger_rate; + mmi_notify_charger_rate(chip, max_charger_rate); + + mmi_info(chip, "%s charger is detected\n", + charge_rate[chip->max_charger_rate]); + } + + if (mmi_changed) { + power_supply_changed(chip->mmi_psy); + mmi_info(chip, "Combo status: soc:%d, status:%d," + " age:%d, cycles:%d, rate:%s\n", + chip->combo_soc, + chip->combo_status, + chip->combo_age, + chip->combo_cycles, + charge_rate[chip->max_charger_rate]); + } + + mutex_unlock(&chip->battery_lock); +} + +static void mmi_charger_heartbeat_work(struct work_struct *work) +{ + int hb_resch_time; + struct mmi_charger *charger = NULL; + struct mmi_charger_chip *chip = container_of(work, + struct mmi_charger_chip, + heartbeat_work.work); + + /* Have not been resumed so wait another 100 ms */ + if (chip->suspended & IS_SUSPENDED) { + mmi_err(chip, "HB running before Resume\n"); + schedule_delayed_work(&chip->heartbeat_work, + msecs_to_jiffies(100)); + return; + } + + mmi_dbg(chip, "Heartbeat!\n"); + + pm_stay_awake(chip->dev); + alarm_cancel(&chip->heartbeat_alarm); + + mutex_lock(&chip->charger_lock); + list_for_each_entry(charger, &chip->charger_list, list) { + mmi_get_charger_info(chip, charger); + mmi_update_charger_profile(chip, charger); + mmi_reset_charger_configure(chip, charger); + mmi_update_charger_status(chip, charger); + mmi_configure_charger(chip, charger); + } + mmi_update_battery_status(chip); + mutex_unlock(&chip->charger_lock); + + mmi_dbg(chip, "DemoMode:%d, FactoryVersion:%d, FactoryMode:%d," + " dcp_pmax:%d, hvdcp_pmax:%d\n", + chip->demo_mode, + chip->factory_version, + chip->factory_mode, + chip->dcp_pmax, + chip->hvdcp_pmax); + + if (chip->factory_mode || + (chip->factory_version && chip->enable_factory_poweroff)) { + if (chip->max_charger_rate > POWER_SUPPLY_CHARGE_RATE_NONE) { + mmi_dbg(chip, "Factory Kill Armed\n"); + chip->factory_kill_armed = true; + } else if (chip->factory_kill_armed && !factory_kill_disable) { + mmi_warn(chip, "Factory kill power off\n"); + kernel_power_off(); + } else { + chip->factory_kill_armed = false; + } + } + + chip->suspended = 0; + + if (chip->factory_mode) + hb_resch_time = HEARTBEAT_FACTORY_MS; + else + hb_resch_time = chip->heartbeat_interval; + schedule_delayed_work(&chip->heartbeat_work, + msecs_to_jiffies(hb_resch_time)); + if (suspend_wakeups || + chip->max_charger_rate != POWER_SUPPLY_CHARGE_RATE_NONE) + alarm_start_relative(&chip->heartbeat_alarm, + ns_to_ktime(HEARTBEAT_WAKEUP_INTRVAL_NS)); + + if (chip->max_charger_rate == POWER_SUPPLY_CHARGE_RATE_NONE) + pm_relax(chip->dev); + + __pm_relax(chip->mmi_hb_wake_source); +} + +const char *mmi_get_battery_serialnumber(void) +{ + struct device_node *np = of_find_node_by_path("/chosen"); + const char *battsn_buf; + int retval; + + battsn_buf = NULL; + + if (np) + retval = of_property_read_string(np, "mmi,battid", + &battsn_buf); + else + return NULL; + + if ((retval == -EINVAL) || !battsn_buf) { + pr_err("Battsn unused\n"); + of_node_put(np); + return NULL; + + } else + pr_err("Battsn = %s\n", battsn_buf); + + of_node_put(np); + + return battsn_buf; +} +EXPORT_SYMBOL(mmi_get_battery_serialnumber); + +bool mmi_is_factory_mode(void) +{ + struct device_node *np = of_find_node_by_path("/chosen"); + bool factory_mode = false; + const char *bootargs = NULL; + char *bootmode = NULL; + char *end = NULL; + + if (!np) + return factory_mode; + + if (!of_property_read_string(np, "bootargs", &bootargs)) { + bootmode = strstr(bootargs, "androidboot.mode="); + if (bootmode) { + end = strpbrk(bootmode, " "); + bootmode = strpbrk(bootmode, "="); + } + if (bootmode && + end > bootmode && + strnstr(bootmode, "factory", end - bootmode)) { + factory_mode = true; + } + } + of_node_put(np); + + return factory_mode; +} +EXPORT_SYMBOL(mmi_is_factory_mode); + +bool mmi_is_factory_version(void) +{ + struct device_node *np = of_find_node_by_path("/chosen"); + bool factory_version = false; + const char *bootargs = NULL; + char *bootloader = NULL; + char *end = NULL; + + if (this_chip && this_chip->factory_version) + return true; + + if (!np) + return factory_version; + + if (!of_property_read_string(np, "bootargs", &bootargs)) { + bootloader = strstr(bootargs, "androidboot.bootloader="); + if (bootloader) { + end = strpbrk(bootloader, " "); + bootloader = strpbrk(bootloader, "="); + } + if (bootloader && + end > bootloader && + strnstr(bootloader, "factory", end - bootloader)) { + factory_version = true; + } + } + of_node_put(np); + + return factory_version; +} +EXPORT_SYMBOL(mmi_is_factory_version); + +void mmi_get_charger_configure(struct mmi_charger_driver *driver) +{ + struct mmi_charger *charger = NULL; + struct mmi_charger_chip *chip = this_chip; + + if (!chip) { + pr_err("mmi_charger: chip is invalid\n"); + return; + } + + mutex_lock(&chip->charger_lock); + list_for_each_entry(charger, &chip->charger_list, list) { + if (charger->driver == driver) { + mmi_get_charger_info(chip, charger); + mmi_update_charger_profile(chip, charger); + mmi_reset_charger_configure(chip, charger); + mmi_update_charger_status(chip, charger); + mmi_configure_charger(chip, charger); + break; + } + } + mmi_update_battery_status(chip); + mutex_unlock(&chip->charger_lock); +} +EXPORT_SYMBOL(mmi_get_charger_configure); + +int mmi_register_charger_driver(struct mmi_charger_driver *driver) +{ + int ret = 0; + struct mmi_charger_chip *chip = this_chip; + struct mmi_charger *charger = NULL; + struct mmi_battery_pack *battery = NULL; + struct mmi_battery_info batt_info; + struct mmi_charger_info chg_info; + + if (!chip) { + pr_err("%s: No mmi charger chip\n", __func__); + return -ENODEV; + } + + if (!driver) { + mmi_err(chip, "Invalid mmi charger driver\n"); + return -EINVAL; + } + + if (!driver->get_batt_info || + !driver->get_chg_info || + !driver->config_charge || + !driver->is_charge_tapered) { + mmi_err(chip, "[C:%s]: mmi charger function is empty\n", + driver->name); + return -EINVAL; + } + + memset(&batt_info, 0, sizeof(batt_info)); + memset(&chg_info, 0, sizeof(chg_info)); + driver->get_batt_info(driver->data, &batt_info); + driver->get_chg_info(driver->data, &chg_info); + mutex_lock(&chip->charger_lock); + list_for_each_entry(charger, &chip->charger_list, list) { + if (charger->driver == driver) { + ret = -EEXIST; + mmi_err(chip, + "[C:%s]: driver has already registered\n", + driver->name); + goto exit; + } + if (!battery && + !strcmp(charger->battery->sn, batt_info.batt_sn)) + battery = charger->battery; + } + + charger = devm_kzalloc(chip->dev, sizeof(struct mmi_charger), + GFP_KERNEL); + if (!charger) { + ret = -ENOMEM; + goto exit; + } + + if (!battery) { + battery = devm_kzalloc(chip->dev, sizeof(struct mmi_battery_pack), + GFP_KERNEL); + if (!battery) { + devm_kfree(chip->dev, charger); + ret = -ENOMEM; + goto exit; + } + battery->reference++; + battery->age = 100; + battery->cycles = 0; + battery->soc_cycles_start = 100; + memcpy(battery->sn, batt_info.batt_sn, MMI_BATT_SN_LEN); + + mutex_lock(&chip->battery_lock); + list_add_tail(&battery->list, &chip->battery_list); + mutex_unlock(&chip->battery_lock); + } else { + mutex_lock(&chip->battery_lock); + battery->reference++; + mutex_unlock(&chip->battery_lock); + } + + memset(&charger->status, 0, sizeof(struct mmi_charger_status)); + charger->status.pres_temp_zone = ZONE_NONE; + charger->status.pres_chrg_step = STEP_NONE; + charger->status.demo_full_soc = 100; + charger->status.charging_limit_modes = CHARGING_LIMIT_UNKNOWN; + memcpy(&charger->batt_info, &batt_info, sizeof(batt_info)); + memcpy(&charger->chg_info, &chg_info, sizeof(chg_info)); + charger->driver = driver; + charger->battery = battery; + charger->battery->info = &charger->batt_info; + mmi_get_charger_profile(chip, charger); + list_add_tail(&charger->list, &chip->charger_list); +exit: + mutex_unlock(&chip->charger_lock); + + return ret; +} +EXPORT_SYMBOL(mmi_register_charger_driver); + +int mmi_unregister_charger_driver(struct mmi_charger_driver *driver) +{ + int ret = 0; + struct mmi_charger_chip *chip = this_chip; + struct mmi_charger *charger = NULL, *tmp = NULL; + + if (!chip) { + pr_err("%s: No mmi charger chip\n", __func__); + return -ENODEV; + } + + if (!driver) { + mmi_err(chip, "Invalid mmi charger driver\n"); + return -EINVAL; + } + + mutex_lock(&chip->charger_lock); + list_for_each_entry(tmp, &chip->charger_list, list) { + if (tmp->driver == driver) { + charger = tmp; + break; + } + } + + if (charger) { + mutex_lock(&chip->battery_lock); + charger->battery->reference--; + if (charger->battery->reference <= 0) { + list_del(&charger->battery->list); + devm_kfree(chip->dev, charger->battery); + } + mutex_unlock(&chip->battery_lock); + + list_del(&charger->list); + devm_kfree(chip->dev, charger); + } else { + mmi_err(chip, "[C:%s]: driver has not registered yet\n", + driver->name); + ret = -ENOENT; + } + + mutex_unlock(&chip->charger_lock); + return ret; +} +EXPORT_SYMBOL(mmi_unregister_charger_driver); + +static enum alarmtimer_restart mmi_heartbeat_alarm_cb(struct alarm *alarm, + ktime_t now) +{ + struct mmi_charger_chip *chip = container_of(alarm, + struct mmi_charger_chip, + heartbeat_alarm); + + mmi_info(chip, "HB alarm fired\n"); + + __pm_stay_awake(chip->mmi_hb_wake_source); + cancel_delayed_work(&chip->heartbeat_work); + /* Delay by 500 ms to allow devices to resume. */ + schedule_delayed_work(&chip->heartbeat_work, + msecs_to_jiffies(500)); + + return ALARMTIMER_NORESTART; +} + +static int mmi_psy_notifier_call(struct notifier_block *nb, unsigned long val, + void *v) +{ + struct mmi_charger_chip *chip = container_of(nb, + struct mmi_charger_chip, mmi_psy_notifier); + struct power_supply *psy = v; + + if (!chip) { + pr_err("called before chip valid!\n"); + return NOTIFY_DONE; + } + + if (val != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if (psy && + ((strcmp(psy->desc->name, "battery") == 0) || + (strcmp(psy->desc->name, "usb") == 0) || + (strcmp(psy->desc->name, "wireless") == 0))) { + cancel_delayed_work(&chip->heartbeat_work); + schedule_delayed_work(&chip->heartbeat_work, + msecs_to_jiffies(0)); + } + + return NOTIFY_OK; +} + +static int mmi_charger_reboot(struct notifier_block *nb, + unsigned long event, void *unused) +{ + struct mmi_charger_chip *chip = container_of(nb, struct mmi_charger_chip, + mmi_reboot); + + if (!chip) { + pr_err("called before chip valid!\n"); + return NOTIFY_DONE; + } + + if (!chip->factory_mode) + return NOTIFY_DONE; + + switch (event) { + case SYS_POWER_OFF: + factory_kill_disable = true; + chip->force_charger_disabled = true; + schedule_delayed_work(&chip->heartbeat_work, msecs_to_jiffies(0)); + while (chip->max_charger_rate != POWER_SUPPLY_CHARGE_RATE_NONE) { + mmi_info(chip, "Wait for charger removal\n"); + msleep(100); + } + break; + default: + break; + } + + return NOTIFY_DONE; +} + +static enum power_supply_property mmi_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, +}; + +static int mmi_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mmi_charger_chip *chip = power_supply_get_drvdata(psy); + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = chip->combo_status; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = chip->combo_soc; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + val->intval = chip->init_cycles + chip->combo_cycles / 100; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = chip->charge_full_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = chip->charge_full; + break; + default: + val->intval = -EINVAL; + break; + } + + return rc; +} + +static int mmi_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0; + struct mmi_charger_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CYCLE_COUNT: + chip->init_cycles = val->intval; + break; + default: + break; + } + return rc; +} + +static int mmi_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CYCLE_COUNT: + return 1; + default: + break; + } + + return 0; +} + +static const struct power_supply_desc mmi_psy_desc = { + .name = "mmi_battery", + .type = POWER_SUPPLY_TYPE_MAINS, + .get_property = mmi_get_prop, + .set_property = mmi_set_prop, + .property_is_writeable = mmi_prop_is_writeable, + .properties = mmi_props, + .num_properties = ARRAY_SIZE(mmi_props), +}; + +static int mmi_parse_dt(struct mmi_charger_chip *chip) +{ + int rc = 0; + const char *charger_ability = NULL; + struct device_node *node = chip->dev->of_node; + + chip->enable_charging_limit = + of_property_read_bool(node, "mmi,enable-charging-limit"); + + chip->enable_factory_poweroff = + of_property_read_bool(node, "mmi,enable-factory-poweroff"); + + rc = of_property_read_u32(node, "mmi,upper-limit-capacity", + &chip->upper_limit_capacity); + if (rc) + chip->upper_limit_capacity = 100; + + rc = of_property_read_u32(node, "mmi,lower-limit-capacity", + &chip->lower_limit_capacity); + if (rc) + chip->lower_limit_capacity = 0; + + rc = of_property_read_u32(node, "mmi,heartbeat-interval", + &chip->heartbeat_interval); + if (rc) + chip->heartbeat_interval = HEARTBEAT_DELAY_MS; + + rc = of_property_read_u32(node, "mmi,dcp-power-max", + &chip->dcp_pmax); + if (rc) + chip->dcp_pmax = CHARGER_POWER_7P5W; + + rc = of_property_read_u32(node, "mmi,hvdcp-power-max", + &chip->hvdcp_pmax); + if (rc) + chip->hvdcp_pmax = CHARGER_POWER_15W; + + node = of_find_node_by_path("/chosen"); + + if (!node) + return 0; + + rc = of_property_read_string(node, "mmi,charger", &charger_ability); + if ((rc == -EINVAL) || !charger_ability) { + mmi_warn(chip, "mmi,charger is unused\n"); + } else { + mmi_info(chip, "charger ability = %s\n", charger_ability); + if (strstr(charger_ability, "15W")) + chip->hvdcp_pmax = CHARGER_POWER_15W; + else if (strstr(charger_ability, "18W")) + chip->hvdcp_pmax = CHARGER_POWER_18W; + else if (strstr(charger_ability, "20W")) + chip->hvdcp_pmax = CHARGER_POWER_20W; + } + + charger_ability = NULL; + rc = of_property_read_string(node, "mmi,usb_dcp", &charger_ability); + if ((rc == -EINVAL) || !charger_ability) { + mmi_warn(chip, "mmi,usb_dcp is unused\n"); + } else { + mmi_info(chip, "charger ability = %s\n", charger_ability); + if (strstr(charger_ability, "1.5A")) + chip->dcp_pmax = CHARGER_POWER_7P5W; + else if (strstr(charger_ability, "2A")) + chip->dcp_pmax = CHARGER_POWER_10W; + } + + of_node_put(node); + + return 0; +} + +static int mmi_charger_probe(struct platform_device *pdev) +{ + int rc = 0; + struct mmi_charger_chip *chip; + struct power_supply_config psy_cfg = {}; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->name = "mmi_charger"; + chip->dev = &pdev->dev; + psy_cfg.drv_data = chip; + chip->suspended = 0; + chip->combo_status = 0; + chip->combo_age = 100; + chip->combo_cycles = 0; + chip->combo_soc = 100; + chip->charge_full = 0; + chip->charge_full_design = 0; + chip->init_cycles = 0; + chip->factory_version = mmi_is_factory_version(); + chip->factory_mode = mmi_is_factory_mode(); + platform_set_drvdata(pdev, chip); + device_init_wakeup(chip->dev, true); + + chip->debug_enabled = &debug_enabled; + chip->ipc_log = ipc_log_context_create(MMI_LOG_PAGES, MMI_LOG_DIR, 0); + if (!chip->ipc_log) + mmi_err(chip, "Failed to create mmi charger IPC log\n"); + else + mmi_info(chip, "IPC logging is enabled for mmi charger\n"); + + rc = mmi_parse_dt(chip); + if (rc) { + mmi_err(chip, "Failed to parse device tree\n"); + rc = -EINVAL; + goto exit; + } + + this_chip = chip; + mutex_init(&chip->charger_lock); + mutex_init(&chip->battery_lock); + INIT_LIST_HEAD(&chip->charger_list); + INIT_LIST_HEAD(&chip->battery_list); + INIT_DELAYED_WORK(&chip->heartbeat_work, mmi_charger_heartbeat_work); + chip->mmi_hb_wake_source = wakeup_source_register( +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,00) + chip->dev, +#endif + "mmi_hb_wake"); + alarm_init(&chip->heartbeat_alarm, ALARM_BOOTTIME, + mmi_heartbeat_alarm_cb); + + chip->mmi_psy = devm_power_supply_register(chip->dev, + &mmi_psy_desc, + &psy_cfg); + if (IS_ERR(chip->mmi_psy)) { + mmi_err(chip, "failed: mmi power supply register\n"); + rc = PTR_ERR(chip->mmi_psy); + goto exit; + } + + chip->batt_psy = power_supply_get_by_name("battery"); + if (chip->batt_psy) { + rc = sysfs_create_group(&chip->batt_psy->dev.kobj, + &power_supply_mmi_attr_group); + if (rc) + mmi_err(chip, "failed: attr create\n"); + } + + rc = device_create_file(chip->dev, + &dev_attr_dcp_pmax); + if (rc) { + mmi_err(chip, "couldn't create dcp_pmax\n"); + } + + rc = device_create_file(chip->dev, + &dev_attr_hvdcp_pmax); + if (rc) { + mmi_err(chip, "couldn't create hvdcp_pmax\n"); + } + + rc = device_create_file(chip->dev, + &dev_attr_force_demo_mode); + if (rc) { + mmi_err(chip, "couldn't create force_demo_mode\n"); + } + + rc = device_create_file(chip->dev, + &dev_attr_force_max_chrg_temp); + if (rc) { + mmi_err(chip, "couldn't create force_max_chrg_temp\n"); + } + + rc = device_create_file(chip->dev, + &dev_attr_factory_image_mode); + if (rc) { + mmi_err(chip, "couldn't create factory_image_mode\n"); + } + + rc = device_create_file(chip->dev, + &dev_attr_factory_charge_upper); + if (rc) + mmi_err(chip, "couldn't create factory_charge_upper\n"); + + /* Register the notifier for the psy updates*/ + chip->mmi_psy_notifier.notifier_call = mmi_psy_notifier_call; + rc = power_supply_reg_notifier(&chip->mmi_psy_notifier); + if (rc) + mmi_err(chip, "Failed to reg notifier: %d\n", rc); + + if (chip->factory_mode) { + mmi_info(chip, "Entering Factory Mode!\n"); + chip->mmi_reboot.notifier_call = mmi_charger_reboot; + chip->mmi_reboot.next = NULL; + chip->mmi_reboot.priority = 1; + rc = register_reboot_notifier(&chip->mmi_reboot); + if (rc) + mmi_err(chip, "Register for reboot failed\n"); + } + + cancel_delayed_work(&chip->heartbeat_work); + schedule_delayed_work(&chip->heartbeat_work, + msecs_to_jiffies(0)); + + mmi_info(chip, "MMI charger probed successfully!\n"); + return 0; +exit: + ipc_log_context_destroy(chip->ipc_log); + + return rc; +} + +static int mmi_charger_remove(struct platform_device *pdev) +{ + struct mmi_charger_chip *chip = platform_get_drvdata(pdev); + + if (!list_empty(&chip->charger_list)) { + return -EBUSY; + } + + cancel_delayed_work(&chip->heartbeat_work); + + if (chip->factory_mode) + unregister_reboot_notifier(&chip->mmi_reboot); + power_supply_unreg_notifier(&chip->mmi_psy_notifier); + device_remove_file(chip->dev, &dev_attr_force_demo_mode); + device_remove_file(chip->dev, &dev_attr_force_max_chrg_temp); + device_remove_file(chip->dev, &dev_attr_factory_image_mode); + device_remove_file(chip->dev, &dev_attr_factory_charge_upper); + device_remove_file(chip->dev, &dev_attr_dcp_pmax); + device_remove_file(chip->dev, &dev_attr_hvdcp_pmax); + if (chip->batt_psy) { + if (chip->batt_uenvp[0]) { + kfree(chip->batt_uenvp[0]); + chip->batt_uenvp[0] = NULL; + } + sysfs_remove_group(&chip->batt_psy->dev.kobj, + &power_supply_mmi_attr_group); + power_supply_put(chip->batt_psy); + } + wakeup_source_unregister(chip->mmi_hb_wake_source); + ipc_log_context_destroy(chip->ipc_log); + + return 0; +} + +static void mmi_charger_shutdown(struct platform_device *pdev) +{ + struct mmi_charger_chip *chip = platform_get_drvdata(pdev); + + if (chip->batt_uenvp[0]) { + kfree(chip->batt_uenvp[0]); + chip->batt_uenvp[0] = NULL; + } + wakeup_source_unregister(chip->mmi_hb_wake_source); + + return; +} + +#ifdef CONFIG_PM_SLEEP +static int mmi_charger_suspend(struct device *device) +{ + struct platform_device *pdev = to_platform_device(device); + struct mmi_charger_chip *chip = platform_get_drvdata(pdev); + + chip->suspended &= ~WAS_SUSPENDED; + chip->suspended |= IS_SUSPENDED; + + return 0; +} + +static int mmi_charger_resume(struct device *device) +{ + struct platform_device *pdev = to_platform_device(device); + struct mmi_charger_chip *chip = platform_get_drvdata(pdev); + + chip->suspended &= ~IS_SUSPENDED; + chip->suspended |= WAS_SUSPENDED; + + return 0; +} +#else +#define mmi_charger_suspend NULL +#define mmi_charger_resume NULL +#endif + +static const struct dev_pm_ops mmi_charger_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mmi_charger_suspend, mmi_charger_resume) +}; + +static const struct of_device_id match_table[] = { + { .compatible = "mmi,mmi-charger", }, + { }, +}; + +static struct platform_driver mmi_charger_driver = { + .driver = { + .name = "mmi,mmi-charger", + .owner = THIS_MODULE, + .pm = &mmi_charger_dev_pm_ops, + .of_match_table = match_table, + }, + .probe = mmi_charger_probe, + .remove = mmi_charger_remove, + .shutdown = mmi_charger_shutdown, +}; +module_platform_driver(mmi_charger_driver); + +MODULE_DESCRIPTION("MMI Charger Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/mmi_charger/mmi_charger.h b/drivers/power/mmi_charger/mmi_charger.h new file mode 100644 index 000000000000..048e28ed06c9 --- /dev/null +++ b/drivers/power/mmi_charger/mmi_charger.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 Motorola Mobility LLC + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MMI_CHARGER_H__ +#define __MMI_CHARGER_H__ + +#include +#include + +#define mmi_err(chg, fmt, ...) \ + do { \ + pr_err("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + ipc_log_string(chg->ipc_log, \ + "E %s: %s: " fmt, chg->name, __func__, ##__VA_ARGS__); \ + } while (0) + +#define mmi_warn(chg, fmt, ...) \ + do { \ + pr_warn("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + ipc_log_string(chg->ipc_log, \ + "W %s: %s: " fmt, chg->name, __func__, ##__VA_ARGS__); \ + } while (0) + +#define mmi_info(chg, fmt, ...) \ + do { \ + pr_info("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + ipc_log_string(chg->ipc_log, \ + "I %s: %s: " fmt, chg->name, __func__, ##__VA_ARGS__); \ + } while (0) + +#define mmi_dbg(chg, fmt, ...) \ + do { \ + if (*chg->debug_enabled) \ + pr_info("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + else \ + pr_debug("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + ipc_log_string(chg->ipc_log, \ + "D %s: %s: " fmt, chg->name, __func__, ##__VA_ARGS__); \ + } while (0) + +#define MMI_LOG_PAGES (50) +#define MMI_LOG_DIR "mmi_charger" +#define MMI_BATT_SN_LEN 16 +#define CHG_SHOW_MAX_SIZE 50 + +struct mmi_battery_info { + int batt_mv; + int batt_ma; + int batt_soc; + int batt_temp; + int batt_status; + int batt_full_uah; + int batt_design_uah; + char batt_sn[MMI_BATT_SN_LEN]; +}; + +struct mmi_charger_info { + int chrg_mv; + int chrg_ma; + int chrg_type; + int chrg_pmax_mw; + int chrg_present; +}; + +struct mmi_charger_cfg { + int target_fcc; + int target_fv; + int fg_iterm; + int chrg_iterm; + bool full_charged; + bool charging_reset; + bool taper_kickoff; + bool charging_disable; + bool charger_suspend; + + int demo_mode; + bool factory_mode; + bool factory_version; + + int dcp_pmax; + int hvdcp_pmax; +}; + +struct mmi_charger_driver { + char *name; + struct device *dev; + int (*get_batt_info)(void *data, struct mmi_battery_info *batt_info); + int (*get_chg_info)(void *data, struct mmi_charger_info *chg_info); + int (*config_charge)(void *data, struct mmi_charger_cfg *config); + bool (*is_charge_tapered)(void *data, int tapered_ma); + bool (*is_charge_halt)(void *data); + void *data; +}; + +bool mmi_is_factory_mode(void); +bool mmi_is_factory_version(void); +const char *mmi_get_battery_serialnumber(void); +void mmi_get_charger_configure(struct mmi_charger_driver *driver); +int mmi_register_charger_driver(struct mmi_charger_driver *driver); +int mmi_unregister_charger_driver(struct mmi_charger_driver *driver); + +#endif