From c9822bef49565795ecb7c378212cc32d10c05dfe Mon Sep 17 00:00:00 2001 From: Nick Hollinghurst Date: Tue, 29 Apr 2025 12:15:13 +0100 Subject: [PATCH] drm/rp1/rp1_dsi: Move Composite Sync generation into the kernel Move RP1 DPI's PIO-assisted Composite Sync generation code, previously released as a separate utility, into the kernel driver. There are 3 variants for progressive, generic interlaced and TV- style interlaced CSync, alongside the existing VSync fixup. Check that all of GPIOs 1-3 are mapped to DPI, so PIO won't try to snoop on a missing output, or override another device's pins. Add "force_csync" module parameter, for convenience of testing, as few tools can set DRM_MODE_FLAG_CSYNC. Signed-off-by: Nick Hollinghurst --- drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c | 36 +- drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h | 8 +- drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c | 4 +- drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c | 390 +++++++++++++++++++++- 4 files changed, 406 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c index 26d667bc0fefeb..d4406fb8394a24 100644 --- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c @@ -61,6 +61,16 @@ static unsigned int default_bus_fmt = MEDIA_BUS_FMT_RGB666_1X18; module_param(default_bus_fmt, uint, 0644); +/* + * Override DRM mode flags to force the use of Composite Sync on GPIO1. + * This is mostly for testing, as neither panel-timing nor command-line + * arguments nor utilities such as "kmstest" can set DRM_MODE_FLAG_CSYNC. + * Sampled on each enable/mode-switch. Default polarity will be -ve. + * (Setting this may break Vertical Sync on GPIO2 for interlaced modes.) + */ +static bool force_csync; +module_param(force_csync, bool, 0644); + /* -------------------------------------------------------------- */ static void rp1dpi_pipe_update(struct drm_simple_display_pipe *pipe, @@ -89,7 +99,8 @@ 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); + rp1dpi_pio_start(dpi, &pipe->crtc.state->mode, + force_csync); dpi->dpi_running = true; } dpi->cur_fmt = fb->format->format; @@ -294,6 +305,7 @@ static int rp1dpi_platform_probe(struct platform_device *pdev) struct drm_bridge *bridge = NULL; const char *rgb_order = NULL; struct drm_panel *panel; + u32 missing_gpios; int i, j, ret; dev_info(dev, __func__); @@ -354,6 +366,7 @@ static int rp1dpi_platform_probe(struct platform_device *pdev) if (ret) goto done_err; + /* RGB order property - to match VC4 */ dpi->rgb_order_override = RP1DPI_ORDER_UNCHANGED; if (!of_property_read_string(dev->of_node, "rgb_order", &rgb_order)) { if (!strcmp(rgb_order, "rgb")) @@ -368,9 +381,9 @@ static int rp1dpi_platform_probe(struct platform_device *pdev) DRM_ERROR("Invalid dpi order %s - ignored\n", rgb_order); } - /* Check if PIO can snoop on or override DPI's GPIO1 */ - dpi->gpio1_used = false; - for (i = 0; !dpi->gpio1_used; i++) { + /* Check if all of GPIOs 1, 2 and 3 are assigned to DPI */ + missing_gpios = BIT(1) | BIT(2) | BIT(3); + for (i = 0; missing_gpios; i++) { u32 p = 0; const char *str = NULL; struct device_node *np1 = of_parse_phandle(dev->of_node, "pinctrl-0", i); @@ -379,21 +392,26 @@ static int rp1dpi_platform_probe(struct platform_device *pdev) break; if (!of_property_read_string(np1, "function", &str) && !strcmp(str, "dpi")) { - for (j = 0; !dpi->gpio1_used; j++) { + for (j = 0; missing_gpios; j++) { if (of_property_read_string_index(np1, "pins", j, &str)) break; if (!strcmp(str, "gpio1")) - dpi->gpio1_used = true; + missing_gpios &= ~BIT(1); + else if (!strcmp(str, "gpio2")) + missing_gpios &= ~BIT(2); + else if (!strcmp(str, "gpio3")) + missing_gpios &= ~BIT(3); } - for (j = 0; !dpi->gpio1_used; j++) { + for (j = 0; missing_gpios; j++) { if (of_property_read_u32_index(np1, "brcm,pins", j, &p)) break; - if (p == 1) - dpi->gpio1_used = true; + if (p < 32) + missing_gpios &= ~(1 << p); } } of_node_put(np1); } + dpi->sync_gpios_mapped = !missing_gpios; /* Now we have all our resources, finish driver initialization */ dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h index 848a043e1e247a..693042575a902c 100644 --- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h @@ -55,10 +55,9 @@ struct rp1_dpi { unsigned int rgb_order_override; struct completion finished; - /* Experimental stuff for interlace follows */ + /* The following are for Interlace and CSYNC support using PIO */ struct rp1_pio_client *pio; - bool gpio1_used; - bool pio_stole_gpio2; + bool sync_gpios_mapped; spinlock_t hw_lock; /* the following are used in line-match ISR */ dma_addr_t last_dma_addr; @@ -91,5 +90,6 @@ 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); +int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode, + bool force_csync); 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 b6b844afb2e3d1..99d55e866e223e 100644 --- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c @@ -392,7 +392,7 @@ void rp1dpi_hw_setup(struct rp1_dpi *dpi, int order, i; 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_fmt=\'%c%c%c%c\' bus_fmt=0x%x mode=%dx%d total=%dx%d%s %dkHz %cH%cV%cDE%cCK", in_format, in_format >> 8, in_format >> 16, in_format >> 24, bus_format, mode->hdisplay, mode->vdisplay, mode->htotal, mode->vtotal, @@ -497,7 +497,7 @@ void rp1dpi_hw_setup(struct rp1_dpi *dpi, * 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 + * We can't do this and make VSYNC at the same time; DPI's VSYNC is replaced * by a "helper signal" that pulses low for 1 or 2 scan-lines, starting 2.0 or * 2.5 scan-lines respectively before nominal VSYNC start. */ diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c index f636f4cbcc1f7c..bce42a065ab6b2 100644 --- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c +++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c @@ -6,13 +6,16 @@ */ /* - * Use PIO to fix up VSYNC for interlaced modes. + * RP1 DPI can't generate any composite sync, and in interlaced modes + * its native vertical sync output will be an incorrect/modified signal. * - * 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. + * So we need to use PIO *either* to generate CSYNC in both progressive + * and interlaced modes, *or* to fix up VSYNC for interlaced modes only. + * It can't do both: interlaced modes can have only one of CSYNC, VSYNC. + * All these cases require GPIOs 1, 2 and 3 to be declared as outputs. * - * 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. + * Note that PIO's VSYNC or CSYNC output is not synchronous to DPICLK + * and may suffer up to +/-5ns of jitter. */ #include @@ -131,7 +134,6 @@ static int rp1dpi_pio_vsync_ilace(struct rp1_dpi *dpi, 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) @@ -145,7 +147,7 @@ static int rp1dpi_pio_vsync_ilace(struct rp1_dpi *dpi, tc = (u32)clk_get_rate(dpi->clocks[RP1DPI_CLK_DPI]); if (!tc) tc = 1000u * mode->clock; - tc = ((u64)mode->htotal * (u64)sysclk + ((7ul * tc) >> 2)) / + tc = ((u64)mode->htotal * (u64)clock_get_hz(clk_sys) + ((7ul * tc) >> 2)) / (u64)(2ul * tc); if (rp1dpi_pio_start_timer_both(dpi, mode->flags, tc) < 0) { pio_sm_unclaim(dpi->pio, sm); @@ -172,12 +174,11 @@ static int rp1dpi_pio_vsync_ilace(struct rp1_dpi *dpi, return -EBUSY; /* Configure pins and SM */ - dpi->pio_stole_gpio2 = true; sm_config_set_wrap(&cfg, offset, offset + ARRAY_SIZE(instructions) - 1); sm_config_set_sideset(&cfg, 1, false, false); - sm_config_set_sideset_pins(&cfg, 2); + sm_config_set_sideset_pins(&cfg, 2); /* PIO produces VSync on GPIO2 */ pio_gpio_init(dpi->pio, 2); - sm_config_set_jmp_pin(&cfg, 1); /* "DE" is always GPIO1 */ + sm_config_set_jmp_pin(&cfg, 1); /* DE on GPIO1 */ pio_sm_init(dpi->pio, sm, offset, &cfg); pio_sm_set_consecutive_pindirs(dpi->pio, sm, 2, 1, true); @@ -206,12 +207,347 @@ static int rp1dpi_pio_vsync_ilace(struct rp1_dpi *dpi, return 0; } -int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode) +/* + * COMPOSITE SYNC FOR PROGRESSIVE + * + * Copy HSYNC pulses to CSYNC (adding 1 cycle); then when VSYNC + * is asserted, extend each pulse by an additional Y + 1 cycles. + * + * The following time constant should be written to the FIFO: + * (htotal - 2 * hsync_width) * sys_clock / dpi_clock - 2. + * + * The default configuration is +HSync, +VSync, -CSync; other + * polarities can be made by modifying the PIO program code. + */ + +static int rp1dpi_pio_csync_prog(struct rp1_dpi *dpi, + struct drm_display_mode const *mode) +{ + unsigned int i, tc, offset; + unsigned short instructions[] = { /* This is mutable */ + 0x90a0, // 0: pull block side 1 + 0x7040, // 1: out y, 32 side 1 + // .wrap_target + 0xb322, // 2: mov x, y side 1 [3] + 0x3083, // 3: wait 1 gpio, 3 side 1 + 0xa422, // 4: mov x, y side 0 [4] + 0x2003, // 5: wait 0 gpio, 3 side 0 + 0x00c7, // 6: jmp pin, 7 side 0 ; modify to flip VSync polarity + // .wrap ; modify to flip VSync polarity + 0x0047, // 7: jmp x--, 7 side 0 + 0x1002, // 8: jmp 2 side 1 + }; + struct pio_program prog = { + .instructions = instructions, + .length = ARRAY_SIZE(instructions), + .origin = -1 + }; + pio_sm_config cfg = pio_get_default_sm_config(); + int sm = pio_claim_unused_sm(dpi->pio, true); + + if (sm < 0) + return -EBUSY; + + /* Adapt program code for sync polarity; configure program */ + pio_sm_set_enabled(dpi->pio, sm, false); + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + instructions[6] = 0x00c2; /* jmp pin, 2 side 0 */ + if (mode->flags & DRM_MODE_FLAG_NHSYNC) { + instructions[3] ^= 0x80; + instructions[5] ^= 0x80; + } + if (mode->flags & DRM_MODE_FLAG_PCSYNC) { + for (i = 0; i < ARRAY_SIZE(instructions); i++) + instructions[i] ^= 0x1000; + } + offset = pio_add_program(dpi->pio, &prog); + if (offset == PIO_ORIGIN_ANY) + return -EBUSY; + + /* Configure pins and SM */ + sm_config_set_wrap(&cfg, offset + 2, + offset + (mode->flags & DRM_MODE_FLAG_NVSYNC) ? 7 : 6); + sm_config_set_sideset(&cfg, 1, false, false); + sm_config_set_sideset_pins(&cfg, 1); /* PIO produces CSync on GPIO 1 */ + pio_gpio_init(dpi->pio, 1); + sm_config_set_jmp_pin(&cfg, 2); /* VSync on GPIO 2 */ + pio_sm_init(dpi->pio, sm, offset, &cfg); + pio_sm_set_consecutive_pindirs(dpi->pio, sm, 1, 1, true); + + /* Place time constant into the FIFO; start the SM */ + tc = (u32)clk_get_rate(dpi->clocks[RP1DPI_CLK_DPI]); + if (!tc) + tc = 1000u * mode->clock; + tc = ((u64)(mode->htotal - 2 * (mode->hsync_end - mode->hsync_start)) * + (u64)clock_get_hz(clk_sys)) / (u64)tc; + pio_sm_put(dpi->pio, sm, tc - 2); + pio_sm_set_enabled(dpi->pio, sm, true); + + return 0; +} + +/* + * Claim all four SMs. Use SMs 1,2,3 to generate an interrupt: + * 1: At the end of the left "broad pulse" + * 2: In the middle of the scanline + * 3: At the end of the right "broad pulse" + */ +static int rp1dpi_pio_claim_all_start_timers(struct rp1_dpi *dpi, + struct drm_display_mode const *mode) +{ + static const u16 instructions[2][4] = { + { 0xa022, 0x2083, 0x0042, 0xc010 }, /* posedge */ + { 0xa022, 0x2003, 0x0042, 0xc010 }, /* negedge */ + }; + const struct pio_program prog = { + .instructions = instructions[(mode->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0], + .length = ARRAY_SIZE(instructions[0]), + .origin = -1 + }; + u32 tc[3], sysclk, dpiclk; + int offset, i; + + dpiclk = clk_get_rate(dpi->clocks[RP1DPI_CLK_DPI]); + if (!dpiclk) + dpiclk = 1000u * mode->clock; + sysclk = clock_get_hz(clk_sys); + tc[1] = ((u64)mode->htotal * (u64)sysclk) / (u64)(2ul * dpiclk); + tc[2] = ((u64)(mode->htotal + mode->hsync_start - mode->hsync_end) * (u64)sysclk) / + (u64)dpiclk; + tc[0] = tc[2] - tc[1]; + + i = pio_claim_sm_mask(dpi->pio, 0xF); + if (i != 0) + return -EBUSY; + + offset = pio_add_program(dpi->pio, &prog); + if (offset == PIO_ORIGIN_ANY) + return -EBUSY; + + for (i = 0; i < 3; i++) { + pio_sm_config cfg = pio_get_default_sm_config(); + + pio_sm_set_enabled(dpi->pio, i + 1, false); + sm_config_set_wrap(&cfg, offset, offset + 3); + pio_sm_init(dpi->pio, i + 1, offset, &cfg); + + pio_sm_put(dpi->pio, i + 1, tc[i] - 4); + pio_sm_exec(dpi->pio, i + 1, pio_encode_pull(false, false)); + pio_sm_exec(dpi->pio, i + 1, pio_encode_out(pio_y, 32)); + pio_sm_set_enabled(dpi->pio, i + 1, true); + } + + return 0; +} + +/* + * COMPOSITE SYNC FOR INTERLACED + * + * DPI VSYNC (GPIO2) must be a modified signal which is always active-low. + * It should go low for 1 or 2 scanlines, 2 or 2.5 lines before Vsync-start. + * Desired VSync width minus 1 (in half-lines) should be written to the FIFO. + * + * Three PIO SMs will be configured as timers, to fire at the end of a left + * broad pulse, the middle of a scanline, and the end of a right broad pulse. + * + * HSYNC->CSYNC latency is about 5 cycles, with a jitter of up to 1 cycle. + * + * Default program is compiled for +HSync, -CSync. The program may be + * modified for other polarities. GPIO2 polarity is always active low. + */ + +static int rp1dpi_pio_csync_ilace(struct rp1_dpi *dpi, + struct drm_display_mode const *mode) +{ + static const int wrap_target = 2; + static const int wrap = 23; + unsigned short instructions[] = { /* This is mutable */ + 0x90a0, // 0: pull block side 1 + 0x7040, // 1: out y, 32 side 1 + // .wrap_target ; while (true) { + 0x3083, // 2: wait 1 gpio, 3 side 1 ; do { @HSync + 0xa422, // 3: mov x, y side 0 [4] ; CSYNC: x = VSW - 1 + 0x2003, // 4: wait 0 gpio, 3 side 0 ; CSYNC: HSync->CSync + 0x12c2, // 5: jmp pin, 2 side 1 [2] ; } while (VSync) + 0x3083, // 6: wait 1 gpio, 3 side 1 ; @HSync + 0xc442, // 7: irq clear 2 side 0 [4] ; CSYNC: flush IRQ + 0x2003, // 8: wait 0 gpio, 3 side 0 ; CSYNC: Hsync->CSync + 0x30c2, // 9: wait 1 irq, 2 side 1 ; @midline + 0x10d4, // 10: jmp pin, 20 side 1 ; if (!VSync) goto sync_left; + 0x3083, // 11: wait 1 gpio, 3 side 1 ; @HSync + 0xa442, // 12: nop side 0 [4] ; CSYNC: delay + 0x2003, // 13: wait 0 gpio, 3 side 0 ; CSYNC: Hsync->CSync + 0xd042, // 14: irq clear 2 side 1 ; do { flush IRQ + 0xd043, // 15: irq clear 3 side 1 ; flush IRQ + 0x30c2, // 16: wait 1 irq, 2 side 1 ; @midline + 0x20c3, // 17: wait 1 irq, 3 side 0 ; CSYNC: @BroadRight + 0x1054, // 18: jmp x--, 20 side 1 ; if (x-- == 0) + 0x1002, // 19: jmp 2 side 1 ; break; + 0xd041, // 20: irq clear 1 side 1 ; sync_left: flush IRQ + 0x3083, // 21: wait 1 gpio, 3 side 1 ; @HSync + 0x20c1, // 22: wait 1 irq, 1 side 0 ; CSYNC: @BroadLeft + 0x104e, // 23: jmp x--, 14 side 1 ; } while (x--); + // .wrap ; } + }; + 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; + + /* Claim SM 0 and start timers on the other three SMs. */ + i = rp1dpi_pio_claim_all_start_timers(dpi, mode); + if (i < 0) + return -EBUSY; + + /* Adapt program code according to CSync polarity; configure program */ + pio_sm_set_enabled(dpi->pio, 0, false); + for (i = 0; i < prog.length; i++) { + if (mode->flags & DRM_MODE_FLAG_PCSYNC) + 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 -1; + + /* Configure pins and SM; set VSync width; start the SM */ + sm_config_set_wrap(&cfg, offset + wrap_target, offset + wrap); + sm_config_set_sideset(&cfg, 1, false, false); + sm_config_set_sideset_pins(&cfg, 1); /* PIO produces CSync on GPIO 1 */ + pio_gpio_init(dpi->pio, 1); + sm_config_set_jmp_pin(&cfg, 2); /* DPI "helper signal" is GPIO 2 */ + pio_sm_init(dpi->pio, 0, offset, &cfg); + pio_sm_set_consecutive_pindirs(dpi->pio, 0, 1, 1, true); + pio_sm_put(dpi->pio, 0, mode->vsync_end - mode->vsync_start - 1); + pio_sm_set_enabled(dpi->pio, 0, true); + + return 0; +} + +/* + * COMPOSITE SYNC (TV-STYLE) for 625/25i and 525/30i only. + * + * DPI VSYNC (GPIO2) must be a modified signal which is always active-low. + * It should go low for 1 or 2 scanlines, VSyncWidth/2.0 or (VSyncWidth+1)/2.0 + * lines before Vsync-start, i.e. just after the last full active TV line + * (noting that RP1 DPI does not generate half-lines). + * + * This will push the image up by 1 line compared to customary DRM timings in + * "PAL" mode, or 2 lines in "NTSC" mode (which is arguably too low anyway), + * but avoids a collision between an active line and an equalizing pulse. + * + * Another wrinkle is that when the first equalizing pulse aligns with HSync, + * it becomes a normal-width sync pulse. This was a deliberate simplification. + * It is unlikely that any TV will notice or care. + */ + +static int rp1dpi_pio_csync_tv(struct rp1_dpi *dpi, + struct drm_display_mode const *mode) +{ + static const int wrap_target = 6; + static const int wrap = 27; + unsigned short instructions[] = { /* This is mutable */ + 0x3703, // 0: wait 0 gpio, 3 side 1 [7] ; while (HSync) delay; + 0x3083, // 1: wait 1 gpio, 3 side 1 ; do { @HSync + 0xa7e6, // 2: mov osr, isr side 0 [7] ; CSYNC: rewind sequence + 0x2003, // 3: wait 0 gpio, 3 side 0 ; CSYNC: HSync->CSync + 0xb7e6, // 4: mov osr, isr side 1 [7] ; delay + 0x10c1, // 5: jmp pin, 1 side 1 ; } while (VSync) + // .wrap_target ; while (true) { + 0xd042, // 6: irq clear 2 side 1 ; flush stale IRQ + 0xd043, // 7: irq clear 3 side 1 ; flush stale IRQ + 0xb022, // 8: mov x, y side 1 ; X = EQWidth - 3 + 0x30c2, // 9: wait 1 irq, 2 side 1 ; @midline + 0x004a, // 10: jmp x--, 10 side 0 ; CSYNC: while (x--) ; + 0x6021, // 11: out x, 1 side 0 ; CSYNC: next pulse broad? + 0x002e, // 12: jmp !x, 14 side 0 ; CSYNC: if (broad) + 0x20c3, // 13: wait 1 irq, 3 side 0 ; CSYNC: @BroadRight + 0x7021, // 14: out x, 1 side 1 ; sequence not finished? + 0x1020, // 15: jmp !x, 0 side 1 ; if (finished) break + 0xd041, // 16: irq clear 1 side 1 ; flush stale IRQ + 0xb022, // 17: mov x, y side 1 ; X = EQWidth - 3 + 0x3083, // 18: wait 1 gpio, 3 side 1 ; @HSync + 0x0053, // 19: jmp x--, 19 side 0 ; CSYNC: while (x--) ; + 0x6021, // 20: out x, 1 side 0 ; CSYNC: next pulse broad? + 0x0037, // 21: jmp !x, 23 side 0 ; CSYNC: if (broad) + 0x20c1, // 22: wait 1 irq, 1 side 0 ; CSYNC: @BroadLeft + 0x7021, // 23: out x, 1 side 1 ; sequence not finished? + 0x1020, // 24: jmp !x, 0 side 1 ; if (finished) break + 0x10c6, // 25: jmp pin, 6 side 1 ; if (VSync) continue + 0xb0e6, // 26: mov osr, isr side 1 ; rewind sequence + 0x7022, // 27: out x, 2 side 1 ; skip 2 bits + // .wrap ; } + }; + 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; + + /* Claim SM 0 and start timers on the other three SMs. */ + i = rp1dpi_pio_claim_all_start_timers(dpi, mode); + if (i < 0) + return -EBUSY; + + /* Adapt program code according to CSync polarity; configure program */ + pio_sm_set_enabled(dpi->pio, 0, false); + for (i = 0; i < ARRAY_SIZE(instructions); i++) { + if (mode->flags & DRM_MODE_FLAG_PCSYNC) + 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 -1; + + /* Configure pins and SM */ + sm_config_set_wrap(&cfg, offset + wrap_target, offset + wrap); + sm_config_set_sideset(&cfg, 1, false, false); + sm_config_set_sideset_pins(&cfg, 1); /* PIO produces CSync on GPIO 1 */ + pio_gpio_init(dpi->pio, 1); + sm_config_set_jmp_pin(&cfg, 2); /* DPI VSync "helper" signal is GPIO 2 */ + pio_sm_init(dpi->pio, 0, offset, &cfg); + pio_sm_set_consecutive_pindirs(dpi->pio, 0, 1, 1, true); + + /* Load parameters (Vsync pattern; EQ pulse width) into ISR and Y */ + i = (mode->vsync_end - mode->vsync_start <= 5); + pio_sm_put(dpi->pio, 0, i ? 0x02ABFFAA : 0xAABFFEAA); + pio_sm_put(dpi->pio, 0, clock_get_hz(clk_sys) / (i ? 425531 : 434782) - 3); + pio_sm_exec(dpi->pio, 0, pio_encode_pull(false, false)); + pio_sm_exec(dpi->pio, 0, pio_encode_out(pio_y, 32)); + pio_sm_exec(dpi->pio, 0, pio_encode_in(pio_y, 32)); + pio_sm_exec(dpi->pio, 0, pio_encode_pull(false, false)); + pio_sm_exec(dpi->pio, 0, pio_encode_out(pio_y, 32)); + + /* Start the SM */ + pio_sm_set_enabled(dpi->pio, 0, true); + + return 0; +} + +int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode, + bool force_csync) { int r; - if (!(mode->flags & DRM_MODE_FLAG_INTERLACE) || !dpi->gpio1_used) + /* + * Check if PIO is needed *and* we have an appropriate pin mapping + * that allows all three Sync GPIOs to be snooped on or overridden. + */ + if (!(mode->flags & (DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_CSYNC)) && + !force_csync) return 0; + if (!dpi->sync_gpios_mapped) { + drm_warn(&dpi->drm, "DPI needs GPIOs 1-3 for Interlace or CSync\n"); + return -EINVAL; + } if (dpi->pio) pio_close(dpi->pio); @@ -223,7 +559,22 @@ int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode) return -ENODEV; } - r = rp1dpi_pio_vsync_ilace(dpi, mode); + if ((mode->flags & DRM_MODE_FLAG_CSYNC) || force_csync) { + drm_info(&dpi->drm, "Using PIO to generate CSync on GPIO1\n"); + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + if (mode->clock > 15 * mode->htotal && + mode->clock < 16 * mode->htotal && + (mode->vtotal == 525 || mode->vtotal == 625)) + r = rp1dpi_pio_csync_tv(dpi, mode); + else + r = rp1dpi_pio_csync_ilace(dpi, mode); + } else { + r = rp1dpi_pio_csync_prog(dpi, mode); + } + } else { + drm_info(&dpi->drm, "Using PIO to generate VSync on GPIO2\n"); + r = rp1dpi_pio_vsync_ilace(dpi, mode); + } if (r) { drm_err(&dpi->drm, "Failed to initialize PIO\n"); rp1dpi_pio_stop(dpi); @@ -235,10 +586,10 @@ int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode) 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); + /* Return any "stolen" pins to DPI function */ + pio_gpio_set_function(dpi->pio, 1, GPIO_FUNC_FSEL1); + pio_gpio_set_function(dpi->pio, 2, GPIO_FUNC_FSEL1); pio_close(dpi->pio); - dpi->pio_stole_gpio2 = false; dpi->pio = NULL; } } @@ -247,7 +598,12 @@ void rp1dpi_pio_stop(struct rp1_dpi *dpi) int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode) { - return -ENODEV; + if (mode->flags & (DRM_MODE_FLAG_CSYNC | DRM_MODE_FLAG_INTERLACE)) { + drm_warn(&dpi->drm, "DPI needs PIO support for Interlace or CSync\n"); + return -ENODEV; + } else { + return 0; + } } void rp1dpi_pio_stop(struct rp1_dpi *dpi)