diff options
Diffstat (limited to 'drivers/gpu/drm/rcar-du')
22 files changed, 3621 insertions, 0 deletions
diff --git a/drivers/gpu/drm/rcar-du/Kconfig b/drivers/gpu/drm/rcar-du/Kconfig new file mode 100644 index 00000000000..2e3d7b5b0ad --- /dev/null +++ b/drivers/gpu/drm/rcar-du/Kconfig @@ -0,0 +1,19 @@ +config DRM_RCAR_DU +	tristate "DRM Support for R-Car Display Unit" +	depends on DRM && ARM +	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 +	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_LVDS +	bool "R-Car DU LVDS Encoder Support" +	depends on DRM_RCAR_DU +	depends on ARCH_R8A7790 || ARCH_R8A7791 || COMPILE_TEST +	help +	  Enable support the R-Car Display Unit embedded LVDS encoders +	  (currently only on R8A7790). diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile new file mode 100644 index 00000000000..12b8d447783 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/Makefile @@ -0,0 +1,12 @@ +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_LVDS)	+= rcar_du_lvdsenc.o + +obj-$(CONFIG_DRM_RCAR_DU)		+= rcar-du-drm.o diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c new file mode 100644 index 00000000000..299267db289 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -0,0 +1,610 @@ +/* + * rcar_du_crtc.c  --  R-Car Display Unit CRTCs + * + * Copyright (C) 2013 Renesas 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_crtc.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_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 = rcar_du_group_get(rcrtc->group); +	if (ret < 0) +		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->clock); +} + +static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) +{ +	const struct drm_display_mode *mode = &rcrtc->crtc.mode; +	unsigned long clk; +	u32 value; +	u32 div; + +	/* Dot clock */ +	clk = clk_get_rate(rcrtc->clock); +	div = DIV_ROUND_CLOSEST(clk, mode->clock * 1000); +	div = clamp(div, 1U, 64U) - 1; + +	rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? ESCR2 : ESCR, +			    ESCR_DCLKSEL_CLKS | div); +	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; +	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->vtotal - mode->vsync_end - 2); +	rcar_du_crtc_write(rcrtc, VDER, mode->vtotal - mode->vsync_end + +					mode->vdisplay - 2); +	rcar_du_crtc_write(rcrtc, VSPR, mode->vtotal - mode->vsync_end + +					mode->vsync_start - 1); +	rcar_du_crtc_write(rcrtc, VCR,  mode->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 for R8A7790. */ +	if (rcar_du_has(rcdu, RCAR_DU_FEATURE_DEFR8) && +	    output == RCAR_DU_OUTPUT_DPAD0) +		rcdu->dpad0_source = rcrtc->index; +} + +void rcar_du_crtc_update_planes(struct drm_crtc *crtc) +{ +	struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); +	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->crtc != &rcrtc->crtc || !plane->enabled) +			continue; + +		/* Insert the plane in the sorted planes array. */ +		for (j = num_planes++; j > 0; --j) { +			if (planes[j-1]->zpos <= plane->zpos) +				break; +			planes[j] = planes[j-1]; +		} + +		planes[j] = plane; +		prio += plane->format->planes * 4; +	} + +	for (i = 0; i < num_planes; ++i) { +		struct rcar_du_plane *plane = planes[i]; +		unsigned int index = plane->hwindex; + +		prio -= 4; +		dspr |= (index + 1) << prio; +		dptsr |= DPTSR_PnDK(index) |  DPTSR_PnTS(index); + +		if (plane->format->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) { +		u32 value = rcar_du_group_read(rcrtc->group, DPTSR); + +		/* 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. +		 */ +		if (value != dptsr) { +			rcar_du_group_write(rcrtc->group, DPTSR, dptsr); +			if (rcrtc->group->used_crtcs) +				rcar_du_group_restart(rcrtc->group); +		} +	} + +	rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, +			    dspr); +} + +static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc) +{ +	struct drm_crtc *crtc = &rcrtc->crtc; +	unsigned int i; + +	if (rcrtc->started) +		return; + +	if (WARN_ON(rcrtc->plane->format == NULL)) +		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); + +	mutex_lock(&rcrtc->group->planes.lock); +	rcrtc->plane->enabled = true; +	rcar_du_crtc_update_planes(crtc); +	mutex_unlock(&rcrtc->group->planes.lock); + +	/* Setup planes. */ +	for (i = 0; i < ARRAY_SIZE(rcrtc->group->planes.planes); ++i) { +		struct rcar_du_plane *plane = &rcrtc->group->planes.planes[i]; + +		if (plane->crtc != crtc || !plane->enabled) +			continue; + +		rcar_du_plane_setup(plane); +	} + +	/* Select master sync mode. This enables display operation in master +	 * sync mode (with the HSYNC and VSYNC signals configured as outputs and +	 * actively driven). +	 */ +	rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_MASTER); + +	rcar_du_group_start_stop(rcrtc->group, true); + +	rcrtc->started = true; +} + +static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc) +{ +	struct drm_crtc *crtc = &rcrtc->crtc; + +	if (!rcrtc->started) +		return; + +	mutex_lock(&rcrtc->group->planes.lock); +	rcrtc->plane->enabled = false; +	rcar_du_crtc_update_planes(crtc); +	mutex_unlock(&rcrtc->group->planes.lock); + +	/* 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) +{ +	if (rcrtc->dpms != DRM_MODE_DPMS_ON) +		return; + +	rcar_du_crtc_get(rcrtc); +	rcar_du_crtc_start(rcrtc); +} + +static void rcar_du_crtc_update_base(struct rcar_du_crtc *rcrtc) +{ +	struct drm_crtc *crtc = &rcrtc->crtc; + +	rcar_du_plane_compute_base(rcrtc->plane, crtc->primary->fb); +	rcar_du_plane_update_base(rcrtc->plane); +} + +static void rcar_du_crtc_dpms(struct drm_crtc *crtc, int mode) +{ +	struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + +	if (rcrtc->dpms == mode) +		return; + +	if (mode == DRM_MODE_DPMS_ON) { +		rcar_du_crtc_get(rcrtc); +		rcar_du_crtc_start(rcrtc); +	} else { +		rcar_du_crtc_stop(rcrtc); +		rcar_du_crtc_put(rcrtc); +	} + +	rcrtc->dpms = mode; +} + +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_mode_prepare(struct drm_crtc *crtc) +{ +	struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + +	/* We need to access the hardware during mode set, acquire a reference +	 * to the CRTC. +	 */ +	rcar_du_crtc_get(rcrtc); + +	/* Stop the CRTC and release the plane. Force the DPMS mode to off as a +	 * result. +	 */ +	rcar_du_crtc_stop(rcrtc); +	rcar_du_plane_release(rcrtc->plane); + +	rcrtc->dpms = DRM_MODE_DPMS_OFF; +} + +static int rcar_du_crtc_mode_set(struct drm_crtc *crtc, +				 struct drm_display_mode *mode, +				 struct drm_display_mode *adjusted_mode, +				 int x, int y, +				 struct drm_framebuffer *old_fb) +{ +	struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); +	struct rcar_du_device *rcdu = rcrtc->group->dev; +	const struct rcar_du_format_info *format; +	int ret; + +	format = rcar_du_format_info(crtc->primary->fb->pixel_format); +	if (format == NULL) { +		dev_dbg(rcdu->dev, "mode_set: unsupported format %08x\n", +			crtc->primary->fb->pixel_format); +		ret = -EINVAL; +		goto error; +	} + +	ret = rcar_du_plane_reserve(rcrtc->plane, format); +	if (ret < 0) +		goto error; + +	rcrtc->plane->format = format; + +	rcrtc->plane->src_x = x; +	rcrtc->plane->src_y = y; +	rcrtc->plane->width = mode->hdisplay; +	rcrtc->plane->height = mode->vdisplay; + +	rcar_du_plane_compute_base(rcrtc->plane, crtc->primary->fb); + +	rcrtc->outputs = 0; + +	return 0; + +error: +	/* There's no rollback/abort operation to clean up in case of error. We +	 * thus need to release the reference to the CRTC acquired in prepare() +	 * here. +	 */ +	rcar_du_crtc_put(rcrtc); +	return ret; +} + +static void rcar_du_crtc_mode_commit(struct drm_crtc *crtc) +{ +	struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + +	/* We're done, restart the CRTC and set the DPMS mode to on. The +	 * reference to the DU acquired at prepare() time will thus be released +	 * by the DPMS handler (possibly called by the disable() handler). +	 */ +	rcar_du_crtc_start(rcrtc); +	rcrtc->dpms = DRM_MODE_DPMS_ON; +} + +static int rcar_du_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, +				      struct drm_framebuffer *old_fb) +{ +	struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + +	rcrtc->plane->src_x = x; +	rcrtc->plane->src_y = y; + +	rcar_du_crtc_update_base(rcrtc); + +	return 0; +} + +static void rcar_du_crtc_disable(struct drm_crtc *crtc) +{ +	struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + +	rcar_du_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +	rcar_du_plane_release(rcrtc->plane); +} + +static const struct drm_crtc_helper_funcs crtc_helper_funcs = { +	.dpms = rcar_du_crtc_dpms, +	.mode_fixup = rcar_du_crtc_mode_fixup, +	.prepare = rcar_du_crtc_mode_prepare, +	.commit = rcar_du_crtc_mode_commit, +	.mode_set = rcar_du_crtc_mode_set, +	.mode_set_base = rcar_du_crtc_mode_set_base, +	.disable = rcar_du_crtc_disable, +}; + +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_vblank_put(dev, rcrtc->index); +	} +	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); +	spin_unlock_irqrestore(&dev->event_lock, flags); + +	drm_vblank_put(dev, rcrtc->index); +} + +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_VBK) { +		drm_handle_vblank(rcrtc->crtc.dev, rcrtc->index); +		rcar_du_crtc_finish_page_flip(rcrtc); +		ret = IRQ_HANDLED; +	} + +	return ret; +} + +static int rcar_du_crtc_page_flip(struct drm_crtc *crtc, +				  struct drm_framebuffer *fb, +				  struct drm_pending_vblank_event *event, +				  uint32_t page_flip_flags) +{ +	struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); +	struct drm_device *dev = rcrtc->crtc.dev; +	unsigned long flags; + +	spin_lock_irqsave(&dev->event_lock, flags); +	if (rcrtc->event != NULL) { +		spin_unlock_irqrestore(&dev->event_lock, flags); +		return -EBUSY; +	} +	spin_unlock_irqrestore(&dev->event_lock, flags); + +	crtc->primary->fb = fb; +	rcar_du_crtc_update_base(rcrtc); + +	if (event) { +		event->pipe = rcrtc->index; +		drm_vblank_get(dev, rcrtc->index); +		spin_lock_irqsave(&dev->event_lock, flags); +		rcrtc->event = event; +		spin_unlock_irqrestore(&dev->event_lock, flags); +	} + +	return 0; +} + +static const struct drm_crtc_funcs crtc_funcs = { +	.destroy = drm_crtc_cleanup, +	.set_config = drm_crtc_helper_set_config, +	.page_flip = rcar_du_crtc_page_flip, +}; + +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; +	char clk_name[5]; +	char *name; +	int irq; +	int ret; + +	/* Get the CRTC 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); +	} + +	rcrtc->group = rgrp; +	rcrtc->mmio_offset = mmio_offsets[index]; +	rcrtc->index = index; +	rcrtc->dpms = DRM_MODE_DPMS_OFF; +	rcrtc->plane = &rgrp->planes.planes[index % 2]; + +	rcrtc->plane->crtc = crtc; + +	ret = drm_crtc_init(rcdu->ddev, crtc, &crtc_funcs); +	if (ret < 0) +		return ret; + +	drm_crtc_helper_add(crtc, &crtc_helper_funcs); + +	/* 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 ret; +	} + +	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/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h new file mode 100644 index 00000000000..43e7575c700 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h @@ -0,0 +1,55 @@ +/* + * rcar_du_crtc.h  --  R-Car Display Unit CRTCs + * + * Copyright (C) 2013 Renesas 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/platform_data/rcar-du.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> + +struct rcar_du_group; +struct rcar_du_plane; + +struct rcar_du_crtc { +	struct drm_crtc crtc; + +	struct clk *clock; +	unsigned int mmio_offset; +	unsigned int index; +	bool started; + +	struct drm_pending_vblank_event *event; +	unsigned int outputs; +	int dpms; + +	struct rcar_du_group *group; +	struct rcar_du_plane *plane; +}; + +#define to_rcar_crtc(c)	container_of(c, struct rcar_du_crtc, crtc) + +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); +void rcar_du_crtc_update_planes(struct drm_crtc *crtc); + +#endif /* __RCAR_DU_CRTC_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c new file mode 100644 index 00000000000..792fd1d20e8 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c @@ -0,0 +1,320 @@ +/* + * rcar_du_drv.c  --  R-Car Display Unit DRM driver + * + * Copyright (C) 2013 Renesas 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/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.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" + +/* ----------------------------------------------------------------------------- + * 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 rcar_du_platform_data *pdata = pdev->dev.platform_data; +	struct rcar_du_device *rcdu; +	struct resource *mem; +	int ret; + +	if (pdata == 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; +	} + +	rcdu->dev = &pdev->dev; +	rcdu->pdata = pdata; +	rcdu->info = (struct rcar_du_device_info *)pdev->id_entry->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); + +	/* DRM/KMS objects */ +	ret = rcar_du_modeset_init(rcdu); +	if (ret < 0) { +		dev_err(&pdev->dev, "failed to initialize DRM/KMS\n"); +		goto done; +	} + +	/* vblank handling */ +	ret = drm_vblank_init(dev, (1 << rcdu->num_crtcs) - 1); +	if (ret < 0) { +		dev_err(&pdev->dev, "failed to initialize vblank\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, +	.load			= rcar_du_load, +	.unload			= rcar_du_unload, +	.preclose		= rcar_du_preclose, +	.lastclose		= rcar_du_lastclose, +	.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 + */ + +#if 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 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, +		}, +		[RCAR_DU_OUTPUT_DPAD1] = { +			.possible_crtcs = BIT(1) | BIT(0), +			.encoder_type = DRM_MODE_ENCODER_NONE, +		}, +	}, +	.num_lvds = 0, +}; + +static const struct rcar_du_device_info rcar_du_r8a7790_info = { +	.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK | RCAR_DU_FEATURE_DEFR8, +	.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, +		}, +		[RCAR_DU_OUTPUT_LVDS0] = { +			.possible_crtcs = BIT(0), +			.encoder_type = DRM_MODE_ENCODER_LVDS, +		}, +		[RCAR_DU_OUTPUT_LVDS1] = { +			.possible_crtcs = BIT(2) | BIT(1), +			.encoder_type = DRM_MODE_ENCODER_LVDS, +		}, +	}, +	.num_lvds = 2, +}; + +static const struct rcar_du_device_info rcar_du_r8a7791_info = { +	.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK | RCAR_DU_FEATURE_DEFR8, +	.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, +		}, +		[RCAR_DU_OUTPUT_LVDS0] = { +			.possible_crtcs = BIT(0), +			.encoder_type = DRM_MODE_ENCODER_LVDS, +		}, +	}, +	.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 struct platform_driver rcar_du_platform_driver = { +	.probe		= rcar_du_probe, +	.remove		= rcar_du_remove, +	.driver		= { +		.owner	= THIS_MODULE, +		.name	= "rcar-du", +		.pm	= &rcar_du_pm_ops, +	}, +	.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/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h new file mode 100644 index 00000000000..e31b735d3f2 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h @@ -0,0 +1,107 @@ +/* + * rcar_du_drv.h  --  R-Car Display Unit DRM driver + * + * Copyright (C) 2013 Renesas 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/platform_data/rcar-du.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_DEFR8		(1 << 1)	/* Has DEFR8 register */ + +#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 + * + * 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; +}; + +/* + * 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; +}; + +struct rcar_du_device { +	struct device *dev; +	const struct rcar_du_platform_data *pdata; +	const struct rcar_du_device_info *info; + +	void __iomem *mmio; + +	struct drm_device *ddev; +	struct drm_fbdev_cma *fbdev; + +	struct rcar_du_crtc crtcs[3]; +	unsigned int num_crtcs; + +	struct rcar_du_group groups[2]; + +	unsigned int dpad0_source; +	struct rcar_du_lvdsenc *lvds[2]; +}; + +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/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c new file mode 100644 index 00000000000..3daa7a168dc --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c @@ -0,0 +1,202 @@ +/* + * rcar_du_encoder.c  --  R-Car Display Unit Encoder + * + * Copyright (C) 2013 Renesas 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_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 &rcon->encoder->encoder; +} + +/* ----------------------------------------------------------------------------- + * Encoder + */ + +static void rcar_du_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +	struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + +	if (renc->lvds) +		rcar_du_lvdsenc_dpms(renc->lvds, encoder->crtc, mode); +} + +static bool rcar_du_encoder_mode_fixup(struct drm_encoder *encoder, +				       const struct drm_display_mode *mode, +				       struct drm_display_mode *adjusted_mode) +{ +	struct rcar_du_encoder *renc = to_rcar_encoder(encoder); +	const struct drm_display_mode *panel_mode; +	struct drm_device *dev = encoder->dev; +	struct drm_connector *connector; +	bool found = false; + +	/* DAC encoders have currently no restriction on the mode. */ +	if (encoder->encoder_type == DRM_MODE_ENCODER_DAC) +		return true; + +	list_for_each_entry(connector, &dev->mode_config.connector_list, head) { +		if (connector->encoder == encoder) { +			found = true; +			break; +		} +	} + +	if (!found) { +		dev_dbg(dev->dev, "mode_fixup: no connector found\n"); +		return false; +	} + +	if (list_empty(&connector->modes)) { +		dev_dbg(dev->dev, "mode_fixup: empty modes list\n"); +		return false; +	} + +	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 false; + +	/* 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 true; +} + +static void rcar_du_encoder_mode_prepare(struct drm_encoder *encoder) +{ +	struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + +	if (renc->lvds) +		rcar_du_lvdsenc_dpms(renc->lvds, encoder->crtc, +				     DRM_MODE_DPMS_OFF); +} + +static void rcar_du_encoder_mode_commit(struct drm_encoder *encoder) +{ +	struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + +	if (renc->lvds) +		rcar_du_lvdsenc_dpms(renc->lvds, encoder->crtc, +				     DRM_MODE_DPMS_ON); +} + +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 = { +	.dpms = rcar_du_encoder_dpms, +	.mode_fixup = rcar_du_encoder_mode_fixup, +	.prepare = rcar_du_encoder_mode_prepare, +	.commit = rcar_du_encoder_mode_commit, +	.mode_set = rcar_du_encoder_mode_set, +}; + +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, +			 const struct rcar_du_encoder_data *data) +{ +	struct rcar_du_encoder *renc; +	unsigned int encoder_type; +	int ret; + +	renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL); +	if (renc == NULL) +		return -ENOMEM; + +	renc->output = output; + +	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_NONE: +	default: +		/* No external encoder, use the internal encoder type. */ +		encoder_type = rcdu->info->routes[output].encoder_type; +		break; +	} + +	ret = drm_encoder_init(rcdu->ddev, &renc->encoder, &encoder_funcs, +			       encoder_type); +	if (ret < 0) +		return ret; + +	drm_encoder_helper_add(&renc->encoder, &encoder_helper_funcs); + +	switch (encoder_type) { +	case DRM_MODE_ENCODER_LVDS: +		return rcar_du_lvds_connector_init(rcdu, renc, +						   &data->connector.lvds.panel); + +	case DRM_MODE_ENCODER_DAC: +		return rcar_du_vga_connector_init(rcdu, renc); + +	default: +		return -EINVAL; +	} +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h new file mode 100644 index 00000000000..0e5a65e45d0 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h @@ -0,0 +1,49 @@ +/* + * rcar_du_encoder.h  --  R-Car Display Unit Encoder + * + * Copyright (C) 2013 Renesas 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 <linux/platform_data/rcar-du.h> + +#include <drm/drm_crtc.h> + +struct rcar_du_device; +struct rcar_du_lvdsenc; + +struct rcar_du_encoder { +	struct drm_encoder encoder; +	enum rcar_du_output output; +	struct rcar_du_lvdsenc *lvds; +}; + +#define to_rcar_encoder(e) \ +	container_of(e, struct rcar_du_encoder, encoder) + +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, +			 const struct rcar_du_encoder_data *data); + +#endif /* __RCAR_DU_ENCODER_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_group.c b/drivers/gpu/drm/rcar-du/rcar_du_group.c new file mode 100644 index 00000000000..eb53cd97e8c --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_group.c @@ -0,0 +1,187 @@ +/* + * rcar_du_group.c  --  R-Car Display Unit Channels Pair + * + * Copyright (C) 2013 Renesas 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; + +	if (!rcar_du_has(rgrp->dev, RCAR_DU_FEATURE_DEFR8)) +		return; + +	/* 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); + +	rcar_du_group_setup_defr8(rgrp); + +	/* 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; + +	/* 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/drivers/gpu/drm/rcar-du/rcar_du_group.h b/drivers/gpu/drm/rcar-du/rcar_du_group.h new file mode 100644 index 00000000000..5025930972e --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_group.h @@ -0,0 +1,50 @@ +/* + * rcar_du_group.c  --  R-Car Display Unit Planes and CRTCs Group + * + * Copyright (C) 2013 Renesas 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 "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 + * @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 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/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c new file mode 100644 index 00000000000..a87edfac111 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c @@ -0,0 +1,293 @@ +/* + * rcar_du_kms.c  --  R-Car Display Unit Mode Setting + * + * Copyright (C) 2013 Renesas 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_crtc.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_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(max(args->pitch, min_pitch), align); + +	return drm_gem_cma_dumb_create(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 align; + +	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); +	} + +	if (rcar_du_needs(rcdu, RCAR_DU_QUIRK_ALIGN_128B)) +		align = 128; +	else +		align = 16 * format->bpp / 8; + +	if (mode_cmd->pitches[0] & (align - 1) || +	    mode_cmd->pitches[0] >= 8192) { +		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); +} + +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, +}; + +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_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]; + +		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; + +	for (i = 0; i < rcdu->pdata->num_encoders; ++i) { +		const struct rcar_du_encoder_data *pdata = +			&rcdu->pdata->encoders[i]; +		const struct rcar_du_output_routing *route = +			&rcdu->info->routes[pdata->output]; + +		if (pdata->type == RCAR_DU_ENCODER_UNUSED) +			continue; + +		if (pdata->output >= RCAR_DU_OUTPUT_MAX || +		    route->possible_crtcs == 0) { +			dev_warn(rcdu->dev, +				 "encoder %u references unexisting output %u, skipping\n", +				 i, pdata->output); +			continue; +		} + +		ret = rcar_du_encoder_init(rcdu, pdata->type, pdata->output, +					   pdata); +		if (ret < 0) +			return 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 << rcdu->pdata->num_encoders) - 1; +	} + +	/* Now that the CRTCs have been initialized register the planes. */ +	for (i = 0; i < num_groups; ++i) { +		ret = rcar_du_planes_register(&rcdu->groups[i]); +		if (ret < 0) +			return ret; +	} + +	drm_kms_helper_poll_init(dev); + +	drm_helper_disable_unused_functions(dev); + +	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); + +#ifndef CONFIG_FRAMEBUFFER_CONSOLE +	drm_fbdev_cma_restore_mode(fbdev); +#endif + +	rcdu->fbdev = fbdev; + +	return 0; +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.h b/drivers/gpu/drm/rcar-du/rcar_du_kms.h new file mode 100644 index 00000000000..5750e6af565 --- /dev/null +++ b/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 Renesas 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/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c new file mode 100644 index 00000000000..289048d1c7b --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c @@ -0,0 +1,124 @@ +/* + * rcar_du_lvdscon.c  --  R-Car Display Unit LVDS Connector + * + * Copyright (C) 2013 Renesas 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_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_lvdscon.h" + +struct rcar_du_lvds_connector { +	struct rcar_du_connector connector; + +	const struct rcar_du_panel_data *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; +	mode->clock = lvdscon->panel->mode.clock; +	mode->hdisplay = lvdscon->panel->mode.hdisplay; +	mode->hsync_start = lvdscon->panel->mode.hsync_start; +	mode->hsync_end = lvdscon->panel->mode.hsync_end; +	mode->htotal = lvdscon->panel->mode.htotal; +	mode->vdisplay = lvdscon->panel->mode.vdisplay; +	mode->vsync_start = lvdscon->panel->mode.vsync_start; +	mode->vsync_end = lvdscon->panel->mode.vsync_end; +	mode->vtotal = lvdscon->panel->mode.vtotal; +	mode->flags = lvdscon->panel->mode.flags; + +	drm_mode_set_name(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_sysfs_connector_remove(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_helper_connector_dpms, +	.detect = rcar_du_lvds_connector_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.destroy = rcar_du_lvds_connector_destroy, +}; + +int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, +				struct rcar_du_encoder *renc, +				const struct rcar_du_panel_data *panel) +{ +	struct rcar_du_lvds_connector *lvdscon; +	struct drm_connector *connector; +	int ret; + +	lvdscon = devm_kzalloc(rcdu->dev, sizeof(*lvdscon), GFP_KERNEL); +	if (lvdscon == NULL) +		return -ENOMEM; + +	lvdscon->panel = panel; + +	connector = &lvdscon->connector.connector; +	connector->display_info.width_mm = panel->width_mm; +	connector->display_info.height_mm = 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_sysfs_connector_add(connector); +	if (ret < 0) +		return ret; + +	drm_helper_connector_dpms(connector, 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, &renc->encoder); +	if (ret < 0) +		return ret; + +	connector->encoder = &renc->encoder; +	lvdscon->connector.encoder = renc; + +	return 0; +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h new file mode 100644 index 00000000000..bff8683699c --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h @@ -0,0 +1,25 @@ +/* + * rcar_du_lvdscon.h  --  R-Car Display Unit LVDS Connector + * + * Copyright (C) 2013 Renesas 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; +struct rcar_du_panel_data; + +int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, +				struct rcar_du_encoder *renc, +				const struct rcar_du_panel_data *panel); + +#endif /* __RCAR_DU_LVDSCON_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c new file mode 100644 index 00000000000..df30a075d79 --- /dev/null +++ b/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 Renesas 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; +	int dpms; + +	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->dpms == DRM_MODE_DPMS_ON) +		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->dpms = DRM_MODE_DPMS_ON; +	return 0; +} + +static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds) +{ +	if (lvds->dpms == DRM_MODE_DPMS_OFF) +		return; + +	rcar_lvds_write(lvds, LVDCR0, 0); +	rcar_lvds_write(lvds, LVDCR1, 0); + +	clk_disable_unprepare(lvds->clock); + +	lvds->dpms = DRM_MODE_DPMS_OFF; +} + +int rcar_du_lvdsenc_dpms(struct rcar_du_lvdsenc *lvds, +			 struct drm_crtc *crtc, int mode) +{ +	if (mode == DRM_MODE_DPMS_OFF) { +		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->dpms = DRM_MODE_DPMS_OFF; + +		ret = rcar_du_lvdsenc_get_resources(lvds, pdev); +		if (ret < 0) +			return ret; + +		rcdu->lvds[i] = lvds; +	} + +	return 0; +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h new file mode 100644 index 00000000000..7051c6de19a --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h @@ -0,0 +1,46 @@ +/* + * rcar_du_lvdsenc.h  --  R-Car Display Unit LVDS Encoder + * + * Copyright (C) 2013 Renesas 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> +#include <linux/platform_data/rcar-du.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_dpms(struct rcar_du_lvdsenc *lvds, +			 struct drm_crtc *crtc, int mode); +#else +static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu) +{ +	return 0; +} +static inline int rcar_du_lvdsenc_dpms(struct rcar_du_lvdsenc *lvds, +				       struct drm_crtc *crtc, int mode) +{ +	return 0; +} +#endif + +#endif /* __RCAR_DU_LVDSENC_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_plane.c b/drivers/gpu/drm/rcar-du/rcar_du_plane.c new file mode 100644 index 00000000000..3fb69d9ae61 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_plane.c @@ -0,0 +1,516 @@ +/* + * rcar_du_plane.c  --  R-Car Display Unit Planes + * + * Copyright (C) 2013 Renesas 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_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_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) + +struct rcar_du_kms_plane { +	struct drm_plane plane; +	struct rcar_du_plane *hwplane; +}; + +static inline struct rcar_du_plane *to_rcar_plane(struct drm_plane *plane) +{ +	return container_of(plane, struct rcar_du_kms_plane, plane)->hwplane; +} + +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); +} + +int rcar_du_plane_reserve(struct rcar_du_plane *plane, +			  const struct rcar_du_format_info *format) +{ +	struct rcar_du_group *rgrp = plane->group; +	unsigned int i; +	int ret = -EBUSY; + +	mutex_lock(&rgrp->planes.lock); + +	for (i = 0; i < ARRAY_SIZE(rgrp->planes.planes); ++i) { +		if (!(rgrp->planes.free & (1 << i))) +			continue; + +		if (format->planes == 1 || +		    rgrp->planes.free & (1 << ((i + 1) % 8))) +			break; +	} + +	if (i == ARRAY_SIZE(rgrp->planes.planes)) +		goto done; + +	rgrp->planes.free &= ~(1 << i); +	if (format->planes == 2) +		rgrp->planes.free &= ~(1 << ((i + 1) % 8)); + +	plane->hwindex = i; + +	ret = 0; + +done: +	mutex_unlock(&rgrp->planes.lock); +	return ret; +} + +void rcar_du_plane_release(struct rcar_du_plane *plane) +{ +	struct rcar_du_group *rgrp = plane->group; + +	if (plane->hwindex == -1) +		return; + +	mutex_lock(&rgrp->planes.lock); +	rgrp->planes.free |= 1 << plane->hwindex; +	if (plane->format->planes == 2) +		rgrp->planes.free |= 1 << ((plane->hwindex + 1) % 8); +	mutex_unlock(&rgrp->planes.lock); + +	plane->hwindex = -1; +} + +void rcar_du_plane_update_base(struct rcar_du_plane *plane) +{ +	struct rcar_du_group *rgrp = plane->group; +	unsigned int index = plane->hwindex; +	u32 mwr; + +	/* Memory pitch (expressed in pixels) */ +	if (plane->format->planes == 2) +		mwr = plane->pitch; +	else +		mwr = plane->pitch * 8 / plane->format->bpp; + +	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. +	 * +	 * Similarly, for the second plane, NV12 and NV21 formats seem to +	 * require a halved Y position value. +	 */ +	rcar_du_plane_write(rgrp, index, PnSPXR, plane->src_x); +	rcar_du_plane_write(rgrp, index, PnSPYR, plane->src_y * +			    (plane->format->bpp == 32 ? 2 : 1)); +	rcar_du_plane_write(rgrp, index, PnDSA0R, plane->dma[0]); + +	if (plane->format->planes == 2) { +		index = (index + 1) % 8; + +		rcar_du_plane_write(rgrp, index, PnSPXR, plane->src_x); +		rcar_du_plane_write(rgrp, index, PnSPYR, plane->src_y * +				    (plane->format->bpp == 16 ? 2 : 1) / 2); +		rcar_du_plane_write(rgrp, index, PnDSA0R, plane->dma[1]); +	} +} + +void rcar_du_plane_compute_base(struct rcar_du_plane *plane, +				struct drm_framebuffer *fb) +{ +	struct drm_gem_cma_object *gem; + +	plane->pitch = fb->pitches[0]; + +	gem = drm_fb_cma_get_gem_obj(fb, 0); +	plane->dma[0] = gem->paddr + fb->offsets[0]; + +	if (plane->format->planes == 2) { +		gem = drm_fb_cma_get_gem_obj(fb, 1); +		plane->dma[1] = gem->paddr + fb->offsets[1]; +	} +} + +static void rcar_du_plane_setup_mode(struct rcar_du_plane *plane, +				     unsigned int index) +{ +	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 (plane->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 | plane->alpha); + +	pnmr = PnMR_BM_MD | plane->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 ((plane->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 (plane->format->fourcc == DRM_FORMAT_YUYV) +		pnmr |= PnMR_YCDF_YUYV; + +	rcar_du_plane_write(rgrp, index, PnMR, pnmr); + +	switch (plane->format->fourcc) { +	case DRM_FORMAT_RGB565: +		colorkey = ((plane->colorkey & 0xf80000) >> 8) +			 | ((plane->colorkey & 0x00fc00) >> 5) +			 | ((plane->colorkey & 0x0000f8) >> 3); +		rcar_du_plane_write(rgrp, index, PnTC2R, colorkey); +		break; + +	case DRM_FORMAT_ARGB1555: +	case DRM_FORMAT_XRGB1555: +		colorkey = ((plane->colorkey & 0xf80000) >> 9) +			 | ((plane->colorkey & 0x00f800) >> 6) +			 | ((plane->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 | (plane->colorkey & 0xffffff)); +		break; +	} +} + +static void __rcar_du_plane_setup(struct rcar_du_plane *plane, +				  unsigned int index) +{ +	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 |= plane->format->edf | PnDDCR4_CODE; + +	rcar_du_plane_setup_mode(plane, index); + +	if (plane->format->planes == 2) { +		if (plane->hwindex != index) { +			if (plane->format->fourcc == DRM_FORMAT_NV12 || +			    plane->format->fourcc == DRM_FORMAT_NV21) +				ddcr2 |= PnDDCR2_Y420; + +			if (plane->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->width); +	rcar_du_plane_write(rgrp, index, PnDSYR, plane->height); +	rcar_du_plane_write(rgrp, index, PnDPXR, plane->dst_x); +	rcar_du_plane_write(rgrp, index, PnDPYR, plane->dst_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) +{ +	__rcar_du_plane_setup(plane, plane->hwindex); +	if (plane->format->planes == 2) +		__rcar_du_plane_setup(plane, (plane->hwindex + 1) % 8); + +	rcar_du_plane_update_base(plane); +} + +static int +rcar_du_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, +		       struct drm_framebuffer *fb, int crtc_x, int crtc_y, +		       unsigned int crtc_w, unsigned int crtc_h, +		       uint32_t src_x, uint32_t src_y, +		       uint32_t src_w, uint32_t src_h) +{ +	struct rcar_du_plane *rplane = to_rcar_plane(plane); +	struct rcar_du_device *rcdu = rplane->group->dev; +	const struct rcar_du_format_info *format; +	unsigned int nplanes; +	int ret; + +	format = rcar_du_format_info(fb->pixel_format); +	if (format == NULL) { +		dev_dbg(rcdu->dev, "%s: unsupported format %08x\n", __func__, +			fb->pixel_format); +		return -EINVAL; +	} + +	if (src_w >> 16 != crtc_w || src_h >> 16 != crtc_h) { +		dev_dbg(rcdu->dev, "%s: scaling not supported\n", __func__); +		return -EINVAL; +	} + +	nplanes = rplane->format ? rplane->format->planes : 0; + +	/* Reallocate hardware planes if the number of required planes has +	 * changed. +	 */ +	if (format->planes != nplanes) { +		rcar_du_plane_release(rplane); +		ret = rcar_du_plane_reserve(rplane, format); +		if (ret < 0) +			return ret; +	} + +	rplane->crtc = crtc; +	rplane->format = format; + +	rplane->src_x = src_x >> 16; +	rplane->src_y = src_y >> 16; +	rplane->dst_x = crtc_x; +	rplane->dst_y = crtc_y; +	rplane->width = crtc_w; +	rplane->height = crtc_h; + +	rcar_du_plane_compute_base(rplane, fb); +	rcar_du_plane_setup(rplane); + +	mutex_lock(&rplane->group->planes.lock); +	rplane->enabled = true; +	rcar_du_crtc_update_planes(rplane->crtc); +	mutex_unlock(&rplane->group->planes.lock); + +	return 0; +} + +static int rcar_du_plane_disable(struct drm_plane *plane) +{ +	struct rcar_du_plane *rplane = to_rcar_plane(plane); + +	if (!rplane->enabled) +		return 0; + +	mutex_lock(&rplane->group->planes.lock); +	rplane->enabled = false; +	rcar_du_crtc_update_planes(rplane->crtc); +	mutex_unlock(&rplane->group->planes.lock); + +	rcar_du_plane_release(rplane); + +	rplane->crtc = NULL; +	rplane->format = NULL; + +	return 0; +} + +/* Both the .set_property and the .update_plane operations are called with the + * mode_config lock held. There is this no need to explicitly protect access to + * the alpha and colorkey fields and the mode register. + */ +static void rcar_du_plane_set_alpha(struct rcar_du_plane *plane, u32 alpha) +{ +	if (plane->alpha == alpha) +		return; + +	plane->alpha = alpha; +	if (!plane->enabled || plane->format->fourcc != DRM_FORMAT_XRGB1555) +		return; + +	rcar_du_plane_setup_mode(plane, plane->hwindex); +} + +static void rcar_du_plane_set_colorkey(struct rcar_du_plane *plane, +				       u32 colorkey) +{ +	if (plane->colorkey == colorkey) +		return; + +	plane->colorkey = colorkey; +	if (!plane->enabled) +		return; + +	rcar_du_plane_setup_mode(plane, plane->hwindex); +} + +static void rcar_du_plane_set_zpos(struct rcar_du_plane *plane, +				   unsigned int zpos) +{ +	mutex_lock(&plane->group->planes.lock); +	if (plane->zpos == zpos) +		goto done; + +	plane->zpos = zpos; +	if (!plane->enabled) +		goto done; + +	rcar_du_crtc_update_planes(plane->crtc); + +done: +	mutex_unlock(&plane->group->planes.lock); +} + +static int rcar_du_plane_set_property(struct drm_plane *plane, +				      struct drm_property *property, +				      uint64_t value) +{ +	struct rcar_du_plane *rplane = to_rcar_plane(plane); +	struct rcar_du_group *rgrp = rplane->group; + +	if (property == rgrp->planes.alpha) +		rcar_du_plane_set_alpha(rplane, value); +	else if (property == rgrp->planes.colorkey) +		rcar_du_plane_set_colorkey(rplane, value); +	else if (property == rgrp->planes.zpos) +		rcar_du_plane_set_zpos(rplane, value); +	else +		return -EINVAL; + +	return 0; +} + +static const struct drm_plane_funcs rcar_du_plane_funcs = { +	.update_plane = rcar_du_plane_update, +	.disable_plane = rcar_du_plane_disable, +	.set_property = rcar_du_plane_set_property, +	.destroy = drm_plane_cleanup, +}; + +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 i; + +	mutex_init(&planes->lock); +	planes->free = 0xff; + +	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; + +	for (i = 0; i < ARRAY_SIZE(planes->planes); ++i) { +		struct rcar_du_plane *plane = &planes->planes[i]; + +		plane->group = rgrp; +		plane->hwindex = -1; +		plane->alpha = 255; +		plane->colorkey = RCAR_DU_COLORKEY_NONE; +		plane->zpos = 0; +	} + +	return 0; +} + +int rcar_du_planes_register(struct rcar_du_group *rgrp) +{ +	struct rcar_du_planes *planes = &rgrp->planes; +	struct rcar_du_device *rcdu = rgrp->dev; +	unsigned int crtcs; +	unsigned int i; +	int ret; + +	crtcs = ((1 << rcdu->num_crtcs) - 1) & (3 << (2 * rgrp->index)); + +	for (i = 0; i < RCAR_DU_NUM_KMS_PLANES; ++i) { +		struct rcar_du_kms_plane *plane; + +		plane = devm_kzalloc(rcdu->dev, sizeof(*plane), GFP_KERNEL); +		if (plane == NULL) +			return -ENOMEM; + +		plane->hwplane = &planes->planes[i + 2]; +		plane->hwplane->zpos = 1; + +		ret = drm_plane_init(rcdu->ddev, &plane->plane, crtcs, +				     &rcar_du_plane_funcs, formats, +				     ARRAY_SIZE(formats), false); +		if (ret < 0) +			return ret; + +		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/drivers/gpu/drm/rcar-du/rcar_du_plane.h b/drivers/gpu/drm/rcar-du/rcar_du_plane.h new file mode 100644 index 00000000000..f94f9ce8499 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_plane.h @@ -0,0 +1,81 @@ +/* + * rcar_du_plane.h  --  R-Car Display Unit Planes + * + * Copyright (C) 2013 Renesas 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 <linux/mutex.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 KMS planes and CRTCs. As + * using KMS planes requires at least one of the CRTCs being enabled, no more + * than 7 KMS planes can be available. We thus create 7 KMS planes and + * 9 software planes (one for each KMS planes and one for each CRTC). + */ + +#define RCAR_DU_NUM_KMS_PLANES		7 +#define RCAR_DU_NUM_HW_PLANES		8 +#define RCAR_DU_NUM_SW_PLANES		9 + +struct rcar_du_plane { +	struct rcar_du_group *group; +	struct drm_crtc *crtc; + +	bool enabled; + +	int hwindex;		/* 0-based, -1 means unused */ +	unsigned int alpha; +	unsigned int colorkey; +	unsigned int zpos; + +	const struct rcar_du_format_info *format; + +	unsigned long dma[2]; +	unsigned int pitch; + +	unsigned int width; +	unsigned int height; + +	unsigned int src_x; +	unsigned int src_y; +	unsigned int dst_x; +	unsigned int dst_y; +}; + +struct rcar_du_planes { +	struct rcar_du_plane planes[RCAR_DU_NUM_SW_PLANES]; +	unsigned int free; +	struct mutex lock; + +	struct drm_property *alpha; +	struct drm_property *colorkey; +	struct drm_property *zpos; +}; + +int rcar_du_planes_init(struct rcar_du_group *rgrp); +int rcar_du_planes_register(struct rcar_du_group *rgrp); + +void rcar_du_plane_setup(struct rcar_du_plane *plane); +void rcar_du_plane_update_base(struct rcar_du_plane *plane); +void rcar_du_plane_compute_base(struct rcar_du_plane *plane, +				struct drm_framebuffer *fb); +int rcar_du_plane_reserve(struct rcar_du_plane *plane, +			  const struct rcar_du_format_info *format); +void rcar_du_plane_release(struct rcar_du_plane *plane); + +#endif /* __RCAR_DU_PLANE_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_regs.h b/drivers/gpu/drm/rcar-du/rcar_du_regs.h new file mode 100644 index 00000000000..73f7347f740 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_regs.h @@ -0,0 +1,513 @@ +/* + * 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 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_PCDS_CLK(n, clk)	(clk << ((n) * 2)) +#define DIDSR_PCDS_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/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c b/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c new file mode 100644 index 00000000000..ccfe64c7188 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c @@ -0,0 +1,89 @@ +/* + * rcar_du_vgacon.c  --  R-Car Display Unit VGA Connector + * + * Copyright (C) 2013 Renesas 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_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_sysfs_connector_remove(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_helper_connector_dpms, +	.detect = rcar_du_vga_connector_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.destroy = rcar_du_vga_connector_destroy, +}; + +int rcar_du_vga_connector_init(struct rcar_du_device *rcdu, +			       struct rcar_du_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; + +	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_sysfs_connector_add(connector); +	if (ret < 0) +		return ret; + +	drm_helper_connector_dpms(connector, 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, &renc->encoder); +	if (ret < 0) +		return ret; + +	connector->encoder = &renc->encoder; +	rcon->encoder = renc; + +	return 0; +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h b/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h new file mode 100644 index 00000000000..b12b0cf7f11 --- /dev/null +++ b/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 Renesas 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/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h b/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h new file mode 100644 index 00000000000..77cf9289ab6 --- /dev/null +++ b/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__ */  | 
