18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// Battery charger driver for TI's tps65217 38c2ecf20Sopenharmony_ci// 48c2ecf20Sopenharmony_ci// Copyright (C) 2015 Collabora Ltd. 58c2ecf20Sopenharmony_ci// Author: Enric Balletbo i Serra <enric.balletbo@collabora.com> 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci/* 88c2ecf20Sopenharmony_ci * Battery charger driver for TI's tps65217 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/kthread.h> 128c2ecf20Sopenharmony_ci#include <linux/device.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 158c2ecf20Sopenharmony_ci#include <linux/init.h> 168c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/err.h> 198c2ecf20Sopenharmony_ci#include <linux/of.h> 208c2ecf20Sopenharmony_ci#include <linux/of_device.h> 218c2ecf20Sopenharmony_ci#include <linux/power_supply.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include <linux/mfd/core.h> 248c2ecf20Sopenharmony_ci#include <linux/mfd/tps65217.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define CHARGER_STATUS_PRESENT (TPS65217_STATUS_ACPWR | TPS65217_STATUS_USBPWR) 278c2ecf20Sopenharmony_ci#define NUM_CHARGER_IRQS 2 288c2ecf20Sopenharmony_ci#define POLL_INTERVAL (HZ * 2) 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistruct tps65217_charger { 318c2ecf20Sopenharmony_ci struct tps65217 *tps; 328c2ecf20Sopenharmony_ci struct device *dev; 338c2ecf20Sopenharmony_ci struct power_supply *psy; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci int online; 368c2ecf20Sopenharmony_ci int prev_online; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci struct task_struct *poll_task; 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic enum power_supply_property tps65217_charger_props[] = { 428c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_ONLINE, 438c2ecf20Sopenharmony_ci}; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic int tps65217_config_charger(struct tps65217_charger *charger) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci int ret; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* 508c2ecf20Sopenharmony_ci * tps65217 rev. G, p. 31 (see p. 32 for NTC schematic) 518c2ecf20Sopenharmony_ci * 528c2ecf20Sopenharmony_ci * The device can be configured to support a 100k NTC (B = 3960) by 538c2ecf20Sopenharmony_ci * setting the the NTC_TYPE bit in register CHGCONFIG1 to 1. However it 548c2ecf20Sopenharmony_ci * is not recommended to do so. In sleep mode, the charger continues 558c2ecf20Sopenharmony_ci * charging the battery, but all register values are reset to default 568c2ecf20Sopenharmony_ci * values. Therefore, the charger would get the wrong temperature 578c2ecf20Sopenharmony_ci * information. If 100k NTC setting is required, please contact the 588c2ecf20Sopenharmony_ci * factory. 598c2ecf20Sopenharmony_ci * 608c2ecf20Sopenharmony_ci * ATTENTION, conflicting information, from p. 46 618c2ecf20Sopenharmony_ci * 628c2ecf20Sopenharmony_ci * NTC TYPE (for battery temperature measurement) 638c2ecf20Sopenharmony_ci * 0 – 100k (curve 1, B = 3960) 648c2ecf20Sopenharmony_ci * 1 – 10k (curve 2, B = 3480) (default on reset) 658c2ecf20Sopenharmony_ci * 668c2ecf20Sopenharmony_ci */ 678c2ecf20Sopenharmony_ci ret = tps65217_clear_bits(charger->tps, TPS65217_REG_CHGCONFIG1, 688c2ecf20Sopenharmony_ci TPS65217_CHGCONFIG1_NTC_TYPE, 698c2ecf20Sopenharmony_ci TPS65217_PROTECT_NONE); 708c2ecf20Sopenharmony_ci if (ret) { 718c2ecf20Sopenharmony_ci dev_err(charger->dev, 728c2ecf20Sopenharmony_ci "failed to set 100k NTC setting: %d\n", ret); 738c2ecf20Sopenharmony_ci return ret; 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci return 0; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic int tps65217_enable_charging(struct tps65217_charger *charger) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci int ret; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci /* charger already enabled */ 848c2ecf20Sopenharmony_ci if (charger->online) 858c2ecf20Sopenharmony_ci return 0; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci dev_dbg(charger->dev, "%s: enable charging\n", __func__); 888c2ecf20Sopenharmony_ci ret = tps65217_set_bits(charger->tps, TPS65217_REG_CHGCONFIG1, 898c2ecf20Sopenharmony_ci TPS65217_CHGCONFIG1_CHG_EN, 908c2ecf20Sopenharmony_ci TPS65217_CHGCONFIG1_CHG_EN, 918c2ecf20Sopenharmony_ci TPS65217_PROTECT_NONE); 928c2ecf20Sopenharmony_ci if (ret) { 938c2ecf20Sopenharmony_ci dev_err(charger->dev, 948c2ecf20Sopenharmony_ci "%s: Error in writing CHG_EN in reg 0x%x: %d\n", 958c2ecf20Sopenharmony_ci __func__, TPS65217_REG_CHGCONFIG1, ret); 968c2ecf20Sopenharmony_ci return ret; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci charger->online = 1; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci return 0; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic int tps65217_charger_get_property(struct power_supply *psy, 1058c2ecf20Sopenharmony_ci enum power_supply_property psp, 1068c2ecf20Sopenharmony_ci union power_supply_propval *val) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct tps65217_charger *charger = power_supply_get_drvdata(psy); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci if (psp == POWER_SUPPLY_PROP_ONLINE) { 1118c2ecf20Sopenharmony_ci val->intval = charger->online; 1128c2ecf20Sopenharmony_ci return 0; 1138c2ecf20Sopenharmony_ci } 1148c2ecf20Sopenharmony_ci return -EINVAL; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic irqreturn_t tps65217_charger_irq(int irq, void *dev) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci int ret, val; 1208c2ecf20Sopenharmony_ci struct tps65217_charger *charger = dev; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci charger->prev_online = charger->online; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci ret = tps65217_reg_read(charger->tps, TPS65217_REG_STATUS, &val); 1258c2ecf20Sopenharmony_ci if (ret < 0) { 1268c2ecf20Sopenharmony_ci dev_err(charger->dev, "%s: Error in reading reg 0x%x\n", 1278c2ecf20Sopenharmony_ci __func__, TPS65217_REG_STATUS); 1288c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci dev_dbg(charger->dev, "%s: 0x%x\n", __func__, val); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci /* check for charger status bit */ 1348c2ecf20Sopenharmony_ci if (val & CHARGER_STATUS_PRESENT) { 1358c2ecf20Sopenharmony_ci ret = tps65217_enable_charging(charger); 1368c2ecf20Sopenharmony_ci if (ret) { 1378c2ecf20Sopenharmony_ci dev_err(charger->dev, 1388c2ecf20Sopenharmony_ci "failed to enable charger: %d\n", ret); 1398c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci } else { 1428c2ecf20Sopenharmony_ci charger->online = 0; 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci if (charger->prev_online != charger->online) 1468c2ecf20Sopenharmony_ci power_supply_changed(charger->psy); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci ret = tps65217_reg_read(charger->tps, TPS65217_REG_CHGCONFIG0, &val); 1498c2ecf20Sopenharmony_ci if (ret < 0) { 1508c2ecf20Sopenharmony_ci dev_err(charger->dev, "%s: Error in reading reg 0x%x\n", 1518c2ecf20Sopenharmony_ci __func__, TPS65217_REG_CHGCONFIG0); 1528c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci if (val & TPS65217_CHGCONFIG0_ACTIVE) 1568c2ecf20Sopenharmony_ci dev_dbg(charger->dev, "%s: charger is charging\n", __func__); 1578c2ecf20Sopenharmony_ci else 1588c2ecf20Sopenharmony_ci dev_dbg(charger->dev, 1598c2ecf20Sopenharmony_ci "%s: charger is NOT charging\n", __func__); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic int tps65217_charger_poll_task(void *data) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci set_freezable(); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci while (!kthread_should_stop()) { 1698c2ecf20Sopenharmony_ci schedule_timeout_interruptible(POLL_INTERVAL); 1708c2ecf20Sopenharmony_ci try_to_freeze(); 1718c2ecf20Sopenharmony_ci tps65217_charger_irq(-1, data); 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci return 0; 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic const struct power_supply_desc tps65217_charger_desc = { 1778c2ecf20Sopenharmony_ci .name = "tps65217-charger", 1788c2ecf20Sopenharmony_ci .type = POWER_SUPPLY_TYPE_MAINS, 1798c2ecf20Sopenharmony_ci .get_property = tps65217_charger_get_property, 1808c2ecf20Sopenharmony_ci .properties = tps65217_charger_props, 1818c2ecf20Sopenharmony_ci .num_properties = ARRAY_SIZE(tps65217_charger_props), 1828c2ecf20Sopenharmony_ci}; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_cistatic int tps65217_charger_probe(struct platform_device *pdev) 1858c2ecf20Sopenharmony_ci{ 1868c2ecf20Sopenharmony_ci struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent); 1878c2ecf20Sopenharmony_ci struct tps65217_charger *charger; 1888c2ecf20Sopenharmony_ci struct power_supply_config cfg = {}; 1898c2ecf20Sopenharmony_ci struct task_struct *poll_task; 1908c2ecf20Sopenharmony_ci int irq[NUM_CHARGER_IRQS]; 1918c2ecf20Sopenharmony_ci int ret; 1928c2ecf20Sopenharmony_ci int i; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); 1958c2ecf20Sopenharmony_ci if (!charger) 1968c2ecf20Sopenharmony_ci return -ENOMEM; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, charger); 1998c2ecf20Sopenharmony_ci charger->tps = tps; 2008c2ecf20Sopenharmony_ci charger->dev = &pdev->dev; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci cfg.of_node = pdev->dev.of_node; 2038c2ecf20Sopenharmony_ci cfg.drv_data = charger; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci charger->psy = devm_power_supply_register(&pdev->dev, 2068c2ecf20Sopenharmony_ci &tps65217_charger_desc, 2078c2ecf20Sopenharmony_ci &cfg); 2088c2ecf20Sopenharmony_ci if (IS_ERR(charger->psy)) { 2098c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed: power supply register\n"); 2108c2ecf20Sopenharmony_ci return PTR_ERR(charger->psy); 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci irq[0] = platform_get_irq_byname(pdev, "USB"); 2148c2ecf20Sopenharmony_ci irq[1] = platform_get_irq_byname(pdev, "AC"); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci ret = tps65217_config_charger(charger); 2178c2ecf20Sopenharmony_ci if (ret < 0) { 2188c2ecf20Sopenharmony_ci dev_err(charger->dev, "charger config failed, err %d\n", ret); 2198c2ecf20Sopenharmony_ci return ret; 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci /* Create a polling thread if an interrupt is invalid */ 2238c2ecf20Sopenharmony_ci if (irq[0] < 0 || irq[1] < 0) { 2248c2ecf20Sopenharmony_ci poll_task = kthread_run(tps65217_charger_poll_task, 2258c2ecf20Sopenharmony_ci charger, "ktps65217charger"); 2268c2ecf20Sopenharmony_ci if (IS_ERR(poll_task)) { 2278c2ecf20Sopenharmony_ci ret = PTR_ERR(poll_task); 2288c2ecf20Sopenharmony_ci dev_err(charger->dev, 2298c2ecf20Sopenharmony_ci "Unable to run kthread err %d\n", ret); 2308c2ecf20Sopenharmony_ci return ret; 2318c2ecf20Sopenharmony_ci } 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci charger->poll_task = poll_task; 2348c2ecf20Sopenharmony_ci return 0; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci /* Create IRQ threads for charger interrupts */ 2388c2ecf20Sopenharmony_ci for (i = 0; i < NUM_CHARGER_IRQS; i++) { 2398c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, irq[i], NULL, 2408c2ecf20Sopenharmony_ci tps65217_charger_irq, 2418c2ecf20Sopenharmony_ci IRQF_ONESHOT, "tps65217-charger", 2428c2ecf20Sopenharmony_ci charger); 2438c2ecf20Sopenharmony_ci if (ret) { 2448c2ecf20Sopenharmony_ci dev_err(charger->dev, 2458c2ecf20Sopenharmony_ci "Unable to register irq %d err %d\n", irq[i], 2468c2ecf20Sopenharmony_ci ret); 2478c2ecf20Sopenharmony_ci return ret; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci /* Check current state */ 2518c2ecf20Sopenharmony_ci tps65217_charger_irq(-1, charger); 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci return 0; 2558c2ecf20Sopenharmony_ci} 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cistatic int tps65217_charger_remove(struct platform_device *pdev) 2588c2ecf20Sopenharmony_ci{ 2598c2ecf20Sopenharmony_ci struct tps65217_charger *charger = platform_get_drvdata(pdev); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci if (charger->poll_task) 2628c2ecf20Sopenharmony_ci kthread_stop(charger->poll_task); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci return 0; 2658c2ecf20Sopenharmony_ci} 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_cistatic const struct of_device_id tps65217_charger_match_table[] = { 2688c2ecf20Sopenharmony_ci { .compatible = "ti,tps65217-charger", }, 2698c2ecf20Sopenharmony_ci { /* sentinel */ } 2708c2ecf20Sopenharmony_ci}; 2718c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, tps65217_charger_match_table); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_cistatic struct platform_driver tps65217_charger_driver = { 2748c2ecf20Sopenharmony_ci .probe = tps65217_charger_probe, 2758c2ecf20Sopenharmony_ci .remove = tps65217_charger_remove, 2768c2ecf20Sopenharmony_ci .driver = { 2778c2ecf20Sopenharmony_ci .name = "tps65217-charger", 2788c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(tps65217_charger_match_table), 2798c2ecf20Sopenharmony_ci }, 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci}; 2828c2ecf20Sopenharmony_cimodule_platform_driver(tps65217_charger_driver); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2858c2ecf20Sopenharmony_ciMODULE_AUTHOR("Enric Balletbo Serra <enric.balletbo@collabora.com>"); 2868c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TPS65217 battery charger driver"); 287