All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
  2016-01-05 19:15 ` Jean-Francois Moine
@ 2016-01-05 18:28   ` Jean-Francois Moine
  -1 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-05 18:28 UTC (permalink / raw)
  To: linux-arm-kernel

Add the clock types which are used by the sun8i family for video.

Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
---
 drivers/clk/sunxi/Makefile            |   1 +
 drivers/clk/sunxi/clk-sun8i-display.c | 247 ++++++++++++++++++++++++++++++++++
 2 files changed, 258 insertions(+)
 create mode 100644 drivers/clk/sunxi/clk-sun8i-display.c

diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
index cb4c299..145c078 100644
--- a/drivers/clk/sunxi/Makefile
+++ b/drivers/clk/sunxi/Makefile
@@ -10,6 +10,7 @@ obj-y += clk-a10-pll2.o
 obj-y += clk-a20-gmac.o
 obj-y += clk-mod0.o
 obj-y += clk-simple-gates.o
+obj-y += clk-sun8i-display.o
 obj-y += clk-sun8i-mbus.o
 obj-y += clk-sun9i-core.o
 obj-y += clk-sun9i-mmc.o
diff --git a/drivers/clk/sunxi/clk-sun8i-display.c b/drivers/clk/sunxi/clk-sun8i-display.c
new file mode 100644
index 0000000..eded572
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun8i-display.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2015 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/rational.h>
+#include <linux/delay.h>
+
+static DEFINE_SPINLOCK(sun8i_display_lock);
+
+/* PLL3 (video) and PLL10 (de) */
+struct clk_fact {
+	struct clk_hw hw;
+	void __iomem *reg;
+};
+#define to_clk_fact(_hw) container_of(_hw, struct clk_fact, hw)
+
+#define SUN8I_PLL3_MSHIFT	0
+#define SUN8I_PLL3_MMASK	0x0f
+#define SUN8I_PLL3_NSHIFT	8
+#define SUN8I_PLL3_NMASK	0x7f
+#define SUN8I_PLL3_MODE_SEL	0x01000000
+#define SUN8I_PLL3_FRAC_CLK	0x02000000
+
+static int sun8i_pll3_get_fact(unsigned long rate,
+			unsigned long parent_rate,
+			unsigned long *n, unsigned long *m)
+{
+	if (rate == 297000000)
+		return 1;
+	if (rate == 270000000)
+		return 0;
+	rational_best_approximation(rate, parent_rate,
+				SUN8I_PLL3_NMASK + 1, SUN8I_PLL3_MMASK + 1,
+				n, m);
+	return -1;
+}
+
+static unsigned long sun8i_pll3_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct clk_fact *fact = to_clk_fact(hw);
+	u32 reg;
+	u32 n, m;
+
+	reg = readl(fact->reg);
+	if (reg & SUN8I_PLL3_MODE_SEL) {
+		n = (reg >> SUN8I_PLL3_NSHIFT) & SUN8I_PLL3_NMASK;
+		m = (reg >> SUN8I_PLL3_MSHIFT) & SUN8I_PLL3_MMASK;
+		return parent_rate / (m + 1) * (n + 1);
+	}
+	if (reg & SUN8I_PLL3_FRAC_CLK)
+		return 297000000;
+	return 270000000;
+}
+
+static long sun8i_pll3_round_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long *parent_rate)
+{
+	int frac;
+	unsigned long n, m;
+
+	frac = sun8i_pll3_get_fact(rate, *parent_rate, &n, &m);
+	if (frac == 1)
+		return 297000000;
+	if (frac == 0)
+		return 270000000;
+	return (*parent_rate * n) / m;
+}
+
+static int sun8i_pll3_set_rate(struct clk_hw *hw, unsigned long rate,
+			unsigned long parent_rate)
+{
+	struct clk_fact *fact = to_clk_fact(hw);
+	u32 reg;
+	int frac;
+	unsigned long n, m;
+
+	reg = readl(fact->reg) &
+			~(SUN8I_PLL3_MODE_SEL |
+			  SUN8I_PLL3_FRAC_CLK |
+			  (SUN8I_PLL3_NMASK << SUN8I_PLL3_NSHIFT) |
+			  (SUN8I_PLL3_MMASK << SUN8I_PLL3_MSHIFT));
+
+	frac = sun8i_pll3_get_fact(rate, parent_rate, &n, &m);
+	if (frac == 1)
+		reg |= SUN8I_PLL3_FRAC_CLK;	/* 297MHz */
+	else if (frac == 0)
+		;				/* 270MHz */
+	else
+		reg |= SUN8I_PLL3_MODE_SEL |
+			((n - 1) << SUN8I_PLL3_NSHIFT) |
+			((m - 1) << SUN8I_PLL3_MSHIFT);
+
+	writel(reg, fact->reg);
+
+	/* delay 500us so pll stabilizes */
+	__delay(500);
+
+	return 0;
+}
+
+static const struct clk_ops clk_sun8i_pll3_fact_ops = {
+	.recalc_rate = sun8i_pll3_recalc_rate,
+	.round_rate = sun8i_pll3_round_rate,
+	.set_rate = sun8i_pll3_set_rate,
+};
+
+static void __init sun8i_pll3_setup(struct device_node *node)
+{
+	const char *clk_name = node->name, *parent;
+	struct clk_fact *fact;
+	struct clk_gate *gate;
+	void __iomem *reg;
+	struct clk *clk;
+
+	of_property_read_string(node, "clock-output-names", &clk_name);
+	parent = of_clk_get_parent_name(node, 0);
+
+	reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+	if (IS_ERR(reg)) {
+		pr_err("%s: Could not map the clock registers\n", clk_name);
+		return;
+	}
+
+	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+	if (!gate)
+		return;
+
+	gate->reg = reg;
+	gate->bit_idx = 31;
+	gate->lock = &sun8i_display_lock;
+
+	fact = kzalloc(sizeof(*fact), GFP_KERNEL);
+	if (!fact)
+		goto free_gate;
+
+	fact->reg = reg;
+
+	clk = clk_register_composite(NULL, clk_name,
+				     &parent, 1,
+				     NULL, NULL,
+				     &fact->hw, &clk_sun8i_pll3_fact_ops,
+				     &gate->hw, &clk_gate_ops,
+				     0);
+	if (IS_ERR(clk)) {
+		pr_err("%s: Couldn't register the clock\n", clk_name);
+		goto free_fact;
+	}
+
+	of_clk_add_provider(node, of_clk_src_simple_get, clk);
+
+	return;
+
+free_fact:
+	kfree(fact);
+free_gate:
+	kfree(gate);
+}
+
+CLK_OF_DECLARE(sun8i_pll3, "allwinner,sun8i-pll3-clk", sun8i_pll3_setup);
+
+/* DE, TCON0, TVE, HDMI, DEINTERLACE  */
+static void __init sun8i_display_setup(struct device_node *node)
+{
+	const char *clk_name = node->name;
+	const char *parents[2];
+	struct clk_mux *mux = NULL;
+	struct clk_divider *div;
+	struct clk_gate *gate;
+	void __iomem *reg;
+	struct clk *clk;
+	int n;
+
+	of_property_read_string(node, "clock-output-names", &clk_name);
+
+	reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+	if (IS_ERR(reg)) {
+		pr_err("%s: Could not map the clock registers\n", clk_name);
+		return;
+	}
+
+	n = of_clk_parent_fill(node, parents, ARRAY_SIZE(parents));
+
+	if (n > 1) {				/* many possible sources */
+		mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+		if (!mux)
+			return;
+		mux->reg = reg;
+		mux->shift = 24;
+		mux->mask = 0x07;
+		mux->lock = &sun8i_display_lock;
+	}
+
+	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+	if (!gate)
+		goto free_gate;
+
+	gate->reg = reg;
+	gate->bit_idx = 31;
+	gate->lock = &sun8i_display_lock;
+
+	div = kzalloc(sizeof(*div), GFP_KERNEL);
+	if (!div)
+		goto free_gate;
+
+	div->reg = reg;
+	div->shift = 0;
+	div->width = 4;
+	div->lock = &sun8i_display_lock;
+
+	clk = clk_register_composite(NULL, clk_name,
+				     parents, n,
+				     mux ? &mux->hw : NULL, &clk_mux_ops,
+				     &div->hw, &clk_divider_ops,
+				     &gate->hw, &clk_gate_ops,
+				     0);
+	if (IS_ERR(clk)) {
+		pr_err("%s: Couldn't register the clock\n", clk_name);
+		goto free_div;
+	}
+
+	of_clk_add_provider(node, of_clk_src_simple_get, clk);
+
+	return;
+
+free_div:
+	kfree(div);
+free_gate:
+	kfree(gate);
+	kfree(mux);
+}
+
+CLK_OF_DECLARE(sun8i_display, "allwinner,sun8i-display-clk", sun8i_display_setup);
-- 
2.6.4

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
@ 2016-01-05 18:28   ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-05 18:28 UTC (permalink / raw)
  To: Dave Airlie, Maxime Ripard, Chen-Yu Tsai; +Cc: linux-arm-kernel, dri-devel

Add the clock types which are used by the sun8i family for video.

Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
---
 drivers/clk/sunxi/Makefile            |   1 +
 drivers/clk/sunxi/clk-sun8i-display.c | 247 ++++++++++++++++++++++++++++++++++
 2 files changed, 258 insertions(+)
 create mode 100644 drivers/clk/sunxi/clk-sun8i-display.c

diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
index cb4c299..145c078 100644
--- a/drivers/clk/sunxi/Makefile
+++ b/drivers/clk/sunxi/Makefile
@@ -10,6 +10,7 @@ obj-y += clk-a10-pll2.o
 obj-y += clk-a20-gmac.o
 obj-y += clk-mod0.o
 obj-y += clk-simple-gates.o
+obj-y += clk-sun8i-display.o
 obj-y += clk-sun8i-mbus.o
 obj-y += clk-sun9i-core.o
 obj-y += clk-sun9i-mmc.o
diff --git a/drivers/clk/sunxi/clk-sun8i-display.c b/drivers/clk/sunxi/clk-sun8i-display.c
new file mode 100644
index 0000000..eded572
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun8i-display.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2015 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/rational.h>
+#include <linux/delay.h>
+
+static DEFINE_SPINLOCK(sun8i_display_lock);
+
+/* PLL3 (video) and PLL10 (de) */
+struct clk_fact {
+	struct clk_hw hw;
+	void __iomem *reg;
+};
+#define to_clk_fact(_hw) container_of(_hw, struct clk_fact, hw)
+
+#define SUN8I_PLL3_MSHIFT	0
+#define SUN8I_PLL3_MMASK	0x0f
+#define SUN8I_PLL3_NSHIFT	8
+#define SUN8I_PLL3_NMASK	0x7f
+#define SUN8I_PLL3_MODE_SEL	0x01000000
+#define SUN8I_PLL3_FRAC_CLK	0x02000000
+
+static int sun8i_pll3_get_fact(unsigned long rate,
+			unsigned long parent_rate,
+			unsigned long *n, unsigned long *m)
+{
+	if (rate == 297000000)
+		return 1;
+	if (rate == 270000000)
+		return 0;
+	rational_best_approximation(rate, parent_rate,
+				SUN8I_PLL3_NMASK + 1, SUN8I_PLL3_MMASK + 1,
+				n, m);
+	return -1;
+}
+
+static unsigned long sun8i_pll3_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct clk_fact *fact = to_clk_fact(hw);
+	u32 reg;
+	u32 n, m;
+
+	reg = readl(fact->reg);
+	if (reg & SUN8I_PLL3_MODE_SEL) {
+		n = (reg >> SUN8I_PLL3_NSHIFT) & SUN8I_PLL3_NMASK;
+		m = (reg >> SUN8I_PLL3_MSHIFT) & SUN8I_PLL3_MMASK;
+		return parent_rate / (m + 1) * (n + 1);
+	}
+	if (reg & SUN8I_PLL3_FRAC_CLK)
+		return 297000000;
+	return 270000000;
+}
+
+static long sun8i_pll3_round_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long *parent_rate)
+{
+	int frac;
+	unsigned long n, m;
+
+	frac = sun8i_pll3_get_fact(rate, *parent_rate, &n, &m);
+	if (frac == 1)
+		return 297000000;
+	if (frac == 0)
+		return 270000000;
+	return (*parent_rate * n) / m;
+}
+
+static int sun8i_pll3_set_rate(struct clk_hw *hw, unsigned long rate,
+			unsigned long parent_rate)
+{
+	struct clk_fact *fact = to_clk_fact(hw);
+	u32 reg;
+	int frac;
+	unsigned long n, m;
+
+	reg = readl(fact->reg) &
+			~(SUN8I_PLL3_MODE_SEL |
+			  SUN8I_PLL3_FRAC_CLK |
+			  (SUN8I_PLL3_NMASK << SUN8I_PLL3_NSHIFT) |
+			  (SUN8I_PLL3_MMASK << SUN8I_PLL3_MSHIFT));
+
+	frac = sun8i_pll3_get_fact(rate, parent_rate, &n, &m);
+	if (frac == 1)
+		reg |= SUN8I_PLL3_FRAC_CLK;	/* 297MHz */
+	else if (frac == 0)
+		;				/* 270MHz */
+	else
+		reg |= SUN8I_PLL3_MODE_SEL |
+			((n - 1) << SUN8I_PLL3_NSHIFT) |
+			((m - 1) << SUN8I_PLL3_MSHIFT);
+
+	writel(reg, fact->reg);
+
+	/* delay 500us so pll stabilizes */
+	__delay(500);
+
+	return 0;
+}
+
+static const struct clk_ops clk_sun8i_pll3_fact_ops = {
+	.recalc_rate = sun8i_pll3_recalc_rate,
+	.round_rate = sun8i_pll3_round_rate,
+	.set_rate = sun8i_pll3_set_rate,
+};
+
+static void __init sun8i_pll3_setup(struct device_node *node)
+{
+	const char *clk_name = node->name, *parent;
+	struct clk_fact *fact;
+	struct clk_gate *gate;
+	void __iomem *reg;
+	struct clk *clk;
+
+	of_property_read_string(node, "clock-output-names", &clk_name);
+	parent = of_clk_get_parent_name(node, 0);
+
+	reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+	if (IS_ERR(reg)) {
+		pr_err("%s: Could not map the clock registers\n", clk_name);
+		return;
+	}
+
+	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+	if (!gate)
+		return;
+
+	gate->reg = reg;
+	gate->bit_idx = 31;
+	gate->lock = &sun8i_display_lock;
+
+	fact = kzalloc(sizeof(*fact), GFP_KERNEL);
+	if (!fact)
+		goto free_gate;
+
+	fact->reg = reg;
+
+	clk = clk_register_composite(NULL, clk_name,
+				     &parent, 1,
+				     NULL, NULL,
+				     &fact->hw, &clk_sun8i_pll3_fact_ops,
+				     &gate->hw, &clk_gate_ops,
+				     0);
+	if (IS_ERR(clk)) {
+		pr_err("%s: Couldn't register the clock\n", clk_name);
+		goto free_fact;
+	}
+
+	of_clk_add_provider(node, of_clk_src_simple_get, clk);
+
+	return;
+
+free_fact:
+	kfree(fact);
+free_gate:
+	kfree(gate);
+}
+
+CLK_OF_DECLARE(sun8i_pll3, "allwinner,sun8i-pll3-clk", sun8i_pll3_setup);
+
+/* DE, TCON0, TVE, HDMI, DEINTERLACE  */
+static void __init sun8i_display_setup(struct device_node *node)
+{
+	const char *clk_name = node->name;
+	const char *parents[2];
+	struct clk_mux *mux = NULL;
+	struct clk_divider *div;
+	struct clk_gate *gate;
+	void __iomem *reg;
+	struct clk *clk;
+	int n;
+
+	of_property_read_string(node, "clock-output-names", &clk_name);
+
+	reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+	if (IS_ERR(reg)) {
+		pr_err("%s: Could not map the clock registers\n", clk_name);
+		return;
+	}
+
+	n = of_clk_parent_fill(node, parents, ARRAY_SIZE(parents));
+
+	if (n > 1) {				/* many possible sources */
+		mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+		if (!mux)
+			return;
+		mux->reg = reg;
+		mux->shift = 24;
+		mux->mask = 0x07;
+		mux->lock = &sun8i_display_lock;
+	}
+
+	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+	if (!gate)
+		goto free_gate;
+
+	gate->reg = reg;
+	gate->bit_idx = 31;
+	gate->lock = &sun8i_display_lock;
+
+	div = kzalloc(sizeof(*div), GFP_KERNEL);
+	if (!div)
+		goto free_gate;
+
+	div->reg = reg;
+	div->shift = 0;
+	div->width = 4;
+	div->lock = &sun8i_display_lock;
+
+	clk = clk_register_composite(NULL, clk_name,
+				     parents, n,
+				     mux ? &mux->hw : NULL, &clk_mux_ops,
+				     &div->hw, &clk_divider_ops,
+				     &gate->hw, &clk_gate_ops,
+				     0);
+	if (IS_ERR(clk)) {
+		pr_err("%s: Couldn't register the clock\n", clk_name);
+		goto free_div;
+	}
+
+	of_clk_add_provider(node, of_clk_src_simple_get, clk);
+
+	return;
+
+free_div:
+	kfree(div);
+free_gate:
+	kfree(gate);
+	kfree(mux);
+}
+
+CLK_OF_DECLARE(sun8i_display, "allwinner,sun8i-display-clk", sun8i_display_setup);
-- 
2.6.4

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2
  2016-01-05 19:15 ` Jean-Francois Moine
@ 2016-01-05 18:40   ` Jean-Francois Moine
  -1 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-05 18:40 UTC (permalink / raw)
  To: linux-arm-kernel

In recent SoCs, as the H3, Allwinner uses a new display interface, DE2.
This patch adds a DRM video driver for this interface,
and also a driver for the HDMI connector found in the H3.

Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
---
 drivers/gpu/drm/Kconfig             |   2 +
 drivers/gpu/drm/Makefile            |   1 +
 drivers/gpu/drm/sunxi/Kconfig       |  21 ++
 drivers/gpu/drm/sunxi/Makefile      |   8 +
 drivers/gpu/drm/sunxi/de2_crtc.c    | 409 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_crtc.h    |  42 ++++
 drivers/gpu/drm/sunxi/de2_de.c      | 467 +++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_drm.h     |  51 ++++
 drivers/gpu/drm/sunxi/de2_drv.c     | 376 ++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi.c    | 381 ++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi.h    |  34 +++
 drivers/gpu/drm/sunxi/de2_hdmi_h3.c | 478 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi_h3.h |  14 ++
 drivers/gpu/drm/sunxi/de2_plane.c   | 102 ++++++++
 14 files changed, 2386 insertions(+)
 create mode 100644 drivers/gpu/drm/sunxi/Kconfig
 create mode 100644 drivers/gpu/drm/sunxi/Makefile
 create mode 100644 drivers/gpu/drm/sunxi/de2_crtc.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_crtc.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_de.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_drm.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_drv.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_h3.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_h3.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_plane.c

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index c4bf9a1..edef0c8 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -266,3 +266,5 @@ source "drivers/gpu/drm/amd/amdkfd/Kconfig"
 source "drivers/gpu/drm/imx/Kconfig"
 
 source "drivers/gpu/drm/vc4/Kconfig"
+
+source "drivers/gpu/drm/sunxi/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 1e9ff4c..597c246 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -75,3 +75,4 @@ obj-y			+= i2c/
 obj-y			+= panel/
 obj-y			+= bridge/
 obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
+obj-$(CONFIG_DRM_SUNXI) += sunxi/
diff --git a/drivers/gpu/drm/sunxi/Kconfig b/drivers/gpu/drm/sunxi/Kconfig
new file mode 100644
index 0000000..9b4d414
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/Kconfig
@@ -0,0 +1,21 @@
+#
+# Allwinner Video configuration
+#
+
+config DRM_SUNXI
+	tristate "DRM Support for Allwinner Video"
+	depends on DRM && ARCH_SUNXI
+	depends on OF
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_GEM_CMA_HELPER
+	help
+	  Choose this option if you have a Allwinner chipset.
+
+config DRM_SUNXI_DE2
+	tristate "Support for Allwinner Video with DE2 interface"
+	depends on DRM_SUNXI && MACH_SUN8I
+	select REGMAP_MMIO
+	help
+	  Choose this option if your Allwinner chipset has the DE2 interface
+	  as the H3.
diff --git a/drivers/gpu/drm/sunxi/Makefile b/drivers/gpu/drm/sunxi/Makefile
new file mode 100644
index 0000000..8bb533f
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for Allwinner's DRM device driver
+#
+
+sunxi-de2-drm-objs := de2_drv.o de2_de.o de2_crtc.o de2_plane.o
+sunxi-de2-hdmi-objs := de2_hdmi.o de2_hdmi_h3.o
+
+obj-$(CONFIG_DRM_SUNXI_DE2) += sunxi-de2-drm.o sunxi-de2-hdmi.o
diff --git a/drivers/gpu/drm/sunxi/de2_crtc.c b/drivers/gpu/drm/sunxi/de2_crtc.c
new file mode 100644
index 0000000..92c20d0
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_crtc.c
@@ -0,0 +1,409 @@
+/*
+ * Allwinner DRM driver - DE2 CRTC
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/component.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
+
+#include "de2_drm.h"
+#include "de2_crtc.h"
+
+/* I/O map */
+
+struct tcon {
+	u32 gctl;
+#define		TCON_GCTL_TCON_En 0x80000000
+	u32 gint0;
+#define		TCON_GINT0_TCON1_Vb_Int_En 0x40000000
+#define		TCON_GINT0_TCON1_Vb_Int_Flag 0x00001000
+	u32 gint1;
+	u32 dum0[13];
+	u32 tcon0_ctl;
+#define		TCON0_CTL_TCON_En 0x80000000
+	u32 dum1[19];
+	u32 tcon1_ctl;
+#define		TCON1_CTL_TCON_En 0x80000000
+#define		TCON1_CTL_Interlace_En 0x00100000
+#define		TCON1_CTL_Start_Delay_SHIFT 4
+#define		TCON1_CTL_Start_Delay_MASK 0x000001f0
+	u32 basic0;			/* XI/YI */
+	u32 basic1;			/* LS_XO/LS_YO */
+	u32 basic2;			/* XO/YO */
+	u32 basic3;			/* HT/HBP */
+	u32 basic4;			/* VT/VBP */
+	u32 basic5;			/* HSPW/VSPW */
+	u32 dum2;
+	u32 ps_sync;
+	u32 dum3[15];
+	u32 io_pol;
+#define		TCON1_IO_POL_IO0_inv 0x01000000
+#define		TCON1_IO_POL_IO1_inv 0x02000000
+#define		TCON1_IO_POL_IO2_inv 0x04000000
+	u32 io_tri;
+	u32 dum4[2];
+
+	u32 ceu_ctl;			/* 100 */
+#define     TCON_CEU_CTL_ceu_en 0x80000000
+	u32 dum5[3];
+	u32 ceu_rr;
+	u32 ceu_rg;
+	u32 ceu_rb;
+	u32 ceu_rc;
+	u32 ceu_gr;
+	u32 ceu_gg;
+	u32 ceu_gb;
+	u32 ceu_gc;
+	u32 ceu_br;
+	u32 ceu_bg;
+	u32 ceu_bb;
+	u32 ceu_bc;
+	u32 ceu_rv;
+	u32 ceu_gv;
+	u32 ceu_bv;
+	u32 dum6[45];
+
+	u32 mux_ctl;			/* 200 */
+#define		TCON_MUX_CTL_HDMI_SRC_SHIFT 8
+#define		TCON_MUX_CTL_HDMI_SRC_MASK 0x00000300
+	u32 dum7[63];
+
+	u32 fill_ctl;			/* 300 */
+	u32 fill_start0;
+	u32 fill_end0;
+	u32 fill_data0;
+};
+
+#define XY(x, y) (((x) << 16) | (y))
+
+/*
+ * vertical blank functions
+ */
+int de2_enable_vblank(struct drm_device *drm, unsigned crtc)
+{
+	return 0;
+}
+
+void de2_disable_vblank(struct drm_device *drm, unsigned crtc)
+{
+}
+
+static void de2_set_frame_timings(struct lcd *lcd)
+{
+	struct drm_crtc *crtc = &lcd->crtc;
+	const struct drm_display_mode *mode = &crtc->mode;
+	struct tcon *p_tcon = lcd->mmio;
+	int interlace = mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 1;
+	int start_delay;
+	u32 data;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	data = XY(mode->hdisplay - 1, mode->vdisplay / interlace - 1);
+	p_tcon->basic0 = data;
+	p_tcon->basic1 = data;
+	p_tcon->basic2 = data;
+	p_tcon->basic3 = XY(mode->htotal - 1,
+				mode->htotal - mode->hsync_start - 1);
+	p_tcon->basic4 = XY(mode->vtotal * (3 - interlace),
+				mode->vtotal - mode->vsync_start - 1);
+	p_tcon->basic5 = XY(mode->hsync_end - mode->hsync_start - 1,
+				mode->vsync_end - mode->vsync_start - 1);
+
+	p_tcon->ps_sync = XY(1, 1);
+
+	data = TCON1_IO_POL_IO2_inv;
+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+		data |= TCON1_IO_POL_IO0_inv;
+	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+		data |= TCON1_IO_POL_IO1_inv;
+	p_tcon->io_pol = data;
+
+	p_tcon->ceu_ctl &= ~TCON_CEU_CTL_ceu_en;
+
+	if (interlace == 2)
+		p_tcon->tcon1_ctl |= TCON1_CTL_Interlace_En;
+	else
+		p_tcon->tcon1_ctl &= ~TCON1_CTL_Interlace_En;
+
+	p_tcon->fill_ctl = 0;
+	p_tcon->fill_start0 = mode->vtotal + 1;
+	p_tcon->fill_end0 = mode->vtotal;
+	p_tcon->fill_data0 = 0;
+
+	start_delay = (mode->vtotal - mode->vdisplay) / interlace - 5;
+	if (start_delay > 31)
+		start_delay = 31;
+	p_tcon->tcon1_ctl &= ~TCON1_CTL_Start_Delay_MASK;
+	p_tcon->tcon1_ctl |= start_delay << TCON1_CTL_Start_Delay_SHIFT;
+
+	p_tcon->io_tri = 0x0fffffff;
+}
+
+static void de2_crtc_enable(struct drm_crtc *crtc)
+{
+	struct lcd *lcd = crtc_to_lcd(crtc);
+	struct drm_display_mode *mode = &crtc->mode;
+	struct tcon *p_tcon = lcd->mmio;
+
+	DRM_DEBUG_DRIVER("clock %d\n", mode->clock);
+
+	if (mode->clock == 0) {
+		dev_err(lcd->dev, "crtc_start: no clock!\n");
+		return;
+	}
+
+	de2_set_frame_timings(lcd);
+
+	clk_set_rate(lcd->clk, mode->clock * 1000);
+
+	if (lcd->num == 0) {				/* HDMI */
+		p_tcon->mux_ctl &= ~TCON_MUX_CTL_HDMI_SRC_MASK;
+		p_tcon->mux_ctl |= lcd->num << TCON_MUX_CTL_HDMI_SRC_SHIFT;
+	}
+
+	p_tcon->tcon1_ctl |= TCON1_CTL_TCON_En;
+
+	de2_de_enable(lcd->priv, lcd->num);
+
+	if (!lcd->init_done) {
+		lcd->init_done = 1;
+		de2_de_hw_init(lcd->priv, lcd->num);
+	}
+
+	de2_de_panel_init(lcd->priv, lcd->num, mode);
+
+	drm_mode_debug_printmodeline(mode);
+}
+
+static void de2_crtc_disable(struct drm_crtc *crtc)
+{
+	struct lcd *lcd = crtc_to_lcd(crtc);
+	struct tcon *p_tcon = lcd->mmio;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	de2_de_disable(lcd->priv, lcd->num);
+	p_tcon->tcon1_ctl &= ~TCON1_CTL_TCON_En;
+}
+
+static const struct drm_crtc_funcs de2_crtc_funcs = {
+	.destroy	= drm_crtc_cleanup,
+	.set_config	= drm_atomic_helper_set_config,
+	.page_flip	= drm_atomic_helper_page_flip,
+	.reset		= drm_atomic_helper_crtc_reset,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+static const struct drm_crtc_helper_funcs de2_crtc_helper_funcs = {
+	.enable		= de2_crtc_enable,
+	.disable	= de2_crtc_disable,
+};
+
+static void de2_tcon_init(struct lcd *lcd)
+{
+	struct tcon *p_tcon = lcd->mmio;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	p_tcon->tcon0_ctl &= ~TCON0_CTL_TCON_En;
+	p_tcon->tcon1_ctl &= ~TCON1_CTL_TCON_En;
+	p_tcon->gctl &= ~TCON_GCTL_TCON_En;
+
+	p_tcon->gint0 &= ~(TCON_GINT0_TCON1_Vb_Int_En |
+			TCON_GINT0_TCON1_Vb_Int_Flag);
+
+	p_tcon->gctl |= TCON_GCTL_TCON_En;
+}
+
+static int de2_crtc_init(struct drm_device *drm, struct lcd *lcd)
+{
+	struct drm_crtc *crtc = &lcd->crtc;
+	int ret;
+
+	ret = de2_plane_init(drm, lcd);
+	if (ret < 0)
+		return ret;
+
+	ret = drm_crtc_init_with_planes(drm, crtc,
+					&lcd->planes[0], /* primary plane */
+					NULL,		/* cursor */
+					&de2_crtc_funcs);
+	if (ret < 0)
+		return ret;
+
+	de2_tcon_init(lcd);
+
+	drm_crtc_helper_add(crtc, &de2_crtc_helper_funcs);
+
+	return 0;
+}
+
+/*
+ * device init
+ */
+static int de2_lcd_bind(struct device *dev, struct device *master,
+			void *data)
+{
+	struct drm_device *drm = data;
+	struct priv *priv = drm->dev_private;
+	struct lcd *lcd = dev_get_drvdata(dev);
+	int ret;
+
+	lcd->priv = priv;
+
+	ret = de2_crtc_init(drm, lcd);
+	if (ret < 0) {
+		dev_err(dev, "failed to init the crtc\n");
+		return ret;
+	}
+
+	priv->lcds[lcd->num] = lcd;
+
+	return 0;
+}
+
+static void de2_lcd_unbind(struct device *dev, struct device *master,
+			void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct lcd *lcd = platform_get_drvdata(pdev);
+	struct tcon *p_tcon = lcd->mmio;
+
+	if (p_tcon)
+		p_tcon->gctl &= ~TCON_GCTL_TCON_En;
+}
+
+static const struct component_ops de2_lcd_ops = {
+	.bind = de2_lcd_bind,
+	.unbind = de2_lcd_unbind,
+};
+
+static int de2_lcd_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node, *tmp, *parent, *port;
+	struct lcd *lcd;
+	struct resource *res;
+	int id, ret;
+
+	id = of_alias_get_id(np, "lcd");
+	if (id < 0) {
+		dev_err(dev, "no alias for lcd\n");
+		id = 0;
+	}
+	lcd = devm_kzalloc(dev, sizeof *lcd, GFP_KERNEL);
+	if (!lcd) {
+		dev_err(dev, "failed to allocate private data\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, lcd);
+	lcd->dev = dev;
+	lcd->num = id;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "failed to get memory resource\n");
+		return -EINVAL;
+	}
+
+	lcd->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR(lcd->mmio)) {
+		dev_err(dev, "failed to map registers\n");
+		return PTR_ERR(lcd->mmio);
+	}
+
+	snprintf(lcd->name, sizeof(lcd->name), "sunxi-lcd%d", id);
+
+	/* possible CRTCs */
+	parent = np;
+	tmp = of_get_child_by_name(np, "ports");
+	if (tmp)
+		parent = tmp;
+	port = of_get_child_by_name(parent, "port");
+	of_node_put(tmp);
+	if (!port) {
+		dev_err(dev, "no port node\n");
+		return -ENXIO;
+	}
+	lcd->crtc.port = port;
+
+	lcd->gate = devm_clk_get(dev, "gate");
+	if (IS_ERR(lcd->gate)) {
+		dev_err(dev, "gate clock err %d\n", (int) PTR_ERR(lcd->gate));
+		ret = PTR_ERR(lcd->gate);
+		goto err;
+	}
+
+	lcd->clk = devm_clk_get(dev, "clock");
+	if (IS_ERR(lcd->clk)) {
+		dev_err(dev, "video clock err %d\n", (int) PTR_ERR(lcd->clk));
+		ret = PTR_ERR(lcd->clk);
+		goto err;
+	}
+
+	lcd->rstc = devm_reset_control_get(dev, NULL);
+	if (IS_ERR(lcd->rstc)) {
+		dev_err(dev, "reset controller err %d\n",
+				(int) PTR_ERR(lcd->rstc));
+		ret = PTR_ERR(lcd->rstc);
+		goto err;
+	}
+
+	ret = clk_prepare_enable(lcd->clk);
+	if (ret)
+		goto err;
+	ret = clk_prepare_enable(lcd->gate);
+	if (ret)
+		goto err;
+
+	ret = reset_control_deassert(lcd->rstc);
+	if (ret) {
+		dev_err(dev, "reset deassert err %d\n", ret);
+		goto err;
+	}
+
+	return component_add(&pdev->dev, &de2_lcd_ops);
+
+err:
+	clk_disable_unprepare(lcd->gate);
+	clk_disable_unprepare(lcd->clk);
+	of_node_put(lcd->crtc.port);
+	return ret;
+}
+
+static int de2_lcd_remove(struct platform_device *pdev)
+{
+	struct lcd *lcd = platform_get_drvdata(pdev);
+
+	component_del(&pdev->dev, &de2_lcd_ops);
+	if (!IS_ERR_OR_NULL(lcd->rstc))
+		reset_control_assert(lcd->rstc);
+	clk_disable_unprepare(lcd->gate);
+	clk_disable_unprepare(lcd->clk);
+	of_node_put(lcd->crtc.port);
+
+	return 0;
+}
+
+static const struct of_device_id de2_lcd_ids[] = {
+	{ .compatible = "allwinner,sun8i-h3-lcd", },
+	{ }
+};
+
+struct platform_driver de2_lcd_platform_driver = {
+	.probe = de2_lcd_probe,
+	.remove = de2_lcd_remove,
+	.driver = {
+		.name = "sun8i-h3-lcd",
+		.of_match_table = of_match_ptr(de2_lcd_ids),
+	},
+};
diff --git a/drivers/gpu/drm/sunxi/de2_crtc.h b/drivers/gpu/drm/sunxi/de2_crtc.h
new file mode 100644
index 0000000..afddb86
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_crtc.h
@@ -0,0 +1,42 @@
+#ifndef __DE2_CRTC_H__
+#define __DE2_CRTC_H__
+
+/*
+ * Copyright (C) 2015 Jean-Fran??ois Moine
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <drm/drm_plane_helper.h>
+
+struct priv;
+
+#define N_PLANES 4
+struct lcd {
+	void __iomem *mmio;
+
+	struct device *dev;
+	struct drm_crtc crtc;
+	struct priv *priv;	/* DRM/DE private data */
+
+	short num;		/* LCD index 0/1 */
+	short init_done;
+
+	struct clk *clk;
+	struct clk *gate;
+	struct reset_control *rstc;
+
+	char name[16];
+
+	struct drm_pending_vblank_event *event;
+
+	struct drm_plane planes[N_PLANES];
+};
+
+#define crtc_to_lcd(x) container_of(x, struct lcd, crtc)
+
+#endif /* __DE2_CRTC_H__ */
diff --git a/drivers/gpu/drm/sunxi/de2_de.c b/drivers/gpu/drm/sunxi/de2_de.c
new file mode 100644
index 0000000..92e6e44
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_de.c
@@ -0,0 +1,467 @@
+/*
+ * Allwinner DRM driver - Display Engine 2
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include "de2_drm.h"
+
+static DEFINE_SPINLOCK(de_lock);
+
+/* I/O map */
+
+#define DE_MOD_REG 0x0000	/* 1 bit per LCD */
+#define DE_GATE_REG 0x0004
+#define DE_RESET_REG 0x0008
+#define DE_DIV_REG 0x000c	/* 4 bits per LCD */
+#define DE_SEL_REG 0x0010
+
+#define DE_MUX0_BASE 0x00100000
+#define DE_MUX1_BASE 0x00200000
+
+/* MUX registers (addr / MUX base) */
+#define DE_MUX_GLB_REGS 0x00000		/* global control */
+#define DE_MUX_BLD_REGS 0x01000		/* alpha blending */
+#define DE_MUX_CHAN_REGS 0x02000	/* VI/UI overlay channels */
+#define		DE_MUX_CHAN_SZ 0x1000	/* size of a channel */
+#define DE_MUX_VSU_REGS 0x20000		/* VSU */
+#define DE_MUX_GSU1_REGS 0x40000	/* GSUs */
+#define DE_MUX_GSU2_REGS 0x60000
+#define DE_MUX_GSU3_REGS 0x80000
+#define DE_MUX_FCE_REGS 0xa0000		/* FCE */
+#define DE_MUX_BWS_REGS 0xa2000		/* BWS */
+#define DE_MUX_LTI_REGS 0xa4000		/* LTI */
+#define DE_MUX_PEAK_REGS 0xa6000	/* PEAK */
+#define DE_MUX_ASE_REGS 0xa8000		/* ASE */
+#define DE_MUX_FCC_REGS 0xaa000		/* FCC */
+#define DE_MUX_DCSC_REGS 0xb0000	/* DCSC */
+
+/* global control */
+struct de_glb {
+	u32 ctl;
+#define		DE_MUX_GLB_CTL_rt_en 0x0000001
+#define		DE_MUX_GLB_CTL_finish_irq_en 0x0000010
+#define		DE_MUX_GLB_CTL_rtwb_port 0x0001000
+	u32 status;
+	u32 dbuff;
+	u32 size;
+};
+
+/* alpha blending */
+struct de_bld {
+	u32 fcolor_ctl;			/* 00 */
+	struct {
+		u32 fcolor;
+		u32 insize;
+		u32 offset;
+		u32 dum;
+	} attr[5];
+	u32 dum0[11];
+	u32 route;			/* 80 */
+	u32 premultiply;
+	u32 bkcolor;
+	u32 output_size;
+	u32 bld_mode[4];
+	u32 dum1[4];
+	u32 ck_ctl;			/* b0 */
+	u32 ck_cfg;
+	u32 dum2[2];
+	u32 ck_max[4];
+	u32 dum3[4];
+	u32 ck_min[4];
+	u32 dum4[3];
+	u32 out_ctl;			/* fc */
+};
+
+/* VI channel */
+struct de_vi {
+	struct {
+		u32 attr;
+#define			VI_CFG_ATTR_en 0x00000001
+#define			VI_CFG_ATTR_fcolor_en 0x00000010
+#define			VI_CFG_ATTR_fmt_SHIFT 8
+#define			VI_CFG_ATTR_fmt_MASK 0x00001f00
+#define			VI_CFG_ATTR_sel 0x00008000
+#define			VI_CFG_ATTR_top_down 0x00800000
+		u32 size;
+		u32 coord;
+		u32 pitch[3];
+		u32 top_laddr[3];
+		u32 bot_laddr[3];
+	} cfg[4];
+	u32 fcolor[4];			/* c0 */
+	u32 top_haddr[3];		/* d0 */
+	u32 bot_haddr[3];		/* dc */
+	u32 ovl_size[2];		/* e8 */
+	u32 hori[2];			/* f0 */
+	u32 vert[2];			/* f8 */
+};
+
+/* UI channel */
+struct de_ui {
+	struct {
+		u32 attr;
+#define			UI_CFG_ATTR_en 0x00000001
+#define			UI_CFG_ATTR_alpmod_SHIFT 1
+#define			UI_CFG_ATTR_alpmod_MASK 0x00000006
+#define			UI_CFG_ATTR_fcolor_en 0x00000010
+#define			UI_CFG_ATTR_fmt_SHIFT 8
+#define			UI_CFG_ATTR_fmt_MASK 0x00001f00
+#define			UI_CFG_ATTR_top_down 0x00800000
+#define			UI_CFG_ATTR_alpha_SHIFT 24
+#define			UI_CFG_ATTR_alpha_MASK 0xff000000
+		u32 size;
+		u32 coord;
+		u32 pitch;
+		u32 top_laddr;
+		u32 bot_laddr;
+		u32 fcolor;
+		u32 dum;
+	} cfg[4];			/* 00 */
+	u32 top_haddr;			/* 80 */
+	u32 bot_haddr;
+	u32 ovl_size;			/* 88 */
+};
+
+/* BWS */
+struct de_bws {
+	u32 ctl;
+#define			BWS_WIN_en 0x80000000
+	u32 size;
+	u32 win0;
+	u32 win1;
+};
+
+/* all sizes are ((height - 1) << 16) | (width - 1) */
+#define WH(w, h) (((h - 1) << 16) | (w - 1))
+
+#define DE_CORE_CLK_RATE 432000000
+
+static inline void de_write(struct priv *priv, int reg, u32 data)
+{
+	writel_relaxed(data, priv->mmio + reg);
+}
+
+static inline u32 de_read(struct priv *priv, int reg)
+{
+	return readl_relaxed(priv->mmio + reg);
+}
+
+void de2_de_enable(struct priv *priv, int lcd_num)
+{
+	u32 data;
+	unsigned long flags;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	spin_lock_irqsave(&de_lock, flags);
+
+	/* enable the DE */
+	data = 1 << lcd_num;				/* 1 bit / lcd */
+	de_write(priv, DE_RESET_REG,
+			de_read(priv, DE_RESET_REG) | data);
+	de_write(priv, DE_GATE_REG,
+			de_read(priv, DE_GATE_REG) | data);
+	de_write(priv, DE_MOD_REG,
+			de_read(priv, DE_MOD_REG) | data);
+
+	spin_unlock_irqrestore(&de_lock, flags);
+}
+
+void de2_de_disable(struct priv *priv, int lcd_num)
+{
+	u32 data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&de_lock, flags);
+
+	data = ~(1 << lcd_num);			/* 1 bit */
+	de_write(priv, DE_MOD_REG,
+			de_read(priv, DE_MOD_REG) & data);
+	de_write(priv, DE_GATE_REG,
+			de_read(priv, DE_GATE_REG) & data);
+	de_write(priv, DE_RESET_REG,
+			de_read(priv, DE_RESET_REG) & data);
+
+	spin_unlock_irqrestore(&de_lock, flags);
+}
+
+/* hardware init of the DE, done once */
+void de2_de_hw_init(struct priv *priv, int lcd_num)
+{
+	int mux_o = (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
+	int chan;
+	u32 size = WH(1920, 1080);
+	u32 data;
+	unsigned long flags;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	spin_lock_irqsave(&de_lock, flags);
+
+	/* select the LCD */
+	data = de_read(priv, DE_SEL_REG);
+	if (lcd_num == 0)
+		data &= ~1;
+	else
+		data |= 1;
+	de_write(priv, DE_SEL_REG, data);
+
+	/* start init */
+	{
+		struct de_glb *p_glb = priv->mmio + mux_o +
+					DE_MUX_GLB_REGS;
+
+		p_glb->ctl = DE_MUX_GLB_CTL_rt_en | DE_MUX_GLB_CTL_rtwb_port;
+		p_glb->status = 0;
+		p_glb->dbuff = 1;	/* double register switch */
+		p_glb->size = size;
+	}
+
+	/* set the VI/UIs channels */
+	for (chan = 0; chan < 4; chan++) {
+		int chan_o = mux_o + DE_MUX_CHAN_REGS +
+				DE_MUX_CHAN_SZ * chan;
+
+		if (chan == 0)
+			memset(priv->mmio + chan_o, 0, sizeof(struct de_vi));
+		else
+			memset(priv->mmio + chan_o, 0, sizeof(struct de_ui));
+		if (chan == 2 && lcd_num != 0)
+			break;		/* lcd1 only 1 VI and 1 UI */
+	}
+
+	/* alpha blending */
+	{
+		struct de_bld *p_bld = priv->mmio + mux_o +
+					DE_MUX_BLD_REGS;
+
+		memset(priv->mmio + mux_o + DE_MUX_BLD_REGS, 0, 0x44);
+		p_bld->out_ctl = 0;
+	}
+
+	/* disable the enhancements */
+	{
+		struct ctl {
+			u32 ctl;
+		} *p;
+
+		p = priv->mmio + mux_o + DE_MUX_VSU_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_GSU1_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_GSU2_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_GSU3_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_FCE_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_BWS_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_LTI_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_PEAK_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_ASE_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_FCC_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_DCSC_REGS;
+		p->ctl = 0;
+	}
+
+	spin_unlock_irqrestore(&de_lock, flags);
+}
+
+void de2_de_panel_init(struct priv *priv, int lcd_num,
+			struct drm_display_mode *mode)
+{
+	int mux_o = (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
+	u32 size = WH(mode->hdisplay, mode->vdisplay);
+	u32 data;
+	unsigned long flags;
+
+	DRM_DEBUG_DRIVER("%dx%d\n", mode->hdisplay, mode->vdisplay);
+
+	spin_lock_irqsave(&de_lock, flags);
+
+	/* select the LCD */
+	data = de_read(priv, DE_SEL_REG);
+	if (lcd_num == 0)
+		data &= ~1;
+	else
+		data |= 1;
+	de_write(priv, DE_SEL_REG, data);
+
+	/* start init */
+	{
+		struct de_glb *p_glb = priv->mmio + mux_o +
+					DE_MUX_GLB_REGS;
+
+		p_glb->ctl = DE_MUX_GLB_CTL_rt_en | DE_MUX_GLB_CTL_rtwb_port;
+		p_glb->status = 0;
+		p_glb->dbuff = 1;	/* double register switch */
+		p_glb->size = size;
+	}
+
+	/* alpha blending */
+	{
+		struct de_bld *p_bld = priv->mmio + mux_o +
+					DE_MUX_BLD_REGS;
+
+		p_bld->fcolor_ctl = 0x00000101;
+		p_bld->attr[0].fcolor = 0;
+		p_bld->attr[0].insize = size;
+		p_bld->attr[0].offset = 0;	/* 0, 0 */
+		p_bld->route = 1;
+		p_bld->premultiply = 0;
+		p_bld->bkcolor = 0xff000000;
+		p_bld->output_size = size;
+		p_bld->bld_mode[0] = 0x03010301;	/* SRCOVER */
+		p_bld->bld_mode[1] = 0x03010301;
+		p_bld->bld_mode[2] = 0x03010301;
+		p_bld->out_ctl = 
+			mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 0;
+	}
+
+	spin_unlock_irqrestore(&de_lock, flags);
+}
+
+void de2_de_ui_enable(struct priv *priv,
+			int lcd_num, int channel, int layer,
+			dma_addr_t addr, int fmt,
+			int width, int height, int bpp)
+{
+	int mux_o = (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
+	int chan_o = mux_o + DE_MUX_CHAN_REGS + DE_MUX_CHAN_SZ * channel;
+	u32 pitch = width * bpp / 8;
+	u32 size = WH(width, height);
+	u32 offset = 0;		/* (y << 16) | x */
+	u32 data;
+	unsigned long flags;
+
+	DRM_DEBUG_DRIVER("%d:%d:%d addr: %p\n",
+			lcd_num, channel, layer, (void *) addr);
+
+	switch (fmt) {
+	case DRM_FORMAT_ARGB8888:
+		fmt = 0;	/* DE_FORMAT_ARGB_8888 */
+		break;
+	case DRM_FORMAT_XRGB8888:
+		fmt = 4;	/* DE_FORMAT_XRGB_8888 */
+		break;
+	default:
+		pr_err("format %.4s not yet treated\n", (char *) fmt);
+		return;
+	}
+	
+	spin_lock_irqsave(&de_lock, flags);
+
+	/* select the LCD */
+	data = de_read(priv, DE_SEL_REG);
+	if (lcd_num == 0)
+		data &= ~1;
+	else
+		data |= 1;
+	de_write(priv, DE_SEL_REG, data);
+
+	{
+		struct de_glb *p_glb = priv->mmio + mux_o +
+					DE_MUX_GLB_REGS;
+
+		p_glb->dbuff = 1;	/* double register switch */
+	}
+
+	/* set the UI plane */
+	{
+		struct de_ui *p_ui = priv->mmio + chan_o;
+
+		p_ui->cfg[0].attr = UI_CFG_ATTR_en |
+				(fmt << UI_CFG_ATTR_fmt_SHIFT) |
+				(1 << UI_CFG_ATTR_alpmod_SHIFT) |
+				(0xff << UI_CFG_ATTR_alpha_SHIFT);
+		p_ui->cfg[0].size = size;
+		p_ui->cfg[0].coord = offset;
+		p_ui->cfg[0].pitch = pitch;
+		p_ui->cfg[0].top_laddr = addr;
+		p_ui->ovl_size = size;
+	}
+
+	spin_unlock_irqrestore(&de_lock, flags);
+}
+
+int de2_de_init(struct priv *priv, struct device *dev)
+{
+	struct resource *res;
+	int ret;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	res = platform_get_resource(to_platform_device(dev),
+				IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "failed to get memory resource\n");
+		return -EINVAL;
+	}
+
+	priv->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR(priv->mmio)) {
+		dev_err(dev, "failed to map registers\n");
+		return PTR_ERR(priv->mmio);
+	}
+
+	priv->gate = devm_clk_get(dev, "gate");
+	if (IS_ERR(priv->gate)) {
+		dev_err(dev, "gate clock err %d\n", (int) PTR_ERR(priv->gate));
+		return PTR_ERR(priv->gate);
+	}
+
+	priv->clk = devm_clk_get(dev, "clock");
+	if (IS_ERR(priv->clk)) {
+		dev_err(dev, "video clock err %d\n", (int) PTR_ERR(priv->clk));
+		return PTR_ERR(priv->clk);
+	}
+
+	priv->rstc = devm_reset_control_get(dev, NULL);
+	if (IS_ERR(priv->rstc)) {
+		dev_err(dev, "reset controller err %d\n",
+				(int) PTR_ERR(priv->rstc));
+		return PTR_ERR(priv->rstc);
+	}
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret)
+		return ret;
+	ret = clk_prepare_enable(priv->gate);
+	if (ret)
+		goto err_gate;
+
+	/* set the clock rate */
+	clk_set_rate(priv->clk, DE_CORE_CLK_RATE);
+
+	ret = reset_control_deassert(priv->rstc);
+	if (ret) {
+		dev_err(dev, "reset deassert err %d\n", ret);
+		goto err_reset;
+	}
+
+	return 0;
+
+err_reset:
+	clk_disable_unprepare(priv->gate);
+err_gate:
+	clk_disable_unprepare(priv->clk);
+	return ret;
+}
+
+void de2_de_cleanup(struct priv *priv)
+{
+	reset_control_assert(priv->rstc);
+	clk_disable_unprepare(priv->gate);
+	clk_disable_unprepare(priv->clk);
+}
diff --git a/drivers/gpu/drm/sunxi/de2_drm.h b/drivers/gpu/drm/sunxi/de2_drm.h
new file mode 100644
index 0000000..540ad95
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_drm.h
@@ -0,0 +1,51 @@
+#ifndef __DE2_DRM_H__
+#define __DE2_DRM_H__
+/*
+ * Copyright (C) 2016 Jean-Fran??ois Moine
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <drm/drmP.h>
+#include <drm/drm_fb_cma_helper.h>
+
+struct lcd;
+
+#define N_LCDS 2
+struct priv {
+	void __iomem *mmio;
+	struct clk *clk;
+	struct clk *gate;
+	struct reset_control *rstc;
+
+	struct drm_fbdev_cma *fbdev;
+
+	struct lcd *lcds[N_LCDS];
+};
+
+/* in de2_crtc.c */
+int de2_enable_vblank(struct drm_device *drm, unsigned crtc);
+void de2_disable_vblank(struct drm_device *drm, unsigned crtc);
+extern struct platform_driver de2_lcd_platform_driver;
+
+/* in de2_de.c */
+void de2_de_enable(struct priv *priv, int lcd_num);
+void de2_de_disable(struct priv *priv, int lcd_num);
+void de2_de_hw_init(struct priv *priv, int lcd_num);
+void de2_de_panel_init(struct priv *priv, int lcd_num,
+			struct drm_display_mode *mode);
+int de2_de_init(struct priv *priv, struct device *dev);
+void de2_de_cleanup(struct priv *priv);
+void de2_de_ui_enable(struct priv *priv,
+			int lcd_num, int channel,  int layer,
+			dma_addr_t addr, int fmt,
+			int width, int height, int bpp);
+
+/* in de2_plane.c */
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd);
+
+#endif /* __DE2_DRM_H__ */
diff --git a/drivers/gpu/drm/sunxi/de2_drv.c b/drivers/gpu/drm/sunxi/de2_drv.c
new file mode 100644
index 0000000..e48c1fb
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_drv.c
@@ -0,0 +1,376 @@
+/*
+ * Allwinner DRM driver - DE2 DRM driver
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/of_graph.h>
+#include <linux/component.h>
+#include <drm/drm_of.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "de2_drm.h"
+
+#define DRIVER_NAME	"sunxi-de2"
+#define DRIVER_DESC	"Allwinner DRM DE2"
+#define DRIVER_DATE	"20160101"
+#define DRIVER_MAJOR	1
+#define DRIVER_MINOR	0
+
+static void de2_fb_output_poll_changed(struct drm_device *drm)
+{
+	struct priv *priv = drm->dev_private;
+
+	if (priv->fbdev)
+		drm_fbdev_cma_hotplug_event(priv->fbdev);
+}
+
+static const struct drm_mode_config_funcs de2_mode_config_funcs = {
+	.fb_create = drm_fb_cma_create,
+	.output_poll_changed = de2_fb_output_poll_changed,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+/*
+ * DRM operations:
+ */
+static void de2_lastclose(struct drm_device *drm)
+{
+	struct priv *priv = drm->dev_private;
+
+	if (priv->fbdev)
+		drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static const struct file_operations de2_fops = {
+	.owner		= THIS_MODULE,
+	.open		= drm_open,
+	.release	= drm_release,
+	.unlocked_ioctl	= drm_ioctl,
+	.poll		= drm_poll,
+	.read		= drm_read,
+	.llseek		= no_llseek,
+	.mmap		= drm_gem_cma_mmap,
+};
+
+static struct drm_driver de2_drm_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+					DRIVER_ATOMIC,
+	.lastclose		= de2_lastclose,
+	.get_vblank_counter	= drm_vblank_no_hw_counter,
+	.enable_vblank		= de2_enable_vblank,
+	.disable_vblank		= de2_disable_vblank,
+	.gem_free_object	= drm_gem_cma_free_object,
+	.gem_vm_ops		= &drm_gem_cma_vm_ops,
+	.prime_handle_to_fd	= drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle	= drm_gem_prime_fd_to_handle,
+	.gem_prime_import	= drm_gem_prime_import,
+	.gem_prime_export	= drm_gem_prime_export,
+	.gem_prime_get_sg_table	= drm_gem_cma_prime_get_sg_table,
+	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
+	.gem_prime_vmap		= drm_gem_cma_prime_vmap,
+	.gem_prime_vunmap	= drm_gem_cma_prime_vunmap,
+	.gem_prime_mmap		= drm_gem_cma_prime_mmap,
+	.dumb_create		= drm_gem_cma_dumb_create,
+	.dumb_map_offset	= drm_gem_cma_dumb_map_offset,
+	.dumb_destroy		= drm_gem_dumb_destroy,
+	.fops			= &de2_fops,
+	.name			= DRIVER_NAME,
+	.desc			= DRIVER_DESC,
+	.date			= DRIVER_DATE,
+	.major			= DRIVER_MAJOR,
+	.minor			= DRIVER_MINOR,
+};
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * Power management
+ */
+static int de2_pm_suspend(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+
+	drm_kms_helper_poll_disable(drm);
+	return 0;
+}
+
+static int de2_pm_resume(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+
+	drm_kms_helper_poll_enable(drm);
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops de2_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(de2_pm_suspend, de2_pm_resume)
+};
+
+/*
+ * Platform driver
+ */
+
+static int de2_drm_bind(struct device *dev)
+{
+	struct drm_device *drm;
+	struct priv *priv;
+	int ret;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	drm = drm_dev_alloc(&de2_drm_driver, dev);
+	if (!drm)
+		return -ENOMEM;
+
+	ret = drm_dev_set_unique(drm, dev_name(dev));
+	if (ret < 0)
+		goto out1;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		dev_err(dev, "failed to allocate private area\n");
+		ret = -ENOMEM;
+		goto out1;
+	}
+
+	dev_set_drvdata(dev, drm);
+	drm->dev_private = priv;
+
+	drm_mode_config_init(drm);
+	drm->mode_config.min_width = 640;
+	drm->mode_config.min_height = 480;
+	drm->mode_config.max_width = 1920;
+	drm->mode_config.max_height = 1080;
+	drm->mode_config.funcs = &de2_mode_config_funcs;
+
+	ret = drm_dev_register(drm, 0);
+	if (ret < 0)
+		goto out2;
+
+	/* initialize the display engine */
+	ret = de2_de_init(priv, dev);
+	if (ret)
+		goto out3;
+
+	/* start the subdevices */
+	ret = component_bind_all(dev, drm);
+	if (ret < 0)
+		goto out3;
+
+	DRM_DEBUG_DRIVER("%d crtcs %d connectors\n",
+			 drm->mode_config.num_crtc,
+			 drm->mode_config.num_connector);
+
+	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
+	if (ret < 0)
+		dev_warn(dev, "failed to initialize vblank\n");
+
+	drm_mode_config_reset(drm);
+
+	priv->fbdev = drm_fbdev_cma_init(drm,
+					32,	/* bpp */
+					drm->mode_config.num_crtc,
+					drm->mode_config.num_connector);
+	if (IS_ERR(priv->fbdev)) {
+		ret = PTR_ERR(priv->fbdev);
+		priv->fbdev = NULL;
+		goto out4;
+	}
+
+	drm_kms_helper_poll_init(drm);
+
+	return 0;
+
+out4:
+	component_unbind_all(dev, drm);
+out3:
+	drm_dev_unregister(drm);
+out2:
+	kfree(priv);
+out1:
+	drm_dev_unref(drm);
+	return ret;
+}
+
+static void de2_drm_unbind(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+	struct priv *priv = drm->dev_private;
+
+	if (priv)
+		drm_fbdev_cma_fini(priv->fbdev);
+
+	drm_kms_helper_poll_fini(drm);
+
+	component_unbind_all(dev, drm);
+
+	drm_dev_unregister(drm);
+
+	drm_mode_config_cleanup(drm);
+
+	if (priv) {
+		de2_de_cleanup(priv);
+		kfree(priv);
+	}
+
+	drm_dev_unref(drm);
+}
+
+static const struct component_master_ops de2_drm_comp_ops = {
+	.bind = de2_drm_bind,
+	.unbind = de2_drm_unbind,
+};
+
+static int compare_of(struct device *dev, void *data)
+{
+	return dev->of_node == data;
+}
+
+static int de2_drm_add_components(struct device *dev,
+				  int (*compare_of)(struct device *, void *),
+				  const struct component_master_ops *m_ops)
+{
+	struct device_node *ep, *port, *remote;
+	struct component_match *match = NULL;
+	int i;
+
+	if (!dev->of_node)
+		return -EINVAL;
+
+	/* bind the CRTCs */
+	for (i = 0; ; i++) {
+		port = of_parse_phandle(dev->of_node, "ports", i);
+		if (!port)
+			break;
+
+		if (!of_device_is_available(port->parent)) {
+			of_node_put(port);
+			continue;
+		}
+
+		component_match_add(dev, &match, compare_of, port->parent);
+		of_node_put(port);
+	}
+
+	if (i == 0) {
+		dev_err(dev, "missing 'ports' property\n");
+		return -ENODEV;
+	}
+	if (!match) {
+		dev_err(dev, "no available port\n");
+		return -ENODEV;
+	}
+
+	for (i = 0; ; i++) {
+		port = of_parse_phandle(dev->of_node, "ports", i);
+		if (!port)
+			break;
+
+		if (!of_device_is_available(port->parent)) {
+			of_node_put(port);
+			continue;
+		}
+
+		for_each_child_of_node(port, ep) {
+			remote = of_graph_get_remote_port_parent(ep);
+			if (!remote || !of_device_is_available(remote)) {
+				of_node_put(remote);
+				continue;
+			}
+			if (!of_device_is_available(remote->parent)) {
+				dev_warn(dev, "parent device of %s is not available\n",
+					 remote->full_name);
+				of_node_put(remote);
+				continue;
+			}
+
+			component_match_add(dev, &match, compare_of, remote);
+			of_node_put(remote);
+		}
+		of_node_put(port);
+	}
+
+	return component_master_add_with_match(dev, m_ops, match);
+}
+
+static int de2_drm_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = de2_drm_add_components(&pdev->dev,
+				     compare_of,
+				     &de2_drm_comp_ops);
+	if (ret == -EINVAL)
+		ret = -ENXIO;
+	return ret;
+}
+
+static int de2_drm_remove(struct platform_device *pdev)
+{
+	component_master_del(&pdev->dev, &de2_drm_comp_ops);
+
+	return 0;
+}
+
+static struct of_device_id de2_drm_of_match[] = {
+	{ .compatible = "allwinner,sun8i-h3-display-engine" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, de2_drm_of_match);
+
+static struct platform_driver de2_drm_platform_driver = {
+	.probe      = de2_drm_probe,
+	.remove     = de2_drm_remove,
+	.driver     = {
+		.name = "sun8i-h3-display-engine",
+		.pm = &de2_pm_ops,
+		.of_match_table = de2_drm_of_match,
+	},
+};
+
+static int __init de2_drm_init(void)
+{
+	int ret;
+
+/* uncomment to activate the drm traces at startup time */
+/*	drm_debug = DRM_UT_CORE | DRM_UT_DRIVER | DRM_UT_KMS |
+			DRM_UT_PRIME | DRM_UT_ATOMIC; */
+	drm_debug = DRM_UT_DRIVER;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	ret = platform_driver_register(&de2_lcd_platform_driver);
+	if (ret < 0)
+		return ret;
+
+	ret = platform_driver_register(&de2_drm_platform_driver);
+	if (ret < 0)
+		platform_driver_unregister(&de2_lcd_platform_driver);
+
+	return ret;
+}
+
+static void __exit de2_drm_fini(void)
+{
+	platform_driver_unregister(&de2_lcd_platform_driver);
+	platform_driver_unregister(&de2_drm_platform_driver);
+}
+
+module_init(de2_drm_init);
+module_exit(de2_drm_fini);
+
+MODULE_AUTHOR("Jean-Francois Moine <moinejf@free.fr>");
+MODULE_DESCRIPTION("Allwinner DE2 DRM Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sunxi/de2_hdmi.c b/drivers/gpu/drm/sunxi/de2_hdmi.c
new file mode 100644
index 0000000..1966733
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_hdmi.c
@@ -0,0 +1,381 @@
+/*
+ * Allwinner DRM driver - HDMI
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/component.h>
+#include <linux/clk.h>
+#include <linux/hdmi.h>
+#include <linux/of_graph.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+
+#include "de2_hdmi.h"
+#include "de2_hdmi_h3.h"
+
+#define conn_to_priv(x) \
+	container_of(x, struct de2_hdmi_priv, connector)
+
+#define enc_to_priv(x) \
+	container_of(x, struct de2_hdmi_priv, encoder)
+
+/* --- encoder functions --- */
+
+static bool de2_hdmi_encoder_mode_fixup(struct drm_encoder *encoder,
+				  const struct drm_display_mode *mode,
+				  struct drm_display_mode *adjusted_mode)
+{
+	DRM_DEBUG_DRIVER("\n");
+
+	return true;
+}
+
+static void de2_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+					struct drm_display_mode *mode,
+					struct drm_display_mode *adjusted_mode)
+{
+	struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+
+	DRM_DEBUG_DRIVER("\n");
+
+	priv->cea_mode = drm_match_cea_mode(mode);
+
+	clk_set_rate(priv->clk, mode->clock * 1000);
+
+	mutex_lock(&priv->mutex);
+	bsp_hdmi_video(priv);
+	mutex_unlock(&priv->mutex);
+}
+
+static void de2_hdmi_encoder_enable(struct drm_encoder *encoder)
+{ 
+	struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+
+	DRM_DEBUG_DRIVER("\n");
+
+	mutex_lock(&priv->mutex);
+	bsp_hdmi_set_video_en(priv, 1);
+	mutex_unlock(&priv->mutex);
+}
+
+static void de2_hdmi_encoder_disable(struct drm_encoder *encoder)
+{ 
+	struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+
+	mutex_lock(&priv->mutex);
+	bsp_hdmi_set_video_en(priv, 0);
+	mutex_unlock(&priv->mutex);
+}
+
+static const struct drm_encoder_helper_funcs de2_hdmi_encoder_helper_funcs = {
+	.mode_fixup = de2_hdmi_encoder_mode_fixup,
+	.mode_set = de2_hdmi_encoder_mode_set,
+	.enable = de2_hdmi_encoder_enable,
+	.disable = de2_hdmi_encoder_disable,
+};
+
+static const struct drm_encoder_funcs de2_hdmi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+/* --- connector functions --- */
+
+static int de2_hdmi_connector_mode_valid(struct drm_connector *connector,
+					struct drm_display_mode *mode)
+{
+	if (!drm_match_cea_mode(mode))
+		return MODE_NOMODE;
+	return MODE_OK;
+}
+
+static enum drm_connector_status de2_hdmi_connector_detect(
+				struct drm_connector *connector, bool force)
+{
+	struct de2_hdmi_priv *priv = conn_to_priv(connector);
+	int ret;
+
+	mutex_lock(&priv->mutex);
+	ret = bsp_hdmi_get_hpd(priv);
+	mutex_unlock(&priv->mutex);
+
+	return ret ? connector_status_connected :
+			connector_status_disconnected;
+}
+
+static int read_edid_block(void *data, u8 *buf,
+			unsigned int blk, size_t length)
+{
+	struct de2_hdmi_priv *priv = data;
+	int ret;
+
+	mutex_lock(&priv->mutex);
+	ret = bsp_hdmi_ddc_read(priv,
+				6,
+				blk / 2, (blk & 1) ? 128 : 0,
+				length, buf);
+	mutex_unlock(&priv->mutex);
+
+	return ret;
+}
+
+static int de2_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+	struct de2_hdmi_priv *priv = conn_to_priv(connector);
+	struct edid *edid;
+	int n;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	edid = drm_do_get_edid(connector, read_edid_block, priv);
+
+	if (!edid) {
+		dev_warn(priv->dev, "failed to read EDID\n");
+		return 0;
+	}
+
+	drm_mode_connector_update_edid_property(connector, edid);
+	n = drm_add_edid_modes(connector, edid);
+	priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid);
+
+	DRM_DEBUG_DRIVER("%s EDID ok %d modes\n",
+		priv->is_hdmi_sink ? "HDMI" : "DVI", n);
+
+	kfree(edid);
+
+	return n;
+}
+
+static struct drm_encoder *
+de2_hdmi_connector_best_encoder(struct drm_connector *connector)
+{
+	struct de2_hdmi_priv *priv = conn_to_priv(connector);
+
+	DRM_DEBUG_DRIVER("\n");
+
+	return &priv->encoder;
+}
+
+static const
+struct drm_connector_helper_funcs de2_hdmi_connector_helper_funcs = {
+	.get_modes = de2_hdmi_connector_get_modes,
+	.mode_valid = de2_hdmi_connector_mode_valid,
+	.best_encoder = de2_hdmi_connector_best_encoder,
+};
+
+static void de2_hdmi_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs de2_hdmi_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.reset = drm_atomic_helper_connector_reset,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = de2_hdmi_connector_detect,
+	.destroy = de2_hdmi_connector_destroy,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static void de2_hdmi_cleanup(struct de2_hdmi_priv *priv)
+{
+	if (!IS_ERR_OR_NULL(priv->rstc1))
+		reset_control_assert(priv->rstc1);
+	if (!IS_ERR_OR_NULL(priv->rstc0))
+		reset_control_assert(priv->rstc0);
+	clk_disable_unprepare(priv->gate);
+	clk_disable_unprepare(priv->clk_ddc);
+	clk_disable_unprepare(priv->clk);
+}
+
+static int de2_hdmi_bind(struct device *dev, struct device *master, void *data)
+{
+	struct drm_device *drm = data;
+	struct de2_hdmi_priv *priv = dev_get_drvdata(dev);
+	struct drm_encoder *encoder = &priv->encoder;
+	struct drm_connector *connector = &priv->connector;
+	int ret;
+
+	encoder->possible_crtcs =
+			drm_of_find_possible_crtcs(drm, dev->of_node);
+
+	/* if no CRTC, delay */
+	if (encoder->possible_crtcs == 0)
+		return -EPROBE_DEFER;
+
+	/* HDMI init */
+	ret = clk_prepare_enable(priv->clk);
+	if (ret)
+		goto err;
+	ret = clk_prepare_enable(priv->clk_ddc);
+	if (ret)
+		goto err;
+	ret = clk_prepare_enable(priv->gate);
+	if (ret)
+		goto err;
+	ret = reset_control_deassert(priv->rstc0);
+	if (ret)
+		goto err;
+	ret = reset_control_deassert(priv->rstc1);
+	if (ret)
+		goto err;
+
+	mutex_lock(&priv->mutex);
+	bsp_hdmi_init(priv);
+	bsp_hdmi_hrst(priv);		/* hpd reset */
+	mutex_unlock(&priv->mutex);
+
+	/* encoder init */
+	ret = drm_encoder_init(drm, encoder, &de2_hdmi_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS);
+	if (ret)
+		goto err;
+
+	drm_encoder_helper_add(encoder, &de2_hdmi_encoder_helper_funcs);
+
+	/* connector init */
+	ret = drm_connector_init(drm, connector,
+				 &de2_hdmi_connector_funcs,
+				 DRM_MODE_CONNECTOR_HDMIA);
+	if (ret)
+		goto err_connector;
+
+	connector->interlace_allowed = 1;
+	connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+				 DRM_CONNECTOR_POLL_DISCONNECT;
+	drm_connector_helper_add(connector,
+				 &de2_hdmi_connector_helper_funcs);
+
+	ret = drm_connector_register(connector);
+	if (ret)
+		goto err_register;
+
+	drm_mode_connector_attach_encoder(connector, encoder);
+
+	return 0;
+
+err_register:
+	drm_connector_cleanup(connector);
+err_connector:
+	drm_encoder_cleanup(encoder);
+err:
+	de2_hdmi_cleanup(priv);
+	DRM_DEBUG_DRIVER("err %d\n", ret);
+	return ret;
+}
+
+static void de2_hdmi_unbind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct de2_hdmi_priv *priv = dev_get_drvdata(dev);
+
+	drm_connector_unregister(&priv->connector);
+	drm_connector_cleanup(&priv->connector);
+	drm_encoder_cleanup(&priv->encoder);
+	de2_hdmi_cleanup(priv);
+}
+
+static const struct component_ops de2_hdmi_ops = {
+	.bind = de2_hdmi_bind,
+	.unbind = de2_hdmi_unbind,
+};
+
+static int de2_hdmi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct de2_hdmi_priv *priv;
+	struct resource *res;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, priv);
+	priv->dev = dev;
+
+	mutex_init(&priv->mutex);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "failed to get memory resource\n");
+		return -EINVAL;
+	}
+	priv->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR(priv->mmio)) {
+		dev_err(dev, "failed to map registers\n");
+		return PTR_ERR(priv->mmio);
+	}
+
+	priv->gate = devm_clk_get(dev, "gate");
+	if (IS_ERR(priv->gate)) {
+		dev_err(dev, "gate clock err %d\n",
+					(int) PTR_ERR(priv->gate));
+		return PTR_ERR(priv->gate);
+	}
+	priv->clk = devm_clk_get(dev, "clock");
+	if (IS_ERR(priv->clk)) {
+		dev_err(dev, "hdmi clock err %d\n",
+					(int) PTR_ERR(priv->clk));
+		return PTR_ERR(priv->clk);
+	}
+	priv->clk_ddc = devm_clk_get(dev, "ddc-clock");
+	if (IS_ERR(priv->clk_ddc)) {
+		dev_err(dev, "hdmi-ddc clock err %d\n",
+					(int) PTR_ERR(priv->clk_ddc));
+		return PTR_ERR(priv->clk_ddc);
+	}
+	priv->rstc0 = devm_reset_control_get(dev, "hdmi0");
+	if (IS_ERR(priv->rstc0)) {
+		dev_err(dev, "reset controller err %d\n",
+				(int) PTR_ERR(priv->rstc0));
+		return PTR_ERR(priv->rstc0);
+	}
+	priv->rstc1 = devm_reset_control_get(dev, "hdmi1");
+	if (IS_ERR(priv->rstc1)) {
+		dev_err(dev, "reset controller err %d\n",
+				(int) PTR_ERR(priv->rstc1));
+		return PTR_ERR(priv->rstc1);
+	}
+
+	return component_add(dev, &de2_hdmi_ops);
+}
+
+static int de2_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &de2_hdmi_ops);
+
+	return 0;
+}
+
+static const struct of_device_id de2_hdmi_dt_ids[] = {
+	{ .compatible = "allwinner,sun8i-h3-hdmi", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, de2_hdmi_dt_ids);
+
+static struct platform_driver de2_hdmi_driver = {
+	.probe = de2_hdmi_probe,
+	.remove = de2_hdmi_remove,
+	.driver = {
+		.name = "sun8i-h3-hdmi",
+		.of_match_table = of_match_ptr(de2_hdmi_dt_ids),
+	},
+};
+
+module_platform_driver(de2_hdmi_driver);
+
+MODULE_AUTHOR("Jean-Francois Moine <moinejf@free.fr>");
+MODULE_DESCRIPTION("Allwinner DE2 HDMI encoder/connector");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sunxi/de2_hdmi.h b/drivers/gpu/drm/sunxi/de2_hdmi.h
new file mode 100644
index 0000000..840a4c8
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_hdmi.h
@@ -0,0 +1,34 @@
+#ifndef __DE2_HDMI_H__
+#define __DE2_HDMI_H__
+/*
+ * Copyright (C) 2015 Jean-Fran??ois Moine
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <drm/drmP.h>
+
+struct de2_hdmi_priv {
+	struct device *dev;
+	void __iomem *mmio;
+
+	struct drm_encoder encoder;
+	struct drm_connector connector;
+
+	struct clk *clk;
+	struct clk *clk_ddc;
+	struct clk *gate;
+	struct reset_control *rstc0;
+	struct reset_control *rstc1;
+
+	struct mutex mutex;
+	u8 cea_mode;
+	bool is_hdmi_sink;
+	bool is_yuv;
+};
+
+#endif /* __DE2_HDMI_H__ */
diff --git a/drivers/gpu/drm/sunxi/de2_hdmi_h3.c b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
new file mode 100644
index 0000000..c54b090
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
@@ -0,0 +1,478 @@
+/*
+ * Allwinner H3 HDMI lowlevel functions
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * Adapted from the file
+ *	lichee/linux-3.4/drivers/video/sunxi/disp2/hdmi/aw/hdmi_bsp_sun8iw7.c
+ * with no license nor copyright.
+ */
+
+#include <drm/drmP.h>
+
+#include "de2_hdmi.h"
+#include "de2_hdmi_h3.h"
+
+struct para_tab {
+	u32 para[19];
+};
+
+struct pcm_sf {
+	u32 	sf;
+	unsigned char	cs_sf;
+};
+
+/*
+ * [0] = vic (cea Video ID)
+ * [1] used in hdmi_phy_set / bsp_hdmi_audio
+ * [2..17] used in bsp_hdmi_video
+ */
+static const struct para_tab ptbl[] = {
+	{{  6,  1, 1,  1,  5,  3, 0, 1, 4, 0, 0, 160,  20,  38, 124, 240, 22, 0, 0}},
+	{{ 21, 11, 1,  1,  5,  3, 1, 1, 2, 0, 0, 160,  32,  24, 126,  32, 24, 0, 0}},
+	{{  2, 11, 0,  0,  2,  6, 1, 0, 9, 0, 0, 208, 138,  16,  62, 224, 45, 0, 0}},
+	{{ 17, 11, 0,  0,  2,  5, 2, 0, 5, 0, 0, 208, 144,  12,  64,  64, 49, 0, 0}},
+	{{ 19,  4, 0, 96,  5,  5, 2, 2, 5, 1, 0,   0, 188, 184,  40, 208, 30, 1, 1}},
+	{{  4,  4, 0, 96,  5,  5, 2, 1, 5, 0, 0,   0, 114, 110,  40, 208, 30, 1, 1}},
+	{{ 20,  4, 0, 97,  7,  5, 4, 2, 2, 2, 0, 128, 208,  16,  44,  56, 22, 1, 1}},
+	{{  5,  4, 0, 97,  7,  5, 4, 1, 2, 0, 0, 128,  24,  88,  44,  56, 22, 1, 1}},
+	{{ 31,  2, 0, 96,  7,  5, 4, 2, 4, 2, 0, 128, 208,  16,  44,  56, 45, 1, 1}},
+	{{ 16,  2, 0, 96,  7,  5, 4, 1, 4, 0, 0, 128,  24,  88,  44,  56, 45, 1, 1}},
+	{{ 32,  4, 0, 96,  7,  5, 4, 3, 4, 2, 0, 128,  62, 126,  44,  56, 45, 1, 1}},
+	{{ 33,  4, 0,  0,  7,  5, 4, 2, 4, 2, 0, 128, 208,  16,  44,  56, 45, 1, 1}},
+	{{ 34,  4, 0,  0,  7,  5, 4, 1, 4, 0, 0, 128,  24,  88,  44,  56, 45, 1, 1}},
+	{{160,  2, 0, 96,  7,  5, 8, 3, 4, 2, 0, 128,  62, 126,  44, 157, 45, 1, 1}},
+	{{147,  2, 0, 96,  5,  5, 5, 2, 5, 1, 0,   0, 188, 184,  40, 190, 30, 1, 1}},
+	{{132,  2, 0, 96,  5,  5, 5, 1, 5, 0, 0,   0, 114, 110,  40, 160, 30, 1, 1}},
+	{{257,  1, 0, 96, 15, 10, 8, 2, 8, 0, 0,   0,  48, 176,  88, 112, 90, 1, 1}},
+	{{258,  1, 0, 96, 15, 10, 8, 5, 8, 4, 0,   0, 160,  32,  88, 112, 90, 1, 1}},
+};
+
+static inline void hdmi_writeb(struct de2_hdmi_priv *priv,
+			u32 addr, u8 data)
+{
+	writeb_relaxed(data, priv->mmio + addr);
+}
+
+static inline void hdmi_writel(struct de2_hdmi_priv *priv,
+			u32 addr, u32 data)
+{
+	writel_relaxed(data, priv->mmio + addr);
+}
+
+static inline u8 hdmi_readb(struct de2_hdmi_priv *priv,
+			u32 addr)
+{
+	return readb_relaxed(priv->mmio + addr);
+}
+
+static inline u32 hdmi_readl(struct de2_hdmi_priv *priv,
+			u32 addr)
+{
+	return readl_relaxed(priv->mmio + addr);
+}
+
+static void hdmi_phy_init(struct de2_hdmi_priv *priv)
+{
+	int to_cnt;
+	u32 tmp;
+
+	hdmi_writel(priv, 0x10020, 0);
+	hdmi_writel(priv, 0x10020, 1 << 0);
+	udelay(5);
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 16));
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 1));
+	udelay(10);
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 2));
+	udelay(5);
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 3));
+	udelay(40);
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 19));
+	udelay(100);
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 18));
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (7 << 4));
+
+	to_cnt = 10;
+	while (1) {
+		if ((hdmi_readl(priv, 0x10038) & 0x80) == 0x80)
+			break;
+		udelay(200);
+		if (--to_cnt == 0) {
+			pr_warn("hdmi phy init timeout\n");
+			break;
+		}
+	}
+
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (0xf << 8));
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 7));
+
+	hdmi_writel(priv, 0x1002c, 0x39dc5040);
+	hdmi_writel(priv, 0x10030, 0x80084343);
+	msleep(10);
+	hdmi_writel(priv, 0x10034, 0x00000001);
+	hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | 0x02000000);
+	msleep(100);
+	tmp = hdmi_readl(priv, 0x10038);
+	hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | 0xc0000000);
+	hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+						((tmp >> 11) & 0x3f));
+	hdmi_writel(priv, 0x10020, 0x01ff0f7f);
+	hdmi_writel(priv, 0x10024, 0x80639000);
+	hdmi_writel(priv, 0x10028, 0x0f81c405);
+}
+
+static int get_vid(u32 id)
+{
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(ptbl); i++) {
+		if (id == ptbl[i].para[0])
+			return i;
+	}
+
+	return -1;
+}
+
+static int hdmi_phy_set(struct de2_hdmi_priv *priv, int i)
+{
+	u32 tmp;
+
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) & ~0xf000);
+	switch (ptbl[i].para[1]) {
+	case 1:
+
+		hdmi_writel(priv, 0x1002c, 0x31dc5fc0);	/* or 0x30dc5fc0 ? */
+		hdmi_writel(priv, 0x10030, 0x800863c0);
+		msleep(10);
+		hdmi_writel(priv, 0x10034, 0x00000001);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+						0x02000000);
+		msleep(200);
+		tmp = hdmi_readl(priv, 0x10038);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0xc0000000);
+		if (((tmp >> 11) & 0x3f) < 0x3d)
+			hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+					(((tmp >> 11) & 0x3f) + 2));
+		else
+			hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+								0x3f);
+		msleep(100);
+		hdmi_writel(priv, 0x10020, 0x01ffff7f);
+		hdmi_writel(priv, 0x10024, 0x8063b000);
+		hdmi_writel(priv, 0x10028, 0x0f8246b5);
+		break;
+	case 2:				/* 1080P @ 60 & 50 */
+		hdmi_writel(priv, 0x1002c, 0x39dc5040);
+		hdmi_writel(priv, 0x10030, 0x80084381);
+		msleep(10);
+		hdmi_writel(priv, 0x10034, 0x00000001);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0x02000000);
+		msleep(100);
+		tmp = hdmi_readl(priv, 0x10038);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0xc0000000);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+						((tmp >> 11) & 0x3f));
+		hdmi_writel(priv, 0x10020, 0x01ffff7f);
+		hdmi_writel(priv, 0x10024, 0x8063a800);
+		hdmi_writel(priv, 0x10028, 0x0f81c485);
+		break;
+	case 4:				/* 720P @ 50 & 60, 1080I, 1080P */
+		hdmi_writel(priv, 0x1002c, 0x39dc5040);
+		hdmi_writel(priv, 0x10030, 0x80084343);
+		msleep(10);
+		hdmi_writel(priv, 0x10034, 0x00000001);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0x02000000);
+		msleep(100);
+		tmp = hdmi_readl(priv, 0x10038);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0xc0000000);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+						((tmp >> 11) & 0x3f));
+		hdmi_writel(priv, 0x10020, 0x01ffff7f);
+		hdmi_writel(priv, 0x10024, 0x8063b000);
+		hdmi_writel(priv, 0x10028, 0x0f81c405);
+		break;
+	case 11:				/* 480P/576P */
+		hdmi_writel(priv, 0x1002c, 0x39dc5040);
+		hdmi_writel(priv, 0x10030, 0x8008430a);
+		msleep(10);
+		hdmi_writel(priv, 0x10034, 0x00000001);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0x02000000);
+		msleep(100);
+		tmp = hdmi_readl(priv, 0x10038);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0xc0000000);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+						((tmp >> 11) & 0x3f));
+		hdmi_writel(priv, 0x10020, 0x01ffff7f);
+		hdmi_writel(priv, 0x10024, 0x8063b000);
+		hdmi_writel(priv, 0x10028, 0x0f81c405);
+		break;
+	default:
+		return -1;
+	}
+	return 0;
+}
+
+static void bsp_hdmi_inner_init(struct de2_hdmi_priv *priv)
+{
+	hdmi_writeb(priv, 0x10010, 0x45);
+	hdmi_writeb(priv, 0x10011, 0x45);
+	hdmi_writeb(priv, 0x10012, 0x52);
+	hdmi_writeb(priv, 0x10013, 0x54);
+	hdmi_writeb(priv, 0x8080,  0x00);
+	udelay(1);
+	hdmi_writeb(priv, 0xf01f, 0x00);
+	hdmi_writeb(priv, 0x8403, 0xff);
+	hdmi_writeb(priv, 0x904c, 0xff);
+	hdmi_writeb(priv, 0x904e, 0xff);
+	hdmi_writeb(priv, 0xd04c, 0xff);
+	hdmi_writeb(priv, 0x8250, 0xff);
+	hdmi_writeb(priv, 0x8a50, 0xff);
+	hdmi_writeb(priv, 0x8272, 0xff);
+	hdmi_writeb(priv, 0x40c0, 0xff);
+	hdmi_writeb(priv, 0x86f0, 0xff);
+	hdmi_writeb(priv, 0x0ee3, 0xff);
+	hdmi_writeb(priv, 0x8ee2, 0xff);
+	hdmi_writeb(priv, 0xa049, 0xf0);
+	hdmi_writeb(priv, 0xb045, 0x1e);
+	hdmi_writeb(priv, 0x00c1, 0x00);
+	hdmi_writeb(priv, 0x00c1, 0x03);
+	hdmi_writeb(priv, 0x00c0, 0x00);
+	hdmi_writeb(priv, 0x40c1, 0x10);
+	hdmi_writeb(priv, 0x0010, 0xff);
+	hdmi_writeb(priv, 0x0011, 0xff);
+	hdmi_writeb(priv, 0x8010, 0xff);
+	hdmi_writeb(priv, 0x8011, 0xff);
+	hdmi_writeb(priv, 0x0013, 0xff);
+	hdmi_writeb(priv, 0x8012, 0xff);
+	hdmi_writeb(priv, 0x8013, 0xff);
+}
+
+void bsp_hdmi_init(struct de2_hdmi_priv *priv)
+{
+	hdmi_phy_init(priv);
+	bsp_hdmi_inner_init(priv);
+}
+
+void bsp_hdmi_set_video_en(struct de2_hdmi_priv *priv,
+			unsigned char enable)
+{
+	if (enable)
+		hdmi_writel(priv, 0x10020,
+			hdmi_readl(priv, 0x10020) | (0x0f << 12));
+	else
+		hdmi_writel(priv, 0x10020,
+			hdmi_readl(priv, 0x10020) & ~(0x0f << 12));
+}
+
+/* initialize */
+int bsp_hdmi_video(struct de2_hdmi_priv *priv)
+{
+	int i = get_vid(priv->cea_mode);	/* ptbl index */
+	int csc;				/* color space */
+
+	if (i < 0)
+		return i;
+
+	switch (priv->cea_mode) {
+	case 2:				/* 480P */
+	case 6:				/* 1440x480I */
+	case 17:			/* 576P */
+	case 21:			/* 1440x576I */
+		csc = 1;		/* BT601 */
+		break;
+	default:
+		csc = 2;		/* BT709 */
+		break;
+	}
+	if (hdmi_phy_set(priv, i) != 0)
+		return -1;
+
+	bsp_hdmi_inner_init(priv);
+
+	hdmi_writeb(priv, 0x0840, 0x01);
+	hdmi_writeb(priv, 0x4845, 0x00);
+	hdmi_writeb(priv, 0x0040, ptbl[i].para[3] | 0x10);
+	hdmi_writeb(priv, 0x10001, ptbl[i].para[3] < 96 ? 0x03 : 0x00);
+	hdmi_writeb(priv, 0x8040, ptbl[i].para[4]);
+	hdmi_writeb(priv, 0x4043, ptbl[i].para[5]);
+	hdmi_writeb(priv, 0x8042, ptbl[i].para[6]);
+	hdmi_writeb(priv, 0x0042, ptbl[i].para[7]);
+	hdmi_writeb(priv, 0x4042, ptbl[i].para[8]);
+	hdmi_writeb(priv, 0x4041, ptbl[i].para[9]);
+	hdmi_writeb(priv, 0xc041, ptbl[i].para[10]);
+	hdmi_writeb(priv, 0x0041, ptbl[i].para[11]);
+	hdmi_writeb(priv, 0x8041, ptbl[i].para[12]);
+	hdmi_writeb(priv, 0x4040, ptbl[i].para[13]);
+	hdmi_writeb(priv, 0xc040, ptbl[i].para[14]);
+	hdmi_writeb(priv, 0x0043, ptbl[i].para[15]);
+	hdmi_writeb(priv, 0x8043, ptbl[i].para[16]);
+	hdmi_writeb(priv, 0x0045, 0x0c);
+	hdmi_writeb(priv, 0x8044, 0x20);
+	hdmi_writeb(priv, 0x8045, 0x01);
+	hdmi_writeb(priv, 0x0046, 0x0b);
+	hdmi_writeb(priv, 0x0047, 0x16);
+	hdmi_writeb(priv, 0x8046, 0x21);
+	hdmi_writeb(priv, 0x3048, ptbl[i].para[2] ? 0x21 : 0x10);
+	hdmi_writeb(priv, 0x0401, ptbl[i].para[2] ? 0x41 : 0x40);
+	hdmi_writeb(priv, 0x8400, 0x07);
+	hdmi_writeb(priv, 0x8401, 0x00);
+	hdmi_writeb(priv, 0x0402, 0x47);
+	hdmi_writeb(priv, 0x0800, 0x01);
+	hdmi_writeb(priv, 0x0801, 0x07);
+	hdmi_writeb(priv, 0x8800, 0x00);
+	hdmi_writeb(priv, 0x8801, 0x00);
+	hdmi_writeb(priv, 0x0802, 0x00);
+	hdmi_writeb(priv, 0x0803, 0x00);
+	hdmi_writeb(priv, 0x8802, 0x00);
+	hdmi_writeb(priv, 0x8803, 0x00);
+
+	if (priv->is_hdmi_sink) {
+		hdmi_writeb(priv, 0xb045, 0x08);
+		hdmi_writeb(priv, 0x2045, 0x00);
+		hdmi_writeb(priv, 0x2044, 0x0c);
+		hdmi_writeb(priv, 0x6041, 0x03);
+		hdmi_writeb(priv, 0xa044, (ptbl[i].para[0] & 0x100) == 0x100 ?
+					0x20 : (ptbl[i].para[0] & 0x80) == 0x80 ?
+					0x40 :
+					0x00 );
+		hdmi_writeb(priv, 0xa045, (ptbl[i].para[0] & 0x100) == 0x100 ?
+					(ptbl[i].para[0] & 0x7f) : 0x00);
+		hdmi_writeb(priv, 0x2046, 0x00);
+		hdmi_writeb(priv, 0x3046, 0x01);
+		hdmi_writeb(priv, 0x3047, 0x11);
+		hdmi_writeb(priv, 0x4044, 0x00);
+		hdmi_writeb(priv, 0x0052, 0x00);
+		hdmi_writeb(priv, 0x8051, 0x11);
+
+		hdmi_writeb(priv, 0x10010, 0x45);
+		hdmi_writeb(priv, 0x10011, 0x45);
+		hdmi_writeb(priv, 0x10012, 0x52);
+		hdmi_writeb(priv, 0x10013, 0x54);
+		hdmi_writeb(priv, 0x0040, hdmi_readb(priv, 0x0040) | 0x08);
+
+		hdmi_writeb(priv, 0x10010, 0x52);
+		hdmi_writeb(priv, 0x10011, 0x54);
+		hdmi_writeb(priv, 0x10012, 0x41);
+		hdmi_writeb(priv, 0x10013, 0x57);
+		hdmi_writeb(priv, 0x4045, priv->is_yuv ? 0x02 : 0x00);
+		if (ptbl[i].para[17] == 0)
+			hdmi_writeb(priv, 0xc044, (csc << 6) | 0x18);
+		else if (ptbl[i].para[17] == 1)
+			hdmi_writeb(priv, 0xc044, (csc << 6) | 0x28);
+		else
+			hdmi_writeb(priv, 0xc044, (csc << 6) | 0x08);
+
+		hdmi_writeb(priv, 0xc045, priv->is_yuv ? 0x00 : 0x08);
+		hdmi_writeb(priv, 0x4046, ptbl[i].para[0]&0x7f);
+	}
+
+	hdmi_writeb(priv, 0x0082, 0x00);
+	hdmi_writeb(priv, 0x0081, 0x00);
+
+	hdmi_writeb(priv, 0x0840, 0x00);
+
+	return 0;
+}
+
+
+/* get a block of EDID */
+int bsp_hdmi_ddc_read(struct de2_hdmi_priv *priv,
+			char cmd, char pointer, char off,
+			int nbyte, char *pbuf)
+{
+	u32 to_cnt;
+	int ret = 0;
+	
+	hdmi_writeb(priv, 0x10010, 0x45);
+	hdmi_writeb(priv, 0x10011, 0x45);
+	hdmi_writeb(priv, 0x10012, 0x52);
+	hdmi_writeb(priv, 0x10013, 0x54);
+	hdmi_writeb(priv, 0x4ee1, 0x00);
+	to_cnt = 50;
+	while ((hdmi_readb(priv, 0x4ee1) & 0x01) != 0x01) {
+		udelay(10);
+		if (--to_cnt == 0) {	/* wait for 500us for timeout */
+			pr_warn("hdmi ddc reset timeout\n");
+			break;
+		}
+	}
+
+	hdmi_writeb(priv, 0x8ee3, 0x05);
+	hdmi_writeb(priv, 0x0ee3, 0x08);
+	hdmi_writeb(priv, 0x4ee2, 0xd8);
+	hdmi_writeb(priv, 0xcee2, 0xfe);
+
+	while (nbyte > 0) {
+		hdmi_writeb(priv, 0x0ee0, 0xa0 >> 1);	/* DDC addr */
+		hdmi_writeb(priv, 0x0ee1, off);		/* DDC offset */
+		hdmi_writeb(priv, 0x4ee0, 0x60 >> 1);	/* DDC segment addr */
+		hdmi_writeb(priv, 0xcee0, pointer);	/* DDC segment */
+		hdmi_writeb(priv, 0x0ee2, 0x02);	/* start command */
+
+		to_cnt = 50;				/* timeout 100ms */
+		while (1) {
+			if ((hdmi_readb(priv, 0x0013) & 0x02) == 0x02) {
+				hdmi_writeb(priv, 0x0013,
+					hdmi_readb(priv, 0x0013) & 0x02);
+				*pbuf++ = hdmi_readb(priv, 0x8ee1);
+				break;
+			}
+			if ((hdmi_readb(priv, 0x0013) & 0x01) == 0x01) {
+				hdmi_writeb(priv, 0x0013,
+					hdmi_readb(priv, 0x0013) & 0x01);
+				pr_warn("hdmi ddc read error, byte cnt = %d\n",
+					 nbyte);
+				ret = -1;
+				break;
+			}
+			if (--to_cnt == 0) {
+				pr_warn("hdmi ddc read timeout, byte cnt = %d\n",
+					 nbyte);
+				ret = -1;
+				break;
+			}
+			msleep(2);
+		}
+		if (ret)
+			break;
+		nbyte--;
+		off++;
+	}
+	hdmi_writeb(priv, 0x10010, 0x52);
+	hdmi_writeb(priv, 0x10011, 0x54);
+	hdmi_writeb(priv, 0x10012, 0x41);
+	hdmi_writeb(priv, 0x10013, 0x57);
+
+	return ret;
+}
+
+int bsp_hdmi_get_hpd(struct de2_hdmi_priv *priv)
+{
+	int ret;
+
+	hdmi_writeb(priv, 0x10010, 0x45);
+	hdmi_writeb(priv, 0x10011, 0x45);
+	hdmi_writeb(priv, 0x10012, 0x52);
+	hdmi_writeb(priv, 0x10013, 0x54);
+
+	ret = (hdmi_readl(priv, 0x10038) & 0x80000) ? 1 : 0;
+
+	hdmi_writeb(priv, 0x10010, 0x52);
+	hdmi_writeb(priv, 0x10011, 0x54);
+	hdmi_writeb(priv, 0x10012, 0x41);
+	hdmi_writeb(priv, 0x10013, 0x57);
+
+	return ret;
+}
+
+void bsp_hdmi_hrst(struct de2_hdmi_priv *priv)
+{
+	hdmi_writeb(priv, 0x00c1, 0x04);
+}
diff --git a/drivers/gpu/drm/sunxi/de2_hdmi_h3.h b/drivers/gpu/drm/sunxi/de2_hdmi_h3.h
new file mode 100644
index 0000000..59fb6ca
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_hdmi_h3.h
@@ -0,0 +1,14 @@
+#ifndef __DE2_HDMI_H3_H__
+#define __DE2_HDMI_H3_H__
+
+void bsp_hdmi_set_video_en(struct de2_hdmi_priv *priv,
+			unsigned char enable);
+int bsp_hdmi_video(struct de2_hdmi_priv *priv);
+int bsp_hdmi_ddc_read(struct de2_hdmi_priv *priv,
+			char cmd, char pointer, char offset,
+			int nbyte, char *pbuf);
+int bsp_hdmi_get_hpd(struct de2_hdmi_priv *priv);
+void bsp_hdmi_init(struct de2_hdmi_priv *priv);
+void bsp_hdmi_hrst(struct de2_hdmi_priv *priv);
+
+#endif /* __DE2_HDMI_H3_H__ */
diff --git a/drivers/gpu/drm/sunxi/de2_plane.c b/drivers/gpu/drm/sunxi/de2_plane.c
new file mode 100644
index 0000000..53f6c79
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_plane.c
@@ -0,0 +1,102 @@
+/*
+ * Allwinner DRM driver - DE2 Planes
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "de2_drm.h"
+#include "de2_crtc.h"
+
+/* primary plane */
+static const uint32_t primary_formats[] = {
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_XRGB8888,
+};
+
+static void primary_plane_update(struct drm_plane *plane,
+				struct drm_plane_state *old_state)
+{
+	struct drm_plane_state *state = plane->state;
+	struct drm_crtc *crtc = state->crtc;
+	struct lcd *lcd = crtc_to_lcd(crtc);
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_gem_cma_object *gem;
+	int plane_num = plane - lcd->planes;
+	int crtc_x, crtc_y, src_x, src_y;
+	dma_addr_t start;
+
+	if (!crtc || !fb) {
+		DRM_DEBUG_DRIVER("no crtc/fb\n");
+		return;
+	}
+
+	if (!lcd->init_done)
+		return;
+
+	src_x = state->src_x >> 16;
+	src_y = state->src_y >> 16;
+	crtc_x = state->crtc_x;
+	crtc_y = state->crtc_y;
+
+	DRM_DEBUG_DRIVER("%dx%d+%d+%d %.4s\n",
+			state->crtc_w, state->crtc_h, crtc_x, crtc_y,
+			(char *) &fb->pixel_format);
+
+	gem = drm_fb_cma_get_gem_obj(fb, 0);
+
+	start = gem->paddr + fb->offsets[0] +
+			src_y * fb->pitches[0] + src_x;
+
+	if (plane_num == 0) {
+		de2_de_ui_enable(lcd->priv, lcd->num,
+			1, 0,			/* UI 0, layer 0 */
+			start, fb->pixel_format,
+			state->crtc_w,
+			state->crtc_h,
+			fb->bits_per_pixel);
+	} else {
+		DRM_DEBUG_DRIVER("no plane #%d\n", plane_num);
+	}
+}
+
+static const struct drm_plane_helper_funcs primary_plane_helper_funcs = {
+	.atomic_update = primary_plane_update,
+};
+
+static const struct drm_plane_funcs primary_plane_funcs = {
+	.update_plane = drm_primary_helper_update,
+	.disable_plane = drm_primary_helper_disable,
+	.destroy = drm_primary_helper_destroy,
+	.reset = drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd)
+{
+	int ret;
+
+	ret = drm_universal_plane_init(drm, &lcd->planes[0], 0,
+				&primary_plane_funcs,
+				primary_formats, ARRAY_SIZE(primary_formats),
+				DRM_PLANE_TYPE_PRIMARY);
+	if (ret < 0) {
+		dev_err(lcd->dev, "Couldn't initialize primary plane\n");
+		return ret;
+	}
+
+	drm_plane_helper_add(&lcd->planes[0],
+			     &primary_plane_helper_funcs);
+
+	return ret;
+}
-- 
2.6.4

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2
@ 2016-01-05 18:40   ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-05 18:40 UTC (permalink / raw)
  To: Dave Airlie, Maxime Ripard, Chen-Yu Tsai; +Cc: linux-arm-kernel, dri-devel

[-- Attachment #1: Type: text/plain, Size: 65980 bytes --]

In recent SoCs, as the H3, Allwinner uses a new display interface, DE2.
This patch adds a DRM video driver for this interface,
and also a driver for the HDMI connector found in the H3.

Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
---
 drivers/gpu/drm/Kconfig             |   2 +
 drivers/gpu/drm/Makefile            |   1 +
 drivers/gpu/drm/sunxi/Kconfig       |  21 ++
 drivers/gpu/drm/sunxi/Makefile      |   8 +
 drivers/gpu/drm/sunxi/de2_crtc.c    | 409 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_crtc.h    |  42 ++++
 drivers/gpu/drm/sunxi/de2_de.c      | 467 +++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_drm.h     |  51 ++++
 drivers/gpu/drm/sunxi/de2_drv.c     | 376 ++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi.c    | 381 ++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi.h    |  34 +++
 drivers/gpu/drm/sunxi/de2_hdmi_h3.c | 478 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi_h3.h |  14 ++
 drivers/gpu/drm/sunxi/de2_plane.c   | 102 ++++++++
 14 files changed, 2386 insertions(+)
 create mode 100644 drivers/gpu/drm/sunxi/Kconfig
 create mode 100644 drivers/gpu/drm/sunxi/Makefile
 create mode 100644 drivers/gpu/drm/sunxi/de2_crtc.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_crtc.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_de.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_drm.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_drv.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_h3.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_h3.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_plane.c

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index c4bf9a1..edef0c8 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -266,3 +266,5 @@ source "drivers/gpu/drm/amd/amdkfd/Kconfig"
 source "drivers/gpu/drm/imx/Kconfig"
 
 source "drivers/gpu/drm/vc4/Kconfig"
+
+source "drivers/gpu/drm/sunxi/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 1e9ff4c..597c246 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -75,3 +75,4 @@ obj-y			+= i2c/
 obj-y			+= panel/
 obj-y			+= bridge/
 obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
+obj-$(CONFIG_DRM_SUNXI) += sunxi/
diff --git a/drivers/gpu/drm/sunxi/Kconfig b/drivers/gpu/drm/sunxi/Kconfig
new file mode 100644
index 0000000..9b4d414
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/Kconfig
@@ -0,0 +1,21 @@
+#
+# Allwinner Video configuration
+#
+
+config DRM_SUNXI
+	tristate "DRM Support for Allwinner Video"
+	depends on DRM && ARCH_SUNXI
+	depends on OF
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_GEM_CMA_HELPER
+	help
+	  Choose this option if you have a Allwinner chipset.
+
+config DRM_SUNXI_DE2
+	tristate "Support for Allwinner Video with DE2 interface"
+	depends on DRM_SUNXI && MACH_SUN8I
+	select REGMAP_MMIO
+	help
+	  Choose this option if your Allwinner chipset has the DE2 interface
+	  as the H3.
diff --git a/drivers/gpu/drm/sunxi/Makefile b/drivers/gpu/drm/sunxi/Makefile
new file mode 100644
index 0000000..8bb533f
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for Allwinner's DRM device driver
+#
+
+sunxi-de2-drm-objs := de2_drv.o de2_de.o de2_crtc.o de2_plane.o
+sunxi-de2-hdmi-objs := de2_hdmi.o de2_hdmi_h3.o
+
+obj-$(CONFIG_DRM_SUNXI_DE2) += sunxi-de2-drm.o sunxi-de2-hdmi.o
diff --git a/drivers/gpu/drm/sunxi/de2_crtc.c b/drivers/gpu/drm/sunxi/de2_crtc.c
new file mode 100644
index 0000000..92c20d0
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_crtc.c
@@ -0,0 +1,409 @@
+/*
+ * Allwinner DRM driver - DE2 CRTC
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/component.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
+
+#include "de2_drm.h"
+#include "de2_crtc.h"
+
+/* I/O map */
+
+struct tcon {
+	u32 gctl;
+#define		TCON_GCTL_TCON_En 0x80000000
+	u32 gint0;
+#define		TCON_GINT0_TCON1_Vb_Int_En 0x40000000
+#define		TCON_GINT0_TCON1_Vb_Int_Flag 0x00001000
+	u32 gint1;
+	u32 dum0[13];
+	u32 tcon0_ctl;
+#define		TCON0_CTL_TCON_En 0x80000000
+	u32 dum1[19];
+	u32 tcon1_ctl;
+#define		TCON1_CTL_TCON_En 0x80000000
+#define		TCON1_CTL_Interlace_En 0x00100000
+#define		TCON1_CTL_Start_Delay_SHIFT 4
+#define		TCON1_CTL_Start_Delay_MASK 0x000001f0
+	u32 basic0;			/* XI/YI */
+	u32 basic1;			/* LS_XO/LS_YO */
+	u32 basic2;			/* XO/YO */
+	u32 basic3;			/* HT/HBP */
+	u32 basic4;			/* VT/VBP */
+	u32 basic5;			/* HSPW/VSPW */
+	u32 dum2;
+	u32 ps_sync;
+	u32 dum3[15];
+	u32 io_pol;
+#define		TCON1_IO_POL_IO0_inv 0x01000000
+#define		TCON1_IO_POL_IO1_inv 0x02000000
+#define		TCON1_IO_POL_IO2_inv 0x04000000
+	u32 io_tri;
+	u32 dum4[2];
+
+	u32 ceu_ctl;			/* 100 */
+#define     TCON_CEU_CTL_ceu_en 0x80000000
+	u32 dum5[3];
+	u32 ceu_rr;
+	u32 ceu_rg;
+	u32 ceu_rb;
+	u32 ceu_rc;
+	u32 ceu_gr;
+	u32 ceu_gg;
+	u32 ceu_gb;
+	u32 ceu_gc;
+	u32 ceu_br;
+	u32 ceu_bg;
+	u32 ceu_bb;
+	u32 ceu_bc;
+	u32 ceu_rv;
+	u32 ceu_gv;
+	u32 ceu_bv;
+	u32 dum6[45];
+
+	u32 mux_ctl;			/* 200 */
+#define		TCON_MUX_CTL_HDMI_SRC_SHIFT 8
+#define		TCON_MUX_CTL_HDMI_SRC_MASK 0x00000300
+	u32 dum7[63];
+
+	u32 fill_ctl;			/* 300 */
+	u32 fill_start0;
+	u32 fill_end0;
+	u32 fill_data0;
+};
+
+#define XY(x, y) (((x) << 16) | (y))
+
+/*
+ * vertical blank functions
+ */
+int de2_enable_vblank(struct drm_device *drm, unsigned crtc)
+{
+	return 0;
+}
+
+void de2_disable_vblank(struct drm_device *drm, unsigned crtc)
+{
+}
+
+static void de2_set_frame_timings(struct lcd *lcd)
+{
+	struct drm_crtc *crtc = &lcd->crtc;
+	const struct drm_display_mode *mode = &crtc->mode;
+	struct tcon *p_tcon = lcd->mmio;
+	int interlace = mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 1;
+	int start_delay;
+	u32 data;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	data = XY(mode->hdisplay - 1, mode->vdisplay / interlace - 1);
+	p_tcon->basic0 = data;
+	p_tcon->basic1 = data;
+	p_tcon->basic2 = data;
+	p_tcon->basic3 = XY(mode->htotal - 1,
+				mode->htotal - mode->hsync_start - 1);
+	p_tcon->basic4 = XY(mode->vtotal * (3 - interlace),
+				mode->vtotal - mode->vsync_start - 1);
+	p_tcon->basic5 = XY(mode->hsync_end - mode->hsync_start - 1,
+				mode->vsync_end - mode->vsync_start - 1);
+
+	p_tcon->ps_sync = XY(1, 1);
+
+	data = TCON1_IO_POL_IO2_inv;
+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+		data |= TCON1_IO_POL_IO0_inv;
+	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+		data |= TCON1_IO_POL_IO1_inv;
+	p_tcon->io_pol = data;
+
+	p_tcon->ceu_ctl &= ~TCON_CEU_CTL_ceu_en;
+
+	if (interlace == 2)
+		p_tcon->tcon1_ctl |= TCON1_CTL_Interlace_En;
+	else
+		p_tcon->tcon1_ctl &= ~TCON1_CTL_Interlace_En;
+
+	p_tcon->fill_ctl = 0;
+	p_tcon->fill_start0 = mode->vtotal + 1;
+	p_tcon->fill_end0 = mode->vtotal;
+	p_tcon->fill_data0 = 0;
+
+	start_delay = (mode->vtotal - mode->vdisplay) / interlace - 5;
+	if (start_delay > 31)
+		start_delay = 31;
+	p_tcon->tcon1_ctl &= ~TCON1_CTL_Start_Delay_MASK;
+	p_tcon->tcon1_ctl |= start_delay << TCON1_CTL_Start_Delay_SHIFT;
+
+	p_tcon->io_tri = 0x0fffffff;
+}
+
+static void de2_crtc_enable(struct drm_crtc *crtc)
+{
+	struct lcd *lcd = crtc_to_lcd(crtc);
+	struct drm_display_mode *mode = &crtc->mode;
+	struct tcon *p_tcon = lcd->mmio;
+
+	DRM_DEBUG_DRIVER("clock %d\n", mode->clock);
+
+	if (mode->clock == 0) {
+		dev_err(lcd->dev, "crtc_start: no clock!\n");
+		return;
+	}
+
+	de2_set_frame_timings(lcd);
+
+	clk_set_rate(lcd->clk, mode->clock * 1000);
+
+	if (lcd->num == 0) {				/* HDMI */
+		p_tcon->mux_ctl &= ~TCON_MUX_CTL_HDMI_SRC_MASK;
+		p_tcon->mux_ctl |= lcd->num << TCON_MUX_CTL_HDMI_SRC_SHIFT;
+	}
+
+	p_tcon->tcon1_ctl |= TCON1_CTL_TCON_En;
+
+	de2_de_enable(lcd->priv, lcd->num);
+
+	if (!lcd->init_done) {
+		lcd->init_done = 1;
+		de2_de_hw_init(lcd->priv, lcd->num);
+	}
+
+	de2_de_panel_init(lcd->priv, lcd->num, mode);
+
+	drm_mode_debug_printmodeline(mode);
+}
+
+static void de2_crtc_disable(struct drm_crtc *crtc)
+{
+	struct lcd *lcd = crtc_to_lcd(crtc);
+	struct tcon *p_tcon = lcd->mmio;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	de2_de_disable(lcd->priv, lcd->num);
+	p_tcon->tcon1_ctl &= ~TCON1_CTL_TCON_En;
+}
+
+static const struct drm_crtc_funcs de2_crtc_funcs = {
+	.destroy	= drm_crtc_cleanup,
+	.set_config	= drm_atomic_helper_set_config,
+	.page_flip	= drm_atomic_helper_page_flip,
+	.reset		= drm_atomic_helper_crtc_reset,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+static const struct drm_crtc_helper_funcs de2_crtc_helper_funcs = {
+	.enable		= de2_crtc_enable,
+	.disable	= de2_crtc_disable,
+};
+
+static void de2_tcon_init(struct lcd *lcd)
+{
+	struct tcon *p_tcon = lcd->mmio;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	p_tcon->tcon0_ctl &= ~TCON0_CTL_TCON_En;
+	p_tcon->tcon1_ctl &= ~TCON1_CTL_TCON_En;
+	p_tcon->gctl &= ~TCON_GCTL_TCON_En;
+
+	p_tcon->gint0 &= ~(TCON_GINT0_TCON1_Vb_Int_En |
+			TCON_GINT0_TCON1_Vb_Int_Flag);
+
+	p_tcon->gctl |= TCON_GCTL_TCON_En;
+}
+
+static int de2_crtc_init(struct drm_device *drm, struct lcd *lcd)
+{
+	struct drm_crtc *crtc = &lcd->crtc;
+	int ret;
+
+	ret = de2_plane_init(drm, lcd);
+	if (ret < 0)
+		return ret;
+
+	ret = drm_crtc_init_with_planes(drm, crtc,
+					&lcd->planes[0], /* primary plane */
+					NULL,		/* cursor */
+					&de2_crtc_funcs);
+	if (ret < 0)
+		return ret;
+
+	de2_tcon_init(lcd);
+
+	drm_crtc_helper_add(crtc, &de2_crtc_helper_funcs);
+
+	return 0;
+}
+
+/*
+ * device init
+ */
+static int de2_lcd_bind(struct device *dev, struct device *master,
+			void *data)
+{
+	struct drm_device *drm = data;
+	struct priv *priv = drm->dev_private;
+	struct lcd *lcd = dev_get_drvdata(dev);
+	int ret;
+
+	lcd->priv = priv;
+
+	ret = de2_crtc_init(drm, lcd);
+	if (ret < 0) {
+		dev_err(dev, "failed to init the crtc\n");
+		return ret;
+	}
+
+	priv->lcds[lcd->num] = lcd;
+
+	return 0;
+}
+
+static void de2_lcd_unbind(struct device *dev, struct device *master,
+			void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct lcd *lcd = platform_get_drvdata(pdev);
+	struct tcon *p_tcon = lcd->mmio;
+
+	if (p_tcon)
+		p_tcon->gctl &= ~TCON_GCTL_TCON_En;
+}
+
+static const struct component_ops de2_lcd_ops = {
+	.bind = de2_lcd_bind,
+	.unbind = de2_lcd_unbind,
+};
+
+static int de2_lcd_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node, *tmp, *parent, *port;
+	struct lcd *lcd;
+	struct resource *res;
+	int id, ret;
+
+	id = of_alias_get_id(np, "lcd");
+	if (id < 0) {
+		dev_err(dev, "no alias for lcd\n");
+		id = 0;
+	}
+	lcd = devm_kzalloc(dev, sizeof *lcd, GFP_KERNEL);
+	if (!lcd) {
+		dev_err(dev, "failed to allocate private data\n");
+		return -ENOMEM;
+	}
+	dev_set_drvdata(dev, lcd);
+	lcd->dev = dev;
+	lcd->num = id;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "failed to get memory resource\n");
+		return -EINVAL;
+	}
+
+	lcd->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR(lcd->mmio)) {
+		dev_err(dev, "failed to map registers\n");
+		return PTR_ERR(lcd->mmio);
+	}
+
+	snprintf(lcd->name, sizeof(lcd->name), "sunxi-lcd%d", id);
+
+	/* possible CRTCs */
+	parent = np;
+	tmp = of_get_child_by_name(np, "ports");
+	if (tmp)
+		parent = tmp;
+	port = of_get_child_by_name(parent, "port");
+	of_node_put(tmp);
+	if (!port) {
+		dev_err(dev, "no port node\n");
+		return -ENXIO;
+	}
+	lcd->crtc.port = port;
+
+	lcd->gate = devm_clk_get(dev, "gate");
+	if (IS_ERR(lcd->gate)) {
+		dev_err(dev, "gate clock err %d\n", (int) PTR_ERR(lcd->gate));
+		ret = PTR_ERR(lcd->gate);
+		goto err;
+	}
+
+	lcd->clk = devm_clk_get(dev, "clock");
+	if (IS_ERR(lcd->clk)) {
+		dev_err(dev, "video clock err %d\n", (int) PTR_ERR(lcd->clk));
+		ret = PTR_ERR(lcd->clk);
+		goto err;
+	}
+
+	lcd->rstc = devm_reset_control_get(dev, NULL);
+	if (IS_ERR(lcd->rstc)) {
+		dev_err(dev, "reset controller err %d\n",
+				(int) PTR_ERR(lcd->rstc));
+		ret = PTR_ERR(lcd->rstc);
+		goto err;
+	}
+
+	ret = clk_prepare_enable(lcd->clk);
+	if (ret)
+		goto err;
+	ret = clk_prepare_enable(lcd->gate);
+	if (ret)
+		goto err;
+
+	ret = reset_control_deassert(lcd->rstc);
+	if (ret) {
+		dev_err(dev, "reset deassert err %d\n", ret);
+		goto err;
+	}
+
+	return component_add(&pdev->dev, &de2_lcd_ops);
+
+err:
+	clk_disable_unprepare(lcd->gate);
+	clk_disable_unprepare(lcd->clk);
+	of_node_put(lcd->crtc.port);
+	return ret;
+}
+
+static int de2_lcd_remove(struct platform_device *pdev)
+{
+	struct lcd *lcd = platform_get_drvdata(pdev);
+
+	component_del(&pdev->dev, &de2_lcd_ops);
+	if (!IS_ERR_OR_NULL(lcd->rstc))
+		reset_control_assert(lcd->rstc);
+	clk_disable_unprepare(lcd->gate);
+	clk_disable_unprepare(lcd->clk);
+	of_node_put(lcd->crtc.port);
+
+	return 0;
+}
+
+static const struct of_device_id de2_lcd_ids[] = {
+	{ .compatible = "allwinner,sun8i-h3-lcd", },
+	{ }
+};
+
+struct platform_driver de2_lcd_platform_driver = {
+	.probe = de2_lcd_probe,
+	.remove = de2_lcd_remove,
+	.driver = {
+		.name = "sun8i-h3-lcd",
+		.of_match_table = of_match_ptr(de2_lcd_ids),
+	},
+};
diff --git a/drivers/gpu/drm/sunxi/de2_crtc.h b/drivers/gpu/drm/sunxi/de2_crtc.h
new file mode 100644
index 0000000..afddb86
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_crtc.h
@@ -0,0 +1,42 @@
+#ifndef __DE2_CRTC_H__
+#define __DE2_CRTC_H__
+
+/*
+ * Copyright (C) 2015 Jean-François Moine
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <drm/drm_plane_helper.h>
+
+struct priv;
+
+#define N_PLANES 4
+struct lcd {
+	void __iomem *mmio;
+
+	struct device *dev;
+	struct drm_crtc crtc;
+	struct priv *priv;	/* DRM/DE private data */
+
+	short num;		/* LCD index 0/1 */
+	short init_done;
+
+	struct clk *clk;
+	struct clk *gate;
+	struct reset_control *rstc;
+
+	char name[16];
+
+	struct drm_pending_vblank_event *event;
+
+	struct drm_plane planes[N_PLANES];
+};
+
+#define crtc_to_lcd(x) container_of(x, struct lcd, crtc)
+
+#endif /* __DE2_CRTC_H__ */
diff --git a/drivers/gpu/drm/sunxi/de2_de.c b/drivers/gpu/drm/sunxi/de2_de.c
new file mode 100644
index 0000000..92e6e44
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_de.c
@@ -0,0 +1,467 @@
+/*
+ * Allwinner DRM driver - Display Engine 2
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include "de2_drm.h"
+
+static DEFINE_SPINLOCK(de_lock);
+
+/* I/O map */
+
+#define DE_MOD_REG 0x0000	/* 1 bit per LCD */
+#define DE_GATE_REG 0x0004
+#define DE_RESET_REG 0x0008
+#define DE_DIV_REG 0x000c	/* 4 bits per LCD */
+#define DE_SEL_REG 0x0010
+
+#define DE_MUX0_BASE 0x00100000
+#define DE_MUX1_BASE 0x00200000
+
+/* MUX registers (addr / MUX base) */
+#define DE_MUX_GLB_REGS 0x00000		/* global control */
+#define DE_MUX_BLD_REGS 0x01000		/* alpha blending */
+#define DE_MUX_CHAN_REGS 0x02000	/* VI/UI overlay channels */
+#define		DE_MUX_CHAN_SZ 0x1000	/* size of a channel */
+#define DE_MUX_VSU_REGS 0x20000		/* VSU */
+#define DE_MUX_GSU1_REGS 0x40000	/* GSUs */
+#define DE_MUX_GSU2_REGS 0x60000
+#define DE_MUX_GSU3_REGS 0x80000
+#define DE_MUX_FCE_REGS 0xa0000		/* FCE */
+#define DE_MUX_BWS_REGS 0xa2000		/* BWS */
+#define DE_MUX_LTI_REGS 0xa4000		/* LTI */
+#define DE_MUX_PEAK_REGS 0xa6000	/* PEAK */
+#define DE_MUX_ASE_REGS 0xa8000		/* ASE */
+#define DE_MUX_FCC_REGS 0xaa000		/* FCC */
+#define DE_MUX_DCSC_REGS 0xb0000	/* DCSC */
+
+/* global control */
+struct de_glb {
+	u32 ctl;
+#define		DE_MUX_GLB_CTL_rt_en 0x0000001
+#define		DE_MUX_GLB_CTL_finish_irq_en 0x0000010
+#define		DE_MUX_GLB_CTL_rtwb_port 0x0001000
+	u32 status;
+	u32 dbuff;
+	u32 size;
+};
+
+/* alpha blending */
+struct de_bld {
+	u32 fcolor_ctl;			/* 00 */
+	struct {
+		u32 fcolor;
+		u32 insize;
+		u32 offset;
+		u32 dum;
+	} attr[5];
+	u32 dum0[11];
+	u32 route;			/* 80 */
+	u32 premultiply;
+	u32 bkcolor;
+	u32 output_size;
+	u32 bld_mode[4];
+	u32 dum1[4];
+	u32 ck_ctl;			/* b0 */
+	u32 ck_cfg;
+	u32 dum2[2];
+	u32 ck_max[4];
+	u32 dum3[4];
+	u32 ck_min[4];
+	u32 dum4[3];
+	u32 out_ctl;			/* fc */
+};
+
+/* VI channel */
+struct de_vi {
+	struct {
+		u32 attr;
+#define			VI_CFG_ATTR_en 0x00000001
+#define			VI_CFG_ATTR_fcolor_en 0x00000010
+#define			VI_CFG_ATTR_fmt_SHIFT 8
+#define			VI_CFG_ATTR_fmt_MASK 0x00001f00
+#define			VI_CFG_ATTR_sel 0x00008000
+#define			VI_CFG_ATTR_top_down 0x00800000
+		u32 size;
+		u32 coord;
+		u32 pitch[3];
+		u32 top_laddr[3];
+		u32 bot_laddr[3];
+	} cfg[4];
+	u32 fcolor[4];			/* c0 */
+	u32 top_haddr[3];		/* d0 */
+	u32 bot_haddr[3];		/* dc */
+	u32 ovl_size[2];		/* e8 */
+	u32 hori[2];			/* f0 */
+	u32 vert[2];			/* f8 */
+};
+
+/* UI channel */
+struct de_ui {
+	struct {
+		u32 attr;
+#define			UI_CFG_ATTR_en 0x00000001
+#define			UI_CFG_ATTR_alpmod_SHIFT 1
+#define			UI_CFG_ATTR_alpmod_MASK 0x00000006
+#define			UI_CFG_ATTR_fcolor_en 0x00000010
+#define			UI_CFG_ATTR_fmt_SHIFT 8
+#define			UI_CFG_ATTR_fmt_MASK 0x00001f00
+#define			UI_CFG_ATTR_top_down 0x00800000
+#define			UI_CFG_ATTR_alpha_SHIFT 24
+#define			UI_CFG_ATTR_alpha_MASK 0xff000000
+		u32 size;
+		u32 coord;
+		u32 pitch;
+		u32 top_laddr;
+		u32 bot_laddr;
+		u32 fcolor;
+		u32 dum;
+	} cfg[4];			/* 00 */
+	u32 top_haddr;			/* 80 */
+	u32 bot_haddr;
+	u32 ovl_size;			/* 88 */
+};
+
+/* BWS */
+struct de_bws {
+	u32 ctl;
+#define			BWS_WIN_en 0x80000000
+	u32 size;
+	u32 win0;
+	u32 win1;
+};
+
+/* all sizes are ((height - 1) << 16) | (width - 1) */
+#define WH(w, h) (((h - 1) << 16) | (w - 1))
+
+#define DE_CORE_CLK_RATE 432000000
+
+static inline void de_write(struct priv *priv, int reg, u32 data)
+{
+	writel_relaxed(data, priv->mmio + reg);
+}
+
+static inline u32 de_read(struct priv *priv, int reg)
+{
+	return readl_relaxed(priv->mmio + reg);
+}
+
+void de2_de_enable(struct priv *priv, int lcd_num)
+{
+	u32 data;
+	unsigned long flags;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	spin_lock_irqsave(&de_lock, flags);
+
+	/* enable the DE */
+	data = 1 << lcd_num;				/* 1 bit / lcd */
+	de_write(priv, DE_RESET_REG,
+			de_read(priv, DE_RESET_REG) | data);
+	de_write(priv, DE_GATE_REG,
+			de_read(priv, DE_GATE_REG) | data);
+	de_write(priv, DE_MOD_REG,
+			de_read(priv, DE_MOD_REG) | data);
+
+	spin_unlock_irqrestore(&de_lock, flags);
+}
+
+void de2_de_disable(struct priv *priv, int lcd_num)
+{
+	u32 data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&de_lock, flags);
+
+	data = ~(1 << lcd_num);			/* 1 bit */
+	de_write(priv, DE_MOD_REG,
+			de_read(priv, DE_MOD_REG) & data);
+	de_write(priv, DE_GATE_REG,
+			de_read(priv, DE_GATE_REG) & data);
+	de_write(priv, DE_RESET_REG,
+			de_read(priv, DE_RESET_REG) & data);
+
+	spin_unlock_irqrestore(&de_lock, flags);
+}
+
+/* hardware init of the DE, done once */
+void de2_de_hw_init(struct priv *priv, int lcd_num)
+{
+	int mux_o = (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
+	int chan;
+	u32 size = WH(1920, 1080);
+	u32 data;
+	unsigned long flags;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	spin_lock_irqsave(&de_lock, flags);
+
+	/* select the LCD */
+	data = de_read(priv, DE_SEL_REG);
+	if (lcd_num == 0)
+		data &= ~1;
+	else
+		data |= 1;
+	de_write(priv, DE_SEL_REG, data);
+
+	/* start init */
+	{
+		struct de_glb *p_glb = priv->mmio + mux_o +
+					DE_MUX_GLB_REGS;
+
+		p_glb->ctl = DE_MUX_GLB_CTL_rt_en | DE_MUX_GLB_CTL_rtwb_port;
+		p_glb->status = 0;
+		p_glb->dbuff = 1;	/* double register switch */
+		p_glb->size = size;
+	}
+
+	/* set the VI/UIs channels */
+	for (chan = 0; chan < 4; chan++) {
+		int chan_o = mux_o + DE_MUX_CHAN_REGS +
+				DE_MUX_CHAN_SZ * chan;
+
+		if (chan == 0)
+			memset(priv->mmio + chan_o, 0, sizeof(struct de_vi));
+		else
+			memset(priv->mmio + chan_o, 0, sizeof(struct de_ui));
+		if (chan == 2 && lcd_num != 0)
+			break;		/* lcd1 only 1 VI and 1 UI */
+	}
+
+	/* alpha blending */
+	{
+		struct de_bld *p_bld = priv->mmio + mux_o +
+					DE_MUX_BLD_REGS;
+
+		memset(priv->mmio + mux_o + DE_MUX_BLD_REGS, 0, 0x44);
+		p_bld->out_ctl = 0;
+	}
+
+	/* disable the enhancements */
+	{
+		struct ctl {
+			u32 ctl;
+		} *p;
+
+		p = priv->mmio + mux_o + DE_MUX_VSU_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_GSU1_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_GSU2_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_GSU3_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_FCE_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_BWS_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_LTI_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_PEAK_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_ASE_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_FCC_REGS;
+		p->ctl = 0;
+		p = priv->mmio + mux_o + DE_MUX_DCSC_REGS;
+		p->ctl = 0;
+	}
+
+	spin_unlock_irqrestore(&de_lock, flags);
+}
+
+void de2_de_panel_init(struct priv *priv, int lcd_num,
+			struct drm_display_mode *mode)
+{
+	int mux_o = (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
+	u32 size = WH(mode->hdisplay, mode->vdisplay);
+	u32 data;
+	unsigned long flags;
+
+	DRM_DEBUG_DRIVER("%dx%d\n", mode->hdisplay, mode->vdisplay);
+
+	spin_lock_irqsave(&de_lock, flags);
+
+	/* select the LCD */
+	data = de_read(priv, DE_SEL_REG);
+	if (lcd_num == 0)
+		data &= ~1;
+	else
+		data |= 1;
+	de_write(priv, DE_SEL_REG, data);
+
+	/* start init */
+	{
+		struct de_glb *p_glb = priv->mmio + mux_o +
+					DE_MUX_GLB_REGS;
+
+		p_glb->ctl = DE_MUX_GLB_CTL_rt_en | DE_MUX_GLB_CTL_rtwb_port;
+		p_glb->status = 0;
+		p_glb->dbuff = 1;	/* double register switch */
+		p_glb->size = size;
+	}
+
+	/* alpha blending */
+	{
+		struct de_bld *p_bld = priv->mmio + mux_o +
+					DE_MUX_BLD_REGS;
+
+		p_bld->fcolor_ctl = 0x00000101;
+		p_bld->attr[0].fcolor = 0;
+		p_bld->attr[0].insize = size;
+		p_bld->attr[0].offset = 0;	/* 0, 0 */
+		p_bld->route = 1;
+		p_bld->premultiply = 0;
+		p_bld->bkcolor = 0xff000000;
+		p_bld->output_size = size;
+		p_bld->bld_mode[0] = 0x03010301;	/* SRCOVER */
+		p_bld->bld_mode[1] = 0x03010301;
+		p_bld->bld_mode[2] = 0x03010301;
+		p_bld->out_ctl = 
+			mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 0;
+	}
+
+	spin_unlock_irqrestore(&de_lock, flags);
+}
+
+void de2_de_ui_enable(struct priv *priv,
+			int lcd_num, int channel, int layer,
+			dma_addr_t addr, int fmt,
+			int width, int height, int bpp)
+{
+	int mux_o = (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
+	int chan_o = mux_o + DE_MUX_CHAN_REGS + DE_MUX_CHAN_SZ * channel;
+	u32 pitch = width * bpp / 8;
+	u32 size = WH(width, height);
+	u32 offset = 0;		/* (y << 16) | x */
+	u32 data;
+	unsigned long flags;
+
+	DRM_DEBUG_DRIVER("%d:%d:%d addr: %p\n",
+			lcd_num, channel, layer, (void *) addr);
+
+	switch (fmt) {
+	case DRM_FORMAT_ARGB8888:
+		fmt = 0;	/* DE_FORMAT_ARGB_8888 */
+		break;
+	case DRM_FORMAT_XRGB8888:
+		fmt = 4;	/* DE_FORMAT_XRGB_8888 */
+		break;
+	default:
+		pr_err("format %.4s not yet treated\n", (char *) fmt);
+		return;
+	}
+	
+	spin_lock_irqsave(&de_lock, flags);
+
+	/* select the LCD */
+	data = de_read(priv, DE_SEL_REG);
+	if (lcd_num == 0)
+		data &= ~1;
+	else
+		data |= 1;
+	de_write(priv, DE_SEL_REG, data);
+
+	{
+		struct de_glb *p_glb = priv->mmio + mux_o +
+					DE_MUX_GLB_REGS;
+
+		p_glb->dbuff = 1;	/* double register switch */
+	}
+
+	/* set the UI plane */
+	{
+		struct de_ui *p_ui = priv->mmio + chan_o;
+
+		p_ui->cfg[0].attr = UI_CFG_ATTR_en |
+				(fmt << UI_CFG_ATTR_fmt_SHIFT) |
+				(1 << UI_CFG_ATTR_alpmod_SHIFT) |
+				(0xff << UI_CFG_ATTR_alpha_SHIFT);
+		p_ui->cfg[0].size = size;
+		p_ui->cfg[0].coord = offset;
+		p_ui->cfg[0].pitch = pitch;
+		p_ui->cfg[0].top_laddr = addr;
+		p_ui->ovl_size = size;
+	}
+
+	spin_unlock_irqrestore(&de_lock, flags);
+}
+
+int de2_de_init(struct priv *priv, struct device *dev)
+{
+	struct resource *res;
+	int ret;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	res = platform_get_resource(to_platform_device(dev),
+				IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "failed to get memory resource\n");
+		return -EINVAL;
+	}
+
+	priv->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR(priv->mmio)) {
+		dev_err(dev, "failed to map registers\n");
+		return PTR_ERR(priv->mmio);
+	}
+
+	priv->gate = devm_clk_get(dev, "gate");
+	if (IS_ERR(priv->gate)) {
+		dev_err(dev, "gate clock err %d\n", (int) PTR_ERR(priv->gate));
+		return PTR_ERR(priv->gate);
+	}
+
+	priv->clk = devm_clk_get(dev, "clock");
+	if (IS_ERR(priv->clk)) {
+		dev_err(dev, "video clock err %d\n", (int) PTR_ERR(priv->clk));
+		return PTR_ERR(priv->clk);
+	}
+
+	priv->rstc = devm_reset_control_get(dev, NULL);
+	if (IS_ERR(priv->rstc)) {
+		dev_err(dev, "reset controller err %d\n",
+				(int) PTR_ERR(priv->rstc));
+		return PTR_ERR(priv->rstc);
+	}
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret)
+		return ret;
+	ret = clk_prepare_enable(priv->gate);
+	if (ret)
+		goto err_gate;
+
+	/* set the clock rate */
+	clk_set_rate(priv->clk, DE_CORE_CLK_RATE);
+
+	ret = reset_control_deassert(priv->rstc);
+	if (ret) {
+		dev_err(dev, "reset deassert err %d\n", ret);
+		goto err_reset;
+	}
+
+	return 0;
+
+err_reset:
+	clk_disable_unprepare(priv->gate);
+err_gate:
+	clk_disable_unprepare(priv->clk);
+	return ret;
+}
+
+void de2_de_cleanup(struct priv *priv)
+{
+	reset_control_assert(priv->rstc);
+	clk_disable_unprepare(priv->gate);
+	clk_disable_unprepare(priv->clk);
+}
diff --git a/drivers/gpu/drm/sunxi/de2_drm.h b/drivers/gpu/drm/sunxi/de2_drm.h
new file mode 100644
index 0000000..540ad95
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_drm.h
@@ -0,0 +1,51 @@
+#ifndef __DE2_DRM_H__
+#define __DE2_DRM_H__
+/*
+ * Copyright (C) 2016 Jean-François Moine
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <drm/drmP.h>
+#include <drm/drm_fb_cma_helper.h>
+
+struct lcd;
+
+#define N_LCDS 2
+struct priv {
+	void __iomem *mmio;
+	struct clk *clk;
+	struct clk *gate;
+	struct reset_control *rstc;
+
+	struct drm_fbdev_cma *fbdev;
+
+	struct lcd *lcds[N_LCDS];
+};
+
+/* in de2_crtc.c */
+int de2_enable_vblank(struct drm_device *drm, unsigned crtc);
+void de2_disable_vblank(struct drm_device *drm, unsigned crtc);
+extern struct platform_driver de2_lcd_platform_driver;
+
+/* in de2_de.c */
+void de2_de_enable(struct priv *priv, int lcd_num);
+void de2_de_disable(struct priv *priv, int lcd_num);
+void de2_de_hw_init(struct priv *priv, int lcd_num);
+void de2_de_panel_init(struct priv *priv, int lcd_num,
+			struct drm_display_mode *mode);
+int de2_de_init(struct priv *priv, struct device *dev);
+void de2_de_cleanup(struct priv *priv);
+void de2_de_ui_enable(struct priv *priv,
+			int lcd_num, int channel,  int layer,
+			dma_addr_t addr, int fmt,
+			int width, int height, int bpp);
+
+/* in de2_plane.c */
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd);
+
+#endif /* __DE2_DRM_H__ */
diff --git a/drivers/gpu/drm/sunxi/de2_drv.c b/drivers/gpu/drm/sunxi/de2_drv.c
new file mode 100644
index 0000000..e48c1fb
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_drv.c
@@ -0,0 +1,376 @@
+/*
+ * Allwinner DRM driver - DE2 DRM driver
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/of_graph.h>
+#include <linux/component.h>
+#include <drm/drm_of.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "de2_drm.h"
+
+#define DRIVER_NAME	"sunxi-de2"
+#define DRIVER_DESC	"Allwinner DRM DE2"
+#define DRIVER_DATE	"20160101"
+#define DRIVER_MAJOR	1
+#define DRIVER_MINOR	0
+
+static void de2_fb_output_poll_changed(struct drm_device *drm)
+{
+	struct priv *priv = drm->dev_private;
+
+	if (priv->fbdev)
+		drm_fbdev_cma_hotplug_event(priv->fbdev);
+}
+
+static const struct drm_mode_config_funcs de2_mode_config_funcs = {
+	.fb_create = drm_fb_cma_create,
+	.output_poll_changed = de2_fb_output_poll_changed,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+/*
+ * DRM operations:
+ */
+static void de2_lastclose(struct drm_device *drm)
+{
+	struct priv *priv = drm->dev_private;
+
+	if (priv->fbdev)
+		drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static const struct file_operations de2_fops = {
+	.owner		= THIS_MODULE,
+	.open		= drm_open,
+	.release	= drm_release,
+	.unlocked_ioctl	= drm_ioctl,
+	.poll		= drm_poll,
+	.read		= drm_read,
+	.llseek		= no_llseek,
+	.mmap		= drm_gem_cma_mmap,
+};
+
+static struct drm_driver de2_drm_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+					DRIVER_ATOMIC,
+	.lastclose		= de2_lastclose,
+	.get_vblank_counter	= drm_vblank_no_hw_counter,
+	.enable_vblank		= de2_enable_vblank,
+	.disable_vblank		= de2_disable_vblank,
+	.gem_free_object	= drm_gem_cma_free_object,
+	.gem_vm_ops		= &drm_gem_cma_vm_ops,
+	.prime_handle_to_fd	= drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle	= drm_gem_prime_fd_to_handle,
+	.gem_prime_import	= drm_gem_prime_import,
+	.gem_prime_export	= drm_gem_prime_export,
+	.gem_prime_get_sg_table	= drm_gem_cma_prime_get_sg_table,
+	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
+	.gem_prime_vmap		= drm_gem_cma_prime_vmap,
+	.gem_prime_vunmap	= drm_gem_cma_prime_vunmap,
+	.gem_prime_mmap		= drm_gem_cma_prime_mmap,
+	.dumb_create		= drm_gem_cma_dumb_create,
+	.dumb_map_offset	= drm_gem_cma_dumb_map_offset,
+	.dumb_destroy		= drm_gem_dumb_destroy,
+	.fops			= &de2_fops,
+	.name			= DRIVER_NAME,
+	.desc			= DRIVER_DESC,
+	.date			= DRIVER_DATE,
+	.major			= DRIVER_MAJOR,
+	.minor			= DRIVER_MINOR,
+};
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * Power management
+ */
+static int de2_pm_suspend(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+
+	drm_kms_helper_poll_disable(drm);
+	return 0;
+}
+
+static int de2_pm_resume(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+
+	drm_kms_helper_poll_enable(drm);
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops de2_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(de2_pm_suspend, de2_pm_resume)
+};
+
+/*
+ * Platform driver
+ */
+
+static int de2_drm_bind(struct device *dev)
+{
+	struct drm_device *drm;
+	struct priv *priv;
+	int ret;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	drm = drm_dev_alloc(&de2_drm_driver, dev);
+	if (!drm)
+		return -ENOMEM;
+
+	ret = drm_dev_set_unique(drm, dev_name(dev));
+	if (ret < 0)
+		goto out1;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		dev_err(dev, "failed to allocate private area\n");
+		ret = -ENOMEM;
+		goto out1;
+	}
+
+	dev_set_drvdata(dev, drm);
+	drm->dev_private = priv;
+
+	drm_mode_config_init(drm);
+	drm->mode_config.min_width = 640;
+	drm->mode_config.min_height = 480;
+	drm->mode_config.max_width = 1920;
+	drm->mode_config.max_height = 1080;
+	drm->mode_config.funcs = &de2_mode_config_funcs;
+
+	ret = drm_dev_register(drm, 0);
+	if (ret < 0)
+		goto out2;
+
+	/* initialize the display engine */
+	ret = de2_de_init(priv, dev);
+	if (ret)
+		goto out3;
+
+	/* start the subdevices */
+	ret = component_bind_all(dev, drm);
+	if (ret < 0)
+		goto out3;
+
+	DRM_DEBUG_DRIVER("%d crtcs %d connectors\n",
+			 drm->mode_config.num_crtc,
+			 drm->mode_config.num_connector);
+
+	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
+	if (ret < 0)
+		dev_warn(dev, "failed to initialize vblank\n");
+
+	drm_mode_config_reset(drm);
+
+	priv->fbdev = drm_fbdev_cma_init(drm,
+					32,	/* bpp */
+					drm->mode_config.num_crtc,
+					drm->mode_config.num_connector);
+	if (IS_ERR(priv->fbdev)) {
+		ret = PTR_ERR(priv->fbdev);
+		priv->fbdev = NULL;
+		goto out4;
+	}
+
+	drm_kms_helper_poll_init(drm);
+
+	return 0;
+
+out4:
+	component_unbind_all(dev, drm);
+out3:
+	drm_dev_unregister(drm);
+out2:
+	kfree(priv);
+out1:
+	drm_dev_unref(drm);
+	return ret;
+}
+
+static void de2_drm_unbind(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+	struct priv *priv = drm->dev_private;
+
+	if (priv)
+		drm_fbdev_cma_fini(priv->fbdev);
+
+	drm_kms_helper_poll_fini(drm);
+
+	component_unbind_all(dev, drm);
+
+	drm_dev_unregister(drm);
+
+	drm_mode_config_cleanup(drm);
+
+	if (priv) {
+		de2_de_cleanup(priv);
+		kfree(priv);
+	}
+
+	drm_dev_unref(drm);
+}
+
+static const struct component_master_ops de2_drm_comp_ops = {
+	.bind = de2_drm_bind,
+	.unbind = de2_drm_unbind,
+};
+
+static int compare_of(struct device *dev, void *data)
+{
+	return dev->of_node == data;
+}
+
+static int de2_drm_add_components(struct device *dev,
+				  int (*compare_of)(struct device *, void *),
+				  const struct component_master_ops *m_ops)
+{
+	struct device_node *ep, *port, *remote;
+	struct component_match *match = NULL;
+	int i;
+
+	if (!dev->of_node)
+		return -EINVAL;
+
+	/* bind the CRTCs */
+	for (i = 0; ; i++) {
+		port = of_parse_phandle(dev->of_node, "ports", i);
+		if (!port)
+			break;
+
+		if (!of_device_is_available(port->parent)) {
+			of_node_put(port);
+			continue;
+		}
+
+		component_match_add(dev, &match, compare_of, port->parent);
+		of_node_put(port);
+	}
+
+	if (i == 0) {
+		dev_err(dev, "missing 'ports' property\n");
+		return -ENODEV;
+	}
+	if (!match) {
+		dev_err(dev, "no available port\n");
+		return -ENODEV;
+	}
+
+	for (i = 0; ; i++) {
+		port = of_parse_phandle(dev->of_node, "ports", i);
+		if (!port)
+			break;
+
+		if (!of_device_is_available(port->parent)) {
+			of_node_put(port);
+			continue;
+		}
+
+		for_each_child_of_node(port, ep) {
+			remote = of_graph_get_remote_port_parent(ep);
+			if (!remote || !of_device_is_available(remote)) {
+				of_node_put(remote);
+				continue;
+			}
+			if (!of_device_is_available(remote->parent)) {
+				dev_warn(dev, "parent device of %s is not available\n",
+					 remote->full_name);
+				of_node_put(remote);
+				continue;
+			}
+
+			component_match_add(dev, &match, compare_of, remote);
+			of_node_put(remote);
+		}
+		of_node_put(port);
+	}
+
+	return component_master_add_with_match(dev, m_ops, match);
+}
+
+static int de2_drm_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = de2_drm_add_components(&pdev->dev,
+				     compare_of,
+				     &de2_drm_comp_ops);
+	if (ret == -EINVAL)
+		ret = -ENXIO;
+	return ret;
+}
+
+static int de2_drm_remove(struct platform_device *pdev)
+{
+	component_master_del(&pdev->dev, &de2_drm_comp_ops);
+
+	return 0;
+}
+
+static struct of_device_id de2_drm_of_match[] = {
+	{ .compatible = "allwinner,sun8i-h3-display-engine" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, de2_drm_of_match);
+
+static struct platform_driver de2_drm_platform_driver = {
+	.probe      = de2_drm_probe,
+	.remove     = de2_drm_remove,
+	.driver     = {
+		.name = "sun8i-h3-display-engine",
+		.pm = &de2_pm_ops,
+		.of_match_table = de2_drm_of_match,
+	},
+};
+
+static int __init de2_drm_init(void)
+{
+	int ret;
+
+/* uncomment to activate the drm traces at startup time */
+/*	drm_debug = DRM_UT_CORE | DRM_UT_DRIVER | DRM_UT_KMS |
+			DRM_UT_PRIME | DRM_UT_ATOMIC; */
+	drm_debug = DRM_UT_DRIVER;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	ret = platform_driver_register(&de2_lcd_platform_driver);
+	if (ret < 0)
+		return ret;
+
+	ret = platform_driver_register(&de2_drm_platform_driver);
+	if (ret < 0)
+		platform_driver_unregister(&de2_lcd_platform_driver);
+
+	return ret;
+}
+
+static void __exit de2_drm_fini(void)
+{
+	platform_driver_unregister(&de2_lcd_platform_driver);
+	platform_driver_unregister(&de2_drm_platform_driver);
+}
+
+module_init(de2_drm_init);
+module_exit(de2_drm_fini);
+
+MODULE_AUTHOR("Jean-Francois Moine <moinejf@free.fr>");
+MODULE_DESCRIPTION("Allwinner DE2 DRM Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sunxi/de2_hdmi.c b/drivers/gpu/drm/sunxi/de2_hdmi.c
new file mode 100644
index 0000000..1966733
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_hdmi.c
@@ -0,0 +1,381 @@
+/*
+ * Allwinner DRM driver - HDMI
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/component.h>
+#include <linux/clk.h>
+#include <linux/hdmi.h>
+#include <linux/of_graph.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+
+#include "de2_hdmi.h"
+#include "de2_hdmi_h3.h"
+
+#define conn_to_priv(x) \
+	container_of(x, struct de2_hdmi_priv, connector)
+
+#define enc_to_priv(x) \
+	container_of(x, struct de2_hdmi_priv, encoder)
+
+/* --- encoder functions --- */
+
+static bool de2_hdmi_encoder_mode_fixup(struct drm_encoder *encoder,
+				  const struct drm_display_mode *mode,
+				  struct drm_display_mode *adjusted_mode)
+{
+	DRM_DEBUG_DRIVER("\n");
+
+	return true;
+}
+
+static void de2_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+					struct drm_display_mode *mode,
+					struct drm_display_mode *adjusted_mode)
+{
+	struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+
+	DRM_DEBUG_DRIVER("\n");
+
+	priv->cea_mode = drm_match_cea_mode(mode);
+
+	clk_set_rate(priv->clk, mode->clock * 1000);
+
+	mutex_lock(&priv->mutex);
+	bsp_hdmi_video(priv);
+	mutex_unlock(&priv->mutex);
+}
+
+static void de2_hdmi_encoder_enable(struct drm_encoder *encoder)
+{ 
+	struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+
+	DRM_DEBUG_DRIVER("\n");
+
+	mutex_lock(&priv->mutex);
+	bsp_hdmi_set_video_en(priv, 1);
+	mutex_unlock(&priv->mutex);
+}
+
+static void de2_hdmi_encoder_disable(struct drm_encoder *encoder)
+{ 
+	struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+
+	mutex_lock(&priv->mutex);
+	bsp_hdmi_set_video_en(priv, 0);
+	mutex_unlock(&priv->mutex);
+}
+
+static const struct drm_encoder_helper_funcs de2_hdmi_encoder_helper_funcs = {
+	.mode_fixup = de2_hdmi_encoder_mode_fixup,
+	.mode_set = de2_hdmi_encoder_mode_set,
+	.enable = de2_hdmi_encoder_enable,
+	.disable = de2_hdmi_encoder_disable,
+};
+
+static const struct drm_encoder_funcs de2_hdmi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+/* --- connector functions --- */
+
+static int de2_hdmi_connector_mode_valid(struct drm_connector *connector,
+					struct drm_display_mode *mode)
+{
+	if (!drm_match_cea_mode(mode))
+		return MODE_NOMODE;
+	return MODE_OK;
+}
+
+static enum drm_connector_status de2_hdmi_connector_detect(
+				struct drm_connector *connector, bool force)
+{
+	struct de2_hdmi_priv *priv = conn_to_priv(connector);
+	int ret;
+
+	mutex_lock(&priv->mutex);
+	ret = bsp_hdmi_get_hpd(priv);
+	mutex_unlock(&priv->mutex);
+
+	return ret ? connector_status_connected :
+			connector_status_disconnected;
+}
+
+static int read_edid_block(void *data, u8 *buf,
+			unsigned int blk, size_t length)
+{
+	struct de2_hdmi_priv *priv = data;
+	int ret;
+
+	mutex_lock(&priv->mutex);
+	ret = bsp_hdmi_ddc_read(priv,
+				6,
+				blk / 2, (blk & 1) ? 128 : 0,
+				length, buf);
+	mutex_unlock(&priv->mutex);
+
+	return ret;
+}
+
+static int de2_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+	struct de2_hdmi_priv *priv = conn_to_priv(connector);
+	struct edid *edid;
+	int n;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	edid = drm_do_get_edid(connector, read_edid_block, priv);
+
+	if (!edid) {
+		dev_warn(priv->dev, "failed to read EDID\n");
+		return 0;
+	}
+
+	drm_mode_connector_update_edid_property(connector, edid);
+	n = drm_add_edid_modes(connector, edid);
+	priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid);
+
+	DRM_DEBUG_DRIVER("%s EDID ok %d modes\n",
+		priv->is_hdmi_sink ? "HDMI" : "DVI", n);
+
+	kfree(edid);
+
+	return n;
+}
+
+static struct drm_encoder *
+de2_hdmi_connector_best_encoder(struct drm_connector *connector)
+{
+	struct de2_hdmi_priv *priv = conn_to_priv(connector);
+
+	DRM_DEBUG_DRIVER("\n");
+
+	return &priv->encoder;
+}
+
+static const
+struct drm_connector_helper_funcs de2_hdmi_connector_helper_funcs = {
+	.get_modes = de2_hdmi_connector_get_modes,
+	.mode_valid = de2_hdmi_connector_mode_valid,
+	.best_encoder = de2_hdmi_connector_best_encoder,
+};
+
+static void de2_hdmi_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs de2_hdmi_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.reset = drm_atomic_helper_connector_reset,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = de2_hdmi_connector_detect,
+	.destroy = de2_hdmi_connector_destroy,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static void de2_hdmi_cleanup(struct de2_hdmi_priv *priv)
+{
+	if (!IS_ERR_OR_NULL(priv->rstc1))
+		reset_control_assert(priv->rstc1);
+	if (!IS_ERR_OR_NULL(priv->rstc0))
+		reset_control_assert(priv->rstc0);
+	clk_disable_unprepare(priv->gate);
+	clk_disable_unprepare(priv->clk_ddc);
+	clk_disable_unprepare(priv->clk);
+}
+
+static int de2_hdmi_bind(struct device *dev, struct device *master, void *data)
+{
+	struct drm_device *drm = data;
+	struct de2_hdmi_priv *priv = dev_get_drvdata(dev);
+	struct drm_encoder *encoder = &priv->encoder;
+	struct drm_connector *connector = &priv->connector;
+	int ret;
+
+	encoder->possible_crtcs =
+			drm_of_find_possible_crtcs(drm, dev->of_node);
+
+	/* if no CRTC, delay */
+	if (encoder->possible_crtcs == 0)
+		return -EPROBE_DEFER;
+
+	/* HDMI init */
+	ret = clk_prepare_enable(priv->clk);
+	if (ret)
+		goto err;
+	ret = clk_prepare_enable(priv->clk_ddc);
+	if (ret)
+		goto err;
+	ret = clk_prepare_enable(priv->gate);
+	if (ret)
+		goto err;
+	ret = reset_control_deassert(priv->rstc0);
+	if (ret)
+		goto err;
+	ret = reset_control_deassert(priv->rstc1);
+	if (ret)
+		goto err;
+
+	mutex_lock(&priv->mutex);
+	bsp_hdmi_init(priv);
+	bsp_hdmi_hrst(priv);		/* hpd reset */
+	mutex_unlock(&priv->mutex);
+
+	/* encoder init */
+	ret = drm_encoder_init(drm, encoder, &de2_hdmi_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS);
+	if (ret)
+		goto err;
+
+	drm_encoder_helper_add(encoder, &de2_hdmi_encoder_helper_funcs);
+
+	/* connector init */
+	ret = drm_connector_init(drm, connector,
+				 &de2_hdmi_connector_funcs,
+				 DRM_MODE_CONNECTOR_HDMIA);
+	if (ret)
+		goto err_connector;
+
+	connector->interlace_allowed = 1;
+	connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+				 DRM_CONNECTOR_POLL_DISCONNECT;
+	drm_connector_helper_add(connector,
+				 &de2_hdmi_connector_helper_funcs);
+
+	ret = drm_connector_register(connector);
+	if (ret)
+		goto err_register;
+
+	drm_mode_connector_attach_encoder(connector, encoder);
+
+	return 0;
+
+err_register:
+	drm_connector_cleanup(connector);
+err_connector:
+	drm_encoder_cleanup(encoder);
+err:
+	de2_hdmi_cleanup(priv);
+	DRM_DEBUG_DRIVER("err %d\n", ret);
+	return ret;
+}
+
+static void de2_hdmi_unbind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct de2_hdmi_priv *priv = dev_get_drvdata(dev);
+
+	drm_connector_unregister(&priv->connector);
+	drm_connector_cleanup(&priv->connector);
+	drm_encoder_cleanup(&priv->encoder);
+	de2_hdmi_cleanup(priv);
+}
+
+static const struct component_ops de2_hdmi_ops = {
+	.bind = de2_hdmi_bind,
+	.unbind = de2_hdmi_unbind,
+};
+
+static int de2_hdmi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct de2_hdmi_priv *priv;
+	struct resource *res;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, priv);
+	priv->dev = dev;
+
+	mutex_init(&priv->mutex);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "failed to get memory resource\n");
+		return -EINVAL;
+	}
+	priv->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR(priv->mmio)) {
+		dev_err(dev, "failed to map registers\n");
+		return PTR_ERR(priv->mmio);
+	}
+
+	priv->gate = devm_clk_get(dev, "gate");
+	if (IS_ERR(priv->gate)) {
+		dev_err(dev, "gate clock err %d\n",
+					(int) PTR_ERR(priv->gate));
+		return PTR_ERR(priv->gate);
+	}
+	priv->clk = devm_clk_get(dev, "clock");
+	if (IS_ERR(priv->clk)) {
+		dev_err(dev, "hdmi clock err %d\n",
+					(int) PTR_ERR(priv->clk));
+		return PTR_ERR(priv->clk);
+	}
+	priv->clk_ddc = devm_clk_get(dev, "ddc-clock");
+	if (IS_ERR(priv->clk_ddc)) {
+		dev_err(dev, "hdmi-ddc clock err %d\n",
+					(int) PTR_ERR(priv->clk_ddc));
+		return PTR_ERR(priv->clk_ddc);
+	}
+	priv->rstc0 = devm_reset_control_get(dev, "hdmi0");
+	if (IS_ERR(priv->rstc0)) {
+		dev_err(dev, "reset controller err %d\n",
+				(int) PTR_ERR(priv->rstc0));
+		return PTR_ERR(priv->rstc0);
+	}
+	priv->rstc1 = devm_reset_control_get(dev, "hdmi1");
+	if (IS_ERR(priv->rstc1)) {
+		dev_err(dev, "reset controller err %d\n",
+				(int) PTR_ERR(priv->rstc1));
+		return PTR_ERR(priv->rstc1);
+	}
+
+	return component_add(dev, &de2_hdmi_ops);
+}
+
+static int de2_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &de2_hdmi_ops);
+
+	return 0;
+}
+
+static const struct of_device_id de2_hdmi_dt_ids[] = {
+	{ .compatible = "allwinner,sun8i-h3-hdmi", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, de2_hdmi_dt_ids);
+
+static struct platform_driver de2_hdmi_driver = {
+	.probe = de2_hdmi_probe,
+	.remove = de2_hdmi_remove,
+	.driver = {
+		.name = "sun8i-h3-hdmi",
+		.of_match_table = of_match_ptr(de2_hdmi_dt_ids),
+	},
+};
+
+module_platform_driver(de2_hdmi_driver);
+
+MODULE_AUTHOR("Jean-Francois Moine <moinejf@free.fr>");
+MODULE_DESCRIPTION("Allwinner DE2 HDMI encoder/connector");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sunxi/de2_hdmi.h b/drivers/gpu/drm/sunxi/de2_hdmi.h
new file mode 100644
index 0000000..840a4c8
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_hdmi.h
@@ -0,0 +1,34 @@
+#ifndef __DE2_HDMI_H__
+#define __DE2_HDMI_H__
+/*
+ * Copyright (C) 2015 Jean-François Moine
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <drm/drmP.h>
+
+struct de2_hdmi_priv {
+	struct device *dev;
+	void __iomem *mmio;
+
+	struct drm_encoder encoder;
+	struct drm_connector connector;
+
+	struct clk *clk;
+	struct clk *clk_ddc;
+	struct clk *gate;
+	struct reset_control *rstc0;
+	struct reset_control *rstc1;
+
+	struct mutex mutex;
+	u8 cea_mode;
+	bool is_hdmi_sink;
+	bool is_yuv;
+};
+
+#endif /* __DE2_HDMI_H__ */
diff --git a/drivers/gpu/drm/sunxi/de2_hdmi_h3.c b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
new file mode 100644
index 0000000..c54b090
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
@@ -0,0 +1,478 @@
+/*
+ * Allwinner H3 HDMI lowlevel functions
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * Adapted from the file
+ *	lichee/linux-3.4/drivers/video/sunxi/disp2/hdmi/aw/hdmi_bsp_sun8iw7.c
+ * with no license nor copyright.
+ */
+
+#include <drm/drmP.h>
+
+#include "de2_hdmi.h"
+#include "de2_hdmi_h3.h"
+
+struct para_tab {
+	u32 para[19];
+};
+
+struct pcm_sf {
+	u32 	sf;
+	unsigned char	cs_sf;
+};
+
+/*
+ * [0] = vic (cea Video ID)
+ * [1] used in hdmi_phy_set / bsp_hdmi_audio
+ * [2..17] used in bsp_hdmi_video
+ */
+static const struct para_tab ptbl[] = {
+	{{  6,  1, 1,  1,  5,  3, 0, 1, 4, 0, 0, 160,  20,  38, 124, 240, 22, 0, 0}},
+	{{ 21, 11, 1,  1,  5,  3, 1, 1, 2, 0, 0, 160,  32,  24, 126,  32, 24, 0, 0}},
+	{{  2, 11, 0,  0,  2,  6, 1, 0, 9, 0, 0, 208, 138,  16,  62, 224, 45, 0, 0}},
+	{{ 17, 11, 0,  0,  2,  5, 2, 0, 5, 0, 0, 208, 144,  12,  64,  64, 49, 0, 0}},
+	{{ 19,  4, 0, 96,  5,  5, 2, 2, 5, 1, 0,   0, 188, 184,  40, 208, 30, 1, 1}},
+	{{  4,  4, 0, 96,  5,  5, 2, 1, 5, 0, 0,   0, 114, 110,  40, 208, 30, 1, 1}},
+	{{ 20,  4, 0, 97,  7,  5, 4, 2, 2, 2, 0, 128, 208,  16,  44,  56, 22, 1, 1}},
+	{{  5,  4, 0, 97,  7,  5, 4, 1, 2, 0, 0, 128,  24,  88,  44,  56, 22, 1, 1}},
+	{{ 31,  2, 0, 96,  7,  5, 4, 2, 4, 2, 0, 128, 208,  16,  44,  56, 45, 1, 1}},
+	{{ 16,  2, 0, 96,  7,  5, 4, 1, 4, 0, 0, 128,  24,  88,  44,  56, 45, 1, 1}},
+	{{ 32,  4, 0, 96,  7,  5, 4, 3, 4, 2, 0, 128,  62, 126,  44,  56, 45, 1, 1}},
+	{{ 33,  4, 0,  0,  7,  5, 4, 2, 4, 2, 0, 128, 208,  16,  44,  56, 45, 1, 1}},
+	{{ 34,  4, 0,  0,  7,  5, 4, 1, 4, 0, 0, 128,  24,  88,  44,  56, 45, 1, 1}},
+	{{160,  2, 0, 96,  7,  5, 8, 3, 4, 2, 0, 128,  62, 126,  44, 157, 45, 1, 1}},
+	{{147,  2, 0, 96,  5,  5, 5, 2, 5, 1, 0,   0, 188, 184,  40, 190, 30, 1, 1}},
+	{{132,  2, 0, 96,  5,  5, 5, 1, 5, 0, 0,   0, 114, 110,  40, 160, 30, 1, 1}},
+	{{257,  1, 0, 96, 15, 10, 8, 2, 8, 0, 0,   0,  48, 176,  88, 112, 90, 1, 1}},
+	{{258,  1, 0, 96, 15, 10, 8, 5, 8, 4, 0,   0, 160,  32,  88, 112, 90, 1, 1}},
+};
+
+static inline void hdmi_writeb(struct de2_hdmi_priv *priv,
+			u32 addr, u8 data)
+{
+	writeb_relaxed(data, priv->mmio + addr);
+}
+
+static inline void hdmi_writel(struct de2_hdmi_priv *priv,
+			u32 addr, u32 data)
+{
+	writel_relaxed(data, priv->mmio + addr);
+}
+
+static inline u8 hdmi_readb(struct de2_hdmi_priv *priv,
+			u32 addr)
+{
+	return readb_relaxed(priv->mmio + addr);
+}
+
+static inline u32 hdmi_readl(struct de2_hdmi_priv *priv,
+			u32 addr)
+{
+	return readl_relaxed(priv->mmio + addr);
+}
+
+static void hdmi_phy_init(struct de2_hdmi_priv *priv)
+{
+	int to_cnt;
+	u32 tmp;
+
+	hdmi_writel(priv, 0x10020, 0);
+	hdmi_writel(priv, 0x10020, 1 << 0);
+	udelay(5);
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 16));
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 1));
+	udelay(10);
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 2));
+	udelay(5);
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 3));
+	udelay(40);
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 19));
+	udelay(100);
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 18));
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (7 << 4));
+
+	to_cnt = 10;
+	while (1) {
+		if ((hdmi_readl(priv, 0x10038) & 0x80) == 0x80)
+			break;
+		udelay(200);
+		if (--to_cnt == 0) {
+			pr_warn("hdmi phy init timeout\n");
+			break;
+		}
+	}
+
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (0xf << 8));
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 7));
+
+	hdmi_writel(priv, 0x1002c, 0x39dc5040);
+	hdmi_writel(priv, 0x10030, 0x80084343);
+	msleep(10);
+	hdmi_writel(priv, 0x10034, 0x00000001);
+	hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | 0x02000000);
+	msleep(100);
+	tmp = hdmi_readl(priv, 0x10038);
+	hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | 0xc0000000);
+	hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+						((tmp >> 11) & 0x3f));
+	hdmi_writel(priv, 0x10020, 0x01ff0f7f);
+	hdmi_writel(priv, 0x10024, 0x80639000);
+	hdmi_writel(priv, 0x10028, 0x0f81c405);
+}
+
+static int get_vid(u32 id)
+{
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(ptbl); i++) {
+		if (id == ptbl[i].para[0])
+			return i;
+	}
+
+	return -1;
+}
+
+static int hdmi_phy_set(struct de2_hdmi_priv *priv, int i)
+{
+	u32 tmp;
+
+	hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) & ~0xf000);
+	switch (ptbl[i].para[1]) {
+	case 1:
+
+		hdmi_writel(priv, 0x1002c, 0x31dc5fc0);	/* or 0x30dc5fc0 ? */
+		hdmi_writel(priv, 0x10030, 0x800863c0);
+		msleep(10);
+		hdmi_writel(priv, 0x10034, 0x00000001);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+						0x02000000);
+		msleep(200);
+		tmp = hdmi_readl(priv, 0x10038);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0xc0000000);
+		if (((tmp >> 11) & 0x3f) < 0x3d)
+			hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+					(((tmp >> 11) & 0x3f) + 2));
+		else
+			hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+								0x3f);
+		msleep(100);
+		hdmi_writel(priv, 0x10020, 0x01ffff7f);
+		hdmi_writel(priv, 0x10024, 0x8063b000);
+		hdmi_writel(priv, 0x10028, 0x0f8246b5);
+		break;
+	case 2:				/* 1080P @ 60 & 50 */
+		hdmi_writel(priv, 0x1002c, 0x39dc5040);
+		hdmi_writel(priv, 0x10030, 0x80084381);
+		msleep(10);
+		hdmi_writel(priv, 0x10034, 0x00000001);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0x02000000);
+		msleep(100);
+		tmp = hdmi_readl(priv, 0x10038);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0xc0000000);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+						((tmp >> 11) & 0x3f));
+		hdmi_writel(priv, 0x10020, 0x01ffff7f);
+		hdmi_writel(priv, 0x10024, 0x8063a800);
+		hdmi_writel(priv, 0x10028, 0x0f81c485);
+		break;
+	case 4:				/* 720P @ 50 & 60, 1080I, 1080P */
+		hdmi_writel(priv, 0x1002c, 0x39dc5040);
+		hdmi_writel(priv, 0x10030, 0x80084343);
+		msleep(10);
+		hdmi_writel(priv, 0x10034, 0x00000001);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0x02000000);
+		msleep(100);
+		tmp = hdmi_readl(priv, 0x10038);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0xc0000000);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+						((tmp >> 11) & 0x3f));
+		hdmi_writel(priv, 0x10020, 0x01ffff7f);
+		hdmi_writel(priv, 0x10024, 0x8063b000);
+		hdmi_writel(priv, 0x10028, 0x0f81c405);
+		break;
+	case 11:				/* 480P/576P */
+		hdmi_writel(priv, 0x1002c, 0x39dc5040);
+		hdmi_writel(priv, 0x10030, 0x8008430a);
+		msleep(10);
+		hdmi_writel(priv, 0x10034, 0x00000001);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0x02000000);
+		msleep(100);
+		tmp = hdmi_readl(priv, 0x10038);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+							0xc0000000);
+		hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) |
+						((tmp >> 11) & 0x3f));
+		hdmi_writel(priv, 0x10020, 0x01ffff7f);
+		hdmi_writel(priv, 0x10024, 0x8063b000);
+		hdmi_writel(priv, 0x10028, 0x0f81c405);
+		break;
+	default:
+		return -1;
+	}
+	return 0;
+}
+
+static void bsp_hdmi_inner_init(struct de2_hdmi_priv *priv)
+{
+	hdmi_writeb(priv, 0x10010, 0x45);
+	hdmi_writeb(priv, 0x10011, 0x45);
+	hdmi_writeb(priv, 0x10012, 0x52);
+	hdmi_writeb(priv, 0x10013, 0x54);
+	hdmi_writeb(priv, 0x8080,  0x00);
+	udelay(1);
+	hdmi_writeb(priv, 0xf01f, 0x00);
+	hdmi_writeb(priv, 0x8403, 0xff);
+	hdmi_writeb(priv, 0x904c, 0xff);
+	hdmi_writeb(priv, 0x904e, 0xff);
+	hdmi_writeb(priv, 0xd04c, 0xff);
+	hdmi_writeb(priv, 0x8250, 0xff);
+	hdmi_writeb(priv, 0x8a50, 0xff);
+	hdmi_writeb(priv, 0x8272, 0xff);
+	hdmi_writeb(priv, 0x40c0, 0xff);
+	hdmi_writeb(priv, 0x86f0, 0xff);
+	hdmi_writeb(priv, 0x0ee3, 0xff);
+	hdmi_writeb(priv, 0x8ee2, 0xff);
+	hdmi_writeb(priv, 0xa049, 0xf0);
+	hdmi_writeb(priv, 0xb045, 0x1e);
+	hdmi_writeb(priv, 0x00c1, 0x00);
+	hdmi_writeb(priv, 0x00c1, 0x03);
+	hdmi_writeb(priv, 0x00c0, 0x00);
+	hdmi_writeb(priv, 0x40c1, 0x10);
+	hdmi_writeb(priv, 0x0010, 0xff);
+	hdmi_writeb(priv, 0x0011, 0xff);
+	hdmi_writeb(priv, 0x8010, 0xff);
+	hdmi_writeb(priv, 0x8011, 0xff);
+	hdmi_writeb(priv, 0x0013, 0xff);
+	hdmi_writeb(priv, 0x8012, 0xff);
+	hdmi_writeb(priv, 0x8013, 0xff);
+}
+
+void bsp_hdmi_init(struct de2_hdmi_priv *priv)
+{
+	hdmi_phy_init(priv);
+	bsp_hdmi_inner_init(priv);
+}
+
+void bsp_hdmi_set_video_en(struct de2_hdmi_priv *priv,
+			unsigned char enable)
+{
+	if (enable)
+		hdmi_writel(priv, 0x10020,
+			hdmi_readl(priv, 0x10020) | (0x0f << 12));
+	else
+		hdmi_writel(priv, 0x10020,
+			hdmi_readl(priv, 0x10020) & ~(0x0f << 12));
+}
+
+/* initialize */
+int bsp_hdmi_video(struct de2_hdmi_priv *priv)
+{
+	int i = get_vid(priv->cea_mode);	/* ptbl index */
+	int csc;				/* color space */
+
+	if (i < 0)
+		return i;
+
+	switch (priv->cea_mode) {
+	case 2:				/* 480P */
+	case 6:				/* 1440x480I */
+	case 17:			/* 576P */
+	case 21:			/* 1440x576I */
+		csc = 1;		/* BT601 */
+		break;
+	default:
+		csc = 2;		/* BT709 */
+		break;
+	}
+	if (hdmi_phy_set(priv, i) != 0)
+		return -1;
+
+	bsp_hdmi_inner_init(priv);
+
+	hdmi_writeb(priv, 0x0840, 0x01);
+	hdmi_writeb(priv, 0x4845, 0x00);
+	hdmi_writeb(priv, 0x0040, ptbl[i].para[3] | 0x10);
+	hdmi_writeb(priv, 0x10001, ptbl[i].para[3] < 96 ? 0x03 : 0x00);
+	hdmi_writeb(priv, 0x8040, ptbl[i].para[4]);
+	hdmi_writeb(priv, 0x4043, ptbl[i].para[5]);
+	hdmi_writeb(priv, 0x8042, ptbl[i].para[6]);
+	hdmi_writeb(priv, 0x0042, ptbl[i].para[7]);
+	hdmi_writeb(priv, 0x4042, ptbl[i].para[8]);
+	hdmi_writeb(priv, 0x4041, ptbl[i].para[9]);
+	hdmi_writeb(priv, 0xc041, ptbl[i].para[10]);
+	hdmi_writeb(priv, 0x0041, ptbl[i].para[11]);
+	hdmi_writeb(priv, 0x8041, ptbl[i].para[12]);
+	hdmi_writeb(priv, 0x4040, ptbl[i].para[13]);
+	hdmi_writeb(priv, 0xc040, ptbl[i].para[14]);
+	hdmi_writeb(priv, 0x0043, ptbl[i].para[15]);
+	hdmi_writeb(priv, 0x8043, ptbl[i].para[16]);
+	hdmi_writeb(priv, 0x0045, 0x0c);
+	hdmi_writeb(priv, 0x8044, 0x20);
+	hdmi_writeb(priv, 0x8045, 0x01);
+	hdmi_writeb(priv, 0x0046, 0x0b);
+	hdmi_writeb(priv, 0x0047, 0x16);
+	hdmi_writeb(priv, 0x8046, 0x21);
+	hdmi_writeb(priv, 0x3048, ptbl[i].para[2] ? 0x21 : 0x10);
+	hdmi_writeb(priv, 0x0401, ptbl[i].para[2] ? 0x41 : 0x40);
+	hdmi_writeb(priv, 0x8400, 0x07);
+	hdmi_writeb(priv, 0x8401, 0x00);
+	hdmi_writeb(priv, 0x0402, 0x47);
+	hdmi_writeb(priv, 0x0800, 0x01);
+	hdmi_writeb(priv, 0x0801, 0x07);
+	hdmi_writeb(priv, 0x8800, 0x00);
+	hdmi_writeb(priv, 0x8801, 0x00);
+	hdmi_writeb(priv, 0x0802, 0x00);
+	hdmi_writeb(priv, 0x0803, 0x00);
+	hdmi_writeb(priv, 0x8802, 0x00);
+	hdmi_writeb(priv, 0x8803, 0x00);
+
+	if (priv->is_hdmi_sink) {
+		hdmi_writeb(priv, 0xb045, 0x08);
+		hdmi_writeb(priv, 0x2045, 0x00);
+		hdmi_writeb(priv, 0x2044, 0x0c);
+		hdmi_writeb(priv, 0x6041, 0x03);
+		hdmi_writeb(priv, 0xa044, (ptbl[i].para[0] & 0x100) == 0x100 ?
+					0x20 : (ptbl[i].para[0] & 0x80) == 0x80 ?
+					0x40 :
+					0x00 );
+		hdmi_writeb(priv, 0xa045, (ptbl[i].para[0] & 0x100) == 0x100 ?
+					(ptbl[i].para[0] & 0x7f) : 0x00);
+		hdmi_writeb(priv, 0x2046, 0x00);
+		hdmi_writeb(priv, 0x3046, 0x01);
+		hdmi_writeb(priv, 0x3047, 0x11);
+		hdmi_writeb(priv, 0x4044, 0x00);
+		hdmi_writeb(priv, 0x0052, 0x00);
+		hdmi_writeb(priv, 0x8051, 0x11);
+
+		hdmi_writeb(priv, 0x10010, 0x45);
+		hdmi_writeb(priv, 0x10011, 0x45);
+		hdmi_writeb(priv, 0x10012, 0x52);
+		hdmi_writeb(priv, 0x10013, 0x54);
+		hdmi_writeb(priv, 0x0040, hdmi_readb(priv, 0x0040) | 0x08);
+
+		hdmi_writeb(priv, 0x10010, 0x52);
+		hdmi_writeb(priv, 0x10011, 0x54);
+		hdmi_writeb(priv, 0x10012, 0x41);
+		hdmi_writeb(priv, 0x10013, 0x57);
+		hdmi_writeb(priv, 0x4045, priv->is_yuv ? 0x02 : 0x00);
+		if (ptbl[i].para[17] == 0)
+			hdmi_writeb(priv, 0xc044, (csc << 6) | 0x18);
+		else if (ptbl[i].para[17] == 1)
+			hdmi_writeb(priv, 0xc044, (csc << 6) | 0x28);
+		else
+			hdmi_writeb(priv, 0xc044, (csc << 6) | 0x08);
+
+		hdmi_writeb(priv, 0xc045, priv->is_yuv ? 0x00 : 0x08);
+		hdmi_writeb(priv, 0x4046, ptbl[i].para[0]&0x7f);
+	}
+
+	hdmi_writeb(priv, 0x0082, 0x00);
+	hdmi_writeb(priv, 0x0081, 0x00);
+
+	hdmi_writeb(priv, 0x0840, 0x00);
+
+	return 0;
+}
+
+
+/* get a block of EDID */
+int bsp_hdmi_ddc_read(struct de2_hdmi_priv *priv,
+			char cmd, char pointer, char off,
+			int nbyte, char *pbuf)
+{
+	u32 to_cnt;
+	int ret = 0;
+	
+	hdmi_writeb(priv, 0x10010, 0x45);
+	hdmi_writeb(priv, 0x10011, 0x45);
+	hdmi_writeb(priv, 0x10012, 0x52);
+	hdmi_writeb(priv, 0x10013, 0x54);
+	hdmi_writeb(priv, 0x4ee1, 0x00);
+	to_cnt = 50;
+	while ((hdmi_readb(priv, 0x4ee1) & 0x01) != 0x01) {
+		udelay(10);
+		if (--to_cnt == 0) {	/* wait for 500us for timeout */
+			pr_warn("hdmi ddc reset timeout\n");
+			break;
+		}
+	}
+
+	hdmi_writeb(priv, 0x8ee3, 0x05);
+	hdmi_writeb(priv, 0x0ee3, 0x08);
+	hdmi_writeb(priv, 0x4ee2, 0xd8);
+	hdmi_writeb(priv, 0xcee2, 0xfe);
+
+	while (nbyte > 0) {
+		hdmi_writeb(priv, 0x0ee0, 0xa0 >> 1);	/* DDC addr */
+		hdmi_writeb(priv, 0x0ee1, off);		/* DDC offset */
+		hdmi_writeb(priv, 0x4ee0, 0x60 >> 1);	/* DDC segment addr */
+		hdmi_writeb(priv, 0xcee0, pointer);	/* DDC segment */
+		hdmi_writeb(priv, 0x0ee2, 0x02);	/* start command */
+
+		to_cnt = 50;				/* timeout 100ms */
+		while (1) {
+			if ((hdmi_readb(priv, 0x0013) & 0x02) == 0x02) {
+				hdmi_writeb(priv, 0x0013,
+					hdmi_readb(priv, 0x0013) & 0x02);
+				*pbuf++ = hdmi_readb(priv, 0x8ee1);
+				break;
+			}
+			if ((hdmi_readb(priv, 0x0013) & 0x01) == 0x01) {
+				hdmi_writeb(priv, 0x0013,
+					hdmi_readb(priv, 0x0013) & 0x01);
+				pr_warn("hdmi ddc read error, byte cnt = %d\n",
+					 nbyte);
+				ret = -1;
+				break;
+			}
+			if (--to_cnt == 0) {
+				pr_warn("hdmi ddc read timeout, byte cnt = %d\n",
+					 nbyte);
+				ret = -1;
+				break;
+			}
+			msleep(2);
+		}
+		if (ret)
+			break;
+		nbyte--;
+		off++;
+	}
+	hdmi_writeb(priv, 0x10010, 0x52);
+	hdmi_writeb(priv, 0x10011, 0x54);
+	hdmi_writeb(priv, 0x10012, 0x41);
+	hdmi_writeb(priv, 0x10013, 0x57);
+
+	return ret;
+}
+
+int bsp_hdmi_get_hpd(struct de2_hdmi_priv *priv)
+{
+	int ret;
+
+	hdmi_writeb(priv, 0x10010, 0x45);
+	hdmi_writeb(priv, 0x10011, 0x45);
+	hdmi_writeb(priv, 0x10012, 0x52);
+	hdmi_writeb(priv, 0x10013, 0x54);
+
+	ret = (hdmi_readl(priv, 0x10038) & 0x80000) ? 1 : 0;
+
+	hdmi_writeb(priv, 0x10010, 0x52);
+	hdmi_writeb(priv, 0x10011, 0x54);
+	hdmi_writeb(priv, 0x10012, 0x41);
+	hdmi_writeb(priv, 0x10013, 0x57);
+
+	return ret;
+}
+
+void bsp_hdmi_hrst(struct de2_hdmi_priv *priv)
+{
+	hdmi_writeb(priv, 0x00c1, 0x04);
+}
diff --git a/drivers/gpu/drm/sunxi/de2_hdmi_h3.h b/drivers/gpu/drm/sunxi/de2_hdmi_h3.h
new file mode 100644
index 0000000..59fb6ca
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_hdmi_h3.h
@@ -0,0 +1,14 @@
+#ifndef __DE2_HDMI_H3_H__
+#define __DE2_HDMI_H3_H__
+
+void bsp_hdmi_set_video_en(struct de2_hdmi_priv *priv,
+			unsigned char enable);
+int bsp_hdmi_video(struct de2_hdmi_priv *priv);
+int bsp_hdmi_ddc_read(struct de2_hdmi_priv *priv,
+			char cmd, char pointer, char offset,
+			int nbyte, char *pbuf);
+int bsp_hdmi_get_hpd(struct de2_hdmi_priv *priv);
+void bsp_hdmi_init(struct de2_hdmi_priv *priv);
+void bsp_hdmi_hrst(struct de2_hdmi_priv *priv);
+
+#endif /* __DE2_HDMI_H3_H__ */
diff --git a/drivers/gpu/drm/sunxi/de2_plane.c b/drivers/gpu/drm/sunxi/de2_plane.c
new file mode 100644
index 0000000..53f6c79
--- /dev/null
+++ b/drivers/gpu/drm/sunxi/de2_plane.c
@@ -0,0 +1,102 @@
+/*
+ * Allwinner DRM driver - DE2 Planes
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "de2_drm.h"
+#include "de2_crtc.h"
+
+/* primary plane */
+static const uint32_t primary_formats[] = {
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_XRGB8888,
+};
+
+static void primary_plane_update(struct drm_plane *plane,
+				struct drm_plane_state *old_state)
+{
+	struct drm_plane_state *state = plane->state;
+	struct drm_crtc *crtc = state->crtc;
+	struct lcd *lcd = crtc_to_lcd(crtc);
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_gem_cma_object *gem;
+	int plane_num = plane - lcd->planes;
+	int crtc_x, crtc_y, src_x, src_y;
+	dma_addr_t start;
+
+	if (!crtc || !fb) {
+		DRM_DEBUG_DRIVER("no crtc/fb\n");
+		return;
+	}
+
+	if (!lcd->init_done)
+		return;
+
+	src_x = state->src_x >> 16;
+	src_y = state->src_y >> 16;
+	crtc_x = state->crtc_x;
+	crtc_y = state->crtc_y;
+
+	DRM_DEBUG_DRIVER("%dx%d+%d+%d %.4s\n",
+			state->crtc_w, state->crtc_h, crtc_x, crtc_y,
+			(char *) &fb->pixel_format);
+
+	gem = drm_fb_cma_get_gem_obj(fb, 0);
+
+	start = gem->paddr + fb->offsets[0] +
+			src_y * fb->pitches[0] + src_x;
+
+	if (plane_num == 0) {
+		de2_de_ui_enable(lcd->priv, lcd->num,
+			1, 0,			/* UI 0, layer 0 */
+			start, fb->pixel_format,
+			state->crtc_w,
+			state->crtc_h,
+			fb->bits_per_pixel);
+	} else {
+		DRM_DEBUG_DRIVER("no plane #%d\n", plane_num);
+	}
+}
+
+static const struct drm_plane_helper_funcs primary_plane_helper_funcs = {
+	.atomic_update = primary_plane_update,
+};
+
+static const struct drm_plane_funcs primary_plane_funcs = {
+	.update_plane = drm_primary_helper_update,
+	.disable_plane = drm_primary_helper_disable,
+	.destroy = drm_primary_helper_destroy,
+	.reset = drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd)
+{
+	int ret;
+
+	ret = drm_universal_plane_init(drm, &lcd->planes[0], 0,
+				&primary_plane_funcs,
+				primary_formats, ARRAY_SIZE(primary_formats),
+				DRM_PLANE_TYPE_PRIMARY);
+	if (ret < 0) {
+		dev_err(lcd->dev, "Couldn't initialize primary plane\n");
+		return ret;
+	}
+
+	drm_plane_helper_add(&lcd->planes[0],
+			     &primary_plane_helper_funcs);
+
+	return ret;
+}
-- 
2.6.4


[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [PATCH RFC 0/2] Add a display driver to the Allwinner H3
@ 2016-01-05 19:15 ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-05 19:15 UTC (permalink / raw)
  To: linux-arm-kernel

The proposed DRM driver works on a Orange PI 2 with a kernel 4.4-rc1
and the H3 patches found in Hans de Goede's GIT repository.

As there is no documentation about the DE2 nor about the HDMI which
are found in the H3, this driver has been built from Allwiiner's
sources.

So, there may be license problems, especially for the file
de2_hdmi_h3.c which contains a lot of magic values.

The associated DT and documentation will be submitted when the H3 DTs
will be in the kernel.

Jean-Francois Moine (2):
  clk: sunxi: Add sun8i display support
  drm: sunxi: Add a basic DRM driver for Allwinner DE2

 drivers/clk/sunxi/Makefile            |   1 +
 drivers/clk/sunxi/clk-sun8i-display.c | 257 ++++++++++++++++++
 drivers/gpu/drm/Kconfig               |   2 +
 drivers/gpu/drm/Makefile              |   1 +
 drivers/gpu/drm/sunxi/Kconfig         |  21 ++
 drivers/gpu/drm/sunxi/Makefile        |   8 +
 drivers/gpu/drm/sunxi/de2_crtc.c      | 409 +++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_crtc.h      |  42 +++
 drivers/gpu/drm/sunxi/de2_de.c        | 467 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_drm.h       |  51 ++++
 drivers/gpu/drm/sunxi/de2_drv.c       | 376 ++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi.c      | 381 +++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi.h      |  34 +++
 drivers/gpu/drm/sunxi/de2_hdmi_h3.c   | 478 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi_h3.h   |  14 +
 drivers/gpu/drm/sunxi/de2_plane.c     | 102 ++++++++
 16 files changed, 2644 insertions(+)
 create mode 100644 drivers/clk/sunxi/clk-sun8i-display.c
 create mode 100644 drivers/gpu/drm/sunxi/Kconfig
 create mode 100644 drivers/gpu/drm/sunxi/Makefile
 create mode 100644 drivers/gpu/drm/sunxi/de2_crtc.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_crtc.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_de.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_drm.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_drv.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_h3.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_h3.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_plane.c

-- 
2.6.4

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 0/2] Add a display driver to the Allwinner H3
@ 2016-01-05 19:15 ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-05 19:15 UTC (permalink / raw)
  To: Dave Airlie, Maxime Ripard, Chen-Yu Tsai; +Cc: linux-arm-kernel, dri-devel

The proposed DRM driver works on a Orange PI 2 with a kernel 4.4-rc1
and the H3 patches found in Hans de Goede's GIT repository.

As there is no documentation about the DE2 nor about the HDMI which
are found in the H3, this driver has been built from Allwiiner's
sources.

So, there may be license problems, especially for the file
de2_hdmi_h3.c which contains a lot of magic values.

The associated DT and documentation will be submitted when the H3 DTs
will be in the kernel.

Jean-Francois Moine (2):
  clk: sunxi: Add sun8i display support
  drm: sunxi: Add a basic DRM driver for Allwinner DE2

 drivers/clk/sunxi/Makefile            |   1 +
 drivers/clk/sunxi/clk-sun8i-display.c | 257 ++++++++++++++++++
 drivers/gpu/drm/Kconfig               |   2 +
 drivers/gpu/drm/Makefile              |   1 +
 drivers/gpu/drm/sunxi/Kconfig         |  21 ++
 drivers/gpu/drm/sunxi/Makefile        |   8 +
 drivers/gpu/drm/sunxi/de2_crtc.c      | 409 +++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_crtc.h      |  42 +++
 drivers/gpu/drm/sunxi/de2_de.c        | 467 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_drm.h       |  51 ++++
 drivers/gpu/drm/sunxi/de2_drv.c       | 376 ++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi.c      | 381 +++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi.h      |  34 +++
 drivers/gpu/drm/sunxi/de2_hdmi_h3.c   | 478 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/sunxi/de2_hdmi_h3.h   |  14 +
 drivers/gpu/drm/sunxi/de2_plane.c     | 102 ++++++++
 16 files changed, 2644 insertions(+)
 create mode 100644 drivers/clk/sunxi/clk-sun8i-display.c
 create mode 100644 drivers/gpu/drm/sunxi/Kconfig
 create mode 100644 drivers/gpu/drm/sunxi/Makefile
 create mode 100644 drivers/gpu/drm/sunxi/de2_crtc.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_crtc.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_de.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_drm.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_drv.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_h3.c
 create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_h3.h
 create mode 100644 drivers/gpu/drm/sunxi/de2_plane.c

-- 
2.6.4

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2
  2016-01-05 18:40   ` Jean-Francois Moine
@ 2016-01-05 20:38     ` Russell King - ARM Linux
  -1 siblings, 0 replies; 36+ messages in thread
From: Russell King - ARM Linux @ 2016-01-05 20:38 UTC (permalink / raw)
  To: linux-arm-kernel

Some comments from an ARM architecture point of view.  I haven't
reviewed it from a DRM point of view yet.

On Tue, Jan 05, 2016 at 07:40:11PM +0100, Jean-Francois Moine wrote:
> +struct tcon {
> +	u32 gctl;
> +#define		TCON_GCTL_TCON_En 0x80000000
> +	u32 gint0;
> +#define		TCON_GINT0_TCON1_Vb_Int_En 0x40000000
> +#define		TCON_GINT0_TCON1_Vb_Int_Flag 0x00001000
> +	u32 gint1;
> +	u32 dum0[13];
> +	u32 tcon0_ctl;
> +#define		TCON0_CTL_TCON_En 0x80000000
> +	u32 dum1[19];
> +	u32 tcon1_ctl;
> +#define		TCON1_CTL_TCON_En 0x80000000
> +#define		TCON1_CTL_Interlace_En 0x00100000
> +#define		TCON1_CTL_Start_Delay_SHIFT 4
> +#define		TCON1_CTL_Start_Delay_MASK 0x000001f0
> +	u32 basic0;			/* XI/YI */
> +	u32 basic1;			/* LS_XO/LS_YO */
> +	u32 basic2;			/* XO/YO */
> +	u32 basic3;			/* HT/HBP */
> +	u32 basic4;			/* VT/VBP */
> +	u32 basic5;			/* HSPW/VSPW */
> +	u32 dum2;
> +	u32 ps_sync;
> +	u32 dum3[15];
> +	u32 io_pol;
> +#define		TCON1_IO_POL_IO0_inv 0x01000000
> +#define		TCON1_IO_POL_IO1_inv 0x02000000
> +#define		TCON1_IO_POL_IO2_inv 0x04000000
> +	u32 io_tri;
> +	u32 dum4[2];
> +
> +	u32 ceu_ctl;			/* 100 */
> +#define     TCON_CEU_CTL_ceu_en 0x80000000
> +	u32 dum5[3];
> +	u32 ceu_rr;
> +	u32 ceu_rg;
> +	u32 ceu_rb;
> +	u32 ceu_rc;
> +	u32 ceu_gr;
> +	u32 ceu_gg;
> +	u32 ceu_gb;
> +	u32 ceu_gc;
> +	u32 ceu_br;
> +	u32 ceu_bg;
> +	u32 ceu_bb;
> +	u32 ceu_bc;
> +	u32 ceu_rv;
> +	u32 ceu_gv;
> +	u32 ceu_bv;
> +	u32 dum6[45];
> +
> +	u32 mux_ctl;			/* 200 */
> +#define		TCON_MUX_CTL_HDMI_SRC_SHIFT 8
> +#define		TCON_MUX_CTL_HDMI_SRC_MASK 0x00000300
> +	u32 dum7[63];
> +
> +	u32 fill_ctl;			/* 300 */
> +	u32 fill_start0;
> +	u32 fill_end0;
> +	u32 fill_data0;
> +};

This sets off warnings bells for me...

> +static void de2_set_frame_timings(struct lcd *lcd)
> +{
> +	struct drm_crtc *crtc = &lcd->crtc;
> +	const struct drm_display_mode *mode = &crtc->mode;
> +	struct tcon *p_tcon = lcd->mmio;
> +	int interlace = mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 1;
> +	int start_delay;
> +	u32 data;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +
> +	data = XY(mode->hdisplay - 1, mode->vdisplay / interlace - 1);
> +	p_tcon->basic0 = data;
> +	p_tcon->basic1 = data;
> +	p_tcon->basic2 = data;
> +	p_tcon->basic3 = XY(mode->htotal - 1,
> +				mode->htotal - mode->hsync_start - 1);
> +	p_tcon->basic4 = XY(mode->vtotal * (3 - interlace),
> +				mode->vtotal - mode->vsync_start - 1);
> +	p_tcon->basic5 = XY(mode->hsync_end - mode->hsync_start - 1,
> +				mode->vsync_end - mode->vsync_start - 1);
> +
> +	p_tcon->ps_sync = XY(1, 1);
> +
> +	data = TCON1_IO_POL_IO2_inv;
> +	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
> +		data |= TCON1_IO_POL_IO0_inv;
> +	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
> +		data |= TCON1_IO_POL_IO1_inv;
> +	p_tcon->io_pol = data;
> +
> +	p_tcon->ceu_ctl &= ~TCON_CEU_CTL_ceu_en;
> +
> +	if (interlace == 2)
> +		p_tcon->tcon1_ctl |= TCON1_CTL_Interlace_En;
> +	else
> +		p_tcon->tcon1_ctl &= ~TCON1_CTL_Interlace_En;
> +
> +	p_tcon->fill_ctl = 0;
> +	p_tcon->fill_start0 = mode->vtotal + 1;
> +	p_tcon->fill_end0 = mode->vtotal;
> +	p_tcon->fill_data0 = 0;
> +
> +	start_delay = (mode->vtotal - mode->vdisplay) / interlace - 5;
> +	if (start_delay > 31)
> +		start_delay = 31;
> +	p_tcon->tcon1_ctl &= ~TCON1_CTL_Start_Delay_MASK;
> +	p_tcon->tcon1_ctl |= start_delay << TCON1_CTL_Start_Delay_SHIFT;

Here, the compiler might read tcon1_ctl, clear the bits, merge the
new bits in, and write them back.  Or it might decide to read,
clear bits, write back, set the new bits, and write back again.
What happens if start_delay were written as zero?

> +
> +	p_tcon->io_tri = 0x0fffffff;

This whole thing even more so - the compiler is free to reorder
these accesses, merge them together, etc.  If the compiler decides
to inline this into callers, then it can merge these accesses with
accesses in the caller functions too.

This is why we have IO accessors; they prevent the compiler doing
these kinds of optimisations, and to ensure correctness, we have
sparse, and we mark MMIO memory with __iomem to prevent this kind
of programming mistake.

Please use sparse to check your work, and also use the IO accessors.

The same comments apply elsewhere.

> +}
> +
> +static void de2_crtc_enable(struct drm_crtc *crtc)
> +{
> +	struct lcd *lcd = crtc_to_lcd(crtc);
> +	struct drm_display_mode *mode = &crtc->mode;
> +	struct tcon *p_tcon = lcd->mmio;
> +
> +	DRM_DEBUG_DRIVER("clock %d\n", mode->clock);
> +
> +	if (mode->clock == 0) {
> +		dev_err(lcd->dev, "crtc_start: no clock!\n");
> +		return;
> +	}

Why do you need to check this here?

> +
> +	de2_set_frame_timings(lcd);
> +
> +	clk_set_rate(lcd->clk, mode->clock * 1000);

What if the clock can't support the rate?

...

> +	/* set the VI/UIs channels */
> +	for (chan = 0; chan < 4; chan++) {
> +		int chan_o = mux_o + DE_MUX_CHAN_REGS +
> +				DE_MUX_CHAN_SZ * chan;
> +
> +		if (chan == 0)
> +			memset(priv->mmio + chan_o, 0, sizeof(struct de_vi));
> +		else
> +			memset(priv->mmio + chan_o, 0, sizeof(struct de_ui));

The compiler is allowed to optimise memset() any way it chooses; it makes
no guarantees what so ever, and the compiler is allowed to assume that it
is accessing memory which has no side effects.  It's even allowed to use
unaligned accesses if it so pleases, which are illegal for MMIO memory on
ARM.  memset_io() must be used instead.  Sparse would have found this
as well.

...

> +static int de2_hdmi_connector_mode_valid(struct drm_connector *connector,
> +					struct drm_display_mode *mode)
> +{
> +	if (!drm_match_cea_mode(mode))
> +		return MODE_NOMODE;

Maybe detect modes with a zero clock here instead?

> +	return MODE_OK;
> +}

...

> +static void hdmi_phy_init(struct de2_hdmi_priv *priv)
> +{
> +	int to_cnt;
> +	u32 tmp;
> +
> +	hdmi_writel(priv, 0x10020, 0);
> +	hdmi_writel(priv, 0x10020, 1 << 0);
> +	udelay(5);

udelay() does not delay precisely; it may return slightly short of the
requested delay.  I hope you've made allowance for that.


-- 
RMK's Patch system: http://www.arm.linux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2
@ 2016-01-05 20:38     ` Russell King - ARM Linux
  0 siblings, 0 replies; 36+ messages in thread
From: Russell King - ARM Linux @ 2016-01-05 20:38 UTC (permalink / raw)
  To: Jean-Francois Moine
  Cc: dri-devel, Emilio López, Chen-Yu Tsai, Maxime Ripard,
	linux-arm-kernel

Some comments from an ARM architecture point of view.  I haven't
reviewed it from a DRM point of view yet.

On Tue, Jan 05, 2016 at 07:40:11PM +0100, Jean-Francois Moine wrote:
> +struct tcon {
> +	u32 gctl;
> +#define		TCON_GCTL_TCON_En 0x80000000
> +	u32 gint0;
> +#define		TCON_GINT0_TCON1_Vb_Int_En 0x40000000
> +#define		TCON_GINT0_TCON1_Vb_Int_Flag 0x00001000
> +	u32 gint1;
> +	u32 dum0[13];
> +	u32 tcon0_ctl;
> +#define		TCON0_CTL_TCON_En 0x80000000
> +	u32 dum1[19];
> +	u32 tcon1_ctl;
> +#define		TCON1_CTL_TCON_En 0x80000000
> +#define		TCON1_CTL_Interlace_En 0x00100000
> +#define		TCON1_CTL_Start_Delay_SHIFT 4
> +#define		TCON1_CTL_Start_Delay_MASK 0x000001f0
> +	u32 basic0;			/* XI/YI */
> +	u32 basic1;			/* LS_XO/LS_YO */
> +	u32 basic2;			/* XO/YO */
> +	u32 basic3;			/* HT/HBP */
> +	u32 basic4;			/* VT/VBP */
> +	u32 basic5;			/* HSPW/VSPW */
> +	u32 dum2;
> +	u32 ps_sync;
> +	u32 dum3[15];
> +	u32 io_pol;
> +#define		TCON1_IO_POL_IO0_inv 0x01000000
> +#define		TCON1_IO_POL_IO1_inv 0x02000000
> +#define		TCON1_IO_POL_IO2_inv 0x04000000
> +	u32 io_tri;
> +	u32 dum4[2];
> +
> +	u32 ceu_ctl;			/* 100 */
> +#define     TCON_CEU_CTL_ceu_en 0x80000000
> +	u32 dum5[3];
> +	u32 ceu_rr;
> +	u32 ceu_rg;
> +	u32 ceu_rb;
> +	u32 ceu_rc;
> +	u32 ceu_gr;
> +	u32 ceu_gg;
> +	u32 ceu_gb;
> +	u32 ceu_gc;
> +	u32 ceu_br;
> +	u32 ceu_bg;
> +	u32 ceu_bb;
> +	u32 ceu_bc;
> +	u32 ceu_rv;
> +	u32 ceu_gv;
> +	u32 ceu_bv;
> +	u32 dum6[45];
> +
> +	u32 mux_ctl;			/* 200 */
> +#define		TCON_MUX_CTL_HDMI_SRC_SHIFT 8
> +#define		TCON_MUX_CTL_HDMI_SRC_MASK 0x00000300
> +	u32 dum7[63];
> +
> +	u32 fill_ctl;			/* 300 */
> +	u32 fill_start0;
> +	u32 fill_end0;
> +	u32 fill_data0;
> +};

This sets off warnings bells for me...

> +static void de2_set_frame_timings(struct lcd *lcd)
> +{
> +	struct drm_crtc *crtc = &lcd->crtc;
> +	const struct drm_display_mode *mode = &crtc->mode;
> +	struct tcon *p_tcon = lcd->mmio;
> +	int interlace = mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 1;
> +	int start_delay;
> +	u32 data;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +
> +	data = XY(mode->hdisplay - 1, mode->vdisplay / interlace - 1);
> +	p_tcon->basic0 = data;
> +	p_tcon->basic1 = data;
> +	p_tcon->basic2 = data;
> +	p_tcon->basic3 = XY(mode->htotal - 1,
> +				mode->htotal - mode->hsync_start - 1);
> +	p_tcon->basic4 = XY(mode->vtotal * (3 - interlace),
> +				mode->vtotal - mode->vsync_start - 1);
> +	p_tcon->basic5 = XY(mode->hsync_end - mode->hsync_start - 1,
> +				mode->vsync_end - mode->vsync_start - 1);
> +
> +	p_tcon->ps_sync = XY(1, 1);
> +
> +	data = TCON1_IO_POL_IO2_inv;
> +	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
> +		data |= TCON1_IO_POL_IO0_inv;
> +	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
> +		data |= TCON1_IO_POL_IO1_inv;
> +	p_tcon->io_pol = data;
> +
> +	p_tcon->ceu_ctl &= ~TCON_CEU_CTL_ceu_en;
> +
> +	if (interlace == 2)
> +		p_tcon->tcon1_ctl |= TCON1_CTL_Interlace_En;
> +	else
> +		p_tcon->tcon1_ctl &= ~TCON1_CTL_Interlace_En;
> +
> +	p_tcon->fill_ctl = 0;
> +	p_tcon->fill_start0 = mode->vtotal + 1;
> +	p_tcon->fill_end0 = mode->vtotal;
> +	p_tcon->fill_data0 = 0;
> +
> +	start_delay = (mode->vtotal - mode->vdisplay) / interlace - 5;
> +	if (start_delay > 31)
> +		start_delay = 31;
> +	p_tcon->tcon1_ctl &= ~TCON1_CTL_Start_Delay_MASK;
> +	p_tcon->tcon1_ctl |= start_delay << TCON1_CTL_Start_Delay_SHIFT;

Here, the compiler might read tcon1_ctl, clear the bits, merge the
new bits in, and write them back.  Or it might decide to read,
clear bits, write back, set the new bits, and write back again.
What happens if start_delay were written as zero?

> +
> +	p_tcon->io_tri = 0x0fffffff;

This whole thing even more so - the compiler is free to reorder
these accesses, merge them together, etc.  If the compiler decides
to inline this into callers, then it can merge these accesses with
accesses in the caller functions too.

This is why we have IO accessors; they prevent the compiler doing
these kinds of optimisations, and to ensure correctness, we have
sparse, and we mark MMIO memory with __iomem to prevent this kind
of programming mistake.

Please use sparse to check your work, and also use the IO accessors.

The same comments apply elsewhere.

> +}
> +
> +static void de2_crtc_enable(struct drm_crtc *crtc)
> +{
> +	struct lcd *lcd = crtc_to_lcd(crtc);
> +	struct drm_display_mode *mode = &crtc->mode;
> +	struct tcon *p_tcon = lcd->mmio;
> +
> +	DRM_DEBUG_DRIVER("clock %d\n", mode->clock);
> +
> +	if (mode->clock == 0) {
> +		dev_err(lcd->dev, "crtc_start: no clock!\n");
> +		return;
> +	}

Why do you need to check this here?

> +
> +	de2_set_frame_timings(lcd);
> +
> +	clk_set_rate(lcd->clk, mode->clock * 1000);

What if the clock can't support the rate?

...

> +	/* set the VI/UIs channels */
> +	for (chan = 0; chan < 4; chan++) {
> +		int chan_o = mux_o + DE_MUX_CHAN_REGS +
> +				DE_MUX_CHAN_SZ * chan;
> +
> +		if (chan == 0)
> +			memset(priv->mmio + chan_o, 0, sizeof(struct de_vi));
> +		else
> +			memset(priv->mmio + chan_o, 0, sizeof(struct de_ui));

The compiler is allowed to optimise memset() any way it chooses; it makes
no guarantees what so ever, and the compiler is allowed to assume that it
is accessing memory which has no side effects.  It's even allowed to use
unaligned accesses if it so pleases, which are illegal for MMIO memory on
ARM.  memset_io() must be used instead.  Sparse would have found this
as well.

...

> +static int de2_hdmi_connector_mode_valid(struct drm_connector *connector,
> +					struct drm_display_mode *mode)
> +{
> +	if (!drm_match_cea_mode(mode))
> +		return MODE_NOMODE;

Maybe detect modes with a zero clock here instead?

> +	return MODE_OK;
> +}

...

> +static void hdmi_phy_init(struct de2_hdmi_priv *priv)
> +{
> +	int to_cnt;
> +	u32 tmp;
> +
> +	hdmi_writel(priv, 0x10020, 0);
> +	hdmi_writel(priv, 0x10020, 1 << 0);
> +	udelay(5);

udelay() does not delay precisely; it may return slightly short of the
requested delay.  I hope you've made allowance for that.


-- 
RMK's Patch system: http://www.arm.linux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
  2016-01-05 18:28   ` Jean-Francois Moine
@ 2016-01-06  2:39     ` Chen-Yu Tsai
  -1 siblings, 0 replies; 36+ messages in thread
From: Chen-Yu Tsai @ 2016-01-06  2:39 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

First of all, please include the clk subsystem maintainers and the
linux-clk mailing list for all clk related patches.

On Wed, Jan 6, 2016 at 2:28 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
> Add the clock types which are used by the sun8i family for video.

These clocks first appeared in the A31.

> Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
> ---
>  drivers/clk/sunxi/Makefile            |   1 +
>  drivers/clk/sunxi/clk-sun8i-display.c | 247 ++++++++++++++++++++++++++++++++++

Please split this into 2 patches, and 2 files: one for PLL3, named
clk-sun6i-pll3.c, and one for the display mod clocks, named
clk-sun6i-display.c

>  2 files changed, 258 insertions(+)
>  create mode 100644 drivers/clk/sunxi/clk-sun8i-display.c
>
> diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
> index cb4c299..145c078 100644
> --- a/drivers/clk/sunxi/Makefile
> +++ b/drivers/clk/sunxi/Makefile
> @@ -10,6 +10,7 @@ obj-y += clk-a10-pll2.o
>  obj-y += clk-a20-gmac.o
>  obj-y += clk-mod0.o
>  obj-y += clk-simple-gates.o
> +obj-y += clk-sun8i-display.o
>  obj-y += clk-sun8i-mbus.o
>  obj-y += clk-sun9i-core.o
>  obj-y += clk-sun9i-mmc.o
> diff --git a/drivers/clk/sunxi/clk-sun8i-display.c b/drivers/clk/sunxi/clk-sun8i-display.c
> new file mode 100644
> index 0000000..eded572
> --- /dev/null
> +++ b/drivers/clk/sunxi/clk-sun8i-display.c
> @@ -0,0 +1,247 @@
> +/*
> + * Copyright 2015 Jean-Francois Moine <moinejf@free.fr>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/of_address.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/rational.h>
> +#include <linux/delay.h>
> +
> +static DEFINE_SPINLOCK(sun8i_display_lock);
> +
> +/* PLL3 (video) and PLL10 (de) */
> +struct clk_fact {
> +       struct clk_hw hw;
> +       void __iomem *reg;
> +};
> +#define to_clk_fact(_hw) container_of(_hw, struct clk_fact, hw)

What does fact stand for?

> +
> +#define SUN8I_PLL3_MSHIFT      0
> +#define SUN8I_PLL3_MMASK       0x0f

You can use GENMASK for these. Note that GENMASK is inclusive on both ends.

> +#define SUN8I_PLL3_NSHIFT      8
> +#define SUN8I_PLL3_NMASK       0x7f
> +#define SUN8I_PLL3_MODE_SEL    0x01000000
> +#define SUN8I_PLL3_FRAC_CLK    0x02000000

Please use the BIT() macros.

> +
> +static int sun8i_pll3_get_fact(unsigned long rate,
> +                       unsigned long parent_rate,
> +                       unsigned long *n, unsigned long *m)
> +{
> +       if (rate == 297000000)
> +               return 1;
> +       if (rate == 270000000)
> +               return 0;
> +       rational_best_approximation(rate, parent_rate,
> +                               SUN8I_PLL3_NMASK + 1, SUN8I_PLL3_MMASK + 1,
> +                               n, m);
> +       return -1;
> +}
> +
> +static unsigned long sun8i_pll3_recalc_rate(struct clk_hw *hw,
> +                                       unsigned long parent_rate)
> +{
> +       struct clk_fact *fact = to_clk_fact(hw);
> +       u32 reg;
> +       u32 n, m;
> +
> +       reg = readl(fact->reg);
> +       if (reg & SUN8I_PLL3_MODE_SEL) {
> +               n = (reg >> SUN8I_PLL3_NSHIFT) & SUN8I_PLL3_NMASK;
> +               m = (reg >> SUN8I_PLL3_MSHIFT) & SUN8I_PLL3_MMASK;
> +               return parent_rate / (m + 1) * (n + 1);
> +       }
> +       if (reg & SUN8I_PLL3_FRAC_CLK)
> +               return 297000000;
> +       return 270000000;
> +}
> +
> +static long sun8i_pll3_round_rate(struct clk_hw *hw, unsigned long rate,
> +                               unsigned long *parent_rate)
> +{
> +       int frac;
> +       unsigned long n, m;
> +
> +       frac = sun8i_pll3_get_fact(rate, *parent_rate, &n, &m);
> +       if (frac == 1)
> +               return 297000000;
> +       if (frac == 0)
> +               return 270000000;
> +       return (*parent_rate * n) / m;

The ordering is different from the one in recalc_rate. Considering
integer rounding, would the results be different?

> +}
> +
> +static int sun8i_pll3_set_rate(struct clk_hw *hw, unsigned long rate,
> +                       unsigned long parent_rate)
> +{
> +       struct clk_fact *fact = to_clk_fact(hw);
> +       u32 reg;
> +       int frac;
> +       unsigned long n, m;
> +
> +       reg = readl(fact->reg) &
> +                       ~(SUN8I_PLL3_MODE_SEL |
> +                         SUN8I_PLL3_FRAC_CLK |
> +                         (SUN8I_PLL3_NMASK << SUN8I_PLL3_NSHIFT) |
> +                         (SUN8I_PLL3_MMASK << SUN8I_PLL3_MSHIFT));
> +
> +       frac = sun8i_pll3_get_fact(rate, parent_rate, &n, &m);
> +       if (frac == 1)
> +               reg |= SUN8I_PLL3_FRAC_CLK;     /* 297MHz */
> +       else if (frac == 0)
> +               ;                               /* 270MHz */
> +       else
> +               reg |= SUN8I_PLL3_MODE_SEL |
> +                       ((n - 1) << SUN8I_PLL3_NSHIFT) |
> +                       ((m - 1) << SUN8I_PLL3_MSHIFT);
> +
> +       writel(reg, fact->reg);
> +
> +       /* delay 500us so pll stabilizes */
> +       __delay(500);

Bit 28 indicates PLL lock, please use readl_poll_timeout() to check it.

> +
> +       return 0;
> +}
> +
> +static const struct clk_ops clk_sun8i_pll3_fact_ops = {
> +       .recalc_rate = sun8i_pll3_recalc_rate,
> +       .round_rate = sun8i_pll3_round_rate,
> +       .set_rate = sun8i_pll3_set_rate,
> +};
> +
> +static void __init sun8i_pll3_setup(struct device_node *node)
> +{
> +       const char *clk_name = node->name, *parent;
> +       struct clk_fact *fact;
> +       struct clk_gate *gate;
> +       void __iomem *reg;
> +       struct clk *clk;
> +
> +       of_property_read_string(node, "clock-output-names", &clk_name);
> +       parent = of_clk_get_parent_name(node, 0);
> +
> +       reg = of_io_request_and_map(node, 0, of_node_full_name(node));
> +       if (IS_ERR(reg)) {
> +               pr_err("%s: Could not map the clock registers\n", clk_name);
> +               return;
> +       }
> +
> +       gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> +       if (!gate)
> +               return;

You should call release_mem_region() to release the iomem region you
requested with of_io_request_and_map().

> +
> +       gate->reg = reg;
> +       gate->bit_idx = 31;
> +       gate->lock = &sun8i_display_lock;
> +
> +       fact = kzalloc(sizeof(*fact), GFP_KERNEL);
> +       if (!fact)
> +               goto free_gate;
> +
> +       fact->reg = reg;
> +
> +       clk = clk_register_composite(NULL, clk_name,
> +                                    &parent, 1,
> +                                    NULL, NULL,
> +                                    &fact->hw, &clk_sun8i_pll3_fact_ops,
> +                                    &gate->hw, &clk_gate_ops,
> +                                    0);
> +       if (IS_ERR(clk)) {
> +               pr_err("%s: Couldn't register the clock\n", clk_name);
> +               goto free_fact;
> +       }
> +
> +       of_clk_add_provider(node, of_clk_src_simple_get, clk);

Would this fail?

> +
> +       return;
> +
> +free_fact:
> +       kfree(fact);
> +free_gate:
> +       kfree(gate);
> +}
> +
> +CLK_OF_DECLARE(sun8i_pll3, "allwinner,sun8i-pll3-clk", sun8i_pll3_setup);
> +
> +/* DE, TCON0, TVE, HDMI, DEINTERLACE  */
> +static void __init sun8i_display_setup(struct device_node *node)
> +{
> +       const char *clk_name = node->name;
> +       const char *parents[2];

There are as many as six parents for display mod clocks on A31.
A23/A33 mod clocks have holes in there parent array, as some
PLLs are missing. We may need a different driver to deal with
that.

> +       struct clk_mux *mux = NULL;
> +       struct clk_divider *div;
> +       struct clk_gate *gate;
> +       void __iomem *reg;
> +       struct clk *clk;
> +       int n;
> +
> +       of_property_read_string(node, "clock-output-names", &clk_name);
> +
> +       reg = of_io_request_and_map(node, 0, of_node_full_name(node));
> +       if (IS_ERR(reg)) {
> +               pr_err("%s: Could not map the clock registers\n", clk_name);
> +               return;
> +       }
> +
> +       n = of_clk_parent_fill(node, parents, ARRAY_SIZE(parents));
> +
> +       if (n > 1) {                            /* many possible sources */
> +               mux = kzalloc(sizeof(*mux), GFP_KERNEL);
> +               if (!mux)
> +                       return;

release_mem_region().

> +               mux->reg = reg;
> +               mux->shift = 24;
> +               mux->mask = 0x07;

Macros for these, please.

> +               mux->lock = &sun8i_display_lock;
> +       }
> +
> +       gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> +       if (!gate)
> +               goto free_gate;

Label name is confusing. Another "free_mux" wouldn't harm.

> +
> +       gate->reg = reg;
> +       gate->bit_idx = 31;
> +       gate->lock = &sun8i_display_lock;
> +
> +       div = kzalloc(sizeof(*div), GFP_KERNEL);
> +       if (!div)
> +               goto free_gate;
> +
> +       div->reg = reg;
> +       div->shift = 0;
> +       div->width = 4;

Macros, please.

> +       div->lock = &sun8i_display_lock;
> +
> +       clk = clk_register_composite(NULL, clk_name,
> +                                    parents, n,
> +                                    mux ? &mux->hw : NULL, &clk_mux_ops,
> +                                    &div->hw, &clk_divider_ops,
> +                                    &gate->hw, &clk_gate_ops,
> +                                    0);
> +       if (IS_ERR(clk)) {
> +               pr_err("%s: Couldn't register the clock\n", clk_name);
> +               goto free_div;
> +       }
> +
> +       of_clk_add_provider(node, of_clk_src_simple_get, clk);

Would this fail?

> +
> +       return;
> +
> +free_div:
> +       kfree(div);
> +free_gate:
> +       kfree(gate);
> +       kfree(mux);
> +}
> +
> +CLK_OF_DECLARE(sun8i_display, "allwinner,sun8i-display-clk", sun8i_display_setup);
> --
> 2.6.4


Regards
ChenYu

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
@ 2016-01-06  2:39     ` Chen-Yu Tsai
  0 siblings, 0 replies; 36+ messages in thread
From: Chen-Yu Tsai @ 2016-01-06  2:39 UTC (permalink / raw)
  To: Jean-Francois Moine
  Cc: Dave Airlie, dri-devel, Emilio López, Chen-Yu Tsai,
	Maxime Ripard, linux-arm-kernel

Hi,

First of all, please include the clk subsystem maintainers and the
linux-clk mailing list for all clk related patches.

On Wed, Jan 6, 2016 at 2:28 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
> Add the clock types which are used by the sun8i family for video.

These clocks first appeared in the A31.

> Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
> ---
>  drivers/clk/sunxi/Makefile            |   1 +
>  drivers/clk/sunxi/clk-sun8i-display.c | 247 ++++++++++++++++++++++++++++++++++

Please split this into 2 patches, and 2 files: one for PLL3, named
clk-sun6i-pll3.c, and one for the display mod clocks, named
clk-sun6i-display.c

>  2 files changed, 258 insertions(+)
>  create mode 100644 drivers/clk/sunxi/clk-sun8i-display.c
>
> diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
> index cb4c299..145c078 100644
> --- a/drivers/clk/sunxi/Makefile
> +++ b/drivers/clk/sunxi/Makefile
> @@ -10,6 +10,7 @@ obj-y += clk-a10-pll2.o
>  obj-y += clk-a20-gmac.o
>  obj-y += clk-mod0.o
>  obj-y += clk-simple-gates.o
> +obj-y += clk-sun8i-display.o
>  obj-y += clk-sun8i-mbus.o
>  obj-y += clk-sun9i-core.o
>  obj-y += clk-sun9i-mmc.o
> diff --git a/drivers/clk/sunxi/clk-sun8i-display.c b/drivers/clk/sunxi/clk-sun8i-display.c
> new file mode 100644
> index 0000000..eded572
> --- /dev/null
> +++ b/drivers/clk/sunxi/clk-sun8i-display.c
> @@ -0,0 +1,247 @@
> +/*
> + * Copyright 2015 Jean-Francois Moine <moinejf@free.fr>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/of_address.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/rational.h>
> +#include <linux/delay.h>
> +
> +static DEFINE_SPINLOCK(sun8i_display_lock);
> +
> +/* PLL3 (video) and PLL10 (de) */
> +struct clk_fact {
> +       struct clk_hw hw;
> +       void __iomem *reg;
> +};
> +#define to_clk_fact(_hw) container_of(_hw, struct clk_fact, hw)

What does fact stand for?

> +
> +#define SUN8I_PLL3_MSHIFT      0
> +#define SUN8I_PLL3_MMASK       0x0f

You can use GENMASK for these. Note that GENMASK is inclusive on both ends.

> +#define SUN8I_PLL3_NSHIFT      8
> +#define SUN8I_PLL3_NMASK       0x7f
> +#define SUN8I_PLL3_MODE_SEL    0x01000000
> +#define SUN8I_PLL3_FRAC_CLK    0x02000000

Please use the BIT() macros.

> +
> +static int sun8i_pll3_get_fact(unsigned long rate,
> +                       unsigned long parent_rate,
> +                       unsigned long *n, unsigned long *m)
> +{
> +       if (rate == 297000000)
> +               return 1;
> +       if (rate == 270000000)
> +               return 0;
> +       rational_best_approximation(rate, parent_rate,
> +                               SUN8I_PLL3_NMASK + 1, SUN8I_PLL3_MMASK + 1,
> +                               n, m);
> +       return -1;
> +}
> +
> +static unsigned long sun8i_pll3_recalc_rate(struct clk_hw *hw,
> +                                       unsigned long parent_rate)
> +{
> +       struct clk_fact *fact = to_clk_fact(hw);
> +       u32 reg;
> +       u32 n, m;
> +
> +       reg = readl(fact->reg);
> +       if (reg & SUN8I_PLL3_MODE_SEL) {
> +               n = (reg >> SUN8I_PLL3_NSHIFT) & SUN8I_PLL3_NMASK;
> +               m = (reg >> SUN8I_PLL3_MSHIFT) & SUN8I_PLL3_MMASK;
> +               return parent_rate / (m + 1) * (n + 1);
> +       }
> +       if (reg & SUN8I_PLL3_FRAC_CLK)
> +               return 297000000;
> +       return 270000000;
> +}
> +
> +static long sun8i_pll3_round_rate(struct clk_hw *hw, unsigned long rate,
> +                               unsigned long *parent_rate)
> +{
> +       int frac;
> +       unsigned long n, m;
> +
> +       frac = sun8i_pll3_get_fact(rate, *parent_rate, &n, &m);
> +       if (frac == 1)
> +               return 297000000;
> +       if (frac == 0)
> +               return 270000000;
> +       return (*parent_rate * n) / m;

The ordering is different from the one in recalc_rate. Considering
integer rounding, would the results be different?

> +}
> +
> +static int sun8i_pll3_set_rate(struct clk_hw *hw, unsigned long rate,
> +                       unsigned long parent_rate)
> +{
> +       struct clk_fact *fact = to_clk_fact(hw);
> +       u32 reg;
> +       int frac;
> +       unsigned long n, m;
> +
> +       reg = readl(fact->reg) &
> +                       ~(SUN8I_PLL3_MODE_SEL |
> +                         SUN8I_PLL3_FRAC_CLK |
> +                         (SUN8I_PLL3_NMASK << SUN8I_PLL3_NSHIFT) |
> +                         (SUN8I_PLL3_MMASK << SUN8I_PLL3_MSHIFT));
> +
> +       frac = sun8i_pll3_get_fact(rate, parent_rate, &n, &m);
> +       if (frac == 1)
> +               reg |= SUN8I_PLL3_FRAC_CLK;     /* 297MHz */
> +       else if (frac == 0)
> +               ;                               /* 270MHz */
> +       else
> +               reg |= SUN8I_PLL3_MODE_SEL |
> +                       ((n - 1) << SUN8I_PLL3_NSHIFT) |
> +                       ((m - 1) << SUN8I_PLL3_MSHIFT);
> +
> +       writel(reg, fact->reg);
> +
> +       /* delay 500us so pll stabilizes */
> +       __delay(500);

Bit 28 indicates PLL lock, please use readl_poll_timeout() to check it.

> +
> +       return 0;
> +}
> +
> +static const struct clk_ops clk_sun8i_pll3_fact_ops = {
> +       .recalc_rate = sun8i_pll3_recalc_rate,
> +       .round_rate = sun8i_pll3_round_rate,
> +       .set_rate = sun8i_pll3_set_rate,
> +};
> +
> +static void __init sun8i_pll3_setup(struct device_node *node)
> +{
> +       const char *clk_name = node->name, *parent;
> +       struct clk_fact *fact;
> +       struct clk_gate *gate;
> +       void __iomem *reg;
> +       struct clk *clk;
> +
> +       of_property_read_string(node, "clock-output-names", &clk_name);
> +       parent = of_clk_get_parent_name(node, 0);
> +
> +       reg = of_io_request_and_map(node, 0, of_node_full_name(node));
> +       if (IS_ERR(reg)) {
> +               pr_err("%s: Could not map the clock registers\n", clk_name);
> +               return;
> +       }
> +
> +       gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> +       if (!gate)
> +               return;

You should call release_mem_region() to release the iomem region you
requested with of_io_request_and_map().

> +
> +       gate->reg = reg;
> +       gate->bit_idx = 31;
> +       gate->lock = &sun8i_display_lock;
> +
> +       fact = kzalloc(sizeof(*fact), GFP_KERNEL);
> +       if (!fact)
> +               goto free_gate;
> +
> +       fact->reg = reg;
> +
> +       clk = clk_register_composite(NULL, clk_name,
> +                                    &parent, 1,
> +                                    NULL, NULL,
> +                                    &fact->hw, &clk_sun8i_pll3_fact_ops,
> +                                    &gate->hw, &clk_gate_ops,
> +                                    0);
> +       if (IS_ERR(clk)) {
> +               pr_err("%s: Couldn't register the clock\n", clk_name);
> +               goto free_fact;
> +       }
> +
> +       of_clk_add_provider(node, of_clk_src_simple_get, clk);

Would this fail?

> +
> +       return;
> +
> +free_fact:
> +       kfree(fact);
> +free_gate:
> +       kfree(gate);
> +}
> +
> +CLK_OF_DECLARE(sun8i_pll3, "allwinner,sun8i-pll3-clk", sun8i_pll3_setup);
> +
> +/* DE, TCON0, TVE, HDMI, DEINTERLACE  */
> +static void __init sun8i_display_setup(struct device_node *node)
> +{
> +       const char *clk_name = node->name;
> +       const char *parents[2];

There are as many as six parents for display mod clocks on A31.
A23/A33 mod clocks have holes in there parent array, as some
PLLs are missing. We may need a different driver to deal with
that.

> +       struct clk_mux *mux = NULL;
> +       struct clk_divider *div;
> +       struct clk_gate *gate;
> +       void __iomem *reg;
> +       struct clk *clk;
> +       int n;
> +
> +       of_property_read_string(node, "clock-output-names", &clk_name);
> +
> +       reg = of_io_request_and_map(node, 0, of_node_full_name(node));
> +       if (IS_ERR(reg)) {
> +               pr_err("%s: Could not map the clock registers\n", clk_name);
> +               return;
> +       }
> +
> +       n = of_clk_parent_fill(node, parents, ARRAY_SIZE(parents));
> +
> +       if (n > 1) {                            /* many possible sources */
> +               mux = kzalloc(sizeof(*mux), GFP_KERNEL);
> +               if (!mux)
> +                       return;

release_mem_region().

> +               mux->reg = reg;
> +               mux->shift = 24;
> +               mux->mask = 0x07;

Macros for these, please.

> +               mux->lock = &sun8i_display_lock;
> +       }
> +
> +       gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> +       if (!gate)
> +               goto free_gate;

Label name is confusing. Another "free_mux" wouldn't harm.

> +
> +       gate->reg = reg;
> +       gate->bit_idx = 31;
> +       gate->lock = &sun8i_display_lock;
> +
> +       div = kzalloc(sizeof(*div), GFP_KERNEL);
> +       if (!div)
> +               goto free_gate;
> +
> +       div->reg = reg;
> +       div->shift = 0;
> +       div->width = 4;

Macros, please.

> +       div->lock = &sun8i_display_lock;
> +
> +       clk = clk_register_composite(NULL, clk_name,
> +                                    parents, n,
> +                                    mux ? &mux->hw : NULL, &clk_mux_ops,
> +                                    &div->hw, &clk_divider_ops,
> +                                    &gate->hw, &clk_gate_ops,
> +                                    0);
> +       if (IS_ERR(clk)) {
> +               pr_err("%s: Couldn't register the clock\n", clk_name);
> +               goto free_div;
> +       }
> +
> +       of_clk_add_provider(node, of_clk_src_simple_get, clk);

Would this fail?

> +
> +       return;
> +
> +free_div:
> +       kfree(div);
> +free_gate:
> +       kfree(gate);
> +       kfree(mux);
> +}
> +
> +CLK_OF_DECLARE(sun8i_display, "allwinner,sun8i-display-clk", sun8i_display_setup);
> --
> 2.6.4


Regards
ChenYu

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2
  2016-01-05 18:40   ` Jean-Francois Moine
@ 2016-01-06 20:41     ` Jens Kuske
  -1 siblings, 0 replies; 36+ messages in thread
From: Jens Kuske @ 2016-01-06 20:41 UTC (permalink / raw)
  To: linux-arm-kernel

On 05/01/16 19:40, Jean-Francois Moine wrote:
[snip]
> diff --git a/drivers/gpu/drm/sunxi/de2_hdmi_h3.c b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
> new file mode 100644
> index 0000000..c54b090
> --- /dev/null
> +++ b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
> @@ -0,0 +1,478 @@
> +/*
> + * Allwinner H3 HDMI lowlevel functions
> + *
> + * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
> + *
> + * Adapted from the file
> + *	lichee/linux-3.4/drivers/video/sunxi/disp2/hdmi/aw/hdmi_bsp_sun8iw7.c
> + * with no license nor copyright.
> + */
> +
> +#include <drm/drmP.h>
> +
> +#include "de2_hdmi.h"
> +#include "de2_hdmi_h3.h"
> +
> +struct para_tab {
> +	u32 para[19];
> +};
> +
> +struct pcm_sf {
> +	u32 	sf;
> +	unsigned char	cs_sf;
> +};
> +
> +/*
> + * [0] = vic (cea Video ID)
> + * [1] used in hdmi_phy_set / bsp_hdmi_audio
> + * [2..17] used in bsp_hdmi_video
> + */
> +static const struct para_tab ptbl[] = {
> +	{{  6,  1, 1,  1,  5,  3, 0, 1, 4, 0, 0, 160,  20,  38, 124, 240, 22, 0, 0}},
> +	{{ 21, 11, 1,  1,  5,  3, 1, 1, 2, 0, 0, 160,  32,  24, 126,  32, 24, 0, 0}},
> +	{{  2, 11, 0,  0,  2,  6, 1, 0, 9, 0, 0, 208, 138,  16,  62, 224, 45, 0, 0}},
> +	{{ 17, 11, 0,  0,  2,  5, 2, 0, 5, 0, 0, 208, 144,  12,  64,  64, 49, 0, 0}},
> +	{{ 19,  4, 0, 96,  5,  5, 2, 2, 5, 1, 0,   0, 188, 184,  40, 208, 30, 1, 1}},
> +	{{  4,  4, 0, 96,  5,  5, 2, 1, 5, 0, 0,   0, 114, 110,  40, 208, 30, 1, 1}},
> +	{{ 20,  4, 0, 97,  7,  5, 4, 2, 2, 2, 0, 128, 208,  16,  44,  56, 22, 1, 1}},
> +	{{  5,  4, 0, 97,  7,  5, 4, 1, 2, 0, 0, 128,  24,  88,  44,  56, 22, 1, 1}},
> +	{{ 31,  2, 0, 96,  7,  5, 4, 2, 4, 2, 0, 128, 208,  16,  44,  56, 45, 1, 1}},
> +	{{ 16,  2, 0, 96,  7,  5, 4, 1, 4, 0, 0, 128,  24,  88,  44,  56, 45, 1, 1}},
> +	{{ 32,  4, 0, 96,  7,  5, 4, 3, 4, 2, 0, 128,  62, 126,  44,  56, 45, 1, 1}},
> +	{{ 33,  4, 0,  0,  7,  5, 4, 2, 4, 2, 0, 128, 208,  16,  44,  56, 45, 1, 1}},
> +	{{ 34,  4, 0,  0,  7,  5, 4, 1, 4, 0, 0, 128,  24,  88,  44,  56, 45, 1, 1}},
> +	{{160,  2, 0, 96,  7,  5, 8, 3, 4, 2, 0, 128,  62, 126,  44, 157, 45, 1, 1}},
> +	{{147,  2, 0, 96,  5,  5, 5, 2, 5, 1, 0,   0, 188, 184,  40, 190, 30, 1, 1}},
> +	{{132,  2, 0, 96,  5,  5, 5, 1, 5, 0, 0,   0, 114, 110,  40, 160, 30, 1, 1}},
> +	{{257,  1, 0, 96, 15, 10, 8, 2, 8, 0, 0,   0,  48, 176,  88, 112, 90, 1, 1}},
> +	{{258,  1, 0, 96, 15, 10, 8, 5, 8, 4, 0,   0, 160,  32,  88, 112, 90, 1, 1}},
> +};

Hi,

did you try to figure out what the values in this table actually mean?

I tried it some time ago because I wanted to add some more resolutions
to 3.4, but never got further than what I'll add below. But it might be
useful now, to get rid of at least some of the magic constants.
With some more work (what does [1] mean?) we might be able to drop the
entire table and use the values from drm_display_mode directly instead.

unsure (hard to verify):
[2] = pixel repetition (1 = 2x)
[3] = bit0: interlaced (no idea about the 96/0x60 yet)
[17] = something csc related
[18] = unused

pretty sure (verified by comparing with timings):
[4] = horizontal active (high byte)
[5] = vsync width
[6] = vertical active (high byte)
[7] = horizontal blanking (high byte)
[8] = vertical front porch
[9] = horizontal front porch (high byte)
[10] = hsync width (high byte)
[11] = horizontal active (low byte)
[12] = horizontal blanking (low byte)
[13] = horizontal front porch (low byte)
[14] = hsync width (low byte)
[15] = vertical active (low byte)
[16] = vertical blanking


Generally, nice work. I only skimmed over it by now, but I hope to test
and review the hardware related parts more intensively sometime.

Regards,
Jens

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2
@ 2016-01-06 20:41     ` Jens Kuske
  0 siblings, 0 replies; 36+ messages in thread
From: Jens Kuske @ 2016-01-06 20:41 UTC (permalink / raw)
  To: Jean-Francois Moine
  Cc: Dave Airlie, dri-devel, Emilio López, Chen-Yu Tsai,
	Maxime Ripard, linux-arm-kernel

On 05/01/16 19:40, Jean-Francois Moine wrote:
[snip]
> diff --git a/drivers/gpu/drm/sunxi/de2_hdmi_h3.c b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
> new file mode 100644
> index 0000000..c54b090
> --- /dev/null
> +++ b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
> @@ -0,0 +1,478 @@
> +/*
> + * Allwinner H3 HDMI lowlevel functions
> + *
> + * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
> + *
> + * Adapted from the file
> + *	lichee/linux-3.4/drivers/video/sunxi/disp2/hdmi/aw/hdmi_bsp_sun8iw7.c
> + * with no license nor copyright.
> + */
> +
> +#include <drm/drmP.h>
> +
> +#include "de2_hdmi.h"
> +#include "de2_hdmi_h3.h"
> +
> +struct para_tab {
> +	u32 para[19];
> +};
> +
> +struct pcm_sf {
> +	u32 	sf;
> +	unsigned char	cs_sf;
> +};
> +
> +/*
> + * [0] = vic (cea Video ID)
> + * [1] used in hdmi_phy_set / bsp_hdmi_audio
> + * [2..17] used in bsp_hdmi_video
> + */
> +static const struct para_tab ptbl[] = {
> +	{{  6,  1, 1,  1,  5,  3, 0, 1, 4, 0, 0, 160,  20,  38, 124, 240, 22, 0, 0}},
> +	{{ 21, 11, 1,  1,  5,  3, 1, 1, 2, 0, 0, 160,  32,  24, 126,  32, 24, 0, 0}},
> +	{{  2, 11, 0,  0,  2,  6, 1, 0, 9, 0, 0, 208, 138,  16,  62, 224, 45, 0, 0}},
> +	{{ 17, 11, 0,  0,  2,  5, 2, 0, 5, 0, 0, 208, 144,  12,  64,  64, 49, 0, 0}},
> +	{{ 19,  4, 0, 96,  5,  5, 2, 2, 5, 1, 0,   0, 188, 184,  40, 208, 30, 1, 1}},
> +	{{  4,  4, 0, 96,  5,  5, 2, 1, 5, 0, 0,   0, 114, 110,  40, 208, 30, 1, 1}},
> +	{{ 20,  4, 0, 97,  7,  5, 4, 2, 2, 2, 0, 128, 208,  16,  44,  56, 22, 1, 1}},
> +	{{  5,  4, 0, 97,  7,  5, 4, 1, 2, 0, 0, 128,  24,  88,  44,  56, 22, 1, 1}},
> +	{{ 31,  2, 0, 96,  7,  5, 4, 2, 4, 2, 0, 128, 208,  16,  44,  56, 45, 1, 1}},
> +	{{ 16,  2, 0, 96,  7,  5, 4, 1, 4, 0, 0, 128,  24,  88,  44,  56, 45, 1, 1}},
> +	{{ 32,  4, 0, 96,  7,  5, 4, 3, 4, 2, 0, 128,  62, 126,  44,  56, 45, 1, 1}},
> +	{{ 33,  4, 0,  0,  7,  5, 4, 2, 4, 2, 0, 128, 208,  16,  44,  56, 45, 1, 1}},
> +	{{ 34,  4, 0,  0,  7,  5, 4, 1, 4, 0, 0, 128,  24,  88,  44,  56, 45, 1, 1}},
> +	{{160,  2, 0, 96,  7,  5, 8, 3, 4, 2, 0, 128,  62, 126,  44, 157, 45, 1, 1}},
> +	{{147,  2, 0, 96,  5,  5, 5, 2, 5, 1, 0,   0, 188, 184,  40, 190, 30, 1, 1}},
> +	{{132,  2, 0, 96,  5,  5, 5, 1, 5, 0, 0,   0, 114, 110,  40, 160, 30, 1, 1}},
> +	{{257,  1, 0, 96, 15, 10, 8, 2, 8, 0, 0,   0,  48, 176,  88, 112, 90, 1, 1}},
> +	{{258,  1, 0, 96, 15, 10, 8, 5, 8, 4, 0,   0, 160,  32,  88, 112, 90, 1, 1}},
> +};

Hi,

did you try to figure out what the values in this table actually mean?

I tried it some time ago because I wanted to add some more resolutions
to 3.4, but never got further than what I'll add below. But it might be
useful now, to get rid of at least some of the magic constants.
With some more work (what does [1] mean?) we might be able to drop the
entire table and use the values from drm_display_mode directly instead.

unsure (hard to verify):
[2] = pixel repetition (1 = 2x)
[3] = bit0: interlaced (no idea about the 96/0x60 yet)
[17] = something csc related
[18] = unused

pretty sure (verified by comparing with timings):
[4] = horizontal active (high byte)
[5] = vsync width
[6] = vertical active (high byte)
[7] = horizontal blanking (high byte)
[8] = vertical front porch
[9] = horizontal front porch (high byte)
[10] = hsync width (high byte)
[11] = horizontal active (low byte)
[12] = horizontal blanking (low byte)
[13] = horizontal front porch (low byte)
[14] = hsync width (low byte)
[15] = vertical active (low byte)
[16] = vertical blanking


Generally, nice work. I only skimmed over it by now, but I hope to test
and review the hardware related parts more intensively sometime.

Regards,
Jens

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 0/2] Add a display driver to the Allwinner H3
  2016-01-05 19:15 ` Jean-Francois Moine
@ 2016-01-06 21:20   ` Maxime Ripard
  -1 siblings, 0 replies; 36+ messages in thread
From: Maxime Ripard @ 2016-01-06 21:20 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

Thanks a lot for your work!

On Tue, Jan 05, 2016 at 08:15:49PM +0100, Jean-Francois Moine wrote:
> The proposed DRM driver works on a Orange PI 2 with a kernel 4.4-rc1
> and the H3 patches found in Hans de Goede's GIT repository.
> 
> As there is no documentation about the DE2 nor about the HDMI which
> are found in the H3, this driver has been built from Allwiiner's
> sources.

That's unfortunate :/

Have you checked in the A64 BSP if there was some useful information?

> So, there may be license problems, especially for the file
> de2_hdmi_h3.c which contains a lot of magic values.

I guess it's the biggest issue with your code right now. What licenses
issues are we talking about here?

Remember that having your Signed-off-by tag on a commit means that you
certify that you have the right to submit the patch under the license
you indicate in the files added and / or modified.

If you don't have such right, for example because you don't have the
right and / or authorization from the initial author to re-license
that code, you cannot put your SoB.

> The associated DT and documentation will be submitted when the H3 DTs
> will be in the kernel.

Having the DT binding documentation would really help in the review.

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20160106/80c37335/attachment.sig>

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 0/2] Add a display driver to the Allwinner H3
@ 2016-01-06 21:20   ` Maxime Ripard
  0 siblings, 0 replies; 36+ messages in thread
From: Maxime Ripard @ 2016-01-06 21:20 UTC (permalink / raw)
  To: Jean-Francois Moine
  Cc: Dave Airlie, Emilio López, Chen-Yu Tsai, linux-arm-kernel,
	dri-devel


[-- Attachment #1.1: Type: text/plain, Size: 1403 bytes --]

Hi,

Thanks a lot for your work!

On Tue, Jan 05, 2016 at 08:15:49PM +0100, Jean-Francois Moine wrote:
> The proposed DRM driver works on a Orange PI 2 with a kernel 4.4-rc1
> and the H3 patches found in Hans de Goede's GIT repository.
> 
> As there is no documentation about the DE2 nor about the HDMI which
> are found in the H3, this driver has been built from Allwiiner's
> sources.

That's unfortunate :/

Have you checked in the A64 BSP if there was some useful information?

> So, there may be license problems, especially for the file
> de2_hdmi_h3.c which contains a lot of magic values.

I guess it's the biggest issue with your code right now. What licenses
issues are we talking about here?

Remember that having your Signed-off-by tag on a commit means that you
certify that you have the right to submit the patch under the license
you indicate in the files added and / or modified.

If you don't have such right, for example because you don't have the
right and / or authorization from the initial author to re-license
that code, you cannot put your SoB.

> The associated DT and documentation will be submitted when the H3 DTs
> will be in the kernel.

Having the DT binding documentation would really help in the review.

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 0/2] Add a display driver to the Allwinner H3
  2016-01-06 21:20   ` Maxime Ripard
@ 2016-01-08 17:13     ` Jean-Francois Moine
  -1 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-08 17:13 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 6 Jan 2016 22:20:46 +0100
Maxime Ripard <maxime.ripard@free-electrons.com> wrote:

> > As there is no documentation about the DE2 nor about the HDMI which
> > are found in the H3, this driver has been built from Allwiiner's
> > sources.
> 
> That's unfortunate :/
> 
> Have you checked in the A64 BSP if there was some useful information?

Same as the H3: no more information about the display system.

> > So, there may be license problems, especially for the file
> > de2_hdmi_h3.c which contains a lot of magic values.
> 
> I guess it's the biggest issue with your code right now. What licenses
> issues are we talking about here?

The documentation about the H3, as the other Allwinner documentations,
starts with:

	This documentation is the original work and copyrighted
	property of Allwinner Technology ("Allwinner"). Reproduction in
	whole or in part must obtain the written approval of Allwinner
	and give clear acknowledgement to the copyright owner.

Then, the DE2 sources contain only:

	All Winner Tech, All Right Reserved. 2014-2015 Copyright (c)

Eventually, there is no copyright/author/history in the HDMI sources.

> Remember that having your Signed-off-by tag on a commit means that you
> certify that you have the right to submit the patch under the license
> you indicate in the files added and / or modified.
> 
> If you don't have such right, for example because you don't have the
> right and / or authorization from the initial author to re-license
> that code, you cannot put your SoB.

OK, sorry. So, please, ignore the whole patch series.

> > The associated DT and documentation will be submitted when the H3 DTs
> > will be in the kernel.
> 
> Having the DT binding documentation would really help in the review.

Here it is, as a sunxi specific documentation, but it could be generic.

--- /dev/null	1970-01-01 01:00:10.240000002 +0100
+++ Documentation/devicetree/bindings/display/sunxi.txt	2016-01-08 17:48:01.775903901 +0100
@@ -0,0 +1,107 @@
+Allwinner sunxi display subsystem
+=================================
+
+The sunxi display subsystems contain a display controller (DE),
+one or two LCD controllers (TCON) and their external interfaces.
+
+Display controller
+==================
+
+Required properties:
+
+- compatible: value should be one of the following
+		"allwinner,sun8i-h3-display-engine"
+
+- clocks: must include clock specifiers corresponding to entries in the
+		clock-names property.
+
+- clock-names: must contain
+		gate: for DE activation
+		clock: DE clock
+
+- resets: phandle to the reset of the device
+
+- ports: phandle's to the LCD ports
+
+LCD controller
+==============
+
+Required properties:
+
+- compatible: value should be one of the following
+		"allwinner,sun8i-h3-lcd"
+
+- clocks: must include clock specifiers corresponding to entries in the
+		clock-names property.
+
+- clock-names: must contain
+		gate: for LCD activation
+		clock: pixel clock
+
+- resets: phandle to the reset of the device
+
+- port: port node with endpoint definitions as defined in
+	Documentation/devicetree/bindings/media/video-interfaces.txt
+
+HDMI support
+============
+
+Required properties:
+
+- compatible: value should be one of the following
+		"allwinner,sun8i-h3-hdmi"
+
+- clocks: must include clock specifiers corresponding to entries in the
+		clock-names property.
+
+- clock-names: must contain
+		gate: for HDMI activation
+		clock: pixel clock
+		ddc-clock: for the HDMI protocol
+
+- resets: one or two phandle's to the reset of the device
+
+- port: port node with endpoint definitions as defined in
+	Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example:
+
+	de: de-controller at 01000000 {
+		compatible = "allwinner,sun8i-h3-display-engine";
+		...
+		clocks = <&bus_gates 44>, <&de_clk>;
+		clock-names = "gate", "clock";
+		resets = <&bus_rst 44>;
+		ports = <&lcd0_p>;
+	};
+
+	lcd0: lcd-controller at 01c0c000 {
+		compatible = "allwinner,sun8i-h3-lcd";
+		...
+		clocks = <&bus_gates 35>, <&tcon0_clk>;
+		clock-names = "gate", "clock";
+		resets = <&bus_rst 35>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		lcd0_p: port {
+			lcd0_ep: endpoint {
+				remote-endpoint = <&hdmi_ep>;
+			};
+		};
+	};
+
+	hdmi: hdmi at 01ee0000 {
+		compatible = "allwinner,sun8i-h3-hdmi";
+		...
+		clocks = <&bus_gates 43>, <&hdmi_clk>,
+			 <&hdmi_slow_clk 31>;
+		clock-names = "gate", "clock", "ddc-clock";
+		resets = <&bus_rst 42>, <&bus_rst 43>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		port {
+			hdmi_ep: endpoint {
+				remote-endpoint = <&lcd0_ep>;
+			};
+		};
+	};

-- 
Ken ar c'henta?	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 0/2] Add a display driver to the Allwinner H3
@ 2016-01-08 17:13     ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-08 17:13 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Emilio López, Chen-Yu Tsai, linux-arm-kernel, dri-devel

On Wed, 6 Jan 2016 22:20:46 +0100
Maxime Ripard <maxime.ripard@free-electrons.com> wrote:

> > As there is no documentation about the DE2 nor about the HDMI which
> > are found in the H3, this driver has been built from Allwiiner's
> > sources.
> 
> That's unfortunate :/
> 
> Have you checked in the A64 BSP if there was some useful information?

Same as the H3: no more information about the display system.

> > So, there may be license problems, especially for the file
> > de2_hdmi_h3.c which contains a lot of magic values.
> 
> I guess it's the biggest issue with your code right now. What licenses
> issues are we talking about here?

The documentation about the H3, as the other Allwinner documentations,
starts with:

	This documentation is the original work and copyrighted
	property of Allwinner Technology ("Allwinner"). Reproduction in
	whole or in part must obtain the written approval of Allwinner
	and give clear acknowledgement to the copyright owner.

Then, the DE2 sources contain only:

	All Winner Tech, All Right Reserved. 2014-2015 Copyright (c)

Eventually, there is no copyright/author/history in the HDMI sources.

> Remember that having your Signed-off-by tag on a commit means that you
> certify that you have the right to submit the patch under the license
> you indicate in the files added and / or modified.
> 
> If you don't have such right, for example because you don't have the
> right and / or authorization from the initial author to re-license
> that code, you cannot put your SoB.

OK, sorry. So, please, ignore the whole patch series.

> > The associated DT and documentation will be submitted when the H3 DTs
> > will be in the kernel.
> 
> Having the DT binding documentation would really help in the review.

Here it is, as a sunxi specific documentation, but it could be generic.

--- /dev/null	1970-01-01 01:00:10.240000002 +0100
+++ Documentation/devicetree/bindings/display/sunxi.txt	2016-01-08 17:48:01.775903901 +0100
@@ -0,0 +1,107 @@
+Allwinner sunxi display subsystem
+=================================
+
+The sunxi display subsystems contain a display controller (DE),
+one or two LCD controllers (TCON) and their external interfaces.
+
+Display controller
+==================
+
+Required properties:
+
+- compatible: value should be one of the following
+		"allwinner,sun8i-h3-display-engine"
+
+- clocks: must include clock specifiers corresponding to entries in the
+		clock-names property.
+
+- clock-names: must contain
+		gate: for DE activation
+		clock: DE clock
+
+- resets: phandle to the reset of the device
+
+- ports: phandle's to the LCD ports
+
+LCD controller
+==============
+
+Required properties:
+
+- compatible: value should be one of the following
+		"allwinner,sun8i-h3-lcd"
+
+- clocks: must include clock specifiers corresponding to entries in the
+		clock-names property.
+
+- clock-names: must contain
+		gate: for LCD activation
+		clock: pixel clock
+
+- resets: phandle to the reset of the device
+
+- port: port node with endpoint definitions as defined in
+	Documentation/devicetree/bindings/media/video-interfaces.txt
+
+HDMI support
+============
+
+Required properties:
+
+- compatible: value should be one of the following
+		"allwinner,sun8i-h3-hdmi"
+
+- clocks: must include clock specifiers corresponding to entries in the
+		clock-names property.
+
+- clock-names: must contain
+		gate: for HDMI activation
+		clock: pixel clock
+		ddc-clock: for the HDMI protocol
+
+- resets: one or two phandle's to the reset of the device
+
+- port: port node with endpoint definitions as defined in
+	Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example:
+
+	de: de-controller@01000000 {
+		compatible = "allwinner,sun8i-h3-display-engine";
+		...
+		clocks = <&bus_gates 44>, <&de_clk>;
+		clock-names = "gate", "clock";
+		resets = <&bus_rst 44>;
+		ports = <&lcd0_p>;
+	};
+
+	lcd0: lcd-controller@01c0c000 {
+		compatible = "allwinner,sun8i-h3-lcd";
+		...
+		clocks = <&bus_gates 35>, <&tcon0_clk>;
+		clock-names = "gate", "clock";
+		resets = <&bus_rst 35>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		lcd0_p: port {
+			lcd0_ep: endpoint {
+				remote-endpoint = <&hdmi_ep>;
+			};
+		};
+	};
+
+	hdmi: hdmi@01ee0000 {
+		compatible = "allwinner,sun8i-h3-hdmi";
+		...
+		clocks = <&bus_gates 43>, <&hdmi_clk>,
+			 <&hdmi_slow_clk 31>;
+		clock-names = "gate", "clock", "ddc-clock";
+		resets = <&bus_rst 42>, <&bus_rst 43>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		port {
+			hdmi_ep: endpoint {
+				remote-endpoint = <&lcd0_ep>;
+			};
+		};
+	};

-- 
Ken ar c'hentañ	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
  2016-01-06  2:39     ` Chen-Yu Tsai
@ 2016-01-08 17:50       ` Jean-Francois Moine
  -1 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-08 17:50 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 6 Jan 2016 10:39:51 +0800
Chen-Yu Tsai <wens@csie.org> wrote:

> First of all, please include the clk subsystem maintainers and the
> linux-clk mailing list for all clk related patches.

OK.

> On Wed, Jan 6, 2016 at 2:28 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
> > Add the clock types which are used by the sun8i family for video.
> 
> These clocks first appeared in the A31.

Sorry, I have the documentation of only some sun8i SoCs.

> > Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
> > ---
> >  drivers/clk/sunxi/Makefile            |   1 +
> >  drivers/clk/sunxi/clk-sun8i-display.c | 247 ++++++++++++++++++++++++++++++++++
> 
> Please split this into 2 patches, and 2 files: one for PLL3, named
> clk-sun6i-pll3.c, and one for the display mod clocks, named
> clk-sun6i-display.c

No problem.

	[snip]
> > diff --git a/drivers/clk/sunxi/clk-sun8i-display.c b/drivers/clk/sunxi/clk-sun8i-display.c
> > new file mode 100644
> > index 0000000..eded572
> > --- /dev/null
> > +++ b/drivers/clk/sunxi/clk-sun8i-display.c
> > @@ -0,0 +1,247 @@
> > +/*
> > + * Copyright 2015 Jean-Francois Moine <moinejf@free.fr>
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; either version 2 of the License, or
> > + * (at your option) any later version.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + * GNU General Public License for more details.
> > + */
> > +
> > +#include <linux/clk-provider.h>
> > +#include <linux/of_address.h>
> > +#include <linux/slab.h>
> > +#include <linux/spinlock.h>
> > +#include <linux/rational.h>
> > +#include <linux/delay.h>
> > +
> > +static DEFINE_SPINLOCK(sun8i_display_lock);
> > +
> > +/* PLL3 (video) and PLL10 (de) */
> > +struct clk_fact {
> > +       struct clk_hw hw;
> > +       void __iomem *reg;
> > +};
> > +#define to_clk_fact(_hw) container_of(_hw, struct clk_fact, hw)
> 
> What does fact stand for?

pre-divide plus factor. Have you a better name?

> > +
> > +#define SUN8I_PLL3_MSHIFT      0
> > +#define SUN8I_PLL3_MMASK       0x0f
> 
> You can use GENMASK for these. Note that GENMASK is inclusive on both ends.
> 
> > +#define SUN8I_PLL3_NSHIFT      8
> > +#define SUN8I_PLL3_NMASK       0x7f
> > +#define SUN8I_PLL3_MODE_SEL    0x01000000
> > +#define SUN8I_PLL3_FRAC_CLK    0x02000000
> 
> Please use the BIT() macros.

OK.

> > +
> > +static int sun8i_pll3_get_fact(unsigned long rate,
> > +                       unsigned long parent_rate,
> > +                       unsigned long *n, unsigned long *m)
> > +{
> > +       if (rate == 297000000)
> > +               return 1;
> > +       if (rate == 270000000)
> > +               return 0;
> > +       rational_best_approximation(rate, parent_rate,
> > +                               SUN8I_PLL3_NMASK + 1, SUN8I_PLL3_MMASK + 1,
> > +                               n, m);
> > +       return -1;
> > +}
> > +
> > +static unsigned long sun8i_pll3_recalc_rate(struct clk_hw *hw,
> > +                                       unsigned long parent_rate)
> > +{
> > +       struct clk_fact *fact = to_clk_fact(hw);
> > +       u32 reg;
> > +       u32 n, m;
> > +
> > +       reg = readl(fact->reg);
> > +       if (reg & SUN8I_PLL3_MODE_SEL) {
> > +               n = (reg >> SUN8I_PLL3_NSHIFT) & SUN8I_PLL3_NMASK;
> > +               m = (reg >> SUN8I_PLL3_MSHIFT) & SUN8I_PLL3_MMASK;
> > +               return parent_rate / (m + 1) * (n + 1);
> > +       }
> > +       if (reg & SUN8I_PLL3_FRAC_CLK)
> > +               return 297000000;
> > +       return 270000000;
> > +}
> > +
> > +static long sun8i_pll3_round_rate(struct clk_hw *hw, unsigned long rate,
> > +                               unsigned long *parent_rate)
> > +{
> > +       int frac;
> > +       unsigned long n, m;
> > +
> > +       frac = sun8i_pll3_get_fact(rate, *parent_rate, &n, &m);
> > +       if (frac == 1)
> > +               return 297000000;
> > +       if (frac == 0)
> > +               return 270000000;
> > +       return (*parent_rate * n) / m;
> 
> The ordering is different from the one in recalc_rate. Considering
> integer rounding, would the results be different?

Maybe. But, you are right, 'm', as a pre-divider, should be before 'n'.

> > +}
> > +
> > +static int sun8i_pll3_set_rate(struct clk_hw *hw, unsigned long rate,
> > +                       unsigned long parent_rate)
> > +{
> > +       struct clk_fact *fact = to_clk_fact(hw);
> > +       u32 reg;
> > +       int frac;
> > +       unsigned long n, m;
> > +
> > +       reg = readl(fact->reg) &
> > +                       ~(SUN8I_PLL3_MODE_SEL |
> > +                         SUN8I_PLL3_FRAC_CLK |
> > +                         (SUN8I_PLL3_NMASK << SUN8I_PLL3_NSHIFT) |
> > +                         (SUN8I_PLL3_MMASK << SUN8I_PLL3_MSHIFT));
> > +
> > +       frac = sun8i_pll3_get_fact(rate, parent_rate, &n, &m);
> > +       if (frac == 1)
> > +               reg |= SUN8I_PLL3_FRAC_CLK;     /* 297MHz */
> > +       else if (frac == 0)
> > +               ;                               /* 270MHz */
> > +       else
> > +               reg |= SUN8I_PLL3_MODE_SEL |
> > +                       ((n - 1) << SUN8I_PLL3_NSHIFT) |
> > +                       ((m - 1) << SUN8I_PLL3_MSHIFT);
> > +
> > +       writel(reg, fact->reg);
> > +
> > +       /* delay 500us so pll stabilizes */
> > +       __delay(500);
> 
> Bit 28 indicates PLL lock, please use readl_poll_timeout() to check it.

I did not know about this useful macro.

> > +
> > +       return 0;
> > +}
> > +
> > +static const struct clk_ops clk_sun8i_pll3_fact_ops = {
> > +       .recalc_rate = sun8i_pll3_recalc_rate,
> > +       .round_rate = sun8i_pll3_round_rate,
> > +       .set_rate = sun8i_pll3_set_rate,
> > +};
> > +
> > +static void __init sun8i_pll3_setup(struct device_node *node)
> > +{
> > +       const char *clk_name = node->name, *parent;
> > +       struct clk_fact *fact;
> > +       struct clk_gate *gate;
> > +       void __iomem *reg;
> > +       struct clk *clk;
> > +
> > +       of_property_read_string(node, "clock-output-names", &clk_name);
> > +       parent = of_clk_get_parent_name(node, 0);
> > +
> > +       reg = of_io_request_and_map(node, 0, of_node_full_name(node));
> > +       if (IS_ERR(reg)) {
> > +               pr_err("%s: Could not map the clock registers\n", clk_name);
> > +               return;
> > +       }
> > +
> > +       gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> > +       if (!gate)
> > +               return;
> 
> You should call release_mem_region() to release the iomem region you
> requested with of_io_request_and_map().

Right.

> > +
> > +       gate->reg = reg;
> > +       gate->bit_idx = 31;
> > +       gate->lock = &sun8i_display_lock;
> > +
> > +       fact = kzalloc(sizeof(*fact), GFP_KERNEL);
> > +       if (!fact)
> > +               goto free_gate;
> > +
> > +       fact->reg = reg;
> > +
> > +       clk = clk_register_composite(NULL, clk_name,
> > +                                    &parent, 1,
> > +                                    NULL, NULL,
> > +                                    &fact->hw, &clk_sun8i_pll3_fact_ops,
> > +                                    &gate->hw, &clk_gate_ops,
> > +                                    0);
> > +       if (IS_ERR(clk)) {
> > +               pr_err("%s: Couldn't register the clock\n", clk_name);
> > +               goto free_fact;
> > +       }
> > +
> > +       of_clk_add_provider(node, of_clk_src_simple_get, clk);
> 
> Would this fail?

Don't know. This is done this way in many other sunxi clocks.

> > +
> > +       return;
> > +
> > +free_fact:
> > +       kfree(fact);
> > +free_gate:
> > +       kfree(gate);
> > +}
> > +
> > +CLK_OF_DECLARE(sun8i_pll3, "allwinner,sun8i-pll3-clk", sun8i_pll3_setup);
> > +
> > +/* DE, TCON0, TVE, HDMI, DEINTERLACE  */
> > +static void __init sun8i_display_setup(struct device_node *node)
> > +{
> > +       const char *clk_name = node->name;
> > +       const char *parents[2];
> 
> There are as many as six parents for display mod clocks on A31.
> A23/A33 mod clocks have holes in there parent array, as some
> PLLs are missing. We may need a different driver to deal with
> that.

I don't see why: the DT may contain dummy clocks in the parent list.

Otherwise, I agree the remaining remarks.

Thanks for the review.

-- 
Ken ar c'henta?	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
@ 2016-01-08 17:50       ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-08 17:50 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Emilio López, Maxime Ripard, linux-arm-kernel, dri-devel

On Wed, 6 Jan 2016 10:39:51 +0800
Chen-Yu Tsai <wens@csie.org> wrote:

> First of all, please include the clk subsystem maintainers and the
> linux-clk mailing list for all clk related patches.

OK.

> On Wed, Jan 6, 2016 at 2:28 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
> > Add the clock types which are used by the sun8i family for video.
> 
> These clocks first appeared in the A31.

Sorry, I have the documentation of only some sun8i SoCs.

> > Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
> > ---
> >  drivers/clk/sunxi/Makefile            |   1 +
> >  drivers/clk/sunxi/clk-sun8i-display.c | 247 ++++++++++++++++++++++++++++++++++
> 
> Please split this into 2 patches, and 2 files: one for PLL3, named
> clk-sun6i-pll3.c, and one for the display mod clocks, named
> clk-sun6i-display.c

No problem.

	[snip]
> > diff --git a/drivers/clk/sunxi/clk-sun8i-display.c b/drivers/clk/sunxi/clk-sun8i-display.c
> > new file mode 100644
> > index 0000000..eded572
> > --- /dev/null
> > +++ b/drivers/clk/sunxi/clk-sun8i-display.c
> > @@ -0,0 +1,247 @@
> > +/*
> > + * Copyright 2015 Jean-Francois Moine <moinejf@free.fr>
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; either version 2 of the License, or
> > + * (at your option) any later version.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + * GNU General Public License for more details.
> > + */
> > +
> > +#include <linux/clk-provider.h>
> > +#include <linux/of_address.h>
> > +#include <linux/slab.h>
> > +#include <linux/spinlock.h>
> > +#include <linux/rational.h>
> > +#include <linux/delay.h>
> > +
> > +static DEFINE_SPINLOCK(sun8i_display_lock);
> > +
> > +/* PLL3 (video) and PLL10 (de) */
> > +struct clk_fact {
> > +       struct clk_hw hw;
> > +       void __iomem *reg;
> > +};
> > +#define to_clk_fact(_hw) container_of(_hw, struct clk_fact, hw)
> 
> What does fact stand for?

pre-divide plus factor. Have you a better name?

> > +
> > +#define SUN8I_PLL3_MSHIFT      0
> > +#define SUN8I_PLL3_MMASK       0x0f
> 
> You can use GENMASK for these. Note that GENMASK is inclusive on both ends.
> 
> > +#define SUN8I_PLL3_NSHIFT      8
> > +#define SUN8I_PLL3_NMASK       0x7f
> > +#define SUN8I_PLL3_MODE_SEL    0x01000000
> > +#define SUN8I_PLL3_FRAC_CLK    0x02000000
> 
> Please use the BIT() macros.

OK.

> > +
> > +static int sun8i_pll3_get_fact(unsigned long rate,
> > +                       unsigned long parent_rate,
> > +                       unsigned long *n, unsigned long *m)
> > +{
> > +       if (rate == 297000000)
> > +               return 1;
> > +       if (rate == 270000000)
> > +               return 0;
> > +       rational_best_approximation(rate, parent_rate,
> > +                               SUN8I_PLL3_NMASK + 1, SUN8I_PLL3_MMASK + 1,
> > +                               n, m);
> > +       return -1;
> > +}
> > +
> > +static unsigned long sun8i_pll3_recalc_rate(struct clk_hw *hw,
> > +                                       unsigned long parent_rate)
> > +{
> > +       struct clk_fact *fact = to_clk_fact(hw);
> > +       u32 reg;
> > +       u32 n, m;
> > +
> > +       reg = readl(fact->reg);
> > +       if (reg & SUN8I_PLL3_MODE_SEL) {
> > +               n = (reg >> SUN8I_PLL3_NSHIFT) & SUN8I_PLL3_NMASK;
> > +               m = (reg >> SUN8I_PLL3_MSHIFT) & SUN8I_PLL3_MMASK;
> > +               return parent_rate / (m + 1) * (n + 1);
> > +       }
> > +       if (reg & SUN8I_PLL3_FRAC_CLK)
> > +               return 297000000;
> > +       return 270000000;
> > +}
> > +
> > +static long sun8i_pll3_round_rate(struct clk_hw *hw, unsigned long rate,
> > +                               unsigned long *parent_rate)
> > +{
> > +       int frac;
> > +       unsigned long n, m;
> > +
> > +       frac = sun8i_pll3_get_fact(rate, *parent_rate, &n, &m);
> > +       if (frac == 1)
> > +               return 297000000;
> > +       if (frac == 0)
> > +               return 270000000;
> > +       return (*parent_rate * n) / m;
> 
> The ordering is different from the one in recalc_rate. Considering
> integer rounding, would the results be different?

Maybe. But, you are right, 'm', as a pre-divider, should be before 'n'.

> > +}
> > +
> > +static int sun8i_pll3_set_rate(struct clk_hw *hw, unsigned long rate,
> > +                       unsigned long parent_rate)
> > +{
> > +       struct clk_fact *fact = to_clk_fact(hw);
> > +       u32 reg;
> > +       int frac;
> > +       unsigned long n, m;
> > +
> > +       reg = readl(fact->reg) &
> > +                       ~(SUN8I_PLL3_MODE_SEL |
> > +                         SUN8I_PLL3_FRAC_CLK |
> > +                         (SUN8I_PLL3_NMASK << SUN8I_PLL3_NSHIFT) |
> > +                         (SUN8I_PLL3_MMASK << SUN8I_PLL3_MSHIFT));
> > +
> > +       frac = sun8i_pll3_get_fact(rate, parent_rate, &n, &m);
> > +       if (frac == 1)
> > +               reg |= SUN8I_PLL3_FRAC_CLK;     /* 297MHz */
> > +       else if (frac == 0)
> > +               ;                               /* 270MHz */
> > +       else
> > +               reg |= SUN8I_PLL3_MODE_SEL |
> > +                       ((n - 1) << SUN8I_PLL3_NSHIFT) |
> > +                       ((m - 1) << SUN8I_PLL3_MSHIFT);
> > +
> > +       writel(reg, fact->reg);
> > +
> > +       /* delay 500us so pll stabilizes */
> > +       __delay(500);
> 
> Bit 28 indicates PLL lock, please use readl_poll_timeout() to check it.

I did not know about this useful macro.

> > +
> > +       return 0;
> > +}
> > +
> > +static const struct clk_ops clk_sun8i_pll3_fact_ops = {
> > +       .recalc_rate = sun8i_pll3_recalc_rate,
> > +       .round_rate = sun8i_pll3_round_rate,
> > +       .set_rate = sun8i_pll3_set_rate,
> > +};
> > +
> > +static void __init sun8i_pll3_setup(struct device_node *node)
> > +{
> > +       const char *clk_name = node->name, *parent;
> > +       struct clk_fact *fact;
> > +       struct clk_gate *gate;
> > +       void __iomem *reg;
> > +       struct clk *clk;
> > +
> > +       of_property_read_string(node, "clock-output-names", &clk_name);
> > +       parent = of_clk_get_parent_name(node, 0);
> > +
> > +       reg = of_io_request_and_map(node, 0, of_node_full_name(node));
> > +       if (IS_ERR(reg)) {
> > +               pr_err("%s: Could not map the clock registers\n", clk_name);
> > +               return;
> > +       }
> > +
> > +       gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> > +       if (!gate)
> > +               return;
> 
> You should call release_mem_region() to release the iomem region you
> requested with of_io_request_and_map().

Right.

> > +
> > +       gate->reg = reg;
> > +       gate->bit_idx = 31;
> > +       gate->lock = &sun8i_display_lock;
> > +
> > +       fact = kzalloc(sizeof(*fact), GFP_KERNEL);
> > +       if (!fact)
> > +               goto free_gate;
> > +
> > +       fact->reg = reg;
> > +
> > +       clk = clk_register_composite(NULL, clk_name,
> > +                                    &parent, 1,
> > +                                    NULL, NULL,
> > +                                    &fact->hw, &clk_sun8i_pll3_fact_ops,
> > +                                    &gate->hw, &clk_gate_ops,
> > +                                    0);
> > +       if (IS_ERR(clk)) {
> > +               pr_err("%s: Couldn't register the clock\n", clk_name);
> > +               goto free_fact;
> > +       }
> > +
> > +       of_clk_add_provider(node, of_clk_src_simple_get, clk);
> 
> Would this fail?

Don't know. This is done this way in many other sunxi clocks.

> > +
> > +       return;
> > +
> > +free_fact:
> > +       kfree(fact);
> > +free_gate:
> > +       kfree(gate);
> > +}
> > +
> > +CLK_OF_DECLARE(sun8i_pll3, "allwinner,sun8i-pll3-clk", sun8i_pll3_setup);
> > +
> > +/* DE, TCON0, TVE, HDMI, DEINTERLACE  */
> > +static void __init sun8i_display_setup(struct device_node *node)
> > +{
> > +       const char *clk_name = node->name;
> > +       const char *parents[2];
> 
> There are as many as six parents for display mod clocks on A31.
> A23/A33 mod clocks have holes in there parent array, as some
> PLLs are missing. We may need a different driver to deal with
> that.

I don't see why: the DT may contain dummy clocks in the parent list.

Otherwise, I agree the remaining remarks.

Thanks for the review.

-- 
Ken ar c'hentañ	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2
  2016-01-05 20:38     ` Russell King - ARM Linux
@ 2016-01-11 18:56       ` Jean-Francois Moine
  -1 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-11 18:56 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, 5 Jan 2016 20:38:17 +0000
Russell King - ARM Linux <linux@arm.linux.org.uk> wrote:

> Some comments from an ARM architecture point of view.  I haven't
> reviewed it from a DRM point of view yet.

Anyway, thanks for your explanations and remarks.

> > +static void de2_crtc_enable(struct drm_crtc *crtc)
> > +{
	...
> > +	clk_set_rate(lcd->clk, mode->clock * 1000);
> 
> What if the clock can't support the rate?

The function enabling the CRTC has no return code, so the screen would
be blurred. But this would not occur: the video PLL is in the range
30..600MHz.

> > +static int de2_hdmi_connector_mode_valid(struct drm_connector *connector,
> > +					struct drm_display_mode *mode)
> > +{
> > +	if (!drm_match_cea_mode(mode))
> > +		return MODE_NOMODE;
> 
> Maybe detect modes with a zero clock here instead?

We have no documentation about the HDMI hardware and only the CEA modes
are handled in Allwinner's driver.

-- 
Ken ar c'henta?	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2
@ 2016-01-11 18:56       ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-11 18:56 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: dri-devel, Emilio López, Chen-Yu Tsai, Maxime Ripard,
	linux-arm-kernel

On Tue, 5 Jan 2016 20:38:17 +0000
Russell King - ARM Linux <linux@arm.linux.org.uk> wrote:

> Some comments from an ARM architecture point of view.  I haven't
> reviewed it from a DRM point of view yet.

Anyway, thanks for your explanations and remarks.

> > +static void de2_crtc_enable(struct drm_crtc *crtc)
> > +{
	...
> > +	clk_set_rate(lcd->clk, mode->clock * 1000);
> 
> What if the clock can't support the rate?

The function enabling the CRTC has no return code, so the screen would
be blurred. But this would not occur: the video PLL is in the range
30..600MHz.

> > +static int de2_hdmi_connector_mode_valid(struct drm_connector *connector,
> > +					struct drm_display_mode *mode)
> > +{
> > +	if (!drm_match_cea_mode(mode))
> > +		return MODE_NOMODE;
> 
> Maybe detect modes with a zero clock here instead?

We have no documentation about the HDMI hardware and only the CEA modes
are handled in Allwinner's driver.

-- 
Ken ar c'hentañ	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2
  2016-01-06 20:41     ` Jens Kuske
@ 2016-01-13 17:37       ` Jean-Francois Moine
  -1 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-13 17:37 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 6 Jan 2016 21:41:30 +0100
Jens Kuske <jenskuske@gmx.de> wrote:

> On 05/01/16 19:40, Jean-Francois Moine wrote:
> [snip]
> > diff --git a/drivers/gpu/drm/sunxi/de2_hdmi_h3.c b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
> > new file mode 100644
> > index 0000000..c54b090
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
> > @@ -0,0 +1,478 @@
> > +/*
> > + * Allwinner H3 HDMI lowlevel functions
> > + *
> > + * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
> > + *
> > + * Adapted from the file
> > + *	lichee/linux-3.4/drivers/video/sunxi/disp2/hdmi/aw/hdmi_bsp_sun8iw7.c
> > + * with no license nor copyright.
> > + */
	[snip]
> > +/*
> > + * [0] = vic (cea Video ID)
> > + * [1] used in hdmi_phy_set / bsp_hdmi_audio
> > + * [2..17] used in bsp_hdmi_video
> > + */
> > +static const struct para_tab ptbl[] = {
> > +	{{  6,  1, 1,  1,  5,  3, 0, 1, 4, 0, 0, 160,  20,  38, 124, 240, 22, 0, 0}},
	[snip]

> did you try to figure out what the values in this table actually mean?
> 
> I tried it some time ago because I wanted to add some more resolutions
> to 3.4, but never got further than what I'll add below. But it might be
> useful now, to get rid of at least some of the magic constants.
> With some more work (what does [1] mean?) we might be able to drop the
> entire table and use the values from drm_display_mode directly instead.
> 
> unsure (hard to verify):
> [2] = pixel repetition (1 = 2x)
> [3] = bit0: interlaced (no idea about the 96/0x60 yet)
> [17] = something csc related
> [18] = unused
> 
> pretty sure (verified by comparing with timings):
> [4] = horizontal active (high byte)
> [5] = vsync width
> [6] = vertical active (high byte)
> [7] = horizontal blanking (high byte)
> [8] = vertical front porch
> [9] = horizontal front porch (high byte)
> [10] = hsync width (high byte)
> [11] = horizontal active (low byte)
> [12] = horizontal blanking (low byte)
> [13] = horizontal front porch (low byte)
> [14] = hsync width (low byte)
> [15] = vertical active (low byte)
> [16] = vertical blanking
> 
> Generally, nice work. I only skimmed over it by now, but I hope to test
> and review the hardware related parts more intensively sometime.

Hi Jens,

Thanks for this information, but this table is only a very small part
of the HDMI code.

I doubt that anyone could understand the other sequences of the
functions of this BSP without documentation, or could do some reverse
engineering and understand how the DE2 HDMI device works.

So, I think that we have to wait for some information and/or
authorisation from Allwinner before putting a HDMI driver for the H3
(and A83T, A64...) into the mainline.

-- 
Ken ar c'henta?	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2
@ 2016-01-13 17:37       ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-13 17:37 UTC (permalink / raw)
  To: Jens Kuske
  Cc: dri-devel, Emilio López, Chen-Yu Tsai, Maxime Ripard,
	linux-arm-kernel

On Wed, 6 Jan 2016 21:41:30 +0100
Jens Kuske <jenskuske@gmx.de> wrote:

> On 05/01/16 19:40, Jean-Francois Moine wrote:
> [snip]
> > diff --git a/drivers/gpu/drm/sunxi/de2_hdmi_h3.c b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
> > new file mode 100644
> > index 0000000..c54b090
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sunxi/de2_hdmi_h3.c
> > @@ -0,0 +1,478 @@
> > +/*
> > + * Allwinner H3 HDMI lowlevel functions
> > + *
> > + * Copyright (C) 2016 Jean-Francois Moine <moinejf@free.fr>
> > + *
> > + * Adapted from the file
> > + *	lichee/linux-3.4/drivers/video/sunxi/disp2/hdmi/aw/hdmi_bsp_sun8iw7.c
> > + * with no license nor copyright.
> > + */
	[snip]
> > +/*
> > + * [0] = vic (cea Video ID)
> > + * [1] used in hdmi_phy_set / bsp_hdmi_audio
> > + * [2..17] used in bsp_hdmi_video
> > + */
> > +static const struct para_tab ptbl[] = {
> > +	{{  6,  1, 1,  1,  5,  3, 0, 1, 4, 0, 0, 160,  20,  38, 124, 240, 22, 0, 0}},
	[snip]

> did you try to figure out what the values in this table actually mean?
> 
> I tried it some time ago because I wanted to add some more resolutions
> to 3.4, but never got further than what I'll add below. But it might be
> useful now, to get rid of at least some of the magic constants.
> With some more work (what does [1] mean?) we might be able to drop the
> entire table and use the values from drm_display_mode directly instead.
> 
> unsure (hard to verify):
> [2] = pixel repetition (1 = 2x)
> [3] = bit0: interlaced (no idea about the 96/0x60 yet)
> [17] = something csc related
> [18] = unused
> 
> pretty sure (verified by comparing with timings):
> [4] = horizontal active (high byte)
> [5] = vsync width
> [6] = vertical active (high byte)
> [7] = horizontal blanking (high byte)
> [8] = vertical front porch
> [9] = horizontal front porch (high byte)
> [10] = hsync width (high byte)
> [11] = horizontal active (low byte)
> [12] = horizontal blanking (low byte)
> [13] = horizontal front porch (low byte)
> [14] = hsync width (low byte)
> [15] = vertical active (low byte)
> [16] = vertical blanking
> 
> Generally, nice work. I only skimmed over it by now, but I hope to test
> and review the hardware related parts more intensively sometime.

Hi Jens,

Thanks for this information, but this table is only a very small part
of the HDMI code.

I doubt that anyone could understand the other sequences of the
functions of this BSP without documentation, or could do some reverse
engineering and understand how the DE2 HDMI device works.

So, I think that we have to wait for some information and/or
authorisation from Allwinner before putting a HDMI driver for the H3
(and A83T, A64...) into the mainline.

-- 
Ken ar c'hentañ	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 0/2] Add a display driver to the Allwinner H3
  2016-01-08 17:13     ` Jean-Francois Moine
@ 2016-01-18 10:18       ` Maxime Ripard
  -1 siblings, 0 replies; 36+ messages in thread
From: Maxime Ripard @ 2016-01-18 10:18 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

On Fri, Jan 08, 2016 at 06:13:06PM +0100, Jean-Francois Moine wrote:
> On Wed, 6 Jan 2016 22:20:46 +0100
> Maxime Ripard <maxime.ripard@free-electrons.com> wrote:
> 
> > > As there is no documentation about the DE2 nor about the HDMI which
> > > are found in the H3, this driver has been built from Allwiiner's
> > > sources.
> > 
> > That's unfortunate :/
> > 
> > Have you checked in the A64 BSP if there was some useful information?
> 
> Same as the H3: no more information about the display system.

Too bad...

> > > So, there may be license problems, especially for the file
> > > de2_hdmi_h3.c which contains a lot of magic values.
> > 
> > I guess it's the biggest issue with your code right now. What licenses
> > issues are we talking about here?
> 
> The documentation about the H3, as the other Allwinner documentations,
> starts with:
> 
> 	This documentation is the original work and copyrighted
> 	property of Allwinner Technology ("Allwinner"). Reproduction in
> 	whole or in part must obtain the written approval of Allwinner
> 	and give clear acknowledgement to the copyright owner.

I'm not sure this one is an issue. This datasheet is available
publicly, and it's common to have a datasheet with restrictions (or
even an NDA).

> Then, the DE2 sources contain only:
> 
> 	All Winner Tech, All Right Reserved. 2014-2015 Copyright (c)
> 
> Eventually, there is no copyright/author/history in the HDMI sources.

That one is nasty though :/

It seems to be a GPL violation though, so we have two solutions:

A) have a clean room implementation
B) Ask allwinner to comply with the license

The former doesn't look likely to happen soon... Can you open an issue
on this on their linux github repo?

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20160118/ca88bf83/attachment.sig>

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 0/2] Add a display driver to the Allwinner H3
@ 2016-01-18 10:18       ` Maxime Ripard
  0 siblings, 0 replies; 36+ messages in thread
From: Maxime Ripard @ 2016-01-18 10:18 UTC (permalink / raw)
  To: Jean-Francois Moine
  Cc: Dave Airlie, Emilio López, Chen-Yu Tsai, linux-arm-kernel,
	dri-devel


[-- Attachment #1.1: Type: text/plain, Size: 1900 bytes --]

Hi,

On Fri, Jan 08, 2016 at 06:13:06PM +0100, Jean-Francois Moine wrote:
> On Wed, 6 Jan 2016 22:20:46 +0100
> Maxime Ripard <maxime.ripard@free-electrons.com> wrote:
> 
> > > As there is no documentation about the DE2 nor about the HDMI which
> > > are found in the H3, this driver has been built from Allwiiner's
> > > sources.
> > 
> > That's unfortunate :/
> > 
> > Have you checked in the A64 BSP if there was some useful information?
> 
> Same as the H3: no more information about the display system.

Too bad...

> > > So, there may be license problems, especially for the file
> > > de2_hdmi_h3.c which contains a lot of magic values.
> > 
> > I guess it's the biggest issue with your code right now. What licenses
> > issues are we talking about here?
> 
> The documentation about the H3, as the other Allwinner documentations,
> starts with:
> 
> 	This documentation is the original work and copyrighted
> 	property of Allwinner Technology ("Allwinner"). Reproduction in
> 	whole or in part must obtain the written approval of Allwinner
> 	and give clear acknowledgement to the copyright owner.

I'm not sure this one is an issue. This datasheet is available
publicly, and it's common to have a datasheet with restrictions (or
even an NDA).

> Then, the DE2 sources contain only:
> 
> 	All Winner Tech, All Right Reserved. 2014-2015 Copyright (c)
> 
> Eventually, there is no copyright/author/history in the HDMI sources.

That one is nasty though :/

It seems to be a GPL violation though, so we have two solutions:

A) have a clean room implementation
B) Ask allwinner to comply with the license

The former doesn't look likely to happen soon... Can you open an issue
on this on their linux github repo?

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
  2016-01-05 18:28   ` Jean-Francois Moine
@ 2016-01-18 19:09     ` Maxime Ripard
  -1 siblings, 0 replies; 36+ messages in thread
From: Maxime Ripard @ 2016-01-18 19:09 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

On Tue, Jan 05, 2016 at 07:28:25PM +0100, Jean-Francois Moine wrote:
> Add the clock types which are used by the sun8i family for video.
> 
> Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
> ---
>  drivers/clk/sunxi/Makefile            |   1 +
>  drivers/clk/sunxi/clk-sun8i-display.c | 247 ++++++++++++++++++++++++++++++++++
>  2 files changed, 258 insertions(+)
>  create mode 100644 drivers/clk/sunxi/clk-sun8i-display.c
> 
> diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
> index cb4c299..145c078 100644
> --- a/drivers/clk/sunxi/Makefile
> +++ b/drivers/clk/sunxi/Makefile
> @@ -10,6 +10,7 @@ obj-y += clk-a10-pll2.o
>  obj-y += clk-a20-gmac.o
>  obj-y += clk-mod0.o
>  obj-y += clk-simple-gates.o
> +obj-y += clk-sun8i-display.o
>  obj-y += clk-sun8i-mbus.o
>  obj-y += clk-sun9i-core.o
>  obj-y += clk-sun9i-mmc.o
> diff --git a/drivers/clk/sunxi/clk-sun8i-display.c b/drivers/clk/sunxi/clk-sun8i-display.c
> new file mode 100644
> index 0000000..eded572
> --- /dev/null
> +++ b/drivers/clk/sunxi/clk-sun8i-display.c
> @@ -0,0 +1,247 @@
> +/*
> + * Copyright 2015 Jean-Francois Moine <moinejf@free.fr>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/of_address.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/rational.h>
> +#include <linux/delay.h>
> +
> +static DEFINE_SPINLOCK(sun8i_display_lock);
> +
> +/* PLL3 (video) and PLL10 (de) */
> +struct clk_fact {
> +	struct clk_hw hw;
> +	void __iomem *reg;
> +};
> +#define to_clk_fact(_hw) container_of(_hw, struct clk_fact, hw)
> +
> +#define SUN8I_PLL3_MSHIFT	0
> +#define SUN8I_PLL3_MMASK	0x0f
> +#define SUN8I_PLL3_NSHIFT	8
> +#define SUN8I_PLL3_NMASK	0x7f
> +#define SUN8I_PLL3_MODE_SEL	0x01000000
> +#define SUN8I_PLL3_FRAC_CLK	0x02000000
> +
> +static int sun8i_pll3_get_fact(unsigned long rate,
> +			unsigned long parent_rate,
> +			unsigned long *n, unsigned long *m)
> +{
> +	if (rate == 297000000)
> +		return 1;
> +	if (rate == 270000000)
> +		return 0;
> +	rational_best_approximation(rate, parent_rate,
> +				SUN8I_PLL3_NMASK + 1, SUN8I_PLL3_MMASK + 1,
> +				n, m);
> +	return -1;
> +}
> +
> +static unsigned long sun8i_pll3_recalc_rate(struct clk_hw *hw,
> +					unsigned long parent_rate)
> +{
> +	struct clk_fact *fact = to_clk_fact(hw);
> +	u32 reg;
> +	u32 n, m;
> +
> +	reg = readl(fact->reg);
> +	if (reg & SUN8I_PLL3_MODE_SEL) {
> +		n = (reg >> SUN8I_PLL3_NSHIFT) & SUN8I_PLL3_NMASK;
> +		m = (reg >> SUN8I_PLL3_MSHIFT) & SUN8I_PLL3_MMASK;
> +		return parent_rate / (m + 1) * (n + 1);
> +	}
> +	if (reg & SUN8I_PLL3_FRAC_CLK)
> +		return 297000000;
> +	return 270000000;
> +}
> +
> +static long sun8i_pll3_round_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long *parent_rate)
> +{
> +	int frac;
> +	unsigned long n, m;
> +
> +	frac = sun8i_pll3_get_fact(rate, *parent_rate, &n, &m);
> +	if (frac == 1)
> +		return 297000000;
> +	if (frac == 0)
> +		return 270000000;
> +	return (*parent_rate * n) / m;
> +}
> +
> +static int sun8i_pll3_set_rate(struct clk_hw *hw, unsigned long rate,
> +			unsigned long parent_rate)
> +{
> +	struct clk_fact *fact = to_clk_fact(hw);
> +	u32 reg;
> +	int frac;
> +	unsigned long n, m;
> +
> +	reg = readl(fact->reg) &
> +			~(SUN8I_PLL3_MODE_SEL |
> +			  SUN8I_PLL3_FRAC_CLK |
> +			  (SUN8I_PLL3_NMASK << SUN8I_PLL3_NSHIFT) |
> +			  (SUN8I_PLL3_MMASK << SUN8I_PLL3_MSHIFT));
> +
> +	frac = sun8i_pll3_get_fact(rate, parent_rate, &n, &m);
> +	if (frac == 1)
> +		reg |= SUN8I_PLL3_FRAC_CLK;	/* 297MHz */
> +	else if (frac == 0)
> +		;				/* 270MHz */
> +	else
> +		reg |= SUN8I_PLL3_MODE_SEL |
> +			((n - 1) << SUN8I_PLL3_NSHIFT) |
> +			((m - 1) << SUN8I_PLL3_MSHIFT);
> +
> +	writel(reg, fact->reg);
> +
> +	/* delay 500us so pll stabilizes */
> +	__delay(500);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops clk_sun8i_pll3_fact_ops = {
> +	.recalc_rate = sun8i_pll3_recalc_rate,
> +	.round_rate = sun8i_pll3_round_rate,
> +	.set_rate = sun8i_pll3_set_rate,
> +};

We have the clk-factors stuff to handle this easily, could you use
that instead ?

> +
> +static void __init sun8i_pll3_setup(struct device_node *node)
> +{
> +	const char *clk_name = node->name, *parent;
> +	struct clk_fact *fact;
> +	struct clk_gate *gate;
> +	void __iomem *reg;
> +	struct clk *clk;
> +
> +	of_property_read_string(node, "clock-output-names", &clk_name);
> +	parent = of_clk_get_parent_name(node, 0);
> +
> +	reg = of_io_request_and_map(node, 0, of_node_full_name(node));
> +	if (IS_ERR(reg)) {
> +		pr_err("%s: Could not map the clock registers\n", clk_name);
> +		return;
> +	}
> +
> +	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> +	if (!gate)
> +		return;
> +
> +	gate->reg = reg;
> +	gate->bit_idx = 31;
> +	gate->lock = &sun8i_display_lock;
> +
> +	fact = kzalloc(sizeof(*fact), GFP_KERNEL);
> +	if (!fact)
> +		goto free_gate;
> +
> +	fact->reg = reg;
> +
> +	clk = clk_register_composite(NULL, clk_name,
> +				     &parent, 1,
> +				     NULL, NULL,
> +				     &fact->hw, &clk_sun8i_pll3_fact_ops,
> +				     &gate->hw, &clk_gate_ops,
> +				     0);
> +	if (IS_ERR(clk)) {
> +		pr_err("%s: Couldn't register the clock\n", clk_name);
> +		goto free_fact;
> +	}
> +
> +	of_clk_add_provider(node, of_clk_src_simple_get, clk);
> +
> +	return;
> +
> +free_fact:
> +	kfree(fact);
> +free_gate:
> +	kfree(gate);
> +}
> +
> +CLK_OF_DECLARE(sun8i_pll3, "allwinner,sun8i-pll3-clk", sun8i_pll3_setup);
> +
> +/* DE, TCON0, TVE, HDMI, DEINTERLACE  */
> +static void __init sun8i_display_setup(struct device_node *node)
> +{
> +	const char *clk_name = node->name;
> +	const char *parents[2];
> +	struct clk_mux *mux = NULL;
> +	struct clk_divider *div;
> +	struct clk_gate *gate;
> +	void __iomem *reg;
> +	struct clk *clk;
> +	int n;
> +
> +	of_property_read_string(node, "clock-output-names", &clk_name);
> +
> +	reg = of_io_request_and_map(node, 0, of_node_full_name(node));
> +	if (IS_ERR(reg)) {
> +		pr_err("%s: Could not map the clock registers\n", clk_name);
> +		return;
> +	}
> +
> +	n = of_clk_parent_fill(node, parents, ARRAY_SIZE(parents));
> +
> +	if (n > 1) {				/* many possible sources */
> +		mux = kzalloc(sizeof(*mux), GFP_KERNEL);
> +		if (!mux)
> +			return;
> +		mux->reg = reg;
> +		mux->shift = 24;
> +		mux->mask = 0x07;
> +		mux->lock = &sun8i_display_lock;
> +	}
> +
> +	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> +	if (!gate)
> +		goto free_gate;
> +
> +	gate->reg = reg;
> +	gate->bit_idx = 31;
> +	gate->lock = &sun8i_display_lock;
> +
> +	div = kzalloc(sizeof(*div), GFP_KERNEL);
> +	if (!div)
> +		goto free_gate;
> +
> +	div->reg = reg;
> +	div->shift = 0;
> +	div->width = 4;
> +	div->lock = &sun8i_display_lock;
> +
> +	clk = clk_register_composite(NULL, clk_name,
> +				     parents, n,
> +				     mux ? &mux->hw : NULL, &clk_mux_ops,
> +				     &div->hw, &clk_divider_ops,
> +				     &gate->hw, &clk_gate_ops,
> +				     0);
> +	if (IS_ERR(clk)) {
> +		pr_err("%s: Couldn't register the clock\n", clk_name);
> +		goto free_div;
> +	}
> +
> +	of_clk_add_provider(node, of_clk_src_simple_get, clk);
> +
> +	return;
> +
> +free_div:
> +	kfree(div);
> +free_gate:
> +	kfree(gate);
> +	kfree(mux);
> +}
> +
> +CLK_OF_DECLARE(sun8i_display, "allwinner,sun8i-display-clk", sun8i_display_setup);

As part of my DRM patches, I've added a clk-display clock that can
handle that easily.

And actually, as part of bringing up the display engine on the A33, I
already did it:
https://github.com/mripard/linux/commit/92b6843b5ee5b70cb2be3638df31d3eca28a4dba
https://github.com/mripard/linux/commit/81e8ea74be5e72124eb584432bb79ff75f90d9ed

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20160118/190d887b/attachment.sig>

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
@ 2016-01-18 19:09     ` Maxime Ripard
  0 siblings, 0 replies; 36+ messages in thread
From: Maxime Ripard @ 2016-01-18 19:09 UTC (permalink / raw)
  To: Jean-Francois Moine
  Cc: Emilio López, Chen-Yu Tsai, linux-arm-kernel, dri-devel


[-- Attachment #1.1: Type: text/plain, Size: 8539 bytes --]

Hi,

On Tue, Jan 05, 2016 at 07:28:25PM +0100, Jean-Francois Moine wrote:
> Add the clock types which are used by the sun8i family for video.
> 
> Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
> ---
>  drivers/clk/sunxi/Makefile            |   1 +
>  drivers/clk/sunxi/clk-sun8i-display.c | 247 ++++++++++++++++++++++++++++++++++
>  2 files changed, 258 insertions(+)
>  create mode 100644 drivers/clk/sunxi/clk-sun8i-display.c
> 
> diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
> index cb4c299..145c078 100644
> --- a/drivers/clk/sunxi/Makefile
> +++ b/drivers/clk/sunxi/Makefile
> @@ -10,6 +10,7 @@ obj-y += clk-a10-pll2.o
>  obj-y += clk-a20-gmac.o
>  obj-y += clk-mod0.o
>  obj-y += clk-simple-gates.o
> +obj-y += clk-sun8i-display.o
>  obj-y += clk-sun8i-mbus.o
>  obj-y += clk-sun9i-core.o
>  obj-y += clk-sun9i-mmc.o
> diff --git a/drivers/clk/sunxi/clk-sun8i-display.c b/drivers/clk/sunxi/clk-sun8i-display.c
> new file mode 100644
> index 0000000..eded572
> --- /dev/null
> +++ b/drivers/clk/sunxi/clk-sun8i-display.c
> @@ -0,0 +1,247 @@
> +/*
> + * Copyright 2015 Jean-Francois Moine <moinejf@free.fr>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/of_address.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/rational.h>
> +#include <linux/delay.h>
> +
> +static DEFINE_SPINLOCK(sun8i_display_lock);
> +
> +/* PLL3 (video) and PLL10 (de) */
> +struct clk_fact {
> +	struct clk_hw hw;
> +	void __iomem *reg;
> +};
> +#define to_clk_fact(_hw) container_of(_hw, struct clk_fact, hw)
> +
> +#define SUN8I_PLL3_MSHIFT	0
> +#define SUN8I_PLL3_MMASK	0x0f
> +#define SUN8I_PLL3_NSHIFT	8
> +#define SUN8I_PLL3_NMASK	0x7f
> +#define SUN8I_PLL3_MODE_SEL	0x01000000
> +#define SUN8I_PLL3_FRAC_CLK	0x02000000
> +
> +static int sun8i_pll3_get_fact(unsigned long rate,
> +			unsigned long parent_rate,
> +			unsigned long *n, unsigned long *m)
> +{
> +	if (rate == 297000000)
> +		return 1;
> +	if (rate == 270000000)
> +		return 0;
> +	rational_best_approximation(rate, parent_rate,
> +				SUN8I_PLL3_NMASK + 1, SUN8I_PLL3_MMASK + 1,
> +				n, m);
> +	return -1;
> +}
> +
> +static unsigned long sun8i_pll3_recalc_rate(struct clk_hw *hw,
> +					unsigned long parent_rate)
> +{
> +	struct clk_fact *fact = to_clk_fact(hw);
> +	u32 reg;
> +	u32 n, m;
> +
> +	reg = readl(fact->reg);
> +	if (reg & SUN8I_PLL3_MODE_SEL) {
> +		n = (reg >> SUN8I_PLL3_NSHIFT) & SUN8I_PLL3_NMASK;
> +		m = (reg >> SUN8I_PLL3_MSHIFT) & SUN8I_PLL3_MMASK;
> +		return parent_rate / (m + 1) * (n + 1);
> +	}
> +	if (reg & SUN8I_PLL3_FRAC_CLK)
> +		return 297000000;
> +	return 270000000;
> +}
> +
> +static long sun8i_pll3_round_rate(struct clk_hw *hw, unsigned long rate,
> +				unsigned long *parent_rate)
> +{
> +	int frac;
> +	unsigned long n, m;
> +
> +	frac = sun8i_pll3_get_fact(rate, *parent_rate, &n, &m);
> +	if (frac == 1)
> +		return 297000000;
> +	if (frac == 0)
> +		return 270000000;
> +	return (*parent_rate * n) / m;
> +}
> +
> +static int sun8i_pll3_set_rate(struct clk_hw *hw, unsigned long rate,
> +			unsigned long parent_rate)
> +{
> +	struct clk_fact *fact = to_clk_fact(hw);
> +	u32 reg;
> +	int frac;
> +	unsigned long n, m;
> +
> +	reg = readl(fact->reg) &
> +			~(SUN8I_PLL3_MODE_SEL |
> +			  SUN8I_PLL3_FRAC_CLK |
> +			  (SUN8I_PLL3_NMASK << SUN8I_PLL3_NSHIFT) |
> +			  (SUN8I_PLL3_MMASK << SUN8I_PLL3_MSHIFT));
> +
> +	frac = sun8i_pll3_get_fact(rate, parent_rate, &n, &m);
> +	if (frac == 1)
> +		reg |= SUN8I_PLL3_FRAC_CLK;	/* 297MHz */
> +	else if (frac == 0)
> +		;				/* 270MHz */
> +	else
> +		reg |= SUN8I_PLL3_MODE_SEL |
> +			((n - 1) << SUN8I_PLL3_NSHIFT) |
> +			((m - 1) << SUN8I_PLL3_MSHIFT);
> +
> +	writel(reg, fact->reg);
> +
> +	/* delay 500us so pll stabilizes */
> +	__delay(500);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops clk_sun8i_pll3_fact_ops = {
> +	.recalc_rate = sun8i_pll3_recalc_rate,
> +	.round_rate = sun8i_pll3_round_rate,
> +	.set_rate = sun8i_pll3_set_rate,
> +};

We have the clk-factors stuff to handle this easily, could you use
that instead ?

> +
> +static void __init sun8i_pll3_setup(struct device_node *node)
> +{
> +	const char *clk_name = node->name, *parent;
> +	struct clk_fact *fact;
> +	struct clk_gate *gate;
> +	void __iomem *reg;
> +	struct clk *clk;
> +
> +	of_property_read_string(node, "clock-output-names", &clk_name);
> +	parent = of_clk_get_parent_name(node, 0);
> +
> +	reg = of_io_request_and_map(node, 0, of_node_full_name(node));
> +	if (IS_ERR(reg)) {
> +		pr_err("%s: Could not map the clock registers\n", clk_name);
> +		return;
> +	}
> +
> +	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> +	if (!gate)
> +		return;
> +
> +	gate->reg = reg;
> +	gate->bit_idx = 31;
> +	gate->lock = &sun8i_display_lock;
> +
> +	fact = kzalloc(sizeof(*fact), GFP_KERNEL);
> +	if (!fact)
> +		goto free_gate;
> +
> +	fact->reg = reg;
> +
> +	clk = clk_register_composite(NULL, clk_name,
> +				     &parent, 1,
> +				     NULL, NULL,
> +				     &fact->hw, &clk_sun8i_pll3_fact_ops,
> +				     &gate->hw, &clk_gate_ops,
> +				     0);
> +	if (IS_ERR(clk)) {
> +		pr_err("%s: Couldn't register the clock\n", clk_name);
> +		goto free_fact;
> +	}
> +
> +	of_clk_add_provider(node, of_clk_src_simple_get, clk);
> +
> +	return;
> +
> +free_fact:
> +	kfree(fact);
> +free_gate:
> +	kfree(gate);
> +}
> +
> +CLK_OF_DECLARE(sun8i_pll3, "allwinner,sun8i-pll3-clk", sun8i_pll3_setup);
> +
> +/* DE, TCON0, TVE, HDMI, DEINTERLACE  */
> +static void __init sun8i_display_setup(struct device_node *node)
> +{
> +	const char *clk_name = node->name;
> +	const char *parents[2];
> +	struct clk_mux *mux = NULL;
> +	struct clk_divider *div;
> +	struct clk_gate *gate;
> +	void __iomem *reg;
> +	struct clk *clk;
> +	int n;
> +
> +	of_property_read_string(node, "clock-output-names", &clk_name);
> +
> +	reg = of_io_request_and_map(node, 0, of_node_full_name(node));
> +	if (IS_ERR(reg)) {
> +		pr_err("%s: Could not map the clock registers\n", clk_name);
> +		return;
> +	}
> +
> +	n = of_clk_parent_fill(node, parents, ARRAY_SIZE(parents));
> +
> +	if (n > 1) {				/* many possible sources */
> +		mux = kzalloc(sizeof(*mux), GFP_KERNEL);
> +		if (!mux)
> +			return;
> +		mux->reg = reg;
> +		mux->shift = 24;
> +		mux->mask = 0x07;
> +		mux->lock = &sun8i_display_lock;
> +	}
> +
> +	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
> +	if (!gate)
> +		goto free_gate;
> +
> +	gate->reg = reg;
> +	gate->bit_idx = 31;
> +	gate->lock = &sun8i_display_lock;
> +
> +	div = kzalloc(sizeof(*div), GFP_KERNEL);
> +	if (!div)
> +		goto free_gate;
> +
> +	div->reg = reg;
> +	div->shift = 0;
> +	div->width = 4;
> +	div->lock = &sun8i_display_lock;
> +
> +	clk = clk_register_composite(NULL, clk_name,
> +				     parents, n,
> +				     mux ? &mux->hw : NULL, &clk_mux_ops,
> +				     &div->hw, &clk_divider_ops,
> +				     &gate->hw, &clk_gate_ops,
> +				     0);
> +	if (IS_ERR(clk)) {
> +		pr_err("%s: Couldn't register the clock\n", clk_name);
> +		goto free_div;
> +	}
> +
> +	of_clk_add_provider(node, of_clk_src_simple_get, clk);
> +
> +	return;
> +
> +free_div:
> +	kfree(div);
> +free_gate:
> +	kfree(gate);
> +	kfree(mux);
> +}
> +
> +CLK_OF_DECLARE(sun8i_display, "allwinner,sun8i-display-clk", sun8i_display_setup);

As part of my DRM patches, I've added a clk-display clock that can
handle that easily.

And actually, as part of bringing up the display engine on the A33, I
already did it:
https://github.com/mripard/linux/commit/92b6843b5ee5b70cb2be3638df31d3eca28a4dba
https://github.com/mripard/linux/commit/81e8ea74be5e72124eb584432bb79ff75f90d9ed

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
  2016-01-18 19:09     ` Maxime Ripard
@ 2016-01-19  8:09       ` Jean-Francois Moine
  -1 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-19  8:09 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, 18 Jan 2016 20:09:04 +0100
Maxime Ripard <maxime.ripard@free-electrons.com> wrote:

> > +static const struct clk_ops clk_sun8i_pll3_fact_ops = {
> > +	.recalc_rate = sun8i_pll3_recalc_rate,
> > +	.round_rate = sun8i_pll3_round_rate,
> > +	.set_rate = sun8i_pll3_set_rate,
> > +};
> 
> We have the clk-factors stuff to handle this easily, could you use
> that instead ?

No, the sun6i/8i pll3 offers direct 297MHz and 270MHz.

> As part of my DRM patches, I've added a clk-display clock that can
> handle that easily.
> 
> And actually, as part of bringing up the display engine on the A33, I
> already did it:
> https://github.com/mripard/linux/commit/92b6843b5ee5b70cb2be3638df31d3eca28a4dba
> https://github.com/mripard/linux/commit/81e8ea74be5e72124eb584432bb79ff75f90d9ed

I don't remember any patch request from yours in the Linux
mailing-lists about these developments.

Otherwise, about this old RFC, Chen-Yu Tsai replied:

> > Add the clock types which are used by the sun8i family for video.
> 
> These clocks first appeared in the A31.
> 
> > Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
> > ---
> >  drivers/clk/sunxi/Makefile            |   1 +
> >  drivers/clk/sunxi/clk-sun8i-display.c | 247 ++++++++++++++++++++++++++++++++++
> 
> Please split this into 2 patches, and 2 files: one for PLL3, named
> clk-sun6i-pll3.c, and one for the display mod clocks, named
> clk-sun6i-display.c

My new patch series about the H3 display was sent 4 days ago
(but not sure it reached the list linux-clk at vger.kernel.org
because of some non-ASCII characters).

-- 
A galon		|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
@ 2016-01-19  8:09       ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-19  8:09 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Emilio López, Chen-Yu Tsai, linux-arm-kernel, dri-devel

On Mon, 18 Jan 2016 20:09:04 +0100
Maxime Ripard <maxime.ripard@free-electrons.com> wrote:

> > +static const struct clk_ops clk_sun8i_pll3_fact_ops = {
> > +	.recalc_rate = sun8i_pll3_recalc_rate,
> > +	.round_rate = sun8i_pll3_round_rate,
> > +	.set_rate = sun8i_pll3_set_rate,
> > +};
> 
> We have the clk-factors stuff to handle this easily, could you use
> that instead ?

No, the sun6i/8i pll3 offers direct 297MHz and 270MHz.

> As part of my DRM patches, I've added a clk-display clock that can
> handle that easily.
> 
> And actually, as part of bringing up the display engine on the A33, I
> already did it:
> https://github.com/mripard/linux/commit/92b6843b5ee5b70cb2be3638df31d3eca28a4dba
> https://github.com/mripard/linux/commit/81e8ea74be5e72124eb584432bb79ff75f90d9ed

I don't remember any patch request from yours in the Linux
mailing-lists about these developments.

Otherwise, about this old RFC, Chen-Yu Tsai replied:

> > Add the clock types which are used by the sun8i family for video.
> 
> These clocks first appeared in the A31.
> 
> > Signed-off-by: Jean-Francois Moine <moinejf@free.fr>
> > ---
> >  drivers/clk/sunxi/Makefile            |   1 +
> >  drivers/clk/sunxi/clk-sun8i-display.c | 247 ++++++++++++++++++++++++++++++++++
> 
> Please split this into 2 patches, and 2 files: one for PLL3, named
> clk-sun6i-pll3.c, and one for the display mod clocks, named
> clk-sun6i-display.c

My new patch series about the H3 display was sent 4 days ago
(but not sure it reached the list linux-clk@vger.kernel.org
because of some non-ASCII characters).

-- 
A galon		|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 0/2] Add a display driver to the Allwinner H3
  2016-01-18 10:18       ` Maxime Ripard
@ 2016-01-19  8:49         ` Jean-Francois Moine
  -1 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-19  8:49 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, 18 Jan 2016 11:18:27 +0100
Maxime Ripard <maxime.ripard@free-electrons.com> wrote:

> > Then, the DE2 sources contain only:
> > 
> > 	All Winner Tech, All Right Reserved. 2014-2015 Copyright (c)
> > 
> > Eventually, there is no copyright/author/history in the HDMI sources.
> 
> That one is nasty though :/
> 
> It seems to be a GPL violation though, so we have two solutions:
> 
> A) have a clean room implementation
> B) Ask allwinner to comply with the license
> 
> The former doesn't look likely to happen soon... Can you open an issue
> on this on their linux github repo?

Do you really think that I could have more luck than people of the
linux-sunxi or pine64 teams?

-- 
A galon		|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 0/2] Add a display driver to the Allwinner H3
@ 2016-01-19  8:49         ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-19  8:49 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Emilio López, Chen-Yu Tsai, linux-arm-kernel, dri-devel

On Mon, 18 Jan 2016 11:18:27 +0100
Maxime Ripard <maxime.ripard@free-electrons.com> wrote:

> > Then, the DE2 sources contain only:
> > 
> > 	All Winner Tech, All Right Reserved. 2014-2015 Copyright (c)
> > 
> > Eventually, there is no copyright/author/history in the HDMI sources.
> 
> That one is nasty though :/
> 
> It seems to be a GPL violation though, so we have two solutions:
> 
> A) have a clean room implementation
> B) Ask allwinner to comply with the license
> 
> The former doesn't look likely to happen soon... Can you open an issue
> on this on their linux github repo?

Do you really think that I could have more luck than people of the
linux-sunxi or pine64 teams?

-- 
A galon		|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
  2016-01-19  8:09       ` Jean-Francois Moine
@ 2016-01-27 21:50         ` Maxime Ripard
  -1 siblings, 0 replies; 36+ messages in thread
From: Maxime Ripard @ 2016-01-27 21:50 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

On Tue, Jan 19, 2016 at 09:09:01AM +0100, Jean-Francois Moine wrote:
> On Mon, 18 Jan 2016 20:09:04 +0100
> Maxime Ripard <maxime.ripard@free-electrons.com> wrote:
> 
> > > +static const struct clk_ops clk_sun8i_pll3_fact_ops = {
> > > +	.recalc_rate = sun8i_pll3_recalc_rate,
> > > +	.round_rate = sun8i_pll3_round_rate,
> > > +	.set_rate = sun8i_pll3_set_rate,
> > > +};
> > 
> > We have the clk-factors stuff to handle this easily, could you use
> > that instead ?
> 
> No, the sun6i/8i pll3 offers direct 297MHz and 270MHz.

That's true, but so far it's something that never has been really
needed. This PLL is not the same one using the fractional mode, so I
guess we could extend the clk-factors to be able to deal with
that. The video pll in the A10 (pll3) is also in this case, so does
the A31 PLL3 and PLL4.

Also note that all these clocks can reach those frequencies through
what allwinner calls the integer mode, so apart from the hardware
readout, we don't really need it anyway.

> > As part of my DRM patches, I've added a clk-display clock that can
> > handle that easily.
> > 
> > And actually, as part of bringing up the display engine on the A33, I
> > already did it:
> > https://github.com/mripard/linux/commit/92b6843b5ee5b70cb2be3638df31d3eca28a4dba
> > https://github.com/mripard/linux/commit/81e8ea74be5e72124eb584432bb79ff75f90d9ed
> 
> I don't remember any patch request from yours in the Linux
> mailing-lists about these developments.

Indeed, I was waiting for the first DRM developments to get in before
sending those for review.

> 
> Otherwise, about this old RFC, Chen-Yu Tsai replied:
> 
> > > Add the clock types which are used by the sun8i family for video.
> > 
> > These clocks first appeared in the A31.

The video PLL is, the display engine and tcon clocks are a bit
different (mostly because of their weird parent configuration that
need a muxing table). Note that I'm talking about the A23 / A33. I
haven't checked for the H3.

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20160127/0cf1a529/attachment.sig>

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
@ 2016-01-27 21:50         ` Maxime Ripard
  0 siblings, 0 replies; 36+ messages in thread
From: Maxime Ripard @ 2016-01-27 21:50 UTC (permalink / raw)
  To: Jean-Francois Moine
  Cc: Emilio López, Chen-Yu Tsai, linux-arm-kernel, dri-devel


[-- Attachment #1.1: Type: text/plain, Size: 2159 bytes --]

Hi,

On Tue, Jan 19, 2016 at 09:09:01AM +0100, Jean-Francois Moine wrote:
> On Mon, 18 Jan 2016 20:09:04 +0100
> Maxime Ripard <maxime.ripard@free-electrons.com> wrote:
> 
> > > +static const struct clk_ops clk_sun8i_pll3_fact_ops = {
> > > +	.recalc_rate = sun8i_pll3_recalc_rate,
> > > +	.round_rate = sun8i_pll3_round_rate,
> > > +	.set_rate = sun8i_pll3_set_rate,
> > > +};
> > 
> > We have the clk-factors stuff to handle this easily, could you use
> > that instead ?
> 
> No, the sun6i/8i pll3 offers direct 297MHz and 270MHz.

That's true, but so far it's something that never has been really
needed. This PLL is not the same one using the fractional mode, so I
guess we could extend the clk-factors to be able to deal with
that. The video pll in the A10 (pll3) is also in this case, so does
the A31 PLL3 and PLL4.

Also note that all these clocks can reach those frequencies through
what allwinner calls the integer mode, so apart from the hardware
readout, we don't really need it anyway.

> > As part of my DRM patches, I've added a clk-display clock that can
> > handle that easily.
> > 
> > And actually, as part of bringing up the display engine on the A33, I
> > already did it:
> > https://github.com/mripard/linux/commit/92b6843b5ee5b70cb2be3638df31d3eca28a4dba
> > https://github.com/mripard/linux/commit/81e8ea74be5e72124eb584432bb79ff75f90d9ed
> 
> I don't remember any patch request from yours in the Linux
> mailing-lists about these developments.

Indeed, I was waiting for the first DRM developments to get in before
sending those for review.

> 
> Otherwise, about this old RFC, Chen-Yu Tsai replied:
> 
> > > Add the clock types which are used by the sun8i family for video.
> > 
> > These clocks first appeared in the A31.

The video PLL is, the display engine and tcon clocks are a bit
different (mostly because of their weird parent configuration that
need a muxing table). Note that I'm talking about the A23 / A33. I
haven't checked for the H3.

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
  2016-01-27 21:50         ` Maxime Ripard
@ 2016-01-28 14:55           ` Jean-Francois Moine
  -1 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-28 14:55 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 27 Jan 2016 22:50:51 +0100
Maxime Ripard <maxime.ripard@free-electrons.com> wrote:

> Hi,
> 
> On Tue, Jan 19, 2016 at 09:09:01AM +0100, Jean-Francois Moine wrote:
> > On Mon, 18 Jan 2016 20:09:04 +0100
> > Maxime Ripard <maxime.ripard@free-electrons.com> wrote:
	[snip]
> > > We have the clk-factors stuff to handle this easily, could you use
> > > that instead ?
> > 
> > No, the sun6i/8i pll3 offers direct 297MHz and 270MHz.
> 
> That's true, but so far it's something that never has been really
> needed. This PLL is not the same one using the fractional mode, so I
> guess we could extend the clk-factors to be able to deal with
> that. The video pll in the A10 (pll3) is also in this case, so does
> the A31 PLL3 and PLL4.
> 
> Also note that all these clocks can reach those frequencies through
> what allwinner calls the integer mode, so apart from the hardware
> readout, we don't really need it anyway.

Maybe it is too simple! 297MHz is the PLL3 frequency which can be used
for most video modes. And, so, as it is the default value, in my tests,
I never saw the PLL3 set_rate function being called.

	[snip]
> > Otherwise, about this old RFC, Chen-Yu Tsai replied:
> > 
> > > > Add the clock types which are used by the sun8i family for video.
> > > 
> > > These clocks first appeared in the A31.
> 
> The video PLL is, the display engine and tcon clocks are a bit
> different (mostly because of their weird parent configuration that
> need a muxing table). Note that I'm talking about the A23 / A33. I
> haven't checked for the H3.

The TCONs of the A23/A33 and H3 SoCs are quite the same.
The display engines 2 of the H3/A64/A83T are the same and they ask for
a fixed clock (432MHz).

-- 
Ken ar c'henta?	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 1/2] clk: sunxi: Add sun8i display support
@ 2016-01-28 14:55           ` Jean-Francois Moine
  0 siblings, 0 replies; 36+ messages in thread
From: Jean-Francois Moine @ 2016-01-28 14:55 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Emilio López, Chen-Yu Tsai, linux-arm-kernel, dri-devel

On Wed, 27 Jan 2016 22:50:51 +0100
Maxime Ripard <maxime.ripard@free-electrons.com> wrote:

> Hi,
> 
> On Tue, Jan 19, 2016 at 09:09:01AM +0100, Jean-Francois Moine wrote:
> > On Mon, 18 Jan 2016 20:09:04 +0100
> > Maxime Ripard <maxime.ripard@free-electrons.com> wrote:
	[snip]
> > > We have the clk-factors stuff to handle this easily, could you use
> > > that instead ?
> > 
> > No, the sun6i/8i pll3 offers direct 297MHz and 270MHz.
> 
> That's true, but so far it's something that never has been really
> needed. This PLL is not the same one using the fractional mode, so I
> guess we could extend the clk-factors to be able to deal with
> that. The video pll in the A10 (pll3) is also in this case, so does
> the A31 PLL3 and PLL4.
> 
> Also note that all these clocks can reach those frequencies through
> what allwinner calls the integer mode, so apart from the hardware
> readout, we don't really need it anyway.

Maybe it is too simple! 297MHz is the PLL3 frequency which can be used
for most video modes. And, so, as it is the default value, in my tests,
I never saw the PLL3 set_rate function being called.

	[snip]
> > Otherwise, about this old RFC, Chen-Yu Tsai replied:
> > 
> > > > Add the clock types which are used by the sun8i family for video.
> > > 
> > > These clocks first appeared in the A31.
> 
> The video PLL is, the display engine and tcon clocks are a bit
> different (mostly because of their weird parent configuration that
> need a muxing table). Note that I'm talking about the A23 / A33. I
> haven't checked for the H3.

The TCONs of the A23/A33 and H3 SoCs are quite the same.
The display engines 2 of the H3/A64/A83T are the same and they ask for
a fixed clock (432MHz).

-- 
Ken ar c'hentañ	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH RFC 0/2] Add a display driver to the Allwinner H3
  2016-01-19  8:49         ` Jean-Francois Moine
@ 2016-02-02 16:58           ` Maxime Ripard
  -1 siblings, 0 replies; 36+ messages in thread
From: Maxime Ripard @ 2016-02-02 16:58 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jan 19, 2016 at 09:49:07AM +0100, Jean-Francois Moine wrote:
> On Mon, 18 Jan 2016 11:18:27 +0100
> Maxime Ripard <maxime.ripard@free-electrons.com> wrote:
> 
> > > Then, the DE2 sources contain only:
> > > 
> > > 	All Winner Tech, All Right Reserved. 2014-2015 Copyright (c)
> > > 
> > > Eventually, there is no copyright/author/history in the HDMI sources.
> > 
> > That one is nasty though :/
> > 
> > It seems to be a GPL violation though, so we have two solutions:
> > 
> > A) have a clean room implementation
> > B) Ask allwinner to comply with the license
> > 
> > The former doesn't look likely to happen soon... Can you open an issue
> > on this on their linux github repo?
> 
> Do you really think that I could have more luck than people of the
> linux-sunxi or pine64 teams?

It did work for a few issues in the past...

Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20160202/549266c0/attachment.sig>

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH RFC 0/2] Add a display driver to the Allwinner H3
@ 2016-02-02 16:58           ` Maxime Ripard
  0 siblings, 0 replies; 36+ messages in thread
From: Maxime Ripard @ 2016-02-02 16:58 UTC (permalink / raw)
  To: Jean-Francois Moine
  Cc: Emilio López, Chen-Yu Tsai, linux-arm-kernel, dri-devel


[-- Attachment #1.1: Type: text/plain, Size: 986 bytes --]

On Tue, Jan 19, 2016 at 09:49:07AM +0100, Jean-Francois Moine wrote:
> On Mon, 18 Jan 2016 11:18:27 +0100
> Maxime Ripard <maxime.ripard@free-electrons.com> wrote:
> 
> > > Then, the DE2 sources contain only:
> > > 
> > > 	All Winner Tech, All Right Reserved. 2014-2015 Copyright (c)
> > > 
> > > Eventually, there is no copyright/author/history in the HDMI sources.
> > 
> > That one is nasty though :/
> > 
> > It seems to be a GPL violation though, so we have two solutions:
> > 
> > A) have a clean room implementation
> > B) Ask allwinner to comply with the license
> > 
> > The former doesn't look likely to happen soon... Can you open an issue
> > on this on their linux github repo?
> 
> Do you really think that I could have more luck than people of the
> linux-sunxi or pine64 teams?

It did work for a few issues in the past...

Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 36+ messages in thread

end of thread, other threads:[~2016-02-02 16:59 UTC | newest]

Thread overview: 36+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-01-05 19:15 [PATCH RFC 0/2] Add a display driver to the Allwinner H3 Jean-Francois Moine
2016-01-05 19:15 ` Jean-Francois Moine
2016-01-05 18:28 ` [PATCH RFC 1/2] clk: sunxi: Add sun8i display support Jean-Francois Moine
2016-01-05 18:28   ` Jean-Francois Moine
2016-01-06  2:39   ` Chen-Yu Tsai
2016-01-06  2:39     ` Chen-Yu Tsai
2016-01-08 17:50     ` Jean-Francois Moine
2016-01-08 17:50       ` Jean-Francois Moine
2016-01-18 19:09   ` Maxime Ripard
2016-01-18 19:09     ` Maxime Ripard
2016-01-19  8:09     ` Jean-Francois Moine
2016-01-19  8:09       ` Jean-Francois Moine
2016-01-27 21:50       ` Maxime Ripard
2016-01-27 21:50         ` Maxime Ripard
2016-01-28 14:55         ` Jean-Francois Moine
2016-01-28 14:55           ` Jean-Francois Moine
2016-01-05 18:40 ` [PATCH RFC 2/2] drm: sunxi: Add a basic DRM driver for Allwinner DE2 Jean-Francois Moine
2016-01-05 18:40   ` Jean-Francois Moine
2016-01-05 20:38   ` Russell King - ARM Linux
2016-01-05 20:38     ` Russell King - ARM Linux
2016-01-11 18:56     ` Jean-Francois Moine
2016-01-11 18:56       ` Jean-Francois Moine
2016-01-06 20:41   ` Jens Kuske
2016-01-06 20:41     ` Jens Kuske
2016-01-13 17:37     ` Jean-Francois Moine
2016-01-13 17:37       ` Jean-Francois Moine
2016-01-06 21:20 ` [PATCH RFC 0/2] Add a display driver to the Allwinner H3 Maxime Ripard
2016-01-06 21:20   ` Maxime Ripard
2016-01-08 17:13   ` Jean-Francois Moine
2016-01-08 17:13     ` Jean-Francois Moine
2016-01-18 10:18     ` Maxime Ripard
2016-01-18 10:18       ` Maxime Ripard
2016-01-19  8:49       ` Jean-Francois Moine
2016-01-19  8:49         ` Jean-Francois Moine
2016-02-02 16:58         ` Maxime Ripard
2016-02-02 16:58           ` Maxime Ripard

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.