Skip to content

Commit

Permalink
drm: rp1: rp1-dpi: Add interlaced modes and PIO program to fix VSYNC
Browse files Browse the repository at this point in the history
Implement interlaced modes by wobbling the base pointer and VFP width
for every field. This results in correct pixels but incorrect VSYNC.

Now use PIO to generate a fixed-up VSYNC by sampling DE and HSYNC.
This requires DPI's DE output to be mapped to GPIO1, which we check.

When DE is not exposed, the internal fixup is disabled. VSYNC/GPIO2
becomes a modified signal, designed to help an external device or
PIO program synthesize CSYNC or VSYNC.

Signed-off-by: Nick Hollinghurst <[email protected]>
  • Loading branch information
njhollinghurst committed Dec 10, 2024
1 parent 53a924f commit 5b1b66a
Show file tree
Hide file tree
Showing 5 changed files with 424 additions and 48 deletions.
2 changes: 1 addition & 1 deletion drivers/gpu/drm/rp1/rp1-dpi/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only

drm-rp1-dpi-y := rp1_dpi.o rp1_dpi_hw.o rp1_dpi_cfg.o
drm-rp1-dpi-y := rp1_dpi.o rp1_dpi_hw.o rp1_dpi_cfg.o rp1_dpi_pio.o

obj-$(CONFIG_DRM_RP1_DPI) += drm-rp1-dpi.o
34 changes: 33 additions & 1 deletion drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ static void rp1dpi_pipe_update(struct drm_simple_display_pipe *pipe,
if (dpi->dpi_running &&
fb->format->format != dpi->cur_fmt) {
rp1dpi_hw_stop(dpi);
rp1dpi_pio_stop(dpi);
dpi->dpi_running = false;
}
if (!dpi->dpi_running) {
Expand All @@ -88,6 +89,7 @@ static void rp1dpi_pipe_update(struct drm_simple_display_pipe *pipe,
dpi->bus_fmt,
dpi->de_inv,
&pipe->crtc.state->mode);
rp1dpi_pio_start(dpi, &pipe->crtc.state->mode);
dpi->dpi_running = true;
}
dpi->cur_fmt = fb->format->format;
Expand Down Expand Up @@ -187,6 +189,7 @@ static void rp1dpi_pipe_disable(struct drm_simple_display_pipe *pipe)
drm_crtc_vblank_off(&pipe->crtc);
if (dpi->dpi_running) {
rp1dpi_hw_stop(dpi);
rp1dpi_pio_stop(dpi);
dpi->dpi_running = false;
}
clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_DPI]);
Expand Down Expand Up @@ -236,6 +239,7 @@ static void rp1dpi_stopall(struct drm_device *drm)
if (dpi->dpi_running || rp1dpi_hw_busy(dpi)) {
rp1dpi_hw_stop(dpi);
clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_DPI]);
rp1dpi_pio_stop(dpi);
dpi->dpi_running = false;
}
rp1dpi_vidout_poweroff(dpi);
Expand Down Expand Up @@ -273,7 +277,7 @@ static int rp1dpi_platform_probe(struct platform_device *pdev)
struct rp1_dpi *dpi;
struct drm_bridge *bridge = NULL;
struct drm_panel *panel;
int i, ret;
int i, j, ret;

dev_info(dev, __func__);
ret = drm_of_find_panel_or_bridge(pdev->dev.of_node, 0, 0,
Expand All @@ -295,6 +299,7 @@ static int rp1dpi_platform_probe(struct platform_device *pdev)
return ret;
}
dpi->pdev = pdev;
spin_lock_init(&dpi->hw_lock);

dpi->bus_fmt = default_bus_fmt;
ret = of_property_read_u32(dev->of_node, "default_bus_fmt", &dpi->bus_fmt);
Expand Down Expand Up @@ -332,6 +337,33 @@ static int rp1dpi_platform_probe(struct platform_device *pdev)
if (ret)
goto done_err;

/* Check if PIO can snoop on or override DPI's GPIO1 */
dpi->gpio1_used = false;
for (i = 0; !dpi->gpio1_used; i++) {
u32 p = 0;
const char *str = NULL;
struct device_node *np1 = of_parse_phandle(dev->of_node, "pinctrl-0", i);

if (!np1)
break;

if (!of_property_read_string(np1, "function", &str) && !strcmp(str, "dpi")) {
for (j = 0; !dpi->gpio1_used; j++) {
if (of_property_read_string_index(np1, "pins", j, &str))
break;
if (!strcmp(str, "gpio1"))
dpi->gpio1_used = true;
}
for (j = 0; !dpi->gpio1_used; j++) {
if (of_property_read_u32_index(np1, "brcm,pins", j, &p))
break;
if (p == 1)
dpi->gpio1_used = true;
}
}
of_node_put(np1);
}

/* Now we have all our resources, finish driver initialization */
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
init_completion(&dpi->finished);
Expand Down
18 changes: 18 additions & 0 deletions drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ struct rp1_dpi {
bool de_inv, clk_inv;
bool dpi_running, pipe_enabled;
struct completion finished;

/* Experimental stuff for interlace follows */
struct rp1_pio_client *pio;
bool gpio1_used;
bool pio_stole_gpio2;

spinlock_t hw_lock; /* the following are used in line-match ISR */
dma_addr_t last_dma_addr;
u32 last_stride;
u32 shorter_front_porch;
bool interlaced;
bool lower_field_flag;
};

/* ---------------------------------------------------------------------- */
Expand All @@ -67,3 +79,9 @@ void rp1dpi_hw_vblank_ctrl(struct rp1_dpi *dpi, int enable);

void rp1dpi_vidout_setup(struct rp1_dpi *dpi, bool drive_negedge);
void rp1dpi_vidout_poweroff(struct rp1_dpi *dpi);

/* ---------------------------------------------------------------------- */
/* PIO control -- we need PIO to generate VSync (from DE) when interlaced */

int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode);
void rp1dpi_pio_stop(struct rp1_dpi *dpi);
193 changes: 147 additions & 46 deletions drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c
Original file line number Diff line number Diff line change
Expand Up @@ -354,42 +354,26 @@ void rp1dpi_hw_setup(struct rp1_dpi *dpi,
u32 in_format, u32 bus_format, bool de_inv,
struct drm_display_mode const *mode)
{
u32 shift, imask, omask, rgbsz;
u32 shift, imask, omask, rgbsz, vctrl;
int i;

pr_info("%s: in_fmt=\'%c%c%c%c\' bus_fmt=0x%x mode=%dx%d total=%dx%d %dkHz %cH%cV%cD%cC",
__func__, in_format, in_format >> 8, in_format >> 16, in_format >> 24, bus_format,
mode->hdisplay, mode->vdisplay,
mode->htotal, mode->vtotal,
mode->clock,
(mode->flags & DRM_MODE_FLAG_NHSYNC) ? '-' : '+',
(mode->flags & DRM_MODE_FLAG_NVSYNC) ? '-' : '+',
de_inv ? '-' : '+',
dpi->clk_inv ? '-' : '+');
drm_info(&dpi->drm,
"in_fmt=\'%c%c%c%c\' bus_fmt=0x%x mode=%dx%d total=%dx%d%s %dkHz %cH%cV%cD%cC",
in_format, in_format >> 8, in_format >> 16, in_format >> 24, bus_format,
mode->hdisplay, mode->vdisplay,
mode->htotal, mode->vtotal,
(mode->flags & DRM_MODE_FLAG_INTERLACE) ? "i" : "",
mode->clock,
(mode->flags & DRM_MODE_FLAG_NHSYNC) ? '-' : '+',
(mode->flags & DRM_MODE_FLAG_NVSYNC) ? '-' : '+',
de_inv ? '-' : '+',
dpi->clk_inv ? '-' : '+');

/*
* Configure all DPI/DMA block registers, except base address.
* DMA will not actually start until a FB base address is specified
* using rp1dpi_hw_update().
*/
rp1dpi_hw_write(dpi, DPI_DMA_VISIBLE_AREA,
BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, mode->vdisplay - 1) |
BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1));

rp1dpi_hw_write(dpi, DPI_DMA_SYNC_WIDTH,
BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, mode->vsync_end - mode->vsync_start - 1) |
BITS(DPI_DMA_SYNC_WIDTH_COLSM1, mode->hsync_end - mode->hsync_start - 1));

/* In these registers, "back porch" time includes sync width */
rp1dpi_hw_write(dpi, DPI_DMA_BACK_PORCH,
BITS(DPI_DMA_BACK_PORCH_ROWSM1, mode->vtotal - mode->vsync_start - 1) |
BITS(DPI_DMA_BACK_PORCH_COLSM1, mode->htotal - mode->hsync_start - 1));

rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH,
BITS(DPI_DMA_FRONT_PORCH_ROWSM1, mode->vsync_start - mode->vdisplay - 1) |
BITS(DPI_DMA_FRONT_PORCH_COLSM1, mode->hsync_start - mode->hdisplay - 1));

/* Input to output pixel format conversion */
for (i = 0; i < ARRAY_SIZE(my_formats); ++i) {
if (my_formats[i].format == in_format)
break;
Expand Down Expand Up @@ -417,6 +401,80 @@ void rp1dpi_hw_setup(struct rp1_dpi *dpi,
BITS(DPI_DMA_QOS_LLEV, 0x8) |
BITS(DPI_DMA_QOS_LQOS, 0x7));

if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) {
rp1dpi_hw_write(dpi, DPI_DMA_VISIBLE_AREA,
BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, mode->vdisplay - 1) |
BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1));

rp1dpi_hw_write(dpi, DPI_DMA_SYNC_WIDTH,
BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, mode->vsync_end - mode->vsync_start - 1) |

Check failure on line 410 in drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: line length of 106 exceeds 100 columns
BITS(DPI_DMA_SYNC_WIDTH_COLSM1, mode->hsync_end - mode->hsync_start - 1));

Check failure on line 411 in drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: line length of 106 exceeds 100 columns

/* In these registers, "back porch" time includes sync width */
rp1dpi_hw_write(dpi, DPI_DMA_BACK_PORCH,
BITS(DPI_DMA_BACK_PORCH_ROWSM1, mode->vtotal - mode->vsync_start - 1) |

Check failure on line 415 in drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: line length of 103 exceeds 100 columns
BITS(DPI_DMA_BACK_PORCH_COLSM1, mode->htotal - mode->hsync_start - 1));

Check failure on line 416 in drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: line length of 103 exceeds 100 columns

rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH,
BITS(DPI_DMA_FRONT_PORCH_ROWSM1, mode->vsync_start - mode->vdisplay - 1) |

Check failure on line 419 in drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: line length of 106 exceeds 100 columns
BITS(DPI_DMA_FRONT_PORCH_COLSM1, mode->hsync_start - mode->hdisplay - 1));

Check failure on line 420 in drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: line length of 106 exceeds 100 columns

vctrl = BITS(DPI_DMA_CONTROL_VSYNC_POL, !!(mode->flags & DRM_MODE_FLAG_NVSYNC)) |
BITS(DPI_DMA_CONTROL_VBP_EN, (mode->vtotal != mode->vsync_start)) |
BITS(DPI_DMA_CONTROL_VFP_EN, (mode->vsync_start != mode->vdisplay)) |
BITS(DPI_DMA_CONTROL_VSYNC_EN, (mode->vsync_end != mode->vsync_start));

dpi->interlaced = false;
} else {
/*
* Experimental interlace support
*
* RP1 DPI hardware wasn't designed to support interlace, but lets us change
* both the VFP line count and the next DMA address while running. That allows
* pixel data to be correctly timed for interlace, but VSYNC remains wrong.
*
* It is necessary to use external hardware (such as PIO) to regenerate VSYNC
* based on HSYNC, DE (which *must* both be mapped to GPIOs 1, 3 respectively).
* This driver includes a PIO program to do that, when DE is enabled.
*
* An alternative fixup is to synthesize CSYNC from HSYNC and modified-VSYNC.
* We don't implement that here, but to facilitate it, DPI's VSYNC is replaced
* by a "helper signal" that pulses low for 1 or 2 scan-lines, starting 2.5 or
* 3.0 scan-lines before the vertical datum "0v".
*/
int vact = mode->vdisplay >> 1; /* visible lines per field. Can't do half-lines */
int vtot0 = mode->vtotal >> 1; /* vtotal should always be odd when interlaced. */
int vfp0 = (mode->vsync_start >= mode->vdisplay + 7) ?
((mode->vsync_start - mode->vdisplay - 5) >> 1) : 1;
int vbp = max(0, vtot0 - vact - vfp0);

rp1dpi_hw_write(dpi, DPI_DMA_VISIBLE_AREA,
BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, vact - 1) |
BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1));

rp1dpi_hw_write(dpi, DPI_DMA_SYNC_WIDTH,
BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, vtot0 - 2) |
BITS(DPI_DMA_SYNC_WIDTH_COLSM1, mode->hsync_end - mode->hsync_start - 1));

Check failure on line 457 in drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: line length of 106 exceeds 100 columns

rp1dpi_hw_write(dpi, DPI_DMA_BACK_PORCH,
BITS(DPI_DMA_BACK_PORCH_ROWSM1, vbp - 1) |
BITS(DPI_DMA_BACK_PORCH_COLSM1, mode->htotal - mode->hsync_start - 1));

Check failure on line 461 in drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: line length of 103 exceeds 100 columns

dpi->shorter_front_porch =
BITS(DPI_DMA_FRONT_PORCH_ROWSM1, vfp0 - 1) |
BITS(DPI_DMA_FRONT_PORCH_COLSM1, mode->hsync_start - mode->hdisplay - 1);
rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH, dpi->shorter_front_porch);

vctrl = BITS(DPI_DMA_CONTROL_VSYNC_POL, 0) |
BITS(DPI_DMA_CONTROL_VBP_EN, (vbp > 0)) |
BITS(DPI_DMA_CONTROL_VFP_EN, 1) |
BITS(DPI_DMA_CONTROL_VSYNC_EN, 1);

dpi->interlaced = true;
}
dpi->lower_field_flag = false;
dpi->last_dma_addr = 0;

rp1dpi_hw_write(dpi, DPI_DMA_IRQ_FLAGS, -1);
rp1dpi_hw_vblank_ctrl(dpi, 1);

Expand All @@ -425,49 +483,64 @@ void rp1dpi_hw_setup(struct rp1_dpi *dpi,
pr_warn("%s: Unexpectedly busy at start!", __func__);

rp1dpi_hw_write(dpi, DPI_DMA_CONTROL,
vctrl |
BITS(DPI_DMA_CONTROL_ARM, !i) |
BITS(DPI_DMA_CONTROL_AUTO_REPEAT, 1) |
BITS(DPI_DMA_CONTROL_HIGH_WATER, 448) |
BITS(DPI_DMA_CONTROL_DEN_POL, de_inv) |
BITS(DPI_DMA_CONTROL_HSYNC_POL, !!(mode->flags & DRM_MODE_FLAG_NHSYNC)) |
BITS(DPI_DMA_CONTROL_VSYNC_POL, !!(mode->flags & DRM_MODE_FLAG_NVSYNC)) |
BITS(DPI_DMA_CONTROL_COLORM, 0) |
BITS(DPI_DMA_CONTROL_SHUTDN, 0) |
BITS(DPI_DMA_CONTROL_HBP_EN, (mode->htotal != mode->hsync_end)) |
BITS(DPI_DMA_CONTROL_HFP_EN, (mode->hsync_start != mode->hdisplay)) |
BITS(DPI_DMA_CONTROL_VBP_EN, (mode->vtotal != mode->vsync_end)) |
BITS(DPI_DMA_CONTROL_VFP_EN, (mode->vsync_start != mode->vdisplay)) |
BITS(DPI_DMA_CONTROL_HSYNC_EN, (mode->hsync_end != mode->hsync_start)) |
BITS(DPI_DMA_CONTROL_VSYNC_EN, (mode->vsync_end != mode->vsync_start)));
BITS(DPI_DMA_CONTROL_HSYNC_EN, (mode->hsync_end != mode->hsync_start)));
}

void rp1dpi_hw_update(struct rp1_dpi *dpi, dma_addr_t addr, u32 offset, u32 stride)
{
u64 a = addr + offset;
unsigned long flags;

spin_lock_irqsave(&dpi->hw_lock, flags);

/*
* Update STRIDE, DMAH and DMAL only. When called after rp1dpi_hw_setup(),
* DMA starts immediately; if already running, the buffer will flip at
* the next vertical sync event.
* the next vertical sync event. In interlaced mode, we need to adjust
* the address and stride to display only the current field, saving
* the original address (so it can be flipped for subsequent fields).
*/
addr += offset;
dpi->last_dma_addr = addr;
dpi->last_stride = stride;
if (dpi->interlaced) {
if (dpi->lower_field_flag)
addr += stride;
stride *= 2;
}
rp1dpi_hw_write(dpi, DPI_DMA_DMA_STRIDE, stride);
rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_H, a >> 32);
rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_L, a & 0xFFFFFFFFu);
rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_H, addr >> 32);
rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_L, addr & 0xFFFFFFFFu);

spin_unlock_irqrestore(&dpi->hw_lock, flags);
}

void rp1dpi_hw_stop(struct rp1_dpi *dpi)
{
u32 ctrl;
unsigned long flags;

/*
* Stop DMA by turning off the Auto-Repeat flag, and wait up to 100ms for
* the current and any queued frame to end. "Force drain" flags are not used,
* as they seem to prevent DMA from re-starting properly; it's safer to wait.
* Stop DMA by turning off Auto-Repeat (and disable S/W field-flip),
* then wait up to 100ms for the current and any queued frame to end.
* (There is a "force drain" flag, but it can leave DPI in a broken
* state which prevents it from restarting; it's safer to wait.)
*/
spin_lock_irqsave(&dpi->hw_lock, flags);
dpi->last_dma_addr = 0;
reinit_completion(&dpi->finished);
ctrl = rp1dpi_hw_read(dpi, DPI_DMA_CONTROL);
ctrl &= ~(DPI_DMA_CONTROL_ARM_MASK | DPI_DMA_CONTROL_AUTO_REPEAT_MASK);
rp1dpi_hw_write(dpi, DPI_DMA_CONTROL, ctrl);
spin_unlock_irqrestore(&dpi->hw_lock, flags);

if (!wait_for_completion_timeout(&dpi->finished, HZ / 10))
drm_err(&dpi->drm, "%s: timed out waiting for idle\n", __func__);
rp1dpi_hw_write(dpi, DPI_DMA_IRQ_EN, 0);
Expand All @@ -476,10 +549,11 @@ void rp1dpi_hw_stop(struct rp1_dpi *dpi)
void rp1dpi_hw_vblank_ctrl(struct rp1_dpi *dpi, int enable)
{
rp1dpi_hw_write(dpi, DPI_DMA_IRQ_EN,
BITS(DPI_DMA_IRQ_EN_AFIFO_EMPTY, 1) |
BITS(DPI_DMA_IRQ_EN_UNDERFLOW, 1) |
BITS(DPI_DMA_IRQ_EN_DMA_READY, !!enable) |
BITS(DPI_DMA_IRQ_EN_MATCH_LINE, 4095));
BITS(DPI_DMA_IRQ_EN_AFIFO_EMPTY, 1) |
BITS(DPI_DMA_IRQ_EN_UNDERFLOW, 1) |
BITS(DPI_DMA_IRQ_EN_DMA_READY, !!enable) |
BITS(DPI_DMA_IRQ_EN_MATCH, dpi->interlaced) |
BITS(DPI_DMA_IRQ_EN_MATCH_LINE, 32));
}

irqreturn_t rp1dpi_hw_isr(int irq, void *dev)
Expand All @@ -498,7 +572,34 @@ irqreturn_t rp1dpi_hw_isr(int irq, void *dev)
drm_crtc_handle_vblank(&dpi->pipe.crtc);
if (u & DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK)
complete(&dpi->finished);

/*
* Added for interlace support: We use this mid-frame interrupt to
* wobble the VFP between fields, re-submitting the next-buffer address
* with an offset to display the opposite field. NB: rp1dpi_hw_update()
* may be called at any time, before or after, so locking is needed.
* H/W Auto-update is no longer needed (unless this IRQ is lost).
*/
if ((u & DPI_DMA_IRQ_FLAGS_MATCH_MASK) && dpi->interlaced) {
unsigned long flags;
dma_addr_t a;

spin_lock_irqsave(&dpi->hw_lock, flags);
dpi->lower_field_flag = !dpi->lower_field_flag;
rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH,
dpi->shorter_front_porch +
BITS(DPI_DMA_FRONT_PORCH_ROWSM1, dpi->lower_field_flag));

Check failure on line 591 in drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: line length of 105 exceeds 100 columns
a = dpi->last_dma_addr;
if (a) {
if (dpi->lower_field_flag)
a += dpi->last_stride;
rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_H, a >> 32);
rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_L, a & 0xFFFFFFFFu);
}
spin_unlock_irqrestore(&dpi->hw_lock, flags);
}
}
}

return u ? IRQ_HANDLED : IRQ_NONE;
}
Loading

0 comments on commit 5b1b66a

Please sign in to comment.