brintos

brintos / linux-shallow public Read only

0
0
Text · 10.4 KiB · 6d92e98 Raw
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