drm: fix plane rotation when restoring fbdev configuration
[deliverable/linux.git] / drivers / gpu / drm / drm_fb_helper.c
index d5d8cea1a67996a4a3e61ebc657e68ec7ae6d9b6..99569ee5adee30fb372731dcaf7833eb057a62f1 100644 (file)
@@ -49,10 +49,11 @@ static LIST_HEAD(kernel_fb_helper_list);
  * helper functions used by many drivers to implement the kernel mode setting
  * interfaces.
  *
- * Initialization is done as a three-step process with drm_fb_helper_init(),
- * drm_fb_helper_single_add_all_connectors() and drm_fb_helper_initial_config().
- * Drivers with fancier requirements than the default behaviour can override the
- * second step with their own code.  Teardown is done with drm_fb_helper_fini().
+ * Initialization is done as a four-step process with drm_fb_helper_prepare(),
+ * drm_fb_helper_init(), drm_fb_helper_single_add_all_connectors() and
+ * drm_fb_helper_initial_config(). Drivers with fancier requirements than the
+ * default behaviour can override the third step with their own code.
+ * Teardown is done with drm_fb_helper_fini().
  *
  * At runtime drivers should restore the fbdev console by calling
  * drm_fb_helper_restore_fbdev_mode() from their ->lastclose callback. They
@@ -63,6 +64,19 @@ static LIST_HEAD(kernel_fb_helper_list);
  *
  * All other functions exported by the fb helper library can be used to
  * implement the fbdev driver interface by the driver.
+ *
+ * It is possible, though perhaps somewhat tricky, to implement race-free
+ * hotplug detection using the fbdev helpers. The drm_fb_helper_prepare()
+ * helper must be called first to initialize the minimum required to make
+ * hotplug detection work. Drivers also need to make sure to properly set up
+ * the dev->mode_config.funcs member. After calling drm_kms_helper_poll_init()
+ * it is safe to enable interrupts and start processing hotplug events. At the
+ * same time, drivers should initialize all modeset objects such as CRTCs,
+ * encoders and connectors. To finish up the fbdev helper initialization, the
+ * drm_fb_helper_init() function is called. To probe for all attached displays
+ * and set up an initial configuration using the detected hardware, drivers
+ * should call drm_fb_helper_single_add_all_connectors() followed by
+ * drm_fb_helper_initial_config().
  */
 
 /**
@@ -105,6 +119,58 @@ fail:
 }
 EXPORT_SYMBOL(drm_fb_helper_single_add_all_connectors);
 
+int drm_fb_helper_add_one_connector(struct drm_fb_helper *fb_helper, struct drm_connector *connector)
+{
+       struct drm_fb_helper_connector **temp;
+       struct drm_fb_helper_connector *fb_helper_connector;
+
+       WARN_ON(!mutex_is_locked(&fb_helper->dev->mode_config.mutex));
+       if (fb_helper->connector_count + 1 > fb_helper->connector_info_alloc_count) {
+               temp = krealloc(fb_helper->connector_info, sizeof(struct drm_fb_helper_connector) * (fb_helper->connector_count + 1), GFP_KERNEL);
+               if (!temp)
+                       return -ENOMEM;
+
+               fb_helper->connector_info_alloc_count = fb_helper->connector_count + 1;
+               fb_helper->connector_info = temp;
+       }
+
+
+       fb_helper_connector = kzalloc(sizeof(struct drm_fb_helper_connector), GFP_KERNEL);
+       if (!fb_helper_connector)
+               return -ENOMEM;
+
+       fb_helper_connector->connector = connector;
+       fb_helper->connector_info[fb_helper->connector_count++] = fb_helper_connector;
+       return 0;
+}
+EXPORT_SYMBOL(drm_fb_helper_add_one_connector);
+
+int drm_fb_helper_remove_one_connector(struct drm_fb_helper *fb_helper,
+                                      struct drm_connector *connector)
+{
+       struct drm_fb_helper_connector *fb_helper_connector;
+       int i, j;
+
+       WARN_ON(!mutex_is_locked(&fb_helper->dev->mode_config.mutex));
+
+       for (i = 0; i < fb_helper->connector_count; i++) {
+               if (fb_helper->connector_info[i]->connector == connector)
+                       break;
+       }
+
+       if (i == fb_helper->connector_count)
+               return -EINVAL;
+       fb_helper_connector = fb_helper->connector_info[i];
+
+       for (j = i + 1; j < fb_helper->connector_count; j++) {
+               fb_helper->connector_info[j - 1] = fb_helper->connector_info[j];
+       }
+       fb_helper->connector_count--;
+       kfree(fb_helper_connector);
+       return 0;
+}
+EXPORT_SYMBOL(drm_fb_helper_remove_one_connector);
+
 static int drm_fb_helper_parse_command_line(struct drm_fb_helper *fb_helper)
 {
        struct drm_fb_helper_connector *fb_helper_conn;
@@ -199,9 +265,6 @@ int drm_fb_helper_debug_enter(struct fb_info *info)
        struct drm_crtc_helper_funcs *funcs;
        int i;
 
-       if (list_empty(&kernel_fb_helper_list))
-               return false;
-
        list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
                for (i = 0; i < helper->crtc_count; i++) {
                        struct drm_mode_set *mode_set =
@@ -282,10 +345,17 @@ static bool restore_fbdev_mode(struct drm_fb_helper *fb_helper)
 
        drm_warn_on_modeset_not_all_locked(dev);
 
-       list_for_each_entry(plane, &dev->mode_config.plane_list, head)
+       list_for_each_entry(plane, &dev->mode_config.plane_list, head) {
                if (plane->type != DRM_PLANE_TYPE_PRIMARY)
                        drm_plane_force_disable(plane);
 
+               if (dev->mode_config.rotation_property) {
+                       drm_mode_plane_set_obj_prop(plane,
+                                                   dev->mode_config.rotation_property,
+                                                   BIT(DRM_ROTATE_0));
+               }
+       }
+
        for (i = 0; i < fb_helper->crtc_count; i++) {
                struct drm_mode_set *mode_set = &fb_helper->crtc_info[i].mode_set;
                struct drm_crtc *crtc = mode_set->crtc;
@@ -530,6 +600,24 @@ static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper)
        kfree(helper->crtc_info);
 }
 
+/**
+ * drm_fb_helper_prepare - setup a drm_fb_helper structure
+ * @dev: DRM device
+ * @helper: driver-allocated fbdev helper structure to set up
+ * @funcs: pointer to structure of functions associate with this helper
+ *
+ * Sets up the bare minimum to make the framebuffer helper usable. This is
+ * useful to implement race-free initialization of the polling helpers.
+ */
+void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
+                          const struct drm_fb_helper_funcs *funcs)
+{
+       INIT_LIST_HEAD(&helper->kernel_fb_list);
+       helper->funcs = funcs;
+       helper->dev = dev;
+}
+EXPORT_SYMBOL(drm_fb_helper_prepare);
+
 /**
  * drm_fb_helper_init - initialize a drm_fb_helper structure
  * @dev: drm device
@@ -542,8 +630,7 @@ static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper)
  * nor register the fbdev. This is only done in drm_fb_helper_initial_config()
  * to allow driver writes more control over the exact init sequence.
  *
- * Drivers must set fb_helper->funcs before calling
- * drm_fb_helper_initial_config().
+ * Drivers must call drm_fb_helper_prepare() before calling this function.
  *
  * RETURNS:
  * Zero if everything went ok, nonzero otherwise.
@@ -558,10 +645,6 @@ int drm_fb_helper_init(struct drm_device *dev,
        if (!max_conn_count)
                return -EINVAL;
 
-       fb_helper->dev = dev;
-
-       INIT_LIST_HEAD(&fb_helper->kernel_fb_list);
-
        fb_helper->crtc_info = kcalloc(crtc_count, sizeof(struct drm_fb_helper_crtc), GFP_KERNEL);
        if (!fb_helper->crtc_info)
                return -ENOMEM;
@@ -572,6 +655,7 @@ int drm_fb_helper_init(struct drm_device *dev,
                kfree(fb_helper->crtc_info);
                return -ENOMEM;
        }
+       fb_helper->connector_info_alloc_count = dev->mode_config.num_connector;
        fb_helper->connector_count = 0;
 
        for (i = 0; i < crtc_count; i++) {
@@ -1056,7 +1140,6 @@ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
        info->fix.ypanstep = 1; /* doing it in hw */
        info->fix.ywrapstep = 0;
        info->fix.accel = FB_ACCEL_NONE;
-       info->fix.type_aux = 0;
 
        info->fix.line_length = pitch;
        return;
@@ -1613,8 +1696,10 @@ EXPORT_SYMBOL(drm_fb_helper_initial_config);
  * either the output polling work or a work item launched from the driver's
  * hotplug interrupt).
  *
- * Note that the driver must ensure that this is only called _after_ the fb has
- * been fully set up, i.e. after the call to drm_fb_helper_initial_config.
+ * Note that drivers may call this even before calling
+ * drm_fb_helper_initial_config but only aftert drm_fb_helper_init. This allows
+ * for a race-free fbcon setup and will make sure that the fbdev emulation will
+ * not miss any hotplug events.
  *
  * RETURNS:
  * 0 on success and a non-zero error code otherwise.
@@ -1624,11 +1709,8 @@ int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper)
        struct drm_device *dev = fb_helper->dev;
        u32 max_width, max_height;
 
-       if (!fb_helper->fb)
-               return 0;
-
        mutex_lock(&fb_helper->dev->mode_config.mutex);
-       if (!drm_fb_helper_is_bound(fb_helper)) {
+       if (!fb_helper->fb || !drm_fb_helper_is_bound(fb_helper)) {
                fb_helper->delayed_hotplug = true;
                mutex_unlock(&fb_helper->dev->mode_config.mutex);
                return 0;
This page took 0.026809 seconds and 5 git commands to generate.