// SPDX-License-Identifier: GPL-2.0-only /* * rk818 usb power driver * * Copyright (c) 2021 Ondřej Jirman */ #include #include #include #include #include #include #include #include #define RK818_CHG_STS_MASK (7u << 4) /* charger status */ #define RK818_CHG_STS_NONE (0u << 4) #define RK818_CHG_STS_WAKEUP_CUR (1u << 4) #define RK818_CHG_STS_TRICKLE_CUR (2u << 4) #define RK818_CHG_STS_CC_OR_CV (3u << 4) #define RK818_CHG_STS_TERMINATED (4u << 4) #define RK818_CHG_STS_USB_OV (5u << 4) #define RK818_CHG_STS_BAT_TEMP_FAULT (6u << 4) #define RK818_CHG_STS_TIMEOUT (7u << 4) /* RK818_SUP_STS_REG */ #define RK818_SUP_STS_USB_VLIM_EN BIT(3) /* input voltage limit enable */ #define RK818_SUP_STS_USB_ILIM_EN BIT(2) /* input current limit enable */ #define RK818_SUP_STS_USB_EXS BIT(1) /* USB power connected */ #define RK818_SUP_STS_USB_EFF BIT(0) /* USB fault */ /* RK818_USB_CTRL_REG */ #define RK818_USB_CTRL_USB_ILIM_MASK (0xfu) #define RK818_USB_CTRL_USB_CHG_SD_VSEL_OFFSET 4 #define RK818_USB_CTRL_USB_CHG_SD_VSEL_MASK (0x7u << 4) /* RK818_CHRG_CTRL_REG1 */ #define RK818_CHRG_CTRL_REG1_CHRG_EN BIT(7) #define RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_OFFSET 4 #define RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_MASK (0x7u << 4) #define RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_OFFSET 0 #define RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_MASK (0xfu << 0) /* RK818_CHRG_CTRL_REG3 */ #define RK818_CHRG_CTRL_REG3_CHRG_TERM_DIGITAL BIT(5) struct rk818_charger { struct device *dev; struct rk808 *rk818; struct regmap *regmap; struct power_supply *usb_psy; struct power_supply *charger_psy; bool apply_ilim; }; // {{{ USB supply static int rk818_usb_set_input_current_max(struct rk818_charger *cg, int val) { int ret; unsigned reg; if (val < 450000) reg = 1; else if (val < 850000) reg = 0; else if (val < 1000000) reg = 2; else if (val < 3000000) reg = 3 + (val - 1000000) / 250000; else reg = 11; dev_info(cg->dev, "applying input current limit %d mA\n", val / 1000); ret = regmap_update_bits(cg->regmap, RK818_USB_CTRL_REG, RK818_USB_CTRL_USB_ILIM_MASK, reg); if (ret) dev_err(cg->dev, "USB input current limit setting failed (%d)\n", ret); return ret; } static int rk818_usb_get_input_current_max(struct rk818_charger *cg, int *val) { unsigned reg; int ret; ret = regmap_read(cg->regmap, RK818_USB_CTRL_REG, ®); if (ret) { dev_err(cg->dev, "USB input current limit getting failed (%d)\n", ret); return ret; } reg &= RK818_USB_CTRL_USB_ILIM_MASK; if (reg == 0) *val = 450000; else if (reg == 1) *val = 80000; else if (reg == 2) *val = 850000; else if (reg < 11) *val = 1000000 + (reg - 3) * 250000; else *val = 3000000; return 0; } static int rk818_usb_set_input_voltage_min(struct rk818_charger *cg, int val) { unsigned reg; int ret; if (val < 2780000) reg = 0; else if (val < 3270000) reg = (val - 2780000) / 70000; else reg = 7; ret = regmap_update_bits(cg->regmap, RK818_USB_CTRL_REG, RK818_USB_CTRL_USB_CHG_SD_VSEL_MASK, reg << RK818_USB_CTRL_USB_CHG_SD_VSEL_OFFSET); if (ret) dev_err(cg->dev, "USB input voltage limit setting failed (%d)\n", ret); return ret; } static int rk818_usb_get_input_voltage_min(struct rk818_charger *cg, int *val) { unsigned reg; int ret; ret = regmap_read(cg->regmap, RK818_USB_CTRL_REG, ®); if (ret) { dev_err(cg->dev, "USB input voltage limit getting failed (%d)\n", ret); return ret; } reg &= RK818_USB_CTRL_USB_CHG_SD_VSEL_MASK; reg >>= RK818_USB_CTRL_USB_CHG_SD_VSEL_OFFSET; *val = 2780000 + (reg * 70000); return 0; } static int rk818_usb_power_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct rk818_charger *cg = power_supply_get_drvdata(psy); unsigned reg; int ret; switch (psp) { case POWER_SUPPLY_PROP_PRESENT: ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); if (ret) return ret; val->intval = !!(reg & RK818_SUP_STS_USB_EXS); break; case POWER_SUPPLY_PROP_HEALTH: ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); if (ret) return ret; if (!(reg & RK818_SUP_STS_USB_EXS)) { val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; } else if (reg & RK818_SUP_STS_USB_EFF) { val->intval = POWER_SUPPLY_HEALTH_GOOD; } else { val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; } break; case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: return rk818_usb_get_input_voltage_min(cg, &val->intval); case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return rk818_usb_get_input_current_max(cg, &val->intval); default: return -EINVAL; } return 0; } static int rk818_usb_power_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct rk818_charger *cg = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: return rk818_usb_set_input_voltage_min(cg, val->intval); case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return rk818_usb_set_input_current_max(cg, val->intval); default: return -EINVAL; } } static int rk818_usb_power_prop_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: return 1; default: return 0; } } /* Sync the input-current-limit with our parent supply (if we have one) */ static void rk818_usb_power_external_power_changed(struct power_supply *psy) { struct rk818_charger *cg = power_supply_get_drvdata(psy); union power_supply_propval val; int ret; ret = power_supply_get_property_from_supplier(cg->usb_psy, POWER_SUPPLY_PROP_CURRENT_MAX, &val); if (ret) return; /* * We only want to start applying input current limit after we get first * non-0 value from the supplier. Until then, we keep the limit applied * by the bootloader. If we lower the limit before the charger is properly * detected, we risk boot failure due to insufficient power. */ if (!cg->apply_ilim) { if (!val.intval) return; cg->apply_ilim = true; } if (val.intval < 500000) val.intval = 500000; rk818_usb_power_set_property(cg->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); } static enum power_supply_property rk818_usb_power_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, }; static const struct power_supply_desc rk818_usb_desc = { .name = "rk818-usb", .type = POWER_SUPPLY_TYPE_USB, .properties = rk818_usb_power_props, .num_properties = ARRAY_SIZE(rk818_usb_power_props), .property_is_writeable = rk818_usb_power_prop_writeable, .get_property = rk818_usb_power_get_property, .set_property = rk818_usb_power_set_property, .external_power_changed = rk818_usb_power_external_power_changed, }; // }}} // {{{ Charger supply static int rk818_charger_set_current_max(struct rk818_charger *cg, int val) { unsigned reg; int ret; if (val < 1000000) reg = 0; else if (val < 3000000) reg = (val - 1000000) / 200000; else reg = 10; ret = regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_MASK, reg << RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_OFFSET); if (ret) dev_err(cg->dev, "Charging max current setting failed (%d)\n", ret); return ret; } static int rk818_charger_get_current_max(struct rk818_charger *cg, int *val) { unsigned reg; int ret; ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); if (ret) { dev_err(cg->dev, "Charging max current getting failed (%d)\n", ret); return ret; } reg &= RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_MASK; reg >>= RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_OFFSET; *val = 1000000 + reg * 200000; return 0; } static int rk818_charger_set_voltage_max(struct rk818_charger *cg, int val) { unsigned reg; int ret; if (val < 4050000) reg = 0; else if (val < 4350000) reg = (val - 4050000) / 50000; else reg = 6; ret = regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_MASK, reg << RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_OFFSET); if (ret) dev_err(cg->dev, "Charging end voltage setting failed (%d)\n", ret); return ret; } static int rk818_charger_get_voltage_max(struct rk818_charger *cg, int *val) { unsigned reg; int ret; ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); if (ret) { dev_err(cg->dev, "Charging end voltage getting failed (%d)\n", ret); return ret; } reg &= RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_MASK; reg >>= RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_OFFSET; *val = 4050000 + reg * 50000; return 0; } struct rk818_battery; struct rk818_battery* rk818_battery_get(void); int rk818_battery_get_property(struct rk818_battery *di, enum power_supply_property psp, union power_supply_propval *val); static int rk818_charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct rk818_charger *cg = power_supply_get_drvdata(psy); struct rk818_battery* di = rk818_battery_get(); unsigned reg; int ret; switch (psp) { case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_VOLTAGE_NOW: case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_CAPACITY: case POWER_SUPPLY_PROP_TEMP: case POWER_SUPPLY_PROP_STATUS: case POWER_SUPPLY_PROP_CHARGE_COUNTER: case POWER_SUPPLY_PROP_CHARGE_FULL: if (!di) return -ENODEV; return rk818_battery_get_property(di, psp, val); default:; } switch (psp) { case POWER_SUPPLY_PROP_ONLINE: ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); if (ret) { dev_err(cg->dev, "failed to read the charger state (%d)\n", ret); return ret; } val->intval = !!(reg & RK818_CHRG_CTRL_REG1_CHRG_EN); break; case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); if (ret) { dev_err(cg->dev, "failed to read the charger state (%d)\n", ret); return ret; } if (reg & RK818_CHRG_CTRL_REG1_CHRG_EN) val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; else val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; return 0; case POWER_SUPPLY_PROP_STATUS: ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); if (ret) return ret; switch (reg & RK818_CHG_STS_MASK) { case RK818_CHG_STS_WAKEUP_CUR: case RK818_CHG_STS_TRICKLE_CUR: case RK818_CHG_STS_CC_OR_CV: val->intval = POWER_SUPPLY_STATUS_CHARGING; break; case RK818_CHG_STS_TERMINATED: default: val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; } break; case POWER_SUPPLY_PROP_CHARGE_TYPE: ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); if (ret) return ret; switch (reg & RK818_CHG_STS_MASK) { case RK818_CHG_STS_WAKEUP_CUR: case RK818_CHG_STS_TRICKLE_CUR: val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; break; case RK818_CHG_STS_CC_OR_CV: val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; break; case RK818_CHG_STS_TERMINATED: val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; break; default: val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; break; } break; case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG2, ®); if (ret) return ret; val->intval = 100000 + ((reg >> 6) & 3) * 50000; break; case POWER_SUPPLY_PROP_HEALTH: ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); if (ret) return ret; switch (reg & RK818_CHG_STS_MASK) { case RK818_CHG_STS_USB_OV: val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; break; case RK818_CHG_STS_BAT_TEMP_FAULT: val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; break; case RK818_CHG_STS_TIMEOUT: val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; break; default: val->intval = POWER_SUPPLY_HEALTH_GOOD; break; } break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: return rk818_charger_get_voltage_max(cg, &val->intval); case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: ret = rk818_charger_get_current_max(cg, &val->intval); val->intval /= 10; return ret; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: return rk818_charger_get_current_max(cg, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: val->intval = 4350000; break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: val->intval = 3000000; break; case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: val->intval = 11400000; return 0; case POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN: val->intval = 0; return 0; default: return -EINVAL; } return 0; } static int rk818_charger_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct rk818_charger *cg = power_supply_get_drvdata(psy); int ret; switch (psp) { case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: switch (val->intval) { case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: return regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, RK818_CHRG_CTRL_REG1_CHRG_EN, RK818_CHRG_CTRL_REG1_CHRG_EN); case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: return regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, RK818_CHRG_CTRL_REG1_CHRG_EN, 0); default: return -EINVAL; } case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: return rk818_charger_set_voltage_max(cg, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: return rk818_charger_set_current_max(cg, val->intval); default: return -EINVAL; } } static int rk818_charger_prop_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: return 1; default: return 0; } } static enum power_supply_property rk818_charger_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, POWER_SUPPLY_PROP_PRECHARGE_CURRENT, POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN, // inherited from BSP battery driver POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_CHARGE_COUNTER, POWER_SUPPLY_PROP_CHARGE_FULL, }; /* * We import some capacity tracking functionality from the BSP battery driver. * Some poor soul will have to understand and clean up the BSP battery driver, * but not me, not now. :) */ static const struct power_supply_desc rk818_charger_desc = { .name = "rk818-battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = rk818_charger_props, .num_properties = ARRAY_SIZE(rk818_charger_props), .property_is_writeable = rk818_charger_prop_writeable, .get_property = rk818_charger_get_property, .set_property = rk818_charger_set_property, }; // }}} static int rk818_charger_probe(struct platform_device *pdev) { struct rk808 *rk818 = dev_get_drvdata(pdev->dev.parent); struct power_supply_config psy_cfg = { }; struct device *dev = &pdev->dev; struct rk818_charger *cg; int ret; cg = devm_kzalloc(dev, sizeof(*cg), GFP_KERNEL); if (!cg) return -ENOMEM; cg->rk818 = rk818; cg->dev = dev; cg->regmap = rk818->regmap; platform_set_drvdata(pdev, cg); psy_cfg.drv_data = cg; psy_cfg.of_node = dev->of_node; cg->usb_psy = devm_power_supply_register(dev, &rk818_usb_desc, &psy_cfg); if (IS_ERR(cg->usb_psy)) return dev_err_probe(dev, PTR_ERR(cg->usb_psy), "register usb power supply fail\n"); cg->charger_psy = devm_power_supply_register(dev, &rk818_charger_desc, &psy_cfg); if (IS_ERR(cg->charger_psy)) return dev_err_probe(dev, PTR_ERR(cg->charger_psy), "register charger power supply fail\n"); /* disable voltage limit and enable input current limit */ ret = regmap_update_bits(cg->regmap, RK818_SUP_STS_REG, RK818_SUP_STS_USB_ILIM_EN | RK818_SUP_STS_USB_VLIM_EN, RK818_SUP_STS_USB_ILIM_EN); if (ret) dev_warn(cg->dev, "failed to enable input current limit (%d)\n", ret); /* make sure analog control loop is enabled */ ret = regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG3, RK818_CHRG_CTRL_REG3_CHRG_TERM_DIGITAL, 0); if (ret) dev_warn(cg->dev, "failed to enable analog control loop (%d)\n", ret); /* enable charger and set some reasonable limits on each boot */ ret = regmap_write(cg->regmap, RK818_CHRG_CTRL_REG1, RK818_CHRG_CTRL_REG1_CHRG_EN | (1) /* 1.2A */ | (5 << 4) /* 4.3V */); if (ret) dev_warn(cg->dev, "failed to enable charger (%d)\n", ret); rk818_usb_power_external_power_changed(cg->usb_psy); return 0; } static int rk818_charger_remove(struct platform_device *pdev) { //struct rk818_charger *cg = platform_get_drvdata(pdev); return 0; } static void rk818_charger_shutdown(struct platform_device *pdev) { } static int rk818_charger_suspend(struct platform_device *pdev, pm_message_t state) { return 0; } static int rk818_charger_resume(struct platform_device *pdev) { return 0; } static const struct of_device_id rk818_charger_of_match[] = { { .compatible = "rockchip,rk818-charger", }, { }, }; static struct platform_driver rk818_charger_driver = { .probe = rk818_charger_probe, .remove = rk818_charger_remove, .suspend = rk818_charger_suspend, .resume = rk818_charger_resume, .shutdown = rk818_charger_shutdown, .driver = { .name = "rk818-charger", .of_match_table = rk818_charger_of_match, }, }; module_platform_driver(rk818_charger_driver); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:rk818-charger"); MODULE_AUTHOR("Ondřej Jirman ");