// SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX8971_REG_CHGINT 0x0f #define MAX8971_REG_CHG_RST BIT(0) #define MAX8971_REG_CHGINT_MASK 0x01 #define MAX8971_AICL_MASK BIT(7) #define MAX8971_REG_CHG_STAT 0x02 #define MAX8971_CHG_MASK BIT(3) #define MAX8971_REG_DETAILS1 0x03 #define MAX8971_REG_DETAILS2 0x04 #define MAX8971_REG_CHGCNTL1 0x05 #define MAX8971_REG_FCHGCRNT 0x06 #define MAX8971_REG_DCCRNT 0x07 #define MAX8971_CHGRSTRT_MASK BIT(6) #define MAX8971_REG_TOPOFF 0x08 #define MAX8971_REG_TEMPREG 0x09 #define MAX8971_REG_PROTCMD 0x0a #define MAX8971_CHGPROT_LOCKED 0x00 #define MAX8971_CHGPROT_UNLOCKED 0x03 #define MAX8971_FCHGT_DEFAULT 2 #define MAX8971_TOPOFFT_DEFAULT 3 static const char *max8971_manufacturer = "Maxim Integrated"; static const char *max8971_model = "MAX8971"; enum max8971_charging_state { MAX8971_CHARGING_DEAD_BATTERY, MAX8971_CHARGING_PREQUALIFICATION, MAX8971_CHARGING_FAST_CONST_CURRENT, MAX8971_CHARGING_FAST_CONST_VOLTAGE, MAX8971_CHARGING_TOP_OFF, MAX8971_CHARGING_DONE, MAX8971_CHARGING_TIMER_FAULT, MAX8971_CHARGING_SUSPENDED_THERMAL, MAX8971_CHARGING_OFF, MAX8971_CHARGING_THERMAL_LOOP, }; enum max8971_health_state { MAX8971_HEALTH_UNKNOWN, MAX8971_HEALTH_COLD, MAX8971_HEALTH_COOL, MAX8971_HEALTH_WARM, MAX8971_HEALTH_HOT, MAX8971_HEALTH_OVERHEAT, }; /* Fast-Charge current limit, 250..1550 mA, 50 mA steps */ #define MAX8971_CHG_CC_STEP 50000U #define MAX8971_CHG_CC_MIN 250000U #define MAX8971_CHG_CC_MAX 1550000U /* Input current limit, 250..1500 mA, 25 mA steps */ #define MAX8971_DCILMT_STEP 25000U #define MAX8971_DCILMT_MIN 250000U #define MAX8971_DCILMT_MAX 1500000U enum max8971_field_idx { THM_DTLS, /* DETAILS1 */ BAT_DTLS, CHG_DTLS, /* DETAILS2 */ CHG_CC, FCHG_T, /* FCHGCRNT */ DCI_LMT, /* DCCRNT */ TOPOFF_T, TOPOFF_S, /* TOPOFF */ CPROT, /* PROTCMD */ MAX8971_N_REGMAP_FIELDS }; static const struct reg_field max8971_reg_field[MAX8971_N_REGMAP_FIELDS] = { [THM_DTLS] = REG_FIELD(MAX8971_REG_DETAILS1, 0, 2), [BAT_DTLS] = REG_FIELD(MAX8971_REG_DETAILS2, 4, 5), [CHG_DTLS] = REG_FIELD(MAX8971_REG_DETAILS2, 0, 3), [CHG_CC] = REG_FIELD(MAX8971_REG_FCHGCRNT, 0, 4), [FCHG_T] = REG_FIELD(MAX8971_REG_FCHGCRNT, 5, 7), [DCI_LMT] = REG_FIELD(MAX8971_REG_DCCRNT, 0, 5), [TOPOFF_T] = REG_FIELD(MAX8971_REG_TOPOFF, 5, 7), [TOPOFF_S] = REG_FIELD(MAX8971_REG_TOPOFF, 2, 3), [CPROT] = REG_FIELD(MAX8971_REG_PROTCMD, 2, 3), }; static const struct regmap_config max8971_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = MAX8971_REG_CHGINT, }; struct max8971_data { struct device *dev; struct power_supply *psy_mains; struct extcon_dev *edev; struct notifier_block extcon_nb; struct delayed_work extcon_work; struct regmap *regmap; struct regmap_field *rfield[MAX8971_N_REGMAP_FIELDS]; enum power_supply_usb_type usb_type; u32 fchgt; u32 tofft; u32 toffs; bool present; }; static int max8971_get_status(struct max8971_data *priv, int *val) { u32 regval; int err; err = regmap_field_read(priv->rfield[CHG_DTLS], ®val); if (err) return err; switch (regval) { case MAX8971_CHARGING_DEAD_BATTERY: case MAX8971_CHARGING_PREQUALIFICATION: case MAX8971_CHARGING_FAST_CONST_CURRENT: case MAX8971_CHARGING_FAST_CONST_VOLTAGE: case MAX8971_CHARGING_TOP_OFF: case MAX8971_CHARGING_THERMAL_LOOP: *val = POWER_SUPPLY_STATUS_CHARGING; break; case MAX8971_CHARGING_DONE: *val = POWER_SUPPLY_STATUS_FULL; break; case MAX8971_CHARGING_TIMER_FAULT: *val = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case MAX8971_CHARGING_OFF: case MAX8971_CHARGING_SUSPENDED_THERMAL: *val = POWER_SUPPLY_STATUS_DISCHARGING; break; default: *val = POWER_SUPPLY_STATUS_UNKNOWN; } return 0; } static int max8971_get_charge_type(struct max8971_data *priv, int *val) { u32 regval; int err; err = regmap_field_read(priv->rfield[CHG_DTLS], ®val); if (err) return err; switch (regval) { case MAX8971_CHARGING_DEAD_BATTERY: case MAX8971_CHARGING_PREQUALIFICATION: *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; break; case MAX8971_CHARGING_FAST_CONST_CURRENT: case MAX8971_CHARGING_FAST_CONST_VOLTAGE: *val = POWER_SUPPLY_CHARGE_TYPE_FAST; break; case MAX8971_CHARGING_TOP_OFF: case MAX8971_CHARGING_THERMAL_LOOP: *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD; break; case MAX8971_CHARGING_DONE: case MAX8971_CHARGING_TIMER_FAULT: case MAX8971_CHARGING_SUSPENDED_THERMAL: case MAX8971_CHARGING_OFF: *val = POWER_SUPPLY_CHARGE_TYPE_NONE; break; default: *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; } return 0; } static int max8971_get_health(struct max8971_data *priv, int *val) { u32 regval; int err; err = regmap_field_read(priv->rfield[THM_DTLS], ®val); if (err) return err; switch (regval) { case MAX8971_HEALTH_COLD: *val = POWER_SUPPLY_HEALTH_COLD; break; case MAX8971_HEALTH_COOL: *val = POWER_SUPPLY_HEALTH_COOL; break; case MAX8971_HEALTH_WARM: *val = POWER_SUPPLY_HEALTH_GOOD; break; case MAX8971_HEALTH_HOT: *val = POWER_SUPPLY_HEALTH_HOT; break; case MAX8971_HEALTH_OVERHEAT: *val = POWER_SUPPLY_HEALTH_OVERHEAT; break; case MAX8971_HEALTH_UNKNOWN: default: *val = POWER_SUPPLY_HEALTH_UNKNOWN; } return 0; } static int max8971_get_online(struct max8971_data *priv, int *val) { u32 regval; int err; err = regmap_read(priv->regmap, MAX8971_REG_CHG_STAT, ®val); if (err) return err; if (priv->present) /* CHG_OK bit is 0 when charger is online */ *val = !(regval & MAX8971_CHG_MASK); else *val = priv->present; return 0; } static int max8971_get_integer(struct max8971_data *priv, enum max8971_field_idx fidx, u32 clamp_min, u32 clamp_max, u32 mult, int *val) { u32 regval; int err; err = regmap_field_read(priv->rfield[fidx], ®val); if (err) return err; *val = clamp_val(regval * mult, clamp_min, clamp_max); return 0; } static int max8971_set_integer(struct max8971_data *priv, enum max8971_field_idx fidx, u32 clamp_min, u32 clamp_max, u32 div, int val) { u32 regval; regval = clamp_val(val, clamp_min, clamp_max) / div; return regmap_field_write(priv->rfield[fidx], regval); } static int max8971_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct max8971_data *priv = power_supply_get_drvdata(psy); int err = 0; switch (psp) { case POWER_SUPPLY_PROP_STATUS: err = max8971_get_status(priv, &val->intval); break; case POWER_SUPPLY_PROP_CHARGE_TYPE: err = max8971_get_charge_type(priv, &val->intval); break; case POWER_SUPPLY_PROP_USB_TYPE: val->intval = priv->usb_type; break; case POWER_SUPPLY_PROP_HEALTH: err = max8971_get_health(priv, &val->intval); break; case POWER_SUPPLY_PROP_ONLINE: err = max8971_get_online(priv, &val->intval); break; case POWER_SUPPLY_PROP_PRESENT: val->intval = priv->present; break; case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: val->intval = MAX8971_CHG_CC_MAX; break; case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: err = max8971_get_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX, MAX8971_CHG_CC_STEP, &val->intval); break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: err = max8971_get_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX, MAX8971_DCILMT_STEP, &val->intval); break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = max8971_model; break; case POWER_SUPPLY_PROP_MANUFACTURER: val->strval = max8971_manufacturer; break; default: err = -EINVAL; } return err; } static int max8971_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct max8971_data *priv = power_supply_get_drvdata(psy); int err = 0; switch (psp) { case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: err = max8971_set_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX, MAX8971_CHG_CC_STEP, val->intval); break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: err = max8971_set_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX, MAX8971_DCILMT_STEP, val->intval); break; default: err = -EINVAL; } return err; }; static int max8971_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 max8971_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_PRESENT, 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 max8971_charger_desc = { .name = "max8971-charger", .type = POWER_SUPPLY_TYPE_USB, .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | BIT(POWER_SUPPLY_USB_TYPE_SDP) | BIT(POWER_SUPPLY_USB_TYPE_DCP) | BIT(POWER_SUPPLY_USB_TYPE_CDP) | BIT(POWER_SUPPLY_USB_TYPE_ACA), .properties = max8971_properties, .num_properties = ARRAY_SIZE(max8971_properties), .get_property = max8971_get_property, .set_property = max8971_set_property, .property_is_writeable = max8971_property_is_writeable, }; static void max8971_update_config(struct max8971_data *priv) { regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_UNLOCKED); if (priv->fchgt != MAX8971_FCHGT_DEFAULT) regmap_field_write(priv->rfield[FCHG_T], priv->fchgt); regmap_write_bits(priv->regmap, MAX8971_REG_DCCRNT, MAX8971_CHGRSTRT_MASK, MAX8971_CHGRSTRT_MASK); if (priv->tofft != MAX8971_TOPOFFT_DEFAULT) regmap_field_write(priv->rfield[TOPOFF_T], priv->tofft); if (priv->toffs) regmap_field_write(priv->rfield[TOPOFF_S], priv->toffs); regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_LOCKED); } static ssize_t fast_charge_timer_show(struct device *dev, struct device_attribute *attr, char *buf) { struct power_supply *psy = to_power_supply(dev); struct max8971_data *priv = power_supply_get_drvdata(psy); u32 regval; int err; err = regmap_field_read(priv->rfield[FCHG_T], ®val); if (err) return err; switch (regval) { case 0x1 ... 0x7: /* Time is off by 3 hours comparing to value */ regval += 3; break; case 0x0: default: regval = 0; break; } return sysfs_emit(buf, "%u\n", regval); } static ssize_t fast_charge_timer_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct power_supply *psy = to_power_supply(dev); struct max8971_data *priv = power_supply_get_drvdata(psy); unsigned long hours; int val, err; err = kstrtoul(buf, 10, &hours); if (err) return err; val = hours - 3; if (val <= 0 || val > 7) priv->fchgt = 0; else priv->fchgt = val; max8971_update_config(priv); return count; } static ssize_t top_off_threshold_current_show(struct device *dev, struct device_attribute *attr, char *buf) { struct power_supply *psy = to_power_supply(dev); struct max8971_data *priv = power_supply_get_drvdata(psy); u32 regval, val; int err; err = regmap_field_read(priv->rfield[TOPOFF_S], ®val); if (err) return err; /* 50uA start with 50uA step */ val = regval * 50 + 50; val *= 1000; return sysfs_emit(buf, "%u\n", val); } static ssize_t top_off_threshold_current_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct power_supply *psy = to_power_supply(dev); struct max8971_data *priv = power_supply_get_drvdata(psy); unsigned long uamp; int err; err = kstrtoul(buf, 10, &uamp); if (err) return err; if (uamp < 50000 || uamp > 200000) return -EINVAL; priv->toffs = uamp / 50000 - 1; max8971_update_config(priv); return count; } static ssize_t top_off_timer_show(struct device *dev, struct device_attribute *attr, char *buf) { struct power_supply *psy = to_power_supply(dev); struct max8971_data *priv = power_supply_get_drvdata(psy); u32 regval; int err; err = regmap_field_read(priv->rfield[TOPOFF_T], ®val); if (err) return err; /* 10 min intervals */ regval *= 10; return sysfs_emit(buf, "%u\n", regval); } static ssize_t top_off_timer_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct power_supply *psy = to_power_supply(dev); struct max8971_data *priv = power_supply_get_drvdata(psy); unsigned long minutes; int err; err = kstrtoul(buf, 10, &minutes); if (err) return err; if (minutes > 70) return -EINVAL; priv->tofft = minutes / 10; max8971_update_config(priv); return count; } static DEVICE_ATTR_RW(fast_charge_timer); static DEVICE_ATTR_RW(top_off_threshold_current); static DEVICE_ATTR_RW(top_off_timer); static struct attribute *max8971_attrs[] = { &dev_attr_fast_charge_timer.attr, &dev_attr_top_off_threshold_current.attr, &dev_attr_top_off_timer.attr, NULL }; ATTRIBUTE_GROUPS(max8971); static void max8971_extcon_evt_worker(struct work_struct *work) { struct max8971_data *priv = container_of(work, struct max8971_data, extcon_work.work); struct device *dev = priv->dev; struct extcon_dev *edev = priv->edev; u32 chgcc, dcilmt; if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0) { dev_dbg(dev, "USB SDP charger is connected\n"); priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP; chgcc = 500000; dcilmt = 500000; } else if (extcon_get_state(edev, EXTCON_USB) > 0) { dev_dbg(dev, "USB charger is connected\n"); priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP; chgcc = 500000; dcilmt = 500000; } else if (extcon_get_state(edev, EXTCON_DISP_MHL) > 0) { dev_dbg(dev, "MHL plug is connected\n"); priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP; chgcc = 500000; dcilmt = 500000; } else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0) { dev_dbg(dev, "USB DCP charger is connected\n"); priv->usb_type = POWER_SUPPLY_USB_TYPE_DCP; chgcc = 900000; dcilmt = 1200000; } else if (extcon_get_state(edev, EXTCON_CHG_USB_FAST) > 0) { dev_dbg(dev, "USB FAST charger is connected\n"); priv->usb_type = POWER_SUPPLY_USB_TYPE_ACA; chgcc = 900000; dcilmt = 1200000; } else if (extcon_get_state(edev, EXTCON_CHG_USB_SLOW) > 0) { dev_dbg(dev, "USB SLOW charger is connected\n"); priv->usb_type = POWER_SUPPLY_USB_TYPE_ACA; chgcc = 900000; dcilmt = 1200000; } else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0) { dev_dbg(dev, "USB CDP charger is connected\n"); priv->usb_type = POWER_SUPPLY_USB_TYPE_CDP; chgcc = 900000; dcilmt = 1200000; } else { dev_dbg(dev, "USB state is unknown\n"); priv->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; return; } regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_UNLOCKED); max8971_set_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX, MAX8971_CHG_CC_STEP, chgcc); max8971_set_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX, MAX8971_DCILMT_STEP, dcilmt); regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_LOCKED); } static int extcon_get_charger_type(struct notifier_block *nb, unsigned long state, void *data) { struct max8971_data *priv = container_of(nb, struct max8971_data, extcon_nb); schedule_delayed_work(&priv->extcon_work, 0); return NOTIFY_OK; } static irqreturn_t max8971_interrupt(int irq, void *dev_id) { struct max8971_data *priv = dev_id; struct device *dev = priv->dev; int err, state; err = regmap_read(priv->regmap, MAX8971_REG_CHGINT, &state); if (err) dev_err(dev, "interrupt reg read failed %d\n", err); err = regmap_write_bits(priv->regmap, MAX8971_REG_CHGINT_MASK, MAX8971_AICL_MASK, MAX8971_AICL_MASK); if (err) dev_err(dev, "failed to mask IRQ\n"); /* set presence prop */ priv->present = state & MAX8971_REG_CHG_RST; /* on every plug chip resets to default */ if (priv->present) max8971_update_config(priv); /* update supply status */ power_supply_changed(priv->psy_mains); return IRQ_HANDLED; } static int max8971_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct max8971_data *priv; struct device_node *extcon; struct power_supply_config cfg = { }; int err, i; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = dev; priv->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; i2c_set_clientdata(client, priv); priv->regmap = devm_regmap_init_i2c(client, &max8971_regmap_config); if (IS_ERR(priv->regmap)) return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n"); for (i = 0; i < MAX8971_N_REGMAP_FIELDS; i++) { priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap, max8971_reg_field[i]); if (IS_ERR(priv->rfield[i])) return dev_err_probe(dev, PTR_ERR(priv->rfield[i]), "cannot allocate regmap field\n"); } cfg.attr_grp = max8971_groups; cfg.drv_data = priv; cfg.fwnode = dev_fwnode(dev); priv->psy_mains = devm_power_supply_register(dev, &max8971_charger_desc, &cfg); if (IS_ERR(priv->psy_mains)) return dev_err_probe(dev, PTR_ERR(priv->psy_mains), "failed to register mains supply\n"); err = regmap_write_bits(priv->regmap, MAX8971_REG_CHGINT_MASK, MAX8971_AICL_MASK, MAX8971_AICL_MASK); if (err) return dev_err_probe(dev, err, "failed to mask IRQ\n"); err = devm_request_threaded_irq(dev, client->irq, NULL, &max8971_interrupt, IRQF_ONESHOT | IRQF_SHARED, client->name, priv); if (err) return dev_err_probe(dev, err, "failed to register IRQ %d\n", client->irq); extcon = of_graph_get_remote_node(dev->of_node, -1, -1); if (!extcon) return 0; priv->edev = extcon_find_edev_by_node(extcon); of_node_put(extcon); if (IS_ERR(priv->edev)) return dev_err_probe(dev, PTR_ERR(priv->edev), "failed to find extcon\n"); err = devm_delayed_work_autocancel(dev, &priv->extcon_work, max8971_extcon_evt_worker); if (err) return dev_err_probe(dev, err, "failed to add extcon evt stop action\n"); priv->extcon_nb.notifier_call = extcon_get_charger_type; err = devm_extcon_register_notifier_all(dev, priv->edev, &priv->extcon_nb); if (err) return dev_err_probe(dev, err, "failed to register notifier\n"); /* Initial configuration work with 1 sec delay */ schedule_delayed_work(&priv->extcon_work, msecs_to_jiffies(1000)); return 0; } static int __maybe_unused max8971_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct max8971_data *priv = i2c_get_clientdata(client); irq_wake_thread(client->irq, priv); return 0; } static SIMPLE_DEV_PM_OPS(max8971_pm_ops, NULL, max8971_resume); static const struct of_device_id max8971_match_ids[] = { { .compatible = "maxim,max8971" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, max8971_match_ids); static const struct i2c_device_id max8971_i2c_id[] = { { "max8971" }, { } }; MODULE_DEVICE_TABLE(i2c, max8971_i2c_id); static struct i2c_driver max8971_driver = { .driver = { .name = "max8971-charger", .of_match_table = max8971_match_ids, .pm = &max8971_pm_ops, }, .probe = max8971_probe, .id_table = max8971_i2c_id, }; module_i2c_driver(max8971_driver); MODULE_AUTHOR("Svyatoslav Ryhel "); MODULE_DESCRIPTION("MAX8971 Charger Driver"); MODULE_LICENSE("GPL");