1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
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>
15 struct cec_adapter
*adap
;
16 struct cec_notifier
*notifier
;
19 struct gpio_desc
*cec_gpio
;
23 struct gpio_desc
*hpd_gpio
;
28 struct gpio_desc
*v5_gpio
;
34 static int cec_gpio_read(struct cec_adapter
*adap
)
36 struct cec_gpio
*cec
= cec_get_drvdata(adap
);
40 return gpiod_get_value(cec
->cec_gpio
);
43 static void cec_gpio_high(struct cec_adapter
*adap
)
45 struct cec_gpio
*cec
= cec_get_drvdata(adap
);
49 cec
->cec_is_low
= false;
50 gpiod_set_value(cec
->cec_gpio
, 1);
53 static void cec_gpio_low(struct cec_adapter
*adap
)
55 struct cec_gpio
*cec
= cec_get_drvdata(adap
);
59 cec
->cec_is_low
= true;
60 gpiod_set_value(cec
->cec_gpio
, 0);
63 static irqreturn_t
cec_hpd_gpio_irq_handler_thread(int irq
, void *priv
)
65 struct cec_gpio
*cec
= priv
;
67 cec_queue_pin_hpd_event(cec
->adap
, cec
->hpd_is_high
, cec
->hpd_ts
);
71 static irqreturn_t
cec_5v_gpio_irq_handler(int irq
, void *priv
)
73 struct cec_gpio
*cec
= priv
;
74 int val
= gpiod_get_value(cec
->v5_gpio
);
75 bool is_high
= val
> 0;
77 if (val
< 0 || is_high
== cec
->v5_is_high
)
79 cec
->v5_ts
= ktime_get();
80 cec
->v5_is_high
= is_high
;
81 return IRQ_WAKE_THREAD
;
84 static irqreturn_t
cec_5v_gpio_irq_handler_thread(int irq
, void *priv
)
86 struct cec_gpio
*cec
= priv
;
88 cec_queue_pin_5v_event(cec
->adap
, cec
->v5_is_high
, cec
->v5_ts
);
92 static irqreturn_t
cec_hpd_gpio_irq_handler(int irq
, void *priv
)
94 struct cec_gpio
*cec
= priv
;
95 int val
= gpiod_get_value(cec
->hpd_gpio
);
96 bool is_high
= val
> 0;
98 if (val
< 0 || is_high
== cec
->hpd_is_high
)
100 cec
->hpd_ts
= ktime_get();
101 cec
->hpd_is_high
= is_high
;
102 return IRQ_WAKE_THREAD
;
105 static irqreturn_t
cec_gpio_irq_handler(int irq
, void *priv
)
107 struct cec_gpio
*cec
= priv
;
108 int val
= gpiod_get_value(cec
->cec_gpio
);
111 cec_pin_changed(cec
->adap
, val
> 0);
115 static bool cec_gpio_enable_irq(struct cec_adapter
*adap
)
117 struct cec_gpio
*cec
= cec_get_drvdata(adap
);
119 enable_irq(cec
->cec_irq
);
123 static void cec_gpio_disable_irq(struct cec_adapter
*adap
)
125 struct cec_gpio
*cec
= cec_get_drvdata(adap
);
127 disable_irq(cec
->cec_irq
);
130 static void cec_gpio_status(struct cec_adapter
*adap
, struct seq_file
*file
)
132 struct cec_gpio
*cec
= cec_get_drvdata(adap
);
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
);
137 seq_printf(file
, "hpd: %s\n",
138 cec
->hpd_is_high
? "high" : "low");
140 seq_printf(file
, "5V: %s\n",
141 cec
->v5_is_high
? "high" : "low");
144 static int cec_gpio_read_hpd(struct cec_adapter
*adap
)
146 struct cec_gpio
*cec
= cec_get_drvdata(adap
);
150 return gpiod_get_value(cec
->hpd_gpio
);
153 static int cec_gpio_read_5v(struct cec_adapter
*adap
)
155 struct cec_gpio
*cec
= cec_get_drvdata(adap
);
159 return gpiod_get_value(cec
->v5_gpio
);
162 static void cec_gpio_free(struct cec_adapter
*adap
)
164 cec_gpio_disable_irq(adap
);
167 static const struct cec_pin_ops cec_gpio_pin_ops
= {
168 .read
= cec_gpio_read
,
170 .high
= cec_gpio_high
,
171 .enable_irq
= cec_gpio_enable_irq
,
172 .disable_irq
= cec_gpio_disable_irq
,
173 .status
= cec_gpio_status
,
174 .free
= cec_gpio_free
,
175 .read_hpd
= cec_gpio_read_hpd
,
176 .read_5v
= cec_gpio_read_5v
,
179 static int cec_gpio_probe(struct platform_device
*pdev
)
181 struct device
*dev
= &pdev
->dev
;
182 struct device
*hdmi_dev
;
183 struct cec_gpio
*cec
;
184 u32 caps
= CEC_CAP_DEFAULTS
| CEC_CAP_MONITOR_ALL
| CEC_CAP_MONITOR_PIN
;
187 hdmi_dev
= cec_notifier_parse_hdmi_phandle(dev
);
188 if (PTR_ERR(hdmi_dev
) == -EPROBE_DEFER
)
189 return PTR_ERR(hdmi_dev
);
190 if (IS_ERR(hdmi_dev
))
191 caps
|= CEC_CAP_PHYS_ADDR
;
193 cec
= devm_kzalloc(dev
, sizeof(*cec
), GFP_KERNEL
);
199 cec
->cec_gpio
= devm_gpiod_get(dev
, "cec", GPIOD_OUT_HIGH_OPEN_DRAIN
);
200 if (IS_ERR(cec
->cec_gpio
))
201 return PTR_ERR(cec
->cec_gpio
);
202 cec
->cec_irq
= gpiod_to_irq(cec
->cec_gpio
);
204 cec
->hpd_gpio
= devm_gpiod_get_optional(dev
, "hpd", GPIOD_IN
);
205 if (IS_ERR(cec
->hpd_gpio
))
206 return PTR_ERR(cec
->hpd_gpio
);
208 cec
->v5_gpio
= devm_gpiod_get_optional(dev
, "v5", GPIOD_IN
);
209 if (IS_ERR(cec
->v5_gpio
))
210 return PTR_ERR(cec
->v5_gpio
);
212 cec
->adap
= cec_pin_allocate_adapter(&cec_gpio_pin_ops
,
213 cec
, pdev
->name
, caps
);
214 if (IS_ERR(cec
->adap
))
215 return PTR_ERR(cec
->adap
);
217 ret
= devm_request_irq(dev
, cec
->cec_irq
, cec_gpio_irq_handler
,
218 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
219 cec
->adap
->name
, cec
);
223 cec_gpio_disable_irq(cec
->adap
);
226 cec
->hpd_irq
= gpiod_to_irq(cec
->hpd_gpio
);
227 ret
= devm_request_threaded_irq(dev
, cec
->hpd_irq
,
228 cec_hpd_gpio_irq_handler
,
229 cec_hpd_gpio_irq_handler_thread
,
231 IRQF_TRIGGER_FALLING
| IRQF_TRIGGER_RISING
,
238 cec
->v5_irq
= gpiod_to_irq(cec
->v5_gpio
);
239 ret
= devm_request_threaded_irq(dev
, cec
->v5_irq
,
240 cec_5v_gpio_irq_handler
,
241 cec_5v_gpio_irq_handler_thread
,
243 IRQF_TRIGGER_FALLING
| IRQF_TRIGGER_RISING
,
249 if (!IS_ERR(hdmi_dev
)) {
250 cec
->notifier
= cec_notifier_cec_adap_register(hdmi_dev
, NULL
,
252 if (!cec
->notifier
) {
258 ret
= cec_register_adapter(cec
->adap
, &pdev
->dev
);
262 platform_set_drvdata(pdev
, cec
);
266 cec_notifier_cec_adap_unregister(cec
->notifier
, cec
->adap
);
268 cec_delete_adapter(cec
->adap
);
272 static int cec_gpio_remove(struct platform_device
*pdev
)
274 struct cec_gpio
*cec
= platform_get_drvdata(pdev
);
276 cec_notifier_cec_adap_unregister(cec
->notifier
, cec
->adap
);
277 cec_unregister_adapter(cec
->adap
);
281 static const struct of_device_id cec_gpio_match
[] = {
283 .compatible
= "cec-gpio",
287 MODULE_DEVICE_TABLE(of
, cec_gpio_match
);
289 static struct platform_driver cec_gpio_pdrv
= {
290 .probe
= cec_gpio_probe
,
291 .remove
= cec_gpio_remove
,
294 .of_match_table
= cec_gpio_match
,
298 module_platform_driver(cec_gpio_pdrv
);
300 MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>");
301 MODULE_LICENSE("GPL v2");
302 MODULE_DESCRIPTION("CEC GPIO driver");