Skip to content

Commit b2ed33e

Browse files
committed
platform/x86: Add lenovo-yoga-tab2-pro-1380-fastcharger driver
Add a new driver for the custom fast charging protocol found on Lenovo Yoga Tablet 2 1380F / 1380L models. Signed-off-by: Hans de Goede <[email protected]> Reviewed-by: Ilpo Järvinen <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 9c0beb6 commit b2ed33e

File tree

3 files changed

+350
-0
lines changed

3 files changed

+350
-0
lines changed

drivers/platform/x86/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,17 @@ config YOGABOOK
133133
To compile this driver as a module, choose M here: the module will
134134
be called lenovo-yogabook.
135135

136+
config YT2_1380
137+
tristate "Lenovo Yoga Tablet 2 1380 fast charge driver"
138+
depends on SERIAL_DEV_BUS
139+
depends on ACPI
140+
help
141+
Say Y here to enable support for the custom fast charging protocol
142+
found on the Lenovo Yoga Tablet 2 1380F / 1380L models.
143+
144+
To compile this driver as a module, choose M here: the module will
145+
be called lenovo-yogabook.
146+
136147
config ACERHDF
137148
tristate "Acer Aspire One temperature and fan driver"
138149
depends on ACPI && THERMAL

drivers/platform/x86/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
6666
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
6767
obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
6868
obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
69+
obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
6970
obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
7071

7172
# Intel
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Support for the custom fast charging protocol found on the Lenovo Yoga
4+
* Tablet 2 1380F / 1380L models.
5+
*
6+
* Copyright (C) 2024 Hans de Goede <[email protected]>
7+
*/
8+
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9+
10+
#include <linux/delay.h>
11+
#include <linux/err.h>
12+
#include <linux/errno.h>
13+
#include <linux/extcon.h>
14+
#include <linux/gpio/consumer.h>
15+
#include <linux/module.h>
16+
#include <linux/notifier.h>
17+
#include <linux/pinctrl/consumer.h>
18+
#include <linux/pinctrl/machine.h>
19+
#include <linux/platform_device.h>
20+
#include <linux/serdev.h>
21+
#include <linux/time.h>
22+
#include <linux/types.h>
23+
#include <linux/workqueue.h>
24+
#include "serdev_helpers.h"
25+
26+
#define YT2_1380_FC_PDEV_NAME "lenovo-yoga-tab2-pro-1380-fastcharger"
27+
#define YT2_1380_FC_SERDEV_CTRL "serial0"
28+
#define YT2_1380_FC_SERDEV_NAME "serial0-0"
29+
#define YT2_1380_FC_EXTCON_NAME "i2c-lc824206xa"
30+
31+
#define YT2_1380_FC_MAX_TRIES 5
32+
#define YT2_1380_FC_PIN_SW_DELAY_US (10 * USEC_PER_MSEC)
33+
#define YT2_1380_FC_UART_DRAIN_DELAY_US (50 * USEC_PER_MSEC)
34+
#define YT2_1380_FC_VOLT_SW_DELAY_US (1000 * USEC_PER_MSEC)
35+
36+
struct yt2_1380_fc {
37+
struct device *dev;
38+
struct pinctrl *pinctrl;
39+
struct pinctrl_state *gpio_state;
40+
struct pinctrl_state *uart_state;
41+
struct gpio_desc *uart3_txd;
42+
struct gpio_desc *uart3_rxd;
43+
struct extcon_dev *extcon;
44+
struct notifier_block nb;
45+
struct work_struct work;
46+
bool fast_charging;
47+
};
48+
49+
static int yt2_1380_fc_set_gpio_mode(struct yt2_1380_fc *fc, bool enable)
50+
{
51+
struct pinctrl_state *state = enable ? fc->gpio_state : fc->uart_state;
52+
int ret;
53+
54+
ret = pinctrl_select_state(fc->pinctrl, state);
55+
if (ret) {
56+
dev_err(fc->dev, "Error %d setting pinctrl state\n", ret);
57+
return ret;
58+
}
59+
60+
fsleep(YT2_1380_FC_PIN_SW_DELAY_US);
61+
return 0;
62+
}
63+
64+
static bool yt2_1380_fc_dedicated_charger_connected(struct yt2_1380_fc *fc)
65+
{
66+
return extcon_get_state(fc->extcon, EXTCON_CHG_USB_DCP) > 0;
67+
}
68+
69+
static bool yt2_1380_fc_fast_charger_connected(struct yt2_1380_fc *fc)
70+
{
71+
return extcon_get_state(fc->extcon, EXTCON_CHG_USB_FAST) > 0;
72+
}
73+
74+
static void yt2_1380_fc_worker(struct work_struct *work)
75+
{
76+
struct yt2_1380_fc *fc = container_of(work, struct yt2_1380_fc, work);
77+
int i, ret;
78+
79+
/* Do nothing if already fast charging */
80+
if (yt2_1380_fc_fast_charger_connected(fc))
81+
return;
82+
83+
for (i = 0; i < YT2_1380_FC_MAX_TRIES; i++) {
84+
/* Set pins to UART mode (for charger disconnect and retries) */
85+
ret = yt2_1380_fc_set_gpio_mode(fc, false);
86+
if (ret)
87+
return;
88+
89+
/* Only try 12V charging if a dedicated charger is detected */
90+
if (!yt2_1380_fc_dedicated_charger_connected(fc))
91+
return;
92+
93+
/* Send the command to switch to 12V charging */
94+
ret = serdev_device_write_buf(to_serdev_device(fc->dev), "SC", strlen("SC"));
95+
if (ret != strlen("SC")) {
96+
dev_err(fc->dev, "Error %d writing to uart\n", ret);
97+
return;
98+
}
99+
100+
fsleep(YT2_1380_FC_UART_DRAIN_DELAY_US);
101+
102+
/* Re-check a charger is still connected */
103+
if (!yt2_1380_fc_dedicated_charger_connected(fc))
104+
return;
105+
106+
/*
107+
* Now switch the lines to GPIO (output, high). The charger
108+
* expects the lines being driven high after the command.
109+
* Presumably this is used to detect the tablet getting
110+
* unplugged (to switch back to 5V output on unplug).
111+
*/
112+
ret = yt2_1380_fc_set_gpio_mode(fc, true);
113+
if (ret)
114+
return;
115+
116+
fsleep(YT2_1380_FC_VOLT_SW_DELAY_US);
117+
118+
if (yt2_1380_fc_fast_charger_connected(fc))
119+
return; /* Success */
120+
}
121+
122+
dev_dbg(fc->dev, "Failed to switch to 12V charging (not the original charger?)\n");
123+
/* Failed to enable 12V fast charging, reset pins to default UART mode */
124+
yt2_1380_fc_set_gpio_mode(fc, false);
125+
}
126+
127+
static int yt2_1380_fc_extcon_evt(struct notifier_block *nb,
128+
unsigned long event, void *param)
129+
{
130+
struct yt2_1380_fc *fc = container_of(nb, struct yt2_1380_fc, nb);
131+
132+
schedule_work(&fc->work);
133+
return NOTIFY_OK;
134+
}
135+
136+
static size_t yt2_1380_fc_receive(struct serdev_device *serdev, const u8 *data, size_t len)
137+
{
138+
/*
139+
* Since the USB data lines are shorted for DCP detection, echos of
140+
* the "SC" command send in yt2_1380_fc_worker() will be received.
141+
*/
142+
dev_dbg(&serdev->dev, "recv: %*ph\n", (int)len, data);
143+
return len;
144+
}
145+
146+
static const struct serdev_device_ops yt2_1380_fc_serdev_ops = {
147+
.receive_buf = yt2_1380_fc_receive,
148+
.write_wakeup = serdev_device_write_wakeup,
149+
};
150+
151+
static int yt2_1380_fc_serdev_probe(struct serdev_device *serdev)
152+
{
153+
struct device *dev = &serdev->dev;
154+
struct yt2_1380_fc *fc;
155+
int ret;
156+
157+
fc = devm_kzalloc(dev, sizeof(*fc), GFP_KERNEL);
158+
if (!fc)
159+
return -ENOMEM;
160+
161+
fc->dev = dev;
162+
fc->nb.notifier_call = yt2_1380_fc_extcon_evt;
163+
INIT_WORK(&fc->work, yt2_1380_fc_worker);
164+
165+
/*
166+
* Do this first since it may return -EPROBE_DEFER.
167+
* There is no extcon_put(), so there is no need to free this.
168+
*/
169+
fc->extcon = extcon_get_extcon_dev(YT2_1380_FC_EXTCON_NAME);
170+
if (IS_ERR(fc->extcon))
171+
return dev_err_probe(dev, PTR_ERR(fc->extcon), "getting extcon\n");
172+
173+
fc->pinctrl = devm_pinctrl_get(dev);
174+
if (IS_ERR(fc->pinctrl))
175+
return dev_err_probe(dev, PTR_ERR(fc->pinctrl), "getting pinctrl\n");
176+
177+
/*
178+
* To switch the UART3 pins connected to the USB data lines between
179+
* UART and GPIO modes.
180+
*/
181+
fc->gpio_state = pinctrl_lookup_state(fc->pinctrl, "uart3_gpio");
182+
fc->uart_state = pinctrl_lookup_state(fc->pinctrl, "uart3_uart");
183+
if (IS_ERR(fc->gpio_state) || IS_ERR(fc->uart_state))
184+
return dev_err_probe(dev, -EINVAL, "getting pinctrl states\n");
185+
186+
ret = yt2_1380_fc_set_gpio_mode(fc, true);
187+
if (ret)
188+
return ret;
189+
190+
fc->uart3_txd = devm_gpiod_get(dev, "uart3_txd", GPIOD_OUT_HIGH);
191+
if (IS_ERR(fc->uart3_txd))
192+
return dev_err_probe(dev, PTR_ERR(fc->uart3_txd), "getting uart3_txd gpio\n");
193+
194+
fc->uart3_rxd = devm_gpiod_get(dev, "uart3_rxd", GPIOD_OUT_HIGH);
195+
if (IS_ERR(fc->uart3_rxd))
196+
return dev_err_probe(dev, PTR_ERR(fc->uart3_rxd), "getting uart3_rxd gpio\n");
197+
198+
ret = yt2_1380_fc_set_gpio_mode(fc, false);
199+
if (ret)
200+
return ret;
201+
202+
ret = devm_serdev_device_open(dev, serdev);
203+
if (ret)
204+
return dev_err_probe(dev, ret, "opening UART device\n");
205+
206+
serdev_device_set_baudrate(serdev, 600);
207+
serdev_device_set_flow_control(serdev, false);
208+
serdev_device_set_drvdata(serdev, fc);
209+
serdev_device_set_client_ops(serdev, &yt2_1380_fc_serdev_ops);
210+
211+
ret = devm_extcon_register_notifier_all(dev, fc->extcon, &fc->nb);
212+
if (ret)
213+
return dev_err_probe(dev, ret, "registering extcon notifier\n");
214+
215+
/* In case the extcon already has detected a DCP charger */
216+
schedule_work(&fc->work);
217+
218+
return 0;
219+
}
220+
221+
struct serdev_device_driver yt2_1380_fc_serdev_driver = {
222+
.probe = yt2_1380_fc_serdev_probe,
223+
.driver = {
224+
.name = KBUILD_MODNAME,
225+
},
226+
};
227+
228+
static const struct pinctrl_map yt2_1380_fc_pinctrl_map[] = {
229+
PIN_MAP_MUX_GROUP(YT2_1380_FC_SERDEV_NAME, "uart3_uart",
230+
"INT33FC:00", "uart3_grp", "uart"),
231+
PIN_MAP_MUX_GROUP(YT2_1380_FC_SERDEV_NAME, "uart3_gpio",
232+
"INT33FC:00", "uart3_grp_gpio", "gpio"),
233+
};
234+
235+
static int yt2_1380_fc_pdev_probe(struct platform_device *pdev)
236+
{
237+
struct serdev_device *serdev;
238+
struct device *ctrl_dev;
239+
int ret;
240+
241+
/* Register pinctrl mappings for setting the UART3 pins mode */
242+
ret = pinctrl_register_mappings(yt2_1380_fc_pinctrl_map,
243+
ARRAY_SIZE(yt2_1380_fc_pinctrl_map));
244+
if (ret)
245+
return ret;
246+
247+
/* And create the serdev to talk to the charger over the UART3 pins */
248+
ctrl_dev = get_serdev_controller("PNP0501", "1", 0, YT2_1380_FC_SERDEV_CTRL);
249+
if (IS_ERR(ctrl_dev)) {
250+
ret = PTR_ERR(ctrl_dev);
251+
goto out_pinctrl_unregister_mappings;
252+
}
253+
254+
serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev));
255+
put_device(ctrl_dev);
256+
if (!serdev) {
257+
ret = -ENOMEM;
258+
goto out_pinctrl_unregister_mappings;
259+
}
260+
261+
ret = serdev_device_add(serdev);
262+
if (ret) {
263+
dev_err_probe(&pdev->dev, ret, "adding serdev\n");
264+
serdev_device_put(serdev);
265+
goto out_pinctrl_unregister_mappings;
266+
}
267+
268+
/*
269+
* serdev device <-> driver matching relies on OF or ACPI matches and
270+
* neither is available here, manually bind the driver.
271+
*/
272+
ret = device_driver_attach(&yt2_1380_fc_serdev_driver.driver, &serdev->dev);
273+
if (ret) {
274+
/* device_driver_attach() maps EPROBE_DEFER to EAGAIN, map it back */
275+
ret = (ret == -EAGAIN) ? -EPROBE_DEFER : ret;
276+
dev_err_probe(&pdev->dev, ret, "attaching serdev driver\n");
277+
goto out_serdev_device_remove;
278+
}
279+
280+
/* So that yt2_1380_fc_pdev_remove() can remove the serdev */
281+
platform_set_drvdata(pdev, serdev);
282+
return 0;
283+
284+
out_serdev_device_remove:
285+
serdev_device_remove(serdev);
286+
out_pinctrl_unregister_mappings:
287+
pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map);
288+
return ret;
289+
}
290+
291+
static void yt2_1380_fc_pdev_remove(struct platform_device *pdev)
292+
{
293+
struct serdev_device *serdev = platform_get_drvdata(pdev);
294+
295+
serdev_device_remove(serdev);
296+
pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map);
297+
}
298+
299+
static struct platform_driver yt2_1380_fc_pdev_driver = {
300+
.probe = yt2_1380_fc_pdev_probe,
301+
.remove_new = yt2_1380_fc_pdev_remove,
302+
.driver = {
303+
.name = YT2_1380_FC_PDEV_NAME,
304+
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
305+
},
306+
};
307+
308+
static int __init yt2_1380_fc_module_init(void)
309+
{
310+
int ret;
311+
312+
/*
313+
* serdev driver MUST be registered first because pdev driver calls
314+
* device_driver_attach() on the serdev, serdev-driver pair.
315+
*/
316+
ret = serdev_device_driver_register(&yt2_1380_fc_serdev_driver);
317+
if (ret)
318+
return ret;
319+
320+
ret = platform_driver_register(&yt2_1380_fc_pdev_driver);
321+
if (ret)
322+
serdev_device_driver_unregister(&yt2_1380_fc_serdev_driver);
323+
324+
return ret;
325+
}
326+
module_init(yt2_1380_fc_module_init);
327+
328+
static void __exit yt2_1380_fc_module_exit(void)
329+
{
330+
platform_driver_unregister(&yt2_1380_fc_pdev_driver);
331+
serdev_device_driver_unregister(&yt2_1380_fc_serdev_driver);
332+
}
333+
module_exit(yt2_1380_fc_module_exit);
334+
335+
MODULE_ALIAS("platform:" YT2_1380_FC_PDEV_NAME);
336+
MODULE_DESCRIPTION("Lenovo Yoga Tablet 2 1380 fast charge driver");
337+
MODULE_AUTHOR("Hans de Goede <[email protected]>");
338+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)