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