1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Intel INT3496 ACPI device extcon driver
4  *
5  * Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com>
6  *
7  * Based on android x86 kernel code which is:
8  *
9  * Copyright (c) 2014, Intel Corporation.
10  * Author: David Cohen <david.a.cohen@linux.intel.com>
11  */
12 
13 #include <linux/acpi.h>
14 #include <linux/devm-helpers.h>
15 #include <linux/extcon-provider.h>
16 #include <linux/gpio/consumer.h>
17 #include <linux/interrupt.h>
18 #include <linux/module.h>
19 #include <linux/platform_device.h>
20 #include <linux/regulator/consumer.h>
21 
22 #define INT3496_GPIO_USB_ID	0
23 #define INT3496_GPIO_VBUS_EN	1
24 #define INT3496_GPIO_USB_MUX	2
25 #define DEBOUNCE_TIME		msecs_to_jiffies(50)
26 
27 struct int3496_data {
28 	struct device *dev;
29 	struct extcon_dev *edev;
30 	struct delayed_work work;
31 	struct gpio_desc *gpio_usb_id;
32 	struct gpio_desc *gpio_vbus_en;
33 	struct gpio_desc *gpio_usb_mux;
34 	struct regulator *vbus_boost;
35 	int usb_id_irq;
36 	bool vbus_boost_enabled;
37 };
38 
39 static const unsigned int int3496_cable[] = {
40 	EXTCON_USB_HOST,
41 	EXTCON_NONE,
42 };
43 
44 static const struct acpi_gpio_params id_gpios = { INT3496_GPIO_USB_ID, 0, false };
45 static const struct acpi_gpio_params vbus_gpios = { INT3496_GPIO_VBUS_EN, 0, false };
46 static const struct acpi_gpio_params mux_gpios = { INT3496_GPIO_USB_MUX, 0, false };
47 
48 static const struct acpi_gpio_mapping acpi_int3496_default_gpios[] = {
49 	/*
50 	 * Some platforms have a bug in ACPI GPIO description making IRQ
51 	 * GPIO to be output only. Ask the GPIO core to ignore this limit.
52 	 */
53 	{ "id-gpios", &id_gpios, 1, ACPI_GPIO_QUIRK_NO_IO_RESTRICTION },
54 	{ "vbus-gpios", &vbus_gpios, 1 },
55 	{ "mux-gpios", &mux_gpios, 1 },
56 	{ },
57 };
58 
59 static void int3496_set_vbus_boost(struct int3496_data *data, bool enable)
60 {
61 	int ret;
62 
63 	if (IS_ERR_OR_NULL(data->vbus_boost))
64 		return;
65 
66 	if (data->vbus_boost_enabled == enable)
67 		return;
68 
69 	if (enable)
70 		ret = regulator_enable(data->vbus_boost);
71 	else
72 		ret = regulator_disable(data->vbus_boost);
73 
74 	if (ret == 0)
75 		data->vbus_boost_enabled = enable;
76 	else
77 		dev_err(data->dev, "Error updating Vbus boost regulator: %d\n", ret);
78 }
79 
80 static void int3496_do_usb_id(struct work_struct *work)
81 {
82 	struct int3496_data *data =
83 		container_of(work, struct int3496_data, work.work);
84 	int id = gpiod_get_value_cansleep(data->gpio_usb_id);
85 
86 	/* id == 1: PERIPHERAL, id == 0: HOST */
87 	dev_dbg(data->dev, "Connected %s cable\n", id ? "PERIPHERAL" : "HOST");
88 
89 	/*
90 	 * Peripheral: set USB mux to peripheral and disable VBUS
91 	 * Host: set USB mux to host and enable VBUS
92 	 */
93 	if (!IS_ERR(data->gpio_usb_mux))
94 		gpiod_direction_output(data->gpio_usb_mux, id);
95 
96 	if (!IS_ERR(data->gpio_vbus_en))
97 		gpiod_direction_output(data->gpio_vbus_en, !id);
98 	else
99 		int3496_set_vbus_boost(data, !id);
100 
101 	extcon_set_state_sync(data->edev, EXTCON_USB_HOST, !id);
102 }
103 
104 static irqreturn_t int3496_thread_isr(int irq, void *priv)
105 {
106 	struct int3496_data *data = priv;
107 
108 	/* Let the pin settle before processing it */
109 	mod_delayed_work(system_wq, &data->work, DEBOUNCE_TIME);
110 
111 	return IRQ_HANDLED;
112 }
113 
114 static int int3496_probe(struct platform_device *pdev)
115 {
116 	struct device *dev = &pdev->dev;
117 	struct int3496_data *data;
118 	int ret;
119 
120 	if (has_acpi_companion(dev)) {
121 		ret = devm_acpi_dev_add_driver_gpios(dev, acpi_int3496_default_gpios);
122 		if (ret) {
123 			dev_err(dev, "can't add GPIO ACPI mapping\n");
124 			return ret;
125 		}
126 	}
127 
128 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
129 	if (!data)
130 		return -ENOMEM;
131 
132 	data->dev = dev;
133 	ret = devm_delayed_work_autocancel(dev, &data->work, int3496_do_usb_id);
134 	if (ret)
135 		return ret;
136 
137 	data->gpio_usb_id =
138 		devm_gpiod_get(dev, "id", GPIOD_IN | GPIOD_FLAGS_BIT_NONEXCLUSIVE);
139 	if (IS_ERR(data->gpio_usb_id)) {
140 		ret = PTR_ERR(data->gpio_usb_id);
141 		dev_err(dev, "can't request USB ID GPIO: %d\n", ret);
142 		return ret;
143 	}
144 
145 	data->usb_id_irq = gpiod_to_irq(data->gpio_usb_id);
146 	if (data->usb_id_irq < 0) {
147 		dev_err(dev, "can't get USB ID IRQ: %d\n", data->usb_id_irq);
148 		return data->usb_id_irq;
149 	}
150 
151 	data->gpio_vbus_en = devm_gpiod_get(dev, "vbus", GPIOD_ASIS);
152 	if (IS_ERR(data->gpio_vbus_en)) {
153 		dev_dbg(dev, "can't request VBUS EN GPIO\n");
154 		data->vbus_boost = devm_regulator_get_optional(dev, "vbus");
155 	}
156 
157 	data->gpio_usb_mux = devm_gpiod_get(dev, "mux", GPIOD_ASIS);
158 	if (IS_ERR(data->gpio_usb_mux))
159 		dev_dbg(dev, "can't request USB MUX GPIO\n");
160 
161 	/* register extcon device */
162 	data->edev = devm_extcon_dev_allocate(dev, int3496_cable);
163 	if (IS_ERR(data->edev))
164 		return -ENOMEM;
165 
166 	ret = devm_extcon_dev_register(dev, data->edev);
167 	if (ret < 0) {
168 		dev_err(dev, "can't register extcon device: %d\n", ret);
169 		return ret;
170 	}
171 
172 	ret = devm_request_threaded_irq(dev, data->usb_id_irq,
173 					NULL, int3496_thread_isr,
174 					IRQF_SHARED | IRQF_ONESHOT |
175 					IRQF_TRIGGER_RISING |
176 					IRQF_TRIGGER_FALLING,
177 					dev_name(dev), data);
178 	if (ret < 0) {
179 		dev_err(dev, "can't request IRQ for USB ID GPIO: %d\n", ret);
180 		return ret;
181 	}
182 
183 	/* process id-pin so that we start with the right status */
184 	queue_delayed_work(system_wq, &data->work, 0);
185 	flush_delayed_work(&data->work);
186 
187 	platform_set_drvdata(pdev, data);
188 
189 	return 0;
190 }
191 
192 static const struct acpi_device_id int3496_acpi_match[] = {
193 	{ "INT3496" },
194 	{ }
195 };
196 MODULE_DEVICE_TABLE(acpi, int3496_acpi_match);
197 
198 static const struct platform_device_id int3496_ids[] = {
199 	{ .name = "intel-int3496" },
200 	{},
201 };
202 MODULE_DEVICE_TABLE(platform, int3496_ids);
203 
204 static struct platform_driver int3496_driver = {
205 	.driver = {
206 		.name = "intel-int3496",
207 		.acpi_match_table = int3496_acpi_match,
208 	},
209 	.probe = int3496_probe,
210 	.id_table = int3496_ids,
211 };
212 
213 module_platform_driver(int3496_driver);
214 
215 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
216 MODULE_DESCRIPTION("Intel INT3496 ACPI device extcon driver");
217 MODULE_LICENSE("GPL v2");
218