linux/drivers/media/cec/platform/s5p/s5p_cec.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/* drivers/media/platform/s5p-cec/s5p_cec.c
   3 *
   4 * Samsung S5P CEC driver
   5 *
   6 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
   7 *
   8 * This driver is based on the "cec interface driver for exynos soc" by
   9 * SangPil Moon.
  10 */
  11
  12#include <linux/clk.h>
  13#include <linux/interrupt.h>
  14#include <linux/kernel.h>
  15#include <linux/mfd/syscon.h>
  16#include <linux/module.h>
  17#include <linux/of.h>
  18#include <linux/of_platform.h>
  19#include <linux/platform_device.h>
  20#include <linux/pm_runtime.h>
  21#include <linux/timer.h>
  22#include <linux/workqueue.h>
  23#include <media/cec.h>
  24#include <media/cec-notifier.h>
  25
  26#include "exynos_hdmi_cec.h"
  27#include "regs-cec.h"
  28#include "s5p_cec.h"
  29
  30#define CEC_NAME        "s5p-cec"
  31
  32static int debug;
  33module_param(debug, int, 0644);
  34MODULE_PARM_DESC(debug, "debug level (0-2)");
  35
  36static int s5p_cec_adap_enable(struct cec_adapter *adap, bool enable)
  37{
  38        int ret;
  39        struct s5p_cec_dev *cec = cec_get_drvdata(adap);
  40
  41        if (enable) {
  42                ret = pm_runtime_resume_and_get(cec->dev);
  43                if (ret < 0)
  44                        return ret;
  45
  46                s5p_cec_reset(cec);
  47
  48                s5p_cec_set_divider(cec);
  49                s5p_cec_threshold(cec);
  50
  51                s5p_cec_unmask_tx_interrupts(cec);
  52                s5p_cec_unmask_rx_interrupts(cec);
  53                s5p_cec_enable_rx(cec);
  54        } else {
  55                s5p_cec_mask_tx_interrupts(cec);
  56                s5p_cec_mask_rx_interrupts(cec);
  57                pm_runtime_put(cec->dev);
  58        }
  59
  60        return 0;
  61}
  62
  63static int s5p_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
  64{
  65        struct s5p_cec_dev *cec = cec_get_drvdata(adap);
  66
  67        s5p_cec_set_addr(cec, addr);
  68        return 0;
  69}
  70
  71static int s5p_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
  72                                 u32 signal_free_time, struct cec_msg *msg)
  73{
  74        struct s5p_cec_dev *cec = cec_get_drvdata(adap);
  75
  76        /*
  77         * Unclear if 0 retries are allowed by the hardware, so have 1 as
  78         * the minimum.
  79         */
  80        s5p_cec_copy_packet(cec, msg->msg, msg->len, max(1, attempts - 1));
  81        return 0;
  82}
  83
  84static irqreturn_t s5p_cec_irq_handler(int irq, void *priv)
  85{
  86        struct s5p_cec_dev *cec = priv;
  87        u32 status = 0;
  88
  89        status = s5p_cec_get_status(cec);
  90
  91        dev_dbg(cec->dev, "irq received\n");
  92
  93        if (status & CEC_STATUS_TX_DONE) {
  94                if (status & CEC_STATUS_TX_NACK) {
  95                        dev_dbg(cec->dev, "CEC_STATUS_TX_NACK set\n");
  96                        cec->tx = STATE_NACK;
  97                } else if (status & CEC_STATUS_TX_ERROR) {
  98                        dev_dbg(cec->dev, "CEC_STATUS_TX_ERROR set\n");
  99                        cec->tx = STATE_ERROR;
 100                } else {
 101                        dev_dbg(cec->dev, "CEC_STATUS_TX_DONE\n");
 102                        cec->tx = STATE_DONE;
 103                }
 104                s5p_clr_pending_tx(cec);
 105        }
 106
 107        if (status & CEC_STATUS_RX_DONE) {
 108                if (status & CEC_STATUS_RX_ERROR) {
 109                        dev_dbg(cec->dev, "CEC_STATUS_RX_ERROR set\n");
 110                        s5p_cec_rx_reset(cec);
 111                        s5p_cec_enable_rx(cec);
 112                } else {
 113                        dev_dbg(cec->dev, "CEC_STATUS_RX_DONE set\n");
 114                        if (cec->rx != STATE_IDLE)
 115                                dev_dbg(cec->dev, "Buffer overrun (worker did not process previous message)\n");
 116                        cec->rx = STATE_BUSY;
 117                        cec->msg.len = status >> 24;
 118                        cec->msg.rx_status = CEC_RX_STATUS_OK;
 119                        s5p_cec_get_rx_buf(cec, cec->msg.len,
 120                                        cec->msg.msg);
 121                        cec->rx = STATE_DONE;
 122                        s5p_cec_enable_rx(cec);
 123                }
 124                /* Clear interrupt pending bit */
 125                s5p_clr_pending_rx(cec);
 126        }
 127        return IRQ_WAKE_THREAD;
 128}
 129
 130static irqreturn_t s5p_cec_irq_handler_thread(int irq, void *priv)
 131{
 132        struct s5p_cec_dev *cec = priv;
 133
 134        dev_dbg(cec->dev, "irq processing thread\n");
 135        switch (cec->tx) {
 136        case STATE_DONE:
 137                cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);
 138                cec->tx = STATE_IDLE;
 139                break;
 140        case STATE_NACK:
 141                cec_transmit_done(cec->adap,
 142                        CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_NACK,
 143                        0, 1, 0, 0);
 144                cec->tx = STATE_IDLE;
 145                break;
 146        case STATE_ERROR:
 147                cec_transmit_done(cec->adap,
 148                        CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_ERROR,
 149                        0, 0, 0, 1);
 150                cec->tx = STATE_IDLE;
 151                break;
 152        case STATE_BUSY:
 153                dev_err(cec->dev, "state set to busy, this should not occur here\n");
 154                break;
 155        default:
 156                break;
 157        }
 158
 159        switch (cec->rx) {
 160        case STATE_DONE:
 161                cec_received_msg(cec->adap, &cec->msg);
 162                cec->rx = STATE_IDLE;
 163                break;
 164        default:
 165                break;
 166        }
 167
 168        return IRQ_HANDLED;
 169}
 170
 171static const struct cec_adap_ops s5p_cec_adap_ops = {
 172        .adap_enable = s5p_cec_adap_enable,
 173        .adap_log_addr = s5p_cec_adap_log_addr,
 174        .adap_transmit = s5p_cec_adap_transmit,
 175};
 176
 177static int s5p_cec_probe(struct platform_device *pdev)
 178{
 179        struct device *dev = &pdev->dev;
 180        struct device *hdmi_dev;
 181        struct resource *res;
 182        struct s5p_cec_dev *cec;
 183        bool needs_hpd = of_property_read_bool(pdev->dev.of_node, "needs-hpd");
 184        int ret;
 185
 186        hdmi_dev = cec_notifier_parse_hdmi_phandle(dev);
 187
 188        if (IS_ERR(hdmi_dev))
 189                return PTR_ERR(hdmi_dev);
 190
 191        cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL);
 192        if (!cec)
 193                return -ENOMEM;
 194
 195        cec->dev = dev;
 196
 197        cec->irq = platform_get_irq(pdev, 0);
 198        if (cec->irq < 0)
 199                return cec->irq;
 200
 201        ret = devm_request_threaded_irq(dev, cec->irq, s5p_cec_irq_handler,
 202                s5p_cec_irq_handler_thread, 0, pdev->name, cec);
 203        if (ret)
 204                return ret;
 205
 206        cec->clk = devm_clk_get(dev, "hdmicec");
 207        if (IS_ERR(cec->clk))
 208                return PTR_ERR(cec->clk);
 209
 210        cec->pmu = syscon_regmap_lookup_by_phandle(dev->of_node,
 211                                                 "samsung,syscon-phandle");
 212        if (IS_ERR(cec->pmu))
 213                return -EPROBE_DEFER;
 214
 215        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 216        cec->reg = devm_ioremap_resource(dev, res);
 217        if (IS_ERR(cec->reg))
 218                return PTR_ERR(cec->reg);
 219
 220        cec->adap = cec_allocate_adapter(&s5p_cec_adap_ops, cec, CEC_NAME,
 221                CEC_CAP_DEFAULTS | (needs_hpd ? CEC_CAP_NEEDS_HPD : 0) |
 222                CEC_CAP_CONNECTOR_INFO, 1);
 223        ret = PTR_ERR_OR_ZERO(cec->adap);
 224        if (ret)
 225                return ret;
 226
 227        cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL,
 228                                                       cec->adap);
 229        if (!cec->notifier) {
 230                ret = -ENOMEM;
 231                goto err_delete_adapter;
 232        }
 233
 234        ret = cec_register_adapter(cec->adap, &pdev->dev);
 235        if (ret)
 236                goto err_notifier;
 237
 238        platform_set_drvdata(pdev, cec);
 239        pm_runtime_enable(dev);
 240
 241        dev_dbg(dev, "successfully probed\n");
 242        return 0;
 243
 244err_notifier:
 245        cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
 246
 247err_delete_adapter:
 248        cec_delete_adapter(cec->adap);
 249        return ret;
 250}
 251
 252static int s5p_cec_remove(struct platform_device *pdev)
 253{
 254        struct s5p_cec_dev *cec = platform_get_drvdata(pdev);
 255
 256        cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
 257        cec_unregister_adapter(cec->adap);
 258        pm_runtime_disable(&pdev->dev);
 259        return 0;
 260}
 261
 262static int __maybe_unused s5p_cec_runtime_suspend(struct device *dev)
 263{
 264        struct s5p_cec_dev *cec = dev_get_drvdata(dev);
 265
 266        clk_disable_unprepare(cec->clk);
 267        return 0;
 268}
 269
 270static int __maybe_unused s5p_cec_runtime_resume(struct device *dev)
 271{
 272        struct s5p_cec_dev *cec = dev_get_drvdata(dev);
 273        int ret;
 274
 275        ret = clk_prepare_enable(cec->clk);
 276        if (ret < 0)
 277                return ret;
 278        return 0;
 279}
 280
 281static const struct dev_pm_ops s5p_cec_pm_ops = {
 282        SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
 283                                pm_runtime_force_resume)
 284        SET_RUNTIME_PM_OPS(s5p_cec_runtime_suspend, s5p_cec_runtime_resume,
 285                           NULL)
 286};
 287
 288static const struct of_device_id s5p_cec_match[] = {
 289        {
 290                .compatible     = "samsung,s5p-cec",
 291        },
 292        {},
 293};
 294MODULE_DEVICE_TABLE(of, s5p_cec_match);
 295
 296static struct platform_driver s5p_cec_pdrv = {
 297        .probe  = s5p_cec_probe,
 298        .remove = s5p_cec_remove,
 299        .driver = {
 300                .name           = CEC_NAME,
 301                .of_match_table = s5p_cec_match,
 302                .pm             = &s5p_cec_pm_ops,
 303        },
 304};
 305
 306module_platform_driver(s5p_cec_pdrv);
 307
 308MODULE_AUTHOR("Kamil Debski <kamil@wypas.org>");
 309MODULE_LICENSE("GPL");
 310MODULE_DESCRIPTION("Samsung S5P CEC driver");
 311