14be5e864SMauro Carvalho Chehab // SPDX-License-Identifier: GPL-2.0-or-later
24be5e864SMauro Carvalho Chehab /* drivers/media/platform/s5p-cec/s5p_cec.c
34be5e864SMauro Carvalho Chehab *
44be5e864SMauro Carvalho Chehab * Samsung S5P CEC driver
54be5e864SMauro Carvalho Chehab *
64be5e864SMauro Carvalho Chehab * Copyright (c) 2014 Samsung Electronics Co., Ltd.
74be5e864SMauro Carvalho Chehab *
84be5e864SMauro Carvalho Chehab * This driver is based on the "cec interface driver for exynos soc" by
94be5e864SMauro Carvalho Chehab * SangPil Moon.
104be5e864SMauro Carvalho Chehab */
114be5e864SMauro Carvalho Chehab
124be5e864SMauro Carvalho Chehab #include <linux/clk.h>
134be5e864SMauro Carvalho Chehab #include <linux/interrupt.h>
144be5e864SMauro Carvalho Chehab #include <linux/kernel.h>
154be5e864SMauro Carvalho Chehab #include <linux/mfd/syscon.h>
164be5e864SMauro Carvalho Chehab #include <linux/module.h>
174be5e864SMauro Carvalho Chehab #include <linux/of.h>
184be5e864SMauro Carvalho Chehab #include <linux/of_platform.h>
194be5e864SMauro Carvalho Chehab #include <linux/platform_device.h>
204be5e864SMauro Carvalho Chehab #include <linux/pm_runtime.h>
214be5e864SMauro Carvalho Chehab #include <linux/timer.h>
224be5e864SMauro Carvalho Chehab #include <linux/workqueue.h>
234be5e864SMauro Carvalho Chehab #include <media/cec.h>
244be5e864SMauro Carvalho Chehab #include <media/cec-notifier.h>
254be5e864SMauro Carvalho Chehab
264be5e864SMauro Carvalho Chehab #include "exynos_hdmi_cec.h"
274be5e864SMauro Carvalho Chehab #include "regs-cec.h"
284be5e864SMauro Carvalho Chehab #include "s5p_cec.h"
294be5e864SMauro Carvalho Chehab
304be5e864SMauro Carvalho Chehab #define CEC_NAME "s5p-cec"
314be5e864SMauro Carvalho Chehab
324be5e864SMauro Carvalho Chehab static int debug;
334be5e864SMauro Carvalho Chehab module_param(debug, int, 0644);
344be5e864SMauro Carvalho Chehab MODULE_PARM_DESC(debug, "debug level (0-2)");
354be5e864SMauro Carvalho Chehab
s5p_cec_adap_enable(struct cec_adapter * adap,bool enable)364be5e864SMauro Carvalho Chehab static int s5p_cec_adap_enable(struct cec_adapter *adap, bool enable)
374be5e864SMauro Carvalho Chehab {
38fdc34e82SMauro Carvalho Chehab int ret;
394be5e864SMauro Carvalho Chehab struct s5p_cec_dev *cec = cec_get_drvdata(adap);
404be5e864SMauro Carvalho Chehab
414be5e864SMauro Carvalho Chehab if (enable) {
42fdc34e82SMauro Carvalho Chehab ret = pm_runtime_resume_and_get(cec->dev);
43fdc34e82SMauro Carvalho Chehab if (ret < 0)
44fdc34e82SMauro Carvalho Chehab return ret;
454be5e864SMauro Carvalho Chehab
464be5e864SMauro Carvalho Chehab s5p_cec_reset(cec);
474be5e864SMauro Carvalho Chehab
484be5e864SMauro Carvalho Chehab s5p_cec_set_divider(cec);
494be5e864SMauro Carvalho Chehab s5p_cec_threshold(cec);
504be5e864SMauro Carvalho Chehab
514be5e864SMauro Carvalho Chehab s5p_cec_unmask_tx_interrupts(cec);
524be5e864SMauro Carvalho Chehab s5p_cec_unmask_rx_interrupts(cec);
534be5e864SMauro Carvalho Chehab s5p_cec_enable_rx(cec);
544be5e864SMauro Carvalho Chehab } else {
554be5e864SMauro Carvalho Chehab s5p_cec_mask_tx_interrupts(cec);
564be5e864SMauro Carvalho Chehab s5p_cec_mask_rx_interrupts(cec);
57747bad54SMauro Carvalho Chehab pm_runtime_put(cec->dev);
584be5e864SMauro Carvalho Chehab }
594be5e864SMauro Carvalho Chehab
604be5e864SMauro Carvalho Chehab return 0;
614be5e864SMauro Carvalho Chehab }
624be5e864SMauro Carvalho Chehab
s5p_cec_adap_log_addr(struct cec_adapter * adap,u8 addr)634be5e864SMauro Carvalho Chehab static int s5p_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
644be5e864SMauro Carvalho Chehab {
654be5e864SMauro Carvalho Chehab struct s5p_cec_dev *cec = cec_get_drvdata(adap);
664be5e864SMauro Carvalho Chehab
674be5e864SMauro Carvalho Chehab s5p_cec_set_addr(cec, addr);
684be5e864SMauro Carvalho Chehab return 0;
694be5e864SMauro Carvalho Chehab }
704be5e864SMauro Carvalho Chehab
s5p_cec_adap_transmit(struct cec_adapter * adap,u8 attempts,u32 signal_free_time,struct cec_msg * msg)714be5e864SMauro Carvalho Chehab static int s5p_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
724be5e864SMauro Carvalho Chehab u32 signal_free_time, struct cec_msg *msg)
734be5e864SMauro Carvalho Chehab {
744be5e864SMauro Carvalho Chehab struct s5p_cec_dev *cec = cec_get_drvdata(adap);
754be5e864SMauro Carvalho Chehab
764be5e864SMauro Carvalho Chehab /*
774be5e864SMauro Carvalho Chehab * Unclear if 0 retries are allowed by the hardware, so have 1 as
784be5e864SMauro Carvalho Chehab * the minimum.
794be5e864SMauro Carvalho Chehab */
804be5e864SMauro Carvalho Chehab s5p_cec_copy_packet(cec, msg->msg, msg->len, max(1, attempts - 1));
814be5e864SMauro Carvalho Chehab return 0;
824be5e864SMauro Carvalho Chehab }
834be5e864SMauro Carvalho Chehab
s5p_cec_irq_handler(int irq,void * priv)844be5e864SMauro Carvalho Chehab static irqreturn_t s5p_cec_irq_handler(int irq, void *priv)
854be5e864SMauro Carvalho Chehab {
864be5e864SMauro Carvalho Chehab struct s5p_cec_dev *cec = priv;
874be5e864SMauro Carvalho Chehab u32 status = 0;
884be5e864SMauro Carvalho Chehab
894be5e864SMauro Carvalho Chehab status = s5p_cec_get_status(cec);
904be5e864SMauro Carvalho Chehab
914be5e864SMauro Carvalho Chehab dev_dbg(cec->dev, "irq received\n");
924be5e864SMauro Carvalho Chehab
934be5e864SMauro Carvalho Chehab if (status & CEC_STATUS_TX_DONE) {
944be5e864SMauro Carvalho Chehab if (status & CEC_STATUS_TX_NACK) {
954be5e864SMauro Carvalho Chehab dev_dbg(cec->dev, "CEC_STATUS_TX_NACK set\n");
964be5e864SMauro Carvalho Chehab cec->tx = STATE_NACK;
974be5e864SMauro Carvalho Chehab } else if (status & CEC_STATUS_TX_ERROR) {
984be5e864SMauro Carvalho Chehab dev_dbg(cec->dev, "CEC_STATUS_TX_ERROR set\n");
994be5e864SMauro Carvalho Chehab cec->tx = STATE_ERROR;
1004be5e864SMauro Carvalho Chehab } else {
1014be5e864SMauro Carvalho Chehab dev_dbg(cec->dev, "CEC_STATUS_TX_DONE\n");
1024be5e864SMauro Carvalho Chehab cec->tx = STATE_DONE;
1034be5e864SMauro Carvalho Chehab }
1044be5e864SMauro Carvalho Chehab s5p_clr_pending_tx(cec);
1054be5e864SMauro Carvalho Chehab }
1064be5e864SMauro Carvalho Chehab
1074be5e864SMauro Carvalho Chehab if (status & CEC_STATUS_RX_DONE) {
1084be5e864SMauro Carvalho Chehab if (status & CEC_STATUS_RX_ERROR) {
1094be5e864SMauro Carvalho Chehab dev_dbg(cec->dev, "CEC_STATUS_RX_ERROR set\n");
1104be5e864SMauro Carvalho Chehab s5p_cec_rx_reset(cec);
1114be5e864SMauro Carvalho Chehab s5p_cec_enable_rx(cec);
1124be5e864SMauro Carvalho Chehab } else {
1134be5e864SMauro Carvalho Chehab dev_dbg(cec->dev, "CEC_STATUS_RX_DONE set\n");
1144be5e864SMauro Carvalho Chehab if (cec->rx != STATE_IDLE)
1154be5e864SMauro Carvalho Chehab dev_dbg(cec->dev, "Buffer overrun (worker did not process previous message)\n");
1164be5e864SMauro Carvalho Chehab cec->rx = STATE_BUSY;
1174be5e864SMauro Carvalho Chehab cec->msg.len = status >> 24;
11893f65ce0SHans Verkuil if (cec->msg.len > CEC_MAX_MSG_SIZE)
11993f65ce0SHans Verkuil cec->msg.len = CEC_MAX_MSG_SIZE;
1204be5e864SMauro Carvalho Chehab cec->msg.rx_status = CEC_RX_STATUS_OK;
1214be5e864SMauro Carvalho Chehab s5p_cec_get_rx_buf(cec, cec->msg.len,
1224be5e864SMauro Carvalho Chehab cec->msg.msg);
1234be5e864SMauro Carvalho Chehab cec->rx = STATE_DONE;
1244be5e864SMauro Carvalho Chehab s5p_cec_enable_rx(cec);
1254be5e864SMauro Carvalho Chehab }
1264be5e864SMauro Carvalho Chehab /* Clear interrupt pending bit */
1274be5e864SMauro Carvalho Chehab s5p_clr_pending_rx(cec);
1284be5e864SMauro Carvalho Chehab }
1294be5e864SMauro Carvalho Chehab return IRQ_WAKE_THREAD;
1304be5e864SMauro Carvalho Chehab }
1314be5e864SMauro Carvalho Chehab
s5p_cec_irq_handler_thread(int irq,void * priv)1324be5e864SMauro Carvalho Chehab static irqreturn_t s5p_cec_irq_handler_thread(int irq, void *priv)
1334be5e864SMauro Carvalho Chehab {
1344be5e864SMauro Carvalho Chehab struct s5p_cec_dev *cec = priv;
1354be5e864SMauro Carvalho Chehab
1364be5e864SMauro Carvalho Chehab dev_dbg(cec->dev, "irq processing thread\n");
1374be5e864SMauro Carvalho Chehab switch (cec->tx) {
1384be5e864SMauro Carvalho Chehab case STATE_DONE:
1394be5e864SMauro Carvalho Chehab cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);
1404be5e864SMauro Carvalho Chehab cec->tx = STATE_IDLE;
1414be5e864SMauro Carvalho Chehab break;
1424be5e864SMauro Carvalho Chehab case STATE_NACK:
1434be5e864SMauro Carvalho Chehab cec_transmit_done(cec->adap,
1444be5e864SMauro Carvalho Chehab CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_NACK,
1454be5e864SMauro Carvalho Chehab 0, 1, 0, 0);
1464be5e864SMauro Carvalho Chehab cec->tx = STATE_IDLE;
1474be5e864SMauro Carvalho Chehab break;
1484be5e864SMauro Carvalho Chehab case STATE_ERROR:
1494be5e864SMauro Carvalho Chehab cec_transmit_done(cec->adap,
1504be5e864SMauro Carvalho Chehab CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_ERROR,
1514be5e864SMauro Carvalho Chehab 0, 0, 0, 1);
1524be5e864SMauro Carvalho Chehab cec->tx = STATE_IDLE;
1534be5e864SMauro Carvalho Chehab break;
1544be5e864SMauro Carvalho Chehab case STATE_BUSY:
1554be5e864SMauro Carvalho Chehab dev_err(cec->dev, "state set to busy, this should not occur here\n");
1564be5e864SMauro Carvalho Chehab break;
1574be5e864SMauro Carvalho Chehab default:
1584be5e864SMauro Carvalho Chehab break;
1594be5e864SMauro Carvalho Chehab }
1604be5e864SMauro Carvalho Chehab
1614be5e864SMauro Carvalho Chehab switch (cec->rx) {
1624be5e864SMauro Carvalho Chehab case STATE_DONE:
1634be5e864SMauro Carvalho Chehab cec_received_msg(cec->adap, &cec->msg);
1644be5e864SMauro Carvalho Chehab cec->rx = STATE_IDLE;
1654be5e864SMauro Carvalho Chehab break;
1664be5e864SMauro Carvalho Chehab default:
1674be5e864SMauro Carvalho Chehab break;
1684be5e864SMauro Carvalho Chehab }
1694be5e864SMauro Carvalho Chehab
1704be5e864SMauro Carvalho Chehab return IRQ_HANDLED;
1714be5e864SMauro Carvalho Chehab }
1724be5e864SMauro Carvalho Chehab
1734be5e864SMauro Carvalho Chehab static const struct cec_adap_ops s5p_cec_adap_ops = {
1744be5e864SMauro Carvalho Chehab .adap_enable = s5p_cec_adap_enable,
1754be5e864SMauro Carvalho Chehab .adap_log_addr = s5p_cec_adap_log_addr,
1764be5e864SMauro Carvalho Chehab .adap_transmit = s5p_cec_adap_transmit,
1774be5e864SMauro Carvalho Chehab };
1784be5e864SMauro Carvalho Chehab
s5p_cec_probe(struct platform_device * pdev)1794be5e864SMauro Carvalho Chehab static int s5p_cec_probe(struct platform_device *pdev)
1804be5e864SMauro Carvalho Chehab {
1814be5e864SMauro Carvalho Chehab struct device *dev = &pdev->dev;
1824be5e864SMauro Carvalho Chehab struct device *hdmi_dev;
1834be5e864SMauro Carvalho Chehab struct s5p_cec_dev *cec;
1844be5e864SMauro Carvalho Chehab bool needs_hpd = of_property_read_bool(pdev->dev.of_node, "needs-hpd");
1854be5e864SMauro Carvalho Chehab int ret;
1864be5e864SMauro Carvalho Chehab
1874be5e864SMauro Carvalho Chehab hdmi_dev = cec_notifier_parse_hdmi_phandle(dev);
1884be5e864SMauro Carvalho Chehab
1894be5e864SMauro Carvalho Chehab if (IS_ERR(hdmi_dev))
1904be5e864SMauro Carvalho Chehab return PTR_ERR(hdmi_dev);
1914be5e864SMauro Carvalho Chehab
1924be5e864SMauro Carvalho Chehab cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL);
1934be5e864SMauro Carvalho Chehab if (!cec)
1944be5e864SMauro Carvalho Chehab return -ENOMEM;
1954be5e864SMauro Carvalho Chehab
1964be5e864SMauro Carvalho Chehab cec->dev = dev;
1974be5e864SMauro Carvalho Chehab
1984be5e864SMauro Carvalho Chehab cec->irq = platform_get_irq(pdev, 0);
1994be5e864SMauro Carvalho Chehab if (cec->irq < 0)
2004be5e864SMauro Carvalho Chehab return cec->irq;
2014be5e864SMauro Carvalho Chehab
2024be5e864SMauro Carvalho Chehab ret = devm_request_threaded_irq(dev, cec->irq, s5p_cec_irq_handler,
2034be5e864SMauro Carvalho Chehab s5p_cec_irq_handler_thread, 0, pdev->name, cec);
2044be5e864SMauro Carvalho Chehab if (ret)
2054be5e864SMauro Carvalho Chehab return ret;
2064be5e864SMauro Carvalho Chehab
2074be5e864SMauro Carvalho Chehab cec->clk = devm_clk_get(dev, "hdmicec");
2084be5e864SMauro Carvalho Chehab if (IS_ERR(cec->clk))
2094be5e864SMauro Carvalho Chehab return PTR_ERR(cec->clk);
2104be5e864SMauro Carvalho Chehab
2114be5e864SMauro Carvalho Chehab cec->pmu = syscon_regmap_lookup_by_phandle(dev->of_node,
2124be5e864SMauro Carvalho Chehab "samsung,syscon-phandle");
2134be5e864SMauro Carvalho Chehab if (IS_ERR(cec->pmu))
2144be5e864SMauro Carvalho Chehab return -EPROBE_DEFER;
2154be5e864SMauro Carvalho Chehab
216399e0f9aSCai Huoqing cec->reg = devm_platform_ioremap_resource(pdev, 0);
2174be5e864SMauro Carvalho Chehab if (IS_ERR(cec->reg))
2184be5e864SMauro Carvalho Chehab return PTR_ERR(cec->reg);
2194be5e864SMauro Carvalho Chehab
2204be5e864SMauro Carvalho Chehab cec->adap = cec_allocate_adapter(&s5p_cec_adap_ops, cec, CEC_NAME,
2214be5e864SMauro Carvalho Chehab CEC_CAP_DEFAULTS | (needs_hpd ? CEC_CAP_NEEDS_HPD : 0) |
2224be5e864SMauro Carvalho Chehab CEC_CAP_CONNECTOR_INFO, 1);
2234be5e864SMauro Carvalho Chehab ret = PTR_ERR_OR_ZERO(cec->adap);
2244be5e864SMauro Carvalho Chehab if (ret)
2254be5e864SMauro Carvalho Chehab return ret;
2264be5e864SMauro Carvalho Chehab
2274be5e864SMauro Carvalho Chehab cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL,
2284be5e864SMauro Carvalho Chehab cec->adap);
2294be5e864SMauro Carvalho Chehab if (!cec->notifier) {
2304be5e864SMauro Carvalho Chehab ret = -ENOMEM;
2314be5e864SMauro Carvalho Chehab goto err_delete_adapter;
2324be5e864SMauro Carvalho Chehab }
2334be5e864SMauro Carvalho Chehab
2344be5e864SMauro Carvalho Chehab ret = cec_register_adapter(cec->adap, &pdev->dev);
2354be5e864SMauro Carvalho Chehab if (ret)
2364be5e864SMauro Carvalho Chehab goto err_notifier;
2374be5e864SMauro Carvalho Chehab
2384be5e864SMauro Carvalho Chehab platform_set_drvdata(pdev, cec);
2394be5e864SMauro Carvalho Chehab pm_runtime_enable(dev);
2404be5e864SMauro Carvalho Chehab
2414be5e864SMauro Carvalho Chehab dev_dbg(dev, "successfully probed\n");
2424be5e864SMauro Carvalho Chehab return 0;
2434be5e864SMauro Carvalho Chehab
2444be5e864SMauro Carvalho Chehab err_notifier:
2454be5e864SMauro Carvalho Chehab cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
2464be5e864SMauro Carvalho Chehab
2474be5e864SMauro Carvalho Chehab err_delete_adapter:
2484be5e864SMauro Carvalho Chehab cec_delete_adapter(cec->adap);
2494be5e864SMauro Carvalho Chehab return ret;
2504be5e864SMauro Carvalho Chehab }
2514be5e864SMauro Carvalho Chehab
s5p_cec_remove(struct platform_device * pdev)252*3f8b9bbcSUwe Kleine-König static void s5p_cec_remove(struct platform_device *pdev)
2534be5e864SMauro Carvalho Chehab {
2544be5e864SMauro Carvalho Chehab struct s5p_cec_dev *cec = platform_get_drvdata(pdev);
2554be5e864SMauro Carvalho Chehab
2564be5e864SMauro Carvalho Chehab cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
2574be5e864SMauro Carvalho Chehab cec_unregister_adapter(cec->adap);
2584be5e864SMauro Carvalho Chehab pm_runtime_disable(&pdev->dev);
2594be5e864SMauro Carvalho Chehab }
2604be5e864SMauro Carvalho Chehab
s5p_cec_runtime_suspend(struct device * dev)2614be5e864SMauro Carvalho Chehab static int __maybe_unused s5p_cec_runtime_suspend(struct device *dev)
2624be5e864SMauro Carvalho Chehab {
2634be5e864SMauro Carvalho Chehab struct s5p_cec_dev *cec = dev_get_drvdata(dev);
2644be5e864SMauro Carvalho Chehab
2654be5e864SMauro Carvalho Chehab clk_disable_unprepare(cec->clk);
2664be5e864SMauro Carvalho Chehab return 0;
2674be5e864SMauro Carvalho Chehab }
2684be5e864SMauro Carvalho Chehab
s5p_cec_runtime_resume(struct device * dev)2694be5e864SMauro Carvalho Chehab static int __maybe_unused s5p_cec_runtime_resume(struct device *dev)
2704be5e864SMauro Carvalho Chehab {
2714be5e864SMauro Carvalho Chehab struct s5p_cec_dev *cec = dev_get_drvdata(dev);
2724be5e864SMauro Carvalho Chehab int ret;
2734be5e864SMauro Carvalho Chehab
2744be5e864SMauro Carvalho Chehab ret = clk_prepare_enable(cec->clk);
2754be5e864SMauro Carvalho Chehab if (ret < 0)
2764be5e864SMauro Carvalho Chehab return ret;
2774be5e864SMauro Carvalho Chehab return 0;
2784be5e864SMauro Carvalho Chehab }
2794be5e864SMauro Carvalho Chehab
2804be5e864SMauro Carvalho Chehab static const struct dev_pm_ops s5p_cec_pm_ops = {
2814be5e864SMauro Carvalho Chehab SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
2824be5e864SMauro Carvalho Chehab pm_runtime_force_resume)
2834be5e864SMauro Carvalho Chehab SET_RUNTIME_PM_OPS(s5p_cec_runtime_suspend, s5p_cec_runtime_resume,
2844be5e864SMauro Carvalho Chehab NULL)
2854be5e864SMauro Carvalho Chehab };
2864be5e864SMauro Carvalho Chehab
2874be5e864SMauro Carvalho Chehab static const struct of_device_id s5p_cec_match[] = {
2884be5e864SMauro Carvalho Chehab {
2894be5e864SMauro Carvalho Chehab .compatible = "samsung,s5p-cec",
2904be5e864SMauro Carvalho Chehab },
2914be5e864SMauro Carvalho Chehab {},
2924be5e864SMauro Carvalho Chehab };
2934be5e864SMauro Carvalho Chehab MODULE_DEVICE_TABLE(of, s5p_cec_match);
2944be5e864SMauro Carvalho Chehab
2954be5e864SMauro Carvalho Chehab static struct platform_driver s5p_cec_pdrv = {
2964be5e864SMauro Carvalho Chehab .probe = s5p_cec_probe,
297*3f8b9bbcSUwe Kleine-König .remove_new = s5p_cec_remove,
2984be5e864SMauro Carvalho Chehab .driver = {
2994be5e864SMauro Carvalho Chehab .name = CEC_NAME,
3004be5e864SMauro Carvalho Chehab .of_match_table = s5p_cec_match,
3014be5e864SMauro Carvalho Chehab .pm = &s5p_cec_pm_ops,
3024be5e864SMauro Carvalho Chehab },
3034be5e864SMauro Carvalho Chehab };
3044be5e864SMauro Carvalho Chehab
3054be5e864SMauro Carvalho Chehab module_platform_driver(s5p_cec_pdrv);
3064be5e864SMauro Carvalho Chehab
3074be5e864SMauro Carvalho Chehab MODULE_AUTHOR("Kamil Debski <kamil@wypas.org>");
3084be5e864SMauro Carvalho Chehab MODULE_LICENSE("GPL");
3094be5e864SMauro Carvalho Chehab MODULE_DESCRIPTION("Samsung S5P CEC driver");
310