diff options
Diffstat (limited to 'kernel/drivers/gpu/drm/rcar-du')
26 files changed, 4507 insertions, 0 deletions
diff --git a/kernel/drivers/gpu/drm/rcar-du/Kconfig b/kernel/drivers/gpu/drm/rcar-du/Kconfig new file mode 100644 index 000000000..11485a4a1 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/Kconfig @@ -0,0 +1,27 @@ +config DRM_RCAR_DU + tristate "DRM Support for R-Car Display Unit" + depends on DRM && ARM && HAVE_DMA_ATTRS + depends on ARCH_SHMOBILE || COMPILE_TEST + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_GEM_CMA_HELPER + select DRM_KMS_FB_HELPER + select VIDEOMODE_HELPERS + help + Choose this option if you have an R-Car chipset. + If M is selected the module will be called rcar-du-drm. + +config DRM_RCAR_HDMI + bool "R-Car DU HDMI Encoder Support" + depends on DRM_RCAR_DU + depends on OF + help + Enable support for external HDMI encoders. + +config DRM_RCAR_LVDS + bool "R-Car DU LVDS Encoder Support" + depends on DRM_RCAR_DU + depends on ARCH_R8A7790 || ARCH_R8A7791 || COMPILE_TEST + help + Enable support for the R-Car Display Unit embedded LVDS encoders + (currently only on R8A7790 and R8A7791). diff --git a/kernel/drivers/gpu/drm/rcar-du/Makefile b/kernel/drivers/gpu/drm/rcar-du/Makefile new file mode 100644 index 000000000..05de1c409 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/Makefile @@ -0,0 +1,14 @@ +rcar-du-drm-y := rcar_du_crtc.o \ + rcar_du_drv.o \ + rcar_du_encoder.o \ + rcar_du_group.o \ + rcar_du_kms.o \ + rcar_du_lvdscon.o \ + rcar_du_plane.o \ + rcar_du_vgacon.o + +rcar-du-drm-$(CONFIG_DRM_RCAR_HDMI) += rcar_du_hdmicon.o \ + rcar_du_hdmienc.o +rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o + +obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_crtc.c new file mode 100644 index 000000000..7d0b8ef9b --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -0,0 +1,638 @@ +/* + * rcar_du_crtc.c -- R-Car Display Unit CRTCs + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/mutex.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_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> + +#include "rcar_du_crtc.h" +#include "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_plane.h" +#include "rcar_du_regs.h" + +static u32 rcar_du_crtc_read(struct rcar_du_crtc *rcrtc, u32 reg) +{ + struct rcar_du_device *rcdu = rcrtc->group->dev; + + return rcar_du_read(rcdu, rcrtc->mmio_offset + reg); +} + +static void rcar_du_crtc_write(struct rcar_du_crtc *rcrtc, u32 reg, u32 data) +{ + struct rcar_du_device *rcdu = rcrtc->group->dev; + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, data); +} + +static void rcar_du_crtc_clr(struct rcar_du_crtc *rcrtc, u32 reg, u32 clr) +{ + struct rcar_du_device *rcdu = rcrtc->group->dev; + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, + rcar_du_read(rcdu, rcrtc->mmio_offset + reg) & ~clr); +} + +static void rcar_du_crtc_set(struct rcar_du_crtc *rcrtc, u32 reg, u32 set) +{ + struct rcar_du_device *rcdu = rcrtc->group->dev; + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, + rcar_du_read(rcdu, rcrtc->mmio_offset + reg) | set); +} + +static void rcar_du_crtc_clr_set(struct rcar_du_crtc *rcrtc, u32 reg, + u32 clr, u32 set) +{ + struct rcar_du_device *rcdu = rcrtc->group->dev; + u32 value = rcar_du_read(rcdu, rcrtc->mmio_offset + reg); + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, (value & ~clr) | set); +} + +static int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc) +{ + int ret; + + ret = clk_prepare_enable(rcrtc->clock); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(rcrtc->extclock); + if (ret < 0) + goto error_clock; + + ret = rcar_du_group_get(rcrtc->group); + if (ret < 0) + goto error_group; + + return 0; + +error_group: + clk_disable_unprepare(rcrtc->extclock); +error_clock: + clk_disable_unprepare(rcrtc->clock); + return ret; +} + +static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc) +{ + rcar_du_group_put(rcrtc->group); + + clk_disable_unprepare(rcrtc->extclock); + clk_disable_unprepare(rcrtc->clock); +} + +/* ----------------------------------------------------------------------------- + * Hardware Setup + */ + +static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) +{ + const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode; + unsigned long mode_clock = mode->clock * 1000; + unsigned long clk; + u32 value; + u32 escr; + u32 div; + + /* Compute the clock divisor and select the internal or external dot + * clock based on the requested frequency. + */ + clk = clk_get_rate(rcrtc->clock); + div = DIV_ROUND_CLOSEST(clk, mode_clock); + div = clamp(div, 1U, 64U) - 1; + escr = div | ESCR_DCLKSEL_CLKS; + + if (rcrtc->extclock) { + unsigned long extclk; + unsigned long extrate; + unsigned long rate; + u32 extdiv; + + extclk = clk_get_rate(rcrtc->extclock); + extdiv = DIV_ROUND_CLOSEST(extclk, mode_clock); + extdiv = clamp(extdiv, 1U, 64U) - 1; + + rate = clk / (div + 1); + extrate = extclk / (extdiv + 1); + + if (abs((long)extrate - (long)mode_clock) < + abs((long)rate - (long)mode_clock)) { + dev_dbg(rcrtc->group->dev->dev, + "crtc%u: using external clock\n", rcrtc->index); + escr = extdiv | ESCR_DCLKSEL_DCLKIN; + } + } + + rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? ESCR2 : ESCR, + escr); + rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? OTAR2 : OTAR, 0); + + /* Signal polarities */ + value = ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : DSMR_VSL) + | ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : DSMR_HSL) + | DSMR_DIPM_DE | DSMR_CSPM; + rcar_du_crtc_write(rcrtc, DSMR, value); + + /* Display timings */ + rcar_du_crtc_write(rcrtc, HDSR, mode->htotal - mode->hsync_start - 19); + rcar_du_crtc_write(rcrtc, HDER, mode->htotal - mode->hsync_start + + mode->hdisplay - 19); + rcar_du_crtc_write(rcrtc, HSWR, mode->hsync_end - + mode->hsync_start - 1); + rcar_du_crtc_write(rcrtc, HCR, mode->htotal - 1); + + rcar_du_crtc_write(rcrtc, VDSR, mode->crtc_vtotal - + mode->crtc_vsync_end - 2); + rcar_du_crtc_write(rcrtc, VDER, mode->crtc_vtotal - + mode->crtc_vsync_end + + mode->crtc_vdisplay - 2); + rcar_du_crtc_write(rcrtc, VSPR, mode->crtc_vtotal - + mode->crtc_vsync_end + + mode->crtc_vsync_start - 1); + rcar_du_crtc_write(rcrtc, VCR, mode->crtc_vtotal - 1); + + rcar_du_crtc_write(rcrtc, DESR, mode->htotal - mode->hsync_start); + rcar_du_crtc_write(rcrtc, DEWR, mode->hdisplay); +} + +void rcar_du_crtc_route_output(struct drm_crtc *crtc, + enum rcar_du_output output) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + struct rcar_du_device *rcdu = rcrtc->group->dev; + + /* Store the route from the CRTC output to the DU output. The DU will be + * configured when starting the CRTC. + */ + rcrtc->outputs |= BIT(output); + + /* Store RGB routing to DPAD0, the hardware will be configured when + * starting the CRTC. + */ + if (output == RCAR_DU_OUTPUT_DPAD0) + rcdu->dpad0_source = rcrtc->index; +} + +static unsigned int plane_zpos(struct rcar_du_plane *plane) +{ + return to_rcar_du_plane_state(plane->plane.state)->zpos; +} + +static const struct rcar_du_format_info * +plane_format(struct rcar_du_plane *plane) +{ + return to_rcar_du_plane_state(plane->plane.state)->format; +} + +static void rcar_du_crtc_update_planes(struct rcar_du_crtc *rcrtc) +{ + struct rcar_du_plane *planes[RCAR_DU_NUM_HW_PLANES]; + unsigned int num_planes = 0; + unsigned int prio = 0; + unsigned int i; + u32 dptsr = 0; + u32 dspr = 0; + + for (i = 0; i < ARRAY_SIZE(rcrtc->group->planes.planes); ++i) { + struct rcar_du_plane *plane = &rcrtc->group->planes.planes[i]; + unsigned int j; + + if (plane->plane.state->crtc != &rcrtc->crtc) + continue; + + /* Insert the plane in the sorted planes array. */ + for (j = num_planes++; j > 0; --j) { + if (plane_zpos(planes[j-1]) <= plane_zpos(plane)) + break; + planes[j] = planes[j-1]; + } + + planes[j] = plane; + prio += plane_format(plane)->planes * 4; + } + + for (i = 0; i < num_planes; ++i) { + struct rcar_du_plane *plane = planes[i]; + struct drm_plane_state *state = plane->plane.state; + unsigned int index = to_rcar_du_plane_state(state)->hwindex; + + prio -= 4; + dspr |= (index + 1) << prio; + dptsr |= DPTSR_PnDK(index) | DPTSR_PnTS(index); + + if (plane_format(plane)->planes == 2) { + index = (index + 1) % 8; + + prio -= 4; + dspr |= (index + 1) << prio; + dptsr |= DPTSR_PnDK(index) | DPTSR_PnTS(index); + } + } + + /* Select display timing and dot clock generator 2 for planes associated + * with superposition controller 2. + */ + if (rcrtc->index % 2) { + /* The DPTSR register is updated when the display controller is + * stopped. We thus need to restart the DU. Once again, sorry + * for the flicker. One way to mitigate the issue would be to + * pre-associate planes with CRTCs (either with a fixed 4/4 + * split, or through a module parameter). Flicker would then + * occur only if we need to break the pre-association. + */ + mutex_lock(&rcrtc->group->lock); + if (rcar_du_group_read(rcrtc->group, DPTSR) != dptsr) { + rcar_du_group_write(rcrtc->group, DPTSR, dptsr); + if (rcrtc->group->used_crtcs) + rcar_du_group_restart(rcrtc->group); + } + mutex_unlock(&rcrtc->group->lock); + } + + rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, + dspr); +} + +/* ----------------------------------------------------------------------------- + * Page Flip + */ + +void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc, + struct drm_file *file) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + /* Destroy the pending vertical blanking event associated with the + * pending page flip, if any, and disable vertical blanking interrupts. + */ + spin_lock_irqsave(&dev->event_lock, flags); + event = rcrtc->event; + if (event && event->base.file_priv == file) { + rcrtc->event = NULL; + event->base.destroy(&event->base); + drm_crtc_vblank_put(&rcrtc->crtc); + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = rcrtc->event; + rcrtc->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + if (event == NULL) + return; + + spin_lock_irqsave(&dev->event_lock, flags); + drm_send_vblank_event(dev, rcrtc->index, event); + wake_up(&rcrtc->flip_wait); + spin_unlock_irqrestore(&dev->event_lock, flags); + + drm_crtc_vblank_put(&rcrtc->crtc); +} + +static bool rcar_du_crtc_page_flip_pending(struct rcar_du_crtc *rcrtc) +{ + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + bool pending; + + spin_lock_irqsave(&dev->event_lock, flags); + pending = rcrtc->event != NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + return pending; +} + +static void rcar_du_crtc_wait_page_flip(struct rcar_du_crtc *rcrtc) +{ + struct rcar_du_device *rcdu = rcrtc->group->dev; + + if (wait_event_timeout(rcrtc->flip_wait, + !rcar_du_crtc_page_flip_pending(rcrtc), + msecs_to_jiffies(50))) + return; + + dev_warn(rcdu->dev, "page flip timeout\n"); + + rcar_du_crtc_finish_page_flip(rcrtc); +} + +/* ----------------------------------------------------------------------------- + * Start/Stop and Suspend/Resume + */ + +static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc) +{ + struct drm_crtc *crtc = &rcrtc->crtc; + bool interlaced; + + if (rcrtc->started) + return; + + /* Set display off and background to black */ + rcar_du_crtc_write(rcrtc, DOOR, DOOR_RGB(0, 0, 0)); + rcar_du_crtc_write(rcrtc, BPOR, BPOR_RGB(0, 0, 0)); + + /* Configure display timings and output routing */ + rcar_du_crtc_set_display_timing(rcrtc); + rcar_du_group_set_routing(rcrtc->group); + + /* Start with all planes disabled. */ + rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, 0); + + /* Select master sync mode. This enables display operation in master + * sync mode (with the HSYNC and VSYNC signals configured as outputs and + * actively driven). + */ + interlaced = rcrtc->crtc.mode.flags & DRM_MODE_FLAG_INTERLACE; + rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK | DSYSR_SCM_MASK, + (interlaced ? DSYSR_SCM_INT_VIDEO : 0) | + DSYSR_TVM_MASTER); + + rcar_du_group_start_stop(rcrtc->group, true); + + /* Turn vertical blanking interrupt reporting back on. */ + drm_crtc_vblank_on(crtc); + + rcrtc->started = true; +} + +static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc) +{ + struct drm_crtc *crtc = &rcrtc->crtc; + + if (!rcrtc->started) + return; + + /* Disable vertical blanking interrupt reporting. We first need to wait + * for page flip completion before stopping the CRTC as userspace + * expects page flips to eventually complete. + */ + rcar_du_crtc_wait_page_flip(rcrtc); + drm_crtc_vblank_off(crtc); + + /* Select switch sync mode. This stops display operation and configures + * the HSYNC and VSYNC signals as inputs. + */ + rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_SWITCH); + + rcar_du_group_start_stop(rcrtc->group, false); + + rcrtc->started = false; +} + +void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc) +{ + rcar_du_crtc_stop(rcrtc); + rcar_du_crtc_put(rcrtc); +} + +void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc) +{ + unsigned int i; + + if (!rcrtc->enabled) + return; + + rcar_du_crtc_get(rcrtc); + rcar_du_crtc_start(rcrtc); + + /* Commit the planes state. */ + for (i = 0; i < ARRAY_SIZE(rcrtc->group->planes.planes); ++i) { + struct rcar_du_plane *plane = &rcrtc->group->planes.planes[i]; + + if (plane->plane.state->crtc != &rcrtc->crtc) + continue; + + rcar_du_plane_setup(plane); + } + + rcar_du_crtc_update_planes(rcrtc); +} + +/* ----------------------------------------------------------------------------- + * CRTC Functions + */ + +static void rcar_du_crtc_enable(struct drm_crtc *crtc) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + if (rcrtc->enabled) + return; + + rcar_du_crtc_get(rcrtc); + rcar_du_crtc_start(rcrtc); + + rcrtc->enabled = true; +} + +static void rcar_du_crtc_disable(struct drm_crtc *crtc) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + if (!rcrtc->enabled) + return; + + rcar_du_crtc_stop(rcrtc); + rcar_du_crtc_put(rcrtc); + + rcrtc->enabled = false; + rcrtc->outputs = 0; +} + +static bool rcar_du_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* TODO Fixup modes */ + return true; +} + +static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc) +{ + struct drm_pending_vblank_event *event = crtc->state->event; + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + if (event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&dev->event_lock, flags); + rcrtc->event = event; + spin_unlock_irqrestore(&dev->event_lock, flags); + } +} + +static void rcar_du_crtc_atomic_flush(struct drm_crtc *crtc) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + rcar_du_crtc_update_planes(rcrtc); +} + +static const struct drm_crtc_helper_funcs crtc_helper_funcs = { + .mode_fixup = rcar_du_crtc_mode_fixup, + .disable = rcar_du_crtc_disable, + .enable = rcar_du_crtc_enable, + .atomic_begin = rcar_du_crtc_atomic_begin, + .atomic_flush = rcar_du_crtc_atomic_flush, +}; + +static const struct drm_crtc_funcs crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static irqreturn_t rcar_du_crtc_irq(int irq, void *arg) +{ + struct rcar_du_crtc *rcrtc = arg; + irqreturn_t ret = IRQ_NONE; + u32 status; + + status = rcar_du_crtc_read(rcrtc, DSSR); + rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK); + + if (status & DSSR_FRM) { + drm_handle_vblank(rcrtc->crtc.dev, rcrtc->index); + rcar_du_crtc_finish_page_flip(rcrtc); + ret = IRQ_HANDLED; + } + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index) +{ + static const unsigned int mmio_offsets[] = { + DU0_REG_OFFSET, DU1_REG_OFFSET, DU2_REG_OFFSET + }; + + struct rcar_du_device *rcdu = rgrp->dev; + struct platform_device *pdev = to_platform_device(rcdu->dev); + struct rcar_du_crtc *rcrtc = &rcdu->crtcs[index]; + struct drm_crtc *crtc = &rcrtc->crtc; + unsigned int irqflags; + struct clk *clk; + char clk_name[9]; + char *name; + int irq; + int ret; + + /* Get the CRTC clock and the optional external clock. */ + if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CRTC_IRQ_CLOCK)) { + sprintf(clk_name, "du.%u", index); + name = clk_name; + } else { + name = NULL; + } + + rcrtc->clock = devm_clk_get(rcdu->dev, name); + if (IS_ERR(rcrtc->clock)) { + dev_err(rcdu->dev, "no clock for CRTC %u\n", index); + return PTR_ERR(rcrtc->clock); + } + + sprintf(clk_name, "dclkin.%u", index); + clk = devm_clk_get(rcdu->dev, clk_name); + if (!IS_ERR(clk)) { + rcrtc->extclock = clk; + } else if (PTR_ERR(rcrtc->clock) == -EPROBE_DEFER) { + dev_info(rcdu->dev, "can't get external clock %u\n", index); + return -EPROBE_DEFER; + } + + init_waitqueue_head(&rcrtc->flip_wait); + + rcrtc->group = rgrp; + rcrtc->mmio_offset = mmio_offsets[index]; + rcrtc->index = index; + rcrtc->enabled = false; + + ret = drm_crtc_init_with_planes(rcdu->ddev, crtc, + &rgrp->planes.planes[index % 2].plane, + NULL, &crtc_funcs); + if (ret < 0) + return ret; + + drm_crtc_helper_add(crtc, &crtc_helper_funcs); + + /* Start with vertical blanking interrupt reporting disabled. */ + drm_crtc_vblank_off(crtc); + + /* Register the interrupt handler. */ + if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CRTC_IRQ_CLOCK)) { + irq = platform_get_irq(pdev, index); + irqflags = 0; + } else { + irq = platform_get_irq(pdev, 0); + irqflags = IRQF_SHARED; + } + + if (irq < 0) { + dev_err(rcdu->dev, "no IRQ for CRTC %u\n", index); + return irq; + } + + ret = devm_request_irq(rcdu->dev, irq, rcar_du_crtc_irq, irqflags, + dev_name(rcdu->dev), rcrtc); + if (ret < 0) { + dev_err(rcdu->dev, + "failed to register IRQ for CRTC %u\n", index); + return ret; + } + + return 0; +} + +void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable) +{ + if (enable) { + rcar_du_crtc_write(rcrtc, DSRCR, DSRCR_VBCL); + rcar_du_crtc_set(rcrtc, DIER, DIER_VBE); + } else { + rcar_du_crtc_clr(rcrtc, DIER, DIER_VBE); + } +} diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_crtc.h new file mode 100644 index 000000000..5d9aa9b33 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_crtc.h @@ -0,0 +1,64 @@ +/* + * rcar_du_crtc.h -- R-Car Display Unit CRTCs + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_CRTC_H__ +#define __RCAR_DU_CRTC_H__ + +#include <linux/mutex.h> +#include <linux/wait.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> + +struct rcar_du_group; + +struct rcar_du_crtc { + struct drm_crtc crtc; + + struct clk *clock; + struct clk *extclock; + unsigned int mmio_offset; + unsigned int index; + bool started; + + struct drm_pending_vblank_event *event; + wait_queue_head_t flip_wait; + + unsigned int outputs; + bool enabled; + + struct rcar_du_group *group; +}; + +#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc) + +enum rcar_du_output { + RCAR_DU_OUTPUT_DPAD0, + RCAR_DU_OUTPUT_DPAD1, + RCAR_DU_OUTPUT_LVDS0, + RCAR_DU_OUTPUT_LVDS1, + RCAR_DU_OUTPUT_TCON, + RCAR_DU_OUTPUT_MAX, +}; + +int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index); +void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable); +void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc, + struct drm_file *file); +void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc); +void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc); + +void rcar_du_crtc_route_output(struct drm_crtc *crtc, + enum rcar_du_output output); + +#endif /* __RCAR_DU_CRTC_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_drv.c new file mode 100644 index 000000000..da1216a73 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_drv.c @@ -0,0 +1,350 @@ +/* + * rcar_du_drv.c -- R-Car Display Unit DRM driver + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#include "rcar_du_crtc.h" +#include "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_regs.h" + +/* ----------------------------------------------------------------------------- + * Device Information + */ + +static const struct rcar_du_device_info rcar_du_r8a7779_info = { + .features = 0, + .num_crtcs = 2, + .routes = { + /* R8A7779 has two RGB outputs and one (currently unsupported) + * TCON output. + */ + [RCAR_DU_OUTPUT_DPAD0] = { + .possible_crtcs = BIT(0), + .encoder_type = DRM_MODE_ENCODER_NONE, + .port = 0, + }, + [RCAR_DU_OUTPUT_DPAD1] = { + .possible_crtcs = BIT(1) | BIT(0), + .encoder_type = DRM_MODE_ENCODER_NONE, + .port = 1, + }, + }, + .num_lvds = 0, +}; + +static const struct rcar_du_device_info rcar_du_r8a7790_info = { + .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK + | RCAR_DU_FEATURE_EXT_CTRL_REGS, + .quirks = RCAR_DU_QUIRK_ALIGN_128B | RCAR_DU_QUIRK_LVDS_LANES, + .num_crtcs = 3, + .routes = { + /* R8A7790 has one RGB output, two LVDS outputs and one + * (currently unsupported) TCON output. + */ + [RCAR_DU_OUTPUT_DPAD0] = { + .possible_crtcs = BIT(2) | BIT(1) | BIT(0), + .encoder_type = DRM_MODE_ENCODER_NONE, + .port = 0, + }, + [RCAR_DU_OUTPUT_LVDS0] = { + .possible_crtcs = BIT(0), + .encoder_type = DRM_MODE_ENCODER_LVDS, + .port = 1, + }, + [RCAR_DU_OUTPUT_LVDS1] = { + .possible_crtcs = BIT(2) | BIT(1), + .encoder_type = DRM_MODE_ENCODER_LVDS, + .port = 2, + }, + }, + .num_lvds = 2, +}; + +static const struct rcar_du_device_info rcar_du_r8a7791_info = { + .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK + | RCAR_DU_FEATURE_EXT_CTRL_REGS, + .num_crtcs = 2, + .routes = { + /* R8A7791 has one RGB output, one LVDS output and one + * (currently unsupported) TCON output. + */ + [RCAR_DU_OUTPUT_DPAD0] = { + .possible_crtcs = BIT(1), + .encoder_type = DRM_MODE_ENCODER_NONE, + .port = 0, + }, + [RCAR_DU_OUTPUT_LVDS0] = { + .possible_crtcs = BIT(0), + .encoder_type = DRM_MODE_ENCODER_LVDS, + .port = 1, + }, + }, + .num_lvds = 1, +}; + +static const struct platform_device_id rcar_du_id_table[] = { + { "rcar-du-r8a7779", (kernel_ulong_t)&rcar_du_r8a7779_info }, + { "rcar-du-r8a7790", (kernel_ulong_t)&rcar_du_r8a7790_info }, + { "rcar-du-r8a7791", (kernel_ulong_t)&rcar_du_r8a7791_info }, + { } +}; + +MODULE_DEVICE_TABLE(platform, rcar_du_id_table); + +static const struct of_device_id rcar_du_of_table[] = { + { .compatible = "renesas,du-r8a7779", .data = &rcar_du_r8a7779_info }, + { .compatible = "renesas,du-r8a7790", .data = &rcar_du_r8a7790_info }, + { .compatible = "renesas,du-r8a7791", .data = &rcar_du_r8a7791_info }, + { } +}; + +MODULE_DEVICE_TABLE(of, rcar_du_of_table); + +/* ----------------------------------------------------------------------------- + * DRM operations + */ + +static int rcar_du_unload(struct drm_device *dev) +{ + struct rcar_du_device *rcdu = dev->dev_private; + + if (rcdu->fbdev) + drm_fbdev_cma_fini(rcdu->fbdev); + + drm_kms_helper_poll_fini(dev); + drm_mode_config_cleanup(dev); + drm_vblank_cleanup(dev); + + dev->irq_enabled = 0; + dev->dev_private = NULL; + + return 0; +} + +static int rcar_du_load(struct drm_device *dev, unsigned long flags) +{ + struct platform_device *pdev = dev->platformdev; + struct device_node *np = pdev->dev.of_node; + struct rcar_du_device *rcdu; + struct resource *mem; + int ret; + + if (np == NULL) { + dev_err(dev->dev, "no platform data\n"); + return -ENODEV; + } + + rcdu = devm_kzalloc(&pdev->dev, sizeof(*rcdu), GFP_KERNEL); + if (rcdu == NULL) { + dev_err(dev->dev, "failed to allocate private data\n"); + return -ENOMEM; + } + + init_waitqueue_head(&rcdu->commit.wait); + + rcdu->dev = &pdev->dev; + rcdu->info = np ? of_match_device(rcar_du_of_table, rcdu->dev)->data + : (void *)platform_get_device_id(pdev)->driver_data; + rcdu->ddev = dev; + dev->dev_private = rcdu; + + /* I/O resources */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rcdu->mmio = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(rcdu->mmio)) + return PTR_ERR(rcdu->mmio); + + /* Initialize vertical blanking interrupts handling. Start with vblank + * disabled for all CRTCs. + */ + ret = drm_vblank_init(dev, (1 << rcdu->info->num_crtcs) - 1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to initialize vblank\n"); + goto done; + } + + /* DRM/KMS objects */ + ret = rcar_du_modeset_init(rcdu); + if (ret < 0) { + dev_err(&pdev->dev, "failed to initialize DRM/KMS\n"); + goto done; + } + + dev->irq_enabled = 1; + + platform_set_drvdata(pdev, rcdu); + +done: + if (ret) + rcar_du_unload(dev); + + return ret; +} + +static void rcar_du_preclose(struct drm_device *dev, struct drm_file *file) +{ + struct rcar_du_device *rcdu = dev->dev_private; + unsigned int i; + + for (i = 0; i < rcdu->num_crtcs; ++i) + rcar_du_crtc_cancel_page_flip(&rcdu->crtcs[i], file); +} + +static void rcar_du_lastclose(struct drm_device *dev) +{ + struct rcar_du_device *rcdu = dev->dev_private; + + drm_fbdev_cma_restore_mode(rcdu->fbdev); +} + +static int rcar_du_enable_vblank(struct drm_device *dev, int crtc) +{ + struct rcar_du_device *rcdu = dev->dev_private; + + rcar_du_crtc_enable_vblank(&rcdu->crtcs[crtc], true); + + return 0; +} + +static void rcar_du_disable_vblank(struct drm_device *dev, int crtc) +{ + struct rcar_du_device *rcdu = dev->dev_private; + + rcar_du_crtc_enable_vblank(&rcdu->crtcs[crtc], false); +} + +static const struct file_operations rcar_du_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .mmap = drm_gem_cma_mmap, +}; + +static struct drm_driver rcar_du_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME + | DRIVER_ATOMIC, + .load = rcar_du_load, + .unload = rcar_du_unload, + .preclose = rcar_du_preclose, + .lastclose = rcar_du_lastclose, + .set_busid = drm_platform_set_busid, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = rcar_du_enable_vblank, + .disable_vblank = rcar_du_disable_vblank, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, + .dumb_create = rcar_du_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + .fops = &rcar_du_fops, + .name = "rcar-du", + .desc = "Renesas R-Car Display Unit", + .date = "20130110", + .major = 1, + .minor = 0, +}; + +/* ----------------------------------------------------------------------------- + * Power management + */ + +#ifdef CONFIG_PM_SLEEP +static int rcar_du_pm_suspend(struct device *dev) +{ + struct rcar_du_device *rcdu = dev_get_drvdata(dev); + + drm_kms_helper_poll_disable(rcdu->ddev); + /* TODO Suspend the CRTC */ + + return 0; +} + +static int rcar_du_pm_resume(struct device *dev) +{ + struct rcar_du_device *rcdu = dev_get_drvdata(dev); + + /* TODO Resume the CRTC */ + + drm_kms_helper_poll_enable(rcdu->ddev); + return 0; +} +#endif + +static const struct dev_pm_ops rcar_du_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(rcar_du_pm_suspend, rcar_du_pm_resume) +}; + +/* ----------------------------------------------------------------------------- + * Platform driver + */ + +static int rcar_du_probe(struct platform_device *pdev) +{ + return drm_platform_init(&rcar_du_driver, pdev); +} + +static int rcar_du_remove(struct platform_device *pdev) +{ + struct rcar_du_device *rcdu = platform_get_drvdata(pdev); + + drm_put_dev(rcdu->ddev); + + return 0; +} + +static struct platform_driver rcar_du_platform_driver = { + .probe = rcar_du_probe, + .remove = rcar_du_remove, + .driver = { + .name = "rcar-du", + .pm = &rcar_du_pm_ops, + .of_match_table = rcar_du_of_table, + }, + .id_table = rcar_du_id_table, +}; + +module_platform_driver(rcar_du_platform_driver); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_drv.h new file mode 100644 index 000000000..c7c538dd2 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_drv.h @@ -0,0 +1,117 @@ +/* + * rcar_du_drv.h -- R-Car Display Unit DRM driver + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_DRV_H__ +#define __RCAR_DU_DRV_H__ + +#include <linux/kernel.h> +#include <linux/wait.h> + +#include "rcar_du_crtc.h" +#include "rcar_du_group.h" + +struct clk; +struct device; +struct drm_device; +struct drm_fbdev_cma; +struct rcar_du_device; +struct rcar_du_lvdsenc; + +#define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK (1 << 0) /* Per-CRTC IRQ and clock */ +#define RCAR_DU_FEATURE_EXT_CTRL_REGS (1 << 1) /* Has extended control registers */ + +#define RCAR_DU_QUIRK_ALIGN_128B (1 << 0) /* Align pitches to 128 bytes */ +#define RCAR_DU_QUIRK_LVDS_LANES (1 << 1) /* LVDS lanes 1 and 3 inverted */ + +/* + * struct rcar_du_output_routing - Output routing specification + * @possible_crtcs: bitmask of possible CRTCs for the output + * @encoder_type: DRM type of the internal encoder associated with the output + * @port: device tree port number corresponding to this output route + * + * The DU has 5 possible outputs (DPAD0/1, LVDS0/1, TCON). Output routing data + * specify the valid SoC outputs, which CRTCs can drive the output, and the type + * of in-SoC encoder for the output. + */ +struct rcar_du_output_routing { + unsigned int possible_crtcs; + unsigned int encoder_type; + unsigned int port; +}; + +/* + * struct rcar_du_device_info - DU model-specific information + * @features: device features (RCAR_DU_FEATURE_*) + * @quirks: device quirks (RCAR_DU_QUIRK_*) + * @num_crtcs: total number of CRTCs + * @routes: array of CRTC to output routes, indexed by output (RCAR_DU_OUTPUT_*) + * @num_lvds: number of internal LVDS encoders + */ +struct rcar_du_device_info { + unsigned int features; + unsigned int quirks; + unsigned int num_crtcs; + struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX]; + unsigned int num_lvds; +}; + +#define RCAR_DU_MAX_CRTCS 3 +#define RCAR_DU_MAX_GROUPS DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2) +#define RCAR_DU_MAX_LVDS 2 + +struct rcar_du_device { + struct device *dev; + const struct rcar_du_device_info *info; + + void __iomem *mmio; + + struct drm_device *ddev; + struct drm_fbdev_cma *fbdev; + + struct rcar_du_crtc crtcs[RCAR_DU_MAX_CRTCS]; + unsigned int num_crtcs; + + struct rcar_du_group groups[RCAR_DU_MAX_GROUPS]; + + unsigned int dpad0_source; + struct rcar_du_lvdsenc *lvds[RCAR_DU_MAX_LVDS]; + + struct { + wait_queue_head_t wait; + u32 pending; + } commit; +}; + +static inline bool rcar_du_has(struct rcar_du_device *rcdu, + unsigned int feature) +{ + return rcdu->info->features & feature; +} + +static inline bool rcar_du_needs(struct rcar_du_device *rcdu, + unsigned int quirk) +{ + return rcdu->info->quirks & quirk; +} + +static inline u32 rcar_du_read(struct rcar_du_device *rcdu, u32 reg) +{ + return ioread32(rcdu->mmio + reg); +} + +static inline void rcar_du_write(struct rcar_du_device *rcdu, u32 reg, u32 data) +{ + iowrite32(data, rcdu->mmio + reg); +} + +#endif /* __RCAR_DU_DRV_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_encoder.c new file mode 100644 index 000000000..d0ae1e800 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_encoder.c @@ -0,0 +1,209 @@ +/* + * rcar_du_encoder.c -- R-Car Display Unit Encoder + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/export.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> + +#include "rcar_du_drv.h" +#include "rcar_du_encoder.h" +#include "rcar_du_hdmicon.h" +#include "rcar_du_hdmienc.h" +#include "rcar_du_kms.h" +#include "rcar_du_lvdscon.h" +#include "rcar_du_lvdsenc.h" +#include "rcar_du_vgacon.h" + +/* ----------------------------------------------------------------------------- + * Common connector functions + */ + +struct drm_encoder * +rcar_du_connector_best_encoder(struct drm_connector *connector) +{ + struct rcar_du_connector *rcon = to_rcar_connector(connector); + + return rcar_encoder_to_drm_encoder(rcon->encoder); +} + +/* ----------------------------------------------------------------------------- + * Encoder + */ + +static void rcar_du_encoder_disable(struct drm_encoder *encoder) +{ + struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + + if (renc->lvds) + rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, false); +} + +static void rcar_du_encoder_enable(struct drm_encoder *encoder) +{ + struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + + if (renc->lvds) + rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, true); +} + +static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + const struct drm_display_mode *mode = &crtc_state->mode; + const struct drm_display_mode *panel_mode; + struct drm_connector *connector = conn_state->connector; + struct drm_device *dev = encoder->dev; + + /* DAC encoders have currently no restriction on the mode. */ + if (encoder->encoder_type == DRM_MODE_ENCODER_DAC) + return 0; + + if (list_empty(&connector->modes)) { + dev_dbg(dev->dev, "encoder: empty modes list\n"); + return -EINVAL; + } + + panel_mode = list_first_entry(&connector->modes, + struct drm_display_mode, head); + + /* We're not allowed to modify the resolution. */ + if (mode->hdisplay != panel_mode->hdisplay || + mode->vdisplay != panel_mode->vdisplay) + return -EINVAL; + + /* The flat panel mode is fixed, just copy it to the adjusted mode. */ + drm_mode_copy(adjusted_mode, panel_mode); + + /* The internal LVDS encoder has a clock frequency operating range of + * 30MHz to 150MHz. Clamp the clock accordingly. + */ + if (renc->lvds) + adjusted_mode->clock = clamp(adjusted_mode->clock, + 30000, 150000); + + return 0; +} + +static void rcar_du_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + + rcar_du_crtc_route_output(encoder->crtc, renc->output); +} + +static const struct drm_encoder_helper_funcs encoder_helper_funcs = { + .mode_set = rcar_du_encoder_mode_set, + .disable = rcar_du_encoder_disable, + .enable = rcar_du_encoder_enable, + .atomic_check = rcar_du_encoder_atomic_check, +}; + +static const struct drm_encoder_funcs encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +int rcar_du_encoder_init(struct rcar_du_device *rcdu, + enum rcar_du_encoder_type type, + enum rcar_du_output output, + struct device_node *enc_node, + struct device_node *con_node) +{ + struct rcar_du_encoder *renc; + struct drm_encoder *encoder; + unsigned int encoder_type; + int ret; + + renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL); + if (renc == NULL) + return -ENOMEM; + + renc->output = output; + encoder = rcar_encoder_to_drm_encoder(renc); + + switch (output) { + case RCAR_DU_OUTPUT_LVDS0: + renc->lvds = rcdu->lvds[0]; + break; + + case RCAR_DU_OUTPUT_LVDS1: + renc->lvds = rcdu->lvds[1]; + break; + + default: + break; + } + + switch (type) { + case RCAR_DU_ENCODER_VGA: + encoder_type = DRM_MODE_ENCODER_DAC; + break; + case RCAR_DU_ENCODER_LVDS: + encoder_type = DRM_MODE_ENCODER_LVDS; + break; + case RCAR_DU_ENCODER_HDMI: + encoder_type = DRM_MODE_ENCODER_TMDS; + break; + case RCAR_DU_ENCODER_NONE: + default: + /* No external encoder, use the internal encoder type. */ + encoder_type = rcdu->info->routes[output].encoder_type; + break; + } + + if (type == RCAR_DU_ENCODER_HDMI) { + ret = rcar_du_hdmienc_init(rcdu, renc, enc_node); + if (ret < 0) + goto done; + } else { + ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs, + encoder_type); + if (ret < 0) + goto done; + + drm_encoder_helper_add(encoder, &encoder_helper_funcs); + } + + switch (encoder_type) { + case DRM_MODE_ENCODER_LVDS: + ret = rcar_du_lvds_connector_init(rcdu, renc, con_node); + break; + + case DRM_MODE_ENCODER_DAC: + ret = rcar_du_vga_connector_init(rcdu, renc); + break; + + case DRM_MODE_ENCODER_TMDS: + ret = rcar_du_hdmi_connector_init(rcdu, renc); + break; + + default: + ret = -EINVAL; + break; + } + +done: + if (ret < 0) { + if (encoder->name) + encoder->funcs->destroy(encoder); + devm_kfree(rcdu->dev, renc); + } + + return ret; +} diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_encoder.h new file mode 100644 index 000000000..719b6f2a0 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_encoder.h @@ -0,0 +1,61 @@ +/* + * rcar_du_encoder.h -- R-Car Display Unit Encoder + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_ENCODER_H__ +#define __RCAR_DU_ENCODER_H__ + +#include <drm/drm_crtc.h> +#include <drm/drm_encoder_slave.h> + +struct rcar_du_device; +struct rcar_du_hdmienc; +struct rcar_du_lvdsenc; + +enum rcar_du_encoder_type { + RCAR_DU_ENCODER_UNUSED = 0, + RCAR_DU_ENCODER_NONE, + RCAR_DU_ENCODER_VGA, + RCAR_DU_ENCODER_LVDS, + RCAR_DU_ENCODER_HDMI, +}; + +struct rcar_du_encoder { + struct drm_encoder_slave slave; + enum rcar_du_output output; + struct rcar_du_hdmienc *hdmi; + struct rcar_du_lvdsenc *lvds; +}; + +#define to_rcar_encoder(e) \ + container_of(e, struct rcar_du_encoder, slave.base) + +#define rcar_encoder_to_drm_encoder(e) (&(e)->slave.base) + +struct rcar_du_connector { + struct drm_connector connector; + struct rcar_du_encoder *encoder; +}; + +#define to_rcar_connector(c) \ + container_of(c, struct rcar_du_connector, connector) + +struct drm_encoder * +rcar_du_connector_best_encoder(struct drm_connector *connector); + +int rcar_du_encoder_init(struct rcar_du_device *rcdu, + enum rcar_du_encoder_type type, + enum rcar_du_output output, + struct device_node *enc_node, + struct device_node *con_node); + +#endif /* __RCAR_DU_ENCODER_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_group.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_group.c new file mode 100644 index 000000000..1bdc0ee0c --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_group.c @@ -0,0 +1,200 @@ +/* + * rcar_du_group.c -- R-Car Display Unit Channels Pair + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * The R8A7779 DU is split in per-CRTC resources (scan-out engine, blending + * unit, timings generator, ...) and device-global resources (start/stop + * control, planes, ...) shared between the two CRTCs. + * + * The R8A7790 introduced a third CRTC with its own set of global resources. + * This would be modeled as two separate DU device instances if it wasn't for + * a handful or resources that are shared between the three CRTCs (mostly + * related to input and output routing). For this reason the R8A7790 DU must be + * modeled as a single device with three CRTCs, two sets of "semi-global" + * resources, and a few device-global resources. + * + * The rcar_du_group object is a driver specific object, without any real + * counterpart in the DU documentation, that models those semi-global resources. + */ + +#include <linux/clk.h> +#include <linux/io.h> + +#include "rcar_du_drv.h" +#include "rcar_du_group.h" +#include "rcar_du_regs.h" + +u32 rcar_du_group_read(struct rcar_du_group *rgrp, u32 reg) +{ + return rcar_du_read(rgrp->dev, rgrp->mmio_offset + reg); +} + +void rcar_du_group_write(struct rcar_du_group *rgrp, u32 reg, u32 data) +{ + rcar_du_write(rgrp->dev, rgrp->mmio_offset + reg, data); +} + +static void rcar_du_group_setup_defr8(struct rcar_du_group *rgrp) +{ + u32 defr8 = DEFR8_CODE | DEFR8_DEFE8; + + /* The DEFR8 register for the first group also controls RGB output + * routing to DPAD0 + */ + if (rgrp->index == 0) + defr8 |= DEFR8_DRGBS_DU(rgrp->dev->dpad0_source); + + rcar_du_group_write(rgrp, DEFR8, defr8); +} + +static void rcar_du_group_setup(struct rcar_du_group *rgrp) +{ + /* Enable extended features */ + rcar_du_group_write(rgrp, DEFR, DEFR_CODE | DEFR_DEFE); + rcar_du_group_write(rgrp, DEFR2, DEFR2_CODE | DEFR2_DEFE2G); + rcar_du_group_write(rgrp, DEFR3, DEFR3_CODE | DEFR3_DEFE3); + rcar_du_group_write(rgrp, DEFR4, DEFR4_CODE); + rcar_du_group_write(rgrp, DEFR5, DEFR5_CODE | DEFR5_DEFE5); + + if (rcar_du_has(rgrp->dev, RCAR_DU_FEATURE_EXT_CTRL_REGS)) { + rcar_du_group_setup_defr8(rgrp); + + /* Configure input dot clock routing. We currently hardcode the + * configuration to routing DOTCLKINn to DUn. + */ + rcar_du_group_write(rgrp, DIDSR, DIDSR_CODE | + DIDSR_LCDS_DCLKIN(2) | + DIDSR_LCDS_DCLKIN(1) | + DIDSR_LCDS_DCLKIN(0) | + DIDSR_PDCS_CLK(2, 0) | + DIDSR_PDCS_CLK(1, 0) | + DIDSR_PDCS_CLK(0, 0)); + } + + /* Use DS1PR and DS2PR to configure planes priorities and connects the + * superposition 0 to DU0 pins. DU1 pins will be configured dynamically. + */ + rcar_du_group_write(rgrp, DORCR, DORCR_PG1D_DS1 | DORCR_DPRS); +} + +/* + * rcar_du_group_get - Acquire a reference to the DU channels group + * + * Acquiring the first reference setups core registers. A reference must be held + * before accessing any hardware registers. + * + * This function must be called with the DRM mode_config lock held. + * + * Return 0 in case of success or a negative error code otherwise. + */ +int rcar_du_group_get(struct rcar_du_group *rgrp) +{ + if (rgrp->use_count) + goto done; + + rcar_du_group_setup(rgrp); + +done: + rgrp->use_count++; + return 0; +} + +/* + * rcar_du_group_put - Release a reference to the DU + * + * This function must be called with the DRM mode_config lock held. + */ +void rcar_du_group_put(struct rcar_du_group *rgrp) +{ + --rgrp->use_count; +} + +static void __rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start) +{ + rcar_du_group_write(rgrp, DSYSR, + (rcar_du_group_read(rgrp, DSYSR) & ~(DSYSR_DRES | DSYSR_DEN)) | + (start ? DSYSR_DEN : DSYSR_DRES)); +} + +void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start) +{ + /* Many of the configuration bits are only updated when the display + * reset (DRES) bit in DSYSR is set to 1, disabling *both* CRTCs. Some + * of those bits could be pre-configured, but others (especially the + * bits related to plane assignment to display timing controllers) need + * to be modified at runtime. + * + * Restart the display controller if a start is requested. Sorry for the + * flicker. It should be possible to move most of the "DRES-update" bits + * setup to driver initialization time and minimize the number of cases + * when the display controller will have to be restarted. + */ + if (start) { + if (rgrp->used_crtcs++ != 0) + __rcar_du_group_start_stop(rgrp, false); + __rcar_du_group_start_stop(rgrp, true); + } else { + if (--rgrp->used_crtcs == 0) + __rcar_du_group_start_stop(rgrp, false); + } +} + +void rcar_du_group_restart(struct rcar_du_group *rgrp) +{ + __rcar_du_group_start_stop(rgrp, false); + __rcar_du_group_start_stop(rgrp, true); +} + +static int rcar_du_set_dpad0_routing(struct rcar_du_device *rcdu) +{ + int ret; + + if (!rcar_du_has(rcdu, RCAR_DU_FEATURE_EXT_CTRL_REGS)) + return 0; + + /* RGB output routing to DPAD0 is configured in the DEFR8 register of + * the first group. As this function can be called with the DU0 and DU1 + * CRTCs disabled, we need to enable the first group clock before + * accessing the register. + */ + ret = clk_prepare_enable(rcdu->crtcs[0].clock); + if (ret < 0) + return ret; + + rcar_du_group_setup_defr8(&rcdu->groups[0]); + + clk_disable_unprepare(rcdu->crtcs[0].clock); + + return 0; +} + +int rcar_du_group_set_routing(struct rcar_du_group *rgrp) +{ + struct rcar_du_crtc *crtc0 = &rgrp->dev->crtcs[rgrp->index * 2]; + u32 dorcr = rcar_du_group_read(rgrp, DORCR); + + dorcr &= ~(DORCR_PG2T | DORCR_DK2S | DORCR_PG2D_MASK); + + /* Set the DPAD1 pins sources. Select CRTC 0 if explicitly requested and + * CRTC 1 in all other cases to avoid cloning CRTC 0 to DPAD0 and DPAD1 + * by default. + */ + if (crtc0->outputs & BIT(RCAR_DU_OUTPUT_DPAD1)) + dorcr |= DORCR_PG2D_DS1; + else + dorcr |= DORCR_PG2T | DORCR_DK2S | DORCR_PG2D_DS2; + + rcar_du_group_write(rgrp, DORCR, dorcr); + + return rcar_du_set_dpad0_routing(rgrp->dev); +} diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_group.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_group.h new file mode 100644 index 000000000..ed36433fb --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_group.h @@ -0,0 +1,55 @@ +/* + * rcar_du_group.c -- R-Car Display Unit Planes and CRTCs Group + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_GROUP_H__ +#define __RCAR_DU_GROUP_H__ + +#include <linux/mutex.h> + +#include "rcar_du_plane.h" + +struct rcar_du_device; + +/* + * struct rcar_du_group - CRTCs and planes group + * @dev: the DU device + * @mmio_offset: registers offset in the device memory map + * @index: group index + * @use_count: number of users of the group (rcar_du_group_(get|put)) + * @used_crtcs: number of CRTCs currently in use + * @lock: protects the DPTSR register + * @planes: planes handled by the group + */ +struct rcar_du_group { + struct rcar_du_device *dev; + unsigned int mmio_offset; + unsigned int index; + + unsigned int use_count; + unsigned int used_crtcs; + + struct mutex lock; + + struct rcar_du_planes planes; +}; + +u32 rcar_du_group_read(struct rcar_du_group *rgrp, u32 reg); +void rcar_du_group_write(struct rcar_du_group *rgrp, u32 reg, u32 data); + +int rcar_du_group_get(struct rcar_du_group *rgrp); +void rcar_du_group_put(struct rcar_du_group *rgrp); +void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start); +void rcar_du_group_restart(struct rcar_du_group *rgrp); +int rcar_du_group_set_routing(struct rcar_du_group *rgrp); + +#endif /* __RCAR_DU_GROUP_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c new file mode 100644 index 000000000..96f2eb437 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c @@ -0,0 +1,126 @@ +/* + * R-Car Display Unit HDMI Connector + * + * Copyright (C) 2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> + +#include "rcar_du_drv.h" +#include "rcar_du_encoder.h" +#include "rcar_du_hdmicon.h" +#include "rcar_du_kms.h" + +#define to_slave_funcs(e) (to_rcar_encoder(e)->slave.slave_funcs) + +static int rcar_du_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct rcar_du_connector *con = to_rcar_connector(connector); + struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(con->encoder); + struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); + + if (sfuncs->get_modes == NULL) + return 0; + + return sfuncs->get_modes(encoder, connector); +} + +static int rcar_du_hdmi_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct rcar_du_connector *con = to_rcar_connector(connector); + struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(con->encoder); + struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); + + if (sfuncs->mode_valid == NULL) + return MODE_OK; + + return sfuncs->mode_valid(encoder, mode); +} + +static const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = rcar_du_hdmi_connector_get_modes, + .mode_valid = rcar_du_hdmi_connector_mode_valid, + .best_encoder = rcar_du_connector_best_encoder, +}; + +static void rcar_du_hdmi_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static enum drm_connector_status +rcar_du_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct rcar_du_connector *con = to_rcar_connector(connector); + struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(con->encoder); + struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); + + if (sfuncs->detect == NULL) + return connector_status_unknown; + + return sfuncs->detect(encoder, connector); +} + +static const struct drm_connector_funcs connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .detect = rcar_du_hdmi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = rcar_du_hdmi_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc) +{ + struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc); + struct rcar_du_connector *rcon; + struct drm_connector *connector; + int ret; + + rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL); + if (rcon == NULL) + return -ENOMEM; + + connector = &rcon->connector; + connector->display_info.width_mm = 0; + connector->display_info.height_mm = 0; + connector->interlace_allowed = true; + connector->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret < 0) + return ret; + + drm_connector_helper_add(connector, &connector_helper_funcs); + ret = drm_connector_register(connector); + if (ret < 0) + return ret; + + connector->dpms = DRM_MODE_DPMS_OFF; + drm_object_property_set_value(&connector->base, + rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF); + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret < 0) + return ret; + + rcon->encoder = renc; + + return 0; +} diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h new file mode 100644 index 000000000..87daa9492 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h @@ -0,0 +1,31 @@ +/* + * R-Car Display Unit HDMI Connector + * + * Copyright (C) 2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_HDMICON_H__ +#define __RCAR_DU_HDMICON_H__ + +struct rcar_du_device; +struct rcar_du_encoder; + +#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI) +int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc); +#else +static inline int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc) +{ + return -ENOSYS; +} +#endif + +#endif /* __RCAR_DU_HDMICON_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c new file mode 100644 index 000000000..81da84192 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c @@ -0,0 +1,168 @@ +/* + * R-Car Display Unit HDMI Encoder + * + * Copyright (C) 2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/slab.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> + +#include "rcar_du_drv.h" +#include "rcar_du_encoder.h" +#include "rcar_du_hdmienc.h" +#include "rcar_du_lvdsenc.h" + +struct rcar_du_hdmienc { + struct rcar_du_encoder *renc; + struct device *dev; + bool enabled; +}; + +#define to_rcar_hdmienc(e) (to_rcar_encoder(e)->hdmi) +#define to_slave_funcs(e) (to_rcar_encoder(e)->slave.slave_funcs) + +static void rcar_du_hdmienc_disable(struct drm_encoder *encoder) +{ + struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); + struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); + + if (sfuncs->dpms) + sfuncs->dpms(encoder, DRM_MODE_DPMS_OFF); + + if (hdmienc->renc->lvds) + rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc, + false); + + hdmienc->enabled = false; +} + +static void rcar_du_hdmienc_enable(struct drm_encoder *encoder) +{ + struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); + struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); + + if (hdmienc->renc->lvds) + rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc, + true); + + if (sfuncs->dpms) + sfuncs->dpms(encoder, DRM_MODE_DPMS_ON); + + hdmienc->enabled = true; +} + +static int rcar_du_hdmienc_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); + struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + const struct drm_display_mode *mode = &crtc_state->mode; + + /* The internal LVDS encoder has a clock frequency operating range of + * 30MHz to 150MHz. Clamp the clock accordingly. + */ + if (hdmienc->renc->lvds) + adjusted_mode->clock = clamp(adjusted_mode->clock, + 30000, 150000); + + if (sfuncs->mode_fixup == NULL) + return 0; + + return sfuncs->mode_fixup(encoder, mode, adjusted_mode) ? 0 : -EINVAL; +} + +static void rcar_du_hdmienc_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); + struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder); + + if (sfuncs->mode_set) + sfuncs->mode_set(encoder, mode, adjusted_mode); + + rcar_du_crtc_route_output(encoder->crtc, hdmienc->renc->output); +} + +static const struct drm_encoder_helper_funcs encoder_helper_funcs = { + .mode_set = rcar_du_hdmienc_mode_set, + .disable = rcar_du_hdmienc_disable, + .enable = rcar_du_hdmienc_enable, + .atomic_check = rcar_du_hdmienc_atomic_check, +}; + +static void rcar_du_hdmienc_cleanup(struct drm_encoder *encoder) +{ + struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); + + if (hdmienc->enabled) + rcar_du_hdmienc_disable(encoder); + + drm_encoder_cleanup(encoder); + put_device(hdmienc->dev); +} + +static const struct drm_encoder_funcs encoder_funcs = { + .destroy = rcar_du_hdmienc_cleanup, +}; + +int rcar_du_hdmienc_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc, struct device_node *np) +{ + struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc); + struct drm_i2c_encoder_driver *driver; + struct i2c_client *i2c_slave; + struct rcar_du_hdmienc *hdmienc; + int ret; + + hdmienc = devm_kzalloc(rcdu->dev, sizeof(*hdmienc), GFP_KERNEL); + if (hdmienc == NULL) + return -ENOMEM; + + /* Locate the slave I2C device and driver. */ + i2c_slave = of_find_i2c_device_by_node(np); + if (!i2c_slave || !i2c_get_clientdata(i2c_slave)) + return -EPROBE_DEFER; + + hdmienc->dev = &i2c_slave->dev; + + if (hdmienc->dev->driver == NULL) { + ret = -EPROBE_DEFER; + goto error; + } + + /* Initialize the slave encoder. */ + driver = to_drm_i2c_encoder_driver(to_i2c_driver(hdmienc->dev->driver)); + ret = driver->encoder_init(i2c_slave, rcdu->ddev, &renc->slave); + if (ret < 0) + goto error; + + ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (ret < 0) + goto error; + + drm_encoder_helper_add(encoder, &encoder_helper_funcs); + + renc->hdmi = hdmienc; + hdmienc->renc = renc; + + return 0; + +error: + put_device(hdmienc->dev); + return ret; +} diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h new file mode 100644 index 000000000..2ff0128ac --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h @@ -0,0 +1,35 @@ +/* + * R-Car Display Unit HDMI Encoder + * + * Copyright (C) 2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_HDMIENC_H__ +#define __RCAR_DU_HDMIENC_H__ + +#include <linux/module.h> + +struct device_node; +struct rcar_du_device; +struct rcar_du_encoder; + +#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI) +int rcar_du_hdmienc_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc, struct device_node *np); +#else +static inline int rcar_du_hdmienc_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc, + struct device_node *np) +{ + return -ENOSYS; +} +#endif + +#endif /* __RCAR_DU_HDMIENC_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_kms.c new file mode 100644 index 000000000..93117f159 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_kms.c @@ -0,0 +1,747 @@ +/* + * rcar_du_kms.c -- R-Car Display Unit Mode Setting + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <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 <linux/of_graph.h> +#include <linux/wait.h> + +#include "rcar_du_crtc.h" +#include "rcar_du_drv.h" +#include "rcar_du_encoder.h" +#include "rcar_du_kms.h" +#include "rcar_du_lvdsenc.h" +#include "rcar_du_regs.h" + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +static const struct rcar_du_format_info rcar_du_format_infos[] = { + { + .fourcc = DRM_FORMAT_RGB565, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_TP | PnMR_DDDF_16BPP, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_ARGB1555, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_ARGB, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_XRGB1555, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_ARGB, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_XRGB8888, + .bpp = 32, + .planes = 1, + .pnmr = PnMR_SPIM_TP | PnMR_DDDF_16BPP, + .edf = PnDDCR4_EDF_RGB888, + }, { + .fourcc = DRM_FORMAT_ARGB8888, + .bpp = 32, + .planes = 1, + .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_16BPP, + .edf = PnDDCR4_EDF_ARGB8888, + }, { + .fourcc = DRM_FORMAT_UYVY, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_YUYV, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_NV12, + .bpp = 12, + .planes = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_NV21, + .bpp = 12, + .planes = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + /* In YUV 4:2:2, only NV16 is supported (NV61 isn't) */ + .fourcc = DRM_FORMAT_NV16, + .bpp = 16, + .planes = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, +}; + +const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcar_du_format_infos); ++i) { + if (rcar_du_format_infos[i].fourcc == fourcc) + return &rcar_du_format_infos[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Frame buffer + */ + +int rcar_du_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct rcar_du_device *rcdu = dev->dev_private; + unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8); + unsigned int align; + + /* The R8A7779 DU requires a 16 pixels pitch alignment as documented, + * but the R8A7790 DU seems to require a 128 bytes pitch alignment. + */ + if (rcar_du_needs(rcdu, RCAR_DU_QUIRK_ALIGN_128B)) + align = 128; + else + align = 16 * args->bpp / 8; + + args->pitch = roundup(min_pitch, align); + + return drm_gem_cma_dumb_create_internal(file, dev, args); +} + +static struct drm_framebuffer * +rcar_du_fb_create(struct drm_device *dev, struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct rcar_du_device *rcdu = dev->dev_private; + const struct rcar_du_format_info *format; + unsigned int max_pitch; + unsigned int align; + unsigned int bpp; + + format = rcar_du_format_info(mode_cmd->pixel_format); + if (format == NULL) { + dev_dbg(dev->dev, "unsupported pixel format %08x\n", + mode_cmd->pixel_format); + return ERR_PTR(-EINVAL); + } + + /* + * The pitch and alignment constraints are expressed in pixels on the + * hardware side and in bytes in the DRM API. + */ + bpp = format->planes == 2 ? 1 : format->bpp / 8; + max_pitch = 4096 * bpp; + + if (rcar_du_needs(rcdu, RCAR_DU_QUIRK_ALIGN_128B)) + align = 128; + else + align = 16 * bpp; + + if (mode_cmd->pitches[0] & (align - 1) || + mode_cmd->pitches[0] >= max_pitch) { + dev_dbg(dev->dev, "invalid pitch value %u\n", + mode_cmd->pitches[0]); + return ERR_PTR(-EINVAL); + } + + if (format->planes == 2) { + if (mode_cmd->pitches[1] != mode_cmd->pitches[0]) { + dev_dbg(dev->dev, + "luma and chroma pitches do not match\n"); + return ERR_PTR(-EINVAL); + } + } + + return drm_fb_cma_create(dev, file_priv, mode_cmd); +} + +static void rcar_du_output_poll_changed(struct drm_device *dev) +{ + struct rcar_du_device *rcdu = dev->dev_private; + + drm_fbdev_cma_hotplug_event(rcdu->fbdev); +} + +/* ----------------------------------------------------------------------------- + * Atomic Check and Update + */ + +/* + * Atomic hardware plane allocator + * + * The hardware plane allocator is solely based on the atomic plane states + * without keeping any external state to avoid races between .atomic_check() + * and .atomic_commit(). + * + * The core idea is to avoid using a free planes bitmask that would need to be + * shared between check and commit handlers with a collective knowledge based on + * the allocated hardware plane(s) for each KMS plane. The allocator then loops + * over all plane states to compute the free planes bitmask, allocates hardware + * planes based on that bitmask, and stores the result back in the plane states. + * + * For this to work we need to access the current state of planes not touched by + * the atomic update. To ensure that it won't be modified, we need to lock all + * planes using drm_atomic_get_plane_state(). This effectively serializes atomic + * updates from .atomic_check() up to completion (when swapping the states if + * the check step has succeeded) or rollback (when freeing the states if the + * check step has failed). + * + * Allocation is performed in the .atomic_check() handler and applied + * automatically when the core swaps the old and new states. + */ + +static bool rcar_du_plane_needs_realloc(struct rcar_du_plane *plane, + struct rcar_du_plane_state *state) +{ + const struct rcar_du_format_info *cur_format; + + cur_format = to_rcar_du_plane_state(plane->plane.state)->format; + + /* Lowering the number of planes doesn't strictly require reallocation + * as the extra hardware plane will be freed when committing, but doing + * so could lead to more fragmentation. + */ + return !cur_format || cur_format->planes != state->format->planes; +} + +static unsigned int rcar_du_plane_hwmask(struct rcar_du_plane_state *state) +{ + unsigned int mask; + + if (state->hwindex == -1) + return 0; + + mask = 1 << state->hwindex; + if (state->format->planes == 2) + mask |= 1 << ((state->hwindex + 1) % 8); + + return mask; +} + +static int rcar_du_plane_hwalloc(unsigned int num_planes, unsigned int free) +{ + unsigned int i; + + for (i = 0; i < RCAR_DU_NUM_HW_PLANES; ++i) { + if (!(free & (1 << i))) + continue; + + if (num_planes == 1 || free & (1 << ((i + 1) % 8))) + break; + } + + return i == RCAR_DU_NUM_HW_PLANES ? -EBUSY : i; +} + +static int rcar_du_atomic_check(struct drm_device *dev, + struct drm_atomic_state *state) +{ + struct rcar_du_device *rcdu = dev->dev_private; + unsigned int group_freed_planes[RCAR_DU_MAX_GROUPS] = { 0, }; + unsigned int group_free_planes[RCAR_DU_MAX_GROUPS] = { 0, }; + bool needs_realloc = false; + unsigned int groups = 0; + unsigned int i; + int ret; + + ret = drm_atomic_helper_check(dev, state); + if (ret < 0) + return ret; + + /* Check if hardware planes need to be reallocated. */ + for (i = 0; i < dev->mode_config.num_total_plane; ++i) { + struct rcar_du_plane_state *plane_state; + struct rcar_du_plane *plane; + unsigned int index; + + if (!state->planes[i]) + continue; + + plane = to_rcar_plane(state->planes[i]); + plane_state = to_rcar_du_plane_state(state->plane_states[i]); + + /* If the plane is being disabled we don't need to go through + * the full reallocation procedure. Just mark the hardware + * plane(s) as freed. + */ + if (!plane_state->format) { + index = plane - plane->group->planes.planes; + group_freed_planes[plane->group->index] |= 1 << index; + plane_state->hwindex = -1; + continue; + } + + /* If the plane needs to be reallocated mark it as such, and + * mark the hardware plane(s) as free. + */ + if (rcar_du_plane_needs_realloc(plane, plane_state)) { + groups |= 1 << plane->group->index; + needs_realloc = true; + + index = plane - plane->group->planes.planes; + group_freed_planes[plane->group->index] |= 1 << index; + plane_state->hwindex = -1; + } + } + + if (!needs_realloc) + return 0; + + /* Grab all plane states for the groups that need reallocation to ensure + * locking and avoid racy updates. This serializes the update operation, + * but there's not much we can do about it as that's the hardware + * design. + * + * Compute the used planes mask for each group at the same time to avoid + * looping over the planes separately later. + */ + while (groups) { + unsigned int index = ffs(groups) - 1; + struct rcar_du_group *group = &rcdu->groups[index]; + unsigned int used_planes = 0; + + for (i = 0; i < RCAR_DU_NUM_KMS_PLANES; ++i) { + struct rcar_du_plane *plane = &group->planes.planes[i]; + struct rcar_du_plane_state *plane_state; + struct drm_plane_state *s; + + s = drm_atomic_get_plane_state(state, &plane->plane); + if (IS_ERR(s)) + return PTR_ERR(s); + + /* If the plane has been freed in the above loop its + * hardware planes must not be added to the used planes + * bitmask. However, the current state doesn't reflect + * the free state yet, as we've modified the new state + * above. Use the local freed planes list to check for + * that condition instead. + */ + if (group_freed_planes[index] & (1 << i)) + continue; + + plane_state = to_rcar_du_plane_state(plane->plane.state); + used_planes |= rcar_du_plane_hwmask(plane_state); + } + + group_free_planes[index] = 0xff & ~used_planes; + groups &= ~(1 << index); + } + + /* Reallocate hardware planes for each plane that needs it. */ + for (i = 0; i < dev->mode_config.num_total_plane; ++i) { + struct rcar_du_plane_state *plane_state; + struct rcar_du_plane *plane; + int idx; + + if (!state->planes[i]) + continue; + + plane = to_rcar_plane(state->planes[i]); + plane_state = to_rcar_du_plane_state(state->plane_states[i]); + + /* Skip planes that are being disabled or don't need to be + * reallocated. + */ + if (!plane_state->format || + !rcar_du_plane_needs_realloc(plane, plane_state)) + continue; + + idx = rcar_du_plane_hwalloc(plane_state->format->planes, + group_free_planes[plane->group->index]); + if (idx < 0) { + dev_dbg(rcdu->dev, "%s: no available hardware plane\n", + __func__); + return idx; + } + + plane_state->hwindex = idx; + + group_free_planes[plane->group->index] &= + ~rcar_du_plane_hwmask(plane_state); + } + + return 0; +} + +struct rcar_du_commit { + struct work_struct work; + struct drm_device *dev; + struct drm_atomic_state *state; + u32 crtcs; +}; + +static void rcar_du_atomic_complete(struct rcar_du_commit *commit) +{ + struct drm_device *dev = commit->dev; + struct rcar_du_device *rcdu = dev->dev_private; + struct drm_atomic_state *old_state = commit->state; + + /* Apply the atomic update. */ + drm_atomic_helper_commit_modeset_disables(dev, old_state); + drm_atomic_helper_commit_modeset_enables(dev, old_state); + drm_atomic_helper_commit_planes(dev, old_state); + + drm_atomic_helper_wait_for_vblanks(dev, old_state); + + drm_atomic_helper_cleanup_planes(dev, old_state); + + drm_atomic_state_free(old_state); + + /* Complete the commit, wake up any waiter. */ + spin_lock(&rcdu->commit.wait.lock); + rcdu->commit.pending &= ~commit->crtcs; + wake_up_all_locked(&rcdu->commit.wait); + spin_unlock(&rcdu->commit.wait.lock); + + kfree(commit); +} + +static void rcar_du_atomic_work(struct work_struct *work) +{ + struct rcar_du_commit *commit = + container_of(work, struct rcar_du_commit, work); + + rcar_du_atomic_complete(commit); +} + +static int rcar_du_atomic_commit(struct drm_device *dev, + struct drm_atomic_state *state, bool async) +{ + struct rcar_du_device *rcdu = dev->dev_private; + struct rcar_du_commit *commit; + unsigned int i; + int ret; + + ret = drm_atomic_helper_prepare_planes(dev, state); + if (ret) + return ret; + + /* Allocate the commit object. */ + commit = kzalloc(sizeof(*commit), GFP_KERNEL); + if (commit == NULL) + return -ENOMEM; + + INIT_WORK(&commit->work, rcar_du_atomic_work); + commit->dev = dev; + commit->state = state; + + /* Wait until all affected CRTCs have completed previous commits and + * mark them as pending. + */ + for (i = 0; i < dev->mode_config.num_crtc; ++i) { + if (state->crtcs[i]) + commit->crtcs |= 1 << drm_crtc_index(state->crtcs[i]); + } + + spin_lock(&rcdu->commit.wait.lock); + ret = wait_event_interruptible_locked(rcdu->commit.wait, + !(rcdu->commit.pending & commit->crtcs)); + if (ret == 0) + rcdu->commit.pending |= commit->crtcs; + spin_unlock(&rcdu->commit.wait.lock); + + if (ret) { + kfree(commit); + return ret; + } + + /* Swap the state, this is the point of no return. */ + drm_atomic_helper_swap_state(dev, state); + + if (async) + schedule_work(&commit->work); + else + rcar_du_atomic_complete(commit); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +static const struct drm_mode_config_funcs rcar_du_mode_config_funcs = { + .fb_create = rcar_du_fb_create, + .output_poll_changed = rcar_du_output_poll_changed, + .atomic_check = rcar_du_atomic_check, + .atomic_commit = rcar_du_atomic_commit, +}; + +static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, + enum rcar_du_output output, + struct of_endpoint *ep) +{ + static const struct { + const char *compatible; + enum rcar_du_encoder_type type; + } encoders[] = { + { "adi,adv7123", RCAR_DU_ENCODER_VGA }, + { "adi,adv7511w", RCAR_DU_ENCODER_HDMI }, + { "thine,thc63lvdm83d", RCAR_DU_ENCODER_LVDS }, + }; + + enum rcar_du_encoder_type enc_type = RCAR_DU_ENCODER_NONE; + struct device_node *connector = NULL; + struct device_node *encoder = NULL; + struct device_node *ep_node = NULL; + struct device_node *entity_ep_node; + struct device_node *entity; + int ret; + + /* + * Locate the connected entity and infer its type from the number of + * endpoints. + */ + entity = of_graph_get_remote_port_parent(ep->local_node); + if (!entity) { + dev_dbg(rcdu->dev, "unconnected endpoint %s, skipping\n", + ep->local_node->full_name); + return 0; + } + + entity_ep_node = of_parse_phandle(ep->local_node, "remote-endpoint", 0); + + for_each_endpoint_of_node(entity, ep_node) { + if (ep_node == entity_ep_node) + continue; + + /* + * We've found one endpoint other than the input, this must + * be an encoder. Locate the connector. + */ + encoder = entity; + connector = of_graph_get_remote_port_parent(ep_node); + of_node_put(ep_node); + + if (!connector) { + dev_warn(rcdu->dev, + "no connector for encoder %s, skipping\n", + encoder->full_name); + of_node_put(entity_ep_node); + of_node_put(encoder); + return 0; + } + + break; + } + + of_node_put(entity_ep_node); + + if (encoder) { + /* + * If an encoder has been found, get its type based on its + * compatible string. + */ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(encoders); ++i) { + if (of_device_is_compatible(encoder, + encoders[i].compatible)) { + enc_type = encoders[i].type; + break; + } + } + + if (i == ARRAY_SIZE(encoders)) { + dev_warn(rcdu->dev, + "unknown encoder type for %s, skipping\n", + encoder->full_name); + of_node_put(encoder); + of_node_put(connector); + return 0; + } + } else { + /* + * If no encoder has been found the entity must be the + * connector. + */ + connector = entity; + } + + ret = rcar_du_encoder_init(rcdu, enc_type, output, encoder, connector); + of_node_put(encoder); + of_node_put(connector); + + return ret < 0 ? ret : 1; +} + +static int rcar_du_encoders_init(struct rcar_du_device *rcdu) +{ + struct device_node *np = rcdu->dev->of_node; + struct device_node *ep_node; + unsigned int num_encoders = 0; + + /* + * Iterate over the endpoints and create one encoder for each output + * pipeline. + */ + for_each_endpoint_of_node(np, ep_node) { + enum rcar_du_output output; + struct of_endpoint ep; + unsigned int i; + int ret; + + ret = of_graph_parse_endpoint(ep_node, &ep); + if (ret < 0) { + of_node_put(ep_node); + return ret; + } + + /* Find the output route corresponding to the port number. */ + for (i = 0; i < RCAR_DU_OUTPUT_MAX; ++i) { + if (rcdu->info->routes[i].possible_crtcs && + rcdu->info->routes[i].port == ep.port) { + output = i; + break; + } + } + + if (i == RCAR_DU_OUTPUT_MAX) { + dev_warn(rcdu->dev, + "port %u references unexisting output, skipping\n", + ep.port); + continue; + } + + /* Process the output pipeline. */ + ret = rcar_du_encoders_init_one(rcdu, output, &ep); + if (ret < 0) { + if (ret == -EPROBE_DEFER) { + of_node_put(ep_node); + return ret; + } + + dev_info(rcdu->dev, + "encoder initialization failed, skipping\n"); + continue; + } + + num_encoders += ret; + } + + return num_encoders; +} + +int rcar_du_modeset_init(struct rcar_du_device *rcdu) +{ + static const unsigned int mmio_offsets[] = { + DU0_REG_OFFSET, DU2_REG_OFFSET + }; + + struct drm_device *dev = rcdu->ddev; + struct drm_encoder *encoder; + struct drm_fbdev_cma *fbdev; + unsigned int num_encoders; + unsigned int num_groups; + unsigned int i; + int ret; + + drm_mode_config_init(dev); + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = 4095; + dev->mode_config.max_height = 2047; + dev->mode_config.funcs = &rcar_du_mode_config_funcs; + + rcdu->num_crtcs = rcdu->info->num_crtcs; + + /* Initialize the groups. */ + num_groups = DIV_ROUND_UP(rcdu->num_crtcs, 2); + + for (i = 0; i < num_groups; ++i) { + struct rcar_du_group *rgrp = &rcdu->groups[i]; + + mutex_init(&rgrp->lock); + + rgrp->dev = rcdu; + rgrp->mmio_offset = mmio_offsets[i]; + rgrp->index = i; + + ret = rcar_du_planes_init(rgrp); + if (ret < 0) + return ret; + } + + /* Create the CRTCs. */ + for (i = 0; i < rcdu->num_crtcs; ++i) { + struct rcar_du_group *rgrp = &rcdu->groups[i / 2]; + + ret = rcar_du_crtc_create(rgrp, i); + if (ret < 0) + return ret; + } + + /* Initialize the encoders. */ + ret = rcar_du_lvdsenc_init(rcdu); + if (ret < 0) + return ret; + + ret = rcar_du_encoders_init(rcdu); + if (ret < 0) + return ret; + + if (ret == 0) { + dev_err(rcdu->dev, "error: no encoder could be initialized\n"); + return -EINVAL; + } + + num_encoders = ret; + + /* Set the possible CRTCs and possible clones. There's always at least + * one way for all encoders to clone each other, set all bits in the + * possible clones field. + */ + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + const struct rcar_du_output_routing *route = + &rcdu->info->routes[renc->output]; + + encoder->possible_crtcs = route->possible_crtcs; + encoder->possible_clones = (1 << num_encoders) - 1; + } + + drm_mode_config_reset(dev); + + drm_kms_helper_poll_init(dev); + + if (dev->mode_config.num_connector) { + fbdev = drm_fbdev_cma_init(dev, 32, dev->mode_config.num_crtc, + dev->mode_config.num_connector); + if (IS_ERR(fbdev)) + return PTR_ERR(fbdev); + + rcdu->fbdev = fbdev; + } else { + dev_info(rcdu->dev, + "no connector found, disabling fbdev emulation\n"); + } + + return 0; +} diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_kms.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_kms.h new file mode 100644 index 000000000..07951d5fe --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_kms.h @@ -0,0 +1,39 @@ +/* + * rcar_du_kms.h -- R-Car Display Unit Mode Setting + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_KMS_H__ +#define __RCAR_DU_KMS_H__ + +#include <linux/types.h> + +struct drm_file; +struct drm_device; +struct drm_mode_create_dumb; +struct rcar_du_device; + +struct rcar_du_format_info { + u32 fourcc; + unsigned int bpp; + unsigned int planes; + unsigned int pnmr; + unsigned int edf; +}; + +const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc); + +int rcar_du_modeset_init(struct rcar_du_device *rcdu); + +int rcar_du_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args); + +#endif /* __RCAR_DU_KMS_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c new file mode 100644 index 000000000..0c43032fc --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c @@ -0,0 +1,135 @@ +/* + * rcar_du_lvdscon.c -- R-Car Display Unit LVDS Connector + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> + +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include "rcar_du_drv.h" +#include "rcar_du_encoder.h" +#include "rcar_du_kms.h" +#include "rcar_du_lvdscon.h" + +struct rcar_du_lvds_connector { + struct rcar_du_connector connector; + + struct { + unsigned int width_mm; /* Panel width in mm */ + unsigned int height_mm; /* Panel height in mm */ + struct videomode mode; + } panel; +}; + +#define to_rcar_lvds_connector(c) \ + container_of(c, struct rcar_du_lvds_connector, connector.connector) + +static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector) +{ + struct rcar_du_lvds_connector *lvdscon = + to_rcar_lvds_connector(connector); + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (mode == NULL) + return 0; + + mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; + + drm_display_mode_from_videomode(&lvdscon->panel.mode, mode); + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = rcar_du_lvds_connector_get_modes, + .best_encoder = rcar_du_connector_best_encoder, +}; + +static void rcar_du_lvds_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static enum drm_connector_status +rcar_du_lvds_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .detect = rcar_du_lvds_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = rcar_du_lvds_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc, + /* TODO const */ struct device_node *np) +{ + struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc); + struct rcar_du_lvds_connector *lvdscon; + struct drm_connector *connector; + struct display_timing timing; + int ret; + + lvdscon = devm_kzalloc(rcdu->dev, sizeof(*lvdscon), GFP_KERNEL); + if (lvdscon == NULL) + return -ENOMEM; + + ret = of_get_display_timing(np, "panel-timing", &timing); + if (ret < 0) + return ret; + + videomode_from_timing(&timing, &lvdscon->panel.mode); + + of_property_read_u32(np, "width-mm", &lvdscon->panel.width_mm); + of_property_read_u32(np, "height-mm", &lvdscon->panel.height_mm); + + connector = &lvdscon->connector.connector; + connector->display_info.width_mm = lvdscon->panel.width_mm; + connector->display_info.height_mm = lvdscon->panel.height_mm; + + ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + if (ret < 0) + return ret; + + drm_connector_helper_add(connector, &connector_helper_funcs); + ret = drm_connector_register(connector); + if (ret < 0) + return ret; + + connector->dpms = DRM_MODE_DPMS_OFF; + drm_object_property_set_value(&connector->base, + rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF); + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret < 0) + return ret; + + lvdscon->connector.encoder = renc; + + return 0; +} diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h new file mode 100644 index 000000000..d4881ee0b --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h @@ -0,0 +1,24 @@ +/* + * rcar_du_lvdscon.h -- R-Car Display Unit LVDS Connector + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_LVDSCON_H__ +#define __RCAR_DU_LVDSCON_H__ + +struct rcar_du_device; +struct rcar_du_encoder; + +int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc, + struct device_node *np); + +#endif /* __RCAR_DU_LVDSCON_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c new file mode 100644 index 000000000..85043c5ba --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c @@ -0,0 +1,192 @@ +/* + * rcar_du_lvdsenc.c -- R-Car Display Unit LVDS Encoder + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include "rcar_du_drv.h" +#include "rcar_du_encoder.h" +#include "rcar_du_lvdsenc.h" +#include "rcar_lvds_regs.h" + +struct rcar_du_lvdsenc { + struct rcar_du_device *dev; + + unsigned int index; + void __iomem *mmio; + struct clk *clock; + bool enabled; + + enum rcar_lvds_input input; +}; + +static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data) +{ + iowrite32(data, lvds->mmio + reg); +} + +static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds, + struct rcar_du_crtc *rcrtc) +{ + const struct drm_display_mode *mode = &rcrtc->crtc.mode; + unsigned int freq = mode->clock; + u32 lvdcr0; + u32 lvdhcr; + u32 pllcr; + int ret; + + if (lvds->enabled) + return 0; + + ret = clk_prepare_enable(lvds->clock); + if (ret < 0) + return ret; + + /* PLL clock configuration */ + if (freq <= 38000) + pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M; + else if (freq <= 60000) + pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M; + else if (freq <= 121000) + pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M; + else + pllcr = LVDPLLCR_PLLDLYCNT_150M; + + rcar_lvds_write(lvds, LVDPLLCR, pllcr); + + /* Hardcode the channels and control signals routing for now. + * + * HSYNC -> CTRL0 + * VSYNC -> CTRL1 + * DISP -> CTRL2 + * 0 -> CTRL3 + */ + rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO | + LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC | + LVDCTRCR_CTR0SEL_HSYNC); + + if (rcar_du_needs(lvds->dev, RCAR_DU_QUIRK_LVDS_LANES)) + lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3) + | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1); + else + lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1) + | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3); + + rcar_lvds_write(lvds, LVDCHCR, lvdhcr); + + /* Select the input, hardcode mode 0, enable LVDS operation and turn + * bias circuitry on. + */ + lvdcr0 = LVDCR0_BEN | LVDCR0_LVEN; + if (rcrtc->index == 2) + lvdcr0 |= LVDCR0_DUSEL; + rcar_lvds_write(lvds, LVDCR0, lvdcr0); + + /* Turn all the channels on. */ + rcar_lvds_write(lvds, LVDCR1, LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) | + LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY); + + /* Turn the PLL on, wait for the startup delay, and turn the output + * on. + */ + lvdcr0 |= LVDCR0_PLLEN; + rcar_lvds_write(lvds, LVDCR0, lvdcr0); + + usleep_range(100, 150); + + lvdcr0 |= LVDCR0_LVRES; + rcar_lvds_write(lvds, LVDCR0, lvdcr0); + + lvds->enabled = true; + return 0; +} + +static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds) +{ + if (!lvds->enabled) + return; + + rcar_lvds_write(lvds, LVDCR0, 0); + rcar_lvds_write(lvds, LVDCR1, 0); + + clk_disable_unprepare(lvds->clock); + + lvds->enabled = false; +} + +int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, struct drm_crtc *crtc, + bool enable) +{ + if (!enable) { + rcar_du_lvdsenc_stop(lvds); + return 0; + } else if (crtc) { + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + return rcar_du_lvdsenc_start(lvds, rcrtc); + } else + return -EINVAL; +} + +static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds, + struct platform_device *pdev) +{ + struct resource *mem; + char name[7]; + + sprintf(name, "lvds.%u", lvds->index); + + mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + lvds->mmio = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(lvds->mmio)) + return PTR_ERR(lvds->mmio); + + lvds->clock = devm_clk_get(&pdev->dev, name); + if (IS_ERR(lvds->clock)) { + dev_err(&pdev->dev, "failed to get clock for %s\n", name); + return PTR_ERR(lvds->clock); + } + + return 0; +} + +int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu) +{ + struct platform_device *pdev = to_platform_device(rcdu->dev); + struct rcar_du_lvdsenc *lvds; + unsigned int i; + int ret; + + for (i = 0; i < rcdu->info->num_lvds; ++i) { + lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL); + if (lvds == NULL) { + dev_err(&pdev->dev, "failed to allocate private data\n"); + return -ENOMEM; + } + + lvds->dev = rcdu; + lvds->index = i; + lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0; + lvds->enabled = false; + + ret = rcar_du_lvdsenc_get_resources(lvds, pdev); + if (ret < 0) + return ret; + + rcdu->lvds[i] = lvds; + } + + return 0; +} diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h new file mode 100644 index 000000000..9a6001c07 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h @@ -0,0 +1,45 @@ +/* + * rcar_du_lvdsenc.h -- R-Car Display Unit LVDS Encoder + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_LVDSENC_H__ +#define __RCAR_DU_LVDSENC_H__ + +#include <linux/io.h> +#include <linux/module.h> + +struct rcar_drm_crtc; +struct rcar_du_lvdsenc; + +enum rcar_lvds_input { + RCAR_LVDS_INPUT_DU0, + RCAR_LVDS_INPUT_DU1, + RCAR_LVDS_INPUT_DU2, +}; + +#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS) +int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu); +int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, + struct drm_crtc *crtc, bool enable); +#else +static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu) +{ + return 0; +} +static inline int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, + struct drm_crtc *crtc, bool enable) +{ + return 0; +} +#endif + +#endif /* __RCAR_DU_LVDSENC_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_plane.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_plane.c new file mode 100644 index 000000000..210e5c3fd --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_plane.c @@ -0,0 +1,460 @@ +/* + * rcar_du_plane.c -- R-Car Display Unit Planes + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <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/drm_plane_helper.h> + +#include "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_plane.h" +#include "rcar_du_regs.h" + +#define RCAR_DU_COLORKEY_NONE (0 << 24) +#define RCAR_DU_COLORKEY_SOURCE (1 << 24) +#define RCAR_DU_COLORKEY_MASK (1 << 24) + +static u32 rcar_du_plane_read(struct rcar_du_group *rgrp, + unsigned int index, u32 reg) +{ + return rcar_du_read(rgrp->dev, + rgrp->mmio_offset + index * PLANE_OFF + reg); +} + +static void rcar_du_plane_write(struct rcar_du_group *rgrp, + unsigned int index, u32 reg, u32 data) +{ + rcar_du_write(rgrp->dev, rgrp->mmio_offset + index * PLANE_OFF + reg, + data); +} + +static void rcar_du_plane_setup_fb(struct rcar_du_plane *plane) +{ + struct rcar_du_plane_state *state = + to_rcar_du_plane_state(plane->plane.state); + struct drm_framebuffer *fb = plane->plane.state->fb; + struct rcar_du_group *rgrp = plane->group; + unsigned int src_x = state->state.src_x >> 16; + unsigned int src_y = state->state.src_y >> 16; + unsigned int index = state->hwindex; + struct drm_gem_cma_object *gem; + bool interlaced; + u32 mwr; + + interlaced = state->state.crtc->state->adjusted_mode.flags + & DRM_MODE_FLAG_INTERLACE; + + /* Memory pitch (expressed in pixels). Must be doubled for interlaced + * operation with 32bpp formats. + */ + if (state->format->planes == 2) + mwr = fb->pitches[0]; + else + mwr = fb->pitches[0] * 8 / state->format->bpp; + + if (interlaced && state->format->bpp == 32) + mwr *= 2; + + rcar_du_plane_write(rgrp, index, PnMWR, mwr); + + /* The Y position is expressed in raster line units and must be doubled + * for 32bpp formats, according to the R8A7790 datasheet. No mention of + * doubling the Y position is found in the R8A7779 datasheet, but the + * rule seems to apply there as well. + * + * Despite not being documented, doubling seem not to be needed when + * operating in interlaced mode. + * + * Similarly, for the second plane, NV12 and NV21 formats seem to + * require a halved Y position value, in both progressive and interlaced + * modes. + */ + rcar_du_plane_write(rgrp, index, PnSPXR, src_x); + rcar_du_plane_write(rgrp, index, PnSPYR, src_y * + (!interlaced && state->format->bpp == 32 ? 2 : 1)); + + gem = drm_fb_cma_get_gem_obj(fb, 0); + rcar_du_plane_write(rgrp, index, PnDSA0R, gem->paddr + fb->offsets[0]); + + if (state->format->planes == 2) { + index = (index + 1) % 8; + + rcar_du_plane_write(rgrp, index, PnMWR, fb->pitches[0]); + + rcar_du_plane_write(rgrp, index, PnSPXR, src_x); + rcar_du_plane_write(rgrp, index, PnSPYR, src_y * + (state->format->bpp == 16 ? 2 : 1) / 2); + + gem = drm_fb_cma_get_gem_obj(fb, 1); + rcar_du_plane_write(rgrp, index, PnDSA0R, + gem->paddr + fb->offsets[1]); + } +} + +static void rcar_du_plane_setup_mode(struct rcar_du_plane *plane, + unsigned int index) +{ + struct rcar_du_plane_state *state = + to_rcar_du_plane_state(plane->plane.state); + struct rcar_du_group *rgrp = plane->group; + u32 colorkey; + u32 pnmr; + + /* The PnALPHAR register controls alpha-blending in 16bpp formats + * (ARGB1555 and XRGB1555). + * + * For ARGB, set the alpha value to 0, and enable alpha-blending when + * the A bit is 0. This maps A=0 to alpha=0 and A=1 to alpha=255. + * + * For XRGB, set the alpha value to the plane-wide alpha value and + * enable alpha-blending regardless of the X bit value. + */ + if (state->format->fourcc != DRM_FORMAT_XRGB1555) + rcar_du_plane_write(rgrp, index, PnALPHAR, PnALPHAR_ABIT_0); + else + rcar_du_plane_write(rgrp, index, PnALPHAR, + PnALPHAR_ABIT_X | state->alpha); + + pnmr = PnMR_BM_MD | state->format->pnmr; + + /* Disable color keying when requested. YUV formats have the + * PnMR_SPIM_TP_OFF bit set in their pnmr field, disabling color keying + * automatically. + */ + if ((state->colorkey & RCAR_DU_COLORKEY_MASK) == RCAR_DU_COLORKEY_NONE) + pnmr |= PnMR_SPIM_TP_OFF; + + /* For packed YUV formats we need to select the U/V order. */ + if (state->format->fourcc == DRM_FORMAT_YUYV) + pnmr |= PnMR_YCDF_YUYV; + + rcar_du_plane_write(rgrp, index, PnMR, pnmr); + + switch (state->format->fourcc) { + case DRM_FORMAT_RGB565: + colorkey = ((state->colorkey & 0xf80000) >> 8) + | ((state->colorkey & 0x00fc00) >> 5) + | ((state->colorkey & 0x0000f8) >> 3); + rcar_du_plane_write(rgrp, index, PnTC2R, colorkey); + break; + + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + colorkey = ((state->colorkey & 0xf80000) >> 9) + | ((state->colorkey & 0x00f800) >> 6) + | ((state->colorkey & 0x0000f8) >> 3); + rcar_du_plane_write(rgrp, index, PnTC2R, colorkey); + break; + + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + rcar_du_plane_write(rgrp, index, PnTC3R, + PnTC3R_CODE | (state->colorkey & 0xffffff)); + break; + } +} + +static void __rcar_du_plane_setup(struct rcar_du_plane *plane, + unsigned int index) +{ + struct rcar_du_plane_state *state = + to_rcar_du_plane_state(plane->plane.state); + struct rcar_du_group *rgrp = plane->group; + u32 ddcr2 = PnDDCR2_CODE; + u32 ddcr4; + + /* Data format + * + * The data format is selected by the DDDF field in PnMR and the EDF + * field in DDCR4. + */ + ddcr4 = rcar_du_plane_read(rgrp, index, PnDDCR4); + ddcr4 &= ~PnDDCR4_EDF_MASK; + ddcr4 |= state->format->edf | PnDDCR4_CODE; + + rcar_du_plane_setup_mode(plane, index); + + if (state->format->planes == 2) { + if (state->hwindex != index) { + if (state->format->fourcc == DRM_FORMAT_NV12 || + state->format->fourcc == DRM_FORMAT_NV21) + ddcr2 |= PnDDCR2_Y420; + + if (state->format->fourcc == DRM_FORMAT_NV21) + ddcr2 |= PnDDCR2_NV21; + + ddcr2 |= PnDDCR2_DIVU; + } else { + ddcr2 |= PnDDCR2_DIVY; + } + } + + rcar_du_plane_write(rgrp, index, PnDDCR2, ddcr2); + rcar_du_plane_write(rgrp, index, PnDDCR4, ddcr4); + + /* Destination position and size */ + rcar_du_plane_write(rgrp, index, PnDSXR, plane->plane.state->crtc_w); + rcar_du_plane_write(rgrp, index, PnDSYR, plane->plane.state->crtc_h); + rcar_du_plane_write(rgrp, index, PnDPXR, plane->plane.state->crtc_x); + rcar_du_plane_write(rgrp, index, PnDPYR, plane->plane.state->crtc_y); + + /* Wrap-around and blinking, disabled */ + rcar_du_plane_write(rgrp, index, PnWASPR, 0); + rcar_du_plane_write(rgrp, index, PnWAMWR, 4095); + rcar_du_plane_write(rgrp, index, PnBTR, 0); + rcar_du_plane_write(rgrp, index, PnMLR, 0); +} + +void rcar_du_plane_setup(struct rcar_du_plane *plane) +{ + struct rcar_du_plane_state *state = + to_rcar_du_plane_state(plane->plane.state); + + __rcar_du_plane_setup(plane, state->hwindex); + if (state->format->planes == 2) + __rcar_du_plane_setup(plane, (state->hwindex + 1) % 8); + + rcar_du_plane_setup_fb(plane); +} + +static int rcar_du_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct rcar_du_plane_state *rstate = to_rcar_du_plane_state(state); + struct rcar_du_plane *rplane = to_rcar_plane(plane); + struct rcar_du_device *rcdu = rplane->group->dev; + + if (!state->fb || !state->crtc) { + rstate->format = NULL; + return 0; + } + + if (state->src_w >> 16 != state->crtc_w || + state->src_h >> 16 != state->crtc_h) { + dev_dbg(rcdu->dev, "%s: scaling not supported\n", __func__); + return -EINVAL; + } + + rstate->format = rcar_du_format_info(state->fb->pixel_format); + if (rstate->format == NULL) { + dev_dbg(rcdu->dev, "%s: unsupported format %08x\n", __func__, + state->fb->pixel_format); + return -EINVAL; + } + + return 0; +} + +static void rcar_du_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct rcar_du_plane *rplane = to_rcar_plane(plane); + + if (plane->state->crtc) + rcar_du_plane_setup(rplane); +} + +static const struct drm_plane_helper_funcs rcar_du_plane_helper_funcs = { + .atomic_check = rcar_du_plane_atomic_check, + .atomic_update = rcar_du_plane_atomic_update, +}; + +static void rcar_du_plane_reset(struct drm_plane *plane) +{ + struct rcar_du_plane_state *state; + + if (plane->state && plane->state->fb) + drm_framebuffer_unreference(plane->state->fb); + + kfree(plane->state); + plane->state = NULL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state == NULL) + return; + + state->hwindex = -1; + state->alpha = 255; + state->colorkey = RCAR_DU_COLORKEY_NONE; + state->zpos = plane->type == DRM_PLANE_TYPE_PRIMARY ? 0 : 1; + + plane->state = &state->state; + plane->state->plane = plane; +} + +static struct drm_plane_state * +rcar_du_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct rcar_du_plane_state *state; + struct rcar_du_plane_state *copy; + + state = to_rcar_du_plane_state(plane->state); + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (copy == NULL) + return NULL; + + if (copy->state.fb) + drm_framebuffer_reference(copy->state.fb); + + return ©->state; +} + +static void rcar_du_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + if (state->fb) + drm_framebuffer_unreference(state->fb); + + kfree(to_rcar_du_plane_state(state)); +} + +static int rcar_du_plane_atomic_set_property(struct drm_plane *plane, + struct drm_plane_state *state, + struct drm_property *property, + uint64_t val) +{ + struct rcar_du_plane_state *rstate = to_rcar_du_plane_state(state); + struct rcar_du_plane *rplane = to_rcar_plane(plane); + struct rcar_du_group *rgrp = rplane->group; + + if (property == rgrp->planes.alpha) + rstate->alpha = val; + else if (property == rgrp->planes.colorkey) + rstate->colorkey = val; + else if (property == rgrp->planes.zpos) + rstate->zpos = val; + else + return -EINVAL; + + return 0; +} + +static int rcar_du_plane_atomic_get_property(struct drm_plane *plane, + const struct drm_plane_state *state, struct drm_property *property, + uint64_t *val) +{ + const struct rcar_du_plane_state *rstate = + container_of(state, const struct rcar_du_plane_state, state); + struct rcar_du_plane *rplane = to_rcar_plane(plane); + struct rcar_du_group *rgrp = rplane->group; + + if (property == rgrp->planes.alpha) + *val = rstate->alpha; + else if (property == rgrp->planes.colorkey) + *val = rstate->colorkey; + else if (property == rgrp->planes.zpos) + *val = rstate->zpos; + else + return -EINVAL; + + return 0; +} + +static const struct drm_plane_funcs rcar_du_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = rcar_du_plane_reset, + .set_property = drm_atomic_helper_plane_set_property, + .destroy = drm_plane_cleanup, + .atomic_duplicate_state = rcar_du_plane_atomic_duplicate_state, + .atomic_destroy_state = rcar_du_plane_atomic_destroy_state, + .atomic_set_property = rcar_du_plane_atomic_set_property, + .atomic_get_property = rcar_du_plane_atomic_get_property, +}; + +static const uint32_t formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV16, +}; + +int rcar_du_planes_init(struct rcar_du_group *rgrp) +{ + struct rcar_du_planes *planes = &rgrp->planes; + struct rcar_du_device *rcdu = rgrp->dev; + unsigned int num_planes; + unsigned int num_crtcs; + unsigned int crtcs; + unsigned int i; + int ret; + + planes->alpha = + drm_property_create_range(rcdu->ddev, 0, "alpha", 0, 255); + if (planes->alpha == NULL) + return -ENOMEM; + + /* The color key is expressed as an RGB888 triplet stored in a 32-bit + * integer in XRGB8888 format. Bit 24 is used as a flag to disable (0) + * or enable source color keying (1). + */ + planes->colorkey = + drm_property_create_range(rcdu->ddev, 0, "colorkey", + 0, 0x01ffffff); + if (planes->colorkey == NULL) + return -ENOMEM; + + planes->zpos = + drm_property_create_range(rcdu->ddev, 0, "zpos", 1, 7); + if (planes->zpos == NULL) + return -ENOMEM; + + /* Create one primary plane per in this group CRTC and seven overlay + * planes. + */ + num_crtcs = min(rcdu->num_crtcs - 2 * rgrp->index, 2U); + num_planes = num_crtcs + 7; + + crtcs = ((1 << rcdu->num_crtcs) - 1) & (3 << (2 * rgrp->index)); + + for (i = 0; i < num_planes; ++i) { + enum drm_plane_type type = i < num_crtcs + ? DRM_PLANE_TYPE_PRIMARY + : DRM_PLANE_TYPE_OVERLAY; + struct rcar_du_plane *plane = &planes->planes[i]; + + plane->group = rgrp; + + ret = drm_universal_plane_init(rcdu->ddev, &plane->plane, crtcs, + &rcar_du_plane_funcs, formats, + ARRAY_SIZE(formats), type); + if (ret < 0) + return ret; + + drm_plane_helper_add(&plane->plane, + &rcar_du_plane_helper_funcs); + + if (type == DRM_PLANE_TYPE_PRIMARY) + continue; + + drm_object_attach_property(&plane->plane.base, + planes->alpha, 255); + drm_object_attach_property(&plane->plane.base, + planes->colorkey, + RCAR_DU_COLORKEY_NONE); + drm_object_attach_property(&plane->plane.base, + planes->zpos, 1); + } + + return 0; +} diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_plane.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_plane.h new file mode 100644 index 000000000..abff0ebeb --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_plane.h @@ -0,0 +1,70 @@ +/* + * rcar_du_plane.h -- R-Car Display Unit Planes + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_PLANE_H__ +#define __RCAR_DU_PLANE_H__ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> + +struct rcar_du_format_info; +struct rcar_du_group; + +/* The RCAR DU has 8 hardware planes, shared between primary and overlay planes. + * As using overlay planes requires at least one of the CRTCs being enabled, no + * more than 7 overlay planes can be available. We thus create 1 primary plane + * per CRTC and 7 overlay planes, for a total of up to 9 KMS planes. + */ +#define RCAR_DU_NUM_KMS_PLANES 9 +#define RCAR_DU_NUM_HW_PLANES 8 + +struct rcar_du_plane { + struct drm_plane plane; + struct rcar_du_group *group; +}; + +static inline struct rcar_du_plane *to_rcar_plane(struct drm_plane *plane) +{ + return container_of(plane, struct rcar_du_plane, plane); +} + +struct rcar_du_planes { + struct rcar_du_plane planes[RCAR_DU_NUM_KMS_PLANES]; + + struct drm_property *alpha; + struct drm_property *colorkey; + struct drm_property *zpos; +}; + +struct rcar_du_plane_state { + struct drm_plane_state state; + + const struct rcar_du_format_info *format; + int hwindex; /* 0-based, -1 means unused */ + + unsigned int alpha; + unsigned int colorkey; + unsigned int zpos; +}; + +static inline struct rcar_du_plane_state * +to_rcar_du_plane_state(struct drm_plane_state *state) +{ + return container_of(state, struct rcar_du_plane_state, state); +} + +int rcar_du_planes_init(struct rcar_du_group *rgrp); + +void rcar_du_plane_setup(struct rcar_du_plane *plane); + +#endif /* __RCAR_DU_PLANE_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_regs.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_regs.h new file mode 100644 index 000000000..70fcbc471 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_regs.h @@ -0,0 +1,514 @@ +/* + * rcar_du_regs.h -- R-Car Display Unit Registers Definitions + * + * Copyright (C) 2013 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ + +#ifndef __RCAR_DU_REGS_H__ +#define __RCAR_DU_REGS_H__ + +#define DU0_REG_OFFSET 0x00000 +#define DU1_REG_OFFSET 0x30000 +#define DU2_REG_OFFSET 0x40000 + +/* ----------------------------------------------------------------------------- + * Display Control Registers + */ + +#define DSYSR 0x00000 /* display 1 */ +#define DSYSR_ILTS (1 << 29) +#define DSYSR_DSEC (1 << 20) +#define DSYSR_IUPD (1 << 16) +#define DSYSR_DRES (1 << 9) +#define DSYSR_DEN (1 << 8) +#define DSYSR_TVM_MASTER (0 << 6) +#define DSYSR_TVM_SWITCH (1 << 6) +#define DSYSR_TVM_TVSYNC (2 << 6) +#define DSYSR_TVM_MASK (3 << 6) +#define DSYSR_SCM_INT_NONE (0 << 4) +#define DSYSR_SCM_INT_SYNC (2 << 4) +#define DSYSR_SCM_INT_VIDEO (3 << 4) +#define DSYSR_SCM_MASK (3 << 4) + +#define DSMR 0x00004 +#define DSMR_VSPM (1 << 28) +#define DSMR_ODPM (1 << 27) +#define DSMR_DIPM_DISP (0 << 25) +#define DSMR_DIPM_CSYNC (1 << 25) +#define DSMR_DIPM_DE (3 << 25) +#define DSMR_DIPM_MASK (3 << 25) +#define DSMR_CSPM (1 << 24) +#define DSMR_DIL (1 << 19) +#define DSMR_VSL (1 << 18) +#define DSMR_HSL (1 << 17) +#define DSMR_DDIS (1 << 16) +#define DSMR_CDEL (1 << 15) +#define DSMR_CDEM_CDE (0 << 13) +#define DSMR_CDEM_LOW (2 << 13) +#define DSMR_CDEM_HIGH (3 << 13) +#define DSMR_CDEM_MASK (3 << 13) +#define DSMR_CDED (1 << 12) +#define DSMR_ODEV (1 << 8) +#define DSMR_CSY_VH_OR (0 << 6) +#define DSMR_CSY_333 (2 << 6) +#define DSMR_CSY_222 (3 << 6) +#define DSMR_CSY_MASK (3 << 6) + +#define DSSR 0x00008 +#define DSSR_VC1FB_DSA0 (0 << 30) +#define DSSR_VC1FB_DSA1 (1 << 30) +#define DSSR_VC1FB_DSA2 (2 << 30) +#define DSSR_VC1FB_INIT (3 << 30) +#define DSSR_VC1FB_MASK (3 << 30) +#define DSSR_VC0FB_DSA0 (0 << 28) +#define DSSR_VC0FB_DSA1 (1 << 28) +#define DSSR_VC0FB_DSA2 (2 << 28) +#define DSSR_VC0FB_INIT (3 << 28) +#define DSSR_VC0FB_MASK (3 << 28) +#define DSSR_DFB(n) (1 << ((n)+15)) +#define DSSR_TVR (1 << 15) +#define DSSR_FRM (1 << 14) +#define DSSR_VBK (1 << 11) +#define DSSR_RINT (1 << 9) +#define DSSR_HBK (1 << 8) +#define DSSR_ADC(n) (1 << ((n)-1)) + +#define DSRCR 0x0000c +#define DSRCR_TVCL (1 << 15) +#define DSRCR_FRCL (1 << 14) +#define DSRCR_VBCL (1 << 11) +#define DSRCR_RICL (1 << 9) +#define DSRCR_HBCL (1 << 8) +#define DSRCR_ADCL(n) (1 << ((n)-1)) +#define DSRCR_MASK 0x0000cbff + +#define DIER 0x00010 +#define DIER_TVE (1 << 15) +#define DIER_FRE (1 << 14) +#define DIER_VBE (1 << 11) +#define DIER_RIE (1 << 9) +#define DIER_HBE (1 << 8) +#define DIER_ADCE(n) (1 << ((n)-1)) + +#define CPCR 0x00014 +#define CPCR_CP4CE (1 << 19) +#define CPCR_CP3CE (1 << 18) +#define CPCR_CP2CE (1 << 17) +#define CPCR_CP1CE (1 << 16) + +#define DPPR 0x00018 +#define DPPR_DPE(n) (1 << ((n)*4-1)) +#define DPPR_DPS(n, p) (((p)-1) << DPPR_DPS_SHIFT(n)) +#define DPPR_DPS_SHIFT(n) (((n)-1)*4) +#define DPPR_BPP16 (DPPR_DPE(8) | DPPR_DPS(8, 1)) /* plane1 */ +#define DPPR_BPP32_P1 (DPPR_DPE(7) | DPPR_DPS(7, 1)) +#define DPPR_BPP32_P2 (DPPR_DPE(8) | DPPR_DPS(8, 2)) +#define DPPR_BPP32 (DPPR_BPP32_P1 | DPPR_BPP32_P2) /* plane1 & 2 */ + +#define DEFR 0x00020 +#define DEFR_CODE (0x7773 << 16) +#define DEFR_EXSL (1 << 12) +#define DEFR_EXVL (1 << 11) +#define DEFR_EXUP (1 << 5) +#define DEFR_VCUP (1 << 4) +#define DEFR_DEFE (1 << 0) + +#define DAPCR 0x00024 +#define DAPCR_CODE (0x7773 << 16) +#define DAPCR_AP2E (1 << 4) +#define DAPCR_AP1E (1 << 0) + +#define DCPCR 0x00028 +#define DCPCR_CODE (0x7773 << 16) +#define DCPCR_CA2B (1 << 13) +#define DCPCR_CD2F (1 << 12) +#define DCPCR_DC2E (1 << 8) +#define DCPCR_CAB (1 << 5) +#define DCPCR_CDF (1 << 4) +#define DCPCR_DCE (1 << 0) + +#define DEFR2 0x00034 +#define DEFR2_CODE (0x7775 << 16) +#define DEFR2_DEFE2G (1 << 0) + +#define DEFR3 0x00038 +#define DEFR3_CODE (0x7776 << 16) +#define DEFR3_EVDA (1 << 14) +#define DEFR3_EVDM_1 (1 << 12) +#define DEFR3_EVDM_2 (2 << 12) +#define DEFR3_EVDM_3 (3 << 12) +#define DEFR3_VMSM2_EMA (1 << 6) +#define DEFR3_VMSM1_ENA (1 << 4) +#define DEFR3_DEFE3 (1 << 0) + +#define DEFR4 0x0003c +#define DEFR4_CODE (0x7777 << 16) +#define DEFR4_LRUO (1 << 5) +#define DEFR4_SPCE (1 << 4) + +#define DVCSR 0x000d0 +#define DVCSR_VCnFB2_DSA0(n) (0 << ((n)*2+16)) +#define DVCSR_VCnFB2_DSA1(n) (1 << ((n)*2+16)) +#define DVCSR_VCnFB2_DSA2(n) (2 << ((n)*2+16)) +#define DVCSR_VCnFB2_INIT(n) (3 << ((n)*2+16)) +#define DVCSR_VCnFB2_MASK(n) (3 << ((n)*2+16)) +#define DVCSR_VCnFB_DSA0(n) (0 << ((n)*2)) +#define DVCSR_VCnFB_DSA1(n) (1 << ((n)*2)) +#define DVCSR_VCnFB_DSA2(n) (2 << ((n)*2)) +#define DVCSR_VCnFB_INIT(n) (3 << ((n)*2)) +#define DVCSR_VCnFB_MASK(n) (3 << ((n)*2)) + +#define DEFR5 0x000e0 +#define DEFR5_CODE (0x66 << 24) +#define DEFR5_YCRGB2_DIS (0 << 14) +#define DEFR5_YCRGB2_PRI1 (1 << 14) +#define DEFR5_YCRGB2_PRI2 (2 << 14) +#define DEFR5_YCRGB2_PRI3 (3 << 14) +#define DEFR5_YCRGB2_MASK (3 << 14) +#define DEFR5_YCRGB1_DIS (0 << 12) +#define DEFR5_YCRGB1_PRI1 (1 << 12) +#define DEFR5_YCRGB1_PRI2 (2 << 12) +#define DEFR5_YCRGB1_PRI3 (3 << 12) +#define DEFR5_YCRGB1_MASK (3 << 12) +#define DEFR5_DEFE5 (1 << 0) + +#define DDLTR 0x000e4 +#define DDLTR_CODE (0x7766 << 16) +#define DDLTR_DLAR2 (1 << 6) +#define DDLTR_DLAY2 (1 << 5) +#define DDLTR_DLAY1 (1 << 1) + +#define DEFR6 0x000e8 +#define DEFR6_CODE (0x7778 << 16) +#define DEFR6_ODPM22_D2SMR (0 << 10) +#define DEFR6_ODPM22_DISP (2 << 10) +#define DEFR6_ODPM22_CDE (3 << 10) +#define DEFR6_ODPM22_MASK (3 << 10) +#define DEFR6_ODPM12_DSMR (0 << 8) +#define DEFR6_ODPM12_DISP (2 << 8) +#define DEFR6_ODPM12_CDE (3 << 8) +#define DEFR6_ODPM12_MASK (3 << 8) +#define DEFR6_TCNE2 (1 << 6) +#define DEFR6_MLOS1 (1 << 2) +#define DEFR6_DEFAULT (DEFR6_CODE | DEFR6_TCNE2) + +/* ----------------------------------------------------------------------------- + * R8A7790-only Control Registers + */ + +#define DD1SSR 0x20008 +#define DD1SSR_TVR (1 << 15) +#define DD1SSR_FRM (1 << 14) +#define DD1SSR_BUF (1 << 12) +#define DD1SSR_VBK (1 << 11) +#define DD1SSR_RINT (1 << 9) +#define DD1SSR_HBK (1 << 8) +#define DD1SSR_ADC(n) (1 << ((n)-1)) + +#define DD1SRCR 0x2000c +#define DD1SRCR_TVR (1 << 15) +#define DD1SRCR_FRM (1 << 14) +#define DD1SRCR_BUF (1 << 12) +#define DD1SRCR_VBK (1 << 11) +#define DD1SRCR_RINT (1 << 9) +#define DD1SRCR_HBK (1 << 8) +#define DD1SRCR_ADC(n) (1 << ((n)-1)) + +#define DD1IER 0x20010 +#define DD1IER_TVR (1 << 15) +#define DD1IER_FRM (1 << 14) +#define DD1IER_BUF (1 << 12) +#define DD1IER_VBK (1 << 11) +#define DD1IER_RINT (1 << 9) +#define DD1IER_HBK (1 << 8) +#define DD1IER_ADC(n) (1 << ((n)-1)) + +#define DEFR8 0x20020 +#define DEFR8_CODE (0x7790 << 16) +#define DEFR8_VSCS (1 << 6) +#define DEFR8_DRGBS_DU(n) ((n) << 4) +#define DEFR8_DRGBS_MASK (3 << 4) +#define DEFR8_DEFE8 (1 << 0) + +#define DOFLR 0x20024 +#define DOFLR_CODE (0x7790 << 16) +#define DOFLR_HSYCFL1 (1 << 13) +#define DOFLR_VSYCFL1 (1 << 12) +#define DOFLR_ODDFL1 (1 << 11) +#define DOFLR_DISPFL1 (1 << 10) +#define DOFLR_CDEFL1 (1 << 9) +#define DOFLR_RGBFL1 (1 << 8) +#define DOFLR_HSYCFL0 (1 << 5) +#define DOFLR_VSYCFL0 (1 << 4) +#define DOFLR_ODDFL0 (1 << 3) +#define DOFLR_DISPFL0 (1 << 2) +#define DOFLR_CDEFL0 (1 << 1) +#define DOFLR_RGBFL0 (1 << 0) + +#define DIDSR 0x20028 +#define DIDSR_CODE (0x7790 << 16) +#define DIDSR_LCDS_DCLKIN(n) (0 << (8 + (n) * 2)) +#define DIDSR_LCDS_LVDS0(n) (2 << (8 + (n) * 2)) +#define DIDSR_LCDS_LVDS1(n) (3 << (8 + (n) * 2)) +#define DIDSR_LCDS_MASK(n) (3 << (8 + (n) * 2)) +#define DIDSR_PDCS_CLK(n, clk) (clk << ((n) * 2)) +#define DIDSR_PDCS_MASK(n) (3 << ((n) * 2)) + +/* ----------------------------------------------------------------------------- + * Display Timing Generation Registers + */ + +#define HDSR 0x00040 +#define HDER 0x00044 +#define VDSR 0x00048 +#define VDER 0x0004c +#define HCR 0x00050 +#define HSWR 0x00054 +#define VCR 0x00058 +#define VSPR 0x0005c +#define EQWR 0x00060 +#define SPWR 0x00064 +#define CLAMPSR 0x00070 +#define CLAMPWR 0x00074 +#define DESR 0x00078 +#define DEWR 0x0007c + +/* ----------------------------------------------------------------------------- + * Display Attribute Registers + */ + +#define CP1TR 0x00080 +#define CP2TR 0x00084 +#define CP3TR 0x00088 +#define CP4TR 0x0008c + +#define DOOR 0x00090 +#define DOOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2)) +#define CDER 0x00094 +#define CDER_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2)) +#define BPOR 0x00098 +#define BPOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2)) + +#define RINTOFSR 0x0009c + +#define DSHPR 0x000c8 +#define DSHPR_CODE (0x7776 << 16) +#define DSHPR_PRIH (0xa << 4) +#define DSHPR_PRIL_BPP16 (0x8 << 0) +#define DSHPR_PRIL_BPP32 (0x9 << 0) + +/* ----------------------------------------------------------------------------- + * Display Plane Registers + */ + +#define PLANE_OFF 0x00100 + +#define PnMR 0x00100 /* plane 1 */ +#define PnMR_VISL_VIN0 (0 << 26) /* use Video Input 0 */ +#define PnMR_VISL_VIN1 (1 << 26) /* use Video Input 1 */ +#define PnMR_VISL_VIN2 (2 << 26) /* use Video Input 2 */ +#define PnMR_VISL_VIN3 (3 << 26) /* use Video Input 3 */ +#define PnMR_YCDF_YUYV (1 << 20) /* YUYV format */ +#define PnMR_TC_R (0 << 17) /* Tranparent color is PnTC1R */ +#define PnMR_TC_CP (1 << 17) /* Tranparent color is color palette */ +#define PnMR_WAE (1 << 16) /* Wrap around Enable */ +#define PnMR_SPIM_TP (0 << 12) /* Transparent Color */ +#define PnMR_SPIM_ALP (1 << 12) /* Alpha Blending */ +#define PnMR_SPIM_EOR (2 << 12) /* EOR */ +#define PnMR_SPIM_TP_OFF (1 << 14) /* No Transparent Color */ +#define PnMR_CPSL_CP1 (0 << 8) /* Color Palette selected 1 */ +#define PnMR_CPSL_CP2 (1 << 8) /* Color Palette selected 2 */ +#define PnMR_CPSL_CP3 (2 << 8) /* Color Palette selected 3 */ +#define PnMR_CPSL_CP4 (3 << 8) /* Color Palette selected 4 */ +#define PnMR_DC (1 << 7) /* Display Area Change */ +#define PnMR_BM_MD (0 << 4) /* Manual Display Change Mode */ +#define PnMR_BM_AR (1 << 4) /* Auto Rendering Mode */ +#define PnMR_BM_AD (2 << 4) /* Auto Display Change Mode */ +#define PnMR_BM_VC (3 << 4) /* Video Capture Mode */ +#define PnMR_DDDF_8BPP (0 << 0) /* 8bit */ +#define PnMR_DDDF_16BPP (1 << 0) /* 16bit or 32bit */ +#define PnMR_DDDF_ARGB (2 << 0) /* ARGB */ +#define PnMR_DDDF_YC (3 << 0) /* YC */ +#define PnMR_DDDF_MASK (3 << 0) + +#define PnMWR 0x00104 + +#define PnALPHAR 0x00108 +#define PnALPHAR_ABIT_1 (0 << 12) +#define PnALPHAR_ABIT_0 (1 << 12) +#define PnALPHAR_ABIT_X (2 << 12) + +#define PnDSXR 0x00110 +#define PnDSYR 0x00114 +#define PnDPXR 0x00118 +#define PnDPYR 0x0011c + +#define PnDSA0R 0x00120 +#define PnDSA1R 0x00124 +#define PnDSA2R 0x00128 +#define PnDSA_MASK 0xfffffff0 + +#define PnSPXR 0x00130 +#define PnSPYR 0x00134 +#define PnWASPR 0x00138 +#define PnWAMWR 0x0013c + +#define PnBTR 0x00140 + +#define PnTC1R 0x00144 +#define PnTC2R 0x00148 +#define PnTC3R 0x0014c +#define PnTC3R_CODE (0x66 << 24) + +#define PnMLR 0x00150 + +#define PnSWAPR 0x00180 +#define PnSWAPR_DIGN (1 << 4) +#define PnSWAPR_SPQW (1 << 3) +#define PnSWAPR_SPLW (1 << 2) +#define PnSWAPR_SPWD (1 << 1) +#define PnSWAPR_SPBY (1 << 0) + +#define PnDDCR 0x00184 +#define PnDDCR_CODE (0x7775 << 16) +#define PnDDCR_LRGB1 (1 << 11) +#define PnDDCR_LRGB0 (1 << 10) + +#define PnDDCR2 0x00188 +#define PnDDCR2_CODE (0x7776 << 16) +#define PnDDCR2_NV21 (1 << 5) +#define PnDDCR2_Y420 (1 << 4) +#define PnDDCR2_DIVU (1 << 1) +#define PnDDCR2_DIVY (1 << 0) + +#define PnDDCR4 0x00190 +#define PnDDCR4_CODE (0x7766 << 16) +#define PnDDCR4_SDFS_RGB (0 << 4) +#define PnDDCR4_SDFS_YC (5 << 4) +#define PnDDCR4_SDFS_MASK (7 << 4) +#define PnDDCR4_EDF_NONE (0 << 0) +#define PnDDCR4_EDF_ARGB8888 (1 << 0) +#define PnDDCR4_EDF_RGB888 (2 << 0) +#define PnDDCR4_EDF_RGB666 (3 << 0) +#define PnDDCR4_EDF_MASK (7 << 0) + +#define APnMR 0x0a100 +#define APnMR_WAE (1 << 16) /* Wrap around Enable */ +#define APnMR_DC (1 << 7) /* Display Area Change */ +#define APnMR_BM_MD (0 << 4) /* Manual Display Change Mode */ +#define APnMR_BM_AD (2 << 4) /* Auto Display Change Mode */ + +#define APnMWR 0x0a104 + +#define APnDSXR 0x0a110 +#define APnDSYR 0x0a114 +#define APnDPXR 0x0a118 +#define APnDPYR 0x0a11c + +#define APnDSA0R 0x0a120 +#define APnDSA1R 0x0a124 +#define APnDSA2R 0x0a128 + +#define APnSPXR 0x0a130 +#define APnSPYR 0x0a134 +#define APnWASPR 0x0a138 +#define APnWAMWR 0x0a13c + +#define APnBTR 0x0a140 + +#define APnMLR 0x0a150 +#define APnSWAPR 0x0a180 + +/* ----------------------------------------------------------------------------- + * Display Capture Registers + */ + +#define DCMR 0x0c100 +#define DCMWR 0x0c104 +#define DCSAR 0x0c120 +#define DCMLR 0x0c150 + +/* ----------------------------------------------------------------------------- + * Color Palette Registers + */ + +#define CP1_000R 0x01000 +#define CP1_255R 0x013fc +#define CP2_000R 0x02000 +#define CP2_255R 0x023fc +#define CP3_000R 0x03000 +#define CP3_255R 0x033fc +#define CP4_000R 0x04000 +#define CP4_255R 0x043fc + +/* ----------------------------------------------------------------------------- + * External Synchronization Control Registers + */ + +#define ESCR 0x10000 +#define ESCR2 0x31000 +#define ESCR_DCLKOINV (1 << 25) +#define ESCR_DCLKSEL_DCLKIN (0 << 20) +#define ESCR_DCLKSEL_CLKS (1 << 20) +#define ESCR_DCLKSEL_MASK (1 << 20) +#define ESCR_DCLKDIS (1 << 16) +#define ESCR_SYNCSEL_OFF (0 << 8) +#define ESCR_SYNCSEL_EXVSYNC (2 << 8) +#define ESCR_SYNCSEL_EXHSYNC (3 << 8) +#define ESCR_FRQSEL_MASK (0x3f << 0) + +#define OTAR 0x10004 +#define OTAR2 0x31004 + +/* ----------------------------------------------------------------------------- + * Dual Display Output Control Registers + */ + +#define DORCR 0x11000 +#define DORCR_PG2T (1 << 30) +#define DORCR_DK2S (1 << 28) +#define DORCR_PG2D_DS1 (0 << 24) +#define DORCR_PG2D_DS2 (1 << 24) +#define DORCR_PG2D_FIX0 (2 << 24) +#define DORCR_PG2D_DOOR (3 << 24) +#define DORCR_PG2D_MASK (3 << 24) +#define DORCR_DR1D (1 << 21) +#define DORCR_PG1D_DS1 (0 << 16) +#define DORCR_PG1D_DS2 (1 << 16) +#define DORCR_PG1D_FIX0 (2 << 16) +#define DORCR_PG1D_DOOR (3 << 16) +#define DORCR_PG1D_MASK (3 << 16) +#define DORCR_RGPV (1 << 4) +#define DORCR_DPRS (1 << 0) + +#define DPTSR 0x11004 +#define DPTSR_PnDK(n) (1 << ((n) + 16)) +#define DPTSR_PnTS(n) (1 << (n)) + +#define DAPTSR 0x11008 +#define DAPTSR_APnDK(n) (1 << ((n) + 16)) +#define DAPTSR_APnTS(n) (1 << (n)) + +#define DS1PR 0x11020 +#define DS2PR 0x11024 + +/* ----------------------------------------------------------------------------- + * YC-RGB Conversion Coefficient Registers + */ + +#define YNCR 0x11080 +#define YNOR 0x11084 +#define CRNOR 0x11088 +#define CBNOR 0x1108c +#define RCRCR 0x11090 +#define GCRCR 0x11094 +#define GCBCR 0x11098 +#define BCBCR 0x1109c + +#endif /* __RCAR_DU_REGS_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c b/kernel/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c new file mode 100644 index 000000000..e0a5d8f93 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c @@ -0,0 +1,94 @@ +/* + * rcar_du_vgacon.c -- R-Car Display Unit VGA Connector + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> + +#include "rcar_du_drv.h" +#include "rcar_du_encoder.h" +#include "rcar_du_kms.h" +#include "rcar_du_vgacon.h" + +static int rcar_du_vga_connector_get_modes(struct drm_connector *connector) +{ + return 0; +} + +static const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = rcar_du_vga_connector_get_modes, + .best_encoder = rcar_du_connector_best_encoder, +}; + +static void rcar_du_vga_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static enum drm_connector_status +rcar_du_vga_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .detect = rcar_du_vga_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = rcar_du_vga_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +int rcar_du_vga_connector_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc) +{ + struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc); + struct rcar_du_connector *rcon; + struct drm_connector *connector; + int ret; + + rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL); + if (rcon == NULL) + return -ENOMEM; + + connector = &rcon->connector; + connector->display_info.width_mm = 0; + connector->display_info.height_mm = 0; + connector->interlace_allowed = true; + + ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, + DRM_MODE_CONNECTOR_VGA); + if (ret < 0) + return ret; + + drm_connector_helper_add(connector, &connector_helper_funcs); + ret = drm_connector_register(connector); + if (ret < 0) + return ret; + + connector->dpms = DRM_MODE_DPMS_OFF; + drm_object_property_set_value(&connector->base, + rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF); + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret < 0) + return ret; + + rcon->encoder = renc; + + return 0; +} diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h b/kernel/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h new file mode 100644 index 000000000..112f50316 --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h @@ -0,0 +1,23 @@ +/* + * rcar_du_vgacon.h -- R-Car Display Unit VGA Connector + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __RCAR_DU_VGACON_H__ +#define __RCAR_DU_VGACON_H__ + +struct rcar_du_device; +struct rcar_du_encoder; + +int rcar_du_vga_connector_init(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc); + +#endif /* __RCAR_DU_VGACON_H__ */ diff --git a/kernel/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h b/kernel/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h new file mode 100644 index 000000000..77cf9289a --- /dev/null +++ b/kernel/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h @@ -0,0 +1,69 @@ +/* + * rcar_lvds_regs.h -- R-Car LVDS Interface Registers Definitions + * + * Copyright (C) 2013 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ + +#ifndef __RCAR_LVDS_REGS_H__ +#define __RCAR_LVDS_REGS_H__ + +#define LVDCR0 0x0000 +#define LVDCR0_DUSEL (1 << 15) +#define LVDCR0_DMD (1 << 12) +#define LVDCR0_LVMD_MASK (0xf << 8) +#define LVDCR0_LVMD_SHIFT 8 +#define LVDCR0_PLLEN (1 << 4) +#define LVDCR0_BEN (1 << 2) +#define LVDCR0_LVEN (1 << 1) +#define LVDCR0_LVRES (1 << 0) + +#define LVDCR1 0x0004 +#define LVDCR1_CKSEL (1 << 15) +#define LVDCR1_CHSTBY(n) (3 << (2 + (n) * 2)) +#define LVDCR1_CLKSTBY (3 << 0) + +#define LVDPLLCR 0x0008 +#define LVDPLLCR_CEEN (1 << 14) +#define LVDPLLCR_FBEN (1 << 13) +#define LVDPLLCR_COSEL (1 << 12) +#define LVDPLLCR_PLLDLYCNT_150M (0x1bf << 0) +#define LVDPLLCR_PLLDLYCNT_121M (0x22c << 0) +#define LVDPLLCR_PLLDLYCNT_60M (0x77b << 0) +#define LVDPLLCR_PLLDLYCNT_38M (0x69a << 0) +#define LVDPLLCR_PLLDLYCNT_MASK (0x7ff << 0) + +#define LVDCTRCR 0x000c +#define LVDCTRCR_CTR3SEL_ZERO (0 << 12) +#define LVDCTRCR_CTR3SEL_ODD (1 << 12) +#define LVDCTRCR_CTR3SEL_CDE (2 << 12) +#define LVDCTRCR_CTR3SEL_MASK (7 << 12) +#define LVDCTRCR_CTR2SEL_DISP (0 << 8) +#define LVDCTRCR_CTR2SEL_ODD (1 << 8) +#define LVDCTRCR_CTR2SEL_CDE (2 << 8) +#define LVDCTRCR_CTR2SEL_HSYNC (3 << 8) +#define LVDCTRCR_CTR2SEL_VSYNC (4 << 8) +#define LVDCTRCR_CTR2SEL_MASK (7 << 8) +#define LVDCTRCR_CTR1SEL_VSYNC (0 << 4) +#define LVDCTRCR_CTR1SEL_DISP (1 << 4) +#define LVDCTRCR_CTR1SEL_ODD (2 << 4) +#define LVDCTRCR_CTR1SEL_CDE (3 << 4) +#define LVDCTRCR_CTR1SEL_HSYNC (4 << 4) +#define LVDCTRCR_CTR1SEL_MASK (7 << 4) +#define LVDCTRCR_CTR0SEL_HSYNC (0 << 0) +#define LVDCTRCR_CTR0SEL_VSYNC (1 << 0) +#define LVDCTRCR_CTR0SEL_DISP (2 << 0) +#define LVDCTRCR_CTR0SEL_ODD (3 << 0) +#define LVDCTRCR_CTR0SEL_CDE (4 << 0) +#define LVDCTRCR_CTR0SEL_MASK (7 << 0) + +#define LVDCHCR 0x0010 +#define LVDCHCR_CHSEL_CH(n, c) ((((c) - (n)) & 3) << ((n) * 4)) +#define LVDCHCR_CHSEL_MASK(n) (3 << ((n) * 4)) + +#endif /* __RCAR_LVDS_REGS_H__ */ |