diff options
Diffstat (limited to 'drivers/gpu/drm/gma500')
70 files changed, 31251 insertions, 0 deletions
diff --git a/drivers/gpu/drm/gma500/Kconfig b/drivers/gpu/drm/gma500/Kconfig new file mode 100644 index 00000000000..17f928ec84e --- /dev/null +++ b/drivers/gpu/drm/gma500/Kconfig @@ -0,0 +1,38 @@ +config DRM_GMA500 +	tristate "Intel GMA5/600 KMS Framebuffer" +	depends on DRM && PCI && X86 +	select FB_CFB_COPYAREA +	select FB_CFB_FILLRECT +	select FB_CFB_IMAGEBLIT +	select DRM_KMS_HELPER +	select DRM_KMS_FB_HELPER +	select DRM_TTM +	# GMA500 depends on ACPI_VIDEO when ACPI is enabled, just like i915 +	select ACPI_VIDEO if ACPI +	select BACKLIGHT_CLASS_DEVICE if ACPI +	select INPUT if ACPI +	help +	  Say yes for an experimental 2D KMS framebuffer driver for the +	  Intel GMA500 ('Poulsbo') and other Intel IMG based graphics +	  devices. + +config DRM_GMA600 +	bool "Intel GMA600 support (Experimental)" +	depends on DRM_GMA500 +	help +	  Say yes to include support for GMA600 (Intel Moorestown/Oaktrail) +	  platforms with LVDS ports. MIPI is not currently supported. + +config DRM_GMA3600 +	bool "Intel GMA3600/3650 support (Experimental)" +	depends on DRM_GMA500 +	help +	  Say yes to include basic support for Intel GMA3600/3650 (Intel +	  Cedar Trail) platforms. + +config DRM_MEDFIELD +	bool "Intel Medfield support (Experimental)" +	depends on DRM_GMA500 && X86_INTEL_MID +	help +	  Say yes to include support for the Intel Medfield platform. + diff --git a/drivers/gpu/drm/gma500/Makefile b/drivers/gpu/drm/gma500/Makefile new file mode 100644 index 00000000000..b1531557637 --- /dev/null +++ b/drivers/gpu/drm/gma500/Makefile @@ -0,0 +1,55 @@ +# +#	KMS driver for the GMA500 +# +ccflags-y += -I$(srctree)/include/drm + +gma500_gfx-y += \ +	  accel_2d.o \ +	  backlight.o \ +	  framebuffer.o \ +	  gem.o \ +	  gtt.o \ +	  intel_bios.o \ +	  intel_i2c.o \ +	  intel_gmbus.o \ +	  mmu.o \ +	  blitter.o \ +	  power.o \ +	  psb_drv.o \ +	  gma_display.o \ +	  gma_device.o \ +	  psb_intel_display.o \ +	  psb_intel_lvds.o \ +	  psb_intel_modes.o \ +	  psb_intel_sdvo.o \ +	  psb_lid.o \ +	  psb_irq.o \ +	  psb_device.o \ +	  mid_bios.o + +gma500_gfx-$(CONFIG_ACPI) +=  opregion.o \ + +gma500_gfx-$(CONFIG_DRM_GMA3600) +=  cdv_device.o \ +	  cdv_intel_crt.o \ +	  cdv_intel_display.o \ +	  cdv_intel_hdmi.o \ +	  cdv_intel_lvds.o \ +	  cdv_intel_dp.o + +gma500_gfx-$(CONFIG_DRM_GMA600) += oaktrail_device.o \ +	  oaktrail_crtc.o \ +	  oaktrail_lvds.o \ +	  oaktrail_hdmi.o \ +	  oaktrail_hdmi_i2c.o + +gma500_gfx-$(CONFIG_DRM_MEDFIELD) += mdfld_device.o \ +	  mdfld_output.o \ +	  mdfld_intel_display.o \ +	  mdfld_dsi_output.o \ +	  mdfld_dsi_dpi.o \ +	  mdfld_dsi_pkg_sender.o \ +	  mdfld_tpo_vid.o \ +	  mdfld_tmd_vid.o \ +	  tc35876x-dsi-lvds.o + +obj-$(CONFIG_DRM_GMA500) += gma500_gfx.o diff --git a/drivers/gpu/drm/gma500/accel_2d.c b/drivers/gpu/drm/gma500/accel_2d.c new file mode 100644 index 00000000000..de6f62a6ceb --- /dev/null +++ b/drivers/gpu/drm/gma500/accel_2d.c @@ -0,0 +1,364 @@ +/************************************************************************** + * Copyright (c) 2007-2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to + * develop this driver. + * + **************************************************************************/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/console.h> + +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/drm_crtc.h> + +#include "psb_drv.h" +#include "psb_reg.h" +#include "framebuffer.h" + +/** + *	psb_spank		-	reset the 2D engine + *	@dev_priv: our PSB DRM device + * + *	Soft reset the graphics engine and then reload the necessary registers. + *	We use this at initialisation time but it will become relevant for + *	accelerated X later + */ +void psb_spank(struct drm_psb_private *dev_priv) +{ +	PSB_WSGX32(_PSB_CS_RESET_BIF_RESET | _PSB_CS_RESET_DPM_RESET | +		_PSB_CS_RESET_TA_RESET | _PSB_CS_RESET_USE_RESET | +		_PSB_CS_RESET_ISP_RESET | _PSB_CS_RESET_TSP_RESET | +		_PSB_CS_RESET_TWOD_RESET, PSB_CR_SOFT_RESET); +	PSB_RSGX32(PSB_CR_SOFT_RESET); + +	msleep(1); + +	PSB_WSGX32(0, PSB_CR_SOFT_RESET); +	wmb(); +	PSB_WSGX32(PSB_RSGX32(PSB_CR_BIF_CTRL) | _PSB_CB_CTRL_CLEAR_FAULT, +		   PSB_CR_BIF_CTRL); +	wmb(); +	(void) PSB_RSGX32(PSB_CR_BIF_CTRL); + +	msleep(1); +	PSB_WSGX32(PSB_RSGX32(PSB_CR_BIF_CTRL) & ~_PSB_CB_CTRL_CLEAR_FAULT, +		   PSB_CR_BIF_CTRL); +	(void) PSB_RSGX32(PSB_CR_BIF_CTRL); +	PSB_WSGX32(dev_priv->gtt.gatt_start, PSB_CR_BIF_TWOD_REQ_BASE); +} + +/** + *	psb2_2d_wait_available	-	wait for FIFO room + *	@dev_priv: our DRM device + *	@size: size (in dwords) of the command we want to issue + * + *	Wait until there is room to load the FIFO with our data. If the + *	device is not responding then reset it + */ +static int psb_2d_wait_available(struct drm_psb_private *dev_priv, +			  unsigned size) +{ +	uint32_t avail = PSB_RSGX32(PSB_CR_2D_SOCIF); +	unsigned long t = jiffies + HZ; + +	while (avail < size) { +		avail = PSB_RSGX32(PSB_CR_2D_SOCIF); +		if (time_after(jiffies, t)) { +			psb_spank(dev_priv); +			return -EIO; +		} +	} +	return 0; +} + +/** + *	psb_2d_submit		-	submit a 2D command + *	@dev_priv: our DRM device + *	@cmdbuf: command to issue + *	@size: length (in dwords) + * + *	Issue one or more 2D commands to the accelerator. This needs to be + *	serialized later when we add the GEM interfaces for acceleration + */ +static int psbfb_2d_submit(struct drm_psb_private *dev_priv, uint32_t *cmdbuf, +								unsigned size) +{ +	int ret = 0; +	int i; +	unsigned submit_size; +	unsigned long flags; + +	spin_lock_irqsave(&dev_priv->lock_2d, flags); +	while (size > 0) { +		submit_size = (size < 0x60) ? size : 0x60; +		size -= submit_size; +		ret = psb_2d_wait_available(dev_priv, submit_size); +		if (ret) +			break; + +		submit_size <<= 2; + +		for (i = 0; i < submit_size; i += 4) +			PSB_WSGX32(*cmdbuf++, PSB_SGX_2D_SLAVE_PORT + i); + +		(void)PSB_RSGX32(PSB_SGX_2D_SLAVE_PORT + i - 4); +	} +	spin_unlock_irqrestore(&dev_priv->lock_2d, flags); +	return ret; +} + + +/** + *	psb_accel_2d_copy_direction	-	compute blit order + *	@xdir: X direction of move + *	@ydir: Y direction of move + * + *	Compute the correct order setings to ensure that an overlapping blit + *	correctly copies all the pixels. + */ +static u32 psb_accel_2d_copy_direction(int xdir, int ydir) +{ +	if (xdir < 0) +		return (ydir < 0) ? PSB_2D_COPYORDER_BR2TL : +						PSB_2D_COPYORDER_TR2BL; +	else +		return (ydir < 0) ? PSB_2D_COPYORDER_BL2TR : +						PSB_2D_COPYORDER_TL2BR; +} + +/** + *	psb_accel_2d_copy		-	accelerated 2D copy + *	@dev_priv: our DRM device + *	@src_offset in bytes + *	@src_stride in bytes + *	@src_format psb 2D format defines + *	@dst_offset in bytes + *	@dst_stride in bytes + *	@dst_format psb 2D format defines + *	@src_x offset in pixels + *	@src_y offset in pixels + *	@dst_x offset in pixels + *	@dst_y offset in pixels + *	@size_x of the copied area + *	@size_y of the copied area + * + *	Format and issue a 2D accelerated copy command. + */ +static int psb_accel_2d_copy(struct drm_psb_private *dev_priv, +			     uint32_t src_offset, uint32_t src_stride, +			     uint32_t src_format, uint32_t dst_offset, +			     uint32_t dst_stride, uint32_t dst_format, +			     uint16_t src_x, uint16_t src_y, +			     uint16_t dst_x, uint16_t dst_y, +			     uint16_t size_x, uint16_t size_y) +{ +	uint32_t blit_cmd; +	uint32_t buffer[10]; +	uint32_t *buf; +	uint32_t direction; + +	buf = buffer; + +	direction = +	    psb_accel_2d_copy_direction(src_x - dst_x, src_y - dst_y); + +	if (direction == PSB_2D_COPYORDER_BR2TL || +	    direction == PSB_2D_COPYORDER_TR2BL) { +		src_x += size_x - 1; +		dst_x += size_x - 1; +	} +	if (direction == PSB_2D_COPYORDER_BR2TL || +	    direction == PSB_2D_COPYORDER_BL2TR) { +		src_y += size_y - 1; +		dst_y += size_y - 1; +	} + +	blit_cmd = +	    PSB_2D_BLIT_BH | +	    PSB_2D_ROT_NONE | +	    PSB_2D_DSTCK_DISABLE | +	    PSB_2D_SRCCK_DISABLE | +	    PSB_2D_USE_PAT | PSB_2D_ROP3_SRCCOPY | direction; + +	*buf++ = PSB_2D_FENCE_BH; +	*buf++ = +	    PSB_2D_DST_SURF_BH | dst_format | (dst_stride << +					       PSB_2D_DST_STRIDE_SHIFT); +	*buf++ = dst_offset; +	*buf++ = +	    PSB_2D_SRC_SURF_BH | src_format | (src_stride << +					       PSB_2D_SRC_STRIDE_SHIFT); +	*buf++ = src_offset; +	*buf++ = +	    PSB_2D_SRC_OFF_BH | (src_x << PSB_2D_SRCOFF_XSTART_SHIFT) | +	    (src_y << PSB_2D_SRCOFF_YSTART_SHIFT); +	*buf++ = blit_cmd; +	*buf++ = +	    (dst_x << PSB_2D_DST_XSTART_SHIFT) | (dst_y << +						  PSB_2D_DST_YSTART_SHIFT); +	*buf++ = +	    (size_x << PSB_2D_DST_XSIZE_SHIFT) | (size_y << +						  PSB_2D_DST_YSIZE_SHIFT); +	*buf++ = PSB_2D_FLUSH_BH; + +	return psbfb_2d_submit(dev_priv, buffer, buf - buffer); +} + +/** + *	psbfb_copyarea_accel	-	copyarea acceleration for /dev/fb + *	@info: our framebuffer + *	@a: copyarea parameters from the framebuffer core + * + *	Perform a 2D copy via the accelerator + */ +static void psbfb_copyarea_accel(struct fb_info *info, +				 const struct fb_copyarea *a) +{ +	struct psb_fbdev *fbdev = info->par; +	struct psb_framebuffer *psbfb = &fbdev->pfb; +	struct drm_device *dev = psbfb->base.dev; +	struct drm_framebuffer *fb = fbdev->psb_fb_helper.fb; +	struct drm_psb_private *dev_priv = dev->dev_private; +	uint32_t offset; +	uint32_t stride; +	uint32_t src_format; +	uint32_t dst_format; + +	if (!fb) +		return; + +	offset = psbfb->gtt->offset; +	stride = fb->pitches[0]; + +	switch (fb->depth) { +	case 8: +		src_format = PSB_2D_SRC_332RGB; +		dst_format = PSB_2D_DST_332RGB; +		break; +	case 15: +		src_format = PSB_2D_SRC_555RGB; +		dst_format = PSB_2D_DST_555RGB; +		break; +	case 16: +		src_format = PSB_2D_SRC_565RGB; +		dst_format = PSB_2D_DST_565RGB; +		break; +	case 24: +	case 32: +		/* this is wrong but since we don't do blending its okay */ +		src_format = PSB_2D_SRC_8888ARGB; +		dst_format = PSB_2D_DST_8888ARGB; +		break; +	default: +		/* software fallback */ +		cfb_copyarea(info, a); +		return; +	} + +	if (!gma_power_begin(dev, false)) { +		cfb_copyarea(info, a); +		return; +	} +	psb_accel_2d_copy(dev_priv, +			  offset, stride, src_format, +			  offset, stride, dst_format, +			  a->sx, a->sy, a->dx, a->dy, a->width, a->height); +	gma_power_end(dev); +} + +/** + *	psbfb_copyarea	-	2D copy interface + *	@info: our framebuffer + *	@region: region to copy + * + *	Copy an area of the framebuffer console either by the accelerator + *	or directly using the cfb helpers according to the request + */ +void psbfb_copyarea(struct fb_info *info, +			   const struct fb_copyarea *region) +{ +	if (unlikely(info->state != FBINFO_STATE_RUNNING)) +		return; + +	/* Avoid the 8 pixel erratum */ +	if (region->width == 8 || region->height == 8 || +		(info->flags & FBINFO_HWACCEL_DISABLED)) +		return cfb_copyarea(info, region); + +	psbfb_copyarea_accel(info, region); +} + +/** + *	psbfb_sync	-	synchronize 2D + *	@info: our framebuffer + * + *	Wait for the 2D engine to quiesce so that we can do CPU + *	access to the framebuffer again + */ +int psbfb_sync(struct fb_info *info) +{ +	struct psb_fbdev *fbdev = info->par; +	struct psb_framebuffer *psbfb = &fbdev->pfb; +	struct drm_device *dev = psbfb->base.dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	unsigned long _end = jiffies + HZ; +	int busy = 0; +	unsigned long flags; + +	spin_lock_irqsave(&dev_priv->lock_2d, flags); +	/* +	 * First idle the 2D engine. +	 */ + +	if ((PSB_RSGX32(PSB_CR_2D_SOCIF) == _PSB_C2_SOCIF_EMPTY) && +	    ((PSB_RSGX32(PSB_CR_2D_BLIT_STATUS) & _PSB_C2B_STATUS_BUSY) == 0)) +		goto out; + +	do { +		busy = (PSB_RSGX32(PSB_CR_2D_SOCIF) != _PSB_C2_SOCIF_EMPTY); +		cpu_relax(); +	} while (busy && !time_after_eq(jiffies, _end)); + +	if (busy) +		busy = (PSB_RSGX32(PSB_CR_2D_SOCIF) != _PSB_C2_SOCIF_EMPTY); +	if (busy) +		goto out; + +	do { +		busy = ((PSB_RSGX32(PSB_CR_2D_BLIT_STATUS) & +						_PSB_C2B_STATUS_BUSY) != 0); +		cpu_relax(); +	} while (busy && !time_after_eq(jiffies, _end)); +	if (busy) +		busy = ((PSB_RSGX32(PSB_CR_2D_BLIT_STATUS) & +					_PSB_C2B_STATUS_BUSY) != 0); + +out: +	spin_unlock_irqrestore(&dev_priv->lock_2d, flags); +	return (busy) ? -EBUSY : 0; +} diff --git a/drivers/gpu/drm/gma500/backlight.c b/drivers/gpu/drm/gma500/backlight.c new file mode 100644 index 00000000000..ea7dfc59d79 --- /dev/null +++ b/drivers/gpu/drm/gma500/backlight.c @@ -0,0 +1,94 @@ +/* + * GMA500 Backlight Interface + * + * Copyright (c) 2009-2011, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: Eric Knopp + * + */ + +#include "psb_drv.h" +#include "psb_intel_reg.h" +#include "psb_intel_drv.h" +#include "intel_bios.h" +#include "power.h" + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +static void do_gma_backlight_set(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	backlight_update_status(dev_priv->backlight_device); +} +#endif + +void gma_backlight_enable(struct drm_device *dev) +{ +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	struct drm_psb_private *dev_priv = dev->dev_private; +	dev_priv->backlight_enabled = true; +	if (dev_priv->backlight_device) { +		dev_priv->backlight_device->props.brightness = dev_priv->backlight_level; +		do_gma_backlight_set(dev); +	} +#endif	 +} + +void gma_backlight_disable(struct drm_device *dev) +{ +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	struct drm_psb_private *dev_priv = dev->dev_private; +	dev_priv->backlight_enabled = false; +	if (dev_priv->backlight_device) { +		dev_priv->backlight_device->props.brightness = 0; +		do_gma_backlight_set(dev); +	} +#endif	 +} + +void gma_backlight_set(struct drm_device *dev, int v) +{ +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	struct drm_psb_private *dev_priv = dev->dev_private; +	dev_priv->backlight_level = v; +	if (dev_priv->backlight_device && dev_priv->backlight_enabled) { +		dev_priv->backlight_device->props.brightness = v; +		do_gma_backlight_set(dev); +	} +#endif	 +} + +int gma_backlight_init(struct drm_device *dev) +{ +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	struct drm_psb_private *dev_priv = dev->dev_private; +	dev_priv->backlight_enabled = true; +	return dev_priv->ops->backlight_init(dev); +#else +	return 0; +#endif +} + +void gma_backlight_exit(struct drm_device *dev) +{ +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	struct drm_psb_private *dev_priv = dev->dev_private; +	if (dev_priv->backlight_device) { +		dev_priv->backlight_device->props.brightness = 0; +		backlight_update_status(dev_priv->backlight_device); +		backlight_device_unregister(dev_priv->backlight_device); +	} +#endif +} diff --git a/drivers/gpu/drm/gma500/blitter.c b/drivers/gpu/drm/gma500/blitter.c new file mode 100644 index 00000000000..9cd54a6fb89 --- /dev/null +++ b/drivers/gpu/drm/gma500/blitter.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014, Patrik Jakobsson + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * Authors: Patrik Jakobsson <patrik.r.jakobsson@gmail.com> + */ + +#include "psb_drv.h" + +#include "blitter.h" +#include "psb_reg.h" + +/* Wait for the blitter to be completely idle */ +int gma_blt_wait_idle(struct drm_psb_private *dev_priv) +{ +	unsigned long stop = jiffies + HZ; +	int busy = 1; + +	/* NOP for Cedarview */ +	if (IS_CDV(dev_priv->dev)) +		return 0; + +	/* First do a quick check */ +	if ((PSB_RSGX32(PSB_CR_2D_SOCIF) == _PSB_C2_SOCIF_EMPTY) && +	    ((PSB_RSGX32(PSB_CR_2D_BLIT_STATUS) & _PSB_C2B_STATUS_BUSY) == 0)) +		return 0; + +	do { +		busy = (PSB_RSGX32(PSB_CR_2D_SOCIF) != _PSB_C2_SOCIF_EMPTY); +	} while (busy && !time_after_eq(jiffies, stop)); + +	if (busy) +		return -EBUSY; + +	do { +		busy = ((PSB_RSGX32(PSB_CR_2D_BLIT_STATUS) & +			_PSB_C2B_STATUS_BUSY) != 0); +	} while (busy && !time_after_eq(jiffies, stop)); + +	/* If still busy, we probably have a hang */ +	return (busy) ? -EBUSY : 0; +} diff --git a/drivers/gpu/drm/gma500/blitter.h b/drivers/gpu/drm/gma500/blitter.h new file mode 100644 index 00000000000..b83648df590 --- /dev/null +++ b/drivers/gpu/drm/gma500/blitter.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014, Patrik Jakobsson + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * Authors: Patrik Jakobsson <patrik.r.jakobsson@gmail.com> + */ + +#ifndef __BLITTER_H +#define __BLITTER_H + +extern int gma_blt_wait_idle(struct drm_psb_private *dev_priv); + +#endif diff --git a/drivers/gpu/drm/gma500/cdv_device.c b/drivers/gpu/drm/gma500/cdv_device.c new file mode 100644 index 00000000000..3531f90e53d --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_device.c @@ -0,0 +1,626 @@ +/************************************************************************** + * Copyright (c) 2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +#include <linux/backlight.h> +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/gma_drm.h> +#include "psb_drv.h" +#include "psb_reg.h" +#include "psb_intel_reg.h" +#include "intel_bios.h" +#include "cdv_device.h" +#include "gma_device.h" + +#define VGA_SR_INDEX		0x3c4 +#define VGA_SR_DATA		0x3c5 + +static void cdv_disable_vga(struct drm_device *dev) +{ +	u8 sr1; +	u32 vga_reg; + +	vga_reg = VGACNTRL; + +	outb(1, VGA_SR_INDEX); +	sr1 = inb(VGA_SR_DATA); +	outb(sr1 | 1<<5, VGA_SR_DATA); +	udelay(300); + +	REG_WRITE(vga_reg, VGA_DISP_DISABLE); +	REG_READ(vga_reg); +} + +static int cdv_output_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	drm_mode_create_scaling_mode_property(dev); + +	cdv_disable_vga(dev); + +	cdv_intel_crt_init(dev, &dev_priv->mode_dev); +	cdv_intel_lvds_init(dev, &dev_priv->mode_dev); + +	/* These bits indicate HDMI not SDVO on CDV */ +	if (REG_READ(SDVOB) & SDVO_DETECTED) { +		cdv_hdmi_init(dev, &dev_priv->mode_dev, SDVOB); +		if (REG_READ(DP_B) & DP_DETECTED) +			cdv_intel_dp_init(dev, &dev_priv->mode_dev, DP_B); +	} + +	if (REG_READ(SDVOC) & SDVO_DETECTED) { +		cdv_hdmi_init(dev, &dev_priv->mode_dev, SDVOC); +		if (REG_READ(DP_C) & DP_DETECTED) +			cdv_intel_dp_init(dev, &dev_priv->mode_dev, DP_C); +	} +	return 0; +} + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE + +/* + *	Cedartrail Backlght Interfaces + */ + +static struct backlight_device *cdv_backlight_device; + +static int cdv_backlight_combination_mode(struct drm_device *dev) +{ +	return REG_READ(BLC_PWM_CTL2) & PWM_LEGACY_MODE; +} + +static u32 cdv_get_max_backlight(struct drm_device *dev) +{ +	u32 max = REG_READ(BLC_PWM_CTL); + +	if (max == 0) { +		DRM_DEBUG_KMS("LVDS Panel PWM value is 0!\n"); +		/* i915 does this, I believe which means that we should not +		 * smash PWM control as firmware will take control of it. */ +		return 1; +	} + +	max >>= 16; +	if (cdv_backlight_combination_mode(dev)) +		max *= 0xff; +	return max; +} + +static int cdv_get_brightness(struct backlight_device *bd) +{ +	struct drm_device *dev = bl_get_data(bd); +	u32 val = REG_READ(BLC_PWM_CTL) & BACKLIGHT_DUTY_CYCLE_MASK; + +	if (cdv_backlight_combination_mode(dev)) { +		u8 lbpc; + +		val &= ~1; +		pci_read_config_byte(dev->pdev, 0xF4, &lbpc); +		val *= lbpc; +	} +	return (val * 100)/cdv_get_max_backlight(dev); + +} + +static int cdv_set_brightness(struct backlight_device *bd) +{ +	struct drm_device *dev = bl_get_data(bd); +	int level = bd->props.brightness; +	u32 blc_pwm_ctl; + +	/* Percentage 1-100% being valid */ +	if (level < 1) +		level = 1; + +	level *= cdv_get_max_backlight(dev); +	level /= 100; + +	if (cdv_backlight_combination_mode(dev)) { +		u32 max = cdv_get_max_backlight(dev); +		u8 lbpc; + +		lbpc = level * 0xfe / max + 1; +		level /= lbpc; + +		pci_write_config_byte(dev->pdev, 0xF4, lbpc); +	} + +	blc_pwm_ctl = REG_READ(BLC_PWM_CTL) & ~BACKLIGHT_DUTY_CYCLE_MASK; +	REG_WRITE(BLC_PWM_CTL, (blc_pwm_ctl | +				(level << BACKLIGHT_DUTY_CYCLE_SHIFT))); +	return 0; +} + +static const struct backlight_ops cdv_ops = { +	.get_brightness = cdv_get_brightness, +	.update_status  = cdv_set_brightness, +}; + +static int cdv_backlight_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct backlight_properties props; + +	memset(&props, 0, sizeof(struct backlight_properties)); +	props.max_brightness = 100; +	props.type = BACKLIGHT_PLATFORM; + +	cdv_backlight_device = backlight_device_register("psb-bl", +					NULL, (void *)dev, &cdv_ops, &props); +	if (IS_ERR(cdv_backlight_device)) +		return PTR_ERR(cdv_backlight_device); + +	cdv_backlight_device->props.brightness = +			cdv_get_brightness(cdv_backlight_device); +	backlight_update_status(cdv_backlight_device); +	dev_priv->backlight_device = cdv_backlight_device; +	dev_priv->backlight_enabled = true; +	return 0; +} + +#endif + +/* + *	Provide the Cedarview specific chip logic and low level methods + *	for power management + * + *	FIXME: we need to implement the apm/ospm base management bits + *	for this and the MID devices. + */ + +static inline u32 CDV_MSG_READ32(uint port, uint offset) +{ +	int mcr = (0x10<<24) | (port << 16) | (offset << 8); +	uint32_t ret_val = 0; +	struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); +	pci_write_config_dword(pci_root, 0xD0, mcr); +	pci_read_config_dword(pci_root, 0xD4, &ret_val); +	pci_dev_put(pci_root); +	return ret_val; +} + +static inline void CDV_MSG_WRITE32(uint port, uint offset, u32 value) +{ +	int mcr = (0x11<<24) | (port << 16) | (offset << 8) | 0xF0; +	struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); +	pci_write_config_dword(pci_root, 0xD4, value); +	pci_write_config_dword(pci_root, 0xD0, mcr); +	pci_dev_put(pci_root); +} + +#define PSB_PM_SSC			0x20 +#define PSB_PM_SSS			0x30 +#define PSB_PWRGT_GFX_ON		0x02 +#define PSB_PWRGT_GFX_OFF		0x01 +#define PSB_PWRGT_GFX_D0		0x00 +#define PSB_PWRGT_GFX_D3		0x03 + +static void cdv_init_pm(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 pwr_cnt; +	int i; + +	dev_priv->apm_base = CDV_MSG_READ32(PSB_PUNIT_PORT, +							PSB_APMBA) & 0xFFFF; +	dev_priv->ospm_base = CDV_MSG_READ32(PSB_PUNIT_PORT, +							PSB_OSPMBA) & 0xFFFF; + +	/* Power status */ +	pwr_cnt = inl(dev_priv->apm_base + PSB_APM_CMD); + +	/* Enable the GPU */ +	pwr_cnt &= ~PSB_PWRGT_GFX_MASK; +	pwr_cnt |= PSB_PWRGT_GFX_ON; +	outl(pwr_cnt, dev_priv->apm_base + PSB_APM_CMD); + +	/* Wait for the GPU power */ +	for (i = 0; i < 5; i++) { +		u32 pwr_sts = inl(dev_priv->apm_base + PSB_APM_STS); +		if ((pwr_sts & PSB_PWRGT_GFX_MASK) == 0) +			return; +		udelay(10); +	} +	dev_err(dev->dev, "GPU: power management timed out.\n"); +} + +static void cdv_errata(struct drm_device *dev) +{ +	/* Disable bonus launch. +	 *	CPU and GPU competes for memory and display misses updates and +	 *	flickers. Worst with dual core, dual displays. +	 * +	 *	Fixes were done to Win 7 gfx driver to disable a feature called +	 *	Bonus Launch to work around the issue, by degrading +	 *	performance. +	 */ +	 CDV_MSG_WRITE32(3, 0x30, 0x08027108); +} + +/** + *	cdv_save_display_registers	-	save registers lost on suspend + *	@dev: our DRM device + * + *	Save the state we need in order to be able to restore the interface + *	upon resume from suspend + */ +static int cdv_save_display_registers(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_save_area *regs = &dev_priv->regs; +	struct drm_connector *connector; + +	dev_dbg(dev->dev, "Saving GPU registers.\n"); + +	pci_read_config_byte(dev->pdev, 0xF4, ®s->cdv.saveLBB); + +	regs->cdv.saveDSPCLK_GATE_D = REG_READ(DSPCLK_GATE_D); +	regs->cdv.saveRAMCLK_GATE_D = REG_READ(RAMCLK_GATE_D); + +	regs->cdv.saveDSPARB = REG_READ(DSPARB); +	regs->cdv.saveDSPFW[0] = REG_READ(DSPFW1); +	regs->cdv.saveDSPFW[1] = REG_READ(DSPFW2); +	regs->cdv.saveDSPFW[2] = REG_READ(DSPFW3); +	regs->cdv.saveDSPFW[3] = REG_READ(DSPFW4); +	regs->cdv.saveDSPFW[4] = REG_READ(DSPFW5); +	regs->cdv.saveDSPFW[5] = REG_READ(DSPFW6); + +	regs->cdv.saveADPA = REG_READ(ADPA); + +	regs->cdv.savePP_CONTROL = REG_READ(PP_CONTROL); +	regs->cdv.savePFIT_PGM_RATIOS = REG_READ(PFIT_PGM_RATIOS); +	regs->saveBLC_PWM_CTL = REG_READ(BLC_PWM_CTL); +	regs->saveBLC_PWM_CTL2 = REG_READ(BLC_PWM_CTL2); +	regs->cdv.saveLVDS = REG_READ(LVDS); + +	regs->cdv.savePFIT_CONTROL = REG_READ(PFIT_CONTROL); + +	regs->cdv.savePP_ON_DELAYS = REG_READ(PP_ON_DELAYS); +	regs->cdv.savePP_OFF_DELAYS = REG_READ(PP_OFF_DELAYS); +	regs->cdv.savePP_CYCLE = REG_READ(PP_CYCLE); + +	regs->cdv.saveVGACNTRL = REG_READ(VGACNTRL); + +	regs->cdv.saveIER = REG_READ(PSB_INT_ENABLE_R); +	regs->cdv.saveIMR = REG_READ(PSB_INT_MASK_R); + +	list_for_each_entry(connector, &dev->mode_config.connector_list, head) +		connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF); + +	return 0; +} + +/** + *	cdv_restore_display_registers	-	restore lost register state + *	@dev: our DRM device + * + *	Restore register state that was lost during suspend and resume. + * + *	FIXME: review + */ +static int cdv_restore_display_registers(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_save_area *regs = &dev_priv->regs; +	struct drm_connector *connector; +	u32 temp; + +	pci_write_config_byte(dev->pdev, 0xF4, regs->cdv.saveLBB); + +	REG_WRITE(DSPCLK_GATE_D, regs->cdv.saveDSPCLK_GATE_D); +	REG_WRITE(RAMCLK_GATE_D, regs->cdv.saveRAMCLK_GATE_D); + +	/* BIOS does below anyway */ +	REG_WRITE(DPIO_CFG, 0); +	REG_WRITE(DPIO_CFG, DPIO_MODE_SELECT_0 | DPIO_CMN_RESET_N); + +	temp = REG_READ(DPLL_A); +	if ((temp & DPLL_SYNCLOCK_ENABLE) == 0) { +		REG_WRITE(DPLL_A, temp | DPLL_SYNCLOCK_ENABLE); +		REG_READ(DPLL_A); +	} + +	temp = REG_READ(DPLL_B); +	if ((temp & DPLL_SYNCLOCK_ENABLE) == 0) { +		REG_WRITE(DPLL_B, temp | DPLL_SYNCLOCK_ENABLE); +		REG_READ(DPLL_B); +	} + +	udelay(500); + +	REG_WRITE(DSPFW1, regs->cdv.saveDSPFW[0]); +	REG_WRITE(DSPFW2, regs->cdv.saveDSPFW[1]); +	REG_WRITE(DSPFW3, regs->cdv.saveDSPFW[2]); +	REG_WRITE(DSPFW4, regs->cdv.saveDSPFW[3]); +	REG_WRITE(DSPFW5, regs->cdv.saveDSPFW[4]); +	REG_WRITE(DSPFW6, regs->cdv.saveDSPFW[5]); + +	REG_WRITE(DSPARB, regs->cdv.saveDSPARB); +	REG_WRITE(ADPA, regs->cdv.saveADPA); + +	REG_WRITE(BLC_PWM_CTL2, regs->saveBLC_PWM_CTL2); +	REG_WRITE(LVDS, regs->cdv.saveLVDS); +	REG_WRITE(PFIT_CONTROL, regs->cdv.savePFIT_CONTROL); +	REG_WRITE(PFIT_PGM_RATIOS, regs->cdv.savePFIT_PGM_RATIOS); +	REG_WRITE(BLC_PWM_CTL, regs->saveBLC_PWM_CTL); +	REG_WRITE(PP_ON_DELAYS, regs->cdv.savePP_ON_DELAYS); +	REG_WRITE(PP_OFF_DELAYS, regs->cdv.savePP_OFF_DELAYS); +	REG_WRITE(PP_CYCLE, regs->cdv.savePP_CYCLE); +	REG_WRITE(PP_CONTROL, regs->cdv.savePP_CONTROL); + +	REG_WRITE(VGACNTRL, regs->cdv.saveVGACNTRL); + +	REG_WRITE(PSB_INT_ENABLE_R, regs->cdv.saveIER); +	REG_WRITE(PSB_INT_MASK_R, regs->cdv.saveIMR); + +	/* Fix arbitration bug */ +	cdv_errata(dev); + +	drm_mode_config_reset(dev); + +	list_for_each_entry(connector, &dev->mode_config.connector_list, head) +		connector->funcs->dpms(connector, DRM_MODE_DPMS_ON); + +	/* Resume the modeset for every activated CRTC */ +	drm_helper_resume_force_mode(dev); +	return 0; +} + +static int cdv_power_down(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 pwr_cnt, pwr_mask, pwr_sts; +	int tries = 5; + +	pwr_cnt = inl(dev_priv->apm_base + PSB_APM_CMD); +	pwr_cnt &= ~PSB_PWRGT_GFX_MASK; +	pwr_cnt |= PSB_PWRGT_GFX_OFF; +	pwr_mask = PSB_PWRGT_GFX_MASK; + +	outl(pwr_cnt, dev_priv->apm_base + PSB_APM_CMD); + +	while (tries--) { +		pwr_sts = inl(dev_priv->apm_base + PSB_APM_STS); +		if ((pwr_sts & pwr_mask) == PSB_PWRGT_GFX_D3) +			return 0; +		udelay(10); +	} +	return 0; +} + +static int cdv_power_up(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 pwr_cnt, pwr_mask, pwr_sts; +	int tries = 5; + +	pwr_cnt = inl(dev_priv->apm_base + PSB_APM_CMD); +	pwr_cnt &= ~PSB_PWRGT_GFX_MASK; +	pwr_cnt |= PSB_PWRGT_GFX_ON; +	pwr_mask = PSB_PWRGT_GFX_MASK; + +	outl(pwr_cnt, dev_priv->apm_base + PSB_APM_CMD); + +	while (tries--) { +		pwr_sts = inl(dev_priv->apm_base + PSB_APM_STS); +		if ((pwr_sts & pwr_mask) == PSB_PWRGT_GFX_D0) +			return 0; +		udelay(10); +	} +	return 0; +} + +static void cdv_hotplug_work_func(struct work_struct *work) +{ +        struct drm_psb_private *dev_priv = container_of(work, struct drm_psb_private, +							hotplug_work);                  +        struct drm_device *dev = dev_priv->dev; + +        /* Just fire off a uevent and let userspace tell us what to do */ +        drm_helper_hpd_irq_event(dev); +}                        + +/* The core driver has received a hotplug IRQ. We are in IRQ context +   so extract the needed information and kick off queued processing */ +    +static int cdv_hotplug_event(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	schedule_work(&dev_priv->hotplug_work); +	REG_WRITE(PORT_HOTPLUG_STAT, REG_READ(PORT_HOTPLUG_STAT)); +	return 1; +} + +static void cdv_hotplug_enable(struct drm_device *dev, bool on) +{ +	if (on) { +		u32 hotplug = REG_READ(PORT_HOTPLUG_EN); +		hotplug |= HDMIB_HOTPLUG_INT_EN | HDMIC_HOTPLUG_INT_EN | +			   HDMID_HOTPLUG_INT_EN | CRT_HOTPLUG_INT_EN; +		REG_WRITE(PORT_HOTPLUG_EN, hotplug); +	}  else { +		REG_WRITE(PORT_HOTPLUG_EN, 0); +		REG_WRITE(PORT_HOTPLUG_STAT, REG_READ(PORT_HOTPLUG_STAT)); +	}	 +} + +static const char *force_audio_names[] = { +	"off", +	"auto", +	"on", +}; + +void cdv_intel_attach_force_audio_property(struct drm_connector *connector) +{ +	struct drm_device *dev = connector->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct drm_property *prop; +	int i; + +	prop = dev_priv->force_audio_property; +	if (prop == NULL) { +		prop = drm_property_create(dev, DRM_MODE_PROP_ENUM, +					   "audio", +					   ARRAY_SIZE(force_audio_names)); +		if (prop == NULL) +			return; + +		for (i = 0; i < ARRAY_SIZE(force_audio_names); i++) +			drm_property_add_enum(prop, i, i-1, force_audio_names[i]); + +		dev_priv->force_audio_property = prop; +	} +	drm_object_attach_property(&connector->base, prop, 0); +} + + +static const char *broadcast_rgb_names[] = { +	"Full", +	"Limited 16:235", +}; + +void cdv_intel_attach_broadcast_rgb_property(struct drm_connector *connector) +{ +	struct drm_device *dev = connector->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct drm_property *prop; +	int i; + +	prop = dev_priv->broadcast_rgb_property; +	if (prop == NULL) { +		prop = drm_property_create(dev, DRM_MODE_PROP_ENUM, +					   "Broadcast RGB", +					   ARRAY_SIZE(broadcast_rgb_names)); +		if (prop == NULL) +			return; + +		for (i = 0; i < ARRAY_SIZE(broadcast_rgb_names); i++) +			drm_property_add_enum(prop, i, i, broadcast_rgb_names[i]); + +		dev_priv->broadcast_rgb_property = prop; +	} + +	drm_object_attach_property(&connector->base, prop, 0); +} + +/* Cedarview */ +static const struct psb_offset cdv_regmap[2] = { +	{ +		.fp0 = FPA0, +		.fp1 = FPA1, +		.cntr = DSPACNTR, +		.conf = PIPEACONF, +		.src = PIPEASRC, +		.dpll = DPLL_A, +		.dpll_md = DPLL_A_MD, +		.htotal = HTOTAL_A, +		.hblank = HBLANK_A, +		.hsync = HSYNC_A, +		.vtotal = VTOTAL_A, +		.vblank = VBLANK_A, +		.vsync = VSYNC_A, +		.stride = DSPASTRIDE, +		.size = DSPASIZE, +		.pos = DSPAPOS, +		.base = DSPABASE, +		.surf = DSPASURF, +		.addr = DSPABASE, +		.status = PIPEASTAT, +		.linoff = DSPALINOFF, +		.tileoff = DSPATILEOFF, +		.palette = PALETTE_A, +	}, +	{ +		.fp0 = FPB0, +		.fp1 = FPB1, +		.cntr = DSPBCNTR, +		.conf = PIPEBCONF, +		.src = PIPEBSRC, +		.dpll = DPLL_B, +		.dpll_md = DPLL_B_MD, +		.htotal = HTOTAL_B, +		.hblank = HBLANK_B, +		.hsync = HSYNC_B, +		.vtotal = VTOTAL_B, +		.vblank = VBLANK_B, +		.vsync = VSYNC_B, +		.stride = DSPBSTRIDE, +		.size = DSPBSIZE, +		.pos = DSPBPOS, +		.base = DSPBBASE, +		.surf = DSPBSURF, +		.addr = DSPBBASE, +		.status = PIPEBSTAT, +		.linoff = DSPBLINOFF, +		.tileoff = DSPBTILEOFF, +		.palette = PALETTE_B, +	} +}; + +static int cdv_chip_setup(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	INIT_WORK(&dev_priv->hotplug_work, cdv_hotplug_work_func); + +	if (pci_enable_msi(dev->pdev)) +		dev_warn(dev->dev, "Enabling MSI failed!\n"); +	dev_priv->regmap = cdv_regmap; +	gma_get_core_freq(dev); +	psb_intel_opregion_init(dev); +	psb_intel_init_bios(dev); +	cdv_hotplug_enable(dev, false); +	return 0; +} + +/* CDV is much like Poulsbo but has MID like SGX offsets and PM */ + +const struct psb_ops cdv_chip_ops = { +	.name = "GMA3600/3650", +	.accel_2d = 0, +	.pipes = 2, +	.crtcs = 2, +	.hdmi_mask = (1 << 0) | (1 << 1), +	.lvds_mask = (1 << 1), +	.sdvo_mask = (1 << 0), +	.cursor_needs_phys = 0, +	.sgx_offset = MRST_SGX_OFFSET, +	.chip_setup = cdv_chip_setup, +	.errata = cdv_errata, + +	.crtc_helper = &cdv_intel_helper_funcs, +	.crtc_funcs = &cdv_intel_crtc_funcs, +	.clock_funcs = &cdv_clock_funcs, + +	.output_init = cdv_output_init, +	.hotplug = cdv_hotplug_event, +	.hotplug_enable = cdv_hotplug_enable, + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	.backlight_init = cdv_backlight_init, +#endif + +	.init_pm = cdv_init_pm, +	.save_regs = cdv_save_display_registers, +	.restore_regs = cdv_restore_display_registers, +	.power_down = cdv_power_down, +	.power_up = cdv_power_up, +	.update_wm = cdv_update_wm, +	.disable_sr = cdv_disable_sr, +}; diff --git a/drivers/gpu/drm/gma500/cdv_device.h b/drivers/gpu/drm/gma500/cdv_device.h new file mode 100644 index 00000000000..705c11d47d4 --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_device.h @@ -0,0 +1,30 @@ +/* + * Copyright © 2011 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc.,  + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +extern const struct drm_crtc_helper_funcs cdv_intel_helper_funcs; +extern const struct drm_crtc_funcs cdv_intel_crtc_funcs; +extern const struct gma_clock_funcs cdv_clock_funcs; +extern void cdv_intel_crt_init(struct drm_device *dev, +			struct psb_intel_mode_device *mode_dev); +extern void cdv_intel_lvds_init(struct drm_device *dev, +			struct psb_intel_mode_device *mode_dev); +extern void cdv_hdmi_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev, +			int reg); +extern struct drm_display_mode *cdv_intel_crtc_mode_get(struct drm_device *dev, +					     struct drm_crtc *crtc); +extern void cdv_update_wm(struct drm_device *dev, struct drm_crtc *crtc); +extern void cdv_disable_sr(struct drm_device *dev); diff --git a/drivers/gpu/drm/gma500/cdv_intel_crt.c b/drivers/gpu/drm/gma500/cdv_intel_crt.c new file mode 100644 index 00000000000..c18268cd516 --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_intel_crt.c @@ -0,0 +1,317 @@ +/* + * Copyright © 2006-2007 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + */ + +#include <linux/i2c.h> +#include <drm/drmP.h> + +#include "intel_bios.h" +#include "psb_drv.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "power.h" +#include "cdv_device.h" +#include <linux/pm_runtime.h> + + +static void cdv_intel_crt_dpms(struct drm_encoder *encoder, int mode) +{ +	struct drm_device *dev = encoder->dev; +	u32 temp, reg; +	reg = ADPA; + +	temp = REG_READ(reg); +	temp &= ~(ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE); +	temp &= ~ADPA_DAC_ENABLE; + +	switch (mode) { +	case DRM_MODE_DPMS_ON: +		temp |= ADPA_DAC_ENABLE; +		break; +	case DRM_MODE_DPMS_STANDBY: +		temp |= ADPA_DAC_ENABLE | ADPA_HSYNC_CNTL_DISABLE; +		break; +	case DRM_MODE_DPMS_SUSPEND: +		temp |= ADPA_DAC_ENABLE | ADPA_VSYNC_CNTL_DISABLE; +		break; +	case DRM_MODE_DPMS_OFF: +		temp |= ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE; +		break; +	} + +	REG_WRITE(reg, temp); +} + +static int cdv_intel_crt_mode_valid(struct drm_connector *connector, +				struct drm_display_mode *mode) +{ +	if (mode->flags & DRM_MODE_FLAG_DBLSCAN) +		return MODE_NO_DBLESCAN; + +	/* The lowest clock for CDV is 20000KHz */ +	if (mode->clock < 20000) +		return MODE_CLOCK_LOW; + +	/* The max clock for CDV is 355 instead of 400 */ +	if (mode->clock > 355000) +		return MODE_CLOCK_HIGH; + +	return MODE_OK; +} + +static void cdv_intel_crt_mode_set(struct drm_encoder *encoder, +			       struct drm_display_mode *mode, +			       struct drm_display_mode *adjusted_mode) +{ + +	struct drm_device *dev = encoder->dev; +	struct drm_crtc *crtc = encoder->crtc; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int dpll_md_reg; +	u32 adpa, dpll_md; +	u32 adpa_reg; + +	if (gma_crtc->pipe == 0) +		dpll_md_reg = DPLL_A_MD; +	else +		dpll_md_reg = DPLL_B_MD; + +	adpa_reg = ADPA; + +	/* +	 * Disable separate mode multiplier used when cloning SDVO to CRT +	 * XXX this needs to be adjusted when we really are cloning +	 */ +	{ +		dpll_md = REG_READ(dpll_md_reg); +		REG_WRITE(dpll_md_reg, +			   dpll_md & ~DPLL_MD_UDI_MULTIPLIER_MASK); +	} + +	adpa = 0; +	if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) +		adpa |= ADPA_HSYNC_ACTIVE_HIGH; +	if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) +		adpa |= ADPA_VSYNC_ACTIVE_HIGH; + +	if (gma_crtc->pipe == 0) +		adpa |= ADPA_PIPE_A_SELECT; +	else +		adpa |= ADPA_PIPE_B_SELECT; + +	REG_WRITE(adpa_reg, adpa); +} + + +/** + * Uses CRT_HOTPLUG_EN and CRT_HOTPLUG_STAT to detect CRT presence. + * + * \return true if CRT is connected. + * \return false if CRT is disconnected. + */ +static bool cdv_intel_crt_detect_hotplug(struct drm_connector *connector, +								bool force) +{ +	struct drm_device *dev = connector->dev; +	u32 hotplug_en; +	int i, tries = 0, ret = false; +	u32 orig; + +	/* +	 * On a CDV thep, CRT detect sequence need to be done twice +	 * to get a reliable result. +	 */ +	tries = 2; + +	orig = hotplug_en = REG_READ(PORT_HOTPLUG_EN); +	hotplug_en &= ~(CRT_HOTPLUG_DETECT_MASK); +	hotplug_en |= CRT_HOTPLUG_FORCE_DETECT; + +	hotplug_en |= CRT_HOTPLUG_ACTIVATION_PERIOD_64; +	hotplug_en |= CRT_HOTPLUG_VOLTAGE_COMPARE_50; + +	for (i = 0; i < tries ; i++) { +		unsigned long timeout; +		/* turn on the FORCE_DETECT */ +		REG_WRITE(PORT_HOTPLUG_EN, hotplug_en); +		timeout = jiffies + msecs_to_jiffies(1000); +		/* wait for FORCE_DETECT to go off */ +		do { +			if (!(REG_READ(PORT_HOTPLUG_EN) & +					CRT_HOTPLUG_FORCE_DETECT)) +				break; +			msleep(1); +		} while (time_after(timeout, jiffies)); +	} + +	if ((REG_READ(PORT_HOTPLUG_STAT) & CRT_HOTPLUG_MONITOR_MASK) != +	    CRT_HOTPLUG_MONITOR_NONE) +		ret = true; + +	 /* clear the interrupt we just generated, if any */ +	REG_WRITE(PORT_HOTPLUG_STAT, CRT_HOTPLUG_INT_STATUS); + +	/* and put the bits back */ +	REG_WRITE(PORT_HOTPLUG_EN, orig); +	return ret; +} + +static enum drm_connector_status cdv_intel_crt_detect( +				struct drm_connector *connector, bool force) +{ +	if (cdv_intel_crt_detect_hotplug(connector, force)) +		return connector_status_connected; +	else +		return connector_status_disconnected; +} + +static void cdv_intel_crt_destroy(struct drm_connector *connector) +{ +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); + +	psb_intel_i2c_destroy(gma_encoder->ddc_bus); +	drm_sysfs_connector_remove(connector); +	drm_connector_cleanup(connector); +	kfree(connector); +} + +static int cdv_intel_crt_get_modes(struct drm_connector *connector) +{ +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	return psb_intel_ddc_get_modes(connector, +				       &gma_encoder->ddc_bus->adapter); +} + +static int cdv_intel_crt_set_property(struct drm_connector *connector, +				  struct drm_property *property, +				  uint64_t value) +{ +	return 0; +} + +/* + * Routines for controlling stuff on the analog port + */ + +static const struct drm_encoder_helper_funcs cdv_intel_crt_helper_funcs = { +	.dpms = cdv_intel_crt_dpms, +	.mode_fixup = gma_encoder_mode_fixup, +	.prepare = gma_encoder_prepare, +	.commit = gma_encoder_commit, +	.mode_set = cdv_intel_crt_mode_set, +}; + +static const struct drm_connector_funcs cdv_intel_crt_connector_funcs = { +	.dpms = drm_helper_connector_dpms, +	.detect = cdv_intel_crt_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.destroy = cdv_intel_crt_destroy, +	.set_property = cdv_intel_crt_set_property, +}; + +static const struct drm_connector_helper_funcs +				cdv_intel_crt_connector_helper_funcs = { +	.mode_valid = cdv_intel_crt_mode_valid, +	.get_modes = cdv_intel_crt_get_modes, +	.best_encoder = gma_best_encoder, +}; + +static void cdv_intel_crt_enc_destroy(struct drm_encoder *encoder) +{ +	drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs cdv_intel_crt_enc_funcs = { +	.destroy = cdv_intel_crt_enc_destroy, +}; + +void cdv_intel_crt_init(struct drm_device *dev, +			struct psb_intel_mode_device *mode_dev) +{ + +	struct gma_connector *gma_connector; +	struct gma_encoder *gma_encoder; +	struct drm_connector *connector; +	struct drm_encoder *encoder; + +	u32 i2c_reg; + +	gma_encoder = kzalloc(sizeof(struct gma_encoder), GFP_KERNEL); +	if (!gma_encoder) +		return; + +	gma_connector = kzalloc(sizeof(struct gma_connector), GFP_KERNEL); +	if (!gma_connector) +		goto failed_connector; + +	connector = &gma_connector->base; +	connector->polled = DRM_CONNECTOR_POLL_HPD; +	drm_connector_init(dev, connector, +		&cdv_intel_crt_connector_funcs, DRM_MODE_CONNECTOR_VGA); + +	encoder = &gma_encoder->base; +	drm_encoder_init(dev, encoder, +		&cdv_intel_crt_enc_funcs, DRM_MODE_ENCODER_DAC); + +	gma_connector_attach_encoder(gma_connector, gma_encoder); + +	/* Set up the DDC bus. */ +	i2c_reg = GPIOA; +	/* Remove the following code for CDV */ +	/* +	if (dev_priv->crt_ddc_bus != 0) +		i2c_reg = dev_priv->crt_ddc_bus; +	}*/ +	gma_encoder->ddc_bus = psb_intel_i2c_create(dev, +							  i2c_reg, "CRTDDC_A"); +	if (!gma_encoder->ddc_bus) { +		dev_printk(KERN_ERR, &dev->pdev->dev, "DDC bus registration " +			   "failed.\n"); +		goto failed_ddc; +	} + +	gma_encoder->type = INTEL_OUTPUT_ANALOG; +	/* +	psb_intel_output->clone_mask = (1 << INTEL_ANALOG_CLONE_BIT); +	psb_intel_output->crtc_mask = (1 << 0) | (1 << 1); +	*/ +	connector->interlace_allowed = 0; +	connector->doublescan_allowed = 0; + +	drm_encoder_helper_add(encoder, &cdv_intel_crt_helper_funcs); +	drm_connector_helper_add(connector, +					&cdv_intel_crt_connector_helper_funcs); + +	drm_sysfs_connector_add(connector); + +	return; +failed_ddc: +	drm_encoder_cleanup(&gma_encoder->base); +	drm_connector_cleanup(&gma_connector->base); +	kfree(gma_connector); +failed_connector: +	kfree(gma_encoder); +	return; +} diff --git a/drivers/gpu/drm/gma500/cdv_intel_display.c b/drivers/gpu/drm/gma500/cdv_intel_display.c new file mode 100644 index 00000000000..66727328832 --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_intel_display.c @@ -0,0 +1,999 @@ +/* + * Copyright © 2006-2011 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + */ + +#include <linux/i2c.h> + +#include <drm/drmP.h> +#include "framebuffer.h" +#include "psb_drv.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "gma_display.h" +#include "power.h" +#include "cdv_device.h" + +static bool cdv_intel_find_dp_pll(const struct gma_limit_t *limit, +				  struct drm_crtc *crtc, int target, +				  int refclk, struct gma_clock_t *best_clock); + + +#define CDV_LIMIT_SINGLE_LVDS_96	0 +#define CDV_LIMIT_SINGLE_LVDS_100	1 +#define CDV_LIMIT_DAC_HDMI_27		2 +#define CDV_LIMIT_DAC_HDMI_96		3 +#define CDV_LIMIT_DP_27			4 +#define CDV_LIMIT_DP_100		5 + +static const struct gma_limit_t cdv_intel_limits[] = { +	{			/* CDV_SINGLE_LVDS_96MHz */ +	 .dot = {.min = 20000, .max = 115500}, +	 .vco = {.min = 1800000, .max = 3600000}, +	 .n = {.min = 2, .max = 6}, +	 .m = {.min = 60, .max = 160}, +	 .m1 = {.min = 0, .max = 0}, +	 .m2 = {.min = 58, .max = 158}, +	 .p = {.min = 28, .max = 140}, +	 .p1 = {.min = 2, .max = 10}, +	 .p2 = {.dot_limit = 200000, .p2_slow = 14, .p2_fast = 14}, +	 .find_pll = gma_find_best_pll, +	 }, +	{			/* CDV_SINGLE_LVDS_100MHz */ +	 .dot = {.min = 20000, .max = 115500}, +	 .vco = {.min = 1800000, .max = 3600000}, +	 .n = {.min = 2, .max = 6}, +	 .m = {.min = 60, .max = 160}, +	 .m1 = {.min = 0, .max = 0}, +	 .m2 = {.min = 58, .max = 158}, +	 .p = {.min = 28, .max = 140}, +	 .p1 = {.min = 2, .max = 10}, +	 /* The single-channel range is 25-112Mhz, and dual-channel +	  * is 80-224Mhz.  Prefer single channel as much as possible. +	  */ +	 .p2 = {.dot_limit = 200000, .p2_slow = 14, .p2_fast = 14}, +	 .find_pll = gma_find_best_pll, +	 }, +	{			/* CDV_DAC_HDMI_27MHz */ +	 .dot = {.min = 20000, .max = 400000}, +	 .vco = {.min = 1809000, .max = 3564000}, +	 .n = {.min = 1, .max = 1}, +	 .m = {.min = 67, .max = 132}, +	 .m1 = {.min = 0, .max = 0}, +	 .m2 = {.min = 65, .max = 130}, +	 .p = {.min = 5, .max = 90}, +	 .p1 = {.min = 1, .max = 9}, +	 .p2 = {.dot_limit = 225000, .p2_slow = 10, .p2_fast = 5}, +	 .find_pll = gma_find_best_pll, +	 }, +	{			/* CDV_DAC_HDMI_96MHz */ +	 .dot = {.min = 20000, .max = 400000}, +	 .vco = {.min = 1800000, .max = 3600000}, +	 .n = {.min = 2, .max = 6}, +	 .m = {.min = 60, .max = 160}, +	 .m1 = {.min = 0, .max = 0}, +	 .m2 = {.min = 58, .max = 158}, +	 .p = {.min = 5, .max = 100}, +	 .p1 = {.min = 1, .max = 10}, +	 .p2 = {.dot_limit = 225000, .p2_slow = 10, .p2_fast = 5}, +	 .find_pll = gma_find_best_pll, +	 }, +	{			/* CDV_DP_27MHz */ +	 .dot = {.min = 160000, .max = 272000}, +	 .vco = {.min = 1809000, .max = 3564000}, +	 .n = {.min = 1, .max = 1}, +	 .m = {.min = 67, .max = 132}, +	 .m1 = {.min = 0, .max = 0}, +	 .m2 = {.min = 65, .max = 130}, +	 .p = {.min = 5, .max = 90}, +	 .p1 = {.min = 1, .max = 9}, +	 .p2 = {.dot_limit = 225000, .p2_slow = 10, .p2_fast = 10}, +	 .find_pll = cdv_intel_find_dp_pll, +	 }, +	{			/* CDV_DP_100MHz */ +	 .dot = {.min = 160000, .max = 272000}, +	 .vco = {.min = 1800000, .max = 3600000}, +	 .n = {.min = 2, .max = 6}, +	 .m = {.min = 60, .max = 164}, +	 .m1 = {.min = 0, .max = 0}, +	 .m2 = {.min = 58, .max = 162}, +	 .p = {.min = 5, .max = 100}, +	 .p1 = {.min = 1, .max = 10}, +	 .p2 = {.dot_limit = 225000, .p2_slow = 10, .p2_fast = 10}, +	 .find_pll = cdv_intel_find_dp_pll, +	 }	 +}; + +#define _wait_for(COND, MS, W) ({ \ +	unsigned long timeout__ = jiffies + msecs_to_jiffies(MS);	\ +	int ret__ = 0;							\ +	while (!(COND)) {						\ +		if (time_after(jiffies, timeout__)) {			\ +			ret__ = -ETIMEDOUT;				\ +			break;						\ +		}							\ +		if (W && !in_dbg_master())				\ +			msleep(W);					\ +	}								\ +	ret__;								\ +}) + +#define wait_for(COND, MS) _wait_for(COND, MS, 1) + + +int cdv_sb_read(struct drm_device *dev, u32 reg, u32 *val) +{ +	int ret; + +	ret = wait_for((REG_READ(SB_PCKT) & SB_BUSY) == 0, 1000); +	if (ret) { +		DRM_ERROR("timeout waiting for SB to idle before read\n"); +		return ret; +	} + +	REG_WRITE(SB_ADDR, reg); +	REG_WRITE(SB_PCKT, +		   SET_FIELD(SB_OPCODE_READ, SB_OPCODE) | +		   SET_FIELD(SB_DEST_DPLL, SB_DEST) | +		   SET_FIELD(0xf, SB_BYTE_ENABLE)); + +	ret = wait_for((REG_READ(SB_PCKT) & SB_BUSY) == 0, 1000); +	if (ret) { +		DRM_ERROR("timeout waiting for SB to idle after read\n"); +		return ret; +	} + +	*val = REG_READ(SB_DATA); + +	return 0; +} + +int cdv_sb_write(struct drm_device *dev, u32 reg, u32 val) +{ +	int ret; +	static bool dpio_debug = true; +	u32 temp; + +	if (dpio_debug) { +		if (cdv_sb_read(dev, reg, &temp) == 0) +			DRM_DEBUG_KMS("0x%08x: 0x%08x (before)\n", reg, temp); +		DRM_DEBUG_KMS("0x%08x: 0x%08x\n", reg, val); +	} + +	ret = wait_for((REG_READ(SB_PCKT) & SB_BUSY) == 0, 1000); +	if (ret) { +		DRM_ERROR("timeout waiting for SB to idle before write\n"); +		return ret; +	} + +	REG_WRITE(SB_ADDR, reg); +	REG_WRITE(SB_DATA, val); +	REG_WRITE(SB_PCKT, +		   SET_FIELD(SB_OPCODE_WRITE, SB_OPCODE) | +		   SET_FIELD(SB_DEST_DPLL, SB_DEST) | +		   SET_FIELD(0xf, SB_BYTE_ENABLE)); + +	ret = wait_for((REG_READ(SB_PCKT) & SB_BUSY) == 0, 1000); +	if (ret) { +		DRM_ERROR("timeout waiting for SB to idle after write\n"); +		return ret; +	} + +	if (dpio_debug) { +		if (cdv_sb_read(dev, reg, &temp) == 0) +			DRM_DEBUG_KMS("0x%08x: 0x%08x (after)\n", reg, temp); +	} + +	return 0; +} + +/* Reset the DPIO configuration register.  The BIOS does this at every + * mode set. + */ +void cdv_sb_reset(struct drm_device *dev) +{ + +	REG_WRITE(DPIO_CFG, 0); +	REG_READ(DPIO_CFG); +	REG_WRITE(DPIO_CFG, DPIO_MODE_SELECT_0 | DPIO_CMN_RESET_N); +} + +/* Unlike most Intel display engines, on Cedarview the DPLL registers + * are behind this sideband bus.  They must be programmed while the + * DPLL reference clock is on in the DPLL control register, but before + * the DPLL is enabled in the DPLL control register. + */ +static int +cdv_dpll_set_clock_cdv(struct drm_device *dev, struct drm_crtc *crtc, +		       struct gma_clock_t *clock, bool is_lvds, u32 ddi_select) +{ +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int pipe = gma_crtc->pipe; +	u32 m, n_vco, p; +	int ret = 0; +	int dpll_reg = (pipe == 0) ? DPLL_A : DPLL_B; +	int ref_sfr = (pipe == 0) ? SB_REF_DPLLA : SB_REF_DPLLB; +	u32 ref_value; +	u32 lane_reg, lane_value; + +	cdv_sb_reset(dev); + +	REG_WRITE(dpll_reg, DPLL_SYNCLOCK_ENABLE | DPLL_VGA_MODE_DIS); + +	udelay(100); + +	/* Follow the BIOS and write the REF/SFR Register. Hardcoded value */ +	ref_value = 0x68A701; + +	cdv_sb_write(dev, SB_REF_SFR(pipe), ref_value); + +	/* We don't know what the other fields of these regs are, so +	 * leave them in place. +	 */ +	/*  +	 * The BIT 14:13 of 0x8010/0x8030 is used to select the ref clk +	 * for the pipe A/B. Display spec 1.06 has wrong definition. +	 * Correct definition is like below: +	 * +	 * refclka mean use clock from same PLL +	 * +	 * if DPLLA sets 01 and DPLLB sets 01, they use clock from their pll +	 * +	 * if DPLLA sets 01 and DPLLB sets 02, both use clk from DPLLA +	 * +	 */   +	ret = cdv_sb_read(dev, ref_sfr, &ref_value); +	if (ret) +		return ret; +	ref_value &= ~(REF_CLK_MASK); + +	/* use DPLL_A for pipeB on CRT/HDMI */ +	if (pipe == 1 && !is_lvds && !(ddi_select & DP_MASK)) { +		DRM_DEBUG_KMS("use DPLLA for pipe B\n"); +		ref_value |= REF_CLK_DPLLA; +	} else { +		DRM_DEBUG_KMS("use their DPLL for pipe A/B\n"); +		ref_value |= REF_CLK_DPLL; +	} +	ret = cdv_sb_write(dev, ref_sfr, ref_value); +	if (ret) +		return ret; + +	ret = cdv_sb_read(dev, SB_M(pipe), &m); +	if (ret) +		return ret; +	m &= ~SB_M_DIVIDER_MASK; +	m |= ((clock->m2) << SB_M_DIVIDER_SHIFT); +	ret = cdv_sb_write(dev, SB_M(pipe), m); +	if (ret) +		return ret; + +	ret = cdv_sb_read(dev, SB_N_VCO(pipe), &n_vco); +	if (ret) +		return ret; + +	/* Follow the BIOS to program the N_DIVIDER REG */ +	n_vco &= 0xFFFF; +	n_vco |= 0x107; +	n_vco &= ~(SB_N_VCO_SEL_MASK | +		   SB_N_DIVIDER_MASK | +		   SB_N_CB_TUNE_MASK); + +	n_vco |= ((clock->n) << SB_N_DIVIDER_SHIFT); + +	if (clock->vco < 2250000) { +		n_vco |= (2 << SB_N_CB_TUNE_SHIFT); +		n_vco |= (0 << SB_N_VCO_SEL_SHIFT); +	} else if (clock->vco < 2750000) { +		n_vco |= (1 << SB_N_CB_TUNE_SHIFT); +		n_vco |= (1 << SB_N_VCO_SEL_SHIFT); +	} else if (clock->vco < 3300000) { +		n_vco |= (0 << SB_N_CB_TUNE_SHIFT); +		n_vco |= (2 << SB_N_VCO_SEL_SHIFT); +	} else { +		n_vco |= (0 << SB_N_CB_TUNE_SHIFT); +		n_vco |= (3 << SB_N_VCO_SEL_SHIFT); +	} + +	ret = cdv_sb_write(dev, SB_N_VCO(pipe), n_vco); +	if (ret) +		return ret; + +	ret = cdv_sb_read(dev, SB_P(pipe), &p); +	if (ret) +		return ret; +	p &= ~(SB_P2_DIVIDER_MASK | SB_P1_DIVIDER_MASK); +	p |= SET_FIELD(clock->p1, SB_P1_DIVIDER); +	switch (clock->p2) { +	case 5: +		p |= SET_FIELD(SB_P2_5, SB_P2_DIVIDER); +		break; +	case 10: +		p |= SET_FIELD(SB_P2_10, SB_P2_DIVIDER); +		break; +	case 14: +		p |= SET_FIELD(SB_P2_14, SB_P2_DIVIDER); +		break; +	case 7: +		p |= SET_FIELD(SB_P2_7, SB_P2_DIVIDER); +		break; +	default: +		DRM_ERROR("Bad P2 clock: %d\n", clock->p2); +		return -EINVAL; +	} +	ret = cdv_sb_write(dev, SB_P(pipe), p); +	if (ret) +		return ret; + +	if (ddi_select) { +		if ((ddi_select & DDI_MASK) == DDI0_SELECT) { +			lane_reg = PSB_LANE0; +			cdv_sb_read(dev, lane_reg, &lane_value); +			lane_value &= ~(LANE_PLL_MASK); +			lane_value |= LANE_PLL_ENABLE | LANE_PLL_PIPE(pipe); +			cdv_sb_write(dev, lane_reg, lane_value); + +			lane_reg = PSB_LANE1; +			cdv_sb_read(dev, lane_reg, &lane_value); +			lane_value &= ~(LANE_PLL_MASK); +			lane_value |= LANE_PLL_ENABLE | LANE_PLL_PIPE(pipe); +			cdv_sb_write(dev, lane_reg, lane_value); +		} else { +			lane_reg = PSB_LANE2; +			cdv_sb_read(dev, lane_reg, &lane_value); +			lane_value &= ~(LANE_PLL_MASK); +			lane_value |= LANE_PLL_ENABLE | LANE_PLL_PIPE(pipe); +			cdv_sb_write(dev, lane_reg, lane_value); + +			lane_reg = PSB_LANE3; +			cdv_sb_read(dev, lane_reg, &lane_value); +			lane_value &= ~(LANE_PLL_MASK); +			lane_value |= LANE_PLL_ENABLE | LANE_PLL_PIPE(pipe); +			cdv_sb_write(dev, lane_reg, lane_value); +		} +	} +	return 0; +} + +static const struct gma_limit_t *cdv_intel_limit(struct drm_crtc *crtc, +						 int refclk) +{ +	const struct gma_limit_t *limit; +	if (gma_pipe_has_type(crtc, INTEL_OUTPUT_LVDS)) { +		/* +		 * Now only single-channel LVDS is supported on CDV. If it is +		 * incorrect, please add the dual-channel LVDS. +		 */ +		if (refclk == 96000) +			limit = &cdv_intel_limits[CDV_LIMIT_SINGLE_LVDS_96]; +		else +			limit = &cdv_intel_limits[CDV_LIMIT_SINGLE_LVDS_100]; +	} else if (gma_pipe_has_type(crtc, INTEL_OUTPUT_DISPLAYPORT) || +			gma_pipe_has_type(crtc, INTEL_OUTPUT_EDP)) { +		if (refclk == 27000) +			limit = &cdv_intel_limits[CDV_LIMIT_DP_27]; +		else +			limit = &cdv_intel_limits[CDV_LIMIT_DP_100]; +	} else { +		if (refclk == 27000) +			limit = &cdv_intel_limits[CDV_LIMIT_DAC_HDMI_27]; +		else +			limit = &cdv_intel_limits[CDV_LIMIT_DAC_HDMI_96]; +	} +	return limit; +} + +/* m1 is reserved as 0 in CDV, n is a ring counter */ +static void cdv_intel_clock(int refclk, struct gma_clock_t *clock) +{ +	clock->m = clock->m2 + 2; +	clock->p = clock->p1 * clock->p2; +	clock->vco = (refclk * clock->m) / clock->n; +	clock->dot = clock->vco / clock->p; +} + +static bool cdv_intel_find_dp_pll(const struct gma_limit_t *limit, +				  struct drm_crtc *crtc, int target, +				  int refclk, +				  struct gma_clock_t *best_clock) +{ +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct gma_clock_t clock; + +	switch (refclk) { +	case 27000: +		if (target < 200000) { +			clock.p1 = 2; +			clock.p2 = 10; +			clock.n = 1; +			clock.m1 = 0; +			clock.m2 = 118; +		} else { +			clock.p1 = 1; +			clock.p2 = 10; +			clock.n = 1; +			clock.m1 = 0; +			clock.m2 = 98; +		} +		break; + +	case 100000: +		if (target < 200000) { +			clock.p1 = 2; +			clock.p2 = 10; +			clock.n = 5; +			clock.m1 = 0; +			clock.m2 = 160; +		} else { +			clock.p1 = 1; +			clock.p2 = 10; +			clock.n = 5; +			clock.m1 = 0; +			clock.m2 = 133; +		} +		break; + +	default: +		return false; +	} + +	gma_crtc->clock_funcs->clock(refclk, &clock); +	memcpy(best_clock, &clock, sizeof(struct gma_clock_t)); +	return true; +} + +#define		FIFO_PIPEA		(1 << 0) +#define		FIFO_PIPEB		(1 << 1) + +static bool cdv_intel_pipe_enabled(struct drm_device *dev, int pipe) +{ +	struct drm_crtc *crtc; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = NULL; + +	crtc = dev_priv->pipe_to_crtc_mapping[pipe]; +	gma_crtc = to_gma_crtc(crtc); + +	if (crtc->primary->fb == NULL || !gma_crtc->active) +		return false; +	return true; +} + +void cdv_disable_sr(struct drm_device *dev) +{ +	if (REG_READ(FW_BLC_SELF) & FW_BLC_SELF_EN) { + +		/* Disable self-refresh before adjust WM */ +		REG_WRITE(FW_BLC_SELF, (REG_READ(FW_BLC_SELF) & ~FW_BLC_SELF_EN)); +		REG_READ(FW_BLC_SELF); + +		gma_wait_for_vblank(dev); + +		/* Cedarview workaround to write ovelay plane, which force to leave +		 * MAX_FIFO state. +		 */ +		REG_WRITE(OV_OVADD, 0/*dev_priv->ovl_offset*/); +		REG_READ(OV_OVADD); + +		gma_wait_for_vblank(dev); +	} + +} + +void cdv_update_wm(struct drm_device *dev, struct drm_crtc *crtc) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); + +	/* Is only one pipe enabled? */ +	if (cdv_intel_pipe_enabled(dev, 0) ^ cdv_intel_pipe_enabled(dev, 1)) { +		u32 fw; + +		fw = REG_READ(DSPFW1); +		fw &= ~DSP_FIFO_SR_WM_MASK; +		fw |= (0x7e << DSP_FIFO_SR_WM_SHIFT); +		fw &= ~CURSOR_B_FIFO_WM_MASK; +		fw |= (0x4 << CURSOR_B_FIFO_WM_SHIFT); +		REG_WRITE(DSPFW1, fw); + +		fw = REG_READ(DSPFW2); +		fw &= ~CURSOR_A_FIFO_WM_MASK; +		fw |= (0x6 << CURSOR_A_FIFO_WM_SHIFT); +		fw &= ~DSP_PLANE_C_FIFO_WM_MASK; +		fw |= (0x8 << DSP_PLANE_C_FIFO_WM_SHIFT); +		REG_WRITE(DSPFW2, fw); + +		REG_WRITE(DSPFW3, 0x36000000); + +		/* ignore FW4 */ + +		/* Is pipe b lvds ? */ +		if (gma_crtc->pipe == 1 && +		    gma_pipe_has_type(crtc, INTEL_OUTPUT_LVDS)) { +			REG_WRITE(DSPFW5, 0x00040330); +		} else { +			fw = (3 << DSP_PLANE_B_FIFO_WM1_SHIFT) | +			     (4 << DSP_PLANE_A_FIFO_WM1_SHIFT) | +			     (3 << CURSOR_B_FIFO_WM1_SHIFT) | +			     (4 << CURSOR_FIFO_SR_WM1_SHIFT); +			REG_WRITE(DSPFW5, fw); +		} + +		REG_WRITE(DSPFW6, 0x10); + +		gma_wait_for_vblank(dev); + +		/* enable self-refresh for single pipe active */ +		REG_WRITE(FW_BLC_SELF, FW_BLC_SELF_EN); +		REG_READ(FW_BLC_SELF); +		gma_wait_for_vblank(dev); + +	} else { + +		/* HW team suggested values... */ +		REG_WRITE(DSPFW1, 0x3f880808); +		REG_WRITE(DSPFW2, 0x0b020202); +		REG_WRITE(DSPFW3, 0x24000000); +		REG_WRITE(DSPFW4, 0x08030202); +		REG_WRITE(DSPFW5, 0x01010101); +		REG_WRITE(DSPFW6, 0x1d0); + +		gma_wait_for_vblank(dev); + +		dev_priv->ops->disable_sr(dev); +	} +} + +/** + * Return the pipe currently connected to the panel fitter, + * or -1 if the panel fitter is not present or not in use + */ +static int cdv_intel_panel_fitter_pipe(struct drm_device *dev) +{ +	u32 pfit_control; + +	pfit_control = REG_READ(PFIT_CONTROL); + +	/* See if the panel fitter is in use */ +	if ((pfit_control & PFIT_ENABLE) == 0) +		return -1; +	return (pfit_control >> 29) & 0x3; +} + +static int cdv_intel_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 drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	int refclk; +	struct gma_clock_t clock; +	u32 dpll = 0, dspcntr, pipeconf; +	bool ok; +	bool is_crt = false, is_lvds = false, is_tv = false; +	bool is_hdmi = false, is_dp = false; +	struct drm_mode_config *mode_config = &dev->mode_config; +	struct drm_connector *connector; +	const struct gma_limit_t *limit; +	u32 ddi_select = 0; +	bool is_edp = false; + +	list_for_each_entry(connector, &mode_config->connector_list, head) { +		struct gma_encoder *gma_encoder = +					gma_attached_encoder(connector); + +		if (!connector->encoder +		    || connector->encoder->crtc != crtc) +			continue; + +		ddi_select = gma_encoder->ddi_select; +		switch (gma_encoder->type) { +		case INTEL_OUTPUT_LVDS: +			is_lvds = true; +			break; +		case INTEL_OUTPUT_TVOUT: +			is_tv = true; +			break; +		case INTEL_OUTPUT_ANALOG: +			is_crt = true; +			break; +		case INTEL_OUTPUT_HDMI: +			is_hdmi = true; +			break; +		case INTEL_OUTPUT_DISPLAYPORT: +			is_dp = true; +			break; +		case INTEL_OUTPUT_EDP: +			is_edp = true; +			break; +		default: +			DRM_ERROR("invalid output type.\n"); +			return 0; +		} +	} + +	if (dev_priv->dplla_96mhz) +		/* low-end sku, 96/100 mhz */ +		refclk = 96000; +	else +		/* high-end sku, 27/100 mhz */ +		refclk = 27000; +	if (is_dp || is_edp) { +		/* +		 * Based on the spec the low-end SKU has only CRT/LVDS. So it is +		 * unnecessary to consider it for DP/eDP. +		 * On the high-end SKU, it will use the 27/100M reference clk +		 * for DP/eDP. When using SSC clock, the ref clk is 100MHz.Otherwise +		 * it will be 27MHz. From the VBIOS code it seems that the pipe A choose +		 * 27MHz for DP/eDP while the Pipe B chooses the 100MHz. +		 */  +		if (pipe == 0) +			refclk = 27000; +		else +			refclk = 100000; +	} + +	if (is_lvds && dev_priv->lvds_use_ssc) { +		refclk = dev_priv->lvds_ssc_freq * 1000; +		DRM_DEBUG_KMS("Use SSC reference clock %d Mhz\n", dev_priv->lvds_ssc_freq); +	} + +	drm_mode_debug_printmodeline(adjusted_mode); +	 +	limit = gma_crtc->clock_funcs->limit(crtc, refclk); + +	ok = limit->find_pll(limit, crtc, adjusted_mode->clock, refclk, +				 &clock); +	if (!ok) { +		DRM_ERROR("Couldn't find PLL settings for mode! target: %d, actual: %d", +			  adjusted_mode->clock, clock.dot); +		return 0; +	} + +	dpll = DPLL_VGA_MODE_DIS; +	if (is_tv) { +		/* XXX: just matching BIOS for now */ +/*	dpll |= PLL_REF_INPUT_TVCLKINBC; */ +		dpll |= 3; +	} +/*		dpll |= PLL_REF_INPUT_DREFCLK; */ + +	if (is_dp || is_edp) { +		cdv_intel_dp_set_m_n(crtc, mode, adjusted_mode); +	} else { +		REG_WRITE(PIPE_GMCH_DATA_M(pipe), 0); +		REG_WRITE(PIPE_GMCH_DATA_N(pipe), 0); +		REG_WRITE(PIPE_DP_LINK_M(pipe), 0); +		REG_WRITE(PIPE_DP_LINK_N(pipe), 0); +	} + +	dpll |= DPLL_SYNCLOCK_ENABLE; +/*	if (is_lvds) +		dpll |= DPLLB_MODE_LVDS; +	else +		dpll |= DPLLB_MODE_DAC_SERIAL; */ +	/* dpll |= (2 << 11); */ + +	/* setup pipeconf */ +	pipeconf = REG_READ(map->conf); + +	pipeconf &= ~(PIPE_BPC_MASK); +	if (is_edp) { +		switch (dev_priv->edp.bpp) { +		case 24: +			pipeconf |= PIPE_8BPC; +			break; +		case 18: +			pipeconf |= PIPE_6BPC; +			break; +		case 30: +			pipeconf |= PIPE_10BPC; +			break; +		default: +			pipeconf |= PIPE_8BPC; +			break; +		} +	} else if (is_lvds) { +		/* the BPC will be 6 if it is 18-bit LVDS panel */ +		if ((REG_READ(LVDS) & LVDS_A3_POWER_MASK) == LVDS_A3_POWER_UP) +			pipeconf |= PIPE_8BPC; +		else +			pipeconf |= PIPE_6BPC; +	} else +		pipeconf |= PIPE_8BPC; +			 +	/* Set up the display plane register */ +	dspcntr = DISPPLANE_GAMMA_ENABLE; + +	if (pipe == 0) +		dspcntr |= DISPPLANE_SEL_PIPE_A; +	else +		dspcntr |= DISPPLANE_SEL_PIPE_B; + +	dspcntr |= DISPLAY_PLANE_ENABLE; +	pipeconf |= PIPEACONF_ENABLE; + +	REG_WRITE(map->dpll, dpll | DPLL_VGA_MODE_DIS | DPLL_SYNCLOCK_ENABLE); +	REG_READ(map->dpll); + +	cdv_dpll_set_clock_cdv(dev, crtc, &clock, is_lvds, ddi_select); + +	udelay(150); + + +	/* The LVDS pin pair needs to be on before the DPLLs are enabled. +	 * This is an exception to the general rule that mode_set doesn't turn +	 * things on. +	 */ +	if (is_lvds) { +		u32 lvds = REG_READ(LVDS); + +		lvds |= +		    LVDS_PORT_EN | LVDS_A0A2_CLKA_POWER_UP | +		    LVDS_PIPEB_SELECT; +		/* Set the B0-B3 data pairs corresponding to +		 * whether we're going to +		 * set the DPLLs for dual-channel mode or not. +		 */ +		if (clock.p2 == 7) +			lvds |= LVDS_B0B3_POWER_UP | LVDS_CLKB_POWER_UP; +		else +			lvds &= ~(LVDS_B0B3_POWER_UP | LVDS_CLKB_POWER_UP); + +		/* It would be nice to set 24 vs 18-bit mode (LVDS_A3_POWER_UP) +		 * appropriately here, but we need to look more +		 * thoroughly into how panels behave in the two modes. +		 */ + +		REG_WRITE(LVDS, lvds); +		REG_READ(LVDS); +	} + +	dpll |= DPLL_VCO_ENABLE; + +	/* Disable the panel fitter if it was on our pipe */ +	if (cdv_intel_panel_fitter_pipe(dev) == pipe) +		REG_WRITE(PFIT_CONTROL, 0); + +	DRM_DEBUG_KMS("Mode for pipe %c:\n", pipe == 0 ? 'A' : 'B'); +	drm_mode_debug_printmodeline(mode); + +	REG_WRITE(map->dpll, +		(REG_READ(map->dpll) & ~DPLL_LOCK) | DPLL_VCO_ENABLE); +	REG_READ(map->dpll); +	/* Wait for the clocks to stabilize. */ +	udelay(150); /* 42 usec w/o calibration, 110 with.  rounded up. */ + +	if (!(REG_READ(map->dpll) & DPLL_LOCK)) { +		dev_err(dev->dev, "Failed to get DPLL lock\n"); +		return -EBUSY; +	} + +	{ +		int sdvo_pixel_multiply = adjusted_mode->clock / mode->clock; +		REG_WRITE(map->dpll_md, (0 << DPLL_MD_UDI_DIVIDER_SHIFT) | ((sdvo_pixel_multiply - 1) << DPLL_MD_UDI_MULTIPLIER_SHIFT)); +	} + +	REG_WRITE(map->htotal, (adjusted_mode->crtc_hdisplay - 1) | +		  ((adjusted_mode->crtc_htotal - 1) << 16)); +	REG_WRITE(map->hblank, (adjusted_mode->crtc_hblank_start - 1) | +		  ((adjusted_mode->crtc_hblank_end - 1) << 16)); +	REG_WRITE(map->hsync, (adjusted_mode->crtc_hsync_start - 1) | +		  ((adjusted_mode->crtc_hsync_end - 1) << 16)); +	REG_WRITE(map->vtotal, (adjusted_mode->crtc_vdisplay - 1) | +		  ((adjusted_mode->crtc_vtotal - 1) << 16)); +	REG_WRITE(map->vblank, (adjusted_mode->crtc_vblank_start - 1) | +		  ((adjusted_mode->crtc_vblank_end - 1) << 16)); +	REG_WRITE(map->vsync, (adjusted_mode->crtc_vsync_start - 1) | +		  ((adjusted_mode->crtc_vsync_end - 1) << 16)); +	/* pipesrc and dspsize control the size that is scaled from, +	 * which should always be the user's requested size. +	 */ +	REG_WRITE(map->size, +		  ((mode->vdisplay - 1) << 16) | (mode->hdisplay - 1)); +	REG_WRITE(map->pos, 0); +	REG_WRITE(map->src, +		  ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1)); +	REG_WRITE(map->conf, pipeconf); +	REG_READ(map->conf); + +	gma_wait_for_vblank(dev); + +	REG_WRITE(map->cntr, dspcntr); + +	/* Flush the plane changes */ +	{ +		struct drm_crtc_helper_funcs *crtc_funcs = +		    crtc->helper_private; +		crtc_funcs->mode_set_base(crtc, x, y, old_fb); +	} + +	gma_wait_for_vblank(dev); + +	return 0; +} + +/** Derive the pixel clock for the given refclk and divisors for 8xx chips. */ + +/* FIXME: why are we using this, should it be cdv_ in this tree ? */ + +static void i8xx_clock(int refclk, struct gma_clock_t *clock) +{ +	clock->m = 5 * (clock->m1 + 2) + (clock->m2 + 2); +	clock->p = clock->p1 * clock->p2; +	clock->vco = refclk * clock->m / (clock->n + 2); +	clock->dot = clock->vco / clock->p; +} + +/* Returns the clock of the currently programmed mode of the given pipe. */ +static int cdv_intel_crtc_clock_get(struct drm_device *dev, +				struct drm_crtc *crtc) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	u32 dpll; +	u32 fp; +	struct gma_clock_t clock; +	bool is_lvds; +	struct psb_pipe *p = &dev_priv->regs.pipe[pipe]; + +	if (gma_power_begin(dev, false)) { +		dpll = REG_READ(map->dpll); +		if ((dpll & DISPLAY_RATE_SELECT_FPA1) == 0) +			fp = REG_READ(map->fp0); +		else +			fp = REG_READ(map->fp1); +		is_lvds = (pipe == 1) && (REG_READ(LVDS) & LVDS_PORT_EN); +		gma_power_end(dev); +	} else { +		dpll = p->dpll; +		if ((dpll & DISPLAY_RATE_SELECT_FPA1) == 0) +			fp = p->fp0; +		else +			fp = p->fp1; + +		is_lvds = (pipe == 1) && +				(dev_priv->regs.psb.saveLVDS & LVDS_PORT_EN); +	} + +	clock.m1 = (fp & FP_M1_DIV_MASK) >> FP_M1_DIV_SHIFT; +	clock.m2 = (fp & FP_M2_DIV_MASK) >> FP_M2_DIV_SHIFT; +	clock.n = (fp & FP_N_DIV_MASK) >> FP_N_DIV_SHIFT; + +	if (is_lvds) { +		clock.p1 = +		    ffs((dpll & +			 DPLL_FPA01_P1_POST_DIV_MASK_I830_LVDS) >> +			DPLL_FPA01_P1_POST_DIV_SHIFT); +		if (clock.p1 == 0) { +			clock.p1 = 4; +			dev_err(dev->dev, "PLL %d\n", dpll); +		} +		clock.p2 = 14; + +		if ((dpll & PLL_REF_INPUT_MASK) == +		    PLLB_REF_INPUT_SPREADSPECTRUMIN) { +			/* XXX: might not be 66MHz */ +			i8xx_clock(66000, &clock); +		} else +			i8xx_clock(48000, &clock); +	} else { +		if (dpll & PLL_P1_DIVIDE_BY_TWO) +			clock.p1 = 2; +		else { +			clock.p1 = +			    ((dpll & +			      DPLL_FPA01_P1_POST_DIV_MASK_I830) >> +			     DPLL_FPA01_P1_POST_DIV_SHIFT) + 2; +		} +		if (dpll & PLL_P2_DIVIDE_BY_4) +			clock.p2 = 4; +		else +			clock.p2 = 2; + +		i8xx_clock(48000, &clock); +	} + +	/* XXX: It would be nice to validate the clocks, but we can't reuse +	 * i830PllIsValid() because it relies on the xf86_config connector +	 * configuration being accurate, which it isn't necessarily. +	 */ + +	return clock.dot; +} + +/** Returns the currently programmed mode of the given pipe. */ +struct drm_display_mode *cdv_intel_crtc_mode_get(struct drm_device *dev, +					     struct drm_crtc *crtc) +{ +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int pipe = gma_crtc->pipe; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_pipe *p = &dev_priv->regs.pipe[pipe]; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	struct drm_display_mode *mode; +	int htot; +	int hsync; +	int vtot; +	int vsync; + +	if (gma_power_begin(dev, false)) { +		htot = REG_READ(map->htotal); +		hsync = REG_READ(map->hsync); +		vtot = REG_READ(map->vtotal); +		vsync = REG_READ(map->vsync); +		gma_power_end(dev); +	} else { +		htot = p->htotal; +		hsync = p->hsync; +		vtot = p->vtotal; +		vsync = p->vsync; +	} + +	mode = kzalloc(sizeof(*mode), GFP_KERNEL); +	if (!mode) +		return NULL; + +	mode->clock = cdv_intel_crtc_clock_get(dev, crtc); +	mode->hdisplay = (htot & 0xffff) + 1; +	mode->htotal = ((htot & 0xffff0000) >> 16) + 1; +	mode->hsync_start = (hsync & 0xffff) + 1; +	mode->hsync_end = ((hsync & 0xffff0000) >> 16) + 1; +	mode->vdisplay = (vtot & 0xffff) + 1; +	mode->vtotal = ((vtot & 0xffff0000) >> 16) + 1; +	mode->vsync_start = (vsync & 0xffff) + 1; +	mode->vsync_end = ((vsync & 0xffff0000) >> 16) + 1; + +	drm_mode_set_name(mode); +	drm_mode_set_crtcinfo(mode, 0); + +	return mode; +} + +const struct drm_crtc_helper_funcs cdv_intel_helper_funcs = { +	.dpms = gma_crtc_dpms, +	.mode_fixup = gma_crtc_mode_fixup, +	.mode_set = cdv_intel_crtc_mode_set, +	.mode_set_base = gma_pipe_set_base, +	.prepare = gma_crtc_prepare, +	.commit = gma_crtc_commit, +	.disable = gma_crtc_disable, +}; + +const struct drm_crtc_funcs cdv_intel_crtc_funcs = { +	.save = gma_crtc_save, +	.restore = gma_crtc_restore, +	.cursor_set = gma_crtc_cursor_set, +	.cursor_move = gma_crtc_cursor_move, +	.gamma_set = gma_crtc_gamma_set, +	.set_config = gma_crtc_set_config, +	.destroy = gma_crtc_destroy, +}; + +const struct gma_clock_funcs cdv_clock_funcs = { +	.clock = cdv_intel_clock, +	.limit = cdv_intel_limit, +	.pll_is_valid = gma_pll_is_valid, +}; diff --git a/drivers/gpu/drm/gma500/cdv_intel_dp.c b/drivers/gpu/drm/gma500/cdv_intel_dp.c new file mode 100644 index 00000000000..9ff30c2efad --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_intel_dp.c @@ -0,0 +1,1952 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + *    Keith Packard <keithp@keithp.com> + * + */ + +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include "psb_drv.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "gma_display.h" +#include <drm/drm_dp_helper.h> + +#define _wait_for(COND, MS, W) ({ \ +        unsigned long timeout__ = jiffies + msecs_to_jiffies(MS);       \ +        int ret__ = 0;                                                  \ +        while (! (COND)) {                                              \ +                if (time_after(jiffies, timeout__)) {                   \ +                        ret__ = -ETIMEDOUT;                             \ +                        break;                                          \ +                }                                                       \ +                if (W && !in_dbg_master()) msleep(W);                   \ +        }                                                               \ +        ret__;                                                          \ +})       + +#define wait_for(COND, MS) _wait_for(COND, MS, 1) + +#define DP_LINK_STATUS_SIZE	6 +#define DP_LINK_CHECK_TIMEOUT	(10 * 1000) + +#define DP_LINK_CONFIGURATION_SIZE	9 + +#define CDV_FAST_LINK_TRAIN	1 + +struct cdv_intel_dp { +	uint32_t output_reg; +	uint32_t DP; +	uint8_t  link_configuration[DP_LINK_CONFIGURATION_SIZE]; +	bool has_audio; +	int force_audio; +	uint32_t color_range; +	uint8_t link_bw; +	uint8_t lane_count; +	uint8_t dpcd[4]; +	struct gma_encoder *encoder; +	struct i2c_adapter adapter; +	struct i2c_algo_dp_aux_data algo; +	uint8_t	train_set[4]; +	uint8_t link_status[DP_LINK_STATUS_SIZE]; +	int panel_power_up_delay; +	int panel_power_down_delay; +	int panel_power_cycle_delay; +	int backlight_on_delay; +	int backlight_off_delay; +	struct drm_display_mode *panel_fixed_mode;  /* for eDP */ +	bool panel_on; +}; + +struct ddi_regoff { +	uint32_t	PreEmph1; +	uint32_t	PreEmph2; +	uint32_t	VSwing1; +	uint32_t	VSwing2; +	uint32_t	VSwing3; +	uint32_t	VSwing4; +	uint32_t	VSwing5; +}; + +static struct ddi_regoff ddi_DP_train_table[] = { +	{.PreEmph1 = 0x812c, .PreEmph2 = 0x8124, .VSwing1 = 0x8154, +	.VSwing2 = 0x8148, .VSwing3 = 0x814C, .VSwing4 = 0x8150, +	.VSwing5 = 0x8158,}, +	{.PreEmph1 = 0x822c, .PreEmph2 = 0x8224, .VSwing1 = 0x8254, +	.VSwing2 = 0x8248, .VSwing3 = 0x824C, .VSwing4 = 0x8250, +	.VSwing5 = 0x8258,}, +}; + +static uint32_t dp_vswing_premph_table[] = { +        0x55338954,	0x4000, +        0x554d8954,	0x2000, +        0x55668954,	0, +        0x559ac0d4,	0x6000, +}; +/** + * is_edp - is the given port attached to an eDP panel (either CPU or PCH) + * @intel_dp: DP struct + * + * If a CPU or PCH DP output is attached to an eDP panel, this function + * will return true, and false otherwise. + */ +static bool is_edp(struct gma_encoder *encoder) +{ +	return encoder->type == INTEL_OUTPUT_EDP; +} + + +static void cdv_intel_dp_start_link_train(struct gma_encoder *encoder); +static void cdv_intel_dp_complete_link_train(struct gma_encoder *encoder); +static void cdv_intel_dp_link_down(struct gma_encoder *encoder); + +static int +cdv_intel_dp_max_lane_count(struct gma_encoder *encoder) +{ +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	int max_lane_count = 4; + +	if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11) { +		max_lane_count = intel_dp->dpcd[DP_MAX_LANE_COUNT] & 0x1f; +		switch (max_lane_count) { +		case 1: case 2: case 4: +			break; +		default: +			max_lane_count = 4; +		} +	} +	return max_lane_count; +} + +static int +cdv_intel_dp_max_link_bw(struct gma_encoder *encoder) +{ +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	int max_link_bw = intel_dp->dpcd[DP_MAX_LINK_RATE]; + +	switch (max_link_bw) { +	case DP_LINK_BW_1_62: +	case DP_LINK_BW_2_7: +		break; +	default: +		max_link_bw = DP_LINK_BW_1_62; +		break; +	} +	return max_link_bw; +} + +static int +cdv_intel_dp_link_clock(uint8_t link_bw) +{ +	if (link_bw == DP_LINK_BW_2_7) +		return 270000; +	else +		return 162000; +} + +static int +cdv_intel_dp_link_required(int pixel_clock, int bpp) +{ +	return (pixel_clock * bpp + 7) / 8; +} + +static int +cdv_intel_dp_max_data_rate(int max_link_clock, int max_lanes) +{ +	return (max_link_clock * max_lanes * 19) / 20; +} + +static void cdv_intel_edp_panel_vdd_on(struct gma_encoder *intel_encoder) +{ +	struct drm_device *dev = intel_encoder->base.dev; +	struct cdv_intel_dp *intel_dp = intel_encoder->dev_priv; +	u32 pp; + +	if (intel_dp->panel_on) { +		DRM_DEBUG_KMS("Skip VDD on because of panel on\n"); +		return; +	}	 +	DRM_DEBUG_KMS("\n"); + +	pp = REG_READ(PP_CONTROL); + +	pp |= EDP_FORCE_VDD; +	REG_WRITE(PP_CONTROL, pp); +	REG_READ(PP_CONTROL); +	msleep(intel_dp->panel_power_up_delay); +} + +static void cdv_intel_edp_panel_vdd_off(struct gma_encoder *intel_encoder) +{ +	struct drm_device *dev = intel_encoder->base.dev; +	u32 pp; + +	DRM_DEBUG_KMS("\n"); +	pp = REG_READ(PP_CONTROL); + +	pp &= ~EDP_FORCE_VDD; +	REG_WRITE(PP_CONTROL, pp); +	REG_READ(PP_CONTROL); + +} + +/* Returns true if the panel was already on when called */ +static bool cdv_intel_edp_panel_on(struct gma_encoder *intel_encoder) +{ +	struct drm_device *dev = intel_encoder->base.dev; +	struct cdv_intel_dp *intel_dp = intel_encoder->dev_priv; +	u32 pp, idle_on_mask = PP_ON | PP_SEQUENCE_NONE; + +	if (intel_dp->panel_on) +		return true; + +	DRM_DEBUG_KMS("\n"); +	pp = REG_READ(PP_CONTROL); +	pp &= ~PANEL_UNLOCK_MASK; + +	pp |= (PANEL_UNLOCK_REGS | POWER_TARGET_ON); +	REG_WRITE(PP_CONTROL, pp); +	REG_READ(PP_CONTROL); + +	if (wait_for(((REG_READ(PP_STATUS) & idle_on_mask) == idle_on_mask), 1000)) { +		DRM_DEBUG_KMS("Error in Powering up eDP panel, status %x\n", REG_READ(PP_STATUS)); +		intel_dp->panel_on = false; +	} else +		intel_dp->panel_on = true;	 +	msleep(intel_dp->panel_power_up_delay); + +	return false; +} + +static void cdv_intel_edp_panel_off (struct gma_encoder *intel_encoder) +{ +	struct drm_device *dev = intel_encoder->base.dev; +	u32 pp, idle_off_mask = PP_ON ; +	struct cdv_intel_dp *intel_dp = intel_encoder->dev_priv; + +	DRM_DEBUG_KMS("\n"); + +	pp = REG_READ(PP_CONTROL); + +	if ((pp & POWER_TARGET_ON) == 0)  +		return; + +	intel_dp->panel_on = false; +	pp &= ~PANEL_UNLOCK_MASK; +	/* ILK workaround: disable reset around power sequence */ + +	pp &= ~POWER_TARGET_ON; +	pp &= ~EDP_FORCE_VDD; +	pp &= ~EDP_BLC_ENABLE; +	REG_WRITE(PP_CONTROL, pp); +	REG_READ(PP_CONTROL); +	DRM_DEBUG_KMS("PP_STATUS %x\n", REG_READ(PP_STATUS)); + +	if (wait_for((REG_READ(PP_STATUS) & idle_off_mask) == 0, 1000)) { +		DRM_DEBUG_KMS("Error in turning off Panel\n");	 +	} + +	msleep(intel_dp->panel_power_cycle_delay); +	DRM_DEBUG_KMS("Over\n"); +} + +static void cdv_intel_edp_backlight_on (struct gma_encoder *intel_encoder) +{ +	struct drm_device *dev = intel_encoder->base.dev; +	u32 pp; + +	DRM_DEBUG_KMS("\n"); +	/* +	 * If we enable the backlight right away following a panel power +	 * on, we may see slight flicker as the panel syncs with the eDP +	 * link.  So delay a bit to make sure the image is solid before +	 * allowing it to appear. +	 */ +	msleep(300); +	pp = REG_READ(PP_CONTROL); + +	pp |= EDP_BLC_ENABLE; +	REG_WRITE(PP_CONTROL, pp); +	gma_backlight_enable(dev); +} + +static void cdv_intel_edp_backlight_off (struct gma_encoder *intel_encoder) +{ +	struct drm_device *dev = intel_encoder->base.dev; +	struct cdv_intel_dp *intel_dp = intel_encoder->dev_priv; +	u32 pp; + +	DRM_DEBUG_KMS("\n"); +	gma_backlight_disable(dev); +	msleep(10); +	pp = REG_READ(PP_CONTROL); + +	pp &= ~EDP_BLC_ENABLE; +	REG_WRITE(PP_CONTROL, pp); +	msleep(intel_dp->backlight_off_delay); +} + +static int +cdv_intel_dp_mode_valid(struct drm_connector *connector, +		    struct drm_display_mode *mode) +{ +	struct gma_encoder *encoder = gma_attached_encoder(connector); +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	int max_link_clock = cdv_intel_dp_link_clock(cdv_intel_dp_max_link_bw(encoder)); +	int max_lanes = cdv_intel_dp_max_lane_count(encoder); +	struct drm_psb_private *dev_priv = connector->dev->dev_private; + +	if (is_edp(encoder) && intel_dp->panel_fixed_mode) { +		if (mode->hdisplay > intel_dp->panel_fixed_mode->hdisplay) +			return MODE_PANEL; +		if (mode->vdisplay > intel_dp->panel_fixed_mode->vdisplay) +			return MODE_PANEL; +	} + +	/* only refuse the mode on non eDP since we have seen some weird eDP panels +	   which are outside spec tolerances but somehow work by magic */ +	if (!is_edp(encoder) && +	    (cdv_intel_dp_link_required(mode->clock, dev_priv->edp.bpp) +	     > cdv_intel_dp_max_data_rate(max_link_clock, max_lanes))) +		return MODE_CLOCK_HIGH; + +	if (is_edp(encoder)) { +	    if (cdv_intel_dp_link_required(mode->clock, 24) +	     	> cdv_intel_dp_max_data_rate(max_link_clock, max_lanes)) +		return MODE_CLOCK_HIGH; +		 +	} +	if (mode->clock < 10000) +		return MODE_CLOCK_LOW; + +	return MODE_OK; +} + +static uint32_t +pack_aux(uint8_t *src, int src_bytes) +{ +	int	i; +	uint32_t v = 0; + +	if (src_bytes > 4) +		src_bytes = 4; +	for (i = 0; i < src_bytes; i++) +		v |= ((uint32_t) src[i]) << ((3-i) * 8); +	return v; +} + +static void +unpack_aux(uint32_t src, uint8_t *dst, int dst_bytes) +{ +	int i; +	if (dst_bytes > 4) +		dst_bytes = 4; +	for (i = 0; i < dst_bytes; i++) +		dst[i] = src >> ((3-i) * 8); +} + +static int +cdv_intel_dp_aux_ch(struct gma_encoder *encoder, +		uint8_t *send, int send_bytes, +		uint8_t *recv, int recv_size) +{ +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	uint32_t output_reg = intel_dp->output_reg; +	struct drm_device *dev = encoder->base.dev; +	uint32_t ch_ctl = output_reg + 0x10; +	uint32_t ch_data = ch_ctl + 4; +	int i; +	int recv_bytes; +	uint32_t status; +	uint32_t aux_clock_divider; +	int try, precharge; + +	/* The clock divider is based off the hrawclk, +	 * and would like to run at 2MHz. So, take the +	 * hrawclk value and divide by 2 and use that +	 * On CDV platform it uses 200MHz as hrawclk. +	 * +	 */ +	aux_clock_divider = 200 / 2; + +	precharge = 4; +	if (is_edp(encoder)) +		precharge = 10; + +	if (REG_READ(ch_ctl) & DP_AUX_CH_CTL_SEND_BUSY) { +		DRM_ERROR("dp_aux_ch not started status 0x%08x\n", +			  REG_READ(ch_ctl)); +		return -EBUSY; +	} + +	/* Must try at least 3 times according to DP spec */ +	for (try = 0; try < 5; try++) { +		/* Load the send data into the aux channel data registers */ +		for (i = 0; i < send_bytes; i += 4) +			REG_WRITE(ch_data + i, +				   pack_aux(send + i, send_bytes - i)); +	 +		/* Send the command and wait for it to complete */ +		REG_WRITE(ch_ctl, +			   DP_AUX_CH_CTL_SEND_BUSY | +			   DP_AUX_CH_CTL_TIME_OUT_400us | +			   (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) | +			   (precharge << DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT) | +			   (aux_clock_divider << DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT) | +			   DP_AUX_CH_CTL_DONE | +			   DP_AUX_CH_CTL_TIME_OUT_ERROR | +			   DP_AUX_CH_CTL_RECEIVE_ERROR); +		for (;;) { +			status = REG_READ(ch_ctl); +			if ((status & DP_AUX_CH_CTL_SEND_BUSY) == 0) +				break; +			udelay(100); +		} +	 +		/* Clear done status and any errors */ +		REG_WRITE(ch_ctl, +			   status | +			   DP_AUX_CH_CTL_DONE | +			   DP_AUX_CH_CTL_TIME_OUT_ERROR | +			   DP_AUX_CH_CTL_RECEIVE_ERROR); +		if (status & DP_AUX_CH_CTL_DONE) +			break; +	} + +	if ((status & DP_AUX_CH_CTL_DONE) == 0) { +		DRM_ERROR("dp_aux_ch not done status 0x%08x\n", status); +		return -EBUSY; +	} + +	/* Check for timeout or receive error. +	 * Timeouts occur when the sink is not connected +	 */ +	if (status & DP_AUX_CH_CTL_RECEIVE_ERROR) { +		DRM_ERROR("dp_aux_ch receive error status 0x%08x\n", status); +		return -EIO; +	} + +	/* Timeouts occur when the device isn't connected, so they're +	 * "normal" -- don't fill the kernel log with these */ +	if (status & DP_AUX_CH_CTL_TIME_OUT_ERROR) { +		DRM_DEBUG_KMS("dp_aux_ch timeout status 0x%08x\n", status); +		return -ETIMEDOUT; +	} + +	/* Unload any bytes sent back from the other side */ +	recv_bytes = ((status & DP_AUX_CH_CTL_MESSAGE_SIZE_MASK) >> +		      DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT); +	if (recv_bytes > recv_size) +		recv_bytes = recv_size; +	 +	for (i = 0; i < recv_bytes; i += 4) +		unpack_aux(REG_READ(ch_data + i), +			   recv + i, recv_bytes - i); + +	return recv_bytes; +} + +/* Write data to the aux channel in native mode */ +static int +cdv_intel_dp_aux_native_write(struct gma_encoder *encoder, +			  uint16_t address, uint8_t *send, int send_bytes) +{ +	int ret; +	uint8_t	msg[20]; +	int msg_bytes; +	uint8_t	ack; + +	if (send_bytes > 16) +		return -1; +	msg[0] = DP_AUX_NATIVE_WRITE << 4; +	msg[1] = address >> 8; +	msg[2] = address & 0xff; +	msg[3] = send_bytes - 1; +	memcpy(&msg[4], send, send_bytes); +	msg_bytes = send_bytes + 4; +	for (;;) { +		ret = cdv_intel_dp_aux_ch(encoder, msg, msg_bytes, &ack, 1); +		if (ret < 0) +			return ret; +		ack >>= 4; +		if ((ack & DP_AUX_NATIVE_REPLY_MASK) == DP_AUX_NATIVE_REPLY_ACK) +			break; +		else if ((ack & DP_AUX_NATIVE_REPLY_MASK) == DP_AUX_NATIVE_REPLY_DEFER) +			udelay(100); +		else +			return -EIO; +	} +	return send_bytes; +} + +/* Write a single byte to the aux channel in native mode */ +static int +cdv_intel_dp_aux_native_write_1(struct gma_encoder *encoder, +			    uint16_t address, uint8_t byte) +{ +	return cdv_intel_dp_aux_native_write(encoder, address, &byte, 1); +} + +/* read bytes from a native aux channel */ +static int +cdv_intel_dp_aux_native_read(struct gma_encoder *encoder, +			 uint16_t address, uint8_t *recv, int recv_bytes) +{ +	uint8_t msg[4]; +	int msg_bytes; +	uint8_t reply[20]; +	int reply_bytes; +	uint8_t ack; +	int ret; + +	msg[0] = DP_AUX_NATIVE_READ << 4; +	msg[1] = address >> 8; +	msg[2] = address & 0xff; +	msg[3] = recv_bytes - 1; + +	msg_bytes = 4; +	reply_bytes = recv_bytes + 1; + +	for (;;) { +		ret = cdv_intel_dp_aux_ch(encoder, msg, msg_bytes, +				      reply, reply_bytes); +		if (ret == 0) +			return -EPROTO; +		if (ret < 0) +			return ret; +		ack = reply[0] >> 4; +		if ((ack & DP_AUX_NATIVE_REPLY_MASK) == DP_AUX_NATIVE_REPLY_ACK) { +			memcpy(recv, reply + 1, ret - 1); +			return ret - 1; +		} +		else if ((ack & DP_AUX_NATIVE_REPLY_MASK) == DP_AUX_NATIVE_REPLY_DEFER) +			udelay(100); +		else +			return -EIO; +	} +} + +static int +cdv_intel_dp_i2c_aux_ch(struct i2c_adapter *adapter, int mode, +		    uint8_t write_byte, uint8_t *read_byte) +{ +	struct i2c_algo_dp_aux_data *algo_data = adapter->algo_data; +	struct cdv_intel_dp *intel_dp = container_of(adapter, +						struct cdv_intel_dp, +						adapter); +	struct gma_encoder *encoder = intel_dp->encoder; +	uint16_t address = algo_data->address; +	uint8_t msg[5]; +	uint8_t reply[2]; +	unsigned retry; +	int msg_bytes; +	int reply_bytes; +	int ret; + +	/* Set up the command byte */ +	if (mode & MODE_I2C_READ) +		msg[0] = DP_AUX_I2C_READ << 4; +	else +		msg[0] = DP_AUX_I2C_WRITE << 4; + +	if (!(mode & MODE_I2C_STOP)) +		msg[0] |= DP_AUX_I2C_MOT << 4; + +	msg[1] = address >> 8; +	msg[2] = address; + +	switch (mode) { +	case MODE_I2C_WRITE: +		msg[3] = 0; +		msg[4] = write_byte; +		msg_bytes = 5; +		reply_bytes = 1; +		break; +	case MODE_I2C_READ: +		msg[3] = 0; +		msg_bytes = 4; +		reply_bytes = 2; +		break; +	default: +		msg_bytes = 3; +		reply_bytes = 1; +		break; +	} + +	for (retry = 0; retry < 5; retry++) { +		ret = cdv_intel_dp_aux_ch(encoder, +				      msg, msg_bytes, +				      reply, reply_bytes); +		if (ret < 0) { +			DRM_DEBUG_KMS("aux_ch failed %d\n", ret); +			return ret; +		} + +		switch ((reply[0] >> 4) & DP_AUX_NATIVE_REPLY_MASK) { +		case DP_AUX_NATIVE_REPLY_ACK: +			/* I2C-over-AUX Reply field is only valid +			 * when paired with AUX ACK. +			 */ +			break; +		case DP_AUX_NATIVE_REPLY_NACK: +			DRM_DEBUG_KMS("aux_ch native nack\n"); +			return -EREMOTEIO; +		case DP_AUX_NATIVE_REPLY_DEFER: +			udelay(100); +			continue; +		default: +			DRM_ERROR("aux_ch invalid native reply 0x%02x\n", +				  reply[0]); +			return -EREMOTEIO; +		} + +		switch ((reply[0] >> 4) & DP_AUX_I2C_REPLY_MASK) { +		case DP_AUX_I2C_REPLY_ACK: +			if (mode == MODE_I2C_READ) { +				*read_byte = reply[1]; +			} +			return reply_bytes - 1; +		case DP_AUX_I2C_REPLY_NACK: +			DRM_DEBUG_KMS("aux_i2c nack\n"); +			return -EREMOTEIO; +		case DP_AUX_I2C_REPLY_DEFER: +			DRM_DEBUG_KMS("aux_i2c defer\n"); +			udelay(100); +			break; +		default: +			DRM_ERROR("aux_i2c invalid reply 0x%02x\n", reply[0]); +			return -EREMOTEIO; +		} +	} + +	DRM_ERROR("too many retries, giving up\n"); +	return -EREMOTEIO; +} + +static int +cdv_intel_dp_i2c_init(struct gma_connector *connector, +		      struct gma_encoder *encoder, const char *name) +{ +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	int ret; + +	DRM_DEBUG_KMS("i2c_init %s\n", name); + +	intel_dp->algo.running = false; +	intel_dp->algo.address = 0; +	intel_dp->algo.aux_ch = cdv_intel_dp_i2c_aux_ch; + +	memset(&intel_dp->adapter, '\0', sizeof (intel_dp->adapter)); +	intel_dp->adapter.owner = THIS_MODULE; +	intel_dp->adapter.class = I2C_CLASS_DDC; +	strncpy (intel_dp->adapter.name, name, sizeof(intel_dp->adapter.name) - 1); +	intel_dp->adapter.name[sizeof(intel_dp->adapter.name) - 1] = '\0'; +	intel_dp->adapter.algo_data = &intel_dp->algo; +	intel_dp->adapter.dev.parent = connector->base.kdev; + +	if (is_edp(encoder)) +		cdv_intel_edp_panel_vdd_on(encoder); +	ret = i2c_dp_aux_add_bus(&intel_dp->adapter); +	if (is_edp(encoder)) +		cdv_intel_edp_panel_vdd_off(encoder); +	 +	return ret; +} + +static void cdv_intel_fixed_panel_mode(struct drm_display_mode *fixed_mode, +	struct drm_display_mode *adjusted_mode) +{ +	adjusted_mode->hdisplay = fixed_mode->hdisplay; +	adjusted_mode->hsync_start = fixed_mode->hsync_start; +	adjusted_mode->hsync_end = fixed_mode->hsync_end; +	adjusted_mode->htotal = fixed_mode->htotal; + +	adjusted_mode->vdisplay = fixed_mode->vdisplay; +	adjusted_mode->vsync_start = fixed_mode->vsync_start; +	adjusted_mode->vsync_end = fixed_mode->vsync_end; +	adjusted_mode->vtotal = fixed_mode->vtotal; + +	adjusted_mode->clock = fixed_mode->clock; + +	drm_mode_set_crtcinfo(adjusted_mode, CRTC_INTERLACE_HALVE_V); +} + +static bool +cdv_intel_dp_mode_fixup(struct drm_encoder *encoder, const struct drm_display_mode *mode, +		    struct drm_display_mode *adjusted_mode) +{ +	struct drm_psb_private *dev_priv = encoder->dev->dev_private; +	struct gma_encoder *intel_encoder = to_gma_encoder(encoder); +	struct cdv_intel_dp *intel_dp = intel_encoder->dev_priv; +	int lane_count, clock; +	int max_lane_count = cdv_intel_dp_max_lane_count(intel_encoder); +	int max_clock = cdv_intel_dp_max_link_bw(intel_encoder) == DP_LINK_BW_2_7 ? 1 : 0; +	static int bws[2] = { DP_LINK_BW_1_62, DP_LINK_BW_2_7 }; +	int refclock = mode->clock; +	int bpp = 24; + +	if (is_edp(intel_encoder) && intel_dp->panel_fixed_mode) { +		cdv_intel_fixed_panel_mode(intel_dp->panel_fixed_mode, adjusted_mode); +		refclock = intel_dp->panel_fixed_mode->clock; +		bpp = dev_priv->edp.bpp; +	} + +	for (lane_count = 1; lane_count <= max_lane_count; lane_count <<= 1) { +		for (clock = max_clock; clock >= 0; clock--) { +			int link_avail = cdv_intel_dp_max_data_rate(cdv_intel_dp_link_clock(bws[clock]), lane_count); + +			if (cdv_intel_dp_link_required(refclock, bpp) <= link_avail) { +				intel_dp->link_bw = bws[clock]; +				intel_dp->lane_count = lane_count; +				adjusted_mode->clock = cdv_intel_dp_link_clock(intel_dp->link_bw); +				DRM_DEBUG_KMS("Display port link bw %02x lane " +						"count %d clock %d\n", +				       intel_dp->link_bw, intel_dp->lane_count, +				       adjusted_mode->clock); +				return true; +			} +		} +	} +	if (is_edp(intel_encoder)) { +		/* okay we failed just pick the highest */ +		intel_dp->lane_count = max_lane_count; +		intel_dp->link_bw = bws[max_clock]; +		adjusted_mode->clock = cdv_intel_dp_link_clock(intel_dp->link_bw); +		DRM_DEBUG_KMS("Force picking display port link bw %02x lane " +			      "count %d clock %d\n", +			      intel_dp->link_bw, intel_dp->lane_count, +			      adjusted_mode->clock); + +		return true; +	} +	return false; +} + +struct cdv_intel_dp_m_n { +	uint32_t	tu; +	uint32_t	gmch_m; +	uint32_t	gmch_n; +	uint32_t	link_m; +	uint32_t	link_n; +}; + +static void +cdv_intel_reduce_ratio(uint32_t *num, uint32_t *den) +{ +	/* +	while (*num > 0xffffff || *den > 0xffffff) { +		*num >>= 1; +		*den >>= 1; +	}*/ +	uint64_t value, m; +	m = *num; +	value = m * (0x800000); +	m = do_div(value, *den); +	*num = value; +	*den = 0x800000; +} + +static void +cdv_intel_dp_compute_m_n(int bpp, +		     int nlanes, +		     int pixel_clock, +		     int link_clock, +		     struct cdv_intel_dp_m_n *m_n) +{ +	m_n->tu = 64; +	m_n->gmch_m = (pixel_clock * bpp + 7) >> 3; +	m_n->gmch_n = link_clock * nlanes; +	cdv_intel_reduce_ratio(&m_n->gmch_m, &m_n->gmch_n); +	m_n->link_m = pixel_clock; +	m_n->link_n = link_clock; +	cdv_intel_reduce_ratio(&m_n->link_m, &m_n->link_n); +} + +void +cdv_intel_dp_set_m_n(struct drm_crtc *crtc, struct drm_display_mode *mode, +		 struct drm_display_mode *adjusted_mode) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct drm_mode_config *mode_config = &dev->mode_config; +	struct drm_encoder *encoder; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int lane_count = 4, bpp = 24; +	struct cdv_intel_dp_m_n m_n; +	int pipe = gma_crtc->pipe; + +	/* +	 * Find the lane count in the intel_encoder private +	 */ +	list_for_each_entry(encoder, &mode_config->encoder_list, head) { +		struct gma_encoder *intel_encoder; +		struct cdv_intel_dp *intel_dp; + +		if (encoder->crtc != crtc) +			continue; + +		intel_encoder = to_gma_encoder(encoder); +		intel_dp = intel_encoder->dev_priv; +		if (intel_encoder->type == INTEL_OUTPUT_DISPLAYPORT) { +			lane_count = intel_dp->lane_count; +			break; +		} else if (is_edp(intel_encoder)) { +			lane_count = intel_dp->lane_count; +			bpp = dev_priv->edp.bpp; +			break; +		} +	} + +	/* +	 * Compute the GMCH and Link ratios. The '3' here is +	 * the number of bytes_per_pixel post-LUT, which we always +	 * set up for 8-bits of R/G/B, or 3 bytes total. +	 */ +	cdv_intel_dp_compute_m_n(bpp, lane_count, +			     mode->clock, adjusted_mode->clock, &m_n); + +	{ +		REG_WRITE(PIPE_GMCH_DATA_M(pipe), +			   ((m_n.tu - 1) << PIPE_GMCH_DATA_M_TU_SIZE_SHIFT) | +			   m_n.gmch_m); +		REG_WRITE(PIPE_GMCH_DATA_N(pipe), m_n.gmch_n); +		REG_WRITE(PIPE_DP_LINK_M(pipe), m_n.link_m); +		REG_WRITE(PIPE_DP_LINK_N(pipe), m_n.link_n); +	} +} + +static void +cdv_intel_dp_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, +		  struct drm_display_mode *adjusted_mode) +{ +	struct gma_encoder *intel_encoder = to_gma_encoder(encoder); +	struct drm_crtc *crtc = encoder->crtc; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct cdv_intel_dp *intel_dp = intel_encoder->dev_priv; +	struct drm_device *dev = encoder->dev; + +	intel_dp->DP = DP_VOLTAGE_0_4 | DP_PRE_EMPHASIS_0; +	intel_dp->DP |= intel_dp->color_range; + +	if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) +		intel_dp->DP |= DP_SYNC_HS_HIGH; +	if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) +		intel_dp->DP |= DP_SYNC_VS_HIGH; + +	intel_dp->DP |= DP_LINK_TRAIN_OFF; + +	switch (intel_dp->lane_count) { +	case 1: +		intel_dp->DP |= DP_PORT_WIDTH_1; +		break; +	case 2: +		intel_dp->DP |= DP_PORT_WIDTH_2; +		break; +	case 4: +		intel_dp->DP |= DP_PORT_WIDTH_4; +		break; +	} +	if (intel_dp->has_audio) +		intel_dp->DP |= DP_AUDIO_OUTPUT_ENABLE; + +	memset(intel_dp->link_configuration, 0, DP_LINK_CONFIGURATION_SIZE); +	intel_dp->link_configuration[0] = intel_dp->link_bw; +	intel_dp->link_configuration[1] = intel_dp->lane_count; + +	/* +	 * Check for DPCD version > 1.1 and enhanced framing support +	 */ +	if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11 && +	    (intel_dp->dpcd[DP_MAX_LANE_COUNT] & DP_ENHANCED_FRAME_CAP)) { +		intel_dp->link_configuration[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; +		intel_dp->DP |= DP_ENHANCED_FRAMING; +	} + +	/* CPT DP's pipe select is decided in TRANS_DP_CTL */ +	if (gma_crtc->pipe == 1) +		intel_dp->DP |= DP_PIPEB_SELECT; + +	REG_WRITE(intel_dp->output_reg, (intel_dp->DP | DP_PORT_EN)); +	DRM_DEBUG_KMS("DP expected reg is %x\n", intel_dp->DP); +	if (is_edp(intel_encoder)) { +		uint32_t pfit_control; +		cdv_intel_edp_panel_on(intel_encoder); + +		if (mode->hdisplay != adjusted_mode->hdisplay || +			    mode->vdisplay != adjusted_mode->vdisplay) +			pfit_control = PFIT_ENABLE; +		else +			pfit_control = 0; + +		pfit_control |= gma_crtc->pipe << PFIT_PIPE_SHIFT; + +		REG_WRITE(PFIT_CONTROL, pfit_control); +	} +} + + +/* If the sink supports it, try to set the power state appropriately */ +static void cdv_intel_dp_sink_dpms(struct gma_encoder *encoder, int mode) +{ +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	int ret, i; + +	/* Should have a valid DPCD by this point */ +	if (intel_dp->dpcd[DP_DPCD_REV] < 0x11) +		return; + +	if (mode != DRM_MODE_DPMS_ON) { +		ret = cdv_intel_dp_aux_native_write_1(encoder, DP_SET_POWER, +						  DP_SET_POWER_D3); +		if (ret != 1) +			DRM_DEBUG_DRIVER("failed to write sink power state\n"); +	} else { +		/* +		 * When turning on, we need to retry for 1ms to give the sink +		 * time to wake up. +		 */ +		for (i = 0; i < 3; i++) { +			ret = cdv_intel_dp_aux_native_write_1(encoder, +							  DP_SET_POWER, +							  DP_SET_POWER_D0); +			if (ret == 1) +				break; +			udelay(1000); +		} +	} +} + +static void cdv_intel_dp_prepare(struct drm_encoder *encoder) +{ +	struct gma_encoder *intel_encoder = to_gma_encoder(encoder); +	int edp = is_edp(intel_encoder); + +	if (edp) { +		cdv_intel_edp_backlight_off(intel_encoder); +		cdv_intel_edp_panel_off(intel_encoder); +		cdv_intel_edp_panel_vdd_on(intel_encoder); +        } +	/* Wake up the sink first */ +	cdv_intel_dp_sink_dpms(intel_encoder, DRM_MODE_DPMS_ON); +	cdv_intel_dp_link_down(intel_encoder); +	if (edp) +		cdv_intel_edp_panel_vdd_off(intel_encoder); +} + +static void cdv_intel_dp_commit(struct drm_encoder *encoder) +{ +	struct gma_encoder *intel_encoder = to_gma_encoder(encoder); +	int edp = is_edp(intel_encoder); + +	if (edp) +		cdv_intel_edp_panel_on(intel_encoder); +	cdv_intel_dp_start_link_train(intel_encoder); +	cdv_intel_dp_complete_link_train(intel_encoder); +	if (edp) +		cdv_intel_edp_backlight_on(intel_encoder); +} + +static void +cdv_intel_dp_dpms(struct drm_encoder *encoder, int mode) +{ +	struct gma_encoder *intel_encoder = to_gma_encoder(encoder); +	struct cdv_intel_dp *intel_dp = intel_encoder->dev_priv; +	struct drm_device *dev = encoder->dev; +	uint32_t dp_reg = REG_READ(intel_dp->output_reg); +	int edp = is_edp(intel_encoder); + +	if (mode != DRM_MODE_DPMS_ON) { +		if (edp) { +			cdv_intel_edp_backlight_off(intel_encoder); +			cdv_intel_edp_panel_vdd_on(intel_encoder); +		} +		cdv_intel_dp_sink_dpms(intel_encoder, mode); +		cdv_intel_dp_link_down(intel_encoder); +		if (edp) { +			cdv_intel_edp_panel_vdd_off(intel_encoder); +			cdv_intel_edp_panel_off(intel_encoder); +		} +	} else { +        	if (edp) +			cdv_intel_edp_panel_on(intel_encoder); +		cdv_intel_dp_sink_dpms(intel_encoder, mode); +		if (!(dp_reg & DP_PORT_EN)) { +			cdv_intel_dp_start_link_train(intel_encoder); +			cdv_intel_dp_complete_link_train(intel_encoder); +		} +		if (edp) +        		cdv_intel_edp_backlight_on(intel_encoder); +	} +} + +/* + * Native read with retry for link status and receiver capability reads for + * cases where the sink may still be asleep. + */ +static bool +cdv_intel_dp_aux_native_read_retry(struct gma_encoder *encoder, uint16_t address, +			       uint8_t *recv, int recv_bytes) +{ +	int ret, i; + +	/* +	 * Sinks are *supposed* to come up within 1ms from an off state, +	 * but we're also supposed to retry 3 times per the spec. +	 */ +	for (i = 0; i < 3; i++) { +		ret = cdv_intel_dp_aux_native_read(encoder, address, recv, +					       recv_bytes); +		if (ret == recv_bytes) +			return true; +		udelay(1000); +	} + +	return false; +} + +/* + * Fetch AUX CH registers 0x202 - 0x207 which contain + * link status information + */ +static bool +cdv_intel_dp_get_link_status(struct gma_encoder *encoder) +{ +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	return cdv_intel_dp_aux_native_read_retry(encoder, +					      DP_LANE0_1_STATUS, +					      intel_dp->link_status, +					      DP_LINK_STATUS_SIZE); +} + +static uint8_t +cdv_intel_dp_link_status(uint8_t link_status[DP_LINK_STATUS_SIZE], +		     int r) +{ +	return link_status[r - DP_LANE0_1_STATUS]; +} + +static uint8_t +cdv_intel_get_adjust_request_voltage(uint8_t link_status[DP_LINK_STATUS_SIZE], +				 int lane) +{ +	int	    i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); +	int	    s = ((lane & 1) ? +			 DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : +			 DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT); +	uint8_t l = cdv_intel_dp_link_status(link_status, i); + +	return ((l >> s) & 3) << DP_TRAIN_VOLTAGE_SWING_SHIFT; +} + +static uint8_t +cdv_intel_get_adjust_request_pre_emphasis(uint8_t link_status[DP_LINK_STATUS_SIZE], +				      int lane) +{ +	int	    i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); +	int	    s = ((lane & 1) ? +			 DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT : +			 DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT); +	uint8_t l = cdv_intel_dp_link_status(link_status, i); + +	return ((l >> s) & 3) << DP_TRAIN_PRE_EMPHASIS_SHIFT; +} + + +#if 0 +static char	*voltage_names[] = { +	"0.4V", "0.6V", "0.8V", "1.2V" +}; +static char	*pre_emph_names[] = { +	"0dB", "3.5dB", "6dB", "9.5dB" +}; +static char	*link_train_names[] = { +	"pattern 1", "pattern 2", "idle", "off" +}; +#endif + +#define CDV_DP_VOLTAGE_MAX	    DP_TRAIN_VOLTAGE_SWING_1200 +/* +static uint8_t +cdv_intel_dp_pre_emphasis_max(uint8_t voltage_swing) +{ +	switch (voltage_swing & DP_TRAIN_VOLTAGE_SWING_MASK) { +	case DP_TRAIN_VOLTAGE_SWING_400: +		return DP_TRAIN_PRE_EMPHASIS_6; +	case DP_TRAIN_VOLTAGE_SWING_600: +		return DP_TRAIN_PRE_EMPHASIS_6; +	case DP_TRAIN_VOLTAGE_SWING_800: +		return DP_TRAIN_PRE_EMPHASIS_3_5; +	case DP_TRAIN_VOLTAGE_SWING_1200: +	default: +		return DP_TRAIN_PRE_EMPHASIS_0; +	} +} +*/ +static void +cdv_intel_get_adjust_train(struct gma_encoder *encoder) +{ +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	uint8_t v = 0; +	uint8_t p = 0; +	int lane; + +	for (lane = 0; lane < intel_dp->lane_count; lane++) { +		uint8_t this_v = cdv_intel_get_adjust_request_voltage(intel_dp->link_status, lane); +		uint8_t this_p = cdv_intel_get_adjust_request_pre_emphasis(intel_dp->link_status, lane); + +		if (this_v > v) +			v = this_v; +		if (this_p > p) +			p = this_p; +	} +	 +	if (v >= CDV_DP_VOLTAGE_MAX) +		v = CDV_DP_VOLTAGE_MAX | DP_TRAIN_MAX_SWING_REACHED; + +	if (p == DP_TRAIN_PRE_EMPHASIS_MASK) +		p |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; +		 +	for (lane = 0; lane < 4; lane++) +		intel_dp->train_set[lane] = v | p; +} + + +static uint8_t +cdv_intel_get_lane_status(uint8_t link_status[DP_LINK_STATUS_SIZE], +		      int lane) +{ +	int i = DP_LANE0_1_STATUS + (lane >> 1); +	int s = (lane & 1) * 4; +	uint8_t l = cdv_intel_dp_link_status(link_status, i); + +	return (l >> s) & 0xf; +} + +/* Check for clock recovery is done on all channels */ +static bool +cdv_intel_clock_recovery_ok(uint8_t link_status[DP_LINK_STATUS_SIZE], int lane_count) +{ +	int lane; +	uint8_t lane_status; + +	for (lane = 0; lane < lane_count; lane++) { +		lane_status = cdv_intel_get_lane_status(link_status, lane); +		if ((lane_status & DP_LANE_CR_DONE) == 0) +			return false; +	} +	return true; +} + +/* Check to see if channel eq is done on all channels */ +#define CHANNEL_EQ_BITS (DP_LANE_CR_DONE|\ +			 DP_LANE_CHANNEL_EQ_DONE|\ +			 DP_LANE_SYMBOL_LOCKED) +static bool +cdv_intel_channel_eq_ok(struct gma_encoder *encoder) +{ +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	uint8_t lane_align; +	uint8_t lane_status; +	int lane; + +	lane_align = cdv_intel_dp_link_status(intel_dp->link_status, +					  DP_LANE_ALIGN_STATUS_UPDATED); +	if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0) +		return false; +	for (lane = 0; lane < intel_dp->lane_count; lane++) { +		lane_status = cdv_intel_get_lane_status(intel_dp->link_status, lane); +		if ((lane_status & CHANNEL_EQ_BITS) != CHANNEL_EQ_BITS) +			return false; +	} +	return true; +} + +static bool +cdv_intel_dp_set_link_train(struct gma_encoder *encoder, +			uint32_t dp_reg_value, +			uint8_t dp_train_pat) +{ +	 +	struct drm_device *dev = encoder->base.dev; +	int ret; +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; + +	REG_WRITE(intel_dp->output_reg, dp_reg_value); +	REG_READ(intel_dp->output_reg); + +	ret = cdv_intel_dp_aux_native_write_1(encoder, +				    DP_TRAINING_PATTERN_SET, +				    dp_train_pat); + +	if (ret != 1) { +		DRM_DEBUG_KMS("Failure in setting link pattern %x\n", +				dp_train_pat); +		return false; +	} + +	return true; +} + + +static bool +cdv_intel_dplink_set_level(struct gma_encoder *encoder, +			uint8_t dp_train_pat) +{ +	 +	int ret; +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; + +	ret = cdv_intel_dp_aux_native_write(encoder, +					DP_TRAINING_LANE0_SET, +					intel_dp->train_set, +					intel_dp->lane_count); + +	if (ret != intel_dp->lane_count) { +		DRM_DEBUG_KMS("Failure in setting level %d, lane_cnt= %d\n", +				intel_dp->train_set[0], intel_dp->lane_count); +		return false; +	} +	return true; +} + +static void +cdv_intel_dp_set_vswing_premph(struct gma_encoder *encoder, uint8_t signal_level) +{ +	struct drm_device *dev = encoder->base.dev; +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	struct ddi_regoff *ddi_reg; +	int vswing, premph, index; + +	if (intel_dp->output_reg == DP_B) +		ddi_reg = &ddi_DP_train_table[0]; +	else +		ddi_reg = &ddi_DP_train_table[1]; + +	vswing = (signal_level & DP_TRAIN_VOLTAGE_SWING_MASK); +	premph = ((signal_level & DP_TRAIN_PRE_EMPHASIS_MASK)) >> +				DP_TRAIN_PRE_EMPHASIS_SHIFT; + +	if (vswing + premph > 3) +		return; +#ifdef CDV_FAST_LINK_TRAIN +	return; +#endif +	DRM_DEBUG_KMS("Test2\n"); +	//return ; +	cdv_sb_reset(dev); +	/* ;Swing voltage programming +        ;gfx_dpio_set_reg(0xc058, 0x0505313A) */ +	cdv_sb_write(dev, ddi_reg->VSwing5, 0x0505313A); + +	/* ;gfx_dpio_set_reg(0x8154, 0x43406055) */ +	cdv_sb_write(dev, ddi_reg->VSwing1, 0x43406055); + +	/* ;gfx_dpio_set_reg(0x8148, 0x55338954) +	 * The VSwing_PreEmph table is also considered based on the vswing/premp +	 */ +	index = (vswing + premph) * 2; +	if (premph == 1 && vswing == 1) { +		cdv_sb_write(dev, ddi_reg->VSwing2, 0x055738954); +	} else +		cdv_sb_write(dev, ddi_reg->VSwing2, dp_vswing_premph_table[index]); + +	/* ;gfx_dpio_set_reg(0x814c, 0x40802040) */ +	if ((vswing + premph) == DP_TRAIN_VOLTAGE_SWING_1200) +		cdv_sb_write(dev, ddi_reg->VSwing3, 0x70802040); +	else +		cdv_sb_write(dev, ddi_reg->VSwing3, 0x40802040); + +	/* ;gfx_dpio_set_reg(0x8150, 0x2b405555) */ +	/* cdv_sb_write(dev, ddi_reg->VSwing4, 0x2b405555); */ + +	/* ;gfx_dpio_set_reg(0x8154, 0xc3406055) */ +	cdv_sb_write(dev, ddi_reg->VSwing1, 0xc3406055); + +	/* ;Pre emphasis programming +	 * ;gfx_dpio_set_reg(0xc02c, 0x1f030040) +	 */ +	cdv_sb_write(dev, ddi_reg->PreEmph1, 0x1f030040); + +	/* ;gfx_dpio_set_reg(0x8124, 0x00004000) */ +	index = 2 * premph + 1; +	cdv_sb_write(dev, ddi_reg->PreEmph2, dp_vswing_premph_table[index]); +	return;	 +} + + +/* Enable corresponding port and start training pattern 1 */ +static void +cdv_intel_dp_start_link_train(struct gma_encoder *encoder) +{ +	struct drm_device *dev = encoder->base.dev; +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	int i; +	uint8_t voltage; +	bool clock_recovery = false; +	int tries; +	u32 reg; +	uint32_t DP = intel_dp->DP; + +	DP |= DP_PORT_EN; +	DP &= ~DP_LINK_TRAIN_MASK; +		 +	reg = DP;	 +	reg |= DP_LINK_TRAIN_PAT_1; +	/* Enable output, wait for it to become active */ +	REG_WRITE(intel_dp->output_reg, reg); +	REG_READ(intel_dp->output_reg); +	gma_wait_for_vblank(dev); + +	DRM_DEBUG_KMS("Link config\n"); +	/* Write the link configuration data */ +	cdv_intel_dp_aux_native_write(encoder, DP_LINK_BW_SET, +				  intel_dp->link_configuration, +				  2); + +	memset(intel_dp->train_set, 0, 4); +	voltage = 0; +	tries = 0; +	clock_recovery = false; + +	DRM_DEBUG_KMS("Start train\n"); +		reg = DP | DP_LINK_TRAIN_PAT_1; + + +	for (;;) { +		/* Use intel_dp->train_set[0] to set the voltage and pre emphasis values */ +		DRM_DEBUG_KMS("DP Link Train Set %x, Link_config %x, %x\n", +				intel_dp->train_set[0], +				intel_dp->link_configuration[0], +				intel_dp->link_configuration[1]); + +		if (!cdv_intel_dp_set_link_train(encoder, reg, DP_TRAINING_PATTERN_1)) { +			DRM_DEBUG_KMS("Failure in aux-transfer setting pattern 1\n"); +		} +		cdv_intel_dp_set_vswing_premph(encoder, intel_dp->train_set[0]); +		/* Set training pattern 1 */ + +		cdv_intel_dplink_set_level(encoder, DP_TRAINING_PATTERN_1); + +		udelay(200); +		if (!cdv_intel_dp_get_link_status(encoder)) +			break; + +		DRM_DEBUG_KMS("DP Link status %x, %x, %x, %x, %x, %x\n", +				intel_dp->link_status[0], intel_dp->link_status[1], intel_dp->link_status[2], +				intel_dp->link_status[3], intel_dp->link_status[4], intel_dp->link_status[5]); + +		if (cdv_intel_clock_recovery_ok(intel_dp->link_status, intel_dp->lane_count)) { +			DRM_DEBUG_KMS("PT1 train is done\n"); +			clock_recovery = true; +			break; +		} + +		/* Check to see if we've tried the max voltage */ +		for (i = 0; i < intel_dp->lane_count; i++) +			if ((intel_dp->train_set[i] & DP_TRAIN_MAX_SWING_REACHED) == 0) +				break; +		if (i == intel_dp->lane_count) +			break; + +		/* Check to see if we've tried the same voltage 5 times */ +		if ((intel_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == voltage) { +			++tries; +			if (tries == 5) +				break; +		} else +			tries = 0; +		voltage = intel_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK; + +		/* Compute new intel_dp->train_set as requested by target */ +		cdv_intel_get_adjust_train(encoder); + +	} + +	if (!clock_recovery) { +		DRM_DEBUG_KMS("failure in DP patter 1 training, train set %x\n", intel_dp->train_set[0]); +	} +	 +	intel_dp->DP = DP; +} + +static void +cdv_intel_dp_complete_link_train(struct gma_encoder *encoder) +{ +	struct drm_device *dev = encoder->base.dev; +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	bool channel_eq = false; +	int tries, cr_tries; +	u32 reg; +	uint32_t DP = intel_dp->DP; + +	/* channel equalization */ +	tries = 0; +	cr_tries = 0; +	channel_eq = false; + +	DRM_DEBUG_KMS("\n"); +		reg = DP | DP_LINK_TRAIN_PAT_2; + +	for (;;) { + +		DRM_DEBUG_KMS("DP Link Train Set %x, Link_config %x, %x\n", +				intel_dp->train_set[0], +				intel_dp->link_configuration[0], +				intel_dp->link_configuration[1]); +        	/* channel eq pattern */ + +		if (!cdv_intel_dp_set_link_train(encoder, reg, +					     DP_TRAINING_PATTERN_2)) { +			DRM_DEBUG_KMS("Failure in aux-transfer setting pattern 2\n"); +		} +		/* Use intel_dp->train_set[0] to set the voltage and pre emphasis values */ + +		if (cr_tries > 5) { +			DRM_ERROR("failed to train DP, aborting\n"); +			cdv_intel_dp_link_down(encoder); +			break; +		} + +		cdv_intel_dp_set_vswing_premph(encoder, intel_dp->train_set[0]); + +		cdv_intel_dplink_set_level(encoder, DP_TRAINING_PATTERN_2); + +		udelay(1000); +		if (!cdv_intel_dp_get_link_status(encoder)) +			break; + +		DRM_DEBUG_KMS("DP Link status %x, %x, %x, %x, %x, %x\n", +				intel_dp->link_status[0], intel_dp->link_status[1], intel_dp->link_status[2], +				intel_dp->link_status[3], intel_dp->link_status[4], intel_dp->link_status[5]); + +		/* Make sure clock is still ok */ +		if (!cdv_intel_clock_recovery_ok(intel_dp->link_status, intel_dp->lane_count)) { +			cdv_intel_dp_start_link_train(encoder); +			cr_tries++; +			continue; +		} + +		if (cdv_intel_channel_eq_ok(encoder)) { +			DRM_DEBUG_KMS("PT2 train is done\n"); +			channel_eq = true; +			break; +		} + +		/* Try 5 times, then try clock recovery if that fails */ +		if (tries > 5) { +			cdv_intel_dp_link_down(encoder); +			cdv_intel_dp_start_link_train(encoder); +			tries = 0; +			cr_tries++; +			continue; +		} + +		/* Compute new intel_dp->train_set as requested by target */ +		cdv_intel_get_adjust_train(encoder); +		++tries; + +	} + +	reg = DP | DP_LINK_TRAIN_OFF; + +	REG_WRITE(intel_dp->output_reg, reg); +	REG_READ(intel_dp->output_reg); +	cdv_intel_dp_aux_native_write_1(encoder, +				    DP_TRAINING_PATTERN_SET, DP_TRAINING_PATTERN_DISABLE); +} + +static void +cdv_intel_dp_link_down(struct gma_encoder *encoder) +{ +	struct drm_device *dev = encoder->base.dev; +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	uint32_t DP = intel_dp->DP; + +	if ((REG_READ(intel_dp->output_reg) & DP_PORT_EN) == 0) +		return; + +	DRM_DEBUG_KMS("\n"); + + +	{ +		DP &= ~DP_LINK_TRAIN_MASK; +		REG_WRITE(intel_dp->output_reg, DP | DP_LINK_TRAIN_PAT_IDLE); +	} +	REG_READ(intel_dp->output_reg); + +	msleep(17); + +	REG_WRITE(intel_dp->output_reg, DP & ~DP_PORT_EN); +	REG_READ(intel_dp->output_reg); +} + +static enum drm_connector_status cdv_dp_detect(struct gma_encoder *encoder) +{ +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	enum drm_connector_status status; + +	status = connector_status_disconnected; +	if (cdv_intel_dp_aux_native_read(encoder, 0x000, intel_dp->dpcd, +				     sizeof (intel_dp->dpcd)) == sizeof (intel_dp->dpcd)) +	{ +		if (intel_dp->dpcd[DP_DPCD_REV] != 0) +			status = connector_status_connected; +	} +	if (status == connector_status_connected) +		DRM_DEBUG_KMS("DPCD: Rev=%x LN_Rate=%x LN_CNT=%x LN_DOWNSP=%x\n", +			intel_dp->dpcd[0], intel_dp->dpcd[1], +			intel_dp->dpcd[2], intel_dp->dpcd[3]); +	return status; +} + +/** + * Uses CRT_HOTPLUG_EN and CRT_HOTPLUG_STAT to detect DP connection. + * + * \return true if DP port is connected. + * \return false if DP port is disconnected. + */ +static enum drm_connector_status +cdv_intel_dp_detect(struct drm_connector *connector, bool force) +{ +	struct gma_encoder *encoder = gma_attached_encoder(connector); +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	enum drm_connector_status status; +	struct edid *edid = NULL; +	int edp = is_edp(encoder); + +	intel_dp->has_audio = false; + +	if (edp) +		cdv_intel_edp_panel_vdd_on(encoder); +	status = cdv_dp_detect(encoder); +	if (status != connector_status_connected) { +		if (edp) +			cdv_intel_edp_panel_vdd_off(encoder); +		return status; +        } + +	if (intel_dp->force_audio) { +		intel_dp->has_audio = intel_dp->force_audio > 0; +	} else { +		edid = drm_get_edid(connector, &intel_dp->adapter); +		if (edid) { +			intel_dp->has_audio = drm_detect_monitor_audio(edid); +			kfree(edid); +		} +	} +	if (edp) +		cdv_intel_edp_panel_vdd_off(encoder); + +	return connector_status_connected; +} + +static int cdv_intel_dp_get_modes(struct drm_connector *connector) +{ +	struct gma_encoder *intel_encoder = gma_attached_encoder(connector); +	struct cdv_intel_dp *intel_dp = intel_encoder->dev_priv; +	struct edid *edid = NULL; +	int ret = 0; +	int edp = is_edp(intel_encoder); + + +	edid = drm_get_edid(connector, &intel_dp->adapter); +	if (edid) { +		drm_mode_connector_update_edid_property(connector, edid); +		ret = drm_add_edid_modes(connector, edid); +		kfree(edid); +	} + +	if (is_edp(intel_encoder)) { +		struct drm_device *dev = connector->dev; +		struct drm_psb_private *dev_priv = dev->dev_private; +		 +		cdv_intel_edp_panel_vdd_off(intel_encoder); +		if (ret) { +			if (edp && !intel_dp->panel_fixed_mode) { +				struct drm_display_mode *newmode; +				list_for_each_entry(newmode, &connector->probed_modes, +					    head) { +					if (newmode->type & DRM_MODE_TYPE_PREFERRED) { +						intel_dp->panel_fixed_mode = +							drm_mode_duplicate(dev, newmode); +						break; +					} +				} +			} + +			return ret; +		} +		if (!intel_dp->panel_fixed_mode && dev_priv->lfp_lvds_vbt_mode) { +			intel_dp->panel_fixed_mode = +				drm_mode_duplicate(dev, dev_priv->lfp_lvds_vbt_mode); +			if (intel_dp->panel_fixed_mode) { +				intel_dp->panel_fixed_mode->type |= +					DRM_MODE_TYPE_PREFERRED; +			} +		} +		if (intel_dp->panel_fixed_mode != NULL) { +			struct drm_display_mode *mode; +			mode = drm_mode_duplicate(dev, intel_dp->panel_fixed_mode); +			drm_mode_probed_add(connector, mode); +			return 1; +		} +	} + +	return ret; +} + +static bool +cdv_intel_dp_detect_audio(struct drm_connector *connector) +{ +	struct gma_encoder *encoder = gma_attached_encoder(connector); +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	struct edid *edid; +	bool has_audio = false; +	int edp = is_edp(encoder); + +	if (edp) +		cdv_intel_edp_panel_vdd_on(encoder); + +	edid = drm_get_edid(connector, &intel_dp->adapter); +	if (edid) { +		has_audio = drm_detect_monitor_audio(edid); +		kfree(edid); +	} +	if (edp) +		cdv_intel_edp_panel_vdd_off(encoder); + +	return has_audio; +} + +static int +cdv_intel_dp_set_property(struct drm_connector *connector, +		      struct drm_property *property, +		      uint64_t val) +{ +	struct drm_psb_private *dev_priv = connector->dev->dev_private; +	struct gma_encoder *encoder = gma_attached_encoder(connector); +	struct cdv_intel_dp *intel_dp = encoder->dev_priv; +	int ret; + +	ret = drm_object_property_set_value(&connector->base, property, val); +	if (ret) +		return ret; + +	if (property == dev_priv->force_audio_property) { +		int i = val; +		bool has_audio; + +		if (i == intel_dp->force_audio) +			return 0; + +		intel_dp->force_audio = i; + +		if (i == 0) +			has_audio = cdv_intel_dp_detect_audio(connector); +		else +			has_audio = i > 0; + +		if (has_audio == intel_dp->has_audio) +			return 0; + +		intel_dp->has_audio = has_audio; +		goto done; +	} + +	if (property == dev_priv->broadcast_rgb_property) { +		if (val == !!intel_dp->color_range) +			return 0; + +		intel_dp->color_range = val ? DP_COLOR_RANGE_16_235 : 0; +		goto done; +	} + +	return -EINVAL; + +done: +	if (encoder->base.crtc) { +		struct drm_crtc *crtc = encoder->base.crtc; +		drm_crtc_helper_set_mode(crtc, &crtc->mode, +					 crtc->x, crtc->y, +					 crtc->primary->fb); +	} + +	return 0; +} + +static void +cdv_intel_dp_destroy(struct drm_connector *connector) +{ +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct cdv_intel_dp *intel_dp = gma_encoder->dev_priv; + +	if (is_edp(gma_encoder)) { +	/*	cdv_intel_panel_destroy_backlight(connector->dev); */ +		if (intel_dp->panel_fixed_mode) { +			kfree(intel_dp->panel_fixed_mode); +			intel_dp->panel_fixed_mode = NULL; +		} +	} +	i2c_del_adapter(&intel_dp->adapter); +	drm_sysfs_connector_remove(connector); +	drm_connector_cleanup(connector); +	kfree(connector); +} + +static void cdv_intel_dp_encoder_destroy(struct drm_encoder *encoder) +{ +	drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_helper_funcs cdv_intel_dp_helper_funcs = { +	.dpms = cdv_intel_dp_dpms, +	.mode_fixup = cdv_intel_dp_mode_fixup, +	.prepare = cdv_intel_dp_prepare, +	.mode_set = cdv_intel_dp_mode_set, +	.commit = cdv_intel_dp_commit, +}; + +static const struct drm_connector_funcs cdv_intel_dp_connector_funcs = { +	.dpms = drm_helper_connector_dpms, +	.detect = cdv_intel_dp_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.set_property = cdv_intel_dp_set_property, +	.destroy = cdv_intel_dp_destroy, +}; + +static const struct drm_connector_helper_funcs cdv_intel_dp_connector_helper_funcs = { +	.get_modes = cdv_intel_dp_get_modes, +	.mode_valid = cdv_intel_dp_mode_valid, +	.best_encoder = gma_best_encoder, +}; + +static const struct drm_encoder_funcs cdv_intel_dp_enc_funcs = { +	.destroy = cdv_intel_dp_encoder_destroy, +}; + + +static void cdv_intel_dp_add_properties(struct drm_connector *connector) +{ +	cdv_intel_attach_force_audio_property(connector); +	cdv_intel_attach_broadcast_rgb_property(connector); +} + +/* check the VBT to see whether the eDP is on DP-D port */ +static bool cdv_intel_dpc_is_edp(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct child_device_config *p_child; +	int i; + +	if (!dev_priv->child_dev_num) +		return false; + +	for (i = 0; i < dev_priv->child_dev_num; i++) { +		p_child = dev_priv->child_dev + i; + +		if (p_child->dvo_port == PORT_IDPC && +		    p_child->device_type == DEVICE_TYPE_eDP) +			return true; +	} +	return false; +} + +/* Cedarview display clock gating + +   We need this disable dot get correct behaviour while enabling +   DP/eDP. TODO - investigate if we can turn it back to normality +   after enabling */ +static void cdv_disable_intel_clock_gating(struct drm_device *dev) +{ +	u32 reg_value; +	reg_value = REG_READ(DSPCLK_GATE_D); + +	reg_value |= (DPUNIT_PIPEB_GATE_DISABLE | +			DPUNIT_PIPEA_GATE_DISABLE | +			DPCUNIT_CLOCK_GATE_DISABLE | +			DPLSUNIT_CLOCK_GATE_DISABLE | +			DPOUNIT_CLOCK_GATE_DISABLE | +		 	DPIOUNIT_CLOCK_GATE_DISABLE);	 + +	REG_WRITE(DSPCLK_GATE_D, reg_value); + +	udelay(500);		 +} + +void +cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev, int output_reg) +{ +	struct gma_encoder *gma_encoder; +	struct gma_connector *gma_connector; +	struct drm_connector *connector; +	struct drm_encoder *encoder; +	struct cdv_intel_dp *intel_dp; +	const char *name = NULL; +	int type = DRM_MODE_CONNECTOR_DisplayPort; + +	gma_encoder = kzalloc(sizeof(struct gma_encoder), GFP_KERNEL); +	if (!gma_encoder) +		return; +        gma_connector = kzalloc(sizeof(struct gma_connector), GFP_KERNEL); +        if (!gma_connector) +                goto err_connector; +	intel_dp = kzalloc(sizeof(struct cdv_intel_dp), GFP_KERNEL); +	if (!intel_dp) +	        goto err_priv; + +	if ((output_reg == DP_C) && cdv_intel_dpc_is_edp(dev)) +		type = DRM_MODE_CONNECTOR_eDP; + +	connector = &gma_connector->base; +	encoder = &gma_encoder->base; + +	drm_connector_init(dev, connector, &cdv_intel_dp_connector_funcs, type); +	drm_encoder_init(dev, encoder, &cdv_intel_dp_enc_funcs, DRM_MODE_ENCODER_TMDS); + +	gma_connector_attach_encoder(gma_connector, gma_encoder); + +	if (type == DRM_MODE_CONNECTOR_DisplayPort) +		gma_encoder->type = INTEL_OUTPUT_DISPLAYPORT; +        else +		gma_encoder->type = INTEL_OUTPUT_EDP; + + +	gma_encoder->dev_priv=intel_dp; +	intel_dp->encoder = gma_encoder; +	intel_dp->output_reg = output_reg; +	 +	drm_encoder_helper_add(encoder, &cdv_intel_dp_helper_funcs); +	drm_connector_helper_add(connector, &cdv_intel_dp_connector_helper_funcs); + +	connector->polled = DRM_CONNECTOR_POLL_HPD; +	connector->interlace_allowed = false; +	connector->doublescan_allowed = false; + +	drm_sysfs_connector_add(connector); + +	/* Set up the DDC bus. */ +	switch (output_reg) { +		case DP_B: +			name = "DPDDC-B"; +			gma_encoder->ddi_select = (DP_MASK | DDI0_SELECT); +			break; +		case DP_C: +			name = "DPDDC-C"; +			gma_encoder->ddi_select = (DP_MASK | DDI1_SELECT); +			break; +	} + +	cdv_disable_intel_clock_gating(dev); + +	cdv_intel_dp_i2c_init(gma_connector, gma_encoder, name); +        /* FIXME:fail check */ +	cdv_intel_dp_add_properties(connector); + +	if (is_edp(gma_encoder)) { +		int ret; +		struct edp_power_seq cur; +                u32 pp_on, pp_off, pp_div; +		u32 pwm_ctrl; + +		pp_on = REG_READ(PP_CONTROL); +		pp_on &= ~PANEL_UNLOCK_MASK; +	        pp_on |= PANEL_UNLOCK_REGS; +		 +		REG_WRITE(PP_CONTROL, pp_on); + +		pwm_ctrl = REG_READ(BLC_PWM_CTL2); +		pwm_ctrl |= PWM_PIPE_B; +		REG_WRITE(BLC_PWM_CTL2, pwm_ctrl); + +                pp_on = REG_READ(PP_ON_DELAYS); +                pp_off = REG_READ(PP_OFF_DELAYS); +                pp_div = REG_READ(PP_DIVISOR); +	 +		/* Pull timing values out of registers */ +                cur.t1_t3 = (pp_on & PANEL_POWER_UP_DELAY_MASK) >> +                        PANEL_POWER_UP_DELAY_SHIFT; + +                cur.t8 = (pp_on & PANEL_LIGHT_ON_DELAY_MASK) >> +                        PANEL_LIGHT_ON_DELAY_SHIFT; + +                cur.t9 = (pp_off & PANEL_LIGHT_OFF_DELAY_MASK) >> +                        PANEL_LIGHT_OFF_DELAY_SHIFT; + +                cur.t10 = (pp_off & PANEL_POWER_DOWN_DELAY_MASK) >> +                        PANEL_POWER_DOWN_DELAY_SHIFT; + +                cur.t11_t12 = ((pp_div & PANEL_POWER_CYCLE_DELAY_MASK) >> +                               PANEL_POWER_CYCLE_DELAY_SHIFT); + +                DRM_DEBUG_KMS("cur t1_t3 %d t8 %d t9 %d t10 %d t11_t12 %d\n", +                              cur.t1_t3, cur.t8, cur.t9, cur.t10, cur.t11_t12); + + +		intel_dp->panel_power_up_delay = cur.t1_t3 / 10; +                intel_dp->backlight_on_delay = cur.t8 / 10; +                intel_dp->backlight_off_delay = cur.t9 / 10; +                intel_dp->panel_power_down_delay = cur.t10 / 10; +                intel_dp->panel_power_cycle_delay = (cur.t11_t12 - 1) * 100; + +                DRM_DEBUG_KMS("panel power up delay %d, power down delay %d, power cycle delay %d\n", +                              intel_dp->panel_power_up_delay, intel_dp->panel_power_down_delay, +                              intel_dp->panel_power_cycle_delay); + +                DRM_DEBUG_KMS("backlight on delay %d, off delay %d\n", +                              intel_dp->backlight_on_delay, intel_dp->backlight_off_delay); + + +		cdv_intel_edp_panel_vdd_on(gma_encoder); +		ret = cdv_intel_dp_aux_native_read(gma_encoder, DP_DPCD_REV, +					       intel_dp->dpcd, +					       sizeof(intel_dp->dpcd)); +		cdv_intel_edp_panel_vdd_off(gma_encoder); +		if (ret == 0) { +			/* if this fails, presume the device is a ghost */ +			DRM_INFO("failed to retrieve link info, disabling eDP\n"); +			cdv_intel_dp_encoder_destroy(encoder); +			cdv_intel_dp_destroy(connector); +			goto err_priv; +		} else { +        		DRM_DEBUG_KMS("DPCD: Rev=%x LN_Rate=%x LN_CNT=%x LN_DOWNSP=%x\n", +				intel_dp->dpcd[0], intel_dp->dpcd[1],  +				intel_dp->dpcd[2], intel_dp->dpcd[3]); +			 +		} +		/* The CDV reference driver moves pnale backlight setup into the displays that +		   have a backlight: this is a good idea and one we should probably adopt, however +		   we need to migrate all the drivers before we can do that */ +                /*cdv_intel_panel_setup_backlight(dev); */ +	} +	return; + +err_priv: +	kfree(gma_connector); +err_connector: +	kfree(gma_encoder); +} diff --git a/drivers/gpu/drm/gma500/cdv_intel_hdmi.c b/drivers/gpu/drm/gma500/cdv_intel_hdmi.c new file mode 100644 index 00000000000..b99084b3f70 --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_intel_hdmi.c @@ -0,0 +1,369 @@ +/* + * Copyright © 2006-2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + *	jim liu <jim.liu@intel.com> + * + * FIXME: + *	We should probably make this generic and share it with Medfield + */ + +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> +#include "psb_intel_drv.h" +#include "psb_drv.h" +#include "psb_intel_reg.h" +#include "cdv_device.h" +#include <linux/pm_runtime.h> + +/* hdmi control bits */ +#define HDMI_NULL_PACKETS_DURING_VSYNC	(1 << 9) +#define HDMI_BORDER_ENABLE		(1 << 7) +#define HDMI_AUDIO_ENABLE		(1 << 6) +#define HDMI_VSYNC_ACTIVE_HIGH		(1 << 4) +#define HDMI_HSYNC_ACTIVE_HIGH		(1 << 3) +/* hdmi-b control bits */ +#define	HDMIB_PIPE_B_SELECT		(1 << 30) + + +struct mid_intel_hdmi_priv { +	u32 hdmi_reg; +	u32 save_HDMIB; +	bool has_hdmi_sink; +	bool has_hdmi_audio; +	/* Should set this when detect hotplug */ +	bool hdmi_device_connected; +	struct mdfld_hdmi_i2c *i2c_bus; +	struct i2c_adapter *hdmi_i2c_adapter;	/* for control functions */ +	struct drm_device *dev; +}; + +static void cdv_hdmi_mode_set(struct drm_encoder *encoder, +			struct drm_display_mode *mode, +			struct drm_display_mode *adjusted_mode) +{ +	struct drm_device *dev = encoder->dev; +	struct gma_encoder *gma_encoder = to_gma_encoder(encoder); +	struct mid_intel_hdmi_priv *hdmi_priv = gma_encoder->dev_priv; +	u32 hdmib; +	struct drm_crtc *crtc = encoder->crtc; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); + +	hdmib = (2 << 10); + +	if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) +		hdmib |= HDMI_VSYNC_ACTIVE_HIGH; +	if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) +		hdmib |= HDMI_HSYNC_ACTIVE_HIGH; + +	if (gma_crtc->pipe == 1) +		hdmib |= HDMIB_PIPE_B_SELECT; + +	if (hdmi_priv->has_hdmi_audio) { +		hdmib |= HDMI_AUDIO_ENABLE; +		hdmib |= HDMI_NULL_PACKETS_DURING_VSYNC; +	} + +	REG_WRITE(hdmi_priv->hdmi_reg, hdmib); +	REG_READ(hdmi_priv->hdmi_reg); +} + +static void cdv_hdmi_dpms(struct drm_encoder *encoder, int mode) +{ +	struct drm_device *dev = encoder->dev; +	struct gma_encoder *gma_encoder = to_gma_encoder(encoder); +	struct mid_intel_hdmi_priv *hdmi_priv = gma_encoder->dev_priv; +	u32 hdmib; + +	hdmib = REG_READ(hdmi_priv->hdmi_reg); + +	if (mode != DRM_MODE_DPMS_ON) +		REG_WRITE(hdmi_priv->hdmi_reg, hdmib & ~HDMIB_PORT_EN); +	else +		REG_WRITE(hdmi_priv->hdmi_reg, hdmib | HDMIB_PORT_EN); +	REG_READ(hdmi_priv->hdmi_reg); +} + +static void cdv_hdmi_save(struct drm_connector *connector) +{ +	struct drm_device *dev = connector->dev; +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct mid_intel_hdmi_priv *hdmi_priv = gma_encoder->dev_priv; + +	hdmi_priv->save_HDMIB = REG_READ(hdmi_priv->hdmi_reg); +} + +static void cdv_hdmi_restore(struct drm_connector *connector) +{ +	struct drm_device *dev = connector->dev; +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct mid_intel_hdmi_priv *hdmi_priv = gma_encoder->dev_priv; + +	REG_WRITE(hdmi_priv->hdmi_reg, hdmi_priv->save_HDMIB); +	REG_READ(hdmi_priv->hdmi_reg); +} + +static enum drm_connector_status cdv_hdmi_detect( +				struct drm_connector *connector, bool force) +{ +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct mid_intel_hdmi_priv *hdmi_priv = gma_encoder->dev_priv; +	struct edid *edid = NULL; +	enum drm_connector_status status = connector_status_disconnected; + +	edid = drm_get_edid(connector, &gma_encoder->i2c_bus->adapter); + +	hdmi_priv->has_hdmi_sink = false; +	hdmi_priv->has_hdmi_audio = false; +	if (edid) { +		if (edid->input & DRM_EDID_INPUT_DIGITAL) { +			status = connector_status_connected; +			hdmi_priv->has_hdmi_sink = +						drm_detect_hdmi_monitor(edid); +			hdmi_priv->has_hdmi_audio = +						drm_detect_monitor_audio(edid); +		} +		kfree(edid); +	} +	return status; +} + +static int cdv_hdmi_set_property(struct drm_connector *connector, +				       struct drm_property *property, +				       uint64_t value) +{ +	struct drm_encoder *encoder = connector->encoder; + +	if (!strcmp(property->name, "scaling mode") && encoder) { +		struct gma_crtc *crtc = to_gma_crtc(encoder->crtc); +		bool centre; +		uint64_t curValue; + +		if (!crtc) +			return -1; + +		switch (value) { +		case DRM_MODE_SCALE_FULLSCREEN: +			break; +		case DRM_MODE_SCALE_NO_SCALE: +			break; +		case DRM_MODE_SCALE_ASPECT: +			break; +		default: +			return -1; +		} + +		if (drm_object_property_get_value(&connector->base, +							property, &curValue)) +			return -1; + +		if (curValue == value) +			return 0; + +		if (drm_object_property_set_value(&connector->base, +							property, value)) +			return -1; + +		centre = (curValue == DRM_MODE_SCALE_NO_SCALE) || +			(value == DRM_MODE_SCALE_NO_SCALE); + +		if (crtc->saved_mode.hdisplay != 0 && +		    crtc->saved_mode.vdisplay != 0) { +			if (centre) { +				if (!drm_crtc_helper_set_mode(encoder->crtc, &crtc->saved_mode, +					    encoder->crtc->x, encoder->crtc->y, encoder->crtc->primary->fb)) +					return -1; +			} else { +				struct drm_encoder_helper_funcs *helpers +						    = encoder->helper_private; +				helpers->mode_set(encoder, &crtc->saved_mode, +					     &crtc->saved_adjusted_mode); +			} +		} +	} +	return 0; +} + +/* + * Return the list of HDMI DDC modes if available. + */ +static int cdv_hdmi_get_modes(struct drm_connector *connector) +{ +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct edid *edid = NULL; +	int ret = 0; + +	edid = drm_get_edid(connector, &gma_encoder->i2c_bus->adapter); +	if (edid) { +		drm_mode_connector_update_edid_property(connector, edid); +		ret = drm_add_edid_modes(connector, edid); +		kfree(edid); +	} +	return ret; +} + +static int cdv_hdmi_mode_valid(struct drm_connector *connector, +				 struct drm_display_mode *mode) +{ +	if (mode->clock > 165000) +		return MODE_CLOCK_HIGH; +	if (mode->clock < 20000) +		return MODE_CLOCK_HIGH; + +	/* just in case */ +	if (mode->flags & DRM_MODE_FLAG_DBLSCAN) +		return MODE_NO_DBLESCAN; + +	/* just in case */ +	if (mode->flags & DRM_MODE_FLAG_INTERLACE) +		return MODE_NO_INTERLACE; + +	return MODE_OK; +} + +static void cdv_hdmi_destroy(struct drm_connector *connector) +{ +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); + +	if (gma_encoder->i2c_bus) +		psb_intel_i2c_destroy(gma_encoder->i2c_bus); +	drm_sysfs_connector_remove(connector); +	drm_connector_cleanup(connector); +	kfree(connector); +} + +static const struct drm_encoder_helper_funcs cdv_hdmi_helper_funcs = { +	.dpms = cdv_hdmi_dpms, +	.mode_fixup = gma_encoder_mode_fixup, +	.prepare = gma_encoder_prepare, +	.mode_set = cdv_hdmi_mode_set, +	.commit = gma_encoder_commit, +}; + +static const struct drm_connector_helper_funcs +					cdv_hdmi_connector_helper_funcs = { +	.get_modes = cdv_hdmi_get_modes, +	.mode_valid = cdv_hdmi_mode_valid, +	.best_encoder = gma_best_encoder, +}; + +static const struct drm_connector_funcs cdv_hdmi_connector_funcs = { +	.dpms = drm_helper_connector_dpms, +	.save = cdv_hdmi_save, +	.restore = cdv_hdmi_restore, +	.detect = cdv_hdmi_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.set_property = cdv_hdmi_set_property, +	.destroy = cdv_hdmi_destroy, +}; + +void cdv_hdmi_init(struct drm_device *dev, +			struct psb_intel_mode_device *mode_dev, int reg) +{ +	struct gma_encoder *gma_encoder; +	struct gma_connector *gma_connector; +	struct drm_connector *connector; +	struct drm_encoder *encoder; +	struct mid_intel_hdmi_priv *hdmi_priv; +	int ddc_bus; + +	gma_encoder = kzalloc(sizeof(struct gma_encoder), GFP_KERNEL); + +	if (!gma_encoder) +		return; + +	gma_connector = kzalloc(sizeof(struct gma_connector), +				      GFP_KERNEL); + +	if (!gma_connector) +		goto err_connector; + +	hdmi_priv = kzalloc(sizeof(struct mid_intel_hdmi_priv), GFP_KERNEL); + +	if (!hdmi_priv) +		goto err_priv; + +	connector = &gma_connector->base; +	connector->polled = DRM_CONNECTOR_POLL_HPD; +	encoder = &gma_encoder->base; +	drm_connector_init(dev, connector, +			   &cdv_hdmi_connector_funcs, +			   DRM_MODE_CONNECTOR_DVID); + +	drm_encoder_init(dev, encoder, &psb_intel_lvds_enc_funcs, +			 DRM_MODE_ENCODER_TMDS); + +	gma_connector_attach_encoder(gma_connector, gma_encoder); +	gma_encoder->type = INTEL_OUTPUT_HDMI; +	hdmi_priv->hdmi_reg = reg; +	hdmi_priv->has_hdmi_sink = false; +	gma_encoder->dev_priv = hdmi_priv; + +	drm_encoder_helper_add(encoder, &cdv_hdmi_helper_funcs); +	drm_connector_helper_add(connector, +				 &cdv_hdmi_connector_helper_funcs); +	connector->display_info.subpixel_order = SubPixelHorizontalRGB; +	connector->interlace_allowed = false; +	connector->doublescan_allowed = false; + +	drm_object_attach_property(&connector->base, +				      dev->mode_config.scaling_mode_property, +				      DRM_MODE_SCALE_FULLSCREEN); + +	switch (reg) { +	case SDVOB: +		ddc_bus = GPIOE; +		gma_encoder->ddi_select = DDI0_SELECT; +		break; +	case SDVOC: +		ddc_bus = GPIOD; +		gma_encoder->ddi_select = DDI1_SELECT; +		break; +	default: +		DRM_ERROR("unknown reg 0x%x for HDMI\n", reg); +		goto failed_ddc; +		break; +	} + +	gma_encoder->i2c_bus = psb_intel_i2c_create(dev, +				ddc_bus, (reg == SDVOB) ? "HDMIB" : "HDMIC"); + +	if (!gma_encoder->i2c_bus) { +		dev_err(dev->dev, "No ddc adapter available!\n"); +		goto failed_ddc; +	} + +	hdmi_priv->hdmi_i2c_adapter = &(gma_encoder->i2c_bus->adapter); +	hdmi_priv->dev = dev; +	drm_sysfs_connector_add(connector); +	return; + +failed_ddc: +	drm_encoder_cleanup(encoder); +	drm_connector_cleanup(connector); +err_priv: +	kfree(gma_connector); +err_connector: +	kfree(gma_encoder); +} diff --git a/drivers/gpu/drm/gma500/cdv_intel_lvds.c b/drivers/gpu/drm/gma500/cdv_intel_lvds.c new file mode 100644 index 00000000000..8ecc920fc26 --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_intel_lvds.c @@ -0,0 +1,798 @@ +/* + * Copyright © 2006-2011 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + *	Dave Airlie <airlied@linux.ie> + *	Jesse Barnes <jesse.barnes@intel.com> + */ + +#include <linux/i2c.h> +#include <linux/dmi.h> +#include <drm/drmP.h> + +#include "intel_bios.h" +#include "psb_drv.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "power.h" +#include <linux/pm_runtime.h> +#include "cdv_device.h" + +/** + * LVDS I2C backlight control macros + */ +#define BRIGHTNESS_MAX_LEVEL 100 +#define BRIGHTNESS_MASK 0xFF +#define BLC_I2C_TYPE	0x01 +#define BLC_PWM_TYPT	0x02 + +#define BLC_POLARITY_NORMAL 0 +#define BLC_POLARITY_INVERSE 1 + +#define PSB_BLC_MAX_PWM_REG_FREQ       (0xFFFE) +#define PSB_BLC_MIN_PWM_REG_FREQ	(0x2) +#define PSB_BLC_PWM_PRECISION_FACTOR	(10) +#define PSB_BACKLIGHT_PWM_CTL_SHIFT	(16) +#define PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR (0xFFFE) + +struct cdv_intel_lvds_priv { +	/** +	 * Saved LVDO output states +	 */ +	uint32_t savePP_ON; +	uint32_t savePP_OFF; +	uint32_t saveLVDS; +	uint32_t savePP_CONTROL; +	uint32_t savePP_CYCLE; +	uint32_t savePFIT_CONTROL; +	uint32_t savePFIT_PGM_RATIOS; +	uint32_t saveBLC_PWM_CTL; +}; + +/* + * Returns the maximum level of the backlight duty cycle field. + */ +static u32 cdv_intel_lvds_get_max_backlight(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 retval; + +	if (gma_power_begin(dev, false)) { +		retval = ((REG_READ(BLC_PWM_CTL) & +			  BACKLIGHT_MODULATION_FREQ_MASK) >> +			  BACKLIGHT_MODULATION_FREQ_SHIFT) * 2; + +		gma_power_end(dev); +	} else +		retval = ((dev_priv->regs.saveBLC_PWM_CTL & +			  BACKLIGHT_MODULATION_FREQ_MASK) >> +			  BACKLIGHT_MODULATION_FREQ_SHIFT) * 2; + +	return retval; +} + +#if 0 +/* + * Set LVDS backlight level by I2C command + */ +static int cdv_lvds_i2c_set_brightness(struct drm_device *dev, +					unsigned int level) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_i2c_chan *lvds_i2c_bus = dev_priv->lvds_i2c_bus; +	u8 out_buf[2]; +	unsigned int blc_i2c_brightness; + +	struct i2c_msg msgs[] = { +		{ +			.addr = lvds_i2c_bus->slave_addr, +			.flags = 0, +			.len = 2, +			.buf = out_buf, +		} +	}; + +	blc_i2c_brightness = BRIGHTNESS_MASK & ((unsigned int)level * +			     BRIGHTNESS_MASK / +			     BRIGHTNESS_MAX_LEVEL); + +	if (dev_priv->lvds_bl->pol == BLC_POLARITY_INVERSE) +		blc_i2c_brightness = BRIGHTNESS_MASK - blc_i2c_brightness; + +	out_buf[0] = dev_priv->lvds_bl->brightnesscmd; +	out_buf[1] = (u8)blc_i2c_brightness; + +	if (i2c_transfer(&lvds_i2c_bus->adapter, msgs, 1) == 1) +		return 0; + +	DRM_ERROR("I2C transfer error\n"); +	return -1; +} + + +static int cdv_lvds_pwm_set_brightness(struct drm_device *dev, int level) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	u32 max_pwm_blc; +	u32 blc_pwm_duty_cycle; + +	max_pwm_blc = cdv_intel_lvds_get_max_backlight(dev); + +	/*BLC_PWM_CTL Should be initiated while backlight device init*/ +	BUG_ON((max_pwm_blc & PSB_BLC_MAX_PWM_REG_FREQ) == 0); + +	blc_pwm_duty_cycle = level * max_pwm_blc / BRIGHTNESS_MAX_LEVEL; + +	if (dev_priv->lvds_bl->pol == BLC_POLARITY_INVERSE) +		blc_pwm_duty_cycle = max_pwm_blc - blc_pwm_duty_cycle; + +	blc_pwm_duty_cycle &= PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR; +	REG_WRITE(BLC_PWM_CTL, +		  (max_pwm_blc << PSB_BACKLIGHT_PWM_CTL_SHIFT) | +		  (blc_pwm_duty_cycle)); + +	return 0; +} + +/* + * Set LVDS backlight level either by I2C or PWM + */ +void cdv_intel_lvds_set_brightness(struct drm_device *dev, int level) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (!dev_priv->lvds_bl) { +		DRM_ERROR("NO LVDS Backlight Info\n"); +		return; +	} + +	if (dev_priv->lvds_bl->type == BLC_I2C_TYPE) +		cdv_lvds_i2c_set_brightness(dev, level); +	else +		cdv_lvds_pwm_set_brightness(dev, level); +} +#endif + +/** + * Sets the backlight level. + * + * level backlight level, from 0 to cdv_intel_lvds_get_max_backlight(). + */ +static void cdv_intel_lvds_set_backlight(struct drm_device *dev, int level) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 blc_pwm_ctl; + +	if (gma_power_begin(dev, false)) { +		blc_pwm_ctl = +			REG_READ(BLC_PWM_CTL) & ~BACKLIGHT_DUTY_CYCLE_MASK; +		REG_WRITE(BLC_PWM_CTL, +				(blc_pwm_ctl | +				(level << BACKLIGHT_DUTY_CYCLE_SHIFT))); +		gma_power_end(dev); +	} else { +		blc_pwm_ctl = dev_priv->regs.saveBLC_PWM_CTL & +				~BACKLIGHT_DUTY_CYCLE_MASK; +		dev_priv->regs.saveBLC_PWM_CTL = (blc_pwm_ctl | +					(level << BACKLIGHT_DUTY_CYCLE_SHIFT)); +	} +} + +/** + * Sets the power state for the panel. + */ +static void cdv_intel_lvds_set_power(struct drm_device *dev, +				     struct drm_encoder *encoder, bool on) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 pp_status; + +	if (!gma_power_begin(dev, true)) +		return; + +	if (on) { +		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) | +			  POWER_TARGET_ON); +		do { +			pp_status = REG_READ(PP_STATUS); +		} while ((pp_status & PP_ON) == 0); + +		cdv_intel_lvds_set_backlight(dev, +				dev_priv->mode_dev.backlight_duty_cycle); +	} else { +		cdv_intel_lvds_set_backlight(dev, 0); + +		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) & +			  ~POWER_TARGET_ON); +		do { +			pp_status = REG_READ(PP_STATUS); +		} while (pp_status & PP_ON); +	} +	gma_power_end(dev); +} + +static void cdv_intel_lvds_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +	struct drm_device *dev = encoder->dev; +	if (mode == DRM_MODE_DPMS_ON) +		cdv_intel_lvds_set_power(dev, encoder, true); +	else +		cdv_intel_lvds_set_power(dev, encoder, false); +	/* XXX: We never power down the LVDS pairs. */ +} + +static void cdv_intel_lvds_save(struct drm_connector *connector) +{ +} + +static void cdv_intel_lvds_restore(struct drm_connector *connector) +{ +} + +static int cdv_intel_lvds_mode_valid(struct drm_connector *connector, +			      struct drm_display_mode *mode) +{ +	struct drm_device *dev = connector->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct drm_display_mode *fixed_mode = +					dev_priv->mode_dev.panel_fixed_mode; + +	/* just in case */ +	if (mode->flags & DRM_MODE_FLAG_DBLSCAN) +		return MODE_NO_DBLESCAN; + +	/* just in case */ +	if (mode->flags & DRM_MODE_FLAG_INTERLACE) +		return MODE_NO_INTERLACE; + +	if (fixed_mode) { +		if (mode->hdisplay > fixed_mode->hdisplay) +			return MODE_PANEL; +		if (mode->vdisplay > fixed_mode->vdisplay) +			return MODE_PANEL; +	} +	return MODE_OK; +} + +static bool cdv_intel_lvds_mode_fixup(struct drm_encoder *encoder, +				  const struct drm_display_mode *mode, +				  struct drm_display_mode *adjusted_mode) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; +	struct drm_encoder *tmp_encoder; +	struct drm_display_mode *panel_fixed_mode = mode_dev->panel_fixed_mode; + +	/* Should never happen!! */ +	list_for_each_entry(tmp_encoder, &dev->mode_config.encoder_list, +			    head) { +		if (tmp_encoder != encoder +		    && tmp_encoder->crtc == encoder->crtc) { +			printk(KERN_ERR "Can't enable LVDS and another " +			       "encoder on the same pipe\n"); +			return false; +		} +	} + +	/* +	 * If we have timings from the BIOS for the panel, put them in +	 * to the adjusted mode.  The CRTC will be set up for this mode, +	 * with the panel scaling set up to source from the H/VDisplay +	 * of the original mode. +	 */ +	if (panel_fixed_mode != NULL) { +		adjusted_mode->hdisplay = panel_fixed_mode->hdisplay; +		adjusted_mode->hsync_start = panel_fixed_mode->hsync_start; +		adjusted_mode->hsync_end = panel_fixed_mode->hsync_end; +		adjusted_mode->htotal = panel_fixed_mode->htotal; +		adjusted_mode->vdisplay = panel_fixed_mode->vdisplay; +		adjusted_mode->vsync_start = panel_fixed_mode->vsync_start; +		adjusted_mode->vsync_end = panel_fixed_mode->vsync_end; +		adjusted_mode->vtotal = panel_fixed_mode->vtotal; +		adjusted_mode->clock = panel_fixed_mode->clock; +		drm_mode_set_crtcinfo(adjusted_mode, +				      CRTC_INTERLACE_HALVE_V); +	} + +	/* +	 * XXX: It would be nice to support lower refresh rates on the +	 * panels to reduce power consumption, and perhaps match the +	 * user's requested refresh rate. +	 */ + +	return true; +} + +static void cdv_intel_lvds_prepare(struct drm_encoder *encoder) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; + +	if (!gma_power_begin(dev, true)) +		return; + +	mode_dev->saveBLC_PWM_CTL = REG_READ(BLC_PWM_CTL); +	mode_dev->backlight_duty_cycle = (mode_dev->saveBLC_PWM_CTL & +					  BACKLIGHT_DUTY_CYCLE_MASK); + +	cdv_intel_lvds_set_power(dev, encoder, false); + +	gma_power_end(dev); +} + +static void cdv_intel_lvds_commit(struct drm_encoder *encoder) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; + +	if (mode_dev->backlight_duty_cycle == 0) +		mode_dev->backlight_duty_cycle = +		    cdv_intel_lvds_get_max_backlight(dev); + +	cdv_intel_lvds_set_power(dev, encoder, true); +} + +static void cdv_intel_lvds_mode_set(struct drm_encoder *encoder, +				struct drm_display_mode *mode, +				struct drm_display_mode *adjusted_mode) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(encoder->crtc); +	u32 pfit_control; + +	/* +	 * The LVDS pin pair will already have been turned on in the +	 * cdv_intel_crtc_mode_set since it has a large impact on the DPLL +	 * settings. +	 */ + +	/* +	 * Enable automatic panel scaling so that non-native modes fill the +	 * screen.  Should be enabled before the pipe is enabled, according to +	 * register description and PRM. +	 */ +	if (mode->hdisplay != adjusted_mode->hdisplay || +	    mode->vdisplay != adjusted_mode->vdisplay) +		pfit_control = (PFIT_ENABLE | VERT_AUTO_SCALE | +				HORIZ_AUTO_SCALE | VERT_INTERP_BILINEAR | +				HORIZ_INTERP_BILINEAR); +	else +		pfit_control = 0; + +	pfit_control |= gma_crtc->pipe << PFIT_PIPE_SHIFT; + +	if (dev_priv->lvds_dither) +		pfit_control |= PANEL_8TO6_DITHER_ENABLE; + +	REG_WRITE(PFIT_CONTROL, pfit_control); +} + +/** + * Detect the LVDS connection. + * + * This always returns CONNECTOR_STATUS_CONNECTED. + * This connector should only have + * been set up if the LVDS was actually connected anyway. + */ +static enum drm_connector_status cdv_intel_lvds_detect( +				struct drm_connector *connector, bool force) +{ +	return connector_status_connected; +} + +/** + * Return the list of DDC modes if available, or the BIOS fixed mode otherwise. + */ +static int cdv_intel_lvds_get_modes(struct drm_connector *connector) +{ +	struct drm_device *dev = connector->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; +	int ret; + +	ret = psb_intel_ddc_get_modes(connector, &gma_encoder->i2c_bus->adapter); + +	if (ret) +		return ret; + +	/* Didn't get an EDID, so +	 * Set wide sync ranges so we get all modes +	 * handed to valid_mode for checking +	 */ +	connector->display_info.min_vfreq = 0; +	connector->display_info.max_vfreq = 200; +	connector->display_info.min_hfreq = 0; +	connector->display_info.max_hfreq = 200; +	if (mode_dev->panel_fixed_mode != NULL) { +		struct drm_display_mode *mode = +		    drm_mode_duplicate(dev, mode_dev->panel_fixed_mode); +		drm_mode_probed_add(connector, mode); +		return 1; +	} + +	return 0; +} + +/** + * cdv_intel_lvds_destroy - unregister and free LVDS structures + * @connector: connector to free + * + * Unregister the DDC bus for this connector then free the driver private + * structure. + */ +static void cdv_intel_lvds_destroy(struct drm_connector *connector) +{ +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); + +	if (gma_encoder->i2c_bus) +		psb_intel_i2c_destroy(gma_encoder->i2c_bus); +	drm_sysfs_connector_remove(connector); +	drm_connector_cleanup(connector); +	kfree(connector); +} + +static int cdv_intel_lvds_set_property(struct drm_connector *connector, +				       struct drm_property *property, +				       uint64_t value) +{ +	struct drm_encoder *encoder = connector->encoder; + +	if (!strcmp(property->name, "scaling mode") && encoder) { +		struct gma_crtc *crtc = to_gma_crtc(encoder->crtc); +		uint64_t curValue; + +		if (!crtc) +			return -1; + +		switch (value) { +		case DRM_MODE_SCALE_FULLSCREEN: +			break; +		case DRM_MODE_SCALE_NO_SCALE: +			break; +		case DRM_MODE_SCALE_ASPECT: +			break; +		default: +			return -1; +		} + +		if (drm_object_property_get_value(&connector->base, +						     property, +						     &curValue)) +			return -1; + +		if (curValue == value) +			return 0; + +		if (drm_object_property_set_value(&connector->base, +							property, +							value)) +			return -1; + +		if (crtc->saved_mode.hdisplay != 0 && +		    crtc->saved_mode.vdisplay != 0) { +			if (!drm_crtc_helper_set_mode(encoder->crtc, +						      &crtc->saved_mode, +						      encoder->crtc->x, +						      encoder->crtc->y, +						      encoder->crtc->primary->fb)) +				return -1; +		} +	} else if (!strcmp(property->name, "backlight") && encoder) { +		if (drm_object_property_set_value(&connector->base, +							property, +							value)) +			return -1; +		else +                        gma_backlight_set(encoder->dev, value); +	} else if (!strcmp(property->name, "DPMS") && encoder) { +		struct drm_encoder_helper_funcs *helpers = +					encoder->helper_private; +		helpers->dpms(encoder, value); +	} +	return 0; +} + +static const struct drm_encoder_helper_funcs +					cdv_intel_lvds_helper_funcs = { +	.dpms = cdv_intel_lvds_encoder_dpms, +	.mode_fixup = cdv_intel_lvds_mode_fixup, +	.prepare = cdv_intel_lvds_prepare, +	.mode_set = cdv_intel_lvds_mode_set, +	.commit = cdv_intel_lvds_commit, +}; + +static const struct drm_connector_helper_funcs +				cdv_intel_lvds_connector_helper_funcs = { +	.get_modes = cdv_intel_lvds_get_modes, +	.mode_valid = cdv_intel_lvds_mode_valid, +	.best_encoder = gma_best_encoder, +}; + +static const struct drm_connector_funcs cdv_intel_lvds_connector_funcs = { +	.dpms = drm_helper_connector_dpms, +	.save = cdv_intel_lvds_save, +	.restore = cdv_intel_lvds_restore, +	.detect = cdv_intel_lvds_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.set_property = cdv_intel_lvds_set_property, +	.destroy = cdv_intel_lvds_destroy, +}; + + +static void cdv_intel_lvds_enc_destroy(struct drm_encoder *encoder) +{ +	drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs cdv_intel_lvds_enc_funcs = { +	.destroy = cdv_intel_lvds_enc_destroy, +}; + +/* + * Enumerate the child dev array parsed from VBT to check whether + * the LVDS is present. + * If it is present, return 1. + * If it is not present, return false. + * If no child dev is parsed from VBT, it assumes that the LVDS is present. + */ +static bool lvds_is_present_in_vbt(struct drm_device *dev, +				   u8 *i2c_pin) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	int i; + +	if (!dev_priv->child_dev_num) +		return true; + +	for (i = 0; i < dev_priv->child_dev_num; i++) { +		struct child_device_config *child = dev_priv->child_dev + i; + +		/* If the device type is not LFP, continue. +		 * We have to check both the new identifiers as well as the +		 * old for compatibility with some BIOSes. +		 */ +		if (child->device_type != DEVICE_TYPE_INT_LFP && +		    child->device_type != DEVICE_TYPE_LFP) +			continue; + +		if (child->i2c_pin) +		    *i2c_pin = child->i2c_pin; + +		/* However, we cannot trust the BIOS writers to populate +		 * the VBT correctly.  Since LVDS requires additional +		 * information from AIM blocks, a non-zero addin offset is +		 * a good indicator that the LVDS is actually present. +		 */ +		if (child->addin_offset) +			return true; + +		/* But even then some BIOS writers perform some black magic +		 * and instantiate the device without reference to any +		 * additional data.  Trust that if the VBT was written into +		 * the OpRegion then they have validated the LVDS's existence. +		 */ +		if (dev_priv->opregion.vbt) +			return true; +	} + +	return false; +} + +/** + * cdv_intel_lvds_init - setup LVDS connectors on this device + * @dev: drm device + * + * Create the connector, register the LVDS DDC bus, and try to figure out what + * modes we can display on the LVDS panel (if present). + */ +void cdv_intel_lvds_init(struct drm_device *dev, +		     struct psb_intel_mode_device *mode_dev) +{ +	struct gma_encoder *gma_encoder; +	struct gma_connector *gma_connector; +	struct cdv_intel_lvds_priv *lvds_priv; +	struct drm_connector *connector; +	struct drm_encoder *encoder; +	struct drm_display_mode *scan; +	struct drm_crtc *crtc; +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 lvds; +	int pipe; +	u8 pin; + +	pin = GMBUS_PORT_PANEL; +	if (!lvds_is_present_in_vbt(dev, &pin)) { +		DRM_DEBUG_KMS("LVDS is not present in VBT\n"); +		return; +	} + +	gma_encoder = kzalloc(sizeof(struct gma_encoder), +				    GFP_KERNEL); +	if (!gma_encoder) +		return; + +	gma_connector = kzalloc(sizeof(struct gma_connector), +				      GFP_KERNEL); +	if (!gma_connector) +		goto failed_connector; + +	lvds_priv = kzalloc(sizeof(struct cdv_intel_lvds_priv), GFP_KERNEL); +	if (!lvds_priv) +		goto failed_lvds_priv; + +	gma_encoder->dev_priv = lvds_priv; + +	connector = &gma_connector->base; +	encoder = &gma_encoder->base; + + +	drm_connector_init(dev, connector, +			   &cdv_intel_lvds_connector_funcs, +			   DRM_MODE_CONNECTOR_LVDS); + +	drm_encoder_init(dev, encoder, +			 &cdv_intel_lvds_enc_funcs, +			 DRM_MODE_ENCODER_LVDS); + + +	gma_connector_attach_encoder(gma_connector, gma_encoder); +	gma_encoder->type = INTEL_OUTPUT_LVDS; + +	drm_encoder_helper_add(encoder, &cdv_intel_lvds_helper_funcs); +	drm_connector_helper_add(connector, +				 &cdv_intel_lvds_connector_helper_funcs); +	connector->display_info.subpixel_order = SubPixelHorizontalRGB; +	connector->interlace_allowed = false; +	connector->doublescan_allowed = false; + +	/*Attach connector properties*/ +	drm_object_attach_property(&connector->base, +				      dev->mode_config.scaling_mode_property, +				      DRM_MODE_SCALE_FULLSCREEN); +	drm_object_attach_property(&connector->base, +				      dev_priv->backlight_property, +				      BRIGHTNESS_MAX_LEVEL); + +	/** +	 * Set up I2C bus +	 * FIXME: distroy i2c_bus when exit +	 */ +	gma_encoder->i2c_bus = psb_intel_i2c_create(dev, +							 GPIOB, +							 "LVDSBLC_B"); +	if (!gma_encoder->i2c_bus) { +		dev_printk(KERN_ERR, +			&dev->pdev->dev, "I2C bus registration failed.\n"); +		goto failed_blc_i2c; +	} +	gma_encoder->i2c_bus->slave_addr = 0x2C; +	dev_priv->lvds_i2c_bus = gma_encoder->i2c_bus; + +	/* +	 * LVDS discovery: +	 * 1) check for EDID on DDC +	 * 2) check for VBT data +	 * 3) check to see if LVDS is already on +	 *    if none of the above, no panel +	 * 4) make sure lid is open +	 *    if closed, act like it's not there for now +	 */ + +	/* Set up the DDC bus. */ +	gma_encoder->ddc_bus = psb_intel_i2c_create(dev, +							 GPIOC, +							 "LVDSDDC_C"); +	if (!gma_encoder->ddc_bus) { +		dev_printk(KERN_ERR, &dev->pdev->dev, +			   "DDC bus registration " "failed.\n"); +		goto failed_ddc; +	} + +	/* +	 * Attempt to get the fixed panel mode from DDC.  Assume that the +	 * preferred mode is the right one. +	 */ +	mutex_lock(&dev->mode_config.mutex); +	psb_intel_ddc_get_modes(connector, +				&gma_encoder->ddc_bus->adapter); +	list_for_each_entry(scan, &connector->probed_modes, head) { +		if (scan->type & DRM_MODE_TYPE_PREFERRED) { +			mode_dev->panel_fixed_mode = +			    drm_mode_duplicate(dev, scan); +			goto out;	/* FIXME: check for quirks */ +		} +	} + +	/* Failed to get EDID, what about VBT? do we need this?*/ +	if (dev_priv->lfp_lvds_vbt_mode) { +		mode_dev->panel_fixed_mode = +			drm_mode_duplicate(dev, dev_priv->lfp_lvds_vbt_mode); +		if (mode_dev->panel_fixed_mode) { +			mode_dev->panel_fixed_mode->type |= +				DRM_MODE_TYPE_PREFERRED; +			goto out;	/* FIXME: check for quirks */ +		} +	} +	/* +	 * If we didn't get EDID, try checking if the panel is already turned +	 * on.	If so, assume that whatever is currently programmed is the +	 * correct mode. +	 */ +	lvds = REG_READ(LVDS); +	pipe = (lvds & LVDS_PIPEB_SELECT) ? 1 : 0; +	crtc = psb_intel_get_crtc_from_pipe(dev, pipe); + +	if (crtc && (lvds & LVDS_PORT_EN)) { +		mode_dev->panel_fixed_mode = +		    cdv_intel_crtc_mode_get(dev, crtc); +		if (mode_dev->panel_fixed_mode) { +			mode_dev->panel_fixed_mode->type |= +			    DRM_MODE_TYPE_PREFERRED; +			goto out;	/* FIXME: check for quirks */ +		} +	} + +	/* If we still don't have a mode after all that, give up. */ +	if (!mode_dev->panel_fixed_mode) { +		DRM_DEBUG +			("Found no modes on the lvds, ignoring the LVDS\n"); +		goto failed_find; +	} + +	/* setup PWM */ +	{ +		u32 pwm; + +		pwm = REG_READ(BLC_PWM_CTL2); +		if (pipe == 1) +			pwm |= PWM_PIPE_B; +		else +			pwm &= ~PWM_PIPE_B; +		pwm |= PWM_ENABLE; +		REG_WRITE(BLC_PWM_CTL2, pwm); +	} + +out: +	mutex_unlock(&dev->mode_config.mutex); +	drm_sysfs_connector_add(connector); +	return; + +failed_find: +	mutex_unlock(&dev->mode_config.mutex); +	printk(KERN_ERR "Failed find\n"); +	if (gma_encoder->ddc_bus) +		psb_intel_i2c_destroy(gma_encoder->ddc_bus); +failed_ddc: +	printk(KERN_ERR "Failed DDC\n"); +	if (gma_encoder->i2c_bus) +		psb_intel_i2c_destroy(gma_encoder->i2c_bus); +failed_blc_i2c: +	printk(KERN_ERR "Failed BLC\n"); +	drm_encoder_cleanup(encoder); +	drm_connector_cleanup(connector); +	kfree(lvds_priv); +failed_lvds_priv: +	kfree(gma_connector); +failed_connector: +	kfree(gma_encoder); +} diff --git a/drivers/gpu/drm/gma500/framebuffer.c b/drivers/gpu/drm/gma500/framebuffer.c new file mode 100644 index 00000000000..e7fcc148f33 --- /dev/null +++ b/drivers/gpu/drm/gma500/framebuffer.c @@ -0,0 +1,795 @@ +/************************************************************************** + * Copyright (c) 2007-2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/console.h> + +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_helper.h> + +#include "psb_drv.h" +#include "psb_intel_reg.h" +#include "psb_intel_drv.h" +#include "framebuffer.h" +#include "gtt.h" + +static void psb_user_framebuffer_destroy(struct drm_framebuffer *fb); +static int psb_user_framebuffer_create_handle(struct drm_framebuffer *fb, +					      struct drm_file *file_priv, +					      unsigned int *handle); + +static const struct drm_framebuffer_funcs psb_fb_funcs = { +	.destroy = psb_user_framebuffer_destroy, +	.create_handle = psb_user_framebuffer_create_handle, +}; + +#define CMAP_TOHW(_val, _width) ((((_val) << (_width)) + 0x7FFF - (_val)) >> 16) + +static int psbfb_setcolreg(unsigned regno, unsigned red, unsigned green, +			   unsigned blue, unsigned transp, +			   struct fb_info *info) +{ +	struct psb_fbdev *fbdev = info->par; +	struct drm_framebuffer *fb = fbdev->psb_fb_helper.fb; +	uint32_t v; + +	if (!fb) +		return -ENOMEM; + +	if (regno > 255) +		return 1; + +	red = CMAP_TOHW(red, info->var.red.length); +	blue = CMAP_TOHW(blue, info->var.blue.length); +	green = CMAP_TOHW(green, info->var.green.length); +	transp = CMAP_TOHW(transp, info->var.transp.length); + +	v = (red << info->var.red.offset) | +	    (green << info->var.green.offset) | +	    (blue << info->var.blue.offset) | +	    (transp << info->var.transp.offset); + +	if (regno < 16) { +		switch (fb->bits_per_pixel) { +		case 16: +			((uint32_t *) info->pseudo_palette)[regno] = v; +			break; +		case 24: +		case 32: +			((uint32_t *) info->pseudo_palette)[regno] = v; +			break; +		} +	} + +	return 0; +} + +static int psbfb_pan(struct fb_var_screeninfo *var, struct fb_info *info) +{ +	struct psb_fbdev *fbdev = info->par; +	struct psb_framebuffer *psbfb = &fbdev->pfb; +	struct drm_device *dev = psbfb->base.dev; + +	/* +	 *	We have to poke our nose in here. The core fb code assumes +	 *	panning is part of the hardware that can be invoked before +	 *	the actual fb is mapped. In our case that isn't quite true. +	 */ +	if (psbfb->gtt->npage) { +		/* GTT roll shifts in 4K pages, we need to shift the right +		   number of pages */ +		int pages = info->fix.line_length >> 12; +		psb_gtt_roll(dev, psbfb->gtt, var->yoffset * pages); +	} +        return 0; +} + +static int psbfb_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ +	struct psb_framebuffer *psbfb = vma->vm_private_data; +	struct drm_device *dev = psbfb->base.dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	int page_num; +	int i; +	unsigned long address; +	int ret; +	unsigned long pfn; +	unsigned long phys_addr = (unsigned long)dev_priv->stolen_base + +				  psbfb->gtt->offset; + +	page_num = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; +	address = (unsigned long)vmf->virtual_address - (vmf->pgoff << PAGE_SHIFT); + +	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + +	for (i = 0; i < page_num; i++) { +		pfn = (phys_addr >> PAGE_SHIFT); + +		ret = vm_insert_mixed(vma, address, pfn); +		if (unlikely((ret == -EBUSY) || (ret != 0 && i > 0))) +			break; +		else if (unlikely(ret != 0)) { +			ret = (ret == -ENOMEM) ? VM_FAULT_OOM : VM_FAULT_SIGBUS; +			return ret; +		} +		address += PAGE_SIZE; +		phys_addr += PAGE_SIZE; +	} +	return VM_FAULT_NOPAGE; +} + +static void psbfb_vm_open(struct vm_area_struct *vma) +{ +} + +static void psbfb_vm_close(struct vm_area_struct *vma) +{ +} + +static const struct vm_operations_struct psbfb_vm_ops = { +	.fault	= psbfb_vm_fault, +	.open	= psbfb_vm_open, +	.close	= psbfb_vm_close +}; + +static int psbfb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ +	struct psb_fbdev *fbdev = info->par; +	struct psb_framebuffer *psbfb = &fbdev->pfb; + +	if (vma->vm_pgoff != 0) +		return -EINVAL; +	if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) +		return -EINVAL; + +	if (!psbfb->addr_space) +		psbfb->addr_space = vma->vm_file->f_mapping; +	/* +	 * If this is a GEM object then info->screen_base is the virtual +	 * kernel remapping of the object. FIXME: Review if this is +	 * suitable for our mmap work +	 */ +	vma->vm_ops = &psbfb_vm_ops; +	vma->vm_private_data = (void *)psbfb; +	vma->vm_flags |= VM_IO | VM_MIXEDMAP | VM_DONTEXPAND | VM_DONTDUMP; +	return 0; +} + +static int psbfb_ioctl(struct fb_info *info, unsigned int cmd, +						unsigned long arg) +{ +	return -ENOTTY; +} + +static struct fb_ops psbfb_ops = { +	.owner = THIS_MODULE, +	.fb_check_var = drm_fb_helper_check_var, +	.fb_set_par = drm_fb_helper_set_par, +	.fb_blank = drm_fb_helper_blank, +	.fb_setcolreg = psbfb_setcolreg, +	.fb_fillrect = cfb_fillrect, +	.fb_copyarea = psbfb_copyarea, +	.fb_imageblit = cfb_imageblit, +	.fb_mmap = psbfb_mmap, +	.fb_sync = psbfb_sync, +	.fb_ioctl = psbfb_ioctl, +}; + +static struct fb_ops psbfb_roll_ops = { +	.owner = THIS_MODULE, +	.fb_check_var = drm_fb_helper_check_var, +	.fb_set_par = drm_fb_helper_set_par, +	.fb_blank = drm_fb_helper_blank, +	.fb_setcolreg = psbfb_setcolreg, +	.fb_fillrect = cfb_fillrect, +	.fb_copyarea = cfb_copyarea, +	.fb_imageblit = cfb_imageblit, +	.fb_pan_display = psbfb_pan, +	.fb_mmap = psbfb_mmap, +	.fb_ioctl = psbfb_ioctl, +}; + +static struct fb_ops psbfb_unaccel_ops = { +	.owner = THIS_MODULE, +	.fb_check_var = drm_fb_helper_check_var, +	.fb_set_par = drm_fb_helper_set_par, +	.fb_blank = drm_fb_helper_blank, +	.fb_setcolreg = psbfb_setcolreg, +	.fb_fillrect = cfb_fillrect, +	.fb_copyarea = cfb_copyarea, +	.fb_imageblit = cfb_imageblit, +	.fb_mmap = psbfb_mmap, +	.fb_ioctl = psbfb_ioctl, +}; + +/** + *	psb_framebuffer_init	-	initialize a framebuffer + *	@dev: our DRM device + *	@fb: framebuffer to set up + *	@mode_cmd: mode description + *	@gt: backing object + * + *	Configure and fill in the boilerplate for our frame buffer. Return + *	0 on success or an error code if we fail. + */ +static int psb_framebuffer_init(struct drm_device *dev, +					struct psb_framebuffer *fb, +					struct drm_mode_fb_cmd2 *mode_cmd, +					struct gtt_range *gt) +{ +	u32 bpp, depth; +	int ret; + +	drm_fb_get_bpp_depth(mode_cmd->pixel_format, &depth, &bpp); + +	if (mode_cmd->pitches[0] & 63) +		return -EINVAL; +	switch (bpp) { +	case 8: +	case 16: +	case 24: +	case 32: +		break; +	default: +		return -EINVAL; +	} +	drm_helper_mode_fill_fb_struct(&fb->base, mode_cmd); +	fb->gtt = gt; +	ret = drm_framebuffer_init(dev, &fb->base, &psb_fb_funcs); +	if (ret) { +		dev_err(dev->dev, "framebuffer init failed: %d\n", ret); +		return ret; +	} +	return 0; +} + +/** + *	psb_framebuffer_create	-	create a framebuffer backed by gt + *	@dev: our DRM device + *	@mode_cmd: the description of the requested mode + *	@gt: the backing object + * + *	Create a framebuffer object backed by the gt, and fill in the + *	boilerplate required + * + *	TODO: review object references + */ + +static struct drm_framebuffer *psb_framebuffer_create +			(struct drm_device *dev, +			 struct drm_mode_fb_cmd2 *mode_cmd, +			 struct gtt_range *gt) +{ +	struct psb_framebuffer *fb; +	int ret; + +	fb = kzalloc(sizeof(*fb), GFP_KERNEL); +	if (!fb) +		return ERR_PTR(-ENOMEM); + +	ret = psb_framebuffer_init(dev, fb, mode_cmd, gt); +	if (ret) { +		kfree(fb); +		return ERR_PTR(ret); +	} +	return &fb->base; +} + +/** + *	psbfb_alloc		-	allocate frame buffer memory + *	@dev: the DRM device + *	@aligned_size: space needed + *	@force: fall back to GEM buffers if need be + * + *	Allocate the frame buffer. In the usual case we get a GTT range that + *	is stolen memory backed and life is simple. If there isn't sufficient + *	we fail as we don't have the virtual mapping space to really vmap it + *	and the kernel console code can't handle non linear framebuffers. + * + *	Re-address this as and if the framebuffer layer grows this ability. + */ +static struct gtt_range *psbfb_alloc(struct drm_device *dev, int aligned_size) +{ +	struct gtt_range *backing; +	/* Begin by trying to use stolen memory backing */ +	backing = psb_gtt_alloc_range(dev, aligned_size, "fb", 1, PAGE_SIZE); +	if (backing) { +		drm_gem_private_object_init(dev, &backing->gem, aligned_size); +		return backing; +	} +	return NULL; +} + +/** + *	psbfb_create		-	create a framebuffer + *	@fbdev: the framebuffer device + *	@sizes: specification of the layout + * + *	Create a framebuffer to the specifications provided + */ +static int psbfb_create(struct psb_fbdev *fbdev, +				struct drm_fb_helper_surface_size *sizes) +{ +	struct drm_device *dev = fbdev->psb_fb_helper.dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct fb_info *info; +	struct drm_framebuffer *fb; +	struct psb_framebuffer *psbfb = &fbdev->pfb; +	struct drm_mode_fb_cmd2 mode_cmd; +	struct device *device = &dev->pdev->dev; +	int size; +	int ret; +	struct gtt_range *backing; +	u32 bpp, depth; +	int gtt_roll = 0; +	int pitch_lines = 0; + +	mode_cmd.width = sizes->surface_width; +	mode_cmd.height = sizes->surface_height; +	bpp = sizes->surface_bpp; +	depth = sizes->surface_depth; + +	/* No 24bit packed */ +	if (bpp == 24) +		bpp = 32; + +	do { +		/* +		 * Acceleration via the GTT requires pitch to be +		 * power of two aligned. Preferably page but less +		 * is ok with some fonts +		 */ +        	mode_cmd.pitches[0] =  ALIGN(mode_cmd.width * ((bpp + 7) / 8), 4096 >> pitch_lines); + +        	size = mode_cmd.pitches[0] * mode_cmd.height; +        	size = ALIGN(size, PAGE_SIZE); + +		/* Allocate the fb in the GTT with stolen page backing */ +		backing = psbfb_alloc(dev, size); + +		if (pitch_lines) +			pitch_lines *= 2; +		else +			pitch_lines = 1; +		gtt_roll++; +	} while (backing == NULL && pitch_lines <= 16); + +	/* The final pitch we accepted if we succeeded */ +	pitch_lines /= 2; + +	if (backing == NULL) { +		/* +		 *	We couldn't get the space we wanted, fall back to the +		 *	display engine requirement instead.  The HW requires +		 *	the pitch to be 64 byte aligned +		 */ + +		gtt_roll = 0;	/* Don't use GTT accelerated scrolling */ +		pitch_lines = 64; + +		mode_cmd.pitches[0] =  ALIGN(mode_cmd.width * ((bpp + 7) / 8), 64); + +		size = mode_cmd.pitches[0] * mode_cmd.height; +		size = ALIGN(size, PAGE_SIZE); + +		/* Allocate the framebuffer in the GTT with stolen page backing */ +		backing = psbfb_alloc(dev, size); +		if (backing == NULL) +			return -ENOMEM; +	} + +	memset(dev_priv->vram_addr + backing->offset, 0, size); + +	mutex_lock(&dev->struct_mutex); + +	info = framebuffer_alloc(0, device); +	if (!info) { +		ret = -ENOMEM; +		goto out_err1; +	} +	info->par = fbdev; + +	mode_cmd.pixel_format = drm_mode_legacy_fb_format(bpp, depth); + +	ret = psb_framebuffer_init(dev, psbfb, &mode_cmd, backing); +	if (ret) +		goto out_unref; + +	fb = &psbfb->base; +	psbfb->fbdev = info; + +	fbdev->psb_fb_helper.fb = fb; +	fbdev->psb_fb_helper.fbdev = info; + +	drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); +	strcpy(info->fix.id, "psbdrmfb"); + +	info->flags = FBINFO_DEFAULT; +	if (dev_priv->ops->accel_2d && pitch_lines > 8)	/* 2D engine */ +		info->fbops = &psbfb_ops; +	else if (gtt_roll) {	/* GTT rolling seems best */ +		info->fbops = &psbfb_roll_ops; +		info->flags |= FBINFO_HWACCEL_YPAN; +	} else	/* Software */ +		info->fbops = &psbfb_unaccel_ops; + +	ret = fb_alloc_cmap(&info->cmap, 256, 0); +	if (ret) { +		ret = -ENOMEM; +		goto out_unref; +	} + +	info->fix.smem_start = dev->mode_config.fb_base; +	info->fix.smem_len = size; +	info->fix.ywrapstep = gtt_roll; +	info->fix.ypanstep = 0; + +	/* Accessed stolen memory directly */ +	info->screen_base = dev_priv->vram_addr + backing->offset; +	info->screen_size = size; + +	if (dev_priv->gtt.stolen_size) { +		info->apertures = alloc_apertures(1); +		if (!info->apertures) { +			ret = -ENOMEM; +			goto out_unref; +		} +		info->apertures->ranges[0].base = dev->mode_config.fb_base; +		info->apertures->ranges[0].size = dev_priv->gtt.stolen_size; +	} + +	drm_fb_helper_fill_var(info, &fbdev->psb_fb_helper, +				sizes->fb_width, sizes->fb_height); + +	info->fix.mmio_start = pci_resource_start(dev->pdev, 0); +	info->fix.mmio_len = pci_resource_len(dev->pdev, 0); + +	/* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */ + +	dev_dbg(dev->dev, "allocated %dx%d fb\n", +					psbfb->base.width, psbfb->base.height); + +	mutex_unlock(&dev->struct_mutex); +	return 0; +out_unref: +	if (backing->stolen) +		psb_gtt_free_range(dev, backing); +	else +		drm_gem_object_unreference(&backing->gem); +out_err1: +	mutex_unlock(&dev->struct_mutex); +	psb_gtt_free_range(dev, backing); +	return ret; +} + +/** + *	psb_user_framebuffer_create	-	create framebuffer + *	@dev: our DRM device + *	@filp: client file + *	@cmd: mode request + * + *	Create a new framebuffer backed by a userspace GEM object + */ +static struct drm_framebuffer *psb_user_framebuffer_create +			(struct drm_device *dev, struct drm_file *filp, +			 struct drm_mode_fb_cmd2 *cmd) +{ +	struct gtt_range *r; +	struct drm_gem_object *obj; + +	/* +	 *	Find the GEM object and thus the gtt range object that is +	 *	to back this space +	 */ +	obj = drm_gem_object_lookup(dev, filp, cmd->handles[0]); +	if (obj == NULL) +		return ERR_PTR(-ENOENT); + +	/* Let the core code do all the work */ +	r = container_of(obj, struct gtt_range, gem); +	return psb_framebuffer_create(dev, cmd, r); +} + +static void psbfb_gamma_set(struct drm_crtc *crtc, u16 red, u16 green, +							u16 blue, int regno) +{ +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); + +	gma_crtc->lut_r[regno] = red >> 8; +	gma_crtc->lut_g[regno] = green >> 8; +	gma_crtc->lut_b[regno] = blue >> 8; +} + +static void psbfb_gamma_get(struct drm_crtc *crtc, u16 *red, +					u16 *green, u16 *blue, int regno) +{ +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); + +	*red = gma_crtc->lut_r[regno] << 8; +	*green = gma_crtc->lut_g[regno] << 8; +	*blue = gma_crtc->lut_b[regno] << 8; +} + +static int psbfb_probe(struct drm_fb_helper *helper, +				struct drm_fb_helper_surface_size *sizes) +{ +	struct psb_fbdev *psb_fbdev = (struct psb_fbdev *)helper; +	struct drm_device *dev = psb_fbdev->psb_fb_helper.dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	int bytespp; + +	bytespp = sizes->surface_bpp / 8; +	if (bytespp == 3)	/* no 24bit packed */ +		bytespp = 4; + +	/* If the mode will not fit in 32bit then switch to 16bit to get +	   a console on full resolution. The X mode setting server will +	   allocate its own 32bit GEM framebuffer */ +	if (ALIGN(sizes->fb_width * bytespp, 64) * sizes->fb_height > +	                dev_priv->vram_stolen_size) { +                sizes->surface_bpp = 16; +                sizes->surface_depth = 16; +        } + +	return psbfb_create(psb_fbdev, sizes); +} + +static struct drm_fb_helper_funcs psb_fb_helper_funcs = { +	.gamma_set = psbfb_gamma_set, +	.gamma_get = psbfb_gamma_get, +	.fb_probe = psbfb_probe, +}; + +static int psb_fbdev_destroy(struct drm_device *dev, struct psb_fbdev *fbdev) +{ +	struct fb_info *info; +	struct psb_framebuffer *psbfb = &fbdev->pfb; + +	if (fbdev->psb_fb_helper.fbdev) { +		info = fbdev->psb_fb_helper.fbdev; +		unregister_framebuffer(info); +		if (info->cmap.len) +			fb_dealloc_cmap(&info->cmap); +		framebuffer_release(info); +	} +	drm_fb_helper_fini(&fbdev->psb_fb_helper); +	drm_framebuffer_unregister_private(&psbfb->base); +	drm_framebuffer_cleanup(&psbfb->base); + +	if (psbfb->gtt) +		drm_gem_object_unreference(&psbfb->gtt->gem); +	return 0; +} + +int psb_fbdev_init(struct drm_device *dev) +{ +	struct psb_fbdev *fbdev; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	fbdev = kzalloc(sizeof(struct psb_fbdev), GFP_KERNEL); +	if (!fbdev) { +		dev_err(dev->dev, "no memory\n"); +		return -ENOMEM; +	} + +	dev_priv->fbdev = fbdev; +	fbdev->psb_fb_helper.funcs = &psb_fb_helper_funcs; + +	drm_fb_helper_init(dev, &fbdev->psb_fb_helper, dev_priv->ops->crtcs, +							INTELFB_CONN_LIMIT); + +	drm_fb_helper_single_add_all_connectors(&fbdev->psb_fb_helper); + +	/* disable all the possible outputs/crtcs before entering KMS mode */ +	drm_helper_disable_unused_functions(dev); + +	drm_fb_helper_initial_config(&fbdev->psb_fb_helper, 32); +	return 0; +} + +static void psb_fbdev_fini(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (!dev_priv->fbdev) +		return; + +	psb_fbdev_destroy(dev, dev_priv->fbdev); +	kfree(dev_priv->fbdev); +	dev_priv->fbdev = NULL; +} + +static void psbfb_output_poll_changed(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_fbdev *fbdev = (struct psb_fbdev *)dev_priv->fbdev; +	drm_fb_helper_hotplug_event(&fbdev->psb_fb_helper); +} + +/** + *	psb_user_framebuffer_create_handle - add hamdle to a framebuffer + *	@fb: framebuffer + *	@file_priv: our DRM file + *	@handle: returned handle + * + *	Our framebuffer object is a GTT range which also contains a GEM + *	object. We need to turn it into a handle for userspace. GEM will do + *	the work for us + */ +static int psb_user_framebuffer_create_handle(struct drm_framebuffer *fb, +					      struct drm_file *file_priv, +					      unsigned int *handle) +{ +	struct psb_framebuffer *psbfb = to_psb_fb(fb); +	struct gtt_range *r = psbfb->gtt; +	return drm_gem_handle_create(file_priv, &r->gem, handle); +} + +/** + *	psb_user_framebuffer_destroy	-	destruct user created fb + *	@fb: framebuffer + * + *	User framebuffers are backed by GEM objects so all we have to do is + *	clean up a bit and drop the reference, GEM will handle the fallout + */ +static void psb_user_framebuffer_destroy(struct drm_framebuffer *fb) +{ +	struct psb_framebuffer *psbfb = to_psb_fb(fb); +	struct gtt_range *r = psbfb->gtt; + +	/* Let DRM do its clean up */ +	drm_framebuffer_cleanup(fb); +	/*  We are no longer using the resource in GEM */ +	drm_gem_object_unreference_unlocked(&r->gem); +	kfree(fb); +} + +static const struct drm_mode_config_funcs psb_mode_funcs = { +	.fb_create = psb_user_framebuffer_create, +	.output_poll_changed = psbfb_output_poll_changed, +}; + +static int psb_create_backlight_property(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct drm_property *backlight; + +	if (dev_priv->backlight_property) +		return 0; + +	backlight = drm_property_create_range(dev, 0, "backlight", 0, 100); + +	dev_priv->backlight_property = backlight; + +	return 0; +} + +static void psb_setup_outputs(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct drm_connector *connector; + +	drm_mode_create_scaling_mode_property(dev); +	psb_create_backlight_property(dev); + +	dev_priv->ops->output_init(dev); + +	list_for_each_entry(connector, &dev->mode_config.connector_list, +			    head) { +		struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +		struct drm_encoder *encoder = &gma_encoder->base; +		int crtc_mask = 0, clone_mask = 0; + +		/* valid crtcs */ +		switch (gma_encoder->type) { +		case INTEL_OUTPUT_ANALOG: +			crtc_mask = (1 << 0); +			clone_mask = (1 << INTEL_OUTPUT_ANALOG); +			break; +		case INTEL_OUTPUT_SDVO: +			crtc_mask = dev_priv->ops->sdvo_mask; +			clone_mask = (1 << INTEL_OUTPUT_SDVO); +			break; +		case INTEL_OUTPUT_LVDS: +		        crtc_mask = dev_priv->ops->lvds_mask; +			clone_mask = (1 << INTEL_OUTPUT_LVDS); +			break; +		case INTEL_OUTPUT_MIPI: +			crtc_mask = (1 << 0); +			clone_mask = (1 << INTEL_OUTPUT_MIPI); +			break; +		case INTEL_OUTPUT_MIPI2: +			crtc_mask = (1 << 2); +			clone_mask = (1 << INTEL_OUTPUT_MIPI2); +			break; +		case INTEL_OUTPUT_HDMI: +		        crtc_mask = dev_priv->ops->hdmi_mask; +			clone_mask = (1 << INTEL_OUTPUT_HDMI); +			break; +		case INTEL_OUTPUT_DISPLAYPORT: +			crtc_mask = (1 << 0) | (1 << 1); +			clone_mask = (1 << INTEL_OUTPUT_DISPLAYPORT); +			break; +		case INTEL_OUTPUT_EDP: +			crtc_mask = (1 << 1); +			clone_mask = (1 << INTEL_OUTPUT_EDP); +		} +		encoder->possible_crtcs = crtc_mask; +		encoder->possible_clones = +		    gma_connector_clones(dev, clone_mask); +	} +} + +void psb_modeset_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; +	int i; + +	drm_mode_config_init(dev); + +	dev->mode_config.min_width = 0; +	dev->mode_config.min_height = 0; + +	dev->mode_config.funcs = &psb_mode_funcs; + +	/* set memory base */ +	/* Oaktrail and Poulsbo should use BAR 2*/ +	pci_read_config_dword(dev->pdev, PSB_BSM, (u32 *) +					&(dev->mode_config.fb_base)); + +	/* num pipes is 2 for PSB but 1 for Mrst */ +	for (i = 0; i < dev_priv->num_pipe; i++) +		psb_intel_crtc_init(dev, i, mode_dev); + +	dev->mode_config.max_width = 4096; +	dev->mode_config.max_height = 4096; + +	psb_setup_outputs(dev); + +	if (dev_priv->ops->errata) +	        dev_priv->ops->errata(dev); + +        dev_priv->modeset = true; +} + +void psb_modeset_cleanup(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	if (dev_priv->modeset) { +		mutex_lock(&dev->struct_mutex); + +		drm_kms_helper_poll_fini(dev); +		psb_fbdev_fini(dev); +		drm_mode_config_cleanup(dev); + +		mutex_unlock(&dev->struct_mutex); +	} +} diff --git a/drivers/gpu/drm/gma500/framebuffer.h b/drivers/gpu/drm/gma500/framebuffer.h new file mode 100644 index 00000000000..395f20b07aa --- /dev/null +++ b/drivers/gpu/drm/gma500/framebuffer.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2008-2011, Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *      Eric Anholt <eric@anholt.net> + * + */ + +#ifndef _FRAMEBUFFER_H_ +#define _FRAMEBUFFER_H_ + +#include <drm/drmP.h> +#include <drm/drm_fb_helper.h> + +#include "psb_drv.h" + +struct psb_framebuffer { +	struct drm_framebuffer base; +	struct address_space *addr_space; +	struct fb_info *fbdev; +	struct gtt_range *gtt; +}; + +struct psb_fbdev { +	struct drm_fb_helper psb_fb_helper; +	struct psb_framebuffer pfb; +}; + +#define to_psb_fb(x) container_of(x, struct psb_framebuffer, base) + +extern int gma_connector_clones(struct drm_device *dev, int type_mask); + +#endif + diff --git a/drivers/gpu/drm/gma500/gem.c b/drivers/gpu/drm/gma500/gem.c new file mode 100644 index 00000000000..c707fa6fca8 --- /dev/null +++ b/drivers/gpu/drm/gma500/gem.c @@ -0,0 +1,229 @@ +/* + *  psb GEM interface + * + * Copyright (c) 2011, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: Alan Cox + * + * TODO: + *	-	we need to work out if the MMU is relevant (eg for + *		accelerated operations on a GEM object) + */ + +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/gma_drm.h> +#include <drm/drm_vma_manager.h> +#include "psb_drv.h" + +void psb_gem_free_object(struct drm_gem_object *obj) +{ +	struct gtt_range *gtt = container_of(obj, struct gtt_range, gem); + +	/* Remove the list map if one is present */ +	drm_gem_free_mmap_offset(obj); +	drm_gem_object_release(obj); + +	/* This must occur last as it frees up the memory of the GEM object */ +	psb_gtt_free_range(obj->dev, gtt); +} + +int psb_gem_get_aperture(struct drm_device *dev, void *data, +				struct drm_file *file) +{ +	return -EINVAL; +} + +/** + *	psb_gem_dumb_map_gtt	-	buffer mapping for dumb interface + *	@file: our drm client file + *	@dev: drm device + *	@handle: GEM handle to the object (from dumb_create) + * + *	Do the necessary setup to allow the mapping of the frame buffer + *	into user memory. We don't have to do much here at the moment. + */ +int psb_gem_dumb_map_gtt(struct drm_file *file, struct drm_device *dev, +			 uint32_t handle, uint64_t *offset) +{ +	int ret = 0; +	struct drm_gem_object *obj; + +	mutex_lock(&dev->struct_mutex); + +	/* GEM does all our handle to object mapping */ +	obj = drm_gem_object_lookup(dev, file, handle); +	if (obj == NULL) { +		ret = -ENOENT; +		goto unlock; +	} +	/* What validation is needed here ? */ + +	/* Make it mmapable */ +	ret = drm_gem_create_mmap_offset(obj); +	if (ret) +		goto out; +	*offset = drm_vma_node_offset_addr(&obj->vma_node); +out: +	drm_gem_object_unreference(obj); +unlock: +	mutex_unlock(&dev->struct_mutex); +	return ret; +} + +/** + *	psb_gem_create		-	create a mappable object + *	@file: the DRM file of the client + *	@dev: our device + *	@size: the size requested + *	@handlep: returned handle (opaque number) + * + *	Create a GEM object, fill in the boilerplate and attach a handle to + *	it so that userspace can speak about it. This does the core work + *	for the various methods that do/will create GEM objects for things + */ +int psb_gem_create(struct drm_file *file, struct drm_device *dev, u64 size, +		   u32 *handlep, int stolen, u32 align) +{ +	struct gtt_range *r; +	int ret; +	u32 handle; + +	size = roundup(size, PAGE_SIZE); + +	/* Allocate our object - for now a direct gtt range which is not +	   stolen memory backed */ +	r = psb_gtt_alloc_range(dev, size, "gem", 0, PAGE_SIZE); +	if (r == NULL) { +		dev_err(dev->dev, "no memory for %lld byte GEM object\n", size); +		return -ENOSPC; +	} +	/* Initialize the extra goodies GEM needs to do all the hard work */ +	if (drm_gem_object_init(dev, &r->gem, size) != 0) { +		psb_gtt_free_range(dev, r); +		/* GEM doesn't give an error code so use -ENOMEM */ +		dev_err(dev->dev, "GEM init failed for %lld\n", size); +		return -ENOMEM; +	} +	/* Limit the object to 32bit mappings */ +	mapping_set_gfp_mask(r->gem.filp->f_mapping, GFP_KERNEL | __GFP_DMA32); +	/* Give the object a handle so we can carry it more easily */ +	ret = drm_gem_handle_create(file, &r->gem, &handle); +	if (ret) { +		dev_err(dev->dev, "GEM handle failed for %p, %lld\n", +							&r->gem, size); +		drm_gem_object_release(&r->gem); +		psb_gtt_free_range(dev, r); +		return ret; +	} +	/* We have the initial and handle reference but need only one now */ +	drm_gem_object_unreference(&r->gem); +	*handlep = handle; +	return 0; +} + +/** + *	psb_gem_dumb_create	-	create a dumb buffer + *	@drm_file: our client file + *	@dev: our device + *	@args: the requested arguments copied from userspace + * + *	Allocate a buffer suitable for use for a frame buffer of the + *	form described by user space. Give userspace a handle by which + *	to reference it. + */ +int psb_gem_dumb_create(struct drm_file *file, struct drm_device *dev, +			struct drm_mode_create_dumb *args) +{ +	args->pitch = ALIGN(args->width * ((args->bpp + 7) / 8), 64); +	args->size = args->pitch * args->height; +	return psb_gem_create(file, dev, args->size, &args->handle, 0, +			      PAGE_SIZE); +} + +/** + *	psb_gem_fault		-	pagefault handler for GEM objects + *	@vma: the VMA of the GEM object + *	@vmf: fault detail + * + *	Invoked when a fault occurs on an mmap of a GEM managed area. GEM + *	does most of the work for us including the actual map/unmap calls + *	but we need to do the actual page work. + * + *	This code eventually needs to handle faulting objects in and out + *	of the GTT and repacking it when we run out of space. We can put + *	that off for now and for our simple uses + * + *	The VMA was set up by GEM. In doing so it also ensured that the + *	vma->vm_private_data points to the GEM object that is backing this + *	mapping. + */ +int psb_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ +	struct drm_gem_object *obj; +	struct gtt_range *r; +	int ret; +	unsigned long pfn; +	pgoff_t page_offset; +	struct drm_device *dev; +	struct drm_psb_private *dev_priv; + +	obj = vma->vm_private_data;	/* GEM object */ +	dev = obj->dev; +	dev_priv = dev->dev_private; + +	r = container_of(obj, struct gtt_range, gem);	/* Get the gtt range */ + +	/* Make sure we don't parallel update on a fault, nor move or remove +	   something from beneath our feet */ +	mutex_lock(&dev->struct_mutex); + +	/* For now the mmap pins the object and it stays pinned. As things +	   stand that will do us no harm */ +	if (r->mmapping == 0) { +		ret = psb_gtt_pin(r); +		if (ret < 0) { +			dev_err(dev->dev, "gma500: pin failed: %d\n", ret); +			goto fail; +		} +		r->mmapping = 1; +	} + +	/* Page relative to the VMA start - we must calculate this ourselves +	   because vmf->pgoff is the fake GEM offset */ +	page_offset = ((unsigned long) vmf->virtual_address - vma->vm_start) +				>> PAGE_SHIFT; + +	/* CPU view of the page, don't go via the GART for CPU writes */ +	if (r->stolen) +		pfn = (dev_priv->stolen_base + r->offset) >> PAGE_SHIFT; +	else +		pfn = page_to_pfn(r->pages[page_offset]); +	ret = vm_insert_pfn(vma, (unsigned long)vmf->virtual_address, pfn); + +fail: +	mutex_unlock(&dev->struct_mutex); +	switch (ret) { +	case 0: +	case -ERESTARTSYS: +	case -EINTR: +		return VM_FAULT_NOPAGE; +	case -ENOMEM: +		return VM_FAULT_OOM; +	default: +		return VM_FAULT_SIGBUS; +	} +} diff --git a/drivers/gpu/drm/gma500/gem.h b/drivers/gpu/drm/gma500/gem.h new file mode 100644 index 00000000000..1381c5190f4 --- /dev/null +++ b/drivers/gpu/drm/gma500/gem.h @@ -0,0 +1,21 @@ +/************************************************************************** + * Copyright (c) 2014 Patrik Jakobsson + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + **************************************************************************/ + +#ifndef _GEM_H +#define _GEM_H + +extern int psb_gem_create(struct drm_file *file, struct drm_device *dev, +			  u64 size, u32 *handlep, int stolen, u32 align); +#endif diff --git a/drivers/gpu/drm/gma500/gma_device.c b/drivers/gpu/drm/gma500/gma_device.c new file mode 100644 index 00000000000..4a295f9ba06 --- /dev/null +++ b/drivers/gpu/drm/gma500/gma_device.c @@ -0,0 +1,56 @@ +/************************************************************************** + * Copyright (c) 2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + **************************************************************************/ + +#include <drm/drmP.h> +#include "psb_drv.h" + +void gma_get_core_freq(struct drm_device *dev) +{ +	uint32_t clock; +	struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); +	struct drm_psb_private *dev_priv = dev->dev_private; + +	/*pci_write_config_dword(pci_root, 0xD4, 0x00C32004);*/ +	/*pci_write_config_dword(pci_root, 0xD0, 0xE0033000);*/ + +	pci_write_config_dword(pci_root, 0xD0, 0xD0050300); +	pci_read_config_dword(pci_root, 0xD4, &clock); +	pci_dev_put(pci_root); + +	switch (clock & 0x07) { +	case 0: +		dev_priv->core_freq = 100; +		break; +	case 1: +		dev_priv->core_freq = 133; +		break; +	case 2: +		dev_priv->core_freq = 150; +		break; +	case 3: +		dev_priv->core_freq = 178; +		break; +	case 4: +		dev_priv->core_freq = 200; +		break; +	case 5: +	case 6: +	case 7: +		dev_priv->core_freq = 266; +		break; +	default: +		dev_priv->core_freq = 0; +	} +} diff --git a/drivers/gpu/drm/gma500/gma_device.h b/drivers/gpu/drm/gma500/gma_device.h new file mode 100644 index 00000000000..e1dbb007b82 --- /dev/null +++ b/drivers/gpu/drm/gma500/gma_device.h @@ -0,0 +1,21 @@ +/************************************************************************** + * Copyright (c) 2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + **************************************************************************/ + +#ifndef _GMA_DEVICE_H +#define _GMA_DEVICE_H + +extern void gma_get_core_freq(struct drm_device *dev); + +#endif diff --git a/drivers/gpu/drm/gma500/gma_display.c b/drivers/gpu/drm/gma500/gma_display.c new file mode 100644 index 00000000000..9bb9bddd881 --- /dev/null +++ b/drivers/gpu/drm/gma500/gma_display.c @@ -0,0 +1,791 @@ +/* + * Copyright © 2006-2011 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + *	Patrik Jakobsson <patrik.r.jakobsson@gmail.com> + */ + +#include <drm/drmP.h> +#include "gma_display.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "psb_drv.h" +#include "framebuffer.h" + +/** + * Returns whether any output on the specified pipe is of the specified type + */ +bool gma_pipe_has_type(struct drm_crtc *crtc, int type) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_mode_config *mode_config = &dev->mode_config; +	struct drm_connector *l_entry; + +	list_for_each_entry(l_entry, &mode_config->connector_list, head) { +		if (l_entry->encoder && l_entry->encoder->crtc == crtc) { +			struct gma_encoder *gma_encoder = +						gma_attached_encoder(l_entry); +			if (gma_encoder->type == type) +				return true; +		} +	} + +	return false; +} + +void gma_wait_for_vblank(struct drm_device *dev) +{ +	/* Wait for 20ms, i.e. one cycle at 50hz. */ +	mdelay(20); +} + +int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y, +		      struct drm_framebuffer *old_fb) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct psb_framebuffer *psbfb = to_psb_fb(crtc->primary->fb); +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	unsigned long start, offset; +	u32 dspcntr; +	int ret = 0; + +	if (!gma_power_begin(dev, true)) +		return 0; + +	/* no fb bound */ +	if (!crtc->primary->fb) { +		dev_err(dev->dev, "No FB bound\n"); +		goto gma_pipe_cleaner; +	} + +	/* We are displaying this buffer, make sure it is actually loaded +	   into the GTT */ +	ret = psb_gtt_pin(psbfb->gtt); +	if (ret < 0) +		goto gma_pipe_set_base_exit; +	start = psbfb->gtt->offset; +	offset = y * crtc->primary->fb->pitches[0] + x * (crtc->primary->fb->bits_per_pixel / 8); + +	REG_WRITE(map->stride, crtc->primary->fb->pitches[0]); + +	dspcntr = REG_READ(map->cntr); +	dspcntr &= ~DISPPLANE_PIXFORMAT_MASK; + +	switch (crtc->primary->fb->bits_per_pixel) { +	case 8: +		dspcntr |= DISPPLANE_8BPP; +		break; +	case 16: +		if (crtc->primary->fb->depth == 15) +			dspcntr |= DISPPLANE_15_16BPP; +		else +			dspcntr |= DISPPLANE_16BPP; +		break; +	case 24: +	case 32: +		dspcntr |= DISPPLANE_32BPP_NO_ALPHA; +		break; +	default: +		dev_err(dev->dev, "Unknown color depth\n"); +		ret = -EINVAL; +		goto gma_pipe_set_base_exit; +	} +	REG_WRITE(map->cntr, dspcntr); + +	dev_dbg(dev->dev, +		"Writing base %08lX %08lX %d %d\n", start, offset, x, y); + +	/* FIXME: Investigate whether this really is the base for psb and why +		  the linear offset is named base for the other chips. map->surf +		  should be the base and map->linoff the offset for all chips */ +	if (IS_PSB(dev)) { +		REG_WRITE(map->base, offset + start); +		REG_READ(map->base); +	} else { +		REG_WRITE(map->base, offset); +		REG_READ(map->base); +		REG_WRITE(map->surf, start); +		REG_READ(map->surf); +	} + +gma_pipe_cleaner: +	/* If there was a previous display we can now unpin it */ +	if (old_fb) +		psb_gtt_unpin(to_psb_fb(old_fb)->gtt); + +gma_pipe_set_base_exit: +	gma_power_end(dev); +	return ret; +} + +/* Loads the palette/gamma unit for the CRTC with the prepared values */ +void gma_crtc_load_lut(struct drm_crtc *crtc) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	const struct psb_offset *map = &dev_priv->regmap[gma_crtc->pipe]; +	int palreg = map->palette; +	int i; + +	/* The clocks have to be on to load the palette. */ +	if (!crtc->enabled) +		return; + +	if (gma_power_begin(dev, false)) { +		for (i = 0; i < 256; i++) { +			REG_WRITE(palreg + 4 * i, +				  ((gma_crtc->lut_r[i] + +				  gma_crtc->lut_adj[i]) << 16) | +				  ((gma_crtc->lut_g[i] + +				  gma_crtc->lut_adj[i]) << 8) | +				  (gma_crtc->lut_b[i] + +				  gma_crtc->lut_adj[i])); +		} +		gma_power_end(dev); +	} else { +		for (i = 0; i < 256; i++) { +			/* FIXME: Why pipe[0] and not pipe[..._crtc->pipe]? */ +			dev_priv->regs.pipe[0].palette[i] = +				  ((gma_crtc->lut_r[i] + +				  gma_crtc->lut_adj[i]) << 16) | +				  ((gma_crtc->lut_g[i] + +				  gma_crtc->lut_adj[i]) << 8) | +				  (gma_crtc->lut_b[i] + +				  gma_crtc->lut_adj[i]); +		} + +	} +} + +void gma_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green, u16 *blue, +			u32 start, u32 size) +{ +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int i; +	int end = (start + size > 256) ? 256 : start + size; + +	for (i = start; i < end; i++) { +		gma_crtc->lut_r[i] = red[i] >> 8; +		gma_crtc->lut_g[i] = green[i] >> 8; +		gma_crtc->lut_b[i] = blue[i] >> 8; +	} + +	gma_crtc_load_lut(crtc); +} + +/** + * Sets the power management mode of the pipe and plane. + * + * This code should probably grow support for turning the cursor off and back + * on appropriately at the same time as we're turning the pipe off/on. + */ +void gma_crtc_dpms(struct drm_crtc *crtc, int mode) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	u32 temp; + +	/* XXX: When our outputs are all unaware of DPMS modes other than off +	 * and on, we should map those modes to DRM_MODE_DPMS_OFF in the CRTC. +	 */ + +	if (IS_CDV(dev)) +		dev_priv->ops->disable_sr(dev); + +	switch (mode) { +	case DRM_MODE_DPMS_ON: +	case DRM_MODE_DPMS_STANDBY: +	case DRM_MODE_DPMS_SUSPEND: +		if (gma_crtc->active) +			break; + +		gma_crtc->active = true; + +		/* Enable the DPLL */ +		temp = REG_READ(map->dpll); +		if ((temp & DPLL_VCO_ENABLE) == 0) { +			REG_WRITE(map->dpll, temp); +			REG_READ(map->dpll); +			/* Wait for the clocks to stabilize. */ +			udelay(150); +			REG_WRITE(map->dpll, temp | DPLL_VCO_ENABLE); +			REG_READ(map->dpll); +			/* Wait for the clocks to stabilize. */ +			udelay(150); +			REG_WRITE(map->dpll, temp | DPLL_VCO_ENABLE); +			REG_READ(map->dpll); +			/* Wait for the clocks to stabilize. */ +			udelay(150); +		} + +		/* Enable the plane */ +		temp = REG_READ(map->cntr); +		if ((temp & DISPLAY_PLANE_ENABLE) == 0) { +			REG_WRITE(map->cntr, +				  temp | DISPLAY_PLANE_ENABLE); +			/* Flush the plane changes */ +			REG_WRITE(map->base, REG_READ(map->base)); +		} + +		udelay(150); + +		/* Enable the pipe */ +		temp = REG_READ(map->conf); +		if ((temp & PIPEACONF_ENABLE) == 0) +			REG_WRITE(map->conf, temp | PIPEACONF_ENABLE); + +		temp = REG_READ(map->status); +		temp &= ~(0xFFFF); +		temp |= PIPE_FIFO_UNDERRUN; +		REG_WRITE(map->status, temp); +		REG_READ(map->status); + +		gma_crtc_load_lut(crtc); + +		/* Give the overlay scaler a chance to enable +		 * if it's on this pipe */ +		/* psb_intel_crtc_dpms_video(crtc, true); TODO */ +		break; +	case DRM_MODE_DPMS_OFF: +		if (!gma_crtc->active) +			break; + +		gma_crtc->active = false; + +		/* Give the overlay scaler a chance to disable +		 * if it's on this pipe */ +		/* psb_intel_crtc_dpms_video(crtc, FALSE); TODO */ + +		/* Disable the VGA plane that we never use */ +		REG_WRITE(VGACNTRL, VGA_DISP_DISABLE); + +		/* Turn off vblank interrupts */ +		drm_vblank_off(dev, pipe); + +		/* Wait for vblank for the disable to take effect */ +		gma_wait_for_vblank(dev); + +		/* Disable plane */ +		temp = REG_READ(map->cntr); +		if ((temp & DISPLAY_PLANE_ENABLE) != 0) { +			REG_WRITE(map->cntr, +				  temp & ~DISPLAY_PLANE_ENABLE); +			/* Flush the plane changes */ +			REG_WRITE(map->base, REG_READ(map->base)); +			REG_READ(map->base); +		} + +		/* Disable pipe */ +		temp = REG_READ(map->conf); +		if ((temp & PIPEACONF_ENABLE) != 0) { +			REG_WRITE(map->conf, temp & ~PIPEACONF_ENABLE); +			REG_READ(map->conf); +		} + +		/* Wait for vblank for the disable to take effect. */ +		gma_wait_for_vblank(dev); + +		udelay(150); + +		/* Disable DPLL */ +		temp = REG_READ(map->dpll); +		if ((temp & DPLL_VCO_ENABLE) != 0) { +			REG_WRITE(map->dpll, temp & ~DPLL_VCO_ENABLE); +			REG_READ(map->dpll); +		} + +		/* Wait for the clocks to turn off. */ +		udelay(150); +		break; +	} + +	if (IS_CDV(dev)) +		dev_priv->ops->update_wm(dev, crtc); + +	/* Set FIFO watermarks */ +	REG_WRITE(DSPARB, 0x3F3E); +} + +int gma_crtc_cursor_set(struct drm_crtc *crtc, +			struct drm_file *file_priv, +			uint32_t handle, +			uint32_t width, uint32_t height) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int pipe = gma_crtc->pipe; +	uint32_t control = (pipe == 0) ? CURACNTR : CURBCNTR; +	uint32_t base = (pipe == 0) ? CURABASE : CURBBASE; +	uint32_t temp; +	size_t addr = 0; +	struct gtt_range *gt; +	struct gtt_range *cursor_gt = gma_crtc->cursor_gt; +	struct drm_gem_object *obj; +	void *tmp_dst, *tmp_src; +	int ret = 0, i, cursor_pages; + +	/* If we didn't get a handle then turn the cursor off */ +	if (!handle) { +		temp = CURSOR_MODE_DISABLE; +		mutex_lock(&dev->struct_mutex); + +		if (gma_power_begin(dev, false)) { +			REG_WRITE(control, temp); +			REG_WRITE(base, 0); +			gma_power_end(dev); +		} + +		/* Unpin the old GEM object */ +		if (gma_crtc->cursor_obj) { +			gt = container_of(gma_crtc->cursor_obj, +					  struct gtt_range, gem); +			psb_gtt_unpin(gt); +			drm_gem_object_unreference(gma_crtc->cursor_obj); +			gma_crtc->cursor_obj = NULL; +		} + +		mutex_unlock(&dev->struct_mutex); +		return 0; +	} + +	/* Currently we only support 64x64 cursors */ +	if (width != 64 || height != 64) { +		dev_dbg(dev->dev, "We currently only support 64x64 cursors\n"); +		return -EINVAL; +	} + +	mutex_lock(&dev->struct_mutex); +	obj = drm_gem_object_lookup(dev, file_priv, handle); +	if (!obj) { +		ret = -ENOENT; +		goto unlock; +	} + +	if (obj->size < width * height * 4) { +		dev_dbg(dev->dev, "Buffer is too small\n"); +		ret = -ENOMEM; +		goto unref_cursor; +	} + +	gt = container_of(obj, struct gtt_range, gem); + +	/* Pin the memory into the GTT */ +	ret = psb_gtt_pin(gt); +	if (ret) { +		dev_err(dev->dev, "Can not pin down handle 0x%x\n", handle); +		goto unref_cursor; +	} + +	if (dev_priv->ops->cursor_needs_phys) { +		if (cursor_gt == NULL) { +			dev_err(dev->dev, "No hardware cursor mem available"); +			ret = -ENOMEM; +			goto unref_cursor; +		} + +		/* Prevent overflow */ +		if (gt->npage > 4) +			cursor_pages = 4; +		else +			cursor_pages = gt->npage; + +		/* Copy the cursor to cursor mem */ +		tmp_dst = dev_priv->vram_addr + cursor_gt->offset; +		for (i = 0; i < cursor_pages; i++) { +			tmp_src = kmap(gt->pages[i]); +			memcpy(tmp_dst, tmp_src, PAGE_SIZE); +			kunmap(gt->pages[i]); +			tmp_dst += PAGE_SIZE; +		} + +		addr = gma_crtc->cursor_addr; +	} else { +		addr = gt->offset; +		gma_crtc->cursor_addr = addr; +	} + +	temp = 0; +	/* set the pipe for the cursor */ +	temp |= (pipe << 28); +	temp |= CURSOR_MODE_64_ARGB_AX | MCURSOR_GAMMA_ENABLE; + +	if (gma_power_begin(dev, false)) { +		REG_WRITE(control, temp); +		REG_WRITE(base, addr); +		gma_power_end(dev); +	} + +	/* unpin the old bo */ +	if (gma_crtc->cursor_obj) { +		gt = container_of(gma_crtc->cursor_obj, struct gtt_range, gem); +		psb_gtt_unpin(gt); +		drm_gem_object_unreference(gma_crtc->cursor_obj); +	} + +	gma_crtc->cursor_obj = obj; +unlock: +	mutex_unlock(&dev->struct_mutex); +	return ret; + +unref_cursor: +	drm_gem_object_unreference(obj); +	mutex_unlock(&dev->struct_mutex); +	return ret; +} + +int gma_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) +{ +	struct drm_device *dev = crtc->dev; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int pipe = gma_crtc->pipe; +	uint32_t temp = 0; +	uint32_t addr; + +	if (x < 0) { +		temp |= (CURSOR_POS_SIGN << CURSOR_X_SHIFT); +		x = -x; +	} +	if (y < 0) { +		temp |= (CURSOR_POS_SIGN << CURSOR_Y_SHIFT); +		y = -y; +	} + +	temp |= ((x & CURSOR_POS_MASK) << CURSOR_X_SHIFT); +	temp |= ((y & CURSOR_POS_MASK) << CURSOR_Y_SHIFT); + +	addr = gma_crtc->cursor_addr; + +	if (gma_power_begin(dev, false)) { +		REG_WRITE((pipe == 0) ? CURAPOS : CURBPOS, temp); +		REG_WRITE((pipe == 0) ? CURABASE : CURBBASE, addr); +		gma_power_end(dev); +	} +	return 0; +} + +bool gma_encoder_mode_fixup(struct drm_encoder *encoder, +			    const struct drm_display_mode *mode, +			    struct drm_display_mode *adjusted_mode) +{ +	return true; +} + +bool gma_crtc_mode_fixup(struct drm_crtc *crtc, +			 const struct drm_display_mode *mode, +			 struct drm_display_mode *adjusted_mode) +{ +	return true; +} + +void gma_crtc_prepare(struct drm_crtc *crtc) +{ +	struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; +	crtc_funcs->dpms(crtc, DRM_MODE_DPMS_OFF); +} + +void gma_crtc_commit(struct drm_crtc *crtc) +{ +	struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; +	crtc_funcs->dpms(crtc, DRM_MODE_DPMS_ON); +} + +void gma_crtc_disable(struct drm_crtc *crtc) +{ +	struct gtt_range *gt; +	struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; + +	crtc_funcs->dpms(crtc, DRM_MODE_DPMS_OFF); + +	if (crtc->primary->fb) { +		gt = to_psb_fb(crtc->primary->fb)->gtt; +		psb_gtt_unpin(gt); +	} +} + +void gma_crtc_destroy(struct drm_crtc *crtc) +{ +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); + +	kfree(gma_crtc->crtc_state); +	drm_crtc_cleanup(crtc); +	kfree(gma_crtc); +} + +int gma_crtc_set_config(struct drm_mode_set *set) +{ +	struct drm_device *dev = set->crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	int ret; + +	if (!dev_priv->rpm_enabled) +		return drm_crtc_helper_set_config(set); + +	pm_runtime_forbid(&dev->pdev->dev); +	ret = drm_crtc_helper_set_config(set); +	pm_runtime_allow(&dev->pdev->dev); + +	return ret; +} + +/** + * Save HW states of given crtc + */ +void gma_crtc_save(struct drm_crtc *crtc) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct psb_intel_crtc_state *crtc_state = gma_crtc->crtc_state; +	const struct psb_offset *map = &dev_priv->regmap[gma_crtc->pipe]; +	uint32_t palette_reg; +	int i; + +	if (!crtc_state) { +		dev_err(dev->dev, "No CRTC state found\n"); +		return; +	} + +	crtc_state->saveDSPCNTR = REG_READ(map->cntr); +	crtc_state->savePIPECONF = REG_READ(map->conf); +	crtc_state->savePIPESRC = REG_READ(map->src); +	crtc_state->saveFP0 = REG_READ(map->fp0); +	crtc_state->saveFP1 = REG_READ(map->fp1); +	crtc_state->saveDPLL = REG_READ(map->dpll); +	crtc_state->saveHTOTAL = REG_READ(map->htotal); +	crtc_state->saveHBLANK = REG_READ(map->hblank); +	crtc_state->saveHSYNC = REG_READ(map->hsync); +	crtc_state->saveVTOTAL = REG_READ(map->vtotal); +	crtc_state->saveVBLANK = REG_READ(map->vblank); +	crtc_state->saveVSYNC = REG_READ(map->vsync); +	crtc_state->saveDSPSTRIDE = REG_READ(map->stride); + +	/* NOTE: DSPSIZE DSPPOS only for psb */ +	crtc_state->saveDSPSIZE = REG_READ(map->size); +	crtc_state->saveDSPPOS = REG_READ(map->pos); + +	crtc_state->saveDSPBASE = REG_READ(map->base); + +	palette_reg = map->palette; +	for (i = 0; i < 256; ++i) +		crtc_state->savePalette[i] = REG_READ(palette_reg + (i << 2)); +} + +/** + * Restore HW states of given crtc + */ +void gma_crtc_restore(struct drm_crtc *crtc) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc =  to_gma_crtc(crtc); +	struct psb_intel_crtc_state *crtc_state = gma_crtc->crtc_state; +	const struct psb_offset *map = &dev_priv->regmap[gma_crtc->pipe]; +	uint32_t palette_reg; +	int i; + +	if (!crtc_state) { +		dev_err(dev->dev, "No crtc state\n"); +		return; +	} + +	if (crtc_state->saveDPLL & DPLL_VCO_ENABLE) { +		REG_WRITE(map->dpll, +			crtc_state->saveDPLL & ~DPLL_VCO_ENABLE); +		REG_READ(map->dpll); +		udelay(150); +	} + +	REG_WRITE(map->fp0, crtc_state->saveFP0); +	REG_READ(map->fp0); + +	REG_WRITE(map->fp1, crtc_state->saveFP1); +	REG_READ(map->fp1); + +	REG_WRITE(map->dpll, crtc_state->saveDPLL); +	REG_READ(map->dpll); +	udelay(150); + +	REG_WRITE(map->htotal, crtc_state->saveHTOTAL); +	REG_WRITE(map->hblank, crtc_state->saveHBLANK); +	REG_WRITE(map->hsync, crtc_state->saveHSYNC); +	REG_WRITE(map->vtotal, crtc_state->saveVTOTAL); +	REG_WRITE(map->vblank, crtc_state->saveVBLANK); +	REG_WRITE(map->vsync, crtc_state->saveVSYNC); +	REG_WRITE(map->stride, crtc_state->saveDSPSTRIDE); + +	REG_WRITE(map->size, crtc_state->saveDSPSIZE); +	REG_WRITE(map->pos, crtc_state->saveDSPPOS); + +	REG_WRITE(map->src, crtc_state->savePIPESRC); +	REG_WRITE(map->base, crtc_state->saveDSPBASE); +	REG_WRITE(map->conf, crtc_state->savePIPECONF); + +	gma_wait_for_vblank(dev); + +	REG_WRITE(map->cntr, crtc_state->saveDSPCNTR); +	REG_WRITE(map->base, crtc_state->saveDSPBASE); + +	gma_wait_for_vblank(dev); + +	palette_reg = map->palette; +	for (i = 0; i < 256; ++i) +		REG_WRITE(palette_reg + (i << 2), crtc_state->savePalette[i]); +} + +void gma_encoder_prepare(struct drm_encoder *encoder) +{ +	struct drm_encoder_helper_funcs *encoder_funcs = +	    encoder->helper_private; +	/* lvds has its own version of prepare see psb_intel_lvds_prepare */ +	encoder_funcs->dpms(encoder, DRM_MODE_DPMS_OFF); +} + +void gma_encoder_commit(struct drm_encoder *encoder) +{ +	struct drm_encoder_helper_funcs *encoder_funcs = +	    encoder->helper_private; +	/* lvds has its own version of commit see psb_intel_lvds_commit */ +	encoder_funcs->dpms(encoder, DRM_MODE_DPMS_ON); +} + +void gma_encoder_destroy(struct drm_encoder *encoder) +{ +	struct gma_encoder *intel_encoder = to_gma_encoder(encoder); + +	drm_encoder_cleanup(encoder); +	kfree(intel_encoder); +} + +/* Currently there is only a 1:1 mapping of encoders and connectors */ +struct drm_encoder *gma_best_encoder(struct drm_connector *connector) +{ +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); + +	return &gma_encoder->base; +} + +void gma_connector_attach_encoder(struct gma_connector *connector, +				  struct gma_encoder *encoder) +{ +	connector->encoder = encoder; +	drm_mode_connector_attach_encoder(&connector->base, +					  &encoder->base); +} + +#define GMA_PLL_INVALID(s) { /* DRM_ERROR(s); */ return false; } + +bool gma_pll_is_valid(struct drm_crtc *crtc, +		      const struct gma_limit_t *limit, +		      struct gma_clock_t *clock) +{ +	if (clock->p1 < limit->p1.min || limit->p1.max < clock->p1) +		GMA_PLL_INVALID("p1 out of range"); +	if (clock->p < limit->p.min || limit->p.max < clock->p) +		GMA_PLL_INVALID("p out of range"); +	if (clock->m2 < limit->m2.min || limit->m2.max < clock->m2) +		GMA_PLL_INVALID("m2 out of range"); +	if (clock->m1 < limit->m1.min || limit->m1.max < clock->m1) +		GMA_PLL_INVALID("m1 out of range"); +	/* On CDV m1 is always 0 */ +	if (clock->m1 <= clock->m2 && clock->m1 != 0) +		GMA_PLL_INVALID("m1 <= m2 && m1 != 0"); +	if (clock->m < limit->m.min || limit->m.max < clock->m) +		GMA_PLL_INVALID("m out of range"); +	if (clock->n < limit->n.min || limit->n.max < clock->n) +		GMA_PLL_INVALID("n out of range"); +	if (clock->vco < limit->vco.min || limit->vco.max < clock->vco) +		GMA_PLL_INVALID("vco out of range"); +	/* XXX: We may need to be checking "Dot clock" +	 * depending on the multiplier, connector, etc., +	 * rather than just a single range. +	 */ +	if (clock->dot < limit->dot.min || limit->dot.max < clock->dot) +		GMA_PLL_INVALID("dot out of range"); + +	return true; +} + +bool gma_find_best_pll(const struct gma_limit_t *limit, +		       struct drm_crtc *crtc, int target, int refclk, +		       struct gma_clock_t *best_clock) +{ +	struct drm_device *dev = crtc->dev; +	const struct gma_clock_funcs *clock_funcs = +						to_gma_crtc(crtc)->clock_funcs; +	struct gma_clock_t clock; +	int err = target; + +	if (gma_pipe_has_type(crtc, INTEL_OUTPUT_LVDS) && +	    (REG_READ(LVDS) & LVDS_PORT_EN) != 0) { +		/* +		 * For LVDS, if the panel is on, just rely on its current +		 * settings for dual-channel.  We haven't figured out how to +		 * reliably set up different single/dual channel state, if we +		 * even can. +		 */ +		if ((REG_READ(LVDS) & LVDS_CLKB_POWER_MASK) == +		    LVDS_CLKB_POWER_UP) +			clock.p2 = limit->p2.p2_fast; +		else +			clock.p2 = limit->p2.p2_slow; +	} else { +		if (target < limit->p2.dot_limit) +			clock.p2 = limit->p2.p2_slow; +		else +			clock.p2 = limit->p2.p2_fast; +	} + +	memset(best_clock, 0, sizeof(*best_clock)); + +	/* m1 is always 0 on CDV so the outmost loop will run just once */ +	for (clock.m1 = limit->m1.min; clock.m1 <= limit->m1.max; clock.m1++) { +		for (clock.m2 = limit->m2.min; +		     (clock.m2 < clock.m1 || clock.m1 == 0) && +		      clock.m2 <= limit->m2.max; clock.m2++) { +			for (clock.n = limit->n.min; +			     clock.n <= limit->n.max; clock.n++) { +				for (clock.p1 = limit->p1.min; +				     clock.p1 <= limit->p1.max; +				     clock.p1++) { +					int this_err; + +					clock_funcs->clock(refclk, &clock); + +					if (!clock_funcs->pll_is_valid(crtc, +								limit, &clock)) +						continue; + +					this_err = abs(clock.dot - target); +					if (this_err < err) { +						*best_clock = clock; +						err = this_err; +					} +				} +			} +		} +	} + +	return err != target; +} diff --git a/drivers/gpu/drm/gma500/gma_display.h b/drivers/gpu/drm/gma500/gma_display.h new file mode 100644 index 00000000000..ed569d8a6af --- /dev/null +++ b/drivers/gpu/drm/gma500/gma_display.h @@ -0,0 +1,106 @@ +/* + * Copyright © 2006-2011 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + *	Patrik Jakobsson <patrik.r.jakobsson@gmail.com> + */ + +#ifndef _GMA_DISPLAY_H_ +#define _GMA_DISPLAY_H_ + +#include <linux/pm_runtime.h> + +struct gma_clock_t { +	/* given values */ +	int n; +	int m1, m2; +	int p1, p2; +	/* derived values */ +	int dot; +	int vco; +	int m; +	int p; +}; + +struct gma_range_t { +	int min, max; +}; + +struct gma_p2_t { +	int dot_limit; +	int p2_slow, p2_fast; +}; + +struct gma_limit_t { +	struct gma_range_t dot, vco, n, m, m1, m2, p, p1; +	struct gma_p2_t p2; +	bool (*find_pll)(const struct gma_limit_t *, struct drm_crtc *, +			 int target, int refclk, +			 struct gma_clock_t *best_clock); +}; + +struct gma_clock_funcs { +	void (*clock)(int refclk, struct gma_clock_t *clock); +	const struct gma_limit_t *(*limit)(struct drm_crtc *crtc, int refclk); +	bool (*pll_is_valid)(struct drm_crtc *crtc, +			     const struct gma_limit_t *limit, +			     struct gma_clock_t *clock); +}; + +/* Common pipe related functions */ +extern bool gma_pipe_has_type(struct drm_crtc *crtc, int type); +extern void gma_wait_for_vblank(struct drm_device *dev); +extern int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y, +			     struct drm_framebuffer *old_fb); +extern int gma_crtc_cursor_set(struct drm_crtc *crtc, +			       struct drm_file *file_priv, +			       uint32_t handle, +			       uint32_t width, uint32_t height); +extern int gma_crtc_cursor_move(struct drm_crtc *crtc, int x, int y); +extern void gma_crtc_load_lut(struct drm_crtc *crtc); +extern void gma_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green, +			       u16 *blue, u32 start, u32 size); +extern void gma_crtc_dpms(struct drm_crtc *crtc, int mode); +extern bool gma_crtc_mode_fixup(struct drm_crtc *crtc, +				const struct drm_display_mode *mode, +				struct drm_display_mode *adjusted_mode); +extern void gma_crtc_prepare(struct drm_crtc *crtc); +extern void gma_crtc_commit(struct drm_crtc *crtc); +extern void gma_crtc_disable(struct drm_crtc *crtc); +extern void gma_crtc_destroy(struct drm_crtc *crtc); +extern int gma_crtc_set_config(struct drm_mode_set *set); + +extern void gma_crtc_save(struct drm_crtc *crtc); +extern void gma_crtc_restore(struct drm_crtc *crtc); + +extern void gma_encoder_prepare(struct drm_encoder *encoder); +extern void gma_encoder_commit(struct drm_encoder *encoder); +extern void gma_encoder_destroy(struct drm_encoder *encoder); +extern bool gma_encoder_mode_fixup(struct drm_encoder *encoder, +				   const struct drm_display_mode *mode, +				   struct drm_display_mode *adjusted_mode); + +/* Common clock related functions */ +extern const struct gma_limit_t *gma_limit(struct drm_crtc *crtc, int refclk); +extern void gma_clock(int refclk, struct gma_clock_t *clock); +extern bool gma_pll_is_valid(struct drm_crtc *crtc, +			     const struct gma_limit_t *limit, +			     struct gma_clock_t *clock); +extern bool gma_find_best_pll(const struct gma_limit_t *limit, +			      struct drm_crtc *crtc, int target, int refclk, +			      struct gma_clock_t *best_clock); +#endif diff --git a/drivers/gpu/drm/gma500/gtt.c b/drivers/gpu/drm/gma500/gtt.c new file mode 100644 index 00000000000..592d205a008 --- /dev/null +++ b/drivers/gpu/drm/gma500/gtt.c @@ -0,0 +1,587 @@ +/* + * Copyright (c) 2007, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: Thomas Hellstrom <thomas-at-tungstengraphics.com> + *	    Alan Cox <alan@linux.intel.com> + */ + +#include <drm/drmP.h> +#include <linux/shmem_fs.h> +#include "psb_drv.h" +#include "blitter.h" + + +/* + *	GTT resource allocator - manage page mappings in GTT space + */ + +/** + *	psb_gtt_mask_pte	-	generate GTT pte entry + *	@pfn: page number to encode + *	@type: type of memory in the GTT + * + *	Set the GTT entry for the appropriate memory type. + */ +static inline uint32_t psb_gtt_mask_pte(uint32_t pfn, int type) +{ +	uint32_t mask = PSB_PTE_VALID; + +	/* Ensure we explode rather than put an invalid low mapping of +	   a high mapping page into the gtt */ +	BUG_ON(pfn & ~(0xFFFFFFFF >> PAGE_SHIFT)); + +	if (type & PSB_MMU_CACHED_MEMORY) +		mask |= PSB_PTE_CACHED; +	if (type & PSB_MMU_RO_MEMORY) +		mask |= PSB_PTE_RO; +	if (type & PSB_MMU_WO_MEMORY) +		mask |= PSB_PTE_WO; + +	return (pfn << PAGE_SHIFT) | mask; +} + +/** + *	psb_gtt_entry		-	find the GTT entries for a gtt_range + *	@dev: our DRM device + *	@r: our GTT range + * + *	Given a gtt_range object return the GTT offset of the page table + *	entries for this gtt_range + */ +static u32 __iomem *psb_gtt_entry(struct drm_device *dev, struct gtt_range *r) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	unsigned long offset; + +	offset = r->resource.start - dev_priv->gtt_mem->start; + +	return dev_priv->gtt_map + (offset >> PAGE_SHIFT); +} + +/** + *	psb_gtt_insert	-	put an object into the GTT + *	@dev: our DRM device + *	@r: our GTT range + * + *	Take our preallocated GTT range and insert the GEM object into + *	the GTT. This is protected via the gtt mutex which the caller + *	must hold. + */ +static int psb_gtt_insert(struct drm_device *dev, struct gtt_range *r, +			  int resume) +{ +	u32 __iomem *gtt_slot; +	u32 pte; +	struct page **pages; +	int i; + +	if (r->pages == NULL) { +		WARN_ON(1); +		return -EINVAL; +	} + +	WARN_ON(r->stolen);	/* refcount these maybe ? */ + +	gtt_slot = psb_gtt_entry(dev, r); +	pages = r->pages; + +	if (!resume) { +		/* Make sure changes are visible to the GPU */ +		set_pages_array_wc(pages, r->npage); +	} + +	/* Write our page entries into the GTT itself */ +	for (i = r->roll; i < r->npage; i++) { +		pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]), +				       PSB_MMU_CACHED_MEMORY); +		iowrite32(pte, gtt_slot++); +	} +	for (i = 0; i < r->roll; i++) { +		pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]), +				       PSB_MMU_CACHED_MEMORY); +		iowrite32(pte, gtt_slot++); +	} +	/* Make sure all the entries are set before we return */ +	ioread32(gtt_slot - 1); + +	return 0; +} + +/** + *	psb_gtt_remove	-	remove an object from the GTT + *	@dev: our DRM device + *	@r: our GTT range + * + *	Remove a preallocated GTT range from the GTT. Overwrite all the + *	page table entries with the dummy page. This is protected via the gtt + *	mutex which the caller must hold. + */ +void psb_gtt_remove(struct drm_device *dev, struct gtt_range *r) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 __iomem *gtt_slot; +	u32 pte; +	int i; + +	WARN_ON(r->stolen); + +	gtt_slot = psb_gtt_entry(dev, r); +	pte = psb_gtt_mask_pte(page_to_pfn(dev_priv->scratch_page), +			       PSB_MMU_CACHED_MEMORY); + +	for (i = 0; i < r->npage; i++) +		iowrite32(pte, gtt_slot++); +	ioread32(gtt_slot - 1); +	set_pages_array_wb(r->pages, r->npage); +} + +/** + *	psb_gtt_roll	-	set scrolling position + *	@dev: our DRM device + *	@r: the gtt mapping we are using + *	@roll: roll offset + * + *	Roll an existing pinned mapping by moving the pages through the GTT. + *	This allows us to implement hardware scrolling on the consoles without + *	a 2D engine + */ +void psb_gtt_roll(struct drm_device *dev, struct gtt_range *r, int roll) +{ +	u32 __iomem *gtt_slot; +	u32 pte; +	int i; + +	if (roll >= r->npage) { +		WARN_ON(1); +		return; +	} + +	r->roll = roll; + +	/* Not currently in the GTT - no worry we will write the mapping at +	   the right position when it gets pinned */ +	if (!r->stolen && !r->in_gart) +		return; + +	gtt_slot = psb_gtt_entry(dev, r); + +	for (i = r->roll; i < r->npage; i++) { +		pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]), +				       PSB_MMU_CACHED_MEMORY); +		iowrite32(pte, gtt_slot++); +	} +	for (i = 0; i < r->roll; i++) { +		pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]), +				       PSB_MMU_CACHED_MEMORY); +		iowrite32(pte, gtt_slot++); +	} +	ioread32(gtt_slot - 1); +} + +/** + *	psb_gtt_attach_pages	-	attach and pin GEM pages + *	@gt: the gtt range + * + *	Pin and build an in kernel list of the pages that back our GEM object. + *	While we hold this the pages cannot be swapped out. This is protected + *	via the gtt mutex which the caller must hold. + */ +static int psb_gtt_attach_pages(struct gtt_range *gt) +{ +	struct page **pages; + +	WARN_ON(gt->pages); + +	pages = drm_gem_get_pages(>->gem, 0); +	if (IS_ERR(pages)) +		return PTR_ERR(pages); + +	gt->npage = gt->gem.size / PAGE_SIZE; +	gt->pages = pages; + +	return 0; +} + +/** + *	psb_gtt_detach_pages	-	attach and pin GEM pages + *	@gt: the gtt range + * + *	Undo the effect of psb_gtt_attach_pages. At this point the pages + *	must have been removed from the GTT as they could now be paged out + *	and move bus address. This is protected via the gtt mutex which the + *	caller must hold. + */ +static void psb_gtt_detach_pages(struct gtt_range *gt) +{ +	drm_gem_put_pages(>->gem, gt->pages, true, false); +	gt->pages = NULL; +} + +/** + *	psb_gtt_pin		-	pin pages into the GTT + *	@gt: range to pin + * + *	Pin a set of pages into the GTT. The pins are refcounted so that + *	multiple pins need multiple unpins to undo. + * + *	Non GEM backed objects treat this as a no-op as they are always GTT + *	backed objects. + */ +int psb_gtt_pin(struct gtt_range *gt) +{ +	int ret = 0; +	struct drm_device *dev = gt->gem.dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 gpu_base = dev_priv->gtt.gatt_start; + +	mutex_lock(&dev_priv->gtt_mutex); + +	if (gt->in_gart == 0 && gt->stolen == 0) { +		ret = psb_gtt_attach_pages(gt); +		if (ret < 0) +			goto out; +		ret = psb_gtt_insert(dev, gt, 0); +		if (ret < 0) { +			psb_gtt_detach_pages(gt); +			goto out; +		} +		psb_mmu_insert_pages(psb_mmu_get_default_pd(dev_priv->mmu), +				     gt->pages, (gpu_base + gt->offset), +				     gt->npage, 0, 0, PSB_MMU_CACHED_MEMORY); +	} +	gt->in_gart++; +out: +	mutex_unlock(&dev_priv->gtt_mutex); +	return ret; +} + +/** + *	psb_gtt_unpin		-	Drop a GTT pin requirement + *	@gt: range to pin + * + *	Undoes the effect of psb_gtt_pin. On the last drop the GEM object + *	will be removed from the GTT which will also drop the page references + *	and allow the VM to clean up or page stuff. + * + *	Non GEM backed objects treat this as a no-op as they are always GTT + *	backed objects. + */ +void psb_gtt_unpin(struct gtt_range *gt) +{ +	struct drm_device *dev = gt->gem.dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 gpu_base = dev_priv->gtt.gatt_start; +	int ret; + +	/* While holding the gtt_mutex no new blits can be initiated */ +	mutex_lock(&dev_priv->gtt_mutex); + +	/* Wait for any possible usage of the memory to be finished */ +	ret = gma_blt_wait_idle(dev_priv); +	if (ret) { +		DRM_ERROR("Failed to idle the blitter, unpin failed!"); +		goto out; +	} + +	WARN_ON(!gt->in_gart); + +	gt->in_gart--; +	if (gt->in_gart == 0 && gt->stolen == 0) { +		psb_mmu_remove_pages(psb_mmu_get_default_pd(dev_priv->mmu), +				     (gpu_base + gt->offset), gt->npage, 0, 0); +		psb_gtt_remove(dev, gt); +		psb_gtt_detach_pages(gt); +	} + +out: +	mutex_unlock(&dev_priv->gtt_mutex); +} + +/* + *	GTT resource allocator - allocate and manage GTT address space + */ + +/** + *	psb_gtt_alloc_range	-	allocate GTT address space + *	@dev: Our DRM device + *	@len: length (bytes) of address space required + *	@name: resource name + *	@backed: resource should be backed by stolen pages + * + *	Ask the kernel core to find us a suitable range of addresses + *	to use for a GTT mapping. + * + *	Returns a gtt_range structure describing the object, or NULL on + *	error. On successful return the resource is both allocated and marked + *	as in use. + */ +struct gtt_range *psb_gtt_alloc_range(struct drm_device *dev, int len, +				      const char *name, int backed, u32 align) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gtt_range *gt; +	struct resource *r = dev_priv->gtt_mem; +	int ret; +	unsigned long start, end; + +	if (backed) { +		/* The start of the GTT is the stolen pages */ +		start = r->start; +		end = r->start + dev_priv->gtt.stolen_size - 1; +	} else { +		/* The rest we will use for GEM backed objects */ +		start = r->start + dev_priv->gtt.stolen_size; +		end = r->end; +	} + +	gt = kzalloc(sizeof(struct gtt_range), GFP_KERNEL); +	if (gt == NULL) +		return NULL; +	gt->resource.name = name; +	gt->stolen = backed; +	gt->in_gart = backed; +	gt->roll = 0; +	/* Ensure this is set for non GEM objects */ +	gt->gem.dev = dev; +	ret = allocate_resource(dev_priv->gtt_mem, >->resource, +				len, start, end, align, NULL, NULL); +	if (ret == 0) { +		gt->offset = gt->resource.start - r->start; +		return gt; +	} +	kfree(gt); +	return NULL; +} + +/** + *	psb_gtt_free_range	-	release GTT address space + *	@dev: our DRM device + *	@gt: a mapping created with psb_gtt_alloc_range + * + *	Release a resource that was allocated with psb_gtt_alloc_range. If the + *	object has been pinned by mmap users we clean this up here currently. + */ +void psb_gtt_free_range(struct drm_device *dev, struct gtt_range *gt) +{ +	/* Undo the mmap pin if we are destroying the object */ +	if (gt->mmapping) { +		psb_gtt_unpin(gt); +		gt->mmapping = 0; +	} +	WARN_ON(gt->in_gart && !gt->stolen); +	release_resource(>->resource); +	kfree(gt); +} + +static void psb_gtt_alloc(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	init_rwsem(&dev_priv->gtt.sem); +} + +void psb_gtt_takedown(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (dev_priv->gtt_map) { +		iounmap(dev_priv->gtt_map); +		dev_priv->gtt_map = NULL; +	} +	if (dev_priv->gtt_initialized) { +		pci_write_config_word(dev->pdev, PSB_GMCH_CTRL, +				      dev_priv->gmch_ctrl); +		PSB_WVDC32(dev_priv->pge_ctl, PSB_PGETBL_CTL); +		(void) PSB_RVDC32(PSB_PGETBL_CTL); +	} +	if (dev_priv->vram_addr) +		iounmap(dev_priv->gtt_map); +} + +int psb_gtt_init(struct drm_device *dev, int resume) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	unsigned gtt_pages; +	unsigned long stolen_size, vram_stolen_size; +	unsigned i, num_pages; +	unsigned pfn_base; +	struct psb_gtt *pg; + +	int ret = 0; +	uint32_t pte; + +	if (!resume) { +		mutex_init(&dev_priv->gtt_mutex); +		psb_gtt_alloc(dev); +	} + +	pg = &dev_priv->gtt; + +	/* Enable the GTT */ +	pci_read_config_word(dev->pdev, PSB_GMCH_CTRL, &dev_priv->gmch_ctrl); +	pci_write_config_word(dev->pdev, PSB_GMCH_CTRL, +			      dev_priv->gmch_ctrl | _PSB_GMCH_ENABLED); + +	dev_priv->pge_ctl = PSB_RVDC32(PSB_PGETBL_CTL); +	PSB_WVDC32(dev_priv->pge_ctl | _PSB_PGETBL_ENABLED, PSB_PGETBL_CTL); +	(void) PSB_RVDC32(PSB_PGETBL_CTL); + +	/* The root resource we allocate address space from */ +	dev_priv->gtt_initialized = 1; + +	pg->gtt_phys_start = dev_priv->pge_ctl & PAGE_MASK; + +	/* +	 *	The video mmu has a hw bug when accessing 0x0D0000000. +	 *	Make gatt start at 0x0e000,0000. This doesn't actually +	 *	matter for us but may do if the video acceleration ever +	 *	gets opened up. +	 */ +	pg->mmu_gatt_start = 0xE0000000; + +	pg->gtt_start = pci_resource_start(dev->pdev, PSB_GTT_RESOURCE); +	gtt_pages = pci_resource_len(dev->pdev, PSB_GTT_RESOURCE) +								>> PAGE_SHIFT; +	/* CDV doesn't report this. In which case the system has 64 gtt pages */ +	if (pg->gtt_start == 0 || gtt_pages == 0) { +		dev_dbg(dev->dev, "GTT PCI BAR not initialized.\n"); +		gtt_pages = 64; +		pg->gtt_start = dev_priv->pge_ctl; +	} + +	pg->gatt_start = pci_resource_start(dev->pdev, PSB_GATT_RESOURCE); +	pg->gatt_pages = pci_resource_len(dev->pdev, PSB_GATT_RESOURCE) +								>> PAGE_SHIFT; +	dev_priv->gtt_mem = &dev->pdev->resource[PSB_GATT_RESOURCE]; + +	if (pg->gatt_pages == 0 || pg->gatt_start == 0) { +		static struct resource fudge;	/* Preferably peppermint */ +		/* This can occur on CDV systems. Fudge it in this case. +		   We really don't care what imaginary space is being allocated +		   at this point */ +		dev_dbg(dev->dev, "GATT PCI BAR not initialized.\n"); +		pg->gatt_start = 0x40000000; +		pg->gatt_pages = (128 * 1024 * 1024) >> PAGE_SHIFT; +		/* This is a little confusing but in fact the GTT is providing +		   a view from the GPU into memory and not vice versa. As such +		   this is really allocating space that is not the same as the +		   CPU address space on CDV */ +		fudge.start = 0x40000000; +		fudge.end = 0x40000000 + 128 * 1024 * 1024 - 1; +		fudge.name = "fudge"; +		fudge.flags = IORESOURCE_MEM; +		dev_priv->gtt_mem = &fudge; +	} + +	pci_read_config_dword(dev->pdev, PSB_BSM, &dev_priv->stolen_base); +	vram_stolen_size = pg->gtt_phys_start - dev_priv->stolen_base +								- PAGE_SIZE; + +	stolen_size = vram_stolen_size; + +	dev_dbg(dev->dev, "Stolen memory base 0x%x, size %luK\n", +			dev_priv->stolen_base, vram_stolen_size / 1024); + +	if (resume && (gtt_pages != pg->gtt_pages) && +	    (stolen_size != pg->stolen_size)) { +		dev_err(dev->dev, "GTT resume error.\n"); +		ret = -EINVAL; +		goto out_err; +	} + +	pg->gtt_pages = gtt_pages; +	pg->stolen_size = stolen_size; +	dev_priv->vram_stolen_size = vram_stolen_size; + +	/* +	 *	Map the GTT and the stolen memory area +	 */ +	if (!resume) +		dev_priv->gtt_map = ioremap_nocache(pg->gtt_phys_start, +						gtt_pages << PAGE_SHIFT); +	if (!dev_priv->gtt_map) { +		dev_err(dev->dev, "Failure to map gtt.\n"); +		ret = -ENOMEM; +		goto out_err; +	} + +	if (!resume) +		dev_priv->vram_addr = ioremap_wc(dev_priv->stolen_base, +						 stolen_size); + +	if (!dev_priv->vram_addr) { +		dev_err(dev->dev, "Failure to map stolen base.\n"); +		ret = -ENOMEM; +		goto out_err; +	} + +	/* +	 * Insert vram stolen pages into the GTT +	 */ + +	pfn_base = dev_priv->stolen_base >> PAGE_SHIFT; +	num_pages = vram_stolen_size >> PAGE_SHIFT; +	dev_dbg(dev->dev, "Set up %d stolen pages starting at 0x%08x, GTT offset %dK\n", +		num_pages, pfn_base << PAGE_SHIFT, 0); +	for (i = 0; i < num_pages; ++i) { +		pte = psb_gtt_mask_pte(pfn_base + i, PSB_MMU_CACHED_MEMORY); +		iowrite32(pte, dev_priv->gtt_map + i); +	} + +	/* +	 * Init rest of GTT to the scratch page to avoid accidents or scribbles +	 */ + +	pfn_base = page_to_pfn(dev_priv->scratch_page); +	pte = psb_gtt_mask_pte(pfn_base, PSB_MMU_CACHED_MEMORY); +	for (; i < gtt_pages; ++i) +		iowrite32(pte, dev_priv->gtt_map + i); + +	(void) ioread32(dev_priv->gtt_map + i - 1); +	return 0; + +out_err: +	psb_gtt_takedown(dev); +	return ret; +} + +int psb_gtt_restore(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct resource *r = dev_priv->gtt_mem->child; +	struct gtt_range *range; +	unsigned int restored = 0, total = 0, size = 0; + +	/* On resume, the gtt_mutex is already initialized */ +	mutex_lock(&dev_priv->gtt_mutex); +	psb_gtt_init(dev, 1); + +	while (r != NULL) { +		range = container_of(r, struct gtt_range, resource); +		if (range->pages) { +			psb_gtt_insert(dev, range, 1); +			size += range->resource.end - range->resource.start; +			restored++; +		} +		r = r->sibling; +		total++; +	} +	mutex_unlock(&dev_priv->gtt_mutex); +	DRM_DEBUG_DRIVER("Restored %u of %u gtt ranges (%u KB)", restored, +			 total, (size / 1024)); + +	return 0; +} diff --git a/drivers/gpu/drm/gma500/gtt.h b/drivers/gpu/drm/gma500/gtt.h new file mode 100644 index 00000000000..f5860a739bd --- /dev/null +++ b/drivers/gpu/drm/gma500/gtt.h @@ -0,0 +1,65 @@ +/************************************************************************** + * Copyright (c) 2007-2008, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +#ifndef _PSB_GTT_H_ +#define _PSB_GTT_H_ + +#include <drm/drmP.h> + +/* This wants cleaning up with respect to the psb_dev and un-needed stuff */ +struct psb_gtt { +	uint32_t gatt_start; +	uint32_t mmu_gatt_start; +	uint32_t gtt_start; +	uint32_t gtt_phys_start; +	unsigned gtt_pages; +	unsigned gatt_pages; +	unsigned long stolen_size; +	unsigned long vram_stolen_size; +	struct rw_semaphore sem; +}; + +/* Exported functions */ +extern int psb_gtt_init(struct drm_device *dev, int resume); +extern void psb_gtt_takedown(struct drm_device *dev); + +/* Each gtt_range describes an allocation in the GTT area */ +struct gtt_range { +	struct resource resource;	/* Resource for our allocation */ +	u32 offset;			/* GTT offset of our object */ +	struct drm_gem_object gem;	/* GEM high level stuff */ +	int in_gart;			/* Currently in the GART (ref ct) */ +	bool stolen;			/* Backed from stolen RAM */ +	bool mmapping;			/* Is mmappable */ +	struct page **pages;		/* Backing pages if present */ +	int npage;			/* Number of backing pages */ +	int roll;			/* Roll applied to the GTT entries */ +}; + +extern struct gtt_range *psb_gtt_alloc_range(struct drm_device *dev, int len, +					     const char *name, int backed, +					     u32 align); +extern void psb_gtt_kref_put(struct gtt_range *gt); +extern void psb_gtt_free_range(struct drm_device *dev, struct gtt_range *gt); +extern int psb_gtt_pin(struct gtt_range *gt); +extern void psb_gtt_unpin(struct gtt_range *gt); +extern void psb_gtt_roll(struct drm_device *dev, +					struct gtt_range *gt, int roll); +extern int psb_gtt_restore(struct drm_device *dev); +#endif diff --git a/drivers/gpu/drm/gma500/intel_bios.c b/drivers/gpu/drm/gma500/intel_bios.c new file mode 100644 index 00000000000..d3497348c4d --- /dev/null +++ b/drivers/gpu/drm/gma500/intel_bios.c @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2006 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *    Eric Anholt <eric@anholt.net> + * + */ +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/gma_drm.h> +#include "psb_drv.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "intel_bios.h" + +#define	SLAVE_ADDR1	0x70 +#define	SLAVE_ADDR2	0x72 + +static void *find_section(struct bdb_header *bdb, int section_id) +{ +	u8 *base = (u8 *)bdb; +	int index = 0; +	u16 total, current_size; +	u8 current_id; + +	/* skip to first section */ +	index += bdb->header_size; +	total = bdb->bdb_size; + +	/* walk the sections looking for section_id */ +	while (index < total) { +		current_id = *(base + index); +		index++; +		current_size = *((u16 *)(base + index)); +		index += 2; +		if (current_id == section_id) +			return base + index; +		index += current_size; +	} + +	return NULL; +} + +static void +parse_edp(struct drm_psb_private *dev_priv, struct bdb_header *bdb) +{ +	struct bdb_edp *edp; +	struct edp_power_seq *edp_pps; +	struct edp_link_params *edp_link_params; +	uint8_t	panel_type; + +	edp = find_section(bdb, BDB_EDP); +	 +	dev_priv->edp.bpp = 18; +	if (!edp) { +		if (dev_priv->edp.support) { +			DRM_DEBUG_KMS("No eDP BDB found but eDP panel supported, assume %dbpp panel color depth.\n", +				      dev_priv->edp.bpp); +		} +		return; +	} + +	panel_type = dev_priv->panel_type; +	switch ((edp->color_depth >> (panel_type * 2)) & 3) { +	case EDP_18BPP: +		dev_priv->edp.bpp = 18; +		break; +	case EDP_24BPP: +		dev_priv->edp.bpp = 24; +		break; +	case EDP_30BPP: +		dev_priv->edp.bpp = 30; +		break; +	} + +	/* Get the eDP sequencing and link info */ +	edp_pps = &edp->power_seqs[panel_type]; +	edp_link_params = &edp->link_params[panel_type]; + +	dev_priv->edp.pps = *edp_pps; + +	DRM_DEBUG_KMS("EDP timing in vbt t1_t3 %d t8 %d t9 %d t10 %d t11_t12 %d\n", +				dev_priv->edp.pps.t1_t3, dev_priv->edp.pps.t8,  +				dev_priv->edp.pps.t9, dev_priv->edp.pps.t10, +				dev_priv->edp.pps.t11_t12); + +	dev_priv->edp.rate = edp_link_params->rate ? DP_LINK_BW_2_7 : +		DP_LINK_BW_1_62; +	switch (edp_link_params->lanes) { +	case 0: +		dev_priv->edp.lanes = 1; +		break; +	case 1: +		dev_priv->edp.lanes = 2; +		break; +	case 3: +	default: +		dev_priv->edp.lanes = 4; +		break; +	} +	DRM_DEBUG_KMS("VBT reports EDP: Lane_count %d, Lane_rate %d, Bpp %d\n", +			dev_priv->edp.lanes, dev_priv->edp.rate, dev_priv->edp.bpp); + +	switch (edp_link_params->preemphasis) { +	case 0: +		dev_priv->edp.preemphasis = DP_TRAIN_PRE_EMPHASIS_0; +		break; +	case 1: +		dev_priv->edp.preemphasis = DP_TRAIN_PRE_EMPHASIS_3_5; +		break; +	case 2: +		dev_priv->edp.preemphasis = DP_TRAIN_PRE_EMPHASIS_6; +		break; +	case 3: +		dev_priv->edp.preemphasis = DP_TRAIN_PRE_EMPHASIS_9_5; +		break; +	} +	switch (edp_link_params->vswing) { +	case 0: +		dev_priv->edp.vswing = DP_TRAIN_VOLTAGE_SWING_400; +		break; +	case 1: +		dev_priv->edp.vswing = DP_TRAIN_VOLTAGE_SWING_600; +		break; +	case 2: +		dev_priv->edp.vswing = DP_TRAIN_VOLTAGE_SWING_800; +		break; +	case 3: +		dev_priv->edp.vswing = DP_TRAIN_VOLTAGE_SWING_1200; +		break; +	} +	DRM_DEBUG_KMS("VBT reports EDP: VSwing  %d, Preemph %d\n", +			dev_priv->edp.vswing, dev_priv->edp.preemphasis); +} + +static u16 +get_blocksize(void *p) +{ +	u16 *block_ptr, block_size; + +	block_ptr = (u16 *)((char *)p - 2); +	block_size = *block_ptr; +	return block_size; +} + +static void fill_detail_timing_data(struct drm_display_mode *panel_fixed_mode, +			struct lvds_dvo_timing *dvo_timing) +{ +	panel_fixed_mode->hdisplay = (dvo_timing->hactive_hi << 8) | +		dvo_timing->hactive_lo; +	panel_fixed_mode->hsync_start = panel_fixed_mode->hdisplay + +		((dvo_timing->hsync_off_hi << 8) | dvo_timing->hsync_off_lo); +	panel_fixed_mode->hsync_end = panel_fixed_mode->hsync_start + +		dvo_timing->hsync_pulse_width; +	panel_fixed_mode->htotal = panel_fixed_mode->hdisplay + +		((dvo_timing->hblank_hi << 8) | dvo_timing->hblank_lo); + +	panel_fixed_mode->vdisplay = (dvo_timing->vactive_hi << 8) | +		dvo_timing->vactive_lo; +	panel_fixed_mode->vsync_start = panel_fixed_mode->vdisplay + +		dvo_timing->vsync_off; +	panel_fixed_mode->vsync_end = panel_fixed_mode->vsync_start + +		dvo_timing->vsync_pulse_width; +	panel_fixed_mode->vtotal = panel_fixed_mode->vdisplay + +		((dvo_timing->vblank_hi << 8) | dvo_timing->vblank_lo); +	panel_fixed_mode->clock = dvo_timing->clock * 10; +	panel_fixed_mode->type = DRM_MODE_TYPE_PREFERRED; + +	if (dvo_timing->hsync_positive) +		panel_fixed_mode->flags |= DRM_MODE_FLAG_PHSYNC; +	else +		panel_fixed_mode->flags |= DRM_MODE_FLAG_NHSYNC; + +	if (dvo_timing->vsync_positive) +		panel_fixed_mode->flags |= DRM_MODE_FLAG_PVSYNC; +	else +		panel_fixed_mode->flags |= DRM_MODE_FLAG_NVSYNC; + +	/* Some VBTs have bogus h/vtotal values */ +	if (panel_fixed_mode->hsync_end > panel_fixed_mode->htotal) +		panel_fixed_mode->htotal = panel_fixed_mode->hsync_end + 1; +	if (panel_fixed_mode->vsync_end > panel_fixed_mode->vtotal) +		panel_fixed_mode->vtotal = panel_fixed_mode->vsync_end + 1; + +	drm_mode_set_name(panel_fixed_mode); +} + +static void parse_backlight_data(struct drm_psb_private *dev_priv, +				struct bdb_header *bdb) +{ +	struct bdb_lvds_backlight *vbt_lvds_bl = NULL; +	struct bdb_lvds_backlight *lvds_bl; +	u8 p_type = 0; +	void *bl_start = NULL; +	struct bdb_lvds_options *lvds_opts +				= find_section(bdb, BDB_LVDS_OPTIONS); + +	dev_priv->lvds_bl = NULL; + +	if (lvds_opts) +		p_type = lvds_opts->panel_type; +	else +		return; + +	bl_start = find_section(bdb, BDB_LVDS_BACKLIGHT); +	vbt_lvds_bl = (struct bdb_lvds_backlight *)(bl_start + 1) + p_type; + +	lvds_bl = kmemdup(vbt_lvds_bl, sizeof(*vbt_lvds_bl), GFP_KERNEL); +	if (!lvds_bl) { +		dev_err(dev_priv->dev->dev, "out of memory for backlight data\n"); +		return; +	} +	dev_priv->lvds_bl = lvds_bl; +} + +/* Try to find integrated panel data */ +static void parse_lfp_panel_data(struct drm_psb_private *dev_priv, +			    struct bdb_header *bdb) +{ +	struct bdb_lvds_options *lvds_options; +	struct bdb_lvds_lfp_data *lvds_lfp_data; +	struct bdb_lvds_lfp_data_entry *entry; +	struct lvds_dvo_timing *dvo_timing; +	struct drm_display_mode *panel_fixed_mode; + +	/* Defaults if we can't find VBT info */ +	dev_priv->lvds_dither = 0; +	dev_priv->lvds_vbt = 0; + +	lvds_options = find_section(bdb, BDB_LVDS_OPTIONS); +	if (!lvds_options) +		return; + +	dev_priv->lvds_dither = lvds_options->pixel_dither; +	dev_priv->panel_type = lvds_options->panel_type; + +	if (lvds_options->panel_type == 0xff) +		return; + +	lvds_lfp_data = find_section(bdb, BDB_LVDS_LFP_DATA); +	if (!lvds_lfp_data) +		return; + + +	entry = &lvds_lfp_data->data[lvds_options->panel_type]; +	dvo_timing = &entry->dvo_timing; + +	panel_fixed_mode = kzalloc(sizeof(*panel_fixed_mode), +				      GFP_KERNEL); +	if (panel_fixed_mode == NULL) { +		dev_err(dev_priv->dev->dev, "out of memory for fixed panel mode\n"); +		return; +	} + +	dev_priv->lvds_vbt = 1; +	fill_detail_timing_data(panel_fixed_mode, dvo_timing); + +	if (panel_fixed_mode->htotal > 0 && panel_fixed_mode->vtotal > 0) { +		dev_priv->lfp_lvds_vbt_mode = panel_fixed_mode; +		drm_mode_debug_printmodeline(panel_fixed_mode); +	} else { +		dev_dbg(dev_priv->dev->dev, "ignoring invalid LVDS VBT\n"); +		dev_priv->lvds_vbt = 0; +		kfree(panel_fixed_mode); +	} +	return; +} + +/* Try to find sdvo panel data */ +static void parse_sdvo_panel_data(struct drm_psb_private *dev_priv, +		      struct bdb_header *bdb) +{ +	struct bdb_sdvo_lvds_options *sdvo_lvds_options; +	struct lvds_dvo_timing *dvo_timing; +	struct drm_display_mode *panel_fixed_mode; + +	dev_priv->sdvo_lvds_vbt_mode = NULL; + +	sdvo_lvds_options = find_section(bdb, BDB_SDVO_LVDS_OPTIONS); +	if (!sdvo_lvds_options) +		return; + +	dvo_timing = find_section(bdb, BDB_SDVO_PANEL_DTDS); +	if (!dvo_timing) +		return; + +	panel_fixed_mode = kzalloc(sizeof(*panel_fixed_mode), GFP_KERNEL); + +	if (!panel_fixed_mode) +		return; + +	fill_detail_timing_data(panel_fixed_mode, +			dvo_timing + sdvo_lvds_options->panel_type); + +	dev_priv->sdvo_lvds_vbt_mode = panel_fixed_mode; + +	return; +} + +static void parse_general_features(struct drm_psb_private *dev_priv, +		       struct bdb_header *bdb) +{ +	struct bdb_general_features *general; + +	/* Set sensible defaults in case we can't find the general block */ +	dev_priv->int_tv_support = 1; +	dev_priv->int_crt_support = 1; + +	general = find_section(bdb, BDB_GENERAL_FEATURES); +	if (general) { +		dev_priv->int_tv_support = general->int_tv_support; +		dev_priv->int_crt_support = general->int_crt_support; +		dev_priv->lvds_use_ssc = general->enable_ssc; + +		if (dev_priv->lvds_use_ssc) { +			dev_priv->lvds_ssc_freq +				= general->ssc_freq ? 100 : 96; +		} +	} +} + +static void +parse_sdvo_device_mapping(struct drm_psb_private *dev_priv, +			  struct bdb_header *bdb) +{ +	struct sdvo_device_mapping *p_mapping; +	struct bdb_general_definitions *p_defs; +	struct child_device_config *p_child; +	int i, child_device_num, count; +	u16	block_size; + +	p_defs = find_section(bdb, BDB_GENERAL_DEFINITIONS); +	if (!p_defs) { +		DRM_DEBUG_KMS("No general definition block is found, unable to construct sdvo mapping.\n"); +		return; +	} +	/* judge whether the size of child device meets the requirements. +	 * If the child device size obtained from general definition block +	 * is different with sizeof(struct child_device_config), skip the +	 * parsing of sdvo device info +	 */ +	if (p_defs->child_dev_size != sizeof(*p_child)) { +		/* different child dev size . Ignore it */ +		DRM_DEBUG_KMS("different child size is found. Invalid.\n"); +		return; +	} +	/* get the block size of general definitions */ +	block_size = get_blocksize(p_defs); +	/* get the number of child device */ +	child_device_num = (block_size - sizeof(*p_defs)) / +				sizeof(*p_child); +	count = 0; +	for (i = 0; i < child_device_num; i++) { +		p_child = &(p_defs->devices[i]); +		if (!p_child->device_type) { +			/* skip the device block if device type is invalid */ +			continue; +		} +		if (p_child->slave_addr != SLAVE_ADDR1 && +			p_child->slave_addr != SLAVE_ADDR2) { +			/* +			 * If the slave address is neither 0x70 nor 0x72, +			 * it is not a SDVO device. Skip it. +			 */ +			continue; +		} +		if (p_child->dvo_port != DEVICE_PORT_DVOB && +			p_child->dvo_port != DEVICE_PORT_DVOC) { +			/* skip the incorrect SDVO port */ +			DRM_DEBUG_KMS("Incorrect SDVO port. Skip it\n"); +			continue; +		} +		DRM_DEBUG_KMS("the SDVO device with slave addr %2x is found on" +				" %s port\n", +				p_child->slave_addr, +				(p_child->dvo_port == DEVICE_PORT_DVOB) ? +					"SDVOB" : "SDVOC"); +		p_mapping = &(dev_priv->sdvo_mappings[p_child->dvo_port - 1]); +		if (!p_mapping->initialized) { +			p_mapping->dvo_port = p_child->dvo_port; +			p_mapping->slave_addr = p_child->slave_addr; +			p_mapping->dvo_wiring = p_child->dvo_wiring; +			p_mapping->ddc_pin = p_child->ddc_pin; +			p_mapping->i2c_pin = p_child->i2c_pin; +			p_mapping->initialized = 1; +			DRM_DEBUG_KMS("SDVO device: dvo=%x, addr=%x, wiring=%d, ddc_pin=%d, i2c_pin=%d\n", +				      p_mapping->dvo_port, +				      p_mapping->slave_addr, +				      p_mapping->dvo_wiring, +				      p_mapping->ddc_pin, +				      p_mapping->i2c_pin); +		} else { +			DRM_DEBUG_KMS("Maybe one SDVO port is shared by " +					 "two SDVO device.\n"); +		} +		if (p_child->slave2_addr) { +			/* Maybe this is a SDVO device with multiple inputs */ +			/* And the mapping info is not added */ +			DRM_DEBUG_KMS("there exists the slave2_addr. Maybe this" +				" is a SDVO device with multiple inputs.\n"); +		} +		count++; +	} + +	if (!count) { +		/* No SDVO device info is found */ +		DRM_DEBUG_KMS("No SDVO device info is found in VBT\n"); +	} +	return; +} + + +static void +parse_driver_features(struct drm_psb_private *dev_priv, +		      struct bdb_header *bdb) +{ +	struct bdb_driver_features *driver; + +	driver = find_section(bdb, BDB_DRIVER_FEATURES); +	if (!driver) +		return; + +	if (driver->lvds_config == BDB_DRIVER_FEATURE_EDP) +		dev_priv->edp.support = 1; + +	/* This bit means to use 96Mhz for DPLL_A or not */ +	if (driver->primary_lfp_id) +		dev_priv->dplla_96mhz = true; +	else +		dev_priv->dplla_96mhz = false; +} + +static void +parse_device_mapping(struct drm_psb_private *dev_priv, +		       struct bdb_header *bdb) +{ +	struct bdb_general_definitions *p_defs; +	struct child_device_config *p_child, *child_dev_ptr; +	int i, child_device_num, count; +	u16	block_size; + +	p_defs = find_section(bdb, BDB_GENERAL_DEFINITIONS); +	if (!p_defs) { +		DRM_DEBUG_KMS("No general definition block is found, no devices defined.\n"); +		return; +	} +	/* judge whether the size of child device meets the requirements. +	 * If the child device size obtained from general definition block +	 * is different with sizeof(struct child_device_config), skip the +	 * parsing of sdvo device info +	 */ +	if (p_defs->child_dev_size != sizeof(*p_child)) { +		/* different child dev size . Ignore it */ +		DRM_DEBUG_KMS("different child size is found. Invalid.\n"); +		return; +	} +	/* get the block size of general definitions */ +	block_size = get_blocksize(p_defs); +	/* get the number of child device */ +	child_device_num = (block_size - sizeof(*p_defs)) / +				sizeof(*p_child); +	count = 0; +	/* get the number of child devices that are present */ +	for (i = 0; i < child_device_num; i++) { +		p_child = &(p_defs->devices[i]); +		if (!p_child->device_type) { +			/* skip the device block if device type is invalid */ +			continue; +		} +		count++; +	} +	if (!count) { +		DRM_DEBUG_KMS("no child dev is parsed from VBT\n"); +		return; +	} +	dev_priv->child_dev = kcalloc(count, sizeof(*p_child), GFP_KERNEL); +	if (!dev_priv->child_dev) { +		DRM_DEBUG_KMS("No memory space for child devices\n"); +		return; +	} + +	dev_priv->child_dev_num = count; +	count = 0; +	for (i = 0; i < child_device_num; i++) { +		p_child = &(p_defs->devices[i]); +		if (!p_child->device_type) { +			/* skip the device block if device type is invalid */ +			continue; +		} +		child_dev_ptr = dev_priv->child_dev + count; +		count++; +		memcpy((void *)child_dev_ptr, (void *)p_child, +					sizeof(*p_child)); +	} +	return; +} + + +/** + * psb_intel_init_bios - initialize VBIOS settings & find VBT + * @dev: DRM device + * + * Loads the Video BIOS and checks that the VBT exists.  Sets scratch registers + * to appropriate values. + * + * VBT existence is a sanity check that is relied on by other i830_bios.c code. + * Note that it would be better to use a BIOS call to get the VBT, as BIOSes may + * feed an updated VBT back through that, compared to what we'll fetch using + * this method of groping around in the BIOS data. + * + * Returns 0 on success, nonzero on failure. + */ +int psb_intel_init_bios(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct pci_dev *pdev = dev->pdev; +	struct vbt_header *vbt = NULL; +	struct bdb_header *bdb = NULL; +	u8 __iomem *bios = NULL; +	size_t size; +	int i; + + +	dev_priv->panel_type = 0xff; + +	/* XXX Should this validation be moved to intel_opregion.c? */ +	if (dev_priv->opregion.vbt) { +		struct vbt_header *vbt = dev_priv->opregion.vbt; +		if (memcmp(vbt->signature, "$VBT", 4) == 0) { +			DRM_DEBUG_KMS("Using VBT from OpRegion: %20s\n", +					 vbt->signature); +			bdb = (struct bdb_header *)((char *)vbt + vbt->bdb_offset); +		} else +			dev_priv->opregion.vbt = NULL; +	} + +	if (bdb == NULL) { +		bios = pci_map_rom(pdev, &size); +		if (!bios) +			return -1; + +		/* Scour memory looking for the VBT signature */ +		for (i = 0; i + 4 < size; i++) { +			if (!memcmp(bios + i, "$VBT", 4)) { +				vbt = (struct vbt_header *)(bios + i); +				break; +			} +		} + +		if (!vbt) { +			dev_err(dev->dev, "VBT signature missing\n"); +			pci_unmap_rom(pdev, bios); +			return -1; +		} +		bdb = (struct bdb_header *)(bios + i + vbt->bdb_offset); +	} + +	/* Grab useful general dxefinitions */ +	parse_general_features(dev_priv, bdb); +	parse_driver_features(dev_priv, bdb); +	parse_lfp_panel_data(dev_priv, bdb); +	parse_sdvo_panel_data(dev_priv, bdb); +	parse_sdvo_device_mapping(dev_priv, bdb); +	parse_device_mapping(dev_priv, bdb); +	parse_backlight_data(dev_priv, bdb); +	parse_edp(dev_priv, bdb); + +	if (bios) +		pci_unmap_rom(pdev, bios); + +	return 0; +} + +/** + * Destroy and free VBT data + */ +void psb_intel_destroy_bios(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	kfree(dev_priv->sdvo_lvds_vbt_mode); +	kfree(dev_priv->lfp_lvds_vbt_mode); +	kfree(dev_priv->lvds_bl); +} diff --git a/drivers/gpu/drm/gma500/intel_bios.h b/drivers/gpu/drm/gma500/intel_bios.h new file mode 100644 index 00000000000..978ae4b25e8 --- /dev/null +++ b/drivers/gpu/drm/gma500/intel_bios.h @@ -0,0 +1,621 @@ +/* + * Copyright (c) 2006 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *    Eric Anholt <eric@anholt.net> + * + */ + +#ifndef _INTEL_BIOS_H_ +#define _INTEL_BIOS_H_ + +#include <drm/drmP.h> +#include <drm/drm_dp_helper.h> + +struct vbt_header { +	u8 signature[20];		/**< Always starts with 'VBT$' */ +	u16 version;			/**< decimal */ +	u16 header_size;		/**< in bytes */ +	u16 vbt_size;			/**< in bytes */ +	u8 vbt_checksum; +	u8 reserved0; +	u32 bdb_offset;			/**< from beginning of VBT */ +	u32 aim_offset[4];		/**< from beginning of VBT */ +} __attribute__((packed)); + + +struct bdb_header { +	u8 signature[16];		/**< Always 'BIOS_DATA_BLOCK' */ +	u16 version;			/**< decimal */ +	u16 header_size;		/**< in bytes */ +	u16 bdb_size;			/**< in bytes */ +}; + +/* strictly speaking, this is a "skip" block, but it has interesting info */ +struct vbios_data { +	u8 type; /* 0 == desktop, 1 == mobile */ +	u8 relstage; +	u8 chipset; +	u8 lvds_present:1; +	u8 tv_present:1; +	u8 rsvd2:6; /* finish byte */ +	u8 rsvd3[4]; +	u8 signon[155]; +	u8 copyright[61]; +	u16 code_segment; +	u8 dos_boot_mode; +	u8 bandwidth_percent; +	u8 rsvd4; /* popup memory size */ +	u8 resize_pci_bios; +	u8 rsvd5; /* is crt already on ddc2 */ +} __attribute__((packed)); + +/* + * There are several types of BIOS data blocks (BDBs), each block has + * an ID and size in the first 3 bytes (ID in first, size in next 2). + * Known types are listed below. + */ +#define BDB_GENERAL_FEATURES	  1 +#define BDB_GENERAL_DEFINITIONS	  2 +#define BDB_OLD_TOGGLE_LIST	  3 +#define BDB_MODE_SUPPORT_LIST	  4 +#define BDB_GENERIC_MODE_TABLE	  5 +#define BDB_EXT_MMIO_REGS	  6 +#define BDB_SWF_IO		  7 +#define BDB_SWF_MMIO		  8 +#define BDB_DOT_CLOCK_TABLE	  9 +#define BDB_MODE_REMOVAL_TABLE	 10 +#define BDB_CHILD_DEVICE_TABLE	 11 +#define BDB_DRIVER_FEATURES	 12 +#define BDB_DRIVER_PERSISTENCE	 13 +#define BDB_EXT_TABLE_PTRS	 14 +#define BDB_DOT_CLOCK_OVERRIDE	 15 +#define BDB_DISPLAY_SELECT	 16 +/* 17 rsvd */ +#define BDB_DRIVER_ROTATION	 18 +#define BDB_DISPLAY_REMOVE	 19 +#define BDB_OEM_CUSTOM		 20 +#define BDB_EFP_LIST		 21 /* workarounds for VGA hsync/vsync */ +#define BDB_SDVO_LVDS_OPTIONS	 22 +#define BDB_SDVO_PANEL_DTDS	 23 +#define BDB_SDVO_LVDS_PNP_IDS	 24 +#define BDB_SDVO_LVDS_POWER_SEQ	 25 +#define BDB_TV_OPTIONS		 26 +#define BDB_EDP			 27 +#define BDB_LVDS_OPTIONS	 40 +#define BDB_LVDS_LFP_DATA_PTRS	 41 +#define BDB_LVDS_LFP_DATA	 42 +#define BDB_LVDS_BACKLIGHT	 43 +#define BDB_LVDS_POWER		 44 +#define BDB_SKIP		254 /* VBIOS private block, ignore */ + +struct bdb_general_features { +	/* bits 1 */ +	u8 panel_fitting:2; +	u8 flexaim:1; +	u8 msg_enable:1; +	u8 clear_screen:3; +	u8 color_flip:1; + +	/* bits 2 */ +	u8 download_ext_vbt:1; +	u8 enable_ssc:1; +	u8 ssc_freq:1; +	u8 enable_lfp_on_override:1; +	u8 disable_ssc_ddt:1; +	u8 rsvd8:3; /* finish byte */ + +	/* bits 3 */ +	u8 disable_smooth_vision:1; +	u8 single_dvi:1; +	u8 rsvd9:6; /* finish byte */ + +	/* bits 4 */ +	u8 legacy_monitor_detect; + +	/* bits 5 */ +	u8 int_crt_support:1; +	u8 int_tv_support:1; +	u8 int_efp_support:1; +	u8 dp_ssc_enb:1;	/* PCH attached eDP supports SSC */ +	u8 dp_ssc_freq:1;	/* SSC freq for PCH attached eDP */ +	u8 rsvd11:3; /* finish byte */ +} __attribute__((packed)); + +/* pre-915 */ +#define GPIO_PIN_DVI_LVDS	0x03 /* "DVI/LVDS DDC GPIO pins" */ +#define GPIO_PIN_ADD_I2C	0x05 /* "ADDCARD I2C GPIO pins" */ +#define GPIO_PIN_ADD_DDC	0x04 /* "ADDCARD DDC GPIO pins" */ +#define GPIO_PIN_ADD_DDC_I2C	0x06 /* "ADDCARD DDC/I2C GPIO pins" */ + +/* Pre 915 */ +#define DEVICE_TYPE_NONE	0x00 +#define DEVICE_TYPE_CRT		0x01 +#define DEVICE_TYPE_TV		0x09 +#define DEVICE_TYPE_EFP		0x12 +#define DEVICE_TYPE_LFP		0x22 +/* On 915+ */ +#define DEVICE_TYPE_CRT_DPMS		0x6001 +#define DEVICE_TYPE_CRT_DPMS_HOTPLUG	0x4001 +#define DEVICE_TYPE_TV_COMPOSITE	0x0209 +#define DEVICE_TYPE_TV_MACROVISION	0x0289 +#define DEVICE_TYPE_TV_RF_COMPOSITE	0x020c +#define DEVICE_TYPE_TV_SVIDEO_COMPOSITE	0x0609 +#define DEVICE_TYPE_TV_SCART		0x0209 +#define DEVICE_TYPE_TV_CODEC_HOTPLUG_PWR 0x6009 +#define DEVICE_TYPE_EFP_HOTPLUG_PWR	0x6012 +#define DEVICE_TYPE_EFP_DVI_HOTPLUG_PWR	0x6052 +#define DEVICE_TYPE_EFP_DVI_I		0x6053 +#define DEVICE_TYPE_EFP_DVI_D_DUAL	0x6152 +#define DEVICE_TYPE_EFP_DVI_D_HDCP	0x60d2 +#define DEVICE_TYPE_OPENLDI_HOTPLUG_PWR	0x6062 +#define DEVICE_TYPE_OPENLDI_DUALPIX	0x6162 +#define DEVICE_TYPE_LFP_PANELLINK	0x5012 +#define DEVICE_TYPE_LFP_CMOS_PWR	0x5042 +#define DEVICE_TYPE_LFP_LVDS_PWR	0x5062 +#define DEVICE_TYPE_LFP_LVDS_DUAL	0x5162 +#define DEVICE_TYPE_LFP_LVDS_DUAL_HDCP	0x51e2 + +#define DEVICE_CFG_NONE		0x00 +#define DEVICE_CFG_12BIT_DVOB	0x01 +#define DEVICE_CFG_12BIT_DVOC	0x02 +#define DEVICE_CFG_24BIT_DVOBC	0x09 +#define DEVICE_CFG_24BIT_DVOCB	0x0a +#define DEVICE_CFG_DUAL_DVOB	0x11 +#define DEVICE_CFG_DUAL_DVOC	0x12 +#define DEVICE_CFG_DUAL_DVOBC	0x13 +#define DEVICE_CFG_DUAL_LINK_DVOBC	0x19 +#define DEVICE_CFG_DUAL_LINK_DVOCB	0x1a + +#define DEVICE_WIRE_NONE	0x00 +#define DEVICE_WIRE_DVOB	0x01 +#define DEVICE_WIRE_DVOC	0x02 +#define DEVICE_WIRE_DVOBC	0x03 +#define DEVICE_WIRE_DVOBB	0x05 +#define DEVICE_WIRE_DVOCC	0x06 +#define DEVICE_WIRE_DVOB_MASTER 0x0d +#define DEVICE_WIRE_DVOC_MASTER 0x0e + +#define DEVICE_PORT_DVOA	0x00 /* none on 845+ */ +#define DEVICE_PORT_DVOB	0x01 +#define DEVICE_PORT_DVOC	0x02 + +struct child_device_config { +	u16 handle; +	u16 device_type; +	u8  device_id[10]; /* ascii string */ +	u16 addin_offset; +	u8  dvo_port; /* See Device_PORT_* above */ +	u8  i2c_pin; +	u8  slave_addr; +	u8  ddc_pin; +	u16 edid_ptr; +	u8  dvo_cfg; /* See DEVICE_CFG_* above */ +	u8  dvo2_port; +	u8  i2c2_pin; +	u8  slave2_addr; +	u8  ddc2_pin; +	u8  capabilities; +	u8  dvo_wiring;/* See DEVICE_WIRE_* above */ +	u8  dvo2_wiring; +	u16 extended_type; +	u8  dvo_function; +} __attribute__((packed)); + + +struct bdb_general_definitions { +	/* DDC GPIO */ +	u8 crt_ddc_gmbus_pin; + +	/* DPMS bits */ +	u8 dpms_acpi:1; +	u8 skip_boot_crt_detect:1; +	u8 dpms_aim:1; +	u8 rsvd1:5; /* finish byte */ + +	/* boot device bits */ +	u8 boot_display[2]; +	u8 child_dev_size; + +	/* +	 * Device info: +	 * If TV is present, it'll be at devices[0]. +	 * LVDS will be next, either devices[0] or [1], if present. +	 * On some platforms the number of device is 6. But could be as few as +	 * 4 if both TV and LVDS are missing. +	 * And the device num is related with the size of general definition +	 * block. It is obtained by using the following formula: +	 * number = (block_size - sizeof(bdb_general_definitions))/ +	 *	     sizeof(child_device_config); +	 */ +	struct child_device_config devices[0]; +}; + +struct bdb_lvds_options { +	u8 panel_type; +	u8 rsvd1; +	/* LVDS capabilities, stored in a dword */ +	u8 pfit_mode:2; +	u8 pfit_text_mode_enhanced:1; +	u8 pfit_gfx_mode_enhanced:1; +	u8 pfit_ratio_auto:1; +	u8 pixel_dither:1; +	u8 lvds_edid:1; +	u8 rsvd2:1; +	u8 rsvd4; +} __attribute__((packed)); + +struct bdb_lvds_backlight { +	u8 type:2; +	u8 pol:1; +	u8 gpio:3; +	u8 gmbus:2; +	u16 freq; +	u8 minbrightness; +	u8 i2caddr; +	u8 brightnesscmd; +	/*FIXME: more...*/ +} __attribute__((packed)); + +/* LFP pointer table contains entries to the struct below */ +struct bdb_lvds_lfp_data_ptr { +	u16 fp_timing_offset; /* offsets are from start of bdb */ +	u8 fp_table_size; +	u16 dvo_timing_offset; +	u8 dvo_table_size; +	u16 panel_pnp_id_offset; +	u8 pnp_table_size; +} __attribute__((packed)); + +struct bdb_lvds_lfp_data_ptrs { +	u8 lvds_entries; /* followed by one or more lvds_data_ptr structs */ +	struct bdb_lvds_lfp_data_ptr ptr[16]; +} __attribute__((packed)); + +/* LFP data has 3 blocks per entry */ +struct lvds_fp_timing { +	u16 x_res; +	u16 y_res; +	u32 lvds_reg; +	u32 lvds_reg_val; +	u32 pp_on_reg; +	u32 pp_on_reg_val; +	u32 pp_off_reg; +	u32 pp_off_reg_val; +	u32 pp_cycle_reg; +	u32 pp_cycle_reg_val; +	u32 pfit_reg; +	u32 pfit_reg_val; +	u16 terminator; +} __attribute__((packed)); + +struct lvds_dvo_timing { +	u16 clock;		/**< In 10khz */ +	u8 hactive_lo; +	u8 hblank_lo; +	u8 hblank_hi:4; +	u8 hactive_hi:4; +	u8 vactive_lo; +	u8 vblank_lo; +	u8 vblank_hi:4; +	u8 vactive_hi:4; +	u8 hsync_off_lo; +	u8 hsync_pulse_width; +	u8 vsync_pulse_width:4; +	u8 vsync_off:4; +	u8 rsvd0:6; +	u8 hsync_off_hi:2; +	u8 h_image; +	u8 v_image; +	u8 max_hv; +	u8 h_border; +	u8 v_border; +	u8 rsvd1:3; +	u8 digital:2; +	u8 vsync_positive:1; +	u8 hsync_positive:1; +	u8 rsvd2:1; +} __attribute__((packed)); + +struct lvds_pnp_id { +	u16 mfg_name; +	u16 product_code; +	u32 serial; +	u8 mfg_week; +	u8 mfg_year; +} __attribute__((packed)); + +struct bdb_lvds_lfp_data_entry { +	struct lvds_fp_timing fp_timing; +	struct lvds_dvo_timing dvo_timing; +	struct lvds_pnp_id pnp_id; +} __attribute__((packed)); + +struct bdb_lvds_lfp_data { +	struct bdb_lvds_lfp_data_entry data[16]; +} __attribute__((packed)); + +struct aimdb_header { +	char signature[16]; +	char oem_device[20]; +	u16 aimdb_version; +	u16 aimdb_header_size; +	u16 aimdb_size; +} __attribute__((packed)); + +struct aimdb_block { +	u8 aimdb_id; +	u16 aimdb_size; +} __attribute__((packed)); + +struct vch_panel_data { +	u16 fp_timing_offset; +	u8 fp_timing_size; +	u16 dvo_timing_offset; +	u8 dvo_timing_size; +	u16 text_fitting_offset; +	u8 text_fitting_size; +	u16 graphics_fitting_offset; +	u8 graphics_fitting_size; +} __attribute__((packed)); + +struct vch_bdb_22 { +	struct aimdb_block aimdb_block; +	struct vch_panel_data panels[16]; +} __attribute__((packed)); + +struct bdb_sdvo_lvds_options { +	u8 panel_backlight; +	u8 h40_set_panel_type; +	u8 panel_type; +	u8 ssc_clk_freq; +	u16 als_low_trip; +	u16 als_high_trip; +	u8 sclalarcoeff_tab_row_num; +	u8 sclalarcoeff_tab_row_size; +	u8 coefficient[8]; +	u8 panel_misc_bits_1; +	u8 panel_misc_bits_2; +	u8 panel_misc_bits_3; +	u8 panel_misc_bits_4; +} __attribute__((packed)); + +#define BDB_DRIVER_FEATURE_NO_LVDS		0 +#define BDB_DRIVER_FEATURE_INT_LVDS		1 +#define BDB_DRIVER_FEATURE_SDVO_LVDS		2 +#define BDB_DRIVER_FEATURE_EDP			3 + +struct bdb_driver_features { +	u8 boot_dev_algorithm:1; +	u8 block_display_switch:1; +	u8 allow_display_switch:1; +	u8 hotplug_dvo:1; +	u8 dual_view_zoom:1; +	u8 int15h_hook:1; +	u8 sprite_in_clone:1; +	u8 primary_lfp_id:1; + +	u16 boot_mode_x; +	u16 boot_mode_y; +	u8 boot_mode_bpp; +	u8 boot_mode_refresh; + +	u16 enable_lfp_primary:1; +	u16 selective_mode_pruning:1; +	u16 dual_frequency:1; +	u16 render_clock_freq:1; /* 0: high freq; 1: low freq */ +	u16 nt_clone_support:1; +	u16 power_scheme_ui:1; /* 0: CUI; 1: 3rd party */ +	u16 sprite_display_assign:1; /* 0: secondary; 1: primary */ +	u16 cui_aspect_scaling:1; +	u16 preserve_aspect_ratio:1; +	u16 sdvo_device_power_down:1; +	u16 crt_hotplug:1; +	u16 lvds_config:2; +	u16 tv_hotplug:1; +	u16 hdmi_config:2; + +	u8 static_display:1; +	u8 reserved2:7; +	u16 legacy_crt_max_x; +	u16 legacy_crt_max_y; +	u8 legacy_crt_max_refresh; + +	u8 hdmi_termination; +	u8 custom_vbt_version; +} __attribute__((packed)); + +#define EDP_18BPP	0 +#define EDP_24BPP	1 +#define EDP_30BPP	2 +#define EDP_RATE_1_62	0 +#define EDP_RATE_2_7	1 +#define EDP_LANE_1	0 +#define EDP_LANE_2	1 +#define EDP_LANE_4	3 +#define EDP_PREEMPHASIS_NONE	0 +#define EDP_PREEMPHASIS_3_5dB	1 +#define EDP_PREEMPHASIS_6dB	2 +#define EDP_PREEMPHASIS_9_5dB	3 +#define EDP_VSWING_0_4V		0 +#define EDP_VSWING_0_6V		1 +#define EDP_VSWING_0_8V		2 +#define EDP_VSWING_1_2V		3 + +struct edp_power_seq { +	u16 t1_t3; +	u16 t8; +	u16 t9; +	u16 t10; +	u16 t11_t12; +} __attribute__ ((packed)); + +struct edp_link_params { +	u8 rate:4; +	u8 lanes:4; +	u8 preemphasis:4; +	u8 vswing:4; +} __attribute__ ((packed)); + +struct bdb_edp { +	struct edp_power_seq power_seqs[16]; +	u32 color_depth; +	u32 sdrrs_msa_timing_delay; +	struct edp_link_params link_params[16]; +} __attribute__ ((packed)); + +extern int psb_intel_init_bios(struct drm_device *dev); +extern void psb_intel_destroy_bios(struct drm_device *dev); + +/* + * Driver<->VBIOS interaction occurs through scratch bits in + * GR18 & SWF*. + */ + +/* GR18 bits are set on display switch and hotkey events */ +#define GR18_DRIVER_SWITCH_EN	(1<<7) /* 0: VBIOS control, 1: driver control */ +#define GR18_HOTKEY_MASK	0x78 /* See also SWF4 15:0 */ +#define   GR18_HK_NONE		(0x0<<3) +#define   GR18_HK_LFP_STRETCH	(0x1<<3) +#define   GR18_HK_TOGGLE_DISP	(0x2<<3) +#define   GR18_HK_DISP_SWITCH	(0x4<<3) /* see SWF14 15:0 for what to enable */ +#define   GR18_HK_POPUP_DISABLED (0x6<<3) +#define   GR18_HK_POPUP_ENABLED	(0x7<<3) +#define   GR18_HK_PFIT		(0x8<<3) +#define   GR18_HK_APM_CHANGE	(0xa<<3) +#define   GR18_HK_MULTIPLE	(0xc<<3) +#define GR18_USER_INT_EN	(1<<2) +#define GR18_A0000_FLUSH_EN	(1<<1) +#define GR18_SMM_EN		(1<<0) + +/* Set by driver, cleared by VBIOS */ +#define SWF00_YRES_SHIFT	16 +#define SWF00_XRES_SHIFT	0 +#define SWF00_RES_MASK		0xffff + +/* Set by VBIOS at boot time and driver at runtime */ +#define SWF01_TV2_FORMAT_SHIFT	8 +#define SWF01_TV1_FORMAT_SHIFT	0 +#define SWF01_TV_FORMAT_MASK	0xffff + +#define SWF10_VBIOS_BLC_I2C_EN	(1<<29) +#define SWF10_GTT_OVERRIDE_EN	(1<<28) +#define SWF10_LFP_DPMS_OVR	(1<<27) /* override DPMS on display switch */ +#define SWF10_ACTIVE_TOGGLE_LIST_MASK (7<<24) +#define   SWF10_OLD_TOGGLE	0x0 +#define   SWF10_TOGGLE_LIST_1	0x1 +#define   SWF10_TOGGLE_LIST_2	0x2 +#define   SWF10_TOGGLE_LIST_3	0x3 +#define   SWF10_TOGGLE_LIST_4	0x4 +#define SWF10_PANNING_EN	(1<<23) +#define SWF10_DRIVER_LOADED	(1<<22) +#define SWF10_EXTENDED_DESKTOP	(1<<21) +#define SWF10_EXCLUSIVE_MODE	(1<<20) +#define SWF10_OVERLAY_EN	(1<<19) +#define SWF10_PLANEB_HOLDOFF	(1<<18) +#define SWF10_PLANEA_HOLDOFF	(1<<17) +#define SWF10_VGA_HOLDOFF	(1<<16) +#define SWF10_ACTIVE_DISP_MASK	0xffff +#define   SWF10_PIPEB_LFP2	(1<<15) +#define   SWF10_PIPEB_EFP2	(1<<14) +#define   SWF10_PIPEB_TV2	(1<<13) +#define   SWF10_PIPEB_CRT2	(1<<12) +#define   SWF10_PIPEB_LFP	(1<<11) +#define   SWF10_PIPEB_EFP	(1<<10) +#define   SWF10_PIPEB_TV	(1<<9) +#define   SWF10_PIPEB_CRT	(1<<8) +#define   SWF10_PIPEA_LFP2	(1<<7) +#define   SWF10_PIPEA_EFP2	(1<<6) +#define   SWF10_PIPEA_TV2	(1<<5) +#define   SWF10_PIPEA_CRT2	(1<<4) +#define   SWF10_PIPEA_LFP	(1<<3) +#define   SWF10_PIPEA_EFP	(1<<2) +#define   SWF10_PIPEA_TV	(1<<1) +#define   SWF10_PIPEA_CRT	(1<<0) + +#define SWF11_MEMORY_SIZE_SHIFT	16 +#define SWF11_SV_TEST_EN	(1<<15) +#define SWF11_IS_AGP		(1<<14) +#define SWF11_DISPLAY_HOLDOFF	(1<<13) +#define SWF11_DPMS_REDUCED	(1<<12) +#define SWF11_IS_VBE_MODE	(1<<11) +#define SWF11_PIPEB_ACCESS	(1<<10) /* 0 here means pipe a */ +#define SWF11_DPMS_MASK		0x07 +#define   SWF11_DPMS_OFF	(1<<2) +#define   SWF11_DPMS_SUSPEND	(1<<1) +#define   SWF11_DPMS_STANDBY	(1<<0) +#define   SWF11_DPMS_ON		0 + +#define SWF14_GFX_PFIT_EN	(1<<31) +#define SWF14_TEXT_PFIT_EN	(1<<30) +#define SWF14_LID_STATUS_CLOSED	(1<<29) /* 0 here means open */ +#define SWF14_POPUP_EN		(1<<28) +#define SWF14_DISPLAY_HOLDOFF	(1<<27) +#define SWF14_DISP_DETECT_EN	(1<<26) +#define SWF14_DOCKING_STATUS_DOCKED (1<<25) /* 0 here means undocked */ +#define SWF14_DRIVER_STATUS	(1<<24) +#define SWF14_OS_TYPE_WIN9X	(1<<23) +#define SWF14_OS_TYPE_WINNT	(1<<22) +/* 21:19 rsvd */ +#define SWF14_PM_TYPE_MASK	0x00070000 +#define   SWF14_PM_ACPI_VIDEO	(0x4 << 16) +#define   SWF14_PM_ACPI		(0x3 << 16) +#define   SWF14_PM_APM_12	(0x2 << 16) +#define   SWF14_PM_APM_11	(0x1 << 16) +#define SWF14_HK_REQUEST_MASK	0x0000ffff /* see GR18 6:3 for event type */ +	  /* if GR18 indicates a display switch */ +#define   SWF14_DS_PIPEB_LFP2_EN (1<<15) +#define   SWF14_DS_PIPEB_EFP2_EN (1<<14) +#define   SWF14_DS_PIPEB_TV2_EN  (1<<13) +#define   SWF14_DS_PIPEB_CRT2_EN (1<<12) +#define   SWF14_DS_PIPEB_LFP_EN  (1<<11) +#define   SWF14_DS_PIPEB_EFP_EN  (1<<10) +#define   SWF14_DS_PIPEB_TV_EN	 (1<<9) +#define   SWF14_DS_PIPEB_CRT_EN  (1<<8) +#define   SWF14_DS_PIPEA_LFP2_EN (1<<7) +#define   SWF14_DS_PIPEA_EFP2_EN (1<<6) +#define   SWF14_DS_PIPEA_TV2_EN  (1<<5) +#define   SWF14_DS_PIPEA_CRT2_EN (1<<4) +#define   SWF14_DS_PIPEA_LFP_EN  (1<<3) +#define   SWF14_DS_PIPEA_EFP_EN  (1<<2) +#define   SWF14_DS_PIPEA_TV_EN	 (1<<1) +#define   SWF14_DS_PIPEA_CRT_EN  (1<<0) +	  /* if GR18 indicates a panel fitting request */ +#define   SWF14_PFIT_EN		(1<<0) /* 0 means disable */ +	  /* if GR18 indicates an APM change request */ +#define   SWF14_APM_HIBERNATE	0x4 +#define   SWF14_APM_SUSPEND	0x3 +#define   SWF14_APM_STANDBY	0x1 +#define   SWF14_APM_RESTORE	0x0 + +/* Add the device class for LFP, TV, HDMI */ +#define	 DEVICE_TYPE_INT_LFP	0x1022 +#define	 DEVICE_TYPE_INT_TV	0x1009 +#define	 DEVICE_TYPE_HDMI	0x60D2 +#define	 DEVICE_TYPE_DP		0x68C6 +#define	 DEVICE_TYPE_eDP	0x78C6 + +/* define the DVO port for HDMI output type */ +#define		DVO_B		1 +#define		DVO_C		2 +#define		DVO_D		3 + +/* define the PORT for DP output type */ +#define		PORT_IDPB	7 +#define		PORT_IDPC	8 +#define		PORT_IDPD	9 + +#endif /* _INTEL_BIOS_H_ */ diff --git a/drivers/gpu/drm/gma500/intel_gmbus.c b/drivers/gpu/drm/gma500/intel_gmbus.c new file mode 100644 index 00000000000..566d330aaee --- /dev/null +++ b/drivers/gpu/drm/gma500/intel_gmbus.c @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2006 Dave Airlie <airlied@linux.ie> + * Copyright © 2006-2008,2010 Intel Corporation + *   Jesse Barnes <jesse.barnes@intel.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + *	Chris Wilson <chris@chris-wilson.co.uk> + */ +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <drm/drmP.h> +#include "psb_intel_drv.h" +#include <drm/gma_drm.h> +#include "psb_drv.h" +#include "psb_intel_reg.h" + +#define _wait_for(COND, MS, W) ({ \ +	unsigned long timeout__ = jiffies + msecs_to_jiffies(MS);	\ +	int ret__ = 0;							\ +	while (! (COND)) {						\ +		if (time_after(jiffies, timeout__)) {			\ +			ret__ = -ETIMEDOUT;				\ +			break;						\ +		}							\ +		if (W && !(in_atomic() || in_dbg_master())) msleep(W);	\ +	}								\ +	ret__;								\ +}) + +#define wait_for(COND, MS) _wait_for(COND, MS, 1) +#define wait_for_atomic(COND, MS) _wait_for(COND, MS, 0) + +#define GMBUS_REG_READ(reg) ioread32(dev_priv->gmbus_reg + (reg)) +#define GMBUS_REG_WRITE(reg, val) iowrite32((val), dev_priv->gmbus_reg + (reg)) + +/* Intel GPIO access functions */ + +#define I2C_RISEFALL_TIME 20 + +static inline struct intel_gmbus * +to_intel_gmbus(struct i2c_adapter *i2c) +{ +	return container_of(i2c, struct intel_gmbus, adapter); +} + +struct intel_gpio { +	struct i2c_adapter adapter; +	struct i2c_algo_bit_data algo; +	struct drm_psb_private *dev_priv; +	u32 reg; +}; + +void +gma_intel_i2c_reset(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	GMBUS_REG_WRITE(GMBUS0, 0); +} + +static void intel_i2c_quirk_set(struct drm_psb_private *dev_priv, bool enable) +{ +	/* When using bit bashing for I2C, this bit needs to be set to 1 */ +	/* FIXME: We are never Pineview, right? + +	u32 val; + +	if (!IS_PINEVIEW(dev_priv->dev)) +		return; + +	val = REG_READ(DSPCLK_GATE_D); +	if (enable) +		val |= DPCUNIT_CLOCK_GATE_DISABLE; +	else +		val &= ~DPCUNIT_CLOCK_GATE_DISABLE; +	REG_WRITE(DSPCLK_GATE_D, val); + +	return; +	*/ +} + +static u32 get_reserved(struct intel_gpio *gpio) +{ +	struct drm_psb_private *dev_priv = gpio->dev_priv; +	u32 reserved = 0; + +	/* On most chips, these bits must be preserved in software. */ +	reserved = GMBUS_REG_READ(gpio->reg) & +				     (GPIO_DATA_PULLUP_DISABLE | +				      GPIO_CLOCK_PULLUP_DISABLE); + +	return reserved; +} + +static int get_clock(void *data) +{ +	struct intel_gpio *gpio = data; +	struct drm_psb_private *dev_priv = gpio->dev_priv; +	u32 reserved = get_reserved(gpio); +	GMBUS_REG_WRITE(gpio->reg, reserved | GPIO_CLOCK_DIR_MASK); +	GMBUS_REG_WRITE(gpio->reg, reserved); +	return (GMBUS_REG_READ(gpio->reg) & GPIO_CLOCK_VAL_IN) != 0; +} + +static int get_data(void *data) +{ +	struct intel_gpio *gpio = data; +	struct drm_psb_private *dev_priv = gpio->dev_priv; +	u32 reserved = get_reserved(gpio); +	GMBUS_REG_WRITE(gpio->reg, reserved | GPIO_DATA_DIR_MASK); +	GMBUS_REG_WRITE(gpio->reg, reserved); +	return (GMBUS_REG_READ(gpio->reg) & GPIO_DATA_VAL_IN) != 0; +} + +static void set_clock(void *data, int state_high) +{ +	struct intel_gpio *gpio = data; +	struct drm_psb_private *dev_priv = gpio->dev_priv; +	u32 reserved = get_reserved(gpio); +	u32 clock_bits; + +	if (state_high) +		clock_bits = GPIO_CLOCK_DIR_IN | GPIO_CLOCK_DIR_MASK; +	else +		clock_bits = GPIO_CLOCK_DIR_OUT | GPIO_CLOCK_DIR_MASK | +			GPIO_CLOCK_VAL_MASK; + +	GMBUS_REG_WRITE(gpio->reg, reserved | clock_bits); +	GMBUS_REG_READ(gpio->reg); /* Posting */ +} + +static void set_data(void *data, int state_high) +{ +	struct intel_gpio *gpio = data; +	struct drm_psb_private *dev_priv = gpio->dev_priv; +	u32 reserved = get_reserved(gpio); +	u32 data_bits; + +	if (state_high) +		data_bits = GPIO_DATA_DIR_IN | GPIO_DATA_DIR_MASK; +	else +		data_bits = GPIO_DATA_DIR_OUT | GPIO_DATA_DIR_MASK | +			GPIO_DATA_VAL_MASK; + +	GMBUS_REG_WRITE(gpio->reg, reserved | data_bits); +	GMBUS_REG_READ(gpio->reg); +} + +static struct i2c_adapter * +intel_gpio_create(struct drm_psb_private *dev_priv, u32 pin) +{ +	static const int map_pin_to_reg[] = { +		0, +		GPIOB, +		GPIOA, +		GPIOC, +		GPIOD, +		GPIOE, +		0, +		GPIOF, +	}; +	struct intel_gpio *gpio; + +	if (pin >= ARRAY_SIZE(map_pin_to_reg) || !map_pin_to_reg[pin]) +		return NULL; + +	gpio = kzalloc(sizeof(struct intel_gpio), GFP_KERNEL); +	if (gpio == NULL) +		return NULL; + +	gpio->reg = map_pin_to_reg[pin]; +	gpio->dev_priv = dev_priv; + +	snprintf(gpio->adapter.name, sizeof(gpio->adapter.name), +		 "gma500 GPIO%c", "?BACDE?F"[pin]); +	gpio->adapter.owner = THIS_MODULE; +	gpio->adapter.algo_data	= &gpio->algo; +	gpio->adapter.dev.parent = &dev_priv->dev->pdev->dev; +	gpio->algo.setsda = set_data; +	gpio->algo.setscl = set_clock; +	gpio->algo.getsda = get_data; +	gpio->algo.getscl = get_clock; +	gpio->algo.udelay = I2C_RISEFALL_TIME; +	gpio->algo.timeout = usecs_to_jiffies(2200); +	gpio->algo.data = gpio; + +	if (i2c_bit_add_bus(&gpio->adapter)) +		goto out_free; + +	return &gpio->adapter; + +out_free: +	kfree(gpio); +	return NULL; +} + +static int +intel_i2c_quirk_xfer(struct drm_psb_private *dev_priv, +		     struct i2c_adapter *adapter, +		     struct i2c_msg *msgs, +		     int num) +{ +	struct intel_gpio *gpio = container_of(adapter, +					       struct intel_gpio, +					       adapter); +	int ret; + +	gma_intel_i2c_reset(dev_priv->dev); + +	intel_i2c_quirk_set(dev_priv, true); +	set_data(gpio, 1); +	set_clock(gpio, 1); +	udelay(I2C_RISEFALL_TIME); + +	ret = adapter->algo->master_xfer(adapter, msgs, num); + +	set_data(gpio, 1); +	set_clock(gpio, 1); +	intel_i2c_quirk_set(dev_priv, false); + +	return ret; +} + +static int +gmbus_xfer(struct i2c_adapter *adapter, +	   struct i2c_msg *msgs, +	   int num) +{ +	struct intel_gmbus *bus = container_of(adapter, +					       struct intel_gmbus, +					       adapter); +	struct drm_psb_private *dev_priv = adapter->algo_data; +	int i, reg_offset; + +	if (bus->force_bit) +		return intel_i2c_quirk_xfer(dev_priv, +					    bus->force_bit, msgs, num); + +	reg_offset = 0; + +	GMBUS_REG_WRITE(GMBUS0 + reg_offset, bus->reg0); + +	for (i = 0; i < num; i++) { +		u16 len = msgs[i].len; +		u8 *buf = msgs[i].buf; + +		if (msgs[i].flags & I2C_M_RD) { +			GMBUS_REG_WRITE(GMBUS1 + reg_offset, +					GMBUS_CYCLE_WAIT | +					(i + 1 == num ? GMBUS_CYCLE_STOP : 0) | +					(len << GMBUS_BYTE_COUNT_SHIFT) | +					(msgs[i].addr << GMBUS_SLAVE_ADDR_SHIFT) | +					GMBUS_SLAVE_READ | GMBUS_SW_RDY); +			GMBUS_REG_READ(GMBUS2+reg_offset); +			do { +				u32 val, loop = 0; + +				if (wait_for(GMBUS_REG_READ(GMBUS2 + reg_offset) & +					     (GMBUS_SATOER | GMBUS_HW_RDY), 50)) +					goto timeout; +				if (GMBUS_REG_READ(GMBUS2 + reg_offset) & GMBUS_SATOER) +					goto clear_err; + +				val = GMBUS_REG_READ(GMBUS3 + reg_offset); +				do { +					*buf++ = val & 0xff; +					val >>= 8; +				} while (--len && ++loop < 4); +			} while (len); +		} else { +			u32 val, loop; + +			val = loop = 0; +			do { +				val |= *buf++ << (8 * loop); +			} while (--len && ++loop < 4); + +			GMBUS_REG_WRITE(GMBUS3 + reg_offset, val); +			GMBUS_REG_WRITE(GMBUS1 + reg_offset, +				   (i + 1 == num ? GMBUS_CYCLE_STOP : GMBUS_CYCLE_WAIT) | +				   (msgs[i].len << GMBUS_BYTE_COUNT_SHIFT) | +				   (msgs[i].addr << GMBUS_SLAVE_ADDR_SHIFT) | +				   GMBUS_SLAVE_WRITE | GMBUS_SW_RDY); +			GMBUS_REG_READ(GMBUS2+reg_offset); + +			while (len) { +				if (wait_for(GMBUS_REG_READ(GMBUS2 + reg_offset) & +					     (GMBUS_SATOER | GMBUS_HW_RDY), 50)) +					goto timeout; +				if (GMBUS_REG_READ(GMBUS2 + reg_offset) & +				    GMBUS_SATOER) +					goto clear_err; + +				val = loop = 0; +				do { +					val |= *buf++ << (8 * loop); +				} while (--len && ++loop < 4); + +				GMBUS_REG_WRITE(GMBUS3 + reg_offset, val); +				GMBUS_REG_READ(GMBUS2+reg_offset); +			} +		} + +		if (i + 1 < num && wait_for(GMBUS_REG_READ(GMBUS2 + reg_offset) & (GMBUS_SATOER | GMBUS_HW_WAIT_PHASE), 50)) +			goto timeout; +		if (GMBUS_REG_READ(GMBUS2 + reg_offset) & GMBUS_SATOER) +			goto clear_err; +	} + +	goto done; + +clear_err: +	/* Toggle the Software Clear Interrupt bit. This has the effect +	 * of resetting the GMBUS controller and so clearing the +	 * BUS_ERROR raised by the slave's NAK. +	 */ +	GMBUS_REG_WRITE(GMBUS1 + reg_offset, GMBUS_SW_CLR_INT); +	GMBUS_REG_WRITE(GMBUS1 + reg_offset, 0); + +done: +	/* Mark the GMBUS interface as disabled. We will re-enable it at the +	 * start of the next xfer, till then let it sleep. +	 */ +	GMBUS_REG_WRITE(GMBUS0 + reg_offset, 0); +	return i; + +timeout: +	DRM_INFO("GMBUS timed out, falling back to bit banging on pin %d [%s]\n", +		 bus->reg0 & 0xff, bus->adapter.name); +	GMBUS_REG_WRITE(GMBUS0 + reg_offset, 0); + +	/* Hardware may not support GMBUS over these pins? Try GPIO bitbanging instead. */ +	bus->force_bit = intel_gpio_create(dev_priv, bus->reg0 & 0xff); +	if (!bus->force_bit) +		return -ENOMEM; + +	return intel_i2c_quirk_xfer(dev_priv, bus->force_bit, msgs, num); +} + +static u32 gmbus_func(struct i2c_adapter *adapter) +{ +	struct intel_gmbus *bus = container_of(adapter, +					       struct intel_gmbus, +					       adapter); + +	if (bus->force_bit) +		bus->force_bit->algo->functionality(bus->force_bit); + +	return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | +		/* I2C_FUNC_10BIT_ADDR | */ +		I2C_FUNC_SMBUS_READ_BLOCK_DATA | +		I2C_FUNC_SMBUS_BLOCK_PROC_CALL); +} + +static const struct i2c_algorithm gmbus_algorithm = { +	.master_xfer	= gmbus_xfer, +	.functionality	= gmbus_func +}; + +/** + * intel_gmbus_setup - instantiate all Intel i2c GMBuses + * @dev: DRM device + */ +int gma_intel_setup_gmbus(struct drm_device *dev) +{ +	static const char *names[GMBUS_NUM_PORTS] = { +		"disabled", +		"ssc", +		"vga", +		"panel", +		"dpc", +		"dpb", +		"reserved", +		"dpd", +	}; +	struct drm_psb_private *dev_priv = dev->dev_private; +	int ret, i; + +	dev_priv->gmbus = kcalloc(GMBUS_NUM_PORTS, sizeof(struct intel_gmbus), +				  GFP_KERNEL); +	if (dev_priv->gmbus == NULL) +		return -ENOMEM; + +	if (IS_MRST(dev)) +		dev_priv->gmbus_reg = dev_priv->aux_reg; +	else +		dev_priv->gmbus_reg = dev_priv->vdc_reg; + +	for (i = 0; i < GMBUS_NUM_PORTS; i++) { +		struct intel_gmbus *bus = &dev_priv->gmbus[i]; + +		bus->adapter.owner = THIS_MODULE; +		bus->adapter.class = I2C_CLASS_DDC; +		snprintf(bus->adapter.name, +			 sizeof(bus->adapter.name), +			 "gma500 gmbus %s", +			 names[i]); + +		bus->adapter.dev.parent = &dev->pdev->dev; +		bus->adapter.algo_data	= dev_priv; + +		bus->adapter.algo = &gmbus_algorithm; +		ret = i2c_add_adapter(&bus->adapter); +		if (ret) +			goto err; + +		/* By default use a conservative clock rate */ +		bus->reg0 = i | GMBUS_RATE_100KHZ; + +		/* XXX force bit banging until GMBUS is fully debugged */ +		bus->force_bit = intel_gpio_create(dev_priv, i); +	} + +	gma_intel_i2c_reset(dev_priv->dev); + +	return 0; + +err: +	while (--i) { +		struct intel_gmbus *bus = &dev_priv->gmbus[i]; +		i2c_del_adapter(&bus->adapter); +	} +	kfree(dev_priv->gmbus); +	dev_priv->gmbus = NULL; +	return ret; +} + +void gma_intel_gmbus_set_speed(struct i2c_adapter *adapter, int speed) +{ +	struct intel_gmbus *bus = to_intel_gmbus(adapter); + +	/* speed: +	 * 0x0 = 100 KHz +	 * 0x1 = 50 KHz +	 * 0x2 = 400 KHz +	 * 0x3 = 1000 Khz +	 */ +	bus->reg0 = (bus->reg0 & ~(0x3 << 8)) | (speed << 8); +} + +void gma_intel_gmbus_force_bit(struct i2c_adapter *adapter, bool force_bit) +{ +	struct intel_gmbus *bus = to_intel_gmbus(adapter); + +	if (force_bit) { +		if (bus->force_bit == NULL) { +			struct drm_psb_private *dev_priv = adapter->algo_data; +			bus->force_bit = intel_gpio_create(dev_priv, +							   bus->reg0 & 0xff); +		} +	} else { +		if (bus->force_bit) { +			i2c_del_adapter(bus->force_bit); +			kfree(bus->force_bit); +			bus->force_bit = NULL; +		} +	} +} + +void gma_intel_teardown_gmbus(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	int i; + +	if (dev_priv->gmbus == NULL) +		return; + +	for (i = 0; i < GMBUS_NUM_PORTS; i++) { +		struct intel_gmbus *bus = &dev_priv->gmbus[i]; +		if (bus->force_bit) { +			i2c_del_adapter(bus->force_bit); +			kfree(bus->force_bit); +		} +		i2c_del_adapter(&bus->adapter); +	} + +	dev_priv->gmbus_reg = NULL; /* iounmap is done in driver_unload */ +	kfree(dev_priv->gmbus); +	dev_priv->gmbus = NULL; +} diff --git a/drivers/gpu/drm/gma500/intel_i2c.c b/drivers/gpu/drm/gma500/intel_i2c.c new file mode 100644 index 00000000000..98a28c20955 --- /dev/null +++ b/drivers/gpu/drm/gma500/intel_i2c.c @@ -0,0 +1,169 @@ +/* + * Copyright © 2006-2007 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + */ +#include <linux/export.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "psb_drv.h" +#include "psb_intel_reg.h" + +/* + * Intel GPIO access functions + */ + +#define I2C_RISEFALL_TIME 20 + +static int get_clock(void *data) +{ +	struct psb_intel_i2c_chan *chan = data; +	struct drm_device *dev = chan->drm_dev; +	u32 val; + +	val = REG_READ(chan->reg); +	return (val & GPIO_CLOCK_VAL_IN) != 0; +} + +static int get_data(void *data) +{ +	struct psb_intel_i2c_chan *chan = data; +	struct drm_device *dev = chan->drm_dev; +	u32 val; + +	val = REG_READ(chan->reg); +	return (val & GPIO_DATA_VAL_IN) != 0; +} + +static void set_clock(void *data, int state_high) +{ +	struct psb_intel_i2c_chan *chan = data; +	struct drm_device *dev = chan->drm_dev; +	u32 reserved = 0, clock_bits; + +	/* On most chips, these bits must be preserved in software. */ +	reserved = +		    REG_READ(chan->reg) & (GPIO_DATA_PULLUP_DISABLE | +					   GPIO_CLOCK_PULLUP_DISABLE); + +	if (state_high) +		clock_bits = GPIO_CLOCK_DIR_IN | GPIO_CLOCK_DIR_MASK; +	else +		clock_bits = GPIO_CLOCK_DIR_OUT | GPIO_CLOCK_DIR_MASK | +		    GPIO_CLOCK_VAL_MASK; +	REG_WRITE(chan->reg, reserved | clock_bits); +	udelay(I2C_RISEFALL_TIME);	/* wait for the line to change state */ +} + +static void set_data(void *data, int state_high) +{ +	struct psb_intel_i2c_chan *chan = data; +	struct drm_device *dev = chan->drm_dev; +	u32 reserved = 0, data_bits; + +	/* On most chips, these bits must be preserved in software. */ +	reserved = +		    REG_READ(chan->reg) & (GPIO_DATA_PULLUP_DISABLE | +					   GPIO_CLOCK_PULLUP_DISABLE); + +	if (state_high) +		data_bits = GPIO_DATA_DIR_IN | GPIO_DATA_DIR_MASK; +	else +		data_bits = +		    GPIO_DATA_DIR_OUT | GPIO_DATA_DIR_MASK | +		    GPIO_DATA_VAL_MASK; + +	REG_WRITE(chan->reg, reserved | data_bits); +	udelay(I2C_RISEFALL_TIME);	/* wait for the line to change state */ +} + +/** + * psb_intel_i2c_create - instantiate an Intel i2c bus using the specified GPIO reg + * @dev: DRM device + * @output: driver specific output device + * @reg: GPIO reg to use + * @name: name for this bus + * + * Creates and registers a new i2c bus with the Linux i2c layer, for use + * in output probing and control (e.g. DDC or SDVO control functions). + * + * Possible values for @reg include: + *   %GPIOA + *   %GPIOB + *   %GPIOC + *   %GPIOD + *   %GPIOE + *   %GPIOF + *   %GPIOG + *   %GPIOH + * see PRM for details on how these different busses are used. + */ +struct psb_intel_i2c_chan *psb_intel_i2c_create(struct drm_device *dev, +					const u32 reg, const char *name) +{ +	struct psb_intel_i2c_chan *chan; + +	chan = kzalloc(sizeof(struct psb_intel_i2c_chan), GFP_KERNEL); +	if (!chan) +		goto out_free; + +	chan->drm_dev = dev; +	chan->reg = reg; +	snprintf(chan->adapter.name, I2C_NAME_SIZE, "intel drm %s", name); +	chan->adapter.owner = THIS_MODULE; +	chan->adapter.algo_data = &chan->algo; +	chan->adapter.dev.parent = &dev->pdev->dev; +	chan->algo.setsda = set_data; +	chan->algo.setscl = set_clock; +	chan->algo.getsda = get_data; +	chan->algo.getscl = get_clock; +	chan->algo.udelay = 20; +	chan->algo.timeout = usecs_to_jiffies(2200); +	chan->algo.data = chan; + +	i2c_set_adapdata(&chan->adapter, chan); + +	if (i2c_bit_add_bus(&chan->adapter)) +		goto out_free; + +	/* JJJ:  raise SCL and SDA? */ +	set_data(chan, 1); +	set_clock(chan, 1); +	udelay(20); + +	return chan; + +out_free: +	kfree(chan); +	return NULL; +} + +/** + * psb_intel_i2c_destroy - unregister and free i2c bus resources + * @output: channel to free + * + * Unregister the adapter from the i2c layer, then free the structure. + */ +void psb_intel_i2c_destroy(struct psb_intel_i2c_chan *chan) +{ +	if (!chan) +		return; + +	i2c_del_adapter(&chan->adapter); +	kfree(chan); +} diff --git a/drivers/gpu/drm/gma500/mdfld_device.c b/drivers/gpu/drm/gma500/mdfld_device.c new file mode 100644 index 00000000000..265ad0de44a --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_device.c @@ -0,0 +1,551 @@ +/************************************************************************** + * Copyright (c) 2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +#include "psb_drv.h" +#include "mid_bios.h" +#include "mdfld_output.h" +#include "mdfld_dsi_output.h" +#include "tc35876x-dsi-lvds.h" + +#include <asm/intel_scu_ipc.h> + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE + +#define MRST_BLC_MAX_PWM_REG_FREQ	    0xFFFF +#define BLC_PWM_PRECISION_FACTOR 100	/* 10000000 */ +#define BLC_PWM_FREQ_CALC_CONSTANT 32 +#define MHz 1000000 +#define BRIGHTNESS_MIN_LEVEL 1 +#define BRIGHTNESS_MAX_LEVEL 100 +#define BRIGHTNESS_MASK	0xFF +#define BLC_POLARITY_NORMAL 0 +#define BLC_POLARITY_INVERSE 1 +#define BLC_ADJUSTMENT_MAX 100 + +#define MDFLD_BLC_PWM_PRECISION_FACTOR    10 +#define MDFLD_BLC_MAX_PWM_REG_FREQ        0xFFFE +#define MDFLD_BLC_MIN_PWM_REG_FREQ        0x2 + +#define MDFLD_BACKLIGHT_PWM_POLARITY_BIT_CLEAR (0xFFFE) +#define MDFLD_BACKLIGHT_PWM_CTL_SHIFT	(16) + +static struct backlight_device *mdfld_backlight_device; + +int mdfld_set_brightness(struct backlight_device *bd) +{ +	struct drm_device *dev = +		(struct drm_device *)bl_get_data(mdfld_backlight_device); +	struct drm_psb_private *dev_priv = dev->dev_private; +	int level = bd->props.brightness; + +	DRM_DEBUG_DRIVER("backlight level set to %d\n", level); + +	/* Perform value bounds checking */ +	if (level < BRIGHTNESS_MIN_LEVEL) +		level = BRIGHTNESS_MIN_LEVEL; + +	if (gma_power_begin(dev, false)) { +		u32 adjusted_level = 0; + +		/* +		 * Adjust the backlight level with the percent in +		 * dev_priv->blc_adj2 +		 */ +		adjusted_level = level * dev_priv->blc_adj2; +		adjusted_level = adjusted_level / BLC_ADJUSTMENT_MAX; +		dev_priv->brightness_adjusted = adjusted_level; + +		if (mdfld_get_panel_type(dev, 0) == TC35876X) { +			if (dev_priv->dpi_panel_on[0] || +					dev_priv->dpi_panel_on[2]) +				tc35876x_brightness_control(dev, +						dev_priv->brightness_adjusted); +		} else { +			if (dev_priv->dpi_panel_on[0]) +				mdfld_dsi_brightness_control(dev, 0, +						dev_priv->brightness_adjusted); +		} + +		if (dev_priv->dpi_panel_on[2]) +			mdfld_dsi_brightness_control(dev, 2, +					dev_priv->brightness_adjusted); +		gma_power_end(dev); +	} + +	/* cache the brightness for later use */ +	dev_priv->brightness = level; +	return 0; +} + +static int mdfld_get_brightness(struct backlight_device *bd) +{ +	struct drm_device *dev = +		(struct drm_device *)bl_get_data(mdfld_backlight_device); +	struct drm_psb_private *dev_priv = dev->dev_private; + +	DRM_DEBUG_DRIVER("brightness = 0x%x \n", dev_priv->brightness); + +	/* return locally cached var instead of HW read (due to DPST etc.) */ +	return dev_priv->brightness; +} + +static const struct backlight_ops mdfld_ops = { +	.get_brightness = mdfld_get_brightness, +	.update_status  = mdfld_set_brightness, +}; + +static int device_backlight_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = (struct drm_psb_private *) +		dev->dev_private; + +	dev_priv->blc_adj1 = BLC_ADJUSTMENT_MAX; +	dev_priv->blc_adj2 = BLC_ADJUSTMENT_MAX; + +	return 0; +} + +static int mdfld_backlight_init(struct drm_device *dev) +{ +	struct backlight_properties props; +	int ret = 0; + +	memset(&props, 0, sizeof(struct backlight_properties)); +	props.max_brightness = BRIGHTNESS_MAX_LEVEL; +	props.type = BACKLIGHT_PLATFORM; +	mdfld_backlight_device = backlight_device_register("mdfld-bl", +				NULL, (void *)dev, &mdfld_ops, &props); + +	if (IS_ERR(mdfld_backlight_device)) +		return PTR_ERR(mdfld_backlight_device); + +	ret = device_backlight_init(dev); +	if (ret) +		return ret; + +	mdfld_backlight_device->props.brightness = BRIGHTNESS_MAX_LEVEL; +	mdfld_backlight_device->props.max_brightness = BRIGHTNESS_MAX_LEVEL; +	backlight_update_status(mdfld_backlight_device); +	return 0; +} +#endif + +struct backlight_device *mdfld_get_backlight_device(void) +{ +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	return mdfld_backlight_device; +#else +	return NULL; +#endif +} + +/* + * mdfld_save_display_registers + * + * Description: We are going to suspend so save current display + * register state. + * + * Notes: FIXME_JLIU7 need to add the support for DPI MIPI & HDMI audio + */ +static int mdfld_save_display_registers(struct drm_device *dev, int pipenum) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct medfield_state *regs = &dev_priv->regs.mdfld; +	struct psb_pipe *pipe = &dev_priv->regs.pipe[pipenum]; +	const struct psb_offset *map = &dev_priv->regmap[pipenum]; +	int i; +	u32 *mipi_val; + +	/* register */ +	u32 mipi_reg = MIPI; + +	switch (pipenum) { +	case 0: +		mipi_val = ®s->saveMIPI; +		break; +	case 1: +		mipi_val = ®s->saveMIPI; +		break; +	case 2: +		/* register */ +		mipi_reg = MIPI_C; +		/* pointer to values */ +		mipi_val = ®s->saveMIPI_C; +		break; +	default: +		DRM_ERROR("%s, invalid pipe number.\n", __func__); +		return -EINVAL; +	} + +	/* Pipe & plane A info */ +	pipe->dpll = PSB_RVDC32(map->dpll); +	pipe->fp0 = PSB_RVDC32(map->fp0); +	pipe->conf = PSB_RVDC32(map->conf); +	pipe->htotal = PSB_RVDC32(map->htotal); +	pipe->hblank = PSB_RVDC32(map->hblank); +	pipe->hsync = PSB_RVDC32(map->hsync); +	pipe->vtotal = PSB_RVDC32(map->vtotal); +	pipe->vblank = PSB_RVDC32(map->vblank); +	pipe->vsync = PSB_RVDC32(map->vsync); +	pipe->src = PSB_RVDC32(map->src); +	pipe->stride = PSB_RVDC32(map->stride); +	pipe->linoff = PSB_RVDC32(map->linoff); +	pipe->tileoff = PSB_RVDC32(map->tileoff); +	pipe->size = PSB_RVDC32(map->size); +	pipe->pos = PSB_RVDC32(map->pos); +	pipe->surf = PSB_RVDC32(map->surf); +	pipe->cntr = PSB_RVDC32(map->cntr); +	pipe->status = PSB_RVDC32(map->status); + +	/*save palette (gamma) */ +	for (i = 0; i < 256; i++) +		pipe->palette[i] = PSB_RVDC32(map->palette + (i << 2)); + +	if (pipenum == 1) { +		regs->savePFIT_CONTROL = PSB_RVDC32(PFIT_CONTROL); +		regs->savePFIT_PGM_RATIOS = PSB_RVDC32(PFIT_PGM_RATIOS); + +		regs->saveHDMIPHYMISCCTL = PSB_RVDC32(HDMIPHYMISCCTL); +		regs->saveHDMIB_CONTROL = PSB_RVDC32(HDMIB_CONTROL); +		return 0; +	} + +	*mipi_val = PSB_RVDC32(mipi_reg); +	return 0; +} + +/* + * mdfld_restore_display_registers + * + * Description: We are going to resume so restore display register state. + * + * Notes: FIXME_JLIU7 need to add the support for DPI MIPI & HDMI audio + */ +static int mdfld_restore_display_registers(struct drm_device *dev, int pipenum) +{ +	/* To get  panel out of ULPS mode. */ +	u32 temp = 0; +	u32 device_ready_reg = DEVICE_READY_REG; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct mdfld_dsi_config *dsi_config = NULL; +	struct medfield_state *regs = &dev_priv->regs.mdfld; +	struct psb_pipe *pipe = &dev_priv->regs.pipe[pipenum]; +	const struct psb_offset *map = &dev_priv->regmap[pipenum]; +	u32 i; +	u32 dpll; +	u32 timeout = 0; + +	/* register */ +	u32 mipi_reg = MIPI; + +	/* values */ +	u32 dpll_val = pipe->dpll; +	u32 mipi_val = regs->saveMIPI; + +	switch (pipenum) { +	case 0: +		dpll_val &= ~DPLL_VCO_ENABLE; +		dsi_config = dev_priv->dsi_configs[0]; +		break; +	case 1: +		dpll_val &= ~DPLL_VCO_ENABLE; +		break; +	case 2: +		mipi_reg = MIPI_C; +		mipi_val = regs->saveMIPI_C; +		dsi_config = dev_priv->dsi_configs[1]; +		break; +	default: +		DRM_ERROR("%s, invalid pipe number.\n", __func__); +		return -EINVAL; +	} + +	/*make sure VGA plane is off. it initializes to on after reset!*/ +	PSB_WVDC32(0x80000000, VGACNTRL); + +	if (pipenum == 1) { +		PSB_WVDC32(dpll_val & ~DPLL_VCO_ENABLE, map->dpll); +		PSB_RVDC32(map->dpll); + +		PSB_WVDC32(pipe->fp0, map->fp0); +	} else { + +		dpll = PSB_RVDC32(map->dpll); + +		if (!(dpll & DPLL_VCO_ENABLE)) { + +			/* When ungating power of DPLL, needs to wait 0.5us +			   before enable the VCO */ +			if (dpll & MDFLD_PWR_GATE_EN) { +				dpll &= ~MDFLD_PWR_GATE_EN; +				PSB_WVDC32(dpll, map->dpll); +				/* FIXME_MDFLD PO - change 500 to 1 after PO */ +				udelay(500); +			} + +			PSB_WVDC32(pipe->fp0, map->fp0); +			PSB_WVDC32(dpll_val, map->dpll); +			/* FIXME_MDFLD PO - change 500 to 1 after PO */ +			udelay(500); + +			dpll_val |= DPLL_VCO_ENABLE; +			PSB_WVDC32(dpll_val, map->dpll); +			PSB_RVDC32(map->dpll); + +			/* wait for DSI PLL to lock */ +			while (timeout < 20000 && +			  !(PSB_RVDC32(map->conf) & PIPECONF_DSIPLL_LOCK)) { +				udelay(150); +				timeout++; +			} + +			if (timeout == 20000) { +				DRM_ERROR("%s, can't lock DSIPLL.\n", +								__func__); +				return -EINVAL; +			} +		} +	} +	/* Restore mode */ +	PSB_WVDC32(pipe->htotal, map->htotal); +	PSB_WVDC32(pipe->hblank, map->hblank); +	PSB_WVDC32(pipe->hsync, map->hsync); +	PSB_WVDC32(pipe->vtotal, map->vtotal); +	PSB_WVDC32(pipe->vblank, map->vblank); +	PSB_WVDC32(pipe->vsync, map->vsync); +	PSB_WVDC32(pipe->src, map->src); +	PSB_WVDC32(pipe->status, map->status); + +	/*set up the plane*/ +	PSB_WVDC32(pipe->stride, map->stride); +	PSB_WVDC32(pipe->linoff, map->linoff); +	PSB_WVDC32(pipe->tileoff, map->tileoff); +	PSB_WVDC32(pipe->size, map->size); +	PSB_WVDC32(pipe->pos, map->pos); +	PSB_WVDC32(pipe->surf, map->surf); + +	if (pipenum == 1) { +		/* restore palette (gamma) */ +		/*DRM_UDELAY(50000); */ +		for (i = 0; i < 256; i++) +			PSB_WVDC32(pipe->palette[i], map->palette + (i << 2)); + +		PSB_WVDC32(regs->savePFIT_CONTROL, PFIT_CONTROL); +		PSB_WVDC32(regs->savePFIT_PGM_RATIOS, PFIT_PGM_RATIOS); + +		/*TODO: resume HDMI port */ + +		/*TODO: resume pipe*/ + +		/*enable the plane*/ +		PSB_WVDC32(pipe->cntr & ~DISPLAY_PLANE_ENABLE, map->cntr); + +		return 0; +	} + +	/*set up pipe related registers*/ +	PSB_WVDC32(mipi_val, mipi_reg); + +	/*setup MIPI adapter + MIPI IP registers*/ +	if (dsi_config) +		mdfld_dsi_controller_init(dsi_config, pipenum); + +	if (in_atomic() || in_interrupt()) +		mdelay(20); +	else +		msleep(20); + +	/*enable the plane*/ +	PSB_WVDC32(pipe->cntr, map->cntr); + +	if (in_atomic() || in_interrupt()) +		mdelay(20); +	else +		msleep(20); + +	/* LP Hold Release */ +	temp = REG_READ(mipi_reg); +	temp |= LP_OUTPUT_HOLD_RELEASE; +	REG_WRITE(mipi_reg, temp); +	mdelay(1); + + +	/* Set DSI host to exit from Utra Low Power State */ +	temp = REG_READ(device_ready_reg); +	temp &= ~ULPS_MASK; +	temp |= 0x3; +	temp |= EXIT_ULPS_DEV_READY; +	REG_WRITE(device_ready_reg, temp); +	mdelay(1); + +	temp = REG_READ(device_ready_reg); +	temp &= ~ULPS_MASK; +	temp |= EXITING_ULPS; +	REG_WRITE(device_ready_reg, temp); +	mdelay(1); + +	/*enable the pipe*/ +	PSB_WVDC32(pipe->conf, map->conf); + +	/* restore palette (gamma) */ +	/*DRM_UDELAY(50000); */ +	for (i = 0; i < 256; i++) +		PSB_WVDC32(pipe->palette[i], map->palette + (i << 2)); + +	return 0; +} + +static int mdfld_save_registers(struct drm_device *dev) +{ +	/* mdfld_save_cursor_overlay_registers(dev); */ +	mdfld_save_display_registers(dev, 0); +	mdfld_save_display_registers(dev, 2); +	mdfld_disable_crtc(dev, 0); +	mdfld_disable_crtc(dev, 2); + +	return 0; +} + +static int mdfld_restore_registers(struct drm_device *dev) +{ +	mdfld_restore_display_registers(dev, 2); +	mdfld_restore_display_registers(dev, 0); +	/* mdfld_restore_cursor_overlay_registers(dev); */ + +	return 0; +} + +static int mdfld_power_down(struct drm_device *dev) +{ +	/* FIXME */ +	return 0; +} + +static int mdfld_power_up(struct drm_device *dev) +{ +	/* FIXME */ +	return 0; +} + +/* Medfield  */ +static const struct psb_offset mdfld_regmap[3] = { +	{ +		.fp0 = MRST_FPA0, +		.fp1 = MRST_FPA1, +		.cntr = DSPACNTR, +		.conf = PIPEACONF, +		.src = PIPEASRC, +		.dpll = MRST_DPLL_A, +		.htotal = HTOTAL_A, +		.hblank = HBLANK_A, +		.hsync = HSYNC_A, +		.vtotal = VTOTAL_A, +		.vblank = VBLANK_A, +		.vsync = VSYNC_A, +		.stride = DSPASTRIDE, +		.size = DSPASIZE, +		.pos = DSPAPOS, +		.surf = DSPASURF, +		.addr = MRST_DSPABASE, +		.status = PIPEASTAT, +		.linoff = DSPALINOFF, +		.tileoff = DSPATILEOFF, +		.palette = PALETTE_A, +	}, +	{ +		.fp0 = MDFLD_DPLL_DIV0, +		.cntr = DSPBCNTR, +		.conf = PIPEBCONF, +		.src = PIPEBSRC, +		.dpll = MDFLD_DPLL_B, +		.htotal = HTOTAL_B, +		.hblank = HBLANK_B, +		.hsync = HSYNC_B, +		.vtotal = VTOTAL_B, +		.vblank = VBLANK_B, +		.vsync = VSYNC_B, +		.stride = DSPBSTRIDE, +		.size = DSPBSIZE, +		.pos = DSPBPOS, +		.surf = DSPBSURF, +		.addr = MRST_DSPBBASE, +		.status = PIPEBSTAT, +		.linoff = DSPBLINOFF, +		.tileoff = DSPBTILEOFF, +		.palette = PALETTE_B, +	}, +	{ +		.fp0 = MRST_FPA0,	/* This is what the old code did ?? */ +		.cntr = DSPCCNTR, +		.conf = PIPECCONF, +		.src = PIPECSRC, +		/* No DPLL_C */ +		.dpll = MRST_DPLL_A, +		.htotal = HTOTAL_C, +		.hblank = HBLANK_C, +		.hsync = HSYNC_C, +		.vtotal = VTOTAL_C, +		.vblank = VBLANK_C, +		.vsync = VSYNC_C, +		.stride = DSPCSTRIDE, +		.size = DSPBSIZE, +		.pos = DSPCPOS, +		.surf = DSPCSURF, +		.addr = MDFLD_DSPCBASE, +		.status = PIPECSTAT, +		.linoff = DSPCLINOFF, +		.tileoff = DSPCTILEOFF, +		.palette = PALETTE_C, +	}, +}; + +static int mdfld_chip_setup(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	if (pci_enable_msi(dev->pdev)) +		dev_warn(dev->dev, "Enabling MSI failed!\n"); +	dev_priv->regmap = mdfld_regmap; +	return mid_chip_setup(dev); +} + +const struct psb_ops mdfld_chip_ops = { +	.name = "mdfld", +	.accel_2d = 0, +	.pipes = 3, +	.crtcs = 3, +	.lvds_mask = (1 << 1), +	.hdmi_mask = (1 << 1), +	.cursor_needs_phys = 0, +	.sgx_offset = MRST_SGX_OFFSET, + +	.chip_setup = mdfld_chip_setup, +	.crtc_helper = &mdfld_helper_funcs, +	.crtc_funcs = &psb_intel_crtc_funcs, + +	.output_init = mdfld_output_init, + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	.backlight_init = mdfld_backlight_init, +#endif + +	.save_regs = mdfld_save_registers, +	.restore_regs = mdfld_restore_registers, +	.power_down = mdfld_power_down, +	.power_up = mdfld_power_up, +}; diff --git a/drivers/gpu/drm/gma500/mdfld_dsi_dpi.c b/drivers/gpu/drm/gma500/mdfld_dsi_dpi.c new file mode 100644 index 00000000000..d4813e03f5e --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_dsi_dpi.c @@ -0,0 +1,1016 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * jim liu <jim.liu@intel.com> + * Jackie Li<yaodong.li@intel.com> + */ + +#include "mdfld_dsi_dpi.h" +#include "mdfld_output.h" +#include "mdfld_dsi_pkg_sender.h" +#include "psb_drv.h" +#include "tc35876x-dsi-lvds.h" + +static void mdfld_dsi_dpi_shut_down(struct mdfld_dsi_dpi_output *output, +								int pipe); + +static void mdfld_wait_for_HS_DATA_FIFO(struct drm_device *dev, u32 pipe) +{ +	u32 gen_fifo_stat_reg = MIPI_GEN_FIFO_STAT_REG(pipe); +	int timeout = 0; + +	udelay(500); + +	/* This will time out after approximately 2+ seconds */ +	while ((timeout < 20000) && +		(REG_READ(gen_fifo_stat_reg) & DSI_FIFO_GEN_HS_DATA_FULL)) { +		udelay(100); +		timeout++; +	} + +	if (timeout == 20000) +		DRM_INFO("MIPI: HS Data FIFO was never cleared!\n"); +} + +static void mdfld_wait_for_HS_CTRL_FIFO(struct drm_device *dev, u32 pipe) +{ +	u32 gen_fifo_stat_reg = MIPI_GEN_FIFO_STAT_REG(pipe); +	int timeout = 0; + +	udelay(500); + +	/* This will time out after approximately 2+ seconds */ +	while ((timeout < 20000) && (REG_READ(gen_fifo_stat_reg) +					& DSI_FIFO_GEN_HS_CTRL_FULL)) { +		udelay(100); +		timeout++; +	} +	if (timeout == 20000) +		DRM_INFO("MIPI: HS CMD FIFO was never cleared!\n"); +} + +static void mdfld_wait_for_DPI_CTRL_FIFO(struct drm_device *dev, u32 pipe) +{ +	u32 gen_fifo_stat_reg = MIPI_GEN_FIFO_STAT_REG(pipe); +	int timeout = 0; + +	udelay(500); + +	/* This will time out after approximately 2+ seconds */ +	while ((timeout < 20000) && ((REG_READ(gen_fifo_stat_reg) & +					DPI_FIFO_EMPTY) != DPI_FIFO_EMPTY)) { +		udelay(100); +		timeout++; +	} + +	if (timeout == 20000) +		DRM_ERROR("MIPI: DPI FIFO was never cleared\n"); +} + +static void mdfld_wait_for_SPL_PKG_SENT(struct drm_device *dev, u32 pipe) +{ +	u32 intr_stat_reg = MIPI_INTR_STAT_REG(pipe); +	int timeout = 0; + +	udelay(500); + +	/* This will time out after approximately 2+ seconds */ +	while ((timeout < 20000) && (!(REG_READ(intr_stat_reg) +					& DSI_INTR_STATE_SPL_PKG_SENT))) { +		udelay(100); +		timeout++; +	} + +	if (timeout == 20000) +                DRM_ERROR("MIPI: SPL_PKT_SENT_INTERRUPT was not sent successfully!\n"); +} + +/* For TC35876X */ + +static void dsi_set_device_ready_state(struct drm_device *dev, int state, +				int pipe) +{ +	REG_FLD_MOD(MIPI_DEVICE_READY_REG(pipe), !!state, 0, 0); +} + +static void dsi_set_pipe_plane_enable_state(struct drm_device *dev, +							int state, int pipe) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 pipeconf_reg = PIPEACONF; +	u32 dspcntr_reg = DSPACNTR; + +	u32 dspcntr = dev_priv->dspcntr[pipe]; +	u32 mipi = MIPI_PORT_EN | PASS_FROM_SPHY_TO_AFE | SEL_FLOPPED_HSTX; + +	if (pipe) { +		pipeconf_reg = PIPECCONF; +		dspcntr_reg = DSPCCNTR; +	} else +		mipi &= (~0x03); + +	if (state) { +		/*Set up pipe */ +		REG_WRITE(pipeconf_reg, BIT(31)); + +		if (REG_BIT_WAIT(pipeconf_reg, 1, 30)) +			dev_err(&dev->pdev->dev, "%s: Pipe enable timeout\n", +				__func__); + +		/*Set up display plane */ +		REG_WRITE(dspcntr_reg, dspcntr); +	} else { +		u32 dspbase_reg = pipe ? MDFLD_DSPCBASE : MRST_DSPABASE; + +		/* Put DSI lanes to ULPS to disable pipe */ +		REG_FLD_MOD(MIPI_DEVICE_READY_REG(pipe), 2, 2, 1); +		REG_READ(MIPI_DEVICE_READY_REG(pipe)); /* posted write? */ + +		/* LP Hold */ +		REG_FLD_MOD(MIPI_PORT_CONTROL(pipe), 0, 16, 16); +		REG_READ(MIPI_PORT_CONTROL(pipe)); /* posted write? */ + +		/* Disable display plane */ +		REG_FLD_MOD(dspcntr_reg, 0, 31, 31); + +		/* Flush the plane changes ??? posted write? */ +		REG_WRITE(dspbase_reg, REG_READ(dspbase_reg)); +		REG_READ(dspbase_reg); + +		/* Disable PIPE */ +		REG_FLD_MOD(pipeconf_reg, 0, 31, 31); + +		if (REG_BIT_WAIT(pipeconf_reg, 0, 30)) +			dev_err(&dev->pdev->dev, "%s: Pipe disable timeout\n", +				__func__); + +		if (REG_BIT_WAIT(MIPI_GEN_FIFO_STAT_REG(pipe), 1, 28)) +			dev_err(&dev->pdev->dev, "%s: FIFO not empty\n", +				__func__); +	} +} + +static void mdfld_dsi_configure_down(struct mdfld_dsi_encoder *dsi_encoder, +								int pipe) +{ +	struct mdfld_dsi_dpi_output *dpi_output = +				MDFLD_DSI_DPI_OUTPUT(dsi_encoder); +	struct mdfld_dsi_config *dsi_config = +				mdfld_dsi_encoder_get_config(dsi_encoder); +	struct drm_device *dev = dsi_config->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (!dev_priv->dpi_panel_on[pipe]) { +		dev_err(dev->dev, "DPI panel is already off\n"); +		return; +	} +	tc35876x_toshiba_bridge_panel_off(dev); +	tc35876x_set_bridge_reset_state(dev, 1); +	dsi_set_pipe_plane_enable_state(dev, 0, pipe); +	mdfld_dsi_dpi_shut_down(dpi_output, pipe); +	dsi_set_device_ready_state(dev, 0, pipe); +} + +static void mdfld_dsi_configure_up(struct mdfld_dsi_encoder *dsi_encoder, +								int pipe) +{ +	struct mdfld_dsi_dpi_output *dpi_output = +				MDFLD_DSI_DPI_OUTPUT(dsi_encoder); +	struct mdfld_dsi_config *dsi_config = +				mdfld_dsi_encoder_get_config(dsi_encoder); +	struct drm_device *dev = dsi_config->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (dev_priv->dpi_panel_on[pipe]) { +		dev_err(dev->dev, "DPI panel is already on\n"); +		return; +	} + +	/* For resume path sequence */ +	mdfld_dsi_dpi_shut_down(dpi_output, pipe); +	dsi_set_device_ready_state(dev, 0, pipe); + +	dsi_set_device_ready_state(dev, 1, pipe); +	tc35876x_set_bridge_reset_state(dev, 0); +	tc35876x_configure_lvds_bridge(dev); +	mdfld_dsi_dpi_turn_on(dpi_output, pipe);  /* Send turn on command */ +	dsi_set_pipe_plane_enable_state(dev, 1, pipe); +} +/* End for TC35876X */ + +/* ************************************************************************* *\ + * FUNCTION: mdfld_dsi_tpo_ic_init + * + * DESCRIPTION:  This function is called only by mrst_dsi_mode_set and + *               restore_display_registers.  since this function does not + *               acquire the mutex, it is important that the calling function + *               does! +\* ************************************************************************* */ +static void mdfld_dsi_tpo_ic_init(struct mdfld_dsi_config *dsi_config, u32 pipe) +{ +	struct drm_device *dev = dsi_config->dev; +	u32 dcsChannelNumber = dsi_config->channel_num; +	u32 gen_data_reg = MIPI_HS_GEN_DATA_REG(pipe); +	u32 gen_ctrl_reg = MIPI_HS_GEN_CTRL_REG(pipe); +	u32 gen_ctrl_val = GEN_LONG_WRITE; + +	DRM_INFO("Enter mrst init TPO MIPI display.\n"); + +	gen_ctrl_val |= dcsChannelNumber << DCS_CHANNEL_NUMBER_POS; + +	/* Flip page order */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x00008036); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x02 << WORD_COUNTS_POS)); + +	/* 0xF0 */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x005a5af0); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x03 << WORD_COUNTS_POS)); + +	/* Write protection key */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x005a5af1); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x03 << WORD_COUNTS_POS)); + +	/* 0xFC */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x005a5afc); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x03 << WORD_COUNTS_POS)); + +	/* 0xB7 */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x770000b7); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x00000044); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x05 << WORD_COUNTS_POS)); + +	/* 0xB6 */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x000a0ab6); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x03 << WORD_COUNTS_POS)); + +	/* 0xF2 */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x081010f2); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x4a070708); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x000000c5); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x09 << WORD_COUNTS_POS)); + +	/* 0xF8 */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x024003f8); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x01030a04); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x0e020220); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x00000004); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x0d << WORD_COUNTS_POS)); + +	/* 0xE2 */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x398fc3e2); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x0000916f); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x06 << WORD_COUNTS_POS)); + +	/* 0xB0 */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x000000b0); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x02 << WORD_COUNTS_POS)); + +	/* 0xF4 */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x240242f4); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x78ee2002); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x2a071050); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x507fee10); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x10300710); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x14 << WORD_COUNTS_POS)); + +	/* 0xBA */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x19fe07ba); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x101c0a31); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x00000010); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x09 << WORD_COUNTS_POS)); + +	/* 0xBB */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x28ff07bb); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x24280a31); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x00000034); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x09 << WORD_COUNTS_POS)); + +	/* 0xFB */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x535d05fb); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x1b1a2130); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x221e180e); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x131d2120); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x535d0508); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x1c1a2131); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x231f160d); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x111b2220); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x535c2008); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x1f1d2433); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x2c251a10); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x2c34372d); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x00000023); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x31 << WORD_COUNTS_POS)); + +	/* 0xFA */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x525c0bfa); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x1c1c232f); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x2623190e); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x18212625); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x545d0d0e); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x1e1d2333); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x26231a10); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x1a222725); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x545d280f); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x21202635); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x31292013); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x31393d33); +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x00000029); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x31 << WORD_COUNTS_POS)); + +	/* Set DM */ +	mdfld_wait_for_HS_DATA_FIFO(dev, pipe); +	REG_WRITE(gen_data_reg, 0x000100f7); +	mdfld_wait_for_HS_CTRL_FIFO(dev, pipe); +	REG_WRITE(gen_ctrl_reg, gen_ctrl_val | (0x03 << WORD_COUNTS_POS)); +} + +static u16 mdfld_dsi_dpi_to_byte_clock_count(int pixel_clock_count, +						int num_lane, int bpp) +{ +	return (u16)((pixel_clock_count * bpp) / (num_lane * 8)); +} + +/* + * Calculate the dpi time basing on a given drm mode @mode + * return 0 on success. + * FIXME: I was using proposed mode value for calculation, may need to + * use crtc mode values later + */ +int mdfld_dsi_dpi_timing_calculation(struct drm_display_mode *mode, +				struct mdfld_dsi_dpi_timing *dpi_timing, +				int num_lane, int bpp) +{ +	int pclk_hsync, pclk_hfp, pclk_hbp, pclk_hactive; +	int pclk_vsync, pclk_vfp, pclk_vbp; + +	pclk_hactive = mode->hdisplay; +	pclk_hfp = mode->hsync_start - mode->hdisplay; +	pclk_hsync = mode->hsync_end - mode->hsync_start; +	pclk_hbp = mode->htotal - mode->hsync_end; + +	pclk_vfp = mode->vsync_start - mode->vdisplay; +	pclk_vsync = mode->vsync_end - mode->vsync_start; +	pclk_vbp = mode->vtotal - mode->vsync_end; + +	/* +	 * byte clock counts were calculated by following formula +	 * bclock_count = pclk_count * bpp / num_lane / 8 +	 */ +	dpi_timing->hsync_count = mdfld_dsi_dpi_to_byte_clock_count( +						pclk_hsync, num_lane, bpp); +	dpi_timing->hbp_count = mdfld_dsi_dpi_to_byte_clock_count( +						pclk_hbp, num_lane, bpp); +	dpi_timing->hfp_count = mdfld_dsi_dpi_to_byte_clock_count( +						pclk_hfp, num_lane, bpp); +	dpi_timing->hactive_count = mdfld_dsi_dpi_to_byte_clock_count( +						pclk_hactive, num_lane, bpp); +	dpi_timing->vsync_count = mdfld_dsi_dpi_to_byte_clock_count( +						pclk_vsync, num_lane, bpp); +	dpi_timing->vbp_count = mdfld_dsi_dpi_to_byte_clock_count( +						pclk_vbp, num_lane, bpp); +	dpi_timing->vfp_count = mdfld_dsi_dpi_to_byte_clock_count( +						pclk_vfp, num_lane, bpp); + +	return 0; +} + +void mdfld_dsi_dpi_controller_init(struct mdfld_dsi_config *dsi_config, +								int pipe) +{ +	struct drm_device *dev = dsi_config->dev; +	int lane_count = dsi_config->lane_count; +	struct mdfld_dsi_dpi_timing dpi_timing; +	struct drm_display_mode *mode = dsi_config->mode; +	u32 val; + +	/*un-ready device*/ +	REG_FLD_MOD(MIPI_DEVICE_READY_REG(pipe), 0, 0, 0); + +	/*init dsi adapter before kicking off*/ +	REG_WRITE(MIPI_CTRL_REG(pipe), 0x00000018); + +	/*enable all interrupts*/ +	REG_WRITE(MIPI_INTR_EN_REG(pipe), 0xffffffff); + +	/*set up func_prg*/ +	val = lane_count; +	val |= dsi_config->channel_num << DSI_DPI_VIRT_CHANNEL_OFFSET; + +	switch (dsi_config->bpp) { +	case 16: +		val |= DSI_DPI_COLOR_FORMAT_RGB565; +		break; +	case 18: +		val |= DSI_DPI_COLOR_FORMAT_RGB666; +		break; +	case 24: +		val |= DSI_DPI_COLOR_FORMAT_RGB888; +		break; +	default: +		DRM_ERROR("unsupported color format, bpp = %d\n", +							dsi_config->bpp); +	} +	REG_WRITE(MIPI_DSI_FUNC_PRG_REG(pipe), val); + +	REG_WRITE(MIPI_HS_TX_TIMEOUT_REG(pipe), +			(mode->vtotal * mode->htotal * dsi_config->bpp / +				(8 * lane_count)) & DSI_HS_TX_TIMEOUT_MASK); +	REG_WRITE(MIPI_LP_RX_TIMEOUT_REG(pipe), +				0xffff & DSI_LP_RX_TIMEOUT_MASK); + +	/*max value: 20 clock cycles of txclkesc*/ +	REG_WRITE(MIPI_TURN_AROUND_TIMEOUT_REG(pipe), +				0x14 & DSI_TURN_AROUND_TIMEOUT_MASK); + +	/*min 21 txclkesc, max: ffffh*/ +	REG_WRITE(MIPI_DEVICE_RESET_TIMER_REG(pipe), +				0xffff & DSI_RESET_TIMER_MASK); + +	REG_WRITE(MIPI_DPI_RESOLUTION_REG(pipe), +				mode->vdisplay << 16 | mode->hdisplay); + +	/*set DPI timing registers*/ +	mdfld_dsi_dpi_timing_calculation(mode, &dpi_timing, +				dsi_config->lane_count, dsi_config->bpp); + +	REG_WRITE(MIPI_HSYNC_COUNT_REG(pipe), +			dpi_timing.hsync_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_HBP_COUNT_REG(pipe), +			dpi_timing.hbp_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_HFP_COUNT_REG(pipe), +			dpi_timing.hfp_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_HACTIVE_COUNT_REG(pipe), +			dpi_timing.hactive_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_VSYNC_COUNT_REG(pipe), +			dpi_timing.vsync_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_VBP_COUNT_REG(pipe), +			dpi_timing.vbp_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_VFP_COUNT_REG(pipe), +			dpi_timing.vfp_count & DSI_DPI_TIMING_MASK); + +	REG_WRITE(MIPI_HIGH_LOW_SWITCH_COUNT_REG(pipe), 0x46); + +	/*min: 7d0 max: 4e20*/ +	REG_WRITE(MIPI_INIT_COUNT_REG(pipe), 0x000007d0); + +	/*set up video mode*/ +	val = dsi_config->video_mode | DSI_DPI_COMPLETE_LAST_LINE; +	REG_WRITE(MIPI_VIDEO_MODE_FORMAT_REG(pipe), val); + +	REG_WRITE(MIPI_EOT_DISABLE_REG(pipe), 0x00000000); + +	REG_WRITE(MIPI_LP_BYTECLK_REG(pipe), 0x00000004); + +	/*TODO: figure out how to setup these registers*/ +	if (mdfld_get_panel_type(dev, pipe) == TC35876X) +		REG_WRITE(MIPI_DPHY_PARAM_REG(pipe), 0x2A0c6008); +	else +		REG_WRITE(MIPI_DPHY_PARAM_REG(pipe), 0x150c3408); + +	REG_WRITE(MIPI_CLK_LANE_SWITCH_TIME_CNT_REG(pipe), (0xa << 16) | 0x14); + +	if (mdfld_get_panel_type(dev, pipe) == TC35876X) +		tc35876x_set_bridge_reset_state(dev, 0);  /*Pull High Reset */ + +	/*set device ready*/ +	REG_FLD_MOD(MIPI_DEVICE_READY_REG(pipe), 1, 0, 0); +} + +void mdfld_dsi_dpi_turn_on(struct mdfld_dsi_dpi_output *output, int pipe) +{ +	struct drm_device *dev = output->dev; + +	/* clear special packet sent bit */ +	if (REG_READ(MIPI_INTR_STAT_REG(pipe)) & DSI_INTR_STATE_SPL_PKG_SENT) +		REG_WRITE(MIPI_INTR_STAT_REG(pipe), +					DSI_INTR_STATE_SPL_PKG_SENT); + +	/*send turn on package*/ +	REG_WRITE(MIPI_DPI_CONTROL_REG(pipe), DSI_DPI_CTRL_HS_TURN_ON); + +	/*wait for SPL_PKG_SENT interrupt*/ +	mdfld_wait_for_SPL_PKG_SENT(dev, pipe); + +	if (REG_READ(MIPI_INTR_STAT_REG(pipe)) & DSI_INTR_STATE_SPL_PKG_SENT) +		REG_WRITE(MIPI_INTR_STAT_REG(pipe), +					DSI_INTR_STATE_SPL_PKG_SENT); + +	output->panel_on = 1; + +	/* FIXME the following is disabled to WA the X slow start issue +	   for TMD panel +	if (pipe == 2) +		dev_priv->dpi_panel_on2 = true; +	else if (pipe == 0) +		dev_priv->dpi_panel_on = true; */ +} + +static void mdfld_dsi_dpi_shut_down(struct mdfld_dsi_dpi_output *output, +								int pipe) +{ +	struct drm_device *dev = output->dev; + +	/*if output is on, or mode setting didn't happen, ignore this*/ +	if ((!output->panel_on) || output->first_boot) { +		output->first_boot = 0; +		return; +	} + +	/* Wait for dpi fifo to empty */ +	mdfld_wait_for_DPI_CTRL_FIFO(dev, pipe); + +	/* Clear the special packet interrupt bit if set */ +	if (REG_READ(MIPI_INTR_STAT_REG(pipe)) & DSI_INTR_STATE_SPL_PKG_SENT) +		REG_WRITE(MIPI_INTR_STAT_REG(pipe), +					DSI_INTR_STATE_SPL_PKG_SENT); + +	if (REG_READ(MIPI_DPI_CONTROL_REG(pipe)) == DSI_DPI_CTRL_HS_SHUTDOWN) +		goto shutdown_out; + +	REG_WRITE(MIPI_DPI_CONTROL_REG(pipe), DSI_DPI_CTRL_HS_SHUTDOWN); + +shutdown_out: +	output->panel_on = 0; +	output->first_boot = 0; + +	/* FIXME the following is disabled to WA the X slow start issue +	   for TMD panel +	if (pipe == 2) +		dev_priv->dpi_panel_on2 = false; +	else if (pipe == 0) +		dev_priv->dpi_panel_on = false;	 */ +} + +static void mdfld_dsi_dpi_set_power(struct drm_encoder *encoder, bool on) +{ +	struct mdfld_dsi_encoder *dsi_encoder = mdfld_dsi_encoder(encoder); +	struct mdfld_dsi_dpi_output *dpi_output = +				MDFLD_DSI_DPI_OUTPUT(dsi_encoder); +	struct mdfld_dsi_config *dsi_config = +				mdfld_dsi_encoder_get_config(dsi_encoder); +	int pipe = mdfld_dsi_encoder_get_pipe(dsi_encoder); +	struct drm_device *dev = dsi_config->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	/*start up display island if it was shutdown*/ +	if (!gma_power_begin(dev, true)) +		return; + +	if (on) { +		if (mdfld_get_panel_type(dev, pipe) == TMD_VID) +			mdfld_dsi_dpi_turn_on(dpi_output, pipe); +		else if (mdfld_get_panel_type(dev, pipe) == TC35876X) +			mdfld_dsi_configure_up(dsi_encoder, pipe); +		else { +			/*enable mipi port*/ +			REG_WRITE(MIPI_PORT_CONTROL(pipe), +				REG_READ(MIPI_PORT_CONTROL(pipe)) | BIT(31)); +			REG_READ(MIPI_PORT_CONTROL(pipe)); + +			mdfld_dsi_dpi_turn_on(dpi_output, pipe); +			mdfld_dsi_tpo_ic_init(dsi_config, pipe); +		} +		dev_priv->dpi_panel_on[pipe] = true; +	} else { +		if (mdfld_get_panel_type(dev, pipe) == TMD_VID) +			mdfld_dsi_dpi_shut_down(dpi_output, pipe); +		else if (mdfld_get_panel_type(dev, pipe) == TC35876X) +			mdfld_dsi_configure_down(dsi_encoder, pipe); +		else { +			mdfld_dsi_dpi_shut_down(dpi_output, pipe); + +			/*disable mipi port*/ +			REG_WRITE(MIPI_PORT_CONTROL(pipe), +				REG_READ(MIPI_PORT_CONTROL(pipe)) & ~BIT(31)); +			REG_READ(MIPI_PORT_CONTROL(pipe)); +		} +		dev_priv->dpi_panel_on[pipe] = false; +	} +	gma_power_end(dev); +} + +void mdfld_dsi_dpi_dpms(struct drm_encoder *encoder, int mode) +{ +	mdfld_dsi_dpi_set_power(encoder, mode == DRM_MODE_DPMS_ON); +} + +bool mdfld_dsi_dpi_mode_fixup(struct drm_encoder *encoder, +				     const struct drm_display_mode *mode, +				     struct drm_display_mode *adjusted_mode) +{ +	struct mdfld_dsi_encoder *dsi_encoder = mdfld_dsi_encoder(encoder); +	struct mdfld_dsi_config *dsi_config = +				mdfld_dsi_encoder_get_config(dsi_encoder); +	struct drm_display_mode *fixed_mode = dsi_config->fixed_mode; + +	if (fixed_mode) { +		adjusted_mode->hdisplay = fixed_mode->hdisplay; +		adjusted_mode->hsync_start = fixed_mode->hsync_start; +		adjusted_mode->hsync_end = fixed_mode->hsync_end; +		adjusted_mode->htotal = fixed_mode->htotal; +		adjusted_mode->vdisplay = fixed_mode->vdisplay; +		adjusted_mode->vsync_start = fixed_mode->vsync_start; +		adjusted_mode->vsync_end = fixed_mode->vsync_end; +		adjusted_mode->vtotal = fixed_mode->vtotal; +		adjusted_mode->clock = fixed_mode->clock; +		drm_mode_set_crtcinfo(adjusted_mode, CRTC_INTERLACE_HALVE_V); +	} +	return true; +} + +void mdfld_dsi_dpi_prepare(struct drm_encoder *encoder) +{ +	mdfld_dsi_dpi_set_power(encoder, false); +} + +void mdfld_dsi_dpi_commit(struct drm_encoder *encoder) +{ +	mdfld_dsi_dpi_set_power(encoder, true); +} + +/* For TC35876X */ +/* This functionality was implemented in FW in iCDK */ +/* But removed in DV0 and later. So need to add here. */ +static void mipi_set_properties(struct mdfld_dsi_config *dsi_config, int pipe) +{ +	struct drm_device *dev = dsi_config->dev; + +	REG_WRITE(MIPI_CTRL_REG(pipe), 0x00000018); +	REG_WRITE(MIPI_INTR_EN_REG(pipe), 0xffffffff); +	REG_WRITE(MIPI_HS_TX_TIMEOUT_REG(pipe), 0xffffff); +	REG_WRITE(MIPI_LP_RX_TIMEOUT_REG(pipe), 0xffffff); +	REG_WRITE(MIPI_TURN_AROUND_TIMEOUT_REG(pipe), 0x14); +	REG_WRITE(MIPI_DEVICE_RESET_TIMER_REG(pipe), 0xff); +	REG_WRITE(MIPI_HIGH_LOW_SWITCH_COUNT_REG(pipe), 0x25); +	REG_WRITE(MIPI_INIT_COUNT_REG(pipe), 0xf0); +	REG_WRITE(MIPI_EOT_DISABLE_REG(pipe), 0x00000000); +	REG_WRITE(MIPI_LP_BYTECLK_REG(pipe), 0x00000004); +	REG_WRITE(MIPI_DBI_BW_CTRL_REG(pipe), 0x00000820); +	REG_WRITE(MIPI_CLK_LANE_SWITCH_TIME_CNT_REG(pipe), (0xa << 16) | 0x14); +} + +static void mdfld_mipi_set_video_timing(struct mdfld_dsi_config *dsi_config, +					int pipe) +{ +	struct drm_device *dev = dsi_config->dev; +	struct mdfld_dsi_dpi_timing dpi_timing; +	struct drm_display_mode *mode = dsi_config->mode; + +	mdfld_dsi_dpi_timing_calculation(mode, &dpi_timing, +					dsi_config->lane_count, +					dsi_config->bpp); + +	REG_WRITE(MIPI_DPI_RESOLUTION_REG(pipe), +		mode->vdisplay << 16 | mode->hdisplay); +	REG_WRITE(MIPI_HSYNC_COUNT_REG(pipe), +		dpi_timing.hsync_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_HBP_COUNT_REG(pipe), +		dpi_timing.hbp_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_HFP_COUNT_REG(pipe), +		dpi_timing.hfp_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_HACTIVE_COUNT_REG(pipe), +		dpi_timing.hactive_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_VSYNC_COUNT_REG(pipe), +		dpi_timing.vsync_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_VBP_COUNT_REG(pipe), +		dpi_timing.vbp_count & DSI_DPI_TIMING_MASK); +	REG_WRITE(MIPI_VFP_COUNT_REG(pipe), +		dpi_timing.vfp_count & DSI_DPI_TIMING_MASK); +} + +static void mdfld_mipi_config(struct mdfld_dsi_config *dsi_config, int pipe) +{ +	struct drm_device *dev = dsi_config->dev; +	int lane_count = dsi_config->lane_count; + +	if (pipe) { +		REG_WRITE(MIPI_PORT_CONTROL(0), 0x00000002); +		REG_WRITE(MIPI_PORT_CONTROL(2), 0x80000000); +	} else { +		REG_WRITE(MIPI_PORT_CONTROL(0), 0x80010000); +		REG_WRITE(MIPI_PORT_CONTROL(2), 0x00); +	} + +	REG_WRITE(MIPI_DPHY_PARAM_REG(pipe), 0x150A600F); +	REG_WRITE(MIPI_VIDEO_MODE_FORMAT_REG(pipe), 0x0000000F); + +	/* lane_count = 3 */ +	REG_WRITE(MIPI_DSI_FUNC_PRG_REG(pipe), 0x00000200 | lane_count); + +	mdfld_mipi_set_video_timing(dsi_config, pipe); +} + +static void mdfld_set_pipe_timing(struct mdfld_dsi_config *dsi_config, int pipe) +{ +	struct drm_device *dev = dsi_config->dev; +	struct drm_display_mode *mode = dsi_config->mode; + +	REG_WRITE(HTOTAL_A, ((mode->htotal - 1) << 16) | (mode->hdisplay - 1)); +	REG_WRITE(HBLANK_A, ((mode->htotal - 1) << 16) | (mode->hdisplay - 1)); +	REG_WRITE(HSYNC_A, +		((mode->hsync_end - 1) << 16) | (mode->hsync_start - 1)); + +	REG_WRITE(VTOTAL_A, ((mode->vtotal - 1) << 16) | (mode->vdisplay - 1)); +	REG_WRITE(VBLANK_A, ((mode->vtotal - 1) << 16) | (mode->vdisplay - 1)); +	REG_WRITE(VSYNC_A, +		((mode->vsync_end - 1) << 16) | (mode->vsync_start - 1)); + +	REG_WRITE(PIPEASRC, +		((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1)); +} +/* End for TC35876X */ + +void mdfld_dsi_dpi_mode_set(struct drm_encoder *encoder, +				   struct drm_display_mode *mode, +				   struct drm_display_mode *adjusted_mode) +{ +	struct mdfld_dsi_encoder *dsi_encoder = mdfld_dsi_encoder(encoder); +	struct mdfld_dsi_dpi_output *dpi_output = +					MDFLD_DSI_DPI_OUTPUT(dsi_encoder); +	struct mdfld_dsi_config *dsi_config = +				mdfld_dsi_encoder_get_config(dsi_encoder); +	struct drm_device *dev = dsi_config->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	int pipe = mdfld_dsi_encoder_get_pipe(dsi_encoder); + +	u32 pipeconf_reg = PIPEACONF; +	u32 dspcntr_reg = DSPACNTR; + +	u32 pipeconf = dev_priv->pipeconf[pipe]; +	u32 dspcntr = dev_priv->dspcntr[pipe]; +	u32 mipi = MIPI_PORT_EN | PASS_FROM_SPHY_TO_AFE | SEL_FLOPPED_HSTX; + +	if (pipe) { +		pipeconf_reg = PIPECCONF; +		dspcntr_reg = DSPCCNTR; +	} else { +		if (mdfld_get_panel_type(dev, pipe) == TC35876X) +			mipi &= (~0x03); /* Use all four lanes */ +		else +			mipi |= 2; +	} + +	/*start up display island if it was shutdown*/ +	if (!gma_power_begin(dev, true)) +		return; + +	if (mdfld_get_panel_type(dev, pipe) == TC35876X) { +		/* +		 * The following logic is required to reset the bridge and +		 * configure. This also starts the DSI clock at 200MHz. +		 */ +		tc35876x_set_bridge_reset_state(dev, 0);  /*Pull High Reset */ +		tc35876x_toshiba_bridge_panel_on(dev); +		udelay(100); +		/* Now start the DSI clock */ +		REG_WRITE(MRST_DPLL_A, 0x00); +		REG_WRITE(MRST_FPA0, 0xC1); +		REG_WRITE(MRST_DPLL_A, 0x00800000); +		udelay(500); +		REG_WRITE(MRST_DPLL_A, 0x80800000); + +		if (REG_BIT_WAIT(pipeconf_reg, 1, 29)) +			dev_err(&dev->pdev->dev, "%s: DSI PLL lock timeout\n", +				__func__); + +		REG_WRITE(MIPI_DPHY_PARAM_REG(pipe), 0x2A0c6008); + +		mipi_set_properties(dsi_config, pipe); +		mdfld_mipi_config(dsi_config, pipe); +		mdfld_set_pipe_timing(dsi_config, pipe); + +		REG_WRITE(DSPABASE, 0x00); +		REG_WRITE(DSPASIZE, +			((mode->vdisplay - 1) << 16) | (mode->hdisplay - 1)); + +		REG_WRITE(DSPACNTR, 0x98000000); +		REG_WRITE(DSPASURF, 0x00); + +		REG_WRITE(VGACNTRL, 0x80000000); +		REG_WRITE(DEVICE_READY_REG, 0x00000001); + +		REG_WRITE(MIPI_PORT_CONTROL(pipe), 0x80810000); +	} else { +		/*set up mipi port FIXME: do at init time */ +		REG_WRITE(MIPI_PORT_CONTROL(pipe), mipi); +	} +	REG_READ(MIPI_PORT_CONTROL(pipe)); + +	if (mdfld_get_panel_type(dev, pipe) == TMD_VID) { +		/* NOP */ +	} else if (mdfld_get_panel_type(dev, pipe) == TC35876X) { +		/* set up DSI controller DPI interface */ +		mdfld_dsi_dpi_controller_init(dsi_config, pipe); + +		/* Configure MIPI Bridge and Panel */ +		tc35876x_configure_lvds_bridge(dev); +		dev_priv->dpi_panel_on[pipe] = true; +	} else { +		/*turn on DPI interface*/ +		mdfld_dsi_dpi_turn_on(dpi_output, pipe); +	} + +	/*set up pipe*/ +	REG_WRITE(pipeconf_reg, pipeconf); +	REG_READ(pipeconf_reg); + +	/*set up display plane*/ +	REG_WRITE(dspcntr_reg, dspcntr); +	REG_READ(dspcntr_reg); + +	msleep(20); /* FIXME: this should wait for vblank */ + +	if (mdfld_get_panel_type(dev, pipe) == TMD_VID) { +		/* NOP */ +	} else if (mdfld_get_panel_type(dev, pipe) == TC35876X) { +		mdfld_dsi_dpi_turn_on(dpi_output, pipe); +	} else { +		/* init driver ic */ +		mdfld_dsi_tpo_ic_init(dsi_config, pipe); +		/*init backlight*/ +		mdfld_dsi_brightness_init(dsi_config, pipe); +	} + +	gma_power_end(dev); +} + +/* + * Init DSI DPI encoder. + * Allocate an mdfld_dsi_encoder and attach it to given @dsi_connector + * return pointer of newly allocated DPI encoder, NULL on error + */ +struct mdfld_dsi_encoder *mdfld_dsi_dpi_init(struct drm_device *dev, +				struct mdfld_dsi_connector *dsi_connector, +				const struct panel_funcs *p_funcs) +{ +	struct mdfld_dsi_dpi_output *dpi_output = NULL; +	struct mdfld_dsi_config *dsi_config; +	struct drm_connector *connector = NULL; +	struct drm_encoder *encoder = NULL; +	int pipe; +	u32 data; +	int ret; + +	pipe = dsi_connector->pipe; + +	if (mdfld_get_panel_type(dev, pipe) != TC35876X) { +		dsi_config = mdfld_dsi_get_config(dsi_connector); + +		/* panel hard-reset */ +		if (p_funcs->reset) { +			ret = p_funcs->reset(pipe); +			if (ret) { +				DRM_ERROR("Panel %d hard-reset failed\n", pipe); +				return NULL; +			} +		} + +		/* panel drvIC init */ +		if (p_funcs->drv_ic_init) +			p_funcs->drv_ic_init(dsi_config, pipe); + +		/* panel power mode detect */ +		ret = mdfld_dsi_get_power_mode(dsi_config, &data, false); +		if (ret) { +			DRM_ERROR("Panel %d get power mode failed\n", pipe); +			dsi_connector->status = connector_status_disconnected; +		} else { +			DRM_INFO("pipe %d power mode 0x%x\n", pipe, data); +			dsi_connector->status = connector_status_connected; +		} +	} + +	dpi_output = kzalloc(sizeof(struct mdfld_dsi_dpi_output), GFP_KERNEL); +	if (!dpi_output) { +		DRM_ERROR("No memory\n"); +		return NULL; +	} + +	if (dsi_connector->pipe) +		dpi_output->panel_on = 0; +	else +		dpi_output->panel_on = 0; + +	dpi_output->dev = dev; +	if (mdfld_get_panel_type(dev, pipe) != TC35876X) +		dpi_output->p_funcs = p_funcs; +	dpi_output->first_boot = 1; + +	/*get fixed mode*/ +	dsi_config = mdfld_dsi_get_config(dsi_connector); + +	/*create drm encoder object*/ +	connector = &dsi_connector->base.base; +	encoder = &dpi_output->base.base.base; +	drm_encoder_init(dev, +			encoder, +			p_funcs->encoder_funcs, +			DRM_MODE_ENCODER_LVDS); +	drm_encoder_helper_add(encoder, +				p_funcs->encoder_helper_funcs); + +	/*attach to given connector*/ +	drm_mode_connector_attach_encoder(connector, encoder); + +	/*set possible crtcs and clones*/ +	if (dsi_connector->pipe) { +		encoder->possible_crtcs = (1 << 2); +		encoder->possible_clones = (1 << 1); +	} else { +		encoder->possible_crtcs = (1 << 0); +		encoder->possible_clones = (1 << 0); +	} + +	dsi_connector->base.encoder = &dpi_output->base.base; + +	return &dpi_output->base; +} diff --git a/drivers/gpu/drm/gma500/mdfld_dsi_dpi.h b/drivers/gpu/drm/gma500/mdfld_dsi_dpi.h new file mode 100644 index 00000000000..2b40663e169 --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_dsi_dpi.h @@ -0,0 +1,79 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * jim liu <jim.liu@intel.com> + * Jackie Li<yaodong.li@intel.com> + */ + +#ifndef __MDFLD_DSI_DPI_H__ +#define __MDFLD_DSI_DPI_H__ + +#include "mdfld_dsi_output.h" +#include "mdfld_output.h" + +struct mdfld_dsi_dpi_timing { +	u16 hsync_count; +	u16 hbp_count; +	u16 hfp_count; +	u16 hactive_count; +	u16 vsync_count; +	u16 vbp_count; +	u16 vfp_count; +}; + +struct mdfld_dsi_dpi_output { +	struct mdfld_dsi_encoder base; +	struct drm_device *dev; + +	int panel_on; +	int first_boot; + +	const struct panel_funcs *p_funcs; +}; + +#define MDFLD_DSI_DPI_OUTPUT(dsi_encoder)\ +	container_of(dsi_encoder, struct mdfld_dsi_dpi_output, base) + +/* Export functions */ +extern int mdfld_dsi_dpi_timing_calculation(struct drm_display_mode *mode, +				struct mdfld_dsi_dpi_timing *dpi_timing, +				int num_lane, int bpp); +extern struct mdfld_dsi_encoder *mdfld_dsi_dpi_init(struct drm_device *dev, +				struct mdfld_dsi_connector *dsi_connector, +				const struct panel_funcs *p_funcs); + +/* MDFLD DPI helper functions */ +extern void mdfld_dsi_dpi_dpms(struct drm_encoder *encoder, int mode); +extern bool mdfld_dsi_dpi_mode_fixup(struct drm_encoder *encoder, +				const struct drm_display_mode *mode, +				struct drm_display_mode *adjusted_mode); +extern void mdfld_dsi_dpi_prepare(struct drm_encoder *encoder); +extern void mdfld_dsi_dpi_commit(struct drm_encoder *encoder); +extern void mdfld_dsi_dpi_mode_set(struct drm_encoder *encoder, +				struct drm_display_mode *mode, +				struct drm_display_mode *adjusted_mode); +extern void mdfld_dsi_dpi_turn_on(struct mdfld_dsi_dpi_output *output, +				int pipe); +extern void mdfld_dsi_dpi_controller_init(struct mdfld_dsi_config *dsi_config, +				int pipe); +#endif /*__MDFLD_DSI_DPI_H__*/ diff --git a/drivers/gpu/drm/gma500/mdfld_dsi_output.c b/drivers/gpu/drm/gma500/mdfld_dsi_output.c new file mode 100644 index 00000000000..6e91b20ce2e --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_dsi_output.c @@ -0,0 +1,614 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * jim liu <jim.liu@intel.com> + * Jackie Li<yaodong.li@intel.com> + */ + +#include <linux/module.h> + +#include "mdfld_dsi_output.h" +#include "mdfld_dsi_dpi.h" +#include "mdfld_output.h" +#include "mdfld_dsi_pkg_sender.h" +#include "tc35876x-dsi-lvds.h" +#include <linux/pm_runtime.h> +#include <asm/intel_scu_ipc.h> + +/* get the LABC from command line. */ +static int LABC_control = 1; + +#ifdef MODULE +module_param(LABC_control, int, 0644); +#else + +static int __init parse_LABC_control(char *arg) +{ +	/* LABC control can be passed in as a cmdline parameter */ +	/* to enable this feature add LABC=1 to cmdline */ +	/* to disable this feature add LABC=0 to cmdline */ +	if (!arg) +		return -EINVAL; + +	if (!strcasecmp(arg, "0")) +		LABC_control = 0; +	else if (!strcasecmp(arg, "1")) +		LABC_control = 1; + +	return 0; +} +early_param("LABC", parse_LABC_control); +#endif + +/** + * Check and see if the generic control or data buffer is empty and ready. + */ +void mdfld_dsi_gen_fifo_ready(struct drm_device *dev, u32 gen_fifo_stat_reg, +							u32 fifo_stat) +{ +	u32 GEN_BF_time_out_count; + +	/* Check MIPI Adatper command registers */ +	for (GEN_BF_time_out_count = 0; +			GEN_BF_time_out_count < GEN_FB_TIME_OUT; +			GEN_BF_time_out_count++) { +		if ((REG_READ(gen_fifo_stat_reg) & fifo_stat) == fifo_stat) +			break; +		udelay(100); +	} + +	if (GEN_BF_time_out_count == GEN_FB_TIME_OUT) +		DRM_ERROR("mdfld_dsi_gen_fifo_ready, Timeout. gen_fifo_stat_reg = 0x%x.\n", +					gen_fifo_stat_reg); +} + +/** + * Manage the DSI MIPI keyboard and display brightness. + * FIXME: this is exported to OSPM code. should work out an specific + * display interface to OSPM. + */ + +void mdfld_dsi_brightness_init(struct mdfld_dsi_config *dsi_config, int pipe) +{ +	struct mdfld_dsi_pkg_sender *sender = +				mdfld_dsi_get_pkg_sender(dsi_config); +	struct drm_device *dev; +	struct drm_psb_private *dev_priv; +	u32 gen_ctrl_val; + +	if (!sender) { +		DRM_ERROR("No sender found\n"); +		return; +	} + +	dev = sender->dev; +	dev_priv = dev->dev_private; + +	/* Set default display backlight value to 85% (0xd8)*/ +	mdfld_dsi_send_mcs_short(sender, write_display_brightness, 0xd8, 1, +				true); + +	/* Set minimum brightness setting of CABC function to 20% (0x33)*/ +	mdfld_dsi_send_mcs_short(sender, write_cabc_min_bright, 0x33, 1, true); + +	/* Enable backlight or/and LABC */ +	gen_ctrl_val = BRIGHT_CNTL_BLOCK_ON | DISPLAY_DIMMING_ON | +								BACKLIGHT_ON; +	if (LABC_control == 1) +		gen_ctrl_val |= DISPLAY_DIMMING_ON | DISPLAY_BRIGHTNESS_AUTO +								| GAMMA_AUTO; + +	if (LABC_control == 1) +		gen_ctrl_val |= AMBIENT_LIGHT_SENSE_ON; + +	dev_priv->mipi_ctrl_display = gen_ctrl_val; + +	mdfld_dsi_send_mcs_short(sender, write_ctrl_display, (u8)gen_ctrl_val, +				1, true); + +	mdfld_dsi_send_mcs_short(sender, write_ctrl_cabc, UI_IMAGE, 1, true); +} + +void mdfld_dsi_brightness_control(struct drm_device *dev, int pipe, int level) +{ +	struct mdfld_dsi_pkg_sender *sender; +	struct drm_psb_private *dev_priv; +	struct mdfld_dsi_config *dsi_config; +	u32 gen_ctrl_val = 0; +	int p_type = TMD_VID; + +	if (!dev || (pipe != 0 && pipe != 2)) { +		DRM_ERROR("Invalid parameter\n"); +		return; +	} + +	p_type = mdfld_get_panel_type(dev, 0); + +	dev_priv = dev->dev_private; + +	if (pipe) +		dsi_config = dev_priv->dsi_configs[1]; +	else +		dsi_config = dev_priv->dsi_configs[0]; + +	sender = mdfld_dsi_get_pkg_sender(dsi_config); + +	if (!sender) { +		DRM_ERROR("No sender found\n"); +		return; +	} + +	gen_ctrl_val = (level * 0xff / MDFLD_DSI_BRIGHTNESS_MAX_LEVEL) & 0xff; + +	dev_dbg(sender->dev->dev, "pipe = %d, gen_ctrl_val = %d.\n", +							pipe, gen_ctrl_val); + +	if (p_type == TMD_VID) { +		/* Set display backlight value */ +		mdfld_dsi_send_mcs_short(sender, tmd_write_display_brightness, +					(u8)gen_ctrl_val, 1, true); +	} else { +		/* Set display backlight value */ +		mdfld_dsi_send_mcs_short(sender, write_display_brightness, +					(u8)gen_ctrl_val, 1, true); + +		/* Enable backlight control */ +		if (level == 0) +			gen_ctrl_val = 0; +		else +			gen_ctrl_val = dev_priv->mipi_ctrl_display; + +		mdfld_dsi_send_mcs_short(sender, write_ctrl_display, +					(u8)gen_ctrl_val, 1, true); +	} +} + +static int mdfld_dsi_get_panel_status(struct mdfld_dsi_config *dsi_config, +				u8 dcs, u32 *data, bool hs) +{ +	struct mdfld_dsi_pkg_sender *sender +		= mdfld_dsi_get_pkg_sender(dsi_config); + +	if (!sender || !data) { +		DRM_ERROR("Invalid parameter\n"); +		return -EINVAL; +	} + +	return mdfld_dsi_read_mcs(sender, dcs, data, 1, hs); +} + +int mdfld_dsi_get_power_mode(struct mdfld_dsi_config *dsi_config, u32 *mode, +			bool hs) +{ +	if (!dsi_config || !mode) { +		DRM_ERROR("Invalid parameter\n"); +		return -EINVAL; +	} + +	return mdfld_dsi_get_panel_status(dsi_config, 0x0a, mode, hs); +} + +/* + * NOTE: this function was used by OSPM. + * TODO: will be removed later, should work out display interfaces for OSPM + */ +void mdfld_dsi_controller_init(struct mdfld_dsi_config *dsi_config, int pipe) +{ +	if (!dsi_config || ((pipe != 0) && (pipe != 2))) { +		DRM_ERROR("Invalid parameters\n"); +		return; +	} + +	mdfld_dsi_dpi_controller_init(dsi_config, pipe); +} + +static void mdfld_dsi_connector_save(struct drm_connector *connector) +{ +} + +static void mdfld_dsi_connector_restore(struct drm_connector *connector) +{ +} + +/* FIXME: start using the force parameter */ +static enum drm_connector_status +mdfld_dsi_connector_detect(struct drm_connector *connector, bool force) +{ +	struct mdfld_dsi_connector *dsi_connector +		= mdfld_dsi_connector(connector); + +	dsi_connector->status = connector_status_connected; + +	return dsi_connector->status; +} + +static int mdfld_dsi_connector_set_property(struct drm_connector *connector, +				struct drm_property *property, +				uint64_t value) +{ +	struct drm_encoder *encoder = connector->encoder; + +	if (!strcmp(property->name, "scaling mode") && encoder) { +		struct gma_crtc *gma_crtc = to_gma_crtc(encoder->crtc); +		bool centerechange; +		uint64_t val; + +		if (!gma_crtc) +			goto set_prop_error; + +		switch (value) { +		case DRM_MODE_SCALE_FULLSCREEN: +			break; +		case DRM_MODE_SCALE_NO_SCALE: +			break; +		case DRM_MODE_SCALE_ASPECT: +			break; +		default: +			goto set_prop_error; +		} + +		if (drm_object_property_get_value(&connector->base, property, &val)) +			goto set_prop_error; + +		if (val == value) +			goto set_prop_done; + +		if (drm_object_property_set_value(&connector->base, +							property, value)) +			goto set_prop_error; + +		centerechange = (val == DRM_MODE_SCALE_NO_SCALE) || +			(value == DRM_MODE_SCALE_NO_SCALE); + +		if (gma_crtc->saved_mode.hdisplay != 0 && +		    gma_crtc->saved_mode.vdisplay != 0) { +			if (centerechange) { +				if (!drm_crtc_helper_set_mode(encoder->crtc, +						&gma_crtc->saved_mode, +						encoder->crtc->x, +						encoder->crtc->y, +						encoder->crtc->primary->fb)) +					goto set_prop_error; +			} else { +				struct drm_encoder_helper_funcs *funcs = +						encoder->helper_private; +				funcs->mode_set(encoder, +					&gma_crtc->saved_mode, +					&gma_crtc->saved_adjusted_mode); +			} +		} +	} else if (!strcmp(property->name, "backlight") && encoder) { +		if (drm_object_property_set_value(&connector->base, property, +									value)) +			goto set_prop_error; +		else +			gma_backlight_set(encoder->dev, value); +	} +set_prop_done: +	return 0; +set_prop_error: +	return -1; +} + +static void mdfld_dsi_connector_destroy(struct drm_connector *connector) +{ +	struct mdfld_dsi_connector *dsi_connector = +					mdfld_dsi_connector(connector); +	struct mdfld_dsi_pkg_sender *sender; + +	if (!dsi_connector) +		return; +	drm_sysfs_connector_remove(connector); +	drm_connector_cleanup(connector); +	sender = dsi_connector->pkg_sender; +	mdfld_dsi_pkg_sender_destroy(sender); +	kfree(dsi_connector); +} + +static int mdfld_dsi_connector_get_modes(struct drm_connector *connector) +{ +	struct mdfld_dsi_connector *dsi_connector = +				mdfld_dsi_connector(connector); +	struct mdfld_dsi_config *dsi_config = +				mdfld_dsi_get_config(dsi_connector); +	struct drm_display_mode *fixed_mode = dsi_config->fixed_mode; +	struct drm_display_mode *dup_mode = NULL; +	struct drm_device *dev = connector->dev; + +	connector->display_info.min_vfreq = 0; +	connector->display_info.max_vfreq = 200; +	connector->display_info.min_hfreq = 0; +	connector->display_info.max_hfreq = 200; + +	if (fixed_mode) { +		dev_dbg(dev->dev, "fixed_mode %dx%d\n", +				fixed_mode->hdisplay, fixed_mode->vdisplay); +		dup_mode = drm_mode_duplicate(dev, fixed_mode); +		drm_mode_probed_add(connector, dup_mode); +		return 1; +	} +	DRM_ERROR("Didn't get any modes!\n"); +	return 0; +} + +static int mdfld_dsi_connector_mode_valid(struct drm_connector *connector, +						struct drm_display_mode *mode) +{ +	struct mdfld_dsi_connector *dsi_connector = +					mdfld_dsi_connector(connector); +	struct mdfld_dsi_config *dsi_config = +					mdfld_dsi_get_config(dsi_connector); +	struct drm_display_mode *fixed_mode = dsi_config->fixed_mode; + +	if (mode->flags & DRM_MODE_FLAG_DBLSCAN) +		return MODE_NO_DBLESCAN; + +	if (mode->flags & DRM_MODE_FLAG_INTERLACE) +		return MODE_NO_INTERLACE; + +	/** +	 * FIXME: current DC has no fitting unit, reject any mode setting +	 * request +	 * Will figure out a way to do up-scaling(pannel fitting) later. +	 **/ +	if (fixed_mode) { +		if (mode->hdisplay != fixed_mode->hdisplay) +			return MODE_PANEL; + +		if (mode->vdisplay != fixed_mode->vdisplay) +			return MODE_PANEL; +	} + +	return MODE_OK; +} + +static void mdfld_dsi_connector_dpms(struct drm_connector *connector, int mode) +{ +	if (mode == connector->dpms) +		return; + +	/*first, execute dpms*/ + +	drm_helper_connector_dpms(connector, mode); +} + +static struct drm_encoder *mdfld_dsi_connector_best_encoder( +				struct drm_connector *connector) +{ +	struct mdfld_dsi_connector *dsi_connector = +				mdfld_dsi_connector(connector); +	struct mdfld_dsi_config *dsi_config = +				mdfld_dsi_get_config(dsi_connector); +	return &dsi_config->encoder->base.base; +} + +/*DSI connector funcs*/ +static const struct drm_connector_funcs mdfld_dsi_connector_funcs = { +	.dpms = /*drm_helper_connector_dpms*/mdfld_dsi_connector_dpms, +	.save = mdfld_dsi_connector_save, +	.restore = mdfld_dsi_connector_restore, +	.detect = mdfld_dsi_connector_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.set_property = mdfld_dsi_connector_set_property, +	.destroy = mdfld_dsi_connector_destroy, +}; + +/*DSI connector helper funcs*/ +static const struct drm_connector_helper_funcs +	mdfld_dsi_connector_helper_funcs = { +	.get_modes = mdfld_dsi_connector_get_modes, +	.mode_valid = mdfld_dsi_connector_mode_valid, +	.best_encoder = mdfld_dsi_connector_best_encoder, +}; + +static int mdfld_dsi_get_default_config(struct drm_device *dev, +				struct mdfld_dsi_config *config, int pipe) +{ +	if (!dev || !config) { +		DRM_ERROR("Invalid parameters"); +		return -EINVAL; +	} + +	config->bpp = 24; +	if (mdfld_get_panel_type(dev, pipe) == TC35876X) +		config->lane_count = 4; +	else +		config->lane_count = 2; +	config->channel_num = 0; + +	if (mdfld_get_panel_type(dev, pipe) == TMD_VID) +		config->video_mode = MDFLD_DSI_VIDEO_NON_BURST_MODE_SYNC_PULSE; +	else if (mdfld_get_panel_type(dev, pipe) == TC35876X) +		config->video_mode = +				MDFLD_DSI_VIDEO_NON_BURST_MODE_SYNC_EVENTS; +	else +		config->video_mode = MDFLD_DSI_VIDEO_BURST_MODE; + +	return 0; +} + +int mdfld_dsi_panel_reset(int pipe) +{ +	unsigned gpio; +	int ret = 0; + +	switch (pipe) { +	case 0: +		gpio = 128; +		break; +	case 2: +		gpio = 34; +		break; +	default: +		DRM_ERROR("Invalid output\n"); +		return -EINVAL; +	} + +	ret = gpio_request(gpio, "gfx"); +	if (ret) { +		DRM_ERROR("gpio_rqueset failed\n"); +		return ret; +	} + +	ret = gpio_direction_output(gpio, 1); +	if (ret) { +		DRM_ERROR("gpio_direction_output failed\n"); +		goto gpio_error; +	} + +	gpio_get_value(128); + +gpio_error: +	if (gpio_is_valid(gpio)) +		gpio_free(gpio); + +	return ret; +} + +/* + * MIPI output init + * @dev drm device + * @pipe pipe number. 0 or 2 + * @config + * + * Do the initialization of a MIPI output, including create DRM mode objects + * initialization of DSI output on @pipe + */ +void mdfld_dsi_output_init(struct drm_device *dev, +			   int pipe, +			   const struct panel_funcs *p_vid_funcs) +{ +	struct mdfld_dsi_config *dsi_config; +	struct mdfld_dsi_connector *dsi_connector; +	struct drm_connector *connector; +	struct mdfld_dsi_encoder *encoder; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct panel_info dsi_panel_info; +	u32 width_mm, height_mm; + +	dev_dbg(dev->dev, "init DSI output on pipe %d\n", pipe); + +	if (pipe != 0 && pipe != 2) { +		DRM_ERROR("Invalid parameter\n"); +		return; +	} + +	/*create a new connetor*/ +	dsi_connector = kzalloc(sizeof(struct mdfld_dsi_connector), GFP_KERNEL); +	if (!dsi_connector) { +		DRM_ERROR("No memory"); +		return; +	} + +	dsi_connector->pipe =  pipe; + +	dsi_config = kzalloc(sizeof(struct mdfld_dsi_config), +			GFP_KERNEL); +	if (!dsi_config) { +		DRM_ERROR("cannot allocate memory for DSI config\n"); +		goto dsi_init_err0; +	} +	mdfld_dsi_get_default_config(dev, dsi_config, pipe); + +	dsi_connector->private = dsi_config; + +	dsi_config->changed = 1; +	dsi_config->dev = dev; + +	dsi_config->fixed_mode = p_vid_funcs->get_config_mode(dev); +	if (p_vid_funcs->get_panel_info(dev, pipe, &dsi_panel_info)) +			goto dsi_init_err0; + +	width_mm = dsi_panel_info.width_mm; +	height_mm = dsi_panel_info.height_mm; + +	dsi_config->mode = dsi_config->fixed_mode; +	dsi_config->connector = dsi_connector; + +	if (!dsi_config->fixed_mode) { +		DRM_ERROR("No pannel fixed mode was found\n"); +		goto dsi_init_err0; +	} + +	if (pipe && dev_priv->dsi_configs[0]) { +		dsi_config->dvr_ic_inited = 0; +		dev_priv->dsi_configs[1] = dsi_config; +	} else if (pipe == 0) { +		dsi_config->dvr_ic_inited = 1; +		dev_priv->dsi_configs[0] = dsi_config; +	} else { +		DRM_ERROR("Trying to init MIPI1 before MIPI0\n"); +		goto dsi_init_err0; +	} + + +	connector = &dsi_connector->base.base; +	drm_connector_init(dev, connector, &mdfld_dsi_connector_funcs, +						DRM_MODE_CONNECTOR_LVDS); +	drm_connector_helper_add(connector, &mdfld_dsi_connector_helper_funcs); + +	connector->display_info.subpixel_order = SubPixelHorizontalRGB; +	connector->display_info.width_mm = width_mm; +	connector->display_info.height_mm = height_mm; +	connector->interlace_allowed = false; +	connector->doublescan_allowed = false; + +	/*attach properties*/ +	drm_object_attach_property(&connector->base, +				dev->mode_config.scaling_mode_property, +				DRM_MODE_SCALE_FULLSCREEN); +	drm_object_attach_property(&connector->base, +				dev_priv->backlight_property, +				MDFLD_DSI_BRIGHTNESS_MAX_LEVEL); + +	/*init DSI package sender on this output*/ +	if (mdfld_dsi_pkg_sender_init(dsi_connector, pipe)) { +		DRM_ERROR("Package Sender initialization failed on pipe %d\n", +									pipe); +		goto dsi_init_err0; +	} + +	encoder = mdfld_dsi_dpi_init(dev, dsi_connector, p_vid_funcs); +	if (!encoder) { +		DRM_ERROR("Create DPI encoder failed\n"); +		goto dsi_init_err1; +	} +	encoder->private = dsi_config; +	dsi_config->encoder = encoder; +	encoder->base.type = (pipe == 0) ? INTEL_OUTPUT_MIPI : +		INTEL_OUTPUT_MIPI2; +	drm_sysfs_connector_add(connector); +	return; + +	/*TODO: add code to destroy outputs on error*/ +dsi_init_err1: +	/*destroy sender*/ +	mdfld_dsi_pkg_sender_destroy(dsi_connector->pkg_sender); + +	drm_connector_cleanup(connector); + +	kfree(dsi_config->fixed_mode); +	kfree(dsi_config); +dsi_init_err0: +	kfree(dsi_connector); +} diff --git a/drivers/gpu/drm/gma500/mdfld_dsi_output.h b/drivers/gpu/drm/gma500/mdfld_dsi_output.h new file mode 100644 index 00000000000..5b646c1f0c3 --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_dsi_output.h @@ -0,0 +1,377 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * jim liu <jim.liu@intel.com> + * Jackie Li<yaodong.li@intel.com> + */ + +#ifndef __MDFLD_DSI_OUTPUT_H__ +#define __MDFLD_DSI_OUTPUT_H__ + +#include <linux/backlight.h> +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> + +#include "psb_drv.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "mdfld_output.h" + +#include <asm/intel-mid.h> + +#define FLD_MASK(start, end)	(((1 << ((start) - (end) + 1)) - 1) << (end)) +#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end)) +#define FLD_GET(val, start, end) (((val) & FLD_MASK(start, end)) >> (end)) +#define FLD_MOD(orig, val, start, end) \ +	(((orig) & ~FLD_MASK(start, end)) | FLD_VAL(val, start, end)) + +#define REG_FLD_MOD(reg, val, start, end) \ +	REG_WRITE(reg, FLD_MOD(REG_READ(reg), val, start, end)) + +static inline int REGISTER_FLD_WAIT(struct drm_device *dev, u32 reg, +		u32 val, int start, int end) +{ +	int t = 100000; + +	while (FLD_GET(REG_READ(reg), start, end) != val) { +		if (--t == 0) +			return 1; +	} + +	return 0; +} + +#define REG_FLD_WAIT(reg, val, start, end) \ +	REGISTER_FLD_WAIT(dev, reg, val, start, end) + +#define REG_BIT_WAIT(reg, val, bitnum) \ +	REGISTER_FLD_WAIT(dev, reg, val, bitnum, bitnum) + +#define MDFLD_DSI_BRIGHTNESS_MAX_LEVEL 100 + +#ifdef DEBUG +#define CHECK_PIPE(pipe) ({			\ +	const typeof(pipe) __pipe = (pipe);	\ +	BUG_ON(__pipe != 0 && __pipe != 2);	\ +	__pipe;	}) +#else +#define CHECK_PIPE(pipe) (pipe) +#endif + +/* + * Actual MIPIA->MIPIC reg offset is 0x800, value 0x400 is valid for 0 and 2 + */ +#define REG_OFFSET(pipe) (CHECK_PIPE(pipe) * 0x400) + +/* mdfld DSI controller registers */ +#define MIPI_DEVICE_READY_REG(pipe)		(0xb000 + REG_OFFSET(pipe)) +#define MIPI_INTR_STAT_REG(pipe)		(0xb004 + REG_OFFSET(pipe)) +#define MIPI_INTR_EN_REG(pipe)			(0xb008 + REG_OFFSET(pipe)) +#define MIPI_DSI_FUNC_PRG_REG(pipe)		(0xb00c + REG_OFFSET(pipe)) +#define MIPI_HS_TX_TIMEOUT_REG(pipe)		(0xb010 + REG_OFFSET(pipe)) +#define MIPI_LP_RX_TIMEOUT_REG(pipe)		(0xb014 + REG_OFFSET(pipe)) +#define MIPI_TURN_AROUND_TIMEOUT_REG(pipe)	(0xb018 + REG_OFFSET(pipe)) +#define MIPI_DEVICE_RESET_TIMER_REG(pipe)	(0xb01c + REG_OFFSET(pipe)) +#define MIPI_DPI_RESOLUTION_REG(pipe)		(0xb020 + REG_OFFSET(pipe)) +#define MIPI_DBI_FIFO_THROTTLE_REG(pipe)	(0xb024 + REG_OFFSET(pipe)) +#define MIPI_HSYNC_COUNT_REG(pipe)		(0xb028 + REG_OFFSET(pipe)) +#define MIPI_HBP_COUNT_REG(pipe)		(0xb02c + REG_OFFSET(pipe)) +#define MIPI_HFP_COUNT_REG(pipe)		(0xb030 + REG_OFFSET(pipe)) +#define MIPI_HACTIVE_COUNT_REG(pipe)		(0xb034 + REG_OFFSET(pipe)) +#define MIPI_VSYNC_COUNT_REG(pipe)		(0xb038 + REG_OFFSET(pipe)) +#define MIPI_VBP_COUNT_REG(pipe)		(0xb03c + REG_OFFSET(pipe)) +#define MIPI_VFP_COUNT_REG(pipe)		(0xb040 + REG_OFFSET(pipe)) +#define MIPI_HIGH_LOW_SWITCH_COUNT_REG(pipe)	(0xb044 + REG_OFFSET(pipe)) +#define MIPI_DPI_CONTROL_REG(pipe)		(0xb048 + REG_OFFSET(pipe)) +#define MIPI_DPI_DATA_REG(pipe)			(0xb04c + REG_OFFSET(pipe)) +#define MIPI_INIT_COUNT_REG(pipe)		(0xb050 + REG_OFFSET(pipe)) +#define MIPI_MAX_RETURN_PACK_SIZE_REG(pipe)	(0xb054 + REG_OFFSET(pipe)) +#define MIPI_VIDEO_MODE_FORMAT_REG(pipe)	(0xb058 + REG_OFFSET(pipe)) +#define MIPI_EOT_DISABLE_REG(pipe)		(0xb05c + REG_OFFSET(pipe)) +#define MIPI_LP_BYTECLK_REG(pipe)		(0xb060 + REG_OFFSET(pipe)) +#define MIPI_LP_GEN_DATA_REG(pipe)		(0xb064 + REG_OFFSET(pipe)) +#define MIPI_HS_GEN_DATA_REG(pipe)		(0xb068 + REG_OFFSET(pipe)) +#define MIPI_LP_GEN_CTRL_REG(pipe)		(0xb06c + REG_OFFSET(pipe)) +#define MIPI_HS_GEN_CTRL_REG(pipe)		(0xb070 + REG_OFFSET(pipe)) +#define MIPI_GEN_FIFO_STAT_REG(pipe)		(0xb074 + REG_OFFSET(pipe)) +#define MIPI_HS_LS_DBI_ENABLE_REG(pipe)		(0xb078 + REG_OFFSET(pipe)) +#define MIPI_DPHY_PARAM_REG(pipe)		(0xb080 + REG_OFFSET(pipe)) +#define MIPI_DBI_BW_CTRL_REG(pipe)		(0xb084 + REG_OFFSET(pipe)) +#define MIPI_CLK_LANE_SWITCH_TIME_CNT_REG(pipe)	(0xb088 + REG_OFFSET(pipe)) + +#define MIPI_CTRL_REG(pipe)			(0xb104 + REG_OFFSET(pipe)) +#define MIPI_DATA_ADD_REG(pipe)			(0xb108 + REG_OFFSET(pipe)) +#define MIPI_DATA_LEN_REG(pipe)			(0xb10c + REG_OFFSET(pipe)) +#define MIPI_CMD_ADD_REG(pipe)			(0xb110 + REG_OFFSET(pipe)) +#define MIPI_CMD_LEN_REG(pipe)			(0xb114 + REG_OFFSET(pipe)) + +/* non-uniform reg offset */ +#define MIPI_PORT_CONTROL(pipe)		(CHECK_PIPE(pipe) ? MIPI_C : MIPI) + +#define DSI_DEVICE_READY				(0x1) +#define DSI_POWER_STATE_ULPS_ENTER			(0x2 << 1) +#define DSI_POWER_STATE_ULPS_EXIT			(0x1 << 1) +#define DSI_POWER_STATE_ULPS_OFFSET			(0x1) + + +#define DSI_ONE_DATA_LANE					(0x1) +#define DSI_TWO_DATA_LANE					(0x2) +#define DSI_THREE_DATA_LANE					(0X3) +#define DSI_FOUR_DATA_LANE					(0x4) +#define DSI_DPI_VIRT_CHANNEL_OFFSET			(0x3) +#define DSI_DBI_VIRT_CHANNEL_OFFSET			(0x5) +#define DSI_DPI_COLOR_FORMAT_RGB565			(0x01 << 7) +#define DSI_DPI_COLOR_FORMAT_RGB666			(0x02 << 7) +#define DSI_DPI_COLOR_FORMAT_RGB666_UNPACK		(0x03 << 7) +#define DSI_DPI_COLOR_FORMAT_RGB888			(0x04 << 7) +#define DSI_DBI_COLOR_FORMAT_OPTION2			(0x05 << 13) + +#define DSI_INTR_STATE_RXSOTERROR			BIT(0) + +#define DSI_INTR_STATE_SPL_PKG_SENT			BIT(30) +#define DSI_INTR_STATE_TE				BIT(31) + +#define DSI_HS_TX_TIMEOUT_MASK				(0xffffff) + +#define DSI_LP_RX_TIMEOUT_MASK				(0xffffff) + +#define DSI_TURN_AROUND_TIMEOUT_MASK		(0x3f) + +#define DSI_RESET_TIMER_MASK				(0xffff) + +#define DSI_DBI_FIFO_WM_HALF				(0x0) +#define DSI_DBI_FIFO_WM_QUARTER				(0x1) +#define DSI_DBI_FIFO_WM_LOW					(0x2) + +#define DSI_DPI_TIMING_MASK					(0xffff) + +#define DSI_INIT_TIMER_MASK					(0xffff) + +#define DSI_DBI_RETURN_PACK_SIZE_MASK		(0x3ff) + +#define DSI_LP_BYTECLK_MASK					(0x0ffff) + +#define DSI_HS_CTRL_GEN_SHORT_W0			(0x03) +#define DSI_HS_CTRL_GEN_SHORT_W1			(0x13) +#define DSI_HS_CTRL_GEN_SHORT_W2			(0x23) +#define DSI_HS_CTRL_GEN_R0					(0x04) +#define DSI_HS_CTRL_GEN_R1					(0x14) +#define DSI_HS_CTRL_GEN_R2					(0x24) +#define DSI_HS_CTRL_GEN_LONG_W				(0x29) +#define DSI_HS_CTRL_MCS_SHORT_W0			(0x05) +#define DSI_HS_CTRL_MCS_SHORT_W1			(0x15) +#define DSI_HS_CTRL_MCS_R0					(0x06) +#define DSI_HS_CTRL_MCS_LONG_W				(0x39) +#define DSI_HS_CTRL_VC_OFFSET				(0x06) +#define DSI_HS_CTRL_WC_OFFSET				(0x08) + +#define	DSI_FIFO_GEN_HS_DATA_FULL			BIT(0) +#define DSI_FIFO_GEN_HS_DATA_HALF_EMPTY		BIT(1) +#define DSI_FIFO_GEN_HS_DATA_EMPTY			BIT(2) +#define DSI_FIFO_GEN_LP_DATA_FULL			BIT(8) +#define DSI_FIFO_GEN_LP_DATA_HALF_EMPTY		BIT(9) +#define DSI_FIFO_GEN_LP_DATA_EMPTY			BIT(10) +#define DSI_FIFO_GEN_HS_CTRL_FULL			BIT(16) +#define DSI_FIFO_GEN_HS_CTRL_HALF_EMPTY		BIT(17) +#define DSI_FIFO_GEN_HS_CTRL_EMPTY			BIT(18) +#define DSI_FIFO_GEN_LP_CTRL_FULL			BIT(24) +#define DSI_FIFO_GEN_LP_CTRL_HALF_EMPTY		BIT(25) +#define DSI_FIFO_GEN_LP_CTRL_EMPTY			BIT(26) +#define DSI_FIFO_DBI_EMPTY					BIT(27) +#define DSI_FIFO_DPI_EMPTY					BIT(28) + +#define DSI_DBI_HS_LP_SWITCH_MASK			(0x1) + +#define DSI_HS_LP_SWITCH_COUNTER_OFFSET		(0x0) +#define DSI_LP_HS_SWITCH_COUNTER_OFFSET		(0x16) + +#define DSI_DPI_CTRL_HS_SHUTDOWN			(0x00000001) +#define DSI_DPI_CTRL_HS_TURN_ON				(0x00000002) + +/*dsi power modes*/ +#define DSI_POWER_MODE_DISPLAY_ON	BIT(2) +#define DSI_POWER_MODE_NORMAL_ON	BIT(3) +#define DSI_POWER_MODE_SLEEP_OUT	BIT(4) +#define DSI_POWER_MODE_PARTIAL_ON	BIT(5) +#define DSI_POWER_MODE_IDLE_ON		BIT(6) + +enum { +	MDFLD_DSI_VIDEO_NON_BURST_MODE_SYNC_PULSE = 1, +	MDFLD_DSI_VIDEO_NON_BURST_MODE_SYNC_EVENTS = 2, +	MDFLD_DSI_VIDEO_BURST_MODE = 3, +}; + +#define DSI_DPI_COMPLETE_LAST_LINE			BIT(2) +#define DSI_DPI_DISABLE_BTA					BIT(3) + +struct mdfld_dsi_connector { +	struct gma_connector base; + +	int pipe; +	void *private; +	void *pkg_sender; + +	/* Connection status */ +	enum drm_connector_status status; +}; + +struct mdfld_dsi_encoder { +	struct gma_encoder base; +	void *private; +}; + +/* + * DSI config, consists of one DSI connector, two DSI encoders. + * DRM will pick up on DSI encoder basing on differents configs. + */ +struct mdfld_dsi_config { +	struct drm_device *dev; +	struct drm_display_mode *fixed_mode; +	struct drm_display_mode *mode; + +	struct mdfld_dsi_connector *connector; +	struct mdfld_dsi_encoder *encoder; + +	int changed; + +	int bpp; +	int lane_count; +	/*Virtual channel number for this encoder*/ +	int channel_num; +	/*video mode configure*/ +	int video_mode; + +	int dvr_ic_inited; +}; + +static inline struct mdfld_dsi_connector *mdfld_dsi_connector( +		struct drm_connector *connector) +{ +	struct gma_connector *gma_connector; + +	gma_connector = to_gma_connector(connector); + +	return container_of(gma_connector, struct mdfld_dsi_connector, base); +} + +static inline struct mdfld_dsi_encoder *mdfld_dsi_encoder( +		struct drm_encoder *encoder) +{ +	struct gma_encoder *gma_encoder; + +	gma_encoder = to_gma_encoder(encoder); + +	return container_of(gma_encoder, struct mdfld_dsi_encoder, base); +} + +static inline struct mdfld_dsi_config * +	mdfld_dsi_get_config(struct mdfld_dsi_connector *connector) +{ +	if (!connector) +		return NULL; +	return (struct mdfld_dsi_config *)connector->private; +} + +static inline void *mdfld_dsi_get_pkg_sender(struct mdfld_dsi_config *config) +{ +	struct mdfld_dsi_connector *dsi_connector; + +	if (!config) +		return NULL; + +	dsi_connector = config->connector; + +	if (!dsi_connector) +		return NULL; + +	return dsi_connector->pkg_sender; +} + +static inline struct mdfld_dsi_config * +	mdfld_dsi_encoder_get_config(struct mdfld_dsi_encoder *encoder) +{ +	if (!encoder) +		return NULL; +	return (struct mdfld_dsi_config *)encoder->private; +} + +static inline struct mdfld_dsi_connector * +	mdfld_dsi_encoder_get_connector(struct mdfld_dsi_encoder *encoder) +{ +	struct mdfld_dsi_config *config; + +	if (!encoder) +		return NULL; + +	config = mdfld_dsi_encoder_get_config(encoder); +	if (!config) +		return NULL; + +	return config->connector; +} + +static inline void *mdfld_dsi_encoder_get_pkg_sender( +				struct mdfld_dsi_encoder *encoder) +{ +	struct mdfld_dsi_config *dsi_config; + +	dsi_config = mdfld_dsi_encoder_get_config(encoder); +	if (!dsi_config) +		return NULL; + +	return mdfld_dsi_get_pkg_sender(dsi_config); +} + +static inline int mdfld_dsi_encoder_get_pipe(struct mdfld_dsi_encoder *encoder) +{ +	struct mdfld_dsi_connector *connector; + +	if (!encoder) +		return -1; + +	connector = mdfld_dsi_encoder_get_connector(encoder); +	if (!connector) +		return -1; +	return connector->pipe; +} + +/* Export functions */ +extern void mdfld_dsi_gen_fifo_ready(struct drm_device *dev, +					u32 gen_fifo_stat_reg, u32 fifo_stat); +extern void mdfld_dsi_brightness_init(struct mdfld_dsi_config *dsi_config, +					int pipe); +extern void mdfld_dsi_brightness_control(struct drm_device *dev, int pipe, +					int level); +extern void mdfld_dsi_output_init(struct drm_device *dev, +					int pipe, +					const struct panel_funcs *p_vid_funcs); +extern void mdfld_dsi_controller_init(struct mdfld_dsi_config *dsi_config, +					int pipe); + +extern int mdfld_dsi_get_power_mode(struct mdfld_dsi_config *dsi_config, +					u32 *mode, bool hs); +extern int mdfld_dsi_panel_reset(int pipe); + +#endif /*__MDFLD_DSI_OUTPUT_H__*/ diff --git a/drivers/gpu/drm/gma500/mdfld_dsi_pkg_sender.c b/drivers/gpu/drm/gma500/mdfld_dsi_pkg_sender.c new file mode 100644 index 00000000000..87885d8c06e --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_dsi_pkg_sender.c @@ -0,0 +1,688 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Jackie Li<yaodong.li@intel.com> + */ + +#include <linux/freezer.h> + +#include "mdfld_dsi_output.h" +#include "mdfld_dsi_pkg_sender.h" +#include "mdfld_dsi_dpi.h" + +#define MDFLD_DSI_READ_MAX_COUNT		5000 + +enum data_type { +	DSI_DT_GENERIC_SHORT_WRITE_0	= 0x03, +	DSI_DT_GENERIC_SHORT_WRITE_1	= 0x13, +	DSI_DT_GENERIC_SHORT_WRITE_2	= 0x23, +	DSI_DT_GENERIC_READ_0		= 0x04, +	DSI_DT_GENERIC_READ_1		= 0x14, +	DSI_DT_GENERIC_READ_2		= 0x24, +	DSI_DT_GENERIC_LONG_WRITE	= 0x29, +	DSI_DT_DCS_SHORT_WRITE_0	= 0x05, +	DSI_DT_DCS_SHORT_WRITE_1	= 0x15, +	DSI_DT_DCS_READ			= 0x06, +	DSI_DT_DCS_LONG_WRITE		= 0x39, +}; + +enum { +	MDFLD_DSI_PANEL_MODE_SLEEP = 0x1, +}; + +enum { +	MDFLD_DSI_PKG_SENDER_FREE = 0x0, +	MDFLD_DSI_PKG_SENDER_BUSY = 0x1, +}; + +static const char *const dsi_errors[] = { +	"RX SOT Error", +	"RX SOT Sync Error", +	"RX EOT Sync Error", +	"RX Escape Mode Entry Error", +	"RX LP TX Sync Error", +	"RX HS Receive Timeout Error", +	"RX False Control Error", +	"RX ECC Single Bit Error", +	"RX ECC Multibit Error", +	"RX Checksum Error", +	"RX DSI Data Type Not Recognised", +	"RX DSI VC ID Invalid", +	"TX False Control Error", +	"TX ECC Single Bit Error", +	"TX ECC Multibit Error", +	"TX Checksum Error", +	"TX DSI Data Type Not Recognised", +	"TX DSI VC ID invalid", +	"High Contention", +	"Low contention", +	"DPI FIFO Under run", +	"HS TX Timeout", +	"LP RX Timeout", +	"Turn Around ACK Timeout", +	"ACK With No Error", +	"RX Invalid TX Length", +	"RX Prot Violation", +	"HS Generic Write FIFO Full", +	"LP Generic Write FIFO Full", +	"Generic Read Data Avail" +	"Special Packet Sent", +	"Tearing Effect", +}; + +static inline int wait_for_gen_fifo_empty(struct mdfld_dsi_pkg_sender *sender, +						u32 mask) +{ +	struct drm_device *dev = sender->dev; +	u32 gen_fifo_stat_reg = sender->mipi_gen_fifo_stat_reg; +	int retry = 0xffff; + +	while (retry--) { +		if ((mask & REG_READ(gen_fifo_stat_reg)) == mask) +			return 0; +		udelay(100); +	} +	DRM_ERROR("fifo is NOT empty 0x%08x\n", REG_READ(gen_fifo_stat_reg)); +	return -EIO; +} + +static int wait_for_all_fifos_empty(struct mdfld_dsi_pkg_sender *sender) +{ +	return wait_for_gen_fifo_empty(sender, (BIT(2) | BIT(10) | BIT(18) | +						BIT(26) | BIT(27) | BIT(28))); +} + +static int wait_for_lp_fifos_empty(struct mdfld_dsi_pkg_sender *sender) +{ +	return wait_for_gen_fifo_empty(sender, (BIT(10) | BIT(26))); +} + +static int wait_for_hs_fifos_empty(struct mdfld_dsi_pkg_sender *sender) +{ +	return wait_for_gen_fifo_empty(sender, (BIT(2) | BIT(18))); +} + +static int handle_dsi_error(struct mdfld_dsi_pkg_sender *sender, u32 mask) +{ +	u32 intr_stat_reg = sender->mipi_intr_stat_reg; +	struct drm_device *dev = sender->dev; + +	dev_dbg(sender->dev->dev, "Handling error 0x%08x\n", mask); + +	switch (mask) { +	case BIT(0): +	case BIT(1): +	case BIT(2): +	case BIT(3): +	case BIT(4): +	case BIT(5): +	case BIT(6): +	case BIT(7): +	case BIT(8): +	case BIT(9): +	case BIT(10): +	case BIT(11): +	case BIT(12): +	case BIT(13): +		dev_dbg(sender->dev->dev, "No Action required\n"); +		break; +	case BIT(14): +		/*wait for all fifo empty*/ +		/*wait_for_all_fifos_empty(sender)*/ +		break; +	case BIT(15): +		dev_dbg(sender->dev->dev, "No Action required\n"); +		break; +	case BIT(16): +		break; +	case BIT(17): +		break; +	case BIT(18): +	case BIT(19): +		dev_dbg(sender->dev->dev, "High/Low contention detected\n"); +		/*wait for contention recovery time*/ +		/*mdelay(10);*/ +		/*wait for all fifo empty*/ +		if (0) +			wait_for_all_fifos_empty(sender); +		break; +	case BIT(20): +		dev_dbg(sender->dev->dev, "No Action required\n"); +		break; +	case BIT(21): +		/*wait for all fifo empty*/ +		/*wait_for_all_fifos_empty(sender);*/ +		break; +	case BIT(22): +		break; +	case BIT(23): +	case BIT(24): +	case BIT(25): +	case BIT(26): +	case BIT(27): +		dev_dbg(sender->dev->dev, "HS Gen fifo full\n"); +		REG_WRITE(intr_stat_reg, mask); +		wait_for_hs_fifos_empty(sender); +		break; +	case BIT(28): +		dev_dbg(sender->dev->dev, "LP Gen fifo full\n"); +		REG_WRITE(intr_stat_reg, mask); +		wait_for_lp_fifos_empty(sender); +		break; +	case BIT(29): +	case BIT(30): +	case BIT(31): +		dev_dbg(sender->dev->dev, "No Action required\n"); +		break; +	} + +	if (mask & REG_READ(intr_stat_reg)) +		dev_dbg(sender->dev->dev, +				"Cannot clean interrupt 0x%08x\n", mask); +	return 0; +} + +static int dsi_error_handler(struct mdfld_dsi_pkg_sender *sender) +{ +	struct drm_device *dev = sender->dev; +	u32 intr_stat_reg = sender->mipi_intr_stat_reg; +	u32 mask; +	u32 intr_stat; +	int i; +	int err = 0; + +	intr_stat = REG_READ(intr_stat_reg); + +	for (i = 0; i < 32; i++) { +		mask = (0x00000001UL) << i; +		if (intr_stat & mask) { +			dev_dbg(sender->dev->dev, "[DSI]: %s\n", dsi_errors[i]); +			err = handle_dsi_error(sender, mask); +			if (err) +				DRM_ERROR("Cannot handle error\n"); +		} +	} +	return err; +} + +static int send_short_pkg(struct mdfld_dsi_pkg_sender *sender, u8 data_type, +			u8 cmd, u8 param, bool hs) +{ +	struct drm_device *dev = sender->dev; +	u32 ctrl_reg; +	u32 val; +	u8 virtual_channel = 0; + +	if (hs) { +		ctrl_reg = sender->mipi_hs_gen_ctrl_reg; + +		/* FIXME: wait_for_hs_fifos_empty(sender); */ +	} else { +		ctrl_reg = sender->mipi_lp_gen_ctrl_reg; + +		/* FIXME: wait_for_lp_fifos_empty(sender); */ +	} + +	val = FLD_VAL(param, 23, 16) | FLD_VAL(cmd, 15, 8) | +		FLD_VAL(virtual_channel, 7, 6) | FLD_VAL(data_type, 5, 0); + +	REG_WRITE(ctrl_reg, val); + +	return 0; +} + +static int send_long_pkg(struct mdfld_dsi_pkg_sender *sender, u8 data_type, +			u8 *data, int len, bool hs) +{ +	struct drm_device *dev = sender->dev; +	u32 ctrl_reg; +	u32 data_reg; +	u32 val; +	u8 *p; +	u8 b1, b2, b3, b4; +	u8 virtual_channel = 0; +	int i; + +	if (hs) { +		ctrl_reg = sender->mipi_hs_gen_ctrl_reg; +		data_reg = sender->mipi_hs_gen_data_reg; + +		/* FIXME: wait_for_hs_fifos_empty(sender); */ +	} else { +		ctrl_reg = sender->mipi_lp_gen_ctrl_reg; +		data_reg = sender->mipi_lp_gen_data_reg; + +		/* FIXME: wait_for_lp_fifos_empty(sender); */ +	} + +	p = data; +	for (i = 0; i < len / 4; i++) { +		b1 = *p++; +		b2 = *p++; +		b3 = *p++; +		b4 = *p++; + +		REG_WRITE(data_reg, b4 << 24 | b3 << 16 | b2 << 8 | b1); +	} + +	i = len % 4; +	if (i) { +		b1 = 0; b2 = 0; b3 = 0; + +		switch (i) { +		case 3: +			b1 = *p++; +			b2 = *p++; +			b3 = *p++; +			break; +		case 2: +			b1 = *p++; +			b2 = *p++; +			break; +		case 1: +			b1 = *p++; +			break; +		} + +		REG_WRITE(data_reg, b3 << 16 | b2 << 8 | b1); +	} + +	val = FLD_VAL(len, 23, 8) | FLD_VAL(virtual_channel, 7, 6) | +		FLD_VAL(data_type, 5, 0); + +	REG_WRITE(ctrl_reg, val); + +	return 0; +} + +static int send_pkg_prepare(struct mdfld_dsi_pkg_sender *sender, u8 data_type, +			u8 *data, u16 len) +{ +	u8 cmd; + +	switch (data_type) { +	case DSI_DT_DCS_SHORT_WRITE_0: +	case DSI_DT_DCS_SHORT_WRITE_1: +	case DSI_DT_DCS_LONG_WRITE: +		cmd = *data; +		break; +	default: +		return 0; +	} + +	/*this prevents other package sending while doing msleep*/ +	sender->status = MDFLD_DSI_PKG_SENDER_BUSY; + +	/*wait for 120 milliseconds in case exit_sleep_mode just be sent*/ +	if (unlikely(cmd == DCS_ENTER_SLEEP_MODE)) { +		/*TODO: replace it with msleep later*/ +		mdelay(120); +	} + +	if (unlikely(cmd == DCS_EXIT_SLEEP_MODE)) { +		/*TODO: replace it with msleep later*/ +		mdelay(120); +	} +	return 0; +} + +static int send_pkg_done(struct mdfld_dsi_pkg_sender *sender, u8 data_type, +			u8 *data, u16 len) +{ +	u8 cmd; + +	switch (data_type) { +	case DSI_DT_DCS_SHORT_WRITE_0: +	case DSI_DT_DCS_SHORT_WRITE_1: +	case DSI_DT_DCS_LONG_WRITE: +		cmd = *data; +		break; +	default: +		return 0; +	} + +	/*update panel status*/ +	if (unlikely(cmd == DCS_ENTER_SLEEP_MODE)) { +		sender->panel_mode |= MDFLD_DSI_PANEL_MODE_SLEEP; +		/*TODO: replace it with msleep later*/ +		mdelay(120); +	} else if (unlikely(cmd == DCS_EXIT_SLEEP_MODE)) { +		sender->panel_mode &= ~MDFLD_DSI_PANEL_MODE_SLEEP; +		/*TODO: replace it with msleep later*/ +		mdelay(120); +	} else if (unlikely(cmd == DCS_SOFT_RESET)) { +		/*TODO: replace it with msleep later*/ +		mdelay(5); +	} + +	sender->status = MDFLD_DSI_PKG_SENDER_FREE; + +	return 0; +} + +static int send_pkg(struct mdfld_dsi_pkg_sender *sender, u8 data_type, +		u8 *data, u16 len, bool hs) +{ +	int ret; + +	/*handle DSI error*/ +	ret = dsi_error_handler(sender); +	if (ret) { +		DRM_ERROR("Error handling failed\n"); +		return -EAGAIN; +	} + +	/* send pkg */ +	if (sender->status == MDFLD_DSI_PKG_SENDER_BUSY) { +		DRM_ERROR("sender is busy\n"); +		return -EAGAIN; +	} + +	ret = send_pkg_prepare(sender, data_type, data, len); +	if (ret) { +		DRM_ERROR("send_pkg_prepare error\n"); +		return ret; +	} + +	switch (data_type) { +	case DSI_DT_GENERIC_SHORT_WRITE_0: +	case DSI_DT_GENERIC_SHORT_WRITE_1: +	case DSI_DT_GENERIC_SHORT_WRITE_2: +	case DSI_DT_GENERIC_READ_0: +	case DSI_DT_GENERIC_READ_1: +	case DSI_DT_GENERIC_READ_2: +	case DSI_DT_DCS_SHORT_WRITE_0: +	case DSI_DT_DCS_SHORT_WRITE_1: +	case DSI_DT_DCS_READ: +		ret = send_short_pkg(sender, data_type, data[0], data[1], hs); +		break; +	case DSI_DT_GENERIC_LONG_WRITE: +	case DSI_DT_DCS_LONG_WRITE: +		ret = send_long_pkg(sender, data_type, data, len, hs); +		break; +	} + +	send_pkg_done(sender, data_type, data, len); + +	/*FIXME: should I query complete and fifo empty here?*/ + +	return ret; +} + +int mdfld_dsi_send_mcs_long(struct mdfld_dsi_pkg_sender *sender, u8 *data, +			u32 len, bool hs) +{ +	unsigned long flags; + +	if (!sender || !data || !len) { +		DRM_ERROR("Invalid parameters\n"); +		return -EINVAL; +	} + +	spin_lock_irqsave(&sender->lock, flags); +	send_pkg(sender, DSI_DT_DCS_LONG_WRITE, data, len, hs); +	spin_unlock_irqrestore(&sender->lock, flags); + +	return 0; +} + +int mdfld_dsi_send_mcs_short(struct mdfld_dsi_pkg_sender *sender, u8 cmd, +			u8 param, u8 param_num, bool hs) +{ +	u8 data[2]; +	unsigned long flags; +	u8 data_type; + +	if (!sender) { +		DRM_ERROR("Invalid parameter\n"); +		return -EINVAL; +	} + +	data[0] = cmd; + +	if (param_num) { +		data_type = DSI_DT_DCS_SHORT_WRITE_1; +		data[1] = param; +	} else { +		data_type = DSI_DT_DCS_SHORT_WRITE_0; +		data[1] = 0; +	} + +	spin_lock_irqsave(&sender->lock, flags); +	send_pkg(sender, data_type, data, sizeof(data), hs); +	spin_unlock_irqrestore(&sender->lock, flags); + +	return 0; +} + +int mdfld_dsi_send_gen_short(struct mdfld_dsi_pkg_sender *sender, u8 param0, +			u8 param1, u8 param_num, bool hs) +{ +	u8 data[2]; +	unsigned long flags; +	u8 data_type; + +	if (!sender || param_num > 2) { +		DRM_ERROR("Invalid parameter\n"); +		return -EINVAL; +	} + +	switch (param_num) { +	case 0: +		data_type = DSI_DT_GENERIC_SHORT_WRITE_0; +		data[0] = 0; +		data[1] = 0; +		break; +	case 1: +		data_type = DSI_DT_GENERIC_SHORT_WRITE_1; +		data[0] = param0; +		data[1] = 0; +		break; +	case 2: +		data_type = DSI_DT_GENERIC_SHORT_WRITE_2; +		data[0] = param0; +		data[1] = param1; +		break; +	} + +	spin_lock_irqsave(&sender->lock, flags); +	send_pkg(sender, data_type, data, sizeof(data), hs); +	spin_unlock_irqrestore(&sender->lock, flags); + +	return 0; +} + +int mdfld_dsi_send_gen_long(struct mdfld_dsi_pkg_sender *sender, u8 *data, +			u32 len, bool hs) +{ +	unsigned long flags; + +	if (!sender || !data || !len) { +		DRM_ERROR("Invalid parameters\n"); +		return -EINVAL; +	} + +	spin_lock_irqsave(&sender->lock, flags); +	send_pkg(sender, DSI_DT_GENERIC_LONG_WRITE, data, len, hs); +	spin_unlock_irqrestore(&sender->lock, flags); + +	return 0; +} + +static int __read_panel_data(struct mdfld_dsi_pkg_sender *sender, u8 data_type, +			u8 *data, u16 len, u32 *data_out, u16 len_out, bool hs) +{ +	unsigned long flags; +	struct drm_device *dev = sender->dev; +	int i; +	u32 gen_data_reg; +	int retry = MDFLD_DSI_READ_MAX_COUNT; + +	if (!sender || !data_out || !len_out) { +		DRM_ERROR("Invalid parameters\n"); +		return -EINVAL; +	} + +	/** +	 * do reading. +	 * 0) send out generic read request +	 * 1) polling read data avail interrupt +	 * 2) read data +	 */ +	spin_lock_irqsave(&sender->lock, flags); + +	REG_WRITE(sender->mipi_intr_stat_reg, BIT(29)); + +	if ((REG_READ(sender->mipi_intr_stat_reg) & BIT(29))) +		DRM_ERROR("Can NOT clean read data valid interrupt\n"); + +	/*send out read request*/ +	send_pkg(sender, data_type, data, len, hs); + +	/*polling read data avail interrupt*/ +	while (retry && !(REG_READ(sender->mipi_intr_stat_reg) & BIT(29))) { +		udelay(100); +		retry--; +	} + +	if (!retry) { +		spin_unlock_irqrestore(&sender->lock, flags); +		return -ETIMEDOUT; +	} + +	REG_WRITE(sender->mipi_intr_stat_reg, BIT(29)); + +	/*read data*/ +	if (hs) +		gen_data_reg = sender->mipi_hs_gen_data_reg; +	else +		gen_data_reg = sender->mipi_lp_gen_data_reg; + +	for (i = 0; i < len_out; i++) +		*(data_out + i) = REG_READ(gen_data_reg); + +	spin_unlock_irqrestore(&sender->lock, flags); + +	return 0; +} + +int mdfld_dsi_read_mcs(struct mdfld_dsi_pkg_sender *sender, u8 cmd, +		u32 *data, u16 len, bool hs) +{ +	if (!sender || !data || !len) { +		DRM_ERROR("Invalid parameters\n"); +		return -EINVAL; +	} + +	return __read_panel_data(sender, DSI_DT_DCS_READ, &cmd, 1, +				data, len, hs); +} + +int mdfld_dsi_pkg_sender_init(struct mdfld_dsi_connector *dsi_connector, +								int pipe) +{ +	struct mdfld_dsi_pkg_sender *pkg_sender; +	struct mdfld_dsi_config *dsi_config = +				mdfld_dsi_get_config(dsi_connector); +	struct drm_device *dev = dsi_config->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	u32 mipi_val = 0; + +	if (!dsi_connector) { +		DRM_ERROR("Invalid parameter\n"); +		return -EINVAL; +	} + +	pkg_sender = dsi_connector->pkg_sender; + +	if (!pkg_sender || IS_ERR(pkg_sender)) { +		pkg_sender = kzalloc(sizeof(struct mdfld_dsi_pkg_sender), +								GFP_KERNEL); +		if (!pkg_sender) { +			DRM_ERROR("Create DSI pkg sender failed\n"); +			return -ENOMEM; +		} +		dsi_connector->pkg_sender = (void *)pkg_sender; +	} + +	pkg_sender->dev = dev; +	pkg_sender->dsi_connector = dsi_connector; +	pkg_sender->pipe = pipe; +	pkg_sender->pkg_num = 0; +	pkg_sender->panel_mode = 0; +	pkg_sender->status = MDFLD_DSI_PKG_SENDER_FREE; + +	/*init regs*/ +	/* FIXME: should just copy the regmap ptr ? */ +	pkg_sender->dpll_reg = map->dpll; +	pkg_sender->dspcntr_reg = map->cntr; +	pkg_sender->pipeconf_reg = map->conf; +	pkg_sender->dsplinoff_reg = map->linoff; +	pkg_sender->dspsurf_reg = map->surf; +	pkg_sender->pipestat_reg = map->status; + +	pkg_sender->mipi_intr_stat_reg = MIPI_INTR_STAT_REG(pipe); +	pkg_sender->mipi_lp_gen_data_reg = MIPI_LP_GEN_DATA_REG(pipe); +	pkg_sender->mipi_hs_gen_data_reg = MIPI_HS_GEN_DATA_REG(pipe); +	pkg_sender->mipi_lp_gen_ctrl_reg = MIPI_LP_GEN_CTRL_REG(pipe); +	pkg_sender->mipi_hs_gen_ctrl_reg = MIPI_HS_GEN_CTRL_REG(pipe); +	pkg_sender->mipi_gen_fifo_stat_reg = MIPI_GEN_FIFO_STAT_REG(pipe); +	pkg_sender->mipi_data_addr_reg = MIPI_DATA_ADD_REG(pipe); +	pkg_sender->mipi_data_len_reg = MIPI_DATA_LEN_REG(pipe); +	pkg_sender->mipi_cmd_addr_reg = MIPI_CMD_ADD_REG(pipe); +	pkg_sender->mipi_cmd_len_reg = MIPI_CMD_LEN_REG(pipe); + +	/*init lock*/ +	spin_lock_init(&pkg_sender->lock); + +	if (mdfld_get_panel_type(dev, pipe) != TC35876X) { +		/** +		 * For video mode, don't enable DPI timing output here, +		 * will init the DPI timing output during mode setting. +		 */ +		mipi_val = PASS_FROM_SPHY_TO_AFE | SEL_FLOPPED_HSTX; + +		if (pipe == 0) +			mipi_val |= 0x2; + +		REG_WRITE(MIPI_PORT_CONTROL(pipe), mipi_val); +		REG_READ(MIPI_PORT_CONTROL(pipe)); + +		/* do dsi controller init */ +		mdfld_dsi_controller_init(dsi_config, pipe); +	} + +	return 0; +} + +void mdfld_dsi_pkg_sender_destroy(struct mdfld_dsi_pkg_sender *sender) +{ +	if (!sender || IS_ERR(sender)) +		return; + +	/*free*/ +	kfree(sender); +} + + diff --git a/drivers/gpu/drm/gma500/mdfld_dsi_pkg_sender.h b/drivers/gpu/drm/gma500/mdfld_dsi_pkg_sender.h new file mode 100644 index 00000000000..459cd7ea8b8 --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_dsi_pkg_sender.h @@ -0,0 +1,92 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Jackie Li<yaodong.li@intel.com> + */ +#ifndef __MDFLD_DSI_PKG_SENDER_H__ +#define __MDFLD_DSI_PKG_SENDER_H__ + +#include <linux/kthread.h> + +#define MDFLD_MAX_DCS_PARAM	8 + +struct mdfld_dsi_pkg_sender { +	struct drm_device *dev; +	struct mdfld_dsi_connector *dsi_connector; +	u32 status; +	u32 panel_mode; + +	int pipe; + +	spinlock_t lock; + +	u32 pkg_num; + +	/* Registers */ +	u32 dpll_reg; +	u32 dspcntr_reg; +	u32 pipeconf_reg; +	u32 pipestat_reg; +	u32 dsplinoff_reg; +	u32 dspsurf_reg; + +	u32 mipi_intr_stat_reg; +	u32 mipi_lp_gen_data_reg; +	u32 mipi_hs_gen_data_reg; +	u32 mipi_lp_gen_ctrl_reg; +	u32 mipi_hs_gen_ctrl_reg; +	u32 mipi_gen_fifo_stat_reg; +	u32 mipi_data_addr_reg; +	u32 mipi_data_len_reg; +	u32 mipi_cmd_addr_reg; +	u32 mipi_cmd_len_reg; +}; + +/* DCS definitions */ +#define DCS_SOFT_RESET			0x01 +#define DCS_ENTER_SLEEP_MODE		0x10 +#define DCS_EXIT_SLEEP_MODE		0x11 +#define DCS_SET_DISPLAY_OFF		0x28 +#define DCS_SET_DISPLAY_ON		0x29 +#define DCS_SET_COLUMN_ADDRESS		0x2a +#define DCS_SET_PAGE_ADDRESS		0x2b +#define DCS_WRITE_MEM_START		0x2c +#define DCS_SET_TEAR_OFF		0x34 +#define DCS_SET_TEAR_ON			0x35 + +extern int mdfld_dsi_pkg_sender_init(struct mdfld_dsi_connector *dsi_connector, +					int pipe); +extern void mdfld_dsi_pkg_sender_destroy(struct mdfld_dsi_pkg_sender *sender); +int mdfld_dsi_send_mcs_short(struct mdfld_dsi_pkg_sender *sender, u8 cmd, +					u8 param, u8 param_num, bool hs); +int mdfld_dsi_send_mcs_long(struct mdfld_dsi_pkg_sender *sender, u8 *data, +					u32 len, bool hs); +int mdfld_dsi_send_gen_short(struct mdfld_dsi_pkg_sender *sender, u8 param0, +					u8 param1, u8 param_num, bool hs); +int mdfld_dsi_send_gen_long(struct mdfld_dsi_pkg_sender *sender, u8 *data, +					u32 len, bool hs); +/* Read interfaces */ +int mdfld_dsi_read_mcs(struct mdfld_dsi_pkg_sender *sender, u8 cmd, +		u32 *data, u16 len, bool hs); + +#endif diff --git a/drivers/gpu/drm/gma500/mdfld_intel_display.c b/drivers/gpu/drm/gma500/mdfld_intel_display.c new file mode 100644 index 00000000000..8cc8a5abbc7 --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_intel_display.c @@ -0,0 +1,1035 @@ +/* + * Copyright © 2006-2007 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + */ + +#include <linux/i2c.h> +#include <linux/pm_runtime.h> + +#include <drm/drmP.h> +#include "psb_intel_reg.h" +#include "gma_display.h" +#include "framebuffer.h" +#include "mdfld_output.h" +#include "mdfld_dsi_output.h" + +/* Hardcoded currently */ +static int ksel = KSEL_CRYSTAL_19; + +struct psb_intel_range_t { +	int min, max; +}; + +struct mrst_limit_t { +	struct psb_intel_range_t dot, m, p1; +}; + +struct mrst_clock_t { +	/* derived values */ +	int dot; +	int m; +	int p1; +}; + +#define COUNT_MAX 0x10000000 + +void mdfldWaitForPipeDisable(struct drm_device *dev, int pipe) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	int count, temp; + +	switch (pipe) { +	case 0: +	case 1: +	case 2: +		break; +	default: +		DRM_ERROR("Illegal Pipe Number.\n"); +		return; +	} + +	/* FIXME JLIU7_PO */ +	gma_wait_for_vblank(dev); +	return; + +	/* Wait for for the pipe disable to take effect. */ +	for (count = 0; count < COUNT_MAX; count++) { +		temp = REG_READ(map->conf); +		if ((temp & PIPEACONF_PIPE_STATE) == 0) +			break; +	} +} + +void mdfldWaitForPipeEnable(struct drm_device *dev, int pipe) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	int count, temp; + +	switch (pipe) { +	case 0: +	case 1: +	case 2: +		break; +	default: +		DRM_ERROR("Illegal Pipe Number.\n"); +		return; +	} + +	/* FIXME JLIU7_PO */ +	gma_wait_for_vblank(dev); +	return; + +	/* Wait for for the pipe enable to take effect. */ +	for (count = 0; count < COUNT_MAX; count++) { +		temp = REG_READ(map->conf); +		if ((temp & PIPEACONF_PIPE_STATE) == 1) +			break; +	} +} + +/** + * Return the pipe currently connected to the panel fitter, + * or -1 if the panel fitter is not present or not in use + */ +static int psb_intel_panel_fitter_pipe(struct drm_device *dev) +{ +	u32 pfit_control; + +	pfit_control = REG_READ(PFIT_CONTROL); + +	/* See if the panel fitter is in use */ +	if ((pfit_control & PFIT_ENABLE) == 0) +		return -1; + +	/* 965 can place panel fitter on either pipe */ +	return (pfit_control >> 29) & 0x3; +} + +static struct drm_device globle_dev; + +void mdfld__intel_plane_set_alpha(int enable) +{ +	struct drm_device *dev = &globle_dev; +	int dspcntr_reg = DSPACNTR; +	u32 dspcntr; + +	dspcntr = REG_READ(dspcntr_reg); + +	if (enable) { +		dspcntr &= ~DISPPLANE_32BPP_NO_ALPHA; +		dspcntr |= DISPPLANE_32BPP; +	} else { +		dspcntr &= ~DISPPLANE_32BPP; +		dspcntr |= DISPPLANE_32BPP_NO_ALPHA; +	} + +	REG_WRITE(dspcntr_reg, dspcntr); +} + +static int check_fb(struct drm_framebuffer *fb) +{ +	if (!fb) +		return 0; + +	switch (fb->bits_per_pixel) { +	case 8: +	case 16: +	case 24: +	case 32: +		return 0; +	default: +		DRM_ERROR("Unknown color depth\n"); +		return -EINVAL; +	} +} + +static int mdfld__intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, +				struct drm_framebuffer *old_fb) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct psb_framebuffer *psbfb = to_psb_fb(crtc->primary->fb); +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	unsigned long start, offset; +	u32 dspcntr; +	int ret; + +	memcpy(&globle_dev, dev, sizeof(struct drm_device)); + +	dev_dbg(dev->dev, "pipe = 0x%x.\n", pipe); + +	/* no fb bound */ +	if (!crtc->primary->fb) { +		dev_dbg(dev->dev, "No FB bound\n"); +		return 0; +	} + +	ret = check_fb(crtc->primary->fb); +	if (ret) +		return ret; + +	if (pipe > 2) { +		DRM_ERROR("Illegal Pipe Number.\n"); +		return -EINVAL; +	} + +	if (!gma_power_begin(dev, true)) +		return 0; + +	start = psbfb->gtt->offset; +	offset = y * crtc->primary->fb->pitches[0] + x * (crtc->primary->fb->bits_per_pixel / 8); + +	REG_WRITE(map->stride, crtc->primary->fb->pitches[0]); +	dspcntr = REG_READ(map->cntr); +	dspcntr &= ~DISPPLANE_PIXFORMAT_MASK; + +	switch (crtc->primary->fb->bits_per_pixel) { +	case 8: +		dspcntr |= DISPPLANE_8BPP; +		break; +	case 16: +		if (crtc->primary->fb->depth == 15) +			dspcntr |= DISPPLANE_15_16BPP; +		else +			dspcntr |= DISPPLANE_16BPP; +		break; +	case 24: +	case 32: +		dspcntr |= DISPPLANE_32BPP_NO_ALPHA; +		break; +	} +	REG_WRITE(map->cntr, dspcntr); + +	dev_dbg(dev->dev, "Writing base %08lX %08lX %d %d\n", +						start, offset, x, y); +	REG_WRITE(map->linoff, offset); +	REG_READ(map->linoff); +	REG_WRITE(map->surf, start); +	REG_READ(map->surf); + +	gma_power_end(dev); + +	return 0; +} + +/* + * Disable the pipe, plane and pll. + * + */ +void mdfld_disable_crtc(struct drm_device *dev, int pipe) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	u32 temp; + +	dev_dbg(dev->dev, "pipe = %d\n", pipe); + + +	if (pipe != 1) +		mdfld_dsi_gen_fifo_ready(dev, MIPI_GEN_FIFO_STAT_REG(pipe), +				HS_CTRL_FIFO_EMPTY | HS_DATA_FIFO_EMPTY); + +	/* Disable display plane */ +	temp = REG_READ(map->cntr); +	if ((temp & DISPLAY_PLANE_ENABLE) != 0) { +		REG_WRITE(map->cntr, +			  temp & ~DISPLAY_PLANE_ENABLE); +		/* Flush the plane changes */ +		REG_WRITE(map->base, REG_READ(map->base)); +		REG_READ(map->base); +	} + +	/* FIXME_JLIU7 MDFLD_PO revisit */ + +	/* Next, disable display pipes */ +	temp = REG_READ(map->conf); +	if ((temp & PIPEACONF_ENABLE) != 0) { +		temp &= ~PIPEACONF_ENABLE; +		temp |= PIPECONF_PLANE_OFF | PIPECONF_CURSOR_OFF; +		REG_WRITE(map->conf, temp); +		REG_READ(map->conf); + +		/* Wait for for the pipe disable to take effect. */ +		mdfldWaitForPipeDisable(dev, pipe); +	} + +	temp = REG_READ(map->dpll); +	if (temp & DPLL_VCO_ENABLE) { +		if ((pipe != 1 && +			!((REG_READ(PIPEACONF) | REG_READ(PIPECCONF)) +				& PIPEACONF_ENABLE)) || pipe == 1) { +			temp &= ~(DPLL_VCO_ENABLE); +			REG_WRITE(map->dpll, temp); +			REG_READ(map->dpll); +			/* Wait for the clocks to turn off. */ +			/* FIXME_MDFLD PO may need more delay */ +			udelay(500); + +			if (!(temp & MDFLD_PWR_GATE_EN)) { +				/* gating power of DPLL */ +				REG_WRITE(map->dpll, temp | MDFLD_PWR_GATE_EN); +				/* FIXME_MDFLD PO - change 500 to 1 after PO */ +				udelay(5000); +			} +		} +	} + +} + +/** + * Sets the power management mode of the pipe and plane. + * + * This code should probably grow support for turning the cursor off and back + * on appropriately at the same time as we're turning the pipe off/on. + */ +static void mdfld_crtc_dpms(struct drm_crtc *crtc, int mode) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	u32 pipeconf = dev_priv->pipeconf[pipe]; +	u32 temp; +	int timeout = 0; + +	dev_dbg(dev->dev, "mode = %d, pipe = %d\n", mode, pipe); + +	/* Note: Old code uses pipe a stat for pipe b but that appears +	   to be a bug */ + +	if (!gma_power_begin(dev, true)) +		return; + +	/* XXX: When our outputs are all unaware of DPMS modes other than off +	 * and on, we should map those modes to DRM_MODE_DPMS_OFF in the CRTC. +	 */ +	switch (mode) { +	case DRM_MODE_DPMS_ON: +	case DRM_MODE_DPMS_STANDBY: +	case DRM_MODE_DPMS_SUSPEND: +		/* Enable the DPLL */ +		temp = REG_READ(map->dpll); + +		if ((temp & DPLL_VCO_ENABLE) == 0) { +			/* When ungating power of DPLL, needs to wait 0.5us +			   before enable the VCO */ +			if (temp & MDFLD_PWR_GATE_EN) { +				temp &= ~MDFLD_PWR_GATE_EN; +				REG_WRITE(map->dpll, temp); +				/* FIXME_MDFLD PO - change 500 to 1 after PO */ +				udelay(500); +			} + +			REG_WRITE(map->dpll, temp); +			REG_READ(map->dpll); +			/* FIXME_MDFLD PO - change 500 to 1 after PO */ +			udelay(500); + +			REG_WRITE(map->dpll, temp | DPLL_VCO_ENABLE); +			REG_READ(map->dpll); + +			/** +			 * wait for DSI PLL to lock +			 * NOTE: only need to poll status of pipe 0 and pipe 1, +			 * since both MIPI pipes share the same PLL. +			 */ +			while ((pipe != 2) && (timeout < 20000) && +			  !(REG_READ(map->conf) & PIPECONF_DSIPLL_LOCK)) { +				udelay(150); +				timeout++; +			} +		} + +		/* Enable the plane */ +		temp = REG_READ(map->cntr); +		if ((temp & DISPLAY_PLANE_ENABLE) == 0) { +			REG_WRITE(map->cntr, +				temp | DISPLAY_PLANE_ENABLE); +			/* Flush the plane changes */ +			REG_WRITE(map->base, REG_READ(map->base)); +		} + +		/* Enable the pipe */ +		temp = REG_READ(map->conf); +		if ((temp & PIPEACONF_ENABLE) == 0) { +			REG_WRITE(map->conf, pipeconf); + +			/* Wait for for the pipe enable to take effect. */ +			mdfldWaitForPipeEnable(dev, pipe); +		} + +		/*workaround for sighting 3741701 Random X blank display*/ +		/*perform w/a in video mode only on pipe A or C*/ +		if (pipe == 0 || pipe == 2) { +			REG_WRITE(map->status, REG_READ(map->status)); +			msleep(100); +			if (PIPE_VBLANK_STATUS & REG_READ(map->status)) +				dev_dbg(dev->dev, "OK"); +			else { +				dev_dbg(dev->dev, "STUCK!!!!"); +				/*shutdown controller*/ +				temp = REG_READ(map->cntr); +				REG_WRITE(map->cntr, +						temp & ~DISPLAY_PLANE_ENABLE); +				REG_WRITE(map->base, REG_READ(map->base)); +				/*mdfld_dsi_dpi_shut_down(dev, pipe);*/ +				REG_WRITE(0xb048, 1); +				msleep(100); +				temp = REG_READ(map->conf); +				temp &= ~PIPEACONF_ENABLE; +				REG_WRITE(map->conf, temp); +				msleep(100); /*wait for pipe disable*/ +				REG_WRITE(MIPI_DEVICE_READY_REG(pipe), 0); +				msleep(100); +				REG_WRITE(0xb004, REG_READ(0xb004)); +				/* try to bring the controller back up again*/ +				REG_WRITE(MIPI_DEVICE_READY_REG(pipe), 1); +				temp = REG_READ(map->cntr); +				REG_WRITE(map->cntr, +						temp | DISPLAY_PLANE_ENABLE); +				REG_WRITE(map->base, REG_READ(map->base)); +				/*mdfld_dsi_dpi_turn_on(dev, pipe);*/ +				REG_WRITE(0xb048, 2); +				msleep(100); +				temp = REG_READ(map->conf); +				temp |= PIPEACONF_ENABLE; +				REG_WRITE(map->conf, temp); +			} +		} + +		gma_crtc_load_lut(crtc); + +		/* Give the overlay scaler a chance to enable +		   if it's on this pipe */ +		/* psb_intel_crtc_dpms_video(crtc, true); TODO */ + +		break; +	case DRM_MODE_DPMS_OFF: +		/* Give the overlay scaler a chance to disable +		 * if it's on this pipe */ +		/* psb_intel_crtc_dpms_video(crtc, FALSE); TODO */ +		if (pipe != 1) +			mdfld_dsi_gen_fifo_ready(dev, +				MIPI_GEN_FIFO_STAT_REG(pipe), +				HS_CTRL_FIFO_EMPTY | HS_DATA_FIFO_EMPTY); + +		/* Disable the VGA plane that we never use */ +		REG_WRITE(VGACNTRL, VGA_DISP_DISABLE); + +		/* Disable display plane */ +		temp = REG_READ(map->cntr); +		if ((temp & DISPLAY_PLANE_ENABLE) != 0) { +			REG_WRITE(map->cntr, +				  temp & ~DISPLAY_PLANE_ENABLE); +			/* Flush the plane changes */ +			REG_WRITE(map->base, REG_READ(map->base)); +			REG_READ(map->base); +		} + +		/* Next, disable display pipes */ +		temp = REG_READ(map->conf); +		if ((temp & PIPEACONF_ENABLE) != 0) { +			temp &= ~PIPEACONF_ENABLE; +			temp |= PIPECONF_PLANE_OFF | PIPECONF_CURSOR_OFF; +			REG_WRITE(map->conf, temp); +			REG_READ(map->conf); + +			/* Wait for for the pipe disable to take effect. */ +			mdfldWaitForPipeDisable(dev, pipe); +		} + +		temp = REG_READ(map->dpll); +		if (temp & DPLL_VCO_ENABLE) { +			if ((pipe != 1 && !((REG_READ(PIPEACONF) +				| REG_READ(PIPECCONF)) & PIPEACONF_ENABLE)) +					|| pipe == 1) { +				temp &= ~(DPLL_VCO_ENABLE); +				REG_WRITE(map->dpll, temp); +				REG_READ(map->dpll); +				/* Wait for the clocks to turn off. */ +				/* FIXME_MDFLD PO may need more delay */ +				udelay(500); +			} +		} +		break; +	} +	gma_power_end(dev); +} + + +#define MDFLD_LIMT_DPLL_19	    0 +#define MDFLD_LIMT_DPLL_25	    1 +#define MDFLD_LIMT_DPLL_83	    2 +#define MDFLD_LIMT_DPLL_100	    3 +#define MDFLD_LIMT_DSIPLL_19	    4 +#define MDFLD_LIMT_DSIPLL_25	    5 +#define MDFLD_LIMT_DSIPLL_83	    6 +#define MDFLD_LIMT_DSIPLL_100	    7 + +#define MDFLD_DOT_MIN		  19750 +#define MDFLD_DOT_MAX		  120000 +#define MDFLD_DPLL_M_MIN_19	    113 +#define MDFLD_DPLL_M_MAX_19	    155 +#define MDFLD_DPLL_P1_MIN_19	    2 +#define MDFLD_DPLL_P1_MAX_19	    10 +#define MDFLD_DPLL_M_MIN_25	    101 +#define MDFLD_DPLL_M_MAX_25	    130 +#define MDFLD_DPLL_P1_MIN_25	    2 +#define MDFLD_DPLL_P1_MAX_25	    10 +#define MDFLD_DPLL_M_MIN_83	    64 +#define MDFLD_DPLL_M_MAX_83	    64 +#define MDFLD_DPLL_P1_MIN_83	    2 +#define MDFLD_DPLL_P1_MAX_83	    2 +#define MDFLD_DPLL_M_MIN_100	    64 +#define MDFLD_DPLL_M_MAX_100	    64 +#define MDFLD_DPLL_P1_MIN_100	    2 +#define MDFLD_DPLL_P1_MAX_100	    2 +#define MDFLD_DSIPLL_M_MIN_19	    131 +#define MDFLD_DSIPLL_M_MAX_19	    175 +#define MDFLD_DSIPLL_P1_MIN_19	    3 +#define MDFLD_DSIPLL_P1_MAX_19	    8 +#define MDFLD_DSIPLL_M_MIN_25	    97 +#define MDFLD_DSIPLL_M_MAX_25	    140 +#define MDFLD_DSIPLL_P1_MIN_25	    3 +#define MDFLD_DSIPLL_P1_MAX_25	    9 +#define MDFLD_DSIPLL_M_MIN_83	    33 +#define MDFLD_DSIPLL_M_MAX_83	    92 +#define MDFLD_DSIPLL_P1_MIN_83	    2 +#define MDFLD_DSIPLL_P1_MAX_83	    3 +#define MDFLD_DSIPLL_M_MIN_100	    97 +#define MDFLD_DSIPLL_M_MAX_100	    140 +#define MDFLD_DSIPLL_P1_MIN_100	    3 +#define MDFLD_DSIPLL_P1_MAX_100	    9 + +static const struct mrst_limit_t mdfld_limits[] = { +	{			/* MDFLD_LIMT_DPLL_19 */ +	 .dot = {.min = MDFLD_DOT_MIN, .max = MDFLD_DOT_MAX}, +	 .m = {.min = MDFLD_DPLL_M_MIN_19, .max = MDFLD_DPLL_M_MAX_19}, +	 .p1 = {.min = MDFLD_DPLL_P1_MIN_19, .max = MDFLD_DPLL_P1_MAX_19}, +	 }, +	{			/* MDFLD_LIMT_DPLL_25 */ +	 .dot = {.min = MDFLD_DOT_MIN, .max = MDFLD_DOT_MAX}, +	 .m = {.min = MDFLD_DPLL_M_MIN_25, .max = MDFLD_DPLL_M_MAX_25}, +	 .p1 = {.min = MDFLD_DPLL_P1_MIN_25, .max = MDFLD_DPLL_P1_MAX_25}, +	 }, +	{			/* MDFLD_LIMT_DPLL_83 */ +	 .dot = {.min = MDFLD_DOT_MIN, .max = MDFLD_DOT_MAX}, +	 .m = {.min = MDFLD_DPLL_M_MIN_83, .max = MDFLD_DPLL_M_MAX_83}, +	 .p1 = {.min = MDFLD_DPLL_P1_MIN_83, .max = MDFLD_DPLL_P1_MAX_83}, +	 }, +	{			/* MDFLD_LIMT_DPLL_100 */ +	 .dot = {.min = MDFLD_DOT_MIN, .max = MDFLD_DOT_MAX}, +	 .m = {.min = MDFLD_DPLL_M_MIN_100, .max = MDFLD_DPLL_M_MAX_100}, +	 .p1 = {.min = MDFLD_DPLL_P1_MIN_100, .max = MDFLD_DPLL_P1_MAX_100}, +	 }, +	{			/* MDFLD_LIMT_DSIPLL_19 */ +	 .dot = {.min = MDFLD_DOT_MIN, .max = MDFLD_DOT_MAX}, +	 .m = {.min = MDFLD_DSIPLL_M_MIN_19, .max = MDFLD_DSIPLL_M_MAX_19}, +	 .p1 = {.min = MDFLD_DSIPLL_P1_MIN_19, .max = MDFLD_DSIPLL_P1_MAX_19}, +	 }, +	{			/* MDFLD_LIMT_DSIPLL_25 */ +	 .dot = {.min = MDFLD_DOT_MIN, .max = MDFLD_DOT_MAX}, +	 .m = {.min = MDFLD_DSIPLL_M_MIN_25, .max = MDFLD_DSIPLL_M_MAX_25}, +	 .p1 = {.min = MDFLD_DSIPLL_P1_MIN_25, .max = MDFLD_DSIPLL_P1_MAX_25}, +	 }, +	{			/* MDFLD_LIMT_DSIPLL_83 */ +	 .dot = {.min = MDFLD_DOT_MIN, .max = MDFLD_DOT_MAX}, +	 .m = {.min = MDFLD_DSIPLL_M_MIN_83, .max = MDFLD_DSIPLL_M_MAX_83}, +	 .p1 = {.min = MDFLD_DSIPLL_P1_MIN_83, .max = MDFLD_DSIPLL_P1_MAX_83}, +	 }, +	{			/* MDFLD_LIMT_DSIPLL_100 */ +	 .dot = {.min = MDFLD_DOT_MIN, .max = MDFLD_DOT_MAX}, +	 .m = {.min = MDFLD_DSIPLL_M_MIN_100, .max = MDFLD_DSIPLL_M_MAX_100}, +	 .p1 = {.min = MDFLD_DSIPLL_P1_MIN_100, .max = MDFLD_DSIPLL_P1_MAX_100}, +	 }, +}; + +#define MDFLD_M_MIN	    21 +#define MDFLD_M_MAX	    180 +static const u32 mdfld_m_converts[] = { +/* M configuration table from 9-bit LFSR table */ +	224, 368, 440, 220, 366, 439, 219, 365, 182, 347, /* 21 - 30 */ +	173, 342, 171, 85, 298, 149, 74, 37, 18, 265,   /* 31 - 40 */ +	388, 194, 353, 432, 216, 108, 310, 155, 333, 166, /* 41 - 50 */ +	83, 41, 276, 138, 325, 162, 337, 168, 340, 170, /* 51 - 60 */ +	341, 426, 469, 234, 373, 442, 221, 110, 311, 411, /* 61 - 70 */ +	461, 486, 243, 377, 188, 350, 175, 343, 427, 213, /* 71 - 80 */ +	106, 53, 282, 397, 354, 227, 113, 56, 284, 142, /* 81 - 90 */ +	71, 35, 273, 136, 324, 418, 465, 488, 500, 506, /* 91 - 100 */ +	253, 126, 63, 287, 399, 455, 483, 241, 376, 444, /* 101 - 110 */ +	478, 495, 503, 251, 381, 446, 479, 239, 375, 443, /* 111 - 120 */ +	477, 238, 119, 315, 157, 78, 295, 147, 329, 420, /* 121 - 130 */ +	210, 105, 308, 154, 77, 38, 275, 137, 68, 290, /* 131 - 140 */ +	145, 328, 164, 82, 297, 404, 458, 485, 498, 249, /* 141 - 150 */ +	380, 190, 351, 431, 471, 235, 117, 314, 413, 206, /* 151 - 160 */ +	103, 51, 25, 12, 262, 387, 193, 96, 48, 280, /* 161 - 170 */ +	396, 198, 99, 305, 152, 76, 294, 403, 457, 228, /* 171 - 180 */ +}; + +static const struct mrst_limit_t *mdfld_limit(struct drm_crtc *crtc) +{ +	const struct mrst_limit_t *limit = NULL; +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (gma_pipe_has_type(crtc, INTEL_OUTPUT_MIPI) +	    || gma_pipe_has_type(crtc, INTEL_OUTPUT_MIPI2)) { +		if ((ksel == KSEL_CRYSTAL_19) || (ksel == KSEL_BYPASS_19)) +			limit = &mdfld_limits[MDFLD_LIMT_DSIPLL_19]; +		else if (ksel == KSEL_BYPASS_25) +			limit = &mdfld_limits[MDFLD_LIMT_DSIPLL_25]; +		else if ((ksel == KSEL_BYPASS_83_100) && +				(dev_priv->core_freq == 166)) +			limit = &mdfld_limits[MDFLD_LIMT_DSIPLL_83]; +		else if ((ksel == KSEL_BYPASS_83_100) && +			 (dev_priv->core_freq == 100 || +				dev_priv->core_freq == 200)) +			limit = &mdfld_limits[MDFLD_LIMT_DSIPLL_100]; +	} else if (gma_pipe_has_type(crtc, INTEL_OUTPUT_HDMI)) { +		if ((ksel == KSEL_CRYSTAL_19) || (ksel == KSEL_BYPASS_19)) +			limit = &mdfld_limits[MDFLD_LIMT_DPLL_19]; +		else if (ksel == KSEL_BYPASS_25) +			limit = &mdfld_limits[MDFLD_LIMT_DPLL_25]; +		else if ((ksel == KSEL_BYPASS_83_100) && +				(dev_priv->core_freq == 166)) +			limit = &mdfld_limits[MDFLD_LIMT_DPLL_83]; +		else if ((ksel == KSEL_BYPASS_83_100) && +				 (dev_priv->core_freq == 100 || +				 dev_priv->core_freq == 200)) +			limit = &mdfld_limits[MDFLD_LIMT_DPLL_100]; +	} else { +		limit = NULL; +		dev_dbg(dev->dev, "mdfld_limit Wrong display type.\n"); +	} + +	return limit; +} + +/** Derive the pixel clock for the given refclk and divisors for 8xx chips. */ +static void mdfld_clock(int refclk, struct mrst_clock_t *clock) +{ +	clock->dot = (refclk * clock->m) / clock->p1; +} + +/** + * Returns a set of divisors for the desired target clock with the given refclk, + * or FALSE.  Divisor values are the actual divisors for + */ +static bool +mdfldFindBestPLL(struct drm_crtc *crtc, int target, int refclk, +		struct mrst_clock_t *best_clock) +{ +	struct mrst_clock_t clock; +	const struct mrst_limit_t *limit = mdfld_limit(crtc); +	int err = target; + +	memset(best_clock, 0, sizeof(*best_clock)); + +	for (clock.m = limit->m.min; clock.m <= limit->m.max; clock.m++) { +		for (clock.p1 = limit->p1.min; clock.p1 <= limit->p1.max; +		     clock.p1++) { +			int this_err; + +			mdfld_clock(refclk, &clock); + +			this_err = abs(clock.dot - target); +			if (this_err < err) { +				*best_clock = clock; +				err = this_err; +			} +		} +	} +	return err != target; +} + +static int mdfld_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 drm_device *dev = crtc->dev; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct drm_psb_private *dev_priv = dev->dev_private; +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	int refclk = 0; +	int clk_n = 0, clk_p2 = 0, clk_byte = 1, clk = 0, m_conv = 0, +								clk_tmp = 0; +	struct mrst_clock_t clock; +	bool ok; +	u32 dpll = 0, fp = 0; +	bool is_mipi = false, is_mipi2 = false, is_hdmi = false; +	struct drm_mode_config *mode_config = &dev->mode_config; +	struct gma_encoder *gma_encoder = NULL; +	uint64_t scalingType = DRM_MODE_SCALE_FULLSCREEN; +	struct drm_encoder *encoder; +	struct drm_connector *connector; +	int timeout = 0; +	int ret; + +	dev_dbg(dev->dev, "pipe = 0x%x\n", pipe); + +#if 0 +	if (pipe == 1) { +		if (!gma_power_begin(dev, true)) +			return 0; +		android_hdmi_crtc_mode_set(crtc, mode, adjusted_mode, +			x, y, old_fb); +		goto mrst_crtc_mode_set_exit; +	} +#endif + +	ret = check_fb(crtc->primary->fb); +	if (ret) +		return ret; + +	dev_dbg(dev->dev, "adjusted_hdisplay = %d\n", +		 adjusted_mode->hdisplay); +	dev_dbg(dev->dev, "adjusted_vdisplay = %d\n", +		 adjusted_mode->vdisplay); +	dev_dbg(dev->dev, "adjusted_hsync_start = %d\n", +		 adjusted_mode->hsync_start); +	dev_dbg(dev->dev, "adjusted_hsync_end = %d\n", +		 adjusted_mode->hsync_end); +	dev_dbg(dev->dev, "adjusted_htotal = %d\n", +		 adjusted_mode->htotal); +	dev_dbg(dev->dev, "adjusted_vsync_start = %d\n", +		 adjusted_mode->vsync_start); +	dev_dbg(dev->dev, "adjusted_vsync_end = %d\n", +		 adjusted_mode->vsync_end); +	dev_dbg(dev->dev, "adjusted_vtotal = %d\n", +		 adjusted_mode->vtotal); +	dev_dbg(dev->dev, "adjusted_clock = %d\n", +		 adjusted_mode->clock); +	dev_dbg(dev->dev, "hdisplay = %d\n", +		 mode->hdisplay); +	dev_dbg(dev->dev, "vdisplay = %d\n", +		 mode->vdisplay); + +	if (!gma_power_begin(dev, true)) +		return 0; + +	memcpy(&gma_crtc->saved_mode, mode, +					sizeof(struct drm_display_mode)); +	memcpy(&gma_crtc->saved_adjusted_mode, adjusted_mode, +					sizeof(struct drm_display_mode)); + +	list_for_each_entry(connector, &mode_config->connector_list, head) { +		if (!connector) +			continue; + +		encoder = connector->encoder; + +		if (!encoder) +			continue; + +		if (encoder->crtc != crtc) +			continue; + +		gma_encoder = gma_attached_encoder(connector); + +		switch (gma_encoder->type) { +		case INTEL_OUTPUT_MIPI: +			is_mipi = true; +			break; +		case INTEL_OUTPUT_MIPI2: +			is_mipi2 = true; +			break; +		case INTEL_OUTPUT_HDMI: +			is_hdmi = true; +			break; +		} +	} + +	/* Disable the VGA plane that we never use */ +	REG_WRITE(VGACNTRL, VGA_DISP_DISABLE); + +	/* Disable the panel fitter if it was on our pipe */ +	if (psb_intel_panel_fitter_pipe(dev) == pipe) +		REG_WRITE(PFIT_CONTROL, 0); + +	/* pipesrc and dspsize control the size that is scaled from, +	 * which should always be the user's requested size. +	 */ +	if (pipe == 1) { +		/* FIXME: To make HDMI display with 864x480 (TPO), 480x864 +		 * (PYR) or 480x854 (TMD), set the sprite width/height and +		 * souce image size registers with the adjusted mode for +		 * pipe B. +		 */ + +		/* +		 * The defined sprite rectangle must always be completely +		 * contained within the displayable area of the screen image +		 * (frame buffer). +		 */ +		REG_WRITE(map->size, ((min(mode->crtc_vdisplay, adjusted_mode->crtc_vdisplay) - 1) << 16) +				| (min(mode->crtc_hdisplay, adjusted_mode->crtc_hdisplay) - 1)); +		/* Set the CRTC with encoder mode. */ +		REG_WRITE(map->src, ((mode->crtc_hdisplay - 1) << 16) +				 | (mode->crtc_vdisplay - 1)); +	} else { +		REG_WRITE(map->size, +				((mode->crtc_vdisplay - 1) << 16) | +						(mode->crtc_hdisplay - 1)); +		REG_WRITE(map->src, +				((mode->crtc_hdisplay - 1) << 16) | +						(mode->crtc_vdisplay - 1)); +	} + +	REG_WRITE(map->pos, 0); + +	if (gma_encoder) +		drm_object_property_get_value(&connector->base, +			dev->mode_config.scaling_mode_property, &scalingType); + +	if (scalingType == DRM_MODE_SCALE_NO_SCALE) { +		/* Medfield doesn't have register support for centering so we +		 * need to mess with the h/vblank and h/vsync start and ends +		 * to get centering +		 */ +		int offsetX = 0, offsetY = 0; + +		offsetX = (adjusted_mode->crtc_hdisplay - +					mode->crtc_hdisplay) / 2; +		offsetY = (adjusted_mode->crtc_vdisplay - +					mode->crtc_vdisplay) / 2; + +		REG_WRITE(map->htotal, (mode->crtc_hdisplay - 1) | +			((adjusted_mode->crtc_htotal - 1) << 16)); +		REG_WRITE(map->vtotal, (mode->crtc_vdisplay - 1) | +			((adjusted_mode->crtc_vtotal - 1) << 16)); +		REG_WRITE(map->hblank, (adjusted_mode->crtc_hblank_start - +								offsetX - 1) | +			((adjusted_mode->crtc_hblank_end - offsetX - 1) << 16)); +		REG_WRITE(map->hsync, (adjusted_mode->crtc_hsync_start - +								offsetX - 1) | +			((adjusted_mode->crtc_hsync_end - offsetX - 1) << 16)); +		REG_WRITE(map->vblank, (adjusted_mode->crtc_vblank_start - +								offsetY - 1) | +			((adjusted_mode->crtc_vblank_end - offsetY - 1) << 16)); +		REG_WRITE(map->vsync, (adjusted_mode->crtc_vsync_start - +								offsetY - 1) | +			((adjusted_mode->crtc_vsync_end - offsetY - 1) << 16)); +	} else { +		REG_WRITE(map->htotal, (adjusted_mode->crtc_hdisplay - 1) | +			((adjusted_mode->crtc_htotal - 1) << 16)); +		REG_WRITE(map->vtotal, (adjusted_mode->crtc_vdisplay - 1) | +			((adjusted_mode->crtc_vtotal - 1) << 16)); +		REG_WRITE(map->hblank, (adjusted_mode->crtc_hblank_start - 1) | +			((adjusted_mode->crtc_hblank_end - 1) << 16)); +		REG_WRITE(map->hsync, (adjusted_mode->crtc_hsync_start - 1) | +			((adjusted_mode->crtc_hsync_end - 1) << 16)); +		REG_WRITE(map->vblank, (adjusted_mode->crtc_vblank_start - 1) | +			((adjusted_mode->crtc_vblank_end - 1) << 16)); +		REG_WRITE(map->vsync, (adjusted_mode->crtc_vsync_start - 1) | +			((adjusted_mode->crtc_vsync_end - 1) << 16)); +	} + +	/* Flush the plane changes */ +	{ +		struct drm_crtc_helper_funcs *crtc_funcs = +		    crtc->helper_private; +		crtc_funcs->mode_set_base(crtc, x, y, old_fb); +	} + +	/* setup pipeconf */ +	dev_priv->pipeconf[pipe] = PIPEACONF_ENABLE; /* FIXME_JLIU7 REG_READ(pipeconf_reg); */ + +	/* Set up the display plane register */ +	dev_priv->dspcntr[pipe] = REG_READ(map->cntr); +	dev_priv->dspcntr[pipe] |= pipe << DISPPLANE_SEL_PIPE_POS; +	dev_priv->dspcntr[pipe] |= DISPLAY_PLANE_ENABLE; + +	if (is_mipi2) +		goto mrst_crtc_mode_set_exit; +	clk = adjusted_mode->clock; + +	if (is_hdmi) { +		if ((ksel == KSEL_CRYSTAL_19) || (ksel == KSEL_BYPASS_19)) { +			refclk = 19200; + +			if (is_mipi || is_mipi2) +				clk_n = 1, clk_p2 = 8; +			else if (is_hdmi) +				clk_n = 1, clk_p2 = 10; +		} else if (ksel == KSEL_BYPASS_25) { +			refclk = 25000; + +			if (is_mipi || is_mipi2) +				clk_n = 1, clk_p2 = 8; +			else if (is_hdmi) +				clk_n = 1, clk_p2 = 10; +		} else if ((ksel == KSEL_BYPASS_83_100) && +					dev_priv->core_freq == 166) { +			refclk = 83000; + +			if (is_mipi || is_mipi2) +				clk_n = 4, clk_p2 = 8; +			else if (is_hdmi) +				clk_n = 4, clk_p2 = 10; +		} else if ((ksel == KSEL_BYPASS_83_100) && +					(dev_priv->core_freq == 100 || +					dev_priv->core_freq == 200)) { +			refclk = 100000; +			if (is_mipi || is_mipi2) +				clk_n = 4, clk_p2 = 8; +			else if (is_hdmi) +				clk_n = 4, clk_p2 = 10; +		} + +		if (is_mipi) +			clk_byte = dev_priv->bpp / 8; +		else if (is_mipi2) +			clk_byte = dev_priv->bpp2 / 8; + +		clk_tmp = clk * clk_n * clk_p2 * clk_byte; + +		dev_dbg(dev->dev, "clk = %d, clk_n = %d, clk_p2 = %d.\n", +					clk, clk_n, clk_p2); +		dev_dbg(dev->dev, "adjusted_mode->clock = %d, clk_tmp = %d.\n", +					adjusted_mode->clock, clk_tmp); + +		ok = mdfldFindBestPLL(crtc, clk_tmp, refclk, &clock); + +		if (!ok) { +			DRM_ERROR +			    ("mdfldFindBestPLL fail in mdfld_crtc_mode_set.\n"); +		} else { +			m_conv = mdfld_m_converts[(clock.m - MDFLD_M_MIN)]; + +			dev_dbg(dev->dev, "dot clock = %d," +				 "m = %d, p1 = %d, m_conv = %d.\n", +					clock.dot, clock.m, +					clock.p1, m_conv); +		} + +		dpll = REG_READ(map->dpll); + +		if (dpll & DPLL_VCO_ENABLE) { +			dpll &= ~DPLL_VCO_ENABLE; +			REG_WRITE(map->dpll, dpll); +			REG_READ(map->dpll); + +			/* FIXME jliu7 check the DPLL lock bit PIPEACONF[29] */ +			/* FIXME_MDFLD PO - change 500 to 1 after PO */ +			udelay(500); + +			/* reset M1, N1 & P1 */ +			REG_WRITE(map->fp0, 0); +			dpll &= ~MDFLD_P1_MASK; +			REG_WRITE(map->dpll, dpll); +			/* FIXME_MDFLD PO - change 500 to 1 after PO */ +			udelay(500); +		} + +		/* When ungating power of DPLL, needs to wait 0.5us before +		 * enable the VCO */ +		if (dpll & MDFLD_PWR_GATE_EN) { +			dpll &= ~MDFLD_PWR_GATE_EN; +			REG_WRITE(map->dpll, dpll); +			/* FIXME_MDFLD PO - change 500 to 1 after PO */ +			udelay(500); +		} +		dpll = 0; + +#if 0 /* FIXME revisit later */ +		if (ksel == KSEL_CRYSTAL_19 || ksel == KSEL_BYPASS_19 || +						ksel == KSEL_BYPASS_25) +			dpll &= ~MDFLD_INPUT_REF_SEL; +		else if (ksel == KSEL_BYPASS_83_100) +			dpll |= MDFLD_INPUT_REF_SEL; +#endif /* FIXME revisit later */ + +		if (is_hdmi) +			dpll |= MDFLD_VCO_SEL; + +		fp = (clk_n / 2) << 16; +		fp |= m_conv; + +		/* compute bitmask from p1 value */ +		dpll |= (1 << (clock.p1 - 2)) << 17; + +#if 0 /* 1080p30 & 720p */ +		dpll = 0x00050000; +		fp = 0x000001be; +#endif +#if 0 /* 480p */ +		dpll = 0x02010000; +		fp = 0x000000d2; +#endif +	} else { +#if 0 /*DBI_TPO_480x864*/ +		dpll = 0x00020000; +		fp = 0x00000156; +#endif /* DBI_TPO_480x864 */ /* get from spec. */ + +		dpll = 0x00800000; +		fp = 0x000000c1; +	} + +	REG_WRITE(map->fp0, fp); +	REG_WRITE(map->dpll, dpll); +	/* FIXME_MDFLD PO - change 500 to 1 after PO */ +	udelay(500); + +	dpll |= DPLL_VCO_ENABLE; +	REG_WRITE(map->dpll, dpll); +	REG_READ(map->dpll); + +	/* wait for DSI PLL to lock */ +	while (timeout < 20000 && +			!(REG_READ(map->conf) & PIPECONF_DSIPLL_LOCK)) { +		udelay(150); +		timeout++; +	} + +	if (is_mipi) +		goto mrst_crtc_mode_set_exit; + +	dev_dbg(dev->dev, "is_mipi = 0x%x\n", is_mipi); + +	REG_WRITE(map->conf, dev_priv->pipeconf[pipe]); +	REG_READ(map->conf); + +	/* Wait for for the pipe enable to take effect. */ +	REG_WRITE(map->cntr, dev_priv->dspcntr[pipe]); +	gma_wait_for_vblank(dev); + +mrst_crtc_mode_set_exit: + +	gma_power_end(dev); + +	return 0; +} + +const struct drm_crtc_helper_funcs mdfld_helper_funcs = { +	.dpms = mdfld_crtc_dpms, +	.mode_fixup = gma_crtc_mode_fixup, +	.mode_set = mdfld_crtc_mode_set, +	.mode_set_base = mdfld__intel_pipe_set_base, +	.prepare = gma_crtc_prepare, +	.commit = gma_crtc_commit, +}; + diff --git a/drivers/gpu/drm/gma500/mdfld_output.c b/drivers/gpu/drm/gma500/mdfld_output.c new file mode 100644 index 00000000000..c95966bb0c9 --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_output.c @@ -0,0 +1,74 @@ +/* + * Copyright (c)  2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicensen + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Thomas Eaton <thomas.g.eaton@intel.com> + * Scott Rowe <scott.m.rowe@intel.com> +*/ + +#include "mdfld_output.h" +#include "mdfld_dsi_dpi.h" +#include "mdfld_dsi_output.h" + +#include "tc35876x-dsi-lvds.h" + +int mdfld_get_panel_type(struct drm_device *dev, int pipe) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	return dev_priv->mdfld_panel_id; +} + +static void mdfld_init_panel(struct drm_device *dev, int mipi_pipe, +								int p_type) +{ +	switch (p_type) { +	case TPO_VID: +		mdfld_dsi_output_init(dev, mipi_pipe, &mdfld_tpo_vid_funcs); +		break; +	case TC35876X: +		tc35876x_init(dev); +		mdfld_dsi_output_init(dev, mipi_pipe, &mdfld_tc35876x_funcs); +		break; +	case TMD_VID: +		mdfld_dsi_output_init(dev, mipi_pipe, &mdfld_tmd_vid_funcs); +		break; +	case HDMI: +/*		if (dev_priv->mdfld_hdmi_present) +			mdfld_hdmi_init(dev, &dev_priv->mode_dev); */ +		break; +	} +} + + +int mdfld_output_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	/* FIXME: hardcoded for now */ +	dev_priv->mdfld_panel_id = TC35876X; +	/* MIPI panel 1 */ +	mdfld_init_panel(dev, 0, dev_priv->mdfld_panel_id); +	/* HDMI panel */ +	mdfld_init_panel(dev, 1, HDMI); +	return 0; +} + diff --git a/drivers/gpu/drm/gma500/mdfld_output.h b/drivers/gpu/drm/gma500/mdfld_output.h new file mode 100644 index 00000000000..ab2b27c0f03 --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_output.h @@ -0,0 +1,77 @@ +/* + * Copyright (c)  2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicensen + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Thomas Eaton <thomas.g.eaton@intel.com> + * Scott Rowe <scott.m.rowe@intel.com> +*/ + +#ifndef MDFLD_OUTPUT_H +#define MDFLD_OUTPUT_H + +#include "psb_drv.h" + +#define TPO_PANEL_WIDTH		84 +#define TPO_PANEL_HEIGHT	46 +#define TMD_PANEL_WIDTH		39 +#define TMD_PANEL_HEIGHT	71 + +struct mdfld_dsi_config; + +enum panel_type { +	TPO_VID, +	TMD_VID, +	HDMI, +	TC35876X, +}; + +struct panel_info { +	u32 width_mm; +	u32 height_mm; +	/* Other info */ +}; + +struct panel_funcs { +	const struct drm_encoder_funcs *encoder_funcs; +	const struct drm_encoder_helper_funcs *encoder_helper_funcs; +	struct drm_display_mode * (*get_config_mode)(struct drm_device *); +	int (*get_panel_info)(struct drm_device *, int, struct panel_info *); +	int (*reset)(int pipe); +	void (*drv_ic_init)(struct mdfld_dsi_config *dsi_config, int pipe); +}; + +int mdfld_output_init(struct drm_device *dev); + +struct backlight_device *mdfld_get_backlight_device(void); +int mdfld_set_brightness(struct backlight_device *bd); + +int mdfld_get_panel_type(struct drm_device *dev, int pipe); + +extern const struct drm_crtc_helper_funcs mdfld_helper_funcs; + +extern const struct panel_funcs mdfld_tmd_vid_funcs; +extern const struct panel_funcs mdfld_tpo_vid_funcs; + +extern void mdfld_disable_crtc(struct drm_device *dev, int pipe); +extern void mdfldWaitForPipeEnable(struct drm_device *dev, int pipe); +extern void mdfldWaitForPipeDisable(struct drm_device *dev, int pipe); +#endif diff --git a/drivers/gpu/drm/gma500/mdfld_tmd_vid.c b/drivers/gpu/drm/gma500/mdfld_tmd_vid.c new file mode 100644 index 00000000000..dc0c6c3d3d2 --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_tmd_vid.c @@ -0,0 +1,201 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Jim Liu <jim.liu@intel.com> + * Jackie Li<yaodong.li@intel.com> + * Gideon Eaton <eaton. + * Scott Rowe <scott.m.rowe@intel.com> + */ + +#include "mdfld_dsi_dpi.h" +#include "mdfld_dsi_pkg_sender.h" + +static struct drm_display_mode *tmd_vid_get_config_mode(struct drm_device *dev) +{ +	struct drm_display_mode *mode; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_timing_info *ti = &dev_priv->gct_data.DTD; +	bool use_gct = false; /*Disable GCT for now*/ + +	mode = kzalloc(sizeof(*mode), GFP_KERNEL); +	if (!mode) +		return NULL; + +	if (use_gct) { +		mode->hdisplay = (ti->hactive_hi << 8) | ti->hactive_lo; +		mode->vdisplay = (ti->vactive_hi << 8) | ti->vactive_lo; +		mode->hsync_start = mode->hdisplay + \ +				((ti->hsync_offset_hi << 8) | \ +				ti->hsync_offset_lo); +		mode->hsync_end = mode->hsync_start + \ +				((ti->hsync_pulse_width_hi << 8) | \ +				ti->hsync_pulse_width_lo); +		mode->htotal = mode->hdisplay + ((ti->hblank_hi << 8) | \ +								ti->hblank_lo); +		mode->vsync_start = \ +			mode->vdisplay + ((ti->vsync_offset_hi << 8) | \ +						ti->vsync_offset_lo); +		mode->vsync_end = \ +			mode->vsync_start + ((ti->vsync_pulse_width_hi << 8) | \ +						ti->vsync_pulse_width_lo); +		mode->vtotal = mode->vdisplay + \ +				((ti->vblank_hi << 8) | ti->vblank_lo); +		mode->clock = ti->pixel_clock * 10; + +		dev_dbg(dev->dev, "hdisplay is %d\n", mode->hdisplay); +		dev_dbg(dev->dev, "vdisplay is %d\n", mode->vdisplay); +		dev_dbg(dev->dev, "HSS is %d\n", mode->hsync_start); +		dev_dbg(dev->dev, "HSE is %d\n", mode->hsync_end); +		dev_dbg(dev->dev, "htotal is %d\n", mode->htotal); +		dev_dbg(dev->dev, "VSS is %d\n", mode->vsync_start); +		dev_dbg(dev->dev, "VSE is %d\n", mode->vsync_end); +		dev_dbg(dev->dev, "vtotal is %d\n", mode->vtotal); +		dev_dbg(dev->dev, "clock is %d\n", mode->clock); +	} else { +		mode->hdisplay = 480; +		mode->vdisplay = 854; +		mode->hsync_start = 487; +		mode->hsync_end = 490; +		mode->htotal = 499; +		mode->vsync_start = 861; +		mode->vsync_end = 865; +		mode->vtotal = 873; +		mode->clock = 33264; +	} + +	drm_mode_set_name(mode); +	drm_mode_set_crtcinfo(mode, 0); + +	mode->type |= DRM_MODE_TYPE_PREFERRED; + +	return mode; +} + +static int tmd_vid_get_panel_info(struct drm_device *dev, +				int pipe, +				struct panel_info *pi) +{ +	if (!dev || !pi) +		return -EINVAL; + +	pi->width_mm = TMD_PANEL_WIDTH; +	pi->height_mm = TMD_PANEL_HEIGHT; + +	return 0; +} + +/* ************************************************************************* *\ + * FUNCTION: mdfld_init_TMD_MIPI + * + * DESCRIPTION:  This function is called only by mrst_dsi_mode_set and + *               restore_display_registers.  since this function does not + *               acquire the mutex, it is important that the calling function + *               does! +\* ************************************************************************* */ + +/* FIXME: make the below data u8 instead of u32; note byte order! */ +static u32 tmd_cmd_mcap_off[] = {0x000000b2}; +static u32 tmd_cmd_enable_lane_switch[] = {0x000101ef}; +static u32 tmd_cmd_set_lane_num[] = {0x006360ef}; +static u32 tmd_cmd_pushing_clock0[] = {0x00cc2fef}; +static u32 tmd_cmd_pushing_clock1[] = {0x00dd6eef}; +static u32 tmd_cmd_set_mode[] = {0x000000b3}; +static u32 tmd_cmd_set_sync_pulse_mode[] = {0x000961ef}; +static u32 tmd_cmd_set_column[] = {0x0100002a, 0x000000df}; +static u32 tmd_cmd_set_page[] = {0x0300002b, 0x00000055}; +static u32 tmd_cmd_set_video_mode[] = {0x00000153}; +/*no auto_bl,need add in furture*/ +static u32 tmd_cmd_enable_backlight[] = {0x00005ab4}; +static u32 tmd_cmd_set_backlight_dimming[] = {0x00000ebd}; + +static void mdfld_dsi_tmd_drv_ic_init(struct mdfld_dsi_config *dsi_config, +				      int pipe) +{ +	struct mdfld_dsi_pkg_sender *sender +			= mdfld_dsi_get_pkg_sender(dsi_config); + +	DRM_INFO("Enter mdfld init TMD MIPI display.\n"); + +	if (!sender) { +		DRM_ERROR("Cannot get sender\n"); +		return; +	} + +	if (dsi_config->dvr_ic_inited) +		return; + +	msleep(3); + +	/* FIXME: make the below data u8 instead of u32; note byte order! */ + +	mdfld_dsi_send_gen_long(sender, (u8 *) tmd_cmd_mcap_off, +				sizeof(tmd_cmd_mcap_off), false); +	mdfld_dsi_send_gen_long(sender, (u8 *) tmd_cmd_enable_lane_switch, +				sizeof(tmd_cmd_enable_lane_switch), false); +	mdfld_dsi_send_gen_long(sender, (u8 *) tmd_cmd_set_lane_num, +				sizeof(tmd_cmd_set_lane_num), false); +	mdfld_dsi_send_gen_long(sender, (u8 *) tmd_cmd_pushing_clock0, +				sizeof(tmd_cmd_pushing_clock0), false); +	mdfld_dsi_send_gen_long(sender, (u8 *) tmd_cmd_pushing_clock1, +				sizeof(tmd_cmd_pushing_clock1), false); +	mdfld_dsi_send_gen_long(sender, (u8 *) tmd_cmd_set_mode, +				sizeof(tmd_cmd_set_mode), false); +	mdfld_dsi_send_gen_long(sender, (u8 *) tmd_cmd_set_sync_pulse_mode, +				sizeof(tmd_cmd_set_sync_pulse_mode), false); +	mdfld_dsi_send_mcs_long(sender, (u8 *) tmd_cmd_set_column, +				sizeof(tmd_cmd_set_column), false); +	mdfld_dsi_send_mcs_long(sender, (u8 *) tmd_cmd_set_page, +				sizeof(tmd_cmd_set_page), false); +	mdfld_dsi_send_gen_long(sender, (u8 *) tmd_cmd_set_video_mode, +				sizeof(tmd_cmd_set_video_mode), false); +	mdfld_dsi_send_gen_long(sender, (u8 *) tmd_cmd_enable_backlight, +				sizeof(tmd_cmd_enable_backlight), false); +	mdfld_dsi_send_gen_long(sender, (u8 *) tmd_cmd_set_backlight_dimming, +				sizeof(tmd_cmd_set_backlight_dimming), false); + +	dsi_config->dvr_ic_inited = 1; +} + +/*TPO DPI encoder helper funcs*/ +static const struct drm_encoder_helper_funcs +				mdfld_tpo_dpi_encoder_helper_funcs = { +	.dpms = mdfld_dsi_dpi_dpms, +	.mode_fixup = mdfld_dsi_dpi_mode_fixup, +	.prepare = mdfld_dsi_dpi_prepare, +	.mode_set = mdfld_dsi_dpi_mode_set, +	.commit = mdfld_dsi_dpi_commit, +}; + +/*TPO DPI encoder funcs*/ +static const struct drm_encoder_funcs mdfld_tpo_dpi_encoder_funcs = { +	.destroy = drm_encoder_cleanup, +}; + +const struct panel_funcs mdfld_tmd_vid_funcs = { +	.encoder_funcs = &mdfld_tpo_dpi_encoder_funcs, +	.encoder_helper_funcs = &mdfld_tpo_dpi_encoder_helper_funcs, +	.get_config_mode = &tmd_vid_get_config_mode, +	.get_panel_info = tmd_vid_get_panel_info, +	.reset = mdfld_dsi_panel_reset, +	.drv_ic_init = mdfld_dsi_tmd_drv_ic_init, +}; diff --git a/drivers/gpu/drm/gma500/mdfld_tpo_vid.c b/drivers/gpu/drm/gma500/mdfld_tpo_vid.c new file mode 100644 index 00000000000..d8d4170725b --- /dev/null +++ b/drivers/gpu/drm/gma500/mdfld_tpo_vid.c @@ -0,0 +1,124 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * jim liu <jim.liu@intel.com> + * Jackie Li<yaodong.li@intel.com> + */ + +#include "mdfld_dsi_dpi.h" + +static struct drm_display_mode *tpo_vid_get_config_mode(struct drm_device *dev) +{ +	struct drm_display_mode *mode; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_timing_info *ti = &dev_priv->gct_data.DTD; +	bool use_gct = false; + +	mode = kzalloc(sizeof(*mode), GFP_KERNEL); +	if (!mode) +		return NULL; + +	if (use_gct) { +		mode->hdisplay = (ti->hactive_hi << 8) | ti->hactive_lo; +		mode->vdisplay = (ti->vactive_hi << 8) | ti->vactive_lo; +		mode->hsync_start = mode->hdisplay + +				((ti->hsync_offset_hi << 8) | +				ti->hsync_offset_lo); +		mode->hsync_end = mode->hsync_start + +				((ti->hsync_pulse_width_hi << 8) | +				ti->hsync_pulse_width_lo); +		mode->htotal = mode->hdisplay + ((ti->hblank_hi << 8) | +								ti->hblank_lo); +		mode->vsync_start = +			mode->vdisplay + ((ti->vsync_offset_hi << 8) | +						ti->vsync_offset_lo); +		mode->vsync_end = +			mode->vsync_start + ((ti->vsync_pulse_width_hi << 8) | +						ti->vsync_pulse_width_lo); +		mode->vtotal = mode->vdisplay + +				((ti->vblank_hi << 8) | ti->vblank_lo); +		mode->clock = ti->pixel_clock * 10; + +		dev_dbg(dev->dev, "hdisplay is %d\n", mode->hdisplay); +		dev_dbg(dev->dev, "vdisplay is %d\n", mode->vdisplay); +		dev_dbg(dev->dev, "HSS is %d\n", mode->hsync_start); +		dev_dbg(dev->dev, "HSE is %d\n", mode->hsync_end); +		dev_dbg(dev->dev, "htotal is %d\n", mode->htotal); +		dev_dbg(dev->dev, "VSS is %d\n", mode->vsync_start); +		dev_dbg(dev->dev, "VSE is %d\n", mode->vsync_end); +		dev_dbg(dev->dev, "vtotal is %d\n", mode->vtotal); +		dev_dbg(dev->dev, "clock is %d\n", mode->clock); +	} else { +		mode->hdisplay = 864; +		mode->vdisplay = 480; +		mode->hsync_start = 873; +		mode->hsync_end = 876; +		mode->htotal = 887; +		mode->vsync_start = 487; +		mode->vsync_end = 490; +		mode->vtotal = 499; +		mode->clock = 33264; +	} + +	drm_mode_set_name(mode); +	drm_mode_set_crtcinfo(mode, 0); + +	mode->type |= DRM_MODE_TYPE_PREFERRED; + +	return mode; +} + +static int tpo_vid_get_panel_info(struct drm_device *dev, +				int pipe, +				struct panel_info *pi) +{ +	if (!dev || !pi) +		return -EINVAL; + +	pi->width_mm = TPO_PANEL_WIDTH; +	pi->height_mm = TPO_PANEL_HEIGHT; + +	return 0; +} + +/*TPO DPI encoder helper funcs*/ +static const struct drm_encoder_helper_funcs +				mdfld_tpo_dpi_encoder_helper_funcs = { +	.dpms = mdfld_dsi_dpi_dpms, +	.mode_fixup = mdfld_dsi_dpi_mode_fixup, +	.prepare = mdfld_dsi_dpi_prepare, +	.mode_set = mdfld_dsi_dpi_mode_set, +	.commit = mdfld_dsi_dpi_commit, +}; + +/*TPO DPI encoder funcs*/ +static const struct drm_encoder_funcs mdfld_tpo_dpi_encoder_funcs = { +	.destroy = drm_encoder_cleanup, +}; + +const struct panel_funcs mdfld_tpo_vid_funcs = { +	.encoder_funcs = &mdfld_tpo_dpi_encoder_funcs, +	.encoder_helper_funcs = &mdfld_tpo_dpi_encoder_helper_funcs, +	.get_config_mode = &tpo_vid_get_config_mode, +	.get_panel_info = tpo_vid_get_panel_info, +}; diff --git a/drivers/gpu/drm/gma500/mid_bios.c b/drivers/gpu/drm/gma500/mid_bios.c new file mode 100644 index 00000000000..a97e38e284f --- /dev/null +++ b/drivers/gpu/drm/gma500/mid_bios.c @@ -0,0 +1,338 @@ +/************************************************************************** + * Copyright (c) 2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +/* TODO + * - Split functions by vbt type + * - Make them all take drm_device + * - Check ioremap failures + */ + +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/gma_drm.h> +#include "psb_drv.h" +#include "mid_bios.h" + +static void mid_get_fuse_settings(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); +	uint32_t fuse_value = 0; +	uint32_t fuse_value_tmp = 0; + +#define FB_REG06 0xD0810600 +#define FB_MIPI_DISABLE  (1 << 11) +#define FB_REG09 0xD0810900 +#define FB_REG09 0xD0810900 +#define FB_SKU_MASK  0x7000 +#define FB_SKU_SHIFT 12 +#define FB_SKU_100 0 +#define FB_SKU_100L 1 +#define FB_SKU_83 2 +	if (pci_root == NULL) { +		WARN_ON(1); +		return; +	} + + +	pci_write_config_dword(pci_root, 0xD0, FB_REG06); +	pci_read_config_dword(pci_root, 0xD4, &fuse_value); + +	/* FB_MIPI_DISABLE doesn't mean LVDS on with Medfield */ +	if (IS_MRST(dev)) +		dev_priv->iLVDS_enable = fuse_value & FB_MIPI_DISABLE; + +	DRM_INFO("internal display is %s\n", +		 dev_priv->iLVDS_enable ? "LVDS display" : "MIPI display"); + +	 /* Prevent runtime suspend at start*/ +	 if (dev_priv->iLVDS_enable) { +		dev_priv->is_lvds_on = true; +		dev_priv->is_mipi_on = false; +	} else { +		dev_priv->is_mipi_on = true; +		dev_priv->is_lvds_on = false; +	} + +	dev_priv->video_device_fuse = fuse_value; + +	pci_write_config_dword(pci_root, 0xD0, FB_REG09); +	pci_read_config_dword(pci_root, 0xD4, &fuse_value); + +	dev_dbg(dev->dev, "SKU values is 0x%x.\n", fuse_value); +	fuse_value_tmp = (fuse_value & FB_SKU_MASK) >> FB_SKU_SHIFT; + +	dev_priv->fuse_reg_value = fuse_value; + +	switch (fuse_value_tmp) { +	case FB_SKU_100: +		dev_priv->core_freq = 200; +		break; +	case FB_SKU_100L: +		dev_priv->core_freq = 100; +		break; +	case FB_SKU_83: +		dev_priv->core_freq = 166; +		break; +	default: +		dev_warn(dev->dev, "Invalid SKU values, SKU value = 0x%08x\n", +								fuse_value_tmp); +		dev_priv->core_freq = 0; +	} +	dev_dbg(dev->dev, "LNC core clk is %dMHz.\n", dev_priv->core_freq); +	pci_dev_put(pci_root); +} + +/* + *	Get the revison ID, B0:D2:F0;0x08 + */ +static void mid_get_pci_revID(struct drm_psb_private *dev_priv) +{ +	uint32_t platform_rev_id = 0; +	struct pci_dev *pci_gfx_root = pci_get_bus_and_slot(0, PCI_DEVFN(2, 0)); + +	if (pci_gfx_root == NULL) { +		WARN_ON(1); +		return; +	} +	pci_read_config_dword(pci_gfx_root, 0x08, &platform_rev_id); +	dev_priv->platform_rev_id = (uint8_t) platform_rev_id; +	pci_dev_put(pci_gfx_root); +	dev_dbg(dev_priv->dev->dev, "platform_rev_id is %x\n", +					dev_priv->platform_rev_id); +} + +struct mid_vbt_header { +	u32 signature; +	u8 revision; +} __packed; + +/* The same for r0 and r1 */ +struct vbt_r0 { +	struct mid_vbt_header vbt_header; +	u8 size; +	u8 checksum; +} __packed; + +struct vbt_r10 { +	struct mid_vbt_header vbt_header; +	u8 checksum; +	u16 size; +	u8 panel_count; +	u8 primary_panel_idx; +	u8 secondary_panel_idx; +	u8 __reserved[5]; +} __packed; + +static int read_vbt_r0(u32 addr, struct vbt_r0 *vbt) +{ +	void __iomem *vbt_virtual; + +	vbt_virtual = ioremap(addr, sizeof(*vbt)); +	if (vbt_virtual == NULL) +		return -1; + +	memcpy_fromio(vbt, vbt_virtual, sizeof(*vbt)); +	iounmap(vbt_virtual); + +	return 0; +} + +static int read_vbt_r10(u32 addr, struct vbt_r10 *vbt) +{ +	void __iomem *vbt_virtual; + +	vbt_virtual = ioremap(addr, sizeof(*vbt)); +	if (!vbt_virtual) +		return -1; + +	memcpy_fromio(vbt, vbt_virtual, sizeof(*vbt)); +	iounmap(vbt_virtual); + +	return 0; +} + +static int mid_get_vbt_data_r0(struct drm_psb_private *dev_priv, u32 addr) +{ +	struct vbt_r0 vbt; +	void __iomem *gct_virtual; +	struct gct_r0 gct; +	u8 bpi; + +	if (read_vbt_r0(addr, &vbt)) +		return -1; + +	gct_virtual = ioremap(addr + sizeof(vbt), vbt.size - sizeof(vbt)); +	if (!gct_virtual) +		return -1; +	memcpy_fromio(&gct, gct_virtual, sizeof(gct)); +	iounmap(gct_virtual); + +	bpi = gct.PD.BootPanelIndex; +	dev_priv->gct_data.bpi = bpi; +	dev_priv->gct_data.pt = gct.PD.PanelType; +	dev_priv->gct_data.DTD = gct.panel[bpi].DTD; +	dev_priv->gct_data.Panel_Port_Control = +		gct.panel[bpi].Panel_Port_Control; +	dev_priv->gct_data.Panel_MIPI_Display_Descriptor = +		gct.panel[bpi].Panel_MIPI_Display_Descriptor; + +	return 0; +} + +static int mid_get_vbt_data_r1(struct drm_psb_private *dev_priv, u32 addr) +{ +	struct vbt_r0 vbt; +	void __iomem *gct_virtual; +	struct gct_r1 gct; +	u8 bpi; + +	if (read_vbt_r0(addr, &vbt)) +		return -1; + +	gct_virtual = ioremap(addr + sizeof(vbt), vbt.size - sizeof(vbt)); +	if (!gct_virtual) +		return -1; +	memcpy_fromio(&gct, gct_virtual, sizeof(gct)); +	iounmap(gct_virtual); + +	bpi = gct.PD.BootPanelIndex; +	dev_priv->gct_data.bpi = bpi; +	dev_priv->gct_data.pt = gct.PD.PanelType; +	dev_priv->gct_data.DTD = gct.panel[bpi].DTD; +	dev_priv->gct_data.Panel_Port_Control = +		gct.panel[bpi].Panel_Port_Control; +	dev_priv->gct_data.Panel_MIPI_Display_Descriptor = +		gct.panel[bpi].Panel_MIPI_Display_Descriptor; + +	return 0; +} + +static int mid_get_vbt_data_r10(struct drm_psb_private *dev_priv, u32 addr) +{ +	struct vbt_r10 vbt; +	void __iomem *gct_virtual; +	struct gct_r10 *gct; +	struct oaktrail_timing_info *dp_ti = &dev_priv->gct_data.DTD; +	struct gct_r10_timing_info *ti; +	int ret = -1; + +	if (read_vbt_r10(addr, &vbt)) +		return -1; + +	gct = kmalloc(sizeof(*gct) * vbt.panel_count, GFP_KERNEL); +	if (!gct) +		return -1; + +	gct_virtual = ioremap(addr + sizeof(vbt), +			sizeof(*gct) * vbt.panel_count); +	if (!gct_virtual) +		goto out; +	memcpy_fromio(gct, gct_virtual, sizeof(*gct)); +	iounmap(gct_virtual); + +	dev_priv->gct_data.bpi = vbt.primary_panel_idx; +	dev_priv->gct_data.Panel_MIPI_Display_Descriptor = +		gct[vbt.primary_panel_idx].Panel_MIPI_Display_Descriptor; + +	ti = &gct[vbt.primary_panel_idx].DTD; +	dp_ti->pixel_clock = ti->pixel_clock; +	dp_ti->hactive_hi = ti->hactive_hi; +	dp_ti->hactive_lo = ti->hactive_lo; +	dp_ti->hblank_hi = ti->hblank_hi; +	dp_ti->hblank_lo = ti->hblank_lo; +	dp_ti->hsync_offset_hi = ti->hsync_offset_hi; +	dp_ti->hsync_offset_lo = ti->hsync_offset_lo; +	dp_ti->hsync_pulse_width_hi = ti->hsync_pulse_width_hi; +	dp_ti->hsync_pulse_width_lo = ti->hsync_pulse_width_lo; +	dp_ti->vactive_hi = ti->vactive_hi; +	dp_ti->vactive_lo = ti->vactive_lo; +	dp_ti->vblank_hi = ti->vblank_hi; +	dp_ti->vblank_lo = ti->vblank_lo; +	dp_ti->vsync_offset_hi = ti->vsync_offset_hi; +	dp_ti->vsync_offset_lo = ti->vsync_offset_lo; +	dp_ti->vsync_pulse_width_hi = ti->vsync_pulse_width_hi; +	dp_ti->vsync_pulse_width_lo = ti->vsync_pulse_width_lo; + +	ret = 0; +out: +	kfree(gct); +	return ret; +} + +static void mid_get_vbt_data(struct drm_psb_private *dev_priv) +{ +	struct drm_device *dev = dev_priv->dev; +	u32 addr; +	u8 __iomem *vbt_virtual; +	struct mid_vbt_header vbt_header; +	struct pci_dev *pci_gfx_root = pci_get_bus_and_slot(0, PCI_DEVFN(2, 0)); +	int ret = -1; + +	/* Get the address of the platform config vbt */ +	pci_read_config_dword(pci_gfx_root, 0xFC, &addr); +	pci_dev_put(pci_gfx_root); + +	dev_dbg(dev->dev, "drm platform config address is %x\n", addr); + +	if (!addr) +		goto out; + +	/* get the virtual address of the vbt */ +	vbt_virtual = ioremap(addr, sizeof(vbt_header)); +	if (!vbt_virtual) +		goto out; + +	memcpy_fromio(&vbt_header, vbt_virtual, sizeof(vbt_header)); +	iounmap(vbt_virtual); + +	if (memcmp(&vbt_header.signature, "$GCT", 4)) +		goto out; + +	dev_dbg(dev->dev, "GCT revision is %02x\n", vbt_header.revision); + +	switch (vbt_header.revision) { +	case 0x00: +		ret = mid_get_vbt_data_r0(dev_priv, addr); +		break; +	case 0x01: +		ret = mid_get_vbt_data_r1(dev_priv, addr); +		break; +	case 0x10: +		ret = mid_get_vbt_data_r10(dev_priv, addr); +		break; +	default: +		dev_err(dev->dev, "Unknown revision of GCT!\n"); +	} + +out: +	if (ret) +		dev_err(dev->dev, "Unable to read GCT!"); +	else +		dev_priv->has_gct = true; +} + +int mid_chip_setup(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	mid_get_fuse_settings(dev); +	mid_get_vbt_data(dev_priv); +	mid_get_pci_revID(dev_priv); +	return 0; +} diff --git a/drivers/gpu/drm/gma500/mid_bios.h b/drivers/gpu/drm/gma500/mid_bios.h new file mode 100644 index 00000000000..00e7d564b7e --- /dev/null +++ b/drivers/gpu/drm/gma500/mid_bios.h @@ -0,0 +1,21 @@ +/************************************************************************** + * Copyright (c) 2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +extern int mid_chip_setup(struct drm_device *dev); + diff --git a/drivers/gpu/drm/gma500/mmu.c b/drivers/gpu/drm/gma500/mmu.c new file mode 100644 index 00000000000..0eaf11c1993 --- /dev/null +++ b/drivers/gpu/drm/gma500/mmu.c @@ -0,0 +1,812 @@ +/************************************************************************** + * Copyright (c) 2007, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ +#include <drm/drmP.h> +#include "psb_drv.h" +#include "psb_reg.h" +#include "mmu.h" + +/* + * Code for the SGX MMU: + */ + +/* + * clflush on one processor only: + * clflush should apparently flush the cache line on all processors in an + * SMP system. + */ + +/* + * kmap atomic: + * The usage of the slots must be completely encapsulated within a spinlock, and + * no other functions that may be using the locks for other purposed may be + * called from within the locked region. + * Since the slots are per processor, this will guarantee that we are the only + * user. + */ + +/* + * TODO: Inserting ptes from an interrupt handler: + * This may be desirable for some SGX functionality where the GPU can fault in + * needed pages. For that, we need to make an atomic insert_pages function, that + * may fail. + * If it fails, the caller need to insert the page using a workqueue function, + * but on average it should be fast. + */ + +static inline uint32_t psb_mmu_pt_index(uint32_t offset) +{ +	return (offset >> PSB_PTE_SHIFT) & 0x3FF; +} + +static inline uint32_t psb_mmu_pd_index(uint32_t offset) +{ +	return offset >> PSB_PDE_SHIFT; +} + +#if defined(CONFIG_X86) +static inline void psb_clflush(void *addr) +{ +	__asm__ __volatile__("clflush (%0)\n" : : "r"(addr) : "memory"); +} + +static inline void psb_mmu_clflush(struct psb_mmu_driver *driver, void *addr) +{ +	if (!driver->has_clflush) +		return; + +	mb(); +	psb_clflush(addr); +	mb(); +} +#else + +static inline void psb_mmu_clflush(struct psb_mmu_driver *driver, void *addr) +{; +} + +#endif + +static void psb_mmu_flush_pd_locked(struct psb_mmu_driver *driver, int force) +{ +	struct drm_device *dev = driver->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (atomic_read(&driver->needs_tlbflush) || force) { +		uint32_t val = PSB_RSGX32(PSB_CR_BIF_CTRL); +		PSB_WSGX32(val | _PSB_CB_CTRL_INVALDC, PSB_CR_BIF_CTRL); + +		/* Make sure data cache is turned off before enabling it */ +		wmb(); +		PSB_WSGX32(val & ~_PSB_CB_CTRL_INVALDC, PSB_CR_BIF_CTRL); +		(void)PSB_RSGX32(PSB_CR_BIF_CTRL); +		if (driver->msvdx_mmu_invaldc) +			atomic_set(driver->msvdx_mmu_invaldc, 1); +	} +	atomic_set(&driver->needs_tlbflush, 0); +} + +#if 0 +static void psb_mmu_flush_pd(struct psb_mmu_driver *driver, int force) +{ +	down_write(&driver->sem); +	psb_mmu_flush_pd_locked(driver, force); +	up_write(&driver->sem); +} +#endif + +void psb_mmu_flush(struct psb_mmu_driver *driver) +{ +	struct drm_device *dev = driver->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	uint32_t val; + +	down_write(&driver->sem); +	val = PSB_RSGX32(PSB_CR_BIF_CTRL); +	if (atomic_read(&driver->needs_tlbflush)) +		PSB_WSGX32(val | _PSB_CB_CTRL_INVALDC, PSB_CR_BIF_CTRL); +	else +		PSB_WSGX32(val | _PSB_CB_CTRL_FLUSH, PSB_CR_BIF_CTRL); + +	/* Make sure data cache is turned off and MMU is flushed before +	   restoring bank interface control register */ +	wmb(); +	PSB_WSGX32(val & ~(_PSB_CB_CTRL_FLUSH | _PSB_CB_CTRL_INVALDC), +		   PSB_CR_BIF_CTRL); +	(void)PSB_RSGX32(PSB_CR_BIF_CTRL); + +	atomic_set(&driver->needs_tlbflush, 0); +	if (driver->msvdx_mmu_invaldc) +		atomic_set(driver->msvdx_mmu_invaldc, 1); +	up_write(&driver->sem); +} + +void psb_mmu_set_pd_context(struct psb_mmu_pd *pd, int hw_context) +{ +	struct drm_device *dev = pd->driver->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	uint32_t offset = (hw_context == 0) ? PSB_CR_BIF_DIR_LIST_BASE0 : +			  PSB_CR_BIF_DIR_LIST_BASE1 + hw_context * 4; + +	down_write(&pd->driver->sem); +	PSB_WSGX32(page_to_pfn(pd->p) << PAGE_SHIFT, offset); +	wmb(); +	psb_mmu_flush_pd_locked(pd->driver, 1); +	pd->hw_context = hw_context; +	up_write(&pd->driver->sem); + +} + +static inline unsigned long psb_pd_addr_end(unsigned long addr, +					    unsigned long end) +{ +	addr = (addr + PSB_PDE_MASK + 1) & ~PSB_PDE_MASK; +	return (addr < end) ? addr : end; +} + +static inline uint32_t psb_mmu_mask_pte(uint32_t pfn, int type) +{ +	uint32_t mask = PSB_PTE_VALID; + +	if (type & PSB_MMU_CACHED_MEMORY) +		mask |= PSB_PTE_CACHED; +	if (type & PSB_MMU_RO_MEMORY) +		mask |= PSB_PTE_RO; +	if (type & PSB_MMU_WO_MEMORY) +		mask |= PSB_PTE_WO; + +	return (pfn << PAGE_SHIFT) | mask; +} + +struct psb_mmu_pd *psb_mmu_alloc_pd(struct psb_mmu_driver *driver, +				    int trap_pagefaults, int invalid_type) +{ +	struct psb_mmu_pd *pd = kmalloc(sizeof(*pd), GFP_KERNEL); +	uint32_t *v; +	int i; + +	if (!pd) +		return NULL; + +	pd->p = alloc_page(GFP_DMA32); +	if (!pd->p) +		goto out_err1; +	pd->dummy_pt = alloc_page(GFP_DMA32); +	if (!pd->dummy_pt) +		goto out_err2; +	pd->dummy_page = alloc_page(GFP_DMA32); +	if (!pd->dummy_page) +		goto out_err3; + +	if (!trap_pagefaults) { +		pd->invalid_pde = psb_mmu_mask_pte(page_to_pfn(pd->dummy_pt), +						   invalid_type); +		pd->invalid_pte = psb_mmu_mask_pte(page_to_pfn(pd->dummy_page), +						   invalid_type); +	} else { +		pd->invalid_pde = 0; +		pd->invalid_pte = 0; +	} + +	v = kmap(pd->dummy_pt); +	for (i = 0; i < (PAGE_SIZE / sizeof(uint32_t)); ++i) +		v[i] = pd->invalid_pte; + +	kunmap(pd->dummy_pt); + +	v = kmap(pd->p); +	for (i = 0; i < (PAGE_SIZE / sizeof(uint32_t)); ++i) +		v[i] = pd->invalid_pde; + +	kunmap(pd->p); + +	clear_page(kmap(pd->dummy_page)); +	kunmap(pd->dummy_page); + +	pd->tables = vmalloc_user(sizeof(struct psb_mmu_pt *) * 1024); +	if (!pd->tables) +		goto out_err4; + +	pd->hw_context = -1; +	pd->pd_mask = PSB_PTE_VALID; +	pd->driver = driver; + +	return pd; + +out_err4: +	__free_page(pd->dummy_page); +out_err3: +	__free_page(pd->dummy_pt); +out_err2: +	__free_page(pd->p); +out_err1: +	kfree(pd); +	return NULL; +} + +static void psb_mmu_free_pt(struct psb_mmu_pt *pt) +{ +	__free_page(pt->p); +	kfree(pt); +} + +void psb_mmu_free_pagedir(struct psb_mmu_pd *pd) +{ +	struct psb_mmu_driver *driver = pd->driver; +	struct drm_device *dev = driver->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_mmu_pt *pt; +	int i; + +	down_write(&driver->sem); +	if (pd->hw_context != -1) { +		PSB_WSGX32(0, PSB_CR_BIF_DIR_LIST_BASE0 + pd->hw_context * 4); +		psb_mmu_flush_pd_locked(driver, 1); +	} + +	/* Should take the spinlock here, but we don't need to do that +	   since we have the semaphore in write mode. */ + +	for (i = 0; i < 1024; ++i) { +		pt = pd->tables[i]; +		if (pt) +			psb_mmu_free_pt(pt); +	} + +	vfree(pd->tables); +	__free_page(pd->dummy_page); +	__free_page(pd->dummy_pt); +	__free_page(pd->p); +	kfree(pd); +	up_write(&driver->sem); +} + +static struct psb_mmu_pt *psb_mmu_alloc_pt(struct psb_mmu_pd *pd) +{ +	struct psb_mmu_pt *pt = kmalloc(sizeof(*pt), GFP_KERNEL); +	void *v; +	uint32_t clflush_add = pd->driver->clflush_add >> PAGE_SHIFT; +	uint32_t clflush_count = PAGE_SIZE / clflush_add; +	spinlock_t *lock = &pd->driver->lock; +	uint8_t *clf; +	uint32_t *ptes; +	int i; + +	if (!pt) +		return NULL; + +	pt->p = alloc_page(GFP_DMA32); +	if (!pt->p) { +		kfree(pt); +		return NULL; +	} + +	spin_lock(lock); + +	v = kmap_atomic(pt->p); +	clf = (uint8_t *) v; +	ptes = (uint32_t *) v; +	for (i = 0; i < (PAGE_SIZE / sizeof(uint32_t)); ++i) +		*ptes++ = pd->invalid_pte; + +#if defined(CONFIG_X86) +	if (pd->driver->has_clflush && pd->hw_context != -1) { +		mb(); +		for (i = 0; i < clflush_count; ++i) { +			psb_clflush(clf); +			clf += clflush_add; +		} +		mb(); +	} +#endif +	kunmap_atomic(v); +	spin_unlock(lock); + +	pt->count = 0; +	pt->pd = pd; +	pt->index = 0; + +	return pt; +} + +struct psb_mmu_pt *psb_mmu_pt_alloc_map_lock(struct psb_mmu_pd *pd, +					     unsigned long addr) +{ +	uint32_t index = psb_mmu_pd_index(addr); +	struct psb_mmu_pt *pt; +	uint32_t *v; +	spinlock_t *lock = &pd->driver->lock; + +	spin_lock(lock); +	pt = pd->tables[index]; +	while (!pt) { +		spin_unlock(lock); +		pt = psb_mmu_alloc_pt(pd); +		if (!pt) +			return NULL; +		spin_lock(lock); + +		if (pd->tables[index]) { +			spin_unlock(lock); +			psb_mmu_free_pt(pt); +			spin_lock(lock); +			pt = pd->tables[index]; +			continue; +		} + +		v = kmap_atomic(pd->p); +		pd->tables[index] = pt; +		v[index] = (page_to_pfn(pt->p) << 12) | pd->pd_mask; +		pt->index = index; +		kunmap_atomic((void *) v); + +		if (pd->hw_context != -1) { +			psb_mmu_clflush(pd->driver, (void *)&v[index]); +			atomic_set(&pd->driver->needs_tlbflush, 1); +		} +	} +	pt->v = kmap_atomic(pt->p); +	return pt; +} + +static struct psb_mmu_pt *psb_mmu_pt_map_lock(struct psb_mmu_pd *pd, +					      unsigned long addr) +{ +	uint32_t index = psb_mmu_pd_index(addr); +	struct psb_mmu_pt *pt; +	spinlock_t *lock = &pd->driver->lock; + +	spin_lock(lock); +	pt = pd->tables[index]; +	if (!pt) { +		spin_unlock(lock); +		return NULL; +	} +	pt->v = kmap_atomic(pt->p); +	return pt; +} + +static void psb_mmu_pt_unmap_unlock(struct psb_mmu_pt *pt) +{ +	struct psb_mmu_pd *pd = pt->pd; +	uint32_t *v; + +	kunmap_atomic(pt->v); +	if (pt->count == 0) { +		v = kmap_atomic(pd->p); +		v[pt->index] = pd->invalid_pde; +		pd->tables[pt->index] = NULL; + +		if (pd->hw_context != -1) { +			psb_mmu_clflush(pd->driver, (void *)&v[pt->index]); +			atomic_set(&pd->driver->needs_tlbflush, 1); +		} +		kunmap_atomic(pt->v); +		spin_unlock(&pd->driver->lock); +		psb_mmu_free_pt(pt); +		return; +	} +	spin_unlock(&pd->driver->lock); +} + +static inline void psb_mmu_set_pte(struct psb_mmu_pt *pt, unsigned long addr, +				   uint32_t pte) +{ +	pt->v[psb_mmu_pt_index(addr)] = pte; +} + +static inline void psb_mmu_invalidate_pte(struct psb_mmu_pt *pt, +					  unsigned long addr) +{ +	pt->v[psb_mmu_pt_index(addr)] = pt->pd->invalid_pte; +} + +struct psb_mmu_pd *psb_mmu_get_default_pd(struct psb_mmu_driver *driver) +{ +	struct psb_mmu_pd *pd; + +	down_read(&driver->sem); +	pd = driver->default_pd; +	up_read(&driver->sem); + +	return pd; +} + +/* Returns the physical address of the PD shared by sgx/msvdx */ +uint32_t psb_get_default_pd_addr(struct psb_mmu_driver *driver) +{ +	struct psb_mmu_pd *pd; + +	pd = psb_mmu_get_default_pd(driver); +	return page_to_pfn(pd->p) << PAGE_SHIFT; +} + +void psb_mmu_driver_takedown(struct psb_mmu_driver *driver) +{ +	struct drm_device *dev = driver->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	PSB_WSGX32(driver->bif_ctrl, PSB_CR_BIF_CTRL); +	psb_mmu_free_pagedir(driver->default_pd); +	kfree(driver); +} + +struct psb_mmu_driver *psb_mmu_driver_init(struct drm_device *dev, +					   int trap_pagefaults, +					   int invalid_type, +					   atomic_t *msvdx_mmu_invaldc) +{ +	struct psb_mmu_driver *driver; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	driver = kmalloc(sizeof(*driver), GFP_KERNEL); + +	if (!driver) +		return NULL; + +	driver->dev = dev; +	driver->default_pd = psb_mmu_alloc_pd(driver, trap_pagefaults, +					      invalid_type); +	if (!driver->default_pd) +		goto out_err1; + +	spin_lock_init(&driver->lock); +	init_rwsem(&driver->sem); +	down_write(&driver->sem); +	atomic_set(&driver->needs_tlbflush, 1); +	driver->msvdx_mmu_invaldc = msvdx_mmu_invaldc; + +	driver->bif_ctrl = PSB_RSGX32(PSB_CR_BIF_CTRL); +	PSB_WSGX32(driver->bif_ctrl | _PSB_CB_CTRL_CLEAR_FAULT, +		   PSB_CR_BIF_CTRL); +	PSB_WSGX32(driver->bif_ctrl & ~_PSB_CB_CTRL_CLEAR_FAULT, +		   PSB_CR_BIF_CTRL); + +	driver->has_clflush = 0; + +#if defined(CONFIG_X86) +	if (boot_cpu_has(X86_FEATURE_CLFLUSH)) { +		uint32_t tfms, misc, cap0, cap4, clflush_size; + +		/* +		 * clflush size is determined at kernel setup for x86_64 but not +		 * for i386. We have to do it here. +		 */ + +		cpuid(0x00000001, &tfms, &misc, &cap0, &cap4); +		clflush_size = ((misc >> 8) & 0xff) * 8; +		driver->has_clflush = 1; +		driver->clflush_add = +		    PAGE_SIZE * clflush_size / sizeof(uint32_t); +		driver->clflush_mask = driver->clflush_add - 1; +		driver->clflush_mask = ~driver->clflush_mask; +	} +#endif + +	up_write(&driver->sem); +	return driver; + +out_err1: +	kfree(driver); +	return NULL; +} + +#if defined(CONFIG_X86) +static void psb_mmu_flush_ptes(struct psb_mmu_pd *pd, unsigned long address, +			       uint32_t num_pages, uint32_t desired_tile_stride, +			       uint32_t hw_tile_stride) +{ +	struct psb_mmu_pt *pt; +	uint32_t rows = 1; +	uint32_t i; +	unsigned long addr; +	unsigned long end; +	unsigned long next; +	unsigned long add; +	unsigned long row_add; +	unsigned long clflush_add = pd->driver->clflush_add; +	unsigned long clflush_mask = pd->driver->clflush_mask; + +	if (!pd->driver->has_clflush) +		return; + +	if (hw_tile_stride) +		rows = num_pages / desired_tile_stride; +	else +		desired_tile_stride = num_pages; + +	add = desired_tile_stride << PAGE_SHIFT; +	row_add = hw_tile_stride << PAGE_SHIFT; +	mb(); +	for (i = 0; i < rows; ++i) { + +		addr = address; +		end = addr + add; + +		do { +			next = psb_pd_addr_end(addr, end); +			pt = psb_mmu_pt_map_lock(pd, addr); +			if (!pt) +				continue; +			do { +				psb_clflush(&pt->v[psb_mmu_pt_index(addr)]); +			} while (addr += clflush_add, +				 (addr & clflush_mask) < next); + +			psb_mmu_pt_unmap_unlock(pt); +		} while (addr = next, next != end); +		address += row_add; +	} +	mb(); +} +#else +static void psb_mmu_flush_ptes(struct psb_mmu_pd *pd, unsigned long address, +			       uint32_t num_pages, uint32_t desired_tile_stride, +			       uint32_t hw_tile_stride) +{ +	drm_ttm_cache_flush(); +} +#endif + +void psb_mmu_remove_pfn_sequence(struct psb_mmu_pd *pd, +				 unsigned long address, uint32_t num_pages) +{ +	struct psb_mmu_pt *pt; +	unsigned long addr; +	unsigned long end; +	unsigned long next; +	unsigned long f_address = address; + +	down_read(&pd->driver->sem); + +	addr = address; +	end = addr + (num_pages << PAGE_SHIFT); + +	do { +		next = psb_pd_addr_end(addr, end); +		pt = psb_mmu_pt_alloc_map_lock(pd, addr); +		if (!pt) +			goto out; +		do { +			psb_mmu_invalidate_pte(pt, addr); +			--pt->count; +		} while (addr += PAGE_SIZE, addr < next); +		psb_mmu_pt_unmap_unlock(pt); + +	} while (addr = next, next != end); + +out: +	if (pd->hw_context != -1) +		psb_mmu_flush_ptes(pd, f_address, num_pages, 1, 1); + +	up_read(&pd->driver->sem); + +	if (pd->hw_context != -1) +		psb_mmu_flush(pd->driver); + +	return; +} + +void psb_mmu_remove_pages(struct psb_mmu_pd *pd, unsigned long address, +			  uint32_t num_pages, uint32_t desired_tile_stride, +			  uint32_t hw_tile_stride) +{ +	struct psb_mmu_pt *pt; +	uint32_t rows = 1; +	uint32_t i; +	unsigned long addr; +	unsigned long end; +	unsigned long next; +	unsigned long add; +	unsigned long row_add; +	unsigned long f_address = address; + +	if (hw_tile_stride) +		rows = num_pages / desired_tile_stride; +	else +		desired_tile_stride = num_pages; + +	add = desired_tile_stride << PAGE_SHIFT; +	row_add = hw_tile_stride << PAGE_SHIFT; + +	down_read(&pd->driver->sem); + +	/* Make sure we only need to flush this processor's cache */ + +	for (i = 0; i < rows; ++i) { + +		addr = address; +		end = addr + add; + +		do { +			next = psb_pd_addr_end(addr, end); +			pt = psb_mmu_pt_map_lock(pd, addr); +			if (!pt) +				continue; +			do { +				psb_mmu_invalidate_pte(pt, addr); +				--pt->count; + +			} while (addr += PAGE_SIZE, addr < next); +			psb_mmu_pt_unmap_unlock(pt); + +		} while (addr = next, next != end); +		address += row_add; +	} +	if (pd->hw_context != -1) +		psb_mmu_flush_ptes(pd, f_address, num_pages, +				   desired_tile_stride, hw_tile_stride); + +	up_read(&pd->driver->sem); + +	if (pd->hw_context != -1) +		psb_mmu_flush(pd->driver); +} + +int psb_mmu_insert_pfn_sequence(struct psb_mmu_pd *pd, uint32_t start_pfn, +				unsigned long address, uint32_t num_pages, +				int type) +{ +	struct psb_mmu_pt *pt; +	uint32_t pte; +	unsigned long addr; +	unsigned long end; +	unsigned long next; +	unsigned long f_address = address; +	int ret = -ENOMEM; + +	down_read(&pd->driver->sem); + +	addr = address; +	end = addr + (num_pages << PAGE_SHIFT); + +	do { +		next = psb_pd_addr_end(addr, end); +		pt = psb_mmu_pt_alloc_map_lock(pd, addr); +		if (!pt) { +			ret = -ENOMEM; +			goto out; +		} +		do { +			pte = psb_mmu_mask_pte(start_pfn++, type); +			psb_mmu_set_pte(pt, addr, pte); +			pt->count++; +		} while (addr += PAGE_SIZE, addr < next); +		psb_mmu_pt_unmap_unlock(pt); + +	} while (addr = next, next != end); +	ret = 0; + +out: +	if (pd->hw_context != -1) +		psb_mmu_flush_ptes(pd, f_address, num_pages, 1, 1); + +	up_read(&pd->driver->sem); + +	if (pd->hw_context != -1) +		psb_mmu_flush(pd->driver); + +	return 0; +} + +int psb_mmu_insert_pages(struct psb_mmu_pd *pd, struct page **pages, +			 unsigned long address, uint32_t num_pages, +			 uint32_t desired_tile_stride, uint32_t hw_tile_stride, +			 int type) +{ +	struct psb_mmu_pt *pt; +	uint32_t rows = 1; +	uint32_t i; +	uint32_t pte; +	unsigned long addr; +	unsigned long end; +	unsigned long next; +	unsigned long add; +	unsigned long row_add; +	unsigned long f_address = address; +	int ret = -ENOMEM; + +	if (hw_tile_stride) { +		if (num_pages % desired_tile_stride != 0) +			return -EINVAL; +		rows = num_pages / desired_tile_stride; +	} else { +		desired_tile_stride = num_pages; +	} + +	add = desired_tile_stride << PAGE_SHIFT; +	row_add = hw_tile_stride << PAGE_SHIFT; + +	down_read(&pd->driver->sem); + +	for (i = 0; i < rows; ++i) { + +		addr = address; +		end = addr + add; + +		do { +			next = psb_pd_addr_end(addr, end); +			pt = psb_mmu_pt_alloc_map_lock(pd, addr); +			if (!pt) +				goto out; +			do { +				pte = psb_mmu_mask_pte(page_to_pfn(*pages++), +						       type); +				psb_mmu_set_pte(pt, addr, pte); +				pt->count++; +			} while (addr += PAGE_SIZE, addr < next); +			psb_mmu_pt_unmap_unlock(pt); + +		} while (addr = next, next != end); + +		address += row_add; +	} + +	ret = 0; +out: +	if (pd->hw_context != -1) +		psb_mmu_flush_ptes(pd, f_address, num_pages, +				   desired_tile_stride, hw_tile_stride); + +	up_read(&pd->driver->sem); + +	if (pd->hw_context != -1) +		psb_mmu_flush(pd->driver); + +	return ret; +} + +int psb_mmu_virtual_to_pfn(struct psb_mmu_pd *pd, uint32_t virtual, +			   unsigned long *pfn) +{ +	int ret; +	struct psb_mmu_pt *pt; +	uint32_t tmp; +	spinlock_t *lock = &pd->driver->lock; + +	down_read(&pd->driver->sem); +	pt = psb_mmu_pt_map_lock(pd, virtual); +	if (!pt) { +		uint32_t *v; + +		spin_lock(lock); +		v = kmap_atomic(pd->p); +		tmp = v[psb_mmu_pd_index(virtual)]; +		kunmap_atomic(v); +		spin_unlock(lock); + +		if (tmp != pd->invalid_pde || !(tmp & PSB_PTE_VALID) || +		    !(pd->invalid_pte & PSB_PTE_VALID)) { +			ret = -EINVAL; +			goto out; +		} +		ret = 0; +		*pfn = pd->invalid_pte >> PAGE_SHIFT; +		goto out; +	} +	tmp = pt->v[psb_mmu_pt_index(virtual)]; +	if (!(tmp & PSB_PTE_VALID)) { +		ret = -EINVAL; +	} else { +		ret = 0; +		*pfn = tmp >> PAGE_SHIFT; +	} +	psb_mmu_pt_unmap_unlock(pt); +out: +	up_read(&pd->driver->sem); +	return ret; +} diff --git a/drivers/gpu/drm/gma500/mmu.h b/drivers/gpu/drm/gma500/mmu.h new file mode 100644 index 00000000000..e89abec6209 --- /dev/null +++ b/drivers/gpu/drm/gma500/mmu.h @@ -0,0 +1,93 @@ +/************************************************************************** + * Copyright (c) 2007-2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + **************************************************************************/ + +#ifndef __MMU_H +#define __MMU_H + +struct psb_mmu_driver { +	/* protects driver- and pd structures. Always take in read mode +	 * before taking the page table spinlock. +	 */ +	struct rw_semaphore sem; + +	/* protects page tables, directory tables and pt tables. +	 * and pt structures. +	 */ +	spinlock_t lock; + +	atomic_t needs_tlbflush; +	atomic_t *msvdx_mmu_invaldc; +	struct psb_mmu_pd *default_pd; +	uint32_t bif_ctrl; +	int has_clflush; +	int clflush_add; +	unsigned long clflush_mask; + +	struct drm_device *dev; +}; + +struct psb_mmu_pd; + +struct psb_mmu_pt { +	struct psb_mmu_pd *pd; +	uint32_t index; +	uint32_t count; +	struct page *p; +	uint32_t *v; +}; + +struct psb_mmu_pd { +	struct psb_mmu_driver *driver; +	int hw_context; +	struct psb_mmu_pt **tables; +	struct page *p; +	struct page *dummy_pt; +	struct page *dummy_page; +	uint32_t pd_mask; +	uint32_t invalid_pde; +	uint32_t invalid_pte; +}; + +extern struct psb_mmu_driver *psb_mmu_driver_init(struct drm_device *dev, +						  int trap_pagefaults, +						  int invalid_type, +						  atomic_t *msvdx_mmu_invaldc); +extern void psb_mmu_driver_takedown(struct psb_mmu_driver *driver); +extern struct psb_mmu_pd *psb_mmu_get_default_pd(struct psb_mmu_driver +						 *driver); +extern struct psb_mmu_pd *psb_mmu_alloc_pd(struct psb_mmu_driver *driver, +					   int trap_pagefaults, +					   int invalid_type); +extern void psb_mmu_free_pagedir(struct psb_mmu_pd *pd); +extern void psb_mmu_flush(struct psb_mmu_driver *driver); +extern void psb_mmu_remove_pfn_sequence(struct psb_mmu_pd *pd, +					unsigned long address, +					uint32_t num_pages); +extern int psb_mmu_insert_pfn_sequence(struct psb_mmu_pd *pd, +				       uint32_t start_pfn, +				       unsigned long address, +				       uint32_t num_pages, int type); +extern int psb_mmu_virtual_to_pfn(struct psb_mmu_pd *pd, uint32_t virtual, +				  unsigned long *pfn); +extern void psb_mmu_set_pd_context(struct psb_mmu_pd *pd, int hw_context); +extern int psb_mmu_insert_pages(struct psb_mmu_pd *pd, struct page **pages, +				unsigned long address, uint32_t num_pages, +				uint32_t desired_tile_stride, +				uint32_t hw_tile_stride, int type); +extern void psb_mmu_remove_pages(struct psb_mmu_pd *pd, +				 unsigned long address, uint32_t num_pages, +				 uint32_t desired_tile_stride, +				 uint32_t hw_tile_stride); + +#endif diff --git a/drivers/gpu/drm/gma500/oaktrail.h b/drivers/gpu/drm/gma500/oaktrail.h new file mode 100644 index 00000000000..30adbbe2302 --- /dev/null +++ b/drivers/gpu/drm/gma500/oaktrail.h @@ -0,0 +1,257 @@ +/************************************************************************** + * Copyright (c) 2007-2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +/* MID device specific descriptors */ + +struct oaktrail_timing_info { +	u16 pixel_clock; +	u8 hactive_lo; +	u8 hblank_lo; +	u8 hblank_hi:4; +	u8 hactive_hi:4; +	u8 vactive_lo; +	u8 vblank_lo; +	u8 vblank_hi:4; +	u8 vactive_hi:4; +	u8 hsync_offset_lo; +	u8 hsync_pulse_width_lo; +	u8 vsync_pulse_width_lo:4; +	u8 vsync_offset_lo:4; +	u8 vsync_pulse_width_hi:2; +	u8 vsync_offset_hi:2; +	u8 hsync_pulse_width_hi:2; +	u8 hsync_offset_hi:2; +	u8 width_mm_lo; +	u8 height_mm_lo; +	u8 height_mm_hi:4; +	u8 width_mm_hi:4; +	u8 hborder; +	u8 vborder; +	u8 unknown0:1; +	u8 hsync_positive:1; +	u8 vsync_positive:1; +	u8 separate_sync:2; +	u8 stereo:1; +	u8 unknown6:1; +	u8 interlaced:1; +} __packed; + +struct gct_r10_timing_info { +	u16 pixel_clock; +	u32 hactive_lo:8; +	u32 hactive_hi:4; +	u32 hblank_lo:8; +	u32 hblank_hi:4; +	u32 hsync_offset_lo:8; +	u16 hsync_offset_hi:2; +	u16 hsync_pulse_width_lo:8; +	u16 hsync_pulse_width_hi:2; +	u16 hsync_positive:1; +	u16 rsvd_1:3; +	u8  vactive_lo:8; +	u16 vactive_hi:4; +	u16 vblank_lo:8; +	u16 vblank_hi:4; +	u16 vsync_offset_lo:4; +	u16 vsync_offset_hi:2; +	u16 vsync_pulse_width_lo:4; +	u16 vsync_pulse_width_hi:2; +	u16 vsync_positive:1; +	u16 rsvd_2:3; +} __packed; + +struct oaktrail_panel_descriptor_v1 { +	u32 Panel_Port_Control; /* 1 dword, Register 0x61180 if LVDS */ +				/* 0x61190 if MIPI */ +	u32 Panel_Power_On_Sequencing;/*1 dword,Register 0x61208,*/ +	u32 Panel_Power_Off_Sequencing;/*1 dword,Register 0x6120C,*/ +	u32 Panel_Power_Cycle_Delay_and_Reference_Divisor;/* 1 dword */ +						/* Register 0x61210 */ +	struct oaktrail_timing_info DTD;/*18 bytes, Standard definition */ +	u16 Panel_Backlight_Inverter_Descriptor;/* 16 bits, as follows */ +				/* Bit 0, Frequency, 15 bits,0 - 32767Hz */ +			/* Bit 15, Polarity, 1 bit, 0: Normal, 1: Inverted */ +	u16 Panel_MIPI_Display_Descriptor; +			/*16 bits, Defined as follows: */ +			/* if MIPI, 0x0000 if LVDS */ +			/* Bit 0, Type, 2 bits, */ +			/* 0: Type-1, */ +			/* 1: Type-2, */ +			/* 2: Type-3, */ +			/* 3: Type-4 */ +			/* Bit 2, Pixel Format, 4 bits */ +			/* Bit0: 16bpp (not supported in LNC), */ +			/* Bit1: 18bpp loosely packed, */ +			/* Bit2: 18bpp packed, */ +			/* Bit3: 24bpp */ +			/* Bit 6, Reserved, 2 bits, 00b */ +			/* Bit 8, Minimum Supported Frame Rate, 6 bits, 0 - 63Hz */ +			/* Bit 14, Reserved, 2 bits, 00b */ +} __packed; + +struct oaktrail_panel_descriptor_v2 { +	u32 Panel_Port_Control; /* 1 dword, Register 0x61180 if LVDS */ +				/* 0x61190 if MIPI */ +	u32 Panel_Power_On_Sequencing;/*1 dword,Register 0x61208,*/ +	u32 Panel_Power_Off_Sequencing;/*1 dword,Register 0x6120C,*/ +	u8 Panel_Power_Cycle_Delay_and_Reference_Divisor;/* 1 byte */ +						/* Register 0x61210 */ +	struct oaktrail_timing_info DTD;/*18 bytes, Standard definition */ +	u16 Panel_Backlight_Inverter_Descriptor;/*16 bits, as follows*/ +				/*Bit 0, Frequency, 16 bits, 0 - 32767Hz*/ +	u8 Panel_Initial_Brightness;/* [7:0] 0 - 100% */ +			/*Bit 7, Polarity, 1 bit,0: Normal, 1: Inverted*/ +	u16 Panel_MIPI_Display_Descriptor; +			/*16 bits, Defined as follows: */ +			/* if MIPI, 0x0000 if LVDS */ +			/* Bit 0, Type, 2 bits, */ +			/* 0: Type-1, */ +			/* 1: Type-2, */ +			/* 2: Type-3, */ +			/* 3: Type-4 */ +			/* Bit 2, Pixel Format, 4 bits */ +			/* Bit0: 16bpp (not supported in LNC), */ +			/* Bit1: 18bpp loosely packed, */ +			/* Bit2: 18bpp packed, */ +			/* Bit3: 24bpp */ +			/* Bit 6, Reserved, 2 bits, 00b */ +			/* Bit 8, Minimum Supported Frame Rate, 6 bits, 0 - 63Hz */ +			/* Bit 14, Reserved, 2 bits, 00b */ +} __packed; + +union oaktrail_panel_rx { +	struct { +		u16 NumberOfLanes:2; /*Num of Lanes, 2 bits,0 = 1 lane,*/ +			/* 1 = 2 lanes, 2 = 3 lanes, 3 = 4 lanes. */ +		u16 MaxLaneFreq:3; /* 0: 100MHz, 1: 200MHz, 2: 300MHz, */ +		/*3: 400MHz, 4: 500MHz, 5: 600MHz, 6: 700MHz, 7: 800MHz.*/ +		u16 SupportedVideoTransferMode:2; /*0: Non-burst only */ +					/* 1: Burst and non-burst */ +					/* 2/3: Reserved */ +		u16 HSClkBehavior:1; /*0: Continuous, 1: Non-continuous*/ +		u16 DuoDisplaySupport:1; /*1 bit,0: No, 1: Yes*/ +		u16 ECC_ChecksumCapabilities:1;/*1 bit,0: No, 1: Yes*/ +		u16 BidirectionalCommunication:1;/*1 bit,0: No, 1: Yes */ +		u16 Rsvd:5;/*5 bits,00000b */ +	} panelrx; +	u16 panel_receiver; +} __packed; + +struct gct_r0 { +	union { /*8 bits,Defined as follows: */ +		struct { +			u8 PanelType:4; /*4 bits, Bit field for panels*/ +					/* 0 - 3: 0 = LVDS, 1 = MIPI*/ +					/*2 bits,Specifies which of the*/ +			u8 BootPanelIndex:2; +					/* 4 panels to use by default*/ +			u8 BootMIPI_DSI_RxIndex:2;/*Specifies which of*/ +					/* the 4 MIPI DSI receivers to use*/ +		} PD; +		u8 PanelDescriptor; +	}; +	struct oaktrail_panel_descriptor_v1 panel[4];/*panel descrs,38 bytes each*/ +	union oaktrail_panel_rx panelrx[4]; /* panel receivers*/ +} __packed; + +struct gct_r1 { +	union { /*8 bits,Defined as follows: */ +		struct { +			u8 PanelType:4; /*4 bits, Bit field for panels*/ +					/* 0 - 3: 0 = LVDS, 1 = MIPI*/ +					/*2 bits,Specifies which of the*/ +			u8 BootPanelIndex:2; +					/* 4 panels to use by default*/ +			u8 BootMIPI_DSI_RxIndex:2;/*Specifies which of*/ +					/* the 4 MIPI DSI receivers to use*/ +		} PD; +		u8 PanelDescriptor; +	}; +	struct oaktrail_panel_descriptor_v2 panel[4];/*panel descrs,38 bytes each*/ +	union oaktrail_panel_rx panelrx[4]; /* panel receivers*/ +} __packed; + +struct gct_r10 { +	struct gct_r10_timing_info DTD; +	u16 Panel_MIPI_Display_Descriptor; +	u16 Panel_MIPI_Receiver_Descriptor; +	u16 Panel_Backlight_Inverter_Descriptor; +	u8 Panel_Initial_Brightness; +	u32 MIPI_Ctlr_Init_ptr; +	u32 MIPI_Panel_Init_ptr; +} __packed; + +struct oaktrail_gct_data { +	u8 bpi; /* boot panel index, number of panel used during boot */ +	u8 pt; /* panel type, 4 bit field, 0=lvds, 1=mipi */ +	struct oaktrail_timing_info DTD; /* timing info for the selected panel */ +	u32 Panel_Port_Control; +	u32 PP_On_Sequencing;/*1 dword,Register 0x61208,*/ +	u32 PP_Off_Sequencing;/*1 dword,Register 0x6120C,*/ +	u32 PP_Cycle_Delay; +	u16 Panel_Backlight_Inverter_Descriptor; +	u16 Panel_MIPI_Display_Descriptor; +} __packed; + +#define MODE_SETTING_IN_CRTC		0x1 +#define MODE_SETTING_IN_ENCODER		0x2 +#define MODE_SETTING_ON_GOING		0x3 +#define MODE_SETTING_IN_DSR		0x4 +#define MODE_SETTING_ENCODER_DONE	0x8 + +/* + *	Moorestown HDMI interfaces + */ + +struct oaktrail_hdmi_dev { +	struct pci_dev *dev; +	void __iomem *regs; +	unsigned int mmio, mmio_len; +	int dpms_mode; +	struct hdmi_i2c_dev *i2c_dev; + +	/* register state */ +	u32 saveDPLL_CTRL; +	u32 saveDPLL_DIV_CTRL; +	u32 saveDPLL_ADJUST; +	u32 saveDPLL_UPDATE; +	u32 saveDPLL_CLK_ENABLE; +	u32 savePCH_HTOTAL_B; +	u32 savePCH_HBLANK_B; +	u32 savePCH_HSYNC_B; +	u32 savePCH_VTOTAL_B; +	u32 savePCH_VBLANK_B; +	u32 savePCH_VSYNC_B; +	u32 savePCH_PIPEBCONF; +	u32 savePCH_PIPEBSRC; +}; + +extern void oaktrail_hdmi_setup(struct drm_device *dev); +extern void oaktrail_hdmi_teardown(struct drm_device *dev); +extern int  oaktrail_hdmi_i2c_init(struct pci_dev *dev); +extern void oaktrail_hdmi_i2c_exit(struct pci_dev *dev); +extern void oaktrail_hdmi_save(struct drm_device *dev); +extern void oaktrail_hdmi_restore(struct drm_device *dev); +extern void oaktrail_hdmi_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev); +extern int oaktrail_crtc_hdmi_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); +extern void oaktrail_crtc_hdmi_dpms(struct drm_crtc *crtc, int mode); + + diff --git a/drivers/gpu/drm/gma500/oaktrail_crtc.c b/drivers/gpu/drm/gma500/oaktrail_crtc.c new file mode 100644 index 00000000000..2de216c2374 --- /dev/null +++ b/drivers/gpu/drm/gma500/oaktrail_crtc.c @@ -0,0 +1,672 @@ +/* + * Copyright © 2009 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/i2c.h> +#include <linux/pm_runtime.h> + +#include <drm/drmP.h> +#include "framebuffer.h" +#include "psb_drv.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "gma_display.h" +#include "power.h" + +#define MRST_LIMIT_LVDS_100L	0 +#define MRST_LIMIT_LVDS_83	1 +#define MRST_LIMIT_LVDS_100	2 +#define MRST_LIMIT_SDVO		3 + +#define MRST_DOT_MIN		  19750 +#define MRST_DOT_MAX		  120000 +#define MRST_M_MIN_100L		    20 +#define MRST_M_MIN_100		    10 +#define MRST_M_MIN_83		    12 +#define MRST_M_MAX_100L		    34 +#define MRST_M_MAX_100		    17 +#define MRST_M_MAX_83		    20 +#define MRST_P1_MIN		    2 +#define MRST_P1_MAX_0		    7 +#define MRST_P1_MAX_1		    8 + +static bool mrst_lvds_find_best_pll(const struct gma_limit_t *limit, +				    struct drm_crtc *crtc, int target, +				    int refclk, struct gma_clock_t *best_clock); + +static bool mrst_sdvo_find_best_pll(const struct gma_limit_t *limit, +				    struct drm_crtc *crtc, int target, +				    int refclk, struct gma_clock_t *best_clock); + +static const struct gma_limit_t mrst_limits[] = { +	{			/* MRST_LIMIT_LVDS_100L */ +	 .dot = {.min = MRST_DOT_MIN, .max = MRST_DOT_MAX}, +	 .m = {.min = MRST_M_MIN_100L, .max = MRST_M_MAX_100L}, +	 .p1 = {.min = MRST_P1_MIN, .max = MRST_P1_MAX_1}, +	 .find_pll = mrst_lvds_find_best_pll, +	 }, +	{			/* MRST_LIMIT_LVDS_83L */ +	 .dot = {.min = MRST_DOT_MIN, .max = MRST_DOT_MAX}, +	 .m = {.min = MRST_M_MIN_83, .max = MRST_M_MAX_83}, +	 .p1 = {.min = MRST_P1_MIN, .max = MRST_P1_MAX_0}, +	 .find_pll = mrst_lvds_find_best_pll, +	 }, +	{			/* MRST_LIMIT_LVDS_100 */ +	 .dot = {.min = MRST_DOT_MIN, .max = MRST_DOT_MAX}, +	 .m = {.min = MRST_M_MIN_100, .max = MRST_M_MAX_100}, +	 .p1 = {.min = MRST_P1_MIN, .max = MRST_P1_MAX_1}, +	 .find_pll = mrst_lvds_find_best_pll, +	 }, +	{			/* MRST_LIMIT_SDVO */ +	 .vco = {.min = 1400000, .max = 2800000}, +	 .n = {.min = 3, .max = 7}, +	 .m = {.min = 80, .max = 137}, +	 .p1 = {.min = 1, .max = 2}, +	 .p2 = {.dot_limit = 200000, .p2_slow = 10, .p2_fast = 10}, +	 .find_pll = mrst_sdvo_find_best_pll, +	 }, +}; + +#define MRST_M_MIN	    10 +static const u32 oaktrail_m_converts[] = { +	0x2B, 0x15, 0x2A, 0x35, 0x1A, 0x0D, 0x26, 0x33, 0x19, 0x2C, +	0x36, 0x3B, 0x1D, 0x2E, 0x37, 0x1B, 0x2D, 0x16, 0x0B, 0x25, +	0x12, 0x09, 0x24, 0x32, 0x39, 0x1c, +}; + +static const struct gma_limit_t *mrst_limit(struct drm_crtc *crtc, +					    int refclk) +{ +	const struct gma_limit_t *limit = NULL; +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (gma_pipe_has_type(crtc, INTEL_OUTPUT_LVDS) +	    || gma_pipe_has_type(crtc, INTEL_OUTPUT_MIPI)) { +		switch (dev_priv->core_freq) { +		case 100: +			limit = &mrst_limits[MRST_LIMIT_LVDS_100L]; +			break; +		case 166: +			limit = &mrst_limits[MRST_LIMIT_LVDS_83]; +			break; +		case 200: +			limit = &mrst_limits[MRST_LIMIT_LVDS_100]; +			break; +		} +	} else if (gma_pipe_has_type(crtc, INTEL_OUTPUT_SDVO)) { +		limit = &mrst_limits[MRST_LIMIT_SDVO]; +	} else { +		limit = NULL; +		dev_err(dev->dev, "mrst_limit Wrong display type.\n"); +	} + +	return limit; +} + +/** Derive the pixel clock for the given refclk and divisors for 8xx chips. */ +static void mrst_lvds_clock(int refclk, struct gma_clock_t *clock) +{ +	clock->dot = (refclk * clock->m) / (14 * clock->p1); +} + +static void mrst_print_pll(struct gma_clock_t *clock) +{ +	DRM_DEBUG_DRIVER("dotclock=%d,  m=%d, m1=%d, m2=%d, n=%d, p1=%d, p2=%d\n", +			 clock->dot, clock->m, clock->m1, clock->m2, clock->n, +			 clock->p1, clock->p2); +} + +static bool mrst_sdvo_find_best_pll(const struct gma_limit_t *limit, +				    struct drm_crtc *crtc, int target, +				    int refclk, struct gma_clock_t *best_clock) +{ +	struct gma_clock_t clock; +	u32 target_vco, actual_freq; +	s32 freq_error, min_error = 100000; + +	memset(best_clock, 0, sizeof(*best_clock)); + +	for (clock.m = limit->m.min; clock.m <= limit->m.max; clock.m++) { +		for (clock.n = limit->n.min; clock.n <= limit->n.max; +		     clock.n++) { +			for (clock.p1 = limit->p1.min; +			     clock.p1 <= limit->p1.max; clock.p1++) { +				/* p2 value always stored in p2_slow on SDVO */ +				clock.p = clock.p1 * limit->p2.p2_slow; +				target_vco = target * clock.p; + +				/* VCO will increase at this point so break */ +				if (target_vco > limit->vco.max) +					break; + +				if (target_vco < limit->vco.min) +					continue; + +				actual_freq = (refclk * clock.m) / +					      (clock.n * clock.p); +				freq_error = 10000 - +					     ((target * 10000) / actual_freq); + +				if (freq_error < -min_error) { +					/* freq_error will start to decrease at +					   this point so break */ +					break; +				} + +				if (freq_error < 0) +					freq_error = -freq_error; + +				if (freq_error < min_error) { +					min_error = freq_error; +					*best_clock = clock; +				} +			} +		} +		if (min_error == 0) +			break; +	} + +	return min_error == 0; +} + +/** + * Returns a set of divisors for the desired target clock with the given refclk, + * or FALSE.  Divisor values are the actual divisors for + */ +static bool mrst_lvds_find_best_pll(const struct gma_limit_t *limit, +				    struct drm_crtc *crtc, int target, +				    int refclk, struct gma_clock_t *best_clock) +{ +	struct gma_clock_t clock; +	int err = target; + +	memset(best_clock, 0, sizeof(*best_clock)); + +	for (clock.m = limit->m.min; clock.m <= limit->m.max; clock.m++) { +		for (clock.p1 = limit->p1.min; clock.p1 <= limit->p1.max; +		     clock.p1++) { +			int this_err; + +			mrst_lvds_clock(refclk, &clock); + +			this_err = abs(clock.dot - target); +			if (this_err < err) { +				*best_clock = clock; +				err = this_err; +			} +		} +	} +	return err != target; +} + +/** + * Sets the power management mode of the pipe and plane. + * + * This code should probably grow support for turning the cursor off and back + * on appropriately at the same time as we're turning the pipe off/on. + */ +static void oaktrail_crtc_dpms(struct drm_crtc *crtc, int mode) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	u32 temp; +	int i; +	int need_aux = gma_pipe_has_type(crtc, INTEL_OUTPUT_SDVO) ? 1 : 0; + +	if (gma_pipe_has_type(crtc, INTEL_OUTPUT_HDMI)) { +		oaktrail_crtc_hdmi_dpms(crtc, mode); +		return; +	} + +	if (!gma_power_begin(dev, true)) +		return; + +	/* XXX: When our outputs are all unaware of DPMS modes other than off +	 * and on, we should map those modes to DRM_MODE_DPMS_OFF in the CRTC. +	 */ +	switch (mode) { +	case DRM_MODE_DPMS_ON: +	case DRM_MODE_DPMS_STANDBY: +	case DRM_MODE_DPMS_SUSPEND: +		for (i = 0; i <= need_aux; i++) { +			/* Enable the DPLL */ +			temp = REG_READ_WITH_AUX(map->dpll, i); +			if ((temp & DPLL_VCO_ENABLE) == 0) { +				REG_WRITE_WITH_AUX(map->dpll, temp, i); +				REG_READ_WITH_AUX(map->dpll, i); +				/* Wait for the clocks to stabilize. */ +				udelay(150); +				REG_WRITE_WITH_AUX(map->dpll, +						   temp | DPLL_VCO_ENABLE, i); +				REG_READ_WITH_AUX(map->dpll, i); +				/* Wait for the clocks to stabilize. */ +				udelay(150); +				REG_WRITE_WITH_AUX(map->dpll, +						   temp | DPLL_VCO_ENABLE, i); +				REG_READ_WITH_AUX(map->dpll, i); +				/* Wait for the clocks to stabilize. */ +				udelay(150); +			} + +			/* Enable the pipe */ +			temp = REG_READ_WITH_AUX(map->conf, i); +			if ((temp & PIPEACONF_ENABLE) == 0) { +				REG_WRITE_WITH_AUX(map->conf, +						   temp | PIPEACONF_ENABLE, i); +			} + +			/* Enable the plane */ +			temp = REG_READ_WITH_AUX(map->cntr, i); +			if ((temp & DISPLAY_PLANE_ENABLE) == 0) { +				REG_WRITE_WITH_AUX(map->cntr, +						   temp | DISPLAY_PLANE_ENABLE, +						   i); +				/* Flush the plane changes */ +				REG_WRITE_WITH_AUX(map->base, +					REG_READ_WITH_AUX(map->base, i), i); +			} + +		} +		gma_crtc_load_lut(crtc); + +		/* Give the overlay scaler a chance to enable +		   if it's on this pipe */ +		/* psb_intel_crtc_dpms_video(crtc, true); TODO */ +		break; +	case DRM_MODE_DPMS_OFF: +		/* Give the overlay scaler a chance to disable +		 * if it's on this pipe */ +		/* psb_intel_crtc_dpms_video(crtc, FALSE); TODO */ + +		for (i = 0; i <= need_aux; i++) { +			/* Disable the VGA plane that we never use */ +			REG_WRITE_WITH_AUX(VGACNTRL, VGA_DISP_DISABLE, i); +			/* Disable display plane */ +			temp = REG_READ_WITH_AUX(map->cntr, i); +			if ((temp & DISPLAY_PLANE_ENABLE) != 0) { +				REG_WRITE_WITH_AUX(map->cntr, +					temp & ~DISPLAY_PLANE_ENABLE, i); +				/* Flush the plane changes */ +				REG_WRITE_WITH_AUX(map->base, +						   REG_READ(map->base), i); +				REG_READ_WITH_AUX(map->base, i); +			} + +			/* Next, disable display pipes */ +			temp = REG_READ_WITH_AUX(map->conf, i); +			if ((temp & PIPEACONF_ENABLE) != 0) { +				REG_WRITE_WITH_AUX(map->conf, +						   temp & ~PIPEACONF_ENABLE, i); +				REG_READ_WITH_AUX(map->conf, i); +			} +			/* Wait for for the pipe disable to take effect. */ +			gma_wait_for_vblank(dev); + +			temp = REG_READ_WITH_AUX(map->dpll, i); +			if ((temp & DPLL_VCO_ENABLE) != 0) { +				REG_WRITE_WITH_AUX(map->dpll, +						   temp & ~DPLL_VCO_ENABLE, i); +				REG_READ_WITH_AUX(map->dpll, i); +			} + +			/* Wait for the clocks to turn off. */ +			udelay(150); +		} +		break; +	} + +	/* Set FIFO Watermarks (values taken from EMGD) */ +	REG_WRITE(DSPARB, 0x3f80); +	REG_WRITE(DSPFW1, 0x3f8f0404); +	REG_WRITE(DSPFW2, 0x04040f04); +	REG_WRITE(DSPFW3, 0x0); +	REG_WRITE(DSPFW4, 0x04040404); +	REG_WRITE(DSPFW5, 0x04040404); +	REG_WRITE(DSPFW6, 0x78); +	REG_WRITE(DSPCHICKENBIT, REG_READ(DSPCHICKENBIT) | 0xc040); + +	gma_power_end(dev); +} + +/** + * Return the pipe currently connected to the panel fitter, + * or -1 if the panel fitter is not present or not in use + */ +static int oaktrail_panel_fitter_pipe(struct drm_device *dev) +{ +	u32 pfit_control; + +	pfit_control = REG_READ(PFIT_CONTROL); + +	/* See if the panel fitter is in use */ +	if ((pfit_control & PFIT_ENABLE) == 0) +		return -1; +	return (pfit_control >> 29) & 3; +} + +static int oaktrail_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 drm_device *dev = crtc->dev; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct drm_psb_private *dev_priv = dev->dev_private; +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	int refclk = 0; +	struct gma_clock_t clock; +	const struct gma_limit_t *limit; +	u32 dpll = 0, fp = 0, dspcntr, pipeconf; +	bool ok, is_sdvo = false; +	bool is_lvds = false; +	bool is_mipi = false; +	struct drm_mode_config *mode_config = &dev->mode_config; +	struct gma_encoder *gma_encoder = NULL; +	uint64_t scalingType = DRM_MODE_SCALE_FULLSCREEN; +	struct drm_connector *connector; +	int i; +	int need_aux = gma_pipe_has_type(crtc, INTEL_OUTPUT_SDVO) ? 1 : 0; + +	if (gma_pipe_has_type(crtc, INTEL_OUTPUT_HDMI)) +		return oaktrail_crtc_hdmi_mode_set(crtc, mode, adjusted_mode, x, y, old_fb); + +	if (!gma_power_begin(dev, true)) +		return 0; + +	memcpy(&gma_crtc->saved_mode, +		mode, +		sizeof(struct drm_display_mode)); +	memcpy(&gma_crtc->saved_adjusted_mode, +		adjusted_mode, +		sizeof(struct drm_display_mode)); + +	list_for_each_entry(connector, &mode_config->connector_list, head) { +		if (!connector->encoder || connector->encoder->crtc != crtc) +			continue; + +		gma_encoder = gma_attached_encoder(connector); + +		switch (gma_encoder->type) { +		case INTEL_OUTPUT_LVDS: +			is_lvds = true; +			break; +		case INTEL_OUTPUT_SDVO: +			is_sdvo = true; +			break; +		case INTEL_OUTPUT_MIPI: +			is_mipi = true; +			break; +		} +	} + +	/* Disable the VGA plane that we never use */ +	for (i = 0; i <= need_aux; i++) +		REG_WRITE_WITH_AUX(VGACNTRL, VGA_DISP_DISABLE, i); + +	/* Disable the panel fitter if it was on our pipe */ +	if (oaktrail_panel_fitter_pipe(dev) == pipe) +		REG_WRITE(PFIT_CONTROL, 0); + +	for (i = 0; i <= need_aux; i++) { +		REG_WRITE_WITH_AUX(map->src, ((mode->crtc_hdisplay - 1) << 16) | +					     (mode->crtc_vdisplay - 1), i); +	} + +	if (gma_encoder) +		drm_object_property_get_value(&connector->base, +			dev->mode_config.scaling_mode_property, &scalingType); + +	if (scalingType == DRM_MODE_SCALE_NO_SCALE) { +		/* Moorestown doesn't have register support for centering so +		 * we need to mess with the h/vblank and h/vsync start and +		 * ends to get centering */ +		int offsetX = 0, offsetY = 0; + +		offsetX = (adjusted_mode->crtc_hdisplay - +			   mode->crtc_hdisplay) / 2; +		offsetY = (adjusted_mode->crtc_vdisplay - +			   mode->crtc_vdisplay) / 2; + +		for (i = 0; i <= need_aux; i++) { +			REG_WRITE_WITH_AUX(map->htotal, (mode->crtc_hdisplay - 1) | +				((adjusted_mode->crtc_htotal - 1) << 16), i); +			REG_WRITE_WITH_AUX(map->vtotal, (mode->crtc_vdisplay - 1) | +				((adjusted_mode->crtc_vtotal - 1) << 16), i); +			REG_WRITE_WITH_AUX(map->hblank, +				(adjusted_mode->crtc_hblank_start - offsetX - 1) | +				((adjusted_mode->crtc_hblank_end - offsetX - 1) << 16), i); +			REG_WRITE_WITH_AUX(map->hsync, +				(adjusted_mode->crtc_hsync_start - offsetX - 1) | +				((adjusted_mode->crtc_hsync_end - offsetX - 1) << 16), i); +			REG_WRITE_WITH_AUX(map->vblank, +				(adjusted_mode->crtc_vblank_start - offsetY - 1) | +				((adjusted_mode->crtc_vblank_end - offsetY - 1) << 16), i); +			REG_WRITE_WITH_AUX(map->vsync, +				(adjusted_mode->crtc_vsync_start - offsetY - 1) | +				((adjusted_mode->crtc_vsync_end - offsetY - 1) << 16), i); +		} +	} else { +		for (i = 0; i <= need_aux; i++) { +			REG_WRITE_WITH_AUX(map->htotal, (adjusted_mode->crtc_hdisplay - 1) | +				((adjusted_mode->crtc_htotal - 1) << 16), i); +			REG_WRITE_WITH_AUX(map->vtotal, (adjusted_mode->crtc_vdisplay - 1) | +				((adjusted_mode->crtc_vtotal - 1) << 16), i); +			REG_WRITE_WITH_AUX(map->hblank, (adjusted_mode->crtc_hblank_start - 1) | +				((adjusted_mode->crtc_hblank_end - 1) << 16), i); +			REG_WRITE_WITH_AUX(map->hsync, (adjusted_mode->crtc_hsync_start - 1) | +				((adjusted_mode->crtc_hsync_end - 1) << 16), i); +			REG_WRITE_WITH_AUX(map->vblank, (adjusted_mode->crtc_vblank_start - 1) | +				((adjusted_mode->crtc_vblank_end - 1) << 16), i); +			REG_WRITE_WITH_AUX(map->vsync, (adjusted_mode->crtc_vsync_start - 1) | +				((adjusted_mode->crtc_vsync_end - 1) << 16), i); +		} +	} + +	/* Flush the plane changes */ +	{ +		struct drm_crtc_helper_funcs *crtc_funcs = +		    crtc->helper_private; +		crtc_funcs->mode_set_base(crtc, x, y, old_fb); +	} + +	/* setup pipeconf */ +	pipeconf = REG_READ(map->conf); + +	/* Set up the display plane register */ +	dspcntr = REG_READ(map->cntr); +	dspcntr |= DISPPLANE_GAMMA_ENABLE; + +	if (pipe == 0) +		dspcntr |= DISPPLANE_SEL_PIPE_A; +	else +		dspcntr |= DISPPLANE_SEL_PIPE_B; + +	if (is_mipi) +		goto oaktrail_crtc_mode_set_exit; + + +	dpll = 0;		/*BIT16 = 0 for 100MHz reference */ + +	refclk = is_sdvo ? 96000 : dev_priv->core_freq * 1000; +	limit = mrst_limit(crtc, refclk); +	ok = limit->find_pll(limit, crtc, adjusted_mode->clock, +			     refclk, &clock); + +	if (is_sdvo) { +		/* Convert calculated values to register values */ +		clock.p1 = (1L << (clock.p1 - 1)); +		clock.m -= 2; +		clock.n = (1L << (clock.n - 1)); +	} + +	if (!ok) +		DRM_ERROR("Failed to find proper PLL settings"); + +	mrst_print_pll(&clock); + +	if (is_sdvo) +		fp = clock.n << 16 | clock.m; +	else +		fp = oaktrail_m_converts[(clock.m - MRST_M_MIN)] << 8; + +	dpll |= DPLL_VGA_MODE_DIS; + + +	dpll |= DPLL_VCO_ENABLE; + +	if (is_lvds) +		dpll |= DPLLA_MODE_LVDS; +	else +		dpll |= DPLLB_MODE_DAC_SERIAL; + +	if (is_sdvo) { +		int sdvo_pixel_multiply = +		    adjusted_mode->clock / mode->clock; + +		dpll |= DPLL_DVO_HIGH_SPEED; +		dpll |= +		    (sdvo_pixel_multiply - +		     1) << SDVO_MULTIPLIER_SHIFT_HIRES; +	} + + +	/* compute bitmask from p1 value */ +	if (is_sdvo) +		dpll |= clock.p1 << 16; // dpll |= (1 << (clock.p1 - 1)) << 16; +	else +		dpll |= (1 << (clock.p1 - 2)) << 17; + +	dpll |= DPLL_VCO_ENABLE; + +	if (dpll & DPLL_VCO_ENABLE) { +		for (i = 0; i <= need_aux; i++) { +			REG_WRITE_WITH_AUX(map->fp0, fp, i); +			REG_WRITE_WITH_AUX(map->dpll, dpll & ~DPLL_VCO_ENABLE, i); +			REG_READ_WITH_AUX(map->dpll, i); +			/* Check the DPLLA lock bit PIPEACONF[29] */ +			udelay(150); +		} +	} + +	for (i = 0; i <= need_aux; i++) { +		REG_WRITE_WITH_AUX(map->fp0, fp, i); +		REG_WRITE_WITH_AUX(map->dpll, dpll, i); +		REG_READ_WITH_AUX(map->dpll, i); +		/* Wait for the clocks to stabilize. */ +		udelay(150); + +		/* write it again -- the BIOS does, after all */ +		REG_WRITE_WITH_AUX(map->dpll, dpll, i); +		REG_READ_WITH_AUX(map->dpll, i); +		/* Wait for the clocks to stabilize. */ +		udelay(150); + +		REG_WRITE_WITH_AUX(map->conf, pipeconf, i); +		REG_READ_WITH_AUX(map->conf, i); +		gma_wait_for_vblank(dev); + +		REG_WRITE_WITH_AUX(map->cntr, dspcntr, i); +		gma_wait_for_vblank(dev); +	} + +oaktrail_crtc_mode_set_exit: +	gma_power_end(dev); +	return 0; +} + +static int oaktrail_pipe_set_base(struct drm_crtc *crtc, +			    int x, int y, struct drm_framebuffer *old_fb) +{ +	struct drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct psb_framebuffer *psbfb = to_psb_fb(crtc->primary->fb); +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	unsigned long start, offset; + +	u32 dspcntr; +	int ret = 0; + +	/* no fb bound */ +	if (!crtc->primary->fb) { +		dev_dbg(dev->dev, "No FB bound\n"); +		return 0; +	} + +	if (!gma_power_begin(dev, true)) +		return 0; + +	start = psbfb->gtt->offset; +	offset = y * crtc->primary->fb->pitches[0] + x * (crtc->primary->fb->bits_per_pixel / 8); + +	REG_WRITE(map->stride, crtc->primary->fb->pitches[0]); + +	dspcntr = REG_READ(map->cntr); +	dspcntr &= ~DISPPLANE_PIXFORMAT_MASK; + +	switch (crtc->primary->fb->bits_per_pixel) { +	case 8: +		dspcntr |= DISPPLANE_8BPP; +		break; +	case 16: +		if (crtc->primary->fb->depth == 15) +			dspcntr |= DISPPLANE_15_16BPP; +		else +			dspcntr |= DISPPLANE_16BPP; +		break; +	case 24: +	case 32: +		dspcntr |= DISPPLANE_32BPP_NO_ALPHA; +		break; +	default: +		dev_err(dev->dev, "Unknown color depth\n"); +		ret = -EINVAL; +		goto pipe_set_base_exit; +	} +	REG_WRITE(map->cntr, dspcntr); + +	REG_WRITE(map->base, offset); +	REG_READ(map->base); +	REG_WRITE(map->surf, start); +	REG_READ(map->surf); + +pipe_set_base_exit: +	gma_power_end(dev); +	return ret; +} + +const struct drm_crtc_helper_funcs oaktrail_helper_funcs = { +	.dpms = oaktrail_crtc_dpms, +	.mode_fixup = gma_crtc_mode_fixup, +	.mode_set = oaktrail_crtc_mode_set, +	.mode_set_base = oaktrail_pipe_set_base, +	.prepare = gma_crtc_prepare, +	.commit = gma_crtc_commit, +}; + +/* Not used yet */ +const struct gma_clock_funcs mrst_clock_funcs = { +	.clock = mrst_lvds_clock, +	.limit = mrst_limit, +	.pll_is_valid = gma_pll_is_valid, +}; diff --git a/drivers/gpu/drm/gma500/oaktrail_device.c b/drivers/gpu/drm/gma500/oaktrail_device.c new file mode 100644 index 00000000000..368a03ae301 --- /dev/null +++ b/drivers/gpu/drm/gma500/oaktrail_device.c @@ -0,0 +1,575 @@ +/************************************************************************** + * Copyright (c) 2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +#include <linux/backlight.h> +#include <linux/module.h> +#include <linux/dmi.h> +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/gma_drm.h> +#include "psb_drv.h" +#include "psb_reg.h" +#include "psb_intel_reg.h" +#include <asm/intel-mid.h> +#include <asm/intel_scu_ipc.h> +#include "mid_bios.h" +#include "intel_bios.h" + +static int oaktrail_output_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	if (dev_priv->iLVDS_enable) +		oaktrail_lvds_init(dev, &dev_priv->mode_dev); +	else +		dev_err(dev->dev, "DSI is not supported\n"); +	if (dev_priv->hdmi_priv) +		oaktrail_hdmi_init(dev, &dev_priv->mode_dev); + +	psb_intel_sdvo_init(dev, SDVOB); + +	return 0; +} + +/* + *	Provide the low level interfaces for the Moorestown backlight + */ + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE + +#define MRST_BLC_MAX_PWM_REG_FREQ	    0xFFFF +#define BLC_PWM_PRECISION_FACTOR 100	/* 10000000 */ +#define BLC_PWM_FREQ_CALC_CONSTANT 32 +#define MHz 1000000 +#define BLC_ADJUSTMENT_MAX 100 + +static struct backlight_device *oaktrail_backlight_device; +static int oaktrail_brightness; + +static int oaktrail_set_brightness(struct backlight_device *bd) +{ +	struct drm_device *dev = bl_get_data(oaktrail_backlight_device); +	struct drm_psb_private *dev_priv = dev->dev_private; +	int level = bd->props.brightness; +	u32 blc_pwm_ctl; +	u32 max_pwm_blc; + +	/* Percentage 1-100% being valid */ +	if (level < 1) +		level = 1; + +	if (gma_power_begin(dev, 0)) { +		/* Calculate and set the brightness value */ +		max_pwm_blc = REG_READ(BLC_PWM_CTL) >> 16; +		blc_pwm_ctl = level * max_pwm_blc / 100; + +		/* Adjust the backlight level with the percent in +		 * dev_priv->blc_adj1; +		 */ +		blc_pwm_ctl = blc_pwm_ctl * dev_priv->blc_adj1; +		blc_pwm_ctl = blc_pwm_ctl / 100; + +		/* Adjust the backlight level with the percent in +		 * dev_priv->blc_adj2; +		 */ +		blc_pwm_ctl = blc_pwm_ctl * dev_priv->blc_adj2; +		blc_pwm_ctl = blc_pwm_ctl / 100; + +		/* force PWM bit on */ +		REG_WRITE(BLC_PWM_CTL2, (0x80000000 | REG_READ(BLC_PWM_CTL2))); +		REG_WRITE(BLC_PWM_CTL, (max_pwm_blc << 16) | blc_pwm_ctl); +		gma_power_end(dev); +	} +	oaktrail_brightness = level; +	return 0; +} + +static int oaktrail_get_brightness(struct backlight_device *bd) +{ +	/* return locally cached var instead of HW read (due to DPST etc.) */ +	/* FIXME: ideally return actual value in case firmware fiddled with +	   it */ +	return oaktrail_brightness; +} + +static int device_backlight_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	unsigned long core_clock; +	u16 bl_max_freq; +	uint32_t value; +	uint32_t blc_pwm_precision_factor; + +	dev_priv->blc_adj1 = BLC_ADJUSTMENT_MAX; +	dev_priv->blc_adj2 = BLC_ADJUSTMENT_MAX; +	bl_max_freq = 256; +	/* this needs to be set elsewhere */ +	blc_pwm_precision_factor = BLC_PWM_PRECISION_FACTOR; + +	core_clock = dev_priv->core_freq; + +	value = (core_clock * MHz) / BLC_PWM_FREQ_CALC_CONSTANT; +	value *= blc_pwm_precision_factor; +	value /= bl_max_freq; +	value /= blc_pwm_precision_factor; + +	if (value > (unsigned long long)MRST_BLC_MAX_PWM_REG_FREQ) +			return -ERANGE; + +	if (gma_power_begin(dev, false)) { +		REG_WRITE(BLC_PWM_CTL2, (0x80000000 | REG_READ(BLC_PWM_CTL2))); +		REG_WRITE(BLC_PWM_CTL, value | (value << 16)); +		gma_power_end(dev); +	} +	return 0; +} + +static const struct backlight_ops oaktrail_ops = { +	.get_brightness = oaktrail_get_brightness, +	.update_status  = oaktrail_set_brightness, +}; + +static int oaktrail_backlight_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	int ret; +	struct backlight_properties props; + +	memset(&props, 0, sizeof(struct backlight_properties)); +	props.max_brightness = 100; +	props.type = BACKLIGHT_PLATFORM; + +	oaktrail_backlight_device = backlight_device_register("oaktrail-bl", +				NULL, (void *)dev, &oaktrail_ops, &props); + +	if (IS_ERR(oaktrail_backlight_device)) +		return PTR_ERR(oaktrail_backlight_device); + +	ret = device_backlight_init(dev); +	if (ret < 0) { +		backlight_device_unregister(oaktrail_backlight_device); +		return ret; +	} +	oaktrail_backlight_device->props.brightness = 100; +	oaktrail_backlight_device->props.max_brightness = 100; +	backlight_update_status(oaktrail_backlight_device); +	dev_priv->backlight_device = oaktrail_backlight_device; +	return 0; +} + +#endif + +/* + *	Provide the Moorestown specific chip logic and low level methods + *	for power management + */ + +/** + *	oaktrail_save_display_registers	-	save registers lost on suspend + *	@dev: our DRM device + * + *	Save the state we need in order to be able to restore the interface + *	upon resume from suspend + */ +static int oaktrail_save_display_registers(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_save_area *regs = &dev_priv->regs; +	struct psb_pipe *p = ®s->pipe[0]; +	int i; +	u32 pp_stat; + +	/* Display arbitration control + watermarks */ +	regs->psb.saveDSPARB = PSB_RVDC32(DSPARB); +	regs->psb.saveDSPFW1 = PSB_RVDC32(DSPFW1); +	regs->psb.saveDSPFW2 = PSB_RVDC32(DSPFW2); +	regs->psb.saveDSPFW3 = PSB_RVDC32(DSPFW3); +	regs->psb.saveDSPFW4 = PSB_RVDC32(DSPFW4); +	regs->psb.saveDSPFW5 = PSB_RVDC32(DSPFW5); +	regs->psb.saveDSPFW6 = PSB_RVDC32(DSPFW6); +	regs->psb.saveCHICKENBIT = PSB_RVDC32(DSPCHICKENBIT); + +	/* Pipe & plane A info */ +	p->conf = PSB_RVDC32(PIPEACONF); +	p->src = PSB_RVDC32(PIPEASRC); +	p->fp0 = PSB_RVDC32(MRST_FPA0); +	p->fp1 = PSB_RVDC32(MRST_FPA1); +	p->dpll = PSB_RVDC32(MRST_DPLL_A); +	p->htotal = PSB_RVDC32(HTOTAL_A); +	p->hblank = PSB_RVDC32(HBLANK_A); +	p->hsync = PSB_RVDC32(HSYNC_A); +	p->vtotal = PSB_RVDC32(VTOTAL_A); +	p->vblank = PSB_RVDC32(VBLANK_A); +	p->vsync = PSB_RVDC32(VSYNC_A); +	regs->psb.saveBCLRPAT_A = PSB_RVDC32(BCLRPAT_A); +	p->cntr = PSB_RVDC32(DSPACNTR); +	p->stride = PSB_RVDC32(DSPASTRIDE); +	p->addr = PSB_RVDC32(DSPABASE); +	p->surf = PSB_RVDC32(DSPASURF); +	p->linoff = PSB_RVDC32(DSPALINOFF); +	p->tileoff = PSB_RVDC32(DSPATILEOFF); + +	/* Save cursor regs */ +	regs->psb.saveDSPACURSOR_CTRL = PSB_RVDC32(CURACNTR); +	regs->psb.saveDSPACURSOR_BASE = PSB_RVDC32(CURABASE); +	regs->psb.saveDSPACURSOR_POS = PSB_RVDC32(CURAPOS); + +	/* Save palette (gamma) */ +	for (i = 0; i < 256; i++) +		p->palette[i] = PSB_RVDC32(PALETTE_A + (i << 2)); + +	if (dev_priv->hdmi_priv) +		oaktrail_hdmi_save(dev); + +	/* Save performance state */ +	regs->psb.savePERF_MODE = PSB_RVDC32(MRST_PERF_MODE); + +	/* LVDS state */ +	regs->psb.savePP_CONTROL = PSB_RVDC32(PP_CONTROL); +	regs->psb.savePFIT_PGM_RATIOS = PSB_RVDC32(PFIT_PGM_RATIOS); +	regs->psb.savePFIT_AUTO_RATIOS = PSB_RVDC32(PFIT_AUTO_RATIOS); +	regs->saveBLC_PWM_CTL = PSB_RVDC32(BLC_PWM_CTL); +	regs->saveBLC_PWM_CTL2 = PSB_RVDC32(BLC_PWM_CTL2); +	regs->psb.saveLVDS = PSB_RVDC32(LVDS); +	regs->psb.savePFIT_CONTROL = PSB_RVDC32(PFIT_CONTROL); +	regs->psb.savePP_ON_DELAYS = PSB_RVDC32(LVDSPP_ON); +	regs->psb.savePP_OFF_DELAYS = PSB_RVDC32(LVDSPP_OFF); +	regs->psb.savePP_DIVISOR = PSB_RVDC32(PP_CYCLE); + +	/* HW overlay */ +	regs->psb.saveOV_OVADD = PSB_RVDC32(OV_OVADD); +	regs->psb.saveOV_OGAMC0 = PSB_RVDC32(OV_OGAMC0); +	regs->psb.saveOV_OGAMC1 = PSB_RVDC32(OV_OGAMC1); +	regs->psb.saveOV_OGAMC2 = PSB_RVDC32(OV_OGAMC2); +	regs->psb.saveOV_OGAMC3 = PSB_RVDC32(OV_OGAMC3); +	regs->psb.saveOV_OGAMC4 = PSB_RVDC32(OV_OGAMC4); +	regs->psb.saveOV_OGAMC5 = PSB_RVDC32(OV_OGAMC5); + +	/* DPST registers */ +	regs->psb.saveHISTOGRAM_INT_CONTROL_REG = +					PSB_RVDC32(HISTOGRAM_INT_CONTROL); +	regs->psb.saveHISTOGRAM_LOGIC_CONTROL_REG = +					PSB_RVDC32(HISTOGRAM_LOGIC_CONTROL); +	regs->psb.savePWM_CONTROL_LOGIC = PSB_RVDC32(PWM_CONTROL_LOGIC); + +	if (dev_priv->iLVDS_enable) { +		/* Shut down the panel */ +		PSB_WVDC32(0, PP_CONTROL); + +		do { +			pp_stat = PSB_RVDC32(PP_STATUS); +		} while (pp_stat & 0x80000000); + +		/* Turn off the plane */ +		PSB_WVDC32(0x58000000, DSPACNTR); +		/* Trigger the plane disable */ +		PSB_WVDC32(0, DSPASURF); + +		/* Wait ~4 ticks */ +		msleep(4); + +		/* Turn off pipe */ +		PSB_WVDC32(0x0, PIPEACONF); +		/* Wait ~8 ticks */ +		msleep(8); + +		/* Turn off PLLs */ +		PSB_WVDC32(0, MRST_DPLL_A); +	} +	return 0; +} + +/** + *	oaktrail_restore_display_registers	-	restore lost register state + *	@dev: our DRM device + * + *	Restore register state that was lost during suspend and resume. + */ +static int oaktrail_restore_display_registers(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_save_area *regs = &dev_priv->regs; +	struct psb_pipe *p = ®s->pipe[0]; +	u32 pp_stat; +	int i; + +	/* Display arbitration + watermarks */ +	PSB_WVDC32(regs->psb.saveDSPARB, DSPARB); +	PSB_WVDC32(regs->psb.saveDSPFW1, DSPFW1); +	PSB_WVDC32(regs->psb.saveDSPFW2, DSPFW2); +	PSB_WVDC32(regs->psb.saveDSPFW3, DSPFW3); +	PSB_WVDC32(regs->psb.saveDSPFW4, DSPFW4); +	PSB_WVDC32(regs->psb.saveDSPFW5, DSPFW5); +	PSB_WVDC32(regs->psb.saveDSPFW6, DSPFW6); +	PSB_WVDC32(regs->psb.saveCHICKENBIT, DSPCHICKENBIT); + +	/* Make sure VGA plane is off. it initializes to on after reset!*/ +	PSB_WVDC32(0x80000000, VGACNTRL); + +	/* set the plls */ +	PSB_WVDC32(p->fp0, MRST_FPA0); +	PSB_WVDC32(p->fp1, MRST_FPA1); + +	/* Actually enable it */ +	PSB_WVDC32(p->dpll, MRST_DPLL_A); +	DRM_UDELAY(150); + +	/* Restore mode */ +	PSB_WVDC32(p->htotal, HTOTAL_A); +	PSB_WVDC32(p->hblank, HBLANK_A); +	PSB_WVDC32(p->hsync, HSYNC_A); +	PSB_WVDC32(p->vtotal, VTOTAL_A); +	PSB_WVDC32(p->vblank, VBLANK_A); +	PSB_WVDC32(p->vsync, VSYNC_A); +	PSB_WVDC32(p->src, PIPEASRC); +	PSB_WVDC32(regs->psb.saveBCLRPAT_A, BCLRPAT_A); + +	/* Restore performance mode*/ +	PSB_WVDC32(regs->psb.savePERF_MODE, MRST_PERF_MODE); + +	/* Enable the pipe*/ +	if (dev_priv->iLVDS_enable) +		PSB_WVDC32(p->conf, PIPEACONF); + +	/* Set up the plane*/ +	PSB_WVDC32(p->linoff, DSPALINOFF); +	PSB_WVDC32(p->stride, DSPASTRIDE); +	PSB_WVDC32(p->tileoff, DSPATILEOFF); + +	/* Enable the plane */ +	PSB_WVDC32(p->cntr, DSPACNTR); +	PSB_WVDC32(p->surf, DSPASURF); + +	/* Enable Cursor A */ +	PSB_WVDC32(regs->psb.saveDSPACURSOR_CTRL, CURACNTR); +	PSB_WVDC32(regs->psb.saveDSPACURSOR_POS, CURAPOS); +	PSB_WVDC32(regs->psb.saveDSPACURSOR_BASE, CURABASE); + +	/* Restore palette (gamma) */ +	for (i = 0; i < 256; i++) +		PSB_WVDC32(p->palette[i], PALETTE_A + (i << 2)); + +	if (dev_priv->hdmi_priv) +		oaktrail_hdmi_restore(dev); + +	if (dev_priv->iLVDS_enable) { +		PSB_WVDC32(regs->saveBLC_PWM_CTL2, BLC_PWM_CTL2); +		PSB_WVDC32(regs->psb.saveLVDS, LVDS); /*port 61180h*/ +		PSB_WVDC32(regs->psb.savePFIT_CONTROL, PFIT_CONTROL); +		PSB_WVDC32(regs->psb.savePFIT_PGM_RATIOS, PFIT_PGM_RATIOS); +		PSB_WVDC32(regs->psb.savePFIT_AUTO_RATIOS, PFIT_AUTO_RATIOS); +		PSB_WVDC32(regs->saveBLC_PWM_CTL, BLC_PWM_CTL); +		PSB_WVDC32(regs->psb.savePP_ON_DELAYS, LVDSPP_ON); +		PSB_WVDC32(regs->psb.savePP_OFF_DELAYS, LVDSPP_OFF); +		PSB_WVDC32(regs->psb.savePP_DIVISOR, PP_CYCLE); +		PSB_WVDC32(regs->psb.savePP_CONTROL, PP_CONTROL); +	} + +	/* Wait for cycle delay */ +	do { +		pp_stat = PSB_RVDC32(PP_STATUS); +	} while (pp_stat & 0x08000000); + +	/* Wait for panel power up */ +	do { +		pp_stat = PSB_RVDC32(PP_STATUS); +	} while (pp_stat & 0x10000000); + +	/* Restore HW overlay */ +	PSB_WVDC32(regs->psb.saveOV_OVADD, OV_OVADD); +	PSB_WVDC32(regs->psb.saveOV_OGAMC0, OV_OGAMC0); +	PSB_WVDC32(regs->psb.saveOV_OGAMC1, OV_OGAMC1); +	PSB_WVDC32(regs->psb.saveOV_OGAMC2, OV_OGAMC2); +	PSB_WVDC32(regs->psb.saveOV_OGAMC3, OV_OGAMC3); +	PSB_WVDC32(regs->psb.saveOV_OGAMC4, OV_OGAMC4); +	PSB_WVDC32(regs->psb.saveOV_OGAMC5, OV_OGAMC5); + +	/* DPST registers */ +	PSB_WVDC32(regs->psb.saveHISTOGRAM_INT_CONTROL_REG, +						HISTOGRAM_INT_CONTROL); +	PSB_WVDC32(regs->psb.saveHISTOGRAM_LOGIC_CONTROL_REG, +						HISTOGRAM_LOGIC_CONTROL); +	PSB_WVDC32(regs->psb.savePWM_CONTROL_LOGIC, PWM_CONTROL_LOGIC); + +	return 0; +} + +/** + *	oaktrail_power_down	-	power down the display island + *	@dev: our DRM device + * + *	Power down the display interface of our device + */ +static int oaktrail_power_down(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 pwr_mask ; +	u32 pwr_sts; + +	pwr_mask = PSB_PWRGT_DISPLAY_MASK; +	outl(pwr_mask, dev_priv->ospm_base + PSB_PM_SSC); + +	while (true) { +		pwr_sts = inl(dev_priv->ospm_base + PSB_PM_SSS); +		if ((pwr_sts & pwr_mask) == pwr_mask) +			break; +		else +			udelay(10); +	} +	return 0; +} + +/* + * oaktrail_power_up + * + * Restore power to the specified island(s) (powergating) + */ +static int oaktrail_power_up(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 pwr_mask = PSB_PWRGT_DISPLAY_MASK; +	u32 pwr_sts, pwr_cnt; + +	pwr_cnt = inl(dev_priv->ospm_base + PSB_PM_SSC); +	pwr_cnt &= ~pwr_mask; +	outl(pwr_cnt, (dev_priv->ospm_base + PSB_PM_SSC)); + +	while (true) { +		pwr_sts = inl(dev_priv->ospm_base + PSB_PM_SSS); +		if ((pwr_sts & pwr_mask) == 0) +			break; +		else +			udelay(10); +	} +	return 0; +} + +/* Oaktrail */ +static const struct psb_offset oaktrail_regmap[2] = { +	{ +		.fp0 = MRST_FPA0, +		.fp1 = MRST_FPA1, +		.cntr = DSPACNTR, +		.conf = PIPEACONF, +		.src = PIPEASRC, +		.dpll = MRST_DPLL_A, +		.htotal = HTOTAL_A, +		.hblank = HBLANK_A, +		.hsync = HSYNC_A, +		.vtotal = VTOTAL_A, +		.vblank = VBLANK_A, +		.vsync = VSYNC_A, +		.stride = DSPASTRIDE, +		.size = DSPASIZE, +		.pos = DSPAPOS, +		.surf = DSPASURF, +		.addr = MRST_DSPABASE, +		.base = MRST_DSPABASE, +		.status = PIPEASTAT, +		.linoff = DSPALINOFF, +		.tileoff = DSPATILEOFF, +		.palette = PALETTE_A, +	}, +	{ +		.fp0 = FPB0, +		.fp1 = FPB1, +		.cntr = DSPBCNTR, +		.conf = PIPEBCONF, +		.src = PIPEBSRC, +		.dpll = DPLL_B, +		.htotal = HTOTAL_B, +		.hblank = HBLANK_B, +		.hsync = HSYNC_B, +		.vtotal = VTOTAL_B, +		.vblank = VBLANK_B, +		.vsync = VSYNC_B, +		.stride = DSPBSTRIDE, +		.size = DSPBSIZE, +		.pos = DSPBPOS, +		.surf = DSPBSURF, +		.addr = DSPBBASE, +		.base = DSPBBASE, +		.status = PIPEBSTAT, +		.linoff = DSPBLINOFF, +		.tileoff = DSPBTILEOFF, +		.palette = PALETTE_B, +	}, +}; + +static int oaktrail_chip_setup(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	int ret; +	 +	if (pci_enable_msi(dev->pdev)) +		dev_warn(dev->dev, "Enabling MSI failed!\n"); + +	dev_priv->regmap = oaktrail_regmap; + +	ret = mid_chip_setup(dev); +	if (ret < 0) +		return ret; +	if (!dev_priv->has_gct) { +		/* Now pull the BIOS data */ +		psb_intel_opregion_init(dev); +		psb_intel_init_bios(dev); +	} +	gma_intel_setup_gmbus(dev); +	oaktrail_hdmi_setup(dev); +	return 0; +} + +static void oaktrail_teardown(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	gma_intel_teardown_gmbus(dev); +	oaktrail_hdmi_teardown(dev); +	if (!dev_priv->has_gct) +		psb_intel_destroy_bios(dev); +} + +const struct psb_ops oaktrail_chip_ops = { +	.name = "Oaktrail", +	.accel_2d = 1, +	.pipes = 2, +	.crtcs = 2, +	.hdmi_mask = (1 << 1), +	.lvds_mask = (1 << 0), +	.sdvo_mask = (1 << 1), +	.cursor_needs_phys = 0, +	.sgx_offset = MRST_SGX_OFFSET, + +	.chip_setup = oaktrail_chip_setup, +	.chip_teardown = oaktrail_teardown, +	.crtc_helper = &oaktrail_helper_funcs, +	.crtc_funcs = &psb_intel_crtc_funcs, + +	.output_init = oaktrail_output_init, + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	.backlight_init = oaktrail_backlight_init, +#endif + +	.save_regs = oaktrail_save_display_registers, +	.restore_regs = oaktrail_restore_display_registers, +	.power_down = oaktrail_power_down, +	.power_up = oaktrail_power_up, + +	.i2c_bus = 1, +}; diff --git a/drivers/gpu/drm/gma500/oaktrail_hdmi.c b/drivers/gpu/drm/gma500/oaktrail_hdmi.c new file mode 100644 index 00000000000..cf018ddcc5a --- /dev/null +++ b/drivers/gpu/drm/gma500/oaktrail_hdmi.c @@ -0,0 +1,856 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + *	Li Peng <peng.li@intel.com> + */ + +#include <drm/drmP.h> +#include <drm/drm.h> +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "psb_drv.h" + +#define HDMI_READ(reg)		readl(hdmi_dev->regs + (reg)) +#define HDMI_WRITE(reg, val)	writel(val, hdmi_dev->regs + (reg)) + +#define HDMI_HCR	0x1000 +#define HCR_ENABLE_HDCP		(1 << 5) +#define HCR_ENABLE_AUDIO	(1 << 2) +#define HCR_ENABLE_PIXEL	(1 << 1) +#define HCR_ENABLE_TMDS		(1 << 0) + +#define HDMI_HICR	0x1004 +#define HDMI_HSR	0x1008 +#define HDMI_HISR	0x100C +#define HDMI_DETECT_HDP		(1 << 0) + +#define HDMI_VIDEO_REG	0x3000 +#define HDMI_UNIT_EN		(1 << 7) +#define HDMI_MODE_OUTPUT	(1 << 0) +#define HDMI_HBLANK_A	0x3100 + +#define HDMI_AUDIO_CTRL	0x4000 +#define HDMI_ENABLE_AUDIO	(1 << 0) + +#define PCH_HTOTAL_B	0x3100 +#define PCH_HBLANK_B	0x3104 +#define PCH_HSYNC_B	0x3108 +#define PCH_VTOTAL_B	0x310C +#define PCH_VBLANK_B	0x3110 +#define PCH_VSYNC_B	0x3114 +#define PCH_PIPEBSRC	0x311C + +#define PCH_PIPEB_DSL	0x3800 +#define PCH_PIPEB_SLC	0x3804 +#define PCH_PIPEBCONF	0x3808 +#define PCH_PIPEBSTAT	0x3824 + +#define CDVO_DFT	0x5000 +#define CDVO_SLEWRATE	0x5004 +#define CDVO_STRENGTH	0x5008 +#define CDVO_RCOMP	0x500C + +#define DPLL_CTRL       0x6000 +#define DPLL_PDIV_SHIFT		16 +#define DPLL_PDIV_MASK		(0xf << 16) +#define DPLL_PWRDN		(1 << 4) +#define DPLL_RESET		(1 << 3) +#define DPLL_FASTEN		(1 << 2) +#define DPLL_ENSTAT		(1 << 1) +#define DPLL_DITHEN		(1 << 0) + +#define DPLL_DIV_CTRL   0x6004 +#define DPLL_CLKF_MASK		0xffffffc0 +#define DPLL_CLKR_MASK		(0x3f) + +#define DPLL_CLK_ENABLE 0x6008 +#define DPLL_EN_DISP		(1 << 31) +#define DPLL_SEL_HDMI		(1 << 8) +#define DPLL_EN_HDMI		(1 << 1) +#define DPLL_EN_VGA		(1 << 0) + +#define DPLL_ADJUST     0x600C +#define DPLL_STATUS     0x6010 +#define DPLL_UPDATE     0x6014 +#define DPLL_DFT        0x6020 + +struct intel_range { +	int	min, max; +}; + +struct oaktrail_hdmi_limit { +	struct intel_range vco, np, nr, nf; +}; + +struct oaktrail_hdmi_clock { +	int np; +	int nr; +	int nf; +	int dot; +}; + +#define VCO_MIN		320000 +#define VCO_MAX		1650000 +#define	NP_MIN		1 +#define	NP_MAX		15 +#define	NR_MIN		1 +#define	NR_MAX		64 +#define NF_MIN		2 +#define NF_MAX		4095 + +static const struct oaktrail_hdmi_limit oaktrail_hdmi_limit = { +	.vco = { .min = VCO_MIN,		.max = VCO_MAX }, +	.np  = { .min = NP_MIN,			.max = NP_MAX  }, +	.nr  = { .min = NR_MIN,			.max = NR_MAX  }, +	.nf  = { .min = NF_MIN,			.max = NF_MAX  }, +}; + +static void oaktrail_hdmi_audio_enable(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv; + +	HDMI_WRITE(HDMI_HCR, 0x67); +	HDMI_READ(HDMI_HCR); + +	HDMI_WRITE(0x51a8, 0x10); +	HDMI_READ(0x51a8); + +	HDMI_WRITE(HDMI_AUDIO_CTRL, 0x1); +	HDMI_READ(HDMI_AUDIO_CTRL); +} + +static void oaktrail_hdmi_audio_disable(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv; + +	HDMI_WRITE(0x51a8, 0x0); +	HDMI_READ(0x51a8); + +	HDMI_WRITE(HDMI_AUDIO_CTRL, 0x0); +	HDMI_READ(HDMI_AUDIO_CTRL); + +	HDMI_WRITE(HDMI_HCR, 0x47); +	HDMI_READ(HDMI_HCR); +} + +static unsigned int htotal_calculate(struct drm_display_mode *mode) +{ +	u32 htotal, new_crtc_htotal; + +	htotal = (mode->crtc_hdisplay - 1) | ((mode->crtc_htotal - 1) << 16); + +	/* +	 * 1024 x 768  new_crtc_htotal = 0x1024; +	 * 1280 x 1024 new_crtc_htotal = 0x0c34; +	 */ +	new_crtc_htotal = (mode->crtc_htotal - 1) * 200 * 1000 / mode->clock; + +	DRM_DEBUG_KMS("new crtc htotal 0x%4x\n", new_crtc_htotal); +	return (mode->crtc_hdisplay - 1) | (new_crtc_htotal << 16); +} + +static void oaktrail_hdmi_find_dpll(struct drm_crtc *crtc, int target, +				int refclk, struct oaktrail_hdmi_clock *best_clock) +{ +	int np_min, np_max, nr_min, nr_max; +	int np, nr, nf; + +	np_min = DIV_ROUND_UP(oaktrail_hdmi_limit.vco.min, target * 10); +	np_max = oaktrail_hdmi_limit.vco.max / (target * 10); +	if (np_min < oaktrail_hdmi_limit.np.min) +		np_min = oaktrail_hdmi_limit.np.min; +	if (np_max > oaktrail_hdmi_limit.np.max) +		np_max = oaktrail_hdmi_limit.np.max; + +	nr_min = DIV_ROUND_UP((refclk * 1000), (target * 10 * np_max)); +	nr_max = DIV_ROUND_UP((refclk * 1000), (target * 10 * np_min)); +	if (nr_min < oaktrail_hdmi_limit.nr.min) +		nr_min = oaktrail_hdmi_limit.nr.min; +	if (nr_max > oaktrail_hdmi_limit.nr.max) +		nr_max = oaktrail_hdmi_limit.nr.max; + +	np = DIV_ROUND_UP((refclk * 1000), (target * 10 * nr_max)); +	nr = DIV_ROUND_UP((refclk * 1000), (target * 10 * np)); +	nf = DIV_ROUND_CLOSEST((target * 10 * np * nr), refclk); +	DRM_DEBUG_KMS("np, nr, nf %d %d %d\n", np, nr, nf); + +	/* +	 * 1024 x 768  np = 1; nr = 0x26; nf = 0x0fd8000; +	 * 1280 x 1024 np = 1; nr = 0x17; nf = 0x1034000; +	 */ +	best_clock->np = np; +	best_clock->nr = nr - 1; +	best_clock->nf = (nf << 14); +} + +static void scu_busy_loop(void __iomem *scu_base) +{ +	u32 status = 0; +	u32 loop_count = 0; + +	status = readl(scu_base + 0x04); +	while (status & 1) { +		udelay(1); /* scu processing time is in few u secods */ +		status = readl(scu_base + 0x04); +		loop_count++; +		/* break if scu doesn't reset busy bit after huge retry */ +		if (loop_count > 1000) { +			DRM_DEBUG_KMS("SCU IPC timed out"); +			return; +		} +	} +} + +/* + *	You don't want to know, you really really don't want to know.... + * + *	This is magic. However it's safe magic because of the way the platform + *	works and it is necessary magic. + */ +static void oaktrail_hdmi_reset(struct drm_device *dev) +{ +	void __iomem *base; +	unsigned long scu_ipc_mmio = 0xff11c000UL; +	int scu_len = 1024; + +	base = ioremap((resource_size_t)scu_ipc_mmio, scu_len); +	if (base == NULL) { +		DRM_ERROR("failed to map scu mmio\n"); +		return; +	} + +	/* scu ipc: assert hdmi controller reset */ +	writel(0xff11d118, base + 0x0c); +	writel(0x7fffffdf, base + 0x80); +	writel(0x42005, base + 0x0); +	scu_busy_loop(base); + +	/* scu ipc: de-assert hdmi controller reset */ +	writel(0xff11d118, base + 0x0c); +	writel(0x7fffffff, base + 0x80); +	writel(0x42005, base + 0x0); +	scu_busy_loop(base); + +	iounmap(base); +} + +int oaktrail_crtc_hdmi_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 drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv; +	int pipe = 1; +	int htot_reg = (pipe == 0) ? HTOTAL_A : HTOTAL_B; +	int hblank_reg = (pipe == 0) ? HBLANK_A : HBLANK_B; +	int hsync_reg = (pipe == 0) ? HSYNC_A : HSYNC_B; +	int vtot_reg = (pipe == 0) ? VTOTAL_A : VTOTAL_B; +	int vblank_reg = (pipe == 0) ? VBLANK_A : VBLANK_B; +	int vsync_reg = (pipe == 0) ? VSYNC_A : VSYNC_B; +	int dspsize_reg = (pipe == 0) ? DSPASIZE : DSPBSIZE; +	int dsppos_reg = (pipe == 0) ? DSPAPOS : DSPBPOS; +	int pipesrc_reg = (pipe == 0) ? PIPEASRC : PIPEBSRC; +	int pipeconf_reg = (pipe == 0) ? PIPEACONF : PIPEBCONF; +	int refclk; +	struct oaktrail_hdmi_clock clock; +	u32 dspcntr, pipeconf, dpll, temp; +	int dspcntr_reg = DSPBCNTR; + +	if (!gma_power_begin(dev, true)) +		return 0; + +	/* Disable the VGA plane that we never use */ +	REG_WRITE(VGACNTRL, VGA_DISP_DISABLE); + +	/* Disable dpll if necessary */ +	dpll = REG_READ(DPLL_CTRL); +	if ((dpll & DPLL_PWRDN) == 0) { +		REG_WRITE(DPLL_CTRL, dpll | (DPLL_PWRDN | DPLL_RESET)); +		REG_WRITE(DPLL_DIV_CTRL, 0x00000000); +		REG_WRITE(DPLL_STATUS, 0x1); +	} +	udelay(150); + +	/* Reset controller */ +	oaktrail_hdmi_reset(dev); + +	/* program and enable dpll */ +	refclk = 25000; +	oaktrail_hdmi_find_dpll(crtc, adjusted_mode->clock, refclk, &clock); + +	/* Set the DPLL */ +	dpll = REG_READ(DPLL_CTRL); +	dpll &= ~DPLL_PDIV_MASK; +	dpll &= ~(DPLL_PWRDN | DPLL_RESET); +	REG_WRITE(DPLL_CTRL, 0x00000008); +	REG_WRITE(DPLL_DIV_CTRL, ((clock.nf << 6) | clock.nr)); +	REG_WRITE(DPLL_ADJUST, ((clock.nf >> 14) - 1)); +	REG_WRITE(DPLL_CTRL, (dpll | (clock.np << DPLL_PDIV_SHIFT) | DPLL_ENSTAT | DPLL_DITHEN)); +	REG_WRITE(DPLL_UPDATE, 0x80000000); +	REG_WRITE(DPLL_CLK_ENABLE, 0x80050102); +	udelay(150); + +	/* configure HDMI */ +	HDMI_WRITE(0x1004, 0x1fd); +	HDMI_WRITE(0x2000, 0x1); +	HDMI_WRITE(0x2008, 0x0); +	HDMI_WRITE(0x3130, 0x8); +	HDMI_WRITE(0x101c, 0x1800810); + +	temp = htotal_calculate(adjusted_mode); +	REG_WRITE(htot_reg, temp); +	REG_WRITE(hblank_reg, (adjusted_mode->crtc_hblank_start - 1) | ((adjusted_mode->crtc_hblank_end - 1) << 16)); +	REG_WRITE(hsync_reg, (adjusted_mode->crtc_hsync_start - 1) | ((adjusted_mode->crtc_hsync_end - 1) << 16)); +	REG_WRITE(vtot_reg, (adjusted_mode->crtc_vdisplay - 1) | ((adjusted_mode->crtc_vtotal - 1) << 16)); +	REG_WRITE(vblank_reg, (adjusted_mode->crtc_vblank_start - 1) | ((adjusted_mode->crtc_vblank_end - 1) << 16)); +	REG_WRITE(vsync_reg, (adjusted_mode->crtc_vsync_start - 1) | ((adjusted_mode->crtc_vsync_end - 1) << 16)); +	REG_WRITE(pipesrc_reg, ((mode->crtc_hdisplay - 1) << 16) |  (mode->crtc_vdisplay - 1)); + +	REG_WRITE(PCH_HTOTAL_B, (adjusted_mode->crtc_hdisplay - 1) | ((adjusted_mode->crtc_htotal - 1) << 16)); +	REG_WRITE(PCH_HBLANK_B, (adjusted_mode->crtc_hblank_start - 1) | ((adjusted_mode->crtc_hblank_end - 1) << 16)); +	REG_WRITE(PCH_HSYNC_B, (adjusted_mode->crtc_hsync_start - 1) | ((adjusted_mode->crtc_hsync_end - 1) << 16)); +	REG_WRITE(PCH_VTOTAL_B, (adjusted_mode->crtc_vdisplay - 1) | ((adjusted_mode->crtc_vtotal - 1) << 16)); +	REG_WRITE(PCH_VBLANK_B, (adjusted_mode->crtc_vblank_start - 1) | ((adjusted_mode->crtc_vblank_end - 1) << 16)); +	REG_WRITE(PCH_VSYNC_B, (adjusted_mode->crtc_vsync_start - 1) | ((adjusted_mode->crtc_vsync_end - 1) << 16)); +	REG_WRITE(PCH_PIPEBSRC, ((mode->crtc_hdisplay - 1) << 16) |  (mode->crtc_vdisplay - 1)); + +	temp = adjusted_mode->crtc_hblank_end - adjusted_mode->crtc_hblank_start; +	HDMI_WRITE(HDMI_HBLANK_A, ((adjusted_mode->crtc_hdisplay - 1) << 16) |  temp); + +	REG_WRITE(dspsize_reg, ((mode->vdisplay - 1) << 16) | (mode->hdisplay - 1)); +	REG_WRITE(dsppos_reg, 0); + +	/* Flush the plane changes */ +	{ +		struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; +		crtc_funcs->mode_set_base(crtc, x, y, old_fb); +	} + +	/* Set up the display plane register */ +	dspcntr = REG_READ(dspcntr_reg); +	dspcntr |= DISPPLANE_GAMMA_ENABLE; +	dspcntr |= DISPPLANE_SEL_PIPE_B; +	dspcntr |= DISPLAY_PLANE_ENABLE; + +	/* setup pipeconf */ +	pipeconf = REG_READ(pipeconf_reg); +	pipeconf |= PIPEACONF_ENABLE; + +	REG_WRITE(pipeconf_reg, pipeconf); +	REG_READ(pipeconf_reg); + +	REG_WRITE(PCH_PIPEBCONF, pipeconf); +	REG_READ(PCH_PIPEBCONF); +	gma_wait_for_vblank(dev); + +	REG_WRITE(dspcntr_reg, dspcntr); +	gma_wait_for_vblank(dev); + +	gma_power_end(dev); + +	return 0; +} + +void oaktrail_crtc_hdmi_dpms(struct drm_crtc *crtc, int mode) +{ +	struct drm_device *dev = crtc->dev; +	u32 temp; + +	DRM_DEBUG_KMS("%s %d\n", __func__, mode); + +	switch (mode) { +	case DRM_MODE_DPMS_OFF: +		REG_WRITE(VGACNTRL, 0x80000000); + +		/* Disable plane */ +		temp = REG_READ(DSPBCNTR); +		if ((temp & DISPLAY_PLANE_ENABLE) != 0) { +			REG_WRITE(DSPBCNTR, temp & ~DISPLAY_PLANE_ENABLE); +			REG_READ(DSPBCNTR); +			/* Flush the plane changes */ +			REG_WRITE(DSPBSURF, REG_READ(DSPBSURF)); +			REG_READ(DSPBSURF); +		} + +		/* Disable pipe B */ +		temp = REG_READ(PIPEBCONF); +		if ((temp & PIPEACONF_ENABLE) != 0) { +			REG_WRITE(PIPEBCONF, temp & ~PIPEACONF_ENABLE); +			REG_READ(PIPEBCONF); +		} + +		/* Disable LNW Pipes, etc */ +		temp = REG_READ(PCH_PIPEBCONF); +		if ((temp & PIPEACONF_ENABLE) != 0) { +			REG_WRITE(PCH_PIPEBCONF, temp & ~PIPEACONF_ENABLE); +			REG_READ(PCH_PIPEBCONF); +		} + +		/* wait for pipe off */ +		udelay(150); + +		/* Disable dpll */ +		temp = REG_READ(DPLL_CTRL); +		if ((temp & DPLL_PWRDN) == 0) { +			REG_WRITE(DPLL_CTRL, temp | (DPLL_PWRDN | DPLL_RESET)); +			REG_WRITE(DPLL_STATUS, 0x1); +		} + +		/* wait for dpll off */ +		udelay(150); + +		break; +	case DRM_MODE_DPMS_ON: +	case DRM_MODE_DPMS_STANDBY: +	case DRM_MODE_DPMS_SUSPEND: +		/* Enable dpll */ +		temp = REG_READ(DPLL_CTRL); +		if ((temp & DPLL_PWRDN) != 0) { +			REG_WRITE(DPLL_CTRL, temp & ~(DPLL_PWRDN | DPLL_RESET)); +			temp = REG_READ(DPLL_CLK_ENABLE); +			REG_WRITE(DPLL_CLK_ENABLE, temp | DPLL_EN_DISP | DPLL_SEL_HDMI | DPLL_EN_HDMI); +			REG_READ(DPLL_CLK_ENABLE); +		} +		/* wait for dpll warm up */ +		udelay(150); + +		/* Enable pipe B */ +		temp = REG_READ(PIPEBCONF); +		if ((temp & PIPEACONF_ENABLE) == 0) { +			REG_WRITE(PIPEBCONF, temp | PIPEACONF_ENABLE); +			REG_READ(PIPEBCONF); +		} + +		/* Enable LNW Pipe B */ +		temp = REG_READ(PCH_PIPEBCONF); +		if ((temp & PIPEACONF_ENABLE) == 0) { +			REG_WRITE(PCH_PIPEBCONF, temp | PIPEACONF_ENABLE); +			REG_READ(PCH_PIPEBCONF); +		} + +		gma_wait_for_vblank(dev); + +		/* Enable plane */ +		temp = REG_READ(DSPBCNTR); +		if ((temp & DISPLAY_PLANE_ENABLE) == 0) { +			REG_WRITE(DSPBCNTR, temp | DISPLAY_PLANE_ENABLE); +			/* Flush the plane changes */ +			REG_WRITE(DSPBSURF, REG_READ(DSPBSURF)); +			REG_READ(DSPBSURF); +		} + +		gma_crtc_load_lut(crtc); +	} + +	/* DSPARB */ +	REG_WRITE(DSPARB, 0x00003fbf); + +	/* FW1 */ +	REG_WRITE(0x70034, 0x3f880a0a); + +	/* FW2 */ +	REG_WRITE(0x70038, 0x0b060808); + +	/* FW4 */ +	REG_WRITE(0x70050, 0x08030404); + +	/* FW5 */ +	REG_WRITE(0x70054, 0x04040404); + +	/* LNC Chicken Bits - Squawk! */ +	REG_WRITE(0x70400, 0x4000); + +	return; +} + +static void oaktrail_hdmi_dpms(struct drm_encoder *encoder, int mode) +{ +	static int dpms_mode = -1; + +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv; +	u32 temp; + +	if (dpms_mode == mode) +		return; + +	if (mode != DRM_MODE_DPMS_ON) +		temp = 0x0; +	else +		temp = 0x99; + +	dpms_mode = mode; +	HDMI_WRITE(HDMI_VIDEO_REG, temp); +} + +static int oaktrail_hdmi_mode_valid(struct drm_connector *connector, +				struct drm_display_mode *mode) +{ +	if (mode->clock > 165000) +		return MODE_CLOCK_HIGH; +	if (mode->clock < 20000) +		return MODE_CLOCK_LOW; + +	if (mode->flags & DRM_MODE_FLAG_DBLSCAN) +		return MODE_NO_DBLESCAN; + +	return MODE_OK; +} + +static enum drm_connector_status +oaktrail_hdmi_detect(struct drm_connector *connector, bool force) +{ +	enum drm_connector_status status; +	struct drm_device *dev = connector->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv; +	u32 temp; + +	temp = HDMI_READ(HDMI_HSR); +	DRM_DEBUG_KMS("HDMI_HSR %x\n", temp); + +	if ((temp & HDMI_DETECT_HDP) != 0) +		status = connector_status_connected; +	else +		status = connector_status_disconnected; + +	return status; +} + +static const unsigned char raw_edid[] = { +	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x10, 0xac, 0x2f, 0xa0, +	0x53, 0x55, 0x33, 0x30, 0x16, 0x13, 0x01, 0x03, 0x0e, 0x3a, 0x24, 0x78, +	0xea, 0xe9, 0xf5, 0xac, 0x51, 0x30, 0xb4, 0x25, 0x11, 0x50, 0x54, 0xa5, +	0x4b, 0x00, 0x81, 0x80, 0xa9, 0x40, 0x71, 0x4f, 0xb3, 0x00, 0x01, 0x01, +	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x28, 0x3c, 0x80, 0xa0, 0x70, 0xb0, +	0x23, 0x40, 0x30, 0x20, 0x36, 0x00, 0x46, 0x6c, 0x21, 0x00, 0x00, 0x1a, +	0x00, 0x00, 0x00, 0xff, 0x00, 0x47, 0x4e, 0x37, 0x32, 0x31, 0x39, 0x35, +	0x52, 0x30, 0x33, 0x55, 0x53, 0x0a, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x44, +	0x45, 0x4c, 0x4c, 0x20, 0x32, 0x37, 0x30, 0x39, 0x57, 0x0a, 0x20, 0x20, +	0x00, 0x00, 0x00, 0xfd, 0x00, 0x38, 0x4c, 0x1e, 0x53, 0x11, 0x00, 0x0a, +	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x8d +}; + +static int oaktrail_hdmi_get_modes(struct drm_connector *connector) +{ +	struct i2c_adapter *i2c_adap; +	struct edid *edid; +	int ret = 0; + +	/* +	 *	FIXME: We need to figure this lot out. In theory we can +	 *	read the EDID somehow but I've yet to find working reference +	 *	code. +	 */ +	i2c_adap = i2c_get_adapter(3); +	if (i2c_adap == NULL) { +		DRM_ERROR("No ddc adapter available!\n"); +		edid = (struct edid *)raw_edid; +	} else { +		edid = (struct edid *)raw_edid; +		/* FIXME ? edid = drm_get_edid(connector, i2c_adap); */ +	} + +	if (edid) { +		drm_mode_connector_update_edid_property(connector, edid); +		ret = drm_add_edid_modes(connector, edid); +	} +	return ret; +} + +static void oaktrail_hdmi_mode_set(struct drm_encoder *encoder, +			       struct drm_display_mode *mode, +			       struct drm_display_mode *adjusted_mode) +{ +	struct drm_device *dev = encoder->dev; + +	oaktrail_hdmi_audio_enable(dev); +	return; +} + +static void oaktrail_hdmi_destroy(struct drm_connector *connector) +{ +	return; +} + +static const struct drm_encoder_helper_funcs oaktrail_hdmi_helper_funcs = { +	.dpms = oaktrail_hdmi_dpms, +	.mode_fixup = gma_encoder_mode_fixup, +	.prepare = gma_encoder_prepare, +	.mode_set = oaktrail_hdmi_mode_set, +	.commit = gma_encoder_commit, +}; + +static const struct drm_connector_helper_funcs +					oaktrail_hdmi_connector_helper_funcs = { +	.get_modes = oaktrail_hdmi_get_modes, +	.mode_valid = oaktrail_hdmi_mode_valid, +	.best_encoder = gma_best_encoder, +}; + +static const struct drm_connector_funcs oaktrail_hdmi_connector_funcs = { +	.dpms = drm_helper_connector_dpms, +	.detect = oaktrail_hdmi_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.destroy = oaktrail_hdmi_destroy, +}; + +static void oaktrail_hdmi_enc_destroy(struct drm_encoder *encoder) +{ +	drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs oaktrail_hdmi_enc_funcs = { +	.destroy = oaktrail_hdmi_enc_destroy, +}; + +void oaktrail_hdmi_init(struct drm_device *dev, +					struct psb_intel_mode_device *mode_dev) +{ +	struct gma_encoder *gma_encoder; +	struct gma_connector *gma_connector; +	struct drm_connector *connector; +	struct drm_encoder *encoder; + +	gma_encoder = kzalloc(sizeof(struct gma_encoder), GFP_KERNEL); +	if (!gma_encoder) +		return; + +	gma_connector = kzalloc(sizeof(struct gma_connector), GFP_KERNEL); +	if (!gma_connector) +		goto failed_connector; + +	connector = &gma_connector->base; +	encoder = &gma_encoder->base; +	drm_connector_init(dev, connector, +			   &oaktrail_hdmi_connector_funcs, +			   DRM_MODE_CONNECTOR_DVID); + +	drm_encoder_init(dev, encoder, +			 &oaktrail_hdmi_enc_funcs, +			 DRM_MODE_ENCODER_TMDS); + +	gma_connector_attach_encoder(gma_connector, gma_encoder); + +	gma_encoder->type = INTEL_OUTPUT_HDMI; +	drm_encoder_helper_add(encoder, &oaktrail_hdmi_helper_funcs); +	drm_connector_helper_add(connector, &oaktrail_hdmi_connector_helper_funcs); + +	connector->display_info.subpixel_order = SubPixelHorizontalRGB; +	connector->interlace_allowed = false; +	connector->doublescan_allowed = false; +	drm_sysfs_connector_add(connector); +	dev_info(dev->dev, "HDMI initialised.\n"); + +	return; + +failed_connector: +	kfree(gma_encoder); +} + +static DEFINE_PCI_DEVICE_TABLE(hdmi_ids) = { +	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080d) }, +	{ 0 } +}; + +void oaktrail_hdmi_setup(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct pci_dev *pdev; +	struct oaktrail_hdmi_dev *hdmi_dev; +	int ret; + +	pdev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x080d, NULL); +	if (!pdev) +		return; + +	hdmi_dev = kzalloc(sizeof(struct oaktrail_hdmi_dev), GFP_KERNEL); +	if (!hdmi_dev) { +		dev_err(dev->dev, "failed to allocate memory\n"); +		goto out; +	} + + +	ret = pci_enable_device(pdev); +	if (ret) { +		dev_err(dev->dev, "failed to enable hdmi controller\n"); +		goto free; +	} + +	hdmi_dev->mmio = pci_resource_start(pdev, 0); +	hdmi_dev->mmio_len = pci_resource_len(pdev, 0); +	hdmi_dev->regs = ioremap(hdmi_dev->mmio, hdmi_dev->mmio_len); +	if (!hdmi_dev->regs) { +		dev_err(dev->dev, "failed to map hdmi mmio\n"); +		goto free; +	} + +	hdmi_dev->dev = pdev; +	pci_set_drvdata(pdev, hdmi_dev); + +	/* Initialize i2c controller */ +	ret = oaktrail_hdmi_i2c_init(hdmi_dev->dev); +	if (ret) +		dev_err(dev->dev, "HDMI I2C initialization failed\n"); + +	dev_priv->hdmi_priv = hdmi_dev; +	oaktrail_hdmi_audio_disable(dev); + +	dev_info(dev->dev, "HDMI hardware present.\n"); + +	return; + +free: +	kfree(hdmi_dev); +out: +	return; +} + +void oaktrail_hdmi_teardown(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv; +	struct pci_dev *pdev; + +	if (hdmi_dev) { +		pdev = hdmi_dev->dev; +		pci_set_drvdata(pdev, NULL); +		oaktrail_hdmi_i2c_exit(pdev); +		iounmap(hdmi_dev->regs); +		kfree(hdmi_dev); +		pci_dev_put(pdev); +	} +} + +/* save HDMI register state */ +void oaktrail_hdmi_save(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv; +	struct psb_state *regs = &dev_priv->regs.psb; +	struct psb_pipe *pipeb = &dev_priv->regs.pipe[1]; +	int i; + +	/* dpll */ +	hdmi_dev->saveDPLL_CTRL = PSB_RVDC32(DPLL_CTRL); +	hdmi_dev->saveDPLL_DIV_CTRL = PSB_RVDC32(DPLL_DIV_CTRL); +	hdmi_dev->saveDPLL_ADJUST = PSB_RVDC32(DPLL_ADJUST); +	hdmi_dev->saveDPLL_UPDATE = PSB_RVDC32(DPLL_UPDATE); +	hdmi_dev->saveDPLL_CLK_ENABLE = PSB_RVDC32(DPLL_CLK_ENABLE); + +	/* pipe B */ +	pipeb->conf = PSB_RVDC32(PIPEBCONF); +	pipeb->src = PSB_RVDC32(PIPEBSRC); +	pipeb->htotal = PSB_RVDC32(HTOTAL_B); +	pipeb->hblank = PSB_RVDC32(HBLANK_B); +	pipeb->hsync = PSB_RVDC32(HSYNC_B); +	pipeb->vtotal = PSB_RVDC32(VTOTAL_B); +	pipeb->vblank = PSB_RVDC32(VBLANK_B); +	pipeb->vsync = PSB_RVDC32(VSYNC_B); + +	hdmi_dev->savePCH_PIPEBCONF = PSB_RVDC32(PCH_PIPEBCONF); +	hdmi_dev->savePCH_PIPEBSRC = PSB_RVDC32(PCH_PIPEBSRC); +	hdmi_dev->savePCH_HTOTAL_B = PSB_RVDC32(PCH_HTOTAL_B); +	hdmi_dev->savePCH_HBLANK_B = PSB_RVDC32(PCH_HBLANK_B); +	hdmi_dev->savePCH_HSYNC_B  = PSB_RVDC32(PCH_HSYNC_B); +	hdmi_dev->savePCH_VTOTAL_B = PSB_RVDC32(PCH_VTOTAL_B); +	hdmi_dev->savePCH_VBLANK_B = PSB_RVDC32(PCH_VBLANK_B); +	hdmi_dev->savePCH_VSYNC_B  = PSB_RVDC32(PCH_VSYNC_B); + +	/* plane */ +	pipeb->cntr = PSB_RVDC32(DSPBCNTR); +	pipeb->stride = PSB_RVDC32(DSPBSTRIDE); +	pipeb->addr = PSB_RVDC32(DSPBBASE); +	pipeb->surf = PSB_RVDC32(DSPBSURF); +	pipeb->linoff = PSB_RVDC32(DSPBLINOFF); +	pipeb->tileoff = PSB_RVDC32(DSPBTILEOFF); + +	/* cursor B */ +	regs->saveDSPBCURSOR_CTRL = PSB_RVDC32(CURBCNTR); +	regs->saveDSPBCURSOR_BASE = PSB_RVDC32(CURBBASE); +	regs->saveDSPBCURSOR_POS = PSB_RVDC32(CURBPOS); + +	/* save palette */ +	for (i = 0; i < 256; i++) +		pipeb->palette[i] = PSB_RVDC32(PALETTE_B + (i << 2)); +} + +/* restore HDMI register state */ +void oaktrail_hdmi_restore(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv; +	struct psb_state *regs = &dev_priv->regs.psb; +	struct psb_pipe *pipeb = &dev_priv->regs.pipe[1]; +	int i; + +	/* dpll */ +	PSB_WVDC32(hdmi_dev->saveDPLL_CTRL, DPLL_CTRL); +	PSB_WVDC32(hdmi_dev->saveDPLL_DIV_CTRL, DPLL_DIV_CTRL); +	PSB_WVDC32(hdmi_dev->saveDPLL_ADJUST, DPLL_ADJUST); +	PSB_WVDC32(hdmi_dev->saveDPLL_UPDATE, DPLL_UPDATE); +	PSB_WVDC32(hdmi_dev->saveDPLL_CLK_ENABLE, DPLL_CLK_ENABLE); +	DRM_UDELAY(150); + +	/* pipe */ +	PSB_WVDC32(pipeb->src, PIPEBSRC); +	PSB_WVDC32(pipeb->htotal, HTOTAL_B); +	PSB_WVDC32(pipeb->hblank, HBLANK_B); +	PSB_WVDC32(pipeb->hsync,  HSYNC_B); +	PSB_WVDC32(pipeb->vtotal, VTOTAL_B); +	PSB_WVDC32(pipeb->vblank, VBLANK_B); +	PSB_WVDC32(pipeb->vsync,  VSYNC_B); + +	PSB_WVDC32(hdmi_dev->savePCH_PIPEBSRC, PCH_PIPEBSRC); +	PSB_WVDC32(hdmi_dev->savePCH_HTOTAL_B, PCH_HTOTAL_B); +	PSB_WVDC32(hdmi_dev->savePCH_HBLANK_B, PCH_HBLANK_B); +	PSB_WVDC32(hdmi_dev->savePCH_HSYNC_B,  PCH_HSYNC_B); +	PSB_WVDC32(hdmi_dev->savePCH_VTOTAL_B, PCH_VTOTAL_B); +	PSB_WVDC32(hdmi_dev->savePCH_VBLANK_B, PCH_VBLANK_B); +	PSB_WVDC32(hdmi_dev->savePCH_VSYNC_B,  PCH_VSYNC_B); + +	PSB_WVDC32(pipeb->conf, PIPEBCONF); +	PSB_WVDC32(hdmi_dev->savePCH_PIPEBCONF, PCH_PIPEBCONF); + +	/* plane */ +	PSB_WVDC32(pipeb->linoff, DSPBLINOFF); +	PSB_WVDC32(pipeb->stride, DSPBSTRIDE); +	PSB_WVDC32(pipeb->tileoff, DSPBTILEOFF); +	PSB_WVDC32(pipeb->cntr, DSPBCNTR); +	PSB_WVDC32(pipeb->surf, DSPBSURF); + +	/* cursor B */ +	PSB_WVDC32(regs->saveDSPBCURSOR_CTRL, CURBCNTR); +	PSB_WVDC32(regs->saveDSPBCURSOR_POS, CURBPOS); +	PSB_WVDC32(regs->saveDSPBCURSOR_BASE, CURBBASE); + +	/* restore palette */ +	for (i = 0; i < 256; i++) +		PSB_WVDC32(pipeb->palette[i], PALETTE_B + (i << 2)); +} diff --git a/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c b/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c new file mode 100644 index 00000000000..e2810706114 --- /dev/null +++ b/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c @@ -0,0 +1,327 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + *	Li Peng <peng.li@intel.com> + */ + +#include <linux/export.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include "psb_drv.h" + +#define HDMI_READ(reg)		readl(hdmi_dev->regs + (reg)) +#define HDMI_WRITE(reg, val)	writel(val, hdmi_dev->regs + (reg)) + +#define HDMI_HCR	0x1000 +#define HCR_DETECT_HDP		(1 << 6) +#define HCR_ENABLE_HDCP		(1 << 5) +#define HCR_ENABLE_AUDIO	(1 << 2) +#define HCR_ENABLE_PIXEL	(1 << 1) +#define HCR_ENABLE_TMDS		(1 << 0) +#define HDMI_HICR	0x1004 +#define HDMI_INTR_I2C_ERROR	(1 << 4) +#define HDMI_INTR_I2C_FULL	(1 << 3) +#define HDMI_INTR_I2C_DONE	(1 << 2) +#define HDMI_INTR_HPD		(1 << 0) +#define HDMI_HSR	0x1008 +#define HDMI_HISR	0x100C +#define HDMI_HI2CRDB0	0x1200 +#define HDMI_HI2CHCR	0x1240 +#define HI2C_HDCP_WRITE		(0 << 2) +#define HI2C_HDCP_RI_READ	(1 << 2) +#define HI2C_HDCP_READ		(2 << 2) +#define HI2C_EDID_READ		(3 << 2) +#define HI2C_READ_CONTINUE	(1 << 1) +#define HI2C_ENABLE_TRANSACTION	(1 << 0) + +#define HDMI_ICRH	0x1100 +#define HDMI_HI2CTDR0	0x1244 +#define HDMI_HI2CTDR1	0x1248 + +#define I2C_STAT_INIT		0 +#define I2C_READ_DONE		1 +#define I2C_TRANSACTION_DONE	2 + +struct hdmi_i2c_dev { +	struct i2c_adapter *adap; +	struct mutex i2c_lock; +	struct completion complete; +	int status; +	struct i2c_msg *msg; +	int buf_offset; +}; + +static void hdmi_i2c_irq_enable(struct oaktrail_hdmi_dev *hdmi_dev) +{ +	u32 temp; + +	temp = HDMI_READ(HDMI_HICR); +	temp |= (HDMI_INTR_I2C_ERROR | HDMI_INTR_I2C_FULL | HDMI_INTR_I2C_DONE); +	HDMI_WRITE(HDMI_HICR, temp); +	HDMI_READ(HDMI_HICR); +} + +static void hdmi_i2c_irq_disable(struct oaktrail_hdmi_dev *hdmi_dev) +{ +	HDMI_WRITE(HDMI_HICR, 0x0); +	HDMI_READ(HDMI_HICR); +} + +static int xfer_read(struct i2c_adapter *adap, struct i2c_msg *pmsg) +{ +	struct oaktrail_hdmi_dev *hdmi_dev = i2c_get_adapdata(adap); +	struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev; +	u32 temp; + +	i2c_dev->status = I2C_STAT_INIT; +	i2c_dev->msg = pmsg; +	i2c_dev->buf_offset = 0; +	reinit_completion(&i2c_dev->complete); + +	/* Enable I2C transaction */ +	temp = ((pmsg->len) << 20) | HI2C_EDID_READ | HI2C_ENABLE_TRANSACTION; +	HDMI_WRITE(HDMI_HI2CHCR, temp); +	HDMI_READ(HDMI_HI2CHCR); + +	while (i2c_dev->status != I2C_TRANSACTION_DONE) +		wait_for_completion_interruptible_timeout(&i2c_dev->complete, +								10 * HZ); + +	return 0; +} + +static int xfer_write(struct i2c_adapter *adap, struct i2c_msg *pmsg) +{ +	/* +	 * XXX: i2c write seems isn't useful for EDID probe, don't do anything +	 */ +	return 0; +} + +static int oaktrail_hdmi_i2c_access(struct i2c_adapter *adap, +				struct i2c_msg *pmsg, +				int num) +{ +	struct oaktrail_hdmi_dev *hdmi_dev = i2c_get_adapdata(adap); +	struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev; +	int i; + +	mutex_lock(&i2c_dev->i2c_lock); + +	/* Enable i2c unit */ +	HDMI_WRITE(HDMI_ICRH, 0x00008760); + +	/* Enable irq */ +	hdmi_i2c_irq_enable(hdmi_dev); +	for (i = 0; i < num; i++) { +		if (pmsg->len && pmsg->buf) { +			if (pmsg->flags & I2C_M_RD) +				xfer_read(adap, pmsg); +			else +				xfer_write(adap, pmsg); +		} +		pmsg++;         /* next message */ +	} + +	/* Disable irq */ +	hdmi_i2c_irq_disable(hdmi_dev); + +	mutex_unlock(&i2c_dev->i2c_lock); + +	return i; +} + +static u32 oaktrail_hdmi_i2c_func(struct i2c_adapter *adapter) +{ +	return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR; +} + +static const struct i2c_algorithm oaktrail_hdmi_i2c_algorithm = { +	.master_xfer	= oaktrail_hdmi_i2c_access, +	.functionality  = oaktrail_hdmi_i2c_func, +}; + +static struct i2c_adapter oaktrail_hdmi_i2c_adapter = { +	.name		= "oaktrail_hdmi_i2c", +	.nr		= 3, +	.owner		= THIS_MODULE, +	.class		= I2C_CLASS_DDC, +	.algo		= &oaktrail_hdmi_i2c_algorithm, +}; + +static void hdmi_i2c_read(struct oaktrail_hdmi_dev *hdmi_dev) +{ +	struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev; +	struct i2c_msg *msg = i2c_dev->msg; +	u8 *buf = msg->buf; +	u32 temp; +	int i, offset; + +	offset = i2c_dev->buf_offset; +	for (i = 0; i < 0x10; i++) { +		temp = HDMI_READ(HDMI_HI2CRDB0 + (i * 4)); +		memcpy(buf + (offset + i * 4), &temp, 4); +	} +	i2c_dev->buf_offset += (0x10 * 4); + +	/* clearing read buffer full intr */ +	temp = HDMI_READ(HDMI_HISR); +	HDMI_WRITE(HDMI_HISR, temp | HDMI_INTR_I2C_FULL); +	HDMI_READ(HDMI_HISR); + +	/* continue read transaction */ +	temp = HDMI_READ(HDMI_HI2CHCR); +	HDMI_WRITE(HDMI_HI2CHCR, temp | HI2C_READ_CONTINUE); +	HDMI_READ(HDMI_HI2CHCR); + +	i2c_dev->status = I2C_READ_DONE; +	return; +} + +static void hdmi_i2c_transaction_done(struct oaktrail_hdmi_dev *hdmi_dev) +{ +	struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev; +	u32 temp; + +	/* clear transaction done intr */ +	temp = HDMI_READ(HDMI_HISR); +	HDMI_WRITE(HDMI_HISR, temp | HDMI_INTR_I2C_DONE); +	HDMI_READ(HDMI_HISR); + + +	temp = HDMI_READ(HDMI_HI2CHCR); +	HDMI_WRITE(HDMI_HI2CHCR, temp & ~HI2C_ENABLE_TRANSACTION); +	HDMI_READ(HDMI_HI2CHCR); + +	i2c_dev->status = I2C_TRANSACTION_DONE; +	return; +} + +static irqreturn_t oaktrail_hdmi_i2c_handler(int this_irq, void *dev) +{ +	struct oaktrail_hdmi_dev *hdmi_dev = dev; +	struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev; +	u32 stat; + +	stat = HDMI_READ(HDMI_HISR); + +	if (stat & HDMI_INTR_HPD) { +		HDMI_WRITE(HDMI_HISR, stat | HDMI_INTR_HPD); +		HDMI_READ(HDMI_HISR); +	} + +	if (stat & HDMI_INTR_I2C_FULL) +		hdmi_i2c_read(hdmi_dev); + +	if (stat & HDMI_INTR_I2C_DONE) +		hdmi_i2c_transaction_done(hdmi_dev); + +	complete(&i2c_dev->complete); + +	return IRQ_HANDLED; +} + +/* + * choose alternate function 2 of GPIO pin 52, 53, + * which is used by HDMI I2C logic + */ +static void oaktrail_hdmi_i2c_gpio_fix(void) +{ +	void __iomem *base; +	unsigned int gpio_base = 0xff12c000; +	int gpio_len = 0x1000; +	u32 temp; + +	base = ioremap((resource_size_t)gpio_base, gpio_len); +	if (base == NULL) { +		DRM_ERROR("gpio ioremap fail\n"); +		return; +	} + +	temp = readl(base + 0x44); +	DRM_DEBUG_DRIVER("old gpio val %x\n", temp); +	writel((temp | 0x00000a00), (base +  0x44)); +	temp = readl(base + 0x44); +	DRM_DEBUG_DRIVER("new gpio val %x\n", temp); + +	iounmap(base); +} + +int oaktrail_hdmi_i2c_init(struct pci_dev *dev) +{ +	struct oaktrail_hdmi_dev *hdmi_dev; +	struct hdmi_i2c_dev *i2c_dev; +	int ret; + +	hdmi_dev = pci_get_drvdata(dev); + +	i2c_dev = kzalloc(sizeof(struct hdmi_i2c_dev), GFP_KERNEL); +	if (i2c_dev == NULL) { +		DRM_ERROR("Can't allocate interface\n"); +		ret = -ENOMEM; +		goto exit; +	} + +	i2c_dev->adap = &oaktrail_hdmi_i2c_adapter; +	i2c_dev->status = I2C_STAT_INIT; +	init_completion(&i2c_dev->complete); +	mutex_init(&i2c_dev->i2c_lock); +	i2c_set_adapdata(&oaktrail_hdmi_i2c_adapter, hdmi_dev); +	hdmi_dev->i2c_dev = i2c_dev; + +	/* Enable HDMI I2C function on gpio */ +	oaktrail_hdmi_i2c_gpio_fix(); + +	/* request irq */ +	ret = request_irq(dev->irq, oaktrail_hdmi_i2c_handler, IRQF_SHARED, +			  oaktrail_hdmi_i2c_adapter.name, hdmi_dev); +	if (ret) { +		DRM_ERROR("Failed to request IRQ for I2C controller\n"); +		goto err; +	} + +	/* Adapter registration */ +	ret = i2c_add_numbered_adapter(&oaktrail_hdmi_i2c_adapter); +	return ret; + +err: +	kfree(i2c_dev); +exit: +	return ret; +} + +void oaktrail_hdmi_i2c_exit(struct pci_dev *dev) +{ +	struct oaktrail_hdmi_dev *hdmi_dev; +	struct hdmi_i2c_dev *i2c_dev; + +	hdmi_dev = pci_get_drvdata(dev); +	i2c_del_adapter(&oaktrail_hdmi_i2c_adapter); + +	i2c_dev = hdmi_dev->i2c_dev; +	kfree(i2c_dev); +	free_irq(dev->irq, hdmi_dev); +} diff --git a/drivers/gpu/drm/gma500/oaktrail_lvds.c b/drivers/gpu/drm/gma500/oaktrail_lvds.c new file mode 100644 index 00000000000..9b099468a5d --- /dev/null +++ b/drivers/gpu/drm/gma500/oaktrail_lvds.c @@ -0,0 +1,425 @@ +/* + * Copyright © 2006-2009 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + *	Dave Airlie <airlied@linux.ie> + *	Jesse Barnes <jesse.barnes@intel.com> + */ + +#include <linux/i2c.h> +#include <drm/drmP.h> +#include <asm/intel-mid.h> + +#include "intel_bios.h" +#include "psb_drv.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "power.h" +#include <linux/pm_runtime.h> + +/* The max/min PWM frequency in BPCR[31:17] - */ +/* The smallest number is 1 (not 0) that can fit in the + * 15-bit field of the and then*/ +/* shifts to the left by one bit to get the actual 16-bit + * value that the 15-bits correspond to.*/ +#define MRST_BLC_MAX_PWM_REG_FREQ	    0xFFFF +#define BRIGHTNESS_MAX_LEVEL 100 + +/** + * Sets the power state for the panel. + */ +static void oaktrail_lvds_set_power(struct drm_device *dev, +				struct gma_encoder *gma_encoder, +				bool on) +{ +	u32 pp_status; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (!gma_power_begin(dev, true)) +		return; + +	if (on) { +		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) | +			  POWER_TARGET_ON); +		do { +			pp_status = REG_READ(PP_STATUS); +		} while ((pp_status & (PP_ON | PP_READY)) == PP_READY); +		dev_priv->is_lvds_on = true; +		if (dev_priv->ops->lvds_bl_power) +			dev_priv->ops->lvds_bl_power(dev, true); +	} else { +		if (dev_priv->ops->lvds_bl_power) +			dev_priv->ops->lvds_bl_power(dev, false); +		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) & +			  ~POWER_TARGET_ON); +		do { +			pp_status = REG_READ(PP_STATUS); +		} while (pp_status & PP_ON); +		dev_priv->is_lvds_on = false; +		pm_request_idle(&dev->pdev->dev); +	} +	gma_power_end(dev); +} + +static void oaktrail_lvds_dpms(struct drm_encoder *encoder, int mode) +{ +	struct drm_device *dev = encoder->dev; +	struct gma_encoder *gma_encoder = to_gma_encoder(encoder); + +	if (mode == DRM_MODE_DPMS_ON) +		oaktrail_lvds_set_power(dev, gma_encoder, true); +	else +		oaktrail_lvds_set_power(dev, gma_encoder, false); + +	/* XXX: We never power down the LVDS pairs. */ +} + +static void oaktrail_lvds_mode_set(struct drm_encoder *encoder, +			       struct drm_display_mode *mode, +			       struct drm_display_mode *adjusted_mode) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; +	struct drm_mode_config *mode_config = &dev->mode_config; +	struct drm_connector *connector = NULL; +	struct drm_crtc *crtc = encoder->crtc; +	u32 lvds_port; +	uint64_t v = DRM_MODE_SCALE_FULLSCREEN; + +	if (!gma_power_begin(dev, true)) +		return; + +	/* +	 * The LVDS pin pair will already have been turned on in the +	 * psb_intel_crtc_mode_set since it has a large impact on the DPLL +	 * settings. +	 */ +	lvds_port = (REG_READ(LVDS) & +		    (~LVDS_PIPEB_SELECT)) | +		    LVDS_PORT_EN | +		    LVDS_BORDER_EN; + +	/* If the firmware says dither on Moorestown, or the BIOS does +	   on Oaktrail then enable dithering */ +	if (mode_dev->panel_wants_dither || dev_priv->lvds_dither) +		lvds_port |= MRST_PANEL_8TO6_DITHER_ENABLE; + +	REG_WRITE(LVDS, lvds_port); + +	/* Find the connector we're trying to set up */ +	list_for_each_entry(connector, &mode_config->connector_list, head) { +		if (!connector->encoder || connector->encoder->crtc != crtc) +			continue; +	} + +	if (!connector) { +		DRM_ERROR("Couldn't find connector when setting mode"); +		return; +	} + +	drm_object_property_get_value( +		&connector->base, +		dev->mode_config.scaling_mode_property, +		&v); + +	if (v == DRM_MODE_SCALE_NO_SCALE) +		REG_WRITE(PFIT_CONTROL, 0); +	else if (v == DRM_MODE_SCALE_ASPECT) { +		if ((mode->vdisplay != adjusted_mode->crtc_vdisplay) || +		    (mode->hdisplay != adjusted_mode->crtc_hdisplay)) { +			if ((adjusted_mode->crtc_hdisplay * mode->vdisplay) == +			    (mode->hdisplay * adjusted_mode->crtc_vdisplay)) +				REG_WRITE(PFIT_CONTROL, PFIT_ENABLE); +			else if ((adjusted_mode->crtc_hdisplay * +				mode->vdisplay) > (mode->hdisplay * +				adjusted_mode->crtc_vdisplay)) +				REG_WRITE(PFIT_CONTROL, PFIT_ENABLE | +					  PFIT_SCALING_MODE_PILLARBOX); +			else +				REG_WRITE(PFIT_CONTROL, PFIT_ENABLE | +					  PFIT_SCALING_MODE_LETTERBOX); +		} else +			REG_WRITE(PFIT_CONTROL, PFIT_ENABLE); +	} else /*(v == DRM_MODE_SCALE_FULLSCREEN)*/ +		REG_WRITE(PFIT_CONTROL, PFIT_ENABLE); + +	gma_power_end(dev); +} + +static void oaktrail_lvds_prepare(struct drm_encoder *encoder) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_encoder *gma_encoder = to_gma_encoder(encoder); +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; + +	if (!gma_power_begin(dev, true)) +		return; + +	mode_dev->saveBLC_PWM_CTL = REG_READ(BLC_PWM_CTL); +	mode_dev->backlight_duty_cycle = (mode_dev->saveBLC_PWM_CTL & +					  BACKLIGHT_DUTY_CYCLE_MASK); +	oaktrail_lvds_set_power(dev, gma_encoder, false); +	gma_power_end(dev); +} + +static u32 oaktrail_lvds_get_max_backlight(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 ret; + +	if (gma_power_begin(dev, false)) { +		ret = ((REG_READ(BLC_PWM_CTL) & +			  BACKLIGHT_MODULATION_FREQ_MASK) >> +			  BACKLIGHT_MODULATION_FREQ_SHIFT) * 2; + +		gma_power_end(dev); +	} else +		ret = ((dev_priv->regs.saveBLC_PWM_CTL & +			  BACKLIGHT_MODULATION_FREQ_MASK) >> +			  BACKLIGHT_MODULATION_FREQ_SHIFT) * 2; + +	return ret; +} + +static void oaktrail_lvds_commit(struct drm_encoder *encoder) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_encoder *gma_encoder = to_gma_encoder(encoder); +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; + +	if (mode_dev->backlight_duty_cycle == 0) +		mode_dev->backlight_duty_cycle = +					oaktrail_lvds_get_max_backlight(dev); +	oaktrail_lvds_set_power(dev, gma_encoder, true); +} + +static const struct drm_encoder_helper_funcs oaktrail_lvds_helper_funcs = { +	.dpms = oaktrail_lvds_dpms, +	.mode_fixup = psb_intel_lvds_mode_fixup, +	.prepare = oaktrail_lvds_prepare, +	.mode_set = oaktrail_lvds_mode_set, +	.commit = oaktrail_lvds_commit, +}; + +/* Returns the panel fixed mode from configuration. */ + +static void oaktrail_lvds_get_configuration_mode(struct drm_device *dev, +					struct psb_intel_mode_device *mode_dev) +{ +	struct drm_display_mode *mode = NULL; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct oaktrail_timing_info *ti = &dev_priv->gct_data.DTD; + +	mode_dev->panel_fixed_mode = NULL; + +	/* Use the firmware provided data on Moorestown */ +	if (dev_priv->has_gct) { +		mode = kzalloc(sizeof(*mode), GFP_KERNEL); +		if (!mode) +			return; + +		mode->hdisplay = (ti->hactive_hi << 8) | ti->hactive_lo; +		mode->vdisplay = (ti->vactive_hi << 8) | ti->vactive_lo; +		mode->hsync_start = mode->hdisplay + \ +				((ti->hsync_offset_hi << 8) | \ +				ti->hsync_offset_lo); +		mode->hsync_end = mode->hsync_start + \ +				((ti->hsync_pulse_width_hi << 8) | \ +				ti->hsync_pulse_width_lo); +		mode->htotal = mode->hdisplay + ((ti->hblank_hi << 8) | \ +							ti->hblank_lo); +		mode->vsync_start = \ +			mode->vdisplay + ((ti->vsync_offset_hi << 4) | \ +						ti->vsync_offset_lo); +		mode->vsync_end = \ +			mode->vsync_start + ((ti->vsync_pulse_width_hi << 4) | \ +						ti->vsync_pulse_width_lo); +		mode->vtotal = mode->vdisplay + \ +				((ti->vblank_hi << 8) | ti->vblank_lo); +		mode->clock = ti->pixel_clock * 10; +#if 0 +		printk(KERN_INFO "hdisplay is %d\n", mode->hdisplay); +		printk(KERN_INFO "vdisplay is %d\n", mode->vdisplay); +		printk(KERN_INFO "HSS is %d\n", mode->hsync_start); +		printk(KERN_INFO "HSE is %d\n", mode->hsync_end); +		printk(KERN_INFO "htotal is %d\n", mode->htotal); +		printk(KERN_INFO "VSS is %d\n", mode->vsync_start); +		printk(KERN_INFO "VSE is %d\n", mode->vsync_end); +		printk(KERN_INFO "vtotal is %d\n", mode->vtotal); +		printk(KERN_INFO "clock is %d\n", mode->clock); +#endif +		mode_dev->panel_fixed_mode = mode; +	} + +	/* Use the BIOS VBT mode if available */ +	if (mode_dev->panel_fixed_mode == NULL && mode_dev->vbt_mode) +		mode_dev->panel_fixed_mode = drm_mode_duplicate(dev, +						mode_dev->vbt_mode); + +	/* Then try the LVDS VBT mode */ +	if (mode_dev->panel_fixed_mode == NULL) +		if (dev_priv->lfp_lvds_vbt_mode) +			mode_dev->panel_fixed_mode = +				drm_mode_duplicate(dev, +					dev_priv->lfp_lvds_vbt_mode); + +	/* If we still got no mode then bail */ +	if (mode_dev->panel_fixed_mode == NULL) +		return; + +	drm_mode_set_name(mode_dev->panel_fixed_mode); +	drm_mode_set_crtcinfo(mode_dev->panel_fixed_mode, 0); +} + +/** + * oaktrail_lvds_init - setup LVDS connectors on this device + * @dev: drm device + * + * Create the connector, register the LVDS DDC bus, and try to figure out what + * modes we can display on the LVDS panel (if present). + */ +void oaktrail_lvds_init(struct drm_device *dev, +		    struct psb_intel_mode_device *mode_dev) +{ +	struct gma_encoder *gma_encoder; +	struct gma_connector *gma_connector; +	struct drm_connector *connector; +	struct drm_encoder *encoder; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct edid *edid; +	struct i2c_adapter *i2c_adap; +	struct drm_display_mode *scan;	/* *modes, *bios_mode; */ + +	gma_encoder = kzalloc(sizeof(struct gma_encoder), GFP_KERNEL); +	if (!gma_encoder) +		return; + +	gma_connector = kzalloc(sizeof(struct gma_connector), GFP_KERNEL); +	if (!gma_connector) +		goto failed_connector; + +	connector = &gma_connector->base; +	encoder = &gma_encoder->base; +	dev_priv->is_lvds_on = true; +	drm_connector_init(dev, connector, +			   &psb_intel_lvds_connector_funcs, +			   DRM_MODE_CONNECTOR_LVDS); + +	drm_encoder_init(dev, encoder, &psb_intel_lvds_enc_funcs, +			 DRM_MODE_ENCODER_LVDS); + +	gma_connector_attach_encoder(gma_connector, gma_encoder); +	gma_encoder->type = INTEL_OUTPUT_LVDS; + +	drm_encoder_helper_add(encoder, &oaktrail_lvds_helper_funcs); +	drm_connector_helper_add(connector, +				 &psb_intel_lvds_connector_helper_funcs); +	connector->display_info.subpixel_order = SubPixelHorizontalRGB; +	connector->interlace_allowed = false; +	connector->doublescan_allowed = false; + +	drm_object_attach_property(&connector->base, +					dev->mode_config.scaling_mode_property, +					DRM_MODE_SCALE_FULLSCREEN); +	drm_object_attach_property(&connector->base, +					dev_priv->backlight_property, +					BRIGHTNESS_MAX_LEVEL); + +	mode_dev->panel_wants_dither = false; +	if (dev_priv->has_gct) +		mode_dev->panel_wants_dither = (dev_priv->gct_data. +			Panel_Port_Control & MRST_PANEL_8TO6_DITHER_ENABLE); +        if (dev_priv->lvds_dither) +                mode_dev->panel_wants_dither = 1; + +	/* +	 * LVDS discovery: +	 * 1) check for EDID on DDC +	 * 2) check for VBT data +	 * 3) check to see if LVDS is already on +	 *    if none of the above, no panel +	 * 4) make sure lid is open +	 *    if closed, act like it's not there for now +	 */ + +	mutex_lock(&dev->mode_config.mutex); +	i2c_adap = i2c_get_adapter(dev_priv->ops->i2c_bus); +	if (i2c_adap == NULL) +		dev_err(dev->dev, "No ddc adapter available!\n"); +	/* +	 * Attempt to get the fixed panel mode from DDC.  Assume that the +	 * preferred mode is the right one. +	 */ +	if (i2c_adap) { +		edid = drm_get_edid(connector, i2c_adap); +		if (edid) { +			drm_mode_connector_update_edid_property(connector, +									edid); +			drm_add_edid_modes(connector, edid); +			kfree(edid); +		} + +		list_for_each_entry(scan, &connector->probed_modes, head) { +			if (scan->type & DRM_MODE_TYPE_PREFERRED) { +				mode_dev->panel_fixed_mode = +				    drm_mode_duplicate(dev, scan); +				goto out;	/* FIXME: check for quirks */ +			} +		} +	} +	/* +	 * If we didn't get EDID, try geting panel timing +	 * from configuration data +	 */ +	oaktrail_lvds_get_configuration_mode(dev, mode_dev); + +	if (mode_dev->panel_fixed_mode) { +		mode_dev->panel_fixed_mode->type |= DRM_MODE_TYPE_PREFERRED; +		goto out;	/* FIXME: check for quirks */ +	} + +	/* If we still don't have a mode after all that, give up. */ +	if (!mode_dev->panel_fixed_mode) { +		dev_err(dev->dev, "Found no modes on the lvds, ignoring the LVDS\n"); +		goto failed_find; +	} + +out: +	mutex_unlock(&dev->mode_config.mutex); + +	drm_sysfs_connector_add(connector); +	return; + +failed_find: +	mutex_unlock(&dev->mode_config.mutex); + +	dev_dbg(dev->dev, "No LVDS modes found, disabling.\n"); +	if (gma_encoder->ddc_bus) +		psb_intel_i2c_destroy(gma_encoder->ddc_bus); + +/* failed_ddc: */ + +	drm_encoder_cleanup(encoder); +	drm_connector_cleanup(connector); +	kfree(gma_connector); +failed_connector: +	kfree(gma_encoder); +} + diff --git a/drivers/gpu/drm/gma500/opregion.c b/drivers/gpu/drm/gma500/opregion.c new file mode 100644 index 00000000000..ab696ca7eee --- /dev/null +++ b/drivers/gpu/drm/gma500/opregion.c @@ -0,0 +1,357 @@ +/* + * Copyright 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ +#include <linux/acpi.h> +#include "psb_drv.h" +#include "psb_intel_reg.h" + +#define PCI_ASLE 0xe4 +#define PCI_ASLS 0xfc + +#define OPREGION_HEADER_OFFSET 0 +#define OPREGION_ACPI_OFFSET   0x100 +#define   ACPI_CLID 0x01ac /* current lid state indicator */ +#define   ACPI_CDCK 0x01b0 /* current docking state indicator */ +#define OPREGION_SWSCI_OFFSET  0x200 +#define OPREGION_ASLE_OFFSET   0x300 +#define OPREGION_VBT_OFFSET    0x400 + +#define OPREGION_SIGNATURE "IntelGraphicsMem" +#define MBOX_ACPI      (1<<0) +#define MBOX_SWSCI     (1<<1) +#define MBOX_ASLE      (1<<2) + +struct opregion_header { +	u8 signature[16]; +	u32 size; +	u32 opregion_ver; +	u8 bios_ver[32]; +	u8 vbios_ver[16]; +	u8 driver_ver[16]; +	u32 mboxes; +	u8 reserved[164]; +} __packed; + +/* OpRegion mailbox #1: public ACPI methods */ +struct opregion_acpi { +	u32 drdy;	/* driver readiness */ +	u32 csts;	/* notification status */ +	u32 cevt;	/* current event */ +	u8 rsvd1[20]; +	u32 didl[8];	/* supported display devices ID list */ +	u32 cpdl[8];	/* currently presented display list */ +	u32 cadl[8];	/* currently active display list */ +	u32 nadl[8];	/* next active devices list */ +	u32 aslp;	/* ASL sleep time-out */ +	u32 tidx;	/* toggle table index */ +	u32 chpd;	/* current hotplug enable indicator */ +	u32 clid;	/* current lid state*/ +	u32 cdck;	/* current docking state */ +	u32 sxsw;	/* Sx state resume */ +	u32 evts;	/* ASL supported events */ +	u32 cnot;	/* current OS notification */ +	u32 nrdy;	/* driver status */ +	u8 rsvd2[60]; +} __packed; + +/* OpRegion mailbox #2: SWSCI */ +struct opregion_swsci { +	/*FIXME: add it later*/ +} __packed; + +/* OpRegion mailbox #3: ASLE */ +struct opregion_asle { +	u32 ardy;	/* driver readiness */ +	u32 aslc;	/* ASLE interrupt command */ +	u32 tche;	/* technology enabled indicator */ +	u32 alsi;	/* current ALS illuminance reading */ +	u32 bclp;	/* backlight brightness to set */ +	u32 pfit;	/* panel fitting state */ +	u32 cblv;	/* current brightness level */ +	u16 bclm[20];	/* backlight level duty cycle mapping table */ +	u32 cpfm;	/* current panel fitting mode */ +	u32 epfm;	/* enabled panel fitting modes */ +	u8 plut[74];	/* panel LUT and identifier */ +	u32 pfmb;	/* PWM freq and min brightness */ +	u8 rsvd[102]; +} __packed; + +/* ASLE irq request bits */ +#define ASLE_SET_ALS_ILLUM     (1 << 0) +#define ASLE_SET_BACKLIGHT     (1 << 1) +#define ASLE_SET_PFIT          (1 << 2) +#define ASLE_SET_PWM_FREQ      (1 << 3) +#define ASLE_REQ_MSK           0xf + +/* response bits of ASLE irq request */ +#define ASLE_ALS_ILLUM_FAILED   (1<<10) +#define ASLE_BACKLIGHT_FAILED   (1<<12) +#define ASLE_PFIT_FAILED        (1<<14) +#define ASLE_PWM_FREQ_FAILED    (1<<16) + +/* ASLE backlight brightness to set */ +#define ASLE_BCLP_VALID                (1<<31) +#define ASLE_BCLP_MSK          (~(1<<31)) + +/* ASLE panel fitting request */ +#define ASLE_PFIT_VALID         (1<<31) +#define ASLE_PFIT_CENTER (1<<0) +#define ASLE_PFIT_STRETCH_TEXT (1<<1) +#define ASLE_PFIT_STRETCH_GFX (1<<2) + +/* response bits of ASLE irq request */ +#define ASLE_ALS_ILLUM_FAILED	(1<<10) +#define ASLE_BACKLIGHT_FAILED	(1<<12) +#define ASLE_PFIT_FAILED	(1<<14) +#define ASLE_PWM_FREQ_FAILED	(1<<16) + +/* ASLE backlight brightness to set */ +#define ASLE_BCLP_VALID                (1<<31) +#define ASLE_BCLP_MSK          (~(1<<31)) + +/* ASLE panel fitting request */ +#define ASLE_PFIT_VALID         (1<<31) +#define ASLE_PFIT_CENTER (1<<0) +#define ASLE_PFIT_STRETCH_TEXT (1<<1) +#define ASLE_PFIT_STRETCH_GFX (1<<2) + +/* PWM frequency and minimum brightness */ +#define ASLE_PFMB_BRIGHTNESS_MASK (0xff) +#define ASLE_PFMB_BRIGHTNESS_VALID (1<<8) +#define ASLE_PFMB_PWM_MASK (0x7ffffe00) +#define ASLE_PFMB_PWM_VALID (1<<31) + +#define ASLE_CBLV_VALID         (1<<31) + +static struct psb_intel_opregion *system_opregion; + +static u32 asle_set_backlight(struct drm_device *dev, u32 bclp) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct opregion_asle *asle = dev_priv->opregion.asle; +	struct backlight_device *bd = dev_priv->backlight_device; + +	DRM_DEBUG_DRIVER("asle set backlight %x\n", bclp); + +	if (!(bclp & ASLE_BCLP_VALID)) +		return ASLE_BACKLIGHT_FAILED; + +	if (bd == NULL) +		return ASLE_BACKLIGHT_FAILED; + +	bclp &= ASLE_BCLP_MSK; +	if (bclp > 255) +		return ASLE_BACKLIGHT_FAILED; + +	if (config_enabled(CONFIG_BACKLIGHT_CLASS_DEVICE)) { +		int max = bd->props.max_brightness; +		gma_backlight_set(dev, bclp * max / 255); +	} + +	asle->cblv = (bclp * 0x64) / 0xff | ASLE_CBLV_VALID; + +	return 0; +} + +static void psb_intel_opregion_asle_work(struct work_struct *work) +{ +	struct psb_intel_opregion *opregion = +		container_of(work, struct psb_intel_opregion, asle_work); +	struct drm_psb_private *dev_priv = +		container_of(opregion, struct drm_psb_private, opregion); +	struct opregion_asle *asle = opregion->asle; +	u32 asle_stat = 0; +	u32 asle_req; + +	if (!asle) +		return; + +	asle_req = asle->aslc & ASLE_REQ_MSK; +	if (!asle_req) { +		DRM_DEBUG_DRIVER("non asle set request??\n"); +		return; +	} + +	if (asle_req & ASLE_SET_BACKLIGHT) +		asle_stat |= asle_set_backlight(dev_priv->dev, asle->bclp); + +	asle->aslc = asle_stat; + +} + +void psb_intel_opregion_asle_intr(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (dev_priv->opregion.asle) +		schedule_work(&dev_priv->opregion.asle_work); +} + +#define ASLE_ALS_EN    (1<<0) +#define ASLE_BLC_EN    (1<<1) +#define ASLE_PFIT_EN   (1<<2) +#define ASLE_PFMB_EN   (1<<3) + +void psb_intel_opregion_enable_asle(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct opregion_asle *asle = dev_priv->opregion.asle; + +	if (asle && system_opregion ) { +		/* Don't do this on Medfield or other non PC like devices, they +		   use the bit for something different altogether */ +		psb_enable_pipestat(dev_priv, 0, PIPE_LEGACY_BLC_EVENT_ENABLE); +		psb_enable_pipestat(dev_priv, 1, PIPE_LEGACY_BLC_EVENT_ENABLE); + +		asle->tche = ASLE_ALS_EN | ASLE_BLC_EN | ASLE_PFIT_EN +								| ASLE_PFMB_EN; +		asle->ardy = 1; +	} +} + +#define ACPI_EV_DISPLAY_SWITCH (1<<0) +#define ACPI_EV_LID            (1<<1) +#define ACPI_EV_DOCK           (1<<2) + + +static int psb_intel_opregion_video_event(struct notifier_block *nb, +					  unsigned long val, void *data) +{ +	/* The only video events relevant to opregion are 0x80. These indicate +	   either a docking event, lid switch or display switch request. In +	   Linux, these are handled by the dock, button and video drivers. +	   We might want to fix the video driver to be opregion-aware in +	   future, but right now we just indicate to the firmware that the +	   request has been handled */ + +	struct opregion_acpi *acpi; + +	if (!system_opregion) +		return NOTIFY_DONE; + +	acpi = system_opregion->acpi; +	acpi->csts = 0; + +	return NOTIFY_OK; +} + +static struct notifier_block psb_intel_opregion_notifier = { +	.notifier_call = psb_intel_opregion_video_event, +}; + +void psb_intel_opregion_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_opregion *opregion = &dev_priv->opregion; + +	if (!opregion->header) +		return; + +	if (opregion->acpi) { +		/* Notify BIOS we are ready to handle ACPI video ext notifs. +		 * Right now, all the events are handled by the ACPI video +		 * module. We don't actually need to do anything with them. */ +		opregion->acpi->csts = 0; +		opregion->acpi->drdy = 1; + +		system_opregion = opregion; +		register_acpi_notifier(&psb_intel_opregion_notifier); +	} +} + +void psb_intel_opregion_fini(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_opregion *opregion = &dev_priv->opregion; + +	if (!opregion->header) +		return; + +	if (opregion->acpi) { +		opregion->acpi->drdy = 0; + +		system_opregion = NULL; +		unregister_acpi_notifier(&psb_intel_opregion_notifier); +	} + +	cancel_work_sync(&opregion->asle_work); + +	/* just clear all opregion memory pointers now */ +	iounmap(opregion->header); +	opregion->header = NULL; +	opregion->acpi = NULL; +	opregion->swsci = NULL; +	opregion->asle = NULL; +	opregion->vbt = NULL; +} + +int psb_intel_opregion_setup(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_opregion *opregion = &dev_priv->opregion; +	u32 opregion_phy, mboxes; +	void __iomem *base; +	int err = 0; + +	pci_read_config_dword(dev->pdev, PCI_ASLS, &opregion_phy); +	if (opregion_phy == 0) { +		DRM_DEBUG_DRIVER("ACPI Opregion not supported\n"); +		return -ENOTSUPP; +	} + +	INIT_WORK(&opregion->asle_work, psb_intel_opregion_asle_work); + +	DRM_DEBUG("OpRegion detected at 0x%8x\n", opregion_phy); +	base = acpi_os_ioremap(opregion_phy, 8*1024); +	if (!base) +		return -ENOMEM; + +	if (memcmp(base, OPREGION_SIGNATURE, 16)) { +		DRM_DEBUG_DRIVER("opregion signature mismatch\n"); +		err = -EINVAL; +		goto err_out; +	} + +	opregion->header = base; +	opregion->vbt = base + OPREGION_VBT_OFFSET; + +	opregion->lid_state = base + ACPI_CLID; + +	mboxes = opregion->header->mboxes; +	if (mboxes & MBOX_ACPI) { +		DRM_DEBUG_DRIVER("Public ACPI methods supported\n"); +		opregion->acpi = base + OPREGION_ACPI_OFFSET; +	} + +	if (mboxes & MBOX_ASLE) { +		DRM_DEBUG_DRIVER("ASLE supported\n"); +		opregion->asle = base + OPREGION_ASLE_OFFSET; +	} + +	return 0; + +err_out: +	iounmap(base); +	return err; +} + diff --git a/drivers/gpu/drm/gma500/opregion.h b/drivers/gpu/drm/gma500/opregion.h new file mode 100644 index 00000000000..4a90f8b0e16 --- /dev/null +++ b/drivers/gpu/drm/gma500/opregion.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#if defined(CONFIG_ACPI) +extern void psb_intel_opregion_asle_intr(struct drm_device *dev); +extern void psb_intel_opregion_init(struct drm_device *dev); +extern void psb_intel_opregion_fini(struct drm_device *dev); +extern int psb_intel_opregion_setup(struct drm_device *dev); +extern void psb_intel_opregion_enable_asle(struct drm_device *dev); + +#else + +extern inline void psb_intel_opregion_asle_intr(struct drm_device *dev) +{ +} + +extern inline void psb_intel_opregion_init(struct drm_device *dev) +{ +} + +extern inline void psb_intel_opregion_fini(struct drm_device *dev) +{ +} + +extern inline int psb_intel_opregion_setup(struct drm_device *dev) +{ +	return 0; +} + +extern inline void psb_intel_opregion_enable_asle(struct drm_device *dev) +{ +} +#endif diff --git a/drivers/gpu/drm/gma500/power.c b/drivers/gpu/drm/gma500/power.c new file mode 100644 index 00000000000..b6b135fcd59 --- /dev/null +++ b/drivers/gpu/drm/gma500/power.c @@ -0,0 +1,332 @@ +/************************************************************************** + * Copyright (c) 2009-2011, Intel Corporation. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + *    Benjamin Defnet <benjamin.r.defnet@intel.com> + *    Rajesh Poornachandran <rajesh.poornachandran@intel.com> + * Massively reworked + *    Alan Cox <alan@linux.intel.com> + */ + +#include "power.h" +#include "psb_drv.h" +#include "psb_reg.h" +#include "psb_intel_reg.h" +#include <linux/mutex.h> +#include <linux/pm_runtime.h> + +static struct mutex power_mutex;	/* Serialize power ops */ +static spinlock_t power_ctrl_lock;	/* Serialize power claim */ + +/** + *	gma_power_init		-	initialise power manager + *	@dev: our device + * + *	Set up for power management tracking of our hardware. + */ +void gma_power_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	/* FIXME: Move APM/OSPM base into relevant device code */ +	dev_priv->apm_base = dev_priv->apm_reg & 0xffff; +	dev_priv->ospm_base &= 0xffff; + +	dev_priv->display_power = true;	/* We start active */ +	dev_priv->display_count = 0;	/* Currently no users */ +	dev_priv->suspended = false;	/* And not suspended */ +	spin_lock_init(&power_ctrl_lock); +	mutex_init(&power_mutex); + +	if (dev_priv->ops->init_pm) +		dev_priv->ops->init_pm(dev); +} + +/** + *	gma_power_uninit	-	end power manager + *	@dev: device to end for + * + *	Undo the effects of gma_power_init + */ +void gma_power_uninit(struct drm_device *dev) +{ +	pm_runtime_disable(&dev->pdev->dev); +	pm_runtime_set_suspended(&dev->pdev->dev); +} + +/** + *	gma_suspend_display	-	suspend the display logic + *	@dev: our DRM device + * + *	Suspend the display logic of the graphics interface + */ +static void gma_suspend_display(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (dev_priv->suspended) +		return; +	dev_priv->ops->save_regs(dev); +	dev_priv->ops->power_down(dev); +	dev_priv->display_power = false; +} + +/** + *	gma_resume_display	-	resume display side logic + * + *	Resume the display hardware restoring state and enabling + *	as necessary. + */ +static void gma_resume_display(struct pci_dev *pdev) +{ +	struct drm_device *dev = pci_get_drvdata(pdev); +	struct drm_psb_private *dev_priv = dev->dev_private; + +	/* turn on the display power island */ +	dev_priv->ops->power_up(dev); +	dev_priv->suspended = false; +	dev_priv->display_power = true; + +	PSB_WVDC32(dev_priv->pge_ctl | _PSB_PGETBL_ENABLED, PSB_PGETBL_CTL); +	pci_write_config_word(pdev, PSB_GMCH_CTRL, +			dev_priv->gmch_ctrl | _PSB_GMCH_ENABLED); + +	psb_gtt_restore(dev); /* Rebuild our GTT mappings */ +	dev_priv->ops->restore_regs(dev); +} + +/** + *	gma_suspend_pci		-	suspend PCI side + *	@pdev: PCI device + * + *	Perform the suspend processing on our PCI device state + */ +static void gma_suspend_pci(struct pci_dev *pdev) +{ +	struct drm_device *dev = pci_get_drvdata(pdev); +	struct drm_psb_private *dev_priv = dev->dev_private; +	int bsm, vbt; + +	if (dev_priv->suspended) +		return; + +	pci_save_state(pdev); +	pci_read_config_dword(pdev, 0x5C, &bsm); +	dev_priv->regs.saveBSM = bsm; +	pci_read_config_dword(pdev, 0xFC, &vbt); +	dev_priv->regs.saveVBT = vbt; +	pci_read_config_dword(pdev, PSB_PCIx_MSI_ADDR_LOC, &dev_priv->msi_addr); +	pci_read_config_dword(pdev, PSB_PCIx_MSI_DATA_LOC, &dev_priv->msi_data); + +	pci_disable_device(pdev); +	pci_set_power_state(pdev, PCI_D3hot); + +	dev_priv->suspended = true; +} + +/** + *	gma_resume_pci		-	resume helper + *	@dev: our PCI device + * + *	Perform the resume processing on our PCI device state - rewrite + *	register state and re-enable the PCI device + */ +static bool gma_resume_pci(struct pci_dev *pdev) +{ +	struct drm_device *dev = pci_get_drvdata(pdev); +	struct drm_psb_private *dev_priv = dev->dev_private; +	int ret; + +	if (!dev_priv->suspended) +		return true; + +	pci_set_power_state(pdev, PCI_D0); +	pci_restore_state(pdev); +	pci_write_config_dword(pdev, 0x5c, dev_priv->regs.saveBSM); +	pci_write_config_dword(pdev, 0xFC, dev_priv->regs.saveVBT); +	/* restoring MSI address and data in PCIx space */ +	pci_write_config_dword(pdev, PSB_PCIx_MSI_ADDR_LOC, dev_priv->msi_addr); +	pci_write_config_dword(pdev, PSB_PCIx_MSI_DATA_LOC, dev_priv->msi_data); +	ret = pci_enable_device(pdev); + +	if (ret != 0) +		dev_err(&pdev->dev, "pci_enable failed: %d\n", ret); +	else +		dev_priv->suspended = false; +	return !dev_priv->suspended; +} + +/** + *	gma_power_suspend		-	bus callback for suspend + *	@pdev: our PCI device + *	@state: suspend type + * + *	Called back by the PCI layer during a suspend of the system. We + *	perform the necessary shut down steps and save enough state that + *	we can undo this when resume is called. + */ +int gma_power_suspend(struct device *_dev) +{ +	struct pci_dev *pdev = container_of(_dev, struct pci_dev, dev); +	struct drm_device *dev = pci_get_drvdata(pdev); +	struct drm_psb_private *dev_priv = dev->dev_private; + +	mutex_lock(&power_mutex); +	if (!dev_priv->suspended) { +		if (dev_priv->display_count) { +			mutex_unlock(&power_mutex); +			dev_err(dev->dev, "GPU hardware busy, cannot suspend\n"); +			return -EBUSY; +		} +		psb_irq_uninstall(dev); +		gma_suspend_display(dev); +		gma_suspend_pci(pdev); +	} +	mutex_unlock(&power_mutex); +	return 0; +} + +/** + *	gma_power_resume		-	resume power + *	@pdev: PCI device + * + *	Resume the PCI side of the graphics and then the displays + */ +int gma_power_resume(struct device *_dev) +{ +	struct pci_dev *pdev = container_of(_dev, struct pci_dev, dev); +	struct drm_device *dev = pci_get_drvdata(pdev); + +	mutex_lock(&power_mutex); +	gma_resume_pci(pdev); +	gma_resume_display(pdev); +	psb_irq_preinstall(dev); +	psb_irq_postinstall(dev); +	mutex_unlock(&power_mutex); +	return 0; +} + +/** + *	gma_power_is_on		-	returne true if power is on + *	@dev: our DRM device + * + *	Returns true if the display island power is on at this moment + */ +bool gma_power_is_on(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	return dev_priv->display_power; +} + +/** + *	gma_power_begin		-	begin requiring power + *	@dev: our DRM device + *	@force_on: true to force power on + * + *	Begin an action that requires the display power island is enabled. + *	We refcount the islands. + */ +bool gma_power_begin(struct drm_device *dev, bool force_on) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	int ret; +	unsigned long flags; + +	spin_lock_irqsave(&power_ctrl_lock, flags); +	/* Power already on ? */ +	if (dev_priv->display_power) { +		dev_priv->display_count++; +		pm_runtime_get(&dev->pdev->dev); +		spin_unlock_irqrestore(&power_ctrl_lock, flags); +		return true; +	} +	if (force_on == false) +		goto out_false; + +	/* Ok power up needed */ +	ret = gma_resume_pci(dev->pdev); +	if (ret == 0) { +		psb_irq_preinstall(dev); +		psb_irq_postinstall(dev); +		pm_runtime_get(&dev->pdev->dev); +		dev_priv->display_count++; +		spin_unlock_irqrestore(&power_ctrl_lock, flags); +		return true; +	} +out_false: +	spin_unlock_irqrestore(&power_ctrl_lock, flags); +	return false; +} + +/** + *	gma_power_end		-	end use of power + *	@dev: Our DRM device + * + *	Indicate that one of our gma_power_begin() requested periods when + *	the diplay island power is needed has completed. + */ +void gma_power_end(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	unsigned long flags; +	spin_lock_irqsave(&power_ctrl_lock, flags); +	dev_priv->display_count--; +	WARN_ON(dev_priv->display_count < 0); +	spin_unlock_irqrestore(&power_ctrl_lock, flags); +	pm_runtime_put(&dev->pdev->dev); +} + +int psb_runtime_suspend(struct device *dev) +{ +	return gma_power_suspend(dev); +} + +int psb_runtime_resume(struct device *dev) +{ +	return gma_power_resume(dev); +} + +int psb_runtime_idle(struct device *dev) +{ +	struct drm_device *drmdev = pci_get_drvdata(to_pci_dev(dev)); +	struct drm_psb_private *dev_priv = drmdev->dev_private; +	if (dev_priv->display_count) +		return 0; +	else +		return 1; +} + +int gma_power_thaw(struct device *_dev) +{ +	return gma_power_resume(_dev); +} + +int gma_power_freeze(struct device *_dev) +{ +	return gma_power_suspend(_dev); +} + +int gma_power_restore(struct device *_dev) +{ +	return gma_power_resume(_dev); +} diff --git a/drivers/gpu/drm/gma500/power.h b/drivers/gpu/drm/gma500/power.h new file mode 100644 index 00000000000..56d8708bd41 --- /dev/null +++ b/drivers/gpu/drm/gma500/power.h @@ -0,0 +1,70 @@ +/************************************************************************** + * Copyright (c) 2009-2011, Intel Corporation. + * All Rights Reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + *    Benjamin Defnet <benjamin.r.defnet@intel.com> + *    Rajesh Poornachandran <rajesh.poornachandran@intel.com> + * Massively reworked + *    Alan Cox <alan@linux.intel.com> + */ +#ifndef _PSB_POWERMGMT_H_ +#define _PSB_POWERMGMT_H_ + +#include <linux/pci.h> +#include <drm/drmP.h> + +void gma_power_init(struct drm_device *dev); +void gma_power_uninit(struct drm_device *dev); + +/* + * The kernel bus power management  will call these functions + */ +int gma_power_suspend(struct device *dev); +int gma_power_resume(struct device *dev); +int gma_power_thaw(struct device *dev); +int gma_power_freeze(struct device *dev); +int gma_power_restore(struct device *_dev); + +/* + * These are the functions the driver should use to wrap all hw access + * (i.e. register reads and writes) + */ +bool gma_power_begin(struct drm_device *dev, bool force); +void gma_power_end(struct drm_device *dev); + +/* + * Use this function to do an instantaneous check for if the hw is on. + * Only use this in cases where you know the mutex is already held such + * as in irq install/uninstall and you need to + * prevent a deadlock situation.  Otherwise use gma_power_begin(). + */ +bool gma_power_is_on(struct drm_device *dev); + +/* + * GFX-Runtime PM callbacks + */ +int psb_runtime_suspend(struct device *dev); +int psb_runtime_resume(struct device *dev); +int psb_runtime_idle(struct device *dev); + +#endif /*_PSB_POWERMGMT_H_*/ diff --git a/drivers/gpu/drm/gma500/psb_device.c b/drivers/gpu/drm/gma500/psb_device.c new file mode 100644 index 00000000000..07df7d4eea7 --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_device.c @@ -0,0 +1,360 @@ +/************************************************************************** + * Copyright (c) 2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +#include <linux/backlight.h> +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/gma_drm.h> +#include "psb_drv.h" +#include "psb_reg.h" +#include "psb_intel_reg.h" +#include "intel_bios.h" +#include "psb_device.h" +#include "gma_device.h" + +static int psb_output_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	psb_intel_lvds_init(dev, &dev_priv->mode_dev); +	psb_intel_sdvo_init(dev, SDVOB); +	return 0; +} + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE + +/* + *	Poulsbo Backlight Interfaces + */ + +#define BLC_PWM_PRECISION_FACTOR 100	/* 10000000 */ +#define BLC_PWM_FREQ_CALC_CONSTANT 32 +#define MHz 1000000 + +#define PSB_BLC_PWM_PRECISION_FACTOR    10 +#define PSB_BLC_MAX_PWM_REG_FREQ        0xFFFE +#define PSB_BLC_MIN_PWM_REG_FREQ        0x2 + +#define PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR (0xFFFE) +#define PSB_BACKLIGHT_PWM_CTL_SHIFT	(16) + +static int psb_brightness; +static struct backlight_device *psb_backlight_device; + +static int psb_get_brightness(struct backlight_device *bd) +{ +	/* return locally cached var instead of HW read (due to DPST etc.) */ +	/* FIXME: ideally return actual value in case firmware fiddled with +	   it */ +	return psb_brightness; +} + + +static int psb_backlight_setup(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	unsigned long core_clock; +	/* u32 bl_max_freq; */ +	/* unsigned long value; */ +	u16 bl_max_freq; +	uint32_t value; +	uint32_t blc_pwm_precision_factor; + +	/* get bl_max_freq and pol from dev_priv*/ +	if (!dev_priv->lvds_bl) { +		dev_err(dev->dev, "Has no valid LVDS backlight info\n"); +		return -ENOENT; +	} +	bl_max_freq = dev_priv->lvds_bl->freq; +	blc_pwm_precision_factor = PSB_BLC_PWM_PRECISION_FACTOR; + +	core_clock = dev_priv->core_freq; + +	value = (core_clock * MHz) / BLC_PWM_FREQ_CALC_CONSTANT; +	value *= blc_pwm_precision_factor; +	value /= bl_max_freq; +	value /= blc_pwm_precision_factor; + +	if (value > (unsigned long long)PSB_BLC_MAX_PWM_REG_FREQ || +		 value < (unsigned long long)PSB_BLC_MIN_PWM_REG_FREQ) +				return -ERANGE; +	else { +		value &= PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR; +		REG_WRITE(BLC_PWM_CTL, +			(value << PSB_BACKLIGHT_PWM_CTL_SHIFT) | (value)); +	} +	return 0; +} + +static int psb_set_brightness(struct backlight_device *bd) +{ +	struct drm_device *dev = bl_get_data(psb_backlight_device); +	int level = bd->props.brightness; + +	/* Percentage 1-100% being valid */ +	if (level < 1) +		level = 1; + +	psb_intel_lvds_set_brightness(dev, level); +	psb_brightness = level; +	return 0; +} + +static const struct backlight_ops psb_ops = { +	.get_brightness = psb_get_brightness, +	.update_status  = psb_set_brightness, +}; + +static int psb_backlight_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	int ret; +	struct backlight_properties props; + +	memset(&props, 0, sizeof(struct backlight_properties)); +	props.max_brightness = 100; +	props.type = BACKLIGHT_PLATFORM; + +	psb_backlight_device = backlight_device_register("psb-bl", +					NULL, (void *)dev, &psb_ops, &props); +	if (IS_ERR(psb_backlight_device)) +		return PTR_ERR(psb_backlight_device); + +	ret = psb_backlight_setup(dev); +	if (ret < 0) { +		backlight_device_unregister(psb_backlight_device); +		psb_backlight_device = NULL; +		return ret; +	} +	psb_backlight_device->props.brightness = 100; +	psb_backlight_device->props.max_brightness = 100; +	backlight_update_status(psb_backlight_device); +	dev_priv->backlight_device = psb_backlight_device; + +	/* This must occur after the backlight is properly initialised */ +	psb_lid_timer_init(dev_priv); + +	return 0; +} + +#endif + +/* + *	Provide the Poulsbo specific chip logic and low level methods + *	for power management + */ + +static void psb_init_pm(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	u32 gating = PSB_RSGX32(PSB_CR_CLKGATECTL); +	gating &= ~3;	/* Disable 2D clock gating */ +	gating |= 1; +	PSB_WSGX32(gating, PSB_CR_CLKGATECTL); +	PSB_RSGX32(PSB_CR_CLKGATECTL); +} + +/** + *	psb_save_display_registers	-	save registers lost on suspend + *	@dev: our DRM device + * + *	Save the state we need in order to be able to restore the interface + *	upon resume from suspend + */ +static int psb_save_display_registers(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct drm_crtc *crtc; +	struct drm_connector *connector; +	struct psb_state *regs = &dev_priv->regs.psb; + +	/* Display arbitration control + watermarks */ +	regs->saveDSPARB = PSB_RVDC32(DSPARB); +	regs->saveDSPFW1 = PSB_RVDC32(DSPFW1); +	regs->saveDSPFW2 = PSB_RVDC32(DSPFW2); +	regs->saveDSPFW3 = PSB_RVDC32(DSPFW3); +	regs->saveDSPFW4 = PSB_RVDC32(DSPFW4); +	regs->saveDSPFW5 = PSB_RVDC32(DSPFW5); +	regs->saveDSPFW6 = PSB_RVDC32(DSPFW6); +	regs->saveCHICKENBIT = PSB_RVDC32(DSPCHICKENBIT); + +	/* Save crtc and output state */ +	drm_modeset_lock_all(dev); +	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { +		if (drm_helper_crtc_in_use(crtc)) +			crtc->funcs->save(crtc); +	} + +	list_for_each_entry(connector, &dev->mode_config.connector_list, head) +		if (connector->funcs->save) +			connector->funcs->save(connector); + +	drm_modeset_unlock_all(dev); +	return 0; +} + +/** + *	psb_restore_display_registers	-	restore lost register state + *	@dev: our DRM device + * + *	Restore register state that was lost during suspend and resume. + */ +static int psb_restore_display_registers(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct drm_crtc *crtc; +	struct drm_connector *connector; +	struct psb_state *regs = &dev_priv->regs.psb; + +	/* Display arbitration + watermarks */ +	PSB_WVDC32(regs->saveDSPARB, DSPARB); +	PSB_WVDC32(regs->saveDSPFW1, DSPFW1); +	PSB_WVDC32(regs->saveDSPFW2, DSPFW2); +	PSB_WVDC32(regs->saveDSPFW3, DSPFW3); +	PSB_WVDC32(regs->saveDSPFW4, DSPFW4); +	PSB_WVDC32(regs->saveDSPFW5, DSPFW5); +	PSB_WVDC32(regs->saveDSPFW6, DSPFW6); +	PSB_WVDC32(regs->saveCHICKENBIT, DSPCHICKENBIT); + +	/*make sure VGA plane is off. it initializes to on after reset!*/ +	PSB_WVDC32(0x80000000, VGACNTRL); + +	drm_modeset_lock_all(dev); +	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) +		if (drm_helper_crtc_in_use(crtc)) +			crtc->funcs->restore(crtc); + +	list_for_each_entry(connector, &dev->mode_config.connector_list, head) +		if (connector->funcs->restore) +			connector->funcs->restore(connector); + +	drm_modeset_unlock_all(dev); +	return 0; +} + +static int psb_power_down(struct drm_device *dev) +{ +	return 0; +} + +static int psb_power_up(struct drm_device *dev) +{ +	return 0; +} + +/* Poulsbo */ +static const struct psb_offset psb_regmap[2] = { +	{ +		.fp0 = FPA0, +		.fp1 = FPA1, +		.cntr = DSPACNTR, +		.conf = PIPEACONF, +		.src = PIPEASRC, +		.dpll = DPLL_A, +		.htotal = HTOTAL_A, +		.hblank = HBLANK_A, +		.hsync = HSYNC_A, +		.vtotal = VTOTAL_A, +		.vblank = VBLANK_A, +		.vsync = VSYNC_A, +		.stride = DSPASTRIDE, +		.size = DSPASIZE, +		.pos = DSPAPOS, +		.base = DSPABASE, +		.surf = DSPASURF, +		.addr = DSPABASE, +		.status = PIPEASTAT, +		.linoff = DSPALINOFF, +		.tileoff = DSPATILEOFF, +		.palette = PALETTE_A, +	}, +	{ +		.fp0 = FPB0, +		.fp1 = FPB1, +		.cntr = DSPBCNTR, +		.conf = PIPEBCONF, +		.src = PIPEBSRC, +		.dpll = DPLL_B, +		.htotal = HTOTAL_B, +		.hblank = HBLANK_B, +		.hsync = HSYNC_B, +		.vtotal = VTOTAL_B, +		.vblank = VBLANK_B, +		.vsync = VSYNC_B, +		.stride = DSPBSTRIDE, +		.size = DSPBSIZE, +		.pos = DSPBPOS, +		.base = DSPBBASE, +		.surf = DSPBSURF, +		.addr = DSPBBASE, +		.status = PIPEBSTAT, +		.linoff = DSPBLINOFF, +		.tileoff = DSPBTILEOFF, +		.palette = PALETTE_B, +	} +}; + +static int psb_chip_setup(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	dev_priv->regmap = psb_regmap; +	gma_get_core_freq(dev); +	gma_intel_setup_gmbus(dev); +	psb_intel_opregion_init(dev); +	psb_intel_init_bios(dev); +	return 0; +} + +static void psb_chip_teardown(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	psb_lid_timer_takedown(dev_priv); +	gma_intel_teardown_gmbus(dev); +} + +const struct psb_ops psb_chip_ops = { +	.name = "Poulsbo", +	.accel_2d = 1, +	.pipes = 2, +	.crtcs = 2, +	.hdmi_mask = (1 << 0), +	.lvds_mask = (1 << 1), +	.sdvo_mask = (1 << 0), +	.cursor_needs_phys = 1, +	.sgx_offset = PSB_SGX_OFFSET, +	.chip_setup = psb_chip_setup, +	.chip_teardown = psb_chip_teardown, + +	.crtc_helper = &psb_intel_helper_funcs, +	.crtc_funcs = &psb_intel_crtc_funcs, +	.clock_funcs = &psb_clock_funcs, + +	.output_init = psb_output_init, + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	.backlight_init = psb_backlight_init, +#endif + +	.init_pm = psb_init_pm, +	.save_regs = psb_save_display_registers, +	.restore_regs = psb_restore_display_registers, +	.power_down = psb_power_down, +	.power_up = psb_power_up, +}; + diff --git a/drivers/gpu/drm/gma500/psb_device.h b/drivers/gpu/drm/gma500/psb_device.h new file mode 100644 index 00000000000..35e304c7f85 --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_device.h @@ -0,0 +1,24 @@ +/* + * Copyright © 2013 Patrik Jakobsson + * Copyright © 2011 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _PSB_DEVICE_H_ +#define _PSB_DEVICE_H_ + +extern const struct gma_clock_funcs psb_clock_funcs; + +#endif diff --git a/drivers/gpu/drm/gma500/psb_drv.c b/drivers/gpu/drm/gma500/psb_drv.c new file mode 100644 index 00000000000..6e8fe9ec02b --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_drv.c @@ -0,0 +1,529 @@ +/************************************************************************** + * Copyright (c) 2007-2011, Intel Corporation. + * All Rights Reserved. + * Copyright (c) 2008, Tungsten Graphics, Inc. Cedar Park, TX., USA. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +#include <drm/drmP.h> +#include <drm/drm.h> +#include "psb_drv.h" +#include "framebuffer.h" +#include "psb_reg.h" +#include "psb_intel_reg.h" +#include "intel_bios.h" +#include "mid_bios.h" +#include <drm/drm_pciids.h> +#include "power.h" +#include <linux/cpu.h> +#include <linux/notifier.h> +#include <linux/spinlock.h> +#include <linux/pm_runtime.h> +#include <acpi/video.h> +#include <linux/module.h> + +static struct drm_driver driver; +static int psb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent); + +/* + * The table below contains a mapping of the PCI vendor ID and the PCI Device ID + * to the different groups of PowerVR 5-series chip designs + * + * 0x8086 = Intel Corporation + * + * PowerVR SGX535    - Poulsbo    - Intel GMA 500, Intel Atom Z5xx + * PowerVR SGX535    - Moorestown - Intel GMA 600 + * PowerVR SGX535    - Oaktrail   - Intel GMA 600, Intel Atom Z6xx, E6xx + * PowerVR SGX540    - Medfield   - Intel Atom Z2460 + * PowerVR SGX544MP2 - Medfield   - + * PowerVR SGX545    - Cedartrail - Intel GMA 3600, Intel Atom D2500, N2600 + * PowerVR SGX545    - Cedartrail - Intel GMA 3650, Intel Atom D2550, D2700, + *                                  N2800 + */ +static DEFINE_PCI_DEVICE_TABLE(pciidlist) = { +	{ 0x8086, 0x8108, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &psb_chip_ops }, +	{ 0x8086, 0x8109, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &psb_chip_ops }, +#if defined(CONFIG_DRM_GMA600) +	{ 0x8086, 0x4100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops }, +	{ 0x8086, 0x4101, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops }, +	{ 0x8086, 0x4102, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops }, +	{ 0x8086, 0x4103, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops }, +	{ 0x8086, 0x4104, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops }, +	{ 0x8086, 0x4105, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops }, +	{ 0x8086, 0x4106, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops }, +	{ 0x8086, 0x4107, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops }, +	{ 0x8086, 0x4108, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops }, +#endif +#if defined(CONFIG_DRM_MEDFIELD) +	{ 0x8086, 0x0130, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &mdfld_chip_ops }, +	{ 0x8086, 0x0131, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &mdfld_chip_ops }, +	{ 0x8086, 0x0132, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &mdfld_chip_ops }, +	{ 0x8086, 0x0133, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &mdfld_chip_ops }, +	{ 0x8086, 0x0134, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &mdfld_chip_ops }, +	{ 0x8086, 0x0135, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &mdfld_chip_ops }, +	{ 0x8086, 0x0136, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &mdfld_chip_ops }, +	{ 0x8086, 0x0137, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &mdfld_chip_ops }, +#endif +#if defined(CONFIG_DRM_GMA3600) +	{ 0x8086, 0x0be0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0be1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0be2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0be3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0be4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0be5, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0be6, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0be7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0be8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0be9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0bea, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0beb, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0bec, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0bed, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0bee, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +	{ 0x8086, 0x0bef, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops }, +#endif +	{ 0, } +}; +MODULE_DEVICE_TABLE(pci, pciidlist); + +/* + * Standard IOCTLs. + */ +static const struct drm_ioctl_desc psb_ioctls[] = { +}; + +static void psb_driver_lastclose(struct drm_device *dev) +{ +	int ret; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_fbdev *fbdev = dev_priv->fbdev; + +	ret = drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->psb_fb_helper); +	if (ret) +		DRM_DEBUG("failed to restore crtc mode\n"); + +	return; +} + +static int psb_do_init(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_gtt *pg = &dev_priv->gtt; + +	uint32_t stolen_gtt; + +	if (pg->mmu_gatt_start & 0x0FFFFFFF) { +		dev_err(dev->dev, "Gatt must be 256M aligned. This is a bug.\n"); +		return -EINVAL; +	} + +	stolen_gtt = (pg->stolen_size >> PAGE_SHIFT) * 4; +	stolen_gtt = (stolen_gtt + PAGE_SIZE - 1) >> PAGE_SHIFT; +	stolen_gtt = (stolen_gtt < pg->gtt_pages) ? stolen_gtt : pg->gtt_pages; + +	dev_priv->gatt_free_offset = pg->mmu_gatt_start + +	    (stolen_gtt << PAGE_SHIFT) * 1024; + +	spin_lock_init(&dev_priv->irqmask_lock); +	spin_lock_init(&dev_priv->lock_2d); + +	PSB_WSGX32(0x00000000, PSB_CR_BIF_BANK0); +	PSB_WSGX32(0x00000000, PSB_CR_BIF_BANK1); +	PSB_RSGX32(PSB_CR_BIF_BANK1); + +	/* Do not bypass any MMU access, let them pagefault instead */ +	PSB_WSGX32((PSB_RSGX32(PSB_CR_BIF_CTRL) & ~_PSB_MMU_ER_MASK), +		   PSB_CR_BIF_CTRL); +	PSB_RSGX32(PSB_CR_BIF_CTRL); + +	psb_spank(dev_priv); + +	/* mmu_gatt ?? */ +	PSB_WSGX32(pg->gatt_start, PSB_CR_BIF_TWOD_REQ_BASE); +	PSB_RSGX32(PSB_CR_BIF_TWOD_REQ_BASE); /* Post */ + +	return 0; +} + +static int psb_driver_unload(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	/* TODO: Kill vblank etc here */ + +	if (dev_priv) { +		if (dev_priv->backlight_device) +			gma_backlight_exit(dev); +		psb_modeset_cleanup(dev); + +		if (dev_priv->ops->chip_teardown) +			dev_priv->ops->chip_teardown(dev); + +		psb_intel_opregion_fini(dev); + +		if (dev_priv->pf_pd) { +			psb_mmu_free_pagedir(dev_priv->pf_pd); +			dev_priv->pf_pd = NULL; +		} +		if (dev_priv->mmu) { +			struct psb_gtt *pg = &dev_priv->gtt; + +			down_read(&pg->sem); +			psb_mmu_remove_pfn_sequence( +				psb_mmu_get_default_pd +				(dev_priv->mmu), +				pg->mmu_gatt_start, +				dev_priv->vram_stolen_size >> PAGE_SHIFT); +			up_read(&pg->sem); +			psb_mmu_driver_takedown(dev_priv->mmu); +			dev_priv->mmu = NULL; +		} +		psb_gtt_takedown(dev); +		if (dev_priv->scratch_page) { +			set_pages_wb(dev_priv->scratch_page, 1); +			__free_page(dev_priv->scratch_page); +			dev_priv->scratch_page = NULL; +		} +		if (dev_priv->vdc_reg) { +			iounmap(dev_priv->vdc_reg); +			dev_priv->vdc_reg = NULL; +		} +		if (dev_priv->sgx_reg) { +			iounmap(dev_priv->sgx_reg); +			dev_priv->sgx_reg = NULL; +		} +		if (dev_priv->aux_reg) { +			iounmap(dev_priv->aux_reg); +			dev_priv->aux_reg = NULL; +		} +		if (dev_priv->aux_pdev) +			pci_dev_put(dev_priv->aux_pdev); + +		/* Destroy VBT data */ +		psb_intel_destroy_bios(dev); + +		kfree(dev_priv); +		dev->dev_private = NULL; +	} +	gma_power_uninit(dev); +	return 0; +} + +static int psb_driver_load(struct drm_device *dev, unsigned long flags) +{ +	struct drm_psb_private *dev_priv; +	unsigned long resource_start, resource_len; +	unsigned long irqflags; +	int ret = -ENOMEM; +	struct drm_connector *connector; +	struct gma_encoder *gma_encoder; +	struct psb_gtt *pg; + +	/* allocating and initializing driver private data */ +	dev_priv = kzalloc(sizeof(*dev_priv), GFP_KERNEL); +	if (dev_priv == NULL) +		return -ENOMEM; + +	dev_priv->ops = (struct psb_ops *)flags; +	dev_priv->dev = dev; +	dev->dev_private = (void *) dev_priv; + +	pg = &dev_priv->gtt; + +	pci_set_master(dev->pdev); + +	dev_priv->num_pipe = dev_priv->ops->pipes; + +	resource_start = pci_resource_start(dev->pdev, PSB_MMIO_RESOURCE); + +	dev_priv->vdc_reg = +	    ioremap(resource_start + PSB_VDC_OFFSET, PSB_VDC_SIZE); +	if (!dev_priv->vdc_reg) +		goto out_err; + +	dev_priv->sgx_reg = ioremap(resource_start + dev_priv->ops->sgx_offset, +							PSB_SGX_SIZE); +	if (!dev_priv->sgx_reg) +		goto out_err; + +	if (IS_MRST(dev)) { +		dev_priv->aux_pdev = pci_get_bus_and_slot(0, PCI_DEVFN(3, 0)); + +		if (dev_priv->aux_pdev) { +			resource_start = pci_resource_start(dev_priv->aux_pdev, +							    PSB_AUX_RESOURCE); +			resource_len = pci_resource_len(dev_priv->aux_pdev, +							PSB_AUX_RESOURCE); +			dev_priv->aux_reg = ioremap_nocache(resource_start, +							    resource_len); +			if (!dev_priv->aux_reg) +				goto out_err; + +			DRM_DEBUG_KMS("Found aux vdc"); +		} else { +			/* Couldn't find the aux vdc so map to primary vdc */ +			dev_priv->aux_reg = dev_priv->vdc_reg; +			DRM_DEBUG_KMS("Couldn't find aux pci device"); +		} +		dev_priv->gmbus_reg = dev_priv->aux_reg; +	} else { +		dev_priv->gmbus_reg = dev_priv->vdc_reg; +	} + +	psb_intel_opregion_setup(dev); + +	ret = dev_priv->ops->chip_setup(dev); +	if (ret) +		goto out_err; + +	/* Init OSPM support */ +	gma_power_init(dev); + +	ret = -ENOMEM; + +	dev_priv->scratch_page = alloc_page(GFP_DMA32 | __GFP_ZERO); +	if (!dev_priv->scratch_page) +		goto out_err; + +	set_pages_uc(dev_priv->scratch_page, 1); + +	ret = psb_gtt_init(dev, 0); +	if (ret) +		goto out_err; + +	dev_priv->mmu = psb_mmu_driver_init(dev, 1, 0, 0); +	if (!dev_priv->mmu) +		goto out_err; + +	dev_priv->pf_pd = psb_mmu_alloc_pd(dev_priv->mmu, 1, 0); +	if (!dev_priv->pf_pd) +		goto out_err; + +	ret = psb_do_init(dev); +	if (ret) +		return ret; + +	/* Add stolen memory to SGX MMU */ +	down_read(&pg->sem); +	ret = psb_mmu_insert_pfn_sequence(psb_mmu_get_default_pd(dev_priv->mmu), +					  dev_priv->stolen_base >> PAGE_SHIFT, +					  pg->gatt_start, +					  pg->stolen_size >> PAGE_SHIFT, 0); +	up_read(&pg->sem); + +	psb_mmu_set_pd_context(psb_mmu_get_default_pd(dev_priv->mmu), 0); +	psb_mmu_set_pd_context(dev_priv->pf_pd, 1); + +	PSB_WSGX32(0x20000000, PSB_CR_PDS_EXEC_BASE); +	PSB_WSGX32(0x30000000, PSB_CR_BIF_3D_REQ_BASE); + +	acpi_video_register(); + +	/* Setup vertical blanking handling */ +	ret = drm_vblank_init(dev, dev_priv->num_pipe); +	if (ret) +		goto out_err; + +	/* +	 * Install interrupt handlers prior to powering off SGX or else we will +	 * crash. +	 */ +	dev_priv->vdc_irq_mask = 0; +	dev_priv->pipestat[0] = 0; +	dev_priv->pipestat[1] = 0; +	dev_priv->pipestat[2] = 0; +	spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); +	PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM); +	PSB_WVDC32(0x00000000, PSB_INT_ENABLE_R); +	PSB_WVDC32(0xFFFFFFFF, PSB_INT_MASK_R); +	spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); + +	drm_irq_install(dev, dev->pdev->irq); + +	dev->vblank_disable_allowed = true; +	dev->max_vblank_count = 0xffffff; /* only 24 bits of frame count */ +	dev->driver->get_vblank_counter = psb_get_vblank_counter; + +	psb_modeset_init(dev); +	psb_fbdev_init(dev); +	drm_kms_helper_poll_init(dev); + +	/* Only add backlight support if we have LVDS output */ +	list_for_each_entry(connector, &dev->mode_config.connector_list, +			    head) { +		gma_encoder = gma_attached_encoder(connector); + +		switch (gma_encoder->type) { +		case INTEL_OUTPUT_LVDS: +		case INTEL_OUTPUT_MIPI: +			ret = gma_backlight_init(dev); +			break; +		} +	} + +	if (ret) +		return ret; +	psb_intel_opregion_enable_asle(dev); +#if 0 +	/* Enable runtime pm at last */ +	pm_runtime_enable(&dev->pdev->dev); +	pm_runtime_set_active(&dev->pdev->dev); +#endif +	/* Intel drm driver load is done, continue doing pvr load */ +	return 0; +out_err: +	psb_driver_unload(dev); +	return ret; +} + +static int psb_driver_device_is_agp(struct drm_device *dev) +{ +	return 0; +} + +static inline void get_brightness(struct backlight_device *bd) +{ +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	if (bd) { +		bd->props.brightness = bd->ops->get_brightness(bd); +		backlight_update_status(bd); +	} +#endif +} + +static long psb_unlocked_ioctl(struct file *filp, unsigned int cmd, +			       unsigned long arg) +{ +	struct drm_file *file_priv = filp->private_data; +	struct drm_device *dev = file_priv->minor->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	static unsigned int runtime_allowed; + +	if (runtime_allowed == 1 && dev_priv->is_lvds_on) { +		runtime_allowed++; +		pm_runtime_allow(&dev->pdev->dev); +		dev_priv->rpm_enabled = 1; +	} +	return drm_ioctl(filp, cmd, arg); +	/* FIXME: do we need to wrap the other side of this */ +} + +/* + * When a client dies: + *    - Check for and clean up flipped page state + */ +static void psb_driver_preclose(struct drm_device *dev, struct drm_file *priv) +{ +} + +static int psb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ +	return drm_get_pci_dev(pdev, ent, &driver); +} + + +static void psb_pci_remove(struct pci_dev *pdev) +{ +	struct drm_device *dev = pci_get_drvdata(pdev); +	drm_put_dev(dev); +} + +static const struct dev_pm_ops psb_pm_ops = { +	.resume = gma_power_resume, +	.suspend = gma_power_suspend, +	.thaw = gma_power_thaw, +	.freeze = gma_power_freeze, +	.restore = gma_power_restore, +	.runtime_suspend = psb_runtime_suspend, +	.runtime_resume = psb_runtime_resume, +	.runtime_idle = psb_runtime_idle, +}; + +static const struct vm_operations_struct psb_gem_vm_ops = { +	.fault = psb_gem_fault, +	.open = drm_gem_vm_open, +	.close = drm_gem_vm_close, +}; + +static const struct file_operations psb_gem_fops = { +	.owner = THIS_MODULE, +	.open = drm_open, +	.release = drm_release, +	.unlocked_ioctl = psb_unlocked_ioctl, +	.mmap = drm_gem_mmap, +	.poll = drm_poll, +	.read = drm_read, +}; + +static struct drm_driver driver = { +	.driver_features = DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED | \ +			   DRIVER_MODESET | DRIVER_GEM, +	.load = psb_driver_load, +	.unload = psb_driver_unload, +	.lastclose = psb_driver_lastclose, +	.preclose = psb_driver_preclose, + +	.num_ioctls = ARRAY_SIZE(psb_ioctls), +	.device_is_agp = psb_driver_device_is_agp, +	.irq_preinstall = psb_irq_preinstall, +	.irq_postinstall = psb_irq_postinstall, +	.irq_uninstall = psb_irq_uninstall, +	.irq_handler = psb_irq_handler, +	.enable_vblank = psb_enable_vblank, +	.disable_vblank = psb_disable_vblank, +	.get_vblank_counter = psb_get_vblank_counter, + +	.gem_free_object = psb_gem_free_object, +	.gem_vm_ops = &psb_gem_vm_ops, + +	.dumb_create = psb_gem_dumb_create, +	.dumb_map_offset = psb_gem_dumb_map_gtt, +	.dumb_destroy = drm_gem_dumb_destroy, +	.ioctls = psb_ioctls, +	.fops = &psb_gem_fops, +	.name = DRIVER_NAME, +	.desc = DRIVER_DESC, +	.date = DRIVER_DATE, +	.major = DRIVER_MAJOR, +	.minor = DRIVER_MINOR, +	.patchlevel = DRIVER_PATCHLEVEL +}; + +static struct pci_driver psb_pci_driver = { +	.name = DRIVER_NAME, +	.id_table = pciidlist, +	.probe = psb_pci_probe, +	.remove = psb_pci_remove, +	.driver.pm = &psb_pm_ops, +}; + +static int __init psb_init(void) +{ +	return drm_pci_init(&driver, &psb_pci_driver); +} + +static void __exit psb_exit(void) +{ +	drm_pci_exit(&driver, &psb_pci_driver); +} + +late_initcall(psb_init); +module_exit(psb_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); diff --git a/drivers/gpu/drm/gma500/psb_drv.h b/drivers/gpu/drm/gma500/psb_drv.h new file mode 100644 index 00000000000..55ebe2bd88d --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_drv.h @@ -0,0 +1,922 @@ +/************************************************************************** + * Copyright (c) 2007-2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + **************************************************************************/ + +#ifndef _PSB_DRV_H_ +#define _PSB_DRV_H_ + +#include <linux/kref.h> + +#include <drm/drmP.h> +#include <drm/drm_global.h> +#include <drm/gma_drm.h> +#include "psb_reg.h" +#include "psb_intel_drv.h" +#include "gma_display.h" +#include "intel_bios.h" +#include "gtt.h" +#include "power.h" +#include "opregion.h" +#include "oaktrail.h" +#include "mmu.h" + +#define DRIVER_AUTHOR "Alan Cox <alan@linux.intel.com> and others" +#define DRIVER_LICENSE "GPL" + +#define DRIVER_NAME "gma500" +#define DRIVER_DESC "DRM driver for the Intel GMA500, GMA600, GMA3600, GMA3650" +#define DRIVER_DATE "20140314" + +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +/* Append new drm mode definition here, align with libdrm definition */ +#define DRM_MODE_SCALE_NO_SCALE   	2 + +enum { +	CHIP_PSB_8108 = 0,		/* Poulsbo */ +	CHIP_PSB_8109 = 1,		/* Poulsbo */ +	CHIP_MRST_4100 = 2,		/* Moorestown/Oaktrail */ +	CHIP_MFLD_0130 = 3,		/* Medfield */ +}; + +#define IS_PSB(dev) (((dev)->pdev->device & 0xfffe) == 0x8108) +#define IS_MRST(dev) (((dev)->pdev->device & 0xfff0) == 0x4100) +#define IS_MFLD(dev) (((dev)->pdev->device & 0xfff8) == 0x0130) +#define IS_CDV(dev) (((dev)->pdev->device & 0xfff0) == 0x0be0) + +/* Hardware offsets */ +#define PSB_VDC_OFFSET		 0x00000000 +#define PSB_VDC_SIZE		 0x000080000 +#define MRST_MMIO_SIZE		 0x0000C0000 +#define MDFLD_MMIO_SIZE          0x000100000 +#define PSB_SGX_SIZE		 0x8000 +#define PSB_SGX_OFFSET		 0x00040000 +#define MRST_SGX_OFFSET		 0x00080000 + +/* PCI resource identifiers */ +#define PSB_MMIO_RESOURCE	 0 +#define PSB_AUX_RESOURCE	 0 +#define PSB_GATT_RESOURCE	 2 +#define PSB_GTT_RESOURCE	 3 + +/* PCI configuration */ +#define PSB_GMCH_CTRL		 0x52 +#define PSB_BSM			 0x5C +#define _PSB_GMCH_ENABLED	 0x4 +#define PSB_PGETBL_CTL		 0x2020 +#define _PSB_PGETBL_ENABLED	 0x00000001 +#define PSB_SGX_2D_SLAVE_PORT	 0x4000 + +/* TODO: To get rid of */ +#define PSB_TT_PRIV0_LIMIT	 (256*1024*1024) +#define PSB_TT_PRIV0_PLIMIT	 (PSB_TT_PRIV0_LIMIT >> PAGE_SHIFT) + +/* SGX side MMU definitions (these can probably go) */ + +/* Flags for external memory type field */ +#define PSB_MMU_CACHED_MEMORY	  0x0001	/* Bind to MMU only */ +#define PSB_MMU_RO_MEMORY	  0x0002	/* MMU RO memory */ +#define PSB_MMU_WO_MEMORY	  0x0004	/* MMU WO memory */ + +/* PTE's and PDE's */ +#define PSB_PDE_MASK		  0x003FFFFF +#define PSB_PDE_SHIFT		  22 +#define PSB_PTE_SHIFT		  12 + +/* Cache control */ +#define PSB_PTE_VALID		  0x0001	/* PTE / PDE valid */ +#define PSB_PTE_WO		  0x0002	/* Write only */ +#define PSB_PTE_RO		  0x0004	/* Read only */ +#define PSB_PTE_CACHED		  0x0008	/* CPU cache coherent */ + +/* VDC registers and bits */ +#define PSB_MSVDX_CLOCKGATING	  0x2064 +#define PSB_TOPAZ_CLOCKGATING	  0x2068 +#define PSB_HWSTAM		  0x2098 +#define PSB_INSTPM		  0x20C0 +#define PSB_INT_IDENTITY_R        0x20A4 +#define _PSB_IRQ_ASLE		  (1<<0) +#define _MDFLD_PIPEC_EVENT_FLAG   (1<<2) +#define _MDFLD_PIPEC_VBLANK_FLAG  (1<<3) +#define _PSB_DPST_PIPEB_FLAG      (1<<4) +#define _MDFLD_PIPEB_EVENT_FLAG   (1<<4) +#define _PSB_VSYNC_PIPEB_FLAG	  (1<<5) +#define _PSB_DPST_PIPEA_FLAG      (1<<6) +#define _PSB_PIPEA_EVENT_FLAG     (1<<6) +#define _PSB_VSYNC_PIPEA_FLAG	  (1<<7) +#define _MDFLD_MIPIA_FLAG	  (1<<16) +#define _MDFLD_MIPIC_FLAG	  (1<<17) +#define _PSB_IRQ_DISP_HOTSYNC	  (1<<17) +#define _PSB_IRQ_SGX_FLAG	  (1<<18) +#define _PSB_IRQ_MSVDX_FLAG	  (1<<19) +#define _LNC_IRQ_TOPAZ_FLAG	  (1<<20) + +#define _PSB_PIPE_EVENT_FLAG	(_PSB_VSYNC_PIPEA_FLAG | \ +				 _PSB_VSYNC_PIPEB_FLAG) + +/* This flag includes all the display IRQ bits excepts the vblank irqs. */ +#define _MDFLD_DISP_ALL_IRQ_FLAG (_MDFLD_PIPEC_EVENT_FLAG | \ +				  _MDFLD_PIPEB_EVENT_FLAG | \ +				  _PSB_PIPEA_EVENT_FLAG | \ +				  _PSB_VSYNC_PIPEA_FLAG | \ +				  _MDFLD_MIPIA_FLAG | \ +				  _MDFLD_MIPIC_FLAG) +#define PSB_INT_IDENTITY_R	  0x20A4 +#define PSB_INT_MASK_R		  0x20A8 +#define PSB_INT_ENABLE_R	  0x20A0 + +#define _PSB_MMU_ER_MASK      0x0001FF00 +#define _PSB_MMU_ER_HOST      (1 << 16) +#define GPIOA			0x5010 +#define GPIOB			0x5014 +#define GPIOC			0x5018 +#define GPIOD			0x501c +#define GPIOE			0x5020 +#define GPIOF			0x5024 +#define GPIOG			0x5028 +#define GPIOH			0x502c +#define GPIO_CLOCK_DIR_MASK		(1 << 0) +#define GPIO_CLOCK_DIR_IN		(0 << 1) +#define GPIO_CLOCK_DIR_OUT		(1 << 1) +#define GPIO_CLOCK_VAL_MASK		(1 << 2) +#define GPIO_CLOCK_VAL_OUT		(1 << 3) +#define GPIO_CLOCK_VAL_IN		(1 << 4) +#define GPIO_CLOCK_PULLUP_DISABLE	(1 << 5) +#define GPIO_DATA_DIR_MASK		(1 << 8) +#define GPIO_DATA_DIR_IN		(0 << 9) +#define GPIO_DATA_DIR_OUT		(1 << 9) +#define GPIO_DATA_VAL_MASK		(1 << 10) +#define GPIO_DATA_VAL_OUT		(1 << 11) +#define GPIO_DATA_VAL_IN		(1 << 12) +#define GPIO_DATA_PULLUP_DISABLE	(1 << 13) + +#define VCLK_DIVISOR_VGA0   0x6000 +#define VCLK_DIVISOR_VGA1   0x6004 +#define VCLK_POST_DIV	    0x6010 + +#define PSB_COMM_2D (PSB_ENGINE_2D << 4) +#define PSB_COMM_3D (PSB_ENGINE_3D << 4) +#define PSB_COMM_TA (PSB_ENGINE_TA << 4) +#define PSB_COMM_HP (PSB_ENGINE_HP << 4) +#define PSB_COMM_USER_IRQ (1024 >> 2) +#define PSB_COMM_USER_IRQ_LOST (PSB_COMM_USER_IRQ + 1) +#define PSB_COMM_FW (2048 >> 2) + +#define PSB_UIRQ_VISTEST	       1 +#define PSB_UIRQ_OOM_REPLY	       2 +#define PSB_UIRQ_FIRE_TA_REPLY	       3 +#define PSB_UIRQ_FIRE_RASTER_REPLY     4 + +#define PSB_2D_SIZE (256*1024*1024) +#define PSB_MAX_RELOC_PAGES 1024 + +#define PSB_LOW_REG_OFFS 0x0204 +#define PSB_HIGH_REG_OFFS 0x0600 + +#define PSB_NUM_VBLANKS 2 + + +#define PSB_2D_SIZE (256*1024*1024) +#define PSB_MAX_RELOC_PAGES 1024 + +#define PSB_LOW_REG_OFFS 0x0204 +#define PSB_HIGH_REG_OFFS 0x0600 + +#define PSB_NUM_VBLANKS 2 +#define PSB_WATCHDOG_DELAY (HZ * 2) +#define PSB_LID_DELAY (HZ / 10) + +#define MDFLD_PNW_B0 0x04 +#define MDFLD_PNW_C0 0x08 + +#define MDFLD_DSR_2D_3D_0 	(1 << 0) +#define MDFLD_DSR_2D_3D_2 	(1 << 1) +#define MDFLD_DSR_CURSOR_0 	(1 << 2) +#define MDFLD_DSR_CURSOR_2	(1 << 3) +#define MDFLD_DSR_OVERLAY_0 	(1 << 4) +#define MDFLD_DSR_OVERLAY_2 	(1 << 5) +#define MDFLD_DSR_MIPI_CONTROL	(1 << 6) +#define MDFLD_DSR_DAMAGE_MASK_0	((1 << 0) | (1 << 2) | (1 << 4)) +#define MDFLD_DSR_DAMAGE_MASK_2	((1 << 1) | (1 << 3) | (1 << 5)) +#define MDFLD_DSR_2D_3D 	(MDFLD_DSR_2D_3D_0 | MDFLD_DSR_2D_3D_2) + +#define MDFLD_DSR_RR		45 +#define MDFLD_DPU_ENABLE 	(1 << 31) +#define MDFLD_DSR_FULLSCREEN 	(1 << 30) +#define MDFLD_DSR_DELAY		(HZ / MDFLD_DSR_RR) + +#define PSB_PWR_STATE_ON		1 +#define PSB_PWR_STATE_OFF		2 + +#define PSB_PMPOLICY_NOPM		0 +#define PSB_PMPOLICY_CLOCKGATING	1 +#define PSB_PMPOLICY_POWERDOWN		2 + +#define PSB_PMSTATE_POWERUP		0 +#define PSB_PMSTATE_CLOCKGATED		1 +#define PSB_PMSTATE_POWERDOWN		2 +#define PSB_PCIx_MSI_ADDR_LOC		0x94 +#define PSB_PCIx_MSI_DATA_LOC		0x98 + +/* Medfield crystal settings */ +#define KSEL_CRYSTAL_19 1 +#define KSEL_BYPASS_19 5 +#define KSEL_BYPASS_25 6 +#define KSEL_BYPASS_83_100 7 + +struct opregion_header; +struct opregion_acpi; +struct opregion_swsci; +struct opregion_asle; + +struct psb_intel_opregion { +	struct opregion_header *header; +	struct opregion_acpi *acpi; +	struct opregion_swsci *swsci; +	struct opregion_asle *asle; +	void *vbt; +	u32 __iomem *lid_state; +	struct work_struct asle_work; +}; + +struct sdvo_device_mapping { +	u8 initialized; +	u8 dvo_port; +	u8 slave_addr; +	u8 dvo_wiring; +	u8 i2c_pin; +	u8 i2c_speed; +	u8 ddc_pin; +}; + +struct intel_gmbus { +	struct i2c_adapter adapter; +	struct i2c_adapter *force_bit; +	u32 reg0; +}; + +/* Register offset maps */ +struct psb_offset { +	u32	fp0; +	u32	fp1; +	u32	cntr; +	u32	conf; +	u32	src; +	u32	dpll; +	u32	dpll_md; +	u32	htotal; +	u32	hblank; +	u32	hsync; +	u32	vtotal; +	u32	vblank; +	u32	vsync; +	u32	stride; +	u32	size; +	u32	pos; +	u32	surf; +	u32	addr; +	u32	base; +	u32	status; +	u32	linoff; +	u32	tileoff; +	u32	palette; +}; + +/* + *	Register save state. This is used to hold the context when the + *	device is powered off. In the case of Oaktrail this can (but does not + *	yet) include screen blank. Operations occuring during the save + *	update the register cache instead. + */ + +/* Common status for pipes */ +struct psb_pipe { +	u32	fp0; +	u32	fp1; +	u32	cntr; +	u32	conf; +	u32	src; +	u32	dpll; +	u32	dpll_md; +	u32	htotal; +	u32	hblank; +	u32	hsync; +	u32	vtotal; +	u32	vblank; +	u32	vsync; +	u32	stride; +	u32	size; +	u32	pos; +	u32	base; +	u32	surf; +	u32	addr; +	u32	status; +	u32	linoff; +	u32	tileoff; +	u32	palette[256]; +}; + +struct psb_state { +	uint32_t saveVCLK_DIVISOR_VGA0; +	uint32_t saveVCLK_DIVISOR_VGA1; +	uint32_t saveVCLK_POST_DIV; +	uint32_t saveVGACNTRL; +	uint32_t saveADPA; +	uint32_t saveLVDS; +	uint32_t saveDVOA; +	uint32_t saveDVOB; +	uint32_t saveDVOC; +	uint32_t savePP_ON; +	uint32_t savePP_OFF; +	uint32_t savePP_CONTROL; +	uint32_t savePP_CYCLE; +	uint32_t savePFIT_CONTROL; +	uint32_t saveCLOCKGATING; +	uint32_t saveDSPARB; +	uint32_t savePFIT_AUTO_RATIOS; +	uint32_t savePFIT_PGM_RATIOS; +	uint32_t savePP_ON_DELAYS; +	uint32_t savePP_OFF_DELAYS; +	uint32_t savePP_DIVISOR; +	uint32_t saveBCLRPAT_A; +	uint32_t saveBCLRPAT_B; +	uint32_t savePERF_MODE; +	uint32_t saveDSPFW1; +	uint32_t saveDSPFW2; +	uint32_t saveDSPFW3; +	uint32_t saveDSPFW4; +	uint32_t saveDSPFW5; +	uint32_t saveDSPFW6; +	uint32_t saveCHICKENBIT; +	uint32_t saveDSPACURSOR_CTRL; +	uint32_t saveDSPBCURSOR_CTRL; +	uint32_t saveDSPACURSOR_BASE; +	uint32_t saveDSPBCURSOR_BASE; +	uint32_t saveDSPACURSOR_POS; +	uint32_t saveDSPBCURSOR_POS; +	uint32_t saveOV_OVADD; +	uint32_t saveOV_OGAMC0; +	uint32_t saveOV_OGAMC1; +	uint32_t saveOV_OGAMC2; +	uint32_t saveOV_OGAMC3; +	uint32_t saveOV_OGAMC4; +	uint32_t saveOV_OGAMC5; +	uint32_t saveOVC_OVADD; +	uint32_t saveOVC_OGAMC0; +	uint32_t saveOVC_OGAMC1; +	uint32_t saveOVC_OGAMC2; +	uint32_t saveOVC_OGAMC3; +	uint32_t saveOVC_OGAMC4; +	uint32_t saveOVC_OGAMC5; + +	/* DPST register save */ +	uint32_t saveHISTOGRAM_INT_CONTROL_REG; +	uint32_t saveHISTOGRAM_LOGIC_CONTROL_REG; +	uint32_t savePWM_CONTROL_LOGIC; +}; + +struct medfield_state { +	uint32_t saveMIPI; +	uint32_t saveMIPI_C; + +	uint32_t savePFIT_CONTROL; +	uint32_t savePFIT_PGM_RATIOS; +	uint32_t saveHDMIPHYMISCCTL; +	uint32_t saveHDMIB_CONTROL; +}; + +struct cdv_state { +	uint32_t saveDSPCLK_GATE_D; +	uint32_t saveRAMCLK_GATE_D; +	uint32_t saveDSPARB; +	uint32_t saveDSPFW[6]; +	uint32_t saveADPA; +	uint32_t savePP_CONTROL; +	uint32_t savePFIT_PGM_RATIOS; +	uint32_t saveLVDS; +	uint32_t savePFIT_CONTROL; +	uint32_t savePP_ON_DELAYS; +	uint32_t savePP_OFF_DELAYS; +	uint32_t savePP_CYCLE; +	uint32_t saveVGACNTRL; +	uint32_t saveIER; +	uint32_t saveIMR; +	u8	 saveLBB; +}; + +struct psb_save_area { +	struct psb_pipe pipe[3]; +	uint32_t saveBSM; +	uint32_t saveVBT; +	union { +	        struct psb_state psb; +		struct medfield_state mdfld; +		struct cdv_state cdv; +	}; +	uint32_t saveBLC_PWM_CTL2; +	uint32_t saveBLC_PWM_CTL; +}; + +struct psb_ops; + +#define PSB_NUM_PIPE		3 + +struct drm_psb_private { +	struct drm_device *dev; +	struct pci_dev *aux_pdev; /* Currently only used by mrst */ +	const struct psb_ops *ops; +	const struct psb_offset *regmap; +	 +	struct child_device_config *child_dev; +	int child_dev_num; + +	struct psb_gtt gtt; + +	/* GTT Memory manager */ +	struct psb_gtt_mm *gtt_mm; +	struct page *scratch_page; +	u32 __iomem *gtt_map; +	uint32_t stolen_base; +	u8 __iomem *vram_addr; +	unsigned long vram_stolen_size; +	int gtt_initialized; +	u16 gmch_ctrl;		/* Saved GTT setup */ +	u32 pge_ctl; + +	struct mutex gtt_mutex; +	struct resource *gtt_mem;	/* Our PCI resource */ + +	struct psb_mmu_driver *mmu; +	struct psb_mmu_pd *pf_pd; + +	/* Register base */ +	uint8_t __iomem *sgx_reg; +	uint8_t __iomem *vdc_reg; +	uint8_t __iomem *aux_reg; /* Auxillary vdc pipe regs */ +	uint32_t gatt_free_offset; + +	/* Fencing / irq */ +	uint32_t vdc_irq_mask; +	uint32_t pipestat[PSB_NUM_PIPE]; + +	spinlock_t irqmask_lock; + +	/* Power */ +	bool suspended; +	bool display_power; +	int display_count; + +	/* Modesetting */ +	struct psb_intel_mode_device mode_dev; +	bool modeset;	/* true if we have done the mode_device setup */ + +	struct drm_crtc *plane_to_crtc_mapping[PSB_NUM_PIPE]; +	struct drm_crtc *pipe_to_crtc_mapping[PSB_NUM_PIPE]; +	uint32_t num_pipe; + +	/* OSPM info (Power management base) (TODO: can go ?) */ +	uint32_t ospm_base; + +	/* Sizes info */ +	u32 fuse_reg_value; +	u32 video_device_fuse; + +	/* PCI revision ID for B0:D2:F0 */ +	uint8_t platform_rev_id; + +	/* gmbus */ +	struct intel_gmbus *gmbus; +	uint8_t __iomem *gmbus_reg; + +	/* Used by SDVO */ +	int crt_ddc_pin; +	/* FIXME: The mappings should be parsed from bios but for now we can +		  pretend there are no mappings available */ +	struct sdvo_device_mapping sdvo_mappings[2]; +	u32 hotplug_supported_mask; +	struct drm_property *broadcast_rgb_property; +	struct drm_property *force_audio_property; + +	/* LVDS info */ +	int backlight_duty_cycle;	/* restore backlight to this value */ +	bool panel_wants_dither; +	struct drm_display_mode *panel_fixed_mode; +	struct drm_display_mode *lfp_lvds_vbt_mode; +	struct drm_display_mode *sdvo_lvds_vbt_mode; + +	struct bdb_lvds_backlight *lvds_bl; /* LVDS backlight info from VBT */ +	struct psb_intel_i2c_chan *lvds_i2c_bus; /* FIXME: Remove this? */ + +	/* Feature bits from the VBIOS */ +	unsigned int int_tv_support:1; +	unsigned int lvds_dither:1; +	unsigned int lvds_vbt:1; +	unsigned int int_crt_support:1; +	unsigned int lvds_use_ssc:1; +	int lvds_ssc_freq; +	bool is_lvds_on; +	bool is_mipi_on; +	u32 mipi_ctrl_display; + +	unsigned int core_freq; +	uint32_t iLVDS_enable; + +	/* Runtime PM state */ +	int rpm_enabled; + +	/* MID specific */ +	bool has_gct; +	struct oaktrail_gct_data gct_data; + +	/* Oaktrail HDMI state */ +	struct oaktrail_hdmi_dev *hdmi_priv; +	 +	/* Register state */ +	struct psb_save_area regs; + +	/* MSI reg save */ +	uint32_t msi_addr; +	uint32_t msi_data; + +	/* Hotplug handling */ +	struct work_struct hotplug_work; + +	/* LID-Switch */ +	spinlock_t lid_lock; +	struct timer_list lid_timer; +	struct psb_intel_opregion opregion; +	u32 lid_last_state; + +	/* Watchdog */ +	uint32_t apm_reg; +	uint16_t apm_base; + +	/* +	 * Used for modifying backlight from +	 * xrandr -- consider removing and using HAL instead +	 */ +	struct backlight_device *backlight_device; +	struct drm_property *backlight_property; +	bool backlight_enabled; +	int backlight_level; +	uint32_t blc_adj1; +	uint32_t blc_adj2; + +	void *fbdev; + +	/* 2D acceleration */ +	spinlock_t lock_2d; + +	/* Panel brightness */ +	int brightness; +	int brightness_adjusted; + +	bool dsr_enable; +	u32 dsr_fb_update; +	bool dpi_panel_on[3]; +	void *dsi_configs[2]; +	u32 bpp; +	u32 bpp2; + +	u32 pipeconf[3]; +	u32 dspcntr[3]; + +	int mdfld_panel_id; + +	bool dplla_96mhz;	/* DPLL data from the VBT */ + +	struct { +		int rate; +		int lanes; +		int preemphasis; +		int vswing; + +		bool initialized; +		bool support; +		int bpp; +		struct edp_power_seq pps; +	} edp; +	uint8_t panel_type; +}; + + +/* Operations for each board type */ +struct psb_ops { +	const char *name; +	unsigned int accel_2d:1; +	int pipes;		/* Number of output pipes */ +	int crtcs;		/* Number of CRTCs */ +	int sgx_offset;		/* Base offset of SGX device */ +	int hdmi_mask;		/* Mask of HDMI CRTCs */ +	int lvds_mask;		/* Mask of LVDS CRTCs */ +	int sdvo_mask;		/* Mask of SDVO CRTCs */ +	int cursor_needs_phys;  /* If cursor base reg need physical address */ + +	/* Sub functions */ +	struct drm_crtc_helper_funcs const *crtc_helper; +	struct drm_crtc_funcs const *crtc_funcs; +	const struct gma_clock_funcs *clock_funcs; + +	/* Setup hooks */ +	int (*chip_setup)(struct drm_device *dev); +	void (*chip_teardown)(struct drm_device *dev); +	/* Optional helper caller after modeset */ +	void (*errata)(struct drm_device *dev); + +	/* Display management hooks */ +	int (*output_init)(struct drm_device *dev); +	int (*hotplug)(struct drm_device *dev); +	void (*hotplug_enable)(struct drm_device *dev, bool on); +	/* Power management hooks */ +	void (*init_pm)(struct drm_device *dev); +	int (*save_regs)(struct drm_device *dev); +	int (*restore_regs)(struct drm_device *dev); +	int (*power_up)(struct drm_device *dev); +	int (*power_down)(struct drm_device *dev); +	void (*update_wm)(struct drm_device *dev, struct drm_crtc *crtc); +	void (*disable_sr)(struct drm_device *dev); + +	void (*lvds_bl_power)(struct drm_device *dev, bool on); +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE +	/* Backlight */ +	int (*backlight_init)(struct drm_device *dev); +#endif +	int i2c_bus;		/* I2C bus identifier for Moorestown */ +}; + + + +extern int drm_crtc_probe_output_modes(struct drm_device *dev, int, int); +extern int drm_pick_crtcs(struct drm_device *dev); + +static inline struct drm_psb_private *psb_priv(struct drm_device *dev) +{ +	return (struct drm_psb_private *) dev->dev_private; +} + +/* psb_irq.c */ +extern irqreturn_t psb_irq_handler(int irq, void *arg); +extern int psb_irq_enable_dpst(struct drm_device *dev); +extern int psb_irq_disable_dpst(struct drm_device *dev); +extern void psb_irq_preinstall(struct drm_device *dev); +extern int psb_irq_postinstall(struct drm_device *dev); +extern void psb_irq_uninstall(struct drm_device *dev); +extern void psb_irq_turn_on_dpst(struct drm_device *dev); +extern void psb_irq_turn_off_dpst(struct drm_device *dev); + +extern void psb_irq_uninstall_islands(struct drm_device *dev, int hw_islands); +extern int psb_vblank_wait2(struct drm_device *dev, unsigned int *sequence); +extern int psb_vblank_wait(struct drm_device *dev, unsigned int *sequence); +extern int psb_enable_vblank(struct drm_device *dev, int crtc); +extern void psb_disable_vblank(struct drm_device *dev, int crtc); +void +psb_enable_pipestat(struct drm_psb_private *dev_priv, int pipe, u32 mask); + +void +psb_disable_pipestat(struct drm_psb_private *dev_priv, int pipe, u32 mask); + +extern u32 psb_get_vblank_counter(struct drm_device *dev, int crtc); + +/* framebuffer.c */ +extern int psbfb_probed(struct drm_device *dev); +extern int psbfb_remove(struct drm_device *dev, +			struct drm_framebuffer *fb); +/* accel_2d.c */ +extern void psbfb_copyarea(struct fb_info *info, +					const struct fb_copyarea *region); +extern int psbfb_sync(struct fb_info *info); +extern void psb_spank(struct drm_psb_private *dev_priv); + +/* psb_reset.c */ +extern void psb_lid_timer_init(struct drm_psb_private *dev_priv); +extern void psb_lid_timer_takedown(struct drm_psb_private *dev_priv); +extern void psb_print_pagefault(struct drm_psb_private *dev_priv); + +/* modesetting */ +extern void psb_modeset_init(struct drm_device *dev); +extern void psb_modeset_cleanup(struct drm_device *dev); +extern int psb_fbdev_init(struct drm_device *dev); + +/* backlight.c */ +int gma_backlight_init(struct drm_device *dev); +void gma_backlight_exit(struct drm_device *dev); +void gma_backlight_disable(struct drm_device *dev); +void gma_backlight_enable(struct drm_device *dev); +void gma_backlight_set(struct drm_device *dev, int v); + +/* oaktrail_crtc.c */ +extern const struct drm_crtc_helper_funcs oaktrail_helper_funcs; + +/* oaktrail_lvds.c */ +extern void oaktrail_lvds_init(struct drm_device *dev, +		    struct psb_intel_mode_device *mode_dev); + +/* psb_intel_display.c */ +extern const struct drm_crtc_helper_funcs psb_intel_helper_funcs; +extern const struct drm_crtc_funcs psb_intel_crtc_funcs; + +/* psb_intel_lvds.c */ +extern const struct drm_connector_helper_funcs +					psb_intel_lvds_connector_helper_funcs; +extern const struct drm_connector_funcs psb_intel_lvds_connector_funcs; + +/* gem.c */ +extern void psb_gem_free_object(struct drm_gem_object *obj); +extern int psb_gem_get_aperture(struct drm_device *dev, void *data, +			struct drm_file *file); +extern int psb_gem_dumb_create(struct drm_file *file, struct drm_device *dev, +			struct drm_mode_create_dumb *args); +extern int psb_gem_dumb_map_gtt(struct drm_file *file, struct drm_device *dev, +			uint32_t handle, uint64_t *offset); +extern int psb_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); +extern int psb_gem_create_ioctl(struct drm_device *dev, void *data, +			struct drm_file *file); +extern int psb_gem_mmap_ioctl(struct drm_device *dev, void *data, +					struct drm_file *file); + +/* psb_device.c */ +extern const struct psb_ops psb_chip_ops; + +/* oaktrail_device.c */ +extern const struct psb_ops oaktrail_chip_ops; + +/* mdlfd_device.c */ +extern const struct psb_ops mdfld_chip_ops; + +/* cdv_device.c */ +extern const struct psb_ops cdv_chip_ops; + +/* Debug print bits setting */ +#define PSB_D_GENERAL (1 << 0) +#define PSB_D_INIT    (1 << 1) +#define PSB_D_IRQ     (1 << 2) +#define PSB_D_ENTRY   (1 << 3) +/* debug the get H/V BP/FP count */ +#define PSB_D_HV      (1 << 4) +#define PSB_D_DBI_BF  (1 << 5) +#define PSB_D_PM      (1 << 6) +#define PSB_D_RENDER  (1 << 7) +#define PSB_D_REG     (1 << 8) +#define PSB_D_MSVDX   (1 << 9) +#define PSB_D_TOPAZ   (1 << 10) + +extern int drm_idle_check_interval; + +/* Utilities */ +static inline u32 MRST_MSG_READ32(uint port, uint offset) +{ +	int mcr = (0xD0<<24) | (port << 16) | (offset << 8); +	uint32_t ret_val = 0; +	struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); +	pci_write_config_dword(pci_root, 0xD0, mcr); +	pci_read_config_dword(pci_root, 0xD4, &ret_val); +	pci_dev_put(pci_root); +	return ret_val; +} +static inline void MRST_MSG_WRITE32(uint port, uint offset, u32 value) +{ +	int mcr = (0xE0<<24) | (port << 16) | (offset << 8) | 0xF0; +	struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); +	pci_write_config_dword(pci_root, 0xD4, value); +	pci_write_config_dword(pci_root, 0xD0, mcr); +	pci_dev_put(pci_root); +} +static inline u32 MDFLD_MSG_READ32(uint port, uint offset) +{ +	int mcr = (0x10<<24) | (port << 16) | (offset << 8); +	uint32_t ret_val = 0; +	struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); +	pci_write_config_dword(pci_root, 0xD0, mcr); +	pci_read_config_dword(pci_root, 0xD4, &ret_val); +	pci_dev_put(pci_root); +	return ret_val; +} +static inline void MDFLD_MSG_WRITE32(uint port, uint offset, u32 value) +{ +	int mcr = (0x11<<24) | (port << 16) | (offset << 8) | 0xF0; +	struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); +	pci_write_config_dword(pci_root, 0xD4, value); +	pci_write_config_dword(pci_root, 0xD0, mcr); +	pci_dev_put(pci_root); +} + +static inline uint32_t REGISTER_READ(struct drm_device *dev, uint32_t reg) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	return ioread32(dev_priv->vdc_reg + reg); +} + +static inline uint32_t REGISTER_READ_AUX(struct drm_device *dev, uint32_t reg) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	return ioread32(dev_priv->aux_reg + reg); +} + +#define REG_READ(reg)	       REGISTER_READ(dev, (reg)) +#define REG_READ_AUX(reg)      REGISTER_READ_AUX(dev, (reg)) + +/* Useful for post reads */ +static inline uint32_t REGISTER_READ_WITH_AUX(struct drm_device *dev, +					      uint32_t reg, int aux) +{ +	uint32_t val; + +	if (aux) +		val = REG_READ_AUX(reg); +	else +		val = REG_READ(reg); + +	return val; +} + +#define REG_READ_WITH_AUX(reg, aux) REGISTER_READ_WITH_AUX(dev, (reg), (aux)) + +static inline void REGISTER_WRITE(struct drm_device *dev, uint32_t reg, +				  uint32_t val) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	iowrite32((val), dev_priv->vdc_reg + (reg)); +} + +static inline void REGISTER_WRITE_AUX(struct drm_device *dev, uint32_t reg, +				      uint32_t val) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	iowrite32((val), dev_priv->aux_reg + (reg)); +} + +#define REG_WRITE(reg, val)	REGISTER_WRITE(dev, (reg), (val)) +#define REG_WRITE_AUX(reg, val)	REGISTER_WRITE_AUX(dev, (reg), (val)) + +static inline void REGISTER_WRITE_WITH_AUX(struct drm_device *dev, uint32_t reg, +				      uint32_t val, int aux) +{ +	if (aux) +		REG_WRITE_AUX(reg, val); +	else +		REG_WRITE(reg, val); +} + +#define REG_WRITE_WITH_AUX(reg, val, aux) REGISTER_WRITE_WITH_AUX(dev, (reg), (val), (aux)) + +static inline void REGISTER_WRITE16(struct drm_device *dev, +					uint32_t reg, uint32_t val) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	iowrite16((val), dev_priv->vdc_reg + (reg)); +} + +#define REG_WRITE16(reg, val)	  REGISTER_WRITE16(dev, (reg), (val)) + +static inline void REGISTER_WRITE8(struct drm_device *dev, +				       uint32_t reg, uint32_t val) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	iowrite8((val), dev_priv->vdc_reg + (reg)); +} + +#define REG_WRITE8(reg, val)		REGISTER_WRITE8(dev, (reg), (val)) + +#define PSB_WVDC32(_val, _offs)		iowrite32(_val, dev_priv->vdc_reg + (_offs)) +#define PSB_RVDC32(_offs)		ioread32(dev_priv->vdc_reg + (_offs)) + +/* #define TRAP_SGX_PM_FAULT 1 */ +#ifdef TRAP_SGX_PM_FAULT +#define PSB_RSGX32(_offs)						\ +({									\ +	if (inl(dev_priv->apm_base + PSB_APM_STS) & 0x3) {		\ +		printk(KERN_ERR						\ +			"access sgx when it's off!! (READ) %s, %d\n",	\ +	       __FILE__, __LINE__);					\ +		melay(1000);						\ +	}								\ +	ioread32(dev_priv->sgx_reg + (_offs));				\ +}) +#else +#define PSB_RSGX32(_offs)		ioread32(dev_priv->sgx_reg + (_offs)) +#endif +#define PSB_WSGX32(_val, _offs)		iowrite32(_val, dev_priv->sgx_reg + (_offs)) + +#define MSVDX_REG_DUMP 0 + +#define PSB_WMSVDX32(_val, _offs)	iowrite32(_val, dev_priv->msvdx_reg + (_offs)) +#define PSB_RMSVDX32(_offs)		ioread32(dev_priv->msvdx_reg + (_offs)) + +#endif diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c new file mode 100644 index 00000000000..87b50ba64ed --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_intel_display.c @@ -0,0 +1,584 @@ +/* + * Copyright © 2006-2011 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + */ + +#include <linux/i2c.h> + +#include <drm/drmP.h> +#include "framebuffer.h" +#include "psb_drv.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "gma_display.h" +#include "power.h" + +#define INTEL_LIMIT_I9XX_SDVO_DAC   0 +#define INTEL_LIMIT_I9XX_LVDS	    1 + +static const struct gma_limit_t psb_intel_limits[] = { +	{			/* INTEL_LIMIT_I9XX_SDVO_DAC */ +	 .dot = {.min = 20000, .max = 400000}, +	 .vco = {.min = 1400000, .max = 2800000}, +	 .n = {.min = 1, .max = 6}, +	 .m = {.min = 70, .max = 120}, +	 .m1 = {.min = 8, .max = 18}, +	 .m2 = {.min = 3, .max = 7}, +	 .p = {.min = 5, .max = 80}, +	 .p1 = {.min = 1, .max = 8}, +	 .p2 = {.dot_limit = 200000, .p2_slow = 10, .p2_fast = 5}, +	 .find_pll = gma_find_best_pll, +	 }, +	{			/* INTEL_LIMIT_I9XX_LVDS */ +	 .dot = {.min = 20000, .max = 400000}, +	 .vco = {.min = 1400000, .max = 2800000}, +	 .n = {.min = 1, .max = 6}, +	 .m = {.min = 70, .max = 120}, +	 .m1 = {.min = 8, .max = 18}, +	 .m2 = {.min = 3, .max = 7}, +	 .p = {.min = 7, .max = 98}, +	 .p1 = {.min = 1, .max = 8}, +	 /* The single-channel range is 25-112Mhz, and dual-channel +	  * is 80-224Mhz.  Prefer single channel as much as possible. +	  */ +	 .p2 = {.dot_limit = 112000, .p2_slow = 14, .p2_fast = 7}, +	 .find_pll = gma_find_best_pll, +	 }, +}; + +static const struct gma_limit_t *psb_intel_limit(struct drm_crtc *crtc, +						 int refclk) +{ +	const struct gma_limit_t *limit; + +	if (gma_pipe_has_type(crtc, INTEL_OUTPUT_LVDS)) +		limit = &psb_intel_limits[INTEL_LIMIT_I9XX_LVDS]; +	else +		limit = &psb_intel_limits[INTEL_LIMIT_I9XX_SDVO_DAC]; +	return limit; +} + +static void psb_intel_clock(int refclk, struct gma_clock_t *clock) +{ +	clock->m = 5 * (clock->m1 + 2) + (clock->m2 + 2); +	clock->p = clock->p1 * clock->p2; +	clock->vco = refclk * clock->m / (clock->n + 2); +	clock->dot = clock->vco / clock->p; +} + +/** + * Return the pipe currently connected to the panel fitter, + * or -1 if the panel fitter is not present or not in use + */ +static int psb_intel_panel_fitter_pipe(struct drm_device *dev) +{ +	u32 pfit_control; + +	pfit_control = REG_READ(PFIT_CONTROL); + +	/* See if the panel fitter is in use */ +	if ((pfit_control & PFIT_ENABLE) == 0) +		return -1; +	/* Must be on PIPE 1 for PSB */ +	return 1; +} + +static int psb_intel_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 drm_device *dev = crtc->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	int refclk; +	struct gma_clock_t clock; +	u32 dpll = 0, fp = 0, dspcntr, pipeconf; +	bool ok, is_sdvo = false; +	bool is_lvds = false, is_tv = false; +	struct drm_mode_config *mode_config = &dev->mode_config; +	struct drm_connector *connector; +	const struct gma_limit_t *limit; + +	/* No scan out no play */ +	if (crtc->primary->fb == NULL) { +		crtc_funcs->mode_set_base(crtc, x, y, old_fb); +		return 0; +	} + +	list_for_each_entry(connector, &mode_config->connector_list, head) { +		struct gma_encoder *gma_encoder = gma_attached_encoder(connector); + +		if (!connector->encoder +		    || connector->encoder->crtc != crtc) +			continue; + +		switch (gma_encoder->type) { +		case INTEL_OUTPUT_LVDS: +			is_lvds = true; +			break; +		case INTEL_OUTPUT_SDVO: +			is_sdvo = true; +			break; +		case INTEL_OUTPUT_TVOUT: +			is_tv = true; +			break; +		} +	} + +	refclk = 96000; + +	limit = gma_crtc->clock_funcs->limit(crtc, refclk); + +	ok = limit->find_pll(limit, crtc, adjusted_mode->clock, refclk, +				 &clock); +	if (!ok) { +		DRM_ERROR("Couldn't find PLL settings for mode! target: %d, actual: %d", +			  adjusted_mode->clock, clock.dot); +		return 0; +	} + +	fp = clock.n << 16 | clock.m1 << 8 | clock.m2; + +	dpll = DPLL_VGA_MODE_DIS; +	if (is_lvds) { +		dpll |= DPLLB_MODE_LVDS; +		dpll |= DPLL_DVO_HIGH_SPEED; +	} else +		dpll |= DPLLB_MODE_DAC_SERIAL; +	if (is_sdvo) { +		int sdvo_pixel_multiply = +			    adjusted_mode->clock / mode->clock; +		dpll |= DPLL_DVO_HIGH_SPEED; +		dpll |= +		    (sdvo_pixel_multiply - 1) << SDVO_MULTIPLIER_SHIFT_HIRES; +	} + +	/* compute bitmask from p1 value */ +	dpll |= (1 << (clock.p1 - 1)) << 16; +	switch (clock.p2) { +	case 5: +		dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_5; +		break; +	case 7: +		dpll |= DPLLB_LVDS_P2_CLOCK_DIV_7; +		break; +	case 10: +		dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_10; +		break; +	case 14: +		dpll |= DPLLB_LVDS_P2_CLOCK_DIV_14; +		break; +	} + +	if (is_tv) { +		/* XXX: just matching BIOS for now */ +/*	dpll |= PLL_REF_INPUT_TVCLKINBC; */ +		dpll |= 3; +	} +	dpll |= PLL_REF_INPUT_DREFCLK; + +	/* setup pipeconf */ +	pipeconf = REG_READ(map->conf); + +	/* Set up the display plane register */ +	dspcntr = DISPPLANE_GAMMA_ENABLE; + +	if (pipe == 0) +		dspcntr |= DISPPLANE_SEL_PIPE_A; +	else +		dspcntr |= DISPPLANE_SEL_PIPE_B; + +	dspcntr |= DISPLAY_PLANE_ENABLE; +	pipeconf |= PIPEACONF_ENABLE; +	dpll |= DPLL_VCO_ENABLE; + + +	/* Disable the panel fitter if it was on our pipe */ +	if (psb_intel_panel_fitter_pipe(dev) == pipe) +		REG_WRITE(PFIT_CONTROL, 0); + +	drm_mode_debug_printmodeline(mode); + +	if (dpll & DPLL_VCO_ENABLE) { +		REG_WRITE(map->fp0, fp); +		REG_WRITE(map->dpll, dpll & ~DPLL_VCO_ENABLE); +		REG_READ(map->dpll); +		udelay(150); +	} + +	/* The LVDS pin pair needs to be on before the DPLLs are enabled. +	 * This is an exception to the general rule that mode_set doesn't turn +	 * things on. +	 */ +	if (is_lvds) { +		u32 lvds = REG_READ(LVDS); + +		lvds &= ~LVDS_PIPEB_SELECT; +		if (pipe == 1) +			lvds |= LVDS_PIPEB_SELECT; + +		lvds |= LVDS_PORT_EN | LVDS_A0A2_CLKA_POWER_UP; +		/* Set the B0-B3 data pairs corresponding to +		 * whether we're going to +		 * set the DPLLs for dual-channel mode or not. +		 */ +		lvds &= ~(LVDS_B0B3_POWER_UP | LVDS_CLKB_POWER_UP); +		if (clock.p2 == 7) +			lvds |= LVDS_B0B3_POWER_UP | LVDS_CLKB_POWER_UP; + +		/* It would be nice to set 24 vs 18-bit mode (LVDS_A3_POWER_UP) +		 * appropriately here, but we need to look more +		 * thoroughly into how panels behave in the two modes. +		 */ + +		REG_WRITE(LVDS, lvds); +		REG_READ(LVDS); +	} + +	REG_WRITE(map->fp0, fp); +	REG_WRITE(map->dpll, dpll); +	REG_READ(map->dpll); +	/* Wait for the clocks to stabilize. */ +	udelay(150); + +	/* write it again -- the BIOS does, after all */ +	REG_WRITE(map->dpll, dpll); + +	REG_READ(map->dpll); +	/* Wait for the clocks to stabilize. */ +	udelay(150); + +	REG_WRITE(map->htotal, (adjusted_mode->crtc_hdisplay - 1) | +		  ((adjusted_mode->crtc_htotal - 1) << 16)); +	REG_WRITE(map->hblank, (adjusted_mode->crtc_hblank_start - 1) | +		  ((adjusted_mode->crtc_hblank_end - 1) << 16)); +	REG_WRITE(map->hsync, (adjusted_mode->crtc_hsync_start - 1) | +		  ((adjusted_mode->crtc_hsync_end - 1) << 16)); +	REG_WRITE(map->vtotal, (adjusted_mode->crtc_vdisplay - 1) | +		  ((adjusted_mode->crtc_vtotal - 1) << 16)); +	REG_WRITE(map->vblank, (adjusted_mode->crtc_vblank_start - 1) | +		  ((adjusted_mode->crtc_vblank_end - 1) << 16)); +	REG_WRITE(map->vsync, (adjusted_mode->crtc_vsync_start - 1) | +		  ((adjusted_mode->crtc_vsync_end - 1) << 16)); +	/* pipesrc and dspsize control the size that is scaled from, +	 * which should always be the user's requested size. +	 */ +	REG_WRITE(map->size, +		  ((mode->vdisplay - 1) << 16) | (mode->hdisplay - 1)); +	REG_WRITE(map->pos, 0); +	REG_WRITE(map->src, +		  ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1)); +	REG_WRITE(map->conf, pipeconf); +	REG_READ(map->conf); + +	gma_wait_for_vblank(dev); + +	REG_WRITE(map->cntr, dspcntr); + +	/* Flush the plane changes */ +	crtc_funcs->mode_set_base(crtc, x, y, old_fb); + +	gma_wait_for_vblank(dev); + +	return 0; +} + +/* Returns the clock of the currently programmed mode of the given pipe. */ +static int psb_intel_crtc_clock_get(struct drm_device *dev, +				struct drm_crtc *crtc) +{ +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct drm_psb_private *dev_priv = dev->dev_private; +	int pipe = gma_crtc->pipe; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; +	u32 dpll; +	u32 fp; +	struct gma_clock_t clock; +	bool is_lvds; +	struct psb_pipe *p = &dev_priv->regs.pipe[pipe]; + +	if (gma_power_begin(dev, false)) { +		dpll = REG_READ(map->dpll); +		if ((dpll & DISPLAY_RATE_SELECT_FPA1) == 0) +			fp = REG_READ(map->fp0); +		else +			fp = REG_READ(map->fp1); +		is_lvds = (pipe == 1) && (REG_READ(LVDS) & LVDS_PORT_EN); +		gma_power_end(dev); +	} else { +		dpll = p->dpll; + +		if ((dpll & DISPLAY_RATE_SELECT_FPA1) == 0) +			fp = p->fp0; +		else +		        fp = p->fp1; + +		is_lvds = (pipe == 1) && (dev_priv->regs.psb.saveLVDS & +								LVDS_PORT_EN); +	} + +	clock.m1 = (fp & FP_M1_DIV_MASK) >> FP_M1_DIV_SHIFT; +	clock.m2 = (fp & FP_M2_DIV_MASK) >> FP_M2_DIV_SHIFT; +	clock.n = (fp & FP_N_DIV_MASK) >> FP_N_DIV_SHIFT; + +	if (is_lvds) { +		clock.p1 = +		    ffs((dpll & +			 DPLL_FPA01_P1_POST_DIV_MASK_I830_LVDS) >> +			DPLL_FPA01_P1_POST_DIV_SHIFT); +		clock.p2 = 14; + +		if ((dpll & PLL_REF_INPUT_MASK) == +		    PLLB_REF_INPUT_SPREADSPECTRUMIN) { +			/* XXX: might not be 66MHz */ +			psb_intel_clock(66000, &clock); +		} else +			psb_intel_clock(48000, &clock); +	} else { +		if (dpll & PLL_P1_DIVIDE_BY_TWO) +			clock.p1 = 2; +		else { +			clock.p1 = +			    ((dpll & +			      DPLL_FPA01_P1_POST_DIV_MASK_I830) >> +			     DPLL_FPA01_P1_POST_DIV_SHIFT) + 2; +		} +		if (dpll & PLL_P2_DIVIDE_BY_4) +			clock.p2 = 4; +		else +			clock.p2 = 2; + +		psb_intel_clock(48000, &clock); +	} + +	/* XXX: It would be nice to validate the clocks, but we can't reuse +	 * i830PllIsValid() because it relies on the xf86_config connector +	 * configuration being accurate, which it isn't necessarily. +	 */ + +	return clock.dot; +} + +/** Returns the currently programmed mode of the given pipe. */ +struct drm_display_mode *psb_intel_crtc_mode_get(struct drm_device *dev, +					     struct drm_crtc *crtc) +{ +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	int pipe = gma_crtc->pipe; +	struct drm_display_mode *mode; +	int htot; +	int hsync; +	int vtot; +	int vsync; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_pipe *p = &dev_priv->regs.pipe[pipe]; +	const struct psb_offset *map = &dev_priv->regmap[pipe]; + +	if (gma_power_begin(dev, false)) { +		htot = REG_READ(map->htotal); +		hsync = REG_READ(map->hsync); +		vtot = REG_READ(map->vtotal); +		vsync = REG_READ(map->vsync); +		gma_power_end(dev); +	} else { +		htot = p->htotal; +		hsync = p->hsync; +		vtot = p->vtotal; +		vsync = p->vsync; +	} + +	mode = kzalloc(sizeof(*mode), GFP_KERNEL); +	if (!mode) +		return NULL; + +	mode->clock = psb_intel_crtc_clock_get(dev, crtc); +	mode->hdisplay = (htot & 0xffff) + 1; +	mode->htotal = ((htot & 0xffff0000) >> 16) + 1; +	mode->hsync_start = (hsync & 0xffff) + 1; +	mode->hsync_end = ((hsync & 0xffff0000) >> 16) + 1; +	mode->vdisplay = (vtot & 0xffff) + 1; +	mode->vtotal = ((vtot & 0xffff0000) >> 16) + 1; +	mode->vsync_start = (vsync & 0xffff) + 1; +	mode->vsync_end = ((vsync & 0xffff0000) >> 16) + 1; + +	drm_mode_set_name(mode); +	drm_mode_set_crtcinfo(mode, 0); + +	return mode; +} + +const struct drm_crtc_helper_funcs psb_intel_helper_funcs = { +	.dpms = gma_crtc_dpms, +	.mode_fixup = gma_crtc_mode_fixup, +	.mode_set = psb_intel_crtc_mode_set, +	.mode_set_base = gma_pipe_set_base, +	.prepare = gma_crtc_prepare, +	.commit = gma_crtc_commit, +	.disable = gma_crtc_disable, +}; + +const struct drm_crtc_funcs psb_intel_crtc_funcs = { +	.save = gma_crtc_save, +	.restore = gma_crtc_restore, +	.cursor_set = gma_crtc_cursor_set, +	.cursor_move = gma_crtc_cursor_move, +	.gamma_set = gma_crtc_gamma_set, +	.set_config = gma_crtc_set_config, +	.destroy = gma_crtc_destroy, +}; + +const struct gma_clock_funcs psb_clock_funcs = { +	.clock = psb_intel_clock, +	.limit = psb_intel_limit, +	.pll_is_valid = gma_pll_is_valid, +}; + +/* + * Set the default value of cursor control and base register + * to zero. This is a workaround for h/w defect on Oaktrail + */ +static void psb_intel_cursor_init(struct drm_device *dev, +				  struct gma_crtc *gma_crtc) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 control[3] = { CURACNTR, CURBCNTR, CURCCNTR }; +	u32 base[3] = { CURABASE, CURBBASE, CURCBASE }; +	struct gtt_range *cursor_gt; + +	if (dev_priv->ops->cursor_needs_phys) { +		/* Allocate 4 pages of stolen mem for a hardware cursor. That +		 * is enough for the 64 x 64 ARGB cursors we support. +		 */ +		cursor_gt = psb_gtt_alloc_range(dev, 4 * PAGE_SIZE, "cursor", 1, +						PAGE_SIZE); +		if (!cursor_gt) { +			gma_crtc->cursor_gt = NULL; +			goto out; +		} +		gma_crtc->cursor_gt = cursor_gt; +		gma_crtc->cursor_addr = dev_priv->stolen_base + +							cursor_gt->offset; +	} else { +		gma_crtc->cursor_gt = NULL; +	} + +out: +	REG_WRITE(control[gma_crtc->pipe], 0); +	REG_WRITE(base[gma_crtc->pipe], 0); +} + +void psb_intel_crtc_init(struct drm_device *dev, int pipe, +		     struct psb_intel_mode_device *mode_dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_crtc *gma_crtc; +	int i; +	uint16_t *r_base, *g_base, *b_base; + +	/* We allocate a extra array of drm_connector pointers +	 * for fbdev after the crtc */ +	gma_crtc = kzalloc(sizeof(struct gma_crtc) + +			(INTELFB_CONN_LIMIT * sizeof(struct drm_connector *)), +			GFP_KERNEL); +	if (gma_crtc == NULL) +		return; + +	gma_crtc->crtc_state = +		kzalloc(sizeof(struct psb_intel_crtc_state), GFP_KERNEL); +	if (!gma_crtc->crtc_state) { +		dev_err(dev->dev, "Crtc state error: No memory\n"); +		kfree(gma_crtc); +		return; +	} + +	/* Set the CRTC operations from the chip specific data */ +	drm_crtc_init(dev, &gma_crtc->base, dev_priv->ops->crtc_funcs); + +	/* Set the CRTC clock functions from chip specific data */ +	gma_crtc->clock_funcs = dev_priv->ops->clock_funcs; + +	drm_mode_crtc_set_gamma_size(&gma_crtc->base, 256); +	gma_crtc->pipe = pipe; +	gma_crtc->plane = pipe; + +	r_base = gma_crtc->base.gamma_store; +	g_base = r_base + 256; +	b_base = g_base + 256; +	for (i = 0; i < 256; i++) { +		gma_crtc->lut_r[i] = i; +		gma_crtc->lut_g[i] = i; +		gma_crtc->lut_b[i] = i; +		r_base[i] = i << 8; +		g_base[i] = i << 8; +		b_base[i] = i << 8; + +		gma_crtc->lut_adj[i] = 0; +	} + +	gma_crtc->mode_dev = mode_dev; +	gma_crtc->cursor_addr = 0; + +	drm_crtc_helper_add(&gma_crtc->base, +						dev_priv->ops->crtc_helper); + +	/* Setup the array of drm_connector pointer array */ +	gma_crtc->mode_set.crtc = &gma_crtc->base; +	BUG_ON(pipe >= ARRAY_SIZE(dev_priv->plane_to_crtc_mapping) || +	       dev_priv->plane_to_crtc_mapping[gma_crtc->plane] != NULL); +	dev_priv->plane_to_crtc_mapping[gma_crtc->plane] = &gma_crtc->base; +	dev_priv->pipe_to_crtc_mapping[gma_crtc->pipe] = &gma_crtc->base; +	gma_crtc->mode_set.connectors = (struct drm_connector **)(gma_crtc + 1); +	gma_crtc->mode_set.num_connectors = 0; +	psb_intel_cursor_init(dev, gma_crtc); + +	/* Set to true so that the pipe is forced off on initial config. */ +	gma_crtc->active = true; +} + +struct drm_crtc *psb_intel_get_crtc_from_pipe(struct drm_device *dev, int pipe) +{ +	struct drm_crtc *crtc = NULL; + +	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { +		struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +		if (gma_crtc->pipe == pipe) +			break; +	} +	return crtc; +} + +int gma_connector_clones(struct drm_device *dev, int type_mask) +{ +	int index_mask = 0; +	struct drm_connector *connector; +	int entry = 0; + +	list_for_each_entry(connector, &dev->mode_config.connector_list, +			    head) { +		struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +		if (type_mask & (1 << gma_encoder->type)) +			index_mask |= (1 << entry); +		entry++; +	} +	return index_mask; +} diff --git a/drivers/gpu/drm/gma500/psb_intel_drv.h b/drivers/gpu/drm/gma500/psb_intel_drv.h new file mode 100644 index 00000000000..336bd3aa1a0 --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_intel_drv.h @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2009-2011, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef __INTEL_DRV_H__ +#define __INTEL_DRV_H__ + +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <linux/gpio.h> +#include "gma_display.h" + +/* + * Display related stuff + */ + +/* maximum connectors per crtcs in the mode set */ +#define INTELFB_CONN_LIMIT 4 + +/* Intel Pipe Clone Bit */ +#define INTEL_HDMIB_CLONE_BIT 1 +#define INTEL_HDMIC_CLONE_BIT 2 +#define INTEL_HDMID_CLONE_BIT 3 +#define INTEL_HDMIE_CLONE_BIT 4 +#define INTEL_HDMIF_CLONE_BIT 5 +#define INTEL_SDVO_NON_TV_CLONE_BIT 6 +#define INTEL_SDVO_TV_CLONE_BIT 7 +#define INTEL_SDVO_LVDS_CLONE_BIT 8 +#define INTEL_ANALOG_CLONE_BIT 9 +#define INTEL_TV_CLONE_BIT 10 +#define INTEL_DP_B_CLONE_BIT 11 +#define INTEL_DP_C_CLONE_BIT 12 +#define INTEL_DP_D_CLONE_BIT 13 +#define INTEL_LVDS_CLONE_BIT 14 +#define INTEL_DVO_TMDS_CLONE_BIT 15 +#define INTEL_DVO_LVDS_CLONE_BIT 16 +#define INTEL_EDP_CLONE_BIT 17 + +/* these are outputs from the chip - integrated only + * external chips are via DVO or SDVO output */ +#define INTEL_OUTPUT_UNUSED 0 +#define INTEL_OUTPUT_ANALOG 1 +#define INTEL_OUTPUT_DVO 2 +#define INTEL_OUTPUT_SDVO 3 +#define INTEL_OUTPUT_LVDS 4 +#define INTEL_OUTPUT_TVOUT 5 +#define INTEL_OUTPUT_HDMI 6 +#define INTEL_OUTPUT_MIPI 7 +#define INTEL_OUTPUT_MIPI2 8 +#define INTEL_OUTPUT_DISPLAYPORT 9 +#define INTEL_OUTPUT_EDP 10 + +#define INTEL_MODE_PIXEL_MULTIPLIER_SHIFT (0x0) +#define INTEL_MODE_PIXEL_MULTIPLIER_MASK (0xf << INTEL_MODE_PIXEL_MULTIPLIER_SHIFT) + +static inline void +psb_intel_mode_set_pixel_multiplier(struct drm_display_mode *mode, +				int multiplier) +{ +	mode->clock *= multiplier; +	mode->private_flags |= multiplier; +} + +static inline int +psb_intel_mode_get_pixel_multiplier(const struct drm_display_mode *mode) +{ +	return (mode->private_flags & INTEL_MODE_PIXEL_MULTIPLIER_MASK) +	       >> INTEL_MODE_PIXEL_MULTIPLIER_SHIFT; +} + + +/* + * Hold information useally put on the device driver privates here, + * since it needs to be shared across multiple of devices drivers privates. + */ +struct psb_intel_mode_device { + +	/* +	 * Abstracted memory manager operations +	 */ +	 size_t(*bo_offset) (struct drm_device *dev, void *bo); + +	/* +	 * LVDS info +	 */ +	int backlight_duty_cycle;	/* restore backlight to this value */ +	bool panel_wants_dither; +	struct drm_display_mode *panel_fixed_mode; +	struct drm_display_mode *panel_fixed_mode2; +	struct drm_display_mode *vbt_mode;	/* if any */ + +	uint32_t saveBLC_PWM_CTL; +}; + +struct psb_intel_i2c_chan { +	/* for getting at dev. private (mmio etc.) */ +	struct drm_device *drm_dev; +	u32 reg;		/* GPIO reg */ +	struct i2c_adapter adapter; +	struct i2c_algo_bit_data algo; +	u8 slave_addr; +}; + +struct gma_encoder { +	struct drm_encoder base; +	int type; +	bool needs_tv_clock; +	void (*hot_plug)(struct gma_encoder *); +	int crtc_mask; +	int clone_mask; +	u32 ddi_select;	/* Channel info */ +#define DDI0_SELECT	0x01 +#define DDI1_SELECT	0x02 +#define DP_MASK		0x8000 +#define DDI_MASK	0x03 +	void *dev_priv; /* For sdvo_priv, lvds_priv, etc... */ + +	/* FIXME: Either make SDVO and LVDS store it's i2c here or give CDV it's +	   own set of output privates */ +	struct psb_intel_i2c_chan *i2c_bus; +	struct psb_intel_i2c_chan *ddc_bus; +}; + +struct gma_connector { +	struct drm_connector base; +	struct gma_encoder *encoder; +}; + +struct psb_intel_crtc_state { +	uint32_t saveDSPCNTR; +	uint32_t savePIPECONF; +	uint32_t savePIPESRC; +	uint32_t saveDPLL; +	uint32_t saveFP0; +	uint32_t saveFP1; +	uint32_t saveHTOTAL; +	uint32_t saveHBLANK; +	uint32_t saveHSYNC; +	uint32_t saveVTOTAL; +	uint32_t saveVBLANK; +	uint32_t saveVSYNC; +	uint32_t saveDSPSTRIDE; +	uint32_t saveDSPSIZE; +	uint32_t saveDSPPOS; +	uint32_t saveDSPBASE; +	uint32_t savePalette[256]; +}; + +struct gma_crtc { +	struct drm_crtc base; +	int pipe; +	int plane; +	uint32_t cursor_addr; +	struct gtt_range *cursor_gt; +	u8 lut_r[256], lut_g[256], lut_b[256]; +	u8 lut_adj[256]; +	struct psb_intel_framebuffer *fbdev_fb; +	/* a mode_set for fbdev users on this crtc */ +	struct drm_mode_set mode_set; + +	/* GEM object that holds our cursor */ +	struct drm_gem_object *cursor_obj; + +	struct drm_display_mode saved_mode; +	struct drm_display_mode saved_adjusted_mode; + +	struct psb_intel_mode_device *mode_dev; + +	/*crtc mode setting flags*/ +	u32 mode_flags; + +	bool active; + +	/* Saved Crtc HW states */ +	struct psb_intel_crtc_state *crtc_state; + +	const struct gma_clock_funcs *clock_funcs; +}; + +#define to_gma_crtc(x)	\ +		container_of(x, struct gma_crtc, base) +#define to_gma_connector(x) \ +		container_of(x, struct gma_connector, base) +#define to_gma_encoder(x)	\ +		container_of(x, struct gma_encoder, base) +#define to_psb_intel_framebuffer(x)	\ +		container_of(x, struct psb_intel_framebuffer, base) + +struct psb_intel_i2c_chan *psb_intel_i2c_create(struct drm_device *dev, +					const u32 reg, const char *name); +void psb_intel_i2c_destroy(struct psb_intel_i2c_chan *chan); +int psb_intel_ddc_get_modes(struct drm_connector *connector, +			    struct i2c_adapter *adapter); +extern bool psb_intel_ddc_probe(struct i2c_adapter *adapter); + +extern void psb_intel_crtc_init(struct drm_device *dev, int pipe, +			    struct psb_intel_mode_device *mode_dev); +extern void psb_intel_crt_init(struct drm_device *dev); +extern bool psb_intel_sdvo_init(struct drm_device *dev, int output_device); +extern void psb_intel_dvo_init(struct drm_device *dev); +extern void psb_intel_tv_init(struct drm_device *dev); +extern void psb_intel_lvds_init(struct drm_device *dev, +			    struct psb_intel_mode_device *mode_dev); +extern void psb_intel_lvds_set_brightness(struct drm_device *dev, int level); +extern void oaktrail_lvds_init(struct drm_device *dev, +			   struct psb_intel_mode_device *mode_dev); +extern void oaktrail_wait_for_INTR_PKT_SENT(struct drm_device *dev); +extern void oaktrail_dsi_init(struct drm_device *dev, +			   struct psb_intel_mode_device *mode_dev); +extern void mid_dsi_init(struct drm_device *dev, +		    struct psb_intel_mode_device *mode_dev, int dsi_num); + +extern struct drm_encoder *gma_best_encoder(struct drm_connector *connector); +extern void gma_connector_attach_encoder(struct gma_connector *connector, +					 struct gma_encoder *encoder); + +static inline struct gma_encoder *gma_attached_encoder( +						struct drm_connector *connector) +{ +	return to_gma_connector(connector)->encoder; +} + +extern struct drm_display_mode *psb_intel_crtc_mode_get(struct drm_device *dev, +						    struct drm_crtc *crtc); +extern struct drm_crtc *psb_intel_get_crtc_from_pipe(struct drm_device *dev, +						 int pipe); +extern struct drm_connector *psb_intel_sdvo_find(struct drm_device *dev, +					     int sdvoB); +extern int psb_intel_sdvo_supports_hotplug(struct drm_connector *connector); +extern void psb_intel_sdvo_set_hotplug(struct drm_connector *connector, +				   int enable); +extern int intelfb_probe(struct drm_device *dev); +extern int intelfb_remove(struct drm_device *dev, +			  struct drm_framebuffer *fb); +extern bool psb_intel_lvds_mode_fixup(struct drm_encoder *encoder, +				      const struct drm_display_mode *mode, +				      struct drm_display_mode *adjusted_mode); +extern int psb_intel_lvds_mode_valid(struct drm_connector *connector, +				     struct drm_display_mode *mode); +extern int psb_intel_lvds_set_property(struct drm_connector *connector, +					struct drm_property *property, +					uint64_t value); +extern void psb_intel_lvds_destroy(struct drm_connector *connector); +extern const struct drm_encoder_funcs psb_intel_lvds_enc_funcs; + +/* intel_gmbus.c */ +extern void gma_intel_i2c_reset(struct drm_device *dev); +extern int gma_intel_setup_gmbus(struct drm_device *dev); +extern void gma_intel_gmbus_set_speed(struct i2c_adapter *adapter, int speed); +extern void gma_intel_gmbus_force_bit(struct i2c_adapter *adapter, bool force_bit); +extern void gma_intel_teardown_gmbus(struct drm_device *dev); + +/* DP support */ +extern void cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev, int output_reg); +extern void cdv_intel_dp_set_m_n(struct drm_crtc *crtc, +					struct drm_display_mode *mode, +					struct drm_display_mode *adjusted_mode); + +extern void psb_intel_attach_force_audio_property(struct drm_connector *connector); +extern void psb_intel_attach_broadcast_rgb_property(struct drm_connector *connector); + +extern int cdv_sb_read(struct drm_device *dev, u32 reg, u32 *val); +extern int cdv_sb_write(struct drm_device *dev, u32 reg, u32 val); +extern void cdv_sb_reset(struct drm_device *dev); + +extern void cdv_intel_attach_force_audio_property(struct drm_connector *connector); +extern void cdv_intel_attach_broadcast_rgb_property(struct drm_connector *connector); + +#endif				/* __INTEL_DRV_H__ */ diff --git a/drivers/gpu/drm/gma500/psb_intel_lvds.c b/drivers/gpu/drm/gma500/psb_intel_lvds.c new file mode 100644 index 00000000000..d7778d0472c --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_intel_lvds.c @@ -0,0 +1,850 @@ +/* + * Copyright © 2006-2007 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + *	Dave Airlie <airlied@linux.ie> + *	Jesse Barnes <jesse.barnes@intel.com> + */ + +#include <linux/i2c.h> +#include <drm/drmP.h> + +#include "intel_bios.h" +#include "psb_drv.h" +#include "psb_intel_drv.h" +#include "psb_intel_reg.h" +#include "power.h" +#include <linux/pm_runtime.h> + +/* + * LVDS I2C backlight control macros + */ +#define BRIGHTNESS_MAX_LEVEL 100 +#define BRIGHTNESS_MASK 0xFF +#define BLC_I2C_TYPE	0x01 +#define BLC_PWM_TYPT	0x02 + +#define BLC_POLARITY_NORMAL 0 +#define BLC_POLARITY_INVERSE 1 + +#define PSB_BLC_MAX_PWM_REG_FREQ       (0xFFFE) +#define PSB_BLC_MIN_PWM_REG_FREQ	(0x2) +#define PSB_BLC_PWM_PRECISION_FACTOR	(10) +#define PSB_BACKLIGHT_PWM_CTL_SHIFT	(16) +#define PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR (0xFFFE) + +struct psb_intel_lvds_priv { +	/* +	 * Saved LVDO output states +	 */ +	uint32_t savePP_ON; +	uint32_t savePP_OFF; +	uint32_t saveLVDS; +	uint32_t savePP_CONTROL; +	uint32_t savePP_CYCLE; +	uint32_t savePFIT_CONTROL; +	uint32_t savePFIT_PGM_RATIOS; +	uint32_t saveBLC_PWM_CTL; + +	struct psb_intel_i2c_chan *i2c_bus; +	struct psb_intel_i2c_chan *ddc_bus; +}; + + +/* + * Returns the maximum level of the backlight duty cycle field. + */ +static u32 psb_intel_lvds_get_max_backlight(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 ret; + +	if (gma_power_begin(dev, false)) { +		ret = REG_READ(BLC_PWM_CTL); +		gma_power_end(dev); +	} else /* Powered off, use the saved value */ +		ret = dev_priv->regs.saveBLC_PWM_CTL; + +	/* Top 15bits hold the frequency mask */ +	ret = (ret &  BACKLIGHT_MODULATION_FREQ_MASK) >> +					BACKLIGHT_MODULATION_FREQ_SHIFT; + +        ret *= 2;	/* Return a 16bit range as needed for setting */ +        if (ret == 0) +                dev_err(dev->dev, "BL bug: Reg %08x save %08X\n", +                        REG_READ(BLC_PWM_CTL), dev_priv->regs.saveBLC_PWM_CTL); +	return ret; +} + +/* + * Set LVDS backlight level by I2C command + * + * FIXME: at some point we need to both track this for PM and also + * disable runtime pm on MRST if the brightness is nil (ie blanked) + */ +static int psb_lvds_i2c_set_brightness(struct drm_device *dev, +					unsigned int level) +{ +	struct drm_psb_private *dev_priv = +		(struct drm_psb_private *)dev->dev_private; + +	struct psb_intel_i2c_chan *lvds_i2c_bus = dev_priv->lvds_i2c_bus; +	u8 out_buf[2]; +	unsigned int blc_i2c_brightness; + +	struct i2c_msg msgs[] = { +		{ +			.addr = lvds_i2c_bus->slave_addr, +			.flags = 0, +			.len = 2, +			.buf = out_buf, +		} +	}; + +	blc_i2c_brightness = BRIGHTNESS_MASK & ((unsigned int)level * +			     BRIGHTNESS_MASK / +			     BRIGHTNESS_MAX_LEVEL); + +	if (dev_priv->lvds_bl->pol == BLC_POLARITY_INVERSE) +		blc_i2c_brightness = BRIGHTNESS_MASK - blc_i2c_brightness; + +	out_buf[0] = dev_priv->lvds_bl->brightnesscmd; +	out_buf[1] = (u8)blc_i2c_brightness; + +	if (i2c_transfer(&lvds_i2c_bus->adapter, msgs, 1) == 1) { +		dev_dbg(dev->dev, "I2C set brightness.(command, value) (%d, %d)\n", +			dev_priv->lvds_bl->brightnesscmd, +			blc_i2c_brightness); +		return 0; +	} + +	dev_err(dev->dev, "I2C transfer error\n"); +	return -1; +} + + +static int psb_lvds_pwm_set_brightness(struct drm_device *dev, int level) +{ +	struct drm_psb_private *dev_priv = +			(struct drm_psb_private *)dev->dev_private; + +	u32 max_pwm_blc; +	u32 blc_pwm_duty_cycle; + +	max_pwm_blc = psb_intel_lvds_get_max_backlight(dev); + +	/*BLC_PWM_CTL Should be initiated while backlight device init*/ +	BUG_ON(max_pwm_blc == 0); + +	blc_pwm_duty_cycle = level * max_pwm_blc / BRIGHTNESS_MAX_LEVEL; + +	if (dev_priv->lvds_bl->pol == BLC_POLARITY_INVERSE) +		blc_pwm_duty_cycle = max_pwm_blc - blc_pwm_duty_cycle; + +	blc_pwm_duty_cycle &= PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR; +	REG_WRITE(BLC_PWM_CTL, +		  (max_pwm_blc << PSB_BACKLIGHT_PWM_CTL_SHIFT) | +		  (blc_pwm_duty_cycle)); + +        dev_info(dev->dev, "Backlight lvds set brightness %08x\n", +		  (max_pwm_blc << PSB_BACKLIGHT_PWM_CTL_SHIFT) | +		  (blc_pwm_duty_cycle)); + +	return 0; +} + +/* + * Set LVDS backlight level either by I2C or PWM + */ +void psb_intel_lvds_set_brightness(struct drm_device *dev, int level) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; + +	dev_dbg(dev->dev, "backlight level is %d\n", level); + +	if (!dev_priv->lvds_bl) { +		dev_err(dev->dev, "NO LVDS backlight info\n"); +		return; +	} + +	if (dev_priv->lvds_bl->type == BLC_I2C_TYPE) +		psb_lvds_i2c_set_brightness(dev, level); +	else +		psb_lvds_pwm_set_brightness(dev, level); +} + +/* + * Sets the backlight level. + * + * level: backlight level, from 0 to psb_intel_lvds_get_max_backlight(). + */ +static void psb_intel_lvds_set_backlight(struct drm_device *dev, int level) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 blc_pwm_ctl; + +	if (gma_power_begin(dev, false)) { +		blc_pwm_ctl = REG_READ(BLC_PWM_CTL); +		blc_pwm_ctl &= ~BACKLIGHT_DUTY_CYCLE_MASK; +		REG_WRITE(BLC_PWM_CTL, +				(blc_pwm_ctl | +				(level << BACKLIGHT_DUTY_CYCLE_SHIFT))); +		dev_priv->regs.saveBLC_PWM_CTL = (blc_pwm_ctl | +					(level << BACKLIGHT_DUTY_CYCLE_SHIFT)); +		gma_power_end(dev); +	} else { +		blc_pwm_ctl = dev_priv->regs.saveBLC_PWM_CTL & +				~BACKLIGHT_DUTY_CYCLE_MASK; +		dev_priv->regs.saveBLC_PWM_CTL = (blc_pwm_ctl | +					(level << BACKLIGHT_DUTY_CYCLE_SHIFT)); +	} +} + +/* + * Sets the power state for the panel. + */ +static void psb_intel_lvds_set_power(struct drm_device *dev, bool on) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; +	u32 pp_status; + +	if (!gma_power_begin(dev, true)) { +	        dev_err(dev->dev, "set power, chip off!\n"); +		return; +        } +         +	if (on) { +		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) | +			  POWER_TARGET_ON); +		do { +			pp_status = REG_READ(PP_STATUS); +		} while ((pp_status & PP_ON) == 0); + +		psb_intel_lvds_set_backlight(dev, +					     mode_dev->backlight_duty_cycle); +	} else { +		psb_intel_lvds_set_backlight(dev, 0); + +		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) & +			  ~POWER_TARGET_ON); +		do { +			pp_status = REG_READ(PP_STATUS); +		} while (pp_status & PP_ON); +	} + +	gma_power_end(dev); +} + +static void psb_intel_lvds_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +	struct drm_device *dev = encoder->dev; + +	if (mode == DRM_MODE_DPMS_ON) +		psb_intel_lvds_set_power(dev, true); +	else +		psb_intel_lvds_set_power(dev, false); + +	/* XXX: We never power down the LVDS pairs. */ +} + +static void psb_intel_lvds_save(struct drm_connector *connector) +{ +	struct drm_device *dev = connector->dev; +	struct drm_psb_private *dev_priv = +		(struct drm_psb_private *)dev->dev_private; +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct psb_intel_lvds_priv *lvds_priv = +		(struct psb_intel_lvds_priv *)gma_encoder->dev_priv; + +	lvds_priv->savePP_ON = REG_READ(LVDSPP_ON); +	lvds_priv->savePP_OFF = REG_READ(LVDSPP_OFF); +	lvds_priv->saveLVDS = REG_READ(LVDS); +	lvds_priv->savePP_CONTROL = REG_READ(PP_CONTROL); +	lvds_priv->savePP_CYCLE = REG_READ(PP_CYCLE); +	/*lvds_priv->savePP_DIVISOR = REG_READ(PP_DIVISOR);*/ +	lvds_priv->saveBLC_PWM_CTL = REG_READ(BLC_PWM_CTL); +	lvds_priv->savePFIT_CONTROL = REG_READ(PFIT_CONTROL); +	lvds_priv->savePFIT_PGM_RATIOS = REG_READ(PFIT_PGM_RATIOS); + +	/*TODO: move backlight_duty_cycle to psb_intel_lvds_priv*/ +	dev_priv->backlight_duty_cycle = (dev_priv->regs.saveBLC_PWM_CTL & +						BACKLIGHT_DUTY_CYCLE_MASK); + +	/* +	 * If the light is off at server startup, +	 * just make it full brightness +	 */ +	if (dev_priv->backlight_duty_cycle == 0) +		dev_priv->backlight_duty_cycle = +		psb_intel_lvds_get_max_backlight(dev); + +	dev_dbg(dev->dev, "(0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x)\n", +			lvds_priv->savePP_ON, +			lvds_priv->savePP_OFF, +			lvds_priv->saveLVDS, +			lvds_priv->savePP_CONTROL, +			lvds_priv->savePP_CYCLE, +			lvds_priv->saveBLC_PWM_CTL); +} + +static void psb_intel_lvds_restore(struct drm_connector *connector) +{ +	struct drm_device *dev = connector->dev; +	u32 pp_status; +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct psb_intel_lvds_priv *lvds_priv = +		(struct psb_intel_lvds_priv *)gma_encoder->dev_priv; + +	dev_dbg(dev->dev, "(0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x)\n", +			lvds_priv->savePP_ON, +			lvds_priv->savePP_OFF, +			lvds_priv->saveLVDS, +			lvds_priv->savePP_CONTROL, +			lvds_priv->savePP_CYCLE, +			lvds_priv->saveBLC_PWM_CTL); + +	REG_WRITE(BLC_PWM_CTL, lvds_priv->saveBLC_PWM_CTL); +	REG_WRITE(PFIT_CONTROL, lvds_priv->savePFIT_CONTROL); +	REG_WRITE(PFIT_PGM_RATIOS, lvds_priv->savePFIT_PGM_RATIOS); +	REG_WRITE(LVDSPP_ON, lvds_priv->savePP_ON); +	REG_WRITE(LVDSPP_OFF, lvds_priv->savePP_OFF); +	/*REG_WRITE(PP_DIVISOR, lvds_priv->savePP_DIVISOR);*/ +	REG_WRITE(PP_CYCLE, lvds_priv->savePP_CYCLE); +	REG_WRITE(PP_CONTROL, lvds_priv->savePP_CONTROL); +	REG_WRITE(LVDS, lvds_priv->saveLVDS); + +	if (lvds_priv->savePP_CONTROL & POWER_TARGET_ON) { +		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) | +			POWER_TARGET_ON); +		do { +			pp_status = REG_READ(PP_STATUS); +		} while ((pp_status & PP_ON) == 0); +	} else { +		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) & +			~POWER_TARGET_ON); +		do { +			pp_status = REG_READ(PP_STATUS); +		} while (pp_status & PP_ON); +	} +} + +int psb_intel_lvds_mode_valid(struct drm_connector *connector, +				 struct drm_display_mode *mode) +{ +	struct drm_psb_private *dev_priv = connector->dev->dev_private; +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct drm_display_mode *fixed_mode = +					dev_priv->mode_dev.panel_fixed_mode; + +	if (gma_encoder->type == INTEL_OUTPUT_MIPI2) +		fixed_mode = dev_priv->mode_dev.panel_fixed_mode2; + +	/* just in case */ +	if (mode->flags & DRM_MODE_FLAG_DBLSCAN) +		return MODE_NO_DBLESCAN; + +	/* just in case */ +	if (mode->flags & DRM_MODE_FLAG_INTERLACE) +		return MODE_NO_INTERLACE; + +	if (fixed_mode) { +		if (mode->hdisplay > fixed_mode->hdisplay) +			return MODE_PANEL; +		if (mode->vdisplay > fixed_mode->vdisplay) +			return MODE_PANEL; +	} +	return MODE_OK; +} + +bool psb_intel_lvds_mode_fixup(struct drm_encoder *encoder, +				  const struct drm_display_mode *mode, +				  struct drm_display_mode *adjusted_mode) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; +	struct gma_crtc *gma_crtc = to_gma_crtc(encoder->crtc); +	struct drm_encoder *tmp_encoder; +	struct drm_display_mode *panel_fixed_mode = mode_dev->panel_fixed_mode; +	struct gma_encoder *gma_encoder = to_gma_encoder(encoder); + +	if (gma_encoder->type == INTEL_OUTPUT_MIPI2) +		panel_fixed_mode = mode_dev->panel_fixed_mode2; + +	/* PSB requires the LVDS is on pipe B, MRST has only one pipe anyway */ +	if (!IS_MRST(dev) && gma_crtc->pipe == 0) { +		printk(KERN_ERR "Can't support LVDS on pipe A\n"); +		return false; +	} +	if (IS_MRST(dev) && gma_crtc->pipe != 0) { +		printk(KERN_ERR "Must use PIPE A\n"); +		return false; +	} +	/* Should never happen!! */ +	list_for_each_entry(tmp_encoder, &dev->mode_config.encoder_list, +			    head) { +		if (tmp_encoder != encoder +		    && tmp_encoder->crtc == encoder->crtc) { +			printk(KERN_ERR "Can't enable LVDS and another " +			       "encoder on the same pipe\n"); +			return false; +		} +	} + +	/* +	 * If we have timings from the BIOS for the panel, put them in +	 * to the adjusted mode.  The CRTC will be set up for this mode, +	 * with the panel scaling set up to source from the H/VDisplay +	 * of the original mode. +	 */ +	if (panel_fixed_mode != NULL) { +		adjusted_mode->hdisplay = panel_fixed_mode->hdisplay; +		adjusted_mode->hsync_start = panel_fixed_mode->hsync_start; +		adjusted_mode->hsync_end = panel_fixed_mode->hsync_end; +		adjusted_mode->htotal = panel_fixed_mode->htotal; +		adjusted_mode->vdisplay = panel_fixed_mode->vdisplay; +		adjusted_mode->vsync_start = panel_fixed_mode->vsync_start; +		adjusted_mode->vsync_end = panel_fixed_mode->vsync_end; +		adjusted_mode->vtotal = panel_fixed_mode->vtotal; +		adjusted_mode->clock = panel_fixed_mode->clock; +		drm_mode_set_crtcinfo(adjusted_mode, +				      CRTC_INTERLACE_HALVE_V); +	} + +	/* +	 * XXX: It would be nice to support lower refresh rates on the +	 * panels to reduce power consumption, and perhaps match the +	 * user's requested refresh rate. +	 */ + +	return true; +} + +static void psb_intel_lvds_prepare(struct drm_encoder *encoder) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; + +	if (!gma_power_begin(dev, true)) +		return; + +	mode_dev->saveBLC_PWM_CTL = REG_READ(BLC_PWM_CTL); +	mode_dev->backlight_duty_cycle = (mode_dev->saveBLC_PWM_CTL & +					  BACKLIGHT_DUTY_CYCLE_MASK); + +	psb_intel_lvds_set_power(dev, false); + +	gma_power_end(dev); +} + +static void psb_intel_lvds_commit(struct drm_encoder *encoder) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; + +	if (mode_dev->backlight_duty_cycle == 0) +		mode_dev->backlight_duty_cycle = +		    psb_intel_lvds_get_max_backlight(dev); + +	psb_intel_lvds_set_power(dev, true); +} + +static void psb_intel_lvds_mode_set(struct drm_encoder *encoder, +				struct drm_display_mode *mode, +				struct drm_display_mode *adjusted_mode) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 pfit_control; + +	/* +	 * The LVDS pin pair will already have been turned on in the +	 * psb_intel_crtc_mode_set since it has a large impact on the DPLL +	 * settings. +	 */ + +	/* +	 * Enable automatic panel scaling so that non-native modes fill the +	 * screen.  Should be enabled before the pipe is enabled, according to +	 * register description and PRM. +	 */ +	if (mode->hdisplay != adjusted_mode->hdisplay || +	    mode->vdisplay != adjusted_mode->vdisplay) +		pfit_control = (PFIT_ENABLE | VERT_AUTO_SCALE | +				HORIZ_AUTO_SCALE | VERT_INTERP_BILINEAR | +				HORIZ_INTERP_BILINEAR); +	else +		pfit_control = 0; + +	if (dev_priv->lvds_dither) +		pfit_control |= PANEL_8TO6_DITHER_ENABLE; + +	REG_WRITE(PFIT_CONTROL, pfit_control); +} + +/* + * Detect the LVDS connection. + * + * This always returns CONNECTOR_STATUS_CONNECTED. + * This connector should only have + * been set up if the LVDS was actually connected anyway. + */ +static enum drm_connector_status psb_intel_lvds_detect(struct drm_connector +						   *connector, bool force) +{ +	return connector_status_connected; +} + +/* + * Return the list of DDC modes if available, or the BIOS fixed mode otherwise. + */ +static int psb_intel_lvds_get_modes(struct drm_connector *connector) +{ +	struct drm_device *dev = connector->dev; +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev; +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct psb_intel_lvds_priv *lvds_priv = gma_encoder->dev_priv; +	int ret = 0; + +	if (!IS_MRST(dev)) +		ret = psb_intel_ddc_get_modes(connector, &lvds_priv->i2c_bus->adapter); + +	if (ret) +		return ret; + +	/* Didn't get an EDID, so +	 * Set wide sync ranges so we get all modes +	 * handed to valid_mode for checking +	 */ +	connector->display_info.min_vfreq = 0; +	connector->display_info.max_vfreq = 200; +	connector->display_info.min_hfreq = 0; +	connector->display_info.max_hfreq = 200; + +	if (mode_dev->panel_fixed_mode != NULL) { +		struct drm_display_mode *mode = +		    drm_mode_duplicate(dev, mode_dev->panel_fixed_mode); +		drm_mode_probed_add(connector, mode); +		return 1; +	} + +	return 0; +} + +/** + * psb_intel_lvds_destroy - unregister and free LVDS structures + * @connector: connector to free + * + * Unregister the DDC bus for this connector then free the driver private + * structure. + */ +void psb_intel_lvds_destroy(struct drm_connector *connector) +{ +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct psb_intel_lvds_priv *lvds_priv = gma_encoder->dev_priv; + +	if (lvds_priv->ddc_bus) +		psb_intel_i2c_destroy(lvds_priv->ddc_bus); +	drm_sysfs_connector_remove(connector); +	drm_connector_cleanup(connector); +	kfree(connector); +} + +int psb_intel_lvds_set_property(struct drm_connector *connector, +				       struct drm_property *property, +				       uint64_t value) +{ +	struct drm_encoder *encoder = connector->encoder; + +	if (!encoder) +		return -1; + +	if (!strcmp(property->name, "scaling mode")) { +		struct gma_crtc *crtc = to_gma_crtc(encoder->crtc); +		uint64_t curval; + +		if (!crtc) +			goto set_prop_error; + +		switch (value) { +		case DRM_MODE_SCALE_FULLSCREEN: +			break; +		case DRM_MODE_SCALE_NO_SCALE: +			break; +		case DRM_MODE_SCALE_ASPECT: +			break; +		default: +			goto set_prop_error; +		} + +		if (drm_object_property_get_value(&connector->base, +						     property, +						     &curval)) +			goto set_prop_error; + +		if (curval == value) +			goto set_prop_done; + +		if (drm_object_property_set_value(&connector->base, +							property, +							value)) +			goto set_prop_error; + +		if (crtc->saved_mode.hdisplay != 0 && +		    crtc->saved_mode.vdisplay != 0) { +			if (!drm_crtc_helper_set_mode(encoder->crtc, +						      &crtc->saved_mode, +						      encoder->crtc->x, +						      encoder->crtc->y, +						      encoder->crtc->primary->fb)) +				goto set_prop_error; +		} +	} else if (!strcmp(property->name, "backlight")) { +		if (drm_object_property_set_value(&connector->base, +							property, +							value)) +			goto set_prop_error; +		else +                        gma_backlight_set(encoder->dev, value); +	} else if (!strcmp(property->name, "DPMS")) { +		struct drm_encoder_helper_funcs *hfuncs +						= encoder->helper_private; +		hfuncs->dpms(encoder, value); +	} + +set_prop_done: +	return 0; +set_prop_error: +	return -1; +} + +static const struct drm_encoder_helper_funcs psb_intel_lvds_helper_funcs = { +	.dpms = psb_intel_lvds_encoder_dpms, +	.mode_fixup = psb_intel_lvds_mode_fixup, +	.prepare = psb_intel_lvds_prepare, +	.mode_set = psb_intel_lvds_mode_set, +	.commit = psb_intel_lvds_commit, +}; + +const struct drm_connector_helper_funcs +				psb_intel_lvds_connector_helper_funcs = { +	.get_modes = psb_intel_lvds_get_modes, +	.mode_valid = psb_intel_lvds_mode_valid, +	.best_encoder = gma_best_encoder, +}; + +const struct drm_connector_funcs psb_intel_lvds_connector_funcs = { +	.dpms = drm_helper_connector_dpms, +	.save = psb_intel_lvds_save, +	.restore = psb_intel_lvds_restore, +	.detect = psb_intel_lvds_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.set_property = psb_intel_lvds_set_property, +	.destroy = psb_intel_lvds_destroy, +}; + + +static void psb_intel_lvds_enc_destroy(struct drm_encoder *encoder) +{ +	drm_encoder_cleanup(encoder); +} + +const struct drm_encoder_funcs psb_intel_lvds_enc_funcs = { +	.destroy = psb_intel_lvds_enc_destroy, +}; + + + +/** + * psb_intel_lvds_init - setup LVDS connectors on this device + * @dev: drm device + * + * Create the connector, register the LVDS DDC bus, and try to figure out what + * modes we can display on the LVDS panel (if present). + */ +void psb_intel_lvds_init(struct drm_device *dev, +			 struct psb_intel_mode_device *mode_dev) +{ +	struct gma_encoder *gma_encoder; +	struct gma_connector *gma_connector; +	struct psb_intel_lvds_priv *lvds_priv; +	struct drm_connector *connector; +	struct drm_encoder *encoder; +	struct drm_display_mode *scan;	/* *modes, *bios_mode; */ +	struct drm_crtc *crtc; +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 lvds; +	int pipe; + +	gma_encoder = kzalloc(sizeof(struct gma_encoder), GFP_KERNEL); +	if (!gma_encoder) { +		dev_err(dev->dev, "gma_encoder allocation error\n"); +		return; +	} + +	gma_connector = kzalloc(sizeof(struct gma_connector), GFP_KERNEL); +	if (!gma_connector) { +		dev_err(dev->dev, "gma_connector allocation error\n"); +		goto failed_encoder; +	} + +	lvds_priv = kzalloc(sizeof(struct psb_intel_lvds_priv), GFP_KERNEL); +	if (!lvds_priv) { +		dev_err(dev->dev, "LVDS private allocation error\n"); +		goto failed_connector; +	} + +	gma_encoder->dev_priv = lvds_priv; + +	connector = &gma_connector->base; +	encoder = &gma_encoder->base; +	drm_connector_init(dev, connector, +			   &psb_intel_lvds_connector_funcs, +			   DRM_MODE_CONNECTOR_LVDS); + +	drm_encoder_init(dev, encoder, +			 &psb_intel_lvds_enc_funcs, +			 DRM_MODE_ENCODER_LVDS); + +	gma_connector_attach_encoder(gma_connector, gma_encoder); +	gma_encoder->type = INTEL_OUTPUT_LVDS; + +	drm_encoder_helper_add(encoder, &psb_intel_lvds_helper_funcs); +	drm_connector_helper_add(connector, +				 &psb_intel_lvds_connector_helper_funcs); +	connector->display_info.subpixel_order = SubPixelHorizontalRGB; +	connector->interlace_allowed = false; +	connector->doublescan_allowed = false; + +	/*Attach connector properties*/ +	drm_object_attach_property(&connector->base, +				      dev->mode_config.scaling_mode_property, +				      DRM_MODE_SCALE_FULLSCREEN); +	drm_object_attach_property(&connector->base, +				      dev_priv->backlight_property, +				      BRIGHTNESS_MAX_LEVEL); + +	/* +	 * Set up I2C bus +	 * FIXME: distroy i2c_bus when exit +	 */ +	lvds_priv->i2c_bus = psb_intel_i2c_create(dev, GPIOB, "LVDSBLC_B"); +	if (!lvds_priv->i2c_bus) { +		dev_printk(KERN_ERR, +			&dev->pdev->dev, "I2C bus registration failed.\n"); +		goto failed_blc_i2c; +	} +	lvds_priv->i2c_bus->slave_addr = 0x2C; +	dev_priv->lvds_i2c_bus =  lvds_priv->i2c_bus; + +	/* +	 * LVDS discovery: +	 * 1) check for EDID on DDC +	 * 2) check for VBT data +	 * 3) check to see if LVDS is already on +	 *    if none of the above, no panel +	 * 4) make sure lid is open +	 *    if closed, act like it's not there for now +	 */ + +	/* Set up the DDC bus. */ +	lvds_priv->ddc_bus = psb_intel_i2c_create(dev, GPIOC, "LVDSDDC_C"); +	if (!lvds_priv->ddc_bus) { +		dev_printk(KERN_ERR, &dev->pdev->dev, +			   "DDC bus registration " "failed.\n"); +		goto failed_ddc; +	} + +	/* +	 * Attempt to get the fixed panel mode from DDC.  Assume that the +	 * preferred mode is the right one. +	 */ +	mutex_lock(&dev->mode_config.mutex); +	psb_intel_ddc_get_modes(connector, &lvds_priv->ddc_bus->adapter); +	list_for_each_entry(scan, &connector->probed_modes, head) { +		if (scan->type & DRM_MODE_TYPE_PREFERRED) { +			mode_dev->panel_fixed_mode = +			    drm_mode_duplicate(dev, scan); +			goto out;	/* FIXME: check for quirks */ +		} +	} + +	/* Failed to get EDID, what about VBT? do we need this? */ +	if (mode_dev->vbt_mode) +		mode_dev->panel_fixed_mode = +		    drm_mode_duplicate(dev, mode_dev->vbt_mode); + +	if (!mode_dev->panel_fixed_mode) +		if (dev_priv->lfp_lvds_vbt_mode) +			mode_dev->panel_fixed_mode = +				drm_mode_duplicate(dev, +					dev_priv->lfp_lvds_vbt_mode); + +	/* +	 * If we didn't get EDID, try checking if the panel is already turned +	 * on.	If so, assume that whatever is currently programmed is the +	 * correct mode. +	 */ +	lvds = REG_READ(LVDS); +	pipe = (lvds & LVDS_PIPEB_SELECT) ? 1 : 0; +	crtc = psb_intel_get_crtc_from_pipe(dev, pipe); + +	if (crtc && (lvds & LVDS_PORT_EN)) { +		mode_dev->panel_fixed_mode = +		    psb_intel_crtc_mode_get(dev, crtc); +		if (mode_dev->panel_fixed_mode) { +			mode_dev->panel_fixed_mode->type |= +			    DRM_MODE_TYPE_PREFERRED; +			goto out;	/* FIXME: check for quirks */ +		} +	} + +	/* If we still don't have a mode after all that, give up. */ +	if (!mode_dev->panel_fixed_mode) { +		dev_err(dev->dev, "Found no modes on the lvds, ignoring the LVDS\n"); +		goto failed_find; +	} + +	/* +	 * Blacklist machines with BIOSes that list an LVDS panel without +	 * actually having one. +	 */ +out: +	mutex_unlock(&dev->mode_config.mutex); +	drm_sysfs_connector_add(connector); +	return; + +failed_find: +	mutex_unlock(&dev->mode_config.mutex); +	if (lvds_priv->ddc_bus) +		psb_intel_i2c_destroy(lvds_priv->ddc_bus); +failed_ddc: +	if (lvds_priv->i2c_bus) +		psb_intel_i2c_destroy(lvds_priv->i2c_bus); +failed_blc_i2c: +	drm_encoder_cleanup(encoder); +	drm_connector_cleanup(connector); +failed_connector: +	kfree(gma_connector); +failed_encoder: +	kfree(gma_encoder); +} + diff --git a/drivers/gpu/drm/gma500/psb_intel_modes.c b/drivers/gpu/drm/gma500/psb_intel_modes.c new file mode 100644 index 00000000000..4fca0d6feeb --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_intel_modes.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2007 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authers: Jesse Barnes <jesse.barnes@intel.com> + */ + +#include <linux/i2c.h> +#include <linux/fb.h> +#include <drm/drmP.h> +#include "psb_intel_drv.h" + +/** + * psb_intel_ddc_probe + * + */ +bool psb_intel_ddc_probe(struct i2c_adapter *adapter) +{ +	u8 out_buf[] = { 0x0, 0x0 }; +	u8 buf[2]; +	int ret; +	struct i2c_msg msgs[] = { +		{ +		 .addr = 0x50, +		 .flags = 0, +		 .len = 1, +		 .buf = out_buf, +		 }, +		{ +		 .addr = 0x50, +		 .flags = I2C_M_RD, +		 .len = 1, +		 .buf = buf, +		 } +	}; + +	ret = i2c_transfer(adapter, msgs, 2); +	if (ret == 2) +		return true; + +	return false; +} + +/** + * psb_intel_ddc_get_modes - get modelist from monitor + * @connector: DRM connector device to use + * + * Fetch the EDID information from @connector using the DDC bus. + */ +int psb_intel_ddc_get_modes(struct drm_connector *connector, +			    struct i2c_adapter *adapter) +{ +	struct edid *edid; +	int ret = 0; + +	edid = drm_get_edid(connector, adapter); +	if (edid) { +		drm_mode_connector_update_edid_property(connector, edid); +		ret = drm_add_edid_modes(connector, edid); +		kfree(edid); +	} +	return ret; +} diff --git a/drivers/gpu/drm/gma500/psb_intel_reg.h b/drivers/gpu/drm/gma500/psb_intel_reg.h new file mode 100644 index 00000000000..0be30e4d146 --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_intel_reg.h @@ -0,0 +1,1545 @@ +/* + * Copyright (c) 2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef __PSB_INTEL_REG_H__ +#define __PSB_INTEL_REG_H__ + +/* + * GPIO regs + */ +#define GPIOA			0x5010 +#define GPIOB			0x5014 +#define GPIOC			0x5018 +#define GPIOD			0x501c +#define GPIOE			0x5020 +#define GPIOF			0x5024 +#define GPIOG			0x5028 +#define GPIOH			0x502c +# define GPIO_CLOCK_DIR_MASK		(1 << 0) +# define GPIO_CLOCK_DIR_IN		(0 << 1) +# define GPIO_CLOCK_DIR_OUT		(1 << 1) +# define GPIO_CLOCK_VAL_MASK		(1 << 2) +# define GPIO_CLOCK_VAL_OUT		(1 << 3) +# define GPIO_CLOCK_VAL_IN		(1 << 4) +# define GPIO_CLOCK_PULLUP_DISABLE	(1 << 5) +# define GPIO_DATA_DIR_MASK		(1 << 8) +# define GPIO_DATA_DIR_IN		(0 << 9) +# define GPIO_DATA_DIR_OUT		(1 << 9) +# define GPIO_DATA_VAL_MASK		(1 << 10) +# define GPIO_DATA_VAL_OUT		(1 << 11) +# define GPIO_DATA_VAL_IN		(1 << 12) +# define GPIO_DATA_PULLUP_DISABLE	(1 << 13) + +#define GMBUS0			0x5100 /* clock/port select */ +#define   GMBUS_RATE_100KHZ	(0<<8) +#define   GMBUS_RATE_50KHZ	(1<<8) +#define   GMBUS_RATE_400KHZ	(2<<8) /* reserved on Pineview */ +#define   GMBUS_RATE_1MHZ	(3<<8) /* reserved on Pineview */ +#define   GMBUS_HOLD_EXT	(1<<7) /* 300ns hold time, rsvd on Pineview */ +#define   GMBUS_PORT_DISABLED	0 +#define   GMBUS_PORT_SSC	1 +#define   GMBUS_PORT_VGADDC	2 +#define   GMBUS_PORT_PANEL	3 +#define   GMBUS_PORT_DPC	4 /* HDMIC */ +#define   GMBUS_PORT_DPB	5 /* SDVO, HDMIB */ +				  /* 6 reserved */ +#define   GMBUS_PORT_DPD	7 /* HDMID */ +#define   GMBUS_NUM_PORTS       8 +#define GMBUS1			0x5104 /* command/status */ +#define   GMBUS_SW_CLR_INT	(1<<31) +#define   GMBUS_SW_RDY		(1<<30) +#define   GMBUS_ENT		(1<<29) /* enable timeout */ +#define   GMBUS_CYCLE_NONE	(0<<25) +#define   GMBUS_CYCLE_WAIT	(1<<25) +#define   GMBUS_CYCLE_INDEX	(2<<25) +#define   GMBUS_CYCLE_STOP	(4<<25) +#define   GMBUS_BYTE_COUNT_SHIFT 16 +#define   GMBUS_SLAVE_INDEX_SHIFT 8 +#define   GMBUS_SLAVE_ADDR_SHIFT 1 +#define   GMBUS_SLAVE_READ	(1<<0) +#define   GMBUS_SLAVE_WRITE	(0<<0) +#define GMBUS2			0x5108 /* status */ +#define   GMBUS_INUSE		(1<<15) +#define   GMBUS_HW_WAIT_PHASE	(1<<14) +#define   GMBUS_STALL_TIMEOUT	(1<<13) +#define   GMBUS_INT		(1<<12) +#define   GMBUS_HW_RDY		(1<<11) +#define   GMBUS_SATOER		(1<<10) +#define   GMBUS_ACTIVE		(1<<9) +#define GMBUS3			0x510c /* data buffer bytes 3-0 */ +#define GMBUS4			0x5110 /* interrupt mask (Pineview+) */ +#define   GMBUS_SLAVE_TIMEOUT_EN (1<<4) +#define   GMBUS_NAK_EN		(1<<3) +#define   GMBUS_IDLE_EN		(1<<2) +#define   GMBUS_HW_WAIT_EN	(1<<1) +#define   GMBUS_HW_RDY_EN	(1<<0) +#define GMBUS5			0x5120 /* byte index */ +#define   GMBUS_2BYTE_INDEX_EN	(1<<31) + +#define BLC_PWM_CTL		0x61254 +#define BLC_PWM_CTL2		0x61250 +#define  PWM_ENABLE		(1 << 31) +#define  PWM_LEGACY_MODE	(1 << 30) +#define  PWM_PIPE_B		(1 << 29) +#define BLC_PWM_CTL_C		0x62254 +#define BLC_PWM_CTL2_C		0x62250 +#define BACKLIGHT_MODULATION_FREQ_SHIFT		(17) +/* + * This is the most significant 15 bits of the number of backlight cycles in a + * complete cycle of the modulated backlight control. + * + * The actual value is this field multiplied by two. + */ +#define BACKLIGHT_MODULATION_FREQ_MASK	(0x7fff << 17) +#define BLM_LEGACY_MODE			(1 << 16) +/* + * This is the number of cycles out of the backlight modulation cycle for which + * the backlight is on. + * + * This field must be no greater than the number of cycles in the complete + * backlight modulation cycle. + */ +#define BACKLIGHT_DUTY_CYCLE_SHIFT	(0) +#define BACKLIGHT_DUTY_CYCLE_MASK	(0xffff) + +#define I915_GCFGC			0xf0 +#define I915_LOW_FREQUENCY_ENABLE	(1 << 7) +#define I915_DISPLAY_CLOCK_190_200_MHZ	(0 << 4) +#define I915_DISPLAY_CLOCK_333_MHZ	(4 << 4) +#define I915_DISPLAY_CLOCK_MASK		(7 << 4) + +#define I855_HPLLCC			0xc0 +#define I855_CLOCK_CONTROL_MASK		(3 << 0) +#define I855_CLOCK_133_200		(0 << 0) +#define I855_CLOCK_100_200		(1 << 0) +#define I855_CLOCK_100_133		(2 << 0) +#define I855_CLOCK_166_250		(3 << 0) + +/* I830 CRTC registers */ +#define HTOTAL_A		0x60000 +#define HBLANK_A		0x60004 +#define HSYNC_A			0x60008 +#define VTOTAL_A		0x6000c +#define VBLANK_A		0x60010 +#define VSYNC_A			0x60014 +#define PIPEASRC		0x6001c +#define BCLRPAT_A		0x60020 +#define VSYNCSHIFT_A		0x60028 + +#define HTOTAL_B		0x61000 +#define HBLANK_B		0x61004 +#define HSYNC_B			0x61008 +#define VTOTAL_B		0x6100c +#define VBLANK_B		0x61010 +#define VSYNC_B			0x61014 +#define PIPEBSRC		0x6101c +#define BCLRPAT_B		0x61020 +#define VSYNCSHIFT_B		0x61028 + +#define HTOTAL_C		0x62000 +#define HBLANK_C		0x62004 +#define HSYNC_C			0x62008 +#define VTOTAL_C		0x6200c +#define VBLANK_C		0x62010 +#define VSYNC_C			0x62014 +#define PIPECSRC		0x6201c +#define BCLRPAT_C		0x62020 +#define VSYNCSHIFT_C		0x62028 + +#define PP_STATUS		0x61200 +# define PP_ON				(1 << 31) +/* + * Indicates that all dependencies of the panel are on: + * + * - PLL enabled + * - pipe enabled + * - LVDS/DVOB/DVOC on + */ +#define PP_READY			(1 << 30) +#define PP_SEQUENCE_NONE		(0 << 28) +#define PP_SEQUENCE_ON			(1 << 28) +#define PP_SEQUENCE_OFF			(2 << 28) +#define PP_SEQUENCE_MASK		0x30000000 +#define	PP_CYCLE_DELAY_ACTIVE		(1 << 27) +#define	PP_SEQUENCE_STATE_ON_IDLE	(1 << 3) +#define	PP_SEQUENCE_STATE_MASK		0x0000000f + +#define PP_CONTROL		0x61204 +#define POWER_TARGET_ON			(1 << 0) +#define	PANEL_UNLOCK_REGS		(0xabcd << 16) +#define	PANEL_UNLOCK_MASK		(0xffff << 16) +#define	EDP_FORCE_VDD			(1 << 3) +#define	EDP_BLC_ENABLE			(1 << 2) +#define	PANEL_POWER_RESET		(1 << 1) +#define	PANEL_POWER_OFF			(0 << 0) +#define	PANEL_POWER_ON			(1 << 0) + +/* Poulsbo/Oaktrail */ +#define LVDSPP_ON		0x61208 +#define LVDSPP_OFF		0x6120c +#define PP_CYCLE		0x61210 + +/* Cedartrail */ +#define PP_ON_DELAYS		0x61208		/* Cedartrail */ +#define PANEL_PORT_SELECT_MASK 		(3 << 30) +#define PANEL_PORT_SELECT_LVDS 		(0 << 30) +#define PANEL_PORT_SELECT_EDP		(1 << 30) +#define PANEL_POWER_UP_DELAY_MASK	(0x1fff0000) +#define PANEL_POWER_UP_DELAY_SHIFT	16 +#define PANEL_LIGHT_ON_DELAY_MASK	(0x1fff) +#define PANEL_LIGHT_ON_DELAY_SHIFT	0 + +#define PP_OFF_DELAYS		0x6120c		/* Cedartrail */ +#define PANEL_POWER_DOWN_DELAY_MASK	(0x1fff0000) +#define PANEL_POWER_DOWN_DELAY_SHIFT	16 +#define PANEL_LIGHT_OFF_DELAY_MASK	(0x1fff) +#define PANEL_LIGHT_OFF_DELAY_SHIFT	0 + +#define PP_DIVISOR		0x61210		/* Cedartrail */ +#define  PP_REFERENCE_DIVIDER_MASK	(0xffffff00) +#define  PP_REFERENCE_DIVIDER_SHIFT	8 +#define  PANEL_POWER_CYCLE_DELAY_MASK	(0x1f) +#define  PANEL_POWER_CYCLE_DELAY_SHIFT	0 + +#define PFIT_CONTROL		0x61230 +#define PFIT_ENABLE			(1 << 31) +#define PFIT_PIPE_MASK			(3 << 29) +#define PFIT_PIPE_SHIFT			29 +#define PFIT_SCALING_MODE_PILLARBOX	(1 << 27) +#define PFIT_SCALING_MODE_LETTERBOX	(3 << 26) +#define VERT_INTERP_DISABLE		(0 << 10) +#define VERT_INTERP_BILINEAR		(1 << 10) +#define VERT_INTERP_MASK		(3 << 10) +#define VERT_AUTO_SCALE			(1 << 9) +#define HORIZ_INTERP_DISABLE		(0 << 6) +#define HORIZ_INTERP_BILINEAR		(1 << 6) +#define HORIZ_INTERP_MASK		(3 << 6) +#define HORIZ_AUTO_SCALE		(1 << 5) +#define PANEL_8TO6_DITHER_ENABLE	(1 << 3) + +#define PFIT_PGM_RATIOS		0x61234 +#define PFIT_VERT_SCALE_MASK			0xfff00000 +#define PFIT_HORIZ_SCALE_MASK			0x0000fff0 + +#define PFIT_AUTO_RATIOS	0x61238 + +#define DPLL_A			0x06014 +#define DPLL_B			0x06018 +#define DPLL_VCO_ENABLE			(1 << 31) +#define DPLL_DVO_HIGH_SPEED		(1 << 30) +#define DPLL_SYNCLOCK_ENABLE		(1 << 29) +#define DPLL_VGA_MODE_DIS		(1 << 28) +#define DPLLB_MODE_DAC_SERIAL		(1 << 26)	/* i915 */ +#define DPLLB_MODE_LVDS			(2 << 26)	/* i915 */ +#define DPLL_MODE_MASK			(3 << 26) +#define DPLL_DAC_SERIAL_P2_CLOCK_DIV_10	(0 << 24)	/* i915 */ +#define DPLL_DAC_SERIAL_P2_CLOCK_DIV_5	(1 << 24)	/* i915 */ +#define DPLLB_LVDS_P2_CLOCK_DIV_14	(0 << 24)	/* i915 */ +#define DPLLB_LVDS_P2_CLOCK_DIV_7	(1 << 24)	/* i915 */ +#define DPLL_P2_CLOCK_DIV_MASK		0x03000000	/* i915 */ +#define DPLL_FPA0h1_P1_POST_DIV_MASK	0x00ff0000	/* i915 */ +#define DPLL_LOCK			(1 << 15)	/* CDV */ + +/* + *  The i830 generation, in DAC/serial mode, defines p1 as two plus this + * bitfield, or just 2 if PLL_P1_DIVIDE_BY_TWO is set. + */ +# define DPLL_FPA01_P1_POST_DIV_MASK_I830	0x001f0000 +/* + * The i830 generation, in LVDS mode, defines P1 as the bit number set within + * this field (only one bit may be set). + */ +#define DPLL_FPA01_P1_POST_DIV_MASK_I830_LVDS	0x003f0000 +#define DPLL_FPA01_P1_POST_DIV_SHIFT	16 +#define PLL_P2_DIVIDE_BY_4		(1 << 23)	/* i830, required +							 * in DVO non-gang */ +# define PLL_P1_DIVIDE_BY_TWO		(1 << 21)	/* i830 */ +#define PLL_REF_INPUT_DREFCLK		(0 << 13) +#define PLL_REF_INPUT_TVCLKINA		(1 << 13)	/* i830 */ +#define PLL_REF_INPUT_TVCLKINBC		(2 << 13)	/* SDVO +								 * TVCLKIN */ +#define PLLB_REF_INPUT_SPREADSPECTRUMIN	(3 << 13) +#define PLL_REF_INPUT_MASK		(3 << 13) +#define PLL_LOAD_PULSE_PHASE_SHIFT	9 +/* + * Parallel to Serial Load Pulse phase selection. + * Selects the phase for the 10X DPLL clock for the PCIe + * digital display port. The range is 4 to 13; 10 or more + * is just a flip delay. The default is 6 + */ +#define PLL_LOAD_PULSE_PHASE_MASK	(0xf << PLL_LOAD_PULSE_PHASE_SHIFT) +#define DISPLAY_RATE_SELECT_FPA1	(1 << 8) + +/* + * SDVO multiplier for 945G/GM. Not used on 965. + * + * DPLL_MD_UDI_MULTIPLIER_MASK + */ +#define SDVO_MULTIPLIER_MASK		0x000000ff +#define SDVO_MULTIPLIER_SHIFT_HIRES	4 +#define SDVO_MULTIPLIER_SHIFT_VGA	0 + +/* + * PLL_MD + */ +/* Pipe A SDVO/UDI clock multiplier/divider register for G965. */ +#define DPLL_A_MD		0x0601c +/* Pipe B SDVO/UDI clock multiplier/divider register for G965. */ +#define DPLL_B_MD		0x06020 +/* + * UDI pixel divider, controlling how many pixels are stuffed into a packet. + * + * Value is pixels minus 1.  Must be set to 1 pixel for SDVO. + */ +#define DPLL_MD_UDI_DIVIDER_MASK	0x3f000000 +#define DPLL_MD_UDI_DIVIDER_SHIFT	24 +/* UDI pixel divider for VGA, same as DPLL_MD_UDI_DIVIDER_MASK. */ +#define DPLL_MD_VGA_UDI_DIVIDER_MASK	0x003f0000 +#define DPLL_MD_VGA_UDI_DIVIDER_SHIFT	16 +/* + * SDVO/UDI pixel multiplier. + * + * SDVO requires that the bus clock rate be between 1 and 2 Ghz, and the bus + * clock rate is 10 times the DPLL clock.  At low resolution/refresh rate + * modes, the bus rate would be below the limits, so SDVO allows for stuffing + * dummy bytes in the datastream at an increased clock rate, with both sides of + * the link knowing how many bytes are fill. + * + * So, for a mode with a dotclock of 65Mhz, we would want to double the clock + * rate to 130Mhz to get a bus rate of 1.30Ghz.  The DPLL clock rate would be + * set to 130Mhz, and the SDVO multiplier set to 2x in this register and + * through an SDVO command. + * + * This register field has values of multiplication factor minus 1, with + * a maximum multiplier of 5 for SDVO. + */ +#define DPLL_MD_UDI_MULTIPLIER_MASK	0x00003f00 +#define DPLL_MD_UDI_MULTIPLIER_SHIFT	8 +/* + * SDVO/UDI pixel multiplier for VGA, same as DPLL_MD_UDI_MULTIPLIER_MASK. + * This best be set to the default value (3) or the CRT won't work. No, + * I don't entirely understand what this does... + */ +#define DPLL_MD_VGA_UDI_MULTIPLIER_MASK	0x0000003f +#define DPLL_MD_VGA_UDI_MULTIPLIER_SHIFT 0 + +#define DPLL_TEST		0x606c +#define DPLLB_TEST_SDVO_DIV_1		(0 << 22) +#define DPLLB_TEST_SDVO_DIV_2		(1 << 22) +#define DPLLB_TEST_SDVO_DIV_4		(2 << 22) +#define DPLLB_TEST_SDVO_DIV_MASK	(3 << 22) +#define DPLLB_TEST_N_BYPASS		(1 << 19) +#define DPLLB_TEST_M_BYPASS		(1 << 18) +#define DPLLB_INPUT_BUFFER_ENABLE	(1 << 16) +#define DPLLA_TEST_N_BYPASS		(1 << 3) +#define DPLLA_TEST_M_BYPASS		(1 << 2) +#define DPLLA_INPUT_BUFFER_ENABLE	(1 << 0) + +#define ADPA			0x61100 +#define ADPA_DAC_ENABLE			(1 << 31) +#define ADPA_DAC_DISABLE		0 +#define ADPA_PIPE_SELECT_MASK		(1 << 30) +#define ADPA_PIPE_A_SELECT		0 +#define ADPA_PIPE_B_SELECT		(1 << 30) +#define ADPA_USE_VGA_HVPOLARITY		(1 << 15) +#define ADPA_SETS_HVPOLARITY		0 +#define ADPA_VSYNC_CNTL_DISABLE		(1 << 11) +#define ADPA_VSYNC_CNTL_ENABLE		0 +#define ADPA_HSYNC_CNTL_DISABLE		(1 << 10) +#define ADPA_HSYNC_CNTL_ENABLE		0 +#define ADPA_VSYNC_ACTIVE_HIGH		(1 << 4) +#define ADPA_VSYNC_ACTIVE_LOW		0 +#define ADPA_HSYNC_ACTIVE_HIGH		(1 << 3) +#define ADPA_HSYNC_ACTIVE_LOW		0 + +#define FPA0			0x06040 +#define FPA1			0x06044 +#define FPB0			0x06048 +#define FPB1			0x0604c +#define FP_N_DIV_MASK			0x003f0000 +#define FP_N_DIV_SHIFT			16 +#define FP_M1_DIV_MASK			0x00003f00 +#define FP_M1_DIV_SHIFT			8 +#define FP_M2_DIV_MASK			0x0000003f +#define FP_M2_DIV_SHIFT			0 + +#define PORT_HOTPLUG_EN		0x61110 +#define HDMIB_HOTPLUG_INT_EN		(1 << 29) +#define HDMIC_HOTPLUG_INT_EN		(1 << 28) +#define HDMID_HOTPLUG_INT_EN		(1 << 27) +#define SDVOB_HOTPLUG_INT_EN		(1 << 26) +#define SDVOC_HOTPLUG_INT_EN		(1 << 25) +#define TV_HOTPLUG_INT_EN		(1 << 18) +#define CRT_HOTPLUG_INT_EN		(1 << 9) +#define CRT_HOTPLUG_FORCE_DETECT	(1 << 3) +/* CDV.. */ +#define CRT_HOTPLUG_ACTIVATION_PERIOD_64	(1 << 8) +#define CRT_HOTPLUG_DAC_ON_TIME_2M		(0 << 7) +#define CRT_HOTPLUG_DAC_ON_TIME_4M		(1 << 7) +#define CRT_HOTPLUG_VOLTAGE_COMPARE_40		(0 << 5) +#define CRT_HOTPLUG_VOLTAGE_COMPARE_50		(1 << 5) +#define CRT_HOTPLUG_VOLTAGE_COMPARE_60		(2 << 5) +#define CRT_HOTPLUG_VOLTAGE_COMPARE_70		(3 << 5) +#define CRT_HOTPLUG_VOLTAGE_COMPARE_MASK	(3 << 5) +#define CRT_HOTPLUG_DETECT_DELAY_1G		(0 << 4) +#define CRT_HOTPLUG_DETECT_DELAY_2G		(1 << 4) +#define CRT_HOTPLUG_DETECT_VOLTAGE_325MV	(0 << 2) +#define CRT_HOTPLUG_DETECT_VOLTAGE_475MV	(1 << 2) +#define CRT_HOTPLUG_DETECT_MASK			0x000000F8 + +#define PORT_HOTPLUG_STAT	0x61114 +#define CRT_HOTPLUG_INT_STATUS		(1 << 11) +#define TV_HOTPLUG_INT_STATUS		(1 << 10) +#define CRT_HOTPLUG_MONITOR_MASK	(3 << 8) +#define CRT_HOTPLUG_MONITOR_COLOR	(3 << 8) +#define CRT_HOTPLUG_MONITOR_MONO	(2 << 8) +#define CRT_HOTPLUG_MONITOR_NONE	(0 << 8) +#define SDVOC_HOTPLUG_INT_STATUS	(1 << 7) +#define SDVOB_HOTPLUG_INT_STATUS	(1 << 6) + +#define SDVOB			0x61140 +#define SDVOC			0x61160 +#define SDVO_ENABLE			(1 << 31) +#define SDVO_PIPE_B_SELECT		(1 << 30) +#define SDVO_STALL_SELECT		(1 << 29) +#define SDVO_INTERRUPT_ENABLE		(1 << 26) +#define SDVO_COLOR_RANGE_16_235		(1 << 8) +#define SDVO_AUDIO_ENABLE		(1 << 6) + +/** + * 915G/GM SDVO pixel multiplier. + * + * Programmed value is multiplier - 1, up to 5x. + * + * DPLL_MD_UDI_MULTIPLIER_MASK + */ +#define SDVO_PORT_MULTIPLY_MASK		(7 << 23) +#define SDVO_PORT_MULTIPLY_SHIFT	23 +#define SDVO_PHASE_SELECT_MASK		(15 << 19) +#define SDVO_PHASE_SELECT_DEFAULT	(6 << 19) +#define SDVO_CLOCK_OUTPUT_INVERT	(1 << 18) +#define SDVOC_GANG_MODE			(1 << 16) +#define SDVO_BORDER_ENABLE		(1 << 7) +#define SDVOB_PCIE_CONCURRENCY		(1 << 3) +#define SDVO_DETECTED			(1 << 2) +/* Bits to be preserved when writing */ +#define SDVOB_PRESERVE_MASK		((1 << 17) | (1 << 16) | (1 << 14)) +#define SDVOC_PRESERVE_MASK		(1 << 17) + +/* + * This register controls the LVDS output enable, pipe selection, and data + * format selection. + * + * All of the clock/data pairs are force powered down by power sequencing. + */ +#define LVDS			0x61180 +/* + * Enables the LVDS port.  This bit must be set before DPLLs are enabled, as + * the DPLL semantics change when the LVDS is assigned to that pipe. + */ +#define LVDS_PORT_EN			(1 << 31) +/* Selects pipe B for LVDS data.  Must be set on pre-965. */ +#define LVDS_PIPEB_SELECT		(1 << 30) + +/* Turns on border drawing to allow centered display. */ +#define LVDS_BORDER_EN			(1 << 15) + +/* + * Enables the A0-A2 data pairs and CLKA, containing 18 bits of color data per + * pixel. + */ +#define LVDS_A0A2_CLKA_POWER_MASK	(3 << 8) +#define LVDS_A0A2_CLKA_POWER_DOWN	(0 << 8) +#define LVDS_A0A2_CLKA_POWER_UP		(3 << 8) +/* + * Controls the A3 data pair, which contains the additional LSBs for 24 bit + * mode.  Only enabled if LVDS_A0A2_CLKA_POWER_UP also indicates it should be + * on. + */ +#define LVDS_A3_POWER_MASK		(3 << 6) +#define LVDS_A3_POWER_DOWN		(0 << 6) +#define LVDS_A3_POWER_UP		(3 << 6) +/* + * Controls the CLKB pair.  This should only be set when LVDS_B0B3_POWER_UP + * is set. + */ +#define LVDS_CLKB_POWER_MASK		(3 << 4) +#define LVDS_CLKB_POWER_DOWN		(0 << 4) +#define LVDS_CLKB_POWER_UP		(3 << 4) +/* + * Controls the B0-B3 data pairs.  This must be set to match the DPLL p2 + * setting for whether we are in dual-channel mode.  The B3 pair will + * additionally only be powered up when LVDS_A3_POWER_UP is set. + */ +#define LVDS_B0B3_POWER_MASK		(3 << 2) +#define LVDS_B0B3_POWER_DOWN		(0 << 2) +#define LVDS_B0B3_POWER_UP		(3 << 2) + +#define PIPEACONF		0x70008 +#define PIPEACONF_ENABLE		(1 << 31) +#define PIPEACONF_DISABLE		0 +#define PIPEACONF_DOUBLE_WIDE		(1 << 30) +#define PIPECONF_ACTIVE			(1 << 30) +#define PIPECONF_DSIPLL_LOCK		(1 << 29) +#define PIPEACONF_SINGLE_WIDE		0 +#define PIPEACONF_PIPE_UNLOCKED		0 +#define PIPEACONF_DSR			(1 << 26) +#define PIPEACONF_PIPE_LOCKED		(1 << 25) +#define PIPEACONF_PALETTE		0 +#define PIPECONF_FORCE_BORDER		(1 << 25) +#define PIPEACONF_GAMMA			(1 << 24) +#define PIPECONF_PROGRESSIVE		(0 << 21) +#define PIPECONF_INTERLACE_W_FIELD_INDICATION	(6 << 21) +#define PIPECONF_INTERLACE_FIELD_0_ONLY		(7 << 21) +#define PIPECONF_PLANE_OFF		(1 << 19) +#define PIPECONF_CURSOR_OFF		(1 << 18) + +#define PIPEBCONF		0x71008 +#define PIPEBCONF_ENABLE		(1 << 31) +#define PIPEBCONF_DISABLE		0 +#define PIPEBCONF_DOUBLE_WIDE		(1 << 30) +#define PIPEBCONF_DISABLE		0 +#define PIPEBCONF_GAMMA			(1 << 24) +#define PIPEBCONF_PALETTE		0 + +#define PIPECCONF		0x72008 + +#define PIPEBGCMAXRED		0x71010 +#define PIPEBGCMAXGREEN		0x71014 +#define PIPEBGCMAXBLUE		0x71018 + +#define PIPEASTAT		0x70024 +#define PIPEBSTAT		0x71024 +#define PIPECSTAT		0x72024 +#define PIPE_VBLANK_INTERRUPT_STATUS		(1UL << 1) +#define PIPE_START_VBLANK_INTERRUPT_STATUS	(1UL << 2) +#define PIPE_VBLANK_CLEAR			(1 << 1) +#define PIPE_VBLANK_STATUS			(1 << 1) +#define PIPE_TE_STATUS				(1UL << 6) +#define PIPE_DPST_EVENT_STATUS			(1UL << 7) +#define PIPE_VSYNC_CLEAR			(1UL << 9) +#define PIPE_VSYNC_STATUS			(1UL << 9) +#define PIPE_HDMI_AUDIO_UNDERRUN_STATUS		(1UL << 10) +#define PIPE_HDMI_AUDIO_BUFFER_DONE_STATUS	(1UL << 11) +#define PIPE_VBLANK_INTERRUPT_ENABLE		(1UL << 17) +#define PIPE_START_VBLANK_INTERRUPT_ENABLE	(1UL << 18) +#define PIPE_TE_ENABLE				(1UL << 22) +#define PIPE_LEGACY_BLC_EVENT_ENABLE		(1UL << 22) +#define PIPE_DPST_EVENT_ENABLE			(1UL << 23) +#define PIPE_VSYNC_ENABL			(1UL << 25) +#define PIPE_HDMI_AUDIO_UNDERRUN		(1UL << 26) +#define PIPE_HDMI_AUDIO_BUFFER_DONE		(1UL << 27) +#define PIPE_FIFO_UNDERRUN			(1UL << 31) +#define PIPE_HDMI_AUDIO_INT_MASK		(PIPE_HDMI_AUDIO_UNDERRUN | \ +						PIPE_HDMI_AUDIO_BUFFER_DONE) +#define PIPE_EVENT_MASK ((1 << 29)|(1 << 28)|(1 << 27)|(1 << 26)|(1 << 24)|(1 << 23)|(1 << 22)|(1 << 21)|(1 << 20)|(1 << 16)) +#define PIPE_VBLANK_MASK ((1 << 25)|(1 << 24)|(1 << 18)|(1 << 17)) +#define HISTOGRAM_INT_CONTROL		0x61268 +#define HISTOGRAM_BIN_DATA		0X61264 +#define HISTOGRAM_LOGIC_CONTROL		0x61260 +#define PWM_CONTROL_LOGIC		0x61250 +#define PIPE_HOTPLUG_INTERRUPT_STATUS		(1UL << 10) +#define HISTOGRAM_INTERRUPT_ENABLE		(1UL << 31) +#define HISTOGRAM_LOGIC_ENABLE			(1UL << 31) +#define PWM_LOGIC_ENABLE			(1UL << 31) +#define PWM_PHASEIN_ENABLE			(1UL << 25) +#define PWM_PHASEIN_INT_ENABLE			(1UL << 24) +#define PWM_PHASEIN_VB_COUNT			0x00001f00 +#define PWM_PHASEIN_INC				0x0000001f +#define HISTOGRAM_INT_CTRL_CLEAR		(1UL << 30) +#define DPST_YUV_LUMA_MODE			0 + +struct dpst_ie_histogram_control { +	union { +		uint32_t data; +		struct { +			uint32_t bin_reg_index:7; +			uint32_t reserved:4; +			uint32_t bin_reg_func_select:1; +			uint32_t sync_to_phase_in:1; +			uint32_t alt_enhancement_mode:2; +			uint32_t reserved1:1; +			uint32_t sync_to_phase_in_count:8; +			uint32_t histogram_mode_select:1; +			uint32_t reserved2:4; +			uint32_t ie_pipe_assignment:1; +			uint32_t ie_mode_table_enabled:1; +			uint32_t ie_histogram_enable:1; +		}; +	}; +}; + +struct dpst_guardband { +	union { +		uint32_t data; +		struct { +			uint32_t guardband:22; +			uint32_t guardband_interrupt_delay:8; +			uint32_t interrupt_status:1; +			uint32_t interrupt_enable:1; +		}; +	}; +}; + +#define PIPEAFRAMEHIGH		0x70040 +#define PIPEAFRAMEPIXEL		0x70044 +#define PIPEBFRAMEHIGH		0x71040 +#define PIPEBFRAMEPIXEL		0x71044 +#define PIPECFRAMEHIGH		0x72040 +#define PIPECFRAMEPIXEL		0x72044 +#define PIPE_FRAME_HIGH_MASK	0x0000ffff +#define PIPE_FRAME_HIGH_SHIFT	0 +#define PIPE_FRAME_LOW_MASK	0xff000000 +#define PIPE_FRAME_LOW_SHIFT	24 +#define PIPE_PIXEL_MASK		0x00ffffff +#define PIPE_PIXEL_SHIFT	0 + +#define FW_BLC_SELF		0x20e0  +#define FW_BLC_SELF_EN          (1<<15) + +#define DSPARB			0x70030 +#define DSPFW1			0x70034 +#define DSP_FIFO_SR_WM_MASK		0xFF800000 +#define DSP_FIFO_SR_WM_SHIFT		23 +#define CURSOR_B_FIFO_WM_MASK		0x003F0000 +#define CURSOR_B_FIFO_WM_SHIFT		16 +#define DSPFW2			0x70038 +#define CURSOR_A_FIFO_WM_MASK		0x3F00 +#define CURSOR_A_FIFO_WM_SHIFT		8 +#define DSP_PLANE_C_FIFO_WM_MASK	0x7F +#define DSP_PLANE_C_FIFO_WM_SHIFT	0 +#define DSPFW3			0x7003c +#define DSPFW4			0x70050 +#define DSPFW5			0x70054 +#define DSP_PLANE_B_FIFO_WM1_SHIFT	24 +#define DSP_PLANE_A_FIFO_WM1_SHIFT	16 +#define CURSOR_B_FIFO_WM1_SHIFT		8 +#define CURSOR_FIFO_SR_WM1_SHIFT	0 +#define DSPFW6			0x70058 +#define DSPCHICKENBIT		0x70400 +#define DSPACNTR		0x70180 +#define DSPBCNTR		0x71180 +#define DSPCCNTR		0x72180 +#define DISPLAY_PLANE_ENABLE			(1 << 31) +#define DISPLAY_PLANE_DISABLE			0 +#define DISPPLANE_GAMMA_ENABLE			(1 << 30) +#define DISPPLANE_GAMMA_DISABLE			0 +#define DISPPLANE_PIXFORMAT_MASK		(0xf << 26) +#define DISPPLANE_8BPP				(0x2 << 26) +#define DISPPLANE_15_16BPP			(0x4 << 26) +#define DISPPLANE_16BPP				(0x5 << 26) +#define DISPPLANE_32BPP_NO_ALPHA		(0x6 << 26) +#define DISPPLANE_32BPP				(0x7 << 26) +#define DISPPLANE_STEREO_ENABLE			(1 << 25) +#define DISPPLANE_STEREO_DISABLE		0 +#define DISPPLANE_SEL_PIPE_MASK			(1 << 24) +#define DISPPLANE_SEL_PIPE_POS			24 +#define DISPPLANE_SEL_PIPE_A			0 +#define DISPPLANE_SEL_PIPE_B			(1 << 24) +#define DISPPLANE_SRC_KEY_ENABLE		(1 << 22) +#define DISPPLANE_SRC_KEY_DISABLE		0 +#define DISPPLANE_LINE_DOUBLE			(1 << 20) +#define DISPPLANE_NO_LINE_DOUBLE		0 +#define DISPPLANE_STEREO_POLARITY_FIRST		0 +#define DISPPLANE_STEREO_POLARITY_SECOND	(1 << 18) +/* plane B only */ +#define DISPPLANE_ALPHA_TRANS_ENABLE		(1 << 15) +#define DISPPLANE_ALPHA_TRANS_DISABLE		0 +#define DISPPLANE_SPRITE_ABOVE_DISPLAYA		0 +#define DISPPLANE_SPRITE_ABOVE_OVERLAY		(1) +#define DISPPLANE_BOTTOM			(4) + +#define DSPABASE		0x70184 +#define DSPALINOFF		0x70184 +#define DSPASTRIDE		0x70188 + +#define DSPBBASE		0x71184 +#define DSPBLINOFF		0X71184 +#define DSPBADDR		DSPBBASE +#define DSPBSTRIDE		0x71188 + +#define DSPCBASE		0x72184 +#define DSPCLINOFF		0x72184 +#define DSPCSTRIDE		0x72188 + +#define DSPAKEYVAL		0x70194 +#define DSPAKEYMASK		0x70198 + +#define DSPAPOS			0x7018C	/* reserved */ +#define DSPASIZE		0x70190 +#define DSPBPOS			0x7118C +#define DSPBSIZE		0x71190 +#define DSPCPOS			0x7218C +#define DSPCSIZE		0x72190 + +#define DSPASURF		0x7019C +#define DSPATILEOFF		0x701A4 + +#define DSPBSURF		0x7119C +#define DSPBTILEOFF		0x711A4 + +#define DSPCSURF		0x7219C +#define DSPCTILEOFF		0x721A4 +#define DSPCKEYMAXVAL		0x721A0 +#define DSPCKEYMINVAL		0x72194 +#define DSPCKEYMSK		0x72198 + +#define VGACNTRL		0x71400 +#define VGA_DISP_DISABLE		(1 << 31) +#define VGA_2X_MODE			(1 << 30) +#define VGA_PIPE_B_SELECT		(1 << 29) + +/* + * Overlay registers + */ +#define OV_C_OFFSET		0x08000 +#define OV_OVADD		0x30000 +#define OV_DOVASTA		0x30008 +# define OV_PIPE_SELECT			((1 << 6)|(1 << 7)) +# define OV_PIPE_SELECT_POS		6 +# define OV_PIPE_A			0 +# define OV_PIPE_C			1 +#define OV_OGAMC5		0x30010 +#define OV_OGAMC4		0x30014 +#define OV_OGAMC3		0x30018 +#define OV_OGAMC2		0x3001C +#define OV_OGAMC1		0x30020 +#define OV_OGAMC0		0x30024 +#define OVC_OVADD		0x38000 +#define OVC_DOVCSTA		0x38008 +#define OVC_OGAMC5		0x38010 +#define OVC_OGAMC4		0x38014 +#define OVC_OGAMC3		0x38018 +#define OVC_OGAMC2		0x3801C +#define OVC_OGAMC1		0x38020 +#define OVC_OGAMC0		0x38024 + +/* + * Some BIOS scratch area registers.  The 845 (and 830?) store the amount + * of video memory available to the BIOS in SWF1. + */ +#define SWF0			0x71410 +#define SWF1			0x71414 +#define SWF2			0x71418 +#define SWF3			0x7141c +#define SWF4			0x71420 +#define SWF5			0x71424 +#define SWF6			0x71428 + +/* + * 855 scratch registers. + */ +#define SWF00			0x70410 +#define SWF01			0x70414 +#define SWF02			0x70418 +#define SWF03			0x7041c +#define SWF04			0x70420 +#define SWF05			0x70424 +#define SWF06			0x70428 + +#define SWF10			SWF0 +#define SWF11			SWF1 +#define SWF12			SWF2 +#define SWF13			SWF3 +#define SWF14			SWF4 +#define SWF15			SWF5 +#define SWF16			SWF6 + +#define SWF30			0x72414 +#define SWF31			0x72418 +#define SWF32			0x7241c + + +/* + * Palette registers + */ +#define PALETTE_A		0x0a000 +#define PALETTE_B		0x0a800 +#define PALETTE_C		0x0ac00 + +/* Cursor A & B regs */ +#define CURACNTR		0x70080 +#define CURSOR_MODE_DISABLE		0x00 +#define CURSOR_MODE_64_32B_AX		0x07 +#define CURSOR_MODE_64_ARGB_AX		((1 << 5) | CURSOR_MODE_64_32B_AX) +#define MCURSOR_GAMMA_ENABLE		(1 << 26) +#define CURABASE		0x70084 +#define CURAPOS			0x70088 +#define CURSOR_POS_MASK			0x007FF +#define CURSOR_POS_SIGN			0x8000 +#define CURSOR_X_SHIFT			0 +#define CURSOR_Y_SHIFT			16 +#define CURBCNTR		0x700c0 +#define CURBBASE		0x700c4 +#define CURBPOS			0x700c8 +#define CURCCNTR		0x700e0 +#define CURCBASE		0x700e4 +#define CURCPOS			0x700e8 + +/* + * Interrupt Registers + */ +#define IER			0x020a0 +#define IIR			0x020a4 +#define IMR			0x020a8 +#define ISR			0x020ac + +/* + * MOORESTOWN delta registers + */ +#define MRST_DPLL_A		0x0f014 +#define MDFLD_DPLL_B		0x0f018 +#define MDFLD_INPUT_REF_SEL		(1 << 14) +#define MDFLD_VCO_SEL			(1 << 16) +#define DPLLA_MODE_LVDS			(2 << 26)	/* mrst */ +#define MDFLD_PLL_LATCHEN		(1 << 28) +#define MDFLD_PWR_GATE_EN		(1 << 30) +#define MDFLD_P1_MASK			(0x1FF << 17) +#define MRST_FPA0		0x0f040 +#define MRST_FPA1		0x0f044 +#define MDFLD_DPLL_DIV0		0x0f048 +#define MDFLD_DPLL_DIV1		0x0f04c +#define MRST_PERF_MODE		0x020f4 + +/* + * MEDFIELD HDMI registers + */ +#define HDMIPHYMISCCTL		0x61134 +#define HDMI_PHY_POWER_DOWN		0x7f +#define HDMIB_CONTROL		0x61140 +#define HDMIB_PORT_EN			(1 << 31) +#define HDMIB_PIPE_B_SELECT		(1 << 30) +#define HDMIB_NULL_PACKET		(1 << 9) +#define HDMIB_HDCP_PORT			(1 << 5) + +/* #define LVDS			0x61180 */ +#define MRST_PANEL_8TO6_DITHER_ENABLE	(1 << 25) +#define MRST_PANEL_24_DOT_1_FORMAT	(1 << 24) +#define LVDS_A3_POWER_UP_0_OUTPUT	(1 << 6) + +#define MIPI			0x61190 +#define MIPI_C			0x62190 +#define MIPI_PORT_EN			(1 << 31) +/* Turns on border drawing to allow centered display. */ +#define SEL_FLOPPED_HSTX		(1 << 23) +#define PASS_FROM_SPHY_TO_AFE		(1 << 16) +#define MIPI_BORDER_EN			(1 << 15) +#define MIPIA_3LANE_MIPIC_1LANE		0x1 +#define MIPIA_2LANE_MIPIC_2LANE		0x2 +#define TE_TRIGGER_DSI_PROTOCOL		(1 << 2) +#define TE_TRIGGER_GPIO_PIN		(1 << 3) +#define MIPI_TE_COUNT		0x61194 + +/* #define PP_CONTROL	0x61204 */ +#define POWER_DOWN_ON_RESET		(1 << 1) + +/* #define PFIT_CONTROL	0x61230 */ +#define PFIT_PIPE_SELECT		(3 << 29) +#define PFIT_PIPE_SELECT_SHIFT		(29) + +/* #define BLC_PWM_CTL		0x61254 */ +#define MRST_BACKLIGHT_MODULATION_FREQ_SHIFT	(16) +#define MRST_BACKLIGHT_MODULATION_FREQ_MASK	(0xffff << 16) + +/* #define PIPEACONF 0x70008 */ +#define PIPEACONF_PIPE_STATE		(1 << 30) +/* #define DSPACNTR		0x70180 */ + +#define MRST_DSPABASE		0x7019c +#define MRST_DSPBBASE		0x7119c +#define MDFLD_DSPCBASE		0x7219c + +/* + * Moorestown registers. + */ + +/* + *	MIPI IP registers + */ +#define MIPIC_REG_OFFSET		0x800 + +#define DEVICE_READY_REG		0xb000 +#define LP_OUTPUT_HOLD				(1 << 16) +#define EXIT_ULPS_DEV_READY			0x3 +#define LP_OUTPUT_HOLD_RELEASE			0x810000 +# define ENTERING_ULPS				(2 << 1) +# define EXITING_ULPS				(1 << 1) +# define ULPS_MASK				(3 << 1) +# define BUS_POSSESSION				(1 << 3) +#define INTR_STAT_REG			0xb004 +#define RX_SOT_ERROR				(1 << 0) +#define RX_SOT_SYNC_ERROR			(1 << 1) +#define RX_ESCAPE_MODE_ENTRY_ERROR		(1 << 3) +#define RX_LP_TX_SYNC_ERROR			(1 << 4) +#define RX_HS_RECEIVE_TIMEOUT_ERROR		(1 << 5) +#define RX_FALSE_CONTROL_ERROR			(1 << 6) +#define RX_ECC_SINGLE_BIT_ERROR			(1 << 7) +#define RX_ECC_MULTI_BIT_ERROR			(1 << 8) +#define RX_CHECKSUM_ERROR			(1 << 9) +#define RX_DSI_DATA_TYPE_NOT_RECOGNIZED		(1 << 10) +#define RX_DSI_VC_ID_INVALID			(1 << 11) +#define TX_FALSE_CONTROL_ERROR			(1 << 12) +#define TX_ECC_SINGLE_BIT_ERROR			(1 << 13) +#define TX_ECC_MULTI_BIT_ERROR			(1 << 14) +#define TX_CHECKSUM_ERROR			(1 << 15) +#define TX_DSI_DATA_TYPE_NOT_RECOGNIZED		(1 << 16) +#define TX_DSI_VC_ID_INVALID			(1 << 17) +#define HIGH_CONTENTION				(1 << 18) +#define LOW_CONTENTION				(1 << 19) +#define DPI_FIFO_UNDER_RUN			(1 << 20) +#define HS_TX_TIMEOUT				(1 << 21) +#define LP_RX_TIMEOUT				(1 << 22) +#define TURN_AROUND_ACK_TIMEOUT			(1 << 23) +#define ACK_WITH_NO_ERROR			(1 << 24) +#define HS_GENERIC_WR_FIFO_FULL			(1 << 27) +#define LP_GENERIC_WR_FIFO_FULL			(1 << 28) +#define SPL_PKT_SENT				(1 << 30) +#define INTR_EN_REG			0xb008 +#define DSI_FUNC_PRG_REG		0xb00c +#define DPI_CHANNEL_NUMBER_POS			0x03 +#define DBI_CHANNEL_NUMBER_POS			0x05 +#define FMT_DPI_POS				0x07 +#define FMT_DBI_POS				0x0A +#define DBI_DATA_WIDTH_POS			0x0D + +/* DPI PIXEL FORMATS */ +#define RGB_565_FMT				0x01	/* RGB 565 FORMAT */ +#define RGB_666_FMT				0x02	/* RGB 666 FORMAT */ +#define LRGB_666_FMT				0x03	/* RGB LOOSELY PACKED +							 * 666 FORMAT +							 */ +#define RGB_888_FMT				0x04	/* RGB 888 FORMAT */ +#define VIRTUAL_CHANNEL_NUMBER_0		0x00	/* Virtual channel 0 */ +#define VIRTUAL_CHANNEL_NUMBER_1		0x01	/* Virtual channel 1 */ +#define VIRTUAL_CHANNEL_NUMBER_2		0x02	/* Virtual channel 2 */ +#define VIRTUAL_CHANNEL_NUMBER_3		0x03	/* Virtual channel 3 */ + +#define DBI_NOT_SUPPORTED			0x00	/* command mode +							 * is not supported +							 */ +#define DBI_DATA_WIDTH_16BIT			0x01	/* 16 bit data */ +#define DBI_DATA_WIDTH_9BIT			0x02	/* 9 bit data */ +#define DBI_DATA_WIDTH_8BIT			0x03	/* 8 bit data */ +#define DBI_DATA_WIDTH_OPT1			0x04	/* option 1 */ +#define DBI_DATA_WIDTH_OPT2			0x05	/* option 2 */ + +#define HS_TX_TIMEOUT_REG		0xb010 +#define LP_RX_TIMEOUT_REG		0xb014 +#define TURN_AROUND_TIMEOUT_REG		0xb018 +#define DEVICE_RESET_REG		0xb01C +#define DPI_RESOLUTION_REG		0xb020 +#define RES_V_POS				0x10 +#define DBI_RESOLUTION_REG		0xb024 /* Reserved for MDFLD */ +#define HORIZ_SYNC_PAD_COUNT_REG	0xb028 +#define HORIZ_BACK_PORCH_COUNT_REG	0xb02C +#define HORIZ_FRONT_PORCH_COUNT_REG	0xb030 +#define HORIZ_ACTIVE_AREA_COUNT_REG	0xb034 +#define VERT_SYNC_PAD_COUNT_REG		0xb038 +#define VERT_BACK_PORCH_COUNT_REG	0xb03c +#define VERT_FRONT_PORCH_COUNT_REG	0xb040 +#define HIGH_LOW_SWITCH_COUNT_REG	0xb044 +#define DPI_CONTROL_REG			0xb048 +#define DPI_SHUT_DOWN				(1 << 0) +#define DPI_TURN_ON				(1 << 1) +#define DPI_COLOR_MODE_ON			(1 << 2) +#define DPI_COLOR_MODE_OFF			(1 << 3) +#define DPI_BACK_LIGHT_ON			(1 << 4) +#define DPI_BACK_LIGHT_OFF			(1 << 5) +#define DPI_LP					(1 << 6) +#define DPI_DATA_REG			0xb04c +#define DPI_BACK_LIGHT_ON_DATA			0x07 +#define DPI_BACK_LIGHT_OFF_DATA			0x17 +#define INIT_COUNT_REG			0xb050 +#define MAX_RET_PAK_REG			0xb054 +#define VIDEO_FMT_REG			0xb058 +#define COMPLETE_LAST_PCKT			(1 << 2) +#define EOT_DISABLE_REG			0xb05c +#define ENABLE_CLOCK_STOPPING			(1 << 1) +#define LP_BYTECLK_REG			0xb060 +#define LP_GEN_DATA_REG			0xb064 +#define HS_GEN_DATA_REG			0xb068 +#define LP_GEN_CTRL_REG			0xb06C +#define HS_GEN_CTRL_REG			0xb070 +#define DCS_CHANNEL_NUMBER_POS		0x6 +#define MCS_COMMANDS_POS		0x8 +#define WORD_COUNTS_POS			0x8 +#define MCS_PARAMETER_POS			0x10 +#define GEN_FIFO_STAT_REG		0xb074 +#define HS_DATA_FIFO_FULL			(1 << 0) +#define HS_DATA_FIFO_HALF_EMPTY			(1 << 1) +#define HS_DATA_FIFO_EMPTY			(1 << 2) +#define LP_DATA_FIFO_FULL			(1 << 8) +#define LP_DATA_FIFO_HALF_EMPTY			(1 << 9) +#define LP_DATA_FIFO_EMPTY			(1 << 10) +#define HS_CTRL_FIFO_FULL			(1 << 16) +#define HS_CTRL_FIFO_HALF_EMPTY			(1 << 17) +#define HS_CTRL_FIFO_EMPTY			(1 << 18) +#define LP_CTRL_FIFO_FULL			(1 << 24) +#define LP_CTRL_FIFO_HALF_EMPTY			(1 << 25) +#define LP_CTRL_FIFO_EMPTY			(1 << 26) +#define DBI_FIFO_EMPTY				(1 << 27) +#define DPI_FIFO_EMPTY				(1 << 28) +#define HS_LS_DBI_ENABLE_REG		0xb078 +#define TXCLKESC_REG			0xb07c +#define DPHY_PARAM_REG			0xb080 +#define DBI_BW_CTRL_REG			0xb084 +#define CLK_LANE_SWT_REG		0xb088 + +/* + * MIPI Adapter registers + */ +#define MIPI_CONTROL_REG		0xb104 +#define MIPI_2X_CLOCK_BITS			((1 << 0) | (1 << 1)) +#define MIPI_DATA_ADDRESS_REG		0xb108 +#define MIPI_DATA_LENGTH_REG		0xb10C +#define MIPI_COMMAND_ADDRESS_REG	0xb110 +#define MIPI_COMMAND_LENGTH_REG		0xb114 +#define MIPI_READ_DATA_RETURN_REG0	0xb118 +#define MIPI_READ_DATA_RETURN_REG1	0xb11C +#define MIPI_READ_DATA_RETURN_REG2	0xb120 +#define MIPI_READ_DATA_RETURN_REG3	0xb124 +#define MIPI_READ_DATA_RETURN_REG4	0xb128 +#define MIPI_READ_DATA_RETURN_REG5	0xb12C +#define MIPI_READ_DATA_RETURN_REG6	0xb130 +#define MIPI_READ_DATA_RETURN_REG7	0xb134 +#define MIPI_READ_DATA_VALID_REG	0xb138 + +/* DBI COMMANDS */ +#define soft_reset			0x01 +/* + *	The display module performs a software reset. + *	Registers are written with their SW Reset default values. + */ +#define get_power_mode			0x0a +/* + *	The display module returns the current power mode + */ +#define get_address_mode		0x0b +/* + *	The display module returns the current status. + */ +#define get_pixel_format		0x0c +/* + *	This command gets the pixel format for the RGB image data + *	used by the interface. + */ +#define get_display_mode		0x0d +/* + *	The display module returns the Display Image Mode status. + */ +#define get_signal_mode			0x0e +/* + *	The display module returns the Display Signal Mode. + */ +#define get_diagnostic_result		0x0f +/* + *	The display module returns the self-diagnostic results following + *	a Sleep Out command. + */ +#define enter_sleep_mode		0x10 +/* + *	This command causes the display module to enter the Sleep mode. + *	In this mode, all unnecessary blocks inside the display module are + *	disabled except interface communication. This is the lowest power + *	mode the display module supports. + */ +#define exit_sleep_mode			0x11 +/* + *	This command causes the display module to exit Sleep mode. + *	All blocks inside the display module are enabled. + */ +#define enter_partial_mode		0x12 +/* + *	This command causes the display module to enter the Partial Display + *	Mode. The Partial Display Mode window is described by the + *	set_partial_area command. + */ +#define enter_normal_mode		0x13 +/* + *	This command causes the display module to enter the Normal mode. + *	Normal Mode is defined as Partial Display mode and Scroll mode are off + */ +#define exit_invert_mode		0x20 +/* + *	This command causes the display module to stop inverting the image + *	data on the display device. The frame memory contents remain unchanged. + *	No status bits are changed. + */ +#define enter_invert_mode		0x21 +/* + *	This command causes the display module to invert the image data only on + *	the display device. The frame memory contents remain unchanged. + *	No status bits are changed. + */ +#define set_gamma_curve			0x26 +/* + *	This command selects the desired gamma curve for the display device. + *	Four fixed gamma curves are defined in section DCS spec. + */ +#define set_display_off			0x28 +/* ************************************************************************* *\ +This command causes the display module to stop displaying the image data +on the display device. The frame memory contents remain unchanged. +No status bits are changed. +\* ************************************************************************* */ +#define set_display_on			0x29 +/* ************************************************************************* *\ +This command causes the display module to start displaying the image data +on the display device. The frame memory contents remain unchanged. +No status bits are changed. +\* ************************************************************************* */ +#define set_column_address		0x2a +/* + *	This command defines the column extent of the frame memory accessed by + *	the hostprocessor with the read_memory_continue and + *	write_memory_continue commands. + *	No status bits are changed. + */ +#define set_page_addr			0x2b +/* + *	This command defines the page extent of the frame memory accessed by + *	the host processor with the write_memory_continue and + *	read_memory_continue command. + *	No status bits are changed. + */ +#define write_mem_start			0x2c +/* + *	This command transfers image data from the host processor to the + *	display modules frame memory starting at the pixel location specified + *	by preceding set_column_address and set_page_address commands. + */ +#define set_partial_area		0x30 +/* + *	This command defines the Partial Display mode s display area. + *	There are two parameters associated with this command, the first + *	defines the Start Row (SR) and the second the End Row (ER). SR and ER + *	refer to the Frame Memory Line Pointer. + */ +#define set_scroll_area			0x33 +/* + *	This command defines the display modules Vertical Scrolling Area. + */ +#define set_tear_off			0x34 +/* + *	This command turns off the display modules Tearing Effect output + *	signal on the TE signal line. + */ +#define set_tear_on			0x35 +/* + *	This command turns on the display modules Tearing Effect output signal + *	on the TE signal line. + */ +#define set_address_mode		0x36 +/* + *	This command sets the data order for transfers from the host processor + *	to display modules frame memory,bits B[7:5] and B3, and from the + *	display modules frame memory to the display device, bits B[2:0] and B4. + */ +#define set_scroll_start		0x37 +/* + *	This command sets the start of the vertical scrolling area in the frame + *	memory. The vertical scrolling area is fully defined when this command + *	is used with the set_scroll_area command The set_scroll_start command + *	has one parameter, the Vertical Scroll Pointer. The VSP defines the + *	line in the frame memory that is written to the display device as the + *	first line of the vertical scroll area. + */ +#define exit_idle_mode			0x38 +/* + *	This command causes the display module to exit Idle mode. + */ +#define enter_idle_mode			0x39 +/* + *	This command causes the display module to enter Idle Mode. + *	In Idle Mode, color expression is reduced. Colors are shown on the + *	display device using the MSB of each of the R, G and B color + *	components in the frame memory + */ +#define set_pixel_format		0x3a +/* + *	This command sets the pixel format for the RGB image data used by the + *	interface. + *	Bits D[6:4]  DPI Pixel Format Definition + *	Bits D[2:0]  DBI Pixel Format Definition + *	Bits D7 and D3 are not used. + */ +#define DCS_PIXEL_FORMAT_3bpp		0x1 +#define DCS_PIXEL_FORMAT_8bpp		0x2 +#define DCS_PIXEL_FORMAT_12bpp		0x3 +#define DCS_PIXEL_FORMAT_16bpp		0x5 +#define DCS_PIXEL_FORMAT_18bpp		0x6 +#define DCS_PIXEL_FORMAT_24bpp		0x7 + +#define write_mem_cont			0x3c + +/* + *	This command transfers image data from the host processor to the + *	display module's frame memory continuing from the pixel location + *	following the previous write_memory_continue or write_memory_start + *	command. + */ +#define set_tear_scanline		0x44 +/* + *	This command turns on the display modules Tearing Effect output signal + *	on the TE signal line when the display module reaches line N. + */ +#define get_scanline			0x45 +/* + *	The display module returns the current scanline, N, used to update the + *	 display device. The total number of scanlines on a display device is + *	defined as VSYNC + VBP + VACT + VFP.The first scanline is defined as + *	the first line of V Sync and is denoted as Line 0. + *	When in Sleep Mode, the value returned by get_scanline is undefined. + */ + +/* MCS or Generic COMMANDS */ +/* MCS/generic data type */ +#define GEN_SHORT_WRITE_0	0x03  /* generic short write, no parameters */ +#define GEN_SHORT_WRITE_1	0x13  /* generic short write, 1 parameters */ +#define GEN_SHORT_WRITE_2	0x23  /* generic short write, 2 parameters */ +#define GEN_READ_0		0x04  /* generic read, no parameters */ +#define GEN_READ_1		0x14  /* generic read, 1 parameters */ +#define GEN_READ_2		0x24  /* generic read, 2 parameters */ +#define GEN_LONG_WRITE		0x29  /* generic long write */ +#define MCS_SHORT_WRITE_0	0x05  /* MCS short write, no parameters */ +#define MCS_SHORT_WRITE_1	0x15  /* MCS short write, 1 parameters */ +#define MCS_READ		0x06  /* MCS read, no parameters */ +#define MCS_LONG_WRITE		0x39  /* MCS long write */ +/* MCS/generic commands */ +/* TPO MCS */ +#define write_display_profile		0x50 +#define write_display_brightness	0x51 +#define write_ctrl_display		0x53 +#define write_ctrl_cabc			0x55 +  #define UI_IMAGE		0x01 +  #define STILL_IMAGE		0x02 +  #define MOVING_IMAGE		0x03 +#define write_hysteresis		0x57 +#define write_gamma_setting		0x58 +#define write_cabc_min_bright		0x5e +#define write_kbbc_profile		0x60 +/* TMD MCS */ +#define tmd_write_display_brightness 0x8c + +/* + *	This command is used to control ambient light, panel backlight + *	brightness and gamma settings. + */ +#define BRIGHT_CNTL_BLOCK_ON	(1 << 5) +#define AMBIENT_LIGHT_SENSE_ON	(1 << 4) +#define DISPLAY_DIMMING_ON	(1 << 3) +#define BACKLIGHT_ON		(1 << 2) +#define DISPLAY_BRIGHTNESS_AUTO	(1 << 1) +#define GAMMA_AUTO		(1 << 0) + +/* DCS Interface Pixel Formats */ +#define DCS_PIXEL_FORMAT_3BPP	0x1 +#define DCS_PIXEL_FORMAT_8BPP	0x2 +#define DCS_PIXEL_FORMAT_12BPP	0x3 +#define DCS_PIXEL_FORMAT_16BPP	0x5 +#define DCS_PIXEL_FORMAT_18BPP	0x6 +#define DCS_PIXEL_FORMAT_24BPP	0x7 +/* ONE PARAMETER READ DATA */ +#define addr_mode_data		0xfc +#define diag_res_data		0x00 +#define disp_mode_data		0x23 +#define pxl_fmt_data		0x77 +#define pwr_mode_data		0x74 +#define sig_mode_data		0x00 +/* TWO PARAMETERS READ DATA */ +#define scanline_data1		0xff +#define scanline_data2		0xff +#define NON_BURST_MODE_SYNC_PULSE	0x01	/* Non Burst Mode +						 * with Sync Pulse +						 */ +#define NON_BURST_MODE_SYNC_EVENTS	0x02	/* Non Burst Mode +						 * with Sync events +						 */ +#define BURST_MODE			0x03	/* Burst Mode */ +#define DBI_COMMAND_BUFFER_SIZE		0x240   /* 0x32 */    /* 0x120 */ +						/* Allocate at least +						 * 0x100 Byte with 32 +						 * byte alignment +						 */ +#define DBI_DATA_BUFFER_SIZE		0x120	/* Allocate at least +						 * 0x100 Byte with 32 +						 * byte alignment +						 */ +#define DBI_CB_TIME_OUT			0xFFFF + +#define GEN_FB_TIME_OUT			2000 + +#define SKU_83				0x01 +#define SKU_100				0x02 +#define SKU_100L			0x04 +#define SKU_BYPASS			0x08 + +/* Some handy macros for playing with bitfields. */ +#define PSB_MASK(high, low) (((1<<((high)-(low)+1))-1)<<(low)) +#define SET_FIELD(value, field) (((value) << field ## _SHIFT) & field ## _MASK) +#define GET_FIELD(word, field) (((word)  & field ## _MASK) >> field ## _SHIFT) + +#define _PIPE(pipe, a, b) ((a) + (pipe)*((b)-(a))) + +/* PCI config space */ + +#define SB_PCKT         0x02100 /* cedarview */ +# define SB_OPCODE_MASK                         PSB_MASK(31, 16) +# define SB_OPCODE_SHIFT                        16 +# define SB_OPCODE_READ                         0 +# define SB_OPCODE_WRITE                        1 +# define SB_DEST_MASK                           PSB_MASK(15, 8) +# define SB_DEST_SHIFT                          8 +# define SB_DEST_DPLL                           0x88 +# define SB_BYTE_ENABLE_MASK                    PSB_MASK(7, 4) +# define SB_BYTE_ENABLE_SHIFT                   4 +# define SB_BUSY                                (1 << 0) + +#define DSPCLK_GATE_D		0x6200 +# define VRHUNIT_CLOCK_GATE_DISABLE		(1 << 28) /* Fixed value on CDV */ +# define DPOUNIT_CLOCK_GATE_DISABLE		(1 << 11) +# define DPIOUNIT_CLOCK_GATE_DISABLE		(1 << 6) +# define DPUNIT_PIPEB_GATE_DISABLE		(1 << 30) +# define DPUNIT_PIPEA_GATE_DISABLE		(1 << 25) +# define DPCUNIT_CLOCK_GATE_DISABLE		(1 << 24) +# define DPLSUNIT_CLOCK_GATE_DISABLE		(1 << 13) + +#define RAMCLK_GATE_D		0x6210 + +/* 32-bit value read/written from the DPIO reg. */ +#define SB_DATA		0x02104 /* cedarview */ +/* 32-bit address of the DPIO reg to be read/written. */ +#define SB_ADDR		0x02108 /* cedarview */ +#define DPIO_CFG	0x02110 /* cedarview */ +# define DPIO_MODE_SELECT_1			(1 << 3) +# define DPIO_MODE_SELECT_0			(1 << 2) +# define DPIO_SFR_BYPASS			(1 << 1) +/* reset is active low */ +# define DPIO_CMN_RESET_N			(1 << 0) + +/* Cedarview sideband registers */ +#define _SB_M_A			0x8008 +#define _SB_M_B			0x8028 +#define SB_M(pipe) _PIPE(pipe, _SB_M_A, _SB_M_B) +# define SB_M_DIVIDER_MASK			(0xFF << 24) +# define SB_M_DIVIDER_SHIFT			24 + +#define _SB_N_VCO_A		0x8014 +#define _SB_N_VCO_B		0x8034 +#define SB_N_VCO(pipe) _PIPE(pipe, _SB_N_VCO_A, _SB_N_VCO_B) +#define SB_N_VCO_SEL_MASK			PSB_MASK(31, 30) +#define SB_N_VCO_SEL_SHIFT			30 +#define SB_N_DIVIDER_MASK			PSB_MASK(29, 26) +#define SB_N_DIVIDER_SHIFT			26 +#define SB_N_CB_TUNE_MASK			PSB_MASK(25, 24) +#define SB_N_CB_TUNE_SHIFT			24 + +/* the bit 14:13 is used to select between the different reference clock for Pipe A/B */ +#define SB_REF_DPLLA		0x8010 +#define SB_REF_DPLLB		0x8030 +#define	REF_CLK_MASK		(0x3 << 13) +#define REF_CLK_CORE		(0 << 13) +#define REF_CLK_DPLL		(1 << 13) +#define REF_CLK_DPLLA		(2 << 13) +/* For the DPLL B, it will use the reference clk from DPLL A when using (2 << 13) */ + +#define _SB_REF_A		0x8018 +#define _SB_REF_B		0x8038 +#define SB_REF_SFR(pipe)	_PIPE(pipe, _SB_REF_A, _SB_REF_B) + +#define _SB_P_A			0x801c +#define _SB_P_B			0x803c +#define SB_P(pipe) _PIPE(pipe, _SB_P_A, _SB_P_B) +#define SB_P2_DIVIDER_MASK			PSB_MASK(31, 30) +#define SB_P2_DIVIDER_SHIFT			30 +#define SB_P2_10				0 /* HDMI, DP, DAC */ +#define SB_P2_5				1 /* DAC */ +#define SB_P2_14				2 /* LVDS single */ +#define SB_P2_7				3 /* LVDS double */ +#define SB_P1_DIVIDER_MASK			PSB_MASK(15, 12) +#define SB_P1_DIVIDER_SHIFT			12 + +#define PSB_LANE0		0x120 +#define PSB_LANE1		0x220 +#define PSB_LANE2		0x2320 +#define PSB_LANE3		0x2420 + +#define LANE_PLL_MASK		(0x7 << 20) +#define LANE_PLL_ENABLE		(0x3 << 20) +#define LANE_PLL_PIPE(p)	(((p) == 0) ? (1 << 21) : (0 << 21)) + +#define DP_B				0x64100 +#define DP_C				0x64200 + +#define   DP_PORT_EN			(1 << 31) +#define   DP_PIPEB_SELECT		(1 << 30) +#define   DP_PIPE_MASK			(1 << 30) + +/* Link training mode - select a suitable mode for each stage */ +#define   DP_LINK_TRAIN_PAT_1		(0 << 28) +#define   DP_LINK_TRAIN_PAT_2		(1 << 28) +#define   DP_LINK_TRAIN_PAT_IDLE	(2 << 28) +#define   DP_LINK_TRAIN_OFF		(3 << 28) +#define   DP_LINK_TRAIN_MASK		(3 << 28) +#define   DP_LINK_TRAIN_SHIFT		28 + +/* Signal voltages. These are mostly controlled by the other end */ +#define   DP_VOLTAGE_0_4		(0 << 25) +#define   DP_VOLTAGE_0_6		(1 << 25) +#define   DP_VOLTAGE_0_8		(2 << 25) +#define   DP_VOLTAGE_1_2		(3 << 25) +#define   DP_VOLTAGE_MASK		(7 << 25) +#define   DP_VOLTAGE_SHIFT		25 + +/* Signal pre-emphasis levels, like voltages, the other end tells us what + * they want + */ +#define   DP_PRE_EMPHASIS_0		(0 << 22) +#define   DP_PRE_EMPHASIS_3_5		(1 << 22) +#define   DP_PRE_EMPHASIS_6		(2 << 22) +#define   DP_PRE_EMPHASIS_9_5		(3 << 22) +#define   DP_PRE_EMPHASIS_MASK		(7 << 22) +#define   DP_PRE_EMPHASIS_SHIFT		22 + +/* How many wires to use. I guess 3 was too hard */ +#define   DP_PORT_WIDTH_1		(0 << 19) +#define   DP_PORT_WIDTH_2		(1 << 19) +#define   DP_PORT_WIDTH_4		(3 << 19) +#define   DP_PORT_WIDTH_MASK		(7 << 19) + +/* Mystic DPCD version 1.1 special mode */ +#define   DP_ENHANCED_FRAMING		(1 << 18) + +/** locked once port is enabled */ +#define   DP_PORT_REVERSAL		(1 << 15) + +/** sends the clock on lane 15 of the PEG for debug */ +#define   DP_CLOCK_OUTPUT_ENABLE	(1 << 13) + +#define   DP_SCRAMBLING_DISABLE		(1 << 12) +#define   DP_SCRAMBLING_DISABLE_IRONLAKE	(1 << 7) + +/** limit RGB values to avoid confusing TVs */ +#define   DP_COLOR_RANGE_16_235		(1 << 8) + +/** Turn on the audio link */ +#define   DP_AUDIO_OUTPUT_ENABLE	(1 << 6) + +/** vs and hs sync polarity */ +#define   DP_SYNC_VS_HIGH		(1 << 4) +#define   DP_SYNC_HS_HIGH		(1 << 3) + +/** A fantasy */ +#define   DP_DETECTED			(1 << 2) + +/** The aux channel provides a way to talk to the + * signal sink for DDC etc. Max packet size supported + * is 20 bytes in each direction, hence the 5 fixed + * data registers + */ +#define DPB_AUX_CH_CTL			0x64110 +#define DPB_AUX_CH_DATA1		0x64114 +#define DPB_AUX_CH_DATA2		0x64118 +#define DPB_AUX_CH_DATA3		0x6411c +#define DPB_AUX_CH_DATA4		0x64120 +#define DPB_AUX_CH_DATA5		0x64124 + +#define DPC_AUX_CH_CTL			0x64210 +#define DPC_AUX_CH_DATA1		0x64214 +#define DPC_AUX_CH_DATA2		0x64218 +#define DPC_AUX_CH_DATA3		0x6421c +#define DPC_AUX_CH_DATA4		0x64220 +#define DPC_AUX_CH_DATA5		0x64224 + +#define   DP_AUX_CH_CTL_SEND_BUSY	    (1 << 31) +#define   DP_AUX_CH_CTL_DONE		    (1 << 30) +#define   DP_AUX_CH_CTL_INTERRUPT	    (1 << 29) +#define   DP_AUX_CH_CTL_TIME_OUT_ERROR	    (1 << 28) +#define   DP_AUX_CH_CTL_TIME_OUT_400us	    (0 << 26) +#define   DP_AUX_CH_CTL_TIME_OUT_600us	    (1 << 26) +#define   DP_AUX_CH_CTL_TIME_OUT_800us	    (2 << 26) +#define   DP_AUX_CH_CTL_TIME_OUT_1600us	    (3 << 26) +#define   DP_AUX_CH_CTL_TIME_OUT_MASK	    (3 << 26) +#define   DP_AUX_CH_CTL_RECEIVE_ERROR	    (1 << 25) +#define   DP_AUX_CH_CTL_MESSAGE_SIZE_MASK    (0x1f << 20) +#define   DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT   20 +#define   DP_AUX_CH_CTL_PRECHARGE_2US_MASK   (0xf << 16) +#define   DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT  16 +#define   DP_AUX_CH_CTL_AUX_AKSV_SELECT	    (1 << 15) +#define   DP_AUX_CH_CTL_MANCHESTER_TEST	    (1 << 14) +#define   DP_AUX_CH_CTL_SYNC_TEST	    (1 << 13) +#define   DP_AUX_CH_CTL_DEGLITCH_TEST	    (1 << 12) +#define   DP_AUX_CH_CTL_PRECHARGE_TEST	    (1 << 11) +#define   DP_AUX_CH_CTL_BIT_CLOCK_2X_MASK    (0x7ff) +#define   DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT   0 + +/* + * Computing GMCH M and N values for the Display Port link + * + * GMCH M/N = dot clock * bytes per pixel / ls_clk * # of lanes + * + * ls_clk (we assume) is the DP link clock (1.62 or 2.7 GHz) + * + * The GMCH value is used internally + * + * bytes_per_pixel is the number of bytes coming out of the plane, + * which is after the LUTs, so we want the bytes for our color format. + * For our current usage, this is always 3, one byte for R, G and B. + */ + +#define _PIPEA_GMCH_DATA_M			0x70050 +#define _PIPEB_GMCH_DATA_M			0x71050 + +/* Transfer unit size for display port - 1, default is 0x3f (for TU size 64) */ +#define   PIPE_GMCH_DATA_M_TU_SIZE_MASK		(0x3f << 25) +#define   PIPE_GMCH_DATA_M_TU_SIZE_SHIFT	25 + +#define   PIPE_GMCH_DATA_M_MASK			(0xffffff) + +#define _PIPEA_GMCH_DATA_N			0x70054 +#define _PIPEB_GMCH_DATA_N			0x71054 +#define   PIPE_GMCH_DATA_N_MASK			(0xffffff) + +/* + * Computing Link M and N values for the Display Port link + * + * Link M / N = pixel_clock / ls_clk + * + * (the DP spec calls pixel_clock the 'strm_clk') + * + * The Link value is transmitted in the Main Stream + * Attributes and VB-ID. + */ + +#define _PIPEA_DP_LINK_M				0x70060 +#define _PIPEB_DP_LINK_M				0x71060 +#define   PIPEA_DP_LINK_M_MASK			(0xffffff) + +#define _PIPEA_DP_LINK_N				0x70064 +#define _PIPEB_DP_LINK_N				0x71064 +#define   PIPEA_DP_LINK_N_MASK			(0xffffff) + +#define PIPE_GMCH_DATA_M(pipe) _PIPE(pipe, _PIPEA_GMCH_DATA_M, _PIPEB_GMCH_DATA_M) +#define PIPE_GMCH_DATA_N(pipe) _PIPE(pipe, _PIPEA_GMCH_DATA_N, _PIPEB_GMCH_DATA_N) +#define PIPE_DP_LINK_M(pipe) _PIPE(pipe, _PIPEA_DP_LINK_M, _PIPEB_DP_LINK_M) +#define PIPE_DP_LINK_N(pipe) _PIPE(pipe, _PIPEA_DP_LINK_N, _PIPEB_DP_LINK_N) + +#define   PIPE_BPC_MASK				(7 << 5) +#define   PIPE_8BPC				(0 << 5) +#define   PIPE_10BPC				(1 << 5) +#define   PIPE_6BPC				(2 << 5) + +#endif diff --git a/drivers/gpu/drm/gma500/psb_intel_sdvo.c b/drivers/gpu/drm/gma500/psb_intel_sdvo.c new file mode 100644 index 00000000000..deeb0829b12 --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_intel_sdvo.c @@ -0,0 +1,2643 @@ +/* + * Copyright 2006 Dave Airlie <airlied@linux.ie> + * Copyright © 2006-2007 Intel Corporation + *   Jesse Barnes <jesse.barnes@intel.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + */ +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> +#include "psb_intel_drv.h" +#include <drm/gma_drm.h> +#include "psb_drv.h" +#include "psb_intel_sdvo_regs.h" +#include "psb_intel_reg.h" + +#define SDVO_TMDS_MASK (SDVO_OUTPUT_TMDS0 | SDVO_OUTPUT_TMDS1) +#define SDVO_RGB_MASK  (SDVO_OUTPUT_RGB0 | SDVO_OUTPUT_RGB1) +#define SDVO_LVDS_MASK (SDVO_OUTPUT_LVDS0 | SDVO_OUTPUT_LVDS1) +#define SDVO_TV_MASK   (SDVO_OUTPUT_CVBS0 | SDVO_OUTPUT_SVID0) + +#define SDVO_OUTPUT_MASK (SDVO_TMDS_MASK | SDVO_RGB_MASK | SDVO_LVDS_MASK |\ +                         SDVO_TV_MASK) + +#define IS_TV(c)	(c->output_flag & SDVO_TV_MASK) +#define IS_TMDS(c)	(c->output_flag & SDVO_TMDS_MASK) +#define IS_LVDS(c)	(c->output_flag & SDVO_LVDS_MASK) +#define IS_TV_OR_LVDS(c) (c->output_flag & (SDVO_TV_MASK | SDVO_LVDS_MASK)) + + +static const char *tv_format_names[] = { +	"NTSC_M"   , "NTSC_J"  , "NTSC_443", +	"PAL_B"    , "PAL_D"   , "PAL_G"   , +	"PAL_H"    , "PAL_I"   , "PAL_M"   , +	"PAL_N"    , "PAL_NC"  , "PAL_60"  , +	"SECAM_B"  , "SECAM_D" , "SECAM_G" , +	"SECAM_K"  , "SECAM_K1", "SECAM_L" , +	"SECAM_60" +}; + +#define TV_FORMAT_NUM  (sizeof(tv_format_names) / sizeof(*tv_format_names)) + +struct psb_intel_sdvo { +	struct gma_encoder base; + +	struct i2c_adapter *i2c; +	u8 slave_addr; + +	struct i2c_adapter ddc; + +	/* Register for the SDVO device: SDVOB or SDVOC */ +	int sdvo_reg; + +	/* Active outputs controlled by this SDVO output */ +	uint16_t controlled_output; + +	/* +	 * Capabilities of the SDVO device returned by +	 * i830_sdvo_get_capabilities() +	 */ +	struct psb_intel_sdvo_caps caps; + +	/* Pixel clock limitations reported by the SDVO device, in kHz */ +	int pixel_clock_min, pixel_clock_max; + +	/* +	* For multiple function SDVO device, +	* this is for current attached outputs. +	*/ +	uint16_t attached_output; + +	/** +	 * This is used to select the color range of RBG outputs in HDMI mode. +	 * It is only valid when using TMDS encoding and 8 bit per color mode. +	 */ +	uint32_t color_range; + +	/** +	 * This is set if we're going to treat the device as TV-out. +	 * +	 * While we have these nice friendly flags for output types that ought +	 * to decide this for us, the S-Video output on our HDMI+S-Video card +	 * shows up as RGB1 (VGA). +	 */ +	bool is_tv; + +	/* This is for current tv format name */ +	int tv_format_index; + +	/** +	 * This is set if we treat the device as HDMI, instead of DVI. +	 */ +	bool is_hdmi; +	bool has_hdmi_monitor; +	bool has_hdmi_audio; + +	/** +	 * This is set if we detect output of sdvo device as LVDS and +	 * have a valid fixed mode to use with the panel. +	 */ +	bool is_lvds; + +	/** +	 * This is sdvo fixed pannel mode pointer +	 */ +	struct drm_display_mode *sdvo_lvds_fixed_mode; + +	/* DDC bus used by this SDVO encoder */ +	uint8_t ddc_bus; + +	/* Input timings for adjusted_mode */ +	struct psb_intel_sdvo_dtd input_dtd; + +	/* Saved SDVO output states */ +	uint32_t saveSDVO; /* Can be SDVOB or SDVOC depending on sdvo_reg */ +}; + +struct psb_intel_sdvo_connector { +	struct gma_connector base; + +	/* Mark the type of connector */ +	uint16_t output_flag; + +	int force_audio; + +	/* This contains all current supported TV format */ +	u8 tv_format_supported[TV_FORMAT_NUM]; +	int   format_supported_num; +	struct drm_property *tv_format; + +	/* add the property for the SDVO-TV */ +	struct drm_property *left; +	struct drm_property *right; +	struct drm_property *top; +	struct drm_property *bottom; +	struct drm_property *hpos; +	struct drm_property *vpos; +	struct drm_property *contrast; +	struct drm_property *saturation; +	struct drm_property *hue; +	struct drm_property *sharpness; +	struct drm_property *flicker_filter; +	struct drm_property *flicker_filter_adaptive; +	struct drm_property *flicker_filter_2d; +	struct drm_property *tv_chroma_filter; +	struct drm_property *tv_luma_filter; +	struct drm_property *dot_crawl; + +	/* add the property for the SDVO-TV/LVDS */ +	struct drm_property *brightness; + +	/* Add variable to record current setting for the above property */ +	u32	left_margin, right_margin, top_margin, bottom_margin; + +	/* this is to get the range of margin.*/ +	u32	max_hscan,  max_vscan; +	u32	max_hpos, cur_hpos; +	u32	max_vpos, cur_vpos; +	u32	cur_brightness, max_brightness; +	u32	cur_contrast,	max_contrast; +	u32	cur_saturation, max_saturation; +	u32	cur_hue,	max_hue; +	u32	cur_sharpness,	max_sharpness; +	u32	cur_flicker_filter,		max_flicker_filter; +	u32	cur_flicker_filter_adaptive,	max_flicker_filter_adaptive; +	u32	cur_flicker_filter_2d,		max_flicker_filter_2d; +	u32	cur_tv_chroma_filter,	max_tv_chroma_filter; +	u32	cur_tv_luma_filter,	max_tv_luma_filter; +	u32	cur_dot_crawl,	max_dot_crawl; +}; + +static struct psb_intel_sdvo *to_psb_intel_sdvo(struct drm_encoder *encoder) +{ +	return container_of(encoder, struct psb_intel_sdvo, base.base); +} + +static struct psb_intel_sdvo *intel_attached_sdvo(struct drm_connector *connector) +{ +	return container_of(gma_attached_encoder(connector), +			    struct psb_intel_sdvo, base); +} + +static struct psb_intel_sdvo_connector *to_psb_intel_sdvo_connector(struct drm_connector *connector) +{ +	return container_of(to_gma_connector(connector), struct psb_intel_sdvo_connector, base); +} + +static bool +psb_intel_sdvo_output_setup(struct psb_intel_sdvo *psb_intel_sdvo, uint16_t flags); +static bool +psb_intel_sdvo_tv_create_property(struct psb_intel_sdvo *psb_intel_sdvo, +			      struct psb_intel_sdvo_connector *psb_intel_sdvo_connector, +			      int type); +static bool +psb_intel_sdvo_create_enhance_property(struct psb_intel_sdvo *psb_intel_sdvo, +				   struct psb_intel_sdvo_connector *psb_intel_sdvo_connector); + +/** + * Writes the SDVOB or SDVOC with the given value, but always writes both + * SDVOB and SDVOC to work around apparent hardware issues (according to + * comments in the BIOS). + */ +static void psb_intel_sdvo_write_sdvox(struct psb_intel_sdvo *psb_intel_sdvo, u32 val) +{ +	struct drm_device *dev = psb_intel_sdvo->base.base.dev; +	u32 bval = val, cval = val; +	int i, j; +	int need_aux = IS_MRST(dev) ? 1 : 0; + +	for (j = 0; j <= need_aux; j++) { +		if (psb_intel_sdvo->sdvo_reg == SDVOB) +			cval = REG_READ_WITH_AUX(SDVOC, j); +		else +			bval = REG_READ_WITH_AUX(SDVOB, j); + +		/* +		* Write the registers twice for luck. Sometimes, +		* writing them only once doesn't appear to 'stick'. +		* The BIOS does this too. Yay, magic +		*/ +		for (i = 0; i < 2; i++) { +			REG_WRITE_WITH_AUX(SDVOB, bval, j); +			REG_READ_WITH_AUX(SDVOB, j); +			REG_WRITE_WITH_AUX(SDVOC, cval, j); +			REG_READ_WITH_AUX(SDVOC, j); +		} +	} +} + +static bool psb_intel_sdvo_read_byte(struct psb_intel_sdvo *psb_intel_sdvo, u8 addr, u8 *ch) +{ +	struct i2c_msg msgs[] = { +		{ +			.addr = psb_intel_sdvo->slave_addr, +			.flags = 0, +			.len = 1, +			.buf = &addr, +		}, +		{ +			.addr = psb_intel_sdvo->slave_addr, +			.flags = I2C_M_RD, +			.len = 1, +			.buf = ch, +		} +	}; +	int ret; + +	if ((ret = i2c_transfer(psb_intel_sdvo->i2c, msgs, 2)) == 2) +		return true; + +	DRM_DEBUG_KMS("i2c transfer returned %d\n", ret); +	return false; +} + +#define SDVO_CMD_NAME_ENTRY(cmd) {cmd, #cmd} +/** Mapping of command numbers to names, for debug output */ +static const struct _sdvo_cmd_name { +	u8 cmd; +	const char *name; +} sdvo_cmd_names[] = { +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_RESET), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_DEVICE_CAPS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_FIRMWARE_REV), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_TRAINED_INPUTS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_ACTIVE_OUTPUTS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_ACTIVE_OUTPUTS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_IN_OUT_MAP), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_IN_OUT_MAP), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_ATTACHED_DISPLAYS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HOT_PLUG_SUPPORT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_ACTIVE_HOT_PLUG), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_ACTIVE_HOT_PLUG), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_INTERRUPT_EVENT_SOURCE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_TARGET_INPUT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_TARGET_OUTPUT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_INPUT_TIMINGS_PART1), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_INPUT_TIMINGS_PART2), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_INPUT_TIMINGS_PART1), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_INPUT_TIMINGS_PART2), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_INPUT_TIMINGS_PART1), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_OUTPUT_TIMINGS_PART1), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_OUTPUT_TIMINGS_PART2), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_OUTPUT_TIMINGS_PART1), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_OUTPUT_TIMINGS_PART2), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_CREATE_PREFERRED_INPUT_TIMING), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_PREFERRED_INPUT_TIMING_PART1), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_PREFERRED_INPUT_TIMING_PART2), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_INPUT_PIXEL_CLOCK_RANGE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_OUTPUT_PIXEL_CLOCK_RANGE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SUPPORTED_CLOCK_RATE_MULTS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_CLOCK_RATE_MULT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_CLOCK_RATE_MULT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SUPPORTED_TV_FORMATS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_TV_FORMAT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_TV_FORMAT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SUPPORTED_POWER_STATES), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_POWER_STATE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_ENCODER_POWER_STATE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_DISPLAY_POWER_STATE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_CONTROL_BUS_SWITCH), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SDTV_RESOLUTION_SUPPORT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SCALED_HDTV_RESOLUTION_SUPPORT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SUPPORTED_ENHANCEMENTS), + +    /* Add the op code for SDVO enhancements */ +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_HPOS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HPOS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_HPOS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_VPOS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_VPOS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_VPOS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_SATURATION), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SATURATION), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_SATURATION), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_HUE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HUE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_HUE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_CONTRAST), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_CONTRAST), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_CONTRAST), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_BRIGHTNESS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_BRIGHTNESS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_BRIGHTNESS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_OVERSCAN_H), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_OVERSCAN_H), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_OVERSCAN_H), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_OVERSCAN_V), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_OVERSCAN_V), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_OVERSCAN_V), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_FLICKER_FILTER), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_FLICKER_FILTER), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_FLICKER_FILTER), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_FLICKER_FILTER_ADAPTIVE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_FLICKER_FILTER_ADAPTIVE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_FLICKER_FILTER_ADAPTIVE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_FLICKER_FILTER_2D), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_FLICKER_FILTER_2D), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_FLICKER_FILTER_2D), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_SHARPNESS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SHARPNESS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_SHARPNESS), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_DOT_CRAWL), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_DOT_CRAWL), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_TV_CHROMA_FILTER), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_TV_CHROMA_FILTER), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_TV_CHROMA_FILTER), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_MAX_TV_LUMA_FILTER), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_TV_LUMA_FILTER), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_TV_LUMA_FILTER), + +    /* HDMI op code */ +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SUPP_ENCODE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_ENCODE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_ENCODE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_PIXEL_REPLI), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_PIXEL_REPLI), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_COLORIMETRY_CAP), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_COLORIMETRY), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_COLORIMETRY), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_AUDIO_ENCRYPT_PREFER), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_AUDIO_STAT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_AUDIO_STAT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HBUF_INDEX), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_HBUF_INDEX), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HBUF_INFO), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HBUF_AV_SPLIT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_HBUF_AV_SPLIT), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HBUF_TXRATE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_HBUF_TXRATE), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_HBUF_DATA), +    SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HBUF_DATA), +}; + +#define IS_SDVOB(reg)	(reg == SDVOB) +#define SDVO_NAME(svdo) (IS_SDVOB((svdo)->sdvo_reg) ? "SDVOB" : "SDVOC") + +static void psb_intel_sdvo_debug_write(struct psb_intel_sdvo *psb_intel_sdvo, u8 cmd, +				   const void *args, int args_len) +{ +	int i; + +	DRM_DEBUG_KMS("%s: W: %02X ", +				SDVO_NAME(psb_intel_sdvo), cmd); +	for (i = 0; i < args_len; i++) +		DRM_DEBUG_KMS("%02X ", ((u8 *)args)[i]); +	for (; i < 8; i++) +		DRM_DEBUG_KMS("   "); +	for (i = 0; i < ARRAY_SIZE(sdvo_cmd_names); i++) { +		if (cmd == sdvo_cmd_names[i].cmd) { +			DRM_DEBUG_KMS("(%s)", sdvo_cmd_names[i].name); +			break; +		} +	} +	if (i == ARRAY_SIZE(sdvo_cmd_names)) +		DRM_DEBUG_KMS("(%02X)", cmd); +	DRM_DEBUG_KMS("\n"); +} + +static const char *cmd_status_names[] = { +	"Power on", +	"Success", +	"Not supported", +	"Invalid arg", +	"Pending", +	"Target not specified", +	"Scaling not supported" +}; + +static bool psb_intel_sdvo_write_cmd(struct psb_intel_sdvo *psb_intel_sdvo, u8 cmd, +				 const void *args, int args_len) +{ +	u8 buf[args_len*2 + 2], status; +	struct i2c_msg msgs[args_len + 3]; +	int i, ret; + +	psb_intel_sdvo_debug_write(psb_intel_sdvo, cmd, args, args_len); + +	for (i = 0; i < args_len; i++) { +		msgs[i].addr = psb_intel_sdvo->slave_addr; +		msgs[i].flags = 0; +		msgs[i].len = 2; +		msgs[i].buf = buf + 2 *i; +		buf[2*i + 0] = SDVO_I2C_ARG_0 - i; +		buf[2*i + 1] = ((u8*)args)[i]; +	} +	msgs[i].addr = psb_intel_sdvo->slave_addr; +	msgs[i].flags = 0; +	msgs[i].len = 2; +	msgs[i].buf = buf + 2*i; +	buf[2*i + 0] = SDVO_I2C_OPCODE; +	buf[2*i + 1] = cmd; + +	/* the following two are to read the response */ +	status = SDVO_I2C_CMD_STATUS; +	msgs[i+1].addr = psb_intel_sdvo->slave_addr; +	msgs[i+1].flags = 0; +	msgs[i+1].len = 1; +	msgs[i+1].buf = &status; + +	msgs[i+2].addr = psb_intel_sdvo->slave_addr; +	msgs[i+2].flags = I2C_M_RD; +	msgs[i+2].len = 1; +	msgs[i+2].buf = &status; + +	ret = i2c_transfer(psb_intel_sdvo->i2c, msgs, i+3); +	if (ret < 0) { +		DRM_DEBUG_KMS("I2c transfer returned %d\n", ret); +		return false; +	} +	if (ret != i+3) { +		/* failure in I2C transfer */ +		DRM_DEBUG_KMS("I2c transfer returned %d/%d\n", ret, i+3); +		return false; +	} + +	return true; +} + +static bool psb_intel_sdvo_read_response(struct psb_intel_sdvo *psb_intel_sdvo, +				     void *response, int response_len) +{ +	u8 retry = 5; +	u8 status; +	int i; + +	DRM_DEBUG_KMS("%s: R: ", SDVO_NAME(psb_intel_sdvo)); + +	/* +	 * The documentation states that all commands will be +	 * processed within 15µs, and that we need only poll +	 * the status byte a maximum of 3 times in order for the +	 * command to be complete. +	 * +	 * Check 5 times in case the hardware failed to read the docs. +	 */ +	if (!psb_intel_sdvo_read_byte(psb_intel_sdvo, +				  SDVO_I2C_CMD_STATUS, +				  &status)) +		goto log_fail; + +	while ((status == SDVO_CMD_STATUS_PENDING || +		status == SDVO_CMD_STATUS_TARGET_NOT_SPECIFIED) && retry--) { +		udelay(15); +		if (!psb_intel_sdvo_read_byte(psb_intel_sdvo, +					  SDVO_I2C_CMD_STATUS, +					  &status)) +			goto log_fail; +	} + +	if (status <= SDVO_CMD_STATUS_SCALING_NOT_SUPP) +		DRM_DEBUG_KMS("(%s)", cmd_status_names[status]); +	else +		DRM_DEBUG_KMS("(??? %d)", status); + +	if (status != SDVO_CMD_STATUS_SUCCESS) +		goto log_fail; + +	/* Read the command response */ +	for (i = 0; i < response_len; i++) { +		if (!psb_intel_sdvo_read_byte(psb_intel_sdvo, +					  SDVO_I2C_RETURN_0 + i, +					  &((u8 *)response)[i])) +			goto log_fail; +		DRM_DEBUG_KMS(" %02X", ((u8 *)response)[i]); +	} +	DRM_DEBUG_KMS("\n"); +	return true; + +log_fail: +	DRM_DEBUG_KMS("... failed\n"); +	return false; +} + +static int psb_intel_sdvo_get_pixel_multiplier(struct drm_display_mode *mode) +{ +	if (mode->clock >= 100000) +		return 1; +	else if (mode->clock >= 50000) +		return 2; +	else +		return 4; +} + +static bool psb_intel_sdvo_set_control_bus_switch(struct psb_intel_sdvo *psb_intel_sdvo, +					      u8 ddc_bus) +{ +	/* This must be the immediately preceding write before the i2c xfer */ +	return psb_intel_sdvo_write_cmd(psb_intel_sdvo, +				    SDVO_CMD_SET_CONTROL_BUS_SWITCH, +				    &ddc_bus, 1); +} + +static bool psb_intel_sdvo_set_value(struct psb_intel_sdvo *psb_intel_sdvo, u8 cmd, const void *data, int len) +{ +	if (!psb_intel_sdvo_write_cmd(psb_intel_sdvo, cmd, data, len)) +		return false; + +	return psb_intel_sdvo_read_response(psb_intel_sdvo, NULL, 0); +} + +static bool +psb_intel_sdvo_get_value(struct psb_intel_sdvo *psb_intel_sdvo, u8 cmd, void *value, int len) +{ +	if (!psb_intel_sdvo_write_cmd(psb_intel_sdvo, cmd, NULL, 0)) +		return false; + +	return psb_intel_sdvo_read_response(psb_intel_sdvo, value, len); +} + +static bool psb_intel_sdvo_set_target_input(struct psb_intel_sdvo *psb_intel_sdvo) +{ +	struct psb_intel_sdvo_set_target_input_args targets = {0}; +	return psb_intel_sdvo_set_value(psb_intel_sdvo, +				    SDVO_CMD_SET_TARGET_INPUT, +				    &targets, sizeof(targets)); +} + +/** + * Return whether each input is trained. + * + * This function is making an assumption about the layout of the response, + * which should be checked against the docs. + */ +static bool psb_intel_sdvo_get_trained_inputs(struct psb_intel_sdvo *psb_intel_sdvo, bool *input_1, bool *input_2) +{ +	struct psb_intel_sdvo_get_trained_inputs_response response; + +	BUILD_BUG_ON(sizeof(response) != 1); +	if (!psb_intel_sdvo_get_value(psb_intel_sdvo, SDVO_CMD_GET_TRAINED_INPUTS, +				  &response, sizeof(response))) +		return false; + +	*input_1 = response.input0_trained; +	*input_2 = response.input1_trained; +	return true; +} + +static bool psb_intel_sdvo_set_active_outputs(struct psb_intel_sdvo *psb_intel_sdvo, +					  u16 outputs) +{ +	return psb_intel_sdvo_set_value(psb_intel_sdvo, +				    SDVO_CMD_SET_ACTIVE_OUTPUTS, +				    &outputs, sizeof(outputs)); +} + +static bool psb_intel_sdvo_set_encoder_power_state(struct psb_intel_sdvo *psb_intel_sdvo, +					       int mode) +{ +	u8 state = SDVO_ENCODER_STATE_ON; + +	switch (mode) { +	case DRM_MODE_DPMS_ON: +		state = SDVO_ENCODER_STATE_ON; +		break; +	case DRM_MODE_DPMS_STANDBY: +		state = SDVO_ENCODER_STATE_STANDBY; +		break; +	case DRM_MODE_DPMS_SUSPEND: +		state = SDVO_ENCODER_STATE_SUSPEND; +		break; +	case DRM_MODE_DPMS_OFF: +		state = SDVO_ENCODER_STATE_OFF; +		break; +	} + +	return psb_intel_sdvo_set_value(psb_intel_sdvo, +				    SDVO_CMD_SET_ENCODER_POWER_STATE, &state, sizeof(state)); +} + +static bool psb_intel_sdvo_get_input_pixel_clock_range(struct psb_intel_sdvo *psb_intel_sdvo, +						   int *clock_min, +						   int *clock_max) +{ +	struct psb_intel_sdvo_pixel_clock_range clocks; + +	BUILD_BUG_ON(sizeof(clocks) != 4); +	if (!psb_intel_sdvo_get_value(psb_intel_sdvo, +				  SDVO_CMD_GET_INPUT_PIXEL_CLOCK_RANGE, +				  &clocks, sizeof(clocks))) +		return false; + +	/* Convert the values from units of 10 kHz to kHz. */ +	*clock_min = clocks.min * 10; +	*clock_max = clocks.max * 10; +	return true; +} + +static bool psb_intel_sdvo_set_target_output(struct psb_intel_sdvo *psb_intel_sdvo, +					 u16 outputs) +{ +	return psb_intel_sdvo_set_value(psb_intel_sdvo, +				    SDVO_CMD_SET_TARGET_OUTPUT, +				    &outputs, sizeof(outputs)); +} + +static bool psb_intel_sdvo_set_timing(struct psb_intel_sdvo *psb_intel_sdvo, u8 cmd, +				  struct psb_intel_sdvo_dtd *dtd) +{ +	return psb_intel_sdvo_set_value(psb_intel_sdvo, cmd, &dtd->part1, sizeof(dtd->part1)) && +		psb_intel_sdvo_set_value(psb_intel_sdvo, cmd + 1, &dtd->part2, sizeof(dtd->part2)); +} + +static bool psb_intel_sdvo_set_input_timing(struct psb_intel_sdvo *psb_intel_sdvo, +					 struct psb_intel_sdvo_dtd *dtd) +{ +	return psb_intel_sdvo_set_timing(psb_intel_sdvo, +				     SDVO_CMD_SET_INPUT_TIMINGS_PART1, dtd); +} + +static bool psb_intel_sdvo_set_output_timing(struct psb_intel_sdvo *psb_intel_sdvo, +					 struct psb_intel_sdvo_dtd *dtd) +{ +	return psb_intel_sdvo_set_timing(psb_intel_sdvo, +				     SDVO_CMD_SET_OUTPUT_TIMINGS_PART1, dtd); +} + +static bool +psb_intel_sdvo_create_preferred_input_timing(struct psb_intel_sdvo *psb_intel_sdvo, +					 uint16_t clock, +					 uint16_t width, +					 uint16_t height) +{ +	struct psb_intel_sdvo_preferred_input_timing_args args; + +	memset(&args, 0, sizeof(args)); +	args.clock = clock; +	args.width = width; +	args.height = height; +	args.interlace = 0; + +	if (psb_intel_sdvo->is_lvds && +	   (psb_intel_sdvo->sdvo_lvds_fixed_mode->hdisplay != width || +	    psb_intel_sdvo->sdvo_lvds_fixed_mode->vdisplay != height)) +		args.scaled = 1; + +	return psb_intel_sdvo_set_value(psb_intel_sdvo, +				    SDVO_CMD_CREATE_PREFERRED_INPUT_TIMING, +				    &args, sizeof(args)); +} + +static bool psb_intel_sdvo_get_preferred_input_timing(struct psb_intel_sdvo *psb_intel_sdvo, +						  struct psb_intel_sdvo_dtd *dtd) +{ +	BUILD_BUG_ON(sizeof(dtd->part1) != 8); +	BUILD_BUG_ON(sizeof(dtd->part2) != 8); +	return psb_intel_sdvo_get_value(psb_intel_sdvo, SDVO_CMD_GET_PREFERRED_INPUT_TIMING_PART1, +				    &dtd->part1, sizeof(dtd->part1)) && +		psb_intel_sdvo_get_value(psb_intel_sdvo, SDVO_CMD_GET_PREFERRED_INPUT_TIMING_PART2, +				     &dtd->part2, sizeof(dtd->part2)); +} + +static bool psb_intel_sdvo_set_clock_rate_mult(struct psb_intel_sdvo *psb_intel_sdvo, u8 val) +{ +	return psb_intel_sdvo_set_value(psb_intel_sdvo, SDVO_CMD_SET_CLOCK_RATE_MULT, &val, 1); +} + +static void psb_intel_sdvo_get_dtd_from_mode(struct psb_intel_sdvo_dtd *dtd, +					 const struct drm_display_mode *mode) +{ +	uint16_t width, height; +	uint16_t h_blank_len, h_sync_len, v_blank_len, v_sync_len; +	uint16_t h_sync_offset, v_sync_offset; + +	width = mode->crtc_hdisplay; +	height = mode->crtc_vdisplay; + +	/* do some mode translations */ +	h_blank_len = mode->crtc_hblank_end - mode->crtc_hblank_start; +	h_sync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; + +	v_blank_len = mode->crtc_vblank_end - mode->crtc_vblank_start; +	v_sync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; + +	h_sync_offset = mode->crtc_hsync_start - mode->crtc_hblank_start; +	v_sync_offset = mode->crtc_vsync_start - mode->crtc_vblank_start; + +	dtd->part1.clock = mode->clock / 10; +	dtd->part1.h_active = width & 0xff; +	dtd->part1.h_blank = h_blank_len & 0xff; +	dtd->part1.h_high = (((width >> 8) & 0xf) << 4) | +		((h_blank_len >> 8) & 0xf); +	dtd->part1.v_active = height & 0xff; +	dtd->part1.v_blank = v_blank_len & 0xff; +	dtd->part1.v_high = (((height >> 8) & 0xf) << 4) | +		((v_blank_len >> 8) & 0xf); + +	dtd->part2.h_sync_off = h_sync_offset & 0xff; +	dtd->part2.h_sync_width = h_sync_len & 0xff; +	dtd->part2.v_sync_off_width = (v_sync_offset & 0xf) << 4 | +		(v_sync_len & 0xf); +	dtd->part2.sync_off_width_high = ((h_sync_offset & 0x300) >> 2) | +		((h_sync_len & 0x300) >> 4) | ((v_sync_offset & 0x30) >> 2) | +		((v_sync_len & 0x30) >> 4); + +	dtd->part2.dtd_flags = 0x18; +	if (mode->flags & DRM_MODE_FLAG_PHSYNC) +		dtd->part2.dtd_flags |= 0x2; +	if (mode->flags & DRM_MODE_FLAG_PVSYNC) +		dtd->part2.dtd_flags |= 0x4; + +	dtd->part2.sdvo_flags = 0; +	dtd->part2.v_sync_off_high = v_sync_offset & 0xc0; +	dtd->part2.reserved = 0; +} + +static void psb_intel_sdvo_get_mode_from_dtd(struct drm_display_mode * mode, +					 const struct psb_intel_sdvo_dtd *dtd) +{ +	mode->hdisplay = dtd->part1.h_active; +	mode->hdisplay += ((dtd->part1.h_high >> 4) & 0x0f) << 8; +	mode->hsync_start = mode->hdisplay + dtd->part2.h_sync_off; +	mode->hsync_start += (dtd->part2.sync_off_width_high & 0xc0) << 2; +	mode->hsync_end = mode->hsync_start + dtd->part2.h_sync_width; +	mode->hsync_end += (dtd->part2.sync_off_width_high & 0x30) << 4; +	mode->htotal = mode->hdisplay + dtd->part1.h_blank; +	mode->htotal += (dtd->part1.h_high & 0xf) << 8; + +	mode->vdisplay = dtd->part1.v_active; +	mode->vdisplay += ((dtd->part1.v_high >> 4) & 0x0f) << 8; +	mode->vsync_start = mode->vdisplay; +	mode->vsync_start += (dtd->part2.v_sync_off_width >> 4) & 0xf; +	mode->vsync_start += (dtd->part2.sync_off_width_high & 0x0c) << 2; +	mode->vsync_start += dtd->part2.v_sync_off_high & 0xc0; +	mode->vsync_end = mode->vsync_start + +		(dtd->part2.v_sync_off_width & 0xf); +	mode->vsync_end += (dtd->part2.sync_off_width_high & 0x3) << 4; +	mode->vtotal = mode->vdisplay + dtd->part1.v_blank; +	mode->vtotal += (dtd->part1.v_high & 0xf) << 8; + +	mode->clock = dtd->part1.clock * 10; + +	mode->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); +	if (dtd->part2.dtd_flags & 0x2) +		mode->flags |= DRM_MODE_FLAG_PHSYNC; +	if (dtd->part2.dtd_flags & 0x4) +		mode->flags |= DRM_MODE_FLAG_PVSYNC; +} + +static bool psb_intel_sdvo_check_supp_encode(struct psb_intel_sdvo *psb_intel_sdvo) +{ +	struct psb_intel_sdvo_encode encode; + +	BUILD_BUG_ON(sizeof(encode) != 2); +	return psb_intel_sdvo_get_value(psb_intel_sdvo, +				  SDVO_CMD_GET_SUPP_ENCODE, +				  &encode, sizeof(encode)); +} + +static bool psb_intel_sdvo_set_encode(struct psb_intel_sdvo *psb_intel_sdvo, +				  uint8_t mode) +{ +	return psb_intel_sdvo_set_value(psb_intel_sdvo, SDVO_CMD_SET_ENCODE, &mode, 1); +} + +static bool psb_intel_sdvo_set_colorimetry(struct psb_intel_sdvo *psb_intel_sdvo, +				       uint8_t mode) +{ +	return psb_intel_sdvo_set_value(psb_intel_sdvo, SDVO_CMD_SET_COLORIMETRY, &mode, 1); +} + +#if 0 +static void psb_intel_sdvo_dump_hdmi_buf(struct psb_intel_sdvo *psb_intel_sdvo) +{ +	int i, j; +	uint8_t set_buf_index[2]; +	uint8_t av_split; +	uint8_t buf_size; +	uint8_t buf[48]; +	uint8_t *pos; + +	psb_intel_sdvo_get_value(encoder, SDVO_CMD_GET_HBUF_AV_SPLIT, &av_split, 1); + +	for (i = 0; i <= av_split; i++) { +		set_buf_index[0] = i; set_buf_index[1] = 0; +		psb_intel_sdvo_write_cmd(encoder, SDVO_CMD_SET_HBUF_INDEX, +				     set_buf_index, 2); +		psb_intel_sdvo_write_cmd(encoder, SDVO_CMD_GET_HBUF_INFO, NULL, 0); +		psb_intel_sdvo_read_response(encoder, &buf_size, 1); + +		pos = buf; +		for (j = 0; j <= buf_size; j += 8) { +			psb_intel_sdvo_write_cmd(encoder, SDVO_CMD_GET_HBUF_DATA, +					     NULL, 0); +			psb_intel_sdvo_read_response(encoder, pos, 8); +			pos += 8; +		} +	} +} +#endif + +static bool psb_intel_sdvo_set_avi_infoframe(struct psb_intel_sdvo *psb_intel_sdvo) +{ +	DRM_INFO("HDMI is not supported yet"); + +	return false; +#if 0 +	struct dip_infoframe avi_if = { +		.type = DIP_TYPE_AVI, +		.ver = DIP_VERSION_AVI, +		.len = DIP_LEN_AVI, +	}; +	uint8_t tx_rate = SDVO_HBUF_TX_VSYNC; +	uint8_t set_buf_index[2] = { 1, 0 }; +	uint64_t *data = (uint64_t *)&avi_if; +	unsigned i; + +	intel_dip_infoframe_csum(&avi_if); + +	if (!psb_intel_sdvo_set_value(psb_intel_sdvo, +				  SDVO_CMD_SET_HBUF_INDEX, +				  set_buf_index, 2)) +		return false; + +	for (i = 0; i < sizeof(avi_if); i += 8) { +		if (!psb_intel_sdvo_set_value(psb_intel_sdvo, +					  SDVO_CMD_SET_HBUF_DATA, +					  data, 8)) +			return false; +		data++; +	} + +	return psb_intel_sdvo_set_value(psb_intel_sdvo, +				    SDVO_CMD_SET_HBUF_TXRATE, +				    &tx_rate, 1); +#endif +} + +static bool psb_intel_sdvo_set_tv_format(struct psb_intel_sdvo *psb_intel_sdvo) +{ +	struct psb_intel_sdvo_tv_format format; +	uint32_t format_map; + +	format_map = 1 << psb_intel_sdvo->tv_format_index; +	memset(&format, 0, sizeof(format)); +	memcpy(&format, &format_map, min(sizeof(format), sizeof(format_map))); + +	BUILD_BUG_ON(sizeof(format) != 6); +	return psb_intel_sdvo_set_value(psb_intel_sdvo, +				    SDVO_CMD_SET_TV_FORMAT, +				    &format, sizeof(format)); +} + +static bool +psb_intel_sdvo_set_output_timings_from_mode(struct psb_intel_sdvo *psb_intel_sdvo, +					const struct drm_display_mode *mode) +{ +	struct psb_intel_sdvo_dtd output_dtd; + +	if (!psb_intel_sdvo_set_target_output(psb_intel_sdvo, +					  psb_intel_sdvo->attached_output)) +		return false; + +	psb_intel_sdvo_get_dtd_from_mode(&output_dtd, mode); +	if (!psb_intel_sdvo_set_output_timing(psb_intel_sdvo, &output_dtd)) +		return false; + +	return true; +} + +static bool +psb_intel_sdvo_set_input_timings_for_mode(struct psb_intel_sdvo *psb_intel_sdvo, +					const struct drm_display_mode *mode, +					struct drm_display_mode *adjusted_mode) +{ +	/* Reset the input timing to the screen. Assume always input 0. */ +	if (!psb_intel_sdvo_set_target_input(psb_intel_sdvo)) +		return false; + +	if (!psb_intel_sdvo_create_preferred_input_timing(psb_intel_sdvo, +						      mode->clock / 10, +						      mode->hdisplay, +						      mode->vdisplay)) +		return false; + +	if (!psb_intel_sdvo_get_preferred_input_timing(psb_intel_sdvo, +						   &psb_intel_sdvo->input_dtd)) +		return false; + +	psb_intel_sdvo_get_mode_from_dtd(adjusted_mode, &psb_intel_sdvo->input_dtd); + +	drm_mode_set_crtcinfo(adjusted_mode, 0); +	return true; +} + +static bool psb_intel_sdvo_mode_fixup(struct drm_encoder *encoder, +				  const struct drm_display_mode *mode, +				  struct drm_display_mode *adjusted_mode) +{ +	struct psb_intel_sdvo *psb_intel_sdvo = to_psb_intel_sdvo(encoder); +	int multiplier; + +	/* We need to construct preferred input timings based on our +	 * output timings.  To do that, we have to set the output +	 * timings, even though this isn't really the right place in +	 * the sequence to do it. Oh well. +	 */ +	if (psb_intel_sdvo->is_tv) { +		if (!psb_intel_sdvo_set_output_timings_from_mode(psb_intel_sdvo, mode)) +			return false; + +		(void) psb_intel_sdvo_set_input_timings_for_mode(psb_intel_sdvo, +							     mode, +							     adjusted_mode); +	} else if (psb_intel_sdvo->is_lvds) { +		if (!psb_intel_sdvo_set_output_timings_from_mode(psb_intel_sdvo, +							     psb_intel_sdvo->sdvo_lvds_fixed_mode)) +			return false; + +		(void) psb_intel_sdvo_set_input_timings_for_mode(psb_intel_sdvo, +							     mode, +							     adjusted_mode); +	} + +	/* Make the CRTC code factor in the SDVO pixel multiplier.  The +	 * SDVO device will factor out the multiplier during mode_set. +	 */ +	multiplier = psb_intel_sdvo_get_pixel_multiplier(adjusted_mode); +	psb_intel_mode_set_pixel_multiplier(adjusted_mode, multiplier); + +	return true; +} + +static void psb_intel_sdvo_mode_set(struct drm_encoder *encoder, +				struct drm_display_mode *mode, +				struct drm_display_mode *adjusted_mode) +{ +	struct drm_device *dev = encoder->dev; +	struct drm_crtc *crtc = encoder->crtc; +	struct gma_crtc *gma_crtc = to_gma_crtc(crtc); +	struct psb_intel_sdvo *psb_intel_sdvo = to_psb_intel_sdvo(encoder); +	u32 sdvox; +	struct psb_intel_sdvo_in_out_map in_out; +	struct psb_intel_sdvo_dtd input_dtd; +	int pixel_multiplier = psb_intel_mode_get_pixel_multiplier(adjusted_mode); +	int rate; +	int need_aux = IS_MRST(dev) ? 1 : 0; + +	if (!mode) +		return; + +	/* First, set the input mapping for the first input to our controlled +	 * output. This is only correct if we're a single-input device, in +	 * which case the first input is the output from the appropriate SDVO +	 * channel on the motherboard.  In a two-input device, the first input +	 * will be SDVOB and the second SDVOC. +	 */ +	in_out.in0 = psb_intel_sdvo->attached_output; +	in_out.in1 = 0; + +	psb_intel_sdvo_set_value(psb_intel_sdvo, +			     SDVO_CMD_SET_IN_OUT_MAP, +			     &in_out, sizeof(in_out)); + +	/* Set the output timings to the screen */ +	if (!psb_intel_sdvo_set_target_output(psb_intel_sdvo, +					  psb_intel_sdvo->attached_output)) +		return; + +	/* We have tried to get input timing in mode_fixup, and filled into +	 * adjusted_mode. +	 */ +	if (psb_intel_sdvo->is_tv || psb_intel_sdvo->is_lvds) { +		input_dtd = psb_intel_sdvo->input_dtd; +	} else { +		/* Set the output timing to the screen */ +		if (!psb_intel_sdvo_set_target_output(psb_intel_sdvo, +						  psb_intel_sdvo->attached_output)) +			return; + +		psb_intel_sdvo_get_dtd_from_mode(&input_dtd, adjusted_mode); +		(void) psb_intel_sdvo_set_output_timing(psb_intel_sdvo, &input_dtd); +	} + +	/* Set the input timing to the screen. Assume always input 0. */ +	if (!psb_intel_sdvo_set_target_input(psb_intel_sdvo)) +		return; + +	if (psb_intel_sdvo->has_hdmi_monitor) { +		psb_intel_sdvo_set_encode(psb_intel_sdvo, SDVO_ENCODE_HDMI); +		psb_intel_sdvo_set_colorimetry(psb_intel_sdvo, +					   SDVO_COLORIMETRY_RGB256); +		psb_intel_sdvo_set_avi_infoframe(psb_intel_sdvo); +	} else +		psb_intel_sdvo_set_encode(psb_intel_sdvo, SDVO_ENCODE_DVI); + +	if (psb_intel_sdvo->is_tv && +	    !psb_intel_sdvo_set_tv_format(psb_intel_sdvo)) +		return; + +	(void) psb_intel_sdvo_set_input_timing(psb_intel_sdvo, &input_dtd); + +	switch (pixel_multiplier) { +	default: +	case 1: rate = SDVO_CLOCK_RATE_MULT_1X; break; +	case 2: rate = SDVO_CLOCK_RATE_MULT_2X; break; +	case 4: rate = SDVO_CLOCK_RATE_MULT_4X; break; +	} +	if (!psb_intel_sdvo_set_clock_rate_mult(psb_intel_sdvo, rate)) +		return; + +	/* Set the SDVO control regs. */ +	if (need_aux) +		sdvox = REG_READ_AUX(psb_intel_sdvo->sdvo_reg); +	else +		sdvox = REG_READ(psb_intel_sdvo->sdvo_reg); + +	switch (psb_intel_sdvo->sdvo_reg) { +	case SDVOB: +		sdvox &= SDVOB_PRESERVE_MASK; +		break; +	case SDVOC: +		sdvox &= SDVOC_PRESERVE_MASK; +		break; +	} +	sdvox |= (9 << 19) | SDVO_BORDER_ENABLE; + +	if (gma_crtc->pipe == 1) +		sdvox |= SDVO_PIPE_B_SELECT; +	if (psb_intel_sdvo->has_hdmi_audio) +		sdvox |= SDVO_AUDIO_ENABLE; + +	/* FIXME: Check if this is needed for PSB +	sdvox |= (pixel_multiplier - 1) << SDVO_PORT_MULTIPLY_SHIFT; +	*/ + +	if (input_dtd.part2.sdvo_flags & SDVO_NEED_TO_STALL) +		sdvox |= SDVO_STALL_SELECT; +	psb_intel_sdvo_write_sdvox(psb_intel_sdvo, sdvox); +} + +static void psb_intel_sdvo_dpms(struct drm_encoder *encoder, int mode) +{ +	struct drm_device *dev = encoder->dev; +	struct psb_intel_sdvo *psb_intel_sdvo = to_psb_intel_sdvo(encoder); +	u32 temp; +	int i; +	int need_aux = IS_MRST(dev) ? 1 : 0; + +	switch (mode) { +	case DRM_MODE_DPMS_ON: +		DRM_DEBUG("DPMS_ON"); +		break; +	case DRM_MODE_DPMS_OFF: +		DRM_DEBUG("DPMS_OFF"); +		break; +	default: +		DRM_DEBUG("DPMS: %d", mode); +	} + +	if (mode != DRM_MODE_DPMS_ON) { +		psb_intel_sdvo_set_active_outputs(psb_intel_sdvo, 0); +		if (0) +			psb_intel_sdvo_set_encoder_power_state(psb_intel_sdvo, mode); + +		if (mode == DRM_MODE_DPMS_OFF) { +			if (need_aux) +				temp = REG_READ_AUX(psb_intel_sdvo->sdvo_reg); +			else +				temp = REG_READ(psb_intel_sdvo->sdvo_reg); + +			if ((temp & SDVO_ENABLE) != 0) { +				psb_intel_sdvo_write_sdvox(psb_intel_sdvo, temp & ~SDVO_ENABLE); +			} +		} +	} else { +		bool input1, input2; +		u8 status; + +		if (need_aux) +			temp = REG_READ_AUX(psb_intel_sdvo->sdvo_reg); +		else +			temp = REG_READ(psb_intel_sdvo->sdvo_reg); + +		if ((temp & SDVO_ENABLE) == 0) +			psb_intel_sdvo_write_sdvox(psb_intel_sdvo, temp | SDVO_ENABLE); + +		for (i = 0; i < 2; i++) +			gma_wait_for_vblank(dev); + +		status = psb_intel_sdvo_get_trained_inputs(psb_intel_sdvo, &input1, &input2); +		/* Warn if the device reported failure to sync. +		 * A lot of SDVO devices fail to notify of sync, but it's +		 * a given it the status is a success, we succeeded. +		 */ +		if (status == SDVO_CMD_STATUS_SUCCESS && !input1) { +			DRM_DEBUG_KMS("First %s output reported failure to " +					"sync\n", SDVO_NAME(psb_intel_sdvo)); +		} + +		if (0) +			psb_intel_sdvo_set_encoder_power_state(psb_intel_sdvo, mode); +		psb_intel_sdvo_set_active_outputs(psb_intel_sdvo, psb_intel_sdvo->attached_output); +	} +	return; +} + +static int psb_intel_sdvo_mode_valid(struct drm_connector *connector, +				 struct drm_display_mode *mode) +{ +	struct psb_intel_sdvo *psb_intel_sdvo = intel_attached_sdvo(connector); + +	if (mode->flags & DRM_MODE_FLAG_DBLSCAN) +		return MODE_NO_DBLESCAN; + +	if (psb_intel_sdvo->pixel_clock_min > mode->clock) +		return MODE_CLOCK_LOW; + +	if (psb_intel_sdvo->pixel_clock_max < mode->clock) +		return MODE_CLOCK_HIGH; + +	if (psb_intel_sdvo->is_lvds) { +		if (mode->hdisplay > psb_intel_sdvo->sdvo_lvds_fixed_mode->hdisplay) +			return MODE_PANEL; + +		if (mode->vdisplay > psb_intel_sdvo->sdvo_lvds_fixed_mode->vdisplay) +			return MODE_PANEL; +	} + +	return MODE_OK; +} + +static bool psb_intel_sdvo_get_capabilities(struct psb_intel_sdvo *psb_intel_sdvo, struct psb_intel_sdvo_caps *caps) +{ +	BUILD_BUG_ON(sizeof(*caps) != 8); +	if (!psb_intel_sdvo_get_value(psb_intel_sdvo, +				  SDVO_CMD_GET_DEVICE_CAPS, +				  caps, sizeof(*caps))) +		return false; + +	DRM_DEBUG_KMS("SDVO capabilities:\n" +		      "  vendor_id: %d\n" +		      "  device_id: %d\n" +		      "  device_rev_id: %d\n" +		      "  sdvo_version_major: %d\n" +		      "  sdvo_version_minor: %d\n" +		      "  sdvo_inputs_mask: %d\n" +		      "  smooth_scaling: %d\n" +		      "  sharp_scaling: %d\n" +		      "  up_scaling: %d\n" +		      "  down_scaling: %d\n" +		      "  stall_support: %d\n" +		      "  output_flags: %d\n", +		      caps->vendor_id, +		      caps->device_id, +		      caps->device_rev_id, +		      caps->sdvo_version_major, +		      caps->sdvo_version_minor, +		      caps->sdvo_inputs_mask, +		      caps->smooth_scaling, +		      caps->sharp_scaling, +		      caps->up_scaling, +		      caps->down_scaling, +		      caps->stall_support, +		      caps->output_flags); + +	return true; +} + +/* No use! */ +#if 0 +struct drm_connector* psb_intel_sdvo_find(struct drm_device *dev, int sdvoB) +{ +	struct drm_connector *connector = NULL; +	struct psb_intel_sdvo *iout = NULL; +	struct psb_intel_sdvo *sdvo; + +	/* find the sdvo connector */ +	list_for_each_entry(connector, &dev->mode_config.connector_list, head) { +		iout = to_psb_intel_sdvo(connector); + +		if (iout->type != INTEL_OUTPUT_SDVO) +			continue; + +		sdvo = iout->dev_priv; + +		if (sdvo->sdvo_reg == SDVOB && sdvoB) +			return connector; + +		if (sdvo->sdvo_reg == SDVOC && !sdvoB) +			return connector; + +	} + +	return NULL; +} + +int psb_intel_sdvo_supports_hotplug(struct drm_connector *connector) +{ +	u8 response[2]; +	u8 status; +	struct psb_intel_sdvo *psb_intel_sdvo; +	DRM_DEBUG_KMS("\n"); + +	if (!connector) +		return 0; + +	psb_intel_sdvo = to_psb_intel_sdvo(connector); + +	return psb_intel_sdvo_get_value(psb_intel_sdvo, SDVO_CMD_GET_HOT_PLUG_SUPPORT, +				    &response, 2) && response[0]; +} + +void psb_intel_sdvo_set_hotplug(struct drm_connector *connector, int on) +{ +	u8 response[2]; +	u8 status; +	struct psb_intel_sdvo *psb_intel_sdvo = to_psb_intel_sdvo(connector); + +	psb_intel_sdvo_write_cmd(psb_intel_sdvo, SDVO_CMD_GET_ACTIVE_HOT_PLUG, NULL, 0); +	psb_intel_sdvo_read_response(psb_intel_sdvo, &response, 2); + +	if (on) { +		psb_intel_sdvo_write_cmd(psb_intel_sdvo, SDVO_CMD_GET_HOT_PLUG_SUPPORT, NULL, 0); +		status = psb_intel_sdvo_read_response(psb_intel_sdvo, &response, 2); + +		psb_intel_sdvo_write_cmd(psb_intel_sdvo, SDVO_CMD_SET_ACTIVE_HOT_PLUG, &response, 2); +	} else { +		response[0] = 0; +		response[1] = 0; +		psb_intel_sdvo_write_cmd(psb_intel_sdvo, SDVO_CMD_SET_ACTIVE_HOT_PLUG, &response, 2); +	} + +	psb_intel_sdvo_write_cmd(psb_intel_sdvo, SDVO_CMD_GET_ACTIVE_HOT_PLUG, NULL, 0); +	psb_intel_sdvo_read_response(psb_intel_sdvo, &response, 2); +} +#endif + +static bool +psb_intel_sdvo_multifunc_encoder(struct psb_intel_sdvo *psb_intel_sdvo) +{ +	/* Is there more than one type of output? */ +	int caps = psb_intel_sdvo->caps.output_flags & 0xf; +	return caps & -caps; +} + +static struct edid * +psb_intel_sdvo_get_edid(struct drm_connector *connector) +{ +	struct psb_intel_sdvo *sdvo = intel_attached_sdvo(connector); +	return drm_get_edid(connector, &sdvo->ddc); +} + +/* Mac mini hack -- use the same DDC as the analog connector */ +static struct edid * +psb_intel_sdvo_get_analog_edid(struct drm_connector *connector) +{ +	struct drm_psb_private *dev_priv = connector->dev->dev_private; + +	return drm_get_edid(connector, +			    &dev_priv->gmbus[dev_priv->crt_ddc_pin].adapter); +} + +static enum drm_connector_status +psb_intel_sdvo_hdmi_sink_detect(struct drm_connector *connector) +{ +	struct psb_intel_sdvo *psb_intel_sdvo = intel_attached_sdvo(connector); +	enum drm_connector_status status; +	struct edid *edid; + +	edid = psb_intel_sdvo_get_edid(connector); + +	if (edid == NULL && psb_intel_sdvo_multifunc_encoder(psb_intel_sdvo)) { +		u8 ddc, saved_ddc = psb_intel_sdvo->ddc_bus; + +		/* +		 * Don't use the 1 as the argument of DDC bus switch to get +		 * the EDID. It is used for SDVO SPD ROM. +		 */ +		for (ddc = psb_intel_sdvo->ddc_bus >> 1; ddc > 1; ddc >>= 1) { +			psb_intel_sdvo->ddc_bus = ddc; +			edid = psb_intel_sdvo_get_edid(connector); +			if (edid) +				break; +		} +		/* +		 * If we found the EDID on the other bus, +		 * assume that is the correct DDC bus. +		 */ +		if (edid == NULL) +			psb_intel_sdvo->ddc_bus = saved_ddc; +	} + +	/* +	 * When there is no edid and no monitor is connected with VGA +	 * port, try to use the CRT ddc to read the EDID for DVI-connector. +	 */ +	if (edid == NULL) +		edid = psb_intel_sdvo_get_analog_edid(connector); + +	status = connector_status_unknown; +	if (edid != NULL) { +		/* DDC bus is shared, match EDID to connector type */ +		if (edid->input & DRM_EDID_INPUT_DIGITAL) { +			status = connector_status_connected; +			if (psb_intel_sdvo->is_hdmi) { +				psb_intel_sdvo->has_hdmi_monitor = drm_detect_hdmi_monitor(edid); +				psb_intel_sdvo->has_hdmi_audio = drm_detect_monitor_audio(edid); +			} +		} else +			status = connector_status_disconnected; +		kfree(edid); +	} + +	if (status == connector_status_connected) { +		struct psb_intel_sdvo_connector *psb_intel_sdvo_connector = to_psb_intel_sdvo_connector(connector); +		if (psb_intel_sdvo_connector->force_audio) +			psb_intel_sdvo->has_hdmi_audio = psb_intel_sdvo_connector->force_audio > 0; +	} + +	return status; +} + +static enum drm_connector_status +psb_intel_sdvo_detect(struct drm_connector *connector, bool force) +{ +	uint16_t response; +	struct psb_intel_sdvo *psb_intel_sdvo = intel_attached_sdvo(connector); +	struct psb_intel_sdvo_connector *psb_intel_sdvo_connector = to_psb_intel_sdvo_connector(connector); +	enum drm_connector_status ret; + +	if (!psb_intel_sdvo_write_cmd(psb_intel_sdvo, +				  SDVO_CMD_GET_ATTACHED_DISPLAYS, NULL, 0)) +		return connector_status_unknown; + +	/* add 30ms delay when the output type might be TV */ +	if (psb_intel_sdvo->caps.output_flags & +	    (SDVO_OUTPUT_SVID0 | SDVO_OUTPUT_CVBS0)) +		mdelay(30); + +	if (!psb_intel_sdvo_read_response(psb_intel_sdvo, &response, 2)) +		return connector_status_unknown; + +	DRM_DEBUG_KMS("SDVO response %d %d [%x]\n", +		      response & 0xff, response >> 8, +		      psb_intel_sdvo_connector->output_flag); + +	if (response == 0) +		return connector_status_disconnected; + +	psb_intel_sdvo->attached_output = response; + +	psb_intel_sdvo->has_hdmi_monitor = false; +	psb_intel_sdvo->has_hdmi_audio = false; + +	if ((psb_intel_sdvo_connector->output_flag & response) == 0) +		ret = connector_status_disconnected; +	else if (IS_TMDS(psb_intel_sdvo_connector)) +		ret = psb_intel_sdvo_hdmi_sink_detect(connector); +	else { +		struct edid *edid; + +		/* if we have an edid check it matches the connection */ +		edid = psb_intel_sdvo_get_edid(connector); +		if (edid == NULL) +			edid = psb_intel_sdvo_get_analog_edid(connector); +		if (edid != NULL) { +			if (edid->input & DRM_EDID_INPUT_DIGITAL) +				ret = connector_status_disconnected; +			else +				ret = connector_status_connected; +			kfree(edid); +		} else +			ret = connector_status_connected; +	} + +	/* May update encoder flag for like clock for SDVO TV, etc.*/ +	if (ret == connector_status_connected) { +		psb_intel_sdvo->is_tv = false; +		psb_intel_sdvo->is_lvds = false; +		psb_intel_sdvo->base.needs_tv_clock = false; + +		if (response & SDVO_TV_MASK) { +			psb_intel_sdvo->is_tv = true; +			psb_intel_sdvo->base.needs_tv_clock = true; +		} +		if (response & SDVO_LVDS_MASK) +			psb_intel_sdvo->is_lvds = psb_intel_sdvo->sdvo_lvds_fixed_mode != NULL; +	} + +	return ret; +} + +static void psb_intel_sdvo_get_ddc_modes(struct drm_connector *connector) +{ +	struct edid *edid; + +	/* set the bus switch and get the modes */ +	edid = psb_intel_sdvo_get_edid(connector); + +	/* +	 * Mac mini hack.  On this device, the DVI-I connector shares one DDC +	 * link between analog and digital outputs. So, if the regular SDVO +	 * DDC fails, check to see if the analog output is disconnected, in +	 * which case we'll look there for the digital DDC data. +	 */ +	if (edid == NULL) +		edid = psb_intel_sdvo_get_analog_edid(connector); + +	if (edid != NULL) { +		struct psb_intel_sdvo_connector *psb_intel_sdvo_connector = to_psb_intel_sdvo_connector(connector); +		bool monitor_is_digital = !!(edid->input & DRM_EDID_INPUT_DIGITAL); +		bool connector_is_digital = !!IS_TMDS(psb_intel_sdvo_connector); + +		if (connector_is_digital == monitor_is_digital) { +			drm_mode_connector_update_edid_property(connector, edid); +			drm_add_edid_modes(connector, edid); +		} + +		kfree(edid); +	} +} + +/* + * Set of SDVO TV modes. + * Note!  This is in reply order (see loop in get_tv_modes). + * XXX: all 60Hz refresh? + */ +static const struct drm_display_mode sdvo_tv_modes[] = { +	{ DRM_MODE("320x200", DRM_MODE_TYPE_DRIVER, 5815, 320, 321, 384, +		   416, 0, 200, 201, 232, 233, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("320x240", DRM_MODE_TYPE_DRIVER, 6814, 320, 321, 384, +		   416, 0, 240, 241, 272, 273, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("400x300", DRM_MODE_TYPE_DRIVER, 9910, 400, 401, 464, +		   496, 0, 300, 301, 332, 333, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("640x350", DRM_MODE_TYPE_DRIVER, 16913, 640, 641, 704, +		   736, 0, 350, 351, 382, 383, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("640x400", DRM_MODE_TYPE_DRIVER, 19121, 640, 641, 704, +		   736, 0, 400, 401, 432, 433, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 22654, 640, 641, 704, +		   736, 0, 480, 481, 512, 513, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("704x480", DRM_MODE_TYPE_DRIVER, 24624, 704, 705, 768, +		   800, 0, 480, 481, 512, 513, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("704x576", DRM_MODE_TYPE_DRIVER, 29232, 704, 705, 768, +		   800, 0, 576, 577, 608, 609, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("720x350", DRM_MODE_TYPE_DRIVER, 18751, 720, 721, 784, +		   816, 0, 350, 351, 382, 383, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 21199, 720, 721, 784, +		   816, 0, 400, 401, 432, 433, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 25116, 720, 721, 784, +		   816, 0, 480, 481, 512, 513, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("720x540", DRM_MODE_TYPE_DRIVER, 28054, 720, 721, 784, +		   816, 0, 540, 541, 572, 573, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 29816, 720, 721, 784, +		   816, 0, 576, 577, 608, 609, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("768x576", DRM_MODE_TYPE_DRIVER, 31570, 768, 769, 832, +		   864, 0, 576, 577, 608, 609, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 34030, 800, 801, 864, +		   896, 0, 600, 601, 632, 633, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("832x624", DRM_MODE_TYPE_DRIVER, 36581, 832, 833, 896, +		   928, 0, 624, 625, 656, 657, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("920x766", DRM_MODE_TYPE_DRIVER, 48707, 920, 921, 984, +		   1016, 0, 766, 767, 798, 799, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 53827, 1024, 1025, 1088, +		   1120, 0, 768, 769, 800, 801, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 87265, 1280, 1281, 1344, +		   1376, 0, 1024, 1025, 1056, 1057, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +}; + +static void psb_intel_sdvo_get_tv_modes(struct drm_connector *connector) +{ +	struct psb_intel_sdvo *psb_intel_sdvo = intel_attached_sdvo(connector); +	struct psb_intel_sdvo_sdtv_resolution_request tv_res; +	uint32_t reply = 0, format_map = 0; +	int i; + +	/* Read the list of supported input resolutions for the selected TV +	 * format. +	 */ +	format_map = 1 << psb_intel_sdvo->tv_format_index; +	memcpy(&tv_res, &format_map, +	       min(sizeof(format_map), sizeof(struct psb_intel_sdvo_sdtv_resolution_request))); + +	if (!psb_intel_sdvo_set_target_output(psb_intel_sdvo, psb_intel_sdvo->attached_output)) +		return; + +	BUILD_BUG_ON(sizeof(tv_res) != 3); +	if (!psb_intel_sdvo_write_cmd(psb_intel_sdvo, +				  SDVO_CMD_GET_SDTV_RESOLUTION_SUPPORT, +				  &tv_res, sizeof(tv_res))) +		return; +	if (!psb_intel_sdvo_read_response(psb_intel_sdvo, &reply, 3)) +		return; + +	for (i = 0; i < ARRAY_SIZE(sdvo_tv_modes); i++) +		if (reply & (1 << i)) { +			struct drm_display_mode *nmode; +			nmode = drm_mode_duplicate(connector->dev, +						   &sdvo_tv_modes[i]); +			if (nmode) +				drm_mode_probed_add(connector, nmode); +		} +} + +static void psb_intel_sdvo_get_lvds_modes(struct drm_connector *connector) +{ +	struct psb_intel_sdvo *psb_intel_sdvo = intel_attached_sdvo(connector); +	struct drm_psb_private *dev_priv = connector->dev->dev_private; +	struct drm_display_mode *newmode; + +	/* +	 * Attempt to get the mode list from DDC. +	 * Assume that the preferred modes are +	 * arranged in priority order. +	 */ +	psb_intel_ddc_get_modes(connector, psb_intel_sdvo->i2c); +	if (list_empty(&connector->probed_modes) == false) +		goto end; + +	/* Fetch modes from VBT */ +	if (dev_priv->sdvo_lvds_vbt_mode != NULL) { +		newmode = drm_mode_duplicate(connector->dev, +					     dev_priv->sdvo_lvds_vbt_mode); +		if (newmode != NULL) { +			/* Guarantee the mode is preferred */ +			newmode->type = (DRM_MODE_TYPE_PREFERRED | +					 DRM_MODE_TYPE_DRIVER); +			drm_mode_probed_add(connector, newmode); +		} +	} + +end: +	list_for_each_entry(newmode, &connector->probed_modes, head) { +		if (newmode->type & DRM_MODE_TYPE_PREFERRED) { +			psb_intel_sdvo->sdvo_lvds_fixed_mode = +				drm_mode_duplicate(connector->dev, newmode); + +			drm_mode_set_crtcinfo(psb_intel_sdvo->sdvo_lvds_fixed_mode, +					      0); + +			psb_intel_sdvo->is_lvds = true; +			break; +		} +	} + +} + +static int psb_intel_sdvo_get_modes(struct drm_connector *connector) +{ +	struct psb_intel_sdvo_connector *psb_intel_sdvo_connector = to_psb_intel_sdvo_connector(connector); + +	if (IS_TV(psb_intel_sdvo_connector)) +		psb_intel_sdvo_get_tv_modes(connector); +	else if (IS_LVDS(psb_intel_sdvo_connector)) +		psb_intel_sdvo_get_lvds_modes(connector); +	else +		psb_intel_sdvo_get_ddc_modes(connector); + +	return !list_empty(&connector->probed_modes); +} + +static void +psb_intel_sdvo_destroy_enhance_property(struct drm_connector *connector) +{ +	struct psb_intel_sdvo_connector *psb_intel_sdvo_connector = to_psb_intel_sdvo_connector(connector); +	struct drm_device *dev = connector->dev; + +	if (psb_intel_sdvo_connector->left) +		drm_property_destroy(dev, psb_intel_sdvo_connector->left); +	if (psb_intel_sdvo_connector->right) +		drm_property_destroy(dev, psb_intel_sdvo_connector->right); +	if (psb_intel_sdvo_connector->top) +		drm_property_destroy(dev, psb_intel_sdvo_connector->top); +	if (psb_intel_sdvo_connector->bottom) +		drm_property_destroy(dev, psb_intel_sdvo_connector->bottom); +	if (psb_intel_sdvo_connector->hpos) +		drm_property_destroy(dev, psb_intel_sdvo_connector->hpos); +	if (psb_intel_sdvo_connector->vpos) +		drm_property_destroy(dev, psb_intel_sdvo_connector->vpos); +	if (psb_intel_sdvo_connector->saturation) +		drm_property_destroy(dev, psb_intel_sdvo_connector->saturation); +	if (psb_intel_sdvo_connector->contrast) +		drm_property_destroy(dev, psb_intel_sdvo_connector->contrast); +	if (psb_intel_sdvo_connector->hue) +		drm_property_destroy(dev, psb_intel_sdvo_connector->hue); +	if (psb_intel_sdvo_connector->sharpness) +		drm_property_destroy(dev, psb_intel_sdvo_connector->sharpness); +	if (psb_intel_sdvo_connector->flicker_filter) +		drm_property_destroy(dev, psb_intel_sdvo_connector->flicker_filter); +	if (psb_intel_sdvo_connector->flicker_filter_2d) +		drm_property_destroy(dev, psb_intel_sdvo_connector->flicker_filter_2d); +	if (psb_intel_sdvo_connector->flicker_filter_adaptive) +		drm_property_destroy(dev, psb_intel_sdvo_connector->flicker_filter_adaptive); +	if (psb_intel_sdvo_connector->tv_luma_filter) +		drm_property_destroy(dev, psb_intel_sdvo_connector->tv_luma_filter); +	if (psb_intel_sdvo_connector->tv_chroma_filter) +		drm_property_destroy(dev, psb_intel_sdvo_connector->tv_chroma_filter); +	if (psb_intel_sdvo_connector->dot_crawl) +		drm_property_destroy(dev, psb_intel_sdvo_connector->dot_crawl); +	if (psb_intel_sdvo_connector->brightness) +		drm_property_destroy(dev, psb_intel_sdvo_connector->brightness); +} + +static void psb_intel_sdvo_destroy(struct drm_connector *connector) +{ +	struct psb_intel_sdvo_connector *psb_intel_sdvo_connector = to_psb_intel_sdvo_connector(connector); + +	if (psb_intel_sdvo_connector->tv_format) +		drm_property_destroy(connector->dev, +				     psb_intel_sdvo_connector->tv_format); + +	psb_intel_sdvo_destroy_enhance_property(connector); +	drm_sysfs_connector_remove(connector); +	drm_connector_cleanup(connector); +	kfree(connector); +} + +static bool psb_intel_sdvo_detect_hdmi_audio(struct drm_connector *connector) +{ +	struct psb_intel_sdvo *psb_intel_sdvo = intel_attached_sdvo(connector); +	struct edid *edid; +	bool has_audio = false; + +	if (!psb_intel_sdvo->is_hdmi) +		return false; + +	edid = psb_intel_sdvo_get_edid(connector); +	if (edid != NULL && edid->input & DRM_EDID_INPUT_DIGITAL) +		has_audio = drm_detect_monitor_audio(edid); + +	return has_audio; +} + +static int +psb_intel_sdvo_set_property(struct drm_connector *connector, +			struct drm_property *property, +			uint64_t val) +{ +	struct psb_intel_sdvo *psb_intel_sdvo = intel_attached_sdvo(connector); +	struct psb_intel_sdvo_connector *psb_intel_sdvo_connector = to_psb_intel_sdvo_connector(connector); +	struct drm_psb_private *dev_priv = connector->dev->dev_private; +	uint16_t temp_value; +	uint8_t cmd; +	int ret; + +	ret = drm_object_property_set_value(&connector->base, property, val); +	if (ret) +		return ret; + +	if (property == dev_priv->force_audio_property) { +		int i = val; +		bool has_audio; + +		if (i == psb_intel_sdvo_connector->force_audio) +			return 0; + +		psb_intel_sdvo_connector->force_audio = i; + +		if (i == 0) +			has_audio = psb_intel_sdvo_detect_hdmi_audio(connector); +		else +			has_audio = i > 0; + +		if (has_audio == psb_intel_sdvo->has_hdmi_audio) +			return 0; + +		psb_intel_sdvo->has_hdmi_audio = has_audio; +		goto done; +	} + +	if (property == dev_priv->broadcast_rgb_property) { +		if (val == !!psb_intel_sdvo->color_range) +			return 0; + +		psb_intel_sdvo->color_range = val ? SDVO_COLOR_RANGE_16_235 : 0; +		goto done; +	} + +#define CHECK_PROPERTY(name, NAME) \ +	if (psb_intel_sdvo_connector->name == property) { \ +		if (psb_intel_sdvo_connector->cur_##name == temp_value) return 0; \ +		if (psb_intel_sdvo_connector->max_##name < temp_value) return -EINVAL; \ +		cmd = SDVO_CMD_SET_##NAME; \ +		psb_intel_sdvo_connector->cur_##name = temp_value; \ +		goto set_value; \ +	} + +	if (property == psb_intel_sdvo_connector->tv_format) { +		if (val >= TV_FORMAT_NUM) +			return -EINVAL; + +		if (psb_intel_sdvo->tv_format_index == +		    psb_intel_sdvo_connector->tv_format_supported[val]) +			return 0; + +		psb_intel_sdvo->tv_format_index = psb_intel_sdvo_connector->tv_format_supported[val]; +		goto done; +	} else if (IS_TV_OR_LVDS(psb_intel_sdvo_connector)) { +		temp_value = val; +		if (psb_intel_sdvo_connector->left == property) { +			drm_object_property_set_value(&connector->base, +							 psb_intel_sdvo_connector->right, val); +			if (psb_intel_sdvo_connector->left_margin == temp_value) +				return 0; + +			psb_intel_sdvo_connector->left_margin = temp_value; +			psb_intel_sdvo_connector->right_margin = temp_value; +			temp_value = psb_intel_sdvo_connector->max_hscan - +				psb_intel_sdvo_connector->left_margin; +			cmd = SDVO_CMD_SET_OVERSCAN_H; +			goto set_value; +		} else if (psb_intel_sdvo_connector->right == property) { +			drm_object_property_set_value(&connector->base, +							 psb_intel_sdvo_connector->left, val); +			if (psb_intel_sdvo_connector->right_margin == temp_value) +				return 0; + +			psb_intel_sdvo_connector->left_margin = temp_value; +			psb_intel_sdvo_connector->right_margin = temp_value; +			temp_value = psb_intel_sdvo_connector->max_hscan - +				psb_intel_sdvo_connector->left_margin; +			cmd = SDVO_CMD_SET_OVERSCAN_H; +			goto set_value; +		} else if (psb_intel_sdvo_connector->top == property) { +			drm_object_property_set_value(&connector->base, +							 psb_intel_sdvo_connector->bottom, val); +			if (psb_intel_sdvo_connector->top_margin == temp_value) +				return 0; + +			psb_intel_sdvo_connector->top_margin = temp_value; +			psb_intel_sdvo_connector->bottom_margin = temp_value; +			temp_value = psb_intel_sdvo_connector->max_vscan - +				psb_intel_sdvo_connector->top_margin; +			cmd = SDVO_CMD_SET_OVERSCAN_V; +			goto set_value; +		} else if (psb_intel_sdvo_connector->bottom == property) { +			drm_object_property_set_value(&connector->base, +							 psb_intel_sdvo_connector->top, val); +			if (psb_intel_sdvo_connector->bottom_margin == temp_value) +				return 0; + +			psb_intel_sdvo_connector->top_margin = temp_value; +			psb_intel_sdvo_connector->bottom_margin = temp_value; +			temp_value = psb_intel_sdvo_connector->max_vscan - +				psb_intel_sdvo_connector->top_margin; +			cmd = SDVO_CMD_SET_OVERSCAN_V; +			goto set_value; +		} +		CHECK_PROPERTY(hpos, HPOS) +		CHECK_PROPERTY(vpos, VPOS) +		CHECK_PROPERTY(saturation, SATURATION) +		CHECK_PROPERTY(contrast, CONTRAST) +		CHECK_PROPERTY(hue, HUE) +		CHECK_PROPERTY(brightness, BRIGHTNESS) +		CHECK_PROPERTY(sharpness, SHARPNESS) +		CHECK_PROPERTY(flicker_filter, FLICKER_FILTER) +		CHECK_PROPERTY(flicker_filter_2d, FLICKER_FILTER_2D) +		CHECK_PROPERTY(flicker_filter_adaptive, FLICKER_FILTER_ADAPTIVE) +		CHECK_PROPERTY(tv_chroma_filter, TV_CHROMA_FILTER) +		CHECK_PROPERTY(tv_luma_filter, TV_LUMA_FILTER) +		CHECK_PROPERTY(dot_crawl, DOT_CRAWL) +	} + +	return -EINVAL; /* unknown property */ + +set_value: +	if (!psb_intel_sdvo_set_value(psb_intel_sdvo, cmd, &temp_value, 2)) +		return -EIO; + + +done: +	if (psb_intel_sdvo->base.base.crtc) { +		struct drm_crtc *crtc = psb_intel_sdvo->base.base.crtc; +		drm_crtc_helper_set_mode(crtc, &crtc->mode, crtc->x, +					 crtc->y, crtc->primary->fb); +	} + +	return 0; +#undef CHECK_PROPERTY +} + +static void psb_intel_sdvo_save(struct drm_connector *connector) +{ +	struct drm_device *dev = connector->dev; +	struct gma_encoder *gma_encoder = gma_attached_encoder(connector); +	struct psb_intel_sdvo *sdvo = to_psb_intel_sdvo(&gma_encoder->base); + +	sdvo->saveSDVO = REG_READ(sdvo->sdvo_reg); +} + +static void psb_intel_sdvo_restore(struct drm_connector *connector) +{ +	struct drm_device *dev = connector->dev; +	struct drm_encoder *encoder = &gma_attached_encoder(connector)->base; +	struct psb_intel_sdvo *sdvo = to_psb_intel_sdvo(encoder); +	struct drm_crtc *crtc = encoder->crtc; + +	REG_WRITE(sdvo->sdvo_reg, sdvo->saveSDVO); + +	/* Force a full mode set on the crtc. We're supposed to have the +	   mode_config lock already. */ +	if (connector->status == connector_status_connected) +		drm_crtc_helper_set_mode(crtc, &crtc->mode, crtc->x, crtc->y, +					 NULL); +} + +static const struct drm_encoder_helper_funcs psb_intel_sdvo_helper_funcs = { +	.dpms = psb_intel_sdvo_dpms, +	.mode_fixup = psb_intel_sdvo_mode_fixup, +	.prepare = gma_encoder_prepare, +	.mode_set = psb_intel_sdvo_mode_set, +	.commit = gma_encoder_commit, +}; + +static const struct drm_connector_funcs psb_intel_sdvo_connector_funcs = { +	.dpms = drm_helper_connector_dpms, +	.save = psb_intel_sdvo_save, +	.restore = psb_intel_sdvo_restore, +	.detect = psb_intel_sdvo_detect, +	.fill_modes = drm_helper_probe_single_connector_modes, +	.set_property = psb_intel_sdvo_set_property, +	.destroy = psb_intel_sdvo_destroy, +}; + +static const struct drm_connector_helper_funcs psb_intel_sdvo_connector_helper_funcs = { +	.get_modes = psb_intel_sdvo_get_modes, +	.mode_valid = psb_intel_sdvo_mode_valid, +	.best_encoder = gma_best_encoder, +}; + +static void psb_intel_sdvo_enc_destroy(struct drm_encoder *encoder) +{ +	struct psb_intel_sdvo *psb_intel_sdvo = to_psb_intel_sdvo(encoder); + +	if (psb_intel_sdvo->sdvo_lvds_fixed_mode != NULL) +		drm_mode_destroy(encoder->dev, +				 psb_intel_sdvo->sdvo_lvds_fixed_mode); + +	i2c_del_adapter(&psb_intel_sdvo->ddc); +	gma_encoder_destroy(encoder); +} + +static const struct drm_encoder_funcs psb_intel_sdvo_enc_funcs = { +	.destroy = psb_intel_sdvo_enc_destroy, +}; + +static void +psb_intel_sdvo_guess_ddc_bus(struct psb_intel_sdvo *sdvo) +{ +	/* FIXME: At the moment, ddc_bus = 2 is the only thing that works. +	 * We need to figure out if this is true for all available poulsbo +	 * hardware, or if we need to fiddle with the guessing code above. +	 * The problem might go away if we can parse sdvo mappings from bios */ +	sdvo->ddc_bus = 2; + +#if 0 +	uint16_t mask = 0; +	unsigned int num_bits; + +	/* Make a mask of outputs less than or equal to our own priority in the +	 * list. +	 */ +	switch (sdvo->controlled_output) { +	case SDVO_OUTPUT_LVDS1: +		mask |= SDVO_OUTPUT_LVDS1; +	case SDVO_OUTPUT_LVDS0: +		mask |= SDVO_OUTPUT_LVDS0; +	case SDVO_OUTPUT_TMDS1: +		mask |= SDVO_OUTPUT_TMDS1; +	case SDVO_OUTPUT_TMDS0: +		mask |= SDVO_OUTPUT_TMDS0; +	case SDVO_OUTPUT_RGB1: +		mask |= SDVO_OUTPUT_RGB1; +	case SDVO_OUTPUT_RGB0: +		mask |= SDVO_OUTPUT_RGB0; +		break; +	} + +	/* Count bits to find what number we are in the priority list. */ +	mask &= sdvo->caps.output_flags; +	num_bits = hweight16(mask); +	/* If more than 3 outputs, default to DDC bus 3 for now. */ +	if (num_bits > 3) +		num_bits = 3; + +	/* Corresponds to SDVO_CONTROL_BUS_DDCx */ +	sdvo->ddc_bus = 1 << num_bits; +#endif +} + +/** + * Choose the appropriate DDC bus for control bus switch command for this + * SDVO output based on the controlled output. + * + * DDC bus number assignment is in a priority order of RGB outputs, then TMDS + * outputs, then LVDS outputs. + */ +static void +psb_intel_sdvo_select_ddc_bus(struct drm_psb_private *dev_priv, +			  struct psb_intel_sdvo *sdvo, u32 reg) +{ +	struct sdvo_device_mapping *mapping; + +	if (IS_SDVOB(reg)) +		mapping = &(dev_priv->sdvo_mappings[0]); +	else +		mapping = &(dev_priv->sdvo_mappings[1]); + +	if (mapping->initialized) +		sdvo->ddc_bus = 1 << ((mapping->ddc_pin & 0xf0) >> 4); +	else +		psb_intel_sdvo_guess_ddc_bus(sdvo); +} + +static void +psb_intel_sdvo_select_i2c_bus(struct drm_psb_private *dev_priv, +			  struct psb_intel_sdvo *sdvo, u32 reg) +{ +	struct sdvo_device_mapping *mapping; +	u8 pin, speed; + +	if (IS_SDVOB(reg)) +		mapping = &dev_priv->sdvo_mappings[0]; +	else +		mapping = &dev_priv->sdvo_mappings[1]; + +	pin = GMBUS_PORT_DPB; +	speed = GMBUS_RATE_1MHZ >> 8; +	if (mapping->initialized) { +		pin = mapping->i2c_pin; +		speed = mapping->i2c_speed; +	} + +	if (pin < GMBUS_NUM_PORTS) { +		sdvo->i2c = &dev_priv->gmbus[pin].adapter; +		gma_intel_gmbus_set_speed(sdvo->i2c, speed); +		gma_intel_gmbus_force_bit(sdvo->i2c, true); +	} else +		sdvo->i2c = &dev_priv->gmbus[GMBUS_PORT_DPB].adapter; +} + +static bool +psb_intel_sdvo_is_hdmi_connector(struct psb_intel_sdvo *psb_intel_sdvo, int device) +{ +	return psb_intel_sdvo_check_supp_encode(psb_intel_sdvo); +} + +static u8 +psb_intel_sdvo_get_slave_addr(struct drm_device *dev, int sdvo_reg) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct sdvo_device_mapping *my_mapping, *other_mapping; + +	if (IS_SDVOB(sdvo_reg)) { +		my_mapping = &dev_priv->sdvo_mappings[0]; +		other_mapping = &dev_priv->sdvo_mappings[1]; +	} else { +		my_mapping = &dev_priv->sdvo_mappings[1]; +		other_mapping = &dev_priv->sdvo_mappings[0]; +	} + +	/* If the BIOS described our SDVO device, take advantage of it. */ +	if (my_mapping->slave_addr) +		return my_mapping->slave_addr; + +	/* If the BIOS only described a different SDVO device, use the +	 * address that it isn't using. +	 */ +	if (other_mapping->slave_addr) { +		if (other_mapping->slave_addr == 0x70) +			return 0x72; +		else +			return 0x70; +	} + +	/* No SDVO device info is found for another DVO port, +	 * so use mapping assumption we had before BIOS parsing. +	 */ +	if (IS_SDVOB(sdvo_reg)) +		return 0x70; +	else +		return 0x72; +} + +static void +psb_intel_sdvo_connector_init(struct psb_intel_sdvo_connector *connector, +			  struct psb_intel_sdvo *encoder) +{ +	drm_connector_init(encoder->base.base.dev, +			   &connector->base.base, +			   &psb_intel_sdvo_connector_funcs, +			   connector->base.base.connector_type); + +	drm_connector_helper_add(&connector->base.base, +				 &psb_intel_sdvo_connector_helper_funcs); + +	connector->base.base.interlace_allowed = 0; +	connector->base.base.doublescan_allowed = 0; +	connector->base.base.display_info.subpixel_order = SubPixelHorizontalRGB; + +	gma_connector_attach_encoder(&connector->base, &encoder->base); +	drm_sysfs_connector_add(&connector->base.base); +} + +static void +psb_intel_sdvo_add_hdmi_properties(struct psb_intel_sdvo_connector *connector) +{ +	/* FIXME: We don't support HDMI at the moment +	struct drm_device *dev = connector->base.base.dev; + +	intel_attach_force_audio_property(&connector->base.base); +	intel_attach_broadcast_rgb_property(&connector->base.base); +	*/ +} + +static bool +psb_intel_sdvo_dvi_init(struct psb_intel_sdvo *psb_intel_sdvo, int device) +{ +	struct drm_encoder *encoder = &psb_intel_sdvo->base.base; +	struct drm_connector *connector; +	struct gma_connector *intel_connector; +	struct psb_intel_sdvo_connector *psb_intel_sdvo_connector; + +	psb_intel_sdvo_connector = kzalloc(sizeof(struct psb_intel_sdvo_connector), GFP_KERNEL); +	if (!psb_intel_sdvo_connector) +		return false; + +	if (device == 0) { +		psb_intel_sdvo->controlled_output |= SDVO_OUTPUT_TMDS0; +		psb_intel_sdvo_connector->output_flag = SDVO_OUTPUT_TMDS0; +	} else if (device == 1) { +		psb_intel_sdvo->controlled_output |= SDVO_OUTPUT_TMDS1; +		psb_intel_sdvo_connector->output_flag = SDVO_OUTPUT_TMDS1; +	} + +	intel_connector = &psb_intel_sdvo_connector->base; +	connector = &intel_connector->base; +	// connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; +	encoder->encoder_type = DRM_MODE_ENCODER_TMDS; +	connector->connector_type = DRM_MODE_CONNECTOR_DVID; + +	if (psb_intel_sdvo_is_hdmi_connector(psb_intel_sdvo, device)) { +		connector->connector_type = DRM_MODE_CONNECTOR_HDMIA; +		psb_intel_sdvo->is_hdmi = true; +	} +	psb_intel_sdvo->base.clone_mask = ((1 << INTEL_SDVO_NON_TV_CLONE_BIT) | +				       (1 << INTEL_ANALOG_CLONE_BIT)); + +	psb_intel_sdvo_connector_init(psb_intel_sdvo_connector, psb_intel_sdvo); +	if (psb_intel_sdvo->is_hdmi) +		psb_intel_sdvo_add_hdmi_properties(psb_intel_sdvo_connector); + +	return true; +} + +static bool +psb_intel_sdvo_tv_init(struct psb_intel_sdvo *psb_intel_sdvo, int type) +{ +	struct drm_encoder *encoder = &psb_intel_sdvo->base.base; +	struct drm_connector *connector; +	struct gma_connector *intel_connector; +	struct psb_intel_sdvo_connector *psb_intel_sdvo_connector; + +	psb_intel_sdvo_connector = kzalloc(sizeof(struct psb_intel_sdvo_connector), GFP_KERNEL); +	if (!psb_intel_sdvo_connector) +		return false; + +	intel_connector = &psb_intel_sdvo_connector->base; +	connector = &intel_connector->base; +	encoder->encoder_type = DRM_MODE_ENCODER_TVDAC; +	connector->connector_type = DRM_MODE_CONNECTOR_SVIDEO; + +	psb_intel_sdvo->controlled_output |= type; +	psb_intel_sdvo_connector->output_flag = type; + +	psb_intel_sdvo->is_tv = true; +	psb_intel_sdvo->base.needs_tv_clock = true; +	psb_intel_sdvo->base.clone_mask = 1 << INTEL_SDVO_TV_CLONE_BIT; + +	psb_intel_sdvo_connector_init(psb_intel_sdvo_connector, psb_intel_sdvo); + +	if (!psb_intel_sdvo_tv_create_property(psb_intel_sdvo, psb_intel_sdvo_connector, type)) +		goto err; + +	if (!psb_intel_sdvo_create_enhance_property(psb_intel_sdvo, psb_intel_sdvo_connector)) +		goto err; + +	return true; + +err: +	psb_intel_sdvo_destroy(connector); +	return false; +} + +static bool +psb_intel_sdvo_analog_init(struct psb_intel_sdvo *psb_intel_sdvo, int device) +{ +	struct drm_encoder *encoder = &psb_intel_sdvo->base.base; +	struct drm_connector *connector; +	struct gma_connector *intel_connector; +	struct psb_intel_sdvo_connector *psb_intel_sdvo_connector; + +	psb_intel_sdvo_connector = kzalloc(sizeof(struct psb_intel_sdvo_connector), GFP_KERNEL); +	if (!psb_intel_sdvo_connector) +		return false; + +	intel_connector = &psb_intel_sdvo_connector->base; +	connector = &intel_connector->base; +	connector->polled = DRM_CONNECTOR_POLL_CONNECT; +	encoder->encoder_type = DRM_MODE_ENCODER_DAC; +	connector->connector_type = DRM_MODE_CONNECTOR_VGA; + +	if (device == 0) { +		psb_intel_sdvo->controlled_output |= SDVO_OUTPUT_RGB0; +		psb_intel_sdvo_connector->output_flag = SDVO_OUTPUT_RGB0; +	} else if (device == 1) { +		psb_intel_sdvo->controlled_output |= SDVO_OUTPUT_RGB1; +		psb_intel_sdvo_connector->output_flag = SDVO_OUTPUT_RGB1; +	} + +	psb_intel_sdvo->base.clone_mask = ((1 << INTEL_SDVO_NON_TV_CLONE_BIT) | +				       (1 << INTEL_ANALOG_CLONE_BIT)); + +	psb_intel_sdvo_connector_init(psb_intel_sdvo_connector, +				  psb_intel_sdvo); +	return true; +} + +static bool +psb_intel_sdvo_lvds_init(struct psb_intel_sdvo *psb_intel_sdvo, int device) +{ +	struct drm_encoder *encoder = &psb_intel_sdvo->base.base; +	struct drm_connector *connector; +	struct gma_connector *intel_connector; +	struct psb_intel_sdvo_connector *psb_intel_sdvo_connector; + +	psb_intel_sdvo_connector = kzalloc(sizeof(struct psb_intel_sdvo_connector), GFP_KERNEL); +	if (!psb_intel_sdvo_connector) +		return false; + +	intel_connector = &psb_intel_sdvo_connector->base; +	connector = &intel_connector->base; +	encoder->encoder_type = DRM_MODE_ENCODER_LVDS; +	connector->connector_type = DRM_MODE_CONNECTOR_LVDS; + +	if (device == 0) { +		psb_intel_sdvo->controlled_output |= SDVO_OUTPUT_LVDS0; +		psb_intel_sdvo_connector->output_flag = SDVO_OUTPUT_LVDS0; +	} else if (device == 1) { +		psb_intel_sdvo->controlled_output |= SDVO_OUTPUT_LVDS1; +		psb_intel_sdvo_connector->output_flag = SDVO_OUTPUT_LVDS1; +	} + +	psb_intel_sdvo->base.clone_mask = ((1 << INTEL_ANALOG_CLONE_BIT) | +				       (1 << INTEL_SDVO_LVDS_CLONE_BIT)); + +	psb_intel_sdvo_connector_init(psb_intel_sdvo_connector, psb_intel_sdvo); +	if (!psb_intel_sdvo_create_enhance_property(psb_intel_sdvo, psb_intel_sdvo_connector)) +		goto err; + +	return true; + +err: +	psb_intel_sdvo_destroy(connector); +	return false; +} + +static bool +psb_intel_sdvo_output_setup(struct psb_intel_sdvo *psb_intel_sdvo, uint16_t flags) +{ +	psb_intel_sdvo->is_tv = false; +	psb_intel_sdvo->base.needs_tv_clock = false; +	psb_intel_sdvo->is_lvds = false; + +	/* SDVO requires XXX1 function may not exist unless it has XXX0 function.*/ + +	if (flags & SDVO_OUTPUT_TMDS0) +		if (!psb_intel_sdvo_dvi_init(psb_intel_sdvo, 0)) +			return false; + +	if ((flags & SDVO_TMDS_MASK) == SDVO_TMDS_MASK) +		if (!psb_intel_sdvo_dvi_init(psb_intel_sdvo, 1)) +			return false; + +	/* TV has no XXX1 function block */ +	if (flags & SDVO_OUTPUT_SVID0) +		if (!psb_intel_sdvo_tv_init(psb_intel_sdvo, SDVO_OUTPUT_SVID0)) +			return false; + +	if (flags & SDVO_OUTPUT_CVBS0) +		if (!psb_intel_sdvo_tv_init(psb_intel_sdvo, SDVO_OUTPUT_CVBS0)) +			return false; + +	if (flags & SDVO_OUTPUT_RGB0) +		if (!psb_intel_sdvo_analog_init(psb_intel_sdvo, 0)) +			return false; + +	if ((flags & SDVO_RGB_MASK) == SDVO_RGB_MASK) +		if (!psb_intel_sdvo_analog_init(psb_intel_sdvo, 1)) +			return false; + +	if (flags & SDVO_OUTPUT_LVDS0) +		if (!psb_intel_sdvo_lvds_init(psb_intel_sdvo, 0)) +			return false; + +	if ((flags & SDVO_LVDS_MASK) == SDVO_LVDS_MASK) +		if (!psb_intel_sdvo_lvds_init(psb_intel_sdvo, 1)) +			return false; + +	if ((flags & SDVO_OUTPUT_MASK) == 0) { +		unsigned char bytes[2]; + +		psb_intel_sdvo->controlled_output = 0; +		memcpy(bytes, &psb_intel_sdvo->caps.output_flags, 2); +		DRM_DEBUG_KMS("%s: Unknown SDVO output type (0x%02x%02x)\n", +			      SDVO_NAME(psb_intel_sdvo), +			      bytes[0], bytes[1]); +		return false; +	} +	psb_intel_sdvo->base.crtc_mask = (1 << 0) | (1 << 1); + +	return true; +} + +static bool psb_intel_sdvo_tv_create_property(struct psb_intel_sdvo *psb_intel_sdvo, +					  struct psb_intel_sdvo_connector *psb_intel_sdvo_connector, +					  int type) +{ +	struct drm_device *dev = psb_intel_sdvo->base.base.dev; +	struct psb_intel_sdvo_tv_format format; +	uint32_t format_map, i; + +	if (!psb_intel_sdvo_set_target_output(psb_intel_sdvo, type)) +		return false; + +	BUILD_BUG_ON(sizeof(format) != 6); +	if (!psb_intel_sdvo_get_value(psb_intel_sdvo, +				  SDVO_CMD_GET_SUPPORTED_TV_FORMATS, +				  &format, sizeof(format))) +		return false; + +	memcpy(&format_map, &format, min(sizeof(format_map), sizeof(format))); + +	if (format_map == 0) +		return false; + +	psb_intel_sdvo_connector->format_supported_num = 0; +	for (i = 0 ; i < TV_FORMAT_NUM; i++) +		if (format_map & (1 << i)) +			psb_intel_sdvo_connector->tv_format_supported[psb_intel_sdvo_connector->format_supported_num++] = i; + + +	psb_intel_sdvo_connector->tv_format = +			drm_property_create(dev, DRM_MODE_PROP_ENUM, +					    "mode", psb_intel_sdvo_connector->format_supported_num); +	if (!psb_intel_sdvo_connector->tv_format) +		return false; + +	for (i = 0; i < psb_intel_sdvo_connector->format_supported_num; i++) +		drm_property_add_enum( +				psb_intel_sdvo_connector->tv_format, i, +				i, tv_format_names[psb_intel_sdvo_connector->tv_format_supported[i]]); + +	psb_intel_sdvo->tv_format_index = psb_intel_sdvo_connector->tv_format_supported[0]; +	drm_object_attach_property(&psb_intel_sdvo_connector->base.base.base, +				      psb_intel_sdvo_connector->tv_format, 0); +	return true; + +} + +#define ENHANCEMENT(name, NAME) do { \ +	if (enhancements.name) { \ +		if (!psb_intel_sdvo_get_value(psb_intel_sdvo, SDVO_CMD_GET_MAX_##NAME, &data_value, 4) || \ +		    !psb_intel_sdvo_get_value(psb_intel_sdvo, SDVO_CMD_GET_##NAME, &response, 2)) \ +			return false; \ +		psb_intel_sdvo_connector->max_##name = data_value[0]; \ +		psb_intel_sdvo_connector->cur_##name = response; \ +		psb_intel_sdvo_connector->name = \ +			drm_property_create_range(dev, 0, #name, 0, data_value[0]); \ +		if (!psb_intel_sdvo_connector->name) return false; \ +		drm_object_attach_property(&connector->base, \ +					      psb_intel_sdvo_connector->name, \ +					      psb_intel_sdvo_connector->cur_##name); \ +		DRM_DEBUG_KMS(#name ": max %d, default %d, current %d\n", \ +			      data_value[0], data_value[1], response); \ +	} \ +} while(0) + +static bool +psb_intel_sdvo_create_enhance_property_tv(struct psb_intel_sdvo *psb_intel_sdvo, +				      struct psb_intel_sdvo_connector *psb_intel_sdvo_connector, +				      struct psb_intel_sdvo_enhancements_reply enhancements) +{ +	struct drm_device *dev = psb_intel_sdvo->base.base.dev; +	struct drm_connector *connector = &psb_intel_sdvo_connector->base.base; +	uint16_t response, data_value[2]; + +	/* when horizontal overscan is supported, Add the left/right  property */ +	if (enhancements.overscan_h) { +		if (!psb_intel_sdvo_get_value(psb_intel_sdvo, +					  SDVO_CMD_GET_MAX_OVERSCAN_H, +					  &data_value, 4)) +			return false; + +		if (!psb_intel_sdvo_get_value(psb_intel_sdvo, +					  SDVO_CMD_GET_OVERSCAN_H, +					  &response, 2)) +			return false; + +		psb_intel_sdvo_connector->max_hscan = data_value[0]; +		psb_intel_sdvo_connector->left_margin = data_value[0] - response; +		psb_intel_sdvo_connector->right_margin = psb_intel_sdvo_connector->left_margin; +		psb_intel_sdvo_connector->left = +			drm_property_create_range(dev, 0, "left_margin", 0, data_value[0]); +		if (!psb_intel_sdvo_connector->left) +			return false; + +		drm_object_attach_property(&connector->base, +					      psb_intel_sdvo_connector->left, +					      psb_intel_sdvo_connector->left_margin); + +		psb_intel_sdvo_connector->right = +			drm_property_create_range(dev, 0, "right_margin", 0, data_value[0]); +		if (!psb_intel_sdvo_connector->right) +			return false; + +		drm_object_attach_property(&connector->base, +					      psb_intel_sdvo_connector->right, +					      psb_intel_sdvo_connector->right_margin); +		DRM_DEBUG_KMS("h_overscan: max %d, " +			      "default %d, current %d\n", +			      data_value[0], data_value[1], response); +	} + +	if (enhancements.overscan_v) { +		if (!psb_intel_sdvo_get_value(psb_intel_sdvo, +					  SDVO_CMD_GET_MAX_OVERSCAN_V, +					  &data_value, 4)) +			return false; + +		if (!psb_intel_sdvo_get_value(psb_intel_sdvo, +					  SDVO_CMD_GET_OVERSCAN_V, +					  &response, 2)) +			return false; + +		psb_intel_sdvo_connector->max_vscan = data_value[0]; +		psb_intel_sdvo_connector->top_margin = data_value[0] - response; +		psb_intel_sdvo_connector->bottom_margin = psb_intel_sdvo_connector->top_margin; +		psb_intel_sdvo_connector->top = +			drm_property_create_range(dev, 0, "top_margin", 0, data_value[0]); +		if (!psb_intel_sdvo_connector->top) +			return false; + +		drm_object_attach_property(&connector->base, +					      psb_intel_sdvo_connector->top, +					      psb_intel_sdvo_connector->top_margin); + +		psb_intel_sdvo_connector->bottom = +			drm_property_create_range(dev, 0, "bottom_margin", 0, data_value[0]); +		if (!psb_intel_sdvo_connector->bottom) +			return false; + +		drm_object_attach_property(&connector->base, +					      psb_intel_sdvo_connector->bottom, +					      psb_intel_sdvo_connector->bottom_margin); +		DRM_DEBUG_KMS("v_overscan: max %d, " +			      "default %d, current %d\n", +			      data_value[0], data_value[1], response); +	} + +	ENHANCEMENT(hpos, HPOS); +	ENHANCEMENT(vpos, VPOS); +	ENHANCEMENT(saturation, SATURATION); +	ENHANCEMENT(contrast, CONTRAST); +	ENHANCEMENT(hue, HUE); +	ENHANCEMENT(sharpness, SHARPNESS); +	ENHANCEMENT(brightness, BRIGHTNESS); +	ENHANCEMENT(flicker_filter, FLICKER_FILTER); +	ENHANCEMENT(flicker_filter_adaptive, FLICKER_FILTER_ADAPTIVE); +	ENHANCEMENT(flicker_filter_2d, FLICKER_FILTER_2D); +	ENHANCEMENT(tv_chroma_filter, TV_CHROMA_FILTER); +	ENHANCEMENT(tv_luma_filter, TV_LUMA_FILTER); + +	if (enhancements.dot_crawl) { +		if (!psb_intel_sdvo_get_value(psb_intel_sdvo, SDVO_CMD_GET_DOT_CRAWL, &response, 2)) +			return false; + +		psb_intel_sdvo_connector->max_dot_crawl = 1; +		psb_intel_sdvo_connector->cur_dot_crawl = response & 0x1; +		psb_intel_sdvo_connector->dot_crawl = +			drm_property_create_range(dev, 0, "dot_crawl", 0, 1); +		if (!psb_intel_sdvo_connector->dot_crawl) +			return false; + +		drm_object_attach_property(&connector->base, +					      psb_intel_sdvo_connector->dot_crawl, +					      psb_intel_sdvo_connector->cur_dot_crawl); +		DRM_DEBUG_KMS("dot crawl: current %d\n", response); +	} + +	return true; +} + +static bool +psb_intel_sdvo_create_enhance_property_lvds(struct psb_intel_sdvo *psb_intel_sdvo, +					struct psb_intel_sdvo_connector *psb_intel_sdvo_connector, +					struct psb_intel_sdvo_enhancements_reply enhancements) +{ +	struct drm_device *dev = psb_intel_sdvo->base.base.dev; +	struct drm_connector *connector = &psb_intel_sdvo_connector->base.base; +	uint16_t response, data_value[2]; + +	ENHANCEMENT(brightness, BRIGHTNESS); + +	return true; +} +#undef ENHANCEMENT + +static bool psb_intel_sdvo_create_enhance_property(struct psb_intel_sdvo *psb_intel_sdvo, +					       struct psb_intel_sdvo_connector *psb_intel_sdvo_connector) +{ +	union { +		struct psb_intel_sdvo_enhancements_reply reply; +		uint16_t response; +	} enhancements; + +	BUILD_BUG_ON(sizeof(enhancements) != 2); + +	enhancements.response = 0; +	psb_intel_sdvo_get_value(psb_intel_sdvo, +			     SDVO_CMD_GET_SUPPORTED_ENHANCEMENTS, +			     &enhancements, sizeof(enhancements)); +	if (enhancements.response == 0) { +		DRM_DEBUG_KMS("No enhancement is supported\n"); +		return true; +	} + +	if (IS_TV(psb_intel_sdvo_connector)) +		return psb_intel_sdvo_create_enhance_property_tv(psb_intel_sdvo, psb_intel_sdvo_connector, enhancements.reply); +	else if(IS_LVDS(psb_intel_sdvo_connector)) +		return psb_intel_sdvo_create_enhance_property_lvds(psb_intel_sdvo, psb_intel_sdvo_connector, enhancements.reply); +	else +		return true; +} + +static int psb_intel_sdvo_ddc_proxy_xfer(struct i2c_adapter *adapter, +				     struct i2c_msg *msgs, +				     int num) +{ +	struct psb_intel_sdvo *sdvo = adapter->algo_data; + +	if (!psb_intel_sdvo_set_control_bus_switch(sdvo, sdvo->ddc_bus)) +		return -EIO; + +	return sdvo->i2c->algo->master_xfer(sdvo->i2c, msgs, num); +} + +static u32 psb_intel_sdvo_ddc_proxy_func(struct i2c_adapter *adapter) +{ +	struct psb_intel_sdvo *sdvo = adapter->algo_data; +	return sdvo->i2c->algo->functionality(sdvo->i2c); +} + +static const struct i2c_algorithm psb_intel_sdvo_ddc_proxy = { +	.master_xfer	= psb_intel_sdvo_ddc_proxy_xfer, +	.functionality	= psb_intel_sdvo_ddc_proxy_func +}; + +static bool +psb_intel_sdvo_init_ddc_proxy(struct psb_intel_sdvo *sdvo, +			  struct drm_device *dev) +{ +	sdvo->ddc.owner = THIS_MODULE; +	sdvo->ddc.class = I2C_CLASS_DDC; +	snprintf(sdvo->ddc.name, I2C_NAME_SIZE, "SDVO DDC proxy"); +	sdvo->ddc.dev.parent = &dev->pdev->dev; +	sdvo->ddc.algo_data = sdvo; +	sdvo->ddc.algo = &psb_intel_sdvo_ddc_proxy; + +	return i2c_add_adapter(&sdvo->ddc) == 0; +} + +bool psb_intel_sdvo_init(struct drm_device *dev, int sdvo_reg) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	struct gma_encoder *gma_encoder; +	struct psb_intel_sdvo *psb_intel_sdvo; +	int i; + +	psb_intel_sdvo = kzalloc(sizeof(struct psb_intel_sdvo), GFP_KERNEL); +	if (!psb_intel_sdvo) +		return false; + +	psb_intel_sdvo->sdvo_reg = sdvo_reg; +	psb_intel_sdvo->slave_addr = psb_intel_sdvo_get_slave_addr(dev, sdvo_reg) >> 1; +	psb_intel_sdvo_select_i2c_bus(dev_priv, psb_intel_sdvo, sdvo_reg); +	if (!psb_intel_sdvo_init_ddc_proxy(psb_intel_sdvo, dev)) { +		kfree(psb_intel_sdvo); +		return false; +	} + +	/* encoder type will be decided later */ +	gma_encoder = &psb_intel_sdvo->base; +	gma_encoder->type = INTEL_OUTPUT_SDVO; +	drm_encoder_init(dev, &gma_encoder->base, &psb_intel_sdvo_enc_funcs, 0); + +	/* Read the regs to test if we can talk to the device */ +	for (i = 0; i < 0x40; i++) { +		u8 byte; + +		if (!psb_intel_sdvo_read_byte(psb_intel_sdvo, i, &byte)) { +			DRM_DEBUG_KMS("No SDVO device found on SDVO%c\n", +				      IS_SDVOB(sdvo_reg) ? 'B' : 'C'); +			goto err; +		} +	} + +	if (IS_SDVOB(sdvo_reg)) +		dev_priv->hotplug_supported_mask |= SDVOB_HOTPLUG_INT_STATUS; +	else +		dev_priv->hotplug_supported_mask |= SDVOC_HOTPLUG_INT_STATUS; + +	drm_encoder_helper_add(&gma_encoder->base, &psb_intel_sdvo_helper_funcs); + +	/* In default case sdvo lvds is false */ +	if (!psb_intel_sdvo_get_capabilities(psb_intel_sdvo, &psb_intel_sdvo->caps)) +		goto err; + +	if (psb_intel_sdvo_output_setup(psb_intel_sdvo, +				    psb_intel_sdvo->caps.output_flags) != true) { +		DRM_DEBUG_KMS("SDVO output failed to setup on SDVO%c\n", +			      IS_SDVOB(sdvo_reg) ? 'B' : 'C'); +		goto err; +	} + +	psb_intel_sdvo_select_ddc_bus(dev_priv, psb_intel_sdvo, sdvo_reg); + +	/* Set the input timing to the screen. Assume always input 0. */ +	if (!psb_intel_sdvo_set_target_input(psb_intel_sdvo)) +		goto err; + +	if (!psb_intel_sdvo_get_input_pixel_clock_range(psb_intel_sdvo, +						    &psb_intel_sdvo->pixel_clock_min, +						    &psb_intel_sdvo->pixel_clock_max)) +		goto err; + +	DRM_DEBUG_KMS("%s device VID/DID: %02X:%02X.%02X, " +			"clock range %dMHz - %dMHz, " +			"input 1: %c, input 2: %c, " +			"output 1: %c, output 2: %c\n", +			SDVO_NAME(psb_intel_sdvo), +			psb_intel_sdvo->caps.vendor_id, psb_intel_sdvo->caps.device_id, +			psb_intel_sdvo->caps.device_rev_id, +			psb_intel_sdvo->pixel_clock_min / 1000, +			psb_intel_sdvo->pixel_clock_max / 1000, +			(psb_intel_sdvo->caps.sdvo_inputs_mask & 0x1) ? 'Y' : 'N', +			(psb_intel_sdvo->caps.sdvo_inputs_mask & 0x2) ? 'Y' : 'N', +			/* check currently supported outputs */ +			psb_intel_sdvo->caps.output_flags & +			(SDVO_OUTPUT_TMDS0 | SDVO_OUTPUT_RGB0) ? 'Y' : 'N', +			psb_intel_sdvo->caps.output_flags & +			(SDVO_OUTPUT_TMDS1 | SDVO_OUTPUT_RGB1) ? 'Y' : 'N'); +	return true; + +err: +	drm_encoder_cleanup(&gma_encoder->base); +	i2c_del_adapter(&psb_intel_sdvo->ddc); +	kfree(psb_intel_sdvo); + +	return false; +} diff --git a/drivers/gpu/drm/gma500/psb_intel_sdvo_regs.h b/drivers/gpu/drm/gma500/psb_intel_sdvo_regs.h new file mode 100644 index 00000000000..600e79744d6 --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_intel_sdvo_regs.h @@ -0,0 +1,723 @@ +/* + * Copyright ? 2006-2007 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + *	Eric Anholt <eric@anholt.net> + */ + +/** + * @file SDVO command definitions and structures. + */ + +#define SDVO_OUTPUT_FIRST   (0) +#define SDVO_OUTPUT_TMDS0   (1 << 0) +#define SDVO_OUTPUT_RGB0    (1 << 1) +#define SDVO_OUTPUT_CVBS0   (1 << 2) +#define SDVO_OUTPUT_SVID0   (1 << 3) +#define SDVO_OUTPUT_YPRPB0  (1 << 4) +#define SDVO_OUTPUT_SCART0  (1 << 5) +#define SDVO_OUTPUT_LVDS0   (1 << 6) +#define SDVO_OUTPUT_TMDS1   (1 << 8) +#define SDVO_OUTPUT_RGB1    (1 << 9) +#define SDVO_OUTPUT_CVBS1   (1 << 10) +#define SDVO_OUTPUT_SVID1   (1 << 11) +#define SDVO_OUTPUT_YPRPB1  (1 << 12) +#define SDVO_OUTPUT_SCART1  (1 << 13) +#define SDVO_OUTPUT_LVDS1   (1 << 14) +#define SDVO_OUTPUT_LAST    (14) + +struct psb_intel_sdvo_caps { +    u8 vendor_id; +    u8 device_id; +    u8 device_rev_id; +    u8 sdvo_version_major; +    u8 sdvo_version_minor; +    unsigned int sdvo_inputs_mask:2; +    unsigned int smooth_scaling:1; +    unsigned int sharp_scaling:1; +    unsigned int up_scaling:1; +    unsigned int down_scaling:1; +    unsigned int stall_support:1; +    unsigned int pad:1; +    u16 output_flags; +} __attribute__((packed)); + +/** This matches the EDID DTD structure, more or less */ +struct psb_intel_sdvo_dtd { +    struct { +	u16 clock;		/**< pixel clock, in 10kHz units */ +	u8 h_active;		/**< lower 8 bits (pixels) */ +	u8 h_blank;		/**< lower 8 bits (pixels) */ +	u8 h_high;		/**< upper 4 bits each h_active, h_blank */ +	u8 v_active;		/**< lower 8 bits (lines) */ +	u8 v_blank;		/**< lower 8 bits (lines) */ +	u8 v_high;		/**< upper 4 bits each v_active, v_blank */ +    } part1; + +    struct { +	u8 h_sync_off;	/**< lower 8 bits, from hblank start */ +	u8 h_sync_width;	/**< lower 8 bits (pixels) */ +	/** lower 4 bits each vsync offset, vsync width */ +	u8 v_sync_off_width; +	/** +	 * 2 high bits of hsync offset, 2 high bits of hsync width, +	 * bits 4-5 of vsync offset, and 2 high bits of vsync width. +	 */ +	u8 sync_off_width_high; +	u8 dtd_flags; +	u8 sdvo_flags; +	/** bits 6-7 of vsync offset at bits 6-7 */ +	u8 v_sync_off_high; +	u8 reserved; +    } part2; +} __attribute__((packed)); + +struct psb_intel_sdvo_pixel_clock_range { +    u16 min;			/**< pixel clock, in 10kHz units */ +    u16 max;			/**< pixel clock, in 10kHz units */ +} __attribute__((packed)); + +struct psb_intel_sdvo_preferred_input_timing_args { +    u16 clock; +    u16 width; +    u16 height; +    u8	interlace:1; +    u8	scaled:1; +    u8	pad:6; +} __attribute__((packed)); + +/* I2C registers for SDVO */ +#define SDVO_I2C_ARG_0				0x07 +#define SDVO_I2C_ARG_1				0x06 +#define SDVO_I2C_ARG_2				0x05 +#define SDVO_I2C_ARG_3				0x04 +#define SDVO_I2C_ARG_4				0x03 +#define SDVO_I2C_ARG_5				0x02 +#define SDVO_I2C_ARG_6				0x01 +#define SDVO_I2C_ARG_7				0x00 +#define SDVO_I2C_OPCODE				0x08 +#define SDVO_I2C_CMD_STATUS			0x09 +#define SDVO_I2C_RETURN_0			0x0a +#define SDVO_I2C_RETURN_1			0x0b +#define SDVO_I2C_RETURN_2			0x0c +#define SDVO_I2C_RETURN_3			0x0d +#define SDVO_I2C_RETURN_4			0x0e +#define SDVO_I2C_RETURN_5			0x0f +#define SDVO_I2C_RETURN_6			0x10 +#define SDVO_I2C_RETURN_7			0x11 +#define SDVO_I2C_VENDOR_BEGIN			0x20 + +/* Status results */ +#define SDVO_CMD_STATUS_POWER_ON		0x0 +#define SDVO_CMD_STATUS_SUCCESS			0x1 +#define SDVO_CMD_STATUS_NOTSUPP			0x2 +#define SDVO_CMD_STATUS_INVALID_ARG		0x3 +#define SDVO_CMD_STATUS_PENDING			0x4 +#define SDVO_CMD_STATUS_TARGET_NOT_SPECIFIED	0x5 +#define SDVO_CMD_STATUS_SCALING_NOT_SUPP	0x6 + +/* SDVO commands, argument/result registers */ + +#define SDVO_CMD_RESET					0x01 + +/** Returns a struct intel_sdvo_caps */ +#define SDVO_CMD_GET_DEVICE_CAPS			0x02 + +#define SDVO_CMD_GET_FIRMWARE_REV			0x86 +# define SDVO_DEVICE_FIRMWARE_MINOR			SDVO_I2C_RETURN_0 +# define SDVO_DEVICE_FIRMWARE_MAJOR			SDVO_I2C_RETURN_1 +# define SDVO_DEVICE_FIRMWARE_PATCH			SDVO_I2C_RETURN_2 + +/** + * Reports which inputs are trained (managed to sync). + * + * Devices must have trained within 2 vsyncs of a mode change. + */ +#define SDVO_CMD_GET_TRAINED_INPUTS			0x03 +struct psb_intel_sdvo_get_trained_inputs_response { +    unsigned int input0_trained:1; +    unsigned int input1_trained:1; +    unsigned int pad:6; +} __attribute__((packed)); + +/** Returns a struct intel_sdvo_output_flags of active outputs. */ +#define SDVO_CMD_GET_ACTIVE_OUTPUTS			0x04 + +/** + * Sets the current set of active outputs. + * + * Takes a struct intel_sdvo_output_flags.  Must be preceded by a SET_IN_OUT_MAP + * on multi-output devices. + */ +#define SDVO_CMD_SET_ACTIVE_OUTPUTS			0x05 + +/** + * Returns the current mapping of SDVO inputs to outputs on the device. + * + * Returns two struct intel_sdvo_output_flags structures. + */ +#define SDVO_CMD_GET_IN_OUT_MAP				0x06 +struct psb_intel_sdvo_in_out_map { +    u16 in0, in1; +}; + +/** + * Sets the current mapping of SDVO inputs to outputs on the device. + * + * Takes two struct i380_sdvo_output_flags structures. + */ +#define SDVO_CMD_SET_IN_OUT_MAP				0x07 + +/** + * Returns a struct intel_sdvo_output_flags of attached displays. + */ +#define SDVO_CMD_GET_ATTACHED_DISPLAYS			0x0b + +/** + * Returns a struct intel_sdvo_ouptut_flags of displays supporting hot plugging. + */ +#define SDVO_CMD_GET_HOT_PLUG_SUPPORT			0x0c + +/** + * Takes a struct intel_sdvo_output_flags. + */ +#define SDVO_CMD_SET_ACTIVE_HOT_PLUG			0x0d + +/** + * Returns a struct intel_sdvo_output_flags of displays with hot plug + * interrupts enabled. + */ +#define SDVO_CMD_GET_ACTIVE_HOT_PLUG			0x0e + +#define SDVO_CMD_GET_INTERRUPT_EVENT_SOURCE		0x0f +struct intel_sdvo_get_interrupt_event_source_response { +    u16 interrupt_status; +    unsigned int ambient_light_interrupt:1; +    unsigned int hdmi_audio_encrypt_change:1; +    unsigned int pad:6; +} __attribute__((packed)); + +/** + * Selects which input is affected by future input commands. + * + * Commands affected include SET_INPUT_TIMINGS_PART[12], + * GET_INPUT_TIMINGS_PART[12], GET_PREFERRED_INPUT_TIMINGS_PART[12], + * GET_INPUT_PIXEL_CLOCK_RANGE, and CREATE_PREFERRED_INPUT_TIMINGS. + */ +#define SDVO_CMD_SET_TARGET_INPUT			0x10 +struct psb_intel_sdvo_set_target_input_args { +    unsigned int target_1:1; +    unsigned int pad:7; +} __attribute__((packed)); + +/** + * Takes a struct intel_sdvo_output_flags of which outputs are targeted by + * future output commands. + * + * Affected commands inclue SET_OUTPUT_TIMINGS_PART[12], + * GET_OUTPUT_TIMINGS_PART[12], and GET_OUTPUT_PIXEL_CLOCK_RANGE. + */ +#define SDVO_CMD_SET_TARGET_OUTPUT			0x11 + +#define SDVO_CMD_GET_INPUT_TIMINGS_PART1		0x12 +#define SDVO_CMD_GET_INPUT_TIMINGS_PART2		0x13 +#define SDVO_CMD_SET_INPUT_TIMINGS_PART1		0x14 +#define SDVO_CMD_SET_INPUT_TIMINGS_PART2		0x15 +#define SDVO_CMD_SET_OUTPUT_TIMINGS_PART1		0x16 +#define SDVO_CMD_SET_OUTPUT_TIMINGS_PART2		0x17 +#define SDVO_CMD_GET_OUTPUT_TIMINGS_PART1		0x18 +#define SDVO_CMD_GET_OUTPUT_TIMINGS_PART2		0x19 +/* Part 1 */ +# define SDVO_DTD_CLOCK_LOW				SDVO_I2C_ARG_0 +# define SDVO_DTD_CLOCK_HIGH				SDVO_I2C_ARG_1 +# define SDVO_DTD_H_ACTIVE				SDVO_I2C_ARG_2 +# define SDVO_DTD_H_BLANK				SDVO_I2C_ARG_3 +# define SDVO_DTD_H_HIGH				SDVO_I2C_ARG_4 +# define SDVO_DTD_V_ACTIVE				SDVO_I2C_ARG_5 +# define SDVO_DTD_V_BLANK				SDVO_I2C_ARG_6 +# define SDVO_DTD_V_HIGH				SDVO_I2C_ARG_7 +/* Part 2 */ +# define SDVO_DTD_HSYNC_OFF				SDVO_I2C_ARG_0 +# define SDVO_DTD_HSYNC_WIDTH				SDVO_I2C_ARG_1 +# define SDVO_DTD_VSYNC_OFF_WIDTH			SDVO_I2C_ARG_2 +# define SDVO_DTD_SYNC_OFF_WIDTH_HIGH			SDVO_I2C_ARG_3 +# define SDVO_DTD_DTD_FLAGS				SDVO_I2C_ARG_4 +# define SDVO_DTD_DTD_FLAG_INTERLACED				(1 << 7) +# define SDVO_DTD_DTD_FLAG_STEREO_MASK				(3 << 5) +# define SDVO_DTD_DTD_FLAG_INPUT_MASK				(3 << 3) +# define SDVO_DTD_DTD_FLAG_SYNC_MASK				(3 << 1) +# define SDVO_DTD_SDVO_FLAS				SDVO_I2C_ARG_5 +# define SDVO_DTD_SDVO_FLAG_STALL				(1 << 7) +# define SDVO_DTD_SDVO_FLAG_CENTERED				(0 << 6) +# define SDVO_DTD_SDVO_FLAG_UPPER_LEFT				(1 << 6) +# define SDVO_DTD_SDVO_FLAG_SCALING_MASK			(3 << 4) +# define SDVO_DTD_SDVO_FLAG_SCALING_NONE			(0 << 4) +# define SDVO_DTD_SDVO_FLAG_SCALING_SHARP			(1 << 4) +# define SDVO_DTD_SDVO_FLAG_SCALING_SMOOTH			(2 << 4) +# define SDVO_DTD_VSYNC_OFF_HIGH			SDVO_I2C_ARG_6 + +/** + * Generates a DTD based on the given width, height, and flags. + * + * This will be supported by any device supporting scaling or interlaced + * modes. + */ +#define SDVO_CMD_CREATE_PREFERRED_INPUT_TIMING		0x1a +# define SDVO_PREFERRED_INPUT_TIMING_CLOCK_LOW		SDVO_I2C_ARG_0 +# define SDVO_PREFERRED_INPUT_TIMING_CLOCK_HIGH		SDVO_I2C_ARG_1 +# define SDVO_PREFERRED_INPUT_TIMING_WIDTH_LOW		SDVO_I2C_ARG_2 +# define SDVO_PREFERRED_INPUT_TIMING_WIDTH_HIGH		SDVO_I2C_ARG_3 +# define SDVO_PREFERRED_INPUT_TIMING_HEIGHT_LOW		SDVO_I2C_ARG_4 +# define SDVO_PREFERRED_INPUT_TIMING_HEIGHT_HIGH	SDVO_I2C_ARG_5 +# define SDVO_PREFERRED_INPUT_TIMING_FLAGS		SDVO_I2C_ARG_6 +# define SDVO_PREFERRED_INPUT_TIMING_FLAGS_INTERLACED		(1 << 0) +# define SDVO_PREFERRED_INPUT_TIMING_FLAGS_SCALED		(1 << 1) + +#define SDVO_CMD_GET_PREFERRED_INPUT_TIMING_PART1	0x1b +#define SDVO_CMD_GET_PREFERRED_INPUT_TIMING_PART2	0x1c + +/** Returns a struct intel_sdvo_pixel_clock_range */ +#define SDVO_CMD_GET_INPUT_PIXEL_CLOCK_RANGE		0x1d +/** Returns a struct intel_sdvo_pixel_clock_range */ +#define SDVO_CMD_GET_OUTPUT_PIXEL_CLOCK_RANGE		0x1e + +/** Returns a byte bitfield containing SDVO_CLOCK_RATE_MULT_* flags */ +#define SDVO_CMD_GET_SUPPORTED_CLOCK_RATE_MULTS		0x1f + +/** Returns a byte containing a SDVO_CLOCK_RATE_MULT_* flag */ +#define SDVO_CMD_GET_CLOCK_RATE_MULT			0x20 +/** Takes a byte containing a SDVO_CLOCK_RATE_MULT_* flag */ +#define SDVO_CMD_SET_CLOCK_RATE_MULT			0x21 +# define SDVO_CLOCK_RATE_MULT_1X				(1 << 0) +# define SDVO_CLOCK_RATE_MULT_2X				(1 << 1) +# define SDVO_CLOCK_RATE_MULT_4X				(1 << 3) + +#define SDVO_CMD_GET_SUPPORTED_TV_FORMATS		0x27 +/** 6 bytes of bit flags for TV formats shared by all TV format functions */ +struct psb_intel_sdvo_tv_format { +    unsigned int ntsc_m:1; +    unsigned int ntsc_j:1; +    unsigned int ntsc_443:1; +    unsigned int pal_b:1; +    unsigned int pal_d:1; +    unsigned int pal_g:1; +    unsigned int pal_h:1; +    unsigned int pal_i:1; + +    unsigned int pal_m:1; +    unsigned int pal_n:1; +    unsigned int pal_nc:1; +    unsigned int pal_60:1; +    unsigned int secam_b:1; +    unsigned int secam_d:1; +    unsigned int secam_g:1; +    unsigned int secam_k:1; + +    unsigned int secam_k1:1; +    unsigned int secam_l:1; +    unsigned int secam_60:1; +    unsigned int hdtv_std_smpte_240m_1080i_59:1; +    unsigned int hdtv_std_smpte_240m_1080i_60:1; +    unsigned int hdtv_std_smpte_260m_1080i_59:1; +    unsigned int hdtv_std_smpte_260m_1080i_60:1; +    unsigned int hdtv_std_smpte_274m_1080i_50:1; + +    unsigned int hdtv_std_smpte_274m_1080i_59:1; +    unsigned int hdtv_std_smpte_274m_1080i_60:1; +    unsigned int hdtv_std_smpte_274m_1080p_23:1; +    unsigned int hdtv_std_smpte_274m_1080p_24:1; +    unsigned int hdtv_std_smpte_274m_1080p_25:1; +    unsigned int hdtv_std_smpte_274m_1080p_29:1; +    unsigned int hdtv_std_smpte_274m_1080p_30:1; +    unsigned int hdtv_std_smpte_274m_1080p_50:1; + +    unsigned int hdtv_std_smpte_274m_1080p_59:1; +    unsigned int hdtv_std_smpte_274m_1080p_60:1; +    unsigned int hdtv_std_smpte_295m_1080i_50:1; +    unsigned int hdtv_std_smpte_295m_1080p_50:1; +    unsigned int hdtv_std_smpte_296m_720p_59:1; +    unsigned int hdtv_std_smpte_296m_720p_60:1; +    unsigned int hdtv_std_smpte_296m_720p_50:1; +    unsigned int hdtv_std_smpte_293m_480p_59:1; + +    unsigned int hdtv_std_smpte_170m_480i_59:1; +    unsigned int hdtv_std_iturbt601_576i_50:1; +    unsigned int hdtv_std_iturbt601_576p_50:1; +    unsigned int hdtv_std_eia_7702a_480i_60:1; +    unsigned int hdtv_std_eia_7702a_480p_60:1; +    unsigned int pad:3; +} __attribute__((packed)); + +#define SDVO_CMD_GET_TV_FORMAT				0x28 + +#define SDVO_CMD_SET_TV_FORMAT				0x29 + +/** Returns the resolutiosn that can be used with the given TV format */ +#define SDVO_CMD_GET_SDTV_RESOLUTION_SUPPORT		0x83 +struct psb_intel_sdvo_sdtv_resolution_request { +    unsigned int ntsc_m:1; +    unsigned int ntsc_j:1; +    unsigned int ntsc_443:1; +    unsigned int pal_b:1; +    unsigned int pal_d:1; +    unsigned int pal_g:1; +    unsigned int pal_h:1; +    unsigned int pal_i:1; + +    unsigned int pal_m:1; +    unsigned int pal_n:1; +    unsigned int pal_nc:1; +    unsigned int pal_60:1; +    unsigned int secam_b:1; +    unsigned int secam_d:1; +    unsigned int secam_g:1; +    unsigned int secam_k:1; + +    unsigned int secam_k1:1; +    unsigned int secam_l:1; +    unsigned int secam_60:1; +    unsigned int pad:5; +} __attribute__((packed)); + +struct psb_intel_sdvo_sdtv_resolution_reply { +    unsigned int res_320x200:1; +    unsigned int res_320x240:1; +    unsigned int res_400x300:1; +    unsigned int res_640x350:1; +    unsigned int res_640x400:1; +    unsigned int res_640x480:1; +    unsigned int res_704x480:1; +    unsigned int res_704x576:1; + +    unsigned int res_720x350:1; +    unsigned int res_720x400:1; +    unsigned int res_720x480:1; +    unsigned int res_720x540:1; +    unsigned int res_720x576:1; +    unsigned int res_768x576:1; +    unsigned int res_800x600:1; +    unsigned int res_832x624:1; + +    unsigned int res_920x766:1; +    unsigned int res_1024x768:1; +    unsigned int res_1280x1024:1; +    unsigned int pad:5; +} __attribute__((packed)); + +/* Get supported resolution with squire pixel aspect ratio that can be +   scaled for the requested HDTV format */ +#define SDVO_CMD_GET_SCALED_HDTV_RESOLUTION_SUPPORT		0x85 + +struct psb_intel_sdvo_hdtv_resolution_request { +    unsigned int hdtv_std_smpte_240m_1080i_59:1; +    unsigned int hdtv_std_smpte_240m_1080i_60:1; +    unsigned int hdtv_std_smpte_260m_1080i_59:1; +    unsigned int hdtv_std_smpte_260m_1080i_60:1; +    unsigned int hdtv_std_smpte_274m_1080i_50:1; +    unsigned int hdtv_std_smpte_274m_1080i_59:1; +    unsigned int hdtv_std_smpte_274m_1080i_60:1; +    unsigned int hdtv_std_smpte_274m_1080p_23:1; + +    unsigned int hdtv_std_smpte_274m_1080p_24:1; +    unsigned int hdtv_std_smpte_274m_1080p_25:1; +    unsigned int hdtv_std_smpte_274m_1080p_29:1; +    unsigned int hdtv_std_smpte_274m_1080p_30:1; +    unsigned int hdtv_std_smpte_274m_1080p_50:1; +    unsigned int hdtv_std_smpte_274m_1080p_59:1; +    unsigned int hdtv_std_smpte_274m_1080p_60:1; +    unsigned int hdtv_std_smpte_295m_1080i_50:1; + +    unsigned int hdtv_std_smpte_295m_1080p_50:1; +    unsigned int hdtv_std_smpte_296m_720p_59:1; +    unsigned int hdtv_std_smpte_296m_720p_60:1; +    unsigned int hdtv_std_smpte_296m_720p_50:1; +    unsigned int hdtv_std_smpte_293m_480p_59:1; +    unsigned int hdtv_std_smpte_170m_480i_59:1; +    unsigned int hdtv_std_iturbt601_576i_50:1; +    unsigned int hdtv_std_iturbt601_576p_50:1; + +    unsigned int hdtv_std_eia_7702a_480i_60:1; +    unsigned int hdtv_std_eia_7702a_480p_60:1; +    unsigned int pad:6; +} __attribute__((packed)); + +struct psb_intel_sdvo_hdtv_resolution_reply { +    unsigned int res_640x480:1; +    unsigned int res_800x600:1; +    unsigned int res_1024x768:1; +    unsigned int res_1280x960:1; +    unsigned int res_1400x1050:1; +    unsigned int res_1600x1200:1; +    unsigned int res_1920x1440:1; +    unsigned int res_2048x1536:1; + +    unsigned int res_2560x1920:1; +    unsigned int res_3200x2400:1; +    unsigned int res_3840x2880:1; +    unsigned int pad1:5; + +    unsigned int res_848x480:1; +    unsigned int res_1064x600:1; +    unsigned int res_1280x720:1; +    unsigned int res_1360x768:1; +    unsigned int res_1704x960:1; +    unsigned int res_1864x1050:1; +    unsigned int res_1920x1080:1; +    unsigned int res_2128x1200:1; + +    unsigned int res_2560x1400:1; +    unsigned int res_2728x1536:1; +    unsigned int res_3408x1920:1; +    unsigned int res_4264x2400:1; +    unsigned int res_5120x2880:1; +    unsigned int pad2:3; + +    unsigned int res_768x480:1; +    unsigned int res_960x600:1; +    unsigned int res_1152x720:1; +    unsigned int res_1124x768:1; +    unsigned int res_1536x960:1; +    unsigned int res_1680x1050:1; +    unsigned int res_1728x1080:1; +    unsigned int res_1920x1200:1; + +    unsigned int res_2304x1440:1; +    unsigned int res_2456x1536:1; +    unsigned int res_3072x1920:1; +    unsigned int res_3840x2400:1; +    unsigned int res_4608x2880:1; +    unsigned int pad3:3; + +    unsigned int res_1280x1024:1; +    unsigned int pad4:7; + +    unsigned int res_1280x768:1; +    unsigned int pad5:7; +} __attribute__((packed)); + +/* Get supported power state returns info for encoder and monitor, rely on +   last SetTargetInput and SetTargetOutput calls */ +#define SDVO_CMD_GET_SUPPORTED_POWER_STATES		0x2a +/* Get power state returns info for encoder and monitor, rely on last +   SetTargetInput and SetTargetOutput calls */ +#define SDVO_CMD_GET_POWER_STATE			0x2b +#define SDVO_CMD_GET_ENCODER_POWER_STATE		0x2b +#define SDVO_CMD_SET_ENCODER_POWER_STATE		0x2c +# define SDVO_ENCODER_STATE_ON					(1 << 0) +# define SDVO_ENCODER_STATE_STANDBY				(1 << 1) +# define SDVO_ENCODER_STATE_SUSPEND				(1 << 2) +# define SDVO_ENCODER_STATE_OFF					(1 << 3) +# define SDVO_MONITOR_STATE_ON					(1 << 4) +# define SDVO_MONITOR_STATE_STANDBY				(1 << 5) +# define SDVO_MONITOR_STATE_SUSPEND				(1 << 6) +# define SDVO_MONITOR_STATE_OFF					(1 << 7) + +#define SDVO_CMD_GET_MAX_PANEL_POWER_SEQUENCING		0x2d +#define SDVO_CMD_GET_PANEL_POWER_SEQUENCING		0x2e +#define SDVO_CMD_SET_PANEL_POWER_SEQUENCING		0x2f +/** + * The panel power sequencing parameters are in units of milliseconds. + * The high fields are bits 8:9 of the 10-bit values. + */ +struct psb_sdvo_panel_power_sequencing { +    u8 t0; +    u8 t1; +    u8 t2; +    u8 t3; +    u8 t4; + +    unsigned int t0_high:2; +    unsigned int t1_high:2; +    unsigned int t2_high:2; +    unsigned int t3_high:2; + +    unsigned int t4_high:2; +    unsigned int pad:6; +} __attribute__((packed)); + +#define SDVO_CMD_GET_MAX_BACKLIGHT_LEVEL		0x30 +struct sdvo_max_backlight_reply { +    u8 max_value; +    u8 default_value; +} __attribute__((packed)); + +#define SDVO_CMD_GET_BACKLIGHT_LEVEL			0x31 +#define SDVO_CMD_SET_BACKLIGHT_LEVEL			0x32 + +#define SDVO_CMD_GET_AMBIENT_LIGHT			0x33 +struct sdvo_get_ambient_light_reply { +    u16 trip_low; +    u16 trip_high; +    u16 value; +} __attribute__((packed)); +#define SDVO_CMD_SET_AMBIENT_LIGHT			0x34 +struct sdvo_set_ambient_light_reply { +    u16 trip_low; +    u16 trip_high; +    unsigned int enable:1; +    unsigned int pad:7; +} __attribute__((packed)); + +/* Set display power state */ +#define SDVO_CMD_SET_DISPLAY_POWER_STATE		0x7d +# define SDVO_DISPLAY_STATE_ON				(1 << 0) +# define SDVO_DISPLAY_STATE_STANDBY			(1 << 1) +# define SDVO_DISPLAY_STATE_SUSPEND			(1 << 2) +# define SDVO_DISPLAY_STATE_OFF				(1 << 3) + +#define SDVO_CMD_GET_SUPPORTED_ENHANCEMENTS		0x84 +struct psb_intel_sdvo_enhancements_reply { +    unsigned int flicker_filter:1; +    unsigned int flicker_filter_adaptive:1; +    unsigned int flicker_filter_2d:1; +    unsigned int saturation:1; +    unsigned int hue:1; +    unsigned int brightness:1; +    unsigned int contrast:1; +    unsigned int overscan_h:1; + +    unsigned int overscan_v:1; +    unsigned int hpos:1; +    unsigned int vpos:1; +    unsigned int sharpness:1; +    unsigned int dot_crawl:1; +    unsigned int dither:1; +    unsigned int tv_chroma_filter:1; +    unsigned int tv_luma_filter:1; +} __attribute__((packed)); + +/* Picture enhancement limits below are dependent on the current TV format, + * and thus need to be queried and set after it. + */ +#define SDVO_CMD_GET_MAX_FLICKER_FILTER			0x4d +#define SDVO_CMD_GET_MAX_FLICKER_FILTER_ADAPTIVE	0x7b +#define SDVO_CMD_GET_MAX_FLICKER_FILTER_2D		0x52 +#define SDVO_CMD_GET_MAX_SATURATION			0x55 +#define SDVO_CMD_GET_MAX_HUE				0x58 +#define SDVO_CMD_GET_MAX_BRIGHTNESS			0x5b +#define SDVO_CMD_GET_MAX_CONTRAST			0x5e +#define SDVO_CMD_GET_MAX_OVERSCAN_H			0x61 +#define SDVO_CMD_GET_MAX_OVERSCAN_V			0x64 +#define SDVO_CMD_GET_MAX_HPOS				0x67 +#define SDVO_CMD_GET_MAX_VPOS				0x6a +#define SDVO_CMD_GET_MAX_SHARPNESS			0x6d +#define SDVO_CMD_GET_MAX_TV_CHROMA_FILTER		0x74 +#define SDVO_CMD_GET_MAX_TV_LUMA_FILTER			0x77 +struct psb_intel_sdvo_enhancement_limits_reply { +    u16 max_value; +    u16 default_value; +} __attribute__((packed)); + +#define SDVO_CMD_GET_LVDS_PANEL_INFORMATION		0x7f +#define SDVO_CMD_SET_LVDS_PANEL_INFORMATION		0x80 +# define SDVO_LVDS_COLOR_DEPTH_18			(0 << 0) +# define SDVO_LVDS_COLOR_DEPTH_24			(1 << 0) +# define SDVO_LVDS_CONNECTOR_SPWG			(0 << 2) +# define SDVO_LVDS_CONNECTOR_OPENLDI			(1 << 2) +# define SDVO_LVDS_SINGLE_CHANNEL			(0 << 4) +# define SDVO_LVDS_DUAL_CHANNEL				(1 << 4) + +#define SDVO_CMD_GET_FLICKER_FILTER			0x4e +#define SDVO_CMD_SET_FLICKER_FILTER			0x4f +#define SDVO_CMD_GET_FLICKER_FILTER_ADAPTIVE		0x50 +#define SDVO_CMD_SET_FLICKER_FILTER_ADAPTIVE		0x51 +#define SDVO_CMD_GET_FLICKER_FILTER_2D			0x53 +#define SDVO_CMD_SET_FLICKER_FILTER_2D			0x54 +#define SDVO_CMD_GET_SATURATION				0x56 +#define SDVO_CMD_SET_SATURATION				0x57 +#define SDVO_CMD_GET_HUE				0x59 +#define SDVO_CMD_SET_HUE				0x5a +#define SDVO_CMD_GET_BRIGHTNESS				0x5c +#define SDVO_CMD_SET_BRIGHTNESS				0x5d +#define SDVO_CMD_GET_CONTRAST				0x5f +#define SDVO_CMD_SET_CONTRAST				0x60 +#define SDVO_CMD_GET_OVERSCAN_H				0x62 +#define SDVO_CMD_SET_OVERSCAN_H				0x63 +#define SDVO_CMD_GET_OVERSCAN_V				0x65 +#define SDVO_CMD_SET_OVERSCAN_V				0x66 +#define SDVO_CMD_GET_HPOS				0x68 +#define SDVO_CMD_SET_HPOS				0x69 +#define SDVO_CMD_GET_VPOS				0x6b +#define SDVO_CMD_SET_VPOS				0x6c +#define SDVO_CMD_GET_SHARPNESS				0x6e +#define SDVO_CMD_SET_SHARPNESS				0x6f +#define SDVO_CMD_GET_TV_CHROMA_FILTER			0x75 +#define SDVO_CMD_SET_TV_CHROMA_FILTER			0x76 +#define SDVO_CMD_GET_TV_LUMA_FILTER			0x78 +#define SDVO_CMD_SET_TV_LUMA_FILTER			0x79 +struct psb_intel_sdvo_enhancements_arg { +    u16 value; +}__attribute__((packed)); + +#define SDVO_CMD_GET_DOT_CRAWL				0x70 +#define SDVO_CMD_SET_DOT_CRAWL				0x71 +# define SDVO_DOT_CRAWL_ON					(1 << 0) +# define SDVO_DOT_CRAWL_DEFAULT_ON				(1 << 1) + +#define SDVO_CMD_GET_DITHER				0x72 +#define SDVO_CMD_SET_DITHER				0x73 +# define SDVO_DITHER_ON						(1 << 0) +# define SDVO_DITHER_DEFAULT_ON					(1 << 1) + +#define SDVO_CMD_SET_CONTROL_BUS_SWITCH			0x7a +# define SDVO_CONTROL_BUS_PROM				(1 << 0) +# define SDVO_CONTROL_BUS_DDC1				(1 << 1) +# define SDVO_CONTROL_BUS_DDC2				(1 << 2) +# define SDVO_CONTROL_BUS_DDC3				(1 << 3) + +/* HDMI op codes */ +#define SDVO_CMD_GET_SUPP_ENCODE	0x9d +#define SDVO_CMD_GET_ENCODE		0x9e +#define SDVO_CMD_SET_ENCODE		0x9f +  #define SDVO_ENCODE_DVI	0x0 +  #define SDVO_ENCODE_HDMI	0x1 +#define SDVO_CMD_SET_PIXEL_REPLI	0x8b +#define SDVO_CMD_GET_PIXEL_REPLI	0x8c +#define SDVO_CMD_GET_COLORIMETRY_CAP	0x8d +#define SDVO_CMD_SET_COLORIMETRY	0x8e +  #define SDVO_COLORIMETRY_RGB256   0x0 +  #define SDVO_COLORIMETRY_RGB220   0x1 +  #define SDVO_COLORIMETRY_YCrCb422 0x3 +  #define SDVO_COLORIMETRY_YCrCb444 0x4 +#define SDVO_CMD_GET_COLORIMETRY	0x8f +#define SDVO_CMD_GET_AUDIO_ENCRYPT_PREFER 0x90 +#define SDVO_CMD_SET_AUDIO_STAT		0x91 +#define SDVO_CMD_GET_AUDIO_STAT		0x92 +#define SDVO_CMD_SET_HBUF_INDEX		0x93 +#define SDVO_CMD_GET_HBUF_INDEX		0x94 +#define SDVO_CMD_GET_HBUF_INFO		0x95 +#define SDVO_CMD_SET_HBUF_AV_SPLIT	0x96 +#define SDVO_CMD_GET_HBUF_AV_SPLIT	0x97 +#define SDVO_CMD_SET_HBUF_DATA		0x98 +#define SDVO_CMD_GET_HBUF_DATA		0x99 +#define SDVO_CMD_SET_HBUF_TXRATE	0x9a +#define SDVO_CMD_GET_HBUF_TXRATE	0x9b +  #define SDVO_HBUF_TX_DISABLED	(0 << 6) +  #define SDVO_HBUF_TX_ONCE	(2 << 6) +  #define SDVO_HBUF_TX_VSYNC	(3 << 6) +#define SDVO_CMD_GET_AUDIO_TX_INFO	0x9c +#define SDVO_NEED_TO_STALL  (1 << 7) + +struct psb_intel_sdvo_encode { +    u8 dvi_rev; +    u8 hdmi_rev; +} __attribute__ ((packed)); diff --git a/drivers/gpu/drm/gma500/psb_irq.c b/drivers/gpu/drm/gma500/psb_irq.c new file mode 100644 index 00000000000..624eb36511c --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_irq.c @@ -0,0 +1,684 @@ +/************************************************************************** + * Copyright (c) 2007, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to + * develop this driver. + * + **************************************************************************/ +/* + */ + +#include <drm/drmP.h> +#include "psb_drv.h" +#include "psb_reg.h" +#include "psb_intel_reg.h" +#include "power.h" +#include "psb_irq.h" +#include "mdfld_output.h" + +/* + * inline functions + */ + +static inline u32 +psb_pipestat(int pipe) +{ +	if (pipe == 0) +		return PIPEASTAT; +	if (pipe == 1) +		return PIPEBSTAT; +	if (pipe == 2) +		return PIPECSTAT; +	BUG(); +} + +static inline u32 +mid_pipe_event(int pipe) +{ +	if (pipe == 0) +		return _PSB_PIPEA_EVENT_FLAG; +	if (pipe == 1) +		return _MDFLD_PIPEB_EVENT_FLAG; +	if (pipe == 2) +		return _MDFLD_PIPEC_EVENT_FLAG; +	BUG(); +} + +static inline u32 +mid_pipe_vsync(int pipe) +{ +	if (pipe == 0) +		return _PSB_VSYNC_PIPEA_FLAG; +	if (pipe == 1) +		return _PSB_VSYNC_PIPEB_FLAG; +	if (pipe == 2) +		return _MDFLD_PIPEC_VBLANK_FLAG; +	BUG(); +} + +static inline u32 +mid_pipeconf(int pipe) +{ +	if (pipe == 0) +		return PIPEACONF; +	if (pipe == 1) +		return PIPEBCONF; +	if (pipe == 2) +		return PIPECCONF; +	BUG(); +} + +void +psb_enable_pipestat(struct drm_psb_private *dev_priv, int pipe, u32 mask) +{ +	if ((dev_priv->pipestat[pipe] & mask) != mask) { +		u32 reg = psb_pipestat(pipe); +		dev_priv->pipestat[pipe] |= mask; +		/* Enable the interrupt, clear any pending status */ +		if (gma_power_begin(dev_priv->dev, false)) { +			u32 writeVal = PSB_RVDC32(reg); +			writeVal |= (mask | (mask >> 16)); +			PSB_WVDC32(writeVal, reg); +			(void) PSB_RVDC32(reg); +			gma_power_end(dev_priv->dev); +		} +	} +} + +void +psb_disable_pipestat(struct drm_psb_private *dev_priv, int pipe, u32 mask) +{ +	if ((dev_priv->pipestat[pipe] & mask) != 0) { +		u32 reg = psb_pipestat(pipe); +		dev_priv->pipestat[pipe] &= ~mask; +		if (gma_power_begin(dev_priv->dev, false)) { +			u32 writeVal = PSB_RVDC32(reg); +			writeVal &= ~mask; +			PSB_WVDC32(writeVal, reg); +			(void) PSB_RVDC32(reg); +			gma_power_end(dev_priv->dev); +		} +	} +} + +static void mid_enable_pipe_event(struct drm_psb_private *dev_priv, int pipe) +{ +	if (gma_power_begin(dev_priv->dev, false)) { +		u32 pipe_event = mid_pipe_event(pipe); +		dev_priv->vdc_irq_mask |= pipe_event; +		PSB_WVDC32(~dev_priv->vdc_irq_mask, PSB_INT_MASK_R); +		PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R); +		gma_power_end(dev_priv->dev); +	} +} + +static void mid_disable_pipe_event(struct drm_psb_private *dev_priv, int pipe) +{ +	if (dev_priv->pipestat[pipe] == 0) { +		if (gma_power_begin(dev_priv->dev, false)) { +			u32 pipe_event = mid_pipe_event(pipe); +			dev_priv->vdc_irq_mask &= ~pipe_event; +			PSB_WVDC32(~dev_priv->vdc_irq_mask, PSB_INT_MASK_R); +			PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R); +			gma_power_end(dev_priv->dev); +		} +	} +} + +/** + * Display controller interrupt handler for pipe event. + * + */ +static void mid_pipe_event_handler(struct drm_device *dev, int pipe) +{ +	struct drm_psb_private *dev_priv = +	    (struct drm_psb_private *) dev->dev_private; + +	uint32_t pipe_stat_val = 0; +	uint32_t pipe_stat_reg = psb_pipestat(pipe); +	uint32_t pipe_enable = dev_priv->pipestat[pipe]; +	uint32_t pipe_status = dev_priv->pipestat[pipe] >> 16; +	uint32_t pipe_clear; +	uint32_t i = 0; + +	spin_lock(&dev_priv->irqmask_lock); + +	pipe_stat_val = PSB_RVDC32(pipe_stat_reg); +	pipe_stat_val &= pipe_enable | pipe_status; +	pipe_stat_val &= pipe_stat_val >> 16; + +	spin_unlock(&dev_priv->irqmask_lock); + +	/* Clear the 2nd level interrupt status bits +	 * Sometimes the bits are very sticky so we repeat until they unstick */ +	for (i = 0; i < 0xffff; i++) { +		PSB_WVDC32(PSB_RVDC32(pipe_stat_reg), pipe_stat_reg); +		pipe_clear = PSB_RVDC32(pipe_stat_reg) & pipe_status; + +		if (pipe_clear == 0) +			break; +	} + +	if (pipe_clear) +		dev_err(dev->dev, +		"%s, can't clear status bits for pipe %d, its value = 0x%x.\n", +		__func__, pipe, PSB_RVDC32(pipe_stat_reg)); + +	if (pipe_stat_val & PIPE_VBLANK_STATUS) +		drm_handle_vblank(dev, pipe); + +	if (pipe_stat_val & PIPE_TE_STATUS) +		drm_handle_vblank(dev, pipe); +} + +/* + * Display controller interrupt handler. + */ +static void psb_vdc_interrupt(struct drm_device *dev, uint32_t vdc_stat) +{ +	if (vdc_stat & _PSB_IRQ_ASLE) +		psb_intel_opregion_asle_intr(dev); + +	if (vdc_stat & _PSB_VSYNC_PIPEA_FLAG) +		mid_pipe_event_handler(dev, 0); + +	if (vdc_stat & _PSB_VSYNC_PIPEB_FLAG) +		mid_pipe_event_handler(dev, 1); +} + +/* + * SGX interrupt handler + */ +static void psb_sgx_interrupt(struct drm_device *dev, u32 stat_1, u32 stat_2) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	u32 val, addr; +	int error = false; + +	if (stat_1 & _PSB_CE_TWOD_COMPLETE) +		val = PSB_RSGX32(PSB_CR_2D_BLIT_STATUS); + +	if (stat_2 & _PSB_CE2_BIF_REQUESTER_FAULT) { +		val = PSB_RSGX32(PSB_CR_BIF_INT_STAT); +		addr = PSB_RSGX32(PSB_CR_BIF_FAULT); +		if (val) { +			if (val & _PSB_CBI_STAT_PF_N_RW) +				DRM_ERROR("SGX MMU page fault:"); +			else +				DRM_ERROR("SGX MMU read / write protection fault:"); + +			if (val & _PSB_CBI_STAT_FAULT_CACHE) +				DRM_ERROR("\tCache requestor"); +			if (val & _PSB_CBI_STAT_FAULT_TA) +				DRM_ERROR("\tTA requestor"); +			if (val & _PSB_CBI_STAT_FAULT_VDM) +				DRM_ERROR("\tVDM requestor"); +			if (val & _PSB_CBI_STAT_FAULT_2D) +				DRM_ERROR("\t2D requestor"); +			if (val & _PSB_CBI_STAT_FAULT_PBE) +				DRM_ERROR("\tPBE requestor"); +			if (val & _PSB_CBI_STAT_FAULT_TSP) +				DRM_ERROR("\tTSP requestor"); +			if (val & _PSB_CBI_STAT_FAULT_ISP) +				DRM_ERROR("\tISP requestor"); +			if (val & _PSB_CBI_STAT_FAULT_USSEPDS) +				DRM_ERROR("\tUSSEPDS requestor"); +			if (val & _PSB_CBI_STAT_FAULT_HOST) +				DRM_ERROR("\tHost requestor"); + +			DRM_ERROR("\tMMU failing address is 0x%08x.\n", +				  (unsigned int)addr); +			error = true; +		} +	} + +	/* Clear bits */ +	PSB_WSGX32(stat_1, PSB_CR_EVENT_HOST_CLEAR); +	PSB_WSGX32(stat_2, PSB_CR_EVENT_HOST_CLEAR2); +	PSB_RSGX32(PSB_CR_EVENT_HOST_CLEAR2); +} + +irqreturn_t psb_irq_handler(int irq, void *arg) +{ +	struct drm_device *dev = arg; +	struct drm_psb_private *dev_priv = dev->dev_private; +	uint32_t vdc_stat, dsp_int = 0, sgx_int = 0, hotplug_int = 0; +	u32 sgx_stat_1, sgx_stat_2; +	int handled = 0; + +	spin_lock(&dev_priv->irqmask_lock); + +	vdc_stat = PSB_RVDC32(PSB_INT_IDENTITY_R); + +	if (vdc_stat & (_PSB_PIPE_EVENT_FLAG|_PSB_IRQ_ASLE)) +		dsp_int = 1; + +	/* FIXME: Handle Medfield +	if (vdc_stat & _MDFLD_DISP_ALL_IRQ_FLAG) +		dsp_int = 1; +	*/ + +	if (vdc_stat & _PSB_IRQ_SGX_FLAG) +		sgx_int = 1; +	if (vdc_stat & _PSB_IRQ_DISP_HOTSYNC) +		hotplug_int = 1; + +	vdc_stat &= dev_priv->vdc_irq_mask; +	spin_unlock(&dev_priv->irqmask_lock); + +	if (dsp_int && gma_power_is_on(dev)) { +		psb_vdc_interrupt(dev, vdc_stat); +		handled = 1; +	} + +	if (sgx_int) { +		sgx_stat_1 = PSB_RSGX32(PSB_CR_EVENT_STATUS); +		sgx_stat_2 = PSB_RSGX32(PSB_CR_EVENT_STATUS2); +		psb_sgx_interrupt(dev, sgx_stat_1, sgx_stat_2); +		handled = 1; +	} + +	/* Note: this bit has other meanings on some devices, so we will +	   need to address that later if it ever matters */ +	if (hotplug_int && dev_priv->ops->hotplug) { +		handled = dev_priv->ops->hotplug(dev); +		REG_WRITE(PORT_HOTPLUG_STAT, REG_READ(PORT_HOTPLUG_STAT)); +	} + +	PSB_WVDC32(vdc_stat, PSB_INT_IDENTITY_R); +	(void) PSB_RVDC32(PSB_INT_IDENTITY_R); +	rmb(); + +	if (!handled) +		return IRQ_NONE; + +	return IRQ_HANDLED; +} + +void psb_irq_preinstall(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = +	    (struct drm_psb_private *) dev->dev_private; +	unsigned long irqflags; + +	spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); + +	if (gma_power_is_on(dev)) { +		PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM); +		PSB_WVDC32(0x00000000, PSB_INT_MASK_R); +		PSB_WVDC32(0x00000000, PSB_INT_ENABLE_R); +		PSB_WSGX32(0x00000000, PSB_CR_EVENT_HOST_ENABLE); +		PSB_RSGX32(PSB_CR_EVENT_HOST_ENABLE); +	} +	if (dev->vblank[0].enabled) +		dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEA_FLAG; +	if (dev->vblank[1].enabled) +		dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEB_FLAG; + +	/* FIXME: Handle Medfield irq mask +	if (dev->vblank[1].enabled) +		dev_priv->vdc_irq_mask |= _MDFLD_PIPEB_EVENT_FLAG; +	if (dev->vblank[2].enabled) +		dev_priv->vdc_irq_mask |= _MDFLD_PIPEC_EVENT_FLAG; +	*/ + +	/* Revisit this area - want per device masks ? */ +	if (dev_priv->ops->hotplug) +		dev_priv->vdc_irq_mask |= _PSB_IRQ_DISP_HOTSYNC; +	dev_priv->vdc_irq_mask |= _PSB_IRQ_ASLE | _PSB_IRQ_SGX_FLAG; + +	/* This register is safe even if display island is off */ +	PSB_WVDC32(~dev_priv->vdc_irq_mask, PSB_INT_MASK_R); +	spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); +} + +int psb_irq_postinstall(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	unsigned long irqflags; + +	spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); + +	/* Enable 2D and MMU fault interrupts */ +	PSB_WSGX32(_PSB_CE2_BIF_REQUESTER_FAULT, PSB_CR_EVENT_HOST_ENABLE2); +	PSB_WSGX32(_PSB_CE_TWOD_COMPLETE, PSB_CR_EVENT_HOST_ENABLE); +	PSB_RSGX32(PSB_CR_EVENT_HOST_ENABLE); /* Post */ + +	/* This register is safe even if display island is off */ +	PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R); +	PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM); + +	if (dev->vblank[0].enabled) +		psb_enable_pipestat(dev_priv, 0, PIPE_VBLANK_INTERRUPT_ENABLE); +	else +		psb_disable_pipestat(dev_priv, 0, PIPE_VBLANK_INTERRUPT_ENABLE); + +	if (dev->vblank[1].enabled) +		psb_enable_pipestat(dev_priv, 1, PIPE_VBLANK_INTERRUPT_ENABLE); +	else +		psb_disable_pipestat(dev_priv, 1, PIPE_VBLANK_INTERRUPT_ENABLE); + +	if (dev->vblank[2].enabled) +		psb_enable_pipestat(dev_priv, 2, PIPE_VBLANK_INTERRUPT_ENABLE); +	else +		psb_disable_pipestat(dev_priv, 2, PIPE_VBLANK_INTERRUPT_ENABLE); + +	if (dev_priv->ops->hotplug_enable) +		dev_priv->ops->hotplug_enable(dev, true); + +	spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); +	return 0; +} + +void psb_irq_uninstall(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	unsigned long irqflags; + +	spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); + +	if (dev_priv->ops->hotplug_enable) +		dev_priv->ops->hotplug_enable(dev, false); + +	PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM); + +	if (dev->vblank[0].enabled) +		psb_disable_pipestat(dev_priv, 0, PIPE_VBLANK_INTERRUPT_ENABLE); + +	if (dev->vblank[1].enabled) +		psb_disable_pipestat(dev_priv, 1, PIPE_VBLANK_INTERRUPT_ENABLE); + +	if (dev->vblank[2].enabled) +		psb_disable_pipestat(dev_priv, 2, PIPE_VBLANK_INTERRUPT_ENABLE); + +	dev_priv->vdc_irq_mask &= _PSB_IRQ_SGX_FLAG | +				  _PSB_IRQ_MSVDX_FLAG | +				  _LNC_IRQ_TOPAZ_FLAG; + +	/* These two registers are safe even if display island is off */ +	PSB_WVDC32(~dev_priv->vdc_irq_mask, PSB_INT_MASK_R); +	PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R); + +	wmb(); + +	/* This register is safe even if display island is off */ +	PSB_WVDC32(PSB_RVDC32(PSB_INT_IDENTITY_R), PSB_INT_IDENTITY_R); +	spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); +} + +void psb_irq_turn_on_dpst(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = +		(struct drm_psb_private *) dev->dev_private; +	u32 hist_reg; +	u32 pwm_reg; + +	if (gma_power_begin(dev, false)) { +		PSB_WVDC32(1 << 31, HISTOGRAM_LOGIC_CONTROL); +		hist_reg = PSB_RVDC32(HISTOGRAM_LOGIC_CONTROL); +		PSB_WVDC32(1 << 31, HISTOGRAM_INT_CONTROL); +		hist_reg = PSB_RVDC32(HISTOGRAM_INT_CONTROL); + +		PSB_WVDC32(0x80010100, PWM_CONTROL_LOGIC); +		pwm_reg = PSB_RVDC32(PWM_CONTROL_LOGIC); +		PSB_WVDC32(pwm_reg | PWM_PHASEIN_ENABLE +						| PWM_PHASEIN_INT_ENABLE, +							   PWM_CONTROL_LOGIC); +		pwm_reg = PSB_RVDC32(PWM_CONTROL_LOGIC); + +		psb_enable_pipestat(dev_priv, 0, PIPE_DPST_EVENT_ENABLE); + +		hist_reg = PSB_RVDC32(HISTOGRAM_INT_CONTROL); +		PSB_WVDC32(hist_reg | HISTOGRAM_INT_CTRL_CLEAR, +							HISTOGRAM_INT_CONTROL); +		pwm_reg = PSB_RVDC32(PWM_CONTROL_LOGIC); +		PSB_WVDC32(pwm_reg | 0x80010100 | PWM_PHASEIN_ENABLE, +							PWM_CONTROL_LOGIC); + +		gma_power_end(dev); +	} +} + +int psb_irq_enable_dpst(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = +		(struct drm_psb_private *) dev->dev_private; +	unsigned long irqflags; + +	spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); + +	/* enable DPST */ +	mid_enable_pipe_event(dev_priv, 0); +	psb_irq_turn_on_dpst(dev); + +	spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); +	return 0; +} + +void psb_irq_turn_off_dpst(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = +	    (struct drm_psb_private *) dev->dev_private; +	u32 hist_reg; +	u32 pwm_reg; + +	if (gma_power_begin(dev, false)) { +		PSB_WVDC32(0x00000000, HISTOGRAM_INT_CONTROL); +		hist_reg = PSB_RVDC32(HISTOGRAM_INT_CONTROL); + +		psb_disable_pipestat(dev_priv, 0, PIPE_DPST_EVENT_ENABLE); + +		pwm_reg = PSB_RVDC32(PWM_CONTROL_LOGIC); +		PSB_WVDC32(pwm_reg & ~PWM_PHASEIN_INT_ENABLE, +							PWM_CONTROL_LOGIC); +		pwm_reg = PSB_RVDC32(PWM_CONTROL_LOGIC); + +		gma_power_end(dev); +	} +} + +int psb_irq_disable_dpst(struct drm_device *dev) +{ +	struct drm_psb_private *dev_priv = +	    (struct drm_psb_private *) dev->dev_private; +	unsigned long irqflags; + +	spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); + +	mid_disable_pipe_event(dev_priv, 0); +	psb_irq_turn_off_dpst(dev); + +	spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); + +	return 0; +} + +/* + * It is used to enable VBLANK interrupt + */ +int psb_enable_vblank(struct drm_device *dev, int pipe) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	unsigned long irqflags; +	uint32_t reg_val = 0; +	uint32_t pipeconf_reg = mid_pipeconf(pipe); + +	/* Medfield is different - we should perhaps extract out vblank +	   and blacklight etc ops */ +	if (IS_MFLD(dev)) +		return mdfld_enable_te(dev, pipe); + +	if (gma_power_begin(dev, false)) { +		reg_val = REG_READ(pipeconf_reg); +		gma_power_end(dev); +	} + +	if (!(reg_val & PIPEACONF_ENABLE)) +		return -EINVAL; + +	spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); + +	if (pipe == 0) +		dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEA_FLAG; +	else if (pipe == 1) +		dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEB_FLAG; + +	PSB_WVDC32(~dev_priv->vdc_irq_mask, PSB_INT_MASK_R); +	PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R); +	psb_enable_pipestat(dev_priv, pipe, PIPE_VBLANK_INTERRUPT_ENABLE); + +	spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); + +	return 0; +} + +/* + * It is used to disable VBLANK interrupt + */ +void psb_disable_vblank(struct drm_device *dev, int pipe) +{ +	struct drm_psb_private *dev_priv = dev->dev_private; +	unsigned long irqflags; + +	if (IS_MFLD(dev)) +		mdfld_disable_te(dev, pipe); +	spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); + +	if (pipe == 0) +		dev_priv->vdc_irq_mask &= ~_PSB_VSYNC_PIPEA_FLAG; +	else if (pipe == 1) +		dev_priv->vdc_irq_mask &= ~_PSB_VSYNC_PIPEB_FLAG; + +	PSB_WVDC32(~dev_priv->vdc_irq_mask, PSB_INT_MASK_R); +	PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R); +	psb_disable_pipestat(dev_priv, pipe, PIPE_VBLANK_INTERRUPT_ENABLE); + +	spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); +} + +/* + * It is used to enable TE interrupt + */ +int mdfld_enable_te(struct drm_device *dev, int pipe) +{ +	struct drm_psb_private *dev_priv = +		(struct drm_psb_private *) dev->dev_private; +	unsigned long irqflags; +	uint32_t reg_val = 0; +	uint32_t pipeconf_reg = mid_pipeconf(pipe); + +	if (gma_power_begin(dev, false)) { +		reg_val = REG_READ(pipeconf_reg); +		gma_power_end(dev); +	} + +	if (!(reg_val & PIPEACONF_ENABLE)) +		return -EINVAL; + +	spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); + +	mid_enable_pipe_event(dev_priv, pipe); +	psb_enable_pipestat(dev_priv, pipe, PIPE_TE_ENABLE); + +	spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); + +	return 0; +} + +/* + * It is used to disable TE interrupt + */ +void mdfld_disable_te(struct drm_device *dev, int pipe) +{ +	struct drm_psb_private *dev_priv = +		(struct drm_psb_private *) dev->dev_private; +	unsigned long irqflags; + +	if (!dev_priv->dsr_enable) +		return; + +	spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); + +	mid_disable_pipe_event(dev_priv, pipe); +	psb_disable_pipestat(dev_priv, pipe, PIPE_TE_ENABLE); + +	spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); +} + +/* Called from drm generic code, passed a 'crtc', which + * we use as a pipe index + */ +u32 psb_get_vblank_counter(struct drm_device *dev, int pipe) +{ +	uint32_t high_frame = PIPEAFRAMEHIGH; +	uint32_t low_frame = PIPEAFRAMEPIXEL; +	uint32_t pipeconf_reg = PIPEACONF; +	uint32_t reg_val = 0; +	uint32_t high1 = 0, high2 = 0, low = 0, count = 0; + +	switch (pipe) { +	case 0: +		break; +	case 1: +		high_frame = PIPEBFRAMEHIGH; +		low_frame = PIPEBFRAMEPIXEL; +		pipeconf_reg = PIPEBCONF; +		break; +	case 2: +		high_frame = PIPECFRAMEHIGH; +		low_frame = PIPECFRAMEPIXEL; +		pipeconf_reg = PIPECCONF; +		break; +	default: +		dev_err(dev->dev, "%s, invalid pipe.\n", __func__); +		return 0; +	} + +	if (!gma_power_begin(dev, false)) +		return 0; + +	reg_val = REG_READ(pipeconf_reg); + +	if (!(reg_val & PIPEACONF_ENABLE)) { +		dev_err(dev->dev, "trying to get vblank count for disabled pipe %d\n", +								pipe); +		goto psb_get_vblank_counter_exit; +	} + +	/* +	 * High & low register fields aren't synchronized, so make sure +	 * we get a low value that's stable across two reads of the high +	 * register. +	 */ +	do { +		high1 = ((REG_READ(high_frame) & PIPE_FRAME_HIGH_MASK) >> +			 PIPE_FRAME_HIGH_SHIFT); +		low =  ((REG_READ(low_frame) & PIPE_FRAME_LOW_MASK) >> +			PIPE_FRAME_LOW_SHIFT); +		high2 = ((REG_READ(high_frame) & PIPE_FRAME_HIGH_MASK) >> +			 PIPE_FRAME_HIGH_SHIFT); +	} while (high1 != high2); + +	count = (high1 << 8) | low; + +psb_get_vblank_counter_exit: + +	gma_power_end(dev); + +	return count; +} + diff --git a/drivers/gpu/drm/gma500/psb_irq.h b/drivers/gpu/drm/gma500/psb_irq.h new file mode 100644 index 00000000000..d0b45ffa112 --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_irq.h @@ -0,0 +1,47 @@ +/************************************************************************** + * Copyright (c) 2009-2011, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: + *    Benjamin Defnet <benjamin.r.defnet@intel.com> + *    Rajesh Poornachandran <rajesh.poornachandran@intel.com> + * + **************************************************************************/ + +#ifndef _PSB_IRQ_H_ +#define _PSB_IRQ_H_ + +#include <drm/drmP.h> + +bool sysirq_init(struct drm_device *dev); +void sysirq_uninit(struct drm_device *dev); + +void psb_irq_preinstall(struct drm_device *dev); +int  psb_irq_postinstall(struct drm_device *dev); +void psb_irq_uninstall(struct drm_device *dev); +irqreturn_t psb_irq_handler(int irq, void *arg); + +int psb_irq_enable_dpst(struct drm_device *dev); +int psb_irq_disable_dpst(struct drm_device *dev); +void psb_irq_turn_on_dpst(struct drm_device *dev); +void psb_irq_turn_off_dpst(struct drm_device *dev); +int  psb_enable_vblank(struct drm_device *dev, int pipe); +void psb_disable_vblank(struct drm_device *dev, int pipe); +u32  psb_get_vblank_counter(struct drm_device *dev, int pipe); + +int mdfld_enable_te(struct drm_device *dev, int pipe); +void mdfld_disable_te(struct drm_device *dev, int pipe); +#endif /* _PSB_IRQ_H_ */ diff --git a/drivers/gpu/drm/gma500/psb_lid.c b/drivers/gpu/drm/gma500/psb_lid.c new file mode 100644 index 00000000000..1d2ebb5e530 --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_lid.c @@ -0,0 +1,94 @@ +/************************************************************************** + * Copyright (c) 2007, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Authors: Thomas Hellstrom <thomas-at-tungstengraphics-dot-com> + **************************************************************************/ + +#include <drm/drmP.h> +#include "psb_drv.h" +#include "psb_reg.h" +#include "psb_intel_reg.h" +#include <linux/spinlock.h> + +static void psb_lid_timer_func(unsigned long data) +{ +	struct drm_psb_private * dev_priv = (struct drm_psb_private *)data; +	struct drm_device *dev = (struct drm_device *)dev_priv->dev; +	struct timer_list *lid_timer = &dev_priv->lid_timer; +	unsigned long irq_flags; +	u32 __iomem *lid_state = dev_priv->opregion.lid_state; +	u32 pp_status; + +	if (readl(lid_state) == dev_priv->lid_last_state) +		goto lid_timer_schedule; + +	if ((readl(lid_state)) & 0x01) { +		/*lid state is open*/ +		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) | POWER_TARGET_ON); +		do { +			pp_status = REG_READ(PP_STATUS); +		} while ((pp_status & PP_ON) == 0 && +			 (pp_status & PP_SEQUENCE_MASK) != 0); + +		if (REG_READ(PP_STATUS) & PP_ON) { +			/*FIXME: should be backlight level before*/ +			psb_intel_lvds_set_brightness(dev, 100); +		} else { +			DRM_DEBUG("LVDS panel never powered up"); +			return; +		} +	} else { +		psb_intel_lvds_set_brightness(dev, 0); + +		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) & ~POWER_TARGET_ON); +		do { +			pp_status = REG_READ(PP_STATUS); +		} while ((pp_status & PP_ON) == 0); +	} +	dev_priv->lid_last_state =  readl(lid_state); + +lid_timer_schedule: +	spin_lock_irqsave(&dev_priv->lid_lock, irq_flags); +	if (!timer_pending(lid_timer)) { +		lid_timer->expires = jiffies + PSB_LID_DELAY; +		add_timer(lid_timer); +	} +	spin_unlock_irqrestore(&dev_priv->lid_lock, irq_flags); +} + +void psb_lid_timer_init(struct drm_psb_private *dev_priv) +{ +	struct timer_list *lid_timer = &dev_priv->lid_timer; +	unsigned long irq_flags; + +	spin_lock_init(&dev_priv->lid_lock); +	spin_lock_irqsave(&dev_priv->lid_lock, irq_flags); + +	init_timer(lid_timer); + +	lid_timer->data = (unsigned long)dev_priv; +	lid_timer->function = psb_lid_timer_func; +	lid_timer->expires = jiffies + PSB_LID_DELAY; + +	add_timer(lid_timer); +	spin_unlock_irqrestore(&dev_priv->lid_lock, irq_flags); +} + +void psb_lid_timer_takedown(struct drm_psb_private *dev_priv) +{ +	del_timer_sync(&dev_priv->lid_timer); +} + diff --git a/drivers/gpu/drm/gma500/psb_reg.h b/drivers/gpu/drm/gma500/psb_reg.h new file mode 100644 index 00000000000..b81c7c1e9c2 --- /dev/null +++ b/drivers/gpu/drm/gma500/psb_reg.h @@ -0,0 +1,582 @@ +/************************************************************************** + * + * Copyright (c) (2005-2007) Imagination Technologies Limited. + * Copyright (c) 2007, Intel Corporation. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.. + * + **************************************************************************/ + +#ifndef _PSB_REG_H_ +#define _PSB_REG_H_ + +#define PSB_CR_CLKGATECTL		0x0000 +#define _PSB_C_CLKGATECTL_AUTO_MAN_REG		(1 << 24) +#define _PSB_C_CLKGATECTL_USE_CLKG_SHIFT	(20) +#define _PSB_C_CLKGATECTL_USE_CLKG_MASK		(0x3 << 20) +#define _PSB_C_CLKGATECTL_DPM_CLKG_SHIFT	(16) +#define _PSB_C_CLKGATECTL_DPM_CLKG_MASK		(0x3 << 16) +#define _PSB_C_CLKGATECTL_TA_CLKG_SHIFT		(12) +#define _PSB_C_CLKGATECTL_TA_CLKG_MASK		(0x3 << 12) +#define _PSB_C_CLKGATECTL_TSP_CLKG_SHIFT	(8) +#define _PSB_C_CLKGATECTL_TSP_CLKG_MASK		(0x3 << 8) +#define _PSB_C_CLKGATECTL_ISP_CLKG_SHIFT	(4) +#define _PSB_C_CLKGATECTL_ISP_CLKG_MASK		(0x3 << 4) +#define _PSB_C_CLKGATECTL_2D_CLKG_SHIFT		(0) +#define _PSB_C_CLKGATECTL_2D_CLKG_MASK		(0x3 << 0) +#define _PSB_C_CLKGATECTL_CLKG_ENABLED		(0) +#define _PSB_C_CLKGATECTL_CLKG_DISABLED		(1) +#define _PSB_C_CLKGATECTL_CLKG_AUTO		(2) + +#define PSB_CR_CORE_ID			0x0010 +#define _PSB_CC_ID_ID_SHIFT			(16) +#define _PSB_CC_ID_ID_MASK			(0xFFFF << 16) +#define _PSB_CC_ID_CONFIG_SHIFT			(0) +#define _PSB_CC_ID_CONFIG_MASK			(0xFFFF << 0) + +#define PSB_CR_CORE_REVISION		0x0014 +#define _PSB_CC_REVISION_DESIGNER_SHIFT		(24) +#define _PSB_CC_REVISION_DESIGNER_MASK		(0xFF << 24) +#define _PSB_CC_REVISION_MAJOR_SHIFT		(16) +#define _PSB_CC_REVISION_MAJOR_MASK		(0xFF << 16) +#define _PSB_CC_REVISION_MINOR_SHIFT		(8) +#define _PSB_CC_REVISION_MINOR_MASK		(0xFF << 8) +#define _PSB_CC_REVISION_MAINTENANCE_SHIFT	(0) +#define _PSB_CC_REVISION_MAINTENANCE_MASK	(0xFF << 0) + +#define PSB_CR_DESIGNER_REV_FIELD1	0x0018 + +#define PSB_CR_SOFT_RESET		0x0080 +#define _PSB_CS_RESET_TSP_RESET		(1 << 6) +#define _PSB_CS_RESET_ISP_RESET		(1 << 5) +#define _PSB_CS_RESET_USE_RESET		(1 << 4) +#define _PSB_CS_RESET_TA_RESET		(1 << 3) +#define _PSB_CS_RESET_DPM_RESET		(1 << 2) +#define _PSB_CS_RESET_TWOD_RESET	(1 << 1) +#define _PSB_CS_RESET_BIF_RESET			(1 << 0) + +#define PSB_CR_DESIGNER_REV_FIELD2	0x001C + +#define PSB_CR_EVENT_HOST_ENABLE2	0x0110 + +#define PSB_CR_EVENT_STATUS2		0x0118 + +#define PSB_CR_EVENT_HOST_CLEAR2	0x0114 +#define _PSB_CE2_BIF_REQUESTER_FAULT		(1 << 4) + +#define PSB_CR_EVENT_STATUS		0x012C + +#define PSB_CR_EVENT_HOST_ENABLE	0x0130 + +#define PSB_CR_EVENT_HOST_CLEAR		0x0134 +#define _PSB_CE_MASTER_INTERRUPT		(1 << 31) +#define _PSB_CE_TA_DPM_FAULT			(1 << 28) +#define _PSB_CE_TWOD_COMPLETE			(1 << 27) +#define _PSB_CE_DPM_OUT_OF_MEMORY_ZLS		(1 << 25) +#define _PSB_CE_DPM_TA_MEM_FREE			(1 << 24) +#define _PSB_CE_PIXELBE_END_RENDER		(1 << 18) +#define _PSB_CE_SW_EVENT			(1 << 14) +#define _PSB_CE_TA_FINISHED			(1 << 13) +#define _PSB_CE_TA_TERMINATE			(1 << 12) +#define _PSB_CE_DPM_REACHED_MEM_THRESH		(1 << 3) +#define _PSB_CE_DPM_OUT_OF_MEMORY_GBL		(1 << 2) +#define _PSB_CE_DPM_OUT_OF_MEMORY_MT		(1 << 1) +#define _PSB_CE_DPM_3D_MEM_FREE			(1 << 0) + + +#define PSB_USE_OFFSET_MASK		0x0007FFFF +#define PSB_USE_OFFSET_SIZE		(PSB_USE_OFFSET_MASK + 1) +#define PSB_CR_USE_CODE_BASE0		0x0A0C +#define PSB_CR_USE_CODE_BASE1		0x0A10 +#define PSB_CR_USE_CODE_BASE2		0x0A14 +#define PSB_CR_USE_CODE_BASE3		0x0A18 +#define PSB_CR_USE_CODE_BASE4		0x0A1C +#define PSB_CR_USE_CODE_BASE5		0x0A20 +#define PSB_CR_USE_CODE_BASE6		0x0A24 +#define PSB_CR_USE_CODE_BASE7		0x0A28 +#define PSB_CR_USE_CODE_BASE8		0x0A2C +#define PSB_CR_USE_CODE_BASE9		0x0A30 +#define PSB_CR_USE_CODE_BASE10		0x0A34 +#define PSB_CR_USE_CODE_BASE11		0x0A38 +#define PSB_CR_USE_CODE_BASE12		0x0A3C +#define PSB_CR_USE_CODE_BASE13		0x0A40 +#define PSB_CR_USE_CODE_BASE14		0x0A44 +#define PSB_CR_USE_CODE_BASE15		0x0A48 +#define PSB_CR_USE_CODE_BASE(_i)	(0x0A0C + ((_i) << 2)) +#define _PSB_CUC_BASE_DM_SHIFT			(25) +#define _PSB_CUC_BASE_DM_MASK			(0x3 << 25) +#define _PSB_CUC_BASE_ADDR_SHIFT		(0)	/* 1024-bit aligned address? */ +#define _PSB_CUC_BASE_ADDR_ALIGNSHIFT		(7) +#define _PSB_CUC_BASE_ADDR_MASK			(0x1FFFFFF << 0) +#define _PSB_CUC_DM_VERTEX			(0) +#define _PSB_CUC_DM_PIXEL			(1) +#define _PSB_CUC_DM_RESERVED			(2) +#define _PSB_CUC_DM_EDM				(3) + +#define PSB_CR_PDS_EXEC_BASE		0x0AB8 +#define _PSB_CR_PDS_EXEC_BASE_ADDR_SHIFT	(20)	/* 1MB aligned address */ +#define _PSB_CR_PDS_EXEC_BASE_ADDR_ALIGNSHIFT	(20) + +#define PSB_CR_EVENT_KICKER		0x0AC4 +#define _PSB_CE_KICKER_ADDRESS_SHIFT		(4)	/* 128-bit aligned address */ + +#define PSB_CR_EVENT_KICK		0x0AC8 +#define _PSB_CE_KICK_NOW			(1 << 0) + +#define PSB_CR_BIF_DIR_LIST_BASE1	0x0C38 + +#define PSB_CR_BIF_CTRL			0x0C00 +#define _PSB_CB_CTRL_CLEAR_FAULT		(1 << 4) +#define _PSB_CB_CTRL_INVALDC			(1 << 3) +#define _PSB_CB_CTRL_FLUSH			(1 << 2) + +#define PSB_CR_BIF_INT_STAT		0x0C04 + +#define PSB_CR_BIF_FAULT		0x0C08 +#define _PSB_CBI_STAT_PF_N_RW			(1 << 14) +#define _PSB_CBI_STAT_FAULT_SHIFT		(0) +#define _PSB_CBI_STAT_FAULT_MASK		(0x3FFF << 0) +#define _PSB_CBI_STAT_FAULT_CACHE		(1 << 1) +#define _PSB_CBI_STAT_FAULT_TA			(1 << 2) +#define _PSB_CBI_STAT_FAULT_VDM			(1 << 3) +#define _PSB_CBI_STAT_FAULT_2D			(1 << 4) +#define _PSB_CBI_STAT_FAULT_PBE			(1 << 5) +#define _PSB_CBI_STAT_FAULT_TSP			(1 << 6) +#define _PSB_CBI_STAT_FAULT_ISP			(1 << 7) +#define _PSB_CBI_STAT_FAULT_USSEPDS		(1 << 8) +#define _PSB_CBI_STAT_FAULT_HOST		(1 << 9) + +#define PSB_CR_BIF_BANK0		0x0C78 +#define PSB_CR_BIF_BANK1		0x0C7C +#define PSB_CR_BIF_DIR_LIST_BASE0	0x0C84 +#define PSB_CR_BIF_TWOD_REQ_BASE	0x0C88 +#define PSB_CR_BIF_3D_REQ_BASE		0x0CAC + +#define PSB_CR_2D_SOCIF			0x0E18 +#define _PSB_C2_SOCIF_FREESPACE_SHIFT		(0) +#define _PSB_C2_SOCIF_FREESPACE_MASK		(0xFF << 0) +#define _PSB_C2_SOCIF_EMPTY			(0x80 << 0) + +#define PSB_CR_2D_BLIT_STATUS		0x0E04 +#define _PSB_C2B_STATUS_BUSY			(1 << 24) +#define _PSB_C2B_STATUS_COMPLETE_SHIFT		(0) +#define _PSB_C2B_STATUS_COMPLETE_MASK		(0xFFFFFF << 0) + +/* + * 2D defs. + */ + +/* + * 2D Slave Port Data : Block Header's Object Type + */ + +#define	PSB_2D_CLIP_BH			(0x00000000) +#define	PSB_2D_PAT_BH			(0x10000000) +#define	PSB_2D_CTRL_BH			(0x20000000) +#define	PSB_2D_SRC_OFF_BH		(0x30000000) +#define	PSB_2D_MASK_OFF_BH		(0x40000000) +#define	PSB_2D_RESERVED1_BH		(0x50000000) +#define	PSB_2D_RESERVED2_BH		(0x60000000) +#define	PSB_2D_FENCE_BH			(0x70000000) +#define	PSB_2D_BLIT_BH			(0x80000000) +#define	PSB_2D_SRC_SURF_BH		(0x90000000) +#define	PSB_2D_DST_SURF_BH		(0xA0000000) +#define	PSB_2D_PAT_SURF_BH		(0xB0000000) +#define	PSB_2D_SRC_PAL_BH		(0xC0000000) +#define	PSB_2D_PAT_PAL_BH		(0xD0000000) +#define	PSB_2D_MASK_SURF_BH		(0xE0000000) +#define	PSB_2D_FLUSH_BH			(0xF0000000) + +/* + * Clip Definition block (PSB_2D_CLIP_BH) + */ +#define PSB_2D_CLIPCOUNT_MAX		(1) +#define PSB_2D_CLIPCOUNT_MASK		(0x00000000) +#define PSB_2D_CLIPCOUNT_CLRMASK	(0xFFFFFFFF) +#define PSB_2D_CLIPCOUNT_SHIFT		(0) +/* clip rectangle min & max */ +#define PSB_2D_CLIP_XMAX_MASK		(0x00FFF000) +#define PSB_2D_CLIP_XMAX_CLRMASK	(0xFF000FFF) +#define PSB_2D_CLIP_XMAX_SHIFT		(12) +#define PSB_2D_CLIP_XMIN_MASK		(0x00000FFF) +#define PSB_2D_CLIP_XMIN_CLRMASK	(0x00FFF000) +#define PSB_2D_CLIP_XMIN_SHIFT		(0) +/* clip rectangle offset */ +#define PSB_2D_CLIP_YMAX_MASK		(0x00FFF000) +#define PSB_2D_CLIP_YMAX_CLRMASK	(0xFF000FFF) +#define PSB_2D_CLIP_YMAX_SHIFT		(12) +#define PSB_2D_CLIP_YMIN_MASK		(0x00000FFF) +#define PSB_2D_CLIP_YMIN_CLRMASK	(0x00FFF000) +#define PSB_2D_CLIP_YMIN_SHIFT		(0) + +/* + * Pattern Control (PSB_2D_PAT_BH) + */ +#define PSB_2D_PAT_HEIGHT_MASK		(0x0000001F) +#define PSB_2D_PAT_HEIGHT_SHIFT		(0) +#define PSB_2D_PAT_WIDTH_MASK		(0x000003E0) +#define PSB_2D_PAT_WIDTH_SHIFT		(5) +#define PSB_2D_PAT_YSTART_MASK		(0x00007C00) +#define PSB_2D_PAT_YSTART_SHIFT		(10) +#define PSB_2D_PAT_XSTART_MASK		(0x000F8000) +#define PSB_2D_PAT_XSTART_SHIFT		(15) + +/* + * 2D Control block (PSB_2D_CTRL_BH) + */ +/* Present Flags */ +#define PSB_2D_SRCCK_CTRL		(0x00000001) +#define PSB_2D_DSTCK_CTRL		(0x00000002) +#define PSB_2D_ALPHA_CTRL		(0x00000004) +/* Colour Key Colour (SRC/DST)*/ +#define PSB_2D_CK_COL_MASK		(0xFFFFFFFF) +#define PSB_2D_CK_COL_CLRMASK		(0x00000000) +#define PSB_2D_CK_COL_SHIFT		(0) +/* Colour Key Mask (SRC/DST)*/ +#define PSB_2D_CK_MASK_MASK		(0xFFFFFFFF) +#define PSB_2D_CK_MASK_CLRMASK		(0x00000000) +#define PSB_2D_CK_MASK_SHIFT		(0) +/* Alpha Control (Alpha/RGB)*/ +#define PSB_2D_GBLALPHA_MASK		(0x000FF000) +#define PSB_2D_GBLALPHA_CLRMASK		(0xFFF00FFF) +#define PSB_2D_GBLALPHA_SHIFT		(12) +#define PSB_2D_SRCALPHA_OP_MASK		(0x00700000) +#define PSB_2D_SRCALPHA_OP_CLRMASK	(0xFF8FFFFF) +#define PSB_2D_SRCALPHA_OP_SHIFT	(20) +#define PSB_2D_SRCALPHA_OP_ONE		(0x00000000) +#define PSB_2D_SRCALPHA_OP_SRC		(0x00100000) +#define PSB_2D_SRCALPHA_OP_DST		(0x00200000) +#define PSB_2D_SRCALPHA_OP_SG		(0x00300000) +#define PSB_2D_SRCALPHA_OP_DG		(0x00400000) +#define PSB_2D_SRCALPHA_OP_GBL		(0x00500000) +#define PSB_2D_SRCALPHA_OP_ZERO		(0x00600000) +#define PSB_2D_SRCALPHA_INVERT		(0x00800000) +#define PSB_2D_SRCALPHA_INVERT_CLR	(0xFF7FFFFF) +#define PSB_2D_DSTALPHA_OP_MASK		(0x07000000) +#define PSB_2D_DSTALPHA_OP_CLRMASK	(0xF8FFFFFF) +#define PSB_2D_DSTALPHA_OP_SHIFT	(24) +#define PSB_2D_DSTALPHA_OP_ONE		(0x00000000) +#define PSB_2D_DSTALPHA_OP_SRC		(0x01000000) +#define PSB_2D_DSTALPHA_OP_DST		(0x02000000) +#define PSB_2D_DSTALPHA_OP_SG		(0x03000000) +#define PSB_2D_DSTALPHA_OP_DG		(0x04000000) +#define PSB_2D_DSTALPHA_OP_GBL		(0x05000000) +#define PSB_2D_DSTALPHA_OP_ZERO		(0x06000000) +#define PSB_2D_DSTALPHA_INVERT		(0x08000000) +#define PSB_2D_DSTALPHA_INVERT_CLR	(0xF7FFFFFF) + +#define PSB_2D_PRE_MULTIPLICATION_ENABLE	(0x10000000) +#define PSB_2D_PRE_MULTIPLICATION_CLRMASK	(0xEFFFFFFF) +#define PSB_2D_ZERO_SOURCE_ALPHA_ENABLE		(0x20000000) +#define PSB_2D_ZERO_SOURCE_ALPHA_CLRMASK	(0xDFFFFFFF) + +/* + *Source Offset (PSB_2D_SRC_OFF_BH) + */ +#define PSB_2D_SRCOFF_XSTART_MASK	((0x00000FFF) << 12) +#define PSB_2D_SRCOFF_XSTART_SHIFT	(12) +#define PSB_2D_SRCOFF_YSTART_MASK	(0x00000FFF) +#define PSB_2D_SRCOFF_YSTART_SHIFT	(0) + +/* + * Mask Offset (PSB_2D_MASK_OFF_BH) + */ +#define PSB_2D_MASKOFF_XSTART_MASK	((0x00000FFF) << 12) +#define PSB_2D_MASKOFF_XSTART_SHIFT	(12) +#define PSB_2D_MASKOFF_YSTART_MASK	(0x00000FFF) +#define PSB_2D_MASKOFF_YSTART_SHIFT	(0) + +/* + * 2D Fence (see PSB_2D_FENCE_BH): bits 0:27 are ignored + */ + +/* + *Blit Rectangle (PSB_2D_BLIT_BH) + */ + +#define PSB_2D_ROT_MASK			(3 << 25) +#define PSB_2D_ROT_CLRMASK		(~PSB_2D_ROT_MASK) +#define PSB_2D_ROT_NONE			(0 << 25) +#define PSB_2D_ROT_90DEGS		(1 << 25) +#define PSB_2D_ROT_180DEGS		(2 << 25) +#define PSB_2D_ROT_270DEGS		(3 << 25) + +#define PSB_2D_COPYORDER_MASK		(3 << 23) +#define PSB_2D_COPYORDER_CLRMASK	(~PSB_2D_COPYORDER_MASK) +#define PSB_2D_COPYORDER_TL2BR		(0 << 23) +#define PSB_2D_COPYORDER_BR2TL		(1 << 23) +#define PSB_2D_COPYORDER_TR2BL		(2 << 23) +#define PSB_2D_COPYORDER_BL2TR		(3 << 23) + +#define PSB_2D_DSTCK_CLRMASK		(0xFF9FFFFF) +#define PSB_2D_DSTCK_DISABLE		(0x00000000) +#define PSB_2D_DSTCK_PASS		(0x00200000) +#define PSB_2D_DSTCK_REJECT		(0x00400000) + +#define PSB_2D_SRCCK_CLRMASK		(0xFFE7FFFF) +#define PSB_2D_SRCCK_DISABLE		(0x00000000) +#define PSB_2D_SRCCK_PASS		(0x00080000) +#define PSB_2D_SRCCK_REJECT		(0x00100000) + +#define PSB_2D_CLIP_ENABLE		(0x00040000) + +#define PSB_2D_ALPHA_ENABLE		(0x00020000) + +#define PSB_2D_PAT_CLRMASK		(0xFFFEFFFF) +#define PSB_2D_PAT_MASK			(0x00010000) +#define PSB_2D_USE_PAT			(0x00010000) +#define PSB_2D_USE_FILL			(0x00000000) +/* + * Tungsten Graphics note on rop codes: If rop A and rop B are + * identical, the mask surface will not be read and need not be + * set up. + */ + +#define PSB_2D_ROP3B_MASK		(0x0000FF00) +#define PSB_2D_ROP3B_CLRMASK		(0xFFFF00FF) +#define PSB_2D_ROP3B_SHIFT		(8) +/* rop code A */ +#define PSB_2D_ROP3A_MASK		(0x000000FF) +#define PSB_2D_ROP3A_CLRMASK		(0xFFFFFF00) +#define PSB_2D_ROP3A_SHIFT		(0) + +#define PSB_2D_ROP4_MASK		(0x0000FFFF) +/* + *	DWORD0:	(Only pass if Pattern control == Use Fill Colour) + *	Fill Colour RGBA8888 + */ +#define PSB_2D_FILLCOLOUR_MASK		(0xFFFFFFFF) +#define PSB_2D_FILLCOLOUR_SHIFT		(0) +/* + *	DWORD1: (Always Present) + *	X Start (Dest) + *	Y Start (Dest) + */ +#define PSB_2D_DST_XSTART_MASK		(0x00FFF000) +#define PSB_2D_DST_XSTART_CLRMASK	(0xFF000FFF) +#define PSB_2D_DST_XSTART_SHIFT		(12) +#define PSB_2D_DST_YSTART_MASK		(0x00000FFF) +#define PSB_2D_DST_YSTART_CLRMASK	(0xFFFFF000) +#define PSB_2D_DST_YSTART_SHIFT		(0) +/* + *	DWORD2: (Always Present) + *	X Size (Dest) + *	Y Size (Dest) + */ +#define PSB_2D_DST_XSIZE_MASK		(0x00FFF000) +#define PSB_2D_DST_XSIZE_CLRMASK	(0xFF000FFF) +#define PSB_2D_DST_XSIZE_SHIFT		(12) +#define PSB_2D_DST_YSIZE_MASK		(0x00000FFF) +#define PSB_2D_DST_YSIZE_CLRMASK	(0xFFFFF000) +#define PSB_2D_DST_YSIZE_SHIFT		(0) + +/* + * Source Surface (PSB_2D_SRC_SURF_BH) + */ +/* + * WORD 0 + */ + +#define PSB_2D_SRC_FORMAT_MASK		(0x00078000) +#define PSB_2D_SRC_1_PAL		(0x00000000) +#define PSB_2D_SRC_2_PAL		(0x00008000) +#define PSB_2D_SRC_4_PAL		(0x00010000) +#define PSB_2D_SRC_8_PAL		(0x00018000) +#define PSB_2D_SRC_8_ALPHA		(0x00020000) +#define PSB_2D_SRC_4_ALPHA		(0x00028000) +#define PSB_2D_SRC_332RGB		(0x00030000) +#define PSB_2D_SRC_4444ARGB		(0x00038000) +#define PSB_2D_SRC_555RGB		(0x00040000) +#define PSB_2D_SRC_1555ARGB		(0x00048000) +#define PSB_2D_SRC_565RGB		(0x00050000) +#define PSB_2D_SRC_0888ARGB		(0x00058000) +#define PSB_2D_SRC_8888ARGB		(0x00060000) +#define PSB_2D_SRC_8888UYVY		(0x00068000) +#define PSB_2D_SRC_RESERVED		(0x00070000) +#define PSB_2D_SRC_1555ARGB_LOOKUP	(0x00078000) + + +#define PSB_2D_SRC_STRIDE_MASK		(0x00007FFF) +#define PSB_2D_SRC_STRIDE_CLRMASK	(0xFFFF8000) +#define PSB_2D_SRC_STRIDE_SHIFT		(0) +/* + *  WORD 1 - Base Address + */ +#define PSB_2D_SRC_ADDR_MASK		(0x0FFFFFFC) +#define PSB_2D_SRC_ADDR_CLRMASK		(0x00000003) +#define PSB_2D_SRC_ADDR_SHIFT		(2) +#define PSB_2D_SRC_ADDR_ALIGNSHIFT	(2) + +/* + * Pattern Surface (PSB_2D_PAT_SURF_BH) + */ +/* + *  WORD 0 + */ + +#define PSB_2D_PAT_FORMAT_MASK		(0x00078000) +#define PSB_2D_PAT_1_PAL		(0x00000000) +#define PSB_2D_PAT_2_PAL		(0x00008000) +#define PSB_2D_PAT_4_PAL		(0x00010000) +#define PSB_2D_PAT_8_PAL		(0x00018000) +#define PSB_2D_PAT_8_ALPHA		(0x00020000) +#define PSB_2D_PAT_4_ALPHA		(0x00028000) +#define PSB_2D_PAT_332RGB		(0x00030000) +#define PSB_2D_PAT_4444ARGB		(0x00038000) +#define PSB_2D_PAT_555RGB		(0x00040000) +#define PSB_2D_PAT_1555ARGB		(0x00048000) +#define PSB_2D_PAT_565RGB		(0x00050000) +#define PSB_2D_PAT_0888ARGB		(0x00058000) +#define PSB_2D_PAT_8888ARGB		(0x00060000) + +#define PSB_2D_PAT_STRIDE_MASK		(0x00007FFF) +#define PSB_2D_PAT_STRIDE_CLRMASK	(0xFFFF8000) +#define PSB_2D_PAT_STRIDE_SHIFT		(0) +/* + *  WORD 1 - Base Address + */ +#define PSB_2D_PAT_ADDR_MASK		(0x0FFFFFFC) +#define PSB_2D_PAT_ADDR_CLRMASK		(0x00000003) +#define PSB_2D_PAT_ADDR_SHIFT		(2) +#define PSB_2D_PAT_ADDR_ALIGNSHIFT	(2) + +/* + * Destination Surface (PSB_2D_DST_SURF_BH) + */ +/* + * WORD 0 + */ + +#define PSB_2D_DST_FORMAT_MASK		(0x00078000) +#define PSB_2D_DST_332RGB		(0x00030000) +#define PSB_2D_DST_4444ARGB		(0x00038000) +#define PSB_2D_DST_555RGB		(0x00040000) +#define PSB_2D_DST_1555ARGB		(0x00048000) +#define PSB_2D_DST_565RGB		(0x00050000) +#define PSB_2D_DST_0888ARGB		(0x00058000) +#define PSB_2D_DST_8888ARGB		(0x00060000) +#define PSB_2D_DST_8888AYUV		(0x00070000) + +#define PSB_2D_DST_STRIDE_MASK		(0x00007FFF) +#define PSB_2D_DST_STRIDE_CLRMASK	(0xFFFF8000) +#define PSB_2D_DST_STRIDE_SHIFT		(0) +/* + * WORD 1 - Base Address + */ +#define PSB_2D_DST_ADDR_MASK		(0x0FFFFFFC) +#define PSB_2D_DST_ADDR_CLRMASK		(0x00000003) +#define PSB_2D_DST_ADDR_SHIFT		(2) +#define PSB_2D_DST_ADDR_ALIGNSHIFT	(2) + +/* + * Mask Surface (PSB_2D_MASK_SURF_BH) + */ +/* + * WORD 0 + */ +#define PSB_2D_MASK_STRIDE_MASK		(0x00007FFF) +#define PSB_2D_MASK_STRIDE_CLRMASK	(0xFFFF8000) +#define PSB_2D_MASK_STRIDE_SHIFT	(0) +/* + *  WORD 1 - Base Address + */ +#define PSB_2D_MASK_ADDR_MASK		(0x0FFFFFFC) +#define PSB_2D_MASK_ADDR_CLRMASK	(0x00000003) +#define PSB_2D_MASK_ADDR_SHIFT		(2) +#define PSB_2D_MASK_ADDR_ALIGNSHIFT	(2) + +/* + * Source Palette (PSB_2D_SRC_PAL_BH) + */ + +#define PSB_2D_SRCPAL_ADDR_SHIFT	(0) +#define PSB_2D_SRCPAL_ADDR_CLRMASK	(0xF0000007) +#define PSB_2D_SRCPAL_ADDR_MASK		(0x0FFFFFF8) +#define PSB_2D_SRCPAL_BYTEALIGN		(1024) + +/* + * Pattern Palette (PSB_2D_PAT_PAL_BH) + */ + +#define PSB_2D_PATPAL_ADDR_SHIFT	(0) +#define PSB_2D_PATPAL_ADDR_CLRMASK	(0xF0000007) +#define PSB_2D_PATPAL_ADDR_MASK		(0x0FFFFFF8) +#define PSB_2D_PATPAL_BYTEALIGN		(1024) + +/* + * Rop3 Codes (2 LS bytes) + */ + +#define PSB_2D_ROP3_SRCCOPY		(0xCCCC) +#define PSB_2D_ROP3_PATCOPY		(0xF0F0) +#define PSB_2D_ROP3_WHITENESS		(0xFFFF) +#define PSB_2D_ROP3_BLACKNESS		(0x0000) +#define PSB_2D_ROP3_SRC			(0xCC) +#define PSB_2D_ROP3_PAT			(0xF0) +#define PSB_2D_ROP3_DST			(0xAA) + +/* + * Sizes. + */ + +#define PSB_SCENE_HW_COOKIE_SIZE	16 +#define PSB_TA_MEM_HW_COOKIE_SIZE	16 + +/* + * Scene stuff. + */ + +#define PSB_NUM_HW_SCENES		2 + +/* + * Scheduler completion actions. + */ + +#define PSB_RASTER_BLOCK		0 +#define PSB_RASTER			1 +#define PSB_RETURN			2 +#define PSB_TA				3 + +/* Power management */ +#define PSB_PUNIT_PORT			0x04 +#define PSB_OSPMBA			0x78 +#define PSB_APMBA			0x7a +#define PSB_APM_CMD			0x0 +#define PSB_APM_STS			0x04 +#define PSB_PWRGT_VID_ENC_MASK		0x30 +#define PSB_PWRGT_VID_DEC_MASK		0xc +#define PSB_PWRGT_GL3_MASK		0xc0 + +#define PSB_PM_SSC			0x20 +#define PSB_PM_SSS			0x30 +#define PSB_PWRGT_DISPLAY_MASK		0xc /*on a different BA than video/gfx*/ +#define MDFLD_PWRGT_DISPLAY_A_CNTR	0x0000000c +#define MDFLD_PWRGT_DISPLAY_B_CNTR	0x0000c000 +#define MDFLD_PWRGT_DISPLAY_C_CNTR	0x00030000 +#define MDFLD_PWRGT_DISP_MIPI_CNTR	0x000c0000 +#define MDFLD_PWRGT_DISPLAY_CNTR    (MDFLD_PWRGT_DISPLAY_A_CNTR | MDFLD_PWRGT_DISPLAY_B_CNTR | MDFLD_PWRGT_DISPLAY_C_CNTR | MDFLD_PWRGT_DISP_MIPI_CNTR) /* 0x000fc00c */ +/* Display SSS register bits are different in A0 vs. B0 */ +#define PSB_PWRGT_GFX_MASK		0x3 +#define MDFLD_PWRGT_DISPLAY_A_STS	0x000000c0 +#define MDFLD_PWRGT_DISPLAY_B_STS	0x00000300 +#define MDFLD_PWRGT_DISPLAY_C_STS	0x00000c00 +#define PSB_PWRGT_GFX_MASK_B0		0xc3 +#define MDFLD_PWRGT_DISPLAY_A_STS_B0	0x0000000c +#define MDFLD_PWRGT_DISPLAY_B_STS_B0	0x0000c000 +#define MDFLD_PWRGT_DISPLAY_C_STS_B0	0x00030000 +#define MDFLD_PWRGT_DISP_MIPI_STS	0x000c0000 +#define MDFLD_PWRGT_DISPLAY_STS_A0    (MDFLD_PWRGT_DISPLAY_A_STS | MDFLD_PWRGT_DISPLAY_B_STS | MDFLD_PWRGT_DISPLAY_C_STS | MDFLD_PWRGT_DISP_MIPI_STS) /* 0x000fc00c */ +#define MDFLD_PWRGT_DISPLAY_STS_B0    (MDFLD_PWRGT_DISPLAY_A_STS_B0 | MDFLD_PWRGT_DISPLAY_B_STS_B0 | MDFLD_PWRGT_DISPLAY_C_STS_B0 | MDFLD_PWRGT_DISP_MIPI_STS) /* 0x000fc00c */ +#endif diff --git a/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c b/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c new file mode 100644 index 00000000000..771ff66711a --- /dev/null +++ b/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c @@ -0,0 +1,829 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include "mdfld_dsi_dpi.h" +#include "mdfld_output.h" +#include "mdfld_dsi_pkg_sender.h" +#include "tc35876x-dsi-lvds.h" +#include <linux/i2c/tc35876x.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <asm/intel_scu_ipc.h> + +static struct i2c_client *tc35876x_client; +static struct i2c_client *cmi_lcd_i2c_client; + +#define FLD_MASK(start, end)	(((1 << ((start) - (end) + 1)) - 1) << (end)) +#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end)) + +/* DSI D-PHY Layer Registers */ +#define D0W_DPHYCONTTX		0x0004 +#define CLW_DPHYCONTRX		0x0020 +#define D0W_DPHYCONTRX		0x0024 +#define D1W_DPHYCONTRX		0x0028 +#define D2W_DPHYCONTRX		0x002C +#define D3W_DPHYCONTRX		0x0030 +#define COM_DPHYCONTRX		0x0038 +#define CLW_CNTRL		0x0040 +#define D0W_CNTRL		0x0044 +#define D1W_CNTRL		0x0048 +#define D2W_CNTRL		0x004C +#define D3W_CNTRL		0x0050 +#define DFTMODE_CNTRL		0x0054 + +/* DSI PPI Layer Registers */ +#define PPI_STARTPPI		0x0104 +#define PPI_BUSYPPI		0x0108 +#define PPI_LINEINITCNT		0x0110 +#define PPI_LPTXTIMECNT		0x0114 +#define PPI_LANEENABLE		0x0134 +#define PPI_TX_RX_TA		0x013C +#define PPI_CLS_ATMR		0x0140 +#define PPI_D0S_ATMR		0x0144 +#define PPI_D1S_ATMR		0x0148 +#define PPI_D2S_ATMR		0x014C +#define PPI_D3S_ATMR		0x0150 +#define PPI_D0S_CLRSIPOCOUNT	0x0164 +#define PPI_D1S_CLRSIPOCOUNT	0x0168 +#define PPI_D2S_CLRSIPOCOUNT	0x016C +#define PPI_D3S_CLRSIPOCOUNT	0x0170 +#define CLS_PRE			0x0180 +#define D0S_PRE			0x0184 +#define D1S_PRE			0x0188 +#define D2S_PRE			0x018C +#define D3S_PRE			0x0190 +#define CLS_PREP		0x01A0 +#define D0S_PREP		0x01A4 +#define D1S_PREP		0x01A8 +#define D2S_PREP		0x01AC +#define D3S_PREP		0x01B0 +#define CLS_ZERO		0x01C0 +#define D0S_ZERO		0x01C4 +#define D1S_ZERO		0x01C8 +#define D2S_ZERO		0x01CC +#define D3S_ZERO		0x01D0 +#define PPI_CLRFLG		0x01E0 +#define PPI_CLRSIPO		0x01E4 +#define HSTIMEOUT		0x01F0 +#define HSTIMEOUTENABLE		0x01F4 + +/* DSI Protocol Layer Registers */ +#define DSI_STARTDSI		0x0204 +#define DSI_BUSYDSI		0x0208 +#define DSI_LANEENABLE		0x0210 +#define DSI_LANESTATUS0		0x0214 +#define DSI_LANESTATUS1		0x0218 +#define DSI_INTSTATUS		0x0220 +#define DSI_INTMASK		0x0224 +#define DSI_INTCLR		0x0228 +#define DSI_LPTXTO		0x0230 + +/* DSI General Registers */ +#define DSIERRCNT		0x0300 + +/* DSI Application Layer Registers */ +#define APLCTRL			0x0400 +#define RDPKTLN			0x0404 + +/* Video Path Registers */ +#define VPCTRL			0x0450 +#define HTIM1			0x0454 +#define HTIM2			0x0458 +#define VTIM1			0x045C +#define VTIM2			0x0460 +#define VFUEN			0x0464 + +/* LVDS Registers */ +#define LVMX0003		0x0480 +#define LVMX0407		0x0484 +#define LVMX0811		0x0488 +#define LVMX1215		0x048C +#define LVMX1619		0x0490 +#define LVMX2023		0x0494 +#define LVMX2427		0x0498 +#define LVCFG			0x049C +#define LVPHY0			0x04A0 +#define LVPHY1			0x04A4 + +/* System Registers */ +#define SYSSTAT			0x0500 +#define SYSRST			0x0504 + +/* GPIO Registers */ +/*#define GPIOC			0x0520*/ +#define GPIOO			0x0524 +#define GPIOI			0x0528 + +/* I2C Registers */ +#define I2CTIMCTRL		0x0540 +#define I2CMADDR		0x0544 +#define WDATAQ			0x0548 +#define RDATAQ			0x054C + +/* Chip/Rev Registers */ +#define IDREG			0x0580 + +/* Debug Registers */ +#define DEBUG00			0x05A0 +#define DEBUG01			0x05A4 + +/* Panel CABC registers */ +#define PANEL_PWM_CONTROL	0x90 +#define PANEL_FREQ_DIVIDER_HI	0x91 +#define PANEL_FREQ_DIVIDER_LO	0x92 +#define PANEL_DUTY_CONTROL	0x93 +#define PANEL_MODIFY_RGB	0x94 +#define PANEL_FRAMERATE_CONTROL	0x96 +#define PANEL_PWM_MIN		0x97 +#define PANEL_PWM_REF		0x98 +#define PANEL_PWM_MAX		0x99 +#define PANEL_ALLOW_DISTORT	0x9A +#define PANEL_BYPASS_PWMI	0x9B + +/* Panel color management registers */ +#define PANEL_CM_ENABLE		0x700 +#define PANEL_CM_HUE		0x701 +#define PANEL_CM_SATURATION	0x702 +#define PANEL_CM_INTENSITY	0x703 +#define PANEL_CM_BRIGHTNESS	0x704 +#define PANEL_CM_CE_ENABLE	0x705 +#define PANEL_CM_PEAK_EN	0x710 +#define PANEL_CM_GAIN		0x711 +#define PANEL_CM_HUETABLE_START	0x730 +#define PANEL_CM_HUETABLE_END	0x747 /* inclusive */ + +/* Input muxing for registers LVMX0003...LVMX2427 */ +enum { +	INPUT_R0,	/* 0 */ +	INPUT_R1, +	INPUT_R2, +	INPUT_R3, +	INPUT_R4, +	INPUT_R5, +	INPUT_R6, +	INPUT_R7, +	INPUT_G0,	/* 8 */ +	INPUT_G1, +	INPUT_G2, +	INPUT_G3, +	INPUT_G4, +	INPUT_G5, +	INPUT_G6, +	INPUT_G7, +	INPUT_B0,	/* 16 */ +	INPUT_B1, +	INPUT_B2, +	INPUT_B3, +	INPUT_B4, +	INPUT_B5, +	INPUT_B6, +	INPUT_B7, +	INPUT_HSYNC,	/* 24 */ +	INPUT_VSYNC, +	INPUT_DE, +	LOGIC_0, +	/* 28...31 undefined */ +}; + +#define INPUT_MUX(lvmx03, lvmx02, lvmx01, lvmx00)		\ +	(FLD_VAL(lvmx03, 29, 24) | FLD_VAL(lvmx02, 20, 16) |	\ +	FLD_VAL(lvmx01, 12, 8) | FLD_VAL(lvmx00, 4, 0)) + +/** + * tc35876x_regw - Write DSI-LVDS bridge register using I2C + * @client: struct i2c_client to use + * @reg: register address + * @value: value to write + * + * Returns 0 on success, or a negative error value. + */ +static int tc35876x_regw(struct i2c_client *client, u16 reg, u32 value) +{ +	int r; +	u8 tx_data[] = { +		/* NOTE: Register address big-endian, data little-endian. */ +		(reg >> 8) & 0xff, +		reg & 0xff, +		value & 0xff, +		(value >> 8) & 0xff, +		(value >> 16) & 0xff, +		(value >> 24) & 0xff, +	}; +	struct i2c_msg msgs[] = { +		{ +			.addr = client->addr, +			.flags = 0, +			.buf = tx_data, +			.len = ARRAY_SIZE(tx_data), +		}, +	}; + +	r = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); +	if (r < 0) { +		dev_err(&client->dev, "%s: reg 0x%04x val 0x%08x error %d\n", +			__func__, reg, value, r); +		return r; +	} + +	if (r < ARRAY_SIZE(msgs)) { +		dev_err(&client->dev, "%s: reg 0x%04x val 0x%08x msgs %d\n", +			__func__, reg, value, r); +		return -EAGAIN; +	} + +	dev_dbg(&client->dev, "%s: reg 0x%04x val 0x%08x\n", +			__func__, reg, value); + +	return 0; +} + +/** + * tc35876x_regr - Read DSI-LVDS bridge register using I2C + * @client: struct i2c_client to use + * @reg: register address + * @value: pointer for storing the value + * + * Returns 0 on success, or a negative error value. + */ +static int tc35876x_regr(struct i2c_client *client, u16 reg, u32 *value) +{ +	int r; +	u8 tx_data[] = { +		(reg >> 8) & 0xff, +		reg & 0xff, +	}; +	u8 rx_data[4]; +	struct i2c_msg msgs[] = { +		{ +			.addr = client->addr, +			.flags = 0, +			.buf = tx_data, +			.len = ARRAY_SIZE(tx_data), +		}, +		{ +			.addr = client->addr, +			.flags = I2C_M_RD, +			.buf = rx_data, +			.len = ARRAY_SIZE(rx_data), +		 }, +	}; + +	r = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); +	if (r < 0) { +		dev_err(&client->dev, "%s: reg 0x%04x error %d\n", __func__, +			reg, r); +		return r; +	} + +	if (r < ARRAY_SIZE(msgs)) { +		dev_err(&client->dev, "%s: reg 0x%04x msgs %d\n", __func__, +			reg, r); +		return -EAGAIN; +	} + +	*value = rx_data[0] << 24 | rx_data[1] << 16 | +		rx_data[2] << 8 | rx_data[3]; + +	dev_dbg(&client->dev, "%s: reg 0x%04x value 0x%08x\n", __func__, +		reg, *value); + +	return 0; +} + +void tc35876x_set_bridge_reset_state(struct drm_device *dev, int state) +{ +	struct tc35876x_platform_data *pdata; + +	if (WARN(!tc35876x_client, "%s called before probe", __func__)) +		return; + +	dev_dbg(&tc35876x_client->dev, "%s: state %d\n", __func__, state); + +	pdata = dev_get_platdata(&tc35876x_client->dev); + +	if (pdata->gpio_bridge_reset == -1) +		return; + +	if (state) { +		gpio_set_value_cansleep(pdata->gpio_bridge_reset, 0); +		mdelay(10); +	} else { +		/* Pull MIPI Bridge reset pin to Low */ +		gpio_set_value_cansleep(pdata->gpio_bridge_reset, 0); +		mdelay(20); +		/* Pull MIPI Bridge reset pin to High */ +		gpio_set_value_cansleep(pdata->gpio_bridge_reset, 1); +		mdelay(40); +	} +} + +void tc35876x_configure_lvds_bridge(struct drm_device *dev) +{ +	struct i2c_client *i2c = tc35876x_client; +	u32 ppi_lptxtimecnt; +	u32 txtagocnt; +	u32 txtasurecnt; +	u32 id; + +	if (WARN(!tc35876x_client, "%s called before probe", __func__)) +		return; + +	dev_dbg(&tc35876x_client->dev, "%s\n", __func__); + +	if (!tc35876x_regr(i2c, IDREG, &id)) +		dev_info(&tc35876x_client->dev, "tc35876x ID 0x%08x\n", id); +	else +		dev_err(&tc35876x_client->dev, "Cannot read ID\n"); + +	ppi_lptxtimecnt = 4; +	txtagocnt = (5 * ppi_lptxtimecnt - 3) / 4; +	txtasurecnt = 3 * ppi_lptxtimecnt / 2; +	tc35876x_regw(i2c, PPI_TX_RX_TA, FLD_VAL(txtagocnt, 26, 16) | +		FLD_VAL(txtasurecnt, 10, 0)); +	tc35876x_regw(i2c, PPI_LPTXTIMECNT, FLD_VAL(ppi_lptxtimecnt, 10, 0)); + +	tc35876x_regw(i2c, PPI_D0S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0)); +	tc35876x_regw(i2c, PPI_D1S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0)); +	tc35876x_regw(i2c, PPI_D2S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0)); +	tc35876x_regw(i2c, PPI_D3S_CLRSIPOCOUNT, FLD_VAL(1, 5, 0)); + +	/* Enabling MIPI & PPI lanes, Enable 4 lanes */ +	tc35876x_regw(i2c, PPI_LANEENABLE, +		BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0)); +	tc35876x_regw(i2c, DSI_LANEENABLE, +		BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0)); +	tc35876x_regw(i2c, PPI_STARTPPI, BIT(0)); +	tc35876x_regw(i2c, DSI_STARTDSI, BIT(0)); + +	/* Setting LVDS output frequency */ +	tc35876x_regw(i2c, LVPHY0, FLD_VAL(1, 20, 16) | +		FLD_VAL(2, 15, 14) | FLD_VAL(6, 4, 0)); /* 0x00048006 */ + +	/* Setting video panel control register,0x00000120 VTGen=ON ?!?!? */ +	tc35876x_regw(i2c, VPCTRL, BIT(8) | BIT(5)); + +	/* Horizontal back porch and horizontal pulse width. 0x00280028 */ +	tc35876x_regw(i2c, HTIM1, FLD_VAL(40, 24, 16) | FLD_VAL(40, 8, 0)); + +	/* Horizontal front porch and horizontal active video size. 0x00500500*/ +	tc35876x_regw(i2c, HTIM2, FLD_VAL(80, 24, 16) | FLD_VAL(1280, 10, 0)); + +	/* Vertical back porch and vertical sync pulse width. 0x000e000a */ +	tc35876x_regw(i2c, VTIM1, FLD_VAL(14, 23, 16) | FLD_VAL(10, 7, 0)); + +	/* Vertical front porch and vertical display size. 0x000e0320 */ +	tc35876x_regw(i2c, VTIM2, FLD_VAL(14, 23, 16) | FLD_VAL(800, 10, 0)); + +	/* Set above HTIM1, HTIM2, VTIM1, and VTIM2 at next VSYNC. */ +	tc35876x_regw(i2c, VFUEN, BIT(0)); + +	/* Soft reset LCD controller. */ +	tc35876x_regw(i2c, SYSRST, BIT(2)); + +	/* LVDS-TX input muxing */ +	tc35876x_regw(i2c, LVMX0003, +		INPUT_MUX(INPUT_R5, INPUT_R4, INPUT_R3, INPUT_R2)); +	tc35876x_regw(i2c, LVMX0407, +		INPUT_MUX(INPUT_G2, INPUT_R7, INPUT_R1, INPUT_R6)); +	tc35876x_regw(i2c, LVMX0811, +		INPUT_MUX(INPUT_G1, INPUT_G0, INPUT_G4, INPUT_G3)); +	tc35876x_regw(i2c, LVMX1215, +		INPUT_MUX(INPUT_B2, INPUT_G7, INPUT_G6, INPUT_G5)); +	tc35876x_regw(i2c, LVMX1619, +		INPUT_MUX(INPUT_B4, INPUT_B3, INPUT_B1, INPUT_B0)); +	tc35876x_regw(i2c, LVMX2023, +		INPUT_MUX(LOGIC_0,  INPUT_B7, INPUT_B6, INPUT_B5)); +	tc35876x_regw(i2c, LVMX2427, +		INPUT_MUX(INPUT_R0, INPUT_DE, INPUT_VSYNC, INPUT_HSYNC)); + +	/* Enable LVDS transmitter. */ +	tc35876x_regw(i2c, LVCFG, BIT(0)); + +	/* Clear notifications. Don't write reserved bits. Was write 0xffffffff +	 * to 0x0288, must be in error?! */ +	tc35876x_regw(i2c, DSI_INTCLR, FLD_MASK(31, 30) | FLD_MASK(22, 0)); +} + +#define GPIOPWMCTRL	0x38F +#define PWM0CLKDIV0	0x62 /* low byte */ +#define PWM0CLKDIV1	0x61 /* high byte */ + +#define SYSTEMCLK	19200000UL /* 19.2 MHz */ +#define PWM_FREQUENCY	9600 /* Hz */ + +/* f = baseclk / (clkdiv + 1) => clkdiv = (baseclk - f) / f */ +static inline u16 calc_clkdiv(unsigned long baseclk, unsigned int f) +{ +	return (baseclk - f) / f; +} + +static void tc35876x_brightness_init(struct drm_device *dev) +{ +	int ret; +	u8 pwmctrl; +	u16 clkdiv; + +	/* Make sure the PWM reference is the 19.2 MHz system clock. Read first +	 * instead of setting directly to catch potential conflicts between PWM +	 * users. */ +	ret = intel_scu_ipc_ioread8(GPIOPWMCTRL, &pwmctrl); +	if (ret || pwmctrl != 0x01) { +		if (ret) +			dev_err(&dev->pdev->dev, "GPIOPWMCTRL read failed\n"); +		else +			dev_warn(&dev->pdev->dev, "GPIOPWMCTRL was not set to system clock (pwmctrl = 0x%02x)\n", pwmctrl); + +		ret = intel_scu_ipc_iowrite8(GPIOPWMCTRL, 0x01); +		if (ret) +			dev_err(&dev->pdev->dev, "GPIOPWMCTRL set failed\n"); +	} + +	clkdiv = calc_clkdiv(SYSTEMCLK, PWM_FREQUENCY); + +	ret = intel_scu_ipc_iowrite8(PWM0CLKDIV1, (clkdiv >> 8) & 0xff); +	if (!ret) +		ret = intel_scu_ipc_iowrite8(PWM0CLKDIV0, clkdiv & 0xff); + +	if (ret) +		dev_err(&dev->pdev->dev, "PWM0CLKDIV set failed\n"); +	else +		dev_dbg(&dev->pdev->dev, "PWM0CLKDIV set to 0x%04x (%d Hz)\n", +			clkdiv, PWM_FREQUENCY); +} + +#define PWM0DUTYCYCLE			0x67 + +void tc35876x_brightness_control(struct drm_device *dev, int level) +{ +	int ret; +	u8 duty_val; +	u8 panel_duty_val; + +	level = clamp(level, 0, MDFLD_DSI_BRIGHTNESS_MAX_LEVEL); + +	/* PWM duty cycle 0x00...0x63 corresponds to 0...99% */ +	duty_val = level * 0x63 / MDFLD_DSI_BRIGHTNESS_MAX_LEVEL; + +	/* I won't pretend to understand this formula. The panel spec is quite +	 * bad engrish. +	 */ +	panel_duty_val = (2 * level - 100) * 0xA9 / +			 MDFLD_DSI_BRIGHTNESS_MAX_LEVEL + 0x56; + +	ret = intel_scu_ipc_iowrite8(PWM0DUTYCYCLE, duty_val); +	if (ret) +		dev_err(&tc35876x_client->dev, "%s: ipc write fail\n", +			__func__); + +	if (cmi_lcd_i2c_client) { +		ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client, +						PANEL_PWM_MAX, panel_duty_val); +		if (ret < 0) +			dev_err(&cmi_lcd_i2c_client->dev, "%s: i2c write failed\n", +				__func__); +	} +} + +void tc35876x_toshiba_bridge_panel_off(struct drm_device *dev) +{ +	struct tc35876x_platform_data *pdata; + +	if (WARN(!tc35876x_client, "%s called before probe", __func__)) +		return; + +	dev_dbg(&tc35876x_client->dev, "%s\n", __func__); + +	pdata = dev_get_platdata(&tc35876x_client->dev); + +	if (pdata->gpio_panel_bl_en != -1) +		gpio_set_value_cansleep(pdata->gpio_panel_bl_en, 0); + +	if (pdata->gpio_panel_vadd != -1) +		gpio_set_value_cansleep(pdata->gpio_panel_vadd, 0); +} + +void tc35876x_toshiba_bridge_panel_on(struct drm_device *dev) +{ +	struct tc35876x_platform_data *pdata; +	struct drm_psb_private *dev_priv = dev->dev_private; + +	if (WARN(!tc35876x_client, "%s called before probe", __func__)) +		return; + +	dev_dbg(&tc35876x_client->dev, "%s\n", __func__); + +	pdata = dev_get_platdata(&tc35876x_client->dev); + +	if (pdata->gpio_panel_vadd != -1) { +		gpio_set_value_cansleep(pdata->gpio_panel_vadd, 1); +		msleep(260); +	} + +	if (cmi_lcd_i2c_client) { +		int ret; +		dev_dbg(&cmi_lcd_i2c_client->dev, "setting TCON\n"); +		/* Bit 4 is average_saving. Setting it to 1, the brightness is +		 * referenced to the average of the frame content. 0 means +		 * reference to the maximum of frame contents. Bits 3:0 are +		 * allow_distort. When set to a nonzero value, all color values +		 * between 255-allow_distort*2 and 255 are mapped to the +		 * 255-allow_distort*2 value. +		 */ +		ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client, +						PANEL_ALLOW_DISTORT, 0x10); +		if (ret < 0) +			dev_err(&cmi_lcd_i2c_client->dev, +				"i2c write failed (%d)\n", ret); +		ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client, +						PANEL_BYPASS_PWMI, 0); +		if (ret < 0) +			dev_err(&cmi_lcd_i2c_client->dev, +				"i2c write failed (%d)\n", ret); +		/* Set minimum brightness value - this is tunable */ +		ret = i2c_smbus_write_byte_data(cmi_lcd_i2c_client, +						PANEL_PWM_MIN, 0x35); +		if (ret < 0) +			dev_err(&cmi_lcd_i2c_client->dev, +				"i2c write failed (%d)\n", ret); +	} + +	if (pdata->gpio_panel_bl_en != -1) +		gpio_set_value_cansleep(pdata->gpio_panel_bl_en, 1); + +	tc35876x_brightness_control(dev, dev_priv->brightness_adjusted); +} + +static struct drm_display_mode *tc35876x_get_config_mode(struct drm_device *dev) +{ +	struct drm_display_mode *mode; + +	dev_dbg(&dev->pdev->dev, "%s\n", __func__); + +	mode = kzalloc(sizeof(*mode), GFP_KERNEL); +	if (!mode) +		return NULL; + +	/* FIXME: do this properly. */ +	mode->hdisplay = 1280; +	mode->vdisplay = 800; +	mode->hsync_start = 1360; +	mode->hsync_end = 1400; +	mode->htotal = 1440; +	mode->vsync_start = 814; +	mode->vsync_end = 824; +	mode->vtotal = 838; +	mode->clock = 33324 << 1; + +	dev_info(&dev->pdev->dev, "hdisplay(w) = %d\n", mode->hdisplay); +	dev_info(&dev->pdev->dev, "vdisplay(h) = %d\n", mode->vdisplay); +	dev_info(&dev->pdev->dev, "HSS = %d\n", mode->hsync_start); +	dev_info(&dev->pdev->dev, "HSE = %d\n", mode->hsync_end); +	dev_info(&dev->pdev->dev, "htotal = %d\n", mode->htotal); +	dev_info(&dev->pdev->dev, "VSS = %d\n", mode->vsync_start); +	dev_info(&dev->pdev->dev, "VSE = %d\n", mode->vsync_end); +	dev_info(&dev->pdev->dev, "vtotal = %d\n", mode->vtotal); +	dev_info(&dev->pdev->dev, "clock = %d\n", mode->clock); + +	drm_mode_set_name(mode); +	drm_mode_set_crtcinfo(mode, 0); + +	mode->type |= DRM_MODE_TYPE_PREFERRED; + +	return mode; +} + +/* DV1 Active area 216.96 x 135.6 mm */ +#define DV1_PANEL_WIDTH 217 +#define DV1_PANEL_HEIGHT 136 + +static int tc35876x_get_panel_info(struct drm_device *dev, int pipe, +				struct panel_info *pi) +{ +	if (!dev || !pi) +		return -EINVAL; + +	pi->width_mm = DV1_PANEL_WIDTH; +	pi->height_mm = DV1_PANEL_HEIGHT; + +	return 0; +} + +static int tc35876x_bridge_probe(struct i2c_client *client, +				const struct i2c_device_id *id) +{ +	struct tc35876x_platform_data *pdata; + +	dev_info(&client->dev, "%s\n", __func__); + +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { +		dev_err(&client->dev, "%s: i2c_check_functionality() failed\n", +			__func__); +		return -ENODEV; +	} + +	pdata = dev_get_platdata(&client->dev); +	if (!pdata) { +		dev_err(&client->dev, "%s: no platform data\n", __func__); +		return -ENODEV; +	} + +	if (pdata->gpio_bridge_reset != -1) { +		gpio_request(pdata->gpio_bridge_reset, "tc35876x bridge reset"); +		gpio_direction_output(pdata->gpio_bridge_reset, 0); +	} + +	if (pdata->gpio_panel_bl_en != -1) { +		gpio_request(pdata->gpio_panel_bl_en, "tc35876x panel bl en"); +		gpio_direction_output(pdata->gpio_panel_bl_en, 0); +	} + +	if (pdata->gpio_panel_vadd != -1) { +		gpio_request(pdata->gpio_panel_vadd, "tc35876x panel vadd"); +		gpio_direction_output(pdata->gpio_panel_vadd, 0); +	} + +	tc35876x_client = client; + +	return 0; +} + +static int tc35876x_bridge_remove(struct i2c_client *client) +{ +	struct tc35876x_platform_data *pdata = dev_get_platdata(&client->dev); + +	dev_dbg(&client->dev, "%s\n", __func__); + +	if (pdata->gpio_bridge_reset != -1) +		gpio_free(pdata->gpio_bridge_reset); + +	if (pdata->gpio_panel_bl_en != -1) +		gpio_free(pdata->gpio_panel_bl_en); + +	if (pdata->gpio_panel_vadd != -1) +		gpio_free(pdata->gpio_panel_vadd); + +	tc35876x_client = NULL; + +	return 0; +} + +static const struct i2c_device_id tc35876x_bridge_id[] = { +	{ "i2c_disp_brig", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, tc35876x_bridge_id); + +static struct i2c_driver tc35876x_bridge_i2c_driver = { +	.driver = { +		.name = "i2c_disp_brig", +	}, +	.id_table = tc35876x_bridge_id, +	.probe = tc35876x_bridge_probe, +	.remove = tc35876x_bridge_remove, +}; + +/* LCD panel I2C */ +static int cmi_lcd_i2c_probe(struct i2c_client *client, +			     const struct i2c_device_id *id) +{ +	dev_info(&client->dev, "%s\n", __func__); + +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { +		dev_err(&client->dev, "%s: i2c_check_functionality() failed\n", +			__func__); +		return -ENODEV; +	} + +	cmi_lcd_i2c_client = client; + +	return 0; +} + +static int cmi_lcd_i2c_remove(struct i2c_client *client) +{ +	dev_dbg(&client->dev, "%s\n", __func__); + +	cmi_lcd_i2c_client = NULL; + +	return 0; +} + +static const struct i2c_device_id cmi_lcd_i2c_id[] = { +	{ "cmi-lcd", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, cmi_lcd_i2c_id); + +static struct i2c_driver cmi_lcd_i2c_driver = { +	.driver = { +		.name = "cmi-lcd", +	}, +	.id_table = cmi_lcd_i2c_id, +	.probe = cmi_lcd_i2c_probe, +	.remove = cmi_lcd_i2c_remove, +}; + +/* HACK to create I2C device while it's not created by platform code */ +#define CMI_LCD_I2C_ADAPTER	2 +#define CMI_LCD_I2C_ADDR	0x60 + +static int cmi_lcd_hack_create_device(void) +{ +	struct i2c_adapter *adapter; +	struct i2c_client *client; +	struct i2c_board_info info = { +		.type = "cmi-lcd", +		.addr = CMI_LCD_I2C_ADDR, +	}; + +	pr_debug("%s\n", __func__); + +	adapter = i2c_get_adapter(CMI_LCD_I2C_ADAPTER); +	if (!adapter) { +		pr_err("%s: i2c_get_adapter(%d) failed\n", __func__, +			CMI_LCD_I2C_ADAPTER); +		return -EINVAL; +	} + +	client = i2c_new_device(adapter, &info); +	if (!client) { +		pr_err("%s: i2c_new_device() failed\n", __func__); +		i2c_put_adapter(adapter); +		return -EINVAL; +	} + +	return 0; +} + +static const struct drm_encoder_helper_funcs tc35876x_encoder_helper_funcs = { +	.dpms = mdfld_dsi_dpi_dpms, +	.mode_fixup = mdfld_dsi_dpi_mode_fixup, +	.prepare = mdfld_dsi_dpi_prepare, +	.mode_set = mdfld_dsi_dpi_mode_set, +	.commit = mdfld_dsi_dpi_commit, +}; + +static const struct drm_encoder_funcs tc35876x_encoder_funcs = { +	.destroy = drm_encoder_cleanup, +}; + +const struct panel_funcs mdfld_tc35876x_funcs = { +	.encoder_funcs = &tc35876x_encoder_funcs, +	.encoder_helper_funcs = &tc35876x_encoder_helper_funcs, +	.get_config_mode = tc35876x_get_config_mode, +	.get_panel_info = tc35876x_get_panel_info, +}; + +void tc35876x_init(struct drm_device *dev) +{ +	int r; + +	dev_dbg(&dev->pdev->dev, "%s\n", __func__); + +	cmi_lcd_hack_create_device(); + +	r = i2c_add_driver(&cmi_lcd_i2c_driver); +	if (r < 0) +		dev_err(&dev->pdev->dev, +			"%s: i2c_add_driver() for %s failed (%d)\n", +			__func__, cmi_lcd_i2c_driver.driver.name, r); + +	r = i2c_add_driver(&tc35876x_bridge_i2c_driver); +	if (r < 0) +		dev_err(&dev->pdev->dev, +			"%s: i2c_add_driver() for %s failed (%d)\n", +			__func__, tc35876x_bridge_i2c_driver.driver.name, r); + +	tc35876x_brightness_init(dev); +} + +void tc35876x_exit(void) +{ +	pr_debug("%s\n", __func__); + +	i2c_del_driver(&tc35876x_bridge_i2c_driver); + +	if (cmi_lcd_i2c_client) +		i2c_del_driver(&cmi_lcd_i2c_driver); +} diff --git a/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.h b/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.h new file mode 100644 index 00000000000..b14b7f9e7d1 --- /dev/null +++ b/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.h @@ -0,0 +1,38 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __MDFLD_DSI_LVDS_BRIDGE_H__ +#define __MDFLD_DSI_LVDS_BRIDGE_H__ + +void tc35876x_set_bridge_reset_state(struct drm_device *dev, int state); +void tc35876x_configure_lvds_bridge(struct drm_device *dev); +void tc35876x_brightness_control(struct drm_device *dev, int level); +void tc35876x_toshiba_bridge_panel_off(struct drm_device *dev); +void tc35876x_toshiba_bridge_panel_on(struct drm_device *dev); +void tc35876x_init(struct drm_device *dev); +void tc35876x_exit(void); + +extern const struct panel_funcs mdfld_tc35876x_funcs; + +#endif /*__MDFLD_DSI_LVDS_BRIDGE_H__*/  | 
