From patchwork Sun Nov 20 09:53:25 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Jean-Francois Moine X-Patchwork-Id: 9438489 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 03FDA606DB for ; Sun, 20 Nov 2016 12:07:45 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E2259285C4 for ; Sun, 20 Nov 2016 12:07:44 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D522228A66; Sun, 20 Nov 2016 12:07:44 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00,FREEMAIL_FROM, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 82FA1285C4 for ; Sun, 20 Nov 2016 12:07:42 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 5D2F96E15A; Sun, 20 Nov 2016 12:07:36 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from smtp1-g21.free.fr (smtp1-g21.free.fr [212.27.42.1]) by gabe.freedesktop.org (Postfix) with ESMTPS id 458EC6E152 for ; Sun, 20 Nov 2016 12:07:29 +0000 (UTC) Received: from localhost (unknown [37.164.201.13]) by smtp1-g21.free.fr (Postfix) with ESMTP id 11B1FB00520; Sun, 20 Nov 2016 13:07:27 +0100 (CET) X-Mailbox-Line: From ff2140c86ea3b06406c77d9e8746474b6f8400c4 Mon Sep 17 00:00:00 2001 Message-Id: In-Reply-To: References: From: Jean-Francois Moine Date: Sun, 20 Nov 2016 10:53:25 +0100 Subject: [PATCH v6 1/5] drm: sun8i: Add a basic DRM driver for Allwinner DE2 To: Dave Airlie , Maxime Ripard , Rob Herring Cc: devicetree@vger.kernel.org, linux-sunxi@googlegroups.com, linux-arm-kernel@lists.infradead.org, dri-devel@lists.freedesktop.org X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP 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 --- .../bindings/display/sunxi/sun8i-de2.txt | 83 +++ 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 | 440 +++++++++++++ drivers/gpu/drm/sun8i/de2_crtc.h | 50 ++ drivers/gpu/drm/sun8i/de2_drm.h | 48 ++ drivers/gpu/drm/sun8i/de2_drv.c | 379 +++++++++++ drivers/gpu/drm/sun8i/de2_plane.c | 712 +++++++++++++++++++++ 10 files changed, 1741 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt 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_drm.h create mode 100644 drivers/gpu/drm/sun8i/de2_drv.c create mode 100644 drivers/gpu/drm/sun8i/de2_plane.c diff --git a/Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt b/Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt new file mode 100644 index 0000000..b9edd4b --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt @@ -0,0 +1,83 @@ +Allwinner sun8i Display Engine 2 subsystem +========================================== + +The Allwinner 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": DE bus gate + "clock": DE clock + +- resets: phandle to the reset of the device + +- ports: phandle's to the LCD ports + +LCD controller +============== + +Required properties: + +- compatible: should be + "allwinner,sun8i-a83t-tcon" + +- clocks: must include clock specifiers corresponding to entries in the + clock-names property. + +- clock-names: must contain + "gate": TCON bus gate + "clock": TCON 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-tcon"; + ... + 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 { + hdmi_ep: endpoint { + remote-endpoint = <&lcd0_ep>; + }; + }; + }; + 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..65c9b93 --- /dev/null +++ b/drivers/gpu/drm/sun8i/de2_crtc.c @@ -0,0 +1,440 @@ +/* + * Allwinner DRM driver - DE2 CRTC + * + * Copyright (C) 2016 Jean-Francois 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 +#include +#include +#include +#include + +#include "de2_drm.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 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, + 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 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); + + writel_relaxed(XY(1, 1), lcd->mmio + TCON1_PS_SYNC_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); + + writel_relaxed(0x0fffffff, /* TRI disabled */ + lcd->mmio + TCON1_IO_TRI_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; + struct clk *parent_clk; + u32 parent_rate; + int ret; + + /* determine and set the best rate for the parent clock (pll-video) */ + if (297000 % mode->clock == 0) + parent_rate = 297000000; + else if ((270000 * 2) % mode->clock == 0) + parent_rate = 270000000; + else + return; /* "640x480" rejected */ + 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; + } + + /* then, set the TCON clock rate */ + ret = clk_set_rate(lcd->clk, mode->clock * 1000); + if (ret) { + dev_err(lcd->dev, "set rate %dKHz failed %d\n", + mode->clock, ret); + return; + } + + /* start the TCON */ + reset_control_deassert(lcd->reset); + clk_prepare_enable(lcd->bus); + clk_prepare_enable(lcd->clk); + lcd->clk_enabled = true; + + 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, index; + + lcd->priv = priv; + + 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) + return ret; + + /* set the lcd/crtc reference */ + index = drm_crtc_index(crtc); + if (index >= ARRAY_SIZE(priv->lcds)) { + dev_err(drm->dev, "Bad crtc index"); + return -ENOENT; + } + priv->lcds[index] = lcd; + + return ret; +} + +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; + + /* get the LCD (mixer) number */ + id = of_alias_get_id(np, "lcd"); + if (id < 0 || id >= 2) { + dev_err(dev, "no or bad alias for lcd\n"); + id = 0; + } + 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 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->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..f663ba4 --- /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 + +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_drm.h b/drivers/gpu/drm/sun8i/de2_drm.h new file mode 100644 index 0000000..c42c30a --- /dev/null +++ b/drivers/gpu/drm/sun8i/de2_drm.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 +#include +#include + +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_drv.c b/drivers/gpu/drm/sun8i/de2_drv.c new file mode 100644 index 0000000..67368f5 --- /dev/null +++ b/drivers/gpu/drm/sun8i/de2_drv.c @@ -0,0 +1,379 @@ +/* + * Allwinner DRM driver - DE2 DRM driver + * + * Copyright (C) 2016 Jean-Francois 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 +#include +#include +#include +#include +#include +#include +#include + +#include "de2_drm.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; + int 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, "out of memory\n"); + 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, "failed to initialize vblank\n"); + + { + struct lcd *lcd; + int i; + + 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, +}; + +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, + .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 "); +MODULE_DESCRIPTION("Allwinner DE2 DRM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/sun8i/de2_plane.c b/drivers/gpu/drm/sun8i/de2_plane.c new file mode 100644 index 0000000..47c94dd --- /dev/null +++ b/drivers/gpu/drm/sun8i/de2_plane.c @@ -0,0 +1,712 @@ +/* + * Allwinner DRM driver - Display Engine 2 + * + * Copyright (C) 2016 Jean-Francois Moine + * 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 +#include +#include +#include +#include +#include + +#include "de2_drm.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) +{ + void __iomem *mixer_io = priv->mmio; + void __iomem *chan_io; + u32 size = WH(1920, 1080); /* (any size) */ + unsigned int i; + u32 data; + + mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE; + chan_io = mixer_io + MIXER_CHAN_REGS; + + /* 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); + + de2_mixer_select(priv, mixer, mixer_io); + 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); + } + + /* clear and set default values alpha blending */ + memset_io(mixer_io + MIXER_BLD_REGS, 0, + MIXER_BLD_ATTR_SIZE * MIXER_BLD_ATTR_N); + 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)); + writel_relaxed(0, mixer_io + MIXER_BLD_REGS + MIXER_BLD_OUT_CTL_REG); + + /* 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; + int mixer = lcd->mixer; + u32 data; + + mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE; + + if (priv->started & (1 << mixer)) + return; /* mixer already enabled */ + + /* 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; + + /* deassert the mixer and enable the 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); + + /* enable */ + andl_relaxed(priv->mmio + DE2_SEL_REG, ~1); /* mixer select */ + writel_relaxed(MIXER_GLB_CTL_rt_en | MIXER_GLB_CTL_rtwb_port, + mixer_io + MIXER_GLB_REGS + MIXER_GLB_CTL_REG); + writel_relaxed(0, mixer_io + MIXER_GLB_REGS + MIXER_GLB_STATUS_REG); + + /* restore the frame buffer size */ + writel_relaxed(WH(lcd->crtc.mode.hdisplay, + lcd->crtc.mode.vdisplay), + mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG); + + /* if not yet done, cleanup */ + if (!(priv->clean & (1 << mixer))) { + priv->clean |= 1 << mixer; + de2_mixer_cleanup(priv, mixer); + } +} + +/* 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; + + if (!(priv->started & (1 << mixer))) + return; /* mixer already disabled */ + + 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; + + mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE; + + chan = plane_tb[plane_num].chan; + layer = plane_tb[plane_num].layer; + + 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 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, chan_disable = 0; + + mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE; + + chan = plane_tb[plane_num].chan; + layer = plane_tb[plane_num].layer; + + chan_io = mixer_io + MIXER_CHAN_REGS + MIXER_CHAN_SZ * chan; + + /* + * check if the pipe should be disabled + * (this code works with only 2 layers) + */ + if (chan == 0) { + if (readl_relaxed(chan_io + VI_CFGx_ATTR(1 - layer)) == 0) + chan_disable = 1; + } else { + if (readl_relaxed(chan_io + UI_CFGx_ATTR(1 - layer)) == 0) + chan_disable = 1; + } + + 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)); + + /* if no more layer in this channel, disable the pipe */ + if (chan_disable) { + 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; +}