File Index
Files and directories
Sign in
FILE

cec-gpio.c

File preview with syntax highlighting.

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