File Index
Files and directories
Sign in
FILE

yoga-tab2-pro-1380-fastcharger.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c C
  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 <hansg@kernel.org>
  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
 36struct 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
 49static 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
 64static 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
 69static 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
 74static 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
127static 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
136static 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
146static 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
151static 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	serdev_device_set_drvdata(serdev, fc);
203	serdev_device_set_client_ops(serdev, &yt2_1380_fc_serdev_ops);
204
205	ret = devm_serdev_device_open(dev, serdev);
206	if (ret)
207		return dev_err_probe(dev, ret, "opening UART device\n");
208
209	serdev_device_set_baudrate(serdev, 600);
210	serdev_device_set_flow_control(serdev, false);
211
212	ret = devm_extcon_register_notifier_all(dev, fc->extcon, &fc->nb);
213	if (ret)
214		return dev_err_probe(dev, ret, "registering extcon notifier\n");
215
216	/* In case the extcon already has detected a DCP charger */
217	schedule_work(&fc->work);
218
219	return 0;
220}
221
222static struct serdev_device_driver yt2_1380_fc_serdev_driver = {
223	.probe = yt2_1380_fc_serdev_probe,
224	.driver = {
225		.name = KBUILD_MODNAME,
226	},
227};
228
229static const struct pinctrl_map yt2_1380_fc_pinctrl_map[] = {
230	PIN_MAP_MUX_GROUP(YT2_1380_FC_SERDEV_NAME, "uart3_uart",
231			  "INT33FC:00", "uart3_grp", "uart"),
232	PIN_MAP_MUX_GROUP(YT2_1380_FC_SERDEV_NAME, "uart3_gpio",
233			  "INT33FC:00", "uart3_grp_gpio", "gpio"),
234};
235
236static int yt2_1380_fc_pdev_probe(struct platform_device *pdev)
237{
238	struct serdev_device *serdev;
239	struct device *ctrl_dev;
240	int ret;
241
242	/* Register pinctrl mappings for setting the UART3 pins mode */
243	ret = devm_pinctrl_register_mappings(&pdev->dev, yt2_1380_fc_pinctrl_map,
244					     ARRAY_SIZE(yt2_1380_fc_pinctrl_map));
245	if (ret)
246		return ret;
247
248	/* And create the serdev to talk to the charger over the UART3 pins */
249	ctrl_dev = get_serdev_controller("PNP0501", "1", 0, YT2_1380_FC_SERDEV_CTRL);
250	if (IS_ERR(ctrl_dev))
251		return PTR_ERR(ctrl_dev);
252
253	serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev));
254	put_device(ctrl_dev);
255	if (!serdev)
256		return -ENOMEM;
257
258	/* Propagate pdev-fwnode set by x86-android-tablets to serdev */
259	device_set_node(&serdev->dev, dev_fwnode(&pdev->dev));
260	/* The fwnode is a managed node, so it will be auto-put on serdev_device_put() */
261	fwnode_handle_get(dev_fwnode(&serdev->dev));
262
263	ret = serdev_device_add(serdev);
264	if (ret) {
265		serdev_device_put(serdev);
266		return dev_err_probe(&pdev->dev, ret, "adding serdev\n");
267	}
268
269	/*
270	 * serdev device <-> driver matching relies on OF or ACPI matches and
271	 * neither is available here, manually bind the driver.
272	 */
273	ret = device_driver_attach(&yt2_1380_fc_serdev_driver.driver, &serdev->dev);
274	if (ret) {
275		/* device_driver_attach() maps EPROBE_DEFER to EAGAIN, map it back */
276		serdev_device_remove(serdev);
277		return dev_err_probe(&pdev->dev,
278				     (ret == -EAGAIN) ? -EPROBE_DEFER : ret,
279				     "attaching serdev driver\n");
280	}
281
282	/* So that yt2_1380_fc_pdev_remove() can remove the serdev */
283	platform_set_drvdata(pdev, serdev);
284	return 0;
285}
286
287static void yt2_1380_fc_pdev_remove(struct platform_device *pdev)
288{
289	struct serdev_device *serdev = platform_get_drvdata(pdev);
290
291	serdev_device_remove(serdev);
292}
293
294static struct platform_driver yt2_1380_fc_pdev_driver = {
295	.probe = yt2_1380_fc_pdev_probe,
296	.remove = yt2_1380_fc_pdev_remove,
297	.driver = {
298		.name = YT2_1380_FC_PDEV_NAME,
299		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
300	},
301};
302
303static int __init yt2_1380_fc_module_init(void)
304{
305	int ret;
306
307	/*
308	 * serdev driver MUST be registered first because pdev driver calls
309	 * device_driver_attach() on the serdev, serdev-driver pair.
310	 */
311	ret = serdev_device_driver_register(&yt2_1380_fc_serdev_driver);
312	if (ret)
313		return ret;
314
315	ret = platform_driver_register(&yt2_1380_fc_pdev_driver);
316	if (ret)
317		serdev_device_driver_unregister(&yt2_1380_fc_serdev_driver);
318
319	return ret;
320}
321module_init(yt2_1380_fc_module_init);
322
323static void __exit yt2_1380_fc_module_exit(void)
324{
325	platform_driver_unregister(&yt2_1380_fc_pdev_driver);
326	serdev_device_driver_unregister(&yt2_1380_fc_serdev_driver);
327}
328module_exit(yt2_1380_fc_module_exit);
329
330MODULE_ALIAS("platform:" YT2_1380_FC_PDEV_NAME);
331MODULE_DESCRIPTION("Lenovo Yoga Tablet 2 1380 fast charge driver");
332MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
333MODULE_LICENSE("GPL");
~/ Read-only file preview.