blob: 296ec122996c515d03af41d4b9e11d0bedd10f85 [file] [log] [blame]
MyungJoo Hambe483082012-04-20 14:16:23 +09001/*
Chanwoo Choi6ba12992015-09-29 22:59:55 +09002 * extcon_gpio.c - Single-state GPIO extcon driver based on extcon class
MyungJoo Hambe483082012-04-20 14:16:23 +09003 *
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.
Chanwoo Choi6ba12992015-09-29 22:59:55 +090018 */
MyungJoo Hambe483082012-04-20 14:16:23 +090019
George Cherian62364352014-09-09 09:44:34 +053020#include <linux/extcon.h>
21#include <linux/extcon/extcon-gpio.h>
22#include <linux/gpio.h>
Chanwoo Choide992ac2015-09-30 14:57:57 +090023#include <linux/gpio/consumer.h>
MyungJoo Hambe483082012-04-20 14:16:23 +090024#include <linux/init.h>
25#include <linux/interrupt.h>
George Cherian62364352014-09-09 09:44:34 +053026#include <linux/kernel.h>
27#include <linux/module.h>
MyungJoo Hambe483082012-04-20 14:16:23 +090028#include <linux/platform_device.h>
29#include <linux/slab.h>
MyungJoo Hambe483082012-04-20 14:16:23 +090030#include <linux/workqueue.h>
Subhash Jadavanie5371bd2017-05-08 18:06:42 -070031#include <linux/of_gpio.h>
MyungJoo Hambe483082012-04-20 14:16:23 +090032
33struct gpio_extcon_data {
Chanwoo Choi60cd62d2014-04-21 20:51:08 +090034 struct extcon_dev *edev;
MyungJoo Hambe483082012-04-20 14:16:23 +090035 int irq;
36 struct delayed_work work;
37 unsigned long debounce_jiffies;
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +090038
Chanwoo Choide992ac2015-09-30 14:57:57 +090039 struct gpio_desc *id_gpiod;
Chanwoo Choi6ba12992015-09-29 22:59:55 +090040 struct gpio_extcon_pdata *pdata;
Subhash Jadavanie5371bd2017-05-08 18:06:42 -070041 unsigned int *supported_cable;
MyungJoo Hambe483082012-04-20 14:16:23 +090042};
43
44static 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
Chanwoo Choide992ac2015-09-30 14:57:57 +090051 state = gpiod_get_value_cansleep(data->id_gpiod);
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +090052 if (data->pdata->gpio_active_low)
Guenter Roeck5bfbdc92013-09-12 08:49:54 +090053 state = !state;
Kishon Vijay Abraham Icb9850d2016-09-15 15:46:11 +053054
Chanwoo Choi8670b452016-08-16 15:55:34 +090055 extcon_set_state_sync(data->edev, data->pdata->extcon_id, state);
MyungJoo Hambe483082012-04-20 14:16:23 +090056}
57
58static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
59{
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +090060 struct gpio_extcon_data *data = dev_id;
MyungJoo Hambe483082012-04-20 14:16:23 +090061
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +090062 queue_delayed_work(system_power_efficient_wq, &data->work,
63 data->debounce_jiffies);
MyungJoo Hambe483082012-04-20 14:16:23 +090064 return IRQ_HANDLED;
65}
66
Chanwoo Choide992ac2015-09-30 14:57:57 +090067static int gpio_extcon_init(struct device *dev, struct gpio_extcon_data *data)
68{
69 struct gpio_extcon_pdata *pdata = data->pdata;
70 int ret;
71
72 ret = devm_gpio_request_one(dev, pdata->gpio, GPIOF_DIR_IN,
73 dev_name(dev));
74 if (ret < 0)
75 return ret;
76
77 data->id_gpiod = gpio_to_desc(pdata->gpio);
78 if (!data->id_gpiod)
79 return -EINVAL;
80
81 if (pdata->debounce) {
82 ret = gpiod_set_debounce(data->id_gpiod,
83 pdata->debounce * 1000);
84 if (ret < 0)
85 data->debounce_jiffies =
86 msecs_to_jiffies(pdata->debounce);
87 }
88
89 data->irq = gpiod_to_irq(data->id_gpiod);
90 if (data->irq < 0)
91 return data->irq;
92
93 return 0;
94}
95
Subhash Jadavanie5371bd2017-05-08 18:06:42 -070096static int extcon_parse_pinctrl_data(struct device *dev,
97 struct gpio_extcon_pdata *pdata)
98{
99 struct pinctrl *pctrl;
100 int ret = 0;
101
102 /* Try to obtain pinctrl handle */
103 pctrl = devm_pinctrl_get(dev);
104 if (IS_ERR(pctrl)) {
105 ret = PTR_ERR(pctrl);
106 goto out;
107 }
108 pdata->pctrl = pctrl;
109
110 /* Look-up and keep the state handy to be used later */
111 pdata->pins_default = pinctrl_lookup_state(pdata->pctrl,
112 "default");
113 if (IS_ERR(pdata->pins_default)) {
114 ret = PTR_ERR(pdata->pins_default);
115 dev_err(dev, "Can't get default pinctrl state, ret %d\n", ret);
116 }
117out:
118 return ret;
119}
120
121/* Parse platform data */
122static
123struct gpio_extcon_pdata *extcon_populate_pdata(struct device *dev)
124{
125 struct gpio_extcon_pdata *pdata = NULL;
126 struct device_node *np = dev->of_node;
127 enum of_gpio_flags flags;
128 u32 val;
129
130 pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
131 if (!pdata)
132 goto out;
133
134 if (of_property_read_u32(np, "extcon-id", &pdata->extcon_id)) {
135 dev_err(dev, "extcon-id property not found\n");
136 goto out;
137 }
138
139 pdata->gpio = of_get_named_gpio_flags(np, "gpio", 0, &flags);
140 if (gpio_is_valid(pdata->gpio)) {
141 if (flags & OF_GPIO_ACTIVE_LOW)
142 pdata->gpio_active_low = true;
143 } else {
144 dev_err(dev, "gpio property not found or invalid\n");
145 goto out;
146 }
147
148 if (of_property_read_u32(np, "irq-flags", &val)) {
149 dev_err(dev, "irq-flags property not found\n");
150 goto out;
151 }
152 pdata->irq_flags = val;
153
154 if (of_property_read_u32(np, "debounce-ms", &val)) {
155 dev_err(dev, "debounce-ms property not found\n");
156 goto out;
157 }
158 pdata->debounce = val;
159
160 if (extcon_parse_pinctrl_data(dev, pdata)) {
161 dev_err(dev, "failed to parse pinctrl data\n");
162 goto out;
163 }
164
165 return pdata;
166out:
167 return NULL;
168}
169
Bill Pemberton44f34fd2012-11-19 13:23:21 -0500170static int gpio_extcon_probe(struct platform_device *pdev)
MyungJoo Hambe483082012-04-20 14:16:23 +0900171{
Chanwoo Choi6ba12992015-09-29 22:59:55 +0900172 struct gpio_extcon_pdata *pdata = dev_get_platdata(&pdev->dev);
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900173 struct gpio_extcon_data *data;
Guenter Roeck10735142013-08-29 21:29:33 -0700174 int ret;
MyungJoo Hambe483082012-04-20 14:16:23 +0900175
Subhash Jadavanie5371bd2017-05-08 18:06:42 -0700176 if (!pdata) {
177 /* try populating pdata from device tree */
178 pdata = extcon_populate_pdata(&pdev->dev);
179 if (!pdata)
180 return -EBUSY;
181 }
182 if (!pdata->irq_flags || pdata->extcon_id >= EXTCON_NUM)
MyungJoo Hambe483082012-04-20 14:16:23 +0900183 return -EINVAL;
MyungJoo Hambe483082012-04-20 14:16:23 +0900184
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900185 data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_extcon_data),
MyungJoo Hambe483082012-04-20 14:16:23 +0900186 GFP_KERNEL);
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900187 if (!data)
MyungJoo Hambe483082012-04-20 14:16:23 +0900188 return -ENOMEM;
Chanwoo Choide992ac2015-09-30 14:57:57 +0900189 data->pdata = pdata;
MyungJoo Hambe483082012-04-20 14:16:23 +0900190
Subhash Jadavanie5371bd2017-05-08 18:06:42 -0700191 ret = pinctrl_select_state(pdata->pctrl, pdata->pins_default);
192 if (ret < 0)
193 dev_err(&pdev->dev, "pinctrl state select failed, ret %d\n",
194 ret);
195
Chanwoo Choide992ac2015-09-30 14:57:57 +0900196 /* Initialize the gpio */
197 ret = gpio_extcon_init(&pdev->dev, data);
198 if (ret < 0)
199 return ret;
200
Subhash Jadavanie5371bd2017-05-08 18:06:42 -0700201 data->supported_cable = devm_kzalloc(&pdev->dev,
202 sizeof(*data->supported_cable) * 2,
203 GFP_KERNEL);
204 if (!data->supported_cable)
205 return -ENOMEM;
206
207 data->supported_cable[0] = pdata->extcon_id;
208 data->supported_cable[1] = EXTCON_NONE;
Chanwoo Choide992ac2015-09-30 14:57:57 +0900209 /* Allocate the memory of extcon devie and register extcon device */
Subhash Jadavanie5371bd2017-05-08 18:06:42 -0700210 data->edev = devm_extcon_dev_allocate(&pdev->dev,
211 data->supported_cable);
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900212 if (IS_ERR(data->edev)) {
Chanwoo Choi60cd62d2014-04-21 20:51:08 +0900213 dev_err(&pdev->dev, "failed to allocate extcon device\n");
214 return -ENOMEM;
215 }
MyungJoo Hambe483082012-04-20 14:16:23 +0900216
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900217 ret = devm_extcon_dev_register(&pdev->dev, data->edev);
MyungJoo Hambe483082012-04-20 14:16:23 +0900218 if (ret < 0)
Axel Lin01eaf242012-06-16 11:56:24 +0800219 return ret;
MyungJoo Hambe483082012-04-20 14:16:23 +0900220
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900221 INIT_DELAYED_WORK(&data->work, gpio_extcon_work);
MyungJoo Hambe483082012-04-20 14:16:23 +0900222
Chanwoo Choide992ac2015-09-30 14:57:57 +0900223 /*
Moritz Fischerb51b3872015-12-23 21:34:07 -0800224 * Request the interrupt of gpio to detect whether external connector
Chanwoo Choide992ac2015-09-30 14:57:57 +0900225 * is attached or detached.
226 */
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900227 ret = devm_request_any_context_irq(&pdev->dev, data->irq,
Chanwoo Choiae59f3a2015-09-25 22:40:51 +0900228 gpio_irq_handler, pdata->irq_flags,
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900229 pdev->name, data);
MyungJoo Hambe483082012-04-20 14:16:23 +0900230 if (ret < 0)
Sangjung Wood92c2f12014-04-21 19:10:10 +0900231 return ret;
MyungJoo Hambe483082012-04-20 14:16:23 +0900232
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900233 platform_set_drvdata(pdev, data);
MyungJoo Hambe483082012-04-20 14:16:23 +0900234 /* Perform initial detection */
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900235 gpio_extcon_work(&data->work.work);
MyungJoo Hambe483082012-04-20 14:16:23 +0900236
237 return 0;
MyungJoo Hambe483082012-04-20 14:16:23 +0900238}
239
Bill Pemberton93ed0322012-11-19 13:25:49 -0500240static int gpio_extcon_remove(struct platform_device *pdev)
MyungJoo Hambe483082012-04-20 14:16:23 +0900241{
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900242 struct gpio_extcon_data *data = platform_get_drvdata(pdev);
MyungJoo Hambe483082012-04-20 14:16:23 +0900243
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900244 cancel_delayed_work_sync(&data->work);
MyungJoo Hambe483082012-04-20 14:16:23 +0900245
246 return 0;
247}
248
Rongjun Ying6544dfa2014-01-09 09:50:13 +0900249#ifdef CONFIG_PM_SLEEP
250static int gpio_extcon_resume(struct device *dev)
251{
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900252 struct gpio_extcon_data *data;
Rongjun Ying6544dfa2014-01-09 09:50:13 +0900253
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900254 data = dev_get_drvdata(dev);
255 if (data->pdata->check_on_resume)
Rongjun Ying6544dfa2014-01-09 09:50:13 +0900256 queue_delayed_work(system_power_efficient_wq,
Chanwoo Choi60f9b9e2015-09-29 20:53:12 +0900257 &data->work, data->debounce_jiffies);
Rongjun Ying6544dfa2014-01-09 09:50:13 +0900258
259 return 0;
260}
261#endif
262
Jingoo Han3cc731d2014-02-27 20:37:15 +0900263static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume);
Rongjun Ying6544dfa2014-01-09 09:50:13 +0900264
Subhash Jadavanie5371bd2017-05-08 18:06:42 -0700265static const struct of_device_id extcon_gpio_of_match[] = {
266 { .compatible = "extcon-gpio"},
267 {},
268};
269
H Hartley Sweeten2878bda2012-05-02 15:38:44 -0700270static struct platform_driver gpio_extcon_driver = {
MyungJoo Hambe483082012-04-20 14:16:23 +0900271 .probe = gpio_extcon_probe,
Bill Pemberton5f7e2222012-11-19 13:20:06 -0500272 .remove = gpio_extcon_remove,
MyungJoo Hambe483082012-04-20 14:16:23 +0900273 .driver = {
274 .name = "extcon-gpio",
Rongjun Ying6544dfa2014-01-09 09:50:13 +0900275 .pm = &gpio_extcon_pm_ops,
Subhash Jadavanie5371bd2017-05-08 18:06:42 -0700276 .of_match_table = of_match_ptr(extcon_gpio_of_match),
MyungJoo Hambe483082012-04-20 14:16:23 +0900277 },
278};
279
H Hartley Sweeten2878bda2012-05-02 15:38:44 -0700280module_platform_driver(gpio_extcon_driver);
MyungJoo Hambe483082012-04-20 14:16:23 +0900281
282MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
283MODULE_DESCRIPTION("GPIO extcon driver");
284MODULE_LICENSE("GPL");