[alsa-devel] [PATCH v5 0/7] ARM: ASoC: drm: sun8i: Add DE2 HDMI audio and video
This patchset series adds HDMI audio and video support to the Allwinner sun8i SoCs which include the display engine 2 (DE2).
A first submission in January for video on the H3 could not enter into the mainline kernel due to the lack of license headers in Allwinner's sources.
Recently, an announce about Tina OS for the R series https://www.youtube.com/watch?v=h7KD-6HblAU was followed by the upload of a new linux-3.4 source tree https://github.com/tinalinux/linux-3.4 with files containing GPL headers.
Well, I don't know if these sources are really from Allwinner, but anyway, this is the opportunity to propose a new version of my DRM HDMI driver.
v5: - add overlay plane - add audio support - add support for the A83T - add back the HDMI driver - many bug fixes v4: - drivers/clk/sunxi/Makefile was missing (Emil Velikov) v3: - add the hardware cursor - simplify and fix the DE2 init sequences - generation for all SUNXI SoCs (Andre Przywara) v2: - remove the HDMI driver - remarks from Chen-Yu Tsai and Russell King - DT documentation added
Jean-Francois Moine (7): drm: sunxi: Add a basic DRM driver for Allwinner DE2 ASoC: sunxi: Add a simple HDMI CODEC drm: sunxi: add DE2 HDMI support ASoC: sunxi: Add sun8i I2S driver ARM: dts: sun8i-h3: add HDMI audio and video nodes ARM: dts: sun8i-h3: Add HDMI audio and video to the Banana Pi M2+ ARM: dts: sun8i-h3: Add HDMI audio and video to the Orange PI 2
.../devicetree/bindings/display/sunxi/hdmi.txt | 52 ++ .../bindings/display/sunxi/sunxi-de2.txt | 83 ++ .../devicetree/bindings/sound/sun4i-i2s.txt | 38 +- arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts | 17 + arch/arm/boot/dts/sun8i-h3-orangepi-2.dts | 17 + arch/arm/boot/dts/sun8i-h3.dtsi | 67 ++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 29 + drivers/gpu/drm/sunxi/Makefile | 9 + drivers/gpu/drm/sunxi/de2_crtc.c | 475 +++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 63 ++ drivers/gpu/drm/sunxi/de2_de.c | 591 +++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 47 ++ drivers/gpu/drm/sunxi/de2_drv.c | 378 +++++++++ drivers/gpu/drm/sunxi/de2_hdmi.c | 396 +++++++++ drivers/gpu/drm/sunxi/de2_hdmi.h | 40 + drivers/gpu/drm/sunxi/de2_hdmi_io.c | 927 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_hdmi_io.h | 25 + drivers/gpu/drm/sunxi/de2_plane.c | 119 +++ include/sound/sunxi_hdmi.h | 23 + sound/soc/codecs/Kconfig | 9 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sunxi-hdmi.c | 106 +++ sound/soc/sunxi/Kconfig | 8 + sound/soc/sunxi/Makefile | 3 + sound/soc/sunxi/sun8i-i2s.c | 700 ++++++++++++++++ 27 files changed, 4222 insertions(+), 5 deletions(-) create mode 100644 Documentation/devicetree/bindings/display/sunxi/hdmi.txt create mode 100644 Documentation/devicetree/bindings/display/sunxi/sunxi-de2.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_hdmi.c create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.h create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_io.c create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_io.h create mode 100644 drivers/gpu/drm/sunxi/de2_plane.c create mode 100644 include/sound/sunxi_hdmi.h create mode 100644 sound/soc/codecs/sunxi-hdmi.c create mode 100644 sound/soc/sunxi/sun8i-i2s.c
Allwinner's recent SoCs, as A64, A83T and H3, contain a new display engine, DE2. This patch adds a DRM video driver for this device.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- .../bindings/display/sunxi/sunxi-de2.txt | 83 +++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 21 + drivers/gpu/drm/sunxi/Makefile | 7 + drivers/gpu/drm/sunxi/de2_crtc.c | 475 +++++++++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 63 +++ drivers/gpu/drm/sunxi/de2_de.c | 591 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 47 ++ drivers/gpu/drm/sunxi/de2_drv.c | 378 +++++++++++++ drivers/gpu/drm/sunxi/de2_plane.c | 119 +++++ 11 files changed, 1787 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sunxi-de2.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/sunxi-de2.txt b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt new file mode 100644 index 0000000..f9cd67a --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt @@ -0,0 +1,83 @@ +Allwinner sunxi Display Engine 2 subsystem +========================================== + +The sunxi DE2 subsystem contains a display controller (DE2), +one or two LCD controllers (TCON) and their external interfaces. + +Display controller +================== + +Required properties: + +- compatible: value should be one of the following + "allwinner,sun8i-a83t-display-engine" + "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-a83t-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 = <&&ccu CLK_BUS_DE>, <&ccu CLK_DE>; + clock-names = "gate", "clock"; + resets = <&ccu RST_BUS_DE>; + ports = <&lcd0_p>; + }; + + lcd0: lcd-controller@01c0c000 { + compatible = "allwinner,sun8i-a83t-lcd"; + ... + clocks = <&ccu CLK_BUS_TCON0>, <&ccu CLK_TCON0>; + clock-names = "gate", "clock"; + resets = <&ccu RST_BUS_TCON0>; + #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 { + type = "video"; + hdmi_ep: endpoint { + remote-endpoint = <&lcd0_ep>; + }; + }; + }; diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 483059a..afd576f 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -187,6 +187,8 @@ source "drivers/gpu/drm/shmobile/Kconfig"
source "drivers/gpu/drm/sun4i/Kconfig"
+source "drivers/gpu/drm/sunxi/Kconfig" + source "drivers/gpu/drm/omapdrm/Kconfig"
source "drivers/gpu/drm/tilcdc/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 25c7204..120d0bf 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-y += omapdrm/ obj-$(CONFIG_DRM_SUN4I) += sun4i/ +obj-$(CONFIG_DRM_SUNXI) += sunxi/ obj-y += tilcdc/ obj-$(CONFIG_DRM_QXL) += qxl/ obj-$(CONFIG_DRM_BOCHS) += bochs/ diff --git a/drivers/gpu/drm/sunxi/Kconfig b/drivers/gpu/drm/sunxi/Kconfig new file mode 100644 index 0000000..56bde2e --- /dev/null +++ b/drivers/gpu/drm/sunxi/Kconfig @@ -0,0 +1,21 @@ +# +# Allwinner Video configuration +# + +config DRM_SUNXI + tristate "DRM Support for Allwinner Video" + depends on DRM && OF + depends on ARCH_SUNXI || COMPILE_TEST + 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 + help + Choose this option if your Allwinner chipset has the DE2 interface + as the A64, A83T and H3. If M is selected the module will be called + sunxi-de2-drm. diff --git a/drivers/gpu/drm/sunxi/Makefile b/drivers/gpu/drm/sunxi/Makefile new file mode 100644 index 0000000..62220cb --- /dev/null +++ b/drivers/gpu/drm/sunxi/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for Allwinner's sun8i 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..dae0fab --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_crtc.c @@ -0,0 +1,475 @@ +/* + * 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 <linux/of_irq.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(14) + u32 gint1; + u32 dum0[13]; + u32 tcon0_ctl; /* 0x40 */ +#define TCON0_CTL_TCON_En BIT(31) + u32 dum1[19]; + u32 tcon1_ctl; /* 0x90 */ +#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; /* 0xb0 */ + u32 dum3[15]; + u32 io_pol; /* 0xf0 */ +#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; /* 0x100 */ +#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; /* 0x200 */ + u32 dum7[63]; + + u32 fill_ctl; /* 0x300 */ + 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 */ +static void de2_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct drm_pending_vblank_event *event = crtc->state->event; + + if (event) { + crtc->state->event = NULL; + spin_lock_irq(&crtc->dev->event_lock); + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +static irqreturn_t de2_lcd_irq(int irq, void *dev_id) +{ + struct lcd *lcd = (struct lcd *) dev_id; + u32 isr; + + isr = tcon_read(lcd->mmio, gint0); + + drm_crtc_handle_vblank(&lcd->crtc); + + tcon_write(lcd->mmio, gint0, isr & ~TCON_GINT0_TCON1_Vb_Int_Flag); + + return IRQ_HANDLED; +} + +int de2_enable_vblank(struct drm_device *drm, unsigned crtc) +{ + struct priv *priv = drm->dev_private; + struct lcd *lcd = priv->lcds[crtc]; + + tcon_write(lcd->mmio, gint0, + tcon_read(lcd->mmio, gint0) | + TCON_GINT0_TCON1_Vb_Int_En); + return 0; +} + +void de2_disable_vblank(struct drm_device *drm, unsigned crtc) +{ + struct priv *priv = drm->dev_private; + struct lcd *lcd = priv->lcds[crtc]; + + tcon_write(lcd->mmio, gint0, + tcon_read(lcd->mmio, gint0) & + ~TCON_GINT0_TCON1_Vb_Int_En); +} + +/* panel functions */ +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; + + 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; + + DRM_DEBUG_DRIVER("\n"); + + clk_set_rate(lcd->clk, mode->clock * 1000); + + de2_set_frame_timings(lcd); + + tcon_write(lcd->mmio, tcon1_ctl, + tcon_read(lcd->mmio, tcon1_ctl) | TCON1_CTL_TCON_En); + + 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); + unsigned long flags; + + DRM_DEBUG_DRIVER("\n"); + + tcon_write(lcd->mmio, tcon1_ctl, + tcon_read(lcd->mmio, tcon1_ctl) & ~TCON1_CTL_TCON_En); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irqsave(&crtc->dev->event_lock, flags); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + crtc->state->event = NULL; + } +} + +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 = { + .atomic_flush = de2_atomic_flush, + .enable = de2_crtc_enable, + .disable = de2_crtc_disable, +}; + +static void de2_tcon_init(struct lcd *lcd) +{ + 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); + + /* disable/ack interrupts */ + tcon_write(lcd->mmio, gint0, 0); +} + +static void de2_tcon_enable(struct lcd *lcd) +{ + 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; + + drm_crtc_helper_add(crtc, &de2_crtc_helper_funcs); + + ret = drm_crtc_init_with_planes(drm, crtc, + &lcd->planes[DE2_PRIMARY_PLANE], + &lcd->planes[DE2_CURSOR_PLANE], + &de2_crtc_funcs, NULL); + if (ret < 0) + return ret; + + de2_tcon_enable(lcd); + + de2_de_enable(lcd->priv, lcd->num); + + 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; + + /* (only 2 LCDs) */ + lcd->crtc_idx = priv->lcds[0] ? 1 : 0; + priv->lcds[lcd->crtc_idx] = lcd; + + ret = de2_crtc_init(drm, lcd); + if (ret < 0) { + dev_err(dev, "failed to init the crtc\n"); + return ret; + } + + 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, irq, 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"); /* optional */ + + 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_optional(dev, NULL); + + irq = irq_of_parse_and_map(np, 0); + if (irq <= 0 || irq == NO_IRQ) { + dev_err(dev, "unable to get irq lcd %d\n", id); + ret = -EINVAL; + goto err; + } + + if (!IS_ERR(lcd->rstc)) { + ret = reset_control_deassert(lcd->rstc); + if (ret) { + dev_err(dev, "reset deassert err %d\n", ret); + goto err; + } + } + + if (!IS_ERR(lcd->gate)) { + ret = clk_prepare_enable(lcd->gate); + if (ret) + goto err2; + } + + ret = clk_prepare_enable(lcd->clk); + if (ret) + goto err2; + + de2_tcon_init(lcd); + + ret = devm_request_irq(dev, irq, de2_lcd_irq, 0, + lcd->name, lcd); + if (ret < 0) { + dev_err(dev, "unable to request irq %d\n", irq); + goto err2; + } + + return component_add(dev, &de2_lcd_ops); + +err2: + if (!IS_ERR_OR_NULL(lcd->rstc)) + reset_control_assert(lcd->rstc); + clk_disable_unprepare(lcd->gate); + clk_disable_unprepare(lcd->clk); +err: + 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-a83t-lcd", }, + { } +}; + +struct platform_driver de2_lcd_platform_driver = { + .probe = de2_lcd_probe, + .remove = de2_lcd_remove, + .driver = { + .name = "sunxi-de2-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..efbe45d --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_crtc.h @@ -0,0 +1,63 @@ +#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; + +enum de2_plane2 { + DE2_PRIMARY_PLANE, + DE2_CURSOR_PLANE, + DE2_VI_PLANE, + DE2_N_PLANES, +}; +struct lcd { + void __iomem *mmio; + + struct device *dev; + struct drm_crtc crtc; + struct priv *priv; /* DRM/DE private data */ + + short num; /* LCD number in hardware */ + short crtc_idx; /* CRTC index in drm */ + + struct clk *clk; + struct clk *gate; + struct reset_control *rstc; + + char name[16]; + + struct drm_pending_vblank_event *event; + + struct drm_plane planes[DE2_N_PLANES]; +}; + +#define crtc_to_lcd(x) container_of(x, struct lcd, crtc) + +/* 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); +void de2_de_plane_disable(struct priv *priv, + int lcd_num, int plane_ix); +void de2_de_plane_update(struct priv *priv, + int lcd_num, int plane_ix, + struct drm_plane_state *state, + struct drm_plane_state *old_state); + +/* in de2_plane.c */ +int de2_plane_init(struct drm_device *drm, struct lcd *lcd); + +#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..0d8cb62 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_de.c @@ -0,0 +1,591 @@ +/* + * ALLWINNER DRM driver - Display Engine 2 + * + * Copyright (C) 2016 Jean-Francois Moine moinejf@free.fr + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * + * 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 <drm/drm_gem_cma_helper.h> + +#include "de2_drm.h" +#include "de2_crtc.h" + +static DEFINE_SPINLOCK(de_lock); + +#define DE_CLK_RATE_A83T 504000000 /* pll-de */ +#define DE_CLK_RATE_H3 432000000 /* de */ + +/* 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 0x30000 /* GSUs */ +#define DE_MUX_GSU2_REGS 0x40000 +#define DE_MUX_GSU3_REGS 0x50000 +#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/SMBL */ + +/* 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]; /* (end of 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]; /* c0 */ + u32 dum3[4]; + u32 ck_min[4]; /* e0 */ + 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_ui_sel BIT(15) +#define VI_CFG_ATTR_top_down BIT(23) + u32 size; + u32 coord; +#define VI_N_PLANES 3 + u32 pitch[VI_N_PLANES]; + u32 top_laddr[VI_N_PLANES]; + u32 bot_laddr[VI_N_PLANES]; + } cfg[4]; + u32 fcolor[4]; /* c0 */ + u32 top_haddr[VI_N_PLANES]; /* d0 */ + u32 bot_haddr[VI_N_PLANES]; /* 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 */ +}; + +/* coordinates and sizes */ +#define XY(x, y) (((y) << 16) | (x)) +#define WH(w, h) (((h - 1) << 16) | (w - 1)) + +/* UI video formats */ +#define DE2_FORMAT_ARGB_8888 0 +#define DE2_FORMAT_BGRA_8888 3 +#define DE2_FORMAT_XRGB_8888 4 +#define DE2_FORMAT_RGB_888 8 +#define DE2_FORMAT_BGR_888 9 + +/* VI video formats */ +#define DE2_FORMAT_YUV422_I_YVYU 1 /* Y-V-Y-U */ +#define DE2_FORMAT_YUV422_I_UYVY 2 /* U-Y-V-Y */ +#define DE2_FORMAT_YUV422_I_YUYV 3 /* Y-U-Y-V */ +#define DE2_FORMAT_YUV422_P 6 /* YYYY UU VV planar */ +#define DE2_FORMAT_YUV420_P 10 /* YYYY U V planar */ + +#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)) +#define vi_read(base, member) \ + readl_relaxed(base + offsetof(struct de_vi, member)) +#define vi_write(base, member, data) \ + writel_relaxed(data, base + offsetof(struct de_vi, member)) + +static const struct { + char chan; + char layer; + char pipe; +} plane2layer[DE2_N_PLANES] = { + [DE2_PRIMARY_PLANE] = {0, 0, 0}, + [DE2_CURSOR_PLANE] = {1, 0, 1}, + [DE2_VI_PLANE] = {0, 1, 0}, +}; + +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); +} + +static void de_lcd_select(struct priv *priv, + int lcd_num, + void __iomem *mux_o) +{ + u32 data; + + /* select the LCD */ + data = de_read(priv, DE_SEL_REG); + data &= ~1; + de_write(priv, DE_SEL_REG, data); + + /* double register switch */ + glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1); +} + +void de2_de_plane_update(struct priv *priv, + int lcd_num, int plane_ix, + struct drm_plane_state *state, + struct drm_plane_state *old_state) +{ + struct drm_framebuffer *fb = state->fb; + struct drm_gem_cma_object *gem; + void __iomem *mux_o = priv->mmio; + void __iomem *chan_o; + u32 size = WH(state->crtc_w, state->crtc_h); + u32 coord; + u32 screen_size; + u32 data, fcolor; + u32 ui_sel, alpha_glob; + int chan, layer, x, y; + unsigned fmt; + unsigned long flags; + + chan = plane2layer[plane_ix].chan; + layer = plane2layer[plane_ix].layer; + + 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; + + x = state->crtc_x >= 0 ? state->crtc_x : 0; + y = state->crtc_y >= 0 ? state->crtc_y : 0; + coord = XY(x, y); + + /* handle the cursor move */ + if (plane_ix == DE2_CURSOR_PLANE + && fb == old_state->fb) { + spin_lock_irqsave(&de_lock, flags); + de_lcd_select(priv, lcd_num, mux_o); + if (chan == 0) + vi_write(chan_o, cfg[layer].coord, coord); + else + ui_write(chan_o, cfg[layer].coord, coord); + spin_unlock_irqrestore(&de_lock, flags); + return; + } + + gem = drm_fb_cma_get_gem_obj(fb, 0); + + ui_sel = alpha_glob = 0; + switch (fb->pixel_format) { + case DRM_FORMAT_ARGB8888: + fmt = DE2_FORMAT_ARGB_8888; + ui_sel = VI_CFG_ATTR_ui_sel; + break; + case DRM_FORMAT_BGRA8888: + fmt = DE2_FORMAT_BGRA_8888; + ui_sel = VI_CFG_ATTR_ui_sel; + break; + case DRM_FORMAT_XRGB8888: + fmt = DE2_FORMAT_XRGB_8888; + ui_sel = VI_CFG_ATTR_ui_sel; + alpha_glob = (1 << UI_CFG_ATTR_alpmod_SHIFT) | + (0xff << UI_CFG_ATTR_alpha_SHIFT); + break; + case DRM_FORMAT_RGB888: + fmt = DE2_FORMAT_RGB_888; + ui_sel = VI_CFG_ATTR_ui_sel; + break; + case DRM_FORMAT_BGR888: + fmt = DE2_FORMAT_BGR_888; + ui_sel = VI_CFG_ATTR_ui_sel; + break; + case DRM_FORMAT_YUYV: + fmt = DE2_FORMAT_YUV422_I_YUYV; + break; + case DRM_FORMAT_YVYU: + fmt = DE2_FORMAT_YUV422_I_YVYU; + break; + case DRM_FORMAT_YUV422: + fmt = DE2_FORMAT_YUV422_P; + break; + case DRM_FORMAT_YUV420: + fmt = DE2_FORMAT_YUV420_P; + break; + case DRM_FORMAT_UYVY: + fmt = DE2_FORMAT_YUV422_I_UYVY; + break; + default: + pr_err("format %.4s not yet treated\n", + (char *) &fb->pixel_format); + return; + } + + spin_lock_irqsave(&de_lock, flags); + + screen_size = plane_ix == DE2_PRIMARY_PLANE ? + size : + glb_read(mux_o + DE_MUX_GLB_REGS, size); + + /* prepare the activation of alpha blending (1 bit per plane) */ + fcolor = bld_read(mux_o + DE_MUX_BLD_REGS, fcolor_ctl) + | (0x100 << plane2layer[plane_ix].pipe); + + de_lcd_select(priv, lcd_num, mux_o); + + if (chan == 0) { /* VI channel */ + int i; + + data = VI_CFG_ATTR_en | (fmt << VI_CFG_ATTR_fmt_SHIFT) | + ui_sel; + vi_write(chan_o, cfg[layer].attr, data); + vi_write(chan_o, cfg[layer].size, size); + vi_write(chan_o, cfg[layer].coord, coord); + for (i = 0; i < VI_N_PLANES; i++) { + vi_write(chan_o, cfg[layer].pitch[i], + fb->pitches[i] ? fb->pitches[i] : + fb->pitches[0]); + vi_write(chan_o, cfg[layer].top_laddr[i], + gem->paddr + fb->offsets[i]); + vi_write(chan_o, fcolor[layer], 0xff000000); + } + if (layer == 0) + vi_write(chan_o, ovl_size[0], screen_size); + + } else { /* UI channel */ + data = UI_CFG_ATTR_en | (fmt << UI_CFG_ATTR_fmt_SHIFT) | + alpha_glob; + ui_write(chan_o, cfg[layer].attr, data); + ui_write(chan_o, cfg[layer].size, size); + ui_write(chan_o, cfg[layer].coord, coord); + ui_write(chan_o, cfg[layer].pitch, fb->pitches[0]); + ui_write(chan_o, cfg[layer].top_laddr, + gem->paddr + fb->offsets[0]); + if (layer == 0) + ui_write(chan_o, ovl_size, screen_size); + } + bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl, fcolor); + + spin_unlock_irqrestore(&de_lock, flags); +} + +void de2_de_plane_disable(struct priv *priv, + int lcd_num, int plane_ix) +{ + void __iomem *mux_o = priv->mmio; + void __iomem *chan_o; + u32 fcolor; + int chan, layer, chan_disable = 0; + unsigned long flags; + + chan = plane2layer[plane_ix].chan; + layer = plane2layer[plane_ix].layer; + + 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; + + /* (only 2 layers) */ + if (chan == 0) { + if (vi_read(chan_o, cfg[1 - layer].attr) == 0) + chan_disable = 1; + } else { + if (ui_read(chan_o, cfg[1 - layer].attr) == 0) + chan_disable = 1; + } + + spin_lock_irqsave(&de_lock, flags); + + fcolor = bld_read(mux_o + DE_MUX_BLD_REGS, fcolor_ctl); + + de_lcd_select(priv, lcd_num, mux_o); + + if (chan == 0) + vi_write(chan_o, cfg[layer].attr, 0); + else + ui_write(chan_o, cfg[layer].attr, 0); + + if (chan_disable) + bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl, + fcolor & ~(0x100 << plane2layer[plane_ix].pipe)); + + 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); + unsigned i; + 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); + + de_lcd_select(priv, lcd_num, mux_o); + + glb_write(mux_o + DE_MUX_GLB_REGS, size, size); + + /* set alpha blending */ + for (i = 0; i < 4; i++) { + bld_write(mux_o + DE_MUX_BLD_REGS, attr[i].fcolor, 0xff000000); + bld_write(mux_o + DE_MUX_BLD_REGS, attr[i].insize, size); + } + bld_write(mux_o + DE_MUX_BLD_REGS, output_size, size); + 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_enable(struct priv *priv, int lcd_num) +{ + void __iomem *mux_o = priv->mmio; + unsigned chan, i; + u32 size = WH(1920, 1080); + u32 data; + unsigned long flags; + + DRM_DEBUG_DRIVER("lcd %d\n", lcd_num); + + de_write(priv, DE_RESET_REG, + de_read(priv, DE_RESET_REG) | + (lcd_num == 0 ? 1 : 4)); + data = 1 << lcd_num; /* 1 bit / lcd */ + 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); + + mux_o += (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE; + + 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; + + memset_io(chan_o, 0, chan == 0 ? + sizeof(struct de_vi) : sizeof(struct de_ui)); + + /* only 1 VI and 1 UI in lcd1 */ + if (chan == 2 && lcd_num == 1) + break; + } + + /* clear and set alpha blending */ + memset_io(mux_o + DE_MUX_BLD_REGS, 0, offsetof(struct de_bld, dum0)); + bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl, 0x00000101); + /* fcolor for primary */ + + /* prepare route for planes */ + data = 0; + for (i = 0; i < DE2_N_PLANES; i++) + data |= plane2layer[i].chan << (plane2layer[i].pipe * 4); + bld_write(mux_o + DE_MUX_BLD_REGS, route, data); + + 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, 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, 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_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); +} + +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"); /* optional */ + + 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_optional(dev, NULL); + + if (!IS_ERR(priv->rstc)) { + ret = reset_control_deassert(priv->rstc); + if (ret) { + dev_err(dev, "reset deassert err %d\n", ret); + return ret; + } + } + + if (!IS_ERR(priv->gate)) { + ret = clk_prepare_enable(priv->gate); + if (ret) + goto err_gate; + } + + ret = clk_prepare_enable(priv->clk); + if (ret) + goto err_enable; + if (priv->soc_type == SOC_A83T) + clk_set_rate(priv->clk, DE_CLK_RATE_A83T); + else + clk_set_rate(priv->clk, DE_CLK_RATE_H3); + + /* set the A83T clock divider = 500 / 250 */ + if (priv->soc_type == SOC_A83T) + de_write(priv, DE_DIV_REG, + 0x00000011); /* div = 2 for both LCDs */ + + return 0; + +err_enable: + clk_disable_unprepare(priv->gate); +err_gate: + if (!IS_ERR(priv->rstc)) + reset_control_assert(priv->rstc); + return ret; +} + +void de2_de_cleanup(struct priv *priv) +{ + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->gate); + if (!IS_ERR(priv->rstc)) + reset_control_assert(priv->rstc); +} diff --git a/drivers/gpu/drm/sunxi/de2_drm.h b/drivers/gpu/drm/sunxi/de2_drm.h new file mode 100644 index 0000000..7bb966c --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_drm.h @@ -0,0 +1,47 @@ +#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 + +/* SoC types */ +#define SOC_A83T 0 +#define SOC_H3 1 + +struct priv { + void __iomem *mmio; + struct clk *clk; + struct clk *gate; + struct reset_control *rstc; + + int soc_type; + + 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 */ +int de2_de_init(struct priv *priv, struct device *dev); +void de2_de_cleanup(struct priv *priv); + +#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..5daa15c --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_drv.c @@ -0,0 +1,378 @@ +/* + * 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_device.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 "20161001" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +static struct of_device_id de2_drm_of_match[] = { + { .compatible = "allwinner,sun8i-a83t-display-engine", + .data = (void *) SOC_A83T }, + { .compatible = "allwinner,sun8i-h3-display-engine", + .data = (void *) SOC_H3 }, + { }, +}; +MODULE_DEVICE_TABLE(of, de2_drm_of_match); + +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 = drm_dev_alloc(&de2_drm_driver, dev); + if (!drm) + return -ENOMEM; + + 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 = 32; /* needed for cursor */ + drm->mode_config.min_height = 32; + drm->mode_config.max_width = 1920; + drm->mode_config.max_height = 1080; + drm->mode_config.funcs = &de2_mode_config_funcs; + + drm->irq_enabled = true; + + /* initialize the display engine */ + priv->soc_type = (int) of_match_device(de2_drm_of_match, dev)->data; + ret = de2_de_init(priv, dev); + if (ret) + goto out2; + + /* start the subdevices */ + ret = component_bind_all(dev, drm); + if (ret < 0) + goto out2; + + ret = drm_dev_register(drm, 0); + 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: + drm_dev_unregister(drm); +out3: + component_unbind_all(dev, 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); + + drm_dev_unregister(drm); + drm_vblank_cleanup(drm); + + drm_mode_config_cleanup(drm); + + component_unbind_all(dev, 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 platform_driver de2_drm_platform_driver = { + .probe = de2_drm_probe, + .remove = de2_drm_remove, + .driver = { + .name = DRIVER_NAME, + .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 v2"); diff --git a/drivers/gpu/drm/sunxi/de2_plane.c b/drivers/gpu/drm/sunxi/de2_plane.c new file mode 100644 index 0000000..b338684 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_plane.c @@ -0,0 +1,119 @@ +/* + * 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 "de2_drm.h" +#include "de2_crtc.h" + +/* plane formats */ +static const uint32_t ui_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, +}; + +static const uint32_t vi_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, + DRM_FORMAT_YUV422, + DRM_FORMAT_YUV420, + DRM_FORMAT_UYVY, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, +}; + +static void de2_plane_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct drm_crtc *crtc = old_state->crtc; + struct lcd *lcd = crtc_to_lcd(crtc); + int plane_num = plane - lcd->planes; + + de2_de_plane_disable(lcd->priv, lcd->num, 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; + int plane_num = plane - lcd->planes; + + if (!crtc || !fb) { + DRM_DEBUG_DRIVER("no crtc/fb\n"); + return; + } + + de2_de_plane_update(lcd->priv, lcd->num, plane_num, + state, old_state); +} + +static const struct drm_plane_helper_funcs plane_helper_funcs = { + .atomic_disable = de2_plane_disable, + .atomic_update = de2_plane_update, +}; + +static const struct drm_plane_funcs plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static int de2_one_plane_init(struct drm_device *drm, + struct drm_plane *plane, + int type, int possible_crtcs, + const uint32_t *formats, + int nformats) +{ + int ret; + + ret = drm_universal_plane_init(drm, plane, possible_crtcs, + &plane_funcs, + formats, nformats, type, NULL); + if (ret >= 0) + drm_plane_helper_add(plane, &plane_helper_funcs); + + return ret; +} + +int de2_plane_init(struct drm_device *drm, struct lcd *lcd) +{ + int ret, possible_crtcs = 1 << lcd->crtc_idx; + + ret = de2_one_plane_init(drm, &lcd->planes[DE2_PRIMARY_PLANE], + DRM_PLANE_TYPE_PRIMARY, possible_crtcs, + ui_formats, ARRAY_SIZE(ui_formats)); + if (ret >= 0) + ret = de2_one_plane_init(drm, &lcd->planes[DE2_CURSOR_PLANE], + DRM_PLANE_TYPE_CURSOR, possible_crtcs, + ui_formats, ARRAY_SIZE(ui_formats)); + if (ret >= 0) + ret = de2_one_plane_init(drm, &lcd->planes[DE2_VI_PLANE], + DRM_PLANE_TYPE_OVERLAY, possible_crtcs, + vi_formats, ARRAY_SIZE(vi_formats)); + if (ret < 0) + dev_err(lcd->dev, "Couldn't initialize the planes err %d\n", + ret); + + return ret; +}
Hi,
On Fri, Oct 21, 2016 at 09:26:18AM +0200, Jean-Francois Moine wrote:
Allwinner's recent SoCs, as A64, A83T and H3, contain a new display engine, DE2. This patch adds a DRM video driver for this device.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Output from checkpatch: total: 0 errors, 20 warnings, 83 checks, 1799 lines checked
.../bindings/display/sunxi/sunxi-de2.txt | 83 +++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 21 + drivers/gpu/drm/sunxi/Makefile | 7 + drivers/gpu/drm/sunxi/de2_crtc.c | 475 +++++++++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 63 +++ drivers/gpu/drm/sunxi/de2_de.c | 591 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 47 ++ drivers/gpu/drm/sunxi/de2_drv.c | 378 +++++++++++++ drivers/gpu/drm/sunxi/de2_plane.c | 119 +++++ 11 files changed, 1787 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sunxi-de2.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/sunxi-de2.txt b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt new file mode 100644 index 0000000..f9cd67a --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt @@ -0,0 +1,83 @@ +Allwinner sunxi Display Engine 2 subsystem +==========================================
+The sunxi DE2 subsystem contains a display controller (DE2),
sunxi is a made up name, and doesn't really mean anything. You can call it either sun8i (because it was introduced in that family).
+one or two LCD controllers (TCON) and their external interfaces.
+Display controller +==================
+Required properties:
+- compatible: value should be one of the following
"allwinner,sun8i-a83t-display-engine"
"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
We've been calling them bus and mod.
+- resets: phandle to the reset of the device
+- ports: phandle's to the LCD ports
Please use the OF graph.
+LCD controller +==============
+Required properties:
+- compatible: value should be one of the following
"allwinner,sun8i-a83t-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 = <&&ccu CLK_BUS_DE>, <&ccu CLK_DE>;
clock-names = "gate", "clock";
resets = <&ccu RST_BUS_DE>;
ports = <&lcd0_p>;
- };
- lcd0: lcd-controller@01c0c000 {
compatible = "allwinner,sun8i-a83t-lcd";
...
clocks = <&ccu CLK_BUS_TCON0>, <&ccu CLK_TCON0>;
clock-names = "gate", "clock";
resets = <&ccu RST_BUS_TCON0>;
#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 {
type = "video";
hdmi_ep: endpoint {
remote-endpoint = <&lcd0_ep>;
};
};
- };
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 483059a..afd576f 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -187,6 +187,8 @@ source "drivers/gpu/drm/shmobile/Kconfig"
source "drivers/gpu/drm/sun4i/Kconfig"
+source "drivers/gpu/drm/sunxi/Kconfig"
source "drivers/gpu/drm/omapdrm/Kconfig"
source "drivers/gpu/drm/tilcdc/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 25c7204..120d0bf 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-y += omapdrm/ obj-$(CONFIG_DRM_SUN4I) += sun4i/ +obj-$(CONFIG_DRM_SUNXI) += sunxi/ obj-y += tilcdc/ obj-$(CONFIG_DRM_QXL) += qxl/ obj-$(CONFIG_DRM_BOCHS) += bochs/ diff --git a/drivers/gpu/drm/sunxi/Kconfig b/drivers/gpu/drm/sunxi/Kconfig new file mode 100644 index 0000000..56bde2e --- /dev/null +++ b/drivers/gpu/drm/sunxi/Kconfig @@ -0,0 +1,21 @@ +# +# Allwinner Video configuration +#
+config DRM_SUNXI
- tristate "DRM Support for Allwinner Video"
- depends on DRM && OF
- depends on ARCH_SUNXI || COMPILE_TEST
- 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
- help
Choose this option if your Allwinner chipset has the DE2 interface
as the A64, A83T and H3. If M is selected the module will be called
sunxi-de2-drm.
diff --git a/drivers/gpu/drm/sunxi/Makefile b/drivers/gpu/drm/sunxi/Makefile new file mode 100644 index 0000000..62220cb --- /dev/null +++ b/drivers/gpu/drm/sunxi/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for Allwinner's sun8i 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..dae0fab --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_crtc.c @@ -0,0 +1,475 @@ +/*
- 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 <linux/of_irq.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(14)
- u32 gint1;
- u32 dum0[13];
- u32 tcon0_ctl; /* 0x40 */
+#define TCON0_CTL_TCON_En BIT(31)
- u32 dum1[19];
- u32 tcon1_ctl; /* 0x90 */
+#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; /* 0xb0 */
- u32 dum3[15];
- u32 io_pol; /* 0xf0 */
+#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; /* 0x100 */
+#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; /* 0x200 */
- u32 dum7[63];
- u32 fill_ctl; /* 0x300 */
- u32 fill_start0;
- u32 fill_end0;
- u32 fill_data0;
+};
Please use defines instead of the structures.
+#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 */ +static void de2_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
+{
- struct drm_pending_vblank_event *event = crtc->state->event;
- if (event) {
crtc->state->event = NULL;
spin_lock_irq(&crtc->dev->event_lock);
if (drm_crtc_vblank_get(crtc) == 0)
drm_crtc_arm_vblank_event(crtc, event);
else
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irq(&crtc->dev->event_lock);
- }
+}
+static irqreturn_t de2_lcd_irq(int irq, void *dev_id) +{
- struct lcd *lcd = (struct lcd *) dev_id;
- u32 isr;
- isr = tcon_read(lcd->mmio, gint0);
- drm_crtc_handle_vblank(&lcd->crtc);
- tcon_write(lcd->mmio, gint0, isr & ~TCON_GINT0_TCON1_Vb_Int_Flag);
- return IRQ_HANDLED;
+}
+int de2_enable_vblank(struct drm_device *drm, unsigned crtc) +{
- struct priv *priv = drm->dev_private;
- struct lcd *lcd = priv->lcds[crtc];
- tcon_write(lcd->mmio, gint0,
tcon_read(lcd->mmio, gint0) |
TCON_GINT0_TCON1_Vb_Int_En);
That's a weird indentation
- return 0;
+}
+void de2_disable_vblank(struct drm_device *drm, unsigned crtc) +{
- struct priv *priv = drm->dev_private;
- struct lcd *lcd = priv->lcds[crtc];
- tcon_write(lcd->mmio, gint0,
tcon_read(lcd->mmio, gint0) &
~TCON_GINT0_TCON1_Vb_Int_En);
+}
+/* panel functions */
Panel functions? In the CRTC driver?
+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;
- 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);
+}
Some comments here would be nice, there's a lot of non trivial things.
+static void de2_crtc_enable(struct drm_crtc *crtc) +{
- struct lcd *lcd = crtc_to_lcd(crtc);
- struct drm_display_mode *mode = &crtc->mode;
- DRM_DEBUG_DRIVER("\n");
Log something useful, or don't.
- clk_set_rate(lcd->clk, mode->clock * 1000);
- de2_set_frame_timings(lcd);
- tcon_write(lcd->mmio, tcon1_ctl,
tcon_read(lcd->mmio, tcon1_ctl) | TCON1_CTL_TCON_En);
- de2_de_panel_init(lcd->priv, lcd->num, mode);
panel_init in the CRTC enable? Shouldn't that be in the panel driver? or at least the encoder?
- drm_mode_debug_printmodeline(mode);
This is already printed by the core.
+}
+static void de2_crtc_disable(struct drm_crtc *crtc) +{
- struct lcd *lcd = crtc_to_lcd(crtc);
- unsigned long flags;
- DRM_DEBUG_DRIVER("\n");
- tcon_write(lcd->mmio, tcon1_ctl,
tcon_read(lcd->mmio, tcon1_ctl) & ~TCON1_CTL_TCON_En);
- if (crtc->state->event && !crtc->state->active) {
spin_lock_irqsave(&crtc->dev->event_lock, flags);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
crtc->state->event = NULL;
- }
+}
+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 = {
- .atomic_flush = de2_atomic_flush,
- .enable = de2_crtc_enable,
- .disable = de2_crtc_disable,
+};
+static void de2_tcon_init(struct lcd *lcd) +{
- 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);
- /* disable/ack interrupts */
- tcon_write(lcd->mmio, gint0, 0);
+}
+static void de2_tcon_enable(struct lcd *lcd) +{
- 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;
- drm_crtc_helper_add(crtc, &de2_crtc_helper_funcs);
- ret = drm_crtc_init_with_planes(drm, crtc,
&lcd->planes[DE2_PRIMARY_PLANE],
&lcd->planes[DE2_CURSOR_PLANE],
&de2_crtc_funcs, NULL);
- if (ret < 0)
return ret;
- de2_tcon_enable(lcd);
- de2_de_enable(lcd->priv, lcd->num);
- 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;
- /* (only 2 LCDs) */
- lcd->crtc_idx = priv->lcds[0] ? 1 : 0;
- priv->lcds[lcd->crtc_idx] = lcd;
- ret = de2_crtc_init(drm, lcd);
- if (ret < 0) {
dev_err(dev, "failed to init the crtc\n");
return ret;
- }
- 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, irq, 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;
What do you need this number for?
- 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"); /* optional */
Having some kind of error checking would still be nice.
- 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_optional(dev, NULL);
Ditto.
- irq = irq_of_parse_and_map(np, 0);
- if (irq <= 0 || irq == NO_IRQ) {
dev_err(dev, "unable to get irq lcd %d\n", id);
ret = -EINVAL;
goto err;
- }
You can use platform_get_irq for that.
- if (!IS_ERR(lcd->rstc)) {
ret = reset_control_deassert(lcd->rstc);
if (ret) {
dev_err(dev, "reset deassert err %d\n", ret);
goto err;
}
- }
- if (!IS_ERR(lcd->gate)) {
ret = clk_prepare_enable(lcd->gate);
if (ret)
goto err2;
- }
- ret = clk_prepare_enable(lcd->clk);
- if (ret)
goto err2;
Is there any reason not to do that in the enable / disable? Leaving clocks running while the device has no guarantee that it's going to be used seems like a waste of resources.
- de2_tcon_init(lcd);
- ret = devm_request_irq(dev, irq, de2_lcd_irq, 0,
lcd->name, lcd);
- if (ret < 0) {
dev_err(dev, "unable to request irq %d\n", irq);
goto err2;
- }
- return component_add(dev, &de2_lcd_ops);
+err2:
- if (!IS_ERR_OR_NULL(lcd->rstc))
reset_control_assert(lcd->rstc);
- clk_disable_unprepare(lcd->gate);
- clk_disable_unprepare(lcd->clk);
+err:
- 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-a83t-lcd", },
- { }
+};
+struct platform_driver de2_lcd_platform_driver = {
- .probe = de2_lcd_probe,
- .remove = de2_lcd_remove,
- .driver = {
.name = "sunxi-de2-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..efbe45d --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_crtc.h @@ -0,0 +1,63 @@ +#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;
+enum de2_plane2 {
- DE2_PRIMARY_PLANE,
- DE2_CURSOR_PLANE,
- DE2_VI_PLANE,
- DE2_N_PLANES,
+}; +struct lcd {
- void __iomem *mmio;
- struct device *dev;
- struct drm_crtc crtc;
- struct priv *priv; /* DRM/DE private data */
- short num; /* LCD number in hardware */
- short crtc_idx; /* CRTC index in drm */
- struct clk *clk;
- struct clk *gate;
- struct reset_control *rstc;
- char name[16];
- struct drm_pending_vblank_event *event;
- struct drm_plane planes[DE2_N_PLANES];
+};
+#define crtc_to_lcd(x) container_of(x, struct lcd, crtc)
+/* 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);
+void de2_de_plane_disable(struct priv *priv,
int lcd_num, int plane_ix);
+void de2_de_plane_update(struct priv *priv,
int lcd_num, int plane_ix,
struct drm_plane_state *state,
struct drm_plane_state *old_state);
Does it need to be exported?
+/* in de2_plane.c */ +int de2_plane_init(struct drm_device *drm, struct lcd *lcd);
+#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..0d8cb62 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_de.c @@ -0,0 +1,591 @@ +/*
- ALLWINNER DRM driver - Display Engine 2
- Copyright (C) 2016 Jean-Francois Moine moinejf@free.fr
- Copyright (c) 2016 Allwinnertech Co., Ltd.
- 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 <drm/drm_gem_cma_helper.h>
+#include "de2_drm.h" +#include "de2_crtc.h"
+static DEFINE_SPINLOCK(de_lock);
+#define DE_CLK_RATE_A83T 504000000 /* pll-de */ +#define DE_CLK_RATE_H3 432000000 /* de */
This can be set in the DT.
+/* 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 0x30000 /* GSUs */ +#define DE_MUX_GSU2_REGS 0x40000 +#define DE_MUX_GSU3_REGS 0x50000 +#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/SMBL */
+/* 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]; /* (end of 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]; /* c0 */
- u32 dum3[4];
- u32 ck_min[4]; /* e0 */
- 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_ui_sel BIT(15) +#define VI_CFG_ATTR_top_down BIT(23)
u32 size;
u32 coord;
+#define VI_N_PLANES 3
u32 pitch[VI_N_PLANES];
u32 top_laddr[VI_N_PLANES];
u32 bot_laddr[VI_N_PLANES];
- } cfg[4];
- u32 fcolor[4]; /* c0 */
- u32 top_haddr[VI_N_PLANES]; /* d0 */
- u32 bot_haddr[VI_N_PLANES]; /* 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 */
+};
Please use defines instead of the structures.
+/* coordinates and sizes */ +#define XY(x, y) (((y) << 16) | (x)) +#define WH(w, h) (((h - 1) << 16) | (w - 1))
+/* UI video formats */ +#define DE2_FORMAT_ARGB_8888 0 +#define DE2_FORMAT_BGRA_8888 3 +#define DE2_FORMAT_XRGB_8888 4 +#define DE2_FORMAT_RGB_888 8 +#define DE2_FORMAT_BGR_888 9
+/* VI video formats */ +#define DE2_FORMAT_YUV422_I_YVYU 1 /* Y-V-Y-U */ +#define DE2_FORMAT_YUV422_I_UYVY 2 /* U-Y-V-Y */ +#define DE2_FORMAT_YUV422_I_YUYV 3 /* Y-U-Y-V */ +#define DE2_FORMAT_YUV422_P 6 /* YYYY UU VV planar */ +#define DE2_FORMAT_YUV420_P 10 /* YYYY U V planar */
+#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))
+#define vi_read(base, member) \
- readl_relaxed(base + offsetof(struct de_vi, member))
+#define vi_write(base, member, data) \
- writel_relaxed(data, base + offsetof(struct de_vi, member))
+static const struct {
- char chan;
- char layer;
- char pipe;
+} plane2layer[DE2_N_PLANES] = {
- [DE2_PRIMARY_PLANE] = {0, 0, 0},
- [DE2_CURSOR_PLANE] = {1, 0, 1},
- [DE2_VI_PLANE] = {0, 1, 0},
+};
Comments?
+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);
+}
+static void de_lcd_select(struct priv *priv,
int lcd_num,
void __iomem *mux_o)
+{
- u32 data;
- /* select the LCD */
- data = de_read(priv, DE_SEL_REG);
- data &= ~1;
- de_write(priv, DE_SEL_REG, data);
- /* double register switch */
- glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1);
+}
+void de2_de_plane_update(struct priv *priv,
int lcd_num, int plane_ix,
struct drm_plane_state *state,
struct drm_plane_state *old_state)
+{
- struct drm_framebuffer *fb = state->fb;
- struct drm_gem_cma_object *gem;
- void __iomem *mux_o = priv->mmio;
- void __iomem *chan_o;
- u32 size = WH(state->crtc_w, state->crtc_h);
- u32 coord;
- u32 screen_size;
- u32 data, fcolor;
- u32 ui_sel, alpha_glob;
- int chan, layer, x, y;
- unsigned fmt;
- unsigned long flags;
- chan = plane2layer[plane_ix].chan;
- layer = plane2layer[plane_ix].layer;
- 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;
- x = state->crtc_x >= 0 ? state->crtc_x : 0;
- y = state->crtc_y >= 0 ? state->crtc_y : 0;
- coord = XY(x, y);
- /* handle the cursor move */
- if (plane_ix == DE2_CURSOR_PLANE
&& fb == old_state->fb) {
spin_lock_irqsave(&de_lock, flags);
de_lcd_select(priv, lcd_num, mux_o);
if (chan == 0)
vi_write(chan_o, cfg[layer].coord, coord);
else
ui_write(chan_o, cfg[layer].coord, coord);
spin_unlock_irqrestore(&de_lock, flags);
return;
- }
- gem = drm_fb_cma_get_gem_obj(fb, 0);
- ui_sel = alpha_glob = 0;
- switch (fb->pixel_format) {
- case DRM_FORMAT_ARGB8888:
fmt = DE2_FORMAT_ARGB_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_BGRA8888:
fmt = DE2_FORMAT_BGRA_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_XRGB8888:
fmt = DE2_FORMAT_XRGB_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
alpha_glob = (1 << UI_CFG_ATTR_alpmod_SHIFT) |
(0xff << UI_CFG_ATTR_alpha_SHIFT);
break;
- case DRM_FORMAT_RGB888:
fmt = DE2_FORMAT_RGB_888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_BGR888:
fmt = DE2_FORMAT_BGR_888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_YUYV:
fmt = DE2_FORMAT_YUV422_I_YUYV;
break;
- case DRM_FORMAT_YVYU:
fmt = DE2_FORMAT_YUV422_I_YVYU;
break;
- case DRM_FORMAT_YUV422:
fmt = DE2_FORMAT_YUV422_P;
break;
- case DRM_FORMAT_YUV420:
fmt = DE2_FORMAT_YUV420_P;
break;
- case DRM_FORMAT_UYVY:
fmt = DE2_FORMAT_YUV422_I_UYVY;
break;
- default:
pr_err("format %.4s not yet treated\n",
(char *) &fb->pixel_format);
return;
- }
- spin_lock_irqsave(&de_lock, flags);
- screen_size = plane_ix == DE2_PRIMARY_PLANE ?
size :
glb_read(mux_o + DE_MUX_GLB_REGS, size);
- /* prepare the activation of alpha blending (1 bit per plane) */
- fcolor = bld_read(mux_o + DE_MUX_BLD_REGS, fcolor_ctl)
| (0x100 << plane2layer[plane_ix].pipe);
- de_lcd_select(priv, lcd_num, mux_o);
- if (chan == 0) { /* VI channel */
int i;
data = VI_CFG_ATTR_en | (fmt << VI_CFG_ATTR_fmt_SHIFT) |
ui_sel;
vi_write(chan_o, cfg[layer].attr, data);
vi_write(chan_o, cfg[layer].size, size);
vi_write(chan_o, cfg[layer].coord, coord);
for (i = 0; i < VI_N_PLANES; i++) {
vi_write(chan_o, cfg[layer].pitch[i],
fb->pitches[i] ? fb->pitches[i] :
fb->pitches[0]);
vi_write(chan_o, cfg[layer].top_laddr[i],
gem->paddr + fb->offsets[i]);
vi_write(chan_o, fcolor[layer], 0xff000000);
}
if (layer == 0)
vi_write(chan_o, ovl_size[0], screen_size);
- } else { /* UI channel */
data = UI_CFG_ATTR_en | (fmt << UI_CFG_ATTR_fmt_SHIFT) |
alpha_glob;
ui_write(chan_o, cfg[layer].attr, data);
ui_write(chan_o, cfg[layer].size, size);
ui_write(chan_o, cfg[layer].coord, coord);
ui_write(chan_o, cfg[layer].pitch, fb->pitches[0]);
ui_write(chan_o, cfg[layer].top_laddr,
gem->paddr + fb->offsets[0]);
if (layer == 0)
ui_write(chan_o, ovl_size, screen_size);
- }
- bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl, fcolor);
- spin_unlock_irqrestore(&de_lock, flags);
+}
Splitting that into functions would make it a bit more trivial and readable.
+void de2_de_plane_disable(struct priv *priv,
int lcd_num, int plane_ix)
+{
- void __iomem *mux_o = priv->mmio;
- void __iomem *chan_o;
- u32 fcolor;
- int chan, layer, chan_disable = 0;
- unsigned long flags;
- chan = plane2layer[plane_ix].chan;
- layer = plane2layer[plane_ix].layer;
- 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;
- /* (only 2 layers) */
- if (chan == 0) {
if (vi_read(chan_o, cfg[1 - layer].attr) == 0)
chan_disable = 1;
- } else {
if (ui_read(chan_o, cfg[1 - layer].attr) == 0)
chan_disable = 1;
- }
- spin_lock_irqsave(&de_lock, flags);
- fcolor = bld_read(mux_o + DE_MUX_BLD_REGS, fcolor_ctl);
- de_lcd_select(priv, lcd_num, mux_o);
- if (chan == 0)
vi_write(chan_o, cfg[layer].attr, 0);
- else
ui_write(chan_o, cfg[layer].attr, 0);
- if (chan_disable)
bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl,
fcolor & ~(0x100 << plane2layer[plane_ix].pipe));
- spin_unlock_irqrestore(&de_lock, flags);
+}
Can't you just disable it?
+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);
- unsigned i;
- 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);
- de_lcd_select(priv, lcd_num, mux_o);
- glb_write(mux_o + DE_MUX_GLB_REGS, size, size);
- /* set alpha blending */
- for (i = 0; i < 4; i++) {
bld_write(mux_o + DE_MUX_BLD_REGS, attr[i].fcolor, 0xff000000);
bld_write(mux_o + DE_MUX_BLD_REGS, attr[i].insize, size);
- }
- bld_write(mux_o + DE_MUX_BLD_REGS, output_size, size);
- 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_enable(struct priv *priv, int lcd_num) +{
- void __iomem *mux_o = priv->mmio;
- unsigned chan, i;
- u32 size = WH(1920, 1080);
- u32 data;
- unsigned long flags;
- DRM_DEBUG_DRIVER("lcd %d\n", lcd_num);
- de_write(priv, DE_RESET_REG,
de_read(priv, DE_RESET_REG) |
(lcd_num == 0 ? 1 : 4));
- data = 1 << lcd_num; /* 1 bit / lcd */
- 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);
- mux_o += (lcd_num == 0) ? DE_MUX0_BASE : DE_MUX1_BASE;
- 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;
memset_io(chan_o, 0, chan == 0 ?
sizeof(struct de_vi) : sizeof(struct de_ui));
/* only 1 VI and 1 UI in lcd1 */
if (chan == 2 && lcd_num == 1)
break;
- }
- /* clear and set alpha blending */
- memset_io(mux_o + DE_MUX_BLD_REGS, 0, offsetof(struct de_bld, dum0));
- bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl, 0x00000101);
/* fcolor for primary */
- /* prepare route for planes */
- data = 0;
- for (i = 0; i < DE2_N_PLANES; i++)
data |= plane2layer[i].chan << (plane2layer[i].pipe * 4);
- bld_write(mux_o + DE_MUX_BLD_REGS, route, data);
- 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, 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, 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_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);
+}
+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"); /* optional */
Error checking
- 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_optional(dev, NULL);
- if (!IS_ERR(priv->rstc)) {
ret = reset_control_deassert(priv->rstc);
if (ret) {
dev_err(dev, "reset deassert err %d\n", ret);
return ret;
}
- }
- if (!IS_ERR(priv->gate)) {
ret = clk_prepare_enable(priv->gate);
if (ret)
goto err_gate;
- }
- ret = clk_prepare_enable(priv->clk);
- if (ret)
goto err_enable;
- if (priv->soc_type == SOC_A83T)
clk_set_rate(priv->clk, DE_CLK_RATE_A83T);
- else
clk_set_rate(priv->clk, DE_CLK_RATE_H3);
- /* set the A83T clock divider = 500 / 250 */
- if (priv->soc_type == SOC_A83T)
de_write(priv, DE_DIV_REG,
0x00000011); /* div = 2 for both LCDs */
- return 0;
+err_enable:
- clk_disable_unprepare(priv->gate);
+err_gate:
- if (!IS_ERR(priv->rstc))
reset_control_assert(priv->rstc);
- return ret;
+}
+void de2_de_cleanup(struct priv *priv) +{
- clk_disable_unprepare(priv->clk);
- clk_disable_unprepare(priv->gate);
- if (!IS_ERR(priv->rstc))
reset_control_assert(priv->rstc);
+} diff --git a/drivers/gpu/drm/sunxi/de2_drm.h b/drivers/gpu/drm/sunxi/de2_drm.h new file mode 100644 index 0000000..7bb966c --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_drm.h @@ -0,0 +1,47 @@ +#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
+/* SoC types */ +#define SOC_A83T 0 +#define SOC_H3 1
+struct priv {
- void __iomem *mmio;
- struct clk *clk;
- struct clk *gate;
- struct reset_control *rstc;
- int soc_type;
- 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 */ +int de2_de_init(struct priv *priv, struct device *dev); +void de2_de_cleanup(struct priv *priv);
+#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..5daa15c --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_drv.c @@ -0,0 +1,378 @@ +/*
- 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_device.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 "20161001" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0
+static struct of_device_id de2_drm_of_match[] = {
- { .compatible = "allwinner,sun8i-a83t-display-engine",
.data = (void *) SOC_A83T },
- { .compatible = "allwinner,sun8i-h3-display-engine",
.data = (void *) SOC_H3 },
- { },
+}; +MODULE_DEVICE_TABLE(of, de2_drm_of_match);
+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)
+};
Why do you need that? How did you test it? There's no runtime_pm calls in your kernel.
+/*
- Platform driver
- */
+static int de2_drm_bind(struct device *dev) +{
- struct drm_device *drm;
- struct priv *priv;
- int ret;
- drm = drm_dev_alloc(&de2_drm_driver, dev);
- if (!drm)
return -ENOMEM;
- 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 = 32; /* needed for cursor */
- drm->mode_config.min_height = 32;
- drm->mode_config.max_width = 1920;
- drm->mode_config.max_height = 1080;
- drm->mode_config.funcs = &de2_mode_config_funcs;
- drm->irq_enabled = true;
- /* initialize the display engine */
- priv->soc_type = (int) of_match_device(de2_drm_of_match, dev)->data;
- ret = de2_de_init(priv, dev);
- if (ret)
goto out2;
- /* start the subdevices */
- ret = component_bind_all(dev, drm);
- if (ret < 0)
goto out2;
- ret = drm_dev_register(drm, 0);
- 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:
- drm_dev_unregister(drm);
+out3:
- component_unbind_all(dev, 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);
- drm_dev_unregister(drm);
- drm_vblank_cleanup(drm);
- drm_mode_config_cleanup(drm);
- component_unbind_all(dev, 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 platform_driver de2_drm_platform_driver = {
- .probe = de2_drm_probe,
- .remove = de2_drm_remove,
- .driver = {
.name = DRIVER_NAME,
.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; */
That's useless.
- 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;
+}
And that really shouldn't be done that way.
+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 v2"); diff --git a/drivers/gpu/drm/sunxi/de2_plane.c b/drivers/gpu/drm/sunxi/de2_plane.c new file mode 100644 index 0000000..b338684 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_plane.c @@ -0,0 +1,119 @@ +/*
- 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 "de2_drm.h" +#include "de2_crtc.h"
+/* plane formats */ +static const uint32_t ui_formats[] = {
- DRM_FORMAT_ARGB8888,
- DRM_FORMAT_BGRA8888,
- DRM_FORMAT_XRGB8888,
- DRM_FORMAT_RGB888,
- DRM_FORMAT_BGR888,
+};
+static const uint32_t vi_formats[] = {
- DRM_FORMAT_XRGB8888,
- DRM_FORMAT_YUYV,
- DRM_FORMAT_YVYU,
- DRM_FORMAT_YUV422,
- DRM_FORMAT_YUV420,
- DRM_FORMAT_UYVY,
- DRM_FORMAT_BGRA8888,
- DRM_FORMAT_RGB888,
- DRM_FORMAT_BGR888,
+};
+static void de2_plane_disable(struct drm_plane *plane,
struct drm_plane_state *old_state)
+{
- struct drm_crtc *crtc = old_state->crtc;
- struct lcd *lcd = crtc_to_lcd(crtc);
- int plane_num = plane - lcd->planes;
- de2_de_plane_disable(lcd->priv, lcd->num, 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;
- int plane_num = plane - lcd->planes;
- if (!crtc || !fb) {
DRM_DEBUG_DRIVER("no crtc/fb\n");
return;
- }
- de2_de_plane_update(lcd->priv, lcd->num, plane_num,
state, old_state);
+}
+static const struct drm_plane_helper_funcs plane_helper_funcs = {
- .atomic_disable = de2_plane_disable,
- .atomic_update = de2_plane_update,
+};
+static const struct drm_plane_funcs plane_funcs = {
- .update_plane = drm_atomic_helper_update_plane,
- .disable_plane = drm_atomic_helper_disable_plane,
- .destroy = drm_plane_cleanup,
- .reset = drm_atomic_helper_plane_reset,
- .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+static int de2_one_plane_init(struct drm_device *drm,
struct drm_plane *plane,
int type, int possible_crtcs,
const uint32_t *formats,
int nformats)
+{
- int ret;
- ret = drm_universal_plane_init(drm, plane, possible_crtcs,
&plane_funcs,
formats, nformats, type, NULL);
- if (ret >= 0)
drm_plane_helper_add(plane, &plane_helper_funcs);
- return ret;
+}
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd) +{
- int ret, possible_crtcs = 1 << lcd->crtc_idx;
- ret = de2_one_plane_init(drm, &lcd->planes[DE2_PRIMARY_PLANE],
DRM_PLANE_TYPE_PRIMARY, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
- if (ret >= 0)
ret = de2_one_plane_init(drm, &lcd->planes[DE2_CURSOR_PLANE],
DRM_PLANE_TYPE_CURSOR, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
Nothing looks really special about that cursor plane. Any reasion not to make it an overlay?
Maxime
On Mon, 24 Oct 2016 16:04:19 +0200 Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
Hi Maxime,
On Fri, Oct 21, 2016 at 09:26:18AM +0200, Jean-Francois Moine wrote:
Allwinner's recent SoCs, as A64, A83T and H3, contain a new display engine, DE2. This patch adds a DRM video driver for this device.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Output from checkpatch: total: 0 errors, 20 warnings, 83 checks, 1799 lines checked
.../bindings/display/sunxi/sunxi-de2.txt | 83 +++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 21 + drivers/gpu/drm/sunxi/Makefile | 7 + drivers/gpu/drm/sunxi/de2_crtc.c | 475 +++++++++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 63 +++ drivers/gpu/drm/sunxi/de2_de.c | 591 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 47 ++ drivers/gpu/drm/sunxi/de2_drv.c | 378 +++++++++++++ drivers/gpu/drm/sunxi/de2_plane.c | 119 +++++ 11 files changed, 1787 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sunxi-de2.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/sunxi-de2.txt b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt new file mode 100644 index 0000000..f9cd67a --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt @@ -0,0 +1,83 @@ +Allwinner sunxi Display Engine 2 subsystem +==========================================
+The sunxi DE2 subsystem contains a display controller (DE2),
sunxi is a made up name, and doesn't really mean anything. You can call it either sun8i (because it was introduced in that family).
OK.
+one or two LCD controllers (TCON) and their external interfaces.
+Display controller +==================
+Required properties:
+- compatible: value should be one of the following
"allwinner,sun8i-a83t-display-engine"
"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
We've been calling them bus and mod.
I can understand "bus" (which is better than "apb"), but why "mod"?
+- resets: phandle to the reset of the device
+- ports: phandle's to the LCD ports
Please use the OF graph.
These ports are references to the graph of nodes. See http://www.kernelhub.org/?msg=911825&p=2
[snip]
diff --git a/drivers/gpu/drm/sunxi/de2_crtc.c b/drivers/gpu/drm/sunxi/de2_crtc.c new file mode 100644 index 0000000..dae0fab --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_crtc.c @@ -0,0 +1,475 @@ +/*
- 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 <linux/of_irq.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(14)
- u32 gint1;
- u32 dum0[13];
- u32 tcon0_ctl; /* 0x40 */
+#define TCON0_CTL_TCON_En BIT(31)
- u32 dum1[19];
- u32 tcon1_ctl; /* 0x90 */
+#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; /* 0xb0 */
- u32 dum3[15];
- u32 io_pol; /* 0xf0 */
+#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; /* 0x100 */
+#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; /* 0x200 */
- u32 dum7[63];
- u32 fill_ctl; /* 0x300 */
- u32 fill_start0;
- u32 fill_end0;
- u32 fill_data0;
+};
Please use defines instead of the structures.
I think that structures are more readable.
+#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 */ +static void de2_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
+{
- struct drm_pending_vblank_event *event = crtc->state->event;
- if (event) {
crtc->state->event = NULL;
spin_lock_irq(&crtc->dev->event_lock);
if (drm_crtc_vblank_get(crtc) == 0)
drm_crtc_arm_vblank_event(crtc, event);
else
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irq(&crtc->dev->event_lock);
- }
+}
+static irqreturn_t de2_lcd_irq(int irq, void *dev_id) +{
- struct lcd *lcd = (struct lcd *) dev_id;
- u32 isr;
- isr = tcon_read(lcd->mmio, gint0);
- drm_crtc_handle_vblank(&lcd->crtc);
- tcon_write(lcd->mmio, gint0, isr & ~TCON_GINT0_TCON1_Vb_Int_Flag);
- return IRQ_HANDLED;
+}
+int de2_enable_vblank(struct drm_device *drm, unsigned crtc) +{
- struct priv *priv = drm->dev_private;
- struct lcd *lcd = priv->lcds[crtc];
- tcon_write(lcd->mmio, gint0,
tcon_read(lcd->mmio, gint0) |
TCON_GINT0_TCON1_Vb_Int_En);
That's a weird indentation
- return 0;
+}
+void de2_disable_vblank(struct drm_device *drm, unsigned crtc) +{
- struct priv *priv = drm->dev_private;
- struct lcd *lcd = priv->lcds[crtc];
- tcon_write(lcd->mmio, gint0,
tcon_read(lcd->mmio, gint0) &
~TCON_GINT0_TCON1_Vb_Int_En);
+}
+/* panel functions */
Panel functions? In the CRTC driver?
Yes, dumb panel.
+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;
- 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);
+}
Some comments here would be nice, there's a lot of non trivial things.
... and no documentation. I just set the values I saw in the I/O memory when running the legacy driver.
+static void de2_crtc_enable(struct drm_crtc *crtc) +{
- struct lcd *lcd = crtc_to_lcd(crtc);
- struct drm_display_mode *mode = &crtc->mode;
- DRM_DEBUG_DRIVER("\n");
Log something useful, or don't.
This was useful when the driver was crashing only God knew where.
- clk_set_rate(lcd->clk, mode->clock * 1000);
- de2_set_frame_timings(lcd);
- tcon_write(lcd->mmio, tcon1_ctl,
tcon_read(lcd->mmio, tcon1_ctl) | TCON1_CTL_TCON_En);
- de2_de_panel_init(lcd->priv, lcd->num, mode);
panel_init in the CRTC enable? Shouldn't that be in the panel driver? or at least the encoder?
I will change to 'dumb panel'.
- drm_mode_debug_printmodeline(mode);
This is already printed by the core.
Not at this time.
[snip]
+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, irq, 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;
What do you need this number for?
It permits access to the overlay planes in the DE2.
- 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"); /* optional */
Having some kind of error checking would still be nice.
And cancel the device creation?
- 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_optional(dev, NULL);
Ditto.
- irq = irq_of_parse_and_map(np, 0);
- if (irq <= 0 || irq == NO_IRQ) {
dev_err(dev, "unable to get irq lcd %d\n", id);
ret = -EINVAL;
goto err;
- }
You can use platform_get_irq for that.
Right. Thanks.
- if (!IS_ERR(lcd->rstc)) {
ret = reset_control_deassert(lcd->rstc);
if (ret) {
dev_err(dev, "reset deassert err %d\n", ret);
goto err;
}
- }
- if (!IS_ERR(lcd->gate)) {
ret = clk_prepare_enable(lcd->gate);
if (ret)
goto err2;
- }
- ret = clk_prepare_enable(lcd->clk);
- if (ret)
goto err2;
Is there any reason not to do that in the enable / disable? Leaving clocks running while the device has no guarantee that it's going to be used seems like a waste of resources.
If the machine does not need video (network server, router..), it is simpler to prevent the video driver to be loaded (DT, module black list...).
- de2_tcon_init(lcd);
- ret = devm_request_irq(dev, irq, de2_lcd_irq, 0,
lcd->name, lcd);
- if (ret < 0) {
dev_err(dev, "unable to request irq %d\n", irq);
goto err2;
- }
- return component_add(dev, &de2_lcd_ops);
+err2:
- if (!IS_ERR_OR_NULL(lcd->rstc))
reset_control_assert(lcd->rstc);
- clk_disable_unprepare(lcd->gate);
- clk_disable_unprepare(lcd->clk);
+err:
- 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-a83t-lcd", },
- { }
+};
+struct platform_driver de2_lcd_platform_driver = {
- .probe = de2_lcd_probe,
- .remove = de2_lcd_remove,
- .driver = {
.name = "sunxi-de2-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..efbe45d --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_crtc.h @@ -0,0 +1,63 @@ +#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;
+enum de2_plane2 {
- DE2_PRIMARY_PLANE,
- DE2_CURSOR_PLANE,
- DE2_VI_PLANE,
- DE2_N_PLANES,
+}; +struct lcd {
- void __iomem *mmio;
- struct device *dev;
- struct drm_crtc crtc;
- struct priv *priv; /* DRM/DE private data */
- short num; /* LCD number in hardware */
- short crtc_idx; /* CRTC index in drm */
- struct clk *clk;
- struct clk *gate;
- struct reset_control *rstc;
- char name[16];
- struct drm_pending_vblank_event *event;
- struct drm_plane planes[DE2_N_PLANES];
+};
+#define crtc_to_lcd(x) container_of(x, struct lcd, crtc)
+/* 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);
+void de2_de_plane_disable(struct priv *priv,
int lcd_num, int plane_ix);
+void de2_de_plane_update(struct priv *priv,
int lcd_num, int plane_ix,
struct drm_plane_state *state,
struct drm_plane_state *old_state);
Does it need to be exported?
I don't understand the question.
+/* in de2_plane.c */ +int de2_plane_init(struct drm_device *drm, struct lcd *lcd);
+#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..0d8cb62 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_de.c @@ -0,0 +1,591 @@ +/*
- ALLWINNER DRM driver - Display Engine 2
- Copyright (C) 2016 Jean-Francois Moine moinejf@free.fr
- Copyright (c) 2016 Allwinnertech Co., Ltd.
- 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 <drm/drm_gem_cma_helper.h>
+#include "de2_drm.h" +#include "de2_crtc.h"
+static DEFINE_SPINLOCK(de_lock);
+#define DE_CLK_RATE_A83T 504000000 /* pll-de */ +#define DE_CLK_RATE_H3 432000000 /* de */
This can be set in the DT.
That's what I had in first releases, but, recently, I saw a 'set parent' code in a clock driver, because 'it is not the plan to expose' some clocks in the DT. I am glad to move these clock rate settings back to the DT.
+/* 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 0x30000 /* GSUs */ +#define DE_MUX_GSU2_REGS 0x40000 +#define DE_MUX_GSU3_REGS 0x50000 +#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/SMBL */
+/* 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]; /* (end of 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]; /* c0 */
- u32 dum3[4];
- u32 ck_min[4]; /* e0 */
- 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_ui_sel BIT(15) +#define VI_CFG_ATTR_top_down BIT(23)
u32 size;
u32 coord;
+#define VI_N_PLANES 3
u32 pitch[VI_N_PLANES];
u32 top_laddr[VI_N_PLANES];
u32 bot_laddr[VI_N_PLANES];
- } cfg[4];
- u32 fcolor[4]; /* c0 */
- u32 top_haddr[VI_N_PLANES]; /* d0 */
- u32 bot_haddr[VI_N_PLANES]; /* 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 */
+};
Please use defines instead of the structures.
Juggling with data in arrays is painful when the offsets are defined by defines, and structures are so much readable.
+/* coordinates and sizes */ +#define XY(x, y) (((y) << 16) | (x)) +#define WH(w, h) (((h - 1) << 16) | (w - 1))
+/* UI video formats */ +#define DE2_FORMAT_ARGB_8888 0 +#define DE2_FORMAT_BGRA_8888 3 +#define DE2_FORMAT_XRGB_8888 4 +#define DE2_FORMAT_RGB_888 8 +#define DE2_FORMAT_BGR_888 9
+/* VI video formats */ +#define DE2_FORMAT_YUV422_I_YVYU 1 /* Y-V-Y-U */ +#define DE2_FORMAT_YUV422_I_UYVY 2 /* U-Y-V-Y */ +#define DE2_FORMAT_YUV422_I_YUYV 3 /* Y-U-Y-V */ +#define DE2_FORMAT_YUV422_P 6 /* YYYY UU VV planar */ +#define DE2_FORMAT_YUV420_P 10 /* YYYY U V planar */
+#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))
+#define vi_read(base, member) \
- readl_relaxed(base + offsetof(struct de_vi, member))
+#define vi_write(base, member, data) \
- writel_relaxed(data, base + offsetof(struct de_vi, member))
+static const struct {
- char chan;
- char layer;
- char pipe;
+} plane2layer[DE2_N_PLANES] = {
- [DE2_PRIMARY_PLANE] = {0, 0, 0},
- [DE2_CURSOR_PLANE] = {1, 0, 1},
- [DE2_VI_PLANE] = {0, 1, 0},
+};
Comments?
This primary plane is channel 0 (VI), layer 0, pipe 0 cursor plane is channel 1 (UI), layer 0, pipe 1 overlay plane is channel 0 (VI), layer 1, pipe 0 or the full explanation: Constraints: The VI channels can do RGB or YUV, while UI channels can do RGB only. The LCD 0 has 1 VI channel and 4 UI channels, while LCD 1 has only 1 VI channel and 1 UI channel. The cursor must go to a channel bigger than the primary channel, otherwise it is not transparent. First try: Letting the primary plane (usually RGB) in the 2nd channel (UI), as this is done in the legacy driver, asks for the cursor to go to the next channel (UI), but this one does not exist in LCD1. Retained layout: So, we must use only 2 channels for the same behaviour on LCD0 (H3) and LCD1 (A83T) The retained combination is: - primary plane in the first channel (VI), - cursor plane inthe 2nd channel (UI), and - overlay plane in the 1st channel (VI).
Note that there could be 3 overlay planes (a channel has 4 layers), but I am not sure that the A83T or the H3 could support 3 simultaneous video streams...
+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);
+}
+static void de_lcd_select(struct priv *priv,
int lcd_num,
void __iomem *mux_o)
+{
- u32 data;
- /* select the LCD */
- data = de_read(priv, DE_SEL_REG);
- data &= ~1;
- de_write(priv, DE_SEL_REG, data);
- /* double register switch */
- glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1);
+}
+void de2_de_plane_update(struct priv *priv,
int lcd_num, int plane_ix,
struct drm_plane_state *state,
struct drm_plane_state *old_state)
+{
- struct drm_framebuffer *fb = state->fb;
- struct drm_gem_cma_object *gem;
- void __iomem *mux_o = priv->mmio;
- void __iomem *chan_o;
- u32 size = WH(state->crtc_w, state->crtc_h);
- u32 coord;
- u32 screen_size;
- u32 data, fcolor;
- u32 ui_sel, alpha_glob;
- int chan, layer, x, y;
- unsigned fmt;
- unsigned long flags;
- chan = plane2layer[plane_ix].chan;
- layer = plane2layer[plane_ix].layer;
- 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;
- x = state->crtc_x >= 0 ? state->crtc_x : 0;
- y = state->crtc_y >= 0 ? state->crtc_y : 0;
- coord = XY(x, y);
- /* handle the cursor move */
- if (plane_ix == DE2_CURSOR_PLANE
&& fb == old_state->fb) {
spin_lock_irqsave(&de_lock, flags);
de_lcd_select(priv, lcd_num, mux_o);
if (chan == 0)
vi_write(chan_o, cfg[layer].coord, coord);
else
ui_write(chan_o, cfg[layer].coord, coord);
spin_unlock_irqrestore(&de_lock, flags);
return;
- }
- gem = drm_fb_cma_get_gem_obj(fb, 0);
- ui_sel = alpha_glob = 0;
- switch (fb->pixel_format) {
- case DRM_FORMAT_ARGB8888:
fmt = DE2_FORMAT_ARGB_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_BGRA8888:
fmt = DE2_FORMAT_BGRA_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_XRGB8888:
fmt = DE2_FORMAT_XRGB_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
alpha_glob = (1 << UI_CFG_ATTR_alpmod_SHIFT) |
(0xff << UI_CFG_ATTR_alpha_SHIFT);
break;
- case DRM_FORMAT_RGB888:
fmt = DE2_FORMAT_RGB_888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_BGR888:
fmt = DE2_FORMAT_BGR_888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_YUYV:
fmt = DE2_FORMAT_YUV422_I_YUYV;
break;
- case DRM_FORMAT_YVYU:
fmt = DE2_FORMAT_YUV422_I_YVYU;
break;
- case DRM_FORMAT_YUV422:
fmt = DE2_FORMAT_YUV422_P;
break;
- case DRM_FORMAT_YUV420:
fmt = DE2_FORMAT_YUV420_P;
break;
- case DRM_FORMAT_UYVY:
fmt = DE2_FORMAT_YUV422_I_UYVY;
break;
- default:
pr_err("format %.4s not yet treated\n",
(char *) &fb->pixel_format);
return;
- }
- spin_lock_irqsave(&de_lock, flags);
- screen_size = plane_ix == DE2_PRIMARY_PLANE ?
size :
glb_read(mux_o + DE_MUX_GLB_REGS, size);
- /* prepare the activation of alpha blending (1 bit per plane) */
- fcolor = bld_read(mux_o + DE_MUX_BLD_REGS, fcolor_ctl)
| (0x100 << plane2layer[plane_ix].pipe);
- de_lcd_select(priv, lcd_num, mux_o);
- if (chan == 0) { /* VI channel */
int i;
data = VI_CFG_ATTR_en | (fmt << VI_CFG_ATTR_fmt_SHIFT) |
ui_sel;
vi_write(chan_o, cfg[layer].attr, data);
vi_write(chan_o, cfg[layer].size, size);
vi_write(chan_o, cfg[layer].coord, coord);
for (i = 0; i < VI_N_PLANES; i++) {
vi_write(chan_o, cfg[layer].pitch[i],
fb->pitches[i] ? fb->pitches[i] :
fb->pitches[0]);
vi_write(chan_o, cfg[layer].top_laddr[i],
gem->paddr + fb->offsets[i]);
vi_write(chan_o, fcolor[layer], 0xff000000);
}
if (layer == 0)
vi_write(chan_o, ovl_size[0], screen_size);
- } else { /* UI channel */
data = UI_CFG_ATTR_en | (fmt << UI_CFG_ATTR_fmt_SHIFT) |
alpha_glob;
ui_write(chan_o, cfg[layer].attr, data);
ui_write(chan_o, cfg[layer].size, size);
ui_write(chan_o, cfg[layer].coord, coord);
ui_write(chan_o, cfg[layer].pitch, fb->pitches[0]);
ui_write(chan_o, cfg[layer].top_laddr,
gem->paddr + fb->offsets[0]);
if (layer == 0)
ui_write(chan_o, ovl_size, screen_size);
- }
- bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl, fcolor);
- spin_unlock_irqrestore(&de_lock, flags);
+}
Splitting that into functions would make it a bit more trivial and readable.
Not sure: there is a lot of common data and different I/O accesses.
+void de2_de_plane_disable(struct priv *priv,
int lcd_num, int plane_ix)
+{
- void __iomem *mux_o = priv->mmio;
- void __iomem *chan_o;
- u32 fcolor;
- int chan, layer, chan_disable = 0;
- unsigned long flags;
- chan = plane2layer[plane_ix].chan;
- layer = plane2layer[plane_ix].layer;
- 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;
- /* (only 2 layers) */
- if (chan == 0) {
if (vi_read(chan_o, cfg[1 - layer].attr) == 0)
chan_disable = 1;
- } else {
if (ui_read(chan_o, cfg[1 - layer].attr) == 0)
chan_disable = 1;
- }
- spin_lock_irqsave(&de_lock, flags);
- fcolor = bld_read(mux_o + DE_MUX_BLD_REGS, fcolor_ctl);
- de_lcd_select(priv, lcd_num, mux_o);
- if (chan == 0)
vi_write(chan_o, cfg[layer].attr, 0);
- else
ui_write(chan_o, cfg[layer].attr, 0);
- if (chan_disable)
bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl,
fcolor & ~(0x100 << plane2layer[plane_ix].pipe));
- spin_unlock_irqrestore(&de_lock, flags);
+}
Can't you just disable it?
Which 'it'? A layer must be disabled and it is not useful to let the DE2 processor to scan a pipe (channel) without any layer.
+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);
- unsigned i;
- 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);
- de_lcd_select(priv, lcd_num, mux_o);
- glb_write(mux_o + DE_MUX_GLB_REGS, size, size);
- /* set alpha blending */
- for (i = 0; i < 4; i++) {
bld_write(mux_o + DE_MUX_BLD_REGS, attr[i].fcolor, 0xff000000);
bld_write(mux_o + DE_MUX_BLD_REGS, attr[i].insize, size);
- }
- bld_write(mux_o + DE_MUX_BLD_REGS, output_size, size);
- 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_enable(struct priv *priv, int lcd_num) +{
[snip]
+}
+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);
+}
+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"); /* optional */
Error checking
- 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_optional(dev, NULL);
- if (!IS_ERR(priv->rstc)) {
ret = reset_control_deassert(priv->rstc);
if (ret) {
dev_err(dev, "reset deassert err %d\n", ret);
return ret;
}
- }
- if (!IS_ERR(priv->gate)) {
ret = clk_prepare_enable(priv->gate);
if (ret)
goto err_gate;
- }
- ret = clk_prepare_enable(priv->clk);
- if (ret)
goto err_enable;
- if (priv->soc_type == SOC_A83T)
clk_set_rate(priv->clk, DE_CLK_RATE_A83T);
- else
clk_set_rate(priv->clk, DE_CLK_RATE_H3);
- /* set the A83T clock divider = 500 / 250 */
- if (priv->soc_type == SOC_A83T)
de_write(priv, DE_DIV_REG,
0x00000011); /* div = 2 for both LCDs */
- return 0;
+err_enable:
- clk_disable_unprepare(priv->gate);
+err_gate:
- if (!IS_ERR(priv->rstc))
reset_control_assert(priv->rstc);
- return ret;
+}
+void de2_de_cleanup(struct priv *priv) +{
- clk_disable_unprepare(priv->clk);
- clk_disable_unprepare(priv->gate);
- if (!IS_ERR(priv->rstc))
reset_control_assert(priv->rstc);
+}
[snip]
diff --git a/drivers/gpu/drm/sunxi/de2_drv.c b/drivers/gpu/drm/sunxi/de2_drv.c new file mode 100644 index 0000000..5daa15c --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_drv.c
[snip]
+#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)
+};
Why do you need that? How did you test it? There's no runtime_pm calls in your kernel.
That's not tested. I will remove this.
[snip]
+static struct platform_driver de2_drm_platform_driver = {
- .probe = de2_drm_probe,
- .remove = de2_drm_remove,
- .driver = {
.name = DRIVER_NAME,
.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; */
That's useless.
Right, but it seems that some people don't know how to debug a DRM driver. This is only a reminder.
- 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;
+}
And that really shouldn't be done that way.
May you explain?
+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 v2"); diff --git a/drivers/gpu/drm/sunxi/de2_plane.c b/drivers/gpu/drm/sunxi/de2_plane.c new file mode 100644 index 0000000..b338684 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_plane.c @@ -0,0 +1,119 @@ +/*
- 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 "de2_drm.h" +#include "de2_crtc.h"
+/* plane formats */ +static const uint32_t ui_formats[] = {
- DRM_FORMAT_ARGB8888,
- DRM_FORMAT_BGRA8888,
- DRM_FORMAT_XRGB8888,
- DRM_FORMAT_RGB888,
- DRM_FORMAT_BGR888,
+};
+static const uint32_t vi_formats[] = {
- DRM_FORMAT_XRGB8888,
- DRM_FORMAT_YUYV,
- DRM_FORMAT_YVYU,
- DRM_FORMAT_YUV422,
- DRM_FORMAT_YUV420,
- DRM_FORMAT_UYVY,
- DRM_FORMAT_BGRA8888,
- DRM_FORMAT_RGB888,
- DRM_FORMAT_BGR888,
+};
[snip]
+static int de2_one_plane_init(struct drm_device *drm,
struct drm_plane *plane,
int type, int possible_crtcs,
const uint32_t *formats,
int nformats)
+{
- int ret;
- ret = drm_universal_plane_init(drm, plane, possible_crtcs,
&plane_funcs,
formats, nformats, type, NULL);
- if (ret >= 0)
drm_plane_helper_add(plane, &plane_helper_funcs);
- return ret;
+}
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd) +{
- int ret, possible_crtcs = 1 << lcd->crtc_idx;
- ret = de2_one_plane_init(drm, &lcd->planes[DE2_PRIMARY_PLANE],
DRM_PLANE_TYPE_PRIMARY, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
- if (ret >= 0)
ret = de2_one_plane_init(drm, &lcd->planes[DE2_CURSOR_PLANE],
DRM_PLANE_TYPE_CURSOR, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
Nothing looks really special about that cursor plane. Any reasion not to make it an overlay?
As explained above (channel/layer/pipe plane definitions), the cursor cannot go in a channel lower or equal to the one of the primary plane. Then, it must be known and, so, have an explicit plane.
On 25/10/16 15:14, Jean-Francois Moine wrote:
On Mon, 24 Oct 2016 16:04:19 +0200 Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
Hi Maxime,
On Fri, Oct 21, 2016 at 09:26:18AM +0200, Jean-Francois Moine wrote:
Allwinner's recent SoCs, as A64, A83T and H3, contain a new display engine, DE2. This patch adds a DRM video driver for this device.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Output from checkpatch: total: 0 errors, 20 warnings, 83 checks, 1799 lines checked
.../bindings/display/sunxi/sunxi-de2.txt | 83 +++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 21 + drivers/gpu/drm/sunxi/Makefile | 7 + drivers/gpu/drm/sunxi/de2_crtc.c | 475 +++++++++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 63 +++ drivers/gpu/drm/sunxi/de2_de.c | 591 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 47 ++ drivers/gpu/drm/sunxi/de2_drv.c | 378 +++++++++++++ drivers/gpu/drm/sunxi/de2_plane.c | 119 +++++ 11 files changed, 1787 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sunxi-de2.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/sunxi-de2.txt b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt new file mode 100644 index 0000000..f9cd67a --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt @@ -0,0 +1,83 @@ +Allwinner sunxi Display Engine 2 subsystem +==========================================
+The sunxi DE2 subsystem contains a display controller (DE2),
sunxi is a made up name, and doesn't really mean anything. You can call it either sun8i (because it was introduced in that family).
OK.
+one or two LCD controllers (TCON) and their external interfaces.
+Display controller +==================
+Required properties:
+- compatible: value should be one of the following
"allwinner,sun8i-a83t-display-engine"
"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
We've been calling them bus and mod.
I can understand "bus" (which is better than "apb"), but why "mod"?
+- resets: phandle to the reset of the device
+- ports: phandle's to the LCD ports
Please use the OF graph.
These ports are references to the graph of nodes. See http://www.kernelhub.org/?msg=911825&p=2
[snip]
diff --git a/drivers/gpu/drm/sunxi/de2_crtc.c b/drivers/gpu/drm/sunxi/de2_crtc.c new file mode 100644 index 0000000..dae0fab --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_crtc.c @@ -0,0 +1,475 @@ +/*
- 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 <linux/of_irq.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(14)
- u32 gint1;
- u32 dum0[13];
- u32 tcon0_ctl; /* 0x40 */
+#define TCON0_CTL_TCON_En BIT(31)
- u32 dum1[19];
- u32 tcon1_ctl; /* 0x90 */
+#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; /* 0xb0 */
- u32 dum3[15];
- u32 io_pol; /* 0xf0 */
+#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; /* 0x100 */
+#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; /* 0x200 */
- u32 dum7[63];
- u32 fill_ctl; /* 0x300 */
- u32 fill_start0;
- u32 fill_end0;
- u32 fill_data0;
+};
Please use defines instead of the structures.
I think that structures are more readable.
I think for the kernel we don't use C structs to model register frames. The main argument against it is that putting them into a structure puts the actual offset into the hands of the compiler, which is free to insert padding and alignment - within the rules of the ABI. This happens to work when we use 32-bit registers and maybe char fillers only. Most ABIs seem to agree on this part, but there is no guarantee. In fact all those various ABIs used by the kernel could have subtle differences between architectures (for instance for alignment of 64-bit members), so we produce potentially non-portable code.
Also I find it actually harder to read, since the manual refers to register offset addresses, which makes it hard to match with the code, especially if we deviate with the register naming. The fact that we have occasional *comments* to denote the actual offset is a hint that something is sub-optimal. Also having these dummy fillers is really error prone once we insert new registers, as the fill value has to be adjusted accordingly.
So can we stick with what the kernel uses elsewhere and don't pretend that because the current GCC compiles that fine on ARM it will be good forever?
Thanks! Andre.
On Tue, Oct 25, 2016 at 04:14:41PM +0200, Jean-Francois Moine wrote:
+Display controller +==================
+Required properties:
+- compatible: value should be one of the following
"allwinner,sun8i-a83t-display-engine"
"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
We've been calling them bus and mod.
I can understand "bus" (which is better than "apb"), but why "mod"?
Allwinner has been calling the clocks that are supposed to generate the external signals (depending on where you were looking) module or mod clocks (which is also why we have mod in the clock compatibles). The module 1 clocks being used for the audio and the module 0 for the rest (SPI, MMC, NAND, display, etc.)
+- resets: phandle to the reset of the device
+- ports: phandle's to the LCD ports
Please use the OF graph.
These ports are references to the graph of nodes. See http://www.kernelhub.org/?msg=911825&p=2
In an OF-graph, your phandle to the LCD controller would be replaced by an output endpoint.
[snip]
+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(14)
- u32 gint1;
- u32 dum0[13];
- u32 tcon0_ctl; /* 0x40 */
+#define TCON0_CTL_TCON_En BIT(31)
- u32 dum1[19];
- u32 tcon1_ctl; /* 0x90 */
+#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; /* 0xb0 */
- u32 dum3[15];
- u32 io_pol; /* 0xf0 */
+#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; /* 0x100 */
+#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; /* 0x200 */
- u32 dum7[63];
- u32 fill_ctl; /* 0x300 */
- u32 fill_start0;
- u32 fill_end0;
- u32 fill_data0;
+};
Please use defines instead of the structures.
I think that structures are more readable.
That's not really the point. No one in the kernel uses it (and even you use defines for registers offset in some places of that patch). And then you have André arguments.
+void de2_disable_vblank(struct drm_device *drm, unsigned crtc) +{
- struct priv *priv = drm->dev_private;
- struct lcd *lcd = priv->lcds[crtc];
- tcon_write(lcd->mmio, gint0,
tcon_read(lcd->mmio, gint0) &
~TCON_GINT0_TCON1_Vb_Int_En);
+}
+/* panel functions */
Panel functions? In the CRTC driver?
Yes, dumb panel.
What do you mean by that? Using a Parallel/RGB interface?
+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;
- 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);
+}
Some comments here would be nice, there's a lot of non trivial things.
... and no documentation. I just set the values I saw in the I/O memory when running the legacy driver.
That's bad (but definitely not your fault) :/
- lcd->gate = devm_clk_get(dev, "gate"); /* optional */
Having some kind of error checking would still be nice.
And cancel the device creation?
Well, yes. If it cannot enable it, it will at best not work properly, at worst be reset, so it won't be very useful anyway.
- if (!IS_ERR(lcd->rstc)) {
ret = reset_control_deassert(lcd->rstc);
if (ret) {
dev_err(dev, "reset deassert err %d\n", ret);
goto err;
}
- }
- if (!IS_ERR(lcd->gate)) {
ret = clk_prepare_enable(lcd->gate);
if (ret)
goto err2;
- }
- ret = clk_prepare_enable(lcd->clk);
- if (ret)
goto err2;
Is there any reason not to do that in the enable / disable? Leaving clocks running while the device has no guarantee that it's going to be used seems like a waste of resources.
If the machine does not need video (network server, router..), it is simpler to prevent the video driver to be loaded (DT, module black list...).
You might not have control on any of it, or you might just have no monitor attached for example. Recompiling the kernel or updating the DT when you want to plug an HDMI monitor seems like a poor UX :)
+static const struct {
- char chan;
- char layer;
- char pipe;
+} plane2layer[DE2_N_PLANES] = {
- [DE2_PRIMARY_PLANE] = {0, 0, 0},
- [DE2_CURSOR_PLANE] = {1, 0, 1},
- [DE2_VI_PLANE] = {0, 1, 0},
+};
Comments?
This primary plane is channel 0 (VI), layer 0, pipe 0 cursor plane is channel 1 (UI), layer 0, pipe 1 overlay plane is channel 0 (VI), layer 1, pipe 0 or the full explanation: Constraints: The VI channels can do RGB or YUV, while UI channels can do RGB only. The LCD 0 has 1 VI channel and 4 UI channels, while LCD 1 has only 1 VI channel and 1 UI channel. The cursor must go to a channel bigger than the primary channel, otherwise it is not transparent. First try: Letting the primary plane (usually RGB) in the 2nd channel (UI), as this is done in the legacy driver, asks for the cursor to go to the next channel (UI), but this one does not exist in LCD1. Retained layout: So, we must use only 2 channels for the same behaviour on LCD0 (H3) and LCD1 (A83T) The retained combination is: - primary plane in the first channel (VI), - cursor plane inthe 2nd channel (UI), and - overlay plane in the 1st channel (VI).
Note that there could be 3 overlay planes (a channel has 4 layers), but I am not sure that the A83T or the H3 could support 3 simultaneous video streams...
Do you know if the pipe works in the old display engine?
Especially about the two-steps composition that wouldn't allow you to have alpha on all the planes?
If it is similar, I think hardcoding the pipe number is pretty bad, because that would restrict the combination of planes and formats, while some other might have worked.
+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);
+}
+static void de_lcd_select(struct priv *priv,
int lcd_num,
void __iomem *mux_o)
+{
- u32 data;
- /* select the LCD */
- data = de_read(priv, DE_SEL_REG);
- data &= ~1;
- de_write(priv, DE_SEL_REG, data);
- /* double register switch */
- glb_write(mux_o + DE_MUX_GLB_REGS, dbuff, 1);
+}
+void de2_de_plane_update(struct priv *priv,
int lcd_num, int plane_ix,
struct drm_plane_state *state,
struct drm_plane_state *old_state)
+{
- struct drm_framebuffer *fb = state->fb;
- struct drm_gem_cma_object *gem;
- void __iomem *mux_o = priv->mmio;
- void __iomem *chan_o;
- u32 size = WH(state->crtc_w, state->crtc_h);
- u32 coord;
- u32 screen_size;
- u32 data, fcolor;
- u32 ui_sel, alpha_glob;
- int chan, layer, x, y;
- unsigned fmt;
- unsigned long flags;
- chan = plane2layer[plane_ix].chan;
- layer = plane2layer[plane_ix].layer;
- 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;
- x = state->crtc_x >= 0 ? state->crtc_x : 0;
- y = state->crtc_y >= 0 ? state->crtc_y : 0;
- coord = XY(x, y);
- /* handle the cursor move */
- if (plane_ix == DE2_CURSOR_PLANE
&& fb == old_state->fb) {
spin_lock_irqsave(&de_lock, flags);
de_lcd_select(priv, lcd_num, mux_o);
if (chan == 0)
vi_write(chan_o, cfg[layer].coord, coord);
else
ui_write(chan_o, cfg[layer].coord, coord);
spin_unlock_irqrestore(&de_lock, flags);
return;
- }
- gem = drm_fb_cma_get_gem_obj(fb, 0);
- ui_sel = alpha_glob = 0;
- switch (fb->pixel_format) {
- case DRM_FORMAT_ARGB8888:
fmt = DE2_FORMAT_ARGB_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_BGRA8888:
fmt = DE2_FORMAT_BGRA_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_XRGB8888:
fmt = DE2_FORMAT_XRGB_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
alpha_glob = (1 << UI_CFG_ATTR_alpmod_SHIFT) |
(0xff << UI_CFG_ATTR_alpha_SHIFT);
break;
- case DRM_FORMAT_RGB888:
fmt = DE2_FORMAT_RGB_888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_BGR888:
fmt = DE2_FORMAT_BGR_888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_YUYV:
fmt = DE2_FORMAT_YUV422_I_YUYV;
break;
- case DRM_FORMAT_YVYU:
fmt = DE2_FORMAT_YUV422_I_YVYU;
break;
- case DRM_FORMAT_YUV422:
fmt = DE2_FORMAT_YUV422_P;
break;
- case DRM_FORMAT_YUV420:
fmt = DE2_FORMAT_YUV420_P;
break;
- case DRM_FORMAT_UYVY:
fmt = DE2_FORMAT_YUV422_I_UYVY;
break;
- default:
pr_err("format %.4s not yet treated\n",
(char *) &fb->pixel_format);
return;
- }
- spin_lock_irqsave(&de_lock, flags);
- screen_size = plane_ix == DE2_PRIMARY_PLANE ?
size :
glb_read(mux_o + DE_MUX_GLB_REGS, size);
- /* prepare the activation of alpha blending (1 bit per plane) */
- fcolor = bld_read(mux_o + DE_MUX_BLD_REGS, fcolor_ctl)
| (0x100 << plane2layer[plane_ix].pipe);
- de_lcd_select(priv, lcd_num, mux_o);
- if (chan == 0) { /* VI channel */
int i;
data = VI_CFG_ATTR_en | (fmt << VI_CFG_ATTR_fmt_SHIFT) |
ui_sel;
vi_write(chan_o, cfg[layer].attr, data);
vi_write(chan_o, cfg[layer].size, size);
vi_write(chan_o, cfg[layer].coord, coord);
for (i = 0; i < VI_N_PLANES; i++) {
vi_write(chan_o, cfg[layer].pitch[i],
fb->pitches[i] ? fb->pitches[i] :
fb->pitches[0]);
vi_write(chan_o, cfg[layer].top_laddr[i],
gem->paddr + fb->offsets[i]);
vi_write(chan_o, fcolor[layer], 0xff000000);
}
if (layer == 0)
vi_write(chan_o, ovl_size[0], screen_size);
- } else { /* UI channel */
data = UI_CFG_ATTR_en | (fmt << UI_CFG_ATTR_fmt_SHIFT) |
alpha_glob;
ui_write(chan_o, cfg[layer].attr, data);
ui_write(chan_o, cfg[layer].size, size);
ui_write(chan_o, cfg[layer].coord, coord);
ui_write(chan_o, cfg[layer].pitch, fb->pitches[0]);
ui_write(chan_o, cfg[layer].top_laddr,
gem->paddr + fb->offsets[0]);
if (layer == 0)
ui_write(chan_o, ovl_size, screen_size);
- }
- bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl, fcolor);
- spin_unlock_irqrestore(&de_lock, flags);
+}
Splitting that into functions would make it a bit more trivial and readable.
Not sure: there is a lot of common data and different I/O accesses.
You could still have different ones to set the buffers, formats and coordinates for example.
+void de2_de_plane_disable(struct priv *priv,
int lcd_num, int plane_ix)
+{
- void __iomem *mux_o = priv->mmio;
- void __iomem *chan_o;
- u32 fcolor;
- int chan, layer, chan_disable = 0;
- unsigned long flags;
- chan = plane2layer[plane_ix].chan;
- layer = plane2layer[plane_ix].layer;
- 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;
- /* (only 2 layers) */
- if (chan == 0) {
if (vi_read(chan_o, cfg[1 - layer].attr) == 0)
chan_disable = 1;
- } else {
if (ui_read(chan_o, cfg[1 - layer].attr) == 0)
chan_disable = 1;
- }
- spin_lock_irqsave(&de_lock, flags);
- fcolor = bld_read(mux_o + DE_MUX_BLD_REGS, fcolor_ctl);
- de_lcd_select(priv, lcd_num, mux_o);
- if (chan == 0)
vi_write(chan_o, cfg[layer].attr, 0);
- else
ui_write(chan_o, cfg[layer].attr, 0);
- if (chan_disable)
bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl,
fcolor & ~(0x100 << plane2layer[plane_ix].pipe));
- spin_unlock_irqrestore(&de_lock, flags);
+}
Can't you just disable it?
Which 'it'? A layer must be disabled and it is not useful to let the DE2 processor to scan a pipe (channel) without any layer.
Oh, so that's what it does.
I really think that you should put as much comments as possible on what you found out working on this, especially because of the lack of documentation.
+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; */
That's useless.
Right, but it seems that some people don't know how to debug a DRM driver. This is only a reminder.
- 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;
+}
And that really shouldn't be done that way.
May you explain?
This goes against the whole idea of the device and driver model. Drivers should only register themselves, device should be created by buses (or by using some external components if the bus can't: DT, ACPI, etc.). If there's a match, you get probed.
A driver that creates its own device just to probe itself violates that.
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd) +{
- int ret, possible_crtcs = 1 << lcd->crtc_idx;
- ret = de2_one_plane_init(drm, &lcd->planes[DE2_PRIMARY_PLANE],
DRM_PLANE_TYPE_PRIMARY, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
- if (ret >= 0)
ret = de2_one_plane_init(drm, &lcd->planes[DE2_CURSOR_PLANE],
DRM_PLANE_TYPE_CURSOR, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
Nothing looks really special about that cursor plane. Any reasion not to make it an overlay?
As explained above (channel/layer/pipe plane definitions), the cursor cannot go in a channel lower or equal to the one of the primary plane. Then, it must be known and, so, have an explicit plane.
If you were to make it a plane, you could use atomic_check to check this and make sure this doesn't happen. And you would gain a generic plane that can be used for other purposes if needed.
Maxime
On Fri, 28 Oct 2016 00:03:16 +0200 Maxime Ripard maxime.ripard@free-electrons.com wrote:
On Tue, Oct 25, 2016 at 04:14:41PM +0200, Jean-Francois Moine wrote:
+Display controller +==================
+Required properties:
+- compatible: value should be one of the following
"allwinner,sun8i-a83t-display-engine"
"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
We've been calling them bus and mod.
I can understand "bus" (which is better than "apb"), but why "mod"?
Allwinner has been calling the clocks that are supposed to generate the external signals (depending on where you were looking) module or mod clocks (which is also why we have mod in the clock compatibles). The module 1 clocks being used for the audio and the module 0 for the rest (SPI, MMC, NAND, display, etc.)
I did not find any 'module' in the H3 documentation. So, is it really a good name?
+- resets: phandle to the reset of the device
+- ports: phandle's to the LCD ports
Please use the OF graph.
These ports are references to the graph of nodes. See http://www.kernelhub.org/?msg=911825&p=2
In an OF-graph, your phandle to the LCD controller would be replaced by an output endpoint.
This is the DE controller. There is no endpoint link at this level. The Device Engine just handles the planes of the LCDs, but, indeed, the LCDs must know about the DE and the DE must know about the LCDs. There are 2 ways to realize this knowledge in the DT: 1) either the DE has one or two phandle's to the LCDs, 2) or the LCDs have a phandle to the DE.
I chose the 1st way, the DE ports pointing to the endpoint of the LCDs which is part of the video link (OF-graph LCD <-> connector). It would be possible to have phandles to the LCDs themselves, but this asks for more code.
The second way is also possible, but it also complexifies a bit the exchanges DE <-> LCD.
[snip]
+struct tcon {
- u32 gctl;
+#define TCON_GCTL_TCON_En BIT(31)
[snip]
- u32 fill_ctl; /* 0x300 */
- u32 fill_start0;
- u32 fill_end0;
- u32 fill_data0;
+};
Please use defines instead of the structures.
I think that structures are more readable.
That's not really the point. No one in the kernel uses it (and even you use defines for registers offset in some places of that patch). And then you have André arguments.
I am not convinced, but I'll do as you said.
+void de2_disable_vblank(struct drm_device *drm, unsigned crtc) +{
- struct priv *priv = drm->dev_private;
- struct lcd *lcd = priv->lcds[crtc];
- tcon_write(lcd->mmio, gint0,
tcon_read(lcd->mmio, gint0) &
~TCON_GINT0_TCON1_Vb_Int_En);
+}
+/* panel functions */
Panel functions? In the CRTC driver?
Yes, dumb panel.
What do you mean by that? Using a Parallel/RGB interface?
Sorry, I though this was a well-known name. The 'dump panel' was used in the documentation of my previous ARM machine as the video frame sent to the HDMI controller. 'video_frame' is OK for you?
[snip]
- ret = clk_prepare_enable(lcd->clk);
- if (ret)
goto err2;
Is there any reason not to do that in the enable / disable? Leaving clocks running while the device has no guarantee that it's going to be used seems like a waste of resources.
If the machine does not need video (network server, router..), it is simpler to prevent the video driver to be loaded (DT, module black list...).
You might not have control on any of it, or you might just have no monitor attached for example. Recompiling the kernel or updating the DT when you want to plug an HDMI monitor seems like a poor UX :)
OK, I will check if this works.
+static const struct {
- char chan;
- char layer;
- char pipe;
+} plane2layer[DE2_N_PLANES] = {
- [DE2_PRIMARY_PLANE] = {0, 0, 0},
- [DE2_CURSOR_PLANE] = {1, 0, 1},
- [DE2_VI_PLANE] = {0, 1, 0},
+};
Comments?
This primary plane is channel 0 (VI), layer 0, pipe 0 cursor plane is channel 1 (UI), layer 0, pipe 1 overlay plane is channel 0 (VI), layer 1, pipe 0 or the full explanation: Constraints: The VI channels can do RGB or YUV, while UI channels can do RGB only. The LCD 0 has 1 VI channel and 4 UI channels, while LCD 1 has only 1 VI channel and 1 UI channel. The cursor must go to a channel bigger than the primary channel, otherwise it is not transparent. First try: Letting the primary plane (usually RGB) in the 2nd channel (UI), as this is done in the legacy driver, asks for the cursor to go to the next channel (UI), but this one does not exist in LCD1. Retained layout: So, we must use only 2 channels for the same behaviour on LCD0 (H3) and LCD1 (A83T) The retained combination is: - primary plane in the first channel (VI), - cursor plane inthe 2nd channel (UI), and - overlay plane in the 1st channel (VI).
Note that there could be 3 overlay planes (a channel has 4 layers), but I am not sure that the A83T or the H3 could support 3 simultaneous video streams...
Do you know if the pipe works in the old display engine?
Especially about the two-steps composition that wouldn't allow you to have alpha on all the planes?
If it is similar, I think hardcoding the pipe number is pretty bad, because that would restrict the combination of planes and formats, while some other might have worked.
From what I understood about the DE2, the pipes just define the priority
of the overlay channels (one pipe for one channel). With the cursor constraint, there must be at least 2 channels in order (primary, cursor). Then, with these 2 channels/pipes, there can be 6 so-called overlay planes (3 RGB/YUV and 3 RGB only). Enabling the pipes 2 and 3 (LCD 0 only) would offer 8 more planes, but RGB only. Then, it might be useful to have dynamic pipes.
[snip]
+void de2_de_plane_update(struct priv *priv,
int lcd_num, int plane_ix,
struct drm_plane_state *state,
struct drm_plane_state *old_state)
+{
- struct drm_framebuffer *fb = state->fb;
- struct drm_gem_cma_object *gem;
- void __iomem *mux_o = priv->mmio;
- void __iomem *chan_o;
- u32 size = WH(state->crtc_w, state->crtc_h);
- u32 coord;
- u32 screen_size;
- u32 data, fcolor;
- u32 ui_sel, alpha_glob;
- int chan, layer, x, y;
- unsigned fmt;
- unsigned long flags;
- chan = plane2layer[plane_ix].chan;
- layer = plane2layer[plane_ix].layer;
- 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;
- x = state->crtc_x >= 0 ? state->crtc_x : 0;
- y = state->crtc_y >= 0 ? state->crtc_y : 0;
- coord = XY(x, y);
- /* handle the cursor move */
- if (plane_ix == DE2_CURSOR_PLANE
&& fb == old_state->fb) {
spin_lock_irqsave(&de_lock, flags);
de_lcd_select(priv, lcd_num, mux_o);
if (chan == 0)
vi_write(chan_o, cfg[layer].coord, coord);
else
ui_write(chan_o, cfg[layer].coord, coord);
spin_unlock_irqrestore(&de_lock, flags);
return;
- }
- gem = drm_fb_cma_get_gem_obj(fb, 0);
- ui_sel = alpha_glob = 0;
- switch (fb->pixel_format) {
- case DRM_FORMAT_ARGB8888:
fmt = DE2_FORMAT_ARGB_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_BGRA8888:
fmt = DE2_FORMAT_BGRA_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_XRGB8888:
fmt = DE2_FORMAT_XRGB_8888;
ui_sel = VI_CFG_ATTR_ui_sel;
alpha_glob = (1 << UI_CFG_ATTR_alpmod_SHIFT) |
(0xff << UI_CFG_ATTR_alpha_SHIFT);
break;
- case DRM_FORMAT_RGB888:
fmt = DE2_FORMAT_RGB_888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_BGR888:
fmt = DE2_FORMAT_BGR_888;
ui_sel = VI_CFG_ATTR_ui_sel;
break;
- case DRM_FORMAT_YUYV:
fmt = DE2_FORMAT_YUV422_I_YUYV;
break;
- case DRM_FORMAT_YVYU:
fmt = DE2_FORMAT_YUV422_I_YVYU;
break;
- case DRM_FORMAT_YUV422:
fmt = DE2_FORMAT_YUV422_P;
break;
- case DRM_FORMAT_YUV420:
fmt = DE2_FORMAT_YUV420_P;
break;
- case DRM_FORMAT_UYVY:
fmt = DE2_FORMAT_YUV422_I_UYVY;
break;
- default:
pr_err("format %.4s not yet treated\n",
(char *) &fb->pixel_format);
return;
- }
- spin_lock_irqsave(&de_lock, flags);
- screen_size = plane_ix == DE2_PRIMARY_PLANE ?
size :
glb_read(mux_o + DE_MUX_GLB_REGS, size);
- /* prepare the activation of alpha blending (1 bit per plane) */
- fcolor = bld_read(mux_o + DE_MUX_BLD_REGS, fcolor_ctl)
| (0x100 << plane2layer[plane_ix].pipe);
- de_lcd_select(priv, lcd_num, mux_o);
- if (chan == 0) { /* VI channel */
int i;
data = VI_CFG_ATTR_en | (fmt << VI_CFG_ATTR_fmt_SHIFT) |
ui_sel;
vi_write(chan_o, cfg[layer].attr, data);
vi_write(chan_o, cfg[layer].size, size);
vi_write(chan_o, cfg[layer].coord, coord);
for (i = 0; i < VI_N_PLANES; i++) {
vi_write(chan_o, cfg[layer].pitch[i],
fb->pitches[i] ? fb->pitches[i] :
fb->pitches[0]);
vi_write(chan_o, cfg[layer].top_laddr[i],
gem->paddr + fb->offsets[i]);
vi_write(chan_o, fcolor[layer], 0xff000000);
}
if (layer == 0)
vi_write(chan_o, ovl_size[0], screen_size);
- } else { /* UI channel */
data = UI_CFG_ATTR_en | (fmt << UI_CFG_ATTR_fmt_SHIFT) |
alpha_glob;
ui_write(chan_o, cfg[layer].attr, data);
ui_write(chan_o, cfg[layer].size, size);
ui_write(chan_o, cfg[layer].coord, coord);
ui_write(chan_o, cfg[layer].pitch, fb->pitches[0]);
ui_write(chan_o, cfg[layer].top_laddr,
gem->paddr + fb->offsets[0]);
if (layer == 0)
ui_write(chan_o, ovl_size, screen_size);
- }
- bld_write(mux_o + DE_MUX_BLD_REGS, fcolor_ctl, fcolor);
- spin_unlock_irqrestore(&de_lock, flags);
+}
Splitting that into functions would make it a bit more trivial and readable.
Not sure: there is a lot of common data and different I/O accesses.
You could still have different ones to set the buffers, formats and coordinates for example.
I will have a look...
[snip]
+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; */
That's useless.
Right, but it seems that some people don't know how to debug a DRM driver. This is only a reminder.
- 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;
+}
And that really shouldn't be done that way.
May you explain?
This goes against the whole idea of the device and driver model. Drivers should only register themselves, device should be created by buses (or by using some external components if the bus can't: DT, ACPI, etc.). If there's a match, you get probed.
A driver that creates its own device just to probe itself violates that.
In this function (module init), there is no driver yet. The module contains 2 drivers: the DE (planes) and the LCD (CRTC), and there is no macro to handle such modules.
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd) +{
- int ret, possible_crtcs = 1 << lcd->crtc_idx;
- ret = de2_one_plane_init(drm, &lcd->planes[DE2_PRIMARY_PLANE],
DRM_PLANE_TYPE_PRIMARY, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
- if (ret >= 0)
ret = de2_one_plane_init(drm, &lcd->planes[DE2_CURSOR_PLANE],
DRM_PLANE_TYPE_CURSOR, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
Nothing looks really special about that cursor plane. Any reasion not to make it an overlay?
As explained above (channel/layer/pipe plane definitions), the cursor cannot go in a channel lower or equal to the one of the primary plane. Then, it must be known and, so, have an explicit plane.
If you were to make it a plane, you could use atomic_check to check this and make sure this doesn't happen. And you would gain a generic plane that can be used for other purposes if needed.
The function drm_crtc_init_with_planes() offers a cursor plane for free. On the other side, having 6 overlay planes is more than the SoCs can support.
Hi,
On Fri, Oct 28, 2016 at 07:34:20PM +0200, Jean-Francois Moine wrote:
On Fri, 28 Oct 2016 00:03:16 +0200 Maxime Ripard maxime.ripard@free-electrons.com wrote:
On Tue, Oct 25, 2016 at 04:14:41PM +0200, Jean-Francois Moine wrote:
+Display controller +==================
+Required properties:
+- compatible: value should be one of the following
"allwinner,sun8i-a83t-display-engine"
"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
We've been calling them bus and mod.
I can understand "bus" (which is better than "apb"), but why "mod"?
Allwinner has been calling the clocks that are supposed to generate the external signals (depending on where you were looking) module or mod clocks (which is also why we have mod in the clock compatibles). The module 1 clocks being used for the audio and the module 0 for the rest (SPI, MMC, NAND, display, etc.)
I did not find any 'module' in the H3 documentation. So, is it really a good name?
It's true that they use it less nowadays, but they still do, ie. https://github.com/allwinner-zh/linux-3.4-sunxi/blob/master/drivers/clk/sunx...
And we have to remain consistent anyway.
+- resets: phandle to the reset of the device
+- ports: phandle's to the LCD ports
Please use the OF graph.
These ports are references to the graph of nodes. See http://www.kernelhub.org/?msg=911825&p=2
In an OF-graph, your phandle to the LCD controller would be replaced by an output endpoint.
This is the DE controller. There is no endpoint link at this level.
The display engine definitely has an endpoint: the TCON.
The Device Engine just handles the planes of the LCDs, but, indeed, the LCDs must know about the DE and the DE must know about the LCDs. There are 2 ways to realize this knowledge in the DT:
- either the DE has one or two phandle's to the LCDs,
- or the LCDs have a phandle to the DE.
I chose the 1st way, the DE ports pointing to the endpoint of the LCDs which is part of the video link (OF-graph LCD <-> connector). It would be possible to have phandles to the LCDs themselves, but this asks for more code.
The second way is also possible, but it also complexifies a bit the exchanges DE <-> LCD.
I'm still not sure how it would complexify anything, and why you can't use the display graph to model the relation between the display engine and the TCON (and why you want to use a generic property that refers to the of-graph while it really isn't).
+void de2_disable_vblank(struct drm_device *drm, unsigned crtc) +{
- struct priv *priv = drm->dev_private;
- struct lcd *lcd = priv->lcds[crtc];
- tcon_write(lcd->mmio, gint0,
tcon_read(lcd->mmio, gint0) &
~TCON_GINT0_TCON1_Vb_Int_En);
+}
+/* panel functions */
Panel functions? In the CRTC driver?
Yes, dumb panel.
What do you mean by that? Using a Parallel/RGB interface?
Sorry, I though this was a well-known name. The 'dump panel' was used in the documentation of my previous ARM machine as the video frame sent to the HDMI controller. 'video_frame' is OK for you?
If it's the frame sent to the encoder, then it would be the CRTC by DRM's nomenclature.
+static const struct {
- char chan;
- char layer;
- char pipe;
+} plane2layer[DE2_N_PLANES] = {
- [DE2_PRIMARY_PLANE] = {0, 0, 0},
- [DE2_CURSOR_PLANE] = {1, 0, 1},
- [DE2_VI_PLANE] = {0, 1, 0},
+};
Comments?
This primary plane is channel 0 (VI), layer 0, pipe 0 cursor plane is channel 1 (UI), layer 0, pipe 1 overlay plane is channel 0 (VI), layer 1, pipe 0 or the full explanation: Constraints: The VI channels can do RGB or YUV, while UI channels can do RGB only. The LCD 0 has 1 VI channel and 4 UI channels, while LCD 1 has only 1 VI channel and 1 UI channel. The cursor must go to a channel bigger than the primary channel, otherwise it is not transparent. First try: Letting the primary plane (usually RGB) in the 2nd channel (UI), as this is done in the legacy driver, asks for the cursor to go to the next channel (UI), but this one does not exist in LCD1. Retained layout: So, we must use only 2 channels for the same behaviour on LCD0 (H3) and LCD1 (A83T) The retained combination is: - primary plane in the first channel (VI), - cursor plane inthe 2nd channel (UI), and - overlay plane in the 1st channel (VI).
Note that there could be 3 overlay planes (a channel has 4 layers), but I am not sure that the A83T or the H3 could support 3 simultaneous video streams...
Do you know if the pipe works in the old display engine?
Especially about the two-steps composition that wouldn't allow you to have alpha on all the planes?
If it is similar, I think hardcoding the pipe number is pretty bad, because that would restrict the combination of planes and formats, while some other might have worked.
From what I understood about the DE2, the pipes just define the priority of the overlay channels (one pipe for one channel). With the cursor constraint, there must be at least 2 channels in order (primary, cursor). Then, with these 2 channels/pipes, there can be 6 so-called overlay planes (3 RGB/YUV and 3 RGB only). Enabling the pipes 2 and 3 (LCD 0 only) would offer 8 more planes, but RGB only. Then, it might be useful to have dynamic pipes.
That's very valuable (and definitely should go into a comment), thanks!
I still believe that's it should be into a (simple at first) atomic_check. That would be easier to extend and quite easy to document and get simply by looking at the code.
+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; */
That's useless.
Right, but it seems that some people don't know how to debug a DRM driver. This is only a reminder.
- 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;
+}
And that really shouldn't be done that way.
May you explain?
This goes against the whole idea of the device and driver model. Drivers should only register themselves, device should be created by buses (or by using some external components if the bus can't: DT, ACPI, etc.). If there's a match, you get probed.
A driver that creates its own device just to probe itself violates that.
In this function (module init), there is no driver yet. The module contains 2 drivers: the DE (planes) and the LCD (CRTC), and there is no macro to handle such modules.
Ah, yes, my bad. I thought you were registering a device and a driver. Still this is a very unusual pattern. Why do you need to split the two? Can't you just merge them?
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd) +{
- int ret, possible_crtcs = 1 << lcd->crtc_idx;
- ret = de2_one_plane_init(drm, &lcd->planes[DE2_PRIMARY_PLANE],
DRM_PLANE_TYPE_PRIMARY, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
- if (ret >= 0)
ret = de2_one_plane_init(drm, &lcd->planes[DE2_CURSOR_PLANE],
DRM_PLANE_TYPE_CURSOR, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
Nothing looks really special about that cursor plane. Any reasion not to make it an overlay?
As explained above (channel/layer/pipe plane definitions), the cursor cannot go in a channel lower or equal to the one of the primary plane. Then, it must be known and, so, have an explicit plane.
If you were to make it a plane, you could use atomic_check to check this and make sure this doesn't happen. And you would gain a generic plane that can be used for other purposes if needed.
The function drm_crtc_init_with_planes() offers a cursor plane for free. On the other side, having 6 overlay planes is more than the SoCs can support.
It's not really for free, it costs you a generic plane that could definitely be used for something else and cannot anymore because they've been hardcoded to a cursor.
And having a camera, the VPU or even an application directly output directly into one of these planes seems a much better use of a generic plane than a cursor.
Maxime
On Mon, 7 Nov 2016 23:37:41 +0100 Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
On Fri, Oct 28, 2016 at 07:34:20PM +0200, Jean-Francois Moine wrote:
On Fri, 28 Oct 2016 00:03:16 +0200 Maxime Ripard maxime.ripard@free-electrons.com wrote:
[snip]
We've been calling them bus and mod.
I can understand "bus" (which is better than "apb"), but why "mod"?
Allwinner has been calling the clocks that are supposed to generate the external signals (depending on where you were looking) module or mod clocks (which is also why we have mod in the clock compatibles). The module 1 clocks being used for the audio and the module 0 for the rest (SPI, MMC, NAND, display, etc.)
I did not find any 'module' in the H3 documentation. So, is it really a good name?
It's true that they use it less nowadays, but they still do, ie. https://github.com/allwinner-zh/linux-3.4-sunxi/blob/master/drivers/clk/sunx...
There is a 'mod' suffix, but it is used for the bus gates only, not for the main clocks.
And we have to remain consistent anyway.
I don't see any consistency in the H3 DT: - the bus gates are named "ahb" and apb" - the (main) clocks are named "mmc", "usb0_phy" and "ir" There is no "bus" nor "mod".
+- resets: phandle to the reset of the device
+- ports: phandle's to the LCD ports
Please use the OF graph.
These ports are references to the graph of nodes. See http://www.kernelhub.org/?msg=911825&p=2
In an OF-graph, your phandle to the LCD controller would be replaced by an output endpoint.
This is the DE controller. There is no endpoint link at this level.
The display engine definitely has an endpoint: the TCON.
Not at all. The video chain is simply CRTC (TCON) -> connector (HDMI/LCD/DAC/..) The DE is an ancillary device which handles the planes.
The Device Engine just handles the planes of the LCDs, but, indeed, the LCDs must know about the DE and the DE must know about the LCDs. There are 2 ways to realize this knowledge in the DT:
- either the DE has one or two phandle's to the LCDs,
- or the LCDs have a phandle to the DE.
I chose the 1st way, the DE ports pointing to the endpoint of the LCDs which is part of the video link (OF-graph LCD <-> connector). It would be possible to have phandles to the LCDs themselves, but this asks for more code.
The second way is also possible, but it also complexifies a bit the exchanges DE <-> LCD.
I'm still not sure how it would complexify anything, and why you can't use the display graph to model the relation between the display engine and the TCON (and why you want to use a generic property that refers to the of-graph while it really isn't).
Complexification: 1- my solution: At startup time, the DE device is the DRM device. It has to know the devices entering in the video chains. The CRTCs (LCD/TCON) are found by ports[i] -> parent The connectors are found by ports[i] -> endpoint -> remote_endpoint -> parent 2- with ports pointing to the LCDs: The CRTCs (LCD/TCON) are simply ports[i] The connectors are found by loop on all ports of ports[i] ports[i][j] -> endpoint -> remote_endpoint -> parent 3- with a phandle to the DE in the LCDs: The DE cannot be the DRM device because there is no information about the video devices in the DT. Then, the DRM devices are the LCDs. These LCDs must give their indices to the DE. So, the DE must implement some callback function to accept a LCD definition, and there must be a list of DEs in the driver to make the association DE <-> LCD[i] Some more problem may be raised if a user wants to have the same frame buffer on the 2 LCDs of a DE.
Anyway, my solution is already used in the IMX Soc. See 'display-subsystem' in arch/arm/boot/dts/imx6q.dtsi for an example.
+void de2_disable_vblank(struct drm_device *drm, unsigned crtc) +{
- struct priv *priv = drm->dev_private;
- struct lcd *lcd = priv->lcds[crtc];
- tcon_write(lcd->mmio, gint0,
tcon_read(lcd->mmio, gint0) &
~TCON_GINT0_TCON1_Vb_Int_En);
+}
+/* panel functions */
Panel functions? In the CRTC driver?
Yes, dumb panel.
What do you mean by that? Using a Parallel/RGB interface?
Sorry, I though this was a well-known name. The 'dump panel' was used in the documentation of my previous ARM machine as the video frame sent to the HDMI controller. 'video_frame' is OK for you?
If it's the frame sent to the encoder, then it would be the CRTC by DRM's nomenclature.
The CRTC is a software entity. The frame buffer is a hardware entity.
+static const struct {
- char chan;
- char layer;
- char pipe;
+} plane2layer[DE2_N_PLANES] = {
- [DE2_PRIMARY_PLANE] = {0, 0, 0},
- [DE2_CURSOR_PLANE] = {1, 0, 1},
- [DE2_VI_PLANE] = {0, 1, 0},
+};
[snip]
Comments?
This primary plane is channel 0 (VI), layer 0, pipe 0 cursor plane is channel 1 (UI), layer 0, pipe 1 overlay plane is channel 0 (VI), layer 1, pipe 0 or the full explanation: Constraints: The VI channels can do RGB or YUV, while UI channels can do RGB only. The LCD 0 has 1 VI channel and 4 UI channels, while LCD 1 has only 1 VI channel and 1 UI channel. The cursor must go to a channel bigger than the primary channel, otherwise it is not transparent. First try: Letting the primary plane (usually RGB) in the 2nd channel (UI), as this is done in the legacy driver, asks for the cursor to go to the next channel (UI), but this one does not exist in LCD1. Retained layout: So, we must use only 2 channels for the same behaviour on LCD0 (H3) and LCD1 (A83T) The retained combination is: - primary plane in the first channel (VI), - cursor plane inthe 2nd channel (UI), and - overlay plane in the 1st channel (VI).
Note that there could be 3 overlay planes (a channel has 4 layers), but I am not sure that the A83T or the H3 could support 3 simultaneous video streams...
Do you know if the pipe works in the old display engine?
Especially about the two-steps composition that wouldn't allow you to have alpha on all the planes?
If it is similar, I think hardcoding the pipe number is pretty bad, because that would restrict the combination of planes and formats, while some other might have worked.
From what I understood about the DE2, the pipes just define the priority of the overlay channels (one pipe for one channel). With the cursor constraint, there must be at least 2 channels in order (primary, cursor). Then, with these 2 channels/pipes, there can be 6 so-called overlay planes (3 RGB/YUV and 3 RGB only). Enabling the pipes 2 and 3 (LCD 0 only) would offer 8 more planes, but RGB only. Then, it might be useful to have dynamic pipes.
That's very valuable (and definitely should go into a comment), thanks!
I still believe that's it should be into a (simple at first) atomic_check. That would be easier to extend and quite easy to document and get simply by looking at the code.
Sorry for I don't understand what you mean.
+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; */
That's useless.
Right, but it seems that some people don't know how to debug a DRM driver. This is only a reminder.
- 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;
+}
And that really shouldn't be done that way.
May you explain?
This goes against the whole idea of the device and driver model. Drivers should only register themselves, device should be created by buses (or by using some external components if the bus can't: DT, ACPI, etc.). If there's a match, you get probed.
A driver that creates its own device just to probe itself violates that.
In this function (module init), there is no driver yet. The module contains 2 drivers: the DE (planes) and the LCD (CRTC), and there is no macro to handle such modules.
Ah, yes, my bad. I thought you were registering a device and a driver. Still this is a very unusual pattern. Why do you need to split the two? Can't you just merge them?
The DE and the LCDs are different devices on different drivers. A DE must be only one device because it has to handle concurent accesses from its 2 LCDs. Then 2 drivers.
But only one module. Why? Because there cannot be double direction calls from one module to an other one, and, in our case, for example, - the DRM (DE) device must call vblank functions which are handled in the CRTC (TCON) device, and - the CRTC device must call DE initialization functions at startup time.
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd) +{
- int ret, possible_crtcs = 1 << lcd->crtc_idx;
- ret = de2_one_plane_init(drm, &lcd->planes[DE2_PRIMARY_PLANE],
DRM_PLANE_TYPE_PRIMARY, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
- if (ret >= 0)
ret = de2_one_plane_init(drm, &lcd->planes[DE2_CURSOR_PLANE],
DRM_PLANE_TYPE_CURSOR, possible_crtcs,
ui_formats, ARRAY_SIZE(ui_formats));
Nothing looks really special about that cursor plane. Any reasion not to make it an overlay?
As explained above (channel/layer/pipe plane definitions), the cursor cannot go in a channel lower or equal to the one of the primary plane. Then, it must be known and, so, have an explicit plane.
If you were to make it a plane, you could use atomic_check to check this and make sure this doesn't happen. And you would gain a generic plane that can be used for other purposes if needed.
The function drm_crtc_init_with_planes() offers a cursor plane for free. On the other side, having 6 overlay planes is more than the SoCs can support.
It's not really for free, it costs you a generic plane that could definitely be used for something else and cannot anymore because they've been hardcoded to a cursor.
And having a camera, the VPU or even an application directly output directly into one of these planes seems a much better use of a generic plane than a cursor.
Looking at the harder case (A83T), there may be 8 planes on 2 channels. Using a primary plane and the cursor, 8 planes - primary plane - cursor plane = 6 planes 6 planes are available. If I count correctly, in your example: one camera + one VPU + one application = 3 planes 3 planes are used. So, 3 planes are still available.
On the other side, removing the cursor would just let one more plane. Do we really need this one? In other words, I'd be pleased to know how you run 7 applications doing video overlay.
On Tue, Nov 08, 2016 at 03:37:52PM +0100, Jean-Francois Moine wrote:
On Mon, 7 Nov 2016 23:37:41 +0100 Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
On Fri, Oct 28, 2016 at 07:34:20PM +0200, Jean-Francois Moine wrote:
On Fri, 28 Oct 2016 00:03:16 +0200 Maxime Ripard maxime.ripard@free-electrons.com wrote:
[snip]
We've been calling them bus and mod.
I can understand "bus" (which is better than "apb"), but why "mod"?
Allwinner has been calling the clocks that are supposed to generate the external signals (depending on where you were looking) module or mod clocks (which is also why we have mod in the clock compatibles). The module 1 clocks being used for the audio and the module 0 for the rest (SPI, MMC, NAND, display, etc.)
I did not find any 'module' in the H3 documentation. So, is it really a good name?
It's true that they use it less nowadays, but they still do, ie. https://github.com/allwinner-zh/linux-3.4-sunxi/blob/master/drivers/clk/sunx...
There is a 'mod' suffix, but it is used for the bus gates only, not for the main clocks.
And we have to remain consistent anyway.
I don't see any consistency in the H3 DT:
- the bus gates are named "ahb" and apb"
We've been inconsistent in the past, hence why it would be great to have bus instead.
- the (main) clocks are named "mmc", "usb0_phy" and "ir"
There is no "bus" nor "mod".
Look into the other devices, for other SoCs. Pointing out that we've been bad in the past doesn't mean that we can't improve.
> + > +- resets: phandle to the reset of the device > + > +- ports: phandle's to the LCD ports
Please use the OF graph.
These ports are references to the graph of nodes. See http://www.kernelhub.org/?msg=911825&p=2
In an OF-graph, your phandle to the LCD controller would be replaced by an output endpoint.
This is the DE controller. There is no endpoint link at this level.
The display engine definitely has an endpoint: the TCON.
Not at all. The video chain is simply CRTC (TCON) -> connector (HDMI/LCD/DAC/..) The DE is an ancillary device which handles the planes.
And what does it do exactly with those planes? It outputs it to the TCON.
The Device Engine just handles the planes of the LCDs, but, indeed, the LCDs must know about the DE and the DE must know about the LCDs. There are 2 ways to realize this knowledge in the DT:
- either the DE has one or two phandle's to the LCDs,
- or the LCDs have a phandle to the DE.
I chose the 1st way, the DE ports pointing to the endpoint of the LCDs which is part of the video link (OF-graph LCD <-> connector). It would be possible to have phandles to the LCDs themselves, but this asks for more code.
The second way is also possible, but it also complexifies a bit the exchanges DE <-> LCD.
I'm still not sure how it would complexify anything, and why you can't use the display graph to model the relation between the display engine and the TCON (and why you want to use a generic property that refers to the of-graph while it really isn't).
Complexification: 1- my solution: At startup time, the DE device is the DRM device.
How do you deal with SoCs with multiple display engines then?
It has to know the devices entering in the video chains. The CRTCs (LCD/TCON) are found by ports[i] -> parent The connectors are found by ports[i] -> endpoint -> remote_endpoint -> parent 2- with ports pointing to the LCDs: The CRTCs (LCD/TCON) are simply ports[i] The connectors are found by loop on all ports of ports[i] ports[i][j] -> endpoint -> remote_endpoint -> parent 3- with a phandle to the DE in the LCDs:
What do you mean with LCD? Panels? Why would panels have a phandle to the display engine?
The DE cannot be the DRM device because there is no information about the video devices in the DT. Then, the DRM devices are the LCDs. These LCDs must give their indices to the DE. So, the DE must implement some callback function to accept a LCD definition, and there must be a list of DEs in the driver to make the association DE <-> LCD[i] Some more problem may be raised if a user wants to have the same frame buffer on the 2 LCDs of a DE.
I have no idea what you're talking about, sorry.
Anyway, my solution is already used in the IMX Soc. See 'display-subsystem' in arch/arm/boot/dts/imx6q.dtsi for an example.
Pointing out random example in the tree doesn't make a compelling argument.
> +void de2_disable_vblank(struct drm_device *drm, unsigned crtc) > +{ > + struct priv *priv = drm->dev_private; > + struct lcd *lcd = priv->lcds[crtc]; > + > + tcon_write(lcd->mmio, gint0, > + tcon_read(lcd->mmio, gint0) & > + ~TCON_GINT0_TCON1_Vb_Int_En); > +} > + > +/* panel functions */
Panel functions? In the CRTC driver?
Yes, dumb panel.
What do you mean by that? Using a Parallel/RGB interface?
Sorry, I though this was a well-known name. The 'dump panel' was used in the documentation of my previous ARM machine as the video frame sent to the HDMI controller. 'video_frame' is OK for you?
If it's the frame sent to the encoder, then it would be the CRTC by DRM's nomenclature.
The CRTC is a software entity. The frame buffer is a hardware entity.
Why are you about framebuffer now, this is nowhere in that discussion. Any way, the framebuffer is also what is put in a plane, so there's a name collision here, and you'll probably want to change it.
Judging by the previous discussion, this should really be called encoder if it encodes the frames on a bus format, or CRTC if it composes the planes.
> +static const struct { > + char chan; > + char layer; > + char pipe; > +} plane2layer[DE2_N_PLANES] = { > + [DE2_PRIMARY_PLANE] = {0, 0, 0}, > + [DE2_CURSOR_PLANE] = {1, 0, 1}, > + [DE2_VI_PLANE] = {0, 1, 0}, > +};
[snip]
Comments?
This primary plane is channel 0 (VI), layer 0, pipe 0 cursor plane is channel 1 (UI), layer 0, pipe 1 overlay plane is channel 0 (VI), layer 1, pipe 0 or the full explanation: Constraints: The VI channels can do RGB or YUV, while UI channels can do RGB only. The LCD 0 has 1 VI channel and 4 UI channels, while LCD 1 has only 1 VI channel and 1 UI channel. The cursor must go to a channel bigger than the primary channel, otherwise it is not transparent. First try: Letting the primary plane (usually RGB) in the 2nd channel (UI), as this is done in the legacy driver, asks for the cursor to go to the next channel (UI), but this one does not exist in LCD1. Retained layout: So, we must use only 2 channels for the same behaviour on LCD0 (H3) and LCD1 (A83T) The retained combination is: - primary plane in the first channel (VI), - cursor plane inthe 2nd channel (UI), and - overlay plane in the 1st channel (VI).
Note that there could be 3 overlay planes (a channel has 4 layers), but I am not sure that the A83T or the H3 could support 3 simultaneous video streams...
Do you know if the pipe works in the old display engine?
Especially about the two-steps composition that wouldn't allow you to have alpha on all the planes?
If it is similar, I think hardcoding the pipe number is pretty bad, because that would restrict the combination of planes and formats, while some other might have worked.
From what I understood about the DE2, the pipes just define the priority of the overlay channels (one pipe for one channel). With the cursor constraint, there must be at least 2 channels in order (primary, cursor). Then, with these 2 channels/pipes, there can be 6 so-called overlay planes (3 RGB/YUV and 3 RGB only). Enabling the pipes 2 and 3 (LCD 0 only) would offer 8 more planes, but RGB only. Then, it might be useful to have dynamic pipes.
That's very valuable (and definitely should go into a comment), thanks!
I still believe that's it should be into a (simple at first) atomic_check. That would be easier to extend and quite easy to document and get simply by looking at the code.
Sorry for I don't understand what you mean.
You can check all the constraints you exposed above in atomic_check instead of hardcoding it.
> +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; */
That's useless.
Right, but it seems that some people don't know how to debug a DRM driver. This is only a reminder.
> + 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; > +}
And that really shouldn't be done that way.
May you explain?
This goes against the whole idea of the device and driver model. Drivers should only register themselves, device should be created by buses (or by using some external components if the bus can't: DT, ACPI, etc.). If there's a match, you get probed.
A driver that creates its own device just to probe itself violates that.
In this function (module init), there is no driver yet. The module contains 2 drivers: the DE (planes) and the LCD (CRTC), and there is no macro to handle such modules.
Ah, yes, my bad. I thought you were registering a device and a driver. Still this is a very unusual pattern. Why do you need to split the two? Can't you just merge them?
The DE and the LCDs are different devices on different drivers. A DE must be only one device because it has to handle concurent accesses from its 2 LCDs. Then 2 drivers.
If it's different drivers, why are they in the same module?
But only one module. Why? Because there cannot be double direction calls from one module to an other one, and, in our case, for example,
- the DRM (DE) device must call vblank functions which are handled in the CRTC (TCON) device, and
- the CRTC device must call DE initialization functions at startup time.
I'm sorry, but that doesn't make any sense. The crtc device should take care of the CRTC functions. Your DRM CRTC and encoders can definitely be shared across different devices, you can have a look at how we're doing it for sun4i.
We basically have 3 drivers to create most of the display pipeline: - One for the DRM device - One for the display engine - One for the TCON
Once they have all loaded and registered in the component framework, their bind callback is called, and it's when the DRM entities are created using exported functions to manipulate what needs to be setup.
It's definitely doable, it just takes some effort.
> +int de2_plane_init(struct drm_device *drm, struct lcd *lcd) > +{ > + int ret, possible_crtcs = 1 << lcd->crtc_idx; > + > + ret = de2_one_plane_init(drm, &lcd->planes[DE2_PRIMARY_PLANE], > + DRM_PLANE_TYPE_PRIMARY, possible_crtcs, > + ui_formats, ARRAY_SIZE(ui_formats)); > + if (ret >= 0) > + ret = de2_one_plane_init(drm, &lcd->planes[DE2_CURSOR_PLANE], > + DRM_PLANE_TYPE_CURSOR, possible_crtcs, > + ui_formats, ARRAY_SIZE(ui_formats));
Nothing looks really special about that cursor plane. Any reasion not to make it an overlay?
As explained above (channel/layer/pipe plane definitions), the cursor cannot go in a channel lower or equal to the one of the primary plane. Then, it must be known and, so, have an explicit plane.
If you were to make it a plane, you could use atomic_check to check this and make sure this doesn't happen. And you would gain a generic plane that can be used for other purposes if needed.
The function drm_crtc_init_with_planes() offers a cursor plane for free. On the other side, having 6 overlay planes is more than the SoCs can support.
It's not really for free, it costs you a generic plane that could definitely be used for something else and cannot anymore because they've been hardcoded to a cursor.
And having a camera, the VPU or even an application directly output directly into one of these planes seems a much better use of a generic plane than a cursor.
Looking at the harder case (A83T), there may be 8 planes on 2 channels. Using a primary plane and the cursor, 8 planes - primary plane - cursor plane = 6 planes 6 planes are available. If I count correctly, in your example: one camera + one VPU + one application = 3 planes 3 planes are used. So, 3 planes are still available.
On the other side, removing the cursor would just let one more plane. Do we really need this one? In other words, I'd be pleased to know how you run 7 applications doing video overlay.
You can use those planes to do composition too, with each application having one or more plane. Android uses that.
There's no argument to have a cursor plane, really. Even regular graphic stack like X can use a regular overlay to have its cursor on it. It doesn't *remove* anything, it just allows to use the plane for what it was supposed to be used for.
Maxime
On Wed, 16 Nov 2016 22:33:06 +0100 Maxime Ripard maxime.ripard@free-electrons.com wrote:
The Device Engine just handles the planes of the LCDs, but, indeed, the LCDs must know about the DE and the DE must know about the LCDs. There are 2 ways to realize this knowledge in the DT:
- either the DE has one or two phandle's to the LCDs,
- or the LCDs have a phandle to the DE.
I chose the 1st way, the DE ports pointing to the endpoint of the LCDs which is part of the video link (OF-graph LCD <-> connector). It would be possible to have phandles to the LCDs themselves, but this asks for more code.
The second way is also possible, but it also complexifies a bit the exchanges DE <-> LCD.
I'm still not sure how it would complexify anything, and why you can't use the display graph to model the relation between the display engine and the TCON (and why you want to use a generic property that refers to the of-graph while it really isn't).
Complexification: 1- my solution: At startup time, the DE device is the DRM device.
How do you deal with SoCs with multiple display engines then?
In the H3, A83T and A64, there is only one DE. If many DEs in a SoC, there may be either one DRM device per DE or one DRM device for the whole system. In this last case, the (global) DE would have many resources (many I/O memory maps / IRQs..) and the physical DE of each endpoint would be indicated by the position of its phandle in the 'ports' array (DT documentation).
It has to know the devices entering in the video chains. The CRTCs (LCD/TCON) are found by ports[i] -> parent The connectors are found by ports[i] -> endpoint -> remote_endpoint -> parent 2- with ports pointing to the LCDs: The CRTCs (LCD/TCON) are simply ports[i] The connectors are found by loop on all ports of ports[i] ports[i][j] -> endpoint -> remote_endpoint -> parent 3- with a phandle to the DE in the LCDs:
What do you mean with LCD? Panels? Why would panels have a phandle to the display engine?
LCD is the same as CRTC. I don't think people will connect old CRT's to their new ARM boards. LCD's may be panels, modern TV sets, or any digital display. The word LCD seems clearer to me in this context, even if there may a DAC as an ancoder.
The DE cannot be the DRM device because there is no information about the video devices in the DT. Then, the DRM devices are the LCDs. These LCDs must give their indices to the DE. So, the DE must implement some callback function to accept a LCD definition, and there must be a list of DEs in the driver to make the association DE <-> LCD[i] Some more problem may be raised if a user wants to have the same frame buffer on the 2 LCDs of a DE.
I have no idea what you're talking about, sorry.
Here is the DT (I am using back 'CRTC'):
de: display-controller@xxx { ... }; crtc0: crt-controller@xxx{ ... display-controller = <&de>; ports { ... /* to the crtc0 connectors */ } }; crtc1: crt-controller@xxx{ ... display-controller = <&de>; ports { ... /* to the crtc1 connectors */ }; };
There are 2 DRM devices: one on crtc0, the other one on crtc1. The DE device is isolated. But, to treat the planes, it must receive information about the CRTCs. How?
Anyway, my solution is already used in the IMX Soc. See 'display-subsystem' in arch/arm/boot/dts/imx6q.dtsi for an example.
Pointing out random example in the tree doesn't make a compelling argument.
This is not a random example. There was a thread about the 'ports' phandle in the DT definition of the IMX video subsystem, and what kind of OF function should be used (see one of my previous mails). In the DRI list, nobody objected about the phandle by itself.
> Panel functions? In the CRTC driver?
Yes, dumb panel.
What do you mean by that? Using a Parallel/RGB interface?
Sorry, I though this was a well-known name. The 'dump panel' was used in the documentation of my previous ARM machine as the video frame sent to the HDMI controller. 'video_frame' is OK for you?
If it's the frame sent to the encoder, then it would be the CRTC by DRM's nomenclature.
The CRTC is a software entity. The frame buffer is a hardware entity.
Why are you about framebuffer now, this is nowhere in that discussion. Any way, the framebuffer is also what is put in a plane, so there's a name collision here, and you'll probably want to change it.
Judging by the previous discussion, this should really be called encoder if it encodes the frames on a bus format, or CRTC if it composes the planes.
I think that we will end in agreeing on the words. We just need some time! Here is how I understand the Allwinner's DE2:
- the TCON is the piece of hardware which gets some memory area and sends it to a bus according to some configuation (screen resolution, timings...). The bus data are encoded and transmitted to the connector. At the end, the display device receives frames. So, going back to the TCON, the memory are is the frame buffer.
- this frame buffer is virtual, empty, 'dumb', it is a dumb panel! It is filled by planes. This job is done by a specific image processor, the one contained in the DE2.
- the DE2 processor gets the plane source images from the SoC memory. It adjusts the images according to many configuration parameters and includes the result into the frame buffer.
So: LCD = CRTC = frame buffer = dumb panel = TCON
- LCD = hardware piece of a display device (terminal side) which renders the colored pixels in a digital way. By extension, the hardware part (computer side) of a display engine which handles the definitions of a digital or analog physical display device. By extension also, the software structure (driver side) which defines a physical screen.
- CRTC = DRM software entity which handles the definitions of a screen, but not just a CRT.
- frame buffer = piece of memory which contains the images which are sent to a screen.
- dumb panel = abstract entity which defines the characteristics of a physical screen.
You may note that, in the DE2 scheme, the TCON and LCD are not in the same (software) device while they are part of the same DRM software entity, the CTRC.
If it is similar, I think hardcoding the pipe number is pretty bad, because that would restrict the combination of planes and formats, while some other might have worked.
From what I understood about the DE2, the pipes just define the priority of the overlay channels (one pipe for one channel). With the cursor constraint, there must be at least 2 channels in order (primary, cursor). Then, with these 2 channels/pipes, there can be 6 so-called overlay planes (3 RGB/YUV and 3 RGB only). Enabling the pipes 2 and 3 (LCD 0 only) would offer 8 more planes, but RGB only. Then, it might be useful to have dynamic pipes.
That's very valuable (and definitely should go into a comment), thanks!
I still believe that's it should be into a (simple at first) atomic_check. That would be easier to extend and quite easy to document and get simply by looking at the code.
Sorry for I don't understand what you mean.
You can check all the constraints you exposed above in atomic_check instead of hardcoding it.
Sorry, but I don't like to run useless code for pure static definition.
The DE and the LCDs are different devices on different drivers. A DE must be only one device because it has to handle concurent accesses from its 2 LCDs. Then 2 drivers.
If it's different drivers, why are they in the same module?
But only one module. Why? Because there cannot be double direction calls from one module to an other one, and, in our case, for example,
- the DRM (DE) device must call vblank functions which are handled in the CRTC (TCON) device, and
- the CRTC device must call DE initialization functions at startup time.
I'm sorry, but that doesn't make any sense. The crtc device should take care of the CRTC functions. Your DRM CRTC and encoders can definitely be shared across different devices, you can have a look at how we're doing it for sun4i.
We basically have 3 drivers to create most of the display pipeline:
- One for the DRM device
- One for the display engine
- One for the TCON
Your DRM device is useless. It is simpler to have the DRM device as the display engine. Also, maybe, you have not the constraint the DE being shared between 2 CRTCs.
Once they have all loaded and registered in the component framework, their bind callback is called, and it's when the DRM entities are created using exported functions to manipulate what needs to be setup.
It's definitely doable, it just takes some effort.
It seems you did not look at what I have coded...
On the other side, removing the cursor would just let one more plane. Do we really need this one? In other words, I'd be pleased to know how you run 7 applications doing video overlay.
You can use those planes to do composition too, with each application having one or more plane. Android uses that.
There's no argument to have a cursor plane, really. Even regular graphic stack like X can use a regular overlay to have its cursor on it. It doesn't *remove* anything, it just allows to use the plane for what it was supposed to be used for.
I'd be glad to know how you can have a hardware cursor without defining it in drm_crtc_init_with_planes().
On Tue, Oct 25, 2016 at 04:14:41PM +0200, Jean-Francois Moine wrote:
On Mon, 24 Oct 2016 16:04:19 +0200 Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
Hi Maxime,
On Fri, Oct 21, 2016 at 09:26:18AM +0200, Jean-Francois Moine wrote:
Allwinner's recent SoCs, as A64, A83T and H3, contain a new display engine, DE2. This patch adds a DRM video driver for this device.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Output from checkpatch: total: 0 errors, 20 warnings, 83 checks, 1799 lines checked
.../bindings/display/sunxi/sunxi-de2.txt | 83 +++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 21 + drivers/gpu/drm/sunxi/Makefile | 7 + drivers/gpu/drm/sunxi/de2_crtc.c | 475 +++++++++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 63 +++ drivers/gpu/drm/sunxi/de2_de.c | 591 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 47 ++ drivers/gpu/drm/sunxi/de2_drv.c | 378 +++++++++++++ drivers/gpu/drm/sunxi/de2_plane.c | 119 +++++ 11 files changed, 1787 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sunxi-de2.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/sunxi-de2.txt b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt new file mode 100644 index 0000000..f9cd67a --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt
[...]
+- resets: phandle to the reset of the device
+- ports: phandle's to the LCD ports
Please use the OF graph.
These ports are references to the graph of nodes. See http://www.kernelhub.org/?msg=911825&p=2
I think what Maxime means is describe the DE to LCD connection with OF graph, not just a phandle.
Rob
On Fri, Oct 21, 2016 at 09:26:18AM +0200, Jean-Francois Moine wrote:
+static int de2_drm_bind(struct device *dev) +{
- struct drm_device *drm;
- struct priv *priv;
- int ret;
- drm = drm_dev_alloc(&de2_drm_driver, dev);
- if (!drm)
return -ENOMEM;
- 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 = 32; /* needed for cursor */
- drm->mode_config.min_height = 32;
- drm->mode_config.max_width = 1920;
- drm->mode_config.max_height = 1080;
- drm->mode_config.funcs = &de2_mode_config_funcs;
- drm->irq_enabled = true;
- /* initialize the display engine */
- priv->soc_type = (int) of_match_device(de2_drm_of_match, dev)->data;
- ret = de2_de_init(priv, dev);
- if (ret)
goto out2;
- /* start the subdevices */
- ret = component_bind_all(dev, drm);
- if (ret < 0)
goto out2;
- ret = drm_dev_register(drm, 0);
This needs to be the very last step in your driver load sequence. Kerneldoc explains why. Similar, but inverted for unloading: drm_dev_unregister is the very first thing you must call. -Daniel
On Tue, Oct 25, 2016 at 08:44:22AM +0200, Daniel Vetter wrote:
On Fri, Oct 21, 2016 at 09:26:18AM +0200, Jean-Francois Moine wrote:
+static int de2_drm_bind(struct device *dev) +{
- struct drm_device *drm;
- struct priv *priv;
- int ret;
- drm = drm_dev_alloc(&de2_drm_driver, dev);
Oh and you might want to look into drm_dev_init, allows you to use subclassing instead of pointer chasing for your driver-private data. -Daniel
- if (!drm)
return -ENOMEM;
- 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 = 32; /* needed for cursor */
- drm->mode_config.min_height = 32;
- drm->mode_config.max_width = 1920;
- drm->mode_config.max_height = 1080;
- drm->mode_config.funcs = &de2_mode_config_funcs;
- drm->irq_enabled = true;
- /* initialize the display engine */
- priv->soc_type = (int) of_match_device(de2_drm_of_match, dev)->data;
- ret = de2_de_init(priv, dev);
- if (ret)
goto out2;
- /* start the subdevices */
- ret = component_bind_all(dev, drm);
- if (ret < 0)
goto out2;
- ret = drm_dev_register(drm, 0);
This needs to be the very last step in your driver load sequence. Kerneldoc explains why. Similar, but inverted for unloading: drm_dev_unregister is the very first thing you must call.
-Daniel
Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
On Tue, 25 Oct 2016 08:44:22 +0200 Daniel Vetter daniel@ffwll.ch wrote:
- /* start the subdevices */
- ret = component_bind_all(dev, drm);
- if (ret < 0)
goto out2;
- ret = drm_dev_register(drm, 0);
This needs to be the very last step in your driver load sequence. Kerneldoc explains why. Similar, but inverted for unloading: drm_dev_unregister is the very first thing you must call.
Thanks, and also for embedding the drm device.
On Fri, Oct 21, 2016 at 09:26:18AM +0200, Jean-Francois Moine wrote:
Allwinner's recent SoCs, as A64, A83T and H3, contain a new display engine, DE2. This patch adds a DRM video driver for this device.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
.../bindings/display/sunxi/sunxi-de2.txt | 83 +++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 21 + drivers/gpu/drm/sunxi/Makefile | 7 + drivers/gpu/drm/sunxi/de2_crtc.c | 475 +++++++++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 63 +++ drivers/gpu/drm/sunxi/de2_de.c | 591 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 47 ++ drivers/gpu/drm/sunxi/de2_drv.c | 378 +++++++++++++ drivers/gpu/drm/sunxi/de2_plane.c | 119 +++++ 11 files changed, 1787 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sunxi-de2.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/sunxi-de2.txt b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt new file mode 100644 index 0000000..f9cd67a --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sunxi-de2.txt
- hdmi: hdmi@01ee0000 {
...
#address-cells = <1>;
#size-cells = <0>;
port {
type = "video";
This is proposed, but not an accepted property. Please drop.
hdmi_ep: endpoint {
remote-endpoint = <&lcd0_ep>;
};
};
- };
Allwinner's SoCs include support for both audio and video on HDMI. This patch defines a simple audio CODEC which may be used in sunxi HDMI video drivers.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- include/sound/sunxi_hdmi.h | 23 +++++++++ sound/soc/codecs/Kconfig | 9 ++++ sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sunxi-hdmi.c | 106 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 include/sound/sunxi_hdmi.h create mode 100644 sound/soc/codecs/sunxi-hdmi.c
diff --git a/include/sound/sunxi_hdmi.h b/include/sound/sunxi_hdmi.h new file mode 100644 index 0000000..0986bb9 --- /dev/null +++ b/include/sound/sunxi_hdmi.h @@ -0,0 +1,23 @@ +#ifndef __SUNXI_HDMI_H__ +#define __SUNXI_HDMI_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. + */ + +struct sunxi_hdmi_codec { + u8 *eld; + int (*set_audio_input)(struct device *dev, + int enable, + unsigned sample_rate, + unsigned sample_bit); +}; + +int sunxi_hdmi_codec_register(struct device *dev); +void sunxi_hdmi_codec_unregister(struct device *dev); + +#endif /* __SUNXI_HDMI_H__ */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c67667b..53385b1 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -131,6 +131,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_STA529 if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_STI_SAS + select SND_SOC_SUNXI_HDMI select SND_SOC_TAS2552 if I2C select SND_SOC_TAS5086 if I2C select SND_SOC_TAS571X if I2C @@ -793,6 +794,14 @@ config SND_SOC_STAC9766 config SND_SOC_STI_SAS tristate "codec Audio support for STI SAS codec"
+config SND_SOC_SUNXI_HDMI + tristate "Allwinner sunxi HDMI Support" + default m if DRM_SUNXI_DE2_HDMI=m + default y if DRM_SUNXI_DE2_HDMI=y + select SND_PCM_ELD + help + Enable HDMI audio output + config SND_SOC_TAS2552 tristate "Texas Instruments TAS2552 Mono Audio amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 958cd49..35804eb 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -139,6 +139,7 @@ snd-soc-sta350-objs := sta350.o snd-soc-sta529-objs := sta529.o snd-soc-stac9766-objs := stac9766.o snd-soc-sti-sas-objs := sti-sas.o +snd-soc-sunxi-hdmi-objs := sunxi-hdmi.o snd-soc-tas5086-objs := tas5086.o snd-soc-tas571x-objs := tas571x.o snd-soc-tas5720-objs := tas5720.o @@ -359,6 +360,7 @@ obj-$(CONFIG_SND_SOC_STA350) += snd-soc-sta350.o obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o +obj-$(CONFIG_SND_SOC_SUNXI_HDMI) += snd-soc-sunxi-hdmi.o obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o diff --git a/sound/soc/codecs/sunxi-hdmi.c b/sound/soc/codecs/sunxi-hdmi.c new file mode 100644 index 0000000..0d08676 --- /dev/null +++ b/sound/soc/codecs/sunxi-hdmi.c @@ -0,0 +1,106 @@ +/* + * Allwinner HDMI codec + * + * 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 <sound/soc.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <sound/pcm_drm_eld.h> +#include <sound/pcm_params.h> + +#include "sound/sunxi_hdmi.h" + +#define SUNXI_HDMI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int sunxi_hdmi_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sunxi_hdmi_codec *priv = dev_get_drvdata(dai->dev); + u8 *eld; + + eld = priv->eld; + if (!eld) + return -ENODEV; + + return snd_pcm_hw_constraint_eld(runtime, eld); +} + +static int sunxi_hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sunxi_hdmi_codec *priv = dev_get_drvdata(dai->dev); + unsigned sample_bit; + + if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) + sample_bit = 16; + else + sample_bit = 24; + + return priv->set_audio_input(dai->dev, true, + params_rate(params), + sample_bit); +} + +static void sunxi_hdmi_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sunxi_hdmi_codec *priv = dev_get_drvdata(dai->dev); + + priv->set_audio_input(dai->dev, false, 0, 0); +} + +static const struct snd_soc_dai_ops sunxi_hdmi_codec_ops = { + .startup = sunxi_hdmi_codec_startup, + .hw_params = sunxi_hdmi_hw_params, + .shutdown = sunxi_hdmi_codec_shutdown, +}; + +static struct snd_soc_dai_driver sunxi_hdmi_codec = { + .name = "hdmi", /* must be the name of the node in the DT */ + .playback = { + .stream_name = "HDMI Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 192000, + .formats = SUNXI_HDMI_FORMATS, + }, + .ops = &sunxi_hdmi_codec_ops, +}; + +static const struct snd_soc_codec_driver sunxi_hdmi_codec_drv = { + .ignore_pmdown_time = true, +}; + +/* functions called from the HDMI video driver */ +int sunxi_hdmi_codec_register(struct device *dev) +{ + return snd_soc_register_codec(dev, &sunxi_hdmi_codec_drv, + &sunxi_hdmi_codec, 1); +} +EXPORT_SYMBOL_GPL(sunxi_hdmi_codec_register); + +void sunxi_hdmi_codec_unregister(struct device *dev) +{ + snd_soc_unregister_codec(dev); +} +EXPORT_SYMBOL_GPL(sunxi_hdmi_codec_unregister); + +MODULE_AUTHOR("Jean-Francois Moine moinejf@free.fr"); +MODULE_DESCRIPTION("Allwinner HDMI CODEC"); +MODULE_LICENSE("GPL");
On Fri, Oct 21, 2016 at 3:44 PM, Jean-Francois Moine moinejf@free.fr wrote:
Allwinner's SoCs include support for both audio and video on HDMI. This patch defines a simple audio CODEC which may be used in sunxi HDMI video drivers.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
There's already a driver for basically the same thing:
sound/soc/codec/hdmi-codec.c
You use it by registering a sub-device from your hdmi driver, with the proper platform_data and callbacks. Grep for HDMI_CODEC_DRV_NAME for platforms already using it.
ChenYu
include/sound/sunxi_hdmi.h | 23 +++++++++ sound/soc/codecs/Kconfig | 9 ++++ sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sunxi-hdmi.c | 106 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 include/sound/sunxi_hdmi.h create mode 100644 sound/soc/codecs/sunxi-hdmi.c
diff --git a/include/sound/sunxi_hdmi.h b/include/sound/sunxi_hdmi.h new file mode 100644 index 0000000..0986bb9 --- /dev/null +++ b/include/sound/sunxi_hdmi.h @@ -0,0 +1,23 @@ +#ifndef __SUNXI_HDMI_H__ +#define __SUNXI_HDMI_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.
- */
+struct sunxi_hdmi_codec {
u8 *eld;
int (*set_audio_input)(struct device *dev,
int enable,
unsigned sample_rate,
unsigned sample_bit);
+};
+int sunxi_hdmi_codec_register(struct device *dev); +void sunxi_hdmi_codec_unregister(struct device *dev);
+#endif /* __SUNXI_HDMI_H__ */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c67667b..53385b1 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -131,6 +131,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_STA529 if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_STI_SAS
select SND_SOC_SUNXI_HDMI select SND_SOC_TAS2552 if I2C select SND_SOC_TAS5086 if I2C select SND_SOC_TAS571X if I2C
@@ -793,6 +794,14 @@ config SND_SOC_STAC9766 config SND_SOC_STI_SAS tristate "codec Audio support for STI SAS codec"
+config SND_SOC_SUNXI_HDMI
tristate "Allwinner sunxi HDMI Support"
default m if DRM_SUNXI_DE2_HDMI=m
default y if DRM_SUNXI_DE2_HDMI=y
select SND_PCM_ELD
help
Enable HDMI audio output
config SND_SOC_TAS2552 tristate "Texas Instruments TAS2552 Mono Audio amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 958cd49..35804eb 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -139,6 +139,7 @@ snd-soc-sta350-objs := sta350.o snd-soc-sta529-objs := sta529.o snd-soc-stac9766-objs := stac9766.o snd-soc-sti-sas-objs := sti-sas.o +snd-soc-sunxi-hdmi-objs := sunxi-hdmi.o snd-soc-tas5086-objs := tas5086.o snd-soc-tas571x-objs := tas571x.o snd-soc-tas5720-objs := tas5720.o @@ -359,6 +360,7 @@ obj-$(CONFIG_SND_SOC_STA350) += snd-soc-sta350.o obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o +obj-$(CONFIG_SND_SOC_SUNXI_HDMI) += snd-soc-sunxi-hdmi.o obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o diff --git a/sound/soc/codecs/sunxi-hdmi.c b/sound/soc/codecs/sunxi-hdmi.c new file mode 100644 index 0000000..0d08676 --- /dev/null +++ b/sound/soc/codecs/sunxi-hdmi.c @@ -0,0 +1,106 @@ +/*
- Allwinner HDMI codec
- 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 <sound/soc.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <sound/pcm_drm_eld.h> +#include <sound/pcm_params.h>
+#include "sound/sunxi_hdmi.h"
+#define SUNXI_HDMI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
+static int sunxi_hdmi_codec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
struct snd_pcm_runtime *runtime = substream->runtime;
struct sunxi_hdmi_codec *priv = dev_get_drvdata(dai->dev);
u8 *eld;
eld = priv->eld;
if (!eld)
return -ENODEV;
return snd_pcm_hw_constraint_eld(runtime, eld);
+}
+static int sunxi_hdmi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
struct sunxi_hdmi_codec *priv = dev_get_drvdata(dai->dev);
unsigned sample_bit;
if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE)
sample_bit = 16;
else
sample_bit = 24;
return priv->set_audio_input(dai->dev, true,
params_rate(params),
sample_bit);
+}
+static void sunxi_hdmi_codec_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
struct sunxi_hdmi_codec *priv = dev_get_drvdata(dai->dev);
priv->set_audio_input(dai->dev, false, 0, 0);
+}
+static const struct snd_soc_dai_ops sunxi_hdmi_codec_ops = {
.startup = sunxi_hdmi_codec_startup,
.hw_params = sunxi_hdmi_hw_params,
.shutdown = sunxi_hdmi_codec_shutdown,
+};
+static struct snd_soc_dai_driver sunxi_hdmi_codec = {
.name = "hdmi", /* must be the name of the node in the DT */
.playback = {
.stream_name = "HDMI Playback",
.channels_min = 1,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 8000,
.rate_max = 192000,
.formats = SUNXI_HDMI_FORMATS,
},
.ops = &sunxi_hdmi_codec_ops,
+};
+static const struct snd_soc_codec_driver sunxi_hdmi_codec_drv = {
.ignore_pmdown_time = true,
+};
+/* functions called from the HDMI video driver */ +int sunxi_hdmi_codec_register(struct device *dev) +{
return snd_soc_register_codec(dev, &sunxi_hdmi_codec_drv,
&sunxi_hdmi_codec, 1);
+} +EXPORT_SYMBOL_GPL(sunxi_hdmi_codec_register);
+void sunxi_hdmi_codec_unregister(struct device *dev) +{
snd_soc_unregister_codec(dev);
+} +EXPORT_SYMBOL_GPL(sunxi_hdmi_codec_unregister);
+MODULE_AUTHOR("Jean-Francois Moine moinejf@free.fr"); +MODULE_DESCRIPTION("Allwinner HDMI CODEC");
+MODULE_LICENSE("GPL");
2.10.1
-- You received this message because you are subscribed to the Google Groups "linux-sunxi" group. To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
On Fri, 28 Oct 2016 00:54:34 +0800 Chen-Yu Tsai wens@csie.org wrote:
There's already a driver for basically the same thing:
sound/soc/codec/hdmi-codec.c
You use it by registering a sub-device from your hdmi driver, with the proper platform_data and callbacks. Grep for HDMI_CODEC_DRV_NAME for platforms already using it.
I know that for a long time, and I will not use it on any account: it is a gasworks!
On Thu, Oct 27, 2016 at 07:16:34PM +0200, Jean-Francois Moine wrote:
Chen-Yu Tsai wens@csie.org wrote:
There's already a driver for basically the same thing:
sound/soc/codec/hdmi-codec.c
You use it by registering a sub-device from your hdmi driver, with the proper platform_data and callbacks. Grep for HDMI_CODEC_DRV_NAME for platforms already using it.
I know that for a long time, and I will not use it on any account: it is a gasworks!
If there are problems with what's there then articulate them and fix them, don't just open code something separate. Just open coding something else without any articulated reasoning is not helping things.
This patch adds a HDMI driver to the DE2 based Allwinner's SoCs as A83T and H3. Audio and video are supported.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- .../devicetree/bindings/display/sunxi/hdmi.txt | 52 ++ drivers/gpu/drm/sunxi/Kconfig | 8 + drivers/gpu/drm/sunxi/Makefile | 2 + drivers/gpu/drm/sunxi/de2_hdmi.c | 396 +++++++++ drivers/gpu/drm/sunxi/de2_hdmi.h | 40 + drivers/gpu/drm/sunxi/de2_hdmi_io.c | 927 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_hdmi_io.h | 25 + 7 files changed, 1450 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/hdmi.txt create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.c create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.h create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_io.c create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_io.h
diff --git a/Documentation/devicetree/bindings/display/sunxi/hdmi.txt b/Documentation/devicetree/bindings/display/sunxi/hdmi.txt new file mode 100644 index 0000000..0558c07 --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/hdmi.txt @@ -0,0 +1,52 @@ +Allwinner HDMI Transmitter +========================== + +The Allwinner HDMI transmitters are included in the SoCs. +They support audio and video. + +Required properties: + - #address-cells : should be <1> + - #size-cells : should be <0> + - compatible : should be + "allwinner,sun8i-a83t-hdmi" or + "allwinner,sun8i-h3-hdmi" + - clocks : phandles to the HDMI clocks as described in + Documentation/devicetree/bindings/clock/clock-bindings.txt + - clock-names : must be + "gate" : bus gate + "clock" : streaming clock + "ddc-clock" : DDC clock + - resets : One or two phandles to the HDMI resets + - reset-names : must be + "hdmi0" and "hdmi1" + +Required nodes: + - port: Audio and video input port nodes with endpoint definitions + as defined in Documentation/devicetree/bindings/graph.txt. + +Example: + + hdmi: hdmi@01ee0000 { + compatible = "allwinner,sun8i-a83t-hdmi"; + reg = <0x01ee0000 0x20000>; + clocks = <&ccu CLK_BUS_HDMI>, <&ccu CLK_HDMI>, + <&ccu CLK_HDMI_DDC>; + clock-names = "gate", "clock", "ddc-clock"; + resets = <&ccu RST_HDMI0>, <&ccu RST_HDMI1>; + reset-names = "hdmi0", "hdmi1"; + ... + #address-cells = <1>; + #size-cells = <0>; + port@0 { /* video */ + reg = <0>; + hdmi_lcd1: endpoint { + remote-endpoint = <&lcd1_hdmi>; + }; + }; + port@1 { /* audio */ + reg = <1>; + hdmi_i2s2: endpoint { + remote-endpoint = <&i2s2_hdmi>; + }; + }; + }; diff --git a/drivers/gpu/drm/sunxi/Kconfig b/drivers/gpu/drm/sunxi/Kconfig index 56bde2e..4c82153 100644 --- a/drivers/gpu/drm/sunxi/Kconfig +++ b/drivers/gpu/drm/sunxi/Kconfig @@ -19,3 +19,11 @@ config DRM_SUNXI_DE2 Choose this option if your Allwinner chipset has the DE2 interface as the A64, A83T and H3. If M is selected the module will be called sunxi-de2-drm. + +config DRM_SUNXI_DE2_HDMI + tristate "Support for DE2 HDMI" + depends on DRM_SUNXI_DE2 + select SND_SOC_SUNXI_HDMI if SND_SOC + help + Choose this option if you use want HDMI on DE2. + If M is selected the module will be called sunxi-de2-hdmi. diff --git a/drivers/gpu/drm/sunxi/Makefile b/drivers/gpu/drm/sunxi/Makefile index 62220cb..a268069 100644 --- a/drivers/gpu/drm/sunxi/Makefile +++ b/drivers/gpu/drm/sunxi/Makefile @@ -3,5 +3,7 @@ #
sunxi-de2-drm-objs := de2_drv.o de2_de.o de2_crtc.o de2_plane.o +sunxi-de2-hdmi-objs := de2_hdmi.o de2_hdmi_io.o
obj-$(CONFIG_DRM_SUNXI_DE2) += sunxi-de2-drm.o +obj-$(CONFIG_DRM_SUNXI_DE2_HDMI) += sunxi-de2-hdmi.o diff --git a/drivers/gpu/drm/sunxi/de2_hdmi.c b/drivers/gpu/drm/sunxi/de2_hdmi.c new file mode 100644 index 0000000..a2324cc --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_hdmi.c @@ -0,0 +1,396 @@ +/* + * Allwinner DRM driver - HDMI + * + * Copyright (C) 2016 Jean-Francois Moine moinejf@free.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/component.h> +#include <linux/clk.h> +#include <linux/hdmi.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> + +#include "de2_hdmi.h" +#include "de2_hdmi_io.h" + +static const struct of_device_id de2_hdmi_dt_ids[] = { + { .compatible = "allwinner,sun8i-a83t-hdmi", + .data = (void *) SOC_A83T }, + { .compatible = "allwinner,sun8i-h3-hdmi", + .data = (void *) SOC_H3 }, + { } +}; +MODULE_DEVICE_TABLE(of, de2_hdmi_dt_ids); + +#define conn_to_priv(x) \ + container_of(x, struct de2_hdmi_priv, connector) + +#define enc_to_priv(x) \ + container_of(x, struct de2_hdmi_priv, encoder) + +#define codec_to_priv(x) \ + container_of(x, struct de2_hdmi_priv, codec) + +#if IS_ENABLED(CONFIG_SND_SOC_SUNXI_HDMI) +/* --- audio functions --- */ +static int de2_hdmi_set_audio_input(struct device *dev, + int enable, + unsigned sample_rate, + unsigned sample_bit) +{ + struct de2_hdmi_priv *priv = codec_to_priv(dev_get_drvdata(dev)); + int ret; + + if (!enable) + return 0; + + mutex_lock(&priv->mutex); + ret = hdmi_io_audio(priv, sample_rate, sample_bit); + mutex_unlock(&priv->mutex); + + return ret; +} +#endif + +/* --- encoder functions --- */ + +static void de2_hdmi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct de2_hdmi_priv *priv = enc_to_priv(encoder); + + priv->cea_mode = drm_match_cea_mode(mode); + + DRM_DEBUG_DRIVER("cea_mode %d\n", priv->cea_mode); + + clk_set_rate(priv->clk, mode->clock * 1000); + + mutex_lock(&priv->mutex); + hdmi_io_video_mode(priv); + mutex_unlock(&priv->mutex); +} + +static void de2_hdmi_encoder_enable(struct drm_encoder *encoder) +{ + struct de2_hdmi_priv *priv = enc_to_priv(encoder); + + mutex_lock(&priv->mutex); + hdmi_io_video_on(priv); + mutex_unlock(&priv->mutex); +} + +static void de2_hdmi_encoder_disable(struct drm_encoder *encoder) +{ + struct de2_hdmi_priv *priv = enc_to_priv(encoder); + + mutex_lock(&priv->mutex); + hdmi_io_video_off(priv); + mutex_unlock(&priv->mutex); +} + +static const struct drm_encoder_helper_funcs de2_hdmi_encoder_helper_funcs = { + .mode_set = de2_hdmi_encoder_mode_set, + .enable = de2_hdmi_encoder_enable, + .disable = de2_hdmi_encoder_disable, +}; + +static const struct drm_encoder_funcs de2_hdmi_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +/* --- connector functions --- */ +static int de2_hdmi_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct de2_hdmi_priv *priv = conn_to_priv(connector); + int cea_mode = drm_match_cea_mode(mode); + + if (hdmi_io_mode_valid(cea_mode) < 0) + return MODE_NOMODE; + + if (connector->eld[0]) + priv->codec.eld = connector->eld; /* audio parameters */ + + return MODE_OK; +} + +static enum drm_connector_status de2_hdmi_connector_detect( + struct drm_connector *connector, bool force) +{ + struct de2_hdmi_priv *priv = conn_to_priv(connector); + int ret; + + mutex_lock(&priv->mutex); + ret = hdmi_io_get_hpd(priv); + mutex_unlock(&priv->mutex); + + return ret ? connector_status_connected : + connector_status_disconnected; +} + +static int read_edid_block(void *data, u8 *buf, + unsigned int blk, size_t length) +{ + struct de2_hdmi_priv *priv = data; + int ret; + + mutex_lock(&priv->mutex); + ret = hdmi_io_ddc_read(priv, + blk / 2, (blk & 1) ? 128 : 0, + length, buf); + mutex_unlock(&priv->mutex); + + return ret; +} + +static int de2_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct de2_hdmi_priv *priv = conn_to_priv(connector); + struct edid *edid; + int n; + + DRM_DEBUG_DRIVER("\n"); + + priv->codec.eld = NULL; + + edid = drm_do_get_edid(connector, read_edid_block, priv); + + if (!edid) { + dev_warn(priv->dev, "failed to read EDID\n"); + if (!connector->cmdline_mode.specified) + return 0; + + return drm_add_modes_noedid(connector, + connector->cmdline_mode.xres, + connector->cmdline_mode.yres); + } + + drm_mode_connector_update_edid_property(connector, edid); + n = drm_add_edid_modes(connector, edid); + + drm_edid_to_eld(connector, edid); + + kfree(edid); + + DRM_DEBUG_DRIVER("%s EDID ok %d modes\n", + connector->eld[0] ? "HDMI" : "DVI", n); + + return n; +} + +static const +struct drm_connector_helper_funcs de2_hdmi_connector_helper_funcs = { + .get_modes = de2_hdmi_connector_get_modes, + .mode_valid = de2_hdmi_connector_mode_valid, +}; + +static const struct drm_connector_funcs de2_hdmi_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = de2_hdmi_connector_detect, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static void de2_hdmi_cleanup(struct de2_hdmi_priv *priv) +{ + clk_disable_unprepare(priv->clk_ddc); + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->gate); + if (!IS_ERR_OR_NULL(priv->rstc1)) + reset_control_assert(priv->rstc1); + if (!IS_ERR_OR_NULL(priv->rstc0)) + reset_control_assert(priv->rstc0); +} + +static int de2_hdmi_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct de2_hdmi_priv *priv = codec_to_priv(dev_get_drvdata(dev)); + struct drm_encoder *encoder = &priv->encoder; + struct drm_connector *connector = &priv->connector; + int ret; + + encoder->possible_crtcs = + drm_of_find_possible_crtcs(drm, dev->of_node); + + /* if no CRTC, delay */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + /* HDMI init */ + ret = reset_control_deassert(priv->rstc0); + if (ret) + goto err; + ret = reset_control_deassert(priv->rstc1); + if (ret) + goto err; + if (!IS_ERR(priv->gate)) { + ret = clk_prepare_enable(priv->gate); + if (ret) + goto err; + } + ret = clk_prepare_enable(priv->clk); + if (ret) + goto err; + ret = clk_prepare_enable(priv->clk_ddc); + if (ret) + goto err; + + mutex_lock(&priv->mutex); + hdmi_io_init(priv); + hdmi_io_reset(priv); /* hpd reset */ + mutex_unlock(&priv->mutex); + + /* encoder init */ + ret = drm_encoder_init(drm, encoder, &de2_hdmi_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + goto err; + + drm_encoder_helper_add(encoder, &de2_hdmi_encoder_helper_funcs); + + /* connector init */ + ret = drm_connector_init(drm, connector, + &de2_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) + goto err_connector; + + connector->interlace_allowed = 1; + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + drm_connector_helper_add(connector, + &de2_hdmi_connector_helper_funcs); + + drm_mode_connector_attach_encoder(connector, encoder); + + return 0; + +err_connector: + drm_encoder_cleanup(encoder); +err: + de2_hdmi_cleanup(priv); + DRM_DEBUG_DRIVER("err %d\n", ret); + return ret; +} + +static void de2_hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct de2_hdmi_priv *priv = codec_to_priv(dev_get_drvdata(dev)); + + drm_connector_cleanup(&priv->connector); + drm_encoder_cleanup(&priv->encoder); + de2_hdmi_cleanup(priv); +} + +static const struct component_ops de2_hdmi_ops = { + .bind = de2_hdmi_bind, + .unbind = de2_hdmi_unbind, +}; + +static int de2_hdmi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct de2_hdmi_priv *priv; + struct resource *res; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, &priv->codec); + priv->dev = dev; + + mutex_init(&priv->mutex); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "failed to get memory resource\n"); + return -ENXIO; + } + 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"); /* optional */ + + priv->clk = devm_clk_get(dev, "clock"); + if (IS_ERR(priv->clk)) { + dev_err(dev, "hdmi clock err %d\n", + (int) PTR_ERR(priv->clk)); + return PTR_ERR(priv->clk); + } + priv->clk_ddc = devm_clk_get(dev, "ddc-clock"); + if (IS_ERR(priv->clk_ddc)) { + dev_err(dev, "hdmi-ddc clock err %d\n", + (int) PTR_ERR(priv->clk_ddc)); + return PTR_ERR(priv->clk_ddc); + } + + priv->rstc0 = devm_reset_control_get(dev, "hdmi0"); + if (IS_ERR(priv->rstc0)) { + dev_err(dev, "reset controller err %d\n", + (int) PTR_ERR(priv->rstc0)); + return PTR_ERR(priv->rstc0); + } + + priv->rstc1 = devm_reset_control_get(dev, "hdmi1"); + if (IS_ERR(priv->rstc1)) { + dev_err(dev, "reset controller err %d\n", + (int) PTR_ERR(priv->rstc1)); + return PTR_ERR(priv->rstc1); + } + + priv->soc_type = (int) of_match_device(de2_hdmi_dt_ids, + &pdev->dev)->data; + + if (IS_ENABLED(CONFIG_SND_SOC_SUNXI_HDMI)) { + priv->codec.set_audio_input = de2_hdmi_set_audio_input; + sunxi_hdmi_codec_register(dev); + } + + return component_add(dev, &de2_hdmi_ops); +} + +static int de2_hdmi_remove(struct platform_device *pdev) +{ + if (IS_ENABLED(CONFIG_SND_SOC_SUNXI_HDMI)) + sunxi_hdmi_codec_unregister(&pdev->dev); + component_del(&pdev->dev, &de2_hdmi_ops); + + return 0; +} + +static struct platform_driver de2_hdmi_driver = { + .probe = de2_hdmi_probe, + .remove = de2_hdmi_remove, + .driver = { + .name = "sunxi-de2-hdmi", + .of_match_table = of_match_ptr(de2_hdmi_dt_ids), + }, +}; + +module_platform_driver(de2_hdmi_driver); + +MODULE_AUTHOR("Jean-Francois Moine moinejf@free.fr"); +MODULE_DESCRIPTION("Allwinner DE2 HDMI encoder/connector"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sunxi/de2_hdmi.h b/drivers/gpu/drm/sunxi/de2_hdmi.h new file mode 100644 index 0000000..915d609 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_hdmi.h @@ -0,0 +1,40 @@ +#ifndef __DE2_HDMI_H__ +#define __DE2_HDMI_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 <sound/sunxi_hdmi.h> + +/* SoC types */ +#define SOC_A83T 0 +#define SOC_H3 1 + +struct de2_hdmi_priv { + struct device *dev; + void __iomem *mmio; + + struct drm_encoder encoder; + struct drm_connector connector; + + struct clk *clk; + struct clk *clk_ddc; + struct clk *gate; + struct reset_control *rstc0; + struct reset_control *rstc1; + + struct mutex mutex; + u8 soc_type; + u8 cea_mode; + struct sunxi_hdmi_codec codec; +}; + +#endif /* __DE2_HDMI_H__ */ diff --git a/drivers/gpu/drm/sunxi/de2_hdmi_io.c b/drivers/gpu/drm/sunxi/de2_hdmi_io.c new file mode 100644 index 0000000..127b220 --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_hdmi_io.c @@ -0,0 +1,927 @@ +/* + * Allwinner A83T and H3 HDMI lowlevel functions + * + * Copyright (C) 2016 Jean-Francois Moine moinejf@free.fr + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * + * 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. + */ + +/* + * The HDMI controller in the A83T and H3 seems to be a + * Synopsys DesignWare HDMI controller. + * The PHYs are unknown. + * Documentation: + * https://linux-sunxi.org/DWC_HDMI_Controller + * https://www.synopsys.com/dw/doc.php/ds/c/dwc_hdmi_tx_csds.pdf + */ + +#include <linux/hdmi.h> + +#include "de2_hdmi.h" +#include "de2_hdmi_io.h" + +/* DW registers (obfuscated addresses) */ + +/* Interrupt Registers */ +#define R_0100_HDMI_IH_FC_STAT0 0x0010 +#define R_0101_HDMI_IH_FC_STAT1 0x0011 +#define R_0102_HDMI_IH_FC_STAT2 0x8010 +#define R_0103_HDMI_IH_AS_STAT0 0x8011 +#define R_0104_HDMI_IH_PHY_STAT0 0x0012 +#define R_0105_HDMI_IH_I2CM_STAT0 0x0013 +#define R_0106_HDMI_IH_CEC_STAT0 0x8012 +#define R_0107_HDMI_IH_VP_STAT0 0x8013 +#define R_0108_HDMI_IH_I2CMPHY_STAT0 0x4010 +#define R_01ff_HDMI_IH_MUTE 0xf01f + +/* Video Sample Registers */ +#define R_0200_HDMI_TX_INVID0 0x0800 +#define R_0201_HDMI_TX_INSTUFFING 0x0801 +#define R_0202_HDMI_TX_GYDATA0 0x8800 +#define R_0203_HDMI_TX_GYDATA1 0x8801 +#define R_0204_HDMI_TX_RCRDATA0 0x0802 +#define R_0205_HDMI_TX_RCRDATA1 0x0803 +#define R_0206_HDMI_TX_BCBDATA0 0x8802 +#define R_0207_HDMI_TX_BCBDATA1 0x8803 + +/* Video Packetizer Registers */ +#define R_0801_HDMI_VP_PR_CD 0x0401 +#define R_0802_HDMI_VP_STUFF 0x8400 +#define R_0803_HDMI_VP_REMAP 0x8401 +#define R_0804_HDMI_VP_CONF 0x0402 +#define R_0807_HDMI_VP_MASK 0x8403 + +/* Frame Composer Registers */ +#define R_1000_HDMI_FC_INVIDCONF 0x0040 +#define HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH 0x10 +#define R_1001_HDMI_FC_INHACTV0 0x0041 +#define R_1002_HDMI_FC_INHACTV1 0x8040 +#define R_1003_HDMI_FC_INHBLANK0 0x8041 +#define R_1004_HDMI_FC_INHBLANK1 0x0042 +#define R_1005_HDMI_FC_INVACTV0 0x0043 +#define R_1006_HDMI_FC_INVACTV1 0x8042 +#define R_1007_HDMI_FC_INVBLANK 0x8043 +#define R_1008_HDMI_FC_HSYNCINDELAY0 0x4040 +#define R_1009_HDMI_FC_HSYNCINDELAY1 0x4041 +#define R_100a_HDMI_FC_HSYNCINWIDTH1 0xc040 +#define R_100b_HDMI_FC_HSYNCINWIDTH1 0xc041 +#define R_100c_HDMI_FC_VSYNCINDELAY 0x4042 +#define R_100d_HDMI_FC_VSYNCINWIDTH 0x4043 +#define R_1011_HDMI_FC_CTRLDUR 0x0045 +#define R_1012_HDMI_FC_EXCTRLDUR 0x8044 +#define R_1013_HDMI_FC_EXCTRLSPAC 0x8045 +#define R_1014_HDMI_FC_CH0PREAM 0x0046 +#define R_1015_HDMI_FC_CH1PREAM 0x0047 +#define R_1016_HDMI_FC_CH2PREAM 0x8046 +#define R_1018_HDMI_FC_GCP 0x4044 +#define R_1019_HDMI_FC_AVICONF0 0x4045 +#define HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN 0x20 +#define R_101a_HDMI_FC_AVICONF1 0xc044 +#define R_101b_HDMI_FC_AVICONF2 0xc045 +#define R_101c_HDMI_FC_AVIVID 0x4046 +#define R_1025_HDMI_FC_AUDICONF0 0x2043 +#define R_1026_HDMI_FC_AUDICONF1 0xa042 +#define R_1027_HDMI_FC_AUDICONF2 0xa043 +#define R_1028_HDMI_FC_AUDICONF3 0x6040 +#define R_1029_HDMI_FC_VSDIEEEID0 0x6041 +#define R_1030_HDMI_FC_VSDIEEEID1 0x2044 +#define R_1031_HDMI_FC_VSDIEEEID2 0x2045 +#define R_1032_HDMI_FC_VSDPAYLOAD0 0xa044 +#define R_1033_HDMI_FC_VSDPAYLOAD1 0xa045 +#define R_1034_HDMI_FC_VSDPAYLOAD2 0x2046 +#define R_1063_HDMI_FC_AUDSCONF 0xa049 +#define R_1065_HDMI_FC_AUDSV 0x204b +#define R_1066_HDMI_FC_AUDSU 0xa04a +#define R_1067_HDMI_FC_AUDSCHNLS0 0xa04b +#define HDMI_FC_AUDSCHNLS0_CGMSA 0x30 +#define R_1068_HDMI_FC_AUDSCHNLS1 0x6048 +#define R_1069_HDMI_FC_AUDSCHNLS2 0x6049 +#define R_106a_HDMI_FC_AUDSCHNLS3 0xe048 +#define HDMI_FC_AUDSCHNLS3_OIEC_CH0(v) (v) +#define HDMI_FC_AUDSCHNLS3_OIEC_CH1(v) (v << 4) +#define R_106b_HDMI_FC_AUDSCHNLS4 0xe049 +#define HDMI_FC_AUDSCHNLS4_OIEC_CH2(v) (v) +#define HDMI_FC_AUDSCHNLS4_OIEC_CH3(v) (v << 4) +#define R_106c_HDMI_FC_AUDSCHNLS5 0x604a +#define HDMI_FC_AUDSCHNLS5_OIEC_CH0(v) (v) +#define HDMI_FC_AUDSCHNLS5_OIEC_CH1(v) (v << 4) +#define R_106d_HDMI_FC_AUDSCHNLS6 0x604b +#define HDMI_FC_AUDSCHNLS6_OIEC_CH2(v) (v) +#define HDMI_FC_AUDSCHNLS6_OIEC_CH3(v) (v << 4) +#define R_106e_HDMI_FC_AUDSCHNLS7 0xe04a +#define R_106f_HDMI_FC_AUDSCHNLS8 0xe04b +#define HDMI_FC_AUDSCHNLS8_WORDLENGTH(v) (v) +#define R_10b3_HDMI_FC_DATAUTO0 0xb045 +#define R_10b4_HDMI_FC_DATAUTO1 0x3046 +#define R_10b5_HDMI_FC_DATAUTO2 0x3047 +#define R_10d2_HDMI_FC_MASK0 0x904c +#define R_10d6_HDMI_FC_MASK1 0x904e +#define R_10da_HDMI_FC_MASK2 0xd04c +#define R_10e0_HDMI_FC_PRCONF 0x3048 +#define R_1103_HDMI_FC_GMD_CONF 0x8051 +#define R_1104_HDMI_FC_GMD_HB 0x0052 +#define R_1200_HDMI_FC_DBGFORCE 0x0840 +#define HDMI_FC_DBGFORCE_FORCEAUDIO BIT(4) +#define HDMI_FC_DBGFORCE_FORCEVIDEO BIT(0) +#define R_1219_HDMI_FC_DBGTMDS0 0x4845 + +/* HDMI Source PHY Registers */ +#define R_3000_HDMI_PHY_CONF0 0x0240 +#define HDMI_PHY_CONF0_PDZ BIT(7) +#define HDMI_PHY_CONF0_ENTMDS BIT(6) +#define HDMI_PHY_CONF0_SPARECTRL BIT(5) +#define HDMI_PHY_CONF0_GEN2_PDDQ BIT(4) +#define HDMI_PHY_CONF0_GEN2_TXPWRON BIT(3) +#define HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE BIT(2) +#define HDMI_PHY_CONF0_SELDATAENPOL BIT(1) +#define HDMI_PHY_CONF0_SELDIPIF BIT(0) +#define R_3001_HDMI_PHY_TST0 0x0241 +#define HDMI_PHY_TST0_TSTCLR BIT(5) +#define R_3005_HDMI_PHY_INT0 0x0243 +#define R_3006_HDMI_PHY_MASK0 0x8242 + +/* HDMI Master PHY Registers */ +#define R_3020_HDMI_PHY_I2CM_SLAVE_ADDR 0x2240 +#define HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2 0x69 +#define R_3021_HDMI_PHY_I2CM_ADDRESS_ADDR 0x2241 +#define R_3022_HDMI_PHY_I2CM_DATAO_1_ADDR 0xa240 +#define R_3023_HDMI_PHY_I2CM_DATAO_0_ADDR 0xa241 +#define R_3026_HDMI_PHY_I2CM_OPERATION_ADDR 0xa242 +#define HDMI_PHY_I2CM_OPERATION_ADDR_WRITE 0x10 +#define R_3027_HDMI_PHY_I2CM_INT_ADDR 0xa243 +#define R_3028_HDMI_PHY_I2CM_CTLINT_ADDR 0x6240 + +/* Audio Sampler Registers */ +#define R_3100_HDMI_AUD_CONF0 0x0250 +#define R_3101_HDMI_AUD_CONF1 0x0251 +#define R_3102_HDMI_AUD_INT 0x8250 +#define R_3103_HDMI_AUD_CONF2 0x8251 +#define R_3200_HDMI_AUD_N1 0x0a40 +#define R_3201_HDMI_AUD_N2 0x0a41 +#define R_3202_HDMI_AUD_N3 0x8a40 +#define R_3205_HDMI_AUD_CTS3 0x0a43 +#define R_3206_HDMI_AUD_INPUTCLKFS 0x8a42 +#define R_3302_HDMI_AUD_SPDIFINT 0x8a50 + +/* Generic Parallel Audio Interface Registers */ +#define R_3506_HDMI_GP_POL 0x8272 + +/* Main Controller Registers */ +#define R_4001_HDMI_MC_CLKDIS 0x0081 +#define HDMI_MC_CLKDIS_HDCPCLK_DISABLE BIT(6) +#define HDMI_MC_CLKDIS_AUDCLK_DISABLE BIT(3) +#define HDMI_MC_CLKDIS_TMDSCLK_DISABLE BIT(1) +#define R_4002_HDMI_MC_SWRSTZ 0x8080 +#define R_4004_HDMI_MC_FLOWCTRL 0x0082 +#define R_4005_HDMI_MC_PHYRSTZ 0x0083 +#define HDMI_MC_PHYRSTZ_DEASSERT BIT(0) + +/* HDCP Encryption Engine Registers */ +#define R_5000_HDMI_A_HDCPCFG0 0x00c0 +#define R_5001_HDMI_A_HDCPCFG1 0x00c1 +#define HDMI_A_HDCPCFG1_PH2UPSHFTENC BIT(2) +#define HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE BIT(1) +#define HDMI_A_HDCPCFG1_SWRESET BIT(0) +#define R_5008_HDMI_A_APIINTMSK 0x40c0 +#define R_5009_HDMI_A_VIDPOLCFG 0x40c1 +#define HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH BIT(4) + +/* CEC Engine Registers */ +#define R_7d02_HDMI_CEC_MASK 0x86f0 + +/* I2C Master Registers (E-DDC) */ +#define R_7e00_HDMI_I2CM_SLAVE 0x0ee0 +#define R_7e01_HDMI_I2CM_ADDRESS 0x0ee1 +#define R_7e03_HDMI_I2CM_DATAI 0x8ee1 +#define R_7e04_HDMI_I2CM_OPERATION 0x0ee2 +#define HDMI_I2CM_OPERATION_DDC_READ 0x02 +#define R_7e05_HDMI_I2CM_INT 0x0ee3 +#define R_7e06_HDMI_I2CM_CTLINT 0x8ee2 +#define R_7e07_HDMI_I2CM_DIV 0x8ee3 +#define R_7e08_HDMI_I2CM_SEGADDR 0x4ee0 +#define R_7e09_HDMI_I2CM_SOFTRSTZ 0x4ee1 +#define R_7e0a_HDMI_I2CM_SEGPTR 0xcee0 +#define R_7e0b_HDMI_I2CM_SS_SCL_HCNT_1_ADDR 0xcee1 +#define R_7e0c_HDMI_I2CM_SS_SCL_HCNT_0_ADDR 0x4ee2 +#define R_7e0d_HDMI_I2CM_SS_SCL_LCNT_1_ADDR 0x4ee3 +#define R_7e0e_HDMI_I2CM_SS_SCL_LCNT_0_ADDR 0xcee2 +#define R_7e0f_HDMI_I2CM_FS_SCL_HCNT_1_ADDR 0xcee3 +#define R_7e10_HDMI_I2CM_FS_SCL_HCNT_0_ADDR 0x0ee4 +#define R_7e11_HDMI_I2CM_FS_SCL_LCNT_1_ADDR 0x0ee5 + +/* + * [0] = vic (CEA Video ID) + * [1] used in hdmi_phy_set / hdmi_io_audio + * [2..16] used in hdmi_io_video_mode + */ +static const struct para_tab { + u32 para[17]; +} ptbl[] = { + {{ 6, 1, 1, 1, 5, 3, 0, 1, 4, 0, 0, 160, 20, 38, 124, 240, 22}}, + {{ 21, 11, 1, 1, 5, 3, 1, 1, 2, 0, 0, 160, 32, 24, 126, 32, 24}}, + {{ 2, 11, 0, 0, 2, 6, 1, 0, 9, 0, 0, 208, 138, 16, 62, 224, 45}}, + {{ 17, 11, 0, 0, 2, 5, 2, 0, 5, 0, 0, 208, 144, 12, 64, 64, 49}}, + {{ 19, 4, 0, 0x60, 5, 5, 2, 2, 5, 1, 0, 0, 188, 184, 40, 208, 30}}, + {{ 4, 4, 0, 0x60, 5, 5, 2, 1, 5, 0, 0, 0, 114, 110, 40, 208, 30}}, + {{ 20, 4, 0, 0x61, 7, 5, 4, 2, 2, 2, 0, 128, 208, 16, 44, 56, 22}}, + {{ 5, 4, 0, 0x60, 7, 5, 4, 1, 2, 0, 0, 128, 24, 88, 44, 56, 22}}, + {{ 31, 2, 0, 0x60, 7, 5, 4, 2, 4, 2, 0, 128, 208, 16, 44, 56, 45}}, + {{ 16, 2, 0, 0x60, 7, 5, 4, 1, 4, 0, 0, 128, 24, 88, 44, 56, 45}}, + {{ 32, 4, 0, 0x60, 7, 5, 4, 3, 4, 2, 0, 128, 62, 126, 44, 56, 45}}, + {{ 33, 4, 0, 0, 7, 5, 4, 2, 4, 2, 0, 128, 208, 16, 44, 56, 45}}, + {{ 34, 4, 0, 0, 7, 5, 4, 1, 4, 0, 0, 128, 24, 88, 44, 56, 45}}, +#if 0 + {{160, 2, 0, 0x60, 7, 5, 8, 3, 4, 1, 0, 128, 62, 126, 44, 157, 45}}, + {{147, 2, 0, 0x60, 5, 5, 5, 2, 5, 1, 0, 0, 188, 184, 40, 190, 30}}, + {{132, 2, 0, 0x60, 5, 5, 5, 1, 5, 0, 0, 0, 114, 110, 40, 160, 30}}, + {{257, 1, 0, 0x60, 15, 10, 8, 2, 8, 0, 0, 0, 48, 176, 88, 112, 90}}, + {{258, 1, 0, 0x60, 15, 10, 8, 5, 8, 4, 0, 0, 160, 32, 88, 112, 90}}, +#endif +}; + +#if IS_ENABLED(CONFIG_SND_SOC_SUNXI_HDMI) +/* HDMI_FC_AUDSCHNLS7 values */ +static const struct pcm_sf { + u32 sf; + unsigned char cs_sf; +} sf[] = { + {44100, 0x00}, + {48000, 0x02}, + {96000, 0x0a}, + {192000,0x0e}, + {22050, 0x04}, + {24000, 0x06}, + {32000, 0x03}, + {88200, 0x08}, + {768000,0x09}, + {176400,0x0c}, +}; + +static const struct { + int rate; + unsigned short n1, n2; +} n_table[] = { + {32000, 3072, 4096}, + {44100, 4704, 6272}, + {88200, 4704*2, 6272*2}, + {176400,4704*4, 6272*4}, + {48000, 5120, 6144}, + {96000, 5120*2, 6144*2}, + {192000,5120*4, 6144*4}, +}; +#endif + +static inline void hdmi_writeb(struct de2_hdmi_priv *priv, u32 addr, u8 data) +{ + writeb_relaxed(data, priv->mmio + addr); +} + +static inline void hdmi_writel(struct de2_hdmi_priv *priv, u32 addr, u32 data) +{ + writel_relaxed(data, priv->mmio + addr); +} + +static inline u8 hdmi_readb(struct de2_hdmi_priv *priv, u32 addr) +{ + return readb_relaxed(priv->mmio + addr); +} + +static inline u32 hdmi_readl(struct de2_hdmi_priv *priv, u32 addr) +{ + return readl_relaxed(priv->mmio + addr); +} + +static void hdmi_lock(struct de2_hdmi_priv *priv) +{ + hdmi_writeb(priv, 0x10010, 0x45); + hdmi_writeb(priv, 0x10011, 0x45); + hdmi_writeb(priv, 0x10012, 0x52); + hdmi_writeb(priv, 0x10013, 0x54); +} +static void hdmi_unlock(struct de2_hdmi_priv *priv) +{ + hdmi_writeb(priv, 0x10010, 0x52); + hdmi_writeb(priv, 0x10011, 0x54); + hdmi_writeb(priv, 0x10012, 0x41); + hdmi_writeb(priv, 0x10013, 0x57); +} + +static void hdmi_inner_init(struct de2_hdmi_priv *priv) +{ + u8 clkdis = priv->soc_type == SOC_H3 ? + ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE : 0xff; + + hdmi_lock(priv); + + /* software reset */ + hdmi_writeb(priv, R_4002_HDMI_MC_SWRSTZ, 0x00); + udelay(2); + + /* mask all interrupts */ + hdmi_writeb(priv, R_01ff_HDMI_IH_MUTE, 0x00); + hdmi_writeb(priv, R_0807_HDMI_VP_MASK, 0xff); + hdmi_writeb(priv, R_10d2_HDMI_FC_MASK0, 0xff); + hdmi_writeb(priv, R_10d6_HDMI_FC_MASK1, 0xff); + hdmi_writeb(priv, R_10da_HDMI_FC_MASK2, 0xff); + hdmi_writeb(priv, R_3102_HDMI_AUD_INT, 0xff); + hdmi_writeb(priv, R_3302_HDMI_AUD_SPDIFINT, 0xff); + hdmi_writeb(priv, R_3506_HDMI_GP_POL, 0xff); + hdmi_writeb(priv, R_5008_HDMI_A_APIINTMSK, 0xff); + hdmi_writeb(priv, R_7d02_HDMI_CEC_MASK, 0xff); + hdmi_writeb(priv, R_7e05_HDMI_I2CM_INT, 0xff); + hdmi_writeb(priv, R_7e06_HDMI_I2CM_CTLINT, 0xff); + + hdmi_writeb(priv, R_1063_HDMI_FC_AUDSCONF, 0xf0); + hdmi_writeb(priv, R_10b3_HDMI_FC_DATAUTO0, 0x1e); + hdmi_writeb(priv, R_5001_HDMI_A_HDCPCFG1, 0x00); + hdmi_writeb(priv, R_5001_HDMI_A_HDCPCFG1, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE | + HDMI_A_HDCPCFG1_SWRESET); + hdmi_writeb(priv, R_5000_HDMI_A_HDCPCFG0, 0x00); + hdmi_writeb(priv, R_5009_HDMI_A_VIDPOLCFG, + HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH); + hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, clkdis); + hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, 0x00); + hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, clkdis); + hdmi_writeb(priv, R_0100_HDMI_IH_FC_STAT0, 0xff); + hdmi_writeb(priv, R_0101_HDMI_IH_FC_STAT1, 0xff); + hdmi_writeb(priv, R_0102_HDMI_IH_FC_STAT2, 0xff); + hdmi_writeb(priv, R_0103_HDMI_IH_AS_STAT0, 0xff); + hdmi_writeb(priv, R_0105_HDMI_IH_I2CM_STAT0, 0xff); + hdmi_writeb(priv, R_0106_HDMI_IH_CEC_STAT0, 0xff); + hdmi_writeb(priv, R_0107_HDMI_IH_VP_STAT0, 0xff); +} + +static void hdmi_phy_init_a83t(struct de2_hdmi_priv *priv) +{ + hdmi_inner_init(priv); + + hdmi_writeb(priv, 0x10000, 0x01); + hdmi_writeb(priv, 0x10001, 0x00); + hdmi_writeb(priv, 0x10002, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2); + hdmi_writeb(priv, 0x10003, 0x00); + hdmi_writeb(priv, 0x10007, 0xa0); + hdmi_writeb(priv, R_4005_HDMI_MC_PHYRSTZ, + HDMI_MC_PHYRSTZ_DEASSERT); + udelay(1); + hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE | + HDMI_PHY_CONF0_SELDATAENPOL); + hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_PDDQ | + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE | + HDMI_PHY_CONF0_SELDATAENPOL); + hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_PDDQ | + HDMI_PHY_CONF0_SELDATAENPOL); + hdmi_writeb(priv, R_3006_HDMI_PHY_MASK0, 0xf0); + hdmi_writeb(priv, R_3027_HDMI_PHY_I2CM_INT_ADDR, 0xff); + hdmi_writeb(priv, R_3028_HDMI_PHY_I2CM_CTLINT_ADDR, 0xff); + hdmi_writeb(priv, R_0104_HDMI_IH_PHY_STAT0, 0xff); + hdmi_writeb(priv, R_0108_HDMI_IH_I2CMPHY_STAT0, 0xff); + hdmi_writeb(priv, R_4005_HDMI_MC_PHYRSTZ, 0x00); + hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_PDDQ | + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE | + HDMI_PHY_CONF0_SELDATAENPOL); + hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE | + HDMI_PHY_CONF0_SELDATAENPOL); + hdmi_writeb(priv, R_3001_HDMI_PHY_TST0, HDMI_PHY_TST0_TSTCLR); + hdmi_writeb(priv, R_3020_HDMI_PHY_I2CM_SLAVE_ADDR, + HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2); + hdmi_writeb(priv, R_3001_HDMI_PHY_TST0, 0x00); +} + +static void hdmi_phy_init_h3(struct de2_hdmi_priv *priv) +{ + int to_cnt; + u32 tmp; + + hdmi_writel(priv, 0x10020, 0); + hdmi_writel(priv, 0x10020, 1 << 0); + udelay(5); + hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 16)); + hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 1)); + udelay(10); + hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 2)); + udelay(5); + hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 3)); + usleep_range(40, 50); + hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 19)); + usleep_range(100, 120); + hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 18)); + hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (7 << 4)); + + to_cnt = 10; + while (1) { + if (hdmi_readl(priv, 0x10038) & 0x80) + break; + usleep_range(200, 250); + if (--to_cnt == 0) { + pr_warn("hdmi phy init timeout\n"); + break; + } + } + + hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (0xf << 8)); + hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) | (1 << 7)); + + hdmi_writel(priv, 0x1002c, 0x39dc5040); + hdmi_writel(priv, 0x10030, 0x80084343); + msleep(10); + hdmi_writel(priv, 0x10034, 0x00000001); + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | 0x02000000); + msleep(100); + tmp = hdmi_readl(priv, 0x10038); + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | 0xc0000000); + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | + ((tmp >> 11) & 0x3f)); + hdmi_writel(priv, 0x10020, 0x01ff0f7f); + hdmi_writel(priv, 0x10024, 0x80639000); + hdmi_writel(priv, 0x10028, 0x0f81c405); + + hdmi_inner_init(priv); +} + +void hdmi_io_init(struct de2_hdmi_priv *priv) +{ + if (priv->soc_type == SOC_H3) + hdmi_phy_init_h3(priv); + else + hdmi_phy_init_a83t(priv); +} + +static int get_vid(u32 id) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(ptbl); i++) { + if (id == ptbl[i].para[0]) + return i; + } + + return -1; +} + +static void hdmi_i2cm_write(struct de2_hdmi_priv *priv, + int addr, u8 valh, u8 vall) +{ + hdmi_writeb(priv, R_3021_HDMI_PHY_I2CM_ADDRESS_ADDR, addr); + hdmi_writeb(priv, R_3022_HDMI_PHY_I2CM_DATAO_1_ADDR, valh); + hdmi_writeb(priv, R_3023_HDMI_PHY_I2CM_DATAO_0_ADDR, vall); + hdmi_writeb(priv, R_3026_HDMI_PHY_I2CM_OPERATION_ADDR, + HDMI_PHY_I2CM_OPERATION_ADDR_WRITE); + usleep_range(2000, 2500); +} + +static int hdmi_phy_set_a83t(struct de2_hdmi_priv *priv, int i) +{ + switch (ptbl[i].para[1]) { + case 1: + hdmi_i2cm_write(priv, 0x06, 0x00, 0x00); + hdmi_i2cm_write(priv, 0x15, 0x00, 0x0f); + hdmi_i2cm_write(priv, 0x10, 0x00, 0x00); + hdmi_i2cm_write(priv, 0x19, 0x00, 0x02); + hdmi_i2cm_write(priv, 0x0e, 0x00, 0x00); + hdmi_i2cm_write(priv, 0x09, 0x80, 0x2b); + break; + case 2: /* 1080P @ 60 & 50 */ + hdmi_i2cm_write(priv, 0x06, 0x04, 0xa0); + hdmi_i2cm_write(priv, 0x15, 0x00, 0x0a); + hdmi_i2cm_write(priv, 0x10, 0x00, 0x00); + hdmi_i2cm_write(priv, 0x19, 0x00, 0x02); + hdmi_i2cm_write(priv, 0x0e, 0x00, 0x21); + hdmi_i2cm_write(priv, 0x09, 0x80, 0x29); + break; + case 4: /* 720P @ 50 & 60, 1080I, 1080P */ + hdmi_i2cm_write(priv, 0x06, 0x05, 0x40); + hdmi_i2cm_write(priv, 0x15, 0x00, 0x05); + hdmi_i2cm_write(priv, 0x10, 0x00, 0x00); + hdmi_i2cm_write(priv, 0x19, 0x00, 0x07); + hdmi_i2cm_write(priv, 0x0e, 0x02, 0xb5); + hdmi_i2cm_write(priv, 0x09, 0x80, 0x09); + break; + case 11: /* 480P/576P */ + hdmi_i2cm_write(priv, 0x06, 0x01, + ptbl[i].para[2] ? 0xe3 : 0xe0); + hdmi_i2cm_write(priv, 0x15, 0x00, 0x00); + hdmi_i2cm_write(priv, 0x10, 0x08, 0xda); + hdmi_i2cm_write(priv, 0x19, 0x00, 0x07); + hdmi_i2cm_write(priv, 0x0e, 0x03, 0x18); + hdmi_i2cm_write(priv, 0x09, 0x80, 0x09); + break; + default: + return -1; + } + hdmi_i2cm_write(priv, 0x1e, 0x00, 0x00); + hdmi_i2cm_write(priv, 0x13, 0x00, 0x00); + hdmi_i2cm_write(priv, 0x17, 0x00, 0x00); + hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_TXPWRON | + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE | + HDMI_PHY_CONF0_SELDATAENPOL); + + return 0; +} + +static int hdmi_phy_set_h3(struct de2_hdmi_priv *priv, int i) +{ + u32 tmp; + + hdmi_writel(priv, 0x10020, hdmi_readl(priv, 0x10020) & ~0xf000); + switch (ptbl[i].para[1]) { + case 1: + hdmi_writel(priv, 0x1002c, 0x31dc5fc0); /* or 0x30dc5fc0 ? */ + hdmi_writel(priv, 0x10030, 0x800863c0); + msleep(10); + hdmi_writel(priv, 0x10034, 0x00000001); + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | + 0x02000000); + msleep(200); + tmp = (hdmi_readl(priv, 0x10038) >> 11) & 0x3f; + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | + 0xc0000000); + if (tmp < 0x3d) + tmp += 2; + else + tmp = 0x3f; + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | tmp); + msleep(100); + hdmi_writel(priv, 0x10020, 0x01ffff7f); + hdmi_writel(priv, 0x10024, 0x8063b000); + hdmi_writel(priv, 0x10028, 0x0f8246b5); + break; + case 2: /* 1080P @ 60 & 50 */ + hdmi_writel(priv, 0x1002c, 0x39dc5040); + hdmi_writel(priv, 0x10030, 0x80084381); + msleep(10); + hdmi_writel(priv, 0x10034, 0x00000001); + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | + 0x02000000); + msleep(100); + tmp = (hdmi_readl(priv, 0x10038) >> 11) & 0x3f; + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | + 0xc0000000); + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | tmp); + hdmi_writel(priv, 0x10020, 0x01ffff7f); + hdmi_writel(priv, 0x10024, 0x8063a800); + hdmi_writel(priv, 0x10028, 0x0f81c485); + break; + case 4: /* 720P @ 50 & 60, 1080I, 1080P */ + hdmi_writel(priv, 0x1002c, 0x39dc5040); + hdmi_writel(priv, 0x10030, 0x80084343); + msleep(10); + hdmi_writel(priv, 0x10034, 0x00000001); + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | + 0x02000000); + msleep(100); + tmp = (hdmi_readl(priv, 0x10038) >> 11) & 0x3f; + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | + 0xc0000000); + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | tmp); + hdmi_writel(priv, 0x10020, 0x01ffff7f); + hdmi_writel(priv, 0x10024, 0x8063b000); + hdmi_writel(priv, 0x10028, 0x0f81c405); + break; + case 11: /* 480P/576P */ + hdmi_writel(priv, 0x1002c, 0x39dc5040); + hdmi_writel(priv, 0x10030, 0x8008430a); + msleep(10); + hdmi_writel(priv, 0x10034, 0x00000001); + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | + 0x02000000); + msleep(100); + tmp = (hdmi_readl(priv, 0x10038) >> 11) & 0x3f; + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | + 0xc0000000); + hdmi_writel(priv, 0x1002c, hdmi_readl(priv, 0x1002c) | tmp); + hdmi_writel(priv, 0x10020, 0x01ffff7f); + hdmi_writel(priv, 0x10024, 0x8063b000); + hdmi_writel(priv, 0x10028, 0x0f81c405); + break; + default: + return -1; + } + + return 0; +} + +void hdmi_io_video_on(struct de2_hdmi_priv *priv) +{ + if (priv->soc_type == SOC_H3) + hdmi_writel(priv, 0x10020, + hdmi_readl(priv, 0x10020) | (0x0f << 12)); +#if 0 + else + hdmi_writeb(priv, R_1200_HDMI_FC_DBGFORCE, 0); +#endif +} + +void hdmi_io_video_off(struct de2_hdmi_priv *priv) +{ + if (priv->soc_type == SOC_H3) + hdmi_writel(priv, 0x10020, + hdmi_readl(priv, 0x10020) & ~(0x0f << 12)); +#if 0 + else + hdmi_writeb(priv, R_1200_HDMI_FC_DBGFORCE, + HDMI_FC_DBGFORCE_FORCEVIDEO); +#endif +} + +#if IS_ENABLED(CONFIG_SND_SOC_SUNXI_HDMI) +/* start audio */ +int hdmi_io_audio(struct de2_hdmi_priv *priv, + int sample_rate, int sample_bit) +{ + int id = get_vid(priv->cea_mode); /* ptbl index */ + unsigned int i, n; + + if (id < 0) + return id; + + /* (2 channels) */ + hdmi_writeb(priv, R_1063_HDMI_FC_AUDSCONF, 0xf0); + + hdmi_writeb(priv, R_1065_HDMI_FC_AUDSV, ~0x11); + + hdmi_writeb(priv, R_1066_HDMI_FC_AUDSU, 0x00); + hdmi_writeb(priv, R_1067_HDMI_FC_AUDSCHNLS0, + HDMI_FC_AUDSCHNLS0_CGMSA); + hdmi_writeb(priv, R_1068_HDMI_FC_AUDSCHNLS1, 0x00); + hdmi_writeb(priv, R_1069_HDMI_FC_AUDSCHNLS2, 0x01); + hdmi_writeb(priv, R_106a_HDMI_FC_AUDSCHNLS3, + HDMI_FC_AUDSCHNLS3_OIEC_CH0(2) | + HDMI_FC_AUDSCHNLS3_OIEC_CH1(4)); + hdmi_writeb(priv, R_106b_HDMI_FC_AUDSCHNLS4, + HDMI_FC_AUDSCHNLS4_OIEC_CH2(6) | + HDMI_FC_AUDSCHNLS4_OIEC_CH3(8)); + hdmi_writeb(priv, R_106c_HDMI_FC_AUDSCHNLS5, + HDMI_FC_AUDSCHNLS5_OIEC_CH0(1) | + HDMI_FC_AUDSCHNLS5_OIEC_CH1(3)); + hdmi_writeb(priv, R_106d_HDMI_FC_AUDSCHNLS6, + HDMI_FC_AUDSCHNLS6_OIEC_CH2(5) | + HDMI_FC_AUDSCHNLS6_OIEC_CH3(7)); + hdmi_writeb(priv, R_106e_HDMI_FC_AUDSCHNLS7, 0x01); + for (i = 0; i < ARRAY_SIZE(sf); i++) { + if (sample_rate == sf[i].sf) { + hdmi_writeb(priv, R_106e_HDMI_FC_AUDSCHNLS7, + sf[i].cs_sf); + break; + } + } + hdmi_writeb(priv, R_106f_HDMI_FC_AUDSCHNLS8, + (sample_bit == 16) ? HDMI_FC_AUDSCHNLS8_WORDLENGTH(2) : + (sample_bit == 24 ? HDMI_FC_AUDSCHNLS8_WORDLENGTH(11) : + HDMI_FC_AUDSCHNLS8_WORDLENGTH(0))); + + hdmi_writeb(priv, R_3101_HDMI_AUD_CONF1, sample_bit); + + n = 6272; + for (i = 0; i < ARRAY_SIZE(n_table); i++) { + if (sample_rate == n_table[i].rate) { + if (ptbl[id].para[1] == 1) + n = n_table[i].n1; + else + n = n_table[i].n2; + break; + } + } + + hdmi_writeb(priv, R_3200_HDMI_AUD_N1, n); + hdmi_writeb(priv, R_3201_HDMI_AUD_N2, n >> 8); + hdmi_writeb(priv, R_3202_HDMI_AUD_N3, n >> 16); + hdmi_writeb(priv, R_3205_HDMI_AUD_CTS3, 0x00); + hdmi_writeb(priv, R_3206_HDMI_AUD_INPUTCLKFS, 0x04); + hdmi_writeb(priv, R_1063_HDMI_FC_AUDSCONF, 0x00); /* layout0 */ + hdmi_writeb(priv, R_1025_HDMI_FC_AUDICONF0, 2 * 16); /* 2 channels */ + hdmi_writeb(priv, R_1026_HDMI_FC_AUDICONF1, 0x00); + hdmi_writeb(priv, R_1027_HDMI_FC_AUDICONF2, 0x00); + hdmi_writeb(priv, R_1028_HDMI_FC_AUDICONF3, 0x00); + + hdmi_writeb(priv, R_3103_HDMI_AUD_CONF2, 0x00); /* PCM only */ + + hdmi_writeb(priv, R_3100_HDMI_AUD_CONF0, 0x00); + hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, + HDMI_MC_CLKDIS_AUDCLK_DISABLE); + hdmi_writeb(priv, R_4002_HDMI_MC_SWRSTZ, 0xf7); + usleep_range(100, 120); + hdmi_writeb(priv, R_3100_HDMI_AUD_CONF0, 0xaf); + usleep_range(100, 120); + hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, 0x00); /* enable all clocks */ + + return 0; +} +#endif + + +/* initialize */ +int hdmi_io_video_mode(struct de2_hdmi_priv *priv) +{ + int i = get_vid(priv->cea_mode); /* ptbl index */ + u8 avi_d2; /* AVI InfoFrame Data Byte 2 */ + + if (i < 0) + return i; + + /* colorimetry and aspect ratio */ + switch (priv->cea_mode) { + case 2: /* 480P */ + case 6: /* 1440x480I */ + case 17: /* 576P */ + case 21: /* 1440x576I */ + avi_d2 = (HDMI_COLORIMETRY_ITU_601 << 6) | + (HDMI_PICTURE_ASPECT_4_3 << 4) | 0x08; + break; + default: + avi_d2 = (HDMI_COLORIMETRY_ITU_709 << 6) | + (HDMI_PICTURE_ASPECT_16_9 << 4) | 0x08; + break; + } + if (priv->soc_type == SOC_H3) { + if (hdmi_phy_set_h3(priv, i) != 0) + return -1; + hdmi_inner_init(priv); + } else { + hdmi_io_init(priv); + } + + hdmi_writeb(priv, R_1200_HDMI_FC_DBGFORCE, + HDMI_FC_DBGFORCE_FORCEVIDEO); + hdmi_writeb(priv, R_1219_HDMI_FC_DBGTMDS0, 0x00); + hdmi_writeb(priv, R_1000_HDMI_FC_INVIDCONF, ptbl[i].para[3] | + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH); + hdmi_writeb(priv, 0x10001, ptbl[i].para[3] < 0x60 ? 0x03 : 0x00); + hdmi_writeb(priv, R_1002_HDMI_FC_INHACTV1, ptbl[i].para[4]); + hdmi_writeb(priv, R_100d_HDMI_FC_VSYNCINWIDTH, ptbl[i].para[5]); + hdmi_writeb(priv, R_1006_HDMI_FC_INVACTV1, ptbl[i].para[6]); + hdmi_writeb(priv, R_1004_HDMI_FC_INHBLANK1, ptbl[i].para[7]); + hdmi_writeb(priv, R_100c_HDMI_FC_VSYNCINDELAY, ptbl[i].para[8]); + hdmi_writeb(priv, R_1009_HDMI_FC_HSYNCINDELAY1, ptbl[i].para[9]); + hdmi_writeb(priv, R_100b_HDMI_FC_HSYNCINWIDTH1, ptbl[i].para[10]); + hdmi_writeb(priv, R_1001_HDMI_FC_INHACTV0, ptbl[i].para[11]); + hdmi_writeb(priv, R_1003_HDMI_FC_INHBLANK0, ptbl[i].para[12]); + hdmi_writeb(priv, R_1008_HDMI_FC_HSYNCINDELAY0, ptbl[i].para[13]); + hdmi_writeb(priv, R_100a_HDMI_FC_HSYNCINWIDTH1, ptbl[i].para[14]); + hdmi_writeb(priv, R_1005_HDMI_FC_INVACTV0, ptbl[i].para[15]); + hdmi_writeb(priv, R_1007_HDMI_FC_INVBLANK, ptbl[i].para[16]); + hdmi_writeb(priv, R_1011_HDMI_FC_CTRLDUR, 12); + hdmi_writeb(priv, R_1012_HDMI_FC_EXCTRLDUR, 32); + hdmi_writeb(priv, R_1013_HDMI_FC_EXCTRLSPAC, 1); + hdmi_writeb(priv, R_1014_HDMI_FC_CH0PREAM, 0x0b); + hdmi_writeb(priv, R_1015_HDMI_FC_CH1PREAM, 0x16); + hdmi_writeb(priv, R_1016_HDMI_FC_CH2PREAM, 0x21); + hdmi_writeb(priv, R_10e0_HDMI_FC_PRCONF, ptbl[i].para[2] ? 0x21 : 0x10); + hdmi_writeb(priv, R_0801_HDMI_VP_PR_CD, ptbl[i].para[2] ? 0x41 : 0x40); + hdmi_writeb(priv, R_0802_HDMI_VP_STUFF, 0x07); + hdmi_writeb(priv, R_0803_HDMI_VP_REMAP, 0x00); + hdmi_writeb(priv, R_0804_HDMI_VP_CONF, 0x47); + hdmi_writeb(priv, R_0200_HDMI_TX_INVID0, 0x01); + hdmi_writeb(priv, R_0201_HDMI_TX_INSTUFFING, 0x07); + hdmi_writeb(priv, R_0202_HDMI_TX_GYDATA0, 0x00); + hdmi_writeb(priv, R_0203_HDMI_TX_GYDATA1, 0x00); + hdmi_writeb(priv, R_0204_HDMI_TX_RCRDATA0, 0x00); + hdmi_writeb(priv, R_0205_HDMI_TX_RCRDATA1, 0x00); + hdmi_writeb(priv, R_0206_HDMI_TX_BCBDATA0, 0x00); + hdmi_writeb(priv, R_0207_HDMI_TX_BCBDATA1, 0x00); + + if (priv->codec.eld) { /* if audio/HDMI */ + hdmi_writeb(priv, R_10b3_HDMI_FC_DATAUTO0, 0x08); + hdmi_writeb(priv, R_1031_HDMI_FC_VSDIEEEID2, 0x00); + hdmi_writeb(priv, R_1030_HDMI_FC_VSDIEEEID1, + HDMI_IEEE_OUI >> 8); + hdmi_writeb(priv, R_1029_HDMI_FC_VSDIEEEID0, + HDMI_IEEE_OUI & 0xff); + hdmi_writeb(priv, R_1032_HDMI_FC_VSDPAYLOAD0, 0x00); + hdmi_writeb(priv, R_1033_HDMI_FC_VSDPAYLOAD1, 0x00); + hdmi_writeb(priv, R_1034_HDMI_FC_VSDPAYLOAD2, 0x00); + hdmi_writeb(priv, R_10b4_HDMI_FC_DATAUTO1, 0x01); + hdmi_writeb(priv, R_10b5_HDMI_FC_DATAUTO2, 0x11); + hdmi_writeb(priv, R_1018_HDMI_FC_GCP, 0x00); + hdmi_writeb(priv, R_1104_HDMI_FC_GMD_HB, 0x00); + hdmi_writeb(priv, R_1103_HDMI_FC_GMD_CONF, 0x11); + + hdmi_lock(priv); + hdmi_writeb(priv, R_1000_HDMI_FC_INVIDCONF, + hdmi_readb(priv, R_1000_HDMI_FC_INVIDCONF) | 0x08); + hdmi_unlock(priv); + + /* AVI */ + hdmi_writeb(priv, R_1019_HDMI_FC_AVICONF0, + HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN); + hdmi_writeb(priv, R_101a_HDMI_FC_AVICONF1, avi_d2); + hdmi_writeb(priv, R_101b_HDMI_FC_AVICONF2, 0x08); + hdmi_writeb(priv, R_101c_HDMI_FC_AVIVID, priv->cea_mode); + } + + hdmi_writeb(priv, R_4004_HDMI_MC_FLOWCTRL, 0x00); + hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, 0x00); /* enable all clocks */ + + if (priv->soc_type != SOC_H3) { + if (hdmi_phy_set_a83t(priv, i) != 0) + return -1; + } + + hdmi_writeb(priv, R_1200_HDMI_FC_DBGFORCE, 0x00); + + return 0; +} + +/* get a block of EDID */ +int hdmi_io_ddc_read(struct de2_hdmi_priv *priv, + char pointer, char off, + int nbyte, char *pbuf) +{ + unsigned to_cnt; + u8 reg; + int ret = 0; + + hdmi_lock(priv); + hdmi_writeb(priv, R_7e09_HDMI_I2CM_SOFTRSTZ, 0x00); + to_cnt = 50; + while (!(hdmi_readb(priv, R_7e09_HDMI_I2CM_SOFTRSTZ) & 0x01)) { + udelay(10); + if (--to_cnt == 0) { /* wait for 500us for timeout */ + pr_warn("hdmi ddc reset timeout\n"); + break; + } + } + + hdmi_writeb(priv, R_7e07_HDMI_I2CM_DIV, 0x05); + hdmi_writeb(priv, R_7e05_HDMI_I2CM_INT, 0x08); + hdmi_writeb(priv, R_7e0c_HDMI_I2CM_SS_SCL_HCNT_0_ADDR, 0xd8); + hdmi_writeb(priv, R_7e0e_HDMI_I2CM_SS_SCL_LCNT_0_ADDR, 0xfe); + + while (nbyte > 0) { + hdmi_writeb(priv, R_7e00_HDMI_I2CM_SLAVE, 0xa0 >> 1); + hdmi_writeb(priv, R_7e01_HDMI_I2CM_ADDRESS, off); + hdmi_writeb(priv, R_7e08_HDMI_I2CM_SEGADDR, 0x60 >> 1); + hdmi_writeb(priv, R_7e0a_HDMI_I2CM_SEGPTR, pointer); + hdmi_writeb(priv, R_7e04_HDMI_I2CM_OPERATION, + HDMI_I2CM_OPERATION_DDC_READ); + + to_cnt = 200; /* timeout 100ms */ + while (1) { + reg = hdmi_readb(priv, R_0105_HDMI_IH_I2CM_STAT0); + hdmi_writeb(priv, R_0105_HDMI_IH_I2CM_STAT0, reg); + if (reg & 0x02) { + *pbuf++ = hdmi_readb(priv, R_7e03_HDMI_I2CM_DATAI); + break; + } + if (reg & 0x01) { + pr_warn("hdmi ddc read error\n"); + ret = -1; + break; + } + if (--to_cnt == 0) { + if (!ret) { + pr_warn("hdmi ddc read timeout\n"); + ret = -1; + } + break; + } + usleep_range(800, 1000); + } + if (ret) + break; + nbyte--; + off++; + } + hdmi_unlock(priv); + + return ret; +} + +int hdmi_io_get_hpd(struct de2_hdmi_priv *priv) +{ + int ret; + + hdmi_lock(priv); + + if (priv->soc_type == SOC_H3) + ret = hdmi_readl(priv, 0x10038) & 0x80000; + else + ret = hdmi_readb(priv, R_3005_HDMI_PHY_INT0) & 0x02; + + hdmi_unlock(priv); + + return ret != 0; +} + +void hdmi_io_reset(struct de2_hdmi_priv *priv) +{ + hdmi_writeb(priv, R_5001_HDMI_A_HDCPCFG1, + HDMI_A_HDCPCFG1_PH2UPSHFTENC); + hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, + HDMI_MC_CLKDIS_HDCPCLK_DISABLE); +} + +int hdmi_io_mode_valid(int cea_mode) +{ + return get_vid(cea_mode); +} diff --git a/drivers/gpu/drm/sunxi/de2_hdmi_io.h b/drivers/gpu/drm/sunxi/de2_hdmi_io.h new file mode 100644 index 0000000..2577f0b --- /dev/null +++ b/drivers/gpu/drm/sunxi/de2_hdmi_io.h @@ -0,0 +1,25 @@ +#ifndef __DE2_HDMI_IO_H__ +#define __DE2_HDMI_IO_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. + */ + +void hdmi_io_video_on(struct de2_hdmi_priv *priv); +void hdmi_io_video_off(struct de2_hdmi_priv *priv); +int hdmi_io_audio(struct de2_hdmi_priv *priv, + int sample_rate, int sample_bit); +int hdmi_io_video_mode(struct de2_hdmi_priv *priv); +int hdmi_io_ddc_read(struct de2_hdmi_priv *priv, + char pointer, char offset, + int nbyte, char *pbuf); +int hdmi_io_get_hpd(struct de2_hdmi_priv *priv); +void hdmi_io_init(struct de2_hdmi_priv *priv); +void hdmi_io_reset(struct de2_hdmi_priv *priv); +int hdmi_io_mode_valid(int cea_mode); + +#endif /* __DE2_HDMI_IO_H__ */
Hi,
On Fri, Oct 21, 2016 at 10:08:06AM +0200, Jean-Francois Moine wrote:
This patch adds a HDMI driver to the DE2 based Allwinner's SoCs as A83T and H3. Audio and video are supported.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Output from checkpatch: total: 26 errors, 31 warnings, 70 checks, 1458 lines checked
Please run checkpatch before sending your patches.
Apart from that, this looks an awful lot like the designware HDMI controller, for which a driver already exists. Any reason to create a new driver from scratch?
Maxime
On Fri, Oct 21, 2016 at 10:08:06AM +0200, Jean-Francois Moine wrote:
This patch adds a HDMI driver to the DE2 based Allwinner's SoCs as A83T and H3. Audio and video are supported.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
.../devicetree/bindings/display/sunxi/hdmi.txt | 52 ++ drivers/gpu/drm/sunxi/Kconfig | 8 + drivers/gpu/drm/sunxi/Makefile | 2 + drivers/gpu/drm/sunxi/de2_hdmi.c | 396 +++++++++ drivers/gpu/drm/sunxi/de2_hdmi.h | 40 + drivers/gpu/drm/sunxi/de2_hdmi_io.c | 927 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_hdmi_io.h | 25 + 7 files changed, 1450 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/hdmi.txt create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.c create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.h create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_io.c create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_io.h
diff --git a/Documentation/devicetree/bindings/display/sunxi/hdmi.txt b/Documentation/devicetree/bindings/display/sunxi/hdmi.txt new file mode 100644 index 0000000..0558c07 --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/hdmi.txt @@ -0,0 +1,52 @@ +Allwinner HDMI Transmitter +==========================
+The Allwinner HDMI transmitters are included in the SoCs. +They support audio and video.
+Required properties:
- #address-cells : should be <1>
- #size-cells : should be <0>
- compatible : should be
"allwinner,sun8i-a83t-hdmi" or
"allwinner,sun8i-h3-hdmi"
- clocks : phandles to the HDMI clocks as described in
- Documentation/devicetree/bindings/clock/clock-bindings.txt
- clock-names : must be
"gate" : bus gate
"clock" : streaming clock
"ddc-clock" : DDC clock
- resets : One or two phandles to the HDMI resets
- reset-names : must be
"hdmi0" and "hdmi1"
+Required nodes:
- port: Audio and video input port nodes with endpoint definitions
- as defined in Documentation/devicetree/bindings/graph.txt.
Please define which port number is audio and which is video.
Rob
This patch adds I2S support to sun8i SoCs as the A83T and H3.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- Note: This driver is closed to the sun4i-i2s except that: - it handles the H3 - it creates the sound card (with sun4i-i2s, the sound card is created by the CODECs) --- .../devicetree/bindings/sound/sun4i-i2s.txt | 38 +- sound/soc/sunxi/Kconfig | 8 + sound/soc/sunxi/Makefile | 3 + sound/soc/sunxi/sun8i-i2s.c | 700 +++++++++++++++++++++ 4 files changed, 744 insertions(+), 5 deletions(-) create mode 100644 sound/soc/sunxi/sun8i-i2s.c
diff --git a/Documentation/devicetree/bindings/sound/sun4i-i2s.txt b/Documentation/devicetree/bindings/sound/sun4i-i2s.txt index 7b526ec..2fb0a7a 100644 --- a/Documentation/devicetree/bindings/sound/sun4i-i2s.txt +++ b/Documentation/devicetree/bindings/sound/sun4i-i2s.txt @@ -1,4 +1,4 @@ -* Allwinner A10 I2S controller +* Allwinner A10/A38T/H3 I2S controller
The I2S bus (Inter-IC sound bus) is a serial link for digital audio data transfer between devices in the system. @@ -6,20 +6,30 @@ audio data transfer between devices in the system. Required properties:
- compatible: should be one of the followings - - "allwinner,sun4i-a10-i2s" + - "allwinner,sun4i-a10-i2s" + "allwinner,sun8i-a83t-i2s" + "allwinner,sun8i-h3-i2s" - reg: physical base address of the controller and length of memory mapped region. -- interrupts: should contain the I2S interrupt. - dmas: DMA specifiers for tx and rx dma. See the DMA client binding, Documentation/devicetree/bindings/dma/dma.txt -- dma-names: should include "tx" and "rx". +- dma-names: must include "tx" and/or "rx". - clocks: a list of phandle + clock-specifer pairs, one for each entry in clock-names. - clock-names: should contain followings: - "apb" : clock for the I2S bus interface - "mod" : module clock for the I2S controller - #sound-dai-cells : Must be equal to 0
-Example: +Optional properties: + +- interrupts: I2S interrupt +- resets: phandle to the reset of the device + +Required nodes: + + - port: link to the associated CODEC (DAC, HDMI...) + +Example 1:
i2s0: i2s@01c22400 { #sound-dai-cells = <0>; @@ -32,3 +42,21 @@ i2s0: i2s@01c22400 { <&dma SUN4I_DMA_NORMAL 3>; dma-names = "rx", "tx"; }; + +Example 2: + +i2s2: i2s@1c22800 { + compatible = "allwinner,sun8i-a83t-i2s"; + reg = <0x01c22800 0x60>; + clocks = <&ccu CLK_BUS_I2S2>, <&ccu CLK_I2S2>; + clock-names = "apb", "mod"; + resets = <&ccu RST_I2S2>; + dmas = <&dma 27>; + dma-names = "tx"; + status = "disabled"; + port { + i2s2_hdmi: endpoint { + remote-endpoint = <&hdmi_i2s2>; + }; + }; +}; diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index dd23682..d89b2da 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -26,4 +26,12 @@ config SND_SUN4I_SPDIF help Say Y or M to add support for the S/PDIF audio block in the Allwinner A10 and affiliated SoCs. + +config SND_SUN8I_I2S + tristate "Allwinner sun8i I2S Support" + depends on OF + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for SoC audio on + Allwinner sun8i boards. endmenu diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile index 604c7b84..bcb871b 100644 --- a/sound/soc/sunxi/Makefile +++ b/sound/soc/sunxi/Makefile @@ -1,3 +1,6 @@ obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o + +snd-soc-sun8i-i2s-objs := sun8i-i2s.o +obj-$(CONFIG_SND_SUN8I_I2S) += snd-soc-sun8i-i2s.o diff --git a/sound/soc/sunxi/sun8i-i2s.c b/sound/soc/sunxi/sun8i-i2s.c new file mode 100644 index 0000000..ba15d62 --- /dev/null +++ b/sound/soc/sunxi/sun8i-i2s.c @@ -0,0 +1,700 @@ +/* + * Allwinner sun8i I2S sound card + * + * 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/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/reset.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> + +/* --- hardware --- */ + +#define I2S_CTL 0x00 + /* common */ + #define I2S_CTL_SDO3EN BIT(11) + #define I2S_CTL_SDO2EN BIT(10) + #define I2S_CTL_SDO1EN BIT(9) + #define I2S_CTL_SDO0EN BIT(8) + #define I2S_CTL_TXEN BIT(2) + #define I2S_CTL_RXEN BIT(1) + #define I2S_CTL_GEN BIT(0) + /* a83t */ + #define I2S_CTL_A83T_MS BIT(5) + #define I2S_CTL_A83T_PCM BIT(4) + /* h3 */ + #define I2S_CTL_H3_BCLKOUT BIT(18) + #define I2S_CTL_H3_LRCKOUT BIT(17) + #define I2S_CTL_H3_MODE_MSK (3 << 4) + #define I2S_CTL_H3_MODE_I2S (1 << 4) + +#define I2S_FAT0 0x04 + /* common */ + /* a83t */ + #define I2S_FAT0_A83T_LRCP BIT(7) + #define I2S_FAT0_A83T_BCP BIT(6) + #define I2S_FAT0_A83T_SR_16BIT (0 << 4) + #define I2S_FAT0_A83T_SR_24BIT (2 << 4) + #define I2S_FAT0_A83T_SR_MSK (3 << 4) + #define I2S_FAT0_A83T_WSS_32BCLK (3 << 2) + #define I2S_FAT0_A83T_FMT_I2S1 (0 << 0) + #define I2S_FAT0_A83T_FMT_LFT (1 << 0) + #define I2S_FAT0_A83T_FMT_RGT (2 << 0) + #define I2S_FAT0_A83T_FMT_MSK (3 << 0) + /* h3 */ + #define I2S_FAT0_H3_LRCKR_PERIOD(v) ((v) << 20) + #define I2S_FAT0_H3_LRCKR_PERIOD_MSK (0x3ff << 20) + #define I2S_FAT0_H3_LRCK_POLARITY BIT(19) + #define I2S_FAT0_H3_LRCK_PERIOD(v) ((v) << 8) + #define I2S_FAT0_H3_LRCK_PERIOD_MSK (0x3ff << 8) + #define I2S_FAT0_H3_BCLK_POLARITY BIT(7) + #define I2S_FAT0_H3_SR_16 (3 << 4) + #define I2S_FAT0_H3_SR_24 (5 << 4) + #define I2S_FAT0_H3_SR_MSK (7 << 4) + #define I2S_FAT0_H3_SW_16 (3 << 0) + #define I2S_FAT0_H3_SW_32 (7 << 0) + #define I2S_FAT0_H3_SW_MSK (7 << 0) + +#define I2S_FAT1 0x08 + +#define I2S_FCTL 0x14 + #define I2S_FCTL_FTX BIT(25) + #define I2S_FCTL_FRX BIT(24) + #define I2S_FCTL_TXTL(v) ((v) << 12) + #define I2S_FCTL_TXIM BIT(2) + +#define I2S_INT 0x1c + #define I2S_INT_TXDRQEN BIT(7) + +#define I2S_TXFIFO 0x20 + +#define I2S_CLKD 0x24 + /* common */ + #define I2S_CLKD_BCLKDIV(v) ((v) << 4) + #define I2S_CLKD_MCLKDIV(v) ((v) << 0) + /* a83t */ + #define I2S_CLKD_A83T_MCLKOEN BIT(7) + /* h3 */ + #define I2S_CLKD_H3_MCLKOEN BIT(8) + +#define I2S_TXCNT 0x28 + +#define I2S_RXCNT 0x2c + +/* --- A83T --- */ +#define I2S_TXCHSEL_A83T 0x30 + #define I2S_TXCHSEL_A83T_CHNUM(v) (((v) - 1) << 0) + #define I2S_TXCHSEL_A83T_CHNUM_MSK (7 << 0) + +#define I2S_TXCHMAP_A83T 0x34 + +/* --- H3 --- */ +#define I2S_TXCHCFG_H3 0x30 + #define I2S_TXCHCFG_H3_TX_SLOT_NUM_MSK (7 << 0) + #define I2S_TXCHCFG_H3_TX_SLOT_NUM(v) ((v) << 0) + +#define I2S_TX0CHSEL_H3 0x34 /* 0..3 */ + #define I2S_TXn_H3_OFFSET_MSK (3 << 12) + #define I2S_TXn_H3_OFFSET(v) ((v) << 12) + #define I2S_TXn_H3_CHEN_MSK (0xff << 4) + #define I2S_TXn_H3_CHEN(v) ((v) << 4) + #define I2S_TXn_H3_CHSEL_MSK (7 << 0) + #define I2S_TXn_H3_CHSEL(v) ((v) << 0) + +#define I2S_TX0CHMAP_H3 0x44 /* 0..3 */ + +/* --- driver --- */ + +#define DRV_NAME "sun8i-audio" + +#define I2S_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define PCM_LRCK_PERIOD 32 +#define PCM_LRCKR_PERIOD 1 + +struct priv { + void __iomem *mmio; + struct clk *clk; + struct clk *gate; + struct reset_control *rstc; + int type; +#define SOC_A83T 0 +#define SOC_H3 1 + struct snd_dmaengine_dai_dma_data dma_data; +}; + +static const struct of_device_id sun8i_i2s_of_match[] = { + { .compatible = "allwinner,sun8i-a83t-i2s", + .data = (void *) SOC_A83T }, + { .compatible = "allwinner,sun8i-h3-i2s", + .data = (void *) SOC_H3 }, + { } +}; +MODULE_DEVICE_TABLE(of, sun8i_i2s_of_match); + +/* --- CPU DAI --- */ + +static void sun8i_i2s_init(struct priv *priv) +{ + u32 reg; + + /* disable global */ + reg = readl(priv->mmio + I2S_CTL); + reg &= ~(I2S_CTL_GEN | + I2S_CTL_RXEN | + I2S_CTL_TXEN); + writel(reg, priv->mmio + I2S_CTL); + + /* A83T */ + if (priv->type == SOC_A83T) { + reg &= ~(I2S_CTL_A83T_MS | /* codec clk & FRM slave */ + I2S_CTL_A83T_PCM); /* I2S mode */ + writel(reg, priv->mmio + I2S_CTL); + + reg = readl(priv->mmio + I2S_FAT0); + reg &= ~I2S_FAT0_A83T_FMT_MSK; + reg |= I2S_FAT0_A83T_FMT_I2S1; + + reg &= ~(I2S_FAT0_A83T_LRCP | I2S_FAT0_A83T_BCP); + writel(reg, priv->mmio + I2S_FAT0); + + reg = I2S_FCTL_TXIM | /* fifo */ + I2S_FCTL_TXTL(0x40); + writel(reg, priv->mmio + I2S_FCTL); + + reg = readl(priv->mmio + I2S_FAT0); + reg &= ~(I2S_FAT0_A83T_LRCP | /* normal bit clock + frame */ + I2S_FAT0_A83T_BCP); + writel(reg, priv->mmio + I2S_FAT0); + + /* H3 */ + } else { + reg = readl(priv->mmio + I2S_FCTL); + reg &= ~(I2S_FCTL_FRX | I2S_FCTL_FTX); /* clear the FIFOs */ + writel(reg, priv->mmio + I2S_FCTL); + + writel(0, priv->mmio + I2S_TXCNT); /* FIFO counters */ + writel(0, priv->mmio + I2S_RXCNT); + + reg = readl(priv->mmio + I2S_CTL); + reg |= I2S_CTL_H3_LRCKOUT | I2S_CTL_H3_BCLKOUT; + /* codec clk & FRM slave */ + + reg &= ~I2S_CTL_H3_MODE_MSK; + reg |= I2S_CTL_H3_MODE_I2S; /* I2S mode */ + writel(reg, priv->mmio + I2S_CTL); + + reg = readl(priv->mmio + I2S_TX0CHSEL_H3) & + ~I2S_TXn_H3_OFFSET_MSK; + reg |= I2S_TXn_H3_OFFSET(1); + writel(reg, priv->mmio + I2S_TX0CHSEL_H3); + + reg = readl(priv->mmio + I2S_FAT0); + reg &= ~(I2S_FAT0_H3_BCLK_POLARITY | /* normal bclk & frame */ + I2S_FAT0_H3_LRCK_POLARITY); + writel(reg, priv->mmio + I2S_FAT0); + } +} + +static int sun8i_i2s_set_clock(struct priv *priv, + unsigned long rate) +{ + unsigned long freq; + int ret, i, div; + u32 reg; + static const u8 div_tb[] = { + 1, 2, 4, 6, 8, 12, 16, 24, + }; + + /* compute the sys clock rate and divide values */ + if (rate % 1000 == 0) + freq = 24576000; + else + freq = 22579200; + div = freq / 2 / PCM_LRCK_PERIOD / rate; + if (priv->type == SOC_A83T) + div /= 2; /* bclk_div==0 => mclk/2 */ + for (i = 0; i < ARRAY_SIZE(div_tb) - 1; i++) + if (div_tb[i] >= div) + break; + + ret = clk_set_rate(priv->clk, freq); + if (ret) { + pr_info("Setting sysclk rate failed %d\n", ret); + return ret; + } + + /* set the mclk and bclk dividor register */ + if (priv->type == SOC_A83T) { + reg = I2S_CLKD_A83T_MCLKOEN | I2S_CLKD_MCLKDIV(i); + } else { + reg = I2S_CLKD_H3_MCLKOEN | I2S_CLKD_MCLKDIV(1) | + I2S_CLKD_BCLKDIV(i + 1); + } + writel(reg, priv->mmio + I2S_CLKD); + + /* format */ + reg = readl(priv->mmio + I2S_FAT0); + if (priv->type == SOC_A83T) { + reg |= I2S_FAT0_A83T_WSS_32BCLK; + reg &= ~I2S_FAT0_A83T_SR_MSK; + reg |= I2S_FAT0_A83T_SR_16BIT; + } else { + reg &= ~(I2S_FAT0_H3_LRCKR_PERIOD_MSK | + I2S_FAT0_H3_LRCK_PERIOD_MSK); + reg |= I2S_FAT0_H3_LRCK_PERIOD(PCM_LRCK_PERIOD - 1) | + I2S_FAT0_H3_LRCKR_PERIOD(PCM_LRCKR_PERIOD - 1); + + reg &= ~I2S_FAT0_H3_SW_MSK; + reg |= I2S_FAT0_H3_SW_16; + + reg &= ~I2S_FAT0_H3_SR_MSK; + reg |= I2S_FAT0_H3_SR_16; + } + writel(reg, priv->mmio + I2S_FAT0); + + writel(0, priv->mmio + I2S_FAT1); + + return 0; +} + +static int sun8i_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); + struct priv *priv = snd_soc_card_get_drvdata(card); + int nchan = substream->runtime->channels; + u32 reg; + + if (priv->type == SOC_A83T) { + reg = readl(priv->mmio + I2S_TXCHSEL_A83T); + reg &= ~I2S_TXCHSEL_A83T_CHNUM_MSK; + reg |= I2S_TXCHSEL_A83T_CHNUM(substream->runtime->channels); + writel(reg, priv->mmio + I2S_TXCHSEL_A83T); + + switch (substream->runtime->channels) { + case 1: + reg = 0x76543200; + break; + case 8: + reg = 0x54762310; + break; + default: +/* left/right inversion of channels 0 and 1 */ + reg = 0x76543201; + break; + } + writel(reg, priv->mmio + I2S_TXCHMAP_A83T); + } else { + reg = readl(priv->mmio + I2S_TXCHCFG_H3) & + ~I2S_TXCHCFG_H3_TX_SLOT_NUM_MSK; + if (nchan != 1) + reg |= I2S_TXCHCFG_H3_TX_SLOT_NUM(1); + writel(reg, priv->mmio + I2S_TXCHCFG_H3); + + reg = readl(priv->mmio + I2S_TX0CHSEL_H3); + reg &= ~(I2S_TXn_H3_CHEN_MSK | + I2S_TXn_H3_CHSEL_MSK); + reg |= I2S_TXn_H3_CHEN(3) | + I2S_TXn_H3_CHSEL(1); + writel(reg, priv->mmio + I2S_TX0CHSEL_H3); + + reg = nchan == 1 ? 0 : 0x10; + writel(reg, priv->mmio + I2S_TX0CHMAP_H3); + } + + reg = readl(priv->mmio + I2S_CTL); + reg &= ~(I2S_CTL_SDO3EN | + I2S_CTL_SDO2EN | + I2S_CTL_SDO1EN); + if (nchan >= 7) + reg |= I2S_CTL_SDO3EN; + if (nchan >= 5) + reg |= I2S_CTL_SDO2EN; + if (nchan >= 3) + reg |= I2S_CTL_SDO1EN; + reg |= I2S_CTL_SDO0EN; + writel(reg, priv->mmio + I2S_CTL); + + writel(0, priv->mmio + I2S_TXCNT); + + return 0; +} + +static void sun8i_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); + struct priv *priv = snd_soc_card_get_drvdata(card); + u32 reg; + + reg = readl(priv->mmio + I2S_CTL); + reg &= ~I2S_CTL_GEN; + writel(reg, priv->mmio + I2S_CTL); +} + +static int sun8i_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); + struct priv *priv = snd_soc_card_get_drvdata(card); + u32 reg, reg2; + int sample_resolution; + int ret; + + ret = sun8i_i2s_set_clock(priv, params_rate(params)); + if (ret) + return ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + priv->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + sample_resolution = 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + priv->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + sample_resolution = 24; + break; + default: + return -EINVAL; + } + reg = readl(priv->mmio + I2S_FAT0); + reg2 = readl(priv->mmio + I2S_FCTL); + if (priv->type == SOC_A83T) { + reg &= ~I2S_FAT0_A83T_SR_MSK; + if (sample_resolution == 16) { + reg |= I2S_FAT0_A83T_SR_16BIT; + reg2 |= I2S_FCTL_TXIM; + } else { + reg |= I2S_FAT0_A83T_SR_24BIT; + reg2 &= ~I2S_FCTL_TXIM; + } + } else { + reg &= ~(I2S_FAT0_H3_SR_MSK | I2S_FAT0_H3_SW_MSK); + if (sample_resolution == 16) { + reg |= I2S_FAT0_H3_SR_16 | + I2S_FAT0_H3_SW_16; + reg2 |= I2S_FCTL_TXIM; + } else { + reg |= I2S_FAT0_H3_SR_24 | + I2S_FAT0_H3_SW_32; + reg2 &= ~I2S_FCTL_TXIM; + } + } + writel(reg, priv->mmio + I2S_FAT0); + writel(reg2, priv->mmio + I2S_FCTL); + + /* enable audio interface */ + reg = readl(priv->mmio + I2S_CTL); + reg |= I2S_CTL_GEN; + writel(reg, priv->mmio + I2S_CTL); + msleep(10); + + /* flush TX FIFO */ + reg = readl(priv->mmio + I2S_FCTL); + reg |= I2S_FCTL_FTX; + writel(reg, priv->mmio + I2S_FCTL); + + return 0; +} + +static int sun8i_i2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); + struct priv *priv = snd_soc_card_get_drvdata(card); + u32 reg; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg = readl(priv->mmio + I2S_CTL); + reg |= I2S_CTL_TXEN; + writel(reg, priv->mmio + I2S_CTL); + + /* enable DMA DRQ mode */ + reg = readl(priv->mmio + I2S_INT); + reg |= I2S_INT_TXDRQEN; + writel(reg, priv->mmio + I2S_INT); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + reg = readl(priv->mmio + I2S_INT); + reg &= ~I2S_INT_TXDRQEN; + writel(reg, priv->mmio + I2S_INT); + + reg = readl(priv->mmio + I2S_CTL); + reg &= ~I2S_CTL_TXEN; + writel(reg, priv->mmio + I2S_CTL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops sun8i_i2s_dai_ops = { + .hw_params = sun8i_i2s_hw_params, + .prepare = sun8i_i2s_prepare, + .trigger = sun8i_i2s_trigger, + .shutdown = sun8i_i2s_shutdown, +}; + +static int sun8i_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); + struct priv *priv = snd_soc_card_get_drvdata(card); + + snd_soc_dai_init_dma_data(dai, &priv->dma_data, NULL); + + return 0; +} + +static struct snd_soc_dai_driver sun8i_i2s_dai = { + .probe = sun8i_i2s_dai_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 32000, + .rate_max = 192000, + .formats = I2S_FORMATS, + }, + .ops = &sun8i_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver i2s_component = { + .name = DRV_NAME, +}; + +/* --- dma --- */ + +static const struct snd_pcm_hardware sun8i_i2s_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = I2S_FORMATS, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 32000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 156, + .period_bytes_max = 1024 * 1024, + .periods_min = 1, + .periods_max = 8, + .fifo_size = 128, +}; + +static const struct snd_dmaengine_pcm_config sun8i_i2s_config = { + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .pcm_hardware = &sun8i_i2s_pcm_hardware, + .prealloc_buffer_size = 1024 * 1024, +}; + +/* --- audio card --- */ + +static struct device_node *sun8i_get_codec(struct device *dev) +{ + struct device_node *ep, *remote; + + ep = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!ep) + return NULL; + remote = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + + return remote; +} + +static int sun8i_card_create(struct device *dev, struct priv *priv) +{ + struct snd_soc_card *card; + struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link_component *codec; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + dai_link = devm_kzalloc(dev, sizeof(*dai_link), GFP_KERNEL); + if (!dai_link) + return -ENOMEM; + codec = devm_kzalloc(dev, sizeof(*codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + + codec->of_node = sun8i_get_codec(dev); + if (!codec->of_node) { + dev_err(dev, "no port node\n"); + return -ENXIO; + } + + card->name = codec->of_node->name; + card->dai_link = dai_link; + card->num_links = 1; + dai_link->name = codec->of_node->name; + dai_link->stream_name = codec->of_node->name; + dai_link->platform_name = dev_name(dev); + dai_link->cpu_name = dev_name(dev); + + dai_link->codecs = codec; + dai_link->num_codecs = 1; + + /* the DAI name must be the name of codec node */ + codec->dai_name = codec->of_node->name; + + card->dev = dev; + dev_set_drvdata(dev, card); + snd_soc_card_set_drvdata(card, priv); + + return devm_snd_soc_register_card(dev, card); +} + +/* --- module init --- */ + +static int sun8i_i2s_dev_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct priv *priv; + struct resource *mem; + int ret; + + if (!dev->of_node) { + dev_err(dev, "no DT!\n"); + return -EINVAL; + } + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* get the resources */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->mmio = devm_ioremap_resource(dev, mem); + if (IS_ERR(priv->mmio)) + return PTR_ERR(priv->mmio); + + /* get SoC type */ + priv->type = (int) of_match_device(sun8i_i2s_of_match, + &pdev->dev)->data; + + /* get and enable the clocks */ + priv->gate = devm_clk_get(dev, "apb"); /* optional */ + priv->clk = devm_clk_get(dev, "mod"); + if (IS_ERR(priv->clk)) { + dev_err(dev, "no clock\n"); + return PTR_ERR(priv->clk); + } + ret = clk_set_rate(priv->clk, 24576000); + if (ret) { + dev_err(dev, "cannot set rate of i2s clock %d\n", ret); + return ret; + } + + priv->rstc = devm_reset_control_get_optional(dev, NULL); + if (!IS_ERR(priv->rstc)) { + ret = reset_control_deassert(priv->rstc); + if (ret < 0) + return ret; + } + + if (!IS_ERR(priv->gate)) { + ret = clk_prepare_enable(priv->gate); + if (ret < 0) + goto err_gate; + } + + ret = clk_prepare_enable(priv->clk); + if (ret < 0) + goto err_enable; + + /* activate the audio subsystem */ + sun8i_i2s_init(priv); + + ret = devm_snd_soc_register_component(dev, &i2s_component, + &sun8i_i2s_dai, 1); + if (ret) { + dev_err(dev, "snd_soc_register_component failed %d\n", ret); + goto err_register; + } + + ret = devm_snd_dmaengine_pcm_register(dev, &sun8i_i2s_config, 0); + if (ret) { + dev_err(dev, "pcm_register failed %d\n", ret); + goto err_register; + } + + priv->dma_data.maxburst = priv->type == SOC_A83T ? 8 : 4; + priv->dma_data.addr = mem->start + I2S_TXFIFO; + priv->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + ret = sun8i_card_create(dev, priv); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "register card failed %d\n", ret); + goto err_register; + } + + return 0; + +err_register: + clk_disable_unprepare(priv->clk); +err_enable: + clk_disable_unprepare(priv->gate); +err_gate: + if (!IS_ERR(priv->rstc)) + reset_control_assert(priv->rstc); + + return ret; +} + +static int sun8i_i2s_dev_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = dev_get_drvdata(&pdev->dev); + struct priv *priv = snd_soc_card_get_drvdata(card); + + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->gate); + if (!IS_ERR_OR_NULL(priv->rstc)) + reset_control_assert(priv->rstc); + + return 0; +} + +static struct platform_driver sun8i_i2s_driver = { + .probe = sun8i_i2s_dev_probe, + .remove = sun8i_i2s_dev_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(sun8i_i2s_of_match), + }, +}; + +module_platform_driver(sun8i_i2s_driver); + +MODULE_AUTHOR("Jean-Francois Moine moinejf@free.fr"); +MODULE_DESCRIPTION("Allwinner sun8i I2S ASoC Interface"); +MODULE_LICENSE("GPL v2");
Hi,
On Fri, Oct 21, 2016 at 4:36 PM, Jean-Francois Moine moinejf@free.fr wrote:
This patch adds I2S support to sun8i SoCs as the A83T and H3.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Note: This driver is closed to the sun4i-i2s except that:
- it handles the H3
If it's close to sun4i-i2s, you should probably rework that one to support the newer SoCs.
- it creates the sound card (with sun4i-i2s, the sound card is created by the CODECs)
I think this is wrong. I2S is only the DAI. You typically have a separate platform driver for the whole card, or just use simple-card.
.../devicetree/bindings/sound/sun4i-i2s.txt | 38 +- sound/soc/sunxi/Kconfig | 8 + sound/soc/sunxi/Makefile | 3 + sound/soc/sunxi/sun8i-i2s.c | 700 +++++++++++++++++++++ 4 files changed, 744 insertions(+), 5 deletions(-) create mode 100644 sound/soc/sunxi/sun8i-i2s.c
diff --git a/Documentation/devicetree/bindings/sound/sun4i-i2s.txt b/Documentation/devicetree/bindings/sound/sun4i-i2s.txt index 7b526ec..2fb0a7a 100644 --- a/Documentation/devicetree/bindings/sound/sun4i-i2s.txt +++ b/Documentation/devicetree/bindings/sound/sun4i-i2s.txt @@ -1,4 +1,4 @@ -* Allwinner A10 I2S controller +* Allwinner A10/A38T/H3 I2S controller
The I2S bus (Inter-IC sound bus) is a serial link for digital audio data transfer between devices in the system. @@ -6,20 +6,30 @@ audio data transfer between devices in the system. Required properties:
- compatible: should be one of the followings
- "allwinner,sun4i-a10-i2s"
- "allwinner,sun4i-a10-i2s"
"allwinner,sun8i-a83t-i2s"
"allwinner,sun8i-h3-i2s"
- reg: physical base address of the controller and length of memory mapped region.
-- interrupts: should contain the I2S interrupt.
- dmas: DMA specifiers for tx and rx dma. See the DMA client binding, Documentation/devicetree/bindings/dma/dma.txt
-- dma-names: should include "tx" and "rx". +- dma-names: must include "tx" and/or "rx".
- clocks: a list of phandle + clock-specifer pairs, one for each entry in clock-names.
- clock-names: should contain followings:
- "apb" : clock for the I2S bus interface
- "mod" : module clock for the I2S controller
- #sound-dai-cells : Must be equal to 0
-Example: +Optional properties:
+- interrupts: I2S interrupt +- resets: phandle to the reset of the device
+Required nodes:
- port: link to the associated CODEC (DAC, HDMI...)
Note here you are changing an existing binding, adding a required node. If it were truely different, you probably should've started a new binding.
Regards ChenYu
+Example 1:
i2s0: i2s@01c22400 { #sound-dai-cells = <0>; @@ -32,3 +42,21 @@ i2s0: i2s@01c22400 { <&dma SUN4I_DMA_NORMAL 3>; dma-names = "rx", "tx"; };
+Example 2:
+i2s2: i2s@1c22800 {
compatible = "allwinner,sun8i-a83t-i2s";
reg = <0x01c22800 0x60>;
clocks = <&ccu CLK_BUS_I2S2>, <&ccu CLK_I2S2>;
clock-names = "apb", "mod";
resets = <&ccu RST_I2S2>;
dmas = <&dma 27>;
dma-names = "tx";
status = "disabled";
port {
i2s2_hdmi: endpoint {
remote-endpoint = <&hdmi_i2s2>;
};
};
+};
On Sun, 23 Oct 2016 09:33:16 +0800 Chen-Yu Tsai wens@csie.org wrote:
Note: This driver is closed to the sun4i-i2s except that:
- it handles the H3
If it's close to sun4i-i2s, you should probably rework that one to support the newer SoCs.
- it creates the sound card (with sun4i-i2s, the sound card is created by the CODECs)
I think this is wrong. I2S is only the DAI. You typically have a separate platform driver for the whole card, or just use simple-card.
An other device is not needed. The layout is simple: I2S_controller (CPU DAI) <-> HDMI_CODEC (CODEC DAI) The HDMI CODEC is supported by the HDMI video driver (only one device), so, it cannot be the card device. ASoC does not use the CPU DAI device (I2S_controller), so, it is natural to use it to handle the card. Otherwise, the simple-card asks for a node definition in the DT and this node is a pure Linux software entity. On the other side, the simple-graph-card from Kuninori is not useful for this simple case.
Hi,
On Sun, Oct 23, 2016 at 09:45:03AM +0200, Jean-Francois Moine wrote:
On Sun, 23 Oct 2016 09:33:16 +0800 Chen-Yu Tsai wens@csie.org wrote:
Note: This driver is closed to the sun4i-i2s except that:
- it handles the H3
If it's close to sun4i-i2s, you should probably rework that one to support the newer SoCs.
- it creates the sound card (with sun4i-i2s, the sound card is created by the CODECs)
I think this is wrong. I2S is only the DAI. You typically have a separate platform driver for the whole card, or just use simple-card.
An other device is not needed. The layout is simple: I2S_controller (CPU DAI) <-> HDMI_CODEC (CODEC DAI) The HDMI CODEC is supported by the HDMI video driver (only one device), so, it cannot be the card device. ASoC does not use the CPU DAI device (I2S_controller), so, it is natural to use it to handle the card.
Still, duplicating the driver is not the solution. I agree with Chen-Yu that we want to leverage the driver that is already there.
Maxime
On Mon, 24 Oct 2016 14:34:49 +0200 Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
On Sun, Oct 23, 2016 at 09:45:03AM +0200, Jean-Francois Moine wrote:
On Sun, 23 Oct 2016 09:33:16 +0800 Chen-Yu Tsai wens@csie.org wrote:
Note: This driver is closed to the sun4i-i2s except that:
- it handles the H3
If it's close to sun4i-i2s, you should probably rework that one to support the newer SoCs.
- it creates the sound card (with sun4i-i2s, the sound card is created by the CODECs)
I think this is wrong. I2S is only the DAI. You typically have a separate platform driver for the whole card, or just use simple-card.
An other device is not needed. The layout is simple: I2S_controller (CPU DAI) <-> HDMI_CODEC (CODEC DAI) The HDMI CODEC is supported by the HDMI video driver (only one device), so, it cannot be the card device. ASoC does not use the CPU DAI device (I2S_controller), so, it is natural to use it to handle the card.
Still, duplicating the driver is not the solution. I agree with Chen-Yu that we want to leverage the driver that is already there.
Hi Maxime and Chen-Yu,
After looking at the sun4i-i2s, I found 2 solutions for re-using its code in the DE2 HDMI context:
1) either to split the sun4i-i2s driver into common I/O functions and slave CPU DAI,
2) or to move the sun4i-i2s into a master CPU DAI.
( some explanation about 'master' and 'slave': the master is the component the device of which is also the sound card. As the sound card uses the 'drvdata' of the device, this drvdata pointer cannot be used by the master. In the actual implementations: - sun4i-i2s master: card dev = codec dev, drvdata -> card slave: i2s dev (CPU DAI), drvdata -> i2s data - sun8i-i2s master: card dev = i2s dev (CPU DAI), drvdata -> card slave: codec dev (hdmi), drvdata -> codec data (audio/video) )
In the case 1, there is no functional change, just a source split. The sun8i-i2s will then use the common I/O functions.
In the case 2, the CODECs using the sun4i-i2s would have to move to slave CODEC DAI, i.e. the card is created by the sun4i-i2s code. In the 4.9, there is only one codec (sun4i-codec), so, the change is just to move the card creation and the use of drvdata in both codes.
In either cases, I could not check if this changes raise some regression on the sun4i SoCs side. Then, I'd be glad to know: - which solution suits you? - are you ready to do and test the needed changes at the sun4i side?
On Thu, Oct 27, 2016 at 05:13:25PM +0200, Jean-Francois Moine wrote:
On Mon, 24 Oct 2016 14:34:49 +0200 Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
On Sun, Oct 23, 2016 at 09:45:03AM +0200, Jean-Francois Moine wrote:
On Sun, 23 Oct 2016 09:33:16 +0800 Chen-Yu Tsai wens@csie.org wrote:
Note: This driver is closed to the sun4i-i2s except that:
- it handles the H3
If it's close to sun4i-i2s, you should probably rework that one to support the newer SoCs.
- it creates the sound card (with sun4i-i2s, the sound card is created by the CODECs)
I think this is wrong. I2S is only the DAI. You typically have a separate platform driver for the whole card, or just use simple-card.
An other device is not needed. The layout is simple: I2S_controller (CPU DAI) <-> HDMI_CODEC (CODEC DAI) The HDMI CODEC is supported by the HDMI video driver (only one device), so, it cannot be the card device. ASoC does not use the CPU DAI device (I2S_controller), so, it is natural to use it to handle the card.
Still, duplicating the driver is not the solution. I agree with Chen-Yu that we want to leverage the driver that is already there.
Hi Maxime and Chen-Yu,
After looking at the sun4i-i2s, I found 2 solutions for re-using its code in the DE2 HDMI context:
either to split the sun4i-i2s driver into common I/O functions and slave CPU DAI,
or to move the sun4i-i2s into a master CPU DAI.
( some explanation about 'master' and 'slave': the master is the component the device of which is also the sound card. As the sound card uses the 'drvdata' of the device, this drvdata pointer cannot be used by the master. In the actual implementations:
- sun4i-i2s
master: card dev = codec dev, drvdata -> card slave: i2s dev (CPU DAI), drvdata -> i2s data
- sun8i-i2s
master: card dev = i2s dev (CPU DAI), drvdata -> card slave: codec dev (hdmi), drvdata -> codec data (audio/video) )
In the case 1, there is no functional change, just a source split. The sun8i-i2s will then use the common I/O functions.
In the case 2, the CODECs using the sun4i-i2s would have to move to slave CODEC DAI, i.e. the card is created by the sun4i-i2s code. In the 4.9, there is only one codec (sun4i-codec), so, the change is just to move the card creation and the use of drvdata in both codes.
I think you're mistaken. sun4i-codec has nothing to do with the I2S driver. It's a driver for the (poorly named) Allwinner's Audio Codec which features it's own DAI and Codec directly into the SoC.
The DAI being different from the I2S one.
However, we want to use any codec driver with the i2s driver, including those in sound/soc/codecs, and we already have drivers for them.
So I'm not sure either solution is a good one. Why not just make the HDMI part a codec itself, and use the i2s driver as the CPU DAI, like any other codec?
Maxime
On Sun, 23 Oct 2016 09:33:16 +0800 Chen-Yu Tsai wens@csie.org wrote:
On Fri, Oct 21, 2016 at 4:36 PM, Jean-Francois Moine moinejf@free.fr wrote:
This patch adds I2S support to sun8i SoCs as the A83T and H3.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Note: This driver is closed to the sun4i-i2s except that:
- it handles the H3
If it's close to sun4i-i2s, you should probably rework that one to support the newer SoCs.
I started to add the H3 into the sun4i-i2s, but I am blocked with regmap. Many H3 registers are common with the A10, but some of them have more or less fields, the fields may be at different offsets. And, finally, some registers are completely different. This would not raise any problem, except with regmap which is really painful.
As I may understood, regmap is used to simplify suspend/resume, but, is it useful to save the I2S register on suspend? Practically, I am streaming some tune on my device. I suspend it for any reason. The next morning, I resume it. Are you sure I want to continue to hear the end of the tune?
I better think that streaming should be simply stopped on suspend. Then, there is no need to save the playing registers, and, here I am, there is no need to use regmap.
May I go this way?
Hi,
On Sun, Nov 06, 2016 at 07:02:48PM +0100, Jean-Francois Moine wrote:
On Sun, 23 Oct 2016 09:33:16 +0800 Chen-Yu Tsai wens@csie.org wrote:
On Fri, Oct 21, 2016 at 4:36 PM, Jean-Francois Moine moinejf@free.fr wrote:
This patch adds I2S support to sun8i SoCs as the A83T and H3.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Note: This driver is closed to the sun4i-i2s except that:
- it handles the H3
If it's close to sun4i-i2s, you should probably rework that one to support the newer SoCs.
I started to add the H3 into the sun4i-i2s, but I am blocked with regmap. Many H3 registers are common with the A10, but some of them have more or less fields, the fields may be at different offsets. And, finally, some registers are completely different. This would not raise any problem, except with regmap which is really painful.
That's weird, because regmap's regmap_field should make that much easier.
As I may understood, regmap is used to simplify suspend/resume, but, is it useful to save the I2S register on suspend? Practically, I am streaming some tune on my device. I suspend it for any reason. The next morning, I resume it. Are you sure I want to continue to hear the end of the tune?
I better think that streaming should be simply stopped on suspend.
You're mistaken. The code in there is for *runtime* suspend, ie when the device is no longer used, so that case shouldn't even happen at all.
(And real suspend isn't supported anyway)
Then, there is no need to save the playing registers, and, here I am, there is no need to use regmap.
May I go this way?
No, please don't. regmap is also providing very useful features, such as access to all the registers through debugfs, or tracing. What exactly feels painful to you?
Maxime
On Mon, 7 Nov 2016 21:05:05 +0100 Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
On Sun, Nov 06, 2016 at 07:02:48PM +0100, Jean-Francois Moine wrote:
On Sun, 23 Oct 2016 09:33:16 +0800 Chen-Yu Tsai wens@csie.org wrote:
On Fri, Oct 21, 2016 at 4:36 PM, Jean-Francois Moine moinejf@free.fr wrote:
This patch adds I2S support to sun8i SoCs as the A83T and H3.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Note: This driver is closed to the sun4i-i2s except that:
- it handles the H3
If it's close to sun4i-i2s, you should probably rework that one to support the newer SoCs.
I started to add the H3 into the sun4i-i2s, but I am blocked with regmap. Many H3 registers are common with the A10, but some of them have more or less fields, the fields may be at different offsets. And, finally, some registers are completely different. This would not raise any problem, except with regmap which is really painful.
That's weird, because regmap's regmap_field should make that much easier.
#define field_relaxed(addr, mask, val) \ writel_relaxed((readl_relaxed(addr) & mask) | val, addr)
As I may understood, regmap is used to simplify suspend/resume, but, is it useful to save the I2S register on suspend? Practically, I am streaming some tune on my device. I suspend it for any reason. The next morning, I resume it. Are you sure I want to continue to hear the end of the tune?
I better think that streaming should be simply stopped on suspend.
You're mistaken. The code in there is for *runtime* suspend, ie when the device is no longer used, so that case shouldn't even happen at all.
(And real suspend isn't supported anyway)
Is it time to remove this useless code?
Then, there is no need to save the playing registers, and, here I am, there is no need to use regmap.
May I go this way?
No, please don't. regmap is also providing very useful features, such as access to all the registers through debugfs, or tracing. What exactly feels painful to you?
When the I/O registers are in memory (that's the case), you may access them (read and write) thru /dev/mem. Also, is a register access trace really needed in this driver?
The pain is to define the regmap_config (which registers can be read/write/volatile and which can be the values the u-boot let us in the registers at startup time), and the lot of code which is run instead of simple load/store machine instructions.
On Tue, Nov 08, 2016 at 11:51:29AM +0100, Jean-Francois Moine wrote:
On Mon, 7 Nov 2016 21:05:05 +0100 Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
On Sun, Nov 06, 2016 at 07:02:48PM +0100, Jean-Francois Moine wrote:
On Sun, 23 Oct 2016 09:33:16 +0800 Chen-Yu Tsai wens@csie.org wrote:
On Fri, Oct 21, 2016 at 4:36 PM, Jean-Francois Moine moinejf@free.fr wrote:
This patch adds I2S support to sun8i SoCs as the A83T and H3.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Note: This driver is closed to the sun4i-i2s except that:
- it handles the H3
If it's close to sun4i-i2s, you should probably rework that one to support the newer SoCs.
I started to add the H3 into the sun4i-i2s, but I am blocked with regmap. Many H3 registers are common with the A10, but some of them have more or less fields, the fields may be at different offsets. And, finally, some registers are completely different. This would not raise any problem, except with regmap which is really painful.
That's weird, because regmap's regmap_field should make that much easier.
#define field_relaxed(addr, mask, val) \ writel_relaxed((readl_relaxed(addr) & mask) | val, addr)
I'm not sure what you mean here.
As I may understood, regmap is used to simplify suspend/resume, but, is it useful to save the I2S register on suspend? Practically, I am streaming some tune on my device. I suspend it for any reason. The next morning, I resume it. Are you sure I want to continue to hear the end of the tune?
I better think that streaming should be simply stopped on suspend.
You're mistaken. The code in there is for *runtime* suspend, ie when the device is no longer used, so that case shouldn't even happen at all.
(And real suspend isn't supported anyway)
Is it time to remove this useless code?
Which useless code?
Then, there is no need to save the playing registers, and, here I am, there is no need to use regmap.
May I go this way?
No, please don't. regmap is also providing very useful features, such as access to all the registers through debugfs, or tracing. What exactly feels painful to you?
When the I/O registers are in memory (that's the case), you may access them (read and write) thru /dev/mem.
For all the registers if you want to dump all of them. It needs scripting, it needs root access, and it needs some tool (either devmem or a custom one) to dump the values. And this is if you have the right kernel configuration options (devmem enabled, with the protection against mapped devices disabled).
It just works with debugfs.
Also, is a register access trace really needed in this driver?
Yes.
The pain is to define the regmap_config (which registers can be read/write/volatile and which can be the values the u-boot let us in the registers at startup time), and the lot of code which is run instead of simple load/store machine instructions.
This is only needed if you want to use caching, and caching is optional.
Maxime
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- The patch for the A83T DT is not included in this patchset because the clock driver sunxi-ng does not support the A83T clocks. --- arch/arm/boot/dts/sun8i-h3.dtsi | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-h3.dtsi b/arch/arm/boot/dts/sun8i-h3.dtsi index 75a8654..869f3be 100644 --- a/arch/arm/boot/dts/sun8i-h3.dtsi +++ b/arch/arm/boot/dts/sun8i-h3.dtsi @@ -140,6 +140,16 @@ #size-cells = <1>; ranges;
+ de: de-controller@01000000 { + compatible = "allwinner,sun8i-h3-display-engine"; + reg = <0x01000000 0x400000>; + clocks = <&ccu CLK_BUS_DE>, <&ccu CLK_DE>; + clock-names = "gate", "clock"; + resets = <&ccu RST_BUS_DE>; + ports = <&lcd0_p>; + status = "disabled"; + }; + dma: dma-controller@01c02000 { compatible = "allwinner,sun8i-h3-dma"; reg = <0x01c02000 0x1000>; @@ -149,6 +159,23 @@ #dma-cells = <1>; };
+ lcd0: lcd-controller@01c0c000 { + compatible = "allwinner,sun8i-a83t-lcd"; + reg = <0x01c0c000 0x400>; + clocks = <&ccu CLK_BUS_TCON0>, <&ccu CLK_TCON0>; + clock-names = "gate", "clock"; + resets = <&ccu RST_BUS_TCON0>; + interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + lcd0_p: port { + lcd0_hdmi: endpoint { + remote-endpoint = <&hdmi_lcd0>; + }; + }; + }; + mmc0: mmc@01c0f000 { compatible = "allwinner,sun7i-a20-mmc"; reg = <0x01c0f000 0x1000>; @@ -439,6 +466,22 @@ status = "disabled"; };
+ i2s2: i2s@1c22800 { + compatible = "allwinner,sun8i-h3-i2s"; + reg = <0x01c22800 0x60>; + clocks = <&ccu CLK_I2S2>; + clock-names = "mod"; + resets = <&ccu RST_BUS_I2S2>; + dmas = <&dma 27>; + dma-names = "tx"; + status = "disabled"; + port { + i2s2_hdmi: endpoint { + remote-endpoint = <&hdmi_i2s2>; + }; + }; + }; + uart0: serial@01c28000 { compatible = "snps,dw-apb-uart"; reg = <0x01c28000 0x400>; @@ -541,6 +584,30 @@ interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>; };
+ hdmi: hdmi@01ee0000 { + compatible = "allwinner,sun8i-h3-hdmi"; + reg = <0x01ee0000 0x20000>; + clocks = <&ccu CLK_HDMI>, <&ccu CLK_HDMI_DDC>; + clock-names = "clock", "ddc-clock"; + resets = <&ccu RST_BUS_HDMI0>, <&ccu RST_BUS_HDMI1>; + reset-names = "hdmi0", "hdmi1"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + hdmi_lcd0: endpoint { + remote-endpoint = <&lcd0_hdmi>; + }; + }; + port@1 { + reg = <1>; + hdmi_i2s2: endpoint { + remote-endpoint = <&i2s2_hdmi>; + }; + }; + }; + rtc: rtc@01f00000 { compatible = "allwinner,sun6i-a31-rtc"; reg = <0x01f00000 0x54>;
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- The patch for the Banana Pi M3 (A83T) is the same as this one. --- arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts b/arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts index 06fddaa..2e81de8 100644 --- a/arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts +++ b/arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts @@ -55,6 +55,7 @@ aliases { serial0 = &uart0; serial1 = &uart1; + lcd0 = &lcd0; };
chosen { @@ -93,6 +94,10 @@ }; };
+&de { + status = "okay"; +}; + &ehci1 { status = "okay"; }; @@ -101,12 +106,24 @@ status = "okay"; };
+&hdmi { + status = "okay"; +}; + +&i2s2 { + status = "okay"; +}; + &ir { pinctrl-names = "default"; pinctrl-0 = <&ir_pins_a>; status = "okay"; };
+&lcd0 { + status = "okay"; +}; + &mmc0 { pinctrl-names = "default"; pinctrl-0 = <&mmc0_pins_a>, <&mmc0_cd_pin>;
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- The same patch may be applied to other H3 based boards (Orange PI xx). --- arch/arm/boot/dts/sun8i-h3-orangepi-2.dts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-h3-orangepi-2.dts b/arch/arm/boot/dts/sun8i-h3-orangepi-2.dts index e5bcaba..799ceb9 100644 --- a/arch/arm/boot/dts/sun8i-h3-orangepi-2.dts +++ b/arch/arm/boot/dts/sun8i-h3-orangepi-2.dts @@ -56,6 +56,7 @@ serial0 = &uart0; /* ethernet0 is the H3 emac, defined in sun8i-h3.dtsi */ ethernet1 = &rtl8189; + lcd0 = &lcd0; };
chosen { @@ -105,16 +106,32 @@ }; };
+&de { + status = "okay"; +}; + &ehci1 { status = "okay"; };
+&hdmi { + status = "okay"; +}; + +&i2s2 { + status = "okay"; +}; + &ir { pinctrl-names = "default"; pinctrl-0 = <&ir_pins_a>; status = "okay"; };
+&lcd0 { + status = "okay"; +}; + &mmc0 { pinctrl-names = "default"; pinctrl-0 = <&mmc0_pins_a>, <&mmc0_cd_pin>;
On Sat, Oct 22, 2016 at 9:28 PM, Jean-Francois Moine moinejf@free.fr wrote:
This patchset series adds HDMI audio and video support to the Allwinner sun8i SoCs which include the display engine 2 (DE2).
A first submission in January for video on the H3 could not enter into the mainline kernel due to the lack of license headers in Allwinner's sources.
Recently, an announce about Tina OS for the R series https://www.youtube.com/watch?v=h7KD-6HblAU was followed by the upload of a new linux-3.4 source tree https://github.com/tinalinux/linux-3.4 with files containing GPL headers.
Well, I don't know if these sources are really from Allwinner, but anyway, this is the opportunity to propose a new version of my DRM HDMI driver.
Could you clarify about this bit? Did you just clean up Allwinner's existing drivers? Or just use them as reference? Either way I think this deserves some mention in all your copyright headers.
Otherwise what difference does the new release make?
Regards ChenYu
v5: - add overlay plane - add audio support - add support for the A83T - add back the HDMI driver - many bug fixes v4: - drivers/clk/sunxi/Makefile was missing (Emil Velikov) v3: - add the hardware cursor - simplify and fix the DE2 init sequences - generation for all SUNXI SoCs (Andre Przywara) v2: - remove the HDMI driver - remarks from Chen-Yu Tsai and Russell King - DT documentation added
Jean-Francois Moine (7): drm: sunxi: Add a basic DRM driver for Allwinner DE2 ASoC: sunxi: Add a simple HDMI CODEC drm: sunxi: add DE2 HDMI support ASoC: sunxi: Add sun8i I2S driver ARM: dts: sun8i-h3: add HDMI audio and video nodes ARM: dts: sun8i-h3: Add HDMI audio and video to the Banana Pi M2+ ARM: dts: sun8i-h3: Add HDMI audio and video to the Orange PI 2
.../devicetree/bindings/display/sunxi/hdmi.txt | 52 ++ .../bindings/display/sunxi/sunxi-de2.txt | 83 ++ .../devicetree/bindings/sound/sun4i-i2s.txt | 38 +- arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts | 17 + arch/arm/boot/dts/sun8i-h3-orangepi-2.dts | 17 + arch/arm/boot/dts/sun8i-h3.dtsi | 67 ++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/sunxi/Kconfig | 29 + drivers/gpu/drm/sunxi/Makefile | 9 + drivers/gpu/drm/sunxi/de2_crtc.c | 475 +++++++++++ drivers/gpu/drm/sunxi/de2_crtc.h | 63 ++ drivers/gpu/drm/sunxi/de2_de.c | 591 +++++++++++++ drivers/gpu/drm/sunxi/de2_drm.h | 47 ++ drivers/gpu/drm/sunxi/de2_drv.c | 378 +++++++++ drivers/gpu/drm/sunxi/de2_hdmi.c | 396 +++++++++ drivers/gpu/drm/sunxi/de2_hdmi.h | 40 + drivers/gpu/drm/sunxi/de2_hdmi_io.c | 927 +++++++++++++++++++++ drivers/gpu/drm/sunxi/de2_hdmi_io.h | 25 + drivers/gpu/drm/sunxi/de2_plane.c | 119 +++ include/sound/sunxi_hdmi.h | 23 + sound/soc/codecs/Kconfig | 9 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sunxi-hdmi.c | 106 +++ sound/soc/sunxi/Kconfig | 8 + sound/soc/sunxi/Makefile | 3 + sound/soc/sunxi/sun8i-i2s.c | 700 ++++++++++++++++ 27 files changed, 4222 insertions(+), 5 deletions(-) create mode 100644 Documentation/devicetree/bindings/display/sunxi/hdmi.txt create mode 100644 Documentation/devicetree/bindings/display/sunxi/sunxi-de2.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_hdmi.c create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi.h create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_io.c create mode 100644 drivers/gpu/drm/sunxi/de2_hdmi_io.h create mode 100644 drivers/gpu/drm/sunxi/de2_plane.c create mode 100644 include/sound/sunxi_hdmi.h create mode 100644 sound/soc/codecs/sunxi-hdmi.c create mode 100644 sound/soc/sunxi/sun8i-i2s.c
-- 2.10.1
-- You received this message because you are subscribed to the Google Groups "linux-sunxi" group. To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
On Sun, 23 Oct 2016 09:38:04 +0800 Chen-Yu Tsai wens@csie.org wrote:
Recently, an announce about Tina OS for the R series https://www.youtube.com/watch?v=h7KD-6HblAU was followed by the upload of a new linux-3.4 source tree https://github.com/tinalinux/linux-3.4 with files containing GPL headers.
Well, I don't know if these sources are really from Allwinner, but anyway, this is the opportunity to propose a new version of my DRM HDMI driver.
Could you clarify about this bit? Did you just clean up Allwinner's existing drivers? Or just use them as reference? Either way I think this deserves some mention in all your copyright headers.
Otherwise what difference does the new release make?
- Allwinner's video driver is not DRM. - their driver has no hardware cursor nor video overlay. - I wrote the video DRM driver from the document Allwinner_H3_Datasheet_V1.2.pdf and the structures defined in linux-3.4/drivers/video/sunxi/disp2/disp/de/lowlevel_sun8iw7/de_rtmx.h Reading Allwinner's code helped me to understand how the DE2 is working. - my lowlevel HDMI is just a cleanup of theirs with explanations about the registers. Magic constants remain due to the lack of knowledge about the PHYs. - the mention of Allwinner in the copyright headers is needed to indicate the source of the structures (DE2) and code (HDMI).
The main difference is the DRM interface and the use of the EDID, permitting dynamic video resolution change with xrandr.
participants (7)
-
André Przywara
-
Chen-Yu Tsai
-
Daniel Vetter
-
Jean-Francois Moine
-
Mark Brown
-
Maxime Ripard
-
Rob Herring