18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * extcon_gpio.c - Single-state GPIO extcon driver based on extcon class 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2008 Google, Inc. 68c2ecf20Sopenharmony_ci * Author: Mike Lockwood <lockwood@android.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Modified by MyungJoo Ham <myungjoo.ham@samsung.com> to support extcon 98c2ecf20Sopenharmony_ci * (originally switch class is supported) 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/extcon-provider.h> 138c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 148c2ecf20Sopenharmony_ci#include <linux/init.h> 158c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 168c2ecf20Sopenharmony_ci#include <linux/kernel.h> 178c2ecf20Sopenharmony_ci#include <linux/module.h> 188c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 198c2ecf20Sopenharmony_ci#include <linux/slab.h> 208c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci/** 238c2ecf20Sopenharmony_ci * struct gpio_extcon_data - A simple GPIO-controlled extcon device state container. 248c2ecf20Sopenharmony_ci * @edev: Extcon device. 258c2ecf20Sopenharmony_ci * @work: Work fired by the interrupt. 268c2ecf20Sopenharmony_ci * @debounce_jiffies: Number of jiffies to wait for the GPIO to stabilize, from the debounce 278c2ecf20Sopenharmony_ci * value. 288c2ecf20Sopenharmony_ci * @gpiod: GPIO descriptor for this external connector. 298c2ecf20Sopenharmony_ci * @extcon_id: The unique id of specific external connector. 308c2ecf20Sopenharmony_ci * @debounce: Debounce time for GPIO IRQ in ms. 318c2ecf20Sopenharmony_ci * @check_on_resume: Boolean describing whether to check the state of gpio 328c2ecf20Sopenharmony_ci * while resuming from sleep. 338c2ecf20Sopenharmony_ci */ 348c2ecf20Sopenharmony_cistruct gpio_extcon_data { 358c2ecf20Sopenharmony_ci struct extcon_dev *edev; 368c2ecf20Sopenharmony_ci struct delayed_work work; 378c2ecf20Sopenharmony_ci unsigned long debounce_jiffies; 388c2ecf20Sopenharmony_ci struct gpio_desc *gpiod; 398c2ecf20Sopenharmony_ci unsigned int extcon_id; 408c2ecf20Sopenharmony_ci unsigned long debounce; 418c2ecf20Sopenharmony_ci bool check_on_resume; 428c2ecf20Sopenharmony_ci}; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic void gpio_extcon_work(struct work_struct *work) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci int state; 478c2ecf20Sopenharmony_ci struct gpio_extcon_data *data = 488c2ecf20Sopenharmony_ci container_of(to_delayed_work(work), struct gpio_extcon_data, 498c2ecf20Sopenharmony_ci work); 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci state = gpiod_get_value_cansleep(data->gpiod); 528c2ecf20Sopenharmony_ci extcon_set_state_sync(data->edev, data->extcon_id, state); 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic irqreturn_t gpio_irq_handler(int irq, void *dev_id) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci struct gpio_extcon_data *data = dev_id; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci queue_delayed_work(system_power_efficient_wq, &data->work, 608c2ecf20Sopenharmony_ci data->debounce_jiffies); 618c2ecf20Sopenharmony_ci return IRQ_HANDLED; 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic int gpio_extcon_probe(struct platform_device *pdev) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci struct gpio_extcon_data *data; 678c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 688c2ecf20Sopenharmony_ci unsigned long irq_flags; 698c2ecf20Sopenharmony_ci int irq; 708c2ecf20Sopenharmony_ci int ret; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci data = devm_kzalloc(dev, sizeof(struct gpio_extcon_data), GFP_KERNEL); 738c2ecf20Sopenharmony_ci if (!data) 748c2ecf20Sopenharmony_ci return -ENOMEM; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci /* 778c2ecf20Sopenharmony_ci * FIXME: extcon_id represents the unique identifier of external 788c2ecf20Sopenharmony_ci * connectors such as EXTCON_USB, EXTCON_DISP_HDMI and so on. extcon_id 798c2ecf20Sopenharmony_ci * is necessary to register the extcon device. But, it's not yet 808c2ecf20Sopenharmony_ci * developed to get the extcon id from device-tree or others. 818c2ecf20Sopenharmony_ci * On later, it have to be solved. 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_ci if (data->extcon_id > EXTCON_NONE) 848c2ecf20Sopenharmony_ci return -EINVAL; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci data->gpiod = devm_gpiod_get(dev, "extcon", GPIOD_IN); 878c2ecf20Sopenharmony_ci if (IS_ERR(data->gpiod)) 888c2ecf20Sopenharmony_ci return PTR_ERR(data->gpiod); 898c2ecf20Sopenharmony_ci irq = gpiod_to_irq(data->gpiod); 908c2ecf20Sopenharmony_ci if (irq <= 0) 918c2ecf20Sopenharmony_ci return irq; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci /* 948c2ecf20Sopenharmony_ci * It is unlikely that this is an acknowledged interrupt that goes 958c2ecf20Sopenharmony_ci * away after handling, what we are looking for are falling edges 968c2ecf20Sopenharmony_ci * if the signal is active low, and rising edges if the signal is 978c2ecf20Sopenharmony_ci * active high. 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_ci if (gpiod_is_active_low(data->gpiod)) 1008c2ecf20Sopenharmony_ci irq_flags = IRQF_TRIGGER_FALLING; 1018c2ecf20Sopenharmony_ci else 1028c2ecf20Sopenharmony_ci irq_flags = IRQF_TRIGGER_RISING; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci /* Allocate the memory of extcon devie and register extcon device */ 1058c2ecf20Sopenharmony_ci data->edev = devm_extcon_dev_allocate(dev, &data->extcon_id); 1068c2ecf20Sopenharmony_ci if (IS_ERR(data->edev)) { 1078c2ecf20Sopenharmony_ci dev_err(dev, "failed to allocate extcon device\n"); 1088c2ecf20Sopenharmony_ci return -ENOMEM; 1098c2ecf20Sopenharmony_ci } 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci ret = devm_extcon_dev_register(dev, data->edev); 1128c2ecf20Sopenharmony_ci if (ret < 0) 1138c2ecf20Sopenharmony_ci return ret; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&data->work, gpio_extcon_work); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci /* 1188c2ecf20Sopenharmony_ci * Request the interrupt of gpio to detect whether external connector 1198c2ecf20Sopenharmony_ci * is attached or detached. 1208c2ecf20Sopenharmony_ci */ 1218c2ecf20Sopenharmony_ci ret = devm_request_any_context_irq(dev, irq, 1228c2ecf20Sopenharmony_ci gpio_irq_handler, irq_flags, 1238c2ecf20Sopenharmony_ci pdev->name, data); 1248c2ecf20Sopenharmony_ci if (ret < 0) 1258c2ecf20Sopenharmony_ci return ret; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, data); 1288c2ecf20Sopenharmony_ci /* Perform initial detection */ 1298c2ecf20Sopenharmony_ci gpio_extcon_work(&data->work.work); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci return 0; 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic int gpio_extcon_remove(struct platform_device *pdev) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci struct gpio_extcon_data *data = platform_get_drvdata(pdev); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&data->work); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci return 0; 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 1448c2ecf20Sopenharmony_cistatic int gpio_extcon_resume(struct device *dev) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct gpio_extcon_data *data; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci data = dev_get_drvdata(dev); 1498c2ecf20Sopenharmony_ci if (data->check_on_resume) 1508c2ecf20Sopenharmony_ci queue_delayed_work(system_power_efficient_wq, 1518c2ecf20Sopenharmony_ci &data->work, data->debounce_jiffies); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci return 0; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci#endif 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic struct platform_driver gpio_extcon_driver = { 1608c2ecf20Sopenharmony_ci .probe = gpio_extcon_probe, 1618c2ecf20Sopenharmony_ci .remove = gpio_extcon_remove, 1628c2ecf20Sopenharmony_ci .driver = { 1638c2ecf20Sopenharmony_ci .name = "extcon-gpio", 1648c2ecf20Sopenharmony_ci .pm = &gpio_extcon_pm_ops, 1658c2ecf20Sopenharmony_ci }, 1668c2ecf20Sopenharmony_ci}; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cimodule_platform_driver(gpio_extcon_driver); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); 1718c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("GPIO extcon driver"); 1728c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 173