1
2
3
4
5
6#include <linux/module.h>
7#include <linux/interrupt.h>
8#include <linux/delay.h>
9#include <linux/platform_device.h>
10#include <linux/gpio/consumer.h>
11#include <media/cec-pin.h>
12
13struct cec_gpio {
14 struct cec_adapter *adap;
15 struct device *dev;
16
17 struct gpio_desc *cec_gpio;
18 int cec_irq;
19 bool cec_is_low;
20 bool cec_have_irq;
21
22 struct gpio_desc *hpd_gpio;
23 int hpd_irq;
24 bool hpd_is_high;
25 ktime_t hpd_ts;
26};
27
28static bool cec_gpio_read(struct cec_adapter *adap)
29{
30 struct cec_gpio *cec = cec_get_drvdata(adap);
31
32 if (cec->cec_is_low)
33 return false;
34 return gpiod_get_value(cec->cec_gpio);
35}
36
37static void cec_gpio_high(struct cec_adapter *adap)
38{
39 struct cec_gpio *cec = cec_get_drvdata(adap);
40
41 if (!cec->cec_is_low)
42 return;
43 cec->cec_is_low = false;
44 gpiod_set_value(cec->cec_gpio, 1);
45}
46
47static void cec_gpio_low(struct cec_adapter *adap)
48{
49 struct cec_gpio *cec = cec_get_drvdata(adap);
50
51 if (cec->cec_is_low)
52 return;
53 if (WARN_ON_ONCE(cec->cec_have_irq))
54 free_irq(cec->cec_irq, cec);
55 cec->cec_have_irq = false;
56 cec->cec_is_low = true;
57 gpiod_set_value(cec->cec_gpio, 0);
58}
59
60static irqreturn_t cec_hpd_gpio_irq_handler_thread(int irq, void *priv)
61{
62 struct cec_gpio *cec = priv;
63
64 cec_queue_pin_hpd_event(cec->adap, cec->hpd_is_high, cec->hpd_ts);
65 return IRQ_HANDLED;
66}
67
68static irqreturn_t cec_hpd_gpio_irq_handler(int irq, void *priv)
69{
70 struct cec_gpio *cec = priv;
71 bool is_high = gpiod_get_value(cec->hpd_gpio);
72
73 if (is_high == cec->hpd_is_high)
74 return IRQ_HANDLED;
75 cec->hpd_ts = ktime_get();
76 cec->hpd_is_high = is_high;
77 return IRQ_WAKE_THREAD;
78}
79
80static irqreturn_t cec_gpio_irq_handler(int irq, void *priv)
81{
82 struct cec_gpio *cec = priv;
83
84 cec_pin_changed(cec->adap, gpiod_get_value(cec->cec_gpio));
85 return IRQ_HANDLED;
86}
87
88static bool cec_gpio_enable_irq(struct cec_adapter *adap)
89{
90 struct cec_gpio *cec = cec_get_drvdata(adap);
91
92 if (cec->cec_have_irq)
93 return true;
94
95 if (request_irq(cec->cec_irq, cec_gpio_irq_handler,
96 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
97 adap->name, cec))
98 return false;
99 cec->cec_have_irq = true;
100 return true;
101}
102
103static void cec_gpio_disable_irq(struct cec_adapter *adap)
104{
105 struct cec_gpio *cec = cec_get_drvdata(adap);
106
107 if (cec->cec_have_irq)
108 free_irq(cec->cec_irq, cec);
109 cec->cec_have_irq = false;
110}
111
112static void cec_gpio_status(struct cec_adapter *adap, struct seq_file *file)
113{
114 struct cec_gpio *cec = cec_get_drvdata(adap);
115
116 seq_printf(file, "mode: %s\n", cec->cec_is_low ? "low-drive" : "read");
117 if (cec->cec_have_irq)
118 seq_printf(file, "using irq: %d\n", cec->cec_irq);
119 if (cec->hpd_gpio)
120 seq_printf(file, "hpd: %s\n",
121 cec->hpd_is_high ? "high" : "low");
122}
123
124static int cec_gpio_read_hpd(struct cec_adapter *adap)
125{
126 struct cec_gpio *cec = cec_get_drvdata(adap);
127
128 if (!cec->hpd_gpio)
129 return -ENOTTY;
130 return gpiod_get_value(cec->hpd_gpio);
131}
132
133static void cec_gpio_free(struct cec_adapter *adap)
134{
135 cec_gpio_disable_irq(adap);
136}
137
138static const struct cec_pin_ops cec_gpio_pin_ops = {
139 .read = cec_gpio_read,
140 .low = cec_gpio_low,
141 .high = cec_gpio_high,
142 .enable_irq = cec_gpio_enable_irq,
143 .disable_irq = cec_gpio_disable_irq,
144 .status = cec_gpio_status,
145 .free = cec_gpio_free,
146 .read_hpd = cec_gpio_read_hpd,
147};
148
149static int cec_gpio_probe(struct platform_device *pdev)
150{
151 struct device *dev = &pdev->dev;
152 struct cec_gpio *cec;
153 int ret;
154
155 cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL);
156 if (!cec)
157 return -ENOMEM;
158
159 cec->dev = dev;
160
161 cec->cec_gpio = devm_gpiod_get(dev, "cec", GPIOD_IN);
162 if (IS_ERR(cec->cec_gpio))
163 return PTR_ERR(cec->cec_gpio);
164 cec->cec_irq = gpiod_to_irq(cec->cec_gpio);
165
166 cec->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
167 if (IS_ERR(cec->hpd_gpio))
168 return PTR_ERR(cec->hpd_gpio);
169
170 cec->adap = cec_pin_allocate_adapter(&cec_gpio_pin_ops,
171 cec, pdev->name, CEC_CAP_DEFAULTS | CEC_CAP_PHYS_ADDR |
172 CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN);
173 if (IS_ERR(cec->adap))
174 return PTR_ERR(cec->adap);
175
176 if (cec->hpd_gpio) {
177 cec->hpd_irq = gpiod_to_irq(cec->hpd_gpio);
178 ret = devm_request_threaded_irq(dev, cec->hpd_irq,
179 cec_hpd_gpio_irq_handler,
180 cec_hpd_gpio_irq_handler_thread,
181 IRQF_ONESHOT |
182 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
183 "hpd-gpio", cec);
184 if (ret)
185 return ret;
186 }
187
188 ret = cec_register_adapter(cec->adap, &pdev->dev);
189 if (ret) {
190 cec_delete_adapter(cec->adap);
191 return ret;
192 }
193
194 platform_set_drvdata(pdev, cec);
195 return 0;
196}
197
198static int cec_gpio_remove(struct platform_device *pdev)
199{
200 struct cec_gpio *cec = platform_get_drvdata(pdev);
201
202 cec_unregister_adapter(cec->adap);
203 return 0;
204}
205
206static const struct of_device_id cec_gpio_match[] = {
207 {
208 .compatible = "cec-gpio",
209 },
210 {},
211};
212MODULE_DEVICE_TABLE(of, cec_gpio_match);
213
214static struct platform_driver cec_gpio_pdrv = {
215 .probe = cec_gpio_probe,
216 .remove = cec_gpio_remove,
217 .driver = {
218 .name = "cec-gpio",
219 .of_match_table = cec_gpio_match,
220 },
221};
222
223module_platform_driver(cec_gpio_pdrv);
224
225MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>");
226MODULE_LICENSE("GPL v2");
227MODULE_DESCRIPTION("CEC GPIO driver");
228