From patchwork Fri Feb 16 11:25:09 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jyri Sarha X-Patchwork-Id: 10224493 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 12AA2603EE for ; Fri, 16 Feb 2018 11:26:00 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id F21022948E for ; Fri, 16 Feb 2018 11:25:59 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E5FB729496; Fri, 16 Feb 2018 11:25:59 +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.1 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED,T_DKIM_INVALID 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 4475E2948E for ; Fri, 16 Feb 2018 11:25:57 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id ED7196E62A; Fri, 16 Feb 2018 11:25:39 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from lelnx193.ext.ti.com (lelnx193.ext.ti.com [198.47.27.77]) by gabe.freedesktop.org (Postfix) with ESMTPS id 655A96E61E for ; Fri, 16 Feb 2018 11:25:29 +0000 (UTC) Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by lelnx193.ext.ti.com (8.15.1/8.15.1) with ESMTP id w1GBPQaP022831; Fri, 16 Feb 2018 05:25:26 -0600 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ti.com; s=ti-com-17Q1; t=1518780326; bh=FhdMXH1YpqvrG8QSt6qg7wE3BSxIcShZeeKQ/vmYMLc=; h=From:To:CC:Subject:Date:In-Reply-To:References; b=Y/w+E9QOL0wmDZDkDSLChNjl7VLtfADTDrVQk9aWGf1cA6Z9iVFpSw/ZtFjKLC0/u MmITxmLSydxO1id+wroXtp1F86L6KmYdUIz5Sif8rfBVDvPCRVETiay6lHANDaQFa7 EdY/971QbicW1x5fscNgD62BJDx4WboBWMh+fn2c= Received: from DFLE115.ent.ti.com (dfle115.ent.ti.com [10.64.6.36]) by dlelxv90.itg.ti.com (8.14.3/8.13.8) with ESMTP id w1GBPQDN003717; Fri, 16 Feb 2018 05:25:26 -0600 Received: from DFLE105.ent.ti.com (10.64.6.26) by DFLE115.ent.ti.com (10.64.6.36) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.1261.35; Fri, 16 Feb 2018 05:25:25 -0600 Received: from dflp32.itg.ti.com (10.64.6.15) by DFLE105.ent.ti.com (10.64.6.26) with Microsoft SMTP Server (version=TLS1_0, cipher=TLS_RSA_WITH_AES_256_CBC_SHA) id 15.1.1261.35 via Frontend Transport; Fri, 16 Feb 2018 05:25:25 -0600 Received: from jadmar.ti.com (ileax41-snat.itg.ti.com [10.172.224.153]) by dflp32.itg.ti.com (8.14.3/8.13.8) with ESMTP id w1GBPCik028841; Fri, 16 Feb 2018 05:25:24 -0600 From: Jyri Sarha To: , , Subject: [PATCH RFC 8/9] drm/omap: add TI DSS6 driver Date: Fri, 16 Feb 2018 13:25:09 +0200 Message-ID: <74c5e058bcef1bb7b986cdc2ecc59caaf35d6e3d.1518780268.git.jsarha@ti.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: References: MIME-Version: 1.0 X-EXCLAIMER-MD-CONFIG: e1e8a2fd-e40a-4ac6-ac9b-f7e9cc9ee180 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Jyri Sarha Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP From: Tomi Valkeinen Add support for DSS6 IP on TI K2G SoC. DSS6 is an evolution of the OMAP DSS (DSS2). OMAP DSS has been quite similar from OMAP2 to OMAP5 (including DRA7 and AM5 which have basically the same IP as OMAP5), but in DSS6 lots of things have been restructured. DSS6 can still be considered as being the same family as DSS2, as the features offered are mostly the same, and the model how things happen and work are the same. What's changed are the registers and how the functionalities in the IP are divided. Thus there was not much point in trying to share the low level parts of the driver, and instead a new driver was written from scratch. This patch adds a new DSS driver for DSS6. The DSS2's panel and encoder drivers and also omapdrm driver can be used with DSS6. Signed-off-by: Tomi Valkeinen Signed-off-by: Jyri Sarha --- drivers/gpu/drm/omapdrm/dss/Kconfig | 8 + drivers/gpu/drm/omapdrm/dss/Makefile | 3 + drivers/gpu/drm/omapdrm/dss/dispc6.c | 1438 ++++++++++++++++++++++++++++++++++ drivers/gpu/drm/omapdrm/dss/dispc6.h | 109 +++ drivers/gpu/drm/omapdrm/dss/dpi6.c | 283 +++++++ drivers/gpu/drm/omapdrm/dss/dss6.c | 346 ++++++++ drivers/gpu/drm/omapdrm/dss/dss6.h | 54 ++ 7 files changed, 2241 insertions(+) create mode 100644 drivers/gpu/drm/omapdrm/dss/dispc6.c create mode 100644 drivers/gpu/drm/omapdrm/dss/dispc6.h create mode 100644 drivers/gpu/drm/omapdrm/dss/dpi6.c create mode 100644 drivers/gpu/drm/omapdrm/dss/dss6.c create mode 100644 drivers/gpu/drm/omapdrm/dss/dss6.h diff --git a/drivers/gpu/drm/omapdrm/dss/Kconfig b/drivers/gpu/drm/omapdrm/dss/Kconfig index f24ebf7..82faea6 100644 --- a/drivers/gpu/drm/omapdrm/dss/Kconfig +++ b/drivers/gpu/drm/omapdrm/dss/Kconfig @@ -132,3 +132,11 @@ config OMAP2_DSS_SLEEP_AFTER_VENC_RESET disable the sleep if it doesn't cause problems on your platform. endif + +config TI_DSS6 + tristate "TI DSS6 support" + select OMAP_DSS_BASE + select VIDEOMODE_HELPERS + select OMAP2_DSS_INIT + help + Enable support for Texas Instruments DSS6 IP. diff --git a/drivers/gpu/drm/omapdrm/dss/Makefile b/drivers/gpu/drm/omapdrm/dss/Makefile index 5950c3f..c669b2f 100644 --- a/drivers/gpu/drm/omapdrm/dss/Makefile +++ b/drivers/gpu/drm/omapdrm/dss/Makefile @@ -18,3 +18,6 @@ omapdss-$(CONFIG_OMAP4_DSS_HDMI) += hdmi4.o hdmi4_core.o omapdss-$(CONFIG_OMAP4_DSS_HDMI_CEC) += hdmi4_cec.o omapdss-$(CONFIG_OMAP5_DSS_HDMI) += hdmi5.o hdmi5_core.o ccflags-$(CONFIG_OMAP2_DSS_DEBUG) += -DDEBUG + +obj-$(CONFIG_TI_DSS6) += omapdss6.o +omapdss6-y := dss6.o dispc6.o dpi6.o diff --git a/drivers/gpu/drm/omapdrm/dss/dispc6.c b/drivers/gpu/drm/omapdrm/dss/dispc6.c new file mode 100644 index 0000000..fbe4de5 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/dispc6.c @@ -0,0 +1,1438 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "omapdss.h" +#include "dss6.h" +#include "dispc6.h" + +#define REG_GET(dispc, idx, start, end) \ + FLD_GET(dispc6_read(dispc, idx), start, end) + +#define REG_FLD_MOD(dispc, idx, val, start, end) \ + dispc6_write(dispc, idx, FLD_MOD(dispc6_read(dispc, idx), val, start, end)) + +#define VID_REG_GET(dispc, plane, idx, start, end) \ + FLD_GET(dispc6_vid_read(dispc, plane, idx), start, end) + +#define VID_REG_FLD_MOD(dispc, plane, idx, val, start, end) \ + dispc6_vid_write(dispc, plane, idx, FLD_MOD(dispc6_vid_read(dispc, plane, idx), val, start, end)) + + +#define VP_REG_GET(dispc, vp, idx, start, end) \ + FLD_GET(dispc6_vp_read(dispc, vp, idx), start, end) + +#define VP_REG_FLD_MOD(dispc, vp, idx, val, start, end) \ + dispc6_vp_write(dispc, vp, idx, FLD_MOD(dispc6_vp_read(dispc, vp, idx), val, start, end)) + +struct dispc_features { + /* XXX should these come from the .dts? Min pclk is not feature of DSS IP */ + unsigned long min_pclk; + unsigned long max_pclk; +}; + +/* Note: 9MHz is a special allowed case, and is handled separately in the code */ +static const struct dispc_features k2g_dispc_feats = { + .min_pclk = 43750000, + .max_pclk = 150000000, +}; + +static const struct of_device_id dispc6_of_match[]; + +struct dispc_device { + struct platform_device *pdev; + + struct dss6_device *dss6; + + void __iomem *base_common; + void __iomem *base_vid1; + void __iomem *base_ovr1; + void __iomem *base_vp1; + + int irq; + irq_handler_t user_handler; + void *user_data; + + const struct dispc_features *feat; + + struct clk *fclk; + struct clk *vp_clk; + + bool is_enabled; + + bool ctx_valid; + u32 ctx_vid1[0x400]; + + u32 gamma_table[256]; +}; + +static void dispc6_write(struct dispc_device *dispc, u16 reg, u32 val) +{ + iowrite32(val, dispc->base_common + reg); +} + +static u32 dispc6_read(struct dispc_device *dispc, u16 reg) +{ + return ioread32(dispc->base_common + reg); +} + +static void dispc6_vid_write(struct dispc_device *dispc, + enum omap_plane_id plane, u16 reg, u32 val) +{ + void __iomem *base = dispc->base_vid1; + + iowrite32(val, base + reg); +} + +static u32 dispc6_vid_read(struct dispc_device *dispc, + enum omap_plane_id plane, u16 reg) +{ + void __iomem *base = dispc->base_vid1; + + return ioread32(base + reg); +} + +static void dispc6_ovr_write(struct dispc_device *dispc, + enum omap_channel channel, u16 reg, u32 val) +{ + void __iomem *base = dispc->base_ovr1; + + iowrite32(val, base + reg); +} +#if 0 /* not used at the moment */ +static u32 dispc6_ovr_read(struct dispc_device *dispc, + enum omap_channel channel, u16 reg) +{ + void __iomem *base = dispc->base_ovr1; + + return ioread32(base + reg); +} +#endif +static void dispc6_vp_write(struct dispc_device *dispc, + enum omap_channel channel, u16 reg, u32 val) +{ + void __iomem *base = dispc->base_vp1; + + iowrite32(val, base + reg); +} + +static u32 dispc6_vp_read(struct dispc_device *dispc, + enum omap_channel channel, u16 reg) +{ + void __iomem *base = dispc->base_vp1; + + return ioread32(base + reg); +} + +int dispc6_runtime_get(struct dispc_device *dispc) +{ + int r; + + dev_dbg(&dispc->pdev->dev, "dispc_runtime_get\n"); + + r = pm_runtime_get_sync(&dispc->pdev->dev); + WARN_ON(r < 0); + return r < 0 ? r : 0; +} + +void dispc6_runtime_put(struct dispc_device *dispc) +{ + int r; + + dev_dbg(&dispc->pdev->dev, "dispc_runtime_put\n"); + + r = pm_runtime_put_sync(&dispc->pdev->dev); + WARN_ON(r < 0); +} + +static void dispc6_save_context(struct dispc_device *dispc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dispc->ctx_vid1); ++i) + dispc->ctx_vid1[i] = dispc6_vid_read(dispc, 0, i); + + dispc->ctx_valid = true; +} + +static void dispc6_restore_context(struct dispc_device *dispc) +{ + int i; + + if (!dispc->ctx_valid) + return; + + for (i = 0; i < ARRAY_SIZE(dispc->ctx_vid1); ++i) + dispc6_vid_write(dispc, 0, i, dispc->ctx_vid1[i]); +} + +static irqreturn_t dispc6_irq_handler(int irq, void *arg) +{ + struct dispc_device *dispc = arg; + u32 stat; + + if (!dispc->is_enabled) + return IRQ_NONE; + + stat = dispc6_read(dispc, DISPC_IRQSTATUS); + + if (stat == 0) + return IRQ_NONE; + + dispc6_write(dispc, DISPC_IRQSTATUS, stat); + + return dispc->user_handler(irq, dispc->user_data); +} + +static int dispc6_request_irq(struct dispc_device *dispc, + irq_handler_t handler, void *dev_id) +{ + int r; + + if (dispc->user_handler != NULL) + return -EBUSY; + + dispc->user_handler = handler; + dispc->user_data = dev_id; + + /* ensure the dispc6_irq_handler sees the values above */ + smp_wmb(); + + r = devm_request_irq(&dispc->pdev->dev, dispc->irq, dispc6_irq_handler, + IRQF_SHARED, "DISPC", dispc); + if (r) { + dispc->user_handler = NULL; + dispc->user_data = NULL; + } + + return r; +} + +static void dispc6_free_irq(struct dispc_device *dispc, void *dev_id) +{ + dispc6_write(dispc, DISPC_IRQENABLE_CLR, 0xffffffff); + + devm_free_irq(&dispc->pdev->dev, dispc->irq, &dispc); + + dispc->user_handler = NULL; + dispc->user_data = NULL; +} + +u64 dispc6_vp_irq_from_raw(u32 stat) +{ + uint channel = 0; + u64 vp_stat = 0; + + if (stat & BIT(0)) + vp_stat |= DSS_IRQ_MGR_FRAME_DONE(channel); + if (stat & BIT(1)) + vp_stat |= DSS_IRQ_MGR_VSYNC_EVEN(channel); + if (stat & BIT(2)) + vp_stat |= DSS_IRQ_MGR_VSYNC_ODD(channel); + if (stat & BIT(4)) + vp_stat |= DSS_IRQ_MGR_SYNC_LOST(channel); + + return vp_stat; +} + +static u32 dispc6_vp_irq_to_raw(u64 vpstat) +{ + uint channel = 0; + u32 stat = 0; + + if (vpstat & DSS_IRQ_MGR_FRAME_DONE(channel)) + stat |= BIT(0); + if (vpstat & DSS_IRQ_MGR_VSYNC_EVEN(channel)) + stat |= BIT(1); + if (vpstat & DSS_IRQ_MGR_VSYNC_ODD(channel)) + stat |= BIT(2); + if (vpstat & DSS_IRQ_MGR_SYNC_LOST(channel)) + stat |= BIT(4); + + return stat; +} + +static u64 dispc6_vid_irq_from_raw(u32 stat) +{ + uint plane = 0; + u64 vid_stat = 0; + + if (stat & BIT(0)) + vid_stat |= DSS_IRQ_OVL_FIFO_UNDERFLOW(plane); + + return vid_stat; +} + +static u32 dispc6_vid_irq_to_raw(u64 vidstat) +{ + uint plane = 0; + u32 stat = 0; + + if (vidstat & DSS_IRQ_OVL_FIFO_UNDERFLOW(plane)) + stat |= BIT(0); + + return stat; +} + + +static u64 dispc6_vp_read_irqstatus(struct dispc_device *dispc, + enum omap_channel channel) +{ + u32 stat = dispc6_vp_read(dispc, channel, DISPC_VP_IRQSTATUS); + + return dispc6_vp_irq_from_raw(stat); +} + +static void dispc6_vp_write_irqstatus(struct dispc_device *dispc, + enum omap_channel channel, + u64 vpstat) +{ + u32 stat = dispc6_vp_irq_to_raw(vpstat); + + dispc6_vp_write(dispc, channel, DISPC_VP_IRQSTATUS, stat); +} + +static u64 dispc6_vid_read_irqstatus(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + u32 stat = dispc6_vid_read(dispc, plane, DISPC_VID_IRQSTATUS); + + return dispc6_vid_irq_from_raw(stat); +} + +static void dispc6_vid_write_irqstatus(struct dispc_device *dispc, + enum omap_plane_id plane, + u64 vidstat) +{ + u32 stat = dispc6_vid_irq_to_raw(vidstat); + + dispc6_vid_write(dispc, plane, DISPC_VID_IRQSTATUS, stat); +} + + +static u64 dispc6_vp_read_irqenable(struct dispc_device *dispc, + enum omap_channel channel) +{ + u32 stat = dispc6_vp_read(dispc, channel, DISPC_VP_IRQENABLE); + + return dispc6_vp_irq_from_raw(stat); +} + +static void dispc6_vp_write_irqenable(struct dispc_device *dispc, + enum omap_channel channel, + u64 vpstat) +{ + u32 stat = dispc6_vp_irq_to_raw(vpstat); + + dispc6_vp_write(dispc, channel, DISPC_VP_IRQENABLE, stat); +} + +static u64 dispc6_vid_read_irqenable(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + u32 stat = dispc6_vid_read(dispc, plane, DISPC_VID_IRQENABLE); + + return dispc6_vid_irq_from_raw(stat); +} + +static void dispc6_vid_write_irqenable(struct dispc_device *dispc, + enum omap_plane_id plane, + u64 vidstat) +{ + u32 stat = dispc6_vid_irq_to_raw(vidstat); + + dispc6_vid_write(dispc, plane, DISPC_VID_IRQENABLE, stat); +} + + +static void dispc6_clear_irqstatus(struct dispc_device *dispc, u64 mask) +{ + dispc6_vp_write_irqstatus(dispc, 0, mask); + dispc6_vid_write_irqstatus(dispc, 0, mask); +} + +static u64 dispc6_read_and_clear_irqstatus(struct dispc_device *dispc) +{ + u64 stat = 0; + + stat |= dispc6_vp_read_irqstatus(dispc, 0); + stat |= dispc6_vid_read_irqstatus(dispc, 0); + + dispc6_clear_irqstatus(dispc, stat); + + return stat; +} + +static u64 dispc6_read_irqenable(struct dispc_device *dispc) +{ + u64 stat = 0; + + stat |= dispc6_vp_read_irqenable(dispc, 0); + stat |= dispc6_vid_read_irqenable(dispc, 0); + + return stat; +} + +static void dispc6_write_irqenable(struct dispc_device *dispc, u64 mask) +{ + u64 old_mask = dispc6_read_irqenable(dispc); + + /* clear the irqstatus for newly enabled irqs */ + dispc6_clear_irqstatus(dispc, (mask ^ old_mask) & mask); + + dispc6_vp_write_irqenable(dispc, 0, mask); + dispc6_vid_write_irqenable(dispc, 0, mask); + + dispc6_write(dispc, DISPC_IRQENABLE_SET, (1 << 0) | (1 << 7)); + + /* flush posted write */ + dispc6_read_irqenable(dispc); +} + + +static bool dispc6_mgr_go_busy(struct dispc_device *dispc, + enum omap_channel channel) +{ + return VP_REG_GET(dispc, channel, DISPC_VP_CONTROL, 5, 5); +} + +static void dispc6_mgr_go(struct dispc_device *dispc, + enum omap_channel channel) +{ + VP_REG_FLD_MOD(dispc, channel, DISPC_VP_CONTROL, 1, 5, 5); +} + +static void dispc6_mgr_enable(struct dispc_device *dispc, + enum omap_channel channel, bool enable) +{ + VP_REG_FLD_MOD(dispc, channel, DISPC_VP_CONTROL, !!enable, 0, 0); +} + +static bool dispc6_mgr_is_enabled(struct dispc_device *dispc, + enum omap_channel channel) +{ + return VP_REG_GET(dispc, channel, DISPC_VP_CONTROL, 0, 0); +} + +static u16 c8_to_c12(u8 c8) +{ + u16 c12; + + c12 = c8 << 4; + + /* Replication logic: Copy c8 4 MSB to 4 LSB for full scale c12 */ + c12 = c8 >> 4; + + return c12; +} + +static u64 argb8888_to_argb12121212(u32 argb8888) +{ + u8 a, r, g, b; + u64 v; + + a = (argb8888 >> 24) & 0xff; + r = (argb8888 >> 16) & 0xff; + g = (argb8888 >> 8) & 0xff; + b = (argb8888 >> 0) & 0xff; + + v = ((u64)c8_to_c12(a) << 36) | ((u64)c8_to_c12(r) << 24) | + ((u64)c8_to_c12(g) << 12) | (u64)c8_to_c12(b); + + return v; +} + +static void dispc6_mgr_setup(struct dispc_device *dispc, + enum omap_channel channel, + const struct omap_overlay_manager_info *info) +{ + u64 v; + + v = argb8888_to_argb12121212(info->default_color); + + dispc6_ovr_write(dispc, 0, DISPC_OVR_DEFAULT_COLOR, v & 0xffffffff); + dispc6_ovr_write(dispc, 0, DISPC_OVR_DEFAULT_COLOR2, (v >> 32) & 0xffff); +} + +static void dispc6_set_num_datalines(struct dispc_device *dispc, + enum omap_channel channel, int num_lines) +{ + int v; + + switch (num_lines) { + case 12: + v = 0; break; + case 16: + v = 1; break; + case 18: + v = 2; break; + case 24: + v = 3; break; + case 30: + v = 4; break; + case 36: + v = 5; break; + default: + __WARN(); + v = 3; + break; + } + + VP_REG_FLD_MOD(dispc, channel, DISPC_VP_CONTROL, v, 10, 8); +} + +static void dispc6_mgr_set_lcd_config(struct dispc_device *dispc, + enum omap_channel channel, + const struct dss_lcd_mgr_config *config) +{ + dispc6_set_num_datalines(dispc, channel, config->video_port_width); +} + +static bool dispc6_lcd_timings_ok(int hsw, int hfp, int hbp, + int vsw, int vfp, int vbp) +{ + if (hsw < 1 || hsw > 256 || + hfp < 1 || hfp > 4096 || + hbp < 1 || hbp > 4096 || + vsw < 1 || vsw > 256 || + vfp < 0 || vfp > 4095 || + vbp < 0 || vbp > 4095) + return false; + return true; +} + +bool dispc6_mgr_timings_ok(struct dispc_device *dispc, + enum omap_channel channel, + const struct videomode *vm) +{ + if (vm->pixelclock < dispc->feat->min_pclk && vm->pixelclock != 9000000) + return false; + + if (vm->pixelclock > dispc->feat->max_pclk) + return false; + + if (vm->hactive > 4096) + return false; + + if (vm->vactive > 4096) + return false; + + /* TODO: add interlace support */ + if (vm->flags & DISPLAY_FLAGS_INTERLACED) + return false; + + if (!dispc6_lcd_timings_ok(vm->hsync_len, vm->hfront_porch, vm->hback_porch, + vm->vsync_len, vm->vfront_porch, vm->vback_porch)) + return false; + + return true; +} + +static void dispc6_mgr_set_timings(struct dispc_device *dispc, + enum omap_channel channel, + const struct videomode *vm) +{ + bool align, onoff, rf, ieo, ipc, ihs, ivs; + + dispc6_vp_write(dispc, channel, DISPC_VP_TIMING_H, + FLD_VAL(vm->hsync_len - 1, 7, 0) | + FLD_VAL(vm->hfront_porch - 1, 19, 8) | + FLD_VAL(vm->hback_porch - 1, 31, 20)); + + dispc6_vp_write(dispc, channel, DISPC_VP_TIMING_V, + FLD_VAL(vm->vsync_len - 1, 7, 0) | + FLD_VAL(vm->vfront_porch, 19, 8) | + FLD_VAL(vm->vback_porch, 31, 20)); + + if (vm->flags & DISPLAY_FLAGS_VSYNC_HIGH) + ivs = false; + else + ivs = true; + + if (vm->flags & DISPLAY_FLAGS_HSYNC_HIGH) + ihs = false; + else + ihs = true; + + if (vm->flags & DISPLAY_FLAGS_DE_HIGH) + ieo = false; + else + ieo = true; + + if (vm->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) + ipc = false; + else + ipc = true; + + /* always use the 'rf' setting */ + onoff = true; + + if (vm->flags & DISPLAY_FLAGS_SYNC_POSEDGE) + rf = true; + else + rf = false; + + /* always use aligned syncs */ + align = true; + + dispc6_vp_write(dispc, channel, DISPC_VP_POL_FREQ, + FLD_VAL(align, 18, 18) | + FLD_VAL(onoff, 17, 17) | + FLD_VAL(rf, 16, 16) | + FLD_VAL(ieo, 15, 15) | + FLD_VAL(ipc, 14, 14) | + FLD_VAL(ihs, 13, 13) | + FLD_VAL(ivs, 12, 12)); + + dispc6_vp_write(dispc, channel, DISPC_VP_SIZE_SCREEN, + FLD_VAL(vm->hactive - 1, 11, 0) | + FLD_VAL(vm->vactive - 1, 27, 16)); +} + +int dispc6_vp_enable_clk(struct dispc_device *dispc, enum omap_channel channel) +{ + return clk_prepare_enable(dispc->vp_clk); +} +void dispc6_vp_disable_clk(struct dispc_device *dispc, + enum omap_channel channel) +{ + clk_disable_unprepare(dispc->vp_clk); +} + +int dispc6_vp_set_clk_rate(struct dispc_device *dispc, + enum omap_channel channel, unsigned long rate) +{ + int r; + unsigned long new_rate; + + r = clk_set_rate(dispc->vp_clk, rate); + if (r) { + dev_err(&dispc->pdev->dev, "Failed to set vp clk rate to %lu\n", + rate); + return r; + } + + new_rate = clk_get_rate(dispc->vp_clk); + + if (rate != new_rate) + dev_warn(&dispc->pdev->dev, + "Failed to get exact pix clock %lu != %lu\n", + rate, new_rate); + + dev_dbg(&dispc->pdev->dev, "New VP rate %lu Hz (requested %lu Hz)\n", + clk_get_rate(dispc->vp_clk), rate); + + return 0; +} + +/* CSC */ + +struct color_conv_coef { + int ry, rcb, rcr; + int gy, gcb, gcr; + int by, bcb, bcr; + int roffset, goffset, boffset; + bool full_range; +}; + +static void dispc6_vid_write_color_conv_coefs(struct dispc_device *dispc, + enum omap_plane_id plane, + const struct color_conv_coef *ct) +{ +#define CVAL(x, y) (FLD_VAL(x, 26, 16) | FLD_VAL(y, 10, 0)) + + dispc6_vid_write(dispc, plane, DISPC_VID_CONV_COEF(0), CVAL(ct->rcr, ct->ry)); + dispc6_vid_write(dispc, plane, DISPC_VID_CONV_COEF(1), CVAL(ct->gy, ct->rcb)); + dispc6_vid_write(dispc, plane, DISPC_VID_CONV_COEF(2), CVAL(ct->gcb, ct->gcr)); + dispc6_vid_write(dispc, plane, DISPC_VID_CONV_COEF(3), CVAL(ct->bcr, ct->by)); + dispc6_vid_write(dispc, plane, DISPC_VID_CONV_COEF(4), CVAL(0, ct->bcb)); + + dispc6_vid_write(dispc, plane, DISPC_VID_CONV_COEF(5), + FLD_VAL(ct->roffset, 15, 3) | FLD_VAL(ct->goffset, 31, 19)); + dispc6_vid_write(dispc, plane, DISPC_VID_CONV_COEF(6), + FLD_VAL(ct->boffset, 15, 3)); + + VID_REG_FLD_MOD(dispc, plane, DISPC_VID_ATTRIBUTES, ct->full_range, 11, 11); + +#undef CVAL +} + +static void dispc6_vid_csc_setup(struct dispc_device *dispc) +{ + /* YUV -> RGB, ITU-R BT.601, full range */ + const struct color_conv_coef coefs_yuv2rgb_bt601_full = { + 256, 0, 358, + 256, -88, -182, + 256, 452, 0, + 0, -2048, -2048, + true, + }; + + dispc6_vid_write_color_conv_coefs(dispc, 0, &coefs_yuv2rgb_bt601_full); +} + +static void dispc6_vid_csc_enable(struct dispc_device *dispc, + enum omap_plane_id plane, bool enable) +{ + VID_REG_FLD_MOD(dispc, plane, DISPC_VID_ATTRIBUTES, !!enable, 9, 9); +} + +/* SCALER */ + +static u32 dispc6_calc_fir_inc(u32 in, u32 out) +{ + return (u32)div_u64(0x200000ull * in, out); +} + +struct dispc6_vid_fir_coefs { + s16 c2[16]; + s16 c1[16]; + u16 c0[9]; +}; + +static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_null = { + .c2 = { 0 }, + .c1 = { 0 }, + .c0 = { 512, 512, 512, 512, 512, 512, 512, 512, 256, }, +}; + +/* M=8, Upscale x >= 1 */ +static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m8 = { + .c2 = { 0, -4, -8, -16, -24, -32, -40, -48, 0, 2, 4, 6, 8, 6, 4, 2, }, + .c1 = { 0, 28, 56, 94, 132, 176, 220, 266, -56, -60, -64, -62, -60, -50, -40, -20, }, + .c0 = { 512, 506, 500, 478, 456, 424, 392, 352, 312, }, +}; + +/* 5-tap, M=22, Downscale Ratio 2.5 < x < 3 */ +static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m22_5tap = { + .c2 = { 16, 20, 24, 30, 36, 42, 48, 56, 0, 0, 0, 2, 4, 8, 12, 14, }, + .c1 = { 132, 140, 148, 156, 164, 172, 180, 186, 64, 72, 80, 88, 96, 104, 112, 122, }, + .c0 = { 216, 216, 216, 214, 212, 208, 204, 198, 192, }, +}; + +/* 3-tap, M=22, Downscale Ratio 2.5 < x < 3 */ +static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m22_3tap = { + .c1 = { 100, 118, 136, 156, 176, 196, 216, 236, 0, 10, 20, 30, 40, 54, 68, 84, }, + .c0 = { 312, 310, 308, 302, 296, 286, 276, 266, 256, }, +}; + +enum dispc6_vid_fir_coef_set { + DISPC6_VID_FIR_COEF_HORIZ, + DISPC6_VID_FIR_COEF_HORIZ_UV, + DISPC6_VID_FIR_COEF_VERT, + DISPC6_VID_FIR_COEF_VERT_UV, +}; + +static void dispc6_vid_write_fir_coefs(struct dispc_device *dispc, + enum omap_plane_id plane, + enum dispc6_vid_fir_coef_set coef_set, + const struct dispc6_vid_fir_coefs *coefs) +{ + static const u16 c0_regs[] = { + [DISPC6_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H0, + [DISPC6_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H0_C, + [DISPC6_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V0, + [DISPC6_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V0_C, + }; + + static const u16 c12_regs[] = { + [DISPC6_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H12, + [DISPC6_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H12_C, + [DISPC6_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V12, + [DISPC6_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V12_C, + }; + + const u16 c0_base = c0_regs[coef_set]; + const u16 c12_base = c12_regs[coef_set]; + int phase; + + for (phase = 0; phase <= 8; ++phase) { + u16 reg = c0_base + phase * 4; + u16 c0 = coefs->c0[phase]; + + dispc6_vid_write(dispc, plane, reg, c0); + } + + for (phase = 0; phase <= 15; ++phase) { + u16 reg = c12_base + phase * 4; + s16 c1, c2; + u32 c12; + + c1 = coefs->c1[phase]; + c2 = coefs->c2[phase]; + c12 = FLD_VAL(c1, 19, 10) | FLD_VAL(c2, 29, 20); + + dispc6_vid_write(dispc, plane, reg, c12); + } +} + +static void dispc6_vid_write_scale_coefs(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + dispc6_vid_write_fir_coefs(dispc, plane, DISPC6_VID_FIR_COEF_HORIZ, &dispc6_fir_coefs_null); + dispc6_vid_write_fir_coefs(dispc, plane, DISPC6_VID_FIR_COEF_HORIZ_UV, &dispc6_fir_coefs_null); + dispc6_vid_write_fir_coefs(dispc, plane, DISPC6_VID_FIR_COEF_VERT, &dispc6_fir_coefs_null); + dispc6_vid_write_fir_coefs(dispc, plane, DISPC6_VID_FIR_COEF_VERT_UV, &dispc6_fir_coefs_null); +} + +static void dispc6_vid_set_scaling(struct dispc_device *dispc, + enum omap_plane_id plane, + u32 orig_width, u32 orig_height, + u32 out_width, u32 out_height, + u32 fourcc) +{ + u32 in_w, in_h, in_w_uv, in_h_uv; + u32 fir_hinc, fir_vinc, fir_hinc_uv, fir_vinc_uv; + bool scale_x, scale_y; + bool five_taps = false; /* XXX always 3-tap for now */ + + in_w = in_w_uv = orig_width; + in_h = in_h_uv = orig_height; + + switch (fourcc) { + case DRM_FORMAT_NV12: + /* UV is subsampled by 2 horizontally and vertically */ + in_h_uv >>= 1; + in_w_uv >>= 1; + break; + + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + /* UV is subsampled by 2 horizontally */ + in_w_uv >>= 1; + break; + + default: + break; + } + + scale_x = in_w != out_width || in_w_uv != out_width; + scale_y = in_h != out_height || in_h_uv != out_height; + + /* HORIZONTAL RESIZE ENABLE */ + VID_REG_FLD_MOD(dispc, plane, DISPC_VID_ATTRIBUTES, scale_x, 7, 7); + + /* VERTICAL RESIZE ENABLE */ + VID_REG_FLD_MOD(dispc, plane, DISPC_VID_ATTRIBUTES, scale_y, 8, 8); + + /* Skip the rest if no scaling is used */ + if (!scale_x && !scale_y) + return; + + /* VERTICAL 5-TAPS */ + VID_REG_FLD_MOD(dispc, plane, DISPC_VID_ATTRIBUTES, five_taps, 21, 21); + + /* FIR INC */ + + fir_hinc = dispc6_calc_fir_inc(in_w, out_width); + fir_vinc = dispc6_calc_fir_inc(in_h, out_height); + fir_hinc_uv = dispc6_calc_fir_inc(in_w_uv, out_width); + fir_vinc_uv = dispc6_calc_fir_inc(in_h_uv, out_height); + + dispc6_vid_write(dispc, plane, DISPC_VID_FIRH, fir_hinc); + dispc6_vid_write(dispc, plane, DISPC_VID_FIRV, fir_vinc); + dispc6_vid_write(dispc, plane, DISPC_VID_FIRH2, fir_hinc_uv); + dispc6_vid_write(dispc, plane, DISPC_VID_FIRV2, fir_vinc_uv); + + dispc6_vid_write_scale_coefs(dispc, plane); +} + +/* OTHER */ + +static const enum omap_color_mode dispc6_supported_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_YUYV, + DRM_FORMAT_UYVY, + DRM_FORMAT_NV12, + 0 +}; + +static const struct { + u32 fourcc; + u8 dss_code; + u8 bytespp; +} dispc6_color_formats[] = { + { DRM_FORMAT_XRGB8888, 0x27, 4, }, + { DRM_FORMAT_ARGB8888, 0x7, 4, }, + + { DRM_FORMAT_RGBX8888, 0x29, 4, }, + { DRM_FORMAT_RGBA8888, 0x9, 4, }, + + { DRM_FORMAT_YUYV, 0x3e, 2, }, + { DRM_FORMAT_UYVY, 0x3f, 2, }, + + { DRM_FORMAT_NV12, 0x3d, 2, }, +}; + +static bool dispc6_fourcc_is_yuv(u32 fourcc) +{ + switch (fourcc) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_NV12: + return true; + default: + return false; + } +} + +static void dispc6_ovl_set_pixel_format(struct dispc_device *dispc, + enum omap_plane_id plane, u32 fourcc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dispc6_color_formats); ++i) { + if (dispc6_color_formats[i].fourcc == fourcc) { + VID_REG_FLD_MOD(dispc, plane, DISPC_VID_ATTRIBUTES, + dispc6_color_formats[i].dss_code, + 6, 1); + return; + } + } + + __WARN(); +} + +static int dispc6_fourcc_to_bytespp(u32 fourcc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dispc6_color_formats); ++i) { + if (dispc6_color_formats[i].fourcc == fourcc) + return dispc6_color_formats[i].bytespp; + } + + __WARN(); + return 4; +} + +static s32 pixinc(int pixels, u8 ps) +{ + if (pixels == 1) + return 1; + else if (pixels > 1) + return 1 + (pixels - 1) * ps; + else if (pixels < 0) + return 1 - (-pixels + 1) * ps; + + __WARN(); + return 0; +} + +static int dispc6_ovl_setup(struct dispc_device *dispc, + enum omap_plane_id plane, + const struct omap_overlay_info *oi, + const struct videomode *vm, bool mem_to_mem, + enum omap_channel channel) +{ + u32 fourcc = oi->fourcc; + int bytespp = dispc6_fourcc_to_bytespp(fourcc); + + dispc6_ovl_set_pixel_format(dispc, plane, fourcc); + + dispc6_vid_write(dispc, plane, DISPC_VID_BA_0, oi->paddr); + dispc6_vid_write(dispc, plane, DISPC_VID_BA_1, oi->paddr); + + dispc6_vid_write(dispc, plane, DISPC_VID_BA_UV_0, oi->p_uv_addr); + dispc6_vid_write(dispc, plane, DISPC_VID_BA_UV_1, oi->p_uv_addr); + + dispc6_vid_write(dispc, plane, DISPC_VID_PICTURE_SIZE, + (oi->width - 1) | ((oi->height - 1) << 16)); + + dispc6_vid_write(dispc, plane, DISPC_VID_PIXEL_INC, pixinc(1, bytespp)); + dispc6_vid_write(dispc, plane, DISPC_VID_ROW_INC, + pixinc(1 + oi->screen_width - oi->width, bytespp)); + + dispc6_vid_write(dispc, plane, DISPC_VID_POSITION, + oi->pos_x | (oi->pos_y << 16)); + + dispc6_vid_write(dispc, plane, DISPC_VID_SIZE, + (oi->out_width - 1) | ((oi->out_height - 1) << 16)); + + dispc6_vid_set_scaling(dispc, plane, + oi->width, oi->height, oi->out_width, oi->out_height, + fourcc); + + /* enable YUV->RGB color conversion */ + if (dispc6_fourcc_is_yuv(fourcc)) + dispc6_vid_csc_enable(dispc, plane, true); + else + dispc6_vid_csc_enable(dispc, plane, false); + + /* channel */ + VID_REG_FLD_MOD(dispc, plane, DISPC_VID_ATTRIBUTES, 0, 16, 14); + + return 0; +} + +static int dispc6_ovl_enable(struct dispc_device *dispc, + enum omap_plane_id plane, bool enable) +{ + VID_REG_FLD_MOD(dispc, plane, DISPC_VID_ATTRIBUTES, !!enable, 0, 0); + return 0; +} + +static u32 dispc6_vid_get_fifo_size(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + const u32 unit_size = 16; /* 128-bits */ + + return VID_REG_GET(dispc, plane, DISPC_VID_BUF_SIZE_STATUS, 15, 0) * unit_size; +} + +static void dispc6_vid_set_mflag_threshold(struct dispc_device *dispc, + enum omap_plane_id plane, + u32 low, u32 high) +{ + dispc6_vid_write(dispc, plane, DISPC_VID_MFLAG_THRESHOLD, + FLD_VAL(high, 31, 16) | FLD_VAL(low, 15, 0)); +} + +static void dispc6_mflag_setup(struct dispc_device *dispc) +{ + enum omap_plane_id plane = 0; + const u32 unit_size = 16; /* 128-bits */ + u32 size = dispc6_vid_get_fifo_size(dispc, plane); + u32 low, high; + + /* MFLAG_CTRL */ + REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 1, 1, 0); + /* MFLAG_START */ + REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 0, 2, 2); + + /* + * Simulation team suggests below thesholds: + * HT = fifosize * 5 / 8; + * LT = fifosize * 4 / 8; + */ + + low = size * 4 / 8 / unit_size; + high = size * 5 / 8 / unit_size; + + dispc6_vid_set_mflag_threshold(dispc, plane, low, high); +} + +static void dispc6_vp_setup(struct dispc_device *dispc) +{ + /* Enable the gamma Shadow bit-field */ + VP_REG_FLD_MOD(dispc, 0, DISPC_VP_CONFIG, 1, 2, 2); +} + +static void dispc6_initial_config(struct dispc_device *dispc) +{ + dispc6_vid_csc_setup(dispc); + dispc6_mflag_setup(dispc); + dispc6_vp_setup(dispc); +} + +static int dispc6_init_features(struct dispc_device *dispc) +{ + struct platform_device *pdev = dispc->pdev; + const struct of_device_id *match; + + match = of_match_node(dispc6_of_match, pdev->dev.of_node); + if (!match) { + dev_err(&pdev->dev, "Unsupported DISPC version\n"); + return -ENODEV; + } + + dispc->feat = match->data; + + return 0; +} + +static enum omap_dss_output_id +dispc6_mgr_get_supported_outputs(struct dispc_device *dispc, + enum omap_channel channel) +{ + return OMAP_DSS_OUTPUT_DPI; +} + +static const u32 *dispc6_ovl_get_color_modes(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + return dispc6_supported_formats; +} + +static int dispc6_get_num_ovls(struct dispc_device *dispc) +{ + return 1; +} + +static int dispc6_get_num_mgrs(struct dispc_device *dispc) +{ + return 1; +} + +static u32 dispc6_mgr_gamma_size(struct dispc_device *dispc, + enum omap_channel channel) +{ + return ARRAY_SIZE(dispc->gamma_table); +} + +static void dispc6_mgr_write_gamma_table(struct dispc_device *dispc, + enum omap_channel channel) +{ + u32 *table = dispc->gamma_table; + uint hwlen = ARRAY_SIZE(dispc->gamma_table); + unsigned int i; + + dev_dbg(&dispc->pdev->dev, "%s: channel %d\n", __func__, channel); + + for (i = 0; i < hwlen; ++i) { + u32 v = table[i]; + + v |= i << 24; + + dispc6_vp_write(dispc, channel, DISPC_VP_GAMMA_TABLE, v); + } +} + +static void dispc6_restore_gamma_tables(struct dispc_device *dispc) +{ + dev_dbg(&dispc->pdev->dev, "%s()\n", __func__); + + dispc6_mgr_write_gamma_table(dispc, 0); +} + +static const struct drm_color_lut dispc6_mgr_gamma_default_lut[] = { + { .red = 0, .green = 0, .blue = 0, }, + { .red = U16_MAX, .green = U16_MAX, .blue = U16_MAX, }, +}; + +static void dispc6_mgr_set_gamma(struct dispc_device *dispc, + enum omap_channel channel, + const struct drm_color_lut *lut, + unsigned int length) +{ + u32 *table = dispc->gamma_table; + uint hwlen = ARRAY_SIZE(dispc->gamma_table); + static const uint hwbits = 8; + uint i; + + dev_dbg(&dispc->pdev->dev, "%s: channel %d, lut len %u, hw len %u\n", + __func__, channel, length, hwlen); + + if (lut == NULL || length < 2) { + lut = dispc6_mgr_gamma_default_lut; + length = ARRAY_SIZE(dispc6_mgr_gamma_default_lut); + } + + for (i = 0; i < length - 1; ++i) { + uint first = i * (hwlen - 1) / (length - 1); + uint last = (i + 1) * (hwlen - 1) / (length - 1); + uint w = last - first; + u16 r, g, b; + uint j; + + if (w == 0) + continue; + + for (j = 0; j <= w; j++) { + r = (lut[i].red * (w - j) + lut[i+1].red * j) / w; + g = (lut[i].green * (w - j) + lut[i+1].green * j) / w; + b = (lut[i].blue * (w - j) + lut[i+1].blue * j) / w; + + r >>= 16 - hwbits; + g >>= 16 - hwbits; + b >>= 16 - hwbits; + + table[first + j] = (r << (hwbits * 2)) | + (g << hwbits) | b; + } + } + + if (dispc->is_enabled) + dispc6_mgr_write_gamma_table(dispc, channel); +} + +static int dispc6_init_gamma_tables(struct dispc_device *dispc) +{ + dispc6_mgr_set_gamma(dispc, 0, NULL, 0); + + return 0; +} + +static u32 dispc6_get_memory_bandwidth_limit(struct dispc_device *dispc) +{ + u32 limit = 0; + + /* Optional maximum memory bandwidth */ + of_property_read_u32(dispc->pdev->dev.of_node, "max-memory-bandwidth", + &limit); + + return limit; +} + +static const char *dispc6_ovl_name(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + return "vid1"; +} + +static const char *dispc6_mgr_name(struct dispc_device *dispc, + enum omap_channel channel) +{ + return "vp1"; +} + +static bool dispc6_mgr_has_framedone(struct dispc_device *dispc, + enum omap_channel channel) +{ + return true; +} + +static const struct dispc_ops dispc6_ops = { + .read_and_clear_irqstatus = dispc6_read_and_clear_irqstatus, + .write_irqenable = dispc6_write_irqenable, + + .request_irq = dispc6_request_irq, + .free_irq = dispc6_free_irq, + + .runtime_get = dispc6_runtime_get, + .runtime_put = dispc6_runtime_put, + + .get_num_ovls = dispc6_get_num_ovls, + .get_num_mgrs = dispc6_get_num_mgrs, + + .ovl_name = dispc6_ovl_name, + .mgr_name = dispc6_mgr_name, + + .mgr_has_framedone = dispc6_mgr_has_framedone, + + .get_memory_bandwidth_limit = dispc6_get_memory_bandwidth_limit, + + .mgr_enable = dispc6_mgr_enable, + .mgr_is_enabled = dispc6_mgr_is_enabled, + .mgr_go_busy = dispc6_mgr_go_busy, + .mgr_go = dispc6_mgr_go, + .mgr_set_lcd_config = dispc6_mgr_set_lcd_config, + .mgr_set_timings = dispc6_mgr_set_timings, + .mgr_setup = dispc6_mgr_setup, + .mgr_get_supported_outputs = dispc6_mgr_get_supported_outputs, + .mgr_gamma_size = dispc6_mgr_gamma_size, + .mgr_set_gamma = dispc6_mgr_set_gamma, + + .ovl_enable = dispc6_ovl_enable, + .ovl_setup = dispc6_ovl_setup, + .ovl_get_color_modes = dispc6_ovl_get_color_modes, +}; + +static int dispc6_iomap_resource(struct platform_device *pdev, const char *name, + void __iomem **base) +{ + struct resource *res; + void __iomem *b; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (!res) { + dev_err(&pdev->dev, "cannot get mem resource '%s'\n", name); + return -EINVAL; + } + + b = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(b)) { + dev_err(&pdev->dev, "cannot ioremap resource '%s'\n", name); + return PTR_ERR(b); + } + + *base = b; + + return 0; +} + +static int dispc6_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dss6_device *dss6 = dss6_get_device(master); + struct dispc_device *dispc; + int r = 0; + + dispc = kzalloc(sizeof(*dispc), GFP_KERNEL); + if (!dispc) + return -ENOMEM; + + dispc->pdev = pdev; + platform_set_drvdata(pdev, dispc); + dispc->dss6 = dss6; + + r = dispc6_init_features(dispc); + if (r) + goto err_free; + + r = dispc6_iomap_resource(pdev, "common", &dispc->base_common); + if (r) + goto err_free; + + r = dispc6_iomap_resource(pdev, "vid1", &dispc->base_vid1); + if (r) + goto err_free; + + r = dispc6_iomap_resource(pdev, "ovr1", &dispc->base_ovr1); + if (r) + goto err_free; + + r = dispc6_iomap_resource(pdev, "vp1", &dispc->base_vp1); + if (r) + goto err_free; + + dispc->irq = platform_get_irq(dispc->pdev, 0); + if (dispc->irq < 0) { + dev_err(dev, "platform_get_irq failed\n"); + r = -ENODEV; + goto err_free; + } + + dispc->fclk = devm_clk_get(dev, "fck"); + if (IS_ERR(dispc->fclk)) { + dev_err(dev, "Failed to get fclk\n"); + r = PTR_ERR(dispc->fclk); + goto err_free; + } + + dispc->vp_clk = devm_clk_get(dev, "vp"); + if (IS_ERR(dispc->vp_clk)) { + dev_err(dev, "Failed to get vp clk\n"); + r = PTR_ERR(dispc->vp_clk); + goto err_free; + } + + r = dispc6_init_gamma_tables(dispc); + if (r) + goto err_free; + + pm_runtime_enable(&pdev->dev); + + pm_runtime_set_autosuspend_delay(&pdev->dev, 200); + pm_runtime_use_autosuspend(&pdev->dev); + + dss6->data.dispc_ops = &dispc6_ops; + dss6->data.dispc = dispc; + + return 0; +err_free: + kfree(dispc); + return r; +} + +static void dispc6_unbind(struct device *dev, struct device *master, + void *data) +{ + struct dispc_device *dispc = dev_get_drvdata(dev); + struct dss6_device *dss6 = dispc->dss6; + + dss6->data.dispc_ops = NULL; + dss6->data.dispc = NULL; + + pm_runtime_disable(dev); + + kfree(dispc); +} + +static const struct component_ops dispc6_component_ops = { + .bind = dispc6_bind, + .unbind = dispc6_unbind, +}; + +static int dispc6_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &dispc6_component_ops); +} + +static int dispc6_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dispc6_component_ops); + return 0; +} + +static int dispc6_runtime_suspend(struct device *dev) +{ + struct dispc_device *dispc = dev_get_drvdata(dev); + + dev_dbg(dev, "suspend\n"); + + dispc->is_enabled = false; + /* ensure the dispc6_irq_handler sees the is_enabled value */ + smp_wmb(); + /* wait for current handler to finish before turning the DISPC off */ + synchronize_irq(dispc->irq); + + dispc6_save_context(dispc); + + clk_disable_unprepare(dispc->fclk); + + return 0; +} + +static int dispc6_runtime_resume(struct device *dev) +{ + struct dispc_device *dispc = dev_get_drvdata(dev); + + dev_dbg(dev, "resume\n"); + + clk_prepare_enable(dispc->fclk); + + if (REG_GET(dispc, DISPC_SYSSTATUS, 0, 0) == 0) + dev_warn(dev, "DISPC FUNC RESET not done!\n"); + if (REG_GET(dispc, DISPC_SYSSTATUS, 1, 1) == 0) + dev_warn(dev, "DISPC VP RESET not done!\n"); + + dispc6_initial_config(dispc); + + dispc6_restore_context(dispc); + + dispc6_restore_gamma_tables(dispc); + + dispc->is_enabled = true; + /* ensure the dispc6_irq_handler sees the is_enabled value */ + smp_wmb(); + + return 0; +} + +static const struct dev_pm_ops dispc_pm_ops = { + .runtime_suspend = dispc6_runtime_suspend, + .runtime_resume = dispc6_runtime_resume, +}; + +static const struct of_device_id dispc6_of_match[] = { + { .compatible = "ti,k2g-dispc", .data = &k2g_dispc_feats, }, + {}, +}; + +static struct platform_driver dispc6_driver = { + .probe = dispc6_probe, + .remove = dispc6_remove, + .driver = { + .name = "omap_dispc6", + .pm = &dispc_pm_ops, + .of_match_table = dispc6_of_match, + .suppress_bind_attrs = true, + }, +}; + +int __init dispc6_init_platform_driver(void) +{ + return platform_driver_register(&dispc6_driver); +} + +void dispc6_uninit_platform_driver(void) +{ + platform_driver_unregister(&dispc6_driver); +} diff --git a/drivers/gpu/drm/omapdrm/dss/dispc6.h b/drivers/gpu/drm/omapdrm/dss/dispc6.h new file mode 100644 index 0000000..824fda4 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/dispc6.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Tomi Valkeinen + */ + +#ifndef __OMAP2_DISPC6_REG_H +#define __OMAP2_DISPC6_REG_H + +/* COMMON */ + +#define DISPC_REVISION 0x000 +#define DISPC_SYSCONFIG 0x004 +#define DISPC_SYSSTATUS 0x008 + +#define DISPC_IRQ_EOI 0x020 +#define DISPC_IRQSTATUS_RAW 0x024 +#define DISPC_IRQSTATUS 0x028 +#define DISPC_IRQENABLE_SET 0x02c +#define DISPC_IRQENABLE_CLR 0x030 +#define DISPC_IRQWAKEEN 0x034 + +#define DISPC_GLOBAL_MFLAG_ATTRIBUTE 0x040 +#define DISPC_GLOBAL_BUFFER 0x044 +#define DISPC_BA0_FLIPIMMEDIATE_EN 0x048 + +#define DISPC_DBG_CONTROL 0x04c +#define DISPC_DBG_STATUS 0x050 + +#define DISPC_CLKGATING_DISABLE 0x054 + +/* VID */ + +#define DISPC_VID_ACCUH_0 0x0 +#define DISPC_VID_ACCUH_1 0x4 +#define DISPC_VID_ACCUH2_0 0x8 +#define DISPC_VID_ACCUH2_1 0xc + +#define DISPC_VID_ACCUV_0 0x10 +#define DISPC_VID_ACCUV_1 0x14 +#define DISPC_VID_ACCUV2_0 0x18 +#define DISPC_VID_ACCUV2_1 0x1c + +#define DISPC_VID_ATTRIBUTES 0x20 +#define DISPC_VID_ATTRIBUTES2 0x24 + +#define DISPC_VID_BA_0 0x28 +#define DISPC_VID_BA_1 0x2c +#define DISPC_VID_BA_UV_0 0x30 +#define DISPC_VID_BA_UV_1 0x34 +#define DISPC_VID_BUF_SIZE_STATUS 0x38 +#define DISPC_VID_BUF_THRESHOLD 0x3c + +#define DISPC_VID_CONV_COEF(n) (0x40 + (n) * 4) + +#define DISPC_VID_FIRH 0x5c +#define DISPC_VID_FIRH2 0x60 +#define DISPC_VID_FIRV 0x64 +#define DISPC_VID_FIRV2 0x68 + +#define DISPC_VID_FIR_COEFS_H0 0x6c +#define DISPC_VID_FIR_COEF_H0(phase) (0x6c + (phase) * 4) +#define DISPC_VID_FIR_COEFS_H0_C 0x90 +#define DISPC_VID_FIR_COEF_H0_C(phase) (0x90 + (phase) * 4) + +#define DISPC_VID_FIR_COEFS_H12 0xb4 +#define DISPC_VID_FIR_COEF_H12(phase) (0xb4 + (phase) * 4) +#define DISPC_VID_FIR_COEFS_H12_C 0xf4 +#define DISPC_VID_FIR_COEF_H12_C(phase) (0xf4 + (phase) * 4) + +#define DISPC_VID_FIR_COEFS_V0 0x134 +#define DISPC_VID_FIR_COEF_V0(phase) (0x134 + (phase) * 4) +#define DISPC_VID_FIR_COEFS_V0_C 0x158 +#define DISPC_VID_FIR_COEF_V0_C(phase) (0x158 + (phase) * 4) + +#define DISPC_VID_FIR_COEFS_V12 0x17c +#define DISPC_VID_FIR_COEF_V12(phase) (0x17c + (phase) * 4) +#define DISPC_VID_FIR_COEFS_V12_C 0x1bc +#define DISPC_VID_FIR_COEF_V12_C(phase) (0x1bc + (phase) * 4) + +#define DISPC_VID_IRQENABLE 0x200 +#define DISPC_VID_IRQSTATUS 0x204 + +#define DISPC_VID_MFLAG_THRESHOLD 0x208 +#define DISPC_VID_PICTURE_SIZE 0x20c +#define DISPC_VID_PIXEL_INC 0x210 +#define DISPC_VID_POSITION 0x214 +#define DISPC_VID_PRELOAD 0x218 +#define DISPC_VID_ROW_INC 0x21c +#define DISPC_VID_SIZE 0x220 + +/* OVR */ + +#define DISPC_OVR_DEFAULT_COLOR 0x08 +#define DISPC_OVR_DEFAULT_COLOR2 0x0c + +/* VP */ + +#define DISPC_VP_CONFIG 0x00 +#define DISPC_VP_CONTROL 0x04 +#define DISPC_VP_GAMMA_TABLE 0x20 +#define DISPC_VP_IRQENABLE 0x3c +#define DISPC_VP_IRQSTATUS 0x40 +#define DISPC_VP_POL_FREQ 0x4c +#define DISPC_VP_SIZE_SCREEN 0x50 +#define DISPC_VP_TIMING_H 0x54 +#define DISPC_VP_TIMING_V 0x58 + +#endif diff --git a/drivers/gpu/drm/omapdrm/dss/dpi6.c b/drivers/gpu/drm/omapdrm/dss/dpi6.c new file mode 100644 index 0000000..458c8f3 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/dpi6.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Tomi Valkeinen + */ + +#include +#include +#include + +#include "omapdss.h" +#include "dss6.h" + +struct dpi_data { + struct platform_device *pdev; + + struct mutex lock; + + struct videomode vm; + int data_lines; + + struct omap_dss_device output; + + bool port_initialized; + + struct dss6_device *dss6; +}; + +static struct dpi_data *dpi6_get_data_from_dssdev(struct omap_dss_device *out) +{ + return container_of(out, struct dpi_data, output); +} + +static void dpi6_config_lcd_manager(struct dpi_data *dpi) +{ + struct dss_lcd_mgr_config mgr_config = { + .io_pad_mode = DSS_IO_PAD_MODE_BYPASS, + .stallmode = false, + .fifohandcheck = false, + .video_port_width = dpi->data_lines, + .lcden_sig_polarity = 0, + }; + + dss_mgr_set_lcd_config(&dpi->output, &mgr_config); +} + +static int dpi6_connect(struct omap_dss_device *out, + struct omap_dss_device *dst) +{ + struct dpi_data *dpi = dpi6_get_data_from_dssdev(out); + int r; + + r = dss_mgr_connect(&dpi->output, out); + if (r) + return r; + + r = omapdss_output_set_device(out, dst); + if (r) { + dev_err(&dpi->pdev->dev, + "failed to connect output to new device: %s\n", + dst->name); + dss_mgr_disconnect(&dpi->output, out); + return r; + } + + return 0; +} + +static void dpi6_disconnect(struct omap_dss_device *out, + struct omap_dss_device *dst) +{ + struct dpi_data *dpi = dpi6_get_data_from_dssdev(out); + + WARN_ON(dst != out->dst); + + if (dst != out->dst) + return; + + omapdss_output_unset_device(out); + + dss_mgr_disconnect(&dpi->output, out); +} + +static int dpi6_display_enable(struct omap_dss_device *out) +{ + struct dpi_data *dpi = dpi6_get_data_from_dssdev(out); + enum omap_channel channel = out->dispc_channel; + int r; + + mutex_lock(&dpi->lock); + + if (!out->dispc_channel_connected) { + dev_err(&dpi->pdev->dev, "failed to enable display: no output channel set\n"); + r = -ENODEV; + goto err_no_out_mgr; + } + + r = dispc6_runtime_get(dpi->dss6->data.dispc); + if (r) + goto err_get_dispc; + + r = dispc6_vp_set_clk_rate(dpi->dss6->data.dispc, channel, + dpi->vm.pixelclock); + if (r) + goto err_set_clk; + + r = dispc6_vp_enable_clk(dpi->dss6->data.dispc, channel); + if (r) + goto err_enable_clk; + + dpi6_config_lcd_manager(dpi); + + r = dss_mgr_enable(&dpi->output); + if (r) + goto err_mgr_enable; + + dss6_ungate_dpi_clk(&dpi->pdev->dev, out->port_num); + + mutex_unlock(&dpi->lock); + + return 0; + +err_mgr_enable: + dispc6_vp_disable_clk(dpi->dss6->data.dispc, channel); +err_enable_clk: +err_set_clk: + dispc6_runtime_put(dpi->dss6->data.dispc); +err_get_dispc: +err_no_out_mgr: + mutex_unlock(&dpi->lock); + return r; +} + +static void dpi6_display_disable(struct omap_dss_device *out) +{ + struct dpi_data *dpi = dpi6_get_data_from_dssdev(out); + enum omap_channel channel = out->dispc_channel; + + mutex_lock(&dpi->lock); + + dss6_gate_dpi_clk(&dpi->pdev->dev, out->port_num); + + dss_mgr_disable(&dpi->output); + + dispc6_vp_disable_clk(dpi->dss6->data.dispc, channel); + + dispc6_runtime_put(dpi->dss6->data.dispc); + + mutex_unlock(&dpi->lock); +} + +static int dpi6_check_timings(struct omap_dss_device *out, + struct videomode *vm) +{ + struct dpi_data *dpi = dpi6_get_data_from_dssdev(out); + enum omap_channel channel = out->dispc_channel; + + if (!dispc6_mgr_timings_ok(dpi->dss6->data.dispc, channel, vm)) + return -EINVAL; + + return 0; +} + +static void dpi6_set_timings(struct omap_dss_device *out, + struct videomode *vm) +{ + struct dpi_data *dpi = dpi6_get_data_from_dssdev(out); + + mutex_lock(&dpi->lock); + + dpi->vm = *vm; + + mutex_unlock(&dpi->lock); +} + +static void dpi6_get_timings(struct omap_dss_device *out, + struct videomode *vm) +{ + struct dpi_data *dpi = dpi6_get_data_from_dssdev(out); + + mutex_lock(&dpi->lock); + + *vm = dpi->vm; + + mutex_unlock(&dpi->lock); +} + + +static const struct omapdss_dpi_ops dpi6_ops = { + .connect = dpi6_connect, + .disconnect = dpi6_disconnect, + + .enable = dpi6_display_enable, + .disable = dpi6_display_disable, + + .check_timings = dpi6_check_timings, + .set_timings = dpi6_set_timings, + .get_timings = dpi6_get_timings, +}; + +static void dpi6_setup_output_port(struct platform_device *pdev, + struct device_node *port) +{ + struct dpi_data *dpi = port->data; + struct omap_dss_device *out = &dpi->output; + int r; + u32 port_num; + + r = of_property_read_u32(port, "reg", &port_num); + if (r) + port_num = 0; + + switch (port_num) { + case 0: + default: + out->name = "dpi.0"; + break; + } + + out->dev = &pdev->dev; + out->id = OMAP_DSS_OUTPUT_DPI; + out->output_type = OMAP_DISPLAY_TYPE_DPI; + out->dispc_channel = OMAP_DSS_CHANNEL_LCD; + out->port_num = port_num; + out->ops.dpi = &dpi6_ops; + out->owner = THIS_MODULE; + + omapdss_register_output(out); +} + +int dpi6_init_port(struct platform_device *pdev, struct device_node *port) +{ + struct dss6_device *dss6 = dss6_get_device(&pdev->dev); + struct dpi_data *dpi; + struct device_node *ep; + u32 datalines; + int r; + + dpi = devm_kzalloc(&pdev->dev, sizeof(*dpi), GFP_KERNEL); + if (!dpi) + return -ENOMEM; + + ep = of_get_next_child(port, NULL); + if (!ep) + return 0; + + r = of_property_read_u32(ep, "data-lines", &datalines); + if (r) { + dev_err(&dpi->pdev->dev, "failed to parse datalines\n"); + goto err_datalines; + } + + dpi->data_lines = datalines; + + of_node_put(ep); + + dpi->pdev = pdev; + port->data = dpi; + + mutex_init(&dpi->lock); + + dpi6_setup_output_port(pdev, port); + + dpi->dss6 = dss6; + dpi->port_initialized = true; + + return 0; + +err_datalines: + of_node_put(ep); + + return r; +} + +void dpi6_uninit_port(struct device_node *port) +{ + struct dpi_data *dpi = port->data; + + if (!dpi->port_initialized) + return; + + omapdss_unregister_output(&dpi->output); +} diff --git a/drivers/gpu/drm/omapdrm/dss/dss6.c b/drivers/gpu/drm/omapdrm/dss/dss6.c new file mode 100644 index 0000000..180fa69 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/dss6.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "omapdss.h" +#include "dss6.h" + +#define DSS_REVISION 0x00 +#define DSS_SYSCONFIG 0x10 +#define DSS_SYSSTATUS 0x14 +#define DSS_RFBI_CTRL 0x18 +#define DSS_DPI_CTRL 0x1c +#define DSS_DEBUG_CFG 0x40 + +#define REG_GET(data, idx, start, end) \ + FLD_GET(dss6_read(data, idx), start, end) + +#define REG_FLD_MOD(data, idx, val, start, end) \ + dss6_write(data, idx, FLD_MOD(dss6_read(data, idx), val, start, end)) + +struct dss_features { + int num_ports; +}; + +static const struct dss_features k2g_dss_feats = { + .num_ports = 1, +}; + +static const struct of_device_id dss6_of_match[]; + +/* omapdrm device */ + +static struct platform_device *omap_drm_device; + +static int initialize_omapdrm_device(void) +{ + omap_drm_device = platform_device_register_simple("omapdrm", 0, NULL, 0); + if (IS_ERR(omap_drm_device)) + return PTR_ERR(omap_drm_device); + + return 0; +} + +/* omapdrm device end */ + +static inline void dss6_write(struct dss6_device *dss6, u16 reg, u32 val) +{ + iowrite32(val, dss6->base + reg); +} + +static inline u32 dss6_read(struct dss6_device *dss6, u16 reg) +{ + return ioread32(dss6->base + reg); +} + +void dss6_ungate_dpi_clk(struct device *dev, int port) +{ + struct dss6_device *dss6 = dss6_get_device(dev); + + REG_FLD_MOD(dss6, DSS_DPI_CTRL, 1, 1, 0); +} + +void dss6_gate_dpi_clk(struct device *dev, int port) +{ + struct dss6_device *dss6 = dss6_get_device(dev); + + REG_FLD_MOD(dss6, DSS_DPI_CTRL, 0, 1, 0); +} + +static int dss6_init_features(struct platform_device *pdev) +{ + struct dss6_device *dss6 = dss6_get_device(&pdev->dev); + const struct of_device_id *match; + + match = of_match_node(dss6_of_match, pdev->dev.of_node); + if (!match) { + dev_err(&pdev->dev, "Unsupported DSS version\n"); + return -ENODEV; + } + + dss6->feat = match->data; + + return 0; +} + +static int dss6_init_ports(struct platform_device *pdev) +{ + struct dss6_device *dss6 = dss6_get_device(&pdev->dev); + struct device_node *parent = pdev->dev.of_node; + struct device_node *port; + int i, ret; + + for (i = 0; i < dss6->feat->num_ports; i++) { + port = of_graph_get_port_by_id(parent, i); + if (!port) + continue; + + ret = dpi6_init_port(pdev, port); + of_node_put(port); + if (ret) + return ret; + } + return 0; +} + +static void dss6_uninit_ports(struct platform_device *pdev) +{ + struct dss6_device *dss6 = dss6_get_device(&pdev->dev); + struct device_node *parent = pdev->dev.of_node; + struct device_node *port; + int i; + + for (i = 0; i < dss6->feat->num_ports; i++) { + port = of_graph_get_port_by_id(parent, i); + if (!port) + continue; + + dpi6_uninit_port(port); + of_node_put(port); + } +} + +static int dss6_bind(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dss6_device *dss6; + struct resource *dss_mem; + int r; + + dss6 = devm_kzalloc(dev, sizeof(*dss6), GFP_KERNEL); + if (!dss6) + return -ENOMEM; + + dev_set_drvdata(dev, dss6); + dss6->pdev = pdev; + + r = dss6_init_features(dss6->pdev); + if (r) + goto err_features; + + dss_mem = platform_get_resource(dss6->pdev, IORESOURCE_MEM, 0); + if (!dss_mem) { + dev_err(dev, "Failed to get IORESOURCE_MEM\n"); + r = -EINVAL; + goto err_resource; + } + + dss6->base = devm_ioremap(&pdev->dev, dss_mem->start, + resource_size(dss_mem)); + if (!dss6->base) { + dev_err(dev, "Failed to ioremap\n"); + r = -ENOMEM; + goto err_ioremap; + } + + r = dss6_init_ports(pdev); + if (r) + goto err_init_ports; + + dss6->fclk = devm_clk_get(dev, "fck"); + if (IS_ERR(dss6->fclk)) { + dev_err(dev, "Failed to get fclk\n"); + r = PTR_ERR(dss6->fclk); + goto err_clk_get; + } + + dev_dbg(&pdev->dev, "DSS fclk %lu Hz\n", clk_get_rate(dss6->fclk)); + + pm_runtime_enable(&pdev->dev); + + r = component_bind_all(&pdev->dev, NULL); + if (r) + goto err_component; + + r = initialize_omapdrm_device(); + if (r) + goto err_omapdrm_device; + + omapdss_gather_components(dev); + omapdss_set_dss(&dss6->data); + + return 0; + +err_omapdrm_device: + component_unbind_all(&pdev->dev, NULL); +err_component: + pm_runtime_disable(&pdev->dev); +err_clk_get: +err_init_ports: +err_ioremap: +err_resource: +err_features: + kfree(dss6); + return r; +} + +static void dss6_unbind(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + + omapdss_set_dss(NULL); + + platform_device_unregister(omap_drm_device); + + component_unbind_all(&pdev->dev, NULL); + + dss6_uninit_ports(pdev); + + pm_runtime_disable(&pdev->dev); +} + +static const struct component_master_ops dss6_component_ops = { + .bind = dss6_bind, + .unbind = dss6_unbind, +}; + +static int dss6_component_compare(struct device *dev, void *data) +{ + struct device *child = data; + return dev == child; +} + +static int dss6_add_child_component(struct device *dev, void *data) +{ + struct component_match **match = data; + + component_match_add(dev->parent, match, dss6_component_compare, dev); + + return 0; +} + +static int dss6_probe(struct platform_device *pdev) +{ + struct component_match *match = NULL; + int r; + + /* add all the child devices as components */ + device_for_each_child(&pdev->dev, &match, dss6_add_child_component); + + if (!match) { + dev_err(&pdev->dev, "No submodules found\n"); + return -EINVAL; + } + + r = component_master_add_with_match(&pdev->dev, &dss6_component_ops, match); + if (r) + return r; + + return 0; +} + +static int dss6_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &dss6_component_ops); + return 0; +} + +static int dss6_runtime_suspend(struct device *dev) +{ + struct dss6_device *dss6 = dss6_get_device(dev); + + clk_disable_unprepare(dss6->fclk); + + return 0; +} + +static int dss6_runtime_resume(struct device *dev) +{ + struct dss6_device *dss6 = dss6_get_device(dev); + + + clk_prepare_enable(dss6->fclk); + + if (REG_GET(dss6, DSS_SYSSTATUS, 0, 0) == 0) + dev_warn(dev, "DSS FUNC RESET not done!\n"); + + return 0; +} + +static const struct dev_pm_ops dss6_pm_ops = { + .runtime_suspend = dss6_runtime_suspend, + .runtime_resume = dss6_runtime_resume, +}; + +static const struct of_device_id dss6_of_match[] = { + { .compatible = "ti,k2g-dss", .data = &k2g_dss_feats, }, + {}, +}; + +MODULE_DEVICE_TABLE(of, dss6_of_match); + +static struct platform_driver dss6_driver = { + .probe = dss6_probe, + .remove = dss6_remove, + .driver = { + .name = "omap_dss6", + .pm = &dss6_pm_ops, + .of_match_table = dss6_of_match, + .suppress_bind_attrs = true, + }, +}; + +static int __init omap_dss_init(void) +{ + int r; + + r = platform_driver_register(&dss6_driver); + if (r) + return r; + + r = dispc6_init_platform_driver(); + if (r) { + platform_driver_unregister(&dss6_driver); + return r; + } + + return 0; +} + +static void __exit omap_dss_exit(void) +{ + dispc6_uninit_platform_driver(); + platform_driver_unregister(&dss6_driver); +} + +module_init(omap_dss_init); +module_exit(omap_dss_exit); + +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_DESCRIPTION("OMAP6 Display Subsystem"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/omapdrm/dss/dss6.h b/drivers/gpu/drm/omapdrm/dss/dss6.h new file mode 100644 index 0000000..b581d71 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/dss6.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Tomi Valkeinen + */ + +#ifndef __TI_DSS6_H +#define __TI_DSS6_H + +#include "omapdss.h" + +struct dss6_device { + struct platform_device *pdev; + void __iomem *base; + + struct clk *fclk; + + const struct dss_features *feat; + + struct dss_data data; +}; + +static inline struct dss6_device *dss6_get_device(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +/* DSS6 */ +void dss6_ungate_dpi_clk(struct device *dev, int port); +void dss6_gate_dpi_clk(struct device *dev, int port); + +/* DISPC6 */ +int __init dispc6_init_platform_driver(void); +void dispc6_uninit_platform_driver(void); + +int dispc6_runtime_get(struct dispc_device *dispc); +void dispc6_runtime_put(struct dispc_device *dispc); + +bool dispc6_mgr_timings_ok(struct dispc_device *dispc, + enum omap_channel channel, + const struct videomode *vm); + +int dispc6_vp_set_clk_rate(struct dispc_device *dispc, + enum omap_channel channel, unsigned long rate); +int dispc6_vp_enable_clk(struct dispc_device *dispc, + enum omap_channel channel); +void dispc6_vp_disable_clk(struct dispc_device *dispc, + enum omap_channel channel); + +/* DPI6 */ +int dpi6_init_port(struct platform_device *pdev, struct device_node *port); +void dpi6_uninit_port(struct device_node *port); + +#endif /* __TI_DSS6_H */