Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Designware HDMI CEC driver
0004  *
0005  * Copyright (C) 2015-2017 Russell King.
0006  */
0007 #include <linux/interrupt.h>
0008 #include <linux/io.h>
0009 #include <linux/module.h>
0010 #include <linux/platform_device.h>
0011 #include <linux/sched.h>
0012 #include <linux/slab.h>
0013 
0014 #include <drm/drm_edid.h>
0015 
0016 #include <media/cec.h>
0017 #include <media/cec-notifier.h>
0018 
0019 #include "dw-hdmi-cec.h"
0020 
0021 enum {
0022     HDMI_IH_CEC_STAT0   = 0x0106,
0023     HDMI_IH_MUTE_CEC_STAT0  = 0x0186,
0024 
0025     HDMI_CEC_CTRL       = 0x7d00,
0026     CEC_CTRL_START      = BIT(0),
0027     CEC_CTRL_FRAME_TYP  = 3 << 1,
0028     CEC_CTRL_RETRY      = 0 << 1,
0029     CEC_CTRL_NORMAL     = 1 << 1,
0030     CEC_CTRL_IMMED      = 2 << 1,
0031 
0032     HDMI_CEC_STAT       = 0x7d01,
0033     CEC_STAT_DONE       = BIT(0),
0034     CEC_STAT_EOM        = BIT(1),
0035     CEC_STAT_NACK       = BIT(2),
0036     CEC_STAT_ARBLOST    = BIT(3),
0037     CEC_STAT_ERROR_INIT = BIT(4),
0038     CEC_STAT_ERROR_FOLL = BIT(5),
0039     CEC_STAT_WAKEUP     = BIT(6),
0040 
0041     HDMI_CEC_MASK       = 0x7d02,
0042     HDMI_CEC_POLARITY   = 0x7d03,
0043     HDMI_CEC_INT        = 0x7d04,
0044     HDMI_CEC_ADDR_L     = 0x7d05,
0045     HDMI_CEC_ADDR_H     = 0x7d06,
0046     HDMI_CEC_TX_CNT     = 0x7d07,
0047     HDMI_CEC_RX_CNT     = 0x7d08,
0048     HDMI_CEC_TX_DATA0   = 0x7d10,
0049     HDMI_CEC_RX_DATA0   = 0x7d20,
0050     HDMI_CEC_LOCK       = 0x7d30,
0051     HDMI_CEC_WKUPCTRL   = 0x7d31,
0052 };
0053 
0054 struct dw_hdmi_cec {
0055     struct dw_hdmi *hdmi;
0056     const struct dw_hdmi_cec_ops *ops;
0057     u32 addresses;
0058     struct cec_adapter *adap;
0059     struct cec_msg rx_msg;
0060     unsigned int tx_status;
0061     bool tx_done;
0062     bool rx_done;
0063     struct cec_notifier *notify;
0064     int irq;
0065 };
0066 
0067 static void dw_hdmi_write(struct dw_hdmi_cec *cec, u8 val, int offset)
0068 {
0069     cec->ops->write(cec->hdmi, val, offset);
0070 }
0071 
0072 static u8 dw_hdmi_read(struct dw_hdmi_cec *cec, int offset)
0073 {
0074     return cec->ops->read(cec->hdmi, offset);
0075 }
0076 
0077 static int dw_hdmi_cec_log_addr(struct cec_adapter *adap, u8 logical_addr)
0078 {
0079     struct dw_hdmi_cec *cec = cec_get_drvdata(adap);
0080 
0081     if (logical_addr == CEC_LOG_ADDR_INVALID)
0082         cec->addresses = 0;
0083     else
0084         cec->addresses |= BIT(logical_addr) | BIT(15);
0085 
0086     dw_hdmi_write(cec, cec->addresses & 255, HDMI_CEC_ADDR_L);
0087     dw_hdmi_write(cec, cec->addresses >> 8, HDMI_CEC_ADDR_H);
0088 
0089     return 0;
0090 }
0091 
0092 static int dw_hdmi_cec_transmit(struct cec_adapter *adap, u8 attempts,
0093                 u32 signal_free_time, struct cec_msg *msg)
0094 {
0095     struct dw_hdmi_cec *cec = cec_get_drvdata(adap);
0096     unsigned int i, ctrl;
0097 
0098     switch (signal_free_time) {
0099     case CEC_SIGNAL_FREE_TIME_RETRY:
0100         ctrl = CEC_CTRL_RETRY;
0101         break;
0102     case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR:
0103     default:
0104         ctrl = CEC_CTRL_NORMAL;
0105         break;
0106     case CEC_SIGNAL_FREE_TIME_NEXT_XFER:
0107         ctrl = CEC_CTRL_IMMED;
0108         break;
0109     }
0110 
0111     for (i = 0; i < msg->len; i++)
0112         dw_hdmi_write(cec, msg->msg[i], HDMI_CEC_TX_DATA0 + i);
0113 
0114     dw_hdmi_write(cec, msg->len, HDMI_CEC_TX_CNT);
0115     dw_hdmi_write(cec, ctrl | CEC_CTRL_START, HDMI_CEC_CTRL);
0116 
0117     return 0;
0118 }
0119 
0120 static irqreturn_t dw_hdmi_cec_hardirq(int irq, void *data)
0121 {
0122     struct cec_adapter *adap = data;
0123     struct dw_hdmi_cec *cec = cec_get_drvdata(adap);
0124     unsigned int stat = dw_hdmi_read(cec, HDMI_IH_CEC_STAT0);
0125     irqreturn_t ret = IRQ_HANDLED;
0126 
0127     if (stat == 0)
0128         return IRQ_NONE;
0129 
0130     dw_hdmi_write(cec, stat, HDMI_IH_CEC_STAT0);
0131 
0132     if (stat & CEC_STAT_ERROR_INIT) {
0133         cec->tx_status = CEC_TX_STATUS_ERROR;
0134         cec->tx_done = true;
0135         ret = IRQ_WAKE_THREAD;
0136     } else if (stat & CEC_STAT_DONE) {
0137         cec->tx_status = CEC_TX_STATUS_OK;
0138         cec->tx_done = true;
0139         ret = IRQ_WAKE_THREAD;
0140     } else if (stat & CEC_STAT_NACK) {
0141         cec->tx_status = CEC_TX_STATUS_NACK;
0142         cec->tx_done = true;
0143         ret = IRQ_WAKE_THREAD;
0144     }
0145 
0146     if (stat & CEC_STAT_EOM) {
0147         unsigned int len, i;
0148 
0149         len = dw_hdmi_read(cec, HDMI_CEC_RX_CNT);
0150         if (len > sizeof(cec->rx_msg.msg))
0151             len = sizeof(cec->rx_msg.msg);
0152 
0153         for (i = 0; i < len; i++)
0154             cec->rx_msg.msg[i] =
0155                 dw_hdmi_read(cec, HDMI_CEC_RX_DATA0 + i);
0156 
0157         dw_hdmi_write(cec, 0, HDMI_CEC_LOCK);
0158 
0159         cec->rx_msg.len = len;
0160         smp_wmb();
0161         cec->rx_done = true;
0162 
0163         ret = IRQ_WAKE_THREAD;
0164     }
0165 
0166     return ret;
0167 }
0168 
0169 static irqreturn_t dw_hdmi_cec_thread(int irq, void *data)
0170 {
0171     struct cec_adapter *adap = data;
0172     struct dw_hdmi_cec *cec = cec_get_drvdata(adap);
0173 
0174     if (cec->tx_done) {
0175         cec->tx_done = false;
0176         cec_transmit_attempt_done(adap, cec->tx_status);
0177     }
0178     if (cec->rx_done) {
0179         cec->rx_done = false;
0180         smp_rmb();
0181         cec_received_msg(adap, &cec->rx_msg);
0182     }
0183     return IRQ_HANDLED;
0184 }
0185 
0186 static int dw_hdmi_cec_enable(struct cec_adapter *adap, bool enable)
0187 {
0188     struct dw_hdmi_cec *cec = cec_get_drvdata(adap);
0189 
0190     if (!enable) {
0191         dw_hdmi_write(cec, ~0, HDMI_CEC_MASK);
0192         dw_hdmi_write(cec, ~0, HDMI_IH_MUTE_CEC_STAT0);
0193         dw_hdmi_write(cec, 0, HDMI_CEC_POLARITY);
0194 
0195         cec->ops->disable(cec->hdmi);
0196     } else {
0197         unsigned int irqs;
0198 
0199         dw_hdmi_write(cec, 0, HDMI_CEC_CTRL);
0200         dw_hdmi_write(cec, ~0, HDMI_IH_CEC_STAT0);
0201         dw_hdmi_write(cec, 0, HDMI_CEC_LOCK);
0202 
0203         dw_hdmi_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID);
0204 
0205         cec->ops->enable(cec->hdmi);
0206 
0207         irqs = CEC_STAT_ERROR_INIT | CEC_STAT_NACK | CEC_STAT_EOM |
0208                CEC_STAT_DONE;
0209         dw_hdmi_write(cec, irqs, HDMI_CEC_POLARITY);
0210         dw_hdmi_write(cec, ~irqs, HDMI_CEC_MASK);
0211         dw_hdmi_write(cec, ~irqs, HDMI_IH_MUTE_CEC_STAT0);
0212     }
0213     return 0;
0214 }
0215 
0216 static const struct cec_adap_ops dw_hdmi_cec_ops = {
0217     .adap_enable = dw_hdmi_cec_enable,
0218     .adap_log_addr = dw_hdmi_cec_log_addr,
0219     .adap_transmit = dw_hdmi_cec_transmit,
0220 };
0221 
0222 static void dw_hdmi_cec_del(void *data)
0223 {
0224     struct dw_hdmi_cec *cec = data;
0225 
0226     cec_delete_adapter(cec->adap);
0227 }
0228 
0229 static int dw_hdmi_cec_probe(struct platform_device *pdev)
0230 {
0231     struct dw_hdmi_cec_data *data = dev_get_platdata(&pdev->dev);
0232     struct dw_hdmi_cec *cec;
0233     int ret;
0234 
0235     if (!data)
0236         return -ENXIO;
0237 
0238     /*
0239      * Our device is just a convenience - we want to link to the real
0240      * hardware device here, so that userspace can see the association
0241      * between the HDMI hardware and its associated CEC chardev.
0242      */
0243     cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL);
0244     if (!cec)
0245         return -ENOMEM;
0246 
0247     cec->irq = data->irq;
0248     cec->ops = data->ops;
0249     cec->hdmi = data->hdmi;
0250 
0251     platform_set_drvdata(pdev, cec);
0252 
0253     dw_hdmi_write(cec, 0, HDMI_CEC_TX_CNT);
0254     dw_hdmi_write(cec, ~0, HDMI_CEC_MASK);
0255     dw_hdmi_write(cec, ~0, HDMI_IH_MUTE_CEC_STAT0);
0256     dw_hdmi_write(cec, 0, HDMI_CEC_POLARITY);
0257 
0258     cec->adap = cec_allocate_adapter(&dw_hdmi_cec_ops, cec, "dw_hdmi",
0259                      CEC_CAP_DEFAULTS |
0260                      CEC_CAP_CONNECTOR_INFO,
0261                      CEC_MAX_LOG_ADDRS);
0262     if (IS_ERR(cec->adap))
0263         return PTR_ERR(cec->adap);
0264 
0265     /* override the module pointer */
0266     cec->adap->owner = THIS_MODULE;
0267 
0268     ret = devm_add_action_or_reset(&pdev->dev, dw_hdmi_cec_del, cec);
0269     if (ret)
0270         return ret;
0271 
0272     ret = devm_request_threaded_irq(&pdev->dev, cec->irq,
0273                     dw_hdmi_cec_hardirq,
0274                     dw_hdmi_cec_thread, IRQF_SHARED,
0275                     "dw-hdmi-cec", cec->adap);
0276     if (ret < 0)
0277         return ret;
0278 
0279     cec->notify = cec_notifier_cec_adap_register(pdev->dev.parent,
0280                              NULL, cec->adap);
0281     if (!cec->notify)
0282         return -ENOMEM;
0283 
0284     ret = cec_register_adapter(cec->adap, pdev->dev.parent);
0285     if (ret < 0) {
0286         cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
0287         return ret;
0288     }
0289 
0290     /*
0291      * CEC documentation says we must not call cec_delete_adapter
0292      * after a successful call to cec_register_adapter().
0293      */
0294     devm_remove_action(&pdev->dev, dw_hdmi_cec_del, cec);
0295 
0296     return 0;
0297 }
0298 
0299 static int dw_hdmi_cec_remove(struct platform_device *pdev)
0300 {
0301     struct dw_hdmi_cec *cec = platform_get_drvdata(pdev);
0302 
0303     cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
0304     cec_unregister_adapter(cec->adap);
0305 
0306     return 0;
0307 }
0308 
0309 static struct platform_driver dw_hdmi_cec_driver = {
0310     .probe  = dw_hdmi_cec_probe,
0311     .remove = dw_hdmi_cec_remove,
0312     .driver = {
0313         .name = "dw-hdmi-cec",
0314     },
0315 };
0316 module_platform_driver(dw_hdmi_cec_driver);
0317 
0318 MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>");
0319 MODULE_DESCRIPTION("Synopsys Designware HDMI CEC driver for i.MX");
0320 MODULE_LICENSE("GPL");
0321 MODULE_ALIAS(PLATFORM_MODULE_PREFIX "dw-hdmi-cec");