![Fabio Estevam](/assets/img/avatar_default.png)
Currently the framebuffer content is displayed with incorrect offsets in both the vertical and horizontal directions. The fbdev version of the driver does not show this problem. Breno Lima dumped the eLCDIF controller registers on both the drm and fbdev drivers and noticed that the VDCTRL3 register is configured incorrectly in the drm driver. The fbdev driver calculates the vertical and horizontal wait counts of the VDCTRL3 register by doing: back porch + sync length. Looking at the horizontal and vertical timing diagram from include/drm/drm_modes.h this value corresponds to: crtc_[hv]total - crtc_[hv]sync_start So fix the VDCTRL3 register setting accordingly so that the eLCDIF controller can properly show the framebuffer content in the correct position. Reported-by: Breno Lima <breno.lima@nxp.com> Signed-off-by: Fabio Estevam <fabio.estevam@nxp.com> Tested-by: Breno Lima <breno.lima@nxp.com> Tested-by: Marek Vasut <marex@denx.de> Signed-off-by: Dave Airlie <airlied@redhat.com>
279 lines
8.1 KiB
C
279 lines
8.1 KiB
C
/*
|
|
* Copyright (C) 2016 Marek Vasut <marex@denx.de>
|
|
*
|
|
* This code is based on drivers/video/fbdev/mxsfb.c :
|
|
* Copyright (C) 2010 Juergen Beisert, Pengutronix
|
|
* Copyright (C) 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
* Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_crtc.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_fb_helper.h>
|
|
#include <drm/drm_fb_cma_helper.h>
|
|
#include <drm/drm_gem_cma_helper.h>
|
|
#include <drm/drm_of.h>
|
|
#include <drm/drm_plane_helper.h>
|
|
#include <drm/drm_simple_kms_helper.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/platform_data/simplefb.h>
|
|
#include <video/videomode.h>
|
|
|
|
#include "mxsfb_drv.h"
|
|
#include "mxsfb_regs.h"
|
|
|
|
static u32 set_hsync_pulse_width(struct mxsfb_drm_private *mxsfb, u32 val)
|
|
{
|
|
return (val & mxsfb->devdata->hs_wdth_mask) <<
|
|
mxsfb->devdata->hs_wdth_shift;
|
|
}
|
|
|
|
/* Setup the MXSFB registers for decoding the pixels out of the framebuffer */
|
|
static int mxsfb_set_pixel_fmt(struct mxsfb_drm_private *mxsfb)
|
|
{
|
|
struct drm_crtc *crtc = &mxsfb->pipe.crtc;
|
|
struct drm_device *drm = crtc->dev;
|
|
const u32 format = crtc->primary->state->fb->format->format;
|
|
u32 ctrl, ctrl1;
|
|
|
|
ctrl = CTRL_BYPASS_COUNT | CTRL_MASTER;
|
|
|
|
/*
|
|
* WARNING: The bus width, CTRL_SET_BUS_WIDTH(), is configured to
|
|
* match the selected mode here. This differs from the original
|
|
* MXSFB driver, which had the option to configure the bus width
|
|
* to arbitrary value. This limitation should not pose an issue.
|
|
*/
|
|
|
|
/* CTRL1 contains IRQ config and status bits, preserve those. */
|
|
ctrl1 = readl(mxsfb->base + LCDC_CTRL1);
|
|
ctrl1 &= CTRL1_CUR_FRAME_DONE_IRQ_EN | CTRL1_CUR_FRAME_DONE_IRQ;
|
|
|
|
switch (format) {
|
|
case DRM_FORMAT_RGB565:
|
|
dev_dbg(drm->dev, "Setting up RGB565 mode\n");
|
|
ctrl |= CTRL_SET_WORD_LENGTH(0);
|
|
ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf);
|
|
break;
|
|
case DRM_FORMAT_XRGB8888:
|
|
dev_dbg(drm->dev, "Setting up XRGB8888 mode\n");
|
|
ctrl |= CTRL_SET_WORD_LENGTH(3);
|
|
/* Do not use packed pixels = one pixel per word instead. */
|
|
ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0x7);
|
|
break;
|
|
default:
|
|
dev_err(drm->dev, "Unhandled pixel format %08x\n", format);
|
|
return -EINVAL;
|
|
}
|
|
|
|
writel(ctrl1, mxsfb->base + LCDC_CTRL1);
|
|
writel(ctrl, mxsfb->base + LCDC_CTRL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mxsfb_set_bus_fmt(struct mxsfb_drm_private *mxsfb)
|
|
{
|
|
struct drm_crtc *crtc = &mxsfb->pipe.crtc;
|
|
struct drm_device *drm = crtc->dev;
|
|
u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
|
|
u32 reg;
|
|
|
|
reg = readl(mxsfb->base + LCDC_CTRL);
|
|
|
|
if (mxsfb->connector.display_info.num_bus_formats)
|
|
bus_format = mxsfb->connector.display_info.bus_formats[0];
|
|
|
|
reg &= ~CTRL_BUS_WIDTH_MASK;
|
|
switch (bus_format) {
|
|
case MEDIA_BUS_FMT_RGB565_1X16:
|
|
reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_16BIT);
|
|
break;
|
|
case MEDIA_BUS_FMT_RGB666_1X18:
|
|
reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_18BIT);
|
|
break;
|
|
case MEDIA_BUS_FMT_RGB888_1X24:
|
|
reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_24BIT);
|
|
break;
|
|
default:
|
|
dev_err(drm->dev, "Unknown media bus format %d\n", bus_format);
|
|
break;
|
|
}
|
|
writel(reg, mxsfb->base + LCDC_CTRL);
|
|
}
|
|
|
|
static void mxsfb_enable_controller(struct mxsfb_drm_private *mxsfb)
|
|
{
|
|
u32 reg;
|
|
|
|
if (mxsfb->clk_disp_axi)
|
|
clk_prepare_enable(mxsfb->clk_disp_axi);
|
|
clk_prepare_enable(mxsfb->clk);
|
|
mxsfb_enable_axi_clk(mxsfb);
|
|
|
|
/* If it was disabled, re-enable the mode again */
|
|
writel(CTRL_DOTCLK_MODE, mxsfb->base + LCDC_CTRL + REG_SET);
|
|
|
|
/* Enable the SYNC signals first, then the DMA engine */
|
|
reg = readl(mxsfb->base + LCDC_VDCTRL4);
|
|
reg |= VDCTRL4_SYNC_SIGNALS_ON;
|
|
writel(reg, mxsfb->base + LCDC_VDCTRL4);
|
|
|
|
writel(CTRL_RUN, mxsfb->base + LCDC_CTRL + REG_SET);
|
|
}
|
|
|
|
static void mxsfb_disable_controller(struct mxsfb_drm_private *mxsfb)
|
|
{
|
|
u32 reg;
|
|
|
|
/*
|
|
* Even if we disable the controller here, it will still continue
|
|
* until its FIFOs are running out of data
|
|
*/
|
|
writel(CTRL_DOTCLK_MODE, mxsfb->base + LCDC_CTRL + REG_CLR);
|
|
|
|
readl_poll_timeout(mxsfb->base + LCDC_CTRL, reg, !(reg & CTRL_RUN),
|
|
0, 1000);
|
|
|
|
reg = readl(mxsfb->base + LCDC_VDCTRL4);
|
|
reg &= ~VDCTRL4_SYNC_SIGNALS_ON;
|
|
writel(reg, mxsfb->base + LCDC_VDCTRL4);
|
|
|
|
mxsfb_disable_axi_clk(mxsfb);
|
|
|
|
clk_disable_unprepare(mxsfb->clk);
|
|
if (mxsfb->clk_disp_axi)
|
|
clk_disable_unprepare(mxsfb->clk_disp_axi);
|
|
}
|
|
|
|
static void mxsfb_crtc_mode_set_nofb(struct mxsfb_drm_private *mxsfb)
|
|
{
|
|
struct drm_display_mode *m = &mxsfb->pipe.crtc.state->adjusted_mode;
|
|
const u32 bus_flags = mxsfb->connector.display_info.bus_flags;
|
|
u32 vdctrl0, vsync_pulse_len, hsync_pulse_len;
|
|
int err;
|
|
|
|
/*
|
|
* It seems, you can't re-program the controller if it is still
|
|
* running. This may lead to shifted pictures (FIFO issue?), so
|
|
* first stop the controller and drain its FIFOs.
|
|
*/
|
|
mxsfb_enable_axi_clk(mxsfb);
|
|
|
|
/* Clear the FIFOs */
|
|
writel(CTRL1_FIFO_CLEAR, mxsfb->base + LCDC_CTRL1 + REG_SET);
|
|
|
|
err = mxsfb_set_pixel_fmt(mxsfb);
|
|
if (err)
|
|
return;
|
|
|
|
clk_set_rate(mxsfb->clk, m->crtc_clock * 1000);
|
|
|
|
writel(TRANSFER_COUNT_SET_VCOUNT(m->crtc_vdisplay) |
|
|
TRANSFER_COUNT_SET_HCOUNT(m->crtc_hdisplay),
|
|
mxsfb->base + mxsfb->devdata->transfer_count);
|
|
|
|
vsync_pulse_len = m->crtc_vsync_end - m->crtc_vsync_start;
|
|
|
|
vdctrl0 = VDCTRL0_ENABLE_PRESENT | /* Always in DOTCLOCK mode */
|
|
VDCTRL0_VSYNC_PERIOD_UNIT |
|
|
VDCTRL0_VSYNC_PULSE_WIDTH_UNIT |
|
|
VDCTRL0_SET_VSYNC_PULSE_WIDTH(vsync_pulse_len);
|
|
if (m->flags & DRM_MODE_FLAG_PHSYNC)
|
|
vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH;
|
|
if (m->flags & DRM_MODE_FLAG_PVSYNC)
|
|
vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH;
|
|
/* Make sure Data Enable is high active by default */
|
|
if (!(bus_flags & DRM_BUS_FLAG_DE_LOW))
|
|
vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH;
|
|
/*
|
|
* DRM_BUS_FLAG_PIXDATA_ defines are controller centric,
|
|
* controllers VDCTRL0_DOTCLK is display centric.
|
|
* Drive on positive edge -> display samples on falling edge
|
|
* DRM_BUS_FLAG_PIXDATA_POSEDGE -> VDCTRL0_DOTCLK_ACT_FALLING
|
|
*/
|
|
if (bus_flags & DRM_BUS_FLAG_PIXDATA_POSEDGE)
|
|
vdctrl0 |= VDCTRL0_DOTCLK_ACT_FALLING;
|
|
|
|
writel(vdctrl0, mxsfb->base + LCDC_VDCTRL0);
|
|
|
|
mxsfb_set_bus_fmt(mxsfb);
|
|
|
|
/* Frame length in lines. */
|
|
writel(m->crtc_vtotal, mxsfb->base + LCDC_VDCTRL1);
|
|
|
|
/* Line length in units of clocks or pixels. */
|
|
hsync_pulse_len = m->crtc_hsync_end - m->crtc_hsync_start;
|
|
writel(set_hsync_pulse_width(mxsfb, hsync_pulse_len) |
|
|
VDCTRL2_SET_HSYNC_PERIOD(m->crtc_htotal),
|
|
mxsfb->base + LCDC_VDCTRL2);
|
|
|
|
writel(SET_HOR_WAIT_CNT(m->crtc_htotal - m->crtc_hsync_start) |
|
|
SET_VERT_WAIT_CNT(m->crtc_vtotal - m->crtc_vsync_start),
|
|
mxsfb->base + LCDC_VDCTRL3);
|
|
|
|
writel(SET_DOTCLK_H_VALID_DATA_CNT(m->hdisplay),
|
|
mxsfb->base + LCDC_VDCTRL4);
|
|
|
|
mxsfb_disable_axi_clk(mxsfb);
|
|
}
|
|
|
|
void mxsfb_crtc_enable(struct mxsfb_drm_private *mxsfb)
|
|
{
|
|
mxsfb_crtc_mode_set_nofb(mxsfb);
|
|
mxsfb_enable_controller(mxsfb);
|
|
}
|
|
|
|
void mxsfb_crtc_disable(struct mxsfb_drm_private *mxsfb)
|
|
{
|
|
mxsfb_disable_controller(mxsfb);
|
|
}
|
|
|
|
void mxsfb_plane_atomic_update(struct mxsfb_drm_private *mxsfb,
|
|
struct drm_plane_state *state)
|
|
{
|
|
struct drm_simple_display_pipe *pipe = &mxsfb->pipe;
|
|
struct drm_crtc *crtc = &pipe->crtc;
|
|
struct drm_framebuffer *fb = pipe->plane.state->fb;
|
|
struct drm_pending_vblank_event *event;
|
|
struct drm_gem_cma_object *gem;
|
|
|
|
if (!crtc)
|
|
return;
|
|
|
|
spin_lock_irq(&crtc->dev->event_lock);
|
|
event = crtc->state->event;
|
|
if (event) {
|
|
crtc->state->event = NULL;
|
|
|
|
if (drm_crtc_vblank_get(crtc) == 0) {
|
|
drm_crtc_arm_vblank_event(crtc, event);
|
|
} else {
|
|
drm_crtc_send_vblank_event(crtc, event);
|
|
}
|
|
}
|
|
spin_unlock_irq(&crtc->dev->event_lock);
|
|
|
|
if (!fb)
|
|
return;
|
|
|
|
gem = drm_fb_cma_get_gem_obj(fb, 0);
|
|
|
|
mxsfb_enable_axi_clk(mxsfb);
|
|
writel(gem->paddr, mxsfb->base + mxsfb->devdata->next_buf);
|
|
mxsfb_disable_axi_clk(mxsfb);
|
|
}
|