summaryrefslogtreecommitdiff
path: root/linux-core/nv50_kms_wrapper.c
diff options
context:
space:
mode:
authorMaarten Maathuis <madman2003@gmail.com>2008-06-22 16:29:00 +0200
committerMaarten Maathuis <madman2003@gmail.com>2008-06-22 16:29:00 +0200
commit473a1997ace1a9fb545d0457549e50d17eb36175 (patch)
tree93ed8c730a8be1de2564088c4f5aaafc667a1263 /linux-core/nv50_kms_wrapper.c
parent7010d500072977f63a0bac08f2141d69dbd19595 (diff)
NV50: Initial import of kernel modesetting.
Diffstat (limited to 'linux-core/nv50_kms_wrapper.c')
-rw-r--r--linux-core/nv50_kms_wrapper.c960
1 files changed, 960 insertions, 0 deletions
diff --git a/linux-core/nv50_kms_wrapper.c b/linux-core/nv50_kms_wrapper.c
new file mode 100644
index 00000000..01d4bc9a
--- /dev/null
+++ b/linux-core/nv50_kms_wrapper.c
@@ -0,0 +1,960 @@
+/*
+ * Copyright (C) 2008 Maarten Maathuis.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS 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 "nv50_kms_wrapper.h"
+#include "drm_crtc_helper.h" /* be careful what you use from this */
+
+/* This file serves as the interface between the common kernel modesetting code and the device dependent implementation. */
+
+/*
+ * Get private functions.
+ */
+
+struct nv50_kms_priv *nv50_get_kms_priv(struct drm_device *dev)
+{
+ struct drm_nouveau_private *dev_priv = dev->dev_private;
+ return dev_priv->kms_priv;
+}
+
+/*
+ * Allocation functions.
+ */
+
+static void *nv50_kms_alloc_crtc(struct drm_device *dev)
+{
+ struct nv50_kms_priv *kms_priv = nv50_get_kms_priv(dev);
+ struct nv50_kms_crtc *crtc = kzalloc(sizeof(struct nv50_kms_crtc), GFP_KERNEL);
+
+ list_add_tail(&crtc->head, &kms_priv->crtcs);
+
+ return &(crtc->priv);
+}
+
+static void *nv50_kms_alloc_output(struct drm_device *dev)
+{
+ struct nv50_kms_priv *kms_priv = nv50_get_kms_priv(dev);
+ struct nv50_kms_encoder *encoder = kzalloc(sizeof(struct nv50_kms_encoder), GFP_KERNEL);
+
+ list_add_tail(&encoder->head, &kms_priv->encoders);
+
+ return &(encoder->priv);
+}
+
+static void *nv50_kms_alloc_connector(struct drm_device *dev)
+{
+ struct nv50_kms_priv *kms_priv = nv50_get_kms_priv(dev);
+ struct nv50_kms_connector *connector = kzalloc(sizeof(struct nv50_kms_connector), GFP_KERNEL);
+
+ list_add_tail(&connector->head, &kms_priv->connectors);
+
+ return &(connector->priv);
+}
+
+static void nv50_kms_free_crtc(void *crtc)
+{
+ struct nv50_kms_crtc *kms_crtc = from_nv50_crtc(crtc);
+
+ list_del(&kms_crtc->head);
+
+ kfree(kms_crtc);
+}
+
+static void nv50_kms_free_output(void *output)
+{
+ struct nv50_kms_encoder *kms_encoder = from_nv50_output(output);
+
+ list_del(&kms_encoder->head);
+
+ kfree(kms_encoder);
+}
+
+static void nv50_kms_free_connector(void *connector)
+{
+ struct nv50_kms_connector *kms_connector = from_nv50_connector(connector);
+
+ list_del(&kms_connector->head);
+
+ kfree(kms_connector);
+}
+
+/*
+ * Mode conversion functions.
+ */
+
+static struct nouveau_hw_mode *nv50_kms_to_hw_mode(struct drm_display_mode *mode)
+{
+ struct nouveau_hw_mode *hw_mode = kzalloc(sizeof(struct nouveau_hw_mode), GFP_KERNEL);
+
+ /* create hw values. */
+ hw_mode->clock = mode->clock;
+ hw_mode->flags = hw_mode->flags;
+
+ hw_mode->hdisplay = mode->hdisplay;
+ hw_mode->hsync_start = mode->hsync_start;
+ hw_mode->hsync_end = mode->hsync_end;
+ hw_mode->htotal = mode->htotal;
+
+ hw_mode->hblank_start = mode->hdisplay + 1;
+ hw_mode->hblank_end = mode->htotal;
+
+ hw_mode->vdisplay = mode->vdisplay;
+ hw_mode->vsync_start = mode->vsync_start;
+ hw_mode->vsync_end = mode->vsync_end;
+ hw_mode->vtotal = mode->vtotal;
+
+ hw_mode->vblank_start = mode->vdisplay + 1;
+ hw_mode->vblank_end = mode->vtotal;
+
+ return hw_mode;
+}
+
+/*
+ * State mirroring functions.
+ */
+
+static void nv50_kms_mirror_routing(struct drm_device *dev)
+{
+ struct nv50_display *display = nv50_get_display(dev);
+ struct nv50_crtc *crtc = NULL;
+ struct nv50_output *output = NULL;
+ struct nv50_connector *connector = NULL;
+ struct drm_connector *drm_connector = NULL;
+
+ /* Wipe all previous connections. */
+ list_for_each_entry(connector, &display->connectors, head) {
+ connector->output = NULL;
+ }
+
+ list_for_each_entry(output, &display->outputs, head) {
+ output->crtc = NULL;
+ }
+
+ list_for_each_entry(drm_connector, &dev->mode_config.connector_list, head) {
+ if (drm_connector->encoder) {
+ output = to_nv50_output(drm_connector->encoder);
+ connector = to_nv50_connector(drm_connector);
+
+ /* hook up output to connector. */
+ connector->output = output;
+
+ if (drm_connector->encoder->crtc) {
+ crtc = to_nv50_crtc(drm_connector->encoder->crtc);
+
+ /* hook up output to crtc. */
+ output->crtc = crtc;
+ }
+ }
+ }
+}
+
+/*
+ * FB functions.
+ */
+
+static void nv50_kms_framebuffer_destroy(struct drm_framebuffer *drm_framebuffer)
+{
+ drm_framebuffer_cleanup(drm_framebuffer);
+
+ kfree(drm_framebuffer);
+}
+
+static const struct drm_framebuffer_funcs nv50_kms_fb_funcs = {
+ .destroy = nv50_kms_framebuffer_destroy,
+};
+
+/*
+ * Mode config functions.
+ */
+
+static struct drm_framebuffer *nv50_kms_framebuffer_create(struct drm_device *dev,
+ struct drm_file *file_priv, struct drm_mode_fb_cmd *mode_cmd)
+{
+ struct drm_framebuffer *drm_framebuffer = kzalloc(sizeof(struct drm_framebuffer), GFP_KERNEL);
+ if (!drm_framebuffer)
+ return NULL;
+
+ drm_framebuffer_init(dev, drm_framebuffer, &nv50_kms_fb_funcs);
+ drm_helper_mode_fill_fb_struct(drm_framebuffer, mode_cmd);
+
+ return drm_framebuffer;
+}
+
+static int nv50_kms_fb_changed(struct drm_device *dev)
+{
+ return 0; /* not needed until nouveaufb? */
+}
+
+static const struct drm_mode_config_funcs nv50_kms_mode_funcs = {
+ .resize_fb = NULL,
+ .fb_create = nv50_kms_framebuffer_create,
+ .fb_changed = nv50_kms_fb_changed,
+};
+
+/*
+ * CRTC functions.
+ */
+
+static int nv50_kms_crtc_cursor_set(struct drm_crtc *drm_crtc, uint32_t buffer_handle,
+ uint32_t width, uint32_t height)
+{
+ struct nv50_crtc *crtc = to_nv50_crtc(drm_crtc);
+ struct nv50_display *display = nv50_get_display(crtc->dev);
+ int rval;
+
+ if (width != 64 || height != 64)
+ return -EINVAL;
+
+ rval = crtc->cursor->set_bo(crtc, (drm_handle_t) buffer_handle);
+
+ if (rval != 0)
+ return rval;
+
+ /* in case this triggers any other cursor changes */
+ display->update(display);
+
+ return 0;
+}
+
+static int nv50_kms_crtc_cursor_move(struct drm_crtc *drm_crtc, int x, int y)
+{
+ struct nv50_crtc *crtc = to_nv50_crtc(drm_crtc);
+
+ return crtc->cursor->set_pos(crtc, x, y);
+}
+
+void nv50_kms_crtc_gamma_set(struct drm_crtc *drm_crtc, u16 *r, u16 *g, u16 *b,
+ uint32_t size)
+{
+ struct nv50_crtc *crtc = to_nv50_crtc(drm_crtc);
+
+ if (size != 256)
+ return;
+
+ crtc->lut->set(crtc, (uint16_t *)r, (uint16_t *)g, (uint16_t *)b);
+}
+
+int nv50_kms_crtc_set_config(struct drm_mode_set *set)
+{
+ int rval = 0, i;
+ uint32_t crtc_mask = 0;
+ struct drm_device *dev = NULL;
+ struct drm_nouveau_private *dev_priv = NULL;
+ struct nv50_display *display = NULL;
+ struct drm_connector *drm_connector = NULL;
+ struct drm_encoder *drm_encoder = NULL;
+ struct drm_crtc *drm_crtc = NULL;
+
+ struct nv50_crtc *crtc = NULL;
+ struct nv50_output *output = NULL;
+ struct nv50_connector *connector = NULL;
+ struct nouveau_hw_mode *hw_mode = NULL;
+ struct nv50_fb_info fb_info;
+
+ NV50_DEBUG("\n");
+
+ /*
+ * Initial approach is very simple, always set a mode.
+ * Always bail out completely if something is wrong.
+ * Later this could be extended to be more smart.
+ */
+
+ /* Sanity checking */
+ if (!set) {
+ NV50_DEBUG("Sanity check failed\n");
+ goto out;
+ }
+
+ if (!set->crtc || !set->fb || !set->mode || !set->connectors) {
+ NV50_DEBUG("Sanity check failed\n");
+ goto out;
+ }
+
+ /* Basic variable setting */
+ dev = set->crtc->dev;
+ dev_priv = dev->dev_private;
+ display = nv50_get_display(dev);
+ crtc = to_nv50_crtc(set->crtc);
+
+ /* Mode validation */
+ hw_mode = nv50_kms_to_hw_mode(set->mode);
+
+ rval = crtc->validate_mode(crtc, hw_mode);
+
+ if (rval != MODE_OK) {
+ NV50_DEBUG("Mode not ok\n");
+ goto out;
+ }
+
+ for (i = 0; i < set->num_connectors; i++) {
+ drm_connector = set->connectors[i];
+ if (!drm_connector) {
+ NV50_DEBUG("No connector\n");
+ goto out;
+ }
+ connector = to_nv50_connector(drm_connector);
+
+ output = connector->to_output(connector, connector->digital);
+ if (!output) {
+ NV50_DEBUG("No output\n");
+ goto out;
+ }
+
+ rval = output->validate_mode(output, hw_mode);
+ if (rval != MODE_OK) {
+ NV50_DEBUG("Mode not ok\n");
+ goto out;
+ }
+ }
+
+ /* Validation done, move on to cleaning of existing structures. */
+
+ /* find encoders that use this crtc. */
+ list_for_each_entry(drm_encoder, &dev->mode_config.encoder_list, head) {
+ if (drm_encoder->crtc == set->crtc) {
+ /* find the connector that goes with it */
+ list_for_each_entry(drm_connector, &dev->mode_config.connector_list, head) {
+ if (drm_connector->encoder == drm_encoder) {
+ drm_connector->encoder = NULL;
+ break;
+ }
+ }
+ drm_encoder->crtc = NULL;
+ }
+ }
+
+ /* now find if our desired encoders or connectors are in use already. */
+ for (i = 0; i < set->num_connectors; i++) {
+ drm_connector = set->connectors[i];
+ if (!drm_connector) {
+ NV50_DEBUG("No connector\n");
+ goto out;
+ }
+
+ if (!drm_connector->encoder)
+ continue;
+
+ drm_encoder = drm_connector->encoder;
+ drm_connector->encoder = NULL;
+
+ if (!drm_encoder->crtc)
+ continue;
+
+ drm_crtc = drm_encoder->crtc;
+ drm_encoder->crtc = NULL;
+
+ crtc = to_nv50_crtc(drm_crtc);
+ crtc->active = false;
+ drm_crtc->enabled = false;
+ }
+
+ /* set framebuffer */
+ set->crtc->fb = set->fb;
+
+ /* Time to wire up the public encoder, the private one will be handled later. */
+ for (i = 0; i < set->num_connectors; i++) {
+ drm_connector = set->connectors[i];
+ if (!drm_connector) {
+ NV50_DEBUG("No connector\n");
+ goto out;
+ }
+
+ output = connector->to_output(connector, connector->digital);
+ if (!output) {
+ NV50_DEBUG("No output\n");
+ goto out;
+ }
+
+ /* find the encoder public structure that matches out output structure. */
+ drm_encoder = to_nv50_kms_encoder(output);
+
+ if (!drm_encoder) {
+ NV50_DEBUG("No encoder\n");
+ goto out;
+ }
+
+
+ drm_encoder->crtc = set->crtc;
+ drm_connector->encoder = drm_encoder;
+ }
+
+ /* mirror everything to the private structs */
+ nv50_kms_mirror_routing(dev);
+
+ /* set private framebuffer */
+ crtc = to_nv50_crtc(set->crtc);
+ fb_info.block = find_block_by_handle(dev_priv->fb_heap, set->fb->mm_handle);
+ fb_info.width = set->fb->width;
+ fb_info.height = set->fb->height;
+ fb_info.depth = set->fb->depth;
+ fb_info.bpp = set->fb->bits_per_pixel;
+ fb_info.x = set->x;
+ fb_info.y = set->y;
+
+ rval = crtc->fb->bind(crtc, &fb_info);
+ if (rval != 0) {
+ NV50_DEBUG("fb_bind failed\n");
+ goto out;
+ }
+
+ if (!crtc->cursor->enabled) {
+ rval = crtc->cursor->enable(crtc);
+ if (rval != 0) {
+ NV50_DEBUG("cursor_enable failed\n");
+ goto out;
+ }
+ }
+
+ /* modeset time, finally */
+
+ /* disconnect unused outputs */
+ list_for_each_entry(output, &display->outputs, head) {
+ if (output->crtc)
+ crtc_mask |= 1 << output->crtc->index;
+ else
+ output->execute_mode(output, TRUE);
+ }
+
+ rval = crtc->set_mode(crtc, hw_mode);
+ if (rval != 0) {
+ NV50_DEBUG("crtc mode set failed\n");
+ goto out;
+ }
+
+ /* find native mode. */
+ list_for_each_entry(output, &display->outputs, head) {
+ if (output->crtc != crtc)
+ continue;
+
+ *crtc->native_mode = *output->native_mode;
+ list_for_each_entry(connector, &display->connectors, head) {
+ if (connector->output != output)
+ continue;
+
+ crtc->scaling_mode = connector->scaling_mode;
+ break;
+ }
+
+ if (crtc->scaling_mode == SCALE_PANEL)
+ crtc->use_native_mode = false;
+ else
+ crtc->use_native_mode = true;
+
+ break; /* no use in finding more than one mode */
+ }
+
+ rval = crtc->execute_mode(crtc);
+ if (rval != 0) {
+ NV50_DEBUG("crtc execute mode failed\n");
+ goto out;
+ }
+
+ list_for_each_entry(output, &display->outputs, head) {
+ if (output->crtc != crtc)
+ continue;
+
+ rval = output->execute_mode(output, FALSE);
+ if (rval != 0) {
+ NV50_DEBUG("output execute mode failed\n");
+ goto out;
+ }
+ }
+
+ rval = crtc->set_scale(crtc);
+ if (rval != 0) {
+ NV50_DEBUG("crtc set scale failed\n");
+ goto out;
+ }
+
+ /* next line changes crtc, so putting it here is important */
+ display->last_crtc = crtc->index;
+
+ /* blank any unused crtcs */
+ list_for_each_entry(crtc, &display->crtcs, head) {
+ if (!(crtc_mask & (1 << crtc->index)))
+ crtc->blank(crtc, TRUE);
+ }
+
+ display->update(display);
+
+ kfree(hw_mode);
+
+ return 0;
+
+out:
+ display->update(display);
+
+ kfree(hw_mode);
+
+ if (rval != 0)
+ return rval;
+ else
+ return -EINVAL;
+}
+
+static void nv50_kms_crtc_destroy(struct drm_crtc *drm_crtc)
+{
+ struct nv50_crtc *crtc = to_nv50_crtc(drm_crtc);
+
+ drm_crtc_cleanup(drm_crtc);
+
+ /* this will even destroy the public structure. */
+ crtc->destroy(crtc);
+}
+
+static const struct drm_crtc_funcs nv50_kms_crtc_funcs = {
+ .save = NULL,
+ .restore = NULL,
+ .cursor_set = nv50_kms_crtc_cursor_set,
+ .cursor_move = nv50_kms_crtc_cursor_move,
+ .gamma_set = nv50_kms_crtc_gamma_set,
+ .set_config = nv50_kms_crtc_set_config,
+ .destroy = nv50_kms_crtc_destroy,
+};
+
+static int nv50_kms_crtcs_init(struct drm_device *dev)
+{
+ struct nv50_display *display = nv50_get_display(dev);
+ struct nv50_crtc *crtc = NULL;
+
+ /*
+ * This may look a bit confusing, but:
+ * The internal structure is already allocated and so is the public one.
+ * Just a matter of getting to the memory and register it.
+ */
+ list_for_each_entry(crtc, &display->crtcs, head) {
+ struct drm_crtc *drm_crtc = to_nv50_kms_crtc(crtc);
+
+ drm_crtc_init(dev, drm_crtc, &nv50_kms_crtc_funcs);
+ }
+
+ return 0;
+}
+
+/*
+ * Encoder functions
+ */
+
+static void nv50_kms_encoder_destroy(struct drm_encoder *drm_encoder)
+{
+ struct nv50_output *output = to_nv50_output(drm_encoder);
+
+ drm_encoder_cleanup(drm_encoder);
+
+ /* this will even destroy the public structure. */
+ output->destroy(output);
+}
+
+static const struct drm_encoder_funcs nv50_kms_encoder_funcs = {
+ .destroy = nv50_kms_encoder_destroy,
+};
+
+static int nv50_kms_encoders_init(struct drm_device *dev)
+{
+ struct nv50_display *display = nv50_get_display(dev);
+ struct nv50_output *output = NULL;
+
+ list_for_each_entry(output, &display->outputs, head) {
+ struct drm_encoder *drm_encoder = to_nv50_kms_encoder(output);
+ uint32_t type = DRM_MODE_ENCODER_NONE;
+
+ switch (output->type) {
+ case OUTPUT_DAC:
+ type = DRM_MODE_ENCODER_DAC;
+ break;
+ case OUTPUT_TMDS:
+ type = DRM_MODE_ENCODER_TMDS;
+ break;
+ case OUTPUT_LVDS:
+ type = DRM_MODE_ENCODER_LVDS;
+ break;
+ case OUTPUT_TV:
+ type = DRM_MODE_ENCODER_TVDAC;
+ break;
+ default:
+ type = DRM_MODE_ENCODER_NONE;
+ break;
+ }
+
+ if (type == DRM_MODE_ENCODER_NONE) {
+ DRM_ERROR("DRM_MODE_ENCODER_NONE encountered\n");
+ continue;
+ }
+
+ drm_encoder_init(dev, drm_encoder, &nv50_kms_encoder_funcs, type);
+
+ /* I've never seen possible crtc's restricted. */
+ drm_encoder->possible_crtcs = 3;
+ drm_encoder->possible_clones = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * Connector functions
+ */
+
+void nv50_kms_connector_detect_all(struct drm_device *dev)
+{
+ struct drm_connector *drm_connector = NULL;
+ enum drm_connector_status old, new;
+ bool notify = false;
+
+ list_for_each_entry(drm_connector, &dev->mode_config.connector_list, head) {
+ old = drm_connector->status;
+ new = drm_connector->funcs->detect(drm_connector);
+
+ if (new != old) {
+ notify = true;
+ drm_connector->funcs->fill_modes(drm_connector, 0, 0);
+ }
+ }
+
+ /* I think this is the hook that notifies of changes. */
+ if (notify)
+ dev->mode_config.funcs->fb_changed(dev);
+}
+
+static enum drm_connector_status nv50_kms_connector_detect(struct drm_connector *drm_connector)
+{
+ struct nv50_connector *connector = to_nv50_connector(drm_connector);
+ bool connected;
+
+ connected = connector->detect(connector);
+
+ if (connected)
+ drm_connector->status = connector_status_connected;
+ else
+ drm_connector->status = connector_status_disconnected;
+
+ return drm_connector->status;
+}
+
+static void nv50_kms_connector_destroy(struct drm_connector *drm_connector)
+{
+ struct nv50_connector *connector = to_nv50_connector(drm_connector);
+
+ drm_sysfs_connector_remove(drm_connector);
+ drm_connector_cleanup(drm_connector);
+
+ /* this will even destroy the public structure. */
+ connector->destroy(connector);
+}
+
+/*
+ * Detailed mode info for a standard 640x480@60Hz monitor
+ */
+static struct drm_display_mode std_mode[] = {
+ /*{ DRM_MODE("640x480", DRM_MODE_TYPE_DEFAULT, 25200, 640, 656,
+ 752, 800, 0, 480, 490, 492, 525, 0,
+ V_NHSYNC | V_NVSYNC) },*/ /* 640x480@60Hz */
+ { DRM_MODE("1280x1024", DRM_MODE_TYPE_DEFAULT, 135000, 1280, 1296,
+ 1440, 1688, 0, 1024, 1025, 1028, 1066, 0,
+ V_PHSYNC | V_PVSYNC) }, /* 1280x1024@75Hz */
+};
+
+static void nv50_kms_connector_fill_modes(struct drm_connector *drm_connector, uint32_t maxX, uint32_t maxY)
+{
+ struct nv50_connector *connector = to_nv50_connector(drm_connector);
+ int ret = 0;
+ bool connected;
+ struct drm_display_mode *mode, *t;
+ struct edid *edid = NULL;
+
+ DRM_DEBUG("%s\n", drm_get_connector_name(drm_connector));
+ /* set all modes to the unverified state */
+ list_for_each_entry_safe(mode, t, &drm_connector->modes, head)
+ mode->status = MODE_UNVERIFIED;
+
+ connected = connector->detect(connector);
+
+ if (connected)
+ drm_connector->status = connector_status_connected;
+ else
+ drm_connector->status = connector_status_disconnected;
+
+ if (!connected) {
+ DRM_DEBUG("%s is disconnected\n", drm_get_connector_name(drm_connector));
+ /* TODO set EDID to NULL */
+ return;
+ }
+
+ /* Not all connnectors have an i2c channel. */
+ if (connector->i2c_chan)
+ edid = (struct edid *) drm_do_probe_ddc_edid(&connector->i2c_chan->adapter);
+
+ if (edid) {
+ drm_mode_connector_update_edid_property(drm_connector, edid);
+ ret = drm_add_edid_modes(drm_connector, edid);
+ connector->digital = edid->digital; /* cache */
+ }
+
+ if (ret) /* number of modes > 1 */
+ drm_mode_connector_list_update(drm_connector);
+
+ if (maxX && maxY)
+ drm_mode_validate_size(drm_connector->dev, &drm_connector->modes, maxX, maxY, 0);
+
+ 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, connector->digital);
+
+ mode->status = output->validate_mode(output, hw_mode);
+ /* find native mode, TODO: also check if we actually found one */
+ if (mode->status == MODE_OK) {
+ if (mode->type & DRM_MODE_TYPE_PREFERRED)
+ *output->native_mode = *hw_mode;
+ }
+ kfree(hw_mode);
+ }
+ }
+
+ /* revalidate now that we have native mode */
+ 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, connector->digital);
+
+ mode->status = output->validate_mode(output, hw_mode);
+ kfree(hw_mode);
+ }
+ }
+
+ drm_mode_prune_invalid(drm_connector->dev, &drm_connector->modes, TRUE);
+
+ if (list_empty(&drm_connector->modes)) {
+ struct drm_display_mode *stdmode;
+ struct nouveau_hw_mode *hw_mode;
+ struct nv50_output *output;
+
+ DRM_DEBUG("No valid modes on %s\n", drm_get_connector_name(drm_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(drm_connector->dev, &std_mode[0]);
+ drm_mode_probed_add(drm_connector, stdmode);
+ drm_mode_list_concat(&drm_connector->probed_modes,
+ &drm_connector->modes);
+
+ /* also add it as native mode */
+ hw_mode = nv50_kms_to_hw_mode(mode);
+ output = connector->to_output(connector, connector->digital);
+
+ if (hw_mode)
+ *output->native_mode = *hw_mode;
+
+ DRM_DEBUG("Adding standard 640x480 @ 60Hz to %s\n",
+ drm_get_connector_name(drm_connector));
+ }
+
+ drm_mode_sort(&drm_connector->modes);
+
+ DRM_DEBUG("Probed modes for %s\n", drm_get_connector_name(drm_connector));
+
+ list_for_each_entry_safe(mode, t, &drm_connector->modes, head) {
+ mode->vrefresh = drm_mode_vrefresh(mode);
+
+ /* is this needed, as it's unused by the driver? */
+ drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
+ drm_mode_debug_printmodeline(mode);
+ }
+}
+
+static const struct drm_connector_funcs nv50_kms_connector_funcs = {
+ .save = NULL,
+ .restore = NULL,
+ .detect = nv50_kms_connector_detect,
+ .destroy = nv50_kms_connector_destroy,
+ .fill_modes = nv50_kms_connector_fill_modes,
+};
+
+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;
+
+ list_for_each_entry(connector, &display->connectors, head) {
+ struct drm_connector *drm_connector = to_nv50_kms_connector(connector);
+ uint32_t type = DRM_MODE_CONNECTOR_Unknown;
+
+ switch (connector->type) {
+ case CONNECTOR_VGA:
+ type = DRM_MODE_CONNECTOR_VGA;
+ break;
+ case CONNECTOR_DVI_D:
+ type = DRM_MODE_CONNECTOR_DVID;
+ break;
+ case CONNECTOR_DVI_I:
+ type = DRM_MODE_CONNECTOR_DVII;
+ break;
+ case CONNECTOR_LVDS:
+ type = DRM_MODE_CONNECTOR_LVDS;
+ break;
+ case CONNECTOR_TV:
+ type = DRM_MODE_CONNECTOR_SVIDEO;
+ break;
+ default:
+ type = DRM_MODE_CONNECTOR_Unknown;
+ break;
+ }
+
+ if (type == DRM_MODE_CONNECTOR_Unknown) {
+ DRM_ERROR("DRM_MODE_CONNECTOR_Unknown encountered\n");
+ continue;
+ }
+
+ /* It should be allowed sometimes, but let's be safe for the moment. */
+ drm_connector->interlace_allowed = false;
+ drm_connector->doublescan_allowed = false;
+
+ drm_connector_init(dev, drm_connector, &nv50_kms_connector_funcs, type);
+
+ /* attach encoders, possibilities are analog + digital */
+ for (i = 0; i < 2; i++) {
+ struct drm_encoder *drm_encoder = NULL;
+ struct nv50_output *output = connector->to_output(connector, i);
+ if (!output)
+ continue;
+
+ drm_encoder = to_nv50_kms_encoder(output);
+ if (!drm_encoder) {
+ DRM_ERROR("No struct drm_connector to match struct nv50_output\n");
+ continue;
+ }
+
+ drm_mode_connector_attach_encoder(drm_connector, drm_encoder);
+ }
+
+ drm_sysfs_connector_add(drm_connector);
+ }
+
+ return 0;
+}
+
+/*
+ * Main functions
+ */
+
+int nv50_kms_init(struct drm_device *dev)
+{
+ struct drm_nouveau_private *dev_priv = dev->dev_private;
+ struct nv50_kms_priv *kms_priv = kzalloc(sizeof(struct nv50_kms_priv), GFP_KERNEL);
+ struct nv50_display *display = NULL;
+ int rval = 0;
+
+ dev_priv->kms_priv = kms_priv;
+
+ /* function pointers */
+ /* an allocation interface that deals with the outside world, without polluting the core. */
+ dev_priv->alloc_crtc = nv50_kms_alloc_crtc;
+ dev_priv->alloc_output = nv50_kms_alloc_output;
+ dev_priv->alloc_connector = nv50_kms_alloc_connector;
+
+ dev_priv->free_crtc = nv50_kms_free_crtc;
+ dev_priv->free_output = nv50_kms_free_output;
+ dev_priv->free_connector = nv50_kms_free_connector;
+
+ /* bios is needed for tables. */
+ rval = nouveau_parse_bios(dev);
+ if (rval != 0)
+ goto out;
+
+ /* init basic kernel modesetting */
+ drm_mode_config_init(dev);
+
+ dev->mode_config.min_width = 0;
+ dev->mode_config.min_height = 0;
+
+ dev->mode_config.funcs = (void *)&nv50_kms_mode_funcs;
+
+ dev->mode_config.max_width = 8192;
+ dev->mode_config.max_height = 8192;
+
+ dev->mode_config.fb_base = dev_priv->fb_phys;
+
+ /* init kms lists */
+ INIT_LIST_HEAD(&kms_priv->crtcs);
+ INIT_LIST_HEAD(&kms_priv->encoders);
+ INIT_LIST_HEAD(&kms_priv->connectors);
+
+ /* init the internal core, must be done first. */
+ rval = nv50_display_create(dev);
+ if (rval != 0)
+ goto out;
+
+ display = nv50_get_display(dev);
+ if (!display) {
+ rval = -EINVAL;
+ goto out;
+ }
+
+ /* pre-init now */
+ rval = display->pre_init(display);
+ if (rval != 0)
+ goto out;
+
+ /* init external layer */
+ rval = nv50_kms_crtcs_init(dev);
+ if (rval != 0)
+ goto out;
+
+ rval = nv50_kms_encoders_init(dev);
+ if (rval != 0)
+ goto out;
+
+ rval = nv50_kms_connectors_init(dev);
+ if (rval != 0)
+ goto out;
+
+ /* init now, this'll kill the textmode */
+ rval = display->init(display);
+ if (rval != 0)
+ goto out;
+
+ /* process cmdbuffer */
+ display->update(display);
+
+ return 0;
+
+out:
+ kfree(kms_priv);
+ dev_priv->kms_priv = NULL;
+
+ return rval;
+}
+
+int nv50_kms_destroy(struct drm_device *dev)
+{
+ drm_mode_config_cleanup(dev);
+
+ return 0;
+}
+