1715ecbc1SLuca Ceresoli // SPDX-License-Identifier: GPL-2.0+
2715ecbc1SLuca Ceresoli /*
3715ecbc1SLuca Ceresoli * max77976_charger.c - Driver for the Maxim MAX77976 battery charger
4715ecbc1SLuca Ceresoli *
5715ecbc1SLuca Ceresoli * Copyright (C) 2021 Luca Ceresoli
6a6c487cdSLuca Ceresoli * Author: Luca Ceresoli <luca.ceresoli@bootlin.com>
7715ecbc1SLuca Ceresoli */
8715ecbc1SLuca Ceresoli
9715ecbc1SLuca Ceresoli #include <linux/i2c.h>
10715ecbc1SLuca Ceresoli #include <linux/module.h>
11715ecbc1SLuca Ceresoli #include <linux/power_supply.h>
12715ecbc1SLuca Ceresoli #include <linux/regmap.h>
13715ecbc1SLuca Ceresoli
14715ecbc1SLuca Ceresoli #define MAX77976_DRIVER_NAME "max77976-charger"
15715ecbc1SLuca Ceresoli #define MAX77976_CHIP_ID 0x76
16715ecbc1SLuca Ceresoli
17715ecbc1SLuca Ceresoli static const char *max77976_manufacturer = "Maxim Integrated";
18715ecbc1SLuca Ceresoli static const char *max77976_model = "MAX77976";
19715ecbc1SLuca Ceresoli
20715ecbc1SLuca Ceresoli /* --------------------------------------------------------------------------
21715ecbc1SLuca Ceresoli * Register map
22715ecbc1SLuca Ceresoli */
23715ecbc1SLuca Ceresoli
24715ecbc1SLuca Ceresoli #define MAX77976_REG_CHIP_ID 0x00
25715ecbc1SLuca Ceresoli #define MAX77976_REG_CHIP_REVISION 0x01
26715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_INT_OK 0x12
27715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_DETAILS_01 0x14
28715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_CNFG_00 0x16
29715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_CNFG_02 0x18
30715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_CNFG_06 0x1c
31715ecbc1SLuca Ceresoli #define MAX77976_REG_CHG_CNFG_09 0x1f
32715ecbc1SLuca Ceresoli
33715ecbc1SLuca Ceresoli /* CHG_DETAILS_01.CHG_DTLS values */
34715ecbc1SLuca Ceresoli enum max77976_charging_state {
35715ecbc1SLuca Ceresoli MAX77976_CHARGING_PREQUALIFICATION = 0x0,
36715ecbc1SLuca Ceresoli MAX77976_CHARGING_FAST_CONST_CURRENT,
37715ecbc1SLuca Ceresoli MAX77976_CHARGING_FAST_CONST_VOLTAGE,
38715ecbc1SLuca Ceresoli MAX77976_CHARGING_TOP_OFF,
39715ecbc1SLuca Ceresoli MAX77976_CHARGING_DONE,
40715ecbc1SLuca Ceresoli MAX77976_CHARGING_RESERVED_05,
41715ecbc1SLuca Ceresoli MAX77976_CHARGING_TIMER_FAULT,
42715ecbc1SLuca Ceresoli MAX77976_CHARGING_SUSPENDED_QBATT_OFF,
43715ecbc1SLuca Ceresoli MAX77976_CHARGING_OFF,
44715ecbc1SLuca Ceresoli MAX77976_CHARGING_RESERVED_09,
45715ecbc1SLuca Ceresoli MAX77976_CHARGING_THERMAL_SHUTDOWN,
46715ecbc1SLuca Ceresoli MAX77976_CHARGING_WATCHDOG_EXPIRED,
47715ecbc1SLuca Ceresoli MAX77976_CHARGING_SUSPENDED_JEITA,
48715ecbc1SLuca Ceresoli MAX77976_CHARGING_SUSPENDED_THM_REMOVAL,
49715ecbc1SLuca Ceresoli MAX77976_CHARGING_SUSPENDED_PIN,
50715ecbc1SLuca Ceresoli MAX77976_CHARGING_RESERVED_0F,
51715ecbc1SLuca Ceresoli };
52715ecbc1SLuca Ceresoli
53715ecbc1SLuca Ceresoli /* CHG_DETAILS_01.BAT_DTLS values */
54715ecbc1SLuca Ceresoli enum max77976_battery_state {
55715ecbc1SLuca Ceresoli MAX77976_BATTERY_BATTERY_REMOVAL = 0x0,
56715ecbc1SLuca Ceresoli MAX77976_BATTERY_PREQUALIFICATION,
57715ecbc1SLuca Ceresoli MAX77976_BATTERY_TIMER_FAULT,
58715ecbc1SLuca Ceresoli MAX77976_BATTERY_REGULAR_VOLTAGE,
59715ecbc1SLuca Ceresoli MAX77976_BATTERY_LOW_VOLTAGE,
60715ecbc1SLuca Ceresoli MAX77976_BATTERY_OVERVOLTAGE,
61715ecbc1SLuca Ceresoli MAX77976_BATTERY_RESERVED,
62715ecbc1SLuca Ceresoli MAX77976_BATTERY_BATTERY_ONLY, // No valid adapter is present
63715ecbc1SLuca Ceresoli };
64715ecbc1SLuca Ceresoli
65715ecbc1SLuca Ceresoli /* CHG_CNFG_00.MODE values */
66715ecbc1SLuca Ceresoli enum max77976_mode {
67715ecbc1SLuca Ceresoli MAX77976_MODE_CHARGER_BUCK = 0x5,
68715ecbc1SLuca Ceresoli MAX77976_MODE_BOOST = 0x9,
69715ecbc1SLuca Ceresoli };
70715ecbc1SLuca Ceresoli
71715ecbc1SLuca Ceresoli /* CHG_CNFG_02.CHG_CC: charge current limit, 100..5500 mA, 50 mA steps */
72715ecbc1SLuca Ceresoli #define MAX77976_CHG_CC_STEP 50000U
73715ecbc1SLuca Ceresoli #define MAX77976_CHG_CC_MIN 100000U
74715ecbc1SLuca Ceresoli #define MAX77976_CHG_CC_MAX 5500000U
75715ecbc1SLuca Ceresoli
76715ecbc1SLuca Ceresoli /* CHG_CNFG_09.CHGIN_ILIM: input current limit, 100..3200 mA, 100 mA steps */
77715ecbc1SLuca Ceresoli #define MAX77976_CHGIN_ILIM_STEP 100000U
78715ecbc1SLuca Ceresoli #define MAX77976_CHGIN_ILIM_MIN 100000U
79715ecbc1SLuca Ceresoli #define MAX77976_CHGIN_ILIM_MAX 3200000U
80715ecbc1SLuca Ceresoli
81715ecbc1SLuca Ceresoli enum max77976_field_idx {
82715ecbc1SLuca Ceresoli VERSION, REVISION, /* CHIP_REVISION */
83715ecbc1SLuca Ceresoli CHGIN_OK, /* CHG_INT_OK */
84715ecbc1SLuca Ceresoli BAT_DTLS, CHG_DTLS, /* CHG_DETAILS_01 */
85715ecbc1SLuca Ceresoli MODE, /* CHG_CNFG_00 */
86715ecbc1SLuca Ceresoli CHG_CC, /* CHG_CNFG_02 */
87715ecbc1SLuca Ceresoli CHGPROT, /* CHG_CNFG_06 */
88715ecbc1SLuca Ceresoli CHGIN_ILIM, /* CHG_CNFG_09 */
89715ecbc1SLuca Ceresoli MAX77976_N_REGMAP_FIELDS
90715ecbc1SLuca Ceresoli };
91715ecbc1SLuca Ceresoli
92715ecbc1SLuca Ceresoli static const struct reg_field max77976_reg_field[MAX77976_N_REGMAP_FIELDS] = {
93715ecbc1SLuca Ceresoli [VERSION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 4, 7),
94715ecbc1SLuca Ceresoli [REVISION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 0, 3),
95715ecbc1SLuca Ceresoli [CHGIN_OK] = REG_FIELD(MAX77976_REG_CHG_INT_OK, 6, 6),
96715ecbc1SLuca Ceresoli [CHG_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 0, 3),
97715ecbc1SLuca Ceresoli [BAT_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 4, 6),
98715ecbc1SLuca Ceresoli [MODE] = REG_FIELD(MAX77976_REG_CHG_CNFG_00, 0, 3),
99715ecbc1SLuca Ceresoli [CHG_CC] = REG_FIELD(MAX77976_REG_CHG_CNFG_02, 0, 6),
100715ecbc1SLuca Ceresoli [CHGPROT] = REG_FIELD(MAX77976_REG_CHG_CNFG_06, 2, 3),
101715ecbc1SLuca Ceresoli [CHGIN_ILIM] = REG_FIELD(MAX77976_REG_CHG_CNFG_09, 0, 5),
102715ecbc1SLuca Ceresoli };
103715ecbc1SLuca Ceresoli
104715ecbc1SLuca Ceresoli static const struct regmap_config max77976_regmap_config = {
105715ecbc1SLuca Ceresoli .reg_bits = 8,
106715ecbc1SLuca Ceresoli .val_bits = 8,
107715ecbc1SLuca Ceresoli .max_register = 0x24,
108715ecbc1SLuca Ceresoli };
109715ecbc1SLuca Ceresoli
110715ecbc1SLuca Ceresoli /* --------------------------------------------------------------------------
111715ecbc1SLuca Ceresoli * Data structures
112715ecbc1SLuca Ceresoli */
113715ecbc1SLuca Ceresoli
114715ecbc1SLuca Ceresoli struct max77976 {
115715ecbc1SLuca Ceresoli struct i2c_client *client;
116715ecbc1SLuca Ceresoli struct regmap *regmap;
117715ecbc1SLuca Ceresoli struct regmap_field *rfield[MAX77976_N_REGMAP_FIELDS];
118715ecbc1SLuca Ceresoli };
119715ecbc1SLuca Ceresoli
120715ecbc1SLuca Ceresoli /* --------------------------------------------------------------------------
121715ecbc1SLuca Ceresoli * power_supply properties
122715ecbc1SLuca Ceresoli */
123715ecbc1SLuca Ceresoli
max77976_get_status(struct max77976 * chg,int * val)124715ecbc1SLuca Ceresoli static int max77976_get_status(struct max77976 *chg, int *val)
125715ecbc1SLuca Ceresoli {
126715ecbc1SLuca Ceresoli unsigned int regval;
127715ecbc1SLuca Ceresoli int err;
128715ecbc1SLuca Ceresoli
129715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[CHG_DTLS], ®val);
130715ecbc1SLuca Ceresoli if (err < 0)
131715ecbc1SLuca Ceresoli return err;
132715ecbc1SLuca Ceresoli
133715ecbc1SLuca Ceresoli switch (regval) {
134715ecbc1SLuca Ceresoli case MAX77976_CHARGING_PREQUALIFICATION:
135715ecbc1SLuca Ceresoli case MAX77976_CHARGING_FAST_CONST_CURRENT:
136715ecbc1SLuca Ceresoli case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
137715ecbc1SLuca Ceresoli case MAX77976_CHARGING_TOP_OFF:
138715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_STATUS_CHARGING;
139715ecbc1SLuca Ceresoli break;
140715ecbc1SLuca Ceresoli case MAX77976_CHARGING_DONE:
141715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_STATUS_FULL;
142715ecbc1SLuca Ceresoli break;
143715ecbc1SLuca Ceresoli case MAX77976_CHARGING_TIMER_FAULT:
144715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
145715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_JEITA:
146715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
147715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_PIN:
148715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
149715ecbc1SLuca Ceresoli break;
150715ecbc1SLuca Ceresoli case MAX77976_CHARGING_OFF:
151715ecbc1SLuca Ceresoli case MAX77976_CHARGING_THERMAL_SHUTDOWN:
152715ecbc1SLuca Ceresoli case MAX77976_CHARGING_WATCHDOG_EXPIRED:
153715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_STATUS_DISCHARGING;
154715ecbc1SLuca Ceresoli break;
155715ecbc1SLuca Ceresoli default:
156715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_STATUS_UNKNOWN;
157715ecbc1SLuca Ceresoli }
158715ecbc1SLuca Ceresoli
159715ecbc1SLuca Ceresoli return 0;
160715ecbc1SLuca Ceresoli }
161715ecbc1SLuca Ceresoli
max77976_get_charge_type(struct max77976 * chg,int * val)162715ecbc1SLuca Ceresoli static int max77976_get_charge_type(struct max77976 *chg, int *val)
163715ecbc1SLuca Ceresoli {
164715ecbc1SLuca Ceresoli unsigned int regval;
165715ecbc1SLuca Ceresoli int err;
166715ecbc1SLuca Ceresoli
167715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[CHG_DTLS], ®val);
168715ecbc1SLuca Ceresoli if (err < 0)
169715ecbc1SLuca Ceresoli return err;
170715ecbc1SLuca Ceresoli
171715ecbc1SLuca Ceresoli switch (regval) {
172715ecbc1SLuca Ceresoli case MAX77976_CHARGING_PREQUALIFICATION:
173715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
174715ecbc1SLuca Ceresoli break;
175715ecbc1SLuca Ceresoli case MAX77976_CHARGING_FAST_CONST_CURRENT:
176715ecbc1SLuca Ceresoli case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
177715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
178715ecbc1SLuca Ceresoli break;
179715ecbc1SLuca Ceresoli case MAX77976_CHARGING_TOP_OFF:
180715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
181715ecbc1SLuca Ceresoli break;
182715ecbc1SLuca Ceresoli case MAX77976_CHARGING_DONE:
183715ecbc1SLuca Ceresoli case MAX77976_CHARGING_TIMER_FAULT:
184715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
185715ecbc1SLuca Ceresoli case MAX77976_CHARGING_OFF:
186715ecbc1SLuca Ceresoli case MAX77976_CHARGING_THERMAL_SHUTDOWN:
187715ecbc1SLuca Ceresoli case MAX77976_CHARGING_WATCHDOG_EXPIRED:
188715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_JEITA:
189715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
190715ecbc1SLuca Ceresoli case MAX77976_CHARGING_SUSPENDED_PIN:
191715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
192715ecbc1SLuca Ceresoli break;
193715ecbc1SLuca Ceresoli default:
194715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
195715ecbc1SLuca Ceresoli }
196715ecbc1SLuca Ceresoli
197715ecbc1SLuca Ceresoli return 0;
198715ecbc1SLuca Ceresoli }
199715ecbc1SLuca Ceresoli
max77976_get_health(struct max77976 * chg,int * val)200715ecbc1SLuca Ceresoli static int max77976_get_health(struct max77976 *chg, int *val)
201715ecbc1SLuca Ceresoli {
202715ecbc1SLuca Ceresoli unsigned int regval;
203715ecbc1SLuca Ceresoli int err;
204715ecbc1SLuca Ceresoli
205715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[BAT_DTLS], ®val);
206715ecbc1SLuca Ceresoli if (err < 0)
207715ecbc1SLuca Ceresoli return err;
208715ecbc1SLuca Ceresoli
209715ecbc1SLuca Ceresoli switch (regval) {
210715ecbc1SLuca Ceresoli case MAX77976_BATTERY_BATTERY_REMOVAL:
211715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_NO_BATTERY;
212715ecbc1SLuca Ceresoli break;
213715ecbc1SLuca Ceresoli case MAX77976_BATTERY_LOW_VOLTAGE:
214715ecbc1SLuca Ceresoli case MAX77976_BATTERY_REGULAR_VOLTAGE:
215715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_GOOD;
216715ecbc1SLuca Ceresoli break;
217715ecbc1SLuca Ceresoli case MAX77976_BATTERY_TIMER_FAULT:
218715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
219715ecbc1SLuca Ceresoli break;
220715ecbc1SLuca Ceresoli case MAX77976_BATTERY_OVERVOLTAGE:
221715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
222715ecbc1SLuca Ceresoli break;
223715ecbc1SLuca Ceresoli case MAX77976_BATTERY_PREQUALIFICATION:
224715ecbc1SLuca Ceresoli case MAX77976_BATTERY_BATTERY_ONLY:
225715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_UNKNOWN;
226715ecbc1SLuca Ceresoli break;
227715ecbc1SLuca Ceresoli default:
228715ecbc1SLuca Ceresoli *val = POWER_SUPPLY_HEALTH_UNKNOWN;
229715ecbc1SLuca Ceresoli }
230715ecbc1SLuca Ceresoli
231715ecbc1SLuca Ceresoli return 0;
232715ecbc1SLuca Ceresoli }
233715ecbc1SLuca Ceresoli
max77976_get_online(struct max77976 * chg,int * val)234715ecbc1SLuca Ceresoli static int max77976_get_online(struct max77976 *chg, int *val)
235715ecbc1SLuca Ceresoli {
236715ecbc1SLuca Ceresoli unsigned int regval;
237715ecbc1SLuca Ceresoli int err;
238715ecbc1SLuca Ceresoli
239715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[CHGIN_OK], ®val);
240715ecbc1SLuca Ceresoli if (err < 0)
241715ecbc1SLuca Ceresoli return err;
242715ecbc1SLuca Ceresoli
243715ecbc1SLuca Ceresoli *val = (regval ? 1 : 0);
244715ecbc1SLuca Ceresoli
245715ecbc1SLuca Ceresoli return 0;
246715ecbc1SLuca Ceresoli }
247715ecbc1SLuca Ceresoli
max77976_get_integer(struct max77976 * chg,enum max77976_field_idx fidx,unsigned int clamp_min,unsigned int clamp_max,unsigned int mult,int * val)248715ecbc1SLuca Ceresoli static int max77976_get_integer(struct max77976 *chg, enum max77976_field_idx fidx,
249715ecbc1SLuca Ceresoli unsigned int clamp_min, unsigned int clamp_max,
250715ecbc1SLuca Ceresoli unsigned int mult, int *val)
251715ecbc1SLuca Ceresoli {
252715ecbc1SLuca Ceresoli unsigned int regval;
253715ecbc1SLuca Ceresoli int err;
254715ecbc1SLuca Ceresoli
255715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[fidx], ®val);
256715ecbc1SLuca Ceresoli if (err < 0)
257715ecbc1SLuca Ceresoli return err;
258715ecbc1SLuca Ceresoli
259715ecbc1SLuca Ceresoli *val = clamp_val(regval * mult, clamp_min, clamp_max);
260715ecbc1SLuca Ceresoli
261715ecbc1SLuca Ceresoli return 0;
262715ecbc1SLuca Ceresoli }
263715ecbc1SLuca Ceresoli
max77976_set_integer(struct max77976 * chg,enum max77976_field_idx fidx,unsigned int clamp_min,unsigned int clamp_max,unsigned int div,int val)264715ecbc1SLuca Ceresoli static int max77976_set_integer(struct max77976 *chg, enum max77976_field_idx fidx,
265715ecbc1SLuca Ceresoli unsigned int clamp_min, unsigned int clamp_max,
266715ecbc1SLuca Ceresoli unsigned int div, int val)
267715ecbc1SLuca Ceresoli {
268715ecbc1SLuca Ceresoli unsigned int regval;
269715ecbc1SLuca Ceresoli
270715ecbc1SLuca Ceresoli regval = clamp_val(val, clamp_min, clamp_max) / div;
271715ecbc1SLuca Ceresoli
272715ecbc1SLuca Ceresoli return regmap_field_write(chg->rfield[fidx], regval);
273715ecbc1SLuca Ceresoli }
274715ecbc1SLuca Ceresoli
max77976_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)275715ecbc1SLuca Ceresoli static int max77976_get_property(struct power_supply *psy,
276715ecbc1SLuca Ceresoli enum power_supply_property psp,
277715ecbc1SLuca Ceresoli union power_supply_propval *val)
278715ecbc1SLuca Ceresoli {
279715ecbc1SLuca Ceresoli struct max77976 *chg = power_supply_get_drvdata(psy);
280715ecbc1SLuca Ceresoli int err = 0;
281715ecbc1SLuca Ceresoli
282715ecbc1SLuca Ceresoli switch (psp) {
283715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_STATUS:
284715ecbc1SLuca Ceresoli err = max77976_get_status(chg, &val->intval);
285715ecbc1SLuca Ceresoli break;
286715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_CHARGE_TYPE:
287715ecbc1SLuca Ceresoli err = max77976_get_charge_type(chg, &val->intval);
288715ecbc1SLuca Ceresoli break;
289715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_HEALTH:
290715ecbc1SLuca Ceresoli err = max77976_get_health(chg, &val->intval);
291715ecbc1SLuca Ceresoli break;
292715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_ONLINE:
293715ecbc1SLuca Ceresoli err = max77976_get_online(chg, &val->intval);
294715ecbc1SLuca Ceresoli break;
295715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
296715ecbc1SLuca Ceresoli val->intval = MAX77976_CHG_CC_MAX;
297715ecbc1SLuca Ceresoli break;
298715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
299715ecbc1SLuca Ceresoli err = max77976_get_integer(chg, CHG_CC,
300715ecbc1SLuca Ceresoli MAX77976_CHG_CC_MIN,
301715ecbc1SLuca Ceresoli MAX77976_CHG_CC_MAX,
302715ecbc1SLuca Ceresoli MAX77976_CHG_CC_STEP,
303715ecbc1SLuca Ceresoli &val->intval);
304715ecbc1SLuca Ceresoli break;
305715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
306715ecbc1SLuca Ceresoli err = max77976_get_integer(chg, CHGIN_ILIM,
307715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_MIN,
308715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_MAX,
309715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_STEP,
310715ecbc1SLuca Ceresoli &val->intval);
311715ecbc1SLuca Ceresoli break;
312715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_MODEL_NAME:
313715ecbc1SLuca Ceresoli val->strval = max77976_model;
314715ecbc1SLuca Ceresoli break;
315715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_MANUFACTURER:
316715ecbc1SLuca Ceresoli val->strval = max77976_manufacturer;
317715ecbc1SLuca Ceresoli break;
318715ecbc1SLuca Ceresoli default:
319715ecbc1SLuca Ceresoli err = -EINVAL;
320715ecbc1SLuca Ceresoli }
321715ecbc1SLuca Ceresoli
322715ecbc1SLuca Ceresoli return err;
323715ecbc1SLuca Ceresoli }
324715ecbc1SLuca Ceresoli
max77976_set_property(struct power_supply * psy,enum power_supply_property psp,const union power_supply_propval * val)325715ecbc1SLuca Ceresoli static int max77976_set_property(struct power_supply *psy,
326715ecbc1SLuca Ceresoli enum power_supply_property psp,
327715ecbc1SLuca Ceresoli const union power_supply_propval *val)
328715ecbc1SLuca Ceresoli {
329715ecbc1SLuca Ceresoli struct max77976 *chg = power_supply_get_drvdata(psy);
330715ecbc1SLuca Ceresoli int err = 0;
331715ecbc1SLuca Ceresoli
332715ecbc1SLuca Ceresoli switch (psp) {
333715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
334715ecbc1SLuca Ceresoli err = max77976_set_integer(chg, CHG_CC,
335715ecbc1SLuca Ceresoli MAX77976_CHG_CC_MIN,
336715ecbc1SLuca Ceresoli MAX77976_CHG_CC_MAX,
337715ecbc1SLuca Ceresoli MAX77976_CHG_CC_STEP,
338715ecbc1SLuca Ceresoli val->intval);
339715ecbc1SLuca Ceresoli break;
340715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
341715ecbc1SLuca Ceresoli err = max77976_set_integer(chg, CHGIN_ILIM,
342715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_MIN,
343715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_MAX,
344715ecbc1SLuca Ceresoli MAX77976_CHGIN_ILIM_STEP,
345715ecbc1SLuca Ceresoli val->intval);
346715ecbc1SLuca Ceresoli break;
347715ecbc1SLuca Ceresoli default:
348715ecbc1SLuca Ceresoli err = -EINVAL;
349715ecbc1SLuca Ceresoli }
350715ecbc1SLuca Ceresoli
351715ecbc1SLuca Ceresoli return err;
352715ecbc1SLuca Ceresoli };
353715ecbc1SLuca Ceresoli
max77976_property_is_writeable(struct power_supply * psy,enum power_supply_property psp)354715ecbc1SLuca Ceresoli static int max77976_property_is_writeable(struct power_supply *psy,
355715ecbc1SLuca Ceresoli enum power_supply_property psp)
356715ecbc1SLuca Ceresoli {
357715ecbc1SLuca Ceresoli switch (psp) {
358715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
359715ecbc1SLuca Ceresoli case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
360715ecbc1SLuca Ceresoli return true;
361715ecbc1SLuca Ceresoli default:
362715ecbc1SLuca Ceresoli return false;
363715ecbc1SLuca Ceresoli }
364715ecbc1SLuca Ceresoli }
365715ecbc1SLuca Ceresoli
366715ecbc1SLuca Ceresoli static enum power_supply_property max77976_psy_props[] = {
367715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_STATUS,
368715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_CHARGE_TYPE,
369715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_HEALTH,
370715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_ONLINE,
371715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
372715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
373715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
374715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_MODEL_NAME,
375715ecbc1SLuca Ceresoli POWER_SUPPLY_PROP_MANUFACTURER,
376715ecbc1SLuca Ceresoli };
377715ecbc1SLuca Ceresoli
378715ecbc1SLuca Ceresoli static const struct power_supply_desc max77976_psy_desc = {
379715ecbc1SLuca Ceresoli .name = MAX77976_DRIVER_NAME,
380715ecbc1SLuca Ceresoli .type = POWER_SUPPLY_TYPE_USB,
381715ecbc1SLuca Ceresoli .properties = max77976_psy_props,
382715ecbc1SLuca Ceresoli .num_properties = ARRAY_SIZE(max77976_psy_props),
383715ecbc1SLuca Ceresoli .get_property = max77976_get_property,
384715ecbc1SLuca Ceresoli .set_property = max77976_set_property,
385715ecbc1SLuca Ceresoli .property_is_writeable = max77976_property_is_writeable,
386715ecbc1SLuca Ceresoli };
387715ecbc1SLuca Ceresoli
388715ecbc1SLuca Ceresoli /* --------------------------------------------------------------------------
389715ecbc1SLuca Ceresoli * Entry point
390715ecbc1SLuca Ceresoli */
391715ecbc1SLuca Ceresoli
max77976_detect(struct max77976 * chg)392715ecbc1SLuca Ceresoli static int max77976_detect(struct max77976 *chg)
393715ecbc1SLuca Ceresoli {
394715ecbc1SLuca Ceresoli struct device *dev = &chg->client->dev;
395715ecbc1SLuca Ceresoli unsigned int id, ver, rev;
396715ecbc1SLuca Ceresoli int err;
397715ecbc1SLuca Ceresoli
398715ecbc1SLuca Ceresoli err = regmap_read(chg->regmap, MAX77976_REG_CHIP_ID, &id);
399715ecbc1SLuca Ceresoli if (err)
400715ecbc1SLuca Ceresoli return dev_err_probe(dev, err, "cannot read chip ID\n");
401715ecbc1SLuca Ceresoli
402715ecbc1SLuca Ceresoli if (id != MAX77976_CHIP_ID)
403715ecbc1SLuca Ceresoli return dev_err_probe(dev, -ENXIO, "unknown model ID 0x%02x\n", id);
404715ecbc1SLuca Ceresoli
405715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[VERSION], &ver);
406715ecbc1SLuca Ceresoli if (!err)
407715ecbc1SLuca Ceresoli err = regmap_field_read(chg->rfield[REVISION], &rev);
408715ecbc1SLuca Ceresoli if (err)
409715ecbc1SLuca Ceresoli return dev_err_probe(dev, -ENXIO, "cannot read version/revision\n");
410715ecbc1SLuca Ceresoli
411715ecbc1SLuca Ceresoli dev_info(dev, "detected model MAX779%02x ver %u rev %u", id, ver, rev);
412715ecbc1SLuca Ceresoli
413715ecbc1SLuca Ceresoli return 0;
414715ecbc1SLuca Ceresoli }
415715ecbc1SLuca Ceresoli
max77976_configure(struct max77976 * chg)416715ecbc1SLuca Ceresoli static int max77976_configure(struct max77976 *chg)
417715ecbc1SLuca Ceresoli {
418715ecbc1SLuca Ceresoli struct device *dev = &chg->client->dev;
419715ecbc1SLuca Ceresoli int err;
420715ecbc1SLuca Ceresoli
421715ecbc1SLuca Ceresoli /* Magic value to unlock writing to some registers */
422715ecbc1SLuca Ceresoli err = regmap_field_write(chg->rfield[CHGPROT], 0x3);
423715ecbc1SLuca Ceresoli if (err)
424715ecbc1SLuca Ceresoli goto err;
425715ecbc1SLuca Ceresoli
426715ecbc1SLuca Ceresoli /*
427715ecbc1SLuca Ceresoli * Mode 5 = Charger ON, OTG OFF, buck ON, boost OFF.
428715ecbc1SLuca Ceresoli * Other modes are not implemented by this driver.
429715ecbc1SLuca Ceresoli */
430715ecbc1SLuca Ceresoli err = regmap_field_write(chg->rfield[MODE], MAX77976_MODE_CHARGER_BUCK);
431715ecbc1SLuca Ceresoli if (err)
432715ecbc1SLuca Ceresoli goto err;
433715ecbc1SLuca Ceresoli
434715ecbc1SLuca Ceresoli return 0;
435715ecbc1SLuca Ceresoli
436715ecbc1SLuca Ceresoli err:
437715ecbc1SLuca Ceresoli return dev_err_probe(dev, err, "error while configuring");
438715ecbc1SLuca Ceresoli }
439715ecbc1SLuca Ceresoli
max77976_probe(struct i2c_client * client)440715ecbc1SLuca Ceresoli static int max77976_probe(struct i2c_client *client)
441715ecbc1SLuca Ceresoli {
442715ecbc1SLuca Ceresoli struct device *dev = &client->dev;
443715ecbc1SLuca Ceresoli struct power_supply_config psy_cfg = {};
444715ecbc1SLuca Ceresoli struct power_supply *psy;
445715ecbc1SLuca Ceresoli struct max77976 *chg;
446715ecbc1SLuca Ceresoli int err;
447715ecbc1SLuca Ceresoli int i;
448715ecbc1SLuca Ceresoli
449715ecbc1SLuca Ceresoli chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);
450715ecbc1SLuca Ceresoli if (!chg)
451715ecbc1SLuca Ceresoli return -ENOMEM;
452715ecbc1SLuca Ceresoli
453715ecbc1SLuca Ceresoli i2c_set_clientdata(client, chg);
454715ecbc1SLuca Ceresoli psy_cfg.drv_data = chg;
455*79b9630eSThomas Weißschuh psy_cfg.no_wakeup_source = true;
456715ecbc1SLuca Ceresoli chg->client = client;
457715ecbc1SLuca Ceresoli
458715ecbc1SLuca Ceresoli chg->regmap = devm_regmap_init_i2c(client, &max77976_regmap_config);
459715ecbc1SLuca Ceresoli if (IS_ERR(chg->regmap))
460715ecbc1SLuca Ceresoli return dev_err_probe(dev, PTR_ERR(chg->regmap),
461715ecbc1SLuca Ceresoli "cannot allocate regmap\n");
462715ecbc1SLuca Ceresoli
463715ecbc1SLuca Ceresoli for (i = 0; i < MAX77976_N_REGMAP_FIELDS; i++) {
464715ecbc1SLuca Ceresoli chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap,
465715ecbc1SLuca Ceresoli max77976_reg_field[i]);
466715ecbc1SLuca Ceresoli if (IS_ERR(chg->rfield[i]))
467715ecbc1SLuca Ceresoli return dev_err_probe(dev, PTR_ERR(chg->rfield[i]),
468715ecbc1SLuca Ceresoli "cannot allocate regmap field\n");
469715ecbc1SLuca Ceresoli }
470715ecbc1SLuca Ceresoli
471715ecbc1SLuca Ceresoli err = max77976_detect(chg);
472715ecbc1SLuca Ceresoli if (err)
473715ecbc1SLuca Ceresoli return err;
474715ecbc1SLuca Ceresoli
475715ecbc1SLuca Ceresoli err = max77976_configure(chg);
476715ecbc1SLuca Ceresoli if (err)
477715ecbc1SLuca Ceresoli return err;
478715ecbc1SLuca Ceresoli
479*79b9630eSThomas Weißschuh psy = devm_power_supply_register(dev, &max77976_psy_desc, &psy_cfg);
480715ecbc1SLuca Ceresoli if (IS_ERR(psy))
481715ecbc1SLuca Ceresoli return dev_err_probe(dev, PTR_ERR(psy), "cannot register\n");
482715ecbc1SLuca Ceresoli
483715ecbc1SLuca Ceresoli return 0;
484715ecbc1SLuca Ceresoli }
485715ecbc1SLuca Ceresoli
486715ecbc1SLuca Ceresoli static const struct i2c_device_id max77976_i2c_id[] = {
487ebacfa1fSUwe Kleine-König { MAX77976_DRIVER_NAME },
488ebacfa1fSUwe Kleine-König { }
489715ecbc1SLuca Ceresoli };
490715ecbc1SLuca Ceresoli MODULE_DEVICE_TABLE(i2c, max77976_i2c_id);
491715ecbc1SLuca Ceresoli
492715ecbc1SLuca Ceresoli static const struct of_device_id max77976_of_id[] = {
493715ecbc1SLuca Ceresoli { .compatible = "maxim,max77976" },
494715ecbc1SLuca Ceresoli { },
495715ecbc1SLuca Ceresoli };
496715ecbc1SLuca Ceresoli MODULE_DEVICE_TABLE(of, max77976_of_id);
497715ecbc1SLuca Ceresoli
498715ecbc1SLuca Ceresoli static struct i2c_driver max77976_driver = {
499715ecbc1SLuca Ceresoli .driver = {
500715ecbc1SLuca Ceresoli .name = MAX77976_DRIVER_NAME,
501715ecbc1SLuca Ceresoli .of_match_table = max77976_of_id,
502715ecbc1SLuca Ceresoli },
503fe20b1dcSUwe Kleine-König .probe = max77976_probe,
504715ecbc1SLuca Ceresoli .id_table = max77976_i2c_id,
505715ecbc1SLuca Ceresoli };
506715ecbc1SLuca Ceresoli module_i2c_driver(max77976_driver);
507715ecbc1SLuca Ceresoli
508a6c487cdSLuca Ceresoli MODULE_AUTHOR("Luca Ceresoli <luca.ceresoli@bootlin.com>");
509715ecbc1SLuca Ceresoli MODULE_DESCRIPTION("Maxim MAX77976 charger driver");
510715ecbc1SLuca Ceresoli MODULE_LICENSE("GPL v2");
511