diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/gpu/drm/arm | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/arm')
-rw-r--r-- | drivers/gpu/drm/arm/Kconfig | 42 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/Makefile | 5 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_crtc.c | 335 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_drv.c | 439 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_drv.h | 38 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_regs.h | 87 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/malidp_crtc.c | 560 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/malidp_drv.c | 943 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/malidp_drv.h | 94 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/malidp_hw.c | 1151 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/malidp_hw.h | 383 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/malidp_mw.c | 267 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/malidp_mw.h | 14 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/malidp_planes.c | 533 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/malidp_regs.h | 264 |
15 files changed, 5155 insertions, 0 deletions
diff --git a/drivers/gpu/drm/arm/Kconfig b/drivers/gpu/drm/arm/Kconfig new file mode 100644 index 000000000..9a18e1bd5 --- /dev/null +++ b/drivers/gpu/drm/arm/Kconfig @@ -0,0 +1,42 @@ +config DRM_ARM + bool + help + Choose this option to select drivers for ARM's devices + +config DRM_HDLCD + tristate "ARM HDLCD" + depends on DRM && OF && (ARM || ARM64) + depends on COMMON_CLK + select DRM_ARM + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + help + Choose this option if you have an ARM High Definition Colour LCD + controller. + + If M is selected the module will be called hdlcd. + +config DRM_HDLCD_SHOW_UNDERRUN + bool "Show underrun conditions" + depends on DRM_HDLCD + default n + help + Enable this option to show in red colour the pixels that the + HDLCD device did not fetch from framebuffer due to underrun + conditions. + +config DRM_MALI_DISPLAY + tristate "ARM Mali Display Processor" + depends on DRM && OF && (ARM || ARM64) + depends on COMMON_CLK + select DRM_ARM + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_GEM_CMA_HELPER + select VIDEOMODE_HELPERS + help + Choose this option if you want to compile the ARM Mali Display + Processor driver. It supports the DP500, DP550 and DP650 variants + of the hardware. + + If compiled as a module it will be called mali-dp. diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile new file mode 100644 index 000000000..3bf31d1a4 --- /dev/null +++ b/drivers/gpu/drm/arm/Makefile @@ -0,0 +1,5 @@ +hdlcd-y := hdlcd_drv.o hdlcd_crtc.o +obj-$(CONFIG_DRM_HDLCD) += hdlcd.o +mali-dp-y := malidp_drv.o malidp_hw.o malidp_planes.o malidp_crtc.o +mali-dp-y += malidp_mw.o +obj-$(CONFIG_DRM_MALI_DISPLAY) += mali-dp.o diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c new file mode 100644 index 000000000..e69d996ea --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2013-2015 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Implementation of a CRTC class for the HDLCD driver. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_plane_helper.h> +#include <linux/clk.h> +#include <linux/of_graph.h> +#include <linux/platform_data/simplefb.h> +#include <video/videomode.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +/* + * The HDLCD controller is a dumb RGB streamer that gets connected to + * a single HDMI transmitter or in the case of the ARM Models it gets + * emulated by the software that does the actual rendering. + * + */ + +static void hdlcd_crtc_cleanup(struct drm_crtc *crtc) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + /* stop the controller on cleanup */ + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0); + drm_crtc_cleanup(crtc); +} + +static int hdlcd_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + unsigned int mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, mask | HDLCD_INTERRUPT_VSYNC); + + return 0; +} + +static void hdlcd_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + unsigned int mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, mask & ~HDLCD_INTERRUPT_VSYNC); +} + +static const struct drm_crtc_funcs hdlcd_crtc_funcs = { + .destroy = hdlcd_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, + .enable_vblank = hdlcd_crtc_enable_vblank, + .disable_vblank = hdlcd_crtc_disable_vblank, +}; + +static struct simplefb_format supported_formats[] = SIMPLEFB_FORMATS; + +/* + * Setup the HDLCD registers for decoding the pixels out of the framebuffer + */ +static int hdlcd_set_pxl_fmt(struct drm_crtc *crtc) +{ + unsigned int btpp; + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + const struct drm_framebuffer *fb = crtc->primary->state->fb; + uint32_t pixel_format; + struct simplefb_format *format = NULL; + int i; + + pixel_format = fb->format->format; + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) { + if (supported_formats[i].fourcc == pixel_format) + format = &supported_formats[i]; + } + + if (WARN_ON(!format)) + return 0; + + /* HDLCD uses 'bytes per pixel', zero means 1 byte */ + btpp = (format->bits_per_pixel + 7) / 8; + hdlcd_write(hdlcd, HDLCD_REG_PIXEL_FORMAT, (btpp - 1) << 3); + + /* + * The format of the HDLCD_REG_<color>_SELECT register is: + * - bits[23:16] - default value for that color component + * - bits[11:8] - number of bits to extract for each color component + * - bits[4:0] - index of the lowest bit to extract + * + * The default color value is used when bits[11:8] are zero, when the + * pixel is outside the visible frame area or when there is a + * buffer underrun. + */ + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->red.offset | +#ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN + 0x00ff0000 | /* show underruns in red */ +#endif + ((format->red.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset | + ((format->green.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->blue.offset | + ((format->blue.length & 0xf) << 8)); + + return 0; +} + +static void hdlcd_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + struct drm_display_mode *m = &crtc->state->adjusted_mode; + struct videomode vm; + unsigned int polarities, err; + + vm.vfront_porch = m->crtc_vsync_start - m->crtc_vdisplay; + vm.vback_porch = m->crtc_vtotal - m->crtc_vsync_end; + vm.vsync_len = m->crtc_vsync_end - m->crtc_vsync_start; + vm.hfront_porch = m->crtc_hsync_start - m->crtc_hdisplay; + vm.hback_porch = m->crtc_htotal - m->crtc_hsync_end; + vm.hsync_len = m->crtc_hsync_end - m->crtc_hsync_start; + + polarities = HDLCD_POLARITY_DATAEN | HDLCD_POLARITY_DATA; + + if (m->flags & DRM_MODE_FLAG_PHSYNC) + polarities |= HDLCD_POLARITY_HSYNC; + if (m->flags & DRM_MODE_FLAG_PVSYNC) + polarities |= HDLCD_POLARITY_VSYNC; + + /* Allow max number of outstanding requests and largest burst size */ + hdlcd_write(hdlcd, HDLCD_REG_BUS_OPTIONS, + HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16); + + hdlcd_write(hdlcd, HDLCD_REG_V_DATA, m->crtc_vdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_BACK_PORCH, vm.vback_porch - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_FRONT_PORCH, vm.vfront_porch - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_SYNC, vm.vsync_len - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_DATA, m->crtc_hdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_BACK_PORCH, vm.hback_porch - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_FRONT_PORCH, vm.hfront_porch - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_SYNC, vm.hsync_len - 1); + hdlcd_write(hdlcd, HDLCD_REG_POLARITIES, polarities); + + err = hdlcd_set_pxl_fmt(crtc); + if (err) + return; + + clk_set_rate(hdlcd->clk, m->crtc_clock * 1000); +} + +static void hdlcd_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + clk_prepare_enable(hdlcd->clk); + hdlcd_crtc_mode_set_nofb(crtc); + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 1); + drm_crtc_vblank_on(crtc); +} + +static void hdlcd_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + drm_crtc_vblank_off(crtc); + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0); + clk_disable_unprepare(hdlcd->clk); +} + +static enum drm_mode_status hdlcd_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + long rate, clk_rate = mode->clock * 1000; + + rate = clk_round_rate(hdlcd->clk, clk_rate); + /* 0.1% seems a close enough tolerance for the TDA19988 on Juno */ + if (abs(rate - clk_rate) * 1000 > clk_rate) { + /* clock required by mode not supported by hardware */ + return MODE_NOCLOCK; + } + + return MODE_OK; +} + +static void hdlcd_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *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 const struct drm_crtc_helper_funcs hdlcd_crtc_helper_funcs = { + .mode_valid = hdlcd_crtc_mode_valid, + .atomic_begin = hdlcd_crtc_atomic_begin, + .atomic_enable = hdlcd_crtc_atomic_enable, + .atomic_disable = hdlcd_crtc_atomic_disable, +}; + +static int hdlcd_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + int i; + struct drm_crtc *crtc; + struct drm_crtc_state *crtc_state; + u32 src_h = state->src_h >> 16; + + /* only the HDLCD_REG_FB_LINE_COUNT register has a limit */ + if (src_h >= HDLCD_MAX_YRES) { + DRM_DEBUG_KMS("Invalid source width: %d\n", src_h); + return -EINVAL; + } + + for_each_new_crtc_in_state(state->state, crtc, crtc_state, i) { + /* we cannot disable the plane while the CRTC is active */ + if (!state->fb && crtc_state->active) + return -EINVAL; + return drm_atomic_helper_check_plane_state(state, crtc_state, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + false, true); + } + + return 0; +} + +static void hdlcd_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_framebuffer *fb = plane->state->fb; + struct hdlcd_drm_private *hdlcd; + u32 dest_h; + dma_addr_t scanout_start; + + if (!fb) + return; + + dest_h = drm_rect_height(&plane->state->dst); + scanout_start = drm_fb_cma_get_gem_addr(fb, plane->state, 0); + + hdlcd = plane->dev->dev_private; + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_LENGTH, fb->pitches[0]); + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_PITCH, fb->pitches[0]); + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_COUNT, dest_h - 1); + hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start); +} + +static const struct drm_plane_helper_funcs hdlcd_plane_helper_funcs = { + .atomic_check = hdlcd_plane_atomic_check, + .atomic_update = hdlcd_plane_atomic_update, +}; + +static const struct drm_plane_funcs hdlcd_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 struct drm_plane *hdlcd_plane_init(struct drm_device *drm) +{ + struct hdlcd_drm_private *hdlcd = drm->dev_private; + struct drm_plane *plane = NULL; + u32 formats[ARRAY_SIZE(supported_formats)], i; + int ret; + + plane = devm_kzalloc(drm->dev, sizeof(*plane), GFP_KERNEL); + if (!plane) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) + formats[i] = supported_formats[i].fourcc; + + ret = drm_universal_plane_init(drm, plane, 0xff, &hdlcd_plane_funcs, + formats, ARRAY_SIZE(formats), + NULL, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ERR_PTR(ret); + + drm_plane_helper_add(plane, &hdlcd_plane_helper_funcs); + hdlcd->plane = plane; + + return plane; +} + +int hdlcd_setup_crtc(struct drm_device *drm) +{ + struct hdlcd_drm_private *hdlcd = drm->dev_private; + struct drm_plane *primary; + int ret; + + primary = hdlcd_plane_init(drm); + if (IS_ERR(primary)) + return PTR_ERR(primary); + + ret = drm_crtc_init_with_planes(drm, &hdlcd->crtc, primary, NULL, + &hdlcd_crtc_funcs, NULL); + if (ret) + return ret; + + drm_crtc_helper_add(&hdlcd->crtc, &hdlcd_crtc_helper_funcs); + return 0; +} diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c new file mode 100644 index 000000000..0ed1cde98 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_drv.c @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2013-2015 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Driver + */ + +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/console.h> +#include <linux/list.h> +#include <linux/of_graph.h> +#include <linux/of_reserved_mem.h> +#include <linux/pm_runtime.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_of.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +static int hdlcd_load(struct drm_device *drm, unsigned long flags) +{ + struct hdlcd_drm_private *hdlcd = drm->dev_private; + struct platform_device *pdev = to_platform_device(drm->dev); + struct resource *res; + u32 version; + int ret; + + hdlcd->clk = devm_clk_get(drm->dev, "pxlclk"); + if (IS_ERR(hdlcd->clk)) + return PTR_ERR(hdlcd->clk); + +#ifdef CONFIG_DEBUG_FS + atomic_set(&hdlcd->buffer_underrun_count, 0); + atomic_set(&hdlcd->bus_error_count, 0); + atomic_set(&hdlcd->vsync_count, 0); + atomic_set(&hdlcd->dma_end_count, 0); +#endif + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdlcd->mmio = devm_ioremap_resource(drm->dev, res); + if (IS_ERR(hdlcd->mmio)) { + DRM_ERROR("failed to map control registers area\n"); + ret = PTR_ERR(hdlcd->mmio); + hdlcd->mmio = NULL; + return ret; + } + + version = hdlcd_read(hdlcd, HDLCD_REG_VERSION); + if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) { + DRM_ERROR("unknown product id: 0x%x\n", version); + return -EINVAL; + } + DRM_INFO("found ARM HDLCD version r%dp%d\n", + (version & HDLCD_VERSION_MAJOR_MASK) >> 8, + version & HDLCD_VERSION_MINOR_MASK); + + /* Get the optional framebuffer memory resource */ + ret = of_reserved_mem_device_init(drm->dev); + if (ret && ret != -ENODEV) + return ret; + + ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32)); + if (ret) + goto setup_fail; + + ret = hdlcd_setup_crtc(drm); + if (ret < 0) { + DRM_ERROR("failed to create crtc\n"); + goto setup_fail; + } + + ret = drm_irq_install(drm, platform_get_irq(pdev, 0)); + if (ret < 0) { + DRM_ERROR("failed to install IRQ handler\n"); + goto irq_fail; + } + + return 0; + +irq_fail: + drm_crtc_cleanup(&hdlcd->crtc); +setup_fail: + of_reserved_mem_device_release(drm->dev); + + return ret; +} + +static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .output_poll_changed = drm_fb_helper_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void hdlcd_setup_mode_config(struct drm_device *drm) +{ + drm_mode_config_init(drm); + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + drm->mode_config.max_width = HDLCD_MAX_XRES; + drm->mode_config.max_height = HDLCD_MAX_YRES; + drm->mode_config.funcs = &hdlcd_mode_config_funcs; +} + +static irqreturn_t hdlcd_irq(int irq, void *arg) +{ + struct drm_device *drm = arg; + struct hdlcd_drm_private *hdlcd = drm->dev_private; + unsigned long irq_status; + + irq_status = hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS); + +#ifdef CONFIG_DEBUG_FS + if (irq_status & HDLCD_INTERRUPT_UNDERRUN) + atomic_inc(&hdlcd->buffer_underrun_count); + + if (irq_status & HDLCD_INTERRUPT_DMA_END) + atomic_inc(&hdlcd->dma_end_count); + + if (irq_status & HDLCD_INTERRUPT_BUS_ERROR) + atomic_inc(&hdlcd->bus_error_count); + + if (irq_status & HDLCD_INTERRUPT_VSYNC) + atomic_inc(&hdlcd->vsync_count); + +#endif + if (irq_status & HDLCD_INTERRUPT_VSYNC) + drm_crtc_handle_vblank(&hdlcd->crtc); + + /* acknowledge interrupt(s) */ + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, irq_status); + + return IRQ_HANDLED; +} + +static void hdlcd_irq_preinstall(struct drm_device *drm) +{ + struct hdlcd_drm_private *hdlcd = drm->dev_private; + /* Ensure interrupts are disabled */ + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, 0); + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, ~0); +} + +static int hdlcd_irq_postinstall(struct drm_device *drm) +{ +#ifdef CONFIG_DEBUG_FS + struct hdlcd_drm_private *hdlcd = drm->dev_private; + unsigned long irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + + /* enable debug interrupts */ + irq_mask |= HDLCD_DEBUG_INT_MASK; + + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); +#endif + return 0; +} + +static void hdlcd_irq_uninstall(struct drm_device *drm) +{ + struct hdlcd_drm_private *hdlcd = drm->dev_private; + /* disable all the interrupts that we might have enabled */ + unsigned long irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + +#ifdef CONFIG_DEBUG_FS + /* disable debug interrupts */ + irq_mask &= ~HDLCD_DEBUG_INT_MASK; +#endif + + /* disable vsync interrupts */ + irq_mask &= ~HDLCD_INTERRUPT_VSYNC; + + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); +} + +#ifdef CONFIG_DEBUG_FS +static int hdlcd_show_underrun_count(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *drm = node->minor->dev; + struct hdlcd_drm_private *hdlcd = drm->dev_private; + + seq_printf(m, "underrun : %d\n", atomic_read(&hdlcd->buffer_underrun_count)); + seq_printf(m, "dma_end : %d\n", atomic_read(&hdlcd->dma_end_count)); + seq_printf(m, "bus_error: %d\n", atomic_read(&hdlcd->bus_error_count)); + seq_printf(m, "vsync : %d\n", atomic_read(&hdlcd->vsync_count)); + return 0; +} + +static int hdlcd_show_pxlclock(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *drm = node->minor->dev; + struct hdlcd_drm_private *hdlcd = drm->dev_private; + unsigned long clkrate = clk_get_rate(hdlcd->clk); + unsigned long mode_clock = hdlcd->crtc.mode.crtc_clock * 1000; + + seq_printf(m, "hw : %lu\n", clkrate); + seq_printf(m, "mode: %lu\n", mode_clock); + return 0; +} + +static struct drm_info_list hdlcd_debugfs_list[] = { + { "interrupt_count", hdlcd_show_underrun_count, 0 }, + { "clocks", hdlcd_show_pxlclock, 0 }, +}; + +static int hdlcd_debugfs_init(struct drm_minor *minor) +{ + return drm_debugfs_create_files(hdlcd_debugfs_list, + ARRAY_SIZE(hdlcd_debugfs_list), minor->debugfs_root, minor); +} +#endif + +DEFINE_DRM_GEM_CMA_FOPS(fops); + +static struct drm_driver hdlcd_driver = { + .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | + DRIVER_MODESET | DRIVER_PRIME | + DRIVER_ATOMIC, + .lastclose = drm_fb_helper_lastclose, + .irq_handler = hdlcd_irq, + .irq_preinstall = hdlcd_irq_preinstall, + .irq_postinstall = hdlcd_irq_postinstall, + .irq_uninstall = hdlcd_irq_uninstall, + .gem_free_object_unlocked = drm_gem_cma_free_object, + .gem_print_info = drm_gem_cma_print_info, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .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, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = hdlcd_debugfs_init, +#endif + .fops = &fops, + .name = "hdlcd", + .desc = "ARM HDLCD Controller DRM", + .date = "20151021", + .major = 1, + .minor = 0, +}; + +static int hdlcd_drm_bind(struct device *dev) +{ + struct drm_device *drm; + struct hdlcd_drm_private *hdlcd; + int ret; + + hdlcd = devm_kzalloc(dev, sizeof(*hdlcd), GFP_KERNEL); + if (!hdlcd) + return -ENOMEM; + + drm = drm_dev_alloc(&hdlcd_driver, dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + drm->dev_private = hdlcd; + dev_set_drvdata(dev, drm); + + hdlcd_setup_mode_config(drm); + ret = hdlcd_load(drm, 0); + if (ret) + goto err_free; + + /* Set the CRTC's port so that the encoder component can find it */ + hdlcd->crtc.port = of_graph_get_port_by_id(dev->of_node, 0); + + ret = component_bind_all(dev, drm); + if (ret) { + DRM_ERROR("Failed to bind all components\n"); + goto err_unload; + } + + ret = pm_runtime_set_active(dev); + if (ret) + goto err_pm_active; + + pm_runtime_enable(dev); + + ret = drm_vblank_init(drm, drm->mode_config.num_crtc); + if (ret < 0) { + DRM_ERROR("failed to initialise vblank\n"); + goto err_vblank; + } + + drm_mode_config_reset(drm); + drm_kms_helper_poll_init(drm); + + ret = drm_fb_cma_fbdev_init(drm, 32, 0); + if (ret) + goto err_fbdev; + + ret = drm_dev_register(drm, 0); + if (ret) + goto err_register; + + return 0; + +err_register: + drm_fb_cma_fbdev_fini(drm); +err_fbdev: + drm_kms_helper_poll_fini(drm); +err_vblank: + pm_runtime_disable(drm->dev); +err_pm_active: + drm_atomic_helper_shutdown(drm); + component_unbind_all(dev, drm); +err_unload: + of_node_put(hdlcd->crtc.port); + hdlcd->crtc.port = NULL; + drm_irq_uninstall(drm); + of_reserved_mem_device_release(drm->dev); +err_free: + drm_mode_config_cleanup(drm); + dev_set_drvdata(dev, NULL); + drm_dev_put(drm); + + return ret; +} + +static void hdlcd_drm_unbind(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct hdlcd_drm_private *hdlcd = drm->dev_private; + + drm_dev_unregister(drm); + drm_fb_cma_fbdev_fini(drm); + drm_kms_helper_poll_fini(drm); + component_unbind_all(dev, drm); + of_node_put(hdlcd->crtc.port); + hdlcd->crtc.port = NULL; + pm_runtime_get_sync(dev); + drm_crtc_vblank_off(&hdlcd->crtc); + drm_irq_uninstall(drm); + drm_atomic_helper_shutdown(drm); + pm_runtime_put(dev); + if (pm_runtime_enabled(dev)) + pm_runtime_disable(dev); + of_reserved_mem_device_release(dev); + drm_mode_config_cleanup(drm); + drm->dev_private = NULL; + dev_set_drvdata(dev, NULL); + drm_dev_put(drm); +} + +static const struct component_master_ops hdlcd_master_ops = { + .bind = hdlcd_drm_bind, + .unbind = hdlcd_drm_unbind, +}; + +static int compare_dev(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static int hdlcd_probe(struct platform_device *pdev) +{ + struct device_node *port; + struct component_match *match = NULL; + + /* there is only one output port inside each device, find it */ + port = of_graph_get_remote_node(pdev->dev.of_node, 0, 0); + if (!port) + return -ENODEV; + + drm_of_component_match_add(&pdev->dev, &match, compare_dev, port); + of_node_put(port); + + return component_master_add_with_match(&pdev->dev, &hdlcd_master_ops, + match); +} + +static int hdlcd_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &hdlcd_master_ops); + return 0; +} + +static const struct of_device_id hdlcd_of_match[] = { + { .compatible = "arm,hdlcd" }, + {}, +}; +MODULE_DEVICE_TABLE(of, hdlcd_of_match); + +static int __maybe_unused hdlcd_pm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(drm); +} + +static int __maybe_unused hdlcd_pm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + drm_mode_config_helper_resume(drm); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(hdlcd_pm_ops, hdlcd_pm_suspend, hdlcd_pm_resume); + +static struct platform_driver hdlcd_platform_driver = { + .probe = hdlcd_probe, + .remove = hdlcd_remove, + .driver = { + .name = "hdlcd", + .pm = &hdlcd_pm_ops, + .of_match_table = hdlcd_of_match, + }, +}; + +module_platform_driver(hdlcd_platform_driver); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("ARM HDLCD DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h new file mode 100644 index 000000000..fd438d177 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_drv.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ARM HDLCD Controller register definition + */ + +#ifndef __HDLCD_DRV_H__ +#define __HDLCD_DRV_H__ + +struct hdlcd_drm_private { + void __iomem *mmio; + struct clk *clk; + struct drm_crtc crtc; + struct drm_plane *plane; +#ifdef CONFIG_DEBUG_FS + atomic_t buffer_underrun_count; + atomic_t bus_error_count; + atomic_t vsync_count; + atomic_t dma_end_count; +#endif +}; + +#define crtc_to_hdlcd_priv(x) container_of(x, struct hdlcd_drm_private, crtc) + +static inline void hdlcd_write(struct hdlcd_drm_private *hdlcd, + unsigned int reg, u32 value) +{ + writel(value, hdlcd->mmio + reg); +} + +static inline u32 hdlcd_read(struct hdlcd_drm_private *hdlcd, unsigned int reg) +{ + return readl(hdlcd->mmio + reg); +} + +int hdlcd_setup_crtc(struct drm_device *dev); +void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd); + +#endif /* __HDLCD_DRV_H__ */ diff --git a/drivers/gpu/drm/arm/hdlcd_regs.h b/drivers/gpu/drm/arm/hdlcd_regs.h new file mode 100644 index 000000000..66799ebef --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_regs.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Controller register definition + */ + +#ifndef __HDLCD_REGS_H__ +#define __HDLCD_REGS_H__ + +/* register offsets */ +#define HDLCD_REG_VERSION 0x0000 /* ro */ +#define HDLCD_REG_INT_RAWSTAT 0x0010 /* rw */ +#define HDLCD_REG_INT_CLEAR 0x0014 /* wo */ +#define HDLCD_REG_INT_MASK 0x0018 /* rw */ +#define HDLCD_REG_INT_STATUS 0x001c /* ro */ +#define HDLCD_REG_FB_BASE 0x0100 /* rw */ +#define HDLCD_REG_FB_LINE_LENGTH 0x0104 /* rw */ +#define HDLCD_REG_FB_LINE_COUNT 0x0108 /* rw */ +#define HDLCD_REG_FB_LINE_PITCH 0x010c /* rw */ +#define HDLCD_REG_BUS_OPTIONS 0x0110 /* rw */ +#define HDLCD_REG_V_SYNC 0x0200 /* rw */ +#define HDLCD_REG_V_BACK_PORCH 0x0204 /* rw */ +#define HDLCD_REG_V_DATA 0x0208 /* rw */ +#define HDLCD_REG_V_FRONT_PORCH 0x020c /* rw */ +#define HDLCD_REG_H_SYNC 0x0210 /* rw */ +#define HDLCD_REG_H_BACK_PORCH 0x0214 /* rw */ +#define HDLCD_REG_H_DATA 0x0218 /* rw */ +#define HDLCD_REG_H_FRONT_PORCH 0x021c /* rw */ +#define HDLCD_REG_POLARITIES 0x0220 /* rw */ +#define HDLCD_REG_COMMAND 0x0230 /* rw */ +#define HDLCD_REG_PIXEL_FORMAT 0x0240 /* rw */ +#define HDLCD_REG_RED_SELECT 0x0244 /* rw */ +#define HDLCD_REG_GREEN_SELECT 0x0248 /* rw */ +#define HDLCD_REG_BLUE_SELECT 0x024c /* rw */ + +/* version */ +#define HDLCD_PRODUCT_ID 0x1CDC0000 +#define HDLCD_PRODUCT_MASK 0xFFFF0000 +#define HDLCD_VERSION_MAJOR_MASK 0x0000FF00 +#define HDLCD_VERSION_MINOR_MASK 0x000000FF + +/* interrupts */ +#define HDLCD_INTERRUPT_DMA_END (1 << 0) +#define HDLCD_INTERRUPT_BUS_ERROR (1 << 1) +#define HDLCD_INTERRUPT_VSYNC (1 << 2) +#define HDLCD_INTERRUPT_UNDERRUN (1 << 3) +#define HDLCD_DEBUG_INT_MASK (HDLCD_INTERRUPT_DMA_END | \ + HDLCD_INTERRUPT_BUS_ERROR | \ + HDLCD_INTERRUPT_UNDERRUN) + +/* polarities */ +#define HDLCD_POLARITY_VSYNC (1 << 0) +#define HDLCD_POLARITY_HSYNC (1 << 1) +#define HDLCD_POLARITY_DATAEN (1 << 2) +#define HDLCD_POLARITY_DATA (1 << 3) +#define HDLCD_POLARITY_PIXELCLK (1 << 4) + +/* commands */ +#define HDLCD_COMMAND_DISABLE (0 << 0) +#define HDLCD_COMMAND_ENABLE (1 << 0) + +/* pixel format */ +#define HDLCD_PIXEL_FMT_LITTLE_ENDIAN (0 << 31) +#define HDLCD_PIXEL_FMT_BIG_ENDIAN (1 << 31) +#define HDLCD_BYTES_PER_PIXEL_MASK (3 << 3) + +/* bus options */ +#define HDLCD_BUS_BURST_MASK 0x01f +#define HDLCD_BUS_MAX_OUTSTAND 0xf00 +#define HDLCD_BUS_BURST_NONE (0 << 0) +#define HDLCD_BUS_BURST_1 (1 << 0) +#define HDLCD_BUS_BURST_2 (1 << 1) +#define HDLCD_BUS_BURST_4 (1 << 2) +#define HDLCD_BUS_BURST_8 (1 << 3) +#define HDLCD_BUS_BURST_16 (1 << 4) + +/* Max resolution supported is 4096x4096, 32bpp */ +#define HDLCD_MAX_XRES 4096 +#define HDLCD_MAX_YRES 4096 + +#define NR_PALETTE 256 + +#endif /* __HDLCD_REGS_H__ */ diff --git a/drivers/gpu/drm/arm/malidp_crtc.c b/drivers/gpu/drm/arm/malidp_crtc.c new file mode 100644 index 000000000..ef44202fb --- /dev/null +++ b/drivers/gpu/drm/arm/malidp_crtc.c @@ -0,0 +1,560 @@ +/* + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * ARM Mali DP500/DP550/DP650 driver (crtc operations) + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <video/videomode.h> + +#include "malidp_drv.h" +#include "malidp_hw.h" + +static enum drm_mode_status malidp_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct malidp_drm *malidp = crtc_to_malidp_device(crtc); + struct malidp_hw_device *hwdev = malidp->dev; + + /* + * check that the hardware can drive the required clock rate, + * but skip the check if the clock is meant to be disabled (req_rate = 0) + */ + long rate, req_rate = mode->crtc_clock * 1000; + + if (req_rate) { + rate = clk_round_rate(hwdev->pxlclk, req_rate); + if (rate != req_rate) { + DRM_DEBUG_DRIVER("pxlclk doesn't support %ld Hz\n", + req_rate); + return MODE_NOCLOCK; + } + } + + return MODE_OK; +} + +static void malidp_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct malidp_drm *malidp = crtc_to_malidp_device(crtc); + struct malidp_hw_device *hwdev = malidp->dev; + struct videomode vm; + int err = pm_runtime_get_sync(crtc->dev->dev); + + if (err < 0) { + DRM_DEBUG_DRIVER("Failed to enable runtime power management: %d\n", err); + return; + } + + drm_display_mode_to_videomode(&crtc->state->adjusted_mode, &vm); + clk_prepare_enable(hwdev->pxlclk); + + /* We rely on firmware to set mclk to a sensible level. */ + clk_set_rate(hwdev->pxlclk, crtc->state->adjusted_mode.crtc_clock * 1000); + + hwdev->hw->modeset(hwdev, &vm); + hwdev->hw->leave_config_mode(hwdev); + drm_crtc_vblank_on(crtc); +} + +static void malidp_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct malidp_drm *malidp = crtc_to_malidp_device(crtc); + struct malidp_hw_device *hwdev = malidp->dev; + int err; + + /* always disable planes on the CRTC that is being turned off */ + drm_atomic_helper_disable_planes_on_crtc(old_state, false); + + drm_crtc_vblank_off(crtc); + hwdev->hw->enter_config_mode(hwdev); + + clk_disable_unprepare(hwdev->pxlclk); + + err = pm_runtime_put(crtc->dev->dev); + if (err < 0) { + DRM_DEBUG_DRIVER("Failed to disable runtime power management: %d\n", err); + } +} + +static const struct gamma_curve_segment { + u16 start; + u16 end; +} segments[MALIDP_COEFFTAB_NUM_COEFFS] = { + /* sector 0 */ + { 0, 0 }, { 1, 1 }, { 2, 2 }, { 3, 3 }, + { 4, 4 }, { 5, 5 }, { 6, 6 }, { 7, 7 }, + { 8, 8 }, { 9, 9 }, { 10, 10 }, { 11, 11 }, + { 12, 12 }, { 13, 13 }, { 14, 14 }, { 15, 15 }, + /* sector 1 */ + { 16, 19 }, { 20, 23 }, { 24, 27 }, { 28, 31 }, + /* sector 2 */ + { 32, 39 }, { 40, 47 }, { 48, 55 }, { 56, 63 }, + /* sector 3 */ + { 64, 79 }, { 80, 95 }, { 96, 111 }, { 112, 127 }, + /* sector 4 */ + { 128, 159 }, { 160, 191 }, { 192, 223 }, { 224, 255 }, + /* sector 5 */ + { 256, 319 }, { 320, 383 }, { 384, 447 }, { 448, 511 }, + /* sector 6 */ + { 512, 639 }, { 640, 767 }, { 768, 895 }, { 896, 1023 }, + { 1024, 1151 }, { 1152, 1279 }, { 1280, 1407 }, { 1408, 1535 }, + { 1536, 1663 }, { 1664, 1791 }, { 1792, 1919 }, { 1920, 2047 }, + { 2048, 2175 }, { 2176, 2303 }, { 2304, 2431 }, { 2432, 2559 }, + { 2560, 2687 }, { 2688, 2815 }, { 2816, 2943 }, { 2944, 3071 }, + { 3072, 3199 }, { 3200, 3327 }, { 3328, 3455 }, { 3456, 3583 }, + { 3584, 3711 }, { 3712, 3839 }, { 3840, 3967 }, { 3968, 4095 }, +}; + +#define DE_COEFTAB_DATA(a, b) ((((a) & 0xfff) << 16) | (((b) & 0xfff))) + +static void malidp_generate_gamma_table(struct drm_property_blob *lut_blob, + u32 coeffs[MALIDP_COEFFTAB_NUM_COEFFS]) +{ + struct drm_color_lut *lut = (struct drm_color_lut *)lut_blob->data; + int i; + + for (i = 0; i < MALIDP_COEFFTAB_NUM_COEFFS; ++i) { + u32 a, b, delta_in, out_start, out_end; + + delta_in = segments[i].end - segments[i].start; + /* DP has 12-bit internal precision for its LUTs. */ + out_start = drm_color_lut_extract(lut[segments[i].start].green, + 12); + out_end = drm_color_lut_extract(lut[segments[i].end].green, 12); + a = (delta_in == 0) ? 0 : ((out_end - out_start) * 256) / delta_in; + b = out_start; + coeffs[i] = DE_COEFTAB_DATA(a, b); + } +} + +/* + * Check if there is a new gamma LUT and if it is of an acceptable size. Also, + * reject any LUTs that use distinct red, green, and blue curves. + */ +static int malidp_crtc_atomic_check_gamma(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct malidp_crtc_state *mc = to_malidp_crtc_state(state); + struct drm_color_lut *lut; + size_t lut_size; + int i; + + if (!state->color_mgmt_changed || !state->gamma_lut) + return 0; + + if (crtc->state->gamma_lut && + (crtc->state->gamma_lut->base.id == state->gamma_lut->base.id)) + return 0; + + if (state->gamma_lut->length % sizeof(struct drm_color_lut)) + return -EINVAL; + + lut_size = state->gamma_lut->length / sizeof(struct drm_color_lut); + if (lut_size != MALIDP_GAMMA_LUT_SIZE) + return -EINVAL; + + lut = (struct drm_color_lut *)state->gamma_lut->data; + for (i = 0; i < lut_size; ++i) + if (!((lut[i].red == lut[i].green) && + (lut[i].red == lut[i].blue))) + return -EINVAL; + + if (!state->mode_changed) { + int ret; + + state->mode_changed = true; + /* + * Kerneldoc for drm_atomic_helper_check_modeset mandates that + * it be invoked when the driver sets ->mode_changed. Since + * changing the gamma LUT doesn't depend on any external + * resources, it is safe to call it only once. + */ + ret = drm_atomic_helper_check_modeset(crtc->dev, state->state); + if (ret) + return ret; + } + + malidp_generate_gamma_table(state->gamma_lut, mc->gamma_coeffs); + return 0; +} + +/* + * Check if there is a new CTM and if it contains valid input. Valid here means + * that the number is inside the representable range for a Q3.12 number, + * excluding truncating the fractional part of the input data. + * + * The COLORADJ registers can be changed atomically. + */ +static int malidp_crtc_atomic_check_ctm(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct malidp_crtc_state *mc = to_malidp_crtc_state(state); + struct drm_color_ctm *ctm; + int i; + + if (!state->color_mgmt_changed) + return 0; + + if (!state->ctm) + return 0; + + if (crtc->state->ctm && (crtc->state->ctm->base.id == + state->ctm->base.id)) + return 0; + + /* + * The size of the ctm is checked in + * drm_atomic_replace_property_blob_from_id. + */ + ctm = (struct drm_color_ctm *)state->ctm->data; + for (i = 0; i < ARRAY_SIZE(ctm->matrix); ++i) { + /* Convert from S31.32 to Q3.12. */ + s64 val = ctm->matrix[i]; + u32 mag = ((((u64)val) & ~BIT_ULL(63)) >> 20) & + GENMASK_ULL(14, 0); + + /* + * Convert to 2s complement and check the destination's top bit + * for overflow. NB: Can't check before converting or it'd + * incorrectly reject the case: + * sign == 1 + * mag == 0x2000 + */ + if (val & BIT_ULL(63)) + mag = ~mag + 1; + if (!!(val & BIT_ULL(63)) != !!(mag & BIT(14))) + return -EINVAL; + mc->coloradj_coeffs[i] = mag; + } + + return 0; +} + +static int malidp_crtc_atomic_check_scaling(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct malidp_drm *malidp = crtc_to_malidp_device(crtc); + struct malidp_hw_device *hwdev = malidp->dev; + struct malidp_crtc_state *cs = to_malidp_crtc_state(state); + struct malidp_se_config *s = &cs->scaler_config; + struct drm_plane *plane; + struct videomode vm; + const struct drm_plane_state *pstate; + u32 h_upscale_factor = 0; /* U16.16 */ + u32 v_upscale_factor = 0; /* U16.16 */ + u8 scaling = cs->scaled_planes_mask; + int ret; + + if (!scaling) { + s->scale_enable = false; + goto mclk_calc; + } + + /* The scaling engine can only handle one plane at a time. */ + if (scaling & (scaling - 1)) + return -EINVAL; + + drm_atomic_crtc_state_for_each_plane_state(plane, pstate, state) { + struct malidp_plane *mp = to_malidp_plane(plane); + u32 phase; + + if (!(mp->layer->id & scaling)) + continue; + + /* + * Convert crtc_[w|h] to U32.32, then divide by U16.16 src_[w|h] + * to get the U16.16 result. + */ + h_upscale_factor = div_u64((u64)pstate->crtc_w << 32, + pstate->src_w); + v_upscale_factor = div_u64((u64)pstate->crtc_h << 32, + pstate->src_h); + + s->enhancer_enable = ((h_upscale_factor >> 16) >= 2 || + (v_upscale_factor >> 16) >= 2); + + if (pstate->rotation & MALIDP_ROTATED_MASK) { + s->input_w = pstate->src_h >> 16; + s->input_h = pstate->src_w >> 16; + } else { + s->input_w = pstate->src_w >> 16; + s->input_h = pstate->src_h >> 16; + } + + s->output_w = pstate->crtc_w; + s->output_h = pstate->crtc_h; + +#define SE_N_PHASE 4 +#define SE_SHIFT_N_PHASE 12 + /* Calculate initial_phase and delta_phase for horizontal. */ + phase = s->input_w; + s->h_init_phase = + ((phase << SE_N_PHASE) / s->output_w + 1) / 2; + + phase = s->input_w; + phase <<= (SE_SHIFT_N_PHASE + SE_N_PHASE); + s->h_delta_phase = phase / s->output_w; + + /* Same for vertical. */ + phase = s->input_h; + s->v_init_phase = + ((phase << SE_N_PHASE) / s->output_h + 1) / 2; + + phase = s->input_h; + phase <<= (SE_SHIFT_N_PHASE + SE_N_PHASE); + s->v_delta_phase = phase / s->output_h; +#undef SE_N_PHASE +#undef SE_SHIFT_N_PHASE + s->plane_src_id = mp->layer->id; + } + + s->scale_enable = true; + s->hcoeff = malidp_se_select_coeffs(h_upscale_factor); + s->vcoeff = malidp_se_select_coeffs(v_upscale_factor); + +mclk_calc: + drm_display_mode_to_videomode(&state->adjusted_mode, &vm); + ret = hwdev->hw->se_calc_mclk(hwdev, s, &vm); + if (ret < 0) + return -EINVAL; + return 0; +} + +static int malidp_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct malidp_drm *malidp = crtc_to_malidp_device(crtc); + struct malidp_hw_device *hwdev = malidp->dev; + struct drm_plane *plane; + const struct drm_plane_state *pstate; + u32 rot_mem_free, rot_mem_usable; + int rotated_planes = 0; + int ret; + + /* + * check if there is enough rotation memory available for planes + * that need 90° and 270° rotation. Each plane has set its required + * memory size in the ->plane_check() callback, here we only make + * sure that the sums are less that the total usable memory. + * + * The rotation memory allocation algorithm (for each plane): + * a. If no more rotated planes exist, all remaining rotate + * memory in the bank is available for use by the plane. + * b. If other rotated planes exist, and plane's layer ID is + * DE_VIDEO1, it can use all the memory from first bank if + * secondary rotation memory bank is available, otherwise it can + * use up to half the bank's memory. + * c. If other rotated planes exist, and plane's layer ID is not + * DE_VIDEO1, it can use half of the available memory + * + * Note: this algorithm assumes that the order in which the planes are + * checked always has DE_VIDEO1 plane first in the list if it is + * rotated. Because that is how we create the planes in the first + * place, under current DRM version things work, but if ever the order + * in which drm_atomic_crtc_state_for_each_plane() iterates over planes + * changes, we need to pre-sort the planes before validation. + */ + + /* first count the number of rotated planes */ + drm_atomic_crtc_state_for_each_plane_state(plane, pstate, state) { + if (pstate->rotation & MALIDP_ROTATED_MASK) + rotated_planes++; + } + + rot_mem_free = hwdev->rotation_memory[0]; + /* + * if we have more than 1 plane using rotation memory, use the second + * block of rotation memory as well + */ + if (rotated_planes > 1) + rot_mem_free += hwdev->rotation_memory[1]; + + /* now validate the rotation memory requirements */ + drm_atomic_crtc_state_for_each_plane_state(plane, pstate, state) { + struct malidp_plane *mp = to_malidp_plane(plane); + struct malidp_plane_state *ms = to_malidp_plane_state(pstate); + + if (pstate->rotation & MALIDP_ROTATED_MASK) { + /* process current plane */ + rotated_planes--; + + if (!rotated_planes) { + /* no more rotated planes, we can use what's left */ + rot_mem_usable = rot_mem_free; + } else { + if ((mp->layer->id != DE_VIDEO1) || + (hwdev->rotation_memory[1] == 0)) + rot_mem_usable = rot_mem_free / 2; + else + rot_mem_usable = hwdev->rotation_memory[0]; + } + + rot_mem_free -= rot_mem_usable; + + if (ms->rotmem_size > rot_mem_usable) + return -EINVAL; + } + } + + /* If only the writeback routing has changed, we don't need a modeset */ + if (state->connectors_changed) { + u32 old_mask = crtc->state->connector_mask; + u32 new_mask = state->connector_mask; + + if ((old_mask ^ new_mask) == + (1 << drm_connector_index(&malidp->mw_connector.base))) + state->connectors_changed = false; + } + + ret = malidp_crtc_atomic_check_gamma(crtc, state); + ret = ret ? ret : malidp_crtc_atomic_check_ctm(crtc, state); + ret = ret ? ret : malidp_crtc_atomic_check_scaling(crtc, state); + + return ret; +} + +static const struct drm_crtc_helper_funcs malidp_crtc_helper_funcs = { + .mode_valid = malidp_crtc_mode_valid, + .atomic_check = malidp_crtc_atomic_check, + .atomic_enable = malidp_crtc_atomic_enable, + .atomic_disable = malidp_crtc_atomic_disable, +}; + +static struct drm_crtc_state *malidp_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct malidp_crtc_state *state, *old_state; + + if (WARN_ON(!crtc->state)) + return NULL; + + old_state = to_malidp_crtc_state(crtc->state); + state = kmalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base); + memcpy(state->gamma_coeffs, old_state->gamma_coeffs, + sizeof(state->gamma_coeffs)); + memcpy(state->coloradj_coeffs, old_state->coloradj_coeffs, + sizeof(state->coloradj_coeffs)); + memcpy(&state->scaler_config, &old_state->scaler_config, + sizeof(state->scaler_config)); + state->scaled_planes_mask = 0; + + return &state->base; +} + +static void malidp_crtc_reset(struct drm_crtc *crtc) +{ + struct malidp_crtc_state *state = NULL; + + if (crtc->state) { + state = to_malidp_crtc_state(crtc->state); + __drm_atomic_helper_crtc_destroy_state(crtc->state); + } + + kfree(state); + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) { + crtc->state = &state->base; + crtc->state->crtc = crtc; + } +} + +static void malidp_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct malidp_crtc_state *mali_state = NULL; + + if (state) { + mali_state = to_malidp_crtc_state(state); + __drm_atomic_helper_crtc_destroy_state(state); + } + + kfree(mali_state); +} + +static int malidp_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct malidp_drm *malidp = crtc_to_malidp_device(crtc); + struct malidp_hw_device *hwdev = malidp->dev; + + malidp_hw_enable_irq(hwdev, MALIDP_DE_BLOCK, + hwdev->hw->map.de_irq_map.vsync_irq); + return 0; +} + +static void malidp_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct malidp_drm *malidp = crtc_to_malidp_device(crtc); + struct malidp_hw_device *hwdev = malidp->dev; + + malidp_hw_disable_irq(hwdev, MALIDP_DE_BLOCK, + hwdev->hw->map.de_irq_map.vsync_irq); +} + +static const struct drm_crtc_funcs malidp_crtc_funcs = { + .gamma_set = drm_atomic_helper_legacy_gamma_set, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = malidp_crtc_reset, + .atomic_duplicate_state = malidp_crtc_duplicate_state, + .atomic_destroy_state = malidp_crtc_destroy_state, + .enable_vblank = malidp_crtc_enable_vblank, + .disable_vblank = malidp_crtc_disable_vblank, +}; + +int malidp_crtc_init(struct drm_device *drm) +{ + struct malidp_drm *malidp = drm->dev_private; + struct drm_plane *primary = NULL, *plane; + int ret; + + ret = malidp_de_planes_init(drm); + if (ret < 0) { + DRM_ERROR("Failed to initialise planes\n"); + return ret; + } + + drm_for_each_plane(plane, drm) { + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + primary = plane; + break; + } + } + + if (!primary) { + DRM_ERROR("no primary plane found\n"); + return -EINVAL; + } + + ret = drm_crtc_init_with_planes(drm, &malidp->crtc, primary, NULL, + &malidp_crtc_funcs, NULL); + if (ret) + return ret; + + drm_crtc_helper_add(&malidp->crtc, &malidp_crtc_helper_funcs); + drm_mode_crtc_set_gamma_size(&malidp->crtc, MALIDP_GAMMA_LUT_SIZE); + /* No inverse-gamma: it is per-plane. */ + drm_crtc_enable_color_mgmt(&malidp->crtc, 0, true, MALIDP_GAMMA_LUT_SIZE); + + malidp_se_set_enh_coeffs(malidp->dev); + + return 0; +} diff --git a/drivers/gpu/drm/arm/malidp_drv.c b/drivers/gpu/drm/arm/malidp_drv.c new file mode 100644 index 000000000..1ab511e33 --- /dev/null +++ b/drivers/gpu/drm/arm/malidp_drv.c @@ -0,0 +1,943 @@ +/* + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * ARM Mali DP500/DP550/DP650 KMS/DRM driver + */ + +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/of_reserved_mem.h> +#include <linux/pm_runtime.h> +#include <linux/debugfs.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_of.h> + +#include "malidp_drv.h" +#include "malidp_mw.h" +#include "malidp_regs.h" +#include "malidp_hw.h" + +#define MALIDP_CONF_VALID_TIMEOUT 250 + +static void malidp_write_gamma_table(struct malidp_hw_device *hwdev, + u32 data[MALIDP_COEFFTAB_NUM_COEFFS]) +{ + int i; + /* Update all channels with a single gamma curve. */ + const u32 gamma_write_mask = GENMASK(18, 16); + /* + * Always write an entire table, so the address field in + * DE_COEFFTAB_ADDR is 0 and we can use the gamma_write_mask bitmask + * directly. + */ + malidp_hw_write(hwdev, gamma_write_mask, + hwdev->hw->map.coeffs_base + MALIDP_COEF_TABLE_ADDR); + for (i = 0; i < MALIDP_COEFFTAB_NUM_COEFFS; ++i) + malidp_hw_write(hwdev, data[i], + hwdev->hw->map.coeffs_base + + MALIDP_COEF_TABLE_DATA); +} + +static void malidp_atomic_commit_update_gamma(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct malidp_drm *malidp = crtc_to_malidp_device(crtc); + struct malidp_hw_device *hwdev = malidp->dev; + + if (!crtc->state->color_mgmt_changed) + return; + + if (!crtc->state->gamma_lut) { + malidp_hw_clearbits(hwdev, + MALIDP_DISP_FUNC_GAMMA, + MALIDP_DE_DISPLAY_FUNC); + } else { + struct malidp_crtc_state *mc = + to_malidp_crtc_state(crtc->state); + + if (!old_state->gamma_lut || (crtc->state->gamma_lut->base.id != + old_state->gamma_lut->base.id)) + malidp_write_gamma_table(hwdev, mc->gamma_coeffs); + + malidp_hw_setbits(hwdev, MALIDP_DISP_FUNC_GAMMA, + MALIDP_DE_DISPLAY_FUNC); + } +} + +static +void malidp_atomic_commit_update_coloradj(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct malidp_drm *malidp = crtc_to_malidp_device(crtc); + struct malidp_hw_device *hwdev = malidp->dev; + int i; + + if (!crtc->state->color_mgmt_changed) + return; + + if (!crtc->state->ctm) { + malidp_hw_clearbits(hwdev, MALIDP_DISP_FUNC_CADJ, + MALIDP_DE_DISPLAY_FUNC); + } else { + struct malidp_crtc_state *mc = + to_malidp_crtc_state(crtc->state); + + if (!old_state->ctm || (crtc->state->ctm->base.id != + old_state->ctm->base.id)) + for (i = 0; i < MALIDP_COLORADJ_NUM_COEFFS; ++i) + malidp_hw_write(hwdev, + mc->coloradj_coeffs[i], + hwdev->hw->map.coeffs_base + + MALIDP_COLOR_ADJ_COEF + 4 * i); + + malidp_hw_setbits(hwdev, MALIDP_DISP_FUNC_CADJ, + MALIDP_DE_DISPLAY_FUNC); + } +} + +static void malidp_atomic_commit_se_config(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct malidp_crtc_state *cs = to_malidp_crtc_state(crtc->state); + struct malidp_crtc_state *old_cs = to_malidp_crtc_state(old_state); + struct malidp_drm *malidp = crtc_to_malidp_device(crtc); + struct malidp_hw_device *hwdev = malidp->dev; + struct malidp_se_config *s = &cs->scaler_config; + struct malidp_se_config *old_s = &old_cs->scaler_config; + u32 se_control = hwdev->hw->map.se_base + + ((hwdev->hw->map.features & MALIDP_REGMAP_HAS_CLEARIRQ) ? + 0x10 : 0xC); + u32 layer_control = se_control + MALIDP_SE_LAYER_CONTROL; + u32 scr = se_control + MALIDP_SE_SCALING_CONTROL; + u32 val; + + /* Set SE_CONTROL */ + if (!s->scale_enable) { + val = malidp_hw_read(hwdev, se_control); + val &= ~MALIDP_SE_SCALING_EN; + malidp_hw_write(hwdev, val, se_control); + return; + } + + hwdev->hw->se_set_scaling_coeffs(hwdev, s, old_s); + val = malidp_hw_read(hwdev, se_control); + val |= MALIDP_SE_SCALING_EN | MALIDP_SE_ALPHA_EN; + + val &= ~MALIDP_SE_ENH(MALIDP_SE_ENH_MASK); + val |= s->enhancer_enable ? MALIDP_SE_ENH(3) : 0; + + val |= MALIDP_SE_RGBO_IF_EN; + malidp_hw_write(hwdev, val, se_control); + + /* Set IN_SIZE & OUT_SIZE. */ + val = MALIDP_SE_SET_V_SIZE(s->input_h) | + MALIDP_SE_SET_H_SIZE(s->input_w); + malidp_hw_write(hwdev, val, layer_control + MALIDP_SE_L0_IN_SIZE); + val = MALIDP_SE_SET_V_SIZE(s->output_h) | + MALIDP_SE_SET_H_SIZE(s->output_w); + malidp_hw_write(hwdev, val, layer_control + MALIDP_SE_L0_OUT_SIZE); + + /* Set phase regs. */ + malidp_hw_write(hwdev, s->h_init_phase, scr + MALIDP_SE_H_INIT_PH); + malidp_hw_write(hwdev, s->h_delta_phase, scr + MALIDP_SE_H_DELTA_PH); + malidp_hw_write(hwdev, s->v_init_phase, scr + MALIDP_SE_V_INIT_PH); + malidp_hw_write(hwdev, s->v_delta_phase, scr + MALIDP_SE_V_DELTA_PH); +} + +/* + * set the "config valid" bit and wait until the hardware acts on it + */ +static int malidp_set_and_wait_config_valid(struct drm_device *drm) +{ + struct malidp_drm *malidp = drm->dev_private; + struct malidp_hw_device *hwdev = malidp->dev; + int ret; + + hwdev->hw->set_config_valid(hwdev, 1); + /* don't wait for config_valid flag if we are in config mode */ + if (hwdev->hw->in_config_mode(hwdev)) { + atomic_set(&malidp->config_valid, MALIDP_CONFIG_VALID_DONE); + return 0; + } + + ret = wait_event_interruptible_timeout(malidp->wq, + atomic_read(&malidp->config_valid) == MALIDP_CONFIG_VALID_DONE, + msecs_to_jiffies(MALIDP_CONF_VALID_TIMEOUT)); + + return (ret > 0) ? 0 : -ETIMEDOUT; +} + +static void malidp_atomic_commit_hw_done(struct drm_atomic_state *state) +{ + struct drm_device *drm = state->dev; + struct malidp_drm *malidp = drm->dev_private; + int loop = 5; + + malidp->event = malidp->crtc.state->event; + malidp->crtc.state->event = NULL; + + if (malidp->crtc.state->active) { + /* + * if we have an event to deliver to userspace, make sure + * the vblank is enabled as we are sending it from the IRQ + * handler. + */ + if (malidp->event) + drm_crtc_vblank_get(&malidp->crtc); + + /* only set config_valid if the CRTC is enabled */ + if (malidp_set_and_wait_config_valid(drm) < 0) { + /* + * make a loop around the second CVAL setting and + * try 5 times before giving up. + */ + while (loop--) { + if (!malidp_set_and_wait_config_valid(drm)) + break; + } + DRM_DEBUG_DRIVER("timed out waiting for updated configuration\n"); + } + + } else if (malidp->event) { + /* CRTC inactive means vblank IRQ is disabled, send event directly */ + spin_lock_irq(&drm->event_lock); + drm_crtc_send_vblank_event(&malidp->crtc, malidp->event); + malidp->event = NULL; + spin_unlock_irq(&drm->event_lock); + } + drm_atomic_helper_commit_hw_done(state); +} + +static void malidp_atomic_commit_tail(struct drm_atomic_state *state) +{ + struct drm_device *drm = state->dev; + struct malidp_drm *malidp = drm->dev_private; + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state; + int i; + + pm_runtime_get_sync(drm->dev); + + /* + * set config_valid to a special value to let IRQ handlers + * know that we are updating registers + */ + atomic_set(&malidp->config_valid, MALIDP_CONFIG_START); + malidp->dev->hw->set_config_valid(malidp->dev, 0); + + drm_atomic_helper_commit_modeset_disables(drm, state); + + for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) { + malidp_atomic_commit_update_gamma(crtc, old_crtc_state); + malidp_atomic_commit_update_coloradj(crtc, old_crtc_state); + malidp_atomic_commit_se_config(crtc, old_crtc_state); + } + + drm_atomic_helper_commit_planes(drm, state, DRM_PLANE_COMMIT_ACTIVE_ONLY); + + malidp_mw_atomic_commit(drm, state); + + drm_atomic_helper_commit_modeset_enables(drm, state); + + malidp_atomic_commit_hw_done(state); + + pm_runtime_put(drm->dev); + + drm_atomic_helper_cleanup_planes(drm, state); +} + +static const struct drm_mode_config_helper_funcs malidp_mode_config_helpers = { + .atomic_commit_tail = malidp_atomic_commit_tail, +}; + +static const struct drm_mode_config_funcs malidp_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .output_poll_changed = drm_fb_helper_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static int malidp_init(struct drm_device *drm) +{ + int ret; + struct malidp_drm *malidp = drm->dev_private; + struct malidp_hw_device *hwdev = malidp->dev; + + drm_mode_config_init(drm); + + drm->mode_config.min_width = hwdev->min_line_size; + drm->mode_config.min_height = hwdev->min_line_size; + drm->mode_config.max_width = hwdev->max_line_size; + drm->mode_config.max_height = hwdev->max_line_size; + drm->mode_config.funcs = &malidp_mode_config_funcs; + drm->mode_config.helper_private = &malidp_mode_config_helpers; + + ret = malidp_crtc_init(drm); + if (ret) + goto crtc_fail; + + ret = malidp_mw_connector_init(drm); + if (ret) + goto crtc_fail; + + return 0; + +crtc_fail: + drm_mode_config_cleanup(drm); + return ret; +} + +static void malidp_fini(struct drm_device *drm) +{ + drm_mode_config_cleanup(drm); +} + +static int malidp_irq_init(struct platform_device *pdev) +{ + int irq_de, irq_se, ret = 0; + struct drm_device *drm = dev_get_drvdata(&pdev->dev); + struct malidp_drm *malidp = drm->dev_private; + struct malidp_hw_device *hwdev = malidp->dev; + + /* fetch the interrupts from DT */ + irq_de = platform_get_irq_byname(pdev, "DE"); + if (irq_de < 0) { + DRM_ERROR("no 'DE' IRQ specified!\n"); + return irq_de; + } + irq_se = platform_get_irq_byname(pdev, "SE"); + if (irq_se < 0) { + DRM_ERROR("no 'SE' IRQ specified!\n"); + return irq_se; + } + + ret = malidp_de_irq_init(drm, irq_de); + if (ret) + return ret; + + ret = malidp_se_irq_init(drm, irq_se); + if (ret) { + malidp_de_irq_fini(hwdev); + return ret; + } + + return 0; +} + +DEFINE_DRM_GEM_CMA_FOPS(fops); + +static int malidp_dumb_create(struct drm_file *file_priv, + struct drm_device *drm, + struct drm_mode_create_dumb *args) +{ + struct malidp_drm *malidp = drm->dev_private; + /* allocate for the worst case scenario, i.e. rotated buffers */ + u8 alignment = malidp_hw_get_pitch_align(malidp->dev, 1); + + args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), alignment); + + return drm_gem_cma_dumb_create_internal(file_priv, drm, args); +} + +#ifdef CONFIG_DEBUG_FS + +static void malidp_error_stats_init(struct malidp_error_stats *error_stats) +{ + error_stats->num_errors = 0; + error_stats->last_error_status = 0; + error_stats->last_error_vblank = -1; +} + +void malidp_error(struct malidp_drm *malidp, + struct malidp_error_stats *error_stats, u32 status, + u64 vblank) +{ + unsigned long irqflags; + + spin_lock_irqsave(&malidp->errors_lock, irqflags); + error_stats->last_error_status = status; + error_stats->last_error_vblank = vblank; + error_stats->num_errors++; + spin_unlock_irqrestore(&malidp->errors_lock, irqflags); +} + +void malidp_error_stats_dump(const char *prefix, + struct malidp_error_stats error_stats, + struct seq_file *m) +{ + seq_printf(m, "[%s] num_errors : %d\n", prefix, + error_stats.num_errors); + seq_printf(m, "[%s] last_error_status : 0x%08x\n", prefix, + error_stats.last_error_status); + seq_printf(m, "[%s] last_error_vblank : %lld\n", prefix, + error_stats.last_error_vblank); +} + +static int malidp_show_stats(struct seq_file *m, void *arg) +{ + struct drm_device *drm = m->private; + struct malidp_drm *malidp = drm->dev_private; + unsigned long irqflags; + struct malidp_error_stats de_errors, se_errors; + + spin_lock_irqsave(&malidp->errors_lock, irqflags); + de_errors = malidp->de_errors; + se_errors = malidp->se_errors; + spin_unlock_irqrestore(&malidp->errors_lock, irqflags); + malidp_error_stats_dump("DE", de_errors, m); + malidp_error_stats_dump("SE", se_errors, m); + return 0; +} + +static int malidp_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, malidp_show_stats, inode->i_private); +} + +static ssize_t malidp_debugfs_write(struct file *file, const char __user *ubuf, + size_t len, loff_t *offp) +{ + struct seq_file *m = file->private_data; + struct drm_device *drm = m->private; + struct malidp_drm *malidp = drm->dev_private; + unsigned long irqflags; + + spin_lock_irqsave(&malidp->errors_lock, irqflags); + malidp_error_stats_init(&malidp->de_errors); + malidp_error_stats_init(&malidp->se_errors); + spin_unlock_irqrestore(&malidp->errors_lock, irqflags); + return len; +} + +static const struct file_operations malidp_debugfs_fops = { + .owner = THIS_MODULE, + .open = malidp_debugfs_open, + .read = seq_read, + .write = malidp_debugfs_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int malidp_debugfs_init(struct drm_minor *minor) +{ + struct malidp_drm *malidp = minor->dev->dev_private; + struct dentry *dentry = NULL; + + malidp_error_stats_init(&malidp->de_errors); + malidp_error_stats_init(&malidp->se_errors); + spin_lock_init(&malidp->errors_lock); + dentry = debugfs_create_file("debug", + S_IRUGO | S_IWUSR, + minor->debugfs_root, minor->dev, + &malidp_debugfs_fops); + if (!dentry) { + DRM_ERROR("Cannot create debug file\n"); + return -ENOMEM; + } + return 0; +} + +#endif //CONFIG_DEBUG_FS + +static struct drm_driver malidp_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC | + DRIVER_PRIME, + .lastclose = drm_fb_helper_lastclose, + .gem_free_object_unlocked = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = malidp_dumb_create, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .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, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = malidp_debugfs_init, +#endif + .fops = &fops, + .name = "mali-dp", + .desc = "ARM Mali Display Processor driver", + .date = "20160106", + .major = 1, + .minor = 0, +}; + +static const struct of_device_id malidp_drm_of_match[] = { + { + .compatible = "arm,mali-dp500", + .data = &malidp_device[MALIDP_500] + }, + { + .compatible = "arm,mali-dp550", + .data = &malidp_device[MALIDP_550] + }, + { + .compatible = "arm,mali-dp650", + .data = &malidp_device[MALIDP_650] + }, + {}, +}; +MODULE_DEVICE_TABLE(of, malidp_drm_of_match); + +static bool malidp_is_compatible_hw_id(struct malidp_hw_device *hwdev, + const struct of_device_id *dev_id) +{ + u32 core_id; + const char *compatstr_dp500 = "arm,mali-dp500"; + bool is_dp500; + bool dt_is_dp500; + + /* + * The DP500 CORE_ID register is in a different location, so check it + * first. If the product id field matches, then this is DP500, otherwise + * check the DP550/650 CORE_ID register. + */ + core_id = malidp_hw_read(hwdev, MALIDP500_DC_BASE + MALIDP_DE_CORE_ID); + /* Offset 0x18 will never read 0x500 on products other than DP500. */ + is_dp500 = (MALIDP_PRODUCT_ID(core_id) == 0x500); + dt_is_dp500 = strnstr(dev_id->compatible, compatstr_dp500, + sizeof(dev_id->compatible)) != NULL; + if (is_dp500 != dt_is_dp500) { + DRM_ERROR("Device-tree expects %s, but hardware %s DP500.\n", + dev_id->compatible, is_dp500 ? "is" : "is not"); + return false; + } else if (!dt_is_dp500) { + u16 product_id; + char buf[32]; + + core_id = malidp_hw_read(hwdev, + MALIDP550_DC_BASE + MALIDP_DE_CORE_ID); + product_id = MALIDP_PRODUCT_ID(core_id); + snprintf(buf, sizeof(buf), "arm,mali-dp%X", product_id); + if (!strnstr(dev_id->compatible, buf, + sizeof(dev_id->compatible))) { + DRM_ERROR("Device-tree expects %s, but hardware is DP%03X.\n", + dev_id->compatible, product_id); + return false; + } + } + return true; +} + +static bool malidp_has_sufficient_address_space(const struct resource *res, + const struct of_device_id *dev_id) +{ + resource_size_t res_size = resource_size(res); + const char *compatstr_dp500 = "arm,mali-dp500"; + + if (!strnstr(dev_id->compatible, compatstr_dp500, + sizeof(dev_id->compatible))) + return res_size >= MALIDP550_ADDR_SPACE_SIZE; + else if (res_size < MALIDP500_ADDR_SPACE_SIZE) + return false; + return true; +} + +static ssize_t core_id_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct malidp_drm *malidp = drm->dev_private; + + return snprintf(buf, PAGE_SIZE, "%08x\n", malidp->core_id); +} + +DEVICE_ATTR_RO(core_id); + +static int malidp_init_sysfs(struct device *dev) +{ + int ret = device_create_file(dev, &dev_attr_core_id); + + if (ret) + DRM_ERROR("failed to create device file for core_id\n"); + + return ret; +} + +static void malidp_fini_sysfs(struct device *dev) +{ + device_remove_file(dev, &dev_attr_core_id); +} + +#define MAX_OUTPUT_CHANNELS 3 + +static int malidp_runtime_pm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct malidp_drm *malidp = drm->dev_private; + struct malidp_hw_device *hwdev = malidp->dev; + + /* we can only suspend if the hardware is in config mode */ + WARN_ON(!hwdev->hw->in_config_mode(hwdev)); + + malidp_se_irq_fini(hwdev); + malidp_de_irq_fini(hwdev); + hwdev->pm_suspended = true; + clk_disable_unprepare(hwdev->mclk); + clk_disable_unprepare(hwdev->aclk); + clk_disable_unprepare(hwdev->pclk); + + return 0; +} + +static int malidp_runtime_pm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct malidp_drm *malidp = drm->dev_private; + struct malidp_hw_device *hwdev = malidp->dev; + + clk_prepare_enable(hwdev->pclk); + clk_prepare_enable(hwdev->aclk); + clk_prepare_enable(hwdev->mclk); + hwdev->pm_suspended = false; + malidp_de_irq_hw_init(hwdev); + malidp_se_irq_hw_init(hwdev); + + return 0; +} + +static int malidp_bind(struct device *dev) +{ + struct resource *res; + struct drm_device *drm; + struct malidp_drm *malidp; + struct malidp_hw_device *hwdev; + struct platform_device *pdev = to_platform_device(dev); + struct of_device_id const *dev_id; + struct drm_encoder *encoder; + /* number of lines for the R, G and B output */ + u8 output_width[MAX_OUTPUT_CHANNELS]; + int ret = 0, i; + u32 version, out_depth = 0; + + malidp = devm_kzalloc(dev, sizeof(*malidp), GFP_KERNEL); + if (!malidp) + return -ENOMEM; + + hwdev = devm_kzalloc(dev, sizeof(*hwdev), GFP_KERNEL); + if (!hwdev) + return -ENOMEM; + + hwdev->hw = (struct malidp_hw *)of_device_get_match_data(dev); + malidp->dev = hwdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hwdev->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(hwdev->regs)) + return PTR_ERR(hwdev->regs); + + hwdev->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(hwdev->pclk)) + return PTR_ERR(hwdev->pclk); + + hwdev->aclk = devm_clk_get(dev, "aclk"); + if (IS_ERR(hwdev->aclk)) + return PTR_ERR(hwdev->aclk); + + hwdev->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(hwdev->mclk)) + return PTR_ERR(hwdev->mclk); + + hwdev->pxlclk = devm_clk_get(dev, "pxlclk"); + if (IS_ERR(hwdev->pxlclk)) + return PTR_ERR(hwdev->pxlclk); + + /* Get the optional framebuffer memory resource */ + ret = of_reserved_mem_device_init(dev); + if (ret && ret != -ENODEV) + return ret; + + drm = drm_dev_alloc(&malidp_driver, dev); + if (IS_ERR(drm)) { + ret = PTR_ERR(drm); + goto alloc_fail; + } + + drm->dev_private = malidp; + dev_set_drvdata(dev, drm); + + /* Enable power management */ + pm_runtime_enable(dev); + + /* Resume device to enable the clocks */ + if (pm_runtime_enabled(dev)) + pm_runtime_get_sync(dev); + else + malidp_runtime_pm_resume(dev); + + dev_id = of_match_device(malidp_drm_of_match, dev); + if (!dev_id) { + ret = -EINVAL; + goto query_hw_fail; + } + + if (!malidp_has_sufficient_address_space(res, dev_id)) { + DRM_ERROR("Insufficient address space in device-tree.\n"); + ret = -EINVAL; + goto query_hw_fail; + } + + if (!malidp_is_compatible_hw_id(hwdev, dev_id)) { + ret = -EINVAL; + goto query_hw_fail; + } + + ret = hwdev->hw->query_hw(hwdev); + if (ret) { + DRM_ERROR("Invalid HW configuration\n"); + goto query_hw_fail; + } + + version = malidp_hw_read(hwdev, hwdev->hw->map.dc_base + MALIDP_DE_CORE_ID); + DRM_INFO("found ARM Mali-DP%3x version r%dp%d\n", version >> 16, + (version >> 12) & 0xf, (version >> 8) & 0xf); + + malidp->core_id = version; + + /* set the number of lines used for output of RGB data */ + ret = of_property_read_u8_array(dev->of_node, + "arm,malidp-output-port-lines", + output_width, MAX_OUTPUT_CHANNELS); + if (ret) + goto query_hw_fail; + + for (i = 0; i < MAX_OUTPUT_CHANNELS; i++) + out_depth = (out_depth << 8) | (output_width[i] & 0xf); + malidp_hw_write(hwdev, out_depth, hwdev->hw->map.out_depth_base); + hwdev->output_color_depth = out_depth; + + atomic_set(&malidp->config_valid, MALIDP_CONFIG_VALID_INIT); + init_waitqueue_head(&malidp->wq); + + ret = malidp_init(drm); + if (ret < 0) + goto query_hw_fail; + + ret = malidp_init_sysfs(dev); + if (ret) + goto init_fail; + + /* Set the CRTC's port so that the encoder component can find it */ + malidp->crtc.port = of_graph_get_port_by_id(dev->of_node, 0); + + ret = component_bind_all(dev, drm); + if (ret) { + DRM_ERROR("Failed to bind all components\n"); + goto bind_fail; + } + + /* We expect to have a maximum of two encoders one for the actual + * display and a virtual one for the writeback connector + */ + WARN_ON(drm->mode_config.num_encoder > 2); + list_for_each_entry(encoder, &drm->mode_config.encoder_list, head) { + encoder->possible_clones = + (1 << drm->mode_config.num_encoder) - 1; + } + + ret = malidp_irq_init(pdev); + if (ret < 0) + goto irq_init_fail; + + drm->irq_enabled = true; + + ret = drm_vblank_init(drm, drm->mode_config.num_crtc); + drm_crtc_vblank_reset(&malidp->crtc); + if (ret < 0) { + DRM_ERROR("failed to initialise vblank\n"); + goto vblank_fail; + } + pm_runtime_put(dev); + + drm_mode_config_reset(drm); + + ret = drm_fb_cma_fbdev_init(drm, 32, 0); + if (ret) + goto fbdev_fail; + + drm_kms_helper_poll_init(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + goto register_fail; + + return 0; + +register_fail: + drm_fb_cma_fbdev_fini(drm); + drm_kms_helper_poll_fini(drm); +fbdev_fail: + pm_runtime_get_sync(dev); +vblank_fail: + malidp_se_irq_fini(hwdev); + malidp_de_irq_fini(hwdev); + drm->irq_enabled = false; +irq_init_fail: + drm_atomic_helper_shutdown(drm); + component_unbind_all(dev, drm); +bind_fail: + of_node_put(malidp->crtc.port); + malidp->crtc.port = NULL; +init_fail: + malidp_fini_sysfs(dev); + malidp_fini(drm); +query_hw_fail: + pm_runtime_put(dev); + if (pm_runtime_enabled(dev)) + pm_runtime_disable(dev); + else + malidp_runtime_pm_suspend(dev); + drm->dev_private = NULL; + dev_set_drvdata(dev, NULL); + drm_dev_put(drm); +alloc_fail: + of_reserved_mem_device_release(dev); + + return ret; +} + +static void malidp_unbind(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct malidp_drm *malidp = drm->dev_private; + struct malidp_hw_device *hwdev = malidp->dev; + + drm_dev_unregister(drm); + drm_fb_cma_fbdev_fini(drm); + drm_kms_helper_poll_fini(drm); + pm_runtime_get_sync(dev); + drm_crtc_vblank_off(&malidp->crtc); + malidp_se_irq_fini(hwdev); + malidp_de_irq_fini(hwdev); + drm->irq_enabled = false; + drm_atomic_helper_shutdown(drm); + component_unbind_all(dev, drm); + of_node_put(malidp->crtc.port); + malidp->crtc.port = NULL; + malidp_fini_sysfs(dev); + malidp_fini(drm); + pm_runtime_put(dev); + if (pm_runtime_enabled(dev)) + pm_runtime_disable(dev); + else + malidp_runtime_pm_suspend(dev); + drm->dev_private = NULL; + dev_set_drvdata(dev, NULL); + drm_dev_put(drm); + of_reserved_mem_device_release(dev); +} + +static const struct component_master_ops malidp_master_ops = { + .bind = malidp_bind, + .unbind = malidp_unbind, +}; + +static int malidp_compare_dev(struct device *dev, void *data) +{ + struct device_node *np = data; + + return dev->of_node == np; +} + +static int malidp_platform_probe(struct platform_device *pdev) +{ + struct device_node *port; + struct component_match *match = NULL; + + if (!pdev->dev.of_node) + return -ENODEV; + + /* there is only one output port inside each device, find it */ + port = of_graph_get_remote_node(pdev->dev.of_node, 0, 0); + if (!port) + return -ENODEV; + + drm_of_component_match_add(&pdev->dev, &match, malidp_compare_dev, + port); + of_node_put(port); + return component_master_add_with_match(&pdev->dev, &malidp_master_ops, + match); +} + +static int malidp_platform_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &malidp_master_ops); + return 0; +} + +static int __maybe_unused malidp_pm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(drm); +} + +static int __maybe_unused malidp_pm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + drm_mode_config_helper_resume(drm); + + return 0; +} + +static int __maybe_unused malidp_pm_suspend_late(struct device *dev) +{ + if (!pm_runtime_status_suspended(dev)) { + malidp_runtime_pm_suspend(dev); + pm_runtime_set_suspended(dev); + } + return 0; +} + +static int __maybe_unused malidp_pm_resume_early(struct device *dev) +{ + malidp_runtime_pm_resume(dev); + pm_runtime_set_active(dev); + return 0; +} + +static const struct dev_pm_ops malidp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(malidp_pm_suspend, malidp_pm_resume) \ + SET_LATE_SYSTEM_SLEEP_PM_OPS(malidp_pm_suspend_late, malidp_pm_resume_early) \ + SET_RUNTIME_PM_OPS(malidp_runtime_pm_suspend, malidp_runtime_pm_resume, NULL) +}; + +static struct platform_driver malidp_platform_driver = { + .probe = malidp_platform_probe, + .remove = malidp_platform_remove, + .driver = { + .name = "mali-dp", + .pm = &malidp_pm_ops, + .of_match_table = malidp_drm_of_match, + }, +}; + +module_platform_driver(malidp_platform_driver); + +MODULE_AUTHOR("Liviu Dudau <Liviu.Dudau@arm.com>"); +MODULE_DESCRIPTION("ARM Mali DP DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/arm/malidp_drv.h b/drivers/gpu/drm/arm/malidp_drv.h new file mode 100644 index 000000000..e3eb0cb1f --- /dev/null +++ b/drivers/gpu/drm/arm/malidp_drv.h @@ -0,0 +1,94 @@ +/* + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * ARM Mali DP500/DP550/DP650 KMS/DRM driver structures + */ + +#ifndef __MALIDP_DRV_H__ +#define __MALIDP_DRV_H__ + +#include <drm/drm_writeback.h> +#include <drm/drm_encoder.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/spinlock.h> +#include <drm/drmP.h> +#include "malidp_hw.h" + +#define MALIDP_CONFIG_VALID_INIT 0 +#define MALIDP_CONFIG_VALID_DONE 1 +#define MALIDP_CONFIG_START 0xd0 + +struct malidp_error_stats { + s32 num_errors; + u32 last_error_status; + s64 last_error_vblank; +}; + +struct malidp_drm { + struct malidp_hw_device *dev; + struct drm_crtc crtc; + struct drm_writeback_connector mw_connector; + wait_queue_head_t wq; + struct drm_pending_vblank_event *event; + atomic_t config_valid; + u32 core_id; +#ifdef CONFIG_DEBUG_FS + struct malidp_error_stats de_errors; + struct malidp_error_stats se_errors; + /* Protects errors stats */ + spinlock_t errors_lock; +#endif +}; + +#define crtc_to_malidp_device(x) container_of(x, struct malidp_drm, crtc) + +struct malidp_plane { + struct drm_plane base; + struct malidp_hw_device *hwdev; + const struct malidp_layer *layer; +}; + +struct malidp_plane_state { + struct drm_plane_state base; + + /* size of the required rotation memory if plane is rotated */ + u32 rotmem_size; + /* internal format ID */ + u8 format; + u8 n_planes; +}; + +#define to_malidp_plane(x) container_of(x, struct malidp_plane, base) +#define to_malidp_plane_state(x) container_of(x, struct malidp_plane_state, base) + +struct malidp_crtc_state { + struct drm_crtc_state base; + u32 gamma_coeffs[MALIDP_COEFFTAB_NUM_COEFFS]; + u32 coloradj_coeffs[MALIDP_COLORADJ_NUM_COEFFS]; + struct malidp_se_config scaler_config; + /* Bitfield of all the planes that have requested a scaled output. */ + u8 scaled_planes_mask; +}; + +#define to_malidp_crtc_state(x) container_of(x, struct malidp_crtc_state, base) + +int malidp_de_planes_init(struct drm_device *drm); +int malidp_crtc_init(struct drm_device *drm); + +#ifdef CONFIG_DEBUG_FS +void malidp_error(struct malidp_drm *malidp, + struct malidp_error_stats *error_stats, u32 status, + u64 vblank); +#endif + +/* often used combination of rotational bits */ +#define MALIDP_ROTATED_MASK (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270) + +#endif /* __MALIDP_DRV_H__ */ diff --git a/drivers/gpu/drm/arm/malidp_hw.c b/drivers/gpu/drm/arm/malidp_hw.c new file mode 100644 index 000000000..2781e462c --- /dev/null +++ b/drivers/gpu/drm/arm/malidp_hw.c @@ -0,0 +1,1151 @@ +/* + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * ARM Mali DP500/DP550/DP650 hardware manipulation routines. This is where + * the difference between various versions of the hardware is being dealt with + * in an attempt to provide to the rest of the driver code a unified view + */ + +#include <linux/clk.h> +#include <linux/types.h> +#include <linux/io.h> +#include <drm/drmP.h> +#include <video/videomode.h> +#include <video/display_timing.h> + +#include "malidp_drv.h" +#include "malidp_hw.h" +#include "malidp_mw.h" + +enum { + MW_NOT_ENABLED = 0, /* SE writeback not enabled */ + MW_ONESHOT, /* SE in one-shot mode for writeback */ + MW_START, /* SE started writeback */ + MW_RESTART, /* SE will start another writeback after this one */ + MW_STOP, /* SE needs to stop after this writeback */ +}; + +static const struct malidp_format_id malidp500_de_formats[] = { + /* fourcc, layers supporting the format, internal id */ + { DRM_FORMAT_ARGB2101010, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2 | SE_MEMWRITE, 0 }, + { DRM_FORMAT_ABGR2101010, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2 | SE_MEMWRITE, 1 }, + { DRM_FORMAT_ARGB8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2, 2 }, + { DRM_FORMAT_ABGR8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2, 3 }, + { DRM_FORMAT_XRGB8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2 | SE_MEMWRITE, 4 }, + { DRM_FORMAT_XBGR8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2 | SE_MEMWRITE, 5 }, + { DRM_FORMAT_RGB888, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2, 6 }, + { DRM_FORMAT_BGR888, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2, 7 }, + { DRM_FORMAT_RGBA5551, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2, 8 }, + { DRM_FORMAT_ABGR1555, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2, 9 }, + { DRM_FORMAT_RGB565, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2, 10 }, + { DRM_FORMAT_BGR565, DE_VIDEO1 | DE_GRAPHICS1 | DE_GRAPHICS2, 11 }, + { DRM_FORMAT_UYVY, DE_VIDEO1, 12 }, + { DRM_FORMAT_YUYV, DE_VIDEO1, 13 }, + { DRM_FORMAT_NV12, DE_VIDEO1 | SE_MEMWRITE, 14 }, + { DRM_FORMAT_YUV420, DE_VIDEO1, 15 }, +}; + +#define MALIDP_ID(__group, __format) \ + ((((__group) & 0x7) << 3) | ((__format) & 0x7)) + +#define MALIDP_COMMON_FORMATS \ + /* fourcc, layers supporting the format, internal id */ \ + { DRM_FORMAT_ARGB2101010, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | SE_MEMWRITE, MALIDP_ID(0, 0) }, \ + { DRM_FORMAT_ABGR2101010, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | SE_MEMWRITE, MALIDP_ID(0, 1) }, \ + { DRM_FORMAT_RGBA1010102, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | SE_MEMWRITE, MALIDP_ID(0, 2) }, \ + { DRM_FORMAT_BGRA1010102, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | SE_MEMWRITE, MALIDP_ID(0, 3) }, \ + { DRM_FORMAT_ARGB8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | DE_SMART, MALIDP_ID(1, 0) }, \ + { DRM_FORMAT_ABGR8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | DE_SMART, MALIDP_ID(1, 1) }, \ + { DRM_FORMAT_RGBA8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | DE_SMART, MALIDP_ID(1, 2) }, \ + { DRM_FORMAT_BGRA8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | DE_SMART, MALIDP_ID(1, 3) }, \ + { DRM_FORMAT_XRGB8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | DE_SMART | SE_MEMWRITE, MALIDP_ID(2, 0) }, \ + { DRM_FORMAT_XBGR8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | DE_SMART | SE_MEMWRITE, MALIDP_ID(2, 1) }, \ + { DRM_FORMAT_RGBX8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | DE_SMART | SE_MEMWRITE, MALIDP_ID(2, 2) }, \ + { DRM_FORMAT_BGRX8888, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | DE_SMART | SE_MEMWRITE, MALIDP_ID(2, 3) }, \ + { DRM_FORMAT_RGB888, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | SE_MEMWRITE, MALIDP_ID(3, 0) }, \ + { DRM_FORMAT_BGR888, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2 | SE_MEMWRITE, MALIDP_ID(3, 1) }, \ + { DRM_FORMAT_RGBA5551, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2, MALIDP_ID(4, 0) }, \ + { DRM_FORMAT_ABGR1555, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2, MALIDP_ID(4, 1) }, \ + { DRM_FORMAT_RGB565, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2, MALIDP_ID(4, 2) }, \ + { DRM_FORMAT_BGR565, DE_VIDEO1 | DE_GRAPHICS1 | DE_VIDEO2, MALIDP_ID(4, 3) }, \ + { DRM_FORMAT_YUYV, DE_VIDEO1 | DE_VIDEO2, MALIDP_ID(5, 2) }, \ + { DRM_FORMAT_UYVY, DE_VIDEO1 | DE_VIDEO2, MALIDP_ID(5, 3) }, \ + { DRM_FORMAT_NV12, DE_VIDEO1 | DE_VIDEO2 | SE_MEMWRITE, MALIDP_ID(5, 6) }, \ + { DRM_FORMAT_YUV420, DE_VIDEO1 | DE_VIDEO2, MALIDP_ID(5, 7) } + +static const struct malidp_format_id malidp550_de_formats[] = { + MALIDP_COMMON_FORMATS, +}; + +static const struct malidp_layer malidp500_layers[] = { + { DE_VIDEO1, MALIDP500_DE_LV_BASE, MALIDP500_DE_LV_PTR_BASE, MALIDP_DE_LV_STRIDE0, MALIDP500_LV_YUV2RGB }, + { DE_GRAPHICS1, MALIDP500_DE_LG1_BASE, MALIDP500_DE_LG1_PTR_BASE, MALIDP_DE_LG_STRIDE, 0 }, + { DE_GRAPHICS2, MALIDP500_DE_LG2_BASE, MALIDP500_DE_LG2_PTR_BASE, MALIDP_DE_LG_STRIDE, 0 }, +}; + +static const struct malidp_layer malidp550_layers[] = { + { DE_VIDEO1, MALIDP550_DE_LV1_BASE, MALIDP550_DE_LV1_PTR_BASE, MALIDP_DE_LV_STRIDE0, MALIDP550_LV_YUV2RGB }, + { DE_GRAPHICS1, MALIDP550_DE_LG_BASE, MALIDP550_DE_LG_PTR_BASE, MALIDP_DE_LG_STRIDE, 0 }, + { DE_VIDEO2, MALIDP550_DE_LV2_BASE, MALIDP550_DE_LV2_PTR_BASE, MALIDP_DE_LV_STRIDE0, MALIDP550_LV_YUV2RGB }, + { DE_SMART, MALIDP550_DE_LS_BASE, MALIDP550_DE_LS_PTR_BASE, MALIDP550_DE_LS_R1_STRIDE, 0 }, +}; + +#define SE_N_SCALING_COEFFS 96 +static const u16 dp500_se_scaling_coeffs[][SE_N_SCALING_COEFFS] = { + [MALIDP_UPSCALING_COEFFS - 1] = { + 0x0000, 0x0001, 0x0007, 0x0011, 0x001e, 0x002e, 0x003f, 0x0052, + 0x0064, 0x0073, 0x007d, 0x0080, 0x007a, 0x006c, 0x0053, 0x002f, + 0x0000, 0x3fc6, 0x3f83, 0x3f39, 0x3eea, 0x3e9b, 0x3e4f, 0x3e0a, + 0x3dd4, 0x3db0, 0x3da2, 0x3db1, 0x3dde, 0x3e2f, 0x3ea5, 0x3f40, + 0x0000, 0x00e5, 0x01ee, 0x0315, 0x0456, 0x05aa, 0x0709, 0x086c, + 0x09c9, 0x0b15, 0x0c4a, 0x0d5d, 0x0e4a, 0x0f06, 0x0f91, 0x0fe5, + 0x1000, 0x0fe5, 0x0f91, 0x0f06, 0x0e4a, 0x0d5d, 0x0c4a, 0x0b15, + 0x09c9, 0x086c, 0x0709, 0x05aa, 0x0456, 0x0315, 0x01ee, 0x00e5, + 0x0000, 0x3f40, 0x3ea5, 0x3e2f, 0x3dde, 0x3db1, 0x3da2, 0x3db0, + 0x3dd4, 0x3e0a, 0x3e4f, 0x3e9b, 0x3eea, 0x3f39, 0x3f83, 0x3fc6, + 0x0000, 0x002f, 0x0053, 0x006c, 0x007a, 0x0080, 0x007d, 0x0073, + 0x0064, 0x0052, 0x003f, 0x002e, 0x001e, 0x0011, 0x0007, 0x0001 + }, + [MALIDP_DOWNSCALING_1_5_COEFFS - 1] = { + 0x0059, 0x004f, 0x0041, 0x002e, 0x0016, 0x3ffb, 0x3fd9, 0x3fb4, + 0x3f8c, 0x3f62, 0x3f36, 0x3f09, 0x3edd, 0x3eb3, 0x3e8d, 0x3e6c, + 0x3e52, 0x3e3f, 0x3e35, 0x3e37, 0x3e46, 0x3e61, 0x3e8c, 0x3ec5, + 0x3f0f, 0x3f68, 0x3fd1, 0x004a, 0x00d3, 0x0169, 0x020b, 0x02b8, + 0x036e, 0x042d, 0x04f2, 0x05b9, 0x0681, 0x0745, 0x0803, 0x08ba, + 0x0965, 0x0a03, 0x0a91, 0x0b0d, 0x0b75, 0x0bc6, 0x0c00, 0x0c20, + 0x0c28, 0x0c20, 0x0c00, 0x0bc6, 0x0b75, 0x0b0d, 0x0a91, 0x0a03, + 0x0965, 0x08ba, 0x0803, 0x0745, 0x0681, 0x05b9, 0x04f2, 0x042d, + 0x036e, 0x02b8, 0x020b, 0x0169, 0x00d3, 0x004a, 0x3fd1, 0x3f68, + 0x3f0f, 0x3ec5, 0x3e8c, 0x3e61, 0x3e46, 0x3e37, 0x3e35, 0x3e3f, + 0x3e52, 0x3e6c, 0x3e8d, 0x3eb3, 0x3edd, 0x3f09, 0x3f36, 0x3f62, + 0x3f8c, 0x3fb4, 0x3fd9, 0x3ffb, 0x0016, 0x002e, 0x0041, 0x004f + }, + [MALIDP_DOWNSCALING_2_COEFFS - 1] = { + 0x3f19, 0x3f03, 0x3ef0, 0x3edf, 0x3ed0, 0x3ec5, 0x3ebd, 0x3eb9, + 0x3eb9, 0x3ebf, 0x3eca, 0x3ed9, 0x3eef, 0x3f0a, 0x3f2c, 0x3f52, + 0x3f7f, 0x3fb0, 0x3fe8, 0x0026, 0x006a, 0x00b4, 0x0103, 0x0158, + 0x01b1, 0x020d, 0x026c, 0x02cd, 0x032f, 0x0392, 0x03f4, 0x0455, + 0x04b4, 0x051e, 0x0585, 0x05eb, 0x064c, 0x06a8, 0x06fe, 0x074e, + 0x0796, 0x07d5, 0x080c, 0x0839, 0x085c, 0x0875, 0x0882, 0x0887, + 0x0881, 0x0887, 0x0882, 0x0875, 0x085c, 0x0839, 0x080c, 0x07d5, + 0x0796, 0x074e, 0x06fe, 0x06a8, 0x064c, 0x05eb, 0x0585, 0x051e, + 0x04b4, 0x0455, 0x03f4, 0x0392, 0x032f, 0x02cd, 0x026c, 0x020d, + 0x01b1, 0x0158, 0x0103, 0x00b4, 0x006a, 0x0026, 0x3fe8, 0x3fb0, + 0x3f7f, 0x3f52, 0x3f2c, 0x3f0a, 0x3eef, 0x3ed9, 0x3eca, 0x3ebf, + 0x3eb9, 0x3eb9, 0x3ebd, 0x3ec5, 0x3ed0, 0x3edf, 0x3ef0, 0x3f03 + }, + [MALIDP_DOWNSCALING_2_75_COEFFS - 1] = { + 0x3f51, 0x3f60, 0x3f71, 0x3f84, 0x3f98, 0x3faf, 0x3fc8, 0x3fe3, + 0x0000, 0x001f, 0x0040, 0x0064, 0x008a, 0x00b1, 0x00da, 0x0106, + 0x0133, 0x0160, 0x018e, 0x01bd, 0x01ec, 0x021d, 0x024e, 0x0280, + 0x02b2, 0x02e4, 0x0317, 0x0349, 0x037c, 0x03ad, 0x03df, 0x0410, + 0x0440, 0x0468, 0x048f, 0x04b3, 0x04d6, 0x04f8, 0x0516, 0x0533, + 0x054e, 0x0566, 0x057c, 0x0590, 0x05a0, 0x05ae, 0x05ba, 0x05c3, + 0x05c9, 0x05c3, 0x05ba, 0x05ae, 0x05a0, 0x0590, 0x057c, 0x0566, + 0x054e, 0x0533, 0x0516, 0x04f8, 0x04d6, 0x04b3, 0x048f, 0x0468, + 0x0440, 0x0410, 0x03df, 0x03ad, 0x037c, 0x0349, 0x0317, 0x02e4, + 0x02b2, 0x0280, 0x024e, 0x021d, 0x01ec, 0x01bd, 0x018e, 0x0160, + 0x0133, 0x0106, 0x00da, 0x00b1, 0x008a, 0x0064, 0x0040, 0x001f, + 0x0000, 0x3fe3, 0x3fc8, 0x3faf, 0x3f98, 0x3f84, 0x3f71, 0x3f60 + }, + [MALIDP_DOWNSCALING_4_COEFFS - 1] = { + 0x0094, 0x00a9, 0x00be, 0x00d4, 0x00ea, 0x0101, 0x0118, 0x012f, + 0x0148, 0x0160, 0x017a, 0x0193, 0x01ae, 0x01c8, 0x01e4, 0x01ff, + 0x021c, 0x0233, 0x024a, 0x0261, 0x0278, 0x028f, 0x02a6, 0x02bd, + 0x02d4, 0x02eb, 0x0302, 0x0319, 0x032f, 0x0346, 0x035d, 0x0374, + 0x038a, 0x0397, 0x03a3, 0x03af, 0x03bb, 0x03c6, 0x03d1, 0x03db, + 0x03e4, 0x03ed, 0x03f6, 0x03fe, 0x0406, 0x040d, 0x0414, 0x041a, + 0x0420, 0x041a, 0x0414, 0x040d, 0x0406, 0x03fe, 0x03f6, 0x03ed, + 0x03e4, 0x03db, 0x03d1, 0x03c6, 0x03bb, 0x03af, 0x03a3, 0x0397, + 0x038a, 0x0374, 0x035d, 0x0346, 0x032f, 0x0319, 0x0302, 0x02eb, + 0x02d4, 0x02bd, 0x02a6, 0x028f, 0x0278, 0x0261, 0x024a, 0x0233, + 0x021c, 0x01ff, 0x01e4, 0x01c8, 0x01ae, 0x0193, 0x017a, 0x0160, + 0x0148, 0x012f, 0x0118, 0x0101, 0x00ea, 0x00d4, 0x00be, 0x00a9 + }, +}; + +#define MALIDP_DE_DEFAULT_PREFETCH_START 5 + +static int malidp500_query_hw(struct malidp_hw_device *hwdev) +{ + u32 conf = malidp_hw_read(hwdev, MALIDP500_CONFIG_ID); + /* bit 4 of the CONFIG_ID register holds the line size multiplier */ + u8 ln_size_mult = conf & 0x10 ? 2 : 1; + + hwdev->min_line_size = 2; + hwdev->max_line_size = SZ_2K * ln_size_mult; + hwdev->rotation_memory[0] = SZ_1K * 64 * ln_size_mult; + hwdev->rotation_memory[1] = 0; /* no second rotation memory bank */ + + return 0; +} + +static void malidp500_enter_config_mode(struct malidp_hw_device *hwdev) +{ + u32 status, count = 100; + + malidp_hw_setbits(hwdev, MALIDP500_DC_CONFIG_REQ, MALIDP500_DC_CONTROL); + while (count) { + status = malidp_hw_read(hwdev, hwdev->hw->map.dc_base + MALIDP_REG_STATUS); + if ((status & MALIDP500_DC_CONFIG_REQ) == MALIDP500_DC_CONFIG_REQ) + break; + /* + * entering config mode can take as long as the rendering + * of a full frame, hence the long sleep here + */ + usleep_range(1000, 10000); + count--; + } + WARN(count == 0, "timeout while entering config mode"); +} + +static void malidp500_leave_config_mode(struct malidp_hw_device *hwdev) +{ + u32 status, count = 100; + + malidp_hw_clearbits(hwdev, MALIDP_CFG_VALID, MALIDP500_CONFIG_VALID); + malidp_hw_clearbits(hwdev, MALIDP500_DC_CONFIG_REQ, MALIDP500_DC_CONTROL); + while (count) { + status = malidp_hw_read(hwdev, hwdev->hw->map.dc_base + MALIDP_REG_STATUS); + if ((status & MALIDP500_DC_CONFIG_REQ) == 0) + break; + usleep_range(100, 1000); + count--; + } + WARN(count == 0, "timeout while leaving config mode"); +} + +static bool malidp500_in_config_mode(struct malidp_hw_device *hwdev) +{ + u32 status; + + status = malidp_hw_read(hwdev, hwdev->hw->map.dc_base + MALIDP_REG_STATUS); + if ((status & MALIDP500_DC_CONFIG_REQ) == MALIDP500_DC_CONFIG_REQ) + return true; + + return false; +} + +static void malidp500_set_config_valid(struct malidp_hw_device *hwdev, u8 value) +{ + if (value) + malidp_hw_setbits(hwdev, MALIDP_CFG_VALID, MALIDP500_CONFIG_VALID); + else + malidp_hw_clearbits(hwdev, MALIDP_CFG_VALID, MALIDP500_CONFIG_VALID); +} + +static void malidp500_modeset(struct malidp_hw_device *hwdev, struct videomode *mode) +{ + u32 val = 0; + + malidp_hw_write(hwdev, hwdev->output_color_depth, + hwdev->hw->map.out_depth_base); + malidp_hw_clearbits(hwdev, MALIDP500_DC_CLEAR_MASK, MALIDP500_DC_CONTROL); + if (mode->flags & DISPLAY_FLAGS_HSYNC_HIGH) + val |= MALIDP500_HSYNCPOL; + if (mode->flags & DISPLAY_FLAGS_VSYNC_HIGH) + val |= MALIDP500_VSYNCPOL; + val |= MALIDP_DE_DEFAULT_PREFETCH_START; + malidp_hw_setbits(hwdev, val, MALIDP500_DC_CONTROL); + + /* + * Mali-DP500 encodes the background color like this: + * - red @ MALIDP500_BGND_COLOR[12:0] + * - green @ MALIDP500_BGND_COLOR[27:16] + * - blue @ (MALIDP500_BGND_COLOR + 4)[12:0] + */ + val = ((MALIDP_BGND_COLOR_G & 0xfff) << 16) | + (MALIDP_BGND_COLOR_R & 0xfff); + malidp_hw_write(hwdev, val, MALIDP500_BGND_COLOR); + malidp_hw_write(hwdev, MALIDP_BGND_COLOR_B, MALIDP500_BGND_COLOR + 4); + + val = MALIDP_DE_H_FRONTPORCH(mode->hfront_porch) | + MALIDP_DE_H_BACKPORCH(mode->hback_porch); + malidp_hw_write(hwdev, val, MALIDP500_TIMINGS_BASE + MALIDP_DE_H_TIMINGS); + + val = MALIDP500_DE_V_FRONTPORCH(mode->vfront_porch) | + MALIDP_DE_V_BACKPORCH(mode->vback_porch); + malidp_hw_write(hwdev, val, MALIDP500_TIMINGS_BASE + MALIDP_DE_V_TIMINGS); + + val = MALIDP_DE_H_SYNCWIDTH(mode->hsync_len) | + MALIDP_DE_V_SYNCWIDTH(mode->vsync_len); + malidp_hw_write(hwdev, val, MALIDP500_TIMINGS_BASE + MALIDP_DE_SYNC_WIDTH); + + val = MALIDP_DE_H_ACTIVE(mode->hactive) | MALIDP_DE_V_ACTIVE(mode->vactive); + malidp_hw_write(hwdev, val, MALIDP500_TIMINGS_BASE + MALIDP_DE_HV_ACTIVE); + + if (mode->flags & DISPLAY_FLAGS_INTERLACED) + malidp_hw_setbits(hwdev, MALIDP_DISP_FUNC_ILACED, MALIDP_DE_DISPLAY_FUNC); + else + malidp_hw_clearbits(hwdev, MALIDP_DISP_FUNC_ILACED, MALIDP_DE_DISPLAY_FUNC); +} + +static int malidp500_rotmem_required(struct malidp_hw_device *hwdev, u16 w, u16 h, u32 fmt) +{ + /* RGB888 or BGR888 can't be rotated */ + if ((fmt == DRM_FORMAT_RGB888) || (fmt == DRM_FORMAT_BGR888)) + return -EINVAL; + + /* + * Each layer needs enough rotation memory to fit 8 lines + * worth of pixel data. Required size is then: + * size = rotated_width * (bpp / 8) * 8; + */ + return w * drm_format_plane_cpp(fmt, 0) * 8; +} + +static void malidp500_se_write_pp_coefftab(struct malidp_hw_device *hwdev, + u32 direction, + u16 addr, + u8 coeffs_id) +{ + int i; + u16 scaling_control = MALIDP500_SE_CONTROL + MALIDP_SE_SCALING_CONTROL; + + malidp_hw_write(hwdev, + direction | (addr & MALIDP_SE_COEFFTAB_ADDR_MASK), + scaling_control + MALIDP_SE_COEFFTAB_ADDR); + for (i = 0; i < ARRAY_SIZE(dp500_se_scaling_coeffs); ++i) + malidp_hw_write(hwdev, MALIDP_SE_SET_COEFFTAB_DATA( + dp500_se_scaling_coeffs[coeffs_id][i]), + scaling_control + MALIDP_SE_COEFFTAB_DATA); +} + +static int malidp500_se_set_scaling_coeffs(struct malidp_hw_device *hwdev, + struct malidp_se_config *se_config, + struct malidp_se_config *old_config) +{ + /* Get array indices into dp500_se_scaling_coeffs. */ + u8 h = (u8)se_config->hcoeff - 1; + u8 v = (u8)se_config->vcoeff - 1; + + if (WARN_ON(h >= ARRAY_SIZE(dp500_se_scaling_coeffs) || + v >= ARRAY_SIZE(dp500_se_scaling_coeffs))) + return -EINVAL; + + if ((h == v) && (se_config->hcoeff != old_config->hcoeff || + se_config->vcoeff != old_config->vcoeff)) { + malidp500_se_write_pp_coefftab(hwdev, + (MALIDP_SE_V_COEFFTAB | + MALIDP_SE_H_COEFFTAB), + 0, v); + } else { + if (se_config->vcoeff != old_config->vcoeff) + malidp500_se_write_pp_coefftab(hwdev, + MALIDP_SE_V_COEFFTAB, + 0, v); + if (se_config->hcoeff != old_config->hcoeff) + malidp500_se_write_pp_coefftab(hwdev, + MALIDP_SE_H_COEFFTAB, + 0, h); + } + + return 0; +} + +static long malidp500_se_calc_mclk(struct malidp_hw_device *hwdev, + struct malidp_se_config *se_config, + struct videomode *vm) +{ + unsigned long mclk; + unsigned long pxlclk = vm->pixelclock; /* Hz */ + unsigned long htotal = vm->hactive + vm->hfront_porch + + vm->hback_porch + vm->hsync_len; + unsigned long input_size = se_config->input_w * se_config->input_h; + unsigned long a = 10; + long ret; + + /* + * mclk = max(a, 1.5) * pxlclk + * + * To avoid float calculaiton, using 15 instead of 1.5 and div by + * 10 to get mclk. + */ + if (se_config->scale_enable) { + a = 15 * input_size / (htotal * se_config->output_h); + if (a < 15) + a = 15; + } + mclk = a * pxlclk / 10; + ret = clk_get_rate(hwdev->mclk); + if (ret < mclk) { + DRM_DEBUG_DRIVER("mclk requirement of %lu kHz can't be met.\n", + mclk / 1000); + return -EINVAL; + } + return ret; +} + +static int malidp500_enable_memwrite(struct malidp_hw_device *hwdev, + dma_addr_t *addrs, s32 *pitches, + int num_planes, u16 w, u16 h, u32 fmt_id, + const s16 *rgb2yuv_coeffs) +{ + u32 base = MALIDP500_SE_MEMWRITE_BASE; + u32 de_base = malidp_get_block_base(hwdev, MALIDP_DE_BLOCK); + + /* enable the scaling engine block */ + malidp_hw_setbits(hwdev, MALIDP_SCALE_ENGINE_EN, de_base + MALIDP_DE_DISPLAY_FUNC); + + /* restart the writeback if already enabled */ + if (hwdev->mw_state != MW_NOT_ENABLED) + hwdev->mw_state = MW_RESTART; + else + hwdev->mw_state = MW_START; + + malidp_hw_write(hwdev, fmt_id, base + MALIDP_MW_FORMAT); + switch (num_planes) { + case 2: + malidp_hw_write(hwdev, lower_32_bits(addrs[1]), base + MALIDP_MW_P2_PTR_LOW); + malidp_hw_write(hwdev, upper_32_bits(addrs[1]), base + MALIDP_MW_P2_PTR_HIGH); + malidp_hw_write(hwdev, pitches[1], base + MALIDP_MW_P2_STRIDE); + /* fall through */ + case 1: + malidp_hw_write(hwdev, lower_32_bits(addrs[0]), base + MALIDP_MW_P1_PTR_LOW); + malidp_hw_write(hwdev, upper_32_bits(addrs[0]), base + MALIDP_MW_P1_PTR_HIGH); + malidp_hw_write(hwdev, pitches[0], base + MALIDP_MW_P1_STRIDE); + break; + default: + WARN(1, "Invalid number of planes"); + } + + malidp_hw_write(hwdev, MALIDP_DE_H_ACTIVE(w) | MALIDP_DE_V_ACTIVE(h), + MALIDP500_SE_MEMWRITE_OUT_SIZE); + + if (rgb2yuv_coeffs) { + int i; + + for (i = 0; i < MALIDP_COLORADJ_NUM_COEFFS; i++) { + malidp_hw_write(hwdev, rgb2yuv_coeffs[i], + MALIDP500_SE_RGB_YUV_COEFFS + i * 4); + } + } + + malidp_hw_setbits(hwdev, MALIDP_SE_MEMWRITE_EN, MALIDP500_SE_CONTROL); + + return 0; +} + +static void malidp500_disable_memwrite(struct malidp_hw_device *hwdev) +{ + u32 base = malidp_get_block_base(hwdev, MALIDP_DE_BLOCK); + + if (hwdev->mw_state == MW_START || hwdev->mw_state == MW_RESTART) + hwdev->mw_state = MW_STOP; + malidp_hw_clearbits(hwdev, MALIDP_SE_MEMWRITE_EN, MALIDP500_SE_CONTROL); + malidp_hw_clearbits(hwdev, MALIDP_SCALE_ENGINE_EN, base + MALIDP_DE_DISPLAY_FUNC); +} + +static int malidp550_query_hw(struct malidp_hw_device *hwdev) +{ + u32 conf = malidp_hw_read(hwdev, MALIDP550_CONFIG_ID); + u8 ln_size = (conf >> 4) & 0x3, rsize; + + hwdev->min_line_size = 2; + + switch (ln_size) { + case 0: + hwdev->max_line_size = SZ_2K; + /* two banks of 64KB for rotation memory */ + rsize = 64; + break; + case 1: + hwdev->max_line_size = SZ_4K; + /* two banks of 128KB for rotation memory */ + rsize = 128; + break; + case 2: + hwdev->max_line_size = 1280; + /* two banks of 40KB for rotation memory */ + rsize = 40; + break; + case 3: + /* reserved value */ + hwdev->max_line_size = 0; + return -EINVAL; + } + + hwdev->rotation_memory[0] = hwdev->rotation_memory[1] = rsize * SZ_1K; + return 0; +} + +static void malidp550_enter_config_mode(struct malidp_hw_device *hwdev) +{ + u32 status, count = 100; + + malidp_hw_setbits(hwdev, MALIDP550_DC_CONFIG_REQ, MALIDP550_DC_CONTROL); + while (count) { + status = malidp_hw_read(hwdev, hwdev->hw->map.dc_base + MALIDP_REG_STATUS); + if ((status & MALIDP550_DC_CONFIG_REQ) == MALIDP550_DC_CONFIG_REQ) + break; + /* + * entering config mode can take as long as the rendering + * of a full frame, hence the long sleep here + */ + usleep_range(1000, 10000); + count--; + } + WARN(count == 0, "timeout while entering config mode"); +} + +static void malidp550_leave_config_mode(struct malidp_hw_device *hwdev) +{ + u32 status, count = 100; + + malidp_hw_clearbits(hwdev, MALIDP_CFG_VALID, MALIDP550_CONFIG_VALID); + malidp_hw_clearbits(hwdev, MALIDP550_DC_CONFIG_REQ, MALIDP550_DC_CONTROL); + while (count) { + status = malidp_hw_read(hwdev, hwdev->hw->map.dc_base + MALIDP_REG_STATUS); + if ((status & MALIDP550_DC_CONFIG_REQ) == 0) + break; + usleep_range(100, 1000); + count--; + } + WARN(count == 0, "timeout while leaving config mode"); +} + +static bool malidp550_in_config_mode(struct malidp_hw_device *hwdev) +{ + u32 status; + + status = malidp_hw_read(hwdev, hwdev->hw->map.dc_base + MALIDP_REG_STATUS); + if ((status & MALIDP550_DC_CONFIG_REQ) == MALIDP550_DC_CONFIG_REQ) + return true; + + return false; +} + +static void malidp550_set_config_valid(struct malidp_hw_device *hwdev, u8 value) +{ + if (value) + malidp_hw_setbits(hwdev, MALIDP_CFG_VALID, MALIDP550_CONFIG_VALID); + else + malidp_hw_clearbits(hwdev, MALIDP_CFG_VALID, MALIDP550_CONFIG_VALID); +} + +static void malidp550_modeset(struct malidp_hw_device *hwdev, struct videomode *mode) +{ + u32 val = MALIDP_DE_DEFAULT_PREFETCH_START; + + malidp_hw_write(hwdev, hwdev->output_color_depth, + hwdev->hw->map.out_depth_base); + malidp_hw_write(hwdev, val, MALIDP550_DE_CONTROL); + /* + * Mali-DP550 and Mali-DP650 encode the background color like this: + * - red @ MALIDP550_DE_BGND_COLOR[23:16] + * - green @ MALIDP550_DE_BGND_COLOR[15:8] + * - blue @ MALIDP550_DE_BGND_COLOR[7:0] + * + * We need to truncate the least significant 4 bits from the default + * MALIDP_BGND_COLOR_x values + */ + val = (((MALIDP_BGND_COLOR_R >> 4) & 0xff) << 16) | + (((MALIDP_BGND_COLOR_G >> 4) & 0xff) << 8) | + ((MALIDP_BGND_COLOR_B >> 4) & 0xff); + malidp_hw_write(hwdev, val, MALIDP550_DE_BGND_COLOR); + + val = MALIDP_DE_H_FRONTPORCH(mode->hfront_porch) | + MALIDP_DE_H_BACKPORCH(mode->hback_porch); + malidp_hw_write(hwdev, val, MALIDP550_TIMINGS_BASE + MALIDP_DE_H_TIMINGS); + + val = MALIDP550_DE_V_FRONTPORCH(mode->vfront_porch) | + MALIDP_DE_V_BACKPORCH(mode->vback_porch); + malidp_hw_write(hwdev, val, MALIDP550_TIMINGS_BASE + MALIDP_DE_V_TIMINGS); + + val = MALIDP_DE_H_SYNCWIDTH(mode->hsync_len) | + MALIDP_DE_V_SYNCWIDTH(mode->vsync_len); + if (mode->flags & DISPLAY_FLAGS_HSYNC_HIGH) + val |= MALIDP550_HSYNCPOL; + if (mode->flags & DISPLAY_FLAGS_VSYNC_HIGH) + val |= MALIDP550_VSYNCPOL; + malidp_hw_write(hwdev, val, MALIDP550_TIMINGS_BASE + MALIDP_DE_SYNC_WIDTH); + + val = MALIDP_DE_H_ACTIVE(mode->hactive) | MALIDP_DE_V_ACTIVE(mode->vactive); + malidp_hw_write(hwdev, val, MALIDP550_TIMINGS_BASE + MALIDP_DE_HV_ACTIVE); + + if (mode->flags & DISPLAY_FLAGS_INTERLACED) + malidp_hw_setbits(hwdev, MALIDP_DISP_FUNC_ILACED, MALIDP_DE_DISPLAY_FUNC); + else + malidp_hw_clearbits(hwdev, MALIDP_DISP_FUNC_ILACED, MALIDP_DE_DISPLAY_FUNC); +} + +static int malidp550_rotmem_required(struct malidp_hw_device *hwdev, u16 w, u16 h, u32 fmt) +{ + u32 bytes_per_col; + + /* raw RGB888 or BGR888 can't be rotated */ + if ((fmt == DRM_FORMAT_RGB888) || (fmt == DRM_FORMAT_BGR888)) + return -EINVAL; + + switch (fmt) { + /* 8 lines at 4 bytes per pixel */ + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + /* 16 lines at 2 bytes per pixel */ + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_YUYV: + bytes_per_col = 32; + break; + /* 16 lines at 1.5 bytes per pixel */ + case DRM_FORMAT_NV12: + case DRM_FORMAT_YUV420: + bytes_per_col = 24; + break; + default: + return -EINVAL; + } + + return w * bytes_per_col; +} + +static int malidp550_se_set_scaling_coeffs(struct malidp_hw_device *hwdev, + struct malidp_se_config *se_config, + struct malidp_se_config *old_config) +{ + u32 mask = MALIDP550_SE_CTL_VCSEL(MALIDP550_SE_CTL_SEL_MASK) | + MALIDP550_SE_CTL_HCSEL(MALIDP550_SE_CTL_SEL_MASK); + u32 new_value = MALIDP550_SE_CTL_VCSEL(se_config->vcoeff) | + MALIDP550_SE_CTL_HCSEL(se_config->hcoeff); + + malidp_hw_clearbits(hwdev, mask, MALIDP550_SE_CONTROL); + malidp_hw_setbits(hwdev, new_value, MALIDP550_SE_CONTROL); + return 0; +} + +static long malidp550_se_calc_mclk(struct malidp_hw_device *hwdev, + struct malidp_se_config *se_config, + struct videomode *vm) +{ + unsigned long mclk; + unsigned long pxlclk = vm->pixelclock; + unsigned long htotal = vm->hactive + vm->hfront_porch + + vm->hback_porch + vm->hsync_len; + unsigned long numerator = 1, denominator = 1; + long ret; + + if (se_config->scale_enable) { + numerator = max(se_config->input_w, se_config->output_w) * + se_config->input_h; + numerator += se_config->output_w * + (se_config->output_h - + min(se_config->input_h, se_config->output_h)); + denominator = (htotal - 2) * se_config->output_h; + } + + /* mclk can't be slower than pxlclk. */ + if (numerator < denominator) + numerator = denominator = 1; + mclk = (pxlclk * numerator) / denominator; + ret = clk_get_rate(hwdev->mclk); + if (ret < mclk) { + DRM_DEBUG_DRIVER("mclk requirement of %lu kHz can't be met.\n", + mclk / 1000); + return -EINVAL; + } + return ret; +} + +static int malidp550_enable_memwrite(struct malidp_hw_device *hwdev, + dma_addr_t *addrs, s32 *pitches, + int num_planes, u16 w, u16 h, u32 fmt_id, + const s16 *rgb2yuv_coeffs) +{ + u32 base = MALIDP550_SE_MEMWRITE_BASE; + u32 de_base = malidp_get_block_base(hwdev, MALIDP_DE_BLOCK); + + /* enable the scaling engine block */ + malidp_hw_setbits(hwdev, MALIDP_SCALE_ENGINE_EN, de_base + MALIDP_DE_DISPLAY_FUNC); + + hwdev->mw_state = MW_ONESHOT; + + malidp_hw_write(hwdev, fmt_id, base + MALIDP_MW_FORMAT); + switch (num_planes) { + case 2: + malidp_hw_write(hwdev, lower_32_bits(addrs[1]), base + MALIDP_MW_P2_PTR_LOW); + malidp_hw_write(hwdev, upper_32_bits(addrs[1]), base + MALIDP_MW_P2_PTR_HIGH); + malidp_hw_write(hwdev, pitches[1], base + MALIDP_MW_P2_STRIDE); + /* fall through */ + case 1: + malidp_hw_write(hwdev, lower_32_bits(addrs[0]), base + MALIDP_MW_P1_PTR_LOW); + malidp_hw_write(hwdev, upper_32_bits(addrs[0]), base + MALIDP_MW_P1_PTR_HIGH); + malidp_hw_write(hwdev, pitches[0], base + MALIDP_MW_P1_STRIDE); + break; + default: + WARN(1, "Invalid number of planes"); + } + + malidp_hw_write(hwdev, MALIDP_DE_H_ACTIVE(w) | MALIDP_DE_V_ACTIVE(h), + MALIDP550_SE_MEMWRITE_OUT_SIZE); + malidp_hw_setbits(hwdev, MALIDP550_SE_MEMWRITE_ONESHOT | MALIDP_SE_MEMWRITE_EN, + MALIDP550_SE_CONTROL); + + if (rgb2yuv_coeffs) { + int i; + + for (i = 0; i < MALIDP_COLORADJ_NUM_COEFFS; i++) { + malidp_hw_write(hwdev, rgb2yuv_coeffs[i], + MALIDP550_SE_RGB_YUV_COEFFS + i * 4); + } + } + + return 0; +} + +static void malidp550_disable_memwrite(struct malidp_hw_device *hwdev) +{ + u32 base = malidp_get_block_base(hwdev, MALIDP_DE_BLOCK); + + malidp_hw_clearbits(hwdev, MALIDP550_SE_MEMWRITE_ONESHOT | MALIDP_SE_MEMWRITE_EN, + MALIDP550_SE_CONTROL); + malidp_hw_clearbits(hwdev, MALIDP_SCALE_ENGINE_EN, base + MALIDP_DE_DISPLAY_FUNC); +} + +static int malidp650_query_hw(struct malidp_hw_device *hwdev) +{ + u32 conf = malidp_hw_read(hwdev, MALIDP550_CONFIG_ID); + u8 ln_size = (conf >> 4) & 0x3, rsize; + + hwdev->min_line_size = 4; + + switch (ln_size) { + case 0: + case 2: + /* reserved values */ + hwdev->max_line_size = 0; + return -EINVAL; + case 1: + hwdev->max_line_size = SZ_4K; + /* two banks of 128KB for rotation memory */ + rsize = 128; + break; + case 3: + hwdev->max_line_size = 2560; + /* two banks of 80KB for rotation memory */ + rsize = 80; + } + + hwdev->rotation_memory[0] = hwdev->rotation_memory[1] = rsize * SZ_1K; + return 0; +} + +const struct malidp_hw malidp_device[MALIDP_MAX_DEVICES] = { + [MALIDP_500] = { + .map = { + .coeffs_base = MALIDP500_COEFFS_BASE, + .se_base = MALIDP500_SE_BASE, + .dc_base = MALIDP500_DC_BASE, + .out_depth_base = MALIDP500_OUTPUT_DEPTH, + .features = 0, /* no CLEARIRQ register */ + .n_layers = ARRAY_SIZE(malidp500_layers), + .layers = malidp500_layers, + .de_irq_map = { + .irq_mask = MALIDP_DE_IRQ_UNDERRUN | + MALIDP500_DE_IRQ_AXI_ERR | + MALIDP500_DE_IRQ_VSYNC | + MALIDP500_DE_IRQ_GLOBAL, + .vsync_irq = MALIDP500_DE_IRQ_VSYNC, + .err_mask = MALIDP_DE_IRQ_UNDERRUN | + MALIDP500_DE_IRQ_AXI_ERR | + MALIDP500_DE_IRQ_SATURATION, + }, + .se_irq_map = { + .irq_mask = MALIDP500_SE_IRQ_CONF_MODE | + MALIDP500_SE_IRQ_CONF_VALID | + MALIDP500_SE_IRQ_GLOBAL, + .vsync_irq = MALIDP500_SE_IRQ_CONF_VALID, + .err_mask = MALIDP500_SE_IRQ_INIT_BUSY | + MALIDP500_SE_IRQ_AXI_ERROR | + MALIDP500_SE_IRQ_OVERRUN, + }, + .dc_irq_map = { + .irq_mask = MALIDP500_DE_IRQ_CONF_VALID, + .vsync_irq = MALIDP500_DE_IRQ_CONF_VALID, + }, + .pixel_formats = malidp500_de_formats, + .n_pixel_formats = ARRAY_SIZE(malidp500_de_formats), + .bus_align_bytes = 8, + }, + .query_hw = malidp500_query_hw, + .enter_config_mode = malidp500_enter_config_mode, + .leave_config_mode = malidp500_leave_config_mode, + .in_config_mode = malidp500_in_config_mode, + .set_config_valid = malidp500_set_config_valid, + .modeset = malidp500_modeset, + .rotmem_required = malidp500_rotmem_required, + .se_set_scaling_coeffs = malidp500_se_set_scaling_coeffs, + .se_calc_mclk = malidp500_se_calc_mclk, + .enable_memwrite = malidp500_enable_memwrite, + .disable_memwrite = malidp500_disable_memwrite, + .features = MALIDP_DEVICE_LV_HAS_3_STRIDES, + }, + [MALIDP_550] = { + .map = { + .coeffs_base = MALIDP550_COEFFS_BASE, + .se_base = MALIDP550_SE_BASE, + .dc_base = MALIDP550_DC_BASE, + .out_depth_base = MALIDP550_DE_OUTPUT_DEPTH, + .features = MALIDP_REGMAP_HAS_CLEARIRQ, + .n_layers = ARRAY_SIZE(malidp550_layers), + .layers = malidp550_layers, + .de_irq_map = { + .irq_mask = MALIDP_DE_IRQ_UNDERRUN | + MALIDP550_DE_IRQ_VSYNC, + .vsync_irq = MALIDP550_DE_IRQ_VSYNC, + .err_mask = MALIDP_DE_IRQ_UNDERRUN | + MALIDP550_DE_IRQ_SATURATION | + MALIDP550_DE_IRQ_AXI_ERR, + }, + .se_irq_map = { + .irq_mask = MALIDP550_SE_IRQ_EOW, + .vsync_irq = MALIDP550_SE_IRQ_EOW, + .err_mask = MALIDP550_SE_IRQ_AXI_ERR | + MALIDP550_SE_IRQ_OVR | + MALIDP550_SE_IRQ_IBSY, + }, + .dc_irq_map = { + .irq_mask = MALIDP550_DC_IRQ_CONF_VALID | + MALIDP550_DC_IRQ_SE, + .vsync_irq = MALIDP550_DC_IRQ_CONF_VALID, + }, + .pixel_formats = malidp550_de_formats, + .n_pixel_formats = ARRAY_SIZE(malidp550_de_formats), + .bus_align_bytes = 8, + }, + .query_hw = malidp550_query_hw, + .enter_config_mode = malidp550_enter_config_mode, + .leave_config_mode = malidp550_leave_config_mode, + .in_config_mode = malidp550_in_config_mode, + .set_config_valid = malidp550_set_config_valid, + .modeset = malidp550_modeset, + .rotmem_required = malidp550_rotmem_required, + .se_set_scaling_coeffs = malidp550_se_set_scaling_coeffs, + .se_calc_mclk = malidp550_se_calc_mclk, + .enable_memwrite = malidp550_enable_memwrite, + .disable_memwrite = malidp550_disable_memwrite, + .features = 0, + }, + [MALIDP_650] = { + .map = { + .coeffs_base = MALIDP550_COEFFS_BASE, + .se_base = MALIDP550_SE_BASE, + .dc_base = MALIDP550_DC_BASE, + .out_depth_base = MALIDP550_DE_OUTPUT_DEPTH, + .features = MALIDP_REGMAP_HAS_CLEARIRQ, + .n_layers = ARRAY_SIZE(malidp550_layers), + .layers = malidp550_layers, + .de_irq_map = { + .irq_mask = MALIDP_DE_IRQ_UNDERRUN | + MALIDP650_DE_IRQ_DRIFT | + MALIDP550_DE_IRQ_VSYNC, + .vsync_irq = MALIDP550_DE_IRQ_VSYNC, + .err_mask = MALIDP_DE_IRQ_UNDERRUN | + MALIDP650_DE_IRQ_DRIFT | + MALIDP550_DE_IRQ_SATURATION | + MALIDP550_DE_IRQ_AXI_ERR | + MALIDP650_DE_IRQ_ACEV1 | + MALIDP650_DE_IRQ_ACEV2 | + MALIDP650_DE_IRQ_ACEG | + MALIDP650_DE_IRQ_AXIEP, + }, + .se_irq_map = { + .irq_mask = MALIDP550_SE_IRQ_EOW, + .vsync_irq = MALIDP550_SE_IRQ_EOW, + .err_mask = MALIDP550_SE_IRQ_AXI_ERR | + MALIDP550_SE_IRQ_OVR | + MALIDP550_SE_IRQ_IBSY, + }, + .dc_irq_map = { + .irq_mask = MALIDP550_DC_IRQ_CONF_VALID | + MALIDP550_DC_IRQ_SE, + .vsync_irq = MALIDP550_DC_IRQ_CONF_VALID, + }, + .pixel_formats = malidp550_de_formats, + .n_pixel_formats = ARRAY_SIZE(malidp550_de_formats), + .bus_align_bytes = 16, + }, + .query_hw = malidp650_query_hw, + .enter_config_mode = malidp550_enter_config_mode, + .leave_config_mode = malidp550_leave_config_mode, + .in_config_mode = malidp550_in_config_mode, + .set_config_valid = malidp550_set_config_valid, + .modeset = malidp550_modeset, + .rotmem_required = malidp550_rotmem_required, + .se_set_scaling_coeffs = malidp550_se_set_scaling_coeffs, + .se_calc_mclk = malidp550_se_calc_mclk, + .enable_memwrite = malidp550_enable_memwrite, + .disable_memwrite = malidp550_disable_memwrite, + .features = 0, + }, +}; + +u8 malidp_hw_get_format_id(const struct malidp_hw_regmap *map, + u8 layer_id, u32 format) +{ + unsigned int i; + + for (i = 0; i < map->n_pixel_formats; i++) { + if (((map->pixel_formats[i].layer & layer_id) == layer_id) && + (map->pixel_formats[i].format == format)) + return map->pixel_formats[i].id; + } + + return MALIDP_INVALID_FORMAT_ID; +} + +static void malidp_hw_clear_irq(struct malidp_hw_device *hwdev, u8 block, u32 irq) +{ + u32 base = malidp_get_block_base(hwdev, block); + + if (hwdev->hw->map.features & MALIDP_REGMAP_HAS_CLEARIRQ) + malidp_hw_write(hwdev, irq, base + MALIDP_REG_CLEARIRQ); + else + malidp_hw_write(hwdev, irq, base + MALIDP_REG_STATUS); +} + +static irqreturn_t malidp_de_irq(int irq, void *arg) +{ + struct drm_device *drm = arg; + struct malidp_drm *malidp = drm->dev_private; + struct malidp_hw_device *hwdev; + struct malidp_hw *hw; + const struct malidp_irq_map *de; + u32 status, mask, dc_status; + irqreturn_t ret = IRQ_NONE; + + hwdev = malidp->dev; + hw = hwdev->hw; + de = &hw->map.de_irq_map; + + /* + * if we are suspended it is likely that we were invoked because + * we share an interrupt line with some other driver, don't try + * to read the hardware registers + */ + if (hwdev->pm_suspended) + return IRQ_NONE; + + /* first handle the config valid IRQ */ + dc_status = malidp_hw_read(hwdev, hw->map.dc_base + MALIDP_REG_STATUS); + if (dc_status & hw->map.dc_irq_map.vsync_irq) { + malidp_hw_clear_irq(hwdev, MALIDP_DC_BLOCK, dc_status); + /* do we have a page flip event? */ + if (malidp->event != NULL) { + spin_lock(&drm->event_lock); + drm_crtc_send_vblank_event(&malidp->crtc, malidp->event); + malidp->event = NULL; + spin_unlock(&drm->event_lock); + } + atomic_set(&malidp->config_valid, MALIDP_CONFIG_VALID_DONE); + ret = IRQ_WAKE_THREAD; + } + + status = malidp_hw_read(hwdev, MALIDP_REG_STATUS); + if (!(status & de->irq_mask)) + return ret; + + mask = malidp_hw_read(hwdev, MALIDP_REG_MASKIRQ); + /* keep the status of the enabled interrupts, plus the error bits */ + status &= (mask | de->err_mask); + if ((status & de->vsync_irq) && malidp->crtc.enabled) + drm_crtc_handle_vblank(&malidp->crtc); + +#ifdef CONFIG_DEBUG_FS + if (status & de->err_mask) { + malidp_error(malidp, &malidp->de_errors, status, + drm_crtc_vblank_count(&malidp->crtc)); + } +#endif + malidp_hw_clear_irq(hwdev, MALIDP_DE_BLOCK, status); + + return (ret == IRQ_NONE) ? IRQ_HANDLED : ret; +} + +static irqreturn_t malidp_de_irq_thread_handler(int irq, void *arg) +{ + struct drm_device *drm = arg; + struct malidp_drm *malidp = drm->dev_private; + + wake_up(&malidp->wq); + + return IRQ_HANDLED; +} + +void malidp_de_irq_hw_init(struct malidp_hw_device *hwdev) +{ + /* ensure interrupts are disabled */ + malidp_hw_disable_irq(hwdev, MALIDP_DE_BLOCK, 0xffffffff); + malidp_hw_clear_irq(hwdev, MALIDP_DE_BLOCK, 0xffffffff); + malidp_hw_disable_irq(hwdev, MALIDP_DC_BLOCK, 0xffffffff); + malidp_hw_clear_irq(hwdev, MALIDP_DC_BLOCK, 0xffffffff); + + /* first enable the DC block IRQs */ + malidp_hw_enable_irq(hwdev, MALIDP_DC_BLOCK, + hwdev->hw->map.dc_irq_map.irq_mask); + + /* now enable the DE block IRQs */ + malidp_hw_enable_irq(hwdev, MALIDP_DE_BLOCK, + hwdev->hw->map.de_irq_map.irq_mask); +} + +int malidp_de_irq_init(struct drm_device *drm, int irq) +{ + struct malidp_drm *malidp = drm->dev_private; + struct malidp_hw_device *hwdev = malidp->dev; + int ret; + + /* ensure interrupts are disabled */ + malidp_hw_disable_irq(hwdev, MALIDP_DE_BLOCK, 0xffffffff); + malidp_hw_clear_irq(hwdev, MALIDP_DE_BLOCK, 0xffffffff); + malidp_hw_disable_irq(hwdev, MALIDP_DC_BLOCK, 0xffffffff); + malidp_hw_clear_irq(hwdev, MALIDP_DC_BLOCK, 0xffffffff); + + ret = devm_request_threaded_irq(drm->dev, irq, malidp_de_irq, + malidp_de_irq_thread_handler, + IRQF_SHARED, "malidp-de", drm); + if (ret < 0) { + DRM_ERROR("failed to install DE IRQ handler\n"); + return ret; + } + + malidp_de_irq_hw_init(hwdev); + + return 0; +} + +void malidp_de_irq_fini(struct malidp_hw_device *hwdev) +{ + malidp_hw_disable_irq(hwdev, MALIDP_DE_BLOCK, + hwdev->hw->map.de_irq_map.irq_mask); + malidp_hw_disable_irq(hwdev, MALIDP_DC_BLOCK, + hwdev->hw->map.dc_irq_map.irq_mask); +} + +static irqreturn_t malidp_se_irq(int irq, void *arg) +{ + struct drm_device *drm = arg; + struct malidp_drm *malidp = drm->dev_private; + struct malidp_hw_device *hwdev = malidp->dev; + struct malidp_hw *hw = hwdev->hw; + const struct malidp_irq_map *se = &hw->map.se_irq_map; + u32 status, mask; + + /* + * if we are suspended it is likely that we were invoked because + * we share an interrupt line with some other driver, don't try + * to read the hardware registers + */ + if (hwdev->pm_suspended) + return IRQ_NONE; + + status = malidp_hw_read(hwdev, hw->map.se_base + MALIDP_REG_STATUS); + if (!(status & (se->irq_mask | se->err_mask))) + return IRQ_NONE; + +#ifdef CONFIG_DEBUG_FS + if (status & se->err_mask) + malidp_error(malidp, &malidp->se_errors, status, + drm_crtc_vblank_count(&malidp->crtc)); +#endif + mask = malidp_hw_read(hwdev, hw->map.se_base + MALIDP_REG_MASKIRQ); + status &= mask; + + if (status & se->vsync_irq) { + switch (hwdev->mw_state) { + case MW_ONESHOT: + drm_writeback_signal_completion(&malidp->mw_connector, 0); + break; + case MW_STOP: + drm_writeback_signal_completion(&malidp->mw_connector, 0); + /* disable writeback after stop */ + hwdev->mw_state = MW_NOT_ENABLED; + break; + case MW_RESTART: + drm_writeback_signal_completion(&malidp->mw_connector, 0); + /* fall through to a new start */ + case MW_START: + /* writeback started, need to emulate one-shot mode */ + hw->disable_memwrite(hwdev); + /* + * only set config_valid HW bit if there is no other update + * in progress or if we raced ahead of the DE IRQ handler + * and config_valid flag will not be update until later + */ + status = malidp_hw_read(hwdev, hw->map.dc_base + MALIDP_REG_STATUS); + if ((atomic_read(&malidp->config_valid) != MALIDP_CONFIG_START) || + (status & hw->map.dc_irq_map.vsync_irq)) + hw->set_config_valid(hwdev, 1); + break; + } + } + + malidp_hw_clear_irq(hwdev, MALIDP_SE_BLOCK, status); + + return IRQ_HANDLED; +} + +void malidp_se_irq_hw_init(struct malidp_hw_device *hwdev) +{ + /* ensure interrupts are disabled */ + malidp_hw_disable_irq(hwdev, MALIDP_SE_BLOCK, 0xffffffff); + malidp_hw_clear_irq(hwdev, MALIDP_SE_BLOCK, 0xffffffff); + + malidp_hw_enable_irq(hwdev, MALIDP_SE_BLOCK, + hwdev->hw->map.se_irq_map.irq_mask); +} + +static irqreturn_t malidp_se_irq_thread_handler(int irq, void *arg) +{ + return IRQ_HANDLED; +} + +int malidp_se_irq_init(struct drm_device *drm, int irq) +{ + struct malidp_drm *malidp = drm->dev_private; + struct malidp_hw_device *hwdev = malidp->dev; + int ret; + + /* ensure interrupts are disabled */ + malidp_hw_disable_irq(hwdev, MALIDP_SE_BLOCK, 0xffffffff); + malidp_hw_clear_irq(hwdev, MALIDP_SE_BLOCK, 0xffffffff); + + ret = devm_request_threaded_irq(drm->dev, irq, malidp_se_irq, + malidp_se_irq_thread_handler, + IRQF_SHARED, "malidp-se", drm); + if (ret < 0) { + DRM_ERROR("failed to install SE IRQ handler\n"); + return ret; + } + + hwdev->mw_state = MW_NOT_ENABLED; + malidp_se_irq_hw_init(hwdev); + + return 0; +} + +void malidp_se_irq_fini(struct malidp_hw_device *hwdev) +{ + malidp_hw_disable_irq(hwdev, MALIDP_SE_BLOCK, + hwdev->hw->map.se_irq_map.irq_mask); +} diff --git a/drivers/gpu/drm/arm/malidp_hw.h b/drivers/gpu/drm/arm/malidp_hw.h new file mode 100644 index 000000000..9fc94c081 --- /dev/null +++ b/drivers/gpu/drm/arm/malidp_hw.h @@ -0,0 +1,383 @@ +/* + * + * (C) COPYRIGHT 2013-2016 ARM Limited. All rights reserved. + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * ARM Mali DP hardware manipulation routines. + */ + +#ifndef __MALIDP_HW_H__ +#define __MALIDP_HW_H__ + +#include <linux/bitops.h> +#include "malidp_regs.h" + +struct videomode; +struct clk; + +/* Mali DP IP blocks */ +enum { + MALIDP_DE_BLOCK = 0, + MALIDP_SE_BLOCK, + MALIDP_DC_BLOCK +}; + +/* Mali DP layer IDs */ +enum { + DE_VIDEO1 = BIT(0), + DE_GRAPHICS1 = BIT(1), + DE_GRAPHICS2 = BIT(2), /* used only in DP500 */ + DE_VIDEO2 = BIT(3), + DE_SMART = BIT(4), + SE_MEMWRITE = BIT(5), +}; + +struct malidp_format_id { + u32 format; /* DRM fourcc */ + u8 layer; /* bitmask of layers supporting it */ + u8 id; /* used internally */ +}; + +#define MALIDP_INVALID_FORMAT_ID 0xff + +/* + * hide the differences between register maps + * by using a common structure to hold the + * base register offsets + */ + +struct malidp_irq_map { + u32 irq_mask; /* mask of IRQs that can be enabled in the block */ + u32 vsync_irq; /* IRQ bit used for signaling during VSYNC */ + u32 err_mask; /* mask of bits that represent errors */ +}; + +struct malidp_layer { + u16 id; /* layer ID */ + u16 base; /* address offset for the register bank */ + u16 ptr; /* address offset for the pointer register */ + u16 stride_offset; /* offset to the first stride register. */ + s16 yuv2rgb_offset; /* offset to the YUV->RGB matrix entries */ +}; + +enum malidp_scaling_coeff_set { + MALIDP_UPSCALING_COEFFS = 1, + MALIDP_DOWNSCALING_1_5_COEFFS = 2, + MALIDP_DOWNSCALING_2_COEFFS = 3, + MALIDP_DOWNSCALING_2_75_COEFFS = 4, + MALIDP_DOWNSCALING_4_COEFFS = 5, +}; + +struct malidp_se_config { + u8 scale_enable : 1; + u8 enhancer_enable : 1; + u8 hcoeff : 3; + u8 vcoeff : 3; + u8 plane_src_id; + u16 input_w, input_h; + u16 output_w, output_h; + u32 h_init_phase, h_delta_phase; + u32 v_init_phase, v_delta_phase; +}; + +/* regmap features */ +#define MALIDP_REGMAP_HAS_CLEARIRQ (1 << 0) + +struct malidp_hw_regmap { + /* address offset of the DE register bank */ + /* is always 0x0000 */ + /* address offset of the DE coefficients registers */ + const u16 coeffs_base; + /* address offset of the SE registers bank */ + const u16 se_base; + /* address offset of the DC registers bank */ + const u16 dc_base; + + /* address offset for the output depth register */ + const u16 out_depth_base; + + /* bitmap with register map features */ + const u8 features; + + /* list of supported layers */ + const u8 n_layers; + const struct malidp_layer *layers; + + const struct malidp_irq_map de_irq_map; + const struct malidp_irq_map se_irq_map; + const struct malidp_irq_map dc_irq_map; + + /* list of supported pixel formats for each layer */ + const struct malidp_format_id *pixel_formats; + const u8 n_pixel_formats; + + /* pitch alignment requirement in bytes */ + const u8 bus_align_bytes; +}; + +/* device features */ +/* Unlike DP550/650, DP500 has 3 stride registers in its video layer. */ +#define MALIDP_DEVICE_LV_HAS_3_STRIDES BIT(0) + +struct malidp_hw_device; + +/* + * Static structure containing hardware specific data and pointers to + * functions that behave differently between various versions of the IP. + */ +struct malidp_hw { + const struct malidp_hw_regmap map; + + /* + * Validate the driver instance against the hardware bits + */ + int (*query_hw)(struct malidp_hw_device *hwdev); + + /* + * Set the hardware into config mode, ready to accept mode changes + */ + void (*enter_config_mode)(struct malidp_hw_device *hwdev); + + /* + * Tell hardware to exit configuration mode + */ + void (*leave_config_mode)(struct malidp_hw_device *hwdev); + + /* + * Query if hardware is in configuration mode + */ + bool (*in_config_mode)(struct malidp_hw_device *hwdev); + + /* + * Set/clear configuration valid flag for hardware parameters that can + * be changed outside the configuration mode to the given value. + * Hardware will use the new settings when config valid is set, + * after the end of the current buffer scanout, and will ignore + * any new values for those parameters if config valid flag is cleared + */ + void (*set_config_valid)(struct malidp_hw_device *hwdev, u8 value); + + /* + * Set a new mode in hardware. Requires the hardware to be in + * configuration mode before this function is called. + */ + void (*modeset)(struct malidp_hw_device *hwdev, struct videomode *m); + + /* + * Calculate the required rotation memory given the active area + * and the buffer format. + */ + int (*rotmem_required)(struct malidp_hw_device *hwdev, u16 w, u16 h, u32 fmt); + + int (*se_set_scaling_coeffs)(struct malidp_hw_device *hwdev, + struct malidp_se_config *se_config, + struct malidp_se_config *old_config); + + long (*se_calc_mclk)(struct malidp_hw_device *hwdev, + struct malidp_se_config *se_config, + struct videomode *vm); + /* + * Enable writing to memory the content of the next frame + * @param hwdev - malidp_hw_device structure containing the HW description + * @param addrs - array of addresses for each plane + * @param pitches - array of pitches for each plane + * @param num_planes - number of planes to be written + * @param w - width of the output frame + * @param h - height of the output frame + * @param fmt_id - internal format ID of output buffer + */ + int (*enable_memwrite)(struct malidp_hw_device *hwdev, dma_addr_t *addrs, + s32 *pitches, int num_planes, u16 w, u16 h, u32 fmt_id, + const s16 *rgb2yuv_coeffs); + + /* + * Disable the writing to memory of the next frame's content. + */ + void (*disable_memwrite)(struct malidp_hw_device *hwdev); + + u8 features; +}; + +/* Supported variants of the hardware */ +enum { + MALIDP_500 = 0, + MALIDP_550, + MALIDP_650, + /* keep the next entry last */ + MALIDP_MAX_DEVICES +}; + +extern const struct malidp_hw malidp_device[MALIDP_MAX_DEVICES]; + +/* + * Structure used by the driver during runtime operation. + */ +struct malidp_hw_device { + struct malidp_hw *hw; + void __iomem *regs; + + /* APB clock */ + struct clk *pclk; + /* AXI clock */ + struct clk *aclk; + /* main clock for display core */ + struct clk *mclk; + /* pixel clock for display core */ + struct clk *pxlclk; + + u8 min_line_size; + u16 max_line_size; + u32 output_color_depth; + + /* track the device PM state */ + bool pm_suspended; + + /* track the SE memory writeback state */ + u8 mw_state; + + /* size of memory used for rotating layers, up to two banks available */ + u32 rotation_memory[2]; +}; + +static inline u32 malidp_hw_read(struct malidp_hw_device *hwdev, u32 reg) +{ + WARN_ON(hwdev->pm_suspended); + return readl(hwdev->regs + reg); +} + +static inline void malidp_hw_write(struct malidp_hw_device *hwdev, + u32 value, u32 reg) +{ + WARN_ON(hwdev->pm_suspended); + writel(value, hwdev->regs + reg); +} + +static inline void malidp_hw_setbits(struct malidp_hw_device *hwdev, + u32 mask, u32 reg) +{ + u32 data = malidp_hw_read(hwdev, reg); + + data |= mask; + malidp_hw_write(hwdev, data, reg); +} + +static inline void malidp_hw_clearbits(struct malidp_hw_device *hwdev, + u32 mask, u32 reg) +{ + u32 data = malidp_hw_read(hwdev, reg); + + data &= ~mask; + malidp_hw_write(hwdev, data, reg); +} + +static inline u32 malidp_get_block_base(struct malidp_hw_device *hwdev, + u8 block) +{ + switch (block) { + case MALIDP_SE_BLOCK: + return hwdev->hw->map.se_base; + case MALIDP_DC_BLOCK: + return hwdev->hw->map.dc_base; + } + + return 0; +} + +static inline void malidp_hw_disable_irq(struct malidp_hw_device *hwdev, + u8 block, u32 irq) +{ + u32 base = malidp_get_block_base(hwdev, block); + + malidp_hw_clearbits(hwdev, irq, base + MALIDP_REG_MASKIRQ); +} + +static inline void malidp_hw_enable_irq(struct malidp_hw_device *hwdev, + u8 block, u32 irq) +{ + u32 base = malidp_get_block_base(hwdev, block); + + malidp_hw_setbits(hwdev, irq, base + MALIDP_REG_MASKIRQ); +} + +int malidp_de_irq_init(struct drm_device *drm, int irq); +void malidp_se_irq_hw_init(struct malidp_hw_device *hwdev); +void malidp_de_irq_hw_init(struct malidp_hw_device *hwdev); +void malidp_de_irq_fini(struct malidp_hw_device *hwdev); +int malidp_se_irq_init(struct drm_device *drm, int irq); +void malidp_se_irq_fini(struct malidp_hw_device *hwdev); + +u8 malidp_hw_get_format_id(const struct malidp_hw_regmap *map, + u8 layer_id, u32 format); + +static inline u8 malidp_hw_get_pitch_align(struct malidp_hw_device *hwdev, bool rotated) +{ + /* + * only hardware that cannot do 8 bytes bus alignments have further + * constraints on rotated planes + */ + if (hwdev->hw->map.bus_align_bytes == 8) + return 8; + else + return hwdev->hw->map.bus_align_bytes << (rotated ? 2 : 0); +} + +/* U16.16 */ +#define FP_1_00000 0x00010000 /* 1.0 */ +#define FP_0_66667 0x0000AAAA /* 0.6667 = 1/1.5 */ +#define FP_0_50000 0x00008000 /* 0.5 = 1/2 */ +#define FP_0_36363 0x00005D17 /* 0.36363 = 1/2.75 */ +#define FP_0_25000 0x00004000 /* 0.25 = 1/4 */ + +static inline enum malidp_scaling_coeff_set +malidp_se_select_coeffs(u32 upscale_factor) +{ + return (upscale_factor >= FP_1_00000) ? MALIDP_UPSCALING_COEFFS : + (upscale_factor >= FP_0_66667) ? MALIDP_DOWNSCALING_1_5_COEFFS : + (upscale_factor >= FP_0_50000) ? MALIDP_DOWNSCALING_2_COEFFS : + (upscale_factor >= FP_0_36363) ? MALIDP_DOWNSCALING_2_75_COEFFS : + MALIDP_DOWNSCALING_4_COEFFS; +} + +#undef FP_0_25000 +#undef FP_0_36363 +#undef FP_0_50000 +#undef FP_0_66667 +#undef FP_1_00000 + +static inline void malidp_se_set_enh_coeffs(struct malidp_hw_device *hwdev) +{ + static const s32 enhancer_coeffs[] = { + -8, -8, -8, -8, 128, -8, -8, -8, -8 + }; + u32 val = MALIDP_SE_SET_ENH_LIMIT_LOW(MALIDP_SE_ENH_LOW_LEVEL) | + MALIDP_SE_SET_ENH_LIMIT_HIGH(MALIDP_SE_ENH_HIGH_LEVEL); + u32 image_enh = hwdev->hw->map.se_base + + ((hwdev->hw->map.features & MALIDP_REGMAP_HAS_CLEARIRQ) ? + 0x10 : 0xC) + MALIDP_SE_IMAGE_ENH; + u32 enh_coeffs = image_enh + MALIDP_SE_ENH_COEFF0; + int i; + + malidp_hw_write(hwdev, val, image_enh); + for (i = 0; i < ARRAY_SIZE(enhancer_coeffs); ++i) + malidp_hw_write(hwdev, enhancer_coeffs[i], enh_coeffs + i * 4); +} + +/* + * background color components are defined as 12bits values, + * they will be shifted right when stored on hardware that + * supports only 8bits per channel + */ +#define MALIDP_BGND_COLOR_R 0x000 +#define MALIDP_BGND_COLOR_G 0x000 +#define MALIDP_BGND_COLOR_B 0x000 + +#define MALIDP_COLORADJ_NUM_COEFFS 12 +#define MALIDP_COEFFTAB_NUM_COEFFS 64 + +#define MALIDP_GAMMA_LUT_SIZE 4096 + +#endif /* __MALIDP_HW_H__ */ diff --git a/drivers/gpu/drm/arm/malidp_mw.c b/drivers/gpu/drm/arm/malidp_mw.c new file mode 100644 index 000000000..7266d3c8b --- /dev/null +++ b/drivers/gpu/drm/arm/malidp_mw.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. + * Author: Brian Starkey <brian.starkey@arm.com> + * + * ARM Mali DP Writeback connector implementation + */ +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drmP.h> +#include <drm/drm_writeback.h> + +#include "malidp_drv.h" +#include "malidp_hw.h" +#include "malidp_mw.h" + +#define to_mw_state(_state) (struct malidp_mw_connector_state *)(_state) + +struct malidp_mw_connector_state { + struct drm_connector_state base; + dma_addr_t addrs[2]; + s32 pitches[2]; + u8 format; + u8 n_planes; + bool rgb2yuv_initialized; + const s16 *rgb2yuv_coeffs; +}; + +static int malidp_mw_connector_get_modes(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + + return drm_add_modes_noedid(connector, dev->mode_config.max_width, + dev->mode_config.max_height); +} + +static enum drm_mode_status +malidp_mw_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_device *dev = connector->dev; + struct drm_mode_config *mode_config = &dev->mode_config; + int w = mode->hdisplay, h = mode->vdisplay; + + if ((w < mode_config->min_width) || (w > mode_config->max_width)) + return MODE_BAD_HVALUE; + + if ((h < mode_config->min_height) || (h > mode_config->max_height)) + return MODE_BAD_VVALUE; + + return MODE_OK; +} + +static const struct drm_connector_helper_funcs malidp_mw_connector_helper_funcs = { + .get_modes = malidp_mw_connector_get_modes, + .mode_valid = malidp_mw_connector_mode_valid, +}; + +static void malidp_mw_connector_reset(struct drm_connector *connector) +{ + struct malidp_mw_connector_state *mw_state = + kzalloc(sizeof(*mw_state), GFP_KERNEL); + + if (connector->state) + __drm_atomic_helper_connector_destroy_state(connector->state); + + kfree(connector->state); + __drm_atomic_helper_connector_reset(connector, &mw_state->base); +} + +static enum drm_connector_status +malidp_mw_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void malidp_mw_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static struct drm_connector_state * +malidp_mw_connector_duplicate_state(struct drm_connector *connector) +{ + struct malidp_mw_connector_state *mw_state, *mw_current_state; + + if (WARN_ON(!connector->state)) + return NULL; + + mw_state = kzalloc(sizeof(*mw_state), GFP_KERNEL); + if (!mw_state) + return NULL; + + mw_current_state = to_mw_state(connector->state); + mw_state->rgb2yuv_coeffs = mw_current_state->rgb2yuv_coeffs; + mw_state->rgb2yuv_initialized = mw_current_state->rgb2yuv_initialized; + + __drm_atomic_helper_connector_duplicate_state(connector, &mw_state->base); + + return &mw_state->base; +} + +static const struct drm_connector_funcs malidp_mw_connector_funcs = { + .reset = malidp_mw_connector_reset, + .detect = malidp_mw_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = malidp_mw_connector_destroy, + .atomic_duplicate_state = malidp_mw_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const s16 rgb2yuv_coeffs_bt709_limited[MALIDP_COLORADJ_NUM_COEFFS] = { + 47, 157, 16, + -26, -87, 112, + 112, -102, -10, + 16, 128, 128 +}; + +static int +malidp_mw_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct malidp_mw_connector_state *mw_state = to_mw_state(conn_state); + struct malidp_drm *malidp = encoder->dev->dev_private; + struct drm_framebuffer *fb; + int i, n_planes; + + if (!conn_state->writeback_job || !conn_state->writeback_job->fb) + return 0; + + fb = conn_state->writeback_job->fb; + if ((fb->width != crtc_state->mode.hdisplay) || + (fb->height != crtc_state->mode.vdisplay)) { + DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n", + fb->width, fb->height); + return -EINVAL; + } + + mw_state->format = + malidp_hw_get_format_id(&malidp->dev->hw->map, SE_MEMWRITE, + fb->format->format); + if (mw_state->format == MALIDP_INVALID_FORMAT_ID) { + struct drm_format_name_buf format_name; + + DRM_DEBUG_KMS("Invalid pixel format %s\n", + drm_get_format_name(fb->format->format, + &format_name)); + return -EINVAL; + } + + n_planes = drm_format_num_planes(fb->format->format); + for (i = 0; i < n_planes; i++) { + struct drm_gem_cma_object *obj = drm_fb_cma_get_gem_obj(fb, i); + /* memory write buffers are never rotated */ + u8 alignment = malidp_hw_get_pitch_align(malidp->dev, 0); + + if (fb->pitches[i] & (alignment - 1)) { + DRM_DEBUG_KMS("Invalid pitch %u for plane %d\n", + fb->pitches[i], i); + return -EINVAL; + } + mw_state->pitches[i] = fb->pitches[i]; + mw_state->addrs[i] = obj->paddr + fb->offsets[i]; + } + mw_state->n_planes = n_planes; + + if (fb->format->is_yuv) + mw_state->rgb2yuv_coeffs = rgb2yuv_coeffs_bt709_limited; + + return 0; +} + +static const struct drm_encoder_helper_funcs malidp_mw_encoder_helper_funcs = { + .atomic_check = malidp_mw_encoder_atomic_check, +}; + +static u32 *get_writeback_formats(struct malidp_drm *malidp, int *n_formats) +{ + const struct malidp_hw_regmap *map = &malidp->dev->hw->map; + u32 *formats; + int n, i; + + formats = kcalloc(map->n_pixel_formats, sizeof(*formats), + GFP_KERNEL); + if (!formats) + return NULL; + + for (n = 0, i = 0; i < map->n_pixel_formats; i++) { + if (map->pixel_formats[i].layer & SE_MEMWRITE) + formats[n++] = map->pixel_formats[i].format; + } + + *n_formats = n; + + return formats; +} + +int malidp_mw_connector_init(struct drm_device *drm) +{ + struct malidp_drm *malidp = drm->dev_private; + u32 *formats; + int ret, n_formats; + + if (!malidp->dev->hw->enable_memwrite) + return 0; + + malidp->mw_connector.encoder.possible_crtcs = 1 << drm_crtc_index(&malidp->crtc); + drm_connector_helper_add(&malidp->mw_connector.base, + &malidp_mw_connector_helper_funcs); + + formats = get_writeback_formats(malidp, &n_formats); + if (!formats) + return -ENOMEM; + + ret = drm_writeback_connector_init(drm, &malidp->mw_connector, + &malidp_mw_connector_funcs, + &malidp_mw_encoder_helper_funcs, + formats, n_formats); + kfree(formats); + if (ret) + return ret; + + return 0; +} + +void malidp_mw_atomic_commit(struct drm_device *drm, + struct drm_atomic_state *old_state) +{ + struct malidp_drm *malidp = drm->dev_private; + struct drm_writeback_connector *mw_conn = &malidp->mw_connector; + struct drm_connector_state *conn_state = mw_conn->base.state; + struct malidp_hw_device *hwdev = malidp->dev; + struct malidp_mw_connector_state *mw_state; + + if (!conn_state) + return; + + mw_state = to_mw_state(conn_state); + + if (conn_state->writeback_job && conn_state->writeback_job->fb) { + struct drm_framebuffer *fb = conn_state->writeback_job->fb; + + DRM_DEV_DEBUG_DRIVER(drm->dev, + "Enable memwrite %ux%u:%d %pad fmt: %u\n", + fb->width, fb->height, + mw_state->pitches[0], + &mw_state->addrs[0], + mw_state->format); + + drm_writeback_queue_job(mw_conn, conn_state->writeback_job); + conn_state->writeback_job = NULL; + hwdev->hw->enable_memwrite(hwdev, mw_state->addrs, + mw_state->pitches, mw_state->n_planes, + fb->width, fb->height, mw_state->format, + !mw_state->rgb2yuv_initialized ? + mw_state->rgb2yuv_coeffs : NULL); + mw_state->rgb2yuv_initialized = !!mw_state->rgb2yuv_coeffs; + } else { + DRM_DEV_DEBUG_DRIVER(drm->dev, "Disable memwrite\n"); + hwdev->hw->disable_memwrite(hwdev); + } +} diff --git a/drivers/gpu/drm/arm/malidp_mw.h b/drivers/gpu/drm/arm/malidp_mw.h new file mode 100644 index 000000000..19a007676 --- /dev/null +++ b/drivers/gpu/drm/arm/malidp_mw.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. + * Author: Brian Starkey <brian.starkey@arm.com> + * + */ + +#ifndef __MALIDP_MW_H__ +#define __MALIDP_MW_H__ + +int malidp_mw_connector_init(struct drm_device *drm); +void malidp_mw_atomic_commit(struct drm_device *drm, + struct drm_atomic_state *old_state); +#endif diff --git a/drivers/gpu/drm/arm/malidp_planes.c b/drivers/gpu/drm/arm/malidp_planes.c new file mode 100644 index 000000000..a347b2740 --- /dev/null +++ b/drivers/gpu/drm/arm/malidp_planes.c @@ -0,0 +1,533 @@ +/* + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * ARM Mali DP plane manipulation routines. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_print.h> + +#include "malidp_hw.h" +#include "malidp_drv.h" + +/* Layer specific register offsets */ +#define MALIDP_LAYER_FORMAT 0x000 +#define LAYER_FORMAT_MASK 0x3f +#define MALIDP_LAYER_CONTROL 0x004 +#define LAYER_ENABLE (1 << 0) +#define LAYER_FLOWCFG_MASK 7 +#define LAYER_FLOWCFG(x) (((x) & LAYER_FLOWCFG_MASK) << 1) +#define LAYER_FLOWCFG_SCALE_SE 3 +#define LAYER_ROT_OFFSET 8 +#define LAYER_H_FLIP (1 << 10) +#define LAYER_V_FLIP (1 << 11) +#define LAYER_ROT_MASK (0xf << 8) +#define LAYER_COMP_MASK (0x3 << 12) +#define LAYER_COMP_PIXEL (0x3 << 12) +#define LAYER_COMP_PLANE (0x2 << 12) +#define LAYER_ALPHA_OFFSET (16) +#define LAYER_ALPHA_MASK (0xff) +#define LAYER_ALPHA(x) (((x) & LAYER_ALPHA_MASK) << LAYER_ALPHA_OFFSET) +#define MALIDP_LAYER_COMPOSE 0x008 +#define MALIDP_LAYER_SIZE 0x00c +#define LAYER_H_VAL(x) (((x) & 0x1fff) << 0) +#define LAYER_V_VAL(x) (((x) & 0x1fff) << 16) +#define MALIDP_LAYER_COMP_SIZE 0x010 +#define MALIDP_LAYER_OFFSET 0x014 +#define MALIDP550_LS_ENABLE 0x01c +#define MALIDP550_LS_R1_IN_SIZE 0x020 + +/* + * This 4-entry look-up-table is used to determine the full 8-bit alpha value + * for formats with 1- or 2-bit alpha channels. + * We set it to give 100%/0% opacity for 1-bit formats and 100%/66%/33%/0% + * opacity for 2-bit formats. + */ +#define MALIDP_ALPHA_LUT 0xffaa5500 + +static void malidp_de_plane_destroy(struct drm_plane *plane) +{ + struct malidp_plane *mp = to_malidp_plane(plane); + + drm_plane_cleanup(plane); + kfree(mp); +} + +/* + * Replicate what the default ->reset hook does: free the state pointer and + * allocate a new empty object. We just need enough space to store + * a malidp_plane_state instead of a drm_plane_state. + */ +static void malidp_plane_reset(struct drm_plane *plane) +{ + struct malidp_plane_state *state = to_malidp_plane_state(plane->state); + + if (state) + __drm_atomic_helper_plane_destroy_state(&state->base); + kfree(state); + plane->state = NULL; + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) { + state->base.plane = plane; + state->base.rotation = DRM_MODE_ROTATE_0; + plane->state = &state->base; + } +} + +static struct +drm_plane_state *malidp_duplicate_plane_state(struct drm_plane *plane) +{ + struct malidp_plane_state *state, *m_state; + + if (!plane->state) + return NULL; + + state = kmalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + m_state = to_malidp_plane_state(plane->state); + __drm_atomic_helper_plane_duplicate_state(plane, &state->base); + state->rotmem_size = m_state->rotmem_size; + state->format = m_state->format; + state->n_planes = m_state->n_planes; + + return &state->base; +} + +static void malidp_destroy_plane_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct malidp_plane_state *m_state = to_malidp_plane_state(state); + + __drm_atomic_helper_plane_destroy_state(state); + kfree(m_state); +} + +static void malidp_plane_atomic_print_state(struct drm_printer *p, + const struct drm_plane_state *state) +{ + struct malidp_plane_state *ms = to_malidp_plane_state(state); + + drm_printf(p, "\trotmem_size=%u\n", ms->rotmem_size); + drm_printf(p, "\tformat_id=%u\n", ms->format); + drm_printf(p, "\tn_planes=%u\n", ms->n_planes); +} + +static const struct drm_plane_funcs malidp_de_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = malidp_de_plane_destroy, + .reset = malidp_plane_reset, + .atomic_duplicate_state = malidp_duplicate_plane_state, + .atomic_destroy_state = malidp_destroy_plane_state, + .atomic_print_state = malidp_plane_atomic_print_state, +}; + +static int malidp_se_check_scaling(struct malidp_plane *mp, + struct drm_plane_state *state) +{ + struct drm_crtc_state *crtc_state = + drm_atomic_get_existing_crtc_state(state->state, state->crtc); + struct malidp_crtc_state *mc; + u32 src_w, src_h; + int ret; + + if (!crtc_state) + return -EINVAL; + + mc = to_malidp_crtc_state(crtc_state); + + ret = drm_atomic_helper_check_plane_state(state, crtc_state, + 0, INT_MAX, true, true); + if (ret) + return ret; + + if (state->rotation & MALIDP_ROTATED_MASK) { + src_w = state->src_h >> 16; + src_h = state->src_w >> 16; + } else { + src_w = state->src_w >> 16; + src_h = state->src_h >> 16; + } + + if ((state->crtc_w == src_w) && (state->crtc_h == src_h)) { + /* Scaling not necessary for this plane. */ + mc->scaled_planes_mask &= ~(mp->layer->id); + return 0; + } + + if (mp->layer->id & (DE_SMART | DE_GRAPHICS2)) + return -EINVAL; + + mc->scaled_planes_mask |= mp->layer->id; + /* Defer scaling requirements calculation to the crtc check. */ + return 0; +} + +static int malidp_de_plane_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct malidp_plane *mp = to_malidp_plane(plane); + struct malidp_plane_state *ms = to_malidp_plane_state(state); + bool rotated = state->rotation & MALIDP_ROTATED_MASK; + struct drm_framebuffer *fb; + int i, ret; + + if (!state->crtc || !state->fb) + return 0; + + fb = state->fb; + + ms->format = malidp_hw_get_format_id(&mp->hwdev->hw->map, + mp->layer->id, + fb->format->format); + if (ms->format == MALIDP_INVALID_FORMAT_ID) + return -EINVAL; + + ms->n_planes = fb->format->num_planes; + for (i = 0; i < ms->n_planes; i++) { + u8 alignment = malidp_hw_get_pitch_align(mp->hwdev, rotated); + if (fb->pitches[i] & (alignment - 1)) { + DRM_DEBUG_KMS("Invalid pitch %u for plane %d\n", + fb->pitches[i], i); + return -EINVAL; + } + } + + if ((state->crtc_w > mp->hwdev->max_line_size) || + (state->crtc_h > mp->hwdev->max_line_size) || + (state->crtc_w < mp->hwdev->min_line_size) || + (state->crtc_h < mp->hwdev->min_line_size)) + return -EINVAL; + + /* + * DP550/650 video layers can accept 3 plane formats only if + * fb->pitches[1] == fb->pitches[2] since they don't have a + * third plane stride register. + */ + if (ms->n_planes == 3 && + !(mp->hwdev->hw->features & MALIDP_DEVICE_LV_HAS_3_STRIDES) && + (state->fb->pitches[1] != state->fb->pitches[2])) + return -EINVAL; + + ret = malidp_se_check_scaling(mp, state); + if (ret) + return ret; + + /* packed RGB888 / BGR888 can't be rotated or flipped */ + if (state->rotation != DRM_MODE_ROTATE_0 && + (fb->format->format == DRM_FORMAT_RGB888 || + fb->format->format == DRM_FORMAT_BGR888)) + return -EINVAL; + + ms->rotmem_size = 0; + if (state->rotation & MALIDP_ROTATED_MASK) { + int val; + + val = mp->hwdev->hw->rotmem_required(mp->hwdev, state->crtc_w, + state->crtc_h, + fb->format->format); + if (val < 0) + return val; + + ms->rotmem_size = val; + } + + return 0; +} + +static void malidp_de_set_plane_pitches(struct malidp_plane *mp, + int num_planes, unsigned int pitches[3]) +{ + int i; + int num_strides = num_planes; + + if (!mp->layer->stride_offset) + return; + + if (num_planes == 3) + num_strides = (mp->hwdev->hw->features & + MALIDP_DEVICE_LV_HAS_3_STRIDES) ? 3 : 2; + + for (i = 0; i < num_strides; ++i) + malidp_hw_write(mp->hwdev, pitches[i], + mp->layer->base + + mp->layer->stride_offset + i * 4); +} + +static const s16 +malidp_yuv2rgb_coeffs[][DRM_COLOR_RANGE_MAX][MALIDP_COLORADJ_NUM_COEFFS] = { + [DRM_COLOR_YCBCR_BT601][DRM_COLOR_YCBCR_LIMITED_RANGE] = { + 1192, 0, 1634, + 1192, -401, -832, + 1192, 2066, 0, + 64, 512, 512 + }, + [DRM_COLOR_YCBCR_BT601][DRM_COLOR_YCBCR_FULL_RANGE] = { + 1024, 0, 1436, + 1024, -352, -731, + 1024, 1815, 0, + 0, 512, 512 + }, + [DRM_COLOR_YCBCR_BT709][DRM_COLOR_YCBCR_LIMITED_RANGE] = { + 1192, 0, 1836, + 1192, -218, -546, + 1192, 2163, 0, + 64, 512, 512 + }, + [DRM_COLOR_YCBCR_BT709][DRM_COLOR_YCBCR_FULL_RANGE] = { + 1024, 0, 1613, + 1024, -192, -479, + 1024, 1900, 0, + 0, 512, 512 + }, + [DRM_COLOR_YCBCR_BT2020][DRM_COLOR_YCBCR_LIMITED_RANGE] = { + 1024, 0, 1476, + 1024, -165, -572, + 1024, 1884, 0, + 0, 512, 512 + }, + [DRM_COLOR_YCBCR_BT2020][DRM_COLOR_YCBCR_FULL_RANGE] = { + 1024, 0, 1510, + 1024, -168, -585, + 1024, 1927, 0, + 0, 512, 512 + } +}; + +static void malidp_de_set_color_encoding(struct malidp_plane *plane, + enum drm_color_encoding enc, + enum drm_color_range range) +{ + unsigned int i; + + for (i = 0; i < MALIDP_COLORADJ_NUM_COEFFS; i++) { + /* coefficients are signed, two's complement values */ + malidp_hw_write(plane->hwdev, malidp_yuv2rgb_coeffs[enc][range][i], + plane->layer->base + plane->layer->yuv2rgb_offset + + i * 4); + } +} + +static void malidp_de_plane_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct malidp_plane *mp; + struct malidp_plane_state *ms = to_malidp_plane_state(plane->state); + u32 src_w, src_h, dest_w, dest_h, val; + int i; + bool format_has_alpha = plane->state->fb->format->has_alpha; + + mp = to_malidp_plane(plane); + + /* convert src values from Q16 fixed point to integer */ + src_w = plane->state->src_w >> 16; + src_h = plane->state->src_h >> 16; + dest_w = plane->state->crtc_w; + dest_h = plane->state->crtc_h; + + val = malidp_hw_read(mp->hwdev, mp->layer->base); + val = (val & ~LAYER_FORMAT_MASK) | ms->format; + malidp_hw_write(mp->hwdev, val, mp->layer->base); + + for (i = 0; i < ms->n_planes; i++) { + /* calculate the offset for the layer's plane registers */ + u16 ptr = mp->layer->ptr + (i << 4); + dma_addr_t fb_addr = drm_fb_cma_get_gem_addr(plane->state->fb, + plane->state, i); + + malidp_hw_write(mp->hwdev, lower_32_bits(fb_addr), ptr); + malidp_hw_write(mp->hwdev, upper_32_bits(fb_addr), ptr + 4); + } + malidp_de_set_plane_pitches(mp, ms->n_planes, + plane->state->fb->pitches); + + if ((plane->state->color_encoding != old_state->color_encoding) || + (plane->state->color_range != old_state->color_range)) + malidp_de_set_color_encoding(mp, plane->state->color_encoding, + plane->state->color_range); + + malidp_hw_write(mp->hwdev, LAYER_H_VAL(src_w) | LAYER_V_VAL(src_h), + mp->layer->base + MALIDP_LAYER_SIZE); + + malidp_hw_write(mp->hwdev, LAYER_H_VAL(dest_w) | LAYER_V_VAL(dest_h), + mp->layer->base + MALIDP_LAYER_COMP_SIZE); + + malidp_hw_write(mp->hwdev, LAYER_H_VAL(plane->state->crtc_x) | + LAYER_V_VAL(plane->state->crtc_y), + mp->layer->base + MALIDP_LAYER_OFFSET); + + if (mp->layer->id == DE_SMART) + malidp_hw_write(mp->hwdev, + LAYER_H_VAL(src_w) | LAYER_V_VAL(src_h), + mp->layer->base + MALIDP550_LS_R1_IN_SIZE); + + /* first clear the rotation bits */ + val = malidp_hw_read(mp->hwdev, mp->layer->base + MALIDP_LAYER_CONTROL); + val &= ~LAYER_ROT_MASK; + + /* setup the rotation and axis flip bits */ + if (plane->state->rotation & DRM_MODE_ROTATE_MASK) + val |= ilog2(plane->state->rotation & DRM_MODE_ROTATE_MASK) << + LAYER_ROT_OFFSET; + if (plane->state->rotation & DRM_MODE_REFLECT_X) + val |= LAYER_H_FLIP; + if (plane->state->rotation & DRM_MODE_REFLECT_Y) + val |= LAYER_V_FLIP; + + val &= ~LAYER_COMP_MASK; + if (format_has_alpha) { + + /* + * always enable pixel alpha blending until we have a way + * to change blend modes + */ + val |= LAYER_COMP_PIXEL; + } else { + + /* + * do not enable pixel alpha blending as the color channel + * does not have any alpha information + */ + val |= LAYER_COMP_PLANE; + + /* Set layer alpha coefficient to 0xff ie fully opaque */ + val |= LAYER_ALPHA(0xff); + } + + val &= ~LAYER_FLOWCFG(LAYER_FLOWCFG_MASK); + if (plane->state->crtc) { + struct malidp_crtc_state *m = + to_malidp_crtc_state(plane->state->crtc->state); + + if (m->scaler_config.scale_enable && + m->scaler_config.plane_src_id == mp->layer->id) + val |= LAYER_FLOWCFG(LAYER_FLOWCFG_SCALE_SE); + } + + /* set the 'enable layer' bit */ + val |= LAYER_ENABLE; + + malidp_hw_write(mp->hwdev, val, + mp->layer->base + MALIDP_LAYER_CONTROL); +} + +static void malidp_de_plane_disable(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct malidp_plane *mp = to_malidp_plane(plane); + + malidp_hw_clearbits(mp->hwdev, + LAYER_ENABLE | LAYER_FLOWCFG(LAYER_FLOWCFG_MASK), + mp->layer->base + MALIDP_LAYER_CONTROL); +} + +static const struct drm_plane_helper_funcs malidp_de_plane_helper_funcs = { + .atomic_check = malidp_de_plane_check, + .atomic_update = malidp_de_plane_update, + .atomic_disable = malidp_de_plane_disable, +}; + +int malidp_de_planes_init(struct drm_device *drm) +{ + struct malidp_drm *malidp = drm->dev_private; + const struct malidp_hw_regmap *map = &malidp->dev->hw->map; + struct malidp_plane *plane = NULL; + enum drm_plane_type plane_type; + unsigned long crtcs = BIT(drm->mode_config.num_crtc); + unsigned long flags = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_180 | + DRM_MODE_ROTATE_270 | DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y; + u32 *formats; + int ret, i, j, n; + + formats = kcalloc(map->n_pixel_formats, sizeof(*formats), GFP_KERNEL); + if (!formats) { + ret = -ENOMEM; + goto cleanup; + } + + for (i = 0; i < map->n_layers; i++) { + u8 id = map->layers[i].id; + + plane = kzalloc(sizeof(*plane), GFP_KERNEL); + if (!plane) { + ret = -ENOMEM; + goto cleanup; + } + + /* build the list of DRM supported formats based on the map */ + for (n = 0, j = 0; j < map->n_pixel_formats; j++) { + if ((map->pixel_formats[j].layer & id) == id) + formats[n++] = map->pixel_formats[j].format; + } + + plane_type = (i == 0) ? DRM_PLANE_TYPE_PRIMARY : + DRM_PLANE_TYPE_OVERLAY; + ret = drm_universal_plane_init(drm, &plane->base, crtcs, + &malidp_de_plane_funcs, formats, + n, NULL, plane_type, NULL); + if (ret < 0) + goto cleanup; + + drm_plane_helper_add(&plane->base, + &malidp_de_plane_helper_funcs); + plane->hwdev = malidp->dev; + plane->layer = &map->layers[i]; + + if (id == DE_SMART) { + /* + * Enable the first rectangle in the SMART layer to be + * able to use it as a drm plane. + */ + malidp_hw_write(malidp->dev, 1, + plane->layer->base + MALIDP550_LS_ENABLE); + /* Skip the features which the SMART layer doesn't have. */ + continue; + } + + drm_plane_create_rotation_property(&plane->base, DRM_MODE_ROTATE_0, flags); + malidp_hw_write(malidp->dev, MALIDP_ALPHA_LUT, + plane->layer->base + MALIDP_LAYER_COMPOSE); + + /* Attach the YUV->RGB property only to video layers */ + if (id & (DE_VIDEO1 | DE_VIDEO2)) { + /* default encoding for YUV->RGB is BT601 NARROW */ + enum drm_color_encoding enc = DRM_COLOR_YCBCR_BT601; + enum drm_color_range range = DRM_COLOR_YCBCR_LIMITED_RANGE; + + ret = drm_plane_create_color_properties(&plane->base, + BIT(DRM_COLOR_YCBCR_BT601) | \ + BIT(DRM_COLOR_YCBCR_BT709) | \ + BIT(DRM_COLOR_YCBCR_BT2020), + BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) | \ + BIT(DRM_COLOR_YCBCR_FULL_RANGE), + enc, range); + if (!ret) + /* program the HW registers */ + malidp_de_set_color_encoding(plane, enc, range); + else + DRM_WARN("Failed to create video layer %d color properties\n", id); + } + } + + kfree(formats); + + return 0; + +cleanup: + kfree(formats); + + return ret; +} diff --git a/drivers/gpu/drm/arm/malidp_regs.h b/drivers/gpu/drm/arm/malidp_regs.h new file mode 100644 index 000000000..6ffe84977 --- /dev/null +++ b/drivers/gpu/drm/arm/malidp_regs.h @@ -0,0 +1,264 @@ +/* + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved. + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms + * of such GNU licence. + * + * ARM Mali DP500/DP550/DP650 registers definition. + */ + +#ifndef __MALIDP_REGS_H__ +#define __MALIDP_REGS_H__ + +/* + * abbreviations used: + * - DC - display core (general settings) + * - DE - display engine + * - SE - scaling engine + */ + +/* interrupt bit masks */ +#define MALIDP_DE_IRQ_UNDERRUN (1 << 0) + +#define MALIDP500_DE_IRQ_AXI_ERR (1 << 4) +#define MALIDP500_DE_IRQ_VSYNC (1 << 5) +#define MALIDP500_DE_IRQ_PROG_LINE (1 << 6) +#define MALIDP500_DE_IRQ_SATURATION (1 << 7) +#define MALIDP500_DE_IRQ_CONF_VALID (1 << 8) +#define MALIDP500_DE_IRQ_CONF_MODE (1 << 11) +#define MALIDP500_DE_IRQ_CONF_ACTIVE (1 << 17) +#define MALIDP500_DE_IRQ_PM_ACTIVE (1 << 18) +#define MALIDP500_DE_IRQ_TESTMODE_ACTIVE (1 << 19) +#define MALIDP500_DE_IRQ_FORCE_BLNK_ACTIVE (1 << 24) +#define MALIDP500_DE_IRQ_AXI_BUSY (1 << 28) +#define MALIDP500_DE_IRQ_GLOBAL (1 << 31) +#define MALIDP500_SE_IRQ_CONF_MODE (1 << 0) +#define MALIDP500_SE_IRQ_CONF_VALID (1 << 4) +#define MALIDP500_SE_IRQ_INIT_BUSY (1 << 5) +#define MALIDP500_SE_IRQ_AXI_ERROR (1 << 8) +#define MALIDP500_SE_IRQ_OVERRUN (1 << 9) +#define MALIDP500_SE_IRQ_PROG_LINE1 (1 << 12) +#define MALIDP500_SE_IRQ_PROG_LINE2 (1 << 13) +#define MALIDP500_SE_IRQ_CONF_ACTIVE (1 << 17) +#define MALIDP500_SE_IRQ_PM_ACTIVE (1 << 18) +#define MALIDP500_SE_IRQ_AXI_BUSY (1 << 28) +#define MALIDP500_SE_IRQ_GLOBAL (1 << 31) + +#define MALIDP550_DE_IRQ_SATURATION (1 << 8) +#define MALIDP550_DE_IRQ_VSYNC (1 << 12) +#define MALIDP550_DE_IRQ_PROG_LINE (1 << 13) +#define MALIDP550_DE_IRQ_AXI_ERR (1 << 16) +#define MALIDP550_SE_IRQ_EOW (1 << 0) +#define MALIDP550_SE_IRQ_AXI_ERR (1 << 16) +#define MALIDP550_SE_IRQ_OVR (1 << 17) +#define MALIDP550_SE_IRQ_IBSY (1 << 18) +#define MALIDP550_DC_IRQ_CONF_VALID (1 << 0) +#define MALIDP550_DC_IRQ_CONF_MODE (1 << 4) +#define MALIDP550_DC_IRQ_CONF_ACTIVE (1 << 16) +#define MALIDP550_DC_IRQ_DE (1 << 20) +#define MALIDP550_DC_IRQ_SE (1 << 24) + +#define MALIDP650_DE_IRQ_DRIFT (1 << 4) +#define MALIDP650_DE_IRQ_ACEV1 (1 << 17) +#define MALIDP650_DE_IRQ_ACEV2 (1 << 18) +#define MALIDP650_DE_IRQ_ACEG (1 << 19) +#define MALIDP650_DE_IRQ_AXIEP (1 << 28) + +/* bit masks that are common between products */ +#define MALIDP_CFG_VALID (1 << 0) +#define MALIDP_DISP_FUNC_GAMMA (1 << 0) +#define MALIDP_DISP_FUNC_CADJ (1 << 4) +#define MALIDP_DISP_FUNC_ILACED (1 << 8) +#define MALIDP_SCALE_ENGINE_EN (1 << 16) +#define MALIDP_SE_MEMWRITE_EN (2 << 5) + +/* register offsets for IRQ management */ +#define MALIDP_REG_STATUS 0x00000 +#define MALIDP_REG_SETIRQ 0x00004 +#define MALIDP_REG_MASKIRQ 0x00008 +#define MALIDP_REG_CLEARIRQ 0x0000c + +/* register offsets */ +#define MALIDP_DE_CORE_ID 0x00018 +#define MALIDP_DE_DISPLAY_FUNC 0x00020 + +/* these offsets are relative to MALIDP5x0_TIMINGS_BASE */ +#define MALIDP_DE_H_TIMINGS 0x0 +#define MALIDP_DE_V_TIMINGS 0x4 +#define MALIDP_DE_SYNC_WIDTH 0x8 +#define MALIDP_DE_HV_ACTIVE 0xc + +/* Stride register offsets relative to Lx_BASE */ +#define MALIDP_DE_LG_STRIDE 0x18 +#define MALIDP_DE_LV_STRIDE0 0x18 +#define MALIDP550_DE_LS_R1_STRIDE 0x28 + +/* macros to set values into registers */ +#define MALIDP_DE_H_FRONTPORCH(x) (((x) & 0xfff) << 0) +#define MALIDP_DE_H_BACKPORCH(x) (((x) & 0x3ff) << 16) +#define MALIDP500_DE_V_FRONTPORCH(x) (((x) & 0xff) << 0) +#define MALIDP550_DE_V_FRONTPORCH(x) (((x) & 0xfff) << 0) +#define MALIDP_DE_V_BACKPORCH(x) (((x) & 0xff) << 16) +#define MALIDP_DE_H_SYNCWIDTH(x) (((x) & 0x3ff) << 0) +#define MALIDP_DE_V_SYNCWIDTH(x) (((x) & 0xff) << 16) +#define MALIDP_DE_H_ACTIVE(x) (((x) & 0x1fff) << 0) +#define MALIDP_DE_V_ACTIVE(x) (((x) & 0x1fff) << 16) + +#define MALIDP_PRODUCT_ID(__core_id) ((u32)(__core_id) >> 16) + +/* register offsets relative to MALIDP5x0_COEFFS_BASE */ +#define MALIDP_COLOR_ADJ_COEF 0x00000 +#define MALIDP_COEF_TABLE_ADDR 0x00030 +#define MALIDP_COEF_TABLE_DATA 0x00034 + +/* Scaling engine registers and masks. */ +#define MALIDP_SE_SCALING_EN (1 << 0) +#define MALIDP_SE_ALPHA_EN (1 << 1) +#define MALIDP_SE_ENH_MASK 3 +#define MALIDP_SE_ENH(x) (((x) & MALIDP_SE_ENH_MASK) << 2) +#define MALIDP_SE_RGBO_IF_EN (1 << 4) +#define MALIDP550_SE_CTL_SEL_MASK 7 +#define MALIDP550_SE_CTL_VCSEL(x) \ + (((x) & MALIDP550_SE_CTL_SEL_MASK) << 20) +#define MALIDP550_SE_CTL_HCSEL(x) \ + (((x) & MALIDP550_SE_CTL_SEL_MASK) << 16) + +/* Blocks with offsets from SE_CONTROL register. */ +#define MALIDP_SE_LAYER_CONTROL 0x14 +#define MALIDP_SE_L0_IN_SIZE 0x00 +#define MALIDP_SE_L0_OUT_SIZE 0x04 +#define MALIDP_SE_SET_V_SIZE(x) (((x) & 0x1fff) << 16) +#define MALIDP_SE_SET_H_SIZE(x) (((x) & 0x1fff) << 0) +#define MALIDP_SE_SCALING_CONTROL 0x24 +#define MALIDP_SE_H_INIT_PH 0x00 +#define MALIDP_SE_H_DELTA_PH 0x04 +#define MALIDP_SE_V_INIT_PH 0x08 +#define MALIDP_SE_V_DELTA_PH 0x0c +#define MALIDP_SE_COEFFTAB_ADDR 0x10 +#define MALIDP_SE_COEFFTAB_ADDR_MASK 0x7f +#define MALIDP_SE_V_COEFFTAB (1 << 8) +#define MALIDP_SE_H_COEFFTAB (1 << 9) +#define MALIDP_SE_SET_V_COEFFTAB_ADDR(x) \ + (MALIDP_SE_V_COEFFTAB | ((x) & MALIDP_SE_COEFFTAB_ADDR_MASK)) +#define MALIDP_SE_SET_H_COEFFTAB_ADDR(x) \ + (MALIDP_SE_H_COEFFTAB | ((x) & MALIDP_SE_COEFFTAB_ADDR_MASK)) +#define MALIDP_SE_COEFFTAB_DATA 0x14 +#define MALIDP_SE_COEFFTAB_DATA_MASK 0x3fff +#define MALIDP_SE_SET_COEFFTAB_DATA(x) \ + ((x) & MALIDP_SE_COEFFTAB_DATA_MASK) +/* Enhance coeffents reigster offset */ +#define MALIDP_SE_IMAGE_ENH 0x3C +/* ENH_LIMITS offset 0x0 */ +#define MALIDP_SE_ENH_LOW_LEVEL 24 +#define MALIDP_SE_ENH_HIGH_LEVEL 63 +#define MALIDP_SE_ENH_LIMIT_MASK 0xfff +#define MALIDP_SE_SET_ENH_LIMIT_LOW(x) \ + ((x) & MALIDP_SE_ENH_LIMIT_MASK) +#define MALIDP_SE_SET_ENH_LIMIT_HIGH(x) \ + (((x) & MALIDP_SE_ENH_LIMIT_MASK) << 16) +#define MALIDP_SE_ENH_COEFF0 0x04 + + +/* register offsets relative to MALIDP5x0_SE_MEMWRITE_BASE */ +#define MALIDP_MW_FORMAT 0x00000 +#define MALIDP_MW_P1_STRIDE 0x00004 +#define MALIDP_MW_P2_STRIDE 0x00008 +#define MALIDP_MW_P1_PTR_LOW 0x0000c +#define MALIDP_MW_P1_PTR_HIGH 0x00010 +#define MALIDP_MW_P2_PTR_LOW 0x0002c +#define MALIDP_MW_P2_PTR_HIGH 0x00030 + +/* register offsets and bits specific to DP500 */ +#define MALIDP500_ADDR_SPACE_SIZE 0x01000 +#define MALIDP500_DC_BASE 0x00000 +#define MALIDP500_DC_CONTROL 0x0000c +#define MALIDP500_DC_CONFIG_REQ (1 << 17) +#define MALIDP500_HSYNCPOL (1 << 20) +#define MALIDP500_VSYNCPOL (1 << 21) +#define MALIDP500_DC_CLEAR_MASK 0x300fff +#define MALIDP500_DE_LINE_COUNTER 0x00010 +#define MALIDP500_DE_AXI_CONTROL 0x00014 +#define MALIDP500_DE_SECURE_CTRL 0x0001c +#define MALIDP500_DE_CHROMA_KEY 0x00024 +#define MALIDP500_TIMINGS_BASE 0x00028 + +#define MALIDP500_CONFIG_3D 0x00038 +#define MALIDP500_BGND_COLOR 0x0003c +#define MALIDP500_OUTPUT_DEPTH 0x00044 +#define MALIDP500_COEFFS_BASE 0x00078 + +/* + * The YUV2RGB coefficients on the DP500 are not in the video layer's register + * block. They belong in a separate block above the layer's registers, hence + * the negative offset. + */ +#define MALIDP500_LV_YUV2RGB ((s16)(-0xB8)) +#define MALIDP500_DE_LV_BASE 0x00100 +#define MALIDP500_DE_LV_PTR_BASE 0x00124 +#define MALIDP500_DE_LG1_BASE 0x00200 +#define MALIDP500_DE_LG1_PTR_BASE 0x0021c +#define MALIDP500_DE_LG2_BASE 0x00300 +#define MALIDP500_DE_LG2_PTR_BASE 0x0031c +#define MALIDP500_SE_BASE 0x00c00 +#define MALIDP500_SE_CONTROL 0x00c0c +#define MALIDP500_SE_MEMWRITE_OUT_SIZE 0x00c2c +#define MALIDP500_SE_RGB_YUV_COEFFS 0x00C74 +#define MALIDP500_SE_MEMWRITE_BASE 0x00e00 +#define MALIDP500_DC_IRQ_BASE 0x00f00 +#define MALIDP500_CONFIG_VALID 0x00f00 +#define MALIDP500_CONFIG_ID 0x00fd4 + +/* register offsets and bits specific to DP550/DP650 */ +#define MALIDP550_ADDR_SPACE_SIZE 0x10000 +#define MALIDP550_DE_CONTROL 0x00010 +#define MALIDP550_DE_LINE_COUNTER 0x00014 +#define MALIDP550_DE_AXI_CONTROL 0x00018 +#define MALIDP550_DE_QOS 0x0001c +#define MALIDP550_TIMINGS_BASE 0x00030 +#define MALIDP550_HSYNCPOL (1 << 12) +#define MALIDP550_VSYNCPOL (1 << 28) + +#define MALIDP550_DE_DISP_SIDEBAND 0x00040 +#define MALIDP550_DE_BGND_COLOR 0x00044 +#define MALIDP550_DE_OUTPUT_DEPTH 0x0004c +#define MALIDP550_COEFFS_BASE 0x00050 +#define MALIDP550_LV_YUV2RGB 0x00084 +#define MALIDP550_DE_LV1_BASE 0x00100 +#define MALIDP550_DE_LV1_PTR_BASE 0x00124 +#define MALIDP550_DE_LV2_BASE 0x00200 +#define MALIDP550_DE_LV2_PTR_BASE 0x00224 +#define MALIDP550_DE_LG_BASE 0x00300 +#define MALIDP550_DE_LG_PTR_BASE 0x0031c +#define MALIDP550_DE_LS_BASE 0x00400 +#define MALIDP550_DE_LS_PTR_BASE 0x0042c +#define MALIDP550_DE_PERF_BASE 0x00500 +#define MALIDP550_SE_BASE 0x08000 +#define MALIDP550_SE_CONTROL 0x08010 +#define MALIDP550_SE_MEMWRITE_ONESHOT (1 << 7) +#define MALIDP550_SE_MEMWRITE_OUT_SIZE 0x08030 +#define MALIDP550_SE_RGB_YUV_COEFFS 0x08078 +#define MALIDP550_SE_MEMWRITE_BASE 0x08100 +#define MALIDP550_DC_BASE 0x0c000 +#define MALIDP550_DC_CONTROL 0x0c010 +#define MALIDP550_DC_CONFIG_REQ (1 << 16) +#define MALIDP550_CONFIG_VALID 0x0c014 +#define MALIDP550_CONFIG_ID 0x0ffd4 + +/* + * Starting with DP550 the register map blocks has been standardised to the + * following layout: + * + * Offset Block registers + * 0x00000 Display Engine + * 0x08000 Scaling Engine + * 0x0c000 Display Core + * 0x10000 Secure control + * + * The old DP500 IP mixes some DC with the DE registers, hence the need + * for a mapping structure. + */ + +#endif /* __MALIDP_REGS_H__ */ |