extcon-gpio.c (4503B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * extcon_gpio.c - Single-state GPIO extcon driver based on extcon class 4 * 5 * Copyright (C) 2008 Google, Inc. 6 * Author: Mike Lockwood <lockwood@android.com> 7 * 8 * Modified by MyungJoo Ham <myungjoo.ham@samsung.com> to support extcon 9 * (originally switch class is supported) 10 */ 11 12#include <linux/devm-helpers.h> 13#include <linux/extcon-provider.h> 14#include <linux/gpio/consumer.h> 15#include <linux/init.h> 16#include <linux/interrupt.h> 17#include <linux/kernel.h> 18#include <linux/module.h> 19#include <linux/platform_device.h> 20#include <linux/slab.h> 21#include <linux/workqueue.h> 22 23/** 24 * struct gpio_extcon_data - A simple GPIO-controlled extcon device state container. 25 * @edev: Extcon device. 26 * @work: Work fired by the interrupt. 27 * @debounce_jiffies: Number of jiffies to wait for the GPIO to stabilize, from the debounce 28 * value. 29 * @gpiod: GPIO descriptor for this external connector. 30 * @extcon_id: The unique id of specific external connector. 31 * @debounce: Debounce time for GPIO IRQ in ms. 32 * @check_on_resume: Boolean describing whether to check the state of gpio 33 * while resuming from sleep. 34 */ 35struct gpio_extcon_data { 36 struct extcon_dev *edev; 37 struct delayed_work work; 38 unsigned long debounce_jiffies; 39 struct gpio_desc *gpiod; 40 unsigned int extcon_id; 41 unsigned long debounce; 42 bool check_on_resume; 43}; 44 45static void gpio_extcon_work(struct work_struct *work) 46{ 47 int state; 48 struct gpio_extcon_data *data = 49 container_of(to_delayed_work(work), struct gpio_extcon_data, 50 work); 51 52 state = gpiod_get_value_cansleep(data->gpiod); 53 extcon_set_state_sync(data->edev, data->extcon_id, state); 54} 55 56static irqreturn_t gpio_irq_handler(int irq, void *dev_id) 57{ 58 struct gpio_extcon_data *data = dev_id; 59 60 queue_delayed_work(system_power_efficient_wq, &data->work, 61 data->debounce_jiffies); 62 return IRQ_HANDLED; 63} 64 65static int gpio_extcon_probe(struct platform_device *pdev) 66{ 67 struct gpio_extcon_data *data; 68 struct device *dev = &pdev->dev; 69 unsigned long irq_flags; 70 int irq; 71 int ret; 72 73 data = devm_kzalloc(dev, sizeof(struct gpio_extcon_data), GFP_KERNEL); 74 if (!data) 75 return -ENOMEM; 76 77 /* 78 * FIXME: extcon_id represents the unique identifier of external 79 * connectors such as EXTCON_USB, EXTCON_DISP_HDMI and so on. extcon_id 80 * is necessary to register the extcon device. But, it's not yet 81 * developed to get the extcon id from device-tree or others. 82 * On later, it have to be solved. 83 */ 84 if (data->extcon_id > EXTCON_NONE) 85 return -EINVAL; 86 87 data->gpiod = devm_gpiod_get(dev, "extcon", GPIOD_IN); 88 if (IS_ERR(data->gpiod)) 89 return PTR_ERR(data->gpiod); 90 irq = gpiod_to_irq(data->gpiod); 91 if (irq <= 0) 92 return irq; 93 94 /* 95 * It is unlikely that this is an acknowledged interrupt that goes 96 * away after handling, what we are looking for are falling edges 97 * if the signal is active low, and rising edges if the signal is 98 * active high. 99 */ 100 if (gpiod_is_active_low(data->gpiod)) 101 irq_flags = IRQF_TRIGGER_FALLING; 102 else 103 irq_flags = IRQF_TRIGGER_RISING; 104 105 /* Allocate the memory of extcon devie and register extcon device */ 106 data->edev = devm_extcon_dev_allocate(dev, &data->extcon_id); 107 if (IS_ERR(data->edev)) { 108 dev_err(dev, "failed to allocate extcon device\n"); 109 return -ENOMEM; 110 } 111 112 ret = devm_extcon_dev_register(dev, data->edev); 113 if (ret < 0) 114 return ret; 115 116 ret = devm_delayed_work_autocancel(dev, &data->work, gpio_extcon_work); 117 if (ret) 118 return ret; 119 120 /* 121 * Request the interrupt of gpio to detect whether external connector 122 * is attached or detached. 123 */ 124 ret = devm_request_any_context_irq(dev, irq, 125 gpio_irq_handler, irq_flags, 126 pdev->name, data); 127 if (ret < 0) 128 return ret; 129 130 platform_set_drvdata(pdev, data); 131 /* Perform initial detection */ 132 gpio_extcon_work(&data->work.work); 133 134 return 0; 135} 136 137#ifdef CONFIG_PM_SLEEP 138static int gpio_extcon_resume(struct device *dev) 139{ 140 struct gpio_extcon_data *data; 141 142 data = dev_get_drvdata(dev); 143 if (data->check_on_resume) 144 queue_delayed_work(system_power_efficient_wq, 145 &data->work, data->debounce_jiffies); 146 147 return 0; 148} 149#endif 150 151static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume); 152 153static struct platform_driver gpio_extcon_driver = { 154 .probe = gpio_extcon_probe, 155 .driver = { 156 .name = "extcon-gpio", 157 .pm = &gpio_extcon_pm_ops, 158 }, 159}; 160 161module_platform_driver(gpio_extcon_driver); 162 163MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); 164MODULE_DESCRIPTION("GPIO extcon driver"); 165MODULE_LICENSE("GPL");