| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (c) 2023, Nikita Travkin <nikita@trvn.ru>
- */
- #include <linux/errno.h>
- #include <linux/module.h>
- #include <linux/platform_device.h>
- #include <linux/power_supply.h>
- #include <linux/property.h>
- #include <linux/regmap.h>
- #include <linux/slab.h>
- #include <linux/delay.h>
- #include <linux/interrupt.h>
- #include <linux/extcon-provider.h>
- #include <linux/mod_devicetable.h>
- /* Two bytes: type + subtype */
- #define PM8916_PERPH_TYPE 0x04
- #define PM8916_LBC_CHGR_TYPE 0x1502
- #define PM8916_LBC_BAT_IF_TYPE 0x1602
- #define PM8916_LBC_USB_TYPE 0x1702
- #define PM8916_LBC_MISC_TYPE 0x1802
- #define PM8916_LBC_CHGR_CHG_OPTION 0x08
- #define PM8916_LBC_CHGR_PMIC_CHARGER BIT(7)
- #define PM8916_LBC_CHGR_CHG_STATUS 0x09
- #define PM8916_INT_RT_STS 0x10
- #define PM8916_LBC_USB_USBIN_VALID BIT(1)
- #define PM8916_LBC_CHGR_VDD_MAX 0x40
- #define PM8916_LBC_CHGR_VDD_SAFE 0x41
- #define PM8916_LBC_CHGR_IBAT_MAX 0x44
- #define PM8916_LBC_CHGR_IBAT_SAFE 0x45
- #define PM8916_LBC_CHGR_TCHG_MAX_EN 0x60
- #define PM8916_LBC_CHGR_TCHG_MAX_ENABLED BIT(7)
- #define PM8916_LBC_CHGR_TCHG_MAX 0x61
- #define PM8916_LBC_CHGR_CHG_CTRL 0x49
- #define PM8916_LBC_CHGR_CHG_EN BIT(7)
- #define PM8916_LBC_CHGR_PSTG_EN BIT(5)
- #define PM8916_LBC_CHGR_MIN_CURRENT 90000
- #define PM8916_LBC_CHGR_MAX_CURRENT 1440000
- #define PM8916_LBC_CHGR_MIN_VOLTAGE 4000000
- #define PM8916_LBC_CHGR_MAX_VOLTAGE 4775000
- #define PM8916_LBC_CHGR_VOLTAGE_STEP 25000
- #define PM8916_LBC_CHGR_MIN_TIME 4
- #define PM8916_LBC_CHGR_MAX_TIME 256
- struct pm8916_lbc_charger {
- struct device *dev;
- struct extcon_dev *edev;
- struct power_supply *charger;
- struct power_supply_battery_info *info;
- struct regmap *regmap;
- unsigned int reg[4];
- bool online;
- unsigned int charge_voltage_max;
- unsigned int charge_voltage_safe;
- unsigned int charge_current_max;
- unsigned int charge_current_safe;
- };
- static const unsigned int pm8916_lbc_charger_cable[] = {
- EXTCON_USB,
- EXTCON_NONE,
- };
- enum {
- LBC_CHGR = 0,
- LBC_BAT_IF,
- LBC_USB,
- LBC_MISC,
- };
- static int pm8916_lbc_charger_configure(struct pm8916_lbc_charger *chg)
- {
- int ret = 0;
- unsigned int tmp;
- chg->charge_voltage_max = clamp_t(u32, chg->charge_voltage_max,
- PM8916_LBC_CHGR_MIN_VOLTAGE, chg->charge_voltage_safe);
- tmp = chg->charge_voltage_max - PM8916_LBC_CHGR_MIN_VOLTAGE;
- tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP;
- chg->charge_voltage_max = PM8916_LBC_CHGR_MIN_VOLTAGE + tmp * PM8916_LBC_CHGR_VOLTAGE_STEP;
- ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX, tmp);
- if (ret)
- goto error;
- chg->charge_current_max = min(chg->charge_current_max, chg->charge_current_safe);
- tmp = clamp_t(u32, chg->charge_current_max,
- PM8916_LBC_CHGR_MIN_CURRENT, PM8916_LBC_CHGR_MAX_CURRENT);
- tmp = chg->charge_current_max / PM8916_LBC_CHGR_MIN_CURRENT - 1;
- chg->charge_current_max = (tmp + 1) * PM8916_LBC_CHGR_MIN_CURRENT;
- ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_MAX, tmp);
- if (ret)
- goto error;
- ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_CTRL,
- PM8916_LBC_CHGR_CHG_EN | PM8916_LBC_CHGR_PSTG_EN);
- if (ret)
- goto error;
- return ret;
- error:
- dev_err(chg->dev, "Failed to configure charging: %pe\n", ERR_PTR(ret));
- return ret;
- }
- static int pm8916_lbc_charger_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
- {
- struct pm8916_lbc_charger *chg = power_supply_get_drvdata(psy);
- switch (psp) {
- case POWER_SUPPLY_PROP_ONLINE:
- val->intval = chg->online;
- return 0;
- case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
- val->intval = chg->charge_voltage_max;
- return 0;
- case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
- val->intval = chg->charge_current_max;
- return 0;
- default:
- return -EINVAL;
- };
- }
- static int pm8916_lbc_charger_set_property(struct power_supply *psy,
- enum power_supply_property prop,
- const union power_supply_propval *val)
- {
- struct pm8916_lbc_charger *chg = power_supply_get_drvdata(psy);
- switch (prop) {
- case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
- chg->charge_current_max = val->intval;
- return pm8916_lbc_charger_configure(chg);
- default:
- return -EINVAL;
- }
- }
- static int pm8916_lbc_charger_property_is_writeable(struct power_supply *psy,
- enum power_supply_property psp)
- {
- switch (psp) {
- case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
- return true;
- default:
- return false;
- }
- }
- static enum power_supply_property pm8916_lbc_charger_properties[] = {
- POWER_SUPPLY_PROP_ONLINE,
- POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
- POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
- };
- static irqreturn_t pm8916_lbc_charger_state_changed_irq(int irq, void *data)
- {
- struct pm8916_lbc_charger *chg = data;
- unsigned int tmp;
- int ret;
- ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp);
- if (ret)
- return IRQ_HANDLED;
- chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID);
- extcon_set_state_sync(chg->edev, EXTCON_USB, chg->online);
- power_supply_changed(chg->charger);
- return IRQ_HANDLED;
- }
- static int pm8916_lbc_charger_probe_dt(struct pm8916_lbc_charger *chg)
- {
- struct device *dev = chg->dev;
- int ret = 0;
- unsigned int tmp;
- ret = device_property_read_u32(dev, "qcom,fast-charge-safe-voltage", &chg->charge_voltage_safe);
- if (ret)
- return ret;
- if (chg->charge_voltage_safe < PM8916_LBC_CHGR_MIN_VOLTAGE)
- return -EINVAL;
- chg->charge_voltage_safe = clamp_t(u32, chg->charge_voltage_safe,
- PM8916_LBC_CHGR_MIN_VOLTAGE, PM8916_LBC_CHGR_MAX_VOLTAGE);
- tmp = chg->charge_voltage_safe - PM8916_LBC_CHGR_MIN_VOLTAGE;
- tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP;
- ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_SAFE, tmp);
- if (ret)
- return ret;
- ret = device_property_read_u32(dev, "qcom,fast-charge-safe-current", &chg->charge_current_safe);
- if (ret)
- return ret;
- if (chg->charge_current_safe < PM8916_LBC_CHGR_MIN_CURRENT)
- return -EINVAL;
- chg->charge_current_safe = clamp_t(u32, chg->charge_current_safe,
- PM8916_LBC_CHGR_MIN_CURRENT, PM8916_LBC_CHGR_MAX_CURRENT);
- chg->charge_current_max = chg->charge_current_safe;
- tmp = chg->charge_current_safe / PM8916_LBC_CHGR_MIN_CURRENT - 1;
- ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_SAFE, tmp);
- if (ret)
- return ret;
- /* Disable charger timeout. */
- ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 0x00);
- if (ret)
- return ret;
- return ret;
- }
- static const struct power_supply_desc pm8916_lbc_charger_psy_desc = {
- .name = "pm8916-lbc-chgr",
- .type = POWER_SUPPLY_TYPE_USB,
- .properties = pm8916_lbc_charger_properties,
- .num_properties = ARRAY_SIZE(pm8916_lbc_charger_properties),
- .get_property = pm8916_lbc_charger_get_property,
- .set_property = pm8916_lbc_charger_set_property,
- .property_is_writeable = pm8916_lbc_charger_property_is_writeable,
- };
- static int pm8916_lbc_charger_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct pm8916_lbc_charger *chg;
- struct power_supply_config psy_cfg = {};
- int ret, len, irq;
- unsigned int tmp;
- chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);
- if (!chg)
- return -ENOMEM;
- chg->dev = dev;
- chg->regmap = dev_get_regmap(pdev->dev.parent, NULL);
- if (!chg->regmap)
- return -ENODEV;
- len = device_property_count_u32(dev, "reg");
- if (len < 0)
- return len;
- if (len != 4)
- return dev_err_probe(dev, -EINVAL,
- "Wrong amount of reg values: %d (4 expected)\n", len);
- irq = platform_get_irq_byname(pdev, "usb_vbus");
- if (irq < 0)
- return irq;
- ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq,
- IRQF_ONESHOT, "pm8916_lbc", chg);
- if (ret)
- return ret;
- ret = device_property_read_u32_array(dev, "reg", chg->reg, len);
- if (ret)
- return ret;
- ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_PERPH_TYPE, &tmp, 2);
- if (ret)
- goto comm_error;
- if (tmp != PM8916_LBC_CHGR_TYPE)
- goto type_error;
- ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_BAT_IF] + PM8916_PERPH_TYPE, &tmp, 2);
- if (ret)
- goto comm_error;
- if (tmp != PM8916_LBC_BAT_IF_TYPE)
- goto type_error;
- ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_USB] + PM8916_PERPH_TYPE, &tmp, 2);
- if (ret)
- goto comm_error;
- if (tmp != PM8916_LBC_USB_TYPE)
- goto type_error;
- ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_MISC] + PM8916_PERPH_TYPE, &tmp, 2);
- if (ret)
- goto comm_error;
- if (tmp != PM8916_LBC_MISC_TYPE)
- goto type_error;
- ret = regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_OPTION, &tmp);
- if (ret)
- goto comm_error;
- if (tmp != PM8916_LBC_CHGR_PMIC_CHARGER)
- dev_err_probe(dev, -ENODEV, "The system is using an external charger\n");
- ret = pm8916_lbc_charger_probe_dt(chg);
- if (ret)
- dev_err_probe(dev, ret, "Error while parsing device tree\n");
- psy_cfg.drv_data = chg;
- psy_cfg.of_node = dev->of_node;
- chg->charger = devm_power_supply_register(dev, &pm8916_lbc_charger_psy_desc, &psy_cfg);
- if (IS_ERR(chg->charger))
- return dev_err_probe(dev, PTR_ERR(chg->charger), "Unable to register charger\n");
- ret = power_supply_get_battery_info(chg->charger, &chg->info);
- if (ret)
- return dev_err_probe(dev, ret, "Unable to get battery info\n");
- chg->edev = devm_extcon_dev_allocate(dev, pm8916_lbc_charger_cable);
- if (IS_ERR(chg->edev))
- return PTR_ERR(chg->edev);
- ret = devm_extcon_dev_register(dev, chg->edev);
- if (ret < 0)
- return dev_err_probe(dev, ret, "failed to register extcon device\n");
- ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp);
- if (ret)
- goto comm_error;
- chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID);
- extcon_set_state_sync(chg->edev, EXTCON_USB, chg->online);
- chg->charge_voltage_max = chg->info->voltage_max_design_uv;
- ret = pm8916_lbc_charger_configure(chg);
- if (ret)
- return ret;
- return 0;
- comm_error:
- return dev_err_probe(dev, ret, "Unable to communicate with device\n");
- type_error:
- return dev_err_probe(dev, -ENODEV, "Device reported wrong type: 0x%X\n", tmp);
- }
- static const struct of_device_id pm8916_lbc_charger_of_match[] = {
- { .compatible = "qcom,pm8916-lbc", },
- {}
- };
- MODULE_DEVICE_TABLE(of, pm8916_lbc_charger_of_match);
- static struct platform_driver pm8916_lbc_charger_driver = {
- .driver = {
- .name = "pm8916-lbc",
- .of_match_table = pm8916_lbc_charger_of_match,
- },
- .probe = pm8916_lbc_charger_probe,
- };
- module_platform_driver(pm8916_lbc_charger_driver);
- MODULE_DESCRIPTION("pm8916 LBC driver");
- MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
- MODULE_LICENSE("GPL");
|