12e464ff0SAndy Shevchenko // SPDX-License-Identifier: GPL-2.0
22f556bdbSDavid Cohen /*
32f556bdbSDavid Cohen * Intel INT3496 ACPI device extcon driver
42f556bdbSDavid Cohen *
52f556bdbSDavid Cohen * Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com>
62f556bdbSDavid Cohen *
72f556bdbSDavid Cohen * Based on android x86 kernel code which is:
82f556bdbSDavid Cohen *
92f556bdbSDavid Cohen * Copyright (c) 2014, Intel Corporation.
102f556bdbSDavid Cohen * Author: David Cohen <david.a.cohen@linux.intel.com>
112f556bdbSDavid Cohen */
122f556bdbSDavid Cohen
132f556bdbSDavid Cohen #include <linux/acpi.h>
14f94a5becSMatti Vaittinen #include <linux/devm-helpers.h>
15176aa360SChanwoo Choi #include <linux/extcon-provider.h>
16c355bb1aSWolfram Sang #include <linux/gpio/consumer.h>
172f556bdbSDavid Cohen #include <linux/interrupt.h>
182f556bdbSDavid Cohen #include <linux/module.h>
192f556bdbSDavid Cohen #include <linux/platform_device.h>
20*4c018cc8SHans de Goede #include <linux/regulator/consumer.h>
212f556bdbSDavid Cohen
222f556bdbSDavid Cohen #define INT3496_GPIO_USB_ID 0
232f556bdbSDavid Cohen #define INT3496_GPIO_VBUS_EN 1
242f556bdbSDavid Cohen #define INT3496_GPIO_USB_MUX 2
252f556bdbSDavid Cohen #define DEBOUNCE_TIME msecs_to_jiffies(50)
262f556bdbSDavid Cohen
272f556bdbSDavid Cohen struct int3496_data {
282f556bdbSDavid Cohen struct device *dev;
292f556bdbSDavid Cohen struct extcon_dev *edev;
302f556bdbSDavid Cohen struct delayed_work work;
312f556bdbSDavid Cohen struct gpio_desc *gpio_usb_id;
322f556bdbSDavid Cohen struct gpio_desc *gpio_vbus_en;
332f556bdbSDavid Cohen struct gpio_desc *gpio_usb_mux;
34*4c018cc8SHans de Goede struct regulator *vbus_boost;
352f556bdbSDavid Cohen int usb_id_irq;
36*4c018cc8SHans de Goede bool vbus_boost_enabled;
372f556bdbSDavid Cohen };
382f556bdbSDavid Cohen
392f556bdbSDavid Cohen static const unsigned int int3496_cable[] = {
402f556bdbSDavid Cohen EXTCON_USB_HOST,
412f556bdbSDavid Cohen EXTCON_NONE,
422f556bdbSDavid Cohen };
432f556bdbSDavid Cohen
448cb2cbaeSAndy Shevchenko static const struct acpi_gpio_params id_gpios = { INT3496_GPIO_USB_ID, 0, false };
458cb2cbaeSAndy Shevchenko static const struct acpi_gpio_params vbus_gpios = { INT3496_GPIO_VBUS_EN, 0, false };
468cb2cbaeSAndy Shevchenko static const struct acpi_gpio_params mux_gpios = { INT3496_GPIO_USB_MUX, 0, false };
478cb2cbaeSAndy Shevchenko
488cb2cbaeSAndy Shevchenko static const struct acpi_gpio_mapping acpi_int3496_default_gpios[] = {
49eca0f13cSAndy Shevchenko /*
50eca0f13cSAndy Shevchenko * Some platforms have a bug in ACPI GPIO description making IRQ
51eca0f13cSAndy Shevchenko * GPIO to be output only. Ask the GPIO core to ignore this limit.
52eca0f13cSAndy Shevchenko */
53eca0f13cSAndy Shevchenko { "id-gpios", &id_gpios, 1, ACPI_GPIO_QUIRK_NO_IO_RESTRICTION },
548cb2cbaeSAndy Shevchenko { "vbus-gpios", &vbus_gpios, 1 },
558cb2cbaeSAndy Shevchenko { "mux-gpios", &mux_gpios, 1 },
568cb2cbaeSAndy Shevchenko { },
578cb2cbaeSAndy Shevchenko };
588cb2cbaeSAndy Shevchenko
int3496_set_vbus_boost(struct int3496_data * data,bool enable)59*4c018cc8SHans de Goede static void int3496_set_vbus_boost(struct int3496_data *data, bool enable)
60*4c018cc8SHans de Goede {
61*4c018cc8SHans de Goede int ret;
62*4c018cc8SHans de Goede
63*4c018cc8SHans de Goede if (IS_ERR_OR_NULL(data->vbus_boost))
64*4c018cc8SHans de Goede return;
65*4c018cc8SHans de Goede
66*4c018cc8SHans de Goede if (data->vbus_boost_enabled == enable)
67*4c018cc8SHans de Goede return;
68*4c018cc8SHans de Goede
69*4c018cc8SHans de Goede if (enable)
70*4c018cc8SHans de Goede ret = regulator_enable(data->vbus_boost);
71*4c018cc8SHans de Goede else
72*4c018cc8SHans de Goede ret = regulator_disable(data->vbus_boost);
73*4c018cc8SHans de Goede
74*4c018cc8SHans de Goede if (ret == 0)
75*4c018cc8SHans de Goede data->vbus_boost_enabled = enable;
76*4c018cc8SHans de Goede else
77*4c018cc8SHans de Goede dev_err(data->dev, "Error updating Vbus boost regulator: %d\n", ret);
78*4c018cc8SHans de Goede }
79*4c018cc8SHans de Goede
int3496_do_usb_id(struct work_struct * work)802f556bdbSDavid Cohen static void int3496_do_usb_id(struct work_struct *work)
812f556bdbSDavid Cohen {
822f556bdbSDavid Cohen struct int3496_data *data =
832f556bdbSDavid Cohen container_of(work, struct int3496_data, work.work);
842f556bdbSDavid Cohen int id = gpiod_get_value_cansleep(data->gpio_usb_id);
852f556bdbSDavid Cohen
862f556bdbSDavid Cohen /* id == 1: PERIPHERAL, id == 0: HOST */
872f556bdbSDavid Cohen dev_dbg(data->dev, "Connected %s cable\n", id ? "PERIPHERAL" : "HOST");
882f556bdbSDavid Cohen
892f556bdbSDavid Cohen /*
902f556bdbSDavid Cohen * Peripheral: set USB mux to peripheral and disable VBUS
912f556bdbSDavid Cohen * Host: set USB mux to host and enable VBUS
922f556bdbSDavid Cohen */
932f556bdbSDavid Cohen if (!IS_ERR(data->gpio_usb_mux))
942f556bdbSDavid Cohen gpiod_direction_output(data->gpio_usb_mux, id);
952f556bdbSDavid Cohen
962f556bdbSDavid Cohen if (!IS_ERR(data->gpio_vbus_en))
972f556bdbSDavid Cohen gpiod_direction_output(data->gpio_vbus_en, !id);
98*4c018cc8SHans de Goede else
99*4c018cc8SHans de Goede int3496_set_vbus_boost(data, !id);
1002f556bdbSDavid Cohen
1012f556bdbSDavid Cohen extcon_set_state_sync(data->edev, EXTCON_USB_HOST, !id);
1022f556bdbSDavid Cohen }
1032f556bdbSDavid Cohen
int3496_thread_isr(int irq,void * priv)1042f556bdbSDavid Cohen static irqreturn_t int3496_thread_isr(int irq, void *priv)
1052f556bdbSDavid Cohen {
1062f556bdbSDavid Cohen struct int3496_data *data = priv;
1072f556bdbSDavid Cohen
1082f556bdbSDavid Cohen /* Let the pin settle before processing it */
1092f556bdbSDavid Cohen mod_delayed_work(system_wq, &data->work, DEBOUNCE_TIME);
1102f556bdbSDavid Cohen
1112f556bdbSDavid Cohen return IRQ_HANDLED;
1122f556bdbSDavid Cohen }
1132f556bdbSDavid Cohen
int3496_probe(struct platform_device * pdev)1142f556bdbSDavid Cohen static int int3496_probe(struct platform_device *pdev)
1152f556bdbSDavid Cohen {
1162f556bdbSDavid Cohen struct device *dev = &pdev->dev;
1172f556bdbSDavid Cohen struct int3496_data *data;
1182f556bdbSDavid Cohen int ret;
1192f556bdbSDavid Cohen
120c26aef6dSHans de Goede if (has_acpi_companion(dev)) {
1211f4be247SAndy Shevchenko ret = devm_acpi_dev_add_driver_gpios(dev, acpi_int3496_default_gpios);
1228cb2cbaeSAndy Shevchenko if (ret) {
1238cb2cbaeSAndy Shevchenko dev_err(dev, "can't add GPIO ACPI mapping\n");
1248cb2cbaeSAndy Shevchenko return ret;
1258cb2cbaeSAndy Shevchenko }
126c26aef6dSHans de Goede }
1278cb2cbaeSAndy Shevchenko
1282f556bdbSDavid Cohen data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
1292f556bdbSDavid Cohen if (!data)
1302f556bdbSDavid Cohen return -ENOMEM;
1312f556bdbSDavid Cohen
1322f556bdbSDavid Cohen data->dev = dev;
133f94a5becSMatti Vaittinen ret = devm_delayed_work_autocancel(dev, &data->work, int3496_do_usb_id);
134f94a5becSMatti Vaittinen if (ret)
135f94a5becSMatti Vaittinen return ret;
1362f556bdbSDavid Cohen
13712514f66SHans de Goede data->gpio_usb_id =
13812514f66SHans de Goede devm_gpiod_get(dev, "id", GPIOD_IN | GPIOD_FLAGS_BIT_NONEXCLUSIVE);
1392f556bdbSDavid Cohen if (IS_ERR(data->gpio_usb_id)) {
1402f556bdbSDavid Cohen ret = PTR_ERR(data->gpio_usb_id);
1412f556bdbSDavid Cohen dev_err(dev, "can't request USB ID GPIO: %d\n", ret);
1422f556bdbSDavid Cohen return ret;
1432f556bdbSDavid Cohen }
1442f556bdbSDavid Cohen
1452f556bdbSDavid Cohen data->usb_id_irq = gpiod_to_irq(data->gpio_usb_id);
146bafa687dSAndy Shevchenko if (data->usb_id_irq < 0) {
1472f556bdbSDavid Cohen dev_err(dev, "can't get USB ID IRQ: %d\n", data->usb_id_irq);
148bafa687dSAndy Shevchenko return data->usb_id_irq;
1492f556bdbSDavid Cohen }
1502f556bdbSDavid Cohen
151408c5b41SHans de Goede data->gpio_vbus_en = devm_gpiod_get(dev, "vbus", GPIOD_ASIS);
152*4c018cc8SHans de Goede if (IS_ERR(data->gpio_vbus_en)) {
153019c34aaSHans de Goede dev_dbg(dev, "can't request VBUS EN GPIO\n");
154*4c018cc8SHans de Goede data->vbus_boost = devm_regulator_get_optional(dev, "vbus");
155*4c018cc8SHans de Goede }
1562f556bdbSDavid Cohen
157408c5b41SHans de Goede data->gpio_usb_mux = devm_gpiod_get(dev, "mux", GPIOD_ASIS);
1582f556bdbSDavid Cohen if (IS_ERR(data->gpio_usb_mux))
159019c34aaSHans de Goede dev_dbg(dev, "can't request USB MUX GPIO\n");
1602f556bdbSDavid Cohen
1612f556bdbSDavid Cohen /* register extcon device */
1622f556bdbSDavid Cohen data->edev = devm_extcon_dev_allocate(dev, int3496_cable);
1632f556bdbSDavid Cohen if (IS_ERR(data->edev))
1642f556bdbSDavid Cohen return -ENOMEM;
1652f556bdbSDavid Cohen
1662f556bdbSDavid Cohen ret = devm_extcon_dev_register(dev, data->edev);
1672f556bdbSDavid Cohen if (ret < 0) {
1682f556bdbSDavid Cohen dev_err(dev, "can't register extcon device: %d\n", ret);
1692f556bdbSDavid Cohen return ret;
1702f556bdbSDavid Cohen }
1712f556bdbSDavid Cohen
1722f556bdbSDavid Cohen ret = devm_request_threaded_irq(dev, data->usb_id_irq,
1732f556bdbSDavid Cohen NULL, int3496_thread_isr,
1742f556bdbSDavid Cohen IRQF_SHARED | IRQF_ONESHOT |
1752f556bdbSDavid Cohen IRQF_TRIGGER_RISING |
1762f556bdbSDavid Cohen IRQF_TRIGGER_FALLING,
1772f556bdbSDavid Cohen dev_name(dev), data);
1782f556bdbSDavid Cohen if (ret < 0) {
1792f556bdbSDavid Cohen dev_err(dev, "can't request IRQ for USB ID GPIO: %d\n", ret);
1802f556bdbSDavid Cohen return ret;
1812f556bdbSDavid Cohen }
1822f556bdbSDavid Cohen
1830434352dSHans de Goede /* process id-pin so that we start with the right status */
1842f556bdbSDavid Cohen queue_delayed_work(system_wq, &data->work, 0);
1850434352dSHans de Goede flush_delayed_work(&data->work);
1862f556bdbSDavid Cohen
1872f556bdbSDavid Cohen platform_set_drvdata(pdev, data);
1882f556bdbSDavid Cohen
1892f556bdbSDavid Cohen return 0;
1902f556bdbSDavid Cohen }
1912f556bdbSDavid Cohen
192ff890bc0SArvind Yadav static const struct acpi_device_id int3496_acpi_match[] = {
1932f556bdbSDavid Cohen { "INT3496" },
1942f556bdbSDavid Cohen { }
1952f556bdbSDavid Cohen };
1962f556bdbSDavid Cohen MODULE_DEVICE_TABLE(acpi, int3496_acpi_match);
1972f556bdbSDavid Cohen
198c26aef6dSHans de Goede static const struct platform_device_id int3496_ids[] = {
199c26aef6dSHans de Goede { .name = "intel-int3496" },
200c26aef6dSHans de Goede {},
201c26aef6dSHans de Goede };
202c26aef6dSHans de Goede MODULE_DEVICE_TABLE(platform, int3496_ids);
203c26aef6dSHans de Goede
2042f556bdbSDavid Cohen static struct platform_driver int3496_driver = {
2052f556bdbSDavid Cohen .driver = {
2062f556bdbSDavid Cohen .name = "intel-int3496",
2072f556bdbSDavid Cohen .acpi_match_table = int3496_acpi_match,
2082f556bdbSDavid Cohen },
2092f556bdbSDavid Cohen .probe = int3496_probe,
210c26aef6dSHans de Goede .id_table = int3496_ids,
2112f556bdbSDavid Cohen };
2122f556bdbSDavid Cohen
2132f556bdbSDavid Cohen module_platform_driver(int3496_driver);
2142f556bdbSDavid Cohen
2152f556bdbSDavid Cohen MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
2162f556bdbSDavid Cohen MODULE_DESCRIPTION("Intel INT3496 ACPI device extcon driver");
2172e464ff0SAndy Shevchenko MODULE_LICENSE("GPL v2");
218