diff --git a/drivers/gpu/drm/rp1/rp1-dpi/Makefile b/drivers/gpu/drm/rp1/rp1-dpi/Makefile index 79fdc790316791..30d499c2959eb3 100644 --- a/drivers/gpu/drm/rp1/rp1-dpi/Makefile +++ b/drivers/gpu/drm/rp1/rp1-dpi/Makefile @@ -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 diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c index c97114a8916568..0683f23f52ba17 100644 --- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c @@ -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) { @@ -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; @@ -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]); @@ -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); @@ -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, @@ -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); @@ -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); diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h index 1d32216bcca6ab..9b8ba03c942523 100644 --- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h @@ -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; }; /* ---------------------------------------------------------------------- */ @@ -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); diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c index e4c2592d8498e4..743e806a0bc7c3 100644 --- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c @@ -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; @@ -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) | + 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)); + + 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)); + + 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)); + + 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); @@ -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); @@ -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) @@ -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)); + 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; } diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c new file mode 100644 index 00000000000000..595434c28e3591 --- /dev/null +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PIO code for Raspberry Pi RP1 DPI driver + * + * Copyright (c) 2024 Raspberry Pi Limited. + */ + +/* + * Use PIO to fix up VSYNC for interlaced modes. + * + * For this to work we *require* DPI's pinctrl to enable DE on GPIO1. + * PIO can then snoop on HSYNC and DE pins to generate corrected VSYNC. + * + * Note that corrected VSYNC outputs will not be synchronous to DPICLK, + * will lag HSYNC by about 30ns and may suffer up to 5ns of jitter. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rp1_dpi.h" + +/* + * Start a PIO SM to generate an interrupt just after HSYNC onset, then another + * after a fixed delay (during which we assume HSYNC will have been deasserted). + */ + +static int rp1dpi_pio_start_timer_both(struct rp1_dpi *dpi, u32 flags, u32 tc) +{ + static const u16 instructions[2][5] = { + { 0xa022, 0x2083, 0xc001, 0x0043, 0xc001 }, /* posedge */ + { 0xa022, 0x2003, 0xc001, 0x0043, 0xc001 }, /* negedge */ + }; + const struct pio_program prog = { + .instructions = instructions[(flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0], + .length = ARRAY_SIZE(instructions[0]), + .origin = -1 + }; + int offset, sm; + + sm = pio_claim_unused_sm(dpi->pio, true); + if (sm < 0) + return -EBUSY; + + offset = pio_add_program(dpi->pio, &prog); + if (offset == PIO_ORIGIN_ANY) + return -EBUSY; + + pio_sm_config cfg = pio_get_default_sm_config(); + + pio_sm_set_enabled(dpi->pio, sm, false); + sm_config_set_wrap(&cfg, offset, offset + 4); + pio_sm_init(dpi->pio, sm, offset, &cfg); + + pio_sm_put(dpi->pio, sm, tc - 4); + pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false)); + pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32)); + pio_sm_set_enabled(dpi->pio, sm, true); + + return 0; +} + +/* + * Snoop on DE, HSYNC to count half-lines in the vertical blanking interval + * to determine when the VSYNC pulse should start and finish. Then, at a + * suitable moment (which should be an odd number of half-lines since the + * last active line), sample DE again to detect field phase. + * + * This version assumes VFP length is within 2..129 half-lines for any field + * (one half-line delay is needed to sample DE; we always wait for the next + * half-line boundary to improve VSync start accuracy). + */ + +static int rp1dpi_pio_vsync_ilace(struct rp1_dpi *dpi, + struct drm_display_mode const *mode) +{ + static const int wrap_target = 14; + static const int wrap = 26; + u16 instructions[] = { /* This is mutable */ + 0xa0e6, // 0: mov osr, isr side 0 ; top: rewind parameters + 0x2081, // 1: wait 1 gpio, 1 side 0 ; main: while (!DE) wait; + 0x2783, // 2: wait 1 gpio, 3 side 0 [7] ; do { @HSync + 0xc041, // 3: irq clear 1 side 0 ; flush stale IRQs + 0x20c1, // 4: wait 1 irq, 1 side 0 ; @midline + 0x00c1, // 5: jmp pin, 1 side 0 ; } while (DE) + 0x0007, // 6: jmp 7 side 0 ; + 0x6027, // 7: out x, 7 side 0 ; x = VFPlen - 2 + 0x000a, // 8: jmp 10 side 0 ; while (x--) { + 0x20c1, // 9: wait 1 irq, 1 side 0 ; @halfline + 0x0049, // 10: jmp x--, 9 side 0 ; } + 0x6021, // 11: out x, 1 side 0 ; test for aligned case + 0x003a, // 12: jmp !x, 26 side 0 ; if (!x) goto precise; + 0x20c1, // 13: wait 1 irq, 1 side 0 ; @halfline + // .wrap_target ; vsjoin: + 0xb722, // 14: mov x, y side 1 [7] ; VSYNC=1; x = VSyncLen + 0xd041, // 15: irq clear 1 side 1 ; VSYNC=1; flush stale IRQs + 0x30c1, // 16: wait 1 irq, 1 side 1 ; VSYNC=1; do { @halfline + 0x1050, // 17: jmp x--, 16 side 1 ; VSYNC=1; } while (x--) + 0x6028, // 18: out x, 8 side 0 ; VSYNC=0; x = VBPLen + 0x0015, // 19: jmp 21 side 0 ; while (x--) { + 0x20c1, // 20: wait 1 irq, 1 side 0 ; @halfline + 0x0054, // 21: jmp x--, 20 side 0 ; } + 0x00c0, // 22: jmp pin, 0 side 0 ; if (DE) reset phase + 0x0018, // 23: jmp 24 side 0 ; + 0x00e1, // 24: jmp !osre, 1 side 0 ; if (!phase) goto main + 0x0000, // 25: jmp 0 side 0 ; goto top + 0x2083, // 26: wait 1 gpio, 3 side 0 ; precise: @HSync + // .wrap ; goto vsjoin + }; + struct pio_program prog = { + .instructions = instructions, + .length = ARRAY_SIZE(instructions), + .origin = -1 + }; + pio_sm_config cfg = pio_get_default_sm_config(); + unsigned int i, offset; + u32 tc, vfp, vbp; + u32 sysclk = clock_get_hz(clk_sys); + int sm = pio_claim_unused_sm(dpi->pio, true); + + if (sm < 0) + return -EBUSY; + + /* Compute mid-line time constant and start the timer SM */ + tc = (mode->htotal * (u64)sysclk) / (u64)(2000u * mode->clock); + if (rp1dpi_pio_start_timer_both(dpi, mode->flags, tc) < 0) { + pio_sm_unclaim(dpi->pio, sm); + return -EBUSY; + } + + /* Adapt program code according to DE and Sync polarity; configure program */ + pio_sm_set_enabled(dpi->pio, sm, false); + if (dpi->de_inv) { + instructions[1] ^= 0x0080; + instructions[5] = 0x00c7; + instructions[6] = 0x0001; + instructions[22] = 0x00d8; + instructions[23] = 0x0000; + } + for (i = 0; i < ARRAY_SIZE(instructions); i++) { + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + instructions[i] ^= 0x1000; + if ((mode->flags & DRM_MODE_FLAG_NHSYNC) && (instructions[i] & 0xe07f) == 0x2003) + instructions[i] ^= 0x0080; + } + offset = pio_add_program(dpi->pio, &prog); + if (offset == PIO_ORIGIN_ANY) + return -EBUSY; + + /* Configure pins and SM */ + dpi->pio_stole_gpio2 = true; + sm_config_set_wrap(&cfg, offset + wrap_target, offset + wrap); + sm_config_set_sideset(&cfg, 1, false, false); + sm_config_set_sideset_pins(&cfg, 2); + pio_gpio_init(dpi->pio, 2); + sm_config_set_jmp_pin(&cfg, 1); /* "DE" is always GPIO1 */ + pio_sm_init(dpi->pio, sm, offset, &cfg); + pio_sm_set_consecutive_pindirs(dpi->pio, sm, 2, 1, true); + + /* Compute vertical times, remembering how we rounded vdisplay, vtotal */ + vfp = mode->vsync_start - (mode->vdisplay & ~1); + vbp = (mode->vtotal | 1) - mode->vsync_end; + if (vfp > 128) { + vbp += vfp - 128; + vfp = 128; + } else if (vfp < 3) { + vbp = (vbp > 3 - vfp) ? (vbp - 3 + vfp) : 0; + vfp = 3; + } + + pio_sm_put(dpi->pio, sm, + (vfp - 2) + ((vfp & 1) << 7) + (vbp << 8) + + ((vfp - 3) << 16) + (((~vfp) & 1) << 23) + ((vbp + 1) << 24)); + pio_sm_put(dpi->pio, sm, mode->vsync_end - mode->vsync_start - 1); + pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false)); + pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32)); + pio_sm_exec(dpi->pio, sm, pio_encode_in(pio_y, 32)); + pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false)); + pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32)); + pio_sm_set_enabled(dpi->pio, sm, true); + + return 0; +} + +int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode) +{ + int r; + + if (!(mode->flags & DRM_MODE_FLAG_INTERLACE) || !dpi->gpio1_used) + return 0; + + if (dpi->pio) + pio_close(dpi->pio); + + dpi->pio = pio_open(); + if (IS_ERR(dpi->pio)) { + drm_err(&dpi->drm, "Could not open PIO\n"); + dpi->pio = NULL; + return -ENODEV; + } + + r = rp1dpi_pio_vsync_ilace(dpi, mode); + if (r) { + drm_err(&dpi->drm, "Failed to initialize PIO\n"); + rp1dpi_pio_stop(dpi); + } + + return r; +} + +void rp1dpi_pio_stop(struct rp1_dpi *dpi) +{ + if (dpi->pio) { + if (dpi->pio_stole_gpio2) + pio_gpio_set_function(dpi->pio, 2, GPIO_FUNC_FSEL1); + pio_close(dpi->pio); + dpi->pio_stole_gpio2 = false; + dpi->pio = NULL; + } +}