162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci// Battery charger driver for TI's tps65217
362306a36Sopenharmony_ci//
462306a36Sopenharmony_ci// Copyright (C) 2015 Collabora Ltd.
562306a36Sopenharmony_ci// Author: Enric Balletbo i Serra <enric.balletbo@collabora.com>
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci/*
862306a36Sopenharmony_ci * Battery charger driver for TI's tps65217
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/kthread.h>
1262306a36Sopenharmony_ci#include <linux/device.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <linux/interrupt.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci#include <linux/err.h>
1962306a36Sopenharmony_ci#include <linux/of.h>
2062306a36Sopenharmony_ci#include <linux/power_supply.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <linux/mfd/core.h>
2362306a36Sopenharmony_ci#include <linux/mfd/tps65217.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define CHARGER_STATUS_PRESENT	(TPS65217_STATUS_ACPWR | TPS65217_STATUS_USBPWR)
2662306a36Sopenharmony_ci#define NUM_CHARGER_IRQS	2
2762306a36Sopenharmony_ci#define POLL_INTERVAL		(HZ * 2)
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistruct tps65217_charger {
3062306a36Sopenharmony_ci	struct tps65217 *tps;
3162306a36Sopenharmony_ci	struct device *dev;
3262306a36Sopenharmony_ci	struct power_supply *psy;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	int	online;
3562306a36Sopenharmony_ci	int	prev_online;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	struct task_struct	*poll_task;
3862306a36Sopenharmony_ci};
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic enum power_supply_property tps65217_charger_props[] = {
4162306a36Sopenharmony_ci	POWER_SUPPLY_PROP_ONLINE,
4262306a36Sopenharmony_ci};
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic int tps65217_config_charger(struct tps65217_charger *charger)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	int ret;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	/*
4962306a36Sopenharmony_ci	 * tps65217 rev. G, p. 31 (see p. 32 for NTC schematic)
5062306a36Sopenharmony_ci	 *
5162306a36Sopenharmony_ci	 * The device can be configured to support a 100k NTC (B = 3960) by
5262306a36Sopenharmony_ci	 * setting the NTC_TYPE bit in register CHGCONFIG1 to 1. However it
5362306a36Sopenharmony_ci	 * is not recommended to do so. In sleep mode, the charger continues
5462306a36Sopenharmony_ci	 * charging the battery, but all register values are reset to default
5562306a36Sopenharmony_ci	 * values. Therefore, the charger would get the wrong temperature
5662306a36Sopenharmony_ci	 * information. If 100k NTC setting is required, please contact the
5762306a36Sopenharmony_ci	 * factory.
5862306a36Sopenharmony_ci	 *
5962306a36Sopenharmony_ci	 * ATTENTION, conflicting information, from p. 46
6062306a36Sopenharmony_ci	 *
6162306a36Sopenharmony_ci	 * NTC TYPE (for battery temperature measurement)
6262306a36Sopenharmony_ci	 *   0 – 100k (curve 1, B = 3960)
6362306a36Sopenharmony_ci	 *   1 – 10k  (curve 2, B = 3480) (default on reset)
6462306a36Sopenharmony_ci	 *
6562306a36Sopenharmony_ci	 */
6662306a36Sopenharmony_ci	ret = tps65217_clear_bits(charger->tps, TPS65217_REG_CHGCONFIG1,
6762306a36Sopenharmony_ci				  TPS65217_CHGCONFIG1_NTC_TYPE,
6862306a36Sopenharmony_ci				  TPS65217_PROTECT_NONE);
6962306a36Sopenharmony_ci	if (ret) {
7062306a36Sopenharmony_ci		dev_err(charger->dev,
7162306a36Sopenharmony_ci			"failed to set 100k NTC setting: %d\n", ret);
7262306a36Sopenharmony_ci		return ret;
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	return 0;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic int tps65217_enable_charging(struct tps65217_charger *charger)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	int ret;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	/* charger already enabled */
8362306a36Sopenharmony_ci	if (charger->online)
8462306a36Sopenharmony_ci		return 0;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	dev_dbg(charger->dev, "%s: enable charging\n", __func__);
8762306a36Sopenharmony_ci	ret = tps65217_set_bits(charger->tps, TPS65217_REG_CHGCONFIG1,
8862306a36Sopenharmony_ci				TPS65217_CHGCONFIG1_CHG_EN,
8962306a36Sopenharmony_ci				TPS65217_CHGCONFIG1_CHG_EN,
9062306a36Sopenharmony_ci				TPS65217_PROTECT_NONE);
9162306a36Sopenharmony_ci	if (ret) {
9262306a36Sopenharmony_ci		dev_err(charger->dev,
9362306a36Sopenharmony_ci			"%s: Error in writing CHG_EN in reg 0x%x: %d\n",
9462306a36Sopenharmony_ci			__func__, TPS65217_REG_CHGCONFIG1, ret);
9562306a36Sopenharmony_ci		return ret;
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	charger->online = 1;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	return 0;
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic int tps65217_charger_get_property(struct power_supply *psy,
10462306a36Sopenharmony_ci					 enum power_supply_property psp,
10562306a36Sopenharmony_ci					 union power_supply_propval *val)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct tps65217_charger *charger = power_supply_get_drvdata(psy);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (psp == POWER_SUPPLY_PROP_ONLINE) {
11062306a36Sopenharmony_ci		val->intval = charger->online;
11162306a36Sopenharmony_ci		return 0;
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci	return -EINVAL;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic irqreturn_t tps65217_charger_irq(int irq, void *dev)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	int ret, val;
11962306a36Sopenharmony_ci	struct tps65217_charger *charger = dev;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	charger->prev_online = charger->online;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	ret = tps65217_reg_read(charger->tps, TPS65217_REG_STATUS, &val);
12462306a36Sopenharmony_ci	if (ret < 0) {
12562306a36Sopenharmony_ci		dev_err(charger->dev, "%s: Error in reading reg 0x%x\n",
12662306a36Sopenharmony_ci			__func__, TPS65217_REG_STATUS);
12762306a36Sopenharmony_ci		return IRQ_HANDLED;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	dev_dbg(charger->dev, "%s: 0x%x\n", __func__, val);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/* check for charger status bit */
13362306a36Sopenharmony_ci	if (val & CHARGER_STATUS_PRESENT) {
13462306a36Sopenharmony_ci		ret = tps65217_enable_charging(charger);
13562306a36Sopenharmony_ci		if (ret) {
13662306a36Sopenharmony_ci			dev_err(charger->dev,
13762306a36Sopenharmony_ci				"failed to enable charger: %d\n", ret);
13862306a36Sopenharmony_ci			return IRQ_HANDLED;
13962306a36Sopenharmony_ci		}
14062306a36Sopenharmony_ci	} else {
14162306a36Sopenharmony_ci		charger->online = 0;
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	if (charger->prev_online != charger->online)
14562306a36Sopenharmony_ci		power_supply_changed(charger->psy);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	ret = tps65217_reg_read(charger->tps, TPS65217_REG_CHGCONFIG0, &val);
14862306a36Sopenharmony_ci	if (ret < 0) {
14962306a36Sopenharmony_ci		dev_err(charger->dev, "%s: Error in reading reg 0x%x\n",
15062306a36Sopenharmony_ci			__func__, TPS65217_REG_CHGCONFIG0);
15162306a36Sopenharmony_ci		return IRQ_HANDLED;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (val & TPS65217_CHGCONFIG0_ACTIVE)
15562306a36Sopenharmony_ci		dev_dbg(charger->dev, "%s: charger is charging\n", __func__);
15662306a36Sopenharmony_ci	else
15762306a36Sopenharmony_ci		dev_dbg(charger->dev,
15862306a36Sopenharmony_ci			"%s: charger is NOT charging\n", __func__);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	return IRQ_HANDLED;
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistatic int tps65217_charger_poll_task(void *data)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	set_freezable();
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	while (!kthread_should_stop()) {
16862306a36Sopenharmony_ci		schedule_timeout_interruptible(POLL_INTERVAL);
16962306a36Sopenharmony_ci		try_to_freeze();
17062306a36Sopenharmony_ci		tps65217_charger_irq(-1, data);
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci	return 0;
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic const struct power_supply_desc tps65217_charger_desc = {
17662306a36Sopenharmony_ci	.name			= "tps65217-charger",
17762306a36Sopenharmony_ci	.type			= POWER_SUPPLY_TYPE_MAINS,
17862306a36Sopenharmony_ci	.get_property		= tps65217_charger_get_property,
17962306a36Sopenharmony_ci	.properties		= tps65217_charger_props,
18062306a36Sopenharmony_ci	.num_properties		= ARRAY_SIZE(tps65217_charger_props),
18162306a36Sopenharmony_ci};
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic int tps65217_charger_probe(struct platform_device *pdev)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
18662306a36Sopenharmony_ci	struct tps65217_charger *charger;
18762306a36Sopenharmony_ci	struct power_supply_config cfg = {};
18862306a36Sopenharmony_ci	struct task_struct *poll_task;
18962306a36Sopenharmony_ci	int irq[NUM_CHARGER_IRQS];
19062306a36Sopenharmony_ci	int ret;
19162306a36Sopenharmony_ci	int i;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
19462306a36Sopenharmony_ci	if (!charger)
19562306a36Sopenharmony_ci		return -ENOMEM;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	platform_set_drvdata(pdev, charger);
19862306a36Sopenharmony_ci	charger->tps = tps;
19962306a36Sopenharmony_ci	charger->dev = &pdev->dev;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	cfg.of_node = pdev->dev.of_node;
20262306a36Sopenharmony_ci	cfg.drv_data = charger;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	charger->psy = devm_power_supply_register(&pdev->dev,
20562306a36Sopenharmony_ci						  &tps65217_charger_desc,
20662306a36Sopenharmony_ci						  &cfg);
20762306a36Sopenharmony_ci	if (IS_ERR(charger->psy)) {
20862306a36Sopenharmony_ci		dev_err(&pdev->dev, "failed: power supply register\n");
20962306a36Sopenharmony_ci		return PTR_ERR(charger->psy);
21062306a36Sopenharmony_ci	}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	irq[0] = platform_get_irq_byname(pdev, "USB");
21362306a36Sopenharmony_ci	irq[1] = platform_get_irq_byname(pdev, "AC");
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	ret = tps65217_config_charger(charger);
21662306a36Sopenharmony_ci	if (ret < 0) {
21762306a36Sopenharmony_ci		dev_err(charger->dev, "charger config failed, err %d\n", ret);
21862306a36Sopenharmony_ci		return ret;
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	/* Create a polling thread if an interrupt is invalid */
22262306a36Sopenharmony_ci	if (irq[0] < 0 || irq[1] < 0) {
22362306a36Sopenharmony_ci		poll_task = kthread_run(tps65217_charger_poll_task,
22462306a36Sopenharmony_ci					charger, "ktps65217charger");
22562306a36Sopenharmony_ci		if (IS_ERR(poll_task)) {
22662306a36Sopenharmony_ci			ret = PTR_ERR(poll_task);
22762306a36Sopenharmony_ci			dev_err(charger->dev,
22862306a36Sopenharmony_ci				"Unable to run kthread err %d\n", ret);
22962306a36Sopenharmony_ci			return ret;
23062306a36Sopenharmony_ci		}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci		charger->poll_task = poll_task;
23362306a36Sopenharmony_ci		return 0;
23462306a36Sopenharmony_ci	}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	/* Create IRQ threads for charger interrupts */
23762306a36Sopenharmony_ci	for (i = 0; i < NUM_CHARGER_IRQS; i++) {
23862306a36Sopenharmony_ci		ret = devm_request_threaded_irq(&pdev->dev, irq[i], NULL,
23962306a36Sopenharmony_ci						tps65217_charger_irq,
24062306a36Sopenharmony_ci						IRQF_ONESHOT, "tps65217-charger",
24162306a36Sopenharmony_ci						charger);
24262306a36Sopenharmony_ci		if (ret) {
24362306a36Sopenharmony_ci			dev_err(charger->dev,
24462306a36Sopenharmony_ci				"Unable to register irq %d err %d\n", irq[i],
24562306a36Sopenharmony_ci				ret);
24662306a36Sopenharmony_ci			return ret;
24762306a36Sopenharmony_ci		}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci		/* Check current state */
25062306a36Sopenharmony_ci		tps65217_charger_irq(-1, charger);
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	return 0;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic int tps65217_charger_remove(struct platform_device *pdev)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	struct tps65217_charger *charger = platform_get_drvdata(pdev);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	if (charger->poll_task)
26162306a36Sopenharmony_ci		kthread_stop(charger->poll_task);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	return 0;
26462306a36Sopenharmony_ci}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_cistatic const struct of_device_id tps65217_charger_match_table[] = {
26762306a36Sopenharmony_ci	{ .compatible = "ti,tps65217-charger", },
26862306a36Sopenharmony_ci	{ /* sentinel */ }
26962306a36Sopenharmony_ci};
27062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, tps65217_charger_match_table);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_cistatic struct platform_driver tps65217_charger_driver = {
27362306a36Sopenharmony_ci	.probe	= tps65217_charger_probe,
27462306a36Sopenharmony_ci	.remove = tps65217_charger_remove,
27562306a36Sopenharmony_ci	.driver	= {
27662306a36Sopenharmony_ci		.name	= "tps65217-charger",
27762306a36Sopenharmony_ci		.of_match_table = of_match_ptr(tps65217_charger_match_table),
27862306a36Sopenharmony_ci	},
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci};
28162306a36Sopenharmony_cimodule_platform_driver(tps65217_charger_driver);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
28462306a36Sopenharmony_ciMODULE_AUTHOR("Enric Balletbo Serra <enric.balletbo@collabora.com>");
28562306a36Sopenharmony_ciMODULE_DESCRIPTION("TPS65217 battery charger driver");
286