On Mon, Nov 28, 2016 at 03:23:54PM +0100, 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@xxxxxxx> Scrolled around a bit, seemed all reasonable. Acked-by: Daniel Vetter <daniel.vetter@xxxxxxxx> Not sure a new driver for each chip is reasonable, experience says that long-term you want to share quite a pile of code between different hw platforms from the same vendor. But that's entirely up to you. -Daniel > --- > drivers/gpu/drm/Kconfig | 2 + > drivers/gpu/drm/Makefile | 1 + > drivers/gpu/drm/sun8i/Kconfig | 19 + > drivers/gpu/drm/sun8i/Makefile | 7 + > drivers/gpu/drm/sun8i/de2_crtc.c | 449 +++++++++++++++++++++++ > drivers/gpu/drm/sun8i/de2_crtc.h | 50 +++ > drivers/gpu/drm/sun8i/de2_drv.c | 317 ++++++++++++++++ > drivers/gpu/drm/sun8i/de2_drv.h | 48 +++ > drivers/gpu/drm/sun8i/de2_plane.c | 734 ++++++++++++++++++++++++++++++++++++++ > 9 files changed, 1627 insertions(+) > create mode 100644 drivers/gpu/drm/sun8i/Kconfig > create mode 100644 drivers/gpu/drm/sun8i/Makefile > create mode 100644 drivers/gpu/drm/sun8i/de2_crtc.c > create mode 100644 drivers/gpu/drm/sun8i/de2_crtc.h > create mode 100644 drivers/gpu/drm/sun8i/de2_drv.c > create mode 100644 drivers/gpu/drm/sun8i/de2_drv.h > create mode 100644 drivers/gpu/drm/sun8i/de2_plane.c > > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig > index 95fc041..bb1bfbc 100644 > --- a/drivers/gpu/drm/Kconfig > +++ b/drivers/gpu/drm/Kconfig > @@ -202,6 +202,8 @@ source "drivers/gpu/drm/shmobile/Kconfig" > > source "drivers/gpu/drm/sun4i/Kconfig" > > +source "drivers/gpu/drm/sun8i/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 883f3e7..3e1eaa0 100644 > --- a/drivers/gpu/drm/Makefile > +++ b/drivers/gpu/drm/Makefile > @@ -72,6 +72,7 @@ obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/ > obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ > obj-y += omapdrm/ > obj-$(CONFIG_DRM_SUN4I) += sun4i/ > +obj-$(CONFIG_DRM_SUN8I) += sun8i/ > obj-y += tilcdc/ > obj-$(CONFIG_DRM_QXL) += qxl/ > obj-$(CONFIG_DRM_BOCHS) += bochs/ > diff --git a/drivers/gpu/drm/sun8i/Kconfig b/drivers/gpu/drm/sun8i/Kconfig > new file mode 100644 > index 0000000..6940895 > --- /dev/null > +++ b/drivers/gpu/drm/sun8i/Kconfig > @@ -0,0 +1,19 @@ > +# > +# Allwinner DE2 Video configuration > +# > + > +config DRM_SUN8I > + bool > + > +config DRM_SUN8I_DE2 > + tristate "Support for Allwinner Video with DE2 interface" > + depends on DRM && OF > + depends on ARCH_SUNXI || COMPILE_TEST > + select DRM_GEM_CMA_HELPER > + select DRM_KMS_CMA_HELPER > + select DRM_KMS_HELPER > + select DRM_SUN8I > + 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 > + sun8i-de2-drm. > diff --git a/drivers/gpu/drm/sun8i/Makefile b/drivers/gpu/drm/sun8i/Makefile > new file mode 100644 > index 0000000..f107919 > --- /dev/null > +++ b/drivers/gpu/drm/sun8i/Makefile > @@ -0,0 +1,7 @@ > +# > +# Makefile for Allwinner's sun8i DRM device driver > +# > + > +sun8i-de2-drm-objs := de2_drv.o de2_crtc.o de2_plane.o > + > +obj-$(CONFIG_DRM_SUN8I_DE2) += sun8i-de2-drm.o > diff --git a/drivers/gpu/drm/sun8i/de2_crtc.c b/drivers/gpu/drm/sun8i/de2_crtc.c > new file mode 100644 > index 0000000..4e94ccc > --- /dev/null > +++ b/drivers/gpu/drm/sun8i/de2_crtc.c > @@ -0,0 +1,449 @@ > +/* > + * Allwinner DRM driver - DE2 CRTC > + * > + * Copyright (C) 2016 Jean-Francois Moine <moinejf@xxxxxxx> > + * > + * 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 <linux/io.h> > +#include <linux/of_irq.h> > +#include <linux/of_graph.h> > + > +#include "de2_drv.h" > +#include "de2_crtc.h" > + > +/* I/O map */ > + > +#define TCON_GCTL_REG 0x00 > +#define TCON_GCTL_TCON_ENABLE BIT(31) > +#define TCON_GINT0_REG 0x04 > +#define TCON_GINT0_TCON1_Vb_Int_En BIT(30) > +#define TCON_GINT0_TCON1_Vb_Int_Flag BIT(14) > +#define TCON_GINT0_TCON1_Vb_Line_Int_Flag BIT(12) > +#define TCON0_CTL_REG 0x40 > +#define TCON0_CTL_TCON_ENABLE BIT(31) > +#define TCON1_CTL_REG 0x90 > +#define TCON1_CTL_TCON_ENABLE BIT(31) > +#define TCON1_CTL_INTERLACE_ENABLE BIT(20) > +#define TCON1_CTL_Start_Delay_SHIFT 4 > +#define TCON1_CTL_Start_Delay_MASK GENMASK(8, 4) > +#define TCON1_BASIC0_REG 0x94 /* XI/YI */ > +#define TCON1_BASIC1_REG 0x98 /* LS_XO/LS_YO */ > +#define TCON1_BASIC2_REG 0x9c /* XO/YO */ > +#define TCON1_BASIC3_REG 0xa0 /* HT/HBP */ > +#define TCON1_BASIC4_REG 0xa4 /* VT/VBP */ > +#define TCON1_BASIC5_REG 0xa8 /* HSPW/VSPW */ > +#define TCON1_PS_SYNC_REG 0xb0 > +#define TCON1_IO_POL_REG 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) > +#define TCON1_IO_TRI_REG 0xf4 > +#define TCON_CEU_CTL_REG 0x100 > +#define TCON_CEU_CTL_ceu_en BIT(31) > +#define TCON1_FILL_CTL_REG 0x300 > +#define TCON1_FILL_START0_REG 0x304 > +#define TCON1_FILL_END0_REG 0x308 > +#define TCON1_FILL_DATA0_REG 0x30c > + > +#define XY(x, y) (((x) << 16) | (y)) > + > +#define andl_relaxed(addr, val) \ > + writel_relaxed(readl_relaxed(addr) & val, addr) > +#define orl_relaxed(addr, val) \ > + writel_relaxed(readl_relaxed(addr) | val, addr) > + > +/* 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 = readl_relaxed(lcd->mmio + TCON_GINT0_REG); > + > + drm_crtc_handle_vblank(&lcd->crtc); > + > + writel_relaxed(isr & > + ~(TCON_GINT0_TCON1_Vb_Int_Flag | > + TCON_GINT0_TCON1_Vb_Line_Int_Flag), > + lcd->mmio + TCON_GINT0_REG); > + > + return IRQ_HANDLED; > +} > + > +int de2_enable_vblank(struct drm_device *drm, unsigned int crtc_ix) > +{ > + struct priv *priv = drm_to_priv(drm); > + struct lcd *lcd = priv->lcds[crtc_ix]; > + > + orl_relaxed(lcd->mmio + TCON_GINT0_REG, TCON_GINT0_TCON1_Vb_Int_En); > + > + return 0; > +} > + > +void de2_disable_vblank(struct drm_device *drm, unsigned int crtc_ix) > +{ > + struct priv *priv = drm_to_priv(drm); > + struct lcd *lcd = priv->lcds[crtc_ix]; > + > + andl_relaxed(lcd->mmio + TCON_GINT0_REG, ~TCON_GINT0_TCON1_Vb_Int_En); > +} > + > +void de2_vblank_reset(struct lcd *lcd) > +{ > + drm_crtc_vblank_reset(&lcd->crtc); > +} > + > +/* frame functions */ > +static int de2_crtc_set_clock(struct lcd *lcd, int rate) > +{ > + struct clk *parent_clk; > + u32 parent_rate; > + int ret; > + > + /* determine and set the best rate for the parent clock (pll-video) */ > + if ((270000 * 2) % rate == 0) > + parent_rate = 270000000; > + else if (297000 % rate == 0) > + parent_rate = 297000000; > + else > + return -EINVAL; /* unsupported clock */ > + > + parent_clk = clk_get_parent(lcd->clk); > + > + ret = clk_set_rate(parent_clk, parent_rate); > + if (ret) { > + dev_err(lcd->dev, "set parent rate failed %d\n", ret); > + return ret; > + } > + ret = clk_set_rate(lcd->clk, rate * 1000); > + if (ret) { > + dev_err(lcd->dev, "set rate failed %d\n", ret); > + return ret; > + } > + > + /* enable the clock */ > + reset_control_deassert(lcd->reset); > + clk_prepare_enable(lcd->bus); > + clk_prepare_enable(lcd->clk); > + > + return ret; > +} > + > +static void de2_tcon_init(struct lcd *lcd) > +{ > + andl_relaxed(lcd->mmio + TCON0_CTL_REG, ~TCON0_CTL_TCON_ENABLE); > + andl_relaxed(lcd->mmio + TCON1_CTL_REG, ~TCON1_CTL_TCON_ENABLE); > + andl_relaxed(lcd->mmio + TCON_GCTL_REG, ~TCON_GCTL_TCON_ENABLE); > + > + /* disable/ack interrupts */ > + writel_relaxed(0, lcd->mmio + TCON_GINT0_REG); > +} > + > +static void de2_tcon_enable(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; > + > + orl_relaxed(lcd->mmio + TCON_GCTL_REG, TCON_GCTL_TCON_ENABLE); > + > + data = XY(mode->hdisplay - 1, mode->vdisplay / interlace - 1); > + writel_relaxed(data, lcd->mmio + TCON1_BASIC0_REG); > + writel_relaxed(data, lcd->mmio + TCON1_BASIC1_REG); > + writel_relaxed(data, lcd->mmio + TCON1_BASIC2_REG); > + writel_relaxed(XY(mode->htotal - 1, > + mode->htotal - mode->hsync_start - 1), > + lcd->mmio + TCON1_BASIC3_REG); > + writel_relaxed(XY(mode->vtotal * (3 - interlace), > + mode->vtotal - mode->vsync_start - 1), > + lcd->mmio + TCON1_BASIC4_REG); > + writel_relaxed(XY(mode->hsync_end - mode->hsync_start - 1, > + mode->vsync_end - mode->vsync_start - 1), > + lcd->mmio + TCON1_BASIC5_REG); > + > + 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; > + writel_relaxed(data, lcd->mmio + TCON1_IO_POL_REG); > + > + andl_relaxed(lcd->mmio + TCON_CEU_CTL_REG, ~TCON_CEU_CTL_ceu_en); > + > + if (interlace == 2) > + orl_relaxed(lcd->mmio + TCON1_CTL_REG, > + TCON1_CTL_INTERLACE_ENABLE); > + else > + andl_relaxed(lcd->mmio + TCON1_CTL_REG, > + ~TCON1_CTL_INTERLACE_ENABLE); > + > + writel_relaxed(0, lcd->mmio + TCON1_FILL_CTL_REG); > + writel_relaxed(mode->vtotal + 1, lcd->mmio + TCON1_FILL_START0_REG); > + writel_relaxed(mode->vtotal, lcd->mmio + TCON1_FILL_END0_REG); > + writel_relaxed(0, lcd->mmio + TCON1_FILL_DATA0_REG); > + > + start_delay = (mode->vtotal - mode->vdisplay) / interlace - 5; > + if (start_delay > 31) > + start_delay = 31; > + data = readl_relaxed(lcd->mmio + TCON1_CTL_REG); > + data &= ~TCON1_CTL_Start_Delay_MASK; > + data |= start_delay << TCON1_CTL_Start_Delay_SHIFT; > + writel_relaxed(data, lcd->mmio + TCON1_CTL_REG); > + > + orl_relaxed(lcd->mmio + TCON1_CTL_REG, TCON1_CTL_TCON_ENABLE); > +} > + > +static void de2_tcon_disable(struct lcd *lcd) > +{ > + andl_relaxed(lcd->mmio + TCON1_CTL_REG, ~TCON1_CTL_TCON_ENABLE); > + andl_relaxed(lcd->mmio + TCON_GCTL_REG, ~TCON_GCTL_TCON_ENABLE); > +} > + > +static void de2_crtc_enable(struct drm_crtc *crtc) > +{ > + struct lcd *lcd = crtc_to_lcd(crtc); > + struct drm_display_mode *mode = &crtc->mode; > + > + if (de2_crtc_set_clock(lcd, mode->clock) < 0) > + return; > + lcd->clk_enabled = true; > + > + /* start the TCON and the DE */ > + de2_tcon_enable(lcd); > + de2_de_enable(lcd); > + > + /* turn on blanking interrupt */ > + drm_crtc_vblank_on(crtc); > +} > + > +static void de2_crtc_disable(struct drm_crtc *crtc, > + struct drm_crtc_state *old_crtc_state) > +{ > + struct lcd *lcd = crtc_to_lcd(crtc); > + > + if (!lcd->clk_enabled) > + return; /* already disabled */ > + lcd->clk_enabled = false; > + > + de2_de_disable(lcd); > + > + drm_crtc_vblank_off(crtc); > + > + de2_tcon_disable(lcd); > + > + clk_disable_unprepare(lcd->clk); > + clk_disable_unprepare(lcd->bus); > + reset_control_assert(lcd->reset); > +} > + > +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, > + .atomic_disable = de2_crtc_disable, > +}; > + > +/* device init */ > +static int de2_lcd_bind(struct device *dev, struct device *master, > + void *data) > +{ > + struct drm_device *drm = data; > + struct priv *priv = drm_to_priv(drm); > + struct lcd *lcd = dev_get_drvdata(dev); > + struct drm_crtc *crtc = &lcd->crtc; > + int ret, i, crtc_ix; > + > + lcd->priv = priv; > + > + /* set the CRTC reference */ > + crtc_ix = drm_crtc_index(crtc); > + if (crtc_ix >= ARRAY_SIZE(priv->lcds)) { > + dev_err(drm->dev, "Bad crtc index"); > + return -ENOENT; > + } > + priv->lcds[crtc_ix] = lcd; > + > + /* and the mixer index (DT port index in the DE) */ > + for (i = 0; ; i++) { > + struct device_node *port; > + > + port = of_parse_phandle(drm->dev->of_node, "ports", i); > + if (!port) > + break; > + if (port == lcd->crtc.port) { > + lcd->mixer = i; > + break; > + } > + } > + > + ret = de2_plane_init(drm, lcd); > + if (ret < 0) > + return ret; > + > + drm_crtc_helper_add(crtc, &de2_crtc_helper_funcs); > + > + return drm_crtc_init_with_planes(drm, crtc, > + &lcd->planes[DE2_PRIMARY_PLANE], > + &lcd->planes[DE2_CURSOR_PLANE], > + &de2_crtc_funcs, NULL); > +} > + > +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->priv) > + lcd->priv->lcds[drm_crtc_index(&lcd->crtc)] = NULL; > +} > + > +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; > + > + lcd = devm_kzalloc(dev, sizeof(*lcd), GFP_KERNEL); > + if (!lcd) > + return -ENOMEM; > + > + dev_set_drvdata(dev, lcd); > + lcd->dev = dev; > + lcd->mixer = 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); > + } > + > + /* possible CRTC */ > + 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->bus = devm_clk_get(dev, "bus"); > + if (IS_ERR(lcd->bus)) { > + dev_err(dev, "get bus clock err %d\n", (int) PTR_ERR(lcd->bus)); > + ret = PTR_ERR(lcd->bus); > + goto err; > + } > + > + lcd->clk = devm_clk_get(dev, "clock"); > + if (IS_ERR(lcd->clk)) { > + ret = PTR_ERR(lcd->clk); > + dev_err(dev, "get video clock err %d\n", ret); > + goto err; > + } > + > + lcd->reset = devm_reset_control_get(dev, NULL); > + if (IS_ERR(lcd->reset)) { > + ret = PTR_ERR(lcd->reset); > + dev_err(dev, "get reset err %d\n", ret); > + goto err; > + } > + > + irq = platform_get_irq(pdev, 0); > + if (irq <= 0) { > + dev_err(dev, "unable to get irq\n"); > + ret = -EINVAL; > + goto err; > + } > + > + de2_tcon_init(lcd); /* stop TCON and avoid interrupts */ > + > + ret = devm_request_irq(dev, irq, de2_lcd_irq, 0, > + dev_name(dev), lcd); > + if (ret < 0) { > + dev_err(dev, "unable to request irq %d\n", irq); > + goto err; > + } > + > + return component_add(dev, &de2_lcd_ops); > + > +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); > + > + of_node_put(lcd->crtc.port); > + > + return 0; > +} > + > +static const struct of_device_id de2_lcd_ids[] = { > + { .compatible = "allwinner,sun8i-a83t-tcon", }, > + { } > +}; > + > +struct platform_driver de2_lcd_platform_driver = { > + .probe = de2_lcd_probe, > + .remove = de2_lcd_remove, > + .driver = { > + .name = "sun8i-de2-tcon", > + .of_match_table = of_match_ptr(de2_lcd_ids), > + }, > +}; > diff --git a/drivers/gpu/drm/sun8i/de2_crtc.h b/drivers/gpu/drm/sun8i/de2_crtc.h > new file mode 100644 > index 0000000..c0d34a7 > --- /dev/null > +++ b/drivers/gpu/drm/sun8i/de2_crtc.h > @@ -0,0 +1,50 @@ > +#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 <drm/drm_plane_helper.h> > + > +struct clk; > +struct reset_control; > +struct priv; > + > +/* planes */ > +#define DE2_PRIMARY_PLANE 0 > +#define DE2_CURSOR_PLANE 1 > +#define DE2_N_PLANES 5 /* number of planes - see plane_tb[] in de2_plane.c */ > + > +struct lcd { > + void __iomem *mmio; > + > + struct device *dev; > + struct drm_crtc crtc; > + > + struct priv *priv; /* DRM/DE private data */ > + > + u8 mixer; /* LCD (mixer) number */ > + u8 delayed; /* bitmap of planes with delayed update */ > + > + u8 clk_enabled; /* used for error in crtc_enable */ > + > + struct clk *clk; > + struct clk *bus; > + struct reset_control *reset; > + > + struct drm_plane planes[DE2_N_PLANES]; > +}; > + > +#define crtc_to_lcd(x) container_of(x, struct lcd, crtc) > + > +/* in de2_plane.c */ > +void de2_de_enable(struct lcd *lcd); > +void de2_de_disable(struct lcd *lcd); > +int de2_plane_init(struct drm_device *drm, struct lcd *lcd); > + > +#endif /* __DE2_CRTC_H__ */ > diff --git a/drivers/gpu/drm/sun8i/de2_drv.c b/drivers/gpu/drm/sun8i/de2_drv.c > new file mode 100644 > index 0000000..f96babe > --- /dev/null > +++ b/drivers/gpu/drm/sun8i/de2_drv.c > @@ -0,0 +1,317 @@ > +/* > + * Allwinner DRM driver - DE2 DRM driver > + * > + * Copyright (C) 2016 Jean-Francois Moine <moinejf@xxxxxxx> > + * > + * 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/of_device.h> > +#include <drm/drm_of.h> > +#include <linux/component.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_fb_cma_helper.h> > +#include <drm/drm_gem_cma_helper.h> > + > +#include "de2_drv.h" > + > +#define DRIVER_NAME "sun8i-de2" > +#define DRIVER_DESC "Allwinner DRM DE2" > +#define DRIVER_DATE "20161101" > +#define DRIVER_MAJOR 1 > +#define DRIVER_MINOR 0 > + > +static const 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_to_priv(drm); > + > + 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_to_priv(drm); > + > + 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, > +}; > + > +/* > + * Platform driver > + */ > + > +static int de2_drm_bind(struct device *dev) > +{ > + struct drm_device *drm; > + struct priv *priv; > + struct resource *res; > + struct lcd *lcd; > + int i, ret; > + > + priv = kzalloc(sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + drm = &priv->drm; > + dev_set_drvdata(dev, drm); > + > + /* get the resources */ > + priv->soc_type = (int) of_match_device(de2_drm_of_match, dev)->data; > + > + res = platform_get_resource(to_platform_device(dev), > + IORESOURCE_MEM, 0); > + if (!res) { > + dev_err(dev, "failed to get memory resource\n"); > + ret = -EINVAL; > + goto out1; > + } > + > + priv->mmio = devm_ioremap_resource(dev, res); > + if (IS_ERR(priv->mmio)) { > + ret = PTR_ERR(priv->mmio); > + dev_err(dev, "failed to map registers %d\n", ret); > + goto out1; > + } > + > + priv->gate = devm_clk_get(dev, "bus"); > + if (IS_ERR(priv->gate)) { > + ret = PTR_ERR(priv->gate); > + dev_err(dev, "bus gate err %d\n", ret); > + goto out1; > + } > + > + priv->clk = devm_clk_get(dev, "clock"); > + if (IS_ERR(priv->clk)) { > + ret = PTR_ERR(priv->clk); > + dev_err(dev, "clock err %d\n", ret); > + goto out1; > + } > + > + priv->reset = devm_reset_control_get(dev, NULL); > + if (IS_ERR(priv->reset)) { > + ret = PTR_ERR(priv->reset); > + dev_err(dev, "reset err %d\n", ret); > + goto out1; > + } > + > + mutex_init(&priv->mutex); /* protect DE I/O accesses */ > + > + ret = drm_dev_init(drm, &de2_drm_driver, dev); > + if (ret != 0) { > + dev_err(dev, "dev_init failed %d\n", ret); > + goto out1; > + } > + > + 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; > + > + /* start the subdevices */ > + ret = component_bind_all(dev, drm); > + if (ret < 0) > + goto out2; > + > + /* initialize and disable vertical blanking on all CRTCs */ > + ret = drm_vblank_init(drm, drm->mode_config.num_crtc); > + if (ret < 0) > + dev_warn(dev, "vblank_init failed %d\n", ret); > + > + for (i = 0; i < ARRAY_SIZE(priv->lcds); i++) { > + lcd = priv->lcds[i]; > + if (lcd) > + de2_vblank_reset(lcd); > + } > + > + 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 out3; > + } > + > + drm_kms_helper_poll_init(drm); > + > + ret = drm_dev_register(drm, 0); > + if (ret < 0) > + goto out4; > + > + return 0; > + > +out4: > + drm_fbdev_cma_fini(priv->fbdev); > +out3: > + component_unbind_all(dev, drm); > +out2: > + drm_dev_unref(drm); > +out1: > + kfree(priv); > + return ret; > +} > + > +static void de2_drm_unbind(struct device *dev) > +{ > + struct drm_device *drm = dev_get_drvdata(dev); > + struct priv *priv = drm_to_priv(drm); > + > + drm_dev_unregister(drm); > + > + drm_fbdev_cma_fini(priv->fbdev); > + drm_kms_helper_poll_fini(drm); > + drm_vblank_cleanup(drm); > + drm_mode_config_cleanup(drm); > + > + component_unbind_all(dev, drm); > + > + kfree(priv); > +} > + > +static const struct component_master_ops de2_drm_comp_ops = { > + .bind = de2_drm_bind, > + .unbind = de2_drm_unbind, > +}; > + > +/* > + * drm_of_component_probe() does: > + * - bind of the ports (lcd-controller.port) > + * - bind of the remote nodes (hdmi, tve..) > + */ > +static int compare_of(struct device *dev, void *data) > +{ > + struct device_node *np = data; > + > + if (of_node_cmp(np->name, "port") == 0) { > + np = of_get_parent(np); > + of_node_put(np); > + } > + return dev->of_node == np; > +} > + > +static int de2_drm_probe(struct platform_device *pdev) > +{ > + int ret; > + > + ret = drm_of_component_probe(&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, > + .of_match_table = de2_drm_of_match, > + }, > +}; > + > +static int __init de2_drm_init(void) > +{ > + int ret; > + > + 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@xxxxxxx>"); > +MODULE_DESCRIPTION("Allwinner DE2 DRM Driver"); > +MODULE_LICENSE("GPL v2"); > diff --git a/drivers/gpu/drm/sun8i/de2_drv.h b/drivers/gpu/drm/sun8i/de2_drv.h > new file mode 100644 > index 0000000..c42c30a > --- /dev/null > +++ b/drivers/gpu/drm/sun8i/de2_drv.h > @@ -0,0 +1,48 @@ > +#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 <drm/drmP.h> > +#include <linux/clk.h> > +#include <linux/reset.h> > + > +struct drm_fbdev_cma; > +struct lcd; > + > +#define N_LCDS 2 > + > +struct priv { > + struct drm_device drm; > + void __iomem *mmio; > + struct clk *clk; > + struct clk *gate; > + struct reset_control *reset; > + > + struct mutex mutex; /* protect DE I/O access */ > + u8 soc_type; > +#define SOC_A83T 0 > +#define SOC_H3 1 > + u8 started; /* bitmap of started mixers */ > + u8 clean; /* bitmap of clean mixers */ > + > + struct drm_fbdev_cma *fbdev; > + > + struct lcd *lcds[N_LCDS]; /* CRTCs */ > +}; > + > +#define drm_to_priv(x) container_of(x, struct priv, drm) > + > +/* in de2_crtc.c */ > +int de2_enable_vblank(struct drm_device *drm, unsigned int crtc); > +void de2_disable_vblank(struct drm_device *drm, unsigned int crtc); > +void de2_vblank_reset(struct lcd *lcd); > +extern struct platform_driver de2_lcd_platform_driver; > + > +#endif /* __DE2_DRM_H__ */ > diff --git a/drivers/gpu/drm/sun8i/de2_plane.c b/drivers/gpu/drm/sun8i/de2_plane.c > new file mode 100644 > index 0000000..2fd72dc > --- /dev/null > +++ b/drivers/gpu/drm/sun8i/de2_plane.c > @@ -0,0 +1,734 @@ > +/* > + * Allwinner DRM driver - Display Engine 2 > + * > + * Copyright (C) 2016 Jean-Francois Moine <moinejf@xxxxxxx> > + * Adapted from the sun8iw6 and sun8iw7 disp2 drivers > + * 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 <linux/io.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_fb_cma_helper.h> > +#include <drm/drm_gem_cma_helper.h> > +#include <drm/drm_plane_helper.h> > + > +#include "de2_drv.h" > +#include "de2_crtc.h" > + > +/* DE2 I/O map */ > + > +#define DE2_MOD_REG 0x0000 /* 1 bit per LCD */ > +#define DE2_GATE_REG 0x0004 > +#define DE2_RESET_REG 0x0008 > +#define DE2_DIV_REG 0x000c /* 4 bits per LCD */ > +#define DE2_SEL_REG 0x0010 > + > +#define DE2_MIXER0_BASE 0x00100000 /* LCD 0 */ > +#define DE2_MIXER1_BASE 0x00200000 /* LCD 1 */ > + > +/* mixer registers (addr / mixer base) */ > +#define MIXER_GLB_REGS 0x00000 /* global control */ > +#define MIXER_BLD_REGS 0x01000 /* alpha blending */ > +#define MIXER_CHAN_REGS 0x02000 /* VI/UI overlay channels */ > +#define MIXER_CHAN_SZ 0x1000 /* size of a channel */ > +#define MIXER_VSU_REGS 0x20000 /* VSU */ > +#define MIXER_GSU1_REGS 0x30000 /* GSUs */ > +#define MIXER_GSU2_REGS 0x40000 > +#define MIXER_GSU3_REGS 0x50000 > +#define MIXER_FCE_REGS 0xa0000 /* FCE */ > +#define MIXER_BWS_REGS 0xa2000 /* BWS */ > +#define MIXER_LTI_REGS 0xa4000 /* LTI */ > +#define MIXER_PEAK_REGS 0xa6000 /* PEAK */ > +#define MIXER_ASE_REGS 0xa8000 /* ASE */ > +#define MIXER_FCC_REGS 0xaa000 /* FCC */ > +#define MIXER_DCSC_REGS 0xb0000 /* DCSC/SMBL */ > + > +/* global control */ > +#define MIXER_GLB_CTL_REG 0x00 > +#define MIXER_GLB_CTL_rt_en BIT(0) > +#define MIXER_GLB_CTL_finish_irq_en BIT(4) > +#define MIXER_GLB_CTL_rtwb_port BIT(12) > +#define MIXER_GLB_STATUS_REG 0x04 > +#define MIXER_GLB_DBUFF_REG 0x08 > +#define MIXER_GLB_SIZE_REG 0x0c > + > +/* alpha blending */ > +#define MIXER_BLD_FCOLOR_CTL_REG 0x00 > +#define MIXER_BLD_FCOLOR_CTL_PEN(pipe) (0x0100 << (pipe)) > +#define MIXER_BLD_ATTR_N 4 /* number of attribute blocks */ > +#define MIXER_BLD_ATTR_SIZE (4 * 4) /* size of an attribute block */ > +#define MIXER_BLD_ATTRx_FCOLOR(x) (0x04 + MIXER_BLD_ATTR_SIZE * (x)) > +#define MIXER_BLD_ATTRx_INSIZE(x) (0x08 + MIXER_BLD_ATTR_SIZE * (x)) > +#define MIXER_BLD_ATTRx_OFFSET(x) (0x0c + MIXER_BLD_ATTR_SIZE * (x)) > +#define MIXER_BLD_ROUTE_REG 0x80 > +#define MIXER_BLD_ROUTE(chan, pipe) ((chan) << ((pipe) * 4)) > +#define MIXER_BLD_PREMULTIPLY_REG 0x84 > +#define MIXER_BLD_BKCOLOR_REG 0x88 > +#define MIXER_BLD_OUTPUT_SIZE_REG 0x8c > +#define MIXER_BLD_MODEx_REG(x) (0x90 + 4 * (x)) /* x = 0..3 */ > +#define MIXER_BLD_MODE_SRCOVER 0x03010301 > +#define MIXER_BLD_OUT_CTL_REG 0xfc > + > +/* VI channel (channel 0) */ > +#define VI_CFG_N 4 /* number of layers */ > +#define VI_CFG_SIZE 0x30 /* size of a layer */ > +#define VI_CFGx_ATTR(l) (0x00 + VI_CFG_SIZE * (l)) > +#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) > +#define VI_CFGx_SIZE(l) (0x04 + VI_CFG_SIZE * (l)) > +#define VI_CFGx_COORD(l) (0x08 + VI_CFG_SIZE * (l)) > +#define VI_N_PLANES 3 > +#define VI_CFGx_PITCHy(l, p) (0x0c + VI_CFG_SIZE * (l) + 4 * (p)) > +#define VI_CFGx_TOP_LADDRy(l, p) (0x18 + VI_CFG_SIZE * (l) + 4 * (p)) > +#define VI_CFGx_BOT_LADDRy(l, p) (0x24 + VI_CFG_SIZE * (l) + 4 * (p)) > +#define VI_FCOLORx(l) (0xc0 + 4 * (l)) > +#define VI_TOP_HADDRx(p) (0xd0 + 4 * (p)) > +#define VI_BOT_HADDRx(p) (0xdc + 4 * (p)) > +#define VI_OVL_SIZEx(n) (0xe8 + 4 * (n)) > +#define VI_HORI_DSx(n) (0xf0 + 4 * (n)) > +#define VI_VERT_DSx(n) (0xf8 + 4 * (n)) > +#define VI_SIZE 0x100 > + > +/* UI channel (channels 1..3) */ > +#define UI_CFG_N 4 /* number of layers */ > +#define UI_CFG_SIZE (8 * 4) /* size of a layer */ > +#define UI_CFGx_ATTR(l) (0x00 + UI_CFG_SIZE * (l)) > +#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) > +#define UI_CFGx_SIZE(l) (0x04 + UI_CFG_SIZE * (l)) > +#define UI_CFGx_COORD(l) (0x08 + UI_CFG_SIZE * (l)) > +#define UI_CFGx_PITCH(l) (0x0c + UI_CFG_SIZE * (l)) > +#define UI_CFGx_TOP_LADDR(l) (0x10 + UI_CFG_SIZE * (l)) > +#define UI_CFGx_BOT_LADDR(l) (0x14 + UI_CFG_SIZE * (l)) > +#define UI_CFGx_FCOLOR(l) (0x18 + UI_CFG_SIZE * (l)) > +#define UI_TOP_HADDR 0x80 > +#define UI_BOT_HADDR 0x84 > +#define UI_OVL_SIZE 0x88 > +#define UI_SIZE 0x8c > + > +/* 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 /* YVYU */ > +#define DE2_FORMAT_YUV422_I_UYVY 2 /* UYVY */ > +#define DE2_FORMAT_YUV422_I_YUYV 3 /* YUYV */ > +#define DE2_FORMAT_YUV422_P 6 /* YYYY UU VV planar */ > +#define DE2_FORMAT_YUV420_P 10 /* YYYY U V planar */ > + > +/* 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, > +}; > + > +/* > + * plane table > + * > + * The chosen channel/layer assignment of the planes respects > + * the following constraints: > + * - the cursor must be in a channel higher than the primary channel > + * - there are 4 channels in the LCD 0 and only 2 channels in the LCD 1 > + */ > +static const struct { > + u8 chan; > + u8 layer; > + u8 pipe; > + u8 type; /* plane type */ > + const uint32_t *formats; > + u8 n_formats; > +} plane_tb[] = { > + [DE2_PRIMARY_PLANE] = { /* primary plane: channel 0 (VI) */ > + 0, 0, 0, > + DRM_PLANE_TYPE_PRIMARY, > + ui_formats, ARRAY_SIZE(ui_formats), > + }, > + [DE2_CURSOR_PLANE] = { /* cursor: channel 1 (UI) */ > + 1, 0, 1, > + DRM_PLANE_TYPE_CURSOR, > + ui_formats, ARRAY_SIZE(ui_formats), > + }, > + { > + 0, 1, 0, /* 1st overlay: channel 0, layer 1 */ > + DRM_PLANE_TYPE_OVERLAY, > + vi_formats, ARRAY_SIZE(vi_formats), > + }, > + { > + 0, 2, 0, /* 2nd overlay: channel 0, layer 2 */ > + DRM_PLANE_TYPE_OVERLAY, > + vi_formats, ARRAY_SIZE(vi_formats), > + }, > + { > + 0, 3, 0, /* 3rd overlay: channel 0, layer 3 */ > + DRM_PLANE_TYPE_OVERLAY, > + vi_formats, ARRAY_SIZE(vi_formats), > + }, > +}; > + > +static inline void andl_relaxed(void __iomem *addr, u32 val) > +{ > + writel_relaxed(readl_relaxed(addr) & val, addr); > +} > + > +static inline void orl_relaxed(void __iomem *addr, u32 val) > +{ > + writel_relaxed(readl_relaxed(addr) | val, addr); > +} > + > +/* alert the DE processor about changes in a mixer configuration */ > +static void de2_mixer_select(struct priv *priv, > + int mixer, > + void __iomem *mixer_io) > +{ > + /* select the mixer */ > + andl_relaxed(priv->mmio + DE2_SEL_REG, ~1); > + > + /* double register switch */ > + writel_relaxed(1, mixer_io + MIXER_GLB_REGS + MIXER_GLB_DBUFF_REG); > +} > + > +/* > + * cleanup a mixer > + * > + * This is needed only once after power on. > + */ > +static void de2_mixer_cleanup(struct priv *priv, int mixer, > + u32 size) > +{ > + void __iomem *mixer_io = priv->mmio; > + void __iomem *chan_io; > + u32 data; > + unsigned int i; > + > + mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE; > + chan_io = mixer_io + MIXER_CHAN_REGS; > + > + andl_relaxed(priv->mmio + DE2_SEL_REG, ~1); > + writel_relaxed(1, mixer_io + MIXER_GLB_REGS + MIXER_GLB_DBUFF_REG); > + > + writel_relaxed(MIXER_GLB_CTL_rt_en, > + mixer_io + MIXER_GLB_REGS + MIXER_GLB_CTL_REG); > + writel_relaxed(0, mixer_io + MIXER_GLB_REGS + MIXER_GLB_STATUS_REG); > + > + writel_relaxed(size, mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG); > + > + /* > + * clear the VI/UI channels > + * LCD0: 1 VI and 3 UIs > + * LCD1: 1 VI and 1 UI > + */ > + memset_io(chan_io, 0, VI_SIZE); > + memset_io(chan_io + MIXER_CHAN_SZ, 0, UI_SIZE); > + if (mixer == 0) { > + memset_io(chan_io + MIXER_CHAN_SZ * 2, 0, UI_SIZE); > + memset_io(chan_io + MIXER_CHAN_SZ * 3, 0, UI_SIZE); > + } > + > + /* alpha blending */ > + writel_relaxed(0x00000001 | /* fcolor for primary */ > + MIXER_BLD_FCOLOR_CTL_PEN(0), > + mixer_io + MIXER_BLD_REGS + MIXER_BLD_FCOLOR_CTL_REG); > + for (i = 0; i < MIXER_BLD_ATTR_N; i++) { > + writel_relaxed(0xff000000, > + mixer_io + MIXER_BLD_REGS + MIXER_BLD_ATTRx_FCOLOR(i)); > + writel_relaxed(size, > + mixer_io + MIXER_BLD_REGS + MIXER_BLD_ATTRx_INSIZE(i)); > + writel_relaxed(0, > + mixer_io + MIXER_BLD_REGS + MIXER_BLD_ATTRx_OFFSET(i)); > + } > + writel_relaxed(0, mixer_io + MIXER_BLD_REGS + MIXER_BLD_OUT_CTL_REG); > + > + /* prepare the pipe route for the planes */ > + data = 0; > + for (i = 0; i < DE2_N_PLANES; i++) > + data |= MIXER_BLD_ROUTE(plane_tb[i].chan, plane_tb[i].pipe); > + writel_relaxed(data, mixer_io + MIXER_BLD_REGS + MIXER_BLD_ROUTE_REG); > + > + writel_relaxed(0, mixer_io + MIXER_BLD_REGS + > + MIXER_BLD_PREMULTIPLY_REG); > + writel_relaxed(0xff000000, mixer_io + MIXER_BLD_REGS + > + MIXER_BLD_BKCOLOR_REG); > + writel_relaxed(size, mixer_io + MIXER_BLD_REGS + > + MIXER_BLD_OUTPUT_SIZE_REG); > + writel_relaxed(MIXER_BLD_MODE_SRCOVER, > + mixer_io + MIXER_BLD_REGS + MIXER_BLD_MODEx_REG(0)); > + writel_relaxed(MIXER_BLD_MODE_SRCOVER, > + mixer_io + MIXER_BLD_REGS + MIXER_BLD_MODEx_REG(1)); > + > + /* disable the enhancements */ > + writel_relaxed(0, mixer_io + MIXER_VSU_REGS); > + writel_relaxed(0, mixer_io + MIXER_GSU1_REGS); > + writel_relaxed(0, mixer_io + MIXER_GSU2_REGS); > + writel_relaxed(0, mixer_io + MIXER_GSU3_REGS); > + writel_relaxed(0, mixer_io + MIXER_FCE_REGS); > + writel_relaxed(0, mixer_io + MIXER_BWS_REGS); > + writel_relaxed(0, mixer_io + MIXER_LTI_REGS); > + writel_relaxed(0, mixer_io + MIXER_PEAK_REGS); > + writel_relaxed(0, mixer_io + MIXER_ASE_REGS); > + writel_relaxed(0, mixer_io + MIXER_FCC_REGS); > + writel_relaxed(0, mixer_io + MIXER_DCSC_REGS); > +} > + > +/* enable a mixer */ > +static void de2_mixer_enable(struct lcd *lcd) > +{ > + struct priv *priv = lcd->priv; > + void __iomem *mixer_io = priv->mmio; > + struct drm_display_mode *mode = &lcd->crtc.mode; > + u32 size = WH(mode->hdisplay, mode->vdisplay); > + u32 data; > + int mixer = lcd->mixer; > + int i; > + > + mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE; > + > + /* if not done yet, start the DE processor */ > + if (!priv->started) { > + reset_control_deassert(priv->reset); > + clk_prepare_enable(priv->gate); > + clk_prepare_enable(priv->clk); > + } > + priv->started |= 1 << mixer; > + > + /* set the A83T clock divider (500 / 2) = 250MHz */ > + if (priv->soc_type == SOC_A83T) > + writel_relaxed(0x00000011, /* div = 2 for both LCDs */ > + priv->mmio + DE2_DIV_REG); > + > + /* deassert the mixer and enable its clock */ > + orl_relaxed(priv->mmio + DE2_RESET_REG, mixer == 0 ? 1 : 4); > + data = 1 << mixer; /* 1 bit / lcd */ > + orl_relaxed(priv->mmio + DE2_GATE_REG, data); > + orl_relaxed(priv->mmio + DE2_MOD_REG, data); > + > + /* if not done yet, cleanup and enable */ > + if (!(priv->clean & (1 << mixer))) { > + priv->clean |= 1 << mixer; > + de2_mixer_cleanup(priv, mixer, size); > + return; > + } > + > + /* enable */ > + de2_mixer_select(priv, mixer, mixer_io); > + > + writel_relaxed(MIXER_GLB_CTL_rt_en, > + mixer_io + MIXER_GLB_REGS + MIXER_GLB_CTL_REG); > + writel_relaxed(0, mixer_io + MIXER_GLB_REGS + MIXER_GLB_STATUS_REG); > + > + /* set the size of the frame buffer */ > + writel_relaxed(size, mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG); > + for (i = 0; i < MIXER_BLD_ATTR_N; i++) > + writel_relaxed(size, mixer_io + MIXER_BLD_REGS + > + MIXER_BLD_ATTRx_INSIZE(i)); > + writel_relaxed(size, mixer_io + MIXER_BLD_REGS + > + MIXER_BLD_OUTPUT_SIZE_REG); > + > + writel_relaxed(mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 0, > + mixer_io + MIXER_BLD_REGS + MIXER_BLD_OUT_CTL_REG); > +} > + > +/* enable a LCD (DE mixer) */ > +void de2_de_enable(struct lcd *lcd) > +{ > + mutex_lock(&lcd->priv->mutex); > + > + de2_mixer_enable(lcd); > + > + mutex_unlock(&lcd->priv->mutex); > +} > + > +/* disable a LCD (DE mixer) */ > +void de2_de_disable(struct lcd *lcd) > +{ > + struct priv *priv = lcd->priv; > + void __iomem *mixer_io = priv->mmio; > + int mixer = lcd->mixer; > + u32 data; > + > + mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE; > + > + mutex_lock(&priv->mutex); > + > + de2_mixer_select(priv, mixer, mixer_io); > + > + writel_relaxed(0, mixer_io + MIXER_GLB_REGS + MIXER_GLB_CTL_REG); > + > + data = ~(1 << mixer); > + andl_relaxed(priv->mmio + DE2_MOD_REG, data); > + andl_relaxed(priv->mmio + DE2_GATE_REG, data); > + andl_relaxed(priv->mmio + DE2_RESET_REG, data); > + > + mutex_unlock(&priv->mutex); > + > + /* if all mixers are disabled, stop the DE */ > + priv->started &= ~(1 << mixer); > + if (!priv->started) { > + clk_disable_unprepare(priv->clk); > + clk_disable_unprepare(priv->gate); > + reset_control_assert(priv->reset); > + } > +} > + > +static void de2_vi_update(void __iomem *chan_io, > + struct drm_gem_cma_object *gem, > + int layer, > + unsigned int fmt, > + u32 ui_sel, > + u32 size, > + u32 coord, > + struct drm_framebuffer *fb, > + u32 screen_size) > +{ > + int i; > + > + writel_relaxed(VI_CFG_ATTR_en | > + (fmt << VI_CFG_ATTR_fmt_SHIFT) | > + ui_sel, > + chan_io + VI_CFGx_ATTR(layer)); > + writel_relaxed(size, chan_io + VI_CFGx_SIZE(layer)); > + writel_relaxed(coord, chan_io + VI_CFGx_COORD(layer)); > + for (i = 0; i < VI_N_PLANES; i++) { > + writel_relaxed(fb->pitches[i] ? fb->pitches[i] : > + fb->pitches[0], > + chan_io + VI_CFGx_PITCHy(layer, i)); > + writel_relaxed(gem->paddr + fb->offsets[i], > + chan_io + VI_CFGx_TOP_LADDRy(layer, i)); > + } > + writel_relaxed(0xff000000, chan_io + VI_FCOLORx(layer)); > + if (layer == 0) { > + writel_relaxed(screen_size, > + chan_io + VI_OVL_SIZEx(0)); > + } > +} > + > +static void de2_ui_update(void __iomem *chan_io, > + struct drm_gem_cma_object *gem, > + int layer, > + unsigned int fmt, > + u32 alpha_glob, > + u32 size, > + u32 coord, > + struct drm_framebuffer *fb, > + u32 screen_size) > +{ > + writel_relaxed(UI_CFG_ATTR_en | > + (fmt << UI_CFG_ATTR_fmt_SHIFT) | > + alpha_glob, > + chan_io + UI_CFGx_ATTR(layer)); > + writel_relaxed(size, chan_io + UI_CFGx_SIZE(layer)); > + writel_relaxed(coord, chan_io + UI_CFGx_COORD(layer)); > + writel_relaxed(fb->pitches[0], chan_io + UI_CFGx_PITCH(layer)); > + writel_relaxed(gem->paddr + fb->offsets[0], > + chan_io + UI_CFGx_TOP_LADDR(layer)); > + if (layer == 0) > + writel_relaxed(screen_size, chan_io + UI_OVL_SIZE); > +} > + > +static void de2_plane_update(struct priv *priv, struct lcd *lcd, > + int plane_num, > + struct drm_plane_state *state, > + struct drm_plane_state *old_state) > +{ > + void __iomem *mixer_io = priv->mmio; > + void __iomem *chan_io; > + struct drm_framebuffer *fb = state->fb; > + struct drm_gem_cma_object *gem; > + u32 size = WH(state->crtc_w, state->crtc_h); > + u32 coord, screen_size; > + u32 fcolor; > + u32 ui_sel, alpha_glob; > + int mixer = lcd->mixer; > + int chan, layer, x, y; > + unsigned int fmt; > + > + chan = plane_tb[plane_num].chan; > + layer = plane_tb[plane_num].layer; > + > + mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE; > + chan_io = mixer_io + MIXER_CHAN_REGS + MIXER_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); > + > + /* if plane update was delayed, force a full update */ > + if (priv->lcds[drm_crtc_index(&lcd->crtc)]->delayed & > + (1 << plane_num)) { > + priv->lcds[drm_crtc_index(&lcd->crtc)]->delayed &= > + ~(1 << plane_num); > + > + /* handle plane move */ > + } else if (fb == old_state->fb) { > + de2_mixer_select(priv, mixer, mixer_io); > + if (chan == 0) > + writel_relaxed(coord, chan_io + VI_CFGx_COORD(layer)); > + else > + writel_relaxed(coord, chan_io + UI_CFGx_COORD(layer)); > + 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("de2_plane_update: format %.4s not yet treated\n", > + (char *) &fb->pixel_format); > + return; > + } > + > + /* the overlay size is the one of the primary plane */ > + screen_size = plane_num == DE2_PRIMARY_PLANE ? > + size : > + readl_relaxed(mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG); > + > + /* prepare pipe enable */ > + fcolor = readl_relaxed(mixer_io + MIXER_BLD_REGS + > + MIXER_BLD_FCOLOR_CTL_REG); > + fcolor |= MIXER_BLD_FCOLOR_CTL_PEN(plane_tb[plane_num].pipe); > + > + de2_mixer_select(priv, mixer, mixer_io); > + > + if (chan == 0) /* VI channel */ > + de2_vi_update(chan_io, gem, layer, fmt, ui_sel, size, coord, > + fb, screen_size); > + else /* UI channel */ > + de2_ui_update(chan_io, gem, layer, fmt, alpha_glob, size, coord, > + fb, screen_size); > + writel_relaxed(fcolor, mixer_io + MIXER_BLD_REGS + > + MIXER_BLD_FCOLOR_CTL_REG); > +} > + > +static int vi_nb_layers(void __iomem *chan_io) > +{ > + int layer, n = 0; > + > + for (layer = 0; layer < 4; layer++) { > + if (readl_relaxed(chan_io + VI_CFGx_ATTR(layer)) != 0) > + n++; > + } > + > + return n; > +} > + > +static int ui_nb_layers(void __iomem *chan_io) > +{ > + int layer, n = 0; > + > + for (layer = 0; layer < 4; layer++) { > + if (readl_relaxed(chan_io + UI_CFGx_ATTR(layer)) != 0) > + n++; > + } > + > + return n; > +} > + > +static void de2_plane_disable(struct priv *priv, > + int mixer, int plane_num) > +{ > + void __iomem *mixer_io = priv->mmio; > + void __iomem *chan_io; > + u32 fcolor; > + int chan, layer, n; > + > + chan = plane_tb[plane_num].chan; > + layer = plane_tb[plane_num].layer; > + > + mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE; > + chan_io = mixer_io + MIXER_CHAN_REGS + MIXER_CHAN_SZ * chan; > + > + if (chan == 0) > + n = vi_nb_layers(chan_io); > + else > + n = ui_nb_layers(chan_io); > + > + fcolor = readl_relaxed(mixer_io + MIXER_BLD_REGS + > + MIXER_BLD_FCOLOR_CTL_REG); > + > + de2_mixer_select(priv, mixer, mixer_io); > + > + if (chan == 0) > + writel_relaxed(0, chan_io + VI_CFGx_ATTR(layer)); > + else > + writel_relaxed(0, chan_io + UI_CFGx_ATTR(layer)); > + > + /* disable the pipe if no more active layer */ > + if (n <= 1) > + writel_relaxed(fcolor & > + ~MIXER_BLD_FCOLOR_CTL_PEN(plane_tb[plane_num].pipe), > + mixer_io + MIXER_BLD_REGS + MIXER_BLD_FCOLOR_CTL_REG); > +} > + > +static void de2_drm_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 priv *priv = lcd->priv; > + int plane_num = plane - lcd->planes; > + > + /* if the crtc is disabled, mark update delayed */ > + if (!(priv->started & (1 << lcd->mixer))) { > + lcd->delayed |= 1 << plane_num; > + return; /* mixer disabled */ > + } > + > + mutex_lock(&priv->mutex); > + > + de2_plane_update(priv, lcd, plane_num, state, old_state); > + > + mutex_unlock(&priv->mutex); > +} > + > +static void de2_drm_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); > + struct priv *priv = lcd->priv; > + int plane_num = plane - lcd->planes; > + > + if (!(priv->started & (1 << lcd->mixer))) > + return; /* mixer disabled */ > + > + mutex_lock(&priv->mutex); > + > + de2_plane_disable(lcd->priv, lcd->mixer, plane_num); > + > + mutex_unlock(&priv->mutex); > +} > + > +static const struct drm_plane_helper_funcs plane_helper_funcs = { > + .atomic_update = de2_drm_plane_update, > + .atomic_disable = de2_drm_plane_disable, > +}; > + > +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 possible_crtcs, > + int plane_num) > +{ > + int ret; > + > + ret = drm_universal_plane_init(drm, plane, possible_crtcs, > + &plane_funcs, > + plane_tb[plane_num].formats, > + plane_tb[plane_num].n_formats, > + plane_tb[plane_num].type, NULL); > + if (ret >= 0) > + drm_plane_helper_add(plane, &plane_helper_funcs); > + > + return ret; > +} > + > +/* initialize the planes */ > +int de2_plane_init(struct drm_device *drm, struct lcd *lcd) > +{ > + int i, n, ret, possible_crtcs = 1 << drm_crtc_index(&lcd->crtc); > + > + n = ARRAY_SIZE(plane_tb); > + if (n != DE2_N_PLANES) { > + dev_err(lcd->dev, "Bug: incorrect number of planes %d != " > + __stringify(DE2_N_PLANES) "\n", n); > + return -EINVAL; > + } > + > + for (i = 0; i < n; i++) { > + ret = de2_one_plane_init(drm, &lcd->planes[i], > + possible_crtcs, i); > + if (ret < 0) { > + dev_err(lcd->dev, "plane init failed %d\n", ret); > + break; > + } > + } > + > + return ret; > +} > -- > 2.10.2 > > _______________________________________________ > dri-devel mailing list > dri-devel@xxxxxxxxxxxxxxxxxxxxx > https://lists.freedesktop.org/mailman/listinfo/dri-devel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html