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