xref: /dflybsd-src/sys/dev/drm/drm_fb_helper.c (revision 9edbd4a07c3138f5c4f076f77de5d722fcc606cc)
15718399fSFrançois Tigeot /*
25718399fSFrançois Tigeot  * Copyright (c) 2006-2009 Red Hat Inc.
35718399fSFrançois Tigeot  * Copyright (c) 2006-2008 Intel Corporation
45718399fSFrançois Tigeot  * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
55718399fSFrançois Tigeot  *
65718399fSFrançois Tigeot  * DRM framebuffer helper functions
75718399fSFrançois Tigeot  *
85718399fSFrançois Tigeot  * Permission to use, copy, modify, distribute, and sell this software and its
95718399fSFrançois Tigeot  * documentation for any purpose is hereby granted without fee, provided that
105718399fSFrançois Tigeot  * the above copyright notice appear in all copies and that both that copyright
115718399fSFrançois Tigeot  * notice and this permission notice appear in supporting documentation, and
125718399fSFrançois Tigeot  * that the name of the copyright holders not be used in advertising or
135718399fSFrançois Tigeot  * publicity pertaining to distribution of the software without specific,
145718399fSFrançois Tigeot  * written prior permission.  The copyright holders make no representations
155718399fSFrançois Tigeot  * about the suitability of this software for any purpose.  It is provided "as
165718399fSFrançois Tigeot  * is" without express or implied warranty.
175718399fSFrançois Tigeot  *
185718399fSFrançois Tigeot  * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
195718399fSFrançois Tigeot  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
205718399fSFrançois Tigeot  * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
215718399fSFrançois Tigeot  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
225718399fSFrançois Tigeot  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
235718399fSFrançois Tigeot  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
245718399fSFrançois Tigeot  * OF THIS SOFTWARE.
255718399fSFrançois Tigeot  *
265718399fSFrançois Tigeot  * Authors:
275718399fSFrançois Tigeot  *      Dave Airlie <airlied@linux.ie>
285718399fSFrançois Tigeot  *      Jesse Barnes <jesse.barnes@intel.com>
295718399fSFrançois Tigeot  */
305718399fSFrançois Tigeot 
3118e26a6dSFrançois Tigeot #include <drm/drmP.h>
3218e26a6dSFrançois Tigeot #include <drm/drm_crtc.h>
3318e26a6dSFrançois Tigeot #include <drm/drm_fb_helper.h>
3418e26a6dSFrançois Tigeot #include <drm/drm_crtc_helper.h>
355718399fSFrançois Tigeot 
3628828b89SFrançois Tigeot static LINUX_LIST_HEAD(kernel_fb_helper_list);
375718399fSFrançois Tigeot 
38*9edbd4a0SFrançois Tigeot /**
39*9edbd4a0SFrançois Tigeot  * DOC: fbdev helpers
40*9edbd4a0SFrançois Tigeot  *
41*9edbd4a0SFrançois Tigeot  * The fb helper functions are useful to provide an fbdev on top of a drm kernel
42*9edbd4a0SFrançois Tigeot  * mode setting driver. They can be used mostly independantely from the crtc
43*9edbd4a0SFrançois Tigeot  * helper functions used by many drivers to implement the kernel mode setting
44*9edbd4a0SFrançois Tigeot  * interfaces.
45*9edbd4a0SFrançois Tigeot  *
46*9edbd4a0SFrançois Tigeot  * Initialization is done as a three-step process with drm_fb_helper_init(),
47*9edbd4a0SFrançois Tigeot  * drm_fb_helper_single_add_all_connectors() and drm_fb_helper_initial_config().
48*9edbd4a0SFrançois Tigeot  * Drivers with fancier requirements than the default beheviour can override the
49*9edbd4a0SFrançois Tigeot  * second step with their own code.  Teardown is done with drm_fb_helper_fini().
50*9edbd4a0SFrançois Tigeot  *
51*9edbd4a0SFrançois Tigeot  * At runtime drivers should restore the fbdev console by calling
52*9edbd4a0SFrançois Tigeot  * drm_fb_helper_restore_fbdev_mode() from their ->lastclose callback. They
53*9edbd4a0SFrançois Tigeot  * should also notify the fb helper code from updates to the output
54*9edbd4a0SFrançois Tigeot  * configuration by calling drm_fb_helper_hotplug_event(). For easier
55*9edbd4a0SFrançois Tigeot  * integration with the output polling code in drm_crtc_helper.c the modeset
56*9edbd4a0SFrançois Tigeot  * code proves a ->output_poll_changed callback.
57*9edbd4a0SFrançois Tigeot  *
58*9edbd4a0SFrançois Tigeot  * All other functions exported by the fb helper library can be used to
59*9edbd4a0SFrançois Tigeot  * implement the fbdev driver interface by the driver.
60*9edbd4a0SFrançois Tigeot  */
61*9edbd4a0SFrançois Tigeot 
62*9edbd4a0SFrançois Tigeot /**
63*9edbd4a0SFrançois Tigeot  * drm_fb_helper_single_add_all_connectors() - add all connectors to fbdev
64*9edbd4a0SFrançois Tigeot  * 					       emulation helper
65*9edbd4a0SFrançois Tigeot  * @fb_helper: fbdev initialized with drm_fb_helper_init
66*9edbd4a0SFrançois Tigeot  *
67*9edbd4a0SFrançois Tigeot  * This functions adds all the available connectors for use with the given
68*9edbd4a0SFrançois Tigeot  * fb_helper. This is a separate step to allow drivers to freely assign
69*9edbd4a0SFrançois Tigeot  * connectors to the fbdev, e.g. if some are reserved for special purposes or
70*9edbd4a0SFrançois Tigeot  * not adequate to be used for the fbcon.
71*9edbd4a0SFrançois Tigeot  *
72*9edbd4a0SFrançois Tigeot  * Since this is part of the initial setup before the fbdev is published, no
73*9edbd4a0SFrançois Tigeot  * locking is required.
74*9edbd4a0SFrançois Tigeot  */
755718399fSFrançois Tigeot int drm_fb_helper_single_add_all_connectors(struct drm_fb_helper *fb_helper)
765718399fSFrançois Tigeot {
775718399fSFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
785718399fSFrançois Tigeot 	struct drm_connector *connector;
79*9edbd4a0SFrançois Tigeot 	int i;
805718399fSFrançois Tigeot 
815718399fSFrançois Tigeot 	list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
825718399fSFrançois Tigeot 		struct drm_fb_helper_connector *fb_helper_connector;
835718399fSFrançois Tigeot 
84*9edbd4a0SFrançois Tigeot 		fb_helper_connector = kzalloc(sizeof(struct drm_fb_helper_connector), GFP_KERNEL);
85*9edbd4a0SFrançois Tigeot 		if (!fb_helper_connector)
86*9edbd4a0SFrançois Tigeot 			goto fail;
875718399fSFrançois Tigeot 
885718399fSFrançois Tigeot 		fb_helper_connector->connector = connector;
895718399fSFrançois Tigeot 		fb_helper->connector_info[fb_helper->connector_count++] = fb_helper_connector;
905718399fSFrançois Tigeot 	}
915718399fSFrançois Tigeot 	return 0;
92*9edbd4a0SFrançois Tigeot fail:
93*9edbd4a0SFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
94*9edbd4a0SFrançois Tigeot 		kfree(fb_helper->connector_info[i]);
95*9edbd4a0SFrançois Tigeot 		fb_helper->connector_info[i] = NULL;
965718399fSFrançois Tigeot 	}
97*9edbd4a0SFrançois Tigeot 	fb_helper->connector_count = 0;
98*9edbd4a0SFrançois Tigeot 	return -ENOMEM;
99*9edbd4a0SFrançois Tigeot }
100*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_single_add_all_connectors);
1015718399fSFrançois Tigeot 
1025718399fSFrançois Tigeot static int
1035718399fSFrançois Tigeot fb_get_options(const char *connector_name, char **option)
1045718399fSFrançois Tigeot {
1055718399fSFrançois Tigeot 
1065718399fSFrançois Tigeot 	return (1);
1075718399fSFrançois Tigeot }
1085718399fSFrançois Tigeot 
1095718399fSFrançois Tigeot static int drm_fb_helper_parse_command_line(struct drm_fb_helper *fb_helper)
1105718399fSFrançois Tigeot {
1115718399fSFrançois Tigeot 	struct drm_fb_helper_connector *fb_helper_conn;
1125718399fSFrançois Tigeot 	int i;
1135718399fSFrançois Tigeot 
1145718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
1154a968d2aSFrançois Tigeot 		struct drm_cmdline_mode *mode;
1164a968d2aSFrançois Tigeot 		struct drm_connector *connector;
1175718399fSFrançois Tigeot 		char *option = NULL;
1185718399fSFrançois Tigeot 
1195718399fSFrançois Tigeot 		fb_helper_conn = fb_helper->connector_info[i];
1204a968d2aSFrançois Tigeot 		connector = fb_helper_conn->connector;
1214a968d2aSFrançois Tigeot 		mode = &fb_helper_conn->cmdline_mode;
1225718399fSFrançois Tigeot 
1235718399fSFrançois Tigeot 		/* do something on return - turn off connector maybe */
1244a968d2aSFrançois Tigeot 		if (fb_get_options(drm_get_connector_name(connector), &option))
1255718399fSFrançois Tigeot 			continue;
1265718399fSFrançois Tigeot 
1274a968d2aSFrançois Tigeot 		if (drm_mode_parse_command_line_for_connector(option,
1284a968d2aSFrançois Tigeot 							      connector,
1294a968d2aSFrançois Tigeot 							      mode)) {
1304a968d2aSFrançois Tigeot 			if (mode->force) {
1314a968d2aSFrançois Tigeot 				const char *s;
1324a968d2aSFrançois Tigeot 				switch (mode->force) {
1334a968d2aSFrançois Tigeot 				case DRM_FORCE_OFF:
1344a968d2aSFrançois Tigeot 					s = "OFF";
1354a968d2aSFrançois Tigeot 					break;
1364a968d2aSFrançois Tigeot 				case DRM_FORCE_ON_DIGITAL:
1374a968d2aSFrançois Tigeot 					s = "ON - dig";
1384a968d2aSFrançois Tigeot 					break;
1394a968d2aSFrançois Tigeot 				default:
1404a968d2aSFrançois Tigeot 				case DRM_FORCE_ON:
1414a968d2aSFrançois Tigeot 					s = "ON";
1424a968d2aSFrançois Tigeot 					break;
1434a968d2aSFrançois Tigeot 				}
1444a968d2aSFrançois Tigeot 
1454a968d2aSFrançois Tigeot 				DRM_INFO("forcing %s connector %s\n",
1464a968d2aSFrançois Tigeot 					 drm_get_connector_name(connector), s);
1474a968d2aSFrançois Tigeot 				connector->force = mode->force;
1484a968d2aSFrançois Tigeot 			}
1494a968d2aSFrançois Tigeot 
1504a968d2aSFrançois Tigeot 			DRM_DEBUG_KMS("cmdline mode for connector %s %dx%d@%dHz%s%s%s\n",
1514a968d2aSFrançois Tigeot 				      drm_get_connector_name(connector),
1524a968d2aSFrançois Tigeot 				      mode->xres, mode->yres,
1534a968d2aSFrançois Tigeot 				      mode->refresh_specified ? mode->refresh : 60,
1544a968d2aSFrançois Tigeot 				      mode->rb ? " reduced blanking" : "",
1554a968d2aSFrançois Tigeot 				      mode->margins ? " with margins" : "",
1564a968d2aSFrançois Tigeot 				      mode->interlace ?  " interlaced" : "");
1574a968d2aSFrançois Tigeot 		}
1584a968d2aSFrançois Tigeot 
1595718399fSFrançois Tigeot 	}
1605718399fSFrançois Tigeot 	return 0;
1615718399fSFrançois Tigeot }
1625718399fSFrançois Tigeot 
1635718399fSFrançois Tigeot #if 0
1645718399fSFrançois Tigeot static void drm_fb_helper_save_lut_atomic(struct drm_crtc *crtc, struct drm_fb_helper *helper)
1655718399fSFrançois Tigeot {
1665718399fSFrançois Tigeot 	uint16_t *r_base, *g_base, *b_base;
1675718399fSFrançois Tigeot 	int i;
1685718399fSFrançois Tigeot 
169*9edbd4a0SFrançois Tigeot 	if (helper->funcs->gamma_get == NULL)
170*9edbd4a0SFrançois Tigeot 		return;
171*9edbd4a0SFrançois Tigeot 
1725718399fSFrançois Tigeot 	r_base = crtc->gamma_store;
1735718399fSFrançois Tigeot 	g_base = r_base + crtc->gamma_size;
1745718399fSFrançois Tigeot 	b_base = g_base + crtc->gamma_size;
1755718399fSFrançois Tigeot 
1765718399fSFrançois Tigeot 	for (i = 0; i < crtc->gamma_size; i++)
1775718399fSFrançois Tigeot 		helper->funcs->gamma_get(crtc, &r_base[i], &g_base[i], &b_base[i], i);
1785718399fSFrançois Tigeot }
1795718399fSFrançois Tigeot 
1805718399fSFrançois Tigeot static void drm_fb_helper_restore_lut_atomic(struct drm_crtc *crtc)
1815718399fSFrançois Tigeot {
1825718399fSFrançois Tigeot 	uint16_t *r_base, *g_base, *b_base;
1835718399fSFrançois Tigeot 
184*9edbd4a0SFrançois Tigeot 	if (crtc->funcs->gamma_set == NULL)
185*9edbd4a0SFrançois Tigeot 		return;
186*9edbd4a0SFrançois Tigeot 
1875718399fSFrançois Tigeot 	r_base = crtc->gamma_store;
1885718399fSFrançois Tigeot 	g_base = r_base + crtc->gamma_size;
1895718399fSFrançois Tigeot 	b_base = g_base + crtc->gamma_size;
1905718399fSFrançois Tigeot 
1915718399fSFrançois Tigeot 	crtc->funcs->gamma_set(crtc, r_base, g_base, b_base, 0, crtc->gamma_size);
1925718399fSFrançois Tigeot }
1935718399fSFrançois Tigeot 
194*9edbd4a0SFrançois Tigeot /**
195*9edbd4a0SFrançois Tigeot  * drm_fb_helper_debug_enter - implementation for ->fb_debug_enter
196*9edbd4a0SFrançois Tigeot  * @info: fbdev registered by the helper
197*9edbd4a0SFrançois Tigeot  */
1985718399fSFrançois Tigeot int drm_fb_helper_debug_enter(struct fb_info *info)
1995718399fSFrançois Tigeot {
2005718399fSFrançois Tigeot 	struct drm_fb_helper *helper = info->par;
2015718399fSFrançois Tigeot 	struct drm_crtc_helper_funcs *funcs;
2025718399fSFrançois Tigeot 	int i;
2035718399fSFrançois Tigeot 
2045718399fSFrançois Tigeot 	if (list_empty(&kernel_fb_helper_list))
2055718399fSFrançois Tigeot 		return false;
2065718399fSFrançois Tigeot 
2075718399fSFrançois Tigeot 	list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
2085718399fSFrançois Tigeot 		for (i = 0; i < helper->crtc_count; i++) {
2095718399fSFrançois Tigeot 			struct drm_mode_set *mode_set =
2105718399fSFrançois Tigeot 				&helper->crtc_info[i].mode_set;
2115718399fSFrançois Tigeot 
2125718399fSFrançois Tigeot 			if (!mode_set->crtc->enabled)
2135718399fSFrançois Tigeot 				continue;
2145718399fSFrançois Tigeot 
2155718399fSFrançois Tigeot 			funcs =	mode_set->crtc->helper_private;
2165718399fSFrançois Tigeot 			drm_fb_helper_save_lut_atomic(mode_set->crtc, helper);
2175718399fSFrançois Tigeot 			funcs->mode_set_base_atomic(mode_set->crtc,
2185718399fSFrançois Tigeot 						    mode_set->fb,
2195718399fSFrançois Tigeot 						    mode_set->x,
2205718399fSFrançois Tigeot 						    mode_set->y,
2215718399fSFrançois Tigeot 						    ENTER_ATOMIC_MODE_SET);
2225718399fSFrançois Tigeot 		}
2235718399fSFrançois Tigeot 	}
2245718399fSFrançois Tigeot 
2255718399fSFrançois Tigeot 	return 0;
2265718399fSFrançois Tigeot }
227*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_debug_enter);
2285718399fSFrançois Tigeot 
2295718399fSFrançois Tigeot /* Find the real fb for a given fb helper CRTC */
2305718399fSFrançois Tigeot static struct drm_framebuffer *drm_mode_config_fb(struct drm_crtc *crtc)
2315718399fSFrançois Tigeot {
2325718399fSFrançois Tigeot 	struct drm_device *dev = crtc->dev;
2335718399fSFrançois Tigeot 	struct drm_crtc *c;
2345718399fSFrançois Tigeot 
2355718399fSFrançois Tigeot 	list_for_each_entry(c, &dev->mode_config.crtc_list, head) {
2365718399fSFrançois Tigeot 		if (crtc->base.id == c->base.id)
2375718399fSFrançois Tigeot 			return c->fb;
2385718399fSFrançois Tigeot 	}
2395718399fSFrançois Tigeot 
2405718399fSFrançois Tigeot 	return NULL;
2415718399fSFrançois Tigeot }
2425718399fSFrançois Tigeot 
243*9edbd4a0SFrançois Tigeot /**
244*9edbd4a0SFrançois Tigeot  * drm_fb_helper_debug_leave - implementation for ->fb_debug_leave
245*9edbd4a0SFrançois Tigeot  * @info: fbdev registered by the helper
246*9edbd4a0SFrançois Tigeot  */
2475718399fSFrançois Tigeot int drm_fb_helper_debug_leave(struct fb_info *info)
2485718399fSFrançois Tigeot {
2495718399fSFrançois Tigeot 	struct drm_fb_helper *helper = info->par;
2505718399fSFrançois Tigeot 	struct drm_crtc *crtc;
2515718399fSFrançois Tigeot 	struct drm_crtc_helper_funcs *funcs;
2525718399fSFrançois Tigeot 	struct drm_framebuffer *fb;
2535718399fSFrançois Tigeot 	int i;
2545718399fSFrançois Tigeot 
2555718399fSFrançois Tigeot 	for (i = 0; i < helper->crtc_count; i++) {
2565718399fSFrançois Tigeot 		struct drm_mode_set *mode_set = &helper->crtc_info[i].mode_set;
2575718399fSFrançois Tigeot 		crtc = mode_set->crtc;
2585718399fSFrançois Tigeot 		funcs = crtc->helper_private;
2595718399fSFrançois Tigeot 		fb = drm_mode_config_fb(crtc);
2605718399fSFrançois Tigeot 
2615718399fSFrançois Tigeot 		if (!crtc->enabled)
2625718399fSFrançois Tigeot 			continue;
2635718399fSFrançois Tigeot 
2645718399fSFrançois Tigeot 		if (!fb) {
2655718399fSFrançois Tigeot 			DRM_ERROR("no fb to restore??\n");
2665718399fSFrançois Tigeot 			continue;
2675718399fSFrançois Tigeot 		}
2685718399fSFrançois Tigeot 
2695718399fSFrançois Tigeot 		drm_fb_helper_restore_lut_atomic(mode_set->crtc);
2705718399fSFrançois Tigeot 		funcs->mode_set_base_atomic(mode_set->crtc, fb, crtc->x,
2715718399fSFrançois Tigeot 					    crtc->y, LEAVE_ATOMIC_MODE_SET);
2725718399fSFrançois Tigeot 	}
2735718399fSFrançois Tigeot 
2745718399fSFrançois Tigeot 	return 0;
2755718399fSFrançois Tigeot }
276*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_debug_leave);
2775718399fSFrançois Tigeot #endif
2785718399fSFrançois Tigeot 
279*9edbd4a0SFrançois Tigeot /**
280*9edbd4a0SFrançois Tigeot  * drm_fb_helper_restore_fbdev_mode - restore fbdev configuration
281*9edbd4a0SFrançois Tigeot  * @fb_helper: fbcon to restore
282*9edbd4a0SFrançois Tigeot  *
283*9edbd4a0SFrançois Tigeot  * This should be called from driver's drm ->lastclose callback
284*9edbd4a0SFrançois Tigeot  * when implementing an fbcon on top of kms using this helper. This ensures that
285*9edbd4a0SFrançois Tigeot  * the user isn't greeted with a black screen when e.g. X dies.
286*9edbd4a0SFrançois Tigeot  */
2875718399fSFrançois Tigeot bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper)
2885718399fSFrançois Tigeot {
289*9edbd4a0SFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
290*9edbd4a0SFrançois Tigeot 	struct drm_plane *plane;
2915718399fSFrançois Tigeot 	bool error = false;
292*9edbd4a0SFrançois Tigeot 	int i;
293*9edbd4a0SFrançois Tigeot 
294*9edbd4a0SFrançois Tigeot 	drm_warn_on_modeset_not_all_locked(dev);
295*9edbd4a0SFrançois Tigeot 
296*9edbd4a0SFrançois Tigeot 	list_for_each_entry(plane, &dev->mode_config.plane_list, head)
297*9edbd4a0SFrançois Tigeot 		drm_plane_force_disable(plane);
298*9edbd4a0SFrançois Tigeot 
2995718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->crtc_count; i++) {
3005718399fSFrançois Tigeot 		struct drm_mode_set *mode_set = &fb_helper->crtc_info[i].mode_set;
301*9edbd4a0SFrançois Tigeot 		struct drm_crtc *crtc = mode_set->crtc;
302*9edbd4a0SFrançois Tigeot 		int ret;
303*9edbd4a0SFrançois Tigeot 
304*9edbd4a0SFrançois Tigeot 		if (crtc->funcs->cursor_set) {
305*9edbd4a0SFrançois Tigeot 			ret = crtc->funcs->cursor_set(crtc, NULL, 0, 0, 0);
306*9edbd4a0SFrançois Tigeot 			if (ret)
307*9edbd4a0SFrançois Tigeot 				error = true;
308*9edbd4a0SFrançois Tigeot 		}
309*9edbd4a0SFrançois Tigeot 
310*9edbd4a0SFrançois Tigeot 		ret = drm_mode_set_config_internal(mode_set);
3115718399fSFrançois Tigeot 		if (ret)
3125718399fSFrançois Tigeot 			error = true;
3135718399fSFrançois Tigeot 	}
3145718399fSFrançois Tigeot 	return error;
3155718399fSFrançois Tigeot }
316*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_restore_fbdev_mode);
3175718399fSFrançois Tigeot 
3185718399fSFrançois Tigeot #if 0
319*9edbd4a0SFrançois Tigeot /*
320*9edbd4a0SFrançois Tigeot  * restore fbcon display for all kms driver's using this helper, used for sysrq
321*9edbd4a0SFrançois Tigeot  * and panic handling.
322*9edbd4a0SFrançois Tigeot  */
323*9edbd4a0SFrançois Tigeot static bool drm_fb_helper_force_kernel_mode(void)
3245718399fSFrançois Tigeot {
3255718399fSFrançois Tigeot 	bool ret, error = false;
3265718399fSFrançois Tigeot 	struct drm_fb_helper *helper;
3275718399fSFrançois Tigeot 
3285718399fSFrançois Tigeot 	if (list_empty(&kernel_fb_helper_list))
3295718399fSFrançois Tigeot 		return false;
3305718399fSFrançois Tigeot 
3315718399fSFrançois Tigeot 	list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
3325718399fSFrançois Tigeot 		if (helper->dev->switch_power_state == DRM_SWITCH_POWER_OFF)
3335718399fSFrançois Tigeot 			continue;
3345718399fSFrançois Tigeot 
3355718399fSFrançois Tigeot 		ret = drm_fb_helper_restore_fbdev_mode(helper);
3365718399fSFrançois Tigeot 		if (ret)
3375718399fSFrançois Tigeot 			error = true;
3385718399fSFrançois Tigeot 	}
3395718399fSFrançois Tigeot 	return error;
3405718399fSFrançois Tigeot }
3415718399fSFrançois Tigeot 
342*9edbd4a0SFrançois Tigeot static int drm_fb_helper_panic(struct notifier_block *n, unsigned long ununsed,
3435718399fSFrançois Tigeot 			void *panic_str)
3445718399fSFrançois Tigeot {
345*9edbd4a0SFrançois Tigeot 	/*
346*9edbd4a0SFrançois Tigeot 	 * It's a waste of time and effort to switch back to text console
347*9edbd4a0SFrançois Tigeot 	 * if the kernel should reboot before panic messages can be seen.
348*9edbd4a0SFrançois Tigeot 	 */
349*9edbd4a0SFrançois Tigeot 	if (panic_timeout < 0)
3505718399fSFrançois Tigeot 		return 0;
351*9edbd4a0SFrançois Tigeot 
352*9edbd4a0SFrançois Tigeot 	pr_err("panic occurred, switching back to text console\n");
353*9edbd4a0SFrançois Tigeot 	return drm_fb_helper_force_kernel_mode();
3545718399fSFrançois Tigeot }
3555718399fSFrançois Tigeot 
3565718399fSFrançois Tigeot static struct notifier_block paniced = {
3575718399fSFrançois Tigeot 	.notifier_call = drm_fb_helper_panic,
3585718399fSFrançois Tigeot };
359*9edbd4a0SFrançois Tigeot #endif
3605718399fSFrançois Tigeot 
361*9edbd4a0SFrançois Tigeot static bool drm_fb_helper_is_bound(struct drm_fb_helper *fb_helper)
362*9edbd4a0SFrançois Tigeot {
363*9edbd4a0SFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
364*9edbd4a0SFrançois Tigeot 	struct drm_crtc *crtc;
365*9edbd4a0SFrançois Tigeot 	int bound = 0, crtcs_bound = 0;
366*9edbd4a0SFrançois Tigeot 
367*9edbd4a0SFrançois Tigeot 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
368*9edbd4a0SFrançois Tigeot 		if (crtc->fb)
369*9edbd4a0SFrançois Tigeot 			crtcs_bound++;
370*9edbd4a0SFrançois Tigeot 		if (crtc->fb == fb_helper->fb)
371*9edbd4a0SFrançois Tigeot 			bound++;
372*9edbd4a0SFrançois Tigeot 	}
373*9edbd4a0SFrançois Tigeot 
374*9edbd4a0SFrançois Tigeot 	if (bound < crtcs_bound)
375*9edbd4a0SFrançois Tigeot 		return false;
376*9edbd4a0SFrançois Tigeot 	return true;
377*9edbd4a0SFrançois Tigeot }
378*9edbd4a0SFrançois Tigeot 
379*9edbd4a0SFrançois Tigeot #if 0
380*9edbd4a0SFrançois Tigeot #ifdef CONFIG_MAGIC_SYSRQ
381*9edbd4a0SFrançois Tigeot static void drm_fb_helper_restore_work_fn(struct work_struct *ignored)
3825718399fSFrançois Tigeot {
3835718399fSFrançois Tigeot 	bool ret;
3845718399fSFrançois Tigeot 	ret = drm_fb_helper_force_kernel_mode();
3855718399fSFrançois Tigeot 	if (ret == true)
3865718399fSFrançois Tigeot 		DRM_ERROR("Failed to restore crtc configuration\n");
3875718399fSFrançois Tigeot }
3885718399fSFrançois Tigeot static DECLARE_WORK(drm_fb_helper_restore_work, drm_fb_helper_restore_work_fn);
3895718399fSFrançois Tigeot 
3905718399fSFrançois Tigeot static void drm_fb_helper_sysrq(int dummy1)
3915718399fSFrançois Tigeot {
3925718399fSFrançois Tigeot 	schedule_work(&drm_fb_helper_restore_work);
3935718399fSFrançois Tigeot }
3945718399fSFrançois Tigeot 
3955718399fSFrançois Tigeot static struct sysrq_key_op sysrq_drm_fb_helper_restore_op = {
3965718399fSFrançois Tigeot 	.handler = drm_fb_helper_sysrq,
3975718399fSFrançois Tigeot 	.help_msg = "force-fb(V)",
3985718399fSFrançois Tigeot 	.action_msg = "Restore framebuffer console",
3995718399fSFrançois Tigeot };
4005718399fSFrançois Tigeot #else
4015718399fSFrançois Tigeot static struct sysrq_key_op sysrq_drm_fb_helper_restore_op = { };
4025718399fSFrançois Tigeot #endif
4035718399fSFrançois Tigeot 
404*9edbd4a0SFrançois Tigeot static void drm_fb_helper_dpms(struct fb_info *info, int dpms_mode)
4055718399fSFrançois Tigeot {
4065718399fSFrançois Tigeot 	struct drm_fb_helper *fb_helper = info->par;
4075718399fSFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
4085718399fSFrançois Tigeot 	struct drm_crtc *crtc;
4095718399fSFrançois Tigeot 	struct drm_connector *connector;
4105718399fSFrançois Tigeot 	int i, j;
4115718399fSFrançois Tigeot 
4125718399fSFrançois Tigeot 	/*
413*9edbd4a0SFrançois Tigeot 	 * fbdev->blank can be called from irq context in case of a panic.
414*9edbd4a0SFrançois Tigeot 	 * Since we already have our own special panic handler which will
415*9edbd4a0SFrançois Tigeot 	 * restore the fbdev console mode completely, just bail out early.
4165718399fSFrançois Tigeot 	 */
417*9edbd4a0SFrançois Tigeot 	if (oops_in_progress)
418*9edbd4a0SFrançois Tigeot 		return;
419*9edbd4a0SFrançois Tigeot 
420*9edbd4a0SFrançois Tigeot 	/*
421*9edbd4a0SFrançois Tigeot 	 * For each CRTC in this fb, turn the connectors on/off.
422*9edbd4a0SFrançois Tigeot 	 */
423*9edbd4a0SFrançois Tigeot 	drm_modeset_lock_all(dev);
424*9edbd4a0SFrançois Tigeot 	if (!drm_fb_helper_is_bound(fb_helper)) {
425*9edbd4a0SFrançois Tigeot 		drm_modeset_unlock_all(dev);
426*9edbd4a0SFrançois Tigeot 		return;
427*9edbd4a0SFrançois Tigeot 	}
428*9edbd4a0SFrançois Tigeot 
4295718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->crtc_count; i++) {
4305718399fSFrançois Tigeot 		crtc = fb_helper->crtc_info[i].mode_set.crtc;
4315718399fSFrançois Tigeot 
4325718399fSFrançois Tigeot 		if (!crtc->enabled)
4335718399fSFrançois Tigeot 			continue;
4345718399fSFrançois Tigeot 
435*9edbd4a0SFrançois Tigeot 		/* Walk the connectors & encoders on this fb turning them on/off */
4365718399fSFrançois Tigeot 		for (j = 0; j < fb_helper->connector_count; j++) {
4375718399fSFrançois Tigeot 			connector = fb_helper->connector_info[j]->connector;
438*9edbd4a0SFrançois Tigeot 			connector->funcs->dpms(connector, dpms_mode);
439*9edbd4a0SFrançois Tigeot 			drm_object_property_set_value(&connector->base,
440*9edbd4a0SFrançois Tigeot 				dev->mode_config.dpms_property, dpms_mode);
4415718399fSFrançois Tigeot 		}
442*9edbd4a0SFrançois Tigeot 	}
443*9edbd4a0SFrançois Tigeot 	drm_modeset_unlock_all(dev);
444*9edbd4a0SFrançois Tigeot }
4455718399fSFrançois Tigeot 
446*9edbd4a0SFrançois Tigeot /**
447*9edbd4a0SFrançois Tigeot  * drm_fb_helper_blank - implementation for ->fb_blank
448*9edbd4a0SFrançois Tigeot  * @blank: desired blanking state
449*9edbd4a0SFrançois Tigeot  * @info: fbdev registered by the helper
4505718399fSFrançois Tigeot  */
4515718399fSFrançois Tigeot int drm_fb_helper_blank(int blank, struct fb_info *info)
4525718399fSFrançois Tigeot {
4535718399fSFrançois Tigeot 	switch (blank) {
4545718399fSFrançois Tigeot 	/* Display: On; HSync: On, VSync: On */
4555718399fSFrançois Tigeot 	case FB_BLANK_UNBLANK:
456*9edbd4a0SFrançois Tigeot 		drm_fb_helper_dpms(info, DRM_MODE_DPMS_ON);
4575718399fSFrançois Tigeot 		break;
4585718399fSFrançois Tigeot 	/* Display: Off; HSync: On, VSync: On */
4595718399fSFrançois Tigeot 	case FB_BLANK_NORMAL:
460*9edbd4a0SFrançois Tigeot 		drm_fb_helper_dpms(info, DRM_MODE_DPMS_STANDBY);
4615718399fSFrançois Tigeot 		break;
4625718399fSFrançois Tigeot 	/* Display: Off; HSync: Off, VSync: On */
4635718399fSFrançois Tigeot 	case FB_BLANK_HSYNC_SUSPEND:
464*9edbd4a0SFrançois Tigeot 		drm_fb_helper_dpms(info, DRM_MODE_DPMS_STANDBY);
4655718399fSFrançois Tigeot 		break;
4665718399fSFrançois Tigeot 	/* Display: Off; HSync: On, VSync: Off */
4675718399fSFrançois Tigeot 	case FB_BLANK_VSYNC_SUSPEND:
468*9edbd4a0SFrançois Tigeot 		drm_fb_helper_dpms(info, DRM_MODE_DPMS_SUSPEND);
4695718399fSFrançois Tigeot 		break;
4705718399fSFrançois Tigeot 	/* Display: Off; HSync: Off, VSync: Off */
4715718399fSFrançois Tigeot 	case FB_BLANK_POWERDOWN:
472*9edbd4a0SFrançois Tigeot 		drm_fb_helper_dpms(info, DRM_MODE_DPMS_OFF);
4735718399fSFrançois Tigeot 		break;
4745718399fSFrançois Tigeot 	}
4755718399fSFrançois Tigeot 	return 0;
4765718399fSFrançois Tigeot }
477*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_blank);
4785718399fSFrançois Tigeot #endif
4795718399fSFrançois Tigeot 
4805718399fSFrançois Tigeot static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper)
4815718399fSFrançois Tigeot {
4825718399fSFrançois Tigeot 	int i;
4835718399fSFrançois Tigeot 
4845718399fSFrançois Tigeot 	for (i = 0; i < helper->connector_count; i++)
485*9edbd4a0SFrançois Tigeot 		kfree(helper->connector_info[i]);
486*9edbd4a0SFrançois Tigeot 	kfree(helper->connector_info);
4876f486c69SFrançois Tigeot 	for (i = 0; i < helper->crtc_count; i++) {
488*9edbd4a0SFrançois Tigeot 		kfree(helper->crtc_info[i].mode_set.connectors);
4896f486c69SFrançois Tigeot 		if (helper->crtc_info[i].mode_set.mode)
4906f486c69SFrançois Tigeot 			drm_mode_destroy(helper->dev, helper->crtc_info[i].mode_set.mode);
4916f486c69SFrançois Tigeot 	}
492*9edbd4a0SFrançois Tigeot 	kfree(helper->crtc_info);
4935718399fSFrançois Tigeot }
4945718399fSFrançois Tigeot 
495*9edbd4a0SFrançois Tigeot /**
496*9edbd4a0SFrançois Tigeot  * drm_fb_helper_init - initialize a drm_fb_helper structure
497*9edbd4a0SFrançois Tigeot  * @dev: drm device
498*9edbd4a0SFrançois Tigeot  * @fb_helper: driver-allocated fbdev helper structure to initialize
499*9edbd4a0SFrançois Tigeot  * @crtc_count: maximum number of crtcs to support in this fbdev emulation
500*9edbd4a0SFrançois Tigeot  * @max_conn_count: max connector count
501*9edbd4a0SFrançois Tigeot  *
502*9edbd4a0SFrançois Tigeot  * This allocates the structures for the fbdev helper with the given limits.
503*9edbd4a0SFrançois Tigeot  * Note that this won't yet touch the hardware (through the driver interfaces)
504*9edbd4a0SFrançois Tigeot  * nor register the fbdev. This is only done in drm_fb_helper_initial_config()
505*9edbd4a0SFrançois Tigeot  * to allow driver writes more control over the exact init sequence.
506*9edbd4a0SFrançois Tigeot  *
507*9edbd4a0SFrançois Tigeot  * Drivers must set fb_helper->funcs before calling
508*9edbd4a0SFrançois Tigeot  * drm_fb_helper_initial_config().
509*9edbd4a0SFrançois Tigeot  *
510*9edbd4a0SFrançois Tigeot  * RETURNS:
511*9edbd4a0SFrançois Tigeot  * Zero if everything went ok, nonzero otherwise.
512*9edbd4a0SFrançois Tigeot  */
5135718399fSFrançois Tigeot int drm_fb_helper_init(struct drm_device *dev,
5145718399fSFrançois Tigeot 		       struct drm_fb_helper *fb_helper,
5155718399fSFrançois Tigeot 		       int crtc_count, int max_conn_count)
5165718399fSFrançois Tigeot {
5175718399fSFrançois Tigeot 	struct drm_crtc *crtc;
5185718399fSFrançois Tigeot 	int i;
5195718399fSFrançois Tigeot 
5205718399fSFrançois Tigeot 	fb_helper->dev = dev;
5215718399fSFrançois Tigeot 
5225718399fSFrançois Tigeot 	INIT_LIST_HEAD(&fb_helper->kernel_fb_list);
5235718399fSFrançois Tigeot 
5245718399fSFrançois Tigeot 	fb_helper->crtc_info = kmalloc(crtc_count *
5255a3b77d5SFrançois Tigeot 	    sizeof(struct drm_fb_helper_crtc), M_DRM, M_WAITOK | M_ZERO);
5264a968d2aSFrançois Tigeot 	if (!fb_helper->crtc_info)
5274a968d2aSFrançois Tigeot 		return -ENOMEM;
5285718399fSFrançois Tigeot 
5295718399fSFrançois Tigeot 	fb_helper->crtc_count = crtc_count;
5305718399fSFrançois Tigeot 	fb_helper->connector_info = kmalloc(dev->mode_config.num_connector *
5315a3b77d5SFrançois Tigeot 	    sizeof(struct drm_fb_helper_connector *), M_DRM,
5325718399fSFrançois Tigeot 	    M_WAITOK | M_ZERO);
5334a968d2aSFrançois Tigeot 	if (!fb_helper->connector_info) {
534158486a6SFrançois Tigeot 		kfree(fb_helper->crtc_info);
5354a968d2aSFrançois Tigeot 		return -ENOMEM;
5364a968d2aSFrançois Tigeot 	}
5375718399fSFrançois Tigeot 	fb_helper->connector_count = 0;
5385718399fSFrançois Tigeot 
5395718399fSFrançois Tigeot 	for (i = 0; i < crtc_count; i++) {
5405718399fSFrançois Tigeot 		fb_helper->crtc_info[i].mode_set.connectors =
5415718399fSFrançois Tigeot 			kmalloc(max_conn_count * sizeof(struct drm_connector *),
5425a3b77d5SFrançois Tigeot 			    M_DRM, M_WAITOK | M_ZERO);
5435718399fSFrançois Tigeot 
5444a968d2aSFrançois Tigeot 		if (!fb_helper->crtc_info[i].mode_set.connectors)
5454a968d2aSFrançois Tigeot 			goto out_free;
5465718399fSFrançois Tigeot 		fb_helper->crtc_info[i].mode_set.num_connectors = 0;
5475718399fSFrançois Tigeot 	}
5485718399fSFrançois Tigeot 
5495718399fSFrançois Tigeot 	i = 0;
5505718399fSFrançois Tigeot 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
5515718399fSFrançois Tigeot 		fb_helper->crtc_info[i].mode_set.crtc = crtc;
5525718399fSFrançois Tigeot 		i++;
5535718399fSFrançois Tigeot 	}
5544a968d2aSFrançois Tigeot 
5555718399fSFrançois Tigeot 	return 0;
5564a968d2aSFrançois Tigeot out_free:
5574a968d2aSFrançois Tigeot 	drm_fb_helper_crtc_free(fb_helper);
5584a968d2aSFrançois Tigeot 	return -ENOMEM;
5595718399fSFrançois Tigeot }
5604a968d2aSFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_init);
5615718399fSFrançois Tigeot 
5625718399fSFrançois Tigeot void drm_fb_helper_fini(struct drm_fb_helper *fb_helper)
5635718399fSFrançois Tigeot {
5645718399fSFrançois Tigeot 	if (!list_empty(&fb_helper->kernel_fb_list)) {
5655718399fSFrançois Tigeot 		list_del(&fb_helper->kernel_fb_list);
5665718399fSFrançois Tigeot 		if (list_empty(&kernel_fb_helper_list)) {
5675718399fSFrançois Tigeot #if 0
568*9edbd4a0SFrançois Tigeot 			pr_info("drm: unregistered panic notifier\n");
5695718399fSFrançois Tigeot 			atomic_notifier_chain_unregister(&panic_notifier_list,
5705718399fSFrançois Tigeot 							 &paniced);
5715718399fSFrançois Tigeot 			unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
5725718399fSFrançois Tigeot #endif
5735718399fSFrançois Tigeot 		}
5745718399fSFrançois Tigeot 	}
5755718399fSFrançois Tigeot 
5765718399fSFrançois Tigeot 	drm_fb_helper_crtc_free(fb_helper);
5775718399fSFrançois Tigeot 
5785718399fSFrançois Tigeot }
579*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_fini);
5805718399fSFrançois Tigeot 
5815718399fSFrançois Tigeot #if 0
5825718399fSFrançois Tigeot static int setcolreg(struct drm_crtc *crtc, u16 red, u16 green,
5835718399fSFrançois Tigeot 		     u16 blue, u16 regno, struct fb_info *info)
5845718399fSFrançois Tigeot {
5855718399fSFrançois Tigeot 	struct drm_fb_helper *fb_helper = info->par;
5865718399fSFrançois Tigeot 	struct drm_framebuffer *fb = fb_helper->fb;
5875718399fSFrançois Tigeot 	int pindex;
5885718399fSFrançois Tigeot 
589*9edbd4a0SFrançois Tigeot 	if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
5905718399fSFrançois Tigeot 		u32 *palette;
5915718399fSFrançois Tigeot 		u32 value;
5925718399fSFrançois Tigeot 		/* place color in psuedopalette */
5935718399fSFrançois Tigeot 		if (regno > 16)
5945718399fSFrançois Tigeot 			return -EINVAL;
5955718399fSFrançois Tigeot 		palette = (u32 *)info->pseudo_palette;
5965718399fSFrançois Tigeot 		red >>= (16 - info->var.red.length);
5975718399fSFrançois Tigeot 		green >>= (16 - info->var.green.length);
5985718399fSFrançois Tigeot 		blue >>= (16 - info->var.blue.length);
5995718399fSFrançois Tigeot 		value = (red << info->var.red.offset) |
6005718399fSFrançois Tigeot 			(green << info->var.green.offset) |
6015718399fSFrançois Tigeot 			(blue << info->var.blue.offset);
6025718399fSFrançois Tigeot 		if (info->var.transp.length > 0) {
6035718399fSFrançois Tigeot 			u32 mask = (1 << info->var.transp.length) - 1;
6045718399fSFrançois Tigeot 			mask <<= info->var.transp.offset;
6055718399fSFrançois Tigeot 			value |= mask;
6065718399fSFrançois Tigeot 		}
6075718399fSFrançois Tigeot 		palette[regno] = value;
6085718399fSFrançois Tigeot 		return 0;
6095718399fSFrançois Tigeot 	}
6105718399fSFrançois Tigeot 
611*9edbd4a0SFrançois Tigeot 	/*
612*9edbd4a0SFrançois Tigeot 	 * The driver really shouldn't advertise pseudo/directcolor
613*9edbd4a0SFrançois Tigeot 	 * visuals if it can't deal with the palette.
614*9edbd4a0SFrançois Tigeot 	 */
615*9edbd4a0SFrançois Tigeot 	if (WARN_ON(!fb_helper->funcs->gamma_set ||
616*9edbd4a0SFrançois Tigeot 		    !fb_helper->funcs->gamma_get))
617*9edbd4a0SFrançois Tigeot 		return -EINVAL;
618*9edbd4a0SFrançois Tigeot 
6195718399fSFrançois Tigeot 	pindex = regno;
6205718399fSFrançois Tigeot 
6215718399fSFrançois Tigeot 	if (fb->bits_per_pixel == 16) {
6225718399fSFrançois Tigeot 		pindex = regno << 3;
6235718399fSFrançois Tigeot 
6245718399fSFrançois Tigeot 		if (fb->depth == 16 && regno > 63)
6255718399fSFrançois Tigeot 			return -EINVAL;
6265718399fSFrançois Tigeot 		if (fb->depth == 15 && regno > 31)
6275718399fSFrançois Tigeot 			return -EINVAL;
6285718399fSFrançois Tigeot 
6295718399fSFrançois Tigeot 		if (fb->depth == 16) {
6305718399fSFrançois Tigeot 			u16 r, g, b;
6315718399fSFrançois Tigeot 			int i;
6325718399fSFrançois Tigeot 			if (regno < 32) {
6335718399fSFrançois Tigeot 				for (i = 0; i < 8; i++)
6345718399fSFrançois Tigeot 					fb_helper->funcs->gamma_set(crtc, red,
6355718399fSFrançois Tigeot 						green, blue, pindex + i);
6365718399fSFrançois Tigeot 			}
6375718399fSFrançois Tigeot 
6385718399fSFrançois Tigeot 			fb_helper->funcs->gamma_get(crtc, &r,
6395718399fSFrançois Tigeot 						    &g, &b,
6405718399fSFrançois Tigeot 						    pindex >> 1);
6415718399fSFrançois Tigeot 
6425718399fSFrançois Tigeot 			for (i = 0; i < 4; i++)
6435718399fSFrançois Tigeot 				fb_helper->funcs->gamma_set(crtc, r,
6445718399fSFrançois Tigeot 							    green, b,
6455718399fSFrançois Tigeot 							    (pindex >> 1) + i);
6465718399fSFrançois Tigeot 		}
6475718399fSFrançois Tigeot 	}
6485718399fSFrançois Tigeot 
6495718399fSFrançois Tigeot 	if (fb->depth != 16)
6505718399fSFrançois Tigeot 		fb_helper->funcs->gamma_set(crtc, red, green, blue, pindex);
6515718399fSFrançois Tigeot 	return 0;
6525718399fSFrançois Tigeot }
6535718399fSFrançois Tigeot 
654*9edbd4a0SFrançois Tigeot /**
655*9edbd4a0SFrançois Tigeot  * drm_fb_helper_setcmap - implementation for ->fb_setcmap
656*9edbd4a0SFrançois Tigeot  * @cmap: cmap to set
657*9edbd4a0SFrançois Tigeot  * @info: fbdev registered by the helper
658*9edbd4a0SFrançois Tigeot  */
6595718399fSFrançois Tigeot int drm_fb_helper_setcmap(struct fb_cmap *cmap, struct fb_info *info)
6605718399fSFrançois Tigeot {
6615718399fSFrançois Tigeot 	struct drm_fb_helper *fb_helper = info->par;
662*9edbd4a0SFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
6635718399fSFrançois Tigeot 	struct drm_crtc_helper_funcs *crtc_funcs;
6645718399fSFrançois Tigeot 	u16 *red, *green, *blue, *transp;
6655718399fSFrançois Tigeot 	struct drm_crtc *crtc;
6665718399fSFrançois Tigeot 	int i, j, rc = 0;
6675718399fSFrançois Tigeot 	int start;
6685718399fSFrançois Tigeot 
669*9edbd4a0SFrançois Tigeot 	drm_modeset_lock_all(dev);
670*9edbd4a0SFrançois Tigeot 	if (!drm_fb_helper_is_bound(fb_helper)) {
671*9edbd4a0SFrançois Tigeot 		drm_modeset_unlock_all(dev);
672*9edbd4a0SFrançois Tigeot 		return -EBUSY;
673*9edbd4a0SFrançois Tigeot 	}
674*9edbd4a0SFrançois Tigeot 
6755718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->crtc_count; i++) {
6765718399fSFrançois Tigeot 		crtc = fb_helper->crtc_info[i].mode_set.crtc;
6775718399fSFrançois Tigeot 		crtc_funcs = crtc->helper_private;
6785718399fSFrançois Tigeot 
6795718399fSFrançois Tigeot 		red = cmap->red;
6805718399fSFrançois Tigeot 		green = cmap->green;
6815718399fSFrançois Tigeot 		blue = cmap->blue;
6825718399fSFrançois Tigeot 		transp = cmap->transp;
6835718399fSFrançois Tigeot 		start = cmap->start;
6845718399fSFrançois Tigeot 
6855718399fSFrançois Tigeot 		for (j = 0; j < cmap->len; j++) {
6865718399fSFrançois Tigeot 			u16 hred, hgreen, hblue, htransp = 0xffff;
6875718399fSFrançois Tigeot 
6885718399fSFrançois Tigeot 			hred = *red++;
6895718399fSFrançois Tigeot 			hgreen = *green++;
6905718399fSFrançois Tigeot 			hblue = *blue++;
6915718399fSFrançois Tigeot 
6925718399fSFrançois Tigeot 			if (transp)
6935718399fSFrançois Tigeot 				htransp = *transp++;
6945718399fSFrançois Tigeot 
6955718399fSFrançois Tigeot 			rc = setcolreg(crtc, hred, hgreen, hblue, start++, info);
6965718399fSFrançois Tigeot 			if (rc)
697*9edbd4a0SFrançois Tigeot 				goto out;
6985718399fSFrançois Tigeot 		}
699*9edbd4a0SFrançois Tigeot 		if (crtc_funcs->load_lut)
7005718399fSFrançois Tigeot 			crtc_funcs->load_lut(crtc);
7015718399fSFrançois Tigeot 	}
702*9edbd4a0SFrançois Tigeot  out:
703*9edbd4a0SFrançois Tigeot 	drm_modeset_unlock_all(dev);
7045718399fSFrançois Tigeot 	return rc;
7055718399fSFrançois Tigeot }
706*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_setcmap);
7075718399fSFrançois Tigeot 
708*9edbd4a0SFrançois Tigeot /**
709*9edbd4a0SFrançois Tigeot  * drm_fb_helper_check_var - implementation for ->fb_check_var
710*9edbd4a0SFrançois Tigeot  * @var: screeninfo to check
711*9edbd4a0SFrançois Tigeot  * @info: fbdev registered by the helper
712*9edbd4a0SFrançois Tigeot  */
7135718399fSFrançois Tigeot int drm_fb_helper_check_var(struct fb_var_screeninfo *var,
7145718399fSFrançois Tigeot 			    struct fb_info *info)
7155718399fSFrançois Tigeot {
7165718399fSFrançois Tigeot 	struct drm_fb_helper *fb_helper = info->par;
7175718399fSFrançois Tigeot 	struct drm_framebuffer *fb = fb_helper->fb;
7185718399fSFrançois Tigeot 	int depth;
7195718399fSFrançois Tigeot 
7205718399fSFrançois Tigeot 	if (var->pixclock != 0 || in_dbg_master())
7215718399fSFrançois Tigeot 		return -EINVAL;
7225718399fSFrançois Tigeot 
7235718399fSFrançois Tigeot 	/* Need to resize the fb object !!! */
7245718399fSFrançois Tigeot 	if (var->bits_per_pixel > fb->bits_per_pixel ||
7255718399fSFrançois Tigeot 	    var->xres > fb->width || var->yres > fb->height ||
7265718399fSFrançois Tigeot 	    var->xres_virtual > fb->width || var->yres_virtual > fb->height) {
7275718399fSFrançois Tigeot 		DRM_DEBUG("fb userspace requested width/height/bpp is greater than current fb "
7285718399fSFrançois Tigeot 			  "request %dx%d-%d (virtual %dx%d) > %dx%d-%d\n",
7295718399fSFrançois Tigeot 			  var->xres, var->yres, var->bits_per_pixel,
7305718399fSFrançois Tigeot 			  var->xres_virtual, var->yres_virtual,
7315718399fSFrançois Tigeot 			  fb->width, fb->height, fb->bits_per_pixel);
7325718399fSFrançois Tigeot 		return -EINVAL;
7335718399fSFrançois Tigeot 	}
7345718399fSFrançois Tigeot 
7355718399fSFrançois Tigeot 	switch (var->bits_per_pixel) {
7365718399fSFrançois Tigeot 	case 16:
7375718399fSFrançois Tigeot 		depth = (var->green.length == 6) ? 16 : 15;
7385718399fSFrançois Tigeot 		break;
7395718399fSFrançois Tigeot 	case 32:
7405718399fSFrançois Tigeot 		depth = (var->transp.length > 0) ? 32 : 24;
7415718399fSFrançois Tigeot 		break;
7425718399fSFrançois Tigeot 	default:
7435718399fSFrançois Tigeot 		depth = var->bits_per_pixel;
7445718399fSFrançois Tigeot 		break;
7455718399fSFrançois Tigeot 	}
7465718399fSFrançois Tigeot 
7475718399fSFrançois Tigeot 	switch (depth) {
7485718399fSFrançois Tigeot 	case 8:
7495718399fSFrançois Tigeot 		var->red.offset = 0;
7505718399fSFrançois Tigeot 		var->green.offset = 0;
7515718399fSFrançois Tigeot 		var->blue.offset = 0;
7525718399fSFrançois Tigeot 		var->red.length = 8;
7535718399fSFrançois Tigeot 		var->green.length = 8;
7545718399fSFrançois Tigeot 		var->blue.length = 8;
7555718399fSFrançois Tigeot 		var->transp.length = 0;
7565718399fSFrançois Tigeot 		var->transp.offset = 0;
7575718399fSFrançois Tigeot 		break;
7585718399fSFrançois Tigeot 	case 15:
7595718399fSFrançois Tigeot 		var->red.offset = 10;
7605718399fSFrançois Tigeot 		var->green.offset = 5;
7615718399fSFrançois Tigeot 		var->blue.offset = 0;
7625718399fSFrançois Tigeot 		var->red.length = 5;
7635718399fSFrançois Tigeot 		var->green.length = 5;
7645718399fSFrançois Tigeot 		var->blue.length = 5;
7655718399fSFrançois Tigeot 		var->transp.length = 1;
7665718399fSFrançois Tigeot 		var->transp.offset = 15;
7675718399fSFrançois Tigeot 		break;
7685718399fSFrançois Tigeot 	case 16:
7695718399fSFrançois Tigeot 		var->red.offset = 11;
7705718399fSFrançois Tigeot 		var->green.offset = 5;
7715718399fSFrançois Tigeot 		var->blue.offset = 0;
7725718399fSFrançois Tigeot 		var->red.length = 5;
7735718399fSFrançois Tigeot 		var->green.length = 6;
7745718399fSFrançois Tigeot 		var->blue.length = 5;
7755718399fSFrançois Tigeot 		var->transp.length = 0;
7765718399fSFrançois Tigeot 		var->transp.offset = 0;
7775718399fSFrançois Tigeot 		break;
7785718399fSFrançois Tigeot 	case 24:
7795718399fSFrançois Tigeot 		var->red.offset = 16;
7805718399fSFrançois Tigeot 		var->green.offset = 8;
7815718399fSFrançois Tigeot 		var->blue.offset = 0;
7825718399fSFrançois Tigeot 		var->red.length = 8;
7835718399fSFrançois Tigeot 		var->green.length = 8;
7845718399fSFrançois Tigeot 		var->blue.length = 8;
7855718399fSFrançois Tigeot 		var->transp.length = 0;
7865718399fSFrançois Tigeot 		var->transp.offset = 0;
7875718399fSFrançois Tigeot 		break;
7885718399fSFrançois Tigeot 	case 32:
7895718399fSFrançois Tigeot 		var->red.offset = 16;
7905718399fSFrançois Tigeot 		var->green.offset = 8;
7915718399fSFrançois Tigeot 		var->blue.offset = 0;
7925718399fSFrançois Tigeot 		var->red.length = 8;
7935718399fSFrançois Tigeot 		var->green.length = 8;
7945718399fSFrançois Tigeot 		var->blue.length = 8;
7955718399fSFrançois Tigeot 		var->transp.length = 8;
7965718399fSFrançois Tigeot 		var->transp.offset = 24;
7975718399fSFrançois Tigeot 		break;
7985718399fSFrançois Tigeot 	default:
7995718399fSFrançois Tigeot 		return -EINVAL;
8005718399fSFrançois Tigeot 	}
8015718399fSFrançois Tigeot 	return 0;
8025718399fSFrançois Tigeot }
803*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_check_var);
8045718399fSFrançois Tigeot 
805*9edbd4a0SFrançois Tigeot /**
806*9edbd4a0SFrançois Tigeot  * drm_fb_helper_set_par - implementation for ->fb_set_par
807*9edbd4a0SFrançois Tigeot  * @info: fbdev registered by the helper
808*9edbd4a0SFrançois Tigeot  *
809*9edbd4a0SFrançois Tigeot  * This will let fbcon do the mode init and is called at initialization time by
810*9edbd4a0SFrançois Tigeot  * the fbdev core when registering the driver, and later on through the hotplug
811*9edbd4a0SFrançois Tigeot  * callback.
812*9edbd4a0SFrançois Tigeot  */
8135718399fSFrançois Tigeot int drm_fb_helper_set_par(struct fb_info *info)
8145718399fSFrançois Tigeot {
8155718399fSFrançois Tigeot 	struct drm_fb_helper *fb_helper = info->par;
8165718399fSFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
8175718399fSFrançois Tigeot 	struct fb_var_screeninfo *var = &info->var;
8185718399fSFrançois Tigeot 	int ret;
8195718399fSFrançois Tigeot 	int i;
8205718399fSFrançois Tigeot 
8215718399fSFrançois Tigeot 	if (var->pixclock != 0) {
8225718399fSFrançois Tigeot 		DRM_ERROR("PIXEL CLOCK SET\n");
8235718399fSFrançois Tigeot 		return -EINVAL;
8245718399fSFrançois Tigeot 	}
8255718399fSFrançois Tigeot 
826*9edbd4a0SFrançois Tigeot 	drm_modeset_lock_all(dev);
8275718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->crtc_count; i++) {
828*9edbd4a0SFrançois Tigeot 		ret = drm_mode_set_config_internal(&fb_helper->crtc_info[i].mode_set);
8295718399fSFrançois Tigeot 		if (ret) {
830*9edbd4a0SFrançois Tigeot 			drm_modeset_unlock_all(dev);
8315718399fSFrançois Tigeot 			return ret;
8325718399fSFrançois Tigeot 		}
8335718399fSFrançois Tigeot 	}
834*9edbd4a0SFrançois Tigeot 	drm_modeset_unlock_all(dev);
8355718399fSFrançois Tigeot 
8365718399fSFrançois Tigeot 	if (fb_helper->delayed_hotplug) {
8375718399fSFrançois Tigeot 		fb_helper->delayed_hotplug = false;
8385718399fSFrançois Tigeot 		drm_fb_helper_hotplug_event(fb_helper);
8395718399fSFrançois Tigeot 	}
8405718399fSFrançois Tigeot 	return 0;
8415718399fSFrançois Tigeot }
842*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_set_par);
8435718399fSFrançois Tigeot 
844*9edbd4a0SFrançois Tigeot /**
845*9edbd4a0SFrançois Tigeot  * drm_fb_helper_pan_display - implementation for ->fb_pan_display
846*9edbd4a0SFrançois Tigeot  * @var: updated screen information
847*9edbd4a0SFrançois Tigeot  * @info: fbdev registered by the helper
848*9edbd4a0SFrançois Tigeot  */
8495718399fSFrançois Tigeot int drm_fb_helper_pan_display(struct fb_var_screeninfo *var,
8505718399fSFrançois Tigeot 			      struct fb_info *info)
8515718399fSFrançois Tigeot {
8525718399fSFrançois Tigeot 	struct drm_fb_helper *fb_helper = info->par;
8535718399fSFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
8545718399fSFrançois Tigeot 	struct drm_mode_set *modeset;
8555718399fSFrançois Tigeot 	int ret = 0;
8565718399fSFrançois Tigeot 	int i;
8575718399fSFrançois Tigeot 
858*9edbd4a0SFrançois Tigeot 	drm_modeset_lock_all(dev);
859*9edbd4a0SFrançois Tigeot 	if (!drm_fb_helper_is_bound(fb_helper)) {
860*9edbd4a0SFrançois Tigeot 		drm_modeset_unlock_all(dev);
861*9edbd4a0SFrançois Tigeot 		return -EBUSY;
862*9edbd4a0SFrançois Tigeot 	}
8635718399fSFrançois Tigeot 
864*9edbd4a0SFrançois Tigeot 	for (i = 0; i < fb_helper->crtc_count; i++) {
8655718399fSFrançois Tigeot 		modeset = &fb_helper->crtc_info[i].mode_set;
8665718399fSFrançois Tigeot 
8675718399fSFrançois Tigeot 		modeset->x = var->xoffset;
8685718399fSFrançois Tigeot 		modeset->y = var->yoffset;
8695718399fSFrançois Tigeot 
8705718399fSFrançois Tigeot 		if (modeset->num_connectors) {
871*9edbd4a0SFrançois Tigeot 			ret = drm_mode_set_config_internal(modeset);
8725718399fSFrançois Tigeot 			if (!ret) {
8735718399fSFrançois Tigeot 				info->var.xoffset = var->xoffset;
8745718399fSFrançois Tigeot 				info->var.yoffset = var->yoffset;
8755718399fSFrançois Tigeot 			}
8765718399fSFrançois Tigeot 		}
8775718399fSFrançois Tigeot 	}
878*9edbd4a0SFrançois Tigeot 	drm_modeset_unlock_all(dev);
8795718399fSFrançois Tigeot 	return ret;
8805718399fSFrançois Tigeot }
881*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_pan_display);
8825718399fSFrançois Tigeot #endif
8835718399fSFrançois Tigeot 
884*9edbd4a0SFrançois Tigeot /*
885*9edbd4a0SFrançois Tigeot  * Allocates the backing storage and sets up the fbdev info structure through
886*9edbd4a0SFrançois Tigeot  * the ->fb_probe callback and then registers the fbdev and sets up the panic
887*9edbd4a0SFrançois Tigeot  * notifier.
888*9edbd4a0SFrançois Tigeot  */
889*9edbd4a0SFrançois Tigeot static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper,
8905718399fSFrançois Tigeot 					 int preferred_bpp)
8915718399fSFrançois Tigeot {
892*9edbd4a0SFrançois Tigeot 	int ret = 0;
8935718399fSFrançois Tigeot 	int crtc_count = 0;
8945718399fSFrançois Tigeot 	int i;
8955718399fSFrançois Tigeot 	struct fb_info *info;
8965718399fSFrançois Tigeot 	struct drm_fb_helper_surface_size sizes;
8975718399fSFrançois Tigeot 	int gamma_size = 0;
8985718399fSFrançois Tigeot 
8995718399fSFrançois Tigeot 	memset(&sizes, 0, sizeof(struct drm_fb_helper_surface_size));
9005718399fSFrançois Tigeot 	sizes.surface_depth = 24;
9015718399fSFrançois Tigeot 	sizes.surface_bpp = 32;
9025718399fSFrançois Tigeot 	sizes.fb_width = (unsigned)-1;
9035718399fSFrançois Tigeot 	sizes.fb_height = (unsigned)-1;
9045718399fSFrançois Tigeot 
9055718399fSFrançois Tigeot 	/* if driver picks 8 or 16 by default use that
9065718399fSFrançois Tigeot 	   for both depth/bpp */
9074a968d2aSFrançois Tigeot 	if (preferred_bpp != sizes.surface_bpp)
9085718399fSFrançois Tigeot 		sizes.surface_depth = sizes.surface_bpp = preferred_bpp;
9094a968d2aSFrançois Tigeot 
9105718399fSFrançois Tigeot 	/* first up get a count of crtcs now in use and new min/maxes width/heights */
9115718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
9125718399fSFrançois Tigeot 		struct drm_fb_helper_connector *fb_helper_conn = fb_helper->connector_info[i];
9134a968d2aSFrançois Tigeot 		struct drm_cmdline_mode *cmdline_mode;
9145718399fSFrançois Tigeot 
9155718399fSFrançois Tigeot 		cmdline_mode = &fb_helper_conn->cmdline_mode;
9165718399fSFrançois Tigeot 
9175718399fSFrançois Tigeot 		if (cmdline_mode->bpp_specified) {
9185718399fSFrançois Tigeot 			switch (cmdline_mode->bpp) {
9195718399fSFrançois Tigeot 			case 8:
9205718399fSFrançois Tigeot 				sizes.surface_depth = sizes.surface_bpp = 8;
9215718399fSFrançois Tigeot 				break;
9225718399fSFrançois Tigeot 			case 15:
9235718399fSFrançois Tigeot 				sizes.surface_depth = 15;
9245718399fSFrançois Tigeot 				sizes.surface_bpp = 16;
9255718399fSFrançois Tigeot 				break;
9265718399fSFrançois Tigeot 			case 16:
9275718399fSFrançois Tigeot 				sizes.surface_depth = sizes.surface_bpp = 16;
9285718399fSFrançois Tigeot 				break;
9295718399fSFrançois Tigeot 			case 24:
9305718399fSFrançois Tigeot 				sizes.surface_depth = sizes.surface_bpp = 24;
9315718399fSFrançois Tigeot 				break;
9325718399fSFrançois Tigeot 			case 32:
9335718399fSFrançois Tigeot 				sizes.surface_depth = 24;
9345718399fSFrançois Tigeot 				sizes.surface_bpp = 32;
9355718399fSFrançois Tigeot 				break;
9365718399fSFrançois Tigeot 			}
9375718399fSFrançois Tigeot 			break;
9385718399fSFrançois Tigeot 		}
9395718399fSFrançois Tigeot 	}
9405718399fSFrançois Tigeot 
9415718399fSFrançois Tigeot 	crtc_count = 0;
9425718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->crtc_count; i++) {
9435718399fSFrançois Tigeot 		struct drm_display_mode *desired_mode;
9445718399fSFrançois Tigeot 		desired_mode = fb_helper->crtc_info[i].desired_mode;
9455718399fSFrançois Tigeot 
9465718399fSFrançois Tigeot 		if (desired_mode) {
9475718399fSFrançois Tigeot 			if (gamma_size == 0)
9485718399fSFrançois Tigeot 				gamma_size = fb_helper->crtc_info[i].mode_set.crtc->gamma_size;
9495718399fSFrançois Tigeot 			if (desired_mode->hdisplay < sizes.fb_width)
9505718399fSFrançois Tigeot 				sizes.fb_width = desired_mode->hdisplay;
9515718399fSFrançois Tigeot 			if (desired_mode->vdisplay < sizes.fb_height)
9525718399fSFrançois Tigeot 				sizes.fb_height = desired_mode->vdisplay;
9535718399fSFrançois Tigeot 			if (desired_mode->hdisplay > sizes.surface_width)
9545718399fSFrançois Tigeot 				sizes.surface_width = desired_mode->hdisplay;
9555718399fSFrançois Tigeot 			if (desired_mode->vdisplay > sizes.surface_height)
9565718399fSFrançois Tigeot 				sizes.surface_height = desired_mode->vdisplay;
9575718399fSFrançois Tigeot 			crtc_count++;
9585718399fSFrançois Tigeot 		}
9595718399fSFrançois Tigeot 	}
9605718399fSFrançois Tigeot 
9615718399fSFrançois Tigeot 	if (crtc_count == 0 || sizes.fb_width == -1 || sizes.fb_height == -1) {
9625718399fSFrançois Tigeot 		/* hmm everyone went away - assume VGA cable just fell out
9635718399fSFrançois Tigeot 		   and will come back later. */
9645718399fSFrançois Tigeot 		DRM_INFO("Cannot find any crtc or sizes - going 1024x768\n");
9655718399fSFrançois Tigeot 		sizes.fb_width = sizes.surface_width = 1024;
9665718399fSFrançois Tigeot 		sizes.fb_height = sizes.surface_height = 768;
9675718399fSFrançois Tigeot 	}
9685718399fSFrançois Tigeot 
9695718399fSFrançois Tigeot 	/* push down into drivers */
970*9edbd4a0SFrançois Tigeot 	ret = (*fb_helper->funcs->fb_probe)(fb_helper, &sizes);
971*9edbd4a0SFrançois Tigeot 	if (ret < 0)
972*9edbd4a0SFrançois Tigeot 		return ret;
9735718399fSFrançois Tigeot 
9745718399fSFrançois Tigeot 	info = fb_helper->fbdev;
9755718399fSFrançois Tigeot 
976*9edbd4a0SFrançois Tigeot 	/*
977*9edbd4a0SFrançois Tigeot 	 * Set the fb pointer - usually drm_setup_crtcs does this for hotplug
978*9edbd4a0SFrançois Tigeot 	 * events, but at init time drm_setup_crtcs needs to be called before
979*9edbd4a0SFrançois Tigeot 	 * the fb is allocated (since we need to figure out the desired size of
980*9edbd4a0SFrançois Tigeot 	 * the fb before we can allocate it ...). Hence we need to fix things up
981*9edbd4a0SFrançois Tigeot 	 * here again.
982*9edbd4a0SFrançois Tigeot 	 */
9834a968d2aSFrançois Tigeot 	for (i = 0; i < fb_helper->crtc_count; i++)
984*9edbd4a0SFrançois Tigeot 		if (fb_helper->crtc_info[i].mode_set.num_connectors)
9855718399fSFrançois Tigeot 			fb_helper->crtc_info[i].mode_set.fb = fb_helper->fb;
9865718399fSFrançois Tigeot 
987*9edbd4a0SFrançois Tigeot 
9885718399fSFrançois Tigeot #if 0
9895718399fSFrançois Tigeot 	info->var.pixclock = 0;
9904a968d2aSFrançois Tigeot 	if (register_framebuffer(info) < 0)
9915718399fSFrançois Tigeot 		return -EINVAL;
9925718399fSFrançois Tigeot 
9934a968d2aSFrançois Tigeot 	dev_info(fb_helper->dev->dev, "fb%d: %s frame buffer device\n",
9944a968d2aSFrançois Tigeot 			info->node, info->fix.id);
9955718399fSFrançois Tigeot 
9965718399fSFrançois Tigeot 	/* Switch back to kernel console on panic */
9975718399fSFrançois Tigeot 	/* multi card linked list maybe */
9985718399fSFrançois Tigeot 	if (list_empty(&kernel_fb_helper_list)) {
9994a968d2aSFrançois Tigeot 		dev_info(fb_helper->dev->dev, "registered panic notifier\n");
10005718399fSFrançois Tigeot 		atomic_notifier_chain_register(&panic_notifier_list,
10015718399fSFrançois Tigeot 					       &paniced);
10024a968d2aSFrançois Tigeot 		register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
10035718399fSFrançois Tigeot 	}
1004*9edbd4a0SFrançois Tigeot 
10055718399fSFrançois Tigeot 	list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
10065718399fSFrançois Tigeot #endif
10075718399fSFrançois Tigeot 
10085718399fSFrançois Tigeot 	return 0;
10095718399fSFrançois Tigeot }
10105718399fSFrançois Tigeot 
10115718399fSFrançois Tigeot #if 0
1012*9edbd4a0SFrançois Tigeot /**
1013*9edbd4a0SFrançois Tigeot  * drm_fb_helper_fill_fix - initializes fixed fbdev information
1014*9edbd4a0SFrançois Tigeot  * @info: fbdev registered by the helper
1015*9edbd4a0SFrançois Tigeot  * @pitch: desired pitch
1016*9edbd4a0SFrançois Tigeot  * @depth: desired depth
1017*9edbd4a0SFrançois Tigeot  *
1018*9edbd4a0SFrançois Tigeot  * Helper to fill in the fixed fbdev information useful for a non-accelerated
1019*9edbd4a0SFrançois Tigeot  * fbdev emulations. Drivers which support acceleration methods which impose
1020*9edbd4a0SFrançois Tigeot  * additional constraints need to set up their own limits.
1021*9edbd4a0SFrançois Tigeot  *
1022*9edbd4a0SFrançois Tigeot  * Drivers should call this (or their equivalent setup code) from their
1023*9edbd4a0SFrançois Tigeot  * ->fb_probe callback.
1024*9edbd4a0SFrançois Tigeot  */
10255718399fSFrançois Tigeot void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
10265718399fSFrançois Tigeot 			    uint32_t depth)
10275718399fSFrançois Tigeot {
10285718399fSFrançois Tigeot 	info->fix.type = FB_TYPE_PACKED_PIXELS;
10295718399fSFrançois Tigeot 	info->fix.visual = depth == 8 ? FB_VISUAL_PSEUDOCOLOR :
1030*9edbd4a0SFrançois Tigeot 		FB_VISUAL_TRUECOLOR;
10315718399fSFrançois Tigeot 	info->fix.mmio_start = 0;
10325718399fSFrançois Tigeot 	info->fix.mmio_len = 0;
10335718399fSFrançois Tigeot 	info->fix.type_aux = 0;
10345718399fSFrançois Tigeot 	info->fix.xpanstep = 1; /* doing it in hw */
10355718399fSFrançois Tigeot 	info->fix.ypanstep = 1; /* doing it in hw */
10365718399fSFrançois Tigeot 	info->fix.ywrapstep = 0;
10375718399fSFrançois Tigeot 	info->fix.accel = FB_ACCEL_NONE;
10385718399fSFrançois Tigeot 	info->fix.type_aux = 0;
10395718399fSFrançois Tigeot 
10405718399fSFrançois Tigeot 	info->fix.line_length = pitch;
10415718399fSFrançois Tigeot 	return;
10425718399fSFrançois Tigeot }
1043*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_fill_fix);
10445718399fSFrançois Tigeot 
1045*9edbd4a0SFrançois Tigeot /**
1046*9edbd4a0SFrançois Tigeot  * drm_fb_helper_fill_var - initalizes variable fbdev information
1047*9edbd4a0SFrançois Tigeot  * @info: fbdev instance to set up
1048*9edbd4a0SFrançois Tigeot  * @fb_helper: fb helper instance to use as template
1049*9edbd4a0SFrançois Tigeot  * @fb_width: desired fb width
1050*9edbd4a0SFrançois Tigeot  * @fb_height: desired fb height
1051*9edbd4a0SFrançois Tigeot  *
1052*9edbd4a0SFrançois Tigeot  * Sets up the variable fbdev metainformation from the given fb helper instance
1053*9edbd4a0SFrançois Tigeot  * and the drm framebuffer allocated in fb_helper->fb.
1054*9edbd4a0SFrançois Tigeot  *
1055*9edbd4a0SFrançois Tigeot  * Drivers should call this (or their equivalent setup code) from their
1056*9edbd4a0SFrançois Tigeot  * ->fb_probe callback after having allocated the fbdev backing
1057*9edbd4a0SFrançois Tigeot  * storage framebuffer.
1058*9edbd4a0SFrançois Tigeot  */
10595718399fSFrançois Tigeot void drm_fb_helper_fill_var(struct fb_info *info, struct drm_fb_helper *fb_helper,
10605718399fSFrançois Tigeot 			    uint32_t fb_width, uint32_t fb_height)
10615718399fSFrançois Tigeot {
10625718399fSFrançois Tigeot 	struct drm_framebuffer *fb = fb_helper->fb;
10635718399fSFrançois Tigeot 	info->pseudo_palette = fb_helper->pseudo_palette;
10645718399fSFrançois Tigeot 	info->var.xres_virtual = fb->width;
10655718399fSFrançois Tigeot 	info->var.yres_virtual = fb->height;
10665718399fSFrançois Tigeot 	info->var.bits_per_pixel = fb->bits_per_pixel;
10675718399fSFrançois Tigeot 	info->var.accel_flags = FB_ACCELF_TEXT;
10685718399fSFrançois Tigeot 	info->var.xoffset = 0;
10695718399fSFrançois Tigeot 	info->var.yoffset = 0;
10705718399fSFrançois Tigeot 	info->var.activate = FB_ACTIVATE_NOW;
10715718399fSFrançois Tigeot 	info->var.height = -1;
10725718399fSFrançois Tigeot 	info->var.width = -1;
10735718399fSFrançois Tigeot 
10745718399fSFrançois Tigeot 	switch (fb->depth) {
10755718399fSFrançois Tigeot 	case 8:
10765718399fSFrançois Tigeot 		info->var.red.offset = 0;
10775718399fSFrançois Tigeot 		info->var.green.offset = 0;
10785718399fSFrançois Tigeot 		info->var.blue.offset = 0;
10795718399fSFrançois Tigeot 		info->var.red.length = 8; /* 8bit DAC */
10805718399fSFrançois Tigeot 		info->var.green.length = 8;
10815718399fSFrançois Tigeot 		info->var.blue.length = 8;
10825718399fSFrançois Tigeot 		info->var.transp.offset = 0;
10835718399fSFrançois Tigeot 		info->var.transp.length = 0;
10845718399fSFrançois Tigeot 		break;
10855718399fSFrançois Tigeot 	case 15:
10865718399fSFrançois Tigeot 		info->var.red.offset = 10;
10875718399fSFrançois Tigeot 		info->var.green.offset = 5;
10885718399fSFrançois Tigeot 		info->var.blue.offset = 0;
10895718399fSFrançois Tigeot 		info->var.red.length = 5;
10905718399fSFrançois Tigeot 		info->var.green.length = 5;
10915718399fSFrançois Tigeot 		info->var.blue.length = 5;
10925718399fSFrançois Tigeot 		info->var.transp.offset = 15;
10935718399fSFrançois Tigeot 		info->var.transp.length = 1;
10945718399fSFrançois Tigeot 		break;
10955718399fSFrançois Tigeot 	case 16:
10965718399fSFrançois Tigeot 		info->var.red.offset = 11;
10975718399fSFrançois Tigeot 		info->var.green.offset = 5;
10985718399fSFrançois Tigeot 		info->var.blue.offset = 0;
10995718399fSFrançois Tigeot 		info->var.red.length = 5;
11005718399fSFrançois Tigeot 		info->var.green.length = 6;
11015718399fSFrançois Tigeot 		info->var.blue.length = 5;
11025718399fSFrançois Tigeot 		info->var.transp.offset = 0;
11035718399fSFrançois Tigeot 		break;
11045718399fSFrançois Tigeot 	case 24:
11055718399fSFrançois Tigeot 		info->var.red.offset = 16;
11065718399fSFrançois Tigeot 		info->var.green.offset = 8;
11075718399fSFrançois Tigeot 		info->var.blue.offset = 0;
11085718399fSFrançois Tigeot 		info->var.red.length = 8;
11095718399fSFrançois Tigeot 		info->var.green.length = 8;
11105718399fSFrançois Tigeot 		info->var.blue.length = 8;
11115718399fSFrançois Tigeot 		info->var.transp.offset = 0;
11125718399fSFrançois Tigeot 		info->var.transp.length = 0;
11135718399fSFrançois Tigeot 		break;
11145718399fSFrançois Tigeot 	case 32:
11155718399fSFrançois Tigeot 		info->var.red.offset = 16;
11165718399fSFrançois Tigeot 		info->var.green.offset = 8;
11175718399fSFrançois Tigeot 		info->var.blue.offset = 0;
11185718399fSFrançois Tigeot 		info->var.red.length = 8;
11195718399fSFrançois Tigeot 		info->var.green.length = 8;
11205718399fSFrançois Tigeot 		info->var.blue.length = 8;
11215718399fSFrançois Tigeot 		info->var.transp.offset = 24;
11225718399fSFrançois Tigeot 		info->var.transp.length = 8;
11235718399fSFrançois Tigeot 		break;
11245718399fSFrançois Tigeot 	default:
11255718399fSFrançois Tigeot 		break;
11265718399fSFrançois Tigeot 	}
11275718399fSFrançois Tigeot 
11285718399fSFrançois Tigeot 	info->var.xres = fb_width;
11295718399fSFrançois Tigeot 	info->var.yres = fb_height;
11305718399fSFrançois Tigeot }
1131*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_fill_var);
11325718399fSFrançois Tigeot #endif
11335718399fSFrançois Tigeot 
11345718399fSFrançois Tigeot static int drm_fb_helper_probe_connector_modes(struct drm_fb_helper *fb_helper,
11355718399fSFrançois Tigeot 					       uint32_t maxX,
11365718399fSFrançois Tigeot 					       uint32_t maxY)
11375718399fSFrançois Tigeot {
11385718399fSFrançois Tigeot 	struct drm_connector *connector;
11395718399fSFrançois Tigeot 	int count = 0;
11405718399fSFrançois Tigeot 	int i;
11415718399fSFrançois Tigeot 
11425718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
11435718399fSFrançois Tigeot 		connector = fb_helper->connector_info[i]->connector;
11445718399fSFrançois Tigeot 		count += connector->funcs->fill_modes(connector, maxX, maxY);
11455718399fSFrançois Tigeot 	}
11465718399fSFrançois Tigeot 
11475718399fSFrançois Tigeot 	return count;
11485718399fSFrançois Tigeot }
11495718399fSFrançois Tigeot 
11505718399fSFrançois Tigeot static struct drm_display_mode *drm_has_preferred_mode(struct drm_fb_helper_connector *fb_connector, int width, int height)
11515718399fSFrançois Tigeot {
11525718399fSFrançois Tigeot 	struct drm_display_mode *mode;
11535718399fSFrançois Tigeot 
11545718399fSFrançois Tigeot 	list_for_each_entry(mode, &fb_connector->connector->modes, head) {
11555718399fSFrançois Tigeot 		if (drm_mode_width(mode) > width ||
11565718399fSFrançois Tigeot 		    drm_mode_height(mode) > height)
11575718399fSFrançois Tigeot 			continue;
11585718399fSFrançois Tigeot 		if (mode->type & DRM_MODE_TYPE_PREFERRED)
11595718399fSFrançois Tigeot 			return mode;
11605718399fSFrançois Tigeot 	}
11615718399fSFrançois Tigeot 	return NULL;
11625718399fSFrançois Tigeot }
11635718399fSFrançois Tigeot 
11645718399fSFrançois Tigeot static bool drm_has_cmdline_mode(struct drm_fb_helper_connector *fb_connector)
11655718399fSFrançois Tigeot {
11664a968d2aSFrançois Tigeot 	struct drm_cmdline_mode *cmdline_mode;
11675718399fSFrançois Tigeot 	cmdline_mode = &fb_connector->cmdline_mode;
11685718399fSFrançois Tigeot 	return cmdline_mode->specified;
11695718399fSFrançois Tigeot }
11705718399fSFrançois Tigeot 
11715718399fSFrançois Tigeot static struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_connector *fb_helper_conn,
11725718399fSFrançois Tigeot 						      int width, int height)
11735718399fSFrançois Tigeot {
11745718399fSFrançois Tigeot 	struct drm_cmdline_mode *cmdline_mode;
11755718399fSFrançois Tigeot 	struct drm_display_mode *mode = NULL;
11765718399fSFrançois Tigeot 
11774a968d2aSFrançois Tigeot 	cmdline_mode = &fb_helper_conn->cmdline_mode;
1178*9edbd4a0SFrançois Tigeot 	if (cmdline_mode->specified == false)
1179*9edbd4a0SFrançois Tigeot 		return mode;
11805718399fSFrançois Tigeot 
11815718399fSFrançois Tigeot 	/* attempt to find a matching mode in the list of modes
11825718399fSFrançois Tigeot 	 *  we have gotten so far, if not add a CVT mode that conforms
11835718399fSFrançois Tigeot 	 */
11845718399fSFrançois Tigeot 	if (cmdline_mode->rb || cmdline_mode->margins)
11855718399fSFrançois Tigeot 		goto create_mode;
11865718399fSFrançois Tigeot 
11875718399fSFrançois Tigeot 	list_for_each_entry(mode, &fb_helper_conn->connector->modes, head) {
11885718399fSFrançois Tigeot 		/* check width/height */
11895718399fSFrançois Tigeot 		if (mode->hdisplay != cmdline_mode->xres ||
11905718399fSFrançois Tigeot 		    mode->vdisplay != cmdline_mode->yres)
11915718399fSFrançois Tigeot 			continue;
11925718399fSFrançois Tigeot 
11935718399fSFrançois Tigeot 		if (cmdline_mode->refresh_specified) {
11945718399fSFrançois Tigeot 			if (mode->vrefresh != cmdline_mode->refresh)
11955718399fSFrançois Tigeot 				continue;
11965718399fSFrançois Tigeot 		}
11975718399fSFrançois Tigeot 
11985718399fSFrançois Tigeot 		if (cmdline_mode->interlace) {
11995718399fSFrançois Tigeot 			if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
12005718399fSFrançois Tigeot 				continue;
12015718399fSFrançois Tigeot 		}
12025718399fSFrançois Tigeot 		return mode;
12035718399fSFrançois Tigeot 	}
12045718399fSFrançois Tigeot 
12055718399fSFrançois Tigeot create_mode:
1206*9edbd4a0SFrançois Tigeot 	mode = drm_mode_create_from_cmdline_mode(fb_helper_conn->connector->dev,
1207*9edbd4a0SFrançois Tigeot 						 cmdline_mode);
12085718399fSFrançois Tigeot 	list_add(&mode->head, &fb_helper_conn->connector->modes);
12095718399fSFrançois Tigeot 	return mode;
12105718399fSFrançois Tigeot }
12115718399fSFrançois Tigeot 
12125718399fSFrançois Tigeot static bool drm_connector_enabled(struct drm_connector *connector, bool strict)
12135718399fSFrançois Tigeot {
12145718399fSFrançois Tigeot 	bool enable;
12155718399fSFrançois Tigeot 
1216*9edbd4a0SFrançois Tigeot 	if (strict)
12175718399fSFrançois Tigeot 		enable = connector->status == connector_status_connected;
1218*9edbd4a0SFrançois Tigeot 	else
12195718399fSFrançois Tigeot 		enable = connector->status != connector_status_disconnected;
1220*9edbd4a0SFrançois Tigeot 
12215718399fSFrançois Tigeot 	return enable;
12225718399fSFrançois Tigeot }
12235718399fSFrançois Tigeot 
12245718399fSFrançois Tigeot static void drm_enable_connectors(struct drm_fb_helper *fb_helper,
12255718399fSFrançois Tigeot 				  bool *enabled)
12265718399fSFrançois Tigeot {
12275718399fSFrançois Tigeot 	bool any_enabled = false;
12285718399fSFrançois Tigeot 	struct drm_connector *connector;
12295718399fSFrançois Tigeot 	int i = 0;
12305718399fSFrançois Tigeot 
12315718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
12325718399fSFrançois Tigeot 		connector = fb_helper->connector_info[i]->connector;
12335718399fSFrançois Tigeot 		enabled[i] = drm_connector_enabled(connector, true);
12345718399fSFrançois Tigeot 		DRM_DEBUG_KMS("connector %d enabled? %s\n", connector->base.id,
12355718399fSFrançois Tigeot 			  enabled[i] ? "yes" : "no");
12365718399fSFrançois Tigeot 		any_enabled |= enabled[i];
12375718399fSFrançois Tigeot 	}
12385718399fSFrançois Tigeot 
12395718399fSFrançois Tigeot 	if (any_enabled)
12405718399fSFrançois Tigeot 		return;
12415718399fSFrançois Tigeot 
12425718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
12435718399fSFrançois Tigeot 		connector = fb_helper->connector_info[i]->connector;
12445718399fSFrançois Tigeot 		enabled[i] = drm_connector_enabled(connector, false);
12455718399fSFrançois Tigeot 	}
12465718399fSFrançois Tigeot }
12475718399fSFrançois Tigeot 
12485718399fSFrançois Tigeot static bool drm_target_cloned(struct drm_fb_helper *fb_helper,
12495718399fSFrançois Tigeot 			      struct drm_display_mode **modes,
12505718399fSFrançois Tigeot 			      bool *enabled, int width, int height)
12515718399fSFrançois Tigeot {
12525718399fSFrançois Tigeot 	int count, i, j;
12535718399fSFrançois Tigeot 	bool can_clone = false;
12545718399fSFrançois Tigeot 	struct drm_fb_helper_connector *fb_helper_conn;
12555718399fSFrançois Tigeot 	struct drm_display_mode *dmt_mode, *mode;
12565718399fSFrançois Tigeot 
12575718399fSFrançois Tigeot 	/* only contemplate cloning in the single crtc case */
12585718399fSFrançois Tigeot 	if (fb_helper->crtc_count > 1)
12595718399fSFrançois Tigeot 		return false;
12605718399fSFrançois Tigeot 
12615718399fSFrançois Tigeot 	count = 0;
12625718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
12635718399fSFrançois Tigeot 		if (enabled[i])
12645718399fSFrançois Tigeot 			count++;
12655718399fSFrançois Tigeot 	}
12665718399fSFrançois Tigeot 
12675718399fSFrançois Tigeot 	/* only contemplate cloning if more than one connector is enabled */
12685718399fSFrançois Tigeot 	if (count <= 1)
12695718399fSFrançois Tigeot 		return false;
12705718399fSFrançois Tigeot 
12715718399fSFrançois Tigeot 	/* check the command line or if nothing common pick 1024x768 */
12725718399fSFrançois Tigeot 	can_clone = true;
12735718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
12745718399fSFrançois Tigeot 		if (!enabled[i])
12755718399fSFrançois Tigeot 			continue;
12765718399fSFrançois Tigeot 		fb_helper_conn = fb_helper->connector_info[i];
12775718399fSFrançois Tigeot 		modes[i] = drm_pick_cmdline_mode(fb_helper_conn, width, height);
12785718399fSFrançois Tigeot 		if (!modes[i]) {
12795718399fSFrançois Tigeot 			can_clone = false;
12805718399fSFrançois Tigeot 			break;
12815718399fSFrançois Tigeot 		}
12825718399fSFrançois Tigeot 		for (j = 0; j < i; j++) {
12835718399fSFrançois Tigeot 			if (!enabled[j])
12845718399fSFrançois Tigeot 				continue;
12855718399fSFrançois Tigeot 			if (!drm_mode_equal(modes[j], modes[i]))
12865718399fSFrançois Tigeot 				can_clone = false;
12875718399fSFrançois Tigeot 		}
12885718399fSFrançois Tigeot 	}
12895718399fSFrançois Tigeot 
12905718399fSFrançois Tigeot 	if (can_clone) {
12915718399fSFrançois Tigeot 		DRM_DEBUG_KMS("can clone using command line\n");
12925718399fSFrançois Tigeot 		return true;
12935718399fSFrançois Tigeot 	}
12945718399fSFrançois Tigeot 
12955718399fSFrançois Tigeot 	/* try and find a 1024x768 mode on each connector */
12965718399fSFrançois Tigeot 	can_clone = true;
1297ce3d36d7SFrançois Tigeot 	dmt_mode = drm_mode_find_dmt(fb_helper->dev, 1024, 768, 60, false);
12985718399fSFrançois Tigeot 
12995718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
13005718399fSFrançois Tigeot 
13015718399fSFrançois Tigeot 		if (!enabled[i])
13025718399fSFrançois Tigeot 			continue;
13035718399fSFrançois Tigeot 
13045718399fSFrançois Tigeot 		fb_helper_conn = fb_helper->connector_info[i];
13055718399fSFrançois Tigeot 		list_for_each_entry(mode, &fb_helper_conn->connector->modes, head) {
13065718399fSFrançois Tigeot 			if (drm_mode_equal(mode, dmt_mode))
13075718399fSFrançois Tigeot 				modes[i] = mode;
13085718399fSFrançois Tigeot 		}
13095718399fSFrançois Tigeot 		if (!modes[i])
13105718399fSFrançois Tigeot 			can_clone = false;
13115718399fSFrançois Tigeot 	}
13125718399fSFrançois Tigeot 
13135718399fSFrançois Tigeot 	if (can_clone) {
13145718399fSFrançois Tigeot 		DRM_DEBUG_KMS("can clone using 1024x768\n");
13155718399fSFrançois Tigeot 		return true;
13165718399fSFrançois Tigeot 	}
13175718399fSFrançois Tigeot 	DRM_INFO("kms: can't enable cloning when we probably wanted to.\n");
13185718399fSFrançois Tigeot 	return false;
13195718399fSFrançois Tigeot }
13205718399fSFrançois Tigeot 
13215718399fSFrançois Tigeot static bool drm_target_preferred(struct drm_fb_helper *fb_helper,
13225718399fSFrançois Tigeot 				 struct drm_display_mode **modes,
13235718399fSFrançois Tigeot 				 bool *enabled, int width, int height)
13245718399fSFrançois Tigeot {
13255718399fSFrançois Tigeot 	struct drm_fb_helper_connector *fb_helper_conn;
13265718399fSFrançois Tigeot 	int i;
13275718399fSFrançois Tigeot 
13285718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
13295718399fSFrançois Tigeot 		fb_helper_conn = fb_helper->connector_info[i];
13305718399fSFrançois Tigeot 
13315718399fSFrançois Tigeot 		if (enabled[i] == false)
13325718399fSFrançois Tigeot 			continue;
13335718399fSFrançois Tigeot 
13345718399fSFrançois Tigeot 		DRM_DEBUG_KMS("looking for cmdline mode on connector %d\n",
13355718399fSFrançois Tigeot 			      fb_helper_conn->connector->base.id);
13365718399fSFrançois Tigeot 
13375718399fSFrançois Tigeot 		/* got for command line mode first */
13385718399fSFrançois Tigeot 		modes[i] = drm_pick_cmdline_mode(fb_helper_conn, width, height);
13395718399fSFrançois Tigeot 		if (!modes[i]) {
13405718399fSFrançois Tigeot 			DRM_DEBUG_KMS("looking for preferred mode on connector %d\n",
13415718399fSFrançois Tigeot 				      fb_helper_conn->connector->base.id);
13425718399fSFrançois Tigeot 			modes[i] = drm_has_preferred_mode(fb_helper_conn, width, height);
13435718399fSFrançois Tigeot 		}
13445718399fSFrançois Tigeot 		/* No preferred modes, pick one off the list */
13455718399fSFrançois Tigeot 		if (!modes[i] && !list_empty(&fb_helper_conn->connector->modes)) {
13465718399fSFrançois Tigeot 			list_for_each_entry(modes[i], &fb_helper_conn->connector->modes, head)
13475718399fSFrançois Tigeot 				break;
13485718399fSFrançois Tigeot 		}
13495718399fSFrançois Tigeot 		DRM_DEBUG_KMS("found mode %s\n", modes[i] ? modes[i]->name :
13505718399fSFrançois Tigeot 			  "none");
13515718399fSFrançois Tigeot 	}
13525718399fSFrançois Tigeot 	return true;
13535718399fSFrançois Tigeot }
13545718399fSFrançois Tigeot 
13555718399fSFrançois Tigeot static int drm_pick_crtcs(struct drm_fb_helper *fb_helper,
13565718399fSFrançois Tigeot 			  struct drm_fb_helper_crtc **best_crtcs,
13575718399fSFrançois Tigeot 			  struct drm_display_mode **modes,
13585718399fSFrançois Tigeot 			  int n, int width, int height)
13595718399fSFrançois Tigeot {
13605718399fSFrançois Tigeot 	int c, o;
13615718399fSFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
13625718399fSFrançois Tigeot 	struct drm_connector *connector;
13635718399fSFrançois Tigeot 	struct drm_connector_helper_funcs *connector_funcs;
13645718399fSFrançois Tigeot 	struct drm_encoder *encoder;
13655718399fSFrançois Tigeot 	int my_score, best_score, score;
13665718399fSFrançois Tigeot 	struct drm_fb_helper_crtc **crtcs, *crtc;
13675718399fSFrançois Tigeot 	struct drm_fb_helper_connector *fb_helper_conn;
13685718399fSFrançois Tigeot 
13695718399fSFrançois Tigeot 	if (n == fb_helper->connector_count)
13705718399fSFrançois Tigeot 		return 0;
13715718399fSFrançois Tigeot 
13725718399fSFrançois Tigeot 	fb_helper_conn = fb_helper->connector_info[n];
13735718399fSFrançois Tigeot 	connector = fb_helper_conn->connector;
13745718399fSFrançois Tigeot 
13755718399fSFrançois Tigeot 	best_crtcs[n] = NULL;
13765718399fSFrançois Tigeot 	best_score = drm_pick_crtcs(fb_helper, best_crtcs, modes, n+1, width, height);
13775718399fSFrançois Tigeot 	if (modes[n] == NULL)
13785718399fSFrançois Tigeot 		return best_score;
13795718399fSFrançois Tigeot 
1380*9edbd4a0SFrançois Tigeot 	crtcs = kzalloc(dev->mode_config.num_connector *
1381*9edbd4a0SFrançois Tigeot 			sizeof(struct drm_fb_helper_crtc *), GFP_KERNEL);
1382*9edbd4a0SFrançois Tigeot 	if (!crtcs)
1383*9edbd4a0SFrançois Tigeot 		return best_score;
13845718399fSFrançois Tigeot 
13855718399fSFrançois Tigeot 	my_score = 1;
13865718399fSFrançois Tigeot 	if (connector->status == connector_status_connected)
13875718399fSFrançois Tigeot 		my_score++;
13885718399fSFrançois Tigeot 	if (drm_has_cmdline_mode(fb_helper_conn))
13895718399fSFrançois Tigeot 		my_score++;
13905718399fSFrançois Tigeot 	if (drm_has_preferred_mode(fb_helper_conn, width, height))
13915718399fSFrançois Tigeot 		my_score++;
13925718399fSFrançois Tigeot 
13935718399fSFrançois Tigeot 	connector_funcs = connector->helper_private;
13945718399fSFrançois Tigeot 	encoder = connector_funcs->best_encoder(connector);
13955718399fSFrançois Tigeot 	if (!encoder)
13965718399fSFrançois Tigeot 		goto out;
13975718399fSFrançois Tigeot 
13985718399fSFrançois Tigeot 	/* select a crtc for this connector and then attempt to configure
13995718399fSFrançois Tigeot 	   remaining connectors */
14005718399fSFrançois Tigeot 	for (c = 0; c < fb_helper->crtc_count; c++) {
14015718399fSFrançois Tigeot 		crtc = &fb_helper->crtc_info[c];
14025718399fSFrançois Tigeot 
1403*9edbd4a0SFrançois Tigeot 		if ((encoder->possible_crtcs & (1 << c)) == 0)
14045718399fSFrançois Tigeot 			continue;
14055718399fSFrançois Tigeot 
14065718399fSFrançois Tigeot 		for (o = 0; o < n; o++)
14075718399fSFrançois Tigeot 			if (best_crtcs[o] == crtc)
14085718399fSFrançois Tigeot 				break;
14095718399fSFrançois Tigeot 
14105718399fSFrançois Tigeot 		if (o < n) {
14115718399fSFrançois Tigeot 			/* ignore cloning unless only a single crtc */
14125718399fSFrançois Tigeot 			if (fb_helper->crtc_count > 1)
14135718399fSFrançois Tigeot 				continue;
14145718399fSFrançois Tigeot 
14155718399fSFrançois Tigeot 			if (!drm_mode_equal(modes[o], modes[n]))
14165718399fSFrançois Tigeot 				continue;
14175718399fSFrançois Tigeot 		}
14185718399fSFrançois Tigeot 
14195718399fSFrançois Tigeot 		crtcs[n] = crtc;
14205718399fSFrançois Tigeot 		memcpy(crtcs, best_crtcs, n * sizeof(struct drm_fb_helper_crtc *));
14215718399fSFrançois Tigeot 		score = my_score + drm_pick_crtcs(fb_helper, crtcs, modes, n + 1,
14225718399fSFrançois Tigeot 						  width, height);
14235718399fSFrançois Tigeot 		if (score > best_score) {
14245718399fSFrançois Tigeot 			best_score = score;
14255718399fSFrançois Tigeot 			memcpy(best_crtcs, crtcs,
14265718399fSFrançois Tigeot 			       dev->mode_config.num_connector *
14275718399fSFrançois Tigeot 			       sizeof(struct drm_fb_helper_crtc *));
14285718399fSFrançois Tigeot 		}
14295718399fSFrançois Tigeot 	}
14305718399fSFrançois Tigeot out:
1431*9edbd4a0SFrançois Tigeot 	kfree(crtcs);
14325718399fSFrançois Tigeot 	return best_score;
14335718399fSFrançois Tigeot }
14345718399fSFrançois Tigeot 
14355718399fSFrançois Tigeot static void drm_setup_crtcs(struct drm_fb_helper *fb_helper)
14365718399fSFrançois Tigeot {
14375718399fSFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
14385718399fSFrançois Tigeot 	struct drm_fb_helper_crtc **crtcs;
14395718399fSFrançois Tigeot 	struct drm_display_mode **modes;
14405718399fSFrançois Tigeot 	struct drm_mode_set *modeset;
14415718399fSFrançois Tigeot 	bool *enabled;
14425718399fSFrançois Tigeot 	int width, height;
1443*9edbd4a0SFrançois Tigeot 	int i;
14445718399fSFrançois Tigeot 
14455718399fSFrançois Tigeot 	DRM_DEBUG_KMS("\n");
14465718399fSFrançois Tigeot 
14475718399fSFrançois Tigeot 	width = dev->mode_config.max_width;
14485718399fSFrançois Tigeot 	height = dev->mode_config.max_height;
14495718399fSFrançois Tigeot 
14505718399fSFrançois Tigeot 	crtcs = kmalloc(dev->mode_config.num_connector *
14515a3b77d5SFrançois Tigeot 	    sizeof(struct drm_fb_helper_crtc *), M_DRM,
14525718399fSFrançois Tigeot 	    M_WAITOK | M_ZERO);
14535718399fSFrançois Tigeot 	modes = kmalloc(dev->mode_config.num_connector *
14545a3b77d5SFrançois Tigeot 	    sizeof(struct drm_display_mode *), M_DRM,
14555718399fSFrançois Tigeot 	    M_WAITOK | M_ZERO);
14565718399fSFrançois Tigeot 	enabled = kmalloc(dev->mode_config.num_connector *
14575a3b77d5SFrançois Tigeot 	    sizeof(bool), M_DRM, M_WAITOK | M_ZERO);
1458*9edbd4a0SFrançois Tigeot 	if (!crtcs || !modes || !enabled) {
1459*9edbd4a0SFrançois Tigeot 		DRM_ERROR("Memory allocation failed\n");
1460*9edbd4a0SFrançois Tigeot 		goto out;
1461*9edbd4a0SFrançois Tigeot 	}
1462*9edbd4a0SFrançois Tigeot 
14635718399fSFrançois Tigeot 
14645718399fSFrançois Tigeot 	drm_enable_connectors(fb_helper, enabled);
14655718399fSFrançois Tigeot 
1466*9edbd4a0SFrançois Tigeot 	if (!(fb_helper->funcs->initial_config &&
1467*9edbd4a0SFrançois Tigeot 	      fb_helper->funcs->initial_config(fb_helper, crtcs, modes,
1468*9edbd4a0SFrançois Tigeot 					       enabled, width, height))) {
1469*9edbd4a0SFrançois Tigeot 		memset(modes, 0, dev->mode_config.num_connector*sizeof(modes[0]));
1470*9edbd4a0SFrançois Tigeot 		memset(crtcs, 0, dev->mode_config.num_connector*sizeof(crtcs[0]));
14715718399fSFrançois Tigeot 
1472*9edbd4a0SFrançois Tigeot 		if (!drm_target_cloned(fb_helper,
1473*9edbd4a0SFrançois Tigeot 				       modes, enabled, width, height) &&
1474*9edbd4a0SFrançois Tigeot 		    !drm_target_preferred(fb_helper,
1475*9edbd4a0SFrançois Tigeot 					  modes, enabled, width, height))
1476*9edbd4a0SFrançois Tigeot 			DRM_ERROR("Unable to find initial modes\n");
1477*9edbd4a0SFrançois Tigeot 
1478*9edbd4a0SFrançois Tigeot 		DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n",
1479*9edbd4a0SFrançois Tigeot 			      width, height);
14805718399fSFrançois Tigeot 
14815718399fSFrançois Tigeot 		drm_pick_crtcs(fb_helper, crtcs, modes, 0, width, height);
1482*9edbd4a0SFrançois Tigeot 	}
14835718399fSFrançois Tigeot 
14845718399fSFrançois Tigeot 	/* need to set the modesets up here for use later */
14855718399fSFrançois Tigeot 	/* fill out the connector<->crtc mappings into the modesets */
14865718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->crtc_count; i++) {
14875718399fSFrançois Tigeot 		modeset = &fb_helper->crtc_info[i].mode_set;
14885718399fSFrançois Tigeot 		modeset->num_connectors = 0;
1489*9edbd4a0SFrançois Tigeot 		modeset->fb = NULL;
14905718399fSFrançois Tigeot 	}
14915718399fSFrançois Tigeot 
14925718399fSFrançois Tigeot 	for (i = 0; i < fb_helper->connector_count; i++) {
14935718399fSFrançois Tigeot 		struct drm_display_mode *mode = modes[i];
14945718399fSFrançois Tigeot 		struct drm_fb_helper_crtc *fb_crtc = crtcs[i];
14955718399fSFrançois Tigeot 		modeset = &fb_crtc->mode_set;
14965718399fSFrançois Tigeot 
14975718399fSFrançois Tigeot 		if (mode && fb_crtc) {
14985718399fSFrançois Tigeot 			DRM_DEBUG_KMS("desired mode %s set on crtc %d\n",
14995718399fSFrançois Tigeot 				      mode->name, fb_crtc->mode_set.crtc->base.id);
15005718399fSFrançois Tigeot 			fb_crtc->desired_mode = mode;
15015718399fSFrançois Tigeot 			if (modeset->mode)
15025718399fSFrançois Tigeot 				drm_mode_destroy(dev, modeset->mode);
15035718399fSFrançois Tigeot 			modeset->mode = drm_mode_duplicate(dev,
15045718399fSFrançois Tigeot 							   fb_crtc->desired_mode);
15055718399fSFrançois Tigeot 			modeset->connectors[modeset->num_connectors++] = fb_helper->connector_info[i]->connector;
1506*9edbd4a0SFrançois Tigeot 			modeset->fb = fb_helper->fb;
15075718399fSFrançois Tigeot 		}
15085718399fSFrançois Tigeot 	}
15095718399fSFrançois Tigeot 
1510*9edbd4a0SFrançois Tigeot 	/* Clear out any old modes if there are no more connected outputs. */
1511*9edbd4a0SFrançois Tigeot 	for (i = 0; i < fb_helper->crtc_count; i++) {
1512*9edbd4a0SFrançois Tigeot 		modeset = &fb_helper->crtc_info[i].mode_set;
1513*9edbd4a0SFrançois Tigeot 		if (modeset->num_connectors == 0) {
1514*9edbd4a0SFrançois Tigeot 			BUG_ON(modeset->fb);
1515*9edbd4a0SFrançois Tigeot 			BUG_ON(modeset->num_connectors);
1516*9edbd4a0SFrançois Tigeot 			if (modeset->mode)
1517*9edbd4a0SFrançois Tigeot 				drm_mode_destroy(dev, modeset->mode);
1518*9edbd4a0SFrançois Tigeot 			modeset->mode = NULL;
1519*9edbd4a0SFrançois Tigeot 		}
1520*9edbd4a0SFrançois Tigeot 	}
1521*9edbd4a0SFrançois Tigeot out:
1522*9edbd4a0SFrançois Tigeot 	kfree(crtcs);
1523*9edbd4a0SFrançois Tigeot 	kfree(modes);
1524*9edbd4a0SFrançois Tigeot 	kfree(enabled);
15255718399fSFrançois Tigeot }
15265718399fSFrançois Tigeot 
15275718399fSFrançois Tigeot /**
1528*9edbd4a0SFrançois Tigeot  * drm_fb_helper_initial_config - setup a sane initial connector configuration
1529*9edbd4a0SFrançois Tigeot  * @fb_helper: fb_helper device struct
1530*9edbd4a0SFrançois Tigeot  * @bpp_sel: bpp value to use for the framebuffer configuration
15315718399fSFrançois Tigeot  *
1532*9edbd4a0SFrançois Tigeot  * Scans the CRTCs and connectors and tries to put together an initial setup.
15335718399fSFrançois Tigeot  * At the moment, this is a cloned configuration across all heads with
15345718399fSFrançois Tigeot  * a new framebuffer object as the backing store.
15355718399fSFrançois Tigeot  *
1536*9edbd4a0SFrançois Tigeot  * Note that this also registers the fbdev and so allows userspace to call into
1537*9edbd4a0SFrançois Tigeot  * the driver through the fbdev interfaces.
1538*9edbd4a0SFrançois Tigeot  *
1539*9edbd4a0SFrançois Tigeot  * This function will call down into the ->fb_probe callback to let
1540*9edbd4a0SFrançois Tigeot  * the driver allocate and initialize the fbdev info structure and the drm
1541*9edbd4a0SFrançois Tigeot  * framebuffer used to back the fbdev. drm_fb_helper_fill_var() and
1542*9edbd4a0SFrançois Tigeot  * drm_fb_helper_fill_fix() are provided as helpers to setup simple default
1543*9edbd4a0SFrançois Tigeot  * values for the fbdev info structure.
1544*9edbd4a0SFrançois Tigeot  *
15455718399fSFrançois Tigeot  * RETURNS:
15465718399fSFrançois Tigeot  * Zero if everything went ok, nonzero otherwise.
15475718399fSFrançois Tigeot  */
15485718399fSFrançois Tigeot bool drm_fb_helper_initial_config(struct drm_fb_helper *fb_helper, int bpp_sel)
15495718399fSFrançois Tigeot {
15505718399fSFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
15515718399fSFrançois Tigeot 	int count = 0;
15525718399fSFrançois Tigeot 
15535718399fSFrançois Tigeot 	drm_fb_helper_parse_command_line(fb_helper);
15545718399fSFrançois Tigeot 
15555718399fSFrançois Tigeot 	count = drm_fb_helper_probe_connector_modes(fb_helper,
15565718399fSFrançois Tigeot 						    dev->mode_config.max_width,
15575718399fSFrançois Tigeot 						    dev->mode_config.max_height);
15585718399fSFrançois Tigeot 	/*
15595718399fSFrançois Tigeot 	 * we shouldn't end up with no modes here.
15605718399fSFrançois Tigeot 	 */
1561*9edbd4a0SFrançois Tigeot 	if (count == 0)
1562*9edbd4a0SFrançois Tigeot 		dev_info(fb_helper->dev->dev, "No connectors reported connected with modes\n");
1563*9edbd4a0SFrançois Tigeot 
15645718399fSFrançois Tigeot 	drm_setup_crtcs(fb_helper);
15655718399fSFrançois Tigeot 
15665718399fSFrançois Tigeot 	return drm_fb_helper_single_fb_probe(fb_helper, bpp_sel);
15675718399fSFrançois Tigeot }
1568*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_initial_config);
15695718399fSFrançois Tigeot 
1570*9edbd4a0SFrançois Tigeot /**
1571*9edbd4a0SFrançois Tigeot  * drm_fb_helper_hotplug_event - respond to a hotplug notification by
1572*9edbd4a0SFrançois Tigeot  *                               probing all the outputs attached to the fb
1573*9edbd4a0SFrançois Tigeot  * @fb_helper: the drm_fb_helper
1574*9edbd4a0SFrançois Tigeot  *
1575*9edbd4a0SFrançois Tigeot  * Scan the connectors attached to the fb_helper and try to put together a
1576*9edbd4a0SFrançois Tigeot  * setup after *notification of a change in output configuration.
1577*9edbd4a0SFrançois Tigeot  *
1578*9edbd4a0SFrançois Tigeot  * Called at runtime, takes the mode config locks to be able to check/change the
1579*9edbd4a0SFrançois Tigeot  * modeset configuration. Must be run from process context (which usually means
1580*9edbd4a0SFrançois Tigeot  * either the output polling work or a work item launched from the driver's
1581*9edbd4a0SFrançois Tigeot  * hotplug interrupt).
1582*9edbd4a0SFrançois Tigeot  *
1583*9edbd4a0SFrançois Tigeot  * Note that the driver must ensure that this is only called _after_ the fb has
1584*9edbd4a0SFrançois Tigeot  * been fully set up, i.e. after the call to drm_fb_helper_initial_config.
1585*9edbd4a0SFrançois Tigeot  *
1586*9edbd4a0SFrançois Tigeot  * RETURNS:
1587*9edbd4a0SFrançois Tigeot  * 0 on success and a non-zero error code otherwise.
1588*9edbd4a0SFrançois Tigeot  */
15895718399fSFrançois Tigeot int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper)
15905718399fSFrançois Tigeot {
15915718399fSFrançois Tigeot 	struct drm_device *dev = fb_helper->dev;
1592*9edbd4a0SFrançois Tigeot 	u32 max_width, max_height;
15935718399fSFrançois Tigeot 
15945718399fSFrançois Tigeot 	if (!fb_helper->fb)
15955718399fSFrançois Tigeot 		return 0;
15965718399fSFrançois Tigeot 
1597*9edbd4a0SFrançois Tigeot 	mutex_lock(&fb_helper->dev->mode_config.mutex);
1598*9edbd4a0SFrançois Tigeot 	if (!drm_fb_helper_is_bound(fb_helper)) {
15995718399fSFrançois Tigeot 		fb_helper->delayed_hotplug = true;
1600*9edbd4a0SFrançois Tigeot 		mutex_unlock(&fb_helper->dev->mode_config.mutex);
16015718399fSFrançois Tigeot 		return 0;
16025718399fSFrançois Tigeot 	}
16035718399fSFrançois Tigeot 	DRM_DEBUG_KMS("\n");
16045718399fSFrançois Tigeot 
16055718399fSFrançois Tigeot 	max_width = fb_helper->fb->width;
16065718399fSFrançois Tigeot 	max_height = fb_helper->fb->height;
16075718399fSFrançois Tigeot 
1608*9edbd4a0SFrançois Tigeot 	drm_fb_helper_probe_connector_modes(fb_helper, max_width, max_height);
1609*9edbd4a0SFrançois Tigeot 	mutex_unlock(&fb_helper->dev->mode_config.mutex);
1610*9edbd4a0SFrançois Tigeot 
1611*9edbd4a0SFrançois Tigeot 	drm_modeset_lock_all(dev);
16125718399fSFrançois Tigeot 	drm_setup_crtcs(fb_helper);
1613*9edbd4a0SFrançois Tigeot 	drm_modeset_unlock_all(dev);
1614*9edbd4a0SFrançois Tigeot #if 0
1615*9edbd4a0SFrançois Tigeot 	drm_fb_helper_set_par(fb_helper->fbdev);
1616*9edbd4a0SFrançois Tigeot #endif
16175718399fSFrançois Tigeot 
1618*9edbd4a0SFrançois Tigeot 	return 0;
1619*9edbd4a0SFrançois Tigeot }
1620*9edbd4a0SFrançois Tigeot EXPORT_SYMBOL(drm_fb_helper_hotplug_event);
1621*9edbd4a0SFrançois Tigeot 
1622*9edbd4a0SFrançois Tigeot /* The Kconfig DRM_KMS_HELPER selects FRAMEBUFFER_CONSOLE (if !EXPERT)
1623*9edbd4a0SFrançois Tigeot  * but the module doesn't depend on any fb console symbols.  At least
1624*9edbd4a0SFrançois Tigeot  * attempt to load fbcon to avoid leaving the system without a usable console.
1625*9edbd4a0SFrançois Tigeot  */
1626*9edbd4a0SFrançois Tigeot #if defined(CONFIG_FRAMEBUFFER_CONSOLE_MODULE) && !defined(CONFIG_EXPERT)
1627*9edbd4a0SFrançois Tigeot static int __init drm_fb_helper_modinit(void)
1628*9edbd4a0SFrançois Tigeot {
1629*9edbd4a0SFrançois Tigeot 	const char *name = "fbcon";
1630*9edbd4a0SFrançois Tigeot 	struct module *fbcon;
1631*9edbd4a0SFrançois Tigeot 
1632*9edbd4a0SFrançois Tigeot 	mutex_lock(&module_mutex);
1633*9edbd4a0SFrançois Tigeot 	fbcon = find_module(name);
1634*9edbd4a0SFrançois Tigeot 	mutex_unlock(&module_mutex);
1635*9edbd4a0SFrançois Tigeot 
1636*9edbd4a0SFrançois Tigeot 	if (!fbcon)
1637*9edbd4a0SFrançois Tigeot 		request_module_nowait(name);
1638*9edbd4a0SFrançois Tigeot 	return 0;
16395718399fSFrançois Tigeot }
16405718399fSFrançois Tigeot 
1641*9edbd4a0SFrançois Tigeot module_init(drm_fb_helper_modinit);
1642*9edbd4a0SFrançois Tigeot #endif
1643