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(ptr: data->vbus_boost)) |
64 | return; |
65 | |
66 | if (data->vbus_boost_enabled == enable) |
67 | return; |
68 | |
69 | if (enable) |
70 | ret = regulator_enable(regulator: data->vbus_boost); |
71 | else |
72 | ret = regulator_disable(regulator: 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(desc: 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(ptr: data->gpio_usb_mux)) |
94 | gpiod_direction_output(desc: data->gpio_usb_mux, value: id); |
95 | |
96 | if (!IS_ERR(ptr: data->gpio_vbus_en)) |
97 | gpiod_direction_output(desc: data->gpio_vbus_en, value: !id); |
98 | else |
99 | int3496_set_vbus_boost(data, enable: !id); |
100 | |
101 | extcon_set_state_sync(edev: data->edev, EXTCON_USB_HOST, state: !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(wq: system_wq, dwork: &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, gpios: 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, size: sizeof(*data), GFP_KERNEL); |
129 | if (!data) |
130 | return -ENOMEM; |
131 | |
132 | data->dev = dev; |
133 | ret = devm_delayed_work_autocancel(dev, w: &data->work, worker: int3496_do_usb_id); |
134 | if (ret) |
135 | return ret; |
136 | |
137 | data->gpio_usb_id = |
138 | devm_gpiod_get(dev, con_id: "id" , flags: GPIOD_IN | GPIOD_FLAGS_BIT_NONEXCLUSIVE); |
139 | if (IS_ERR(ptr: data->gpio_usb_id)) { |
140 | ret = PTR_ERR(ptr: 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(desc: 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, con_id: "vbus" , flags: GPIOD_ASIS); |
152 | if (IS_ERR(ptr: data->gpio_vbus_en)) { |
153 | dev_dbg(dev, "can't request VBUS EN GPIO\n" ); |
154 | data->vbus_boost = devm_regulator_get_optional(dev, id: "vbus" ); |
155 | } |
156 | |
157 | data->gpio_usb_mux = devm_gpiod_get(dev, con_id: "mux" , flags: GPIOD_ASIS); |
158 | if (IS_ERR(ptr: 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, cable: int3496_cable); |
163 | if (IS_ERR(ptr: data->edev)) |
164 | return -ENOMEM; |
165 | |
166 | ret = devm_extcon_dev_register(dev, edev: 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, irq: data->usb_id_irq, |
173 | NULL, thread_fn: int3496_thread_isr, |
174 | IRQF_SHARED | IRQF_ONESHOT | |
175 | IRQF_TRIGGER_RISING | |
176 | IRQF_TRIGGER_FALLING, |
177 | devname: dev_name(dev), dev_id: 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(wq: system_wq, dwork: &data->work, delay: 0); |
185 | flush_delayed_work(dwork: &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 | |