1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Driver for Analog Devices (Linear Technology) LT3651 charger IC.
4*4882a593Smuzhiyun * Copyright (C) 2017, Topic Embedded Products
5*4882a593Smuzhiyun */
6*4882a593Smuzhiyun
7*4882a593Smuzhiyun #include <linux/device.h>
8*4882a593Smuzhiyun #include <linux/gpio/consumer.h>
9*4882a593Smuzhiyun #include <linux/init.h>
10*4882a593Smuzhiyun #include <linux/interrupt.h>
11*4882a593Smuzhiyun #include <linux/kernel.h>
12*4882a593Smuzhiyun #include <linux/module.h>
13*4882a593Smuzhiyun #include <linux/platform_device.h>
14*4882a593Smuzhiyun #include <linux/power_supply.h>
15*4882a593Smuzhiyun #include <linux/slab.h>
16*4882a593Smuzhiyun #include <linux/of.h>
17*4882a593Smuzhiyun
18*4882a593Smuzhiyun struct lt3651_charger {
19*4882a593Smuzhiyun struct power_supply *charger;
20*4882a593Smuzhiyun struct power_supply_desc charger_desc;
21*4882a593Smuzhiyun struct gpio_desc *acpr_gpio;
22*4882a593Smuzhiyun struct gpio_desc *fault_gpio;
23*4882a593Smuzhiyun struct gpio_desc *chrg_gpio;
24*4882a593Smuzhiyun };
25*4882a593Smuzhiyun
lt3651_charger_irq(int irq,void * devid)26*4882a593Smuzhiyun static irqreturn_t lt3651_charger_irq(int irq, void *devid)
27*4882a593Smuzhiyun {
28*4882a593Smuzhiyun struct power_supply *charger = devid;
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun power_supply_changed(charger);
31*4882a593Smuzhiyun
32*4882a593Smuzhiyun return IRQ_HANDLED;
33*4882a593Smuzhiyun }
34*4882a593Smuzhiyun
psy_to_lt3651_charger(struct power_supply * psy)35*4882a593Smuzhiyun static inline struct lt3651_charger *psy_to_lt3651_charger(
36*4882a593Smuzhiyun struct power_supply *psy)
37*4882a593Smuzhiyun {
38*4882a593Smuzhiyun return power_supply_get_drvdata(psy);
39*4882a593Smuzhiyun }
40*4882a593Smuzhiyun
lt3651_charger_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)41*4882a593Smuzhiyun static int lt3651_charger_get_property(struct power_supply *psy,
42*4882a593Smuzhiyun enum power_supply_property psp, union power_supply_propval *val)
43*4882a593Smuzhiyun {
44*4882a593Smuzhiyun struct lt3651_charger *lt3651_charger = psy_to_lt3651_charger(psy);
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun switch (psp) {
47*4882a593Smuzhiyun case POWER_SUPPLY_PROP_STATUS:
48*4882a593Smuzhiyun if (!lt3651_charger->chrg_gpio) {
49*4882a593Smuzhiyun val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
50*4882a593Smuzhiyun break;
51*4882a593Smuzhiyun }
52*4882a593Smuzhiyun if (gpiod_get_value(lt3651_charger->chrg_gpio))
53*4882a593Smuzhiyun val->intval = POWER_SUPPLY_STATUS_CHARGING;
54*4882a593Smuzhiyun else
55*4882a593Smuzhiyun val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
56*4882a593Smuzhiyun break;
57*4882a593Smuzhiyun case POWER_SUPPLY_PROP_ONLINE:
58*4882a593Smuzhiyun val->intval = gpiod_get_value(lt3651_charger->acpr_gpio);
59*4882a593Smuzhiyun break;
60*4882a593Smuzhiyun case POWER_SUPPLY_PROP_HEALTH:
61*4882a593Smuzhiyun if (!lt3651_charger->fault_gpio) {
62*4882a593Smuzhiyun val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
63*4882a593Smuzhiyun break;
64*4882a593Smuzhiyun }
65*4882a593Smuzhiyun if (!gpiod_get_value(lt3651_charger->fault_gpio)) {
66*4882a593Smuzhiyun val->intval = POWER_SUPPLY_HEALTH_GOOD;
67*4882a593Smuzhiyun break;
68*4882a593Smuzhiyun }
69*4882a593Smuzhiyun /*
70*4882a593Smuzhiyun * If the fault pin is active, the chrg pin explains the type
71*4882a593Smuzhiyun * of failure.
72*4882a593Smuzhiyun */
73*4882a593Smuzhiyun if (!lt3651_charger->chrg_gpio) {
74*4882a593Smuzhiyun val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
75*4882a593Smuzhiyun break;
76*4882a593Smuzhiyun }
77*4882a593Smuzhiyun val->intval = gpiod_get_value(lt3651_charger->chrg_gpio) ?
78*4882a593Smuzhiyun POWER_SUPPLY_HEALTH_OVERHEAT :
79*4882a593Smuzhiyun POWER_SUPPLY_HEALTH_DEAD;
80*4882a593Smuzhiyun break;
81*4882a593Smuzhiyun default:
82*4882a593Smuzhiyun return -EINVAL;
83*4882a593Smuzhiyun }
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun return 0;
86*4882a593Smuzhiyun }
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun static enum power_supply_property lt3651_charger_properties[] = {
89*4882a593Smuzhiyun POWER_SUPPLY_PROP_STATUS,
90*4882a593Smuzhiyun POWER_SUPPLY_PROP_ONLINE,
91*4882a593Smuzhiyun POWER_SUPPLY_PROP_HEALTH,
92*4882a593Smuzhiyun };
93*4882a593Smuzhiyun
lt3651_charger_probe(struct platform_device * pdev)94*4882a593Smuzhiyun static int lt3651_charger_probe(struct platform_device *pdev)
95*4882a593Smuzhiyun {
96*4882a593Smuzhiyun struct power_supply_config psy_cfg = {};
97*4882a593Smuzhiyun struct lt3651_charger *lt3651_charger;
98*4882a593Smuzhiyun struct power_supply_desc *charger_desc;
99*4882a593Smuzhiyun int ret;
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun lt3651_charger = devm_kzalloc(&pdev->dev, sizeof(*lt3651_charger),
102*4882a593Smuzhiyun GFP_KERNEL);
103*4882a593Smuzhiyun if (!lt3651_charger)
104*4882a593Smuzhiyun return -ENOMEM;
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun lt3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev,
107*4882a593Smuzhiyun "lltc,acpr", GPIOD_IN);
108*4882a593Smuzhiyun if (IS_ERR(lt3651_charger->acpr_gpio)) {
109*4882a593Smuzhiyun ret = PTR_ERR(lt3651_charger->acpr_gpio);
110*4882a593Smuzhiyun dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret);
111*4882a593Smuzhiyun return ret;
112*4882a593Smuzhiyun }
113*4882a593Smuzhiyun lt3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev,
114*4882a593Smuzhiyun "lltc,fault", GPIOD_IN);
115*4882a593Smuzhiyun if (IS_ERR(lt3651_charger->fault_gpio)) {
116*4882a593Smuzhiyun ret = PTR_ERR(lt3651_charger->fault_gpio);
117*4882a593Smuzhiyun dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret);
118*4882a593Smuzhiyun return ret;
119*4882a593Smuzhiyun }
120*4882a593Smuzhiyun lt3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev,
121*4882a593Smuzhiyun "lltc,chrg", GPIOD_IN);
122*4882a593Smuzhiyun if (IS_ERR(lt3651_charger->chrg_gpio)) {
123*4882a593Smuzhiyun ret = PTR_ERR(lt3651_charger->chrg_gpio);
124*4882a593Smuzhiyun dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret);
125*4882a593Smuzhiyun return ret;
126*4882a593Smuzhiyun }
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun charger_desc = <3651_charger->charger_desc;
129*4882a593Smuzhiyun charger_desc->name = pdev->dev.of_node->name;
130*4882a593Smuzhiyun charger_desc->type = POWER_SUPPLY_TYPE_MAINS;
131*4882a593Smuzhiyun charger_desc->properties = lt3651_charger_properties;
132*4882a593Smuzhiyun charger_desc->num_properties = ARRAY_SIZE(lt3651_charger_properties);
133*4882a593Smuzhiyun charger_desc->get_property = lt3651_charger_get_property;
134*4882a593Smuzhiyun psy_cfg.of_node = pdev->dev.of_node;
135*4882a593Smuzhiyun psy_cfg.drv_data = lt3651_charger;
136*4882a593Smuzhiyun
137*4882a593Smuzhiyun lt3651_charger->charger = devm_power_supply_register(&pdev->dev,
138*4882a593Smuzhiyun charger_desc, &psy_cfg);
139*4882a593Smuzhiyun if (IS_ERR(lt3651_charger->charger)) {
140*4882a593Smuzhiyun ret = PTR_ERR(lt3651_charger->charger);
141*4882a593Smuzhiyun dev_err(&pdev->dev, "Failed to register power supply: %d\n",
142*4882a593Smuzhiyun ret);
143*4882a593Smuzhiyun return ret;
144*4882a593Smuzhiyun }
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun /*
147*4882a593Smuzhiyun * Acquire IRQs for the GPIO pins if possible. If the system does not
148*4882a593Smuzhiyun * support IRQs on these pins, userspace will have to poll the sysfs
149*4882a593Smuzhiyun * files manually.
150*4882a593Smuzhiyun */
151*4882a593Smuzhiyun if (lt3651_charger->acpr_gpio) {
152*4882a593Smuzhiyun ret = gpiod_to_irq(lt3651_charger->acpr_gpio);
153*4882a593Smuzhiyun if (ret >= 0)
154*4882a593Smuzhiyun ret = devm_request_any_context_irq(&pdev->dev, ret,
155*4882a593Smuzhiyun lt3651_charger_irq,
156*4882a593Smuzhiyun IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
157*4882a593Smuzhiyun dev_name(&pdev->dev), lt3651_charger->charger);
158*4882a593Smuzhiyun if (ret < 0)
159*4882a593Smuzhiyun dev_warn(&pdev->dev, "Failed to request acpr irq\n");
160*4882a593Smuzhiyun }
161*4882a593Smuzhiyun if (lt3651_charger->fault_gpio) {
162*4882a593Smuzhiyun ret = gpiod_to_irq(lt3651_charger->fault_gpio);
163*4882a593Smuzhiyun if (ret >= 0)
164*4882a593Smuzhiyun ret = devm_request_any_context_irq(&pdev->dev, ret,
165*4882a593Smuzhiyun lt3651_charger_irq,
166*4882a593Smuzhiyun IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
167*4882a593Smuzhiyun dev_name(&pdev->dev), lt3651_charger->charger);
168*4882a593Smuzhiyun if (ret < 0)
169*4882a593Smuzhiyun dev_warn(&pdev->dev, "Failed to request fault irq\n");
170*4882a593Smuzhiyun }
171*4882a593Smuzhiyun if (lt3651_charger->chrg_gpio) {
172*4882a593Smuzhiyun ret = gpiod_to_irq(lt3651_charger->chrg_gpio);
173*4882a593Smuzhiyun if (ret >= 0)
174*4882a593Smuzhiyun ret = devm_request_any_context_irq(&pdev->dev, ret,
175*4882a593Smuzhiyun lt3651_charger_irq,
176*4882a593Smuzhiyun IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
177*4882a593Smuzhiyun dev_name(&pdev->dev), lt3651_charger->charger);
178*4882a593Smuzhiyun if (ret < 0)
179*4882a593Smuzhiyun dev_warn(&pdev->dev, "Failed to request chrg irq\n");
180*4882a593Smuzhiyun }
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun platform_set_drvdata(pdev, lt3651_charger);
183*4882a593Smuzhiyun
184*4882a593Smuzhiyun return 0;
185*4882a593Smuzhiyun }
186*4882a593Smuzhiyun
187*4882a593Smuzhiyun static const struct of_device_id lt3651_charger_match[] = {
188*4882a593Smuzhiyun { .compatible = "lltc,ltc3651-charger" }, /* DEPRECATED */
189*4882a593Smuzhiyun { .compatible = "lltc,lt3651-charger" },
190*4882a593Smuzhiyun { }
191*4882a593Smuzhiyun };
192*4882a593Smuzhiyun MODULE_DEVICE_TABLE(of, lt3651_charger_match);
193*4882a593Smuzhiyun
194*4882a593Smuzhiyun static struct platform_driver lt3651_charger_driver = {
195*4882a593Smuzhiyun .probe = lt3651_charger_probe,
196*4882a593Smuzhiyun .driver = {
197*4882a593Smuzhiyun .name = "lt3651-charger",
198*4882a593Smuzhiyun .of_match_table = lt3651_charger_match,
199*4882a593Smuzhiyun },
200*4882a593Smuzhiyun };
201*4882a593Smuzhiyun
202*4882a593Smuzhiyun module_platform_driver(lt3651_charger_driver);
203*4882a593Smuzhiyun
204*4882a593Smuzhiyun MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>");
205*4882a593Smuzhiyun MODULE_DESCRIPTION("Driver for LT3651 charger");
206*4882a593Smuzhiyun MODULE_LICENSE("GPL");
207*4882a593Smuzhiyun MODULE_ALIAS("platform:lt3651-charger");
208