drm/bridge/sii8620: add support for burst eMSC transmissions
authorAndrzej Hajda <a.hajda@samsung.com>
Tue, 6 Dec 2016 15:44:51 +0000 (16:44 +0100)
committerSeung-Woo Kim <sw0312.kim@samsung.com>
Wed, 14 Dec 2016 04:54:21 +0000 (13:54 +0900)
Burst transmissions are used in MHL3 mode negotiation.

Change-Id: Iec7261a46c9743ef95fae0e8e921aeee11cf0cc4
Signed-off-by: Andrzej Hajda <a.hajda@samsung.com>
drivers/gpu/drm/bridge/sil-sii8620.c
drivers/gpu/drm/bridge/sil-sii8620.h

index d2d3fc4c54e6b20cc91995c54d4c8e28038a3b5f..3fc6b8162ddac317dbdca398c08461ee5670d452 100644 (file)
@@ -9,6 +9,8 @@
  * published by the Free Software Foundation.
  */
 
+#include <asm/unaligned.h>
+
 #include <drm/bridge/mhl.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_edid.h>
@@ -29,6 +31,7 @@
 
 #include "sil-sii8620.h"
 
+#define SII8620_BURST_BUF_LEN 288
 #define VAL_RX_HDMI_CTRL2_DEFVAL VAL_RX_HDMI_CTRL2_IDLE_CNT(3)
 #define MHL1_MAX_CLOCK 74250
 #define MHL3_MAX_CLOCK 300000
@@ -74,6 +77,15 @@ struct sii8620 {
        unsigned int gen2_write_burst:1;
        enum sii8620_mt_state mt_state;
        struct list_head mt_queue;
+       struct {
+               int r_size;
+               int r_count;
+               int rx_ack;
+               int rx_count;
+               u8 rx_buf[32];
+               int tx_count;
+               u8 tx_buf[32];
+       } burst;
 
        /* extcon features */
        struct work_struct work;
@@ -509,6 +521,134 @@ static inline void sii8620_mt_read_xdevcap_reg(struct sii8620 *ctx, u8 reg)
        sii8620_mt_read_devcap_reg(ctx, reg | 0x80);
 }
 
+static void *sii8620_burst_get_tx_buf(struct sii8620 *ctx, int len)
+{
+       u8 *buf = &ctx->burst.tx_buf[ctx->burst.tx_count];
+       int size = len + 2;
+
+       if (ctx->burst.tx_count + size > ARRAY_SIZE(ctx->burst.tx_buf)) {
+               dev_err(ctx->dev, "TX-BLK buffer exhausted\n");
+               ctx->error = -EINVAL;
+               return NULL;
+       }
+
+       ctx->burst.tx_count += size;
+       buf[1] = len;
+
+       return buf + 2;
+}
+
+static u8 *sii8620_burst_get_rx_buf(struct sii8620 *ctx, int len)
+{
+       u8 *buf = &ctx->burst.rx_buf[ctx->burst.rx_count];
+       int size = len + 1;
+
+       if (ctx->burst.tx_count + size > ARRAY_SIZE(ctx->burst.tx_buf)) {
+               dev_err(ctx->dev, "RX-BLK buffer exhausted\n");
+               ctx->error = -EINVAL;
+               return NULL;
+       }
+
+       ctx->burst.rx_count += size;
+       buf[0] = len;
+
+       return buf + 1;
+}
+
+static void sii8620_burst_send(struct sii8620 *ctx)
+{
+       int tx_left = ctx->burst.tx_count;
+       u8 *d = ctx->burst.tx_buf;
+
+       while (tx_left > 0) {
+               int len = d[1] + 2;
+
+               if (ctx->burst.r_count + len > ctx->burst.r_size)
+                       break;
+               d[0] = min(ctx->burst.rx_ack, 255);
+               ctx->burst.rx_ack -= d[0];
+               sii8620_write_buf(ctx, REG_EMSC_XMIT_WRITE_PORT, d, len);
+               ctx->burst.r_count += len;
+               tx_left -= len;
+               d += len;
+       }
+
+       ctx->burst.tx_count = tx_left;
+
+       while (ctx->burst.rx_ack > 0) {
+               u8 b[2] = { min(ctx->burst.rx_ack, 255), 0 };
+
+               if (ctx->burst.r_count + 2 > ctx->burst.r_size)
+                       break;
+               ctx->burst.rx_ack -= b[0];
+               sii8620_write_buf(ctx, REG_EMSC_XMIT_WRITE_PORT, b, 2);
+               ctx->burst.r_count += 2;
+       }
+}
+
+static void sii8620_burst_receive(struct sii8620 *ctx)
+{
+       u8 buf[3], *d;
+       int count;
+
+       sii8620_read_buf(ctx, REG_EMSCRFIFOBCNTL, buf, 2);
+       count = get_unaligned_le16(buf);
+       while (count > 0) {
+               int len = min(count, 3);
+
+               sii8620_read_buf(ctx, REG_EMSC_RCV_READ_PORT, buf, len);
+               count -= len;
+               ctx->burst.rx_ack += len - 1;
+               ctx->burst.r_count -= buf[1];
+               if (ctx->burst.r_count < 0)
+                       ctx->burst.r_count = 0;
+
+               if (len < 3 || !buf[2])
+                       continue;
+
+               len = buf[2];
+               d = sii8620_burst_get_rx_buf(ctx, len);
+               if (!d)
+                       continue;
+               sii8620_read_buf(ctx, REG_EMSC_RCV_READ_PORT, d, len);
+               count -= len;
+               ctx->burst.rx_ack += len;
+       }
+}
+
+static void sii8620_burst_tx_rbuf_info(struct sii8620 *ctx, int size)
+{
+       struct mhl_burst_blk_rcv_buffer_info *d =
+               sii8620_burst_get_tx_buf(ctx, sizeof(*d));
+       if (!d)
+               return;
+
+       d->id = cpu_to_be16(MHL_BURST_ID_BLK_RCV_BUFFER_INFO);
+       d->size = cpu_to_le16(size);
+}
+
+static void sii8620_burst_rx_all(struct sii8620 *ctx)
+{
+       u8 *d = ctx->burst.rx_buf;
+       int count = ctx->burst.rx_count;
+
+       while (count-- > 0) {
+               int len = *d++;
+               int id = get_unaligned_be16(&d[0]);
+
+               switch (id) {
+               case MHL_BURST_ID_BLK_RCV_BUFFER_INFO:
+                       ctx->burst.r_size = get_unaligned_le16(&d[2]);
+                       break;
+               default:
+                       break;
+               }
+               count -= len;
+               d += len;
+       }
+       ctx->burst.rx_count = 0;
+}
+
 static void sii8620_fetch_edid(struct sii8620 *ctx)
 {
        u8 lm_ddc, ddc_cmd, int3, cbus;
@@ -1038,7 +1178,7 @@ static int sii8620_wait_for_fsm_state(struct sii8620 *ctx, u8 state)
 
                if ((s & MSK_COC_STAT_0_FSM_STATE) == state)
                        return 0;
-               if (s & BIT_COC_STAT_0_PLL_LOCKED)
+               if (!(s & BIT_COC_STAT_0_PLL_LOCKED))
                        return -EBUSY;
                usleep_range(4000, 6000);
        }
@@ -1414,6 +1554,19 @@ static void sii8620_irq_coc(struct sii8620 *ctx)
 {
        u8 stat = sii8620_readb(ctx, REG_COC_INTR);
 
+       if (stat & BIT_COC_CALIBRATION_DONE) {
+               u8 cstat = sii8620_readb(ctx, REG_COC_STAT_0);
+
+               cstat &= BIT_COC_STAT_0_PLL_LOCKED | MSK_COC_STAT_0_FSM_STATE;
+               if (cstat == (BIT_COC_STAT_0_PLL_LOCKED | 0x02)) {
+                       sii8620_write_seq_static(ctx,
+                               REG_COC_CTLB, 0,
+                               REG_TRXINTMH, BIT_TDM_INTR_SYNC_DATA
+                                             | BIT_TDM_INTR_SYNC_WAIT
+                       );
+               }
+       }
+
        sii8620_write(ctx, REG_COC_INTR, stat);
 }
 
@@ -1504,6 +1657,41 @@ static void sii8620_irq_infr(struct sii8620 *ctx)
                sii8620_start_video(ctx);
 }
 
+static void sii8620_irq_tdm(struct sii8620 *ctx)
+{
+       u8 stat = sii8620_readb(ctx, REG_TRXINTH);
+       u8 tdm = sii8620_readb(ctx, REG_TRXSTA2);
+
+       if ((tdm & MSK_TDM_SYNCHRONIZED) == VAL_TDM_SYNCHRONIZED) {
+               ctx->mode = CM_ECBUS_S;
+               ctx->burst.rx_ack = 0;
+               ctx->burst.r_size = SII8620_BURST_BUF_LEN;
+               sii8620_burst_tx_rbuf_info(ctx, SII8620_BURST_BUF_LEN);
+               sii8620_mt_read_devcap(ctx, true);
+       } else {
+               sii8620_write_seq_static(ctx,
+                       REG_MHL_PLL_CTL2, 0,
+                       REG_MHL_PLL_CTL2, BIT_MHL_PLL_CTL2_CLKDETECT_EN
+               );
+       }
+
+       sii8620_write(ctx, REG_TRXINTH, stat);
+}
+
+static void sii8620_irq_block(struct sii8620 *ctx)
+{
+       u8 stat = sii8620_readb(ctx, REG_EMSCINTR);
+
+       if (stat & BIT_EMSCINTR_SPI_DVLD) {
+               u8 bstat = sii8620_readb(ctx, REG_SPIBURSTSTAT);
+
+               if (bstat & BIT_SPIBURSTSTAT_EMSC_NORMAL_MODE)
+                       sii8620_burst_receive(ctx);
+       }
+
+       sii8620_write(ctx, REG_EMSCINTR, stat);
+}
+
 /* endian agnostic, non-volatile version of test_bit */
 static bool sii8620_test_bit(unsigned int nr, const u8 *addr)
 {
@@ -1519,8 +1707,10 @@ static irqreturn_t sii8620_irq_thread(int irq, void *data)
                { BIT_FAST_INTR_STAT_DISC, sii8620_irq_disc },
                { BIT_FAST_INTR_STAT_G2WB, sii8620_irq_g2wb },
                { BIT_FAST_INTR_STAT_COC, sii8620_irq_coc },
+               { BIT_FAST_INTR_STAT_TDM, sii8620_irq_tdm },
                { BIT_FAST_INTR_STAT_MSC, sii8620_irq_msc },
                { BIT_FAST_INTR_STAT_MERR, sii8620_irq_merr },
+               { BIT_FAST_INTR_STAT_BLOCK, sii8620_irq_block },
                { BIT_FAST_INTR_STAT_EDID, sii8620_irq_edid },
                { BIT_FAST_INTR_STAT_SCDT, sii8620_irq_scdt },
                { BIT_FAST_INTR_STAT_INFR, sii8620_irq_infr },
@@ -1536,7 +1726,9 @@ static irqreturn_t sii8620_irq_thread(int irq, void *data)
                if (sii8620_test_bit(irq_vec[i].bit, stats))
                        irq_vec[i].handler(ctx);
 
+       sii8620_burst_rx_all(ctx);
        sii8620_mt_work(ctx);
+       sii8620_burst_send(ctx);
 
        ret = sii8620_clear_error(ctx);
        if (ret) {
index 3ee4e7e4222625c1b0b839b2a507d7bb550282c7..f7bfbc3d16b942671d7c6322912ced41d1ea20b3 100644 (file)
 
 /* TDM RX Status 2nd, default value: 0x00 */
 #define REG_TRXSTA2                            0x015c
+#define MSK_TDM_SYNCHRONIZED                   0xC0
+#define VAL_TDM_SYNCHRONIZED                   0x80
 
 /* TDM RX INT Low, default value: 0x00 */
 #define REG_TRXINTL                            0x0163
 
 /* TDM RX INT High, default value: 0x00 */
 #define REG_TRXINTH                            0x0164
+#define BIT_TDM_INTR_SYNC_DATA                 BIT(0)
+#define BIT_TDM_INTR_SYNC_WAIT                 BIT(1)
 
 /* TDM RX INTMASK High, default value: 0x00 */
 #define REG_TRXINTMH                           0x0166