brintos

brintos / linux-shallow public Read only

0
0
Text · 9.6 KiB · d525bdc Raw
339 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Support for the custom fast charging protocol found on the Lenovo Yoga4 * Tablet 2 1380F / 1380L models.5 *6 * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>7 */8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt9 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		532#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 charger108		 * expects the lines being driven high after the command.109		 * Presumably this is used to detect the tablet getting110		 * 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 of140	 * 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 between179	 * 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 221struct serdev_device_driver yt2_1380_fc_serdev_driver = {222	.probe = yt2_1380_fc_serdev_probe,223	.driver = {224		.name = KBUILD_MODNAME,225	},226};227 228static 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 235static 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 and270	 * 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 284out_serdev_device_remove:285	serdev_device_remove(serdev);286out_pinctrl_unregister_mappings:287	pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map);288	return ret;289}290 291static 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 299static 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 308static int __init yt2_1380_fc_module_init(void)309{310	int ret;311 312	/*313	 * serdev driver MUST be registered first because pdev driver calls314	 * 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}326module_init(yt2_1380_fc_module_init);327 328static 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}333module_exit(yt2_1380_fc_module_exit);334 335MODULE_ALIAS("platform:" YT2_1380_FC_PDEV_NAME);336MODULE_DESCRIPTION("Lenovo Yoga Tablet 2 1380 fast charge driver");337MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");338MODULE_LICENSE("GPL");339