1 /* 2 * drivers/extcon/extcon_gpio.c 3 * 4 * Single-state GPIO extcon driver based on extcon class 5 * 6 * Copyright (C) 2008 Google, Inc. 7 * Author: Mike Lockwood <lockwood@android.com> 8 * 9 * Modified by MyungJoo Ham <myungjoo.ham@samsung.com> to support extcon 10 * (originally switch class is supported) 11 * 12 * This software is licensed under the terms of the GNU General Public 13 * License version 2, as published by the Free Software Foundation, and 14 * may be copied, distributed, and modified under those terms. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 */ 22 23 #include <linux/module.h> 24 #include <linux/kernel.h> 25 #include <linux/init.h> 26 #include <linux/interrupt.h> 27 #include <linux/platform_device.h> 28 #include <linux/slab.h> 29 #include <linux/workqueue.h> 30 #include <linux/gpio.h> 31 #include <linux/extcon.h> 32 #include <linux/extcon/extcon_gpio.h> 33 34 struct gpio_extcon_data { 35 struct extcon_dev edev; 36 unsigned gpio; 37 const char *state_on; 38 const char *state_off; 39 int irq; 40 struct delayed_work work; 41 unsigned long debounce_jiffies; 42 }; 43 44 static void gpio_extcon_work(struct work_struct *work) 45 { 46 int state; 47 struct gpio_extcon_data *data = 48 container_of(to_delayed_work(work), struct gpio_extcon_data, 49 work); 50 51 state = gpio_get_value(data->gpio); 52 extcon_set_state(&data->edev, state); 53 } 54 55 static irqreturn_t gpio_irq_handler(int irq, void *dev_id) 56 { 57 struct gpio_extcon_data *extcon_data = dev_id; 58 59 schedule_delayed_work(&extcon_data->work, 60 extcon_data->debounce_jiffies); 61 return IRQ_HANDLED; 62 } 63 64 static ssize_t extcon_gpio_print_state(struct extcon_dev *edev, char *buf) 65 { 66 struct gpio_extcon_data *extcon_data = 67 container_of(edev, struct gpio_extcon_data, edev); 68 const char *state; 69 if (extcon_get_state(edev)) 70 state = extcon_data->state_on; 71 else 72 state = extcon_data->state_off; 73 74 if (state) 75 return sprintf(buf, "%s\n", state); 76 return -EINVAL; 77 } 78 79 static int gpio_extcon_probe(struct platform_device *pdev) 80 { 81 struct gpio_extcon_platform_data *pdata = pdev->dev.platform_data; 82 struct gpio_extcon_data *extcon_data; 83 int ret = 0; 84 85 if (!pdata) 86 return -EBUSY; 87 if (!pdata->irq_flags) { 88 dev_err(&pdev->dev, "IRQ flag is not specified.\n"); 89 return -EINVAL; 90 } 91 92 extcon_data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_extcon_data), 93 GFP_KERNEL); 94 if (!extcon_data) 95 return -ENOMEM; 96 97 extcon_data->edev.name = pdata->name; 98 extcon_data->gpio = pdata->gpio; 99 extcon_data->state_on = pdata->state_on; 100 extcon_data->state_off = pdata->state_off; 101 if (pdata->state_on && pdata->state_off) 102 extcon_data->edev.print_state = extcon_gpio_print_state; 103 extcon_data->debounce_jiffies = msecs_to_jiffies(pdata->debounce); 104 105 ret = extcon_dev_register(&extcon_data->edev, &pdev->dev); 106 if (ret < 0) 107 return ret; 108 109 ret = devm_gpio_request_one(&pdev->dev, extcon_data->gpio, GPIOF_DIR_IN, 110 pdev->name); 111 if (ret < 0) 112 goto err; 113 114 INIT_DELAYED_WORK(&extcon_data->work, gpio_extcon_work); 115 116 extcon_data->irq = gpio_to_irq(extcon_data->gpio); 117 if (extcon_data->irq < 0) { 118 ret = extcon_data->irq; 119 goto err; 120 } 121 122 ret = request_any_context_irq(extcon_data->irq, gpio_irq_handler, 123 pdata->irq_flags, pdev->name, 124 extcon_data); 125 if (ret < 0) 126 goto err; 127 128 platform_set_drvdata(pdev, extcon_data); 129 /* Perform initial detection */ 130 gpio_extcon_work(&extcon_data->work.work); 131 132 return 0; 133 134 err: 135 extcon_dev_unregister(&extcon_data->edev); 136 137 return ret; 138 } 139 140 static int gpio_extcon_remove(struct platform_device *pdev) 141 { 142 struct gpio_extcon_data *extcon_data = platform_get_drvdata(pdev); 143 144 cancel_delayed_work_sync(&extcon_data->work); 145 free_irq(extcon_data->irq, extcon_data); 146 extcon_dev_unregister(&extcon_data->edev); 147 148 return 0; 149 } 150 151 static struct platform_driver gpio_extcon_driver = { 152 .probe = gpio_extcon_probe, 153 .remove = gpio_extcon_remove, 154 .driver = { 155 .name = "extcon-gpio", 156 .owner = THIS_MODULE, 157 }, 158 }; 159 160 module_platform_driver(gpio_extcon_driver); 161 162 MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); 163 MODULE_DESCRIPTION("GPIO extcon driver"); 164 MODULE_LICENSE("GPL"); 165