382 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (c) 2023, Nikita Travkin <nikita@trvn.ru>4 */5 6#include <linux/errno.h>7#include <linux/module.h>8#include <linux/platform_device.h>9#include <linux/power_supply.h>10#include <linux/property.h>11#include <linux/regmap.h>12#include <linux/slab.h>13#include <linux/delay.h>14#include <linux/interrupt.h>15#include <linux/extcon-provider.h>16#include <linux/mod_devicetable.h>17 18/* Two bytes: type + subtype */19#define PM8916_PERPH_TYPE 0x0420#define PM8916_LBC_CHGR_TYPE 0x150221#define PM8916_LBC_BAT_IF_TYPE 0x160222#define PM8916_LBC_USB_TYPE 0x170223#define PM8916_LBC_MISC_TYPE 0x180224 25#define PM8916_LBC_CHGR_CHG_OPTION 0x0826#define PM8916_LBC_CHGR_PMIC_CHARGER BIT(7)27 28#define PM8916_LBC_CHGR_CHG_STATUS 0x0929 30#define PM8916_INT_RT_STS 0x1031 32#define PM8916_LBC_USB_USBIN_VALID BIT(1)33 34#define PM8916_LBC_CHGR_VDD_MAX 0x4035#define PM8916_LBC_CHGR_VDD_SAFE 0x4136#define PM8916_LBC_CHGR_IBAT_MAX 0x4437#define PM8916_LBC_CHGR_IBAT_SAFE 0x4538 39#define PM8916_LBC_CHGR_TCHG_MAX_EN 0x6040#define PM8916_LBC_CHGR_TCHG_MAX_ENABLED BIT(7)41#define PM8916_LBC_CHGR_TCHG_MAX 0x6142 43#define PM8916_LBC_CHGR_CHG_CTRL 0x4944#define PM8916_LBC_CHGR_CHG_EN BIT(7)45#define PM8916_LBC_CHGR_PSTG_EN BIT(5)46 47#define PM8916_LBC_CHGR_MIN_CURRENT 9000048#define PM8916_LBC_CHGR_MAX_CURRENT 144000049 50#define PM8916_LBC_CHGR_MIN_VOLTAGE 400000051#define PM8916_LBC_CHGR_MAX_VOLTAGE 477500052#define PM8916_LBC_CHGR_VOLTAGE_STEP 2500053 54#define PM8916_LBC_CHGR_MIN_TIME 455#define PM8916_LBC_CHGR_MAX_TIME 25656 57struct pm8916_lbc_charger {58 struct device *dev;59 struct extcon_dev *edev;60 struct power_supply *charger;61 struct power_supply_battery_info *info;62 struct regmap *regmap;63 unsigned int reg[4];64 bool online;65 unsigned int charge_voltage_max;66 unsigned int charge_voltage_safe;67 unsigned int charge_current_max;68 unsigned int charge_current_safe;69};70 71static const unsigned int pm8916_lbc_charger_cable[] = {72 EXTCON_USB,73 EXTCON_NONE,74};75 76enum {77 LBC_CHGR = 0,78 LBC_BAT_IF,79 LBC_USB,80 LBC_MISC,81};82 83static int pm8916_lbc_charger_configure(struct pm8916_lbc_charger *chg)84{85 int ret = 0;86 unsigned int tmp;87 88 chg->charge_voltage_max = clamp_t(u32, chg->charge_voltage_max,89 PM8916_LBC_CHGR_MIN_VOLTAGE, chg->charge_voltage_safe);90 91 tmp = chg->charge_voltage_max - PM8916_LBC_CHGR_MIN_VOLTAGE;92 tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP;93 chg->charge_voltage_max = PM8916_LBC_CHGR_MIN_VOLTAGE + tmp * PM8916_LBC_CHGR_VOLTAGE_STEP;94 95 ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX, tmp);96 if (ret)97 goto error;98 99 chg->charge_current_max = min(chg->charge_current_max, chg->charge_current_safe);100 101 tmp = clamp_t(u32, chg->charge_current_max,102 PM8916_LBC_CHGR_MIN_CURRENT, PM8916_LBC_CHGR_MAX_CURRENT);103 104 tmp = chg->charge_current_max / PM8916_LBC_CHGR_MIN_CURRENT - 1;105 chg->charge_current_max = (tmp + 1) * PM8916_LBC_CHGR_MIN_CURRENT;106 107 ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_MAX, tmp);108 if (ret)109 goto error;110 111 ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_CTRL,112 PM8916_LBC_CHGR_CHG_EN | PM8916_LBC_CHGR_PSTG_EN);113 if (ret)114 goto error;115 116 return ret;117 118error:119 dev_err(chg->dev, "Failed to configure charging: %pe\n", ERR_PTR(ret));120 return ret;121}122 123static int pm8916_lbc_charger_get_property(struct power_supply *psy,124 enum power_supply_property psp,125 union power_supply_propval *val)126{127 struct pm8916_lbc_charger *chg = power_supply_get_drvdata(psy);128 129 switch (psp) {130 case POWER_SUPPLY_PROP_ONLINE:131 val->intval = chg->online;132 return 0;133 134 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:135 val->intval = chg->charge_voltage_max;136 return 0;137 138 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:139 val->intval = chg->charge_current_max;140 return 0;141 142 default:143 return -EINVAL;144 };145}146 147static int pm8916_lbc_charger_set_property(struct power_supply *psy,148 enum power_supply_property prop,149 const union power_supply_propval *val)150{151 struct pm8916_lbc_charger *chg = power_supply_get_drvdata(psy);152 153 switch (prop) {154 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:155 chg->charge_current_max = val->intval;156 return pm8916_lbc_charger_configure(chg);157 default:158 return -EINVAL;159 }160}161 162static int pm8916_lbc_charger_property_is_writeable(struct power_supply *psy,163 enum power_supply_property psp)164{165 switch (psp) {166 case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:167 return true;168 default:169 return false;170 }171}172 173static enum power_supply_property pm8916_lbc_charger_properties[] = {174 POWER_SUPPLY_PROP_ONLINE,175 POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,176 POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,177};178 179static irqreturn_t pm8916_lbc_charger_state_changed_irq(int irq, void *data)180{181 struct pm8916_lbc_charger *chg = data;182 unsigned int tmp;183 int ret;184 185 ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp);186 if (ret)187 return IRQ_HANDLED;188 189 chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID);190 extcon_set_state_sync(chg->edev, EXTCON_USB, chg->online);191 192 power_supply_changed(chg->charger);193 194 return IRQ_HANDLED;195}196 197static int pm8916_lbc_charger_probe_dt(struct pm8916_lbc_charger *chg)198{199 struct device *dev = chg->dev;200 int ret = 0;201 unsigned int tmp;202 203 ret = device_property_read_u32(dev, "qcom,fast-charge-safe-voltage", &chg->charge_voltage_safe);204 if (ret)205 return ret;206 if (chg->charge_voltage_safe < PM8916_LBC_CHGR_MIN_VOLTAGE)207 return -EINVAL;208 209 chg->charge_voltage_safe = clamp_t(u32, chg->charge_voltage_safe,210 PM8916_LBC_CHGR_MIN_VOLTAGE, PM8916_LBC_CHGR_MAX_VOLTAGE);211 212 tmp = chg->charge_voltage_safe - PM8916_LBC_CHGR_MIN_VOLTAGE;213 tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP;214 ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_SAFE, tmp);215 if (ret)216 return ret;217 218 ret = device_property_read_u32(dev, "qcom,fast-charge-safe-current", &chg->charge_current_safe);219 if (ret)220 return ret;221 if (chg->charge_current_safe < PM8916_LBC_CHGR_MIN_CURRENT)222 return -EINVAL;223 224 chg->charge_current_safe = clamp_t(u32, chg->charge_current_safe,225 PM8916_LBC_CHGR_MIN_CURRENT, PM8916_LBC_CHGR_MAX_CURRENT);226 227 chg->charge_current_max = chg->charge_current_safe;228 229 tmp = chg->charge_current_safe / PM8916_LBC_CHGR_MIN_CURRENT - 1;230 ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_SAFE, tmp);231 if (ret)232 return ret;233 234 /* Disable charger timeout. */235 ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 0x00);236 if (ret)237 return ret;238 239 return ret;240}241 242static const struct power_supply_desc pm8916_lbc_charger_psy_desc = {243 .name = "pm8916-lbc-chgr",244 .type = POWER_SUPPLY_TYPE_USB,245 .properties = pm8916_lbc_charger_properties,246 .num_properties = ARRAY_SIZE(pm8916_lbc_charger_properties),247 .get_property = pm8916_lbc_charger_get_property,248 .set_property = pm8916_lbc_charger_set_property,249 .property_is_writeable = pm8916_lbc_charger_property_is_writeable,250};251 252static int pm8916_lbc_charger_probe(struct platform_device *pdev)253{254 struct device *dev = &pdev->dev;255 struct pm8916_lbc_charger *chg;256 struct power_supply_config psy_cfg = {};257 int ret, len, irq;258 unsigned int tmp;259 260 chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);261 if (!chg)262 return -ENOMEM;263 264 chg->dev = dev;265 266 chg->regmap = dev_get_regmap(pdev->dev.parent, NULL);267 if (!chg->regmap)268 return -ENODEV;269 270 len = device_property_count_u32(dev, "reg");271 if (len < 0)272 return len;273 if (len != 4)274 return dev_err_probe(dev, -EINVAL,275 "Wrong amount of reg values: %d (4 expected)\n", len);276 277 irq = platform_get_irq_byname(pdev, "usb_vbus");278 if (irq < 0)279 return irq;280 281 ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq,282 IRQF_ONESHOT, "pm8916_lbc", chg);283 if (ret)284 return ret;285 286 ret = device_property_read_u32_array(dev, "reg", chg->reg, len);287 if (ret)288 return ret;289 290 ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_PERPH_TYPE, &tmp, 2);291 if (ret)292 goto comm_error;293 if (tmp != PM8916_LBC_CHGR_TYPE)294 goto type_error;295 296 ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_BAT_IF] + PM8916_PERPH_TYPE, &tmp, 2);297 if (ret)298 goto comm_error;299 if (tmp != PM8916_LBC_BAT_IF_TYPE)300 goto type_error;301 302 ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_USB] + PM8916_PERPH_TYPE, &tmp, 2);303 if (ret)304 goto comm_error;305 if (tmp != PM8916_LBC_USB_TYPE)306 goto type_error;307 308 ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_MISC] + PM8916_PERPH_TYPE, &tmp, 2);309 if (ret)310 goto comm_error;311 if (tmp != PM8916_LBC_MISC_TYPE)312 goto type_error;313 314 ret = regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_OPTION, &tmp);315 if (ret)316 goto comm_error;317 if (tmp != PM8916_LBC_CHGR_PMIC_CHARGER)318 dev_err_probe(dev, -ENODEV, "The system is using an external charger\n");319 320 ret = pm8916_lbc_charger_probe_dt(chg);321 if (ret)322 dev_err_probe(dev, ret, "Error while parsing device tree\n");323 324 psy_cfg.drv_data = chg;325 psy_cfg.of_node = dev->of_node;326 327 chg->charger = devm_power_supply_register(dev, &pm8916_lbc_charger_psy_desc, &psy_cfg);328 if (IS_ERR(chg->charger))329 return dev_err_probe(dev, PTR_ERR(chg->charger), "Unable to register charger\n");330 331 ret = power_supply_get_battery_info(chg->charger, &chg->info);332 if (ret)333 return dev_err_probe(dev, ret, "Unable to get battery info\n");334 335 chg->edev = devm_extcon_dev_allocate(dev, pm8916_lbc_charger_cable);336 if (IS_ERR(chg->edev))337 return PTR_ERR(chg->edev);338 339 ret = devm_extcon_dev_register(dev, chg->edev);340 if (ret < 0)341 return dev_err_probe(dev, ret, "failed to register extcon device\n");342 343 ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp);344 if (ret)345 goto comm_error;346 347 chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID);348 extcon_set_state_sync(chg->edev, EXTCON_USB, chg->online);349 350 chg->charge_voltage_max = chg->info->voltage_max_design_uv;351 ret = pm8916_lbc_charger_configure(chg);352 if (ret)353 return ret;354 355 return 0;356 357comm_error:358 return dev_err_probe(dev, ret, "Unable to communicate with device\n");359 360type_error:361 return dev_err_probe(dev, -ENODEV, "Device reported wrong type: 0x%X\n", tmp);362}363 364static const struct of_device_id pm8916_lbc_charger_of_match[] = {365 { .compatible = "qcom,pm8916-lbc", },366 {}367};368MODULE_DEVICE_TABLE(of, pm8916_lbc_charger_of_match);369 370static struct platform_driver pm8916_lbc_charger_driver = {371 .driver = {372 .name = "pm8916-lbc",373 .of_match_table = pm8916_lbc_charger_of_match,374 },375 .probe = pm8916_lbc_charger_probe,376};377module_platform_driver(pm8916_lbc_charger_driver);378 379MODULE_DESCRIPTION("pm8916 LBC driver");380MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");381MODULE_LICENSE("GPL");382