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