mirror of
https://github.com/openwrt/openwrt.git
synced 2025-01-10 06:52:53 +00:00
f4c5d0e77e
Cherry-pick patches to support building RP1 modules.
Signed-off-by: John Audia <therealgraysky@proton.me>
Link: https://github.com/openwrt/openwrt/pull/17233
Signed-off-by: Robert Marko <robimarko@gmail.com>
(cherry picked from commit 613dd79d5e
)
763 lines
28 KiB
Diff
763 lines
28 KiB
Diff
From 7735dd0736322cff23aff95490bae1d69937a9bf Mon Sep 17 00:00:00 2001
|
|
From: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
|
|
Date: Tue, 10 Dec 2024 13:23:09 +0000
|
|
Subject: [PATCH 1456/1482] drm: rp1: rp1-dpi: Add interlaced modes and PIO
|
|
program to fix VSYNC
|
|
|
|
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 <nick.hollinghurst@raspberrypi.com>
|
|
---
|
|
drivers/gpu/drm/rp1/rp1-dpi/Makefile | 2 +-
|
|
drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c | 34 ++-
|
|
drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h | 18 ++
|
|
drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c | 253 ++++++++++++++++------
|
|
drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c | 225 +++++++++++++++++++
|
|
5 files changed, 461 insertions(+), 71 deletions(-)
|
|
create mode 100644 drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c
|
|
|
|
--- 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
|
|
--- 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 dr
|
|
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 dr
|
|
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 d
|
|
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_de
|
|
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
|
|
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
|
|
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
|
|
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);
|
|
--- 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_dp
|
|
|
|
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);
|
|
--- a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c
|
|
+++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c
|
|
@@ -202,7 +202,7 @@
|
|
// Status
|
|
#define DPI_DMA_STATUS 0x3c
|
|
|
|
-#define BITS(field, val) (((val) << (field ## _SHIFT)) & (field ## _MASK))
|
|
+#define BITS(field, val) FIELD_PREP((field ## _MASK), val)
|
|
|
|
static unsigned int rp1dpi_hw_read(struct rp1_dpi *dpi, unsigned int reg)
|
|
{
|
|
@@ -231,69 +231,73 @@ struct rp1dpi_ipixfmt {
|
|
u32 rgbsz; /* Shifts used for scaling; also (BPP/8-1) */
|
|
};
|
|
|
|
-#define IMASK_RGB(r, g, b) (BITS(DPI_DMA_IMASK_R, r) | \
|
|
- BITS(DPI_DMA_IMASK_G, g) | \
|
|
- BITS(DPI_DMA_IMASK_B, b))
|
|
-#define OMASK_RGB(r, g, b) (BITS(DPI_DMA_OMASK_R, r) | \
|
|
- BITS(DPI_DMA_OMASK_G, g) | \
|
|
- BITS(DPI_DMA_OMASK_B, b))
|
|
-#define ISHIFT_RGB(r, g, b) (BITS(DPI_DMA_SHIFT_IR, r) | \
|
|
- BITS(DPI_DMA_SHIFT_IG, g) | \
|
|
- BITS(DPI_DMA_SHIFT_IB, b))
|
|
-#define OSHIFT_RGB(r, g, b) (BITS(DPI_DMA_SHIFT_OR, r) | \
|
|
- BITS(DPI_DMA_SHIFT_OG, g) | \
|
|
- BITS(DPI_DMA_SHIFT_OB, b))
|
|
+#define IMASK_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_IMASK_R_MASK, r) | \
|
|
+ FIELD_PREP_CONST(DPI_DMA_IMASK_G_MASK, g) | \
|
|
+ FIELD_PREP_CONST(DPI_DMA_IMASK_B_MASK, b))
|
|
+#define OMASK_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_OMASK_R_MASK, r) | \
|
|
+ FIELD_PREP_CONST(DPI_DMA_OMASK_G_MASK, g) | \
|
|
+ FIELD_PREP_CONST(DPI_DMA_OMASK_B_MASK, b))
|
|
+#define ISHIFT_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_SHIFT_IR_MASK, r) | \
|
|
+ FIELD_PREP_CONST(DPI_DMA_SHIFT_IG_MASK, g) | \
|
|
+ FIELD_PREP_CONST(DPI_DMA_SHIFT_IB_MASK, b))
|
|
+#define OSHIFT_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_SHIFT_OR_MASK, r) | \
|
|
+ FIELD_PREP_CONST(DPI_DMA_SHIFT_OG_MASK, g) | \
|
|
+ FIELD_PREP_CONST(DPI_DMA_SHIFT_OB_MASK, b))
|
|
|
|
static const struct rp1dpi_ipixfmt my_formats[] = {
|
|
{
|
|
.format = DRM_FORMAT_XRGB8888,
|
|
.mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
|
.shift = ISHIFT_RGB(23, 15, 7),
|
|
- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3),
|
|
+ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 3),
|
|
},
|
|
{
|
|
.format = DRM_FORMAT_XBGR8888,
|
|
.mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
|
.shift = ISHIFT_RGB(7, 15, 23),
|
|
- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3),
|
|
+ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 3),
|
|
},
|
|
{
|
|
.format = DRM_FORMAT_ARGB8888,
|
|
.mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
|
.shift = ISHIFT_RGB(23, 15, 7),
|
|
- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3),
|
|
+ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 3),
|
|
},
|
|
{
|
|
.format = DRM_FORMAT_ABGR8888,
|
|
.mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
|
.shift = ISHIFT_RGB(7, 15, 23),
|
|
- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 3),
|
|
+ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 3),
|
|
},
|
|
{
|
|
.format = DRM_FORMAT_RGB888,
|
|
.mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
|
.shift = ISHIFT_RGB(23, 15, 7),
|
|
- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 2),
|
|
+ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 2),
|
|
},
|
|
{
|
|
.format = DRM_FORMAT_BGR888,
|
|
.mask = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
|
.shift = ISHIFT_RGB(7, 15, 23),
|
|
- .rgbsz = BITS(DPI_DMA_RGBSZ_BPP, 2),
|
|
+ .rgbsz = FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 2),
|
|
},
|
|
{
|
|
.format = DRM_FORMAT_RGB565,
|
|
.mask = IMASK_RGB(0x3e0, 0x3f0, 0x3e0),
|
|
.shift = ISHIFT_RGB(15, 10, 4),
|
|
- .rgbsz = BITS(DPI_DMA_RGBSZ_R, 5) | BITS(DPI_DMA_RGBSZ_G, 6) |
|
|
- BITS(DPI_DMA_RGBSZ_B, 5) | BITS(DPI_DMA_RGBSZ_BPP, 1),
|
|
+ .rgbsz = (FIELD_PREP_CONST(DPI_DMA_RGBSZ_R_MASK, 5) |
|
|
+ FIELD_PREP_CONST(DPI_DMA_RGBSZ_G_MASK, 6) |
|
|
+ FIELD_PREP_CONST(DPI_DMA_RGBSZ_B_MASK, 5) |
|
|
+ FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 1)),
|
|
},
|
|
{
|
|
.format = DRM_FORMAT_BGR565,
|
|
.mask = IMASK_RGB(0x3e0, 0x3f0, 0x3e0),
|
|
.shift = ISHIFT_RGB(4, 10, 15),
|
|
- .rgbsz = BITS(DPI_DMA_RGBSZ_R, 5) | BITS(DPI_DMA_RGBSZ_G, 6) |
|
|
- BITS(DPI_DMA_RGBSZ_B, 5) | BITS(DPI_DMA_RGBSZ_BPP, 1),
|
|
+ .rgbsz = (FIELD_PREP_CONST(DPI_DMA_RGBSZ_R_MASK, 5) |
|
|
+ FIELD_PREP_CONST(DPI_DMA_RGBSZ_G_MASK, 6) |
|
|
+ FIELD_PREP_CONST(DPI_DMA_RGBSZ_B_MASK, 5) |
|
|
+ FIELD_PREP_CONST(DPI_DMA_RGBSZ_BPP_MASK, 1)),
|
|
}
|
|
};
|
|
|
|
@@ -354,42 +358,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 +405,89 @@ 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.0 or
|
|
+ * 2.5 scan-lines respectively before nominal VSYNC start.
|
|
+ */
|
|
+ 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 + 4) ?
|
|
+ ((mode->vsync_start - mode->vdisplay - 2) >> 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 +496,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 +562,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 +585,35 @@ irqreturn_t rp1dpi_hw_isr(int irq, void
|
|
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;
|
|
}
|
|
--- /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 <linux/kernel.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/pio_rp1.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <drm/drm_print.h>
|
|
+
|
|
+#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 ; <modify for -DE fixup>
|
|
+ 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 ; <modify for -DE fixup>
|
|
+ 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;
|
|
+ }
|
|
+}
|