-
Notifications
You must be signed in to change notification settings - Fork 0
/
google_cpm.c
4489 lines (3670 loc) · 124 KB
/
google_cpm.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2020-2022 Google LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#ifdef CONFIG_PM_SLEEP
#define SUPPORT_PM_SLEEP 1
#endif
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <linux/thermal.h>
#include <linux/slab.h>
#include <misc/gvotable.h>
#include "gbms_power_supply.h"
#include "google_bms.h"
#include "google_dc_pps.h"
#include "google_psy.h"
#include <linux/debugfs.h>
#define get_boot_sec() div_u64(ktime_to_ns(ktime_get_boottime()), NSEC_PER_SEC)
/* Non DC Charger is the default */
#define GCPM_DEFAULT_CHARGER 0
/* TODO: handle capabilities based on index number */
#define GCPM_INDEX_DC_DISABLE -1
#define GCPM_INDEX_DC_ENABLE 1
#define GCPM_MAX_CHARGERS 4
/* tier based, disabled now */
#define GCPM_DEFAULT_DC_LIMIT_DEMAND 0
/* thermal will change this */
#define GCPM_DEFAULT_DC_LIMIT_CC_MIN 1000000
#define GCPM_DEFAULT_DC_LIMIT_CC_MIN_WLC 2000000
/* voltage based */
#define GCPM_DEFAULT_DC_LIMIT_VBATT_MIN 3600000
#define GCPM_DEFAULT_DC_LIMIT_DELTA_LOW 200000
/* demand based limits */
#define GCPM_DEFAULT_DC_LIMIT_VBATT_MAX 4450000
#define GCPM_DEFAULT_DC_LIMIT_DELTA_HIGH 200000
/* SOC debounce */
#define GCPM_DEFAULT_DC_LIMIT_SOC_HIGH 100
/* behavior in taper */
#define GCPM_TAPER_STEP_FV_MARGIN 0
#define GCPM_TAPER_STEP_CC_STEP 0
#define GCPM_TAPER_STEP_COUNT 0
#define GCPM_TAPER_STEP_GRACE 1
#define GCPM_TAPER_STEP_VOLTAGE 0
#define GCPM_TAPER_STEP_CURRENT 0
/* enough time for the charger to settle to a new limit */
#define GCPM_TAPER_STEP_INTERVAL_S 120
/* TODO: move to configuration */
#define DC_TA_VMAX_MV 9800000
/* TODO: move to configuration */
#define DC_TA_VMIN_MV 8000000
/* TODO: move to configuration */
#define DC_VBATT_HEADROOM_MV 500000
enum gcpm_dc_state_t {
DC_DISABLED = -1,
DC_IDLE = 0,
DC_ENABLE,
DC_RUNNING,
DC_ENABLE_PASSTHROUGH,
DC_PASSTHROUGH,
};
/* DC_ERROR_RETRY_MS <= DC_RUN_DELAY_MS */
#define DC_ENABLE_DELAY_MS 500
#define DC_RUN_DELAY_MS 9000
#define DC_ERROR_RETRY_MS PPS_ERROR_RETRY_MS
#define PPS_PROG_TIMEOUT_S 10
#define PPS_PROG_RETRY_MS 2000
#define PPS_ACTIVE_RETRY_MS 1500
#define PPS_ACTIVE_USB_TIMEOUT_S 25
#define PPS_ACTIVE_WLC_TIMEOUT_S 500
#define PPS_READY_DELTA_TIMEOUT_S 10
#define PPS_ERROR_RETRY_MS 1000
enum {
PPS_INDEX_NOT_SUPP = -1,
PPS_INDEX_TCPM = 1,
PPS_INDEX_WLC = 2,
PPS_INDEX_MAX = 2,
};
#define MDIS_OF_CDEV_NAME "google,mdis_charger"
#define MDIS_CDEV_NAME "chg_mdis"
#define MDIS_IN_MAX 4
#define MDIS_OUT_MAX GCPM_MAX_CHARGERS
struct mdis_thermal_device
{
struct gcpm_drv *gcpm;
struct mutex tdev_lock;
struct thermal_cooling_device *tcd;
u32 *thermal_mitigation;
int thermal_levels;
int current_level;
int therm_fan_alarm_level;
};
struct gcpm_drv {
struct device *device;
struct power_supply *psy;
struct delayed_work init_work;
/* charge limit for wireless DC (legacy) */
struct gvotable_election *dc_fcc_votable;
bool cp_fcc_hold; /* debounces CP */
int cp_fcc_hold_limit; /* limit to re-enter CP */
/* MDIS: wired and wireless via main charger */
struct gvotable_election *fcc_votable;
struct gvotable_election *dc_icl_votable;
struct gvotable_election *tx_icl_votable;
/* MDIS: wired and wireless via DC charger */
struct gvotable_election *cp_votable;
/* MDIS: configuration */
struct power_supply *mdis_in[MDIS_IN_MAX];
int mdis_in_count;
struct power_supply *mdis_out[MDIS_OUT_MAX];
int mdis_out_count;
u32 *mdis_out_limits[MDIS_OUT_MAX];
u32 mdis_out_sel[MDIS_OUT_MAX];
/* MDIS: device and current budget */
struct mdis_thermal_device thermal_device;
struct gvotable_election *mdis_votable;
struct gvotable_election *fan_level_votable;
/* CSI */
struct gvotable_election *csi_status_votable;
/* combine PPS, route to the active PPS source */
struct power_supply *pps_psy;
/* basically the same as mdis_out */
int chg_psy_retries;
struct power_supply *chg_psy_avail[GCPM_MAX_CHARGERS];
const char *chg_psy_names[GCPM_MAX_CHARGERS];
struct gvotable_election *dc_chg_avail_votable;
struct mutex chg_psy_lock;
int chg_psy_active;
int chg_psy_count;
/* wakelock */
struct wakeup_source *gcpm_ws;
/* force a charger, this might have side effects */
int force_active;
struct logbuffer *log;
/* TCPM state for wired PPS charging */
const char *tcpm_psy_name;
struct power_supply *tcpm_psy;
struct pd_pps_data tcpm_pps_data;
int log_psy_ratelimit;
u32 tcpm_phandle;
/* TCPM state for wireless PPS charging */
const char *wlc_dc_name;
struct power_supply *wlc_dc_psy;
struct pd_pps_data wlc_pps_data;
u32 wlc_phandle;
struct delayed_work select_work;
/* set to force PPS negotiation */
bool force_pps;
/* pps state and detect */
struct delayed_work pps_work;
/* request of output ua, */
int out_ua;
int out_uv;
int dcen_gpio;
u32 dcen_gpio_default;
/* >0 when enabled, pps charger to use */
int pps_index;
/* >0 when enabled, dc_charger */
int dc_index;
/* dc_charging state */
int dc_state;
ktime_t dc_start_time;
/* Disable DC control */
int dc_ctl;
/* force check of the DC limit again (debug) */
bool new_dc_limit;
/* taper off of current at tier, voltage */
u32 taper_step_interval; /* countdown interval in seconds */
u32 taper_step_voltage; /* voltage before countdown */
u32 taper_step_current; /* current before countdown */
u32 taper_step_grace; /* steps from voltage before countdown */
u32 taper_step_count; /* countdown steps before dc_done */
u32 taper_step_fv_margin; /* countdown steps before dc_done */
u32 taper_step_cc_step; /* countdown steps before dc_done */
int taper_step; /* actual countdown */
/* policy: soc% based limits for DC charging */
u32 dc_limit_soc_high; /* DC will not start over high */
/* policy: power demand limit for DC charging */
u32 dc_limit_vbatt_low; /* DC will not stop until low */
u32 dc_limit_vbatt_min; /* DC will start at min */
u32 dc_limit_vbatt_high; /* DC will not start over high */
u32 dc_limit_vbatt_max; /* DC stop at max */
u32 dc_limit_demand;
/* TODO: keep TCPM/DC state in a structure add there */
u32 dc_limit_cc_min; /* PPS_DC stop if CC_MAX is under this */
u32 dc_limit_cc_min_wlc; /* WLC_DC stop if CC_MAX is under this */
/* cc_max and fv_uv are the demand from google_charger */
int cc_max;
int fv_uv;
bool dc_init_complete;
bool init_complete;
bool resume_complete;
struct notifier_block chg_nb;
/* tie up to charger mode */
struct gvotable_election *gbms_mode;
/* debug fs */
struct dentry *debug_entry;
};
#define gcpm_psy_name(psy) \
((psy) && (psy)->desc && (psy)->desc->name ? (psy)->desc->name : "???")
/* TODO: rename to "can_dc" and handle capabilities based on index number */
#define gcpm_is_dc(gcpm, index) \
((index) >= GCPM_INDEX_DC_ENABLE)
/* Logging ----------------------------------------------------------------- */
static int debug_printk_prlog = LOGLEVEL_INFO;
/* ------------------------------------------------------------------------- */
static struct gvotable_election *gcpm_get_cp_votable(struct gcpm_drv *gcpm)
{
if (!gcpm->cp_votable) {
struct gvotable_election *v;
v = gvotable_election_get_handle("GCPM_FCC");
if (!IS_ERR_OR_NULL(v))
gcpm->cp_votable = v;
}
return gcpm->cp_votable;
}
static struct gvotable_election *gcpm_get_dc_icl_votable(struct gcpm_drv *gcpm)
{
if (!gcpm->dc_icl_votable) {
struct gvotable_election *v;
v = gvotable_election_get_handle("DC_ICL");
if (!IS_ERR_OR_NULL(v))
gcpm->dc_icl_votable = v;
}
return gcpm->dc_icl_votable;
}
static struct gvotable_election *gcpm_get_fcc_votable(struct gcpm_drv *gcpm)
{
if (!gcpm->fcc_votable) {
struct gvotable_election *v;
v = gvotable_election_get_handle("MSC_FCC");
if (!IS_ERR_OR_NULL(v))
gcpm->fcc_votable = v;
}
return gcpm->fcc_votable;
}
/* will kick gcpm_fcc_callback(), needs mutex_unlock(&gcpm->chg_psy_lock); */
static int gcpm_update_gcpm_fcc(struct gcpm_drv *gcpm, const char *reason,
int limit, bool enable)
{
struct gvotable_election *el;
int ret = -ENODEV;
el = gcpm_get_cp_votable(gcpm);
if (el)
ret = gvotable_cast_int_vote(el, reason, limit, enable);
return ret;
}
/* current limit for DC charging */
static int gcpm_get_gcpm_fcc(struct gcpm_drv *gcpm)
{
struct gvotable_election *el;
int dc_iin = -1;
el = gcpm_get_cp_votable(gcpm);
if (el)
dc_iin = gvotable_get_current_int_vote(el);
if (dc_iin < 0)
dc_iin = gcpm->cc_max;
return dc_iin;
}
/* ------------------------------------------------------------------------- */
static struct power_supply *gcpm_chg_get_charger(const struct gcpm_drv *gcpm, int index)
{
return (index < 0 || index >= gcpm->chg_psy_count) ? NULL : gcpm->chg_psy_avail[index];
}
static struct power_supply *gcpm_chg_get_default(const struct gcpm_drv *gcpm)
{
return gcpm_chg_get_charger(gcpm, GCPM_DEFAULT_CHARGER);
}
/* TODO: place a lock around the operation? */
static struct power_supply *gcpm_chg_get_active(const struct gcpm_drv *gcpm)
{
return gcpm_chg_get_charger(gcpm, gcpm->chg_psy_active);
}
static bool gcpm_chg_is_cp_active(const struct gcpm_drv *gcpm)
{
return gcpm_is_dc(gcpm, gcpm->chg_psy_active);
}
/* !=NULL if the adapter is not CP */
static struct power_supply *gcpm_chg_get_active_cp(const struct gcpm_drv *gcpm)
{
struct power_supply *psy = NULL;
if (gcpm_chg_is_cp_active(gcpm))
psy = gcpm_chg_get_charger(gcpm, gcpm->chg_psy_active);
return psy;
}
static int gcpm_chg_ping(struct gcpm_drv *gcpm, int index, bool online)
{
struct power_supply *chg_psy;
int ret;
chg_psy = gcpm->chg_psy_avail[index];
if (!chg_psy)
return 0;
ret = GPSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_ONLINE, 0);
if (ret < 0)
pr_debug("adapter %d cannot ping (%d)", index, ret);
return 0;
}
/* use the charger one when avalaible or fallback to the generated one */
static uint64_t gcpm_get_charger_state(const struct gcpm_drv *gcpm,
struct power_supply *chg_psy)
{
union gbms_charger_state chg_state;
int rc;
rc = gbms_read_charger_state(&chg_state, chg_psy);
if (rc < 0)
return 0;
return chg_state.v;
}
/*
* chg_psy_active==-1 if index was active
* NOTE: GBMS_PROP_CHARGING_ENABLED will be pinged later on
*/
static int gcpm_chg_offline(struct gcpm_drv *gcpm, int index)
{
const int active_index = gcpm->chg_psy_active;
struct power_supply *chg_psy;
int ret;
ret = gcpm_update_gcpm_fcc(gcpm, "CC_MAX", gcpm->cc_max, false);
if (ret < 0)
pr_debug("PPS_DC: offline cannot update cp_fcc (%d)\n", ret);
chg_psy = gcpm_chg_get_charger(gcpm, index);
if (!chg_psy)
return 0;
/* OFFLINE should stop charging */
ret = GPSY_SET_PROP(chg_psy, GBMS_PROP_CHARGING_ENABLED, 0);
if (ret == 0)
ret = GPSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_ONLINE, 0);
if (ret == 0 && gcpm->chg_psy_active == index)
gcpm->chg_psy_active = -1;
pr_info("%s: %s active=%d->%d offline_ok=%d\n", __func__,
pps_name(chg_psy), active_index, gcpm->chg_psy_active, ret == 0);
return ret;
}
/* preset charging parameters */
static int gcpm_chg_preset(struct power_supply *chg_psy, int fv_uv, int cc_max)
{
const char *name = gcpm_psy_name(chg_psy);
int ret;
pr_debug("%s: %s fv_uv=%d cc_max=%d\n", __func__, name, fv_uv, cc_max);
ret = GPSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
fv_uv);
if (ret < 0) {
pr_err("%s: %s no fv_uv (%d)\n", __func__, name, ret);
return ret;
}
ret = GPSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
cc_max);
if (ret < 0)
pr_err("%s: %s no cc_max (%d)\n", __func__, name, ret);
return ret;
}
/* setting online might start charging (if ENABLE is set) */
static int gcpm_chg_online(struct power_supply *chg_psy, int fv_uv, int cc_max)
{
const char *name = gcpm_psy_name(chg_psy);
bool preset_ok = true;
int ret;
if (!chg_psy) {
pr_err("%s: invalid charger\n", __func__);
return -EINVAL;
}
/* preset the new charger */
ret = gcpm_chg_preset(chg_psy, fv_uv, cc_max);
if (ret < 0)
preset_ok = false;
/* online (but so we can enable it) */
ret = GPSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_ONLINE, 1);
if (ret < 0) {
pr_debug("%s: %s online failed (%d)\n", __func__, name, ret);
return ret;
}
/* retry preset if failed */
if (!preset_ok)
ret = gcpm_chg_preset(chg_psy, fv_uv, cc_max);
if (ret < 0) {
int rc;
pr_err("%s: %s preset failed (%d)\n", __func__, name, ret);
rc = GPSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_ONLINE, 0);
if (rc < 0)
pr_err("%s: %s offline failed (%d)\n", __func__, name, rc);
}
return ret;
}
/*
* gcpm->chg_psy_active == gcpm->dc_index on success.
* NOTE: call with a lock around gcpm->chg_psy_lock
*/
static int gcpm_chg_start(struct gcpm_drv *gcpm, int index, int fv_uv, int cc_max)
{
const int active_index = gcpm->chg_psy_active;
struct power_supply *chg_psy;
int ret = -EINVAL;
if (index == active_index)
return 0;
if (active_index != -1)
pr_err("%s: %d->%d not idle\n", __func__, active_index, index);
/* validate the index before switch */
chg_psy = gcpm_chg_get_charger(gcpm, index);
if (chg_psy)
ret = gcpm_chg_online(chg_psy, fv_uv, cc_max);
if (ret < 0) {
/* TODO: force active_index if != -1 */
pr_debug("%s: index=%d not online (%d)\n",
__func__, index, ret);
return ret;
}
pr_debug("%s: active=%d->%d\n", __func__, active_index, index);
gcpm->chg_psy_active = index;
return ret;
}
/*
* Enable DirectCharge mode, PPS and DC charger must be already initialized
* NOTE: disable might restart the default charger with stale settings
*/
static int gcpm_dc_enable(struct gcpm_drv *gcpm, bool enabled)
{
if (gcpm->dcen_gpio >= 0 && !gcpm->dcen_gpio_default)
gpio_set_value(gcpm->dcen_gpio, enabled);
if (!gcpm->gbms_mode) {
struct gvotable_election *v;
v = gvotable_election_get_handle(GBMS_MODE_VOTABLE);
if (IS_ERR_OR_NULL(v))
return -ENODEV;
gcpm->gbms_mode = v;
}
return gvotable_cast_long_vote(gcpm->gbms_mode, "GCPM",
GBMS_CHGR_MODE_CHGR_DC, enabled);
}
/*
* disable DC and switch back to the default charger. Final DC statate is
* DC_IDLE (i.e. this can be used to reset dc_state from DC_DISABLED).
* NOTE: call with a lock around gcpm->chg_psy_lock
* NOTE: I could pass in and return dc_state instead of changing gcpm
* must hold a lock on mutex_lock(&gcpm->chg_psy_lock);
*/
static int gcpm_dc_stop(struct gcpm_drv *gcpm, int index)
{
int dc_state = gcpm->dc_state;
int ret = 0;
if (!gcpm_is_dc(gcpm, index))
dc_state = DC_ENABLE_PASSTHROUGH;
switch (dc_state) {
case DC_RUNNING:
case DC_PASSTHROUGH:
ret = gcpm_chg_offline(gcpm, index);
if (ret < 0)
pr_warn("DC_PPS: Cannot offline DC index=%d (%d)",
index, ret);
else
gcpm->dc_state = DC_ENABLE;
/* Fall Through */
case DC_ENABLE:
case DC_ENABLE_PASSTHROUGH:
ret = gcpm_dc_enable(gcpm, false);
if (ret < 0) {
pr_err("DC_PPS: Cannot disable DC (%d)", ret);
break;
}
/* Fall Through */
default:
gcpm->dc_state = DC_DISABLED;
break;
}
return ret;
}
/*
* route the dc_limit to MSC_FCC for wireless charing.
* @return <0 on error, 0 on limit not applied, 1 on limit applied (and positive)
* call holding a lock on mutex_lock(&gcpm->chg_psy_lock);
*/
static int gcpm_dc_fcc_update(struct gcpm_drv *gcpm, int value)
{
struct gvotable_election *msc_fcc;
int limit = value;
int ret = -ENODEV;
msc_fcc = gcpm_get_fcc_votable(gcpm);
if (!msc_fcc)
goto error_exit;
/* apply/enable DC_FCC only when a WLC_DC source is selected */
if ((gcpm->pps_index != PPS_INDEX_WLC) ||
(gcpm->dc_index <= GCPM_DEFAULT_CHARGER) || limit < 0)
limit = -1;
/*
* The thermal voter for FCC wired must be disabled to allow higher
* charger rates for DC_FCC than for the wired case.
*/
ret = gvotable_cast_int_vote(msc_fcc, "DC_FCC", limit, limit >= 0);
if (ret < 0)
pr_err("%s: vote %d on MSC_FCC failed (%d)\n", __func__,
limit, ret);
else
ret = limit >= 0;
error_exit:
dev_dbg(gcpm->device, "%s: DC_FCC->MSC_FCC pps_index=%d value=%d limit=%d applied=%d\n",
__func__, gcpm->pps_index, value, limit, ret);
return ret;
}
/*
* route the dc_limit to MSC_FCC for wireless charging and adjust the MDIS
* limits when switching between CP and non CP charging.
* NOTE: when in MDIS is at level = 0 the cooling zone disable the MDIS votes
* on MSC_FCC, DC_ICL and GCPM_FCC.
* NOTE: called with negative cp_limit when switching from WLC_CP to WLC and
* with the HOLD limit when re-starting PPS_DC and WLC_DC.
* @return <0 on error, 0 on limit not applied, 1 on limit applied and positive
* call holding a lock on mutex_lock(&gcpm->chg_psy_lock);
*/
static int gcpm_update_votes(struct gcpm_drv *gcpm, int cp_limit)
{
const bool enable = gcpm->thermal_device.current_level > 0;
struct gvotable_election *el;
int ret;
pr_debug("%s: cp_limit=%d\n", __func__, cp_limit);
/* update DC_FCC limit before disabling the others */
if (cp_limit > 0)
ret = gcpm_dc_fcc_update(gcpm, enable ? cp_limit : -1);
/* vote on DC_ICL */
el = gcpm_get_dc_icl_votable(gcpm);
if (el)
gvotable_recast_ballot(el, "MDIS", enable && cp_limit == 0);
/* vote on MSC_FCC: applied only when CP is not enabled */
el = gcpm_get_fcc_votable(gcpm);
if (el)
gvotable_recast_ballot(el, "MDIS", enable && cp_limit == 0);
/* vote on GCPM_FCC: valid only on cp */
el = gcpm_get_cp_votable(gcpm);
if (el)
gvotable_recast_ballot(el, "MDIS", enable && cp_limit);
/* update DC_FCC limit after enabling the others */
if (cp_limit <= 0)
ret = gcpm_dc_fcc_update(gcpm, enable ? cp_limit : -1);
return ret;
}
/* NOTE: call with a lock around gcpm->chg_psy_lock */
static int gcpm_dc_start(struct gcpm_drv *gcpm, int index)
{
const int dc_iin = gcpm_get_gcpm_fcc(gcpm);
struct power_supply *dc_psy;
int ret;
pr_info("PPS_DC: index=%d dc_iin=%d hold=%d\n",
index, dc_iin, gcpm->cp_fcc_hold_limit);
/* ENABLE will be called by the dc_pps workloop */
ret = gcpm_chg_start(gcpm, index, gcpm->fv_uv, dc_iin);
if (ret < 0) {
pr_err("PPS_DC: index=%d not started (%d)\n", index, ret);
return ret;
}
/*
* Restoring the DC_FCC limit might change charging current and cause
* demand to fall under dc_limit_demand. The possible resulting loop
* (enable/disable) is solved in gcpm_chg_select_work().
* NOTE: ->cp_fcc_hold_limit cannot be 0
*/
ret = gcpm_update_votes(gcpm, gcpm->cp_fcc_hold_limit);
if (ret < 0)
pr_debug("PPS_DC: start cannot update votes (%d)\n", ret);
ret = gcpm_update_gcpm_fcc(gcpm, "CC_MAX", gcpm->cc_max, true);
if (ret < 0)
pr_debug("PPS_DC: start cannot update cp_fcc (%d)\n", ret);
/* this is the CP */
dc_psy = gcpm_chg_get_active_cp(gcpm);
if (!dc_psy) {
pr_err("PPS_DC: gcpm->dc_state == DC_READY, no adapter\n");
return -ENODEV;
}
/* set IIN_CFG (might not need) */
ret = GPSY_SET_PROP(dc_psy, POWER_SUPPLY_PROP_CURRENT_MAX,
gcpm->out_ua);
if (ret < 0) {
pr_err("PPS_DC: no IIN (%d)\n", ret);
return ret;
}
/* vote on MODE */
ret = gcpm_dc_enable(gcpm, true);
if (ret < 0) {
pr_err("PPS_DC: dc_ready failed=%d\n", ret);
return ret;
}
pr_debug("PPS_DC: dc_ready ok state=%d fv_uv=%d cc_max=%d, out_ua=%d\n",
gcpm->dc_state, gcpm->fv_uv, gcpm->cc_max, gcpm->out_ua);
return 0;
}
/*
* Select the DC charger using the thermal policy.
* DC charging is enabled when demand is over dc_limit (default 0) and
* vbatt > vbatt_min (default or device tree). DC is not disabled when
* vbatt is over vbat low.
* DC is stopped when vbatt is over vbatt_max (default or DT) and not started
* when vbatt is over vbatt_high (some default 200mV under vbatt_max).
* NOTE: program target before enabling chaging.
*/
enum gcpm_dc_ctl_t {
GCPM_DC_CTL_DEFAULT = 0,
GCPM_DC_CTL_DISABLE_WIRED,
GCPM_DC_CTL_DISABLE_WIRELESS,
GCPM_DC_CTL_DISABLE_BOTH,
};
/*
* the current source as index in mdis_in[].
* < 0 error, the index in mdis_in[] if the source is in PPS mode
*/
static int gcpm_mdis_match_cp_source(struct gcpm_drv *gcpm, int *online)
{
union power_supply_propval pval;
int i, ret;
for (i = 0; i < MDIS_IN_MAX; i++) {
if (!gcpm->mdis_in[i])
continue;
ret = power_supply_get_property(gcpm->mdis_in[i],
POWER_SUPPLY_PROP_ONLINE,
&pval);
if (ret || !pval.intval)
continue;
*online = pval.intval;
return i;
}
return -EINVAL;
}
static int gcpm_mdis_in_is_wireless(struct gcpm_drv *gcpm, int index)
{
return index == 1; /* TODO: query at startup using type==WIRELESS */
}
/* return the PPS_CP or the WLC_CP limit */
static int gcpm_chg_select_check_cp_limit(struct gcpm_drv *gcpm)
{
int online, cp_min = -1, in_idx;
in_idx = gcpm_mdis_match_cp_source(gcpm, &online);
if (in_idx < 0 || gcpm_mdis_in_is_wireless(gcpm, in_idx)) {
if (gcpm->dc_limit_cc_min_wlc >= 0)
cp_min = gcpm->dc_limit_cc_min_wlc;
} else if (gcpm->dc_limit_cc_min >= 0) {
cp_min = gcpm->dc_limit_cc_min;
}
/* wlc might use a different (higher) CP limit than wired */
dev_dbg(gcpm->device, "%s: in_idx=%d cp_min=%d\n", __func__, in_idx, cp_min);
return cp_min;
}
/* call holding mutex_lock(&gcpm->chg_psy_lock) */
static int gcpm_chg_select_by_demand(struct gcpm_drv *gcpm)
{
int cc_max = gcpm->cc_max; /* from google_charger */
int index = GCPM_DEFAULT_CHARGER;
int batt_demand = -1;
int cp_min;
/*
* ->cc_max is lowered from the main-charger thermal limit and might
* prevent this code from selecting the CP charger again when thermals
* caused this code to switch from CP to the main charger.
*
* NOTE: Need to check the value directly because source selection is
* done holding a lock on &gcpm->chg_psy_lock (cc_max will become the
* same as gcpm->cp_fcc_hold_limit on exit).
*/
if (gcpm->cp_fcc_hold && gcpm->cp_fcc_hold_limit >= 0 && cc_max != 0) {
/*
* ->cp_fcc_hold is set when a thermal limit caused the switch
* from CP to main-charger. In this case ->cp_fcc_hold_limit
* keeps the current (alternate) CP limit that should be used
* to determine whether to go back to the Charge Pump.
* NOTE: ->cp_fcc_hold_limit is changed when the DC_FCC changes
* (wireless CP) AND when MDIS changes but is not changed when
* the MSC_FCC limit changes. This means that without MDIS
* CP will restart on PD wired only when the actual charging
* current exceeds the cp_min limit.
*/
dev_dbg(gcpm->device, "%s: change due to hold cc_max=%d->%d\n",
__func__, cc_max, gcpm->cp_fcc_hold_limit);
cc_max = gcpm->cp_fcc_hold_limit;
}
/* keeps on default charger until we have valid charging parameters */
if (cc_max <= 0 || gcpm->fv_uv <= 0)
goto exit_done; /* index == GCPM_DEFAULT_CHARGER; */
/*
* power demand comes from charging tier or thermal limit: leave
* dc_limit_demand to 0 to switch only on cp_min.
* TODO: handle capabilities based on index number
*/
batt_demand = (cc_max / 1000) * (gcpm->fv_uv / 1000);
if (batt_demand > gcpm->dc_limit_demand)
index = GCPM_INDEX_DC_ENABLE;
dev_dbg(gcpm->device, "%s: index=%d cc_max=%d gcpm->fv_uv=%d demand=%d, dc_limit=%d\n",
__func__, index, cc_max / 1000, gcpm->fv_uv / 1000,
batt_demand, gcpm->dc_limit_demand);
/*
* the limit for DC depends on the source that is active with the
* complication that using the DC_ICL disables the THERMAL limit
* on MSC_FCC and will cause an immediate reselection of CP.
* The code settig ->hold and ->cp_fcc_hold_limit needs to make sure
* that the limit is appropriate.
*/
cp_min = gcpm_chg_select_check_cp_limit(gcpm);
if (cp_min == -1 || cc_max <= cp_min) {
const bool cp_active = gcpm_chg_is_cp_active(gcpm);
/* current demand less than min demand for CP */
dev_dbg(gcpm->device,
"%s: cc_max=%d under cp_min=%d, ->hold=%d->%d index:%d->%d\n",
__func__, cc_max, cp_min, gcpm->cp_fcc_hold,
gcpm->cp_fcc_hold ? gcpm->cp_fcc_hold : cp_active,
index, GCPM_DEFAULT_CHARGER);
/*
* Switch to the default charger and hold it.
* NOTE: ->cp_fcc_hold is reset in gcpm_dc_fcc_callback()
* and when the MDIS thermal limit changes. This piece is
* only for legacy dc_fcc since the mdis code handle this.
*/
if (!gcpm->cp_fcc_hold)
gcpm->cp_fcc_hold = cp_active;
index = GCPM_DEFAULT_CHARGER;
}
exit_done:
dev_dbg(gcpm->device,
"by_d: index:%d->%d demand=%d,limit=%d cc_max=%d,cp_min=%d, hold=%d",
gcpm->dc_index, index, batt_demand, gcpm->dc_limit_demand,
cc_max, cp_min, gcpm->cp_fcc_hold);
return index;
}
/*
* called only before enabling DC to debounce HIGH SOC.
* call holding mutex_lock(&gcpm->chg_psy_lock)
*/
static int gcpm_chg_select_by_soc(struct power_supply *psy,
const struct gcpm_drv *gcpm)
{
union power_supply_propval pval = { };
int index = gcpm->dc_index; /* debounce it */
int ret;
ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &pval);
if (ret < 0 || pval.intval < gcpm->dc_limit_soc_high)
index = GCPM_INDEX_DC_ENABLE;
pr_debug("%s: index=%d->%d ret=%d soc=%d limit=%d\n", __func__,
gcpm->dc_index, index, ret, pval.intval,
gcpm->dc_limit_soc_high);
return index;
}
/* call holding mutex_lock(&gcpm->chg_psy_lock) */
static int gcpm_chg_select_by_voltage(struct power_supply *psy,
const struct gcpm_drv *gcpm)
{
const int vbatt_min = gcpm->dc_limit_vbatt_min;
const int vbatt_max = gcpm->dc_limit_vbatt_max;
const int vbatt_high = gcpm->dc_limit_vbatt_high;
const int vbatt_low = gcpm->dc_limit_vbatt_low;
int index = GCPM_DEFAULT_CHARGER;
int vbatt = -1;
if (!vbatt_min && !vbatt_max)
return GCPM_INDEX_DC_ENABLE;
vbatt = GPSY_GET_PROP(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
if (vbatt < 0) {
pr_err("CHG_CHK cannot read vbatt %d\n", vbatt);
goto exit_done; /* index == GCPM_DEFAULT_CHARGER; */
}
/* vbatt_max is the hard limit */
if (vbatt_max && vbatt > vbatt_max)
goto exit_done; /* index == GCPM_DEFAULT_CHARGER; */
/*
* Need to keep checking the voltage when vbatt is under low
* to make sure that DC starts at vbatt_min. Also keeps the
* same ->dc_index to avoid instability but keep checking
* demand.
*/
if (vbatt_low && vbatt < vbatt_low) {
index = gcpm->dc_index == GCPM_DEFAULT_CHARGER ?
-EAGAIN : gcpm->dc_index; /* debounce */
} else if (vbatt_min && vbatt < vbatt_min) {
index = gcpm->dc_index == GCPM_DEFAULT_CHARGER ?
-EAGAIN : gcpm->dc_index; /* debounce */
} else if (vbatt_high && vbatt > vbatt_high) {
index = gcpm->dc_index; /* debounce */
} else {
/* vbatt_min <= vbatt <= vbatt_high */
index = GCPM_INDEX_DC_ENABLE;
}
exit_done:
pr_debug("%s: index=%d->%d vbatt=%d: low=%d min=%d high=%d max=%d\n",
__func__, gcpm->dc_index, index, vbatt, vbatt_low, vbatt_min,
vbatt_high, vbatt_max);
return index;
}
/* call holding mutex_lock(&gcpm->chg_psy_lock) */
static int gcpm_chg_select(struct gcpm_drv *gcpm)
{
int index = GCPM_DEFAULT_CHARGER;
int ret;
if (!gcpm->dc_init_complete)
goto exit_done; /* index == GCPM_DEFAULT_CHARGER; */
/* overrides cp_fcc_hold, might trigger taper_control */
if (gcpm->force_active >= 0)
return gcpm->force_active;
/* kill switch */
if (gcpm->dc_ctl == GCPM_DC_CTL_DISABLE_BOTH)
goto exit_done; /* index == GCPM_DEFAULT_CHARGER; */
ret = gvotable_get_current_int_vote(gcpm->dc_chg_avail_votable);
dev_dbg(gcpm->device, "%s: dc_chg_avail vote: %d\n", __func__, ret);
if (ret <= 0)
goto exit_done;
/*
* check demand first to react to thermal engine, then voltage to
* make sure that we are over min and that we don't start over high
* and we stop at max, finally use SOC to not restart if over a
* SOC%
*/
index = gcpm_chg_select_by_demand(gcpm);
if (index == GCPM_INDEX_DC_ENABLE) {
struct power_supply *chg_psy;