123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509 |
- // SPDX-License-Identifier: GPL-2.0+
- /*
- * max77976_charger.c - Driver for the Maxim MAX77976 battery charger
- *
- * Copyright (C) 2021 Luca Ceresoli
- * Author: Luca Ceresoli <[email protected]>
- */
- #include <linux/i2c.h>
- #include <linux/module.h>
- #include <linux/power_supply.h>
- #include <linux/regmap.h>
- #define MAX77976_DRIVER_NAME "max77976-charger"
- #define MAX77976_CHIP_ID 0x76
- static const char *max77976_manufacturer = "Maxim Integrated";
- static const char *max77976_model = "MAX77976";
- /* --------------------------------------------------------------------------
- * Register map
- */
- #define MAX77976_REG_CHIP_ID 0x00
- #define MAX77976_REG_CHIP_REVISION 0x01
- #define MAX77976_REG_CHG_INT_OK 0x12
- #define MAX77976_REG_CHG_DETAILS_01 0x14
- #define MAX77976_REG_CHG_CNFG_00 0x16
- #define MAX77976_REG_CHG_CNFG_02 0x18
- #define MAX77976_REG_CHG_CNFG_06 0x1c
- #define MAX77976_REG_CHG_CNFG_09 0x1f
- /* CHG_DETAILS_01.CHG_DTLS values */
- enum max77976_charging_state {
- MAX77976_CHARGING_PREQUALIFICATION = 0x0,
- MAX77976_CHARGING_FAST_CONST_CURRENT,
- MAX77976_CHARGING_FAST_CONST_VOLTAGE,
- MAX77976_CHARGING_TOP_OFF,
- MAX77976_CHARGING_DONE,
- MAX77976_CHARGING_RESERVED_05,
- MAX77976_CHARGING_TIMER_FAULT,
- MAX77976_CHARGING_SUSPENDED_QBATT_OFF,
- MAX77976_CHARGING_OFF,
- MAX77976_CHARGING_RESERVED_09,
- MAX77976_CHARGING_THERMAL_SHUTDOWN,
- MAX77976_CHARGING_WATCHDOG_EXPIRED,
- MAX77976_CHARGING_SUSPENDED_JEITA,
- MAX77976_CHARGING_SUSPENDED_THM_REMOVAL,
- MAX77976_CHARGING_SUSPENDED_PIN,
- MAX77976_CHARGING_RESERVED_0F,
- };
- /* CHG_DETAILS_01.BAT_DTLS values */
- enum max77976_battery_state {
- MAX77976_BATTERY_BATTERY_REMOVAL = 0x0,
- MAX77976_BATTERY_PREQUALIFICATION,
- MAX77976_BATTERY_TIMER_FAULT,
- MAX77976_BATTERY_REGULAR_VOLTAGE,
- MAX77976_BATTERY_LOW_VOLTAGE,
- MAX77976_BATTERY_OVERVOLTAGE,
- MAX77976_BATTERY_RESERVED,
- MAX77976_BATTERY_BATTERY_ONLY, // No valid adapter is present
- };
- /* CHG_CNFG_00.MODE values */
- enum max77976_mode {
- MAX77976_MODE_CHARGER_BUCK = 0x5,
- MAX77976_MODE_BOOST = 0x9,
- };
- /* CHG_CNFG_02.CHG_CC: charge current limit, 100..5500 mA, 50 mA steps */
- #define MAX77976_CHG_CC_STEP 50000U
- #define MAX77976_CHG_CC_MIN 100000U
- #define MAX77976_CHG_CC_MAX 5500000U
- /* CHG_CNFG_09.CHGIN_ILIM: input current limit, 100..3200 mA, 100 mA steps */
- #define MAX77976_CHGIN_ILIM_STEP 100000U
- #define MAX77976_CHGIN_ILIM_MIN 100000U
- #define MAX77976_CHGIN_ILIM_MAX 3200000U
- enum max77976_field_idx {
- VERSION, REVISION, /* CHIP_REVISION */
- CHGIN_OK, /* CHG_INT_OK */
- BAT_DTLS, CHG_DTLS, /* CHG_DETAILS_01 */
- MODE, /* CHG_CNFG_00 */
- CHG_CC, /* CHG_CNFG_02 */
- CHGPROT, /* CHG_CNFG_06 */
- CHGIN_ILIM, /* CHG_CNFG_09 */
- MAX77976_N_REGMAP_FIELDS
- };
- static const struct reg_field max77976_reg_field[MAX77976_N_REGMAP_FIELDS] = {
- [VERSION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 4, 7),
- [REVISION] = REG_FIELD(MAX77976_REG_CHIP_REVISION, 0, 3),
- [CHGIN_OK] = REG_FIELD(MAX77976_REG_CHG_INT_OK, 6, 6),
- [CHG_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 0, 3),
- [BAT_DTLS] = REG_FIELD(MAX77976_REG_CHG_DETAILS_01, 4, 6),
- [MODE] = REG_FIELD(MAX77976_REG_CHG_CNFG_00, 0, 3),
- [CHG_CC] = REG_FIELD(MAX77976_REG_CHG_CNFG_02, 0, 6),
- [CHGPROT] = REG_FIELD(MAX77976_REG_CHG_CNFG_06, 2, 3),
- [CHGIN_ILIM] = REG_FIELD(MAX77976_REG_CHG_CNFG_09, 0, 5),
- };
- static const struct regmap_config max77976_regmap_config = {
- .reg_bits = 8,
- .val_bits = 8,
- .max_register = 0x24,
- };
- /* --------------------------------------------------------------------------
- * Data structures
- */
- struct max77976 {
- struct i2c_client *client;
- struct regmap *regmap;
- struct regmap_field *rfield[MAX77976_N_REGMAP_FIELDS];
- };
- /* --------------------------------------------------------------------------
- * power_supply properties
- */
- static int max77976_get_status(struct max77976 *chg, int *val)
- {
- unsigned int regval;
- int err;
- err = regmap_field_read(chg->rfield[CHG_DTLS], ®val);
- if (err < 0)
- return err;
- switch (regval) {
- case MAX77976_CHARGING_PREQUALIFICATION:
- case MAX77976_CHARGING_FAST_CONST_CURRENT:
- case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
- case MAX77976_CHARGING_TOP_OFF:
- *val = POWER_SUPPLY_STATUS_CHARGING;
- break;
- case MAX77976_CHARGING_DONE:
- *val = POWER_SUPPLY_STATUS_FULL;
- break;
- case MAX77976_CHARGING_TIMER_FAULT:
- case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
- case MAX77976_CHARGING_SUSPENDED_JEITA:
- case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
- case MAX77976_CHARGING_SUSPENDED_PIN:
- *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
- break;
- case MAX77976_CHARGING_OFF:
- case MAX77976_CHARGING_THERMAL_SHUTDOWN:
- case MAX77976_CHARGING_WATCHDOG_EXPIRED:
- *val = POWER_SUPPLY_STATUS_DISCHARGING;
- break;
- default:
- *val = POWER_SUPPLY_STATUS_UNKNOWN;
- }
- return 0;
- }
- static int max77976_get_charge_type(struct max77976 *chg, int *val)
- {
- unsigned int regval;
- int err;
- err = regmap_field_read(chg->rfield[CHG_DTLS], ®val);
- if (err < 0)
- return err;
- switch (regval) {
- case MAX77976_CHARGING_PREQUALIFICATION:
- *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
- break;
- case MAX77976_CHARGING_FAST_CONST_CURRENT:
- case MAX77976_CHARGING_FAST_CONST_VOLTAGE:
- *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
- break;
- case MAX77976_CHARGING_TOP_OFF:
- *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
- break;
- case MAX77976_CHARGING_DONE:
- case MAX77976_CHARGING_TIMER_FAULT:
- case MAX77976_CHARGING_SUSPENDED_QBATT_OFF:
- case MAX77976_CHARGING_OFF:
- case MAX77976_CHARGING_THERMAL_SHUTDOWN:
- case MAX77976_CHARGING_WATCHDOG_EXPIRED:
- case MAX77976_CHARGING_SUSPENDED_JEITA:
- case MAX77976_CHARGING_SUSPENDED_THM_REMOVAL:
- case MAX77976_CHARGING_SUSPENDED_PIN:
- *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
- break;
- default:
- *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
- }
- return 0;
- }
- static int max77976_get_health(struct max77976 *chg, int *val)
- {
- unsigned int regval;
- int err;
- err = regmap_field_read(chg->rfield[BAT_DTLS], ®val);
- if (err < 0)
- return err;
- switch (regval) {
- case MAX77976_BATTERY_BATTERY_REMOVAL:
- *val = POWER_SUPPLY_HEALTH_NO_BATTERY;
- break;
- case MAX77976_BATTERY_LOW_VOLTAGE:
- case MAX77976_BATTERY_REGULAR_VOLTAGE:
- *val = POWER_SUPPLY_HEALTH_GOOD;
- break;
- case MAX77976_BATTERY_TIMER_FAULT:
- *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
- break;
- case MAX77976_BATTERY_OVERVOLTAGE:
- *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
- break;
- case MAX77976_BATTERY_PREQUALIFICATION:
- case MAX77976_BATTERY_BATTERY_ONLY:
- *val = POWER_SUPPLY_HEALTH_UNKNOWN;
- break;
- default:
- *val = POWER_SUPPLY_HEALTH_UNKNOWN;
- }
- return 0;
- }
- static int max77976_get_online(struct max77976 *chg, int *val)
- {
- unsigned int regval;
- int err;
- err = regmap_field_read(chg->rfield[CHGIN_OK], ®val);
- if (err < 0)
- return err;
- *val = (regval ? 1 : 0);
- return 0;
- }
- static int max77976_get_integer(struct max77976 *chg, enum max77976_field_idx fidx,
- unsigned int clamp_min, unsigned int clamp_max,
- unsigned int mult, int *val)
- {
- unsigned int regval;
- int err;
- err = regmap_field_read(chg->rfield[fidx], ®val);
- if (err < 0)
- return err;
- *val = clamp_val(regval * mult, clamp_min, clamp_max);
- return 0;
- }
- static int max77976_set_integer(struct max77976 *chg, enum max77976_field_idx fidx,
- unsigned int clamp_min, unsigned int clamp_max,
- unsigned int div, int val)
- {
- unsigned int regval;
- regval = clamp_val(val, clamp_min, clamp_max) / div;
- return regmap_field_write(chg->rfield[fidx], regval);
- }
- static int max77976_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
- {
- struct max77976 *chg = power_supply_get_drvdata(psy);
- int err = 0;
- switch (psp) {
- case POWER_SUPPLY_PROP_STATUS:
- err = max77976_get_status(chg, &val->intval);
- break;
- case POWER_SUPPLY_PROP_CHARGE_TYPE:
- err = max77976_get_charge_type(chg, &val->intval);
- break;
- case POWER_SUPPLY_PROP_HEALTH:
- err = max77976_get_health(chg, &val->intval);
- break;
- case POWER_SUPPLY_PROP_ONLINE:
- err = max77976_get_online(chg, &val->intval);
- break;
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
- val->intval = MAX77976_CHG_CC_MAX;
- break;
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
- err = max77976_get_integer(chg, CHG_CC,
- MAX77976_CHG_CC_MIN,
- MAX77976_CHG_CC_MAX,
- MAX77976_CHG_CC_STEP,
- &val->intval);
- break;
- case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
- err = max77976_get_integer(chg, CHGIN_ILIM,
- MAX77976_CHGIN_ILIM_MIN,
- MAX77976_CHGIN_ILIM_MAX,
- MAX77976_CHGIN_ILIM_STEP,
- &val->intval);
- break;
- case POWER_SUPPLY_PROP_MODEL_NAME:
- val->strval = max77976_model;
- break;
- case POWER_SUPPLY_PROP_MANUFACTURER:
- val->strval = max77976_manufacturer;
- break;
- default:
- err = -EINVAL;
- }
- return err;
- }
- static int max77976_set_property(struct power_supply *psy,
- enum power_supply_property psp,
- const union power_supply_propval *val)
- {
- struct max77976 *chg = power_supply_get_drvdata(psy);
- int err = 0;
- switch (psp) {
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
- err = max77976_set_integer(chg, CHG_CC,
- MAX77976_CHG_CC_MIN,
- MAX77976_CHG_CC_MAX,
- MAX77976_CHG_CC_STEP,
- val->intval);
- break;
- case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
- err = max77976_set_integer(chg, CHGIN_ILIM,
- MAX77976_CHGIN_ILIM_MIN,
- MAX77976_CHGIN_ILIM_MAX,
- MAX77976_CHGIN_ILIM_STEP,
- val->intval);
- break;
- default:
- err = -EINVAL;
- }
- return err;
- };
- static int max77976_property_is_writeable(struct power_supply *psy,
- enum power_supply_property psp)
- {
- switch (psp) {
- case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
- case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
- return true;
- default:
- return false;
- }
- }
- static enum power_supply_property max77976_psy_props[] = {
- POWER_SUPPLY_PROP_STATUS,
- POWER_SUPPLY_PROP_CHARGE_TYPE,
- POWER_SUPPLY_PROP_HEALTH,
- POWER_SUPPLY_PROP_ONLINE,
- POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
- POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
- POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
- POWER_SUPPLY_PROP_MODEL_NAME,
- POWER_SUPPLY_PROP_MANUFACTURER,
- };
- static const struct power_supply_desc max77976_psy_desc = {
- .name = MAX77976_DRIVER_NAME,
- .type = POWER_SUPPLY_TYPE_USB,
- .properties = max77976_psy_props,
- .num_properties = ARRAY_SIZE(max77976_psy_props),
- .get_property = max77976_get_property,
- .set_property = max77976_set_property,
- .property_is_writeable = max77976_property_is_writeable,
- };
- /* --------------------------------------------------------------------------
- * Entry point
- */
- static int max77976_detect(struct max77976 *chg)
- {
- struct device *dev = &chg->client->dev;
- unsigned int id, ver, rev;
- int err;
- err = regmap_read(chg->regmap, MAX77976_REG_CHIP_ID, &id);
- if (err)
- return dev_err_probe(dev, err, "cannot read chip ID\n");
- if (id != MAX77976_CHIP_ID)
- return dev_err_probe(dev, -ENXIO, "unknown model ID 0x%02x\n", id);
- err = regmap_field_read(chg->rfield[VERSION], &ver);
- if (!err)
- err = regmap_field_read(chg->rfield[REVISION], &rev);
- if (err)
- return dev_err_probe(dev, -ENXIO, "cannot read version/revision\n");
- dev_info(dev, "detected model MAX779%02x ver %u rev %u", id, ver, rev);
- return 0;
- }
- static int max77976_configure(struct max77976 *chg)
- {
- struct device *dev = &chg->client->dev;
- int err;
- /* Magic value to unlock writing to some registers */
- err = regmap_field_write(chg->rfield[CHGPROT], 0x3);
- if (err)
- goto err;
- /*
- * Mode 5 = Charger ON, OTG OFF, buck ON, boost OFF.
- * Other modes are not implemented by this driver.
- */
- err = regmap_field_write(chg->rfield[MODE], MAX77976_MODE_CHARGER_BUCK);
- if (err)
- goto err;
- return 0;
- err:
- return dev_err_probe(dev, err, "error while configuring");
- }
- static int max77976_probe(struct i2c_client *client)
- {
- struct device *dev = &client->dev;
- struct power_supply_config psy_cfg = {};
- struct power_supply *psy;
- struct max77976 *chg;
- int err;
- int i;
- chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);
- if (!chg)
- return -ENOMEM;
- i2c_set_clientdata(client, chg);
- psy_cfg.drv_data = chg;
- chg->client = client;
- chg->regmap = devm_regmap_init_i2c(client, &max77976_regmap_config);
- if (IS_ERR(chg->regmap))
- return dev_err_probe(dev, PTR_ERR(chg->regmap),
- "cannot allocate regmap\n");
- for (i = 0; i < MAX77976_N_REGMAP_FIELDS; i++) {
- chg->rfield[i] = devm_regmap_field_alloc(dev, chg->regmap,
- max77976_reg_field[i]);
- if (IS_ERR(chg->rfield[i]))
- return dev_err_probe(dev, PTR_ERR(chg->rfield[i]),
- "cannot allocate regmap field\n");
- }
- err = max77976_detect(chg);
- if (err)
- return err;
- err = max77976_configure(chg);
- if (err)
- return err;
- psy = devm_power_supply_register_no_ws(dev, &max77976_psy_desc, &psy_cfg);
- if (IS_ERR(psy))
- return dev_err_probe(dev, PTR_ERR(psy), "cannot register\n");
- return 0;
- }
- static const struct i2c_device_id max77976_i2c_id[] = {
- { MAX77976_DRIVER_NAME, 0 },
- { },
- };
- MODULE_DEVICE_TABLE(i2c, max77976_i2c_id);
- static const struct of_device_id max77976_of_id[] = {
- { .compatible = "maxim,max77976" },
- { },
- };
- MODULE_DEVICE_TABLE(of, max77976_of_id);
- static struct i2c_driver max77976_driver = {
- .driver = {
- .name = MAX77976_DRIVER_NAME,
- .of_match_table = max77976_of_id,
- },
- .probe_new = max77976_probe,
- .id_table = max77976_i2c_id,
- };
- module_i2c_driver(max77976_driver);
- MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
- MODULE_DESCRIPTION("Maxim MAX77976 charger driver");
- MODULE_LICENSE("GPL v2");
|