Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/extcon/extcon-gpio.c
26378 views
1
// SPDX-License-Identifier: GPL-2.0-only
2
/*
3
* extcon_gpio.c - Single-state GPIO extcon driver based on extcon class
4
*
5
* Copyright (C) 2008 Google, Inc.
6
* Author: Mike Lockwood <[email protected]>
7
*
8
* Modified by MyungJoo Ham <[email protected]> to support extcon
9
* (originally switch class is supported)
10
*/
11
12
#include <linux/devm-helpers.h>
13
#include <linux/extcon-provider.h>
14
#include <linux/gpio/consumer.h>
15
#include <linux/init.h>
16
#include <linux/interrupt.h>
17
#include <linux/kernel.h>
18
#include <linux/module.h>
19
#include <linux/platform_device.h>
20
#include <linux/slab.h>
21
#include <linux/workqueue.h>
22
23
/**
24
* struct gpio_extcon_data - A simple GPIO-controlled extcon device state container.
25
* @edev: Extcon device.
26
* @work: Work fired by the interrupt.
27
* @debounce_jiffies: Number of jiffies to wait for the GPIO to stabilize, from the debounce
28
* value.
29
* @gpiod: GPIO descriptor for this external connector.
30
* @extcon_id: The unique id of specific external connector.
31
* @debounce: Debounce time for GPIO IRQ in ms.
32
* @check_on_resume: Boolean describing whether to check the state of gpio
33
* while resuming from sleep.
34
*/
35
struct gpio_extcon_data {
36
struct extcon_dev *edev;
37
struct delayed_work work;
38
unsigned long debounce_jiffies;
39
struct gpio_desc *gpiod;
40
unsigned int extcon_id;
41
unsigned long debounce;
42
bool check_on_resume;
43
};
44
45
static void gpio_extcon_work(struct work_struct *work)
46
{
47
int state;
48
struct gpio_extcon_data *data =
49
container_of(to_delayed_work(work), struct gpio_extcon_data,
50
work);
51
52
state = gpiod_get_value_cansleep(data->gpiod);
53
extcon_set_state_sync(data->edev, data->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_probe(struct platform_device *pdev)
66
{
67
struct gpio_extcon_data *data;
68
struct device *dev = &pdev->dev;
69
unsigned long irq_flags;
70
int irq;
71
int ret;
72
73
data = devm_kzalloc(dev, sizeof(struct gpio_extcon_data), GFP_KERNEL);
74
if (!data)
75
return -ENOMEM;
76
77
/*
78
* FIXME: extcon_id represents the unique identifier of external
79
* connectors such as EXTCON_USB, EXTCON_DISP_HDMI and so on. extcon_id
80
* is necessary to register the extcon device. But, it's not yet
81
* developed to get the extcon id from device-tree or others.
82
* On later, it have to be solved.
83
*/
84
if (data->extcon_id > EXTCON_NONE)
85
return -EINVAL;
86
87
data->gpiod = devm_gpiod_get(dev, "extcon", GPIOD_IN);
88
if (IS_ERR(data->gpiod))
89
return PTR_ERR(data->gpiod);
90
irq = gpiod_to_irq(data->gpiod);
91
if (irq <= 0)
92
return irq;
93
94
/*
95
* It is unlikely that this is an acknowledged interrupt that goes
96
* away after handling, what we are looking for are falling edges
97
* if the signal is active low, and rising edges if the signal is
98
* active high.
99
*/
100
if (gpiod_is_active_low(data->gpiod))
101
irq_flags = IRQF_TRIGGER_FALLING;
102
else
103
irq_flags = IRQF_TRIGGER_RISING;
104
105
/* Allocate the memory of extcon devie and register extcon device */
106
data->edev = devm_extcon_dev_allocate(dev, &data->extcon_id);
107
if (IS_ERR(data->edev)) {
108
dev_err(dev, "failed to allocate extcon device\n");
109
return -ENOMEM;
110
}
111
112
ret = devm_extcon_dev_register(dev, data->edev);
113
if (ret < 0)
114
return ret;
115
116
ret = devm_delayed_work_autocancel(dev, &data->work, gpio_extcon_work);
117
if (ret)
118
return ret;
119
120
/*
121
* Request the interrupt of gpio to detect whether external connector
122
* is attached or detached.
123
*/
124
ret = devm_request_any_context_irq(dev, irq,
125
gpio_irq_handler, irq_flags,
126
pdev->name, data);
127
if (ret < 0)
128
return ret;
129
130
platform_set_drvdata(pdev, data);
131
/* Perform initial detection */
132
gpio_extcon_work(&data->work.work);
133
134
return 0;
135
}
136
137
#ifdef CONFIG_PM_SLEEP
138
static int gpio_extcon_resume(struct device *dev)
139
{
140
struct gpio_extcon_data *data;
141
142
data = dev_get_drvdata(dev);
143
if (data->check_on_resume)
144
queue_delayed_work(system_power_efficient_wq,
145
&data->work, data->debounce_jiffies);
146
147
return 0;
148
}
149
#endif
150
151
static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume);
152
153
static struct platform_driver gpio_extcon_driver = {
154
.probe = gpio_extcon_probe,
155
.driver = {
156
.name = "extcon-gpio",
157
.pm = &gpio_extcon_pm_ops,
158
},
159
};
160
161
module_platform_driver(gpio_extcon_driver);
162
163
MODULE_AUTHOR("Mike Lockwood <[email protected]>");
164
MODULE_DESCRIPTION("GPIO extcon driver");
165
MODULE_LICENSE("GPL");
166
167