191937b14SEnric Balletbo i Serra // SPDX-License-Identifier: GPL-2.0
291937b14SEnric Balletbo i Serra // Battery charger driver for TI's tps65217
391937b14SEnric Balletbo i Serra //
491937b14SEnric Balletbo i Serra // Copyright (C) 2015 Collabora Ltd.
591937b14SEnric Balletbo i Serra // Author: Enric Balletbo i Serra <enric.balletbo@collabora.com>
68c0984e5SSebastian Reichel 
78c0984e5SSebastian Reichel /*
88c0984e5SSebastian Reichel  * Battery charger driver for TI's tps65217
98c0984e5SSebastian Reichel  */
108c0984e5SSebastian Reichel #include <linux/kernel.h>
118c0984e5SSebastian Reichel #include <linux/kthread.h>
128c0984e5SSebastian Reichel #include <linux/device.h>
138c0984e5SSebastian Reichel #include <linux/module.h>
148c0984e5SSebastian Reichel #include <linux/platform_device.h>
158c0984e5SSebastian Reichel #include <linux/init.h>
168c0984e5SSebastian Reichel #include <linux/interrupt.h>
178c0984e5SSebastian Reichel #include <linux/slab.h>
188c0984e5SSebastian Reichel #include <linux/err.h>
198c0984e5SSebastian Reichel #include <linux/of.h>
208c0984e5SSebastian Reichel #include <linux/power_supply.h>
218c0984e5SSebastian Reichel 
228c0984e5SSebastian Reichel #include <linux/mfd/core.h>
238c0984e5SSebastian Reichel #include <linux/mfd/tps65217.h>
248c0984e5SSebastian Reichel 
2520a7e173SMilo Kim #define CHARGER_STATUS_PRESENT	(TPS65217_STATUS_ACPWR | TPS65217_STATUS_USBPWR)
2620a7e173SMilo Kim #define NUM_CHARGER_IRQS	2
278c0984e5SSebastian Reichel #define POLL_INTERVAL		(HZ * 2)
288c0984e5SSebastian Reichel 
298c0984e5SSebastian Reichel struct tps65217_charger {
308c0984e5SSebastian Reichel 	struct tps65217 *tps;
318c0984e5SSebastian Reichel 	struct device *dev;
323967d1f9SMilo Kim 	struct power_supply *psy;
338c0984e5SSebastian Reichel 
343c2e58a6SMilo Kim 	int	online;
353c2e58a6SMilo Kim 	int	prev_online;
368c0984e5SSebastian Reichel 
378c0984e5SSebastian Reichel 	struct task_struct	*poll_task;
388c0984e5SSebastian Reichel };
398c0984e5SSebastian Reichel 
40da50b3a5SMilo Kim static enum power_supply_property tps65217_charger_props[] = {
418c0984e5SSebastian Reichel 	POWER_SUPPLY_PROP_ONLINE,
428c0984e5SSebastian Reichel };
438c0984e5SSebastian Reichel 
tps65217_config_charger(struct tps65217_charger * charger)448c0984e5SSebastian Reichel static int tps65217_config_charger(struct tps65217_charger *charger)
458c0984e5SSebastian Reichel {
468c0984e5SSebastian Reichel 	int ret;
478c0984e5SSebastian Reichel 
488c0984e5SSebastian Reichel 	/*
498c0984e5SSebastian Reichel 	 * tps65217 rev. G, p. 31 (see p. 32 for NTC schematic)
508c0984e5SSebastian Reichel 	 *
518c0984e5SSebastian Reichel 	 * The device can be configured to support a 100k NTC (B = 3960) by
52*3eb7508dSShaomin Deng 	 * setting the NTC_TYPE bit in register CHGCONFIG1 to 1. However it
538c0984e5SSebastian Reichel 	 * is not recommended to do so. In sleep mode, the charger continues
548c0984e5SSebastian Reichel 	 * charging the battery, but all register values are reset to default
558c0984e5SSebastian Reichel 	 * values. Therefore, the charger would get the wrong temperature
568c0984e5SSebastian Reichel 	 * information. If 100k NTC setting is required, please contact the
578c0984e5SSebastian Reichel 	 * factory.
588c0984e5SSebastian Reichel 	 *
598c0984e5SSebastian Reichel 	 * ATTENTION, conflicting information, from p. 46
608c0984e5SSebastian Reichel 	 *
618c0984e5SSebastian Reichel 	 * NTC TYPE (for battery temperature measurement)
628c0984e5SSebastian Reichel 	 *   0 – 100k (curve 1, B = 3960)
638c0984e5SSebastian Reichel 	 *   1 – 10k  (curve 2, B = 3480) (default on reset)
648c0984e5SSebastian Reichel 	 *
658c0984e5SSebastian Reichel 	 */
668c0984e5SSebastian Reichel 	ret = tps65217_clear_bits(charger->tps, TPS65217_REG_CHGCONFIG1,
678c0984e5SSebastian Reichel 				  TPS65217_CHGCONFIG1_NTC_TYPE,
688c0984e5SSebastian Reichel 				  TPS65217_PROTECT_NONE);
698c0984e5SSebastian Reichel 	if (ret) {
708c0984e5SSebastian Reichel 		dev_err(charger->dev,
718c0984e5SSebastian Reichel 			"failed to set 100k NTC setting: %d\n", ret);
728c0984e5SSebastian Reichel 		return ret;
738c0984e5SSebastian Reichel 	}
748c0984e5SSebastian Reichel 
758c0984e5SSebastian Reichel 	return 0;
768c0984e5SSebastian Reichel }
778c0984e5SSebastian Reichel 
tps65217_enable_charging(struct tps65217_charger * charger)788c0984e5SSebastian Reichel static int tps65217_enable_charging(struct tps65217_charger *charger)
798c0984e5SSebastian Reichel {
808c0984e5SSebastian Reichel 	int ret;
818c0984e5SSebastian Reichel 
828c0984e5SSebastian Reichel 	/* charger already enabled */
833c2e58a6SMilo Kim 	if (charger->online)
848c0984e5SSebastian Reichel 		return 0;
858c0984e5SSebastian Reichel 
868c0984e5SSebastian Reichel 	dev_dbg(charger->dev, "%s: enable charging\n", __func__);
878c0984e5SSebastian Reichel 	ret = tps65217_set_bits(charger->tps, TPS65217_REG_CHGCONFIG1,
888c0984e5SSebastian Reichel 				TPS65217_CHGCONFIG1_CHG_EN,
898c0984e5SSebastian Reichel 				TPS65217_CHGCONFIG1_CHG_EN,
908c0984e5SSebastian Reichel 				TPS65217_PROTECT_NONE);
918c0984e5SSebastian Reichel 	if (ret) {
928c0984e5SSebastian Reichel 		dev_err(charger->dev,
938c0984e5SSebastian Reichel 			"%s: Error in writing CHG_EN in reg 0x%x: %d\n",
948c0984e5SSebastian Reichel 			__func__, TPS65217_REG_CHGCONFIG1, ret);
958c0984e5SSebastian Reichel 		return ret;
968c0984e5SSebastian Reichel 	}
978c0984e5SSebastian Reichel 
983c2e58a6SMilo Kim 	charger->online = 1;
998c0984e5SSebastian Reichel 
1008c0984e5SSebastian Reichel 	return 0;
1018c0984e5SSebastian Reichel }
1028c0984e5SSebastian Reichel 
tps65217_charger_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)103757620c4SMilo Kim static int tps65217_charger_get_property(struct power_supply *psy,
1048c0984e5SSebastian Reichel 					 enum power_supply_property psp,
1058c0984e5SSebastian Reichel 					 union power_supply_propval *val)
1068c0984e5SSebastian Reichel {
1078c0984e5SSebastian Reichel 	struct tps65217_charger *charger = power_supply_get_drvdata(psy);
1088c0984e5SSebastian Reichel 
1098c0984e5SSebastian Reichel 	if (psp == POWER_SUPPLY_PROP_ONLINE) {
1103c2e58a6SMilo Kim 		val->intval = charger->online;
1118c0984e5SSebastian Reichel 		return 0;
1128c0984e5SSebastian Reichel 	}
1138c0984e5SSebastian Reichel 	return -EINVAL;
1148c0984e5SSebastian Reichel }
1158c0984e5SSebastian Reichel 
tps65217_charger_irq(int irq,void * dev)1168c0984e5SSebastian Reichel static irqreturn_t tps65217_charger_irq(int irq, void *dev)
1178c0984e5SSebastian Reichel {
1188c0984e5SSebastian Reichel 	int ret, val;
1198c0984e5SSebastian Reichel 	struct tps65217_charger *charger = dev;
1208c0984e5SSebastian Reichel 
1213c2e58a6SMilo Kim 	charger->prev_online = charger->online;
1228c0984e5SSebastian Reichel 
1238c0984e5SSebastian Reichel 	ret = tps65217_reg_read(charger->tps, TPS65217_REG_STATUS, &val);
1248c0984e5SSebastian Reichel 	if (ret < 0) {
1258c0984e5SSebastian Reichel 		dev_err(charger->dev, "%s: Error in reading reg 0x%x\n",
1268c0984e5SSebastian Reichel 			__func__, TPS65217_REG_STATUS);
1278c0984e5SSebastian Reichel 		return IRQ_HANDLED;
1288c0984e5SSebastian Reichel 	}
1298c0984e5SSebastian Reichel 
1308c0984e5SSebastian Reichel 	dev_dbg(charger->dev, "%s: 0x%x\n", __func__, val);
1318c0984e5SSebastian Reichel 
13220a7e173SMilo Kim 	/* check for charger status bit */
13320a7e173SMilo Kim 	if (val & CHARGER_STATUS_PRESENT) {
1348c0984e5SSebastian Reichel 		ret = tps65217_enable_charging(charger);
1358c0984e5SSebastian Reichel 		if (ret) {
1368c0984e5SSebastian Reichel 			dev_err(charger->dev,
1378c0984e5SSebastian Reichel 				"failed to enable charger: %d\n", ret);
1388c0984e5SSebastian Reichel 			return IRQ_HANDLED;
1398c0984e5SSebastian Reichel 		}
1408c0984e5SSebastian Reichel 	} else {
1413c2e58a6SMilo Kim 		charger->online = 0;
1428c0984e5SSebastian Reichel 	}
1438c0984e5SSebastian Reichel 
1443c2e58a6SMilo Kim 	if (charger->prev_online != charger->online)
1453967d1f9SMilo Kim 		power_supply_changed(charger->psy);
1468c0984e5SSebastian Reichel 
1478c0984e5SSebastian Reichel 	ret = tps65217_reg_read(charger->tps, TPS65217_REG_CHGCONFIG0, &val);
1488c0984e5SSebastian Reichel 	if (ret < 0) {
1498c0984e5SSebastian Reichel 		dev_err(charger->dev, "%s: Error in reading reg 0x%x\n",
1508c0984e5SSebastian Reichel 			__func__, TPS65217_REG_CHGCONFIG0);
1518c0984e5SSebastian Reichel 		return IRQ_HANDLED;
1528c0984e5SSebastian Reichel 	}
1538c0984e5SSebastian Reichel 
1548c0984e5SSebastian Reichel 	if (val & TPS65217_CHGCONFIG0_ACTIVE)
1558c0984e5SSebastian Reichel 		dev_dbg(charger->dev, "%s: charger is charging\n", __func__);
1568c0984e5SSebastian Reichel 	else
1578c0984e5SSebastian Reichel 		dev_dbg(charger->dev,
1588c0984e5SSebastian Reichel 			"%s: charger is NOT charging\n", __func__);
1598c0984e5SSebastian Reichel 
1608c0984e5SSebastian Reichel 	return IRQ_HANDLED;
1618c0984e5SSebastian Reichel }
1628c0984e5SSebastian Reichel 
tps65217_charger_poll_task(void * data)1638c0984e5SSebastian Reichel static int tps65217_charger_poll_task(void *data)
1648c0984e5SSebastian Reichel {
1658c0984e5SSebastian Reichel 	set_freezable();
1668c0984e5SSebastian Reichel 
1678c0984e5SSebastian Reichel 	while (!kthread_should_stop()) {
1688c0984e5SSebastian Reichel 		schedule_timeout_interruptible(POLL_INTERVAL);
1698c0984e5SSebastian Reichel 		try_to_freeze();
1708c0984e5SSebastian Reichel 		tps65217_charger_irq(-1, data);
1718c0984e5SSebastian Reichel 	}
1728c0984e5SSebastian Reichel 	return 0;
1738c0984e5SSebastian Reichel }
1748c0984e5SSebastian Reichel 
1758c0984e5SSebastian Reichel static const struct power_supply_desc tps65217_charger_desc = {
1765b903a15SMilo Kim 	.name			= "tps65217-charger",
1778c0984e5SSebastian Reichel 	.type			= POWER_SUPPLY_TYPE_MAINS,
178757620c4SMilo Kim 	.get_property		= tps65217_charger_get_property,
179da50b3a5SMilo Kim 	.properties		= tps65217_charger_props,
180da50b3a5SMilo Kim 	.num_properties		= ARRAY_SIZE(tps65217_charger_props),
1818c0984e5SSebastian Reichel };
1828c0984e5SSebastian Reichel 
tps65217_charger_probe(struct platform_device * pdev)1838c0984e5SSebastian Reichel static int tps65217_charger_probe(struct platform_device *pdev)
1848c0984e5SSebastian Reichel {
1858c0984e5SSebastian Reichel 	struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
1868c0984e5SSebastian Reichel 	struct tps65217_charger *charger;
1878c0984e5SSebastian Reichel 	struct power_supply_config cfg = {};
1889ef0bf11SMilo Kim 	struct task_struct *poll_task;
18920a7e173SMilo Kim 	int irq[NUM_CHARGER_IRQS];
1908c0984e5SSebastian Reichel 	int ret;
19120a7e173SMilo Kim 	int i;
1928c0984e5SSebastian Reichel 
1938c0984e5SSebastian Reichel 	charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
1948c0984e5SSebastian Reichel 	if (!charger)
1958c0984e5SSebastian Reichel 		return -ENOMEM;
1968c0984e5SSebastian Reichel 
197f7c8f1deSSebastian Reichel 	platform_set_drvdata(pdev, charger);
1988c0984e5SSebastian Reichel 	charger->tps = tps;
1998c0984e5SSebastian Reichel 	charger->dev = &pdev->dev;
2008c0984e5SSebastian Reichel 
2018c0984e5SSebastian Reichel 	cfg.of_node = pdev->dev.of_node;
2028c0984e5SSebastian Reichel 	cfg.drv_data = charger;
2038c0984e5SSebastian Reichel 
2043967d1f9SMilo Kim 	charger->psy = devm_power_supply_register(&pdev->dev,
2058c0984e5SSebastian Reichel 						  &tps65217_charger_desc,
2068c0984e5SSebastian Reichel 						  &cfg);
2073967d1f9SMilo Kim 	if (IS_ERR(charger->psy)) {
2088c0984e5SSebastian Reichel 		dev_err(&pdev->dev, "failed: power supply register\n");
2093967d1f9SMilo Kim 		return PTR_ERR(charger->psy);
2108c0984e5SSebastian Reichel 	}
2118c0984e5SSebastian Reichel 
21220a7e173SMilo Kim 	irq[0] = platform_get_irq_byname(pdev, "USB");
21320a7e173SMilo Kim 	irq[1] = platform_get_irq_byname(pdev, "AC");
21447d7d5edSMarcin Niestroj 
2158c0984e5SSebastian Reichel 	ret = tps65217_config_charger(charger);
2168c0984e5SSebastian Reichel 	if (ret < 0) {
2178c0984e5SSebastian Reichel 		dev_err(charger->dev, "charger config failed, err %d\n", ret);
2188c0984e5SSebastian Reichel 		return ret;
2198c0984e5SSebastian Reichel 	}
2208c0984e5SSebastian Reichel 
22120a7e173SMilo Kim 	/* Create a polling thread if an interrupt is invalid */
22220a7e173SMilo Kim 	if (irq[0] < 0 || irq[1] < 0) {
2239ef0bf11SMilo Kim 		poll_task = kthread_run(tps65217_charger_poll_task,
2248c0984e5SSebastian Reichel 					charger, "ktps65217charger");
2259ef0bf11SMilo Kim 		if (IS_ERR(poll_task)) {
2269ef0bf11SMilo Kim 			ret = PTR_ERR(poll_task);
22747d7d5edSMarcin Niestroj 			dev_err(charger->dev,
22847d7d5edSMarcin Niestroj 				"Unable to run kthread err %d\n", ret);
2298c0984e5SSebastian Reichel 			return ret;
2308c0984e5SSebastian Reichel 		}
2319ef0bf11SMilo Kim 
2329ef0bf11SMilo Kim 		charger->poll_task = poll_task;
23320a7e173SMilo Kim 		return 0;
23420a7e173SMilo Kim 	}
23520a7e173SMilo Kim 
23620a7e173SMilo Kim 	/* Create IRQ threads for charger interrupts */
23720a7e173SMilo Kim 	for (i = 0; i < NUM_CHARGER_IRQS; i++) {
23820a7e173SMilo Kim 		ret = devm_request_threaded_irq(&pdev->dev, irq[i], NULL,
23920a7e173SMilo Kim 						tps65217_charger_irq,
2402469b836Sdongjian 						IRQF_ONESHOT, "tps65217-charger",
24120a7e173SMilo Kim 						charger);
24220a7e173SMilo Kim 		if (ret) {
24320a7e173SMilo Kim 			dev_err(charger->dev,
24420a7e173SMilo Kim 				"Unable to register irq %d err %d\n", irq[i],
24520a7e173SMilo Kim 				ret);
24620a7e173SMilo Kim 			return ret;
24720a7e173SMilo Kim 		}
24820a7e173SMilo Kim 
24920a7e173SMilo Kim 		/* Check current state */
25020a7e173SMilo Kim 		tps65217_charger_irq(-1, charger);
25147d7d5edSMarcin Niestroj 	}
2528c0984e5SSebastian Reichel 
2538c0984e5SSebastian Reichel 	return 0;
2548c0984e5SSebastian Reichel }
2558c0984e5SSebastian Reichel 
tps65217_charger_remove(struct platform_device * pdev)2568c0984e5SSebastian Reichel static int tps65217_charger_remove(struct platform_device *pdev)
2578c0984e5SSebastian Reichel {
2588c0984e5SSebastian Reichel 	struct tps65217_charger *charger = platform_get_drvdata(pdev);
2598c0984e5SSebastian Reichel 
2609ef0bf11SMilo Kim 	if (charger->poll_task)
2618c0984e5SSebastian Reichel 		kthread_stop(charger->poll_task);
2628c0984e5SSebastian Reichel 
2638c0984e5SSebastian Reichel 	return 0;
2648c0984e5SSebastian Reichel }
2658c0984e5SSebastian Reichel 
2668c0984e5SSebastian Reichel static const struct of_device_id tps65217_charger_match_table[] = {
2678c0984e5SSebastian Reichel 	{ .compatible = "ti,tps65217-charger", },
2688c0984e5SSebastian Reichel 	{ /* sentinel */ }
2698c0984e5SSebastian Reichel };
2708c0984e5SSebastian Reichel MODULE_DEVICE_TABLE(of, tps65217_charger_match_table);
2718c0984e5SSebastian Reichel 
2728c0984e5SSebastian Reichel static struct platform_driver tps65217_charger_driver = {
2738c0984e5SSebastian Reichel 	.probe	= tps65217_charger_probe,
2748c0984e5SSebastian Reichel 	.remove = tps65217_charger_remove,
2758c0984e5SSebastian Reichel 	.driver	= {
2768c0984e5SSebastian Reichel 		.name	= "tps65217-charger",
2778c0984e5SSebastian Reichel 		.of_match_table = of_match_ptr(tps65217_charger_match_table),
2788c0984e5SSebastian Reichel 	},
2798c0984e5SSebastian Reichel 
2808c0984e5SSebastian Reichel };
2818c0984e5SSebastian Reichel module_platform_driver(tps65217_charger_driver);
2828c0984e5SSebastian Reichel 
2838c0984e5SSebastian Reichel MODULE_LICENSE("GPL v2");
2848c0984e5SSebastian Reichel MODULE_AUTHOR("Enric Balletbo Serra <enric.balletbo@collabora.com>");
2858c0984e5SSebastian Reichel MODULE_DESCRIPTION("TPS65217 battery charger driver");
286