Module Name:    src
Committed By:   jmcneill
Date:           Sun Feb  3 13:15:19 UTC 2019

Modified Files:
        src/sys/arch/arm/sunxi: sunxi_lcdc.c

Log Message:
Add TCON0 support


To generate a diff of this commit:
cvs rdiff -u -r1.2 -r1.3 src/sys/arch/arm/sunxi/sunxi_lcdc.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/arm/sunxi/sunxi_lcdc.c
diff -u src/sys/arch/arm/sunxi/sunxi_lcdc.c:1.2 src/sys/arch/arm/sunxi/sunxi_lcdc.c:1.3
--- src/sys/arch/arm/sunxi/sunxi_lcdc.c:1.2	Thu Jan 31 01:49:28 2019
+++ src/sys/arch/arm/sunxi/sunxi_lcdc.c	Sun Feb  3 13:15:19 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: sunxi_lcdc.c,v 1.2 2019/01/31 01:49:28 jmcneill Exp $ */
+/* $NetBSD: sunxi_lcdc.c,v 1.3 2019/02/03 13:15:19 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2019 Jared D. McNeill <jmcne...@invisible.ca>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: sunxi_lcdc.c,v 1.2 2019/01/31 01:49:28 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: sunxi_lcdc.c,v 1.3 2019/02/03 13:15:19 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -47,11 +47,31 @@ __KERNEL_RCSID(0, "$NetBSD: sunxi_lcdc.c
 #define	 TCON_GCTL_TCON_EN			__BIT(31)
 #define	 TCON_GCTL_GAMMA_EN			__BIT(30)
 #define	 TCON_GCTL_IO_MAP_SEL			__BIT(0)
-
 #define	TCON_GINT0_REG		0x004
 #define	TCON_GINT1_REG		0x008
 #define	 TCON_GINT1_TCON1_LINE_INT_NUM		__BITS(11,0)
 
+#define	TCON0_CTL_REG		0x040
+#define	 TCON0_CTL_TCON0_EN			__BIT(31)
+#define	 TCON0_CTL_START_DELAY			__BITS(8,4)
+#define	 TCON0_CTL_TCON0_SRC_SEL		__BITS(2,0)
+#define	TCON0_DCLK_REG		0x044
+#define	 TCON0_DCLK_EN				__BITS(31,28)
+#define	 TCON0_DCLK_DIV				__BITS(6,0)
+#define	TCON0_BASIC0_REG	0x048
+#define	TCON0_BASIC1_REG	0x04c
+#define	TCON0_BASIC2_REG	0x050
+#define	TCON0_BASIC3_REG	0x054
+#define	TCON0_IO_POL_REG	0x088
+#define	 TCON0_IO_POL_IO_OUTPUT_SEL		__BIT(31)
+#define	 TCON0_IO_POL_DCLK_SEL			__BITS(30,28)
+#define	 TCON0_IO_POL_IO3_INV			__BIT(27)
+#define	 TCON0_IO_POL_IO2_INV			__BIT(26)
+#define	 TCON0_IO_POL_IO1_INV			__BIT(25)
+#define	 TCON0_IO_POL_IO0_INV			__BIT(24)
+#define	 TCON0_IO_POL_DATA_INV			__BITS(23,0)
+#define	TCON0_IO_TRI_REG	0x08c
+
 #define	TCON1_CTL_REG		0x090
 #define	 TCON1_CTL_TCON1_EN			__BIT(31)
 #define	 TCON1_CTL_START_DELAY			__BITS(8,4)
@@ -62,7 +82,6 @@ __KERNEL_RCSID(0, "$NetBSD: sunxi_lcdc.c
 #define	TCON1_BASIC3_REG	0x0a0
 #define	TCON1_BASIC4_REG	0x0a4
 #define	TCON1_BASIC5_REG	0x0a8
-
 #define	TCON1_IO_POL_REG	0x0f0
 #define	 TCON1_IO_POL_IO3_INV			__BIT(27)
 #define	 TCON1_IO_POL_IO2_INV			__BIT(26)
@@ -72,15 +91,20 @@ __KERNEL_RCSID(0, "$NetBSD: sunxi_lcdc.c
 #define	TCON1_IO_TRI_REG	0x0f4
 
 enum {
-	MIXER_PORT_INPUT = 0,
-	MIXER_PORT_OUTPUT = 1,
+	TCON_PORT_INPUT = 0,
+	TCON_PORT_OUTPUT = 1,
+};
+
+enum tcon_type {
+	TYPE_TCON0,
+	TYPE_TCON1,
 };
 
-static const char * const compatible[] = {
-	"allwinner,sun8i-h3-tcon-tv",
-	"allwinner,sun50i-a64-tcon-lcd",
-	"allwinner,sun50i-a64-tcon-tv",
-	NULL
+static const struct of_compat_data compat_data[] = {
+	{ "allwinner,sun8i-h3-tcon-tv",		TYPE_TCON1 },
+	{ "allwinner,sun50i-a64-tcon-lcd",	TYPE_TCON0 },
+	{ "allwinner,sun50i-a64-tcon-tv",	TYPE_TCON1 },
+	{ NULL }
 };
 
 struct sunxi_lcdc_softc;
@@ -97,6 +121,8 @@ struct sunxi_lcdc_softc {
 	bus_space_handle_t	sc_bsh;
 	int			sc_phandle;
 
+	enum tcon_type		sc_type;
+
 	struct clk		*sc_clk_ch[2];
 
 	struct sunxi_lcdc_encoder sc_encoder;
@@ -122,19 +148,19 @@ static const struct drm_encoder_funcs su
 };
 
 static void
-sunxi_lcdc_tcon1_dpms(struct drm_encoder *encoder, int mode)
+sunxi_lcdc_tcon_dpms(struct drm_encoder *encoder, int mode)
 {
 }
 
 static bool
-sunxi_lcdc_tcon1_mode_fixup(struct drm_encoder *encoder,
+sunxi_lcdc_tcon_mode_fixup(struct drm_encoder *encoder,
     const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
 {
 	return true;
 }
 
 static void
-sunxi_lcdc_tcon1_mode_set(struct drm_encoder *encoder,
+sunxi_lcdc_tcon_mode_set(struct drm_encoder *encoder,
     struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
 {
 	struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
@@ -143,6 +169,20 @@ sunxi_lcdc_tcon1_mode_set(struct drm_enc
 }
 
 static void
+sunxi_lcdc_tcon0_prepare(struct drm_encoder *encoder)
+{
+	struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
+	struct sunxi_lcdc_softc * const sc = lcdc_encoder->sc;
+	uint32_t val;
+
+	val = TCON_READ(sc, TCON_GCTL_REG);
+	val |= TCON_GCTL_TCON_EN;
+	TCON_WRITE(sc, TCON_GCTL_REG, val);
+
+	TCON_WRITE(sc, TCON0_IO_TRI_REG, 0);
+}
+
+static void
 sunxi_lcdc_tcon1_prepare(struct drm_encoder *encoder)
 {
 	struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
@@ -158,6 +198,61 @@ sunxi_lcdc_tcon1_prepare(struct drm_enco
 }
 
 static void
+sunxi_lcdc_tcon0_commit(struct drm_encoder *encoder)
+{
+	struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
+	struct sunxi_lcdc_softc * const sc = lcdc_encoder->sc;
+	struct drm_display_mode *mode = &lcdc_encoder->curmode;
+	uint32_t val;
+	int error;
+
+	const u_int interlace_p = (mode->flags & DRM_MODE_FLAG_INTERLACE) != 0;
+	const u_int hspw = mode->hsync_end - mode->hsync_start;
+	const u_int hbp = mode->htotal - mode->hsync_start;
+	const u_int vspw = mode->vsync_end - mode->vsync_start;
+	const u_int vbp = mode->vtotal - mode->vsync_start;
+	const u_int vblank_len =
+	    ((mode->vtotal << interlace_p) >> 1) - mode->vdisplay - 2;
+	const u_int start_delay =
+	    vblank_len >= 32 ? 30 : vblank_len - 2;
+
+	val = TCON0_CTL_TCON0_EN |
+	      __SHIFTIN(start_delay, TCON0_CTL_START_DELAY);
+	TCON_WRITE(sc, TCON0_CTL_REG, val);
+
+	TCON_WRITE(sc, TCON0_BASIC0_REG, ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1));
+	TCON_WRITE(sc, TCON0_BASIC1_REG, ((mode->htotal - 1) << 16) | (hbp - 1));
+	TCON_WRITE(sc, TCON0_BASIC2_REG, ((mode->vtotal * 2) << 16) | (vbp - 1));
+	TCON_WRITE(sc, TCON0_BASIC3_REG, ((hspw - 1) << 16) | (vspw - 1));
+
+	val = TCON_READ(sc, TCON0_IO_POL_REG);
+	val &= ~(TCON0_IO_POL_IO3_INV|TCON0_IO_POL_IO2_INV|
+		 TCON0_IO_POL_IO1_INV|TCON0_IO_POL_IO0_INV|
+		 TCON0_IO_POL_DATA_INV);
+	if ((mode->flags & DRM_MODE_FLAG_PHSYNC) == 0)
+		val |= TCON0_IO_POL_IO1_INV;
+	if ((mode->flags & DRM_MODE_FLAG_PVSYNC) == 0)
+		val |= TCON0_IO_POL_IO0_INV;
+	TCON_WRITE(sc, TCON0_IO_POL_REG, val);
+
+	if (sc->sc_clk_ch[0] != NULL) {
+		error = clk_set_rate(sc->sc_clk_ch[1], mode->crtc_clock * 1000);
+		if (error != 0) {
+			device_printf(sc->sc_dev, "failed to set CH0 PLL rate to %u Hz: %d\n",
+			    mode->crtc_clock * 1000, error);
+			return;
+		}
+		error = clk_enable(sc->sc_clk_ch[1]);
+		if (error != 0) {
+			device_printf(sc->sc_dev, "failed to enable CH0 PLL: %d\n", error);
+			return;
+		}
+	} else {
+		device_printf(sc->sc_dev, "no CH0 PLL configured\n");
+	}
+}
+
+static void
 sunxi_lcdc_tcon1_commit(struct drm_encoder *encoder)
 {
 	struct sunxi_lcdc_encoder *lcdc_encoder = to_sunxi_lcdc_encoder(encoder);
@@ -207,27 +302,52 @@ sunxi_lcdc_tcon1_commit(struct drm_encod
 	}
 }
 
+static const struct drm_encoder_helper_funcs sunxi_lcdc_tcon0_helper_funcs = {
+	.dpms = sunxi_lcdc_tcon_dpms,
+	.mode_fixup = sunxi_lcdc_tcon_mode_fixup,
+	.prepare = sunxi_lcdc_tcon0_prepare,
+	.commit = sunxi_lcdc_tcon0_commit,
+	.mode_set = sunxi_lcdc_tcon_mode_set,
+};
+
 static const struct drm_encoder_helper_funcs sunxi_lcdc_tcon1_helper_funcs = {
-	.dpms = sunxi_lcdc_tcon1_dpms,
-	.mode_fixup = sunxi_lcdc_tcon1_mode_fixup,
+	.dpms = sunxi_lcdc_tcon_dpms,
+	.mode_fixup = sunxi_lcdc_tcon_mode_fixup,
 	.prepare = sunxi_lcdc_tcon1_prepare,
 	.commit = sunxi_lcdc_tcon1_commit,
-	.mode_set = sunxi_lcdc_tcon1_mode_set,
+	.mode_set = sunxi_lcdc_tcon_mode_set,
 };
 
 static int
+sunxi_lcdc_encoder_mode(struct fdt_endpoint *out_ep)
+{
+	struct fdt_endpoint *remote_ep = fdt_endpoint_remote(out_ep);
+
+	if (remote_ep == NULL)
+		return DRM_MODE_ENCODER_NONE;
+
+	switch (fdt_endpoint_type(remote_ep)) {
+	case EP_DRM_BRIDGE:
+		return DRM_MODE_ENCODER_TMDS;
+	case EP_DRM_PANEL:
+		return DRM_MODE_ENCODER_LVDS;
+	default:
+		return DRM_MODE_ENCODER_NONE;
+	}
+}
+
+static int
 sunxi_lcdc_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
 {
 	struct sunxi_lcdc_softc * const sc = device_private(dev);
 	struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep);
 	struct fdt_endpoint *out_ep;
 	struct drm_crtc *crtc;
-	int error;
 
 	if (!activate)
 		return EINVAL;
 
-	if (fdt_endpoint_port_index(ep) != MIXER_PORT_INPUT)
+	if (fdt_endpoint_port_index(ep) != TCON_PORT_INPUT)
 		return EINVAL;
 
 	if (fdt_endpoint_type(in_ep) != EP_DRM_CRTC)
@@ -236,20 +356,27 @@ sunxi_lcdc_ep_activate(device_t dev, str
 	crtc = fdt_endpoint_get_data(in_ep);
 
 	sc->sc_encoder.sc = sc;
+	sc->sc_encoder.base.possible_crtcs = 1 << drm_crtc_index(crtc);
+
+	out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, TCON_PORT_OUTPUT, 0);
+	if (out_ep != NULL) {
+		drm_encoder_init(crtc->dev, &sc->sc_encoder.base, &sunxi_lcdc_funcs,
+		    sunxi_lcdc_encoder_mode(out_ep));
+		drm_encoder_helper_add(&sc->sc_encoder.base, &sunxi_lcdc_tcon0_helper_funcs);
+
+		return fdt_endpoint_activate(out_ep, activate);
+	}
 
-	out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, MIXER_PORT_OUTPUT, 1);
+	out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, TCON_PORT_OUTPUT, 1);
 	if (out_ep != NULL) {
 		drm_encoder_init(crtc->dev, &sc->sc_encoder.base, &sunxi_lcdc_funcs,
-		    DRM_MODE_ENCODER_TMDS);
+		    sunxi_lcdc_encoder_mode(out_ep));
 		drm_encoder_helper_add(&sc->sc_encoder.base, &sunxi_lcdc_tcon1_helper_funcs);
 
-		error = fdt_endpoint_activate(out_ep, activate);
-		if (error != 0)
-			return error;
-		sc->sc_encoder.base.possible_crtcs = 1 << drm_crtc_index(crtc);
+		return fdt_endpoint_activate(out_ep, activate);
 	}
 
-	return 0;
+	return ENXIO;
 }
 
 static void *
@@ -265,7 +392,7 @@ sunxi_lcdc_match(device_t parent, cfdata
 {
 	struct fdt_attach_args * const faa = aux;
 
-	return of_match_compatible(faa->faa_phandle, compatible);
+	return of_match_compat_data(faa->faa_phandle, compat_data);
 }
 
 static void
@@ -303,11 +430,19 @@ sunxi_lcdc_attach(device_t parent, devic
 		return;
 	}
 	sc->sc_phandle = faa->faa_phandle;
+	sc->sc_type = of_search_compatible(phandle, compat_data)->data;
 	sc->sc_clk_ch[0] = fdtbus_clock_get(phandle, "tcon-ch0");
 	sc->sc_clk_ch[1] = fdtbus_clock_get(phandle, "tcon-ch1");
 
 	aprint_naive("\n");
-	aprint_normal(": Timing Controller\n");
+	switch (sc->sc_type) {
+	case TYPE_TCON0:
+		aprint_normal(": TCON0\n");
+		break;
+	case TYPE_TCON1:
+		aprint_normal(": TCON1\n");
+		break;
+	}
 
 	sc->sc_ports.dp_ep_activate = sunxi_lcdc_ep_activate;
 	sc->sc_ports.dp_ep_get_data = sunxi_lcdc_ep_get_data;

Reply via email to