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/extcon/extcon-gpio.h> 22 #include <linux/gpio.h> 23 #include <linux/gpio/consumer.h> 24 #include <linux/init.h> 25 #include <linux/interrupt.h> 26 #include <linux/kernel.h> 27 #include <linux/module.h> 28 #include <linux/platform_device.h> 29 #include <linux/slab.h> 30 #include <linux/workqueue.h> 31 32 struct gpio_extcon_data { 33 struct extcon_dev *edev; 34 int irq; 35 struct delayed_work work; 36 unsigned long debounce_jiffies; 37 38 struct gpio_desc *id_gpiod; 39 struct gpio_extcon_pdata *pdata; 40 }; 41 42 static void gpio_extcon_work(struct work_struct *work) 43 { 44 int state; 45 struct gpio_extcon_data *data = 46 container_of(to_delayed_work(work), struct gpio_extcon_data, 47 work); 48 49 state = gpiod_get_value_cansleep(data->id_gpiod); 50 if (data->pdata->gpio_active_low) 51 state = !state; 52 53 extcon_set_state_sync(data->edev, data->pdata->extcon_id, state); 54 } 55 56 static 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 65 static int gpio_extcon_init(struct device *dev, struct gpio_extcon_data *data) 66 { 67 struct gpio_extcon_pdata *pdata = data->pdata; 68 int ret; 69 70 ret = devm_gpio_request_one(dev, pdata->gpio, GPIOF_DIR_IN, 71 dev_name(dev)); 72 if (ret < 0) 73 return ret; 74 75 data->id_gpiod = gpio_to_desc(pdata->gpio); 76 if (!data->id_gpiod) 77 return -EINVAL; 78 79 if (pdata->debounce) { 80 ret = gpiod_set_debounce(data->id_gpiod, 81 pdata->debounce * 1000); 82 if (ret < 0) 83 data->debounce_jiffies = 84 msecs_to_jiffies(pdata->debounce); 85 } 86 87 data->irq = gpiod_to_irq(data->id_gpiod); 88 if (data->irq < 0) 89 return data->irq; 90 91 return 0; 92 } 93 94 static int gpio_extcon_probe(struct platform_device *pdev) 95 { 96 struct gpio_extcon_pdata *pdata = dev_get_platdata(&pdev->dev); 97 struct gpio_extcon_data *data; 98 int ret; 99 100 if (!pdata) 101 return -EBUSY; 102 if (!pdata->irq_flags || pdata->extcon_id > EXTCON_NONE) 103 return -EINVAL; 104 105 data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_extcon_data), 106 GFP_KERNEL); 107 if (!data) 108 return -ENOMEM; 109 data->pdata = pdata; 110 111 /* Initialize the gpio */ 112 ret = gpio_extcon_init(&pdev->dev, data); 113 if (ret < 0) 114 return ret; 115 116 /* Allocate the memory of extcon devie and register extcon device */ 117 data->edev = devm_extcon_dev_allocate(&pdev->dev, &pdata->extcon_id); 118 if (IS_ERR(data->edev)) { 119 dev_err(&pdev->dev, "failed to allocate extcon device\n"); 120 return -ENOMEM; 121 } 122 123 ret = devm_extcon_dev_register(&pdev->dev, data->edev); 124 if (ret < 0) 125 return ret; 126 127 INIT_DELAYED_WORK(&data->work, gpio_extcon_work); 128 129 /* 130 * Request the interrupt of gpio to detect whether external connector 131 * is attached or detached. 132 */ 133 ret = devm_request_any_context_irq(&pdev->dev, data->irq, 134 gpio_irq_handler, pdata->irq_flags, 135 pdev->name, data); 136 if (ret < 0) 137 return ret; 138 139 platform_set_drvdata(pdev, data); 140 /* Perform initial detection */ 141 gpio_extcon_work(&data->work.work); 142 143 return 0; 144 } 145 146 static int gpio_extcon_remove(struct platform_device *pdev) 147 { 148 struct gpio_extcon_data *data = platform_get_drvdata(pdev); 149 150 cancel_delayed_work_sync(&data->work); 151 152 return 0; 153 } 154 155 #ifdef CONFIG_PM_SLEEP 156 static int gpio_extcon_resume(struct device *dev) 157 { 158 struct gpio_extcon_data *data; 159 160 data = dev_get_drvdata(dev); 161 if (data->pdata->check_on_resume) 162 queue_delayed_work(system_power_efficient_wq, 163 &data->work, data->debounce_jiffies); 164 165 return 0; 166 } 167 #endif 168 169 static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume); 170 171 static struct platform_driver gpio_extcon_driver = { 172 .probe = gpio_extcon_probe, 173 .remove = gpio_extcon_remove, 174 .driver = { 175 .name = "extcon-gpio", 176 .pm = &gpio_extcon_pm_ops, 177 }, 178 }; 179 180 module_platform_driver(gpio_extcon_driver); 181 182 MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); 183 MODULE_DESCRIPTION("GPIO extcon driver"); 184 MODULE_LICENSE("GPL"); 185