drm: sti: HDMI add audio infoframe
authorArnaud Pouliquen <arnaud.pouliquen@st.com>
Thu, 5 Feb 2015 10:55:02 +0000 (11:55 +0100)
committerBenjamin Gaignard <benjamin.gaignard@linaro.org>
Thu, 5 Feb 2015 15:21:19 +0000 (16:21 +0100)
Add a default audio infoframe for HDMI compliance

Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>
drivers/gpu/drm/sti/sti_hdmi.c

index e840ca5de40107abfe4d1cea2362ec11a33d4510..1485ade98710a3078de14c8b577db2a5bd15cd21 100644 (file)
 #define HDMI_SW_DI_1_PKT_WORD5          0x0228
 #define HDMI_SW_DI_1_PKT_WORD6          0x022C
 #define HDMI_SW_DI_CFG                  0x0230
+#define HDMI_SW_DI_2_HEAD_WORD          0x0600
+#define HDMI_SW_DI_2_PKT_WORD0          0x0604
+#define HDMI_SW_DI_2_PKT_WORD1          0x0608
+#define HDMI_SW_DI_2_PKT_WORD2          0x060C
+#define HDMI_SW_DI_2_PKT_WORD3          0x0610
+#define HDMI_SW_DI_2_PKT_WORD4          0x0614
+#define HDMI_SW_DI_2_PKT_WORD5          0x0618
+#define HDMI_SW_DI_2_PKT_WORD6          0x061C
 
 #define HDMI_IFRAME_SLOT_AVI            1
+#define HDMI_IFRAME_SLOT_AUDIO          2
 
 #define  XCAT(prefix, x, suffix)        prefix ## x ## suffix
 #define  HDMI_SW_DI_N_HEAD_WORD(x)      XCAT(HDMI_SW_DI_, x, _HEAD_WORD)
 
 #define HDMI_STA_SW_RST                 BIT(1)
 
+#define HDMI_INFOFRAME_HEADER_TYPE(x)    (((x) & 0xff) <<  0)
+#define HDMI_INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) <<  8)
+#define HDMI_INFOFRAME_HEADER_LEN(x)     (((x) & 0x0f) << 16)
+
 struct sti_hdmi_connector {
        struct drm_connector drm_connector;
        struct drm_encoder *encoder;
@@ -227,6 +240,90 @@ static void hdmi_config(struct sti_hdmi *hdmi)
        hdmi_write(hdmi, conf, HDMI_CFG);
 }
 
+/**
+ * Helper to concatenate infoframe in 32 bits word
+ *
+ * @ptr: pointer on the hdmi internal structure
+ * @data: infoframe to write
+ * @size: size to write
+ */
+static inline unsigned int hdmi_infoframe_subpack(const u8 *ptr, size_t size)
+{
+       unsigned long value = 0;
+       size_t i;
+
+       for (i = size; i > 0; i--)
+               value = (value << 8) | ptr[i - 1];
+
+       return value;
+}
+
+/**
+ * Helper to write info frame
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ * @data: infoframe to write
+ * @size: size to write
+ */
+static void hdmi_infoframe_write_infopack(struct sti_hdmi *hdmi, const u8 *data)
+{
+       const u8 *ptr = data;
+       u32 val, slot, mode, i;
+       u32 head_offset, pack_offset;
+       size_t size;
+
+       switch (*ptr) {
+       case HDMI_INFOFRAME_TYPE_AVI:
+               slot = HDMI_IFRAME_SLOT_AVI;
+               mode = HDMI_IFRAME_FIELD;
+               head_offset = HDMI_SW_DI_N_HEAD_WORD(HDMI_IFRAME_SLOT_AVI);
+               pack_offset = HDMI_SW_DI_N_PKT_WORD0(HDMI_IFRAME_SLOT_AVI);
+               size = HDMI_AVI_INFOFRAME_SIZE;
+               break;
+
+       case HDMI_INFOFRAME_TYPE_AUDIO:
+               slot = HDMI_IFRAME_SLOT_AUDIO;
+               mode = HDMI_IFRAME_FRAME;
+               head_offset = HDMI_SW_DI_N_HEAD_WORD(HDMI_IFRAME_SLOT_AUDIO);
+               pack_offset = HDMI_SW_DI_N_PKT_WORD0(HDMI_IFRAME_SLOT_AUDIO);
+               size = HDMI_AUDIO_INFOFRAME_SIZE;
+               break;
+
+       default:
+               DRM_ERROR("unsupported infoframe type: %#x\n", *ptr);
+               return;
+       }
+
+       /* Disable transmission slot for updated infoframe */
+       val = hdmi_read(hdmi, HDMI_SW_DI_CFG);
+       val &= ~HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, slot);
+       hdmi_write(hdmi, val, HDMI_SW_DI_CFG);
+
+       val = HDMI_INFOFRAME_HEADER_TYPE(*ptr++);
+       val |= HDMI_INFOFRAME_HEADER_VERSION(*ptr++);
+       val |= HDMI_INFOFRAME_HEADER_LEN(*ptr++);
+       writel(val, hdmi->regs + head_offset);
+
+       /*
+        * Each subpack contains 4 bytes
+        * The First Bytes of the first subpacket must contain the checksum
+        * Packet size in increase by one.
+        */
+       for (i = 0; i < size; i += sizeof(u32)) {
+               size_t num;
+
+               num = min_t(size_t, size - i, sizeof(u32));
+               val = hdmi_infoframe_subpack(ptr, num);
+               ptr += sizeof(u32);
+               writel(val, hdmi->regs + pack_offset + i);
+       }
+
+       /* Enable transmission slot for updated infoframe */
+       val = hdmi_read(hdmi, HDMI_SW_DI_CFG);
+       val |= HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_FIELD, slot);
+       hdmi_write(hdmi, val, HDMI_SW_DI_CFG);
+}
+
 /**
  * Prepare and configure the AVI infoframe
  *
@@ -243,8 +340,6 @@ static int hdmi_avi_infoframe_config(struct sti_hdmi *hdmi)
        struct drm_display_mode *mode = &hdmi->mode;
        struct hdmi_avi_infoframe infoframe;
        u8 buffer[HDMI_INFOFRAME_SIZE(AVI)];
-       u8 *frame = buffer + HDMI_INFOFRAME_HEADER_SIZE;
-       u32 val;
        int ret;
 
        DRM_DEBUG_DRIVER("\n");
@@ -266,47 +361,43 @@ static int hdmi_avi_infoframe_config(struct sti_hdmi *hdmi)
                return ret;
        }
 
-       /* Disable transmission slot for AVI infoframe */
-       val = hdmi_read(hdmi, HDMI_SW_DI_CFG);
-       val &= ~HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI);
-       hdmi_write(hdmi, val, HDMI_SW_DI_CFG);
+       hdmi_infoframe_write_infopack(hdmi, buffer);
 
-       /* Infoframe header */
-       val =  buffer[0];
-       val |= buffer[1] << 8;
-       val |= buffer[2] << 16;
-       hdmi_write(hdmi, val, HDMI_SW_DI_N_HEAD_WORD(HDMI_IFRAME_SLOT_AVI));
-
-       /* Infoframe packet bytes */
-       val =  buffer[3];
-       val |= *(frame++) << 8;
-       val |= *(frame++) << 16;
-       val |= *(frame++) << 24;
-       hdmi_write(hdmi, val, HDMI_SW_DI_N_PKT_WORD0(HDMI_IFRAME_SLOT_AVI));
-
-       val =  *(frame++);
-       val |= *(frame++) << 8;
-       val |= *(frame++) << 16;
-       val |= *(frame++) << 24;
-       hdmi_write(hdmi, val, HDMI_SW_DI_N_PKT_WORD1(HDMI_IFRAME_SLOT_AVI));
-
-       val =  *(frame++);
-       val |= *(frame++) << 8;
-       val |= *(frame++) << 16;
-       val |= *(frame++) << 24;
-       hdmi_write(hdmi, val, HDMI_SW_DI_N_PKT_WORD2(HDMI_IFRAME_SLOT_AVI));
-
-       val = *(frame++);
-       val |= *(frame) << 8;
-       hdmi_write(hdmi, val, HDMI_SW_DI_N_PKT_WORD3(HDMI_IFRAME_SLOT_AVI));
-
-       /* Enable transmission slot for AVI infoframe
-        * According to the hdmi specification, AVI infoframe should be
-        * transmitted at least once per two video fields
-        */
-       val = hdmi_read(hdmi, HDMI_SW_DI_CFG);
-       val |= HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_FIELD, HDMI_IFRAME_SLOT_AVI);
-       hdmi_write(hdmi, val, HDMI_SW_DI_CFG);
+       return 0;
+}
+
+/**
+ * Prepare and configure the AUDIO infoframe
+ *
+ * AUDIO infoframe are transmitted once per frame and
+ * contains information about HDMI transmission mode such as audio codec,
+ * sample size, ...
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return negative value if error occurs
+ */
+static int hdmi_audio_infoframe_config(struct sti_hdmi *hdmi)
+{
+       struct hdmi_audio_infoframe infofame;
+       u8 buffer[HDMI_INFOFRAME_SIZE(AUDIO)];
+       int ret;
+
+       ret = hdmi_audio_infoframe_init(&infofame);
+       if (ret < 0) {
+               DRM_ERROR("failed to setup audio infoframe: %d\n", ret);
+               return ret;
+       }
+
+       infofame.channels = 2;
+
+       ret = hdmi_audio_infoframe_pack(&infofame, buffer, sizeof(buffer));
+       if (ret < 0) {
+               DRM_ERROR("failed to pack audio infoframe: %d\n", ret);
+               return ret;
+       }
+
+       hdmi_infoframe_write_infopack(hdmi, buffer);
 
        return 0;
 }
@@ -427,6 +518,10 @@ static void sti_hdmi_pre_enable(struct drm_bridge *bridge)
        if (hdmi_avi_infoframe_config(hdmi))
                DRM_ERROR("Unable to configure AVI infoframe\n");
 
+       /* Program AUDIO infoframe */
+       if (hdmi_audio_infoframe_config(hdmi))
+               DRM_ERROR("Unable to configure AUDIO infoframe\n");
+
        /* Sw reset */
        hdmi_swreset(hdmi);
 }