xref: /openbmc/linux/drivers/extcon/extcon-gpio.c (revision 293d5b43)
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.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 	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 *data = dev_id;
58 
59 	queue_delayed_work(system_power_efficient_wq, &data->work,
60 			      data->debounce_jiffies);
61 	return IRQ_HANDLED;
62 }
63 
64 static int gpio_extcon_init(struct device *dev, struct gpio_extcon_data *data)
65 {
66 	struct gpio_extcon_pdata *pdata = data->pdata;
67 	int ret;
68 
69 	ret = devm_gpio_request_one(dev, pdata->gpio, GPIOF_DIR_IN,
70 				dev_name(dev));
71 	if (ret < 0)
72 		return ret;
73 
74 	data->id_gpiod = gpio_to_desc(pdata->gpio);
75 	if (!data->id_gpiod)
76 		return -EINVAL;
77 
78 	if (pdata->debounce) {
79 		ret = gpiod_set_debounce(data->id_gpiod,
80 					pdata->debounce * 1000);
81 		if (ret < 0)
82 			data->debounce_jiffies =
83 				msecs_to_jiffies(pdata->debounce);
84 	}
85 
86 	data->irq = gpiod_to_irq(data->id_gpiod);
87 	if (data->irq < 0)
88 		return data->irq;
89 
90 	return 0;
91 }
92 
93 static int gpio_extcon_probe(struct platform_device *pdev)
94 {
95 	struct gpio_extcon_pdata *pdata = dev_get_platdata(&pdev->dev);
96 	struct gpio_extcon_data *data;
97 	int ret;
98 
99 	if (!pdata)
100 		return -EBUSY;
101 	if (!pdata->irq_flags || pdata->extcon_id > EXTCON_NONE)
102 		return -EINVAL;
103 
104 	data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_extcon_data),
105 				   GFP_KERNEL);
106 	if (!data)
107 		return -ENOMEM;
108 	data->pdata = pdata;
109 
110 	/* Initialize the gpio */
111 	ret = gpio_extcon_init(&pdev->dev, data);
112 	if (ret < 0)
113 		return ret;
114 
115 	/* Allocate the memory of extcon devie and register extcon device */
116 	data->edev = devm_extcon_dev_allocate(&pdev->dev, &pdata->extcon_id);
117 	if (IS_ERR(data->edev)) {
118 		dev_err(&pdev->dev, "failed to allocate extcon device\n");
119 		return -ENOMEM;
120 	}
121 
122 	ret = devm_extcon_dev_register(&pdev->dev, data->edev);
123 	if (ret < 0)
124 		return ret;
125 
126 	INIT_DELAYED_WORK(&data->work, gpio_extcon_work);
127 
128 	/*
129 	 * Request the interrupt of gpio to detect whether external connector
130 	 * is attached or detached.
131 	 */
132 	ret = devm_request_any_context_irq(&pdev->dev, data->irq,
133 					gpio_irq_handler, pdata->irq_flags,
134 					pdev->name, data);
135 	if (ret < 0)
136 		return ret;
137 
138 	platform_set_drvdata(pdev, data);
139 	/* Perform initial detection */
140 	gpio_extcon_work(&data->work.work);
141 
142 	return 0;
143 }
144 
145 static int gpio_extcon_remove(struct platform_device *pdev)
146 {
147 	struct gpio_extcon_data *data = platform_get_drvdata(pdev);
148 
149 	cancel_delayed_work_sync(&data->work);
150 
151 	return 0;
152 }
153 
154 #ifdef CONFIG_PM_SLEEP
155 static int gpio_extcon_resume(struct device *dev)
156 {
157 	struct gpio_extcon_data *data;
158 
159 	data = dev_get_drvdata(dev);
160 	if (data->pdata->check_on_resume)
161 		queue_delayed_work(system_power_efficient_wq,
162 			&data->work, data->debounce_jiffies);
163 
164 	return 0;
165 }
166 #endif
167 
168 static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume);
169 
170 static struct platform_driver gpio_extcon_driver = {
171 	.probe		= gpio_extcon_probe,
172 	.remove		= gpio_extcon_remove,
173 	.driver		= {
174 		.name	= "extcon-gpio",
175 		.pm	= &gpio_extcon_pm_ops,
176 	},
177 };
178 
179 module_platform_driver(gpio_extcon_driver);
180 
181 MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
182 MODULE_DESCRIPTION("GPIO extcon driver");
183 MODULE_LICENSE("GPL");
184