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