diff options
Diffstat (limited to 'drivers/gpu/drm/mcde')
-rw-r--r-- | drivers/gpu/drm/mcde/mcde_display.c | 57 | ||||
-rw-r--r-- | drivers/gpu/drm/mcde/mcde_drm.h | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/mcde/mcde_drv.c | 18 | ||||
-rw-r--r-- | drivers/gpu/drm/mcde/mcde_dsi.c | 416 | ||||
-rw-r--r-- | drivers/gpu/drm/mcde/mcde_dsi_regs.h | 22 |
5 files changed, 323 insertions, 191 deletions
diff --git a/drivers/gpu/drm/mcde/mcde_display.c b/drivers/gpu/drm/mcde/mcde_display.c index 751454ae3cd1..e59907e68854 100644 --- a/drivers/gpu/drm/mcde/mcde_display.c +++ b/drivers/gpu/drm/mcde/mcde_display.c @@ -498,24 +498,20 @@ static void mcde_configure_channel(struct mcde *mcde, enum mcde_channel ch, } /* Set up channel 0 sync (based on chnl_update_registers()) */ - if (mcde->te_sync) { - /* - * Turn on hardware TE0 synchronization - */ + if (mcde->video_mode || mcde->te_sync) val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_HARDWARE << MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT; - val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE0 - << MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT; - } else { - /* - * Set up sync source to software, out sync formatter - * Code mostly from mcde_hw.c chnl_update_registers() - */ + else val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SOFTWARE << MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT; + + if (mcde->te_sync) + val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE0 + << MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT; + else val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_FORMATTER << MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT; - } + writel(val, mcde->regs + sync); /* Set up pixels per line and lines per frame */ @@ -934,10 +930,17 @@ static void mcde_display_enable(struct drm_simple_display_pipe *pipe, val = readl(mcde->regs + MCDE_CRC); val |= MCDE_CRC_SYCEN0; writel(val, mcde->regs + MCDE_CRC); - - drm_crtc_vblank_on(crtc); } + drm_crtc_vblank_on(crtc); + + if (mcde->video_mode) + /* + * Keep FIFO permanently enabled in video mode, + * otherwise MCDE will stop feeding data to the panel. + */ + mcde_enable_fifo(mcde, MCDE_FIFO_A); + dev_info(drm->dev, "MCDE display is enabled\n"); } @@ -946,13 +949,22 @@ static void mcde_display_disable(struct drm_simple_display_pipe *pipe) struct drm_crtc *crtc = &pipe->crtc; struct drm_device *drm = crtc->dev; struct mcde *mcde = drm->dev_private; + struct drm_pending_vblank_event *event; - if (mcde->te_sync) - drm_crtc_vblank_off(crtc); + drm_crtc_vblank_off(crtc); /* Disable FIFO A flow */ mcde_disable_fifo(mcde, MCDE_FIFO_A, true); + event = crtc->state->event; + if (event) { + crtc->state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } + dev_info(drm->dev, "MCDE display is disabled\n"); } @@ -1048,8 +1060,9 @@ static void mcde_display_update(struct drm_simple_display_pipe *pipe, */ if (fb) { mcde_set_extsrc(mcde, drm_fb_cma_get_gem_addr(fb, pstate, 0)); - /* Send a single frame using software sync */ - mcde_display_send_one_frame(mcde); + if (!mcde->video_mode) + /* Send a single frame using software sync */ + mcde_display_send_one_frame(mcde); dev_info_once(mcde->dev, "sent first display update\n"); } else { /* @@ -1097,6 +1110,8 @@ static struct drm_simple_display_pipe_funcs mcde_display_funcs = { .enable = mcde_display_enable, .disable = mcde_display_disable, .update = mcde_display_update, + .enable_vblank = mcde_display_enable_vblank, + .disable_vblank = mcde_display_disable_vblank, .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, }; @@ -1123,12 +1138,6 @@ int mcde_display_init(struct drm_device *drm) DRM_FORMAT_YUV422, }; - /* Provide vblank only when we have TE enabled */ - if (mcde->te_sync) { - mcde_display_funcs.enable_vblank = mcde_display_enable_vblank; - mcde_display_funcs.disable_vblank = mcde_display_disable_vblank; - } - ret = drm_simple_display_pipe_init(drm, &mcde->pipe, &mcde_display_funcs, formats, ARRAY_SIZE(formats), diff --git a/drivers/gpu/drm/mcde/mcde_drm.h b/drivers/gpu/drm/mcde/mcde_drm.h index dab4db021231..80edd6628979 100644 --- a/drivers/gpu/drm/mcde/mcde_drm.h +++ b/drivers/gpu/drm/mcde/mcde_drm.h @@ -19,6 +19,7 @@ struct mcde { struct mipi_dsi_device *mdsi; s16 stride; bool te_sync; + bool video_mode; bool oneshot_mode; unsigned int flow_active; spinlock_t flow_lock; /* Locks the channel flow control */ diff --git a/drivers/gpu/drm/mcde/mcde_drv.c b/drivers/gpu/drm/mcde/mcde_drv.c index 5649887d2b90..9008ddcfc528 100644 --- a/drivers/gpu/drm/mcde/mcde_drv.c +++ b/drivers/gpu/drm/mcde/mcde_drv.c @@ -179,18 +179,10 @@ static int mcde_modeset_init(struct drm_device *drm) mode_config->min_height = 1; mode_config->max_height = 1080; - /* - * Currently we only support vblank handling on the DSI bridge, using - * TE synchronization. If TE sync is not set up, it is still possible - * to push out a single update on demand, but this is hard for DRM to - * exploit. - */ - if (mcde->te_sync) { - ret = drm_vblank_init(drm, 1); - if (ret) { - dev_err(drm->dev, "failed to init vblank\n"); - goto out_config; - } + ret = drm_vblank_init(drm, 1); + if (ret) { + dev_err(drm->dev, "failed to init vblank\n"); + goto out_config; } ret = mcde_display_init(drm); @@ -339,8 +331,6 @@ static int mcde_probe(struct platform_device *pdev) drm->dev_private = mcde; platform_set_drvdata(pdev, drm); - /* Enable use of the TE signal and interrupt */ - mcde->te_sync = true; /* Enable continuous updates: this is what Linux' framebuffer expects */ mcde->oneshot_mode = false; drm->dev_private = mcde; diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c index ef4c630afe3f..bb6528b01cd0 100644 --- a/drivers/gpu/drm/mcde/mcde_dsi.c +++ b/drivers/gpu/drm/mcde/mcde_dsi.c @@ -39,7 +39,6 @@ struct mcde_dsi { struct device *dev; struct mcde *mcde; struct drm_bridge bridge; - struct drm_connector connector; struct drm_panel *panel; struct drm_bridge *bridge_out; struct mipi_dsi_host dsi_host; @@ -64,11 +63,6 @@ static inline struct mcde_dsi *host_to_mcde_dsi(struct mipi_dsi_host *h) return container_of(h, struct mcde_dsi, dsi_host); } -static inline struct mcde_dsi *connector_to_mcde_dsi(struct drm_connector *c) -{ - return container_of(c, struct mcde_dsi, connector); -} - bool mcde_dsi_irq(struct mipi_dsi_device *mdsi) { struct mcde_dsi *d; @@ -124,12 +118,41 @@ bool mcde_dsi_irq(struct mipi_dsi_device *mdsi) val = readl(d->regs + DSI_VID_MODE_STS_FLAG); if (val) - dev_err(d->dev, "some video mode error status\n"); + dev_dbg(d->dev, "DSI_VID_MODE_STS_FLAG = %08x\n", val); + if (val & DSI_VID_MODE_STS_VSG_RUNNING) + dev_dbg(d->dev, "VID mode VSG running\n"); + if (val & DSI_VID_MODE_STS_ERR_MISSING_DATA) + dev_err(d->dev, "VID mode missing data\n"); + if (val & DSI_VID_MODE_STS_ERR_MISSING_HSYNC) + dev_err(d->dev, "VID mode missing HSYNC\n"); + if (val & DSI_VID_MODE_STS_ERR_MISSING_VSYNC) + dev_err(d->dev, "VID mode missing VSYNC\n"); + if (val & DSI_VID_MODE_STS_REG_ERR_SMALL_LENGTH) + dev_err(d->dev, "VID mode less bytes than expected between two HSYNC\n"); + if (val & DSI_VID_MODE_STS_REG_ERR_SMALL_HEIGHT) + dev_err(d->dev, "VID mode less lines than expected between two VSYNC\n"); + if (val & (DSI_VID_MODE_STS_ERR_BURSTWRITE | + DSI_VID_MODE_STS_ERR_LINEWRITE | + DSI_VID_MODE_STS_ERR_LONGREAD)) + dev_err(d->dev, "VID mode read/write error\n"); + if (val & DSI_VID_MODE_STS_ERR_VRS_WRONG_LENGTH) + dev_err(d->dev, "VID mode received packets differ from expected size\n"); + if (val & DSI_VID_MODE_STS_VSG_RECOVERY) + dev_err(d->dev, "VID mode VSG in recovery mode\n"); writel(val, d->regs + DSI_VID_MODE_STS_CLR); return te_received; } +static void mcde_dsi_attach_to_mcde(struct mcde_dsi *d) +{ + d->mcde->mdsi = d->mdsi; + + d->mcde->video_mode = !!(d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO); + /* Enable use of the TE signal for all command mode panels */ + d->mcde->te_sync = !d->mcde->video_mode; +} + static int mcde_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *mdsi) { @@ -148,7 +171,7 @@ static int mcde_dsi_host_attach(struct mipi_dsi_host *host, d->mdsi = mdsi; if (d->mcde) - d->mcde->mdsi = mdsi; + mcde_dsi_attach_to_mcde(d); return 0; } @@ -223,25 +246,25 @@ static ssize_t mcde_dsi_host_transfer(struct mipi_dsi_host *host, if (txlen > 0) { val = 0; for (i = 0; i < 4 && i < txlen; i++) - val |= tx[i] << (i & 3) * 8; + val |= tx[i] << (i * 8); } writel(val, d->regs + DSI_DIRECT_CMD_WRDAT0); if (txlen > 4) { val = 0; for (i = 0; i < 4 && (i + 4) < txlen; i++) - val |= tx[i + 4] << (i & 3) * 8; + val |= tx[i + 4] << (i * 8); writel(val, d->regs + DSI_DIRECT_CMD_WRDAT1); } if (txlen > 8) { val = 0; for (i = 0; i < 4 && (i + 8) < txlen; i++) - val |= tx[i + 8] << (i & 3) * 8; + val |= tx[i + 8] << (i * 8); writel(val, d->regs + DSI_DIRECT_CMD_WRDAT2); } if (txlen > 12) { val = 0; for (i = 0; i < 4 && (i + 12) < txlen; i++) - val |= tx[i + 12] << (i & 3) * 8; + val |= tx[i + 12] << (i * 8); writel(val, d->regs + DSI_DIRECT_CMD_WRDAT3); } @@ -336,7 +359,7 @@ void mcde_dsi_te_request(struct mipi_dsi_device *mdsi) val |= 0 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT; val |= 2 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT; val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN; - val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_SHORT_WRITE_1 << + val |= MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT; writel(val, d->regs + DSI_DIRECT_CMD_MAIN_SETTINGS); @@ -365,13 +388,14 @@ void mcde_dsi_te_request(struct mipi_dsi_device *mdsi) static void mcde_dsi_setup_video_mode(struct mcde_dsi *d, const struct drm_display_mode *mode) { - u8 bpp = mipi_dsi_pixel_format_to_bpp(d->mdsi->format); + /* cpp, characters per pixel, number of bytes per pixel */ + u8 cpp = mipi_dsi_pixel_format_to_bpp(d->mdsi->format) / 8; + u64 pclk; u64 bpl; - u32 hfp; - u32 hbp; - u32 hsa; + int hfp; + int hbp; + int hsa; u32 blkline_pck, line_duration; - u32 blkeol_pck, blkeol_duration; u32 val; val = 0; @@ -408,11 +432,21 @@ static void mcde_dsi_setup_video_mode(struct mcde_dsi *d, return; } - /* TODO: TVG could be enabled here */ + /* TODO: TVG (test video generator) could be enabled here */ - /* Send blanking packet */ + /* + * During vertical blanking: go to LP mode + * Like with the EOL setting, if this is not set, the EOL area will be + * filled with NULL or blanking packets in the vblank area. + * FIXME: some Samsung phones and display panels such as s6e63m0 use + * DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_BLANKING here instead, + * figure out how to properly configure that from the panel. + */ val |= DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_0; - /* Send EOL packet */ + /* + * During EOL: go to LP mode. If this is not set, the EOL area will be + * filled with NULL or blanking packets. + */ val |= DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0; /* Recovery mode 1 */ val |= 1 << DSI_VID_MAIN_CTL_RECOVERY_MODE_SHIFT; @@ -420,13 +454,13 @@ static void mcde_dsi_setup_video_mode(struct mcde_dsi *d, writel(val, d->regs + DSI_VID_MAIN_CTL); /* Vertical frame parameters are pretty straight-forward */ - val = mode->vdisplay << DSI_VID_VSIZE_VSA_LENGTH_SHIFT; + val = mode->vdisplay << DSI_VID_VSIZE_VACT_LENGTH_SHIFT; /* vertical front porch */ val |= (mode->vsync_start - mode->vdisplay) << DSI_VID_VSIZE_VFP_LENGTH_SHIFT; /* vertical sync active */ val |= (mode->vsync_end - mode->vsync_start) - << DSI_VID_VSIZE_VACT_LENGTH_SHIFT; + << DSI_VID_VSIZE_VSA_LENGTH_SHIFT; /* vertical back porch */ val |= (mode->vtotal - mode->vsync_end) << DSI_VID_VSIZE_VBP_LENGTH_SHIFT; @@ -434,36 +468,54 @@ static void mcde_dsi_setup_video_mode(struct mcde_dsi *d, /* * Horizontal frame parameters: - * horizontal resolution is given in pixels and must be re-calculated - * into bytes since this is what the hardware expects. + * horizontal resolution is given in pixels but must be re-calculated + * into bytes since this is what the hardware expects, these registers + * define the payload size of the packet. + * + * hfp = horizontal front porch in bytes + * hbp = horizontal back porch in bytes + * hsa = horizontal sync active in bytes * * 6 + 2 is HFP header + checksum */ - hfp = (mode->hsync_start - mode->hdisplay) * bpp - 6 - 2; + hfp = (mode->hsync_start - mode->hdisplay) * cpp - 6 - 2; if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { /* + * Use sync pulse for sync: explicit HSA time * 6 is HBP header + checksum * 4 is RGB header + checksum */ - hbp = (mode->htotal - mode->hsync_end) * bpp - 4 - 6; + hbp = (mode->htotal - mode->hsync_end) * cpp - 4 - 6; /* * 6 is HBP header + checksum * 4 is HSW packet bytes * 4 is RGB header + checksum */ - hsa = (mode->hsync_end - mode->hsync_start) * bpp - 4 - 4 - 6; + hsa = (mode->hsync_end - mode->hsync_start) * cpp - 4 - 4 - 6; } else { /* - * HBP includes both back porch and sync + * Use event for sync: HBP includes both back porch and sync * 6 is HBP header + checksum * 4 is HSW packet bytes * 4 is RGB header + checksum */ - hbp = (mode->htotal - mode->hsync_start) * bpp - 4 - 4 - 6; - /* HSA is not considered in this mode and set to 0 */ + hbp = (mode->htotal - mode->hsync_start) * cpp - 4 - 4 - 6; + /* HSA is not present in this mode and set to 0 */ hsa = 0; } - dev_dbg(d->dev, "hfp: %u, hbp: %u, hsa: %u\n", + if (hfp < 0) { + dev_info(d->dev, "hfp negative, set to 0\n"); + hfp = 0; + } + if (hbp < 0) { + dev_info(d->dev, "hbp negative, set to 0\n"); + hbp = 0; + } + if (hsa < 0) { + dev_info(d->dev, "hsa negative, set to 0\n"); + hsa = 0; + } + dev_dbg(d->dev, "hfp: %u, hbp: %u, hsa: %u bytes\n", hfp, hbp, hsa); /* Frame parameters: horizontal sync active */ @@ -474,91 +526,185 @@ static void mcde_dsi_setup_video_mode(struct mcde_dsi *d, val |= hfp << DSI_VID_HSIZE1_HFP_LENGTH_SHIFT; writel(val, d->regs + DSI_VID_HSIZE1); - /* RGB data length (bytes on one scanline) */ - val = mode->hdisplay * (bpp / 8); + /* RGB data length (visible bytes on one scanline) */ + val = mode->hdisplay * cpp; writel(val, d->regs + DSI_VID_HSIZE2); + dev_dbg(d->dev, "RGB length, visible area on a line: %u bytes\n", val); - /* TODO: further adjustments for TVG mode here */ + /* + * Calculate the time between two pixels in picoseconds using + * the supplied refresh rate and total resolution including + * porches and sync. + */ + /* (ps/s) / (pixels/s) = ps/pixels */ + pclk = DIV_ROUND_UP_ULL(1000000000000, + (mode->vrefresh * mode->htotal * mode->vtotal)); + dev_dbg(d->dev, "picoseconds between two pixels: %llu\n", + pclk); /* - * EOL packet length from bits per line calculations: pixel clock - * is given in kHz, calculate the time between two pixels in - * picoseconds. + * How many bytes per line will this update frequency yield? + * + * Calculate the number of picoseconds for one scanline (1), then + * divide by 1000000000000 (2) to get in pixels per second we + * want to output. + * + * Multiply with number of bytes per second at this video display + * frequency (3) to get number of bytes transferred during this + * time. Notice that we use the frequency the display wants, + * not what we actually get from the DSI PLL, which is hs_freq. + * + * These arithmetics are done in a different order to avoid + * overflow. */ - bpl = mode->clock * mode->htotal; - bpl *= (d->hs_freq / 8); - do_div(bpl, 1000000); /* microseconds */ - do_div(bpl, 1000000); /* seconds */ + bpl = pclk * mode->htotal; /* (1) picoseconds per line */ + dev_dbg(d->dev, "picoseconds per line: %llu\n", bpl); + /* Multiply with bytes per second (3) */ + bpl *= (d->mdsi->hs_rate / 8); + /* Pixels per second (2) */ + bpl = DIV_ROUND_DOWN_ULL(bpl, 1000000); /* microseconds */ + bpl = DIV_ROUND_DOWN_ULL(bpl, 1000000); /* seconds */ + /* parallel transactions in all lanes */ bpl *= d->mdsi->lanes; - dev_dbg(d->dev, "calculated bytes per line: %llu\n", bpl); + dev_dbg(d->dev, + "calculated bytes per line: %llu @ %d Hz with HS %lu Hz\n", + bpl, mode->vrefresh, d->mdsi->hs_rate); + /* * 6 is header + checksum, header = 4 bytes, checksum = 2 bytes * 4 is short packet for vsync/hsync */ if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { - /* Fixme: isn't the hsync width in pixels? */ + /* Set the event packet size to 0 (not used) */ + writel(0, d->regs + DSI_VID_BLKSIZE1); + /* + * FIXME: isn't the hsync width in pixels? The porch and + * sync area size is in pixels here, but this -6 + * seems to be for bytes. It looks like this in the vendor + * code though. Is it completely untested? + */ blkline_pck = bpl - (mode->hsync_end - mode->hsync_start) - 6; val = blkline_pck << DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_SHIFT; writel(val, d->regs + DSI_VID_BLKSIZE2); } else { + /* Set the sync pulse packet size to 0 (not used) */ + writel(0, d->regs + DSI_VID_BLKSIZE2); + /* Specifying payload size in bytes (-4-6 from manual) */ blkline_pck = bpl - 4 - 6; + if (blkline_pck > 0x1FFF) + dev_err(d->dev, "blkline_pck too big %d bytes\n", + blkline_pck); val = blkline_pck << DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_SHIFT; + val &= DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_MASK; writel(val, d->regs + DSI_VID_BLKSIZE1); } - line_duration = (blkline_pck + 6) / d->mdsi->lanes; - dev_dbg(d->dev, "line duration %u\n", line_duration); + /* + * The line duration is used to scale back the frequency from + * the max frequency supported by the HS clock to the desired + * update frequency in vrefresh. + */ + line_duration = blkline_pck + 6; + /* + * The datasheet contains this complex condition to decreasing + * the line duration by 1 under very specific circumstances. + * Here we also imply that LP is used during burst EOL. + */ + if (d->mdsi->lanes == 2 && (hsa & 0x01) && (hfp & 0x01) + && (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)) + line_duration--; + line_duration = DIV_ROUND_CLOSEST(line_duration, d->mdsi->lanes); + dev_dbg(d->dev, "line duration %u bytes\n", line_duration); val = line_duration << DSI_VID_DPHY_TIME_REG_LINE_DURATION_SHIFT; /* * This is the time to perform LP->HS on D-PHY * FIXME: nowhere to get this from: DT property on the DSI? + * The manual says this is "system dependent". + * values like 48 and 72 seen in the vendor code. */ - val |= 0 << DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_SHIFT; + val |= 48 << DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_SHIFT; writel(val, d->regs + DSI_VID_DPHY_TIME); - /* Calculate block end of line */ - blkeol_pck = bpl - mode->hdisplay * bpp - 6; - blkeol_duration = (blkeol_pck + 6) / d->mdsi->lanes; - dev_dbg(d->dev, "blkeol pck: %u, duration: %u\n", - blkeol_pck, blkeol_duration); - + /* + * See the manual figure 657 page 2203 for understanding the impact + * of the different burst mode settings. + */ if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) { - /* Set up EOL clock for burst mode */ + int blkeol_pck, blkeol_duration; + /* + * Packet size at EOL for burst mode, this is only used + * if DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0 is NOT set, + * but we instead send NULL or blanking packets at EOL. + * This is given in number of bytes. + * + * See the manual page 2198 for the 13 reg_blkeol_pck bits. + */ + blkeol_pck = bpl - (mode->htotal * cpp) - 6; + if (blkeol_pck < 0) { + dev_err(d->dev, "video block does not fit on line!\n"); + dev_err(d->dev, + "calculated bytes per line: %llu @ %d Hz\n", + bpl, mode->vrefresh); + dev_err(d->dev, + "bytes per line (blkline_pck) %u bytes\n", + blkline_pck); + dev_err(d->dev, + "blkeol_pck becomes %d bytes\n", blkeol_pck); + return; + } + dev_dbg(d->dev, "BLKEOL packet: %d bytes\n", blkeol_pck); + val = readl(d->regs + DSI_VID_BLKSIZE1); + val &= ~DSI_VID_BLKSIZE1_BLKEOL_PCK_MASK; val |= blkeol_pck << DSI_VID_BLKSIZE1_BLKEOL_PCK_SHIFT; writel(val, d->regs + DSI_VID_BLKSIZE1); - writel(blkeol_pck, d->regs + DSI_VID_VCA_SETTING2); - - writel(blkeol_duration, d->regs + DSI_VID_PCK_TIME); - writel(blkeol_duration - 6, d->regs + DSI_VID_VCA_SETTING1); + /* Use the same value for exact burst limit */ + val = blkeol_pck << + DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_SHIFT; + val &= DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_MASK; + writel(val, d->regs + DSI_VID_VCA_SETTING2); + /* + * This BLKEOL duration is claimed to be the duration in clock + * cycles of the BLLP end-of-line (EOL) period for each line if + * DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0 is set. + * + * It is hard to trust the manuals' claim that this is in clock + * cycles as we mimic the behaviour of the vendor code, which + * appears to write a number of bytes that would have been + * transferred on a single lane. + * + * See the manual figure 657 page 2203 and page 2198 for the 13 + * reg_blkeol_duration bits. + * + * FIXME: should this also be set up also for non-burst mode + * according to figure 565 page 2202? + */ + blkeol_duration = DIV_ROUND_CLOSEST(blkeol_pck + 6, + d->mdsi->lanes); + dev_dbg(d->dev, "BLKEOL duration: %d clock cycles\n", + blkeol_duration); + + val = readl(d->regs + DSI_VID_PCK_TIME); + val &= ~DSI_VID_PCK_TIME_BLKEOL_DURATION_MASK; + val |= blkeol_duration << + DSI_VID_PCK_TIME_BLKEOL_DURATION_SHIFT; + writel(val, d->regs + DSI_VID_PCK_TIME); + + /* Max burst limit, this is given in bytes */ + val = readl(d->regs + DSI_VID_VCA_SETTING1); + val &= ~DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_MASK; + val |= (blkeol_pck - 6) << + DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_SHIFT; + writel(val, d->regs + DSI_VID_VCA_SETTING1); } /* Maximum line limit */ val = readl(d->regs + DSI_VID_VCA_SETTING2); - val |= blkline_pck << - DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_SHIFT; + val &= ~DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_MASK; + val |= (blkline_pck - 6) << + DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_SHIFT; writel(val, d->regs + DSI_VID_VCA_SETTING2); - - /* Put IF1 into video mode */ - val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); - val |= DSI_MCTL_MAIN_DATA_CTL_IF1_MODE; - writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); - - /* Disable command mode on IF1 */ - val = readl(d->regs + DSI_CMD_MODE_CTL); - val &= ~DSI_CMD_MODE_CTL_IF1_LP_EN; - writel(val, d->regs + DSI_CMD_MODE_CTL); - - /* Enable some error interrupts */ - val = readl(d->regs + DSI_VID_MODE_STS_CTL); - val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC; - val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA; - writel(val, d->regs + DSI_VID_MODE_STS_CTL); - - /* Enable video mode */ - val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); - val |= DSI_MCTL_MAIN_DATA_CTL_VID_EN; - writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); + dev_dbg(d->dev, "blkline pck: %d bytes\n", blkline_pck - 6); } static void mcde_dsi_start(struct mcde_dsi *d) @@ -670,30 +816,25 @@ static void mcde_dsi_start(struct mcde_dsi *d) static void mcde_dsi_bridge_enable(struct drm_bridge *bridge) { struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); + u32 val; + + if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + /* Enable video mode */ + val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); + val |= DSI_MCTL_MAIN_DATA_CTL_VID_EN; + writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); + } dev_info(d->dev, "enable DSI master\n"); }; -static void mcde_dsi_bridge_mode_set(struct drm_bridge *bridge, - const struct drm_display_mode *mode, - const struct drm_display_mode *adj) +static void mcde_dsi_bridge_pre_enable(struct drm_bridge *bridge) { struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); - unsigned long pixel_clock_hz = mode->clock * 1000; unsigned long hs_freq, lp_freq; u32 val; int ret; - if (!d->mdsi) { - dev_err(d->dev, "no DSI device attached to encoder!\n"); - return; - } - - dev_info(d->dev, "set DSI master to %dx%d %lu Hz %s mode\n", - mode->hdisplay, mode->vdisplay, pixel_clock_hz, - (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? "VIDEO" : "CMD" - ); - /* Copy maximum clock frequencies */ if (d->mdsi->lp_rate) lp_freq = d->mdsi->lp_rate; @@ -732,7 +873,21 @@ static void mcde_dsi_bridge_mode_set(struct drm_bridge *bridge, d->hs_freq); if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) { - mcde_dsi_setup_video_mode(d, mode); + /* Put IF1 into video mode */ + val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); + val |= DSI_MCTL_MAIN_DATA_CTL_IF1_MODE; + writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); + + /* Disable command mode on IF1 */ + val = readl(d->regs + DSI_CMD_MODE_CTL); + val &= ~DSI_CMD_MODE_CTL_IF1_LP_EN; + writel(val, d->regs + DSI_CMD_MODE_CTL); + + /* Enable some error interrupts */ + val = readl(d->regs + DSI_VID_MODE_STS_CTL); + val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC; + val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA; + writel(val, d->regs + DSI_VID_MODE_STS_CTL); } else { /* Command mode, clear IF1 ID */ val = readl(d->regs + DSI_CMD_MODE_CTL); @@ -746,6 +901,26 @@ static void mcde_dsi_bridge_mode_set(struct drm_bridge *bridge, } } +static void mcde_dsi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj) +{ + struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); + + if (!d->mdsi) { + dev_err(d->dev, "no DSI device attached to encoder!\n"); + return; + } + + dev_info(d->dev, "set DSI master to %dx%d %u Hz %s mode\n", + mode->hdisplay, mode->vdisplay, mode->clock * 1000, + (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? "VIDEO" : "CMD" + ); + + if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) + mcde_dsi_setup_video_mode(d, mode); +} + static void mcde_dsi_wait_for_command_mode_stop(struct mcde_dsi *d) { u32 val; @@ -811,67 +986,23 @@ static void mcde_dsi_bridge_disable(struct drm_bridge *bridge) clk_disable_unprepare(d->lp_clk); } -/* - * This connector needs no special handling, just use the default - * helpers for everything. It's pretty dummy. - */ -static const struct drm_connector_funcs mcde_dsi_connector_funcs = { - .reset = drm_atomic_helper_connector_reset, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = drm_connector_cleanup, - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, -}; - -static int mcde_dsi_get_modes(struct drm_connector *connector) -{ - struct mcde_dsi *d = connector_to_mcde_dsi(connector); - - /* Just pass the question to the panel */ - if (d->panel) - return drm_panel_get_modes(d->panel); - - /* TODO: deal with bridges */ - - return 0; -} - -static const struct drm_connector_helper_funcs -mcde_dsi_connector_helper_funcs = { - .get_modes = mcde_dsi_get_modes, -}; - static int mcde_dsi_bridge_attach(struct drm_bridge *bridge) { struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); struct drm_device *drm = bridge->dev; int ret; - drm_connector_helper_add(&d->connector, - &mcde_dsi_connector_helper_funcs); - if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { dev_err(d->dev, "we need atomic updates\n"); return -ENOTSUPP; } - ret = drm_connector_init(drm, &d->connector, - &mcde_dsi_connector_funcs, - DRM_MODE_CONNECTOR_DSI); - if (ret) { - dev_err(d->dev, "failed to initialize DSI bridge connector\n"); - return ret; - } - d->connector.polled = DRM_CONNECTOR_POLL_CONNECT; - /* The encoder in the bridge attached to the DSI bridge */ - drm_connector_attach_encoder(&d->connector, bridge->encoder); - /* Then we attach the DSI bridge to the output (panel etc) bridge */ + /* Attach the DSI bridge to the output (panel etc) bridge */ ret = drm_bridge_attach(bridge->encoder, d->bridge_out, bridge); if (ret) { dev_err(d->dev, "failed to attach the DSI bridge\n"); return ret; } - d->connector.status = connector_status_connected; return 0; } @@ -881,6 +1012,7 @@ static const struct drm_bridge_funcs mcde_dsi_bridge_funcs = { .mode_set = mcde_dsi_bridge_mode_set, .disable = mcde_dsi_bridge_disable, .enable = mcde_dsi_bridge_enable, + .pre_enable = mcde_dsi_bridge_pre_enable, }; static int mcde_dsi_bind(struct device *dev, struct device *master, @@ -901,7 +1033,7 @@ static int mcde_dsi_bind(struct device *dev, struct device *master, d->mcde = mcde; /* If the display attached before binding, set this up */ if (d->mdsi) - d->mcde->mdsi = d->mdsi; + mcde_dsi_attach_to_mcde(d); /* Obtain the clocks */ d->hs_clk = devm_clk_get(dev, "hs"); diff --git a/drivers/gpu/drm/mcde/mcde_dsi_regs.h b/drivers/gpu/drm/mcde/mcde_dsi_regs.h index c9253321a3be..16551af1089e 100644 --- a/drivers/gpu/drm/mcde/mcde_dsi_regs.h +++ b/drivers/gpu/drm/mcde/mcde_dsi_regs.h @@ -123,17 +123,6 @@ #define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT BIT(3) #define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT 8 #define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_MASK 0x00003F00 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_TURN_ON_PERIPHERAL 50 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHUT_DOWN_PERIPHERAL 34 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_SHORT_WRITE_0 3 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_SHORT_WRITE_1 19 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_SHORT_WRITE_2 35 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_GENERIC_LONG_WRITE 41 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_SHORT_WRITE_0 5 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_SHORT_WRITE_1 21 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_LONG_WRITE 57 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_DCS_READ 6 -#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SET_MAX_PKT_SIZE 55 #define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT 14 #define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT 16 #define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN BIT(21) @@ -239,6 +228,7 @@ #define DSI_VID_PCK_TIME 0x000000A8 #define DSI_VID_PCK_TIME_BLKEOL_DURATION_SHIFT 0 +#define DSI_VID_PCK_TIME_BLKEOL_DURATION_MASK 0x00000FFF #define DSI_VID_DPHY_TIME 0x000000AC #define DSI_VID_DPHY_TIME_REG_LINE_DURATION_SHIFT 0 @@ -248,6 +238,16 @@ #define DSI_VID_MODE_STS 0x000000BC #define DSI_VID_MODE_STS_VSG_RUNNING BIT(0) +#define DSI_VID_MODE_STS_ERR_MISSING_DATA BIT(1) +#define DSI_VID_MODE_STS_ERR_MISSING_HSYNC BIT(2) +#define DSI_VID_MODE_STS_ERR_MISSING_VSYNC BIT(3) +#define DSI_VID_MODE_STS_REG_ERR_SMALL_LENGTH BIT(4) +#define DSI_VID_MODE_STS_REG_ERR_SMALL_HEIGHT BIT(5) +#define DSI_VID_MODE_STS_ERR_BURSTWRITE BIT(6) +#define DSI_VID_MODE_STS_ERR_LINEWRITE BIT(7) +#define DSI_VID_MODE_STS_ERR_LONGREAD BIT(8) +#define DSI_VID_MODE_STS_ERR_VRS_WRONG_LENGTH BIT(9) +#define DSI_VID_MODE_STS_VSG_RECOVERY BIT(10) #define DSI_VID_VCA_SETTING1 0x000000C0 #define DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_SHIFT 0 |