162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/module.h>
762306a36Sopenharmony_ci#include <linux/interrupt.h>
862306a36Sopenharmony_ci#include <linux/delay.h>
962306a36Sopenharmony_ci#include <linux/platform_device.h>
1062306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1162306a36Sopenharmony_ci#include <media/cec-notifier.h>
1262306a36Sopenharmony_ci#include <media/cec-pin.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistruct cec_gpio {
1562306a36Sopenharmony_ci	struct cec_adapter	*adap;
1662306a36Sopenharmony_ci	struct cec_notifier	*notifier;
1762306a36Sopenharmony_ci	struct device		*dev;
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci	struct gpio_desc	*cec_gpio;
2062306a36Sopenharmony_ci	int			cec_irq;
2162306a36Sopenharmony_ci	bool			cec_is_low;
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci	struct gpio_desc	*hpd_gpio;
2462306a36Sopenharmony_ci	int			hpd_irq;
2562306a36Sopenharmony_ci	bool			hpd_is_high;
2662306a36Sopenharmony_ci	ktime_t			hpd_ts;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	struct gpio_desc	*v5_gpio;
2962306a36Sopenharmony_ci	int			v5_irq;
3062306a36Sopenharmony_ci	bool			v5_is_high;
3162306a36Sopenharmony_ci	ktime_t			v5_ts;
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic int cec_gpio_read(struct cec_adapter *adap)
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci	struct cec_gpio *cec = cec_get_drvdata(adap);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	if (cec->cec_is_low)
3962306a36Sopenharmony_ci		return 0;
4062306a36Sopenharmony_ci	return gpiod_get_value(cec->cec_gpio);
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic void cec_gpio_high(struct cec_adapter *adap)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct cec_gpio *cec = cec_get_drvdata(adap);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	if (!cec->cec_is_low)
4862306a36Sopenharmony_ci		return;
4962306a36Sopenharmony_ci	cec->cec_is_low = false;
5062306a36Sopenharmony_ci	gpiod_set_value(cec->cec_gpio, 1);
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic void cec_gpio_low(struct cec_adapter *adap)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct cec_gpio *cec = cec_get_drvdata(adap);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if (cec->cec_is_low)
5862306a36Sopenharmony_ci		return;
5962306a36Sopenharmony_ci	cec->cec_is_low = true;
6062306a36Sopenharmony_ci	gpiod_set_value(cec->cec_gpio, 0);
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic irqreturn_t cec_hpd_gpio_irq_handler_thread(int irq, void *priv)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	struct cec_gpio *cec = priv;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	cec_queue_pin_hpd_event(cec->adap, cec->hpd_is_high, cec->hpd_ts);
6862306a36Sopenharmony_ci	return IRQ_HANDLED;
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic irqreturn_t cec_5v_gpio_irq_handler(int irq, void *priv)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct cec_gpio *cec = priv;
7462306a36Sopenharmony_ci	int val = gpiod_get_value(cec->v5_gpio);
7562306a36Sopenharmony_ci	bool is_high = val > 0;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	if (val < 0 || is_high == cec->v5_is_high)
7862306a36Sopenharmony_ci		return IRQ_HANDLED;
7962306a36Sopenharmony_ci	cec->v5_ts = ktime_get();
8062306a36Sopenharmony_ci	cec->v5_is_high = is_high;
8162306a36Sopenharmony_ci	return IRQ_WAKE_THREAD;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic irqreturn_t cec_5v_gpio_irq_handler_thread(int irq, void *priv)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	struct cec_gpio *cec = priv;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	cec_queue_pin_5v_event(cec->adap, cec->v5_is_high, cec->v5_ts);
8962306a36Sopenharmony_ci	return IRQ_HANDLED;
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic irqreturn_t cec_hpd_gpio_irq_handler(int irq, void *priv)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	struct cec_gpio *cec = priv;
9562306a36Sopenharmony_ci	int val = gpiod_get_value(cec->hpd_gpio);
9662306a36Sopenharmony_ci	bool is_high = val > 0;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (val < 0 || is_high == cec->hpd_is_high)
9962306a36Sopenharmony_ci		return IRQ_HANDLED;
10062306a36Sopenharmony_ci	cec->hpd_ts = ktime_get();
10162306a36Sopenharmony_ci	cec->hpd_is_high = is_high;
10262306a36Sopenharmony_ci	return IRQ_WAKE_THREAD;
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic irqreturn_t cec_gpio_irq_handler(int irq, void *priv)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct cec_gpio *cec = priv;
10862306a36Sopenharmony_ci	int val = gpiod_get_value(cec->cec_gpio);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	if (val >= 0)
11162306a36Sopenharmony_ci		cec_pin_changed(cec->adap, val > 0);
11262306a36Sopenharmony_ci	return IRQ_HANDLED;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic bool cec_gpio_enable_irq(struct cec_adapter *adap)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	struct cec_gpio *cec = cec_get_drvdata(adap);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	enable_irq(cec->cec_irq);
12062306a36Sopenharmony_ci	return true;
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic void cec_gpio_disable_irq(struct cec_adapter *adap)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	struct cec_gpio *cec = cec_get_drvdata(adap);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	disable_irq(cec->cec_irq);
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic void cec_gpio_status(struct cec_adapter *adap, struct seq_file *file)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct cec_gpio *cec = cec_get_drvdata(adap);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	seq_printf(file, "mode: %s\n", cec->cec_is_low ? "low-drive" : "read");
13562306a36Sopenharmony_ci	seq_printf(file, "using irq: %d\n", cec->cec_irq);
13662306a36Sopenharmony_ci	if (cec->hpd_gpio)
13762306a36Sopenharmony_ci		seq_printf(file, "hpd: %s\n",
13862306a36Sopenharmony_ci			   cec->hpd_is_high ? "high" : "low");
13962306a36Sopenharmony_ci	if (cec->v5_gpio)
14062306a36Sopenharmony_ci		seq_printf(file, "5V: %s\n",
14162306a36Sopenharmony_ci			   cec->v5_is_high ? "high" : "low");
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic int cec_gpio_read_hpd(struct cec_adapter *adap)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	struct cec_gpio *cec = cec_get_drvdata(adap);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	if (!cec->hpd_gpio)
14962306a36Sopenharmony_ci		return -ENOTTY;
15062306a36Sopenharmony_ci	return gpiod_get_value(cec->hpd_gpio);
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic int cec_gpio_read_5v(struct cec_adapter *adap)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	struct cec_gpio *cec = cec_get_drvdata(adap);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (!cec->v5_gpio)
15862306a36Sopenharmony_ci		return -ENOTTY;
15962306a36Sopenharmony_ci	return gpiod_get_value(cec->v5_gpio);
16062306a36Sopenharmony_ci}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic const struct cec_pin_ops cec_gpio_pin_ops = {
16362306a36Sopenharmony_ci	.read = cec_gpio_read,
16462306a36Sopenharmony_ci	.low = cec_gpio_low,
16562306a36Sopenharmony_ci	.high = cec_gpio_high,
16662306a36Sopenharmony_ci	.enable_irq = cec_gpio_enable_irq,
16762306a36Sopenharmony_ci	.disable_irq = cec_gpio_disable_irq,
16862306a36Sopenharmony_ci	.status = cec_gpio_status,
16962306a36Sopenharmony_ci	.read_hpd = cec_gpio_read_hpd,
17062306a36Sopenharmony_ci	.read_5v = cec_gpio_read_5v,
17162306a36Sopenharmony_ci};
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic int cec_gpio_probe(struct platform_device *pdev)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
17662306a36Sopenharmony_ci	struct device *hdmi_dev;
17762306a36Sopenharmony_ci	struct cec_gpio *cec;
17862306a36Sopenharmony_ci	u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN;
17962306a36Sopenharmony_ci	int ret;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	hdmi_dev = cec_notifier_parse_hdmi_phandle(dev);
18262306a36Sopenharmony_ci	if (PTR_ERR(hdmi_dev) == -EPROBE_DEFER)
18362306a36Sopenharmony_ci		return PTR_ERR(hdmi_dev);
18462306a36Sopenharmony_ci	if (IS_ERR(hdmi_dev))
18562306a36Sopenharmony_ci		caps |= CEC_CAP_PHYS_ADDR;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL);
18862306a36Sopenharmony_ci	if (!cec)
18962306a36Sopenharmony_ci		return -ENOMEM;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	cec->dev = dev;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	cec->cec_gpio = devm_gpiod_get(dev, "cec", GPIOD_OUT_HIGH_OPEN_DRAIN);
19462306a36Sopenharmony_ci	if (IS_ERR(cec->cec_gpio))
19562306a36Sopenharmony_ci		return PTR_ERR(cec->cec_gpio);
19662306a36Sopenharmony_ci	cec->cec_irq = gpiod_to_irq(cec->cec_gpio);
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	cec->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
19962306a36Sopenharmony_ci	if (IS_ERR(cec->hpd_gpio))
20062306a36Sopenharmony_ci		return PTR_ERR(cec->hpd_gpio);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	cec->v5_gpio = devm_gpiod_get_optional(dev, "v5", GPIOD_IN);
20362306a36Sopenharmony_ci	if (IS_ERR(cec->v5_gpio))
20462306a36Sopenharmony_ci		return PTR_ERR(cec->v5_gpio);
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	cec->adap = cec_pin_allocate_adapter(&cec_gpio_pin_ops,
20762306a36Sopenharmony_ci					     cec, pdev->name, caps);
20862306a36Sopenharmony_ci	if (IS_ERR(cec->adap))
20962306a36Sopenharmony_ci		return PTR_ERR(cec->adap);
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	ret = devm_request_irq(dev, cec->cec_irq, cec_gpio_irq_handler,
21262306a36Sopenharmony_ci			       IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN,
21362306a36Sopenharmony_ci			       cec->adap->name, cec);
21462306a36Sopenharmony_ci	if (ret)
21562306a36Sopenharmony_ci		goto del_adap;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	if (cec->hpd_gpio) {
21862306a36Sopenharmony_ci		cec->hpd_irq = gpiod_to_irq(cec->hpd_gpio);
21962306a36Sopenharmony_ci		ret = devm_request_threaded_irq(dev, cec->hpd_irq,
22062306a36Sopenharmony_ci			cec_hpd_gpio_irq_handler,
22162306a36Sopenharmony_ci			cec_hpd_gpio_irq_handler_thread,
22262306a36Sopenharmony_ci			IRQF_ONESHOT |
22362306a36Sopenharmony_ci			IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
22462306a36Sopenharmony_ci			"hpd-gpio", cec);
22562306a36Sopenharmony_ci		if (ret)
22662306a36Sopenharmony_ci			goto del_adap;
22762306a36Sopenharmony_ci	}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	if (cec->v5_gpio) {
23062306a36Sopenharmony_ci		cec->v5_irq = gpiod_to_irq(cec->v5_gpio);
23162306a36Sopenharmony_ci		ret = devm_request_threaded_irq(dev, cec->v5_irq,
23262306a36Sopenharmony_ci			cec_5v_gpio_irq_handler,
23362306a36Sopenharmony_ci			cec_5v_gpio_irq_handler_thread,
23462306a36Sopenharmony_ci			IRQF_ONESHOT |
23562306a36Sopenharmony_ci			IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
23662306a36Sopenharmony_ci			"v5-gpio", cec);
23762306a36Sopenharmony_ci		if (ret)
23862306a36Sopenharmony_ci			goto del_adap;
23962306a36Sopenharmony_ci	}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	if (!IS_ERR(hdmi_dev)) {
24262306a36Sopenharmony_ci		cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL,
24362306a36Sopenharmony_ci							       cec->adap);
24462306a36Sopenharmony_ci		if (!cec->notifier) {
24562306a36Sopenharmony_ci			ret = -ENOMEM;
24662306a36Sopenharmony_ci			goto del_adap;
24762306a36Sopenharmony_ci		}
24862306a36Sopenharmony_ci	}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	ret = cec_register_adapter(cec->adap, &pdev->dev);
25162306a36Sopenharmony_ci	if (ret)
25262306a36Sopenharmony_ci		goto unreg_notifier;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	platform_set_drvdata(pdev, cec);
25562306a36Sopenharmony_ci	return 0;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ciunreg_notifier:
25862306a36Sopenharmony_ci	cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
25962306a36Sopenharmony_cidel_adap:
26062306a36Sopenharmony_ci	cec_delete_adapter(cec->adap);
26162306a36Sopenharmony_ci	return ret;
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic void cec_gpio_remove(struct platform_device *pdev)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	struct cec_gpio *cec = platform_get_drvdata(pdev);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
26962306a36Sopenharmony_ci	cec_unregister_adapter(cec->adap);
27062306a36Sopenharmony_ci}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_cistatic const struct of_device_id cec_gpio_match[] = {
27362306a36Sopenharmony_ci	{
27462306a36Sopenharmony_ci		.compatible	= "cec-gpio",
27562306a36Sopenharmony_ci	},
27662306a36Sopenharmony_ci	{},
27762306a36Sopenharmony_ci};
27862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, cec_gpio_match);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic struct platform_driver cec_gpio_pdrv = {
28162306a36Sopenharmony_ci	.probe	= cec_gpio_probe,
28262306a36Sopenharmony_ci	.remove_new = cec_gpio_remove,
28362306a36Sopenharmony_ci	.driver = {
28462306a36Sopenharmony_ci		.name		= "cec-gpio",
28562306a36Sopenharmony_ci		.of_match_table	= cec_gpio_match,
28662306a36Sopenharmony_ci	},
28762306a36Sopenharmony_ci};
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cimodule_platform_driver(cec_gpio_pdrv);
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ciMODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>");
29262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
29362306a36Sopenharmony_ciMODULE_DESCRIPTION("CEC GPIO driver");
294