162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Intel INT3496 ACPI device extcon driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Based on android x86 kernel code which is: 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Copyright (c) 2014, Intel Corporation. 1062306a36Sopenharmony_ci * Author: David Cohen <david.a.cohen@linux.intel.com> 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/acpi.h> 1462306a36Sopenharmony_ci#include <linux/devm-helpers.h> 1562306a36Sopenharmony_ci#include <linux/extcon-provider.h> 1662306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1762306a36Sopenharmony_ci#include <linux/interrupt.h> 1862306a36Sopenharmony_ci#include <linux/module.h> 1962306a36Sopenharmony_ci#include <linux/platform_device.h> 2062306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define INT3496_GPIO_USB_ID 0 2362306a36Sopenharmony_ci#define INT3496_GPIO_VBUS_EN 1 2462306a36Sopenharmony_ci#define INT3496_GPIO_USB_MUX 2 2562306a36Sopenharmony_ci#define DEBOUNCE_TIME msecs_to_jiffies(50) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistruct int3496_data { 2862306a36Sopenharmony_ci struct device *dev; 2962306a36Sopenharmony_ci struct extcon_dev *edev; 3062306a36Sopenharmony_ci struct delayed_work work; 3162306a36Sopenharmony_ci struct gpio_desc *gpio_usb_id; 3262306a36Sopenharmony_ci struct gpio_desc *gpio_vbus_en; 3362306a36Sopenharmony_ci struct gpio_desc *gpio_usb_mux; 3462306a36Sopenharmony_ci struct regulator *vbus_boost; 3562306a36Sopenharmony_ci int usb_id_irq; 3662306a36Sopenharmony_ci bool vbus_boost_enabled; 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic const unsigned int int3496_cable[] = { 4062306a36Sopenharmony_ci EXTCON_USB_HOST, 4162306a36Sopenharmony_ci EXTCON_NONE, 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic const struct acpi_gpio_params id_gpios = { INT3496_GPIO_USB_ID, 0, false }; 4562306a36Sopenharmony_cistatic const struct acpi_gpio_params vbus_gpios = { INT3496_GPIO_VBUS_EN, 0, false }; 4662306a36Sopenharmony_cistatic const struct acpi_gpio_params mux_gpios = { INT3496_GPIO_USB_MUX, 0, false }; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic const struct acpi_gpio_mapping acpi_int3496_default_gpios[] = { 4962306a36Sopenharmony_ci /* 5062306a36Sopenharmony_ci * Some platforms have a bug in ACPI GPIO description making IRQ 5162306a36Sopenharmony_ci * GPIO to be output only. Ask the GPIO core to ignore this limit. 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_ci { "id-gpios", &id_gpios, 1, ACPI_GPIO_QUIRK_NO_IO_RESTRICTION }, 5462306a36Sopenharmony_ci { "vbus-gpios", &vbus_gpios, 1 }, 5562306a36Sopenharmony_ci { "mux-gpios", &mux_gpios, 1 }, 5662306a36Sopenharmony_ci { }, 5762306a36Sopenharmony_ci}; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic void int3496_set_vbus_boost(struct int3496_data *data, bool enable) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci int ret; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (IS_ERR_OR_NULL(data->vbus_boost)) 6462306a36Sopenharmony_ci return; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci if (data->vbus_boost_enabled == enable) 6762306a36Sopenharmony_ci return; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci if (enable) 7062306a36Sopenharmony_ci ret = regulator_enable(data->vbus_boost); 7162306a36Sopenharmony_ci else 7262306a36Sopenharmony_ci ret = regulator_disable(data->vbus_boost); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci if (ret == 0) 7562306a36Sopenharmony_ci data->vbus_boost_enabled = enable; 7662306a36Sopenharmony_ci else 7762306a36Sopenharmony_ci dev_err(data->dev, "Error updating Vbus boost regulator: %d\n", ret); 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic void int3496_do_usb_id(struct work_struct *work) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci struct int3496_data *data = 8362306a36Sopenharmony_ci container_of(work, struct int3496_data, work.work); 8462306a36Sopenharmony_ci int id = gpiod_get_value_cansleep(data->gpio_usb_id); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci /* id == 1: PERIPHERAL, id == 0: HOST */ 8762306a36Sopenharmony_ci dev_dbg(data->dev, "Connected %s cable\n", id ? "PERIPHERAL" : "HOST"); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci /* 9062306a36Sopenharmony_ci * Peripheral: set USB mux to peripheral and disable VBUS 9162306a36Sopenharmony_ci * Host: set USB mux to host and enable VBUS 9262306a36Sopenharmony_ci */ 9362306a36Sopenharmony_ci if (!IS_ERR(data->gpio_usb_mux)) 9462306a36Sopenharmony_ci gpiod_direction_output(data->gpio_usb_mux, id); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci if (!IS_ERR(data->gpio_vbus_en)) 9762306a36Sopenharmony_ci gpiod_direction_output(data->gpio_vbus_en, !id); 9862306a36Sopenharmony_ci else 9962306a36Sopenharmony_ci int3496_set_vbus_boost(data, !id); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci extcon_set_state_sync(data->edev, EXTCON_USB_HOST, !id); 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic irqreturn_t int3496_thread_isr(int irq, void *priv) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct int3496_data *data = priv; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci /* Let the pin settle before processing it */ 10962306a36Sopenharmony_ci mod_delayed_work(system_wq, &data->work, DEBOUNCE_TIME); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci return IRQ_HANDLED; 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic int int3496_probe(struct platform_device *pdev) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci struct device *dev = &pdev->dev; 11762306a36Sopenharmony_ci struct int3496_data *data; 11862306a36Sopenharmony_ci int ret; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (has_acpi_companion(dev)) { 12162306a36Sopenharmony_ci ret = devm_acpi_dev_add_driver_gpios(dev, acpi_int3496_default_gpios); 12262306a36Sopenharmony_ci if (ret) { 12362306a36Sopenharmony_ci dev_err(dev, "can't add GPIO ACPI mapping\n"); 12462306a36Sopenharmony_ci return ret; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 12962306a36Sopenharmony_ci if (!data) 13062306a36Sopenharmony_ci return -ENOMEM; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci data->dev = dev; 13362306a36Sopenharmony_ci ret = devm_delayed_work_autocancel(dev, &data->work, int3496_do_usb_id); 13462306a36Sopenharmony_ci if (ret) 13562306a36Sopenharmony_ci return ret; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci data->gpio_usb_id = 13862306a36Sopenharmony_ci devm_gpiod_get(dev, "id", GPIOD_IN | GPIOD_FLAGS_BIT_NONEXCLUSIVE); 13962306a36Sopenharmony_ci if (IS_ERR(data->gpio_usb_id)) { 14062306a36Sopenharmony_ci ret = PTR_ERR(data->gpio_usb_id); 14162306a36Sopenharmony_ci dev_err(dev, "can't request USB ID GPIO: %d\n", ret); 14262306a36Sopenharmony_ci return ret; 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci data->usb_id_irq = gpiod_to_irq(data->gpio_usb_id); 14662306a36Sopenharmony_ci if (data->usb_id_irq < 0) { 14762306a36Sopenharmony_ci dev_err(dev, "can't get USB ID IRQ: %d\n", data->usb_id_irq); 14862306a36Sopenharmony_ci return data->usb_id_irq; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci data->gpio_vbus_en = devm_gpiod_get(dev, "vbus", GPIOD_ASIS); 15262306a36Sopenharmony_ci if (IS_ERR(data->gpio_vbus_en)) { 15362306a36Sopenharmony_ci dev_dbg(dev, "can't request VBUS EN GPIO\n"); 15462306a36Sopenharmony_ci data->vbus_boost = devm_regulator_get_optional(dev, "vbus"); 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci data->gpio_usb_mux = devm_gpiod_get(dev, "mux", GPIOD_ASIS); 15862306a36Sopenharmony_ci if (IS_ERR(data->gpio_usb_mux)) 15962306a36Sopenharmony_ci dev_dbg(dev, "can't request USB MUX GPIO\n"); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci /* register extcon device */ 16262306a36Sopenharmony_ci data->edev = devm_extcon_dev_allocate(dev, int3496_cable); 16362306a36Sopenharmony_ci if (IS_ERR(data->edev)) 16462306a36Sopenharmony_ci return -ENOMEM; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci ret = devm_extcon_dev_register(dev, data->edev); 16762306a36Sopenharmony_ci if (ret < 0) { 16862306a36Sopenharmony_ci dev_err(dev, "can't register extcon device: %d\n", ret); 16962306a36Sopenharmony_ci return ret; 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci ret = devm_request_threaded_irq(dev, data->usb_id_irq, 17362306a36Sopenharmony_ci NULL, int3496_thread_isr, 17462306a36Sopenharmony_ci IRQF_SHARED | IRQF_ONESHOT | 17562306a36Sopenharmony_ci IRQF_TRIGGER_RISING | 17662306a36Sopenharmony_ci IRQF_TRIGGER_FALLING, 17762306a36Sopenharmony_ci dev_name(dev), data); 17862306a36Sopenharmony_ci if (ret < 0) { 17962306a36Sopenharmony_ci dev_err(dev, "can't request IRQ for USB ID GPIO: %d\n", ret); 18062306a36Sopenharmony_ci return ret; 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci /* process id-pin so that we start with the right status */ 18462306a36Sopenharmony_ci queue_delayed_work(system_wq, &data->work, 0); 18562306a36Sopenharmony_ci flush_delayed_work(&data->work); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci platform_set_drvdata(pdev, data); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return 0; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic const struct acpi_device_id int3496_acpi_match[] = { 19362306a36Sopenharmony_ci { "INT3496" }, 19462306a36Sopenharmony_ci { } 19562306a36Sopenharmony_ci}; 19662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, int3496_acpi_match); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic const struct platform_device_id int3496_ids[] = { 19962306a36Sopenharmony_ci { .name = "intel-int3496" }, 20062306a36Sopenharmony_ci {}, 20162306a36Sopenharmony_ci}; 20262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(platform, int3496_ids); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic struct platform_driver int3496_driver = { 20562306a36Sopenharmony_ci .driver = { 20662306a36Sopenharmony_ci .name = "intel-int3496", 20762306a36Sopenharmony_ci .acpi_match_table = int3496_acpi_match, 20862306a36Sopenharmony_ci }, 20962306a36Sopenharmony_ci .probe = int3496_probe, 21062306a36Sopenharmony_ci .id_table = int3496_ids, 21162306a36Sopenharmony_ci}; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cimodule_platform_driver(int3496_driver); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 21662306a36Sopenharmony_ciMODULE_DESCRIPTION("Intel INT3496 ACPI device extcon driver"); 21762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 218