blob: 053aac3ceac8cb99a26e7a4d5501fbdb8b2cca11 [file] [log] [blame]
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +05301/* Copyright (c) 2017 The Linux Foundation. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12#define pr_fmt(fmt) "QCOM-STEPCHG: %s: " fmt, __func__
13
14#include <linux/delay.h>
15#include <linux/module.h>
16#include <linux/power_supply.h>
17#include <linux/slab.h>
18#include <linux/pmic-voter.h>
19#include "step-chg-jeita.h"
20
21#define MAX_STEP_CHG_ENTRIES 8
22#define STEP_CHG_VOTER "STEP_CHG_VOTER"
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053023#define JEITA_VOTER "JEITA_VOTER"
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +053024
25#define is_between(left, right, value) \
26 (((left) >= (right) && (left) >= (value) \
27 && (value) >= (right)) \
28 || ((left) <= (right) && (left) <= (value) \
29 && (value) <= (right)))
30
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053031struct range_data {
32 u32 low_threshold;
33 u32 high_threshold;
34 u32 value;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +053035};
36
37struct step_chg_cfg {
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053038 u32 psy_prop;
39 char *prop_name;
40 int hysteresis;
41 struct range_data fcc_cfg[MAX_STEP_CHG_ENTRIES];
42};
43
44struct jeita_fcc_cfg {
45 u32 psy_prop;
46 char *prop_name;
47 int hysteresis;
48 struct range_data fcc_cfg[MAX_STEP_CHG_ENTRIES];
49};
50
51struct jeita_fv_cfg {
52 u32 psy_prop;
53 char *prop_name;
54 int hysteresis;
55 struct range_data fv_cfg[MAX_STEP_CHG_ENTRIES];
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +053056};
57
58struct step_chg_info {
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053059 ktime_t step_last_update_time;
60 ktime_t jeita_last_update_time;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +053061 bool step_chg_enable;
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053062 bool sw_jeita_enable;
63 int jeita_fcc_index;
64 int jeita_fv_index;
65 int step_index;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +053066
67 struct votable *fcc_votable;
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053068 struct votable *fv_votable;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +053069 struct wakeup_source *step_chg_ws;
70 struct power_supply *batt_psy;
71 struct delayed_work status_change_work;
72 struct notifier_block nb;
73};
74
75static struct step_chg_info *the_chip;
76
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053077#define STEP_CHG_HYSTERISIS_DELAY_US 5000000 /* 5 secs */
78
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +053079/*
80 * Step Charging Configuration
81 * Update the table based on the battery profile
82 * Supports VBATT and SOC based source
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053083 * range data must be in increasing ranges and shouldn't overlap
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +053084 */
85static struct step_chg_cfg step_chg_config = {
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053086 .psy_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW,
87 .prop_name = "VBATT",
88 .hysteresis = 100000, /* 100mV */
89 .fcc_cfg = {
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +053090 /* VBAT_LOW VBAT_HIGH FCC */
91 {3600000, 4000000, 3000000},
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053092 {4001000, 4200000, 2800000},
93 {4201000, 4400000, 2000000},
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +053094 },
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +053095 /*
96 * SOC STEP-CHG configuration example.
97 *
98 * .psy_prop = POWER_SUPPLY_PROP_CAPACITY,
99 * .prop_name = "SOC",
100 * .fcc_cfg = {
101 * //SOC_LOW SOC_HIGH FCC
102 * {20, 70, 3000000},
103 * {70, 90, 2750000},
104 * {90, 100, 2500000},
105 * },
106 */
107};
108
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530109/*
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530110 * Jeita Charging Configuration
111 * Update the table based on the battery profile
112 * Please ensure that the TEMP ranges are programmed in the hw so that
113 * an interrupt is issued and a consequent psy changed will cause us to
114 * react immediately.
115 * range data must be in increasing ranges and shouldn't overlap.
116 * Gaps are okay
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530117 */
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530118static struct jeita_fcc_cfg jeita_fcc_config = {
119 .psy_prop = POWER_SUPPLY_PROP_TEMP,
120 .prop_name = "BATT_TEMP",
121 .hysteresis = 10, /* 1degC hysteresis */
122 .fcc_cfg = {
123 /* TEMP_LOW TEMP_HIGH FCC */
124 {0, 100, 600000},
125 {101, 200, 2000000},
126 {201, 450, 3000000},
127 {451, 550, 600000},
128 },
129};
130
131static struct jeita_fv_cfg jeita_fv_config = {
132 .psy_prop = POWER_SUPPLY_PROP_TEMP,
133 .prop_name = "BATT_TEMP",
134 .hysteresis = 10, /* 1degC hysteresis */
135 .fv_cfg = {
136 /* TEMP_LOW TEMP_HIGH FCC */
137 {0, 100, 4200000},
138 {101, 450, 4400000},
139 {451, 550, 4200000},
140 },
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530141};
142
143static bool is_batt_available(struct step_chg_info *chip)
144{
145 if (!chip->batt_psy)
146 chip->batt_psy = power_supply_get_by_name("battery");
147
148 if (!chip->batt_psy)
149 return false;
150
151 return true;
152}
153
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530154static int get_val(struct range_data *range, int hysteresis, int current_index,
155 int threshold,
156 int *new_index, int *val)
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530157{
158 int i;
159
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530160 *new_index = -EINVAL;
161 /* first find the matching index without hysteresis */
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530162 for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++)
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530163 if (is_between(range[i].low_threshold,
164 range[i].high_threshold, threshold)) {
165 *new_index = i;
166 *val = range[i].value;
167 }
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530168
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530169 /* if nothing was found, return -ENODATA */
170 if (*new_index == -EINVAL)
171 return -ENODATA;
172 /*
173 * If we don't have a current_index return this
174 * newfound value. There is no hysterisis from out of range
175 * to in range transition
176 */
177 if (current_index == -EINVAL)
178 return 0;
179
180 /*
181 * Check for hysteresis if it in the neighbourhood
182 * of our current index.
183 */
184 if (*new_index == current_index + 1) {
185 if (threshold < range[*new_index].low_threshold + hysteresis) {
186 /*
187 * Stay in the current index, threshold is not higher
188 * by hysteresis amount
189 */
190 *new_index = current_index;
191 *val = range[current_index].value;
192 }
193 } else if (*new_index == current_index - 1) {
194 if (threshold > range[*new_index].high_threshold - hysteresis) {
195 /*
196 * stay in the current index, threshold is not lower
197 * by hysteresis amount
198 */
199 *new_index = current_index;
200 *val = range[current_index].value;
201 }
202 }
203 return 0;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530204}
205
206static int handle_step_chg_config(struct step_chg_info *chip)
207{
208 union power_supply_propval pval = {0, };
209 int rc = 0, fcc_ua = 0;
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530210 u64 elapsed_us;
211
212 elapsed_us = ktime_us_delta(ktime_get(), chip->step_last_update_time);
213 if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US)
214 goto reschedule;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530215
216 rc = power_supply_get_property(chip->batt_psy,
217 POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED, &pval);
218 if (rc < 0)
219 chip->step_chg_enable = 0;
220 else
221 chip->step_chg_enable = pval.intval;
222
223 if (!chip->step_chg_enable) {
224 if (chip->fcc_votable)
225 vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0);
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530226 goto update_time;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530227 }
228
229 rc = power_supply_get_property(chip->batt_psy,
230 step_chg_config.psy_prop, &pval);
231 if (rc < 0) {
232 pr_err("Couldn't read %s property rc=%d\n",
233 step_chg_config.prop_name, rc);
234 return rc;
235 }
236
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530237 rc = get_val(step_chg_config.fcc_cfg, step_chg_config.hysteresis,
238 chip->step_index,
239 pval.intval,
240 &chip->step_index,
241 &fcc_ua);
242 if (rc < 0) {
243 /* remove the vote if no step-based fcc is found */
244 if (chip->fcc_votable)
245 vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0);
246 goto update_time;
247 }
248
249 if (!chip->fcc_votable)
250 chip->fcc_votable = find_votable("FCC");
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530251 if (!chip->fcc_votable)
252 return -EINVAL;
253
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530254 vote(chip->fcc_votable, STEP_CHG_VOTER, true, fcc_ua);
255
256 pr_debug("%s = %d Step-FCC = %duA\n",
257 step_chg_config.prop_name, pval.intval, fcc_ua);
258
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530259update_time:
260 chip->step_last_update_time = ktime_get();
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530261 return 0;
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530262
263reschedule:
264 /* reschedule 1000uS after the remaining time */
265 return (STEP_CHG_HYSTERISIS_DELAY_US - elapsed_us + 1000);
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530266}
267
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530268static int handle_jeita(struct step_chg_info *chip)
269{
270 union power_supply_propval pval = {0, };
271 int rc = 0, fcc_ua = 0, fv_uv = 0;
272 u64 elapsed_us;
273
274 if (!chip->sw_jeita_enable) {
275 if (chip->fcc_votable)
276 vote(chip->fcc_votable, JEITA_VOTER, false, 0);
277 if (chip->fv_votable)
278 vote(chip->fv_votable, JEITA_VOTER, false, 0);
279 return 0;
280 }
281
282 elapsed_us = ktime_us_delta(ktime_get(), chip->jeita_last_update_time);
283 if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US)
284 goto reschedule;
285
286 rc = power_supply_get_property(chip->batt_psy,
287 jeita_fcc_config.psy_prop, &pval);
288 if (rc < 0) {
289 pr_err("Couldn't read %s property rc=%d\n",
290 step_chg_config.prop_name, rc);
291 return rc;
292 }
293
294 rc = get_val(jeita_fcc_config.fcc_cfg, jeita_fcc_config.hysteresis,
295 chip->jeita_fcc_index,
296 pval.intval,
297 &chip->jeita_fcc_index,
298 &fcc_ua);
299 if (rc < 0) {
300 /* remove the vote if no step-based fcc is found */
301 if (chip->fcc_votable)
302 vote(chip->fcc_votable, JEITA_VOTER, false, 0);
303 goto update_time;
304 }
305
306 if (!chip->fcc_votable)
307 chip->fcc_votable = find_votable("FCC");
308 if (!chip->fcc_votable)
309 /* changing FCC is a must */
310 return -EINVAL;
311
312 vote(chip->fcc_votable, JEITA_VOTER, true, fcc_ua);
313
314 rc = get_val(jeita_fv_config.fv_cfg, jeita_fv_config.hysteresis,
315 chip->jeita_fv_index,
316 pval.intval,
317 &chip->jeita_fv_index,
318 &fv_uv);
319 if (rc < 0) {
320 /* remove the vote if no step-based fcc is found */
321 if (chip->fv_votable)
322 vote(chip->fv_votable, JEITA_VOTER, false, 0);
323 goto update_time;
324 }
325
326 chip->fv_votable = find_votable("FV");
327 if (!chip->fv_votable)
328 goto update_time;
329
330 vote(chip->fv_votable, JEITA_VOTER, true, fv_uv);
331
332 pr_debug("%s = %d FCC = %duA FV = %duV\n",
333 step_chg_config.prop_name, pval.intval, fcc_ua, fv_uv);
334
335update_time:
336 chip->jeita_last_update_time = ktime_get();
337 return 0;
338
339reschedule:
340 /* reschedule 1000uS after the remaining time */
341 return (STEP_CHG_HYSTERISIS_DELAY_US - elapsed_us + 1000);
342}
343
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530344static void status_change_work(struct work_struct *work)
345{
346 struct step_chg_info *chip = container_of(work,
347 struct step_chg_info, status_change_work.work);
348 int rc = 0;
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530349 int reschedule_us;
350 int reschedule_jeita_work_us = 0;
351 int reschedule_step_work_us = 0;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530352
353 if (!is_batt_available(chip))
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530354 return;
355
356 /* skip elapsed_us debounce for handling battery temperature */
357 rc = handle_jeita(chip);
358 if (rc > 0)
359 reschedule_jeita_work_us = rc;
360 else if (rc < 0)
361 pr_err("Couldn't handle sw jeita rc = %d\n", rc);
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530362
363 rc = handle_step_chg_config(chip);
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530364 if (rc > 0)
365 reschedule_step_work_us = rc;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530366 if (rc < 0)
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530367 pr_err("Couldn't handle step rc = %d\n", rc);
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530368
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530369 reschedule_us = min(reschedule_jeita_work_us, reschedule_step_work_us);
370 if (reschedule_us == 0)
371 __pm_relax(chip->step_chg_ws);
372 else
373 schedule_delayed_work(&chip->status_change_work,
374 usecs_to_jiffies(reschedule_us));
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530375}
376
377static int step_chg_notifier_call(struct notifier_block *nb,
378 unsigned long ev, void *v)
379{
380 struct power_supply *psy = v;
381 struct step_chg_info *chip = container_of(nb, struct step_chg_info, nb);
382
383 if (ev != PSY_EVENT_PROP_CHANGED)
384 return NOTIFY_OK;
385
386 if ((strcmp(psy->desc->name, "battery") == 0)) {
387 __pm_stay_awake(chip->step_chg_ws);
388 schedule_delayed_work(&chip->status_change_work, 0);
389 }
390
391 return NOTIFY_OK;
392}
393
394static int step_chg_register_notifier(struct step_chg_info *chip)
395{
396 int rc;
397
398 chip->nb.notifier_call = step_chg_notifier_call;
399 rc = power_supply_reg_notifier(&chip->nb);
400 if (rc < 0) {
401 pr_err("Couldn't register psy notifier rc = %d\n", rc);
402 return rc;
403 }
404
405 return 0;
406}
407
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530408int qcom_step_chg_init(bool step_chg_enable, bool sw_jeita_enable)
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530409{
410 int rc;
411 struct step_chg_info *chip;
412
413 if (the_chip) {
414 pr_err("Already initialized\n");
415 return -EINVAL;
416 }
417
418 chip = kzalloc(sizeof(*chip), GFP_KERNEL);
419 if (!chip)
420 return -ENOMEM;
421
422 chip->step_chg_ws = wakeup_source_register("qcom-step-chg");
423 if (!chip->step_chg_ws) {
424 rc = -EINVAL;
425 goto cleanup;
426 }
427
428 chip->step_chg_enable = step_chg_enable;
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530429 chip->sw_jeita_enable = sw_jeita_enable;
430
431 chip->step_index = -EINVAL;
432 chip->jeita_fcc_index = -EINVAL;
433 chip->jeita_fv_index = -EINVAL;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530434
435 if (step_chg_enable && (!step_chg_config.psy_prop ||
436 !step_chg_config.prop_name)) {
437 /* fail if step-chg configuration is invalid */
438 pr_err("Step-chg configuration not defined - fail\n");
Ashay Jaiswal9aba44a2017-07-20 17:41:36 +0530439 rc = -ENODATA;
440 goto release_wakeup_source;
441 }
442
443 if (sw_jeita_enable && (!jeita_fcc_config.psy_prop ||
444 !jeita_fcc_config.prop_name)) {
445 /* fail if step-chg configuration is invalid */
446 pr_err("Jeita TEMP configuration not defined - fail\n");
447 rc = -ENODATA;
448 goto release_wakeup_source;
449 }
450
451 if (sw_jeita_enable && (!jeita_fv_config.psy_prop ||
452 !jeita_fv_config.prop_name)) {
453 /* fail if step-chg configuration is invalid */
454 pr_err("Jeita TEMP configuration not defined - fail\n");
455 rc = -ENODATA;
456 goto release_wakeup_source;
Anirudh Ghayalfa10fac2017-07-11 09:17:50 +0530457 }
458
459 INIT_DELAYED_WORK(&chip->status_change_work, status_change_work);
460
461 rc = step_chg_register_notifier(chip);
462 if (rc < 0) {
463 pr_err("Couldn't register psy notifier rc = %d\n", rc);
464 goto release_wakeup_source;
465 }
466
467 the_chip = chip;
468
469 if (step_chg_enable)
470 pr_info("Step charging enabled. Using %s source\n",
471 step_chg_config.prop_name);
472
473 return 0;
474
475release_wakeup_source:
476 wakeup_source_unregister(chip->step_chg_ws);
477cleanup:
478 kfree(chip);
479 return rc;
480}
481
482void qcom_step_chg_deinit(void)
483{
484 struct step_chg_info *chip = the_chip;
485
486 if (!chip)
487 return;
488
489 cancel_delayed_work_sync(&chip->status_change_work);
490 power_supply_unreg_notifier(&chip->nb);
491 wakeup_source_unregister(chip->step_chg_ws);
492 the_chip = NULL;
493 kfree(chip);
494}