Merge branch 'drm-next' of git://git.kernel.org/pub/scm/linux/kernel/git/airlied...
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 31 Dec 2008 01:25:49 +0000 (17:25 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 31 Dec 2008 01:25:49 +0000 (17:25 -0800)
* 'drm-next' of git://git.kernel.org/pub/scm/linux/kernel/git/airlied/drm-2.6: (37 commits)
  drm/i915: fix modeset devname allocation + agp init return check.
  drm/i915: Remove redundant test in error path.
  drm: Add a debug node for vblank state.
  drm: Avoid use-before-null-test on dev in drm_cleanup().
  drm/i915: Don't print to dmesg when taking signal during object_pin.
  drm: pin new and unpin old buffer when setting a mode.
  drm/i915: un-EXPORT and make 'intelfb_panic' static
  drm/i915: Delete unused, pointless i915_driver_firstopen.
  drm/i915: fix sparse warnings: returning void-valued expression
  drm/i915: fix sparse warnings: move 'extern' decls to header file
  drm/i915: fix sparse warnings: make symbols static
  drm/i915: fix sparse warnings: declare one-bit bitfield as unsigned
  drm/i915: Don't double-unpin buffers if we take a signal in evict_everything().
  drm/i915: Fix fbcon setup to align display pitch to 64b.
  drm/i915: Add missing userland definitions for gem init/execbuffer.
  i915/drm: provide compat defines for userspace for certain struct members.
  drm: drop DRM_IOCTL_MODE_REPLACEFB, add+remove works just as well.
  drm: sanitise drm modesetting API + remove unused hotplug
  drm: fix allowing master ioctls on non-master fds.
  drm/radeon: use locked rmmap to remove sarea mapping.
  ...

67 files changed:
drivers/gpu/drm/Kconfig
drivers/gpu/drm/Makefile
drivers/gpu/drm/drm_auth.c
drivers/gpu/drm/drm_bufs.c
drivers/gpu/drm/drm_context.c
drivers/gpu/drm/drm_crtc.c [new file with mode: 0644]
drivers/gpu/drm/drm_crtc_helper.c [new file with mode: 0644]
drivers/gpu/drm/drm_drv.c
drivers/gpu/drm/drm_edid.c [new file with mode: 0644]
drivers/gpu/drm/drm_fops.c
drivers/gpu/drm/drm_gem.c
drivers/gpu/drm/drm_hashtab.c
drivers/gpu/drm/drm_ioctl.c
drivers/gpu/drm/drm_irq.c
drivers/gpu/drm/drm_lock.c
drivers/gpu/drm/drm_mm.c
drivers/gpu/drm/drm_modes.c [new file with mode: 0644]
drivers/gpu/drm/drm_proc.c
drivers/gpu/drm/drm_stub.c
drivers/gpu/drm/drm_sysfs.c
drivers/gpu/drm/drm_vm.c
drivers/gpu/drm/i915/Makefile
drivers/gpu/drm/i915/dvo.h [new file with mode: 0644]
drivers/gpu/drm/i915/dvo_ch7017.c [new file with mode: 0644]
drivers/gpu/drm/i915/dvo_ch7xxx.c [new file with mode: 0644]
drivers/gpu/drm/i915/dvo_ivch.c [new file with mode: 0644]
drivers/gpu/drm/i915/dvo_sil164.c [new file with mode: 0644]
drivers/gpu/drm/i915/dvo_tfp410.c [new file with mode: 0644]
drivers/gpu/drm/i915/i915_dma.c
drivers/gpu/drm/i915/i915_drv.c
drivers/gpu/drm/i915/i915_drv.h
drivers/gpu/drm/i915/i915_gem.c
drivers/gpu/drm/i915/i915_gem_proc.c
drivers/gpu/drm/i915/i915_gem_tiling.c
drivers/gpu/drm/i915/i915_irq.c
drivers/gpu/drm/i915/i915_mem.c
drivers/gpu/drm/i915/i915_opregion.c
drivers/gpu/drm/i915/i915_reg.h
drivers/gpu/drm/i915/intel_bios.c [new file with mode: 0644]
drivers/gpu/drm/i915/intel_bios.h [new file with mode: 0644]
drivers/gpu/drm/i915/intel_crt.c [new file with mode: 0644]
drivers/gpu/drm/i915/intel_display.c [new file with mode: 0644]
drivers/gpu/drm/i915/intel_drv.h [new file with mode: 0644]
drivers/gpu/drm/i915/intel_dvo.c [new file with mode: 0644]
drivers/gpu/drm/i915/intel_fb.c [new file with mode: 0644]
drivers/gpu/drm/i915/intel_i2c.c [new file with mode: 0644]
drivers/gpu/drm/i915/intel_lvds.c [new file with mode: 0644]
drivers/gpu/drm/i915/intel_modes.c [new file with mode: 0644]
drivers/gpu/drm/i915/intel_sdvo.c [new file with mode: 0644]
drivers/gpu/drm/i915/intel_sdvo_regs.h [new file with mode: 0644]
drivers/gpu/drm/i915/intel_tv.c [new file with mode: 0644]
drivers/gpu/drm/radeon/r300_cmdbuf.c
drivers/gpu/drm/radeon/radeon_cp.c
drivers/gpu/drm/radeon/radeon_drv.c
drivers/gpu/drm/radeon/radeon_drv.h
drivers/gpu/drm/radeon/radeon_state.c
drivers/video/console/vgacon.c
include/drm/Kbuild
include/drm/drm.h
include/drm/drmP.h
include/drm/drm_crtc.h [new file with mode: 0644]
include/drm/drm_crtc_helper.h [new file with mode: 0644]
include/drm/drm_edid.h [new file with mode: 0644]
include/drm/drm_mode.h [new file with mode: 0644]
include/drm/drm_sarea.h
include/drm/i915_drm.h
include/linux/console.h

index a8b33c2ec8d2fa951a4b81509cf944eb0e4759af..5130b72d593c5726e34263ad0d63eb248b1265f6 100644 (file)
@@ -7,6 +7,8 @@
 menuconfig DRM
        tristate "Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)"
        depends on (AGP || AGP=n) && PCI && !EMULATED_CMPXCHG && MMU
+       select I2C
+       select I2C_ALGOBIT
        help
          Kernel-level support for the Direct Rendering Infrastructure (DRI)
          introduced in XFree86 4.0. If you say Y here, you need to select
@@ -65,6 +67,10 @@ config DRM_I830
          will load the correct one.
 
 config DRM_I915
+       select FB_CFB_FILLRECT
+       select FB_CFB_COPYAREA
+       select FB_CFB_IMAGEBLIT
+       depends on FB
        tristate "i915 driver"
        help
          Choose this option if you have a system that has Intel 830M, 845G,
@@ -76,6 +82,17 @@ config DRM_I915
 
 endchoice
 
+config DRM_I915_KMS
+       bool "Enable modesetting on intel by default"
+       depends on DRM_I915
+       help
+       Choose this option if you want kernel modesetting enabled by default,
+       and you have a new enough userspace to support this. Running old
+       userspaces with this enabled will cause pain.  Note that this causes
+       the driver to bind to PCI devices, which precludes loading things
+       like intelfb.
+
+
 config DRM_MGA
        tristate "Matrox g200/g400"
        depends on DRM
index 74da99495e21d8cbce22853d5e943d2b5e37e647..30022c4a5c12a0be54aef5cfb7b0fddd7abd06df 100644 (file)
@@ -9,7 +9,8 @@ drm-y       :=  drm_auth.o drm_bufs.o drm_cache.o \
                drm_drv.o drm_fops.o drm_gem.o drm_ioctl.o drm_irq.o \
                drm_lock.o drm_memory.o drm_proc.o drm_stub.o drm_vm.o \
                drm_agpsupport.o drm_scatter.o ati_pcigart.o drm_pci.o \
-               drm_sysfs.o drm_hashtab.o drm_sman.o drm_mm.o
+               drm_sysfs.o drm_hashtab.o drm_sman.o drm_mm.o \
+               drm_crtc.o drm_crtc_helper.o drm_modes.o drm_edid.o
 
 drm-$(CONFIG_COMPAT) += drm_ioc32.o
 
index a73462723d2d517d2c7af88cb46880b9024ee354..ca7a9ef5007b3c383f05f6e8969dfb37c1368408 100644 (file)
  * the one with matching magic number, while holding the drm_device::struct_mutex
  * lock.
  */
-static struct drm_file *drm_find_file(struct drm_device * dev, drm_magic_t magic)
+static struct drm_file *drm_find_file(struct drm_master *master, drm_magic_t magic)
 {
        struct drm_file *retval = NULL;
        struct drm_magic_entry *pt;
        struct drm_hash_item *hash;
+       struct drm_device *dev = master->minor->dev;
 
        mutex_lock(&dev->struct_mutex);
-       if (!drm_ht_find_item(&dev->magiclist, (unsigned long)magic, &hash)) {
+       if (!drm_ht_find_item(&master->magiclist, (unsigned long)magic, &hash)) {
                pt = drm_hash_entry(hash, struct drm_magic_entry, hash_item);
                retval = pt->priv;
        }
@@ -71,11 +72,11 @@ static struct drm_file *drm_find_file(struct drm_device * dev, drm_magic_t magic
  * associated the magic number hash key in drm_device::magiclist, while holding
  * the drm_device::struct_mutex lock.
  */
-static int drm_add_magic(struct drm_device * dev, struct drm_file * priv,
+static int drm_add_magic(struct drm_master *master, struct drm_file *priv,
                         drm_magic_t magic)
 {
        struct drm_magic_entry *entry;
-
+       struct drm_device *dev = master->minor->dev;
        DRM_DEBUG("%d\n", magic);
 
        entry = drm_alloc(sizeof(*entry), DRM_MEM_MAGIC);
@@ -83,11 +84,10 @@ static int drm_add_magic(struct drm_device * dev, struct drm_file * priv,
                return -ENOMEM;
        memset(entry, 0, sizeof(*entry));
        entry->priv = priv;
-
        entry->hash_item.key = (unsigned long)magic;
        mutex_lock(&dev->struct_mutex);
-       drm_ht_insert_item(&dev->magiclist, &entry->hash_item);
-       list_add_tail(&entry->head, &dev->magicfree);
+       drm_ht_insert_item(&master->magiclist, &entry->hash_item);
+       list_add_tail(&entry->head, &master->magicfree);
        mutex_unlock(&dev->struct_mutex);
 
        return 0;
@@ -102,20 +102,21 @@ static int drm_add_magic(struct drm_device * dev, struct drm_file * priv,
  * Searches and unlinks the entry in drm_device::magiclist with the magic
  * number hash key, while holding the drm_device::struct_mutex lock.
  */
-static int drm_remove_magic(struct drm_device * dev, drm_magic_t magic)
+static int drm_remove_magic(struct drm_master *master, drm_magic_t magic)
 {
        struct drm_magic_entry *pt;
        struct drm_hash_item *hash;
+       struct drm_device *dev = master->minor->dev;
 
        DRM_DEBUG("%d\n", magic);
 
        mutex_lock(&dev->struct_mutex);
-       if (drm_ht_find_item(&dev->magiclist, (unsigned long)magic, &hash)) {
+       if (drm_ht_find_item(&master->magiclist, (unsigned long)magic, &hash)) {
                mutex_unlock(&dev->struct_mutex);
                return -EINVAL;
        }
        pt = drm_hash_entry(hash, struct drm_magic_entry, hash_item);
-       drm_ht_remove_item(&dev->magiclist, hash);
+       drm_ht_remove_item(&master->magiclist, hash);
        list_del(&pt->head);
        mutex_unlock(&dev->struct_mutex);
 
@@ -153,9 +154,9 @@ int drm_getmagic(struct drm_device *dev, void *data, struct drm_file *file_priv)
                                ++sequence;     /* reserve 0 */
                        auth->magic = sequence++;
                        spin_unlock(&lock);
-               } while (drm_find_file(dev, auth->magic));
+               } while (drm_find_file(file_priv->master, auth->magic));
                file_priv->magic = auth->magic;
-               drm_add_magic(dev, file_priv, auth->magic);
+               drm_add_magic(file_priv->master, file_priv, auth->magic);
        }
 
        DRM_DEBUG("%u\n", auth->magic);
@@ -181,9 +182,9 @@ int drm_authmagic(struct drm_device *dev, void *data,
        struct drm_file *file;
 
        DRM_DEBUG("%u\n", auth->magic);
-       if ((file = drm_find_file(dev, auth->magic))) {
+       if ((file = drm_find_file(file_priv->master, auth->magic))) {
                file->authenticated = 1;
-               drm_remove_magic(dev, auth->magic);
+               drm_remove_magic(file_priv->master, auth->magic);
                return 0;
        }
        return -EINVAL;
index bde64b84166e1d783a2c5a8ece1816c483e76f0b..72c667f9bee1aadbc4067687d05aca0849be751b 100644 (file)
@@ -54,9 +54,9 @@ static struct drm_map_list *drm_find_matching_map(struct drm_device *dev,
 {
        struct drm_map_list *entry;
        list_for_each_entry(entry, &dev->maplist, head) {
-               if (entry->map && map->type == entry->map->type &&
+               if (entry->map && (entry->master == dev->primary->master) && (map->type == entry->map->type) &&
                    ((entry->map->offset == map->offset) ||
-                    (map->type == _DRM_SHM && map->flags==_DRM_CONTAINS_LOCK))) {
+                    ((map->type == _DRM_SHM) && (map->flags&_DRM_CONTAINS_LOCK)))) {
                        return entry;
                }
        }
@@ -210,12 +210,12 @@ static int drm_addmap_core(struct drm_device * dev, unsigned int offset,
                map->offset = (unsigned long)map->handle;
                if (map->flags & _DRM_CONTAINS_LOCK) {
                        /* Prevent a 2nd X Server from creating a 2nd lock */
-                       if (dev->lock.hw_lock != NULL) {
+                       if (dev->primary->master->lock.hw_lock != NULL) {
                                vfree(map->handle);
                                drm_free(map, sizeof(*map), DRM_MEM_MAPS);
                                return -EBUSY;
                        }
-                       dev->sigdata.lock = dev->lock.hw_lock = map->handle;    /* Pointer to lock */
+                       dev->sigdata.lock = dev->primary->master->lock.hw_lock = map->handle;   /* Pointer to lock */
                }
                break;
        case _DRM_AGP: {
@@ -261,6 +261,9 @@ static int drm_addmap_core(struct drm_device * dev, unsigned int offset,
                }
                DRM_DEBUG("AGP offset = 0x%08lx, size = 0x%08lx\n", map->offset, map->size);
 
+               break;
+       case _DRM_GEM:
+               DRM_ERROR("tried to rmmap GEM object\n");
                break;
        }
        case _DRM_SCATTER_GATHER:
@@ -319,6 +322,7 @@ static int drm_addmap_core(struct drm_device * dev, unsigned int offset,
        list->user_token = list->hash.key << PAGE_SHIFT;
        mutex_unlock(&dev->struct_mutex);
 
+       list->master = dev->primary->master;
        *maplist = list;
        return 0;
        }
@@ -345,7 +349,7 @@ int drm_addmap_ioctl(struct drm_device *dev, void *data,
        struct drm_map_list *maplist;
        int err;
 
-       if (!(capable(CAP_SYS_ADMIN) || map->type == _DRM_AGP))
+       if (!(capable(CAP_SYS_ADMIN) || map->type == _DRM_AGP || map->type == _DRM_SHM))
                return -EPERM;
 
        err = drm_addmap_core(dev, map->offset, map->size, map->type,
@@ -380,10 +384,12 @@ int drm_rmmap_locked(struct drm_device *dev, drm_local_map_t *map)
        struct drm_map_list *r_list = NULL, *list_t;
        drm_dma_handle_t dmah;
        int found = 0;
+       struct drm_master *master;
 
        /* Find the list entry for the map and remove it */
        list_for_each_entry_safe(r_list, list_t, &dev->maplist, head) {
                if (r_list->map == map) {
+                       master = r_list->master;
                        list_del(&r_list->head);
                        drm_ht_remove_key(&dev->map_hash,
                                          r_list->user_token >> PAGE_SHIFT);
@@ -409,6 +415,13 @@ int drm_rmmap_locked(struct drm_device *dev, drm_local_map_t *map)
                break;
        case _DRM_SHM:
                vfree(map->handle);
+               if (master) {
+                       if (dev->sigdata.lock == master->lock.hw_lock)
+                               dev->sigdata.lock = NULL;
+                       master->lock.hw_lock = NULL;   /* SHM removed */
+                       master->lock.file_priv = NULL;
+                       wake_up_interruptible(&master->lock.lock_queue);
+               }
                break;
        case _DRM_AGP:
        case _DRM_SCATTER_GATHER:
@@ -419,11 +432,15 @@ int drm_rmmap_locked(struct drm_device *dev, drm_local_map_t *map)
                dmah.size = map->size;
                __drm_pci_free(dev, &dmah);
                break;
+       case _DRM_GEM:
+               DRM_ERROR("tried to rmmap GEM object\n");
+               break;
        }
        drm_free(map, sizeof(*map), DRM_MEM_MAPS);
 
        return 0;
 }
+EXPORT_SYMBOL(drm_rmmap_locked);
 
 int drm_rmmap(struct drm_device *dev, drm_local_map_t *map)
 {
index d505f695421fbeff447435c450a2882e40133227..809ec0f034524506ef0b4cef13e4d60b89fa10d3 100644 (file)
@@ -256,12 +256,13 @@ static int drm_context_switch(struct drm_device * dev, int old, int new)
  * hardware lock is held, clears the drm_device::context_flag and wakes up
  * drm_device::context_wait.
  */
-static int drm_context_switch_complete(struct drm_device * dev, int new)
+static int drm_context_switch_complete(struct drm_device *dev,
+                                      struct drm_file *file_priv, int new)
 {
        dev->last_context = new;        /* PRE/POST: This is the _only_ writer. */
        dev->last_switch = jiffies;
 
-       if (!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) {
+       if (!_DRM_LOCK_IS_HELD(file_priv->master->lock.hw_lock->lock)) {
                DRM_ERROR("Lock isn't held after context switch\n");
        }
 
@@ -420,7 +421,7 @@ int drm_newctx(struct drm_device *dev, void *data,
        struct drm_ctx *ctx = data;
 
        DRM_DEBUG("%d\n", ctx->handle);
-       drm_context_switch_complete(dev, ctx->handle);
+       drm_context_switch_complete(dev, file_priv, ctx->handle);
 
        return 0;
 }
@@ -442,9 +443,6 @@ int drm_rmctx(struct drm_device *dev, void *data,
        struct drm_ctx *ctx = data;
 
        DRM_DEBUG("%d\n", ctx->handle);
-       if (ctx->handle == DRM_KERNEL_CONTEXT + 1) {
-               file_priv->remove_auth_on_close = 1;
-       }
        if (ctx->handle != DRM_KERNEL_CONTEXT) {
                if (dev->driver->context_dtor)
                        dev->driver->context_dtor(dev, ctx->handle);
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
new file mode 100644 (file)
index 0000000..53c8725
--- /dev/null
@@ -0,0 +1,2446 @@
+/*
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ * Copyright (c) 2008 Red Hat Inc.
+ *
+ * DRM core CRTC related functions
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Keith Packard
+ *     Eric Anholt <eric@anholt.net>
+ *      Dave Airlie <airlied@linux.ie>
+ *      Jesse Barnes <jesse.barnes@intel.com>
+ */
+#include <linux/list.h>
+#include "drm.h"
+#include "drmP.h"
+#include "drm_crtc.h"
+
+struct drm_prop_enum_list {
+       int type;
+       char *name;
+};
+
+/* Avoid boilerplate.  I'm tired of typing. */
+#define DRM_ENUM_NAME_FN(fnname, list)                         \
+       char *fnname(int val)                                   \
+       {                                                       \
+               int i;                                          \
+               for (i = 0; i < ARRAY_SIZE(list); i++) {        \
+                       if (list[i].type == val)                \
+                               return list[i].name;            \
+               }                                               \
+               return "(unknown)";                             \
+       }
+
+/*
+ * Global properties
+ */
+static struct drm_prop_enum_list drm_dpms_enum_list[] =
+{      { DRM_MODE_DPMS_ON, "On" },
+       { DRM_MODE_DPMS_STANDBY, "Standby" },
+       { DRM_MODE_DPMS_SUSPEND, "Suspend" },
+       { DRM_MODE_DPMS_OFF, "Off" }
+};
+
+DRM_ENUM_NAME_FN(drm_get_dpms_name, drm_dpms_enum_list)
+
+/*
+ * Optional properties
+ */
+static struct drm_prop_enum_list drm_scaling_mode_enum_list[] =
+{
+       { DRM_MODE_SCALE_NON_GPU, "Non-GPU" },
+       { DRM_MODE_SCALE_FULLSCREEN, "Fullscreen" },
+       { DRM_MODE_SCALE_NO_SCALE, "No scale" },
+       { DRM_MODE_SCALE_ASPECT, "Aspect" },
+};
+
+static struct drm_prop_enum_list drm_dithering_mode_enum_list[] =
+{
+       { DRM_MODE_DITHERING_OFF, "Off" },
+       { DRM_MODE_DITHERING_ON, "On" },
+};
+
+/*
+ * Non-global properties, but "required" for certain connectors.
+ */
+static struct drm_prop_enum_list drm_dvi_i_select_enum_list[] =
+{
+       { DRM_MODE_SUBCONNECTOR_Automatic, "Automatic" }, /* DVI-I and TV-out */
+       { DRM_MODE_SUBCONNECTOR_DVID,      "DVI-D"     }, /* DVI-I  */
+       { DRM_MODE_SUBCONNECTOR_DVIA,      "DVI-A"     }, /* DVI-I  */
+};
+
+DRM_ENUM_NAME_FN(drm_get_dvi_i_select_name, drm_dvi_i_select_enum_list)
+
+static struct drm_prop_enum_list drm_dvi_i_subconnector_enum_list[] =
+{
+       { DRM_MODE_SUBCONNECTOR_Unknown,   "Unknown"   }, /* DVI-I and TV-out */
+       { DRM_MODE_SUBCONNECTOR_DVID,      "DVI-D"     }, /* DVI-I  */
+       { DRM_MODE_SUBCONNECTOR_DVIA,      "DVI-A"     }, /* DVI-I  */
+};
+
+DRM_ENUM_NAME_FN(drm_get_dvi_i_subconnector_name,
+                drm_dvi_i_subconnector_enum_list)
+
+static struct drm_prop_enum_list drm_tv_select_enum_list[] =
+{
+       { DRM_MODE_SUBCONNECTOR_Automatic, "Automatic" }, /* DVI-I and TV-out */
+       { DRM_MODE_SUBCONNECTOR_Composite, "Composite" }, /* TV-out */
+       { DRM_MODE_SUBCONNECTOR_SVIDEO,    "SVIDEO"    }, /* TV-out */
+       { DRM_MODE_SUBCONNECTOR_Component, "Component" }, /* TV-out */
+};
+
+DRM_ENUM_NAME_FN(drm_get_tv_select_name, drm_tv_select_enum_list)
+
+static struct drm_prop_enum_list drm_tv_subconnector_enum_list[] =
+{
+       { DRM_MODE_SUBCONNECTOR_Unknown,   "Unknown"   }, /* DVI-I and TV-out */
+       { DRM_MODE_SUBCONNECTOR_Composite, "Composite" }, /* TV-out */
+       { DRM_MODE_SUBCONNECTOR_SVIDEO,    "SVIDEO"    }, /* TV-out */
+       { DRM_MODE_SUBCONNECTOR_Component, "Component" }, /* TV-out */
+};
+
+DRM_ENUM_NAME_FN(drm_get_tv_subconnector_name,
+                drm_tv_subconnector_enum_list)
+
+struct drm_conn_prop_enum_list {
+       int type;
+       char *name;
+       int count;
+};
+
+/*
+ * Connector and encoder types.
+ */
+static struct drm_conn_prop_enum_list drm_connector_enum_list[] =
+{      { DRM_MODE_CONNECTOR_Unknown, "Unknown", 0 },
+       { DRM_MODE_CONNECTOR_VGA, "VGA", 0 },
+       { DRM_MODE_CONNECTOR_DVII, "DVI-I", 0 },
+       { DRM_MODE_CONNECTOR_DVID, "DVI-D", 0 },
+       { DRM_MODE_CONNECTOR_DVIA, "DVI-A", 0 },
+       { DRM_MODE_CONNECTOR_Composite, "Composite", 0 },
+       { DRM_MODE_CONNECTOR_SVIDEO, "SVIDEO", 0 },
+       { DRM_MODE_CONNECTOR_LVDS, "LVDS", 0 },
+       { DRM_MODE_CONNECTOR_Component, "Component", 0 },
+       { DRM_MODE_CONNECTOR_9PinDIN, "9-pin DIN", 0 },
+       { DRM_MODE_CONNECTOR_DisplayPort, "DisplayPort", 0 },
+       { DRM_MODE_CONNECTOR_HDMIA, "HDMI Type A", 0 },
+       { DRM_MODE_CONNECTOR_HDMIB, "HDMI Type B", 0 },
+};
+
+static struct drm_prop_enum_list drm_encoder_enum_list[] =
+{      { DRM_MODE_ENCODER_NONE, "None" },
+       { DRM_MODE_ENCODER_DAC, "DAC" },
+       { DRM_MODE_ENCODER_TMDS, "TMDS" },
+       { DRM_MODE_ENCODER_LVDS, "LVDS" },
+       { DRM_MODE_ENCODER_TVDAC, "TV" },
+};
+
+char *drm_get_encoder_name(struct drm_encoder *encoder)
+{
+       static char buf[32];
+
+       snprintf(buf, 32, "%s-%d",
+                drm_encoder_enum_list[encoder->encoder_type].name,
+                encoder->base.id);
+       return buf;
+}
+
+char *drm_get_connector_name(struct drm_connector *connector)
+{
+       static char buf[32];
+
+       snprintf(buf, 32, "%s-%d",
+                drm_connector_enum_list[connector->connector_type].name,
+                connector->connector_type_id);
+       return buf;
+}
+EXPORT_SYMBOL(drm_get_connector_name);
+
+char *drm_get_connector_status_name(enum drm_connector_status status)
+{
+       if (status == connector_status_connected)
+               return "connected";
+       else if (status == connector_status_disconnected)
+               return "disconnected";
+       else
+               return "unknown";
+}
+
+/**
+ * drm_mode_object_get - allocate a new identifier
+ * @dev: DRM device
+ * @ptr: object pointer, used to generate unique ID
+ * @type: object type
+ *
+ * LOCKING:
+ * Caller must hold DRM mode_config lock.
+ *
+ * Create a unique identifier based on @ptr in @dev's identifier space.  Used
+ * for tracking modes, CRTCs and connectors.
+ *
+ * RETURNS:
+ * New unique (relative to other objects in @dev) integer identifier for the
+ * object.
+ */
+static int drm_mode_object_get(struct drm_device *dev,
+                              struct drm_mode_object *obj, uint32_t obj_type)
+{
+       int new_id = 0;
+       int ret;
+
+       WARN(!mutex_is_locked(&dev->mode_config.mutex),
+            "%s called w/o mode_config lock\n", __FUNCTION__);
+again:
+       if (idr_pre_get(&dev->mode_config.crtc_idr, GFP_KERNEL) == 0) {
+               DRM_ERROR("Ran out memory getting a mode number\n");
+               return -EINVAL;
+       }
+
+       ret = idr_get_new_above(&dev->mode_config.crtc_idr, obj, 1, &new_id);
+       if (ret == -EAGAIN)
+               goto again;
+
+       obj->id = new_id;
+       obj->type = obj_type;
+       return 0;
+}
+
+/**
+ * drm_mode_object_put - free an identifer
+ * @dev: DRM device
+ * @id: ID to free
+ *
+ * LOCKING:
+ * Caller must hold DRM mode_config lock.
+ *
+ * Free @id from @dev's unique identifier pool.
+ */
+static void drm_mode_object_put(struct drm_device *dev,
+                               struct drm_mode_object *object)
+{
+       idr_remove(&dev->mode_config.crtc_idr, object->id);
+}
+
+void *drm_mode_object_find(struct drm_device *dev, uint32_t id, uint32_t type)
+{
+       struct drm_mode_object *obj;
+
+       obj = idr_find(&dev->mode_config.crtc_idr, id);
+       if (!obj || (obj->type != type) || (obj->id != id))
+               return NULL;
+
+       return obj;
+}
+EXPORT_SYMBOL(drm_mode_object_find);
+
+/**
+ * drm_crtc_from_fb - find the CRTC structure associated with an fb
+ * @dev: DRM device
+ * @fb: framebuffer in question
+ *
+ * LOCKING:
+ * Caller must hold mode_config lock.
+ *
+ * Find CRTC in the mode_config structure that matches @fb.
+ *
+ * RETURNS:
+ * Pointer to the CRTC or NULL if it wasn't found.
+ */
+struct drm_crtc *drm_crtc_from_fb(struct drm_device *dev,
+                                 struct drm_framebuffer *fb)
+{
+       struct drm_crtc *crtc;
+
+       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+               if (crtc->fb == fb)
+                       return crtc;
+       }
+       return NULL;
+}
+
+/**
+ * drm_framebuffer_init - initialize a framebuffer
+ * @dev: DRM device
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Allocates an ID for the framebuffer's parent mode object, sets its mode
+ * functions & device file and adds it to the master fd list.
+ *
+ * RETURNS:
+ * Zero on success, error code on falure.
+ */
+int drm_framebuffer_init(struct drm_device *dev, struct drm_framebuffer *fb,
+                        const struct drm_framebuffer_funcs *funcs)
+{
+       int ret;
+
+       ret = drm_mode_object_get(dev, &fb->base, DRM_MODE_OBJECT_FB);
+       if (ret) {
+               return ret;
+       }
+
+       fb->dev = dev;
+       fb->funcs = funcs;
+       dev->mode_config.num_fb++;
+       list_add(&fb->head, &dev->mode_config.fb_list);
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_framebuffer_init);
+
+/**
+ * drm_framebuffer_cleanup - remove a framebuffer object
+ * @fb: framebuffer to remove
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Scans all the CRTCs in @dev's mode_config.  If they're using @fb, removes
+ * it, setting it to NULL.
+ */
+void drm_framebuffer_cleanup(struct drm_framebuffer *fb)
+{
+       struct drm_device *dev = fb->dev;
+       struct drm_crtc *crtc;
+
+       /* remove from any CRTC */
+       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+               if (crtc->fb == fb)
+                       crtc->fb = NULL;
+       }
+
+       drm_mode_object_put(dev, &fb->base);
+       list_del(&fb->head);
+       dev->mode_config.num_fb--;
+}
+EXPORT_SYMBOL(drm_framebuffer_cleanup);
+
+/**
+ * drm_crtc_init - Initialise a new CRTC object
+ * @dev: DRM device
+ * @crtc: CRTC object to init
+ * @funcs: callbacks for the new CRTC
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Inits a new object created as base part of an driver crtc object.
+ */
+void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
+                  const struct drm_crtc_funcs *funcs)
+{
+       crtc->dev = dev;
+       crtc->funcs = funcs;
+
+       mutex_lock(&dev->mode_config.mutex);
+       drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC);
+
+       list_add_tail(&crtc->head, &dev->mode_config.crtc_list);
+       dev->mode_config.num_crtc++;
+       mutex_unlock(&dev->mode_config.mutex);
+}
+EXPORT_SYMBOL(drm_crtc_init);
+
+/**
+ * drm_crtc_cleanup - Cleans up the core crtc usage.
+ * @crtc: CRTC to cleanup
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Cleanup @crtc. Removes from drm modesetting space
+ * does NOT free object, caller does that.
+ */
+void drm_crtc_cleanup(struct drm_crtc *crtc)
+{
+       struct drm_device *dev = crtc->dev;
+
+       if (crtc->gamma_store) {
+               kfree(crtc->gamma_store);
+               crtc->gamma_store = NULL;
+       }
+
+       drm_mode_object_put(dev, &crtc->base);
+       list_del(&crtc->head);
+       dev->mode_config.num_crtc--;
+}
+EXPORT_SYMBOL(drm_crtc_cleanup);
+
+/**
+ * drm_mode_probed_add - add a mode to a connector's probed mode list
+ * @connector: connector the new mode
+ * @mode: mode data
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Add @mode to @connector's mode list for later use.
+ */
+void drm_mode_probed_add(struct drm_connector *connector,
+                        struct drm_display_mode *mode)
+{
+       list_add(&mode->head, &connector->probed_modes);
+}
+EXPORT_SYMBOL(drm_mode_probed_add);
+
+/**
+ * drm_mode_remove - remove and free a mode
+ * @connector: connector list to modify
+ * @mode: mode to remove
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Remove @mode from @connector's mode list, then free it.
+ */
+void drm_mode_remove(struct drm_connector *connector,
+                    struct drm_display_mode *mode)
+{
+       list_del(&mode->head);
+       kfree(mode);
+}
+EXPORT_SYMBOL(drm_mode_remove);
+
+/**
+ * drm_connector_init - Init a preallocated connector
+ * @dev: DRM device
+ * @connector: the connector to init
+ * @funcs: callbacks for this connector
+ * @name: user visible name of the connector
+ *
+ * LOCKING:
+ * Caller must hold @dev's mode_config lock.
+ *
+ * Initialises a preallocated connector. Connectors should be
+ * subclassed as part of driver connector objects.
+ */
+void drm_connector_init(struct drm_device *dev,
+                    struct drm_connector *connector,
+                    const struct drm_connector_funcs *funcs,
+                    int connector_type)
+{
+       mutex_lock(&dev->mode_config.mutex);
+
+       connector->dev = dev;
+       connector->funcs = funcs;
+       drm_mode_object_get(dev, &connector->base, DRM_MODE_OBJECT_CONNECTOR);
+       connector->connector_type = connector_type;
+       connector->connector_type_id =
+               ++drm_connector_enum_list[connector_type].count; /* TODO */
+       INIT_LIST_HEAD(&connector->user_modes);
+       INIT_LIST_HEAD(&connector->probed_modes);
+       INIT_LIST_HEAD(&connector->modes);
+       connector->edid_blob_ptr = NULL;
+
+       list_add_tail(&connector->head, &dev->mode_config.connector_list);
+       dev->mode_config.num_connector++;
+
+       drm_connector_attach_property(connector,
+                                     dev->mode_config.edid_property, 0);
+
+       drm_connector_attach_property(connector,
+                                     dev->mode_config.dpms_property, 0);
+
+       mutex_unlock(&dev->mode_config.mutex);
+}
+EXPORT_SYMBOL(drm_connector_init);
+
+/**
+ * drm_connector_cleanup - cleans up an initialised connector
+ * @connector: connector to cleanup
+ *
+ * LOCKING:
+ * Caller must hold @dev's mode_config lock.
+ *
+ * Cleans up the connector but doesn't free the object.
+ */
+void drm_connector_cleanup(struct drm_connector *connector)
+{
+       struct drm_device *dev = connector->dev;
+       struct drm_display_mode *mode, *t;
+
+       list_for_each_entry_safe(mode, t, &connector->probed_modes, head)
+               drm_mode_remove(connector, mode);
+
+       list_for_each_entry_safe(mode, t, &connector->modes, head)
+               drm_mode_remove(connector, mode);
+
+       list_for_each_entry_safe(mode, t, &connector->user_modes, head)
+               drm_mode_remove(connector, mode);
+
+       mutex_lock(&dev->mode_config.mutex);
+       drm_mode_object_put(dev, &connector->base);
+       list_del(&connector->head);
+       mutex_unlock(&dev->mode_config.mutex);
+}
+EXPORT_SYMBOL(drm_connector_cleanup);
+
+void drm_encoder_init(struct drm_device *dev,
+                     struct drm_encoder *encoder,
+                     const struct drm_encoder_funcs *funcs,
+                     int encoder_type)
+{
+       mutex_lock(&dev->mode_config.mutex);
+
+       encoder->dev = dev;
+
+       drm_mode_object_get(dev, &encoder->base, DRM_MODE_OBJECT_ENCODER);
+       encoder->encoder_type = encoder_type;
+       encoder->funcs = funcs;
+
+       list_add_tail(&encoder->head, &dev->mode_config.encoder_list);
+       dev->mode_config.num_encoder++;
+
+       mutex_unlock(&dev->mode_config.mutex);
+}
+EXPORT_SYMBOL(drm_encoder_init);
+
+void drm_encoder_cleanup(struct drm_encoder *encoder)
+{
+       struct drm_device *dev = encoder->dev;
+       mutex_lock(&dev->mode_config.mutex);
+       drm_mode_object_put(dev, &encoder->base);
+       list_del(&encoder->head);
+       mutex_unlock(&dev->mode_config.mutex);
+}
+EXPORT_SYMBOL(drm_encoder_cleanup);
+
+/**
+ * drm_mode_create - create a new display mode
+ * @dev: DRM device
+ *
+ * LOCKING:
+ * Caller must hold DRM mode_config lock.
+ *
+ * Create a new drm_display_mode, give it an ID, and return it.
+ *
+ * RETURNS:
+ * Pointer to new mode on success, NULL on error.
+ */
+struct drm_display_mode *drm_mode_create(struct drm_device *dev)
+{
+       struct drm_display_mode *nmode;
+
+       nmode = kzalloc(sizeof(struct drm_display_mode), GFP_KERNEL);
+       if (!nmode)
+               return NULL;
+
+       drm_mode_object_get(dev, &nmode->base, DRM_MODE_OBJECT_MODE);
+       return nmode;
+}
+EXPORT_SYMBOL(drm_mode_create);
+
+/**
+ * drm_mode_destroy - remove a mode
+ * @dev: DRM device
+ * @mode: mode to remove
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Free @mode's unique identifier, then free it.
+ */
+void drm_mode_destroy(struct drm_device *dev, struct drm_display_mode *mode)
+{
+       drm_mode_object_put(dev, &mode->base);
+
+       kfree(mode);
+}
+EXPORT_SYMBOL(drm_mode_destroy);
+
+static int drm_mode_create_standard_connector_properties(struct drm_device *dev)
+{
+       struct drm_property *edid;
+       struct drm_property *dpms;
+       int i;
+
+       /*
+        * Standard properties (apply to all connectors)
+        */
+       edid = drm_property_create(dev, DRM_MODE_PROP_BLOB |
+                                  DRM_MODE_PROP_IMMUTABLE,
+                                  "EDID", 0);
+       dev->mode_config.edid_property = edid;
+
+       dpms = drm_property_create(dev, DRM_MODE_PROP_ENUM,
+                                  "DPMS", ARRAY_SIZE(drm_dpms_enum_list));
+       for (i = 0; i < ARRAY_SIZE(drm_dpms_enum_list); i++)
+               drm_property_add_enum(dpms, i, drm_dpms_enum_list[i].type,
+                                     drm_dpms_enum_list[i].name);
+       dev->mode_config.dpms_property = dpms;
+
+       return 0;
+}
+
+/**
+ * drm_mode_create_dvi_i_properties - create DVI-I specific connector properties
+ * @dev: DRM device
+ *
+ * Called by a driver the first time a DVI-I connector is made.
+ */
+int drm_mode_create_dvi_i_properties(struct drm_device *dev)
+{
+       struct drm_property *dvi_i_selector;
+       struct drm_property *dvi_i_subconnector;
+       int i;
+
+       if (dev->mode_config.dvi_i_select_subconnector_property)
+               return 0;
+
+       dvi_i_selector =
+               drm_property_create(dev, DRM_MODE_PROP_ENUM,
+                                   "select subconnector",
+                                   ARRAY_SIZE(drm_dvi_i_select_enum_list));
+       for (i = 0; i < ARRAY_SIZE(drm_dvi_i_select_enum_list); i++)
+               drm_property_add_enum(dvi_i_selector, i,
+                                     drm_dvi_i_select_enum_list[i].type,
+                                     drm_dvi_i_select_enum_list[i].name);
+       dev->mode_config.dvi_i_select_subconnector_property = dvi_i_selector;
+
+       dvi_i_subconnector =
+               drm_property_create(dev, DRM_MODE_PROP_ENUM |
+                                   DRM_MODE_PROP_IMMUTABLE,
+                                   "subconnector",
+                                   ARRAY_SIZE(drm_dvi_i_subconnector_enum_list));
+       for (i = 0; i < ARRAY_SIZE(drm_dvi_i_subconnector_enum_list); i++)
+               drm_property_add_enum(dvi_i_subconnector, i,
+                                     drm_dvi_i_subconnector_enum_list[i].type,
+                                     drm_dvi_i_subconnector_enum_list[i].name);
+       dev->mode_config.dvi_i_subconnector_property = dvi_i_subconnector;
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_mode_create_dvi_i_properties);
+
+/**
+ * drm_create_tv_properties - create TV specific connector properties
+ * @dev: DRM device
+ * @num_modes: number of different TV formats (modes) supported
+ * @modes: array of pointers to strings containing name of each format
+ *
+ * Called by a driver's TV initialization routine, this function creates
+ * the TV specific connector properties for a given device.  Caller is
+ * responsible for allocating a list of format names and passing them to
+ * this routine.
+ */
+int drm_mode_create_tv_properties(struct drm_device *dev, int num_modes,
+                                 char *modes[])
+{
+       struct drm_property *tv_selector;
+       struct drm_property *tv_subconnector;
+       int i;
+
+       if (dev->mode_config.tv_select_subconnector_property)
+               return 0;
+
+       /*
+        * Basic connector properties
+        */
+       tv_selector = drm_property_create(dev, DRM_MODE_PROP_ENUM,
+                                         "select subconnector",
+                                         ARRAY_SIZE(drm_tv_select_enum_list));
+       for (i = 0; i < ARRAY_SIZE(drm_tv_select_enum_list); i++)
+               drm_property_add_enum(tv_selector, i,
+                                     drm_tv_select_enum_list[i].type,
+                                     drm_tv_select_enum_list[i].name);
+       dev->mode_config.tv_select_subconnector_property = tv_selector;
+
+       tv_subconnector =
+               drm_property_create(dev, DRM_MODE_PROP_ENUM |
+                                   DRM_MODE_PROP_IMMUTABLE, "subconnector",
+                                   ARRAY_SIZE(drm_tv_subconnector_enum_list));
+       for (i = 0; i < ARRAY_SIZE(drm_tv_subconnector_enum_list); i++)
+               drm_property_add_enum(tv_subconnector, i,
+                                     drm_tv_subconnector_enum_list[i].type,
+                                     drm_tv_subconnector_enum_list[i].name);
+       dev->mode_config.tv_subconnector_property = tv_subconnector;
+
+       /*
+        * Other, TV specific properties: margins & TV modes.
+        */
+       dev->mode_config.tv_left_margin_property =
+               drm_property_create(dev, DRM_MODE_PROP_RANGE,
+                                   "left margin", 2);
+       dev->mode_config.tv_left_margin_property->values[0] = 0;
+       dev->mode_config.tv_left_margin_property->values[1] = 100;
+
+       dev->mode_config.tv_right_margin_property =
+               drm_property_create(dev, DRM_MODE_PROP_RANGE,
+                                   "right margin", 2);
+       dev->mode_config.tv_right_margin_property->values[0] = 0;
+       dev->mode_config.tv_right_margin_property->values[1] = 100;
+
+       dev->mode_config.tv_top_margin_property =
+               drm_property_create(dev, DRM_MODE_PROP_RANGE,
+                                   "top margin", 2);
+       dev->mode_config.tv_top_margin_property->values[0] = 0;
+       dev->mode_config.tv_top_margin_property->values[1] = 100;
+
+       dev->mode_config.tv_bottom_margin_property =
+               drm_property_create(dev, DRM_MODE_PROP_RANGE,
+                                   "bottom margin", 2);
+       dev->mode_config.tv_bottom_margin_property->values[0] = 0;
+       dev->mode_config.tv_bottom_margin_property->values[1] = 100;
+
+       dev->mode_config.tv_mode_property =
+               drm_property_create(dev, DRM_MODE_PROP_ENUM,
+                                   "mode", num_modes);
+       for (i = 0; i < num_modes; i++)
+               drm_property_add_enum(dev->mode_config.tv_mode_property, i,
+                                     i, modes[i]);
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_mode_create_tv_properties);
+
+/**
+ * drm_mode_create_scaling_mode_property - create scaling mode property
+ * @dev: DRM device
+ *
+ * Called by a driver the first time it's needed, must be attached to desired
+ * connectors.
+ */
+int drm_mode_create_scaling_mode_property(struct drm_device *dev)
+{
+       struct drm_property *scaling_mode;
+       int i;
+
+       if (dev->mode_config.scaling_mode_property)
+               return 0;
+
+       scaling_mode =
+               drm_property_create(dev, DRM_MODE_PROP_ENUM, "scaling mode",
+                                   ARRAY_SIZE(drm_scaling_mode_enum_list));
+       for (i = 0; i < ARRAY_SIZE(drm_scaling_mode_enum_list); i++)
+               drm_property_add_enum(scaling_mode, i,
+                                     drm_scaling_mode_enum_list[i].type,
+                                     drm_scaling_mode_enum_list[i].name);
+
+       dev->mode_config.scaling_mode_property = scaling_mode;
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_mode_create_scaling_mode_property);
+
+/**
+ * drm_mode_create_dithering_property - create dithering property
+ * @dev: DRM device
+ *
+ * Called by a driver the first time it's needed, must be attached to desired
+ * connectors.
+ */
+int drm_mode_create_dithering_property(struct drm_device *dev)
+{
+       struct drm_property *dithering_mode;
+       int i;
+
+       if (dev->mode_config.dithering_mode_property)
+               return 0;
+
+       dithering_mode =
+               drm_property_create(dev, DRM_MODE_PROP_ENUM, "dithering",
+                                   ARRAY_SIZE(drm_dithering_mode_enum_list));
+       for (i = 0; i < ARRAY_SIZE(drm_dithering_mode_enum_list); i++)
+               drm_property_add_enum(dithering_mode, i,
+                                     drm_dithering_mode_enum_list[i].type,
+                                     drm_dithering_mode_enum_list[i].name);
+       dev->mode_config.dithering_mode_property = dithering_mode;
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_mode_create_dithering_property);
+
+/**
+ * drm_mode_config_init - initialize DRM mode_configuration structure
+ * @dev: DRM device
+ *
+ * LOCKING:
+ * None, should happen single threaded at init time.
+ *
+ * Initialize @dev's mode_config structure, used for tracking the graphics
+ * configuration of @dev.
+ */
+void drm_mode_config_init(struct drm_device *dev)
+{
+       mutex_init(&dev->mode_config.mutex);
+       INIT_LIST_HEAD(&dev->mode_config.fb_list);
+       INIT_LIST_HEAD(&dev->mode_config.fb_kernel_list);
+       INIT_LIST_HEAD(&dev->mode_config.crtc_list);
+       INIT_LIST_HEAD(&dev->mode_config.connector_list);
+       INIT_LIST_HEAD(&dev->mode_config.encoder_list);
+       INIT_LIST_HEAD(&dev->mode_config.property_list);
+       INIT_LIST_HEAD(&dev->mode_config.property_blob_list);
+       idr_init(&dev->mode_config.crtc_idr);
+
+       mutex_lock(&dev->mode_config.mutex);
+       drm_mode_create_standard_connector_properties(dev);
+       mutex_unlock(&dev->mode_config.mutex);
+
+       /* Just to be sure */
+       dev->mode_config.num_fb = 0;
+       dev->mode_config.num_connector = 0;
+       dev->mode_config.num_crtc = 0;
+       dev->mode_config.num_encoder = 0;
+}
+EXPORT_SYMBOL(drm_mode_config_init);
+
+int drm_mode_group_init(struct drm_device *dev, struct drm_mode_group *group)
+{
+       uint32_t total_objects = 0;
+
+       total_objects += dev->mode_config.num_crtc;
+       total_objects += dev->mode_config.num_connector;
+       total_objects += dev->mode_config.num_encoder;
+
+       if (total_objects == 0)
+               return -EINVAL;
+
+       group->id_list = kzalloc(total_objects * sizeof(uint32_t), GFP_KERNEL);
+       if (!group->id_list)
+               return -ENOMEM;
+
+       group->num_crtcs = 0;
+       group->num_connectors = 0;
+       group->num_encoders = 0;
+       return 0;
+}
+
+int drm_mode_group_init_legacy_group(struct drm_device *dev,
+                                    struct drm_mode_group *group)
+{
+       struct drm_crtc *crtc;
+       struct drm_encoder *encoder;
+       struct drm_connector *connector;
+       int ret;
+
+       if ((ret = drm_mode_group_init(dev, group)))
+               return ret;
+
+       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
+               group->id_list[group->num_crtcs++] = crtc->base.id;
+
+       list_for_each_entry(encoder, &dev->mode_config.encoder_list, head)
+               group->id_list[group->num_crtcs + group->num_encoders++] =
+               encoder->base.id;
+
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head)
+               group->id_list[group->num_crtcs + group->num_encoders +
+                              group->num_connectors++] = connector->base.id;
+
+       return 0;
+}
+
+/**
+ * drm_mode_config_cleanup - free up DRM mode_config info
+ * @dev: DRM device
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Free up all the connectors and CRTCs associated with this DRM device, then
+ * free up the framebuffers and associated buffer objects.
+ *
+ * FIXME: cleanup any dangling user buffer objects too
+ */
+void drm_mode_config_cleanup(struct drm_device *dev)
+{
+       struct drm_connector *connector, *ot;
+       struct drm_crtc *crtc, *ct;
+       struct drm_encoder *encoder, *enct;
+       struct drm_framebuffer *fb, *fbt;
+       struct drm_property *property, *pt;
+
+       list_for_each_entry_safe(encoder, enct, &dev->mode_config.encoder_list,
+                                head) {
+               encoder->funcs->destroy(encoder);
+       }
+
+       list_for_each_entry_safe(connector, ot,
+                                &dev->mode_config.connector_list, head) {
+               connector->funcs->destroy(connector);
+       }
+
+       list_for_each_entry_safe(property, pt, &dev->mode_config.property_list,
+                                head) {
+               drm_property_destroy(dev, property);
+       }
+
+       list_for_each_entry_safe(fb, fbt, &dev->mode_config.fb_list, head) {
+               fb->funcs->destroy(fb);
+       }
+
+       list_for_each_entry_safe(crtc, ct, &dev->mode_config.crtc_list, head) {
+               crtc->funcs->destroy(crtc);
+       }
+
+}
+EXPORT_SYMBOL(drm_mode_config_cleanup);
+
+/**
+ * drm_crtc_convert_to_umode - convert a drm_display_mode into a modeinfo
+ * @out: drm_mode_modeinfo struct to return to the user
+ * @in: drm_display_mode to use
+ *
+ * LOCKING:
+ * None.
+ *
+ * Convert a drm_display_mode into a drm_mode_modeinfo structure to return to
+ * the user.
+ */
+void drm_crtc_convert_to_umode(struct drm_mode_modeinfo *out,
+                              struct drm_display_mode *in)
+{
+       out->clock = in->clock;
+       out->hdisplay = in->hdisplay;
+       out->hsync_start = in->hsync_start;
+       out->hsync_end = in->hsync_end;
+       out->htotal = in->htotal;
+       out->hskew = in->hskew;
+       out->vdisplay = in->vdisplay;
+       out->vsync_start = in->vsync_start;
+       out->vsync_end = in->vsync_end;
+       out->vtotal = in->vtotal;
+       out->vscan = in->vscan;
+       out->vrefresh = in->vrefresh;
+       out->flags = in->flags;
+       out->type = in->type;
+       strncpy(out->name, in->name, DRM_DISPLAY_MODE_LEN);
+       out->name[DRM_DISPLAY_MODE_LEN-1] = 0;
+}
+
+/**
+ * drm_crtc_convert_to_umode - convert a modeinfo into a drm_display_mode
+ * @out: drm_display_mode to return to the user
+ * @in: drm_mode_modeinfo to use
+ *
+ * LOCKING:
+ * None.
+ *
+ * Convert a drm_mode_modeinfo into a drm_display_mode structure to return to
+ * the caller.
+ */
+void drm_crtc_convert_umode(struct drm_display_mode *out,
+                           struct drm_mode_modeinfo *in)
+{
+       out->clock = in->clock;
+       out->hdisplay = in->hdisplay;
+       out->hsync_start = in->hsync_start;
+       out->hsync_end = in->hsync_end;
+       out->htotal = in->htotal;
+       out->hskew = in->hskew;
+       out->vdisplay = in->vdisplay;
+       out->vsync_start = in->vsync_start;
+       out->vsync_end = in->vsync_end;
+       out->vtotal = in->vtotal;
+       out->vscan = in->vscan;
+       out->vrefresh = in->vrefresh;
+       out->flags = in->flags;
+       out->type = in->type;
+       strncpy(out->name, in->name, DRM_DISPLAY_MODE_LEN);
+       out->name[DRM_DISPLAY_MODE_LEN-1] = 0;
+}
+
+/**
+ * drm_mode_getresources - get graphics configuration
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * LOCKING:
+ * Takes mode config lock.
+ *
+ * Construct a set of configuration description structures and return
+ * them to the user, including CRTC, connector and framebuffer configuration.
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_getresources(struct drm_device *dev, void *data,
+                         struct drm_file *file_priv)
+{
+       struct drm_mode_card_res *card_res = data;
+       struct list_head *lh;
+       struct drm_framebuffer *fb;
+       struct drm_connector *connector;
+       struct drm_crtc *crtc;
+       struct drm_encoder *encoder;
+       int ret = 0;
+       int connector_count = 0;
+       int crtc_count = 0;
+       int fb_count = 0;
+       int encoder_count = 0;
+       int copied = 0, i;
+       uint32_t __user *fb_id;
+       uint32_t __user *crtc_id;
+       uint32_t __user *connector_id;
+       uint32_t __user *encoder_id;
+       struct drm_mode_group *mode_group;
+
+       mutex_lock(&dev->mode_config.mutex);
+
+       /*
+        * For the non-control nodes we need to limit the list of resources
+        * by IDs in the group list for this node
+        */
+       list_for_each(lh, &file_priv->fbs)
+               fb_count++;
+
+       mode_group = &file_priv->master->minor->mode_group;
+       if (file_priv->master->minor->type == DRM_MINOR_CONTROL) {
+
+               list_for_each(lh, &dev->mode_config.crtc_list)
+                       crtc_count++;
+
+               list_for_each(lh, &dev->mode_config.connector_list)
+                       connector_count++;
+
+               list_for_each(lh, &dev->mode_config.encoder_list)
+                       encoder_count++;
+       } else {
+
+               crtc_count = mode_group->num_crtcs;
+               connector_count = mode_group->num_connectors;
+               encoder_count = mode_group->num_encoders;
+       }
+
+       card_res->max_height = dev->mode_config.max_height;
+       card_res->min_height = dev->mode_config.min_height;
+       card_res->max_width = dev->mode_config.max_width;
+       card_res->min_width = dev->mode_config.min_width;
+
+       /* handle this in 4 parts */
+       /* FBs */
+       if (card_res->count_fbs >= fb_count) {
+               copied = 0;
+               fb_id = (uint32_t __user *)(unsigned long)card_res->fb_id_ptr;
+               list_for_each_entry(fb, &file_priv->fbs, head) {
+                       if (put_user(fb->base.id, fb_id + copied)) {
+                               ret = -EFAULT;
+                               goto out;
+                       }
+                       copied++;
+               }
+       }
+       card_res->count_fbs = fb_count;
+
+       /* CRTCs */
+       if (card_res->count_crtcs >= crtc_count) {
+               copied = 0;
+               crtc_id = (uint32_t __user *)(unsigned long)card_res->crtc_id_ptr;
+               if (file_priv->master->minor->type == DRM_MINOR_CONTROL) {
+                       list_for_each_entry(crtc, &dev->mode_config.crtc_list,
+                                           head) {
+                               DRM_DEBUG("CRTC ID is %d\n", crtc->base.id);
+                               if (put_user(crtc->base.id, crtc_id + copied)) {
+                                       ret = -EFAULT;
+                                       goto out;
+                               }
+                               copied++;
+                       }
+               } else {
+                       for (i = 0; i < mode_group->num_crtcs; i++) {
+                               if (put_user(mode_group->id_list[i],
+                                            crtc_id + copied)) {
+                                       ret = -EFAULT;
+                                       goto out;
+                               }
+                               copied++;
+                       }
+               }
+       }
+       card_res->count_crtcs = crtc_count;
+
+       /* Encoders */
+       if (card_res->count_encoders >= encoder_count) {
+               copied = 0;
+               encoder_id = (uint32_t __user *)(unsigned long)card_res->encoder_id_ptr;
+               if (file_priv->master->minor->type == DRM_MINOR_CONTROL) {
+                       list_for_each_entry(encoder,
+                                           &dev->mode_config.encoder_list,
+                                           head) {
+                               DRM_DEBUG("ENCODER ID is %d\n",
+                                         encoder->base.id);
+                               if (put_user(encoder->base.id, encoder_id +
+                                            copied)) {
+                                       ret = -EFAULT;
+                                       goto out;
+                               }
+                               copied++;
+                       }
+               } else {
+                       for (i = mode_group->num_crtcs; i < mode_group->num_crtcs + mode_group->num_encoders; i++) {
+                               if (put_user(mode_group->id_list[i],
+                                            encoder_id + copied)) {
+                                       ret = -EFAULT;
+                                       goto out;
+                               }
+                               copied++;
+                       }
+
+               }
+       }
+       card_res->count_encoders = encoder_count;
+
+       /* Connectors */
+       if (card_res->count_connectors >= connector_count) {
+               copied = 0;
+               connector_id = (uint32_t __user *)(unsigned long)card_res->connector_id_ptr;
+               if (file_priv->master->minor->type == DRM_MINOR_CONTROL) {
+                       list_for_each_entry(connector,
+                                           &dev->mode_config.connector_list,
+                                           head) {
+                               DRM_DEBUG("CONNECTOR ID is %d\n",
+                                         connector->base.id);
+                               if (put_user(connector->base.id,
+                                            connector_id + copied)) {
+                                       ret = -EFAULT;
+                                       goto out;
+                               }
+                               copied++;
+                       }
+               } else {
+                       int start = mode_group->num_crtcs +
+                               mode_group->num_encoders;
+                       for (i = start; i < start + mode_group->num_connectors; i++) {
+                               if (put_user(mode_group->id_list[i],
+                                            connector_id + copied)) {
+                                       ret = -EFAULT;
+                                       goto out;
+                               }
+                               copied++;
+                       }
+               }
+       }
+       card_res->count_connectors = connector_count;
+
+       DRM_DEBUG("Counted %d %d %d\n", card_res->count_crtcs,
+                 card_res->count_connectors, card_res->count_encoders);
+
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+/**
+ * drm_mode_getcrtc - get CRTC configuration
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * LOCKING:
+ * Caller? (FIXME)
+ *
+ * Construct a CRTC configuration structure to return to the user.
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_getcrtc(struct drm_device *dev,
+                    void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_crtc *crtc_resp = data;
+       struct drm_crtc *crtc;
+       struct drm_mode_object *obj;
+       int ret = 0;
+
+       mutex_lock(&dev->mode_config.mutex);
+
+       obj = drm_mode_object_find(dev, crtc_resp->crtc_id,
+                                  DRM_MODE_OBJECT_CRTC);
+       if (!obj) {
+               ret = -EINVAL;
+               goto out;
+       }
+       crtc = obj_to_crtc(obj);
+
+       crtc_resp->x = crtc->x;
+       crtc_resp->y = crtc->y;
+       crtc_resp->gamma_size = crtc->gamma_size;
+       if (crtc->fb)
+               crtc_resp->fb_id = crtc->fb->base.id;
+       else
+               crtc_resp->fb_id = 0;
+
+       if (crtc->enabled) {
+
+               drm_crtc_convert_to_umode(&crtc_resp->mode, &crtc->mode);
+               crtc_resp->mode_valid = 1;
+
+       } else {
+               crtc_resp->mode_valid = 0;
+       }
+
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+/**
+ * drm_mode_getconnector - get connector configuration
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * LOCKING:
+ * Caller? (FIXME)
+ *
+ * Construct a connector configuration structure to return to the user.
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_getconnector(struct drm_device *dev, void *data,
+                         struct drm_file *file_priv)
+{
+       struct drm_mode_get_connector *out_resp = data;
+       struct drm_mode_object *obj;
+       struct drm_connector *connector;
+       struct drm_display_mode *mode;
+       int mode_count = 0;
+       int props_count = 0;
+       int encoders_count = 0;
+       int ret = 0;
+       int copied = 0;
+       int i;
+       struct drm_mode_modeinfo u_mode;
+       struct drm_mode_modeinfo __user *mode_ptr;
+       uint32_t __user *prop_ptr;
+       uint64_t __user *prop_values;
+       uint32_t __user *encoder_ptr;
+
+       memset(&u_mode, 0, sizeof(struct drm_mode_modeinfo));
+
+       DRM_DEBUG("connector id %d:\n", out_resp->connector_id);
+
+       mutex_lock(&dev->mode_config.mutex);
+
+       obj = drm_mode_object_find(dev, out_resp->connector_id,
+                                  DRM_MODE_OBJECT_CONNECTOR);
+       if (!obj) {
+               ret = -EINVAL;
+               goto out;
+       }
+       connector = obj_to_connector(obj);
+
+       for (i = 0; i < DRM_CONNECTOR_MAX_PROPERTY; i++) {
+               if (connector->property_ids[i] != 0) {
+                       props_count++;
+               }
+       }
+
+       for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
+               if (connector->encoder_ids[i] != 0) {
+                       encoders_count++;
+               }
+       }
+
+       if (out_resp->count_modes == 0) {
+               connector->funcs->fill_modes(connector,
+                                            dev->mode_config.max_width,
+                                            dev->mode_config.max_height);
+       }
+
+       /* delayed so we get modes regardless of pre-fill_modes state */
+       list_for_each_entry(mode, &connector->modes, head)
+               mode_count++;
+
+       out_resp->connector_id = connector->base.id;
+       out_resp->connector_type = connector->connector_type;
+       out_resp->connector_type_id = connector->connector_type_id;
+       out_resp->mm_width = connector->display_info.width_mm;
+       out_resp->mm_height = connector->display_info.height_mm;
+       out_resp->subpixel = connector->display_info.subpixel_order;
+       out_resp->connection = connector->status;
+       if (connector->encoder)
+               out_resp->encoder_id = connector->encoder->base.id;
+       else
+               out_resp->encoder_id = 0;
+
+       /*
+        * This ioctl is called twice, once to determine how much space is
+        * needed, and the 2nd time to fill it.
+        */
+       if ((out_resp->count_modes >= mode_count) && mode_count) {
+               copied = 0;
+               mode_ptr = (struct drm_mode_modeinfo *)(unsigned long)out_resp->modes_ptr;
+               list_for_each_entry(mode, &connector->modes, head) {
+                       drm_crtc_convert_to_umode(&u_mode, mode);
+                       if (copy_to_user(mode_ptr + copied,
+                                        &u_mode, sizeof(u_mode))) {
+                               ret = -EFAULT;
+                               goto out;
+                       }
+                       copied++;
+               }
+       }
+       out_resp->count_modes = mode_count;
+
+       if ((out_resp->count_props >= props_count) && props_count) {
+               copied = 0;
+               prop_ptr = (uint32_t *)(unsigned long)(out_resp->props_ptr);
+               prop_values = (uint64_t *)(unsigned long)(out_resp->prop_values_ptr);
+               for (i = 0; i < DRM_CONNECTOR_MAX_PROPERTY; i++) {
+                       if (connector->property_ids[i] != 0) {
+                               if (put_user(connector->property_ids[i],
+                                            prop_ptr + copied)) {
+                                       ret = -EFAULT;
+                                       goto out;
+                               }
+
+                               if (put_user(connector->property_values[i],
+                                            prop_values + copied)) {
+                                       ret = -EFAULT;
+                                       goto out;
+                               }
+                               copied++;
+                       }
+               }
+       }
+       out_resp->count_props = props_count;
+
+       if ((out_resp->count_encoders >= encoders_count) && encoders_count) {
+               copied = 0;
+               encoder_ptr = (uint32_t *)(unsigned long)(out_resp->encoders_ptr);
+               for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
+                       if (connector->encoder_ids[i] != 0) {
+                               if (put_user(connector->encoder_ids[i],
+                                            encoder_ptr + copied)) {
+                                       ret = -EFAULT;
+                                       goto out;
+                               }
+                               copied++;
+                       }
+               }
+       }
+       out_resp->count_encoders = encoders_count;
+
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+int drm_mode_getencoder(struct drm_device *dev, void *data,
+                       struct drm_file *file_priv)
+{
+       struct drm_mode_get_encoder *enc_resp = data;
+       struct drm_mode_object *obj;
+       struct drm_encoder *encoder;
+       int ret = 0;
+
+       mutex_lock(&dev->mode_config.mutex);
+       obj = drm_mode_object_find(dev, enc_resp->encoder_id,
+                                  DRM_MODE_OBJECT_ENCODER);
+       if (!obj) {
+               ret = -EINVAL;
+               goto out;
+       }
+       encoder = obj_to_encoder(obj);
+
+       if (encoder->crtc)
+               enc_resp->crtc_id = encoder->crtc->base.id;
+       else
+               enc_resp->crtc_id = 0;
+       enc_resp->encoder_type = encoder->encoder_type;
+       enc_resp->encoder_id = encoder->base.id;
+       enc_resp->possible_crtcs = encoder->possible_crtcs;
+       enc_resp->possible_clones = encoder->possible_clones;
+
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+/**
+ * drm_mode_setcrtc - set CRTC configuration
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * LOCKING:
+ * Caller? (FIXME)
+ *
+ * Build a new CRTC configuration based on user request.
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_setcrtc(struct drm_device *dev, void *data,
+                    struct drm_file *file_priv)
+{
+       struct drm_mode_config *config = &dev->mode_config;
+       struct drm_mode_crtc *crtc_req = data;
+       struct drm_mode_object *obj;
+       struct drm_crtc *crtc, *crtcfb;
+       struct drm_connector **connector_set = NULL, *connector;
+       struct drm_framebuffer *fb = NULL;
+       struct drm_display_mode *mode = NULL;
+       struct drm_mode_set set;
+       uint32_t __user *set_connectors_ptr;
+       int ret = 0;
+       int i;
+
+       mutex_lock(&dev->mode_config.mutex);
+       obj = drm_mode_object_find(dev, crtc_req->crtc_id,
+                                  DRM_MODE_OBJECT_CRTC);
+       if (!obj) {
+               DRM_DEBUG("Unknown CRTC ID %d\n", crtc_req->crtc_id);
+               ret = -EINVAL;
+               goto out;
+       }
+       crtc = obj_to_crtc(obj);
+
+       if (crtc_req->mode_valid) {
+               /* If we have a mode we need a framebuffer. */
+               /* If we pass -1, set the mode with the currently bound fb */
+               if (crtc_req->fb_id == -1) {
+                       list_for_each_entry(crtcfb,
+                                           &dev->mode_config.crtc_list, head) {
+                               if (crtcfb == crtc) {
+                                       DRM_DEBUG("Using current fb for setmode\n");
+                                       fb = crtc->fb;
+                               }
+                       }
+               } else {
+                       obj = drm_mode_object_find(dev, crtc_req->fb_id,
+                                                  DRM_MODE_OBJECT_FB);
+                       if (!obj) {
+                               DRM_DEBUG("Unknown FB ID%d\n", crtc_req->fb_id);
+                               ret = -EINVAL;
+                               goto out;
+                       }
+                       fb = obj_to_fb(obj);
+               }
+
+               mode = drm_mode_create(dev);
+               drm_crtc_convert_umode(mode, &crtc_req->mode);
+               drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
+       }
+
+       if (crtc_req->count_connectors == 0 && mode) {
+               DRM_DEBUG("Count connectors is 0 but mode set\n");
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (crtc_req->count_connectors > 0 && !mode && !fb) {
+               DRM_DEBUG("Count connectors is %d but no mode or fb set\n",
+                         crtc_req->count_connectors);
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (crtc_req->count_connectors > 0) {
+               u32 out_id;
+
+               /* Avoid unbounded kernel memory allocation */
+               if (crtc_req->count_connectors > config->num_connector) {
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               connector_set = kmalloc(crtc_req->count_connectors *
+                                       sizeof(struct drm_connector *),
+                                       GFP_KERNEL);
+               if (!connector_set) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+
+               for (i = 0; i < crtc_req->count_connectors; i++) {
+                       set_connectors_ptr = (uint32_t *)(unsigned long)crtc_req->set_connectors_ptr;
+                       if (get_user(out_id, &set_connectors_ptr[i])) {
+                               ret = -EFAULT;
+                               goto out;
+                       }
+
+                       obj = drm_mode_object_find(dev, out_id,
+                                                  DRM_MODE_OBJECT_CONNECTOR);
+                       if (!obj) {
+                               DRM_DEBUG("Connector id %d unknown\n", out_id);
+                               ret = -EINVAL;
+                               goto out;
+                       }
+                       connector = obj_to_connector(obj);
+
+                       connector_set[i] = connector;
+               }
+       }
+
+       set.crtc = crtc;
+       set.x = crtc_req->x;
+       set.y = crtc_req->y;
+       set.mode = mode;
+       set.connectors = connector_set;
+       set.num_connectors = crtc_req->count_connectors;
+       set.fb =fb;
+       ret = crtc->funcs->set_config(&set);
+
+out:
+       kfree(connector_set);
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+int drm_mode_cursor_ioctl(struct drm_device *dev,
+                       void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_cursor *req = data;
+       struct drm_mode_object *obj;
+       struct drm_crtc *crtc;
+       int ret = 0;
+
+       DRM_DEBUG("\n");
+
+       if (!req->flags) {
+               DRM_ERROR("no operation set\n");
+               return -EINVAL;
+       }
+
+       mutex_lock(&dev->mode_config.mutex);
+       obj = drm_mode_object_find(dev, req->crtc_id, DRM_MODE_OBJECT_CRTC);
+       if (!obj) {
+               DRM_DEBUG("Unknown CRTC ID %d\n", req->crtc_id);
+               ret = -EINVAL;
+               goto out;
+       }
+       crtc = obj_to_crtc(obj);
+
+       if (req->flags & DRM_MODE_CURSOR_BO) {
+               if (!crtc->funcs->cursor_set) {
+                       DRM_ERROR("crtc does not support cursor\n");
+                       ret = -ENXIO;
+                       goto out;
+               }
+               /* Turns off the cursor if handle is 0 */
+               ret = crtc->funcs->cursor_set(crtc, file_priv, req->handle,
+                                             req->width, req->height);
+       }
+
+       if (req->flags & DRM_MODE_CURSOR_MOVE) {
+               if (crtc->funcs->cursor_move) {
+                       ret = crtc->funcs->cursor_move(crtc, req->x, req->y);
+               } else {
+                       DRM_ERROR("crtc does not support cursor\n");
+                       ret = -EFAULT;
+                       goto out;
+               }
+       }
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+/**
+ * drm_mode_addfb - add an FB to the graphics configuration
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * LOCKING:
+ * Takes mode config lock.
+ *
+ * Add a new FB to the specified CRTC, given a user request.
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_addfb(struct drm_device *dev,
+                  void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_fb_cmd *r = data;
+       struct drm_mode_config *config = &dev->mode_config;
+       struct drm_framebuffer *fb;
+       int ret = 0;
+
+       if ((config->min_width > r->width) || (r->width > config->max_width)) {
+               DRM_ERROR("mode new framebuffer width not within limits\n");
+               return -EINVAL;
+       }
+       if ((config->min_height > r->height) || (r->height > config->max_height)) {
+               DRM_ERROR("mode new framebuffer height not within limits\n");
+               return -EINVAL;
+       }
+
+       mutex_lock(&dev->mode_config.mutex);
+
+       /* TODO check buffer is sufficently large */
+       /* TODO setup destructor callback */
+
+       fb = dev->mode_config.funcs->fb_create(dev, file_priv, r);
+       if (!fb) {
+               DRM_ERROR("could not create framebuffer\n");
+               ret = -EINVAL;
+               goto out;
+       }
+
+       r->fb_id = fb->base.id;
+       list_add(&fb->filp_head, &file_priv->fbs);
+
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+/**
+ * drm_mode_rmfb - remove an FB from the configuration
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * LOCKING:
+ * Takes mode config lock.
+ *
+ * Remove the FB specified by the user.
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_rmfb(struct drm_device *dev,
+                  void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_object *obj;
+       struct drm_framebuffer *fb = NULL;
+       struct drm_framebuffer *fbl = NULL;
+       uint32_t *id = data;
+       int ret = 0;
+       int found = 0;
+
+       mutex_lock(&dev->mode_config.mutex);
+       obj = drm_mode_object_find(dev, *id, DRM_MODE_OBJECT_FB);
+       /* TODO check that we realy get a framebuffer back. */
+       if (!obj) {
+               DRM_ERROR("mode invalid framebuffer id\n");
+               ret = -EINVAL;
+               goto out;
+       }
+       fb = obj_to_fb(obj);
+
+       list_for_each_entry(fbl, &file_priv->fbs, filp_head)
+               if (fb == fbl)
+                       found = 1;
+
+       if (!found) {
+               DRM_ERROR("tried to remove a fb that we didn't own\n");
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /* TODO release all crtc connected to the framebuffer */
+       /* TODO unhock the destructor from the buffer object */
+
+       list_del(&fb->filp_head);
+       fb->funcs->destroy(fb);
+
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+/**
+ * drm_mode_getfb - get FB info
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * LOCKING:
+ * Caller? (FIXME)
+ *
+ * Lookup the FB given its ID and return info about it.
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_getfb(struct drm_device *dev,
+                  void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_fb_cmd *r = data;
+       struct drm_mode_object *obj;
+       struct drm_framebuffer *fb;
+       int ret = 0;
+
+       mutex_lock(&dev->mode_config.mutex);
+       obj = drm_mode_object_find(dev, r->fb_id, DRM_MODE_OBJECT_FB);
+       if (!obj) {
+               DRM_ERROR("invalid framebuffer id\n");
+               ret = -EINVAL;
+               goto out;
+       }
+       fb = obj_to_fb(obj);
+
+       r->height = fb->height;
+       r->width = fb->width;
+       r->depth = fb->depth;
+       r->bpp = fb->bits_per_pixel;
+       r->pitch = fb->pitch;
+       fb->funcs->create_handle(fb, file_priv, &r->handle);
+
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+/**
+ * drm_fb_release - remove and free the FBs on this file
+ * @filp: file * from the ioctl
+ *
+ * LOCKING:
+ * Takes mode config lock.
+ *
+ * Destroy all the FBs associated with @filp.
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+void drm_fb_release(struct file *filp)
+{
+       struct drm_file *priv = filp->private_data;
+       struct drm_device *dev = priv->minor->dev;
+       struct drm_framebuffer *fb, *tfb;
+
+       mutex_lock(&dev->mode_config.mutex);
+       list_for_each_entry_safe(fb, tfb, &priv->fbs, filp_head) {
+               list_del(&fb->filp_head);
+               fb->funcs->destroy(fb);
+       }
+       mutex_unlock(&dev->mode_config.mutex);
+}
+
+/**
+ * drm_mode_attachmode - add a mode to the user mode list
+ * @dev: DRM device
+ * @connector: connector to add the mode to
+ * @mode: mode to add
+ *
+ * Add @mode to @connector's user mode list.
+ */
+static int drm_mode_attachmode(struct drm_device *dev,
+                              struct drm_connector *connector,
+                              struct drm_display_mode *mode)
+{
+       int ret = 0;
+
+       list_add_tail(&mode->head, &connector->user_modes);
+       return ret;
+}
+
+int drm_mode_attachmode_crtc(struct drm_device *dev, struct drm_crtc *crtc,
+                            struct drm_display_mode *mode)
+{
+       struct drm_connector *connector;
+       int ret = 0;
+       struct drm_display_mode *dup_mode;
+       int need_dup = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               if (!connector->encoder)
+                       break;
+               if (connector->encoder->crtc == crtc) {
+                       if (need_dup)
+                               dup_mode = drm_mode_duplicate(dev, mode);
+                       else
+                               dup_mode = mode;
+                       ret = drm_mode_attachmode(dev, connector, dup_mode);
+                       if (ret)
+                               return ret;
+                       need_dup = 1;
+               }
+       }
+       return 0;
+}
+EXPORT_SYMBOL(drm_mode_attachmode_crtc);
+
+static int drm_mode_detachmode(struct drm_device *dev,
+                              struct drm_connector *connector,
+                              struct drm_display_mode *mode)
+{
+       int found = 0;
+       int ret = 0;
+       struct drm_display_mode *match_mode, *t;
+
+       list_for_each_entry_safe(match_mode, t, &connector->user_modes, head) {
+               if (drm_mode_equal(match_mode, mode)) {
+                       list_del(&match_mode->head);
+                       drm_mode_destroy(dev, match_mode);
+                       found = 1;
+                       break;
+               }
+       }
+
+       if (!found)
+               ret = -EINVAL;
+
+       return ret;
+}
+
+int drm_mode_detachmode_crtc(struct drm_device *dev, struct drm_display_mode *mode)
+{
+       struct drm_connector *connector;
+
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               drm_mode_detachmode(dev, connector, mode);
+       }
+       return 0;
+}
+EXPORT_SYMBOL(drm_mode_detachmode_crtc);
+
+/**
+ * drm_fb_attachmode - Attach a user mode to an connector
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * This attaches a user specified mode to an connector.
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_attachmode_ioctl(struct drm_device *dev,
+                             void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_mode_cmd *mode_cmd = data;
+       struct drm_connector *connector;
+       struct drm_display_mode *mode;
+       struct drm_mode_object *obj;
+       struct drm_mode_modeinfo *umode = &mode_cmd->mode;
+       int ret = 0;
+
+       mutex_lock(&dev->mode_config.mutex);
+
+       obj = drm_mode_object_find(dev, mode_cmd->connector_id, DRM_MODE_OBJECT_CONNECTOR);
+       if (!obj) {
+               ret = -EINVAL;
+               goto out;
+       }
+       connector = obj_to_connector(obj);
+
+       mode = drm_mode_create(dev);
+       if (!mode) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       drm_crtc_convert_umode(mode, umode);
+
+       ret = drm_mode_attachmode(dev, connector, mode);
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+
+/**
+ * drm_fb_detachmode - Detach a user specified mode from an connector
+ * @inode: inode from the ioctl
+ * @filp: file * from the ioctl
+ * @cmd: cmd from ioctl
+ * @arg: arg from ioctl
+ *
+ * Called by the user via ioctl.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_mode_detachmode_ioctl(struct drm_device *dev,
+                             void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_object *obj;
+       struct drm_mode_mode_cmd *mode_cmd = data;
+       struct drm_connector *connector;
+       struct drm_display_mode mode;
+       struct drm_mode_modeinfo *umode = &mode_cmd->mode;
+       int ret = 0;
+
+       mutex_lock(&dev->mode_config.mutex);
+
+       obj = drm_mode_object_find(dev, mode_cmd->connector_id, DRM_MODE_OBJECT_CONNECTOR);
+       if (!obj) {
+               ret = -EINVAL;
+               goto out;
+       }
+       connector = obj_to_connector(obj);
+
+       drm_crtc_convert_umode(&mode, umode);
+       ret = drm_mode_detachmode(dev, connector, &mode);
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+struct drm_property *drm_property_create(struct drm_device *dev, int flags,
+                                        const char *name, int num_values)
+{
+       struct drm_property *property = NULL;
+
+       property = kzalloc(sizeof(struct drm_property), GFP_KERNEL);
+       if (!property)
+               return NULL;
+
+       if (num_values) {
+               property->values = kzalloc(sizeof(uint64_t)*num_values, GFP_KERNEL);
+               if (!property->values)
+                       goto fail;
+       }
+
+       drm_mode_object_get(dev, &property->base, DRM_MODE_OBJECT_PROPERTY);
+       property->flags = flags;
+       property->num_values = num_values;
+       INIT_LIST_HEAD(&property->enum_blob_list);
+
+       if (name)
+               strncpy(property->name, name, DRM_PROP_NAME_LEN);
+
+       list_add_tail(&property->head, &dev->mode_config.property_list);
+       return property;
+fail:
+       kfree(property);
+       return NULL;
+}
+EXPORT_SYMBOL(drm_property_create);
+
+int drm_property_add_enum(struct drm_property *property, int index,
+                         uint64_t value, const char *name)
+{
+       struct drm_property_enum *prop_enum;
+
+       if (!(property->flags & DRM_MODE_PROP_ENUM))
+               return -EINVAL;
+
+       if (!list_empty(&property->enum_blob_list)) {
+               list_for_each_entry(prop_enum, &property->enum_blob_list, head) {
+                       if (prop_enum->value == value) {
+                               strncpy(prop_enum->name, name, DRM_PROP_NAME_LEN);
+                               prop_enum->name[DRM_PROP_NAME_LEN-1] = '\0';
+                               return 0;
+                       }
+               }
+       }
+
+       prop_enum = kzalloc(sizeof(struct drm_property_enum), GFP_KERNEL);
+       if (!prop_enum)
+               return -ENOMEM;
+
+       strncpy(prop_enum->name, name, DRM_PROP_NAME_LEN);
+       prop_enum->name[DRM_PROP_NAME_LEN-1] = '\0';
+       prop_enum->value = value;
+
+       property->values[index] = value;
+       list_add_tail(&prop_enum->head, &property->enum_blob_list);
+       return 0;
+}
+EXPORT_SYMBOL(drm_property_add_enum);
+
+void drm_property_destroy(struct drm_device *dev, struct drm_property *property)
+{
+       struct drm_property_enum *prop_enum, *pt;
+
+       list_for_each_entry_safe(prop_enum, pt, &property->enum_blob_list, head) {
+               list_del(&prop_enum->head);
+               kfree(prop_enum);
+       }
+
+       if (property->num_values)
+               kfree(property->values);
+       drm_mode_object_put(dev, &property->base);
+       list_del(&property->head);
+       kfree(property);
+}
+EXPORT_SYMBOL(drm_property_destroy);
+
+int drm_connector_attach_property(struct drm_connector *connector,
+                              struct drm_property *property, uint64_t init_val)
+{
+       int i;
+
+       for (i = 0; i < DRM_CONNECTOR_MAX_PROPERTY; i++) {
+               if (connector->property_ids[i] == 0) {
+                       connector->property_ids[i] = property->base.id;
+                       connector->property_values[i] = init_val;
+                       break;
+               }
+       }
+
+       if (i == DRM_CONNECTOR_MAX_PROPERTY)
+               return -EINVAL;
+       return 0;
+}
+EXPORT_SYMBOL(drm_connector_attach_property);
+
+int drm_connector_property_set_value(struct drm_connector *connector,
+                                 struct drm_property *property, uint64_t value)
+{
+       int i;
+
+       for (i = 0; i < DRM_CONNECTOR_MAX_PROPERTY; i++) {
+               if (connector->property_ids[i] == property->base.id) {
+                       connector->property_values[i] = value;
+                       break;
+               }
+       }
+
+       if (i == DRM_CONNECTOR_MAX_PROPERTY)
+               return -EINVAL;
+       return 0;
+}
+EXPORT_SYMBOL(drm_connector_property_set_value);
+
+int drm_connector_property_get_value(struct drm_connector *connector,
+                                 struct drm_property *property, uint64_t *val)
+{
+       int i;
+
+       for (i = 0; i < DRM_CONNECTOR_MAX_PROPERTY; i++) {
+               if (connector->property_ids[i] == property->base.id) {
+                       *val = connector->property_values[i];
+                       break;
+               }
+       }
+
+       if (i == DRM_CONNECTOR_MAX_PROPERTY)
+               return -EINVAL;
+       return 0;
+}
+EXPORT_SYMBOL(drm_connector_property_get_value);
+
+int drm_mode_getproperty_ioctl(struct drm_device *dev,
+                              void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_object *obj;
+       struct drm_mode_get_property *out_resp = data;
+       struct drm_property *property;
+       int enum_count = 0;
+       int blob_count = 0;
+       int value_count = 0;
+       int ret = 0, i;
+       int copied;
+       struct drm_property_enum *prop_enum;
+       struct drm_mode_property_enum __user *enum_ptr;
+       struct drm_property_blob *prop_blob;
+       uint32_t *blob_id_ptr;
+       uint64_t __user *values_ptr;
+       uint32_t __user *blob_length_ptr;
+
+       mutex_lock(&dev->mode_config.mutex);
+       obj = drm_mode_object_find(dev, out_resp->prop_id, DRM_MODE_OBJECT_PROPERTY);
+       if (!obj) {
+               ret = -EINVAL;
+               goto done;
+       }
+       property = obj_to_property(obj);
+
+       if (property->flags & DRM_MODE_PROP_ENUM) {
+               list_for_each_entry(prop_enum, &property->enum_blob_list, head)
+                       enum_count++;
+       } else if (property->flags & DRM_MODE_PROP_BLOB) {
+               list_for_each_entry(prop_blob, &property->enum_blob_list, head)
+                       blob_count++;
+       }
+
+       value_count = property->num_values;
+
+       strncpy(out_resp->name, property->name, DRM_PROP_NAME_LEN);
+       out_resp->name[DRM_PROP_NAME_LEN-1] = 0;
+       out_resp->flags = property->flags;
+
+       if ((out_resp->count_values >= value_count) && value_count) {
+               values_ptr = (uint64_t *)(unsigned long)out_resp->values_ptr;
+               for (i = 0; i < value_count; i++) {
+                       if (copy_to_user(values_ptr + i, &property->values[i], sizeof(uint64_t))) {
+                               ret = -EFAULT;
+                               goto done;
+                       }
+               }
+       }
+       out_resp->count_values = value_count;
+
+       if (property->flags & DRM_MODE_PROP_ENUM) {
+               if ((out_resp->count_enum_blobs >= enum_count) && enum_count) {
+                       copied = 0;
+                       enum_ptr = (struct drm_mode_property_enum *)(unsigned long)out_resp->enum_blob_ptr;
+                       list_for_each_entry(prop_enum, &property->enum_blob_list, head) {
+
+                               if (copy_to_user(&enum_ptr[copied].value, &prop_enum->value, sizeof(uint64_t))) {
+                                       ret = -EFAULT;
+                                       goto done;
+                               }
+
+                               if (copy_to_user(&enum_ptr[copied].name,
+                                                &prop_enum->name, DRM_PROP_NAME_LEN)) {
+                                       ret = -EFAULT;
+                                       goto done;
+                               }
+                               copied++;
+                       }
+               }
+               out_resp->count_enum_blobs = enum_count;
+       }
+
+       if (property->flags & DRM_MODE_PROP_BLOB) {
+               if ((out_resp->count_enum_blobs >= blob_count) && blob_count) {
+                       copied = 0;
+                       blob_id_ptr = (uint32_t *)(unsigned long)out_resp->enum_blob_ptr;
+                       blob_length_ptr = (uint32_t *)(unsigned long)out_resp->values_ptr;
+
+                       list_for_each_entry(prop_blob, &property->enum_blob_list, head) {
+                               if (put_user(prop_blob->base.id, blob_id_ptr + copied)) {
+                                       ret = -EFAULT;
+                                       goto done;
+                               }
+
+                               if (put_user(prop_blob->length, blob_length_ptr + copied)) {
+                                       ret = -EFAULT;
+                                       goto done;
+                               }
+
+                               copied++;
+                       }
+               }
+               out_resp->count_enum_blobs = blob_count;
+       }
+done:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+static struct drm_property_blob *drm_property_create_blob(struct drm_device *dev, int length,
+                                                         void *data)
+{
+       struct drm_property_blob *blob;
+
+       if (!length || !data)
+               return NULL;
+
+       blob = kzalloc(sizeof(struct drm_property_blob)+length, GFP_KERNEL);
+       if (!blob)
+               return NULL;
+
+       blob->data = (void *)((char *)blob + sizeof(struct drm_property_blob));
+       blob->length = length;
+
+       memcpy(blob->data, data, length);
+
+       drm_mode_object_get(dev, &blob->base, DRM_MODE_OBJECT_BLOB);
+
+       list_add_tail(&blob->head, &dev->mode_config.property_blob_list);
+       return blob;
+}
+
+static void drm_property_destroy_blob(struct drm_device *dev,
+                              struct drm_property_blob *blob)
+{
+       drm_mode_object_put(dev, &blob->base);
+       list_del(&blob->head);
+       kfree(blob);
+}
+
+int drm_mode_getblob_ioctl(struct drm_device *dev,
+                          void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_object *obj;
+       struct drm_mode_get_blob *out_resp = data;
+       struct drm_property_blob *blob;
+       int ret = 0;
+       void *blob_ptr;
+
+       mutex_lock(&dev->mode_config.mutex);
+       obj = drm_mode_object_find(dev, out_resp->blob_id, DRM_MODE_OBJECT_BLOB);
+       if (!obj) {
+               ret = -EINVAL;
+               goto done;
+       }
+       blob = obj_to_blob(obj);
+
+       if (out_resp->length == blob->length) {
+               blob_ptr = (void *)(unsigned long)out_resp->data;
+               if (copy_to_user(blob_ptr, blob->data, blob->length)){
+                       ret = -EFAULT;
+                       goto done;
+               }
+       }
+       out_resp->length = blob->length;
+
+done:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+int drm_mode_connector_update_edid_property(struct drm_connector *connector,
+                                           struct edid *edid)
+{
+       struct drm_device *dev = connector->dev;
+       int ret = 0;
+
+       if (connector->edid_blob_ptr)
+               drm_property_destroy_blob(dev, connector->edid_blob_ptr);
+
+       /* Delete edid, when there is none. */
+       if (!edid) {
+               connector->edid_blob_ptr = NULL;
+               ret = drm_connector_property_set_value(connector, dev->mode_config.edid_property, 0);
+               return ret;
+       }
+
+       connector->edid_blob_ptr = drm_property_create_blob(connector->dev, 128, edid);
+
+       ret = drm_connector_property_set_value(connector,
+                                              dev->mode_config.edid_property,
+                                              connector->edid_blob_ptr->base.id);
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_mode_connector_update_edid_property);
+
+int drm_mode_connector_property_set_ioctl(struct drm_device *dev,
+                                      void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_connector_set_property *out_resp = data;
+       struct drm_mode_object *obj;
+       struct drm_property *property;
+       struct drm_connector *connector;
+       int ret = -EINVAL;
+       int i;
+
+       mutex_lock(&dev->mode_config.mutex);
+
+       obj = drm_mode_object_find(dev, out_resp->connector_id, DRM_MODE_OBJECT_CONNECTOR);
+       if (!obj) {
+               goto out;
+       }
+       connector = obj_to_connector(obj);
+
+       for (i = 0; i < DRM_CONNECTOR_MAX_PROPERTY; i++) {
+               if (connector->property_ids[i] == out_resp->prop_id)
+                       break;
+       }
+
+       if (i == DRM_CONNECTOR_MAX_PROPERTY) {
+               goto out;
+       }
+
+       obj = drm_mode_object_find(dev, out_resp->prop_id, DRM_MODE_OBJECT_PROPERTY);
+       if (!obj) {
+               goto out;
+       }
+       property = obj_to_property(obj);
+
+       if (property->flags & DRM_MODE_PROP_IMMUTABLE)
+               goto out;
+
+       if (property->flags & DRM_MODE_PROP_RANGE) {
+               if (out_resp->value < property->values[0])
+                       goto out;
+
+               if (out_resp->value > property->values[1])
+                       goto out;
+       } else {
+               int found = 0;
+               for (i = 0; i < property->num_values; i++) {
+                       if (property->values[i] == out_resp->value) {
+                               found = 1;
+                               break;
+                       }
+               }
+               if (!found) {
+                       goto out;
+               }
+       }
+
+       if (connector->funcs->set_property)
+               ret = connector->funcs->set_property(connector, property, out_resp->value);
+
+       /* store the property value if succesful */
+       if (!ret)
+               drm_connector_property_set_value(connector, property, out_resp->value);
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+
+int drm_mode_connector_attach_encoder(struct drm_connector *connector,
+                                     struct drm_encoder *encoder)
+{
+       int i;
+
+       for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
+               if (connector->encoder_ids[i] == 0) {
+                       connector->encoder_ids[i] = encoder->base.id;
+                       return 0;
+               }
+       }
+       return -ENOMEM;
+}
+EXPORT_SYMBOL(drm_mode_connector_attach_encoder);
+
+void drm_mode_connector_detach_encoder(struct drm_connector *connector,
+                                   struct drm_encoder *encoder)
+{
+       int i;
+       for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
+               if (connector->encoder_ids[i] == encoder->base.id) {
+                       connector->encoder_ids[i] = 0;
+                       if (connector->encoder == encoder)
+                               connector->encoder = NULL;
+                       break;
+               }
+       }
+}
+EXPORT_SYMBOL(drm_mode_connector_detach_encoder);
+
+bool drm_mode_crtc_set_gamma_size(struct drm_crtc *crtc,
+                                 int gamma_size)
+{
+       crtc->gamma_size = gamma_size;
+
+       crtc->gamma_store = kzalloc(gamma_size * sizeof(uint16_t) * 3, GFP_KERNEL);
+       if (!crtc->gamma_store) {
+               crtc->gamma_size = 0;
+               return false;
+       }
+
+       return true;
+}
+EXPORT_SYMBOL(drm_mode_crtc_set_gamma_size);
+
+int drm_mode_gamma_set_ioctl(struct drm_device *dev,
+                            void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_crtc_lut *crtc_lut = data;
+       struct drm_mode_object *obj;
+       struct drm_crtc *crtc;
+       void *r_base, *g_base, *b_base;
+       int size;
+       int ret = 0;
+
+       mutex_lock(&dev->mode_config.mutex);
+       obj = drm_mode_object_find(dev, crtc_lut->crtc_id, DRM_MODE_OBJECT_CRTC);
+       if (!obj) {
+               ret = -EINVAL;
+               goto out;
+       }
+       crtc = obj_to_crtc(obj);
+
+       /* memcpy into gamma store */
+       if (crtc_lut->gamma_size != crtc->gamma_size) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       size = crtc_lut->gamma_size * (sizeof(uint16_t));
+       r_base = crtc->gamma_store;
+       if (copy_from_user(r_base, (void __user *)(unsigned long)crtc_lut->red, size)) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       g_base = r_base + size;
+       if (copy_from_user(g_base, (void __user *)(unsigned long)crtc_lut->green, size)) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       b_base = g_base + size;
+       if (copy_from_user(b_base, (void __user *)(unsigned long)crtc_lut->blue, size)) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       crtc->funcs->gamma_set(crtc, r_base, g_base, b_base, crtc->gamma_size);
+
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+
+}
+
+int drm_mode_gamma_get_ioctl(struct drm_device *dev,
+                            void *data, struct drm_file *file_priv)
+{
+       struct drm_mode_crtc_lut *crtc_lut = data;
+       struct drm_mode_object *obj;
+       struct drm_crtc *crtc;
+       void *r_base, *g_base, *b_base;
+       int size;
+       int ret = 0;
+
+       mutex_lock(&dev->mode_config.mutex);
+       obj = drm_mode_object_find(dev, crtc_lut->crtc_id, DRM_MODE_OBJECT_CRTC);
+       if (!obj) {
+               ret = -EINVAL;
+               goto out;
+       }
+       crtc = obj_to_crtc(obj);
+
+       /* memcpy into gamma store */
+       if (crtc_lut->gamma_size != crtc->gamma_size) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       size = crtc_lut->gamma_size * (sizeof(uint16_t));
+       r_base = crtc->gamma_store;
+       if (copy_to_user((void __user *)(unsigned long)crtc_lut->red, r_base, size)) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       g_base = r_base + size;
+       if (copy_to_user((void __user *)(unsigned long)crtc_lut->green, g_base, size)) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       b_base = g_base + size;
+       if (copy_to_user((void __user *)(unsigned long)crtc_lut->blue, b_base, size)) {
+               ret = -EFAULT;
+               goto out;
+       }
+out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c
new file mode 100644 (file)
index 0000000..d8a982b
--- /dev/null
@@ -0,0 +1,826 @@
+/*
+ * Copyright (c) 2006-2008 Intel Corporation
+ * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
+ *
+ * DRM core CRTC related functions
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Keith Packard
+ *     Eric Anholt <eric@anholt.net>
+ *      Dave Airlie <airlied@linux.ie>
+ *      Jesse Barnes <jesse.barnes@intel.com>
+ */
+
+#include "drmP.h"
+#include "drm_crtc.h"
+#include "drm_crtc_helper.h"
+
+/*
+ * Detailed mode info for 800x600@60Hz
+ */
+static struct drm_display_mode std_mode[] = {
+       { DRM_MODE("800x600", DRM_MODE_TYPE_DEFAULT, 40000, 800, 840,
+                  968, 1056, 0, 600, 601, 605, 628, 0,
+                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
+};
+
+/**
+ * drm_helper_probe_connector_modes - get complete set of display modes
+ * @dev: DRM device
+ * @maxX: max width for modes
+ * @maxY: max height for modes
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Based on @dev's mode_config layout, scan all the connectors and try to detect
+ * modes on them.  Modes will first be added to the connector's probed_modes
+ * list, then culled (based on validity and the @maxX, @maxY parameters) and
+ * put into the normal modes list.
+ *
+ * Intended to be used either at bootup time or when major configuration
+ * changes have occurred.
+ *
+ * FIXME: take into account monitor limits
+ */
+void drm_helper_probe_single_connector_modes(struct drm_connector *connector,
+                                            uint32_t maxX, uint32_t maxY)
+{
+       struct drm_device *dev = connector->dev;
+       struct drm_display_mode *mode, *t;
+       struct drm_connector_helper_funcs *connector_funcs =
+               connector->helper_private;
+       int ret;
+
+       DRM_DEBUG("%s\n", drm_get_connector_name(connector));
+       /* set all modes to the unverified state */
+       list_for_each_entry_safe(mode, t, &connector->modes, head)
+               mode->status = MODE_UNVERIFIED;
+
+       connector->status = connector->funcs->detect(connector);
+
+       if (connector->status == connector_status_disconnected) {
+               DRM_DEBUG("%s is disconnected\n",
+                         drm_get_connector_name(connector));
+               /* TODO set EDID to NULL */
+               return;
+       }
+
+       ret = (*connector_funcs->get_modes)(connector);
+
+       if (ret) {
+               drm_mode_connector_list_update(connector);
+       }
+
+       if (maxX && maxY)
+               drm_mode_validate_size(dev, &connector->modes, maxX,
+                                      maxY, 0);
+       list_for_each_entry_safe(mode, t, &connector->modes, head) {
+               if (mode->status == MODE_OK)
+                       mode->status = connector_funcs->mode_valid(connector,
+                                                                  mode);
+       }
+
+
+       drm_mode_prune_invalid(dev, &connector->modes, true);
+
+       if (list_empty(&connector->modes)) {
+               struct drm_display_mode *stdmode;
+
+               DRM_DEBUG("No valid modes on %s\n",
+                         drm_get_connector_name(connector));
+
+               /* Should we do this here ???
+                * When no valid EDID modes are available we end up
+                * here and bailed in the past, now we add a standard
+                * 640x480@60Hz mode and carry on.
+                */
+               stdmode = drm_mode_duplicate(dev, &std_mode[0]);
+               drm_mode_probed_add(connector, stdmode);
+               drm_mode_list_concat(&connector->probed_modes,
+                                    &connector->modes);
+
+               DRM_DEBUG("Adding standard 640x480 @ 60Hz to %s\n",
+                         drm_get_connector_name(connector));
+       }
+
+       drm_mode_sort(&connector->modes);
+
+       DRM_DEBUG("Probed modes for %s\n", drm_get_connector_name(connector));
+       list_for_each_entry_safe(mode, t, &connector->modes, head) {
+               mode->vrefresh = drm_mode_vrefresh(mode);
+
+               drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
+               drm_mode_debug_printmodeline(mode);
+       }
+}
+EXPORT_SYMBOL(drm_helper_probe_single_connector_modes);
+
+void drm_helper_probe_connector_modes(struct drm_device *dev, uint32_t maxX,
+                                     uint32_t maxY)
+{
+       struct drm_connector *connector;
+
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               drm_helper_probe_single_connector_modes(connector, maxX, maxY);
+       }
+}
+EXPORT_SYMBOL(drm_helper_probe_connector_modes);
+
+
+/**
+ * drm_helper_crtc_in_use - check if a given CRTC is in a mode_config
+ * @crtc: CRTC to check
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Walk @crtc's DRM device's mode_config and see if it's in use.
+ *
+ * RETURNS:
+ * True if @crtc is part of the mode_config, false otherwise.
+ */
+bool drm_helper_crtc_in_use(struct drm_crtc *crtc)
+{
+       struct drm_encoder *encoder;
+       struct drm_device *dev = crtc->dev;
+       /* FIXME: Locking around list access? */
+       list_for_each_entry(encoder, &dev->mode_config.encoder_list, head)
+               if (encoder->crtc == crtc)
+                       return true;
+       return false;
+}
+EXPORT_SYMBOL(drm_helper_crtc_in_use);
+
+/**
+ * drm_disable_unused_functions - disable unused objects
+ * @dev: DRM device
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * If an connector or CRTC isn't part of @dev's mode_config, it can be disabled
+ * by calling its dpms function, which should power it off.
+ */
+void drm_helper_disable_unused_functions(struct drm_device *dev)
+{
+       struct drm_encoder *encoder;
+       struct drm_encoder_helper_funcs *encoder_funcs;
+       struct drm_crtc *crtc;
+
+       list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+               encoder_funcs = encoder->helper_private;
+               if (!encoder->crtc)
+                       (*encoder_funcs->dpms)(encoder, DRM_MODE_DPMS_OFF);
+       }
+
+       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+               struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
+               crtc->enabled = drm_helper_crtc_in_use(crtc);
+               if (!crtc->enabled) {
+                       crtc_funcs->dpms(crtc, DRM_MODE_DPMS_OFF);
+                       crtc->fb = NULL;
+               }
+       }
+}
+EXPORT_SYMBOL(drm_helper_disable_unused_functions);
+
+static struct drm_display_mode *drm_has_preferred_mode(struct drm_connector *connector, int width, int height)
+{
+       struct drm_display_mode *mode;
+
+       list_for_each_entry(mode, &connector->modes, head) {
+               if (drm_mode_width(mode) > width ||
+                   drm_mode_height(mode) > height)
+                       continue;
+               if (mode->type & DRM_MODE_TYPE_PREFERRED)
+                       return mode;
+       }
+       return NULL;
+}
+
+static bool drm_connector_enabled(struct drm_connector *connector, bool strict)
+{
+       bool enable;
+
+       if (strict) {
+               enable = connector->status == connector_status_connected;
+       } else {
+               enable = connector->status != connector_status_disconnected;
+       }
+       return enable;
+}
+
+static void drm_enable_connectors(struct drm_device *dev, bool *enabled)
+{
+       bool any_enabled = false;
+       struct drm_connector *connector;
+       int i = 0;
+
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               enabled[i] = drm_connector_enabled(connector, true);
+               any_enabled |= enabled[i];
+               i++;
+       }
+
+       if (any_enabled)
+               return;
+
+       i = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               enabled[i] = drm_connector_enabled(connector, false);
+               i++;
+       }
+}
+
+static bool drm_target_preferred(struct drm_device *dev,
+                                struct drm_display_mode **modes,
+                                bool *enabled, int width, int height)
+{
+       struct drm_connector *connector;
+       int i = 0;
+
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+
+               if (enabled[i] == false) {
+                       i++;
+                       continue;
+               }
+
+               modes[i] = drm_has_preferred_mode(connector, width, height);
+               if (!modes[i]) {
+                       list_for_each_entry(modes[i], &connector->modes, head)
+                               break;
+               }
+               i++;
+       }
+       return true;
+}
+
+static int drm_pick_crtcs(struct drm_device *dev,
+                         struct drm_crtc **best_crtcs,
+                         struct drm_display_mode **modes,
+                         int n, int width, int height)
+{
+       int c, o;
+       struct drm_connector *connector;
+       struct drm_connector_helper_funcs *connector_funcs;
+       struct drm_encoder *encoder;
+       struct drm_crtc *best_crtc;
+       int my_score, best_score, score;
+       struct drm_crtc **crtcs, *crtc;
+
+       if (n == dev->mode_config.num_connector)
+               return 0;
+       c = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               if (c == n)
+                       break;
+               c++;
+       }
+
+       best_crtcs[n] = NULL;
+       best_crtc = NULL;
+       best_score = drm_pick_crtcs(dev, best_crtcs, modes, n+1, width, height);
+       if (modes[n] == NULL)
+               return best_score;
+
+       crtcs = kmalloc(dev->mode_config.num_connector *
+                       sizeof(struct drm_crtc *), GFP_KERNEL);
+       if (!crtcs)
+               return best_score;
+
+       my_score = 1;
+       if (connector->status == connector_status_connected)
+               my_score++;
+       if (drm_has_preferred_mode(connector, width, height))
+               my_score++;
+
+       connector_funcs = connector->helper_private;
+       encoder = connector_funcs->best_encoder(connector);
+       if (!encoder)
+               goto out;
+
+       connector->encoder = encoder;
+
+       /* select a crtc for this connector and then attempt to configure
+          remaining connectors */
+       c = 0;
+       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+
+               if ((connector->encoder->possible_crtcs & (1 << c)) == 0) {
+                       c++;
+                       continue;
+               }
+
+               for (o = 0; o < n; o++)
+                       if (best_crtcs[o] == crtc)
+                               break;
+
+               if (o < n) {
+                       /* ignore cloning for now */
+                       c++;
+                       continue;
+               }
+
+               crtcs[n] = crtc;
+               memcpy(crtcs, best_crtcs, n * sizeof(struct drm_crtc *));
+               score = my_score + drm_pick_crtcs(dev, crtcs, modes, n + 1,
+                                                 width, height);
+               if (score > best_score) {
+                       best_crtc = crtc;
+                       best_score = score;
+                       memcpy(best_crtcs, crtcs,
+                              dev->mode_config.num_connector *
+                              sizeof(struct drm_crtc *));
+               }
+               c++;
+       }
+out:
+       kfree(crtcs);
+       return best_score;
+}
+
+static void drm_setup_crtcs(struct drm_device *dev)
+{
+       struct drm_crtc **crtcs;
+       struct drm_display_mode **modes;
+       struct drm_encoder *encoder;
+       struct drm_connector *connector;
+       bool *enabled;
+       int width, height;
+       int i, ret;
+
+       width = dev->mode_config.max_width;
+       height = dev->mode_config.max_height;
+
+       /* clean out all the encoder/crtc combos */
+       list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+               encoder->crtc = NULL;
+       }
+
+       crtcs = kcalloc(dev->mode_config.num_connector,
+                       sizeof(struct drm_crtc *), GFP_KERNEL);
+       modes = kcalloc(dev->mode_config.num_connector,
+                       sizeof(struct drm_display_mode *), GFP_KERNEL);
+       enabled = kcalloc(dev->mode_config.num_connector,
+                         sizeof(bool), GFP_KERNEL);
+
+       drm_enable_connectors(dev, enabled);
+
+       ret = drm_target_preferred(dev, modes, enabled, width, height);
+       if (!ret)
+               DRM_ERROR("Unable to find initial modes\n");
+
+       drm_pick_crtcs(dev, crtcs, modes, 0, width, height);
+
+       i = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               struct drm_display_mode *mode = modes[i];
+               struct drm_crtc *crtc = crtcs[i];
+
+               if (connector->encoder == NULL) {
+                       i++;
+                       continue;
+               }
+
+               if (mode && crtc) {
+                       crtc->desired_mode = mode;
+                       connector->encoder->crtc = crtc;
+               } else
+                       connector->encoder->crtc = NULL;
+               i++;
+       }
+
+       kfree(crtcs);
+       kfree(modes);
+       kfree(enabled);
+}
+/**
+ * drm_crtc_set_mode - set a mode
+ * @crtc: CRTC to program
+ * @mode: mode to use
+ * @x: width of mode
+ * @y: height of mode
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Try to set @mode on @crtc.  Give @crtc and its associated connectors a chance
+ * to fixup or reject the mode prior to trying to set it.
+ *
+ * RETURNS:
+ * True if the mode was set successfully, or false otherwise.
+ */
+bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
+                             struct drm_display_mode *mode,
+                             int x, int y,
+                             struct drm_framebuffer *old_fb)
+{
+       struct drm_device *dev = crtc->dev;
+       struct drm_display_mode *adjusted_mode, saved_mode;
+       struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
+       struct drm_encoder_helper_funcs *encoder_funcs;
+       int saved_x, saved_y;
+       struct drm_encoder *encoder;
+       bool ret = true;
+
+       adjusted_mode = drm_mode_duplicate(dev, mode);
+
+       crtc->enabled = drm_helper_crtc_in_use(crtc);
+
+       if (!crtc->enabled)
+               return true;
+
+       saved_mode = crtc->mode;
+       saved_x = crtc->x;
+       saved_y = crtc->y;
+
+       /* Update crtc values up front so the driver can rely on them for mode
+        * setting.
+        */
+       crtc->mode = *mode;
+       crtc->x = x;
+       crtc->y = y;
+
+       if (drm_mode_equal(&saved_mode, &crtc->mode)) {
+               if (saved_x != crtc->x || saved_y != crtc->y) {
+                       crtc_funcs->mode_set_base(crtc, crtc->x, crtc->y,
+                                                 old_fb);
+                       goto done;
+               }
+       }
+
+       /* Pass our mode to the connectors and the CRTC to give them a chance to
+        * adjust it according to limitations or connector properties, and also
+        * a chance to reject the mode entirely.
+        */
+       list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+
+               if (encoder->crtc != crtc)
+                       continue;
+               encoder_funcs = encoder->helper_private;
+               if (!(ret = encoder_funcs->mode_fixup(encoder, mode,
+                                                     adjusted_mode))) {
+                       goto done;
+               }
+       }
+
+       if (!(ret = crtc_funcs->mode_fixup(crtc, mode, adjusted_mode))) {
+               goto done;
+       }
+
+       /* Prepare the encoders and CRTCs before setting the mode. */
+       list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+
+               if (encoder->crtc != crtc)
+                       continue;
+               encoder_funcs = encoder->helper_private;
+               /* Disable the encoders as the first thing we do. */
+               encoder_funcs->prepare(encoder);
+       }
+
+       crtc_funcs->prepare(crtc);
+
+       /* Set up the DPLL and any encoders state that needs to adjust or depend
+        * on the DPLL.
+        */
+       crtc_funcs->mode_set(crtc, mode, adjusted_mode, x, y, old_fb);
+
+       list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+
+               if (encoder->crtc != crtc)
+                       continue;
+
+               DRM_INFO("%s: set mode %s %x\n", drm_get_encoder_name(encoder),
+                        mode->name, mode->base.id);
+               encoder_funcs = encoder->helper_private;
+               encoder_funcs->mode_set(encoder, mode, adjusted_mode);
+       }
+
+       /* Now enable the clocks, plane, pipe, and connectors that we set up. */
+       crtc_funcs->commit(crtc);
+
+       list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+
+               if (encoder->crtc != crtc)
+                       continue;
+
+               encoder_funcs = encoder->helper_private;
+               encoder_funcs->commit(encoder);
+
+       }
+
+       /* XXX free adjustedmode */
+       drm_mode_destroy(dev, adjusted_mode);
+       /* FIXME: add subpixel order */
+done:
+       if (!ret) {
+               crtc->mode = saved_mode;
+               crtc->x = saved_x;
+               crtc->y = saved_y;
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_crtc_helper_set_mode);
+
+
+/**
+ * drm_crtc_helper_set_config - set a new config from userspace
+ * @crtc: CRTC to setup
+ * @crtc_info: user provided configuration
+ * @new_mode: new mode to set
+ * @connector_set: set of connectors for the new config
+ * @fb: new framebuffer
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Setup a new configuration, provided by the user in @crtc_info, and enable
+ * it.
+ *
+ * RETURNS:
+ * Zero. (FIXME)
+ */
+int drm_crtc_helper_set_config(struct drm_mode_set *set)
+{
+       struct drm_device *dev;
+       struct drm_crtc **save_crtcs, *new_crtc;
+       struct drm_encoder **save_encoders, *new_encoder;
+       struct drm_framebuffer *old_fb;
+       bool save_enabled;
+       bool changed = false;
+       bool flip_or_move = false;
+       struct drm_connector *connector;
+       int count = 0, ro, fail = 0;
+       struct drm_crtc_helper_funcs *crtc_funcs;
+       int ret = 0;
+
+       DRM_DEBUG("\n");
+
+       if (!set)
+               return -EINVAL;
+
+       if (!set->crtc)
+               return -EINVAL;
+
+       if (!set->crtc->helper_private)
+               return -EINVAL;
+
+       crtc_funcs = set->crtc->helper_private;
+
+       DRM_DEBUG("crtc: %p %d fb: %p connectors: %p num_connectors: %d (x, y) (%i, %i)\n",
+                 set->crtc, set->crtc->base.id, set->fb, set->connectors,
+                 (int)set->num_connectors, set->x, set->y);
+
+       dev = set->crtc->dev;
+
+       /* save previous config */
+       save_enabled = set->crtc->enabled;
+
+       /* this is meant to be num_connector not num_crtc */
+       save_crtcs = kzalloc(dev->mode_config.num_connector *
+                            sizeof(struct drm_crtc *), GFP_KERNEL);
+       if (!save_crtcs)
+               return -ENOMEM;
+
+       save_encoders = kzalloc(dev->mode_config.num_connector *
+                               sizeof(struct drm_encoders *), GFP_KERNEL);
+       if (!save_encoders) {
+               kfree(save_crtcs);
+               return -ENOMEM;
+       }
+
+       /* We should be able to check here if the fb has the same properties
+        * and then just flip_or_move it */
+       if (set->crtc->fb != set->fb) {
+               /* if we have no fb then its a change not a flip */
+               if (set->crtc->fb == NULL)
+                       changed = true;
+               else
+                       flip_or_move = true;
+       }
+
+       if (set->x != set->crtc->x || set->y != set->crtc->y)
+               flip_or_move = true;
+
+       if (set->mode && !drm_mode_equal(set->mode, &set->crtc->mode)) {
+               DRM_DEBUG("modes are different\n");
+               drm_mode_debug_printmodeline(&set->crtc->mode);
+               drm_mode_debug_printmodeline(set->mode);
+               changed = true;
+       }
+
+       /* a) traverse passed in connector list and get encoders for them */
+       count = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               struct drm_connector_helper_funcs *connector_funcs =
+                       connector->helper_private;
+               save_encoders[count++] = connector->encoder;
+               new_encoder = connector->encoder;
+               for (ro = 0; ro < set->num_connectors; ro++) {
+                       if (set->connectors[ro] == connector) {
+                               new_encoder = connector_funcs->best_encoder(connector);
+                               /* if we can't get an encoder for a connector
+                                  we are setting now - then fail */
+                               if (new_encoder == NULL)
+                                       /* don't break so fail path works correct */
+                                       fail = 1;
+                               break;
+                       }
+               }
+
+               if (new_encoder != connector->encoder) {
+                       changed = true;
+                       connector->encoder = new_encoder;
+               }
+       }
+
+       if (fail) {
+               ret = -EINVAL;
+               goto fail_no_encoder;
+       }
+
+       count = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               if (!connector->encoder)
+                       continue;
+
+               save_crtcs[count++] = connector->encoder->crtc;
+
+               if (connector->encoder->crtc == set->crtc)
+                       new_crtc = NULL;
+               else
+                       new_crtc = connector->encoder->crtc;
+
+               for (ro = 0; ro < set->num_connectors; ro++) {
+                       if (set->connectors[ro] == connector)
+                               new_crtc = set->crtc;
+               }
+               if (new_crtc != connector->encoder->crtc) {
+                       changed = true;
+                       connector->encoder->crtc = new_crtc;
+               }
+       }
+
+       /* mode_set_base is not a required function */
+       if (flip_or_move && !crtc_funcs->mode_set_base)
+               changed = true;
+
+       if (changed) {
+               old_fb = set->crtc->fb;
+               set->crtc->fb = set->fb;
+               set->crtc->enabled = (set->mode != NULL);
+               if (set->mode != NULL) {
+                       DRM_DEBUG("attempting to set mode from userspace\n");
+                       drm_mode_debug_printmodeline(set->mode);
+                       if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
+                                                     set->x, set->y,
+                                                     old_fb)) {
+                               ret = -EINVAL;
+                               goto fail_set_mode;
+                       }
+                       /* TODO are these needed? */
+                       set->crtc->desired_x = set->x;
+                       set->crtc->desired_y = set->y;
+                       set->crtc->desired_mode = set->mode;
+               }
+               drm_helper_disable_unused_functions(dev);
+       } else if (flip_or_move) {
+               old_fb = set->crtc->fb;
+               if (set->crtc->fb != set->fb)
+                       set->crtc->fb = set->fb;
+               crtc_funcs->mode_set_base(set->crtc, set->x, set->y, old_fb);
+       }
+
+       kfree(save_encoders);
+       kfree(save_crtcs);
+       return 0;
+
+fail_set_mode:
+       set->crtc->enabled = save_enabled;
+       count = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head)
+               connector->encoder->crtc = save_crtcs[count++];
+fail_no_encoder:
+       kfree(save_crtcs);
+       count = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               connector->encoder = save_encoders[count++];
+       }
+       kfree(save_encoders);
+       return ret;
+}
+EXPORT_SYMBOL(drm_crtc_helper_set_config);
+
+bool drm_helper_plugged_event(struct drm_device *dev)
+{
+       DRM_DEBUG("\n");
+
+       drm_helper_probe_connector_modes(dev, dev->mode_config.max_width,
+                                        dev->mode_config.max_height);
+
+       drm_setup_crtcs(dev);
+
+       /* alert the driver fb layer */
+       dev->mode_config.funcs->fb_changed(dev);
+
+       /* FIXME: send hotplug event */
+       return true;
+}
+/**
+ * drm_initial_config - setup a sane initial connector configuration
+ * @dev: DRM device
+ * @can_grow: this configuration is growable
+ *
+ * LOCKING:
+ * Called at init time, must take mode config lock.
+ *
+ * Scan the CRTCs and connectors and try to put together an initial setup.
+ * At the moment, this is a cloned configuration across all heads with
+ * a new framebuffer object as the backing store.
+ *
+ * RETURNS:
+ * Zero if everything went ok, nonzero otherwise.
+ */
+bool drm_helper_initial_config(struct drm_device *dev, bool can_grow)
+{
+       int ret = false;
+
+       drm_helper_plugged_event(dev);
+       return ret;
+}
+EXPORT_SYMBOL(drm_helper_initial_config);
+
+/**
+ * drm_hotplug_stage_two
+ * @dev DRM device
+ * @connector hotpluged connector
+ *
+ * LOCKING.
+ * Caller must hold mode config lock, function might grab struct lock.
+ *
+ * Stage two of a hotplug.
+ *
+ * RETURNS:
+ * Zero on success, errno on failure.
+ */
+int drm_helper_hotplug_stage_two(struct drm_device *dev)
+{
+       drm_helper_plugged_event(dev);
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_helper_hotplug_stage_two);
+
+int drm_helper_mode_fill_fb_struct(struct drm_framebuffer *fb,
+                                  struct drm_mode_fb_cmd *mode_cmd)
+{
+       fb->width = mode_cmd->width;
+       fb->height = mode_cmd->height;
+       fb->pitch = mode_cmd->pitch;
+       fb->bits_per_pixel = mode_cmd->bpp;
+       fb->depth = mode_cmd->depth;
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_helper_mode_fill_fb_struct);
+
+int drm_helper_resume_force_mode(struct drm_device *dev)
+{
+       struct drm_crtc *crtc;
+       int ret;
+
+       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+
+               if (!crtc->enabled)
+                       continue;
+
+               ret = drm_crtc_helper_set_mode(crtc, &crtc->mode,
+                                              crtc->x, crtc->y, crtc->fb);
+
+               if (ret == false)
+                       DRM_ERROR("failed to set mode on crtc %p\n", crtc);
+       }
+       return 0;
+}
+EXPORT_SYMBOL(drm_helper_resume_force_mode);
index 996097acb5e76b3c3d7698c0885f563ad9248522..febb517ee679255a7e3ea694f8b68225118b95f0 100644 (file)
@@ -74,6 +74,9 @@ static struct drm_ioctl_desc drm_ioctls[] = {
        DRM_IOCTL_DEF(DRM_IOCTL_SET_SAREA_CTX, drm_setsareactx, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
        DRM_IOCTL_DEF(DRM_IOCTL_GET_SAREA_CTX, drm_getsareactx, DRM_AUTH),
 
+       DRM_IOCTL_DEF(DRM_IOCTL_SET_MASTER, drm_setmaster_ioctl, DRM_ROOT_ONLY),
+       DRM_IOCTL_DEF(DRM_IOCTL_DROP_MASTER, drm_dropmaster_ioctl, DRM_ROOT_ONLY),
+
        DRM_IOCTL_DEF(DRM_IOCTL_ADD_CTX, drm_addctx, DRM_AUTH|DRM_ROOT_ONLY),
        DRM_IOCTL_DEF(DRM_IOCTL_RM_CTX, drm_rmctx, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
        DRM_IOCTL_DEF(DRM_IOCTL_MOD_CTX, drm_modctx, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY),
@@ -123,6 +126,23 @@ static struct drm_ioctl_desc drm_ioctls[] = {
        DRM_IOCTL_DEF(DRM_IOCTL_GEM_CLOSE, drm_gem_close_ioctl, 0),
        DRM_IOCTL_DEF(DRM_IOCTL_GEM_FLINK, drm_gem_flink_ioctl, DRM_AUTH),
        DRM_IOCTL_DEF(DRM_IOCTL_GEM_OPEN, drm_gem_open_ioctl, DRM_AUTH),
+
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_CURSOR, drm_mode_cursor_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETGAMMA, drm_mode_gamma_get_ioctl, DRM_MASTER),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETGAMMA, drm_mode_gamma_set_ioctl, DRM_MASTER),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETENCODER, drm_mode_getencoder, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATTACHMODE, drm_mode_attachmode_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_DETACHMODE, drm_mode_detachmode_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPERTY, drm_mode_getproperty_ioctl, DRM_MASTER | DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPROPERTY, drm_mode_connector_property_set_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPBLOB, drm_mode_getblob_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW),
+       DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW),
 };
 
 #define DRM_CORE_IOCTL_COUNT   ARRAY_SIZE( drm_ioctls )
@@ -138,8 +158,6 @@ static struct drm_ioctl_desc drm_ioctls[] = {
  */
 int drm_lastclose(struct drm_device * dev)
 {
-       struct drm_magic_entry *pt, *next;
-       struct drm_map_list *r_list, *list_t;
        struct drm_vma_entry *vma, *vma_temp;
        int i;
 
@@ -149,13 +167,7 @@ int drm_lastclose(struct drm_device * dev)
                dev->driver->lastclose(dev);
        DRM_DEBUG("driver lastclose completed\n");
 
-       if (dev->unique) {
-               drm_free(dev->unique, strlen(dev->unique) + 1, DRM_MEM_DRIVER);
-               dev->unique = NULL;
-               dev->unique_len = 0;
-       }
-
-       if (dev->irq_enabled)
+       if (dev->irq_enabled && !drm_core_check_feature(dev, DRIVER_MODESET))
                drm_irq_uninstall(dev);
 
        mutex_lock(&dev->struct_mutex);
@@ -164,18 +176,9 @@ int drm_lastclose(struct drm_device * dev)
        drm_drawable_free_all(dev);
        del_timer(&dev->timer);
 
-       /* Clear pid list */
-       if (dev->magicfree.next) {
-               list_for_each_entry_safe(pt, next, &dev->magicfree, head) {
-                       list_del(&pt->head);
-                       drm_ht_remove_item(&dev->magiclist, &pt->hash_item);
-                       drm_free(pt, sizeof(*pt), DRM_MEM_MAGIC);
-               }
-               drm_ht_remove(&dev->magiclist);
-       }
-
        /* Clear AGP information */
-       if (drm_core_has_AGP(dev) && dev->agp) {
+       if (drm_core_has_AGP(dev) && dev->agp &&
+           !drm_core_check_feature(dev, DRIVER_MODESET)) {
                struct drm_agp_mem *entry, *tempe;
 
                /* Remove AGP resources, but leave dev->agp
@@ -194,7 +197,8 @@ int drm_lastclose(struct drm_device * dev)
                dev->agp->acquired = 0;
                dev->agp->enabled = 0;
        }
-       if (drm_core_check_feature(dev, DRIVER_SG) && dev->sg) {
+       if (drm_core_check_feature(dev, DRIVER_SG) && dev->sg &&
+           !drm_core_check_feature(dev, DRIVER_MODESET)) {
                drm_sg_cleanup(dev->sg);
                dev->sg = NULL;
        }
@@ -205,13 +209,6 @@ int drm_lastclose(struct drm_device * dev)
                drm_free(vma, sizeof(*vma), DRM_MEM_VMAS);
        }
 
-       list_for_each_entry_safe(r_list, list_t, &dev->maplist, head) {
-               if (!(r_list->map->flags & _DRM_DRIVER)) {
-                       drm_rmmap_locked(dev, r_list->map);
-                       r_list = NULL;
-               }
-       }
-
        if (drm_core_check_feature(dev, DRIVER_DMA_QUEUE) && dev->queuelist) {
                for (i = 0; i < dev->queue_count; i++) {
                        if (dev->queuelist[i]) {
@@ -228,14 +225,11 @@ int drm_lastclose(struct drm_device * dev)
        }
        dev->queue_count = 0;
 
-       if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+       if (drm_core_check_feature(dev, DRIVER_HAVE_DMA) &&
+           !drm_core_check_feature(dev, DRIVER_MODESET))
                drm_dma_takedown(dev);
 
-       if (dev->lock.hw_lock) {
-               dev->sigdata.lock = dev->lock.hw_lock = NULL;   /* SHM removed */
-               dev->lock.file_priv = NULL;
-               wake_up_interruptible(&dev->lock.lock_queue);
-       }
+       dev->dev_mapping = NULL;
        mutex_unlock(&dev->struct_mutex);
 
        DRM_DEBUG("lastclose completed\n");
@@ -263,6 +257,8 @@ int drm_init(struct drm_driver *driver)
 
        DRM_DEBUG("\n");
 
+       INIT_LIST_HEAD(&driver->device_list);
+
        for (i = 0; driver->pci_driver.id_table[i].vendor != 0; i++) {
                pid = (struct pci_device_id *)&driver->pci_driver.id_table[i];
 
@@ -329,35 +325,24 @@ static void drm_cleanup(struct drm_device * dev)
        drm_ht_remove(&dev->map_hash);
        drm_ctxbitmap_cleanup(dev);
 
+       if (drm_core_check_feature(dev, DRIVER_MODESET))
+               drm_put_minor(&dev->control);
+
+       if (dev->driver->driver_features & DRIVER_GEM)
+               drm_gem_destroy(dev);
+
        drm_put_minor(&dev->primary);
        if (drm_put_dev(dev))
                DRM_ERROR("Cannot unload module\n");
 }
 
-static int drm_minors_cleanup(int id, void *ptr, void *data)
-{
-       struct drm_minor *minor = ptr;
-       struct drm_device *dev;
-       struct drm_driver *driver = data;
-
-       dev = minor->dev;
-       if (minor->dev->driver != driver)
-               return 0;
-
-       if (minor->type != DRM_MINOR_LEGACY)
-               return 0;
-
-       if (dev)
-               pci_dev_put(dev->pdev);
-       drm_cleanup(dev);
-       return 1;
-}
-
 void drm_exit(struct drm_driver *driver)
 {
+       struct drm_device *dev, *tmp;
        DRM_DEBUG("\n");
 
-       idr_for_each(&drm_minors_idr, &drm_minors_cleanup, driver);
+       list_for_each_entry_safe(dev, tmp, &driver->device_list, driver_item)
+               drm_cleanup(dev);
 
        DRM_INFO("Module unloaded\n");
 }
@@ -503,7 +488,7 @@ int drm_ioctl(struct inode *inode, struct file *filp,
                retcode = -EINVAL;
        } else if (((ioctl->flags & DRM_ROOT_ONLY) && !capable(CAP_SYS_ADMIN)) ||
                   ((ioctl->flags & DRM_AUTH) && !file_priv->authenticated) ||
-                  ((ioctl->flags & DRM_MASTER) && !file_priv->master)) {
+                  ((ioctl->flags & DRM_MASTER) && !file_priv->is_master)) {
                retcode = -EACCES;
        } else {
                if (cmd & (IOC_IN | IOC_OUT)) {
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
new file mode 100644 (file)
index 0000000..0fbb0da
--- /dev/null
@@ -0,0 +1,732 @@
+/*
+ * Copyright (c) 2006 Luc Verhaegen (quirks list)
+ * Copyright (c) 2007-2008 Intel Corporation
+ *   Jesse Barnes <jesse.barnes@intel.com>
+ *
+ * DDC probing routines (drm_ddc_read & drm_do_probe_ddc_edid) originally from
+ * FB layer.
+ *   Copyright (C) 2006 Dennis Munsie <dmunsie@cecropia.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, sub license,
+ * 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 NON-INFRINGEMENT. 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/kernel.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+#include "drmP.h"
+#include "drm_edid.h"
+
+/*
+ * TODO:
+ *   - support EDID 1.4 (incl. CE blocks)
+ */
+
+/*
+ * EDID blocks out in the wild have a variety of bugs, try to collect
+ * them here (note that userspace may work around broken monitors first,
+ * but fixes should make their way here so that the kernel "just works"
+ * on as many displays as possible).
+ */
+
+/* First detailed mode wrong, use largest 60Hz mode */
+#define EDID_QUIRK_PREFER_LARGE_60             (1 << 0)
+/* Reported 135MHz pixel clock is too high, needs adjustment */
+#define EDID_QUIRK_135_CLOCK_TOO_HIGH          (1 << 1)
+/* Prefer the largest mode at 75 Hz */
+#define EDID_QUIRK_PREFER_LARGE_75             (1 << 2)
+/* Detail timing is in cm not mm */
+#define EDID_QUIRK_DETAILED_IN_CM              (1 << 3)
+/* Detailed timing descriptors have bogus size values, so just take the
+ * maximum size and use that.
+ */
+#define EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE   (1 << 4)
+/* Monitor forgot to set the first detailed is preferred bit. */
+#define EDID_QUIRK_FIRST_DETAILED_PREFERRED    (1 << 5)
+/* use +hsync +vsync for detailed mode */
+#define EDID_QUIRK_DETAILED_SYNC_PP            (1 << 6)
+
+static struct edid_quirk {
+       char *vendor;
+       int product_id;
+       u32 quirks;
+} edid_quirk_list[] = {
+       /* Acer AL1706 */
+       { "ACR", 44358, EDID_QUIRK_PREFER_LARGE_60 },
+       /* Acer F51 */
+       { "API", 0x7602, EDID_QUIRK_PREFER_LARGE_60 },
+       /* Unknown Acer */
+       { "ACR", 2423, EDID_QUIRK_FIRST_DETAILED_PREFERRED },
+
+       /* Belinea 10 15 55 */
+       { "MAX", 1516, EDID_QUIRK_PREFER_LARGE_60 },
+       { "MAX", 0x77e, EDID_QUIRK_PREFER_LARGE_60 },
+
+       /* Envision Peripherals, Inc. EN-7100e */
+       { "EPI", 59264, EDID_QUIRK_135_CLOCK_TOO_HIGH },
+
+       /* Funai Electronics PM36B */
+       { "FCM", 13600, EDID_QUIRK_PREFER_LARGE_75 |
+         EDID_QUIRK_DETAILED_IN_CM },
+
+       /* LG Philips LCD LP154W01-A5 */
+       { "LPL", 0, EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE },
+       { "LPL", 0x2a00, EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE },
+
+       /* Philips 107p5 CRT */
+       { "PHL", 57364, EDID_QUIRK_FIRST_DETAILED_PREFERRED },
+
+       /* Proview AY765C */
+       { "PTS", 765, EDID_QUIRK_FIRST_DETAILED_PREFERRED },
+
+       /* Samsung SyncMaster 205BW.  Note: irony */
+       { "SAM", 541, EDID_QUIRK_DETAILED_SYNC_PP },
+       /* Samsung SyncMaster 22[5-6]BW */
+       { "SAM", 596, EDID_QUIRK_PREFER_LARGE_60 },
+       { "SAM", 638, EDID_QUIRK_PREFER_LARGE_60 },
+};
+
+
+/* Valid EDID header has these bytes */
+static u8 edid_header[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
+
+/**
+ * edid_is_valid - sanity check EDID data
+ * @edid: EDID data
+ *
+ * Sanity check the EDID block by looking at the header, the version number
+ * and the checksum.  Return 0 if the EDID doesn't check out, or 1 if it's
+ * valid.
+ */
+static bool edid_is_valid(struct edid *edid)
+{
+       int i;
+       u8 csum = 0;
+       u8 *raw_edid = (u8 *)edid;
+
+       if (memcmp(edid->header, edid_header, sizeof(edid_header)))
+               goto bad;
+       if (edid->version != 1) {
+               DRM_ERROR("EDID has major version %d, instead of 1\n", edid->version);
+               goto bad;
+       }
+       if (edid->revision <= 0 || edid->revision > 3) {
+               DRM_ERROR("EDID has minor version %d, which is not between 0-3\n", edid->revision);
+               goto bad;
+       }
+
+       for (i = 0; i < EDID_LENGTH; i++)
+               csum += raw_edid[i];
+       if (csum) {
+               DRM_ERROR("EDID checksum is invalid, remainder is %d\n", csum);
+               goto bad;
+       }
+
+       return 1;
+
+bad:
+       if (raw_edid) {
+               DRM_ERROR("Raw EDID:\n");
+               print_hex_dump_bytes(KERN_ERR, DUMP_PREFIX_NONE, raw_edid, EDID_LENGTH);
+               printk("\n");
+       }
+       return 0;
+}
+
+/**
+ * edid_vendor - match a string against EDID's obfuscated vendor field
+ * @edid: EDID to match
+ * @vendor: vendor string
+ *
+ * Returns true if @vendor is in @edid, false otherwise
+ */
+static bool edid_vendor(struct edid *edid, char *vendor)
+{
+       char edid_vendor[3];
+
+       edid_vendor[0] = ((edid->mfg_id[0] & 0x7c) >> 2) + '@';
+       edid_vendor[1] = (((edid->mfg_id[0] & 0x3) << 3) |
+                         ((edid->mfg_id[1] & 0xe0) >> 5)) + '@';
+       edid_vendor[2] = (edid->mfg_id[2] & 0x1f) + '@';
+
+       return !strncmp(edid_vendor, vendor, 3);
+}
+
+/**
+ * edid_get_quirks - return quirk flags for a given EDID
+ * @edid: EDID to process
+ *
+ * This tells subsequent routines what fixes they need to apply.
+ */
+static u32 edid_get_quirks(struct edid *edid)
+{
+       struct edid_quirk *quirk;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(edid_quirk_list); i++) {
+               quirk = &edid_quirk_list[i];
+
+               if (edid_vendor(edid, quirk->vendor) &&
+                   (EDID_PRODUCT_ID(edid) == quirk->product_id))
+                       return quirk->quirks;
+       }
+
+       return 0;
+}
+
+#define MODE_SIZE(m) ((m)->hdisplay * (m)->vdisplay)
+#define MODE_REFRESH_DIFF(m,r) (abs((m)->vrefresh - target_refresh))
+
+
+/**
+ * edid_fixup_preferred - set preferred modes based on quirk list
+ * @connector: has mode list to fix up
+ * @quirks: quirks list
+ *
+ * Walk the mode list for @connector, clearing the preferred status
+ * on existing modes and setting it anew for the right mode ala @quirks.
+ */
+static void edid_fixup_preferred(struct drm_connector *connector,
+                                u32 quirks)
+{
+       struct drm_display_mode *t, *cur_mode, *preferred_mode;
+       int target_refresh = 0;
+
+       if (list_empty(&connector->probed_modes))
+               return;
+
+       if (quirks & EDID_QUIRK_PREFER_LARGE_60)
+               target_refresh = 60;
+       if (quirks & EDID_QUIRK_PREFER_LARGE_75)
+               target_refresh = 75;
+
+       preferred_mode = list_first_entry(&connector->probed_modes,
+                                         struct drm_display_mode, head);
+
+       list_for_each_entry_safe(cur_mode, t, &connector->probed_modes, head) {
+               cur_mode->type &= ~DRM_MODE_TYPE_PREFERRED;
+
+               if (cur_mode == preferred_mode)
+                       continue;
+
+               /* Largest mode is preferred */
+               if (MODE_SIZE(cur_mode) > MODE_SIZE(preferred_mode))
+                       preferred_mode = cur_mode;
+
+               /* At a given size, try to get closest to target refresh */
+               if ((MODE_SIZE(cur_mode) == MODE_SIZE(preferred_mode)) &&
+                   MODE_REFRESH_DIFF(cur_mode, target_refresh) <
+                   MODE_REFRESH_DIFF(preferred_mode, target_refresh)) {
+                       preferred_mode = cur_mode;
+               }
+       }
+
+       preferred_mode->type |= DRM_MODE_TYPE_PREFERRED;
+}
+
+/**
+ * drm_mode_std - convert standard mode info (width, height, refresh) into mode
+ * @t: standard timing params
+ *
+ * Take the standard timing params (in this case width, aspect, and refresh)
+ * and convert them into a real mode using CVT.
+ *
+ * Punts for now, but should eventually use the FB layer's CVT based mode
+ * generation code.
+ */
+struct drm_display_mode *drm_mode_std(struct drm_device *dev,
+                                     struct std_timing *t)
+{
+       struct drm_display_mode *mode;
+       int hsize = t->hsize * 8 + 248, vsize;
+
+       mode = drm_mode_create(dev);
+       if (!mode)
+               return NULL;
+
+       if (t->aspect_ratio == 0)
+               vsize = (hsize * 10) / 16;
+       else if (t->aspect_ratio == 1)
+               vsize = (hsize * 3) / 4;
+       else if (t->aspect_ratio == 2)
+               vsize = (hsize * 4) / 5;
+       else
+               vsize = (hsize * 9) / 16;
+
+       drm_mode_set_name(mode);
+
+       return mode;
+}
+
+/**
+ * drm_mode_detailed - create a new mode from an EDID detailed timing section
+ * @dev: DRM device (needed to create new mode)
+ * @edid: EDID block
+ * @timing: EDID detailed timing info
+ * @quirks: quirks to apply
+ *
+ * An EDID detailed timing block contains enough info for us to create and
+ * return a new struct drm_display_mode.
+ */
+static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev,
+                                                 struct edid *edid,
+                                                 struct detailed_timing *timing,
+                                                 u32 quirks)
+{
+       struct drm_display_mode *mode;
+       struct detailed_pixel_timing *pt = &timing->data.pixel_data;
+
+       if (pt->stereo) {
+               printk(KERN_WARNING "stereo mode not supported\n");
+               return NULL;
+       }
+       if (!pt->separate_sync) {
+               printk(KERN_WARNING "integrated sync not supported\n");
+               return NULL;
+       }
+
+       mode = drm_mode_create(dev);
+       if (!mode)
+               return NULL;
+
+       mode->type = DRM_MODE_TYPE_DRIVER;
+
+       if (quirks & EDID_QUIRK_135_CLOCK_TOO_HIGH)
+               timing->pixel_clock = 1088;
+
+       mode->clock = timing->pixel_clock * 10;
+
+       mode->hdisplay = (pt->hactive_hi << 8) | pt->hactive_lo;
+       mode->hsync_start = mode->hdisplay + ((pt->hsync_offset_hi << 8) |
+                                             pt->hsync_offset_lo);
+       mode->hsync_end = mode->hsync_start +
+               ((pt->hsync_pulse_width_hi << 8) |
+                pt->hsync_pulse_width_lo);
+       mode->htotal = mode->hdisplay + ((pt->hblank_hi << 8) | pt->hblank_lo);
+
+       mode->vdisplay = (pt->vactive_hi << 8) | pt->vactive_lo;
+       mode->vsync_start = mode->vdisplay + ((pt->vsync_offset_hi << 8) |
+                                             pt->vsync_offset_lo);
+       mode->vsync_end = mode->vsync_start +
+               ((pt->vsync_pulse_width_hi << 8) |
+                pt->vsync_pulse_width_lo);
+       mode->vtotal = mode->vdisplay + ((pt->vblank_hi << 8) | pt->vblank_lo);
+
+       drm_mode_set_name(mode);
+
+       if (pt->interlaced)
+               mode->flags |= DRM_MODE_FLAG_INTERLACE;
+
+       if (quirks & EDID_QUIRK_DETAILED_SYNC_PP) {
+               pt->hsync_positive = 1;
+               pt->vsync_positive = 1;
+       }
+
+       mode->flags |= pt->hsync_positive ? DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC;
+       mode->flags |= pt->vsync_positive ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC;
+
+       mode->width_mm = pt->width_mm_lo | (pt->width_mm_hi << 8);
+       mode->height_mm = pt->height_mm_lo | (pt->height_mm_hi << 8);
+
+       if (quirks & EDID_QUIRK_DETAILED_IN_CM) {
+               mode->width_mm *= 10;
+               mode->height_mm *= 10;
+       }
+
+       if (quirks & EDID_QUIRK_DETAILED_USE_MAXIMUM_SIZE) {
+               mode->width_mm = edid->width_cm * 10;
+               mode->height_mm = edid->height_cm * 10;
+       }
+
+       return mode;
+}
+
+/*
+ * Detailed mode info for the EDID "established modes" data to use.
+ */
+static struct drm_display_mode edid_est_modes[] = {
+       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 40000, 800, 840,
+                  968, 1056, 0, 600, 601, 605, 628, 0,
+                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@60Hz */
+       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 36000, 800, 824,
+                  896, 1024, 0, 600, 601, 603,  625, 0,
+                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@56Hz */
+       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 656,
+                  720, 840, 0, 480, 481, 484, 500, 0,
+                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@75Hz */
+       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 664,
+                  704,  832, 0, 480, 489, 491, 520, 0,
+                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@72Hz */
+       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 30240, 640, 704,
+                  768,  864, 0, 480, 483, 486, 525, 0,
+                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@67Hz */
+       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25200, 640, 656,
+                  752, 800, 0, 480, 490, 492, 525, 0,
+                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@60Hz */
+       { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 35500, 720, 738,
+                  846, 900, 0, 400, 421, 423,  449, 0,
+                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 720x400@88Hz */
+       { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 28320, 720, 738,
+                  846,  900, 0, 400, 412, 414, 449, 0,
+                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 720x400@70Hz */
+       { DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 135000, 1280, 1296,
+                  1440, 1688, 0, 1024, 1025, 1028, 1066, 0,
+                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1280x1024@75Hz */
+       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 78800, 1024, 1040,
+                  1136, 1312, 0,  768, 769, 772, 800, 0,
+                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1024x768@75Hz */
+       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 75000, 1024, 1048,
+                  1184, 1328, 0,  768, 771, 777, 806, 0,
+                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 1024x768@70Hz */
+       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 65000, 1024, 1048,
+                  1184, 1344, 0,  768, 771, 777, 806, 0,
+                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 1024x768@60Hz */
+       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER,44900, 1024, 1032,
+                  1208, 1264, 0, 768, 768, 776, 817, 0,
+                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_INTERLACE) }, /* 1024x768@43Hz */
+       { DRM_MODE("832x624", DRM_MODE_TYPE_DRIVER, 57284, 832, 864,
+                  928, 1152, 0, 624, 625, 628, 667, 0,
+                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 832x624@75Hz */
+       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 49500, 800, 816,
+                  896, 1056, 0, 600, 601, 604,  625, 0,
+                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@75Hz */
+       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 50000, 800, 856,
+                  976, 1040, 0, 600, 637, 643, 666, 0,
+                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@72Hz */
+       { DRM_MODE("1152x864", DRM_MODE_TYPE_DRIVER, 108000, 1152, 1216,
+                  1344, 1600, 0,  864, 865, 868, 900, 0,
+                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1152x864@75Hz */
+};
+
+#define EDID_EST_TIMINGS 16
+#define EDID_STD_TIMINGS 8
+#define EDID_DETAILED_TIMINGS 4
+
+/**
+ * add_established_modes - get est. modes from EDID and add them
+ * @edid: EDID block to scan
+ *
+ * Each EDID block contains a bitmap of the supported "established modes" list
+ * (defined above).  Tease them out and add them to the global modes list.
+ */
+static int add_established_modes(struct drm_connector *connector, struct edid *edid)
+{
+       struct drm_device *dev = connector->dev;
+       unsigned long est_bits = edid->established_timings.t1 |
+               (edid->established_timings.t2 << 8) |
+               ((edid->established_timings.mfg_rsvd & 0x80) << 9);
+       int i, modes = 0;
+
+       for (i = 0; i <= EDID_EST_TIMINGS; i++)
+               if (est_bits & (1<<i)) {
+                       struct drm_display_mode *newmode;
+                       newmode = drm_mode_duplicate(dev, &edid_est_modes[i]);
+                       if (newmode) {
+                               drm_mode_probed_add(connector, newmode);
+                               modes++;
+                       }
+               }
+
+       return modes;
+}
+
+/**
+ * add_standard_modes - get std. modes from EDID and add them
+ * @edid: EDID block to scan
+ *
+ * Standard modes can be calculated using the CVT standard.  Grab them from
+ * @edid, calculate them, and add them to the list.
+ */
+static int add_standard_modes(struct drm_connector *connector, struct edid *edid)
+{
+       struct drm_device *dev = connector->dev;
+       int i, modes = 0;
+
+       for (i = 0; i < EDID_STD_TIMINGS; i++) {
+               struct std_timing *t = &edid->standard_timings[i];
+               struct drm_display_mode *newmode;
+
+               /* If std timings bytes are 1, 1 it's empty */
+               if (t->hsize == 1 && (t->aspect_ratio | t->vfreq) == 1)
+                       continue;
+
+               newmode = drm_mode_std(dev, &edid->standard_timings[i]);
+               if (newmode) {
+                       drm_mode_probed_add(connector, newmode);
+                       modes++;
+               }
+       }
+
+       return modes;
+}
+
+/**
+ * add_detailed_modes - get detailed mode info from EDID data
+ * @connector: attached connector
+ * @edid: EDID block to scan
+ * @quirks: quirks to apply
+ *
+ * Some of the detailed timing sections may contain mode information.  Grab
+ * it and add it to the list.
+ */
+static int add_detailed_info(struct drm_connector *connector,
+                            struct edid *edid, u32 quirks)
+{
+       struct drm_device *dev = connector->dev;
+       int i, j, modes = 0;
+
+       for (i = 0; i < EDID_DETAILED_TIMINGS; i++) {
+               struct detailed_timing *timing = &edid->detailed_timings[i];
+               struct detailed_non_pixel *data = &timing->data.other_data;
+               struct drm_display_mode *newmode;
+
+               /* EDID up to and including 1.2 may put monitor info here */
+               if (edid->version == 1 && edid->revision < 3)
+                       continue;
+
+               /* Detailed mode timing */
+               if (timing->pixel_clock) {
+                       newmode = drm_mode_detailed(dev, edid, timing, quirks);
+                       if (!newmode)
+                               continue;
+
+                       /* First detailed mode is preferred */
+                       if (i == 0 && edid->preferred_timing)
+                               newmode->type |= DRM_MODE_TYPE_PREFERRED;
+                       drm_mode_probed_add(connector, newmode);
+
+                       modes++;
+                       continue;
+               }
+
+               /* Other timing or info */
+               switch (data->type) {
+               case EDID_DETAIL_MONITOR_SERIAL:
+                       break;
+               case EDID_DETAIL_MONITOR_STRING:
+                       break;
+               case EDID_DETAIL_MONITOR_RANGE:
+                       /* Get monitor range data */
+                       break;
+               case EDID_DETAIL_MONITOR_NAME:
+                       break;
+               case EDID_DETAIL_MONITOR_CPDATA:
+                       break;
+               case EDID_DETAIL_STD_MODES:
+                       /* Five modes per detailed section */
+                       for (j = 0; j < 5; i++) {
+                               struct std_timing *std;
+                               struct drm_display_mode *newmode;
+
+                               std = &data->data.timings[j];
+                               newmode = drm_mode_std(dev, std);
+                               if (newmode) {
+                                       drm_mode_probed_add(connector, newmode);
+                                       modes++;
+                               }
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return modes;
+}
+
+#define DDC_ADDR 0x50
+
+unsigned char *drm_do_probe_ddc_edid(struct i2c_adapter *adapter)
+{
+       unsigned char start = 0x0;
+       unsigned char *buf = kmalloc(EDID_LENGTH, GFP_KERNEL);
+       struct i2c_msg msgs[] = {
+               {
+                       .addr   = DDC_ADDR,
+                       .flags  = 0,
+                       .len    = 1,
+                       .buf    = &start,
+               }, {
+                       .addr   = DDC_ADDR,
+                       .flags  = I2C_M_RD,
+                       .len    = EDID_LENGTH,
+                       .buf    = buf,
+               }
+       };
+
+       if (!buf) {
+               dev_warn(&adapter->dev, "unable to allocate memory for EDID "
+                        "block.\n");
+               return NULL;
+       }
+
+       if (i2c_transfer(adapter, msgs, 2) == 2)
+               return buf;
+
+       dev_info(&adapter->dev, "unable to read EDID block.\n");
+       kfree(buf);
+       return NULL;
+}
+EXPORT_SYMBOL(drm_do_probe_ddc_edid);
+
+static unsigned char *drm_ddc_read(struct i2c_adapter *adapter)
+{
+       struct i2c_algo_bit_data *algo_data = adapter->algo_data;
+       unsigned char *edid = NULL;
+       int i, j;
+
+       algo_data->setscl(algo_data->data, 1);
+
+       for (i = 0; i < 1; i++) {
+               /* For some old monitors we need the
+                * following process to initialize/stop DDC
+                */
+               algo_data->setsda(algo_data->data, 1);
+               msleep(13);
+
+               algo_data->setscl(algo_data->data, 1);
+               for (j = 0; j < 5; j++) {
+                       msleep(10);
+                       if (algo_data->getscl(algo_data->data))
+                               break;
+               }
+               if (j == 5)
+                       continue;
+
+               algo_data->setsda(algo_data->data, 0);
+               msleep(15);
+               algo_data->setscl(algo_data->data, 0);
+               msleep(15);
+               algo_data->setsda(algo_data->data, 1);
+               msleep(15);
+
+               /* Do the real work */
+               edid = drm_do_probe_ddc_edid(adapter);
+               algo_data->setsda(algo_data->data, 0);
+               algo_data->setscl(algo_data->data, 0);
+               msleep(15);
+
+               algo_data->setscl(algo_data->data, 1);
+               for (j = 0; j < 10; j++) {
+                       msleep(10);
+                       if (algo_data->getscl(algo_data->data))
+                               break;
+               }
+
+               algo_data->setsda(algo_data->data, 1);
+               msleep(15);
+               algo_data->setscl(algo_data->data, 0);
+               algo_data->setsda(algo_data->data, 0);
+               if (edid)
+                       break;
+       }
+       /* Release the DDC lines when done or the Apple Cinema HD display
+        * will switch off
+        */
+       algo_data->setsda(algo_data->data, 1);
+       algo_data->setscl(algo_data->data, 1);
+
+       return edid;
+}
+
+/**
+ * drm_get_edid - get EDID data, if available
+ * @connector: connector we're probing
+ * @adapter: i2c adapter to use for DDC
+ *
+ * Poke the given connector's i2c channel to grab EDID data if possible.
+ *
+ * Return edid data or NULL if we couldn't find any.
+ */
+struct edid *drm_get_edid(struct drm_connector *connector,
+                         struct i2c_adapter *adapter)
+{
+       struct edid *edid;
+
+       edid = (struct edid *)drm_ddc_read(adapter);
+       if (!edid) {
+               dev_warn(&connector->dev->pdev->dev, "%s: no EDID data\n",
+                        drm_get_connector_name(connector));
+               return NULL;
+       }
+       if (!edid_is_valid(edid)) {
+               dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n",
+                        drm_get_connector_name(connector));
+               kfree(edid);
+               return NULL;
+       }
+
+       connector->display_info.raw_edid = (char *)edid;
+
+       return edid;
+}
+EXPORT_SYMBOL(drm_get_edid);
+
+/**
+ * drm_add_edid_modes - add modes from EDID data, if available
+ * @connector: connector we're probing
+ * @edid: edid data
+ *
+ * Add the specified modes to the connector's mode list.
+ *
+ * Return number of modes added or 0 if we couldn't find any.
+ */
+int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid)
+{
+       int num_modes = 0;
+       u32 quirks;
+
+       if (edid == NULL) {
+               return 0;
+       }
+       if (!edid_is_valid(edid)) {
+               dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n",
+                        drm_get_connector_name(connector));
+               return 0;
+       }
+
+       quirks = edid_get_quirks(edid);
+
+       num_modes += add_established_modes(connector, edid);
+       num_modes += add_standard_modes(connector, edid);
+       num_modes += add_detailed_info(connector, edid, quirks);
+
+       if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75))
+               edid_fixup_preferred(connector, quirks);
+
+       connector->display_info.serration_vsync = edid->serration_vsync;
+       connector->display_info.sync_on_green = edid->sync_on_green;
+       connector->display_info.composite_sync = edid->composite_sync;
+       connector->display_info.separate_syncs = edid->separate_syncs;
+       connector->display_info.blank_to_black = edid->blank_to_black;
+       connector->display_info.video_level = edid->video_level;
+       connector->display_info.digital = edid->digital;
+       connector->display_info.width_mm = edid->width_cm * 10;
+       connector->display_info.height_mm = edid->height_cm * 10;
+       connector->display_info.gamma = edid->gamma;
+       connector->display_info.gtf_supported = edid->default_gtf;
+       connector->display_info.standard_color = edid->standard_color;
+       connector->display_info.display_type = edid->display_type;
+       connector->display_info.active_off_supported = edid->pm_active_off;
+       connector->display_info.suspend_supported = edid->pm_suspend;
+       connector->display_info.standby_supported = edid->pm_standby;
+       connector->display_info.gamma = edid->gamma;
+
+       return num_modes;
+}
+EXPORT_SYMBOL(drm_add_edid_modes);
index 78eeed5caaff1ed68ed01cb2cd27a2ed156caa49..3733e36d135ed2700e221fb6609a0b8809c481c5 100644 (file)
@@ -35,7 +35,6 @@
  */
 
 #include "drmP.h"
-#include "drm_sarea.h"
 #include <linux/poll.h>
 #include <linux/smp_lock.h>
 
@@ -44,10 +43,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
 
 static int drm_setup(struct drm_device * dev)
 {
-       drm_local_map_t *map;
        int i;
        int ret;
-       u32 sareapage;
 
        if (dev->driver->firstopen) {
                ret = dev->driver->firstopen(dev);
@@ -55,20 +52,14 @@ static int drm_setup(struct drm_device * dev)
                        return ret;
        }
 
-       dev->magicfree.next = NULL;
-
-       /* prebuild the SAREA */
-       sareapage = max_t(unsigned, SAREA_MAX, PAGE_SIZE);
-       i = drm_addmap(dev, 0, sareapage, _DRM_SHM, _DRM_CONTAINS_LOCK, &map);
-       if (i != 0)
-               return i;
-
        atomic_set(&dev->ioctl_count, 0);
        atomic_set(&dev->vma_count, 0);
-       dev->buf_use = 0;
-       atomic_set(&dev->buf_alloc, 0);
 
-       if (drm_core_check_feature(dev, DRIVER_HAVE_DMA)) {
+       if (drm_core_check_feature(dev, DRIVER_HAVE_DMA) &&
+           !drm_core_check_feature(dev, DRIVER_MODESET)) {
+               dev->buf_use = 0;
+               atomic_set(&dev->buf_alloc, 0);
+
                i = drm_dma_setup(dev);
                if (i < 0)
                        return i;
@@ -77,16 +68,12 @@ static int drm_setup(struct drm_device * dev)
        for (i = 0; i < ARRAY_SIZE(dev->counts); i++)
                atomic_set(&dev->counts[i], 0);
 
-       drm_ht_create(&dev->magiclist, DRM_MAGIC_HASH_ORDER);
-       INIT_LIST_HEAD(&dev->magicfree);
-
        dev->sigdata.lock = NULL;
-       init_waitqueue_head(&dev->lock.lock_queue);
+
        dev->queue_count = 0;
        dev->queue_reserved = 0;
        dev->queue_slots = 0;
        dev->queuelist = NULL;
-       dev->irq_enabled = 0;
        dev->context_flag = 0;
        dev->interrupt_flag = 0;
        dev->dma_flag = 0;
@@ -147,10 +134,20 @@ int drm_open(struct inode *inode, struct file *filp)
                spin_lock(&dev->count_lock);
                if (!dev->open_count++) {
                        spin_unlock(&dev->count_lock);
-                       return drm_setup(dev);
+                       retcode = drm_setup(dev);
+                       goto out;
                }
                spin_unlock(&dev->count_lock);
        }
+out:
+       mutex_lock(&dev->struct_mutex);
+       if (minor->type == DRM_MINOR_LEGACY) {
+               BUG_ON((dev->dev_mapping != NULL) &&
+                       (dev->dev_mapping != inode->i_mapping));
+               if (dev->dev_mapping == NULL)
+                       dev->dev_mapping = inode->i_mapping;
+       }
+       mutex_unlock(&dev->struct_mutex);
 
        return retcode;
 }
@@ -255,6 +252,7 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
        priv->lock_count = 0;
 
        INIT_LIST_HEAD(&priv->lhead);
+       INIT_LIST_HEAD(&priv->fbs);
 
        if (dev->driver->driver_features & DRIVER_GEM)
                drm_gem_open(dev, priv);
@@ -265,10 +263,42 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
                        goto out_free;
        }
 
+
+       /* if there is no current master make this fd it */
        mutex_lock(&dev->struct_mutex);
-       if (list_empty(&dev->filelist))
-               priv->master = 1;
+       if (!priv->minor->master) {
+               /* create a new master */
+               priv->minor->master = drm_master_create(priv->minor);
+               if (!priv->minor->master) {
+                       ret = -ENOMEM;
+                       goto out_free;
+               }
 
+               priv->is_master = 1;
+               /* take another reference for the copy in the local file priv */
+               priv->master = drm_master_get(priv->minor->master);
+
+               priv->authenticated = 1;
+
+               mutex_unlock(&dev->struct_mutex);
+               if (dev->driver->master_create) {
+                       ret = dev->driver->master_create(dev, priv->master);
+                       if (ret) {
+                               mutex_lock(&dev->struct_mutex);
+                               /* drop both references if this fails */
+                               drm_master_put(&priv->minor->master);
+                               drm_master_put(&priv->master);
+                               mutex_unlock(&dev->struct_mutex);
+                               goto out_free;
+                       }
+               }
+       } else {
+               /* get a reference to the master */
+               priv->master = drm_master_get(priv->minor->master);
+               mutex_unlock(&dev->struct_mutex);
+       }
+
+       mutex_lock(&dev->struct_mutex);
        list_add(&priv->lhead, &dev->filelist);
        mutex_unlock(&dev->struct_mutex);
 
@@ -314,6 +344,74 @@ int drm_fasync(int fd, struct file *filp, int on)
 }
 EXPORT_SYMBOL(drm_fasync);
 
+/*
+ * Reclaim locked buffers; note that this may be a bad idea if the current
+ * context doesn't have the hw lock...
+ */
+static void drm_reclaim_locked_buffers(struct drm_device *dev, struct file *f)
+{
+       struct drm_file *file_priv = f->private_data;
+
+       if (drm_i_have_hw_lock(dev, file_priv)) {
+               dev->driver->reclaim_buffers_locked(dev, file_priv);
+       } else {
+               unsigned long _end = jiffies + 3 * DRM_HZ;
+               int locked = 0;
+
+               drm_idlelock_take(&file_priv->master->lock);
+
+               /*
+                * Wait for a while.
+                */
+               do {
+                       spin_lock_bh(&file_priv->master->lock.spinlock);
+                       locked = file_priv->master->lock.idle_has_lock;
+                       spin_unlock_bh(&file_priv->master->lock.spinlock);
+                       if (locked)
+                               break;
+                       schedule();
+               } while (!time_after_eq(jiffies, _end));
+
+               if (!locked) {
+                       DRM_ERROR("reclaim_buffers_locked() deadlock. Please rework this\n"
+                                 "\tdriver to use reclaim_buffers_idlelocked() instead.\n"
+                                 "\tI will go on reclaiming the buffers anyway.\n");
+               }
+
+               dev->driver->reclaim_buffers_locked(dev, file_priv);
+               drm_idlelock_release(&file_priv->master->lock);
+       }
+}
+
+static void drm_master_release(struct drm_device *dev, struct file *filp)
+{
+       struct drm_file *file_priv = filp->private_data;
+
+       if (dev->driver->reclaim_buffers_locked &&
+           file_priv->master->lock.hw_lock)
+               drm_reclaim_locked_buffers(dev, filp);
+
+       if (dev->driver->reclaim_buffers_idlelocked &&
+           file_priv->master->lock.hw_lock) {
+               drm_idlelock_take(&file_priv->master->lock);
+               dev->driver->reclaim_buffers_idlelocked(dev, file_priv);
+               drm_idlelock_release(&file_priv->master->lock);
+       }
+
+
+       if (drm_i_have_hw_lock(dev, file_priv)) {
+               DRM_DEBUG("File %p released, freeing lock for context %d\n",
+                         filp, _DRM_LOCKING_CONTEXT(file_priv->master->lock.hw_lock->lock));
+               drm_lock_free(&file_priv->master->lock,
+                             _DRM_LOCKING_CONTEXT(file_priv->master->lock.hw_lock->lock));
+       }
+
+       if (drm_core_check_feature(dev, DRIVER_HAVE_DMA) &&
+           !dev->driver->reclaim_buffers_locked) {
+               dev->driver->reclaim_buffers(dev, file_priv);
+       }
+}
+
 /**
  * Release file.
  *
@@ -348,60 +446,9 @@ int drm_release(struct inode *inode, struct file *filp)
                  (long)old_encode_dev(file_priv->minor->device),
                  dev->open_count);
 
-       if (dev->driver->reclaim_buffers_locked && dev->lock.hw_lock) {
-               if (drm_i_have_hw_lock(dev, file_priv)) {
-                       dev->driver->reclaim_buffers_locked(dev, file_priv);
-               } else {
-                       unsigned long endtime = jiffies + 3 * DRM_HZ;
-                       int locked = 0;
-
-                       drm_idlelock_take(&dev->lock);
-
-                       /*
-                        * Wait for a while.
-                        */
-
-                       do{
-                               spin_lock_bh(&dev->lock.spinlock);
-                               locked = dev->lock.idle_has_lock;
-                               spin_unlock_bh(&dev->lock.spinlock);
-                               if (locked)
-                                       break;
-                               schedule();
-                       } while (!time_after_eq(jiffies, endtime));
-
-                       if (!locked) {
-                               DRM_ERROR("reclaim_buffers_locked() deadlock. Please rework this\n"
-                                         "\tdriver to use reclaim_buffers_idlelocked() instead.\n"
-                                         "\tI will go on reclaiming the buffers anyway.\n");
-                       }
-
-                       dev->driver->reclaim_buffers_locked(dev, file_priv);
-                       drm_idlelock_release(&dev->lock);
-               }
-       }
-
-       if (dev->driver->reclaim_buffers_idlelocked && dev->lock.hw_lock) {
-
-               drm_idlelock_take(&dev->lock);
-               dev->driver->reclaim_buffers_idlelocked(dev, file_priv);
-               drm_idlelock_release(&dev->lock);
-
-       }
-
-       if (drm_i_have_hw_lock(dev, file_priv)) {
-               DRM_DEBUG("File %p released, freeing lock for context %d\n",
-                         filp, _DRM_LOCKING_CONTEXT(dev->lock.hw_lock->lock));
-
-               drm_lock_free(&dev->lock,
-                             _DRM_LOCKING_CONTEXT(dev->lock.hw_lock->lock));
-       }
-
-
-       if (drm_core_check_feature(dev, DRIVER_HAVE_DMA) &&
-           !dev->driver->reclaim_buffers_locked) {
-               dev->driver->reclaim_buffers(dev, file_priv);
-       }
+       /* if the master has gone away we can't do anything with the lock */
+       if (file_priv->minor->master)
+               drm_master_release(dev, filp);
 
        if (dev->driver->driver_features & DRIVER_GEM)
                drm_gem_release(dev, file_priv);
@@ -428,12 +475,24 @@ int drm_release(struct inode *inode, struct file *filp)
        mutex_unlock(&dev->ctxlist_mutex);
 
        mutex_lock(&dev->struct_mutex);
-       if (file_priv->remove_auth_on_close == 1) {
+
+       if (file_priv->is_master) {
                struct drm_file *temp;
+               list_for_each_entry(temp, &dev->filelist, lhead) {
+                       if ((temp->master == file_priv->master) &&
+                           (temp != file_priv))
+                               temp->authenticated = 0;
+               }
 
-               list_for_each_entry(temp, &dev->filelist, lhead)
-                       temp->authenticated = 0;
+               if (file_priv->minor->master == file_priv->master) {
+                       /* drop the reference held my the minor */
+                       drm_master_put(&file_priv->minor->master);
+               }
        }
+
+       /* drop the reference held my the file priv */
+       drm_master_put(&file_priv->master);
+       file_priv->is_master = 0;
        list_del(&file_priv->lhead);
        mutex_unlock(&dev->struct_mutex);
 
@@ -448,9 +507,9 @@ int drm_release(struct inode *inode, struct file *filp)
        atomic_inc(&dev->counts[_DRM_STAT_CLOSES]);
        spin_lock(&dev->count_lock);
        if (!--dev->open_count) {
-               if (atomic_read(&dev->ioctl_count) || dev->blocked) {
-                       DRM_ERROR("Device busy: %d %d\n",
-                                 atomic_read(&dev->ioctl_count), dev->blocked);
+               if (atomic_read(&dev->ioctl_count)) {
+                       DRM_ERROR("Device busy: %d\n",
+                                 atomic_read(&dev->ioctl_count));
                        spin_unlock(&dev->count_lock);
                        unlock_kernel();
                        return -EBUSY;
index ccd1afdede0249f5109e614732d85e67bbfd8b4b..9da5814528749a82b4ef12a1b13134d34635557b 100644 (file)
  * up at a later date, and as our interface with shmfs for memory allocation.
  */
 
+/*
+ * We make up offsets for buffer objects so we can recognize them at
+ * mmap time.
+ */
+#define DRM_FILE_PAGE_OFFSET_START ((0xFFFFFFFFUL >> PAGE_SHIFT) + 1)
+#define DRM_FILE_PAGE_OFFSET_SIZE ((0xFFFFFFFFUL >> PAGE_SHIFT) * 16)
+
 /**
  * Initialize the GEM device fields
  */
@@ -71,6 +78,8 @@
 int
 drm_gem_init(struct drm_device *dev)
 {
+       struct drm_gem_mm *mm;
+
        spin_lock_init(&dev->object_name_lock);
        idr_init(&dev->object_name_idr);
        atomic_set(&dev->object_count, 0);
@@ -79,9 +88,41 @@ drm_gem_init(struct drm_device *dev)
        atomic_set(&dev->pin_memory, 0);
        atomic_set(&dev->gtt_count, 0);
        atomic_set(&dev->gtt_memory, 0);
+
+       mm = drm_calloc(1, sizeof(struct drm_gem_mm), DRM_MEM_MM);
+       if (!mm) {
+               DRM_ERROR("out of memory\n");
+               return -ENOMEM;
+       }
+
+       dev->mm_private = mm;
+
+       if (drm_ht_create(&mm->offset_hash, 19)) {
+               drm_free(mm, sizeof(struct drm_gem_mm), DRM_MEM_MM);
+               return -ENOMEM;
+       }
+
+       if (drm_mm_init(&mm->offset_manager, DRM_FILE_PAGE_OFFSET_START,
+                       DRM_FILE_PAGE_OFFSET_SIZE)) {
+               drm_free(mm, sizeof(struct drm_gem_mm), DRM_MEM_MM);
+               drm_ht_remove(&mm->offset_hash);
+               return -ENOMEM;
+       }
+
        return 0;
 }
 
+void
+drm_gem_destroy(struct drm_device *dev)
+{
+       struct drm_gem_mm *mm = dev->mm_private;
+
+       drm_mm_takedown(&mm->offset_manager);
+       drm_ht_remove(&mm->offset_hash);
+       drm_free(mm, sizeof(struct drm_gem_mm), DRM_MEM_MM);
+       dev->mm_private = NULL;
+}
+
 /**
  * Allocate a GEM object of the specified size with shmfs backing store
  */
@@ -419,3 +460,73 @@ drm_gem_object_handle_free(struct kref *kref)
 }
 EXPORT_SYMBOL(drm_gem_object_handle_free);
 
+/**
+ * drm_gem_mmap - memory map routine for GEM objects
+ * @filp: DRM file pointer
+ * @vma: VMA for the area to be mapped
+ *
+ * If a driver supports GEM object mapping, mmap calls on the DRM file
+ * descriptor will end up here.
+ *
+ * If we find the object based on the offset passed in (vma->vm_pgoff will
+ * contain the fake offset we created when the GTT map ioctl was called on
+ * the object), we set up the driver fault handler so that any accesses
+ * to the object can be trapped, to perform migration, GTT binding, surface
+ * register allocation, or performance monitoring.
+ */
+int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+       struct drm_file *priv = filp->private_data;
+       struct drm_device *dev = priv->minor->dev;
+       struct drm_gem_mm *mm = dev->mm_private;
+       struct drm_map *map = NULL;
+       struct drm_gem_object *obj;
+       struct drm_hash_item *hash;
+       unsigned long prot;
+       int ret = 0;
+
+       mutex_lock(&dev->struct_mutex);
+
+       if (drm_ht_find_item(&mm->offset_hash, vma->vm_pgoff, &hash)) {
+               mutex_unlock(&dev->struct_mutex);
+               return drm_mmap(filp, vma);
+       }
+
+       map = drm_hash_entry(hash, struct drm_map_list, hash)->map;
+       if (!map ||
+           ((map->flags & _DRM_RESTRICTED) && !capable(CAP_SYS_ADMIN))) {
+               ret =  -EPERM;
+               goto out_unlock;
+       }
+
+       /* Check for valid size. */
+       if (map->size < vma->vm_end - vma->vm_start) {
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       obj = map->handle;
+       if (!obj->dev->driver->gem_vm_ops) {
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       vma->vm_flags |= VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND;
+       vma->vm_ops = obj->dev->driver->gem_vm_ops;
+       vma->vm_private_data = map->handle;
+       /* FIXME: use pgprot_writecombine when available */
+       prot = pgprot_val(vma->vm_page_prot);
+#ifdef CONFIG_X86
+       prot |= _PAGE_CACHE_WC;
+#endif
+       vma->vm_page_prot = __pgprot(prot);
+
+       vma->vm_file = filp;    /* Needed for drm_vm_open() */
+       drm_vm_open_locked(vma);
+
+out_unlock:
+       mutex_unlock(&dev->struct_mutex);
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_gem_mmap);
index 33160673a7b7846406400226665ae68c409e5e73..af539f7d87dd2639bafe0c1576e069d4c971da1e 100644 (file)
@@ -127,6 +127,7 @@ int drm_ht_insert_item(struct drm_open_hash *ht, struct drm_hash_item *item)
        }
        return 0;
 }
+EXPORT_SYMBOL(drm_ht_insert_item);
 
 /*
  * Just insert an item and return any "bits" bit key that hasn't been
@@ -188,6 +189,7 @@ int drm_ht_remove_item(struct drm_open_hash *ht, struct drm_hash_item *item)
        ht->fill--;
        return 0;
 }
+EXPORT_SYMBOL(drm_ht_remove_item);
 
 void drm_ht_remove(struct drm_open_hash *ht)
 {
index 16829fb3089d8adbfadda119d427e65bcaa8157c..1fad76289e665d0cfbc6f7026f6f568ff6809a53 100644 (file)
@@ -53,12 +53,13 @@ int drm_getunique(struct drm_device *dev, void *data,
                  struct drm_file *file_priv)
 {
        struct drm_unique *u = data;
+       struct drm_master *master = file_priv->master;
 
-       if (u->unique_len >= dev->unique_len) {
-               if (copy_to_user(u->unique, dev->unique, dev->unique_len))
+       if (u->unique_len >= master->unique_len) {
+               if (copy_to_user(u->unique, master->unique, master->unique_len))
                        return -EFAULT;
        }
-       u->unique_len = dev->unique_len;
+       u->unique_len = master->unique_len;
 
        return 0;
 }
@@ -81,36 +82,38 @@ int drm_setunique(struct drm_device *dev, void *data,
                  struct drm_file *file_priv)
 {
        struct drm_unique *u = data;
+       struct drm_master *master = file_priv->master;
        int domain, bus, slot, func, ret;
 
-       if (dev->unique_len || dev->unique)
+       if (master->unique_len || master->unique)
                return -EBUSY;
 
        if (!u->unique_len || u->unique_len > 1024)
                return -EINVAL;
 
-       dev->unique_len = u->unique_len;
-       dev->unique = drm_alloc(u->unique_len + 1, DRM_MEM_DRIVER);
-       if (!dev->unique)
+       master->unique_len = u->unique_len;
+       master->unique_size = u->unique_len + 1;
+       master->unique = drm_alloc(master->unique_size, DRM_MEM_DRIVER);
+       if (!master->unique)
                return -ENOMEM;
-       if (copy_from_user(dev->unique, u->unique, dev->unique_len))
+       if (copy_from_user(master->unique, u->unique, master->unique_len))
                return -EFAULT;
 
-       dev->unique[dev->unique_len] = '\0';
+       master->unique[master->unique_len] = '\0';
 
        dev->devname =
            drm_alloc(strlen(dev->driver->pci_driver.name) +
-                     strlen(dev->unique) + 2, DRM_MEM_DRIVER);
+                     strlen(master->unique) + 2, DRM_MEM_DRIVER);
        if (!dev->devname)
                return -ENOMEM;
 
        sprintf(dev->devname, "%s@%s", dev->driver->pci_driver.name,
-               dev->unique);
+               master->unique);
 
        /* Return error if the busid submitted doesn't match the device's actual
         * busid.
         */
-       ret = sscanf(dev->unique, "PCI:%d:%d:%d", &bus, &slot, &func);
+       ret = sscanf(master->unique, "PCI:%d:%d:%d", &bus, &slot, &func);
        if (ret != 3)
                return -EINVAL;
        domain = bus >> 8;
@@ -125,34 +128,38 @@ int drm_setunique(struct drm_device *dev, void *data,
        return 0;
 }
 
-static int drm_set_busid(struct drm_device * dev)
+static int drm_set_busid(struct drm_device *dev, struct drm_file *file_priv)
 {
+       struct drm_master *master = file_priv->master;
        int len;
 
-       if (dev->unique != NULL)
-               return 0;
+       if (master->unique != NULL)
+               return -EBUSY;
 
-       dev->unique_len = 40;
-       dev->unique = drm_alloc(dev->unique_len + 1, DRM_MEM_DRIVER);
-       if (dev->unique == NULL)
+       master->unique_len = 40;
+       master->unique_size = master->unique_len;
+       master->unique = drm_alloc(master->unique_size, DRM_MEM_DRIVER);
+       if (master->unique == NULL)
                return -ENOMEM;
 
-       len = snprintf(dev->unique, dev->unique_len, "pci:%04x:%02x:%02x.%d",
-                      drm_get_pci_domain(dev), dev->pdev->bus->number,
+       len = snprintf(master->unique, master->unique_len, "pci:%04x:%02x:%02x.%d",
+                      drm_get_pci_domain(dev),
+                      dev->pdev->bus->number,
                       PCI_SLOT(dev->pdev->devfn),
                       PCI_FUNC(dev->pdev->devfn));
-
-       if (len > dev->unique_len)
-               DRM_ERROR("Unique buffer overflowed\n");
+       if (len >= master->unique_len)
+               DRM_ERROR("buffer overflow");
+       else
+               master->unique_len = len;
 
        dev->devname =
-           drm_alloc(strlen(dev->driver->pci_driver.name) + dev->unique_len +
+           drm_alloc(strlen(dev->driver->pci_driver.name) + master->unique_len +
                      2, DRM_MEM_DRIVER);
        if (dev->devname == NULL)
                return -ENOMEM;
 
        sprintf(dev->devname, "%s@%s", dev->driver->pci_driver.name,
-               dev->unique);
+               master->unique);
 
        return 0;
 }
@@ -276,7 +283,7 @@ int drm_getstats(struct drm_device *dev, void *data,
        for (i = 0; i < dev->counters; i++) {
                if (dev->types[i] == _DRM_STAT_LOCK)
                        stats->data[i].value =
-                           (dev->lock.hw_lock ? dev->lock.hw_lock->lock : 0);
+                           (file_priv->master->lock.hw_lock ? file_priv->master->lock.hw_lock->lock : 0);
                else
                        stats->data[i].value = atomic_read(&dev->counts[i]);
                stats->data[i].type = dev->types[i];
@@ -318,7 +325,7 @@ int drm_setversion(struct drm_device *dev, void *data, struct drm_file *file_pri
                        /*
                         * Version 1.1 includes tying of DRM to specific device
                         */
-                       drm_set_busid(dev);
+                       drm_set_busid(dev, file_priv);
                }
        }
 
index 1e787f894b3cf38f6c46eeda39c73cb8a49ad1c5..724e505873cf19e77c6710968a1a8ba6888cedd2 100644 (file)
@@ -116,6 +116,9 @@ void drm_vblank_cleanup(struct drm_device *dev)
                 dev->num_crtcs, DRM_MEM_DRIVER);
        drm_free(dev->last_vblank, sizeof(*dev->last_vblank) * dev->num_crtcs,
                 DRM_MEM_DRIVER);
+       drm_free(dev->last_vblank_wait,
+                sizeof(*dev->last_vblank_wait) * dev->num_crtcs,
+                DRM_MEM_DRIVER);
        drm_free(dev->vblank_inmodeset, sizeof(*dev->vblank_inmodeset) *
                 dev->num_crtcs, DRM_MEM_DRIVER);
 
@@ -161,6 +164,11 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs)
        if (!dev->last_vblank)
                goto err;
 
+       dev->last_vblank_wait = drm_calloc(num_crtcs, sizeof(u32),
+                                          DRM_MEM_DRIVER);
+       if (!dev->last_vblank_wait)
+               goto err;
+
        dev->vblank_inmodeset = drm_calloc(num_crtcs, sizeof(int),
                                         DRM_MEM_DRIVER);
        if (!dev->vblank_inmodeset)
@@ -305,6 +313,8 @@ int drm_control(struct drm_device *dev, void *data,
        case DRM_INST_HANDLER:
                if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
                        return 0;
+               if (drm_core_check_feature(dev, DRIVER_MODESET))
+                       return 0;
                if (dev->if_version < DRM_IF_VERSION(1, 2) &&
                    ctl->irq != dev->pdev->irq)
                        return -EINVAL;
@@ -312,6 +322,8 @@ int drm_control(struct drm_device *dev, void *data,
        case DRM_UNINST_HANDLER:
                if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
                        return 0;
+               if (drm_core_check_feature(dev, DRIVER_MODESET))
+                       return 0;
                return drm_irq_uninstall(dev);
        default:
                return -EINVAL;
@@ -426,6 +438,45 @@ void drm_vblank_put(struct drm_device *dev, int crtc)
 }
 EXPORT_SYMBOL(drm_vblank_put);
 
+/**
+ * drm_vblank_pre_modeset - account for vblanks across mode sets
+ * @dev: DRM device
+ * @crtc: CRTC in question
+ * @post: post or pre mode set?
+ *
+ * Account for vblank events across mode setting events, which will likely
+ * reset the hardware frame counter.
+ */
+void drm_vblank_pre_modeset(struct drm_device *dev, int crtc)
+{
+       /*
+        * To avoid all the problems that might happen if interrupts
+        * were enabled/disabled around or between these calls, we just
+        * have the kernel take a reference on the CRTC (just once though
+        * to avoid corrupting the count if multiple, mismatch calls occur),
+        * so that interrupts remain enabled in the interim.
+        */
+       if (!dev->vblank_inmodeset[crtc]) {
+               dev->vblank_inmodeset[crtc] = 1;
+               drm_vblank_get(dev, crtc);
+       }
+}
+EXPORT_SYMBOL(drm_vblank_pre_modeset);
+
+void drm_vblank_post_modeset(struct drm_device *dev, int crtc)
+{
+       unsigned long irqflags;
+
+       if (dev->vblank_inmodeset[crtc]) {
+               spin_lock_irqsave(&dev->vbl_lock, irqflags);
+               dev->vblank_disable_allowed = 1;
+               dev->vblank_inmodeset[crtc] = 0;
+               spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
+               drm_vblank_put(dev, crtc);
+       }
+}
+EXPORT_SYMBOL(drm_vblank_post_modeset);
+
 /**
  * drm_modeset_ctl - handle vblank event counter changes across mode switch
  * @DRM_IOCTL_ARGS: standard ioctl arguments
@@ -441,7 +492,6 @@ int drm_modeset_ctl(struct drm_device *dev, void *data,
                    struct drm_file *file_priv)
 {
        struct drm_modeset_ctl *modeset = data;
-       unsigned long irqflags;
        int crtc, ret = 0;
 
        /* If drm_vblank_init() hasn't been called yet, just no-op */
@@ -454,28 +504,12 @@ int drm_modeset_ctl(struct drm_device *dev, void *data,
                goto out;
        }
 
-       /*
-        * To avoid all the problems that might happen if interrupts
-        * were enabled/disabled around or between these calls, we just
-        * have the kernel take a reference on the CRTC (just once though
-        * to avoid corrupting the count if multiple, mismatch calls occur),
-        * so that interrupts remain enabled in the interim.
-        */
        switch (modeset->cmd) {
        case _DRM_PRE_MODESET:
-               if (!dev->vblank_inmodeset[crtc]) {
-                       dev->vblank_inmodeset[crtc] = 1;
-                       drm_vblank_get(dev, crtc);
-               }
+               drm_vblank_pre_modeset(dev, crtc);
                break;
        case _DRM_POST_MODESET:
-               if (dev->vblank_inmodeset[crtc]) {
-                       spin_lock_irqsave(&dev->vbl_lock, irqflags);
-                       dev->vblank_disable_allowed = 1;
-                       dev->vblank_inmodeset[crtc] = 0;
-                       spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
-                       drm_vblank_put(dev, crtc);
-               }
+               drm_vblank_post_modeset(dev, crtc);
                break;
        default:
                ret = -EINVAL;
@@ -616,6 +650,7 @@ int drm_wait_vblank(struct drm_device *dev, void *data,
        } else {
                DRM_DEBUG("waiting on vblank count %d, crtc %d\n",
                          vblwait->request.sequence, crtc);
+               dev->last_vblank_wait[crtc] = vblwait->request.sequence;
                DRM_WAIT_ON(ret, dev->vbl_queue[crtc], 3 * DRM_HZ,
                            ((drm_vblank_count(dev, crtc)
                              - vblwait->request.sequence) <= (1 << 23)));
index 1cfa72031f8f6291f18fce595c0c7e179cea07d5..46e7b28f0707397d545948dc21bbcc272f16eb6f 100644 (file)
@@ -52,6 +52,7 @@ int drm_lock(struct drm_device *dev, void *data, struct drm_file *file_priv)
 {
        DECLARE_WAITQUEUE(entry, current);
        struct drm_lock *lock = data;
+       struct drm_master *master = file_priv->master;
        int ret = 0;
 
        ++file_priv->lock_count;
@@ -64,26 +65,27 @@ int drm_lock(struct drm_device *dev, void *data, struct drm_file *file_priv)
 
        DRM_DEBUG("%d (pid %d) requests lock (0x%08x), flags = 0x%08x\n",
                  lock->context, task_pid_nr(current),
-                 dev->lock.hw_lock->lock, lock->flags);
+                 master->lock.hw_lock->lock, lock->flags);
 
        if (drm_core_check_feature(dev, DRIVER_DMA_QUEUE))
                if (lock->context < 0)
                        return -EINVAL;
 
-       add_wait_queue(&dev->lock.lock_queue, &entry);
-       spin_lock_bh(&dev->lock.spinlock);
-       dev->lock.user_waiters++;
-       spin_unlock_bh(&dev->lock.spinlock);
+       add_wait_queue(&master->lock.lock_queue, &entry);
+       spin_lock_bh(&master->lock.spinlock);
+       master->lock.user_waiters++;
+       spin_unlock_bh(&master->lock.spinlock);
+
        for (;;) {
                __set_current_state(TASK_INTERRUPTIBLE);
-               if (!dev->lock.hw_lock) {
+               if (!master->lock.hw_lock) {
                        /* Device has been unregistered */
                        ret = -EINTR;
                        break;
                }
-               if (drm_lock_take(&dev->lock, lock->context)) {
-                       dev->lock.file_priv = file_priv;
-                       dev->lock.lock_time = jiffies;
+               if (drm_lock_take(&master->lock, lock->context)) {
+                       master->lock.file_priv = file_priv;
+                       master->lock.lock_time = jiffies;
                        atomic_inc(&dev->counts[_DRM_STAT_LOCKS]);
                        break;  /* Got lock */
                }
@@ -95,11 +97,11 @@ int drm_lock(struct drm_device *dev, void *data, struct drm_file *file_priv)
                        break;
                }
        }
-       spin_lock_bh(&dev->lock.spinlock);
-       dev->lock.user_waiters--;
-       spin_unlock_bh(&dev->lock.spinlock);
+       spin_lock_bh(&master->lock.spinlock);
+       master->lock.user_waiters--;
+       spin_unlock_bh(&master->lock.spinlock);
        __set_current_state(TASK_RUNNING);
-       remove_wait_queue(&dev->lock.lock_queue, &entry);
+       remove_wait_queue(&master->lock.lock_queue, &entry);
 
        DRM_DEBUG("%d %s\n", lock->context,
                  ret ? "interrupted" : "has lock");
@@ -108,14 +110,14 @@ int drm_lock(struct drm_device *dev, void *data, struct drm_file *file_priv)
        /* don't set the block all signals on the master process for now 
         * really probably not the correct answer but lets us debug xkb
         * xserver for now */
-       if (!file_priv->master) {
+       if (!file_priv->is_master) {
                sigemptyset(&dev->sigmask);
                sigaddset(&dev->sigmask, SIGSTOP);
                sigaddset(&dev->sigmask, SIGTSTP);
                sigaddset(&dev->sigmask, SIGTTIN);
                sigaddset(&dev->sigmask, SIGTTOU);
                dev->sigdata.context = lock->context;
-               dev->sigdata.lock = dev->lock.hw_lock;
+               dev->sigdata.lock = master->lock.hw_lock;
                block_all_signals(drm_notifier, &dev->sigdata, &dev->sigmask);
        }
 
@@ -154,6 +156,7 @@ int drm_lock(struct drm_device *dev, void *data, struct drm_file *file_priv)
 int drm_unlock(struct drm_device *dev, void *data, struct drm_file *file_priv)
 {
        struct drm_lock *lock = data;
+       struct drm_master *master = file_priv->master;
 
        if (lock->context == DRM_KERNEL_CONTEXT) {
                DRM_ERROR("Process %d using kernel context %d\n",
@@ -169,7 +172,7 @@ int drm_unlock(struct drm_device *dev, void *data, struct drm_file *file_priv)
        if (dev->driver->kernel_context_switch_unlock)
                dev->driver->kernel_context_switch_unlock(dev);
        else {
-               if (drm_lock_free(&dev->lock,lock->context)) {
+               if (drm_lock_free(&master->lock, lock->context)) {
                        /* FIXME: Should really bail out here. */
                }
        }
@@ -379,9 +382,10 @@ EXPORT_SYMBOL(drm_idlelock_release);
 
 int drm_i_have_hw_lock(struct drm_device *dev, struct drm_file *file_priv)
 {
-       return (file_priv->lock_count && dev->lock.hw_lock &&
-               _DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock) &&
-               dev->lock.file_priv == file_priv);
+       struct drm_master *master = file_priv->master;
+       return (file_priv->lock_count && master->lock.hw_lock &&
+               _DRM_LOCK_IS_HELD(master->lock.hw_lock->lock) &&
+               master->lock.file_priv == file_priv);
 }
 
 EXPORT_SYMBOL(drm_i_have_hw_lock);
index 217ad7dc70765ef174ad159c8e769fa10392c531..367c590ffbba2d83b5f516d76cb6efacdd1baff1 100644 (file)
@@ -296,3 +296,4 @@ void drm_mm_takedown(struct drm_mm * mm)
 
        drm_free(entry, sizeof(*entry), DRM_MEM_MM);
 }
+EXPORT_SYMBOL(drm_mm_takedown);
diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c
new file mode 100644 (file)
index 0000000..c9b80fd
--- /dev/null
@@ -0,0 +1,576 @@
+/*
+ * The list_sort function is (presumably) licensed under the GPL (see the
+ * top level "COPYING" file for details).
+ *
+ * The remainder of this file is:
+ *
+ * Copyright © 1997-2003 by The XFree86 Project, Inc.
+ * Copyright © 2007 Dave Airlie
+ * Copyright © 2007-2008 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 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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.
+ *
+ * Except as contained in this notice, the name of the copyright holder(s)
+ * and author(s) shall not be used in advertising or otherwise to promote
+ * the sale, use or other dealings in this Software without prior written
+ * authorization from the copyright holder(s) and author(s).
+ */
+
+#include <linux/list.h>
+#include "drmP.h"
+#include "drm.h"
+#include "drm_crtc.h"
+
+/**
+ * drm_mode_debug_printmodeline - debug print a mode
+ * @dev: DRM device
+ * @mode: mode to print
+ *
+ * LOCKING:
+ * None.
+ *
+ * Describe @mode using DRM_DEBUG.
+ */
+void drm_mode_debug_printmodeline(struct drm_display_mode *mode)
+{
+       DRM_DEBUG("Modeline %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x\n",
+                 mode->base.id, mode->name, mode->vrefresh, mode->clock,
+                 mode->hdisplay, mode->hsync_start,
+                 mode->hsync_end, mode->htotal,
+                 mode->vdisplay, mode->vsync_start,
+                 mode->vsync_end, mode->vtotal, mode->type, mode->flags);
+}
+EXPORT_SYMBOL(drm_mode_debug_printmodeline);
+
+/**
+ * drm_mode_set_name - set the name on a mode
+ * @mode: name will be set in this mode
+ *
+ * LOCKING:
+ * None.
+ *
+ * Set the name of @mode to a standard format.
+ */
+void drm_mode_set_name(struct drm_display_mode *mode)
+{
+       snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d", mode->hdisplay,
+                mode->vdisplay);
+}
+EXPORT_SYMBOL(drm_mode_set_name);
+
+/**
+ * drm_mode_list_concat - move modes from one list to another
+ * @head: source list
+ * @new: dst list
+ *
+ * LOCKING:
+ * Caller must ensure both lists are locked.
+ *
+ * Move all the modes from @head to @new.
+ */
+void drm_mode_list_concat(struct list_head *head, struct list_head *new)
+{
+
+       struct list_head *entry, *tmp;
+
+       list_for_each_safe(entry, tmp, head) {
+               list_move_tail(entry, new);
+       }
+}
+EXPORT_SYMBOL(drm_mode_list_concat);
+
+/**
+ * drm_mode_width - get the width of a mode
+ * @mode: mode
+ *
+ * LOCKING:
+ * None.
+ *
+ * Return @mode's width (hdisplay) value.
+ *
+ * FIXME: is this needed?
+ *
+ * RETURNS:
+ * @mode->hdisplay
+ */
+int drm_mode_width(struct drm_display_mode *mode)
+{
+       return mode->hdisplay;
+
+}
+EXPORT_SYMBOL(drm_mode_width);
+
+/**
+ * drm_mode_height - get the height of a mode
+ * @mode: mode
+ *
+ * LOCKING:
+ * None.
+ *
+ * Return @mode's height (vdisplay) value.
+ *
+ * FIXME: is this needed?
+ *
+ * RETURNS:
+ * @mode->vdisplay
+ */
+int drm_mode_height(struct drm_display_mode *mode)
+{
+       return mode->vdisplay;
+}
+EXPORT_SYMBOL(drm_mode_height);
+
+/**
+ * drm_mode_vrefresh - get the vrefresh of a mode
+ * @mode: mode
+ *
+ * LOCKING:
+ * None.
+ *
+ * Return @mode's vrefresh rate or calculate it if necessary.
+ *
+ * FIXME: why is this needed?  shouldn't vrefresh be set already?
+ *
+ * RETURNS:
+ * Vertical refresh rate of @mode x 1000. For precision reasons.
+ */
+int drm_mode_vrefresh(struct drm_display_mode *mode)
+{
+       int refresh = 0;
+       unsigned int calc_val;
+
+       if (mode->vrefresh > 0)
+               refresh = mode->vrefresh;
+       else if (mode->htotal > 0 && mode->vtotal > 0) {
+               /* work out vrefresh the value will be x1000 */
+               calc_val = (mode->clock * 1000);
+
+               calc_val /= mode->htotal;
+               calc_val *= 1000;
+               calc_val /= mode->vtotal;
+
+               refresh = calc_val;
+               if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+                       refresh *= 2;
+               if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
+                       refresh /= 2;
+               if (mode->vscan > 1)
+                       refresh /= mode->vscan;
+       }
+       return refresh;
+}
+EXPORT_SYMBOL(drm_mode_vrefresh);
+
+/**
+ * drm_mode_set_crtcinfo - set CRTC modesetting parameters
+ * @p: mode
+ * @adjust_flags: unused? (FIXME)
+ *
+ * LOCKING:
+ * None.
+ *
+ * Setup the CRTC modesetting parameters for @p, adjusting if necessary.
+ */
+void drm_mode_set_crtcinfo(struct drm_display_mode *p, int adjust_flags)
+{
+       if ((p == NULL) || ((p->type & DRM_MODE_TYPE_CRTC_C) == DRM_MODE_TYPE_BUILTIN))
+               return;
+
+       p->crtc_hdisplay = p->hdisplay;
+       p->crtc_hsync_start = p->hsync_start;
+       p->crtc_hsync_end = p->hsync_end;
+       p->crtc_htotal = p->htotal;
+       p->crtc_hskew = p->hskew;
+       p->crtc_vdisplay = p->vdisplay;
+       p->crtc_vsync_start = p->vsync_start;
+       p->crtc_vsync_end = p->vsync_end;
+       p->crtc_vtotal = p->vtotal;
+
+       if (p->flags & DRM_MODE_FLAG_INTERLACE) {
+               if (adjust_flags & CRTC_INTERLACE_HALVE_V) {
+                       p->crtc_vdisplay /= 2;
+                       p->crtc_vsync_start /= 2;
+                       p->crtc_vsync_end /= 2;
+                       p->crtc_vtotal /= 2;
+               }
+
+               p->crtc_vtotal |= 1;
+       }
+
+       if (p->flags & DRM_MODE_FLAG_DBLSCAN) {
+               p->crtc_vdisplay *= 2;
+               p->crtc_vsync_start *= 2;
+               p->crtc_vsync_end *= 2;
+               p->crtc_vtotal *= 2;
+       }
+
+       if (p->vscan > 1) {
+               p->crtc_vdisplay *= p->vscan;
+               p->crtc_vsync_start *= p->vscan;
+               p->crtc_vsync_end *= p->vscan;
+               p->crtc_vtotal *= p->vscan;
+       }
+
+       p->crtc_vblank_start = min(p->crtc_vsync_start, p->crtc_vdisplay);
+       p->crtc_vblank_end = max(p->crtc_vsync_end, p->crtc_vtotal);
+       p->crtc_hblank_start = min(p->crtc_hsync_start, p->crtc_hdisplay);
+       p->crtc_hblank_end = max(p->crtc_hsync_end, p->crtc_htotal);
+
+       p->crtc_hadjusted = false;
+       p->crtc_vadjusted = false;
+}
+EXPORT_SYMBOL(drm_mode_set_crtcinfo);
+
+
+/**
+ * drm_mode_duplicate - allocate and duplicate an existing mode
+ * @m: mode to duplicate
+ *
+ * LOCKING:
+ * None.
+ *
+ * Just allocate a new mode, copy the existing mode into it, and return
+ * a pointer to it.  Used to create new instances of established modes.
+ */
+struct drm_display_mode *drm_mode_duplicate(struct drm_device *dev,
+                                           struct drm_display_mode *mode)
+{
+       struct drm_display_mode *nmode;
+       int new_id;
+
+       nmode = drm_mode_create(dev);
+       if (!nmode)
+               return NULL;
+
+       new_id = nmode->base.id;
+       *nmode = *mode;
+       nmode->base.id = new_id;
+       INIT_LIST_HEAD(&nmode->head);
+       return nmode;
+}
+EXPORT_SYMBOL(drm_mode_duplicate);
+
+/**
+ * drm_mode_equal - test modes for equality
+ * @mode1: first mode
+ * @mode2: second mode
+ *
+ * LOCKING:
+ * None.
+ *
+ * Check to see if @mode1 and @mode2 are equivalent.
+ *
+ * RETURNS:
+ * True if the modes are equal, false otherwise.
+ */
+bool drm_mode_equal(struct drm_display_mode *mode1, struct drm_display_mode *mode2)
+{
+       /* do clock check convert to PICOS so fb modes get matched
+        * the same */
+       if (mode1->clock && mode2->clock) {
+               if (KHZ2PICOS(mode1->clock) != KHZ2PICOS(mode2->clock))
+                       return false;
+       } else if (mode1->clock != mode2->clock)
+               return false;
+
+       if (mode1->hdisplay == mode2->hdisplay &&
+           mode1->hsync_start == mode2->hsync_start &&
+           mode1->hsync_end == mode2->hsync_end &&
+           mode1->htotal == mode2->htotal &&
+           mode1->hskew == mode2->hskew &&
+           mode1->vdisplay == mode2->vdisplay &&
+           mode1->vsync_start == mode2->vsync_start &&
+           mode1->vsync_end == mode2->vsync_end &&
+           mode1->vtotal == mode2->vtotal &&
+           mode1->vscan == mode2->vscan &&
+           mode1->flags == mode2->flags)
+               return true;
+
+       return false;
+}
+EXPORT_SYMBOL(drm_mode_equal);
+
+/**
+ * drm_mode_validate_size - make sure modes adhere to size constraints
+ * @dev: DRM device
+ * @mode_list: list of modes to check
+ * @maxX: maximum width
+ * @maxY: maximum height
+ * @maxPitch: max pitch
+ *
+ * LOCKING:
+ * Caller must hold a lock protecting @mode_list.
+ *
+ * The DRM device (@dev) has size and pitch limits.  Here we validate the
+ * modes we probed for @dev against those limits and set their status as
+ * necessary.
+ */
+void drm_mode_validate_size(struct drm_device *dev,
+                           struct list_head *mode_list,
+                           int maxX, int maxY, int maxPitch)
+{
+       struct drm_display_mode *mode;
+
+       list_for_each_entry(mode, mode_list, head) {
+               if (maxPitch > 0 && mode->hdisplay > maxPitch)
+                       mode->status = MODE_BAD_WIDTH;
+
+               if (maxX > 0 && mode->hdisplay > maxX)
+                       mode->status = MODE_VIRTUAL_X;
+
+               if (maxY > 0 && mode->vdisplay > maxY)
+                       mode->status = MODE_VIRTUAL_Y;
+       }
+}
+EXPORT_SYMBOL(drm_mode_validate_size);
+
+/**
+ * drm_mode_validate_clocks - validate modes against clock limits
+ * @dev: DRM device
+ * @mode_list: list of modes to check
+ * @min: minimum clock rate array
+ * @max: maximum clock rate array
+ * @n_ranges: number of clock ranges (size of arrays)
+ *
+ * LOCKING:
+ * Caller must hold a lock protecting @mode_list.
+ *
+ * Some code may need to check a mode list against the clock limits of the
+ * device in question.  This function walks the mode list, testing to make
+ * sure each mode falls within a given range (defined by @min and @max
+ * arrays) and sets @mode->status as needed.
+ */
+void drm_mode_validate_clocks(struct drm_device *dev,
+                             struct list_head *mode_list,
+                             int *min, int *max, int n_ranges)
+{
+       struct drm_display_mode *mode;
+       int i;
+
+       list_for_each_entry(mode, mode_list, head) {
+               bool good = false;
+               for (i = 0; i < n_ranges; i++) {
+                       if (mode->clock >= min[i] && mode->clock <= max[i]) {
+                               good = true;
+                               break;
+                       }
+               }
+               if (!good)
+                       mode->status = MODE_CLOCK_RANGE;
+       }
+}
+EXPORT_SYMBOL(drm_mode_validate_clocks);
+
+/**
+ * drm_mode_prune_invalid - remove invalid modes from mode list
+ * @dev: DRM device
+ * @mode_list: list of modes to check
+ * @verbose: be verbose about it
+ *
+ * LOCKING:
+ * Caller must hold a lock protecting @mode_list.
+ *
+ * Once mode list generation is complete, a caller can use this routine to
+ * remove invalid modes from a mode list.  If any of the modes have a
+ * status other than %MODE_OK, they are removed from @mode_list and freed.
+ */
+void drm_mode_prune_invalid(struct drm_device *dev,
+                           struct list_head *mode_list, bool verbose)
+{
+       struct drm_display_mode *mode, *t;
+
+       list_for_each_entry_safe(mode, t, mode_list, head) {
+               if (mode->status != MODE_OK) {
+                       list_del(&mode->head);
+                       if (verbose) {
+                               drm_mode_debug_printmodeline(mode);
+                               DRM_DEBUG("Not using %s mode %d\n", mode->name, mode->status);
+                       }
+                       drm_mode_destroy(dev, mode);
+               }
+       }
+}
+EXPORT_SYMBOL(drm_mode_prune_invalid);
+
+/**
+ * drm_mode_compare - compare modes for favorability
+ * @lh_a: list_head for first mode
+ * @lh_b: list_head for second mode
+ *
+ * LOCKING:
+ * None.
+ *
+ * Compare two modes, given by @lh_a and @lh_b, returning a value indicating
+ * which is better.
+ *
+ * RETURNS:
+ * Negative if @lh_a is better than @lh_b, zero if they're equivalent, or
+ * positive if @lh_b is better than @lh_a.
+ */
+static int drm_mode_compare(struct list_head *lh_a, struct list_head *lh_b)
+{
+       struct drm_display_mode *a = list_entry(lh_a, struct drm_display_mode, head);
+       struct drm_display_mode *b = list_entry(lh_b, struct drm_display_mode, head);
+       int diff;
+
+       diff = ((b->type & DRM_MODE_TYPE_PREFERRED) != 0) -
+               ((a->type & DRM_MODE_TYPE_PREFERRED) != 0);
+       if (diff)
+               return diff;
+       diff = b->hdisplay * b->vdisplay - a->hdisplay * a->vdisplay;
+       if (diff)
+               return diff;
+       diff = b->clock - a->clock;
+       return diff;
+}
+
+/* FIXME: what we don't have a list sort function? */
+/* list sort from Mark J Roberts (mjr@znex.org) */
+void list_sort(struct list_head *head,
+              int (*cmp)(struct list_head *a, struct list_head *b))
+{
+       struct list_head *p, *q, *e, *list, *tail, *oldhead;
+       int insize, nmerges, psize, qsize, i;
+
+       list = head->next;
+       list_del(head);
+       insize = 1;
+       for (;;) {
+               p = oldhead = list;
+               list = tail = NULL;
+               nmerges = 0;
+
+               while (p) {
+                       nmerges++;
+                       q = p;
+                       psize = 0;
+                       for (i = 0; i < insize; i++) {
+                               psize++;
+                               q = q->next == oldhead ? NULL : q->next;
+                               if (!q)
+                                       break;
+                       }
+
+                       qsize = insize;
+                       while (psize > 0 || (qsize > 0 && q)) {
+                               if (!psize) {
+                                       e = q;
+                                       q = q->next;
+                                       qsize--;
+                                       if (q == oldhead)
+                                               q = NULL;
+                               } else if (!qsize || !q) {
+                                       e = p;
+                                       p = p->next;
+                                       psize--;
+                                       if (p == oldhead)
+                                               p = NULL;
+                               } else if (cmp(p, q) <= 0) {
+                                       e = p;
+                                       p = p->next;
+                                       psize--;
+                                       if (p == oldhead)
+                                               p = NULL;
+                               } else {
+                                       e = q;
+                                       q = q->next;
+                                       qsize--;
+                                       if (q == oldhead)
+                                               q = NULL;
+                               }
+                               if (tail)
+                                       tail->next = e;
+                               else
+                                       list = e;
+                               e->prev = tail;
+                               tail = e;
+                       }
+                       p = q;
+               }
+
+               tail->next = list;
+               list->prev = tail;
+
+               if (nmerges <= 1)
+                       break;
+
+               insize *= 2;
+       }
+
+       head->next = list;
+       head->prev = list->prev;
+       list->prev->next = head;
+       list->prev = head;
+}
+
+/**
+ * drm_mode_sort - sort mode list
+ * @mode_list: list to sort
+ *
+ * LOCKING:
+ * Caller must hold a lock protecting @mode_list.
+ *
+ * Sort @mode_list by favorability, putting good modes first.
+ */
+void drm_mode_sort(struct list_head *mode_list)
+{
+       list_sort(mode_list, drm_mode_compare);
+}
+EXPORT_SYMBOL(drm_mode_sort);
+
+/**
+ * drm_mode_connector_list_update - update the mode list for the connector
+ * @connector: the connector to update
+ *
+ * LOCKING:
+ * Caller must hold a lock protecting @mode_list.
+ *
+ * This moves the modes from the @connector probed_modes list
+ * to the actual mode list. It compares the probed mode against the current
+ * list and only adds different modes. All modes unverified after this point
+ * will be removed by the prune invalid modes.
+ */
+void drm_mode_connector_list_update(struct drm_connector *connector)
+{
+       struct drm_display_mode *mode;
+       struct drm_display_mode *pmode, *pt;
+       int found_it;
+
+       list_for_each_entry_safe(pmode, pt, &connector->probed_modes,
+                                head) {
+               found_it = 0;
+               /* go through current modes checking for the new probed mode */
+               list_for_each_entry(mode, &connector->modes, head) {
+                       if (drm_mode_equal(pmode, mode)) {
+                               found_it = 1;
+                               /* if equal delete the probed mode */
+                               mode->status = pmode->status;
+                               list_del(&pmode->head);
+                               drm_mode_destroy(connector->dev, pmode);
+                               break;
+                       }
+               }
+
+               if (!found_it) {
+                       list_move_tail(&pmode->head, &connector->modes);
+               }
+       }
+}
+EXPORT_SYMBOL(drm_mode_connector_list_update);
index ae73b7f7249ad36059dde96219fe2fb33022b082..8df849f66830fbd50bc371fb13abf04360afa914 100644 (file)
@@ -49,6 +49,8 @@ static int drm_queues_info(char *buf, char **start, off_t offset,
                           int request, int *eof, void *data);
 static int drm_bufs_info(char *buf, char **start, off_t offset,
                         int request, int *eof, void *data);
+static int drm_vblank_info(char *buf, char **start, off_t offset,
+                          int request, int *eof, void *data);
 static int drm_gem_name_info(char *buf, char **start, off_t offset,
                             int request, int *eof, void *data);
 static int drm_gem_object_info(char *buf, char **start, off_t offset,
@@ -72,6 +74,7 @@ static struct drm_proc_list {
        {"clients", drm_clients_info, 0},
        {"queues", drm_queues_info, 0},
        {"bufs", drm_bufs_info, 0},
+       {"vblank", drm_vblank_info, 0},
        {"gem_names", drm_gem_name_info, DRIVER_GEM},
        {"gem_objects", drm_gem_object_info, DRIVER_GEM},
 #if DRM_DEBUG_CODE
@@ -195,6 +198,7 @@ static int drm_name_info(char *buf, char **start, off_t offset, int request,
                         int *eof, void *data)
 {
        struct drm_minor *minor = (struct drm_minor *) data;
+       struct drm_master *master = minor->master;
        struct drm_device *dev = minor->dev;
        int len = 0;
 
@@ -203,13 +207,16 @@ static int drm_name_info(char *buf, char **start, off_t offset, int request,
                return 0;
        }
 
+       if (!master)
+               return 0;
+
        *start = &buf[offset];
        *eof = 0;
 
-       if (dev->unique) {
+       if (master->unique) {
                DRM_PROC_PRINT("%s %s %s\n",
                               dev->driver->pci_driver.name,
-                              pci_name(dev->pdev), dev->unique);
+                              pci_name(dev->pdev), master->unique);
        } else {
                DRM_PROC_PRINT("%s %s\n", dev->driver->pci_driver.name,
                               pci_name(dev->pdev));
@@ -453,6 +460,66 @@ static int drm_bufs_info(char *buf, char **start, off_t offset, int request,
        return ret;
 }
 
+/**
+ * Called when "/proc/dri/.../vblank" is read.
+ *
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ */
+static int drm__vblank_info(char *buf, char **start, off_t offset, int request,
+                         int *eof, void *data)
+{
+       struct drm_minor *minor = (struct drm_minor *) data;
+       struct drm_device *dev = minor->dev;
+       int len = 0;
+       int crtc;
+
+       if (offset > DRM_PROC_LIMIT) {
+               *eof = 1;
+               return 0;
+       }
+
+       *start = &buf[offset];
+       *eof = 0;
+
+       for (crtc = 0; crtc < dev->num_crtcs; crtc++) {
+               DRM_PROC_PRINT("CRTC %d enable:     %d\n",
+                              crtc, atomic_read(&dev->vblank_refcount[crtc]));
+               DRM_PROC_PRINT("CRTC %d counter:    %d\n",
+                              crtc, drm_vblank_count(dev, crtc));
+               DRM_PROC_PRINT("CRTC %d last wait:  %d\n",
+                              crtc, dev->last_vblank_wait[crtc]);
+               DRM_PROC_PRINT("CRTC %d in modeset: %d\n",
+                              crtc, dev->vblank_inmodeset[crtc]);
+       }
+
+       if (len > request + offset)
+               return request;
+       *eof = 1;
+       return len - offset;
+}
+
+/**
+ * Simply calls _vblank_info() while holding the drm_device::struct_mutex lock.
+ */
+static int drm_vblank_info(char *buf, char **start, off_t offset, int request,
+                        int *eof, void *data)
+{
+       struct drm_minor *minor = (struct drm_minor *) data;
+       struct drm_device *dev = minor->dev;
+       int ret;
+
+       mutex_lock(&dev->struct_mutex);
+       ret = drm__vblank_info(buf, start, offset, request, eof, data);
+       mutex_unlock(&dev->struct_mutex);
+       return ret;
+}
+
 /**
  * Called when "/proc/dri/.../clients" is read.
  *
index 66c96ec66672ca2746dc70709772d8c780aea426..5ca132afa4f2e128999e319e44e31ad156e6ab74 100644 (file)
@@ -57,6 +57,14 @@ static int drm_minor_get_id(struct drm_device *dev, int type)
        int ret;
        int base = 0, limit = 63;
 
+       if (type == DRM_MINOR_CONTROL) {
+                base += 64;
+                limit = base + 127;
+        } else if (type == DRM_MINOR_RENDER) {
+                base += 128;
+                limit = base + 255;
+        }
+
 again:
        if (idr_pre_get(&drm_minors_idr, GFP_KERNEL) == 0) {
                DRM_ERROR("Out of memory expanding drawable idr\n");
@@ -79,6 +87,104 @@ again:
        return new_id;
 }
 
+struct drm_master *drm_master_create(struct drm_minor *minor)
+{
+       struct drm_master *master;
+
+       master = drm_calloc(1, sizeof(*master), DRM_MEM_DRIVER);
+       if (!master)
+               return NULL;
+
+       kref_init(&master->refcount);
+       spin_lock_init(&master->lock.spinlock);
+       init_waitqueue_head(&master->lock.lock_queue);
+       drm_ht_create(&master->magiclist, DRM_MAGIC_HASH_ORDER);
+       INIT_LIST_HEAD(&master->magicfree);
+       master->minor = minor;
+
+       list_add_tail(&master->head, &minor->master_list);
+
+       return master;
+}
+
+struct drm_master *drm_master_get(struct drm_master *master)
+{
+       kref_get(&master->refcount);
+       return master;
+}
+
+static void drm_master_destroy(struct kref *kref)
+{
+       struct drm_master *master = container_of(kref, struct drm_master, refcount);
+       struct drm_magic_entry *pt, *next;
+       struct drm_device *dev = master->minor->dev;
+
+       list_del(&master->head);
+
+       if (dev->driver->master_destroy)
+               dev->driver->master_destroy(dev, master);
+
+       if (master->unique) {
+               drm_free(master->unique, master->unique_size, DRM_MEM_DRIVER);
+               master->unique = NULL;
+               master->unique_len = 0;
+       }
+
+       list_for_each_entry_safe(pt, next, &master->magicfree, head) {
+               list_del(&pt->head);
+               drm_ht_remove_item(&master->magiclist, &pt->hash_item);
+               drm_free(pt, sizeof(*pt), DRM_MEM_MAGIC);
+       }
+
+       drm_ht_remove(&master->magiclist);
+
+       if (master->lock.hw_lock) {
+               if (dev->sigdata.lock == master->lock.hw_lock)
+                       dev->sigdata.lock = NULL;
+               master->lock.hw_lock = NULL;
+               master->lock.file_priv = NULL;
+               wake_up_interruptible(&master->lock.lock_queue);
+       }
+
+       drm_free(master, sizeof(*master), DRM_MEM_DRIVER);
+}
+
+void drm_master_put(struct drm_master **master)
+{
+       kref_put(&(*master)->refcount, drm_master_destroy);
+       *master = NULL;
+}
+
+int drm_setmaster_ioctl(struct drm_device *dev, void *data,
+                       struct drm_file *file_priv)
+{
+       if (file_priv->minor->master && file_priv->minor->master != file_priv->master)
+               return -EINVAL;
+
+       if (!file_priv->master)
+               return -EINVAL;
+
+       if (!file_priv->minor->master &&
+           file_priv->minor->master != file_priv->master) {
+               mutex_lock(&dev->struct_mutex);
+               file_priv->minor->master = drm_master_get(file_priv->master);
+               mutex_lock(&dev->struct_mutex);
+       }
+
+       return 0;
+}
+
+int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
+                        struct drm_file *file_priv)
+{
+       if (!file_priv->master)
+               return -EINVAL;
+       mutex_lock(&dev->struct_mutex);
+       drm_master_put(&file_priv->minor->master);
+       mutex_unlock(&dev->struct_mutex);
+       return 0;
+}
+
 static int drm_fill_in_dev(struct drm_device * dev, struct pci_dev *pdev,
                           const struct pci_device_id *ent,
                           struct drm_driver *driver)
@@ -92,7 +198,6 @@ static int drm_fill_in_dev(struct drm_device * dev, struct pci_dev *pdev,
 
        spin_lock_init(&dev->count_lock);
        spin_lock_init(&dev->drw_lock);
-       spin_lock_init(&dev->lock.spinlock);
        init_timer(&dev->timer);
        mutex_init(&dev->struct_mutex);
        mutex_init(&dev->ctxlist_mutex);
@@ -140,9 +245,6 @@ static int drm_fill_in_dev(struct drm_device * dev, struct pci_dev *pdev,
                }
        }
 
-       if (dev->driver->load)
-               if ((retcode = dev->driver->load(dev, ent->driver_data)))
-                       goto error_out_unreg;
 
        retcode = drm_ctxbitmap_init(dev);
        if (retcode) {
@@ -200,6 +302,7 @@ static int drm_get_minor(struct drm_device *dev, struct drm_minor **minor, int t
        new_minor->device = MKDEV(DRM_MAJOR, minor_id);
        new_minor->dev = dev;
        new_minor->index = minor_id;
+       INIT_LIST_HEAD(&new_minor->master_list);
 
        idr_replace(&drm_minors_idr, new_minor, minor_id);
 
@@ -267,8 +370,30 @@ int drm_get_dev(struct pci_dev *pdev, const struct pci_device_id *ent,
                printk(KERN_ERR "DRM: Fill_in_dev failed.\n");
                goto err_g2;
        }
+
+       if (drm_core_check_feature(dev, DRIVER_MODESET)) {
+               ret = drm_get_minor(dev, &dev->control, DRM_MINOR_CONTROL);
+               if (ret)
+                       goto err_g2;
+       }
+
        if ((ret = drm_get_minor(dev, &dev->primary, DRM_MINOR_LEGACY)))
-               goto err_g2;
+               goto err_g3;
+
+       if (dev->driver->load) {
+               ret = dev->driver->load(dev, ent->driver_data);
+               if (ret)
+                       goto err_g3;
+       }
+
+        /* setup the grouping for the legacy output */
+       if (drm_core_check_feature(dev, DRIVER_MODESET)) {
+               ret = drm_mode_group_init_legacy_group(dev, &dev->primary->mode_group);
+               if (ret)
+                       goto err_g3;
+       }
+
+       list_add_tail(&dev->driver_item, &driver->device_list);
 
        DRM_INFO("Initialized %s %d.%d.%d %s on minor %d\n",
                 driver->name, driver->major, driver->minor, driver->patchlevel,
@@ -276,6 +401,8 @@ int drm_get_dev(struct pci_dev *pdev, const struct pci_device_id *ent,
 
        return 0;
 
+err_g3:
+       drm_put_minor(&dev->primary);
 err_g2:
        pci_disable_device(pdev);
 err_g1:
@@ -297,11 +424,6 @@ int drm_put_dev(struct drm_device * dev)
 {
        DRM_DEBUG("release primary %s\n", dev->driver->pci_driver.name);
 
-       if (dev->unique) {
-               drm_free(dev->unique, strlen(dev->unique) + 1, DRM_MEM_DRIVER);
-               dev->unique = NULL;
-               dev->unique_len = 0;
-       }
        if (dev->devname) {
                drm_free(dev->devname, strlen(dev->devname) + 1,
                         DRM_MEM_DRIVER);
index 1611b9bcbe7fd6378bc91a069ff8b8afab0d968b..65d72d094c81803757c00133bc9bd58895a2d03a 100644 (file)
@@ -20,6 +20,7 @@
 #include "drmP.h"
 
 #define to_drm_minor(d) container_of(d, struct drm_minor, kdev)
+#define to_drm_connector(d) container_of(d, struct drm_connector, kdev)
 
 /**
  * drm_sysfs_suspend - DRM class suspend hook
@@ -34,7 +35,7 @@ static int drm_sysfs_suspend(struct device *dev, pm_message_t state)
        struct drm_minor *drm_minor = to_drm_minor(dev);
        struct drm_device *drm_dev = drm_minor->dev;
 
-       if (drm_dev->driver->suspend)
+       if (drm_minor->type == DRM_MINOR_LEGACY && drm_dev->driver->suspend)
                return drm_dev->driver->suspend(drm_dev, state);
 
        return 0;
@@ -52,7 +53,7 @@ static int drm_sysfs_resume(struct device *dev)
        struct drm_minor *drm_minor = to_drm_minor(dev);
        struct drm_device *drm_dev = drm_minor->dev;
 
-       if (drm_dev->driver->resume)
+       if (drm_minor->type == DRM_MINOR_LEGACY && drm_dev->driver->resume)
                return drm_dev->driver->resume(drm_dev);
 
        return 0;
@@ -144,6 +145,323 @@ static void drm_sysfs_device_release(struct device *dev)
        return;
 }
 
+/*
+ * Connector properties
+ */
+static ssize_t status_show(struct device *device,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       struct drm_connector *connector = to_drm_connector(device);
+       enum drm_connector_status status;
+
+       status = connector->funcs->detect(connector);
+       return snprintf(buf, PAGE_SIZE, "%s",
+                       drm_get_connector_status_name(status));
+}
+
+static ssize_t dpms_show(struct device *device,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       struct drm_connector *connector = to_drm_connector(device);
+       struct drm_device *dev = connector->dev;
+       uint64_t dpms_status;
+       int ret;
+
+       ret = drm_connector_property_get_value(connector,
+                                           dev->mode_config.dpms_property,
+                                           &dpms_status);
+       if (ret)
+               return 0;
+
+       return snprintf(buf, PAGE_SIZE, "%s",
+                       drm_get_dpms_name((int)dpms_status));
+}
+
+static ssize_t enabled_show(struct device *device,
+                           struct device_attribute *attr,
+                          char *buf)
+{
+       struct drm_connector *connector = to_drm_connector(device);
+
+       return snprintf(buf, PAGE_SIZE, connector->encoder ? "enabled" :
+                       "disabled");
+}
+
+static ssize_t edid_show(struct kobject *kobj, struct bin_attribute *attr,
+                        char *buf, loff_t off, size_t count)
+{
+       struct device *connector_dev = container_of(kobj, struct device, kobj);
+       struct drm_connector *connector = to_drm_connector(connector_dev);
+       unsigned char *edid;
+       size_t size;
+
+       if (!connector->edid_blob_ptr)
+               return 0;
+
+       edid = connector->edid_blob_ptr->data;
+       size = connector->edid_blob_ptr->length;
+       if (!edid)
+               return 0;
+
+       if (off >= size)
+               return 0;
+
+       if (off + count > size)
+               count = size - off;
+       memcpy(buf, edid + off, count);
+
+       return count;
+}
+
+static ssize_t modes_show(struct device *device,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       struct drm_connector *connector = to_drm_connector(device);
+       struct drm_display_mode *mode;
+       int written = 0;
+
+       list_for_each_entry(mode, &connector->modes, head) {
+               written += snprintf(buf + written, PAGE_SIZE - written, "%s\n",
+                                   mode->name);
+       }
+
+       return written;
+}
+
+static ssize_t subconnector_show(struct device *device,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       struct drm_connector *connector = to_drm_connector(device);
+       struct drm_device *dev = connector->dev;
+       struct drm_property *prop = NULL;
+       uint64_t subconnector;
+       int is_tv = 0;
+       int ret;
+
+       switch (connector->connector_type) {
+               case DRM_MODE_CONNECTOR_DVII:
+                       prop = dev->mode_config.dvi_i_subconnector_property;
+                       break;
+               case DRM_MODE_CONNECTOR_Composite:
+               case DRM_MODE_CONNECTOR_SVIDEO:
+               case DRM_MODE_CONNECTOR_Component:
+                       prop = dev->mode_config.tv_subconnector_property;
+                       is_tv = 1;
+                       break;
+               default:
+                       DRM_ERROR("Wrong connector type for this property\n");
+                       return 0;
+       }
+
+       if (!prop) {
+               DRM_ERROR("Unable to find subconnector property\n");
+               return 0;
+       }
+
+       ret = drm_connector_property_get_value(connector, prop, &subconnector);
+       if (ret)
+               return 0;
+
+       return snprintf(buf, PAGE_SIZE, "%s", is_tv ?
+                       drm_get_tv_subconnector_name((int)subconnector) :
+                       drm_get_dvi_i_subconnector_name((int)subconnector));
+}
+
+static ssize_t select_subconnector_show(struct device *device,
+                          struct device_attribute *attr,
+                          char *buf)
+{
+       struct drm_connector *connector = to_drm_connector(device);
+       struct drm_device *dev = connector->dev;
+       struct drm_property *prop = NULL;
+       uint64_t subconnector;
+       int is_tv = 0;
+       int ret;
+
+       switch (connector->connector_type) {
+               case DRM_MODE_CONNECTOR_DVII:
+                       prop = dev->mode_config.dvi_i_select_subconnector_property;
+                       break;
+               case DRM_MODE_CONNECTOR_Composite:
+               case DRM_MODE_CONNECTOR_SVIDEO:
+               case DRM_MODE_CONNECTOR_Component:
+                       prop = dev->mode_config.tv_select_subconnector_property;
+                       is_tv = 1;
+                       break;
+               default:
+                       DRM_ERROR("Wrong connector type for this property\n");
+                       return 0;
+       }
+
+       if (!prop) {
+               DRM_ERROR("Unable to find select subconnector property\n");
+               return 0;
+       }
+
+       ret = drm_connector_property_get_value(connector, prop, &subconnector);
+       if (ret)
+               return 0;
+
+       return snprintf(buf, PAGE_SIZE, "%s", is_tv ?
+                       drm_get_tv_select_name((int)subconnector) :
+                       drm_get_dvi_i_select_name((int)subconnector));
+}
+
+static struct device_attribute connector_attrs[] = {
+       __ATTR_RO(status),
+       __ATTR_RO(enabled),
+       __ATTR_RO(dpms),
+       __ATTR_RO(modes),
+};
+
+/* These attributes are for both DVI-I connectors and all types of tv-out. */
+static struct device_attribute connector_attrs_opt1[] = {
+       __ATTR_RO(subconnector),
+       __ATTR_RO(select_subconnector),
+};
+
+static struct bin_attribute edid_attr = {
+       .attr.name = "edid",
+       .size = 128,
+       .read = edid_show,
+};
+
+/**
+ * drm_sysfs_connector_add - add an connector to sysfs
+ * @connector: connector to add
+ *
+ * Create an connector device in sysfs, along with its associated connector
+ * properties (so far, connection status, dpms, mode list & edid) and
+ * generate a hotplug event so userspace knows there's a new connector
+ * available.
+ *
+ * Note:
+ * This routine should only be called *once* for each DRM minor registered.
+ * A second call for an already registered device will trigger the BUG_ON
+ * below.
+ */
+int drm_sysfs_connector_add(struct drm_connector *connector)
+{
+       struct drm_device *dev = connector->dev;
+       int ret = 0, i, j;
+
+       /* We shouldn't get called more than once for the same connector */
+       BUG_ON(device_is_registered(&connector->kdev));
+
+       connector->kdev.parent = &dev->primary->kdev;
+       connector->kdev.class = drm_class;
+       connector->kdev.release = drm_sysfs_device_release;
+
+       DRM_DEBUG("adding \"%s\" to sysfs\n",
+                 drm_get_connector_name(connector));
+
+       snprintf(connector->kdev.bus_id, BUS_ID_SIZE, "card%d-%s",
+                dev->primary->index, drm_get_connector_name(connector));
+       ret = device_register(&connector->kdev);
+
+       if (ret) {
+               DRM_ERROR("failed to register connector device: %d\n", ret);
+               goto out;
+       }
+
+       /* Standard attributes */
+
+       for (i = 0; i < ARRAY_SIZE(connector_attrs); i++) {
+               ret = device_create_file(&connector->kdev, &connector_attrs[i]);
+               if (ret)
+                       goto err_out_files;
+       }
+
+       /* Optional attributes */
+       /*
+        * In the long run it maybe a good idea to make one set of
+        * optionals per connector type.
+        */
+       switch (connector->connector_type) {
+               case DRM_MODE_CONNECTOR_DVII:
+               case DRM_MODE_CONNECTOR_Composite:
+               case DRM_MODE_CONNECTOR_SVIDEO:
+               case DRM_MODE_CONNECTOR_Component:
+                       for (i = 0; i < ARRAY_SIZE(connector_attrs_opt1); i++) {
+                               ret = device_create_file(&connector->kdev, &connector_attrs_opt1[i]);
+                               if (ret)
+                                       goto err_out_files;
+                       }
+                       break;
+               default:
+                       break;
+       }
+
+       ret = sysfs_create_bin_file(&connector->kdev.kobj, &edid_attr);
+       if (ret)
+               goto err_out_files;
+
+       /* Let userspace know we have a new connector */
+       drm_sysfs_hotplug_event(dev);
+
+       return 0;
+
+err_out_files:
+       if (i > 0)
+               for (j = 0; j < i; j++)
+                       device_remove_file(&connector->kdev,
+                                          &connector_attrs[i]);
+       device_unregister(&connector->kdev);
+
+out:
+       return ret;
+}
+EXPORT_SYMBOL(drm_sysfs_connector_add);
+
+/**
+ * drm_sysfs_connector_remove - remove an connector device from sysfs
+ * @connector: connector to remove
+ *
+ * Remove @connector and its associated attributes from sysfs.  Note that
+ * the device model core will take care of sending the "remove" uevent
+ * at this time, so we don't need to do it.
+ *
+ * Note:
+ * This routine should only be called if the connector was previously
+ * successfully registered.  If @connector hasn't been registered yet,
+ * you'll likely see a panic somewhere deep in sysfs code when called.
+ */
+void drm_sysfs_connector_remove(struct drm_connector *connector)
+{
+       int i;
+
+       DRM_DEBUG("removing \"%s\" from sysfs\n",
+                 drm_get_connector_name(connector));
+
+       for (i = 0; i < ARRAY_SIZE(connector_attrs); i++)
+               device_remove_file(&connector->kdev, &connector_attrs[i]);
+       sysfs_remove_bin_file(&connector->kdev.kobj, &edid_attr);
+       device_unregister(&connector->kdev);
+}
+EXPORT_SYMBOL(drm_sysfs_connector_remove);
+
+/**
+ * drm_sysfs_hotplug_event - generate a DRM uevent
+ * @dev: DRM device
+ *
+ * Send a uevent for the DRM device specified by @dev.  Currently we only
+ * set HOTPLUG=1 in the uevent environment, but this could be expanded to
+ * deal with other types of events.
+ */
+void drm_sysfs_hotplug_event(struct drm_device *dev)
+{
+       char *event_string = "HOTPLUG=1";
+       char *envp[] = { event_string, NULL };
+
+       DRM_DEBUG("generating hotplug event\n");
+
+       kobject_uevent_env(&dev->primary->kdev.kobj, KOBJ_CHANGE, envp);
+}
+
 /**
  * drm_sysfs_device_add - adds a class device to sysfs for a character driver
  * @dev: DRM device to be added
@@ -163,7 +481,12 @@ int drm_sysfs_device_add(struct drm_minor *minor)
        minor->kdev.class = drm_class;
        minor->kdev.release = drm_sysfs_device_release;
        minor->kdev.devt = minor->device;
-       minor_str = "card%d";
+       if (minor->type == DRM_MINOR_CONTROL)
+               minor_str = "controlD%d";
+        else if (minor->type == DRM_MINOR_RENDER)
+                minor_str = "renderD%d";
+        else
+                minor_str = "card%d";
 
        snprintf(minor->kdev.bus_id, BUS_ID_SIZE, minor_str, minor->index);
 
index c234c6f24a8d8fd157a1af029d530d5bbc09bd84..3ffae021d28052173898a3ed449adbc6e96d61d1 100644 (file)
@@ -267,6 +267,9 @@ static void drm_vm_shm_close(struct vm_area_struct *vma)
                                dmah.size = map->size;
                                __drm_pci_free(dev, &dmah);
                                break;
+                       case _DRM_GEM:
+                               DRM_ERROR("tried to rmmap GEM object\n");
+                               break;
                        }
                        drm_free(map, sizeof(*map), DRM_MEM_MAPS);
                }
@@ -399,7 +402,7 @@ static struct vm_operations_struct drm_vm_sg_ops = {
  * Create a new drm_vma_entry structure as the \p vma private data entry and
  * add it to drm_device::vmalist.
  */
-static void drm_vm_open_locked(struct vm_area_struct *vma)
+void drm_vm_open_locked(struct vm_area_struct *vma)
 {
        struct drm_file *priv = vma->vm_file->private_data;
        struct drm_device *dev = priv->minor->dev;
@@ -540,7 +543,7 @@ EXPORT_SYMBOL(drm_core_get_reg_ofs);
  * according to the mapping type and remaps the pages. Finally sets the file
  * pointer and calls vm_open().
  */
-static int drm_mmap_locked(struct file *filp, struct vm_area_struct *vma)
+int drm_mmap_locked(struct file *filp, struct vm_area_struct *vma)
 {
        struct drm_file *priv = filp->private_data;
        struct drm_device *dev = priv->minor->dev;
index d8fb5d8ee7ea38aa55308bda89ebee06978f1366..dd57a5bd457248505922e552be656982c295ad56 100644 (file)
@@ -8,7 +8,22 @@ i915-y := i915_drv.o i915_dma.o i915_irq.o i915_mem.o \
          i915_gem.o \
          i915_gem_debug.o \
          i915_gem_proc.o \
-         i915_gem_tiling.o
+         i915_gem_tiling.o \
+         intel_display.o \
+         intel_crt.o \
+         intel_lvds.o \
+         intel_bios.o \
+         intel_sdvo.o \
+         intel_modes.o \
+         intel_i2c.o \
+         intel_fb.o \
+         intel_tv.o \
+         intel_dvo.o \
+         dvo_ch7xxx.o \
+         dvo_ch7017.o \
+         dvo_ivch.o \
+         dvo_tfp410.o \
+         dvo_sil164.o
 
 i915-$(CONFIG_ACPI)    += i915_opregion.o
 i915-$(CONFIG_COMPAT)   += i915_ioc32.o
diff --git a/drivers/gpu/drm/i915/dvo.h b/drivers/gpu/drm/i915/dvo.h
new file mode 100644 (file)
index 0000000..e747ac4
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright © 2006 Eric Anholt
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef _INTEL_DVO_H
+#define _INTEL_DVO_H
+
+#include <linux/i2c.h>
+#include "drmP.h"
+#include "drm.h"
+#include "drm_crtc.h"
+#include "intel_drv.h"
+
+struct intel_dvo_device {
+       char *name;
+       int type;
+       /* DVOA/B/C output register */
+       u32 dvo_reg;
+       /* GPIO register used for i2c bus to control this device */
+       u32 gpio;
+       int slave_addr;
+       struct intel_i2c_chan *i2c_bus;
+
+       const struct intel_dvo_dev_ops *dev_ops;
+       void *dev_priv;
+
+       struct drm_display_mode *panel_fixed_mode;
+       bool panel_wants_dither;
+};
+
+struct intel_dvo_dev_ops {
+       /*
+        * Initialize the device at startup time.
+        * Returns NULL if the device does not exist.
+        */
+       bool (*init)(struct intel_dvo_device *dvo,
+                    struct intel_i2c_chan *i2cbus);
+
+       /*
+        * Called to allow the output a chance to create properties after the
+        * RandR objects have been created.
+        */
+       void (*create_resources)(struct intel_dvo_device *dvo);
+
+       /*
+        * Turn on/off output or set intermediate power levels if available.
+        *
+        * Unsupported intermediate modes drop to the lower power setting.
+        * If the  mode is DPMSModeOff, the output must be disabled,
+        * as the DPLL may be disabled afterwards.
+        */
+       void (*dpms)(struct intel_dvo_device *dvo, int mode);
+
+       /*
+        * Saves the output's state for restoration on VT switch.
+        */
+       void (*save)(struct intel_dvo_device *dvo);
+
+       /*
+        * Restore's the output's state at VT switch.
+        */
+       void (*restore)(struct intel_dvo_device *dvo);
+
+       /*
+        * Callback for testing a video mode for a given output.
+        *
+        * This function should only check for cases where a mode can't
+        * be supported on the output specifically, and not represent
+        * generic CRTC limitations.
+        *
+        * \return MODE_OK if the mode is valid, or another MODE_* otherwise.
+        */
+       int (*mode_valid)(struct intel_dvo_device *dvo,
+                         struct drm_display_mode *mode);
+
+       /*
+        * Callback to adjust the mode to be set in the CRTC.
+        *
+        * This allows an output to adjust the clock or even the entire set of
+        * timings, which is used for panels with fixed timings or for
+        * buses with clock limitations.
+        */
+       bool (*mode_fixup)(struct intel_dvo_device *dvo,
+                          struct drm_display_mode *mode,
+                          struct drm_display_mode *adjusted_mode);
+
+       /*
+        * Callback for preparing mode changes on an output
+        */
+       void (*prepare)(struct intel_dvo_device *dvo);
+
+       /*
+        * Callback for committing mode changes on an output
+        */
+       void (*commit)(struct intel_dvo_device *dvo);
+
+       /*
+        * Callback for setting up a video mode after fixups have been made.
+        *
+        * This is only called while the output is disabled.  The dpms callback
+        * must be all that's necessary for the output, to turn the output on
+        * after this function is called.
+        */
+       void (*mode_set)(struct intel_dvo_device *dvo,
+                        struct drm_display_mode *mode,
+                        struct drm_display_mode *adjusted_mode);
+
+       /*
+        * Probe for a connected output, and return detect_status.
+        */
+       enum drm_connector_status (*detect)(struct intel_dvo_device *dvo);
+
+       /**
+        * Query the device for the modes it provides.
+        *
+        * This function may also update MonInfo, mm_width, and mm_height.
+        *
+        * \return singly-linked list of modes or NULL if no modes found.
+        */
+       struct drm_display_mode *(*get_modes)(struct intel_dvo_device *dvo);
+
+       /**
+        * Clean up driver-specific bits of the output
+        */
+       void (*destroy) (struct intel_dvo_device *dvo);
+
+       /**
+        * Debugging hook to dump device registers to log file
+        */
+       void (*dump_regs)(struct intel_dvo_device *dvo);
+};
+
+extern struct intel_dvo_dev_ops sil164_ops;
+extern struct intel_dvo_dev_ops ch7xxx_ops;
+extern struct intel_dvo_dev_ops ivch_ops;
+extern struct intel_dvo_dev_ops tfp410_ops;
+extern struct intel_dvo_dev_ops ch7017_ops;
+
+#endif /* _INTEL_DVO_H */
diff --git a/drivers/gpu/drm/i915/dvo_ch7017.c b/drivers/gpu/drm/i915/dvo_ch7017.c
new file mode 100644 (file)
index 0000000..03d4b49
--- /dev/null
@@ -0,0 +1,454 @@
+/*
+ * Copyright © 2006 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 "dvo.h"
+
+#define CH7017_TV_DISPLAY_MODE         0x00
+#define CH7017_FLICKER_FILTER          0x01
+#define CH7017_VIDEO_BANDWIDTH         0x02
+#define CH7017_TEXT_ENHANCEMENT                0x03
+#define CH7017_START_ACTIVE_VIDEO      0x04
+#define CH7017_HORIZONTAL_POSITION     0x05
+#define CH7017_VERTICAL_POSITION       0x06
+#define CH7017_BLACK_LEVEL             0x07
+#define CH7017_CONTRAST_ENHANCEMENT    0x08
+#define CH7017_TV_PLL                  0x09
+#define CH7017_TV_PLL_M                        0x0a
+#define CH7017_TV_PLL_N                        0x0b
+#define CH7017_SUB_CARRIER_0           0x0c
+#define CH7017_CIV_CONTROL             0x10
+#define CH7017_CIV_0                   0x11
+#define CH7017_CHROMA_BOOST            0x14
+#define CH7017_CLOCK_MODE              0x1c
+#define CH7017_INPUT_CLOCK             0x1d
+#define CH7017_GPIO_CONTROL            0x1e
+#define CH7017_INPUT_DATA_FORMAT       0x1f
+#define CH7017_CONNECTION_DETECT       0x20
+#define CH7017_DAC_CONTROL             0x21
+#define CH7017_BUFFERED_CLOCK_OUTPUT   0x22
+#define CH7017_DEFEAT_VSYNC            0x47
+#define CH7017_TEST_PATTERN            0x48
+
+#define CH7017_POWER_MANAGEMENT                0x49
+/** Enables the TV output path. */
+#define CH7017_TV_EN                   (1 << 0)
+#define CH7017_DAC0_POWER_DOWN         (1 << 1)
+#define CH7017_DAC1_POWER_DOWN         (1 << 2)
+#define CH7017_DAC2_POWER_DOWN         (1 << 3)
+#define CH7017_DAC3_POWER_DOWN         (1 << 4)
+/** Powers down the TV out block, and DAC0-3 */
+#define CH7017_TV_POWER_DOWN_EN                (1 << 5)
+
+#define CH7017_VERSION_ID              0x4a
+
+#define CH7017_DEVICE_ID               0x4b
+#define CH7017_DEVICE_ID_VALUE         0x1b
+#define CH7018_DEVICE_ID_VALUE         0x1a
+#define CH7019_DEVICE_ID_VALUE         0x19
+
+#define CH7017_XCLK_D2_ADJUST          0x53
+#define CH7017_UP_SCALER_COEFF_0       0x55
+#define CH7017_UP_SCALER_COEFF_1       0x56
+#define CH7017_UP_SCALER_COEFF_2       0x57
+#define CH7017_UP_SCALER_COEFF_3       0x58
+#define CH7017_UP_SCALER_COEFF_4       0x59
+#define CH7017_UP_SCALER_VERTICAL_INC_0        0x5a
+#define CH7017_UP_SCALER_VERTICAL_INC_1        0x5b
+#define CH7017_GPIO_INVERT             0x5c
+#define CH7017_UP_SCALER_HORIZONTAL_INC_0      0x5d
+#define CH7017_UP_SCALER_HORIZONTAL_INC_1      0x5e
+
+#define CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT   0x5f
+/**< Low bits of horizontal active pixel input */
+
+#define CH7017_ACTIVE_INPUT_LINE_OUTPUT        0x60
+/** High bits of horizontal active pixel input */
+#define CH7017_LVDS_HAP_INPUT_MASK     (0x7 << 0)
+/** High bits of vertical active line output */
+#define CH7017_LVDS_VAL_HIGH_MASK      (0x7 << 3)
+
+#define CH7017_VERTICAL_ACTIVE_LINE_OUTPUT     0x61
+/**< Low bits of vertical active line output */
+
+#define CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT  0x62
+/**< Low bits of horizontal active pixel output */
+
+#define CH7017_LVDS_POWER_DOWN         0x63
+/** High bits of horizontal active pixel output */
+#define CH7017_LVDS_HAP_HIGH_MASK      (0x7 << 0)
+/** Enables the LVDS power down state transition */
+#define CH7017_LVDS_POWER_DOWN_EN      (1 << 6)
+/** Enables the LVDS upscaler */
+#define CH7017_LVDS_UPSCALER_EN                (1 << 7)
+#define CH7017_LVDS_POWER_DOWN_DEFAULT_RESERVED 0x08
+
+#define CH7017_LVDS_ENCODING           0x64
+#define CH7017_LVDS_DITHER_2D          (1 << 2)
+#define CH7017_LVDS_DITHER_DIS         (1 << 3)
+#define CH7017_LVDS_DUAL_CHANNEL_EN    (1 << 4)
+#define CH7017_LVDS_24_BIT             (1 << 5)
+
+#define CH7017_LVDS_ENCODING_2         0x65
+
+#define CH7017_LVDS_PLL_CONTROL                0x66
+/** Enables the LVDS panel output path */
+#define CH7017_LVDS_PANEN              (1 << 0)
+/** Enables the LVDS panel backlight */
+#define CH7017_LVDS_BKLEN              (1 << 3)
+
+#define CH7017_POWER_SEQUENCING_T1     0x67
+#define CH7017_POWER_SEQUENCING_T2     0x68
+#define CH7017_POWER_SEQUENCING_T3     0x69
+#define CH7017_POWER_SEQUENCING_T4     0x6a
+#define CH7017_POWER_SEQUENCING_T5     0x6b
+#define CH7017_GPIO_DRIVER_TYPE                0x6c
+#define CH7017_GPIO_DATA               0x6d
+#define CH7017_GPIO_DIRECTION_CONTROL  0x6e
+
+#define CH7017_LVDS_PLL_FEEDBACK_DIV   0x71
+# define CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT 4
+# define CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT 0
+# define CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED 0x80
+
+#define CH7017_LVDS_PLL_VCO_CONTROL    0x72
+# define CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED 0x80
+# define CH7017_LVDS_PLL_VCO_SHIFT     4
+# define CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT 0
+
+#define CH7017_OUTPUTS_ENABLE          0x73
+# define CH7017_CHARGE_PUMP_LOW                0x0
+# define CH7017_CHARGE_PUMP_HIGH       0x3
+# define CH7017_LVDS_CHANNEL_A         (1 << 3)
+# define CH7017_LVDS_CHANNEL_B         (1 << 4)
+# define CH7017_TV_DAC_A               (1 << 5)
+# define CH7017_TV_DAC_B               (1 << 6)
+# define CH7017_DDC_SELECT_DC2         (1 << 7)
+
+#define CH7017_LVDS_OUTPUT_AMPLITUDE   0x74
+#define CH7017_LVDS_PLL_EMI_REDUCTION  0x75
+#define CH7017_LVDS_POWER_DOWN_FLICKER 0x76
+
+#define CH7017_LVDS_CONTROL_2          0x78
+# define CH7017_LOOP_FILTER_SHIFT      5
+# define CH7017_PHASE_DETECTOR_SHIFT   0
+
+#define CH7017_BANG_LIMIT_CONTROL      0x7f
+
+struct ch7017_priv {
+       uint8_t save_hapi;
+       uint8_t save_vali;
+       uint8_t save_valo;
+       uint8_t save_ailo;
+       uint8_t save_lvds_pll_vco;
+       uint8_t save_feedback_div;
+       uint8_t save_lvds_control_2;
+       uint8_t save_outputs_enable;
+       uint8_t save_lvds_power_down;
+       uint8_t save_power_management;
+};
+
+static void ch7017_dump_regs(struct intel_dvo_device *dvo);
+static void ch7017_dpms(struct intel_dvo_device *dvo, int mode);
+
+static bool ch7017_read(struct intel_dvo_device *dvo, int addr, uint8_t *val)
+{
+       struct intel_i2c_chan *i2cbus = dvo->i2c_bus;
+       u8 out_buf[2];
+       u8 in_buf[2];
+
+       struct i2c_msg msgs[] = {
+               {
+                       .addr = i2cbus->slave_addr,
+                       .flags = 0,
+                       .len = 1,
+                       .buf = out_buf,
+               },
+               {
+                       .addr = i2cbus->slave_addr,
+                       .flags = I2C_M_RD,
+                       .len = 1,
+                       .buf = in_buf,
+               }
+       };
+
+       out_buf[0] = addr;
+       out_buf[1] = 0;
+
+       if (i2c_transfer(&i2cbus->adapter, msgs, 2) == 2) {
+               *val= in_buf[0];
+               return true;
+       };
+
+       return false;
+}
+
+static bool ch7017_write(struct intel_dvo_device *dvo, int addr, uint8_t val)
+{
+       struct intel_i2c_chan *i2cbus = dvo->i2c_bus;
+       uint8_t out_buf[2];
+       struct i2c_msg msg = {
+               .addr = i2cbus->slave_addr,
+               .flags = 0,
+               .len = 2,
+               .buf = out_buf,
+       };
+
+       out_buf[0] = addr;
+       out_buf[1] = val;
+
+       if (i2c_transfer(&i2cbus->adapter, &msg, 1) == 1)
+               return true;
+
+       return false;
+}
+
+/** Probes for a CH7017 on the given bus and slave address. */
+static bool ch7017_init(struct intel_dvo_device *dvo,
+                       struct intel_i2c_chan *i2cbus)
+{
+       struct ch7017_priv *priv;
+       uint8_t val;
+
+       priv = kzalloc(sizeof(struct ch7017_priv), GFP_KERNEL);
+       if (priv == NULL)
+               return false;
+
+       dvo->i2c_bus = i2cbus;
+       dvo->i2c_bus->slave_addr = dvo->slave_addr;
+       dvo->dev_priv = priv;
+
+       if (!ch7017_read(dvo, CH7017_DEVICE_ID, &val))
+               goto fail;
+
+       if (val != CH7017_DEVICE_ID_VALUE &&
+           val != CH7018_DEVICE_ID_VALUE &&
+           val != CH7019_DEVICE_ID_VALUE) {
+               DRM_DEBUG("ch701x not detected, got %d: from %s Slave %d.\n",
+                         val, i2cbus->adapter.name,i2cbus->slave_addr);
+               goto fail;
+       }
+
+       return true;
+fail:
+       kfree(priv);
+       return false;
+}
+
+static enum drm_connector_status ch7017_detect(struct intel_dvo_device *dvo)
+{
+       return connector_status_unknown;
+}
+
+static enum drm_mode_status ch7017_mode_valid(struct intel_dvo_device *dvo,
+                                             struct drm_display_mode *mode)
+{
+       if (mode->clock > 160000)
+               return MODE_CLOCK_HIGH;
+
+       return MODE_OK;
+}
+
+static void ch7017_mode_set(struct intel_dvo_device *dvo,
+                           struct drm_display_mode *mode,
+                           struct drm_display_mode *adjusted_mode)
+{
+       uint8_t lvds_pll_feedback_div, lvds_pll_vco_control;
+       uint8_t outputs_enable, lvds_control_2, lvds_power_down;
+       uint8_t horizontal_active_pixel_input;
+       uint8_t horizontal_active_pixel_output, vertical_active_line_output;
+       uint8_t active_input_line_output;
+
+       DRM_DEBUG("Registers before mode setting\n");
+       ch7017_dump_regs(dvo);
+
+       /* LVDS PLL settings from page 75 of 7017-7017ds.pdf*/
+       if (mode->clock < 100000) {
+               outputs_enable = CH7017_LVDS_CHANNEL_A | CH7017_CHARGE_PUMP_LOW;
+               lvds_pll_feedback_div = CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED |
+                       (2 << CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT) |
+                       (13 << CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT);
+               lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED |
+                       (2 << CH7017_LVDS_PLL_VCO_SHIFT) |
+                       (3 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT);
+               lvds_control_2 = (1 << CH7017_LOOP_FILTER_SHIFT) |
+                       (0 << CH7017_PHASE_DETECTOR_SHIFT);
+       } else {
+               outputs_enable = CH7017_LVDS_CHANNEL_A | CH7017_CHARGE_PUMP_HIGH;
+               lvds_pll_feedback_div = CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED |
+                       (2 << CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT) |
+                       (3 << CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT);
+               lvds_pll_feedback_div = 35;
+               lvds_control_2 = (3 << CH7017_LOOP_FILTER_SHIFT) |
+                       (0 << CH7017_PHASE_DETECTOR_SHIFT);
+               if (1) { /* XXX: dual channel panel detection.  Assume yes for now. */
+                       outputs_enable |= CH7017_LVDS_CHANNEL_B;
+                       lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED |
+                               (2 << CH7017_LVDS_PLL_VCO_SHIFT) |
+                               (13 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT);
+               } else {
+                       lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED |
+                               (1 << CH7017_LVDS_PLL_VCO_SHIFT) |
+                               (13 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT);
+               }
+       }
+
+       horizontal_active_pixel_input = mode->hdisplay & 0x00ff;
+
+       vertical_active_line_output = mode->vdisplay & 0x00ff;
+       horizontal_active_pixel_output = mode->hdisplay & 0x00ff;
+
+       active_input_line_output = ((mode->hdisplay & 0x0700) >> 8) |
+                                  (((mode->vdisplay & 0x0700) >> 8) << 3);
+
+       lvds_power_down = CH7017_LVDS_POWER_DOWN_DEFAULT_RESERVED |
+                         (mode->hdisplay & 0x0700) >> 8;
+
+       ch7017_dpms(dvo, DRM_MODE_DPMS_OFF);
+       ch7017_write(dvo, CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT,
+                       horizontal_active_pixel_input);
+       ch7017_write(dvo, CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT,
+                       horizontal_active_pixel_output);
+       ch7017_write(dvo, CH7017_VERTICAL_ACTIVE_LINE_OUTPUT,
+                       vertical_active_line_output);
+       ch7017_write(dvo, CH7017_ACTIVE_INPUT_LINE_OUTPUT,
+                       active_input_line_output);
+       ch7017_write(dvo, CH7017_LVDS_PLL_VCO_CONTROL, lvds_pll_vco_control);
+       ch7017_write(dvo, CH7017_LVDS_PLL_FEEDBACK_DIV, lvds_pll_feedback_div);
+       ch7017_write(dvo, CH7017_LVDS_CONTROL_2, lvds_control_2);
+       ch7017_write(dvo, CH7017_OUTPUTS_ENABLE, outputs_enable);
+
+       /* Turn the LVDS back on with new settings. */
+       ch7017_write(dvo, CH7017_LVDS_POWER_DOWN, lvds_power_down);
+
+       DRM_DEBUG("Registers after mode setting\n");
+       ch7017_dump_regs(dvo);
+}
+
+/* set the CH7017 power state */
+static void ch7017_dpms(struct intel_dvo_device *dvo, int mode)
+{
+       uint8_t val;
+
+       ch7017_read(dvo, CH7017_LVDS_POWER_DOWN, &val);
+
+       /* Turn off TV/VGA, and never turn it on since we don't support it. */
+       ch7017_write(dvo, CH7017_POWER_MANAGEMENT,
+                       CH7017_DAC0_POWER_DOWN |
+                       CH7017_DAC1_POWER_DOWN |
+                       CH7017_DAC2_POWER_DOWN |
+                       CH7017_DAC3_POWER_DOWN |
+                       CH7017_TV_POWER_DOWN_EN);
+
+       if (mode == DRM_MODE_DPMS_ON) {
+               /* Turn on the LVDS */
+               ch7017_write(dvo, CH7017_LVDS_POWER_DOWN,
+                            val & ~CH7017_LVDS_POWER_DOWN_EN);
+       } else {
+               /* Turn off the LVDS */
+               ch7017_write(dvo, CH7017_LVDS_POWER_DOWN,
+                            val | CH7017_LVDS_POWER_DOWN_EN);
+       }
+
+       /* XXX: Should actually wait for update power status somehow */
+       udelay(20000);
+}
+
+static void ch7017_dump_regs(struct intel_dvo_device *dvo)
+{
+       uint8_t val;
+
+#define DUMP(reg)                                      \
+do {                                                   \
+       ch7017_read(dvo, reg, &val);                    \
+       DRM_DEBUG(#reg ": %02x\n", val);                \
+} while (0)
+
+       DUMP(CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT);
+       DUMP(CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT);
+       DUMP(CH7017_VERTICAL_ACTIVE_LINE_OUTPUT);
+       DUMP(CH7017_ACTIVE_INPUT_LINE_OUTPUT);
+       DUMP(CH7017_LVDS_PLL_VCO_CONTROL);
+       DUMP(CH7017_LVDS_PLL_FEEDBACK_DIV);
+       DUMP(CH7017_LVDS_CONTROL_2);
+       DUMP(CH7017_OUTPUTS_ENABLE);
+       DUMP(CH7017_LVDS_POWER_DOWN);
+}
+
+static void ch7017_save(struct intel_dvo_device *dvo)
+{
+       struct ch7017_priv *priv = dvo->dev_priv;
+
+       ch7017_read(dvo, CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT, &priv->save_hapi);
+       ch7017_read(dvo, CH7017_VERTICAL_ACTIVE_LINE_OUTPUT, &priv->save_valo);
+       ch7017_read(dvo, CH7017_ACTIVE_INPUT_LINE_OUTPUT, &priv->save_ailo);
+       ch7017_read(dvo, CH7017_LVDS_PLL_VCO_CONTROL, &priv->save_lvds_pll_vco);
+       ch7017_read(dvo, CH7017_LVDS_PLL_FEEDBACK_DIV, &priv->save_feedback_div);
+       ch7017_read(dvo, CH7017_LVDS_CONTROL_2, &priv->save_lvds_control_2);
+       ch7017_read(dvo, CH7017_OUTPUTS_ENABLE, &priv->save_outputs_enable);
+       ch7017_read(dvo, CH7017_LVDS_POWER_DOWN, &priv->save_lvds_power_down);
+       ch7017_read(dvo, CH7017_POWER_MANAGEMENT, &priv->save_power_management);
+}
+
+static void ch7017_restore(struct intel_dvo_device *dvo)
+{
+       struct ch7017_priv *priv = dvo->dev_priv;
+
+       /* Power down before changing mode */
+       ch7017_dpms(dvo, DRM_MODE_DPMS_OFF);
+
+       ch7017_write(dvo, CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT, priv->save_hapi);
+       ch7017_write(dvo, CH7017_VERTICAL_ACTIVE_LINE_OUTPUT, priv->save_valo);
+       ch7017_write(dvo, CH7017_ACTIVE_INPUT_LINE_OUTPUT, priv->save_ailo);
+       ch7017_write(dvo, CH7017_LVDS_PLL_VCO_CONTROL, priv->save_lvds_pll_vco);
+       ch7017_write(dvo, CH7017_LVDS_PLL_FEEDBACK_DIV, priv->save_feedback_div);
+       ch7017_write(dvo, CH7017_LVDS_CONTROL_2, priv->save_lvds_control_2);
+       ch7017_write(dvo, CH7017_OUTPUTS_ENABLE, priv->save_outputs_enable);
+       ch7017_write(dvo, CH7017_LVDS_POWER_DOWN, priv->save_lvds_power_down);
+       ch7017_write(dvo, CH7017_POWER_MANAGEMENT, priv->save_power_management);
+}
+
+static void ch7017_destroy(struct intel_dvo_device *dvo)
+{
+       struct ch7017_priv *priv = dvo->dev_priv;
+
+       if (priv) {
+               kfree(priv);
+               dvo->dev_priv = NULL;
+       }
+}
+
+struct intel_dvo_dev_ops ch7017_ops = {
+       .init = ch7017_init,
+       .detect = ch7017_detect,
+       .mode_valid = ch7017_mode_valid,
+       .mode_set = ch7017_mode_set,
+       .dpms = ch7017_dpms,
+       .dump_regs = ch7017_dump_regs,
+       .save = ch7017_save,
+       .restore = ch7017_restore,
+       .destroy = ch7017_destroy,
+};
diff --git a/drivers/gpu/drm/i915/dvo_ch7xxx.c b/drivers/gpu/drm/i915/dvo_ch7xxx.c
new file mode 100644 (file)
index 0000000..d2fd95d
--- /dev/null
@@ -0,0 +1,368 @@
+/**************************************************************************
+
+Copyright © 2006 Dave Airlie
+
+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, sub license, 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 NON-INFRINGEMENT.
+IN NO EVENT SHALL THE AUTHOR 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 "dvo.h"
+
+#define CH7xxx_REG_VID         0x4a
+#define CH7xxx_REG_DID         0x4b
+
+#define CH7011_VID             0x83 /* 7010 as well */
+#define CH7009A_VID            0x84
+#define CH7009B_VID            0x85
+#define CH7301_VID             0x95
+
+#define CH7xxx_VID             0x84
+#define CH7xxx_DID             0x17
+
+#define CH7xxx_NUM_REGS                0x4c
+
+#define CH7xxx_CM              0x1c
+#define CH7xxx_CM_XCM          (1<<0)
+#define CH7xxx_CM_MCP          (1<<2)
+#define CH7xxx_INPUT_CLOCK     0x1d
+#define CH7xxx_GPIO            0x1e
+#define CH7xxx_GPIO_HPIR       (1<<3)
+#define CH7xxx_IDF             0x1f
+
+#define CH7xxx_IDF_HSP         (1<<3)
+#define CH7xxx_IDF_VSP         (1<<4)
+
+#define CH7xxx_CONNECTION_DETECT 0x20
+#define CH7xxx_CDET_DVI                (1<<5)
+
+#define CH7301_DAC_CNTL                0x21
+#define CH7301_HOTPLUG         0x23
+#define CH7xxx_TCTL            0x31
+#define CH7xxx_TVCO            0x32
+#define CH7xxx_TPCP            0x33
+#define CH7xxx_TPD             0x34
+#define CH7xxx_TPVT            0x35
+#define CH7xxx_TLPF            0x36
+#define CH7xxx_TCT             0x37
+#define CH7301_TEST_PATTERN    0x48
+
+#define CH7xxx_PM              0x49
+#define CH7xxx_PM_FPD          (1<<0)
+#define CH7301_PM_DACPD0       (1<<1)
+#define CH7301_PM_DACPD1       (1<<2)
+#define CH7301_PM_DACPD2       (1<<3)
+#define CH7xxx_PM_DVIL         (1<<6)
+#define CH7xxx_PM_DVIP         (1<<7)
+
+#define CH7301_SYNC_POLARITY   0x56
+#define CH7301_SYNC_RGB_YUV    (1<<0)
+#define CH7301_SYNC_POL_DVI    (1<<5)
+
+/** @file
+ * driver for the Chrontel 7xxx DVI chip over DVO.
+ */
+
+static struct ch7xxx_id_struct {
+       uint8_t vid;
+       char *name;
+} ch7xxx_ids[] = {
+       { CH7011_VID, "CH7011" },
+       { CH7009A_VID, "CH7009A" },
+       { CH7009B_VID, "CH7009B" },
+       { CH7301_VID, "CH7301" },
+};
+
+struct ch7xxx_reg_state {
+    uint8_t regs[CH7xxx_NUM_REGS];
+};
+
+struct ch7xxx_priv {
+       bool quiet;
+
+       struct ch7xxx_reg_state save_reg;
+       struct ch7xxx_reg_state mode_reg;
+       uint8_t save_TCTL, save_TPCP, save_TPD, save_TPVT;
+       uint8_t save_TLPF, save_TCT, save_PM, save_IDF;
+};
+
+static void ch7xxx_save(struct intel_dvo_device *dvo);
+
+static char *ch7xxx_get_id(uint8_t vid)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(ch7xxx_ids); i++) {
+               if (ch7xxx_ids[i].vid == vid)
+                       return ch7xxx_ids[i].name;
+       }
+
+       return NULL;
+}
+
+/** Reads an 8 bit register */
+static bool ch7xxx_readb(struct intel_dvo_device *dvo, int addr, uint8_t *ch)
+{
+       struct ch7xxx_priv *ch7xxx= dvo->dev_priv;
+       struct intel_i2c_chan *i2cbus = dvo->i2c_bus;
+       u8 out_buf[2];
+       u8 in_buf[2];
+
+       struct i2c_msg msgs[] = {
+               {
+                       .addr = i2cbus->slave_addr,
+                       .flags = 0,
+                       .len = 1,
+                       .buf = out_buf,
+               },
+               {
+                       .addr = i2cbus->slave_addr,
+                       .flags = I2C_M_RD,
+                       .len = 1,
+                       .buf = in_buf,
+               }
+       };
+
+       out_buf[0] = addr;
+       out_buf[1] = 0;
+
+       if (i2c_transfer(&i2cbus->adapter, msgs, 2) == 2) {
+               *ch = in_buf[0];
+               return true;
+       };
+
+       if (!ch7xxx->quiet) {
+               DRM_DEBUG("Unable to read register 0x%02x from %s:%02x.\n",
+                         addr, i2cbus->adapter.name, i2cbus->slave_addr);
+       }
+       return false;
+}
+
+/** Writes an 8 bit register */
+static bool ch7xxx_writeb(struct intel_dvo_device *dvo, int addr, uint8_t ch)
+{
+       struct ch7xxx_priv *ch7xxx = dvo->dev_priv;
+       struct intel_i2c_chan *i2cbus = dvo->i2c_bus;
+       uint8_t out_buf[2];
+       struct i2c_msg msg = {
+               .addr = i2cbus->slave_addr,
+               .flags = 0,
+               .len = 2,
+               .buf = out_buf,
+       };
+
+       out_buf[0] = addr;
+       out_buf[1] = ch;
+
+       if (i2c_transfer(&i2cbus->adapter, &msg, 1) == 1)
+               return true;
+
+       if (!ch7xxx->quiet) {
+               DRM_DEBUG("Unable to write register 0x%02x to %s:%d.\n",
+                         addr, i2cbus->adapter.name, i2cbus->slave_addr);
+       }
+
+       return false;
+}
+
+static bool ch7xxx_init(struct intel_dvo_device *dvo,
+                       struct intel_i2c_chan *i2cbus)
+{
+       /* this will detect the CH7xxx chip on the specified i2c bus */
+       struct ch7xxx_priv *ch7xxx;
+       uint8_t vendor, device;
+       char *name;
+
+       ch7xxx = kzalloc(sizeof(struct ch7xxx_priv), GFP_KERNEL);
+       if (ch7xxx == NULL)
+               return false;
+
+       dvo->i2c_bus = i2cbus;
+       dvo->i2c_bus->slave_addr = dvo->slave_addr;
+       dvo->dev_priv = ch7xxx;
+       ch7xxx->quiet = true;
+
+       if (!ch7xxx_readb(dvo, CH7xxx_REG_VID, &vendor))
+               goto out;
+
+       name = ch7xxx_get_id(vendor);
+       if (!name) {
+               DRM_DEBUG("ch7xxx not detected; got 0x%02x from %s slave %d.\n",
+                         vendor, i2cbus->adapter.name, i2cbus->slave_addr);
+               goto out;
+       }
+
+
+       if (!ch7xxx_readb(dvo, CH7xxx_REG_DID, &device))
+               goto out;
+
+       if (device != CH7xxx_DID) {
+               DRM_DEBUG("ch7xxx not detected; got 0x%02x from %s slave %d.\n",
+                         vendor, i2cbus->adapter.name, i2cbus->slave_addr);
+               goto out;
+       }
+
+       ch7xxx->quiet = false;
+       DRM_DEBUG("Detected %s chipset, vendor/device ID 0x%02x/0x%02x\n",
+                 name, vendor, device);
+       return true;
+out:
+       kfree(ch7xxx);
+       return false;
+}
+
+static enum drm_connector_status ch7xxx_detect(struct intel_dvo_device *dvo)
+{
+       uint8_t cdet, orig_pm, pm;
+
+       ch7xxx_readb(dvo, CH7xxx_PM, &orig_pm);
+
+       pm = orig_pm;
+       pm &= ~CH7xxx_PM_FPD;
+       pm |= CH7xxx_PM_DVIL | CH7xxx_PM_DVIP;
+
+       ch7xxx_writeb(dvo, CH7xxx_PM, pm);
+
+       ch7xxx_readb(dvo, CH7xxx_CONNECTION_DETECT, &cdet);
+
+       ch7xxx_writeb(dvo, CH7xxx_PM, orig_pm);
+
+       if (cdet & CH7xxx_CDET_DVI)
+               return connector_status_connected;
+       return connector_status_disconnected;
+}
+
+static enum drm_mode_status ch7xxx_mode_valid(struct intel_dvo_device *dvo,
+                                             struct drm_display_mode *mode)
+{
+       if (mode->clock > 165000)
+               return MODE_CLOCK_HIGH;
+
+       return MODE_OK;
+}
+
+static void ch7xxx_mode_set(struct intel_dvo_device *dvo,
+                           struct drm_display_mode *mode,
+                           struct drm_display_mode *adjusted_mode)
+{
+       uint8_t tvco, tpcp, tpd, tlpf, idf;
+
+       if (mode->clock <= 65000) {
+               tvco = 0x23;
+               tpcp = 0x08;
+               tpd = 0x16;
+               tlpf = 0x60;
+       } else {
+               tvco = 0x2d;
+               tpcp = 0x06;
+               tpd = 0x26;
+               tlpf = 0xa0;
+       }
+
+       ch7xxx_writeb(dvo, CH7xxx_TCTL, 0x00);
+       ch7xxx_writeb(dvo, CH7xxx_TVCO, tvco);
+       ch7xxx_writeb(dvo, CH7xxx_TPCP, tpcp);
+       ch7xxx_writeb(dvo, CH7xxx_TPD, tpd);
+       ch7xxx_writeb(dvo, CH7xxx_TPVT, 0x30);
+       ch7xxx_writeb(dvo, CH7xxx_TLPF, tlpf);
+       ch7xxx_writeb(dvo, CH7xxx_TCT, 0x00);
+
+       ch7xxx_readb(dvo, CH7xxx_IDF, &idf);
+
+       idf &= ~(CH7xxx_IDF_HSP | CH7xxx_IDF_VSP);
+       if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+               idf |= CH7xxx_IDF_HSP;
+
+       if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+               idf |= CH7xxx_IDF_HSP;
+
+       ch7xxx_writeb(dvo, CH7xxx_IDF, idf);
+}
+
+/* set the CH7xxx power state */
+static void ch7xxx_dpms(struct intel_dvo_device *dvo, int mode)
+{
+       if (mode == DRM_MODE_DPMS_ON)
+               ch7xxx_writeb(dvo, CH7xxx_PM, CH7xxx_PM_DVIL | CH7xxx_PM_DVIP);
+       else
+               ch7xxx_writeb(dvo, CH7xxx_PM, CH7xxx_PM_FPD);
+}
+
+static void ch7xxx_dump_regs(struct intel_dvo_device *dvo)
+{
+       struct ch7xxx_priv *ch7xxx = dvo->dev_priv;
+       int i;
+
+       for (i = 0; i < CH7xxx_NUM_REGS; i++) {
+               if ((i % 8) == 0 )
+                       DRM_DEBUG("\n %02X: ", i);
+               DRM_DEBUG("%02X ", ch7xxx->mode_reg.regs[i]);
+       }
+}
+
+static void ch7xxx_save(struct intel_dvo_device *dvo)
+{
+       struct ch7xxx_priv *ch7xxx= dvo->dev_priv;
+
+       ch7xxx_readb(dvo, CH7xxx_TCTL, &ch7xxx->save_TCTL);
+       ch7xxx_readb(dvo, CH7xxx_TPCP, &ch7xxx->save_TPCP);
+       ch7xxx_readb(dvo, CH7xxx_TPD, &ch7xxx->save_TPD);
+       ch7xxx_readb(dvo, CH7xxx_TPVT, &ch7xxx->save_TPVT);
+       ch7xxx_readb(dvo, CH7xxx_TLPF, &ch7xxx->save_TLPF);
+       ch7xxx_readb(dvo, CH7xxx_PM, &ch7xxx->save_PM);
+       ch7xxx_readb(dvo, CH7xxx_IDF, &ch7xxx->save_IDF);
+}
+
+static void ch7xxx_restore(struct intel_dvo_device *dvo)
+{
+       struct ch7xxx_priv *ch7xxx = dvo->dev_priv;
+
+       ch7xxx_writeb(dvo, CH7xxx_TCTL, ch7xxx->save_TCTL);
+       ch7xxx_writeb(dvo, CH7xxx_TPCP, ch7xxx->save_TPCP);
+       ch7xxx_writeb(dvo, CH7xxx_TPD, ch7xxx->save_TPD);
+       ch7xxx_writeb(dvo, CH7xxx_TPVT, ch7xxx->save_TPVT);
+       ch7xxx_writeb(dvo, CH7xxx_TLPF, ch7xxx->save_TLPF);
+       ch7xxx_writeb(dvo, CH7xxx_IDF, ch7xxx->save_IDF);
+       ch7xxx_writeb(dvo, CH7xxx_PM, ch7xxx->save_PM);
+}
+
+static void ch7xxx_destroy(struct intel_dvo_device *dvo)
+{
+       struct ch7xxx_priv *ch7xxx = dvo->dev_priv;
+
+       if (ch7xxx) {
+               kfree(ch7xxx);
+               dvo->dev_priv = NULL;
+       }
+}
+
+struct intel_dvo_dev_ops ch7xxx_ops = {
+       .init = ch7xxx_init,
+       .detect = ch7xxx_detect,
+       .mode_valid = ch7xxx_mode_valid,
+       .mode_set = ch7xxx_mode_set,
+       .dpms = ch7xxx_dpms,
+       .dump_regs = ch7xxx_dump_regs,
+       .save = ch7xxx_save,
+       .restore = ch7xxx_restore,
+       .destroy = ch7xxx_destroy,
+};
diff --git a/drivers/gpu/drm/i915/dvo_ivch.c b/drivers/gpu/drm/i915/dvo_ivch.c
new file mode 100644 (file)
index 0000000..0c8d375
--- /dev/null
@@ -0,0 +1,442 @@
+/*
+ * Copyright © 2006 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 "dvo.h"
+
+/*
+ * register definitions for the i82807aa.
+ *
+ * Documentation on this chipset can be found in datasheet #29069001 at
+ * intel.com.
+ */
+
+/*
+ * VCH Revision & GMBus Base Addr
+ */
+#define VR00           0x00
+# define VR00_BASE_ADDRESS_MASK                0x007f
+
+/*
+ * Functionality Enable
+ */
+#define VR01           0x01
+
+/*
+ * Enable the panel fitter
+ */
+# define VR01_PANEL_FIT_ENABLE         (1 << 3)
+/*
+ * Enables the LCD display.
+ *
+ * This must not be set while VR01_DVO_BYPASS_ENABLE is set.
+ */
+# define VR01_LCD_ENABLE               (1 << 2)
+/** Enables the DVO repeater. */
+# define VR01_DVO_BYPASS_ENABLE                (1 << 1)
+/** Enables the DVO clock */
+# define VR01_DVO_ENABLE               (1 << 0)
+
+/*
+ * LCD Interface Format
+ */
+#define VR10           0x10
+/** Enables LVDS output instead of CMOS */
+# define VR10_LVDS_ENABLE              (1 << 4)
+/** Enables 18-bit LVDS output. */
+# define VR10_INTERFACE_1X18           (0 << 2)
+/** Enables 24-bit LVDS or CMOS output */
+# define VR10_INTERFACE_1X24           (1 << 2)
+/** Enables 2x18-bit LVDS or CMOS output. */
+# define VR10_INTERFACE_2X18           (2 << 2)
+/** Enables 2x24-bit LVDS output */
+# define VR10_INTERFACE_2X24           (3 << 2)
+
+/*
+ * VR20 LCD Horizontal Display Size
+ */
+#define VR20   0x20
+
+/*
+ * LCD Vertical Display Size
+ */
+#define VR21   0x20
+
+/*
+ * Panel power down status
+ */
+#define VR30           0x30
+/** Read only bit indicating that the panel is not in a safe poweroff state. */
+# define VR30_PANEL_ON                 (1 << 15)
+
+#define VR40           0x40
+# define VR40_STALL_ENABLE             (1 << 13)
+# define VR40_VERTICAL_INTERP_ENABLE   (1 << 12)
+# define VR40_ENHANCED_PANEL_FITTING   (1 << 11)
+# define VR40_HORIZONTAL_INTERP_ENABLE (1 << 10)
+# define VR40_AUTO_RATIO_ENABLE                (1 << 9)
+# define VR40_CLOCK_GATING_ENABLE      (1 << 8)
+
+/*
+ * Panel Fitting Vertical Ratio
+ * (((image_height - 1) << 16) / ((panel_height - 1))) >> 2
+ */
+#define VR41           0x41
+
+/*
+ * Panel Fitting Horizontal Ratio
+ * (((image_width - 1) << 16) / ((panel_width - 1))) >> 2
+ */
+#define VR42           0x42
+
+/*
+ * Horizontal Image Size
+ */
+#define VR43           0x43
+
+/* VR80 GPIO 0
+ */
+#define VR80       0x80
+#define VR81       0x81
+#define VR82       0x82
+#define VR83       0x83
+#define VR84       0x84
+#define VR85       0x85
+#define VR86       0x86
+#define VR87       0x87
+
+/* VR88 GPIO 8
+ */
+#define VR88       0x88
+
+/* Graphics BIOS scratch 0
+ */
+#define VR8E       0x8E
+# define VR8E_PANEL_TYPE_MASK          (0xf << 0)
+# define VR8E_PANEL_INTERFACE_CMOS     (0 << 4)
+# define VR8E_PANEL_INTERFACE_LVDS     (1 << 4)
+# define VR8E_FORCE_DEFAULT_PANEL      (1 << 5)
+
+/* Graphics BIOS scratch 1
+ */
+#define VR8F       0x8F
+# define VR8F_VCH_PRESENT              (1 << 0)
+# define VR8F_DISPLAY_CONN             (1 << 1)
+# define VR8F_POWER_MASK               (0x3c)
+# define VR8F_POWER_POS                        (2)
+
+
+struct ivch_priv {
+       bool quiet;
+
+       uint16_t width, height;
+
+       uint16_t save_VR01;
+       uint16_t save_VR40;
+};
+
+
+static void ivch_dump_regs(struct intel_dvo_device *dvo);
+
+/**
+ * Reads a register on the ivch.
+ *
+ * Each of the 256 registers are 16 bits long.
+ */
+static bool ivch_read(struct intel_dvo_device *dvo, int addr, uint16_t *data)
+{
+       struct ivch_priv *priv = dvo->dev_priv;
+       struct intel_i2c_chan *i2cbus = dvo->i2c_bus;
+       u8 out_buf[1];
+       u8 in_buf[2];
+
+       struct i2c_msg msgs[] = {
+               {
+                       .addr = i2cbus->slave_addr,
+                       .flags = I2C_M_RD,
+                       .len = 0,
+               },
+               {
+                       .addr = 0,
+                       .flags = I2C_M_NOSTART,
+                       .len = 1,
+                       .buf = out_buf,
+               },
+               {
+                       .addr = i2cbus->slave_addr,
+                       .flags = I2C_M_RD | I2C_M_NOSTART,
+                       .len = 2,
+                       .buf = in_buf,
+               }
+       };
+
+       out_buf[0] = addr;
+
+       if (i2c_transfer(&i2cbus->adapter, msgs, 3) == 3) {
+               *data = (in_buf[1] << 8) | in_buf[0];
+               return true;
+       };
+
+       if (!priv->quiet) {
+               DRM_DEBUG("Unable to read register 0x%02x from %s:%02x.\n",
+                         addr, i2cbus->adapter.name, i2cbus->slave_addr);
+       }
+       return false;
+}
+
+/** Writes a 16-bit register on the ivch */
+static bool ivch_write(struct intel_dvo_device *dvo, int addr, uint16_t data)
+{
+       struct ivch_priv *priv = dvo->dev_priv;
+       struct intel_i2c_chan *i2cbus = dvo->i2c_bus;
+       u8 out_buf[3];
+       struct i2c_msg msg = {
+               .addr = i2cbus->slave_addr,
+               .flags = 0,
+               .len = 3,
+               .buf = out_buf,
+       };
+
+       out_buf[0] = addr;
+       out_buf[1] = data & 0xff;
+       out_buf[2] = data >> 8;
+
+       if (i2c_transfer(&i2cbus->adapter, &msg, 1) == 1)
+               return true;
+
+       if (!priv->quiet) {
+               DRM_DEBUG("Unable to write register 0x%02x to %s:%d.\n",
+                         addr, i2cbus->adapter.name, i2cbus->slave_addr);
+       }
+
+       return false;
+}
+
+/** Probes the given bus and slave address for an ivch */
+static bool ivch_init(struct intel_dvo_device *dvo,
+                     struct intel_i2c_chan *i2cbus)
+{
+       struct ivch_priv *priv;
+       uint16_t temp;
+
+       priv = kzalloc(sizeof(struct ivch_priv), GFP_KERNEL);
+       if (priv == NULL)
+               return false;
+
+       dvo->i2c_bus = i2cbus;
+       dvo->i2c_bus->slave_addr = dvo->slave_addr;
+       dvo->dev_priv = priv;
+       priv->quiet = true;
+
+       if (!ivch_read(dvo, VR00, &temp))
+              &nb