1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
4 */
5
6 #include <linux/delay.h>
7 #include <linux/gpio/consumer.h>
8 #include <linux/interrupt.h>
9 #include <linux/module.h>
10 #include <linux/platform_device.h>
11 #include <linux/seq_file.h>
12 #include <media/cec-notifier.h>
13 #include <media/cec-pin.h>
14
15 struct cec_gpio {
16 struct cec_adapter *adap;
17 struct cec_notifier *notifier;
18 struct device *dev;
19
20 struct gpio_desc *cec_gpio;
21 int cec_irq;
22 bool cec_is_low;
23
24 struct gpio_desc *hpd_gpio;
25 int hpd_irq;
26 bool hpd_is_high;
27 ktime_t hpd_ts;
28
29 struct gpio_desc *v5_gpio;
30 int v5_irq;
31 bool v5_is_high;
32 ktime_t v5_ts;
33 };
34
cec_gpio_read(struct cec_adapter * adap)35 static int cec_gpio_read(struct cec_adapter *adap)
36 {
37 struct cec_gpio *cec = cec_get_drvdata(adap);
38
39 if (cec->cec_is_low)
40 return 0;
41 return gpiod_get_value(cec->cec_gpio);
42 }
43
cec_gpio_high(struct cec_adapter * adap)44 static void cec_gpio_high(struct cec_adapter *adap)
45 {
46 struct cec_gpio *cec = cec_get_drvdata(adap);
47
48 if (!cec->cec_is_low)
49 return;
50 cec->cec_is_low = false;
51 gpiod_set_value(cec->cec_gpio, 1);
52 }
53
cec_gpio_low(struct cec_adapter * adap)54 static void cec_gpio_low(struct cec_adapter *adap)
55 {
56 struct cec_gpio *cec = cec_get_drvdata(adap);
57
58 if (cec->cec_is_low)
59 return;
60 cec->cec_is_low = true;
61 gpiod_set_value(cec->cec_gpio, 0);
62 }
63
cec_hpd_gpio_irq_handler_thread(int irq,void * priv)64 static irqreturn_t cec_hpd_gpio_irq_handler_thread(int irq, void *priv)
65 {
66 struct cec_gpio *cec = priv;
67
68 cec_queue_pin_hpd_event(cec->adap, cec->hpd_is_high, cec->hpd_ts);
69 return IRQ_HANDLED;
70 }
71
cec_5v_gpio_irq_handler(int irq,void * priv)72 static irqreturn_t cec_5v_gpio_irq_handler(int irq, void *priv)
73 {
74 struct cec_gpio *cec = priv;
75 int val = gpiod_get_value(cec->v5_gpio);
76 bool is_high = val > 0;
77
78 if (val < 0 || is_high == cec->v5_is_high)
79 return IRQ_HANDLED;
80 cec->v5_ts = ktime_get();
81 cec->v5_is_high = is_high;
82 return IRQ_WAKE_THREAD;
83 }
84
cec_5v_gpio_irq_handler_thread(int irq,void * priv)85 static irqreturn_t cec_5v_gpio_irq_handler_thread(int irq, void *priv)
86 {
87 struct cec_gpio *cec = priv;
88
89 cec_queue_pin_5v_event(cec->adap, cec->v5_is_high, cec->v5_ts);
90 return IRQ_HANDLED;
91 }
92
cec_hpd_gpio_irq_handler(int irq,void * priv)93 static irqreturn_t cec_hpd_gpio_irq_handler(int irq, void *priv)
94 {
95 struct cec_gpio *cec = priv;
96 int val = gpiod_get_value(cec->hpd_gpio);
97 bool is_high = val > 0;
98
99 if (val < 0 || is_high == cec->hpd_is_high)
100 return IRQ_HANDLED;
101 cec->hpd_ts = ktime_get();
102 cec->hpd_is_high = is_high;
103 return IRQ_WAKE_THREAD;
104 }
105
cec_gpio_irq_handler(int irq,void * priv)106 static irqreturn_t cec_gpio_irq_handler(int irq, void *priv)
107 {
108 struct cec_gpio *cec = priv;
109 int val = gpiod_get_value(cec->cec_gpio);
110
111 if (val >= 0)
112 cec_pin_changed(cec->adap, val > 0);
113 return IRQ_HANDLED;
114 }
115
cec_gpio_enable_irq(struct cec_adapter * adap)116 static bool cec_gpio_enable_irq(struct cec_adapter *adap)
117 {
118 struct cec_gpio *cec = cec_get_drvdata(adap);
119
120 enable_irq(cec->cec_irq);
121 return true;
122 }
123
cec_gpio_disable_irq(struct cec_adapter * adap)124 static void cec_gpio_disable_irq(struct cec_adapter *adap)
125 {
126 struct cec_gpio *cec = cec_get_drvdata(adap);
127
128 disable_irq(cec->cec_irq);
129 }
130
cec_gpio_status(struct cec_adapter * adap,struct seq_file * file)131 static void cec_gpio_status(struct cec_adapter *adap, struct seq_file *file)
132 {
133 struct cec_gpio *cec = cec_get_drvdata(adap);
134
135 seq_printf(file, "mode: %s\n", cec->cec_is_low ? "low-drive" : "read");
136 seq_printf(file, "using irq: %d\n", cec->cec_irq);
137 if (cec->hpd_gpio)
138 seq_printf(file, "hpd: %s\n",
139 cec->hpd_is_high ? "high" : "low");
140 if (cec->v5_gpio)
141 seq_printf(file, "5V: %s\n",
142 cec->v5_is_high ? "high" : "low");
143 }
144
cec_gpio_read_hpd(struct cec_adapter * adap)145 static int cec_gpio_read_hpd(struct cec_adapter *adap)
146 {
147 struct cec_gpio *cec = cec_get_drvdata(adap);
148
149 if (!cec->hpd_gpio)
150 return -ENOTTY;
151 return gpiod_get_value(cec->hpd_gpio);
152 }
153
cec_gpio_read_5v(struct cec_adapter * adap)154 static int cec_gpio_read_5v(struct cec_adapter *adap)
155 {
156 struct cec_gpio *cec = cec_get_drvdata(adap);
157
158 if (!cec->v5_gpio)
159 return -ENOTTY;
160 return gpiod_get_value(cec->v5_gpio);
161 }
162
163 static const struct cec_pin_ops cec_gpio_pin_ops = {
164 .read = cec_gpio_read,
165 .low = cec_gpio_low,
166 .high = cec_gpio_high,
167 .enable_irq = cec_gpio_enable_irq,
168 .disable_irq = cec_gpio_disable_irq,
169 .status = cec_gpio_status,
170 .read_hpd = cec_gpio_read_hpd,
171 .read_5v = cec_gpio_read_5v,
172 };
173
cec_gpio_probe(struct platform_device * pdev)174 static int cec_gpio_probe(struct platform_device *pdev)
175 {
176 struct device *dev = &pdev->dev;
177 struct device *hdmi_dev;
178 struct cec_gpio *cec;
179 u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN;
180 int ret;
181
182 hdmi_dev = cec_notifier_parse_hdmi_phandle(dev);
183 if (PTR_ERR(hdmi_dev) == -EPROBE_DEFER)
184 return PTR_ERR(hdmi_dev);
185 if (IS_ERR(hdmi_dev))
186 caps |= CEC_CAP_PHYS_ADDR;
187
188 cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL);
189 if (!cec)
190 return -ENOMEM;
191
192 cec->dev = dev;
193
194 cec->cec_gpio = devm_gpiod_get(dev, "cec", GPIOD_OUT_HIGH_OPEN_DRAIN);
195 if (IS_ERR(cec->cec_gpio))
196 return PTR_ERR(cec->cec_gpio);
197 cec->cec_irq = gpiod_to_irq(cec->cec_gpio);
198
199 cec->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
200 if (IS_ERR(cec->hpd_gpio))
201 return PTR_ERR(cec->hpd_gpio);
202
203 cec->v5_gpio = devm_gpiod_get_optional(dev, "v5", GPIOD_IN);
204 if (IS_ERR(cec->v5_gpio))
205 return PTR_ERR(cec->v5_gpio);
206
207 cec->adap = cec_pin_allocate_adapter(&cec_gpio_pin_ops,
208 cec, pdev->name, caps);
209 if (IS_ERR(cec->adap))
210 return PTR_ERR(cec->adap);
211
212 ret = devm_request_irq(dev, cec->cec_irq, cec_gpio_irq_handler,
213 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN,
214 cec->adap->name, cec);
215 if (ret)
216 goto del_adap;
217
218 if (cec->hpd_gpio) {
219 cec->hpd_irq = gpiod_to_irq(cec->hpd_gpio);
220 ret = devm_request_threaded_irq(dev, cec->hpd_irq,
221 cec_hpd_gpio_irq_handler,
222 cec_hpd_gpio_irq_handler_thread,
223 IRQF_ONESHOT |
224 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
225 "hpd-gpio", cec);
226 if (ret)
227 goto del_adap;
228 }
229
230 if (cec->v5_gpio) {
231 cec->v5_irq = gpiod_to_irq(cec->v5_gpio);
232 ret = devm_request_threaded_irq(dev, cec->v5_irq,
233 cec_5v_gpio_irq_handler,
234 cec_5v_gpio_irq_handler_thread,
235 IRQF_ONESHOT |
236 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
237 "v5-gpio", cec);
238 if (ret)
239 goto del_adap;
240 }
241
242 if (!IS_ERR(hdmi_dev)) {
243 cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL,
244 cec->adap);
245 if (!cec->notifier) {
246 ret = -ENOMEM;
247 goto del_adap;
248 }
249 }
250
251 ret = cec_register_adapter(cec->adap, &pdev->dev);
252 if (ret)
253 goto unreg_notifier;
254
255 platform_set_drvdata(pdev, cec);
256 return 0;
257
258 unreg_notifier:
259 cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
260 del_adap:
261 cec_delete_adapter(cec->adap);
262 return ret;
263 }
264
cec_gpio_remove(struct platform_device * pdev)265 static void cec_gpio_remove(struct platform_device *pdev)
266 {
267 struct cec_gpio *cec = platform_get_drvdata(pdev);
268
269 cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
270 cec_unregister_adapter(cec->adap);
271 }
272
273 static const struct of_device_id cec_gpio_match[] = {
274 {
275 .compatible = "cec-gpio",
276 },
277 {},
278 };
279 MODULE_DEVICE_TABLE(of, cec_gpio_match);
280
281 static struct platform_driver cec_gpio_pdrv = {
282 .probe = cec_gpio_probe,
283 .remove = cec_gpio_remove,
284 .driver = {
285 .name = "cec-gpio",
286 .of_match_table = cec_gpio_match,
287 },
288 };
289
290 module_platform_driver(cec_gpio_pdrv);
291
292 MODULE_AUTHOR("Hans Verkuil <[email protected]>");
293 MODULE_LICENSE("GPL v2");
294 MODULE_DESCRIPTION("CEC GPIO driver");
295