From a9089c45570c7b2df9155c2cd73aeea59cc0e34e Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Sat, 12 Jul 2008 16:32:09 +1000 Subject: [PATCH 01/14] modesetting/helper: fix array overrun - count should be reset here --- linux-core/drm_crtc_helper.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/linux-core/drm_crtc_helper.c b/linux-core/drm_crtc_helper.c index 18fc9d98..ec76aa93 100644 --- a/linux-core/drm_crtc_helper.c +++ b/linux-core/drm_crtc_helper.c @@ -566,7 +566,8 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set) ret = -EINVAL; goto fail_no_encoder; } - + + count = 0; list_for_each_entry(connector, &dev->mode_config.connector_list, head) { if (!connector->encoder) continue; From 4872ac9c6204c3f212fd622ed292f6fc245020bf Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Wed, 25 Jun 2008 04:39:32 +1000 Subject: [PATCH 02/14] nouveau: interface changes for nv5x 3d --- shared-core/nouveau_drm.h | 20 ++++++++--- shared-core/nouveau_drv.h | 4 ++- shared-core/nouveau_fifo.c | 1 + shared-core/nouveau_mem.c | 71 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 87 insertions(+), 9 deletions(-) diff --git a/shared-core/nouveau_drm.h b/shared-core/nouveau_drm.h index bbb51bc4..4b5869ad 100644 --- a/shared-core/nouveau_drm.h +++ b/shared-core/nouveau_drm.h @@ -25,7 +25,7 @@ #ifndef __NOUVEAU_DRM_H__ #define __NOUVEAU_DRM_H__ -#define NOUVEAU_DRM_HEADER_PATCHLEVEL 10 +#define NOUVEAU_DRM_HEADER_PATCHLEVEL 11 struct drm_nouveau_channel_alloc { uint32_t fb_ctxdma_handle; @@ -85,10 +85,12 @@ struct drm_nouveau_gpuobj_free { #define NOUVEAU_MEM_PINNED 0x00000040 #define NOUVEAU_MEM_USER_BACKED 0x00000080 #define NOUVEAU_MEM_MAPPED 0x00000100 -#define NOUVEAU_MEM_INSTANCE 0x00000200 /* internal */ -#define NOUVEAU_MEM_NOTIFIER 0x00000400 /* internal */ -#define NOUVEAU_MEM_NOVM 0x00000800 /* internal */ -#define NOUVEAU_MEM_USER 0x00001000 /* internal */ +#define NOUVEAU_MEM_TILE 0x00000200 +#define NOUVEAU_MEM_TILE_ZETA 0x00000400 +#define NOUVEAU_MEM_INSTANCE 0x01000000 /* internal */ +#define NOUVEAU_MEM_NOTIFIER 0x02000000 /* internal */ +#define NOUVEAU_MEM_NOVM 0x04000000 /* internal */ +#define NOUVEAU_MEM_USER 0x08000000 /* internal */ #define NOUVEAU_MEM_INTERNAL (NOUVEAU_MEM_INSTANCE | \ NOUVEAU_MEM_NOTIFIER | \ NOUVEAU_MEM_NOVM | \ @@ -107,6 +109,13 @@ struct drm_nouveau_mem_free { int flags; }; +struct drm_nouveau_mem_tile { + uint64_t offset; + uint64_t delta; + uint64_t size; + int flags; +}; + /* FIXME : maybe unify {GET,SET}PARAMs */ #define NOUVEAU_GETPARAM_PCI_VENDOR 3 #define NOUVEAU_GETPARAM_PCI_DEVICE 4 @@ -168,5 +177,6 @@ struct drm_nouveau_sarea { #define DRM_NOUVEAU_GPUOBJ_FREE 0x07 #define DRM_NOUVEAU_MEM_ALLOC 0x08 #define DRM_NOUVEAU_MEM_FREE 0x09 +#define DRM_NOUVEAU_MEM_TILE 0x0a #endif /* __NOUVEAU_DRM_H__ */ diff --git a/shared-core/nouveau_drv.h b/shared-core/nouveau_drv.h index 33e2a5b6..03fe2ba7 100644 --- a/shared-core/nouveau_drv.h +++ b/shared-core/nouveau_drv.h @@ -34,7 +34,7 @@ #define DRIVER_MAJOR 0 #define DRIVER_MINOR 0 -#define DRIVER_PATCHLEVEL 10 +#define DRIVER_PATCHLEVEL 11 #define NOUVEAU_FAMILY 0x0000FFFF #define NOUVEAU_FLAGS 0xFFFF0000 @@ -385,6 +385,8 @@ extern int nouveau_ioctl_mem_alloc(struct drm_device *, void *data, struct drm_file *); extern int nouveau_ioctl_mem_free(struct drm_device *, void *data, struct drm_file *); +extern int nouveau_ioctl_mem_tile(struct drm_device *, void *data, + struct drm_file *); extern struct mem_block* nouveau_mem_alloc(struct drm_device *, int alignment, uint64_t size, int flags, struct drm_file *); diff --git a/shared-core/nouveau_fifo.c b/shared-core/nouveau_fifo.c index d8fda277..085336af 100644 --- a/shared-core/nouveau_fifo.c +++ b/shared-core/nouveau_fifo.c @@ -593,6 +593,7 @@ struct drm_ioctl_desc nouveau_ioctls[] = { DRM_IOCTL_DEF(DRM_NOUVEAU_GPUOBJ_FREE, nouveau_ioctl_gpuobj_free, DRM_AUTH), DRM_IOCTL_DEF(DRM_NOUVEAU_MEM_ALLOC, nouveau_ioctl_mem_alloc, DRM_AUTH), DRM_IOCTL_DEF(DRM_NOUVEAU_MEM_FREE, nouveau_ioctl_mem_free, DRM_AUTH), + DRM_IOCTL_DEF(DRM_NOUVEAU_MEM_TILE, nouveau_ioctl_mem_tile, DRM_AUTH), }; int nouveau_max_ioctl = DRM_ARRAY_SIZE(nouveau_ioctls); diff --git a/shared-core/nouveau_mem.c b/shared-core/nouveau_mem.c index 861d699f..db207e76 100644 --- a/shared-core/nouveau_mem.c +++ b/shared-core/nouveau_mem.c @@ -606,8 +606,11 @@ nouveau_mem_alloc(struct drm_device *dev, int alignment, uint64_t size, /* Align allocation sizes to 64KiB blocks on G8x. We use a 64KiB * page size in the GPU VM. */ - if (flags & NOUVEAU_MEM_FB && dev_priv->card_type >= NV_50) - size = (size + (64 * 1024)) & ~((64 * 1024) - 1); + if (flags & NOUVEAU_MEM_FB && dev_priv->card_type >= NV_50) { + size = (size + 65535) & ~65535; + if (alignment < 16) + alignment = 16; + } /* * Warn about 0 sized allocations, but let it go through. It'll return 1 page @@ -669,6 +672,7 @@ alloc_ok: struct nouveau_gpuobj *pt = dev_priv->vm_vram_pt; unsigned offset = block->start; unsigned count = block->size / 65536; + unsigned tile = 0; if (!pt) { DRM_ERROR("vm alloc without vm pt\n"); @@ -676,11 +680,22 @@ alloc_ok: return NULL; } + /* The tiling stuff is *not* what NVIDIA does - but both the + * 2D and 3D engines seem happy with this simpler method. + * Should look into why NVIDIA do what they do at some point. + */ + if (flags & NOUVEAU_MEM_TILE) { + if (flags & NOUVEAU_MEM_TILE_ZETA) + tile = 0x00002800; + else + tile = 0x00007000; + } + while (count--) { unsigned pte = offset / 65536; INSTANCE_WR(pt, (pte * 2) + 0, offset | 1); - INSTANCE_WR(pt, (pte * 2) + 1, 0x00000000); + INSTANCE_WR(pt, (pte * 2) + 1, 0x00000000 | tile); offset += 65536; } } else { @@ -833,3 +848,53 @@ int nouveau_ioctl_mem_free(struct drm_device *dev, void *data, struct drm_file * nouveau_mem_free(dev, block); return 0; } + +int +nouveau_ioctl_mem_tile(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_nouveau_mem_tile *memtile = data; + struct mem_block *block = NULL; + + NOUVEAU_CHECK_INITIALISED_WITH_RETURN; + + if (dev_priv->card_type < NV_50) + return -EINVAL; + + if (memtile->flags & NOUVEAU_MEM_FB) { + memtile->offset -= 512*1024*1024; + block = find_block(dev_priv->fb_heap, memtile->offset); + } + + if (!block) + return -EINVAL; + + if (block->file_priv != file_priv) + return -EPERM; + + { + struct nouveau_gpuobj *pt = dev_priv->vm_vram_pt; + unsigned offset = block->start + memtile->delta; + unsigned count = memtile->size / 65536; + unsigned tile = 0; + + if (memtile->flags & NOUVEAU_MEM_TILE) { + if (memtile->flags & NOUVEAU_MEM_TILE_ZETA) + tile = 0x00002800; + else + tile = 0x00007000; + } + + while (count--) { + unsigned pte = offset / 65536; + + INSTANCE_WR(pt, (pte * 2) + 0, offset | 1); + INSTANCE_WR(pt, (pte * 2) + 1, 0x00000000 | tile); + offset += 65536; + } + } + + return 0; +} + From 0ef097b598433a5756df2bd6a72deba1f0e1d1c7 Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Mon, 23 Jun 2008 01:24:11 +1000 Subject: [PATCH 03/14] nv50: use same dma object for fb/tt access We depend on the VM fully now for memory protection, separate DMA objects for VRAM and GART are unneccesary. However, until the next interface break (soon) a client can't depend on the objects being the same and must still call NV_OBJ_SET_DMA_* methods appropriately. --- shared-core/nouveau_mem.c | 11 ++++++++++- shared-core/nouveau_object.c | 6 ++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/shared-core/nouveau_mem.c b/shared-core/nouveau_mem.c index db207e76..375463b4 100644 --- a/shared-core/nouveau_mem.c +++ b/shared-core/nouveau_mem.c @@ -806,6 +806,7 @@ int nouveau_ioctl_mem_alloc(struct drm_device *dev, void *data, struct drm_file *file_priv) { + struct drm_nouveau_private *dev_priv = dev->dev_private; struct drm_nouveau_mem_alloc *alloc = data; struct mem_block *block; @@ -822,10 +823,15 @@ nouveau_ioctl_mem_alloc(struct drm_device *dev, void *data, alloc->offset=block->start; alloc->flags=block->flags; + if (dev_priv->card_type >= NV_50 && alloc->flags & NOUVEAU_MEM_FB) + alloc->offset += 512*1024*1024; + return 0; } -int nouveau_ioctl_mem_free(struct drm_device *dev, void *data, struct drm_file *file_priv) +int +nouveau_ioctl_mem_free(struct drm_device *dev, void *data, + struct drm_file *file_priv) { struct drm_nouveau_private *dev_priv = dev->dev_private; struct drm_nouveau_mem_free *memfree = data; @@ -833,6 +839,9 @@ int nouveau_ioctl_mem_free(struct drm_device *dev, void *data, struct drm_file * NOUVEAU_CHECK_INITIALISED_WITH_RETURN; + if (dev_priv->card_type >= NV_50 && memfree->flags & NOUVEAU_MEM_FB) + memfree->offset -= 512*1024*1024; + block=NULL; if (memfree->flags & NOUVEAU_MEM_FB) block = find_block(dev_priv->fb_heap, memfree->offset); diff --git a/shared-core/nouveau_object.c b/shared-core/nouveau_object.c index 5664bfc8..894e7336 100644 --- a/shared-core/nouveau_object.c +++ b/shared-core/nouveau_object.c @@ -1036,8 +1036,7 @@ nouveau_gpuobj_channel_init(struct nouveau_channel *chan, /* VRAM ctxdma */ if (dev_priv->card_type >= NV_50) { ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY, - 512*1024*1024, - dev_priv->fb_available_size, + 0, 0x100000000ULL, NV_DMA_ACCESS_RW, NV_DMA_TARGET_AGP, &vram); if (ret) { @@ -1059,6 +1058,9 @@ nouveau_gpuobj_channel_init(struct nouveau_channel *chan, } /* TT memory ctxdma */ + if (dev_priv->card_type >= NV_50) { + tt = vram; + } else if (dev_priv->gart_info.type != NOUVEAU_GART_NONE) { ret = nouveau_gpuobj_gart_dma_new(chan, 0, dev_priv->gart_info.aper_size, From e2ffee839ed7ae6c55a0a8c6bb8ee872ae8a2a70 Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Sun, 20 Jul 2008 00:09:06 +0200 Subject: [PATCH 04/14] drm: As a workaround don't tear down sg with a modesetting driver. - This allows me to maintain a useful prototype driver. --- linux-core/drm_drv.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/linux-core/drm_drv.c b/linux-core/drm_drv.c index deb8eb9c..4e7c531a 100644 --- a/linux-core/drm_drv.c +++ b/linux-core/drm_drv.c @@ -242,7 +242,10 @@ 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) { + + /* You're supposed to have a real memory manager for modesetting, but this'll suffice as a temporary workaround. */ + /* This assumes sgdma is inited at load time. */ + if (drm_core_check_feature(dev, DRIVER_SG) && !drm_core_check_feature(dev, DRIVER_MODESET) && dev->sg) { drm_sg_cleanup(dev->sg); dev->sg = NULL; } From 65803e53a696347e38d7f6c2c8dc186c6764ff03 Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Sun, 20 Jul 2008 13:49:18 +0200 Subject: [PATCH 05/14] modesetting-101: implement optional scaling and dithering properties --- linux-core/drm_crtc.c | 69 ++++++++++++++++++ linux-core/drm_crtc.h | 8 ++- linux-core/nv50_connector.c | 4 +- linux-core/nv50_connector.h | 1 + linux-core/nv50_crtc.c | 2 +- linux-core/nv50_display.h | 2 +- linux-core/nv50_kms_wrapper.c | 128 ++++++++++++++++++++++++++++++++-- shared-core/drm.h | 10 +++ 8 files changed, 215 insertions(+), 9 deletions(-) diff --git a/linux-core/drm_crtc.c b/linux-core/drm_crtc.c index fc8d1fe8..c984209a 100644 --- a/linux-core/drm_crtc.c +++ b/linux-core/drm_crtc.c @@ -60,6 +60,26 @@ char *drm_get_dpms_name(int val) return "unknown"; } +/* + * 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_select_subconnector_enum_list[] = { { DRM_MODE_SUBCONNECTOR_Automatic, "Automatic" }, /* DVI-I and TV-out */ @@ -102,6 +122,9 @@ char *drm_get_subconnector_name(int val) return "unknown"; } +/* + * Connector and encoder types. + */ static struct drm_prop_enum_list drm_connector_enum_list[] = { { DRM_MODE_CONNECTOR_Unknown, "Unknown" }, { DRM_MODE_CONNECTOR_VGA, "VGA" }, @@ -646,6 +669,52 @@ int drm_mode_create_tv_properties(struct drm_device *dev, int num_modes, } 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) +{ + int i; + + if (dev->mode_config.scaling_mode_property) /* already done */ + return 0; + + dev->mode_config.scaling_mode_property = + 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(dev->mode_config.scaling_mode_property, i, drm_scaling_mode_enum_list[i].type, drm_scaling_mode_enum_list[i].name); + + 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) +{ + int i; + + if (dev->mode_config.dithering_mode_property) /* already done */ + return 0; + + dev->mode_config.dithering_mode_property = + 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(dev->mode_config.dithering_mode_property, i, drm_dithering_mode_enum_list[i].type, drm_dithering_mode_enum_list[i].name); + + return 0; +} +EXPORT_SYMBOL(drm_mode_create_dithering_property); + /** * drm_mode_config_init - initialize DRM mode_configuration structure * @dev: DRM device diff --git a/linux-core/drm_crtc.h b/linux-core/drm_crtc.h index d4bb8794..d88c6149 100644 --- a/linux-core/drm_crtc.h +++ b/linux-core/drm_crtc.h @@ -536,7 +536,7 @@ struct drm_mode_config { struct drm_property *edid_property; struct drm_property *dpms_property; - /* optional properties */ + /* DVI-I properties */ struct drm_property *dvi_i_subconnector_property; struct drm_property *dvi_i_select_subconnector_property; @@ -549,6 +549,10 @@ struct drm_mode_config { struct drm_property *tv_top_margin_property; struct drm_property *tv_bottom_margin_property; + /* Optional properties */ + struct drm_property *scaling_mode_property; + struct drm_property *dithering_mode_property; + /* hotplug */ uint32_t hotplug_counter; }; @@ -650,6 +654,8 @@ extern int drm_property_add_enum(struct drm_property *property, int index, extern int drm_mode_create_dvi_i_properties(struct drm_device *dev); extern int drm_mode_create_tv_properties(struct drm_device *dev, int num_formats, char *formats[]); +extern int drm_mode_create_scaling_mode_property(struct drm_device *dev); +extern int drm_mode_create_dithering_property(struct drm_device *dev); extern char *drm_get_encoder_name(struct drm_encoder *encoder); extern int drm_mode_connector_attach_encoder(struct drm_connector *connector, diff --git a/linux-core/nv50_connector.c b/linux-core/nv50_connector.c index ac5194c0..be133de3 100644 --- a/linux-core/nv50_connector.c +++ b/linux-core/nv50_connector.c @@ -187,7 +187,9 @@ int nv50_connector_create(struct drm_device *dev, int bus, int i2c_index, int ty if (type == CONNECTOR_DVI_D || type == CONNECTOR_DVI_I || type == CONNECTOR_LVDS) connector->scaling_mode = SCALE_FULLSCREEN; else - connector->scaling_mode = SCALE_PANEL; + connector->scaling_mode = SCALE_NON_GPU; + + connector->use_dithering = false; if (i2c_index < 0xf) connector->i2c_chan = nv50_i2c_channel_create(dev, i2c_index); diff --git a/linux-core/nv50_connector.h b/linux-core/nv50_connector.h index ebd6eac6..02b1561e 100644 --- a/linux-core/nv50_connector.h +++ b/linux-core/nv50_connector.h @@ -48,6 +48,7 @@ struct nv50_connector { struct nv50_output *output; int scaling_mode; + bool use_dithering; bool (*detect) (struct nv50_connector *connector); int (*destroy) (struct nv50_connector *connector); diff --git a/linux-core/nv50_crtc.c b/linux-core/nv50_crtc.c index 6c3d404f..ffb976f4 100644 --- a/linux-core/nv50_crtc.c +++ b/linux-core/nv50_crtc.c @@ -264,7 +264,7 @@ static int nv50_crtc_set_scale(struct nv50_crtc *crtc) outY = crtc->native_mode->vdisplay; break; case SCALE_NOSCALE: - case SCALE_PANEL: + case SCALE_NON_GPU: default: outX = crtc->mode->hdisplay; outY = crtc->mode->vdisplay; diff --git a/linux-core/nv50_display.h b/linux-core/nv50_display.h index f20e67da..3c2ee1c9 100644 --- a/linux-core/nv50_display.h +++ b/linux-core/nv50_display.h @@ -68,7 +68,7 @@ struct nv50_display { }; enum scaling_modes { - SCALE_PANEL, + SCALE_NON_GPU, SCALE_FULLSCREEN, SCALE_ASPECT, SCALE_NOSCALE, diff --git a/linux-core/nv50_kms_wrapper.c b/linux-core/nv50_kms_wrapper.c index a7966e9a..b0d64340 100644 --- a/linux-core/nv50_kms_wrapper.c +++ b/linux-core/nv50_kms_wrapper.c @@ -636,10 +636,11 @@ int nv50_kms_crtc_set_config(struct drm_mode_set *set) continue; crtc->scaling_mode = connector->scaling_mode; + crtc->use_dithering = connector->use_dithering; break; } - if (crtc->scaling_mode == SCALE_PANEL) + if (crtc->scaling_mode == SCALE_NON_GPU) crtc->use_native_mode = false; else crtc->use_native_mode = true; @@ -1078,14 +1079,16 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u } } -static bool nv50_kms_connector_set_property(struct drm_connector *connector, +static bool nv50_kms_connector_set_property(struct drm_connector *drm_connector, struct drm_property *property, uint64_t value) { - struct drm_device *dev = connector->dev; + struct drm_device *dev = drm_connector->dev; + struct nv50_connector *connector = to_nv50_connector(drm_connector); - if (property == dev->mode_config.dpms_property && connector->encoder) { - struct nv50_output *output = to_nv50_output(connector->encoder); + /* DPMS */ + if (property == dev->mode_config.dpms_property && drm_connector->encoder) { + struct nv50_output *output = to_nv50_output(drm_connector->encoder); if (!output->set_power_mode(output, (int) value)) return true; @@ -1093,6 +1096,78 @@ static bool nv50_kms_connector_set_property(struct drm_connector *connector, return false; } + /* Scaling mode */ + if (property == dev->mode_config.scaling_mode_property) { + struct nv50_crtc *crtc = NULL; + struct nv50_display *display = nv50_get_display(dev); + int internal_value = 0; + int rval = 0; + + switch (value) { + case DRM_MODE_SCALE_NON_GPU: + internal_value = SCALE_NON_GPU; + break; + case DRM_MODE_SCALE_FULLSCREEN: + internal_value = SCALE_FULLSCREEN; + break; + case DRM_MODE_SCALE_NO_SCALE: + internal_value = SCALE_NOSCALE; + break; + case DRM_MODE_SCALE_ASPECT: + internal_value = SCALE_ASPECT; + break; + default: + break; + } + + connector->scaling_mode = internal_value; + + if (drm_connector->encoder && drm_connector->encoder->crtc) + crtc = to_nv50_crtc(drm_connector->encoder->crtc); + + if (!crtc) + return true; + + crtc->scaling_mode = connector->scaling_mode; + rval = crtc->set_scale(crtc); + if (rval) + return false; + + /* process command buffer */ + display->update(display); + + return true; + } + + /* Dithering */ + if (property == dev->mode_config.dithering_mode_property) { + struct nv50_crtc *crtc = NULL; + struct nv50_display *display = nv50_get_display(dev); + int rval = 0; + + if (value == DRM_MODE_DITHERING_ON) + connector->use_dithering = true; + else + connector->use_dithering = false; + + if (drm_connector->encoder && drm_connector->encoder->crtc) + crtc = to_nv50_crtc(drm_connector->encoder->crtc); + + if (!crtc) + return true; + + /* update hw state */ + crtc->use_dithering = connector->use_dithering; + rval = crtc->set_dither(crtc); + if (rval) + return false; + + /* process command buffer */ + display->update(display); + + return true; + } + return false; } @@ -1105,12 +1180,48 @@ static const struct drm_connector_funcs nv50_kms_connector_funcs = { .set_property = nv50_kms_connector_set_property }; +static int nv50_kms_get_scaling_mode(struct drm_connector *drm_connector) +{ + struct nv50_connector *connector = NULL; + int drm_mode = 0; + + if (!drm_connector) { + DRM_ERROR("drm_connector is NULL\n"); + return 0; + } + + connector = to_nv50_connector(drm_connector); + + switch (connector->scaling_mode) { + case SCALE_NON_GPU: + drm_mode = DRM_MODE_SCALE_NON_GPU; + break; + case SCALE_FULLSCREEN: + drm_mode = DRM_MODE_SCALE_FULLSCREEN; + break; + case SCALE_NOSCALE: + drm_mode = DRM_MODE_SCALE_NO_SCALE; + break; + case SCALE_ASPECT: + drm_mode = DRM_MODE_SCALE_ASPECT; + break; + default: + break; + } + + return drm_mode; +} + static int nv50_kms_connectors_init(struct drm_device *dev) { struct nv50_display *display = nv50_get_display(dev); struct nv50_connector *connector = NULL; int i; + /* Initialise some optional connector properties. */ + drm_mode_create_scaling_mode_property(dev); + drm_mode_create_dithering_property(dev); + list_for_each_entry(connector, &display->connectors, item) { struct drm_connector *drm_connector = to_nv50_kms_connector(connector); uint32_t type = DRM_MODE_CONNECTOR_Unknown; @@ -1154,6 +1265,13 @@ static int nv50_kms_connectors_init(struct drm_device *dev) drm_connector_attach_property(drm_connector, dev->mode_config.dvi_i_select_subconnector_property, 0); } + /* If supported in the future, it will have to use the scalers internally and not expose them. */ + if (type != DRM_MODE_CONNECTOR_SVIDEO) { + drm_connector_attach_property(drm_connector, dev->mode_config.scaling_mode_property, nv50_kms_get_scaling_mode(drm_connector)); + } + + drm_connector_attach_property(drm_connector, dev->mode_config.dithering_mode_property, connector->use_dithering ? DRM_MODE_DITHERING_ON : DRM_MODE_DITHERING_OFF); + /* attach encoders, possibilities are analog + digital */ for (i = 0; i < 2; i++) { struct drm_encoder *drm_encoder = NULL; diff --git a/shared-core/drm.h b/shared-core/drm.h index 2e4d2a94..41190640 100644 --- a/shared-core/drm.h +++ b/shared-core/drm.h @@ -1034,6 +1034,16 @@ struct drm_mm_info_arg { #define DRM_MODE_DPMS_SUSPEND 2 #define DRM_MODE_DPMS_OFF 3 +/* Scaling mode options */ +#define DRM_MODE_SCALE_NON_GPU 0 +#define DRM_MODE_SCALE_FULLSCREEN 1 +#define DRM_MODE_SCALE_NO_SCALE 2 +#define DRM_MODE_SCALE_ASPECT 3 + +/* Dithering mode options */ +#define DRM_MODE_DITHERING_OFF 0 +#define DRM_MODE_DITHERING_ON 1 + struct drm_mode_modeinfo { unsigned int clock; unsigned short hdisplay, hsync_start, hsync_end, htotal, hskew; From 3ef1d05001a9e28ed52536de7e020323d8d34d83 Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Sun, 20 Jul 2008 14:51:22 +0200 Subject: [PATCH 06/14] modesetting-101: set_property should return an int, not a bool --- linux-core/drm_crtc.h | 2 +- linux-core/intel_crt.c | 4 ++-- linux-core/intel_tv.c | 2 +- linux-core/nv50_kms_wrapper.c | 26 ++++++++++++-------------- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/linux-core/drm_crtc.h b/linux-core/drm_crtc.h index d88c6149..117b7213 100644 --- a/linux-core/drm_crtc.h +++ b/linux-core/drm_crtc.h @@ -375,7 +375,7 @@ struct drm_connector_funcs { void (*restore)(struct drm_connector *connector); enum drm_connector_status (*detect)(struct drm_connector *connector); void (*fill_modes)(struct drm_connector *connector, uint32_t max_width, uint32_t max_height); - bool (*set_property)(struct drm_connector *connector, struct drm_property *property, + int (*set_property)(struct drm_connector *connector, struct drm_property *property, uint64_t val); void (*destroy)(struct drm_connector *connector); }; diff --git a/linux-core/intel_crt.c b/linux-core/intel_crt.c index 1b2b5b7e..8e1b833a 100644 --- a/linux-core/intel_crt.c +++ b/linux-core/intel_crt.c @@ -210,7 +210,7 @@ static int intel_crt_get_modes(struct drm_connector *connector) return intel_ddc_get_modes(intel_output); } -static bool intel_crt_set_property(struct drm_connector *connector, +static int intel_crt_set_property(struct drm_connector *connector, struct drm_property *property, uint64_t value) { @@ -219,7 +219,7 @@ static bool intel_crt_set_property(struct drm_connector *connector, if (property == dev->mode_config.dpms_property && connector->encoder) intel_crt_dpms(connector->encoder, (uint32_t)(value & 0xf)); - return true; + return 0; } /* diff --git a/linux-core/intel_tv.c b/linux-core/intel_tv.c index 389487bb..29cfc031 100644 --- a/linux-core/intel_tv.c +++ b/linux-core/intel_tv.c @@ -1560,7 +1560,7 @@ intel_tv_destroy (struct drm_connector *connector) } -static bool +static int intel_tv_set_property(struct drm_connector *connector, struct drm_property *property, uint64_t val) { diff --git a/linux-core/nv50_kms_wrapper.c b/linux-core/nv50_kms_wrapper.c index b0d64340..009972c8 100644 --- a/linux-core/nv50_kms_wrapper.c +++ b/linux-core/nv50_kms_wrapper.c @@ -1079,21 +1079,21 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u } } -static bool nv50_kms_connector_set_property(struct drm_connector *drm_connector, +static int nv50_kms_connector_set_property(struct drm_connector *drm_connector, struct drm_property *property, uint64_t value) { struct drm_device *dev = drm_connector->dev; struct nv50_connector *connector = to_nv50_connector(drm_connector); + int rval = 0; /* DPMS */ if (property == dev->mode_config.dpms_property && drm_connector->encoder) { struct nv50_output *output = to_nv50_output(drm_connector->encoder); - if (!output->set_power_mode(output, (int) value)) - return true; - else - return false; + rval = output->set_power_mode(output, (int) value); + + return rval; } /* Scaling mode */ @@ -1101,7 +1101,6 @@ static bool nv50_kms_connector_set_property(struct drm_connector *drm_connector, struct nv50_crtc *crtc = NULL; struct nv50_display *display = nv50_get_display(dev); int internal_value = 0; - int rval = 0; switch (value) { case DRM_MODE_SCALE_NON_GPU: @@ -1126,24 +1125,23 @@ static bool nv50_kms_connector_set_property(struct drm_connector *drm_connector, crtc = to_nv50_crtc(drm_connector->encoder->crtc); if (!crtc) - return true; + return 0; crtc->scaling_mode = connector->scaling_mode; rval = crtc->set_scale(crtc); if (rval) - return false; + return rval; /* process command buffer */ display->update(display); - return true; + return 0; } /* Dithering */ if (property == dev->mode_config.dithering_mode_property) { struct nv50_crtc *crtc = NULL; struct nv50_display *display = nv50_get_display(dev); - int rval = 0; if (value == DRM_MODE_DITHERING_ON) connector->use_dithering = true; @@ -1154,21 +1152,21 @@ static bool nv50_kms_connector_set_property(struct drm_connector *drm_connector, crtc = to_nv50_crtc(drm_connector->encoder->crtc); if (!crtc) - return true; + return 0; /* update hw state */ crtc->use_dithering = connector->use_dithering; rval = crtc->set_dither(crtc); if (rval) - return false; + return rval; /* process command buffer */ display->update(display); - return true; + return 0; } - return false; + return -EINVAL; } static const struct drm_connector_funcs nv50_kms_connector_funcs = { From f1e4785d4cf04b679948602ffbbef2043ce81ec0 Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Sun, 20 Jul 2008 14:55:59 +0200 Subject: [PATCH 07/14] NV50: LVDS always needs some kind of gpu scaling --- linux-core/nv50_kms_wrapper.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/linux-core/nv50_kms_wrapper.c b/linux-core/nv50_kms_wrapper.c index 009972c8..6e0805fc 100644 --- a/linux-core/nv50_kms_wrapper.c +++ b/linux-core/nv50_kms_wrapper.c @@ -1119,6 +1119,10 @@ static int nv50_kms_connector_set_property(struct drm_connector *drm_connector, break; } + /* LVDS always needs gpu scaling */ + if (connector->type == CONNECTOR_LVDS && internal_value == SCALE_NON_GPU) + return -EINVAL; + connector->scaling_mode = internal_value; if (drm_connector->encoder && drm_connector->encoder->crtc) From e51cd78cac24df15e32e23d8db73614524e6ff0d Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Sun, 20 Jul 2008 14:58:46 +0200 Subject: [PATCH 08/14] modesetting-101: Only store property value when set_property was successful. --- linux-core/drm_crtc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/linux-core/drm_crtc.c b/linux-core/drm_crtc.c index c984209a..91bff1f6 100644 --- a/linux-core/drm_crtc.c +++ b/linux-core/drm_crtc.c @@ -2184,12 +2184,12 @@ int drm_mode_connector_property_set_ioctl(struct drm_device *dev, } } - /* store the property value */ - drm_connector_property_set_value(connector, property, out_resp->value); - 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; From 685bca02fe6b7406bb157a1a4e0f147b47ba28f8 Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Sun, 20 Jul 2008 15:40:40 +0200 Subject: [PATCH 09/14] NV50: delay changing gpu<->non-gpu scaling modes until next modeset --- linux-core/nv50_connector.c | 4 ++-- linux-core/nv50_connector.h | 3 ++- linux-core/nv50_crtc.c | 8 +++++++- linux-core/nv50_crtc.h | 4 ++++ linux-core/nv50_kms_wrapper.c | 21 ++++++++++++++++----- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/linux-core/nv50_connector.c b/linux-core/nv50_connector.c index be133de3..309f450c 100644 --- a/linux-core/nv50_connector.c +++ b/linux-core/nv50_connector.c @@ -185,9 +185,9 @@ int nv50_connector_create(struct drm_device *dev, int bus, int i2c_index, int ty /* some reasonable defaults */ if (type == CONNECTOR_DVI_D || type == CONNECTOR_DVI_I || type == CONNECTOR_LVDS) - connector->scaling_mode = SCALE_FULLSCREEN; + connector->requested_scaling_mode = SCALE_FULLSCREEN; else - connector->scaling_mode = SCALE_NON_GPU; + connector->requested_scaling_mode = SCALE_NON_GPU; connector->use_dithering = false; diff --git a/linux-core/nv50_connector.h b/linux-core/nv50_connector.h index 02b1561e..aac94457 100644 --- a/linux-core/nv50_connector.h +++ b/linux-core/nv50_connector.h @@ -47,7 +47,8 @@ struct nv50_connector { struct nv50_i2c_channel *i2c_chan; struct nv50_output *output; - int scaling_mode; + int requested_scaling_mode; + bool use_dithering; bool (*detect) (struct nv50_connector *connector); diff --git a/linux-core/nv50_crtc.c b/linux-core/nv50_crtc.c index ffb976f4..c4ca7e76 100644 --- a/linux-core/nv50_crtc.c +++ b/linux-core/nv50_crtc.c @@ -255,7 +255,7 @@ static int nv50_crtc_set_scale(struct nv50_crtc *crtc) NV50_DEBUG("\n"); - switch (crtc->scaling_mode) { + switch (crtc->requested_scaling_mode) { case SCALE_ASPECT: nv50_crtc_calc_scale(crtc, &outX, &outY); break; @@ -283,6 +283,9 @@ static int nv50_crtc_set_scale(struct nv50_crtc *crtc) OUT_MODE(NV50_CRTC0_SCALE_RES1 + offset, outY << 16 | outX); OUT_MODE(NV50_CRTC0_SCALE_RES2 + offset, outY << 16 | outX); + /* processed */ + crtc->scaling_mode = crtc->requested_scaling_mode; + return 0; } @@ -492,6 +495,9 @@ int nv50_crtc_create(struct drm_device *dev, int index) crtc->mode = kzalloc(sizeof(struct nouveau_hw_mode), GFP_KERNEL); crtc->native_mode = kzalloc(sizeof(struct nouveau_hw_mode), GFP_KERNEL); + crtc->requested_scaling_mode = SCALE_INVALID; + crtc->scaling_mode = SCALE_INVALID; + if (!crtc->mode || !crtc->native_mode) { rval = -ENOMEM; goto out; diff --git a/linux-core/nv50_crtc.h b/linux-core/nv50_crtc.h index 8235c9d6..b4b83584 100644 --- a/linux-core/nv50_crtc.h +++ b/linux-core/nv50_crtc.h @@ -46,6 +46,10 @@ struct nv50_crtc { bool use_native_mode; bool use_dithering; + + /* Changing scaling modes requires a modeset sometimes. */ + /* We need to know the currently active hw mode, as well as the requested one by the user. */ + int requested_scaling_mode; int scaling_mode; struct nv50_cursor *cursor; diff --git a/linux-core/nv50_kms_wrapper.c b/linux-core/nv50_kms_wrapper.c index 6e0805fc..67836f3f 100644 --- a/linux-core/nv50_kms_wrapper.c +++ b/linux-core/nv50_kms_wrapper.c @@ -635,12 +635,12 @@ int nv50_kms_crtc_set_config(struct drm_mode_set *set) if (connector->output != output) continue; - crtc->scaling_mode = connector->scaling_mode; + crtc->requested_scaling_mode = connector->requested_scaling_mode; crtc->use_dithering = connector->use_dithering; break; } - if (crtc->scaling_mode == SCALE_NON_GPU) + if (crtc->requested_scaling_mode == SCALE_NON_GPU) crtc->use_native_mode = false; else crtc->use_native_mode = true; @@ -1086,6 +1086,7 @@ static int nv50_kms_connector_set_property(struct drm_connector *drm_connector, struct drm_device *dev = drm_connector->dev; struct nv50_connector *connector = to_nv50_connector(drm_connector); int rval = 0; + bool delay_change = false; /* DPMS */ if (property == dev->mode_config.dpms_property && drm_connector->encoder) { @@ -1123,7 +1124,7 @@ static int nv50_kms_connector_set_property(struct drm_connector *drm_connector, if (connector->type == CONNECTOR_LVDS && internal_value == SCALE_NON_GPU) return -EINVAL; - connector->scaling_mode = internal_value; + connector->requested_scaling_mode = internal_value; if (drm_connector->encoder && drm_connector->encoder->crtc) crtc = to_nv50_crtc(drm_connector->encoder->crtc); @@ -1131,7 +1132,17 @@ static int nv50_kms_connector_set_property(struct drm_connector *drm_connector, if (!crtc) return 0; - crtc->scaling_mode = connector->scaling_mode; + crtc->requested_scaling_mode = connector->requested_scaling_mode; + + /* going from and to a gpu scaled regime requires a modesetting, so wait until next modeset */ + if (crtc->scaling_mode == SCALE_NON_GPU || internal_value == SCALE_NON_GPU) { + DRM_INFO("Moving from or to a non-gpu scaled mode, this will be processed upon next modeset."); + delay_change = true; + } + + if (delay_change) + return 0; + rval = crtc->set_scale(crtc); if (rval) return rval; @@ -1194,7 +1205,7 @@ static int nv50_kms_get_scaling_mode(struct drm_connector *drm_connector) connector = to_nv50_connector(drm_connector); - switch (connector->scaling_mode) { + switch (connector->requested_scaling_mode) { case SCALE_NON_GPU: drm_mode = DRM_MODE_SCALE_NON_GPU; break; From d00644c27ddc7023ea0e442c7be6b67d9d0da047 Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Mon, 21 Jul 2008 14:29:13 +0200 Subject: [PATCH 10/14] NV50: Do detect with hpd and load detect if possible. - Appropriate error messages when an unknown situation is encountered are included. - Fallback to i2c will occur when needed. --- linux-core/nv50_connector.c | 49 ++++++++++++++++--- linux-core/nv50_connector.h | 3 +- linux-core/nv50_dac.c | 35 ++++++++++++- linux-core/nv50_kms_wrapper.c | 92 ++++++++++++++++++++++++----------- linux-core/nv50_kms_wrapper.h | 2 +- linux-core/nv50_output.h | 2 +- 6 files changed, 144 insertions(+), 39 deletions(-) diff --git a/linux-core/nv50_connector.c b/linux-core/nv50_connector.c index 309f450c..6e5fb912 100644 --- a/linux-core/nv50_connector.c +++ b/linux-core/nv50_connector.c @@ -76,7 +76,45 @@ static struct nv50_output *nv50_connector_to_output(struct nv50_connector *conne return NULL; } -static bool nv50_connector_detect(struct nv50_connector *connector) +static int nv50_connector_hpd_detect(struct nv50_connector *connector) +{ + struct drm_nouveau_private *dev_priv = connector->dev->dev_private; + struct nv50_output *output = NULL; + bool present = 0; + uint32_t reg = 0; + + /* Assume connected for the moment. */ + if (connector->type == CONNECTOR_LVDS) { + NV50_DEBUG("LVDS is defaulting to connected for the moment.\n"); + return 1; + } + + /* No i2c port, no idea what to do for hotplug. */ + if (connector->i2c_chan->index == 15) { + DRM_ERROR("You have a non-LVDS SOR with no i2c port, please report\n"); + return -EINVAL; + } + + if (connector->i2c_chan->index > 3) { + DRM_ERROR("You have an unusual configuration, index is %d\n", connector->i2c_chan->index); + DRM_ERROR("Please report.\n"); + return -EINVAL; + } + + /* Check hotplug pins. */ + reg = NV_READ(NV50_PCONNECTOR_HOTPLUG_STATE); + if (reg & (NV50_PCONNECTOR_HOTPLUG_STATE_PIN_CONNECTED_I2C0 << (4 * connector->i2c_chan->index))) + present = 1; + + if (present) + NV50_DEBUG("Hotplug detect returned positive for bus %d\n", connector->bus); + else + NV50_DEBUG("Hotplug detect returned negative for bus %d\n", connector->bus); + + return present; +} + +static int nv50_connector_i2c_detect(struct nv50_connector *connector) { /* kindly borrrowed from the intel driver, hope it works. */ uint8_t out_buf[] = { 0x0, 0x0}; @@ -97,13 +135,11 @@ static bool nv50_connector_detect(struct nv50_connector *connector) } }; - NV50_DEBUG("\n"); - if (!connector->i2c_chan) - return false; + return -EINVAL; ret = i2c_transfer(&connector->i2c_chan->adapter, msgs, 2); - DRM_INFO("I2C detect returned %d\n", ret); + NV50_DEBUG("I2C detect returned %d\n", ret); if (ret == 2) return true; @@ -195,7 +231,8 @@ int nv50_connector_create(struct drm_device *dev, int bus, int i2c_index, int ty connector->i2c_chan = nv50_i2c_channel_create(dev, i2c_index); /* set function pointers */ - connector->detect = nv50_connector_detect; + connector->hpd_detect = nv50_connector_hpd_detect; + connector->i2c_detect = nv50_connector_i2c_detect; connector->destroy = nv50_connector_destroy; connector->to_output = nv50_connector_to_output; diff --git a/linux-core/nv50_connector.h b/linux-core/nv50_connector.h index aac94457..fa7316e2 100644 --- a/linux-core/nv50_connector.h +++ b/linux-core/nv50_connector.h @@ -51,7 +51,8 @@ struct nv50_connector { bool use_dithering; - bool (*detect) (struct nv50_connector *connector); + int (*hpd_detect) (struct nv50_connector *connector); + int (*i2c_detect) (struct nv50_connector *connector); int (*destroy) (struct nv50_connector *connector); struct nv50_output *(*to_output) (struct nv50_connector *connector, bool digital); }; diff --git a/linux-core/nv50_dac.c b/linux-core/nv50_dac.c index ca4bb5e1..3f007166 100644 --- a/linux-core/nv50_dac.c +++ b/linux-core/nv50_dac.c @@ -133,6 +133,39 @@ static int nv50_dac_set_power_mode(struct nv50_output *output, int mode) return 0; } +static int nv50_dac_detect(struct nv50_output *output) +{ + struct drm_nouveau_private *dev_priv = output->dev->dev_private; + int or = nv50_output_or_offset(output); + bool present = 0; + uint32_t dpms_state, load_pattern, load_state; + + NV_WRITE(NV50_PDISPLAY_DAC_REGS_CLK_CTRL1(or), 0x00000001); + dpms_state = NV_READ(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(or)); + + NV_WRITE(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(or), 0x00150000 | NV50_PDISPLAY_DAC_REGS_DPMS_CTRL_PENDING); + while (NV_READ(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(or)) & NV50_PDISPLAY_DAC_REGS_DPMS_CTRL_PENDING); + + load_pattern = 340; /* TODO: use a bios table for this */ + + NV_WRITE(NV50_PDISPLAY_DAC_REGS_LOAD_CTRL(or), NV50_PDISPLAY_DAC_REGS_LOAD_CTRL_ACTIVE | load_pattern); + udelay(10000); /* give it some time to process */ + load_state = NV_READ(NV50_PDISPLAY_DAC_REGS_LOAD_CTRL(or)); + + NV_WRITE(NV50_PDISPLAY_DAC_REGS_LOAD_CTRL(or), 0); + NV_WRITE(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(or), dpms_state); + + if ((load_state & NV50_PDISPLAY_DAC_REGS_LOAD_CTRL_PRESENT) == NV50_PDISPLAY_DAC_REGS_LOAD_CTRL_PRESENT) + present = 1; + + if (present) + NV50_DEBUG("Load was detected on output with or %d\n", or); + else + NV50_DEBUG("Load was not detected on output with or %d\n", or); + + return present; +} + static int nv50_dac_destroy(struct nv50_output *output) { struct drm_device *dev = output->dev; @@ -210,7 +243,7 @@ int nv50_dac_create(struct drm_device *dev, int dcb_entry) output->execute_mode = nv50_dac_execute_mode; output->set_clock_mode = nv50_dac_set_clock_mode; output->set_power_mode = nv50_dac_set_power_mode; - output->detect = NULL; /* TODO */ + output->detect = nv50_dac_detect; output->destroy = nv50_dac_destroy; return 0; diff --git a/linux-core/nv50_kms_wrapper.c b/linux-core/nv50_kms_wrapper.c index 67836f3f..8b27f80b 100644 --- a/linux-core/nv50_kms_wrapper.c +++ b/linux-core/nv50_kms_wrapper.c @@ -386,7 +386,7 @@ int nv50_kms_crtc_set_config(struct drm_mode_set *set) /* This is to ensure it knows the connector subtype. */ drm_connector->funcs->fill_modes(drm_connector, 0, 0); - output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector)); + output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector)); if (!output) { DRM_ERROR("No output\n"); goto out; @@ -458,7 +458,7 @@ int nv50_kms_crtc_set_config(struct drm_mode_set *set) goto out; } - output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector)); + output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector)); if (!output) { DRM_ERROR("No output\n"); goto out; @@ -835,7 +835,9 @@ static int nv50_kms_encoders_init(struct drm_device *dev) * Connector functions */ -bool nv50_kms_connector_is_digital(struct drm_connector *drm_connector) + +/* These 2 functions wrap the connector properties that deal with multiple encoders per connector. */ +bool nv50_kms_connector_get_digital(struct drm_connector *drm_connector) { struct drm_device *dev = drm_connector->dev; @@ -892,6 +894,33 @@ bool nv50_kms_connector_is_digital(struct drm_connector *drm_connector) return false; } +static void nv50_kms_connector_set_digital(struct drm_connector *drm_connector, int digital, bool force) +{ + struct drm_device *dev = drm_connector->dev; + + if (drm_connector->connector_type == DRM_MODE_CONNECTOR_DVII) { + uint64_t cur_value, new_value; + + int rval = drm_connector_property_get_value(drm_connector, dev->mode_config.dvi_i_subconnector_property, &cur_value); + if (rval) { + DRM_ERROR("Unable to find subconnector property\n"); + return; + } + + /* Only set when unknown or when forced to do so. */ + if (cur_value != DRM_MODE_SUBCONNECTOR_Unknown && !force) + return; + + if (digital == 1) + new_value = DRM_MODE_SUBCONNECTOR_DVID; + else if (digital == 0) + new_value = DRM_MODE_SUBCONNECTOR_DVIA; + else + new_value = DRM_MODE_SUBCONNECTOR_Unknown; + drm_connector_property_set_value(drm_connector, dev->mode_config.dvi_i_subconnector_property, new_value); + } +} + void nv50_kms_connector_detect_all(struct drm_device *dev) { struct drm_connector *drm_connector = NULL; @@ -903,16 +932,32 @@ void nv50_kms_connector_detect_all(struct drm_device *dev) static enum drm_connector_status nv50_kms_connector_detect(struct drm_connector *drm_connector) { - struct nv50_connector *connector = to_nv50_connector(drm_connector); struct drm_device *dev = drm_connector->dev; - bool connected; - int old_status; + struct nv50_connector *connector = to_nv50_connector(drm_connector); + struct nv50_output *output = NULL; + int hpd_detect = 0, load_detect = 0, i2c_detect = 0; + int old_status = drm_connector->status; - connected = connector->detect(connector); + /* hotplug detect */ + hpd_detect = connector->hpd_detect(connector); - old_status = drm_connector->status; + /* load detect */ + output = connector->to_output(connector, FALSE); /* analog */ + if (output && output->detect) + load_detect = output->detect(output); - if (connected) + if (hpd_detect < 0 || load_detect < 0) /* did an error occur? */ + i2c_detect = connector->i2c_detect(connector); + + if (load_detect == 1) { + nv50_kms_connector_set_digital(drm_connector, 0, TRUE); /* analog, forced */ + } else if (hpd_detect == 1 && load_detect == 0) { + nv50_kms_connector_set_digital(drm_connector, 1, TRUE); /* digital, forced */ + } else { + nv50_kms_connector_set_digital(drm_connector, -1, TRUE); /* unknown, forced */ + } + + if (hpd_detect == 1 || load_detect == 1 || i2c_detect == 1) drm_connector->status = connector_status_connected; else drm_connector->status = connector_status_disconnected; @@ -956,7 +1001,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u struct nv50_connector *connector = to_nv50_connector(drm_connector); struct drm_device *dev = drm_connector->dev; int rval = 0; - bool connected; + bool connected = false; struct drm_display_mode *mode, *t; struct edid *edid = NULL; @@ -965,16 +1010,13 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u list_for_each_entry_safe(mode, t, &drm_connector->modes, head) mode->status = MODE_UNVERIFIED; - connected = connector->detect(connector); + if (nv50_kms_connector_detect(drm_connector) == connector_status_connected) + connected = true; if (connected) - drm_connector->status = connector_status_connected; + NV50_DEBUG("%s is connected\n", drm_get_connector_name(drm_connector)); else - drm_connector->status = connector_status_disconnected; - - if (!connected) { NV50_DEBUG("%s is disconnected\n", drm_get_connector_name(drm_connector)); - } /* Not all connnectors have an i2c channel. */ if (connected && connector->i2c_chan) @@ -986,16 +1028,8 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u if (edid) { rval = drm_add_edid_modes(drm_connector, edid); - /* 2 encoders per connector */ - /* eventually do this based on load detect and hot plug detect */ - if (drm_connector->connector_type == DRM_MODE_CONNECTOR_DVII) { - uint64_t subtype = 0; - if (edid->digital) - subtype = DRM_MODE_SUBCONNECTOR_DVID; - else - subtype = DRM_MODE_SUBCONNECTOR_DVIA; - drm_connector_property_set_value(drm_connector, dev->mode_config.dvi_i_subconnector_property, subtype); - } + /* Only update when relevant and when detect couldn't determine type. */ + nv50_kms_connector_set_digital(drm_connector, edid->digital ? 1 : 0, FALSE); kfree(edid); } @@ -1009,7 +1043,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u list_for_each_entry_safe(mode, t, &drm_connector->modes, head) { if (mode->status == MODE_OK) { struct nouveau_hw_mode *hw_mode = nv50_kms_to_hw_mode(mode); - struct nv50_output *output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector)); + struct nv50_output *output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector)); mode->status = output->validate_mode(output, hw_mode); /* find native mode, TODO: also check if we actually found one */ @@ -1025,7 +1059,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u list_for_each_entry_safe(mode, t, &drm_connector->modes, head) { if (mode->status == MODE_OK) { struct nouveau_hw_mode *hw_mode = nv50_kms_to_hw_mode(mode); - struct nv50_output *output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector)); + struct nv50_output *output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector)); mode->status = output->validate_mode(output, hw_mode); kfree(hw_mode); @@ -1057,7 +1091,7 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u /* also add it as native mode */ hw_mode = nv50_kms_to_hw_mode(mode); - output = connector->to_output(connector, nv50_kms_connector_is_digital(drm_connector)); + output = connector->to_output(connector, nv50_kms_connector_get_digital(drm_connector)); if (hw_mode) *output->native_mode = *hw_mode; diff --git a/linux-core/nv50_kms_wrapper.h b/linux-core/nv50_kms_wrapper.h index 5ac66522..60384804 100644 --- a/linux-core/nv50_kms_wrapper.h +++ b/linux-core/nv50_kms_wrapper.h @@ -87,7 +87,7 @@ struct nv50_kms_priv { struct nv50_kms_priv *nv50_get_kms_priv(struct drm_device *dev); void nv50_kms_connector_detect_all(struct drm_device *dev); -bool nv50_kms_connector_is_digital(struct drm_connector *drm_connector); +bool nv50_kms_connector_get_digital(struct drm_connector *drm_connector); int nv50_kms_init(struct drm_device *dev); int nv50_kms_destroy(struct drm_device *dev); diff --git a/linux-core/nv50_output.h b/linux-core/nv50_output.h index a5faf050..ac6d714f 100644 --- a/linux-core/nv50_output.h +++ b/linux-core/nv50_output.h @@ -51,7 +51,7 @@ struct nv50_output { int (*set_clock_mode) (struct nv50_output *output); /* this is not a normal modeset call, it is a direct register write, so it's executed immediately */ int (*set_power_mode) (struct nv50_output *output, int mode); - bool (*detect) (struct nv50_output *output); + int (*detect) (struct nv50_output *output); int (*destroy) (struct nv50_output *output); }; From 03f8208ab0a3ea77a8b30dd1c3fe2b62892f9d8c Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Mon, 21 Jul 2008 14:50:07 +0200 Subject: [PATCH 11/14] NV50: Use bios table for load pattern when possible. --- linux-core/nouveau_bios.c | 40 +++++++++++++++++++++++++++++++++++-- linux-core/nv50_connector.c | 1 - linux-core/nv50_dac.c | 9 ++++++++- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/linux-core/nouveau_bios.c b/linux-core/nouveau_bios.c index 3e7fe23f..370d6522 100644 --- a/linux-core/nouveau_bios.c +++ b/linux-core/nouveau_bios.c @@ -128,6 +128,39 @@ struct bit_entry { uint16_t offset; }; +static int parse_bit_A_tbl_entry(struct drm_device *dev, struct bios *bios, struct bit_entry *bitentry) +{ + /* Parses the load detect value table. + * + * Starting at bitentry->offset: + * + * offset + 0 (16 bits): table pointer + */ + + uint16_t load_table_pointer; + + if (bitentry->length != 3) { + DRM_ERROR("Do not understand BIT loadval table\n"); + return 0; + } + + load_table_pointer = le16_to_cpu(*((uint16_t *)(&bios->data[bitentry->offset]))); + + if (load_table_pointer == 0x0) { + DRM_ERROR("Pointer to loadval table invalid\n"); + return 0; + } + + /* Some kind of signature */ + if (bios->data[load_table_pointer] != 16 || bios->data[load_table_pointer + 1] != 4 || + bios->data[load_table_pointer + 2] != 4 || bios->data[load_table_pointer + 3] != 2) + return 0; + + bios->dactestval = le32_to_cpu(*((uint32_t *)&bios->data[load_table_pointer + 4])) & 0x3FF; + + return 1; +} + static int parse_bit_C_tbl_entry(struct drm_device *dev, struct bios *bios, struct bit_entry *bitentry) { /* offset + 8 (16 bits): PLL limits table pointer @@ -136,7 +169,7 @@ static int parse_bit_C_tbl_entry(struct drm_device *dev, struct bios *bios, stru */ if (bitentry->length < 10) { - DRM_ERROR( "Do not understand BIT C table\n"); + DRM_ERROR("Do not understand BIT C table\n"); return 0; } @@ -149,7 +182,7 @@ static void parse_bit_structure(struct drm_device *dev, struct bios *bios, const { int entries = bios->data[bitoffset + 4]; /* parse i first, I next (which needs C & M before it), and L before D */ - char parseorder[] = "iCMILDT"; + char parseorder[] = "iCMILDTA"; struct bit_entry bitentry; int i, j, offset; @@ -164,6 +197,9 @@ static void parse_bit_structure(struct drm_device *dev, struct bios *bios, const continue; switch (bitentry.id[0]) { + case 'A': + parse_bit_A_tbl_entry(dev, bios, &bitentry); + break; case 'C': parse_bit_C_tbl_entry(dev, bios, &bitentry); break; diff --git a/linux-core/nv50_connector.c b/linux-core/nv50_connector.c index 6e5fb912..34706bae 100644 --- a/linux-core/nv50_connector.c +++ b/linux-core/nv50_connector.c @@ -79,7 +79,6 @@ static struct nv50_output *nv50_connector_to_output(struct nv50_connector *conne static int nv50_connector_hpd_detect(struct nv50_connector *connector) { struct drm_nouveau_private *dev_priv = connector->dev->dev_private; - struct nv50_output *output = NULL; bool present = 0; uint32_t reg = 0; diff --git a/linux-core/nv50_dac.c b/linux-core/nv50_dac.c index 3f007166..5dddb469 100644 --- a/linux-core/nv50_dac.c +++ b/linux-core/nv50_dac.c @@ -146,7 +146,14 @@ static int nv50_dac_detect(struct nv50_output *output) NV_WRITE(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(or), 0x00150000 | NV50_PDISPLAY_DAC_REGS_DPMS_CTRL_PENDING); while (NV_READ(NV50_PDISPLAY_DAC_REGS_DPMS_CTRL(or)) & NV50_PDISPLAY_DAC_REGS_DPMS_CTRL_PENDING); - load_pattern = 340; /* TODO: use a bios table for this */ + /* Use bios provided value if possible. */ + if (dev_priv->bios.dactestval) { + load_pattern = dev_priv->bios.dactestval; + NV50_DEBUG("Using bios provided load_pattern of %d\n", load_pattern); + } else { + load_pattern = 340; + NV50_DEBUG("Using default load_pattern of %d\n", load_pattern); + } NV_WRITE(NV50_PDISPLAY_DAC_REGS_LOAD_CTRL(or), NV50_PDISPLAY_DAC_REGS_LOAD_CTRL_ACTIVE | load_pattern); udelay(10000); /* give it some time to process */ From 147ef45873868a0df9216dac0370ada1ed835590 Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Mon, 21 Jul 2008 16:40:55 +0200 Subject: [PATCH 12/14] NV50: don't fail on LVDS by default --- linux-core/nouveau_bios.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/linux-core/nouveau_bios.c b/linux-core/nouveau_bios.c index 370d6522..faa2b2b0 100644 --- a/linux-core/nouveau_bios.c +++ b/linux-core/nouveau_bios.c @@ -341,11 +341,12 @@ parse_dcb_entry(struct drm_device *dev, int index, uint8_t dcb_version, uint16_t entry->lvdsconf.use_power_scripts = true; } if (conf & mask) { - DRM_ERROR( - "Unknown LVDS configuration bits, please report\n"); - /* cause output setting to fail, so message is seen */ - dev_priv->dcb_table.entries = 0; - return false; + if (dcb_version < 0x40) { /* we know g80 cards have unknown bits */ + DRM_ERROR("Unknown LVDS configuration bits, please report\n"); + /* cause output setting to fail, so message is seen */ + dev_priv->dcb_table.entries = 0; + return false; + } } break; } From 4d5b9f484885ac01457f0a8c39b24ca4aac34b5a Mon Sep 17 00:00:00 2001 From: Maarten Maathuis Date: Mon, 21 Jul 2008 16:57:25 +0200 Subject: [PATCH 13/14] NV50: Don't create a "native" mode for LVDS when there is none. --- linux-core/nv50_kms_wrapper.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/linux-core/nv50_kms_wrapper.c b/linux-core/nv50_kms_wrapper.c index 8b27f80b..355d25d6 100644 --- a/linux-core/nv50_kms_wrapper.c +++ b/linux-core/nv50_kms_wrapper.c @@ -1079,6 +1079,10 @@ static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, u NV50_DEBUG("No valid modes on %s\n", drm_get_connector_name(drm_connector)); + /* Making up native modes for LVDS is a bad idea. */ + if (drm_connector->connector_type == DRM_MODE_CONNECTOR_LVDS) + return; + /* 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 From 53428453758621da70d9608c9baec58b4b9383ec Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Thu, 24 Jul 2008 15:22:44 +1000 Subject: [PATCH 14/14] drm: use correct mode destructor --- linux-core/drm_modes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linux-core/drm_modes.c b/linux-core/drm_modes.c index 4ee00305..d4cda0be 100644 --- a/linux-core/drm_modes.c +++ b/linux-core/drm_modes.c @@ -398,7 +398,7 @@ void drm_mode_prune_invalid(struct drm_device *dev, drm_mode_debug_printmodeline(mode); DRM_DEBUG("Not using %s mode %d\n", mode->name, mode->status); } - kfree(mode); + drm_mode_destroy(dev, mode); } } } @@ -556,7 +556,7 @@ void drm_mode_connector_list_update(struct drm_connector *connector) /* if equal delete the probed mode */ mode->status = pmode->status; list_del(&pmode->head); - kfree(pmode); + drm_mode_destroy(connector->dev, pmode); break; } }