The proposed DRM driver works on a Orange PI 2 with a kernel 4.4.0 and the H3 patches found in Hans de Goede's GIT repository.
As there is no documentation about the HDMI of the H3, the associated encoder/connector driver has not been included in this patch series. For tests, it may be built as a out-of-tree driver from the tarball: http://moinejf.free.fr/opi2/h3-hdmi.tar.gz and the DT files: http://moinejf.free.fr/opi2/sun8i-h3.dtsi http://moinejf.free.fr/opi2/sun8i-h3-orangepi-plus.dts
Jean-Francois Moine (2): clk: sunxi: Add sun6i/8i video support drm: sunxi: Add a basic DRM driver for Allwinner DE2
Documentation/devicetree/bindings/clock/sunxi.txt | 2 + .../devicetree/bindings/display/sunxi.txt | 81 ++++ drivers/clk/sunxi/clk-sun6i-display.c | 106 +++++ drivers/clk/sunxi/clk-sun6i-pll3.c | 174 ++++++++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 20 + drivers/gpu/drm/sunxi/Makefile | 7 + drivers/gpu/drm/sunxi/de2_crtc.c | 425 +++++++++++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 43 ++ drivers/gpu/drm/sunxi/de2_de.c | 461 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 55 +++ drivers/gpu/drm/sunxi/de2_drv.c | 376 +++++++++++++++++ drivers/gpu/drm/sunxi/de2_plane.c | 114 +++++ 14 files changed, 1867 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi.txt create mode 100644 drivers/clk/sunxi/clk-sun6i-display.c create mode 100644 drivers/clk/sunxi/clk-sun6i-pll3.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_plane.c
Add the clock types which are used by the sun6i/8i families for video.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- Changes: - remarks from Chen-Yu Tsai - DT documentation added --- Documentation/devicetree/bindings/clock/sunxi.txt | 2 + drivers/clk/sunxi/clk-sun6i-display.c | 106 +++++++++++++ drivers/clk/sunxi/clk-sun6i-pll3.c | 174 ++++++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun6i-display.c create mode 100644 drivers/clk/sunxi/clk-sun6i-pll3.c
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index 8a47b77..c57dd0e 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -11,6 +11,7 @@ Required properties: "allwinner,sun6i-a31-pll1-clk" - for the main PLL clock on A31 "allwinner,sun8i-a23-pll1-clk" - for the main PLL clock on A23 "allwinner,sun9i-a80-pll4-clk" - for the peripheral PLLs on A80 + "allwinner,sun6i-pll3-clk" - for the video PLLs clock "allwinner,sun4i-a10-pll5-clk" - for the PLL5 clock "allwinner,sun4i-a10-pll6-clk" - for the PLL6 clock "allwinner,sun6i-a31-pll6-clk" - for the PLL6 clock on A31 @@ -70,6 +71,7 @@ Required properties: "allwinner,sun8i-a23-usb-clk" - for usb gates + resets on A23 "allwinner,sun9i-a80-usb-mod-clk" - for usb gates + resets on A80 "allwinner,sun9i-a80-usb-phy-clk" - for usb phy gates + resets on A80 + "allwinner,sun6i-display-clk" - for the display clocks
Required properties for all clocks: - reg : shall be the control register address for the clock. diff --git a/drivers/clk/sunxi/clk-sun6i-display.c b/drivers/clk/sunxi/clk-sun6i-display.c new file mode 100644 index 0000000..48356e3 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun6i-display.c @@ -0,0 +1,106 @@ +/* + * Copyright 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. + * + * 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(sun6i_display_lock); + +#define SUN6I_DISPLAY_GATE_BIT 31 +#define SUN6I_DISPLAY_SEL_SHIFT 24 +#define SUN6I_DISPLAY_SEL_MASK GENMASK(2, 0) +#define SUN6I_DISPLAY_MSHIFT 0 +#define SUN6I_DISPLAY_MWIDTH 4 + +static void __init sun6i_display_setup(struct device_node *node) +{ + const char *clk_name = node->name; + const char *parents[8]; + struct clk_mux *mux = NULL; + struct clk_divider *div; + struct clk_gate *gate; + struct resource res; + void __iomem *mmio; + struct clk *clk; + int n; + + of_property_read_string(node, "clock-output-names", &clk_name); + + mmio = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (IS_ERR(mmio)) { + 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) + goto free_io; + mux->reg = mmio; + mux->shift = SUN6I_DISPLAY_SEL_SHIFT; + mux->mask = SUN6I_DISPLAY_SEL_MASK; + mux->lock = &sun6i_display_lock; + } + + gate = kzalloc(sizeof(*gate), GFP_KERNEL); + if (!gate) + goto free_mux; + + gate->reg = mmio; + gate->bit_idx = SUN6I_DISPLAY_GATE_BIT; + gate->lock = &sun6i_display_lock; + + div = kzalloc(sizeof(*div), GFP_KERNEL); + if (!div) + goto free_gate; + + div->reg = mmio; + div->shift = SUN6I_DISPLAY_MSHIFT; + div->width = SUN6I_DISPLAY_MWIDTH; + div->lock = &sun6i_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); +free_mux: + kfree(mux); +free_io: + iounmap(mmio); + of_address_to_resource(node, 0, &res); + release_mem_region(res.start, resource_size(&res)); +} + +CLK_OF_DECLARE(sun6i_display, "allwinner,sun6i-display-clk", sun6i_display_setup); diff --git a/drivers/clk/sunxi/clk-sun6i-pll3.c b/drivers/clk/sunxi/clk-sun6i-pll3.c new file mode 100644 index 0000000..3c128a4 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun6i-pll3.c @@ -0,0 +1,174 @@ +/* + * Allwinner video PLL clocks + * + * Copyright 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. + * + * 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/iopoll.h> + +static DEFINE_SPINLOCK(sun6i_pll3_lock); + +struct clk_fact { + struct clk_hw hw; + void __iomem *reg; +}; +#define to_clk_fact(_hw) container_of(_hw, struct clk_fact, hw) + +#define SUN6I_PLL3_MSHIFT 0 +#define SUN6I_PLL3_MMASK GENMASK(3, 0) +#define SUN6I_PLL3_NSHIFT 8 +#define SUN6I_PLL3_NMASK GENMASK(7, 0) +#define SUN6I_PLL3_MODE_SEL BIT(24) +#define SUN6I_PLL3_FRAC_CLK BIT(25) +#define SUN6I_PLL3_LOCK_BIT 28 +#define SUN6I_PLL3_GATE_BIT 31 + +static u32 sun6i_pll3_get_fact(unsigned long rate, + unsigned long parent_rate, + unsigned long *n, unsigned long *m) +{ + if (rate == 297000000) + return SUN6I_PLL3_FRAC_CLK; + if (rate == 270000000) + return 0; + rational_best_approximation(rate, parent_rate, + SUN6I_PLL3_NMASK + 1, SUN6I_PLL3_MMASK + 1, + n, m); + return SUN6I_PLL3_MODE_SEL; +} + +static unsigned long sun6i_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 & SUN6I_PLL3_MODE_SEL) { + n = (reg >> SUN6I_PLL3_NSHIFT) & SUN6I_PLL3_NMASK; + m = (reg >> SUN6I_PLL3_MSHIFT) & SUN6I_PLL3_MMASK; + return parent_rate / (m + 1) * (n + 1); + } + if (reg & SUN6I_PLL3_FRAC_CLK) + return 297000000; + return 270000000; +} + +static long sun6i_pll3_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + u32 frac; + unsigned long n, m; + + frac = sun6i_pll3_get_fact(rate, *parent_rate, &n, &m); + if (frac & SUN6I_PLL3_MODE_SEL) + return *parent_rate / m * n; + if (frac & SUN6I_PLL3_FRAC_CLK) + return 297000000; + return 270000000; +} + +static int sun6i_pll3_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_fact *fact = to_clk_fact(hw); + u32 reg; + unsigned long n, m; + + reg = readl(fact->reg) & + ~(SUN6I_PLL3_MODE_SEL | + SUN6I_PLL3_FRAC_CLK | + (SUN6I_PLL3_NMASK << SUN6I_PLL3_NSHIFT) | + (SUN6I_PLL3_MMASK << SUN6I_PLL3_MSHIFT)); + + reg |= sun6i_pll3_get_fact(rate, parent_rate, &n, &m); + if (reg & SUN6I_PLL3_MODE_SEL) + reg |= ((n - 1) << SUN6I_PLL3_NSHIFT) | + ((m - 1) << SUN6I_PLL3_MSHIFT); + + writel(reg, fact->reg); + + readl_poll_timeout(fact->reg, reg, reg & SUN6I_PLL3_LOCK_BIT, 10, 500); + + return 0; +} + +static const struct clk_ops clk_sun6i_pll3_fact_ops = { + .recalc_rate = sun6i_pll3_recalc_rate, + .round_rate = sun6i_pll3_round_rate, + .set_rate = sun6i_pll3_set_rate, +}; + +static void __init sun6i_pll3_setup(struct device_node *node) +{ + const char *clk_name, *parent; + void __iomem *mmio; + struct clk_fact *fact; + struct clk_gate *gate; + struct resource res; + struct clk *clk; + + mmio = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (IS_ERR(mmio)) { + pr_err("%s: Could not map the clock registers\n", clk_name); + return; + } + + gate = kzalloc(sizeof(*gate), GFP_KERNEL); + if (!gate) + goto free_mmio; + gate->reg = mmio; + gate->bit_idx = SUN6I_PLL3_GATE_BIT; + gate->lock = &sun6i_pll3_lock; + + fact = kzalloc(sizeof(*fact), GFP_KERNEL); + if (!fact) + goto free_gate; + fact->reg = mmio; + + parent = of_clk_get_parent_name(node, 0); + + of_property_read_string(node, "clock-output-names", &clk_name); + + clk = clk_register_composite(NULL, clk_name, + &parent, 1, + NULL, NULL, + &fact->hw, &clk_sun6i_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); +free_mmio: + iounmap(mmio); + of_address_to_resource(node, 0, &res); + release_mem_region(res.start, resource_size(&res)); +} + +CLK_OF_DECLARE(sun6i_pll3, "allwinner,sun6i-pll3-clk", sun6i_pll3_setup);
In recent SoCs, as the H3, Allwinner uses a new display interface, DE2. This patch adds a DRM video driver for this interface.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- Changes: - remarks from Russell King - DT documentation added - working resolution change with xrandr - removal of the HDMI driver --- .../devicetree/bindings/display/sunxi.txt | 81 ++++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 20 + drivers/gpu/drm/sunxi/Makefile | 7 + drivers/gpu/drm/sunxi/de2_crtc.c | 425 +++++++++++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 43 ++ drivers/gpu/drm/sunxi/de2_de.c | 461 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 55 +++ drivers/gpu/drm/sunxi/de2_drv.c | 376 +++++++++++++++++ drivers/gpu/drm/sunxi/de2_plane.c | 114 +++++ 11 files changed, 1585 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi.txt 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_plane.c
diff --git a/Documentation/devicetree/bindings/display/sunxi.txt b/Documentation/devicetree/bindings/display/sunxi.txt new file mode 100644 index 0000000..1070bf0 --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi.txt @@ -0,0 +1,81 @@ +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 + +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 { + ... + #address-cells = <1>; + #size-cells = <0>; + port { + hdmi_ep: endpoint { + remote-endpoint = <&lcd0_ep>; + }; + }; + }; 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..af9cadf --- /dev/null +++ b/drivers/gpu/drm/sunxi/Kconfig @@ -0,0 +1,20 @@ +# +# 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 + 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..3778877 --- /dev/null +++ b/drivers/gpu/drm/sunxi/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for Allwinner's DRM device driver +# + +sunxi-de2-drm-objs := de2_drv.o de2_de.o de2_crtc.o de2_plane.o + +obj-$(CONFIG_DRM_SUNXI_DE2) += sunxi-de2-drm.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..243d0b5 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_crtc.c @@ -0,0 +1,425 @@ +/* + * 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 <asm/io.h> + +#include "de2_drm.h" +#include "de2_crtc.h" + +/* I/O map */ + +struct tcon { + u32 gctl; +#define TCON_GCTL_TCON_En BIT(31) + u32 gint0; +#define TCON_GINT0_TCON1_Vb_Int_En BIT(30) +#define TCON_GINT0_TCON1_Vb_Int_Flag BIT(12) + u32 gint1; + u32 dum0[13]; + u32 tcon0_ctl; +#define TCON0_CTL_TCON_En BIT(31) + u32 dum1[19]; + u32 tcon1_ctl; +#define TCON1_CTL_TCON_En BIT(31) +#define TCON1_CTL_Interlace_En BIT(20) +#define TCON1_CTL_Start_Delay_SHIFT 4 +#define TCON1_CTL_Start_Delay_MASK GENMASK(8, 4) + 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 BIT(24) +#define TCON1_IO_POL_IO1_inv BIT(25) +#define TCON1_IO_POL_IO2_inv BIT(26) + u32 io_tri; + u32 dum4[2]; + + u32 ceu_ctl; /* 100 */ +#define TCON_CEU_CTL_ceu_en BIT(31) + 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 GENMASK(9, 8) + u32 dum7[63]; + + u32 fill_ctl; /* 300 */ + u32 fill_start0; + u32 fill_end0; + u32 fill_data0; +}; + +#define XY(x, y) (((x) << 16) | (y)) + +#define tcon_read(base, member) \ + readl_relaxed(base + offsetof(struct tcon, member)) +#define tcon_write(base, member, data) \ + writel_relaxed(data, base + offsetof(struct tcon, member)) + +/* + * 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; + 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); + tcon_write(lcd->mmio, basic0, data); + tcon_write(lcd->mmio, basic1, data); + tcon_write(lcd->mmio, basic2, data); + tcon_write(lcd->mmio, basic3, + XY(mode->htotal - 1, + mode->htotal - mode->hsync_start - 1)); + tcon_write(lcd->mmio, basic4, + XY(mode->vtotal * (3 - interlace), + mode->vtotal - mode->vsync_start - 1)); + tcon_write(lcd->mmio, basic5, + XY(mode->hsync_end - mode->hsync_start - 1, + mode->vsync_end - mode->vsync_start - 1)); + + tcon_write(lcd->mmio, 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; + tcon_write(lcd->mmio, io_pol, data); + + tcon_write(lcd->mmio, ceu_ctl, + tcon_read(lcd->mmio, ceu_ctl) & ~TCON_CEU_CTL_ceu_en); + + data = tcon_read(lcd->mmio, tcon1_ctl); + if (interlace == 2) + data |= TCON1_CTL_Interlace_En; + else + data &= ~TCON1_CTL_Interlace_En; + tcon_write(lcd->mmio, tcon1_ctl, data); + + tcon_write(lcd->mmio, fill_ctl, 0); + tcon_write(lcd->mmio, fill_start0, mode->vtotal + 1); + tcon_write(lcd->mmio, fill_end0, mode->vtotal); + tcon_write(lcd->mmio, fill_data0, 0); + + start_delay = (mode->vtotal - mode->vdisplay) / interlace - 5; + if (start_delay > 31) + start_delay = 31; + data = tcon_read(lcd->mmio, tcon1_ctl); + data &= ~TCON1_CTL_Start_Delay_MASK; + data |= start_delay << TCON1_CTL_Start_Delay_SHIFT; + tcon_write(lcd->mmio, tcon1_ctl, data); + + tcon_write(lcd->mmio, 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; + u32 data; + + DRM_DEBUG_DRIVER("\n"); + + de2_set_frame_timings(lcd); + + clk_set_rate(lcd->clk, mode->clock * 1000); + + if (lcd->num == 0) { /* HDMI */ + data = tcon_read(lcd->mmio, mux_ctl); + data &= ~TCON_MUX_CTL_HDMI_SRC_MASK; + data |= lcd->num << TCON_MUX_CTL_HDMI_SRC_SHIFT; + tcon_write(lcd->mmio, mux_ctl, data); + } + + tcon_write(lcd->mmio, tcon1_ctl, + tcon_read(lcd->mmio, tcon1_ctl) | TCON1_CTL_TCON_En); + + 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); + + DRM_DEBUG_DRIVER("\n"); + + tcon_write(lcd->mmio, tcon1_ctl, + tcon_read(lcd->mmio, 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) +{ + DRM_DEBUG_DRIVER("\n"); + + tcon_write(lcd->mmio, tcon0_ctl, + tcon_read(lcd->mmio, tcon0_ctl) & ~TCON0_CTL_TCON_En); + tcon_write(lcd->mmio, tcon1_ctl, + tcon_read(lcd->mmio, tcon1_ctl) & ~TCON1_CTL_TCON_En); + tcon_write(lcd->mmio, gctl, + tcon_read(lcd->mmio, gctl) & ~TCON_GCTL_TCON_En); + + tcon_write(lcd->mmio, gint0, + tcon_read(lcd->mmio, gint0) & ~(TCON_GINT0_TCON1_Vb_Int_En | + TCON_GINT0_TCON1_Vb_Int_Flag)); + + tcon_write(lcd->mmio, gctl, + tcon_read(lcd->mmio, 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); + de2_de_enable(lcd->priv, lcd->num); + + 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); + + if (lcd->mmio) { + if (lcd->priv) + de2_de_disable(lcd->priv, lcd->num); + tcon_write(lcd->mmio, gctl, + tcon_read(lcd->mmio, 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..c39b63d --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_crtc.h @@ -0,0 +1,43 @@ +#ifndef __DE2_CRTC_H__ +#define __DE2_CRTC_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 as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#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..bd9cd74 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_de.c @@ -0,0 +1,461 @@ +/* + * 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 <asm/io.h> +#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 BIT(0) +#define DE_MUX_GLB_CTL_finish_irq_en BIT(4) +#define DE_MUX_GLB_CTL_rtwb_port BIT(12) + 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[4]; + u32 dum0[15]; /* (clear offset) */ + 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 BIT(0) +#define VI_CFG_ATTR_fcolor_en BIT(4) +#define VI_CFG_ATTR_fmt_SHIFT 8 +#define VI_CFG_ATTR_fmt_MASK GENMASK(12, 8) +#define VI_CFG_ATTR_sel BIT(15) +#define VI_CFG_ATTR_top_down BIT(23) + 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 BIT(0) +#define UI_CFG_ATTR_alpmod_SHIFT 1 +#define UI_CFG_ATTR_alpmod_MASK GENMASK(2, 1) +#define UI_CFG_ATTR_fcolor_en BIT(4) +#define UI_CFG_ATTR_fmt_SHIFT 8 +#define UI_CFG_ATTR_fmt_MASK GENMASK(12, 8) +#define UI_CFG_ATTR_top_down BIT(23) +#define UI_CFG_ATTR_alpha_SHIFT 24 +#define UI_CFG_ATTR_alpha_MASK GENMASK(31, 24) + 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 */ +}; + +#define DE_CORE_CLK_RATE 432000000 + +/* all sizes are ((height - 1) << 16) | (width - 1) */ +#define WH(w, h) (((h - 1) << 16) | (w - 1)) + +/* video formats */ +#define DE2_FORMAT_ARGB_8888 0 +#define DE2_FORMAT_XRGB_8888 4 + +#define glb_read(base, member) \ + readl_relaxed(base + offsetof(struct de_glb, member)) +#define glb_write(base, member, data) \ + writel_relaxed(data, base + offsetof(struct de_glb, member)) +#define bld_read(base, member) \ + readl_relaxed(base + offsetof(struct de_bld, member)) +#define bld_write(base, member, data) \ + writel_relaxed(data, base + offsetof(struct de_bld, member)) +#define ui_read(base, member) \ + readl_relaxed(base + offsetof(struct de_ui, member)) +#define ui_write(base, member, data) \ + writel_relaxed(data, base + offsetof(struct de_ui, member)) + +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; + + DRM_DEBUG_DRIVER("\n"); + + 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); +} + +void de2_de_disable(struct priv *priv, int lcd_num) +{ + u32 data; + + data = ~(1 << lcd_num); + 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); +} + +/* hardware init of the DE, done once */ +void de2_de_hw_init(struct priv *priv, int lcd_num) +{ + void __iomem *mux_o = priv->mmio; + int chan; + u32 size = WH(1920, 1080); + u32 data; + unsigned long flags; + + mux_o += (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE; + + 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 */ + glb_write(mux_o + DE_MUX_GLB_REGS, ctl, + DE_MUX_GLB_CTL_rt_en | DE_MUX_GLB_CTL_rtwb_port); + glb_write(mux_o + DE_MUX_GLB_REGS, status, 0); + glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1); /* dble reg switch */ + glb_write(mux_o + DE_MUX_GLB_REGS, size, size); + + /* clear the VI/UI channels */ + for (chan = 0; chan < 4; chan++) { + void __iomem *chan_o = mux_o + DE_MUX_CHAN_REGS + + DE_MUX_CHAN_SZ * chan; + + if (chan == 0) + memset_io(chan_o, 0, sizeof(struct de_vi)); + else + memset_io(chan_o, 0, sizeof(struct de_ui)); + if (chan == 2 && lcd_num != 0) + break; /* lcd1 only 1 VI and 1 UI */ + } + + /* clear alpha blending */ + memset_io(mux_o + DE_MUX_BLD_REGS, 0, offsetof(struct de_bld, dum0)); + bld_write(mux_o + DE_MUX_BLD_REGS, out_ctl, 0); + + /* disable the enhancements */ + writel_relaxed(0, mux_o + DE_MUX_VSU_REGS); + writel_relaxed(0, mux_o + DE_MUX_GSU1_REGS); + writel_relaxed(0, mux_o + DE_MUX_GSU2_REGS); + writel_relaxed(0, mux_o + DE_MUX_GSU3_REGS); + writel_relaxed(0, mux_o + DE_MUX_FCE_REGS); + writel_relaxed(0, mux_o + DE_MUX_BWS_REGS); + writel_relaxed(0, mux_o + DE_MUX_LTI_REGS); + writel_relaxed(0, mux_o + DE_MUX_PEAK_REGS); + writel_relaxed(0, mux_o + DE_MUX_ASE_REGS); + writel_relaxed(0, mux_o + DE_MUX_FCC_REGS); + writel_relaxed(0, mux_o + DE_MUX_DCSC_REGS); + + spin_unlock_irqrestore(&de_lock, flags); +} + +void de2_de_panel_init(struct priv *priv, int lcd_num, + struct drm_display_mode *mode) +{ + void __iomem *mux_o = priv->mmio; + u32 size = WH(mode->hdisplay, mode->vdisplay); + u32 data; + unsigned long flags; + + mux_o += (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE; + + 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 */ + glb_write(mux_o + DE_MUX_GLB_REGS, ctl, + DE_MUX_GLB_CTL_rt_en | DE_MUX_GLB_CTL_rtwb_port); + glb_write(mux_o + DE_MUX_GLB_REGS, status, 0); + glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1); /* dble reg switch */ + glb_write(mux_o + DE_MUX_GLB_REGS, size, size); + + /* set alpha blending */ + bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl, 0x00000101); + bld_write(mux_o + DE_MUX_BLD_REGS, attr[0].fcolor, 0); + bld_write(mux_o + DE_MUX_BLD_REGS, attr[0].insize, size); + bld_write(mux_o + DE_MUX_BLD_REGS, attr[0].offset, 0); /* 0, 0 */ + bld_write(mux_o + DE_MUX_BLD_REGS, route, 1); + bld_write(mux_o + DE_MUX_BLD_REGS, premultiply, 0); + bld_write(mux_o + DE_MUX_BLD_REGS, bkcolor, 0xff000000); + bld_write(mux_o + DE_MUX_BLD_REGS, output_size, size); + bld_write(mux_o + DE_MUX_BLD_REGS, bld_mode[0], 0x03010301); + /* SRCOVER */ + bld_write(mux_o + DE_MUX_BLD_REGS, bld_mode[1], 0x03010301); + bld_write(mux_o + DE_MUX_BLD_REGS, bld_mode[2], 0x03010301); + bld_write(mux_o + DE_MUX_BLD_REGS, out_ctl, + mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 0); + + spin_unlock_irqrestore(&de_lock, flags); +} + +void de2_de_ui_disable(struct priv *priv, + int lcd_num, int chan, int layer) +{ + void __iomem *mux_o = priv->mmio; + void __iomem *chan_o; + u32 data; + unsigned long flags; + + mux_o += (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE; + chan_o = mux_o + DE_MUX_CHAN_REGS + DE_MUX_CHAN_SZ * chan; + + 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); + + glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1); /* dble reg switch */ + + /* disable the UI plane */ + ui_write(chan_o, cfg[layer].attr, 0); + + spin_unlock_irqrestore(&de_lock, flags); +} + +void de2_de_ui_enable(struct priv *priv, + int lcd_num, int chan, int layer, + dma_addr_t addr, int fmt, + int width, int height, + int x, int y, int bpp) +{ + void __iomem *mux_o = priv->mmio; + void __iomem *chan_o; + u32 size = WH(width, height); + u32 data; + unsigned long flags; + + mux_o += (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE; + chan_o = mux_o; + chan_o += DE_MUX_CHAN_REGS + DE_MUX_CHAN_SZ * chan; + + DRM_DEBUG_DRIVER("%d:%d:%d addr: %p\n", + lcd_num, chan, layer, (void *) addr); + + switch (fmt) { + case DRM_FORMAT_ARGB8888: + fmt = DE2_FORMAT_ARGB_8888; + break; + case DRM_FORMAT_XRGB8888: + fmt = DE2_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); + + glb_write(mux_o + DE_MUX_GLB_REGS, ctl, + DE_MUX_GLB_CTL_rt_en | DE_MUX_GLB_CTL_rtwb_port); + glb_write(mux_o + DE_MUX_GLB_REGS, status, 0); + glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1); /* dble reg switch */ + + /* set the UI plane */ + ui_write(chan_o, cfg[layer].attr, UI_CFG_ATTR_en | + (fmt << UI_CFG_ATTR_fmt_SHIFT) | + (1 << UI_CFG_ATTR_alpmod_SHIFT) | + (0xff << UI_CFG_ATTR_alpha_SHIFT)); + ui_write(chan_o, cfg[layer].size, size); + ui_write(chan_o, cfg[layer].coord, WH(x, y)); + ui_write(chan_o, cfg[layer].pitch, width * bpp / 8); + ui_write(chan_o, cfg[layer].top_laddr, addr); + ui_write(chan_o, 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; + + /* the DE has a fixed 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..ea2703b --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_drm.h @@ -0,0 +1,55 @@ +#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 as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#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_disable(struct priv *priv, + int lcd_num, int chan, int layer); +void de2_de_ui_enable(struct priv *priv, + int lcd_num, int chan, int layer, + dma_addr_t addr, int fmt, + int width, int height, + int x, int y, 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..0a92c26 --- /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; + } + + /* bind the encoders/connectors */ + 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_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_plane.c b/drivers/gpu/drm/sunxi/de2_plane.c new file mode 100644 index 0000000..0aa6053 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_plane.c @@ -0,0 +1,114 @@ +/* + * 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 de2_plane_disable(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); + int plane_num = plane - lcd->planes; + + DRM_DEBUG_DRIVER("\n"); + + de2_de_ui_disable(lcd->priv, lcd->num, 1, /* first UI */ + plane_num); +} + +static void de2_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); + + /* RGB */ + start = gem->paddr + fb->offsets[0] + + src_y * fb->pitches[0] + src_x; + + de2_de_ui_enable(lcd->priv, lcd->num, 1, /* first UI */ + plane_num, + start, fb->pixel_format, + state->crtc_w, state->crtc_h, + crtc_x, crtc_y, + fb->bits_per_pixel); +} + +static const struct drm_plane_helper_funcs primary_plane_helper_funcs = { + .atomic_disable = de2_plane_disable, + .atomic_update = de2_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; +}
Hi Jean-Francois,
I haven't looked at it in detail yet, I just tried to compile it for ARM64 to prepare for a test on the Allwinner A64.
So just two things I spotted below:
On 15/01/16 15:54, Jean-Francois Moine wrote:
In recent SoCs, as the H3, Allwinner uses a new display interface, DE2. This patch adds a DRM video driver for this interface.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Changes:
- remarks from Russell King
- DT documentation added
- working resolution change with xrandr
- removal of the HDMI driver
.../devicetree/bindings/display/sunxi.txt | 81 ++++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 20 + drivers/gpu/drm/sunxi/Makefile | 7 + drivers/gpu/drm/sunxi/de2_crtc.c | 425 +++++++++++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 43 ++ drivers/gpu/drm/sunxi/de2_de.c | 461 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 55 +++ drivers/gpu/drm/sunxi/de2_drv.c | 376 +++++++++++++++++ drivers/gpu/drm/sunxi/de2_plane.c | 114 +++++ 11 files changed, 1585 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi.txt 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_plane.c
diff --git a/Documentation/devicetree/bindings/display/sunxi.txt b/Documentation/devicetree/bindings/display/sunxi.txt new file mode 100644 index 0000000..1070bf0 --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi.txt @@ -0,0 +1,81 @@ +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
+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 {
...
#address-cells = <1>;
#size-cells = <0>;
port {
hdmi_ep: endpoint {
remote-endpoint = <&lcd0_ep>;
};
};
- };
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..af9cadf --- /dev/null +++ b/drivers/gpu/drm/sunxi/Kconfig @@ -0,0 +1,20 @@ +# +# 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
Why does this _depend_ on MACH_SUN8I? First there is no real dependency from the code point of view AFAICS, and second this DE2 interface will probably be reused with future Allwinner chips. In fact it seems to be same as in the A64 SoC already. So I suggest to make it just depend on DRM_SUNXI.
- 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..3778877 --- /dev/null +++ b/drivers/gpu/drm/sunxi/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for Allwinner's DRM device driver +#
+sunxi-de2-drm-objs := de2_drv.o de2_de.o de2_crtc.o de2_plane.o
+obj-$(CONFIG_DRM_SUNXI_DE2) += sunxi-de2-drm.o
...
diff --git a/drivers/gpu/drm/sunxi/de2_de.c b/drivers/gpu/drm/sunxi/de2_de.c new file mode 100644 index 0000000..bd9cd74 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_de.c @@ -0,0 +1,461 @@ +/*
- 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 <asm/io.h> +#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 BIT(0) +#define DE_MUX_GLB_CTL_finish_irq_en BIT(4) +#define DE_MUX_GLB_CTL_rtwb_port BIT(12)
- 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[4];
- u32 dum0[15]; /* (clear offset) */
- 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 BIT(0) +#define VI_CFG_ATTR_fcolor_en BIT(4) +#define VI_CFG_ATTR_fmt_SHIFT 8 +#define VI_CFG_ATTR_fmt_MASK GENMASK(12, 8) +#define VI_CFG_ATTR_sel BIT(15) +#define VI_CFG_ATTR_top_down BIT(23)
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 BIT(0) +#define UI_CFG_ATTR_alpmod_SHIFT 1 +#define UI_CFG_ATTR_alpmod_MASK GENMASK(2, 1) +#define UI_CFG_ATTR_fcolor_en BIT(4) +#define UI_CFG_ATTR_fmt_SHIFT 8 +#define UI_CFG_ATTR_fmt_MASK GENMASK(12, 8) +#define UI_CFG_ATTR_top_down BIT(23) +#define UI_CFG_ATTR_alpha_SHIFT 24 +#define UI_CFG_ATTR_alpha_MASK GENMASK(31, 24)
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 */
+};
+#define DE_CORE_CLK_RATE 432000000
+/* all sizes are ((height - 1) << 16) | (width - 1) */ +#define WH(w, h) (((h - 1) << 16) | (w - 1))
+/* video formats */ +#define DE2_FORMAT_ARGB_8888 0 +#define DE2_FORMAT_XRGB_8888 4
+#define glb_read(base, member) \
- readl_relaxed(base + offsetof(struct de_glb, member))
+#define glb_write(base, member, data) \
- writel_relaxed(data, base + offsetof(struct de_glb, member))
+#define bld_read(base, member) \
- readl_relaxed(base + offsetof(struct de_bld, member))
+#define bld_write(base, member, data) \
- writel_relaxed(data, base + offsetof(struct de_bld, member))
+#define ui_read(base, member) \
- readl_relaxed(base + offsetof(struct de_ui, member))
+#define ui_write(base, member, data) \
- writel_relaxed(data, base + offsetof(struct de_ui, member))
+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;
- DRM_DEBUG_DRIVER("\n");
- 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);
+}
+void de2_de_disable(struct priv *priv, int lcd_num) +{
- u32 data;
- data = ~(1 << lcd_num);
- 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);
+}
+/* hardware init of the DE, done once */ +void de2_de_hw_init(struct priv *priv, int lcd_num) +{
- void __iomem *mux_o = priv->mmio;
- int chan;
- u32 size = WH(1920, 1080);
- u32 data;
- unsigned long flags;
- mux_o += (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
- 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 */
- glb_write(mux_o + DE_MUX_GLB_REGS, ctl,
DE_MUX_GLB_CTL_rt_en | DE_MUX_GLB_CTL_rtwb_port);
- glb_write(mux_o + DE_MUX_GLB_REGS, status, 0);
- glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1); /* dble reg switch */
- glb_write(mux_o + DE_MUX_GLB_REGS, size, size);
- /* clear the VI/UI channels */
- for (chan = 0; chan < 4; chan++) {
void __iomem *chan_o = mux_o + DE_MUX_CHAN_REGS +
DE_MUX_CHAN_SZ * chan;
if (chan == 0)
memset_io(chan_o, 0, sizeof(struct de_vi));
else
memset_io(chan_o, 0, sizeof(struct de_ui));
if (chan == 2 && lcd_num != 0)
break; /* lcd1 only 1 VI and 1 UI */
- }
- /* clear alpha blending */
- memset_io(mux_o + DE_MUX_BLD_REGS, 0, offsetof(struct de_bld, dum0));
- bld_write(mux_o + DE_MUX_BLD_REGS, out_ctl, 0);
- /* disable the enhancements */
- writel_relaxed(0, mux_o + DE_MUX_VSU_REGS);
- writel_relaxed(0, mux_o + DE_MUX_GSU1_REGS);
- writel_relaxed(0, mux_o + DE_MUX_GSU2_REGS);
- writel_relaxed(0, mux_o + DE_MUX_GSU3_REGS);
- writel_relaxed(0, mux_o + DE_MUX_FCE_REGS);
- writel_relaxed(0, mux_o + DE_MUX_BWS_REGS);
- writel_relaxed(0, mux_o + DE_MUX_LTI_REGS);
- writel_relaxed(0, mux_o + DE_MUX_PEAK_REGS);
- writel_relaxed(0, mux_o + DE_MUX_ASE_REGS);
- writel_relaxed(0, mux_o + DE_MUX_FCC_REGS);
- writel_relaxed(0, mux_o + DE_MUX_DCSC_REGS);
- spin_unlock_irqrestore(&de_lock, flags);
+}
+void de2_de_panel_init(struct priv *priv, int lcd_num,
struct drm_display_mode *mode)
+{
- void __iomem *mux_o = priv->mmio;
- u32 size = WH(mode->hdisplay, mode->vdisplay);
- u32 data;
- unsigned long flags;
- mux_o += (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
- 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 */
- glb_write(mux_o + DE_MUX_GLB_REGS, ctl,
DE_MUX_GLB_CTL_rt_en | DE_MUX_GLB_CTL_rtwb_port);
- glb_write(mux_o + DE_MUX_GLB_REGS, status, 0);
- glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1); /* dble reg switch */
- glb_write(mux_o + DE_MUX_GLB_REGS, size, size);
- /* set alpha blending */
- bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl, 0x00000101);
- bld_write(mux_o + DE_MUX_BLD_REGS, attr[0].fcolor, 0);
- bld_write(mux_o + DE_MUX_BLD_REGS, attr[0].insize, size);
- bld_write(mux_o + DE_MUX_BLD_REGS, attr[0].offset, 0); /* 0, 0 */
- bld_write(mux_o + DE_MUX_BLD_REGS, route, 1);
- bld_write(mux_o + DE_MUX_BLD_REGS, premultiply, 0);
- bld_write(mux_o + DE_MUX_BLD_REGS, bkcolor, 0xff000000);
- bld_write(mux_o + DE_MUX_BLD_REGS, output_size, size);
- bld_write(mux_o + DE_MUX_BLD_REGS, bld_mode[0], 0x03010301);
/* SRCOVER */
- bld_write(mux_o + DE_MUX_BLD_REGS, bld_mode[1], 0x03010301);
- bld_write(mux_o + DE_MUX_BLD_REGS, bld_mode[2], 0x03010301);
- bld_write(mux_o + DE_MUX_BLD_REGS, out_ctl,
mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 0);
- spin_unlock_irqrestore(&de_lock, flags);
+}
+void de2_de_ui_disable(struct priv *priv,
int lcd_num, int chan, int layer)
+{
- void __iomem *mux_o = priv->mmio;
- void __iomem *chan_o;
- u32 data;
- unsigned long flags;
- mux_o += (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
- chan_o = mux_o + DE_MUX_CHAN_REGS + DE_MUX_CHAN_SZ * chan;
- 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);
- glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1); /* dble reg switch */
- /* disable the UI plane */
- ui_write(chan_o, cfg[layer].attr, 0);
- spin_unlock_irqrestore(&de_lock, flags);
+}
+void de2_de_ui_enable(struct priv *priv,
int lcd_num, int chan, int layer,
dma_addr_t addr, int fmt,
int width, int height,
int x, int y, int bpp)
+{
- void __iomem *mux_o = priv->mmio;
- void __iomem *chan_o;
- u32 size = WH(width, height);
- u32 data;
- unsigned long flags;
- mux_o += (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
- chan_o = mux_o;
- chan_o += DE_MUX_CHAN_REGS + DE_MUX_CHAN_SZ * chan;
- DRM_DEBUG_DRIVER("%d:%d:%d addr: %p\n",
lcd_num, chan, layer, (void *) addr);
- switch (fmt) {
- case DRM_FORMAT_ARGB8888:
fmt = DE2_FORMAT_ARGB_8888;
break;
- case DRM_FORMAT_XRGB8888:
fmt = DE2_FORMAT_XRGB_8888;
break;
- default:
pr_err("format %.4s not yet treated\n", (char *) fmt);
This should be (char *) &fmt. This was spotted by the compiler when compiling for arm64.
Cheers, Andre.
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);
- glb_write(mux_o + DE_MUX_GLB_REGS, ctl,
DE_MUX_GLB_CTL_rt_en | DE_MUX_GLB_CTL_rtwb_port);
- glb_write(mux_o + DE_MUX_GLB_REGS, status, 0);
- glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1); /* dble reg switch */
- /* set the UI plane */
- ui_write(chan_o, cfg[layer].attr, UI_CFG_ATTR_en |
(fmt << UI_CFG_ATTR_fmt_SHIFT) |
(1 << UI_CFG_ATTR_alpmod_SHIFT) |
(0xff << UI_CFG_ATTR_alpha_SHIFT));
- ui_write(chan_o, cfg[layer].size, size);
- ui_write(chan_o, cfg[layer].coord, WH(x, y));
- ui_write(chan_o, cfg[layer].pitch, width * bpp / 8);
- ui_write(chan_o, cfg[layer].top_laddr, addr);
- ui_write(chan_o, 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;
- /* the DE has a fixed 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..ea2703b --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_drm.h @@ -0,0 +1,55 @@ +#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 as
- published by the Free Software Foundation; either version 2 of
- the License, or (at your option) any later version.
- */
+#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_disable(struct priv *priv,
int lcd_num, int chan, int layer);
+void de2_de_ui_enable(struct priv *priv,
int lcd_num, int chan, int layer,
dma_addr_t addr, int fmt,
int width, int height,
int x, int y, 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..0a92c26 --- /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;
- }
- /* bind the encoders/connectors */
- 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_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_plane.c b/drivers/gpu/drm/sunxi/de2_plane.c new file mode 100644 index 0000000..0aa6053 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_plane.c @@ -0,0 +1,114 @@ +/*
- 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 de2_plane_disable(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);
- int plane_num = plane - lcd->planes;
- DRM_DEBUG_DRIVER("\n");
- de2_de_ui_disable(lcd->priv, lcd->num, 1, /* first UI */
plane_num);
+}
+static void de2_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);
- /* RGB */
- start = gem->paddr + fb->offsets[0] +
src_y * fb->pitches[0] + src_x;
- de2_de_ui_enable(lcd->priv, lcd->num, 1, /* first UI */
plane_num,
start, fb->pixel_format,
state->crtc_w, state->crtc_h,
crtc_x, crtc_y,
fb->bits_per_pixel);
+}
+static const struct drm_plane_helper_funcs primary_plane_helper_funcs = {
- .atomic_disable = de2_plane_disable,
- .atomic_update = de2_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;
+}
linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
On Wed, 20 Jan 2016 11:14:38 +0000 Andre Przywara andre.przywara@arm.com wrote:
I haven't looked at it in detail yet, I just tried to compile it for ARM64 to prepare for a test on the Allwinner A64.
So just two things I spotted below:
Hi André,
I fixed them in the v3 patch request. Have you succeeded to get video on the A64?
Hi,
On 02/02/16 17:19, Jean-Francois Moine wrote:
On Wed, 20 Jan 2016 11:14:38 +0000 Andre Przywara andre.przywara@arm.com wrote:
I haven't looked at it in detail yet, I just tried to compile it for ARM64 to prepare for a test on the Allwinner A64.
So just two things I spotted below:
Hi André,
I fixed them in the v3 patch request.
Thanks for that.
Have you succeeded to get video on the A64?
No, but I haven't tried yet. I am tempted to give it a go, though. I guess this "headless Linux" term has scared some backers, who had more associations with Sleepy Hollow than with a serial terminal ;-)
Can you sketch (or point me to) what I need to do? Just a simple-framebuffer DT node? I take it that the driver does not depend on any kind of U-Boot initialisation, but instead takes care of this itself?
And would this pave the way for fbturbo also?
Please excuse my probably silly questions, I am more of a headless horseman ;-)
Cheers, Andre.
On Tue, 2 Feb 2016 17:26:13 +0000 Andre Przywara andre.przywara@arm.com wrote:
Can you sketch (or point me to) what I need to do? Just a simple-framebuffer DT node? I take it that the driver does not depend on any kind of U-Boot initialisation, but instead takes care of this itself?
And would this pave the way for fbturbo also?
In my site (http://moinejf.free.fr/opi2/), you may find: - the HDMI driver - to be built out of tree - the DT files (sun8i-h3.dtsi and sun8i-h3-orangepi-plus.dts) which should be close enough to the A64 - my .config (config-4.5.txt)
The driver has no video overlay and no acceleration. The frame buffer (console) works (with a blinking cursor!). X11 works fine with the standard modesetting driver (default).
There is just a problem with the HDMI driver. For unknown reason, sometimes, the EDID cannot be read (timeout - this problem also exists with the kernel 3.4). The remedy is power off / power on (of the board, not of the screen!).
I am using the u-boot from Allwinner. This one activates the video engine, but everything is reset at kernel start time.
About the GPU/VPU, I have no need of them, so I will let this job to specialists and, instead, have a look at audio...
dri-devel@lists.freedesktop.org