mirror of
https://github.com/armbian/build.git
synced 2025-08-14 15:16:58 +02:00
1923 lines
56 KiB
Diff
1923 lines
56 KiB
Diff
diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-r40.c b/drivers/clk/sunxi-ng/ccu-sun8i-r40.c
|
|
index 933f2e68f42a..c16a62a7bdbd 100644
|
|
--- a/drivers/clk/sunxi-ng/ccu-sun8i-r40.c
|
|
+++ b/drivers/clk/sunxi-ng/ccu-sun8i-r40.c
|
|
@@ -65,17 +65,18 @@ static SUNXI_CCU_NM_WITH_GATE_LOCK(pll_audio_base_clk, "pll-audio-base",
|
|
CLK_SET_RATE_UNGATE);
|
|
|
|
/* TODO: The result of N/M is required to be in [8, 25] range. */
|
|
-static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_video0_clk, "pll-video0",
|
|
- "osc24M", 0x0010,
|
|
- 8, 7, /* N */
|
|
- 0, 4, /* M */
|
|
- BIT(24), /* frac enable */
|
|
- BIT(25), /* frac select */
|
|
- 270000000, /* frac rate 0 */
|
|
- 297000000, /* frac rate 1 */
|
|
- BIT(31), /* gate */
|
|
- BIT(28), /* lock */
|
|
- CLK_SET_RATE_UNGATE);
|
|
+static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK_MIN(pll_video0_clk, "pll-video0",
|
|
+ "osc24M", 0x0010,
|
|
+ 192000000, /* Minimum rate */
|
|
+ 8, 7, /* N */
|
|
+ 0, 4, /* M */
|
|
+ BIT(24), /* frac enable */
|
|
+ BIT(25), /* frac select */
|
|
+ 270000000, /* frac rate 0 */
|
|
+ 297000000, /* frac rate 1 */
|
|
+ BIT(31), /* gate */
|
|
+ BIT(28), /* lock */
|
|
+ CLK_SET_RATE_UNGATE);
|
|
|
|
/* TODO: The result of N/M is required to be in [8, 25] range. */
|
|
static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_ve_clk, "pll-ve",
|
|
@@ -151,17 +152,18 @@ static struct ccu_nk pll_periph1_clk = {
|
|
};
|
|
|
|
/* TODO: The result of N/M is required to be in [8, 25] range. */
|
|
-static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_video1_clk, "pll-video1",
|
|
- "osc24M", 0x030,
|
|
- 8, 7, /* N */
|
|
- 0, 4, /* M */
|
|
- BIT(24), /* frac enable */
|
|
- BIT(25), /* frac select */
|
|
- 270000000, /* frac rate 0 */
|
|
- 297000000, /* frac rate 1 */
|
|
- BIT(31), /* gate */
|
|
- BIT(28), /* lock */
|
|
- CLK_SET_RATE_UNGATE);
|
|
+static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK_MIN(pll_video1_clk, "pll-video1",
|
|
+ "osc24M", 0x030,
|
|
+ 192000000, /* Minimum rate */
|
|
+ 8, 7, /* N */
|
|
+ 0, 4, /* M */
|
|
+ BIT(24), /* frac enable */
|
|
+ BIT(25), /* frac select */
|
|
+ 270000000, /* frac rate 0 */
|
|
+ 297000000, /* frac rate 1 */
|
|
+ BIT(31), /* gate */
|
|
+ BIT(28), /* lock */
|
|
+ CLK_SET_RATE_UNGATE);
|
|
|
|
static struct ccu_nkm pll_sata_clk = {
|
|
.enable = BIT(31),
|
|
diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-r40.c b/drivers/clk/sunxi-ng/ccu-sun8i-r40.c
|
|
index c16a62a7bdbd..fa5317719684 100644
|
|
--- a/drivers/clk/sunxi-ng/ccu-sun8i-r40.c
|
|
+++ b/drivers/clk/sunxi-ng/ccu-sun8i-r40.c
|
|
@@ -655,7 +655,8 @@ static SUNXI_CCU_GATE(dram_deinterlace_clk, "dram-deinterlace", "dram",
|
|
|
|
static const char * const de_parents[] = { "pll-periph0-2x", "pll-de" };
|
|
static SUNXI_CCU_M_WITH_MUX_GATE(de_clk, "de", de_parents,
|
|
- 0x104, 0, 4, 24, 3, BIT(31), 0);
|
|
+ 0x104, 0, 4, 24, 3, BIT(31),
|
|
+ CLK_SET_RATE_PARENT);
|
|
static SUNXI_CCU_M_WITH_MUX_GATE(mp_clk, "mp", de_parents,
|
|
0x108, 0, 4, 24, 3, BIT(31), 0);
|
|
|
|
@@ -667,9 +668,11 @@ static SUNXI_CCU_MUX_WITH_GATE(tcon_lcd0_clk, "tcon-lcd0", tcon_parents,
|
|
static SUNXI_CCU_MUX_WITH_GATE(tcon_lcd1_clk, "tcon-lcd1", tcon_parents,
|
|
0x114, 24, 3, BIT(31), CLK_SET_RATE_PARENT);
|
|
static SUNXI_CCU_M_WITH_MUX_GATE(tcon_tv0_clk, "tcon-tv0", tcon_parents,
|
|
- 0x118, 0, 4, 24, 3, BIT(31), 0);
|
|
+ 0x118, 0, 4, 24, 3, BIT(31),
|
|
+ CLK_SET_RATE_PARENT);
|
|
static SUNXI_CCU_M_WITH_MUX_GATE(tcon_tv1_clk, "tcon-tv1", tcon_parents,
|
|
- 0x11c, 0, 4, 24, 3, BIT(31), 0);
|
|
+ 0x11c, 0, 4, 24, 3, BIT(31),
|
|
+ CLK_SET_RATE_PARENT);
|
|
|
|
static const char * const deinterlace_parents[] = { "pll-periph0",
|
|
"pll-periph1" };
|
|
@@ -699,7 +702,8 @@ static SUNXI_CCU_GATE(avs_clk, "avs", "osc24M",
|
|
|
|
static const char * const hdmi_parents[] = { "pll-video0", "pll-video1" };
|
|
static SUNXI_CCU_M_WITH_MUX_GATE(hdmi_clk, "hdmi", hdmi_parents,
|
|
- 0x150, 0, 4, 24, 2, BIT(31), 0);
|
|
+ 0x150, 0, 4, 24, 2, BIT(31),
|
|
+ CLK_SET_RATE_PARENT);
|
|
|
|
static SUNXI_CCU_GATE(hdmi_slow_clk, "hdmi-slow", "osc24M",
|
|
0x154, BIT(31), 0);
|
|
diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-r40.h b/drivers/clk/sunxi-ng/ccu-sun8i-r40.h
|
|
index 0db8e1e97af8..db2a1243f9ff 100644
|
|
--- a/drivers/clk/sunxi-ng/ccu-sun8i-r40.h
|
|
+++ b/drivers/clk/sunxi-ng/ccu-sun8i-r40.h
|
|
@@ -25,7 +25,9 @@
|
|
#define CLK_PLL_AUDIO_2X 4
|
|
#define CLK_PLL_AUDIO_4X 5
|
|
#define CLK_PLL_AUDIO_8X 6
|
|
-#define CLK_PLL_VIDEO0 7
|
|
+
|
|
+/* PLL_VIDEO0 is exported */
|
|
+
|
|
#define CLK_PLL_VIDEO0_2X 8
|
|
#define CLK_PLL_VE 9
|
|
#define CLK_PLL_DDR0 10
|
|
@@ -34,7 +36,9 @@
|
|
#define CLK_PLL_PERIPH0_2X 13
|
|
#define CLK_PLL_PERIPH1 14
|
|
#define CLK_PLL_PERIPH1_2X 15
|
|
-#define CLK_PLL_VIDEO1 16
|
|
+
|
|
+/* PLL_VIDEO1 is exported */
|
|
+
|
|
#define CLK_PLL_VIDEO1_2X 17
|
|
#define CLK_PLL_SATA 18
|
|
#define CLK_PLL_SATA_OUT 19
|
|
diff --git a/include/dt-bindings/clock/sun8i-r40-ccu.h b/include/dt-bindings/clock/sun8i-r40-ccu.h
|
|
index 4fa5f69fc297..f9e15a235626 100644
|
|
--- a/include/dt-bindings/clock/sun8i-r40-ccu.h
|
|
+++ b/include/dt-bindings/clock/sun8i-r40-ccu.h
|
|
@@ -43,6 +43,10 @@
|
|
#ifndef _DT_BINDINGS_CLK_SUN8I_R40_H_
|
|
#define _DT_BINDINGS_CLK_SUN8I_R40_H_
|
|
|
|
+#define CLK_PLL_VIDEO0 7
|
|
+
|
|
+#define CLK_PLL_VIDEO1 16
|
|
+
|
|
#define CLK_CPU 24
|
|
|
|
#define CLK_BUS_MIPI_DSI 29
|
|
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
|
|
index 3346c1e2a7a0..fe31b1510717 100644
|
|
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
|
|
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
|
|
@@ -187,6 +187,62 @@ And on the A23, A31, A31s and A33, you need one more clock line:
|
|
- 'lvds-alt': An alternative clock source, separate from the TCON channel 0
|
|
clock, that can be used to drive the LVDS clock
|
|
|
|
+TCON TOP
|
|
+--------
|
|
+
|
|
+TCON TOPs main purpose is to configure whole display pipeline. It determines
|
|
+relationships between mixers and TCONs, selects source TCON for HDMI, muxes
|
|
+LCD and TV encoder GPIO output, selects TV encoder clock source and contains
|
|
+additional TV TCON and DSI gates.
|
|
+
|
|
+It allows display pipeline to be configured in very different ways:
|
|
+
|
|
+ / LCD0/LVDS0
|
|
+ / [0] TCON-LCD0
|
|
+ | \ MIPI DSI
|
|
+ mixer0 |
|
|
+ \ / [1] TCON-LCD1 - LCD1/LVDS1
|
|
+ TCON-TOP
|
|
+ / \ [2] TCON-TV0 [0] - TVE0/RGB
|
|
+ mixer1 | \
|
|
+ | TCON-TOP - HDMI
|
|
+ | /
|
|
+ \ [3] TCON-TV1 [1] - TVE1/RGB
|
|
+
|
|
+Note that both TCON TOP references same physical unit. Both mixers can be
|
|
+connected to any TCON.
|
|
+
|
|
+Required properties:
|
|
+ - compatible: value must be one of:
|
|
+ * allwinner,sun8i-r40-tcon-top
|
|
+ - reg: base address and size of the memory-mapped region.
|
|
+ - clocks: phandle to the clocks feeding the TCON TOP
|
|
+ * bus: TCON TOP interface clock
|
|
+ * tcon-tv0: TCON TV0 clock
|
|
+ * tve0: TVE0 clock
|
|
+ * tcon-tv1: TCON TV1 clock
|
|
+ * tve1: TVE0 clock
|
|
+ * dsi: MIPI DSI clock
|
|
+ - clock-names: clock name mentioned above
|
|
+ - resets: phandle to the reset line driving the TCON TOP
|
|
+ - #clock-cells : must contain 1
|
|
+ - clock-output-names: Names of clocks created for TCON TV0 channel clock,
|
|
+ TCON TV1 channel clock and DSI channel clock, in that order.
|
|
+
|
|
+- ports: A ports node with endpoint definitions as defined in
|
|
+ Documentation/devicetree/bindings/media/video-interfaces.txt. 6 ports should
|
|
+ be defined:
|
|
+ * port 0 is input for mixer0 mux
|
|
+ * port 1 is output for mixer0 mux
|
|
+ * port 2 is input for mixer1 mux
|
|
+ * port 3 is output for mixer1 mux
|
|
+ * port 4 is input for HDMI mux
|
|
+ * port 5 is output for HDMI mux
|
|
+ All output endpoints for mixer muxes and input endpoints for HDMI mux should
|
|
+ have reg property with the id of the target TCON, as shown in above graph
|
|
+ (0-3 for mixer muxes and 0-1 for HDMI mux). All ports should have only one
|
|
+ endpoint connected to remote endpoint.
|
|
+
|
|
DRC
|
|
---
|
|
|
|
diff --git a/include/dt-bindings/clock/sun8i-tcon-top.h b/include/dt-bindings/clock/sun8i-tcon-top.h
|
|
new file mode 100644
|
|
index 000000000000..25164d767835
|
|
--- /dev/null
|
|
+++ b/include/dt-bindings/clock/sun8i-tcon-top.h
|
|
@@ -0,0 +1,11 @@
|
|
+/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
|
|
+/* Copyright (C) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
|
|
+
|
|
+#ifndef _DT_BINDINGS_CLOCK_SUN8I_TCON_TOP_H_
|
|
+#define _DT_BINDINGS_CLOCK_SUN8I_TCON_TOP_H_
|
|
+
|
|
+#define CLK_TCON_TOP_TV0 0
|
|
+#define CLK_TCON_TOP_TV1 1
|
|
+#define CLK_TCON_TOP_DSI 2
|
|
+
|
|
+#endif /* _DT_BINDINGS_CLOCK_SUN8I_TCON_TOP_H_ */
|
|
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
|
|
index 2589f4acd5ae..09fbfd6304ba 100644
|
|
--- a/drivers/gpu/drm/sun4i/Makefile
|
|
+++ b/drivers/gpu/drm/sun4i/Makefile
|
|
@@ -16,7 +16,8 @@ sun8i-drm-hdmi-y += sun8i_hdmi_phy_clk.o
|
|
|
|
sun8i-mixer-y += sun8i_mixer.o sun8i_ui_layer.o \
|
|
sun8i_vi_layer.o sun8i_ui_scaler.o \
|
|
- sun8i_vi_scaler.o sun8i_csc.o
|
|
+ sun8i_vi_scaler.o sun8i_csc.o \
|
|
+ sun8i_tcon_top.o
|
|
|
|
sun4i-tcon-y += sun4i_crtc.o
|
|
sun4i-tcon-y += sun4i_dotclock.o
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c
|
|
new file mode 100644
|
|
index 000000000000..8da0460e0028
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c
|
|
@@ -0,0 +1,300 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
|
|
+
|
|
+#include <drm/drmP.h>
|
|
+
|
|
+#include <dt-bindings/clock/sun8i-tcon-top.h>
|
|
+
|
|
+#include <linux/bitfield.h>
|
|
+#include <linux/component.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_graph.h>
|
|
+#include <linux/platform_device.h>
|
|
+
|
|
+#include "sun8i_tcon_top.h"
|
|
+
|
|
+static int sun8i_tcon_top_get_connected_ep_id(struct device_node *node,
|
|
+ int port_id)
|
|
+{
|
|
+ struct device_node *ep, *remote, *port;
|
|
+ struct of_endpoint endpoint;
|
|
+
|
|
+ port = of_graph_get_port_by_id(node, port_id);
|
|
+ if (!port)
|
|
+ return -ENOENT;
|
|
+
|
|
+ for_each_available_child_of_node(port, ep) {
|
|
+ remote = of_graph_get_remote_port_parent(ep);
|
|
+ if (!remote)
|
|
+ continue;
|
|
+
|
|
+ if (of_device_is_available(remote)) {
|
|
+ of_graph_parse_endpoint(ep, &endpoint);
|
|
+
|
|
+ of_node_put(remote);
|
|
+
|
|
+ return endpoint.id;
|
|
+ }
|
|
+
|
|
+ of_node_put(remote);
|
|
+ }
|
|
+
|
|
+ return -ENOENT;
|
|
+}
|
|
+
|
|
+static struct clk_hw *sun8i_tcon_top_register_gate(struct device *dev,
|
|
+ struct clk *parent,
|
|
+ void __iomem *regs,
|
|
+ spinlock_t *lock,
|
|
+ u8 bit, int name_index)
|
|
+{
|
|
+ const char *clk_name, *parent_name;
|
|
+ int ret;
|
|
+
|
|
+ parent_name = __clk_get_name(parent);
|
|
+ ret = of_property_read_string_index(dev->of_node,
|
|
+ "clock-output-names", name_index,
|
|
+ &clk_name);
|
|
+ if (ret)
|
|
+ return ERR_PTR(ret);
|
|
+
|
|
+ return clk_hw_register_gate(dev, clk_name, parent_name,
|
|
+ CLK_SET_RATE_PARENT,
|
|
+ regs + TCON_TOP_GATE_SRC_REG,
|
|
+ bit, 0, lock);
|
|
+};
|
|
+
|
|
+static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
|
|
+ void *data)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct clk *dsi, *tcon_tv0, *tcon_tv1, *tve0, *tve1;
|
|
+ struct clk_hw_onecell_data *clk_data;
|
|
+ struct sun8i_tcon_top *tcon_top;
|
|
+ bool mixer0_unused = false;
|
|
+ struct resource *res;
|
|
+ void __iomem *regs;
|
|
+ int ret, i, id;
|
|
+ u32 val;
|
|
+
|
|
+ tcon_top = devm_kzalloc(dev, sizeof(*tcon_top), GFP_KERNEL);
|
|
+ if (!tcon_top)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ clk_data = devm_kzalloc(dev, sizeof(*clk_data) +
|
|
+ sizeof(*clk_data->hws) * CLK_NUM,
|
|
+ GFP_KERNEL);
|
|
+ if (!clk_data)
|
|
+ return -ENOMEM;
|
|
+ tcon_top->clk_data = clk_data;
|
|
+
|
|
+ spin_lock_init(&tcon_top->reg_lock);
|
|
+
|
|
+ tcon_top->rst = devm_reset_control_get(dev, NULL);
|
|
+ if (IS_ERR(tcon_top->rst)) {
|
|
+ dev_err(dev, "Couldn't get our reset line\n");
|
|
+ return PTR_ERR(tcon_top->rst);
|
|
+ }
|
|
+
|
|
+ tcon_top->bus = devm_clk_get(dev, "bus");
|
|
+ if (IS_ERR(tcon_top->bus)) {
|
|
+ dev_err(dev, "Couldn't get the bus clock\n");
|
|
+ return PTR_ERR(tcon_top->bus);
|
|
+ }
|
|
+
|
|
+ dsi = devm_clk_get(dev, "dsi");
|
|
+ if (IS_ERR(dsi)) {
|
|
+ dev_err(dev, "Couldn't get the dsi clock\n");
|
|
+ return PTR_ERR(dsi);
|
|
+ }
|
|
+
|
|
+ tcon_tv0 = devm_clk_get(dev, "tcon-tv0");
|
|
+ if (IS_ERR(tcon_tv0)) {
|
|
+ dev_err(dev, "Couldn't get the tcon-tv0 clock\n");
|
|
+ return PTR_ERR(tcon_tv0);
|
|
+ }
|
|
+
|
|
+ tcon_tv1 = devm_clk_get(dev, "tcon-tv1");
|
|
+ if (IS_ERR(tcon_tv1)) {
|
|
+ dev_err(dev, "Couldn't get the tcon-tv1 clock\n");
|
|
+ return PTR_ERR(tcon_tv1);
|
|
+ }
|
|
+
|
|
+ tve0 = devm_clk_get(dev, "tve0");
|
|
+ if (IS_ERR(tve0)) {
|
|
+ dev_err(dev, "Couldn't get the tve0 clock\n");
|
|
+ return PTR_ERR(tve0);
|
|
+ }
|
|
+
|
|
+ tve1 = devm_clk_get(dev, "tve1");
|
|
+ if (IS_ERR(tve1)) {
|
|
+ dev_err(dev, "Couldn't get the tve1 clock\n");
|
|
+ return PTR_ERR(tve1);
|
|
+ }
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ regs = devm_ioremap_resource(dev, res);
|
|
+ if (IS_ERR(regs))
|
|
+ return PTR_ERR(regs);
|
|
+
|
|
+ ret = reset_control_deassert(tcon_top->rst);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Could not deassert ctrl reset control\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(tcon_top->bus);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Could not enable bus clock\n");
|
|
+ goto err_assert_reset;
|
|
+ }
|
|
+
|
|
+ val = 0;
|
|
+
|
|
+ /* check if HDMI mux output is connected */
|
|
+ if (sun8i_tcon_top_get_connected_ep_id(dev->of_node, 5) >= 0) {
|
|
+ /* find HDMI input endpoint id, if it is connected at all*/
|
|
+ id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 4);
|
|
+ if (id >= 0)
|
|
+ val = FIELD_PREP(TCON_TOP_HDMI_SRC_MSK, id + 1);
|
|
+ else
|
|
+ DRM_DEBUG_DRIVER("TCON TOP HDMI input is not connected\n");
|
|
+ } else {
|
|
+ DRM_DEBUG_DRIVER("TCON TOP HDMI output is not connected\n");
|
|
+ }
|
|
+
|
|
+ writel(val, regs + TCON_TOP_GATE_SRC_REG);
|
|
+
|
|
+ val = 0;
|
|
+
|
|
+ /* process mixer0 mux output */
|
|
+ id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 1);
|
|
+ if (id >= 0) {
|
|
+ val = FIELD_PREP(TCON_TOP_PORT_DE0_MSK, id);
|
|
+ } else {
|
|
+ DRM_DEBUG_DRIVER("TCON TOP mixer0 output is not connected\n");
|
|
+ mixer0_unused = true;
|
|
+ }
|
|
+
|
|
+ /* process mixer1 mux output */
|
|
+ id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 3);
|
|
+ if (id >= 0) {
|
|
+ val |= FIELD_PREP(TCON_TOP_PORT_DE1_MSK, id);
|
|
+
|
|
+ /*
|
|
+ * mixer0 mux has priority over mixer1 mux. We have to
|
|
+ * make sure mixer0 doesn't overtake TCON from mixer1.
|
|
+ */
|
|
+ if (mixer0_unused && id == 0)
|
|
+ val |= FIELD_PREP(TCON_TOP_PORT_DE0_MSK, 1);
|
|
+ } else {
|
|
+ DRM_DEBUG_DRIVER("TCON TOP mixer1 output is not connected\n");
|
|
+ }
|
|
+
|
|
+ writel(val, regs + TCON_TOP_PORT_SEL_REG);
|
|
+
|
|
+ /*
|
|
+ * TCON TOP has two muxes, which select parent clock for each TCON TV
|
|
+ * channel clock. Parent could be either TCON TV or TVE clock. For now
|
|
+ * we leave this fixed to TCON TV, since TVE driver for R40 is not yet
|
|
+ * implemented. Once it is, graph needs to be traversed to determine
|
|
+ * if TVE is active on each TCON TV. If it is, mux should be switched
|
|
+ * to TVE clock parent.
|
|
+ */
|
|
+ clk_data->hws[CLK_TCON_TOP_TV0] =
|
|
+ sun8i_tcon_top_register_gate(dev, tcon_tv0, regs,
|
|
+ &tcon_top->reg_lock,
|
|
+ TCON_TOP_TCON_TV0_GATE, 0);
|
|
+
|
|
+ clk_data->hws[CLK_TCON_TOP_TV1] =
|
|
+ sun8i_tcon_top_register_gate(dev, tcon_tv1, regs,
|
|
+ &tcon_top->reg_lock,
|
|
+ TCON_TOP_TCON_TV1_GATE, 1);
|
|
+
|
|
+ clk_data->hws[CLK_TCON_TOP_DSI] =
|
|
+ sun8i_tcon_top_register_gate(dev, dsi, regs,
|
|
+ &tcon_top->reg_lock,
|
|
+ TCON_TOP_TCON_DSI_GATE, 2);
|
|
+
|
|
+ for (i = 0; i < CLK_NUM; i++)
|
|
+ if (IS_ERR(clk_data->hws[i])) {
|
|
+ ret = PTR_ERR(clk_data->hws[i]);
|
|
+ goto err_unregister_gates;
|
|
+ }
|
|
+
|
|
+ clk_data->num = CLK_NUM;
|
|
+
|
|
+ ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
|
|
+ clk_data);
|
|
+ if (ret)
|
|
+ goto err_unregister_gates;
|
|
+
|
|
+ dev_set_drvdata(dev, tcon_top);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_unregister_gates:
|
|
+ for (i = 0; i < CLK_NUM; i++)
|
|
+ if (clk_data->hws[i])
|
|
+ clk_hw_unregister_gate(clk_data->hws[i]);
|
|
+ clk_disable_unprepare(tcon_top->bus);
|
|
+err_assert_reset:
|
|
+ reset_control_assert(tcon_top->rst);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void sun8i_tcon_top_unbind(struct device *dev, struct device *master,
|
|
+ void *data)
|
|
+{
|
|
+ struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
|
|
+ struct clk_hw_onecell_data *clk_data = tcon_top->clk_data;
|
|
+ int i;
|
|
+
|
|
+ of_clk_del_provider(dev->of_node);
|
|
+ for (i = 0; i < CLK_NUM; i++)
|
|
+ clk_hw_unregister_gate(clk_data->hws[i]);
|
|
+
|
|
+ clk_disable_unprepare(tcon_top->bus);
|
|
+ reset_control_assert(tcon_top->rst);
|
|
+}
|
|
+
|
|
+static const struct component_ops sun8i_tcon_top_ops = {
|
|
+ .bind = sun8i_tcon_top_bind,
|
|
+ .unbind = sun8i_tcon_top_unbind,
|
|
+};
|
|
+
|
|
+static int sun8i_tcon_top_probe(struct platform_device *pdev)
|
|
+{
|
|
+ return component_add(&pdev->dev, &sun8i_tcon_top_ops);
|
|
+}
|
|
+
|
|
+static int sun8i_tcon_top_remove(struct platform_device *pdev)
|
|
+{
|
|
+ component_del(&pdev->dev, &sun8i_tcon_top_ops);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* sun4i_drv uses this list to check if a device node is a TCON TOP */
|
|
+const struct of_device_id sun8i_tcon_top_of_table[] = {
|
|
+ { .compatible = "allwinner,sun8i-r40-tcon-top" },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, sun8i_tcon_top_of_table);
|
|
+EXPORT_SYMBOL(sun8i_tcon_top_of_table);
|
|
+
|
|
+static struct platform_driver sun8i_tcon_top_platform_driver = {
|
|
+ .probe = sun8i_tcon_top_probe,
|
|
+ .remove = sun8i_tcon_top_remove,
|
|
+ .driver = {
|
|
+ .name = "sun8i-tcon-top",
|
|
+ .of_match_table = sun8i_tcon_top_of_table,
|
|
+ },
|
|
+};
|
|
+module_platform_driver(sun8i_tcon_top_platform_driver);
|
|
+
|
|
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
|
|
+MODULE_DESCRIPTION("Allwinner R40 TCON TOP driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.h b/drivers/gpu/drm/sun4i/sun8i_tcon_top.h
|
|
new file mode 100644
|
|
index 000000000000..39838bbfeaee
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.h
|
|
@@ -0,0 +1,40 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0+ */
|
|
+/* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
|
|
+
|
|
+#ifndef _SUN8I_TCON_TOP_H_
|
|
+#define _SUN8I_TCON_TOP_H_
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/clk-provider.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/spinlock.h>
|
|
+
|
|
+#define TCON_TOP_TCON_TV_SETUP_REG 0x00
|
|
+
|
|
+#define TCON_TOP_PORT_SEL_REG 0x1C
|
|
+#define TCON_TOP_PORT_DE0_MSK GENMASK(1, 0)
|
|
+#define TCON_TOP_PORT_DE1_MSK GENMASK(5, 4)
|
|
+
|
|
+#define TCON_TOP_GATE_SRC_REG 0x20
|
|
+#define TCON_TOP_HDMI_SRC_MSK GENMASK(29, 28)
|
|
+#define TCON_TOP_TCON_TV1_GATE 24
|
|
+#define TCON_TOP_TCON_TV0_GATE 20
|
|
+#define TCON_TOP_TCON_DSI_GATE 16
|
|
+
|
|
+#define CLK_NUM 3
|
|
+
|
|
+struct sun8i_tcon_top {
|
|
+ struct clk *bus;
|
|
+ struct clk_hw_onecell_data *clk_data;
|
|
+ struct reset_control *rst;
|
|
+
|
|
+ /*
|
|
+ * spinlock is used to synchronize access to same
|
|
+ * register where multiple clock gates can be set.
|
|
+ */
|
|
+ spinlock_t reg_lock;
|
|
+};
|
|
+
|
|
+extern const struct of_device_id sun8i_tcon_top_of_table[];
|
|
+
|
|
+#endif /* _SUN8I_TCON_TOP_H_ */
|
|
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
index 50d19605c38f..e15fa2389e3f 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
@@ -283,7 +283,6 @@ static int sun4i_drv_add_endpoints(struct device *dev,
|
|
remote = of_graph_get_remote_port_parent(ep);
|
|
if (!remote) {
|
|
DRM_DEBUG_DRIVER("Error retrieving the output node\n");
|
|
- of_node_put(remote);
|
|
continue;
|
|
}
|
|
|
|
@@ -297,11 +296,13 @@ static int sun4i_drv_add_endpoints(struct device *dev,
|
|
|
|
if (of_graph_parse_endpoint(ep, &endpoint)) {
|
|
DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
|
|
+ of_node_put(remote);
|
|
continue;
|
|
}
|
|
|
|
if (!endpoint.id) {
|
|
DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
|
|
+ of_node_put(remote);
|
|
continue;
|
|
}
|
|
}
|
|
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
index e15fa2389e3f..20193d6f33ba 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
@@ -231,12 +231,55 @@ struct endpoint_list {
|
|
DECLARE_KFIFO(fifo, struct device_node *, 16);
|
|
};
|
|
|
|
+static void sun4i_drv_traverse_endpoints(struct endpoint_list *list,
|
|
+ struct device_node *node,
|
|
+ int port_id)
|
|
+{
|
|
+ struct device_node *ep, *remote, *port;
|
|
+
|
|
+ port = of_graph_get_port_by_id(node, port_id);
|
|
+ if (!port) {
|
|
+ DRM_DEBUG_DRIVER("No output to bind on port %d\n", port_id);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for_each_available_child_of_node(port, ep) {
|
|
+ remote = of_graph_get_remote_port_parent(ep);
|
|
+ if (!remote) {
|
|
+ DRM_DEBUG_DRIVER("Error retrieving the output node\n");
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If the node is our TCON, the first port is used for
|
|
+ * panel or bridges, and will not be part of the
|
|
+ * component framework.
|
|
+ */
|
|
+ if (sun4i_drv_node_is_tcon(node)) {
|
|
+ struct of_endpoint endpoint;
|
|
+
|
|
+ if (of_graph_parse_endpoint(ep, &endpoint)) {
|
|
+ DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
|
|
+ of_node_put(remote);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!endpoint.id) {
|
|
+ DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
|
|
+ of_node_put(remote);
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ kfifo_put(&list->fifo, remote);
|
|
+ }
|
|
+}
|
|
+
|
|
static int sun4i_drv_add_endpoints(struct device *dev,
|
|
struct endpoint_list *list,
|
|
struct component_match **match,
|
|
struct device_node *node)
|
|
{
|
|
- struct device_node *port, *ep, *remote;
|
|
int count = 0;
|
|
|
|
/*
|
|
@@ -272,43 +315,8 @@ static int sun4i_drv_add_endpoints(struct device *dev,
|
|
count++;
|
|
}
|
|
|
|
- /* Inputs are listed first, then outputs */
|
|
- port = of_graph_get_port_by_id(node, 1);
|
|
- if (!port) {
|
|
- DRM_DEBUG_DRIVER("No output to bind\n");
|
|
- return count;
|
|
- }
|
|
-
|
|
- for_each_available_child_of_node(port, ep) {
|
|
- remote = of_graph_get_remote_port_parent(ep);
|
|
- if (!remote) {
|
|
- DRM_DEBUG_DRIVER("Error retrieving the output node\n");
|
|
- continue;
|
|
- }
|
|
-
|
|
- /*
|
|
- * If the node is our TCON, the first port is used for
|
|
- * panel or bridges, and will not be part of the
|
|
- * component framework.
|
|
- */
|
|
- if (sun4i_drv_node_is_tcon(node)) {
|
|
- struct of_endpoint endpoint;
|
|
-
|
|
- if (of_graph_parse_endpoint(ep, &endpoint)) {
|
|
- DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
|
|
- of_node_put(remote);
|
|
- continue;
|
|
- }
|
|
-
|
|
- if (!endpoint.id) {
|
|
- DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
|
|
- of_node_put(remote);
|
|
- continue;
|
|
- }
|
|
- }
|
|
-
|
|
- kfifo_put(&list->fifo, remote);
|
|
- }
|
|
+ /* each node has at least one output */
|
|
+ sun4i_drv_traverse_endpoints(list, node, 1);
|
|
|
|
return count;
|
|
}
|
|
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
index 20193d6f33ba..e6c62c079146 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
@@ -26,6 +26,7 @@
|
|
#include "sun4i_frontend.h"
|
|
#include "sun4i_framebuffer.h"
|
|
#include "sun4i_tcon.h"
|
|
+#include "sun8i_tcon_top.h"
|
|
|
|
DEFINE_DRM_GEM_CMA_FOPS(sun4i_drv_fops);
|
|
|
|
@@ -197,6 +198,11 @@ static bool sun4i_drv_node_is_tcon(struct device_node *node)
|
|
return !!of_match_node(sun4i_tcon_of_table, node);
|
|
}
|
|
|
|
+static bool sun4i_drv_node_is_tcon_top(struct device_node *node)
|
|
+{
|
|
+ return !!of_match_node(sun8i_tcon_top_of_table, node);
|
|
+}
|
|
+
|
|
static int compare_of(struct device *dev, void *data)
|
|
{
|
|
DRM_DEBUG_DRIVER("Comparing of node %pOF with %pOF\n",
|
|
@@ -258,6 +264,18 @@ static void sun4i_drv_traverse_endpoints(struct endpoint_list *list,
|
|
if (sun4i_drv_node_is_tcon(node)) {
|
|
struct of_endpoint endpoint;
|
|
|
|
+ /*
|
|
+ * TCON TOP is always probed before TCON. However, TCON
|
|
+ * points back to TCON TOP when it is source for HDMI.
|
|
+ * We have to skip it here to prevent infinite looping
|
|
+ * between TCON TOP and TCON.
|
|
+ */
|
|
+ if (sun4i_drv_node_is_tcon_top(remote)) {
|
|
+ DRM_DEBUG_DRIVER("TCON output endpoint is TCON TOP... skipping\n");
|
|
+ of_node_put(remote);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
if (of_graph_parse_endpoint(ep, &endpoint)) {
|
|
DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
|
|
of_node_put(remote);
|
|
@@ -318,6 +336,12 @@ static int sun4i_drv_add_endpoints(struct device *dev,
|
|
/* each node has at least one output */
|
|
sun4i_drv_traverse_endpoints(list, node, 1);
|
|
|
|
+ /* TCON TOP has second and third output */
|
|
+ if (sun4i_drv_node_is_tcon_top(node)) {
|
|
+ sun4i_drv_traverse_endpoints(list, node, 3);
|
|
+ sun4i_drv_traverse_endpoints(list, node, 5);
|
|
+ }
|
|
+
|
|
return count;
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
index e6c62c079146..6ddf4eaccb40 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
|
|
@@ -198,6 +198,22 @@ static bool sun4i_drv_node_is_tcon(struct device_node *node)
|
|
return !!of_match_node(sun4i_tcon_of_table, node);
|
|
}
|
|
|
|
+static bool sun4i_drv_node_is_tcon_with_ch0(struct device_node *node)
|
|
+{
|
|
+ const struct of_device_id *match;
|
|
+
|
|
+ match = of_match_node(sun4i_tcon_of_table, node);
|
|
+ if (match) {
|
|
+ struct sun4i_tcon_quirks *quirks;
|
|
+
|
|
+ quirks = (struct sun4i_tcon_quirks *)match->data;
|
|
+
|
|
+ return quirks->has_channel_0;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
static bool sun4i_drv_node_is_tcon_top(struct device_node *node)
|
|
{
|
|
return !!of_match_node(sun8i_tcon_top_of_table, node);
|
|
@@ -256,14 +272,7 @@ static void sun4i_drv_traverse_endpoints(struct endpoint_list *list,
|
|
continue;
|
|
}
|
|
|
|
- /*
|
|
- * If the node is our TCON, the first port is used for
|
|
- * panel or bridges, and will not be part of the
|
|
- * component framework.
|
|
- */
|
|
if (sun4i_drv_node_is_tcon(node)) {
|
|
- struct of_endpoint endpoint;
|
|
-
|
|
/*
|
|
* TCON TOP is always probed before TCON. However, TCON
|
|
* points back to TCON TOP when it is source for HDMI.
|
|
@@ -276,16 +285,25 @@ static void sun4i_drv_traverse_endpoints(struct endpoint_list *list,
|
|
continue;
|
|
}
|
|
|
|
- if (of_graph_parse_endpoint(ep, &endpoint)) {
|
|
- DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
|
|
- of_node_put(remote);
|
|
- continue;
|
|
- }
|
|
-
|
|
- if (!endpoint.id) {
|
|
- DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
|
|
- of_node_put(remote);
|
|
- continue;
|
|
+ /*
|
|
+ * If the node is our TCON with channel 0, the first
|
|
+ * port is used for panel or bridges, and will not be
|
|
+ * part of the component framework.
|
|
+ */
|
|
+ if (sun4i_drv_node_is_tcon_with_ch0(node)) {
|
|
+ struct of_endpoint endpoint;
|
|
+
|
|
+ if (of_graph_parse_endpoint(ep, &endpoint)) {
|
|
+ DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
|
|
+ of_node_put(remote);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!endpoint.id) {
|
|
+ DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
|
|
+ of_node_put(remote);
|
|
+ continue;
|
|
+ }
|
|
}
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
|
|
index 08747fc3ee71..264bcc43da11 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
|
|
@@ -791,12 +791,14 @@ static int sun4i_tcon_init_regmap(struct device *dev,
|
|
*/
|
|
static struct sunxi_engine *
|
|
sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv,
|
|
- struct device_node *node)
|
|
+ struct device_node *node,
|
|
+ u32 port_id)
|
|
{
|
|
struct device_node *port, *ep, *remote;
|
|
struct sunxi_engine *engine = ERR_PTR(-EINVAL);
|
|
+ u32 reg = 0;
|
|
|
|
- port = of_graph_get_port_by_id(node, 0);
|
|
+ port = of_graph_get_port_by_id(node, port_id);
|
|
if (!port)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
@@ -826,8 +828,20 @@ sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv,
|
|
if (remote == engine->node)
|
|
goto out_put_remote;
|
|
|
|
+ /*
|
|
+ * According to device tree binding input ports have even id
|
|
+ * number and output ports have odd id. Since component with
|
|
+ * more than one input and one output (TCON TOP) exits, correct
|
|
+ * remote input id has to be calculated by subtracting 1 from
|
|
+ * remote output id. If this for some reason can't be done, 0
|
|
+ * is used as input port id.
|
|
+ */
|
|
+ port = of_graph_get_remote_port(ep);
|
|
+ if (!of_property_read_u32(port, "reg", ®) && reg > 0)
|
|
+ reg -= 1;
|
|
+
|
|
/* keep looking through upstream ports */
|
|
- engine = sun4i_tcon_find_engine_traverse(drv, remote);
|
|
+ engine = sun4i_tcon_find_engine_traverse(drv, remote, reg);
|
|
|
|
out_put_remote:
|
|
of_node_put(remote);
|
|
@@ -950,7 +964,7 @@ static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv,
|
|
|
|
/* Fallback to old method by traversing input endpoints */
|
|
of_node_put(port);
|
|
- return sun4i_tcon_find_engine_traverse(drv, node);
|
|
+ return sun4i_tcon_find_engine_traverse(drv, node, 0);
|
|
}
|
|
|
|
static int sun4i_tcon_bind(struct device *dev, struct device *master,
|
|
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
|
|
index 264bcc43da11..761687ebacba 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
|
|
@@ -1106,23 +1106,25 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
|
|
goto err_free_dotclock;
|
|
}
|
|
|
|
- /*
|
|
- * If we have an LVDS panel connected to the TCON, we should
|
|
- * just probe the LVDS connector. Otherwise, just probe RGB as
|
|
- * we used to.
|
|
- */
|
|
- remote = of_graph_get_remote_node(dev->of_node, 1, 0);
|
|
- if (of_device_is_compatible(remote, "panel-lvds"))
|
|
- if (can_lvds)
|
|
- ret = sun4i_lvds_init(drm, tcon);
|
|
+ if (tcon->quirks->has_channel_0) {
|
|
+ /*
|
|
+ * If we have an LVDS panel connected to the TCON, we should
|
|
+ * just probe the LVDS connector. Otherwise, just probe RGB as
|
|
+ * we used to.
|
|
+ */
|
|
+ remote = of_graph_get_remote_node(dev->of_node, 1, 0);
|
|
+ if (of_device_is_compatible(remote, "panel-lvds"))
|
|
+ if (can_lvds)
|
|
+ ret = sun4i_lvds_init(drm, tcon);
|
|
+ else
|
|
+ ret = -EINVAL;
|
|
else
|
|
- ret = -EINVAL;
|
|
- else
|
|
- ret = sun4i_rgb_init(drm, tcon);
|
|
- of_node_put(remote);
|
|
+ ret = sun4i_rgb_init(drm, tcon);
|
|
+ of_node_put(remote);
|
|
|
|
- if (ret < 0)
|
|
- goto err_free_dotclock;
|
|
+ if (ret < 0)
|
|
+ goto err_free_dotclock;
|
|
+ }
|
|
|
|
if (tcon->quirks->needs_de_be_mux) {
|
|
/*
|
|
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
|
|
index 761687ebacba..a41c7bb0d557 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
|
|
@@ -1178,13 +1178,19 @@ static const struct component_ops sun4i_tcon_ops = {
|
|
static int sun4i_tcon_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *node = pdev->dev.of_node;
|
|
+ const struct sun4i_tcon_quirks *quirks;
|
|
struct drm_bridge *bridge;
|
|
struct drm_panel *panel;
|
|
int ret;
|
|
|
|
- ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge);
|
|
- if (ret == -EPROBE_DEFER)
|
|
- return ret;
|
|
+ quirks = of_device_get_match_data(&pdev->dev);
|
|
+
|
|
+ /* panels and bridges are present only on TCONs with channel 0 */
|
|
+ if (quirks->has_channel_0) {
|
|
+ ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge);
|
|
+ if (ret == -EPROBE_DEFER)
|
|
+ return ret;
|
|
+ }
|
|
|
|
return component_add(&pdev->dev, &sun4i_tcon_ops);
|
|
}
|
|
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
|
|
index fe31b1510717..84fe38dbb900 100644
|
|
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
|
|
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
|
|
@@ -366,6 +366,8 @@ Required properties:
|
|
* allwinner,sun8i-a83t-de2-mixer-0
|
|
* allwinner,sun8i-a83t-de2-mixer-1
|
|
* allwinner,sun8i-h3-de2-mixer-0
|
|
+ * allwinner,sun8i-r40-de2-mixer-0
|
|
+ * allwinner,sun8i-r40-de2-mixer-1
|
|
* allwinner,sun8i-v3s-de2-mixer
|
|
- reg: base address and size of the memory-mapped region.
|
|
- clocks: phandles to the clocks feeding the mixer
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c
|
|
index 126899d6f0d3..ee8febb25903 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_mixer.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c
|
|
@@ -500,6 +500,22 @@ static const struct sun8i_mixer_cfg sun8i_h3_mixer0_cfg = {
|
|
.vi_num = 1,
|
|
};
|
|
|
|
+static const struct sun8i_mixer_cfg sun8i_r40_mixer0_cfg = {
|
|
+ .ccsc = 0,
|
|
+ .mod_rate = 297000000,
|
|
+ .scaler_mask = 0xf,
|
|
+ .ui_num = 3,
|
|
+ .vi_num = 1,
|
|
+};
|
|
+
|
|
+static const struct sun8i_mixer_cfg sun8i_r40_mixer1_cfg = {
|
|
+ .ccsc = 1,
|
|
+ .mod_rate = 297000000,
|
|
+ .scaler_mask = 0x3,
|
|
+ .ui_num = 1,
|
|
+ .vi_num = 1,
|
|
+};
|
|
+
|
|
static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = {
|
|
.vi_num = 2,
|
|
.ui_num = 1,
|
|
@@ -521,6 +537,14 @@ static const struct of_device_id sun8i_mixer_of_table[] = {
|
|
.compatible = "allwinner,sun8i-h3-de2-mixer-0",
|
|
.data = &sun8i_h3_mixer0_cfg,
|
|
},
|
|
+ {
|
|
+ .compatible = "allwinner,sun8i-r40-de2-mixer-0",
|
|
+ .data = &sun8i_r40_mixer0_cfg,
|
|
+ },
|
|
+ {
|
|
+ .compatible = "allwinner,sun8i-r40-de2-mixer-1",
|
|
+ .data = &sun8i_r40_mixer1_cfg,
|
|
+ },
|
|
{
|
|
.compatible = "allwinner,sun8i-v3s-de2-mixer",
|
|
.data = &sun8i_v3s_mixer_cfg,
|
|
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
|
|
index 84fe38dbb900..dc83f21ef188 100644
|
|
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
|
|
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
|
|
@@ -101,6 +101,7 @@ DWC HDMI PHY
|
|
|
|
Required properties:
|
|
- compatible: value must be one of:
|
|
+ * allwinner,sun50i-a64-hdmi-phy
|
|
* allwinner,sun8i-a83t-hdmi-phy
|
|
* allwinner,sun8i-h3-hdmi-phy
|
|
- reg: base address and size of memory-mapped region
|
|
@@ -111,8 +112,9 @@ Required properties:
|
|
- resets: phandle to the reset controller driving the PHY
|
|
- reset-names: must be "phy"
|
|
|
|
-H3 HDMI PHY requires additional clock:
|
|
+H3 and A64 HDMI PHY require additional clocks:
|
|
- pll-0: parent of phy clock
|
|
+ - pll-1: second possible phy clock parent (A64 only)
|
|
|
|
TV Encoder
|
|
----------
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
index 5a52fc489a9d..966688f04741 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
@@ -477,13 +477,15 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
|
|
dev_err(dev, "Couldn't create the PHY clock\n");
|
|
goto err_put_clk_pll0;
|
|
}
|
|
+
|
|
+ clk_prepare_enable(phy->clk_phy);
|
|
}
|
|
|
|
phy->rst_phy = of_reset_control_get_shared(node, "phy");
|
|
if (IS_ERR(phy->rst_phy)) {
|
|
dev_err(dev, "Could not get phy reset control\n");
|
|
ret = PTR_ERR(phy->rst_phy);
|
|
- goto err_put_clk_pll0;
|
|
+ goto err_disable_clk_phy;
|
|
}
|
|
|
|
ret = reset_control_deassert(phy->rst_phy);
|
|
@@ -514,6 +516,8 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
|
|
reset_control_assert(phy->rst_phy);
|
|
err_put_rst_phy:
|
|
reset_control_put(phy->rst_phy);
|
|
+err_disable_clk_phy:
|
|
+ clk_disable_unprepare(phy->clk_phy);
|
|
err_put_clk_pll0:
|
|
if (phy->variant->has_phy_clk)
|
|
clk_put(phy->clk_pll0);
|
|
@@ -531,6 +535,7 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi)
|
|
|
|
clk_disable_unprepare(phy->clk_mod);
|
|
clk_disable_unprepare(phy->clk_bus);
|
|
+ clk_disable_unprepare(phy->clk_phy);
|
|
|
|
reset_control_assert(phy->rst_phy);
|
|
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
|
|
index 79154f0f674a..3ba71aff92fc 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
|
|
@@ -98,7 +98,7 @@
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN BIT(29)
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN BIT(28)
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 BIT(27)
|
|
-#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL BIT(26)
|
|
+#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK BIT(26)
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_PLLEN BIT(25)
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(x) ((x) << 22)
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(x) ((x) << 20)
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
index 966688f04741..e56b9e5b1c42 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
@@ -183,7 +183,13 @@ static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
|
|
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
|
|
SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0);
|
|
|
|
- regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, pll_cfg1_init);
|
|
+ /*
|
|
+ * NOTE: We have to be careful not to overwrite PHY parent
|
|
+ * clock selection bit and clock divider.
|
|
+ */
|
|
+ regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
|
|
+ (u32)~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
|
|
+ pll_cfg1_init);
|
|
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
|
|
(u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
|
|
pll_cfg2_init);
|
|
@@ -352,6 +358,10 @@ static void sun8i_hdmi_phy_init_h3(struct sun8i_hdmi_phy *phy)
|
|
SUN8I_HDMI_PHY_ANA_CFG3_SCLEN |
|
|
SUN8I_HDMI_PHY_ANA_CFG3_SDAEN);
|
|
|
|
+ /* reset PHY PLL clock parent */
|
|
+ regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
|
|
+ SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK, 0);
|
|
+
|
|
/* set HW control of CEC pins */
|
|
regmap_write(phy->regs, SUN8I_HDMI_PHY_CEC_REG, 0);
|
|
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
|
|
index 3ba71aff92fc..46a3aa6a53a9 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
|
|
@@ -147,6 +147,7 @@ struct sun8i_hdmi_phy;
|
|
|
|
struct sun8i_hdmi_phy_variant {
|
|
bool has_phy_clk;
|
|
+ bool has_second_pll;
|
|
void (*phy_init)(struct sun8i_hdmi_phy *phy);
|
|
void (*phy_disable)(struct dw_hdmi *hdmi,
|
|
struct sun8i_hdmi_phy *phy);
|
|
@@ -160,6 +161,7 @@ struct sun8i_hdmi_phy {
|
|
struct clk *clk_mod;
|
|
struct clk *clk_phy;
|
|
struct clk *clk_pll0;
|
|
+ struct clk *clk_pll1;
|
|
unsigned int rcal;
|
|
struct regmap *regs;
|
|
struct reset_control *rst_phy;
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
index e56b9e5b1c42..f0877b3f67e7 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
@@ -482,10 +482,19 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
|
|
goto err_put_clk_mod;
|
|
}
|
|
|
|
+ if (phy->variant->has_second_pll) {
|
|
+ phy->clk_pll1 = of_clk_get_by_name(node, "pll-1");
|
|
+ if (IS_ERR(phy->clk_pll1)) {
|
|
+ dev_err(dev, "Could not get pll-1 clock\n");
|
|
+ ret = PTR_ERR(phy->clk_pll1);
|
|
+ goto err_put_clk_pll0;
|
|
+ }
|
|
+ }
|
|
+
|
|
ret = sun8i_phy_clk_create(phy, dev);
|
|
if (ret) {
|
|
dev_err(dev, "Couldn't create the PHY clock\n");
|
|
- goto err_put_clk_pll0;
|
|
+ goto err_put_clk_pll1;
|
|
}
|
|
|
|
clk_prepare_enable(phy->clk_phy);
|
|
@@ -528,9 +537,10 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
|
|
reset_control_put(phy->rst_phy);
|
|
err_disable_clk_phy:
|
|
clk_disable_unprepare(phy->clk_phy);
|
|
+err_put_clk_pll1:
|
|
+ clk_put(phy->clk_pll1);
|
|
err_put_clk_pll0:
|
|
- if (phy->variant->has_phy_clk)
|
|
- clk_put(phy->clk_pll0);
|
|
+ clk_put(phy->clk_pll0);
|
|
err_put_clk_mod:
|
|
clk_put(phy->clk_mod);
|
|
err_put_clk_bus:
|
|
@@ -551,8 +561,8 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi)
|
|
|
|
reset_control_put(phy->rst_phy);
|
|
|
|
- if (phy->variant->has_phy_clk)
|
|
- clk_put(phy->clk_pll0);
|
|
+ clk_put(phy->clk_pll0);
|
|
+ clk_put(phy->clk_pll1);
|
|
clk_put(phy->clk_mod);
|
|
clk_put(phy->clk_bus);
|
|
}
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
|
|
index 46a3aa6a53a9..aadbe0a10b0c 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
|
|
@@ -99,6 +99,7 @@
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN BIT(28)
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 BIT(27)
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK BIT(26)
|
|
+#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT 26
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_PLLEN BIT(25)
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(x) ((x) << 22)
|
|
#define SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(x) ((x) << 20)
|
|
@@ -190,6 +191,7 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi);
|
|
void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy);
|
|
const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void);
|
|
|
|
-int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev);
|
|
+int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
|
|
+ bool second_parent);
|
|
|
|
#endif /* _SUN8I_DW_HDMI_H_ */
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
index f0877b3f67e7..aea46b08f127 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
@@ -491,7 +491,8 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
|
|
}
|
|
}
|
|
|
|
- ret = sun8i_phy_clk_create(phy, dev);
|
|
+ ret = sun8i_phy_clk_create(phy, dev,
|
|
+ phy->variant->has_second_pll);
|
|
if (ret) {
|
|
dev_err(dev, "Couldn't create the PHY clock\n");
|
|
goto err_put_clk_pll1;
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
|
|
index faea449812f8..a4d31fe3abff 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
|
|
@@ -22,35 +22,45 @@ static int sun8i_phy_clk_determine_rate(struct clk_hw *hw,
|
|
{
|
|
unsigned long rate = req->rate;
|
|
unsigned long best_rate = 0;
|
|
+ struct clk_hw *best_parent = NULL;
|
|
struct clk_hw *parent;
|
|
int best_div = 1;
|
|
- int i;
|
|
+ int i, p;
|
|
|
|
- parent = clk_hw_get_parent(hw);
|
|
-
|
|
- for (i = 1; i <= 16; i++) {
|
|
- unsigned long ideal = rate * i;
|
|
- unsigned long rounded;
|
|
-
|
|
- rounded = clk_hw_round_rate(parent, ideal);
|
|
+ for (p = 0; p < clk_hw_get_num_parents(hw); p++) {
|
|
+ parent = clk_hw_get_parent_by_index(hw, p);
|
|
+ if (!parent)
|
|
+ continue;
|
|
|
|
- if (rounded == ideal) {
|
|
- best_rate = rounded;
|
|
- best_div = i;
|
|
- break;
|
|
+ for (i = 1; i <= 16; i++) {
|
|
+ unsigned long ideal = rate * i;
|
|
+ unsigned long rounded;
|
|
+
|
|
+ rounded = clk_hw_round_rate(parent, ideal);
|
|
+
|
|
+ if (rounded == ideal) {
|
|
+ best_rate = rounded;
|
|
+ best_div = i;
|
|
+ best_parent = parent;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!best_rate ||
|
|
+ abs(rate - rounded / i) <
|
|
+ abs(rate - best_rate / best_div)) {
|
|
+ best_rate = rounded;
|
|
+ best_div = i;
|
|
+ best_parent = parent;
|
|
+ }
|
|
}
|
|
|
|
- if (!best_rate ||
|
|
- abs(rate - rounded / i) <
|
|
- abs(rate - best_rate / best_div)) {
|
|
- best_rate = rounded;
|
|
- best_div = i;
|
|
- }
|
|
+ if (best_rate / best_div == rate)
|
|
+ break;
|
|
}
|
|
|
|
req->rate = best_rate / best_div;
|
|
req->best_parent_rate = best_rate;
|
|
- req->best_parent_hw = parent;
|
|
+ req->best_parent_hw = best_parent;
|
|
|
|
return 0;
|
|
}
|
|
@@ -95,22 +105,58 @@ static int sun8i_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
return 0;
|
|
}
|
|
|
|
+static u8 sun8i_phy_clk_get_parent(struct clk_hw *hw)
|
|
+{
|
|
+ struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
|
|
+ u32 reg;
|
|
+
|
|
+ regmap_read(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, ®);
|
|
+ reg = (reg & SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK) >>
|
|
+ SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT;
|
|
+
|
|
+ return reg;
|
|
+}
|
|
+
|
|
+static int sun8i_phy_clk_set_parent(struct clk_hw *hw, u8 index)
|
|
+{
|
|
+ struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
|
|
+
|
|
+ if (index > 1)
|
|
+ return -EINVAL;
|
|
+
|
|
+ regmap_update_bits(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
|
|
+ SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
|
|
+ index << SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static const struct clk_ops sun8i_phy_clk_ops = {
|
|
.determine_rate = sun8i_phy_clk_determine_rate,
|
|
.recalc_rate = sun8i_phy_clk_recalc_rate,
|
|
.set_rate = sun8i_phy_clk_set_rate,
|
|
+
|
|
+ .get_parent = sun8i_phy_clk_get_parent,
|
|
+ .set_parent = sun8i_phy_clk_set_parent,
|
|
};
|
|
|
|
-int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev)
|
|
+int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
|
|
+ bool second_parent)
|
|
{
|
|
struct clk_init_data init;
|
|
struct sun8i_phy_clk *priv;
|
|
- const char *parents[1];
|
|
+ const char *parents[2];
|
|
|
|
parents[0] = __clk_get_name(phy->clk_pll0);
|
|
if (!parents[0])
|
|
return -ENODEV;
|
|
|
|
+ if (second_parent) {
|
|
+ parents[1] = __clk_get_name(phy->clk_pll1);
|
|
+ if (!parents[1])
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
@@ -118,7 +164,7 @@ int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev)
|
|
init.name = "hdmi-phy-clk";
|
|
init.ops = &sun8i_phy_clk_ops;
|
|
init.parent_names = parents;
|
|
- init.num_parents = 1;
|
|
+ init.num_parents = second_parent ? 2 : 1;
|
|
init.flags = CLK_SET_RATE_PARENT;
|
|
|
|
priv->phy = phy;
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
index aea46b08f127..82502b351aec 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
|
|
@@ -396,6 +396,14 @@ static struct regmap_config sun8i_hdmi_phy_regmap_config = {
|
|
.name = "phy"
|
|
};
|
|
|
|
+static const struct sun8i_hdmi_phy_variant sun50i_a64_hdmi_phy = {
|
|
+ .has_phy_clk = true,
|
|
+ .has_second_pll = true,
|
|
+ .phy_init = &sun8i_hdmi_phy_init_h3,
|
|
+ .phy_disable = &sun8i_hdmi_phy_disable_h3,
|
|
+ .phy_config = &sun8i_hdmi_phy_config_h3,
|
|
+};
|
|
+
|
|
static const struct sun8i_hdmi_phy_variant sun8i_a83t_hdmi_phy = {
|
|
.phy_init = &sun8i_hdmi_phy_init_a83t,
|
|
.phy_disable = &sun8i_hdmi_phy_disable_a83t,
|
|
@@ -410,6 +418,10 @@ static const struct sun8i_hdmi_phy_variant sun8i_h3_hdmi_phy = {
|
|
};
|
|
|
|
static const struct of_device_id sun8i_hdmi_phy_of_table[] = {
|
|
+ {
|
|
+ .compatible = "allwinner,sun50i-a64-hdmi-phy",
|
|
+ .data = &sun50i_a64_hdmi_phy,
|
|
+ },
|
|
{
|
|
.compatible = "allwinner,sun8i-a83t-hdmi-phy",
|
|
.data = &sun8i_a83t_hdmi_phy,
|
|
diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c
|
|
index 1fe122461298..50e738857258 100644
|
|
--- a/drivers/gpu/drm/drm_of.c
|
|
+++ b/drivers/gpu/drm/drm_of.c
|
|
@@ -22,8 +22,8 @@ static void drm_release_of(struct device *dev, void *data)
|
|
* Given a port OF node, return the possible mask of the corresponding
|
|
* CRTC within a device's list of CRTCs. Returns zero if not found.
|
|
*/
|
|
-static uint32_t drm_crtc_port_mask(struct drm_device *dev,
|
|
- struct device_node *port)
|
|
+uint32_t drm_crtc_port_mask(struct drm_device *dev,
|
|
+ struct device_node *port)
|
|
{
|
|
unsigned int index = 0;
|
|
struct drm_crtc *tmp;
|
|
@@ -37,6 +37,7 @@ static uint32_t drm_crtc_port_mask(struct drm_device *dev,
|
|
|
|
return 0;
|
|
}
|
|
+EXPORT_SYMBOL(drm_crtc_port_mask);
|
|
|
|
/**
|
|
* drm_of_find_possible_crtcs - find the possible CRTCs for an encoder port
|
|
diff --git a/include/drm/drm_of.h b/include/drm/drm_of.h
|
|
index b93c239afb60..a61fd77e46ba 100644
|
|
--- a/include/drm/drm_of.h
|
|
+++ b/include/drm/drm_of.h
|
|
@@ -17,6 +17,8 @@ struct drm_bridge;
|
|
struct device_node;
|
|
|
|
#ifdef CONFIG_OF
|
|
+uint32_t drm_crtc_port_mask(struct drm_device *dev,
|
|
+ struct device_node *port);
|
|
uint32_t drm_of_find_possible_crtcs(struct drm_device *dev,
|
|
struct device_node *port);
|
|
void drm_of_component_match_add(struct device *master,
|
|
@@ -34,6 +36,12 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
|
|
struct drm_panel **panel,
|
|
struct drm_bridge **bridge);
|
|
#else
|
|
+static inline uint32_t drm_crtc_port_mask(struct drm_device *dev,
|
|
+ struct device_node *port)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static inline uint32_t drm_of_find_possible_crtcs(struct drm_device *dev,
|
|
struct device_node *port)
|
|
{
|
|
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
|
|
index 9f40a44b456b..d443886e055b 100644
|
|
--- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
|
|
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
|
|
@@ -12,6 +12,7 @@
|
|
#include <drm/drm_crtc_helper.h>
|
|
|
|
#include "sun8i_dw_hdmi.h"
|
|
+#include "sun8i_tcon_top.h"
|
|
|
|
static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder,
|
|
struct drm_display_mode *mode,
|
|
@@ -41,6 +42,48 @@ sun8i_dw_hdmi_mode_valid(struct drm_connector *connector,
|
|
return MODE_OK;
|
|
}
|
|
|
|
+static bool sun8i_dw_hdmi_node_is_tcon_top(struct device_node *node)
|
|
+{
|
|
+ return !!of_match_node(sun8i_tcon_top_of_table, node);
|
|
+}
|
|
+
|
|
+static u32 sun8i_dw_hdmi_find_possible_crtcs(struct drm_device *drm,
|
|
+ struct device_node *node)
|
|
+{
|
|
+ struct device_node *port, *ep, *remote, *remote_port;
|
|
+ u32 crtcs = 0;
|
|
+
|
|
+ port = of_graph_get_port_by_id(node, 0);
|
|
+ if (!port)
|
|
+ return 0;
|
|
+
|
|
+ ep = of_get_next_available_child(port, NULL);
|
|
+ if (!ep)
|
|
+ return 0;
|
|
+
|
|
+ remote = of_graph_get_remote_port_parent(ep);
|
|
+ if (!remote)
|
|
+ return 0;
|
|
+
|
|
+ if (sun8i_dw_hdmi_node_is_tcon_top(remote)) {
|
|
+ port = of_graph_get_port_by_id(remote, 4);
|
|
+ if (!port)
|
|
+ return 0;
|
|
+
|
|
+ for_each_child_of_node(port, ep) {
|
|
+ remote_port = of_graph_get_remote_port(ep);
|
|
+ if (remote_port) {
|
|
+ crtcs |= drm_crtc_port_mask(drm, remote_port);
|
|
+ of_node_put(remote_port);
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ crtcs = drm_of_find_possible_crtcs(drm, node);
|
|
+ }
|
|
+
|
|
+ return crtcs;
|
|
+}
|
|
+
|
|
static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
|
|
void *data)
|
|
{
|
|
@@ -63,7 +106,8 @@ static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
|
|
hdmi->dev = &pdev->dev;
|
|
encoder = &hdmi->encoder;
|
|
|
|
- encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
|
|
+ encoder->possible_crtcs =
|
|
+ sun8i_dw_hdmi_find_possible_crtcs(drm, dev->of_node);
|
|
/*
|
|
* If we failed to find the CRTC(s) which this encoder is
|
|
* supposed to be connected to, it's because the CRTC has
|
|
diff --git a/arch/arm/boot/dts/sun8i-r40.dtsi b/arch/arm/boot/dts/sun8i-r40.dtsi
|
|
index 173dcc1652d2..a2a75fb04caf 100644
|
|
--- a/arch/arm/boot/dts/sun8i-r40.dtsi
|
|
+++ b/arch/arm/boot/dts/sun8i-r40.dtsi
|
|
@@ -42,8 +42,11 @@
|
|
*/
|
|
|
|
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
|
+#include <dt-bindings/clock/sun8i-de2.h>
|
|
#include <dt-bindings/clock/sun8i-r40-ccu.h>
|
|
+#include <dt-bindings/clock/sun8i-tcon-top.h>
|
|
#include <dt-bindings/reset/sun8i-r40-ccu.h>
|
|
+#include <dt-bindings/reset/sun8i-de2.h>
|
|
|
|
/ {
|
|
#address-cells = <1>;
|
|
@@ -99,12 +102,76 @@
|
|
};
|
|
};
|
|
|
|
+ de: display-engine {
|
|
+ compatible = "allwinner,sun8i-r40-display-engine",
|
|
+ "allwinner,sun8i-h3-display-engine";
|
|
+ allwinner,pipelines = <&mixer0>, <&mixer1>;
|
|
+ status = "disabled";
|
|
+ };
|
|
+
|
|
soc {
|
|
compatible = "simple-bus";
|
|
#address-cells = <1>;
|
|
#size-cells = <1>;
|
|
ranges;
|
|
|
|
+ display_clocks: clock@1000000 {
|
|
+ compatible = "allwinner,sun8i-r40-de2-clk",
|
|
+ "allwinner,sun8i-h3-de2-clk";
|
|
+ reg = <0x01000000 0x100000>;
|
|
+ clocks = <&ccu CLK_DE>,
|
|
+ <&ccu CLK_BUS_DE>;
|
|
+ clock-names = "mod",
|
|
+ "bus";
|
|
+ resets = <&ccu RST_BUS_DE>;
|
|
+ #clock-cells = <1>;
|
|
+ #reset-cells = <1>;
|
|
+ };
|
|
+
|
|
+ mixer0: mixer@1100000 {
|
|
+ compatible = "allwinner,sun8i-r40-de2-mixer-0";
|
|
+ reg = <0x01100000 0x100000>;
|
|
+ clocks = <&display_clocks CLK_BUS_MIXER0>,
|
|
+ <&display_clocks CLK_MIXER0>;
|
|
+ clock-names = "bus",
|
|
+ "mod";
|
|
+ resets = <&display_clocks RST_MIXER0>;
|
|
+
|
|
+ ports {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ mixer0_out: port@1 {
|
|
+ reg = <1>;
|
|
+ mixer0_out_tcon_top: endpoint {
|
|
+ remote-endpoint = <&tcon_top_mixer0_in_mixer0>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ mixer1: mixer@1200000 {
|
|
+ compatible = "allwinner,sun8i-r40-de2-mixer-1";
|
|
+ reg = <0x01200000 0x100000>;
|
|
+ clocks = <&display_clocks CLK_BUS_MIXER1>,
|
|
+ <&display_clocks CLK_MIXER1>;
|
|
+ clock-names = "bus",
|
|
+ "mod";
|
|
+ resets = <&display_clocks RST_WB>;
|
|
+
|
|
+ ports {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ mixer1_out: port@1 {
|
|
+ reg = <1>;
|
|
+ mixer1_out_tcon_top: endpoint {
|
|
+ remote-endpoint = <&tcon_top_mixer1_in_mixer1>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
nmi_intc: interrupt-controller@1c00030 {
|
|
compatible = "allwinner,sun7i-a20-sc-nmi";
|
|
interrupt-controller;
|
|
@@ -451,6 +518,163 @@
|
|
#size-cells = <0>;
|
|
};
|
|
|
|
+ tcon_top: tcon-top@1c70000 {
|
|
+ compatible = "allwinner,sun8i-r40-tcon-top";
|
|
+ reg = <0x01c70000 0x1000>;
|
|
+ clocks = <&ccu CLK_BUS_TCON_TOP>,
|
|
+ <&ccu CLK_TCON_TV0>,
|
|
+ <&ccu CLK_TVE0>,
|
|
+ <&ccu CLK_TCON_TV1>,
|
|
+ <&ccu CLK_TVE1>,
|
|
+ <&ccu CLK_DSI_DPHY>;
|
|
+ clock-names = "bus",
|
|
+ "tcon-tv0",
|
|
+ "tve0",
|
|
+ "tcon-tv1",
|
|
+ "tve1",
|
|
+ "dsi";
|
|
+ clock-output-names = "tcon-top-tv0",
|
|
+ "tcon-top-tv1",
|
|
+ "tcon-top-dsi";
|
|
+ resets = <&ccu RST_BUS_TCON_TOP>;
|
|
+ #clock-cells = <1>;
|
|
+
|
|
+ ports {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ tcon_top_mixer0_in: port@0 {
|
|
+ reg = <0>;
|
|
+
|
|
+ tcon_top_mixer0_in_mixer0: endpoint {
|
|
+ remote-endpoint = <&mixer0_out_tcon_top>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ tcon_top_mixer0_out: port@1 {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+ reg = <1>;
|
|
+
|
|
+ tcon_top_mixer0_out_tcon_lcd0: endpoint@0 {
|
|
+ reg = <0>;
|
|
+ };
|
|
+
|
|
+ tcon_top_mixer0_out_tcon_lcd1: endpoint@1 {
|
|
+ reg = <1>;
|
|
+ };
|
|
+
|
|
+ tcon_top_mixer0_out_tcon_tv0: endpoint@2 {
|
|
+ reg = <2>;
|
|
+ };
|
|
+
|
|
+ tcon_top_mixer0_out_tcon_tv1: endpoint@3 {
|
|
+ reg = <3>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ tcon_top_mixer1_in: port@2 {
|
|
+ reg = <2>;
|
|
+
|
|
+ tcon_top_mixer1_in_mixer1: endpoint {
|
|
+ remote-endpoint = <&mixer1_out_tcon_top>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ tcon_top_mixer1_out: port@3 {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+ reg = <3>;
|
|
+
|
|
+ tcon_top_mixer1_out_tcon_lcd0: endpoint@0 {
|
|
+ reg = <0>;
|
|
+ };
|
|
+
|
|
+ tcon_top_mixer1_out_tcon_lcd1: endpoint@1 {
|
|
+ reg = <1>;
|
|
+ };
|
|
+
|
|
+ tcon_top_mixer1_out_tcon_tv0: endpoint@2 {
|
|
+ reg = <2>;
|
|
+ };
|
|
+
|
|
+ tcon_top_mixer1_out_tcon_tv1: endpoint@3 {
|
|
+ reg = <3>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ tcon_top_hdmi_in: port@4 {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+ reg = <4>;
|
|
+
|
|
+ tcon_top_hdmi_in_tcon_tv0: endpoint@0 {
|
|
+ reg = <0>;
|
|
+ };
|
|
+
|
|
+ tcon_top_hdmi_in_tcon_tv1: endpoint@1 {
|
|
+ reg = <1>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ tcon_top_hdmi_out: port@5 {
|
|
+ reg = <5>;
|
|
+
|
|
+ tcon_top_hdmi_out_hdmi: endpoint {
|
|
+ remote-endpoint = <&hdmi_in_tcon_top>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ tcon_tv0: lcd-controller@1c73000 {
|
|
+ compatible = "allwinner,sun8i-r40-tcon-tv",
|
|
+ "allwinner,sun8i-a83t-tcon-tv";
|
|
+ reg = <0x01c73000 0x1000>;
|
|
+ interrupts = <GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ clocks = <&ccu CLK_BUS_TCON_TV0>, <&tcon_top 0>;
|
|
+ clock-names = "ahb", "tcon-ch1";
|
|
+ resets = <&ccu RST_BUS_TCON_TV0>;
|
|
+ reset-names = "lcd";
|
|
+
|
|
+ ports {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ tcon_tv0_in: port@0 {
|
|
+ reg = <0>;
|
|
+ };
|
|
+
|
|
+ tcon_tv0_out: port@1 {
|
|
+ reg = <1>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ tcon_tv1: lcd-controller@1c74000 {
|
|
+ compatible = "allwinner,sun8i-r40-tcon-tv",
|
|
+ "allwinner,sun8i-a83t-tcon-tv";
|
|
+ reg = <0x01c74000 0x1000>;
|
|
+ interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ clocks = <&ccu CLK_BUS_TCON_TV1>, <&tcon_top 1>;
|
|
+ clock-names = "ahb", "tcon-ch1";
|
|
+ resets = <&ccu RST_BUS_TCON_TV1>;
|
|
+ reset-names = "lcd";
|
|
+
|
|
+ ports {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ tcon_tv1_in: port@0 {
|
|
+ reg = <0>;
|
|
+ };
|
|
+
|
|
+ tcon_tv1_out: port@1 {
|
|
+ reg = <1>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
gic: interrupt-controller@1c81000 {
|
|
compatible = "arm,gic-400";
|
|
reg = <0x01c81000 0x1000>,
|
|
@@ -461,6 +685,51 @@
|
|
#interrupt-cells = <3>;
|
|
interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
|
|
};
|
|
+
|
|
+ hdmi: hdmi@1ee0000 {
|
|
+ compatible = "allwinner,sun8i-r40-dw-hdmi",
|
|
+ "allwinner,sun8i-a83t-dw-hdmi";
|
|
+ reg = <0x01ee0000 0x10000>;
|
|
+ reg-io-width = <1>;
|
|
+ interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ clocks = <&ccu CLK_BUS_HDMI0>, <&ccu CLK_HDMI_SLOW>,
|
|
+ <&ccu CLK_HDMI>;
|
|
+ clock-names = "iahb", "isfr", "tmds";
|
|
+ resets = <&ccu RST_BUS_HDMI1>;
|
|
+ reset-names = "ctrl";
|
|
+ phys = <&hdmi_phy>;
|
|
+ phy-names = "hdmi-phy";
|
|
+ status = "disabled";
|
|
+
|
|
+ ports {
|
|
+ #address-cells = <1>;
|
|
+ #size-cells = <0>;
|
|
+
|
|
+ hdmi_in: port@0 {
|
|
+ reg = <0>;
|
|
+
|
|
+ hdmi_in_tcon_top: endpoint {
|
|
+ remote-endpoint = <&tcon_top_hdmi_out_hdmi>;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ hdmi_out: port@1 {
|
|
+ reg = <1>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ hdmi_phy: hdmi-phy@1ef0000 {
|
|
+ compatible = "allwinner,sun8i-r40-hdmi-phy",
|
|
+ "allwinner,sun50i-a64-hdmi-phy";
|
|
+ reg = <0x01ef0000 0x10000>;
|
|
+ clocks = <&ccu CLK_BUS_HDMI1>, <&ccu CLK_HDMI_SLOW>,
|
|
+ <&ccu 7>, <&ccu 16>;
|
|
+ clock-names = "bus", "mod", "pll-0", "pll-1";
|
|
+ resets = <&ccu RST_BUS_HDMI0>;
|
|
+ reset-names = "phy";
|
|
+ #phy-cells = <0>;
|
|
+ };
|
|
};
|
|
|
|
timer {
|
|
diff --git a/arch/arm/boot/dts/sun8i-r40-bananapi-m2-ultra.dts b/arch/arm/boot/dts/sun8i-r40-bananapi-m2-ultra.dts
|
|
index 27d9ccd0ef2f..0ebc2f9a980e 100644
|
|
--- a/arch/arm/boot/dts/sun8i-r40-bananapi-m2-ultra.dts
|
|
+++ b/arch/arm/boot/dts/sun8i-r40-bananapi-m2-ultra.dts
|
|
@@ -58,6 +58,17 @@
|
|
stdout-path = "serial0:115200n8";
|
|
};
|
|
|
|
+ connector {
|
|
+ compatible = "hdmi-connector";
|
|
+ type = "a";
|
|
+
|
|
+ port {
|
|
+ hdmi_con_in: endpoint {
|
|
+ remote-endpoint = <&hdmi_out_con>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
leds {
|
|
compatible = "gpio-leds";
|
|
|
|
@@ -93,6 +104,10 @@
|
|
};
|
|
};
|
|
|
|
+&de {
|
|
+ status = "okay";
|
|
+};
|
|
+
|
|
&ehci1 {
|
|
status = "okay";
|
|
};
|
|
@@ -101,6 +116,16 @@
|
|
status = "okay";
|
|
};
|
|
|
|
+&hdmi {
|
|
+ status = "okay";
|
|
+};
|
|
+
|
|
+&hdmi_out {
|
|
+ hdmi_out_con: endpoint {
|
|
+ remote-endpoint = <&hdmi_con_in>;
|
|
+ };
|
|
+};
|
|
+
|
|
&i2c0 {
|
|
status = "okay";
|
|
|
|
@@ -195,6 +220,26 @@
|
|
status = "okay";
|
|
};
|
|
|
|
+&tcon_top_hdmi_in_tcon_tv0 {
|
|
+ remote-endpoint = <&tcon_tv0_out_tcon_top>;
|
|
+};
|
|
+
|
|
+&tcon_top_mixer0_out_tcon_tv0 {
|
|
+ remote-endpoint = <&tcon_tv0_in_tcon_top>;
|
|
+};
|
|
+
|
|
+&tcon_tv0_in {
|
|
+ tcon_tv0_in_tcon_top: endpoint {
|
|
+ remote-endpoint = <&tcon_top_mixer0_out_tcon_tv0>;
|
|
+ };
|
|
+};
|
|
+
|
|
+&tcon_tv0_out {
|
|
+ tcon_tv0_out_tcon_top: endpoint {
|
|
+ remote-endpoint = <&tcon_top_hdmi_in_tcon_tv0>;
|
|
+ };
|
|
+};
|
|
+
|
|
&uart0 {
|
|
pinctrl-names = "default";
|
|
pinctrl-0 = <&uart0_pb_pins>;
|