From 4f7f110863b074709a52d8b364f88f927a2bd019 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Mon, 19 Sep 2016 16:30:03 -0700 Subject: of_batterydata: add snapshot of DT bindings documentation for batterydata This DT bindings documentation snapshot is taken as of msm-3.18 'commit b428af643156 ("power: qpnp-fg: configure thermal coefficients based on battery type")' While at it, change the name used in the documentation from Qualcomm to Qualcomm Technologies, Inc. CRs-Fixed: 1068593 Change-Id: Ib359e41b7381149a270a5772540bbc80f920919f Signed-off-by: Subbaraman Narayanamurthy --- .../bindings/batterydata/batterydata.txt | 221 +++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 Documentation/devicetree/bindings/batterydata/batterydata.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/batterydata/batterydata.txt b/Documentation/devicetree/bindings/batterydata/batterydata.txt new file mode 100644 index 000000000000..39f9375a6c48 --- /dev/null +++ b/Documentation/devicetree/bindings/batterydata/batterydata.txt @@ -0,0 +1,221 @@ +Battery Profile Data + +Battery Data is a collection of battery profile data made available to +the QPNP Charger and BMS drivers via device tree. + +qcom,battery-data node required properties: +- qcom,rpull-up-kohm : The vadc pullup resistor's resistance value in kOhms. +- qcom,vref-batt-therm-uv : The vadc voltage used to make readings. + For Qualcomm Technologies, Inc. VADCs, this should be + 1800000uV. + +qcom,battery-data node optional properties: +- qcom,batt-id-range-pct : The area of variation between upper and lower bound + for which a given battery ID resistance is valid. This + value is expressed as a percentage of the specified kohm + resistance provided by qcom,batt-id-kohm. + +qcom,battery-data can also include any number of children nodes. These children +nodes will be treated as battery profile data nodes. + +Profile data node required properties: +- qcom,fcc-mah : Full charge count of the battery in milliamp-hours +- qcom,default-rbatt-mohm : The nominal battery resistance value +- qcom,rbatt-capacitive-mohm : The capacitive resistance of the battery. +- qcom,flat-ocv-threshold-uv : The threshold under which the battery can be + considered to be in the flat portion of the discharge + curve. +- qcom,max-voltage-uv : The maximum rated voltage of the battery +- qcom,v-cutoff-uv : The cutoff voltage of the battery at which the device + should shutdown gracefully. +- qcom,chg-term-ua : The termination charging current of the battery. +- qcom,batt-id-kohm : The battery id resistance of the battery. It can be + used as an array which could support multiple IDs for one battery + module when the ID resistance of some battery modules goes across + several ranges. +- qcom,battery-type : A string indicating the type of battery. +- qcom,fg-profile-data : An array of hexadecimal values used to configure more + complex fuel gauge peripherals which have a large amount + of coefficients used in hardware state machines and thus + influencing the final output of the state of charge read + by software. + +Profile data node optional properties: +- qcom,chg-rslow-comp-c1 : A constant for rslow compensation in the fuel gauge. + This will be provided by the profiling tool for + additional fuel gauge accuracy during charging. +- qcom,chg-rslow-comp-c2 : A constant for rslow compensation in the fuel gauge. + This will be provided by the profiling tool for + additional fuel gauge accuracy during charging. +- qcom,chg-rslow-comp-thr : A constant for rslow compensation in the fuel gauge. + This will be provided by the profiling tool for + additional fuel gauge accuracy during charging. +- qcom,chg-rs-to-rslow: A constant for rslow compensation in the fuel gauge. + This will be provided by the profiling tool for + additional fuel gauge accuracy during charging. +- qcom,fastchg-current-ma: Specifies the maximum fastcharge current. +- qcom,fg-cc-cv-threshold-mv: Voltage threshold in mV for transition from constant + charge (CC) to constant voltage (CV). This value should + be 10 mV less than the float voltage. + This property should only be specified if + "qcom,autoadjust-vfloat" property is specified in the + charger driver to ensure a proper operation. +- qcom,thermal-coefficients: Byte array of thermal coefficients for reading + battery thermistor. This should be exactly 6 bytes + in length. + Example: [01 02 03 04 05 06] + +Profile data node required subnodes: +- qcom,fcc-temp-lut : An 1-dimensional lookup table node that encodes + temperature to fcc lookup. The units for this lookup + table should be degrees celsius to milliamp-hours. +- qcom,pc-temp-ocv-lut : A 2-dimensional lookup table node that encodes + temperature and percent charge to open circuit voltage + lookup. The units for this lookup table should be + degrees celsius and percent to millivolts. +- qcom,rbatt-sf-lut : A 2-dimentional lookup table node that encodes + temperature and percent charge to battery internal + resistance lookup. The units for this lookup table + should be degrees celsius and percent to milliohms. + +Profile data node optional subnodes: +- qcom,ibat-acc-luit: A 2-dimentional lookup table that encodes temperature + and battery current to battery ACC (apparent charge + capacity). The units for this lookup table should be + temperature in degrees celsius, ibat in milli-amps + and ACC in milli-ampere-hour. + +Lookup table required properties: +- qcom,lut-col-legend : An array that encodes the legend of the lookup table's + columns. The length of this array will determine the + lookup table's width. +- qcom,lut-data : An array that encodes the lookup table's data. The size of this + array should be equal to the size of qcom,lut-col-legend + multiplied by 1 if it's a 1-dimensional table, or + the size of qcom,lut-row-legend if it's a 2-dimensional + table. The data should be in a flattened row-major + representation. + +Lookup table optional properties: +- qcom,lut-row-legend : An array that encodes the legend of the lookup table's rows. + If this property exists, then it is assumed that the + lookup table is a 2-dimensional table. + +Example: + +In msm8974-mtp.dtsi: + +mtp_batterydata: qcom,battery-data { + qcom,rpull-up-kohm = <100>; + qcom,vref-batt-therm-uv = <1800000>; + + /include/ "batterydata-palladium.dtsi" + /include/ "batterydata-mtp-3000mah.dtsi" +}; + +&pm8941_bms { + qcom,battery-data = <&mtp_batterydata>; +}; + +In batterydata-palladium.dtsi: + +qcom,palladium-batterydata { + qcom,fcc-mah = <1500>; + qcom,default-rbatt-mohm = <236>; + qcom,rbatt-capacitive-mohm = <50>; + qcom,flat-ocv-threshold-uv = <3800000>; + qcom,max-voltage-uv = <4200000>; + qcom,v-cutoff-uv = <3400000>; + qcom,chg-term-ua = <100000>; + qcom,batt-id-kohm = <75>; + qcom,battery-type = "palladium_1500mah"; + + qcom,fcc-temp-lut { + qcom,lut-col-legend = <(-20) 0 25 40 65>; + qcom,lut-data = <1492 1492 1493 1483 1502>; + }; + + qcom,pc-temp-ocv-lut { + qcom,lut-col-legend = <(-20) 0 25 40 65>; + qcom,lut-row-legend = <100 95 90 85 80 75 70>, + <65 60 55 50 45 40 35>, + <30 25 20 15 10 9 8>, + <7 6 5 4 3 2 1 0>; + qcom,lut-data = <4173 4167 4163 4156 4154>, + <4104 4107 4108 4102 4104>, + <4057 4072 4069 4061 4060>, + <3973 4009 4019 4016 4020>, + <3932 3959 3981 3982 3983>, + <3899 3928 3954 3950 3950>, + <3868 3895 3925 3921 3920>, + <3837 3866 3898 3894 3892>, + <3812 3841 3853 3856 3862>, + <3794 3818 3825 3823 3822>, + <3780 3799 3804 3804 3803>, + <3768 3787 3790 3788 3788>, + <3757 3779 3778 3775 3776>, + <3747 3772 3771 3766 3765>, + <3736 3763 3766 3760 3746>, + <3725 3749 3756 3747 3729>, + <3714 3718 3734 3724 3706>, + <3701 3703 3696 3689 3668>, + <3675 3695 3682 3675 3662>, + <3670 3691 3680 3673 3661>, + <3661 3686 3679 3672 3656>, + <3649 3680 3676 3669 3641>, + <3633 3669 3667 3655 3606>, + <3610 3647 3640 3620 3560>, + <3580 3607 3596 3572 3501>, + <3533 3548 3537 3512 3425>, + <3457 3468 3459 3429 3324>, + <3328 3348 3340 3297 3172>, + <3000 3000 3000 3000 3000>; + }; + + qcom,rbatt-sf-lut { + qcom,lut-col-legend = <(-20) 0 25 40 65>; + qcom,lut-row-legend = <100 95 90 85 80 75 70>, + <65 60 55 50 45 40 35>, + <30 25 20 15 10 9 8>, + <7 6 5 4 3 2 1 0>; + qcom,lut-data = <357 187 100 91 91>, + <400 208 105 94 94>, + <390 204 106 95 96>, + <391 201 108 98 98>, + <391 202 110 98 100>, + <390 200 110 99 102>, + <389 200 110 99 102>, + <393 202 101 93 100>, + <407 205 99 89 94>, + <428 208 100 91 96>, + <455 212 102 92 98>, + <495 220 104 93 101>, + <561 232 107 95 102>, + <634 245 112 98 98>, + <714 258 114 98 98>, + <791 266 114 97 100>, + <871 289 108 95 97>, + <973 340 124 108 105>, + <489 241 109 96 99>, + <511 246 110 96 99>, + <534 252 111 95 98>, + <579 263 112 96 96>, + <636 276 111 95 97>, + <730 294 109 96 99>, + <868 328 112 98 104>, + <1089 374 119 101 115>, + <1559 457 128 105 213>, + <12886 1026 637 422 3269>, + <170899 127211 98968 88907 77102>; + }; + + qcom,ibat-acc-lut { + qcom,lut-col-legend = <(-20) 0 25>; + qcom,lut-row-legend = <0 250 500 1000>; + qcom,lut-data = <1470 1470 1473>, + <1406 1406 1430>, + <1247 1247 1414>, + <764 764 1338>; + }; +}; + -- cgit v1.2.3 From c10ab2638cce2f51518ff63088fbb74ca53306a4 Mon Sep 17 00:00:00 2001 From: Nicholas Troast Date: Wed, 24 Aug 2016 09:35:11 -0700 Subject: qpnp-fg-gen3: add support for bucket cycle counter The SOC range is divided into buckets. Whenever the battery is charged across a bucket we increment the counter for that bucket. These cycle counters are exposed through the "cycle_counter" property, and buckets are selected by modifying the "cycle_counter_id" property. Change-Id: I923c657132d071eff4b5bc61c00fb90de8dde62c Signed-off-by: Nicholas Troast --- .../bindings/power/qcom-charger/qpnp-fg-gen3.txt | 5 + drivers/power/qcom-charger/fg-core.h | 15 + drivers/power/qcom-charger/qpnp-fg-gen3.c | 527 ++++++++++++++------- 3 files changed, 379 insertions(+), 168 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt index 199b5c0857f8..bd358593fcb3 100644 --- a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt +++ b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt @@ -140,6 +140,11 @@ First Level Node - FG Gen3 device asleep and the battery is discharging. This option requires qcom,fg-esr-timer-awake to be defined. +- qcom,cycle-counter-en + Usage: optional + Value type: + Definition: Enables the cycle counter feature. + ========================================================== Second Level Nodes - Peripherals managed by FG Gen3 driver ========================================================== diff --git a/drivers/power/qcom-charger/fg-core.h b/drivers/power/qcom-charger/fg-core.h index 4feaaa0e0c4e..515f31a44ce7 100644 --- a/drivers/power/qcom-charger/fg-core.h +++ b/drivers/power/qcom-charger/fg-core.h @@ -54,6 +54,8 @@ CHARS_PER_ITEM) + 1) \ #define FG_SRAM_ADDRESS_MAX 255 +#define BUCKET_COUNT 8 +#define BUCKET_SOC_PCT (256 / BUCKET_COUNT) /* Debug flag definitions */ enum fg_debug_flag { @@ -186,6 +188,15 @@ struct fg_batt_props { int batt_id_kohm; }; +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_irq_info { const char *name; const irq_handler_t handler; @@ -209,6 +220,7 @@ struct fg_chip { char *batt_profile; struct fg_dt_props dt; struct fg_batt_props bp; + struct fg_cyc_ctr_data cyc_ctr; struct notifier_block nb; struct mutex bus_lock; struct mutex sram_rw_lock; @@ -216,6 +228,8 @@ struct fg_chip { u32 batt_info_base; u32 mem_if_base; int nom_cap_uah; + int status; + int prev_status; bool batt_id_avail; bool profile_loaded; bool battery_missing; @@ -223,6 +237,7 @@ struct fg_chip { struct completion soc_ready; struct delayed_work profile_load_work; struct work_struct status_change_work; + struct work_struct cycle_count_work; struct fg_alg_flag *alg_flags; }; diff --git a/drivers/power/qcom-charger/qpnp-fg-gen3.c b/drivers/power/qcom-charger/qpnp-fg-gen3.c index a163061156ba..e0b1d13ca8bf 100644 --- a/drivers/power/qcom-charger/qpnp-fg-gen3.c +++ b/drivers/power/qcom-charger/qpnp-fg-gen3.c @@ -57,6 +57,8 @@ #define PROFILE_LOAD_OFFSET 0 #define NOM_CAP_WORD 58 #define NOM_CAP_OFFSET 0 +#define CYCLE_COUNT_WORD 75 +#define CYCLE_COUNT_OFFSET 0 #define PROFILE_INTEGRITY_WORD 79 #define PROFILE_INTEGRITY_OFFSET 3 #define BATT_SOC_WORD 91 @@ -92,6 +94,8 @@ 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_batt_soc(struct fg_sram_param *sp, + enum fg_sram_param_id id, int val); static void fg_encode_voltage(struct fg_sram_param *sp, enum fg_sram_param_id id, int val, u8 *buf); static void fg_encode_current(struct fg_sram_param *sp, @@ -114,7 +118,7 @@ static void fg_encode_default(struct fg_sram_param *sp, static struct fg_sram_param pmicobalt_v1_sram_params[] = { PARAM(BATT_SOC, BATT_SOC_WORD, BATT_SOC_OFFSET, 4, 1, 1, 0, NULL, - fg_decode_default), + fg_decode_batt_soc), PARAM(VOLTAGE_PRED, VOLTAGE_PRED_WORD, VOLTAGE_PRED_OFFSET, 2, 244141, 1000, 0, NULL, fg_decode_value_16b), PARAM(OCV, OCV_WORD, OCV_OFFSET, 2, 244141, 1000, 0, NULL, @@ -152,7 +156,7 @@ static struct fg_sram_param pmicobalt_v1_sram_params[] = { static struct fg_sram_param pmicobalt_v2_sram_params[] = { PARAM(BATT_SOC, BATT_SOC_WORD, BATT_SOC_OFFSET, 4, 1, 1, 0, NULL, - fg_decode_default), + fg_decode_batt_soc), PARAM(VOLTAGE_PRED, VOLTAGE_PRED_WORD, VOLTAGE_PRED_OFFSET, 2, 244141, 1000, 0, NULL, fg_decode_value_16b), PARAM(OCV, OCV_WORD, OCV_OFFSET, 2, 244141, 1000, 0, NULL, @@ -260,171 +264,6 @@ module_param_named( sram_update_period_ms, fg_sram_update_period_ms, int, S_IRUSR | S_IWUSR ); -/* Other functions HERE */ - -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 is_charger_available(struct fg_chip *chip) -{ - if (!chip->batt_psy) - chip->batt_psy = power_supply_get_by_name("battery"); - - if (!chip->batt_psy) - return false; - - return true; -} - -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, }; - - if (!is_charger_available(chip)) { - fg_dbg(chip, FG_STATUS, "Charger not available?!\n"); - return; - } - - power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS, - &prop); - switch (prop.intval) { - case POWER_SUPPLY_STATUS_CHARGING: - fg_dbg(chip, FG_POWER_SUPPLY, "Charging\n"); - break; - case POWER_SUPPLY_STATUS_DISCHARGING: - fg_dbg(chip, FG_POWER_SUPPLY, "Discharging\n"); - break; - case POWER_SUPPLY_STATUS_FULL: - fg_dbg(chip, FG_POWER_SUPPLY, "Full\n"); - break; - default: - break; - } -} - -#define PROFILE_LEN 224 -#define PROFILE_COMP_LEN 32 -#define SOC_READY_WAIT_MS 2000 -static void profile_load_work(struct work_struct *work) -{ - struct fg_chip *chip = container_of(work, - struct fg_chip, - profile_load_work.work); - int rc; - u8 buf[PROFILE_COMP_LEN], val; - bool tried_again = false, profiles_same = false; - - if (!chip->batt_id_avail) { - pr_err("batt_id not available\n"); - return; - } - - 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; - } - - vote(chip->awake_votable, PROFILE_LOAD, true, 0); - if (val == 0x01) { - fg_dbg(chip, FG_STATUS, "Battery profile integrity bit is set\n"); - 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); - goto out; - } - profiles_same = memcmp(chip->batt_profile, buf, - PROFILE_COMP_LEN) == 0; - if (profiles_same) { - fg_dbg(chip, FG_STATUS, "Battery profile is same\n"); - goto done; - } - fg_dbg(chip, FG_STATUS, "profiles are different?\n"); - } - - 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_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); - goto out; - } - - fg_dbg(chip, FG_STATUS, "SOC is ready\n"); - - /* Set the profile integrity bit */ - val = 0x1; - 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; - } - - fg_dbg(chip, FG_STATUS, "profile loaded successfully"); -done: - 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); - goto out; - } - - chip->nom_cap_uah = (int)(buf[0] | buf[1] << 8) * 1000; - chip->profile_loaded = true; -out: - vote(chip->awake_votable, PROFILE_LOAD, false, 0); - 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); -} - /* All getters HERE */ static int fg_decode_value_16b(struct fg_sram_param *sp, @@ -436,8 +275,17 @@ static int fg_decode_value_16b(struct fg_sram_param *sp, return sp[id].value; } -static int fg_decode_default(struct fg_sram_param *sp, +static int fg_decode_batt_soc(struct fg_sram_param *sp, enum fg_sram_param_id id, int value) +{ + sp[id].value = (u32)value >> 24; + 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) { return value; } @@ -752,6 +600,7 @@ static int fg_get_batt_id(struct fg_chip *chip, int *val) return 0; } +#define PROFILE_LEN 224 static int fg_get_batt_profile(struct fg_chip *chip) { struct device_node *node = chip->dev->of_node; @@ -863,6 +712,316 @@ static int fg_set_esr_timer(struct fg_chip *chip, int cycles, bool charging, return 0; } +/* Other functions HERE */ + +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 is_charger_available(struct fg_chip *chip) +{ + if (!chip->batt_psy) + chip->batt_psy = power_supply_get_by_name("battery"); + + if (!chip->batt_psy) + return false; + + return true; +} + +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, }; + + if (!is_charger_available(chip)) { + fg_dbg(chip, FG_STATUS, "Charger not available?!\n"); + return; + } + + power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS, + &prop); + chip->prev_status = chip->status; + chip->status = prop.intval; + + if (chip->cyc_ctr.en && chip->prev_status != chip->status) + schedule_work(&chip->cycle_count_work); + + switch (prop.intval) { + case POWER_SUPPLY_STATUS_CHARGING: + fg_dbg(chip, FG_POWER_SUPPLY, "Charging\n"); + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + fg_dbg(chip, FG_POWER_SUPPLY, "Discharging\n"); + break; + case POWER_SUPPLY_STATUS_FULL: + fg_dbg(chip, FG_POWER_SUPPLY, "Full\n"); + break; + default: + break; + } +} + +static void restore_cycle_counter(struct fg_chip *chip) +{ + int rc = 0, i; + u8 data[2]; + + 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); + else + chip->cyc_ctr.count[bucket] = cyc_count; + return rc; +} + +static void cycle_count_work(struct work_struct *work) +{ + int rc = 0, bucket, i, batt_soc; + struct fg_chip *chip = container_of(work, + struct fg_chip, + cycle_count_work); + + 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; + } + + if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { + /* Find out which bucket the SOC falls in */ + bucket = batt_soc / BUCKET_SOC_PCT; + pr_debug("batt_soc: %d bucket: %d\n", batt_soc, bucket); + + /* + * If we've started counting for the previous bucket, + * then store the counter for that bucket if the + * counter for current bucket is getting started. + */ + if (bucket > 0 && chip->cyc_ctr.started[bucket - 1] && + !chip->cyc_ctr.started[bucket]) { + rc = fg_inc_store_cycle_ctr(chip, bucket - 1); + if (rc < 0) { + pr_err("Error in storing cycle_ctr rc: %d\n", + rc); + goto out; + } else { + chip->cyc_ctr.started[bucket - 1] = false; + chip->cyc_ctr.last_soc[bucket - 1] = 0; + } + } + if (!chip->cyc_ctr.started[bucket]) { + chip->cyc_ctr.started[bucket] = true; + chip->cyc_ctr.last_soc[bucket] = batt_soc; + } + } else { + for (i = 0; i < BUCKET_COUNT; i++) { + if (chip->cyc_ctr.started[i] && + batt_soc > chip->cyc_ctr.last_soc[i]) { + 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; + } + } +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; +} + +#define PROFILE_COMP_LEN 32 +#define SOC_READY_WAIT_MS 2000 +static void profile_load_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + profile_load_work.work); + int rc; + u8 buf[PROFILE_COMP_LEN], val; + bool tried_again = false, profiles_same = false; + + if (!chip->batt_id_avail) { + pr_err("batt_id not available\n"); + return; + } + + 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; + } + + vote(chip->awake_votable, PROFILE_LOAD, true, 0); + if (val == 0x01) { + fg_dbg(chip, FG_STATUS, "Battery profile integrity bit is set\n"); + 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); + goto out; + } + profiles_same = memcmp(chip->batt_profile, buf, + PROFILE_COMP_LEN) == 0; + if (profiles_same) { + fg_dbg(chip, FG_STATUS, "Battery profile is same\n"); + goto done; + } + fg_dbg(chip, FG_STATUS, "profiles are different?\n"); + } + + clear_cycle_counter(chip); + 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_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); + goto out; + } + + fg_dbg(chip, FG_STATUS, "SOC is ready\n"); + + /* Set the profile integrity bit */ + val = 0x1; + 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; + } + + fg_dbg(chip, FG_STATUS, "profile loaded successfully"); +done: + 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); + goto out; + } + + chip->nom_cap_uah = (int)(buf[0] | buf[1] << 8) * 1000; + chip->profile_loaded = true; +out: + vote(chip->awake_votable, PROFILE_LOAD, false, 0); + 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); +} + /* PSY CALLBACKS STAY HERE */ static int fg_psy_get_property(struct power_supply *psy, @@ -902,6 +1061,11 @@ static int fg_psy_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: pval->intval = chip->bp.float_volt_uv; + 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; default: break; @@ -914,7 +1078,18 @@ 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); + 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; default: break; } @@ -926,6 +1101,8 @@ static int fg_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { + case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: + return 1; default: break; } @@ -965,6 +1142,8 @@ static enum power_supply_property fg_psy_props[] = { 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, }; static const struct power_supply_desc fg_psy_desc = { @@ -1121,6 +1300,9 @@ static int fg_hw_init(struct fg_chip *chip) } } + if (chip->cyc_ctr.en) + restore_cycle_counter(chip); + return 0; } @@ -1180,6 +1362,7 @@ static irqreturn_t fg_batt_missing_irq_handler(int irq, void *data) if (chip->battery_missing) { chip->batt_id_avail = false; chip->profile_loaded = false; + clear_cycle_counter(chip); } else { rc = fg_batt_profile_init(chip); if (rc < 0) { @@ -1222,6 +1405,9 @@ static irqreturn_t fg_delta_soc_irq_handler(int irq, void *data) { struct fg_chip *chip = data; + if (chip->cyc_ctr.en) + schedule_work(&chip->cycle_count_work); + fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq); return IRQ_HANDLED; } @@ -1528,6 +1714,9 @@ static int fg_parse_dt(struct fg_chip *chip) else chip->dt.esr_timer_asleep = temp; + chip->cyc_ctr.en = of_property_read_bool(node, "qcom,cycle-counter-en"); + if (chip->cyc_ctr.en) + chip->cyc_ctr.id = 1; return 0; } @@ -1580,10 +1769,12 @@ static int fg_gen3_probe(struct platform_device *pdev) mutex_init(&chip->bus_lock); mutex_init(&chip->sram_rw_lock); + mutex_init(&chip->cyc_ctr.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_WORK(&chip->cycle_count_work, cycle_count_work); rc = fg_memif_init(chip); if (rc < 0) { -- cgit v1.2.3 From 0ceb2fcb8f8677562a2fb4865d5860d6ec829ee8 Mon Sep 17 00:00:00 2001 From: Harry Yang Date: Fri, 16 Sep 2016 11:14:49 -0700 Subject: qpnp-smb2: introduce workaround bit mask Add a bit mask to enable workarounds only if they are needed for a particular hardware revision. Change-Id: Ibd9a896ff6746a48ddab249d7c8ab762ed3c2fbe Signed-off-by: Harry Yang Signed-off-by: Nicholas Troast Signed-off-by: Subbaraman Narayanamurthy --- .../bindings/power/qcom-charger/qpnp-smb2.txt | 6 +++ drivers/power/qcom-charger/qpnp-smb2.c | 43 ++++++++++++++++++++++ drivers/power/qcom-charger/smb-lib.h | 3 ++ 3 files changed, 52 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt index 1c5dd91891dc..21404dfc4b7b 100644 --- a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt +++ b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt @@ -21,6 +21,12 @@ Charger specific properties: Value type: Definition: "qcom,qpnp-smb2". +- qcom,pmic-revid + Usage: required + Value type: phandle + Definition: Should specify the phandle of PMI's revid module. This is used to + identify the PMI subtype. + - qcom,batteryless-platform Usage: optional Value type: diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index d19e7827ed83..b6a3e8600d7c 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -205,6 +206,7 @@ struct smb_dt_props { int wipower_max_uw; u32 step_soc_threshold[STEP_CHARGING_MAX_STEPS - 1]; s32 step_cc_delta[STEP_CHARGING_MAX_STEPS]; + struct device_node *revid_dev_node; }; struct smb2 { @@ -1085,6 +1087,40 @@ static int smb2_init_hw(struct smb2 *chip) return rc; } +static int smb2_setup_wa_flags(struct smb2 *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 PMICOBALT_SUBTYPE: + break; + default: + pr_err("PMIC subtype %d not supported\n", + pmic_rev_id->pmic_subtype); + return -EINVAL; + } + + return 0; +} + /**************************** * DETERMINE INITIAL STATUS * ****************************/ @@ -1270,6 +1306,13 @@ static int smb2_probe(struct platform_device *pdev) return -EINVAL; } + rc = smb2_setup_wa_flags(chip); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + pr_err("Couldn't setup wa flags rc=%d\n", rc); + return rc; + } + rc = smblib_init(chg); if (rc < 0) { pr_err("Smblib_init failed rc=%d\n", rc); diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index 289fb1706e97..a9703921e8b3 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -161,6 +161,9 @@ struct smb_charger { bool step_chg_enabled; bool is_hdc; + + /* workaround flag */ + u32 wa_flags; }; int smblib_read(struct smb_charger *chg, u16 addr, u8 *val); -- cgit v1.2.3