From: Hans Verkuil hans.verkuil@cisco.com
This patch series adds support for HDMI CEC to the vc4 drm driver. This series is based on 4.13-rc1.
Note: the first cec patch is independent of the vc4 patches and will be merged via the media subsystem for 4.14. Without that patch everything will still work, but it will attempt to retry messages twice as many times as it should.
This has been tested with the Raspberry Pi 2B and 3B. I don't have older rpi's, so I can't test those.
Many thanks to Eric Anholt for his help with this driver!
There is one open issue: when booting the rpi without an HDMI cable connected, then CEC won't work. But neither apparently does HDMI since reconnecting it will not bring back any display.
If you boot with the HDMI cable connected, then all is well and disconnecting and reconnecting the cable will do the right thing.
I don't understand what is going on here, but it does not appear to be CEC related and the same problem occurs without this patch series.
You also need to update your config.txt with this line to prevent the firmware from eating the CEC interrupts:
mask_gpu_interrupt1=0x100
Eric, I've experimented with setting hdmi_ignore_cec=1 but that simply doesn't work. Instead that disables CEC completely. With this mask_gpu_interrupt1 setting everything works perfectly. This also prevents the firmware from sending the initial Active Source CEC message so the CPU has full control over the CEC bus, as it should.
My main concern is that this is rather magical, but it is not something I have any control over.
Changes since v1:
- Merged patch 3 and 4 together as suggested by Eric. - The clock divider is now set explicitly instead of relying on the default value.
Regards,
Hans
Hans Verkuil (3): cec: be smarter about detecting the number of attempts made drm/vc4: prepare for CEC support drm/vc4: add HDMI CEC support
drivers/gpu/drm/vc4/Kconfig | 8 ++ drivers/gpu/drm/vc4/vc4_hdmi.c | 284 ++++++++++++++++++++++++++++++++++++----- drivers/gpu/drm/vc4/vc4_regs.h | 113 ++++++++++++++++ drivers/media/cec/cec-adap.c | 9 +- 4 files changed, 377 insertions(+), 37 deletions(-)
From: Hans Verkuil hans.verkuil@cisco.com
Some hardware does more than one attempt. So when it calls cec_transmit_done when an error occurred it will e.g. use an error count of 2 instead of 1.
The framework always assumed a single attempt, but now it is smarter and will sum the counters to detect how many attempts were made.
Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- drivers/media/cec/cec-adap.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/drivers/media/cec/cec-adap.c b/drivers/media/cec/cec-adap.c index bf45977b2823..e9284dbdc880 100644 --- a/drivers/media/cec/cec-adap.c +++ b/drivers/media/cec/cec-adap.c @@ -472,9 +472,14 @@ void cec_transmit_done(struct cec_adapter *adap, u8 status, u8 arb_lost_cnt, { struct cec_data *data; struct cec_msg *msg; + unsigned int attempts_made = arb_lost_cnt + nack_cnt + + low_drive_cnt + error_cnt; u64 ts = ktime_get_ns();
dprintk(2, "%s: status %02x\n", __func__, status); + if (attempts_made < 1) + attempts_made = 1; + mutex_lock(&adap->lock); data = adap->transmitting; if (!data) { @@ -507,10 +512,10 @@ void cec_transmit_done(struct cec_adapter *adap, u8 status, u8 arb_lost_cnt, * the hardware didn't signal that it retried itself (by setting * CEC_TX_STATUS_MAX_RETRIES), then we will retry ourselves. */ - if (data->attempts > 1 && + if (data->attempts > attempts_made && !(status & (CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_OK))) { /* Retry this message */ - data->attempts--; + data->attempts -= attempts_made; if (msg->timeout) dprintk(2, "retransmit: %*ph (attempts: %d, wait for 0x%02x)\n", msg->len, msg->msg, data->attempts, msg->reply);
From: Hans Verkuil hans.verkuil@cisco.com
In order to support CEC the hsm clock needs to be enabled in vc4_hdmi_bind(), not in vc4_hdmi_encoder_enable(). Otherwise you wouldn't be able to support CEC when there is no hotplug detect signal, which is required by some monitors that turn off the HPD when in standby, but keep the CEC bus alive so they can be woken up.
The HDMI core also has to be enabled in vc4_hdmi_bind() for the same reason.
Signed-off-by: Hans Verkuil hans.verkuil@cisco.com --- drivers/gpu/drm/vc4/vc4_hdmi.c | 59 +++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 30 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index ed63d4e85762..e0104f96011e 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -463,11 +463,6 @@ static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) HD_WRITE(VC4_HD_VID_CTL, HD_READ(VC4_HD_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE);
- HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_SW_RST); - udelay(1); - HD_WRITE(VC4_HD_M_CTL, 0); - - clk_disable_unprepare(hdmi->hsm_clock); clk_disable_unprepare(hdmi->pixel_clock);
ret = pm_runtime_put(&hdmi->pdev->dev); @@ -509,16 +504,6 @@ static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) return; }
- /* This is the rate that is set by the firmware. The number - * needs to be a bit higher than the pixel clock rate - * (generally 148.5Mhz). - */ - ret = clk_set_rate(hdmi->hsm_clock, 163682864); - if (ret) { - DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); - return; - } - ret = clk_set_rate(hdmi->pixel_clock, mode->clock * 1000 * ((mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1)); @@ -533,20 +518,6 @@ static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) return; }
- ret = clk_prepare_enable(hdmi->hsm_clock); - if (ret) { - DRM_ERROR("Failed to turn on HDMI state machine clock: %d\n", - ret); - clk_disable_unprepare(hdmi->pixel_clock); - return; - } - - HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_SW_RST); - udelay(1); - HD_WRITE(VC4_HD_M_CTL, 0); - - HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_ENABLE); - HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL, VC4_HDMI_SW_RESET_HDMI | VC4_HDMI_SW_RESET_FORMAT_DETECT); @@ -1205,6 +1176,23 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) return -EPROBE_DEFER; }
+ /* This is the rate that is set by the firmware. The number + * needs to be a bit higher than the pixel clock rate + * (generally 148.5Mhz). + */ + ret = clk_set_rate(hdmi->hsm_clock, 163682864); + if (ret) { + DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); + goto err_put_i2c; + } + + ret = clk_prepare_enable(hdmi->hsm_clock); + if (ret) { + DRM_ERROR("Failed to turn on HDMI state machine clock: %d\n", + ret); + goto err_put_i2c; + } + /* Only use the GPIO HPD pin if present in the DT, otherwise * we'll use the HDMI core's register. */ @@ -1216,7 +1204,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) &hpd_gpio_flags); if (hdmi->hpd_gpio < 0) { ret = hdmi->hpd_gpio; - goto err_put_i2c; + goto err_unprepare_hsm; }
hdmi->hpd_active_low = hpd_gpio_flags & OF_GPIO_ACTIVE_LOW; @@ -1224,6 +1212,14 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
vc4->hdmi = hdmi;
+ /* HDMI core must be enabled. */ + if (!(HD_READ(VC4_HD_M_CTL) & VC4_HD_M_ENABLE)) { + HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_SW_RST); + udelay(1); + HD_WRITE(VC4_HD_M_CTL, 0); + + HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_ENABLE); + } pm_runtime_enable(dev);
drm_encoder_init(drm, hdmi->encoder, &vc4_hdmi_encoder_funcs, @@ -1244,6 +1240,8 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
err_destroy_encoder: vc4_hdmi_encoder_destroy(hdmi->encoder); +err_unprepare_hsm: + clk_disable_unprepare(hdmi->hsm_clock); pm_runtime_disable(dev); err_put_i2c: put_device(&hdmi->ddc->dev); @@ -1263,6 +1261,7 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, vc4_hdmi_connector_destroy(hdmi->connector); vc4_hdmi_encoder_destroy(hdmi->encoder);
+ clk_disable_unprepare(hdmi->hsm_clock); pm_runtime_disable(dev);
put_device(&hdmi->ddc->dev);
From: Hans Verkuil hans.verkuil@cisco.com
This patch adds support to VC4 for CEC.
Thanks to Eric Anholt for providing me with the CEC register information.
To prevent the firmware from eating the CEC interrupts you need to add this to your config.txt:
mask_gpu_interrupt1=0x100
Signed-off-by: Hans Verkuil hans.verkuil@cisco.com Signed-off-by: Eric Anholt eric@anholt.net --- drivers/gpu/drm/vc4/Kconfig | 8 ++ drivers/gpu/drm/vc4/vc4_hdmi.c | 227 +++++++++++++++++++++++++++++++++++++++-- drivers/gpu/drm/vc4/vc4_regs.h | 113 ++++++++++++++++++++ 3 files changed, 342 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/vc4/Kconfig b/drivers/gpu/drm/vc4/Kconfig index 4361bdcfd28a..fdae18aeab4f 100644 --- a/drivers/gpu/drm/vc4/Kconfig +++ b/drivers/gpu/drm/vc4/Kconfig @@ -19,3 +19,11 @@ config DRM_VC4 This driver requires that "avoid_warnings=2" be present in the config.txt for the firmware, to keep it from smashing our display setup. + +config DRM_VC4_HDMI_CEC + bool "Broadcom VC4 HDMI CEC Support" + depends on DRM_VC4 + select CEC_CORE + help + Choose this option if you have a Broadcom VC4 GPU + and want to use CEC. diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index e0104f96011e..761b95f5deed 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -57,9 +57,14 @@ #include <sound/pcm_drm_eld.h> #include <sound/pcm_params.h> #include <sound/soc.h> +#include "media/cec.h" #include "vc4_drv.h" #include "vc4_regs.h"
+#define HSM_CLOCK_FREQ 163682864 +#define CEC_CLOCK_FREQ 40000 +#define CEC_CLOCK_DIV (HSM_CLOCK_FREQ / CEC_CLOCK_FREQ) + /* HDMI audio information */ struct vc4_hdmi_audio { struct snd_soc_card card; @@ -85,6 +90,11 @@ struct vc4_hdmi { int hpd_gpio; bool hpd_active_low;
+ struct cec_adapter *cec_adap; + struct cec_msg cec_rx_msg; + bool cec_tx_ok; + bool cec_irq_was_rx; + struct clk *pixel_clock; struct clk *hsm_clock; }; @@ -149,6 +159,23 @@ static const struct { HDMI_REG(VC4_HDMI_VERTB1), HDMI_REG(VC4_HDMI_TX_PHY_RESET_CTL), HDMI_REG(VC4_HDMI_TX_PHY_CTL0), + + HDMI_REG(VC4_HDMI_CEC_CNTRL_1), + HDMI_REG(VC4_HDMI_CEC_CNTRL_2), + HDMI_REG(VC4_HDMI_CEC_CNTRL_3), + HDMI_REG(VC4_HDMI_CEC_CNTRL_4), + HDMI_REG(VC4_HDMI_CEC_CNTRL_5), + HDMI_REG(VC4_HDMI_CPU_STATUS), + HDMI_REG(VC4_HDMI_CPU_MASK_STATUS), + + HDMI_REG(VC4_HDMI_CEC_RX_DATA_1), + HDMI_REG(VC4_HDMI_CEC_RX_DATA_2), + HDMI_REG(VC4_HDMI_CEC_RX_DATA_3), + HDMI_REG(VC4_HDMI_CEC_RX_DATA_4), + HDMI_REG(VC4_HDMI_CEC_TX_DATA_1), + HDMI_REG(VC4_HDMI_CEC_TX_DATA_2), + HDMI_REG(VC4_HDMI_CEC_TX_DATA_3), + HDMI_REG(VC4_HDMI_CEC_TX_DATA_4), };
static const struct { @@ -216,8 +243,8 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force) if (gpio_get_value_cansleep(vc4->hdmi->hpd_gpio) ^ vc4->hdmi->hpd_active_low) return connector_status_connected; - else - return connector_status_disconnected; + cec_phys_addr_invalidate(vc4->hdmi->cec_adap); + return connector_status_disconnected; }
if (drm_probe_ddc(vc4->hdmi->ddc)) @@ -225,8 +252,8 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force)
if (HDMI_READ(VC4_HDMI_HOTPLUG) & VC4_HDMI_HOTPLUG_CONNECTED) return connector_status_connected; - else - return connector_status_disconnected; + cec_phys_addr_invalidate(vc4->hdmi->cec_adap); + return connector_status_disconnected; }
static void vc4_hdmi_connector_destroy(struct drm_connector *connector) @@ -247,6 +274,7 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector) struct edid *edid;
edid = drm_get_edid(connector, vc4->hdmi->ddc); + cec_s_phys_addr_from_edid(vc4->hdmi->cec_adap, edid); if (!edid) return -ENODEV;
@@ -1121,6 +1149,159 @@ static void vc4_hdmi_audio_cleanup(struct vc4_hdmi *hdmi) snd_soc_unregister_codec(dev); }
+#ifdef CONFIG_DRM_VC4_HDMI_CEC +static irqreturn_t vc4_cec_irq_handler_thread(int irq, void *priv) +{ + struct vc4_dev *vc4 = priv; + struct vc4_hdmi *hdmi = vc4->hdmi; + + if (hdmi->cec_irq_was_rx) { + if (hdmi->cec_rx_msg.len) + cec_received_msg(hdmi->cec_adap, &hdmi->cec_rx_msg); + } else if (hdmi->cec_tx_ok) { + cec_transmit_done(hdmi->cec_adap, CEC_TX_STATUS_OK, + 0, 0, 0, 0); + } else { + /* + * This CEC implementation makes 1 retry, so if we + * get a NACK, then that means it made 2 attempts. + */ + cec_transmit_done(hdmi->cec_adap, CEC_TX_STATUS_NACK, + 0, 2, 0, 0); + } + return IRQ_HANDLED; +} + +static void vc4_cec_read_msg(struct vc4_dev *vc4, u32 cntrl1) +{ + struct cec_msg *msg = &vc4->hdmi->cec_rx_msg; + unsigned int i; + + msg->len = 1 + ((cntrl1 & VC4_HDMI_CEC_REC_WRD_CNT_MASK) >> + VC4_HDMI_CEC_REC_WRD_CNT_SHIFT); + for (i = 0; i < msg->len; i += 4) { + u32 val = HDMI_READ(VC4_HDMI_CEC_RX_DATA_1 + i); + + msg->msg[i] = val & 0xff; + msg->msg[i + 1] = (val >> 8) & 0xff; + msg->msg[i + 2] = (val >> 16) & 0xff; + msg->msg[i + 3] = (val >> 24) & 0xff; + } +} + +static irqreturn_t vc4_cec_irq_handler(int irq, void *priv) +{ + struct vc4_dev *vc4 = priv; + struct vc4_hdmi *hdmi = vc4->hdmi; + u32 stat = HDMI_READ(VC4_HDMI_CPU_STATUS); + u32 cntrl1, cntrl5; + + if (!(stat & VC4_HDMI_CPU_CEC)) + return IRQ_NONE; + hdmi->cec_rx_msg.len = 0; + cntrl1 = HDMI_READ(VC4_HDMI_CEC_CNTRL_1); + cntrl5 = HDMI_READ(VC4_HDMI_CEC_CNTRL_5); + hdmi->cec_irq_was_rx = cntrl5 & VC4_HDMI_CEC_RX_CEC_INT; + if (hdmi->cec_irq_was_rx) { + vc4_cec_read_msg(vc4, cntrl1); + cntrl1 |= VC4_HDMI_CEC_CLEAR_RECEIVE_OFF; + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, cntrl1); + cntrl1 &= ~VC4_HDMI_CEC_CLEAR_RECEIVE_OFF; + } else { + hdmi->cec_tx_ok = cntrl1 & VC4_HDMI_CEC_TX_STATUS_GOOD; + cntrl1 &= ~VC4_HDMI_CEC_START_XMIT_BEGIN; + } + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, cntrl1); + HDMI_WRITE(VC4_HDMI_CPU_CLEAR, VC4_HDMI_CPU_CEC); + + return IRQ_WAKE_THREAD; +} + +static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct vc4_dev *vc4 = cec_get_drvdata(adap); + /* clock period in microseconds */ + const u32 usecs = 1000000 / CEC_CLOCK_FREQ; + u32 val = HDMI_READ(VC4_HDMI_CEC_CNTRL_5); + + val &= ~(VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET | + VC4_HDMI_CEC_CNT_TO_4700_US_MASK | + VC4_HDMI_CEC_CNT_TO_4500_US_MASK); + val |= ((4700 / usecs) << VC4_HDMI_CEC_CNT_TO_4700_US_SHIFT) | + ((4500 / usecs) << VC4_HDMI_CEC_CNT_TO_4500_US_SHIFT); + + if (enable) { + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val | + VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET); + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val); + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_2, + ((1500 / usecs) << VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT) | + ((1300 / usecs) << VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT) | + ((800 / usecs) << VC4_HDMI_CEC_CNT_TO_800_US_SHIFT) | + ((600 / usecs) << VC4_HDMI_CEC_CNT_TO_600_US_SHIFT) | + ((400 / usecs) << VC4_HDMI_CEC_CNT_TO_400_US_SHIFT)); + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_3, + ((2750 / usecs) << VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT) | + ((2400 / usecs) << VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT) | + ((2050 / usecs) << VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT) | + ((1700 / usecs) << VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT)); + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_4, + ((4300 / usecs) << VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT) | + ((3900 / usecs) << VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT) | + ((3600 / usecs) << VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT) | + ((3500 / usecs) << VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT)); + + HDMI_WRITE(VC4_HDMI_CPU_MASK_CLEAR, VC4_HDMI_CPU_CEC); + } else { + HDMI_WRITE(VC4_HDMI_CPU_MASK_SET, VC4_HDMI_CPU_CEC); + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val | + VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET); + } + return 0; +} + +static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) +{ + struct vc4_dev *vc4 = cec_get_drvdata(adap); + + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, + (HDMI_READ(VC4_HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) | + (log_addr & 0xf) << VC4_HDMI_CEC_ADDR_SHIFT); + return 0; +} + +static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct vc4_dev *vc4 = cec_get_drvdata(adap); + u32 val; + unsigned int i; + + for (i = 0; i < msg->len; i += 4) + HDMI_WRITE(VC4_HDMI_CEC_TX_DATA_1 + i, + (msg->msg[i]) | + (msg->msg[i + 1] << 8) | + (msg->msg[i + 2] << 16) | + (msg->msg[i + 3] << 24)); + + val = HDMI_READ(VC4_HDMI_CEC_CNTRL_1); + val &= ~VC4_HDMI_CEC_START_XMIT_BEGIN; + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, val); + val &= ~VC4_HDMI_CEC_MESSAGE_LENGTH_MASK; + val |= (msg->len - 1) << VC4_HDMI_CEC_MESSAGE_LENGTH_SHIFT; + val |= VC4_HDMI_CEC_START_XMIT_BEGIN; + + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, val); + return 0; +} + +static const struct cec_adap_ops vc4_hdmi_cec_adap_ops = { + .adap_enable = vc4_hdmi_cec_adap_enable, + .adap_log_addr = vc4_hdmi_cec_adap_log_addr, + .adap_transmit = vc4_hdmi_cec_adap_transmit, +}; +#endif + static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); @@ -1180,7 +1361,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) * needs to be a bit higher than the pixel clock rate * (generally 148.5Mhz). */ - ret = clk_set_rate(hdmi->hsm_clock, 163682864); + ret = clk_set_rate(hdmi->hsm_clock, HSM_CLOCK_FREQ); if (ret) { DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); goto err_put_i2c; @@ -1231,6 +1412,34 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) ret = PTR_ERR(hdmi->connector); goto err_destroy_encoder; } +#ifdef CONFIG_DRM_VC4_HDMI_CEC + hdmi->cec_adap = cec_allocate_adapter(&vc4_hdmi_cec_adap_ops, + vc4, "vc4", CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS | + CEC_CAP_PASSTHROUGH | CEC_CAP_RC, 1); + ret = PTR_ERR_OR_ZERO(hdmi->cec_adap); + if (ret < 0) + goto err_destroy_conn; + HDMI_WRITE(VC4_HDMI_CPU_MASK_SET, 0xffffffff); + value = HDMI_READ(VC4_HDMI_CEC_CNTRL_1); + value &= ~VC4_HDMI_CEC_DIV_CLK_CNT_MASK; + /* + * Set the logical address to Unregistered and set the clock + * divider: the hsm_clock rate and this divider setting will + * give a 40 kHz CEC clock. + */ + value |= VC4_HDMI_CEC_ADDR_MASK | + (4091 << VC4_HDMI_CEC_DIV_CLK_CNT_SHIFT); + HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, value); + ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0), + vc4_cec_irq_handler, + vc4_cec_irq_handler_thread, 0, + "vc4 hdmi cec", vc4); + if (ret) + goto err_delete_cec_adap; + ret = cec_register_adapter(hdmi->cec_adap, dev); + if (ret < 0) + goto err_delete_cec_adap; +#endif
ret = vc4_hdmi_audio_init(hdmi); if (ret) @@ -1238,6 +1447,12 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
return 0;
+#ifdef CONFIG_DRM_VC4_HDMI_CEC +err_delete_cec_adap: + cec_delete_adapter(hdmi->cec_adap); +err_destroy_conn: + vc4_hdmi_connector_destroy(hdmi->connector); +#endif err_destroy_encoder: vc4_hdmi_encoder_destroy(hdmi->encoder); err_unprepare_hsm: @@ -1257,7 +1472,7 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, struct vc4_hdmi *hdmi = vc4->hdmi;
vc4_hdmi_audio_cleanup(hdmi); - + cec_unregister_adapter(hdmi->cec_adap); vc4_hdmi_connector_destroy(hdmi->connector); vc4_hdmi_encoder_destroy(hdmi->encoder);
diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h index d382c34c1b9e..55677bd50f66 100644 --- a/drivers/gpu/drm/vc4/vc4_regs.h +++ b/drivers/gpu/drm/vc4/vc4_regs.h @@ -561,16 +561,129 @@ # define VC4_HDMI_VERTB_VBP_MASK VC4_MASK(8, 0) # define VC4_HDMI_VERTB_VBP_SHIFT 0
+#define VC4_HDMI_CEC_CNTRL_1 0x0e8 +/* Set when the transmission has ended. */ +# define VC4_HDMI_CEC_TX_EOM BIT(31) +/* If set, transmission was acked on the 1st or 2nd attempt (only one + * retry is attempted). If in continuous mode, this means TX needs to + * be filled if !TX_EOM. + */ +# define VC4_HDMI_CEC_TX_STATUS_GOOD BIT(30) +# define VC4_HDMI_CEC_RX_EOM BIT(29) +# define VC4_HDMI_CEC_RX_STATUS_GOOD BIT(28) +/* Number of bytes received for the message. */ +# define VC4_HDMI_CEC_REC_WRD_CNT_MASK VC4_MASK(27, 24) +# define VC4_HDMI_CEC_REC_WRD_CNT_SHIFT 24 +/* Sets continuous receive mode. Generates interrupt after each 8 + * bytes to signal that RX_DATA should be consumed, and at RX_EOM. + * + * If disabled, maximum 16 bytes will be received (including header), + * and interrupt at RX_EOM. Later bytes will be acked but not put + * into the RX_DATA. + */ +# define VC4_HDMI_CEC_RX_CONTINUE BIT(23) +# define VC4_HDMI_CEC_TX_CONTINUE BIT(22) +/* Set this after a CEC interrupt. */ +# define VC4_HDMI_CEC_CLEAR_RECEIVE_OFF BIT(21) +/* Starts a TX. Will wait for appropriate idel time before CEC + * activity. Must be cleared in between transmits. + */ +# define VC4_HDMI_CEC_START_XMIT_BEGIN BIT(20) +# define VC4_HDMI_CEC_MESSAGE_LENGTH_MASK VC4_MASK(19, 16) +# define VC4_HDMI_CEC_MESSAGE_LENGTH_SHIFT 16 +/* Device's CEC address */ +# define VC4_HDMI_CEC_ADDR_MASK VC4_MASK(15, 12) +# define VC4_HDMI_CEC_ADDR_SHIFT 12 +/* Divides off of HSM clock to generate CEC bit clock. */ +/* With the current defaults the CEC bit clock is 40 kHz = 25 usec */ +# define VC4_HDMI_CEC_DIV_CLK_CNT_MASK VC4_MASK(11, 0) +# define VC4_HDMI_CEC_DIV_CLK_CNT_SHIFT 0 + +/* Set these fields to how many bit clock cycles get to that many + * microseconds. + */ +#define VC4_HDMI_CEC_CNTRL_2 0x0ec +# define VC4_HDMI_CEC_CNT_TO_1500_US_MASK VC4_MASK(30, 24) +# define VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT 24 +# define VC4_HDMI_CEC_CNT_TO_1300_US_MASK VC4_MASK(23, 17) +# define VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT 17 +# define VC4_HDMI_CEC_CNT_TO_800_US_MASK VC4_MASK(16, 11) +# define VC4_HDMI_CEC_CNT_TO_800_US_SHIFT 11 +# define VC4_HDMI_CEC_CNT_TO_600_US_MASK VC4_MASK(10, 5) +# define VC4_HDMI_CEC_CNT_TO_600_US_SHIFT 5 +# define VC4_HDMI_CEC_CNT_TO_400_US_MASK VC4_MASK(4, 0) +# define VC4_HDMI_CEC_CNT_TO_400_US_SHIFT 0 + +#define VC4_HDMI_CEC_CNTRL_3 0x0f0 +# define VC4_HDMI_CEC_CNT_TO_2750_US_MASK VC4_MASK(31, 24) +# define VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT 24 +# define VC4_HDMI_CEC_CNT_TO_2400_US_MASK VC4_MASK(23, 16) +# define VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT 16 +# define VC4_HDMI_CEC_CNT_TO_2050_US_MASK VC4_MASK(15, 8) +# define VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT 8 +# define VC4_HDMI_CEC_CNT_TO_1700_US_MASK VC4_MASK(7, 0) +# define VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT 0 + +#define VC4_HDMI_CEC_CNTRL_4 0x0f4 +# define VC4_HDMI_CEC_CNT_TO_4300_US_MASK VC4_MASK(31, 24) +# define VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT 24 +# define VC4_HDMI_CEC_CNT_TO_3900_US_MASK VC4_MASK(23, 16) +# define VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT 16 +# define VC4_HDMI_CEC_CNT_TO_3600_US_MASK VC4_MASK(15, 8) +# define VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT 8 +# define VC4_HDMI_CEC_CNT_TO_3500_US_MASK VC4_MASK(7, 0) +# define VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT 0 + +#define VC4_HDMI_CEC_CNTRL_5 0x0f8 +# define VC4_HDMI_CEC_TX_SW_RESET BIT(27) +# define VC4_HDMI_CEC_RX_SW_RESET BIT(26) +# define VC4_HDMI_CEC_PAD_SW_RESET BIT(25) +# define VC4_HDMI_CEC_MUX_TP_OUT_CEC BIT(24) +# define VC4_HDMI_CEC_RX_CEC_INT BIT(23) +# define VC4_HDMI_CEC_CLK_PRELOAD_MASK VC4_MASK(22, 16) +# define VC4_HDMI_CEC_CLK_PRELOAD_SHIFT 16 +# define VC4_HDMI_CEC_CNT_TO_4700_US_MASK VC4_MASK(15, 8) +# define VC4_HDMI_CEC_CNT_TO_4700_US_SHIFT 8 +# define VC4_HDMI_CEC_CNT_TO_4500_US_MASK VC4_MASK(7, 0) +# define VC4_HDMI_CEC_CNT_TO_4500_US_SHIFT 0 + +/* Transmit data, first byte is low byte of the 32-bit reg. MSB of + * each byte transmitted first. + */ +#define VC4_HDMI_CEC_TX_DATA_1 0x0fc +#define VC4_HDMI_CEC_TX_DATA_2 0x100 +#define VC4_HDMI_CEC_TX_DATA_3 0x104 +#define VC4_HDMI_CEC_TX_DATA_4 0x108 +#define VC4_HDMI_CEC_RX_DATA_1 0x10c +#define VC4_HDMI_CEC_RX_DATA_2 0x110 +#define VC4_HDMI_CEC_RX_DATA_3 0x114 +#define VC4_HDMI_CEC_RX_DATA_4 0x118 + #define VC4_HDMI_TX_PHY_RESET_CTL 0x2c0
#define VC4_HDMI_TX_PHY_CTL0 0x2c4 # define VC4_HDMI_TX_PHY_RNG_PWRDN BIT(25)
+/* Interrupt status bits */ +#define VC4_HDMI_CPU_STATUS 0x340 +#define VC4_HDMI_CPU_SET 0x344 +#define VC4_HDMI_CPU_CLEAR 0x348 +# define VC4_HDMI_CPU_CEC BIT(6) +# define VC4_HDMI_CPU_HOTPLUG BIT(0) + +#define VC4_HDMI_CPU_MASK_STATUS 0x34c +#define VC4_HDMI_CPU_MASK_SET 0x350 +#define VC4_HDMI_CPU_MASK_CLEAR 0x354 + #define VC4_HDMI_GCP(x) (0x400 + ((x) * 0x4)) #define VC4_HDMI_RAM_PACKET(x) (0x400 + ((x) * 0x24)) #define VC4_HDMI_PACKET_STRIDE 0x24
#define VC4_HD_M_CTL 0x00c +/* Debug: Current receive value on the CEC pad. */ +# define VC4_HD_CECRXD BIT(9) +/* Debug: Override CEC output to 0. */ +# define VC4_HD_CECOVR BIT(8) # define VC4_HD_M_REGISTER_FILE_STANDBY (3 << 6) # define VC4_HD_M_RAM_STANDBY (3 << 4) # define VC4_HD_M_SW_RST BIT(2)
Hi Eric,
On 16/07/17 12:48, Hans Verkuil wrote:
From: Hans Verkuil hans.verkuil@cisco.com
This patch adds support to VC4 for CEC.
Thanks to Eric Anholt for providing me with the CEC register information.
To prevent the firmware from eating the CEC interrupts you need to add this to your config.txt:
mask_gpu_interrupt1=0x100
I put this text in the commit log, but I think it should also go into the source. Do you agree?
Should I also mention this in the kernel log via a 'dev_info'? It's not an obvious config option, after all.
Or do you have other ideas regarding this?
Regards,
Hans
Signed-off-by: Hans Verkuil hans.verkuil@cisco.com Signed-off-by: Eric Anholt eric@anholt.net
drivers/gpu/drm/vc4/Kconfig | 8 ++ drivers/gpu/drm/vc4/vc4_hdmi.c | 227 +++++++++++++++++++++++++++++++++++++++-- drivers/gpu/drm/vc4/vc4_regs.h | 113 ++++++++++++++++++++ 3 files changed, 342 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/vc4/Kconfig b/drivers/gpu/drm/vc4/Kconfig index 4361bdcfd28a..fdae18aeab4f 100644 --- a/drivers/gpu/drm/vc4/Kconfig +++ b/drivers/gpu/drm/vc4/Kconfig @@ -19,3 +19,11 @@ config DRM_VC4 This driver requires that "avoid_warnings=2" be present in the config.txt for the firmware, to keep it from smashing our display setup.
+config DRM_VC4_HDMI_CEC
bool "Broadcom VC4 HDMI CEC Support"
depends on DRM_VC4
select CEC_CORE
help
Choose this option if you have a Broadcom VC4 GPU
and want to use CEC.
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index e0104f96011e..761b95f5deed 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -57,9 +57,14 @@ #include <sound/pcm_drm_eld.h> #include <sound/pcm_params.h> #include <sound/soc.h> +#include "media/cec.h" #include "vc4_drv.h" #include "vc4_regs.h"
+#define HSM_CLOCK_FREQ 163682864 +#define CEC_CLOCK_FREQ 40000 +#define CEC_CLOCK_DIV (HSM_CLOCK_FREQ / CEC_CLOCK_FREQ)
/* HDMI audio information */ struct vc4_hdmi_audio { struct snd_soc_card card; @@ -85,6 +90,11 @@ struct vc4_hdmi { int hpd_gpio; bool hpd_active_low;
- struct cec_adapter *cec_adap;
- struct cec_msg cec_rx_msg;
- bool cec_tx_ok;
- bool cec_irq_was_rx;
- struct clk *pixel_clock; struct clk *hsm_clock;
}; @@ -149,6 +159,23 @@ static const struct { HDMI_REG(VC4_HDMI_VERTB1), HDMI_REG(VC4_HDMI_TX_PHY_RESET_CTL), HDMI_REG(VC4_HDMI_TX_PHY_CTL0),
- HDMI_REG(VC4_HDMI_CEC_CNTRL_1),
- HDMI_REG(VC4_HDMI_CEC_CNTRL_2),
- HDMI_REG(VC4_HDMI_CEC_CNTRL_3),
- HDMI_REG(VC4_HDMI_CEC_CNTRL_4),
- HDMI_REG(VC4_HDMI_CEC_CNTRL_5),
- HDMI_REG(VC4_HDMI_CPU_STATUS),
- HDMI_REG(VC4_HDMI_CPU_MASK_STATUS),
- HDMI_REG(VC4_HDMI_CEC_RX_DATA_1),
- HDMI_REG(VC4_HDMI_CEC_RX_DATA_2),
- HDMI_REG(VC4_HDMI_CEC_RX_DATA_3),
- HDMI_REG(VC4_HDMI_CEC_RX_DATA_4),
- HDMI_REG(VC4_HDMI_CEC_TX_DATA_1),
- HDMI_REG(VC4_HDMI_CEC_TX_DATA_2),
- HDMI_REG(VC4_HDMI_CEC_TX_DATA_3),
- HDMI_REG(VC4_HDMI_CEC_TX_DATA_4),
};
static const struct { @@ -216,8 +243,8 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force) if (gpio_get_value_cansleep(vc4->hdmi->hpd_gpio) ^ vc4->hdmi->hpd_active_low) return connector_status_connected;
else
return connector_status_disconnected;
cec_phys_addr_invalidate(vc4->hdmi->cec_adap);
return connector_status_disconnected;
}
if (drm_probe_ddc(vc4->hdmi->ddc))
@@ -225,8 +252,8 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force)
if (HDMI_READ(VC4_HDMI_HOTPLUG) & VC4_HDMI_HOTPLUG_CONNECTED) return connector_status_connected;
- else
return connector_status_disconnected;
- cec_phys_addr_invalidate(vc4->hdmi->cec_adap);
- return connector_status_disconnected;
}
static void vc4_hdmi_connector_destroy(struct drm_connector *connector) @@ -247,6 +274,7 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector) struct edid *edid;
edid = drm_get_edid(connector, vc4->hdmi->ddc);
- cec_s_phys_addr_from_edid(vc4->hdmi->cec_adap, edid); if (!edid) return -ENODEV;
@@ -1121,6 +1149,159 @@ static void vc4_hdmi_audio_cleanup(struct vc4_hdmi *hdmi) snd_soc_unregister_codec(dev); }
+#ifdef CONFIG_DRM_VC4_HDMI_CEC +static irqreturn_t vc4_cec_irq_handler_thread(int irq, void *priv) +{
- struct vc4_dev *vc4 = priv;
- struct vc4_hdmi *hdmi = vc4->hdmi;
- if (hdmi->cec_irq_was_rx) {
if (hdmi->cec_rx_msg.len)
cec_received_msg(hdmi->cec_adap, &hdmi->cec_rx_msg);
- } else if (hdmi->cec_tx_ok) {
cec_transmit_done(hdmi->cec_adap, CEC_TX_STATUS_OK,
0, 0, 0, 0);
- } else {
/*
* This CEC implementation makes 1 retry, so if we
* get a NACK, then that means it made 2 attempts.
*/
cec_transmit_done(hdmi->cec_adap, CEC_TX_STATUS_NACK,
0, 2, 0, 0);
- }
- return IRQ_HANDLED;
+}
+static void vc4_cec_read_msg(struct vc4_dev *vc4, u32 cntrl1) +{
- struct cec_msg *msg = &vc4->hdmi->cec_rx_msg;
- unsigned int i;
- msg->len = 1 + ((cntrl1 & VC4_HDMI_CEC_REC_WRD_CNT_MASK) >>
VC4_HDMI_CEC_REC_WRD_CNT_SHIFT);
- for (i = 0; i < msg->len; i += 4) {
u32 val = HDMI_READ(VC4_HDMI_CEC_RX_DATA_1 + i);
msg->msg[i] = val & 0xff;
msg->msg[i + 1] = (val >> 8) & 0xff;
msg->msg[i + 2] = (val >> 16) & 0xff;
msg->msg[i + 3] = (val >> 24) & 0xff;
- }
+}
+static irqreturn_t vc4_cec_irq_handler(int irq, void *priv) +{
- struct vc4_dev *vc4 = priv;
- struct vc4_hdmi *hdmi = vc4->hdmi;
- u32 stat = HDMI_READ(VC4_HDMI_CPU_STATUS);
- u32 cntrl1, cntrl5;
- if (!(stat & VC4_HDMI_CPU_CEC))
return IRQ_NONE;
- hdmi->cec_rx_msg.len = 0;
- cntrl1 = HDMI_READ(VC4_HDMI_CEC_CNTRL_1);
- cntrl5 = HDMI_READ(VC4_HDMI_CEC_CNTRL_5);
- hdmi->cec_irq_was_rx = cntrl5 & VC4_HDMI_CEC_RX_CEC_INT;
- if (hdmi->cec_irq_was_rx) {
vc4_cec_read_msg(vc4, cntrl1);
cntrl1 |= VC4_HDMI_CEC_CLEAR_RECEIVE_OFF;
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, cntrl1);
cntrl1 &= ~VC4_HDMI_CEC_CLEAR_RECEIVE_OFF;
- } else {
hdmi->cec_tx_ok = cntrl1 & VC4_HDMI_CEC_TX_STATUS_GOOD;
cntrl1 &= ~VC4_HDMI_CEC_START_XMIT_BEGIN;
- }
- HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, cntrl1);
- HDMI_WRITE(VC4_HDMI_CPU_CLEAR, VC4_HDMI_CPU_CEC);
- return IRQ_WAKE_THREAD;
+}
+static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) +{
- struct vc4_dev *vc4 = cec_get_drvdata(adap);
- /* clock period in microseconds */
- const u32 usecs = 1000000 / CEC_CLOCK_FREQ;
- u32 val = HDMI_READ(VC4_HDMI_CEC_CNTRL_5);
- val &= ~(VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET |
VC4_HDMI_CEC_CNT_TO_4700_US_MASK |
VC4_HDMI_CEC_CNT_TO_4500_US_MASK);
- val |= ((4700 / usecs) << VC4_HDMI_CEC_CNT_TO_4700_US_SHIFT) |
((4500 / usecs) << VC4_HDMI_CEC_CNT_TO_4500_US_SHIFT);
- if (enable) {
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val |
VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET);
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val);
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_2,
((1500 / usecs) << VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT) |
((1300 / usecs) << VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT) |
((800 / usecs) << VC4_HDMI_CEC_CNT_TO_800_US_SHIFT) |
((600 / usecs) << VC4_HDMI_CEC_CNT_TO_600_US_SHIFT) |
((400 / usecs) << VC4_HDMI_CEC_CNT_TO_400_US_SHIFT));
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_3,
((2750 / usecs) << VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT) |
((2400 / usecs) << VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT) |
((2050 / usecs) << VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT) |
((1700 / usecs) << VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT));
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_4,
((4300 / usecs) << VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT) |
((3900 / usecs) << VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT) |
((3600 / usecs) << VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT) |
((3500 / usecs) << VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT));
HDMI_WRITE(VC4_HDMI_CPU_MASK_CLEAR, VC4_HDMI_CPU_CEC);
- } else {
HDMI_WRITE(VC4_HDMI_CPU_MASK_SET, VC4_HDMI_CPU_CEC);
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val |
VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET);
- }
- return 0;
+}
+static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) +{
- struct vc4_dev *vc4 = cec_get_drvdata(adap);
- HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1,
(HDMI_READ(VC4_HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) |
(log_addr & 0xf) << VC4_HDMI_CEC_ADDR_SHIFT);
- return 0;
+}
+static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
+{
- struct vc4_dev *vc4 = cec_get_drvdata(adap);
- u32 val;
- unsigned int i;
- for (i = 0; i < msg->len; i += 4)
HDMI_WRITE(VC4_HDMI_CEC_TX_DATA_1 + i,
(msg->msg[i]) |
(msg->msg[i + 1] << 8) |
(msg->msg[i + 2] << 16) |
(msg->msg[i + 3] << 24));
- val = HDMI_READ(VC4_HDMI_CEC_CNTRL_1);
- val &= ~VC4_HDMI_CEC_START_XMIT_BEGIN;
- HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, val);
- val &= ~VC4_HDMI_CEC_MESSAGE_LENGTH_MASK;
- val |= (msg->len - 1) << VC4_HDMI_CEC_MESSAGE_LENGTH_SHIFT;
- val |= VC4_HDMI_CEC_START_XMIT_BEGIN;
- HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, val);
- return 0;
+}
+static const struct cec_adap_ops vc4_hdmi_cec_adap_ops = {
- .adap_enable = vc4_hdmi_cec_adap_enable,
- .adap_log_addr = vc4_hdmi_cec_adap_log_addr,
- .adap_transmit = vc4_hdmi_cec_adap_transmit,
+}; +#endif
static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); @@ -1180,7 +1361,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) * needs to be a bit higher than the pixel clock rate * (generally 148.5Mhz). */
- ret = clk_set_rate(hdmi->hsm_clock, 163682864);
- ret = clk_set_rate(hdmi->hsm_clock, HSM_CLOCK_FREQ); if (ret) { DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); goto err_put_i2c;
@@ -1231,6 +1412,34 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) ret = PTR_ERR(hdmi->connector); goto err_destroy_encoder; } +#ifdef CONFIG_DRM_VC4_HDMI_CEC
- hdmi->cec_adap = cec_allocate_adapter(&vc4_hdmi_cec_adap_ops,
vc4, "vc4", CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS |
CEC_CAP_PASSTHROUGH | CEC_CAP_RC, 1);
- ret = PTR_ERR_OR_ZERO(hdmi->cec_adap);
- if (ret < 0)
goto err_destroy_conn;
- HDMI_WRITE(VC4_HDMI_CPU_MASK_SET, 0xffffffff);
- value = HDMI_READ(VC4_HDMI_CEC_CNTRL_1);
- value &= ~VC4_HDMI_CEC_DIV_CLK_CNT_MASK;
- /*
* Set the logical address to Unregistered and set the clock
* divider: the hsm_clock rate and this divider setting will
* give a 40 kHz CEC clock.
*/
- value |= VC4_HDMI_CEC_ADDR_MASK |
(4091 << VC4_HDMI_CEC_DIV_CLK_CNT_SHIFT);
- HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, value);
- ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0),
vc4_cec_irq_handler,
vc4_cec_irq_handler_thread, 0,
"vc4 hdmi cec", vc4);
- if (ret)
goto err_delete_cec_adap;
- ret = cec_register_adapter(hdmi->cec_adap, dev);
- if (ret < 0)
goto err_delete_cec_adap;
+#endif
ret = vc4_hdmi_audio_init(hdmi); if (ret) @@ -1238,6 +1447,12 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
return 0;
+#ifdef CONFIG_DRM_VC4_HDMI_CEC +err_delete_cec_adap:
- cec_delete_adapter(hdmi->cec_adap);
+err_destroy_conn:
- vc4_hdmi_connector_destroy(hdmi->connector);
+#endif err_destroy_encoder: vc4_hdmi_encoder_destroy(hdmi->encoder); err_unprepare_hsm: @@ -1257,7 +1472,7 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, struct vc4_hdmi *hdmi = vc4->hdmi;
vc4_hdmi_audio_cleanup(hdmi);
- cec_unregister_adapter(hdmi->cec_adap); vc4_hdmi_connector_destroy(hdmi->connector); vc4_hdmi_encoder_destroy(hdmi->encoder);
diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h index d382c34c1b9e..55677bd50f66 100644 --- a/drivers/gpu/drm/vc4/vc4_regs.h +++ b/drivers/gpu/drm/vc4/vc4_regs.h @@ -561,16 +561,129 @@ # define VC4_HDMI_VERTB_VBP_MASK VC4_MASK(8, 0) # define VC4_HDMI_VERTB_VBP_SHIFT 0
+#define VC4_HDMI_CEC_CNTRL_1 0x0e8 +/* Set when the transmission has ended. */ +# define VC4_HDMI_CEC_TX_EOM BIT(31) +/* If set, transmission was acked on the 1st or 2nd attempt (only one
- retry is attempted). If in continuous mode, this means TX needs to
- be filled if !TX_EOM.
- */
+# define VC4_HDMI_CEC_TX_STATUS_GOOD BIT(30) +# define VC4_HDMI_CEC_RX_EOM BIT(29) +# define VC4_HDMI_CEC_RX_STATUS_GOOD BIT(28) +/* Number of bytes received for the message. */ +# define VC4_HDMI_CEC_REC_WRD_CNT_MASK VC4_MASK(27, 24) +# define VC4_HDMI_CEC_REC_WRD_CNT_SHIFT 24 +/* Sets continuous receive mode. Generates interrupt after each 8
- bytes to signal that RX_DATA should be consumed, and at RX_EOM.
- If disabled, maximum 16 bytes will be received (including header),
- and interrupt at RX_EOM. Later bytes will be acked but not put
- into the RX_DATA.
- */
+# define VC4_HDMI_CEC_RX_CONTINUE BIT(23) +# define VC4_HDMI_CEC_TX_CONTINUE BIT(22) +/* Set this after a CEC interrupt. */ +# define VC4_HDMI_CEC_CLEAR_RECEIVE_OFF BIT(21) +/* Starts a TX. Will wait for appropriate idel time before CEC
- activity. Must be cleared in between transmits.
- */
+# define VC4_HDMI_CEC_START_XMIT_BEGIN BIT(20) +# define VC4_HDMI_CEC_MESSAGE_LENGTH_MASK VC4_MASK(19, 16) +# define VC4_HDMI_CEC_MESSAGE_LENGTH_SHIFT 16 +/* Device's CEC address */ +# define VC4_HDMI_CEC_ADDR_MASK VC4_MASK(15, 12) +# define VC4_HDMI_CEC_ADDR_SHIFT 12 +/* Divides off of HSM clock to generate CEC bit clock. */ +/* With the current defaults the CEC bit clock is 40 kHz = 25 usec */ +# define VC4_HDMI_CEC_DIV_CLK_CNT_MASK VC4_MASK(11, 0) +# define VC4_HDMI_CEC_DIV_CLK_CNT_SHIFT 0
+/* Set these fields to how many bit clock cycles get to that many
- microseconds.
- */
+#define VC4_HDMI_CEC_CNTRL_2 0x0ec +# define VC4_HDMI_CEC_CNT_TO_1500_US_MASK VC4_MASK(30, 24) +# define VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT 24 +# define VC4_HDMI_CEC_CNT_TO_1300_US_MASK VC4_MASK(23, 17) +# define VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT 17 +# define VC4_HDMI_CEC_CNT_TO_800_US_MASK VC4_MASK(16, 11) +# define VC4_HDMI_CEC_CNT_TO_800_US_SHIFT 11 +# define VC4_HDMI_CEC_CNT_TO_600_US_MASK VC4_MASK(10, 5) +# define VC4_HDMI_CEC_CNT_TO_600_US_SHIFT 5 +# define VC4_HDMI_CEC_CNT_TO_400_US_MASK VC4_MASK(4, 0) +# define VC4_HDMI_CEC_CNT_TO_400_US_SHIFT 0
+#define VC4_HDMI_CEC_CNTRL_3 0x0f0 +# define VC4_HDMI_CEC_CNT_TO_2750_US_MASK VC4_MASK(31, 24) +# define VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT 24 +# define VC4_HDMI_CEC_CNT_TO_2400_US_MASK VC4_MASK(23, 16) +# define VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT 16 +# define VC4_HDMI_CEC_CNT_TO_2050_US_MASK VC4_MASK(15, 8) +# define VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT 8 +# define VC4_HDMI_CEC_CNT_TO_1700_US_MASK VC4_MASK(7, 0) +# define VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT 0
+#define VC4_HDMI_CEC_CNTRL_4 0x0f4 +# define VC4_HDMI_CEC_CNT_TO_4300_US_MASK VC4_MASK(31, 24) +# define VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT 24 +# define VC4_HDMI_CEC_CNT_TO_3900_US_MASK VC4_MASK(23, 16) +# define VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT 16 +# define VC4_HDMI_CEC_CNT_TO_3600_US_MASK VC4_MASK(15, 8) +# define VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT 8 +# define VC4_HDMI_CEC_CNT_TO_3500_US_MASK VC4_MASK(7, 0) +# define VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT 0
+#define VC4_HDMI_CEC_CNTRL_5 0x0f8 +# define VC4_HDMI_CEC_TX_SW_RESET BIT(27) +# define VC4_HDMI_CEC_RX_SW_RESET BIT(26) +# define VC4_HDMI_CEC_PAD_SW_RESET BIT(25) +# define VC4_HDMI_CEC_MUX_TP_OUT_CEC BIT(24) +# define VC4_HDMI_CEC_RX_CEC_INT BIT(23) +# define VC4_HDMI_CEC_CLK_PRELOAD_MASK VC4_MASK(22, 16) +# define VC4_HDMI_CEC_CLK_PRELOAD_SHIFT 16 +# define VC4_HDMI_CEC_CNT_TO_4700_US_MASK VC4_MASK(15, 8) +# define VC4_HDMI_CEC_CNT_TO_4700_US_SHIFT 8 +# define VC4_HDMI_CEC_CNT_TO_4500_US_MASK VC4_MASK(7, 0) +# define VC4_HDMI_CEC_CNT_TO_4500_US_SHIFT 0
+/* Transmit data, first byte is low byte of the 32-bit reg. MSB of
- each byte transmitted first.
- */
+#define VC4_HDMI_CEC_TX_DATA_1 0x0fc +#define VC4_HDMI_CEC_TX_DATA_2 0x100 +#define VC4_HDMI_CEC_TX_DATA_3 0x104 +#define VC4_HDMI_CEC_TX_DATA_4 0x108 +#define VC4_HDMI_CEC_RX_DATA_1 0x10c +#define VC4_HDMI_CEC_RX_DATA_2 0x110 +#define VC4_HDMI_CEC_RX_DATA_3 0x114 +#define VC4_HDMI_CEC_RX_DATA_4 0x118
#define VC4_HDMI_TX_PHY_RESET_CTL 0x2c0
#define VC4_HDMI_TX_PHY_CTL0 0x2c4 # define VC4_HDMI_TX_PHY_RNG_PWRDN BIT(25)
+/* Interrupt status bits */ +#define VC4_HDMI_CPU_STATUS 0x340 +#define VC4_HDMI_CPU_SET 0x344 +#define VC4_HDMI_CPU_CLEAR 0x348 +# define VC4_HDMI_CPU_CEC BIT(6) +# define VC4_HDMI_CPU_HOTPLUG BIT(0)
+#define VC4_HDMI_CPU_MASK_STATUS 0x34c +#define VC4_HDMI_CPU_MASK_SET 0x350 +#define VC4_HDMI_CPU_MASK_CLEAR 0x354
#define VC4_HDMI_GCP(x) (0x400 + ((x) * 0x4)) #define VC4_HDMI_RAM_PACKET(x) (0x400 + ((x) * 0x24)) #define VC4_HDMI_PACKET_STRIDE 0x24
#define VC4_HD_M_CTL 0x00c +/* Debug: Current receive value on the CEC pad. */ +# define VC4_HD_CECRXD BIT(9) +/* Debug: Override CEC output to 0. */ +# define VC4_HD_CECOVR BIT(8) # define VC4_HD_M_REGISTER_FILE_STANDBY (3 << 6) # define VC4_HD_M_RAM_STANDBY (3 << 4) # define VC4_HD_M_SW_RST BIT(2)
Hans Verkuil hverkuil@xs4all.nl writes:
Hi Eric,
On 16/07/17 12:48, Hans Verkuil wrote:
From: Hans Verkuil hans.verkuil@cisco.com
This patch adds support to VC4 for CEC.
Thanks to Eric Anholt for providing me with the CEC register information.
To prevent the firmware from eating the CEC interrupts you need to add this to your config.txt:
mask_gpu_interrupt1=0x100
I put this text in the commit log, but I think it should also go into the source. Do you agree?
Should I also mention this in the kernel log via a 'dev_info'? It's not an obvious config option, after all.
Or do you have other ideas regarding this?
The firmware should have been masking this interrupt for ages (~2 years?), as long as it sees the kernel's DT instead of the downstream one. I've dropped the commit message comment about that, added a bit of explanation of having a separate Kconfig, and pushed.
Huge thanks for your work on this!
dri-devel@lists.freedesktop.org