Extend the driver to send SPD and HDMI Vendor Specific InfoFrames.

While the HDMI block has special block to send HVS InfoFrame, use
GENERIC0 block instead. VENSPEC_INFO registers pack frame data in a way
that requires manual repacking in the driver, while GENERIC0 doesn't
have such format requirements. The msm-4.4 kernel uses GENERIC0 to send
HDR InfoFrame which we do not at this point anyway.

Acked-by: Maxime Ripard <mrip...@kernel.org>
Signed-off-by: Dmitry Baryshkov <dmitry.barysh...@linaro.org>
---
 drivers/gpu/drm/msm/hdmi/hdmi_bridge.c | 93 ++++++++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)

diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c 
b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
index 
15ab0858105328c2f774ec1f79423614bbbaeb41..aee75eee3d4244cd95e44df46d65b8e3e53de735
 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
@@ -69,6 +69,8 @@ static void power_off(struct drm_bridge *bridge)
 }
 
 #define AVI_IFRAME_LINE_NUMBER 1
+#define SPD_IFRAME_LINE_NUMBER 1
+#define VENSPEC_IFRAME_LINE_NUMBER 3
 
 static int msm_hdmi_config_avi_infoframe(struct hdmi *hdmi,
                                         const u8 *buffer, size_t len)
@@ -142,6 +144,74 @@ static int msm_hdmi_config_audio_infoframe(struct hdmi 
*hdmi,
        return 0;
 }
 
+static int msm_hdmi_config_spd_infoframe(struct hdmi *hdmi,
+                                        const u8 *buffer, size_t len)
+{
+       u32 buf[7] = {};
+       u32 val;
+       int i;
+
+       if (len != HDMI_INFOFRAME_SIZE(SPD) || len - 3 > sizeof(buf)) {
+               DRM_DEV_ERROR(&hdmi->pdev->dev,
+                       "failed to configure SPD infoframe\n");
+               return -EINVAL;
+       }
+
+       /* checksum gets written together with the body of the frame */
+       hdmi_write(hdmi, REG_HDMI_GENERIC1_HDR,
+                  buffer[0] |
+                  buffer[1] << 8 |
+                  buffer[2] << 16);
+
+       memcpy(buf, &buffer[3], len - 3);
+
+       for (i = 0; i < ARRAY_SIZE(buf); i++)
+               hdmi_write(hdmi, REG_HDMI_GENERIC1(i), buf[i]);
+
+       val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
+       val |= HDMI_GEN_PKT_CTRL_GENERIC1_SEND |
+                HDMI_GEN_PKT_CTRL_GENERIC1_CONT |
+                HDMI_GEN_PKT_CTRL_GENERIC1_LINE(SPD_IFRAME_LINE_NUMBER);
+       hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
+
+       return 0;
+}
+
+static int msm_hdmi_config_hdmi_infoframe(struct hdmi *hdmi,
+                                         const u8 *buffer, size_t len)
+{
+       u32 buf[7] = {};
+       u32 val;
+       int i;
+
+       if (len < HDMI_INFOFRAME_HEADER_SIZE + HDMI_VENDOR_INFOFRAME_SIZE ||
+           len - 3 > sizeof(buf)) {
+               DRM_DEV_ERROR(&hdmi->pdev->dev,
+                       "failed to configure HDMI infoframe\n");
+               return -EINVAL;
+       }
+
+       /* checksum gets written together with the body of the frame */
+       hdmi_write(hdmi, REG_HDMI_GENERIC0_HDR,
+                  buffer[0] |
+                  buffer[1] << 8 |
+                  buffer[2] << 16);
+
+       memcpy(buf, &buffer[3], len - 3);
+
+       for (i = 0; i < ARRAY_SIZE(buf); i++)
+               hdmi_write(hdmi, REG_HDMI_GENERIC0(i), buf[i]);
+
+       val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
+       val |= HDMI_GEN_PKT_CTRL_GENERIC0_SEND |
+                HDMI_GEN_PKT_CTRL_GENERIC0_CONT |
+                HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE |
+                HDMI_GEN_PKT_CTRL_GENERIC0_LINE(VENSPEC_IFRAME_LINE_NUMBER);
+       hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
+
+       return 0;
+}
+
 static int msm_hdmi_bridge_clear_infoframe(struct drm_bridge *bridge,
                                           enum hdmi_infoframe_type type)
 {
@@ -176,6 +246,25 @@ static int msm_hdmi_bridge_clear_infoframe(struct 
drm_bridge *bridge,
 
                break;
 
+       case HDMI_INFOFRAME_TYPE_SPD:
+               val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
+               val &= ~(HDMI_GEN_PKT_CTRL_GENERIC1_SEND |
+                        HDMI_GEN_PKT_CTRL_GENERIC1_CONT |
+                        HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK);
+               hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
+
+               break;
+
+       case HDMI_INFOFRAME_TYPE_VENDOR:
+               val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
+               val &= ~(HDMI_GEN_PKT_CTRL_GENERIC0_SEND |
+                        HDMI_GEN_PKT_CTRL_GENERIC0_CONT |
+                        HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE |
+                        HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK);
+               hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
+
+               break;
+
        default:
                drm_dbg_driver(hdmi_bridge->base.dev, "Unsupported infoframe 
type %x\n", type);
        }
@@ -197,6 +286,10 @@ static int msm_hdmi_bridge_write_infoframe(struct 
drm_bridge *bridge,
                return msm_hdmi_config_avi_infoframe(hdmi, buffer, len);
        case HDMI_INFOFRAME_TYPE_AUDIO:
                return msm_hdmi_config_audio_infoframe(hdmi, buffer, len);
+       case HDMI_INFOFRAME_TYPE_SPD:
+               return msm_hdmi_config_spd_infoframe(hdmi, buffer, len);
+       case HDMI_INFOFRAME_TYPE_VENDOR:
+               return msm_hdmi_config_hdmi_infoframe(hdmi, buffer, len);
        default:
                drm_dbg_driver(hdmi_bridge->base.dev, "Unsupported infoframe 
type %x\n", type);
                return 0;

-- 
2.39.5

Reply via email to