diff options
Diffstat (limited to 'drivers/gpu')
251 files changed, 20992 insertions, 4117 deletions
diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile index cc9277885dd0..30879df3daea 100644 --- a/drivers/gpu/Makefile +++ b/drivers/gpu/Makefile @@ -1 +1 @@ -obj-y += drm/ vga/ stub/ +obj-y += drm/ vga/ diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index ed9e3af17b31..f8dae851130c 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -215,3 +215,7 @@ source "drivers/gpu/drm/cirrus/Kconfig" source "drivers/gpu/drm/shmobile/Kconfig" source "drivers/gpu/drm/tegra/Kconfig" + +source "drivers/gpu/drm/omapdrm/Kconfig" + +source "drivers/gpu/drm/tilcdc/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 6f58c81cfcbc..0d59b24f8d23 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -50,4 +50,6 @@ obj-$(CONFIG_DRM_UDL) += udl/ obj-$(CONFIG_DRM_AST) += ast/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-$(CONFIG_DRM_TEGRA) += tegra/ +obj-$(CONFIG_DRM_OMAP) += omapdrm/ +obj-$(CONFIG_DRM_TILCDC) += tilcdc/ obj-y += i2c/ diff --git a/drivers/gpu/drm/ast/ast_fb.c b/drivers/gpu/drm/ast/ast_fb.c index 3e6584b940dc..34931fe7d2c5 100644 --- a/drivers/gpu/drm/ast/ast_fb.c +++ b/drivers/gpu/drm/ast/ast_fb.c @@ -40,6 +40,7 @@ #include <drm/drmP.h> #include <drm/drm_crtc.h> #include <drm/drm_fb_helper.h> +#include <drm/drm_crtc_helper.h> #include "ast_drv.h" static void ast_dirty_update(struct ast_fbdev *afbdev, @@ -145,9 +146,10 @@ static int astfb_create_object(struct ast_fbdev *afbdev, return ret; } -static int astfb_create(struct ast_fbdev *afbdev, +static int astfb_create(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { + struct ast_fbdev *afbdev = (struct ast_fbdev *)helper; struct drm_device *dev = afbdev->helper.dev; struct drm_mode_fb_cmd2 mode_cmd; struct drm_framebuffer *fb; @@ -248,26 +250,10 @@ static void ast_fb_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green, *blue = ast_crtc->lut_b[regno] << 8; } -static int ast_find_or_create_single(struct drm_fb_helper *helper, - struct drm_fb_helper_surface_size *sizes) -{ - struct ast_fbdev *afbdev = (struct ast_fbdev *)helper; - int new_fb = 0; - int ret; - - if (!helper->fb) { - ret = astfb_create(afbdev, sizes); - if (ret) - return ret; - new_fb = 1; - } - return new_fb; -} - static struct drm_fb_helper_funcs ast_fb_helper_funcs = { .gamma_set = ast_fb_gamma_set, .gamma_get = ast_fb_gamma_get, - .fb_probe = ast_find_or_create_single, + .fb_probe = astfb_create, }; static void ast_fbdev_destroy(struct drm_device *dev, @@ -314,6 +300,10 @@ int ast_fbdev_init(struct drm_device *dev) } drm_fb_helper_single_add_all_connectors(&afbdev->helper); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + drm_fb_helper_initial_config(&afbdev->helper, 32); return 0; } diff --git a/drivers/gpu/drm/cirrus/cirrus_fbdev.c b/drivers/gpu/drm/cirrus/cirrus_fbdev.c index 3daea0f638c3..e25afccaf85b 100644 --- a/drivers/gpu/drm/cirrus/cirrus_fbdev.c +++ b/drivers/gpu/drm/cirrus/cirrus_fbdev.c @@ -11,6 +11,7 @@ #include <linux/module.h> #include <drm/drmP.h> #include <drm/drm_fb_helper.h> +#include <drm/drm_crtc_helper.h> #include <linux/fb.h> @@ -120,9 +121,10 @@ static int cirrusfb_create_object(struct cirrus_fbdev *afbdev, return ret; } -static int cirrusfb_create(struct cirrus_fbdev *gfbdev, +static int cirrusfb_create(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { + struct cirrus_fbdev *gfbdev = (struct cirrus_fbdev *)helper; struct drm_device *dev = gfbdev->helper.dev; struct cirrus_device *cdev = gfbdev->helper.dev->dev_private; struct fb_info *info; @@ -219,23 +221,6 @@ out_iounmap: return ret; } -static int cirrus_fb_find_or_create_single(struct drm_fb_helper *helper, - struct drm_fb_helper_surface_size - *sizes) -{ - struct cirrus_fbdev *gfbdev = (struct cirrus_fbdev *)helper; - int new_fb = 0; - int ret; - - if (!helper->fb) { - ret = cirrusfb_create(gfbdev, sizes); - if (ret) - return ret; - new_fb = 1; - } - return new_fb; -} - static int cirrus_fbdev_destroy(struct drm_device *dev, struct cirrus_fbdev *gfbdev) { @@ -267,7 +252,7 @@ static int cirrus_fbdev_destroy(struct drm_device *dev, static struct drm_fb_helper_funcs cirrus_fb_helper_funcs = { .gamma_set = cirrus_crtc_fb_gamma_set, .gamma_get = cirrus_crtc_fb_gamma_get, - .fb_probe = cirrus_fb_find_or_create_single, + .fb_probe = cirrusfb_create, }; int cirrus_fbdev_init(struct cirrus_device *cdev) @@ -291,6 +276,9 @@ int cirrus_fbdev_init(struct cirrus_device *cdev) return ret; } drm_fb_helper_single_add_all_connectors(&gfbdev->helper); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(cdev->dev); drm_fb_helper_initial_config(&gfbdev->helper, bpp_sel); return 0; diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 826a5ca3595f..3bdf2a650d9c 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -68,9 +68,23 @@ void drm_modeset_unlock_all(struct drm_device *dev) mutex_unlock(&dev->mode_config.mutex); } - EXPORT_SYMBOL(drm_modeset_unlock_all); +/** + * drm_warn_on_modeset_not_all_locked - check that all modeset locks are locked + * @dev: device + */ +void drm_warn_on_modeset_not_all_locked(struct drm_device *dev) +{ + struct drm_crtc *crtc; + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) + WARN_ON(!mutex_is_locked(&crtc->mutex)); + + WARN_ON(!mutex_is_locked(&dev->mode_config.mutex)); +} +EXPORT_SYMBOL(drm_warn_on_modeset_not_all_locked); + /* Avoid boilerplate. I'm tired of typing. */ #define DRM_ENUM_NAME_FN(fnname, list) \ char *fnname(int val) \ @@ -1982,9 +1996,9 @@ int drm_mode_setplane(struct drm_device *dev, void *data, plane_req->src_w, plane_req->src_h); if (!ret) { old_fb = plane->fb; - fb = NULL; plane->crtc = crtc; plane->fb = fb; + fb = NULL; } drm_modeset_unlock_all(dev); @@ -3778,6 +3792,13 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev, /* Keep the old fb, don't unref it. */ old_fb = NULL; } else { + /* + * Warn if the driver hasn't properly updated the crtc->fb + * field to reflect that the new framebuffer is now used. + * Failing to do so will screw with the reference counting + * on framebuffers. + */ + WARN_ON(crtc->fb != fb); /* Unref only the old framebuffer. */ fb = NULL; } diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 51324256a657..67aa0dd68250 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -354,10 +354,14 @@ drm_do_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter) break; } } - if (i == 4) + + if (i == 4 && print_bad_edid) { dev_warn(connector->dev->dev, "%s: Ignoring invalid EDID block %d.\n", drm_get_connector_name(connector), j); + + connector->bad_edid_counter++; + } } if (valid_extensions != block[0x7e]) { @@ -2050,7 +2054,8 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid) num_modes += add_cvt_modes(connector, edid); num_modes += add_standard_modes(connector, edid); num_modes += add_established_modes(connector, edid); - num_modes += add_inferred_modes(connector, edid); + if (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF) + num_modes += add_inferred_modes(connector, edid); num_modes += add_cea_modes(connector, edid); if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75)) diff --git a/drivers/gpu/drm/drm_encoder_slave.c b/drivers/gpu/drm/drm_encoder_slave.c index 63e733408b6d..48c52f7df4e6 100644 --- a/drivers/gpu/drm/drm_encoder_slave.c +++ b/drivers/gpu/drm/drm_encoder_slave.c @@ -123,3 +123,66 @@ void drm_i2c_encoder_destroy(struct drm_encoder *drm_encoder) module_put(module); } EXPORT_SYMBOL(drm_i2c_encoder_destroy); + +/* + * Wrapper fxns which can be plugged in to drm_encoder_helper_funcs: + */ + +static inline struct drm_encoder_slave_funcs * +get_slave_funcs(struct drm_encoder *enc) +{ + return to_encoder_slave(enc)->slave_funcs; +} + +void drm_i2c_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + get_slave_funcs(encoder)->dpms(encoder, mode); +} +EXPORT_SYMBOL(drm_i2c_encoder_dpms); + +bool drm_i2c_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return get_slave_funcs(encoder)->mode_fixup(encoder, mode, adjusted_mode); +} +EXPORT_SYMBOL(drm_i2c_encoder_mode_fixup); + +void drm_i2c_encoder_prepare(struct drm_encoder *encoder) +{ + drm_i2c_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} +EXPORT_SYMBOL(drm_i2c_encoder_prepare); + +void drm_i2c_encoder_commit(struct drm_encoder *encoder) +{ + drm_i2c_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} +EXPORT_SYMBOL(drm_i2c_encoder_commit); + +void drm_i2c_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + get_slave_funcs(encoder)->mode_set(encoder, mode, adjusted_mode); +} +EXPORT_SYMBOL(drm_i2c_encoder_mode_set); + +enum drm_connector_status drm_i2c_encoder_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + return get_slave_funcs(encoder)->detect(encoder, connector); +} +EXPORT_SYMBOL(drm_i2c_encoder_detect); + +void drm_i2c_encoder_save(struct drm_encoder *encoder) +{ + get_slave_funcs(encoder)->save(encoder); +} +EXPORT_SYMBOL(drm_i2c_encoder_save); + +void drm_i2c_encoder_restore(struct drm_encoder *encoder) +{ + get_slave_funcs(encoder)->restore(encoder); +} +EXPORT_SYMBOL(drm_i2c_encoder_restore); diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c index 3742bc96421e..0b5af7d0edb1 100644 --- a/drivers/gpu/drm/drm_fb_cma_helper.c +++ b/drivers/gpu/drm/drm_fb_cma_helper.c @@ -180,6 +180,59 @@ struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb, } EXPORT_SYMBOL_GPL(drm_fb_cma_get_gem_obj); +#ifdef CONFIG_DEBUG_FS +/** + * drm_fb_cma_describe() - Helper to dump information about a single + * CMA framebuffer object + */ +void drm_fb_cma_describe(struct drm_framebuffer *fb, struct seq_file *m) +{ + struct drm_fb_cma *fb_cma = to_fb_cma(fb); + int i, n = drm_format_num_planes(fb->pixel_format); + + seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height, + (char *)&fb->pixel_format); + + for (i = 0; i < n; i++) { + seq_printf(m, " %d: offset=%d pitch=%d, obj: ", + i, fb->offsets[i], fb->pitches[i]); + drm_gem_cma_describe(fb_cma->obj[i], m); + } +} +EXPORT_SYMBOL_GPL(drm_fb_cma_describe); + +/** + * drm_fb_cma_debugfs_show() - Helper to list CMA framebuffer objects + * in debugfs. + */ +int drm_fb_cma_debugfs_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct drm_framebuffer *fb; + int ret; + + ret = mutex_lock_interruptible(&dev->mode_config.mutex); + if (ret) + return ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) { + mutex_unlock(&dev->mode_config.mutex); + return ret; + } + + list_for_each_entry(fb, &dev->mode_config.fb_list, head) + drm_fb_cma_describe(fb, m); + + mutex_unlock(&dev->struct_mutex); + mutex_unlock(&dev->mode_config.mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(drm_fb_cma_debugfs_show); +#endif + static struct fb_ops drm_fbdev_cma_ops = { .owner = THIS_MODULE, .fb_fillrect = sys_fillrect, @@ -275,23 +328,8 @@ err_drm_gem_cma_free_object: return ret; } -static int drm_fbdev_cma_probe(struct drm_fb_helper *helper, - struct drm_fb_helper_surface_size *sizes) -{ - int ret = 0; - - if (!helper->fb) { - ret = drm_fbdev_cma_create(helper, sizes); - if (ret < 0) - return ret; - ret = 1; - } - - return ret; -} - static struct drm_fb_helper_funcs drm_fb_cma_helper_funcs = { - .fb_probe = drm_fbdev_cma_probe, + .fb_probe = drm_fbdev_cma_create, }; /** @@ -333,6 +371,9 @@ struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev, } + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + ret = drm_fb_helper_initial_config(helper, preferred_bpp); if (ret < 0) { dev_err(dev->dev, "Failed to set inital hw configuration.\n"); @@ -389,8 +430,13 @@ EXPORT_SYMBOL_GPL(drm_fbdev_cma_fini); */ void drm_fbdev_cma_restore_mode(struct drm_fbdev_cma *fbdev_cma) { - if (fbdev_cma) + if (fbdev_cma) { + struct drm_device *dev = fbdev_cma->fb_helper.dev; + + drm_modeset_lock_all(dev); drm_fb_helper_restore_fbdev_mode(&fbdev_cma->fb_helper); + drm_modeset_unlock_all(dev); + } } EXPORT_SYMBOL_GPL(drm_fbdev_cma_restore_mode); diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 0c6e25e979dd..59d6b9bf204b 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -52,9 +52,36 @@ static LIST_HEAD(kernel_fb_helper_list); * mode setting driver. They can be used mostly independantely from the crtc * helper functions used by many drivers to implement the kernel mode setting * interfaces. + * + * Initialization is done as a three-step process with drm_fb_helper_init(), + * drm_fb_helper_single_add_all_connectors() and drm_fb_helper_initial_config(). + * Drivers with fancier requirements than the default beheviour can override the + * second step with their own code. Teardown is done with drm_fb_helper_fini(). + * + * At runtime drivers should restore the fbdev console by calling + * drm_fb_helper_restore_fbdev_mode() from their ->lastclose callback. They + * should also notify the fb helper code from updates to the output + * configuration by calling drm_fb_helper_hotplug_event(). For easier + * integration with the output polling code in drm_crtc_helper.c the modeset + * code proves a ->output_poll_changed callback. + * + * All other functions exported by the fb helper library can be used to + * implement the fbdev driver interface by the driver. */ -/* simple single crtc case helper function */ +/** + * drm_fb_helper_single_add_all_connectors() - add all connectors to fbdev + * emulation helper + * @fb_helper: fbdev initialized with drm_fb_helper_init + * + * This functions adds all the available connectors for use with the given + * fb_helper. This is a separate step to allow drivers to freely assign + * connectors to the fbdev, e.g. if some are reserved for special purposes or + * not adequate to be used for the fbcon. + * + * Since this is part of the initial setup before the fbdev is published, no + * locking is required. + */ int drm_fb_helper_single_add_all_connectors(struct drm_fb_helper *fb_helper) { struct drm_device *dev = fb_helper->dev; @@ -163,6 +190,10 @@ static void drm_fb_helper_restore_lut_atomic(struct drm_crtc *crtc) crtc->funcs->gamma_set(crtc, r_base, g_base, b_base, 0, crtc->gamma_size); } +/** + * drm_fb_helper_debug_enter - implementation for ->fb_debug_enter + * @info: fbdev registered by the helper + */ int drm_fb_helper_debug_enter(struct fb_info *info) { struct drm_fb_helper *helper = info->par; @@ -208,6 +239,10 @@ static struct drm_framebuffer *drm_mode_config_fb(struct drm_crtc *crtc) return NULL; } +/** + * drm_fb_helper_debug_leave - implementation for ->fb_debug_leave + * @info: fbdev registered by the helper + */ int drm_fb_helper_debug_leave(struct fb_info *info) { struct drm_fb_helper *helper = info->par; @@ -239,10 +274,21 @@ int drm_fb_helper_debug_leave(struct fb_info *info) } EXPORT_SYMBOL(drm_fb_helper_debug_leave); +/** + * drm_fb_helper_restore_fbdev_mode - restore fbdev configuration + * @fb_helper: fbcon to restore + * + * This should be called from driver's drm ->lastclose callback + * when implementing an fbcon on top of kms using this helper. This ensures that + * the user isn't greeted with a black screen when e.g. X dies. + */ bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper) { bool error = false; int i, ret; + + drm_warn_on_modeset_not_all_locked(fb_helper->dev); + for (i = 0; i < fb_helper->crtc_count; i++) { struct drm_mode_set *mode_set = &fb_helper->crtc_info[i].mode_set; ret = drm_mode_set_config_internal(mode_set); @@ -253,6 +299,10 @@ bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper) } EXPORT_SYMBOL(drm_fb_helper_restore_fbdev_mode); +/* + * restore fbcon display for all kms driver's using this helper, used for sysrq + * and panic handling. + */ static bool drm_fb_helper_force_kernel_mode(void) { bool ret, error = false; @@ -272,7 +322,7 @@ static bool drm_fb_helper_force_kernel_mode(void) return error; } -int drm_fb_helper_panic(struct notifier_block *n, unsigned long ununsed, +static int drm_fb_helper_panic(struct notifier_block *n, unsigned long ununsed, void *panic_str) { /* @@ -285,26 +335,11 @@ int drm_fb_helper_panic(struct notifier_block *n, unsigned long ununsed, pr_err("panic occurred, switching back to text console\n"); return drm_fb_helper_force_kernel_mode(); } -EXPORT_SYMBOL(drm_fb_helper_panic); static struct notifier_block paniced = { .notifier_call = drm_fb_helper_panic, }; -/** - * drm_fb_helper_restore - restore the framebuffer console (kernel) config - * - * Restore's the kernel's fbcon mode, used for lastclose & panic paths. - */ -void drm_fb_helper_restore(void) -{ - bool ret; - ret = drm_fb_helper_force_kernel_mode(); - if (ret == true) - DRM_ERROR("Failed to restore crtc configuration\n"); -} -EXPORT_SYMBOL(drm_fb_helper_restore); - static bool drm_fb_helper_is_bound(struct drm_fb_helper *fb_helper) { struct drm_device *dev = fb_helper->dev; @@ -326,7 +361,10 @@ static bool drm_fb_helper_is_bound(struct drm_fb_helper *fb_helper) #ifdef CONFIG_MAGIC_SYSRQ static void drm_fb_helper_restore_work_fn(struct work_struct *ignored) { - drm_fb_helper_restore(); + bool ret; + ret = drm_fb_helper_force_kernel_mode(); + if (ret == true) + DRM_ERROR("Failed to restore crtc configuration\n"); } static DECLARE_WORK(drm_fb_helper_restore_work, drm_fb_helper_restore_work_fn); @@ -353,6 +391,14 @@ static void drm_fb_helper_dpms(struct fb_info *info, int dpms_mode) int i, j; /* + * fbdev->blank can be called from irq context in case of a panic. + * Since we already have our own special panic handler which will + * restore the fbdev console mode completely, just bail out early. + */ + if (oops_in_progress) + return; + + /* * For each CRTC in this fb, turn the connectors on/off. */ drm_modeset_lock_all(dev); @@ -378,6 +424,11 @@ static void drm_fb_helper_dpms(struct fb_info *info, int dpms_mode) drm_modeset_unlock_all(dev); } +/** + * drm_fb_helper_blank - implementation for ->fb_blank + * @blank: desired blanking state + * @info: fbdev registered by the helper + */ int drm_fb_helper_blank(int blank, struct fb_info *info) { switch (blank) { @@ -421,6 +472,24 @@ static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper) kfree(helper->crtc_info); } +/** + * drm_fb_helper_init - initialize a drm_fb_helper structure + * @dev: drm device + * @fb_helper: driver-allocated fbdev helper structure to initialize + * @crtc_count: maximum number of crtcs to support in this fbdev emulation + * @max_conn_count: max connector count + * + * This allocates the structures for the fbdev helper with the given limits. + * Note that this won't yet touch the hardware (through the driver interfaces) + * nor register the fbdev. This is only done in drm_fb_helper_initial_config() + * to allow driver writes more control over the exact init sequence. + * + * Drivers must set fb_helper->funcs before calling + * drm_fb_helper_initial_config(). + * + * RETURNS: + * Zero if everything went ok, nonzero otherwise. + */ int drm_fb_helper_init(struct drm_device *dev, struct drm_fb_helper *fb_helper, int crtc_count, int max_conn_count) @@ -549,6 +618,11 @@ static int setcolreg(struct drm_crtc *crtc, u16 red, u16 green, return 0; } +/** + * drm_fb_helper_setcmap - implementation for ->fb_setcmap + * @cmap: cmap to set + * @info: fbdev registered by the helper + */ int drm_fb_helper_setcmap(struct fb_cmap *cmap, struct fb_info *info) { struct drm_fb_helper *fb_helper = info->par; @@ -588,6 +662,11 @@ int drm_fb_helper_setcmap(struct fb_cmap *cmap, struct fb_info *info) } EXPORT_SYMBOL(drm_fb_helper_setcmap); +/** + * drm_fb_helper_check_var - implementation for ->fb_check_var + * @var: screeninfo to check + * @info: fbdev registered by the helper + */ int drm_fb_helper_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { @@ -680,13 +759,19 @@ int drm_fb_helper_check_var(struct fb_var_screeninfo *var, } EXPORT_SYMBOL(drm_fb_helper_check_var); -/* this will let fbcon do the mode init */ +/** + * drm_fb_helper_set_par - implementation for ->fb_set_par + * @info: fbdev registered by the helper + * + * This will let fbcon do the mode init and is called at initialization time by + * the fbdev core when registering the driver, and later on through the hotplug + * callback. + */ int drm_fb_helper_set_par(struct fb_info *info) { struct drm_fb_helper *fb_helper = info->par; struct drm_device *dev = fb_helper->dev; struct fb_var_screeninfo *var = &info->var; - struct drm_crtc *crtc; int ret; int i; @@ -697,7 +782,6 @@ int drm_fb_helper_set_par(struct fb_info *info) drm_modeset_lock_all(dev); for (i = 0; i < fb_helper->crtc_count; i++) { - crtc = fb_helper->crtc_info[i].mode_set.crtc; ret = drm_mode_set_config_internal(&fb_helper->crtc_info[i].mode_set); if (ret) { drm_modeset_unlock_all(dev); @@ -714,6 +798,11 @@ int drm_fb_helper_set_par(struct fb_info *info) } EXPORT_SYMBOL(drm_fb_helper_set_par); +/** + * drm_fb_helper_pan_display - implementation for ->fb_pan_display + * @var: updated screen information + * @info: fbdev registered by the helper + */ int drm_fb_helper_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { @@ -751,10 +840,15 @@ int drm_fb_helper_pan_display(struct fb_var_screeninfo *var, } EXPORT_SYMBOL(drm_fb_helper_pan_display); -int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, - int preferred_bpp) +/* + * Allocates the backing storage and sets up the fbdev info structure through + * the ->fb_probe callback and then registers the fbdev and sets up the panic + * notifier. + */ +static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, + int preferred_bpp) { - int new_fb = 0; + int ret = 0; int crtc_count = 0; int i; struct fb_info *info; @@ -832,27 +926,30 @@ int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, } /* push down into drivers */ - new_fb = (*fb_helper->funcs->fb_probe)(fb_helper, &sizes); - if (new_fb < 0) - return new_fb; + ret = (*fb_helper->funcs->fb_probe)(fb_helper, &sizes); + if (ret < 0) + return ret; info = fb_helper->fbdev; - /* set the fb pointer */ + /* + * Set the fb pointer - usually drm_setup_crtcs does this for hotplug + * events, but at init time drm_setup_crtcs needs to be called before + * the fb is allocated (since we need to figure out the desired size of + * the fb before we can allocate it ...). Hence we need to fix things up + * here again. + */ for (i = 0; i < fb_helper->crtc_count; i++) - fb_helper->crtc_info[i].mode_set.fb = fb_helper->fb; + if (fb_helper->crtc_info[i].mode_set.num_connectors) + fb_helper->crtc_info[i].mode_set.fb = fb_helper->fb; - if (new_fb) { - info->var.pixclock = 0; - if (register_framebuffer(info) < 0) - return -EINVAL; - dev_info(fb_helper->dev->dev, "fb%d: %s frame buffer device\n", - info->node, info->fix.id); + info->var.pixclock = 0; + if (register_framebuffer(info) < 0) + return -EINVAL; - } else { - drm_fb_helper_set_par(info); - } + dev_info(fb_helper->dev->dev, "fb%d: %s frame buffer device\n", + info->node, info->fix.id); /* Switch back to kernel console on panic */ /* multi card linked list maybe */ @@ -862,13 +959,25 @@ int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, &paniced); register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op); } - if (new_fb) - list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list); + + list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list); return 0; } -EXPORT_SYMBOL(drm_fb_helper_single_fb_probe); +/** + * drm_fb_helper_fill_fix - initializes fixed fbdev information + * @info: fbdev registered by the helper + * @pitch: desired pitch + * @depth: desired depth + * + * Helper to fill in the fixed fbdev information useful for a non-accelerated + * fbdev emulations. Drivers which support acceleration methods which impose + * additional constraints need to set up their own limits. + * + * Drivers should call this (or their equivalent setup code) from their + * ->fb_probe callback. + */ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch, uint32_t depth) { @@ -889,6 +998,20 @@ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch, } EXPORT_SYMBOL(drm_fb_helper_fill_fix); +/** + * drm_fb_helper_fill_var - initalizes variable fbdev information + * @info: fbdev instance to set up + * @fb_helper: fb helper instance to use as template + * @fb_width: desired fb width + * @fb_height: desired fb height + * + * Sets up the variable fbdev metainformation from the given fb helper instance + * and the drm framebuffer allocated in fb_helper->fb. + * + * Drivers should call this (or their equivalent setup code) from their + * ->fb_probe callback after having allocated the fbdev backing + * storage framebuffer. + */ void drm_fb_helper_fill_var(struct fb_info *info, struct drm_fb_helper *fb_helper, uint32_t fb_width, uint32_t fb_height) { @@ -1312,6 +1435,7 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper) for (i = 0; i < fb_helper->crtc_count; i++) { modeset = &fb_helper->crtc_info[i].mode_set; modeset->num_connectors = 0; + modeset->fb = NULL; } for (i = 0; i < fb_helper->connector_count; i++) { @@ -1328,9 +1452,21 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper) modeset->mode = drm_mode_duplicate(dev, fb_crtc->desired_mode); modeset->connectors[modeset->num_connectors++] = fb_helper->connector_info[i]->connector; + modeset->fb = fb_helper->fb; } } + /* Clear out any old modes if there are no more connected outputs. */ + for (i = 0; i < fb_helper->crtc_count; i++) { + modeset = &fb_helper->crtc_info[i].mode_set; + if (modeset->num_connectors == 0) { + BUG_ON(modeset->fb); + BUG_ON(modeset->num_connectors); + if (modeset->mode) + drm_mode_destroy(dev, modeset->mode); + modeset->mode = NULL; + } + } out: kfree(crtcs); kfree(modes); @@ -1338,18 +1474,23 @@ out: } /** - * drm_helper_initial_config - setup a sane initial connector configuration + * drm_fb_helper_initial_config - setup a sane initial connector configuration * @fb_helper: fb_helper device struct * @bpp_sel: bpp value to use for the framebuffer configuration * - * LOCKING: - * Called at init time by the driver to set up the @fb_helper initial - * configuration, must take the mode config lock. - * * Scans the CRTCs and connectors and tries to put together an initial setup. * At the moment, this is a cloned configuration across all heads with * a new framebuffer object as the backing store. * + * Note that this also registers the fbdev and so allows userspace to call into + * the driver through the fbdev interfaces. + * + * This function will call down into the ->fb_probe callback to let + * the driver allocate and initialize the fbdev info structure and the drm + * framebuffer used to back the fbdev. drm_fb_helper_fill_var() and + * drm_fb_helper_fill_fix() are provided as helpers to setup simple default + * values for the fbdev info structure. + * * RETURNS: * Zero if everything went ok, nonzero otherwise. */ @@ -1358,9 +1499,6 @@ bool drm_fb_helper_initial_config(struct drm_fb_helper *fb_helper, int bpp_sel) struct drm_device *dev = fb_helper->dev; int count = 0; - /* disable all the possible outputs/crtcs before entering KMS mode */ - drm_helper_disable_unused_functions(fb_helper->dev); - drm_fb_helper_parse_command_line(fb_helper); count = drm_fb_helper_probe_connector_modes(fb_helper, @@ -1383,12 +1521,17 @@ EXPORT_SYMBOL(drm_fb_helper_initial_config); * probing all the outputs attached to the fb * @fb_helper: the drm_fb_helper * - * LOCKING: - * Called at runtime, must take mode config lock. - * * Scan the connectors attached to the fb_helper and try to put together a * setup after *notification of a change in output configuration. * + * Called at runtime, takes the mode config locks to be able to check/change the + * modeset configuration. Must be run from process context (which usually means + * either the output polling work or a work item launched from the driver's + * hotplug interrupt). + * + * Note that the driver must ensure that this is only called _after_ the fb has + * been fully set up, i.e. after the call to drm_fb_helper_initial_config. + * * RETURNS: * 0 on success and a non-zero error code otherwise. */ @@ -1418,7 +1561,9 @@ int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper) drm_setup_crtcs(fb_helper); drm_modeset_unlock_all(dev); - return drm_fb_helper_single_fb_probe(fb_helper, bpp_sel); + drm_fb_helper_set_par(fb_helper->fbdev); + + return 0; } EXPORT_SYMBOL(drm_fb_helper_hotplug_event); diff --git a/drivers/gpu/drm/drm_gem_cma_helper.c b/drivers/gpu/drm/drm_gem_cma_helper.c index 1aa8fee1e865..0a7e011509bd 100644 --- a/drivers/gpu/drm/drm_gem_cma_helper.c +++ b/drivers/gpu/drm/drm_gem_cma_helper.c @@ -249,3 +249,24 @@ int drm_gem_cma_dumb_destroy(struct drm_file *file_priv, return drm_gem_handle_delete(file_priv, handle); } EXPORT_SYMBOL_GPL(drm_gem_cma_dumb_destroy); + +#ifdef CONFIG_DEBUG_FS +void drm_gem_cma_describe(struct drm_gem_cma_object *cma_obj, struct seq_file *m) +{ + struct drm_gem_object *obj = &cma_obj->base; + struct drm_device *dev = obj->dev; + uint64_t off = 0; + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + if (obj->map_list.map) + off = (uint64_t)obj->map_list.hash.key; + + seq_printf(m, "%2d (%2d) %08llx %08Zx %p %d", + obj->name, obj->refcount.refcount.counter, + off, cma_obj->paddr, cma_obj->vaddr, obj->size); + + seq_printf(m, "\n"); +} +EXPORT_SYMBOL_GPL(drm_gem_cma_describe); +#endif diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 38e79927b2d7..a6a8643a6a77 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -867,6 +867,7 @@ void drm_send_vblank_event(struct drm_device *dev, int crtc, now = get_drm_timestamp(); } + e->pipe = crtc; send_vblank_event(dev, e, seq, &now); } EXPORT_SYMBOL(drm_send_vblank_event); diff --git a/drivers/gpu/drm/drm_pci.c b/drivers/gpu/drm/drm_pci.c index 86102a08f65c..bd719e936e13 100644 --- a/drivers/gpu/drm/drm_pci.c +++ b/drivers/gpu/drm/drm_pci.c @@ -439,33 +439,6 @@ int drm_pci_init(struct drm_driver *driver, struct pci_driver *pdriver) return 0; } -#else - -int drm_pci_init(struct drm_driver *driver, struct pci_driver *pdriver) -{ - return -1; -} - -#endif - -EXPORT_SYMBOL(drm_pci_init); - -/*@}*/ -void drm_pci_exit(struct drm_driver *driver, struct pci_driver *pdriver) -{ - struct drm_device *dev, *tmp; - DRM_DEBUG("\n"); - - if (driver->driver_features & DRIVER_MODESET) { - pci_unregister_driver(pdriver); - } else { - list_for_each_entry_safe(dev, tmp, &driver->device_list, driver_item) - drm_put_dev(dev); - } - DRM_INFO("Module unloaded\n"); -} -EXPORT_SYMBOL(drm_pci_exit); - int drm_pcie_get_speed_cap_mask(struct drm_device *dev, u32 *mask) { struct pci_dev *root; @@ -503,3 +476,30 @@ int drm_pcie_get_speed_cap_mask(struct drm_device *dev, u32 *mask) return 0; } EXPORT_SYMBOL(drm_pcie_get_speed_cap_mask); + +#else + +int drm_pci_init(struct drm_driver *driver, struct pci_driver *pdriver) +{ + return -1; +} + +#endif + +EXPORT_SYMBOL(drm_pci_init); + +/*@}*/ +void drm_pci_exit(struct drm_driver *driver, struct pci_driver *pdriver) +{ + struct drm_device *dev, *tmp; + DRM_DEBUG("\n"); + + if (driver->driver_features & DRIVER_MODESET) { + pci_unregister_driver(pdriver); + } else { + list_for_each_entry_safe(dev, tmp, &driver->device_list, driver_item) + drm_put_dev(dev); + } + DRM_INFO("Module unloaded\n"); +} +EXPORT_SYMBOL(drm_pci_exit); diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.c b/drivers/gpu/drm/exynos/exynos_drm_fb.c index 294c0513f587..0e04f4ea441f 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fb.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fb.c @@ -99,6 +99,10 @@ static int exynos_drm_fb_create_handle(struct drm_framebuffer *fb, DRM_DEBUG_KMS("%s\n", __FILE__); + /* This fb should have only one gem object. */ + if (WARN_ON(exynos_fb->buf_cnt != 1)) + return -EINVAL; + return drm_gem_handle_create(file_priv, &exynos_fb->exynos_gem_obj[0]->base, handle); } @@ -217,23 +221,25 @@ exynos_user_fb_create(struct drm_device *dev, struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd) { struct drm_gem_object *obj; + struct exynos_drm_gem_obj *exynos_gem_obj; struct exynos_drm_fb *exynos_fb; int i, ret; DRM_DEBUG_KMS("%s\n", __FILE__); - obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]); - if (!obj) { - DRM_ERROR("failed to lookup gem object\n"); - return ERR_PTR(-ENOENT); - } - exynos_fb = kzalloc(sizeof(*exynos_fb), GFP_KERNEL); if (!exynos_fb) { DRM_ERROR("failed to allocate exynos drm framebuffer\n"); return ERR_PTR(-ENOMEM); } + obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]); + if (!obj) { + DRM_ERROR("failed to lookup gem object\n"); + ret = -ENOENT; + goto err_free; + } + drm_helper_mode_fill_fb_struct(&exynos_fb->fb, mode_cmd); exynos_fb->exynos_gem_obj[0] = to_exynos_gem_obj(obj); exynos_fb->buf_cnt = exynos_drm_format_num_buffers(mode_cmd); @@ -241,43 +247,44 @@ exynos_user_fb_create(struct drm_device *dev, struct drm_file *file_priv, DRM_DEBUG_KMS("buf_cnt = %d\n", exynos_fb->buf_cnt); for (i = 1; i < exynos_fb->buf_cnt; i++) { - struct exynos_drm_gem_obj *exynos_gem_obj; - int ret; - obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[i]); if (!obj) { DRM_ERROR("failed to lookup gem object\n"); - kfree(exynos_fb); - return ERR_PTR(-ENOENT); + ret = -ENOENT; + exynos_fb->buf_cnt = i; + goto err_unreference; } exynos_gem_obj = to_exynos_gem_obj(obj); + exynos_fb->exynos_gem_obj[i] = exynos_gem_obj; ret = check_fb_gem_memory_type(dev, exynos_gem_obj); if (ret < 0) { DRM_ERROR("cannot use this gem memory type for fb.\n"); - kfree(exynos_fb); - return ERR_PTR(ret); + goto err_unreference; } - - exynos_fb->exynos_gem_obj[i] = to_exynos_gem_obj(obj); } ret = drm_framebuffer_init(dev, &exynos_fb->fb, &exynos_drm_fb_funcs); if (ret) { - for (i = 0; i < exynos_fb->buf_cnt; i++) { - struct exynos_drm_gem_obj *gem_obj; - - gem_obj = exynos_fb->exynos_gem_obj[i]; - drm_gem_object_unreference_unlocked(&gem_obj->base); - } - - kfree(exynos_fb); - return ERR_PTR(ret); + DRM_ERROR("failed to init framebuffer.\n"); + goto err_unreference; } return &exynos_fb->fb; + +err_unreference: + for (i = 0; i < exynos_fb->buf_cnt; i++) { + struct drm_gem_object *obj; + + obj = &exynos_fb->exynos_gem_obj[i]->base; + if (obj) + drm_gem_object_unreference_unlocked(obj); + } +err_free: + kfree(exynos_fb); + return ERR_PTR(ret); } struct exynos_drm_gem_buf *exynos_drm_fb_buffer(struct drm_framebuffer *fb, diff --git a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c index 90d335cfb8c0..68f0045f86b8 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c @@ -226,36 +226,8 @@ out: return ret; } -static int exynos_drm_fbdev_probe(struct drm_fb_helper *helper, - struct drm_fb_helper_surface_size *sizes) -{ - int ret = 0; - - DRM_DEBUG_KMS("%s\n", __FILE__); - - /* - * with !helper->fb, it means that this funcion is called first time - * and after that, the helper->fb would be used as clone mode. - */ - if (!helper->fb) { - ret = exynos_drm_fbdev_create(helper, sizes); - if (ret < 0) { - DRM_ERROR("failed to create fbdev.\n"); - return ret; - } - - /* - * fb_helper expects a value more than 1 if succeed - * because register_framebuffer() should be called. - */ - ret = 1; - } - - return ret; -} - static struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = { - .fb_probe = exynos_drm_fbdev_probe, + .fb_probe = exynos_drm_fbdev_create, }; int exynos_drm_fbdev_init(struct drm_device *dev) @@ -295,6 +267,9 @@ int exynos_drm_fbdev_init(struct drm_device *dev) } + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP); if (ret < 0) { DRM_ERROR("failed to set up hw configuration.\n"); @@ -376,5 +351,7 @@ void exynos_drm_fbdev_restore_mode(struct drm_device *dev) if (!private || !private->fb_helper) return; + drm_modeset_lock_all(dev); drm_fb_helper_restore_fbdev_mode(private->fb_helper); + drm_modeset_unlock_all(dev); } diff --git a/drivers/gpu/drm/exynos/exynos_drm_g2d.c b/drivers/gpu/drm/exynos/exynos_drm_g2d.c index 9a4c08e7453c..0fcfbe4660bb 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_g2d.c +++ b/drivers/gpu/drm/exynos/exynos_drm_g2d.c @@ -19,6 +19,7 @@ #include <linux/workqueue.h> #include <linux/dma-mapping.h> #include <linux/dma-attrs.h> +#include <linux/of.h> #include <drm/drmP.h> #include <drm/exynos_drm.h> @@ -429,7 +430,7 @@ static dma_addr_t *g2d_userptr_get_dma_addr(struct drm_device *drm_dev, g2d_userptr->pages = pages; - sgt = kzalloc(sizeof *sgt, GFP_KERNEL); + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); if (!sgt) { DRM_ERROR("failed to allocate sg table.\n"); ret = -ENOMEM; @@ -1240,6 +1241,14 @@ static int g2d_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(g2d_pm_ops, g2d_suspend, g2d_resume); +#ifdef CONFIG_OF +static const struct of_device_id exynos_g2d_match[] = { + { .compatible = "samsung,exynos5250-g2d" }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_g2d_match); +#endif + struct platform_driver g2d_driver = { .probe = g2d_probe, .remove = g2d_remove, @@ -1247,5 +1256,6 @@ struct platform_driver g2d_driver = { .name = "s5p-g2d", .owner = THIS_MODULE, .pm = &g2d_pm_ops, + .of_match_table = of_match_ptr(exynos_g2d_match), }, }; diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c index 473180776528..67e17ce112b6 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c @@ -329,17 +329,11 @@ static struct drm_file *exynos_drm_find_drm_file(struct drm_device *drm_dev, { struct drm_file *file_priv; - mutex_lock(&drm_dev->struct_mutex); - /* find current process's drm_file from filelist. */ - list_for_each_entry(file_priv, &drm_dev->filelist, lhead) { - if (file_priv->filp == filp) { - mutex_unlock(&drm_dev->struct_mutex); + list_for_each_entry(file_priv, &drm_dev->filelist, lhead) + if (file_priv->filp == filp) return file_priv; - } - } - mutex_unlock(&drm_dev->struct_mutex); WARN_ON(1); return ERR_PTR(-EFAULT); @@ -400,9 +394,7 @@ static int exynos_drm_gem_mmap_buffer(struct file *filp, */ drm_gem_object_reference(obj); - mutex_lock(&drm_dev->struct_mutex); drm_vm_open_locked(drm_dev, vma); - mutex_unlock(&drm_dev->struct_mutex); return 0; } @@ -432,6 +424,16 @@ int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, } /* + * We have to use gem object and its fops for specific mmaper, + * but vm_mmap() can deliver only filp. So we have to change + * filp->f_op and filp->private_data temporarily, then restore + * again. So it is important to keep lock until restoration the + * settings to prevent others from misuse of filp->f_op or + * filp->private_data. + */ + mutex_lock(&dev->struct_mutex); + + /* * Set specific mmper's fops. And it will be restored by * exynos_drm_gem_mmap_buffer to dev->driver->fops. * This is used to call specific mapper temporarily. @@ -448,13 +450,20 @@ int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, addr = vm_mmap(file_priv->filp, 0, args->size, PROT_READ | PROT_WRITE, MAP_SHARED, 0); - drm_gem_object_unreference_unlocked(obj); + drm_gem_object_unreference(obj); if (IS_ERR((void *)addr)) { - file_priv->filp->private_data = file_priv; + /* check filp->f_op, filp->private_data are restored */ + if (file_priv->filp->f_op == &exynos_drm_gem_fops) { + file_priv->filp->f_op = fops_get(dev->driver->fops); + file_priv->filp->private_data = file_priv; + } + mutex_unlock(&dev->struct_mutex); return PTR_ERR((void *)addr); } + mutex_unlock(&dev->struct_mutex); + args->mapped = addr; DRM_DEBUG_KMS("mapped = 0x%lx\n", (unsigned long)args->mapped); diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.c b/drivers/gpu/drm/exynos/exynos_drm_hdmi.c index 28644539b305..7c27df03c9ff 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_hdmi.c @@ -124,9 +124,21 @@ static struct edid *drm_hdmi_get_edid(struct device *dev, static int drm_hdmi_check_timing(struct device *dev, void *timing) { struct drm_hdmi_context *ctx = to_context(dev); + int ret = 0; DRM_DEBUG_KMS("%s\n", __FILE__); + /* + * Both, mixer and hdmi should be able to handle the requested mode. + * If any of the two fails, return mode as BAD. + */ + + if (mixer_ops && mixer_ops->check_timing) + ret = mixer_ops->check_timing(ctx->mixer_ctx->ctx, timing); + + if (ret) + return ret; + if (hdmi_ops && hdmi_ops->check_timing) return hdmi_ops->check_timing(ctx->hdmi_ctx->ctx, timing); diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.h b/drivers/gpu/drm/exynos/exynos_drm_hdmi.h index d80516fc9ed7..b7faa3662307 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_hdmi.h +++ b/drivers/gpu/drm/exynos/exynos_drm_hdmi.h @@ -32,7 +32,7 @@ struct exynos_hdmi_ops { bool (*is_connected)(void *ctx); struct edid *(*get_edid)(void *ctx, struct drm_connector *connector); - int (*check_timing)(void *ctx, void *timing); + int (*check_timing)(void *ctx, struct fb_videomode *timing); int (*power_on)(void *ctx, int mode); /* manager */ @@ -58,6 +58,9 @@ struct exynos_mixer_ops { void (*win_mode_set)(void *ctx, struct exynos_drm_overlay *overlay); void (*win_commit)(void *ctx, int zpos); void (*win_disable)(void *ctx, int zpos); + + /* display */ + int (*check_timing)(void *ctx, struct fb_videomode *timing); }; void exynos_hdmi_drv_attach(struct exynos_drm_hdmi_context *ctx); diff --git a/drivers/gpu/drm/exynos/exynos_drm_iommu.h b/drivers/gpu/drm/exynos/exynos_drm_iommu.h index 53b7deea8ab7..598e60f57d4b 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_iommu.h +++ b/drivers/gpu/drm/exynos/exynos_drm_iommu.h @@ -14,7 +14,7 @@ #define EXYNOS_DEV_ADDR_START 0x20000000 #define EXYNOS_DEV_ADDR_SIZE 0x40000000 -#define EXYNOS_DEV_ADDR_ORDER 0x4 +#define EXYNOS_DEV_ADDR_ORDER 0x0 #ifdef CONFIG_DRM_EXYNOS_IOMMU diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index fbab3c468603..6d63f9090f94 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -87,6 +87,73 @@ struct hdmi_resources { int regul_count; }; +struct hdmi_tg_regs { + u8 cmd[1]; + u8 h_fsz[2]; + u8 hact_st[2]; + u8 hact_sz[2]; + u8 v_fsz[2]; + u8 vsync[2]; + u8 vsync2[2]; + u8 vact_st[2]; + u8 vact_sz[2]; + u8 field_chg[2]; + u8 vact_st2[2]; + u8 vact_st3[2]; + u8 vact_st4[2]; + u8 vsync_top_hdmi[2]; + u8 vsync_bot_hdmi[2]; + u8 field_top_hdmi[2]; + u8 field_bot_hdmi[2]; + u8 tg_3d[1]; +}; + +struct hdmi_core_regs { + u8 h_blank[2]; + u8 v2_blank[2]; + u8 v1_blank[2]; + u8 v_line[2]; + u8 h_line[2]; + u8 hsync_pol[1]; + u8 vsync_pol[1]; + u8 int_pro_mode[1]; + u8 v_blank_f0[2]; + u8 v_blank_f1[2]; + u8 h_sync_start[2]; + u8 h_sync_end[2]; + u8 v_sync_line_bef_2[2]; + u8 v_sync_line_bef_1[2]; + u8 v_sync_line_aft_2[2]; + u8 v_sync_line_aft_1[2]; + u8 v_sync_line_aft_pxl_2[2]; + u8 v_sync_line_aft_pxl_1[2]; + u8 v_blank_f2[2]; /* for 3D mode */ + u8 v_blank_f3[2]; /* for 3D mode */ + u8 v_blank_f4[2]; /* for 3D mode */ + u8 v_blank_f5[2]; /* for 3D mode */ + u8 v_sync_line_aft_3[2]; + u8 v_sync_line_aft_4[2]; + u8 v_sync_line_aft_5[2]; + u8 v_sync_line_aft_6[2]; + u8 v_sync_line_aft_pxl_3[2]; + u8 v_sync_line_aft_pxl_4[2]; + u8 v_sync_line_aft_pxl_5[2]; + u8 v_sync_line_aft_pxl_6[2]; + u8 vact_space_1[2]; + u8 vact_space_2[2]; + u8 vact_space_3[2]; + u8 vact_space_4[2]; + u8 vact_space_5[2]; + u8 vact_space_6[2]; +}; + +struct hdmi_v14_conf { + int pixel_clock; + struct hdmi_core_regs core; + struct hdmi_tg_regs tg; + int cea_video_id; +}; + struct hdmi_context { struct device *dev; struct drm_device *drm_dev; @@ -104,6 +171,7 @@ struct hdmi_context { /* current hdmiphy conf index */ int cur_conf; + struct hdmi_v14_conf mode_conf; struct hdmi_resources res; @@ -392,586 +460,132 @@ static const struct hdmi_v13_conf hdmi_v13_confs[] = { }; /* HDMI Version 1.4 */ -static const u8 hdmiphy_conf27_027[32] = { - 0x01, 0xd1, 0x2d, 0x72, 0x40, 0x64, 0x12, 0x08, - 0x43, 0xa0, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80, - 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xe3, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, -}; - -static const u8 hdmiphy_conf74_176[32] = { - 0x01, 0xd1, 0x1f, 0x10, 0x40, 0x5b, 0xef, 0x08, - 0x81, 0xa0, 0xb9, 0xd8, 0x45, 0xa0, 0xac, 0x80, - 0x5a, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xa6, 0x24, 0x01, 0x00, 0x00, 0x01, 0x00, -}; - -static const u8 hdmiphy_conf74_25[32] = { - 0x01, 0xd1, 0x1f, 0x10, 0x40, 0x40, 0xf8, 0x08, - 0x81, 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, - 0x3c, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0xa5, 0x24, 0x01, 0x00, 0x00, 0x01, 0x00, -}; - -static const u8 hdmiphy_conf148_5[32] = { - 0x01, 0xd1, 0x1f, 0x00, 0x40, 0x40, 0xf8, 0x08, - 0x81, 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, - 0x3c, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, - 0x54, 0x4b, 0x25, 0x03, 0x00, 0x00, 0x01, 0x00, -}; - -struct hdmi_tg_regs { - u8 cmd; - u8 h_fsz_l; - u8 h_fsz_h; - u8 hact_st_l; - u8 hact_st_h; - u8 hact_sz_l; - u8 hact_sz_h; - u8 v_fsz_l; - u8 v_fsz_h; - u8 vsync_l; - u8 vsync_h; - u8 vsync2_l; - u8 vsync2_h; - u8 vact_st_l; - u8 vact_st_h; - u8 vact_sz_l; - u8 vact_sz_h; - u8 field_chg_l; - u8 field_chg_h; - u8 vact_st2_l; - u8 vact_st2_h; - u8 vact_st3_l; - u8 vact_st3_h; - u8 vact_st4_l; - u8 vact_st4_h; - u8 vsync_top_hdmi_l; - u8 vsync_top_hdmi_h; - u8 vsync_bot_hdmi_l; - u8 vsync_bot_hdmi_h; - u8 field_top_hdmi_l; - u8 field_top_hdmi_h; - u8 field_bot_hdmi_l; - u8 field_bot_hdmi_h; - u8 tg_3d; -}; - -struct hdmi_core_regs { - u8 h_blank[2]; - u8 v2_blank[2]; - u8 v1_blank[2]; - u8 v_line[2]; - u8 h_line[2]; - u8 hsync_pol[1]; - u8 vsync_pol[1]; - u8 int_pro_mode[1]; - u8 v_blank_f0[2]; - u8 v_blank_f1[2]; - u8 h_sync_start[2]; - u8 h_sync_end[2]; - u8 v_sync_line_bef_2[2]; - u8 v_sync_line_bef_1[2]; - u8 v_sync_line_aft_2[2]; - u8 v_sync_line_aft_1[2]; - u8 v_sync_line_aft_pxl_2[2]; - u8 v_sync_line_aft_pxl_1[2]; - u8 v_blank_f2[2]; /* for 3D mode */ - u8 v_blank_f3[2]; /* for 3D mode */ - u8 v_blank_f4[2]; /* for 3D mode */ - u8 v_blank_f5[2]; /* for 3D mode */ - u8 v_sync_line_aft_3[2]; - u8 v_sync_line_aft_4[2]; - u8 v_sync_line_aft_5[2]; - u8 v_sync_line_aft_6[2]; - u8 v_sync_line_aft_pxl_3[2]; - u8 v_sync_line_aft_pxl_4[2]; - u8 v_sync_line_aft_pxl_5[2]; - u8 v_sync_line_aft_pxl_6[2]; - u8 vact_space_1[2]; - u8 vact_space_2[2]; - u8 vact_space_3[2]; - u8 vact_space_4[2]; - u8 vact_space_5[2]; - u8 vact_space_6[2]; -}; - -struct hdmi_preset_conf { - struct hdmi_core_regs core; - struct hdmi_tg_regs tg; -}; - -struct hdmi_conf { - int width; - int height; - int vrefresh; - bool interlace; - int cea_video_id; - const u8 *hdmiphy_data; - const struct hdmi_preset_conf *conf; -}; - -static const struct hdmi_preset_conf hdmi_conf_480p60 = { - .core = { - .h_blank = {0x8a, 0x00}, - .v2_blank = {0x0d, 0x02}, - .v1_blank = {0x2d, 0x00}, - .v_line = {0x0d, 0x02}, - .h_line = {0x5a, 0x03}, - .hsync_pol = {0x01}, - .vsync_pol = {0x01}, - .int_pro_mode = {0x00}, - .v_blank_f0 = {0xff, 0xff}, - .v_blank_f1 = {0xff, 0xff}, - .h_sync_start = {0x0e, 0x00}, - .h_sync_end = {0x4c, 0x00}, - .v_sync_line_bef_2 = {0x0f, 0x00}, - .v_sync_line_bef_1 = {0x09, 0x00}, - .v_sync_line_aft_2 = {0xff, 0xff}, - .v_sync_line_aft_1 = {0xff, 0xff}, - .v_sync_line_aft_pxl_2 = {0xff, 0xff}, - .v_sync_line_aft_pxl_1 = {0xff, 0xff}, - .v_blank_f2 = {0xff, 0xff}, - .v_blank_f3 = {0xff, 0xff}, - .v_blank_f4 = {0xff, 0xff}, - .v_blank_f5 = {0xff, 0xff}, - .v_sync_line_aft_3 = {0xff, 0xff}, - .v_sync_line_aft_4 = {0xff, 0xff}, - .v_sync_line_aft_5 = {0xff, 0xff}, - .v_sync_line_aft_6 = {0xff, 0xff}, - .v_sync_line_aft_pxl_3 = {0xff, 0xff}, - .v_sync_line_aft_pxl_4 = {0xff, 0xff}, - .v_sync_line_aft_pxl_5 = {0xff, 0xff}, - .v_sync_line_aft_pxl_6 = {0xff, 0xff}, - .vact_space_1 = {0xff, 0xff}, - .vact_space_2 = {0xff, 0xff}, - .vact_space_3 = {0xff, 0xff}, - .vact_space_4 = {0xff, 0xff}, - .vact_space_5 = {0xff, 0xff}, - .vact_space_6 = {0xff, 0xff}, - /* other don't care */ - }, - .tg = { - 0x00, /* cmd */ - 0x5a, 0x03, /* h_fsz */ - 0x8a, 0x00, 0xd0, 0x02, /* hact */ - 0x0d, 0x02, /* v_fsz */ - 0x01, 0x00, 0x33, 0x02, /* vsync */ - 0x2d, 0x00, 0xe0, 0x01, /* vact */ - 0x33, 0x02, /* field_chg */ - 0x48, 0x02, /* vact_st2 */ - 0x00, 0x00, /* vact_st3 */ - 0x00, 0x00, /* vact_st4 */ - 0x01, 0x00, 0x01, 0x00, /* vsync top/bot */ - 0x01, 0x00, 0x33, 0x02, /* field top/bot */ - 0x00, /* 3d FP */ - }, +struct hdmiphy_config { + int pixel_clock; + u8 conf[32]; }; -static const struct hdmi_preset_conf hdmi_conf_720p50 = { - .core = { - .h_blank = {0xbc, 0x02}, - .v2_blank = {0xee, 0x02}, - .v1_blank = {0x1e, 0x00}, - .v_line = {0xee, 0x02}, - .h_line = {0xbc, 0x07}, - .hsync_pol = {0x00}, - .vsync_pol = {0x00}, - .int_pro_mode = {0x00}, - .v_blank_f0 = {0xff, 0xff}, - .v_blank_f1 = {0xff, 0xff}, - .h_sync_start = {0xb6, 0x01}, - .h_sync_end = {0xde, 0x01}, - .v_sync_line_bef_2 = {0x0a, 0x00}, - .v_sync_line_bef_1 = {0x05, 0x00}, - .v_sync_line_aft_2 = {0xff, 0xff}, - .v_sync_line_aft_1 = {0xff, 0xff}, - .v_sync_line_aft_pxl_2 = {0xff, 0xff}, - .v_sync_line_aft_pxl_1 = {0xff, 0xff}, - .v_blank_f2 = {0xff, 0xff}, - .v_blank_f3 = {0xff, 0xff}, - .v_blank_f4 = {0xff, 0xff}, - .v_blank_f5 = {0xff, 0xff}, - .v_sync_line_aft_3 = {0xff, 0xff}, - .v_sync_line_aft_4 = {0xff, 0xff}, - .v_sync_line_aft_5 = {0xff, 0xff}, - .v_sync_line_aft_6 = {0xff, 0xff}, - .v_sync_line_aft_pxl_3 = {0xff, 0xff}, - .v_sync_line_aft_pxl_4 = {0xff, 0xff}, - .v_sync_line_aft_pxl_5 = {0xff, 0xff}, - .v_sync_line_aft_pxl_6 = {0xff, 0xff}, - .vact_space_1 = {0xff, 0xff}, - .vact_space_2 = {0xff, 0xff}, - .vact_space_3 = {0xff, 0xff}, - .vact_space_4 = {0xff, 0xff}, - .vact_space_5 = {0xff, 0xff}, - .vact_space_6 = {0xff, 0xff}, - /* other don't care */ - }, - .tg = { - 0x00, /* cmd */ - 0xbc, 0x07, /* h_fsz */ - 0xbc, 0x02, 0x00, 0x05, /* hact */ - 0xee, 0x02, /* v_fsz */ - 0x01, 0x00, 0x33, 0x02, /* vsync */ - 0x1e, 0x00, 0xd0, 0x02, /* vact */ - 0x33, 0x02, /* field_chg */ - 0x48, 0x02, /* vact_st2 */ - 0x00, 0x00, /* vact_st3 */ - 0x00, 0x00, /* vact_st4 */ - 0x01, 0x00, 0x01, 0x00, /* vsync top/bot */ - 0x01, 0x00, 0x33, 0x02, /* field top/bot */ - 0x00, /* 3d FP */ +/* list of all required phy config settings */ +static const struct hdmiphy_config hdmiphy_v14_configs[] = { + { + .pixel_clock = 25200000, + .conf = { + 0x01, 0x51, 0x2A, 0x75, 0x40, 0x01, 0x00, 0x08, + 0x82, 0x80, 0xfc, 0xd8, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xf4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, }, -}; - -static const struct hdmi_preset_conf hdmi_conf_720p60 = { - .core = { - .h_blank = {0x72, 0x01}, - .v2_blank = {0xee, 0x02}, - .v1_blank = {0x1e, 0x00}, - .v_line = {0xee, 0x02}, - .h_line = {0x72, 0x06}, - .hsync_pol = {0x00}, - .vsync_pol = {0x00}, - .int_pro_mode = {0x00}, - .v_blank_f0 = {0xff, 0xff}, - .v_blank_f1 = {0xff, 0xff}, - .h_sync_start = {0x6c, 0x00}, - .h_sync_end = {0x94, 0x00}, - .v_sync_line_bef_2 = {0x0a, 0x00}, - .v_sync_line_bef_1 = {0x05, 0x00}, - .v_sync_line_aft_2 = {0xff, 0xff}, - .v_sync_line_aft_1 = {0xff, 0xff}, - .v_sync_line_aft_pxl_2 = {0xff, 0xff}, - .v_sync_line_aft_pxl_1 = {0xff, 0xff}, - .v_blank_f2 = {0xff, 0xff}, - .v_blank_f3 = {0xff, 0xff}, - .v_blank_f4 = {0xff, 0xff}, - .v_blank_f5 = {0xff, 0xff}, - .v_sync_line_aft_3 = {0xff, 0xff}, - .v_sync_line_aft_4 = {0xff, 0xff}, - .v_sync_line_aft_5 = {0xff, 0xff}, - .v_sync_line_aft_6 = {0xff, 0xff}, - .v_sync_line_aft_pxl_3 = {0xff, 0xff}, - .v_sync_line_aft_pxl_4 = {0xff, 0xff}, - .v_sync_line_aft_pxl_5 = {0xff, 0xff}, - .v_sync_line_aft_pxl_6 = {0xff, 0xff}, - .vact_space_1 = {0xff, 0xff}, - .vact_space_2 = {0xff, 0xff}, - .vact_space_3 = {0xff, 0xff}, - .vact_space_4 = {0xff, 0xff}, - .vact_space_5 = {0xff, 0xff}, - .vact_space_6 = {0xff, 0xff}, - /* other don't care */ + { + .pixel_clock = 27000000, + .conf = { + 0x01, 0xd1, 0x22, 0x51, 0x40, 0x08, 0xfc, 0x20, + 0x98, 0xa0, 0xcb, 0xd8, 0x45, 0xa0, 0xac, 0x80, + 0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xe4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, }, - .tg = { - 0x00, /* cmd */ - 0x72, 0x06, /* h_fsz */ - 0x72, 0x01, 0x00, 0x05, /* hact */ - 0xee, 0x02, /* v_fsz */ - 0x01, 0x00, 0x33, 0x02, /* vsync */ - 0x1e, 0x00, 0xd0, 0x02, /* vact */ - 0x33, 0x02, /* field_chg */ - 0x48, 0x02, /* vact_st2 */ - 0x00, 0x00, /* vact_st3 */ - 0x00, 0x00, /* vact_st4 */ - 0x01, 0x00, 0x01, 0x00, /* vsync top/bot */ - 0x01, 0x00, 0x33, 0x02, /* field top/bot */ - 0x00, /* 3d FP */ + { + .pixel_clock = 27027000, + .conf = { + 0x01, 0xd1, 0x2d, 0x72, 0x40, 0x64, 0x12, 0x08, + 0x43, 0xa0, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xe3, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, + }, }, -}; - -static const struct hdmi_preset_conf hdmi_conf_1080i50 = { - .core = { - .h_blank = {0xd0, 0x02}, - .v2_blank = {0x32, 0x02}, - .v1_blank = {0x16, 0x00}, - .v_line = {0x65, 0x04}, - .h_line = {0x50, 0x0a}, - .hsync_pol = {0x00}, - .vsync_pol = {0x00}, - .int_pro_mode = {0x01}, - .v_blank_f0 = {0x49, 0x02}, - .v_blank_f1 = {0x65, 0x04}, - .h_sync_start = {0x0e, 0x02}, - .h_sync_end = {0x3a, 0x02}, - .v_sync_line_bef_2 = {0x07, 0x00}, - .v_sync_line_bef_1 = {0x02, 0x00}, - .v_sync_line_aft_2 = {0x39, 0x02}, - .v_sync_line_aft_1 = {0x34, 0x02}, - .v_sync_line_aft_pxl_2 = {0x38, 0x07}, - .v_sync_line_aft_pxl_1 = {0x38, 0x07}, - .v_blank_f2 = {0xff, 0xff}, - .v_blank_f3 = {0xff, 0xff}, - .v_blank_f4 = {0xff, 0xff}, - .v_blank_f5 = {0xff, 0xff}, - .v_sync_line_aft_3 = {0xff, 0xff}, - .v_sync_line_aft_4 = {0xff, 0xff}, - .v_sync_line_aft_5 = {0xff, 0xff}, - .v_sync_line_aft_6 = {0xff, 0xff}, - .v_sync_line_aft_pxl_3 = {0xff, 0xff}, - .v_sync_line_aft_pxl_4 = {0xff, 0xff}, - .v_sync_line_aft_pxl_5 = {0xff, 0xff}, - .v_sync_line_aft_pxl_6 = {0xff, 0xff}, - .vact_space_1 = {0xff, 0xff}, - .vact_space_2 = {0xff, 0xff}, - .vact_space_3 = {0xff, 0xff}, - .vact_space_4 = {0xff, 0xff}, - .vact_space_5 = {0xff, 0xff}, - .vact_space_6 = {0xff, 0xff}, - /* other don't care */ + { + .pixel_clock = 36000000, + .conf = { + 0x01, 0x51, 0x2d, 0x55, 0x40, 0x01, 0x00, 0x08, + 0x82, 0x80, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xab, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, }, - .tg = { - 0x00, /* cmd */ - 0x50, 0x0a, /* h_fsz */ - 0xd0, 0x02, 0x80, 0x07, /* hact */ - 0x65, 0x04, /* v_fsz */ - 0x01, 0x00, 0x33, 0x02, /* vsync */ - 0x16, 0x00, 0x1c, 0x02, /* vact */ - 0x33, 0x02, /* field_chg */ - 0x49, 0x02, /* vact_st2 */ - 0x00, 0x00, /* vact_st3 */ - 0x00, 0x00, /* vact_st4 */ - 0x01, 0x00, 0x33, 0x02, /* vsync top/bot */ - 0x01, 0x00, 0x33, 0x02, /* field top/bot */ - 0x00, /* 3d FP */ + { + .pixel_clock = 40000000, + .conf = { + 0x01, 0x51, 0x32, 0x55, 0x40, 0x01, 0x00, 0x08, + 0x82, 0x80, 0x2c, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0x9a, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, }, -}; - -static const struct hdmi_preset_conf hdmi_conf_1080i60 = { - .core = { - .h_blank = {0x18, 0x01}, - .v2_blank = {0x32, 0x02}, - .v1_blank = {0x16, 0x00}, - .v_line = {0x65, 0x04}, - .h_line = {0x98, 0x08}, - .hsync_pol = {0x00}, - .vsync_pol = {0x00}, - .int_pro_mode = {0x01}, - .v_blank_f0 = {0x49, 0x02}, - .v_blank_f1 = {0x65, 0x04}, - .h_sync_start = {0x56, 0x00}, - .h_sync_end = {0x82, 0x00}, - .v_sync_line_bef_2 = {0x07, 0x00}, - .v_sync_line_bef_1 = {0x02, 0x00}, - .v_sync_line_aft_2 = {0x39, 0x02}, - .v_sync_line_aft_1 = {0x34, 0x02}, - .v_sync_line_aft_pxl_2 = {0xa4, 0x04}, - .v_sync_line_aft_pxl_1 = {0xa4, 0x04}, - .v_blank_f2 = {0xff, 0xff}, - .v_blank_f3 = {0xff, 0xff}, - .v_blank_f4 = {0xff, 0xff}, - .v_blank_f5 = {0xff, 0xff}, - .v_sync_line_aft_3 = {0xff, 0xff}, - .v_sync_line_aft_4 = {0xff, 0xff}, - .v_sync_line_aft_5 = {0xff, 0xff}, - .v_sync_line_aft_6 = {0xff, 0xff}, - .v_sync_line_aft_pxl_3 = {0xff, 0xff}, - .v_sync_line_aft_pxl_4 = {0xff, 0xff}, - .v_sync_line_aft_pxl_5 = {0xff, 0xff}, - .v_sync_line_aft_pxl_6 = {0xff, 0xff}, - .vact_space_1 = {0xff, 0xff}, - .vact_space_2 = {0xff, 0xff}, - .vact_space_3 = {0xff, 0xff}, - .vact_space_4 = {0xff, 0xff}, - .vact_space_5 = {0xff, 0xff}, - .vact_space_6 = {0xff, 0xff}, - /* other don't care */ + { + .pixel_clock = 65000000, + .conf = { + 0x01, 0xd1, 0x36, 0x34, 0x40, 0x1e, 0x0a, 0x08, + 0x82, 0xa0, 0x45, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xbd, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, }, - .tg = { - 0x00, /* cmd */ - 0x98, 0x08, /* h_fsz */ - 0x18, 0x01, 0x80, 0x07, /* hact */ - 0x65, 0x04, /* v_fsz */ - 0x01, 0x00, 0x33, 0x02, /* vsync */ - 0x16, 0x00, 0x1c, 0x02, /* vact */ - 0x33, 0x02, /* field_chg */ - 0x49, 0x02, /* vact_st2 */ - 0x00, 0x00, /* vact_st3 */ - 0x00, 0x00, /* vact_st4 */ - 0x01, 0x00, 0x33, 0x02, /* vsync top/bot */ - 0x01, 0x00, 0x33, 0x02, /* field top/bot */ - 0x00, /* 3d FP */ + { + .pixel_clock = 74176000, + .conf = { + 0x01, 0xd1, 0x3e, 0x35, 0x40, 0x5b, 0xde, 0x08, + 0x82, 0xa0, 0x73, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x56, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xa6, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, }, -}; - -static const struct hdmi_preset_conf hdmi_conf_1080p30 = { - .core = { - .h_blank = {0x18, 0x01}, - .v2_blank = {0x65, 0x04}, - .v1_blank = {0x2d, 0x00}, - .v_line = {0x65, 0x04}, - .h_line = {0x98, 0x08}, - .hsync_pol = {0x00}, - .vsync_pol = {0x00}, - .int_pro_mode = {0x00}, - .v_blank_f0 = {0xff, 0xff}, - .v_blank_f1 = {0xff, 0xff}, - .h_sync_start = {0x56, 0x00}, - .h_sync_end = {0x82, 0x00}, - .v_sync_line_bef_2 = {0x09, 0x00}, - .v_sync_line_bef_1 = {0x04, 0x00}, - .v_sync_line_aft_2 = {0xff, 0xff}, - .v_sync_line_aft_1 = {0xff, 0xff}, - .v_sync_line_aft_pxl_2 = {0xff, 0xff}, - .v_sync_line_aft_pxl_1 = {0xff, 0xff}, - .v_blank_f2 = {0xff, 0xff}, - .v_blank_f3 = {0xff, 0xff}, - .v_blank_f4 = {0xff, 0xff}, - .v_blank_f5 = {0xff, 0xff}, - .v_sync_line_aft_3 = {0xff, 0xff}, - .v_sync_line_aft_4 = {0xff, 0xff}, - .v_sync_line_aft_5 = {0xff, 0xff}, - .v_sync_line_aft_6 = {0xff, 0xff}, - .v_sync_line_aft_pxl_3 = {0xff, 0xff}, - .v_sync_line_aft_pxl_4 = {0xff, 0xff}, - .v_sync_line_aft_pxl_5 = {0xff, 0xff}, - .v_sync_line_aft_pxl_6 = {0xff, 0xff}, - .vact_space_1 = {0xff, 0xff}, - .vact_space_2 = {0xff, 0xff}, - .vact_space_3 = {0xff, 0xff}, - .vact_space_4 = {0xff, 0xff}, - .vact_space_5 = {0xff, 0xff}, - .vact_space_6 = {0xff, 0xff}, - /* other don't care */ + { + .pixel_clock = 74250000, + .conf = { + 0x01, 0xd1, 0x1f, 0x10, 0x40, 0x40, 0xf8, 0x08, + 0x81, 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, + 0x3c, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xa5, 0x24, 0x01, 0x00, 0x00, 0x01, 0x00, + }, }, - .tg = { - 0x00, /* cmd */ - 0x98, 0x08, /* h_fsz */ - 0x18, 0x01, 0x80, 0x07, /* hact */ - 0x65, 0x04, /* v_fsz */ - 0x01, 0x00, 0x33, 0x02, /* vsync */ - 0x2d, 0x00, 0x38, 0x04, /* vact */ - 0x33, 0x02, /* field_chg */ - 0x48, 0x02, /* vact_st2 */ - 0x00, 0x00, /* vact_st3 */ - 0x00, 0x00, /* vact_st4 */ - 0x01, 0x00, 0x01, 0x00, /* vsync top/bot */ - 0x01, 0x00, 0x33, 0x02, /* field top/bot */ - 0x00, /* 3d FP */ + { + .pixel_clock = 83500000, + .conf = { + 0x01, 0xd1, 0x23, 0x11, 0x40, 0x0c, 0xfb, 0x08, + 0x85, 0xa0, 0xd1, 0xd8, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0x93, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, }, -}; - -static const struct hdmi_preset_conf hdmi_conf_1080p50 = { - .core = { - .h_blank = {0xd0, 0x02}, - .v2_blank = {0x65, 0x04}, - .v1_blank = {0x2d, 0x00}, - .v_line = {0x65, 0x04}, - .h_line = {0x50, 0x0a}, - .hsync_pol = {0x00}, - .vsync_pol = {0x00}, - .int_pro_mode = {0x00}, - .v_blank_f0 = {0xff, 0xff}, - .v_blank_f1 = {0xff, 0xff}, - .h_sync_start = {0x0e, 0x02}, - .h_sync_end = {0x3a, 0x02}, - .v_sync_line_bef_2 = {0x09, 0x00}, - .v_sync_line_bef_1 = {0x04, 0x00}, - .v_sync_line_aft_2 = {0xff, 0xff}, - .v_sync_line_aft_1 = {0xff, 0xff}, - .v_sync_line_aft_pxl_2 = {0xff, 0xff}, - .v_sync_line_aft_pxl_1 = {0xff, 0xff}, - .v_blank_f2 = {0xff, 0xff}, - .v_blank_f3 = {0xff, 0xff}, - .v_blank_f4 = {0xff, 0xff}, - .v_blank_f5 = {0xff, 0xff}, - .v_sync_line_aft_3 = {0xff, 0xff}, - .v_sync_line_aft_4 = {0xff, 0xff}, - .v_sync_line_aft_5 = {0xff, 0xff}, - .v_sync_line_aft_6 = {0xff, 0xff}, - .v_sync_line_aft_pxl_3 = {0xff, 0xff}, - .v_sync_line_aft_pxl_4 = {0xff, 0xff}, - .v_sync_line_aft_pxl_5 = {0xff, 0xff}, - .v_sync_line_aft_pxl_6 = {0xff, 0xff}, - .vact_space_1 = {0xff, 0xff}, - .vact_space_2 = {0xff, 0xff}, - .vact_space_3 = {0xff, 0xff}, - .vact_space_4 = {0xff, 0xff}, - .vact_space_5 = {0xff, 0xff}, - .vact_space_6 = {0xff, 0xff}, - /* other don't care */ + { + .pixel_clock = 106500000, + .conf = { + 0x01, 0xd1, 0x2c, 0x12, 0x40, 0x0c, 0x09, 0x08, + 0x84, 0xa0, 0x0a, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0x73, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, }, - .tg = { - 0x00, /* cmd */ - 0x50, 0x0a, /* h_fsz */ - 0xd0, 0x02, 0x80, 0x07, /* hact */ - 0x65, 0x04, /* v_fsz */ - 0x01, 0x00, 0x33, 0x02, /* vsync */ - 0x2d, 0x00, 0x38, 0x04, /* vact */ - 0x33, 0x02, /* field_chg */ - 0x48, 0x02, /* vact_st2 */ - 0x00, 0x00, /* vact_st3 */ - 0x00, 0x00, /* vact_st4 */ - 0x01, 0x00, 0x01, 0x00, /* vsync top/bot */ - 0x01, 0x00, 0x33, 0x02, /* field top/bot */ - 0x00, /* 3d FP */ + { + .pixel_clock = 108000000, + .conf = { + 0x01, 0x51, 0x2d, 0x15, 0x40, 0x01, 0x00, 0x08, + 0x82, 0x80, 0x0e, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xc7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, }, -}; - -static const struct hdmi_preset_conf hdmi_conf_1080p60 = { - .core = { - .h_blank = {0x18, 0x01}, - .v2_blank = {0x65, 0x04}, - .v1_blank = {0x2d, 0x00}, - .v_line = {0x65, 0x04}, - .h_line = {0x98, 0x08}, - .hsync_pol = {0x00}, - .vsync_pol = {0x00}, - .int_pro_mode = {0x00}, - .v_blank_f0 = {0xff, 0xff}, - .v_blank_f1 = {0xff, 0xff}, - .h_sync_start = {0x56, 0x00}, - .h_sync_end = {0x82, 0x00}, - .v_sync_line_bef_2 = {0x09, 0x00}, - .v_sync_line_bef_1 = {0x04, 0x00}, - .v_sync_line_aft_2 = {0xff, 0xff}, - .v_sync_line_aft_1 = {0xff, 0xff}, - .v_sync_line_aft_pxl_2 = {0xff, 0xff}, - .v_sync_line_aft_pxl_1 = {0xff, 0xff}, - .v_blank_f2 = {0xff, 0xff}, - .v_blank_f3 = {0xff, 0xff}, - .v_blank_f4 = {0xff, 0xff}, - .v_blank_f5 = {0xff, 0xff}, - .v_sync_line_aft_3 = {0xff, 0xff}, - .v_sync_line_aft_4 = {0xff, 0xff}, - .v_sync_line_aft_5 = {0xff, 0xff}, - .v_sync_line_aft_6 = {0xff, 0xff}, - .v_sync_line_aft_pxl_3 = {0xff, 0xff}, - .v_sync_line_aft_pxl_4 = {0xff, 0xff}, - .v_sync_line_aft_pxl_5 = {0xff, 0xff}, - .v_sync_line_aft_pxl_6 = {0xff, 0xff}, - /* other don't care */ + { + .pixel_clock = 146250000, + .conf = { + 0x01, 0xd1, 0x3d, 0x15, 0x40, 0x18, 0xfd, 0x08, + 0x83, 0xa0, 0x6e, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0x50, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, }, - .tg = { - 0x00, /* cmd */ - 0x98, 0x08, /* h_fsz */ - 0x18, 0x01, 0x80, 0x07, /* hact */ - 0x65, 0x04, /* v_fsz */ - 0x01, 0x00, 0x33, 0x02, /* vsync */ - 0x2d, 0x00, 0x38, 0x04, /* vact */ - 0x33, 0x02, /* field_chg */ - 0x48, 0x02, /* vact_st2 */ - 0x00, 0x00, /* vact_st3 */ - 0x00, 0x00, /* vact_st4 */ - 0x01, 0x00, 0x01, 0x00, /* vsync top/bot */ - 0x01, 0x00, 0x33, 0x02, /* field top/bot */ - 0x00, /* 3d FP */ + { + .pixel_clock = 148500000, + .conf = { + 0x01, 0xd1, 0x1f, 0x00, 0x40, 0x40, 0xf8, 0x08, + 0x81, 0xa0, 0xba, 0xd8, 0x45, 0xa0, 0xac, 0x80, + 0x3c, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0x4b, 0x25, 0x03, 0x00, 0x00, 0x01, 0x00, + }, }, }; -static const struct hdmi_conf hdmi_confs[] = { - { 720, 480, 60, false, 3, hdmiphy_conf27_027, &hdmi_conf_480p60 }, - { 1280, 720, 50, false, 19, hdmiphy_conf74_25, &hdmi_conf_720p50 }, - { 1280, 720, 60, false, 4, hdmiphy_conf74_25, &hdmi_conf_720p60 }, - { 1920, 1080, 50, true, 20, hdmiphy_conf74_25, &hdmi_conf_1080i50 }, - { 1920, 1080, 60, true, 5, hdmiphy_conf74_25, &hdmi_conf_1080i60 }, - { 1920, 1080, 30, false, 34, hdmiphy_conf74_176, &hdmi_conf_1080p30 }, - { 1920, 1080, 50, false, 31, hdmiphy_conf148_5, &hdmi_conf_1080p50 }, - { 1920, 1080, 60, false, 16, hdmiphy_conf148_5, &hdmi_conf_1080p60 }, -}; - struct hdmi_infoframe { enum HDMI_PACKET_TYPE type; u8 ver; @@ -1275,31 +889,6 @@ static int hdmi_v13_conf_index(struct drm_display_mode *mode) return -EINVAL; } -static int hdmi_v14_conf_index(struct drm_display_mode *mode) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(hdmi_confs); ++i) - if (hdmi_confs[i].width == mode->hdisplay && - hdmi_confs[i].height == mode->vdisplay && - hdmi_confs[i].vrefresh == mode->vrefresh && - hdmi_confs[i].interlace == - ((mode->flags & DRM_MODE_FLAG_INTERLACE) ? - true : false)) - return i; - - return -EINVAL; -} - -static int hdmi_conf_index(struct hdmi_context *hdata, - struct drm_display_mode *mode) -{ - if (hdata->type == HDMI_TYPE13) - return hdmi_v13_conf_index(mode); - - return hdmi_v14_conf_index(mode); -} - static u8 hdmi_chksum(struct hdmi_context *hdata, u32 start, u8 len, u32 hdr_sum) { @@ -1357,7 +946,7 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata, if (hdata->type == HDMI_TYPE13) vic = hdmi_v13_confs[hdata->cur_conf].cea_video_id; else - vic = hdmi_confs[hdata->cur_conf].cea_video_id; + vic = hdata->mode_conf.cea_video_id; hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(4), vic); @@ -1434,44 +1023,51 @@ static int hdmi_v13_check_timing(struct fb_videomode *check_timing) return -EINVAL; } +static int hdmi_v14_find_phy_conf(int pixel_clock) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hdmiphy_v14_configs); i++) { + if (hdmiphy_v14_configs[i].pixel_clock == pixel_clock) + return i; + } + + DRM_DEBUG_KMS("Could not find phy config for %d\n", pixel_clock); + return -EINVAL; +} + static int hdmi_v14_check_timing(struct fb_videomode *check_timing) { int i; - DRM_DEBUG_KMS("valid mode : xres=%d, yres=%d, refresh=%d, intl=%d\n", + DRM_DEBUG_KMS("mode: xres=%d, yres=%d, refresh=%d, clock=%d, intl=%d\n", check_timing->xres, check_timing->yres, - check_timing->refresh, (check_timing->vmode & - FB_VMODE_INTERLACED) ? true : false); + check_timing->refresh, check_timing->pixclock, + (check_timing->vmode & FB_VMODE_INTERLACED) ? + true : false); - for (i = 0; i < ARRAY_SIZE(hdmi_confs); i++) - if (hdmi_confs[i].width == check_timing->xres && - hdmi_confs[i].height == check_timing->yres && - hdmi_confs[i].vrefresh == check_timing->refresh && - hdmi_confs[i].interlace == - ((check_timing->vmode & FB_VMODE_INTERLACED) ? - true : false)) - return 0; - - /* TODO */ + for (i = 0; i < ARRAY_SIZE(hdmiphy_v14_configs); i++) + if (hdmiphy_v14_configs[i].pixel_clock == + check_timing->pixclock) + return 0; return -EINVAL; } -static int hdmi_check_timing(void *ctx, void *timing) +static int hdmi_check_timing(void *ctx, struct fb_videomode *timing) { struct hdmi_context *hdata = ctx; - struct fb_videomode *check_timing = timing; DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); - DRM_DEBUG_KMS("[%d]x[%d] [%d]Hz [%x]\n", check_timing->xres, - check_timing->yres, check_timing->refresh, - check_timing->vmode); + DRM_DEBUG_KMS("[%d]x[%d] [%d]Hz [%x]\n", timing->xres, + timing->yres, timing->refresh, + timing->vmode); if (hdata->type == HDMI_TYPE13) - return hdmi_v13_check_timing(check_timing); + return hdmi_v13_check_timing(timing); else - return hdmi_v14_check_timing(check_timing); + return hdmi_v14_check_timing(timing); } static void hdmi_set_acr(u32 freq, u8 *acr) @@ -1795,9 +1391,8 @@ static void hdmi_v13_timing_apply(struct hdmi_context *hdata) static void hdmi_v14_timing_apply(struct hdmi_context *hdata) { - const struct hdmi_preset_conf *conf = hdmi_confs[hdata->cur_conf].conf; - const struct hdmi_core_regs *core = &conf->core; - const struct hdmi_tg_regs *tg = &conf->tg; + struct hdmi_core_regs *core = &hdata->mode_conf.core; + struct hdmi_tg_regs *tg = &hdata->mode_conf.tg; int tries; /* setting core registers */ @@ -1900,39 +1495,39 @@ static void hdmi_v14_timing_apply(struct hdmi_context *hdata) hdmi_reg_writeb(hdata, HDMI_VACT_SPACE_6_1, core->vact_space_6[1]); /* Timing generator registers */ - hdmi_reg_writeb(hdata, HDMI_TG_H_FSZ_L, tg->h_fsz_l); - hdmi_reg_writeb(hdata, HDMI_TG_H_FSZ_H, tg->h_fsz_h); - hdmi_reg_writeb(hdata, HDMI_TG_HACT_ST_L, tg->hact_st_l); - hdmi_reg_writeb(hdata, HDMI_TG_HACT_ST_H, tg->hact_st_h); - hdmi_reg_writeb(hdata, HDMI_TG_HACT_SZ_L, tg->hact_sz_l); - hdmi_reg_writeb(hdata, HDMI_TG_HACT_SZ_H, tg->hact_sz_h); - hdmi_reg_writeb(hdata, HDMI_TG_V_FSZ_L, tg->v_fsz_l); - hdmi_reg_writeb(hdata, HDMI_TG_V_FSZ_H, tg->v_fsz_h); - hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_L, tg->vsync_l); - hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_H, tg->vsync_h); - hdmi_reg_writeb(hdata, HDMI_TG_VSYNC2_L, tg->vsync2_l); - hdmi_reg_writeb(hdata, HDMI_TG_VSYNC2_H, tg->vsync2_h); - hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST_L, tg->vact_st_l); - hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST_H, tg->vact_st_h); - hdmi_reg_writeb(hdata, HDMI_TG_VACT_SZ_L, tg->vact_sz_l); - hdmi_reg_writeb(hdata, HDMI_TG_VACT_SZ_H, tg->vact_sz_h); - hdmi_reg_writeb(hdata, HDMI_TG_FIELD_CHG_L, tg->field_chg_l); - hdmi_reg_writeb(hdata, HDMI_TG_FIELD_CHG_H, tg->field_chg_h); - hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST2_L, tg->vact_st2_l); - hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST2_H, tg->vact_st2_h); - hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST3_L, tg->vact_st3_l); - hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST3_H, tg->vact_st3_h); - hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST4_L, tg->vact_st4_l); - hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST4_H, tg->vact_st4_h); - hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_TOP_HDMI_L, tg->vsync_top_hdmi_l); - hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_TOP_HDMI_H, tg->vsync_top_hdmi_h); - hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_BOT_HDMI_L, tg->vsync_bot_hdmi_l); - hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_BOT_HDMI_H, tg->vsync_bot_hdmi_h); - hdmi_reg_writeb(hdata, HDMI_TG_FIELD_TOP_HDMI_L, tg->field_top_hdmi_l); - hdmi_reg_writeb(hdata, HDMI_TG_FIELD_TOP_HDMI_H, tg->field_top_hdmi_h); - hdmi_reg_writeb(hdata, HDMI_TG_FIELD_BOT_HDMI_L, tg->field_bot_hdmi_l); - hdmi_reg_writeb(hdata, HDMI_TG_FIELD_BOT_HDMI_H, tg->field_bot_hdmi_h); - hdmi_reg_writeb(hdata, HDMI_TG_3D, tg->tg_3d); + hdmi_reg_writeb(hdata, HDMI_TG_H_FSZ_L, tg->h_fsz[0]); + hdmi_reg_writeb(hdata, HDMI_TG_H_FSZ_H, tg->h_fsz[1]); + hdmi_reg_writeb(hdata, HDMI_TG_HACT_ST_L, tg->hact_st[0]); + hdmi_reg_writeb(hdata, HDMI_TG_HACT_ST_H, tg->hact_st[1]); + hdmi_reg_writeb(hdata, HDMI_TG_HACT_SZ_L, tg->hact_sz[0]); + hdmi_reg_writeb(hdata, HDMI_TG_HACT_SZ_H, tg->hact_sz[1]); + hdmi_reg_writeb(hdata, HDMI_TG_V_FSZ_L, tg->v_fsz[0]); + hdmi_reg_writeb(hdata, HDMI_TG_V_FSZ_H, tg->v_fsz[1]); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_L, tg->vsync[0]); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_H, tg->vsync[1]); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC2_L, tg->vsync2[0]); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC2_H, tg->vsync2[1]); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST_L, tg->vact_st[0]); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST_H, tg->vact_st[1]); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_SZ_L, tg->vact_sz[0]); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_SZ_H, tg->vact_sz[1]); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_CHG_L, tg->field_chg[0]); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_CHG_H, tg->field_chg[1]); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST2_L, tg->vact_st2[0]); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST2_H, tg->vact_st2[1]); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST3_L, tg->vact_st3[0]); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST3_H, tg->vact_st3[1]); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST4_L, tg->vact_st4[0]); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST4_H, tg->vact_st4[1]); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_TOP_HDMI_L, tg->vsync_top_hdmi[0]); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_TOP_HDMI_H, tg->vsync_top_hdmi[1]); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_BOT_HDMI_L, tg->vsync_bot_hdmi[0]); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_BOT_HDMI_H, tg->vsync_bot_hdmi[1]); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_TOP_HDMI_L, tg->field_top_hdmi[0]); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_TOP_HDMI_H, tg->field_top_hdmi[1]); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_BOT_HDMI_L, tg->field_bot_hdmi[0]); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_BOT_HDMI_H, tg->field_bot_hdmi[1]); + hdmi_reg_writeb(hdata, HDMI_TG_3D, tg->tg_3d[0]); /* waiting for HDMIPHY's PLL to get to steady state */ for (tries = 100; tries; --tries) { @@ -2029,10 +1624,17 @@ static void hdmiphy_conf_apply(struct hdmi_context *hdata) } /* pixel clock */ - if (hdata->type == HDMI_TYPE13) + if (hdata->type == HDMI_TYPE13) { hdmiphy_data = hdmi_v13_confs[hdata->cur_conf].hdmiphy_data; - else - hdmiphy_data = hdmi_confs[hdata->cur_conf].hdmiphy_data; + } else { + i = hdmi_v14_find_phy_conf(hdata->mode_conf.pixel_clock); + if (i < 0) { + DRM_ERROR("failed to find hdmiphy conf\n"); + return; + } + + hdmiphy_data = hdmiphy_v14_configs[i].conf; + } memcpy(buffer, hdmiphy_data, 32); ret = i2c_master_send(hdata->hdmiphy_port, buffer, 32); @@ -2100,7 +1702,7 @@ static void hdmi_mode_fixup(void *ctx, struct drm_connector *connector, if (hdata->type == HDMI_TYPE13) index = hdmi_v13_conf_index(adjusted_mode); else - index = hdmi_v14_conf_index(adjusted_mode); + index = hdmi_v14_find_phy_conf(adjusted_mode->clock * 1000); /* just return if user desired mode exists. */ if (index >= 0) @@ -2114,7 +1716,7 @@ static void hdmi_mode_fixup(void *ctx, struct drm_connector *connector, if (hdata->type == HDMI_TYPE13) index = hdmi_v13_conf_index(m); else - index = hdmi_v14_conf_index(m); + index = hdmi_v14_find_phy_conf(m->clock * 1000); if (index >= 0) { struct drm_mode_object base; @@ -2123,6 +1725,9 @@ static void hdmi_mode_fixup(void *ctx, struct drm_connector *connector, DRM_INFO("desired mode doesn't exist so\n"); DRM_INFO("use the most suitable mode among modes.\n"); + DRM_DEBUG_KMS("Adjusted Mode: [%d]x[%d] [%d]Hz\n", + m->hdisplay, m->vdisplay, m->vrefresh); + /* preserve display mode header while copying. */ head = adjusted_mode->head; base = adjusted_mode->base; @@ -2134,6 +1739,122 @@ static void hdmi_mode_fixup(void *ctx, struct drm_connector *connector, } } +static void hdmi_set_reg(u8 *reg_pair, int num_bytes, u32 value) +{ + int i; + BUG_ON(num_bytes > 4); + for (i = 0; i < num_bytes; i++) + reg_pair[i] = (value >> (8 * i)) & 0xff; +} + +static void hdmi_v14_mode_set(struct hdmi_context *hdata, + struct drm_display_mode *m) +{ + struct hdmi_core_regs *core = &hdata->mode_conf.core; + struct hdmi_tg_regs *tg = &hdata->mode_conf.tg; + + hdata->mode_conf.cea_video_id = drm_match_cea_mode(m); + + hdata->mode_conf.pixel_clock = m->clock * 1000; + hdmi_set_reg(core->h_blank, 2, m->htotal - m->hdisplay); + hdmi_set_reg(core->v_line, 2, m->vtotal); + hdmi_set_reg(core->h_line, 2, m->htotal); + hdmi_set_reg(core->hsync_pol, 1, + (m->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0); + hdmi_set_reg(core->vsync_pol, 1, + (m->flags & DRM_MODE_FLAG_NVSYNC) ? 1 : 0); + hdmi_set_reg(core->int_pro_mode, 1, + (m->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0); + + /* + * Quirk requirement for exynos 5 HDMI IP design, + * 2 pixels less than the actual calculation for hsync_start + * and end. + */ + + /* Following values & calculations differ for different type of modes */ + if (m->flags & DRM_MODE_FLAG_INTERLACE) { + /* Interlaced Mode */ + hdmi_set_reg(core->v_sync_line_bef_2, 2, + (m->vsync_end - m->vdisplay) / 2); + hdmi_set_reg(core->v_sync_line_bef_1, 2, + (m->vsync_start - m->vdisplay) / 2); + hdmi_set_reg(core->v2_blank, 2, m->vtotal / 2); + hdmi_set_reg(core->v1_blank, 2, (m->vtotal - m->vdisplay) / 2); + hdmi_set_reg(core->v_blank_f0, 2, (m->vtotal + + ((m->vsync_end - m->vsync_start) * 4) + 5) / 2); + hdmi_set_reg(core->v_blank_f1, 2, m->vtotal); + hdmi_set_reg(core->v_sync_line_aft_2, 2, (m->vtotal / 2) + 7); + hdmi_set_reg(core->v_sync_line_aft_1, 2, (m->vtotal / 2) + 2); + hdmi_set_reg(core->v_sync_line_aft_pxl_2, 2, + (m->htotal / 2) + (m->hsync_start - m->hdisplay)); + hdmi_set_reg(core->v_sync_line_aft_pxl_1, 2, + (m->htotal / 2) + (m->hsync_start - m->hdisplay)); + hdmi_set_reg(tg->vact_st, 2, (m->vtotal - m->vdisplay) / 2); + hdmi_set_reg(tg->vact_sz, 2, m->vdisplay / 2); + hdmi_set_reg(tg->vact_st2, 2, 0x249);/* Reset value + 1*/ + hdmi_set_reg(tg->vact_st3, 2, 0x0); + hdmi_set_reg(tg->vact_st4, 2, 0x0); + } else { + /* Progressive Mode */ + hdmi_set_reg(core->v_sync_line_bef_2, 2, + m->vsync_end - m->vdisplay); + hdmi_set_reg(core->v_sync_line_bef_1, 2, + m->vsync_start - m->vdisplay); + hdmi_set_reg(core->v2_blank, 2, m->vtotal); + hdmi_set_reg(core->v1_blank, 2, m->vtotal - m->vdisplay); + hdmi_set_reg(core->v_blank_f0, 2, 0xffff); + hdmi_set_reg(core->v_blank_f1, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_2, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_1, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_pxl_2, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_pxl_1, 2, 0xffff); + hdmi_set_reg(tg->vact_st, 2, m->vtotal - m->vdisplay); + hdmi_set_reg(tg->vact_sz, 2, m->vdisplay); + hdmi_set_reg(tg->vact_st2, 2, 0x248); /* Reset value */ + hdmi_set_reg(tg->vact_st3, 2, 0x47b); /* Reset value */ + hdmi_set_reg(tg->vact_st4, 2, 0x6ae); /* Reset value */ + } + + /* Following values & calculations are same irrespective of mode type */ + hdmi_set_reg(core->h_sync_start, 2, m->hsync_start - m->hdisplay - 2); + hdmi_set_reg(core->h_sync_end, 2, m->hsync_end - m->hdisplay - 2); + hdmi_set_reg(core->vact_space_1, 2, 0xffff); + hdmi_set_reg(core->vact_space_2, 2, 0xffff); + hdmi_set_reg(core->vact_space_3, 2, 0xffff); + hdmi_set_reg(core->vact_space_4, 2, 0xffff); + hdmi_set_reg(core->vact_space_5, 2, 0xffff); + hdmi_set_reg(core->vact_space_6, 2, 0xffff); + hdmi_set_reg(core->v_blank_f2, 2, 0xffff); + hdmi_set_reg(core->v_blank_f3, 2, 0xffff); + hdmi_set_reg(core->v_blank_f4, 2, 0xffff); + hdmi_set_reg(core->v_blank_f5, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_3, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_4, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_5, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_6, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_pxl_3, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_pxl_4, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_pxl_5, 2, 0xffff); + hdmi_set_reg(core->v_sync_line_aft_pxl_6, 2, 0xffff); + + /* Timing generator registers */ + hdmi_set_reg(tg->cmd, 1, 0x0); + hdmi_set_reg(tg->h_fsz, 2, m->htotal); + hdmi_set_reg(tg->hact_st, 2, m->htotal - m->hdisplay); + hdmi_set_reg(tg->hact_sz, 2, m->hdisplay); + hdmi_set_reg(tg->v_fsz, 2, m->vtotal); + hdmi_set_reg(tg->vsync, 2, 0x1); + hdmi_set_reg(tg->vsync2, 2, 0x233); /* Reset value */ + hdmi_set_reg(tg->field_chg, 2, 0x233); /* Reset value */ + hdmi_set_reg(tg->vsync_top_hdmi, 2, 0x1); /* Reset value */ + hdmi_set_reg(tg->vsync_bot_hdmi, 2, 0x233); /* Reset value */ + hdmi_set_reg(tg->field_top_hdmi, 2, 0x1); /* Reset value */ + hdmi_set_reg(tg->field_bot_hdmi, 2, 0x233); /* Reset value */ + hdmi_set_reg(tg->tg_3d, 1, 0x0); + +} + static void hdmi_mode_set(void *ctx, void *mode) { struct hdmi_context *hdata = ctx; @@ -2141,11 +1862,15 @@ static void hdmi_mode_set(void *ctx, void *mode) DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); - conf_idx = hdmi_conf_index(hdata, mode); - if (conf_idx >= 0) - hdata->cur_conf = conf_idx; - else - DRM_DEBUG_KMS("not supported mode\n"); + if (hdata->type == HDMI_TYPE13) { + conf_idx = hdmi_v13_conf_index(mode); + if (conf_idx >= 0) + hdata->cur_conf = conf_idx; + else + DRM_DEBUG_KMS("not supported mode\n"); + } else { + hdmi_v14_mode_set(hdata, mode); + } } static void hdmi_get_max_resol(void *ctx, unsigned int *width, diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c index c414584bfbae..e919aba29b3d 100644 --- a/drivers/gpu/drm/exynos/exynos_mixer.c +++ b/drivers/gpu/drm/exynos/exynos_mixer.c @@ -284,13 +284,13 @@ static void mixer_cfg_scan(struct mixer_context *ctx, unsigned int height) MXR_CFG_SCAN_PROGRASSIVE); /* choosing between porper HD and SD mode */ - if (height == 480) + if (height <= 480) val |= MXR_CFG_SCAN_NTSC | MXR_CFG_SCAN_SD; - else if (height == 576) + else if (height <= 576) val |= MXR_CFG_SCAN_PAL | MXR_CFG_SCAN_SD; - else if (height == 720) + else if (height <= 720) val |= MXR_CFG_SCAN_HD_720 | MXR_CFG_SCAN_HD; - else if (height == 1080) + else if (height <= 1080) val |= MXR_CFG_SCAN_HD_1080 | MXR_CFG_SCAN_HD; else val |= MXR_CFG_SCAN_HD_720 | MXR_CFG_SCAN_HD; @@ -818,6 +818,29 @@ static void mixer_win_disable(void *ctx, int win) mixer_ctx->win_data[win].enabled = false; } +int mixer_check_timing(void *ctx, struct fb_videomode *timing) +{ + struct mixer_context *mixer_ctx = ctx; + u32 w, h; + + w = timing->xres; + h = timing->yres; + + DRM_DEBUG_KMS("%s : xres=%d, yres=%d, refresh=%d, intl=%d\n", + __func__, timing->xres, timing->yres, + timing->refresh, (timing->vmode & + FB_VMODE_INTERLACED) ? true : false); + + if (mixer_ctx->mxr_ver == MXR_VER_0_0_0_16) + return 0; + + if ((w >= 464 && w <= 720 && h >= 261 && h <= 576) || + (w >= 1024 && w <= 1280 && h >= 576 && h <= 720) || + (w >= 1664 && w <= 1920 && h >= 936 && h <= 1080)) + return 0; + + return -EINVAL; +} static void mixer_wait_for_vblank(void *ctx) { struct mixer_context *mixer_ctx = ctx; @@ -955,6 +978,9 @@ static struct exynos_mixer_ops mixer_ops = { .win_mode_set = mixer_win_mode_set, .win_commit = mixer_win_commit, .win_disable = mixer_win_disable, + + /* display */ + .check_timing = mixer_check_timing, }; static irqreturn_t mixer_irq_handler(int irq, void *arg) diff --git a/drivers/gpu/drm/gma500/framebuffer.c b/drivers/gpu/drm/gma500/framebuffer.c index c1ef37e2efdf..2590cac84257 100644 --- a/drivers/gpu/drm/gma500/framebuffer.c +++ b/drivers/gpu/drm/gma500/framebuffer.c @@ -545,9 +545,7 @@ static int psbfb_probe(struct drm_fb_helper *helper, struct psb_fbdev *psb_fbdev = (struct psb_fbdev *)helper; struct drm_device *dev = psb_fbdev->psb_fb_helper.dev; struct drm_psb_private *dev_priv = dev->dev_private; - int new_fb = 0; int bytespp; - int ret; bytespp = sizes->surface_bpp / 8; if (bytespp == 3) /* no 24bit packed */ @@ -562,13 +560,7 @@ static int psbfb_probe(struct drm_fb_helper *helper, sizes->surface_depth = 16; } - if (!helper->fb) { - ret = psbfb_create(psb_fbdev, sizes); - if (ret) - return ret; - new_fb = 1; - } - return new_fb; + return psbfb_create(psb_fbdev, sizes); } static struct drm_fb_helper_funcs psb_fb_helper_funcs = { @@ -616,6 +608,10 @@ int psb_fbdev_init(struct drm_device *dev) INTELFB_CONN_LIMIT); drm_fb_helper_single_add_all_connectors(&fbdev->psb_fb_helper); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + drm_fb_helper_initial_config(&fbdev->psb_fb_helper, 32); return 0; } diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c index 8033526bb53b..9edb1902a096 100644 --- a/drivers/gpu/drm/gma500/psb_intel_display.c +++ b/drivers/gpu/drm/gma500/psb_intel_display.c @@ -85,14 +85,14 @@ struct psb_intel_limit_t { #define I9XX_DOT_MAX 400000 #define I9XX_VCO_MIN 1400000 #define I9XX_VCO_MAX 2800000 -#define I9XX_N_MIN 3 -#define I9XX_N_MAX 8 +#define I9XX_N_MIN 1 +#define I9XX_N_MAX 6 #define I9XX_M_MIN 70 #define I9XX_M_MAX 120 -#define I9XX_M1_MIN 10 -#define I9XX_M1_MAX 20 -#define I9XX_M2_MIN 5 -#define I9XX_M2_MAX 9 +#define I9XX_M1_MIN 8 +#define I9XX_M1_MAX 18 +#define I9XX_M2_MIN 3 +#define I9XX_M2_MAX 7 #define I9XX_P_SDVO_DAC_MIN 5 #define I9XX_P_SDVO_DAC_MAX 80 #define I9XX_P_LVDS_MIN 7 diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 16118363509a..4d341db462a2 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -19,4 +19,10 @@ config DRM_I2C_SIL164 when used in pairs) TMDS transmitters, used in some nVidia video cards. +config DRM_I2C_NXP_TDA998X + tristate "NXP Semiconductors TDA998X HDMI encoder" + default m if DRM_TILCDC + help + Support for NXP Semiconductors TDA998X HDMI encoders. + endmenu diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile index 92862563e7ee..43aa33baebed 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -5,3 +5,6 @@ obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o sil164-y := sil164_drv.o obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o + +tda998x-y := tda998x_drv.o +obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c new file mode 100644 index 000000000000..e68b58a1aaf9 --- /dev/null +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -0,0 +1,906 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include <linux/module.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_edid.h> + + +#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) + +struct tda998x_priv { + struct i2c_client *cec; + uint16_t rev; + uint8_t current_page; + int dpms; +}; + +#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv) + +/* The TDA9988 series of devices use a paged register scheme.. to simplify + * things we encode the page # in upper bits of the register #. To read/ + * write a given register, we need to make sure CURPAGE register is set + * appropriately. Which implies reads/writes are not atomic. Fun! + */ + +#define REG(page, addr) (((page) << 8) | (addr)) +#define REG2ADDR(reg) ((reg) & 0xff) +#define REG2PAGE(reg) (((reg) >> 8) & 0xff) + +#define REG_CURPAGE 0xff /* write */ + + +/* Page 00h: General Control */ +#define REG_VERSION_LSB REG(0x00, 0x00) /* read */ +#define REG_MAIN_CNTRL0 REG(0x00, 0x01) /* read/write */ +# define MAIN_CNTRL0_SR (1 << 0) +# define MAIN_CNTRL0_DECS (1 << 1) +# define MAIN_CNTRL0_DEHS (1 << 2) +# define MAIN_CNTRL0_CECS (1 << 3) +# define MAIN_CNTRL0_CEHS (1 << 4) +# define MAIN_CNTRL0_SCALER (1 << 7) +#define REG_VERSION_MSB REG(0x00, 0x02) /* read */ +#define REG_SOFTRESET REG(0x00, 0x0a) /* write */ +# define SOFTRESET_AUDIO (1 << 0) +# define SOFTRESET_I2C_MASTER (1 << 1) +#define REG_DDC_DISABLE REG(0x00, 0x0b) /* read/write */ +#define REG_CCLK_ON REG(0x00, 0x0c) /* read/write */ +#define REG_I2C_MASTER REG(0x00, 0x0d) /* read/write */ +# define I2C_MASTER_DIS_MM (1 << 0) +# define I2C_MASTER_DIS_FILT (1 << 1) +# define I2C_MASTER_APP_STRT_LAT (1 << 2) +#define REG_INT_FLAGS_0 REG(0x00, 0x0f) /* read/write */ +#define REG_INT_FLAGS_1 REG(0x00, 0x10) /* read/write */ +#define REG_INT_FLAGS_2 REG(0x00, 0x11) /* read/write */ +# define INT_FLAGS_2_EDID_BLK_RD (1 << 1) +#define REG_ENA_VP_0 REG(0x00, 0x18) /* read/write */ +#define REG_ENA_VP_1 REG(0x00, 0x19) /* read/write */ +#define REG_ENA_VP_2 REG(0x00, 0x1a) /* read/write */ +#define REG_ENA_AP REG(0x00, 0x1e) /* read/write */ +#define REG_VIP_CNTRL_0 REG(0x00, 0x20) /* write */ +# define VIP_CNTRL_0_MIRR_A (1 << 7) +# define VIP_CNTRL_0_SWAP_A(x) (((x) & 7) << 4) +# define VIP_CNTRL_0_MIRR_B (1 << 3) +# define VIP_CNTRL_0_SWAP_B(x) (((x) & 7) << 0) +#define REG_VIP_CNTRL_1 REG(0x00, 0x21) /* write */ +# define VIP_CNTRL_1_MIRR_C (1 << 7) +# define VIP_CNTRL_1_SWAP_C(x) (((x) & 7) << 4) +# define VIP_CNTRL_1_MIRR_D (1 << 3) +# define VIP_CNTRL_1_SWAP_D(x) (((x) & 7) << 0) +#define REG_VIP_CNTRL_2 REG(0x00, 0x22) /* write */ +# define VIP_CNTRL_2_MIRR_E (1 << 7) +# define VIP_CNTRL_2_SWAP_E(x) (((x) & 7) << 4) +# define VIP_CNTRL_2_MIRR_F (1 << 3) +# define VIP_CNTRL_2_SWAP_F(x) (((x) & 7) << 0) +#define REG_VIP_CNTRL_3 REG(0x00, 0x23) /* write */ +# define VIP_CNTRL_3_X_TGL (1 << 0) +# define VIP_CNTRL_3_H_TGL (1 << 1) +# define VIP_CNTRL_3_V_TGL (1 << 2) +# define VIP_CNTRL_3_EMB (1 << 3) +# define VIP_CNTRL_3_SYNC_DE (1 << 4) +# define VIP_CNTRL_3_SYNC_HS (1 << 5) +# define VIP_CNTRL_3_DE_INT (1 << 6) +# define VIP_CNTRL_3_EDGE (1 << 7) +#define REG_VIP_CNTRL_4 REG(0x00, 0x24) /* write */ +# define VIP_CNTRL_4_BLC(x) (((x) & 3) << 0) +# define VIP_CNTRL_4_BLANKIT(x) (((x) & 3) << 2) +# define VIP_CNTRL_4_CCIR656 (1 << 4) +# define VIP_CNTRL_4_656_ALT (1 << 5) +# define VIP_CNTRL_4_TST_656 (1 << 6) +# define VIP_CNTRL_4_TST_PAT (1 << 7) +#define REG_VIP_CNTRL_5 REG(0x00, 0x25) /* write */ +# define VIP_CNTRL_5_CKCASE (1 << 0) +# define VIP_CNTRL_5_SP_CNT(x) (((x) & 3) << 1) +#define REG_MAT_CONTRL REG(0x00, 0x80) /* write */ +# define MAT_CONTRL_MAT_SC(x) (((x) & 3) << 0) +# define MAT_CONTRL_MAT_BP (1 << 2) +#define REG_VIDFORMAT REG(0x00, 0xa0) /* write */ +#define REG_REFPIX_MSB REG(0x00, 0xa1) /* write */ +#define REG_REFPIX_LSB REG(0x00, 0xa2) /* write */ +#define REG_REFLINE_MSB REG(0x00, 0xa3) /* write */ +#define REG_REFLINE_LSB REG(0x00, 0xa4) /* write */ +#define REG_NPIX_MSB REG(0x00, 0xa5) /* write */ +#define REG_NPIX_LSB REG(0x00, 0xa6) /* write */ +#define REG_NLINE_MSB REG(0x00, 0xa7) /* write */ +#define REG_NLINE_LSB REG(0x00, 0xa8) /* write */ +#define REG_VS_LINE_STRT_1_MSB REG(0x00, 0xa9) /* write */ +#define REG_VS_LINE_STRT_1_LSB REG(0x00, 0xaa) /* write */ +#define REG_VS_PIX_STRT_1_MSB REG(0x00, 0xab) /* write */ +#define REG_VS_PIX_STRT_1_LSB REG(0x00, 0xac) /* write */ +#define REG_VS_LINE_END_1_MSB REG(0x00, 0xad) /* write */ +#define REG_VS_LINE_END_1_LSB REG(0x00, 0xae) /* write */ +#define REG_VS_PIX_END_1_MSB REG(0x00, 0xaf) /* write */ +#define REG_VS_PIX_END_1_LSB REG(0x00, 0xb0) /* write */ +#define REG_VS_PIX_STRT_2_MSB REG(0x00, 0xb3) /* write */ +#define REG_VS_PIX_STRT_2_LSB REG(0x00, 0xb4) /* write */ +#define REG_VS_PIX_END_2_MSB REG(0x00, 0xb7) /* write */ +#define REG_VS_PIX_END_2_LSB REG(0x00, 0xb8) /* write */ +#define REG_HS_PIX_START_MSB REG(0x00, 0xb9) /* write */ +#define REG_HS_PIX_START_LSB REG(0x00, 0xba) /* write */ +#define REG_HS_PIX_STOP_MSB REG(0x00, 0xbb) /* write */ +#define REG_HS_PIX_STOP_LSB REG(0x00, 0xbc) /* write */ +#define REG_VWIN_START_1_MSB REG(0x00, 0xbd) /* write */ +#define REG_VWIN_START_1_LSB REG(0x00, 0xbe) /* write */ +#define REG_VWIN_END_1_MSB REG(0x00, 0xbf) /* write */ +#define REG_VWIN_END_1_LSB REG(0x00, 0xc0) /* write */ +#define REG_DE_START_MSB REG(0x00, 0xc5) /* write */ +#define REG_DE_START_LSB REG(0x00, 0xc6) /* write */ +#define REG_DE_STOP_MSB REG(0x00, 0xc7) /* write */ +#define REG_DE_STOP_LSB REG(0x00, 0xc8) /* write */ +#define REG_TBG_CNTRL_0 REG(0x00, 0xca) /* write */ +# define TBG_CNTRL_0_FRAME_DIS (1 << 5) +# define TBG_CNTRL_0_SYNC_MTHD (1 << 6) +# define TBG_CNTRL_0_SYNC_ONCE (1 << 7) +#define REG_TBG_CNTRL_1 REG(0x00, 0xcb) /* write */ +# define TBG_CNTRL_1_VH_TGL_0 (1 << 0) +# define TBG_CNTRL_1_VH_TGL_1 (1 << 1) +# define TBG_CNTRL_1_VH_TGL_2 (1 << 2) +# define TBG_CNTRL_1_VHX_EXT_DE (1 << 3) +# define TBG_CNTRL_1_VHX_EXT_HS (1 << 4) +# define TBG_CNTRL_1_VHX_EXT_VS (1 << 5) +# define TBG_CNTRL_1_DWIN_DIS (1 << 6) +#define REG_ENABLE_SPACE REG(0x00, 0xd6) /* write */ +#define REG_HVF_CNTRL_0 REG(0x00, 0xe4) /* write */ +# define HVF_CNTRL_0_SM (1 << 7) +# define HVF_CNTRL_0_RWB (1 << 6) +# define HVF_CNTRL_0_PREFIL(x) (((x) & 3) << 2) +# define HVF_CNTRL_0_INTPOL(x) (((x) & 3) << 0) +#define REG_HVF_CNTRL_1 REG(0x00, 0xe5) /* write */ +# define HVF_CNTRL_1_FOR (1 << 0) +# define HVF_CNTRL_1_YUVBLK (1 << 1) +# define HVF_CNTRL_1_VQR(x) (((x) & 3) << 2) +# define HVF_CNTRL_1_PAD(x) (((x) & 3) << 4) +# define HVF_CNTRL_1_SEMI_PLANAR (1 << 6) +#define REG_RPT_CNTRL REG(0x00, 0xf0) /* write */ + + +/* Page 02h: PLL settings */ +#define REG_PLL_SERIAL_1 REG(0x02, 0x00) /* read/write */ +# define PLL_SERIAL_1_SRL_FDN (1 << 0) +# define PLL_SERIAL_1_SRL_IZ(x) (((x) & 3) << 1) +# define PLL_SERIAL_1_SRL_MAN_IZ (1 << 6) +#define REG_PLL_SERIAL_2 REG(0x02, 0x01) /* read/write */ +# define PLL_SERIAL_2_SRL_NOSC(x) (((x) & 3) << 0) +# define PLL_SERIAL_2_SRL_PR(x) (((x) & 0xf) << 4) +#define REG_PLL_SERIAL_3 REG(0x02, 0x02) /* read/write */ +# define PLL_SERIAL_3_SRL_CCIR (1 << 0) +# define PLL_SERIAL_3_SRL_DE (1 << 2) +# define PLL_SERIAL_3_SRL_PXIN_SEL (1 << 4) +#define REG_SERIALIZER REG(0x02, 0x03) /* read/write */ +#define REG_BUFFER_OUT REG(0x02, 0x04) /* read/write */ +#define REG_PLL_SCG1 REG(0x02, 0x05) /* read/write */ +#define REG_PLL_SCG2 REG(0x02, 0x06) /* read/write */ +#define REG_PLL_SCGN1 REG(0x02, 0x07) /* read/write */ +#define REG_PLL_SCGN2 REG(0x02, 0x08) /* read/write */ +#define REG_PLL_SCGR1 REG(0x02, 0x09) /* read/write */ +#define REG_PLL_SCGR2 REG(0x02, 0x0a) /* read/write */ +#define REG_AUDIO_DIV REG(0x02, 0x0e) /* read/write */ +#define REG_SEL_CLK REG(0x02, 0x11) /* read/write */ +# define SEL_CLK_SEL_CLK1 (1 << 0) +# define SEL_CLK_SEL_VRF_CLK(x) (((x) & 3) << 1) +# define SEL_CLK_ENA_SC_CLK (1 << 3) +#define REG_ANA_GENERAL REG(0x02, 0x12) /* read/write */ + + +/* Page 09h: EDID Control */ +#define REG_EDID_DATA_0 REG(0x09, 0x00) /* read */ +/* next 127 successive registers are the EDID block */ +#define REG_EDID_CTRL REG(0x09, 0xfa) /* read/write */ +#define REG_DDC_ADDR REG(0x09, 0xfb) /* read/write */ +#define REG_DDC_OFFS REG(0x09, 0xfc) /* read/write */ +#define REG_DDC_SEGM_ADDR REG(0x09, 0xfd) /* read/write */ +#define REG_DDC_SEGM REG(0x09, 0xfe) /* read/write */ + + +/* Page 10h: information frames and packets */ + + +/* Page 11h: audio settings and content info packets */ +#define REG_AIP_CNTRL_0 REG(0x11, 0x00) /* read/write */ +# define AIP_CNTRL_0_RST_FIFO (1 << 0) +# define AIP_CNTRL_0_SWAP (1 << 1) +# define AIP_CNTRL_0_LAYOUT (1 << 2) +# define AIP_CNTRL_0_ACR_MAN (1 << 5) +# define AIP_CNTRL_0_RST_CTS (1 << 6) +#define REG_ENC_CNTRL REG(0x11, 0x0d) /* read/write */ +# define ENC_CNTRL_RST_ENC (1 << 0) +# define ENC_CNTRL_RST_SEL (1 << 1) +# define ENC_CNTRL_CTL_CODE(x) (((x) & 3) << 2) + + +/* Page 12h: HDCP and OTP */ +#define REG_TX3 REG(0x12, 0x9a) /* read/write */ +#define REG_TX33 REG(0x12, 0xb8) /* read/write */ +# define TX33_HDMI (1 << 1) + + +/* Page 13h: Gamut related metadata packets */ + + + +/* CEC registers: (not paged) + */ +#define REG_CEC_FRO_IM_CLK_CTRL 0xfb /* read/write */ +# define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7) +# define CEC_FRO_IM_CLK_CTRL_ENA_OTP (1 << 6) +# define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL (1 << 1) +# define CEC_FRO_IM_CLK_CTRL_FRO_DIV (1 << 0) +#define REG_CEC_RXSHPDLEV 0xfe /* read */ +# define CEC_RXSHPDLEV_RXSENS (1 << 0) +# define CEC_RXSHPDLEV_HPD (1 << 1) + +#define REG_CEC_ENAMODS 0xff /* read/write */ +# define CEC_ENAMODS_DIS_FRO (1 << 6) +# define CEC_ENAMODS_DIS_CCLK (1 << 5) +# define CEC_ENAMODS_EN_RXSENS (1 << 2) +# define CEC_ENAMODS_EN_HDMI (1 << 1) +# define CEC_ENAMODS_EN_CEC (1 << 0) + + +/* Device versions: */ +#define TDA9989N2 0x0101 +#define TDA19989 0x0201 +#define TDA19989N2 0x0202 +#define TDA19988 0x0301 + +static void +cec_write(struct drm_encoder *encoder, uint16_t addr, uint8_t val) +{ + struct i2c_client *client = to_tda998x_priv(encoder)->cec; + uint8_t buf[] = {addr, val}; + int ret; + + ret = i2c_master_send(client, buf, ARRAY_SIZE(buf)); + if (ret < 0) + dev_err(&client->dev, "Error %d writing to cec:0x%x\n", ret, addr); +} + +static uint8_t +cec_read(struct drm_encoder *encoder, uint8_t addr) +{ + struct i2c_client *client = to_tda998x_priv(encoder)->cec; + uint8_t val; + int ret; + + ret = i2c_master_send(client, &addr, sizeof(addr)); + if (ret < 0) + goto fail; + + ret = i2c_master_recv(client, &val, sizeof(val)); + if (ret < 0) + goto fail; + + return val; + +fail: + dev_err(&client->dev, "Error %d reading from cec:0x%x\n", ret, addr); + return 0; +} + +static void +set_page(struct drm_encoder *encoder, uint16_t reg) +{ + struct tda998x_priv *priv = to_tda998x_priv(encoder); + + if (REG2PAGE(reg) != priv->current_page) { + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + uint8_t buf[] = { + REG_CURPAGE, REG2PAGE(reg) + }; + int ret = i2c_master_send(client, buf, sizeof(buf)); + if (ret < 0) + dev_err(&client->dev, "Error %d writing to REG_CURPAGE\n", ret); + + priv->current_page = REG2PAGE(reg); + } +} + +static int +reg_read_range(struct drm_encoder *encoder, uint16_t reg, char *buf, int cnt) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + uint8_t addr = REG2ADDR(reg); + int ret; + + set_page(encoder, reg); + + ret = i2c_master_send(client, &addr, sizeof(addr)); + if (ret < 0) + goto fail; + + ret = i2c_master_recv(client, buf, cnt); + if (ret < 0) + goto fail; + + return ret; + +fail: + dev_err(&client->dev, "Error %d reading from 0x%x\n", ret, reg); + return ret; +} + +static uint8_t +reg_read(struct drm_encoder *encoder, uint16_t reg) +{ + uint8_t val = 0; + reg_read_range(encoder, reg, &val, sizeof(val)); + return val; +} + +static void +reg_write(struct drm_encoder *encoder, uint16_t reg, uint8_t val) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + uint8_t buf[] = {REG2ADDR(reg), val}; + int ret; + + set_page(encoder, reg); + + ret = i2c_master_send(client, buf, ARRAY_SIZE(buf)); + if (ret < 0) + dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); +} + +static void +reg_write16(struct drm_encoder *encoder, uint16_t reg, uint16_t val) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + uint8_t buf[] = {REG2ADDR(reg), val >> 8, val}; + int ret; + + set_page(encoder, reg); + + ret = i2c_master_send(client, buf, ARRAY_SIZE(buf)); + if (ret < 0) + dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); +} + +static void +reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val) +{ + reg_write(encoder, reg, reg_read(encoder, reg) | val); +} + +static void +reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val) +{ + reg_write(encoder, reg, reg_read(encoder, reg) & ~val); +} + +static void +tda998x_reset(struct drm_encoder *encoder) +{ + /* reset audio and i2c master: */ + reg_set(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER); + msleep(50); + reg_clear(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER); + msleep(50); + + /* reset transmitter: */ + reg_set(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR); + reg_clear(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR); + + /* PLL registers common configuration */ + reg_write(encoder, REG_PLL_SERIAL_1, 0x00); + reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1)); + reg_write(encoder, REG_PLL_SERIAL_3, 0x00); + reg_write(encoder, REG_SERIALIZER, 0x00); + reg_write(encoder, REG_BUFFER_OUT, 0x00); + reg_write(encoder, REG_PLL_SCG1, 0x00); + reg_write(encoder, REG_AUDIO_DIV, 0x03); + reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); + reg_write(encoder, REG_PLL_SCGN1, 0xfa); + reg_write(encoder, REG_PLL_SCGN2, 0x00); + reg_write(encoder, REG_PLL_SCGR1, 0x5b); + reg_write(encoder, REG_PLL_SCGR2, 0x00); + reg_write(encoder, REG_PLL_SCG2, 0x10); +} + +/* DRM encoder functions */ + +static void +tda998x_encoder_set_config(struct drm_encoder *encoder, void *params) +{ +} + +static void +tda998x_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct tda998x_priv *priv = to_tda998x_priv(encoder); + + /* we only care about on or off: */ + if (mode != DRM_MODE_DPMS_ON) + mode = DRM_MODE_DPMS_OFF; + + if (mode == priv->dpms) + return; + + switch (mode) { + case DRM_MODE_DPMS_ON: + /* enable audio and video ports */ + reg_write(encoder, REG_ENA_AP, 0xff); + reg_write(encoder, REG_ENA_VP_0, 0xff); + reg_write(encoder, REG_ENA_VP_1, 0xff); + reg_write(encoder, REG_ENA_VP_2, 0xff); + /* set muxing after enabling ports: */ + reg_write(encoder, REG_VIP_CNTRL_0, + VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3)); + reg_write(encoder, REG_VIP_CNTRL_1, + VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1)); + reg_write(encoder, REG_VIP_CNTRL_2, + VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5)); + break; + case DRM_MODE_DPMS_OFF: + /* disable audio and video ports */ + reg_write(encoder, REG_ENA_AP, 0x00); + reg_write(encoder, REG_ENA_VP_0, 0x00); + reg_write(encoder, REG_ENA_VP_1, 0x00); + reg_write(encoder, REG_ENA_VP_2, 0x00); + break; + } + + priv->dpms = mode; +} + +static void +tda998x_encoder_save(struct drm_encoder *encoder) +{ + DBG(""); +} + +static void +tda998x_encoder_restore(struct drm_encoder *encoder) +{ + DBG(""); +} + +static bool +tda998x_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int +tda998x_encoder_mode_valid(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void +tda998x_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct tda998x_priv *priv = to_tda998x_priv(encoder); + uint16_t hs_start, hs_end, line_start, line_end; + uint16_t vwin_start, vwin_end, de_start, de_end; + uint16_t ref_pix, ref_line, pix_start2; + uint8_t reg, div, rep; + + hs_start = mode->hsync_start - mode->hdisplay; + hs_end = mode->hsync_end - mode->hdisplay; + line_start = 1; + line_end = 1 + mode->vsync_end - mode->vsync_start; + vwin_start = mode->vtotal - mode->vsync_start; + vwin_end = vwin_start + mode->vdisplay; + de_start = mode->htotal - mode->hdisplay; + de_end = mode->htotal; + + pix_start2 = 0; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + pix_start2 = (mode->htotal / 2) + hs_start; + + /* TODO how is this value calculated? It is 2 for all common + * formats in the tables in out of tree nxp driver (assuming + * I've properly deciphered their byzantine table system) + */ + ref_line = 2; + + /* this might changes for other color formats from the CRTC: */ + ref_pix = 3 + hs_start; + + div = 148500 / mode->clock; + + DBG("clock=%d, div=%u", mode->clock, div); + DBG("hs_start=%u, hs_end=%u, line_start=%u, line_end=%u", + hs_start, hs_end, line_start, line_end); + DBG("vwin_start=%u, vwin_end=%u, de_start=%u, de_end=%u", + vwin_start, vwin_end, de_start, de_end); + DBG("ref_line=%u, ref_pix=%u, pix_start2=%u", + ref_line, ref_pix, pix_start2); + + /* mute the audio FIFO: */ + reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO); + + /* set HDMI HDCP mode off: */ + reg_set(encoder, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS); + reg_clear(encoder, REG_TX33, TX33_HDMI); + + reg_write(encoder, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0)); + /* no pre-filter or interpolator: */ + reg_write(encoder, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) | + HVF_CNTRL_0_INTPOL(0)); + reg_write(encoder, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0)); + reg_write(encoder, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) | + VIP_CNTRL_4_BLC(0)); + reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR); + + reg_clear(encoder, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ); + reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_DE); + reg_write(encoder, REG_SERIALIZER, 0); + reg_write(encoder, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0)); + + /* TODO enable pixel repeat for pixel rates less than 25Msamp/s */ + rep = 0; + reg_write(encoder, REG_RPT_CNTRL, 0); + reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) | + SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK); + + reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) | + PLL_SERIAL_2_SRL_PR(rep)); + + reg_write16(encoder, REG_VS_PIX_STRT_2_MSB, pix_start2); + reg_write16(encoder, REG_VS_PIX_END_2_MSB, pix_start2); + + /* set color matrix bypass flag: */ + reg_set(encoder, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP); + + /* set BIAS tmds value: */ + reg_write(encoder, REG_ANA_GENERAL, 0x09); + + reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_MTHD); + + reg_write(encoder, REG_VIP_CNTRL_3, 0); + reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_SYNC_HS); + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_V_TGL); + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_H_TGL); + + reg_write(encoder, REG_VIDFORMAT, 0x00); + reg_write16(encoder, REG_NPIX_MSB, mode->hdisplay - 1); + reg_write16(encoder, REG_NLINE_MSB, mode->vdisplay - 1); + reg_write16(encoder, REG_VS_LINE_STRT_1_MSB, line_start); + reg_write16(encoder, REG_VS_LINE_END_1_MSB, line_end); + reg_write16(encoder, REG_VS_PIX_STRT_1_MSB, hs_start); + reg_write16(encoder, REG_VS_PIX_END_1_MSB, hs_start); + reg_write16(encoder, REG_HS_PIX_START_MSB, hs_start); + reg_write16(encoder, REG_HS_PIX_STOP_MSB, hs_end); + reg_write16(encoder, REG_VWIN_START_1_MSB, vwin_start); + reg_write16(encoder, REG_VWIN_END_1_MSB, vwin_end); + reg_write16(encoder, REG_DE_START_MSB, de_start); + reg_write16(encoder, REG_DE_STOP_MSB, de_end); + + if (priv->rev == TDA19988) { + /* let incoming pixels fill the active space (if any) */ + reg_write(encoder, REG_ENABLE_SPACE, 0x01); + } + + reg_write16(encoder, REG_REFPIX_MSB, ref_pix); + reg_write16(encoder, REG_REFLINE_MSB, ref_line); + + reg = TBG_CNTRL_1_VHX_EXT_DE | + TBG_CNTRL_1_VHX_EXT_HS | + TBG_CNTRL_1_VHX_EXT_VS | + TBG_CNTRL_1_DWIN_DIS | /* HDCP off */ + TBG_CNTRL_1_VH_TGL_2; + if (mode->flags & (DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC)) + reg |= TBG_CNTRL_1_VH_TGL_0; + reg_set(encoder, REG_TBG_CNTRL_1, reg); + + /* must be last register set: */ + reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE); +} + +static enum drm_connector_status +tda998x_encoder_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + uint8_t val = cec_read(encoder, REG_CEC_RXSHPDLEV); + return (val & CEC_RXSHPDLEV_HPD) ? connector_status_connected : + connector_status_disconnected; +} + +static int +read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk) +{ + uint8_t offset, segptr; + int ret, i; + + /* enable EDID read irq: */ + reg_set(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + + offset = (blk & 1) ? 128 : 0; + segptr = blk / 2; + + reg_write(encoder, REG_DDC_ADDR, 0xa0); + reg_write(encoder, REG_DDC_OFFS, offset); + reg_write(encoder, REG_DDC_SEGM_ADDR, 0x60); + reg_write(encoder, REG_DDC_SEGM, segptr); + + /* enable reading EDID: */ + reg_write(encoder, REG_EDID_CTRL, 0x1); + + /* flag must be cleared by sw: */ + reg_write(encoder, REG_EDID_CTRL, 0x0); + + /* wait for block read to complete: */ + for (i = 100; i > 0; i--) { + uint8_t val = reg_read(encoder, REG_INT_FLAGS_2); + if (val & INT_FLAGS_2_EDID_BLK_RD) + break; + msleep(1); + } + + if (i == 0) + return -ETIMEDOUT; + + ret = reg_read_range(encoder, REG_EDID_DATA_0, buf, EDID_LENGTH); + if (ret != EDID_LENGTH) { + dev_err(encoder->dev->dev, "failed to read edid block %d: %d", + blk, ret); + return ret; + } + + reg_clear(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + + return 0; +} + +static uint8_t * +do_get_edid(struct drm_encoder *encoder) +{ + int j = 0, valid_extensions = 0; + uint8_t *block, *new; + bool print_bad_edid = drm_debug & DRM_UT_KMS; + + if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL) + return NULL; + + /* base block fetch */ + if (read_edid_block(encoder, block, 0)) + goto fail; + + if (!drm_edid_block_valid(block, 0, print_bad_edid)) + goto fail; + + /* if there's no extensions, we're done */ + if (block[0x7e] == 0) + return block; + + new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL); + if (!new) + goto fail; + block = new; + + for (j = 1; j <= block[0x7e]; j++) { + uint8_t *ext_block = block + (valid_extensions + 1) * EDID_LENGTH; + if (read_edid_block(encoder, ext_block, j)) + goto fail; + + if (!drm_edid_block_valid(ext_block, j, print_bad_edid)) + goto fail; + + valid_extensions++; + } + + if (valid_extensions != block[0x7e]) { + block[EDID_LENGTH-1] += block[0x7e] - valid_extensions; + block[0x7e] = valid_extensions; + new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL); + if (!new) + goto fail; + block = new; + } + + return block; + +fail: + dev_warn(encoder->dev->dev, "failed to read EDID\n"); + kfree(block); + return NULL; +} + +static int +tda998x_encoder_get_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct edid *edid = (struct edid *)do_get_edid(encoder); + int n = 0; + + if (edid) { + drm_mode_connector_update_edid_property(connector, edid); + n = drm_add_edid_modes(connector, edid); + kfree(edid); + } + + return n; +} + +static int +tda998x_encoder_create_resources(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + DBG(""); + return 0; +} + +static int +tda998x_encoder_set_property(struct drm_encoder *encoder, + struct drm_connector *connector, + struct drm_property *property, + uint64_t val) +{ + DBG(""); + return 0; +} + +static void +tda998x_encoder_destroy(struct drm_encoder *encoder) +{ + struct tda998x_priv *priv = to_tda998x_priv(encoder); + drm_i2c_encoder_destroy(encoder); + kfree(priv); +} + +static struct drm_encoder_slave_funcs tda998x_encoder_funcs = { + .set_config = tda998x_encoder_set_config, + .destroy = tda998x_encoder_destroy, + .dpms = tda998x_encoder_dpms, + .save = tda998x_encoder_save, + .restore = tda998x_encoder_restore, + .mode_fixup = tda998x_encoder_mode_fixup, + .mode_valid = tda998x_encoder_mode_valid, + .mode_set = tda998x_encoder_mode_set, + .detect = tda998x_encoder_detect, + .get_modes = tda998x_encoder_get_modes, + .create_resources = tda998x_encoder_create_resources, + .set_property = tda998x_encoder_set_property, +}; + +/* I2C driver functions */ + +static int +tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + return 0; +} + +static int +tda998x_remove(struct i2c_client *client) +{ + return 0; +} + +static int +tda998x_encoder_init(struct i2c_client *client, + struct drm_device *dev, + struct drm_encoder_slave *encoder_slave) +{ + struct drm_encoder *encoder = &encoder_slave->base; + struct tda998x_priv *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->current_page = 0; + priv->cec = i2c_new_dummy(client->adapter, 0x34); + priv->dpms = DRM_MODE_DPMS_OFF; + + encoder_slave->slave_priv = priv; + encoder_slave->slave_funcs = &tda998x_encoder_funcs; + + /* wake up the device: */ + cec_write(encoder, REG_CEC_ENAMODS, + CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI); + + tda998x_reset(encoder); + + /* read version: */ + priv->rev = reg_read(encoder, REG_VERSION_LSB) | + reg_read(encoder, REG_VERSION_MSB) << 8; + + /* mask off feature bits: */ + priv->rev &= ~0x30; /* not-hdcp and not-scalar bit */ + + switch (priv->rev) { + case TDA9989N2: dev_info(dev->dev, "found TDA9989 n2"); break; + case TDA19989: dev_info(dev->dev, "found TDA19989"); break; + case TDA19989N2: dev_info(dev->dev, "found TDA19989 n2"); break; + case TDA19988: dev_info(dev->dev, "found TDA19988"); break; + default: + DBG("found unsupported device: %04x", priv->rev); + goto fail; + } + + /* after reset, enable DDC: */ + reg_write(encoder, REG_DDC_DISABLE, 0x00); + + /* set clock on DDC channel: */ + reg_write(encoder, REG_TX3, 39); + + /* if necessary, disable multi-master: */ + if (priv->rev == TDA19989) + reg_set(encoder, REG_I2C_MASTER, I2C_MASTER_DIS_MM); + + cec_write(encoder, REG_CEC_FRO_IM_CLK_CTRL, + CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL); + + return 0; + +fail: + /* if encoder_init fails, the encoder slave is never registered, + * so cleanup here: + */ + if (priv->cec) + i2c_unregister_device(priv->cec); + kfree(priv); + encoder_slave->slave_priv = NULL; + encoder_slave->slave_funcs = NULL; + return -ENXIO; +} + +static struct i2c_device_id tda998x_ids[] = { + { "tda998x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tda998x_ids); + +static struct drm_i2c_encoder_driver tda998x_driver = { + .i2c_driver = { + .probe = tda998x_probe, + .remove = tda998x_remove, + .driver = { + .name = "tda998x", + }, + .id_table = tda998x_ids, + }, + .encoder_init = tda998x_encoder_init, +}; + +/* Module initialization */ + +static int __init +tda998x_init(void) +{ + DBG(""); + return drm_i2c_encoder_register(THIS_MODULE, &tda998x_driver); +} + +static void __exit +tda998x_exit(void) +{ + DBG(""); + drm_i2c_encoder_unregister(&tda998x_driver); +} + +MODULE_AUTHOR("Rob Clark <robdclark@gmail.com"); +MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder"); +MODULE_LICENSE("GPL"); + +module_init(tda998x_init); +module_exit(tda998x_exit); diff --git a/drivers/gpu/drm/i915/intel_crt.c b/drivers/gpu/drm/i915/intel_crt.c index cfc96878d742..969d08c72d10 100644 --- a/drivers/gpu/drm/i915/intel_crt.c +++ b/drivers/gpu/drm/i915/intel_crt.c @@ -685,7 +685,6 @@ static void intel_crt_reset(struct drm_connector *connector) static const struct drm_encoder_helper_funcs crt_encoder_funcs = { .mode_fixup = intel_crt_mode_fixup, .mode_set = intel_crt_mode_set, - .disable = intel_encoder_noop, }; static const struct drm_connector_funcs intel_crt_connector_funcs = { diff --git a/drivers/gpu/drm/i915/intel_ddi.c b/drivers/gpu/drm/i915/intel_ddi.c index 816c45c71b72..d64af5aa4a1c 100644 --- a/drivers/gpu/drm/i915/intel_ddi.c +++ b/drivers/gpu/drm/i915/intel_ddi.c @@ -1489,7 +1489,6 @@ static const struct drm_encoder_funcs intel_ddi_funcs = { static const struct drm_encoder_helper_funcs intel_ddi_helper_funcs = { .mode_fixup = intel_ddi_mode_fixup, .mode_set = intel_ddi_mode_set, - .disable = intel_encoder_noop, }; void intel_ddi_init(struct drm_device *dev, enum port port) diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 0ff10b3af9ea..a05ac2c91ba2 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -3738,10 +3738,6 @@ void intel_crtc_update_dpms(struct drm_crtc *crtc) intel_crtc_update_sarea(crtc, enable); } -static void intel_crtc_noop(struct drm_crtc *crtc) -{ -} - static void intel_crtc_disable(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; @@ -3790,10 +3786,6 @@ void intel_modeset_disable(struct drm_device *dev) } } -void intel_encoder_noop(struct drm_encoder *encoder) -{ -} - void intel_encoder_destroy(struct drm_encoder *encoder) { struct intel_encoder *intel_encoder = to_intel_encoder(encoder); @@ -7367,7 +7359,6 @@ free_work: static struct drm_crtc_helper_funcs intel_helper_funcs = { .mode_set_base_atomic = intel_pipe_set_base_atomic, .load_lut = intel_crtc_load_lut, - .disable = intel_crtc_noop, }; bool intel_encoder_check_is_cloned(struct intel_encoder *encoder) @@ -8077,14 +8068,9 @@ static int intel_crtc_set_config(struct drm_mode_set *set) BUG_ON(!set->crtc); BUG_ON(!set->crtc->helper_private); - if (!set->mode) - set->fb = NULL; - - /* The fb helper likes to play gross jokes with ->mode_set_config. - * Unfortunately the crtc helper doesn't do much at all for this case, - * so we have to cope with this madness until the fb helper is fixed up. */ - if (set->fb && set->num_connectors == 0) - return 0; + /* Enforce sane interface api - has been abused by the fb helper. */ + BUG_ON(!set->mode && set->fb); + BUG_ON(set->fb && set->num_connectors == 0); if (set->fb) { DRM_DEBUG_KMS("[CRTC:%d] [FB:%d] #connectors=%d (x y) (%i %i)\n", diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index 7b8bfe8982e6..31c0205685ab 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -2561,7 +2561,6 @@ void intel_dp_encoder_destroy(struct drm_encoder *encoder) static const struct drm_encoder_helper_funcs intel_dp_helper_funcs = { .mode_fixup = intel_dp_mode_fixup, .mode_set = intel_dp_mode_set, - .disable = intel_encoder_noop, }; static const struct drm_connector_funcs intel_dp_connector_funcs = { diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index cba02619ec0f..07ebac6fe8ca 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -525,7 +525,6 @@ extern void intel_modeset_disable(struct drm_device *dev); extern void intel_crtc_restore_mode(struct drm_crtc *crtc); extern void intel_crtc_load_lut(struct drm_crtc *crtc); extern void intel_crtc_update_dpms(struct drm_crtc *crtc); -extern void intel_encoder_noop(struct drm_encoder *encoder); extern void intel_encoder_destroy(struct drm_encoder *encoder); extern void intel_encoder_dpms(struct intel_encoder *encoder, int mode); extern bool intel_encoder_check_is_cloned(struct intel_encoder *encoder); diff --git a/drivers/gpu/drm/i915/intel_dvo.c b/drivers/gpu/drm/i915/intel_dvo.c index 15da99533e5b..00e70dbe82da 100644 --- a/drivers/gpu/drm/i915/intel_dvo.c +++ b/drivers/gpu/drm/i915/intel_dvo.c @@ -345,7 +345,6 @@ static void intel_dvo_destroy(struct drm_connector *connector) static const struct drm_encoder_helper_funcs intel_dvo_helper_funcs = { .mode_fixup = intel_dvo_mode_fixup, .mode_set = intel_dvo_mode_set, - .disable = intel_encoder_noop, }; static const struct drm_connector_funcs intel_dvo_connector_funcs = { diff --git a/drivers/gpu/drm/i915/intel_fb.c b/drivers/gpu/drm/i915/intel_fb.c index 1c510da04d16..981bdce3634e 100644 --- a/drivers/gpu/drm/i915/intel_fb.c +++ b/drivers/gpu/drm/i915/intel_fb.c @@ -57,9 +57,10 @@ static struct fb_ops intelfb_ops = { .fb_debug_leave = drm_fb_helper_debug_leave, }; -static int intelfb_create(struct intel_fbdev *ifbdev, +static int intelfb_create(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { + struct intel_fbdev *ifbdev = (struct intel_fbdev *)helper; struct drm_device *dev = ifbdev->helper.dev; struct drm_i915_private *dev_priv = dev->dev_private; struct fb_info *info; @@ -181,26 +182,10 @@ out: return ret; } -static int intel_fb_find_or_create_single(struct drm_fb_helper *helper, - struct drm_fb_helper_surface_size *sizes) -{ - struct intel_fbdev *ifbdev = (struct intel_fbdev *)helper; - int new_fb = 0; - int ret; - - if (!helper->fb) { - ret = intelfb_create(ifbdev, sizes); - if (ret) - return ret; - new_fb = 1; - } - return new_fb; -} - static struct drm_fb_helper_funcs intel_fb_helper_funcs = { .gamma_set = intel_crtc_fb_gamma_set, .gamma_get = intel_crtc_fb_gamma_get, - .fb_probe = intel_fb_find_or_create_single, + .fb_probe = intelfb_create, }; static void intel_fbdev_destroy(struct drm_device *dev, diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c index 3647d03f21ce..83d66602414b 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c @@ -943,7 +943,6 @@ static void intel_hdmi_destroy(struct drm_connector *connector) static const struct drm_encoder_helper_funcs intel_hdmi_helper_funcs = { .mode_fixup = intel_hdmi_mode_fixup, .mode_set = intel_hdmi_mode_set, - .disable = intel_encoder_noop, }; static const struct drm_connector_funcs intel_hdmi_connector_funcs = { diff --git a/drivers/gpu/drm/i915/intel_lvds.c b/drivers/gpu/drm/i915/intel_lvds.c index c7154bfa54cf..3d1d97488cc9 100644 --- a/drivers/gpu/drm/i915/intel_lvds.c +++ b/drivers/gpu/drm/i915/intel_lvds.c @@ -663,7 +663,6 @@ static int intel_lvds_set_property(struct drm_connector *connector, static const struct drm_encoder_helper_funcs intel_lvds_helper_funcs = { .mode_fixup = intel_lvds_mode_fixup, .mode_set = intel_lvds_mode_set, - .disable = intel_encoder_noop, }; static const struct drm_connector_helper_funcs intel_lvds_connector_helper_funcs = { diff --git a/drivers/gpu/drm/i915/intel_sdvo.c b/drivers/gpu/drm/i915/intel_sdvo.c index f01063a2323a..33b46d9694ea 100644 --- a/drivers/gpu/drm/i915/intel_sdvo.c +++ b/drivers/gpu/drm/i915/intel_sdvo.c @@ -2043,7 +2043,6 @@ done: static const struct drm_encoder_helper_funcs intel_sdvo_helper_funcs = { .mode_fixup = intel_sdvo_mode_fixup, .mode_set = intel_sdvo_mode_set, - .disable = intel_encoder_noop, }; static const struct drm_connector_funcs intel_sdvo_connector_funcs = { diff --git a/drivers/gpu/drm/i915/intel_tv.c b/drivers/gpu/drm/i915/intel_tv.c index 984a113c5d13..d808421c1c80 100644 --- a/drivers/gpu/drm/i915/intel_tv.c +++ b/drivers/gpu/drm/i915/intel_tv.c @@ -1487,7 +1487,6 @@ out: static const struct drm_encoder_helper_funcs intel_tv_helper_funcs = { .mode_fixup = intel_tv_mode_fixup, .mode_set = intel_tv_mode_set, - .disable = intel_encoder_noop, }; static const struct drm_connector_funcs intel_tv_connector_funcs = { diff --git a/drivers/gpu/drm/mgag200/mgag200_fb.c b/drivers/gpu/drm/mgag200/mgag200_fb.c index 5c69b432f99a..d2253f639481 100644 --- a/drivers/gpu/drm/mgag200/mgag200_fb.c +++ b/drivers/gpu/drm/mgag200/mgag200_fb.c @@ -13,6 +13,7 @@ #include <linux/module.h> #include <drm/drmP.h> #include <drm/drm_fb_helper.h> +#include <drm/drm_crtc_helper.h> #include <linux/fb.h> @@ -120,9 +121,10 @@ static int mgag200fb_create_object(struct mga_fbdev *afbdev, return ret; } -static int mgag200fb_create(struct mga_fbdev *mfbdev, +static int mgag200fb_create(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { + struct mga_fbdev *mfbdev = (struct mga_fbdev *)helper; struct drm_device *dev = mfbdev->helper.dev; struct drm_mode_fb_cmd2 mode_cmd; struct mga_device *mdev = dev->dev_private; @@ -209,23 +211,6 @@ out: return ret; } -static int mga_fb_find_or_create_single(struct drm_fb_helper *helper, - struct drm_fb_helper_surface_size - *sizes) -{ - struct mga_fbdev *mfbdev = (struct mga_fbdev *)helper; - int new_fb = 0; - int ret; - - if (!helper->fb) { - ret = mgag200fb_create(mfbdev, sizes); - if (ret) - return ret; - new_fb = 1; - } - return new_fb; -} - static int mga_fbdev_destroy(struct drm_device *dev, struct mga_fbdev *mfbdev) { @@ -256,7 +241,7 @@ static int mga_fbdev_destroy(struct drm_device *dev, static struct drm_fb_helper_funcs mga_fb_helper_funcs = { .gamma_set = mga_crtc_fb_gamma_set, .gamma_get = mga_crtc_fb_gamma_get, - .fb_probe = mga_fb_find_or_create_single, + .fb_probe = mgag200fb_create, }; int mgag200_fbdev_init(struct mga_device *mdev) @@ -278,6 +263,10 @@ int mgag200_fbdev_init(struct mga_device *mdev) return ret; } drm_fb_helper_single_add_all_connectors(&mfbdev->helper); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(mdev->dev); + drm_fb_helper_initial_config(&mfbdev->helper, 32); return 0; diff --git a/drivers/gpu/drm/nouveau/Kconfig b/drivers/gpu/drm/nouveau/Kconfig index 47ccc1ad5405..a7ff6d5a34b9 100644 --- a/drivers/gpu/drm/nouveau/Kconfig +++ b/drivers/gpu/drm/nouveau/Kconfig @@ -11,8 +11,9 @@ config DRM_NOUVEAU select FRAMEBUFFER_CONSOLE if !EXPERT select FB_BACKLIGHT if DRM_NOUVEAU_BACKLIGHT select ACPI_VIDEO if ACPI && X86 && BACKLIGHT_CLASS_DEVICE && VIDEO_OUTPUT_CONTROL && INPUT - select ACPI_WMI if ACPI - select MXM_WMI if ACPI + select X86_PLATFORM_DEVICES if ACPI && X86 + select ACPI_WMI if ACPI && X86 + select MXM_WMI if ACPI && X86 select POWER_SUPPLY help Choose this option for open-source nVidia support. diff --git a/drivers/gpu/drm/nouveau/Makefile b/drivers/gpu/drm/nouveau/Makefile index ab25752a0b1e..90f9140eeefd 100644 --- a/drivers/gpu/drm/nouveau/Makefile +++ b/drivers/gpu/drm/nouveau/Makefile @@ -11,6 +11,7 @@ nouveau-y := core/core/client.o nouveau-y += core/core/engctx.o nouveau-y += core/core/engine.o nouveau-y += core/core/enum.o +nouveau-y += core/core/event.o nouveau-y += core/core/falcon.o nouveau-y += core/core/gpuobj.o nouveau-y += core/core/handle.o @@ -40,6 +41,11 @@ nouveau-y += core/subdev/bios/mxm.o nouveau-y += core/subdev/bios/perf.o nouveau-y += core/subdev/bios/pll.o nouveau-y += core/subdev/bios/therm.o +nouveau-y += core/subdev/bios/xpio.o +nouveau-y += core/subdev/bus/nv04.o +nouveau-y += core/subdev/bus/nv31.o +nouveau-y += core/subdev/bus/nv50.o +nouveau-y += core/subdev/bus/nvc0.o nouveau-y += core/subdev/clock/nv04.o nouveau-y += core/subdev/clock/nv40.o nouveau-y += core/subdev/clock/nv50.o @@ -85,9 +91,16 @@ nouveau-y += core/subdev/gpio/base.o nouveau-y += core/subdev/gpio/nv10.o nouveau-y += core/subdev/gpio/nv50.o nouveau-y += core/subdev/gpio/nvd0.o +nouveau-y += core/subdev/gpio/nve0.o nouveau-y += core/subdev/i2c/base.o +nouveau-y += core/subdev/i2c/anx9805.o nouveau-y += core/subdev/i2c/aux.o nouveau-y += core/subdev/i2c/bit.o +nouveau-y += core/subdev/i2c/nv04.o +nouveau-y += core/subdev/i2c/nv4e.o +nouveau-y += core/subdev/i2c/nv50.o +nouveau-y += core/subdev/i2c/nv94.o +nouveau-y += core/subdev/i2c/nvd0.o nouveau-y += core/subdev/ibus/nvc0.o nouveau-y += core/subdev/ibus/nve0.o nouveau-y += core/subdev/instmem/base.o @@ -106,10 +119,15 @@ nouveau-y += core/subdev/mxm/mxms.o nouveau-y += core/subdev/mxm/nv50.o nouveau-y += core/subdev/therm/base.o nouveau-y += core/subdev/therm/fan.o +nouveau-y += core/subdev/therm/fannil.o +nouveau-y += core/subdev/therm/fanpwm.o +nouveau-y += core/subdev/therm/fantog.o nouveau-y += core/subdev/therm/ic.o +nouveau-y += core/subdev/therm/temp.o nouveau-y += core/subdev/therm/nv40.o nouveau-y += core/subdev/therm/nv50.o -nouveau-y += core/subdev/therm/temp.o +nouveau-y += core/subdev/therm/nva3.o +nouveau-y += core/subdev/therm/nvd0.o nouveau-y += core/subdev/timer/base.o nouveau-y += core/subdev/timer/nv04.o nouveau-y += core/subdev/vm/base.o @@ -132,6 +150,7 @@ nouveau-y += core/engine/copy/nvc0.o nouveau-y += core/engine/copy/nve0.o nouveau-y += core/engine/crypt/nv84.o nouveau-y += core/engine/crypt/nv98.o +nouveau-y += core/engine/disp/base.o nouveau-y += core/engine/disp/nv04.o nouveau-y += core/engine/disp/nv50.o nouveau-y += core/engine/disp/nv84.o @@ -141,11 +160,13 @@ nouveau-y += core/engine/disp/nva3.o nouveau-y += core/engine/disp/nvd0.o nouveau-y += core/engine/disp/nve0.o nouveau-y += core/engine/disp/dacnv50.o +nouveau-y += core/engine/disp/dport.o nouveau-y += core/engine/disp/hdanva3.o nouveau-y += core/engine/disp/hdanvd0.o nouveau-y += core/engine/disp/hdminv84.o nouveau-y += core/engine/disp/hdminva3.o nouveau-y += core/engine/disp/hdminvd0.o +nouveau-y += core/engine/disp/piornv50.o nouveau-y += core/engine/disp/sornv50.o nouveau-y += core/engine/disp/sornv94.o nouveau-y += core/engine/disp/sornvd0.o @@ -194,7 +215,8 @@ nouveau-y += nouveau_drm.o nouveau_chan.o nouveau_dma.o nouveau_fence.o nouveau-y += nouveau_irq.o nouveau_vga.o nouveau_agp.o nouveau-y += nouveau_ttm.o nouveau_sgdma.o nouveau_bo.o nouveau_gem.o nouveau-y += nouveau_prime.o nouveau_abi16.o -nouveau-y += nv04_fence.o nv10_fence.o nv50_fence.o nv84_fence.o nvc0_fence.o +nouveau-y += nv04_fence.o nv10_fence.o nv17_fence.o +nouveau-y += nv50_fence.o nv84_fence.o nvc0_fence.o # drm/kms nouveau-y += nouveau_bios.o nouveau_fbcon.o nouveau_display.o @@ -216,7 +238,10 @@ nouveau-y += nouveau_mem.o # other random bits nouveau-$(CONFIG_COMPAT) += nouveau_ioc32.o +ifdef CONFIG_X86 nouveau-$(CONFIG_ACPI) += nouveau_acpi.o +endif nouveau-$(CONFIG_DRM_NOUVEAU_BACKLIGHT) += nouveau_backlight.o +nouveau-$(CONFIG_DEBUG_FS) += nouveau_debugfs.o obj-$(CONFIG_DRM_NOUVEAU)+= nouveau.o diff --git a/drivers/gpu/drm/nouveau/core/core/client.c b/drivers/gpu/drm/nouveau/core/core/client.c index 8bbb58f94a19..295c22165eac 100644 --- a/drivers/gpu/drm/nouveau/core/core/client.c +++ b/drivers/gpu/drm/nouveau/core/core/client.c @@ -99,3 +99,13 @@ nouveau_client_fini(struct nouveau_client *client, bool suspend) nv_debug(client, "%s completed with %d\n", name[suspend], ret); return ret; } + +const char * +nouveau_client_name(void *obj) +{ + const char *client_name = "unknown"; + struct nouveau_client *client = nouveau_client(obj); + if (client) + client_name = client->name; + return client_name; +} diff --git a/drivers/gpu/drm/nouveau/core/core/enum.c b/drivers/gpu/drm/nouveau/core/core/enum.c index 7cc7133d82de..dd434790ccc4 100644 --- a/drivers/gpu/drm/nouveau/core/core/enum.c +++ b/drivers/gpu/drm/nouveau/core/core/enum.c @@ -40,14 +40,15 @@ nouveau_enum_find(const struct nouveau_enum *en, u32 value) return NULL; } -void +const struct nouveau_enum * nouveau_enum_print(const struct nouveau_enum *en, u32 value) { en = nouveau_enum_find(en, value); if (en) - printk("%s", en->name); + pr_cont("%s", en->name); else - printk("(unknown enum 0x%08x)", value); + pr_cont("(unknown enum 0x%08x)", value); + return en; } void @@ -55,7 +56,7 @@ nouveau_bitfield_print(const struct nouveau_bitfield *bf, u32 value) { while (bf->name) { if (value & bf->mask) { - printk(" %s", bf->name); + pr_cont(" %s", bf->name); value &= ~bf->mask; } @@ -63,5 +64,5 @@ nouveau_bitfield_print(const struct nouveau_bitfield *bf, u32 value) } if (value) - printk(" (unknown bits 0x%08x)", value); + pr_cont(" (unknown bits 0x%08x)", value); } diff --git a/drivers/gpu/drm/nouveau/core/core/event.c b/drivers/gpu/drm/nouveau/core/core/event.c new file mode 100644 index 000000000000..6d01e0f0fc8a --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/core/event.c @@ -0,0 +1,106 @@ +/* + * Copyright 2013 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <core/os.h> +#include <core/event.h> + +static void +nouveau_event_put_locked(struct nouveau_event *event, int index, + struct nouveau_eventh *handler) +{ + if (!--event->index[index].refs) + event->disable(event, index); + list_del(&handler->head); +} + +void +nouveau_event_put(struct nouveau_event *event, int index, + struct nouveau_eventh *handler) +{ + unsigned long flags; + + spin_lock_irqsave(&event->lock, flags); + if (index < event->index_nr) + nouveau_event_put_locked(event, index, handler); + spin_unlock_irqrestore(&event->lock, flags); +} + +void +nouveau_event_get(struct nouveau_event *event, int index, + struct nouveau_eventh *handler) +{ + unsigned long flags; + + spin_lock_irqsave(&event->lock, flags); + if (index < event->index_nr) { + list_add(&handler->head, &event->index[index].list); + if (!event->index[index].refs++) + event->enable(event, index); + } + spin_unlock_irqrestore(&event->lock, flags); +} + +void +nouveau_event_trigger(struct nouveau_event *event, int index) +{ + struct nouveau_eventh *handler, *temp; + unsigned long flags; + + if (index >= event->index_nr) + return; + + spin_lock_irqsave(&event->lock, flags); + list_for_each_entry_safe(handler, temp, &event->index[index].list, head) { + if (handler->func(handler, index) == NVKM_EVENT_DROP) { + nouveau_event_put_locked(event, index, handler); + } + } + spin_unlock_irqrestore(&event->lock, flags); +} + +void +nouveau_event_destroy(struct nouveau_event **pevent) +{ + struct nouveau_event *event = *pevent; + if (event) { + kfree(event); + *pevent = NULL; + } +} + +int +nouveau_event_create(int index_nr, struct nouveau_event **pevent) +{ + struct nouveau_event *event; + int i; + + event = *pevent = kzalloc(sizeof(*event) + index_nr * + sizeof(event->index[0]), GFP_KERNEL); + if (!event) + return -ENOMEM; + + spin_lock_init(&event->lock); + for (i = 0; i < index_nr; i++) + INIT_LIST_HEAD(&event->index[i].list); + event->index_nr = index_nr; + return 0; +} diff --git a/drivers/gpu/drm/nouveau/core/engine/copy/nva3.c b/drivers/gpu/drm/nouveau/core/engine/copy/nva3.c index 283248c7b050..d6dc2a65ccd1 100644 --- a/drivers/gpu/drm/nouveau/core/engine/copy/nva3.c +++ b/drivers/gpu/drm/nouveau/core/engine/copy/nva3.c @@ -22,6 +22,7 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/falcon.h> #include <core/class.h> #include <core/enum.h> @@ -100,8 +101,9 @@ nva3_copy_intr(struct nouveau_subdev *subdev) if (stat & 0x00000040) { nv_error(falcon, "DISPATCH_ERROR ["); nouveau_enum_print(nva3_copy_isr_error_name, ssta); - printk("] ch %d [0x%010llx] subc %d mthd 0x%04x data 0x%08x\n", - chid, inst << 12, subc, mthd, data); + pr_cont("] ch %d [0x%010llx %s] subc %d mthd 0x%04x data 0x%08x\n", + chid, inst << 12, nouveau_client_name(engctx), subc, + mthd, data); nv_wo32(falcon, 0x004, 0x00000040); stat &= ~0x00000040; } diff --git a/drivers/gpu/drm/nouveau/core/engine/crypt/nv84.c b/drivers/gpu/drm/nouveau/core/engine/crypt/nv84.c index b97490512723..5bc021f471f9 100644 --- a/drivers/gpu/drm/nouveau/core/engine/crypt/nv84.c +++ b/drivers/gpu/drm/nouveau/core/engine/crypt/nv84.c @@ -22,6 +22,7 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/os.h> #include <core/enum.h> #include <core/class.h> @@ -126,10 +127,11 @@ nv84_crypt_intr(struct nouveau_subdev *subdev) chid = pfifo->chid(pfifo, engctx); if (stat) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv84_crypt_intr_mask, stat); - printk(" ch %d [0x%010llx] mthd 0x%04x data 0x%08x\n", - chid, (u64)inst << 12, mthd, data); + pr_cont(" ch %d [0x%010llx %s] mthd 0x%04x data 0x%08x\n", + chid, (u64)inst << 12, nouveau_client_name(engctx), + mthd, data); } nv_wr32(priv, 0x102130, stat); diff --git a/drivers/gpu/drm/nouveau/core/engine/crypt/nv98.c b/drivers/gpu/drm/nouveau/core/engine/crypt/nv98.c index 21986f3bf0c8..8bf8955051d4 100644 --- a/drivers/gpu/drm/nouveau/core/engine/crypt/nv98.c +++ b/drivers/gpu/drm/nouveau/core/engine/crypt/nv98.c @@ -22,6 +22,7 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/os.h> #include <core/enum.h> #include <core/class.h> @@ -102,8 +103,9 @@ nv98_crypt_intr(struct nouveau_subdev *subdev) if (stat & 0x00000040) { nv_error(priv, "DISPATCH_ERROR ["); nouveau_enum_print(nv98_crypt_isr_error_name, ssta); - printk("] ch %d [0x%010llx] subc %d mthd 0x%04x data 0x%08x\n", - chid, (u64)inst << 12, subc, mthd, data); + pr_cont("] ch %d [0x%010llx %s] subc %d mthd 0x%04x data 0x%08x\n", + chid, (u64)inst << 12, nouveau_client_name(engctx), + subc, mthd, data); nv_wr32(priv, 0x087004, 0x00000040); stat &= ~0x00000040; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/base.c b/drivers/gpu/drm/nouveau/core/engine/disp/base.c new file mode 100644 index 000000000000..7a5cae42834f --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/engine/disp/base.c @@ -0,0 +1,52 @@ +/* + * Copyright 2013 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <engine/disp.h> + +void +_nouveau_disp_dtor(struct nouveau_object *object) +{ + struct nouveau_disp *disp = (void *)object; + nouveau_event_destroy(&disp->vblank); + nouveau_engine_destroy(&disp->base); +} + +int +nouveau_disp_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, int heads, + const char *intname, const char *extname, + int length, void **pobject) +{ + struct nouveau_disp *disp; + int ret; + + ret = nouveau_engine_create_(parent, engine, oclass, true, + intname, extname, length, pobject); + disp = *pobject; + if (ret) + return ret; + + return nouveau_event_create(heads, &disp->vblank); +} diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/dport.c b/drivers/gpu/drm/nouveau/core/engine/disp/dport.c new file mode 100644 index 000000000000..fa27b02ff829 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/engine/disp/dport.c @@ -0,0 +1,346 @@ +/* + * Copyright 2013 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/bios.h> +#include <subdev/bios/dcb.h> +#include <subdev/bios/dp.h> +#include <subdev/bios/init.h> +#include <subdev/i2c.h> + +#include <engine/disp.h> + +#include "dport.h" + +#define DBG(fmt, args...) nv_debug(dp->disp, "DP:%04x:%04x: " fmt, \ + dp->outp->hasht, dp->outp->hashm, ##args) +#define ERR(fmt, args...) nv_error(dp->disp, "DP:%04x:%04x: " fmt, \ + dp->outp->hasht, dp->outp->hashm, ##args) + +/****************************************************************************** + * link training + *****************************************************************************/ +struct dp_state { + const struct nouveau_dp_func *func; + struct nouveau_disp *disp; + struct dcb_output *outp; + struct nvbios_dpout info; + u8 version; + struct nouveau_i2c_port *aux; + int head; + u8 dpcd[4]; + int link_nr; + u32 link_bw; + u8 stat[6]; + u8 conf[4]; +}; + +static int +dp_set_link_config(struct dp_state *dp) +{ + struct nouveau_disp *disp = dp->disp; + struct nouveau_bios *bios = nouveau_bios(disp); + struct nvbios_init init = { + .subdev = nv_subdev(dp->disp), + .bios = bios, + .offset = 0x0000, + .outp = dp->outp, + .crtc = dp->head, + .execute = 1, + }; + u32 lnkcmp; + u8 sink[2]; + + DBG("%d lanes at %d KB/s\n", dp->link_nr, dp->link_bw); + + /* set desired link configuration on the sink */ + sink[0] = dp->link_bw / 27000; + sink[1] = dp->link_nr; + if (dp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP) + sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN; + + nv_wraux(dp->aux, DPCD_LC00, sink, 2); + + /* set desired link configuration on the source */ + if ((lnkcmp = dp->info.lnkcmp)) { + if (dp->version < 0x30) { + while ((dp->link_bw / 10) < nv_ro16(bios, lnkcmp)) + lnkcmp += 4; + init.offset = nv_ro16(bios, lnkcmp + 2); + } else { + while ((dp->link_bw / 27000) < nv_ro08(bios, lnkcmp)) + lnkcmp += 3; + init.offset = nv_ro16(bios, lnkcmp + 1); + } + + nvbios_exec(&init); + } + + return dp->func->lnk_ctl(dp->disp, dp->outp, dp->head, + dp->link_nr, dp->link_bw / 27000, + dp->dpcd[DPCD_RC02] & + DPCD_RC02_ENHANCED_FRAME_CAP); +} + +static void +dp_set_training_pattern(struct dp_state *dp, u8 pattern) +{ + u8 sink_tp; + + DBG("training pattern %d\n", pattern); + dp->func->pattern(dp->disp, dp->outp, dp->head, pattern); + + nv_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1); + sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET; + sink_tp |= pattern; + nv_wraux(dp->aux, DPCD_LC02, &sink_tp, 1); +} + +static int +dp_link_train_commit(struct dp_state *dp) +{ + int i; + + for (i = 0; i < dp->link_nr; i++) { + u8 lane = (dp->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf; + u8 lpre = (lane & 0x0c) >> 2; + u8 lvsw = (lane & 0x03) >> 0; + + dp->conf[i] = (lpre << 3) | lvsw; + if (lvsw == 3) + dp->conf[i] |= DPCD_LC03_MAX_SWING_REACHED; + if (lpre == 3) + dp->conf[i] |= DPCD_LC03_MAX_PRE_EMPHASIS_REACHED; + + DBG("config lane %d %02x\n", i, dp->conf[i]); + dp->func->drv_ctl(dp->disp, dp->outp, dp->head, i, lvsw, lpre); + } + + return nv_wraux(dp->aux, DPCD_LC03(0), dp->conf, 4); +} + +static int +dp_link_train_update(struct dp_state *dp, u32 delay) +{ + int ret; + + udelay(delay); + + ret = nv_rdaux(dp->aux, DPCD_LS02, dp->stat, 6); + if (ret) + return ret; + + DBG("status %*ph\n", 6, dp->stat); + return 0; +} + +static int +dp_link_train_cr(struct dp_state *dp) +{ + bool cr_done = false, abort = false; + int voltage = dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET; + int tries = 0, i; + + dp_set_training_pattern(dp, 1); + + do { + if (dp_link_train_commit(dp) || + dp_link_train_update(dp, 100)) + break; + + cr_done = true; + for (i = 0; i < dp->link_nr; i++) { + u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; + if (!(lane & DPCD_LS02_LANE0_CR_DONE)) { + cr_done = false; + if (dp->conf[i] & DPCD_LC03_MAX_SWING_REACHED) + abort = true; + break; + } + } + + if ((dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET) != voltage) { + voltage = dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET; + tries = 0; + } + } while (!cr_done && !abort && ++tries < 5); + + return cr_done ? 0 : -1; +} + +static int +dp_link_train_eq(struct dp_state *dp) +{ + bool eq_done, cr_done = true; + int tries = 0, i; + + dp_set_training_pattern(dp, 2); + + do { + if (dp_link_train_update(dp, 400)) + break; + + eq_done = !!(dp->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE); + for (i = 0; i < dp->link_nr && eq_done; i++) { + u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; + if (!(lane & DPCD_LS02_LANE0_CR_DONE)) + cr_done = false; + if (!(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) || + !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED)) + eq_done = false; + } + + if (dp_link_train_commit(dp)) + break; + } while (!eq_done && cr_done && ++tries <= 5); + + return eq_done ? 0 : -1; +} + +static void +dp_link_train_init(struct dp_state *dp, bool spread) +{ + struct nvbios_init init = { + .subdev = nv_subdev(dp->disp), + .bios = nouveau_bios(dp->disp), + .outp = dp->outp, + .crtc = dp->head, + .execute = 1, + }; + + /* set desired spread */ + if (spread) + init.offset = dp->info.script[2]; + else + init.offset = dp->info.script[3]; + nvbios_exec(&init); + + /* pre-train script */ + init.offset = dp->info.script[0]; + nvbios_exec(&init); +} + +static void +dp_link_train_fini(struct dp_state *dp) +{ + struct nvbios_init init = { + .subdev = nv_subdev(dp->disp), + .bios = nouveau_bios(dp->disp), + .outp = dp->outp, + .crtc = dp->head, + .execute = 1, + }; + + /* post-train script */ + init.offset = dp->info.script[1], + nvbios_exec(&init); +} + +int +nouveau_dp_train(struct nouveau_disp *disp, const struct nouveau_dp_func *func, + struct dcb_output *outp, int head, u32 datarate) +{ + struct nouveau_bios *bios = nouveau_bios(disp); + struct nouveau_i2c *i2c = nouveau_i2c(disp); + struct dp_state _dp = { + .disp = disp, + .func = func, + .outp = outp, + .head = head, + }, *dp = &_dp; + const u32 bw_list[] = { 270000, 162000, 0 }; + const u32 *link_bw = bw_list; + u8 hdr, cnt, len; + u32 data; + int ret; + + /* find the bios displayport data relevant to this output */ + data = nvbios_dpout_match(bios, outp->hasht, outp->hashm, &dp->version, + &hdr, &cnt, &len, &dp->info); + if (!data) { + ERR("bios data not found\n"); + return -EINVAL; + } + + /* acquire the aux channel and fetch some info about the display */ + if (outp->location) + dp->aux = i2c->find_type(i2c, NV_I2C_TYPE_EXTAUX(outp->extdev)); + else + dp->aux = i2c->find(i2c, NV_I2C_TYPE_DCBI2C(outp->i2c_index)); + if (!dp->aux) { + ERR("no aux channel?!\n"); + return -ENODEV; + } + + ret = nv_rdaux(dp->aux, 0x00000, dp->dpcd, sizeof(dp->dpcd)); + if (ret) { + ERR("failed to read DPCD\n"); + return ret; + } + + /* adjust required bandwidth for 8B/10B coding overhead */ + datarate = (datarate / 8) * 10; + + /* enable down-spreading and execute pre-train script from vbios */ + dp_link_train_init(dp, dp->dpcd[3] & 0x01); + + /* start off at highest link rate supported by encoder and display */ + while (*link_bw > (dp->dpcd[1] * 27000)) + link_bw++; + + while (link_bw[0]) { + /* find minimum required lane count at this link rate */ + dp->link_nr = dp->dpcd[2] & DPCD_RC02_MAX_LANE_COUNT; + while ((dp->link_nr >> 1) * link_bw[0] > datarate) + dp->link_nr >>= 1; + + /* drop link rate to minimum with this lane count */ + while ((link_bw[1] * dp->link_nr) > datarate) + link_bw++; + dp->link_bw = link_bw[0]; + + /* program selected link configuration */ + ret = dp_set_link_config(dp); + if (ret == 0) { + /* attempt to train the link at this configuration */ + memset(dp->stat, 0x00, sizeof(dp->stat)); + if (!dp_link_train_cr(dp) && + !dp_link_train_eq(dp)) + break; + } else + if (ret >= 1) { + /* dp_set_link_config() handled training */ + break; + } + + /* retry at lower rate */ + link_bw++; + } + + /* finish link training */ + dp_set_training_pattern(dp, 0); + + /* execute post-train script from vbios */ + dp_link_train_fini(dp); + return true; +} diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/dport.h b/drivers/gpu/drm/nouveau/core/engine/disp/dport.h new file mode 100644 index 000000000000..0e1bbd18ff6c --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/engine/disp/dport.h @@ -0,0 +1,78 @@ +#ifndef __NVKM_DISP_DPORT_H__ +#define __NVKM_DISP_DPORT_H__ + +/* DPCD Receiver Capabilities */ +#define DPCD_RC00 0x00000 +#define DPCD_RC00_DPCD_REV 0xff +#define DPCD_RC01 0x00001 +#define DPCD_RC01_MAX_LINK_RATE 0xff +#define DPCD_RC02 0x00002 +#define DPCD_RC02_ENHANCED_FRAME_CAP 0x80 +#define DPCD_RC02_MAX_LANE_COUNT 0x1f +#define DPCD_RC03 0x00003 +#define DPCD_RC03_MAX_DOWNSPREAD 0x01 + +/* DPCD Link Configuration */ +#define DPCD_LC00 0x00100 +#define DPCD_LC00_LINK_BW_SET 0xff +#define DPCD_LC01 0x00101 +#define DPCD_LC01_ENHANCED_FRAME_EN 0x80 +#define DPCD_LC01_LANE_COUNT_SET 0x1f +#define DPCD_LC02 0x00102 +#define DPCD_LC02_TRAINING_PATTERN_SET 0x03 +#define DPCD_LC03(l) ((l) + 0x00103) +#define DPCD_LC03_MAX_PRE_EMPHASIS_REACHED 0x20 +#define DPCD_LC03_PRE_EMPHASIS_SET 0x18 +#define DPCD_LC03_MAX_SWING_REACHED 0x04 +#define DPCD_LC03_VOLTAGE_SWING_SET 0x03 + +/* DPCD Link/Sink Status */ +#define DPCD_LS02 0x00202 +#define DPCD_LS02_LANE1_SYMBOL_LOCKED 0x40 +#define DPCD_LS02_LANE1_CHANNEL_EQ_DONE 0x20 +#define DPCD_LS02_LANE1_CR_DONE 0x10 +#define DPCD_LS02_LANE0_SYMBOL_LOCKED 0x04 +#define DPCD_LS02_LANE0_CHANNEL_EQ_DONE 0x02 +#define DPCD_LS02_LANE0_CR_DONE 0x01 +#define DPCD_LS03 0x00203 +#define DPCD_LS03_LANE3_SYMBOL_LOCKED 0x40 +#define DPCD_LS03_LANE3_CHANNEL_EQ_DONE 0x20 +#define DPCD_LS03_LANE3_CR_DONE 0x10 +#define DPCD_LS03_LANE2_SYMBOL_LOCKED 0x04 +#define DPCD_LS03_LANE2_CHANNEL_EQ_DONE 0x02 +#define DPCD_LS03_LANE2_CR_DONE 0x01 +#define DPCD_LS04 0x00204 +#define DPCD_LS04_LINK_STATUS_UPDATED 0x80 +#define DPCD_LS04_DOWNSTREAM_PORT_STATUS_CHANGED 0x40 +#define DPCD_LS04_INTERLANE_ALIGN_DONE 0x01 +#define DPCD_LS06 0x00206 +#define DPCD_LS06_LANE1_PRE_EMPHASIS 0xc0 +#define DPCD_LS06_LANE1_VOLTAGE_SWING 0x30 +#define DPCD_LS06_LANE0_PRE_EMPHASIS 0x0c +#define DPCD_LS06_LANE0_VOLTAGE_SWING 0x03 +#define DPCD_LS07 0x00207 +#define DPCD_LS07_LANE3_PRE_EMPHASIS 0xc0 +#define DPCD_LS07_LANE3_VOLTAGE_SWING 0x30 +#define DPCD_LS07_LANE2_PRE_EMPHASIS 0x0c +#define DPCD_LS07_LANE2_VOLTAGE_SWING 0x03 + +struct nouveau_disp; +struct dcb_output; + +struct nouveau_dp_func { + int (*pattern)(struct nouveau_disp *, struct dcb_output *, + int head, int pattern); + int (*lnk_ctl)(struct nouveau_disp *, struct dcb_output *, int head, + int link_nr, int link_bw, bool enh_frame); + int (*drv_ctl)(struct nouveau_disp *, struct dcb_output *, int head, + int lane, int swing, int preem); +}; + +extern const struct nouveau_dp_func nv94_sor_dp_func; +extern const struct nouveau_dp_func nvd0_sor_dp_func; +extern const struct nouveau_dp_func nv50_pior_dp_func; + +int nouveau_dp_train(struct nouveau_disp *, const struct nouveau_dp_func *, + struct dcb_output *, int, u32); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c index 1c919f2af89f..05e903f08a36 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv04.c @@ -24,21 +24,33 @@ #include <engine/disp.h> +#include <core/event.h> +#include <core/class.h> + struct nv04_disp_priv { struct nouveau_disp base; }; static struct nouveau_oclass nv04_disp_sclass[] = { + { NV04_DISP_CLASS, &nouveau_object_ofuncs }, {}, }; +/******************************************************************************* + * Display engine implementation + ******************************************************************************/ + +static void +nv04_disp_vblank_enable(struct nouveau_event *event, int head) +{ + nv_wr32(event->priv, 0x600140 + (head * 0x2000) , 0x00000001); +} + static void -nv04_disp_intr_vblank(struct nv04_disp_priv *priv, int crtc) +nv04_disp_vblank_disable(struct nouveau_event *event, int head) { - struct nouveau_disp *disp = &priv->base; - if (disp->vblank.notify) - disp->vblank.notify(disp->vblank.data, crtc); + nv_wr32(event->priv, 0x600140 + (head * 0x2000) , 0x00000000); } static void @@ -49,25 +61,25 @@ nv04_disp_intr(struct nouveau_subdev *subdev) u32 crtc1 = nv_rd32(priv, 0x602100); if (crtc0 & 0x00000001) { - nv04_disp_intr_vblank(priv, 0); + nouveau_event_trigger(priv->base.vblank, 0); nv_wr32(priv, 0x600100, 0x00000001); } if (crtc1 & 0x00000001) { - nv04_disp_intr_vblank(priv, 1); + nouveau_event_trigger(priv->base.vblank, 1); nv_wr32(priv, 0x602100, 0x00000001); } } static int nv04_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, - struct nouveau_oclass *oclass, void *data, u32 size, - struct nouveau_object **pobject) + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) { struct nv04_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "DISPLAY", + ret = nouveau_disp_create(parent, engine, oclass, 2, "DISPLAY", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -75,6 +87,9 @@ nv04_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nv04_disp_sclass; nv_subdev(priv)->intr = nv04_disp_intr; + priv->base.vblank->priv = priv; + priv->base.vblank->enable = nv04_disp_vblank_enable; + priv->base.vblank->disable = nv04_disp_vblank_disable; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c index ca1a7d76a95b..5fa13267bd9f 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c @@ -27,7 +27,6 @@ #include <core/handle.h> #include <core/class.h> -#include <engine/software.h> #include <engine/disp.h> #include <subdev/bios.h> @@ -37,7 +36,6 @@ #include <subdev/bios/pll.h> #include <subdev/timer.h> #include <subdev/fb.h> -#include <subdev/bar.h> #include <subdev/clock.h> #include "nv50.h" @@ -335,7 +333,7 @@ nv50_disp_sync_ctor(struct nouveau_object *parent, struct nv50_disp_dmac *dmac; int ret; - if (size < sizeof(*data) || args->head > 1) + if (size < sizeof(*args) || args->head > 1) return -EINVAL; ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, @@ -374,7 +372,7 @@ nv50_disp_ovly_ctor(struct nouveau_object *parent, struct nv50_disp_dmac *dmac; int ret; - if (size < sizeof(*data) || args->head > 1) + if (size < sizeof(*args) || args->head > 1) return -EINVAL; ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, @@ -543,6 +541,18 @@ nv50_disp_curs_ofuncs = { * Base display object ******************************************************************************/ +static void +nv50_disp_base_vblank_enable(struct nouveau_event *event, int head) +{ + nv_mask(event->priv, 0x61002c, (1 << head), (1 << head)); +} + +static void +nv50_disp_base_vblank_disable(struct nouveau_event *event, int head) +{ + nv_mask(event->priv, 0x61002c, (1 << head), (0 << head)); +} + static int nv50_disp_base_ctor(struct nouveau_object *parent, struct nouveau_object *engine, @@ -559,6 +569,9 @@ nv50_disp_base_ctor(struct nouveau_object *parent, if (ret) return ret; + priv->base.vblank->priv = priv; + priv->base.vblank->enable = nv50_disp_base_vblank_enable; + priv->base.vblank->disable = nv50_disp_base_vblank_disable; return nouveau_ramht_new(parent, parent, 0x1000, 0, &base->ramht); } @@ -613,7 +626,7 @@ nv50_disp_base_init(struct nouveau_object *object) nv_wr32(priv, 0x6101e0 + (i * 0x04), tmp); } - /* ... EXT caps */ + /* ... PIOR caps */ for (i = 0; i < 3; i++) { tmp = nv_rd32(priv, 0x61e000 + (i * 0x800)); nv_wr32(priv, 0x6101f0 + (i * 0x04), tmp); @@ -665,6 +678,9 @@ nv50_disp_base_omthds[] = { { SOR_MTHD(NV50_DISP_SOR_LVDS_SCRIPT) , nv50_sor_mthd }, { DAC_MTHD(NV50_DISP_DAC_PWR) , nv50_dac_mthd }, { DAC_MTHD(NV50_DISP_DAC_LOAD) , nv50_dac_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_TMDS_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_DP_PWR) , nv50_pior_mthd }, {}, }; @@ -756,50 +772,6 @@ nv50_disp_intr_error(struct nv50_disp_priv *priv) } } -static void -nv50_disp_intr_vblank(struct nv50_disp_priv *priv, int crtc) -{ - struct nouveau_bar *bar = nouveau_bar(priv); - struct nouveau_disp *disp = &priv->base; - struct nouveau_software_chan *chan, *temp; - unsigned long flags; - - spin_lock_irqsave(&disp->vblank.lock, flags); - list_for_each_entry_safe(chan, temp, &disp->vblank.list, vblank.head) { - if (chan->vblank.crtc != crtc) - continue; - - if (nv_device(priv)->chipset >= 0xc0) { - nv_wr32(priv, 0x001718, 0x80000000 | chan->vblank.channel); - bar->flush(bar); - nv_wr32(priv, 0x06000c, - upper_32_bits(chan->vblank.offset)); - nv_wr32(priv, 0x060010, - lower_32_bits(chan->vblank.offset)); - nv_wr32(priv, 0x060014, chan->vblank.value); - } else { - nv_wr32(priv, 0x001704, chan->vblank.channel); - nv_wr32(priv, 0x001710, 0x80000000 | chan->vblank.ctxdma); - bar->flush(bar); - if (nv_device(priv)->chipset == 0x50) { - nv_wr32(priv, 0x001570, chan->vblank.offset); - nv_wr32(priv, 0x001574, chan->vblank.value); - } else { - nv_wr32(priv, 0x060010, chan->vblank.offset); - nv_wr32(priv, 0x060014, chan->vblank.value); - } - } - - list_del(&chan->vblank.head); - if (disp->vblank.put) - disp->vblank.put(disp->vblank.data, crtc); - } - spin_unlock_irqrestore(&disp->vblank.lock, flags); - - if (disp->vblank.notify) - disp->vblank.notify(disp->vblank.data, crtc); -} - static u16 exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, struct dcb_output *dcb, u8 *ver, u8 *hdr, u8 *cnt, u8 *len, @@ -811,8 +783,8 @@ exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, if (outp < 4) { type = DCB_OUTPUT_ANALOG; mask = 0; - } else { - outp -= 4; + } else + if (outp < 8) { switch (ctrl & 0x00000f00) { case 0x00000000: type = DCB_OUTPUT_LVDS; mask = 1; break; case 0x00000100: type = DCB_OUTPUT_TMDS; mask = 1; break; @@ -824,6 +796,17 @@ exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, nv_error(priv, "unknown SOR mc 0x%08x\n", ctrl); return 0x0000; } + outp -= 4; + } else { + outp = outp - 8; + type = 0x0010; + mask = 0; + switch (ctrl & 0x00000f00) { + case 0x00000000: type |= priv->pior.type[outp]; break; + default: + nv_error(priv, "unknown PIOR mc 0x%08x\n", ctrl); + return 0x0000; + } } mask = 0x00c0 & (mask << 6); @@ -834,6 +817,10 @@ exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, if (!data) return 0x0000; + /* off-chip encoders require matching the exact encoder type */ + if (dcb->location != 0) + type |= dcb->extdev << 8; + return nvbios_outp_match(bios, type, mask, ver, hdr, cnt, len, info); } @@ -848,9 +835,11 @@ exec_script(struct nv50_disp_priv *priv, int head, int id) u32 ctrl = 0x00000000; int i; + /* DAC */ for (i = 0; !(ctrl & (1 << head)) && i < 3; i++) ctrl = nv_rd32(priv, 0x610b5c + (i * 8)); + /* SOR */ if (!(ctrl & (1 << head))) { if (nv_device(priv)->chipset < 0x90 || nv_device(priv)->chipset == 0x92 || @@ -865,6 +854,13 @@ exec_script(struct nv50_disp_priv *priv, int head, int id) } } + /* PIOR */ + if (!(ctrl & (1 << head))) { + for (i = 0; !(ctrl & (1 << head)) && i < 3; i++) + ctrl = nv_rd32(priv, 0x610b84 + (i * 8)); + i += 8; + } + if (!(ctrl & (1 << head))) return false; i--; @@ -894,13 +890,15 @@ exec_clkcmp(struct nv50_disp_priv *priv, int head, int id, u32 pclk, struct nvbios_outp info1; struct nvbios_ocfg info2; u8 ver, hdr, cnt, len; - u16 data, conf; u32 ctrl = 0x00000000; + u32 data, conf = ~0; int i; + /* DAC */ for (i = 0; !(ctrl & (1 << head)) && i < 3; i++) ctrl = nv_rd32(priv, 0x610b58 + (i * 8)); + /* SOR */ if (!(ctrl & (1 << head))) { if (nv_device(priv)->chipset < 0x90 || nv_device(priv)->chipset == 0x92 || @@ -915,34 +913,46 @@ exec_clkcmp(struct nv50_disp_priv *priv, int head, int id, u32 pclk, } } + /* PIOR */ + if (!(ctrl & (1 << head))) { + for (i = 0; !(ctrl & (1 << head)) && i < 3; i++) + ctrl = nv_rd32(priv, 0x610b80 + (i * 8)); + i += 8; + } + if (!(ctrl & (1 << head))) - return 0x0000; + return conf; i--; data = exec_lookup(priv, head, i, ctrl, outp, &ver, &hdr, &cnt, &len, &info1); if (!data) - return 0x0000; - - switch (outp->type) { - case DCB_OUTPUT_TMDS: - conf = (ctrl & 0x00000f00) >> 8; - if (pclk >= 165000) - conf |= 0x0100; - break; - case DCB_OUTPUT_LVDS: - conf = priv->sor.lvdsconf; - break; - case DCB_OUTPUT_DP: + return conf; + + if (outp->location == 0) { + switch (outp->type) { + case DCB_OUTPUT_TMDS: + conf = (ctrl & 0x00000f00) >> 8; + if (pclk >= 165000) + conf |= 0x0100; + break; + case DCB_OUTPUT_LVDS: + conf = priv->sor.lvdsconf; + break; + case DCB_OUTPUT_DP: + conf = (ctrl & 0x00000f00) >> 8; + break; + case DCB_OUTPUT_ANALOG: + default: + conf = 0x00ff; + break; + } + } else { conf = (ctrl & 0x00000f00) >> 8; - break; - case DCB_OUTPUT_ANALOG: - default: - conf = 0x00ff; - break; + pclk = pclk / 2; } data = nvbios_ocfg_match(bios, data, conf, &ver, &hdr, &cnt, &len, &info2); - if (data) { + if (data && id < 0xff) { data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk); if (data) { struct nvbios_init init = { @@ -954,32 +964,37 @@ exec_clkcmp(struct nv50_disp_priv *priv, int head, int id, u32 pclk, .execute = 1, }; - if (nvbios_exec(&init)) - return 0x0000; - return conf; + nvbios_exec(&init); } } - return 0x0000; + return conf; } static void -nv50_disp_intr_unk10(struct nv50_disp_priv *priv, u32 super) +nv50_disp_intr_unk10_0(struct nv50_disp_priv *priv, int head) { - int head = ffs((super & 0x00000060) >> 5) - 1; - if (head >= 0) { - head = ffs((super & 0x00000180) >> 7) - 1; - if (head >= 0) - exec_script(priv, head, 1); - } + exec_script(priv, head, 1); +} - nv_wr32(priv, 0x610024, 0x00000010); - nv_wr32(priv, 0x610030, 0x80000000); +static void +nv50_disp_intr_unk20_0(struct nv50_disp_priv *priv, int head) +{ + exec_script(priv, head, 2); +} + +static void +nv50_disp_intr_unk20_1(struct nv50_disp_priv *priv, int head) +{ + struct nouveau_clock *clk = nouveau_clock(priv); + u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; + if (pclk) + clk->pll_set(clk, PLL_VPLL0 + head, pclk); } static void -nv50_disp_intr_unk20_dp(struct nv50_disp_priv *priv, - struct dcb_output *outp, u32 pclk) +nv50_disp_intr_unk20_2_dp(struct nv50_disp_priv *priv, + struct dcb_output *outp, u32 pclk) { const int link = !(outp->sorconf.link & 1); const int or = ffs(outp->or) - 1; @@ -1085,53 +1100,54 @@ nv50_disp_intr_unk20_dp(struct nv50_disp_priv *priv, } static void -nv50_disp_intr_unk20(struct nv50_disp_priv *priv, u32 super) +nv50_disp_intr_unk20_2(struct nv50_disp_priv *priv, int head) { struct dcb_output outp; - u32 addr, mask, data; - int head; + u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; + u32 hval, hreg = 0x614200 + (head * 0x800); + u32 oval, oreg; + u32 conf = exec_clkcmp(priv, head, 0xff, pclk, &outp); + if (conf != ~0) { + if (outp.location == 0 && outp.type == DCB_OUTPUT_DP) { + u32 soff = (ffs(outp.or) - 1) * 0x08; + u32 ctrl = nv_rd32(priv, 0x610798 + soff); + u32 datarate; + + switch ((ctrl & 0x000f0000) >> 16) { + case 6: datarate = pclk * 30 / 8; break; + case 5: datarate = pclk * 24 / 8; break; + case 2: + default: + datarate = pclk * 18 / 8; + break; + } - /* finish detaching encoder? */ - head = ffs((super & 0x00000180) >> 7) - 1; - if (head >= 0) - exec_script(priv, head, 2); - - /* check whether a vpll change is required */ - head = ffs((super & 0x00000600) >> 9) - 1; - if (head >= 0) { - u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; - if (pclk) { - struct nouveau_clock *clk = nouveau_clock(priv); - clk->pll_set(clk, PLL_VPLL0 + head, pclk); + nouveau_dp_train(&priv->base, priv->sor.dp, + &outp, head, datarate); } - nv_mask(priv, 0x614200 + head * 0x800, 0x0000000f, 0x00000000); - } - - /* (re)attach the relevant OR to the head */ - head = ffs((super & 0x00000180) >> 7) - 1; - if (head >= 0) { - u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; - u32 conf = exec_clkcmp(priv, head, 0, pclk, &outp); - if (conf) { - if (outp.type == DCB_OUTPUT_ANALOG) { - addr = 0x614280 + (ffs(outp.or) - 1) * 0x800; - mask = 0xffffffff; - data = 0x00000000; - } else { - if (outp.type == DCB_OUTPUT_DP) - nv50_disp_intr_unk20_dp(priv, &outp, pclk); - addr = 0x614300 + (ffs(outp.or) - 1) * 0x800; - mask = 0x00000707; - data = (conf & 0x0100) ? 0x0101 : 0x0000; - } - - nv_mask(priv, addr, mask, data); + exec_clkcmp(priv, head, 0, pclk, &outp); + + if (!outp.location && outp.type == DCB_OUTPUT_ANALOG) { + oreg = 0x614280 + (ffs(outp.or) - 1) * 0x800; + oval = 0x00000000; + hval = 0x00000000; + } else + if (!outp.location) { + if (outp.type == DCB_OUTPUT_DP) + nv50_disp_intr_unk20_2_dp(priv, &outp, pclk); + oreg = 0x614300 + (ffs(outp.or) - 1) * 0x800; + oval = (conf & 0x0100) ? 0x00000101 : 0x00000000; + hval = 0x00000000; + } else { + oreg = 0x614380 + (ffs(outp.or) - 1) * 0x800; + oval = 0x00000001; + hval = 0x00000001; } - } - nv_wr32(priv, 0x610024, 0x00000020); - nv_wr32(priv, 0x610030, 0x80000000); + nv_mask(priv, hreg, 0x0000000f, hval); + nv_mask(priv, oreg, 0x00000707, oval); + } } /* If programming a TMDS output on a SOR that can also be configured for @@ -1143,7 +1159,7 @@ nv50_disp_intr_unk20(struct nv50_disp_priv *priv, u32 super) * programmed for DisplayPort. */ static void -nv50_disp_intr_unk40_tmds(struct nv50_disp_priv *priv, struct dcb_output *outp) +nv50_disp_intr_unk40_0_tmds(struct nv50_disp_priv *priv, struct dcb_output *outp) { struct nouveau_bios *bios = nouveau_bios(priv); const int link = !(outp->sorconf.link & 1); @@ -1157,35 +1173,79 @@ nv50_disp_intr_unk40_tmds(struct nv50_disp_priv *priv, struct dcb_output *outp) } static void -nv50_disp_intr_unk40(struct nv50_disp_priv *priv, u32 super) +nv50_disp_intr_unk40_0(struct nv50_disp_priv *priv, int head) { - int head = ffs((super & 0x00000180) >> 7) - 1; - if (head >= 0) { - struct dcb_output outp; - u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; - if (pclk && exec_clkcmp(priv, head, 1, pclk, &outp)) { - if (outp.type == DCB_OUTPUT_TMDS) - nv50_disp_intr_unk40_tmds(priv, &outp); + struct dcb_output outp; + u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff; + if (exec_clkcmp(priv, head, 1, pclk, &outp) != ~0) { + if (outp.location == 0 && outp.type == DCB_OUTPUT_TMDS) + nv50_disp_intr_unk40_0_tmds(priv, &outp); + else + if (outp.location == 1 && outp.type == DCB_OUTPUT_DP) { + u32 soff = (ffs(outp.or) - 1) * 0x08; + u32 ctrl = nv_rd32(priv, 0x610b84 + soff); + u32 datarate; + + switch ((ctrl & 0x000f0000) >> 16) { + case 6: datarate = pclk * 30 / 8; break; + case 5: datarate = pclk * 24 / 8; break; + case 2: + default: + datarate = pclk * 18 / 8; + break; + } + + nouveau_dp_train(&priv->base, priv->pior.dp, + &outp, head, datarate); } } - - nv_wr32(priv, 0x610024, 0x00000040); - nv_wr32(priv, 0x610030, 0x80000000); } -static void -nv50_disp_intr_super(struct nv50_disp_priv *priv, u32 intr1) +void +nv50_disp_intr_supervisor(struct work_struct *work) { + struct nv50_disp_priv *priv = + container_of(work, struct nv50_disp_priv, supervisor); u32 super = nv_rd32(priv, 0x610030); + int head; - nv_debug(priv, "supervisor 0x%08x 0x%08x\n", intr1, super); + nv_debug(priv, "supervisor 0x%08x 0x%08x\n", priv->super, super); - if (intr1 & 0x00000010) - nv50_disp_intr_unk10(priv, super); - if (intr1 & 0x00000020) - nv50_disp_intr_unk20(priv, super); - if (intr1 & 0x00000040) - nv50_disp_intr_unk40(priv, super); + if (priv->super & 0x00000010) { + for (head = 0; head < priv->head.nr; head++) { + if (!(super & (0x00000020 << head))) + continue; + if (!(super & (0x00000080 << head))) + continue; + nv50_disp_intr_unk10_0(priv, head); + } + } else + if (priv->super & 0x00000020) { + for (head = 0; head < priv->head.nr; head++) { + if (!(super & (0x00000080 << head))) + continue; + nv50_disp_intr_unk20_0(priv, head); + } + for (head = 0; head < priv->head.nr; head++) { + if (!(super & (0x00000200 << head))) + continue; + nv50_disp_intr_unk20_1(priv, head); + } + for (head = 0; head < priv->head.nr; head++) { + if (!(super & (0x00000080 << head))) + continue; + nv50_disp_intr_unk20_2(priv, head); + } + } else + if (priv->super & 0x00000040) { + for (head = 0; head < priv->head.nr; head++) { + if (!(super & (0x00000080 << head))) + continue; + nv50_disp_intr_unk40_0(priv, head); + } + } + + nv_wr32(priv, 0x610030, 0x80000000); } void @@ -1201,19 +1261,21 @@ nv50_disp_intr(struct nouveau_subdev *subdev) } if (intr1 & 0x00000004) { - nv50_disp_intr_vblank(priv, 0); + nouveau_event_trigger(priv->base.vblank, 0); nv_wr32(priv, 0x610024, 0x00000004); intr1 &= ~0x00000004; } if (intr1 & 0x00000008) { - nv50_disp_intr_vblank(priv, 1); + nouveau_event_trigger(priv->base.vblank, 1); nv_wr32(priv, 0x610024, 0x00000008); intr1 &= ~0x00000008; } if (intr1 & 0x00000070) { - nv50_disp_intr_super(priv, intr1); + priv->super = (intr1 & 0x00000070); + schedule_work(&priv->supervisor); + nv_wr32(priv, 0x610024, priv->super); intr1 &= ~0x00000070; } } @@ -1226,7 +1288,7 @@ nv50_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", + ret = nouveau_disp_create(parent, engine, oclass, 2, "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -1235,16 +1297,17 @@ nv50_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nv50_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nv50_disp_intr; + INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor); priv->sclass = nv50_disp_sclass; priv->head.nr = 2; priv->dac.nr = 3; priv->sor.nr = 2; + priv->pior.nr = 3; priv->dac.power = nv50_dac_power; priv->dac.sense = nv50_dac_sense; priv->sor.power = nv50_sor_power; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->pior.power = nv50_pior_power; + priv->pior.dp = &nv50_pior_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h index a6bb931450f1..1ae6ceb56704 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.h @@ -3,16 +3,22 @@ #include <core/parent.h> #include <core/namedb.h> +#include <core/engctx.h> #include <core/ramht.h> +#include <core/event.h> #include <engine/dmaobj.h> #include <engine/disp.h> -struct dcb_output; +#include "dport.h" struct nv50_disp_priv { struct nouveau_disp base; struct nouveau_oclass *sclass; + + struct work_struct supervisor; + u32 super; + struct { int nr; } head; @@ -26,23 +32,15 @@ struct nv50_disp_priv { int (*power)(struct nv50_disp_priv *, int sor, u32 data); int (*hda_eld)(struct nv50_disp_priv *, int sor, u8 *, u32); int (*hdmi)(struct nv50_disp_priv *, int head, int sor, u32); - int (*dp_train_init)(struct nv50_disp_priv *, int sor, int link, - int head, u16 type, u16 mask, u32 data, - struct dcb_output *); - int (*dp_train_fini)(struct nv50_disp_priv *, int sor, int link, - int head, u16 type, u16 mask, u32 data, - struct dcb_output *); - int (*dp_train)(struct nv50_disp_priv *, int sor, int link, - u16 type, u16 mask, u32 data, - struct dcb_output *); - int (*dp_lnkctl)(struct nv50_disp_priv *, int sor, int link, - int head, u16 type, u16 mask, u32 data, - struct dcb_output *); - int (*dp_drvctl)(struct nv50_disp_priv *, int sor, int link, - int lane, u16 type, u16 mask, u32 data, - struct dcb_output *); u32 lvdsconf; + const struct nouveau_dp_func *dp; } sor; + struct { + int nr; + int (*power)(struct nv50_disp_priv *, int ext, u32 data); + u8 type[3]; + const struct nouveau_dp_func *dp; + } pior; }; #define DAC_MTHD(n) (n), (n) + 0x03 @@ -81,6 +79,11 @@ int nvd0_sor_dp_lnkctl(struct nv50_disp_priv *, int, int, int, u16, u16, u32, int nvd0_sor_dp_drvctl(struct nv50_disp_priv *, int, int, int, u16, u16, u32, struct dcb_output *); +#define PIOR_MTHD(n) (n), (n) + 0x03 + +int nv50_pior_mthd(struct nouveau_object *, u32, void *, u32); +int nv50_pior_power(struct nv50_disp_priv *, int, u32); + struct nv50_disp_base { struct nouveau_parent base; struct nouveau_ramht *ramht; @@ -124,6 +127,7 @@ extern struct nouveau_ofuncs nv50_disp_oimm_ofuncs; extern struct nouveau_ofuncs nv50_disp_curs_ofuncs; extern struct nouveau_ofuncs nv50_disp_base_ofuncs; extern struct nouveau_oclass nv50_disp_cclass; +void nv50_disp_intr_supervisor(struct work_struct *); void nv50_disp_intr(struct nouveau_subdev *); extern struct nouveau_omthds nv84_disp_base_omthds[]; @@ -137,6 +141,7 @@ extern struct nouveau_ofuncs nvd0_disp_oimm_ofuncs; extern struct nouveau_ofuncs nvd0_disp_curs_ofuncs; extern struct nouveau_ofuncs nvd0_disp_base_ofuncs; extern struct nouveau_oclass nvd0_disp_cclass; +void nvd0_disp_intr_supervisor(struct work_struct *); void nvd0_disp_intr(struct nouveau_subdev *); #endif diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c index fc84eacdfbec..d8c74c0883a1 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv84.c @@ -46,6 +46,9 @@ nv84_disp_base_omthds[] = { { SOR_MTHD(NV50_DISP_SOR_LVDS_SCRIPT) , nv50_sor_mthd }, { DAC_MTHD(NV50_DISP_DAC_PWR) , nv50_dac_mthd }, { DAC_MTHD(NV50_DISP_DAC_LOAD) , nv50_dac_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_TMDS_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_DP_PWR) , nv50_pior_mthd }, {}, }; @@ -63,7 +66,7 @@ nv84_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", + ret = nouveau_disp_create(parent, engine, oclass, 2, "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -72,17 +75,18 @@ nv84_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nv84_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nv50_disp_intr; + INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor); priv->sclass = nv84_disp_sclass; priv->head.nr = 2; priv->dac.nr = 3; priv->sor.nr = 2; + priv->pior.nr = 3; priv->dac.power = nv50_dac_power; priv->dac.sense = nv50_dac_sense; priv->sor.power = nv50_sor_power; priv->sor.hdmi = nv84_hdmi_ctrl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->pior.power = nv50_pior_power; + priv->pior.dp = &nv50_pior_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c index ba9dfd4669a2..a66f949c1f84 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv94.c @@ -44,14 +44,11 @@ nv94_disp_base_omthds[] = { { SOR_MTHD(NV50_DISP_SOR_PWR) , nv50_sor_mthd }, { SOR_MTHD(NV84_DISP_SOR_HDMI_PWR) , nv50_sor_mthd }, { SOR_MTHD(NV50_DISP_SOR_LVDS_SCRIPT) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_TRAIN) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_LNKCTL) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(0)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(1)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(2)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(3)), nv50_sor_mthd }, { DAC_MTHD(NV50_DISP_DAC_PWR) , nv50_dac_mthd }, { DAC_MTHD(NV50_DISP_DAC_LOAD) , nv50_dac_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_TMDS_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_DP_PWR) , nv50_pior_mthd }, {}, }; @@ -69,7 +66,7 @@ nv94_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", + ret = nouveau_disp_create(parent, engine, oclass, 2, "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -78,22 +75,19 @@ nv94_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nv94_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nv50_disp_intr; + INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor); priv->sclass = nv94_disp_sclass; priv->head.nr = 2; priv->dac.nr = 3; priv->sor.nr = 4; + priv->pior.nr = 3; priv->dac.power = nv50_dac_power; priv->dac.sense = nv50_dac_sense; priv->sor.power = nv50_sor_power; priv->sor.hdmi = nv84_hdmi_ctrl; - priv->sor.dp_train = nv94_sor_dp_train; - priv->sor.dp_train_init = nv94_sor_dp_train_init; - priv->sor.dp_train_fini = nv94_sor_dp_train_fini; - priv->sor.dp_lnkctl = nv94_sor_dp_lnkctl; - priv->sor.dp_drvctl = nv94_sor_dp_drvctl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->sor.dp = &nv94_sor_dp_func; + priv->pior.power = nv50_pior_power; + priv->pior.dp = &nv50_pior_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c index 5d63902cdeda..6cf8eefac368 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nva0.c @@ -53,7 +53,7 @@ nva0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", + ret = nouveau_disp_create(parent, engine, oclass, 2, "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -62,17 +62,18 @@ nva0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nva0_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nv50_disp_intr; + INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor); priv->sclass = nva0_disp_sclass; priv->head.nr = 2; priv->dac.nr = 3; priv->sor.nr = 2; + priv->pior.nr = 3; priv->dac.power = nv50_dac_power; priv->dac.sense = nv50_dac_sense; priv->sor.power = nv50_sor_power; priv->sor.hdmi = nv84_hdmi_ctrl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->pior.power = nv50_pior_power; + priv->pior.dp = &nv50_pior_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c b/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c index e9192ca389fa..b75413169eae 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nva3.c @@ -45,14 +45,11 @@ nva3_disp_base_omthds[] = { { SOR_MTHD(NVA3_DISP_SOR_HDA_ELD) , nv50_sor_mthd }, { SOR_MTHD(NV84_DISP_SOR_HDMI_PWR) , nv50_sor_mthd }, { SOR_MTHD(NV50_DISP_SOR_LVDS_SCRIPT) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_TRAIN) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_LNKCTL) , nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(0)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(1)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(2)), nv50_sor_mthd }, - { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(3)), nv50_sor_mthd }, { DAC_MTHD(NV50_DISP_DAC_PWR) , nv50_dac_mthd }, { DAC_MTHD(NV50_DISP_DAC_LOAD) , nv50_dac_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_TMDS_PWR) , nv50_pior_mthd }, + { PIOR_MTHD(NV50_DISP_PIOR_DP_PWR) , nv50_pior_mthd }, {}, }; @@ -70,7 +67,7 @@ nva3_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_disp_priv *priv; int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", + ret = nouveau_disp_create(parent, engine, oclass, 2, "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) @@ -79,23 +76,20 @@ nva3_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nva3_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nv50_disp_intr; + INIT_WORK(&priv->supervisor, nv50_disp_intr_supervisor); priv->sclass = nva3_disp_sclass; priv->head.nr = 2; priv->dac.nr = 3; priv->sor.nr = 4; + priv->pior.nr = 3; priv->dac.power = nv50_dac_power; priv->dac.sense = nv50_dac_sense; priv->sor.power = nv50_sor_power; priv->sor.hda_eld = nva3_hda_eld; priv->sor.hdmi = nva3_hdmi_ctrl; - priv->sor.dp_train = nv94_sor_dp_train; - priv->sor.dp_train_init = nv94_sor_dp_train_init; - priv->sor.dp_train_fini = nv94_sor_dp_train_fini; - priv->sor.dp_lnkctl = nv94_sor_dp_lnkctl; - priv->sor.dp_drvctl = nv94_sor_dp_drvctl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->sor.dp = &nv94_sor_dp_func; + priv->pior.power = nv50_pior_power; + priv->pior.dp = &nv50_pior_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c index 9e38ebff5fb3..788dd34ccb54 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c @@ -27,12 +27,10 @@ #include <core/handle.h> #include <core/class.h> -#include <engine/software.h> #include <engine/disp.h> #include <subdev/timer.h> #include <subdev/fb.h> -#include <subdev/bar.h> #include <subdev/clock.h> #include <subdev/bios.h> @@ -230,7 +228,7 @@ nvd0_disp_sync_ctor(struct nouveau_object *parent, struct nv50_disp_dmac *dmac; int ret; - if (size < sizeof(*data) || args->head >= priv->head.nr) + if (size < sizeof(*args) || args->head >= priv->head.nr) return -EINVAL; ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, @@ -270,7 +268,7 @@ nvd0_disp_ovly_ctor(struct nouveau_object *parent, struct nv50_disp_dmac *dmac; int ret; - if (size < sizeof(*data) || args->head >= priv->head.nr) + if (size < sizeof(*args) || args->head >= priv->head.nr) return -EINVAL; ret = nv50_disp_dmac_create_(parent, engine, oclass, args->pushbuf, @@ -443,6 +441,18 @@ nvd0_disp_curs_ofuncs = { * Base display object ******************************************************************************/ +static void +nvd0_disp_base_vblank_enable(struct nouveau_event *event, int head) +{ + nv_mask(event->priv, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000001); +} + +static void +nvd0_disp_base_vblank_disable(struct nouveau_event *event, int head) +{ + nv_mask(event->priv, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000000); +} + static int nvd0_disp_base_ctor(struct nouveau_object *parent, struct nouveau_object *engine, @@ -459,6 +469,10 @@ nvd0_disp_base_ctor(struct nouveau_object *parent, if (ret) return ret; + priv->base.vblank->priv = priv; + priv->base.vblank->enable = nvd0_disp_base_vblank_enable; + priv->base.vblank->disable = nvd0_disp_base_vblank_disable; + return nouveau_ramht_new(parent, parent, 0x1000, 0, &base->ramht); } @@ -609,13 +623,24 @@ exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, } static bool -exec_script(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, int id) +exec_script(struct nv50_disp_priv *priv, int head, int id) { struct nouveau_bios *bios = nouveau_bios(priv); struct nvbios_outp info; struct dcb_output dcb; u8 ver, hdr, cnt, len; + u32 ctrl = 0x00000000; u16 data; + int outp; + + for (outp = 0; !(ctrl & (1 << head)) && outp < 8; outp++) { + ctrl = nv_rd32(priv, 0x640180 + (outp * 0x20)); + if (ctrl & (1 << head)) + break; + } + + if (outp == 8) + return false; data = exec_lookup(priv, head, outp, ctrl, &dcb, &ver, &hdr, &cnt, &len, &info); if (data) { @@ -635,21 +660,31 @@ exec_script(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl, int id) } static u32 -exec_clkcmp(struct nv50_disp_priv *priv, int head, int outp, - u32 ctrl, int id, u32 pclk) +exec_clkcmp(struct nv50_disp_priv *priv, int head, int id, + u32 pclk, struct dcb_output *dcb) { struct nouveau_bios *bios = nouveau_bios(priv); struct nvbios_outp info1; struct nvbios_ocfg info2; - struct dcb_output dcb; u8 ver, hdr, cnt, len; - u16 data, conf; + u32 ctrl = 0x00000000; + u32 data, conf = ~0; + int outp; - data = exec_lookup(priv, head, outp, ctrl, &dcb, &ver, &hdr, &cnt, &len, &info1); - if (data == 0x0000) + for (outp = 0; !(ctrl & (1 << head)) && outp < 8; outp++) { + ctrl = nv_rd32(priv, 0x660180 + (outp * 0x20)); + if (ctrl & (1 << head)) + break; + } + + if (outp == 8) return false; - switch (dcb.type) { + data = exec_lookup(priv, head, outp, ctrl, dcb, &ver, &hdr, &cnt, &len, &info1); + if (data == 0x0000) + return conf; + + switch (dcb->type) { case DCB_OUTPUT_TMDS: conf = (ctrl & 0x00000f00) >> 8; if (pclk >= 165000) @@ -668,46 +703,52 @@ exec_clkcmp(struct nv50_disp_priv *priv, int head, int outp, } data = nvbios_ocfg_match(bios, data, conf, &ver, &hdr, &cnt, &len, &info2); - if (data) { + if (data && id < 0xff) { data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk); if (data) { struct nvbios_init init = { .subdev = nv_subdev(priv), .bios = bios, .offset = data, - .outp = &dcb, + .outp = dcb, .crtc = head, .execute = 1, }; - if (nvbios_exec(&init)) - return 0x0000; - return conf; + nvbios_exec(&init); } } - return 0x0000; + return conf; } static void -nvd0_display_unk1_handler(struct nv50_disp_priv *priv, u32 head, u32 mask) +nvd0_disp_intr_unk1_0(struct nv50_disp_priv *priv, int head) { - int i; + exec_script(priv, head, 1); +} - for (i = 0; mask && i < 8; i++) { - u32 mcc = nv_rd32(priv, 0x640180 + (i * 0x20)); - if (mcc & (1 << head)) - exec_script(priv, head, i, mcc, 1); - } +static void +nvd0_disp_intr_unk2_0(struct nv50_disp_priv *priv, int head) +{ + exec_script(priv, head, 2); +} - nv_wr32(priv, 0x6101d4, 0x00000000); - nv_wr32(priv, 0x6109d4, 0x00000000); - nv_wr32(priv, 0x6101d0, 0x80000000); +static void +nvd0_disp_intr_unk2_1(struct nv50_disp_priv *priv, int head) +{ + struct nouveau_clock *clk = nouveau_clock(priv); + u32 pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; + if (pclk) + clk->pll_set(clk, PLL_VPLL0 + head, pclk); + nv_wr32(priv, 0x612200 + (head * 0x800), 0x00000000); } static void -nvd0_display_unk2_calc_tu(struct nv50_disp_priv *priv, int head, int or) +nvd0_disp_intr_unk2_2_tu(struct nv50_disp_priv *priv, int head, + struct dcb_output *outp) { + const int or = ffs(outp->or) - 1; const u32 ctrl = nv_rd32(priv, 0x660200 + (or * 0x020)); const u32 conf = nv_rd32(priv, 0x660404 + (head * 0x300)); const u32 pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; @@ -750,105 +791,102 @@ nvd0_display_unk2_calc_tu(struct nv50_disp_priv *priv, int head, int or) } static void -nvd0_display_unk2_handler(struct nv50_disp_priv *priv, u32 head, u32 mask) +nvd0_disp_intr_unk2_2(struct nv50_disp_priv *priv, int head) { - u32 pclk; - int i; - - for (i = 0; mask && i < 8; i++) { - u32 mcc = nv_rd32(priv, 0x640180 + (i * 0x20)); - if (mcc & (1 << head)) - exec_script(priv, head, i, mcc, 2); - } + struct dcb_output outp; + u32 pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; + u32 conf = exec_clkcmp(priv, head, 0xff, pclk, &outp); + if (conf != ~0) { + u32 addr, data; + + if (outp.type == DCB_OUTPUT_DP) { + u32 sync = nv_rd32(priv, 0x660404 + (head * 0x300)); + switch ((sync & 0x000003c0) >> 6) { + case 6: pclk = pclk * 30 / 8; break; + case 5: pclk = pclk * 24 / 8; break; + case 2: + default: + pclk = pclk * 18 / 8; + break; + } - pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; - nv_debug(priv, "head %d pclk %d mask 0x%08x\n", head, pclk, mask); - if (pclk && (mask & 0x00010000)) { - struct nouveau_clock *clk = nouveau_clock(priv); - clk->pll_set(clk, PLL_VPLL0 + head, pclk); - } + nouveau_dp_train(&priv->base, priv->sor.dp, + &outp, head, pclk); + } - nv_wr32(priv, 0x612200 + (head * 0x800), 0x00000000); + exec_clkcmp(priv, head, 0, pclk, &outp); - for (i = 0; mask && i < 8; i++) { - u32 mcp = nv_rd32(priv, 0x660180 + (i * 0x20)), cfg; - if (mcp & (1 << head)) { - if ((cfg = exec_clkcmp(priv, head, i, mcp, 0, pclk))) { - u32 addr, mask, data = 0x00000000; - if (i < 4) { - addr = 0x612280 + ((i - 0) * 0x800); - mask = 0xffffffff; - } else { - switch (mcp & 0x00000f00) { - case 0x00000800: - case 0x00000900: - nvd0_display_unk2_calc_tu(priv, head, i - 4); - break; - default: - break; - } - - addr = 0x612300 + ((i - 4) * 0x800); - mask = 0x00000707; - if (cfg & 0x00000100) - data = 0x00000101; - } - nv_mask(priv, addr, mask, data); - } - break; + if (outp.type == DCB_OUTPUT_ANALOG) { + addr = 0x612280 + (ffs(outp.or) - 1) * 0x800; + data = 0x00000000; + } else { + if (outp.type == DCB_OUTPUT_DP) + nvd0_disp_intr_unk2_2_tu(priv, head, &outp); + addr = 0x612300 + (ffs(outp.or) - 1) * 0x800; + data = (conf & 0x0100) ? 0x00000101 : 0x00000000; } - } - nv_wr32(priv, 0x6101d4, 0x00000000); - nv_wr32(priv, 0x6109d4, 0x00000000); - nv_wr32(priv, 0x6101d0, 0x80000000); + nv_mask(priv, addr, 0x00000707, data); + } } static void -nvd0_display_unk4_handler(struct nv50_disp_priv *priv, u32 head, u32 mask) +nvd0_disp_intr_unk4_0(struct nv50_disp_priv *priv, int head) { - int pclk, i; - - pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; + struct dcb_output outp; + u32 pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; + exec_clkcmp(priv, head, 1, pclk, &outp); +} - for (i = 0; mask && i < 8; i++) { - u32 mcp = nv_rd32(priv, 0x660180 + (i * 0x20)); - if (mcp & (1 << head)) - exec_clkcmp(priv, head, i, mcp, 1, pclk); +void +nvd0_disp_intr_supervisor(struct work_struct *work) +{ + struct nv50_disp_priv *priv = + container_of(work, struct nv50_disp_priv, supervisor); + u32 mask[4]; + int head; + + nv_debug(priv, "supervisor %08x\n", priv->super); + for (head = 0; head < priv->head.nr; head++) { + mask[head] = nv_rd32(priv, 0x6101d4 + (head * 0x800)); + nv_debug(priv, "head %d: 0x%08x\n", head, mask[head]); } - nv_wr32(priv, 0x6101d4, 0x00000000); - nv_wr32(priv, 0x6109d4, 0x00000000); - nv_wr32(priv, 0x6101d0, 0x80000000); -} - -static void -nvd0_disp_intr_vblank(struct nv50_disp_priv *priv, int crtc) -{ - struct nouveau_bar *bar = nouveau_bar(priv); - struct nouveau_disp *disp = &priv->base; - struct nouveau_software_chan *chan, *temp; - unsigned long flags; - - spin_lock_irqsave(&disp->vblank.lock, flags); - list_for_each_entry_safe(chan, temp, &disp->vblank.list, vblank.head) { - if (chan->vblank.crtc != crtc) - continue; - - nv_wr32(priv, 0x001718, 0x80000000 | chan->vblank.channel); - bar->flush(bar); - nv_wr32(priv, 0x06000c, upper_32_bits(chan->vblank.offset)); - nv_wr32(priv, 0x060010, lower_32_bits(chan->vblank.offset)); - nv_wr32(priv, 0x060014, chan->vblank.value); - - list_del(&chan->vblank.head); - if (disp->vblank.put) - disp->vblank.put(disp->vblank.data, crtc); + if (priv->super & 0x00000001) { + for (head = 0; head < priv->head.nr; head++) { + if (!(mask[head] & 0x00001000)) + continue; + nvd0_disp_intr_unk1_0(priv, head); + } + } else + if (priv->super & 0x00000002) { + for (head = 0; head < priv->head.nr; head++) { + if (!(mask[head] & 0x00001000)) + continue; + nvd0_disp_intr_unk2_0(priv, head); + } + for (head = 0; head < priv->head.nr; head++) { + if (!(mask[head] & 0x00010000)) + continue; + nvd0_disp_intr_unk2_1(priv, head); + } + for (head = 0; head < priv->head.nr; head++) { + if (!(mask[head] & 0x00001000)) + continue; + nvd0_disp_intr_unk2_2(priv, head); + } + } else + if (priv->super & 0x00000004) { + for (head = 0; head < priv->head.nr; head++) { + if (!(mask[head] & 0x00001000)) + continue; + nvd0_disp_intr_unk4_0(priv, head); + } } - spin_unlock_irqrestore(&disp->vblank.lock, flags); - if (disp->vblank.notify) - disp->vblank.notify(disp->vblank.data, crtc); + for (head = 0; head < priv->head.nr; head++) + nv_wr32(priv, 0x6101d4 + (head * 0x800), 0x00000000); + nv_wr32(priv, 0x6101d0, 0x80000000); } void @@ -884,27 +922,11 @@ nvd0_disp_intr(struct nouveau_subdev *subdev) if (intr & 0x00100000) { u32 stat = nv_rd32(priv, 0x6100ac); - u32 mask = 0, crtc = ~0; - - while (!mask && ++crtc < priv->head.nr) - mask = nv_rd32(priv, 0x6101d4 + (crtc * 0x800)); - - if (stat & 0x00000001) { - nv_wr32(priv, 0x6100ac, 0x00000001); - nvd0_display_unk1_handler(priv, crtc, mask); - stat &= ~0x00000001; - } - - if (stat & 0x00000002) { - nv_wr32(priv, 0x6100ac, 0x00000002); - nvd0_display_unk2_handler(priv, crtc, mask); - stat &= ~0x00000002; - } - - if (stat & 0x00000004) { - nv_wr32(priv, 0x6100ac, 0x00000004); - nvd0_display_unk4_handler(priv, crtc, mask); - stat &= ~0x00000004; + if (stat & 0x00000007) { + priv->super = (stat & 0x00000007); + schedule_work(&priv->supervisor); + nv_wr32(priv, 0x6100ac, priv->super); + stat &= ~0x00000007; } if (stat) { @@ -920,7 +942,7 @@ nvd0_disp_intr(struct nouveau_subdev *subdev) if (mask & intr) { u32 stat = nv_rd32(priv, 0x6100bc + (i * 0x800)); if (stat & 0x00000001) - nvd0_disp_intr_vblank(priv, i); + nouveau_event_trigger(priv->base.vblank, i); nv_mask(priv, 0x6100bc + (i * 0x800), 0, 0); nv_rd32(priv, 0x6100c0 + (i * 0x800)); } @@ -933,10 +955,11 @@ nvd0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_object **pobject) { struct nv50_disp_priv *priv; + int heads = nv_rd32(parent, 0x022448); int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", - "display", &priv); + ret = nouveau_disp_create(parent, engine, oclass, heads, + "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) return ret; @@ -944,8 +967,9 @@ nvd0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nvd0_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nvd0_disp_intr; + INIT_WORK(&priv->supervisor, nvd0_disp_intr_supervisor); priv->sclass = nvd0_disp_sclass; - priv->head.nr = nv_rd32(priv, 0x022448); + priv->head.nr = heads; priv->dac.nr = 3; priv->sor.nr = 4; priv->dac.power = nv50_dac_power; @@ -953,14 +977,7 @@ nvd0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, priv->sor.power = nv50_sor_power; priv->sor.hda_eld = nvd0_hda_eld; priv->sor.hdmi = nvd0_hdmi_ctrl; - priv->sor.dp_train = nvd0_sor_dp_train; - priv->sor.dp_train_init = nv94_sor_dp_train_init; - priv->sor.dp_train_fini = nv94_sor_dp_train_fini; - priv->sor.dp_lnkctl = nvd0_sor_dp_lnkctl; - priv->sor.dp_drvctl = nvd0_sor_dp_drvctl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->sor.dp = &nvd0_sor_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c index 259537c4587e..20725b363d58 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nve0.c @@ -51,10 +51,11 @@ nve0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_object **pobject) { struct nv50_disp_priv *priv; + int heads = nv_rd32(parent, 0x022448); int ret; - ret = nouveau_disp_create(parent, engine, oclass, "PDISP", - "display", &priv); + ret = nouveau_disp_create(parent, engine, oclass, heads, + "PDISP", "display", &priv); *pobject = nv_object(priv); if (ret) return ret; @@ -62,8 +63,9 @@ nve0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, nv_engine(priv)->sclass = nve0_disp_base_oclass; nv_engine(priv)->cclass = &nv50_disp_cclass; nv_subdev(priv)->intr = nvd0_disp_intr; + INIT_WORK(&priv->supervisor, nvd0_disp_intr_supervisor); priv->sclass = nve0_disp_sclass; - priv->head.nr = nv_rd32(priv, 0x022448); + priv->head.nr = heads; priv->dac.nr = 3; priv->sor.nr = 4; priv->dac.power = nv50_dac_power; @@ -71,14 +73,7 @@ nve0_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine, priv->sor.power = nv50_sor_power; priv->sor.hda_eld = nvd0_hda_eld; priv->sor.hdmi = nvd0_hdmi_ctrl; - priv->sor.dp_train = nvd0_sor_dp_train; - priv->sor.dp_train_init = nv94_sor_dp_train_init; - priv->sor.dp_train_fini = nv94_sor_dp_train_fini; - priv->sor.dp_lnkctl = nvd0_sor_dp_lnkctl; - priv->sor.dp_drvctl = nvd0_sor_dp_drvctl; - - INIT_LIST_HEAD(&priv->base.vblank.list); - spin_lock_init(&priv->base.vblank.lock); + priv->sor.dp = &nvd0_sor_dp_func; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/piornv50.c b/drivers/gpu/drm/nouveau/core/engine/disp/piornv50.c new file mode 100644 index 000000000000..2c8ce351b52d --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/engine/disp/piornv50.c @@ -0,0 +1,140 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <core/os.h> +#include <core/class.h> + +#include <subdev/bios.h> +#include <subdev/bios/dcb.h> +#include <subdev/timer.h> +#include <subdev/i2c.h> + +#include "nv50.h" + +/****************************************************************************** + * DisplayPort + *****************************************************************************/ +static struct nouveau_i2c_port * +nv50_pior_dp_find(struct nouveau_disp *disp, struct dcb_output *outp) +{ + struct nouveau_i2c *i2c = nouveau_i2c(disp); + return i2c->find_type(i2c, NV_I2C_TYPE_EXTAUX(outp->extdev)); +} + +static int +nv50_pior_dp_pattern(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int pattern) +{ + struct nouveau_i2c_port *port; + int ret = -EINVAL; + + port = nv50_pior_dp_find(disp, outp); + if (port) { + if (port->func->pattern) + ret = port->func->pattern(port, pattern); + else + ret = 0; + } + + return ret; +} + +static int +nv50_pior_dp_lnk_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int lane_nr, int link_bw, bool enh) +{ + struct nouveau_i2c_port *port; + int ret = -EINVAL; + + port = nv50_pior_dp_find(disp, outp); + if (port && port->func->lnk_ctl) + ret = port->func->lnk_ctl(port, lane_nr, link_bw, enh); + + return ret; +} + +static int +nv50_pior_dp_drv_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int lane, int vsw, int pre) +{ + struct nouveau_i2c_port *port; + int ret = -EINVAL; + + port = nv50_pior_dp_find(disp, outp); + if (port) { + if (port->func->drv_ctl) + ret = port->func->drv_ctl(port, lane, vsw, pre); + else + ret = 0; + } + + return ret; +} + +const struct nouveau_dp_func +nv50_pior_dp_func = { + .pattern = nv50_pior_dp_pattern, + .lnk_ctl = nv50_pior_dp_lnk_ctl, + .drv_ctl = nv50_pior_dp_drv_ctl, +}; + +/****************************************************************************** + * General PIOR handling + *****************************************************************************/ +int +nv50_pior_power(struct nv50_disp_priv *priv, int or, u32 data) +{ + const u32 stat = data & NV50_DISP_PIOR_PWR_STATE; + const u32 soff = (or * 0x800); + nv_wait(priv, 0x61e004 + soff, 0x80000000, 0x00000000); + nv_mask(priv, 0x61e004 + soff, 0x80000101, 0x80000000 | stat); + nv_wait(priv, 0x61e004 + soff, 0x80000000, 0x00000000); + return 0; +} + +int +nv50_pior_mthd(struct nouveau_object *object, u32 mthd, void *args, u32 size) +{ + struct nv50_disp_priv *priv = (void *)object->engine; + const u8 type = (mthd & NV50_DISP_PIOR_MTHD_TYPE) >> 12; + const u8 or = (mthd & NV50_DISP_PIOR_MTHD_OR); + u32 *data = args; + int ret; + + if (size < sizeof(u32)) + return -EINVAL; + + mthd &= ~NV50_DISP_PIOR_MTHD_TYPE; + mthd &= ~NV50_DISP_PIOR_MTHD_OR; + switch (mthd) { + case NV50_DISP_PIOR_PWR: + ret = priv->pior.power(priv, or, data[0]); + priv->pior.type[or] = type; + break; + default: + return -EINVAL; + } + + return ret; +} diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c b/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c index 39b6b67732d0..ab1e918469a8 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/sornv50.c @@ -79,31 +79,6 @@ nv50_sor_mthd(struct nouveau_object *object, u32 mthd, void *args, u32 size) priv->sor.lvdsconf = data & NV50_DISP_SOR_LVDS_SCRIPT_ID; ret = 0; break; - case NV94_DISP_SOR_DP_TRAIN: - switch (data & NV94_DISP_SOR_DP_TRAIN_OP) { - case NV94_DISP_SOR_DP_TRAIN_OP_PATTERN: - ret = priv->sor.dp_train(priv, or, link, type, mask, data, &outp); - break; - case NV94_DISP_SOR_DP_TRAIN_OP_INIT: - ret = priv->sor.dp_train_init(priv, or, link, head, type, mask, data, &outp); - break; - case NV94_DISP_SOR_DP_TRAIN_OP_FINI: - ret = priv->sor.dp_train_fini(priv, or, link, head, type, mask, data, &outp); - break; - default: - break; - } - break; - case NV94_DISP_SOR_DP_LNKCTL: - ret = priv->sor.dp_lnkctl(priv, or, link, head, type, mask, data, &outp); - break; - case NV94_DISP_SOR_DP_DRVCTL(0): - case NV94_DISP_SOR_DP_DRVCTL(1): - case NV94_DISP_SOR_DP_DRVCTL(2): - case NV94_DISP_SOR_DP_DRVCTL(3): - ret = priv->sor.dp_drvctl(priv, or, link, (mthd & 0xc0) >> 6, - type, mask, data, &outp); - break; default: BUG_ON(1); } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/sornv94.c b/drivers/gpu/drm/nouveau/core/engine/disp/sornv94.c index f6edd009762e..7ec4ee83fb64 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/sornv94.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/sornv94.c @@ -33,124 +33,53 @@ #include "nv50.h" static inline u32 -nv94_sor_dp_lane_map(struct nv50_disp_priv *priv, u8 lane) +nv94_sor_soff(struct dcb_output *outp) { - static const u8 nvaf[] = { 24, 16, 8, 0 }; /* thanks, apple.. */ - static const u8 nv94[] = { 16, 8, 0, 24 }; - if (nv_device(priv)->chipset == 0xaf) - return nvaf[lane]; - return nv94[lane]; + return (ffs(outp->or) - 1) * 0x800; } -int -nv94_sor_dp_train_init(struct nv50_disp_priv *priv, int or, int link, int head, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static inline u32 +nv94_sor_loff(struct dcb_output *outp) { - struct nouveau_bios *bios = nouveau_bios(priv); - struct nvbios_dpout info; - u8 ver, hdr, cnt, len; - u16 outp; - - outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info); - if (outp) { - struct nvbios_init init = { - .subdev = nv_subdev(priv), - .bios = bios, - .outp = dcbo, - .crtc = head, - .execute = 1, - }; - - if (data & NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_ON) - init.offset = info.script[2]; - else - init.offset = info.script[3]; - nvbios_exec(&init); - - init.offset = info.script[0]; - nvbios_exec(&init); - } - - return 0; + return nv94_sor_soff(outp) + !(outp->sorconf.link & 1) * 0x80; } -int -nv94_sor_dp_train_fini(struct nv50_disp_priv *priv, int or, int link, int head, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static inline u32 +nv94_sor_dp_lane_map(struct nv50_disp_priv *priv, u8 lane) { - struct nouveau_bios *bios = nouveau_bios(priv); - struct nvbios_dpout info; - u8 ver, hdr, cnt, len; - u16 outp; - - outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info); - if (outp) { - struct nvbios_init init = { - .subdev = nv_subdev(priv), - .bios = bios, - .offset = info.script[1], - .outp = dcbo, - .crtc = head, - .execute = 1, - }; - - nvbios_exec(&init); - } - - return 0; + static const u8 nvaf[] = { 24, 16, 8, 0 }; /* thanks, apple.. */ + static const u8 nv94[] = { 16, 8, 0, 24 }; + if (nv_device(priv)->chipset == 0xaf) + return nvaf[lane]; + return nv94[lane]; } -int -nv94_sor_dp_train(struct nv50_disp_priv *priv, int or, int link, - u16 type, u16 mask, u32 data, struct dcb_output *info) +static int +nv94_sor_dp_pattern(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int pattern) { - const u32 loff = (or * 0x800) + (link * 0x80); - const u32 patt = (data & NV94_DISP_SOR_DP_TRAIN_PATTERN); - nv_mask(priv, 0x61c10c + loff, 0x0f000000, patt << 24); + struct nv50_disp_priv *priv = (void *)disp; + const u32 loff = nv94_sor_loff(outp); + nv_mask(priv, 0x61c10c + loff, 0x0f000000, pattern << 24); return 0; } -int -nv94_sor_dp_lnkctl(struct nv50_disp_priv *priv, int or, int link, int head, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static int +nv94_sor_dp_lnk_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int link_nr, int link_bw, bool enh_frame) { - struct nouveau_bios *bios = nouveau_bios(priv); - const u32 loff = (or * 0x800) + (link * 0x80); - const u32 soff = (or * 0x800); - u16 link_bw = (data & NV94_DISP_SOR_DP_LNKCTL_WIDTH) >> 8; - u8 link_nr = (data & NV94_DISP_SOR_DP_LNKCTL_COUNT); + struct nv50_disp_priv *priv = (void *)disp; + const u32 soff = nv94_sor_soff(outp); + const u32 loff = nv94_sor_loff(outp); u32 dpctrl = 0x00000000; u32 clksor = 0x00000000; - u32 outp, lane = 0; - u8 ver, hdr, cnt, len; - struct nvbios_dpout info; + u32 lane = 0; int i; - /* -> 10Khz units */ - link_bw *= 2700; - - outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info); - if (outp && info.lnkcmp) { - struct nvbios_init init = { - .subdev = nv_subdev(priv), - .bios = bios, - .offset = 0x0000, - .outp = dcbo, - .crtc = head, - .execute = 1, - }; - - while (link_bw < nv_ro16(bios, info.lnkcmp)) - info.lnkcmp += 4; - init.offset = nv_ro16(bios, info.lnkcmp + 2); - - nvbios_exec(&init); - } - dpctrl |= ((1 << link_nr) - 1) << 16; - if (data & NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH) + if (enh_frame) dpctrl |= 0x00004000; - if (link_bw > 16200) + if (link_bw > 0x06) clksor |= 0x00040000; for (i = 0; i < link_nr; i++) @@ -162,24 +91,25 @@ nv94_sor_dp_lnkctl(struct nv50_disp_priv *priv, int or, int link, int head, return 0; } -int -nv94_sor_dp_drvctl(struct nv50_disp_priv *priv, int or, int link, int lane, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static int +nv94_sor_dp_drv_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int lane, int swing, int preem) { - struct nouveau_bios *bios = nouveau_bios(priv); - const u32 loff = (or * 0x800) + (link * 0x80); - const u8 swing = (data & NV94_DISP_SOR_DP_DRVCTL_VS) >> 8; - const u8 preem = (data & NV94_DISP_SOR_DP_DRVCTL_PE); + struct nouveau_bios *bios = nouveau_bios(disp); + struct nv50_disp_priv *priv = (void *)disp; + const u32 loff = nv94_sor_loff(outp); u32 addr, shift = nv94_sor_dp_lane_map(priv, lane); u8 ver, hdr, cnt, len; - struct nvbios_dpout outp; + struct nvbios_dpout info; struct nvbios_dpcfg ocfg; - addr = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &outp); + addr = nvbios_dpout_match(bios, outp->hasht, outp->hashm, + &ver, &hdr, &cnt, &len, &info); if (!addr) return -ENODEV; - addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem, &ver, &hdr, &cnt, &len, &ocfg); + addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem, + &ver, &hdr, &cnt, &len, &ocfg); if (!addr) return -EINVAL; @@ -188,3 +118,10 @@ nv94_sor_dp_drvctl(struct nv50_disp_priv *priv, int or, int link, int lane, nv_mask(priv, 0x61c130 + loff, 0x0000ff00, ocfg.unk << 8); return 0; } + +const struct nouveau_dp_func +nv94_sor_dp_func = { + .pattern = nv94_sor_dp_pattern, + .lnk_ctl = nv94_sor_dp_lnk_ctl, + .drv_ctl = nv94_sor_dp_drv_ctl, +}; diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/sornvd0.c b/drivers/gpu/drm/nouveau/core/engine/disp/sornvd0.c index c37ce7e29f5d..9e1d435d7282 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/sornvd0.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/sornvd0.c @@ -33,59 +33,49 @@ #include "nv50.h" static inline u32 +nvd0_sor_soff(struct dcb_output *outp) +{ + return (ffs(outp->or) - 1) * 0x800; +} + +static inline u32 +nvd0_sor_loff(struct dcb_output *outp) +{ + return nvd0_sor_soff(outp) + !(outp->sorconf.link & 1) * 0x80; +} + +static inline u32 nvd0_sor_dp_lane_map(struct nv50_disp_priv *priv, u8 lane) { static const u8 nvd0[] = { 16, 8, 0, 24 }; return nvd0[lane]; } -int -nvd0_sor_dp_train(struct nv50_disp_priv *priv, int or, int link, - u16 type, u16 mask, u32 data, struct dcb_output *info) +static int +nvd0_sor_dp_pattern(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int pattern) { - const u32 loff = (or * 0x800) + (link * 0x80); - const u32 patt = (data & NV94_DISP_SOR_DP_TRAIN_PATTERN); - nv_mask(priv, 0x61c110 + loff, 0x0f0f0f0f, 0x01010101 * patt); + struct nv50_disp_priv *priv = (void *)disp; + const u32 loff = nvd0_sor_loff(outp); + nv_mask(priv, 0x61c110 + loff, 0x0f0f0f0f, 0x01010101 * pattern); return 0; } -int -nvd0_sor_dp_lnkctl(struct nv50_disp_priv *priv, int or, int link, int head, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static int +nvd0_sor_dp_lnk_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int link_nr, int link_bw, bool enh_frame) { - struct nouveau_bios *bios = nouveau_bios(priv); - const u32 loff = (or * 0x800) + (link * 0x80); - const u32 soff = (or * 0x800); - const u8 link_bw = (data & NV94_DISP_SOR_DP_LNKCTL_WIDTH) >> 8; - const u8 link_nr = (data & NV94_DISP_SOR_DP_LNKCTL_COUNT); + struct nv50_disp_priv *priv = (void *)disp; + const u32 soff = nvd0_sor_soff(outp); + const u32 loff = nvd0_sor_loff(outp); u32 dpctrl = 0x00000000; u32 clksor = 0x00000000; - u32 outp, lane = 0; - u8 ver, hdr, cnt, len; - struct nvbios_dpout info; + u32 lane = 0; int i; - outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info); - if (outp && info.lnkcmp) { - struct nvbios_init init = { - .subdev = nv_subdev(priv), - .bios = bios, - .offset = 0x0000, - .outp = dcbo, - .crtc = head, - .execute = 1, - }; - - while (nv_ro08(bios, info.lnkcmp) < link_bw) - info.lnkcmp += 3; - init.offset = nv_ro16(bios, info.lnkcmp + 1); - - nvbios_exec(&init); - } - clksor |= link_bw << 18; dpctrl |= ((1 << link_nr) - 1) << 16; - if (data & NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH) + if (enh_frame) dpctrl |= 0x00004000; for (i = 0; i < link_nr; i++) @@ -97,24 +87,25 @@ nvd0_sor_dp_lnkctl(struct nv50_disp_priv *priv, int or, int link, int head, return 0; } -int -nvd0_sor_dp_drvctl(struct nv50_disp_priv *priv, int or, int link, int lane, - u16 type, u16 mask, u32 data, struct dcb_output *dcbo) +static int +nvd0_sor_dp_drv_ctl(struct nouveau_disp *disp, struct dcb_output *outp, + int head, int lane, int swing, int preem) { - struct nouveau_bios *bios = nouveau_bios(priv); - const u32 loff = (or * 0x800) + (link * 0x80); - const u8 swing = (data & NV94_DISP_SOR_DP_DRVCTL_VS) >> 8; - const u8 preem = (data & NV94_DISP_SOR_DP_DRVCTL_PE); + struct nouveau_bios *bios = nouveau_bios(disp); + struct nv50_disp_priv *priv = (void *)disp; + const u32 loff = nvd0_sor_loff(outp); u32 addr, shift = nvd0_sor_dp_lane_map(priv, lane); u8 ver, hdr, cnt, len; - struct nvbios_dpout outp; + struct nvbios_dpout info; struct nvbios_dpcfg ocfg; - addr = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &outp); + addr = nvbios_dpout_match(bios, outp->hasht, outp->hashm, + &ver, &hdr, &cnt, &len, &info); if (!addr) return -ENODEV; - addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem, &ver, &hdr, &cnt, &len, &ocfg); + addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem, + &ver, &hdr, &cnt, &len, &ocfg); if (!addr) return -EINVAL; @@ -124,3 +115,10 @@ nvd0_sor_dp_drvctl(struct nv50_disp_priv *priv, int or, int link, int lane, nv_mask(priv, 0x61c13c + loff, 0x00000000, 0x00000000); return 0; } + +const struct nouveau_dp_func +nvd0_sor_dp_func = { + .pattern = nvd0_sor_dp_pattern, + .lnk_ctl = nvd0_sor_dp_lnk_ctl, + .drv_ctl = nvd0_sor_dp_drv_ctl, +}; diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/base.c b/drivers/gpu/drm/nouveau/core/engine/fifo/base.c index c2b9db335816..7341ebe131fa 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/base.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/base.c @@ -22,8 +22,10 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/object.h> #include <core/handle.h> +#include <core/event.h> #include <core/class.h> #include <engine/dmaobj.h> @@ -146,10 +148,25 @@ nouveau_fifo_chid(struct nouveau_fifo *priv, struct nouveau_object *object) return -1; } +const char * +nouveau_client_name_for_fifo_chid(struct nouveau_fifo *fifo, u32 chid) +{ + struct nouveau_fifo_chan *chan = NULL; + unsigned long flags; + + spin_lock_irqsave(&fifo->lock, flags); + if (chid >= fifo->min && chid <= fifo->max) + chan = (void *)fifo->channel[chid]; + spin_unlock_irqrestore(&fifo->lock, flags); + + return nouveau_client_name(chan); +} + void nouveau_fifo_destroy(struct nouveau_fifo *priv) { kfree(priv->channel); + nouveau_event_destroy(&priv->uevent); nouveau_engine_destroy(&priv->base); } @@ -174,6 +191,10 @@ nouveau_fifo_create_(struct nouveau_object *parent, if (!priv->channel) return -ENOMEM; + ret = nouveau_event_create(1, &priv->uevent); + if (ret) + return ret; + priv->chid = nouveau_fifo_chid; spin_lock_init(&priv->lock); return 0; diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c index a47a8548f9e0..f877bd524a92 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nv04.c @@ -28,6 +28,7 @@ #include <core/namedb.h> #include <core/handle.h> #include <core/ramht.h> +#include <core/event.h> #include <subdev/instmem.h> #include <subdev/instmem/nv04.h> @@ -398,6 +399,98 @@ out: return handled; } +static void +nv04_fifo_cache_error(struct nouveau_device *device, + struct nv04_fifo_priv *priv, u32 chid, u32 get) +{ + u32 mthd, data; + int ptr; + + /* NV_PFIFO_CACHE1_GET actually goes to 0xffc before wrapping on my + * G80 chips, but CACHE1 isn't big enough for this much data.. Tests + * show that it wraps around to the start at GET=0x800.. No clue as to + * why.. + */ + ptr = (get & 0x7ff) >> 2; + + if (device->card_type < NV_40) { + mthd = nv_rd32(priv, NV04_PFIFO_CACHE1_METHOD(ptr)); + data = nv_rd32(priv, NV04_PFIFO_CACHE1_DATA(ptr)); + } else { + mthd = nv_rd32(priv, NV40_PFIFO_CACHE1_METHOD(ptr)); + data = nv_rd32(priv, NV40_PFIFO_CACHE1_DATA(ptr)); + } + + if (!nv04_fifo_swmthd(priv, chid, mthd, data)) { + const char *client_name = + nouveau_client_name_for_fifo_chid(&priv->base, chid); + nv_error(priv, + "CACHE_ERROR - ch %d [%s] subc %d mthd 0x%04x data 0x%08x\n", + chid, client_name, (mthd >> 13) & 7, mthd & 0x1ffc, + data); + } + + nv_wr32(priv, NV04_PFIFO_CACHE1_DMA_PUSH, 0); + nv_wr32(priv, NV03_PFIFO_INTR_0, NV_PFIFO_INTR_CACHE_ERROR); + + nv_wr32(priv, NV03_PFIFO_CACHE1_PUSH0, + nv_rd32(priv, NV03_PFIFO_CACHE1_PUSH0) & ~1); + nv_wr32(priv, NV03_PFIFO_CACHE1_GET, get + 4); + nv_wr32(priv, NV03_PFIFO_CACHE1_PUSH0, + nv_rd32(priv, NV03_PFIFO_CACHE1_PUSH0) | 1); + nv_wr32(priv, NV04_PFIFO_CACHE1_HASH, 0); + + nv_wr32(priv, NV04_PFIFO_CACHE1_DMA_PUSH, + nv_rd32(priv, NV04_PFIFO_CACHE1_DMA_PUSH) | 1); + nv_wr32(priv, NV04_PFIFO_CACHE1_PULL0, 1); +} + +static void +nv04_fifo_dma_pusher(struct nouveau_device *device, struct nv04_fifo_priv *priv, + u32 chid) +{ + const char *client_name; + u32 dma_get = nv_rd32(priv, 0x003244); + u32 dma_put = nv_rd32(priv, 0x003240); + u32 push = nv_rd32(priv, 0x003220); + u32 state = nv_rd32(priv, 0x003228); + + client_name = nouveau_client_name_for_fifo_chid(&priv->base, chid); + + if (device->card_type == NV_50) { + u32 ho_get = nv_rd32(priv, 0x003328); + u32 ho_put = nv_rd32(priv, 0x003320); + u32 ib_get = nv_rd32(priv, 0x003334); + u32 ib_put = nv_rd32(priv, 0x003330); + + nv_error(priv, + "DMA_PUSHER - ch %d [%s] get 0x%02x%08x put 0x%02x%08x ib_get 0x%08x ib_put 0x%08x state 0x%08x (err: %s) push 0x%08x\n", + chid, client_name, ho_get, dma_get, ho_put, dma_put, + ib_get, ib_put, state, nv_dma_state_err(state), push); + + /* METHOD_COUNT, in DMA_STATE on earlier chipsets */ + nv_wr32(priv, 0x003364, 0x00000000); + if (dma_get != dma_put || ho_get != ho_put) { + nv_wr32(priv, 0x003244, dma_put); + nv_wr32(priv, 0x003328, ho_put); + } else + if (ib_get != ib_put) + nv_wr32(priv, 0x003334, ib_put); + } else { + nv_error(priv, + "DMA_PUSHER - ch %d [%s] get 0x%08x put 0x%08x state 0x%08x (err: %s) push 0x%08x\n", + chid, client_name, dma_get, dma_put, state, + nv_dma_state_err(state), push); + + if (dma_get != dma_put) + nv_wr32(priv, 0x003244, dma_put); + } + + nv_wr32(priv, 0x003228, 0x00000000); + nv_wr32(priv, 0x003220, 0x00000001); + nv_wr32(priv, 0x002100, NV_PFIFO_INTR_DMA_PUSHER); +} + void nv04_fifo_intr(struct nouveau_subdev *subdev) { @@ -416,96 +509,12 @@ nv04_fifo_intr(struct nouveau_subdev *subdev) get = nv_rd32(priv, NV03_PFIFO_CACHE1_GET); if (status & NV_PFIFO_INTR_CACHE_ERROR) { - uint32_t mthd, data; - int ptr; - - /* NV_PFIFO_CACHE1_GET actually goes to 0xffc before - * wrapping on my G80 chips, but CACHE1 isn't big - * enough for this much data.. Tests show that it - * wraps around to the start at GET=0x800.. No clue - * as to why.. - */ - ptr = (get & 0x7ff) >> 2; - - if (device->card_type < NV_40) { - mthd = nv_rd32(priv, - NV04_PFIFO_CACHE1_METHOD(ptr)); - data = nv_rd32(priv, - NV04_PFIFO_CACHE1_DATA(ptr)); - } else { - mthd = nv_rd32(priv, - NV40_PFIFO_CACHE1_METHOD(ptr)); - data = nv_rd32(priv, - NV40_PFIFO_CACHE1_DATA(ptr)); - } - - if (!nv04_fifo_swmthd(priv, chid, mthd, data)) { - nv_error(priv, "CACHE_ERROR - Ch %d/%d " - "Mthd 0x%04x Data 0x%08x\n", - chid, (mthd >> 13) & 7, mthd & 0x1ffc, - data); - } - - nv_wr32(priv, NV04_PFIFO_CACHE1_DMA_PUSH, 0); - nv_wr32(priv, NV03_PFIFO_INTR_0, - NV_PFIFO_INTR_CACHE_ERROR); - - nv_wr32(priv, NV03_PFIFO_CACHE1_PUSH0, - nv_rd32(priv, NV03_PFIFO_CACHE1_PUSH0) & ~1); - nv_wr32(priv, NV03_PFIFO_CACHE1_GET, get + 4); - nv_wr32(priv, NV03_PFIFO_CACHE1_PUSH0, - nv_rd32(priv, NV03_PFIFO_CACHE1_PUSH0) | 1); - nv_wr32(priv, NV04_PFIFO_CACHE1_HASH, 0); - - nv_wr32(priv, NV04_PFIFO_CACHE1_DMA_PUSH, - nv_rd32(priv, NV04_PFIFO_CACHE1_DMA_PUSH) | 1); - nv_wr32(priv, NV04_PFIFO_CACHE1_PULL0, 1); - + nv04_fifo_cache_error(device, priv, chid, get); status &= ~NV_PFIFO_INTR_CACHE_ERROR; } if (status & NV_PFIFO_INTR_DMA_PUSHER) { - u32 dma_get = nv_rd32(priv, 0x003244); - u32 dma_put = nv_rd32(priv, 0x003240); - u32 push = nv_rd32(priv, 0x003220); - u32 state = nv_rd32(priv, 0x003228); - - if (device->card_type == NV_50) { - u32 ho_get = nv_rd32(priv, 0x003328); - u32 ho_put = nv_rd32(priv, 0x003320); - u32 ib_get = nv_rd32(priv, 0x003334); - u32 ib_put = nv_rd32(priv, 0x003330); - - nv_error(priv, "DMA_PUSHER - Ch %d Get 0x%02x%08x " - "Put 0x%02x%08x IbGet 0x%08x IbPut 0x%08x " - "State 0x%08x (err: %s) Push 0x%08x\n", - chid, ho_get, dma_get, ho_put, - dma_put, ib_get, ib_put, state, - nv_dma_state_err(state), - push); - - /* METHOD_COUNT, in DMA_STATE on earlier chipsets */ - nv_wr32(priv, 0x003364, 0x00000000); - if (dma_get != dma_put || ho_get != ho_put) { - nv_wr32(priv, 0x003244, dma_put); - nv_wr32(priv, 0x003328, ho_put); - } else - if (ib_get != ib_put) { - nv_wr32(priv, 0x003334, ib_put); - } - } else { - nv_error(priv, "DMA_PUSHER - Ch %d Get 0x%08x " - "Put 0x%08x State 0x%08x (err: %s) Push 0x%08x\n", - chid, dma_get, dma_put, state, - nv_dma_state_err(state), push); - - if (dma_get != dma_put) - nv_wr32(priv, 0x003244, dma_put); - } - - nv_wr32(priv, 0x003228, 0x00000000); - nv_wr32(priv, 0x003220, 0x00000001); - nv_wr32(priv, 0x002100, NV_PFIFO_INTR_DMA_PUSHER); + nv04_fifo_dma_pusher(device, priv, chid); status &= ~NV_PFIFO_INTR_DMA_PUSHER; } @@ -528,6 +537,12 @@ nv04_fifo_intr(struct nouveau_subdev *subdev) status &= ~0x00000010; nv_wr32(priv, 0x002100, 0x00000010); } + + if (status & 0x40000000) { + nouveau_event_trigger(priv->base.uevent, 0); + nv_wr32(priv, 0x002100, 0x40000000); + status &= ~0x40000000; + } } if (status) { diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nv50.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nv50.c index bd096364f680..840af6172788 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/nv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nv50.c @@ -129,7 +129,8 @@ nv50_fifo_context_detach(struct nouveau_object *parent, bool suspend, /* do the kickoff... */ nv_wr32(priv, 0x0032fc, nv_gpuobj(base)->addr >> 12); if (!nv_wait_ne(priv, 0x0032fc, 0xffffffff, 0xffffffff)) { - nv_error(priv, "channel %d unload timeout\n", chan->base.chid); + nv_error(priv, "channel %d [%s] unload timeout\n", + chan->base.chid, nouveau_client_name(chan)); if (suspend) ret = -EBUSY; } @@ -480,7 +481,7 @@ nv50_fifo_init(struct nouveau_object *object) nv_wr32(priv, 0x002044, 0x01003fff); nv_wr32(priv, 0x002100, 0xffffffff); - nv_wr32(priv, 0x002140, 0xffffffff); + nv_wr32(priv, 0x002140, 0xbfffffff); for (i = 0; i < 128; i++) nv_wr32(priv, 0x002600 + (i * 4), 0x00000000); diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c index 1eb1c512f503..094000e87871 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nv84.c @@ -26,6 +26,7 @@ #include <core/client.h> #include <core/engctx.h> #include <core/ramht.h> +#include <core/event.h> #include <core/class.h> #include <core/math.h> @@ -100,7 +101,8 @@ nv84_fifo_context_detach(struct nouveau_object *parent, bool suspend, done = nv_wait_ne(priv, 0x0032fc, 0xffffffff, 0xffffffff); nv_wr32(priv, 0x002520, save); if (!done) { - nv_error(priv, "channel %d unload timeout\n", chan->base.chid); + nv_error(priv, "channel %d [%s] unload timeout\n", + chan->base.chid, nouveau_client_name(chan)); if (suspend) return -EBUSY; } @@ -378,6 +380,20 @@ nv84_fifo_cclass = { * PFIFO engine ******************************************************************************/ +static void +nv84_fifo_uevent_enable(struct nouveau_event *event, int index) +{ + struct nv84_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x40000000, 0x40000000); +} + +static void +nv84_fifo_uevent_disable(struct nouveau_event *event, int index) +{ + struct nv84_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x40000000, 0x00000000); +} + static int nv84_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -401,6 +417,10 @@ nv84_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, if (ret) return ret; + priv->base.uevent->enable = nv84_fifo_uevent_enable; + priv->base.uevent->disable = nv84_fifo_uevent_disable; + priv->base.uevent->priv = priv; + nv_subdev(priv)->unit = 0x00000100; nv_subdev(priv)->intr = nv04_fifo_intr; nv_engine(priv)->cclass = &nv84_fifo_cclass; diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c index b4365dde1859..4f226afb5591 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nvc0.c @@ -27,6 +27,7 @@ #include <core/namedb.h> #include <core/gpuobj.h> #include <core/engctx.h> +#include <core/event.h> #include <core/class.h> #include <core/math.h> #include <core/enum.h> @@ -149,7 +150,8 @@ nvc0_fifo_context_detach(struct nouveau_object *parent, bool suspend, nv_wr32(priv, 0x002634, chan->base.chid); if (!nv_wait(priv, 0x002634, 0xffffffff, chan->base.chid)) { - nv_error(priv, "channel %d kick timeout\n", chan->base.chid); + nv_error(priv, "channel %d [%s] kick timeout\n", + chan->base.chid, nouveau_client_name(chan)); if (suspend) return -EBUSY; } @@ -333,17 +335,17 @@ nvc0_fifo_cclass = { ******************************************************************************/ static const struct nouveau_enum nvc0_fifo_fault_unit[] = { - { 0x00, "PGRAPH" }, + { 0x00, "PGRAPH", NULL, NVDEV_ENGINE_GR }, { 0x03, "PEEPHOLE" }, { 0x04, "BAR1" }, { 0x05, "BAR3" }, - { 0x07, "PFIFO" }, - { 0x10, "PBSP" }, - { 0x11, "PPPP" }, + { 0x07, "PFIFO", NULL, NVDEV_ENGINE_FIFO }, + { 0x10, "PBSP", NULL, NVDEV_ENGINE_BSP }, + { 0x11, "PPPP", NULL, NVDEV_ENGINE_PPP }, { 0x13, "PCOUNTER" }, - { 0x14, "PVP" }, - { 0x15, "PCOPY0" }, - { 0x16, "PCOPY1" }, + { 0x14, "PVP", NULL, NVDEV_ENGINE_VP }, + { 0x15, "PCOPY0", NULL, NVDEV_ENGINE_COPY0 }, + { 0x16, "PCOPY1", NULL, NVDEV_ENGINE_COPY1 }, { 0x17, "PDAEMON" }, {} }; @@ -402,6 +404,9 @@ nvc0_fifo_isr_vm_fault(struct nvc0_fifo_priv *priv, int unit) u32 vahi = nv_rd32(priv, 0x002808 + (unit * 0x10)); u32 stat = nv_rd32(priv, 0x00280c + (unit * 0x10)); u32 client = (stat & 0x00001f00) >> 8; + const struct nouveau_enum *en; + struct nouveau_engine *engine; + struct nouveau_object *engctx = NULL; switch (unit) { case 3: /* PEEPHOLE */ @@ -420,16 +425,26 @@ nvc0_fifo_isr_vm_fault(struct nvc0_fifo_priv *priv, int unit) nv_error(priv, "%s fault at 0x%010llx [", (stat & 0x00000080) ? "write" : "read", (u64)vahi << 32 | valo); nouveau_enum_print(nvc0_fifo_fault_reason, stat & 0x0000000f); - printk("] from "); - nouveau_enum_print(nvc0_fifo_fault_unit, unit); + pr_cont("] from "); + en = nouveau_enum_print(nvc0_fifo_fault_unit, unit); if (stat & 0x00000040) { - printk("/"); + pr_cont("/"); nouveau_enum_print(nvc0_fifo_fault_hubclient, client); } else { - printk("/GPC%d/", (stat & 0x1f000000) >> 24); + pr_cont("/GPC%d/", (stat & 0x1f000000) >> 24); nouveau_enum_print(nvc0_fifo_fault_gpcclient, client); } - printk(" on channel 0x%010llx\n", (u64)inst << 12); + + if (en && en->data2) { + engine = nouveau_engine(priv, en->data2); + if (engine) + engctx = nouveau_engctx_get(engine, inst); + + } + pr_cont(" on channel 0x%010llx [%s]\n", (u64)inst << 12, + nouveau_client_name(engctx)); + + nouveau_engctx_put(engctx); } static int @@ -484,10 +499,12 @@ nvc0_fifo_isr_subfifo_intr(struct nvc0_fifo_priv *priv, int unit) if (show) { nv_error(priv, "SUBFIFO%d:", unit); nouveau_bitfield_print(nvc0_fifo_subfifo_intr, show); - printk("\n"); - nv_error(priv, "SUBFIFO%d: ch %d subc %d mthd 0x%04x " - "data 0x%08x\n", - unit, chid, subc, mthd, data); + pr_cont("\n"); + nv_error(priv, + "SUBFIFO%d: ch %d [%s] subc %d mthd 0x%04x data 0x%08x\n", + unit, chid, + nouveau_client_name_for_fifo_chid(&priv->base, chid), + subc, mthd, data); } nv_wr32(priv, 0x0400c0 + (unit * 0x2000), 0x80600008); @@ -501,12 +518,34 @@ nvc0_fifo_intr(struct nouveau_subdev *subdev) u32 mask = nv_rd32(priv, 0x002140); u32 stat = nv_rd32(priv, 0x002100) & mask; + if (stat & 0x00000001) { + u32 intr = nv_rd32(priv, 0x00252c); + nv_warn(priv, "INTR 0x00000001: 0x%08x\n", intr); + nv_wr32(priv, 0x002100, 0x00000001); + stat &= ~0x00000001; + } + if (stat & 0x00000100) { - nv_warn(priv, "unknown status 0x00000100\n"); + u32 intr = nv_rd32(priv, 0x00254c); + nv_warn(priv, "INTR 0x00000100: 0x%08x\n", intr); nv_wr32(priv, 0x002100, 0x00000100); stat &= ~0x00000100; } + if (stat & 0x00010000) { + u32 intr = nv_rd32(priv, 0x00256c); + nv_warn(priv, "INTR 0x00010000: 0x%08x\n", intr); + nv_wr32(priv, 0x002100, 0x00010000); + stat &= ~0x00010000; + } + + if (stat & 0x01000000) { + u32 intr = nv_rd32(priv, 0x00258c); + nv_warn(priv, "INTR 0x01000000: 0x%08x\n", intr); + nv_wr32(priv, 0x002100, 0x01000000); + stat &= ~0x01000000; + } + if (stat & 0x10000000) { u32 units = nv_rd32(priv, 0x00259c); u32 u = units; @@ -536,11 +575,20 @@ nvc0_fifo_intr(struct nouveau_subdev *subdev) } if (stat & 0x40000000) { - nv_warn(priv, "unknown status 0x40000000\n"); - nv_mask(priv, 0x002a00, 0x00000000, 0x00000000); + u32 intr0 = nv_rd32(priv, 0x0025a4); + u32 intr1 = nv_mask(priv, 0x002a00, 0x00000000, 0x00000); + nv_debug(priv, "INTR 0x40000000: 0x%08x 0x%08x\n", + intr0, intr1); stat &= ~0x40000000; } + if (stat & 0x80000000) { + u32 intr = nv_mask(priv, 0x0025a8, 0x00000000, 0x00000000); + nouveau_event_trigger(priv->base.uevent, 0); + nv_debug(priv, "INTR 0x80000000: 0x%08x\n", intr); + stat &= ~0x80000000; + } + if (stat) { nv_fatal(priv, "unhandled status 0x%08x\n", stat); nv_wr32(priv, 0x002100, stat); @@ -548,6 +596,20 @@ nvc0_fifo_intr(struct nouveau_subdev *subdev) } } +static void +nvc0_fifo_uevent_enable(struct nouveau_event *event, int index) +{ + struct nvc0_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x80000000, 0x80000000); +} + +static void +nvc0_fifo_uevent_disable(struct nouveau_event *event, int index) +{ + struct nvc0_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x80000000, 0x00000000); +} + static int nvc0_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -581,6 +643,10 @@ nvc0_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, if (ret) return ret; + priv->base.uevent->enable = nvc0_fifo_uevent_enable; + priv->base.uevent->disable = nvc0_fifo_uevent_disable; + priv->base.uevent->priv = priv; + nv_subdev(priv)->unit = 0x00000100; nv_subdev(priv)->intr = nvc0_fifo_intr; nv_engine(priv)->cclass = &nvc0_fifo_cclass; @@ -639,7 +705,8 @@ nvc0_fifo_init(struct nouveau_object *object) nv_wr32(priv, 0x002a00, 0xffffffff); /* clears PFIFO.INTR bit 30 */ nv_wr32(priv, 0x002100, 0xffffffff); - nv_wr32(priv, 0x002140, 0xbfffffff); + nv_wr32(priv, 0x002140, 0x3fffffff); + nv_wr32(priv, 0x002628, 0x00000001); /* makes mthd 0x20 work */ return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c b/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c index c930da99c2c1..4419e40d88e9 100644 --- a/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c +++ b/drivers/gpu/drm/nouveau/core/engine/fifo/nve0.c @@ -27,6 +27,7 @@ #include <core/namedb.h> #include <core/gpuobj.h> #include <core/engctx.h> +#include <core/event.h> #include <core/class.h> #include <core/math.h> #include <core/enum.h> @@ -184,7 +185,8 @@ nve0_fifo_context_detach(struct nouveau_object *parent, bool suspend, nv_wr32(priv, 0x002634, chan->base.chid); if (!nv_wait(priv, 0x002634, 0xffffffff, chan->base.chid)) { - nv_error(priv, "channel %d kick timeout\n", chan->base.chid); + nv_error(priv, "channel %d [%s] kick timeout\n", + chan->base.chid, nouveau_client_name(chan)); if (suspend) return -EBUSY; } @@ -412,20 +414,34 @@ nve0_fifo_isr_vm_fault(struct nve0_fifo_priv *priv, int unit) u32 vahi = nv_rd32(priv, 0x2808 + (unit * 0x10)); u32 stat = nv_rd32(priv, 0x280c + (unit * 0x10)); u32 client = (stat & 0x00001f00) >> 8; + const struct nouveau_enum *en; + struct nouveau_engine *engine; + struct nouveau_object *engctx = NULL; nv_error(priv, "PFIFO: %s fault at 0x%010llx [", (stat & 0x00000080) ? "write" : "read", (u64)vahi << 32 | valo); nouveau_enum_print(nve0_fifo_fault_reason, stat & 0x0000000f); - printk("] from "); - nouveau_enum_print(nve0_fifo_fault_unit, unit); + pr_cont("] from "); + en = nouveau_enum_print(nve0_fifo_fault_unit, unit); if (stat & 0x00000040) { - printk("/"); + pr_cont("/"); nouveau_enum_print(nve0_fifo_fault_hubclient, client); } else { - printk("/GPC%d/", (stat & 0x1f000000) >> 24); + pr_cont("/GPC%d/", (stat & 0x1f000000) >> 24); nouveau_enum_print(nve0_fifo_fault_gpcclient, client); } - printk(" on channel 0x%010llx\n", (u64)inst << 12); + + if (en && en->data2) { + engine = nouveau_engine(priv, en->data2); + if (engine) + engctx = nouveau_engctx_get(engine, inst); + + } + + pr_cont(" on channel 0x%010llx [%s]\n", (u64)inst << 12, + nouveau_client_name(engctx)); + + nouveau_engctx_put(engctx); } static int @@ -480,10 +496,12 @@ nve0_fifo_isr_subfifo_intr(struct nve0_fifo_priv *priv, int unit) if (show) { nv_error(priv, "SUBFIFO%d:", unit); nouveau_bitfield_print(nve0_fifo_subfifo_intr, show); - printk("\n"); - nv_error(priv, "SUBFIFO%d: ch %d subc %d mthd 0x%04x " - "data 0x%08x\n", - unit, chid, subc, mthd, data); + pr_cont("\n"); + nv_error(priv, + "SUBFIFO%d: ch %d [%s] subc %d mthd 0x%04x data 0x%08x\n", + unit, chid, + nouveau_client_name_for_fifo_chid(&priv->base, chid), + subc, mthd, data); } nv_wr32(priv, 0x0400c0 + (unit * 0x2000), 0x80600008); @@ -537,6 +555,12 @@ nve0_fifo_intr(struct nouveau_subdev *subdev) stat &= ~0x40000000; } + if (stat & 0x80000000) { + nouveau_event_trigger(priv->base.uevent, 0); + nv_wr32(priv, 0x002100, 0x80000000); + stat &= ~0x80000000; + } + if (stat) { nv_fatal(priv, "unhandled status 0x%08x\n", stat); nv_wr32(priv, 0x002100, stat); @@ -544,6 +568,20 @@ nve0_fifo_intr(struct nouveau_subdev *subdev) } } +static void +nve0_fifo_uevent_enable(struct nouveau_event *event, int index) +{ + struct nve0_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x80000000, 0x80000000); +} + +static void +nve0_fifo_uevent_disable(struct nouveau_event *event, int index) +{ + struct nve0_fifo_priv *priv = event->priv; + nv_mask(priv, 0x002140, 0x80000000, 0x00000000); +} + static int nve0_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -567,6 +605,10 @@ nve0_fifo_ctor(struct nouveau_object *parent, struct nouveau_object *engine, if (ret) return ret; + priv->base.uevent->enable = nve0_fifo_uevent_enable; + priv->base.uevent->disable = nve0_fifo_uevent_disable; + priv->base.uevent->priv = priv; + nv_subdev(priv)->unit = 0x00000100; nv_subdev(priv)->intr = nve0_fifo_intr; nv_engine(priv)->cclass = &nve0_fifo_cclass; @@ -617,7 +659,7 @@ nve0_fifo_init(struct nouveau_object *object) nv_wr32(priv, 0x002a00, 0xffffffff); nv_wr32(priv, 0x002100, 0xffffffff); - nv_wr32(priv, 0x002140, 0xbfffffff); + nv_wr32(priv, 0x002140, 0x3fffffff); return 0; } diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nv04.c b/drivers/gpu/drm/nouveau/core/engine/graph/nv04.c index e30a9c5ff1fc..ad13dcdd15f9 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nv04.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nv04.c @@ -22,6 +22,7 @@ * DEALINGS IN THE SOFTWARE. */ +#include <core/client.h> #include <core/os.h> #include <core/class.h> #include <core/handle.h> @@ -1297,16 +1298,17 @@ nv04_graph_intr(struct nouveau_subdev *subdev) nv_wr32(priv, NV04_PGRAPH_FIFO, 0x00000001); if (show) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv04_graph_intr_name, show); - printk(" nsource:"); + pr_cont(" nsource:"); nouveau_bitfield_print(nv04_graph_nsource, nsource); - printk(" nstatus:"); + pr_cont(" nstatus:"); nouveau_bitfield_print(nv04_graph_nstatus, nstatus); - printk("\n"); - nv_error(priv, "ch %d/%d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, subc, class, mthd, data); + pr_cont("\n"); + nv_error(priv, + "ch %d [%s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, nouveau_client_name(chan), subc, class, mthd, + data); } nouveau_namedb_put(handle); diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nv10.c b/drivers/gpu/drm/nouveau/core/engine/graph/nv10.c index 5c0f843ea249..23c143aaa556 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nv10.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nv10.c @@ -22,6 +22,7 @@ * DEALINGS IN THE SOFTWARE. */ +#include <core/client.h> #include <core/os.h> #include <core/class.h> #include <core/handle.h> @@ -1193,16 +1194,17 @@ nv10_graph_intr(struct nouveau_subdev *subdev) nv_wr32(priv, NV04_PGRAPH_FIFO, 0x00000001); if (show) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv10_graph_intr_name, show); - printk(" nsource:"); + pr_cont(" nsource:"); nouveau_bitfield_print(nv04_graph_nsource, nsource); - printk(" nstatus:"); + pr_cont(" nstatus:"); nouveau_bitfield_print(nv10_graph_nstatus, nstatus); - printk("\n"); - nv_error(priv, "ch %d/%d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, subc, class, mthd, data); + pr_cont("\n"); + nv_error(priv, + "ch %d [%s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, nouveau_client_name(chan), subc, class, mthd, + data); } nouveau_namedb_put(handle); diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nv20.c b/drivers/gpu/drm/nouveau/core/engine/graph/nv20.c index 5b20401bf911..0607b9801748 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nv20.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nv20.c @@ -1,3 +1,4 @@ +#include <core/client.h> #include <core/os.h> #include <core/class.h> #include <core/engctx.h> @@ -224,15 +225,17 @@ nv20_graph_intr(struct nouveau_subdev *subdev) nv_wr32(priv, NV04_PGRAPH_FIFO, 0x00000001); if (show) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv10_graph_intr_name, show); - printk(" nsource:"); + pr_cont(" nsource:"); nouveau_bitfield_print(nv04_graph_nsource, nsource); - printk(" nstatus:"); + pr_cont(" nstatus:"); nouveau_bitfield_print(nv10_graph_nstatus, nstatus); - printk("\n"); - nv_error(priv, "ch %d/%d class 0x%04x mthd 0x%04x data 0x%08x\n", - chid, subc, class, mthd, data); + pr_cont("\n"); + nv_error(priv, + "ch %d [%s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, nouveau_client_name(engctx), subc, class, mthd, + data); } nouveau_engctx_put(engctx); diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nv40.c b/drivers/gpu/drm/nouveau/core/engine/graph/nv40.c index 0b36dd3deebd..17049d5c723d 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nv40.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nv40.c @@ -22,6 +22,7 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/os.h> #include <core/class.h> #include <core/handle.h> @@ -321,16 +322,17 @@ nv40_graph_intr(struct nouveau_subdev *subdev) nv_wr32(priv, NV04_PGRAPH_FIFO, 0x00000001); if (show) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv10_graph_intr_name, show); - printk(" nsource:"); + pr_cont(" nsource:"); nouveau_bitfield_print(nv04_graph_nsource, nsource); - printk(" nstatus:"); + pr_cont(" nstatus:"); nouveau_bitfield_print(nv10_graph_nstatus, nstatus); - printk("\n"); - nv_error(priv, "ch %d [0x%08x] subc %d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, inst << 4, subc, class, mthd, data); + pr_cont("\n"); + nv_error(priv, + "ch %d [0x%08x %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst << 4, nouveau_client_name(engctx), subc, + class, mthd, data); } nouveau_engctx_put(engctx); diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nv50.c b/drivers/gpu/drm/nouveau/core/engine/graph/nv50.c index b1c3d835b4c2..f2b1a7a124f2 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nv50.c @@ -24,6 +24,7 @@ #include <core/os.h> #include <core/class.h> +#include <core/client.h> #include <core/handle.h> #include <core/engctx.h> #include <core/enum.h> @@ -418,7 +419,7 @@ nv50_priv_mp_trap(struct nv50_graph_priv *priv, int tpid, int display) nv_error(priv, "TRAP_MP_EXEC - " "TP %d MP %d: ", tpid, i); nouveau_enum_print(nv50_mp_exec_error_names, status); - printk(" at %06x warp %d, opcode %08x %08x\n", + pr_cont(" at %06x warp %d, opcode %08x %08x\n", pc&0xffffff, pc >> 24, oplow, ophigh); } @@ -532,7 +533,7 @@ nv50_priv_tp_trap(struct nv50_graph_priv *priv, int type, u32 ustatus_old, static int nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, - int chid, u64 inst) + int chid, u64 inst, struct nouveau_object *engctx) { u32 status = nv_rd32(priv, 0x400108); u32 ustatus; @@ -565,12 +566,11 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, nv_error(priv, "TRAP DISPATCH_FAULT\n"); if (display && (addr & 0x80000000)) { - nv_error(priv, "ch %d [0x%010llx] " - "subc %d class 0x%04x mthd 0x%04x " - "data 0x%08x%08x " - "400808 0x%08x 400848 0x%08x\n", - chid, inst, subc, class, mthd, datah, - datal, addr, r848); + nv_error(priv, + "ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x%08x 400808 0x%08x 400848 0x%08x\n", + chid, inst, + nouveau_client_name(engctx), subc, + class, mthd, datah, datal, addr, r848); } else if (display) { nv_error(priv, "no stuck command?\n"); @@ -591,11 +591,11 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, nv_error(priv, "TRAP DISPATCH_QUERY\n"); if (display && (addr & 0x80000000)) { - nv_error(priv, "ch %d [0x%010llx] " - "subc %d class 0x%04x mthd 0x%04x " - "data 0x%08x 40084c 0x%08x\n", - chid, inst, subc, class, mthd, - data, addr); + nv_error(priv, + "ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x 40084c 0x%08x\n", + chid, inst, + nouveau_client_name(engctx), subc, + class, mthd, data, addr); } else if (display) { nv_error(priv, "no stuck command?\n"); @@ -623,7 +623,7 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, if (display) { nv_error(priv, "TRAP_M2MF"); nouveau_bitfield_print(nv50_graph_trap_m2mf, ustatus); - printk("\n"); + pr_cont("\n"); nv_error(priv, "TRAP_M2MF %08x %08x %08x %08x\n", nv_rd32(priv, 0x406804), nv_rd32(priv, 0x406808), nv_rd32(priv, 0x40680c), nv_rd32(priv, 0x406810)); @@ -644,7 +644,7 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, if (display) { nv_error(priv, "TRAP_VFETCH"); nouveau_bitfield_print(nv50_graph_trap_vfetch, ustatus); - printk("\n"); + pr_cont("\n"); nv_error(priv, "TRAP_VFETCH %08x %08x %08x %08x\n", nv_rd32(priv, 0x400c00), nv_rd32(priv, 0x400c08), nv_rd32(priv, 0x400c0c), nv_rd32(priv, 0x400c10)); @@ -661,7 +661,7 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, if (display) { nv_error(priv, "TRAP_STRMOUT"); nouveau_bitfield_print(nv50_graph_trap_strmout, ustatus); - printk("\n"); + pr_cont("\n"); nv_error(priv, "TRAP_STRMOUT %08x %08x %08x %08x\n", nv_rd32(priv, 0x401804), nv_rd32(priv, 0x401808), nv_rd32(priv, 0x40180c), nv_rd32(priv, 0x401810)); @@ -682,7 +682,7 @@ nv50_graph_trap_handler(struct nv50_graph_priv *priv, u32 display, if (display) { nv_error(priv, "TRAP_CCACHE"); nouveau_bitfield_print(nv50_graph_trap_ccache, ustatus); - printk("\n"); + pr_cont("\n"); nv_error(priv, "TRAP_CCACHE %08x %08x %08x %08x" " %08x %08x %08x\n", nv_rd32(priv, 0x405000), nv_rd32(priv, 0x405004), @@ -774,11 +774,12 @@ nv50_graph_intr(struct nouveau_subdev *subdev) u32 ecode = nv_rd32(priv, 0x400110); nv_error(priv, "DATA_ERROR "); nouveau_enum_print(nv50_data_error_names, ecode); - printk("\n"); + pr_cont("\n"); } if (stat & 0x00200000) { - if (!nv50_graph_trap_handler(priv, show, chid, (u64)inst << 12)) + if (!nv50_graph_trap_handler(priv, show, chid, (u64)inst << 12, + engctx)) show &= ~0x00200000; } @@ -786,12 +787,13 @@ nv50_graph_intr(struct nouveau_subdev *subdev) nv_wr32(priv, 0x400500, 0x00010001); if (show) { - nv_error(priv, ""); + nv_error(priv, "%s", ""); nouveau_bitfield_print(nv50_graph_intr_name, show); - printk("\n"); - nv_error(priv, "ch %d [0x%010llx] subc %d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, (u64)inst << 12, subc, class, mthd, data); + pr_cont("\n"); + nv_error(priv, + "ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, (u64)inst << 12, nouveau_client_name(engctx), + subc, class, mthd, data); } if (nv_rd32(priv, 0x400824) & (1 << 31)) @@ -907,9 +909,8 @@ nv50_graph_init(struct nouveau_object *object) nv_wr32(priv, 0x400828, 0x00000000); nv_wr32(priv, 0x40082c, 0x00000000); nv_wr32(priv, 0x400830, 0x00000000); - nv_wr32(priv, 0x400724, 0x00000000); nv_wr32(priv, 0x40032c, 0x00000000); - nv_wr32(priv, 0x400320, 4); /* CTXCTL_CMD = NEWCTXDMA */ + nv_wr32(priv, 0x400330, 0x00000000); /* some unknown zcull magic */ switch (nv_device(priv)->chipset & 0xf0) { diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nvc0.c b/drivers/gpu/drm/nouveau/core/engine/graph/nvc0.c index 45aff5f5085a..0de0dd724aff 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nvc0.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nvc0.c @@ -433,10 +433,10 @@ nvc0_graph_intr(struct nouveau_subdev *subdev) if (stat & 0x00000010) { handle = nouveau_handle_get_class(engctx, class); if (!handle || nv_call(handle->object, mthd, data)) { - nv_error(priv, "ILLEGAL_MTHD ch %d [0x%010llx] " - "subc %d class 0x%04x mthd 0x%04x " - "data 0x%08x\n", - chid, inst << 12, subc, class, mthd, data); + nv_error(priv, + "ILLEGAL_MTHD ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst << 12, nouveau_client_name(engctx), + subc, class, mthd, data); } nouveau_handle_put(handle); nv_wr32(priv, 0x400100, 0x00000010); @@ -444,9 +444,10 @@ nvc0_graph_intr(struct nouveau_subdev *subdev) } if (stat & 0x00000020) { - nv_error(priv, "ILLEGAL_CLASS ch %d [0x%010llx] subc %d " - "class 0x%04x mthd 0x%04x data 0x%08x\n", - chid, inst << 12, subc, class, mthd, data); + nv_error(priv, + "ILLEGAL_CLASS ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst << 12, nouveau_client_name(engctx), subc, + class, mthd, data); nv_wr32(priv, 0x400100, 0x00000020); stat &= ~0x00000020; } @@ -454,15 +455,16 @@ nvc0_graph_intr(struct nouveau_subdev *subdev) if (stat & 0x00100000) { nv_error(priv, "DATA_ERROR ["); nouveau_enum_print(nv50_data_error_names, code); - printk("] ch %d [0x%010llx] subc %d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, inst << 12, subc, class, mthd, data); + pr_cont("] ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst << 12, nouveau_client_name(engctx), subc, + class, mthd, data); nv_wr32(priv, 0x400100, 0x00100000); stat &= ~0x00100000; } if (stat & 0x00200000) { - nv_error(priv, "TRAP ch %d [0x%010llx]\n", chid, inst << 12); + nv_error(priv, "TRAP ch %d [0x%010llx %s]\n", chid, inst << 12, + nouveau_client_name(engctx)); nvc0_graph_trap_intr(priv); nv_wr32(priv, 0x400100, 0x00200000); stat &= ~0x00200000; @@ -611,10 +613,8 @@ nvc0_graph_ctor(struct nouveau_object *parent, struct nouveau_object *engine, static void nvc0_graph_dtor_fw(struct nvc0_graph_fuc *fuc) { - if (fuc->data) { - kfree(fuc->data); - fuc->data = NULL; - } + kfree(fuc->data); + fuc->data = NULL; } void @@ -622,8 +622,7 @@ nvc0_graph_dtor(struct nouveau_object *object) { struct nvc0_graph_priv *priv = (void *)object; - if (priv->data) - kfree(priv->data); + kfree(priv->data); nvc0_graph_dtor_fw(&priv->fuc409c); nvc0_graph_dtor_fw(&priv->fuc409d); diff --git a/drivers/gpu/drm/nouveau/core/engine/graph/nve0.c b/drivers/gpu/drm/nouveau/core/engine/graph/nve0.c index 9f82e9702b46..61cec0f6ff1c 100644 --- a/drivers/gpu/drm/nouveau/core/engine/graph/nve0.c +++ b/drivers/gpu/drm/nouveau/core/engine/graph/nve0.c @@ -78,15 +78,16 @@ nve0_graph_ctxctl_isr(struct nvc0_graph_priv *priv) } static void -nve0_graph_trap_isr(struct nvc0_graph_priv *priv, int chid, u64 inst) +nve0_graph_trap_isr(struct nvc0_graph_priv *priv, int chid, u64 inst, + struct nouveau_object *engctx) { u32 trap = nv_rd32(priv, 0x400108); int rop; if (trap & 0x00000001) { u32 stat = nv_rd32(priv, 0x404000); - nv_error(priv, "DISPATCH ch %d [0x%010llx] 0x%08x\n", - chid, inst, stat); + nv_error(priv, "DISPATCH ch %d [0x%010llx %s] 0x%08x\n", + chid, inst, nouveau_client_name(engctx), stat); nv_wr32(priv, 0x404000, 0xc0000000); nv_wr32(priv, 0x400108, 0x00000001); trap &= ~0x00000001; @@ -94,8 +95,8 @@ nve0_graph_trap_isr(struct nvc0_graph_priv *priv, int chid, u64 inst) if (trap & 0x00000010) { u32 stat = nv_rd32(priv, 0x405840); - nv_error(priv, "SHADER ch %d [0x%010llx] 0x%08x\n", - chid, inst, stat); + nv_error(priv, "SHADER ch %d [0x%010llx %s] 0x%08x\n", + chid, inst, nouveau_client_name(engctx), stat); nv_wr32(priv, 0x405840, 0xc0000000); nv_wr32(priv, 0x400108, 0x00000010); trap &= ~0x00000010; @@ -105,8 +106,10 @@ nve0_graph_trap_isr(struct nvc0_graph_priv *priv, int chid, u64 inst) for (rop = 0; rop < priv->rop_nr; rop++) { u32 statz = nv_rd32(priv, ROP_UNIT(rop, 0x070)); u32 statc = nv_rd32(priv, ROP_UNIT(rop, 0x144)); - nv_error(priv, "ROP%d ch %d [0x%010llx] 0x%08x 0x%08x\n", - rop, chid, inst, statz, statc); + nv_error(priv, + "ROP%d ch %d [0x%010llx %s] 0x%08x 0x%08x\n", + rop, chid, inst, nouveau_client_name(engctx), + statz, statc); nv_wr32(priv, ROP_UNIT(rop, 0x070), 0xc0000000); nv_wr32(priv, ROP_UNIT(rop, 0x144), 0xc0000000); } @@ -115,8 +118,8 @@ nve0_graph_trap_isr(struct nvc0_graph_priv *priv, int chid, u64 inst) } if (trap) { - nv_error(priv, "TRAP ch %d [0x%010llx] 0x%08x\n", - chid, inst, trap); + nv_error(priv, "TRAP ch %d [0x%010llx %s] 0x%08x\n", + chid, inst, nouveau_client_name(engctx), trap); nv_wr32(priv, 0x400108, trap); } } @@ -145,10 +148,10 @@ nve0_graph_intr(struct nouveau_subdev *subdev) if (stat & 0x00000010) { handle = nouveau_handle_get_class(engctx, class); if (!handle || nv_call(handle->object, mthd, data)) { - nv_error(priv, "ILLEGAL_MTHD ch %d [0x%010llx] " - "subc %d class 0x%04x mthd 0x%04x " - "data 0x%08x\n", - chid, inst, subc, class, mthd, data); + nv_error(priv, + "ILLEGAL_MTHD ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst, nouveau_client_name(engctx), subc, + class, mthd, data); } nouveau_handle_put(handle); nv_wr32(priv, 0x400100, 0x00000010); @@ -156,9 +159,10 @@ nve0_graph_intr(struct nouveau_subdev *subdev) } if (stat & 0x00000020) { - nv_error(priv, "ILLEGAL_CLASS ch %d [0x%010llx] subc %d " - "class 0x%04x mthd 0x%04x data 0x%08x\n", - chid, inst, subc, class, mthd, data); + nv_error(priv, + "ILLEGAL_CLASS ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst, nouveau_client_name(engctx), subc, class, + mthd, data); nv_wr32(priv, 0x400100, 0x00000020); stat &= ~0x00000020; } @@ -166,15 +170,15 @@ nve0_graph_intr(struct nouveau_subdev *subdev) if (stat & 0x00100000) { nv_error(priv, "DATA_ERROR ["); nouveau_enum_print(nv50_data_error_names, code); - printk("] ch %d [0x%010llx] subc %d class 0x%04x " - "mthd 0x%04x data 0x%08x\n", - chid, inst, subc, class, mthd, data); + pr_cont("] ch %d [0x%010llx %s] subc %d class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst, nouveau_client_name(engctx), subc, class, + mthd, data); nv_wr32(priv, 0x400100, 0x00100000); stat &= ~0x00100000; } if (stat & 0x00200000) { - nve0_graph_trap_isr(priv, chid, inst); + nve0_graph_trap_isr(priv, chid, inst, engctx); nv_wr32(priv, 0x400100, 0x00200000); stat &= ~0x00200000; } diff --git a/drivers/gpu/drm/nouveau/core/engine/mpeg/nv31.c b/drivers/gpu/drm/nouveau/core/engine/mpeg/nv31.c index 9fd86375f4c4..49ecbb859b25 100644 --- a/drivers/gpu/drm/nouveau/core/engine/mpeg/nv31.c +++ b/drivers/gpu/drm/nouveau/core/engine/mpeg/nv31.c @@ -22,6 +22,7 @@ * Authors: Ben Skeggs */ +#include <core/client.h> #include <core/os.h> #include <core/class.h> #include <core/engctx.h> @@ -231,8 +232,10 @@ nv31_mpeg_intr(struct nouveau_subdev *subdev) nv_wr32(priv, 0x00b230, 0x00000001); if (show) { - nv_error(priv, "ch %d [0x%08x] 0x%08x 0x%08x 0x%08x 0x%08x\n", - chid, inst << 4, stat, type, mthd, data); + nv_error(priv, + "ch %d [0x%08x %s] 0x%08x 0x%08x 0x%08x 0x%08x\n", + chid, inst << 4, nouveau_client_name(engctx), stat, + type, mthd, data); } nouveau_engctx_put(engctx); diff --git a/drivers/gpu/drm/nouveau/core/engine/software/nv50.c b/drivers/gpu/drm/nouveau/core/engine/software/nv50.c index b0e7e1c01ce6..c48e74953771 100644 --- a/drivers/gpu/drm/nouveau/core/engine/software/nv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/software/nv50.c @@ -28,6 +28,9 @@ #include <core/namedb.h> #include <core/handle.h> #include <core/gpuobj.h> +#include <core/event.h> + +#include <subdev/bar.h> #include <engine/software.h> #include <engine/disp.h> @@ -90,18 +93,11 @@ nv50_software_mthd_vblsem_release(struct nouveau_object *object, u32 mthd, { struct nv50_software_chan *chan = (void *)nv_engctx(object->parent); struct nouveau_disp *disp = nouveau_disp(object); - unsigned long flags; u32 crtc = *(u32 *)args; - if (crtc > 1) return -EINVAL; - disp->vblank.get(disp->vblank.data, crtc); - - spin_lock_irqsave(&disp->vblank.lock, flags); - list_add(&chan->base.vblank.head, &disp->vblank.list); - chan->base.vblank.crtc = crtc; - spin_unlock_irqrestore(&disp->vblank.lock, flags); + nouveau_event_get(disp->vblank, crtc, &chan->base.vblank.event); return 0; } @@ -136,6 +132,29 @@ nv50_software_sclass[] = { ******************************************************************************/ static int +nv50_software_vblsem_release(struct nouveau_eventh *event, int head) +{ + struct nouveau_software_chan *chan = + container_of(event, struct nouveau_software_chan, vblank.event); + struct nv50_software_priv *priv = (void *)nv_object(chan)->engine; + struct nouveau_bar *bar = nouveau_bar(priv); + + nv_wr32(priv, 0x001704, chan->vblank.channel); + nv_wr32(priv, 0x001710, 0x80000000 | chan->vblank.ctxdma); + bar->flush(bar); + + if (nv_device(priv)->chipset == 0x50) { + nv_wr32(priv, 0x001570, chan->vblank.offset); + nv_wr32(priv, 0x001574, chan->vblank.value); + } else { + nv_wr32(priv, 0x060010, chan->vblank.offset); + nv_wr32(priv, 0x060014, chan->vblank.value); + } + + return NVKM_EVENT_DROP; +} + +static int nv50_software_context_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -150,6 +169,7 @@ nv50_software_context_ctor(struct nouveau_object *parent, return ret; chan->base.vblank.channel = nv_gpuobj(parent->parent)->addr >> 12; + chan->base.vblank.event.func = nv50_software_vblsem_release; return 0; } @@ -170,8 +190,8 @@ nv50_software_cclass = { static int nv50_software_ctor(struct nouveau_object *parent, struct nouveau_object *engine, - struct nouveau_oclass *oclass, void *data, u32 size, - struct nouveau_object **pobject) + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) { struct nv50_software_priv *priv; int ret; diff --git a/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c b/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c index 282a1cd1bc2f..a523eaad47e3 100644 --- a/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c +++ b/drivers/gpu/drm/nouveau/core/engine/software/nvc0.c @@ -25,6 +25,9 @@ #include <core/os.h> #include <core/class.h> #include <core/engctx.h> +#include <core/event.h> + +#include <subdev/bar.h> #include <engine/software.h> #include <engine/disp.h> @@ -72,18 +75,12 @@ nvc0_software_mthd_vblsem_release(struct nouveau_object *object, u32 mthd, { struct nvc0_software_chan *chan = (void *)nv_engctx(object->parent); struct nouveau_disp *disp = nouveau_disp(object); - unsigned long flags; u32 crtc = *(u32 *)args; if ((nv_device(object)->card_type < NV_E0 && crtc > 1) || crtc > 3) return -EINVAL; - disp->vblank.get(disp->vblank.data, crtc); - - spin_lock_irqsave(&disp->vblank.lock, flags); - list_add(&chan->base.vblank.head, &disp->vblank.list); - chan->base.vblank.crtc = crtc; - spin_unlock_irqrestore(&disp->vblank.lock, flags); + nouveau_event_get(disp->vblank, crtc, &chan->base.vblank.event); return 0; } @@ -118,6 +115,23 @@ nvc0_software_sclass[] = { ******************************************************************************/ static int +nvc0_software_vblsem_release(struct nouveau_eventh *event, int head) +{ + struct nouveau_software_chan *chan = + container_of(event, struct nouveau_software_chan, vblank.event); + struct nvc0_software_priv *priv = (void *)nv_object(chan)->engine; + struct nouveau_bar *bar = nouveau_bar(priv); + + nv_wr32(priv, 0x001718, 0x80000000 | chan->vblank.channel); + bar->flush(bar); + nv_wr32(priv, 0x06000c, upper_32_bits(chan->vblank.offset)); + nv_wr32(priv, 0x060010, lower_32_bits(chan->vblank.offset)); + nv_wr32(priv, 0x060014, chan->vblank.value); + + return NVKM_EVENT_DROP; +} + +static int nvc0_software_context_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -132,6 +146,7 @@ nvc0_software_context_ctor(struct nouveau_object *parent, return ret; chan->base.vblank.channel = nv_gpuobj(parent->parent)->addr >> 12; + chan->base.vblank.event.func = nvc0_software_vblsem_release; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/include/core/class.h b/drivers/gpu/drm/nouveau/core/include/core/class.h index 47c4b3a5bd3a..92d3ab11d962 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/class.h +++ b/drivers/gpu/drm/nouveau/core/include/core/class.h @@ -154,6 +154,14 @@ struct nve0_channel_ind_class { u32 engine; }; +/* 0046: NV04_DISP + */ + +#define NV04_DISP_CLASS 0x00000046 + +struct nv04_display_class { +}; + /* 5070: NV50_DISP * 8270: NV84_DISP * 8370: NVA0_DISP @@ -190,25 +198,6 @@ struct nve0_channel_ind_class { #define NV84_DISP_SOR_HDMI_PWR_REKEY 0x0000007f #define NV50_DISP_SOR_LVDS_SCRIPT 0x00013000 #define NV50_DISP_SOR_LVDS_SCRIPT_ID 0x0000ffff -#define NV94_DISP_SOR_DP_TRAIN 0x00016000 -#define NV94_DISP_SOR_DP_TRAIN_OP 0xf0000000 -#define NV94_DISP_SOR_DP_TRAIN_OP_PATTERN 0x00000000 -#define NV94_DISP_SOR_DP_TRAIN_OP_INIT 0x10000000 -#define NV94_DISP_SOR_DP_TRAIN_OP_FINI 0x20000000 -#define NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD 0x00000001 -#define NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_OFF 0x00000000 -#define NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_ON 0x00000001 -#define NV94_DISP_SOR_DP_TRAIN_PATTERN 0x00000003 -#define NV94_DISP_SOR_DP_TRAIN_PATTERN_DISABLED 0x00000000 -#define NV94_DISP_SOR_DP_LNKCTL 0x00016040 -#define NV94_DISP_SOR_DP_LNKCTL_FRAME 0x80000000 -#define NV94_DISP_SOR_DP_LNKCTL_FRAME_STD 0x00000000 -#define NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH 0x80000000 -#define NV94_DISP_SOR_DP_LNKCTL_WIDTH 0x00001f00 -#define NV94_DISP_SOR_DP_LNKCTL_COUNT 0x00000007 -#define NV94_DISP_SOR_DP_DRVCTL(l) ((l) * 0x40 + 0x00016100) -#define NV94_DISP_SOR_DP_DRVCTL_VS 0x00000300 -#define NV94_DISP_SOR_DP_DRVCTL_PE 0x00000003 #define NV50_DISP_DAC_MTHD 0x00020000 #define NV50_DISP_DAC_MTHD_TYPE 0x0000f000 @@ -230,6 +219,23 @@ struct nve0_channel_ind_class { #define NV50_DISP_DAC_LOAD 0x0002000c #define NV50_DISP_DAC_LOAD_VALUE 0x00000007 +#define NV50_DISP_PIOR_MTHD 0x00030000 +#define NV50_DISP_PIOR_MTHD_TYPE 0x0000f000 +#define NV50_DISP_PIOR_MTHD_OR 0x00000003 + +#define NV50_DISP_PIOR_PWR 0x00030000 +#define NV50_DISP_PIOR_PWR_STATE 0x00000001 +#define NV50_DISP_PIOR_PWR_STATE_ON 0x00000001 +#define NV50_DISP_PIOR_PWR_STATE_OFF 0x00000000 +#define NV50_DISP_PIOR_TMDS_PWR 0x00032000 +#define NV50_DISP_PIOR_TMDS_PWR_STATE 0x00000001 +#define NV50_DISP_PIOR_TMDS_PWR_STATE_ON 0x00000001 +#define NV50_DISP_PIOR_TMDS_PWR_STATE_OFF 0x00000000 +#define NV50_DISP_PIOR_DP_PWR 0x00036000 +#define NV50_DISP_PIOR_DP_PWR_STATE 0x00000001 +#define NV50_DISP_PIOR_DP_PWR_STATE_ON 0x00000001 +#define NV50_DISP_PIOR_DP_PWR_STATE_OFF 0x00000000 + struct nv50_display_class { }; diff --git a/drivers/gpu/drm/nouveau/core/include/core/client.h b/drivers/gpu/drm/nouveau/core/include/core/client.h index 63acc0346ff2..c66eac513803 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/client.h +++ b/drivers/gpu/drm/nouveau/core/include/core/client.h @@ -7,7 +7,7 @@ struct nouveau_client { struct nouveau_namedb base; struct nouveau_handle *root; struct nouveau_object *device; - char name[16]; + char name[32]; u32 debug; struct nouveau_vm *vm; }; @@ -41,5 +41,6 @@ int nouveau_client_create_(const char *name, u64 device, const char *cfg, int nouveau_client_init(struct nouveau_client *); int nouveau_client_fini(struct nouveau_client *, bool suspend); +const char *nouveau_client_name(void *obj); #endif diff --git a/drivers/gpu/drm/nouveau/core/include/core/device.h b/drivers/gpu/drm/nouveau/core/include/core/device.h index e58b6f0984c1..d351a4e5819c 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/device.h +++ b/drivers/gpu/drm/nouveau/core/include/core/device.h @@ -26,6 +26,7 @@ enum nv_subdev_type { */ NVDEV_SUBDEV_MXM, NVDEV_SUBDEV_MC, + NVDEV_SUBDEV_BUS, NVDEV_SUBDEV_TIMER, NVDEV_SUBDEV_FB, NVDEV_SUBDEV_LTCG, diff --git a/drivers/gpu/drm/nouveau/core/include/core/enum.h b/drivers/gpu/drm/nouveau/core/include/core/enum.h index e7b1e181943b..4fc62bb8c1f0 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/enum.h +++ b/drivers/gpu/drm/nouveau/core/include/core/enum.h @@ -5,12 +5,13 @@ struct nouveau_enum { u32 value; const char *name; const void *data; + u32 data2; }; const struct nouveau_enum * nouveau_enum_find(const struct nouveau_enum *, u32 value); -void +const struct nouveau_enum * nouveau_enum_print(const struct nouveau_enum *en, u32 value); struct nouveau_bitfield { diff --git a/drivers/gpu/drm/nouveau/core/include/core/event.h b/drivers/gpu/drm/nouveau/core/include/core/event.h new file mode 100644 index 000000000000..9e094408f14e --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/include/core/event.h @@ -0,0 +1,36 @@ +#ifndef __NVKM_EVENT_H__ +#define __NVKM_EVENT_H__ + +/* return codes from event handlers */ +#define NVKM_EVENT_DROP 0 +#define NVKM_EVENT_KEEP 1 + +struct nouveau_eventh { + struct list_head head; + int (*func)(struct nouveau_eventh *, int index); +}; + +struct nouveau_event { + spinlock_t lock; + + void *priv; + void (*enable)(struct nouveau_event *, int index); + void (*disable)(struct nouveau_event *, int index); + + int index_nr; + struct { + struct list_head list; + int refs; + } index[]; +}; + +int nouveau_event_create(int index_nr, struct nouveau_event **); +void nouveau_event_destroy(struct nouveau_event **); +void nouveau_event_trigger(struct nouveau_event *, int index); + +void nouveau_event_get(struct nouveau_event *, int index, + struct nouveau_eventh *); +void nouveau_event_put(struct nouveau_event *, int index, + struct nouveau_eventh *); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/include/core/object.h b/drivers/gpu/drm/nouveau/core/include/core/object.h index 5982935ee23a..6a902672f6f4 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/object.h +++ b/drivers/gpu/drm/nouveau/core/include/core/object.h @@ -133,7 +133,7 @@ static inline u8 nv_ro08(void *obj, u64 addr) { u8 data = nv_ofuncs(obj)->rd08(obj, addr); - nv_spam(obj, "nv_ro08 0x%08x 0x%02x\n", addr, data); + nv_spam(obj, "nv_ro08 0x%08llx 0x%02x\n", addr, data); return data; } @@ -141,7 +141,7 @@ static inline u16 nv_ro16(void *obj, u64 addr) { u16 data = nv_ofuncs(obj)->rd16(obj, addr); - nv_spam(obj, "nv_ro16 0x%08x 0x%04x\n", addr, data); + nv_spam(obj, "nv_ro16 0x%08llx 0x%04x\n", addr, data); return data; } @@ -149,28 +149,28 @@ static inline u32 nv_ro32(void *obj, u64 addr) { u32 data = nv_ofuncs(obj)->rd32(obj, addr); - nv_spam(obj, "nv_ro32 0x%08x 0x%08x\n", addr, data); + nv_spam(obj, "nv_ro32 0x%08llx 0x%08x\n", addr, data); return data; } static inline void nv_wo08(void *obj, u64 addr, u8 data) { - nv_spam(obj, "nv_wo08 0x%08x 0x%02x\n", addr, data); + nv_spam(obj, "nv_wo08 0x%08llx 0x%02x\n", addr, data); nv_ofuncs(obj)->wr08(obj, addr, data); } static inline void nv_wo16(void *obj, u64 addr, u16 data) { - nv_spam(obj, "nv_wo16 0x%08x 0x%04x\n", addr, data); + nv_spam(obj, "nv_wo16 0x%08llx 0x%04x\n", addr, data); nv_ofuncs(obj)->wr16(obj, addr, data); } static inline void nv_wo32(void *obj, u64 addr, u32 data) { - nv_spam(obj, "nv_wo32 0x%08x 0x%08x\n", addr, data); + nv_spam(obj, "nv_wo32 0x%08llx 0x%08x\n", addr, data); nv_ofuncs(obj)->wr32(obj, addr, data); } diff --git a/drivers/gpu/drm/nouveau/core/include/core/printk.h b/drivers/gpu/drm/nouveau/core/include/core/printk.h index 1d629664f32d..febed2ea5c80 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/printk.h +++ b/drivers/gpu/drm/nouveau/core/include/core/printk.h @@ -15,7 +15,8 @@ struct nouveau_object; #define NV_PRINTK_TRACE KERN_DEBUG #define NV_PRINTK_SPAM KERN_DEBUG -void nv_printk_(struct nouveau_object *, const char *, int, const char *, ...); +void __printf(4, 5) +nv_printk_(struct nouveau_object *, const char *, int, const char *, ...); #define nv_printk(o,l,f,a...) do { \ if (NV_DBG_##l <= CONFIG_NOUVEAU_DEBUG) \ diff --git a/drivers/gpu/drm/nouveau/core/include/engine/disp.h b/drivers/gpu/drm/nouveau/core/include/engine/disp.h index 46948285f3e7..28da6772c095 100644 --- a/drivers/gpu/drm/nouveau/core/include/engine/disp.h +++ b/drivers/gpu/drm/nouveau/core/include/engine/disp.h @@ -4,18 +4,11 @@ #include <core/object.h> #include <core/engine.h> #include <core/device.h> +#include <core/event.h> struct nouveau_disp { struct nouveau_engine base; - - struct { - struct list_head list; - spinlock_t lock; - void (*notify)(void *, int); - void (*get)(void *, int); - void (*put)(void *, int); - void *data; - } vblank; + struct nouveau_event *vblank; }; static inline struct nouveau_disp * @@ -24,16 +17,22 @@ nouveau_disp(void *obj) return (void *)nv_device(obj)->subdev[NVDEV_ENGINE_DISP]; } -#define nouveau_disp_create(p,e,c,i,x,d) \ - nouveau_engine_create((p), (e), (c), true, (i), (x), (d)) -#define nouveau_disp_destroy(d) \ - nouveau_engine_destroy(&(d)->base) +#define nouveau_disp_create(p,e,c,h,i,x,d) \ + nouveau_disp_create_((p), (e), (c), (h), (i), (x), \ + sizeof(**d), (void **)d) +#define nouveau_disp_destroy(d) ({ \ + struct nouveau_disp *disp = (d); \ + _nouveau_disp_dtor(nv_object(disp)); \ +}) #define nouveau_disp_init(d) \ nouveau_engine_init(&(d)->base) #define nouveau_disp_fini(d,s) \ nouveau_engine_fini(&(d)->base, (s)) -#define _nouveau_disp_dtor _nouveau_engine_dtor +int nouveau_disp_create_(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, int heads, + const char *, const char *, int, void **); +void _nouveau_disp_dtor(struct nouveau_object *); #define _nouveau_disp_init _nouveau_engine_init #define _nouveau_disp_fini _nouveau_engine_fini diff --git a/drivers/gpu/drm/nouveau/core/include/engine/fifo.h b/drivers/gpu/drm/nouveau/core/include/engine/fifo.h index f18846c8c6fe..b46c197709f3 100644 --- a/drivers/gpu/drm/nouveau/core/include/engine/fifo.h +++ b/drivers/gpu/drm/nouveau/core/include/engine/fifo.h @@ -65,6 +65,8 @@ struct nouveau_fifo_base { struct nouveau_fifo { struct nouveau_engine base; + struct nouveau_event *uevent; + struct nouveau_object **channel; spinlock_t lock; u16 min; @@ -92,6 +94,8 @@ int nouveau_fifo_create_(struct nouveau_object *, struct nouveau_object *, struct nouveau_oclass *, int min, int max, int size, void **); void nouveau_fifo_destroy(struct nouveau_fifo *); +const char * +nouveau_client_name_for_fifo_chid(struct nouveau_fifo *fifo, u32 chid); #define _nouveau_fifo_init _nouveau_engine_init #define _nouveau_fifo_fini _nouveau_engine_fini diff --git a/drivers/gpu/drm/nouveau/core/include/engine/software.h b/drivers/gpu/drm/nouveau/core/include/engine/software.h index c945691c8564..45799487e573 100644 --- a/drivers/gpu/drm/nouveau/core/include/engine/software.h +++ b/drivers/gpu/drm/nouveau/core/include/engine/software.h @@ -3,17 +3,17 @@ #include <core/engine.h> #include <core/engctx.h> +#include <core/event.h> struct nouveau_software_chan { struct nouveau_engctx base; struct { - struct list_head head; + struct nouveau_eventh event; u32 channel; u32 ctxdma; u64 offset; u32 value; - u32 crtc; } vblank; int (*flip)(void *); diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/dcb.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/dcb.h index b79025da581e..123270e9813a 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/bios/dcb.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/dcb.h @@ -16,6 +16,8 @@ enum dcb_output_type { struct dcb_output { int index; /* may not be raw dcb index if merging has happened */ + u16 hasht; + u16 hashm; enum dcb_output_type type; uint8_t i2c_index; uint8_t heads; @@ -25,6 +27,7 @@ struct dcb_output { uint8_t or; uint8_t link; bool duallink_possible; + uint8_t extdev; union { struct sor_conf { int link; diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/gpio.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/gpio.h index e6563b5cb08e..96d3364f6db3 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/bios/gpio.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/gpio.h @@ -1,17 +1,22 @@ #ifndef __NVBIOS_GPIO_H__ #define __NVBIOS_GPIO_H__ -struct nouveau_bios; - enum dcb_gpio_func_name { DCB_GPIO_PANEL_POWER = 0x01, DCB_GPIO_TVDAC0 = 0x0c, DCB_GPIO_TVDAC1 = 0x2d, - DCB_GPIO_PWM_FAN = 0x09, + DCB_GPIO_FAN = 0x09, DCB_GPIO_FAN_SENSE = 0x3d, DCB_GPIO_UNUSED = 0xff }; +#define DCB_GPIO_LOG_DIR 0x02 +#define DCB_GPIO_LOG_DIR_OUT 0x00 +#define DCB_GPIO_LOG_DIR_IN 0x02 +#define DCB_GPIO_LOG_VAL 0x01 +#define DCB_GPIO_LOG_VAL_LO 0x00 +#define DCB_GPIO_LOG_VAL_HI 0x01 + struct dcb_gpio_func { u8 func; u8 line; diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/i2c.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/i2c.h index 5079bedfd985..10b57a19a7de 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/bios/i2c.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/i2c.h @@ -15,7 +15,7 @@ struct dcb_i2c_entry { enum dcb_i2c_type type; u8 drive; u8 sense; - u32 data; + u8 share; }; u16 dcb_i2c_table(struct nouveau_bios *, u8 *ver, u8 *hdr, u8 *cnt, u8 *len); diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/therm.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/therm.h index a2c4296fc5f6..083541dbe9c8 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/bios/therm.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/therm.h @@ -23,11 +23,27 @@ struct nvbios_therm_sensor { struct nvbios_therm_threshold thrs_shutdown; }; +/* no vbios have more than 6 */ +#define NOUVEAU_TEMP_FAN_TRIP_MAX 10 +struct nouveau_therm_trip_point { + int fan_duty; + int temp; + int hysteresis; +}; + struct nvbios_therm_fan { u16 pwm_freq; u8 min_duty; u8 max_duty; + + u16 bump_period; + u16 slow_down_period; + + struct nouveau_therm_trip_point trip[NOUVEAU_TEMP_FAN_TRIP_MAX]; + u8 nr_fan_trip; + u8 linear_min_temp; + u8 linear_max_temp; }; enum nvbios_therm_domain { diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bios/xpio.h b/drivers/gpu/drm/nouveau/core/include/subdev/bios/xpio.h new file mode 100644 index 000000000000..360baab52e4c --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bios/xpio.h @@ -0,0 +1,19 @@ +#ifndef __NVBIOS_XPIO_H__ +#define __NVBIOS_XPIO_H__ + +#define NVBIOS_XPIO_FLAG_AUX 0x10 +#define NVBIOS_XPIO_FLAG_AUX0 0x00 +#define NVBIOS_XPIO_FLAG_AUX1 0x10 + +struct nvbios_xpio { + u8 type; + u8 addr; + u8 flags; +}; + +u16 dcb_xpio_table(struct nouveau_bios *, u8 idx, + u8 *ver, u8 *hdr, u8 *cnt, u8 *len); +u16 dcb_xpio_parse(struct nouveau_bios *, u8 idx, + u8 *ver, u8 *hdr, u8 *cnt, u8 *len, struct nvbios_xpio *); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/bus.h b/drivers/gpu/drm/nouveau/core/include/subdev/bus.h new file mode 100644 index 000000000000..7d88ec4a6d06 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/include/subdev/bus.h @@ -0,0 +1,41 @@ +#ifndef __NOUVEAU_BUS_H__ +#define __NOUVEAU_BUS_H__ + +#include <core/subdev.h> +#include <core/device.h> + +struct nouveau_bus_intr { + u32 stat; + u32 unit; +}; + +struct nouveau_bus { + struct nouveau_subdev base; +}; + +static inline struct nouveau_bus * +nouveau_bus(void *obj) +{ + return (void *)nv_device(obj)->subdev[NVDEV_SUBDEV_BUS]; +} + +#define nouveau_bus_create(p, e, o, d) \ + nouveau_subdev_create_((p), (e), (o), 0, "PBUS", "master", \ + sizeof(**d), (void **)d) +#define nouveau_bus_destroy(p) \ + nouveau_subdev_destroy(&(p)->base) +#define nouveau_bus_init(p) \ + nouveau_subdev_init(&(p)->base) +#define nouveau_bus_fini(p, s) \ + nouveau_subdev_fini(&(p)->base, (s)) + +#define _nouveau_bus_dtor _nouveau_subdev_dtor +#define _nouveau_bus_init _nouveau_subdev_init +#define _nouveau_bus_fini _nouveau_subdev_fini + +extern struct nouveau_oclass nv04_bus_oclass; +extern struct nouveau_oclass nv31_bus_oclass; +extern struct nouveau_oclass nv50_bus_oclass; +extern struct nouveau_oclass nvc0_bus_oclass; + +#endif diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h b/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h index b75e8f18e52c..c85b9f1579ad 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/gpio.h @@ -3,6 +3,7 @@ #include <core/subdev.h> #include <core/device.h> +#include <core/event.h> #include <subdev/bios.h> #include <subdev/bios/gpio.h> @@ -10,28 +11,18 @@ struct nouveau_gpio { struct nouveau_subdev base; + struct nouveau_event *events; + /* hardware interfaces */ void (*reset)(struct nouveau_gpio *, u8 func); int (*drive)(struct nouveau_gpio *, int line, int dir, int out); int (*sense)(struct nouveau_gpio *, int line); - void (*irq_enable)(struct nouveau_gpio *, int line, bool); /* software interfaces */ int (*find)(struct nouveau_gpio *, int idx, u8 tag, u8 line, struct dcb_gpio_func *); int (*set)(struct nouveau_gpio *, int idx, u8 tag, u8 line, int state); int (*get)(struct nouveau_gpio *, int idx, u8 tag, u8 line); - int (*irq)(struct nouveau_gpio *, int idx, u8 tag, u8 line, bool on); - - /* interrupt handling */ - struct list_head isr; - spinlock_t lock; - - void (*isr_run)(struct nouveau_gpio *, int idx, u32 mask); - int (*isr_add)(struct nouveau_gpio *, int idx, u8 tag, u8 line, - void (*)(void *, int state), void *data); - void (*isr_del)(struct nouveau_gpio *, int idx, u8 tag, u8 line, - void (*)(void *, int state), void *data); }; static inline struct nouveau_gpio * @@ -40,25 +31,23 @@ nouveau_gpio(void *obj) return (void *)nv_device(obj)->subdev[NVDEV_SUBDEV_GPIO]; } -#define nouveau_gpio_create(p,e,o,d) \ - nouveau_gpio_create_((p), (e), (o), sizeof(**d), (void **)d) -#define nouveau_gpio_destroy(p) \ - nouveau_subdev_destroy(&(p)->base) +#define nouveau_gpio_create(p,e,o,l,d) \ + nouveau_gpio_create_((p), (e), (o), (l), sizeof(**d), (void **)d) +#define nouveau_gpio_destroy(p) ({ \ + struct nouveau_gpio *gpio = (p); \ + _nouveau_gpio_dtor(nv_object(gpio)); \ +}) #define nouveau_gpio_fini(p,s) \ nouveau_subdev_fini(&(p)->base, (s)) -int nouveau_gpio_create_(struct nouveau_object *, struct nouveau_object *, - struct nouveau_oclass *, int, void **); -int nouveau_gpio_init(struct nouveau_gpio *); +int nouveau_gpio_create_(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, int, int, void **); +void _nouveau_gpio_dtor(struct nouveau_object *); +int nouveau_gpio_init(struct nouveau_gpio *); extern struct nouveau_oclass nv10_gpio_oclass; extern struct nouveau_oclass nv50_gpio_oclass; extern struct nouveau_oclass nvd0_gpio_oclass; - -void nv50_gpio_dtor(struct nouveau_object *); -int nv50_gpio_init(struct nouveau_object *); -int nv50_gpio_fini(struct nouveau_object *, bool); -void nv50_gpio_intr(struct nouveau_subdev *); -void nv50_gpio_irq_enable(struct nouveau_gpio *, int line, bool); +extern struct nouveau_oclass nve0_gpio_oclass; #endif diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h b/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h index b93ab01e3785..888384c0bed8 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/i2c.h @@ -10,23 +10,59 @@ #define NV_I2C_PORT(n) (0x00 + (n)) #define NV_I2C_DEFAULT(n) (0x80 + (n)) +#define NV_I2C_TYPE_DCBI2C(n) (0x0000 | (n)) +#define NV_I2C_TYPE_EXTDDC(e) (0x0005 | (e) << 8) +#define NV_I2C_TYPE_EXTAUX(e) (0x0006 | (e) << 8) + struct nouveau_i2c_port { + struct nouveau_object base; struct i2c_adapter adapter; - struct nouveau_i2c *i2c; - struct i2c_algo_bit_data bit; + struct list_head head; u8 index; - u8 type; - u32 dcb; - u32 drive; - u32 sense; - u32 state; + + const struct nouveau_i2c_func *func; +}; + +struct nouveau_i2c_func { + void (*acquire)(struct nouveau_i2c_port *); + void (*release)(struct nouveau_i2c_port *); + + void (*drive_scl)(struct nouveau_i2c_port *, int); + void (*drive_sda)(struct nouveau_i2c_port *, int); + int (*sense_scl)(struct nouveau_i2c_port *); + int (*sense_sda)(struct nouveau_i2c_port *); + + int (*aux)(struct nouveau_i2c_port *, u8, u32, u8 *, u8); + int (*pattern)(struct nouveau_i2c_port *, int pattern); + int (*lnk_ctl)(struct nouveau_i2c_port *, int nr, int bw, bool enh); + int (*drv_ctl)(struct nouveau_i2c_port *, int lane, int sw, int pe); }; +#define nouveau_i2c_port_create(p,e,o,i,a,d) \ + nouveau_i2c_port_create_((p), (e), (o), (i), (a), \ + sizeof(**d), (void **)d) +#define nouveau_i2c_port_destroy(p) ({ \ + struct nouveau_i2c_port *port = (p); \ + _nouveau_i2c_port_dtor(nv_object(i2c)); \ +}) +#define nouveau_i2c_port_init(p) \ + nouveau_object_init(&(p)->base) +#define nouveau_i2c_port_fini(p,s) \ + nouveau_object_fini(&(p)->base, (s)) + +int nouveau_i2c_port_create_(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, u8, + const struct i2c_algorithm *, int, void **); +void _nouveau_i2c_port_dtor(struct nouveau_object *); +#define _nouveau_i2c_port_init nouveau_object_init +#define _nouveau_i2c_port_fini nouveau_object_fini + struct nouveau_i2c { struct nouveau_subdev base; struct nouveau_i2c_port *(*find)(struct nouveau_i2c *, u8 index); + struct nouveau_i2c_port *(*find_type)(struct nouveau_i2c *, u16 type); int (*identify)(struct nouveau_i2c *, int index, const char *what, struct i2c_board_info *, bool (*match)(struct nouveau_i2c_port *, @@ -40,21 +76,76 @@ nouveau_i2c(void *obj) return (void *)nv_device(obj)->subdev[NVDEV_SUBDEV_I2C]; } -extern struct nouveau_oclass nouveau_i2c_oclass; +#define nouveau_i2c_create(p,e,o,s,d) \ + nouveau_i2c_create_((p), (e), (o), (s), sizeof(**d), (void **)d) +#define nouveau_i2c_destroy(p) ({ \ + struct nouveau_i2c *i2c = (p); \ + _nouveau_i2c_dtor(nv_object(i2c)); \ +}) +#define nouveau_i2c_init(p) ({ \ + struct nouveau_i2c *i2c = (p); \ + _nouveau_i2c_init(nv_object(i2c)); \ +}) +#define nouveau_i2c_fini(p,s) ({ \ + struct nouveau_i2c *i2c = (p); \ + _nouveau_i2c_fini(nv_object(i2c), (s)); \ +}) -void nouveau_i2c_drive_scl(void *, int); -void nouveau_i2c_drive_sda(void *, int); -int nouveau_i2c_sense_scl(void *); -int nouveau_i2c_sense_sda(void *); +int nouveau_i2c_create_(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, struct nouveau_oclass *, + int, void **); +void _nouveau_i2c_dtor(struct nouveau_object *); +int _nouveau_i2c_init(struct nouveau_object *); +int _nouveau_i2c_fini(struct nouveau_object *, bool); -int nv_rdi2cr(struct nouveau_i2c_port *, u8 addr, u8 reg); -int nv_wri2cr(struct nouveau_i2c_port *, u8 addr, u8 reg, u8 val); -bool nv_probe_i2c(struct nouveau_i2c_port *, u8 addr); - -int nv_rdaux(struct nouveau_i2c_port *, u32 addr, u8 *data, u8 size); -int nv_wraux(struct nouveau_i2c_port *, u32 addr, u8 *data, u8 size); +extern struct nouveau_oclass nv04_i2c_oclass; +extern struct nouveau_oclass nv4e_i2c_oclass; +extern struct nouveau_oclass nv50_i2c_oclass; +extern struct nouveau_oclass nv94_i2c_oclass; +extern struct nouveau_oclass nvd0_i2c_oclass; +extern struct nouveau_oclass nouveau_anx9805_sclass[]; extern const struct i2c_algorithm nouveau_i2c_bit_algo; extern const struct i2c_algorithm nouveau_i2c_aux_algo; +static inline int +nv_rdi2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg) +{ + u8 val; + struct i2c_msg msgs[] = { + { .addr = addr, .flags = 0, .len = 1, .buf = ® }, + { .addr = addr, .flags = I2C_M_RD, .len = 1, .buf = &val }, + }; + + int ret = i2c_transfer(&port->adapter, msgs, 2); + if (ret != 2) + return -EIO; + + return val; +} + +static inline int +nv_wri2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg, u8 val) +{ + u8 buf[2] = { reg, val }; + struct i2c_msg msgs[] = { + { .addr = addr, .flags = 0, .len = 2, .buf = buf }, + }; + + int ret = i2c_transfer(&port->adapter, msgs, 1); + if (ret != 1) + return -EIO; + + return 0; +} + +static inline bool +nv_probe_i2c(struct nouveau_i2c_port *port, u8 addr) +{ + return nv_rdi2cr(port, addr, 0) >= 0; +} + +int nv_rdaux(struct nouveau_i2c_port *, u32 addr, u8 *data, u8 size); +int nv_wraux(struct nouveau_i2c_port *, u32 addr, u8 *data, u8 size); + #endif diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/therm.h b/drivers/gpu/drm/nouveau/core/include/subdev/therm.h index faee569fd458..6b17b614629f 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/therm.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/therm.h @@ -4,10 +4,10 @@ #include <core/device.h> #include <core/subdev.h> -enum nouveau_therm_fan_mode { - FAN_CONTROL_NONE = 0, - FAN_CONTROL_MANUAL = 1, - FAN_CONTROL_NR, +enum nouveau_therm_mode { + NOUVEAU_THERM_CTRL_NONE = 0, + NOUVEAU_THERM_CTRL_MANUAL = 1, + NOUVEAU_THERM_CTRL_AUTO = 2, }; enum nouveau_therm_attr_type { @@ -28,6 +28,11 @@ enum nouveau_therm_attr_type { struct nouveau_therm { struct nouveau_subdev base; + int (*pwm_ctrl)(struct nouveau_therm *, int line, bool); + int (*pwm_get)(struct nouveau_therm *, int line, u32 *, u32 *); + int (*pwm_set)(struct nouveau_therm *, int line, u32, u32); + int (*pwm_clock)(struct nouveau_therm *); + int (*fan_get)(struct nouveau_therm *); int (*fan_set)(struct nouveau_therm *, int); int (*fan_sense)(struct nouveau_therm *); @@ -46,13 +51,29 @@ nouveau_therm(void *obj) } #define nouveau_therm_create(p,e,o,d) \ - nouveau_subdev_create((p), (e), (o), 0, "THERM", "therm", d) -#define nouveau_therm_destroy(p) \ - nouveau_subdev_destroy(&(p)->base) + nouveau_therm_create_((p), (e), (o), sizeof(**d), (void **)d) +#define nouveau_therm_destroy(p) ({ \ + struct nouveau_therm *therm = (p); \ + _nouveau_therm_dtor(nv_object(therm)); \ +}) +#define nouveau_therm_init(p) ({ \ + struct nouveau_therm *therm = (p); \ + _nouveau_therm_init(nv_object(therm)); \ +}) +#define nouveau_therm_fini(p,s) ({ \ + struct nouveau_therm *therm = (p); \ + _nouveau_therm_init(nv_object(therm), (s)); \ +}) -#define _nouveau_therm_dtor _nouveau_subdev_dtor +int nouveau_therm_create_(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, int, void **); +void _nouveau_therm_dtor(struct nouveau_object *); +int _nouveau_therm_init(struct nouveau_object *); +int _nouveau_therm_fini(struct nouveau_object *, bool); extern struct nouveau_oclass nv40_therm_oclass; extern struct nouveau_oclass nv50_therm_oclass; +extern struct nouveau_oclass nva3_therm_oclass; +extern struct nouveau_oclass nvd0_therm_oclass; #endif diff --git a/drivers/gpu/drm/nouveau/core/include/subdev/timer.h b/drivers/gpu/drm/nouveau/core/include/subdev/timer.h index c24ec8ab3db4..e465d158d352 100644 --- a/drivers/gpu/drm/nouveau/core/include/subdev/timer.h +++ b/drivers/gpu/drm/nouveau/core/include/subdev/timer.h @@ -10,6 +10,14 @@ struct nouveau_alarm { void (*func)(struct nouveau_alarm *); }; +static inline void +nouveau_alarm_init(struct nouveau_alarm *alarm, + void (*func)(struct nouveau_alarm *)) +{ + INIT_LIST_HEAD(&alarm->head); + alarm->func = func; +} + bool nouveau_timer_wait_eq(void *, u64 nsec, u32 addr, u32 mask, u32 data); bool nouveau_timer_wait_ne(void *, u64 nsec, u32 addr, u32 mask, u32 data); bool nouveau_timer_wait_cb(void *, u64 nsec, bool (*func)(void *), void *data); diff --git a/drivers/gpu/drm/nouveau/core/os.h b/drivers/gpu/drm/nouveau/core/os.h index cfe3b9cad156..eb496033b55c 100644 --- a/drivers/gpu/drm/nouveau/core/os.h +++ b/drivers/gpu/drm/nouveau/core/os.h @@ -16,6 +16,7 @@ #include <linux/vmalloc.h> #include <linux/acpi.h> #include <linux/dmi.h> +#include <linux/reboot.h> #include <asm/unaligned.h> diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/base.c b/drivers/gpu/drm/nouveau/core/subdev/bios/base.c index f621f69fa1a2..e816f06637a7 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/base.c @@ -172,7 +172,7 @@ out: nv_wr32(bios, pcireg, access); } -#if defined(CONFIG_ACPI) +#if defined(CONFIG_ACPI) && defined(CONFIG_X86) int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len); bool nouveau_acpi_rom_supported(struct pci_dev *pdev); #else diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/dcb.c b/drivers/gpu/drm/nouveau/core/subdev/bios/dcb.c index 0fd87df99dd6..2d9b9d7a7992 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/dcb.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/dcb.c @@ -107,6 +107,18 @@ dcb_outp(struct nouveau_bios *bios, u8 idx, u8 *ver, u8 *len) return 0x0000; } +static inline u16 +dcb_outp_hasht(struct dcb_output *outp) +{ + return (outp->extdev << 8) | (outp->location << 4) | outp->type; +} + +static inline u16 +dcb_outp_hashm(struct dcb_output *outp) +{ + return (outp->heads << 8) | (outp->link << 6) | outp->or; +} + u16 dcb_outp_parse(struct nouveau_bios *bios, u8 idx, u8 *ver, u8 *len, struct dcb_output *outp) @@ -135,34 +147,28 @@ dcb_outp_parse(struct nouveau_bios *bios, u8 idx, u8 *ver, u8 *len, case DCB_OUTPUT_DP: outp->link = (conf & 0x00000030) >> 4; outp->sorconf.link = outp->link; /*XXX*/ + outp->extdev = 0x00; + if (outp->location != 0) + outp->extdev = (conf & 0x0000ff00) >> 8; break; default: break; } } + + outp->hasht = dcb_outp_hasht(outp); + outp->hashm = dcb_outp_hashm(outp); } return dcb; } -static inline u16 -dcb_outp_hasht(struct dcb_output *outp) -{ - return outp->type; -} - -static inline u16 -dcb_outp_hashm(struct dcb_output *outp) -{ - return (outp->heads << 8) | (outp->link << 6) | outp->or; -} - u16 dcb_outp_match(struct nouveau_bios *bios, u16 type, u16 mask, u8 *ver, u8 *len, struct dcb_output *outp) { u16 dcb, idx = 0; while ((dcb = dcb_outp_parse(bios, idx++, ver, len, outp))) { - if (dcb_outp_hasht(outp) == type) { + if ((dcb_outp_hasht(outp) & 0x00ff) == (type & 0x00ff)) { if ((dcb_outp_hashm(outp) & mask) == mask) break; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/extdev.c b/drivers/gpu/drm/nouveau/core/subdev/bios/extdev.c index 5afb568b2d69..b2a676e53580 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/extdev.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/extdev.c @@ -48,7 +48,7 @@ extdev_table(struct nouveau_bios *bios, u8 *ver, u8 *hdr, u8 *len, u8 *cnt) return extdev + *hdr; } -u16 +static u16 nvbios_extdev_entry(struct nouveau_bios *bios, int idx, u8 *ver, u8 *len) { u8 hdr, cnt; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/gpio.c b/drivers/gpu/drm/nouveau/core/subdev/bios/gpio.c index c84e93fa6d95..172a4f999990 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/gpio.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/gpio.c @@ -25,6 +25,7 @@ #include <subdev/bios.h> #include <subdev/bios/dcb.h> #include <subdev/bios/gpio.h> +#include <subdev/bios/xpio.h> u16 dcb_gpio_table(struct nouveau_bios *bios, u8 *ver, u8 *hdr, u8 *cnt, u8 *len) @@ -60,8 +61,14 @@ dcb_gpio_table(struct nouveau_bios *bios, u8 *ver, u8 *hdr, u8 *cnt, u8 *len) u16 dcb_gpio_entry(struct nouveau_bios *bios, int idx, int ent, u8 *ver, u8 *len) { - u8 hdr, cnt; - u16 gpio = !idx ? dcb_gpio_table(bios, ver, &hdr, &cnt, len) : 0x0000; + u8 hdr, cnt, xver; /* use gpio version for xpio entry parsing */ + u16 gpio; + + if (!idx--) + gpio = dcb_gpio_table(bios, ver, &hdr, &cnt, len); + else + gpio = dcb_xpio_table(bios, idx, &xver, &hdr, &cnt, len); + if (gpio && ent < cnt) return gpio + hdr + (ent * *len); return 0x0000; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/i2c.c b/drivers/gpu/drm/nouveau/core/subdev/bios/i2c.c index ad577db83766..cfb9288c6d28 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/i2c.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/i2c.c @@ -70,12 +70,12 @@ dcb_i2c_parse(struct nouveau_bios *bios, u8 idx, struct dcb_i2c_entry *info) u8 ver, len; u16 ent = dcb_i2c_entry(bios, idx, &ver, &len); if (ent) { - info->data = nv_ro32(bios, ent + 0); - info->type = nv_ro08(bios, ent + 3); + info->type = nv_ro08(bios, ent + 3); + info->share = DCB_I2C_UNUSED; if (ver < 0x30) { info->type &= 0x07; if (info->type == 0x07) - info->type = 0xff; + info->type = DCB_I2C_UNUSED; } switch (info->type) { @@ -88,7 +88,11 @@ dcb_i2c_parse(struct nouveau_bios *bios, u8 idx, struct dcb_i2c_entry *info) return 0; case DCB_I2C_NVIO_BIT: case DCB_I2C_NVIO_AUX: - info->drive = nv_ro08(bios, ent + 0); + info->drive = nv_ro08(bios, ent + 0) & 0x0f; + if (nv_ro08(bios, ent + 1) & 0x01) { + info->share = nv_ro08(bios, ent + 1) >> 1; + info->share &= 0x0f; + } return 0; case DCB_I2C_UNUSED: return 0; @@ -121,7 +125,8 @@ dcb_i2c_parse(struct nouveau_bios *bios, u8 idx, struct dcb_i2c_entry *info) if (!info->sense) info->sense = 0x36; } - info->type = DCB_I2C_NV04_BIT; + info->type = DCB_I2C_NV04_BIT; + info->share = DCB_I2C_UNUSED; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/init.c b/drivers/gpu/drm/nouveau/core/subdev/bios/init.c index 690ed438b2ad..2cc1e6a5eb6a 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/init.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/init.c @@ -231,6 +231,11 @@ init_i2c(struct nvbios_init *init, int index) return NULL; } + if (index == -2 && init->outp->location) { + index = NV_I2C_TYPE_EXTAUX(init->outp->extdev); + return i2c->find_type(i2c, index); + } + index = init->outp->i2c_index; } @@ -258,7 +263,7 @@ init_wri2cr(struct nvbios_init *init, u8 index, u8 addr, u8 reg, u8 val) static int init_rdauxr(struct nvbios_init *init, u32 addr) { - struct nouveau_i2c_port *port = init_i2c(init, -1); + struct nouveau_i2c_port *port = init_i2c(init, -2); u8 data; if (port && init_exec(init)) { @@ -274,7 +279,7 @@ init_rdauxr(struct nvbios_init *init, u32 addr) static int init_wrauxr(struct nvbios_init *init, u32 addr, u8 data) { - struct nouveau_i2c_port *port = init_i2c(init, -1); + struct nouveau_i2c_port *port = init_i2c(init, -2); if (port && init_exec(init)) return nv_wraux(port, addr, &data, 1); return -ENODEV; @@ -1816,7 +1821,7 @@ init_ram_restrict_zm_reg_group(struct nvbios_init *init) u8 i, j; trace("RAM_RESTRICT_ZM_REG_GROUP\t" - "R[%08x] 0x%02x 0x%02x\n", addr, incr, num); + "R[0x%08x] 0x%02x 0x%02x\n", addr, incr, num); init->offset += 7; for (i = 0; i < num; i++) { @@ -1849,7 +1854,7 @@ init_copy_zm_reg(struct nvbios_init *init) u32 sreg = nv_ro32(bios, init->offset + 1); u32 dreg = nv_ro32(bios, init->offset + 5); - trace("COPY_ZM_REG\tR[0x%06x] = R[0x%06x]\n", sreg, dreg); + trace("COPY_ZM_REG\tR[0x%06x] = R[0x%06x]\n", dreg, sreg); init->offset += 9; init_wr32(init, dreg, init_rd32(init, sreg)); @@ -1866,7 +1871,7 @@ init_zm_reg_group(struct nvbios_init *init) u32 addr = nv_ro32(bios, init->offset + 1); u8 count = nv_ro08(bios, init->offset + 5); - trace("ZM_REG_GROUP\tR[0x%06x] =\n"); + trace("ZM_REG_GROUP\tR[0x%06x] =\n", addr); init->offset += 6; while (count--) { diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/therm.c b/drivers/gpu/drm/nouveau/core/subdev/bios/therm.c index 862a08a2ae27..22a20573ed1b 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/therm.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/therm.c @@ -55,7 +55,7 @@ therm_table(struct nouveau_bios *bios, u8 *ver, u8 *hdr, u8 *len, u8 *cnt) return therm + nv_ro08(bios, therm + 1); } -u16 +static u16 nvbios_therm_entry(struct nouveau_bios *bios, int idx, u8 *ver, u8 *len) { u8 hdr, cnt; @@ -155,10 +155,15 @@ int nvbios_therm_fan_parse(struct nouveau_bios *bios, struct nvbios_therm_fan *fan) { + struct nouveau_therm_trip_point *cur_trip = NULL; u8 ver, len, i; u16 entry; + uint8_t duty_lut[] = { 0, 0, 25, 0, 40, 0, 50, 0, + 75, 0, 85, 0, 100, 0, 100, 0 }; + i = 0; + fan->nr_fan_trip = 0; while ((entry = nvbios_therm_entry(bios, i++, &ver, &len))) { s16 value = nv_ro16(bios, entry + 1); @@ -167,9 +172,30 @@ nvbios_therm_fan_parse(struct nouveau_bios *bios, fan->min_duty = value & 0xff; fan->max_duty = (value & 0xff00) >> 8; break; + case 0x24: + fan->nr_fan_trip++; + cur_trip = &fan->trip[fan->nr_fan_trip - 1]; + cur_trip->hysteresis = value & 0xf; + cur_trip->temp = (value & 0xff0) >> 4; + cur_trip->fan_duty = duty_lut[(value & 0xf000) >> 12]; + break; + case 0x25: + cur_trip = &fan->trip[fan->nr_fan_trip - 1]; + cur_trip->fan_duty = value; + break; case 0x26: fan->pwm_freq = value; break; + case 0x3b: + fan->bump_period = value; + break; + case 0x3c: + fan->slow_down_period = value; + break; + case 0x46: + fan->linear_min_temp = nv_ro08(bios, entry + 1); + fan->linear_max_temp = nv_ro08(bios, entry + 2); + break; } } diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/xpio.c b/drivers/gpu/drm/nouveau/core/subdev/bios/xpio.c new file mode 100644 index 000000000000..e9b8e5d30a7a --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/xpio.c @@ -0,0 +1,76 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/bios.h> +#include <subdev/bios/gpio.h> +#include <subdev/bios/xpio.h> + +static u16 +dcb_xpiod_table(struct nouveau_bios *bios, u8 *ver, u8 *hdr, u8 *cnt, u8 *len) +{ + u16 data = dcb_gpio_table(bios, ver, hdr, cnt, len); + if (data && *ver >= 0x40 && *hdr >= 0x06) { + u16 xpio = nv_ro16(bios, data + 0x04); + if (xpio) { + *ver = nv_ro08(bios, data + 0x00); + *hdr = nv_ro08(bios, data + 0x01); + *cnt = nv_ro08(bios, data + 0x02); + *len = nv_ro08(bios, data + 0x03); + return xpio; + } + } + return 0x0000; +} + +u16 +dcb_xpio_table(struct nouveau_bios *bios, u8 idx, + u8 *ver, u8 *hdr, u8 *cnt, u8 *len) +{ + u16 data = dcb_xpiod_table(bios, ver, hdr, cnt, len); + if (data && idx < *cnt) { + u16 xpio = nv_ro16(bios, data + *hdr + (idx * *len)); + if (xpio) { + *ver = nv_ro08(bios, data + 0x00); + *hdr = nv_ro08(bios, data + 0x01); + *cnt = nv_ro08(bios, data + 0x02); + *len = nv_ro08(bios, data + 0x03); + return xpio; + } + } + return 0x0000; +} + +u16 +dcb_xpio_parse(struct nouveau_bios *bios, u8 idx, + u8 *ver, u8 *hdr, u8 *cnt, u8 *len, + struct nvbios_xpio *info) +{ + u16 data = dcb_xpio_table(bios, idx, ver, hdr, cnt, len); + if (data && *len >= 6) { + info->type = nv_ro08(bios, data + 0x04); + info->addr = nv_ro08(bios, data + 0x05); + info->flags = nv_ro08(bios, data + 0x06); + } + return 0x0000; +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/bus/nv04.c b/drivers/gpu/drm/nouveau/core/subdev/bus/nv04.c new file mode 100644 index 000000000000..8c7f8057a185 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bus/nv04.c @@ -0,0 +1,95 @@ +/* + * Copyright 2012 Nouveau Community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres <martin.peres@labri.fr> + * Ben Skeggs + */ + +#include <subdev/bus.h> + +struct nv04_bus_priv { + struct nouveau_bus base; +}; + +static void +nv04_bus_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_bus *pbus = nouveau_bus(subdev); + u32 stat = nv_rd32(pbus, 0x001100) & nv_rd32(pbus, 0x001140); + + if (stat & 0x00000001) { + nv_error(pbus, "BUS ERROR\n"); + stat &= ~0x00000001; + nv_wr32(pbus, 0x001100, 0x00000001); + } + + if (stat & 0x00000110) { + subdev = nouveau_subdev(subdev, NVDEV_SUBDEV_GPIO); + if (subdev && subdev->intr) + subdev->intr(subdev); + stat &= ~0x00000110; + nv_wr32(pbus, 0x001100, 0x00000110); + } + + if (stat) { + nv_error(pbus, "unknown intr 0x%08x\n", stat); + nv_mask(pbus, 0x001140, stat, 0x00000000); + } +} + +static int +nv04_bus_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv04_bus_priv *priv; + int ret; + + ret = nouveau_bus_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + nv_subdev(priv)->intr = nv04_bus_intr; + return 0; +} + +static int +nv04_bus_init(struct nouveau_object *object) +{ + struct nv04_bus_priv *priv = (void *)object; + + nv_wr32(priv, 0x001100, 0xffffffff); + nv_wr32(priv, 0x001140, 0x00000111); + + return nouveau_bus_init(&priv->base); +} + +struct nouveau_oclass +nv04_bus_oclass = { + .handle = NV_SUBDEV(BUS, 0x04), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv04_bus_ctor, + .dtor = _nouveau_bus_dtor, + .init = nv04_bus_init, + .fini = _nouveau_bus_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bus/nv31.c b/drivers/gpu/drm/nouveau/core/subdev/bus/nv31.c new file mode 100644 index 000000000000..34132aef34e1 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bus/nv31.c @@ -0,0 +1,112 @@ +/* + * Copyright 2012 Nouveau Community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres <martin.peres@labri.fr> + * Ben Skeggs + */ + +#include <subdev/bus.h> + +struct nv31_bus_priv { + struct nouveau_bus base; +}; + +static void +nv31_bus_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_bus *pbus = nouveau_bus(subdev); + u32 stat = nv_rd32(pbus, 0x001100) & nv_rd32(pbus, 0x001140); + u32 gpio = nv_rd32(pbus, 0x001104) & nv_rd32(pbus, 0x001144); + + if (gpio) { + subdev = nouveau_subdev(pbus, NVDEV_SUBDEV_GPIO); + if (subdev && subdev->intr) + subdev->intr(subdev); + } + + if (stat & 0x00000008) { /* NV41- */ + u32 addr = nv_rd32(pbus, 0x009084); + u32 data = nv_rd32(pbus, 0x009088); + + nv_error(pbus, "MMIO %s of 0x%08x FAULT at 0x%06x\n", + (addr & 0x00000002) ? "write" : "read", data, + (addr & 0x00fffffc)); + + stat &= ~0x00000008; + nv_wr32(pbus, 0x001100, 0x00000008); + } + + if (stat & 0x00070000) { + subdev = nouveau_subdev(pbus, NVDEV_SUBDEV_THERM); + if (subdev && subdev->intr) + subdev->intr(subdev); + stat &= ~0x00070000; + nv_wr32(pbus, 0x001100, 0x00070000); + } + + if (stat) { + nv_error(pbus, "unknown intr 0x%08x\n", stat); + nv_mask(pbus, 0x001140, stat, 0x00000000); + } +} + +static int +nv31_bus_init(struct nouveau_object *object) +{ + struct nv31_bus_priv *priv = (void *)object; + int ret; + + ret = nouveau_bus_init(&priv->base); + if (ret) + return ret; + + nv_wr32(priv, 0x001100, 0xffffffff); + nv_wr32(priv, 0x001140, 0x00070008); + return 0; +} + +static int +nv31_bus_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv31_bus_priv *priv; + int ret; + + ret = nouveau_bus_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + nv_subdev(priv)->intr = nv31_bus_intr; + return 0; +} + +struct nouveau_oclass +nv31_bus_oclass = { + .handle = NV_SUBDEV(BUS, 0x31), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv31_bus_ctor, + .dtor = _nouveau_bus_dtor, + .init = nv31_bus_init, + .fini = _nouveau_bus_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bus/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/bus/nv50.c new file mode 100644 index 000000000000..f5b2117fa8c6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bus/nv50.c @@ -0,0 +1,105 @@ +/* + * Copyright 2012 Nouveau Community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres <martin.peres@labri.fr> + * Ben Skeggs + */ + +#include <subdev/bus.h> + +struct nv50_bus_priv { + struct nouveau_bus base; +}; + +static void +nv50_bus_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_bus *pbus = nouveau_bus(subdev); + u32 stat = nv_rd32(pbus, 0x001100) & nv_rd32(pbus, 0x001140); + + if (stat & 0x00000008) { + u32 addr = nv_rd32(pbus, 0x009084); + u32 data = nv_rd32(pbus, 0x009088); + + nv_error(pbus, "MMIO %s of 0x%08x FAULT at 0x%06x\n", + (addr & 0x00000002) ? "write" : "read", data, + (addr & 0x00fffffc)); + + stat &= ~0x00000008; + nv_wr32(pbus, 0x001100, 0x00000008); + } + + if (stat & 0x00010000) { + subdev = nouveau_subdev(pbus, NVDEV_SUBDEV_THERM); + if (subdev && subdev->intr) + subdev->intr(subdev); + stat &= ~0x00010000; + nv_wr32(pbus, 0x001100, 0x00010000); + } + + if (stat) { + nv_error(pbus, "unknown intr 0x%08x\n", stat); + nv_mask(pbus, 0x001140, stat, 0); + } +} + +static int +nv50_bus_init(struct nouveau_object *object) +{ + struct nv50_bus_priv *priv = (void *)object; + int ret; + + ret = nouveau_bus_init(&priv->base); + if (ret) + return ret; + + nv_wr32(priv, 0x001100, 0xffffffff); + nv_wr32(priv, 0x001140, 0x00010008); + return 0; +} + +static int +nv50_bus_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv50_bus_priv *priv; + int ret; + + ret = nouveau_bus_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + nv_subdev(priv)->intr = nv50_bus_intr; + return 0; +} + +struct nouveau_oclass +nv50_bus_oclass = { + .handle = NV_SUBDEV(BUS, 0x50), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv50_bus_ctor, + .dtor = _nouveau_bus_dtor, + .init = nv50_bus_init, + .fini = _nouveau_bus_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/bus/nvc0.c b/drivers/gpu/drm/nouveau/core/subdev/bus/nvc0.c new file mode 100644 index 000000000000..b192d6246363 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/bus/nvc0.c @@ -0,0 +1,101 @@ +/* + * Copyright 2012 Nouveau Community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres <martin.peres@labri.fr> + * Ben Skeggs + */ + +#include <subdev/bus.h> + +struct nvc0_bus_priv { + struct nouveau_bus base; +}; + +static void +nvc0_bus_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_bus *pbus = nouveau_bus(subdev); + u32 stat = nv_rd32(pbus, 0x001100) & nv_rd32(pbus, 0x001140); + + if (stat & 0x0000000e) { + u32 addr = nv_rd32(pbus, 0x009084); + u32 data = nv_rd32(pbus, 0x009088); + + nv_error(pbus, "MMIO %s of 0x%08x FAULT at 0x%06x [ %s%s%s]\n", + (addr & 0x00000002) ? "write" : "read", data, + (addr & 0x00fffffc), + (stat & 0x00000002) ? "!ENGINE " : "", + (stat & 0x00000004) ? "IBUS " : "", + (stat & 0x00000008) ? "TIMEOUT " : ""); + + nv_wr32(pbus, 0x009084, 0x00000000); + nv_wr32(pbus, 0x001100, (stat & 0x0000000e)); + stat &= ~0x0000000e; + } + + if (stat) { + nv_error(pbus, "unknown intr 0x%08x\n", stat); + nv_mask(pbus, 0x001140, stat, 0x00000000); + } +} + +static int +nvc0_bus_init(struct nouveau_object *object) +{ + struct nvc0_bus_priv *priv = (void *)object; + int ret; + + ret = nouveau_bus_init(&priv->base); + if (ret) + return ret; + + nv_wr32(priv, 0x001100, 0xffffffff); + nv_wr32(priv, 0x001140, 0x0000000e); + return 0; +} + +static int +nvc0_bus_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nvc0_bus_priv *priv; + int ret; + + ret = nouveau_bus_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + nv_subdev(priv)->intr = nvc0_bus_intr; + return 0; +} + +struct nouveau_oclass +nvc0_bus_oclass = { + .handle = NV_SUBDEV(BUS, 0xc0), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nvc0_bus_ctor, + .dtor = _nouveau_bus_dtor, + .init = nvc0_bus_init, + .fini = _nouveau_bus_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/base.c b/drivers/gpu/drm/nouveau/core/subdev/device/base.c index f8a7ed4166cf..3937ced5c753 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/base.c @@ -66,6 +66,7 @@ static const u64 disable_map[] = { [NVDEV_SUBDEV_CLOCK] = NV_DEVICE_DISABLE_CORE, [NVDEV_SUBDEV_MXM] = NV_DEVICE_DISABLE_CORE, [NVDEV_SUBDEV_MC] = NV_DEVICE_DISABLE_CORE, + [NVDEV_SUBDEV_BUS] = NV_DEVICE_DISABLE_CORE, [NVDEV_SUBDEV_TIMER] = NV_DEVICE_DISABLE_CORE, [NVDEV_SUBDEV_FB] = NV_DEVICE_DISABLE_CORE, [NVDEV_SUBDEV_LTCG] = NV_DEVICE_DISABLE_CORE, @@ -103,8 +104,8 @@ nouveau_devobj_ctor(struct nouveau_object *parent, struct nouveau_device *device; struct nouveau_devobj *devobj; struct nv_device_class *args = data; - u64 disable, boot0, strap; - u64 mmio_base, mmio_size; + u32 boot0, strap; + u64 disable, mmio_base, mmio_size; void __iomem *map; int ret, i, c; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv04.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv04.c index 8626d0d6cbbc..473c5c03d3c9 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv04.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv04.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/i2c.h> #include <subdev/clock.h> #include <subdev/devinit.h> @@ -46,10 +47,11 @@ nv04_identify(struct nouveau_device *device) case 0x04: device->cname = "NV04"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv04_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv04_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -63,10 +65,11 @@ nv04_identify(struct nouveau_device *device) case 0x05: device->cname = "NV05"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv05_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv04_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv10.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv10.c index 9c40b0fb23f6..d0774f5bebe1 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv10.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv10.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -48,10 +49,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV10"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -64,10 +66,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV15"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -82,10 +85,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV16"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -100,10 +104,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "nForce"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv1a_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -118,10 +123,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV11"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -136,10 +142,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV17"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -154,10 +161,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "nForce2"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv1a_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -172,10 +180,11 @@ nv10_identify(struct nouveau_device *device) device->cname = "NV18"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv20.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv20.c index 74f88f48e1c2..ab920e0dc45b 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv20.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv20.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -49,10 +50,11 @@ nv20_identify(struct nouveau_device *device) device->cname = "NV20"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv20_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -67,10 +69,11 @@ nv20_identify(struct nouveau_device *device) device->cname = "NV25"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv25_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -85,10 +88,11 @@ nv20_identify(struct nouveau_device *device) device->cname = "NV28"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv25_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -103,10 +107,11 @@ nv20_identify(struct nouveau_device *device) device->cname = "NV2A"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv25_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv30.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv30.c index 0ac1b2c4f61d..5f2110261b04 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv30.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv30.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -49,10 +50,11 @@ nv30_identify(struct nouveau_device *device) device->cname = "NV30"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv30_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -67,10 +69,11 @@ nv30_identify(struct nouveau_device *device) device->cname = "NV35"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv04_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv35_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -85,10 +88,11 @@ nv30_identify(struct nouveau_device *device) device->cname = "NV31"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv30_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -104,10 +108,11 @@ nv30_identify(struct nouveau_device *device) device->cname = "NV36"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv20_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv36_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; @@ -123,10 +128,11 @@ nv30_identify(struct nouveau_device *device) device->cname = "NV34"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv04_clock_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv10_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv10_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv04_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv40.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv40.c index 41d59689a021..f3d55efe9ac9 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv40.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv40.c @@ -24,6 +24,8 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> +#include <subdev/vm.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -50,11 +52,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV40"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv40_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -70,11 +73,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV41"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv41_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -90,11 +94,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV42"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv41_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -110,11 +115,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV43"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv41_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -130,11 +136,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV45"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv40_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -150,11 +157,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "G70"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv47_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -170,11 +178,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "G71"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv49_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -190,11 +199,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "G73"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv04_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv49_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -210,11 +220,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV44"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv44_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -230,11 +241,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "G72"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv46_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -250,11 +262,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "NV44A"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv44_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -270,11 +283,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "C61"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv46_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -290,11 +304,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "C51"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv4e_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv4e_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -310,11 +325,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "C73"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv46_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -330,11 +346,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "C67"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv46_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; @@ -350,11 +367,12 @@ nv40_identify(struct nouveau_device *device) device->cname = "C68"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv10_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv04_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv40_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv40_therm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv1a_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv44_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv31_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv46_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv40_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/device/nv50.c index 6ccfd8585ba2..5ed2fa51ddc2 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nv50.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -57,12 +58,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G80"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv50_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -79,12 +81,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G84"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv50_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -104,12 +107,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G86"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv50_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -129,12 +133,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G92"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv50_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -154,12 +159,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G94"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -179,12 +185,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G96"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv50_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -204,12 +211,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G98"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -229,12 +237,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "G200"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv50_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -254,12 +263,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "MCP77/MCP78"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -279,12 +289,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "MCP79/MCP7A"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nv50_clock_oclass; device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -304,12 +315,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "GT215"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nva3_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -330,12 +342,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "GT216"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nva3_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -355,12 +368,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "GT218"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nva3_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; @@ -380,12 +394,13 @@ nv50_identify(struct nouveau_device *device) device->cname = "MCP89"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nva3_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nv98_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nv50_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nv50_fb_oclass; device->oclass[NVDEV_SUBDEV_INSTMEM] = &nv50_instmem_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nvc0.c b/drivers/gpu/drm/nouveau/core/subdev/device/nvc0.c index f0461685a422..4393eb4d6564 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nvc0.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nvc0.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -57,12 +58,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF100"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -85,12 +87,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF104"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -113,12 +116,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF106"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -141,12 +145,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF114"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -169,12 +174,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF116"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -197,12 +203,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF108"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -225,12 +232,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF110"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nv50_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nv94_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nva3_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -253,12 +261,13 @@ nvc0_identify(struct nouveau_device *device) device->cname = "GF119"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; device->oclass[NVDEV_SUBDEV_GPIO ] = &nvd0_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nvd0_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nvd0_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -282,4 +291,4 @@ nvc0_identify(struct nouveau_device *device) } return 0; -} + } diff --git a/drivers/gpu/drm/nouveau/core/subdev/device/nve0.c b/drivers/gpu/drm/nouveau/core/subdev/device/nve0.c index 03a652876e73..5c12391619fd 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/device/nve0.c +++ b/drivers/gpu/drm/nouveau/core/subdev/device/nve0.c @@ -24,6 +24,7 @@ #include <subdev/device.h> #include <subdev/bios.h> +#include <subdev/bus.h> #include <subdev/gpio.h> #include <subdev/i2c.h> #include <subdev/clock.h> @@ -56,13 +57,14 @@ nve0_identify(struct nouveau_device *device) case 0xe4: device->cname = "GK104"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; - device->oclass[NVDEV_SUBDEV_GPIO ] = &nvd0_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_GPIO ] = &nve0_gpio_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nvd0_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nvd0_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -84,13 +86,14 @@ nve0_identify(struct nouveau_device *device) case 0xe7: device->cname = "GK107"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; - device->oclass[NVDEV_SUBDEV_GPIO ] = &nvd0_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_GPIO ] = &nve0_gpio_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nvd0_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nvd0_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; @@ -112,13 +115,14 @@ nve0_identify(struct nouveau_device *device) case 0xe6: device->cname = "GK106"; device->oclass[NVDEV_SUBDEV_VBIOS ] = &nouveau_bios_oclass; - device->oclass[NVDEV_SUBDEV_GPIO ] = &nvd0_gpio_oclass; - device->oclass[NVDEV_SUBDEV_I2C ] = &nouveau_i2c_oclass; + device->oclass[NVDEV_SUBDEV_GPIO ] = &nve0_gpio_oclass; + device->oclass[NVDEV_SUBDEV_I2C ] = &nvd0_i2c_oclass; device->oclass[NVDEV_SUBDEV_CLOCK ] = &nvc0_clock_oclass; - device->oclass[NVDEV_SUBDEV_THERM ] = &nv50_therm_oclass; + device->oclass[NVDEV_SUBDEV_THERM ] = &nvd0_therm_oclass; device->oclass[NVDEV_SUBDEV_MXM ] = &nv50_mxm_oclass; device->oclass[NVDEV_SUBDEV_DEVINIT] = &nv50_devinit_oclass; device->oclass[NVDEV_SUBDEV_MC ] = &nvc0_mc_oclass; + device->oclass[NVDEV_SUBDEV_BUS ] = &nvc0_bus_oclass; device->oclass[NVDEV_SUBDEV_TIMER ] = &nv04_timer_oclass; device->oclass[NVDEV_SUBDEV_FB ] = &nvc0_fb_oclass; device->oclass[NVDEV_SUBDEV_LTCG ] = &nvc0_ltcg_oclass; diff --git a/drivers/gpu/drm/nouveau/core/subdev/devinit/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/devinit/nv50.c index ae7249b09797..4a8577838417 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/devinit/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/devinit/nv50.c @@ -78,12 +78,13 @@ nv50_devinit_init(struct nouveau_object *object) if (ret) return ret; - /* if we ran the init tables, execute first script pointer for each - * display table output entry that has a matching dcb entry. + /* if we ran the init tables, we have to execute the first script + * pointer of each dcb entry's display encoder table in order + * to properly initialise each encoder. */ - while (priv->base.post && ver) { - u16 data = nvbios_outp_parse(bios, i++, &ver, &hdr, &cnt, &len, &info); - if (data && dcb_outp_match(bios, info.type, info.mask, &ver, &len, &outp)) { + while (priv->base.post && dcb_outp_parse(bios, i, &ver, &hdr, &outp)) { + if (nvbios_outp_match(bios, outp.hasht, outp.hashm, + &ver, &hdr, &cnt, &len, &info)) { struct nvbios_init init = { .subdev = nv_subdev(priv), .bios = bios, @@ -95,7 +96,8 @@ nv50_devinit_init(struct nouveau_object *object) nvbios_exec(&init); } - }; + i++; + } return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/fb/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/fb/nv50.c index 487cb8c6c204..a4338d92b02e 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/fb/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/fb/nv50.c @@ -22,8 +22,10 @@ * Authors: Ben Skeggs */ -#include <core/object.h> +#include <core/client.h> #include <core/enum.h> +#include <core/engctx.h> +#include <core/object.h> #include <subdev/fb.h> #include <subdev/bios.h> @@ -302,17 +304,18 @@ static const struct nouveau_enum vm_client[] = { }; static const struct nouveau_enum vm_engine[] = { - { 0x00000000, "PGRAPH", NULL }, - { 0x00000001, "PVP", NULL }, + { 0x00000000, "PGRAPH", NULL, NVDEV_ENGINE_GR }, + { 0x00000001, "PVP", NULL, NVDEV_ENGINE_VP }, { 0x00000004, "PEEPHOLE", NULL }, - { 0x00000005, "PFIFO", vm_pfifo_subclients }, + { 0x00000005, "PFIFO", vm_pfifo_subclients, NVDEV_ENGINE_FIFO }, { 0x00000006, "BAR", vm_bar_subclients }, - { 0x00000008, "PPPP", NULL }, - { 0x00000009, "PBSP", NULL }, - { 0x0000000a, "PCRYPT", NULL }, + { 0x00000008, "PPPP", NULL, NVDEV_ENGINE_PPP }, + { 0x00000008, "PMPEG", NULL, NVDEV_ENGINE_MPEG }, + { 0x00000009, "PBSP", NULL, NVDEV_ENGINE_BSP }, + { 0x0000000a, "PCRYPT", NULL, NVDEV_ENGINE_CRYPT }, { 0x0000000b, "PCOUNTER", NULL }, { 0x0000000c, "SEMAPHORE_BG", NULL }, - { 0x0000000d, "PCOPY", NULL }, + { 0x0000000d, "PCOPY", NULL, NVDEV_ENGINE_COPY0 }, { 0x0000000e, "PDAEMON", NULL }, {} }; @@ -334,8 +337,10 @@ static void nv50_fb_intr(struct nouveau_subdev *subdev) { struct nouveau_device *device = nv_device(subdev); + struct nouveau_engine *engine; struct nv50_fb_priv *priv = (void *)subdev; const struct nouveau_enum *en, *cl; + struct nouveau_object *engctx = NULL; u32 trap[6], idx, chan; u8 st0, st1, st2, st3; int i; @@ -366,36 +371,55 @@ nv50_fb_intr(struct nouveau_subdev *subdev) } chan = (trap[2] << 16) | trap[1]; - nv_error(priv, "trapped %s at 0x%02x%04x%04x on channel 0x%08x ", + en = nouveau_enum_find(vm_engine, st0); + + if (en && en->data2) { + const struct nouveau_enum *orig_en = en; + while (en->name && en->value == st0 && en->data2) { + engine = nouveau_engine(subdev, en->data2); + if (engine) { + engctx = nouveau_engctx_get(engine, chan); + if (engctx) + break; + } + en++; + } + if (!engctx) + en = orig_en; + } + + nv_error(priv, "trapped %s at 0x%02x%04x%04x on channel 0x%08x [%s] ", (trap[5] & 0x00000100) ? "read" : "write", - trap[5] & 0xff, trap[4] & 0xffff, trap[3] & 0xffff, chan); + trap[5] & 0xff, trap[4] & 0xffff, trap[3] & 0xffff, chan, + nouveau_client_name(engctx)); + + nouveau_engctx_put(engctx); - en = nouveau_enum_find(vm_engine, st0); if (en) - printk("%s/", en->name); + pr_cont("%s/", en->name); else - printk("%02x/", st0); + pr_cont("%02x/", st0); cl = nouveau_enum_find(vm_client, st2); if (cl) - printk("%s/", cl->name); + pr_cont("%s/", cl->name); else - printk("%02x/", st2); + pr_cont("%02x/", st2); if (cl && cl->data) cl = nouveau_enum_find(cl->data, st3); else if (en && en->data) cl = nouveau_enum_find(en->data, st3); else cl = NULL; if (cl) - printk("%s", cl->name); + pr_cont("%s", cl->name); else - printk("%02x", st3); + pr_cont("%02x", st3); - printk(" reason: "); + pr_cont(" reason: "); en = nouveau_enum_find(vm_fault, st1); if (en) - printk("%s\n", en->name); + pr_cont("%s\n", en->name); else - printk("0x%08x\n", st1); + pr_cont("0x%08x\n", st1); } static int diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c index 9fb0f9b92d49..d422acc9af15 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/base.c @@ -102,135 +102,19 @@ nouveau_gpio_get(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line) return ret; } -static int -nouveau_gpio_irq(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, bool on) -{ - struct dcb_gpio_func func; - int ret; - - ret = nouveau_gpio_find(gpio, idx, tag, line, &func); - if (ret == 0) { - if (idx == 0 && gpio->irq_enable) - gpio->irq_enable(gpio, func.line, on); - else - ret = -ENODEV; - } - - return ret; -} - -struct gpio_isr { - struct nouveau_gpio *gpio; - struct list_head head; - struct work_struct work; - int idx; - struct dcb_gpio_func func; - void (*handler)(void *, int); - void *data; - bool inhibit; -}; - -static void -nouveau_gpio_isr_bh(struct work_struct *work) -{ - struct gpio_isr *isr = container_of(work, struct gpio_isr, work); - struct nouveau_gpio *gpio = isr->gpio; - unsigned long flags; - int state; - - state = nouveau_gpio_get(gpio, isr->idx, isr->func.func, - isr->func.line); - if (state >= 0) - isr->handler(isr->data, state); - - spin_lock_irqsave(&gpio->lock, flags); - isr->inhibit = false; - spin_unlock_irqrestore(&gpio->lock, flags); -} - -static void -nouveau_gpio_isr_run(struct nouveau_gpio *gpio, int idx, u32 line_mask) -{ - struct gpio_isr *isr; - - if (idx != 0) - return; - - spin_lock(&gpio->lock); - list_for_each_entry(isr, &gpio->isr, head) { - if (line_mask & (1 << isr->func.line)) { - if (isr->inhibit) - continue; - isr->inhibit = true; - schedule_work(&isr->work); - } - } - spin_unlock(&gpio->lock); -} - -static int -nouveau_gpio_isr_add(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, - void (*handler)(void *, int), void *data) -{ - struct gpio_isr *isr; - unsigned long flags; - int ret; - - isr = kzalloc(sizeof(*isr), GFP_KERNEL); - if (!isr) - return -ENOMEM; - - ret = nouveau_gpio_find(gpio, idx, tag, line, &isr->func); - if (ret) { - kfree(isr); - return ret; - } - - INIT_WORK(&isr->work, nouveau_gpio_isr_bh); - isr->gpio = gpio; - isr->handler = handler; - isr->data = data; - isr->idx = idx; - - spin_lock_irqsave(&gpio->lock, flags); - list_add(&isr->head, &gpio->isr); - spin_unlock_irqrestore(&gpio->lock, flags); - return 0; -} - -static void -nouveau_gpio_isr_del(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, - void (*handler)(void *, int), void *data) +void +_nouveau_gpio_dtor(struct nouveau_object *object) { - struct gpio_isr *isr, *tmp; - struct dcb_gpio_func func; - unsigned long flags; - LIST_HEAD(tofree); - int ret; - - ret = nouveau_gpio_find(gpio, idx, tag, line, &func); - if (ret == 0) { - spin_lock_irqsave(&gpio->lock, flags); - list_for_each_entry_safe(isr, tmp, &gpio->isr, head) { - if (memcmp(&isr->func, &func, sizeof(func)) || - isr->idx != idx || - isr->handler != handler || isr->data != data) - continue; - list_move_tail(&isr->head, &tofree); - } - spin_unlock_irqrestore(&gpio->lock, flags); - - list_for_each_entry_safe(isr, tmp, &tofree, head) { - flush_work(&isr->work); - kfree(isr); - } - } + struct nouveau_gpio *gpio = (void *)object; + nouveau_event_destroy(&gpio->events); + nouveau_subdev_destroy(&gpio->base); } int nouveau_gpio_create_(struct nouveau_object *parent, struct nouveau_object *engine, - struct nouveau_oclass *oclass, int length, void **pobject) + struct nouveau_oclass *oclass, int lines, + int length, void **pobject) { struct nouveau_gpio *gpio; int ret; @@ -241,15 +125,13 @@ nouveau_gpio_create_(struct nouveau_object *parent, if (ret) return ret; + ret = nouveau_event_create(lines, &gpio->events); + if (ret) + return ret; + gpio->find = nouveau_gpio_find; gpio->set = nouveau_gpio_set; gpio->get = nouveau_gpio_get; - gpio->irq = nouveau_gpio_irq; - gpio->isr_run = nouveau_gpio_isr_run; - gpio->isr_add = nouveau_gpio_isr_add; - gpio->isr_del = nouveau_gpio_isr_del; - INIT_LIST_HEAD(&gpio->isr); - spin_lock_init(&gpio->lock); return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/nv10.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/nv10.c index 168d16a9a8e9..76d5d5465ddd 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/gpio/nv10.c +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/nv10.c @@ -24,7 +24,7 @@ * */ -#include <subdev/gpio.h> +#include "priv.h" struct nv10_gpio_priv { struct nouveau_gpio base; @@ -83,27 +83,36 @@ nv10_gpio_drive(struct nouveau_gpio *gpio, int line, int dir, int out) } static void -nv10_gpio_irq_enable(struct nouveau_gpio *gpio, int line, bool on) -{ - u32 mask = 0x00010001 << line; - - nv_wr32(gpio, 0x001104, mask); - nv_mask(gpio, 0x001144, mask, on ? mask : 0); -} - -static void nv10_gpio_intr(struct nouveau_subdev *subdev) { struct nv10_gpio_priv *priv = (void *)subdev; u32 intr = nv_rd32(priv, 0x001104); u32 hi = (intr & 0x0000ffff) >> 0; u32 lo = (intr & 0xffff0000) >> 16; + int i; - priv->base.isr_run(&priv->base, 0, hi | lo); + for (i = 0; (hi | lo) && i < 32; i++) { + if ((hi | lo) & (1 << i)) + nouveau_event_trigger(priv->base.events, i); + } nv_wr32(priv, 0x001104, intr); } +static void +nv10_gpio_intr_enable(struct nouveau_event *event, int line) +{ + nv_wr32(event->priv, 0x001104, 0x00010001 << line); + nv_mask(event->priv, 0x001144, 0x00010001 << line, 0x00010001 << line); +} + +static void +nv10_gpio_intr_disable(struct nouveau_event *event, int line) +{ + nv_wr32(event->priv, 0x001104, 0x00010001 << line); + nv_mask(event->priv, 0x001144, 0x00010001 << line, 0x00000000); +} + static int nv10_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -112,14 +121,16 @@ nv10_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv10_gpio_priv *priv; int ret; - ret = nouveau_gpio_create(parent, engine, oclass, &priv); + ret = nouveau_gpio_create(parent, engine, oclass, 16, &priv); *pobject = nv_object(priv); if (ret) return ret; priv->base.drive = nv10_gpio_drive; priv->base.sense = nv10_gpio_sense; - priv->base.irq_enable = nv10_gpio_irq_enable; + priv->base.events->priv = priv; + priv->base.events->enable = nv10_gpio_intr_enable; + priv->base.events->disable = nv10_gpio_intr_disable; nv_subdev(priv)->intr = nv10_gpio_intr; return 0; } @@ -141,8 +152,6 @@ nv10_gpio_init(struct nouveau_object *object) if (ret) return ret; - nv_wr32(priv, 0x001140, 0x00000000); - nv_wr32(priv, 0x001100, 0xffffffff); nv_wr32(priv, 0x001144, 0x00000000); nv_wr32(priv, 0x001104, 0xffffffff); return 0; @@ -152,7 +161,6 @@ static int nv10_gpio_fini(struct nouveau_object *object, bool suspend) { struct nv10_gpio_priv *priv = (void *)object; - nv_wr32(priv, 0x001140, 0x00000000); nv_wr32(priv, 0x001144, 0x00000000); return nouveau_gpio_fini(&priv->base, suspend); } diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/nv50.c index bf13a1200f26..bf489dcf46e2 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/gpio/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/nv50.c @@ -22,7 +22,7 @@ * Authors: Ben Skeggs */ -#include <subdev/gpio.h> +#include "priv.h" struct nv50_gpio_priv { struct nouveau_gpio base; @@ -95,21 +95,12 @@ nv50_gpio_sense(struct nouveau_gpio *gpio, int line) } void -nv50_gpio_irq_enable(struct nouveau_gpio *gpio, int line, bool on) -{ - u32 reg = line < 16 ? 0xe050 : 0xe070; - u32 mask = 0x00010001 << (line & 0xf); - - nv_wr32(gpio, reg + 4, mask); - nv_mask(gpio, reg + 0, mask, on ? mask : 0); -} - -void nv50_gpio_intr(struct nouveau_subdev *subdev) { struct nv50_gpio_priv *priv = (void *)subdev; u32 intr0, intr1 = 0; u32 hi, lo; + int i; intr0 = nv_rd32(priv, 0xe054) & nv_rd32(priv, 0xe050); if (nv_device(priv)->chipset >= 0x90) @@ -117,13 +108,35 @@ nv50_gpio_intr(struct nouveau_subdev *subdev) hi = (intr0 & 0x0000ffff) | (intr1 << 16); lo = (intr0 >> 16) | (intr1 & 0xffff0000); - priv->base.isr_run(&priv->base, 0, hi | lo); + + for (i = 0; (hi | lo) && i < 32; i++) { + if ((hi | lo) & (1 << i)) + nouveau_event_trigger(priv->base.events, i); + } nv_wr32(priv, 0xe054, intr0); if (nv_device(priv)->chipset >= 0x90) nv_wr32(priv, 0xe074, intr1); } +void +nv50_gpio_intr_enable(struct nouveau_event *event, int line) +{ + const u32 addr = line < 16 ? 0xe050 : 0xe070; + const u32 mask = 0x00010001 << (line & 0xf); + nv_wr32(event->priv, addr + 0x04, mask); + nv_mask(event->priv, addr + 0x00, mask, mask); +} + +void +nv50_gpio_intr_disable(struct nouveau_event *event, int line) +{ + const u32 addr = line < 16 ? 0xe050 : 0xe070; + const u32 mask = 0x00010001 << (line & 0xf); + nv_wr32(event->priv, addr + 0x04, mask); + nv_mask(event->priv, addr + 0x00, mask, 0x00000000); +} + static int nv50_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nouveau_oclass *oclass, void *data, u32 size, @@ -132,7 +145,9 @@ nv50_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nv50_gpio_priv *priv; int ret; - ret = nouveau_gpio_create(parent, engine, oclass, &priv); + ret = nouveau_gpio_create(parent, engine, oclass, + nv_device(parent)->chipset >= 0x90 ? 32 : 16, + &priv); *pobject = nv_object(priv); if (ret) return ret; @@ -140,7 +155,9 @@ nv50_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, priv->base.reset = nv50_gpio_reset; priv->base.drive = nv50_gpio_drive; priv->base.sense = nv50_gpio_sense; - priv->base.irq_enable = nv50_gpio_irq_enable; + priv->base.events->priv = priv; + priv->base.events->enable = nv50_gpio_intr_enable; + priv->base.events->disable = nv50_gpio_intr_disable; nv_subdev(priv)->intr = nv50_gpio_intr; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/nvd0.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/nvd0.c index 83e8b8f16e6a..010431e3acec 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/gpio/nvd0.c +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/nvd0.c @@ -22,13 +22,13 @@ * Authors: Ben Skeggs */ -#include <subdev/gpio.h> +#include "priv.h" struct nvd0_gpio_priv { struct nouveau_gpio base; }; -static void +void nvd0_gpio_reset(struct nouveau_gpio *gpio, u8 match) { struct nouveau_bios *bios = nouveau_bios(gpio); @@ -57,7 +57,7 @@ nvd0_gpio_reset(struct nouveau_gpio *gpio, u8 match) } } -static int +int nvd0_gpio_drive(struct nouveau_gpio *gpio, int line, int dir, int out) { u32 data = ((dir ^ 1) << 13) | (out << 12); @@ -66,7 +66,7 @@ nvd0_gpio_drive(struct nouveau_gpio *gpio, int line, int dir, int out) return 0; } -static int +int nvd0_gpio_sense(struct nouveau_gpio *gpio, int line) { return !!(nv_rd32(gpio, 0x00d610 + (line * 4)) & 0x00004000); @@ -80,7 +80,7 @@ nvd0_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, struct nvd0_gpio_priv *priv; int ret; - ret = nouveau_gpio_create(parent, engine, oclass, &priv); + ret = nouveau_gpio_create(parent, engine, oclass, 32, &priv); *pobject = nv_object(priv); if (ret) return ret; @@ -88,7 +88,9 @@ nvd0_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, priv->base.reset = nvd0_gpio_reset; priv->base.drive = nvd0_gpio_drive; priv->base.sense = nvd0_gpio_sense; - priv->base.irq_enable = nv50_gpio_irq_enable; + priv->base.events->priv = priv; + priv->base.events->enable = nv50_gpio_intr_enable; + priv->base.events->disable = nv50_gpio_intr_disable; nv_subdev(priv)->intr = nv50_gpio_intr; return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/nve0.c b/drivers/gpu/drm/nouveau/core/subdev/gpio/nve0.c new file mode 100644 index 000000000000..16b8c5bf5efa --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/nve0.c @@ -0,0 +1,131 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "priv.h" + +struct nve0_gpio_priv { + struct nouveau_gpio base; +}; + +void +nve0_gpio_intr(struct nouveau_subdev *subdev) +{ + struct nve0_gpio_priv *priv = (void *)subdev; + u32 intr0 = nv_rd32(priv, 0xdc00) & nv_rd32(priv, 0xdc08); + u32 intr1 = nv_rd32(priv, 0xdc80) & nv_rd32(priv, 0xdc88); + u32 hi = (intr0 & 0x0000ffff) | (intr1 << 16); + u32 lo = (intr0 >> 16) | (intr1 & 0xffff0000); + int i; + + for (i = 0; (hi | lo) && i < 32; i++) { + if ((hi | lo) & (1 << i)) + nouveau_event_trigger(priv->base.events, i); + } + + nv_wr32(priv, 0xdc00, intr0); + nv_wr32(priv, 0xdc88, intr1); +} + +void +nve0_gpio_intr_enable(struct nouveau_event *event, int line) +{ + const u32 addr = line < 16 ? 0xdc00 : 0xdc80; + const u32 mask = 0x00010001 << (line & 0xf); + nv_wr32(event->priv, addr + 0x08, mask); + nv_mask(event->priv, addr + 0x00, mask, mask); +} + +void +nve0_gpio_intr_disable(struct nouveau_event *event, int line) +{ + const u32 addr = line < 16 ? 0xdc00 : 0xdc80; + const u32 mask = 0x00010001 << (line & 0xf); + nv_wr32(event->priv, addr + 0x08, mask); + nv_mask(event->priv, addr + 0x00, mask, 0x00000000); +} + +int +nve0_gpio_fini(struct nouveau_object *object, bool suspend) +{ + struct nve0_gpio_priv *priv = (void *)object; + nv_wr32(priv, 0xdc08, 0x00000000); + nv_wr32(priv, 0xdc88, 0x00000000); + return nouveau_gpio_fini(&priv->base, suspend); +} + +int +nve0_gpio_init(struct nouveau_object *object) +{ + struct nve0_gpio_priv *priv = (void *)object; + int ret; + + ret = nouveau_gpio_init(&priv->base); + if (ret) + return ret; + + nv_wr32(priv, 0xdc00, 0xffffffff); + nv_wr32(priv, 0xdc80, 0xffffffff); + return 0; +} + +void +nve0_gpio_dtor(struct nouveau_object *object) +{ + struct nve0_gpio_priv *priv = (void *)object; + nouveau_gpio_destroy(&priv->base); +} + +static int +nve0_gpio_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nve0_gpio_priv *priv; + int ret; + + ret = nouveau_gpio_create(parent, engine, oclass, 32, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.reset = nvd0_gpio_reset; + priv->base.drive = nvd0_gpio_drive; + priv->base.sense = nvd0_gpio_sense; + priv->base.events->priv = priv; + priv->base.events->enable = nve0_gpio_intr_enable; + priv->base.events->disable = nve0_gpio_intr_disable; + nv_subdev(priv)->intr = nve0_gpio_intr; + return 0; +} + +struct nouveau_oclass +nve0_gpio_oclass = { + .handle = NV_SUBDEV(GPIO, 0xe0), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nve0_gpio_ctor, + .dtor = nv50_gpio_dtor, + .init = nve0_gpio_init, + .fini = nve0_gpio_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/gpio/priv.h b/drivers/gpu/drm/nouveau/core/subdev/gpio/priv.h new file mode 100644 index 000000000000..2ee1c895c782 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/gpio/priv.h @@ -0,0 +1,17 @@ +#ifndef __NVKM_GPIO_H__ +#define __NVKM_GPIO_H__ + +#include <subdev/gpio.h> + +void nv50_gpio_dtor(struct nouveau_object *); +int nv50_gpio_init(struct nouveau_object *); +int nv50_gpio_fini(struct nouveau_object *, bool); +void nv50_gpio_intr(struct nouveau_subdev *); +void nv50_gpio_intr_enable(struct nouveau_event *, int line); +void nv50_gpio_intr_disable(struct nouveau_event *, int line); + +void nvd0_gpio_reset(struct nouveau_gpio *, u8); +int nvd0_gpio_drive(struct nouveau_gpio *, int, int, int); +int nvd0_gpio_sense(struct nouveau_gpio *, int); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/anx9805.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/anx9805.c new file mode 100644 index 000000000000..dec94e9d776a --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/anx9805.c @@ -0,0 +1,279 @@ +/* + * Copyright 2013 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs <bskeggs@redhat.com> + */ + +#include <subdev/i2c.h> + +struct anx9805_i2c_port { + struct nouveau_i2c_port base; + u32 addr; + u32 ctrl; +}; + +static int +anx9805_train(struct nouveau_i2c_port *port, int link_nr, int link_bw, bool enh) +{ + struct anx9805_i2c_port *chan = (void *)port; + struct nouveau_i2c_port *mast = (void *)nv_object(chan)->parent; + u8 tmp, i; + + nv_wri2cr(mast, chan->addr, 0xa0, link_bw); + nv_wri2cr(mast, chan->addr, 0xa1, link_nr | (enh ? 0x80 : 0x00)); + nv_wri2cr(mast, chan->addr, 0xa2, 0x01); + nv_wri2cr(mast, chan->addr, 0xa8, 0x01); + + i = 0; + while ((tmp = nv_rdi2cr(mast, chan->addr, 0xa8)) & 0x01) { + mdelay(5); + if (i++ == 100) { + nv_error(port, "link training timed out\n"); + return -ETIMEDOUT; + } + } + + if (tmp & 0x70) { + nv_error(port, "link training failed: 0x%02x\n", tmp); + return -EIO; + } + + return 1; +} + +static int +anx9805_aux(struct nouveau_i2c_port *port, u8 type, u32 addr, u8 *data, u8 size) +{ + struct anx9805_i2c_port *chan = (void *)port; + struct nouveau_i2c_port *mast = (void *)nv_object(chan)->parent; + int i, ret = -ETIMEDOUT; + u8 tmp; + + tmp = nv_rdi2cr(mast, chan->ctrl, 0x07) & ~0x04; + nv_wri2cr(mast, chan->ctrl, 0x07, tmp | 0x04); + nv_wri2cr(mast, chan->ctrl, 0x07, tmp); + nv_wri2cr(mast, chan->ctrl, 0xf7, 0x01); + + nv_wri2cr(mast, chan->addr, 0xe4, 0x80); + for (i = 0; !(type & 1) && i < size; i++) + nv_wri2cr(mast, chan->addr, 0xf0 + i, data[i]); + nv_wri2cr(mast, chan->addr, 0xe5, ((size - 1) << 4) | type); + nv_wri2cr(mast, chan->addr, 0xe6, (addr & 0x000ff) >> 0); + nv_wri2cr(mast, chan->addr, 0xe7, (addr & 0x0ff00) >> 8); + nv_wri2cr(mast, chan->addr, 0xe8, (addr & 0xf0000) >> 16); + nv_wri2cr(mast, chan->addr, 0xe9, 0x01); + + i = 0; + while ((tmp = nv_rdi2cr(mast, chan->addr, 0xe9)) & 0x01) { + mdelay(5); + if (i++ == 32) + goto done; + } + + if ((tmp = nv_rdi2cr(mast, chan->ctrl, 0xf7)) & 0x01) { + ret = -EIO; + goto done; + } + + for (i = 0; (type & 1) && i < size; i++) + data[i] = nv_rdi2cr(mast, chan->addr, 0xf0 + i); + ret = 0; +done: + nv_wri2cr(mast, chan->ctrl, 0xf7, 0x01); + return ret; +} + +static const struct nouveau_i2c_func +anx9805_aux_func = { + .aux = anx9805_aux, + .lnk_ctl = anx9805_train, +}; + +static int +anx9805_aux_chan_ctor(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct nouveau_i2c_port *mast = (void *)parent; + struct anx9805_i2c_port *chan; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_aux_algo, &chan); + *pobject = nv_object(chan); + if (ret) + return ret; + + switch ((oclass->handle & 0xff00) >> 8) { + case 0x0d: + chan->addr = 0x38; + chan->ctrl = 0x39; + break; + case 0x0e: + chan->addr = 0x3c; + chan->ctrl = 0x3b; + break; + default: + BUG_ON(1); + } + + if (mast->adapter.algo == &i2c_bit_algo) { + struct i2c_algo_bit_data *algo = mast->adapter.algo_data; + algo->udelay = max(algo->udelay, 40); + } + + chan->base.func = &anx9805_aux_func; + return 0; +} + +static struct nouveau_ofuncs +anx9805_aux_ofuncs = { + .ctor = anx9805_aux_chan_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, +}; + +static int +anx9805_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct anx9805_i2c_port *port = adap->algo_data; + struct nouveau_i2c_port *mast = (void *)nv_object(port)->parent; + struct i2c_msg *msg = msgs; + int ret = -ETIMEDOUT; + int i, j, cnt = num; + u8 seg = 0x00, off = 0x00, tmp; + + tmp = nv_rdi2cr(mast, port->ctrl, 0x07) & ~0x10; + nv_wri2cr(mast, port->ctrl, 0x07, tmp | 0x10); + nv_wri2cr(mast, port->ctrl, 0x07, tmp); + nv_wri2cr(mast, port->addr, 0x43, 0x05); + mdelay(5); + + while (cnt--) { + if ( (msg->flags & I2C_M_RD) && msg->addr == 0x50) { + nv_wri2cr(mast, port->addr, 0x40, msg->addr << 1); + nv_wri2cr(mast, port->addr, 0x41, seg); + nv_wri2cr(mast, port->addr, 0x42, off); + nv_wri2cr(mast, port->addr, 0x44, msg->len); + nv_wri2cr(mast, port->addr, 0x45, 0x00); + nv_wri2cr(mast, port->addr, 0x43, 0x01); + for (i = 0; i < msg->len; i++) { + j = 0; + while (nv_rdi2cr(mast, port->addr, 0x46) & 0x10) { + mdelay(5); + if (j++ == 32) + goto done; + } + msg->buf[i] = nv_rdi2cr(mast, port->addr, 0x47); + } + } else + if (!(msg->flags & I2C_M_RD)) { + if (msg->addr == 0x50 && msg->len == 0x01) { + off = msg->buf[0]; + } else + if (msg->addr == 0x30 && msg->len == 0x01) { + seg = msg->buf[0]; + } else + goto done; + } else { + goto done; + } + msg++; + } + + ret = num; +done: + nv_wri2cr(mast, port->addr, 0x43, 0x00); + return ret; +} + +static u32 +anx9805_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm +anx9805_i2c_algo = { + .master_xfer = anx9805_xfer, + .functionality = anx9805_func +}; + +static const struct nouveau_i2c_func +anx9805_i2c_func = { +}; + +static int +anx9805_ddc_port_ctor(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct nouveau_i2c_port *mast = (void *)parent; + struct anx9805_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &anx9805_i2c_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + switch ((oclass->handle & 0xff00) >> 8) { + case 0x0d: + port->addr = 0x3d; + port->ctrl = 0x39; + break; + case 0x0e: + port->addr = 0x3f; + port->ctrl = 0x3b; + break; + default: + BUG_ON(1); + } + + if (mast->adapter.algo == &i2c_bit_algo) { + struct i2c_algo_bit_data *algo = mast->adapter.algo_data; + algo->udelay = max(algo->udelay, 40); + } + + port->base.func = &anx9805_i2c_func; + return 0; +} + +static struct nouveau_ofuncs +anx9805_ddc_ofuncs = { + .ctor = anx9805_ddc_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, +}; + +struct nouveau_oclass +nouveau_anx9805_sclass[] = { + { .handle = NV_I2C_TYPE_EXTDDC(0x0d), .ofuncs = &anx9805_ddc_ofuncs }, + { .handle = NV_I2C_TYPE_EXTAUX(0x0d), .ofuncs = &anx9805_aux_ofuncs }, + { .handle = NV_I2C_TYPE_EXTDDC(0x0e), .ofuncs = &anx9805_ddc_ofuncs }, + { .handle = NV_I2C_TYPE_EXTAUX(0x0e), .ofuncs = &anx9805_aux_ofuncs }, + {} +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c index dc27e794a851..5de074ad170b 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/aux.c @@ -24,151 +24,40 @@ #include <subdev/i2c.h> -/****************************************************************************** - * aux channel util functions - *****************************************************************************/ -#define AUX_DBG(fmt, args...) nv_debug(aux, "AUXCH(%d): " fmt, ch, ##args) -#define AUX_ERR(fmt, args...) nv_error(aux, "AUXCH(%d): " fmt, ch, ##args) - -static void -auxch_fini(struct nouveau_i2c *aux, int ch) -{ - nv_mask(aux, 0x00e4e4 + (ch * 0x50), 0x00310000, 0x00000000); -} - -static int -auxch_init(struct nouveau_i2c *aux, int ch) -{ - const u32 unksel = 1; /* nfi which to use, or if it matters.. */ - const u32 ureq = unksel ? 0x00100000 : 0x00200000; - const u32 urep = unksel ? 0x01000000 : 0x02000000; - u32 ctrl, timeout; - - /* wait up to 1ms for any previous transaction to be done... */ - timeout = 1000; - do { - ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); - udelay(1); - if (!timeout--) { - AUX_ERR("begin idle timeout 0x%08x\n", ctrl); - return -EBUSY; - } - } while (ctrl & 0x03010000); - - /* set some magic, and wait up to 1ms for it to appear */ - nv_mask(aux, 0x00e4e4 + (ch * 0x50), 0x00300000, ureq); - timeout = 1000; - do { - ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); - udelay(1); - if (!timeout--) { - AUX_ERR("magic wait 0x%08x\n", ctrl); - auxch_fini(aux, ch); - return -EBUSY; - } - } while ((ctrl & 0x03000000) != urep); - - return 0; -} - -static int -auxch_tx(struct nouveau_i2c *aux, int ch, u8 type, u32 addr, u8 *data, u8 size) -{ - u32 ctrl, stat, timeout, retries; - u32 xbuf[4] = {}; - int ret, i; - - AUX_DBG("%d: 0x%08x %d\n", type, addr, size); - - ret = auxch_init(aux, ch); - if (ret) - goto out; - - stat = nv_rd32(aux, 0x00e4e8 + (ch * 0x50)); - if (!(stat & 0x10000000)) { - AUX_DBG("sink not detected\n"); - ret = -ENXIO; - goto out; - } - - if (!(type & 1)) { - memcpy(xbuf, data, size); - for (i = 0; i < 16; i += 4) { - AUX_DBG("wr 0x%08x\n", xbuf[i / 4]); - nv_wr32(aux, 0x00e4c0 + (ch * 0x50) + i, xbuf[i / 4]); - } - } - - ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); - ctrl &= ~0x0001f0ff; - ctrl |= type << 12; - ctrl |= size - 1; - nv_wr32(aux, 0x00e4e0 + (ch * 0x50), addr); - - /* retry transaction a number of times on failure... */ - ret = -EREMOTEIO; - for (retries = 0; retries < 32; retries++) { - /* reset, and delay a while if this is a retry */ - nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x80000000 | ctrl); - nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x00000000 | ctrl); - if (retries) - udelay(400); - - /* transaction request, wait up to 1ms for it to complete */ - nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x00010000 | ctrl); - - timeout = 1000; - do { - ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); - udelay(1); - if (!timeout--) { - AUX_ERR("tx req timeout 0x%08x\n", ctrl); - goto out; - } - } while (ctrl & 0x00010000); - - /* read status, and check if transaction completed ok */ - stat = nv_mask(aux, 0x00e4e8 + (ch * 0x50), 0, 0); - if (!(stat & 0x000f0f00)) { - ret = 0; - break; - } - - AUX_DBG("%02d 0x%08x 0x%08x\n", retries, ctrl, stat); - } - - if (type & 1) { - for (i = 0; i < 16; i += 4) { - xbuf[i / 4] = nv_rd32(aux, 0x00e4d0 + (ch * 0x50) + i); - AUX_DBG("rd 0x%08x\n", xbuf[i / 4]); - } - memcpy(data, xbuf, size); - } - -out: - auxch_fini(aux, ch); - return ret; -} - int -nv_rdaux(struct nouveau_i2c_port *auxch, u32 addr, u8 *data, u8 size) +nv_rdaux(struct nouveau_i2c_port *port, u32 addr, u8 *data, u8 size) { - return auxch_tx(auxch->i2c, auxch->drive, 9, addr, data, size); + if (port->func->aux) { + if (port->func->acquire) + port->func->acquire(port); + return port->func->aux(port, 9, addr, data, size); + } + return -ENODEV; } int -nv_wraux(struct nouveau_i2c_port *auxch, u32 addr, u8 *data, u8 size) +nv_wraux(struct nouveau_i2c_port *port, u32 addr, u8 *data, u8 size) { - return auxch_tx(auxch->i2c, auxch->drive, 8, addr, data, size); + if (port->func->aux) { + if (port->func->acquire) + port->func->acquire(port); + return port->func->aux(port, 8, addr, data, size); + } + return -ENODEV; } static int aux_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { - struct nouveau_i2c_port *auxch = (struct nouveau_i2c_port *)adap; + struct nouveau_i2c_port *port = adap->algo_data; struct i2c_msg *msg = msgs; int ret, mcnt = num; + if (!port->func->aux) + return -ENODEV; + if ( port->func->acquire) + port->func->acquire(port); + while (mcnt--) { u8 remaining = msg->len; u8 *ptr = msg->buf; @@ -185,8 +74,7 @@ aux_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) if (mcnt || remaining > 16) cmd |= 4; /* MOT */ - ret = auxch_tx(auxch->i2c, auxch->drive, cmd, - msg->addr, ptr, cnt); + ret = port->func->aux(port, cmd, msg->addr, ptr, cnt); if (ret < 0) return ret; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c index dbfc2abf0cfe..a114a0ed7e98 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/base.c @@ -1,5 +1,5 @@ /* - * Copyright 2012 Red Hat Inc. + * Copyright 2013 Red Hat Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -22,64 +22,136 @@ * Authors: Ben Skeggs */ -#include "core/option.h" +#include <core/option.h> -#include "subdev/i2c.h" -#include "subdev/vga.h" +#include <subdev/bios.h> +#include <subdev/bios/dcb.h> +#include <subdev/bios/i2c.h> +#include <subdev/i2c.h> +#include <subdev/vga.h> -int -nv_rdi2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg) +/****************************************************************************** + * interface to linux i2c bit-banging algorithm + *****************************************************************************/ + +#ifdef CONFIG_NOUVEAU_I2C_INTERNAL_DEFAULT +#define CSTMSEL true +#else +#define CSTMSEL false +#endif + +static int +nouveau_i2c_pre_xfer(struct i2c_adapter *adap) { - u8 val; - struct i2c_msg msgs[] = { - { .addr = addr, .flags = 0, .len = 1, .buf = ® }, - { .addr = addr, .flags = I2C_M_RD, .len = 1, .buf = &val }, - }; + struct i2c_algo_bit_data *bit = adap->algo_data; + struct nouveau_i2c_port *port = bit->data; + if (port->func->acquire) + port->func->acquire(port); + return 0; +} - int ret = i2c_transfer(&port->adapter, msgs, 2); - if (ret != 2) - return -EIO; +static void +nouveau_i2c_setscl(void *data, int state) +{ + struct nouveau_i2c_port *port = data; + port->func->drive_scl(port, state); +} - return val; +static void +nouveau_i2c_setsda(void *data, int state) +{ + struct nouveau_i2c_port *port = data; + port->func->drive_sda(port, state); } -int -nv_wri2cr(struct nouveau_i2c_port *port, u8 addr, u8 reg, u8 val) +static int +nouveau_i2c_getscl(void *data) { - struct i2c_msg msgs[] = { - { .addr = addr, .flags = 0, .len = 1, .buf = ® }, - { .addr = addr, .flags = 0, .len = 1, .buf = &val }, - }; + struct nouveau_i2c_port *port = data; + return port->func->sense_scl(port); +} - int ret = i2c_transfer(&port->adapter, msgs, 2); - if (ret != 2) - return -EIO; +static int +nouveau_i2c_getsda(void *data) +{ + struct nouveau_i2c_port *port = data; + return port->func->sense_sda(port); +} - return 0; +/****************************************************************************** + * base i2c "port" class implementation + *****************************************************************************/ + +void +_nouveau_i2c_port_dtor(struct nouveau_object *object) +{ + struct nouveau_i2c_port *port = (void *)object; + i2c_del_adapter(&port->adapter); + nouveau_object_destroy(&port->base); } -bool -nv_probe_i2c(struct nouveau_i2c_port *port, u8 addr) +int +nouveau_i2c_port_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, u8 index, + const struct i2c_algorithm *algo, + int size, void **pobject) { - u8 buf[] = { 0 }; - struct i2c_msg msgs[] = { - { - .addr = addr, - .flags = 0, - .len = 1, - .buf = buf, - }, - { - .addr = addr, - .flags = I2C_M_RD, - .len = 1, - .buf = buf, - } - }; + struct nouveau_device *device = nv_device(parent); + struct nouveau_i2c *i2c = (void *)engine; + struct nouveau_i2c_port *port; + int ret; - return i2c_transfer(&port->adapter, msgs, 2) == 2; + ret = nouveau_object_create_(parent, engine, oclass, 0, size, pobject); + port = *pobject; + if (ret) + return ret; + + snprintf(port->adapter.name, sizeof(port->adapter.name), + "nouveau-%s-%d", device->name, index); + port->adapter.owner = THIS_MODULE; + port->adapter.dev.parent = &device->pdev->dev; + port->index = index; + i2c_set_adapdata(&port->adapter, i2c); + + if ( algo == &nouveau_i2c_bit_algo && + !nouveau_boolopt(device->cfgopt, "NvI2C", CSTMSEL)) { + struct i2c_algo_bit_data *bit; + + bit = kzalloc(sizeof(*bit), GFP_KERNEL); + if (!bit) + return -ENOMEM; + + bit->udelay = 10; + bit->timeout = usecs_to_jiffies(2200); + bit->data = port; + bit->pre_xfer = nouveau_i2c_pre_xfer; + bit->setsda = nouveau_i2c_setsda; + bit->setscl = nouveau_i2c_setscl; + bit->getsda = nouveau_i2c_getsda; + bit->getscl = nouveau_i2c_getscl; + + port->adapter.algo_data = bit; + ret = i2c_bit_add_bus(&port->adapter); + } else { + port->adapter.algo_data = port; + port->adapter.algo = algo; + ret = i2c_add_adapter(&port->adapter); + } + + /* drop port's i2c subdev refcount, i2c handles this itself */ + if (ret == 0) { + list_add_tail(&port->head, &i2c->ports); + atomic_dec(&engine->refcount); + } + + return ret; } +/****************************************************************************** + * base i2c subdev class implementation + *****************************************************************************/ + static struct nouveau_i2c_port * nouveau_i2c_find(struct nouveau_i2c *i2c, u8 index) { @@ -103,29 +175,23 @@ nouveau_i2c_find(struct nouveau_i2c *i2c, u8 index) list_for_each_entry(port, &i2c->ports, head) { if (port->index == index) - break; + return port; } - if (&port->head == &i2c->ports) - return NULL; + return NULL; +} - if (nv_device(i2c)->card_type >= NV_50 && (port->dcb & 0x00000100)) { - u32 reg = 0x00e500, val; - if (port->type == 6) { - reg += port->drive * 0x50; - val = 0x2002; - } else { - reg += ((port->dcb & 0x1e00) >> 9) * 0x50; - val = 0xe001; - } +static struct nouveau_i2c_port * +nouveau_i2c_find_type(struct nouveau_i2c *i2c, u16 type) +{ + struct nouveau_i2c_port *port; - /* nfi, but neither auxch or i2c work if it's 1 */ - nv_mask(i2c, reg + 0x0c, 0x00000001, 0x00000000); - /* nfi, but switches auxch vs normal i2c */ - nv_mask(i2c, reg + 0x00, 0x0000f003, val); + list_for_each_entry(port, &i2c->ports, head) { + if (nv_hclass(port) == type) + return port; } - return port; + return NULL; } static int @@ -155,109 +221,86 @@ nouveau_i2c_identify(struct nouveau_i2c *i2c, int index, const char *what, return -ENODEV; } -void -nouveau_i2c_drive_scl(void *data, int state) +int +_nouveau_i2c_fini(struct nouveau_object *object, bool suspend) { - struct nouveau_i2c_port *port = data; + struct nouveau_i2c *i2c = (void *)object; + struct nouveau_i2c_port *port; + int ret; - if (port->type == DCB_I2C_NV04_BIT) { - u8 val = nv_rdvgac(port->i2c, 0, port->drive); - if (state) val |= 0x20; - else val &= 0xdf; - nv_wrvgac(port->i2c, 0, port->drive, val | 0x01); - } else - if (port->type == DCB_I2C_NV4E_BIT) { - nv_mask(port->i2c, port->drive, 0x2f, state ? 0x21 : 0x01); - } else - if (port->type == DCB_I2C_NVIO_BIT) { - if (state) port->state |= 0x01; - else port->state &= 0xfe; - nv_wr32(port->i2c, port->drive, 4 | port->state); + list_for_each_entry(port, &i2c->ports, head) { + ret = nv_ofuncs(port)->fini(nv_object(port), suspend); + if (ret && suspend) + goto fail; } -} - -void -nouveau_i2c_drive_sda(void *data, int state) -{ - struct nouveau_i2c_port *port = data; - if (port->type == DCB_I2C_NV04_BIT) { - u8 val = nv_rdvgac(port->i2c, 0, port->drive); - if (state) val |= 0x10; - else val &= 0xef; - nv_wrvgac(port->i2c, 0, port->drive, val | 0x01); - } else - if (port->type == DCB_I2C_NV4E_BIT) { - nv_mask(port->i2c, port->drive, 0x1f, state ? 0x11 : 0x01); - } else - if (port->type == DCB_I2C_NVIO_BIT) { - if (state) port->state |= 0x02; - else port->state &= 0xfd; - nv_wr32(port->i2c, port->drive, 4 | port->state); + return nouveau_subdev_fini(&i2c->base, suspend); +fail: + list_for_each_entry_continue_reverse(port, &i2c->ports, head) { + nv_ofuncs(port)->init(nv_object(port)); } + + return ret; } int -nouveau_i2c_sense_scl(void *data) +_nouveau_i2c_init(struct nouveau_object *object) { - struct nouveau_i2c_port *port = data; - struct nouveau_device *device = nv_device(port->i2c); - - if (port->type == DCB_I2C_NV04_BIT) { - return !!(nv_rdvgac(port->i2c, 0, port->sense) & 0x04); - } else - if (port->type == DCB_I2C_NV4E_BIT) { - return !!(nv_rd32(port->i2c, port->sense) & 0x00040000); - } else - if (port->type == DCB_I2C_NVIO_BIT) { - if (device->card_type < NV_D0) - return !!(nv_rd32(port->i2c, port->sense) & 0x01); - else - return !!(nv_rd32(port->i2c, port->sense) & 0x10); + struct nouveau_i2c *i2c = (void *)object; + struct nouveau_i2c_port *port; + int ret; + + ret = nouveau_subdev_init(&i2c->base); + if (ret == 0) { + list_for_each_entry(port, &i2c->ports, head) { + ret = nv_ofuncs(port)->init(nv_object(port)); + if (ret) + goto fail; + } } - return 0; + return ret; +fail: + list_for_each_entry_continue_reverse(port, &i2c->ports, head) { + nv_ofuncs(port)->fini(nv_object(port), false); + } + + return ret; } -int -nouveau_i2c_sense_sda(void *data) +void +_nouveau_i2c_dtor(struct nouveau_object *object) { - struct nouveau_i2c_port *port = data; - struct nouveau_device *device = nv_device(port->i2c); - - if (port->type == DCB_I2C_NV04_BIT) { - return !!(nv_rdvgac(port->i2c, 0, port->sense) & 0x08); - } else - if (port->type == DCB_I2C_NV4E_BIT) { - return !!(nv_rd32(port->i2c, port->sense) & 0x00080000); - } else - if (port->type == DCB_I2C_NVIO_BIT) { - if (device->card_type < NV_D0) - return !!(nv_rd32(port->i2c, port->sense) & 0x02); - else - return !!(nv_rd32(port->i2c, port->sense) & 0x20); + struct nouveau_i2c *i2c = (void *)object; + struct nouveau_i2c_port *port, *temp; + + list_for_each_entry_safe(port, temp, &i2c->ports, head) { + nouveau_object_ref(NULL, (struct nouveau_object **)&port); } - return 0; + nouveau_subdev_destroy(&i2c->base); } -static const u32 nv50_i2c_port[] = { - 0x00e138, 0x00e150, 0x00e168, 0x00e180, - 0x00e254, 0x00e274, 0x00e764, 0x00e780, - 0x00e79c, 0x00e7b8 +static struct nouveau_oclass * +nouveau_i2c_extdev_sclass[] = { + nouveau_anx9805_sclass, }; -static int -nouveau_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, - struct nouveau_oclass *oclass, void *data, u32 size, - struct nouveau_object **pobject) +int +nouveau_i2c_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, + struct nouveau_oclass *sclass, + int length, void **pobject) { - struct nouveau_device *device = nv_device(parent); struct nouveau_bios *bios = nouveau_bios(parent); - struct nouveau_i2c_port *port; struct nouveau_i2c *i2c; + struct nouveau_object *object; struct dcb_i2c_entry info; - int ret, i = -1; + int ret, i, j, index = -1; + struct dcb_output outp; + u8 ver, hdr; + u32 data; ret = nouveau_subdev_create(parent, engine, oclass, 0, "I2C", "i2c", &i2c); @@ -266,142 +309,60 @@ nouveau_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, return ret; i2c->find = nouveau_i2c_find; + i2c->find_type = nouveau_i2c_find_type; i2c->identify = nouveau_i2c_identify; INIT_LIST_HEAD(&i2c->ports); - while (!dcb_i2c_parse(bios, ++i, &info)) { + while (!dcb_i2c_parse(bios, ++index, &info)) { if (info.type == DCB_I2C_UNUSED) continue; - port = kzalloc(sizeof(*port), GFP_KERNEL); - if (!port) { - nv_error(i2c, "failed port memory alloc at %d\n", i); - break; - } - - port->type = info.type; - switch (port->type) { - case DCB_I2C_NV04_BIT: - port->drive = info.drive; - port->sense = info.sense; - break; - case DCB_I2C_NV4E_BIT: - port->drive = 0x600800 + info.drive; - port->sense = port->drive; - break; - case DCB_I2C_NVIO_BIT: - port->drive = info.drive & 0x0f; - if (device->card_type < NV_D0) { - if (port->drive >= ARRAY_SIZE(nv50_i2c_port)) - break; - port->drive = nv50_i2c_port[port->drive]; - port->sense = port->drive; - } else { - port->drive = 0x00d014 + (port->drive * 0x20); - port->sense = port->drive; + oclass = sclass; + do { + ret = -EINVAL; + if (oclass->handle == info.type) { + ret = nouveau_object_ctor(*pobject, *pobject, + oclass, &info, + index, &object); } + } while (ret && (++oclass)->handle); + } + + /* in addition to the busses specified in the i2c table, there + * may be ddc/aux channels hiding behind external tmds/dp/etc + * transmitters. + */ + index = ((index + 0x0f) / 0x10) * 0x10; + i = -1; + while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &outp))) { + if (!outp.location || !outp.extdev) + continue; + + switch (outp.type) { + case DCB_OUTPUT_TMDS: + info.type = NV_I2C_TYPE_EXTDDC(outp.extdev); break; - case DCB_I2C_NVIO_AUX: - port->drive = info.drive & 0x0f; - port->sense = port->drive; - port->adapter.algo = &nouveau_i2c_aux_algo; + case DCB_OUTPUT_DP: + info.type = NV_I2C_TYPE_EXTAUX(outp.extdev); break; default: - break; - } - - if (!port->adapter.algo && !port->drive) { - nv_error(i2c, "I2C%d: type %d index %x/%x unknown\n", - i, port->type, port->drive, port->sense); - kfree(port); continue; } - snprintf(port->adapter.name, sizeof(port->adapter.name), - "nouveau-%s-%d", device->name, i); - port->adapter.owner = THIS_MODULE; - port->adapter.dev.parent = &device->pdev->dev; - port->i2c = i2c; - port->index = i; - port->dcb = info.data; - i2c_set_adapdata(&port->adapter, i2c); - - if (port->adapter.algo != &nouveau_i2c_aux_algo) { - nouveau_i2c_drive_scl(port, 0); - nouveau_i2c_drive_sda(port, 1); - nouveau_i2c_drive_scl(port, 1); - -#ifdef CONFIG_NOUVEAU_I2C_INTERNAL_DEFAULT - if (nouveau_boolopt(device->cfgopt, "NvI2C", true)) { -#else - if (nouveau_boolopt(device->cfgopt, "NvI2C", false)) { -#endif - port->adapter.algo = &nouveau_i2c_bit_algo; - ret = i2c_add_adapter(&port->adapter); - } else { - port->adapter.algo_data = &port->bit; - port->bit.udelay = 10; - port->bit.timeout = usecs_to_jiffies(2200); - port->bit.data = port; - port->bit.setsda = nouveau_i2c_drive_sda; - port->bit.setscl = nouveau_i2c_drive_scl; - port->bit.getsda = nouveau_i2c_sense_sda; - port->bit.getscl = nouveau_i2c_sense_scl; - ret = i2c_bit_add_bus(&port->adapter); - } - } else { - port->adapter.algo = &nouveau_i2c_aux_algo; - ret = i2c_add_adapter(&port->adapter); - } - - if (ret) { - nv_error(i2c, "I2C%d: failed register: %d\n", i, ret); - kfree(port); - continue; + ret = -ENODEV; + j = -1; + while (ret && ++j < ARRAY_SIZE(nouveau_i2c_extdev_sclass)) { + parent = nv_object(i2c->find(i2c, outp.i2c_index)); + oclass = nouveau_i2c_extdev_sclass[j]; + do { + if (oclass->handle != info.type) + continue; + ret = nouveau_object_ctor(parent, *pobject, + oclass, NULL, + index++, &object); + } while (ret && (++oclass)->handle); } - - list_add_tail(&port->head, &i2c->ports); } return 0; } - -static void -nouveau_i2c_dtor(struct nouveau_object *object) -{ - struct nouveau_i2c *i2c = (void *)object; - struct nouveau_i2c_port *port, *temp; - - list_for_each_entry_safe(port, temp, &i2c->ports, head) { - i2c_del_adapter(&port->adapter); - list_del(&port->head); - kfree(port); - } - - nouveau_subdev_destroy(&i2c->base); -} - -static int -nouveau_i2c_init(struct nouveau_object *object) -{ - struct nouveau_i2c *i2c = (void *)object; - return nouveau_subdev_init(&i2c->base); -} - -static int -nouveau_i2c_fini(struct nouveau_object *object, bool suspend) -{ - struct nouveau_i2c *i2c = (void *)object; - return nouveau_subdev_fini(&i2c->base, suspend); -} - -struct nouveau_oclass -nouveau_i2c_oclass = { - .handle = NV_SUBDEV(I2C, 0x00), - .ofuncs = &(struct nouveau_ofuncs) { - .ctor = nouveau_i2c_ctor, - .dtor = nouveau_i2c_dtor, - .init = nouveau_i2c_init, - .fini = nouveau_i2c_fini, - }, -}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c index 1c4c9a5c8e2e..a6e72d3b06b5 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/bit.c @@ -32,25 +32,25 @@ static inline void i2c_drive_scl(struct nouveau_i2c_port *port, int state) { - nouveau_i2c_drive_scl(port, state); + port->func->drive_scl(port, state); } static inline void i2c_drive_sda(struct nouveau_i2c_port *port, int state) { - nouveau_i2c_drive_sda(port, state); + port->func->drive_sda(port, state); } static inline int i2c_sense_scl(struct nouveau_i2c_port *port) { - return nouveau_i2c_sense_scl(port); + return port->func->sense_scl(port); } static inline int i2c_sense_sda(struct nouveau_i2c_port *port) { - return nouveau_i2c_sense_sda(port); + return port->func->sense_sda(port); } static void @@ -77,9 +77,8 @@ i2c_start(struct nouveau_i2c_port *port) { int ret = 0; - port->state = i2c_sense_scl(port); - port->state |= i2c_sense_sda(port) << 1; - if (port->state != 3) { + if (!i2c_sense_scl(port) || + !i2c_sense_sda(port)) { i2c_drive_scl(port, 0); i2c_drive_sda(port, 1); if (!i2c_raise_scl(port)) @@ -184,10 +183,13 @@ i2c_addr(struct nouveau_i2c_port *port, struct i2c_msg *msg) static int i2c_bit_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { - struct nouveau_i2c_port *port = (struct nouveau_i2c_port *)adap; + struct nouveau_i2c_port *port = adap->algo_data; struct i2c_msg *msg = msgs; int ret = 0, mcnt = num; + if (port->func->acquire) + port->func->acquire(port); + while (!ret && mcnt--) { u8 remaining = msg->len; u8 *ptr = msg->buf; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nv04.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv04.c new file mode 100644 index 000000000000..2ad18840fe63 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv04.c @@ -0,0 +1,143 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/i2c.h> +#include <subdev/vga.h> + +struct nv04_i2c_priv { + struct nouveau_i2c base; +}; + +struct nv04_i2c_port { + struct nouveau_i2c_port base; + u8 drive; + u8 sense; +}; + +static void +nv04_i2c_drive_scl(struct nouveau_i2c_port *base, int state) +{ + struct nv04_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv04_i2c_port *port = (void *)base; + u8 val = nv_rdvgac(priv, 0, port->drive); + if (state) val |= 0x20; + else val &= 0xdf; + nv_wrvgac(priv, 0, port->drive, val | 0x01); +} + +static void +nv04_i2c_drive_sda(struct nouveau_i2c_port *base, int state) +{ + struct nv04_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv04_i2c_port *port = (void *)base; + u8 val = nv_rdvgac(priv, 0, port->drive); + if (state) val |= 0x10; + else val &= 0xef; + nv_wrvgac(priv, 0, port->drive, val | 0x01); +} + +static int +nv04_i2c_sense_scl(struct nouveau_i2c_port *base) +{ + struct nv04_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv04_i2c_port *port = (void *)base; + return !!(nv_rdvgac(priv, 0, port->sense) & 0x04); +} + +static int +nv04_i2c_sense_sda(struct nouveau_i2c_port *base) +{ + struct nv04_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv04_i2c_port *port = (void *)base; + return !!(nv_rdvgac(priv, 0, port->sense) & 0x08); +} + +static const struct nouveau_i2c_func +nv04_i2c_func = { + .drive_scl = nv04_i2c_drive_scl, + .drive_sda = nv04_i2c_drive_sda, + .sense_scl = nv04_i2c_sense_scl, + .sense_sda = nv04_i2c_sense_sda, +}; + +static int +nv04_i2c_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv04_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_bit_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + port->base.func = &nv04_i2c_func; + port->drive = info->drive; + port->sense = info->sense; + return 0; +} + +static struct nouveau_oclass +nv04_i2c_sclass[] = { + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NV04_BIT), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv04_i2c_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + {} +}; + +static int +nv04_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv04_i2c_priv *priv; + int ret; + + ret = nouveau_i2c_create(parent, engine, oclass, nv04_i2c_sclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + return 0; +} + +struct nouveau_oclass +nv04_i2c_oclass = { + .handle = NV_SUBDEV(I2C, 0x04), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv04_i2c_ctor, + .dtor = _nouveau_i2c_dtor, + .init = _nouveau_i2c_init, + .fini = _nouveau_i2c_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nv4e.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv4e.c new file mode 100644 index 000000000000..f501ae25dbb3 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv4e.c @@ -0,0 +1,135 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/i2c.h> +#include <subdev/vga.h> + +struct nv4e_i2c_priv { + struct nouveau_i2c base; +}; + +struct nv4e_i2c_port { + struct nouveau_i2c_port base; + u32 addr; +}; + +static void +nv4e_i2c_drive_scl(struct nouveau_i2c_port *base, int state) +{ + struct nv4e_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv4e_i2c_port *port = (void *)base; + nv_mask(priv, port->addr, 0x2f, state ? 0x21 : 0x01); +} + +static void +nv4e_i2c_drive_sda(struct nouveau_i2c_port *base, int state) +{ + struct nv4e_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv4e_i2c_port *port = (void *)base; + nv_mask(priv, port->addr, 0x1f, state ? 0x11 : 0x01); +} + +static int +nv4e_i2c_sense_scl(struct nouveau_i2c_port *base) +{ + struct nv4e_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv4e_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00040000); +} + +static int +nv4e_i2c_sense_sda(struct nouveau_i2c_port *base) +{ + struct nv4e_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv4e_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00080000); +} + +static const struct nouveau_i2c_func +nv4e_i2c_func = { + .drive_scl = nv4e_i2c_drive_scl, + .drive_sda = nv4e_i2c_drive_sda, + .sense_scl = nv4e_i2c_sense_scl, + .sense_sda = nv4e_i2c_sense_sda, +}; + +static int +nv4e_i2c_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv4e_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_bit_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + port->base.func = &nv4e_i2c_func; + port->addr = 0x600800 + info->drive; + return 0; +} + +static struct nouveau_oclass +nv4e_i2c_sclass[] = { + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NV4E_BIT), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv4e_i2c_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + {} +}; + +static int +nv4e_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv4e_i2c_priv *priv; + int ret; + + ret = nouveau_i2c_create(parent, engine, oclass, nv4e_i2c_sclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + return 0; +} + +struct nouveau_oclass +nv4e_i2c_oclass = { + .handle = NV_SUBDEV(I2C, 0x4e), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv4e_i2c_ctor, + .dtor = _nouveau_i2c_dtor, + .init = _nouveau_i2c_init, + .fini = _nouveau_i2c_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.c new file mode 100644 index 000000000000..378dfa324e5f --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.c @@ -0,0 +1,149 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "nv50.h" + +void +nv50_i2c_drive_scl(struct nouveau_i2c_port *base, int state) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + if (state) port->state |= 0x01; + else port->state &= 0xfe; + nv_wr32(priv, port->addr, port->state); +} + +void +nv50_i2c_drive_sda(struct nouveau_i2c_port *base, int state) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + if (state) port->state |= 0x02; + else port->state &= 0xfd; + nv_wr32(priv, port->addr, port->state); +} + +int +nv50_i2c_sense_scl(struct nouveau_i2c_port *base) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00000001); +} + +int +nv50_i2c_sense_sda(struct nouveau_i2c_port *base) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00000002); +} + +static const struct nouveau_i2c_func +nv50_i2c_func = { + .drive_scl = nv50_i2c_drive_scl, + .drive_sda = nv50_i2c_drive_sda, + .sense_scl = nv50_i2c_sense_scl, + .sense_sda = nv50_i2c_sense_sda, +}; + +const u32 nv50_i2c_addr[] = { + 0x00e138, 0x00e150, 0x00e168, 0x00e180, + 0x00e254, 0x00e274, 0x00e764, 0x00e780, + 0x00e79c, 0x00e7b8 +}; +const int nv50_i2c_addr_nr = ARRAY_SIZE(nv50_i2c_addr); + +static int +nv50_i2c_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv50_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_bit_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + if (info->drive >= nv50_i2c_addr_nr) + return -EINVAL; + + port->base.func = &nv50_i2c_func; + port->state = 0x00000007; + port->addr = nv50_i2c_addr[info->drive]; + return 0; +} + +int +nv50_i2c_port_init(struct nouveau_object *object) +{ + struct nv50_i2c_priv *priv = (void *)object->engine; + struct nv50_i2c_port *port = (void *)object; + nv_wr32(priv, port->addr, port->state); + return nouveau_i2c_port_init(&port->base); +} + +static struct nouveau_oclass +nv50_i2c_sclass[] = { + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NVIO_BIT), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv50_i2c_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = nv50_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + {} +}; + +static int +nv50_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv50_i2c_priv *priv; + int ret; + + ret = nouveau_i2c_create(parent, engine, oclass, nv50_i2c_sclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + return 0; +} + +struct nouveau_oclass +nv50_i2c_oclass = { + .handle = NV_SUBDEV(I2C, 0x50), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv50_i2c_ctor, + .dtor = _nouveau_i2c_dtor, + .init = _nouveau_i2c_init, + .fini = _nouveau_i2c_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.h b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.h new file mode 100644 index 000000000000..4e5ba48ebf5a --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv50.h @@ -0,0 +1,32 @@ +#ifndef __NV50_I2C_H__ +#define __NV50_I2C_H__ + +#include <subdev/i2c.h> + +struct nv50_i2c_priv { + struct nouveau_i2c base; +}; + +struct nv50_i2c_port { + struct nouveau_i2c_port base; + u32 addr; + u32 ctrl; + u32 data; + u32 state; +}; + +extern const u32 nv50_i2c_addr[]; +extern const int nv50_i2c_addr_nr; +int nv50_i2c_port_init(struct nouveau_object *); +int nv50_i2c_sense_scl(struct nouveau_i2c_port *); +int nv50_i2c_sense_sda(struct nouveau_i2c_port *); +void nv50_i2c_drive_scl(struct nouveau_i2c_port *, int state); +void nv50_i2c_drive_sda(struct nouveau_i2c_port *, int state); + +int nv94_aux_port_ctor(struct nouveau_object *, struct nouveau_object *, + struct nouveau_oclass *, void *, u32, + struct nouveau_object **); +void nv94_i2c_acquire(struct nouveau_i2c_port *); +void nv94_i2c_release(struct nouveau_i2c_port *); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nv94.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv94.c new file mode 100644 index 000000000000..61b771670bfe --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nv94.c @@ -0,0 +1,285 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "nv50.h" + +#define AUX_DBG(fmt, args...) nv_debug(aux, "AUXCH(%d): " fmt, ch, ##args) +#define AUX_ERR(fmt, args...) nv_error(aux, "AUXCH(%d): " fmt, ch, ##args) + +static void +auxch_fini(struct nouveau_i2c *aux, int ch) +{ + nv_mask(aux, 0x00e4e4 + (ch * 0x50), 0x00310000, 0x00000000); +} + +static int +auxch_init(struct nouveau_i2c *aux, int ch) +{ + const u32 unksel = 1; /* nfi which to use, or if it matters.. */ + const u32 ureq = unksel ? 0x00100000 : 0x00200000; + const u32 urep = unksel ? 0x01000000 : 0x02000000; + u32 ctrl, timeout; + + /* wait up to 1ms for any previous transaction to be done... */ + timeout = 1000; + do { + ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); + udelay(1); + if (!timeout--) { + AUX_ERR("begin idle timeout 0x%08x\n", ctrl); + return -EBUSY; + } + } while (ctrl & 0x03010000); + + /* set some magic, and wait up to 1ms for it to appear */ + nv_mask(aux, 0x00e4e4 + (ch * 0x50), 0x00300000, ureq); + timeout = 1000; + do { + ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); + udelay(1); + if (!timeout--) { + AUX_ERR("magic wait 0x%08x\n", ctrl); + auxch_fini(aux, ch); + return -EBUSY; + } + } while ((ctrl & 0x03000000) != urep); + + return 0; +} + +int +nv94_aux(struct nouveau_i2c_port *base, u8 type, u32 addr, u8 *data, u8 size) +{ + struct nouveau_i2c *aux = nouveau_i2c(base); + struct nv50_i2c_port *port = (void *)base; + u32 ctrl, stat, timeout, retries; + u32 xbuf[4] = {}; + int ch = port->addr; + int ret, i; + + AUX_DBG("%d: 0x%08x %d\n", type, addr, size); + + ret = auxch_init(aux, ch); + if (ret) + goto out; + + stat = nv_rd32(aux, 0x00e4e8 + (ch * 0x50)); + if (!(stat & 0x10000000)) { + AUX_DBG("sink not detected\n"); + ret = -ENXIO; + goto out; + } + + if (!(type & 1)) { + memcpy(xbuf, data, size); + for (i = 0; i < 16; i += 4) { + AUX_DBG("wr 0x%08x\n", xbuf[i / 4]); + nv_wr32(aux, 0x00e4c0 + (ch * 0x50) + i, xbuf[i / 4]); + } + } + + ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); + ctrl &= ~0x0001f0ff; + ctrl |= type << 12; + ctrl |= size - 1; + nv_wr32(aux, 0x00e4e0 + (ch * 0x50), addr); + + /* retry transaction a number of times on failure... */ + ret = -EREMOTEIO; + for (retries = 0; retries < 32; retries++) { + /* reset, and delay a while if this is a retry */ + nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x80000000 | ctrl); + nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x00000000 | ctrl); + if (retries) + udelay(400); + + /* transaction request, wait up to 1ms for it to complete */ + nv_wr32(aux, 0x00e4e4 + (ch * 0x50), 0x00010000 | ctrl); + + timeout = 1000; + do { + ctrl = nv_rd32(aux, 0x00e4e4 + (ch * 0x50)); + udelay(1); + if (!timeout--) { + AUX_ERR("tx req timeout 0x%08x\n", ctrl); + goto out; + } + } while (ctrl & 0x00010000); + + /* read status, and check if transaction completed ok */ + stat = nv_mask(aux, 0x00e4e8 + (ch * 0x50), 0, 0); + if (!(stat & 0x000f0f00)) { + ret = 0; + break; + } + + AUX_DBG("%02d 0x%08x 0x%08x\n", retries, ctrl, stat); + } + + if (type & 1) { + for (i = 0; i < 16; i += 4) { + xbuf[i / 4] = nv_rd32(aux, 0x00e4d0 + (ch * 0x50) + i); + AUX_DBG("rd 0x%08x\n", xbuf[i / 4]); + } + memcpy(data, xbuf, size); + } + +out: + auxch_fini(aux, ch); + return ret; +} + +void +nv94_i2c_acquire(struct nouveau_i2c_port *base) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + if (port->ctrl) { + nv_mask(priv, port->ctrl + 0x0c, 0x00000001, 0x00000000); + nv_mask(priv, port->ctrl + 0x00, 0x0000f003, port->data); + } +} + +void +nv94_i2c_release(struct nouveau_i2c_port *base) +{ +} + +static const struct nouveau_i2c_func +nv94_i2c_func = { + .acquire = nv94_i2c_acquire, + .release = nv94_i2c_release, + .drive_scl = nv50_i2c_drive_scl, + .drive_sda = nv50_i2c_drive_sda, + .sense_scl = nv50_i2c_sense_scl, + .sense_sda = nv50_i2c_sense_sda, +}; + +static int +nv94_i2c_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv50_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_bit_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + if (info->drive >= nv50_i2c_addr_nr) + return -EINVAL; + + port->base.func = &nv94_i2c_func; + port->state = 7; + port->addr = nv50_i2c_addr[info->drive]; + if (info->share != DCB_I2C_UNUSED) { + port->ctrl = 0x00e500 + (info->share * 0x50); + port->data = 0x0000e001; + } + return 0; +} + +static const struct nouveau_i2c_func +nv94_aux_func = { + .acquire = nv94_i2c_acquire, + .release = nv94_i2c_release, + .aux = nv94_aux, +}; + +int +nv94_aux_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv50_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_aux_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + port->base.func = &nv94_aux_func; + port->addr = info->drive; + if (info->share != DCB_I2C_UNUSED) { + port->ctrl = 0x00e500 + (info->drive * 0x50); + port->data = 0x00002002; + } + + return 0; +} + +static struct nouveau_oclass +nv94_i2c_sclass[] = { + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NVIO_BIT), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv94_i2c_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = nv50_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NVIO_AUX), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv94_aux_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + {} +}; + +static int +nv94_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv50_i2c_priv *priv; + int ret; + + ret = nouveau_i2c_create(parent, engine, oclass, nv94_i2c_sclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + return 0; +} + +struct nouveau_oclass +nv94_i2c_oclass = { + .handle = NV_SUBDEV(I2C, 0x94), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv94_i2c_ctor, + .dtor = _nouveau_i2c_dtor, + .init = _nouveau_i2c_init, + .fini = _nouveau_i2c_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/i2c/nvd0.c b/drivers/gpu/drm/nouveau/core/subdev/i2c/nvd0.c new file mode 100644 index 000000000000..f761b8a610f1 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/i2c/nvd0.c @@ -0,0 +1,124 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "nv50.h" + +static int +nvd0_i2c_sense_scl(struct nouveau_i2c_port *base) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00000010); +} + +static int +nvd0_i2c_sense_sda(struct nouveau_i2c_port *base) +{ + struct nv50_i2c_priv *priv = (void *)nv_object(base)->engine; + struct nv50_i2c_port *port = (void *)base; + return !!(nv_rd32(priv, port->addr) & 0x00000020); +} + +static const struct nouveau_i2c_func +nvd0_i2c_func = { + .acquire = nv94_i2c_acquire, + .release = nv94_i2c_release, + .drive_scl = nv50_i2c_drive_scl, + .drive_sda = nv50_i2c_drive_sda, + .sense_scl = nvd0_i2c_sense_scl, + .sense_sda = nvd0_i2c_sense_sda, +}; + +static int +nvd0_i2c_port_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 index, + struct nouveau_object **pobject) +{ + struct dcb_i2c_entry *info = data; + struct nv50_i2c_port *port; + int ret; + + ret = nouveau_i2c_port_create(parent, engine, oclass, index, + &nouveau_i2c_bit_algo, &port); + *pobject = nv_object(port); + if (ret) + return ret; + + port->base.func = &nvd0_i2c_func; + port->state = 0x00000007; + port->addr = 0x00d014 + (info->drive * 0x20); + if (info->share != DCB_I2C_UNUSED) { + port->ctrl = 0x00e500 + (info->share * 0x50); + port->data = 0x0000e001; + } + return 0; +} + +static struct nouveau_oclass +nvd0_i2c_sclass[] = { + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NVIO_BIT), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nvd0_i2c_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = nv50_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + { .handle = NV_I2C_TYPE_DCBI2C(DCB_I2C_NVIO_AUX), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nv94_aux_port_ctor, + .dtor = _nouveau_i2c_port_dtor, + .init = _nouveau_i2c_port_init, + .fini = _nouveau_i2c_port_fini, + }, + }, + {} +}; + +static int +nvd0_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nv50_i2c_priv *priv; + int ret; + + ret = nouveau_i2c_create(parent, engine, oclass, nvd0_i2c_sclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + return 0; +} + +struct nouveau_oclass +nvd0_i2c_oclass = { + .handle = NV_SUBDEV(I2C, 0xd0), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nvd0_i2c_ctor, + .dtor = _nouveau_i2c_dtor, + .init = _nouveau_i2c_init, + .fini = _nouveau_i2c_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/nv04.c b/drivers/gpu/drm/nouveau/core/subdev/mc/nv04.c index 23ebe477a6f0..89da8fa7ea0f 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mc/nv04.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mc/nv04.c @@ -37,7 +37,7 @@ nv04_mc_intr[] = { { 0x00100000, NVDEV_SUBDEV_TIMER }, { 0x01000000, NVDEV_ENGINE_DISP }, /* NV04- PCRTC0 */ { 0x02000000, NVDEV_ENGINE_DISP }, /* NV11- PCRTC1 */ - { 0x10000000, NVDEV_SUBDEV_GPIO }, /* PBUS */ + { 0x10000000, NVDEV_SUBDEV_BUS }, { 0x80000000, NVDEV_ENGINE_SW }, {} }; diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/mc/nv50.c index 8d759f830323..5965add6daee 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mc/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mc/nv50.c @@ -38,6 +38,7 @@ nv50_mc_intr[] = { { 0x00100000, NVDEV_SUBDEV_TIMER }, { 0x00200000, NVDEV_SUBDEV_GPIO }, { 0x04000000, NVDEV_ENGINE_DISP }, + { 0x10000000, NVDEV_SUBDEV_BUS }, { 0x80000000, NVDEV_ENGINE_SW }, { 0x0000d101, NVDEV_SUBDEV_FB }, {}, diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c b/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c index ceb5c83f9459..3a80b29dce0f 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c @@ -35,10 +35,12 @@ nv98_mc_intr[] = { { 0x00001000, NVDEV_ENGINE_GR }, { 0x00004000, NVDEV_ENGINE_CRYPT }, /* NV84:NVA3 */ { 0x00008000, NVDEV_ENGINE_BSP }, + { 0x00080000, NVDEV_SUBDEV_THERM }, /* NVA3:NVC0 */ { 0x00100000, NVDEV_SUBDEV_TIMER }, { 0x00200000, NVDEV_SUBDEV_GPIO }, { 0x00400000, NVDEV_ENGINE_COPY0 }, /* NVA3- */ { 0x04000000, NVDEV_ENGINE_DISP }, + { 0x10000000, NVDEV_SUBDEV_BUS }, { 0x80000000, NVDEV_ENGINE_SW }, { 0x0040d101, NVDEV_SUBDEV_FB }, {}, diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/nvc0.c b/drivers/gpu/drm/nouveau/core/subdev/mc/nvc0.c index 92796682722d..42bbf72023a8 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mc/nvc0.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mc/nvc0.c @@ -36,11 +36,13 @@ nvc0_mc_intr[] = { { 0x00000100, NVDEV_ENGINE_FIFO }, { 0x00001000, NVDEV_ENGINE_GR }, { 0x00008000, NVDEV_ENGINE_BSP }, + { 0x00040000, NVDEV_SUBDEV_THERM }, { 0x00020000, NVDEV_ENGINE_VP }, { 0x00100000, NVDEV_SUBDEV_TIMER }, { 0x00200000, NVDEV_SUBDEV_GPIO }, { 0x02000000, NVDEV_SUBDEV_LTCG }, { 0x04000000, NVDEV_ENGINE_DISP }, + { 0x10000000, NVDEV_SUBDEV_BUS }, { 0x40000000, NVDEV_SUBDEV_IBUS }, { 0x80000000, NVDEV_ENGINE_SW }, {}, diff --git a/drivers/gpu/drm/nouveau/core/subdev/mxm/mxms.c b/drivers/gpu/drm/nouveau/core/subdev/mxm/mxms.c index 839ca1edc132..4bde7f7f7b81 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mxm/mxms.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mxm/mxms.c @@ -156,15 +156,15 @@ mxms_foreach(struct nouveau_mxm *mxm, u8 types, nv_debug(mxm, "%4s: ", mxms_desc_name[type]); for (j = headerlen - 1; j >= 0; j--) - printk("%02x", dump[j]); - printk("\n"); + pr_cont("%02x", dump[j]); + pr_cont("\n"); dump += headerlen; for (i = 0; i < entries; i++, dump += recordlen) { nv_debug(mxm, " "); for (j = recordlen - 1; j >= 0; j--) - printk("%02x", dump[j]); - printk("\n"); + pr_cont("%02x", dump[j]); + pr_cont("\n"); } } diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/base.c b/drivers/gpu/drm/nouveau/core/subdev/therm/base.c index 1674c74a76c8..f794dc89a3b2 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/base.c @@ -29,6 +29,134 @@ #include "priv.h" +static int +nouveau_therm_update_trip(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *priv = (void *)therm; + struct nouveau_therm_trip_point *trip = priv->fan->bios.trip, + *cur_trip = NULL, + *last_trip = priv->last_trip; + u8 temp = therm->temp_get(therm); + u16 duty, i; + + /* look for the trip point corresponding to the current temperature */ + cur_trip = NULL; + for (i = 0; i < priv->fan->bios.nr_fan_trip; i++) { + if (temp >= trip[i].temp) + cur_trip = &trip[i]; + } + + /* account for the hysteresis cycle */ + if (last_trip && temp <= (last_trip->temp) && + temp > (last_trip->temp - last_trip->hysteresis)) + cur_trip = last_trip; + + if (cur_trip) { + duty = cur_trip->fan_duty; + priv->last_trip = cur_trip; + } else { + duty = 0; + priv->last_trip = NULL; + } + + return duty; +} + +static int +nouveau_therm_update_linear(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *priv = (void *)therm; + u8 linear_min_temp = priv->fan->bios.linear_min_temp; + u8 linear_max_temp = priv->fan->bios.linear_max_temp; + u8 temp = therm->temp_get(therm); + u16 duty; + + /* handle the non-linear part first */ + if (temp < linear_min_temp) + return priv->fan->bios.min_duty; + else if (temp > linear_max_temp) + return priv->fan->bios.max_duty; + + /* we are in the linear zone */ + duty = (temp - linear_min_temp); + duty *= (priv->fan->bios.max_duty - priv->fan->bios.min_duty); + duty /= (linear_max_temp - linear_min_temp); + duty += priv->fan->bios.min_duty; + + return duty; +} + +static void +nouveau_therm_update(struct nouveau_therm *therm, int mode) +{ + struct nouveau_timer *ptimer = nouveau_timer(therm); + struct nouveau_therm_priv *priv = (void *)therm; + unsigned long flags; + int duty; + + spin_lock_irqsave(&priv->lock, flags); + if (mode < 0) + mode = priv->mode; + priv->mode = mode; + + switch (mode) { + case NOUVEAU_THERM_CTRL_MANUAL: + duty = nouveau_therm_fan_get(therm); + if (duty < 0) + duty = 100; + break; + case NOUVEAU_THERM_CTRL_AUTO: + if (priv->fan->bios.nr_fan_trip) + duty = nouveau_therm_update_trip(therm); + else + duty = nouveau_therm_update_linear(therm); + break; + case NOUVEAU_THERM_CTRL_NONE: + default: + goto done; + } + + nv_debug(therm, "FAN target request: %d%%\n", duty); + nouveau_therm_fan_set(therm, (mode != NOUVEAU_THERM_CTRL_AUTO), duty); + +done: + if (list_empty(&priv->alarm.head) && (mode == NOUVEAU_THERM_CTRL_AUTO)) + ptimer->alarm(ptimer, 1000000000ULL, &priv->alarm); + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void +nouveau_therm_alarm(struct nouveau_alarm *alarm) +{ + struct nouveau_therm_priv *priv = + container_of(alarm, struct nouveau_therm_priv, alarm); + nouveau_therm_update(&priv->base, -1); +} + +int +nouveau_therm_mode(struct nouveau_therm *therm, int mode) +{ + struct nouveau_therm_priv *priv = (void *)therm; + struct nouveau_device *device = nv_device(therm); + static const char *name[] = { + "disabled", + "manual", + "automatic" + }; + + /* The default PDAEMON ucode interferes with fan management */ + if ((mode >= ARRAY_SIZE(name)) || + (mode != NOUVEAU_THERM_CTRL_NONE && device->card_type >= NV_C0)) + return -EINVAL; + + if (priv->mode == mode) + return 0; + + nv_info(therm, "Thermal management: %s\n", name[mode]); + nouveau_therm_update(therm, mode); + return 0; +} + int nouveau_therm_attr_get(struct nouveau_therm *therm, enum nouveau_therm_attr_type type) @@ -37,11 +165,11 @@ nouveau_therm_attr_get(struct nouveau_therm *therm, switch (type) { case NOUVEAU_THERM_ATTR_FAN_MIN_DUTY: - return priv->bios_fan.min_duty; + return priv->fan->bios.min_duty; case NOUVEAU_THERM_ATTR_FAN_MAX_DUTY: - return priv->bios_fan.max_duty; + return priv->fan->bios.max_duty; case NOUVEAU_THERM_ATTR_FAN_MODE: - return priv->fan.mode; + return priv->mode; case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST: return priv->bios_sensor.thrs_fan_boost.temp; case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST: @@ -73,42 +201,50 @@ nouveau_therm_attr_set(struct nouveau_therm *therm, case NOUVEAU_THERM_ATTR_FAN_MIN_DUTY: if (value < 0) value = 0; - if (value > priv->bios_fan.max_duty) - value = priv->bios_fan.max_duty; - priv->bios_fan.min_duty = value; + if (value > priv->fan->bios.max_duty) + value = priv->fan->bios.max_duty; + priv->fan->bios.min_duty = value; return 0; case NOUVEAU_THERM_ATTR_FAN_MAX_DUTY: if (value < 0) value = 0; - if (value < priv->bios_fan.min_duty) - value = priv->bios_fan.min_duty; - priv->bios_fan.max_duty = value; + if (value < priv->fan->bios.min_duty) + value = priv->fan->bios.min_duty; + priv->fan->bios.max_duty = value; return 0; case NOUVEAU_THERM_ATTR_FAN_MODE: - return nouveau_therm_fan_set_mode(therm, value); + return nouveau_therm_mode(therm, value); case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST: priv->bios_sensor.thrs_fan_boost.temp = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST: priv->bios_sensor.thrs_fan_boost.hysteresis = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_DOWN_CLK: priv->bios_sensor.thrs_down_clock.temp = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_DOWN_CLK_HYST: priv->bios_sensor.thrs_down_clock.hysteresis = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_CRITICAL: priv->bios_sensor.thrs_critical.temp = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_CRITICAL_HYST: priv->bios_sensor.thrs_critical.hysteresis = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_SHUTDOWN: priv->bios_sensor.thrs_shutdown.temp = value; + priv->sensor.program_alarms(therm); return 0; case NOUVEAU_THERM_ATTR_THRS_SHUTDOWN_HYST: priv->bios_sensor.thrs_shutdown.hysteresis = value; + priv->sensor.program_alarms(therm); return 0; } @@ -116,7 +252,7 @@ nouveau_therm_attr_set(struct nouveau_therm *therm, } int -nouveau_therm_init(struct nouveau_object *object) +_nouveau_therm_init(struct nouveau_object *object) { struct nouveau_therm *therm = (void *)object; struct nouveau_therm_priv *priv = (void *)therm; @@ -126,19 +262,69 @@ nouveau_therm_init(struct nouveau_object *object) if (ret) return ret; - if (priv->fan.percent >= 0) - therm->fan_set(therm, priv->fan.percent); - + if (priv->suspend >= 0) + nouveau_therm_mode(therm, priv->mode); + priv->sensor.program_alarms(therm); return 0; } int -nouveau_therm_fini(struct nouveau_object *object, bool suspend) +_nouveau_therm_fini(struct nouveau_object *object, bool suspend) { struct nouveau_therm *therm = (void *)object; struct nouveau_therm_priv *priv = (void *)therm; - priv->fan.percent = therm->fan_get(therm); + if (suspend) { + priv->suspend = priv->mode; + priv->mode = NOUVEAU_THERM_CTRL_NONE; + } return nouveau_subdev_fini(&therm->base, suspend); } + +int +nouveau_therm_create_(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, + int length, void **pobject) +{ + struct nouveau_therm_priv *priv; + int ret; + + ret = nouveau_subdev_create_(parent, engine, oclass, 0, "PTHERM", + "therm", length, pobject); + priv = *pobject; + if (ret) + return ret; + + nouveau_alarm_init(&priv->alarm, nouveau_therm_alarm); + spin_lock_init(&priv->lock); + spin_lock_init(&priv->sensor.alarm_program_lock); + + priv->base.fan_get = nouveau_therm_fan_user_get; + priv->base.fan_set = nouveau_therm_fan_user_set; + priv->base.fan_sense = nouveau_therm_fan_sense; + priv->base.attr_get = nouveau_therm_attr_get; + priv->base.attr_set = nouveau_therm_attr_set; + priv->mode = priv->suspend = -1; /* undefined */ + return 0; +} + +int +nouveau_therm_preinit(struct nouveau_therm *therm) +{ + nouveau_therm_ic_ctor(therm); + nouveau_therm_sensor_ctor(therm); + nouveau_therm_fan_ctor(therm); + + nouveau_therm_mode(therm, NOUVEAU_THERM_CTRL_NONE); + return 0; +} + +void +_nouveau_therm_dtor(struct nouveau_object *object) +{ + struct nouveau_therm_priv *priv = (void *)object; + kfree(priv->fan); + nouveau_subdev_destroy(&priv->base.base); +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c b/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c index 523178685180..c728380d3d62 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c @@ -27,90 +27,107 @@ #include <core/object.h> #include <core/device.h> + #include <subdev/gpio.h> #include <subdev/timer.h> -int -nouveau_therm_fan_get(struct nouveau_therm *therm) +static int +nouveau_fan_update(struct nouveau_fan *fan, bool immediate, int target) { + struct nouveau_therm *therm = fan->parent; struct nouveau_therm_priv *priv = (void *)therm; - struct nouveau_gpio *gpio = nouveau_gpio(therm); - struct dcb_gpio_func func; - int card_type = nv_device(therm)->card_type; - u32 divs, duty; - int ret; - - if (!priv->fan.pwm_get) - return -ENODEV; + struct nouveau_timer *ptimer = nouveau_timer(priv); + unsigned long flags; + int ret = 0; + int duty; + + /* update target fan speed, restricting to allowed range */ + spin_lock_irqsave(&fan->lock, flags); + if (target < 0) + target = fan->percent; + target = max_t(u8, target, fan->bios.min_duty); + target = min_t(u8, target, fan->bios.max_duty); + if (fan->percent != target) { + nv_debug(therm, "FAN target: %d\n", target); + fan->percent = target; + } - ret = gpio->find(gpio, 0, DCB_GPIO_PWM_FAN, 0xff, &func); - if (ret == 0) { - ret = priv->fan.pwm_get(therm, func.line, &divs, &duty); - if (ret == 0 && divs) { - divs = max(divs, duty); - if (card_type <= NV_40 || (func.log[0] & 1)) - duty = divs - duty; - return (duty * 100) / divs; - } + /* check that we're not already at the target duty cycle */ + duty = fan->get(therm); + if (duty == target) + goto done; + + /* smooth out the fanspeed increase/decrease */ + if (!immediate && duty >= 0) { + /* the constant "3" is a rough approximation taken from + * nvidia's behaviour. + * it is meant to bump the fan speed more incrementally + */ + if (duty < target) + duty = min(duty + 3, target); + else if (duty > target) + duty = max(duty - 3, target); + } else { + duty = target; + } - return gpio->get(gpio, 0, func.func, func.line) * 100; + nv_debug(therm, "FAN update: %d\n", duty); + ret = fan->set(therm, duty); + if (ret) + goto done; + + /* schedule next fan update, if not at target speed already */ + if (list_empty(&fan->alarm.head) && target != duty) { + u16 bump_period = fan->bios.bump_period; + u16 slow_down_period = fan->bios.slow_down_period; + u64 delay; + + if (duty > target) + delay = slow_down_period; + else if (duty == target) + delay = min(bump_period, slow_down_period) ; + else + delay = bump_period; + + ptimer->alarm(ptimer, delay * 1000 * 1000, &fan->alarm); } - return -ENODEV; +done: + spin_unlock_irqrestore(&fan->lock, flags); + return ret; +} + +static void +nouveau_fan_alarm(struct nouveau_alarm *alarm) +{ + struct nouveau_fan *fan = container_of(alarm, struct nouveau_fan, alarm); + nouveau_fan_update(fan, false, -1); } int -nouveau_therm_fan_set(struct nouveau_therm *therm, int percent) +nouveau_therm_fan_get(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; - struct nouveau_gpio *gpio = nouveau_gpio(therm); - struct dcb_gpio_func func; - int card_type = nv_device(therm)->card_type; - u32 divs, duty; - int ret; - - if (priv->fan.mode == FAN_CONTROL_NONE) - return -EINVAL; - - if (!priv->fan.pwm_set) - return -ENODEV; - - if (percent < priv->bios_fan.min_duty) - percent = priv->bios_fan.min_duty; - if (percent > priv->bios_fan.max_duty) - percent = priv->bios_fan.max_duty; - - ret = gpio->find(gpio, 0, DCB_GPIO_PWM_FAN, 0xff, &func); - if (ret == 0) { - divs = priv->bios_perf_fan.pwm_divisor; - if (priv->bios_fan.pwm_freq) { - divs = 1; - if (priv->fan.pwm_clock) - divs = priv->fan.pwm_clock(therm); - divs /= priv->bios_fan.pwm_freq; - } - - duty = ((divs * percent) + 99) / 100; - if (card_type <= NV_40 || (func.log[0] & 1)) - duty = divs - duty; - - ret = priv->fan.pwm_set(therm, func.line, divs, duty); - return ret; - } + return priv->fan->get(therm); +} - return -ENODEV; +int +nouveau_therm_fan_set(struct nouveau_therm *therm, bool immediate, int percent) +{ + struct nouveau_therm_priv *priv = (void *)therm; + return nouveau_fan_update(priv->fan, immediate, percent); } int nouveau_therm_fan_sense(struct nouveau_therm *therm) { + struct nouveau_therm_priv *priv = (void *)therm; struct nouveau_timer *ptimer = nouveau_timer(therm); struct nouveau_gpio *gpio = nouveau_gpio(therm); - struct dcb_gpio_func func; u32 cycles, cur, prev; u64 start, end, tach; - if (gpio->find(gpio, 0, DCB_GPIO_FAN_SENSE, 0xff, &func)) + if (priv->fan->tach.func == DCB_GPIO_UNUSED) return -ENODEV; /* Time a complete rotation and extrapolate to RPM: @@ -118,12 +135,12 @@ nouveau_therm_fan_sense(struct nouveau_therm *therm) * We get 4 changes (0 -> 1 -> 0 -> 1) per complete rotation. */ start = ptimer->read(ptimer); - prev = gpio->get(gpio, 0, func.func, func.line); + prev = gpio->get(gpio, 0, priv->fan->tach.func, priv->fan->tach.line); cycles = 0; do { usleep_range(500, 1000); /* supports 0 < rpm < 7500 */ - cur = gpio->get(gpio, 0, func.func, func.line); + cur = gpio->get(gpio, 0, priv->fan->tach.func, priv->fan->tach.line); if (prev != cur) { if (!start) start = ptimer->read(ptimer); @@ -142,34 +159,6 @@ nouveau_therm_fan_sense(struct nouveau_therm *therm) } int -nouveau_therm_fan_set_mode(struct nouveau_therm *therm, - enum nouveau_therm_fan_mode mode) -{ - struct nouveau_therm_priv *priv = (void *)therm; - - if (priv->fan.mode == mode) - return 0; - - if (mode < FAN_CONTROL_NONE || mode >= FAN_CONTROL_NR) - return -EINVAL; - - switch (mode) - { - case FAN_CONTROL_NONE: - nv_info(therm, "switch fan to no-control mode\n"); - break; - case FAN_CONTROL_MANUAL: - nv_info(therm, "switch fan to manual mode\n"); - break; - case FAN_CONTROL_NR: - break; - } - - priv->fan.mode = mode; - return 0; -} - -int nouveau_therm_fan_user_get(struct nouveau_therm *therm) { return nouveau_therm_fan_get(therm); @@ -180,55 +169,86 @@ nouveau_therm_fan_user_set(struct nouveau_therm *therm, int percent) { struct nouveau_therm_priv *priv = (void *)therm; - if (priv->fan.mode != FAN_CONTROL_MANUAL) + if (priv->mode != NOUVEAU_THERM_CTRL_MANUAL) return -EINVAL; - return nouveau_therm_fan_set(therm, percent); + return nouveau_therm_fan_set(therm, true, percent); } -void +static void nouveau_therm_fan_set_defaults(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; - priv->bios_fan.pwm_freq = 0; - priv->bios_fan.min_duty = 0; - priv->bios_fan.max_duty = 100; + priv->fan->bios.pwm_freq = 0; + priv->fan->bios.min_duty = 0; + priv->fan->bios.max_duty = 100; + priv->fan->bios.bump_period = 500; + priv->fan->bios.slow_down_period = 2000; + priv->fan->bios.linear_min_temp = 40; + priv->fan->bios.linear_max_temp = 85; } - static void nouveau_therm_fan_safety_checks(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; - if (priv->bios_fan.min_duty > 100) - priv->bios_fan.min_duty = 100; - if (priv->bios_fan.max_duty > 100) - priv->bios_fan.max_duty = 100; + if (priv->fan->bios.min_duty > 100) + priv->fan->bios.min_duty = 100; + if (priv->fan->bios.max_duty > 100) + priv->fan->bios.max_duty = 100; - if (priv->bios_fan.min_duty > priv->bios_fan.max_duty) - priv->bios_fan.min_duty = priv->bios_fan.max_duty; -} - -int nouveau_fan_pwm_clock_dummy(struct nouveau_therm *therm) -{ - return 1; + if (priv->fan->bios.min_duty > priv->fan->bios.max_duty) + priv->fan->bios.min_duty = priv->fan->bios.max_duty; } int nouveau_therm_fan_ctor(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; + struct nouveau_gpio *gpio = nouveau_gpio(therm); struct nouveau_bios *bios = nouveau_bios(therm); + struct dcb_gpio_func func; + int ret; + /* attempt to locate a drivable fan, and determine control method */ + ret = gpio->find(gpio, 0, DCB_GPIO_FAN, 0xff, &func); + if (ret == 0) { + if (func.log[0] & DCB_GPIO_LOG_DIR_IN) { + nv_debug(therm, "GPIO_FAN is in input mode\n"); + ret = -EINVAL; + } else { + ret = nouveau_fanpwm_create(therm, &func); + if (ret != 0) + ret = nouveau_fantog_create(therm, &func); + } + } + + /* no controllable fan found, create a dummy fan module */ + if (ret != 0) { + ret = nouveau_fannil_create(therm); + if (ret) + return ret; + } + + nv_info(therm, "FAN control: %s\n", priv->fan->type); + + /* attempt to detect a tachometer connection */ + ret = gpio->find(gpio, 0, DCB_GPIO_FAN_SENSE, 0xff, &priv->fan->tach); + if (ret) + priv->fan->tach.func = DCB_GPIO_UNUSED; + + /* initialise fan bump/slow update handling */ + priv->fan->parent = therm; + nouveau_alarm_init(&priv->fan->alarm, nouveau_fan_alarm); + spin_lock_init(&priv->fan->lock); + + /* other random init... */ nouveau_therm_fan_set_defaults(therm); - nvbios_perf_fan_parse(bios, &priv->bios_perf_fan); - if (nvbios_therm_fan_parse(bios, &priv->bios_fan)) + nvbios_perf_fan_parse(bios, &priv->fan->perf); + if (nvbios_therm_fan_parse(bios, &priv->fan->bios)) nv_error(therm, "parsing the thermal table failed\n"); nouveau_therm_fan_safety_checks(therm); - - nouveau_therm_fan_set_mode(therm, FAN_CONTROL_NONE); - return 0; } diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/fannil.c b/drivers/gpu/drm/nouveau/core/subdev/therm/fannil.c new file mode 100644 index 000000000000..b78c182e1d51 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/fannil.c @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "priv.h" + +static int +nouveau_fannil_get(struct nouveau_therm *therm) +{ + return -ENODEV; +} + +static int +nouveau_fannil_set(struct nouveau_therm *therm, int percent) +{ + return -ENODEV; +} + +int +nouveau_fannil_create(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fan *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + tpriv->fan = priv; + if (!priv) + return -ENOMEM; + + priv->type = "none / external"; + priv->get = nouveau_fannil_get; + priv->set = nouveau_fannil_set; + return 0; +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/fanpwm.c b/drivers/gpu/drm/nouveau/core/subdev/therm/fanpwm.c new file mode 100644 index 000000000000..5f71db8e8992 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/fanpwm.c @@ -0,0 +1,107 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + * Martin Peres + */ + +#include <core/option.h> +#include <subdev/gpio.h> + +#include "priv.h" + +struct nouveau_fanpwm_priv { + struct nouveau_fan base; + struct dcb_gpio_func func; +}; + +static int +nouveau_fanpwm_get(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fanpwm_priv *priv = (void *)tpriv->fan; + struct nouveau_gpio *gpio = nouveau_gpio(therm); + int card_type = nv_device(therm)->card_type; + u32 divs, duty; + int ret; + + ret = therm->pwm_get(therm, priv->func.line, &divs, &duty); + if (ret == 0 && divs) { + divs = max(divs, duty); + if (card_type <= NV_40 || (priv->func.log[0] & 1)) + duty = divs - duty; + return (duty * 100) / divs; + } + + return gpio->get(gpio, 0, priv->func.func, priv->func.line) * 100; +} + +static int +nouveau_fanpwm_set(struct nouveau_therm *therm, int percent) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fanpwm_priv *priv = (void *)tpriv->fan; + int card_type = nv_device(therm)->card_type; + u32 divs, duty; + int ret; + + divs = priv->base.perf.pwm_divisor; + if (priv->base.bios.pwm_freq) { + divs = 1; + if (therm->pwm_clock) + divs = therm->pwm_clock(therm); + divs /= priv->base.bios.pwm_freq; + } + + duty = ((divs * percent) + 99) / 100; + if (card_type <= NV_40 || (priv->func.log[0] & 1)) + duty = divs - duty; + + ret = therm->pwm_set(therm, priv->func.line, divs, duty); + if (ret == 0) + ret = therm->pwm_ctrl(therm, priv->func.line, true); + return ret; +} + +int +nouveau_fanpwm_create(struct nouveau_therm *therm, struct dcb_gpio_func *func) +{ + struct nouveau_device *device = nv_device(therm); + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fanpwm_priv *priv; + u32 divs, duty; + + if (!nouveau_boolopt(device->cfgopt, "NvFanPWM", func->param) || + !therm->pwm_ctrl || + therm->pwm_get(therm, func->line, &divs, &duty) == -ENODEV) + return -ENODEV; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + tpriv->fan = &priv->base; + if (!priv) + return -ENOMEM; + + priv->base.type = "PWM"; + priv->base.get = nouveau_fanpwm_get; + priv->base.set = nouveau_fanpwm_set; + priv->func = *func; + return 0; +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/fantog.c b/drivers/gpu/drm/nouveau/core/subdev/therm/fantog.c new file mode 100644 index 000000000000..e601773ee475 --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/fantog.c @@ -0,0 +1,115 @@ +/* + * Copyright 2012 The Nouveau community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres + */ + +#include "priv.h" + +#include <core/object.h> +#include <core/device.h> + +#include <subdev/gpio.h> +#include <subdev/timer.h> + +struct nouveau_fantog_priv { + struct nouveau_fan base; + struct nouveau_alarm alarm; + spinlock_t lock; + u32 period_us; + u32 percent; + struct dcb_gpio_func func; +}; + +static void +nouveau_fantog_update(struct nouveau_fantog_priv *priv, int percent) +{ + struct nouveau_therm_priv *tpriv = (void *)priv->base.parent; + struct nouveau_timer *ptimer = nouveau_timer(tpriv); + struct nouveau_gpio *gpio = nouveau_gpio(tpriv); + unsigned long flags; + int duty; + + spin_lock_irqsave(&priv->lock, flags); + if (percent < 0) + percent = priv->percent; + priv->percent = percent; + + duty = !gpio->get(gpio, 0, DCB_GPIO_FAN, 0xff); + gpio->set(gpio, 0, DCB_GPIO_FAN, 0xff, duty); + + if (list_empty(&priv->alarm.head) && percent != (duty * 100)) { + u64 next_change = (percent * priv->period_us) / 100; + if (!duty) + next_change = priv->period_us - next_change; + ptimer->alarm(ptimer, next_change * 1000, &priv->alarm); + } + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void +nouveau_fantog_alarm(struct nouveau_alarm *alarm) +{ + struct nouveau_fantog_priv *priv = + container_of(alarm, struct nouveau_fantog_priv, alarm); + nouveau_fantog_update(priv, -1); +} + +static int +nouveau_fantog_get(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fantog_priv *priv = (void *)tpriv->fan; + return priv->percent; +} + +static int +nouveau_fantog_set(struct nouveau_therm *therm, int percent) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fantog_priv *priv = (void *)tpriv->fan; + if (therm->pwm_ctrl) + therm->pwm_ctrl(therm, priv->func.line, false); + nouveau_fantog_update(priv, percent); + return 0; +} + +int +nouveau_fantog_create(struct nouveau_therm *therm, struct dcb_gpio_func *func) +{ + struct nouveau_therm_priv *tpriv = (void *)therm; + struct nouveau_fantog_priv *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + tpriv->fan = &priv->base; + if (!priv) + return -ENOMEM; + + priv->base.type = "toggle"; + priv->base.get = nouveau_fantog_get; + priv->base.set = nouveau_fantog_set; + nouveau_alarm_init(&priv->alarm, nouveau_fantog_alarm); + priv->period_us = 100000; /* 10Hz */ + priv->percent = 100; + priv->func = *func; + spin_lock_init(&priv->lock); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/ic.c b/drivers/gpu/drm/nouveau/core/subdev/therm/ic.c index e512ff0aae60..e24090bac195 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/ic.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/ic.c @@ -31,7 +31,7 @@ static bool probe_monitoring_device(struct nouveau_i2c_port *i2c, struct i2c_board_info *info) { - struct nouveau_therm_priv *priv = (void *)nouveau_therm(i2c->i2c); + struct nouveau_therm_priv *priv = (void *)nouveau_therm(i2c); struct i2c_client *client; request_module("%s%s", I2C_MODULE_PREFIX, info->type); @@ -53,6 +53,31 @@ probe_monitoring_device(struct nouveau_i2c_port *i2c, return true; } +static struct i2c_board_info +nv_board_infos[] = { + { I2C_BOARD_INFO("w83l785ts", 0x2d) }, + { I2C_BOARD_INFO("w83781d", 0x2d) }, + { I2C_BOARD_INFO("adt7473", 0x2e) }, + { I2C_BOARD_INFO("adt7473", 0x2d) }, + { I2C_BOARD_INFO("adt7473", 0x2c) }, + { I2C_BOARD_INFO("f75375", 0x2e) }, + { I2C_BOARD_INFO("lm99", 0x4c) }, + { I2C_BOARD_INFO("lm90", 0x4c) }, + { I2C_BOARD_INFO("lm90", 0x4d) }, + { I2C_BOARD_INFO("adm1021", 0x18) }, + { I2C_BOARD_INFO("adm1021", 0x19) }, + { I2C_BOARD_INFO("adm1021", 0x1a) }, + { I2C_BOARD_INFO("adm1021", 0x29) }, + { I2C_BOARD_INFO("adm1021", 0x2a) }, + { I2C_BOARD_INFO("adm1021", 0x2b) }, + { I2C_BOARD_INFO("adm1021", 0x4c) }, + { I2C_BOARD_INFO("adm1021", 0x4d) }, + { I2C_BOARD_INFO("adm1021", 0x4e) }, + { I2C_BOARD_INFO("lm63", 0x18) }, + { I2C_BOARD_INFO("lm63", 0x4e) }, + { } +}; + void nouveau_therm_ic_ctor(struct nouveau_therm *therm) { @@ -60,29 +85,6 @@ nouveau_therm_ic_ctor(struct nouveau_therm *therm) struct nouveau_bios *bios = nouveau_bios(therm); struct nouveau_i2c *i2c = nouveau_i2c(therm); struct nvbios_extdev_func extdev_entry; - struct i2c_board_info info[] = { - { I2C_BOARD_INFO("w83l785ts", 0x2d) }, - { I2C_BOARD_INFO("w83781d", 0x2d) }, - { I2C_BOARD_INFO("adt7473", 0x2e) }, - { I2C_BOARD_INFO("adt7473", 0x2d) }, - { I2C_BOARD_INFO("adt7473", 0x2c) }, - { I2C_BOARD_INFO("f75375", 0x2e) }, - { I2C_BOARD_INFO("lm99", 0x4c) }, - { I2C_BOARD_INFO("lm90", 0x4c) }, - { I2C_BOARD_INFO("lm90", 0x4d) }, - { I2C_BOARD_INFO("adm1021", 0x18) }, - { I2C_BOARD_INFO("adm1021", 0x19) }, - { I2C_BOARD_INFO("adm1021", 0x1a) }, - { I2C_BOARD_INFO("adm1021", 0x29) }, - { I2C_BOARD_INFO("adm1021", 0x2a) }, - { I2C_BOARD_INFO("adm1021", 0x2b) }, - { I2C_BOARD_INFO("adm1021", 0x4c) }, - { I2C_BOARD_INFO("adm1021", 0x4d) }, - { I2C_BOARD_INFO("adm1021", 0x4e) }, - { I2C_BOARD_INFO("lm63", 0x18) }, - { I2C_BOARD_INFO("lm63", 0x4e) }, - { } - }; if (!nvbios_extdev_find(bios, NVBIOS_EXTDEV_LM89, &extdev_entry)) { struct i2c_board_info board[] = { @@ -111,6 +113,6 @@ nouveau_therm_ic_ctor(struct nouveau_therm *therm) /* The vbios doesn't provide the address of an exisiting monitoring device. Let's try our static list. */ - i2c->identify(i2c, NV_I2C_DEFAULT(0), "monitoring device", info, - probe_monitoring_device); + i2c->identify(i2c, NV_I2C_DEFAULT(0), "monitoring device", + nv_board_infos, probe_monitoring_device); } diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c index fcf2cfe731d6..0f5363edb964 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c @@ -25,6 +25,10 @@ #include "priv.h" +struct nv40_therm_priv { + struct nouveau_therm_priv base; +}; + static int nv40_sensor_setup(struct nouveau_therm *therm) { @@ -34,6 +38,7 @@ nv40_sensor_setup(struct nouveau_therm *therm) if (device->chipset >= 0x46) { nv_mask(therm, 0x15b8, 0x80000000, 0); nv_wr32(therm, 0x15b0, 0x80003fff); + mdelay(10); /* wait for the temperature to stabilize */ return nv_rd32(therm, 0x15b4) & 0x3fff; } else { nv_wr32(therm, 0x15b0, 0xff); @@ -75,7 +80,20 @@ nv40_temp_get(struct nouveau_therm *therm) return core_temp; } -int +static int +nv40_fan_pwm_ctrl(struct nouveau_therm *therm, int line, bool enable) +{ + u32 mask = enable ? 0x80000000 : 0x0000000; + if (line == 2) nv_mask(therm, 0x0010f0, 0x80000000, mask); + else if (line == 9) nv_mask(therm, 0x0015f4, 0x80000000, mask); + else { + nv_error(therm, "unknown pwm ctrl for gpio %d\n", line); + return -ENODEV; + } + return 0; +} + +static int nv40_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty) { if (line == 2) { @@ -101,15 +119,15 @@ nv40_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty) return -EINVAL; } -int +static int nv40_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty) { if (line == 2) { - nv_wr32(therm, 0x0010f0, 0x80000000 | (duty << 16) | divs); + nv_mask(therm, 0x0010f0, 0x7fff7fff, (duty << 16) | divs); } else if (line == 9) { nv_wr32(therm, 0x0015f8, divs); - nv_wr32(therm, 0x0015f4, duty | 0x80000000); + nv_mask(therm, 0x0015f4, 0x7fffffff, duty); } else { nv_error(therm, "unknown pwm ctrl for gpio %d\n", line); return -ENODEV; @@ -118,37 +136,51 @@ nv40_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty) return 0; } +static void +nv40_therm_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_therm *therm = nouveau_therm(subdev); + uint32_t stat = nv_rd32(therm, 0x1100); + + /* traitement */ + + /* ack all IRQs */ + nv_wr32(therm, 0x1100, 0x70000); + + nv_error(therm, "THERM received an IRQ: stat = %x\n", stat); +} + static int nv40_therm_ctor(struct nouveau_object *parent, - struct nouveau_object *engine, - struct nouveau_oclass *oclass, void *data, u32 size, - struct nouveau_object **pobject) + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) { - struct nouveau_therm_priv *priv; - struct nouveau_therm *therm; + struct nv40_therm_priv *priv; int ret; ret = nouveau_therm_create(parent, engine, oclass, &priv); *pobject = nv_object(priv); - therm = (void *) priv; if (ret) return ret; - nouveau_therm_ic_ctor(therm); - nouveau_therm_sensor_ctor(therm); - nouveau_therm_fan_ctor(therm); + priv->base.base.pwm_ctrl = nv40_fan_pwm_ctrl; + priv->base.base.pwm_get = nv40_fan_pwm_get; + priv->base.base.pwm_set = nv40_fan_pwm_set; + priv->base.base.temp_get = nv40_temp_get; + priv->base.sensor.program_alarms = nouveau_therm_program_alarms_polling; + nv_subdev(priv)->intr = nv40_therm_intr; + return nouveau_therm_preinit(&priv->base.base); +} - priv->fan.pwm_get = nv40_fan_pwm_get; - priv->fan.pwm_set = nv40_fan_pwm_set; +static int +nv40_therm_init(struct nouveau_object *object) +{ + struct nouveau_therm *therm = (void *)object; - therm->temp_get = nv40_temp_get; - therm->fan_get = nouveau_therm_fan_user_get; - therm->fan_set = nouveau_therm_fan_user_set; - therm->fan_sense = nouveau_therm_fan_sense; - therm->attr_get = nouveau_therm_attr_get; - therm->attr_set = nouveau_therm_attr_set; + nv40_sensor_setup(therm); - return 0; + return _nouveau_therm_init(object); } struct nouveau_oclass @@ -157,7 +189,7 @@ nv40_therm_oclass = { .ofuncs = &(struct nouveau_ofuncs) { .ctor = nv40_therm_ctor, .dtor = _nouveau_therm_dtor, - .init = nouveau_therm_init, - .fini = nouveau_therm_fini, + .init = nv40_therm_init, + .fini = _nouveau_therm_fini, }, -};
\ No newline at end of file +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c index 9360ddd469e7..86632cbd65ce 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c @@ -25,6 +25,10 @@ #include "priv.h" +struct nv50_therm_priv { + struct nouveau_therm_priv base; +}; + static int pwm_info(struct nouveau_therm *therm, int *line, int *ctrl, int *indx) { @@ -51,6 +55,16 @@ pwm_info(struct nouveau_therm *therm, int *line, int *ctrl, int *indx) } int +nv50_fan_pwm_ctrl(struct nouveau_therm *therm, int line, bool enable) +{ + u32 data = enable ? 0x00000001 : 0x00000000; + int ctrl, id, ret = pwm_info(therm, &line, &ctrl, &id); + if (ret == 0) + nv_mask(therm, ctrl, 0x00010001 << line, data << line); + return ret; +} + +int nv50_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty) { int ctrl, id, ret = pwm_info(therm, &line, &ctrl, &id); @@ -73,7 +87,6 @@ nv50_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty) if (ret) return ret; - nv_mask(therm, ctrl, 0x00010001 << line, 0x00000001 << line); nv_wr32(therm, 0x00e114 + (id * 8), divs); nv_wr32(therm, 0x00e118 + (id * 8), duty | 0x80000000); return 0; @@ -111,38 +124,178 @@ nv50_temp_get(struct nouveau_therm *therm) return nv_rd32(therm, 0x20400); } +static void +nv50_therm_program_alarms(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + unsigned long flags; + + spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); + + /* enable RISING and FALLING IRQs for shutdown, THRS 0, 1, 2 and 4 */ + nv_wr32(therm, 0x20000, 0x000003ff); + + /* shutdown: The computer should be shutdown when reached */ + nv_wr32(therm, 0x20484, sensor->thrs_shutdown.hysteresis); + nv_wr32(therm, 0x20480, sensor->thrs_shutdown.temp); + + /* THRS_1 : fan boost*/ + nv_wr32(therm, 0x204c4, sensor->thrs_fan_boost.temp); + + /* THRS_2 : critical */ + nv_wr32(therm, 0x204c0, sensor->thrs_critical.temp); + + /* THRS_4 : down clock */ + nv_wr32(therm, 0x20414, sensor->thrs_down_clock.temp); + spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); + + nv_info(therm, + "Programmed thresholds [ %d(%d), %d(%d), %d(%d), %d(%d) ]\n", + sensor->thrs_fan_boost.temp, sensor->thrs_fan_boost.hysteresis, + sensor->thrs_down_clock.temp, + sensor->thrs_down_clock.hysteresis, + sensor->thrs_critical.temp, sensor->thrs_critical.hysteresis, + sensor->thrs_shutdown.temp, sensor->thrs_shutdown.hysteresis); + +} + +/* must be called with alarm_program_lock taken ! */ +static void +nv50_therm_threshold_hyst_emulation(struct nouveau_therm *therm, + uint32_t thrs_reg, u8 status_bit, + const struct nvbios_therm_threshold *thrs, + enum nouveau_therm_thrs thrs_name) +{ + enum nouveau_therm_thrs_direction direction; + enum nouveau_therm_thrs_state prev_state, new_state; + int temp, cur; + + prev_state = nouveau_therm_sensor_get_threshold_state(therm, thrs_name); + temp = nv_rd32(therm, thrs_reg); + + /* program the next threshold */ + if (temp == thrs->temp) { + nv_wr32(therm, thrs_reg, thrs->temp - thrs->hysteresis); + new_state = NOUVEAU_THERM_THRS_HIGHER; + } else { + nv_wr32(therm, thrs_reg, thrs->temp); + new_state = NOUVEAU_THERM_THRS_LOWER; + } + + /* fix the state (in case someone reprogrammed the alarms) */ + cur = therm->temp_get(therm); + if (new_state == NOUVEAU_THERM_THRS_LOWER && cur > thrs->temp) + new_state = NOUVEAU_THERM_THRS_HIGHER; + else if (new_state == NOUVEAU_THERM_THRS_HIGHER && + cur < thrs->temp - thrs->hysteresis) + new_state = NOUVEAU_THERM_THRS_LOWER; + nouveau_therm_sensor_set_threshold_state(therm, thrs_name, new_state); + + /* find the direction */ + if (prev_state < new_state) + direction = NOUVEAU_THERM_THRS_RISING; + else if (prev_state > new_state) + direction = NOUVEAU_THERM_THRS_FALLING; + else + return; + + /* advertise a change in direction */ + nouveau_therm_sensor_event(therm, thrs_name, direction); +} + +static void +nv50_therm_intr(struct nouveau_subdev *subdev) +{ + struct nouveau_therm *therm = nouveau_therm(subdev); + struct nouveau_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + unsigned long flags; + uint32_t intr; + + spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); + + intr = nv_rd32(therm, 0x20100); + + /* THRS_4: downclock */ + if (intr & 0x002) { + nv50_therm_threshold_hyst_emulation(therm, 0x20414, 24, + &sensor->thrs_down_clock, + NOUVEAU_THERM_THRS_DOWNCLOCK); + intr &= ~0x002; + } + + /* shutdown */ + if (intr & 0x004) { + nv50_therm_threshold_hyst_emulation(therm, 0x20480, 20, + &sensor->thrs_shutdown, + NOUVEAU_THERM_THRS_SHUTDOWN); + intr &= ~0x004; + } + + /* THRS_1 : fan boost */ + if (intr & 0x008) { + nv50_therm_threshold_hyst_emulation(therm, 0x204c4, 21, + &sensor->thrs_fan_boost, + NOUVEAU_THERM_THRS_FANBOOST); + intr &= ~0x008; + } + + /* THRS_2 : critical */ + if (intr & 0x010) { + nv50_therm_threshold_hyst_emulation(therm, 0x204c0, 22, + &sensor->thrs_critical, + NOUVEAU_THERM_THRS_CRITICAL); + intr &= ~0x010; + } + + if (intr) + nv_error(therm, "unhandled intr 0x%08x\n", intr); + + /* ACK everything */ + nv_wr32(therm, 0x20100, 0xffffffff); + nv_wr32(therm, 0x1100, 0x10000); /* PBUS */ + + spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); +} + static int nv50_therm_ctor(struct nouveau_object *parent, - struct nouveau_object *engine, - struct nouveau_oclass *oclass, void *data, u32 size, - struct nouveau_object **pobject) + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) { - struct nouveau_therm_priv *priv; - struct nouveau_therm *therm; + struct nv50_therm_priv *priv; int ret; ret = nouveau_therm_create(parent, engine, oclass, &priv); *pobject = nv_object(priv); - therm = (void *) priv; if (ret) return ret; - nouveau_therm_ic_ctor(therm); - nouveau_therm_sensor_ctor(therm); - nouveau_therm_fan_ctor(therm); + priv->base.base.pwm_ctrl = nv50_fan_pwm_ctrl; + priv->base.base.pwm_get = nv50_fan_pwm_get; + priv->base.base.pwm_set = nv50_fan_pwm_set; + priv->base.base.pwm_clock = nv50_fan_pwm_clock; + priv->base.base.temp_get = nv50_temp_get; + priv->base.sensor.program_alarms = nv50_therm_program_alarms; + nv_subdev(priv)->intr = nv50_therm_intr; - priv->fan.pwm_get = nv50_fan_pwm_get; - priv->fan.pwm_set = nv50_fan_pwm_set; - priv->fan.pwm_clock = nv50_fan_pwm_clock; + /* init the thresholds */ + nouveau_therm_sensor_set_threshold_state(&priv->base.base, + NOUVEAU_THERM_THRS_SHUTDOWN, + NOUVEAU_THERM_THRS_LOWER); + nouveau_therm_sensor_set_threshold_state(&priv->base.base, + NOUVEAU_THERM_THRS_FANBOOST, + NOUVEAU_THERM_THRS_LOWER); + nouveau_therm_sensor_set_threshold_state(&priv->base.base, + NOUVEAU_THERM_THRS_CRITICAL, + NOUVEAU_THERM_THRS_LOWER); + nouveau_therm_sensor_set_threshold_state(&priv->base.base, + NOUVEAU_THERM_THRS_DOWNCLOCK, + NOUVEAU_THERM_THRS_LOWER); - therm->temp_get = nv50_temp_get; - therm->fan_get = nouveau_therm_fan_user_get; - therm->fan_set = nouveau_therm_fan_user_set; - therm->fan_sense = nouveau_therm_fan_sense; - therm->attr_get = nouveau_therm_attr_get; - therm->attr_set = nouveau_therm_attr_set; - - return 0; + return nouveau_therm_preinit(&priv->base.base); } struct nouveau_oclass @@ -151,7 +304,7 @@ nv50_therm_oclass = { .ofuncs = &(struct nouveau_ofuncs) { .ctor = nv50_therm_ctor, .dtor = _nouveau_therm_dtor, - .init = nouveau_therm_init, - .fini = nouveau_therm_fini, + .init = _nouveau_therm_init, + .fini = _nouveau_therm_fini, }, }; diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c new file mode 100644 index 000000000000..2dcc5437116a --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c @@ -0,0 +1,99 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <subdev/gpio.h> + +#include "priv.h" + +struct nva3_therm_priv { + struct nouveau_therm_priv base; +}; + +int +nva3_therm_fan_sense(struct nouveau_therm *therm) +{ + u32 tach = nv_rd32(therm, 0x00e728) & 0x0000ffff; + u32 ctrl = nv_rd32(therm, 0x00e720); + if (ctrl & 0x00000001) + return tach * 60; + return -ENODEV; +} + +static int +nva3_therm_init(struct nouveau_object *object) +{ + struct nva3_therm_priv *priv = (void *)object; + struct dcb_gpio_func *tach = &priv->base.fan->tach; + int ret; + + ret = nouveau_therm_init(&priv->base.base); + if (ret) + return ret; + + /* enable fan tach, count revolutions per-second */ + nv_mask(priv, 0x00e720, 0x00000003, 0x00000002); + if (tach->func != DCB_GPIO_UNUSED) { + nv_wr32(priv, 0x00e724, nv_device(priv)->crystal * 1000); + nv_mask(priv, 0x00e720, 0x001f0000, tach->line << 16); + nv_mask(priv, 0x00e720, 0x00000001, 0x00000001); + } + nv_mask(priv, 0x00e720, 0x00000002, 0x00000000); + + return 0; +} + +static int +nva3_therm_ctor(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nva3_therm_priv *priv; + int ret; + + ret = nouveau_therm_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.base.pwm_ctrl = nv50_fan_pwm_ctrl; + priv->base.base.pwm_get = nv50_fan_pwm_get; + priv->base.base.pwm_set = nv50_fan_pwm_set; + priv->base.base.pwm_clock = nv50_fan_pwm_clock; + priv->base.base.temp_get = nv50_temp_get; + priv->base.base.fan_sense = nva3_therm_fan_sense; + priv->base.sensor.program_alarms = nouveau_therm_program_alarms_polling; + return nouveau_therm_preinit(&priv->base.base); +} + +struct nouveau_oclass +nva3_therm_oclass = { + .handle = NV_SUBDEV(THERM, 0xa3), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nva3_therm_ctor, + .dtor = _nouveau_therm_dtor, + .init = nva3_therm_init, + .fini = _nouveau_therm_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c new file mode 100644 index 000000000000..d7d30ee8332e --- /dev/null +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c @@ -0,0 +1,153 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "priv.h" + +struct nvd0_therm_priv { + struct nouveau_therm_priv base; +}; + +static int +pwm_info(struct nouveau_therm *therm, int line) +{ + u32 gpio = nv_rd32(therm, 0x00d610 + (line * 0x04)); + switch (gpio & 0x000000c0) { + case 0x00000000: /* normal mode, possibly pwm forced off by us */ + case 0x00000040: /* nvio special */ + switch (gpio & 0x0000001f) { + case 0x19: return 1; + case 0x1c: return 0; + default: + break; + } + default: + break; + } + + nv_error(therm, "GPIO %d unknown PWM: 0x%08x\n", line, gpio); + return -ENODEV; +} + +static int +nvd0_fan_pwm_ctrl(struct nouveau_therm *therm, int line, bool enable) +{ + u32 data = enable ? 0x00000040 : 0x00000000; + int indx = pwm_info(therm, line); + if (indx < 0) + return indx; + + nv_mask(therm, 0x00d610 + (line * 0x04), 0x000000c0, data); + return 0; +} + +static int +nvd0_fan_pwm_get(struct nouveau_therm *therm, int line, u32 *divs, u32 *duty) +{ + int indx = pwm_info(therm, line); + if (indx < 0) + return indx; + + if (nv_rd32(therm, 0x00d610 + (line * 0x04)) & 0x00000040) { + *divs = nv_rd32(therm, 0x00e114 + (indx * 8)); + *duty = nv_rd32(therm, 0x00e118 + (indx * 8)); + return 0; + } + + return -EINVAL; +} + +static int +nvd0_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty) +{ + int indx = pwm_info(therm, line); + if (indx < 0) + return indx; + + nv_wr32(therm, 0x00e114 + (indx * 8), divs); + nv_wr32(therm, 0x00e118 + (indx * 8), duty | 0x80000000); + return 0; +} + +static int +nvd0_fan_pwm_clock(struct nouveau_therm *therm) +{ + return (nv_device(therm)->crystal * 1000) / 20; +} + +static int +nvd0_therm_init(struct nouveau_object *object) +{ + struct nvd0_therm_priv *priv = (void *)object; + int ret; + + ret = nouveau_therm_init(&priv->base.base); + if (ret) + return ret; + + /* enable fan tach, count revolutions per-second */ + nv_mask(priv, 0x00e720, 0x00000003, 0x00000002); + if (priv->base.fan->tach.func != DCB_GPIO_UNUSED) { + nv_mask(priv, 0x00d79c, 0x000000ff, priv->base.fan->tach.line); + nv_wr32(priv, 0x00e724, nv_device(priv)->crystal * 1000); + nv_mask(priv, 0x00e720, 0x00000001, 0x00000001); + } + nv_mask(priv, 0x00e720, 0x00000002, 0x00000000); + + return 0; +} + +static int +nvd0_therm_ctor(struct nouveau_object *parent, + struct nouveau_object *engine, + struct nouveau_oclass *oclass, void *data, u32 size, + struct nouveau_object **pobject) +{ + struct nvd0_therm_priv *priv; + int ret; + + ret = nouveau_therm_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.base.pwm_ctrl = nvd0_fan_pwm_ctrl; + priv->base.base.pwm_get = nvd0_fan_pwm_get; + priv->base.base.pwm_set = nvd0_fan_pwm_set; + priv->base.base.pwm_clock = nvd0_fan_pwm_clock; + priv->base.base.temp_get = nv50_temp_get; + priv->base.base.fan_sense = nva3_therm_fan_sense; + priv->base.sensor.program_alarms = nouveau_therm_program_alarms_polling; + return nouveau_therm_preinit(&priv->base.base); +} + +struct nouveau_oclass +nvd0_therm_oclass = { + .handle = NV_SUBDEV(THERM, 0xd0), + .ofuncs = &(struct nouveau_ofuncs) { + .ctor = nvd0_therm_ctor, + .dtor = _nouveau_therm_dtor, + .init = nvd0_therm_init, + .fini = _nouveau_therm_fini, + }, +}; diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h b/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h index 1c3cd6abc36e..06b98706b3fc 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h @@ -1,3 +1,6 @@ +#ifndef __NVTHERM_PRIV_H__ +#define __NVTHERM_PRIV_H__ + /* * Copyright 2012 The Nouveau community * @@ -25,33 +28,81 @@ #include <subdev/therm.h> #include <subdev/bios/extdev.h> +#include <subdev/bios/gpio.h> #include <subdev/bios/perf.h> #include <subdev/bios/therm.h> +#include <subdev/timer.h> + +struct nouveau_fan { + struct nouveau_therm *parent; + const char *type; + + struct nvbios_therm_fan bios; + struct nvbios_perf_fan perf; + + struct nouveau_alarm alarm; + spinlock_t lock; + int percent; + + int (*get)(struct nouveau_therm *therm); + int (*set)(struct nouveau_therm *therm, int percent); + + struct dcb_gpio_func tach; +}; + +enum nouveau_therm_thrs_direction { + NOUVEAU_THERM_THRS_FALLING = 0, + NOUVEAU_THERM_THRS_RISING = 1 +}; + +enum nouveau_therm_thrs_state { + NOUVEAU_THERM_THRS_LOWER = 0, + NOUVEAU_THERM_THRS_HIGHER = 1 +}; + +enum nouveau_therm_thrs { + NOUVEAU_THERM_THRS_FANBOOST = 0, + NOUVEAU_THERM_THRS_DOWNCLOCK = 1, + NOUVEAU_THERM_THRS_CRITICAL = 2, + NOUVEAU_THERM_THRS_SHUTDOWN = 3, + NOUVEAU_THERM_THRS_NR +}; struct nouveau_therm_priv { struct nouveau_therm base; + /* automatic thermal management */ + struct nouveau_alarm alarm; + spinlock_t lock; + struct nouveau_therm_trip_point *last_trip; + int mode; + int suspend; + /* bios */ struct nvbios_therm_sensor bios_sensor; - struct nvbios_therm_fan bios_fan; - struct nvbios_perf_fan bios_perf_fan; /* fan priv */ + struct nouveau_fan *fan; + + /* alarms priv */ struct { - enum nouveau_therm_fan_mode mode; - int percent; + spinlock_t alarm_program_lock; + struct nouveau_alarm therm_poll_alarm; + enum nouveau_therm_thrs_state alarm_state[NOUVEAU_THERM_THRS_NR]; + void (*program_alarms)(struct nouveau_therm *); + } sensor; - int (*pwm_get)(struct nouveau_therm *, int line, u32*, u32*); - int (*pwm_set)(struct nouveau_therm *, int line, u32, u32); - int (*pwm_clock)(struct nouveau_therm *); - } fan; + /* what should be done if the card overheats */ + struct { + void (*downclock)(struct nouveau_therm *, bool active); + void (*pause)(struct nouveau_therm *, bool active); + } emergency; /* ic */ struct i2c_client *ic; }; -int nouveau_therm_init(struct nouveau_object *object); -int nouveau_therm_fini(struct nouveau_object *object, bool suspend); +int nouveau_therm_mode(struct nouveau_therm *therm, int mode); int nouveau_therm_attr_get(struct nouveau_therm *therm, enum nouveau_therm_attr_type type); int nouveau_therm_attr_set(struct nouveau_therm *therm, @@ -63,11 +114,35 @@ int nouveau_therm_sensor_ctor(struct nouveau_therm *therm); int nouveau_therm_fan_ctor(struct nouveau_therm *therm); int nouveau_therm_fan_get(struct nouveau_therm *therm); -int nouveau_therm_fan_set(struct nouveau_therm *therm, int percent); +int nouveau_therm_fan_set(struct nouveau_therm *therm, bool now, int percent); int nouveau_therm_fan_user_get(struct nouveau_therm *therm); int nouveau_therm_fan_user_set(struct nouveau_therm *therm, int percent); -int nouveau_therm_fan_set_mode(struct nouveau_therm *therm, - enum nouveau_therm_fan_mode mode); - int nouveau_therm_fan_sense(struct nouveau_therm *therm); + +int nouveau_therm_preinit(struct nouveau_therm *); + +void nouveau_therm_sensor_set_threshold_state(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs, + enum nouveau_therm_thrs_state st); +enum nouveau_therm_thrs_state +nouveau_therm_sensor_get_threshold_state(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs); +void nouveau_therm_sensor_event(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs, + enum nouveau_therm_thrs_direction dir); +void nouveau_therm_program_alarms_polling(struct nouveau_therm *therm); + +int nv50_fan_pwm_ctrl(struct nouveau_therm *, int, bool); +int nv50_fan_pwm_get(struct nouveau_therm *, int, u32 *, u32 *); +int nv50_fan_pwm_set(struct nouveau_therm *, int, u32, u32); +int nv50_fan_pwm_clock(struct nouveau_therm *); +int nv50_temp_get(struct nouveau_therm *therm); + +int nva3_therm_fan_sense(struct nouveau_therm *); + +int nouveau_fanpwm_create(struct nouveau_therm *, struct dcb_gpio_func *); +int nouveau_fantog_create(struct nouveau_therm *, struct dcb_gpio_func *); +int nouveau_fannil_create(struct nouveau_therm *); + +#endif diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c b/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c index 204282301fb1..b37624af8297 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c @@ -58,11 +58,171 @@ static void nouveau_therm_temp_safety_checks(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *s = &priv->bios_sensor; if (!priv->bios_sensor.slope_div) priv->bios_sensor.slope_div = 1; if (!priv->bios_sensor.offset_den) priv->bios_sensor.offset_den = 1; + + /* enforce a minimum hysteresis on thresholds */ + s->thrs_fan_boost.hysteresis = max_t(u8, s->thrs_fan_boost.hysteresis, 2); + s->thrs_down_clock.hysteresis = max_t(u8, s->thrs_down_clock.hysteresis, 2); + s->thrs_critical.hysteresis = max_t(u8, s->thrs_critical.hysteresis, 2); + s->thrs_shutdown.hysteresis = max_t(u8, s->thrs_shutdown.hysteresis, 2); +} + +/* must be called with alarm_program_lock taken ! */ +void nouveau_therm_sensor_set_threshold_state(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs, + enum nouveau_therm_thrs_state st) +{ + struct nouveau_therm_priv *priv = (void *)therm; + priv->sensor.alarm_state[thrs] = st; +} + +/* must be called with alarm_program_lock taken ! */ +enum nouveau_therm_thrs_state +nouveau_therm_sensor_get_threshold_state(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs) +{ + struct nouveau_therm_priv *priv = (void *)therm; + return priv->sensor.alarm_state[thrs]; +} + +static void +nv_poweroff_work(struct work_struct *work) +{ + orderly_poweroff(true); + kfree(work); +} + +void nouveau_therm_sensor_event(struct nouveau_therm *therm, + enum nouveau_therm_thrs thrs, + enum nouveau_therm_thrs_direction dir) +{ + struct nouveau_therm_priv *priv = (void *)therm; + bool active; + const char *thresolds[] = { + "fanboost", "downclock", "critical", "shutdown" + }; + uint8_t temperature = therm->temp_get(therm); + + if (thrs < 0 || thrs > 3) + return; + + if (dir == NOUVEAU_THERM_THRS_FALLING) + nv_info(therm, "temperature (%u C) went below the '%s' threshold\n", + temperature, thresolds[thrs]); + else + nv_info(therm, "temperature (%u C) hit the '%s' threshold\n", + temperature, thresolds[thrs]); + + active = (dir == NOUVEAU_THERM_THRS_RISING); + switch (thrs) { + case NOUVEAU_THERM_THRS_FANBOOST: + if (active) { + nouveau_therm_fan_set(therm, true, 100); + nouveau_therm_mode(therm, NOUVEAU_THERM_CTRL_AUTO); + } + break; + case NOUVEAU_THERM_THRS_DOWNCLOCK: + if (priv->emergency.downclock) + priv->emergency.downclock(therm, active); + break; + case NOUVEAU_THERM_THRS_CRITICAL: + if (priv->emergency.pause) + priv->emergency.pause(therm, active); + break; + case NOUVEAU_THERM_THRS_SHUTDOWN: + if (active) { + struct work_struct *work; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK(work, nv_poweroff_work); + schedule_work(work); + } + } + break; + case NOUVEAU_THERM_THRS_NR: + break; + } + +} + +/* must be called with alarm_program_lock taken ! */ +static void +nouveau_therm_threshold_hyst_polling(struct nouveau_therm *therm, + const struct nvbios_therm_threshold *thrs, + enum nouveau_therm_thrs thrs_name) +{ + enum nouveau_therm_thrs_direction direction; + enum nouveau_therm_thrs_state prev_state, new_state; + int temp = therm->temp_get(therm); + + prev_state = nouveau_therm_sensor_get_threshold_state(therm, thrs_name); + + if (temp >= thrs->temp && prev_state == NOUVEAU_THERM_THRS_LOWER) { + direction = NOUVEAU_THERM_THRS_RISING; + new_state = NOUVEAU_THERM_THRS_HIGHER; + } else if (temp <= thrs->temp - thrs->hysteresis && + prev_state == NOUVEAU_THERM_THRS_HIGHER) { + direction = NOUVEAU_THERM_THRS_FALLING; + new_state = NOUVEAU_THERM_THRS_LOWER; + } else + return; /* nothing to do */ + + nouveau_therm_sensor_set_threshold_state(therm, thrs_name, new_state); + nouveau_therm_sensor_event(therm, thrs_name, direction); +} + +static void +alarm_timer_callback(struct nouveau_alarm *alarm) +{ + struct nouveau_therm_priv *priv = + container_of(alarm, struct nouveau_therm_priv, sensor.therm_poll_alarm); + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + struct nouveau_timer *ptimer = nouveau_timer(priv); + struct nouveau_therm *therm = &priv->base; + unsigned long flags; + + spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); + + nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_fan_boost, + NOUVEAU_THERM_THRS_FANBOOST); + + nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_down_clock, + NOUVEAU_THERM_THRS_DOWNCLOCK); + + nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_critical, + NOUVEAU_THERM_THRS_CRITICAL); + + nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_shutdown, + NOUVEAU_THERM_THRS_SHUTDOWN); + + /* schedule the next poll in one second */ + if (list_empty(&alarm->head)) + ptimer->alarm(ptimer, 1000 * 1000 * 1000, alarm); + + spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); +} + +void +nouveau_therm_program_alarms_polling(struct nouveau_therm *therm) +{ + struct nouveau_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + + nv_info(therm, + "programmed thresholds [ %d(%d), %d(%d), %d(%d), %d(%d) ]\n", + sensor->thrs_fan_boost.temp, sensor->thrs_fan_boost.hysteresis, + sensor->thrs_down_clock.temp, + sensor->thrs_down_clock.hysteresis, + sensor->thrs_critical.temp, sensor->thrs_critical.hysteresis, + sensor->thrs_shutdown.temp, sensor->thrs_shutdown.hysteresis); + + alarm_timer_callback(&priv->sensor.therm_poll_alarm); } int @@ -71,6 +231,8 @@ nouveau_therm_sensor_ctor(struct nouveau_therm *therm) struct nouveau_therm_priv *priv = (void *)therm; struct nouveau_bios *bios = nouveau_bios(therm); + nouveau_alarm_init(&priv->sensor.therm_poll_alarm, alarm_timer_callback); + nouveau_therm_temp_set_defaults(therm); if (nvbios_therm_sensor_parse(bios, NVBIOS_THERM_DOMAIN_CORE, &priv->bios_sensor)) diff --git a/drivers/gpu/drm/nouveau/core/subdev/timer/nv04.c b/drivers/gpu/drm/nouveau/core/subdev/timer/nv04.c index c26ca9bef671..8e1bae4f12e8 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/timer/nv04.c +++ b/drivers/gpu/drm/nouveau/core/subdev/timer/nv04.c @@ -79,7 +79,7 @@ nv04_timer_alarm_trigger(struct nouveau_timer *ptimer) /* execute any pending alarm handlers */ list_for_each_entry_safe(alarm, atemp, &exec, head) { - list_del(&alarm->head); + list_del_init(&alarm->head); alarm->func(alarm); } } diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.h b/drivers/gpu/drm/nouveau/nouveau_acpi.h index d0da230d7706..74acf0f87785 100644 --- a/drivers/gpu/drm/nouveau/nouveau_acpi.h +++ b/drivers/gpu/drm/nouveau/nouveau_acpi.h @@ -3,7 +3,7 @@ #define ROM_BIOS_PAGE 4096 -#if defined(CONFIG_ACPI) +#if defined(CONFIG_ACPI) && defined(CONFIG_X86) bool nouveau_is_optimus(void); bool nouveau_is_v1_dsm(void); void nouveau_register_dsm_handler(void); diff --git a/drivers/gpu/drm/nouveau/nouveau_backlight.c b/drivers/gpu/drm/nouveau/nouveau_backlight.c index f65b20a375f6..5d940302d2aa 100644 --- a/drivers/gpu/drm/nouveau/nouveau_backlight.c +++ b/drivers/gpu/drm/nouveau/nouveau_backlight.c @@ -84,6 +84,8 @@ nv40_backlight_init(struct drm_connector *connector) props.max_brightness = 31; bd = backlight_device_register("nv_backlight", &connector->kdev, drm, &nv40_bl_ops, &props); + if (IS_ERR(bd)) + return PTR_ERR(bd); drm->backlight = bd; bd->props.brightness = nv40_get_intensity(bd); backlight_update_status(bd); diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.c b/drivers/gpu/drm/nouveau/nouveau_bios.c index 865eddfa30a7..50a6dd02f7c5 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bios.c +++ b/drivers/gpu/drm/nouveau/nouveau_bios.c @@ -678,23 +678,6 @@ int run_tmds_table(struct drm_device *dev, struct dcb_output *dcbent, int head, return 0; } -static void parse_bios_version(struct drm_device *dev, struct nvbios *bios, uint16_t offset) -{ - /* - * offset + 0 (8 bits): Micro version - * offset + 1 (8 bits): Minor version - * offset + 2 (8 bits): Chip version - * offset + 3 (8 bits): Major version - */ - struct nouveau_drm *drm = nouveau_drm(dev); - - bios->major_version = bios->data[offset + 3]; - bios->chip_version = bios->data[offset + 2]; - NV_INFO(drm, "Bios version %02x.%02x.%02x.%02x\n", - bios->data[offset + 3], bios->data[offset + 2], - bios->data[offset + 1], bios->data[offset]); -} - static void parse_script_table_pointers(struct nvbios *bios, uint16_t offset) { /* @@ -710,12 +693,6 @@ static void parse_script_table_pointers(struct nvbios *bios, uint16_t offset) */ bios->init_script_tbls_ptr = ROM16(bios->data[offset]); - bios->macro_index_tbl_ptr = ROM16(bios->data[offset + 2]); - bios->macro_tbl_ptr = ROM16(bios->data[offset + 4]); - bios->condition_tbl_ptr = ROM16(bios->data[offset + 6]); - bios->io_condition_tbl_ptr = ROM16(bios->data[offset + 8]); - bios->io_flag_condition_tbl_ptr = ROM16(bios->data[offset + 10]); - bios->init_function_tbl_ptr = ROM16(bios->data[offset + 12]); } static int parse_bit_A_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) @@ -765,25 +742,6 @@ static int parse_bit_A_tbl_entry(struct drm_device *dev, struct nvbios *bios, st return 0; } -static int parse_bit_C_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) -{ - /* - * offset + 8 (16 bits): PLL limits table pointer - * - * There's more in here, but that's unknown. - */ - struct nouveau_drm *drm = nouveau_drm(dev); - - if (bitentry->length < 10) { - NV_ERROR(drm, "Do not understand BIT C table\n"); - return -EINVAL; - } - - bios->pll_limit_tbl_ptr = ROM16(bios->data[bitentry->offset + 8]); - - return 0; -} - static int parse_bit_display_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) { /* @@ -821,12 +779,6 @@ static int parse_bit_init_tbl_entry(struct drm_device *dev, struct nvbios *bios, } parse_script_table_pointers(bios, bitentry->offset); - - if (bitentry->length >= 16) - bios->some_script_ptr = ROM16(bios->data[bitentry->offset + 14]); - if (bitentry->length >= 18) - bios->init96_tbl_ptr = ROM16(bios->data[bitentry->offset + 16]); - return 0; } @@ -852,8 +804,6 @@ static int parse_bit_i_tbl_entry(struct drm_device *dev, struct nvbios *bios, st return -EINVAL; } - parse_bios_version(dev, bios, bitentry->offset); - /* * bit 4 seems to indicate a mobile bios (doesn't suffer from BMP's * Quadro identity crisis), other bits possibly as for BMP feature byte @@ -1078,9 +1028,6 @@ parse_bit_structure(struct nvbios *bios, const uint16_t bitoffset) return ret; if (bios->major_version >= 0x60) /* g80+ */ parse_bit_table(bios, bitoffset, &BIT_TABLE('A', A)); - ret = parse_bit_table(bios, bitoffset, &BIT_TABLE('C', C)); - if (ret) - return ret; parse_bit_table(bios, bitoffset, &BIT_TABLE('D', display)); ret = parse_bit_table(bios, bitoffset, &BIT_TABLE('I', init)); if (ret) @@ -1228,8 +1175,6 @@ static int parse_bmp_structure(struct drm_device *dev, struct nvbios *bios, unsi */ bios->feature_byte = bmp[9]; - parse_bios_version(dev, bios, offset + 10); - if (bmp_version_major < 5 || bmp_version_minor < 0x10) bios->old_style_init = true; legacy_scripts_offset = 18; @@ -1276,8 +1221,10 @@ static int parse_bmp_structure(struct drm_device *dev, struct nvbios *bios, unsi bios->fp.lvdsmanufacturerpointer = ROM16(bmp[117]); bios->fp.fpxlatemanufacturertableptr = ROM16(bmp[119]); } +#if 0 if (bmplength > 143) bios->pll_limit_tbl_ptr = ROM16(bmp[142]); +#endif if (bmplength > 157) bios->fp.duallink_transition_clk = ROM16(bmp[156]) * 10; @@ -1522,6 +1469,7 @@ parse_dcb20_entry(struct drm_device *dev, struct dcb_table *dcb, } case DCB_OUTPUT_DP: entry->dpconf.sor.link = (conf & 0x00000030) >> 4; + entry->extdev = (conf & 0x0000ff00) >> 8; switch ((conf & 0x00e00000) >> 21) { case 0: entry->dpconf.link_bw = 162000; @@ -1543,8 +1491,10 @@ parse_dcb20_entry(struct drm_device *dev, struct dcb_table *dcb, } break; case DCB_OUTPUT_TMDS: - if (dcb->version >= 0x40) + if (dcb->version >= 0x40) { entry->tmdsconf.sor.link = (conf & 0x00000030) >> 4; + entry->extdev = (conf & 0x0000ff00) >> 8; + } else if (dcb->version >= 0x30) entry->tmdsconf.slave_addr = (conf & 0x00000700) >> 8; else if (dcb->version >= 0x22) @@ -1937,9 +1887,9 @@ parse_dcb_table(struct drm_device *dev, struct nvbios *bios) if (conn[0] != 0xff) { NV_INFO(drm, "DCB conn %02d: ", idx); if (olddcb_conntab(dev)[3] < 4) - printk("%04x\n", ROM16(conn[0])); + pr_cont("%04x\n", ROM16(conn[0])); else - printk("%08x\n", ROM32(conn[0])); + pr_cont("%08x\n", ROM32(conn[0])); } } dcb_fake_connectors(bios); @@ -2052,45 +2002,29 @@ uint8_t *nouveau_bios_embedded_edid(struct drm_device *dev) static bool NVInitVBIOS(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); - struct nvbios *bios = &drm->vbios; - - memset(bios, 0, sizeof(struct nvbios)); - spin_lock_init(&bios->lock); - bios->dev = dev; - - bios->data = nouveau_bios(drm->device)->data; - bios->length = nouveau_bios(drm->device)->size; - return true; -} + struct nouveau_bios *bios = nouveau_bios(drm->device); + struct nvbios *legacy = &drm->vbios; + + memset(legacy, 0, sizeof(struct nvbios)); + spin_lock_init(&legacy->lock); + legacy->dev = dev; + + legacy->data = bios->data; + legacy->length = bios->size; + legacy->major_version = bios->version.major; + legacy->chip_version = bios->version.chip; + if (bios->bit_offset) { + legacy->type = NVBIOS_BIT; + legacy->offset = bios->bit_offset; + return !parse_bit_structure(legacy, legacy->offset + 6); + } else + if (bios->bmp_offset) { + legacy->type = NVBIOS_BMP; + legacy->offset = bios->bmp_offset; + return !parse_bmp_structure(dev, legacy, legacy->offset); + } -static int nouveau_parse_vbios_struct(struct drm_device *dev) -{ - struct nouveau_drm *drm = nouveau_drm(dev); - struct nvbios *bios = &drm->vbios; - const uint8_t bit_signature[] = { 0xff, 0xb8, 'B', 'I', 'T' }; - const uint8_t bmp_signature[] = { 0xff, 0x7f, 'N', 'V', 0x0 }; - int offset; - - offset = findstr(bios->data, bios->length, - bit_signature, sizeof(bit_signature)); - if (offset) { - NV_INFO(drm, "BIT BIOS found\n"); - bios->type = NVBIOS_BIT; - bios->offset = offset; - return parse_bit_structure(bios, offset + 6); - } - - offset = findstr(bios->data, bios->length, - bmp_signature, sizeof(bmp_signature)); - if (offset) { - NV_INFO(drm, "BMP BIOS found\n"); - bios->type = NVBIOS_BMP; - bios->offset = offset; - return parse_bmp_structure(dev, bios, offset); - } - - NV_ERROR(drm, "No known BIOS signature found\n"); - return -ENODEV; + return false; } int @@ -2146,10 +2080,6 @@ nouveau_bios_init(struct drm_device *dev) if (!NVInitVBIOS(dev)) return -ENODEV; - ret = nouveau_parse_vbios_struct(dev); - if (ret) - return ret; - ret = parse_dcb_table(dev, bios); if (ret) return ret; diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.h b/drivers/gpu/drm/nouveau/nouveau_bios.h index f68c54ca422f..7ccd28f11adf 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bios.h +++ b/drivers/gpu/drm/nouveau/nouveau_bios.h @@ -107,20 +107,10 @@ struct nvbios { bool old_style_init; uint16_t init_script_tbls_ptr; uint16_t extra_init_script_tbl_ptr; - uint16_t macro_index_tbl_ptr; - uint16_t macro_tbl_ptr; - uint16_t condition_tbl_ptr; - uint16_t io_condition_tbl_ptr; - uint16_t io_flag_condition_tbl_ptr; - uint16_t init_function_tbl_ptr; - - uint16_t pll_limit_tbl_ptr; + uint16_t ram_restrict_tbl_ptr; uint8_t ram_restrict_group_count; - uint16_t some_script_ptr; /* BIT I + 14 */ - uint16_t init96_tbl_ptr; /* BIT I + 16 */ - struct dcb_table dcb; struct { diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c index 64d6e3047dee..11ca82148edc 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bo.c +++ b/drivers/gpu/drm/nouveau/nouveau_bo.c @@ -28,6 +28,7 @@ */ #include <core/engine.h> +#include <linux/swiotlb.h> #include <subdev/fb.h> #include <subdev/vm.h> @@ -561,7 +562,7 @@ nouveau_bo_move_accel_cleanup(struct nouveau_channel *chan, struct nouveau_fence *fence = NULL; int ret; - ret = nouveau_fence_new(chan, &fence); + ret = nouveau_fence_new(chan, false, &fence); if (ret) return ret; diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.c b/drivers/gpu/drm/nouveau/nouveau_chan.c index 174300b6a02e..eaa80a2b81ee 100644 --- a/drivers/gpu/drm/nouveau/nouveau_chan.c +++ b/drivers/gpu/drm/nouveau/nouveau_chan.c @@ -51,14 +51,15 @@ nouveau_channel_idle(struct nouveau_channel *chan) struct nouveau_fence *fence = NULL; int ret; - ret = nouveau_fence_new(chan, &fence); + ret = nouveau_fence_new(chan, false, &fence); if (!ret) { ret = nouveau_fence_wait(fence, false, false); nouveau_fence_unref(&fence); } if (ret) - NV_ERROR(cli, "failed to idle channel 0x%08x\n", chan->handle); + NV_ERROR(cli, "failed to idle channel 0x%08x [%s]\n", + chan->handle, cli->base.name); return ret; } diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index e620ba8271b4..4dd7ae2ac6c6 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -55,8 +55,6 @@ MODULE_PARM_DESC(duallink, "Allow dual-link TMDS (default: enabled)"); static int nouveau_duallink = 1; module_param_named(duallink, nouveau_duallink, int, 0400); -static void nouveau_connector_hotplug(void *, int); - struct nouveau_encoder * find_encoder(struct drm_connector *connector, int type) { @@ -100,22 +98,6 @@ static void nouveau_connector_destroy(struct drm_connector *connector) { struct nouveau_connector *nv_connector = nouveau_connector(connector); - struct nouveau_gpio *gpio; - struct nouveau_drm *drm; - struct drm_device *dev; - - if (!nv_connector) - return; - - dev = nv_connector->base.dev; - drm = nouveau_drm(dev); - gpio = nouveau_gpio(drm->device); - - if (gpio && nv_connector->hpd != DCB_GPIO_UNUSED) { - gpio->isr_del(gpio, 0, nv_connector->hpd, 0xff, - nouveau_connector_hotplug, connector); - } - kfree(nv_connector->edid); drm_sysfs_connector_remove(connector); drm_connector_cleanup(connector); @@ -130,7 +112,6 @@ nouveau_connector_ddc_detect(struct drm_connector *connector, struct nouveau_connector *nv_connector = nouveau_connector(connector); struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_gpio *gpio = nouveau_gpio(drm->device); - struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct nouveau_i2c_port *port = NULL; int i, panel = -ENODEV; @@ -160,8 +141,7 @@ nouveau_connector_ddc_detect(struct drm_connector *connector, continue; nv_encoder = nouveau_encoder(obj_to_encoder(obj)); - if (nv_encoder->dcb->i2c_index < 0xf) - port = i2c->find(i2c, nv_encoder->dcb->i2c_index); + port = nv_encoder->i2c; if (port && nv_probe_i2c(port, 0x50)) { *pnv_encoder = nv_encoder; break; @@ -399,9 +379,10 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force) struct edid *edid = (struct edid *)nouveau_bios_embedded_edid(dev); if (edid) { - nv_connector->edid = kmalloc(EDID_LENGTH, GFP_KERNEL); - *(nv_connector->edid) = *edid; - status = connector_status_connected; + nv_connector->edid = + kmemdup(edid, EDID_LENGTH, GFP_KERNEL); + if (nv_connector->edid) + status = connector_status_connected; } } @@ -911,6 +892,37 @@ nouveau_connector_funcs_lvds = { .force = nouveau_connector_force }; +static void +nouveau_connector_hotplug_work(struct work_struct *work) +{ + struct nouveau_connector *nv_connector = + container_of(work, struct nouveau_connector, hpd_work); + struct drm_connector *connector = &nv_connector->base; + struct drm_device *dev = connector->dev; + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_gpio *gpio = nouveau_gpio(drm->device); + bool plugged = gpio->get(gpio, 0, nv_connector->hpd.func, 0xff); + + NV_DEBUG(drm, "%splugged %s\n", plugged ? "" : "un", + drm_get_connector_name(connector)); + + if (plugged) + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); + else + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); + + drm_helper_hpd_irq_event(dev); +} + +static int +nouveau_connector_hotplug(struct nouveau_eventh *event, int index) +{ + struct nouveau_connector *nv_connector = + container_of(event, struct nouveau_connector, hpd_func); + schedule_work(&nv_connector->hpd_work); + return NVKM_EVENT_KEEP; +} + static int drm_conntype_from_dcb(enum dcb_connector_type dcb) { @@ -961,6 +973,7 @@ nouveau_connector_create(struct drm_device *dev, int index) return ERR_PTR(-ENOMEM); connector = &nv_connector->base; + INIT_WORK(&nv_connector->hpd_work, nouveau_connector_hotplug_work); nv_connector->index = index; /* attempt to parse vbios connector type and hotplug gpio */ @@ -975,8 +988,11 @@ nouveau_connector_create(struct drm_device *dev, int index) if (olddcb_conntab(dev)[3] >= 4) entry |= (u32)ROM16(nv_connector->dcb[2]) << 16; - nv_connector->hpd = ffs((entry & 0x07033000) >> 12); - nv_connector->hpd = hpd[nv_connector->hpd]; + ret = gpio->find(gpio, 0, hpd[ffs((entry & 0x07033000) >> 12)], + DCB_GPIO_UNUSED, &nv_connector->hpd); + nv_connector->hpd_func.func = nouveau_connector_hotplug; + if (ret) + nv_connector->hpd.func = DCB_GPIO_UNUSED; nv_connector->type = nv_connector->dcb[0]; if (drm_conntype_from_dcb(nv_connector->type) == @@ -999,7 +1015,7 @@ nouveau_connector_create(struct drm_device *dev, int index) } } else { nv_connector->type = DCB_CONNECTOR_NONE; - nv_connector->hpd = DCB_GPIO_UNUSED; + nv_connector->hpd.func = DCB_GPIO_UNUSED; } /* no vbios data, or an unknown dcb connector type - attempt to @@ -1126,31 +1142,9 @@ nouveau_connector_create(struct drm_device *dev, int index) } connector->polled = DRM_CONNECTOR_POLL_CONNECT; - if (gpio && nv_connector->hpd != DCB_GPIO_UNUSED) { - ret = gpio->isr_add(gpio, 0, nv_connector->hpd, 0xff, - nouveau_connector_hotplug, connector); - if (ret == 0) - connector->polled = DRM_CONNECTOR_POLL_HPD; - } + if (nv_connector->hpd.func != DCB_GPIO_UNUSED) + connector->polled = DRM_CONNECTOR_POLL_HPD; drm_sysfs_connector_add(connector); return connector; } - -static void -nouveau_connector_hotplug(void *data, int plugged) -{ - struct drm_connector *connector = data; - struct drm_device *dev = connector->dev; - struct nouveau_drm *drm = nouveau_drm(dev); - - NV_DEBUG(drm, "%splugged %s\n", plugged ? "" : "un", - drm_get_connector_name(connector)); - - if (plugged) - drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); - else - drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); - - drm_helper_hpd_irq_event(dev); -} diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h index 20eb84cce9e6..6e399aad491a 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.h +++ b/drivers/gpu/drm/nouveau/nouveau_connector.h @@ -30,6 +30,11 @@ #include <drm/drm_edid.h> #include "nouveau_crtc.h" +#include <core/event.h> + +#include <subdev/bios.h> +#include <subdev/bios/gpio.h> + struct nouveau_i2c_port; enum nouveau_underscan_type { @@ -61,7 +66,10 @@ struct nouveau_connector { enum dcb_connector_type type; u8 index; u8 *dcb; - u8 hpd; + + struct dcb_gpio_func hpd; + struct work_struct hpd_work; + struct nouveau_eventh hpd_func; int dithering_mode; int dithering_depth; diff --git a/drivers/gpu/drm/nouveau/nouveau_debugfs.c b/drivers/gpu/drm/nouveau/nouveau_debugfs.c new file mode 100644 index 000000000000..5392e07edfc6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_debugfs.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2009 Red Hat <bskeggs@redhat.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* + * Authors: + * Ben Skeggs <bskeggs@redhat.com> + */ + +#include "nouveau_debugfs.h" +#include "nouveau_drm.h" + +static int +nouveau_debugfs_vbios_image(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct nouveau_drm *drm = nouveau_drm(node->minor->dev); + int i; + + for (i = 0; i < drm->vbios.length; i++) + seq_printf(m, "%c", drm->vbios.data[i]); + return 0; +} + +static struct drm_info_list nouveau_debugfs_list[] = { + { "vbios.rom", nouveau_debugfs_vbios_image, 0, NULL }, +}; +#define NOUVEAU_DEBUGFS_ENTRIES ARRAY_SIZE(nouveau_debugfs_list) + +int +nouveau_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(nouveau_debugfs_list, NOUVEAU_DEBUGFS_ENTRIES, + minor->debugfs_root, minor); + return 0; +} + +void +nouveau_debugfs_takedown(struct drm_minor *minor) +{ + drm_debugfs_remove_files(nouveau_debugfs_list, NOUVEAU_DEBUGFS_ENTRIES, + minor); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_debugfs.h b/drivers/gpu/drm/nouveau/nouveau_debugfs.h new file mode 100644 index 000000000000..a62af6fb5f99 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_debugfs.h @@ -0,0 +1,22 @@ +#ifndef __NOUVEAU_DEBUGFS_H__ +#define __NOUVEAU_DEBUGFS_H__ + +#include <drm/drmP.h> + +#if defined(CONFIG_DEBUG_FS) +extern int nouveau_debugfs_init(struct drm_minor *); +extern void nouveau_debugfs_takedown(struct drm_minor *); +#else +static inline int +nouveau_debugfs_init(struct drm_minor *minor) +{ + return 0; +} + +static inline void nouveau_debugfs_takedown(struct drm_minor *minor) +{ +} + +#endif + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c index d42c9e860c16..4610c3a29bbe 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.c +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -41,6 +41,8 @@ #include <subdev/gpio.h> #include <engine/disp.h> +#include <core/class.h> + static void nouveau_user_framebuffer_destroy(struct drm_framebuffer *drm_fb) { @@ -231,8 +233,10 @@ nouveau_display_init(struct drm_device *dev) /* enable hotplug interrupts */ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { struct nouveau_connector *conn = nouveau_connector(connector); - if (gpio) - gpio->irq(gpio, 0, conn->hpd, 0xff, true); + if (gpio && conn->hpd.func != DCB_GPIO_UNUSED) { + nouveau_event_get(gpio->events, conn->hpd.line, + &conn->hpd_func); + } } return ret; @@ -249,37 +253,20 @@ nouveau_display_fini(struct drm_device *dev) /* disable hotplug interrupts */ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { struct nouveau_connector *conn = nouveau_connector(connector); - if (gpio) - gpio->irq(gpio, 0, conn->hpd, 0xff, false); + if (gpio && conn->hpd.func != DCB_GPIO_UNUSED) { + nouveau_event_put(gpio->events, conn->hpd.line, + &conn->hpd_func); + } } drm_kms_helper_poll_disable(dev); disp->fini(dev); } -static void -nouveau_display_vblank_notify(void *data, int crtc) -{ - drm_handle_vblank(data, crtc); -} - -static void -nouveau_display_vblank_get(void *data, int crtc) -{ - drm_vblank_get(data, crtc); -} - -static void -nouveau_display_vblank_put(void *data, int crtc) -{ - drm_vblank_put(data, crtc); -} - int nouveau_display_create(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); - struct nouveau_disp *pdisp = nouveau_disp(drm->device); struct nouveau_display *disp; u32 pclass = dev->pdev->class >> 8; int ret, gen; @@ -288,11 +275,6 @@ nouveau_display_create(struct drm_device *dev) if (!disp) return -ENOMEM; - pdisp->vblank.data = dev; - pdisp->vblank.notify = nouveau_display_vblank_notify; - pdisp->vblank.get = nouveau_display_vblank_get; - pdisp->vblank.put = nouveau_display_vblank_put; - drm_mode_config_init(dev); drm_mode_create_scaling_mode_property(dev); drm_mode_create_dvi_i_properties(dev); @@ -316,17 +298,13 @@ nouveau_display_create(struct drm_device *dev) drm_property_create_range(dev, 0, "underscan vborder", 0, 128); if (gen >= 1) { + /* -90..+90 */ disp->vibrant_hue_property = - drm_property_create(dev, DRM_MODE_PROP_RANGE, - "vibrant hue", 2); - disp->vibrant_hue_property->values[0] = 0; - disp->vibrant_hue_property->values[1] = 180; /* -90..+90 */ + drm_property_create_range(dev, 0, "vibrant hue", 0, 180); + /* -100..+100 */ disp->color_vibrance_property = - drm_property_create(dev, DRM_MODE_PROP_RANGE, - "color vibrance", 2); - disp->color_vibrance_property->values[0] = 0; - disp->color_vibrance_property->values[1] = 200; /* -100..+100 */ + drm_property_create_range(dev, 0, "color vibrance", 0, 200); } dev->mode_config.funcs = &nouveau_mode_config_funcs; @@ -478,39 +456,6 @@ nouveau_display_resume(struct drm_device *dev) } } -int -nouveau_vblank_enable(struct drm_device *dev, int crtc) -{ - struct nouveau_device *device = nouveau_dev(dev); - - if (device->card_type >= NV_D0) - nv_mask(device, 0x6100c0 + (crtc * 0x800), 1, 1); - else - if (device->card_type >= NV_50) - nv_mask(device, NV50_PDISPLAY_INTR_EN_1, 0, - NV50_PDISPLAY_INTR_EN_1_VBLANK_CRTC_(crtc)); - else - NVWriteCRTC(dev, crtc, NV_PCRTC_INTR_EN_0, - NV_PCRTC_INTR_0_VBLANK); - - return 0; -} - -void -nouveau_vblank_disable(struct drm_device *dev, int crtc) -{ - struct nouveau_device *device = nouveau_dev(dev); - - if (device->card_type >= NV_D0) - nv_mask(device, 0x6100c0 + (crtc * 0x800), 1, 0); - else - if (device->card_type >= NV_50) - nv_mask(device, NV50_PDISPLAY_INTR_EN_1, - NV50_PDISPLAY_INTR_EN_1_VBLANK_CRTC_(crtc), 0); - else - NVWriteCRTC(dev, crtc, NV_PCRTC_INTR_EN_0, 0); -} - static int nouveau_page_flip_reserve(struct nouveau_bo *old_bo, struct nouveau_bo *new_bo) @@ -595,7 +540,7 @@ nouveau_page_flip_emit(struct nouveau_channel *chan, } FIRE_RING (chan); - ret = nouveau_fence_new(chan, pfence); + ret = nouveau_fence_new(chan, false, pfence); if (ret) goto fail; diff --git a/drivers/gpu/drm/nouveau/nouveau_display.h b/drivers/gpu/drm/nouveau/nouveau_display.h index 722548bb3bd3..1ea3e4734b62 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.h +++ b/drivers/gpu/drm/nouveau/nouveau_display.h @@ -59,9 +59,6 @@ void nouveau_display_fini(struct drm_device *dev); int nouveau_display_suspend(struct drm_device *dev); void nouveau_display_resume(struct drm_device *dev); -int nouveau_vblank_enable(struct drm_device *dev, int crtc); -void nouveau_vblank_disable(struct drm_device *dev, int crtc); - int nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, struct drm_pending_vblank_event *event); int nouveau_finish_page_flip(struct nouveau_channel *, diff --git a/drivers/gpu/drm/nouveau/nouveau_dma.h b/drivers/gpu/drm/nouveau/nouveau_dma.h index 5c2e22932d1c..690d5930ce32 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dma.h +++ b/drivers/gpu/drm/nouveau/nouveau_dma.h @@ -191,7 +191,7 @@ WIND_RING(struct nouveau_channel *chan) #define NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG 0x00000002 #define NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_GEQUAL 0x00000004 #define NVC0_SUBCHAN_SEMAPHORE_TRIGGER_YIELD 0x00001000 -#define NV84_SUBCHAN_NOTIFY_INTR 0x00000020 +#define NV84_SUBCHAN_UEVENT 0x00000020 #define NV84_SUBCHAN_WRCACHE_FLUSH 0x00000024 #define NV10_SUBCHAN_REF_CNT 0x00000050 #define NVSW_SUBCHAN_PAGE_FLIP 0x00000054 diff --git a/drivers/gpu/drm/nouveau/nouveau_dp.c b/drivers/gpu/drm/nouveau/nouveau_dp.c index 59838651ee8f..36fd22500569 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dp.c +++ b/drivers/gpu/drm/nouveau/nouveau_dp.c @@ -35,300 +35,6 @@ #include <subdev/gpio.h> #include <subdev/i2c.h> -/****************************************************************************** - * link training - *****************************************************************************/ -struct dp_state { - struct nouveau_i2c_port *auxch; - struct nouveau_object *core; - struct dcb_output *dcb; - int crtc; - u8 *dpcd; - int link_nr; - u32 link_bw; - u8 stat[6]; - u8 conf[4]; -}; - -static void -dp_set_link_config(struct drm_device *dev, struct dp_state *dp) -{ - struct nouveau_drm *drm = nouveau_drm(dev); - struct dcb_output *dcb = dp->dcb; - const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); - const u32 moff = (dp->crtc << 3) | (link << 2) | or; - u8 sink[2]; - u32 data; - - NV_DEBUG(drm, "%d lanes at %d KB/s\n", dp->link_nr, dp->link_bw); - - /* set desired link configuration on the source */ - data = ((dp->link_bw / 27000) << 8) | dp->link_nr; - if (dp->dpcd[2] & DP_ENHANCED_FRAME_CAP) - data |= NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH; - - nv_call(dp->core, NV94_DISP_SOR_DP_LNKCTL + moff, data); - - /* inform the sink of the new configuration */ - sink[0] = dp->link_bw / 27000; - sink[1] = dp->link_nr; - if (dp->dpcd[2] & DP_ENHANCED_FRAME_CAP) - sink[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; - - nv_wraux(dp->auxch, DP_LINK_BW_SET, sink, 2); -} - -static void -dp_set_training_pattern(struct drm_device *dev, struct dp_state *dp, u8 pattern) -{ - struct nouveau_drm *drm = nouveau_drm(dev); - struct dcb_output *dcb = dp->dcb; - const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); - const u32 moff = (dp->crtc << 3) | (link << 2) | or; - u8 sink_tp; - - NV_DEBUG(drm, "training pattern %d\n", pattern); - - nv_call(dp->core, NV94_DISP_SOR_DP_TRAIN + moff, pattern); - - nv_rdaux(dp->auxch, DP_TRAINING_PATTERN_SET, &sink_tp, 1); - sink_tp &= ~DP_TRAINING_PATTERN_MASK; - sink_tp |= pattern; - nv_wraux(dp->auxch, DP_TRAINING_PATTERN_SET, &sink_tp, 1); -} - -static int -dp_link_train_commit(struct drm_device *dev, struct dp_state *dp) -{ - struct nouveau_drm *drm = nouveau_drm(dev); - struct dcb_output *dcb = dp->dcb; - const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); - const u32 moff = (dp->crtc << 3) | (link << 2) | or; - int i; - - for (i = 0; i < dp->link_nr; i++) { - u8 lane = (dp->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf; - u8 lpre = (lane & 0x0c) >> 2; - u8 lvsw = (lane & 0x03) >> 0; - - dp->conf[i] = (lpre << 3) | lvsw; - if (lvsw == DP_TRAIN_VOLTAGE_SWING_1200) - dp->conf[i] |= DP_TRAIN_MAX_SWING_REACHED; - if ((lpre << 3) == DP_TRAIN_PRE_EMPHASIS_9_5) - dp->conf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; - - NV_DEBUG(drm, "config lane %d %02x\n", i, dp->conf[i]); - - nv_call(dp->core, NV94_DISP_SOR_DP_DRVCTL(i) + moff, (lvsw << 8) | lpre); - } - - return nv_wraux(dp->auxch, DP_TRAINING_LANE0_SET, dp->conf, 4); -} - -static int -dp_link_train_update(struct drm_device *dev, struct dp_state *dp, u32 delay) -{ - struct nouveau_drm *drm = nouveau_drm(dev); - int ret; - - udelay(delay); - - ret = nv_rdaux(dp->auxch, DP_LANE0_1_STATUS, dp->stat, 6); - if (ret) - return ret; - - NV_DEBUG(drm, "status %*ph\n", 6, dp->stat); - return 0; -} - -static int -dp_link_train_cr(struct drm_device *dev, struct dp_state *dp) -{ - bool cr_done = false, abort = false; - int voltage = dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK; - int tries = 0, i; - - dp_set_training_pattern(dev, dp, DP_TRAINING_PATTERN_1); - - do { - if (dp_link_train_commit(dev, dp) || - dp_link_train_update(dev, dp, 100)) - break; - - cr_done = true; - for (i = 0; i < dp->link_nr; i++) { - u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; - if (!(lane & DP_LANE_CR_DONE)) { - cr_done = false; - if (dp->conf[i] & DP_TRAIN_MAX_SWING_REACHED) - abort = true; - break; - } - } - - if ((dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK) != voltage) { - voltage = dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK; - tries = 0; - } - } while (!cr_done && !abort && ++tries < 5); - - return cr_done ? 0 : -1; -} - -static int -dp_link_train_eq(struct drm_device *dev, struct dp_state *dp) -{ - bool eq_done, cr_done = true; - int tries = 0, i; - - dp_set_training_pattern(dev, dp, DP_TRAINING_PATTERN_2); - - do { - if (dp_link_train_update(dev, dp, 400)) - break; - - eq_done = !!(dp->stat[2] & DP_INTERLANE_ALIGN_DONE); - for (i = 0; i < dp->link_nr && eq_done; i++) { - u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; - if (!(lane & DP_LANE_CR_DONE)) - cr_done = false; - if (!(lane & DP_LANE_CHANNEL_EQ_DONE) || - !(lane & DP_LANE_SYMBOL_LOCKED)) - eq_done = false; - } - - if (dp_link_train_commit(dev, dp)) - break; - } while (!eq_done && cr_done && ++tries <= 5); - - return eq_done ? 0 : -1; -} - -static void -dp_link_train_init(struct drm_device *dev, struct dp_state *dp, bool spread) -{ - struct dcb_output *dcb = dp->dcb; - const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); - const u32 moff = (dp->crtc << 3) | (link << 2) | or; - - nv_call(dp->core, NV94_DISP_SOR_DP_TRAIN + moff, (spread ? - NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_ON : - NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_OFF) | - NV94_DISP_SOR_DP_TRAIN_OP_INIT); -} - -static void -dp_link_train_fini(struct drm_device *dev, struct dp_state *dp) -{ - struct dcb_output *dcb = dp->dcb; - const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); - const u32 moff = (dp->crtc << 3) | (link << 2) | or; - - nv_call(dp->core, NV94_DISP_SOR_DP_TRAIN + moff, - NV94_DISP_SOR_DP_TRAIN_OP_FINI); -} - -static bool -nouveau_dp_link_train(struct drm_encoder *encoder, u32 datarate, - struct nouveau_object *core) -{ - struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); - struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); - struct nouveau_connector *nv_connector = - nouveau_encoder_connector_get(nv_encoder); - struct drm_device *dev = encoder->dev; - struct nouveau_drm *drm = nouveau_drm(dev); - struct nouveau_i2c *i2c = nouveau_i2c(drm->device); - struct nouveau_gpio *gpio = nouveau_gpio(drm->device); - const u32 bw_list[] = { 270000, 162000, 0 }; - const u32 *link_bw = bw_list; - struct dp_state dp; - - dp.auxch = i2c->find(i2c, nv_encoder->dcb->i2c_index); - if (!dp.auxch) - return false; - - dp.core = core; - dp.dcb = nv_encoder->dcb; - dp.crtc = nv_crtc->index; - dp.dpcd = nv_encoder->dp.dpcd; - - /* adjust required bandwidth for 8B/10B coding overhead */ - datarate = (datarate / 8) * 10; - - /* some sinks toggle hotplug in response to some of the actions - * we take during link training (DP_SET_POWER is one), we need - * to ignore them for the moment to avoid races. - */ - gpio->irq(gpio, 0, nv_connector->hpd, 0xff, false); - - /* enable down-spreading and execute pre-train script from vbios */ - dp_link_train_init(dev, &dp, nv_encoder->dp.dpcd[3] & 1); - - /* start off at highest link rate supported by encoder and display */ - while (*link_bw > nv_encoder->dp.link_bw) - link_bw++; - - while (link_bw[0]) { - /* find minimum required lane count at this link rate */ - dp.link_nr = nv_encoder->dp.link_nr; - while ((dp.link_nr >> 1) * link_bw[0] > datarate) - dp.link_nr >>= 1; - - /* drop link rate to minimum with this lane count */ - while ((link_bw[1] * dp.link_nr) > datarate) - link_bw++; - dp.link_bw = link_bw[0]; - - /* program selected link configuration */ - dp_set_link_config(dev, &dp); - - /* attempt to train the link at this configuration */ - memset(dp.stat, 0x00, sizeof(dp.stat)); - if (!dp_link_train_cr(dev, &dp) && - !dp_link_train_eq(dev, &dp)) - break; - - /* retry at lower rate */ - link_bw++; - } - - /* finish link training */ - dp_set_training_pattern(dev, &dp, DP_TRAINING_PATTERN_DISABLE); - - /* execute post-train script from vbios */ - dp_link_train_fini(dev, &dp); - - /* re-enable hotplug detect */ - gpio->irq(gpio, 0, nv_connector->hpd, 0xff, true); - return true; -} - -void -nouveau_dp_dpms(struct drm_encoder *encoder, int mode, u32 datarate, - struct nouveau_object *core) -{ - struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); - struct nouveau_drm *drm = nouveau_drm(encoder->dev); - struct nouveau_i2c *i2c = nouveau_i2c(drm->device); - struct nouveau_i2c_port *auxch; - u8 status; - - auxch = i2c->find(i2c, nv_encoder->dcb->i2c_index); - if (!auxch) - return; - - if (mode == DRM_MODE_DPMS_ON) - status = DP_SET_POWER_D0; - else - status = DP_SET_POWER_D3; - - nv_wraux(auxch, DP_SET_POWER, &status, 1); - - if (mode == DRM_MODE_DPMS_ON) - nouveau_dp_link_train(encoder, datarate, core); -} - static void nouveau_dp_probe_oui(struct drm_device *dev, struct nouveau_i2c_port *auxch, u8 *dpcd) @@ -355,12 +61,11 @@ nouveau_dp_detect(struct drm_encoder *encoder) struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); struct drm_device *dev = encoder->dev; struct nouveau_drm *drm = nouveau_drm(dev); - struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct nouveau_i2c_port *auxch; u8 *dpcd = nv_encoder->dp.dpcd; int ret; - auxch = i2c->find(i2c, nv_encoder->dcb->i2c_index); + auxch = nv_encoder->i2c; if (!auxch) return false; diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 8e8e8ce75528..8a03c58ae988 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -34,6 +34,8 @@ #include <subdev/device.h> #include <subdev/vm.h> +#include <engine/disp.h> + #include "nouveau_drm.h" #include "nouveau_irq.h" #include "nouveau_dma.h" @@ -48,6 +50,7 @@ #include "nouveau_abi16.h" #include "nouveau_fbcon.h" #include "nouveau_fence.h" +#include "nouveau_debugfs.h" MODULE_PARM_DESC(config, "option string to pass to driver core"); static char *nouveau_config; @@ -68,6 +71,32 @@ module_param_named(modeset, nouveau_modeset, int, 0400); static struct drm_driver driver; +static int +nouveau_drm_vblank_enable(struct drm_device *dev, int head) +{ + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_disp *pdisp = nouveau_disp(drm->device); + nouveau_event_get(pdisp->vblank, head, &drm->vblank); + return 0; +} + +static void +nouveau_drm_vblank_disable(struct drm_device *dev, int head) +{ + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_disp *pdisp = nouveau_disp(drm->device); + nouveau_event_put(pdisp->vblank, head, &drm->vblank); +} + +static int +nouveau_drm_vblank_handler(struct nouveau_eventh *event, int head) +{ + struct nouveau_drm *drm = + container_of(event, struct nouveau_drm, vblank); + drm_handle_vblank(drm->dev, head); + return NVKM_EVENT_KEEP; +} + static u64 nouveau_name(struct pci_dev *pdev) { @@ -132,7 +161,8 @@ nouveau_accel_init(struct nouveau_drm *drm) /* initialise synchronisation routines */ if (device->card_type < NV_10) ret = nv04_fence_create(drm); - else if (device->card_type < NV_50) ret = nv10_fence_create(drm); + else if (device->chipset < 0x17) ret = nv10_fence_create(drm); + else if (device->card_type < NV_50) ret = nv17_fence_create(drm); else if (device->chipset < 0x84) ret = nv50_fence_create(drm); else if (device->card_type < NV_C0) ret = nv84_fence_create(drm); else ret = nvc0_fence_create(drm); @@ -259,6 +289,7 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags) dev->dev_private = drm; drm->dev = dev; + drm->vblank.func = nouveau_drm_vblank_handler; INIT_LIST_HEAD(&drm->clients); spin_lock_init(&drm->tile.lock); @@ -398,7 +429,7 @@ nouveau_drm_remove(struct pci_dev *pdev) nouveau_object_debug(); } -int +static int nouveau_do_suspend(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); @@ -469,7 +500,7 @@ int nouveau_pmops_suspend(struct device *dev) return 0; } -int +static int nouveau_do_resume(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); @@ -543,10 +574,11 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv) struct pci_dev *pdev = dev->pdev; struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_cli *cli; - char name[16]; + char name[32], tmpname[TASK_COMM_LEN]; int ret; - snprintf(name, sizeof(name), "%d", pid_nr(fpriv->pid)); + get_task_comm(tmpname, current); + snprintf(name, sizeof(name), "%s[%d]", tmpname, pid_nr(fpriv->pid)); ret = nouveau_cli_create(pdev, name, sizeof(*cli), (void **)&cli); if (ret) @@ -636,14 +668,19 @@ driver = { .postclose = nouveau_drm_postclose, .lastclose = nouveau_vga_lastclose, +#if defined(CONFIG_DEBUG_FS) + .debugfs_init = nouveau_debugfs_init, + .debugfs_cleanup = nouveau_debugfs_takedown, +#endif + .irq_preinstall = nouveau_irq_preinstall, .irq_postinstall = nouveau_irq_postinstall, .irq_uninstall = nouveau_irq_uninstall, .irq_handler = nouveau_irq_handler, .get_vblank_counter = drm_vblank_count, - .enable_vblank = nouveau_vblank_enable, - .disable_vblank = nouveau_vblank_disable, + .enable_vblank = nouveau_drm_vblank_enable, + .disable_vblank = nouveau_drm_vblank_disable, .ioctls = nouveau_ioctls, .fops = &nouveau_driver_fops, diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.h b/drivers/gpu/drm/nouveau/nouveau_drm.h index aa89eb938b47..b25df374c901 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.h +++ b/drivers/gpu/drm/nouveau/nouveau_drm.h @@ -13,6 +13,7 @@ #define DRIVER_PATCHLEVEL 0 #include <core/client.h> +#include <core/event.h> #include <subdev/vm.h> @@ -112,6 +113,7 @@ struct nouveau_drm { struct nvbios vbios; struct nouveau_display *display; struct backlight_device *backlight; + struct nouveau_eventh vblank; /* power management */ struct nouveau_pm *pm; diff --git a/drivers/gpu/drm/nouveau/nouveau_encoder.h b/drivers/gpu/drm/nouveau/nouveau_encoder.h index d0d95bd511ab..e24341229d5e 100644 --- a/drivers/gpu/drm/nouveau/nouveau_encoder.h +++ b/drivers/gpu/drm/nouveau/nouveau_encoder.h @@ -36,19 +36,12 @@ struct nouveau_i2c_port; -struct dp_train_func { - void (*link_set)(struct drm_device *, struct dcb_output *, int crtc, - int nr, u32 bw, bool enhframe); - void (*train_set)(struct drm_device *, struct dcb_output *, u8 pattern); - void (*train_adj)(struct drm_device *, struct dcb_output *, - u8 lane, u8 swing, u8 preem); -}; - struct nouveau_encoder { struct drm_encoder_slave base; struct dcb_output *dcb; int or; + struct nouveau_i2c_port *i2c; /* different to drm_encoder.crtc, this reflects what's * actually programmed on the hw, not the proposed crtc */ diff --git a/drivers/gpu/drm/nouveau/nouveau_fbcon.c b/drivers/gpu/drm/nouveau/nouveau_fbcon.c index d4ecb4deb484..b03531781580 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fbcon.c +++ b/drivers/gpu/drm/nouveau/nouveau_fbcon.c @@ -251,9 +251,10 @@ nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon) } static int -nouveau_fbcon_create(struct nouveau_fbdev *fbcon, +nouveau_fbcon_create(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { + struct nouveau_fbdev *fbcon = (struct nouveau_fbdev *)helper; struct drm_device *dev = fbcon->dev; struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_device *device = nv_device(drm->device); @@ -388,23 +389,6 @@ out: return ret; } -static int -nouveau_fbcon_find_or_create_single(struct drm_fb_helper *helper, - struct drm_fb_helper_surface_size *sizes) -{ - struct nouveau_fbdev *fbcon = (struct nouveau_fbdev *)helper; - int new_fb = 0; - int ret; - - if (!helper->fb) { - ret = nouveau_fbcon_create(fbcon, sizes); - if (ret) - return ret; - new_fb = 1; - } - return new_fb; -} - void nouveau_fbcon_output_poll_changed(struct drm_device *dev) { @@ -450,7 +434,7 @@ void nouveau_fbcon_gpu_lockup(struct fb_info *info) static struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = { .gamma_set = nouveau_fbcon_gamma_set, .gamma_get = nouveau_fbcon_gamma_get, - .fb_probe = nouveau_fbcon_find_or_create_single, + .fb_probe = nouveau_fbcon_create, }; @@ -491,6 +475,9 @@ nouveau_fbcon_init(struct drm_device *dev) else preferred_bpp = 32; + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp); return 0; } diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.c b/drivers/gpu/drm/nouveau/nouveau_fence.c index 1d049be79f74..6c946837a0aa 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fence.c +++ b/drivers/gpu/drm/nouveau/nouveau_fence.c @@ -33,14 +33,14 @@ #include "nouveau_dma.h" #include "nouveau_fence.h" +#include <engine/fifo.h> + void nouveau_fence_context_del(struct nouveau_fence_chan *fctx) { struct nouveau_fence *fence, *fnext; spin_lock(&fctx->lock); list_for_each_entry_safe(fence, fnext, &fctx->pending, head) { - if (fence->work) - fence->work(fence->priv, false); fence->channel = NULL; list_del(&fence->head); nouveau_fence_unref(&fence); @@ -59,17 +59,14 @@ nouveau_fence_context_new(struct nouveau_fence_chan *fctx) static void nouveau_fence_update(struct nouveau_channel *chan) { - struct nouveau_fence_priv *priv = chan->drm->fence; struct nouveau_fence_chan *fctx = chan->fence; struct nouveau_fence *fence, *fnext; spin_lock(&fctx->lock); list_for_each_entry_safe(fence, fnext, &fctx->pending, head) { - if (priv->read(chan) < fence->sequence) + if (fctx->read(chan) < fence->sequence) break; - if (fence->work) - fence->work(fence->priv, true); fence->channel = NULL; list_del(&fence->head); nouveau_fence_unref(&fence); @@ -80,7 +77,6 @@ nouveau_fence_update(struct nouveau_channel *chan) int nouveau_fence_emit(struct nouveau_fence *fence, struct nouveau_channel *chan) { - struct nouveau_fence_priv *priv = chan->drm->fence; struct nouveau_fence_chan *fctx = chan->fence; int ret; @@ -88,7 +84,7 @@ nouveau_fence_emit(struct nouveau_fence *fence, struct nouveau_channel *chan) fence->timeout = jiffies + (3 * DRM_HZ); fence->sequence = ++fctx->sequence; - ret = priv->emit(fence); + ret = fctx->emit(fence); if (!ret) { kref_get(&fence->kref); spin_lock(&fctx->lock); @@ -107,13 +103,87 @@ nouveau_fence_done(struct nouveau_fence *fence) return !fence->channel; } +struct nouveau_fence_uevent { + struct nouveau_eventh handler; + struct nouveau_fence_priv *priv; +}; + +static int +nouveau_fence_wait_uevent_handler(struct nouveau_eventh *event, int index) +{ + struct nouveau_fence_uevent *uevent = + container_of(event, struct nouveau_fence_uevent, handler); + wake_up_all(&uevent->priv->waiting); + return NVKM_EVENT_KEEP; +} + +static int +nouveau_fence_wait_uevent(struct nouveau_fence *fence, bool intr) + +{ + struct nouveau_channel *chan = fence->channel; + struct nouveau_fifo *pfifo = nouveau_fifo(chan->drm->device); + struct nouveau_fence_priv *priv = chan->drm->fence; + struct nouveau_fence_uevent uevent = { + .handler.func = nouveau_fence_wait_uevent_handler, + .priv = priv, + }; + int ret = 0; + + nouveau_event_get(pfifo->uevent, 0, &uevent.handler); + + if (fence->timeout) { + unsigned long timeout = fence->timeout - jiffies; + + if (time_before(jiffies, fence->timeout)) { + if (intr) { + ret = wait_event_interruptible_timeout( + priv->waiting, + nouveau_fence_done(fence), + timeout); + } else { + ret = wait_event_timeout(priv->waiting, + nouveau_fence_done(fence), + timeout); + } + } + + if (ret >= 0) { + fence->timeout = jiffies + ret; + if (time_after_eq(jiffies, fence->timeout)) + ret = -EBUSY; + } + } else { + if (intr) { + ret = wait_event_interruptible(priv->waiting, + nouveau_fence_done(fence)); + } else { + wait_event(priv->waiting, nouveau_fence_done(fence)); + } + } + + nouveau_event_put(pfifo->uevent, 0, &uevent.handler); + if (unlikely(ret < 0)) + return ret; + + return 0; +} + int nouveau_fence_wait(struct nouveau_fence *fence, bool lazy, bool intr) { + struct nouveau_channel *chan = fence->channel; + struct nouveau_fence_priv *priv = chan ? chan->drm->fence : NULL; unsigned long sleep_time = NSEC_PER_MSEC / 1000; ktime_t t; int ret = 0; + while (priv && priv->uevent && lazy && !nouveau_fence_done(fence)) { + ret = nouveau_fence_wait_uevent(fence, intr); + if (ret < 0) + return ret; + } + while (!nouveau_fence_done(fence)) { if (fence->timeout && time_after_eq(jiffies, fence->timeout)) { ret = -EBUSY; @@ -143,14 +213,14 @@ nouveau_fence_wait(struct nouveau_fence *fence, bool lazy, bool intr) int nouveau_fence_sync(struct nouveau_fence *fence, struct nouveau_channel *chan) { - struct nouveau_fence_priv *priv = chan->drm->fence; + struct nouveau_fence_chan *fctx = chan->fence; struct nouveau_channel *prev; int ret = 0; prev = fence ? fence->channel : NULL; if (prev) { if (unlikely(prev != chan && !nouveau_fence_done(fence))) { - ret = priv->sync(fence, prev, chan); + ret = fctx->sync(fence, prev, chan); if (unlikely(ret)) ret = nouveau_fence_wait(fence, true, false); } @@ -182,7 +252,8 @@ nouveau_fence_ref(struct nouveau_fence *fence) } int -nouveau_fence_new(struct nouveau_channel *chan, struct nouveau_fence **pfence) +nouveau_fence_new(struct nouveau_channel *chan, bool sysmem, + struct nouveau_fence **pfence) { struct nouveau_fence *fence; int ret = 0; @@ -193,13 +264,13 @@ nouveau_fence_new(struct nouveau_channel *chan, struct nouveau_fence **pfence) fence = kzalloc(sizeof(*fence), GFP_KERNEL); if (!fence) return -ENOMEM; + + fence->sysmem = sysmem; kref_init(&fence->kref); - if (chan) { - ret = nouveau_fence_emit(fence, chan); - if (ret) - nouveau_fence_unref(&fence); - } + ret = nouveau_fence_emit(fence, chan); + if (ret) + nouveau_fence_unref(&fence); *pfence = fence; return ret; diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.h b/drivers/gpu/drm/nouveau/nouveau_fence.h index cdb83acdffe2..c89943407b52 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fence.h +++ b/drivers/gpu/drm/nouveau/nouveau_fence.h @@ -7,15 +7,15 @@ struct nouveau_fence { struct list_head head; struct kref kref; + bool sysmem; + struct nouveau_channel *channel; unsigned long timeout; u32 sequence; - - void (*work)(void *priv, bool signalled); - void *priv; }; -int nouveau_fence_new(struct nouveau_channel *, struct nouveau_fence **); +int nouveau_fence_new(struct nouveau_channel *, bool sysmem, + struct nouveau_fence **); struct nouveau_fence * nouveau_fence_ref(struct nouveau_fence *); void nouveau_fence_unref(struct nouveau_fence **); @@ -29,6 +29,13 @@ struct nouveau_fence_chan { struct list_head pending; struct list_head flip; + int (*emit)(struct nouveau_fence *); + int (*sync)(struct nouveau_fence *, struct nouveau_channel *, + struct nouveau_channel *); + u32 (*read)(struct nouveau_channel *); + int (*emit32)(struct nouveau_channel *, u64, u32); + int (*sync32)(struct nouveau_channel *, u64, u32); + spinlock_t lock; u32 sequence; }; @@ -39,10 +46,9 @@ struct nouveau_fence_priv { void (*resume)(struct nouveau_drm *); int (*context_new)(struct nouveau_channel *); void (*context_del)(struct nouveau_channel *); - int (*emit)(struct nouveau_fence *); - int (*sync)(struct nouveau_fence *, struct nouveau_channel *, - struct nouveau_channel *); - u32 (*read)(struct nouveau_channel *); + + wait_queue_head_t waiting; + bool uevent; }; #define nouveau_fence(drm) ((struct nouveau_fence_priv *)(drm)->fence) @@ -60,13 +66,31 @@ u32 nv10_fence_read(struct nouveau_channel *); void nv10_fence_context_del(struct nouveau_channel *); void nv10_fence_destroy(struct nouveau_drm *); int nv10_fence_create(struct nouveau_drm *); + +int nv17_fence_create(struct nouveau_drm *); void nv17_fence_resume(struct nouveau_drm *drm); int nv50_fence_create(struct nouveau_drm *); int nv84_fence_create(struct nouveau_drm *); int nvc0_fence_create(struct nouveau_drm *); -u64 nvc0_fence_crtc(struct nouveau_channel *, int crtc); int nouveau_flip_complete(void *chan); +struct nv84_fence_chan { + struct nouveau_fence_chan base; + struct nouveau_vma vma; + struct nouveau_vma vma_gart; + struct nouveau_vma dispc_vma[4]; +}; + +struct nv84_fence_priv { + struct nouveau_fence_priv base; + struct nouveau_bo *bo; + struct nouveau_bo *bo_gart; + u32 *suspend; +}; + +u64 nv84_fence_crtc(struct nouveau_channel *, int); +int nv84_fence_context_new(struct nouveau_channel *); + #endif diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c index d98bee012cab..b4b4d0c1f4af 100644 --- a/drivers/gpu/drm/nouveau/nouveau_gem.c +++ b/drivers/gpu/drm/nouveau/nouveau_gem.c @@ -203,6 +203,7 @@ nouveau_gem_ioctl_new(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_cli *cli = nouveau_cli(file_priv); struct nouveau_fb *pfb = nouveau_fb(drm->device); struct drm_nouveau_gem_new *req = data; struct nouveau_bo *nvbo = NULL; @@ -211,7 +212,7 @@ nouveau_gem_ioctl_new(struct drm_device *dev, void *data, drm->ttm.bdev.dev_mapping = drm->dev->dev_mapping; if (!pfb->memtype_valid(pfb, req->info.tile_flags)) { - NV_ERROR(drm, "bad page flags: 0x%08x\n", req->info.tile_flags); + NV_ERROR(cli, "bad page flags: 0x%08x\n", req->info.tile_flags); return -EINVAL; } @@ -313,6 +314,7 @@ validate_init(struct nouveau_channel *chan, struct drm_file *file_priv, struct drm_nouveau_gem_pushbuf_bo *pbbo, int nr_buffers, struct validate_op *op) { + struct nouveau_cli *cli = nouveau_cli(file_priv); struct drm_device *dev = chan->drm->dev; struct nouveau_drm *drm = nouveau_drm(dev); uint32_t sequence; @@ -323,7 +325,7 @@ validate_init(struct nouveau_channel *chan, struct drm_file *file_priv, sequence = atomic_add_return(1, &drm->ttm.validate_sequence); retry: if (++trycnt > 100000) { - NV_ERROR(drm, "%s failed and gave up.\n", __func__); + NV_ERROR(cli, "%s failed and gave up.\n", __func__); return -EINVAL; } @@ -334,7 +336,7 @@ retry: gem = drm_gem_object_lookup(dev, file_priv, b->handle); if (!gem) { - NV_ERROR(drm, "Unknown handle 0x%08x\n", b->handle); + NV_ERROR(cli, "Unknown handle 0x%08x\n", b->handle); validate_fini(op, NULL); return -ENOENT; } @@ -346,7 +348,7 @@ retry: } if (nvbo->reserved_by && nvbo->reserved_by == file_priv) { - NV_ERROR(drm, "multiple instances of buffer %d on " + NV_ERROR(cli, "multiple instances of buffer %d on " "validation list\n", b->handle); drm_gem_object_unreference_unlocked(gem); validate_fini(op, NULL); @@ -366,7 +368,7 @@ retry: if (unlikely(ret)) { drm_gem_object_unreference_unlocked(gem); if (ret != -ERESTARTSYS) - NV_ERROR(drm, "fail reserve\n"); + NV_ERROR(cli, "fail reserve\n"); return ret; } } @@ -384,7 +386,7 @@ retry: if (b->valid_domains & NOUVEAU_GEM_DOMAIN_GART) list_add_tail(&nvbo->entry, &op->gart_list); else { - NV_ERROR(drm, "invalid valid domains: 0x%08x\n", + NV_ERROR(cli, "invalid valid domains: 0x%08x\n", b->valid_domains); list_add_tail(&nvbo->entry, &op->both_list); validate_fini(op, NULL); @@ -417,8 +419,9 @@ validate_sync(struct nouveau_channel *chan, struct nouveau_bo *nvbo) } static int -validate_list(struct nouveau_channel *chan, struct list_head *list, - struct drm_nouveau_gem_pushbuf_bo *pbbo, uint64_t user_pbbo_ptr) +validate_list(struct nouveau_channel *chan, struct nouveau_cli *cli, + struct list_head *list, struct drm_nouveau_gem_pushbuf_bo *pbbo, + uint64_t user_pbbo_ptr) { struct nouveau_drm *drm = chan->drm; struct drm_nouveau_gem_pushbuf_bo __user *upbbo = @@ -431,7 +434,7 @@ validate_list(struct nouveau_channel *chan, struct list_head *list, ret = validate_sync(chan, nvbo); if (unlikely(ret)) { - NV_ERROR(drm, "fail pre-validate sync\n"); + NV_ERROR(cli, "fail pre-validate sync\n"); return ret; } @@ -439,20 +442,20 @@ validate_list(struct nouveau_channel *chan, struct list_head *list, b->write_domains, b->valid_domains); if (unlikely(ret)) { - NV_ERROR(drm, "fail set_domain\n"); + NV_ERROR(cli, "fail set_domain\n"); return ret; } ret = nouveau_bo_validate(nvbo, true, false); if (unlikely(ret)) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "fail ttm_validate\n"); + NV_ERROR(cli, "fail ttm_validate\n"); return ret; } ret = validate_sync(chan, nvbo); if (unlikely(ret)) { - NV_ERROR(drm, "fail post-validate sync\n"); + NV_ERROR(cli, "fail post-validate sync\n"); return ret; } @@ -488,7 +491,7 @@ nouveau_gem_pushbuf_validate(struct nouveau_channel *chan, uint64_t user_buffers, int nr_buffers, struct validate_op *op, int *apply_relocs) { - struct nouveau_drm *drm = chan->drm; + struct nouveau_cli *cli = nouveau_cli(file_priv); int ret, relocs = 0; INIT_LIST_HEAD(&op->vram_list); @@ -501,32 +504,32 @@ nouveau_gem_pushbuf_validate(struct nouveau_channel *chan, ret = validate_init(chan, file_priv, pbbo, nr_buffers, op); if (unlikely(ret)) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "validate_init\n"); + NV_ERROR(cli, "validate_init\n"); return ret; } - ret = validate_list(chan, &op->vram_list, pbbo, user_buffers); + ret = validate_list(chan, cli, &op->vram_list, pbbo, user_buffers); if (unlikely(ret < 0)) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "validate vram_list\n"); + NV_ERROR(cli, "validate vram_list\n"); validate_fini(op, NULL); return ret; } relocs += ret; - ret = validate_list(chan, &op->gart_list, pbbo, user_buffers); + ret = validate_list(chan, cli, &op->gart_list, pbbo, user_buffers); if (unlikely(ret < 0)) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "validate gart_list\n"); + NV_ERROR(cli, "validate gart_list\n"); validate_fini(op, NULL); return ret; } relocs += ret; - ret = validate_list(chan, &op->both_list, pbbo, user_buffers); + ret = validate_list(chan, cli, &op->both_list, pbbo, user_buffers); if (unlikely(ret < 0)) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "validate both_list\n"); + NV_ERROR(cli, "validate both_list\n"); validate_fini(op, NULL); return ret; } @@ -555,11 +558,10 @@ u_memcpya(uint64_t user, unsigned nmemb, unsigned size) } static int -nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, +nouveau_gem_pushbuf_reloc_apply(struct nouveau_cli *cli, struct drm_nouveau_gem_pushbuf *req, struct drm_nouveau_gem_pushbuf_bo *bo) { - struct nouveau_drm *drm = nouveau_drm(dev); struct drm_nouveau_gem_pushbuf_reloc *reloc = NULL; int ret = 0; unsigned i; @@ -575,7 +577,7 @@ nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, uint32_t data; if (unlikely(r->bo_index > req->nr_buffers)) { - NV_ERROR(drm, "reloc bo index invalid\n"); + NV_ERROR(cli, "reloc bo index invalid\n"); ret = -EINVAL; break; } @@ -585,7 +587,7 @@ nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, continue; if (unlikely(r->reloc_bo_index > req->nr_buffers)) { - NV_ERROR(drm, "reloc container bo index invalid\n"); + NV_ERROR(cli, "reloc container bo index invalid\n"); ret = -EINVAL; break; } @@ -593,7 +595,7 @@ nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, if (unlikely(r->reloc_bo_offset + 4 > nvbo->bo.mem.num_pages << PAGE_SHIFT)) { - NV_ERROR(drm, "reloc outside of bo\n"); + NV_ERROR(cli, "reloc outside of bo\n"); ret = -EINVAL; break; } @@ -602,7 +604,7 @@ nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, ret = ttm_bo_kmap(&nvbo->bo, 0, nvbo->bo.mem.num_pages, &nvbo->kmap); if (ret) { - NV_ERROR(drm, "failed kmap for reloc\n"); + NV_ERROR(cli, "failed kmap for reloc\n"); break; } nvbo->validate_mapped = true; @@ -627,7 +629,7 @@ nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, ret = ttm_bo_wait(&nvbo->bo, false, false, false); spin_unlock(&nvbo->bo.bdev->fence_lock); if (ret) { - NV_ERROR(drm, "reloc wait_idle failed: %d\n", ret); + NV_ERROR(cli, "reloc wait_idle failed: %d\n", ret); break; } @@ -643,6 +645,7 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct nouveau_abi16 *abi16 = nouveau_abi16_get(file_priv, dev); + struct nouveau_cli *cli = nouveau_cli(file_priv); struct nouveau_abi16_chan *temp; struct nouveau_drm *drm = nouveau_drm(dev); struct drm_nouveau_gem_pushbuf *req = data; @@ -672,19 +675,19 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, goto out_next; if (unlikely(req->nr_push > NOUVEAU_GEM_MAX_PUSH)) { - NV_ERROR(drm, "pushbuf push count exceeds limit: %d max %d\n", + NV_ERROR(cli, "pushbuf push count exceeds limit: %d max %d\n", req->nr_push, NOUVEAU_GEM_MAX_PUSH); return nouveau_abi16_put(abi16, -EINVAL); } if (unlikely(req->nr_buffers > NOUVEAU_GEM_MAX_BUFFERS)) { - NV_ERROR(drm, "pushbuf bo count exceeds limit: %d max %d\n", + NV_ERROR(cli, "pushbuf bo count exceeds limit: %d max %d\n", req->nr_buffers, NOUVEAU_GEM_MAX_BUFFERS); return nouveau_abi16_put(abi16, -EINVAL); } if (unlikely(req->nr_relocs > NOUVEAU_GEM_MAX_RELOCS)) { - NV_ERROR(drm, "pushbuf reloc count exceeds limit: %d max %d\n", + NV_ERROR(cli, "pushbuf reloc count exceeds limit: %d max %d\n", req->nr_relocs, NOUVEAU_GEM_MAX_RELOCS); return nouveau_abi16_put(abi16, -EINVAL); } @@ -702,7 +705,7 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, /* Ensure all push buffers are on validate list */ for (i = 0; i < req->nr_push; i++) { if (push[i].bo_index >= req->nr_buffers) { - NV_ERROR(drm, "push %d buffer not in list\n", i); + NV_ERROR(cli, "push %d buffer not in list\n", i); ret = -EINVAL; goto out_prevalid; } @@ -713,15 +716,15 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, req->nr_buffers, &op, &do_reloc); if (ret) { if (ret != -ERESTARTSYS) - NV_ERROR(drm, "validate: %d\n", ret); + NV_ERROR(cli, "validate: %d\n", ret); goto out_prevalid; } /* Apply any relocations that are required */ if (do_reloc) { - ret = nouveau_gem_pushbuf_reloc_apply(dev, req, bo); + ret = nouveau_gem_pushbuf_reloc_apply(cli, req, bo); if (ret) { - NV_ERROR(drm, "reloc apply: %d\n", ret); + NV_ERROR(cli, "reloc apply: %d\n", ret); goto out; } } @@ -729,7 +732,7 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, if (chan->dma.ib_max) { ret = nouveau_dma_wait(chan, req->nr_push + 1, 16); if (ret) { - NV_ERROR(drm, "nv50cal_space: %d\n", ret); + NV_ERROR(cli, "nv50cal_space: %d\n", ret); goto out; } @@ -744,7 +747,7 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, if (nv_device(drm->device)->chipset >= 0x25) { ret = RING_SPACE(chan, req->nr_push * 2); if (ret) { - NV_ERROR(drm, "cal_space: %d\n", ret); + NV_ERROR(cli, "cal_space: %d\n", ret); goto out; } @@ -758,7 +761,7 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, } else { ret = RING_SPACE(chan, req->nr_push * (2 + NOUVEAU_DMA_SKIPS)); if (ret) { - NV_ERROR(drm, "jmp_space: %d\n", ret); + NV_ERROR(cli, "jmp_space: %d\n", ret); goto out; } @@ -794,9 +797,9 @@ nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, } } - ret = nouveau_fence_new(chan, &fence); + ret = nouveau_fence_new(chan, false, &fence); if (ret) { - NV_ERROR(drm, "error fencing pushbuf: %d\n", ret); + NV_ERROR(cli, "error fencing pushbuf: %d\n", ret); WIND_RING(chan); goto out; } diff --git a/drivers/gpu/drm/nouveau/nouveau_pm.c b/drivers/gpu/drm/nouveau/nouveau_pm.c index a701ff5ffa5b..bb54098c6d97 100644 --- a/drivers/gpu/drm/nouveau/nouveau_pm.c +++ b/drivers/gpu/drm/nouveau/nouveau_pm.c @@ -409,6 +409,81 @@ static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, nouveau_hwmon_show_temp, NULL, 0); static ssize_t +nouveau_hwmon_show_temp1_auto_point1_pwm(struct device *d, + struct device_attribute *a, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", 100); +} +static SENSOR_DEVICE_ATTR(temp1_auto_point1_pwm, S_IRUGO, + nouveau_hwmon_show_temp1_auto_point1_pwm, NULL, 0); + +static ssize_t +nouveau_hwmon_temp1_auto_point1_temp(struct device *d, + struct device_attribute *a, char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_FAN_BOOST) * 1000); +} +static ssize_t +nouveau_hwmon_set_temp1_auto_point1_temp(struct device *d, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_FAN_BOOST, + value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IRUGO | S_IWUSR, + nouveau_hwmon_temp1_auto_point1_temp, + nouveau_hwmon_set_temp1_auto_point1_temp, 0); + +static ssize_t +nouveau_hwmon_temp1_auto_point1_temp_hyst(struct device *d, + struct device_attribute *a, char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST) * 1000); +} +static ssize_t +nouveau_hwmon_set_temp1_auto_point1_temp_hyst(struct device *d, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST, + value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp_hyst, S_IRUGO | S_IWUSR, + nouveau_hwmon_temp1_auto_point1_temp_hyst, + nouveau_hwmon_set_temp1_auto_point1_temp_hyst, 0); + +static ssize_t nouveau_hwmon_max_temp(struct device *d, struct device_attribute *a, char *buf) { struct drm_device *dev = dev_get_drvdata(d); @@ -439,6 +514,38 @@ static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, nouveau_hwmon_max_temp, 0); static ssize_t +nouveau_hwmon_max_temp_hyst(struct device *d, struct device_attribute *a, + char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_DOWN_CLK_HYST) * 1000); +} +static ssize_t +nouveau_hwmon_set_max_temp_hyst(struct device *d, struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_DOWN_CLK_HYST, + value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR, + nouveau_hwmon_max_temp_hyst, + nouveau_hwmon_set_max_temp_hyst, 0); + +static ssize_t nouveau_hwmon_critical_temp(struct device *d, struct device_attribute *a, char *buf) { @@ -471,6 +578,107 @@ static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, nouveau_hwmon_set_critical_temp, 0); +static ssize_t +nouveau_hwmon_critical_temp_hyst(struct device *d, struct device_attribute *a, + char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_CRITICAL_HYST) * 1000); +} +static ssize_t +nouveau_hwmon_set_critical_temp_hyst(struct device *d, + struct device_attribute *a, + const char *buf, + size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_CRITICAL_HYST, + value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO | S_IWUSR, + nouveau_hwmon_critical_temp_hyst, + nouveau_hwmon_set_critical_temp_hyst, 0); +static ssize_t +nouveau_hwmon_emergency_temp(struct device *d, struct device_attribute *a, + char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_SHUTDOWN) * 1000); +} +static ssize_t +nouveau_hwmon_set_emergency_temp(struct device *d, struct device_attribute *a, + const char *buf, + size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_SHUTDOWN, value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO | S_IWUSR, + nouveau_hwmon_emergency_temp, + nouveau_hwmon_set_emergency_temp, + 0); + +static ssize_t +nouveau_hwmon_emergency_temp_hyst(struct device *d, struct device_attribute *a, + char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + + return snprintf(buf, PAGE_SIZE, "%d\n", + therm->attr_get(therm, NOUVEAU_THERM_ATTR_THRS_SHUTDOWN_HYST) * 1000); +} +static ssize_t +nouveau_hwmon_set_emergency_temp_hyst(struct device *d, + struct device_attribute *a, + const char *buf, + size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_therm *therm = nouveau_therm(drm->device); + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + therm->attr_set(therm, NOUVEAU_THERM_ATTR_THRS_SHUTDOWN_HYST, + value / 1000); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_emergency_hyst, S_IRUGO | S_IWUSR, + nouveau_hwmon_emergency_temp_hyst, + nouveau_hwmon_set_emergency_temp_hyst, + 0); + static ssize_t nouveau_hwmon_show_name(struct device *dev, struct device_attribute *attr, char *buf) @@ -490,7 +698,7 @@ static SENSOR_DEVICE_ATTR(update_rate, S_IRUGO, NULL, 0); static ssize_t -nouveau_hwmon_show_fan0_input(struct device *d, struct device_attribute *attr, +nouveau_hwmon_show_fan1_input(struct device *d, struct device_attribute *attr, char *buf) { struct drm_device *dev = dev_get_drvdata(d); @@ -499,7 +707,7 @@ nouveau_hwmon_show_fan0_input(struct device *d, struct device_attribute *attr, return snprintf(buf, PAGE_SIZE, "%d\n", therm->fan_sense(therm)); } -static SENSOR_DEVICE_ATTR(fan0_input, S_IRUGO, nouveau_hwmon_show_fan0_input, +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, nouveau_hwmon_show_fan1_input, NULL, 0); static ssize_t @@ -665,14 +873,21 @@ static SENSOR_DEVICE_ATTR(pwm1_max, S_IRUGO | S_IWUSR, static struct attribute *hwmon_attributes[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point1_temp_hyst.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_emergency.dev_attr.attr, + &sensor_dev_attr_temp1_emergency_hyst.dev_attr.attr, &sensor_dev_attr_name.dev_attr.attr, &sensor_dev_attr_update_rate.dev_attr.attr, NULL }; static struct attribute *hwmon_fan_rpm_attributes[] = { - &sensor_dev_attr_fan0_input.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, NULL }; static struct attribute *hwmon_pwm_fan_attributes[] = { @@ -717,7 +932,7 @@ nouveau_hwmon_init(struct drm_device *dev) dev_set_drvdata(hwmon_dev, dev); /* default sysfs entries */ - ret = sysfs_create_group(&dev->pdev->dev.kobj, &hwmon_attrgroup); + ret = sysfs_create_group(&hwmon_dev->kobj, &hwmon_attrgroup); if (ret) { if (ret) goto error; @@ -728,7 +943,7 @@ nouveau_hwmon_init(struct drm_device *dev) * the gpio entries for pwm fan control even when there's no * actual fan connected to it... therm table? */ if (therm->fan_get && therm->fan_get(therm) >= 0) { - ret = sysfs_create_group(&dev->pdev->dev.kobj, + ret = sysfs_create_group(&hwmon_dev->kobj, &hwmon_pwm_fan_attrgroup); if (ret) goto error; @@ -736,7 +951,7 @@ nouveau_hwmon_init(struct drm_device *dev) /* if the card can read the fan rpm */ if (therm->fan_sense(therm) >= 0) { - ret = sysfs_create_group(&dev->pdev->dev.kobj, + ret = sysfs_create_group(&hwmon_dev->kobj, &hwmon_fan_rpm_attrgroup); if (ret) goto error; @@ -764,10 +979,10 @@ nouveau_hwmon_fini(struct drm_device *dev) struct nouveau_pm *pm = nouveau_pm(dev); if (pm->hwmon) { - sysfs_remove_group(&dev->pdev->dev.kobj, &hwmon_attrgroup); - sysfs_remove_group(&dev->pdev->dev.kobj, + sysfs_remove_group(&pm->hwmon->kobj, &hwmon_attrgroup); + sysfs_remove_group(&pm->hwmon->kobj, &hwmon_pwm_fan_attrgroup); - sysfs_remove_group(&dev->pdev->dev.kobj, + sysfs_remove_group(&pm->hwmon->kobj, &hwmon_fan_rpm_attrgroup); hwmon_device_unregister(pm->hwmon); diff --git a/drivers/gpu/drm/nouveau/nv04_dfp.c b/drivers/gpu/drm/nouveau/nv04_dfp.c index 39ffc07f906b..7e24cdf1cb39 100644 --- a/drivers/gpu/drm/nouveau/nv04_dfp.c +++ b/drivers/gpu/drm/nouveau/nv04_dfp.c @@ -490,8 +490,8 @@ static void nv04_dfp_update_backlight(struct drm_encoder *encoder, int mode) /* BIOS scripts usually take care of the backlight, thanks * Apple for your consistency. */ - if (dev->pci_device == 0x0179 || dev->pci_device == 0x0189 || - dev->pci_device == 0x0329) { + if (dev->pci_device == 0x0174 || dev->pci_device == 0x0179 || + dev->pci_device == 0x0189 || dev->pci_device == 0x0329) { if (mode == DRM_MODE_DPMS_ON) { nv_mask(device, NV_PBUS_DEBUG_DUALHEAD_CTL, 0, 1 << 31); nv_mask(device, NV_PCRTC_GPIO_EXT, 3, 1); diff --git a/drivers/gpu/drm/nouveau/nv04_display.c b/drivers/gpu/drm/nouveau/nv04_display.c index 4c6e9f83fe82..ad48444c385c 100644 --- a/drivers/gpu/drm/nouveau/nv04_display.c +++ b/drivers/gpu/drm/nouveau/nv04_display.c @@ -22,6 +22,9 @@ * Author: Ben Skeggs */ +#include <core/object.h> +#include <core/class.h> + #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> @@ -31,6 +34,8 @@ #include "nouveau_encoder.h" #include "nouveau_connector.h" +#include <subdev/i2c.h> + int nv04_display_early_init(struct drm_device *dev) { @@ -53,6 +58,7 @@ int nv04_display_create(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); + struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct dcb_table *dcb = &drm->vbios.dcb; struct drm_connector *connector, *ct; struct drm_encoder *encoder; @@ -71,6 +77,11 @@ nv04_display_create(struct drm_device *dev) nouveau_hw_save_vga_fonts(dev, 1); + ret = nouveau_object_new(nv_object(drm), NVDRM_DEVICE, 0xd1500000, + NV04_DISP_CLASS, NULL, 0, &disp->core); + if (ret) + return ret; + nv04_crtc_create(dev, 0); if (nv_two_heads(dev)) nv04_crtc_create(dev, 1); @@ -114,6 +125,11 @@ nv04_display_create(struct drm_device *dev) } } + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + nv_encoder->i2c = i2c->find(i2c, nv_encoder->dcb->i2c_index); + } + /* Save previous state */ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) crtc->funcs->save(crtc); diff --git a/drivers/gpu/drm/nouveau/nv04_display.h b/drivers/gpu/drm/nouveau/nv04_display.h index 45322802e37d..a0a031dad13f 100644 --- a/drivers/gpu/drm/nouveau/nv04_display.h +++ b/drivers/gpu/drm/nouveau/nv04_display.h @@ -80,6 +80,7 @@ struct nv04_display { struct nv04_mode_state saved_reg; uint32_t saved_vga_font[4][16384]; uint32_t dac_users[4]; + struct nouveau_object *core; }; static inline struct nv04_display * diff --git a/drivers/gpu/drm/nouveau/nv04_fence.c b/drivers/gpu/drm/nouveau/nv04_fence.c index a220b94ba9f2..94eadd1dd10a 100644 --- a/drivers/gpu/drm/nouveau/nv04_fence.c +++ b/drivers/gpu/drm/nouveau/nv04_fence.c @@ -78,6 +78,9 @@ nv04_fence_context_new(struct nouveau_channel *chan) struct nv04_fence_chan *fctx = kzalloc(sizeof(*fctx), GFP_KERNEL); if (fctx) { nouveau_fence_context_new(&fctx->base); + fctx->base.emit = nv04_fence_emit; + fctx->base.sync = nv04_fence_sync; + fctx->base.read = nv04_fence_read; chan->fence = fctx; return 0; } @@ -104,8 +107,5 @@ nv04_fence_create(struct nouveau_drm *drm) priv->base.dtor = nv04_fence_destroy; priv->base.context_new = nv04_fence_context_new; priv->base.context_del = nv04_fence_context_del; - priv->base.emit = nv04_fence_emit; - priv->base.sync = nv04_fence_sync; - priv->base.read = nv04_fence_read; return 0; } diff --git a/drivers/gpu/drm/nouveau/nv04_tv.c b/drivers/gpu/drm/nouveau/nv04_tv.c index 62e826a139b3..4a69ccdef9b4 100644 --- a/drivers/gpu/drm/nouveau/nv04_tv.c +++ b/drivers/gpu/drm/nouveau/nv04_tv.c @@ -184,14 +184,23 @@ static const struct drm_encoder_funcs nv04_tv_funcs = { .destroy = nv04_tv_destroy, }; +static const struct drm_encoder_helper_funcs nv04_tv_helper_funcs = { + .dpms = nv04_tv_dpms, + .save = drm_i2c_encoder_save, + .restore = drm_i2c_encoder_restore, + .mode_fixup = drm_i2c_encoder_mode_fixup, + .prepare = nv04_tv_prepare, + .commit = nv04_tv_commit, + .mode_set = nv04_tv_mode_set, + .detect = drm_i2c_encoder_detect, +}; + int nv04_tv_create(struct drm_connector *connector, struct dcb_output *entry) { struct nouveau_encoder *nv_encoder; struct drm_encoder *encoder; struct drm_device *dev = connector->dev; - struct drm_encoder_helper_funcs *hfuncs; - struct drm_encoder_slave_funcs *sfuncs; struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct nouveau_i2c_port *port = i2c->find(i2c, entry->i2c_index); @@ -207,17 +216,11 @@ nv04_tv_create(struct drm_connector *connector, struct dcb_output *entry) if (!nv_encoder) return -ENOMEM; - hfuncs = kzalloc(sizeof(*hfuncs), GFP_KERNEL); - if (!hfuncs) { - ret = -ENOMEM; - goto fail_free; - } - /* Initialize the common members */ encoder = to_drm_encoder(nv_encoder); drm_encoder_init(dev, encoder, &nv04_tv_funcs, DRM_MODE_ENCODER_TVDAC); - drm_encoder_helper_add(encoder, hfuncs); + drm_encoder_helper_add(encoder, &nv04_tv_helper_funcs); encoder->possible_crtcs = entry->heads; encoder->possible_clones = 0; @@ -230,30 +233,14 @@ nv04_tv_create(struct drm_connector *connector, struct dcb_output *entry) if (ret < 0) goto fail_cleanup; - /* Fill the function pointers */ - sfuncs = get_slave_funcs(encoder); - - *hfuncs = (struct drm_encoder_helper_funcs) { - .dpms = nv04_tv_dpms, - .save = sfuncs->save, - .restore = sfuncs->restore, - .mode_fixup = sfuncs->mode_fixup, - .prepare = nv04_tv_prepare, - .commit = nv04_tv_commit, - .mode_set = nv04_tv_mode_set, - .detect = sfuncs->detect, - }; - /* Attach it to the specified connector. */ - sfuncs->create_resources(encoder, connector); + get_slave_funcs(encoder)->create_resources(encoder, connector); drm_mode_connector_attach_encoder(connector, encoder); return 0; fail_cleanup: drm_encoder_cleanup(encoder); - kfree(hfuncs); -fail_free: kfree(nv_encoder); return ret; } diff --git a/drivers/gpu/drm/nouveau/nv10_fence.c b/drivers/gpu/drm/nouveau/nv10_fence.c index 03017f24d593..06f434f03fba 100644 --- a/drivers/gpu/drm/nouveau/nv10_fence.c +++ b/drivers/gpu/drm/nouveau/nv10_fence.c @@ -27,18 +27,7 @@ #include "nouveau_drm.h" #include "nouveau_dma.h" -#include "nouveau_fence.h" - -struct nv10_fence_chan { - struct nouveau_fence_chan base; -}; - -struct nv10_fence_priv { - struct nouveau_fence_priv base; - struct nouveau_bo *bo; - spinlock_t lock; - u32 sequence; -}; +#include "nv10_fence.h" int nv10_fence_emit(struct nouveau_fence *fence) @@ -61,45 +50,6 @@ nv10_fence_sync(struct nouveau_fence *fence, return -ENODEV; } -int -nv17_fence_sync(struct nouveau_fence *fence, - struct nouveau_channel *prev, struct nouveau_channel *chan) -{ - struct nv10_fence_priv *priv = chan->drm->fence; - u32 value; - int ret; - - if (!mutex_trylock(&prev->cli->mutex)) - return -EBUSY; - - spin_lock(&priv->lock); - value = priv->sequence; - priv->sequence += 2; - spin_unlock(&priv->lock); - - ret = RING_SPACE(prev, 5); - if (!ret) { - BEGIN_NV04(prev, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 4); - OUT_RING (prev, NvSema); - OUT_RING (prev, 0); - OUT_RING (prev, value + 0); - OUT_RING (prev, value + 1); - FIRE_RING (prev); - } - - if (!ret && !(ret = RING_SPACE(chan, 5))) { - BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 4); - OUT_RING (chan, NvSema); - OUT_RING (chan, 0); - OUT_RING (chan, value + 1); - OUT_RING (chan, value + 2); - FIRE_RING (chan); - } - - mutex_unlock(&prev->cli->mutex); - return 0; -} - u32 nv10_fence_read(struct nouveau_channel *chan) { @@ -115,39 +65,20 @@ nv10_fence_context_del(struct nouveau_channel *chan) kfree(fctx); } -static int +int nv10_fence_context_new(struct nouveau_channel *chan) { - struct nv10_fence_priv *priv = chan->drm->fence; struct nv10_fence_chan *fctx; - int ret = 0; fctx = chan->fence = kzalloc(sizeof(*fctx), GFP_KERNEL); if (!fctx) return -ENOMEM; nouveau_fence_context_new(&fctx->base); - - if (priv->bo) { - struct ttm_mem_reg *mem = &priv->bo->bo.mem; - struct nouveau_object *object; - u32 start = mem->start * PAGE_SIZE; - u32 limit = mem->start + mem->size - 1; - - ret = nouveau_object_new(nv_object(chan->cli), chan->handle, - NvSema, 0x0002, - &(struct nv_dma_class) { - .flags = NV_DMA_TARGET_VRAM | - NV_DMA_ACCESS_RDWR, - .start = start, - .limit = limit, - }, sizeof(struct nv_dma_class), - &object); - } - - if (ret) - nv10_fence_context_del(chan); - return ret; + fctx->base.emit = nv10_fence_emit; + fctx->base.read = nv10_fence_read; + fctx->base.sync = nv10_fence_sync; + return 0; } void @@ -162,18 +93,10 @@ nv10_fence_destroy(struct nouveau_drm *drm) kfree(priv); } -void nv17_fence_resume(struct nouveau_drm *drm) -{ - struct nv10_fence_priv *priv = drm->fence; - - nouveau_bo_wr32(priv->bo, 0, priv->sequence); -} - int nv10_fence_create(struct nouveau_drm *drm) { struct nv10_fence_priv *priv; - int ret = 0; priv = drm->fence = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) @@ -182,33 +105,6 @@ nv10_fence_create(struct nouveau_drm *drm) priv->base.dtor = nv10_fence_destroy; priv->base.context_new = nv10_fence_context_new; priv->base.context_del = nv10_fence_context_del; - priv->base.emit = nv10_fence_emit; - priv->base.read = nv10_fence_read; - priv->base.sync = nv10_fence_sync; spin_lock_init(&priv->lock); - - if (nv_device(drm->device)->chipset >= 0x17) { - ret = nouveau_bo_new(drm->dev, 4096, 0x1000, TTM_PL_FLAG_VRAM, - 0, 0x0000, NULL, &priv->bo); - if (!ret) { - ret = nouveau_bo_pin(priv->bo, TTM_PL_FLAG_VRAM); - if (!ret) { - ret = nouveau_bo_map(priv->bo); - if (ret) - nouveau_bo_unpin(priv->bo); - } - if (ret) - nouveau_bo_ref(NULL, &priv->bo); - } - - if (ret == 0) { - nouveau_bo_wr32(priv->bo, 0x000, 0x00000000); - priv->base.sync = nv17_fence_sync; - priv->base.resume = nv17_fence_resume; - } - } - - if (ret) - nv10_fence_destroy(drm); - return ret; + return 0; } diff --git a/drivers/gpu/drm/nouveau/nv10_fence.h b/drivers/gpu/drm/nouveau/nv10_fence.h new file mode 100644 index 000000000000..e5d9204826c2 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv10_fence.h @@ -0,0 +1,19 @@ +#ifndef __NV10_FENCE_H_ +#define __NV10_FENCE_H_ + +#include <core/os.h> +#include "nouveau_fence.h" +#include "nouveau_bo.h" + +struct nv10_fence_chan { + struct nouveau_fence_chan base; +}; + +struct nv10_fence_priv { + struct nouveau_fence_priv base; + struct nouveau_bo *bo; + spinlock_t lock; + u32 sequence; +}; + +#endif diff --git a/drivers/gpu/drm/nouveau/nv17_fence.c b/drivers/gpu/drm/nouveau/nv17_fence.c new file mode 100644 index 000000000000..8e47a9bae8c3 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv17_fence.c @@ -0,0 +1,149 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs <bskeggs@redhat.com> + */ + +#include <core/object.h> +#include <core/class.h> + +#include "nouveau_drm.h" +#include "nouveau_dma.h" +#include "nv10_fence.h" + +int +nv17_fence_sync(struct nouveau_fence *fence, + struct nouveau_channel *prev, struct nouveau_channel *chan) +{ + struct nv10_fence_priv *priv = chan->drm->fence; + u32 value; + int ret; + + if (!mutex_trylock(&prev->cli->mutex)) + return -EBUSY; + + spin_lock(&priv->lock); + value = priv->sequence; + priv->sequence += 2; + spin_unlock(&priv->lock); + + ret = RING_SPACE(prev, 5); + if (!ret) { + BEGIN_NV04(prev, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 4); + OUT_RING (prev, NvSema); + OUT_RING (prev, 0); + OUT_RING (prev, value + 0); + OUT_RING (prev, value + 1); + FIRE_RING (prev); + } + + if (!ret && !(ret = RING_SPACE(chan, 5))) { + BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 4); + OUT_RING (chan, NvSema); + OUT_RING (chan, 0); + OUT_RING (chan, value + 1); + OUT_RING (chan, value + 2); + FIRE_RING (chan); + } + + mutex_unlock(&prev->cli->mutex); + return 0; +} + +static int +nv17_fence_context_new(struct nouveau_channel *chan) +{ + struct nv10_fence_priv *priv = chan->drm->fence; + struct nv10_fence_chan *fctx; + struct ttm_mem_reg *mem = &priv->bo->bo.mem; + struct nouveau_object *object; + u32 start = mem->start * PAGE_SIZE; + u32 limit = mem->start + mem->size - 1; + int ret = 0; + + fctx = chan->fence = kzalloc(sizeof(*fctx), GFP_KERNEL); + if (!fctx) + return -ENOMEM; + + nouveau_fence_context_new(&fctx->base); + fctx->base.emit = nv10_fence_emit; + fctx->base.read = nv10_fence_read; + fctx->base.sync = nv17_fence_sync; + + ret = nouveau_object_new(nv_object(chan->cli), chan->handle, + NvSema, 0x0002, + &(struct nv_dma_class) { + .flags = NV_DMA_TARGET_VRAM | + NV_DMA_ACCESS_RDWR, + .start = start, + .limit = limit, + }, sizeof(struct nv_dma_class), + &object); + if (ret) + nv10_fence_context_del(chan); + return ret; +} + +void +nv17_fence_resume(struct nouveau_drm *drm) +{ + struct nv10_fence_priv *priv = drm->fence; + + nouveau_bo_wr32(priv->bo, 0, priv->sequence); +} + +int +nv17_fence_create(struct nouveau_drm *drm) +{ + struct nv10_fence_priv *priv; + int ret = 0; + + priv = drm->fence = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base.dtor = nv10_fence_destroy; + priv->base.resume = nv17_fence_resume; + priv->base.context_new = nv17_fence_context_new; + priv->base.context_del = nv10_fence_context_del; + spin_lock_init(&priv->lock); + + ret = nouveau_bo_new(drm->dev, 4096, 0x1000, TTM_PL_FLAG_VRAM, + 0, 0x0000, NULL, &priv->bo); + if (!ret) { + ret = nouveau_bo_pin(priv->bo, TTM_PL_FLAG_VRAM); + if (!ret) { + ret = nouveau_bo_map(priv->bo); + if (ret) + nouveau_bo_unpin(priv->bo); + } + if (ret) + nouveau_bo_ref(NULL, &priv->bo); + } + + if (ret) { + nv10_fence_destroy(drm); + return ret; + } + + nouveau_bo_wr32(priv->bo, 0x000, 0x00000000); + return ret; +} diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c index d4cbea19b890..a6237c9cbbc3 100644 --- a/drivers/gpu/drm/nouveau/nv50_display.c +++ b/drivers/gpu/drm/nouveau/nv50_display.c @@ -43,6 +43,7 @@ #include <subdev/timer.h> #include <subdev/bar.h> #include <subdev/fb.h> +#include <subdev/i2c.h> #define EVO_DMA_NR 9 @@ -433,7 +434,10 @@ evo_kick(u32 *push, void *evoc) static bool evo_sync_wait(void *data) { - return nouveau_bo_rd32(data, EVO_MAST_NTFY) != 0x00000000; + if (nouveau_bo_rd32(data, EVO_MAST_NTFY) != 0x00000000) + return true; + usleep_range(1, 2); + return false; } static int @@ -512,7 +516,7 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb, if (ret) return ret; - if (nv_mclass(chan->object) < NVC0_CHANNEL_IND_CLASS) { + if (nv_mclass(chan->object) < NV84_CHANNEL_IND_CLASS) { BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 2); OUT_RING (chan, NvEvoSema0 + nv_crtc->index); OUT_RING (chan, sync->sem.offset); @@ -522,24 +526,36 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb, OUT_RING (chan, sync->sem.offset ^ 0x10); OUT_RING (chan, 0x74b1e000); BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1); - if (nv_mclass(chan->object) < NV84_CHANNEL_DMA_CLASS) - OUT_RING (chan, NvSema); - else - OUT_RING (chan, chan->vram); + OUT_RING (chan, NvSema); + } else + if (nv_mclass(chan->object) < NVC0_CHANNEL_IND_CLASS) { + u64 offset = nv84_fence_crtc(chan, nv_crtc->index); + offset += sync->sem.offset; + + BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset)); + OUT_RING (chan, 0xf00d0000 | sync->sem.value); + OUT_RING (chan, 0x00000002); + BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset ^ 0x10)); + OUT_RING (chan, 0x74b1e000); + OUT_RING (chan, 0x00000001); } else { - u64 offset = nvc0_fence_crtc(chan, nv_crtc->index); + u64 offset = nv84_fence_crtc(chan, nv_crtc->index); offset += sync->sem.offset; BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); OUT_RING (chan, upper_32_bits(offset)); OUT_RING (chan, lower_32_bits(offset)); OUT_RING (chan, 0xf00d0000 | sync->sem.value); - OUT_RING (chan, 0x1002); + OUT_RING (chan, 0x00001002); BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); OUT_RING (chan, upper_32_bits(offset)); OUT_RING (chan, lower_32_bits(offset ^ 0x10)); OUT_RING (chan, 0x74b1e000); - OUT_RING (chan, 0x1001); + OUT_RING (chan, 0x00001001); } FIRE_RING (chan); @@ -1503,9 +1519,6 @@ nv50_dac_disconnect(struct drm_encoder *encoder) evo_mthd(push, 0x0180 + (or * 0x020), 1); evo_data(push, 0x00000000); } - - evo_mthd(push, 0x0080, 1); - evo_data(push, 0x00000000); evo_kick(push, mast); } } @@ -1552,20 +1565,23 @@ static const struct drm_encoder_funcs nv50_dac_func = { static int nv50_dac_create(struct drm_connector *connector, struct dcb_output *dcbe) { - struct drm_device *dev = connector->dev; + struct nouveau_drm *drm = nouveau_drm(connector->dev); + struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct nouveau_encoder *nv_encoder; struct drm_encoder *encoder; + int type = DRM_MODE_ENCODER_DAC; nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); if (!nv_encoder) return -ENOMEM; nv_encoder->dcb = dcbe; nv_encoder->or = ffs(dcbe->or) - 1; + nv_encoder->i2c = i2c->find(i2c, dcbe->i2c_index); encoder = to_drm_encoder(nv_encoder); encoder->possible_crtcs = dcbe->heads; encoder->possible_clones = 0; - drm_encoder_init(dev, encoder, &nv50_dac_func, DRM_MODE_ENCODER_DAC); + drm_encoder_init(connector->dev, encoder, &nv50_dac_func, type); drm_encoder_helper_add(encoder, &nv50_dac_hfunc); drm_mode_connector_attach_encoder(connector, encoder); @@ -1674,9 +1690,6 @@ nv50_sor_dpms(struct drm_encoder *encoder, int mode) } nv_call(disp->core, NV50_DISP_SOR_PWR + or, (mode == DRM_MODE_DPMS_ON)); - - if (nv_encoder->dcb->type == DCB_OUTPUT_DP) - nouveau_dp_dpms(encoder, mode, nv_encoder->dp.datarate, disp->core); } static bool @@ -1719,9 +1732,6 @@ nv50_sor_disconnect(struct drm_encoder *encoder) evo_mthd(push, 0x0200 + (or * 0x20), 1); evo_data(push, 0x00000000); } - - evo_mthd(push, 0x0080, 1); - evo_data(push, 0x00000000); evo_kick(push, mast); } @@ -1733,14 +1743,6 @@ nv50_sor_disconnect(struct drm_encoder *encoder) } static void -nv50_sor_prepare(struct drm_encoder *encoder) -{ - nv50_sor_disconnect(encoder); - if (nouveau_encoder(encoder)->dcb->type == DCB_OUTPUT_DP) - evo_sync(encoder->dev); -} - -static void nv50_sor_commit(struct drm_encoder *encoder) { } @@ -1835,8 +1837,13 @@ nv50_sor_mode_set(struct drm_encoder *encoder, struct drm_display_mode *umode, push = evo_wait(nv50_mast(dev), 8); if (push) { if (nv50_vers(mast) < NVD0_DISP_CLASS) { + u32 ctrl = (depth << 16) | (proto << 8) | owner; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + ctrl |= 0x00001000; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ctrl |= 0x00002000; evo_mthd(push, 0x0600 + (nv_encoder->or * 0x040), 1); - evo_data(push, (depth << 16) | (proto << 8) | owner); + evo_data(push, ctrl); } else { u32 magic = 0x31ec6000 | (nv_crtc->index << 25); u32 syncs = 0x00000001; @@ -1872,7 +1879,7 @@ nv50_sor_destroy(struct drm_encoder *encoder) static const struct drm_encoder_helper_funcs nv50_sor_hfunc = { .dpms = nv50_sor_dpms, .mode_fixup = nv50_sor_mode_fixup, - .prepare = nv50_sor_prepare, + .prepare = nv50_sor_disconnect, .commit = nv50_sor_commit, .mode_set = nv50_sor_mode_set, .disable = nv50_sor_disconnect, @@ -1886,21 +1893,33 @@ static const struct drm_encoder_funcs nv50_sor_func = { static int nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe) { - struct drm_device *dev = connector->dev; + struct nouveau_drm *drm = nouveau_drm(connector->dev); + struct nouveau_i2c *i2c = nouveau_i2c(drm->device); struct nouveau_encoder *nv_encoder; struct drm_encoder *encoder; + int type; + + switch (dcbe->type) { + case DCB_OUTPUT_LVDS: type = DRM_MODE_ENCODER_LVDS; break; + case DCB_OUTPUT_TMDS: + case DCB_OUTPUT_DP: + default: + type = DRM_MODE_ENCODER_TMDS; + break; + } nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); if (!nv_encoder) return -ENOMEM; nv_encoder->dcb = dcbe; nv_encoder->or = ffs(dcbe->or) - 1; + nv_encoder->i2c = i2c->find(i2c, dcbe->i2c_index); nv_encoder->last_dpms = DRM_MODE_DPMS_OFF; encoder = to_drm_encoder(nv_encoder); encoder->possible_crtcs = dcbe->heads; encoder->possible_clones = 0; - drm_encoder_init(dev, encoder, &nv50_sor_func, DRM_MODE_ENCODER_TMDS); + drm_encoder_init(connector->dev, encoder, &nv50_sor_func, type); drm_encoder_helper_add(encoder, &nv50_sor_hfunc); drm_mode_connector_attach_encoder(connector, encoder); @@ -1908,6 +1927,181 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe) } /****************************************************************************** + * PIOR + *****************************************************************************/ + +static void +nv50_pior_dpms(struct drm_encoder *encoder, int mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nv50_disp *disp = nv50_disp(encoder->dev); + u32 mthd = (nv_encoder->dcb->type << 12) | nv_encoder->or; + u32 ctrl = (mode == DRM_MODE_DPMS_ON); + nv_call(disp->core, NV50_DISP_PIOR_PWR + mthd, ctrl); +} + +static bool +nv50_pior_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_connector *nv_connector; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + if (nv_connector && nv_connector->native_mode) { + if (nv_connector->scaling_mode != DRM_MODE_SCALE_NONE) { + int id = adjusted_mode->base.id; + *adjusted_mode = *nv_connector->native_mode; + adjusted_mode->base.id = id; + } + } + + adjusted_mode->clock *= 2; + return true; +} + +static void +nv50_pior_commit(struct drm_encoder *encoder) +{ +} + +static void +nv50_pior_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nv50_mast *mast = nv50_mast(encoder->dev); + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct nouveau_connector *nv_connector; + u8 owner = 1 << nv_crtc->index; + u8 proto, depth; + u32 *push; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + switch (nv_connector->base.display_info.bpc) { + case 10: depth = 0x6; break; + case 8: depth = 0x5; break; + case 6: depth = 0x2; break; + default: depth = 0x0; break; + } + + switch (nv_encoder->dcb->type) { + case DCB_OUTPUT_TMDS: + case DCB_OUTPUT_DP: + proto = 0x0; + break; + default: + BUG_ON(1); + break; + } + + nv50_pior_dpms(encoder, DRM_MODE_DPMS_ON); + + push = evo_wait(mast, 8); + if (push) { + if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { + u32 ctrl = (depth << 16) | (proto << 8) | owner; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + ctrl |= 0x00001000; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ctrl |= 0x00002000; + evo_mthd(push, 0x0700 + (nv_encoder->or * 0x040), 1); + evo_data(push, ctrl); + } + + evo_kick(push, mast); + } + + nv_encoder->crtc = encoder->crtc; +} + +static void +nv50_pior_disconnect(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nv50_mast *mast = nv50_mast(encoder->dev); + const int or = nv_encoder->or; + u32 *push; + + if (nv_encoder->crtc) { + nv50_crtc_prepare(nv_encoder->crtc); + + push = evo_wait(mast, 4); + if (push) { + if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { + evo_mthd(push, 0x0700 + (or * 0x040), 1); + evo_data(push, 0x00000000); + } + evo_kick(push, mast); + } + } + + nv_encoder->crtc = NULL; +} + +static void +nv50_pior_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +static const struct drm_encoder_helper_funcs nv50_pior_hfunc = { + .dpms = nv50_pior_dpms, + .mode_fixup = nv50_pior_mode_fixup, + .prepare = nv50_pior_disconnect, + .commit = nv50_pior_commit, + .mode_set = nv50_pior_mode_set, + .disable = nv50_pior_disconnect, + .get_crtc = nv50_display_crtc_get, +}; + +static const struct drm_encoder_funcs nv50_pior_func = { + .destroy = nv50_pior_destroy, +}; + +static int +nv50_pior_create(struct drm_connector *connector, struct dcb_output *dcbe) +{ + struct nouveau_drm *drm = nouveau_drm(connector->dev); + struct nouveau_i2c *i2c = nouveau_i2c(drm->device); + struct nouveau_i2c_port *ddc = NULL; + struct nouveau_encoder *nv_encoder; + struct drm_encoder *encoder; + int type; + + switch (dcbe->type) { + case DCB_OUTPUT_TMDS: + ddc = i2c->find_type(i2c, NV_I2C_TYPE_EXTDDC(dcbe->extdev)); + type = DRM_MODE_ENCODER_TMDS; + break; + case DCB_OUTPUT_DP: + ddc = i2c->find_type(i2c, NV_I2C_TYPE_EXTAUX(dcbe->extdev)); + type = DRM_MODE_ENCODER_TMDS; + break; + default: + return -ENODEV; + } + + nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); + if (!nv_encoder) + return -ENOMEM; + nv_encoder->dcb = dcbe; + nv_encoder->or = ffs(dcbe->or) - 1; + nv_encoder->i2c = ddc; + + encoder = to_drm_encoder(nv_encoder); + encoder->possible_crtcs = dcbe->heads; + encoder->possible_clones = 0; + drm_encoder_init(connector->dev, encoder, &nv50_pior_func, type); + drm_encoder_helper_add(encoder, &nv50_pior_hfunc); + + drm_mode_connector_attach_encoder(connector, encoder); + return 0; +} + +/****************************************************************************** * Init *****************************************************************************/ void @@ -1923,7 +2117,7 @@ nv50_display_init(struct drm_device *dev) evo_mthd(push, 0x0088, 1); evo_data(push, NvEvoSync); evo_kick(push, nv50_mast(dev)); - return evo_sync(dev); + return 0; } return -EBUSY; @@ -2029,25 +2223,28 @@ nv50_display_create(struct drm_device *dev) if (IS_ERR(connector)) continue; - if (dcbe->location != DCB_LOC_ON_CHIP) { - NV_WARN(drm, "skipping off-chip encoder %d/%d\n", - dcbe->type, ffs(dcbe->or) - 1); - continue; + if (dcbe->location == DCB_LOC_ON_CHIP) { + switch (dcbe->type) { + case DCB_OUTPUT_TMDS: + case DCB_OUTPUT_LVDS: + case DCB_OUTPUT_DP: + ret = nv50_sor_create(connector, dcbe); + break; + case DCB_OUTPUT_ANALOG: + ret = nv50_dac_create(connector, dcbe); + break; + default: + ret = -ENODEV; + break; + } + } else { + ret = nv50_pior_create(connector, dcbe); } - switch (dcbe->type) { - case DCB_OUTPUT_TMDS: - case DCB_OUTPUT_LVDS: - case DCB_OUTPUT_DP: - nv50_sor_create(connector, dcbe); - break; - case DCB_OUTPUT_ANALOG: - nv50_dac_create(connector, dcbe); - break; - default: - NV_WARN(drm, "skipping unsupported encoder %d/%d\n", - dcbe->type, ffs(dcbe->or) - 1); - continue; + if (ret) { + NV_WARN(drm, "failed to create encoder %d/%d/%d: %d\n", + dcbe->location, dcbe->type, + ffs(dcbe->or) - 1, ret); } } diff --git a/drivers/gpu/drm/nouveau/nv50_fence.c b/drivers/gpu/drm/nouveau/nv50_fence.c index d889f3ac0d41..f9701e567db8 100644 --- a/drivers/gpu/drm/nouveau/nv50_fence.c +++ b/drivers/gpu/drm/nouveau/nv50_fence.c @@ -27,27 +27,16 @@ #include "nouveau_drm.h" #include "nouveau_dma.h" -#include "nouveau_fence.h" +#include "nv10_fence.h" #include "nv50_display.h" -struct nv50_fence_chan { - struct nouveau_fence_chan base; -}; - -struct nv50_fence_priv { - struct nouveau_fence_priv base; - struct nouveau_bo *bo; - spinlock_t lock; - u32 sequence; -}; - static int nv50_fence_context_new(struct nouveau_channel *chan) { struct drm_device *dev = chan->drm->dev; - struct nv50_fence_priv *priv = chan->drm->fence; - struct nv50_fence_chan *fctx; + struct nv10_fence_priv *priv = chan->drm->fence; + struct nv10_fence_chan *fctx; struct ttm_mem_reg *mem = &priv->bo->bo.mem; struct nouveau_object *object; int ret, i; @@ -57,6 +46,9 @@ nv50_fence_context_new(struct nouveau_channel *chan) return -ENOMEM; nouveau_fence_context_new(&fctx->base); + fctx->base.emit = nv10_fence_emit; + fctx->base.read = nv10_fence_read; + fctx->base.sync = nv17_fence_sync; ret = nouveau_object_new(nv_object(chan->cli), chan->handle, NvSema, 0x0002, @@ -91,7 +83,7 @@ nv50_fence_context_new(struct nouveau_channel *chan) int nv50_fence_create(struct nouveau_drm *drm) { - struct nv50_fence_priv *priv; + struct nv10_fence_priv *priv; int ret = 0; priv = drm->fence = kzalloc(sizeof(*priv), GFP_KERNEL); @@ -99,11 +91,9 @@ nv50_fence_create(struct nouveau_drm *drm) return -ENOMEM; priv->base.dtor = nv10_fence_destroy; + priv->base.resume = nv17_fence_resume; priv->base.context_new = nv50_fence_context_new; priv->base.context_del = nv10_fence_context_del; - priv->base.emit = nv10_fence_emit; - priv->base.read = nv10_fence_read; - priv->base.sync = nv17_fence_sync; spin_lock_init(&priv->lock); ret = nouveau_bo_new(drm->dev, 4096, 0x1000, TTM_PL_FLAG_VRAM, @@ -119,13 +109,11 @@ nv50_fence_create(struct nouveau_drm *drm) nouveau_bo_ref(NULL, &priv->bo); } - if (ret == 0) { - nouveau_bo_wr32(priv->bo, 0x000, 0x00000000); - priv->base.sync = nv17_fence_sync; - priv->base.resume = nv17_fence_resume; + if (ret) { + nv10_fence_destroy(drm); + return ret; } - if (ret) - nv10_fence_destroy(drm); + nouveau_bo_wr32(priv->bo, 0x000, 0x00000000); return ret; } diff --git a/drivers/gpu/drm/nouveau/nv84_fence.c b/drivers/gpu/drm/nouveau/nv84_fence.c index c686650584b6..9fd475c89820 100644 --- a/drivers/gpu/drm/nouveau/nv84_fence.c +++ b/drivers/gpu/drm/nouveau/nv84_fence.c @@ -23,6 +23,7 @@ */ #include <core/object.h> +#include <core/client.h> #include <core/class.h> #include <engine/fifo.h> @@ -33,79 +34,115 @@ #include "nv50_display.h" -struct nv84_fence_chan { - struct nouveau_fence_chan base; -}; - -struct nv84_fence_priv { - struct nouveau_fence_priv base; - struct nouveau_gpuobj *mem; -}; +u64 +nv84_fence_crtc(struct nouveau_channel *chan, int crtc) +{ + struct nv84_fence_chan *fctx = chan->fence; + return fctx->dispc_vma[crtc].offset; +} static int -nv84_fence_emit(struct nouveau_fence *fence) +nv84_fence_emit32(struct nouveau_channel *chan, u64 virtual, u32 sequence) { - struct nouveau_channel *chan = fence->channel; - struct nouveau_fifo_chan *fifo = (void *)chan->object; - int ret = RING_SPACE(chan, 7); + int ret = RING_SPACE(chan, 8); if (ret == 0) { BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1); - OUT_RING (chan, NvSema); - BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); - OUT_RING (chan, upper_32_bits(fifo->chid * 16)); - OUT_RING (chan, lower_32_bits(fifo->chid * 16)); - OUT_RING (chan, fence->sequence); + OUT_RING (chan, chan->vram); + BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 5); + OUT_RING (chan, upper_32_bits(virtual)); + OUT_RING (chan, lower_32_bits(virtual)); + OUT_RING (chan, sequence); OUT_RING (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG); + OUT_RING (chan, 0x00000000); FIRE_RING (chan); } return ret; } - static int -nv84_fence_sync(struct nouveau_fence *fence, - struct nouveau_channel *prev, struct nouveau_channel *chan) +nv84_fence_sync32(struct nouveau_channel *chan, u64 virtual, u32 sequence) { - struct nouveau_fifo_chan *fifo = (void *)prev->object; int ret = RING_SPACE(chan, 7); if (ret == 0) { BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1); - OUT_RING (chan, NvSema); + OUT_RING (chan, chan->vram); BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); - OUT_RING (chan, upper_32_bits(fifo->chid * 16)); - OUT_RING (chan, lower_32_bits(fifo->chid * 16)); - OUT_RING (chan, fence->sequence); + OUT_RING (chan, upper_32_bits(virtual)); + OUT_RING (chan, lower_32_bits(virtual)); + OUT_RING (chan, sequence); OUT_RING (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_GEQUAL); FIRE_RING (chan); } return ret; } +static int +nv84_fence_emit(struct nouveau_fence *fence) +{ + struct nouveau_channel *chan = fence->channel; + struct nv84_fence_chan *fctx = chan->fence; + struct nouveau_fifo_chan *fifo = (void *)chan->object; + u64 addr = fifo->chid * 16; + + if (fence->sysmem) + addr += fctx->vma_gart.offset; + else + addr += fctx->vma.offset; + + return fctx->base.emit32(chan, addr, fence->sequence); +} + +static int +nv84_fence_sync(struct nouveau_fence *fence, + struct nouveau_channel *prev, struct nouveau_channel *chan) +{ + struct nv84_fence_chan *fctx = chan->fence; + struct nouveau_fifo_chan *fifo = (void *)prev->object; + u64 addr = fifo->chid * 16; + + if (fence->sysmem) + addr += fctx->vma_gart.offset; + else + addr += fctx->vma.offset; + + return fctx->base.sync32(chan, addr, fence->sequence); +} + static u32 nv84_fence_read(struct nouveau_channel *chan) { struct nouveau_fifo_chan *fifo = (void *)chan->object; struct nv84_fence_priv *priv = chan->drm->fence; - return nv_ro32(priv->mem, fifo->chid * 16); + return nouveau_bo_rd32(priv->bo, fifo->chid * 16/4); } static void nv84_fence_context_del(struct nouveau_channel *chan) { + struct drm_device *dev = chan->drm->dev; + struct nv84_fence_priv *priv = chan->drm->fence; struct nv84_fence_chan *fctx = chan->fence; + int i; + + for (i = 0; i < dev->mode_config.num_crtc; i++) { + struct nouveau_bo *bo = nv50_display_crtc_sema(dev, i); + nouveau_bo_vma_del(bo, &fctx->dispc_vma[i]); + } + + nouveau_bo_vma_del(priv->bo, &fctx->vma_gart); + nouveau_bo_vma_del(priv->bo, &fctx->vma); nouveau_fence_context_del(&fctx->base); chan->fence = NULL; kfree(fctx); } -static int +int nv84_fence_context_new(struct nouveau_channel *chan) { - struct drm_device *dev = chan->drm->dev; struct nouveau_fifo_chan *fifo = (void *)chan->object; + struct nouveau_client *client = nouveau_client(fifo); struct nv84_fence_priv *priv = chan->drm->fence; struct nv84_fence_chan *fctx; - struct nouveau_object *object; int ret, i; fctx = chan->fence = kzalloc(sizeof(*fctx), GFP_KERNEL); @@ -113,44 +150,74 @@ nv84_fence_context_new(struct nouveau_channel *chan) return -ENOMEM; nouveau_fence_context_new(&fctx->base); + fctx->base.emit = nv84_fence_emit; + fctx->base.sync = nv84_fence_sync; + fctx->base.read = nv84_fence_read; + fctx->base.emit32 = nv84_fence_emit32; + fctx->base.sync32 = nv84_fence_sync32; - ret = nouveau_object_new(nv_object(chan->cli), chan->handle, - NvSema, 0x0002, - &(struct nv_dma_class) { - .flags = NV_DMA_TARGET_VRAM | - NV_DMA_ACCESS_RDWR, - .start = priv->mem->addr, - .limit = priv->mem->addr + - priv->mem->size - 1, - }, sizeof(struct nv_dma_class), - &object); - - /* dma objects for display sync channel semaphore blocks */ - for (i = 0; !ret && i < dev->mode_config.num_crtc; i++) { - struct nouveau_bo *bo = nv50_display_crtc_sema(dev, i); + ret = nouveau_bo_vma_add(priv->bo, client->vm, &fctx->vma); + if (ret == 0) { + ret = nouveau_bo_vma_add(priv->bo_gart, client->vm, + &fctx->vma_gart); + } - ret = nouveau_object_new(nv_object(chan->cli), chan->handle, - NvEvoSema0 + i, 0x003d, - &(struct nv_dma_class) { - .flags = NV_DMA_TARGET_VRAM | - NV_DMA_ACCESS_RDWR, - .start = bo->bo.offset, - .limit = bo->bo.offset + 0xfff, - }, sizeof(struct nv_dma_class), - &object); + /* map display semaphore buffers into channel's vm */ + for (i = 0; !ret && i < chan->drm->dev->mode_config.num_crtc; i++) { + struct nouveau_bo *bo = nv50_display_crtc_sema(chan->drm->dev, i); + ret = nouveau_bo_vma_add(bo, client->vm, &fctx->dispc_vma[i]); } + nouveau_bo_wr32(priv->bo, fifo->chid * 16/4, 0x00000000); + if (ret) nv84_fence_context_del(chan); - nv_wo32(priv->mem, fifo->chid * 16, 0x00000000); return ret; } +static bool +nv84_fence_suspend(struct nouveau_drm *drm) +{ + struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); + struct nv84_fence_priv *priv = drm->fence; + int i; + + priv->suspend = vmalloc((pfifo->max + 1) * sizeof(u32)); + if (priv->suspend) { + for (i = 0; i <= pfifo->max; i++) + priv->suspend[i] = nouveau_bo_rd32(priv->bo, i*4); + } + + return priv->suspend != NULL; +} + +static void +nv84_fence_resume(struct nouveau_drm *drm) +{ + struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); + struct nv84_fence_priv *priv = drm->fence; + int i; + + if (priv->suspend) { + for (i = 0; i <= pfifo->max; i++) + nouveau_bo_wr32(priv->bo, i*4, priv->suspend[i]); + vfree(priv->suspend); + priv->suspend = NULL; + } +} + static void nv84_fence_destroy(struct nouveau_drm *drm) { struct nv84_fence_priv *priv = drm->fence; - nouveau_gpuobj_ref(NULL, &priv->mem); + nouveau_bo_unmap(priv->bo_gart); + if (priv->bo_gart) + nouveau_bo_unpin(priv->bo_gart); + nouveau_bo_ref(NULL, &priv->bo_gart); + nouveau_bo_unmap(priv->bo); + if (priv->bo) + nouveau_bo_unpin(priv->bo); + nouveau_bo_ref(NULL, &priv->bo); drm->fence = NULL; kfree(priv); } @@ -160,7 +227,6 @@ nv84_fence_create(struct nouveau_drm *drm) { struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); struct nv84_fence_priv *priv; - u32 chan = pfifo->max + 1; int ret; priv = drm->fence = kzalloc(sizeof(*priv), GFP_KERNEL); @@ -168,14 +234,42 @@ nv84_fence_create(struct nouveau_drm *drm) return -ENOMEM; priv->base.dtor = nv84_fence_destroy; + priv->base.suspend = nv84_fence_suspend; + priv->base.resume = nv84_fence_resume; priv->base.context_new = nv84_fence_context_new; priv->base.context_del = nv84_fence_context_del; - priv->base.emit = nv84_fence_emit; - priv->base.sync = nv84_fence_sync; - priv->base.read = nv84_fence_read; - ret = nouveau_gpuobj_new(drm->device, NULL, chan * 16, 0x1000, 0, - &priv->mem); + init_waitqueue_head(&priv->base.waiting); + priv->base.uevent = true; + + ret = nouveau_bo_new(drm->dev, 16 * (pfifo->max + 1), 0, + TTM_PL_FLAG_VRAM, 0, 0, NULL, &priv->bo); + if (ret == 0) { + ret = nouveau_bo_pin(priv->bo, TTM_PL_FLAG_VRAM); + if (ret == 0) { + ret = nouveau_bo_map(priv->bo); + if (ret) + nouveau_bo_unpin(priv->bo); + } + if (ret) + nouveau_bo_ref(NULL, &priv->bo); + } + + if (ret == 0) + ret = nouveau_bo_new(drm->dev, 16 * (pfifo->max + 1), 0, + TTM_PL_FLAG_TT, 0, 0, NULL, + &priv->bo_gart); + if (ret == 0) { + ret = nouveau_bo_pin(priv->bo_gart, TTM_PL_FLAG_TT); + if (ret == 0) { + ret = nouveau_bo_map(priv->bo_gart); + if (ret) + nouveau_bo_unpin(priv->bo_gart); + } + if (ret) + nouveau_bo_ref(NULL, &priv->bo_gart); + } + if (ret) nv84_fence_destroy(drm); return ret; diff --git a/drivers/gpu/drm/nouveau/nvc0_fence.c b/drivers/gpu/drm/nouveau/nvc0_fence.c index 2a56b1b551cb..9566267fbc42 100644 --- a/drivers/gpu/drm/nouveau/nvc0_fence.c +++ b/drivers/gpu/drm/nouveau/nvc0_fence.c @@ -34,203 +34,57 @@ #include "nv50_display.h" -struct nvc0_fence_priv { - struct nouveau_fence_priv base; - struct nouveau_bo *bo; - u32 *suspend; -}; - -struct nvc0_fence_chan { - struct nouveau_fence_chan base; - struct nouveau_vma vma; - struct nouveau_vma dispc_vma[4]; -}; - -u64 -nvc0_fence_crtc(struct nouveau_channel *chan, int crtc) -{ - struct nvc0_fence_chan *fctx = chan->fence; - return fctx->dispc_vma[crtc].offset; -} - static int -nvc0_fence_emit(struct nouveau_fence *fence) +nvc0_fence_emit32(struct nouveau_channel *chan, u64 virtual, u32 sequence) { - struct nouveau_channel *chan = fence->channel; - struct nvc0_fence_chan *fctx = chan->fence; - struct nouveau_fifo_chan *fifo = (void *)chan->object; - u64 addr = fctx->vma.offset + fifo->chid * 16; - int ret; - - ret = RING_SPACE(chan, 5); + int ret = RING_SPACE(chan, 6); if (ret == 0) { - BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); - OUT_RING (chan, upper_32_bits(addr)); - OUT_RING (chan, lower_32_bits(addr)); - OUT_RING (chan, fence->sequence); + BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 5); + OUT_RING (chan, upper_32_bits(virtual)); + OUT_RING (chan, lower_32_bits(virtual)); + OUT_RING (chan, sequence); OUT_RING (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG); + OUT_RING (chan, 0x00000000); FIRE_RING (chan); } - return ret; } static int -nvc0_fence_sync(struct nouveau_fence *fence, - struct nouveau_channel *prev, struct nouveau_channel *chan) +nvc0_fence_sync32(struct nouveau_channel *chan, u64 virtual, u32 sequence) { - struct nvc0_fence_chan *fctx = chan->fence; - struct nouveau_fifo_chan *fifo = (void *)prev->object; - u64 addr = fctx->vma.offset + fifo->chid * 16; - int ret; - - ret = RING_SPACE(chan, 5); + int ret = RING_SPACE(chan, 5); if (ret == 0) { BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); - OUT_RING (chan, upper_32_bits(addr)); - OUT_RING (chan, lower_32_bits(addr)); - OUT_RING (chan, fence->sequence); + OUT_RING (chan, upper_32_bits(virtual)); + OUT_RING (chan, lower_32_bits(virtual)); + OUT_RING (chan, sequence); OUT_RING (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_GEQUAL | NVC0_SUBCHAN_SEMAPHORE_TRIGGER_YIELD); FIRE_RING (chan); } - return ret; } -static u32 -nvc0_fence_read(struct nouveau_channel *chan) -{ - struct nouveau_fifo_chan *fifo = (void *)chan->object; - struct nvc0_fence_priv *priv = chan->drm->fence; - return nouveau_bo_rd32(priv->bo, fifo->chid * 16/4); -} - -static void -nvc0_fence_context_del(struct nouveau_channel *chan) -{ - struct drm_device *dev = chan->drm->dev; - struct nvc0_fence_priv *priv = chan->drm->fence; - struct nvc0_fence_chan *fctx = chan->fence; - int i; - - for (i = 0; i < dev->mode_config.num_crtc; i++) { - struct nouveau_bo *bo = nv50_display_crtc_sema(dev, i); - nouveau_bo_vma_del(bo, &fctx->dispc_vma[i]); - } - - nouveau_bo_vma_del(priv->bo, &fctx->vma); - nouveau_fence_context_del(&fctx->base); - chan->fence = NULL; - kfree(fctx); -} - static int nvc0_fence_context_new(struct nouveau_channel *chan) { - struct nouveau_fifo_chan *fifo = (void *)chan->object; - struct nouveau_client *client = nouveau_client(fifo); - struct nvc0_fence_priv *priv = chan->drm->fence; - struct nvc0_fence_chan *fctx; - int ret, i; - - fctx = chan->fence = kzalloc(sizeof(*fctx), GFP_KERNEL); - if (!fctx) - return -ENOMEM; - - nouveau_fence_context_new(&fctx->base); - - ret = nouveau_bo_vma_add(priv->bo, client->vm, &fctx->vma); - if (ret) - nvc0_fence_context_del(chan); - - /* map display semaphore buffers into channel's vm */ - for (i = 0; !ret && i < chan->drm->dev->mode_config.num_crtc; i++) { - struct nouveau_bo *bo = nv50_display_crtc_sema(chan->drm->dev, i); - ret = nouveau_bo_vma_add(bo, client->vm, &fctx->dispc_vma[i]); + int ret = nv84_fence_context_new(chan); + if (ret == 0) { + struct nv84_fence_chan *fctx = chan->fence; + fctx->base.emit32 = nvc0_fence_emit32; + fctx->base.sync32 = nvc0_fence_sync32; } - - nouveau_bo_wr32(priv->bo, fifo->chid * 16/4, 0x00000000); return ret; } -static bool -nvc0_fence_suspend(struct nouveau_drm *drm) -{ - struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); - struct nvc0_fence_priv *priv = drm->fence; - int i; - - priv->suspend = vmalloc((pfifo->max + 1) * sizeof(u32)); - if (priv->suspend) { - for (i = 0; i <= pfifo->max; i++) - priv->suspend[i] = nouveau_bo_rd32(priv->bo, i); - } - - return priv->suspend != NULL; -} - -static void -nvc0_fence_resume(struct nouveau_drm *drm) -{ - struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); - struct nvc0_fence_priv *priv = drm->fence; - int i; - - if (priv->suspend) { - for (i = 0; i <= pfifo->max; i++) - nouveau_bo_wr32(priv->bo, i, priv->suspend[i]); - vfree(priv->suspend); - priv->suspend = NULL; - } -} - -static void -nvc0_fence_destroy(struct nouveau_drm *drm) -{ - struct nvc0_fence_priv *priv = drm->fence; - nouveau_bo_unmap(priv->bo); - if (priv->bo) - nouveau_bo_unpin(priv->bo); - nouveau_bo_ref(NULL, &priv->bo); - drm->fence = NULL; - kfree(priv); -} - int nvc0_fence_create(struct nouveau_drm *drm) { - struct nouveau_fifo *pfifo = nouveau_fifo(drm->device); - struct nvc0_fence_priv *priv; - int ret; - - priv = drm->fence = kzalloc(sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->base.dtor = nvc0_fence_destroy; - priv->base.suspend = nvc0_fence_suspend; - priv->base.resume = nvc0_fence_resume; - priv->base.context_new = nvc0_fence_context_new; - priv->base.context_del = nvc0_fence_context_del; - priv->base.emit = nvc0_fence_emit; - priv->base.sync = nvc0_fence_sync; - priv->base.read = nvc0_fence_read; - - ret = nouveau_bo_new(drm->dev, 16 * (pfifo->max + 1), 0, - TTM_PL_FLAG_VRAM, 0, 0, NULL, &priv->bo); + int ret = nv84_fence_create(drm); if (ret == 0) { - ret = nouveau_bo_pin(priv->bo, TTM_PL_FLAG_VRAM); - if (ret == 0) { - ret = nouveau_bo_map(priv->bo); - if (ret) - nouveau_bo_unpin(priv->bo); - } - if (ret) - nouveau_bo_ref(NULL, &priv->bo); + struct nv84_fence_priv *priv = drm->fence; + priv->base.context_new = nvc0_fence_context_new; } - - if (ret) - nvc0_fence_destroy(drm); return ret; } diff --git a/drivers/gpu/drm/omapdrm/Kconfig b/drivers/gpu/drm/omapdrm/Kconfig new file mode 100644 index 000000000000..b724a4131435 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/Kconfig @@ -0,0 +1,25 @@ + +config DRM_OMAP + tristate "OMAP DRM" + depends on DRM && !CONFIG_FB_OMAP2 + depends on ARCH_OMAP2PLUS || ARCH_MULTIPLATFORM + select DRM_KMS_HELPER + select OMAP2_DSS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + default n + help + DRM display driver for OMAP2/3/4 based boards. + +config DRM_OMAP_NUM_CRTCS + int "Number of CRTCs" + range 1 10 + default 1 if ARCH_OMAP2 || ARCH_OMAP3 + default 2 if ARCH_OMAP4 + depends on DRM_OMAP + help + Select the number of video overlays which can be used as framebuffers. + The remaining overlays are reserved for video. + diff --git a/drivers/gpu/drm/omapdrm/Makefile b/drivers/gpu/drm/omapdrm/Makefile new file mode 100644 index 000000000000..d85e058f2845 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/Makefile @@ -0,0 +1,24 @@ +# +# Makefile for the drm device driver. This driver provides support for the +# Direct Rendering Infrastructure (DRI) +# + +ccflags-y := -Iinclude/drm -Werror +omapdrm-y := omap_drv.o \ + omap_irq.o \ + omap_debugfs.o \ + omap_crtc.o \ + omap_plane.o \ + omap_encoder.o \ + omap_connector.o \ + omap_fb.o \ + omap_fbdev.o \ + omap_gem.o \ + omap_gem_dmabuf.o \ + omap_dmm_tiler.o \ + tcm-sita.o + +# temporary: +omapdrm-y += omap_gem_helpers.o + +obj-$(CONFIG_DRM_OMAP) += omapdrm.o diff --git a/drivers/gpu/drm/omapdrm/TODO b/drivers/gpu/drm/omapdrm/TODO new file mode 100644 index 000000000000..4d8c18aa5dd7 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/TODO @@ -0,0 +1,23 @@ +TODO +. Where should we do eviction (detatch_pages())? We aren't necessarily + accessing the pages via a GART, so maybe we need some other threshold + to put a cap on the # of pages that can be pin'd. + . Use mm_shrinker to trigger unpinning pages. + . This is mainly theoretical since most of these devices don't actually + have swap or harddrive. +. GEM/shmem backed pages can have existing mappings (kernel linear map, + etc..), which isn't really ideal. +. Revisit GEM sync object infrastructure.. TTM has some framework for this + already. Possibly this could be refactored out and made more common? + There should be some way to do this with less wheel-reinvention. + . This can be handled by the dma-buf fence/reservation stuff when it + lands + +Userspace: +. git://anongit.freedesktop.org/xorg/driver/xf86-video-omap + +Currently tested on +. OMAP3530 beagleboard +. OMAP4430 pandaboard +. OMAP4460 pandaboard +. OMAP5432 uEVM diff --git a/drivers/gpu/drm/omapdrm/omap_connector.c b/drivers/gpu/drm/omapdrm/omap_connector.c new file mode 100644 index 000000000000..44284fd981fc --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_connector.c @@ -0,0 +1,298 @@ +/* + * drivers/gpu/drm/omapdrm/omap_connector.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +/* + * connector funcs + */ + +#define to_omap_connector(x) container_of(x, struct omap_connector, base) + +struct omap_connector { + struct drm_connector base; + struct omap_dss_device *dssdev; + struct drm_encoder *encoder; +}; + +void copy_timings_omap_to_drm(struct drm_display_mode *mode, + struct omap_video_timings *timings) +{ + mode->clock = timings->pixel_clock; + + mode->hdisplay = timings->x_res; + mode->hsync_start = mode->hdisplay + timings->hfp; + mode->hsync_end = mode->hsync_start + timings->hsw; + mode->htotal = mode->hsync_end + timings->hbp; + + mode->vdisplay = timings->y_res; + mode->vsync_start = mode->vdisplay + timings->vfp; + mode->vsync_end = mode->vsync_start + timings->vsw; + mode->vtotal = mode->vsync_end + timings->vbp; + + mode->flags = 0; + + if (timings->interlace) + mode->flags |= DRM_MODE_FLAG_INTERLACE; + + if (timings->hsync_level == OMAPDSS_SIG_ACTIVE_HIGH) + mode->flags |= DRM_MODE_FLAG_PHSYNC; + else + mode->flags |= DRM_MODE_FLAG_NHSYNC; + + if (timings->vsync_level == OMAPDSS_SIG_ACTIVE_HIGH) + mode->flags |= DRM_MODE_FLAG_PVSYNC; + else + mode->flags |= DRM_MODE_FLAG_NVSYNC; +} + +void copy_timings_drm_to_omap(struct omap_video_timings *timings, + struct drm_display_mode *mode) +{ + timings->pixel_clock = mode->clock; + + timings->x_res = mode->hdisplay; + timings->hfp = mode->hsync_start - mode->hdisplay; + timings->hsw = mode->hsync_end - mode->hsync_start; + timings->hbp = mode->htotal - mode->hsync_end; + + timings->y_res = mode->vdisplay; + timings->vfp = mode->vsync_start - mode->vdisplay; + timings->vsw = mode->vsync_end - mode->vsync_start; + timings->vbp = mode->vtotal - mode->vsync_end; + + timings->interlace = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + timings->hsync_level = OMAPDSS_SIG_ACTIVE_HIGH; + else + timings->hsync_level = OMAPDSS_SIG_ACTIVE_LOW; + + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + timings->vsync_level = OMAPDSS_SIG_ACTIVE_HIGH; + else + timings->vsync_level = OMAPDSS_SIG_ACTIVE_LOW; + + timings->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + timings->de_level = OMAPDSS_SIG_ACTIVE_HIGH; + timings->sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; +} + +static enum drm_connector_status omap_connector_detect( + struct drm_connector *connector, bool force) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + enum drm_connector_status ret; + + if (dssdrv->detect) { + if (dssdrv->detect(dssdev)) + ret = connector_status_connected; + else + ret = connector_status_disconnected; + } else { + ret = connector_status_unknown; + } + + VERB("%s: %d (force=%d)", omap_connector->dssdev->name, ret, force); + + return ret; +} + +static void omap_connector_destroy(struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + + DBG("%s", omap_connector->dssdev->name); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(omap_connector); + + omap_dss_put_device(dssdev); +} + +#define MAX_EDID 512 + +static int omap_connector_get_modes(struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + struct drm_device *dev = connector->dev; + int n = 0; + + DBG("%s", omap_connector->dssdev->name); + + /* if display exposes EDID, then we parse that in the normal way to + * build table of supported modes.. otherwise (ie. fixed resolution + * LCD panels) we just return a single mode corresponding to the + * currently configured timings: + */ + if (dssdrv->read_edid) { + void *edid = kzalloc(MAX_EDID, GFP_KERNEL); + + if ((dssdrv->read_edid(dssdev, edid, MAX_EDID) > 0) && + drm_edid_is_valid(edid)) { + drm_mode_connector_update_edid_property( + connector, edid); + n = drm_add_edid_modes(connector, edid); + } else { + drm_mode_connector_update_edid_property( + connector, NULL); + } + kfree(edid); + } else { + struct drm_display_mode *mode = drm_mode_create(dev); + struct omap_video_timings timings = {0}; + + dssdrv->get_timings(dssdev, &timings); + + copy_timings_omap_to_drm(mode, &timings); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + n = 1; + } + + return n; +} + +static int omap_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + struct omap_video_timings timings = {0}; + struct drm_device *dev = connector->dev; + struct drm_display_mode *new_mode; + int ret = MODE_BAD; + + copy_timings_drm_to_omap(&timings, mode); + mode->vrefresh = drm_mode_vrefresh(mode); + + if (!dssdrv->check_timings(dssdev, &timings)) { + /* check if vrefresh is still valid */ + new_mode = drm_mode_duplicate(dev, mode); + new_mode->clock = timings.pixel_clock; + new_mode->vrefresh = 0; + if (mode->vrefresh == drm_mode_vrefresh(new_mode)) + ret = MODE_OK; + drm_mode_destroy(dev, new_mode); + } + + DBG("connector: mode %s: " + "%d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", + (ret == MODE_OK) ? "valid" : "invalid", + mode->base.id, mode->name, mode->vrefresh, mode->clock, + mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, mode->type, mode->flags); + + return ret; +} + +struct drm_encoder *omap_connector_attached_encoder( + struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + return omap_connector->encoder; +} + +static const struct drm_connector_funcs omap_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = omap_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = omap_connector_destroy, +}; + +static const struct drm_connector_helper_funcs omap_connector_helper_funcs = { + .get_modes = omap_connector_get_modes, + .mode_valid = omap_connector_mode_valid, + .best_encoder = omap_connector_attached_encoder, +}; + +/* flush an area of the framebuffer (in case of manual update display that + * is not automatically flushed) + */ +void omap_connector_flush(struct drm_connector *connector, + int x, int y, int w, int h) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + + /* TODO: enable when supported in dss */ + VERB("%s: %d,%d, %dx%d", omap_connector->dssdev->name, x, y, w, h); +} + +/* initialize connector */ +struct drm_connector *omap_connector_init(struct drm_device *dev, + int connector_type, struct omap_dss_device *dssdev, + struct drm_encoder *encoder) +{ + struct drm_connector *connector = NULL; + struct omap_connector *omap_connector; + + DBG("%s", dssdev->name); + + omap_dss_get_device(dssdev); + + omap_connector = kzalloc(sizeof(struct omap_connector), GFP_KERNEL); + if (!omap_connector) { + dev_err(dev->dev, "could not allocate connector\n"); + goto fail; + } + + omap_connector->dssdev = dssdev; + omap_connector->encoder = encoder; + + connector = &omap_connector->base; + + drm_connector_init(dev, connector, &omap_connector_funcs, + connector_type); + drm_connector_helper_add(connector, &omap_connector_helper_funcs); + +#if 0 /* enable when dss2 supports hotplug */ + if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_HPD) + connector->polled = 0; + else +#endif + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + connector->interlace_allowed = 1; + connector->doublescan_allowed = 0; + + drm_sysfs_connector_add(connector); + + return connector; + +fail: + if (connector) + omap_connector_destroy(connector); + + return NULL; +} diff --git a/drivers/gpu/drm/omapdrm/omap_crtc.c b/drivers/gpu/drm/omapdrm/omap_crtc.c new file mode 100644 index 000000000000..ac2258f59805 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_crtc.c @@ -0,0 +1,657 @@ +/* + * drivers/gpu/drm/omapdrm/omap_crtc.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include <drm/drm_mode.h> +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +#define to_omap_crtc(x) container_of(x, struct omap_crtc, base) + +struct omap_crtc { + struct drm_crtc base; + struct drm_plane *plane; + + const char *name; + int pipe; + enum omap_channel channel; + struct omap_overlay_manager_info info; + + /* + * Temporary: eventually this will go away, but it is needed + * for now to keep the output's happy. (They only need + * mgr->id.) Eventually this will be replaced w/ something + * more common-panel-framework-y + */ + struct omap_overlay_manager mgr; + + struct omap_video_timings timings; + bool enabled; + bool full_update; + + struct omap_drm_apply apply; + + struct omap_drm_irq apply_irq; + struct omap_drm_irq error_irq; + + /* list of in-progress apply's: */ + struct list_head pending_applies; + + /* list of queued apply's: */ + struct list_head queued_applies; + + /* for handling queued and in-progress applies: */ + struct work_struct apply_work; + + /* if there is a pending flip, these will be non-null: */ + struct drm_pending_vblank_event *event; + struct drm_framebuffer *old_fb; + + /* for handling page flips without caring about what + * the callback is called from. Possibly we should just + * make omap_gem always call the cb from the worker so + * we don't have to care about this.. + * + * XXX maybe fold into apply_work?? + */ + struct work_struct page_flip_work; +}; + +/* + * Manager-ops, callbacks from output when they need to configure + * the upstream part of the video pipe. + * + * Most of these we can ignore until we add support for command-mode + * panels.. for video-mode the crtc-helpers already do an adequate + * job of sequencing the setup of the video pipe in the proper order + */ + +/* we can probably ignore these until we support command-mode panels: */ +static void omap_crtc_start_update(struct omap_overlay_manager *mgr) +{ +} + +static int omap_crtc_enable(struct omap_overlay_manager *mgr) +{ + return 0; +} + +static void omap_crtc_disable(struct omap_overlay_manager *mgr) +{ +} + +static void omap_crtc_set_timings(struct omap_overlay_manager *mgr, + const struct omap_video_timings *timings) +{ + struct omap_crtc *omap_crtc = container_of(mgr, struct omap_crtc, mgr); + DBG("%s", omap_crtc->name); + omap_crtc->timings = *timings; + omap_crtc->full_update = true; +} + +static void omap_crtc_set_lcd_config(struct omap_overlay_manager *mgr, + const struct dss_lcd_mgr_config *config) +{ + struct omap_crtc *omap_crtc = container_of(mgr, struct omap_crtc, mgr); + DBG("%s", omap_crtc->name); + dispc_mgr_set_lcd_config(omap_crtc->channel, config); +} + +static int omap_crtc_register_framedone_handler( + struct omap_overlay_manager *mgr, + void (*handler)(void *), void *data) +{ + return 0; +} + +static void omap_crtc_unregister_framedone_handler( + struct omap_overlay_manager *mgr, + void (*handler)(void *), void *data) +{ +} + +static const struct dss_mgr_ops mgr_ops = { + .start_update = omap_crtc_start_update, + .enable = omap_crtc_enable, + .disable = omap_crtc_disable, + .set_timings = omap_crtc_set_timings, + .set_lcd_config = omap_crtc_set_lcd_config, + .register_framedone_handler = omap_crtc_register_framedone_handler, + .unregister_framedone_handler = omap_crtc_unregister_framedone_handler, +}; + +/* + * CRTC funcs: + */ + +static void omap_crtc_destroy(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + + DBG("%s", omap_crtc->name); + + WARN_ON(omap_crtc->apply_irq.registered); + omap_irq_unregister(crtc->dev, &omap_crtc->error_irq); + + omap_crtc->plane->funcs->destroy(omap_crtc->plane); + drm_crtc_cleanup(crtc); + + kfree(omap_crtc); +} + +static void omap_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct omap_drm_private *priv = crtc->dev->dev_private; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + bool enabled = (mode == DRM_MODE_DPMS_ON); + int i; + + DBG("%s: %d", omap_crtc->name, mode); + + if (enabled != omap_crtc->enabled) { + omap_crtc->enabled = enabled; + omap_crtc->full_update = true; + omap_crtc_apply(crtc, &omap_crtc->apply); + + /* also enable our private plane: */ + WARN_ON(omap_plane_dpms(omap_crtc->plane, mode)); + + /* and any attached overlay planes: */ + for (i = 0; i < priv->num_planes; i++) { + struct drm_plane *plane = priv->planes[i]; + if (plane->crtc == crtc) + WARN_ON(omap_plane_dpms(plane, mode)); + } + } +} + +static bool omap_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int omap_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, + struct drm_framebuffer *old_fb) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + + mode = adjusted_mode; + + DBG("%s: set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", + omap_crtc->name, mode->base.id, mode->name, + mode->vrefresh, mode->clock, + mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, + mode->type, mode->flags); + + copy_timings_drm_to_omap(&omap_crtc->timings, mode); + omap_crtc->full_update = true; + + return omap_plane_mode_set(omap_crtc->plane, crtc, crtc->fb, + 0, 0, mode->hdisplay, mode->vdisplay, + x << 16, y << 16, + mode->hdisplay << 16, mode->vdisplay << 16, + NULL, NULL); +} + +static void omap_crtc_prepare(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + DBG("%s", omap_crtc->name); + omap_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +} + +static void omap_crtc_commit(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + DBG("%s", omap_crtc->name); + omap_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static int omap_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct drm_plane *plane = omap_crtc->plane; + struct drm_display_mode *mode = &crtc->mode; + + return omap_plane_mode_set(plane, crtc, crtc->fb, + 0, 0, mode->hdisplay, mode->vdisplay, + x << 16, y << 16, + mode->hdisplay << 16, mode->vdisplay << 16, + NULL, NULL); +} + +static void omap_crtc_load_lut(struct drm_crtc *crtc) +{ +} + +static void vblank_cb(void *arg) +{ + struct drm_crtc *crtc = arg; + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + + /* wakeup userspace */ + if (omap_crtc->event) + drm_send_vblank_event(dev, omap_crtc->pipe, omap_crtc->event); + + omap_crtc->event = NULL; + omap_crtc->old_fb = NULL; + + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static void page_flip_worker(struct work_struct *work) +{ + struct omap_crtc *omap_crtc = + container_of(work, struct omap_crtc, page_flip_work); + struct drm_crtc *crtc = &omap_crtc->base; + struct drm_display_mode *mode = &crtc->mode; + struct drm_gem_object *bo; + + mutex_lock(&crtc->mutex); + omap_plane_mode_set(omap_crtc->plane, crtc, crtc->fb, + 0, 0, mode->hdisplay, mode->vdisplay, + crtc->x << 16, crtc->y << 16, + mode->hdisplay << 16, mode->vdisplay << 16, + vblank_cb, crtc); + mutex_unlock(&crtc->mutex); + + bo = omap_framebuffer_bo(crtc->fb, 0); + drm_gem_object_unreference_unlocked(bo); +} + +static void page_flip_cb(void *arg) +{ + struct drm_crtc *crtc = arg; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct omap_drm_private *priv = crtc->dev->dev_private; + + /* avoid assumptions about what ctxt we are called from: */ + queue_work(priv->wq, &omap_crtc->page_flip_work); +} + +static int omap_crtc_page_flip_locked(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct drm_gem_object *bo; + + DBG("%d -> %d (event=%p)", crtc->fb ? crtc->fb->base.id : -1, + fb->base.id, event); + + if (omap_crtc->old_fb) { + dev_err(dev->dev, "already a pending flip\n"); + return -EINVAL; + } + + omap_crtc->event = event; + crtc->fb = fb; + + /* + * Hold a reference temporarily until the crtc is updated + * and takes the reference to the bo. This avoids it + * getting freed from under us: + */ + bo = omap_framebuffer_bo(fb, 0); + drm_gem_object_reference(bo); + + omap_gem_op_async(bo, OMAP_GEM_READ, page_flip_cb, crtc); + + return 0; +} + +static int omap_crtc_set_property(struct drm_crtc *crtc, + struct drm_property *property, uint64_t val) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct omap_drm_private *priv = crtc->dev->dev_private; + + if (property == priv->rotation_prop) { + crtc->invert_dimensions = + !!(val & ((1LL << DRM_ROTATE_90) | (1LL << DRM_ROTATE_270))); + } + + return omap_plane_set_property(omap_crtc->plane, property, val); +} + +static const struct drm_crtc_funcs omap_crtc_funcs = { + .set_config = drm_crtc_helper_set_config, + .destroy = omap_crtc_destroy, + .page_flip = omap_crtc_page_flip_locked, + .set_property = omap_crtc_set_property, +}; + +static const struct drm_crtc_helper_funcs omap_crtc_helper_funcs = { + .dpms = omap_crtc_dpms, + .mode_fixup = omap_crtc_mode_fixup, + .mode_set = omap_crtc_mode_set, + .prepare = omap_crtc_prepare, + .commit = omap_crtc_commit, + .mode_set_base = omap_crtc_mode_set_base, + .load_lut = omap_crtc_load_lut, +}; + +const struct omap_video_timings *omap_crtc_timings(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + return &omap_crtc->timings; +} + +enum omap_channel omap_crtc_channel(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + return omap_crtc->channel; +} + +static void omap_crtc_error_irq(struct omap_drm_irq *irq, uint32_t irqstatus) +{ + struct omap_crtc *omap_crtc = + container_of(irq, struct omap_crtc, error_irq); + struct drm_crtc *crtc = &omap_crtc->base; + DRM_ERROR("%s: errors: %08x\n", omap_crtc->name, irqstatus); + /* avoid getting in a flood, unregister the irq until next vblank */ + omap_irq_unregister(crtc->dev, &omap_crtc->error_irq); +} + +static void omap_crtc_apply_irq(struct omap_drm_irq *irq, uint32_t irqstatus) +{ + struct omap_crtc *omap_crtc = + container_of(irq, struct omap_crtc, apply_irq); + struct drm_crtc *crtc = &omap_crtc->base; + + if (!omap_crtc->error_irq.registered) + omap_irq_register(crtc->dev, &omap_crtc->error_irq); + + if (!dispc_mgr_go_busy(omap_crtc->channel)) { + struct omap_drm_private *priv = + crtc->dev->dev_private; + DBG("%s: apply done", omap_crtc->name); + omap_irq_unregister(crtc->dev, &omap_crtc->apply_irq); + queue_work(priv->wq, &omap_crtc->apply_work); + } +} + +static void apply_worker(struct work_struct *work) +{ + struct omap_crtc *omap_crtc = + container_of(work, struct omap_crtc, apply_work); + struct drm_crtc *crtc = &omap_crtc->base; + struct drm_device *dev = crtc->dev; + struct omap_drm_apply *apply, *n; + bool need_apply; + + /* + * Synchronize everything on mode_config.mutex, to keep + * the callbacks and list modification all serialized + * with respect to modesetting ioctls from userspace. + */ + mutex_lock(&crtc->mutex); + dispc_runtime_get(); + + /* + * If we are still pending a previous update, wait.. when the + * pending update completes, we get kicked again. + */ + if (omap_crtc->apply_irq.registered) + goto out; + + /* finish up previous apply's: */ + list_for_each_entry_safe(apply, n, + &omap_crtc->pending_applies, pending_node) { + apply->post_apply(apply); + list_del(&apply->pending_node); + } + + need_apply = !list_empty(&omap_crtc->queued_applies); + + /* then handle the next round of of queued apply's: */ + list_for_each_entry_safe(apply, n, + &omap_crtc->queued_applies, queued_node) { + apply->pre_apply(apply); + list_del(&apply->queued_node); + apply->queued = false; + list_add_tail(&apply->pending_node, + &omap_crtc->pending_applies); + } + + if (need_apply) { + enum omap_channel channel = omap_crtc->channel; + + DBG("%s: GO", omap_crtc->name); + + if (dispc_mgr_is_enabled(channel)) { + omap_irq_register(dev, &omap_crtc->apply_irq); + dispc_mgr_go(channel); + } else { + struct omap_drm_private *priv = dev->dev_private; + queue_work(priv->wq, &omap_crtc->apply_work); + } + } + +out: + dispc_runtime_put(); + mutex_unlock(&crtc->mutex); +} + +int omap_crtc_apply(struct drm_crtc *crtc, + struct omap_drm_apply *apply) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + + WARN_ON(!mutex_is_locked(&crtc->mutex)); + + /* no need to queue it again if it is already queued: */ + if (apply->queued) + return 0; + + apply->queued = true; + list_add_tail(&apply->queued_node, &omap_crtc->queued_applies); + + /* + * If there are no currently pending updates, then go ahead and + * kick the worker immediately, otherwise it will run again when + * the current update finishes. + */ + if (list_empty(&omap_crtc->pending_applies)) { + struct omap_drm_private *priv = crtc->dev->dev_private; + queue_work(priv->wq, &omap_crtc->apply_work); + } + + return 0; +} + +/* called only from apply */ +static void set_enabled(struct drm_crtc *crtc, bool enable) +{ + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + enum omap_channel channel = omap_crtc->channel; + struct omap_irq_wait *wait = NULL; + + if (dispc_mgr_is_enabled(channel) == enable) + return; + + /* ignore sync-lost irqs during enable/disable */ + omap_irq_unregister(crtc->dev, &omap_crtc->error_irq); + + if (dispc_mgr_get_framedone_irq(channel)) { + if (!enable) { + wait = omap_irq_wait_init(dev, + dispc_mgr_get_framedone_irq(channel), 1); + } + } else { + /* + * When we disable digit output, we need to wait until fields + * are done. Otherwise the DSS is still working, and turning + * off the clocks prevents DSS from going to OFF mode. And when + * enabling, we need to wait for the extra sync losts + */ + wait = omap_irq_wait_init(dev, + dispc_mgr_get_vsync_irq(channel), 2); + } + + dispc_mgr_enable(channel, enable); + + if (wait) { + int ret = omap_irq_wait(dev, wait, msecs_to_jiffies(100)); + if (ret) { + dev_err(dev->dev, "%s: timeout waiting for %s\n", + omap_crtc->name, enable ? "enable" : "disable"); + } + } + + omap_irq_register(crtc->dev, &omap_crtc->error_irq); +} + +static void omap_crtc_pre_apply(struct omap_drm_apply *apply) +{ + struct omap_crtc *omap_crtc = + container_of(apply, struct omap_crtc, apply); + struct drm_crtc *crtc = &omap_crtc->base; + struct drm_encoder *encoder = NULL; + + DBG("%s: enabled=%d, full=%d", omap_crtc->name, + omap_crtc->enabled, omap_crtc->full_update); + + if (omap_crtc->full_update) { + struct omap_drm_private *priv = crtc->dev->dev_private; + int i; + for (i = 0; i < priv->num_encoders; i++) { + if (priv->encoders[i]->crtc == crtc) { + encoder = priv->encoders[i]; + break; + } + } + } + + if (!omap_crtc->enabled) { + set_enabled(&omap_crtc->base, false); + if (encoder) + omap_encoder_set_enabled(encoder, false); + } else { + if (encoder) { + omap_encoder_set_enabled(encoder, false); + omap_encoder_update(encoder, &omap_crtc->mgr, + &omap_crtc->timings); + omap_encoder_set_enabled(encoder, true); + omap_crtc->full_update = false; + } + + dispc_mgr_setup(omap_crtc->channel, &omap_crtc->info); + dispc_mgr_set_timings(omap_crtc->channel, + &omap_crtc->timings); + set_enabled(&omap_crtc->base, true); + } + + omap_crtc->full_update = false; +} + +static void omap_crtc_post_apply(struct omap_drm_apply *apply) +{ + /* nothing needed for post-apply */ +} + +static const char *channel_names[] = { + [OMAP_DSS_CHANNEL_LCD] = "lcd", + [OMAP_DSS_CHANNEL_DIGIT] = "tv", + [OMAP_DSS_CHANNEL_LCD2] = "lcd2", +}; + +/* initialize crtc */ +struct drm_crtc *omap_crtc_init(struct drm_device *dev, + struct drm_plane *plane, enum omap_channel channel, int id) +{ + struct drm_crtc *crtc = NULL; + struct omap_crtc *omap_crtc; + struct omap_overlay_manager_info *info; + + DBG("%s", channel_names[channel]); + + omap_crtc = kzalloc(sizeof(*omap_crtc), GFP_KERNEL); + + if (!omap_crtc) { + dev_err(dev->dev, "could not allocate CRTC\n"); + goto fail; + } + + crtc = &omap_crtc->base; + + INIT_WORK(&omap_crtc->page_flip_work, page_flip_worker); + INIT_WORK(&omap_crtc->apply_work, apply_worker); + + INIT_LIST_HEAD(&omap_crtc->pending_applies); + INIT_LIST_HEAD(&omap_crtc->queued_applies); + + omap_crtc->apply.pre_apply = omap_crtc_pre_apply; + omap_crtc->apply.post_apply = omap_crtc_post_apply; + + omap_crtc->apply_irq.irqmask = pipe2vbl(id); + omap_crtc->apply_irq.irq = omap_crtc_apply_irq; + + omap_crtc->error_irq.irqmask = + dispc_mgr_get_sync_lost_irq(channel); + omap_crtc->error_irq.irq = omap_crtc_error_irq; + omap_irq_register(dev, &omap_crtc->error_irq); + + omap_crtc->channel = channel; + omap_crtc->plane = plane; + omap_crtc->plane->crtc = crtc; + omap_crtc->name = channel_names[channel]; + omap_crtc->pipe = id; + + /* temporary: */ + omap_crtc->mgr.id = channel; + + dss_install_mgr_ops(&mgr_ops); + + /* TODO: fix hard-coded setup.. add properties! */ + info = &omap_crtc->info; + info->default_color = 0x00000000; + info->trans_key = 0x00000000; + info->trans_key_type = OMAP_DSS_COLOR_KEY_GFX_DST; + info->trans_enabled = false; + + drm_crtc_init(dev, crtc, &omap_crtc_funcs); + drm_crtc_helper_add(crtc, &omap_crtc_helper_funcs); + + omap_plane_install_properties(omap_crtc->plane, &crtc->base); + + return crtc; + +fail: + if (crtc) + omap_crtc_destroy(crtc); + + return NULL; +} diff --git a/drivers/gpu/drm/omapdrm/omap_debugfs.c b/drivers/gpu/drm/omapdrm/omap_debugfs.c new file mode 100644 index 000000000000..c27f59da7f29 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_debugfs.c @@ -0,0 +1,125 @@ +/* + * drivers/gpu/drm/omapdrm/omap_debugfs.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" +#include "omap_dmm_tiler.h" + +#include "drm_fb_helper.h" + + +#ifdef CONFIG_DEBUG_FS + +static int gem_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct omap_drm_private *priv = dev->dev_private; + int ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + seq_printf(m, "All Objects:\n"); + omap_gem_describe_objects(&priv->obj_list, m); + + mutex_unlock(&dev->struct_mutex); + + return 0; +} + +static int mm_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + return drm_mm_dump_table(m, dev->mm_private); +} + +static int fb_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct omap_drm_private *priv = dev->dev_private; + struct drm_framebuffer *fb; + + seq_printf(m, "fbcon "); + omap_framebuffer_describe(priv->fbdev->fb, m); + + mutex_lock(&dev->mode_config.fb_lock); + list_for_each_entry(fb, &dev->mode_config.fb_list, head) { + if (fb == priv->fbdev->fb) + continue; + + seq_printf(m, "user "); + omap_framebuffer_describe(fb, m); + } + mutex_unlock(&dev->mode_config.fb_lock); + + return 0; +} + +/* list of debufs files that are applicable to all devices */ +static struct drm_info_list omap_debugfs_list[] = { + {"gem", gem_show, 0}, + {"mm", mm_show, 0}, + {"fb", fb_show, 0}, +}; + +/* list of debugfs files that are specific to devices with dmm/tiler */ +static struct drm_info_list omap_dmm_debugfs_list[] = { + {"tiler_map", tiler_map_show, 0}, +}; + +int omap_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *dev = minor->dev; + int ret; + + ret = drm_debugfs_create_files(omap_debugfs_list, + ARRAY_SIZE(omap_debugfs_list), + minor->debugfs_root, minor); + + if (ret) { + dev_err(dev->dev, "could not install omap_debugfs_list\n"); + return ret; + } + + if (dmm_is_available()) + ret = drm_debugfs_create_files(omap_dmm_debugfs_list, + ARRAY_SIZE(omap_dmm_debugfs_list), + minor->debugfs_root, minor); + + if (ret) { + dev_err(dev->dev, "could not install omap_dmm_debugfs_list\n"); + return ret; + } + + return ret; +} + +void omap_debugfs_cleanup(struct drm_minor *minor) +{ + drm_debugfs_remove_files(omap_debugfs_list, + ARRAY_SIZE(omap_debugfs_list), minor); + if (dmm_is_available()) + drm_debugfs_remove_files(omap_dmm_debugfs_list, + ARRAY_SIZE(omap_dmm_debugfs_list), minor); +} + +#endif diff --git a/drivers/gpu/drm/omapdrm/omap_dmm_priv.h b/drivers/gpu/drm/omapdrm/omap_dmm_priv.h new file mode 100644 index 000000000000..58bcd6ae0255 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_dmm_priv.h @@ -0,0 +1,188 @@ +/* + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Rob Clark <rob@ti.com> + * Andy Gross <andy.gross@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef OMAP_DMM_PRIV_H +#define OMAP_DMM_PRIV_H + +#define DMM_REVISION 0x000 +#define DMM_HWINFO 0x004 +#define DMM_LISA_HWINFO 0x008 +#define DMM_DMM_SYSCONFIG 0x010 +#define DMM_LISA_LOCK 0x01C +#define DMM_LISA_MAP__0 0x040 +#define DMM_LISA_MAP__1 0x044 +#define DMM_TILER_HWINFO 0x208 +#define DMM_TILER_OR__0 0x220 +#define DMM_TILER_OR__1 0x224 +#define DMM_PAT_HWINFO 0x408 +#define DMM_PAT_GEOMETRY 0x40C +#define DMM_PAT_CONFIG 0x410 +#define DMM_PAT_VIEW__0 0x420 +#define DMM_PAT_VIEW__1 0x424 +#define DMM_PAT_VIEW_MAP__0 0x440 +#define DMM_PAT_VIEW_MAP_BASE 0x460 +#define DMM_PAT_IRQ_EOI 0x478 +#define DMM_PAT_IRQSTATUS_RAW 0x480 +#define DMM_PAT_IRQSTATUS 0x490 +#define DMM_PAT_IRQENABLE_SET 0x4A0 +#define DMM_PAT_IRQENABLE_CLR 0x4B0 +#define DMM_PAT_STATUS__0 0x4C0 +#define DMM_PAT_STATUS__1 0x4C4 +#define DMM_PAT_STATUS__2 0x4C8 +#define DMM_PAT_STATUS__3 0x4CC +#define DMM_PAT_DESCR__0 0x500 +#define DMM_PAT_DESCR__1 0x510 +#define DMM_PAT_DESCR__2 0x520 +#define DMM_PAT_DESCR__3 0x530 +#define DMM_PEG_HWINFO 0x608 +#define DMM_PEG_PRIO 0x620 +#define DMM_PEG_PRIO_PAT 0x640 + +#define DMM_IRQSTAT_DST (1<<0) +#define DMM_IRQSTAT_LST (1<<1) +#define DMM_IRQSTAT_ERR_INV_DSC (1<<2) +#define DMM_IRQSTAT_ERR_INV_DATA (1<<3) +#define DMM_IRQSTAT_ERR_UPD_AREA (1<<4) +#define DMM_IRQSTAT_ERR_UPD_CTRL (1<<5) +#define DMM_IRQSTAT_ERR_UPD_DATA (1<<6) +#define DMM_IRQSTAT_ERR_LUT_MISS (1<<7) + +#define DMM_IRQSTAT_ERR_MASK (DMM_IRQ_STAT_ERR_INV_DSC | \ + DMM_IRQ_STAT_ERR_INV_DATA | \ + DMM_IRQ_STAT_ERR_UPD_AREA | \ + DMM_IRQ_STAT_ERR_UPD_CTRL | \ + DMM_IRQ_STAT_ERR_UPD_DATA | \ + DMM_IRQ_STAT_ERR_LUT_MISS) + +#define DMM_PATSTATUS_READY (1<<0) +#define DMM_PATSTATUS_VALID (1<<1) +#define DMM_PATSTATUS_RUN (1<<2) +#define DMM_PATSTATUS_DONE (1<<3) +#define DMM_PATSTATUS_LINKED (1<<4) +#define DMM_PATSTATUS_BYPASSED (1<<7) +#define DMM_PATSTATUS_ERR_INV_DESCR (1<<10) +#define DMM_PATSTATUS_ERR_INV_DATA (1<<11) +#define DMM_PATSTATUS_ERR_UPD_AREA (1<<12) +#define DMM_PATSTATUS_ERR_UPD_CTRL (1<<13) +#define DMM_PATSTATUS_ERR_UPD_DATA (1<<14) +#define DMM_PATSTATUS_ERR_ACCESS (1<<15) + +/* note: don't treat DMM_PATSTATUS_ERR_ACCESS as an error */ +#define DMM_PATSTATUS_ERR (DMM_PATSTATUS_ERR_INV_DESCR | \ + DMM_PATSTATUS_ERR_INV_DATA | \ + DMM_PATSTATUS_ERR_UPD_AREA | \ + DMM_PATSTATUS_ERR_UPD_CTRL | \ + DMM_PATSTATUS_ERR_UPD_DATA) + + + +enum { + PAT_STATUS, + PAT_DESCR +}; + +struct pat_ctrl { + u32 start:4; + u32 dir:4; + u32 lut_id:8; + u32 sync:12; + u32 ini:4; +}; + +struct pat { + uint32_t next_pa; + struct pat_area area; + struct pat_ctrl ctrl; + uint32_t data_pa; +}; + +#define DMM_FIXED_RETRY_COUNT 1000 + +/* create refill buffer big enough to refill all slots, plus 3 descriptors.. + * 3 descriptors is probably the worst-case for # of 2d-slices in a 1d area, + * but I guess you don't hit that worst case at the same time as full area + * refill + */ +#define DESCR_SIZE 128 +#define REFILL_BUFFER_SIZE ((4 * 128 * 256) + (3 * DESCR_SIZE)) + +/* For OMAP5, a fixed offset is added to all Y coordinates for 1D buffers. + * This is used in programming to address the upper portion of the LUT +*/ +#define OMAP5_LUT_OFFSET 128 + +struct dmm; + +struct dmm_txn { + void *engine_handle; + struct tcm *tcm; + + uint8_t *current_va; + dma_addr_t current_pa; + + struct pat *last_pat; +}; + +struct refill_engine { + int id; + struct dmm *dmm; + struct tcm *tcm; + + uint8_t *refill_va; + dma_addr_t refill_pa; + + /* only one trans per engine for now */ + struct dmm_txn txn; + + bool async; + + wait_queue_head_t wait_for_refill; + + struct list_head idle_node; +}; + +struct dmm { + struct device *dev; + void __iomem *base; + int irq; + + struct page *dummy_page; + dma_addr_t dummy_pa; + + void *refill_va; + dma_addr_t refill_pa; + + /* refill engines */ + wait_queue_head_t engine_queue; + struct list_head idle_head; + struct refill_engine *engines; + int num_engines; + atomic_t engine_counter; + + /* container information */ + int container_width; + int container_height; + int lut_width; + int lut_height; + int num_lut; + + /* array of LUT - TCM containers */ + struct tcm **tcm; + + /* allocation list and lock */ + struct list_head alloc_head; +}; + +#endif diff --git a/drivers/gpu/drm/omapdrm/omap_dmm_tiler.c b/drivers/gpu/drm/omapdrm/omap_dmm_tiler.c new file mode 100644 index 000000000000..391021537105 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_dmm_tiler.c @@ -0,0 +1,991 @@ +/* + * DMM IOMMU driver support functions for TI OMAP processors. + * + * Author: Rob Clark <rob@ti.com> + * Andy Gross <andy.gross@ti.com> + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> /* platform_device() */ +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/time.h> +#include <linux/list.h> + +#include "omap_dmm_tiler.h" +#include "omap_dmm_priv.h" + +#define DMM_DRIVER_NAME "dmm" + +/* mappings for associating views to luts */ +static struct tcm *containers[TILFMT_NFORMATS]; +static struct dmm *omap_dmm; + +/* global spinlock for protecting lists */ +static DEFINE_SPINLOCK(list_lock); + +/* Geometry table */ +#define GEOM(xshift, yshift, bytes_per_pixel) { \ + .x_shft = (xshift), \ + .y_shft = (yshift), \ + .cpp = (bytes_per_pixel), \ + .slot_w = 1 << (SLOT_WIDTH_BITS - (xshift)), \ + .slot_h = 1 << (SLOT_HEIGHT_BITS - (yshift)), \ + } + +static const struct { + uint32_t x_shft; /* unused X-bits (as part of bpp) */ + uint32_t y_shft; /* unused Y-bits (as part of bpp) */ + uint32_t cpp; /* bytes/chars per pixel */ + uint32_t slot_w; /* width of each slot (in pixels) */ + uint32_t slot_h; /* height of each slot (in pixels) */ +} geom[TILFMT_NFORMATS] = { + [TILFMT_8BIT] = GEOM(0, 0, 1), + [TILFMT_16BIT] = GEOM(0, 1, 2), + [TILFMT_32BIT] = GEOM(1, 1, 4), + [TILFMT_PAGE] = GEOM(SLOT_WIDTH_BITS, SLOT_HEIGHT_BITS, 1), +}; + + +/* lookup table for registers w/ per-engine instances */ +static const uint32_t reg[][4] = { + [PAT_STATUS] = {DMM_PAT_STATUS__0, DMM_PAT_STATUS__1, + DMM_PAT_STATUS__2, DMM_PAT_STATUS__3}, + [PAT_DESCR] = {DMM_PAT_DESCR__0, DMM_PAT_DESCR__1, + DMM_PAT_DESCR__2, DMM_PAT_DESCR__3}, +}; + +/* simple allocator to grab next 16 byte aligned memory from txn */ +static void *alloc_dma(struct dmm_txn *txn, size_t sz, dma_addr_t *pa) +{ + void *ptr; + struct refill_engine *engine = txn->engine_handle; + + /* dmm programming requires 16 byte aligned addresses */ + txn->current_pa = round_up(txn->current_pa, 16); + txn->current_va = (void *)round_up((long)txn->current_va, 16); + + ptr = txn->current_va; + *pa = txn->current_pa; + + txn->current_pa += sz; + txn->current_va += sz; + + BUG_ON((txn->current_va - engine->refill_va) > REFILL_BUFFER_SIZE); + + return ptr; +} + +/* check status and spin until wait_mask comes true */ +static int wait_status(struct refill_engine *engine, uint32_t wait_mask) +{ + struct dmm *dmm = engine->dmm; + uint32_t r = 0, err, i; + + i = DMM_FIXED_RETRY_COUNT; + while (true) { + r = readl(dmm->base + reg[PAT_STATUS][engine->id]); + err = r & DMM_PATSTATUS_ERR; + if (err) + return -EFAULT; + + if ((r & wait_mask) == wait_mask) + break; + + if (--i == 0) + return -ETIMEDOUT; + + udelay(1); + } + + return 0; +} + +static void release_engine(struct refill_engine *engine) +{ + unsigned long flags; + + spin_lock_irqsave(&list_lock, flags); + list_add(&engine->idle_node, &omap_dmm->idle_head); + spin_unlock_irqrestore(&list_lock, flags); + + atomic_inc(&omap_dmm->engine_counter); + wake_up_interruptible(&omap_dmm->engine_queue); +} + +static irqreturn_t omap_dmm_irq_handler(int irq, void *arg) +{ + struct dmm *dmm = arg; + uint32_t status = readl(dmm->base + DMM_PAT_IRQSTATUS); + int i; + + /* ack IRQ */ + writel(status, dmm->base + DMM_PAT_IRQSTATUS); + + for (i = 0; i < dmm->num_engines; i++) { + if (status & DMM_IRQSTAT_LST) { + wake_up_interruptible(&dmm->engines[i].wait_for_refill); + + if (dmm->engines[i].async) + release_engine(&dmm->engines[i]); + } + + status >>= 8; + } + + return IRQ_HANDLED; +} + +/** + * Get a handle for a DMM transaction + */ +static struct dmm_txn *dmm_txn_init(struct dmm *dmm, struct tcm *tcm) +{ + struct dmm_txn *txn = NULL; + struct refill_engine *engine = NULL; + int ret; + unsigned long flags; + + + /* wait until an engine is available */ + ret = wait_event_interruptible(omap_dmm->engine_queue, + atomic_add_unless(&omap_dmm->engine_counter, -1, 0)); + if (ret) + return ERR_PTR(ret); + + /* grab an idle engine */ + spin_lock_irqsave(&list_lock, flags); + if (!list_empty(&dmm->idle_head)) { + engine = list_entry(dmm->idle_head.next, struct refill_engine, + idle_node); + list_del(&engine->idle_node); + } + spin_unlock_irqrestore(&list_lock, flags); + + BUG_ON(!engine); + + txn = &engine->txn; + engine->tcm = tcm; + txn->engine_handle = engine; + txn->last_pat = NULL; + txn->current_va = engine->refill_va; + txn->current_pa = engine->refill_pa; + + return txn; +} + +/** + * Add region to DMM transaction. If pages or pages[i] is NULL, then the + * corresponding slot is cleared (ie. dummy_pa is programmed) + */ +static void dmm_txn_append(struct dmm_txn *txn, struct pat_area *area, + struct page **pages, uint32_t npages, uint32_t roll) +{ + dma_addr_t pat_pa = 0; + uint32_t *data; + struct pat *pat; + struct refill_engine *engine = txn->engine_handle; + int columns = (1 + area->x1 - area->x0); + int rows = (1 + area->y1 - area->y0); + int i = columns*rows; + + pat = alloc_dma(txn, sizeof(struct pat), &pat_pa); + + if (txn->last_pat) + txn->last_pat->next_pa = (uint32_t)pat_pa; + + pat->area = *area; + + /* adjust Y coordinates based off of container parameters */ + pat->area.y0 += engine->tcm->y_offset; + pat->area.y1 += engine->tcm->y_offset; + + pat->ctrl = (struct pat_ctrl){ + .start = 1, + .lut_id = engine->tcm->lut_id, + }; + + data = alloc_dma(txn, 4*i, &pat->data_pa); + + while (i--) { + int n = i + roll; + if (n >= npages) + n -= npages; + data[i] = (pages && pages[n]) ? + page_to_phys(pages[n]) : engine->dmm->dummy_pa; + } + + txn->last_pat = pat; + + return; +} + +/** + * Commit the DMM transaction. + */ +static int dmm_txn_commit(struct dmm_txn *txn, bool wait) +{ + int ret = 0; + struct refill_engine *engine = txn->engine_handle; + struct dmm *dmm = engine->dmm; + + if (!txn->last_pat) { + dev_err(engine->dmm->dev, "need at least one txn\n"); + ret = -EINVAL; + goto cleanup; + } + + txn->last_pat->next_pa = 0; + + /* write to PAT_DESCR to clear out any pending transaction */ + writel(0x0, dmm->base + reg[PAT_DESCR][engine->id]); + + /* wait for engine ready: */ + ret = wait_status(engine, DMM_PATSTATUS_READY); + if (ret) { + ret = -EFAULT; + goto cleanup; + } + + /* mark whether it is async to denote list management in IRQ handler */ + engine->async = wait ? false : true; + + /* kick reload */ + writel(engine->refill_pa, + dmm->base + reg[PAT_DESCR][engine->id]); + + if (wait) { + if (wait_event_interruptible_timeout(engine->wait_for_refill, + wait_status(engine, DMM_PATSTATUS_READY) == 0, + msecs_to_jiffies(1)) <= 0) { + dev_err(dmm->dev, "timed out waiting for done\n"); + ret = -ETIMEDOUT; + } + } + +cleanup: + /* only place engine back on list if we are done with it */ + if (ret || wait) + release_engine(engine); + + return ret; +} + +/* + * DMM programming + */ +static int fill(struct tcm_area *area, struct page **pages, + uint32_t npages, uint32_t roll, bool wait) +{ + int ret = 0; + struct tcm_area slice, area_s; + struct dmm_txn *txn; + + txn = dmm_txn_init(omap_dmm, area->tcm); + if (IS_ERR_OR_NULL(txn)) + return -ENOMEM; + + tcm_for_each_slice(slice, *area, area_s) { + struct pat_area p_area = { + .x0 = slice.p0.x, .y0 = slice.p0.y, + .x1 = slice.p1.x, .y1 = slice.p1.y, + }; + + dmm_txn_append(txn, &p_area, pages, npages, roll); + + roll += tcm_sizeof(slice); + } + + ret = dmm_txn_commit(txn, wait); + + return ret; +} + +/* + * Pin/unpin + */ + +/* note: slots for which pages[i] == NULL are filled w/ dummy page + */ +int tiler_pin(struct tiler_block *block, struct page **pages, + uint32_t npages, uint32_t roll, bool wait) +{ + int ret; + + ret = fill(&block->area, pages, npages, roll, wait); + + if (ret) + tiler_unpin(block); + + return ret; +} + +int tiler_unpin(struct tiler_block *block) +{ + return fill(&block->area, NULL, 0, 0, false); +} + +/* + * Reserve/release + */ +struct tiler_block *tiler_reserve_2d(enum tiler_fmt fmt, uint16_t w, + uint16_t h, uint16_t align) +{ + struct tiler_block *block = kzalloc(sizeof(*block), GFP_KERNEL); + u32 min_align = 128; + int ret; + unsigned long flags; + + BUG_ON(!validfmt(fmt)); + + /* convert width/height to slots */ + w = DIV_ROUND_UP(w, geom[fmt].slot_w); + h = DIV_ROUND_UP(h, geom[fmt].slot_h); + + /* convert alignment to slots */ + min_align = max(min_align, (geom[fmt].slot_w * geom[fmt].cpp)); + align = ALIGN(align, min_align); + align /= geom[fmt].slot_w * geom[fmt].cpp; + + block->fmt = fmt; + + ret = tcm_reserve_2d(containers[fmt], w, h, align, &block->area); + if (ret) { + kfree(block); + return ERR_PTR(-ENOMEM); + } + + /* add to allocation list */ + spin_lock_irqsave(&list_lock, flags); + list_add(&block->alloc_node, &omap_dmm->alloc_head); + spin_unlock_irqrestore(&list_lock, flags); + + return block; +} + +struct tiler_block *tiler_reserve_1d(size_t size) +{ + struct tiler_block *block = kzalloc(sizeof(*block), GFP_KERNEL); + int num_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + unsigned long flags; + + if (!block) + return ERR_PTR(-ENOMEM); + + block->fmt = TILFMT_PAGE; + + if (tcm_reserve_1d(containers[TILFMT_PAGE], num_pages, + &block->area)) { + kfree(block); + return ERR_PTR(-ENOMEM); + } + + spin_lock_irqsave(&list_lock, flags); + list_add(&block->alloc_node, &omap_dmm->alloc_head); + spin_unlock_irqrestore(&list_lock, flags); + + return block; +} + +/* note: if you have pin'd pages, you should have already unpin'd first! */ +int tiler_release(struct tiler_block *block) +{ + int ret = tcm_free(&block->area); + unsigned long flags; + + if (block->area.tcm) + dev_err(omap_dmm->dev, "failed to release block\n"); + + spin_lock_irqsave(&list_lock, flags); + list_del(&block->alloc_node); + spin_unlock_irqrestore(&list_lock, flags); + + kfree(block); + return ret; +} + +/* + * Utils + */ + +/* calculate the tiler space address of a pixel in a view orientation... + * below description copied from the display subsystem section of TRM: + * + * When the TILER is addressed, the bits: + * [28:27] = 0x0 for 8-bit tiled + * 0x1 for 16-bit tiled + * 0x2 for 32-bit tiled + * 0x3 for page mode + * [31:29] = 0x0 for 0-degree view + * 0x1 for 180-degree view + mirroring + * 0x2 for 0-degree view + mirroring + * 0x3 for 180-degree view + * 0x4 for 270-degree view + mirroring + * 0x5 for 270-degree view + * 0x6 for 90-degree view + * 0x7 for 90-degree view + mirroring + * Otherwise the bits indicated the corresponding bit address to access + * the SDRAM. + */ +static u32 tiler_get_address(enum tiler_fmt fmt, u32 orient, u32 x, u32 y) +{ + u32 x_bits, y_bits, tmp, x_mask, y_mask, alignment; + + x_bits = CONT_WIDTH_BITS - geom[fmt].x_shft; + y_bits = CONT_HEIGHT_BITS - geom[fmt].y_shft; + alignment = geom[fmt].x_shft + geom[fmt].y_shft; + + /* validate coordinate */ + x_mask = MASK(x_bits); + y_mask = MASK(y_bits); + + if (x < 0 || x > x_mask || y < 0 || y > y_mask) { + DBG("invalid coords: %u < 0 || %u > %u || %u < 0 || %u > %u", + x, x, x_mask, y, y, y_mask); + return 0; + } + + /* account for mirroring */ + if (orient & MASK_X_INVERT) + x ^= x_mask; + if (orient & MASK_Y_INVERT) + y ^= y_mask; + + /* get coordinate address */ + if (orient & MASK_XY_FLIP) + tmp = ((x << y_bits) + y); + else + tmp = ((y << x_bits) + x); + + return TIL_ADDR((tmp << alignment), orient, fmt); +} + +dma_addr_t tiler_ssptr(struct tiler_block *block) +{ + BUG_ON(!validfmt(block->fmt)); + + return TILVIEW_8BIT + tiler_get_address(block->fmt, 0, + block->area.p0.x * geom[block->fmt].slot_w, + block->area.p0.y * geom[block->fmt].slot_h); +} + +dma_addr_t tiler_tsptr(struct tiler_block *block, uint32_t orient, + uint32_t x, uint32_t y) +{ + struct tcm_pt *p = &block->area.p0; + BUG_ON(!validfmt(block->fmt)); + + return tiler_get_address(block->fmt, orient, + (p->x * geom[block->fmt].slot_w) + x, + (p->y * geom[block->fmt].slot_h) + y); +} + +void tiler_align(enum tiler_fmt fmt, uint16_t *w, uint16_t *h) +{ + BUG_ON(!validfmt(fmt)); + *w = round_up(*w, geom[fmt].slot_w); + *h = round_up(*h, geom[fmt].slot_h); +} + +uint32_t tiler_stride(enum tiler_fmt fmt, uint32_t orient) +{ + BUG_ON(!validfmt(fmt)); + + if (orient & MASK_XY_FLIP) + return 1 << (CONT_HEIGHT_BITS + geom[fmt].x_shft); + else + return 1 << (CONT_WIDTH_BITS + geom[fmt].y_shft); +} + +size_t tiler_size(enum tiler_fmt fmt, uint16_t w, uint16_t h) +{ + tiler_align(fmt, &w, &h); + return geom[fmt].cpp * w * h; +} + +size_t tiler_vsize(enum tiler_fmt fmt, uint16_t w, uint16_t h) +{ + BUG_ON(!validfmt(fmt)); + return round_up(geom[fmt].cpp * w, PAGE_SIZE) * h; +} + +bool dmm_is_available(void) +{ + return omap_dmm ? true : false; +} + +static int omap_dmm_remove(struct platform_device *dev) +{ + struct tiler_block *block, *_block; + int i; + unsigned long flags; + + if (omap_dmm) { + /* free all area regions */ + spin_lock_irqsave(&list_lock, flags); + list_for_each_entry_safe(block, _block, &omap_dmm->alloc_head, + alloc_node) { + list_del(&block->alloc_node); + kfree(block); + } + spin_unlock_irqrestore(&list_lock, flags); + + for (i = 0; i < omap_dmm->num_lut; i++) + if (omap_dmm->tcm && omap_dmm->tcm[i]) + omap_dmm->tcm[i]->deinit(omap_dmm->tcm[i]); + kfree(omap_dmm->tcm); + + kfree(omap_dmm->engines); + if (omap_dmm->refill_va) + dma_free_writecombine(omap_dmm->dev, + REFILL_BUFFER_SIZE * omap_dmm->num_engines, + omap_dmm->refill_va, + omap_dmm->refill_pa); + if (omap_dmm->dummy_page) + __free_page(omap_dmm->dummy_page); + + if (omap_dmm->irq > 0) + free_irq(omap_dmm->irq, omap_dmm); + + iounmap(omap_dmm->base); + kfree(omap_dmm); + omap_dmm = NULL; + } + + return 0; +} + +static int omap_dmm_probe(struct platform_device *dev) +{ + int ret = -EFAULT, i; + struct tcm_area area = {0}; + u32 hwinfo, pat_geom; + struct resource *mem; + + omap_dmm = kzalloc(sizeof(*omap_dmm), GFP_KERNEL); + if (!omap_dmm) { + dev_err(&dev->dev, "failed to allocate driver data section\n"); + goto fail; + } + + /* initialize lists */ + INIT_LIST_HEAD(&omap_dmm->alloc_head); + INIT_LIST_HEAD(&omap_dmm->idle_head); + + init_waitqueue_head(&omap_dmm->engine_queue); + + /* lookup hwmod data - base address and irq */ + mem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&dev->dev, "failed to get base address resource\n"); + goto fail; + } + + omap_dmm->base = ioremap(mem->start, SZ_2K); + + if (!omap_dmm->base) { + dev_err(&dev->dev, "failed to get dmm base address\n"); + goto fail; + } + + omap_dmm->irq = platform_get_irq(dev, 0); + if (omap_dmm->irq < 0) { + dev_err(&dev->dev, "failed to get IRQ resource\n"); + goto fail; + } + + omap_dmm->dev = &dev->dev; + + hwinfo = readl(omap_dmm->base + DMM_PAT_HWINFO); + omap_dmm->num_engines = (hwinfo >> 24) & 0x1F; + omap_dmm->num_lut = (hwinfo >> 16) & 0x1F; + omap_dmm->container_width = 256; + omap_dmm->container_height = 128; + + atomic_set(&omap_dmm->engine_counter, omap_dmm->num_engines); + + /* read out actual LUT width and height */ + pat_geom = readl(omap_dmm->base + DMM_PAT_GEOMETRY); + omap_dmm->lut_width = ((pat_geom >> 16) & 0xF) << 5; + omap_dmm->lut_height = ((pat_geom >> 24) & 0xF) << 5; + + /* increment LUT by one if on OMAP5 */ + /* LUT has twice the height, and is split into a separate container */ + if (omap_dmm->lut_height != omap_dmm->container_height) + omap_dmm->num_lut++; + + /* initialize DMM registers */ + writel(0x88888888, omap_dmm->base + DMM_PAT_VIEW__0); + writel(0x88888888, omap_dmm->base + DMM_PAT_VIEW__1); + writel(0x80808080, omap_dmm->base + DMM_PAT_VIEW_MAP__0); + writel(0x80000000, omap_dmm->base + DMM_PAT_VIEW_MAP_BASE); + writel(0x88888888, omap_dmm->base + DMM_TILER_OR__0); + writel(0x88888888, omap_dmm->base + DMM_TILER_OR__1); + + ret = request_irq(omap_dmm->irq, omap_dmm_irq_handler, IRQF_SHARED, + "omap_dmm_irq_handler", omap_dmm); + + if (ret) { + dev_err(&dev->dev, "couldn't register IRQ %d, error %d\n", + omap_dmm->irq, ret); + omap_dmm->irq = -1; + goto fail; + } + + /* Enable all interrupts for each refill engine except + * ERR_LUT_MISS<n> (which is just advisory, and we don't care + * about because we want to be able to refill live scanout + * buffers for accelerated pan/scroll) and FILL_DSC<n> which + * we just generally don't care about. + */ + writel(0x7e7e7e7e, omap_dmm->base + DMM_PAT_IRQENABLE_SET); + + omap_dmm->dummy_page = alloc_page(GFP_KERNEL | __GFP_DMA32); + if (!omap_dmm->dummy_page) { + dev_err(&dev->dev, "could not allocate dummy page\n"); + ret = -ENOMEM; + goto fail; + } + + /* set dma mask for device */ + /* NOTE: this is a workaround for the hwmod not initializing properly */ + dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + omap_dmm->dummy_pa = page_to_phys(omap_dmm->dummy_page); + + /* alloc refill memory */ + omap_dmm->refill_va = dma_alloc_writecombine(&dev->dev, + REFILL_BUFFER_SIZE * omap_dmm->num_engines, + &omap_dmm->refill_pa, GFP_KERNEL); + if (!omap_dmm->refill_va) { + dev_err(&dev->dev, "could not allocate refill memory\n"); + goto fail; + } + + /* alloc engines */ + omap_dmm->engines = kzalloc( + omap_dmm->num_engines * sizeof(struct refill_engine), + GFP_KERNEL); + if (!omap_dmm->engines) { + dev_err(&dev->dev, "could not allocate engines\n"); + ret = -ENOMEM; + goto fail; + } + + for (i = 0; i < omap_dmm->num_engines; i++) { + omap_dmm->engines[i].id = i; + omap_dmm->engines[i].dmm = omap_dmm; + omap_dmm->engines[i].refill_va = omap_dmm->refill_va + + (REFILL_BUFFER_SIZE * i); + omap_dmm->engines[i].refill_pa = omap_dmm->refill_pa + + (REFILL_BUFFER_SIZE * i); + init_waitqueue_head(&omap_dmm->engines[i].wait_for_refill); + + list_add(&omap_dmm->engines[i].idle_node, &omap_dmm->idle_head); + } + + omap_dmm->tcm = kzalloc(omap_dmm->num_lut * sizeof(*omap_dmm->tcm), + GFP_KERNEL); + if (!omap_dmm->tcm) { + dev_err(&dev->dev, "failed to allocate lut ptrs\n"); + ret = -ENOMEM; + goto fail; + } + + /* init containers */ + /* Each LUT is associated with a TCM (container manager). We use the + lut_id to denote the lut_id used to identify the correct LUT for + programming during reill operations */ + for (i = 0; i < omap_dmm->num_lut; i++) { + omap_dmm->tcm[i] = sita_init(omap_dmm->container_width, + omap_dmm->container_height, + NULL); + + if (!omap_dmm->tcm[i]) { + dev_err(&dev->dev, "failed to allocate container\n"); + ret = -ENOMEM; + goto fail; + } + + omap_dmm->tcm[i]->lut_id = i; + } + + /* assign access mode containers to applicable tcm container */ + /* OMAP 4 has 1 container for all 4 views */ + /* OMAP 5 has 2 containers, 1 for 2D and 1 for 1D */ + containers[TILFMT_8BIT] = omap_dmm->tcm[0]; + containers[TILFMT_16BIT] = omap_dmm->tcm[0]; + containers[TILFMT_32BIT] = omap_dmm->tcm[0]; + + if (omap_dmm->container_height != omap_dmm->lut_height) { + /* second LUT is used for PAGE mode. Programming must use + y offset that is added to all y coordinates. LUT id is still + 0, because it is the same LUT, just the upper 128 lines */ + containers[TILFMT_PAGE] = omap_dmm->tcm[1]; + omap_dmm->tcm[1]->y_offset = OMAP5_LUT_OFFSET; + omap_dmm->tcm[1]->lut_id = 0; + } else { + containers[TILFMT_PAGE] = omap_dmm->tcm[0]; + } + + area = (struct tcm_area) { + .tcm = NULL, + .p1.x = omap_dmm->container_width - 1, + .p1.y = omap_dmm->container_height - 1, + }; + + /* initialize all LUTs to dummy page entries */ + for (i = 0; i < omap_dmm->num_lut; i++) { + area.tcm = omap_dmm->tcm[i]; + if (fill(&area, NULL, 0, 0, true)) + dev_err(omap_dmm->dev, "refill failed"); + } + + dev_info(omap_dmm->dev, "initialized all PAT entries\n"); + + return 0; + +fail: + if (omap_dmm_remove(dev)) + dev_err(&dev->dev, "cleanup failed\n"); + return ret; +} + +/* + * debugfs support + */ + +#ifdef CONFIG_DEBUG_FS + +static const char *alphabet = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; +static const char *special = ".,:;'\"`~!^-+"; + +static void fill_map(char **map, int xdiv, int ydiv, struct tcm_area *a, + char c, bool ovw) +{ + int x, y; + for (y = a->p0.y / ydiv; y <= a->p1.y / ydiv; y++) + for (x = a->p0.x / xdiv; x <= a->p1.x / xdiv; x++) + if (map[y][x] == ' ' || ovw) + map[y][x] = c; +} + +static void fill_map_pt(char **map, int xdiv, int ydiv, struct tcm_pt *p, + char c) +{ + map[p->y / ydiv][p->x / xdiv] = c; +} + +static char read_map_pt(char **map, int xdiv, int ydiv, struct tcm_pt *p) +{ + return map[p->y / ydiv][p->x / xdiv]; +} + +static int map_width(int xdiv, int x0, int x1) +{ + return (x1 / xdiv) - (x0 / xdiv) + 1; +} + +static void text_map(char **map, int xdiv, char *nice, int yd, int x0, int x1) +{ + char *p = map[yd] + (x0 / xdiv); + int w = (map_width(xdiv, x0, x1) - strlen(nice)) / 2; + if (w >= 0) { + p += w; + while (*nice) + *p++ = *nice++; + } +} + +static void map_1d_info(char **map, int xdiv, int ydiv, char *nice, + struct tcm_area *a) +{ + sprintf(nice, "%dK", tcm_sizeof(*a) * 4); + if (a->p0.y + 1 < a->p1.y) { + text_map(map, xdiv, nice, (a->p0.y + a->p1.y) / 2 / ydiv, 0, + 256 - 1); + } else if (a->p0.y < a->p1.y) { + if (strlen(nice) < map_width(xdiv, a->p0.x, 256 - 1)) + text_map(map, xdiv, nice, a->p0.y / ydiv, + a->p0.x + xdiv, 256 - 1); + else if (strlen(nice) < map_width(xdiv, 0, a->p1.x)) + text_map(map, xdiv, nice, a->p1.y / ydiv, + 0, a->p1.y - xdiv); + } else if (strlen(nice) + 1 < map_width(xdiv, a->p0.x, a->p1.x)) { + text_map(map, xdiv, nice, a->p0.y / ydiv, a->p0.x, a->p1.x); + } +} + +static void map_2d_info(char **map, int xdiv, int ydiv, char *nice, + struct tcm_area *a) +{ + sprintf(nice, "(%d*%d)", tcm_awidth(*a), tcm_aheight(*a)); + if (strlen(nice) + 1 < map_width(xdiv, a->p0.x, a->p1.x)) + text_map(map, xdiv, nice, (a->p0.y + a->p1.y) / 2 / ydiv, + a->p0.x, a->p1.x); +} + +int tiler_map_show(struct seq_file *s, void *arg) +{ + int xdiv = 2, ydiv = 1; + char **map = NULL, *global_map; + struct tiler_block *block; + struct tcm_area a, p; + int i; + const char *m2d = alphabet; + const char *a2d = special; + const char *m2dp = m2d, *a2dp = a2d; + char nice[128]; + int h_adj; + int w_adj; + unsigned long flags; + int lut_idx; + + + if (!omap_dmm) { + /* early return if dmm/tiler device is not initialized */ + return 0; + } + + h_adj = omap_dmm->container_height / ydiv; + w_adj = omap_dmm->container_width / xdiv; + + map = kmalloc(h_adj * sizeof(*map), GFP_KERNEL); + global_map = kmalloc((w_adj + 1) * h_adj, GFP_KERNEL); + + if (!map || !global_map) + goto error; + + for (lut_idx = 0; lut_idx < omap_dmm->num_lut; lut_idx++) { + memset(map, 0, sizeof(h_adj * sizeof(*map))); + memset(global_map, ' ', (w_adj + 1) * h_adj); + + for (i = 0; i < omap_dmm->container_height; i++) { + map[i] = global_map + i * (w_adj + 1); + map[i][w_adj] = 0; + } + + spin_lock_irqsave(&list_lock, flags); + + list_for_each_entry(block, &omap_dmm->alloc_head, alloc_node) { + if (block->area.tcm == omap_dmm->tcm[lut_idx]) { + if (block->fmt != TILFMT_PAGE) { + fill_map(map, xdiv, ydiv, &block->area, + *m2dp, true); + if (!*++a2dp) + a2dp = a2d; + if (!*++m2dp) + m2dp = m2d; + map_2d_info(map, xdiv, ydiv, nice, + &block->area); + } else { + bool start = read_map_pt(map, xdiv, + ydiv, &block->area.p0) == ' '; + bool end = read_map_pt(map, xdiv, ydiv, + &block->area.p1) == ' '; + + tcm_for_each_slice(a, block->area, p) + fill_map(map, xdiv, ydiv, &a, + '=', true); + fill_map_pt(map, xdiv, ydiv, + &block->area.p0, + start ? '<' : 'X'); + fill_map_pt(map, xdiv, ydiv, + &block->area.p1, + end ? '>' : 'X'); + map_1d_info(map, xdiv, ydiv, nice, + &block->area); + } + } + } + + spin_unlock_irqrestore(&list_lock, flags); + + if (s) { + seq_printf(s, "CONTAINER %d DUMP BEGIN\n", lut_idx); + for (i = 0; i < 128; i++) + seq_printf(s, "%03d:%s\n", i, map[i]); + seq_printf(s, "CONTAINER %d DUMP END\n", lut_idx); + } else { + dev_dbg(omap_dmm->dev, "CONTAINER %d DUMP BEGIN\n", + lut_idx); + for (i = 0; i < 128; i++) + dev_dbg(omap_dmm->dev, "%03d:%s\n", i, map[i]); + dev_dbg(omap_dmm->dev, "CONTAINER %d DUMP END\n", + lut_idx); + } + } + +error: + kfree(map); + kfree(global_map); + + return 0; +} +#endif + +#ifdef CONFIG_PM +static int omap_dmm_resume(struct device *dev) +{ + struct tcm_area area; + int i; + + if (!omap_dmm) + return -ENODEV; + + area = (struct tcm_area) { + .tcm = NULL, + .p1.x = omap_dmm->container_width - 1, + .p1.y = omap_dmm->container_height - 1, + }; + + /* initialize all LUTs to dummy page entries */ + for (i = 0; i < omap_dmm->num_lut; i++) { + area.tcm = omap_dmm->tcm[i]; + if (fill(&area, NULL, 0, 0, true)) + dev_err(dev, "refill failed"); + } + + return 0; +} + +static const struct dev_pm_ops omap_dmm_pm_ops = { + .resume = omap_dmm_resume, +}; +#endif + +struct platform_driver omap_dmm_driver = { + .probe = omap_dmm_probe, + .remove = omap_dmm_remove, + .driver = { + .owner = THIS_MODULE, + .name = DMM_DRIVER_NAME, +#ifdef CONFIG_PM + .pm = &omap_dmm_pm_ops, +#endif + }, +}; + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Andy Gross <andy.gross@ti.com>"); +MODULE_DESCRIPTION("OMAP DMM/Tiler Driver"); +MODULE_ALIAS("platform:" DMM_DRIVER_NAME); diff --git a/drivers/gpu/drm/omapdrm/omap_dmm_tiler.h b/drivers/gpu/drm/omapdrm/omap_dmm_tiler.h new file mode 100644 index 000000000000..4fdd61e54bd2 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_dmm_tiler.h @@ -0,0 +1,141 @@ +/* + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Rob Clark <rob@ti.com> + * Andy Gross <andy.gross@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef OMAP_DMM_TILER_H +#define OMAP_DMM_TILER_H + +#include "omap_drv.h" +#include "tcm.h" + +enum tiler_fmt { + TILFMT_8BIT = 0, + TILFMT_16BIT, + TILFMT_32BIT, + TILFMT_PAGE, + TILFMT_NFORMATS +}; + +struct pat_area { + u32 x0:8; + u32 y0:8; + u32 x1:8; + u32 y1:8; +}; + +struct tiler_block { + struct list_head alloc_node; /* node for global block list */ + struct tcm_area area; /* area */ + enum tiler_fmt fmt; /* format */ +}; + +/* bits representing the same slot in DMM-TILER hw-block */ +#define SLOT_WIDTH_BITS 6 +#define SLOT_HEIGHT_BITS 6 + +/* bits reserved to describe coordinates in DMM-TILER hw-block */ +#define CONT_WIDTH_BITS 14 +#define CONT_HEIGHT_BITS 13 + +/* calculated constants */ +#define TILER_PAGE (1 << (SLOT_WIDTH_BITS + SLOT_HEIGHT_BITS)) +#define TILER_WIDTH (1 << (CONT_WIDTH_BITS - SLOT_WIDTH_BITS)) +#define TILER_HEIGHT (1 << (CONT_HEIGHT_BITS - SLOT_HEIGHT_BITS)) + +/* +Table 15-11. Coding and Description of TILER Orientations +S Y X Description Alternate description +0 0 0 0-degree view Natural view +0 0 1 0-degree view with vertical mirror 180-degree view with horizontal mirror +0 1 0 0-degree view with horizontal mirror 180-degree view with vertical mirror +0 1 1 180-degree view +1 0 0 90-degree view with vertical mirror 270-degree view with horizontal mirror +1 0 1 270-degree view +1 1 0 90-degree view +1 1 1 90-degree view with horizontal mirror 270-degree view with vertical mirror + */ +#define MASK_XY_FLIP (1 << 31) +#define MASK_Y_INVERT (1 << 30) +#define MASK_X_INVERT (1 << 29) +#define SHIFT_ACC_MODE 27 +#define MASK_ACC_MODE 3 + +#define MASK(bits) ((1 << (bits)) - 1) + +#define TILVIEW_8BIT 0x60000000u +#define TILVIEW_16BIT (TILVIEW_8BIT + VIEW_SIZE) +#define TILVIEW_32BIT (TILVIEW_16BIT + VIEW_SIZE) +#define TILVIEW_PAGE (TILVIEW_32BIT + VIEW_SIZE) +#define TILVIEW_END (TILVIEW_PAGE + VIEW_SIZE) + +/* create tsptr by adding view orientation and access mode */ +#define TIL_ADDR(x, orient, a)\ + ((u32) (x) | (orient) | ((a) << SHIFT_ACC_MODE)) + +#ifdef CONFIG_DEBUG_FS +int tiler_map_show(struct seq_file *s, void *arg); +#endif + +/* pin/unpin */ +int tiler_pin(struct tiler_block *block, struct page **pages, + uint32_t npages, uint32_t roll, bool wait); +int tiler_unpin(struct tiler_block *block); + +/* reserve/release */ +struct tiler_block *tiler_reserve_2d(enum tiler_fmt fmt, uint16_t w, uint16_t h, + uint16_t align); +struct tiler_block *tiler_reserve_1d(size_t size); +int tiler_release(struct tiler_block *block); + +/* utilities */ +dma_addr_t tiler_ssptr(struct tiler_block *block); +dma_addr_t tiler_tsptr(struct tiler_block *block, uint32_t orient, + uint32_t x, uint32_t y); +uint32_t tiler_stride(enum tiler_fmt fmt, uint32_t orient); +size_t tiler_size(enum tiler_fmt fmt, uint16_t w, uint16_t h); +size_t tiler_vsize(enum tiler_fmt fmt, uint16_t w, uint16_t h); +void tiler_align(enum tiler_fmt fmt, uint16_t *w, uint16_t *h); +bool dmm_is_available(void); + +extern struct platform_driver omap_dmm_driver; + +/* GEM bo flags -> tiler fmt */ +static inline enum tiler_fmt gem2fmt(uint32_t flags) +{ + switch (flags & OMAP_BO_TILED) { + case OMAP_BO_TILED_8: + return TILFMT_8BIT; + case OMAP_BO_TILED_16: + return TILFMT_16BIT; + case OMAP_BO_TILED_32: + return TILFMT_32BIT; + default: + return TILFMT_PAGE; + } +} + +static inline bool validfmt(enum tiler_fmt fmt) +{ + switch (fmt) { + case TILFMT_8BIT: + case TILFMT_16BIT: + case TILFMT_32BIT: + case TILFMT_PAGE: + return true; + default: + return false; + } +} + +#endif diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c new file mode 100644 index 000000000000..9083538bd16a --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_drv.c @@ -0,0 +1,610 @@ +/* + * drivers/gpu/drm/omapdrm/omap_drv.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include "drm_crtc_helper.h" +#include "drm_fb_helper.h" +#include "omap_dmm_tiler.h" + +#define DRIVER_NAME MODULE_NAME +#define DRIVER_DESC "OMAP DRM" +#define DRIVER_DATE "20110917" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +static int num_crtc = CONFIG_DRM_OMAP_NUM_CRTCS; + +MODULE_PARM_DESC(num_crtc, "Number of overlays to use as CRTCs"); +module_param(num_crtc, int, 0600); + +/* + * mode config funcs + */ + +/* Notes about mapping DSS and DRM entities: + * CRTC: overlay + * encoder: manager.. with some extension to allow one primary CRTC + * and zero or more video CRTC's to be mapped to one encoder? + * connector: dssdev.. manager can be attached/detached from different + * devices + */ + +static void omap_fb_output_poll_changed(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + DBG("dev=%p", dev); + if (priv->fbdev) + drm_fb_helper_hotplug_event(priv->fbdev); +} + +static const struct drm_mode_config_funcs omap_mode_config_funcs = { + .fb_create = omap_framebuffer_create, + .output_poll_changed = omap_fb_output_poll_changed, +}; + +static int get_connector_type(struct omap_dss_device *dssdev) +{ + switch (dssdev->type) { + case OMAP_DISPLAY_TYPE_HDMI: + return DRM_MODE_CONNECTOR_HDMIA; + case OMAP_DISPLAY_TYPE_DPI: + if (!strcmp(dssdev->name, "dvi")) + return DRM_MODE_CONNECTOR_DVID; + /* fallthrough */ + default: + return DRM_MODE_CONNECTOR_Unknown; + } +} + +static int omap_modeset_init(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_dss_device *dssdev = NULL; + int num_ovls = dss_feat_get_num_ovls(); + int id; + + drm_mode_config_init(dev); + + omap_drm_irq_install(dev); + + /* + * Create private planes and CRTCs for the last NUM_CRTCs overlay + * plus manager: + */ + for (id = 0; id < min(num_crtc, num_ovls); id++) { + struct drm_plane *plane; + struct drm_crtc *crtc; + + plane = omap_plane_init(dev, id, true); + crtc = omap_crtc_init(dev, plane, pipe2chan(id), id); + + BUG_ON(priv->num_crtcs >= ARRAY_SIZE(priv->crtcs)); + priv->crtcs[id] = crtc; + priv->num_crtcs++; + + priv->planes[id] = plane; + priv->num_planes++; + } + + /* + * Create normal planes for the remaining overlays: + */ + for (; id < num_ovls; id++) { + struct drm_plane *plane = omap_plane_init(dev, id, false); + + BUG_ON(priv->num_planes >= ARRAY_SIZE(priv->planes)); + priv->planes[priv->num_planes++] = plane; + } + + for_each_dss_dev(dssdev) { + struct drm_connector *connector; + struct drm_encoder *encoder; + + if (!dssdev->driver) { + dev_warn(dev->dev, "%s has no driver.. skipping it\n", + dssdev->name); + return 0; + } + + if (!(dssdev->driver->get_timings || + dssdev->driver->read_edid)) { + dev_warn(dev->dev, "%s driver does not support " + "get_timings or read_edid.. skipping it!\n", + dssdev->name); + return 0; + } + + encoder = omap_encoder_init(dev, dssdev); + + if (!encoder) { + dev_err(dev->dev, "could not create encoder: %s\n", + dssdev->name); + return -ENOMEM; + } + + connector = omap_connector_init(dev, + get_connector_type(dssdev), dssdev, encoder); + + if (!connector) { + dev_err(dev->dev, "could not create connector: %s\n", + dssdev->name); + return -ENOMEM; + } + + BUG_ON(priv->num_encoders >= ARRAY_SIZE(priv->encoders)); + BUG_ON(priv->num_connectors >= ARRAY_SIZE(priv->connectors)); + + priv->encoders[priv->num_encoders++] = encoder; + priv->connectors[priv->num_connectors++] = connector; + + drm_mode_connector_attach_encoder(connector, encoder); + + /* figure out which crtc's we can connect the encoder to: */ + encoder->possible_crtcs = 0; + for (id = 0; id < priv->num_crtcs; id++) { + enum omap_dss_output_id supported_outputs = + dss_feat_get_supported_outputs(pipe2chan(id)); + if (supported_outputs & dssdev->output->id) + encoder->possible_crtcs |= (1 << id); + } + } + + dev->mode_config.min_width = 32; + dev->mode_config.min_height = 32; + + /* note: eventually will need some cpu_is_omapXYZ() type stuff here + * to fill in these limits properly on different OMAP generations.. + */ + dev->mode_config.max_width = 2048; + dev->mode_config.max_height = 2048; + + dev->mode_config.funcs = &omap_mode_config_funcs; + + return 0; +} + +static void omap_modeset_free(struct drm_device *dev) +{ + drm_mode_config_cleanup(dev); +} + +/* + * drm ioctl funcs + */ + + +static int ioctl_get_param(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct omap_drm_private *priv = dev->dev_private; + struct drm_omap_param *args = data; + + DBG("%p: param=%llu", dev, args->param); + + switch (args->param) { + case OMAP_PARAM_CHIPSET_ID: + args->value = priv->omaprev; + break; + default: + DBG("unknown parameter %lld", args->param); + return -EINVAL; + } + + return 0; +} + +static int ioctl_set_param(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_param *args = data; + + switch (args->param) { + default: + DBG("unknown parameter %lld", args->param); + return -EINVAL; + } + + return 0; +} + +static int ioctl_gem_new(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_new *args = data; + VERB("%p:%p: size=0x%08x, flags=%08x", dev, file_priv, + args->size.bytes, args->flags); + return omap_gem_new_handle(dev, file_priv, args->size, + args->flags, &args->handle); +} + +static int ioctl_gem_cpu_prep(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_cpu_prep *args = data; + struct drm_gem_object *obj; + int ret; + + VERB("%p:%p: handle=%d, op=%x", dev, file_priv, args->handle, args->op); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) + return -ENOENT; + + ret = omap_gem_op_sync(obj, args->op); + + if (!ret) + ret = omap_gem_op_start(obj, args->op); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int ioctl_gem_cpu_fini(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_cpu_fini *args = data; + struct drm_gem_object *obj; + int ret; + + VERB("%p:%p: handle=%d", dev, file_priv, args->handle); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) + return -ENOENT; + + /* XXX flushy, flushy */ + ret = 0; + + if (!ret) + ret = omap_gem_op_finish(obj, args->op); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int ioctl_gem_info(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_info *args = data; + struct drm_gem_object *obj; + int ret = 0; + + VERB("%p:%p: handle=%d", dev, file_priv, args->handle); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) + return -ENOENT; + + args->size = omap_gem_mmap_size(obj); + args->offset = omap_gem_mmap_offset(obj); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = { + DRM_IOCTL_DEF_DRV(OMAP_GET_PARAM, ioctl_get_param, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_SET_PARAM, ioctl_set_param, DRM_UNLOCKED|DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), + DRM_IOCTL_DEF_DRV(OMAP_GEM_NEW, ioctl_gem_new, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_PREP, ioctl_gem_cpu_prep, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_FINI, ioctl_gem_cpu_fini, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_INFO, ioctl_gem_info, DRM_UNLOCKED|DRM_AUTH), +}; + +/* + * drm driver funcs + */ + +/** + * load - setup chip and create an initial config + * @dev: DRM device + * @flags: startup flags + * + * The driver load routine has to do several things: + * - initialize the memory manager + * - allocate initial config memory + * - setup the DRM framebuffer with the allocated memory + */ +static int dev_load(struct drm_device *dev, unsigned long flags) +{ + struct omap_drm_platform_data *pdata = dev->dev->platform_data; + struct omap_drm_private *priv; + int ret; + + DBG("load: dev=%p", dev); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(dev->dev, "could not allocate priv\n"); + return -ENOMEM; + } + + priv->omaprev = pdata->omaprev; + + dev->dev_private = priv; + + priv->wq = alloc_ordered_workqueue("omapdrm", 0); + + INIT_LIST_HEAD(&priv->obj_list); + + omap_gem_init(dev); + + ret = omap_modeset_init(dev); + if (ret) { + dev_err(dev->dev, "omap_modeset_init failed: ret=%d\n", ret); + dev->dev_private = NULL; + kfree(priv); + return ret; + } + + ret = drm_vblank_init(dev, priv->num_crtcs); + if (ret) + dev_warn(dev->dev, "could not init vblank\n"); + + priv->fbdev = omap_fbdev_init(dev); + if (!priv->fbdev) { + dev_warn(dev->dev, "omap_fbdev_init failed\n"); + /* well, limp along without an fbdev.. maybe X11 will work? */ + } + + /* store off drm_device for use in pm ops */ + dev_set_drvdata(dev->dev, dev); + + drm_kms_helper_poll_init(dev); + + return 0; +} + +static int dev_unload(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + + DBG("unload: dev=%p", dev); + + drm_kms_helper_poll_fini(dev); + drm_vblank_cleanup(dev); + omap_drm_irq_uninstall(dev); + + omap_fbdev_free(dev); + omap_modeset_free(dev); + omap_gem_deinit(dev); + + flush_workqueue(priv->wq); + destroy_workqueue(priv->wq); + + kfree(dev->dev_private); + dev->dev_private = NULL; + + dev_set_drvdata(dev->dev, NULL); + + return 0; +} + +static int dev_open(struct drm_device *dev, struct drm_file *file) +{ + file->driver_priv = NULL; + + DBG("open: dev=%p, file=%p", dev, file); + + return 0; +} + +static int dev_firstopen(struct drm_device *dev) +{ + DBG("firstopen: dev=%p", dev); + return 0; +} + +/** + * lastclose - clean up after all DRM clients have exited + * @dev: DRM device + * + * Take care of cleaning up after all DRM clients have exited. In the + * mode setting case, we want to restore the kernel's initial mode (just + * in case the last client left us in a bad state). + */ +static void dev_lastclose(struct drm_device *dev) +{ + int i; + + /* we don't support vga-switcheroo.. so just make sure the fbdev + * mode is active + */ + struct omap_drm_private *priv = dev->dev_private; + int ret; + + DBG("lastclose: dev=%p", dev); + + if (priv->rotation_prop) { + /* need to restore default rotation state.. not sure + * if there is a cleaner way to restore properties to + * default state? Maybe a flag that properties should + * automatically be restored to default state on + * lastclose? + */ + for (i = 0; i < priv->num_crtcs; i++) { + drm_object_property_set_value(&priv->crtcs[i]->base, + priv->rotation_prop, 0); + } + + for (i = 0; i < priv->num_planes; i++) { + drm_object_property_set_value(&priv->planes[i]->base, + priv->rotation_prop, 0); + } + } + + drm_modeset_lock_all(dev); + ret = drm_fb_helper_restore_fbdev_mode(priv->fbdev); + drm_modeset_unlock_all(dev); + if (ret) + DBG("failed to restore crtc mode"); +} + +static void dev_preclose(struct drm_device *dev, struct drm_file *file) +{ + DBG("preclose: dev=%p", dev); +} + +static void dev_postclose(struct drm_device *dev, struct drm_file *file) +{ + DBG("postclose: dev=%p, file=%p", dev, file); +} + +static const struct vm_operations_struct omap_gem_vm_ops = { + .fault = omap_gem_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static const struct file_operations omapdriver_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .unlocked_ioctl = drm_ioctl, + .release = drm_release, + .mmap = omap_gem_mmap, + .poll = drm_poll, + .fasync = drm_fasync, + .read = drm_read, + .llseek = noop_llseek, +}; + +static struct drm_driver omap_drm_driver = { + .driver_features = + DRIVER_HAVE_IRQ | DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, + .load = dev_load, + .unload = dev_unload, + .open = dev_open, + .firstopen = dev_firstopen, + .lastclose = dev_lastclose, + .preclose = dev_preclose, + .postclose = dev_postclose, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = omap_irq_enable_vblank, + .disable_vblank = omap_irq_disable_vblank, + .irq_preinstall = omap_irq_preinstall, + .irq_postinstall = omap_irq_postinstall, + .irq_uninstall = omap_irq_uninstall, + .irq_handler = omap_irq_handler, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = omap_debugfs_init, + .debugfs_cleanup = omap_debugfs_cleanup, +#endif + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = omap_gem_prime_export, + .gem_prime_import = omap_gem_prime_import, + .gem_init_object = omap_gem_init_object, + .gem_free_object = omap_gem_free_object, + .gem_vm_ops = &omap_gem_vm_ops, + .dumb_create = omap_gem_dumb_create, + .dumb_map_offset = omap_gem_dumb_map_offset, + .dumb_destroy = omap_gem_dumb_destroy, + .ioctls = ioctls, + .num_ioctls = DRM_OMAP_NUM_IOCTLS, + .fops = &omapdriver_fops, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, +}; + +static int pdev_suspend(struct platform_device *pDevice, pm_message_t state) +{ + DBG(""); + return 0; +} + +static int pdev_resume(struct platform_device *device) +{ + DBG(""); + return 0; +} + +static void pdev_shutdown(struct platform_device *device) +{ + DBG(""); +} + +static int pdev_probe(struct platform_device *device) +{ + DBG("%s", device->name); + return drm_platform_init(&omap_drm_driver, device); +} + +static int pdev_remove(struct platform_device *device) +{ + DBG(""); + drm_platform_exit(&omap_drm_driver, device); + + platform_driver_unregister(&omap_dmm_driver); + return 0; +} + +#ifdef CONFIG_PM +static const struct dev_pm_ops omapdrm_pm_ops = { + .resume = omap_gem_resume, +}; +#endif + +struct platform_driver pdev = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &omapdrm_pm_ops, +#endif + }, + .probe = pdev_probe, + .remove = pdev_remove, + .suspend = pdev_suspend, + .resume = pdev_resume, + .shutdown = pdev_shutdown, +}; + +static int __init omap_drm_init(void) +{ + DBG("init"); + if (platform_driver_register(&omap_dmm_driver)) { + /* we can continue on without DMM.. so not fatal */ + dev_err(NULL, "DMM registration failed\n"); + } + return platform_driver_register(&pdev); +} + +static void __exit omap_drm_fini(void) +{ + DBG("fini"); + platform_driver_unregister(&pdev); +} + +/* need late_initcall() so we load after dss_driver's are loaded */ +late_initcall(omap_drm_init); +module_exit(omap_drm_fini); + +MODULE_AUTHOR("Rob Clark <rob@ti.com>"); +MODULE_DESCRIPTION("OMAP DRM Display Driver"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/omapdrm/omap_drv.h b/drivers/gpu/drm/omapdrm/omap_drv.h new file mode 100644 index 000000000000..d4f997bb4ac0 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_drv.h @@ -0,0 +1,333 @@ +/* + * drivers/gpu/drm/omapdrm/omap_drv.h + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __OMAP_DRV_H__ +#define __OMAP_DRV_H__ + +#include <video/omapdss.h> +#include <linux/module.h> +#include <linux/types.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/omap_drm.h> +#include <linux/platform_data/omap_drm.h> + + +#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) +#define VERB(fmt, ...) if (0) DRM_DEBUG(fmt, ##__VA_ARGS__) /* verbose debug */ + +#define MODULE_NAME "omapdrm" + +/* max # of mapper-id's that can be assigned.. todo, come up with a better + * (but still inexpensive) way to store/access per-buffer mapper private + * data.. + */ +#define MAX_MAPPERS 2 + +/* parameters which describe (unrotated) coordinates of scanout within a fb: */ +struct omap_drm_window { + uint32_t rotation; + int32_t crtc_x, crtc_y; /* signed because can be offscreen */ + uint32_t crtc_w, crtc_h; + uint32_t src_x, src_y; + uint32_t src_w, src_h; +}; + +/* Once GO bit is set, we can't make further updates to shadowed registers + * until the GO bit is cleared. So various parts in the kms code that need + * to update shadowed registers queue up a pair of callbacks, pre_apply + * which is called before setting GO bit, and post_apply that is called + * after GO bit is cleared. The crtc manages the queuing, and everyone + * else goes thru omap_crtc_apply() using these callbacks so that the + * code which has to deal w/ GO bit state is centralized. + */ +struct omap_drm_apply { + struct list_head pending_node, queued_node; + bool queued; + void (*pre_apply)(struct omap_drm_apply *apply); + void (*post_apply)(struct omap_drm_apply *apply); +}; + +/* For transiently registering for different DSS irqs that various parts + * of the KMS code need during setup/configuration. We these are not + * necessarily the same as what drm_vblank_get/put() are requesting, and + * the hysteresis in drm_vblank_put() is not necessarily desirable for + * internal housekeeping related irq usage. + */ +struct omap_drm_irq { + struct list_head node; + uint32_t irqmask; + bool registered; + void (*irq)(struct omap_drm_irq *irq, uint32_t irqstatus); +}; + +/* For KMS code that needs to wait for a certain # of IRQs: + */ +struct omap_irq_wait; +struct omap_irq_wait * omap_irq_wait_init(struct drm_device *dev, + uint32_t irqmask, int count); +int omap_irq_wait(struct drm_device *dev, struct omap_irq_wait *wait, + unsigned long timeout); + +struct omap_drm_private { + uint32_t omaprev; + + unsigned int num_crtcs; + struct drm_crtc *crtcs[8]; + + unsigned int num_planes; + struct drm_plane *planes[8]; + + unsigned int num_encoders; + struct drm_encoder *encoders[8]; + + unsigned int num_connectors; + struct drm_connector *connectors[8]; + + struct drm_fb_helper *fbdev; + + struct workqueue_struct *wq; + + /* list of GEM objects: */ + struct list_head obj_list; + + bool has_dmm; + + /* properties: */ + struct drm_property *rotation_prop; + struct drm_property *zorder_prop; + + /* irq handling: */ + struct list_head irq_list; /* list of omap_drm_irq */ + uint32_t vblank_mask; /* irq bits set for userspace vblank */ + struct omap_drm_irq error_handler; +}; + +/* this should probably be in drm-core to standardize amongst drivers */ +#define DRM_ROTATE_0 0 +#define DRM_ROTATE_90 1 +#define DRM_ROTATE_180 2 +#define DRM_ROTATE_270 3 +#define DRM_REFLECT_X 4 +#define DRM_REFLECT_Y 5 + +#ifdef CONFIG_DEBUG_FS +int omap_debugfs_init(struct drm_minor *minor); +void omap_debugfs_cleanup(struct drm_minor *minor); +void omap_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m); +void omap_gem_describe(struct drm_gem_object *obj, struct seq_file *m); +void omap_gem_describe_objects(struct list_head *list, struct seq_file *m); +#endif + +#ifdef CONFIG_PM +int omap_gem_resume(struct device *dev); +#endif + +int omap_irq_enable_vblank(struct drm_device *dev, int crtc); +void omap_irq_disable_vblank(struct drm_device *dev, int crtc); +irqreturn_t omap_irq_handler(DRM_IRQ_ARGS); +void omap_irq_preinstall(struct drm_device *dev); +int omap_irq_postinstall(struct drm_device *dev); +void omap_irq_uninstall(struct drm_device *dev); +void omap_irq_register(struct drm_device *dev, struct omap_drm_irq *irq); +void omap_irq_unregister(struct drm_device *dev, struct omap_drm_irq *irq); +int omap_drm_irq_uninstall(struct drm_device *dev); +int omap_drm_irq_install(struct drm_device *dev); + +struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev); +void omap_fbdev_free(struct drm_device *dev); + +const struct omap_video_timings *omap_crtc_timings(struct drm_crtc *crtc); +enum omap_channel omap_crtc_channel(struct drm_crtc *crtc); +int omap_crtc_apply(struct drm_crtc *crtc, + struct omap_drm_apply *apply); +struct drm_crtc *omap_crtc_init(struct drm_device *dev, + struct drm_plane *plane, enum omap_channel channel, int id); + +struct drm_plane *omap_plane_init(struct drm_device *dev, + int plane_id, bool private_plane); +int omap_plane_dpms(struct drm_plane *plane, int mode); +int omap_plane_mode_set(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h, + void (*fxn)(void *), void *arg); +void omap_plane_install_properties(struct drm_plane *plane, + struct drm_mode_object *obj); +int omap_plane_set_property(struct drm_plane *plane, + struct drm_property *property, uint64_t val); + +struct drm_encoder *omap_encoder_init(struct drm_device *dev, + struct omap_dss_device *dssdev); +int omap_encoder_set_enabled(struct drm_encoder *encoder, bool enabled); +int omap_encoder_update(struct drm_encoder *encoder, + struct omap_overlay_manager *mgr, + struct omap_video_timings *timings); + +struct drm_connector *omap_connector_init(struct drm_device *dev, + int connector_type, struct omap_dss_device *dssdev, + struct drm_encoder *encoder); +struct drm_encoder *omap_connector_attached_encoder( + struct drm_connector *connector); +void omap_connector_flush(struct drm_connector *connector, + int x, int y, int w, int h); + +void copy_timings_omap_to_drm(struct drm_display_mode *mode, + struct omap_video_timings *timings); +void copy_timings_drm_to_omap(struct omap_video_timings *timings, + struct drm_display_mode *mode); + +uint32_t omap_framebuffer_get_formats(uint32_t *pixel_formats, + uint32_t max_formats, enum omap_color_mode supported_modes); +struct drm_framebuffer *omap_framebuffer_create(struct drm_device *dev, + struct drm_file *file, struct drm_mode_fb_cmd2 *mode_cmd); +struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos); +struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb, int p); +int omap_framebuffer_replace(struct drm_framebuffer *a, + struct drm_framebuffer *b, void *arg, + void (*unpin)(void *arg, struct drm_gem_object *bo)); +void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, + struct omap_drm_window *win, struct omap_overlay_info *info); +struct drm_connector *omap_framebuffer_get_next_connector( + struct drm_framebuffer *fb, struct drm_connector *from); +void omap_framebuffer_flush(struct drm_framebuffer *fb, + int x, int y, int w, int h); + +void omap_gem_init(struct drm_device *dev); +void omap_gem_deinit(struct drm_device *dev); + +struct drm_gem_object *omap_gem_new(struct drm_device *dev, + union omap_gem_size gsize, uint32_t flags); +int omap_gem_new_handle(struct drm_device *dev, struct drm_file *file, + union omap_gem_size gsize, uint32_t flags, uint32_t *handle); +void omap_gem_free_object(struct drm_gem_object *obj); +int omap_gem_init_object(struct drm_gem_object *obj); +void *omap_gem_vaddr(struct drm_gem_object *obj); +int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, + uint32_t handle, uint64_t *offset); +int omap_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, + uint32_t handle); +int omap_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args); +int omap_gem_mmap(struct file *filp, struct vm_area_struct *vma); +int omap_gem_mmap_obj(struct drm_gem_object *obj, + struct vm_area_struct *vma); +int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); +int omap_gem_op_start(struct drm_gem_object *obj, enum omap_gem_op op); +int omap_gem_op_finish(struct drm_gem_object *obj, enum omap_gem_op op); +int omap_gem_op_sync(struct drm_gem_object *obj, enum omap_gem_op op); +int omap_gem_op_async(struct drm_gem_object *obj, enum omap_gem_op op, + void (*fxn)(void *arg), void *arg); +int omap_gem_roll(struct drm_gem_object *obj, uint32_t roll); +void omap_gem_cpu_sync(struct drm_gem_object *obj, int pgoff); +void omap_gem_dma_sync(struct drm_gem_object *obj, + enum dma_data_direction dir); +int omap_gem_get_paddr(struct drm_gem_object *obj, + dma_addr_t *paddr, bool remap); +int omap_gem_put_paddr(struct drm_gem_object *obj); +int omap_gem_get_pages(struct drm_gem_object *obj, struct page ***pages, + bool remap); +int omap_gem_put_pages(struct drm_gem_object *obj); +uint32_t omap_gem_flags(struct drm_gem_object *obj); +int omap_gem_rotated_paddr(struct drm_gem_object *obj, uint32_t orient, + int x, int y, dma_addr_t *paddr); +uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj); +size_t omap_gem_mmap_size(struct drm_gem_object *obj); +int omap_gem_tiled_size(struct drm_gem_object *obj, uint16_t *w, uint16_t *h); +int omap_gem_tiled_stride(struct drm_gem_object *obj, uint32_t orient); + +struct dma_buf *omap_gem_prime_export(struct drm_device *dev, + struct drm_gem_object *obj, int flags); +struct drm_gem_object *omap_gem_prime_import(struct drm_device *dev, + struct dma_buf *buffer); + +static inline int align_pitch(int pitch, int width, int bpp) +{ + int bytespp = (bpp + 7) / 8; + /* in case someone tries to feed us a completely bogus stride: */ + pitch = max(pitch, width * bytespp); + /* PVR needs alignment to 8 pixels.. right now that is the most + * restrictive stride requirement.. + */ + return ALIGN(pitch, 8 * bytespp); +} + +static inline enum omap_channel pipe2chan(int pipe) +{ + int num_mgrs = dss_feat_get_num_mgrs(); + + /* + * We usually don't want to create a CRTC for each manager, + * at least not until we have a way to expose private planes + * to userspace. Otherwise there would not be enough video + * pipes left for drm planes. The higher #'d managers tend + * to have more features so start in reverse order. + */ + return num_mgrs - pipe - 1; +} + +/* map crtc to vblank mask */ +static inline uint32_t pipe2vbl(int crtc) +{ + enum omap_channel channel = pipe2chan(crtc); + return dispc_mgr_get_vsync_irq(channel); +} + +static inline int crtc2pipe(struct drm_device *dev, struct drm_crtc *crtc) +{ + struct omap_drm_private *priv = dev->dev_private; + int i; + + for (i = 0; i < ARRAY_SIZE(priv->crtcs); i++) + if (priv->crtcs[i] == crtc) + return i; + + BUG(); /* bogus CRTC ptr */ + return -1; +} + +/* should these be made into common util helpers? + */ + +static inline int objects_lookup(struct drm_device *dev, + struct drm_file *filp, uint32_t pixel_format, + struct drm_gem_object **bos, uint32_t *handles) +{ + int i, n = drm_format_num_planes(pixel_format); + + for (i = 0; i < n; i++) { + bos[i] = drm_gem_object_lookup(dev, filp, handles[i]); + if (!bos[i]) + goto fail; + + } + + return 0; + +fail: + while (--i > 0) + drm_gem_object_unreference_unlocked(bos[i]); + + return -ENOENT; +} + +#endif /* __OMAP_DRV_H__ */ diff --git a/drivers/gpu/drm/omapdrm/omap_encoder.c b/drivers/gpu/drm/omapdrm/omap_encoder.c new file mode 100644 index 000000000000..7e1f2ab65372 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_encoder.c @@ -0,0 +1,170 @@ +/* + * drivers/gpu/drm/omapdrm/omap_encoder.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +#include <linux/list.h> + + +/* + * encoder funcs + */ + +#define to_omap_encoder(x) container_of(x, struct omap_encoder, base) + +/* The encoder and connector both map to same dssdev.. the encoder + * handles the 'active' parts, ie. anything the modifies the state + * of the hw, and the connector handles the 'read-only' parts, like + * detecting connection and reading edid. + */ +struct omap_encoder { + struct drm_encoder base; + struct omap_dss_device *dssdev; +}; + +static void omap_encoder_destroy(struct drm_encoder *encoder) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + drm_encoder_cleanup(encoder); + kfree(omap_encoder); +} + +static const struct drm_encoder_funcs omap_encoder_funcs = { + .destroy = omap_encoder_destroy, +}; + +/* + * The CRTC drm_crtc_helper_set_mode() doesn't really give us the right + * order.. the easiest way to work around this for now is to make all + * the encoder-helper's no-op's and have the omap_crtc code take care + * of the sequencing and call us in the right points. + * + * Eventually to handle connecting CRTCs to different encoders properly, + * either the CRTC helpers need to change or we need to replace + * drm_crtc_helper_set_mode(), but lets wait until atomic-modeset for + * that. + */ + +static void omap_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool omap_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void omap_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ +} + +static void omap_encoder_prepare(struct drm_encoder *encoder) +{ +} + +static void omap_encoder_commit(struct drm_encoder *encoder) +{ +} + +static const struct drm_encoder_helper_funcs omap_encoder_helper_funcs = { + .dpms = omap_encoder_dpms, + .mode_fixup = omap_encoder_mode_fixup, + .mode_set = omap_encoder_mode_set, + .prepare = omap_encoder_prepare, + .commit = omap_encoder_commit, +}; + +/* + * Instead of relying on the helpers for modeset, the omap_crtc code + * calls these functions in the proper sequence. + */ + +int omap_encoder_set_enabled(struct drm_encoder *encoder, bool enabled) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + struct omap_dss_device *dssdev = omap_encoder->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + + if (enabled) { + return dssdrv->enable(dssdev); + } else { + dssdrv->disable(dssdev); + return 0; + } +} + +int omap_encoder_update(struct drm_encoder *encoder, + struct omap_overlay_manager *mgr, + struct omap_video_timings *timings) +{ + struct drm_device *dev = encoder->dev; + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + struct omap_dss_device *dssdev = omap_encoder->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + int ret; + + dssdev->output->manager = mgr; + + ret = dssdrv->check_timings(dssdev, timings); + if (ret) { + dev_err(dev->dev, "could not set timings: %d\n", ret); + return ret; + } + + dssdrv->set_timings(dssdev, timings); + + return 0; +} + +/* initialize encoder */ +struct drm_encoder *omap_encoder_init(struct drm_device *dev, + struct omap_dss_device *dssdev) +{ + struct drm_encoder *encoder = NULL; + struct omap_encoder *omap_encoder; + + omap_encoder = kzalloc(sizeof(*omap_encoder), GFP_KERNEL); + if (!omap_encoder) { + dev_err(dev->dev, "could not allocate encoder\n"); + goto fail; + } + + omap_encoder->dssdev = dssdev; + + encoder = &omap_encoder->base; + + drm_encoder_init(dev, encoder, &omap_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(encoder, &omap_encoder_helper_funcs); + + return encoder; + +fail: + if (encoder) + omap_encoder_destroy(encoder); + + return NULL; +} diff --git a/drivers/gpu/drm/omapdrm/omap_fb.c b/drivers/gpu/drm/omapdrm/omap_fb.c new file mode 100644 index 000000000000..9d5f6f696c72 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_fb.c @@ -0,0 +1,472 @@ +/* + * drivers/gpu/drm/omapdrm/omap_fb.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" +#include "omap_dmm_tiler.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +/* + * framebuffer funcs + */ + +/* per-format info: */ +struct format { + enum omap_color_mode dss_format; + uint32_t pixel_format; + struct { + int stride_bpp; /* this times width is stride */ + int sub_y; /* sub-sample in y dimension */ + } planes[4]; + bool yuv; +}; + +static const struct format formats[] = { + /* 16bpp [A]RGB: */ + { OMAP_DSS_COLOR_RGB16, DRM_FORMAT_RGB565, {{2, 1}}, false }, /* RGB16-565 */ + { OMAP_DSS_COLOR_RGB12U, DRM_FORMAT_RGBX4444, {{2, 1}}, false }, /* RGB12x-4444 */ + { OMAP_DSS_COLOR_RGBX16, DRM_FORMAT_XRGB4444, {{2, 1}}, false }, /* xRGB12-4444 */ + { OMAP_DSS_COLOR_RGBA16, DRM_FORMAT_RGBA4444, {{2, 1}}, false }, /* RGBA12-4444 */ + { OMAP_DSS_COLOR_ARGB16, DRM_FORMAT_ARGB4444, {{2, 1}}, false }, /* ARGB16-4444 */ + { OMAP_DSS_COLOR_XRGB16_1555, DRM_FORMAT_XRGB1555, {{2, 1}}, false }, /* xRGB15-1555 */ + { OMAP_DSS_COLOR_ARGB16_1555, DRM_FORMAT_ARGB1555, {{2, 1}}, false }, /* ARGB16-1555 */ + /* 24bpp RGB: */ + { OMAP_DSS_COLOR_RGB24P, DRM_FORMAT_RGB888, {{3, 1}}, false }, /* RGB24-888 */ + /* 32bpp [A]RGB: */ + { OMAP_DSS_COLOR_RGBX32, DRM_FORMAT_RGBX8888, {{4, 1}}, false }, /* RGBx24-8888 */ + { OMAP_DSS_COLOR_RGB24U, DRM_FORMAT_XRGB8888, {{4, 1}}, false }, /* xRGB24-8888 */ + { OMAP_DSS_COLOR_RGBA32, DRM_FORMAT_RGBA8888, {{4, 1}}, false }, /* RGBA32-8888 */ + { OMAP_DSS_COLOR_ARGB32, DRM_FORMAT_ARGB8888, {{4, 1}}, false }, /* ARGB32-8888 */ + /* YUV: */ + { OMAP_DSS_COLOR_NV12, DRM_FORMAT_NV12, {{1, 1}, {1, 2}}, true }, + { OMAP_DSS_COLOR_YUV2, DRM_FORMAT_YUYV, {{2, 1}}, true }, + { OMAP_DSS_COLOR_UYVY, DRM_FORMAT_UYVY, {{2, 1}}, true }, +}; + +/* convert from overlay's pixel formats bitmask to an array of fourcc's */ +uint32_t omap_framebuffer_get_formats(uint32_t *pixel_formats, + uint32_t max_formats, enum omap_color_mode supported_modes) +{ + uint32_t nformats = 0; + int i = 0; + + for (i = 0; i < ARRAY_SIZE(formats) && nformats < max_formats; i++) + if (formats[i].dss_format & supported_modes) + pixel_formats[nformats++] = formats[i].pixel_format; + + return nformats; +} + +/* per-plane info for the fb: */ +struct plane { + struct drm_gem_object *bo; + uint32_t pitch; + uint32_t offset; + dma_addr_t paddr; +}; + +#define to_omap_framebuffer(x) container_of(x, struct omap_framebuffer, base) + +struct omap_framebuffer { + struct drm_framebuffer base; + const struct format *format; + struct plane planes[4]; +}; + +static int omap_framebuffer_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + return drm_gem_handle_create(file_priv, + omap_fb->planes[0].bo, handle); +} + +static void omap_framebuffer_destroy(struct drm_framebuffer *fb) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + int i, n = drm_format_num_planes(fb->pixel_format); + + DBG("destroy: FB ID: %d (%p)", fb->base.id, fb); + + drm_framebuffer_cleanup(fb); + + for (i = 0; i < n; i++) { + struct plane *plane = &omap_fb->planes[i]; + if (plane->bo) + drm_gem_object_unreference_unlocked(plane->bo); + } + + kfree(omap_fb); +} + +static int omap_framebuffer_dirty(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned flags, unsigned color, + struct drm_clip_rect *clips, unsigned num_clips) +{ + int i; + + for (i = 0; i < num_clips; i++) { + omap_framebuffer_flush(fb, clips[i].x1, clips[i].y1, + clips[i].x2 - clips[i].x1, + clips[i].y2 - clips[i].y1); + } + + return 0; +} + +static const struct drm_framebuffer_funcs omap_framebuffer_funcs = { + .create_handle = omap_framebuffer_create_handle, + .destroy = omap_framebuffer_destroy, + .dirty = omap_framebuffer_dirty, +}; + +static uint32_t get_linear_addr(struct plane *plane, + const struct format *format, int n, int x, int y) +{ + uint32_t offset; + + offset = plane->offset + + (x * format->planes[n].stride_bpp) + + (y * plane->pitch / format->planes[n].sub_y); + + return plane->paddr + offset; +} + +/* update ovl info for scanout, handles cases of multi-planar fb's, etc. + */ +void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, + struct omap_drm_window *win, struct omap_overlay_info *info) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + const struct format *format = omap_fb->format; + struct plane *plane = &omap_fb->planes[0]; + uint32_t x, y, orient = 0; + + info->color_mode = format->dss_format; + + info->pos_x = win->crtc_x; + info->pos_y = win->crtc_y; + info->out_width = win->crtc_w; + info->out_height = win->crtc_h; + info->width = win->src_w; + info->height = win->src_h; + + x = win->src_x; + y = win->src_y; + + if (omap_gem_flags(plane->bo) & OMAP_BO_TILED) { + uint32_t w = win->src_w; + uint32_t h = win->src_h; + + switch (win->rotation & 0xf) { + default: + dev_err(fb->dev->dev, "invalid rotation: %02x", + (uint32_t)win->rotation); + /* fallthru to default to no rotation */ + case 0: + case BIT(DRM_ROTATE_0): + orient = 0; + break; + case BIT(DRM_ROTATE_90): + orient = MASK_XY_FLIP | MASK_X_INVERT; + break; + case BIT(DRM_ROTATE_180): + orient = MASK_X_INVERT | MASK_Y_INVERT; + break; + case BIT(DRM_ROTATE_270): + orient = MASK_XY_FLIP | MASK_Y_INVERT; + break; + } + + if (win->rotation & BIT(DRM_REFLECT_X)) + orient ^= MASK_X_INVERT; + + if (win->rotation & BIT(DRM_REFLECT_Y)) + orient ^= MASK_Y_INVERT; + + /* adjust x,y offset for flip/invert: */ + if (orient & MASK_XY_FLIP) + swap(w, h); + if (orient & MASK_Y_INVERT) + y += h - 1; + if (orient & MASK_X_INVERT) + x += w - 1; + + omap_gem_rotated_paddr(plane->bo, orient, x, y, &info->paddr); + info->rotation_type = OMAP_DSS_ROT_TILER; + info->screen_width = omap_gem_tiled_stride(plane->bo, orient); + } else { + info->paddr = get_linear_addr(plane, format, 0, x, y); + info->rotation_type = OMAP_DSS_ROT_DMA; + info->screen_width = plane->pitch; + } + + /* convert to pixels: */ + info->screen_width /= format->planes[0].stride_bpp; + + if (format->dss_format == OMAP_DSS_COLOR_NV12) { + plane = &omap_fb->planes[1]; + + if (info->rotation_type == OMAP_DSS_ROT_TILER) { + WARN_ON(!(omap_gem_flags(plane->bo) & OMAP_BO_TILED)); + omap_gem_rotated_paddr(plane->bo, orient, + x/2, y/2, &info->p_uv_addr); + } else { + info->p_uv_addr = get_linear_addr(plane, format, 1, x, y); + } + } else { + info->p_uv_addr = 0; + } +} + +/* Call for unpin 'a' (if not NULL), and pin 'b' (if not NULL). Although + * buffers to unpin are just pushed to the unpin fifo so that the + * caller can defer unpin until vblank. + * + * Note if this fails (ie. something went very wrong!), all buffers are + * unpinned, and the caller disables the overlay. We could have tried + * to revert back to the previous set of pinned buffers but if things are + * hosed there is no guarantee that would succeed. + */ +int omap_framebuffer_replace(struct drm_framebuffer *a, + struct drm_framebuffer *b, void *arg, + void (*unpin)(void *arg, struct drm_gem_object *bo)) +{ + int ret = 0, i, na, nb; + struct omap_framebuffer *ofba = to_omap_framebuffer(a); + struct omap_framebuffer *ofbb = to_omap_framebuffer(b); + uint32_t pinned_mask = 0; + + na = a ? drm_format_num_planes(a->pixel_format) : 0; + nb = b ? drm_format_num_planes(b->pixel_format) : 0; + + for (i = 0; i < max(na, nb); i++) { + struct plane *pa, *pb; + + pa = (i < na) ? &ofba->planes[i] : NULL; + pb = (i < nb) ? &ofbb->planes[i] : NULL; + + if (pa) + unpin(arg, pa->bo); + + if (pb && !ret) { + ret = omap_gem_get_paddr(pb->bo, &pb->paddr, true); + if (!ret) { + omap_gem_dma_sync(pb->bo, DMA_TO_DEVICE); + pinned_mask |= (1 << i); + } + } + } + + if (ret) { + /* something went wrong.. unpin what has been pinned */ + for (i = 0; i < nb; i++) { + if (pinned_mask & (1 << i)) { + struct plane *pb = &ofba->planes[i]; + unpin(arg, pb->bo); + } + } + } + + return ret; +} + +struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb, int p) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + if (p >= drm_format_num_planes(fb->pixel_format)) + return NULL; + return omap_fb->planes[p].bo; +} + +/* iterate thru all the connectors, returning ones that are attached + * to the same fb.. + */ +struct drm_connector *omap_framebuffer_get_next_connector( + struct drm_framebuffer *fb, struct drm_connector *from) +{ + struct drm_device *dev = fb->dev; + struct list_head *connector_list = &dev->mode_config.connector_list; + struct drm_connector *connector = from; + + if (!from) + return list_first_entry(connector_list, typeof(*from), head); + + list_for_each_entry_from(connector, connector_list, head) { + if (connector != from) { + struct drm_encoder *encoder = connector->encoder; + struct drm_crtc *crtc = encoder ? encoder->crtc : NULL; + if (crtc && crtc->fb == fb) + return connector; + + } + } + + return NULL; +} + +/* flush an area of the framebuffer (in case of manual update display that + * is not automatically flushed) + */ +void omap_framebuffer_flush(struct drm_framebuffer *fb, + int x, int y, int w, int h) +{ + struct drm_connector *connector = NULL; + + VERB("flush: %d,%d %dx%d, fb=%p", x, y, w, h, fb); + + while ((connector = omap_framebuffer_get_next_connector(fb, connector))) { + /* only consider connectors that are part of a chain */ + if (connector->encoder && connector->encoder->crtc) { + /* TODO: maybe this should propagate thru the crtc who + * could do the coordinate translation.. + */ + struct drm_crtc *crtc = connector->encoder->crtc; + int cx = max(0, x - crtc->x); + int cy = max(0, y - crtc->y); + int cw = w + (x - crtc->x) - cx; + int ch = h + (y - crtc->y) - cy; + + omap_connector_flush(connector, cx, cy, cw, ch); + } + } +} + +#ifdef CONFIG_DEBUG_FS +void omap_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + int i, n = drm_format_num_planes(fb->pixel_format); + + seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height, + (char *)&fb->pixel_format); + + for (i = 0; i < n; i++) { + struct plane *plane = &omap_fb->planes[i]; + seq_printf(m, " %d: offset=%d pitch=%d, obj: ", + i, plane->offset, plane->pitch); + omap_gem_describe(plane->bo, m); + } +} +#endif + +struct drm_framebuffer *omap_framebuffer_create(struct drm_device *dev, + struct drm_file *file, struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_gem_object *bos[4]; + struct drm_framebuffer *fb; + int ret; + + ret = objects_lookup(dev, file, mode_cmd->pixel_format, + bos, mode_cmd->handles); + if (ret) + return ERR_PTR(ret); + + fb = omap_framebuffer_init(dev, mode_cmd, bos); + if (IS_ERR(fb)) { + int i, n = drm_format_num_planes(mode_cmd->pixel_format); + for (i = 0; i < n; i++) + drm_gem_object_unreference_unlocked(bos[i]); + return fb; + } + return fb; +} + +struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos) +{ + struct omap_framebuffer *omap_fb; + struct drm_framebuffer *fb = NULL; + const struct format *format = NULL; + int ret, i, n = drm_format_num_planes(mode_cmd->pixel_format); + + DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d@%4.4s)", + dev, mode_cmd, mode_cmd->width, mode_cmd->height, + (char *)&mode_cmd->pixel_format); + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].pixel_format == mode_cmd->pixel_format) { + format = &formats[i]; + break; + } + } + + if (!format) { + dev_err(dev->dev, "unsupported pixel format: %4.4s\n", + (char *)&mode_cmd->pixel_format); + ret = -EINVAL; + goto fail; + } + + omap_fb = kzalloc(sizeof(*omap_fb), GFP_KERNEL); + if (!omap_fb) { + dev_err(dev->dev, "could not allocate fb\n"); + ret = -ENOMEM; + goto fail; + } + + fb = &omap_fb->base; + omap_fb->format = format; + + for (i = 0; i < n; i++) { + struct plane *plane = &omap_fb->planes[i]; + int size, pitch = mode_cmd->pitches[i]; + + if (pitch < (mode_cmd->width * format->planes[i].stride_bpp)) { + dev_err(dev->dev, "provided buffer pitch is too small! %d < %d\n", + pitch, mode_cmd->width * format->planes[i].stride_bpp); + ret = -EINVAL; + goto fail; + } + + size = pitch * mode_cmd->height / format->planes[i].sub_y; + + if (size > (omap_gem_mmap_size(bos[i]) - mode_cmd->offsets[i])) { + dev_err(dev->dev, "provided buffer object is too small! %d < %d\n", + bos[i]->size - mode_cmd->offsets[i], size); + ret = -EINVAL; + goto fail; + } + + plane->bo = bos[i]; + plane->offset = mode_cmd->offsets[i]; + plane->pitch = pitch; + plane->paddr = 0; + } + + drm_helper_mode_fill_fb_struct(fb, mode_cmd); + + ret = drm_framebuffer_init(dev, fb, &omap_framebuffer_funcs); + if (ret) { + dev_err(dev->dev, "framebuffer init failed: %d\n", ret); + goto fail; + } + + DBG("create: FB ID: %d (%p)", fb->base.id, fb); + + return fb; + +fail: + if (fb) + omap_framebuffer_destroy(fb); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/omapdrm/omap_fbdev.c b/drivers/gpu/drm/omapdrm/omap_fbdev.c new file mode 100644 index 000000000000..f0033bd3e4ae --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_fbdev.c @@ -0,0 +1,399 @@ +/* + * drivers/gpu/drm/omapdrm/omap_fbdev.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_fb_helper.h" + +MODULE_PARM_DESC(ywrap, "Enable ywrap scrolling (omap44xx and later, default 'y')"); +static bool ywrap_enabled = true; +module_param_named(ywrap, ywrap_enabled, bool, 0644); + +/* + * fbdev funcs, to implement legacy fbdev interface on top of drm driver + */ + +#define to_omap_fbdev(x) container_of(x, struct omap_fbdev, base) + +struct omap_fbdev { + struct drm_fb_helper base; + struct drm_framebuffer *fb; + struct drm_gem_object *bo; + bool ywrap_enabled; + + /* for deferred dmm roll when getting called in atomic ctx */ + struct work_struct work; +}; + +static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h); +static struct drm_fb_helper *get_fb(struct fb_info *fbi); + +static ssize_t omap_fbdev_write(struct fb_info *fbi, const char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t res; + + res = fb_sys_write(fbi, buf, count, ppos); + omap_fbdev_flush(fbi, 0, 0, fbi->var.xres, fbi->var.yres); + + return res; +} + +static void omap_fbdev_fillrect(struct fb_info *fbi, + const struct fb_fillrect *rect) +{ + sys_fillrect(fbi, rect); + omap_fbdev_flush(fbi, rect->dx, rect->dy, rect->width, rect->height); +} + +static void omap_fbdev_copyarea(struct fb_info *fbi, + const struct fb_copyarea *area) +{ + sys_copyarea(fbi, area); + omap_fbdev_flush(fbi, area->dx, area->dy, area->width, area->height); +} + +static void omap_fbdev_imageblit(struct fb_info *fbi, + const struct fb_image *image) +{ + sys_imageblit(fbi, image); + omap_fbdev_flush(fbi, image->dx, image->dy, + image->width, image->height); +} + +static void pan_worker(struct work_struct *work) +{ + struct omap_fbdev *fbdev = container_of(work, struct omap_fbdev, work); + struct fb_info *fbi = fbdev->base.fbdev; + int npages; + + /* DMM roll shifts in 4K pages: */ + npages = fbi->fix.line_length >> PAGE_SHIFT; + omap_gem_roll(fbdev->bo, fbi->var.yoffset * npages); +} + +static int omap_fbdev_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + struct drm_fb_helper *helper = get_fb(fbi); + struct omap_fbdev *fbdev = to_omap_fbdev(helper); + + if (!helper) + goto fallback; + + if (!fbdev->ywrap_enabled) + goto fallback; + + if (drm_can_sleep()) { + pan_worker(&fbdev->work); + } else { + struct omap_drm_private *priv = helper->dev->dev_private; + queue_work(priv->wq, &fbdev->work); + } + + return 0; + +fallback: + return drm_fb_helper_pan_display(var, fbi); +} + +static struct fb_ops omap_fb_ops = { + .owner = THIS_MODULE, + + /* Note: to properly handle manual update displays, we wrap the + * basic fbdev ops which write to the framebuffer + */ + .fb_read = fb_sys_read, + .fb_write = omap_fbdev_write, + .fb_fillrect = omap_fbdev_fillrect, + .fb_copyarea = omap_fbdev_copyarea, + .fb_imageblit = omap_fbdev_imageblit, + + .fb_check_var = drm_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_pan_display = omap_fbdev_pan_display, + .fb_blank = drm_fb_helper_blank, + .fb_setcmap = drm_fb_helper_setcmap, +}; + +static int omap_fbdev_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct omap_fbdev *fbdev = to_omap_fbdev(helper); + struct drm_device *dev = helper->dev; + struct omap_drm_private *priv = dev->dev_private; + struct drm_framebuffer *fb = NULL; + union omap_gem_size gsize; + struct fb_info *fbi = NULL; + struct drm_mode_fb_cmd2 mode_cmd = {0}; + dma_addr_t paddr; + int ret; + + /* only doing ARGB32 since this is what is needed to alpha-blend + * with video overlays: + */ + sizes->surface_bpp = 32; + sizes->surface_depth = 32; + + DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, + sizes->surface_height, sizes->surface_bpp, + sizes->fb_width, sizes->fb_height); + + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + + mode_cmd.pitches[0] = align_pitch( + mode_cmd.width * ((sizes->surface_bpp + 7) / 8), + mode_cmd.width, sizes->surface_bpp); + + fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; + if (fbdev->ywrap_enabled) { + /* need to align pitch to page size if using DMM scrolling */ + mode_cmd.pitches[0] = ALIGN(mode_cmd.pitches[0], PAGE_SIZE); + } + + /* allocate backing bo */ + gsize = (union omap_gem_size){ + .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), + }; + DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index); + fbdev->bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); + if (!fbdev->bo) { + dev_err(dev->dev, "failed to allocate buffer object\n"); + ret = -ENOMEM; + goto fail; + } + + fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo); + if (IS_ERR(fb)) { + dev_err(dev->dev, "failed to allocate fb\n"); + /* note: if fb creation failed, we can't rely on fb destroy + * to unref the bo: + */ + drm_gem_object_unreference(fbdev->bo); + ret = PTR_ERR(fb); + goto fail; + } + + /* note: this keeps the bo pinned.. which is perhaps not ideal, + * but is needed as long as we use fb_mmap() to mmap to userspace + * (since this happens using fix.smem_start). Possibly we could + * implement our own mmap using GEM mmap support to avoid this + * (non-tiled buffer doesn't need to be pinned for fbcon to write + * to it). Then we just need to be sure that we are able to re- + * pin it in case of an opps. + */ + ret = omap_gem_get_paddr(fbdev->bo, &paddr, true); + if (ret) { + dev_err(dev->dev, + "could not map (paddr)! Skipping framebuffer alloc\n"); + ret = -ENOMEM; + goto fail; + } + + mutex_lock(&dev->struct_mutex); + + fbi = framebuffer_alloc(0, dev->dev); + if (!fbi) { + dev_err(dev->dev, "failed to allocate fb info\n"); + ret = -ENOMEM; + goto fail_unlock; + } + + DBG("fbi=%p, dev=%p", fbi, dev); + + fbdev->fb = fb; + helper->fb = fb; + helper->fbdev = fbi; + + fbi->par = helper; + fbi->flags = FBINFO_DEFAULT; + fbi->fbops = &omap_fb_ops; + + strcpy(fbi->fix.id, MODULE_NAME); + + ret = fb_alloc_cmap(&fbi->cmap, 256, 0); + if (ret) { + ret = -ENOMEM; + goto fail_unlock; + } + + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); + drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); + + dev->mode_config.fb_base = paddr; + + fbi->screen_base = omap_gem_vaddr(fbdev->bo); + fbi->screen_size = fbdev->bo->size; + fbi->fix.smem_start = paddr; + fbi->fix.smem_len = fbdev->bo->size; + + /* if we have DMM, then we can use it for scrolling by just + * shuffling pages around in DMM rather than doing sw blit. + */ + if (fbdev->ywrap_enabled) { + DRM_INFO("Enabling DMM ywrap scrolling\n"); + fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; + fbi->fix.ywrapstep = 1; + } + + + DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); + DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); + + mutex_unlock(&dev->struct_mutex); + + return 0; + +fail_unlock: + mutex_unlock(&dev->struct_mutex); +fail: + + if (ret) { + if (fbi) + framebuffer_release(fbi); + if (fb) { + drm_framebuffer_unregister_private(fb); + drm_framebuffer_remove(fb); + } + } + + return ret; +} + +static void omap_crtc_fb_gamma_set(struct drm_crtc *crtc, + u16 red, u16 green, u16 blue, int regno) +{ + DBG("fbdev: set gamma"); +} + +static void omap_crtc_fb_gamma_get(struct drm_crtc *crtc, + u16 *red, u16 *green, u16 *blue, int regno) +{ + DBG("fbdev: get gamma"); +} + +static struct drm_fb_helper_funcs omap_fb_helper_funcs = { + .gamma_set = omap_crtc_fb_gamma_set, + .gamma_get = omap_crtc_fb_gamma_get, + .fb_probe = omap_fbdev_create, +}; + +static struct drm_fb_helper *get_fb(struct fb_info *fbi) +{ + if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { + /* these are not the fb's you're looking for */ + return NULL; + } + return fbi->par; +} + +/* flush an area of the framebuffer (in case of manual update display that + * is not automatically flushed) + */ +static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h) +{ + struct drm_fb_helper *helper = get_fb(fbi); + + if (!helper) + return; + + VERB("flush fbdev: %d,%d %dx%d, fbi=%p", x, y, w, h, fbi); + + omap_framebuffer_flush(helper->fb, x, y, w, h); +} + +/* initialize fbdev helper */ +struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_fbdev *fbdev = NULL; + struct drm_fb_helper *helper; + int ret = 0; + + fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) { + dev_err(dev->dev, "could not allocate fbdev\n"); + goto fail; + } + + INIT_WORK(&fbdev->work, pan_worker); + + helper = &fbdev->base; + + helper->funcs = &omap_fb_helper_funcs; + + ret = drm_fb_helper_init(dev, helper, + priv->num_crtcs, priv->num_connectors); + if (ret) { + dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); + goto fail; + } + + drm_fb_helper_single_add_all_connectors(helper); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + + drm_fb_helper_initial_config(helper, 32); + + priv->fbdev = helper; + + return helper; + +fail: + kfree(fbdev); + return NULL; +} + +void omap_fbdev_free(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct drm_fb_helper *helper = priv->fbdev; + struct omap_fbdev *fbdev; + struct fb_info *fbi; + + DBG(); + + fbi = helper->fbdev; + + /* only cleanup framebuffer if it is present */ + if (fbi) { + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + drm_fb_helper_fini(helper); + + fbdev = to_omap_fbdev(priv->fbdev); + + /* this will free the backing object */ + if (fbdev->fb) { + drm_framebuffer_unregister_private(fbdev->fb); + drm_framebuffer_remove(fbdev->fb); + } + + kfree(fbdev); + + priv->fbdev = NULL; +} diff --git a/drivers/gpu/drm/omapdrm/omap_gem.c b/drivers/gpu/drm/omapdrm/omap_gem.c new file mode 100644 index 000000000000..e8302b02691d --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_gem.c @@ -0,0 +1,1511 @@ +/* + * drivers/gpu/drm/omapdrm/omap_gem.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include <linux/spinlock.h> +#include <linux/shmem_fs.h> + +#include "omap_drv.h" +#include "omap_dmm_tiler.h" + +/* remove these once drm core helpers are merged */ +struct page **_drm_gem_get_pages(struct drm_gem_object *obj, gfp_t gfpmask); +void _drm_gem_put_pages(struct drm_gem_object *obj, struct page **pages, + bool dirty, bool accessed); +int _drm_gem_create_mmap_offset_size(struct drm_gem_object *obj, size_t size); + +/* + * GEM buffer object implementation. + */ + +#define to_omap_bo(x) container_of(x, struct omap_gem_object, base) + +/* note: we use upper 8 bits of flags for driver-internal flags: */ +#define OMAP_BO_DMA 0x01000000 /* actually is physically contiguous */ +#define OMAP_BO_EXT_SYNC 0x02000000 /* externally allocated sync object */ +#define OMAP_BO_EXT_MEM 0x04000000 /* externally allocated memory */ + + +struct omap_gem_object { + struct drm_gem_object base; + + struct list_head mm_list; + + uint32_t flags; + + /** width/height for tiled formats (rounded up to slot boundaries) */ + uint16_t width, height; + + /** roll applied when mapping to DMM */ + uint32_t roll; + + /** + * If buffer is allocated physically contiguous, the OMAP_BO_DMA flag + * is set and the paddr is valid. Also if the buffer is remapped in + * TILER and paddr_cnt > 0, then paddr is valid. But if you are using + * the physical address and OMAP_BO_DMA is not set, then you should + * be going thru omap_gem_{get,put}_paddr() to ensure the mapping is + * not removed from under your feet. + * + * Note that OMAP_BO_SCANOUT is a hint from userspace that DMA capable + * buffer is requested, but doesn't mean that it is. Use the + * OMAP_BO_DMA flag to determine if the buffer has a DMA capable + * physical address. + */ + dma_addr_t paddr; + + /** + * # of users of paddr + */ + uint32_t paddr_cnt; + + /** + * tiler block used when buffer is remapped in DMM/TILER. + */ + struct tiler_block *block; + + /** + * Array of backing pages, if allocated. Note that pages are never + * allocated for buffers originally allocated from contiguous memory + */ + struct page **pages; + + /** addresses corresponding to pages in above array */ + dma_addr_t *addrs; + + /** + * Virtual address, if mapped. + */ + void *vaddr; + + /** + * sync-object allocated on demand (if needed) + * + * Per-buffer sync-object for tracking pending and completed hw/dma + * read and write operations. The layout in memory is dictated by + * the SGX firmware, which uses this information to stall the command + * stream if a surface is not ready yet. + * + * Note that when buffer is used by SGX, the sync-object needs to be + * allocated from a special heap of sync-objects. This way many sync + * objects can be packed in a page, and not waste GPU virtual address + * space. Because of this we have to have a omap_gem_set_sync_object() + * API to allow replacement of the syncobj after it has (potentially) + * already been allocated. A bit ugly but I haven't thought of a + * better alternative. + */ + struct { + uint32_t write_pending; + uint32_t write_complete; + uint32_t read_pending; + uint32_t read_complete; + } *sync; +}; + +static int get_pages(struct drm_gem_object *obj, struct page ***pages); +static uint64_t mmap_offset(struct drm_gem_object *obj); + +/* To deal with userspace mmap'ings of 2d tiled buffers, which (a) are + * not necessarily pinned in TILER all the time, and (b) when they are + * they are not necessarily page aligned, we reserve one or more small + * regions in each of the 2d containers to use as a user-GART where we + * can create a second page-aligned mapping of parts of the buffer + * being accessed from userspace. + * + * Note that we could optimize slightly when we know that multiple + * tiler containers are backed by the same PAT.. but I'll leave that + * for later.. + */ +#define NUM_USERGART_ENTRIES 2 +struct usergart_entry { + struct tiler_block *block; /* the reserved tiler block */ + dma_addr_t paddr; + struct drm_gem_object *obj; /* the current pinned obj */ + pgoff_t obj_pgoff; /* page offset of obj currently + mapped in */ +}; +static struct { + struct usergart_entry entry[NUM_USERGART_ENTRIES]; + int height; /* height in rows */ + int height_shift; /* ilog2(height in rows) */ + int slot_shift; /* ilog2(width per slot) */ + int stride_pfn; /* stride in pages */ + int last; /* index of last used entry */ +} *usergart; + +static void evict_entry(struct drm_gem_object *obj, + enum tiler_fmt fmt, struct usergart_entry *entry) +{ + if (obj->dev->dev_mapping) { + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int n = usergart[fmt].height; + size_t size = PAGE_SIZE * n; + loff_t off = mmap_offset(obj) + + (entry->obj_pgoff << PAGE_SHIFT); + const int m = 1 + ((omap_obj->width << fmt) / PAGE_SIZE); + if (m > 1) { + int i; + /* if stride > than PAGE_SIZE then sparse mapping: */ + for (i = n; i > 0; i--) { + unmap_mapping_range(obj->dev->dev_mapping, + off, PAGE_SIZE, 1); + off += PAGE_SIZE * m; + } + } else { + unmap_mapping_range(obj->dev->dev_mapping, off, size, 1); + } + } + + entry->obj = NULL; +} + +/* Evict a buffer from usergart, if it is mapped there */ +static void evict(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + if (omap_obj->flags & OMAP_BO_TILED) { + enum tiler_fmt fmt = gem2fmt(omap_obj->flags); + int i; + + if (!usergart) + return; + + for (i = 0; i < NUM_USERGART_ENTRIES; i++) { + struct usergart_entry *entry = &usergart[fmt].entry[i]; + if (entry->obj == obj) + evict_entry(obj, fmt, entry); + } + } +} + +/* GEM objects can either be allocated from contiguous memory (in which + * case obj->filp==NULL), or w/ shmem backing (obj->filp!=NULL). But non + * contiguous buffers can be remapped in TILER/DMM if they need to be + * contiguous... but we don't do this all the time to reduce pressure + * on TILER/DMM space when we know at allocation time that the buffer + * will need to be scanned out. + */ +static inline bool is_shmem(struct drm_gem_object *obj) +{ + return obj->filp != NULL; +} + +/** + * shmem buffers that are mapped cached can simulate coherency via using + * page faulting to keep track of dirty pages + */ +static inline bool is_cached_coherent(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + return is_shmem(obj) && + ((omap_obj->flags & OMAP_BO_CACHE_MASK) == OMAP_BO_CACHED); +} + +static DEFINE_SPINLOCK(sync_lock); + +/** ensure backing pages are allocated */ +static int omap_gem_attach_pages(struct drm_gem_object *obj) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + struct page **pages; + int npages = obj->size >> PAGE_SHIFT; + int i, ret; + dma_addr_t *addrs; + + WARN_ON(omap_obj->pages); + + /* TODO: __GFP_DMA32 .. but somehow GFP_HIGHMEM is coming from the + * mapping_gfp_mask(mapping) which conflicts w/ GFP_DMA32.. probably + * we actually want CMA memory for it all anyways.. + */ + pages = _drm_gem_get_pages(obj, GFP_KERNEL); + if (IS_ERR(pages)) { + dev_err(obj->dev->dev, "could not get pages: %ld\n", PTR_ERR(pages)); + return PTR_ERR(pages); + } + + /* for non-cached buffers, ensure the new pages are clean because + * DSS, GPU, etc. are not cache coherent: + */ + if (omap_obj->flags & (OMAP_BO_WC|OMAP_BO_UNCACHED)) { + addrs = kmalloc(npages * sizeof(*addrs), GFP_KERNEL); + if (!addrs) { + ret = -ENOMEM; + goto free_pages; + } + + for (i = 0; i < npages; i++) { + addrs[i] = dma_map_page(dev->dev, pages[i], + 0, PAGE_SIZE, DMA_BIDIRECTIONAL); + } + } else { + addrs = kzalloc(npages * sizeof(*addrs), GFP_KERNEL); + if (!addrs) { + ret = -ENOMEM; + goto free_pages; + } + } + + omap_obj->addrs = addrs; + omap_obj->pages = pages; + + return 0; + +free_pages: + _drm_gem_put_pages(obj, pages, true, false); + + return ret; +} + +/** release backing pages */ +static void omap_gem_detach_pages(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + /* for non-cached buffers, ensure the new pages are clean because + * DSS, GPU, etc. are not cache coherent: + */ + if (omap_obj->flags & (OMAP_BO_WC|OMAP_BO_UNCACHED)) { + int i, npages = obj->size >> PAGE_SHIFT; + for (i = 0; i < npages; i++) { + dma_unmap_page(obj->dev->dev, omap_obj->addrs[i], + PAGE_SIZE, DMA_BIDIRECTIONAL); + } + } + + kfree(omap_obj->addrs); + omap_obj->addrs = NULL; + + _drm_gem_put_pages(obj, omap_obj->pages, true, false); + omap_obj->pages = NULL; +} + +/* get buffer flags */ +uint32_t omap_gem_flags(struct drm_gem_object *obj) +{ + return to_omap_bo(obj)->flags; +} + +/** get mmap offset */ +static uint64_t mmap_offset(struct drm_gem_object *obj) +{ + struct drm_device *dev = obj->dev; + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + if (!obj->map_list.map) { + /* Make it mmapable */ + size_t size = omap_gem_mmap_size(obj); + int ret = _drm_gem_create_mmap_offset_size(obj, size); + + if (ret) { + dev_err(dev->dev, "could not allocate mmap offset\n"); + return 0; + } + } + + return (uint64_t)obj->map_list.hash.key << PAGE_SHIFT; +} + +uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj) +{ + uint64_t offset; + mutex_lock(&obj->dev->struct_mutex); + offset = mmap_offset(obj); + mutex_unlock(&obj->dev->struct_mutex); + return offset; +} + +/** get mmap size */ +size_t omap_gem_mmap_size(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + size_t size = obj->size; + + if (omap_obj->flags & OMAP_BO_TILED) { + /* for tiled buffers, the virtual size has stride rounded up + * to 4kb.. (to hide the fact that row n+1 might start 16kb or + * 32kb later!). But we don't back the entire buffer with + * pages, only the valid picture part.. so need to adjust for + * this in the size used to mmap and generate mmap offset + */ + size = tiler_vsize(gem2fmt(omap_obj->flags), + omap_obj->width, omap_obj->height); + } + + return size; +} + +/* get tiled size, returns -EINVAL if not tiled buffer */ +int omap_gem_tiled_size(struct drm_gem_object *obj, uint16_t *w, uint16_t *h) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + if (omap_obj->flags & OMAP_BO_TILED) { + *w = omap_obj->width; + *h = omap_obj->height; + return 0; + } + return -EINVAL; +} + +/* Normal handling for the case of faulting in non-tiled buffers */ +static int fault_1d(struct drm_gem_object *obj, + struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + unsigned long pfn; + pgoff_t pgoff; + + /* We don't use vmf->pgoff since that has the fake offset: */ + pgoff = ((unsigned long)vmf->virtual_address - + vma->vm_start) >> PAGE_SHIFT; + + if (omap_obj->pages) { + omap_gem_cpu_sync(obj, pgoff); + pfn = page_to_pfn(omap_obj->pages[pgoff]); + } else { + BUG_ON(!(omap_obj->flags & OMAP_BO_DMA)); + pfn = (omap_obj->paddr >> PAGE_SHIFT) + pgoff; + } + + VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address, + pfn, pfn << PAGE_SHIFT); + + return vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn); +} + +/* Special handling for the case of faulting in 2d tiled buffers */ +static int fault_2d(struct drm_gem_object *obj, + struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + struct usergart_entry *entry; + enum tiler_fmt fmt = gem2fmt(omap_obj->flags); + struct page *pages[64]; /* XXX is this too much to have on stack? */ + unsigned long pfn; + pgoff_t pgoff, base_pgoff; + void __user *vaddr; + int i, ret, slots; + + /* + * Note the height of the slot is also equal to the number of pages + * that need to be mapped in to fill 4kb wide CPU page. If the slot + * height is 64, then 64 pages fill a 4kb wide by 64 row region. + */ + const int n = usergart[fmt].height; + const int n_shift = usergart[fmt].height_shift; + + /* + * If buffer width in bytes > PAGE_SIZE then the virtual stride is + * rounded up to next multiple of PAGE_SIZE.. this need to be taken + * into account in some of the math, so figure out virtual stride + * in pages + */ + const int m = 1 + ((omap_obj->width << fmt) / PAGE_SIZE); + + /* We don't use vmf->pgoff since that has the fake offset: */ + pgoff = ((unsigned long)vmf->virtual_address - + vma->vm_start) >> PAGE_SHIFT; + + /* + * Actual address we start mapping at is rounded down to previous slot + * boundary in the y direction: + */ + base_pgoff = round_down(pgoff, m << n_shift); + + /* figure out buffer width in slots */ + slots = omap_obj->width >> usergart[fmt].slot_shift; + + vaddr = vmf->virtual_address - ((pgoff - base_pgoff) << PAGE_SHIFT); + + entry = &usergart[fmt].entry[usergart[fmt].last]; + + /* evict previous buffer using this usergart entry, if any: */ + if (entry->obj) + evict_entry(entry->obj, fmt, entry); + + entry->obj = obj; + entry->obj_pgoff = base_pgoff; + + /* now convert base_pgoff to phys offset from virt offset: */ + base_pgoff = (base_pgoff >> n_shift) * slots; + + /* for wider-than 4k.. figure out which part of the slot-row we want: */ + if (m > 1) { + int off = pgoff % m; + entry->obj_pgoff += off; + base_pgoff /= m; + slots = min(slots - (off << n_shift), n); + base_pgoff += off << n_shift; + vaddr += off << PAGE_SHIFT; + } + + /* + * Map in pages. Beyond the valid pixel part of the buffer, we set + * pages[i] to NULL to get a dummy page mapped in.. if someone + * reads/writes it they will get random/undefined content, but at + * least it won't be corrupting whatever other random page used to + * be mapped in, or other undefined behavior. + */ + memcpy(pages, &omap_obj->pages[base_pgoff], + sizeof(struct page *) * slots); + memset(pages + slots, 0, + sizeof(struct page *) * (n - slots)); + + ret = tiler_pin(entry->block, pages, ARRAY_SIZE(pages), 0, true); + if (ret) { + dev_err(obj->dev->dev, "failed to pin: %d\n", ret); + return ret; + } + + pfn = entry->paddr >> PAGE_SHIFT; + + VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address, + pfn, pfn << PAGE_SHIFT); + + for (i = n; i > 0; i--) { + vm_insert_mixed(vma, (unsigned long)vaddr, pfn); + pfn += usergart[fmt].stride_pfn; + vaddr += PAGE_SIZE * m; + } + + /* simple round-robin: */ + usergart[fmt].last = (usergart[fmt].last + 1) % NUM_USERGART_ENTRIES; + + return 0; +} + +/** + * omap_gem_fault - pagefault handler for GEM objects + * @vma: the VMA of the GEM object + * @vmf: fault detail + * + * Invoked when a fault occurs on an mmap of a GEM managed area. GEM + * does most of the work for us including the actual map/unmap calls + * but we need to do the actual page work. + * + * The VMA was set up by GEM. In doing so it also ensured that the + * vma->vm_private_data points to the GEM object that is backing this + * mapping. + */ +int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct drm_gem_object *obj = vma->vm_private_data; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + struct drm_device *dev = obj->dev; + struct page **pages; + int ret; + + /* Make sure we don't parallel update on a fault, nor move or remove + * something from beneath our feet + */ + mutex_lock(&dev->struct_mutex); + + /* if a shmem backed object, make sure we have pages attached now */ + ret = get_pages(obj, &pages); + if (ret) + goto fail; + + /* where should we do corresponding put_pages().. we are mapping + * the original page, rather than thru a GART, so we can't rely + * on eviction to trigger this. But munmap() or all mappings should + * probably trigger put_pages()? + */ + + if (omap_obj->flags & OMAP_BO_TILED) + ret = fault_2d(obj, vma, vmf); + else + ret = fault_1d(obj, vma, vmf); + + +fail: + mutex_unlock(&dev->struct_mutex); + switch (ret) { + case 0: + case -ERESTARTSYS: + case -EINTR: + return VM_FAULT_NOPAGE; + case -ENOMEM: + return VM_FAULT_OOM; + default: + return VM_FAULT_SIGBUS; + } +} + +/** We override mainly to fix up some of the vm mapping flags.. */ +int omap_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) { + DBG("mmap failed: %d", ret); + return ret; + } + + return omap_gem_mmap_obj(vma->vm_private_data, vma); +} + +int omap_gem_mmap_obj(struct drm_gem_object *obj, + struct vm_area_struct *vma) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_flags |= VM_MIXEDMAP; + + if (omap_obj->flags & OMAP_BO_WC) { + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + } else if (omap_obj->flags & OMAP_BO_UNCACHED) { + vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags)); + } else { + /* + * We do have some private objects, at least for scanout buffers + * on hardware without DMM/TILER. But these are allocated write- + * combine + */ + if (WARN_ON(!obj->filp)) + return -EINVAL; + + /* + * Shunt off cached objs to shmem file so they have their own + * address_space (so unmap_mapping_range does what we want, + * in particular in the case of mmap'd dmabufs) + */ + fput(vma->vm_file); + vma->vm_pgoff = 0; + vma->vm_file = get_file(obj->filp); + + vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); + } + + return 0; +} + + +/** + * omap_gem_dumb_create - create a dumb buffer + * @drm_file: our client file + * @dev: our device + * @args: the requested arguments copied from userspace + * + * Allocate a buffer suitable for use for a frame buffer of the + * form described by user space. Give userspace a handle by which + * to reference it. + */ +int omap_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + union omap_gem_size gsize; + + /* in case someone tries to feed us a completely bogus stride: */ + args->pitch = align_pitch(args->pitch, args->width, args->bpp); + args->size = PAGE_ALIGN(args->pitch * args->height); + + gsize = (union omap_gem_size){ + .bytes = args->size, + }; + + return omap_gem_new_handle(dev, file, gsize, + OMAP_BO_SCANOUT | OMAP_BO_WC, &args->handle); +} + +/** + * omap_gem_dumb_destroy - destroy a dumb buffer + * @file: client file + * @dev: our DRM device + * @handle: the object handle + * + * Destroy a handle that was created via omap_gem_dumb_create. + */ +int omap_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, + uint32_t handle) +{ + /* No special work needed, drop the reference and see what falls out */ + return drm_gem_handle_delete(file, handle); +} + +/** + * omap_gem_dumb_map - buffer mapping for dumb interface + * @file: our drm client file + * @dev: drm device + * @handle: GEM handle to the object (from dumb_create) + * + * Do the necessary setup to allow the mapping of the frame buffer + * into user memory. We don't have to do much here at the moment. + */ +int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, + uint32_t handle, uint64_t *offset) +{ + struct drm_gem_object *obj; + int ret = 0; + + /* GEM does all our handle to object mapping */ + obj = drm_gem_object_lookup(dev, file, handle); + if (obj == NULL) { + ret = -ENOENT; + goto fail; + } + + *offset = omap_gem_mmap_offset(obj); + + drm_gem_object_unreference_unlocked(obj); + +fail: + return ret; +} + +/* Set scrolling position. This allows us to implement fast scrolling + * for console. + * + * Call only from non-atomic contexts. + */ +int omap_gem_roll(struct drm_gem_object *obj, uint32_t roll) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + uint32_t npages = obj->size >> PAGE_SHIFT; + int ret = 0; + + if (roll > npages) { + dev_err(obj->dev->dev, "invalid roll: %d\n", roll); + return -EINVAL; + } + + omap_obj->roll = roll; + + mutex_lock(&obj->dev->struct_mutex); + + /* if we aren't mapped yet, we don't need to do anything */ + if (omap_obj->block) { + struct page **pages; + ret = get_pages(obj, &pages); + if (ret) + goto fail; + ret = tiler_pin(omap_obj->block, pages, npages, roll, true); + if (ret) + dev_err(obj->dev->dev, "could not repin: %d\n", ret); + } + +fail: + mutex_unlock(&obj->dev->struct_mutex); + + return ret; +} + +/* Sync the buffer for CPU access.. note pages should already be + * attached, ie. omap_gem_get_pages() + */ +void omap_gem_cpu_sync(struct drm_gem_object *obj, int pgoff) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + if (is_cached_coherent(obj) && omap_obj->addrs[pgoff]) { + dma_unmap_page(dev->dev, omap_obj->addrs[pgoff], + PAGE_SIZE, DMA_BIDIRECTIONAL); + omap_obj->addrs[pgoff] = 0; + } +} + +/* sync the buffer for DMA access */ +void omap_gem_dma_sync(struct drm_gem_object *obj, + enum dma_data_direction dir) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + if (is_cached_coherent(obj)) { + int i, npages = obj->size >> PAGE_SHIFT; + struct page **pages = omap_obj->pages; + bool dirty = false; + + for (i = 0; i < npages; i++) { + if (!omap_obj->addrs[i]) { + omap_obj->addrs[i] = dma_map_page(dev->dev, pages[i], 0, + PAGE_SIZE, DMA_BIDIRECTIONAL); + dirty = true; + } + } + + if (dirty) { + unmap_mapping_range(obj->filp->f_mapping, 0, + omap_gem_mmap_size(obj), 1); + } + } +} + +/* Get physical address for DMA.. if 'remap' is true, and the buffer is not + * already contiguous, remap it to pin in physically contiguous memory.. (ie. + * map in TILER) + */ +int omap_gem_get_paddr(struct drm_gem_object *obj, + dma_addr_t *paddr, bool remap) +{ + struct omap_drm_private *priv = obj->dev->dev_private; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + mutex_lock(&obj->dev->struct_mutex); + + if (remap && is_shmem(obj) && priv->has_dmm) { + if (omap_obj->paddr_cnt == 0) { + struct page **pages; + uint32_t npages = obj->size >> PAGE_SHIFT; + enum tiler_fmt fmt = gem2fmt(omap_obj->flags); + struct tiler_block *block; + + BUG_ON(omap_obj->block); + + ret = get_pages(obj, &pages); + if (ret) + goto fail; + + if (omap_obj->flags & OMAP_BO_TILED) { + block = tiler_reserve_2d(fmt, + omap_obj->width, + omap_obj->height, 0); + } else { + block = tiler_reserve_1d(obj->size); + } + + if (IS_ERR(block)) { + ret = PTR_ERR(block); + dev_err(obj->dev->dev, + "could not remap: %d (%d)\n", ret, fmt); + goto fail; + } + + /* TODO: enable async refill.. */ + ret = tiler_pin(block, pages, npages, + omap_obj->roll, true); + if (ret) { + tiler_release(block); + dev_err(obj->dev->dev, + "could not pin: %d\n", ret); + goto fail; + } + + omap_obj->paddr = tiler_ssptr(block); + omap_obj->block = block; + + DBG("got paddr: %08x", omap_obj->paddr); + } + + omap_obj->paddr_cnt++; + + *paddr = omap_obj->paddr; + } else if (omap_obj->flags & OMAP_BO_DMA) { + *paddr = omap_obj->paddr; + } else { + ret = -EINVAL; + goto fail; + } + +fail: + mutex_unlock(&obj->dev->struct_mutex); + + return ret; +} + +/* Release physical address, when DMA is no longer being performed.. this + * could potentially unpin and unmap buffers from TILER + */ +int omap_gem_put_paddr(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + mutex_lock(&obj->dev->struct_mutex); + if (omap_obj->paddr_cnt > 0) { + omap_obj->paddr_cnt--; + if (omap_obj->paddr_cnt == 0) { + ret = tiler_unpin(omap_obj->block); + if (ret) { + dev_err(obj->dev->dev, + "could not unpin pages: %d\n", ret); + goto fail; + } + ret = tiler_release(omap_obj->block); + if (ret) { + dev_err(obj->dev->dev, + "could not release unmap: %d\n", ret); + } + omap_obj->block = NULL; + } + } +fail: + mutex_unlock(&obj->dev->struct_mutex); + return ret; +} + +/* Get rotated scanout address (only valid if already pinned), at the + * specified orientation and x,y offset from top-left corner of buffer + * (only valid for tiled 2d buffers) + */ +int omap_gem_rotated_paddr(struct drm_gem_object *obj, uint32_t orient, + int x, int y, dma_addr_t *paddr) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = -EINVAL; + + mutex_lock(&obj->dev->struct_mutex); + if ((omap_obj->paddr_cnt > 0) && omap_obj->block && + (omap_obj->flags & OMAP_BO_TILED)) { + *paddr = tiler_tsptr(omap_obj->block, orient, x, y); + ret = 0; + } + mutex_unlock(&obj->dev->struct_mutex); + return ret; +} + +/* Get tiler stride for the buffer (only valid for 2d tiled buffers) */ +int omap_gem_tiled_stride(struct drm_gem_object *obj, uint32_t orient) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = -EINVAL; + if (omap_obj->flags & OMAP_BO_TILED) + ret = tiler_stride(gem2fmt(omap_obj->flags), orient); + return ret; +} + +/* acquire pages when needed (for example, for DMA where physically + * contiguous buffer is not required + */ +static int get_pages(struct drm_gem_object *obj, struct page ***pages) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + if (is_shmem(obj) && !omap_obj->pages) { + ret = omap_gem_attach_pages(obj); + if (ret) { + dev_err(obj->dev->dev, "could not attach pages\n"); + return ret; + } + } + + /* TODO: even phys-contig.. we should have a list of pages? */ + *pages = omap_obj->pages; + + return 0; +} + +/* if !remap, and we don't have pages backing, then fail, rather than + * increasing the pin count (which we don't really do yet anyways, + * because we don't support swapping pages back out). And 'remap' + * might not be quite the right name, but I wanted to keep it working + * similarly to omap_gem_get_paddr(). Note though that mutex is not + * aquired if !remap (because this can be called in atomic ctxt), + * but probably omap_gem_get_paddr() should be changed to work in the + * same way. If !remap, a matching omap_gem_put_pages() call is not + * required (and should not be made). + */ +int omap_gem_get_pages(struct drm_gem_object *obj, struct page ***pages, + bool remap) +{ + int ret; + if (!remap) { + struct omap_gem_object *omap_obj = to_omap_bo(obj); + if (!omap_obj->pages) + return -ENOMEM; + *pages = omap_obj->pages; + return 0; + } + mutex_lock(&obj->dev->struct_mutex); + ret = get_pages(obj, pages); + mutex_unlock(&obj->dev->struct_mutex); + return ret; +} + +/* release pages when DMA no longer being performed */ +int omap_gem_put_pages(struct drm_gem_object *obj) +{ + /* do something here if we dynamically attach/detach pages.. at + * least they would no longer need to be pinned if everyone has + * released the pages.. + */ + return 0; +} + +/* Get kernel virtual address for CPU access.. this more or less only + * exists for omap_fbdev. This should be called with struct_mutex + * held. + */ +void *omap_gem_vaddr(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + WARN_ON(!mutex_is_locked(&obj->dev->struct_mutex)); + if (!omap_obj->vaddr) { + struct page **pages; + int ret = get_pages(obj, &pages); + if (ret) + return ERR_PTR(ret); + omap_obj->vaddr = vmap(pages, obj->size >> PAGE_SHIFT, + VM_MAP, pgprot_writecombine(PAGE_KERNEL)); + } + return omap_obj->vaddr; +} + +#ifdef CONFIG_PM +/* re-pin objects in DMM in resume path: */ +int omap_gem_resume(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + struct omap_drm_private *priv = drm_dev->dev_private; + struct omap_gem_object *omap_obj; + int ret = 0; + + list_for_each_entry(omap_obj, &priv->obj_list, mm_list) { + if (omap_obj->block) { + struct drm_gem_object *obj = &omap_obj->base; + uint32_t npages = obj->size >> PAGE_SHIFT; + WARN_ON(!omap_obj->pages); /* this can't happen */ + ret = tiler_pin(omap_obj->block, + omap_obj->pages, npages, + omap_obj->roll, true); + if (ret) { + dev_err(dev, "could not repin: %d\n", ret); + return ret; + } + } + } + + return 0; +} +#endif + +#ifdef CONFIG_DEBUG_FS +void omap_gem_describe(struct drm_gem_object *obj, struct seq_file *m) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + uint64_t off = 0; + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + if (obj->map_list.map) + off = (uint64_t)obj->map_list.hash.key; + + seq_printf(m, "%08x: %2d (%2d) %08llx %08Zx (%2d) %p %4d", + omap_obj->flags, obj->name, obj->refcount.refcount.counter, + off, omap_obj->paddr, omap_obj->paddr_cnt, + omap_obj->vaddr, omap_obj->roll); + + if (omap_obj->flags & OMAP_BO_TILED) { + seq_printf(m, " %dx%d", omap_obj->width, omap_obj->height); + if (omap_obj->block) { + struct tcm_area *area = &omap_obj->block->area; + seq_printf(m, " (%dx%d, %dx%d)", + area->p0.x, area->p0.y, + area->p1.x, area->p1.y); + } + } else { + seq_printf(m, " %d", obj->size); + } + + seq_printf(m, "\n"); +} + +void omap_gem_describe_objects(struct list_head *list, struct seq_file *m) +{ + struct omap_gem_object *omap_obj; + int count = 0; + size_t size = 0; + + list_for_each_entry(omap_obj, list, mm_list) { + struct drm_gem_object *obj = &omap_obj->base; + seq_printf(m, " "); + omap_gem_describe(obj, m); + count++; + size += obj->size; + } + + seq_printf(m, "Total %d objects, %zu bytes\n", count, size); +} +#endif + +/* Buffer Synchronization: + */ + +struct omap_gem_sync_waiter { + struct list_head list; + struct omap_gem_object *omap_obj; + enum omap_gem_op op; + uint32_t read_target, write_target; + /* notify called w/ sync_lock held */ + void (*notify)(void *arg); + void *arg; +}; + +/* list of omap_gem_sync_waiter.. the notify fxn gets called back when + * the read and/or write target count is achieved which can call a user + * callback (ex. to kick 3d and/or 2d), wakeup blocked task (prep for + * cpu access), etc. + */ +static LIST_HEAD(waiters); + +static inline bool is_waiting(struct omap_gem_sync_waiter *waiter) +{ + struct omap_gem_object *omap_obj = waiter->omap_obj; + if ((waiter->op & OMAP_GEM_READ) && + (omap_obj->sync->read_complete < waiter->read_target)) + return true; + if ((waiter->op & OMAP_GEM_WRITE) && + (omap_obj->sync->write_complete < waiter->write_target)) + return true; + return false; +} + +/* macro for sync debug.. */ +#define SYNCDBG 0 +#define SYNC(fmt, ...) do { if (SYNCDBG) \ + printk(KERN_ERR "%s:%d: "fmt"\n", \ + __func__, __LINE__, ##__VA_ARGS__); \ + } while (0) + + +static void sync_op_update(void) +{ + struct omap_gem_sync_waiter *waiter, *n; + list_for_each_entry_safe(waiter, n, &waiters, list) { + if (!is_waiting(waiter)) { + list_del(&waiter->list); + SYNC("notify: %p", waiter); + waiter->notify(waiter->arg); + kfree(waiter); + } + } +} + +static inline int sync_op(struct drm_gem_object *obj, + enum omap_gem_op op, bool start) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + spin_lock(&sync_lock); + + if (!omap_obj->sync) { + omap_obj->sync = kzalloc(sizeof(*omap_obj->sync), GFP_ATOMIC); + if (!omap_obj->sync) { + ret = -ENOMEM; + goto unlock; + } + } + + if (start) { + if (op & OMAP_GEM_READ) + omap_obj->sync->read_pending++; + if (op & OMAP_GEM_WRITE) + omap_obj->sync->write_pending++; + } else { + if (op & OMAP_GEM_READ) + omap_obj->sync->read_complete++; + if (op & OMAP_GEM_WRITE) + omap_obj->sync->write_complete++; + sync_op_update(); + } + +unlock: + spin_unlock(&sync_lock); + + return ret; +} + +/* it is a bit lame to handle updates in this sort of polling way, but + * in case of PVR, the GPU can directly update read/write complete + * values, and not really tell us which ones it updated.. this also + * means that sync_lock is not quite sufficient. So we'll need to + * do something a bit better when it comes time to add support for + * separate 2d hw.. + */ +void omap_gem_op_update(void) +{ + spin_lock(&sync_lock); + sync_op_update(); + spin_unlock(&sync_lock); +} + +/* mark the start of read and/or write operation */ +int omap_gem_op_start(struct drm_gem_object *obj, enum omap_gem_op op) +{ + return sync_op(obj, op, true); +} + +int omap_gem_op_finish(struct drm_gem_object *obj, enum omap_gem_op op) +{ + return sync_op(obj, op, false); +} + +static DECLARE_WAIT_QUEUE_HEAD(sync_event); + +static void sync_notify(void *arg) +{ + struct task_struct **waiter_task = arg; + *waiter_task = NULL; + wake_up_all(&sync_event); +} + +int omap_gem_op_sync(struct drm_gem_object *obj, enum omap_gem_op op) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + if (omap_obj->sync) { + struct task_struct *waiter_task = current; + struct omap_gem_sync_waiter *waiter = + kzalloc(sizeof(*waiter), GFP_KERNEL); + + if (!waiter) + return -ENOMEM; + + waiter->omap_obj = omap_obj; + waiter->op = op; + waiter->read_target = omap_obj->sync->read_pending; + waiter->write_target = omap_obj->sync->write_pending; + waiter->notify = sync_notify; + waiter->arg = &waiter_task; + + spin_lock(&sync_lock); + if (is_waiting(waiter)) { + SYNC("waited: %p", waiter); + list_add_tail(&waiter->list, &waiters); + spin_unlock(&sync_lock); + ret = wait_event_interruptible(sync_event, + (waiter_task == NULL)); + spin_lock(&sync_lock); + if (waiter_task) { + SYNC("interrupted: %p", waiter); + /* we were interrupted */ + list_del(&waiter->list); + waiter_task = NULL; + } else { + /* freed in sync_op_update() */ + waiter = NULL; + } + } + spin_unlock(&sync_lock); + + if (waiter) + kfree(waiter); + } + return ret; +} + +/* call fxn(arg), either synchronously or asynchronously if the op + * is currently blocked.. fxn() can be called from any context + * + * (TODO for now fxn is called back from whichever context calls + * omap_gem_op_update().. but this could be better defined later + * if needed) + * + * TODO more code in common w/ _sync().. + */ +int omap_gem_op_async(struct drm_gem_object *obj, enum omap_gem_op op, + void (*fxn)(void *arg), void *arg) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + if (omap_obj->sync) { + struct omap_gem_sync_waiter *waiter = + kzalloc(sizeof(*waiter), GFP_ATOMIC); + + if (!waiter) + return -ENOMEM; + + waiter->omap_obj = omap_obj; + waiter->op = op; + waiter->read_target = omap_obj->sync->read_pending; + waiter->write_target = omap_obj->sync->write_pending; + waiter->notify = fxn; + waiter->arg = arg; + + spin_lock(&sync_lock); + if (is_waiting(waiter)) { + SYNC("waited: %p", waiter); + list_add_tail(&waiter->list, &waiters); + spin_unlock(&sync_lock); + return 0; + } + + spin_unlock(&sync_lock); + } + + /* no waiting.. */ + fxn(arg); + + return 0; +} + +/* special API so PVR can update the buffer to use a sync-object allocated + * from it's sync-obj heap. Only used for a newly allocated (from PVR's + * perspective) sync-object, so we overwrite the new syncobj w/ values + * from the already allocated syncobj (if there is one) + */ +int omap_gem_set_sync_object(struct drm_gem_object *obj, void *syncobj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + spin_lock(&sync_lock); + + if ((omap_obj->flags & OMAP_BO_EXT_SYNC) && !syncobj) { + /* clearing a previously set syncobj */ + syncobj = kmemdup(omap_obj->sync, sizeof(*omap_obj->sync), + GFP_ATOMIC); + if (!syncobj) { + ret = -ENOMEM; + goto unlock; + } + omap_obj->flags &= ~OMAP_BO_EXT_SYNC; + omap_obj->sync = syncobj; + } else if (syncobj && !(omap_obj->flags & OMAP_BO_EXT_SYNC)) { + /* replacing an existing syncobj */ + if (omap_obj->sync) { + memcpy(syncobj, omap_obj->sync, sizeof(*omap_obj->sync)); + kfree(omap_obj->sync); + } + omap_obj->flags |= OMAP_BO_EXT_SYNC; + omap_obj->sync = syncobj; + } + +unlock: + spin_unlock(&sync_lock); + return ret; +} + +int omap_gem_init_object(struct drm_gem_object *obj) +{ + return -EINVAL; /* unused */ +} + +/* don't call directly.. called from GEM core when it is time to actually + * free the object.. + */ +void omap_gem_free_object(struct drm_gem_object *obj) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + evict(obj); + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + list_del(&omap_obj->mm_list); + + if (obj->map_list.map) + drm_gem_free_mmap_offset(obj); + + /* this means the object is still pinned.. which really should + * not happen. I think.. + */ + WARN_ON(omap_obj->paddr_cnt > 0); + + /* don't free externally allocated backing memory */ + if (!(omap_obj->flags & OMAP_BO_EXT_MEM)) { + if (omap_obj->pages) + omap_gem_detach_pages(obj); + + if (!is_shmem(obj)) { + dma_free_writecombine(dev->dev, obj->size, + omap_obj->vaddr, omap_obj->paddr); + } else if (omap_obj->vaddr) { + vunmap(omap_obj->vaddr); + } + } + + /* don't free externally allocated syncobj */ + if (!(omap_obj->flags & OMAP_BO_EXT_SYNC)) + kfree(omap_obj->sync); + + drm_gem_object_release(obj); + + kfree(obj); +} + +/* convenience method to construct a GEM buffer object, and userspace handle */ +int omap_gem_new_handle(struct drm_device *dev, struct drm_file *file, + union omap_gem_size gsize, uint32_t flags, uint32_t *handle) +{ + struct drm_gem_object *obj; + int ret; + + obj = omap_gem_new(dev, gsize, flags); + if (!obj) + return -ENOMEM; + + ret = drm_gem_handle_create(file, obj, handle); + if (ret) { + drm_gem_object_release(obj); + kfree(obj); /* TODO isn't there a dtor to call? just copying i915 */ + return ret; + } + + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(obj); + + return 0; +} + +/* GEM buffer object constructor */ +struct drm_gem_object *omap_gem_new(struct drm_device *dev, + union omap_gem_size gsize, uint32_t flags) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_gem_object *omap_obj; + struct drm_gem_object *obj = NULL; + size_t size; + int ret; + + if (flags & OMAP_BO_TILED) { + if (!usergart) { + dev_err(dev->dev, "Tiled buffers require DMM\n"); + goto fail; + } + + /* tiled buffers are always shmem paged backed.. when they are + * scanned out, they are remapped into DMM/TILER + */ + flags &= ~OMAP_BO_SCANOUT; + + /* currently don't allow cached buffers.. there is some caching + * stuff that needs to be handled better + */ + flags &= ~(OMAP_BO_CACHED|OMAP_BO_UNCACHED); + flags |= OMAP_BO_WC; + + /* align dimensions to slot boundaries... */ + tiler_align(gem2fmt(flags), + &gsize.tiled.width, &gsize.tiled.height); + + /* ...and calculate size based on aligned dimensions */ + size = tiler_size(gem2fmt(flags), + gsize.tiled.width, gsize.tiled.height); + } else { + size = PAGE_ALIGN(gsize.bytes); + } + + omap_obj = kzalloc(sizeof(*omap_obj), GFP_KERNEL); + if (!omap_obj) { + dev_err(dev->dev, "could not allocate GEM object\n"); + goto fail; + } + + list_add(&omap_obj->mm_list, &priv->obj_list); + + obj = &omap_obj->base; + + if ((flags & OMAP_BO_SCANOUT) && !priv->has_dmm) { + /* attempt to allocate contiguous memory if we don't + * have DMM for remappign discontiguous buffers + */ + omap_obj->vaddr = dma_alloc_writecombine(dev->dev, size, + &omap_obj->paddr, GFP_KERNEL); + if (omap_obj->vaddr) + flags |= OMAP_BO_DMA; + + } + + omap_obj->flags = flags; + + if (flags & OMAP_BO_TILED) { + omap_obj->width = gsize.tiled.width; + omap_obj->height = gsize.tiled.height; + } + + if (flags & (OMAP_BO_DMA|OMAP_BO_EXT_MEM)) + ret = drm_gem_private_object_init(dev, obj, size); + else + ret = drm_gem_object_init(dev, obj, size); + + if (ret) + goto fail; + + return obj; + +fail: + if (obj) + omap_gem_free_object(obj); + + return NULL; +} + +/* init/cleanup.. if DMM is used, we need to set some stuff up.. */ +void omap_gem_init(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + const enum tiler_fmt fmts[] = { + TILFMT_8BIT, TILFMT_16BIT, TILFMT_32BIT + }; + int i, j; + + if (!dmm_is_available()) { + /* DMM only supported on OMAP4 and later, so this isn't fatal */ + dev_warn(dev->dev, "DMM not available, disable DMM support\n"); + return; + } + + usergart = kzalloc(3 * sizeof(*usergart), GFP_KERNEL); + if (!usergart) { + dev_warn(dev->dev, "could not allocate usergart\n"); + return; + } + + /* reserve 4k aligned/wide regions for userspace mappings: */ + for (i = 0; i < ARRAY_SIZE(fmts); i++) { + uint16_t h = 1, w = PAGE_SIZE >> i; + tiler_align(fmts[i], &w, &h); + /* note: since each region is 1 4kb page wide, and minimum + * number of rows, the height ends up being the same as the + * # of pages in the region + */ + usergart[i].height = h; + usergart[i].height_shift = ilog2(h); + usergart[i].stride_pfn = tiler_stride(fmts[i], 0) >> PAGE_SHIFT; + usergart[i].slot_shift = ilog2((PAGE_SIZE / h) >> i); + for (j = 0; j < NUM_USERGART_ENTRIES; j++) { + struct usergart_entry *entry = &usergart[i].entry[j]; + struct tiler_block *block = + tiler_reserve_2d(fmts[i], w, h, + PAGE_SIZE); + if (IS_ERR(block)) { + dev_err(dev->dev, + "reserve failed: %d, %d, %ld\n", + i, j, PTR_ERR(block)); + return; + } + entry->paddr = tiler_ssptr(block); + entry->block = block; + + DBG("%d:%d: %dx%d: paddr=%08x stride=%d", i, j, w, h, + entry->paddr, + usergart[i].stride_pfn << PAGE_SHIFT); + } + } + + priv->has_dmm = true; +} + +void omap_gem_deinit(struct drm_device *dev) +{ + /* I believe we can rely on there being no more outstanding GEM + * objects which could depend on usergart/dmm at this point. + */ + kfree(usergart); +} diff --git a/drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c b/drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c new file mode 100644 index 000000000000..ac74d1bc67bf --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c @@ -0,0 +1,225 @@ +/* + * drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +#include <linux/dma-buf.h> + +static struct sg_table *omap_gem_map_dma_buf( + struct dma_buf_attachment *attachment, + enum dma_data_direction dir) +{ + struct drm_gem_object *obj = attachment->dmabuf->priv; + struct sg_table *sg; + dma_addr_t paddr; + int ret; + + sg = kzalloc(sizeof(*sg), GFP_KERNEL); + if (!sg) + return ERR_PTR(-ENOMEM); + + /* camera, etc, need physically contiguous.. but we need a + * better way to know this.. + */ + ret = omap_gem_get_paddr(obj, &paddr, true); + if (ret) + goto out; + + ret = sg_alloc_table(sg, 1, GFP_KERNEL); + if (ret) + goto out; + + sg_init_table(sg->sgl, 1); + sg_dma_len(sg->sgl) = obj->size; + sg_set_page(sg->sgl, pfn_to_page(PFN_DOWN(paddr)), obj->size, 0); + sg_dma_address(sg->sgl) = paddr; + + /* this should be after _get_paddr() to ensure we have pages attached */ + omap_gem_dma_sync(obj, dir); + + return sg; +out: + kfree(sg); + return ERR_PTR(ret); +} + +static void omap_gem_unmap_dma_buf(struct dma_buf_attachment *attachment, + struct sg_table *sg, enum dma_data_direction dir) +{ + struct drm_gem_object *obj = attachment->dmabuf->priv; + omap_gem_put_paddr(obj); + sg_free_table(sg); + kfree(sg); +} + +static void omap_gem_dmabuf_release(struct dma_buf *buffer) +{ + struct drm_gem_object *obj = buffer->priv; + /* release reference that was taken when dmabuf was exported + * in omap_gem_prime_set().. + */ + drm_gem_object_unreference_unlocked(obj); +} + + +static int omap_gem_dmabuf_begin_cpu_access(struct dma_buf *buffer, + size_t start, size_t len, enum dma_data_direction dir) +{ + struct drm_gem_object *obj = buffer->priv; + struct page **pages; + if (omap_gem_flags(obj) & OMAP_BO_TILED) { + /* TODO we would need to pin at least part of the buffer to + * get de-tiled view. For now just reject it. + */ + return -ENOMEM; + } + /* make sure we have the pages: */ + return omap_gem_get_pages(obj, &pages, true); +} + +static void omap_gem_dmabuf_end_cpu_access(struct dma_buf *buffer, + size_t start, size_t len, enum dma_data_direction dir) +{ + struct drm_gem_object *obj = buffer->priv; + omap_gem_put_pages(obj); +} + + +static void *omap_gem_dmabuf_kmap_atomic(struct dma_buf *buffer, + unsigned long page_num) +{ + struct drm_gem_object *obj = buffer->priv; + struct page **pages; + omap_gem_get_pages(obj, &pages, false); + omap_gem_cpu_sync(obj, page_num); + return kmap_atomic(pages[page_num]); +} + +static void omap_gem_dmabuf_kunmap_atomic(struct dma_buf *buffer, + unsigned long page_num, void *addr) +{ + kunmap_atomic(addr); +} + +static void *omap_gem_dmabuf_kmap(struct dma_buf *buffer, + unsigned long page_num) +{ + struct drm_gem_object *obj = buffer->priv; + struct page **pages; + omap_gem_get_pages(obj, &pages, false); + omap_gem_cpu_sync(obj, page_num); + return kmap(pages[page_num]); +} + +static void omap_gem_dmabuf_kunmap(struct dma_buf *buffer, + unsigned long page_num, void *addr) +{ + struct drm_gem_object *obj = buffer->priv; + struct page **pages; + omap_gem_get_pages(obj, &pages, false); + kunmap(pages[page_num]); +} + +/* + * TODO maybe we can split up drm_gem_mmap to avoid duplicating + * some here.. or at least have a drm_dmabuf_mmap helper. + */ +static int omap_gem_dmabuf_mmap(struct dma_buf *buffer, + struct vm_area_struct *vma) +{ + struct drm_gem_object *obj = buffer->priv; + int ret = 0; + + if (WARN_ON(!obj->filp)) + return -EINVAL; + + /* Check for valid size. */ + if (omap_gem_mmap_size(obj) < vma->vm_end - vma->vm_start) { + ret = -EINVAL; + goto out_unlock; + } + + if (!obj->dev->driver->gem_vm_ops) { + ret = -EINVAL; + goto out_unlock; + } + + vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP; + vma->vm_ops = obj->dev->driver->gem_vm_ops; + vma->vm_private_data = obj; + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + + /* Take a ref for this mapping of the object, so that the fault + * handler can dereference the mmap offset's pointer to the object. + * This reference is cleaned up by the corresponding vm_close + * (which should happen whether the vma was created by this call, or + * by a vm_open due to mremap or partial unmap or whatever). + */ + vma->vm_ops->open(vma); + +out_unlock: + + return omap_gem_mmap_obj(obj, vma); +} + +struct dma_buf_ops omap_dmabuf_ops = { + .map_dma_buf = omap_gem_map_dma_buf, + .unmap_dma_buf = omap_gem_unmap_dma_buf, + .release = omap_gem_dmabuf_release, + .begin_cpu_access = omap_gem_dmabuf_begin_cpu_access, + .end_cpu_access = omap_gem_dmabuf_end_cpu_access, + .kmap_atomic = omap_gem_dmabuf_kmap_atomic, + .kunmap_atomic = omap_gem_dmabuf_kunmap_atomic, + .kmap = omap_gem_dmabuf_kmap, + .kunmap = omap_gem_dmabuf_kunmap, + .mmap = omap_gem_dmabuf_mmap, +}; + +struct dma_buf *omap_gem_prime_export(struct drm_device *dev, + struct drm_gem_object *obj, int flags) +{ + return dma_buf_export(obj, &omap_dmabuf_ops, obj->size, flags); +} + +struct drm_gem_object *omap_gem_prime_import(struct drm_device *dev, + struct dma_buf *buffer) +{ + struct drm_gem_object *obj; + + /* is this one of own objects? */ + if (buffer->ops == &omap_dmabuf_ops) { + obj = buffer->priv; + /* is it from our device? */ + if (obj->dev == dev) { + /* + * Importing dmabuf exported from out own gem increases + * refcount on gem itself instead of f_count of dmabuf. + */ + drm_gem_object_reference(obj); + dma_buf_put(buffer); + return obj; + } + } + + /* + * TODO add support for importing buffers from other devices.. + * for now we don't need this but would be nice to add eventually + */ + return ERR_PTR(-EINVAL); +} diff --git a/drivers/gpu/drm/omapdrm/omap_gem_helpers.c b/drivers/gpu/drm/omapdrm/omap_gem_helpers.c new file mode 100644 index 000000000000..e4a66a35fc6a --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_gem_helpers.c @@ -0,0 +1,169 @@ +/* + * drivers/gpu/drm/omapdrm/omap_gem_helpers.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* temporary copy of drm_gem_{get,put}_pages() until the + * "drm/gem: add functions to get/put pages" patch is merged.. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/shmem_fs.h> + +#include <drm/drmP.h> + +/** + * drm_gem_get_pages - helper to allocate backing pages for a GEM object + * @obj: obj in question + * @gfpmask: gfp mask of requested pages + */ +struct page **_drm_gem_get_pages(struct drm_gem_object *obj, gfp_t gfpmask) +{ + struct inode *inode; + struct address_space *mapping; + struct page *p, **pages; + int i, npages; + + /* This is the shared memory object that backs the GEM resource */ + inode = obj->filp->f_path.dentry->d_inode; + mapping = inode->i_mapping; + + npages = obj->size >> PAGE_SHIFT; + + pages = drm_malloc_ab(npages, sizeof(struct page *)); + if (pages == NULL) + return ERR_PTR(-ENOMEM); + + gfpmask |= mapping_gfp_mask(mapping); + + for (i = 0; i < npages; i++) { + p = shmem_read_mapping_page_gfp(mapping, i, gfpmask); + if (IS_ERR(p)) + goto fail; + pages[i] = p; + + /* There is a hypothetical issue w/ drivers that require + * buffer memory in the low 4GB.. if the pages are un- + * pinned, and swapped out, they can end up swapped back + * in above 4GB. If pages are already in memory, then + * shmem_read_mapping_page_gfp will ignore the gfpmask, + * even if the already in-memory page disobeys the mask. + * + * It is only a theoretical issue today, because none of + * the devices with this limitation can be populated with + * enough memory to trigger the issue. But this BUG_ON() + * is here as a reminder in case the problem with + * shmem_read_mapping_page_gfp() isn't solved by the time + * it does become a real issue. + * + * See this thread: http://lkml.org/lkml/2011/7/11/238 + */ + BUG_ON((gfpmask & __GFP_DMA32) && + (page_to_pfn(p) >= 0x00100000UL)); + } + + return pages; + +fail: + while (i--) + page_cache_release(pages[i]); + + drm_free_large(pages); + return ERR_CAST(p); +} + +/** + * drm_gem_put_pages - helper to free backing pages for a GEM object + * @obj: obj in question + * @pages: pages to free + */ +void _drm_gem_put_pages(struct drm_gem_object *obj, struct page **pages, + bool dirty, bool accessed) +{ + int i, npages; + + npages = obj->size >> PAGE_SHIFT; + + for (i = 0; i < npages; i++) { + if (dirty) + set_page_dirty(pages[i]); + + if (accessed) + mark_page_accessed(pages[i]); + + /* Undo the reference we took when populating the table */ + page_cache_release(pages[i]); + } + + drm_free_large(pages); +} + +int +_drm_gem_create_mmap_offset_size(struct drm_gem_object *obj, size_t size) +{ + struct drm_device *dev = obj->dev; + struct drm_gem_mm *mm = dev->mm_private; + struct drm_map_list *list; + struct drm_local_map *map; + int ret = 0; + + /* Set the object up for mmap'ing */ + list = &obj->map_list; + list->map = kzalloc(sizeof(struct drm_map_list), GFP_KERNEL); + if (!list->map) + return -ENOMEM; + + map = list->map; + map->type = _DRM_GEM; + map->size = size; + map->handle = obj; + + /* Get a DRM GEM mmap offset allocated... */ + list->file_offset_node = drm_mm_search_free(&mm->offset_manager, + size / PAGE_SIZE, 0, 0); + + if (!list->file_offset_node) { + DRM_ERROR("failed to allocate offset for bo %d\n", obj->name); + ret = -ENOSPC; + goto out_free_list; + } + + list->file_offset_node = drm_mm_get_block(list->file_offset_node, + size / PAGE_SIZE, 0); + if (!list->file_offset_node) { + ret = -ENOMEM; + goto out_free_list; + } + + list->hash.key = list->file_offset_node->start; + ret = drm_ht_insert_item(&mm->offset_hash, &list->hash); + if (ret) { + DRM_ERROR("failed to add to map hash\n"); + goto out_free_mm; + } + + return 0; + +out_free_mm: + drm_mm_put_block(list->file_offset_node); +out_free_list: + kfree(list->map); + list->map = NULL; + + return ret; +} diff --git a/drivers/gpu/drm/omapdrm/omap_irq.c b/drivers/gpu/drm/omapdrm/omap_irq.c new file mode 100644 index 000000000000..e01303ee00c3 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_irq.c @@ -0,0 +1,322 @@ +/* + * drivers/gpu/drm/omapdrm/omap_irq.c + * + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "omap_drv.h" + +static DEFINE_SPINLOCK(list_lock); + +static void omap_irq_error_handler(struct omap_drm_irq *irq, + uint32_t irqstatus) +{ + DRM_ERROR("errors: %08x\n", irqstatus); +} + +/* call with list_lock and dispc runtime held */ +static void omap_irq_update(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_drm_irq *irq; + uint32_t irqmask = priv->vblank_mask; + + BUG_ON(!spin_is_locked(&list_lock)); + + list_for_each_entry(irq, &priv->irq_list, node) + irqmask |= irq->irqmask; + + DBG("irqmask=%08x", irqmask); + + dispc_write_irqenable(irqmask); + dispc_read_irqenable(); /* flush posted write */ +} + +void omap_irq_register(struct drm_device *dev, struct omap_drm_irq *irq) +{ + struct omap_drm_private *priv = dev->dev_private; + unsigned long flags; + + dispc_runtime_get(); + spin_lock_irqsave(&list_lock, flags); + + if (!WARN_ON(irq->registered)) { + irq->registered = true; + list_add(&irq->node, &priv->irq_list); + omap_irq_update(dev); + } + + spin_unlock_irqrestore(&list_lock, flags); + dispc_runtime_put(); +} + +void omap_irq_unregister(struct drm_device *dev, struct omap_drm_irq *irq) +{ + unsigned long flags; + + dispc_runtime_get(); + spin_lock_irqsave(&list_lock, flags); + + if (!WARN_ON(!irq->registered)) { + irq->registered = false; + list_del(&irq->node); + omap_irq_update(dev); + } + + spin_unlock_irqrestore(&list_lock, flags); + dispc_runtime_put(); +} + +struct omap_irq_wait { + struct omap_drm_irq irq; + int count; +}; + +static DECLARE_WAIT_QUEUE_HEAD(wait_event); + +static void wait_irq(struct omap_drm_irq *irq, uint32_t irqstatus) +{ + struct omap_irq_wait *wait = + container_of(irq, struct omap_irq_wait, irq); + wait->count--; + wake_up_all(&wait_event); +} + +struct omap_irq_wait * omap_irq_wait_init(struct drm_device *dev, + uint32_t irqmask, int count) +{ + struct omap_irq_wait *wait = kzalloc(sizeof(*wait), GFP_KERNEL); + wait->irq.irq = wait_irq; + wait->irq.irqmask = irqmask; + wait->count = count; + omap_irq_register(dev, &wait->irq); + return wait; +} + +int omap_irq_wait(struct drm_device *dev, struct omap_irq_wait *wait, + unsigned long timeout) +{ + int ret = wait_event_timeout(wait_event, (wait->count <= 0), timeout); + omap_irq_unregister(dev, &wait->irq); + kfree(wait); + if (ret == 0) + return -1; + return 0; +} + +/** + * enable_vblank - enable vblank interrupt events + * @dev: DRM device + * @crtc: which irq to enable + * + * Enable vblank interrupts for @crtc. If the device doesn't have + * a hardware vblank counter, this routine should be a no-op, since + * interrupts will have to stay on to keep the count accurate. + * + * RETURNS + * Zero on success, appropriate errno if the given @crtc's vblank + * interrupt cannot be enabled. + */ +int omap_irq_enable_vblank(struct drm_device *dev, int crtc) +{ + struct omap_drm_private *priv = dev->dev_private; + unsigned long flags; + + DBG("dev=%p, crtc=%d", dev, crtc); + + dispc_runtime_get(); + spin_lock_irqsave(&list_lock, flags); + priv->vblank_mask |= pipe2vbl(crtc); + omap_irq_update(dev); + spin_unlock_irqrestore(&list_lock, flags); + dispc_runtime_put(); + + return 0; +} + +/** + * disable_vblank - disable vblank interrupt events + * @dev: DRM device + * @crtc: which irq to enable + * + * Disable vblank interrupts for @crtc. If the device doesn't have + * a hardware vblank counter, this routine should be a no-op, since + * interrupts will have to stay on to keep the count accurate. + */ +void omap_irq_disable_vblank(struct drm_device *dev, int crtc) +{ + struct omap_drm_private *priv = dev->dev_private; + unsigned long flags; + + DBG("dev=%p, crtc=%d", dev, crtc); + + dispc_runtime_get(); + spin_lock_irqsave(&list_lock, flags); + priv->vblank_mask &= ~pipe2vbl(crtc); + omap_irq_update(dev); + spin_unlock_irqrestore(&list_lock, flags); + dispc_runtime_put(); +} + +irqreturn_t omap_irq_handler(DRM_IRQ_ARGS) +{ + struct drm_device *dev = (struct drm_device *) arg; + struct omap_drm_private *priv = dev->dev_private; + struct omap_drm_irq *handler, *n; + unsigned long flags; + unsigned int id; + u32 irqstatus; + + irqstatus = dispc_read_irqstatus(); + dispc_clear_irqstatus(irqstatus); + dispc_read_irqstatus(); /* flush posted write */ + + VERB("irqs: %08x", irqstatus); + + for (id = 0; id < priv->num_crtcs; id++) + if (irqstatus & pipe2vbl(id)) + drm_handle_vblank(dev, id); + + spin_lock_irqsave(&list_lock, flags); + list_for_each_entry_safe(handler, n, &priv->irq_list, node) { + if (handler->irqmask & irqstatus) { + spin_unlock_irqrestore(&list_lock, flags); + handler->irq(handler, handler->irqmask & irqstatus); + spin_lock_irqsave(&list_lock, flags); + } + } + spin_unlock_irqrestore(&list_lock, flags); + + return IRQ_HANDLED; +} + +void omap_irq_preinstall(struct drm_device *dev) +{ + DBG("dev=%p", dev); + dispc_runtime_get(); + dispc_clear_irqstatus(0xffffffff); + dispc_runtime_put(); +} + +int omap_irq_postinstall(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_drm_irq *error_handler = &priv->error_handler; + + DBG("dev=%p", dev); + + INIT_LIST_HEAD(&priv->irq_list); + + error_handler->irq = omap_irq_error_handler; + error_handler->irqmask = DISPC_IRQ_OCP_ERR; + + /* for now ignore DISPC_IRQ_SYNC_LOST_DIGIT.. really I think + * we just need to ignore it while enabling tv-out + */ + error_handler->irqmask &= ~DISPC_IRQ_SYNC_LOST_DIGIT; + + omap_irq_register(dev, error_handler); + + return 0; +} + +void omap_irq_uninstall(struct drm_device *dev) +{ + DBG("dev=%p", dev); + // TODO prolly need to call drm_irq_uninstall() somewhere too +} + +/* + * We need a special version, instead of just using drm_irq_install(), + * because we need to register the irq via omapdss. Once omapdss and + * omapdrm are merged together we can assign the dispc hwmod data to + * ourselves and drop these and just use drm_irq_{install,uninstall}() + */ + +int omap_drm_irq_install(struct drm_device *dev) +{ + int ret; + + mutex_lock(&dev->struct_mutex); + + if (dev->irq_enabled) { + mutex_unlock(&dev->struct_mutex); + return -EBUSY; + } + dev->irq_enabled = 1; + mutex_unlock(&dev->struct_mutex); + + /* Before installing handler */ + if (dev->driver->irq_preinstall) + dev->driver->irq_preinstall(dev); + + ret = dispc_request_irq(dev->driver->irq_handler, dev); + + if (ret < 0) { + mutex_lock(&dev->struct_mutex); + dev->irq_enabled = 0; + mutex_unlock(&dev->struct_mutex); + return ret; + } + + /* After installing handler */ + if (dev->driver->irq_postinstall) + ret = dev->driver->irq_postinstall(dev); + + if (ret < 0) { + mutex_lock(&dev->struct_mutex); + dev->irq_enabled = 0; + mutex_unlock(&dev->struct_mutex); + dispc_free_irq(dev); + } + + return ret; +} + +int omap_drm_irq_uninstall(struct drm_device *dev) +{ + unsigned long irqflags; + int irq_enabled, i; + + mutex_lock(&dev->struct_mutex); + irq_enabled = dev->irq_enabled; + dev->irq_enabled = 0; + mutex_unlock(&dev->struct_mutex); + + /* + * Wake up any waiters so they don't hang. + */ + if (dev->num_crtcs) { + spin_lock_irqsave(&dev->vbl_lock, irqflags); + for (i = 0; i < dev->num_crtcs; i++) { + DRM_WAKEUP(&dev->vbl_queue[i]); + dev->vblank_enabled[i] = 0; + dev->last_vblank[i] = + dev->driver->get_vblank_counter(dev, i); + } + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); + } + + if (!irq_enabled) + return -EINVAL; + + if (dev->driver->irq_uninstall) + dev->driver->irq_uninstall(dev); + + dispc_free_irq(dev); + + return 0; +} diff --git a/drivers/gpu/drm/omapdrm/omap_plane.c b/drivers/gpu/drm/omapdrm/omap_plane.c new file mode 100644 index 000000000000..dd68d14ce615 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_plane.c @@ -0,0 +1,450 @@ +/* + * drivers/gpu/drm/omapdrm/omap_plane.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kfifo.h> + +#include "omap_drv.h" +#include "omap_dmm_tiler.h" + +/* some hackery because omapdss has an 'enum omap_plane' (which would be + * better named omap_plane_id).. and compiler seems unhappy about having + * both a 'struct omap_plane' and 'enum omap_plane' + */ +#define omap_plane _omap_plane + +/* + * plane funcs + */ + +struct callback { + void (*fxn)(void *); + void *arg; +}; + +#define to_omap_plane(x) container_of(x, struct omap_plane, base) + +struct omap_plane { + struct drm_plane base; + int id; /* TODO rename omap_plane -> omap_plane_id in omapdss so I can use the enum */ + const char *name; + struct omap_overlay_info info; + struct omap_drm_apply apply; + + /* position/orientation of scanout within the fb: */ + struct omap_drm_window win; + bool enabled; + + /* last fb that we pinned: */ + struct drm_framebuffer *pinned_fb; + + uint32_t nformats; + uint32_t formats[32]; + + struct omap_drm_irq error_irq; + + /* set of bo's pending unpin until next post_apply() */ + DECLARE_KFIFO_PTR(unpin_fifo, struct drm_gem_object *); + + // XXX maybe get rid of this and handle vblank in crtc too? + struct callback apply_done_cb; +}; + +static void unpin(void *arg, struct drm_gem_object *bo) +{ + struct drm_plane *plane = arg; + struct omap_plane *omap_plane = to_omap_plane(plane); + + if (kfifo_put(&omap_plane->unpin_fifo, + (const struct drm_gem_object **)&bo)) { + /* also hold a ref so it isn't free'd while pinned */ + drm_gem_object_reference(bo); + } else { + dev_err(plane->dev->dev, "unpin fifo full!\n"); + omap_gem_put_paddr(bo); + } +} + +/* update which fb (if any) is pinned for scanout */ +static int update_pin(struct drm_plane *plane, struct drm_framebuffer *fb) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + struct drm_framebuffer *pinned_fb = omap_plane->pinned_fb; + + if (pinned_fb != fb) { + int ret; + + DBG("%p -> %p", pinned_fb, fb); + + if (fb) + drm_framebuffer_reference(fb); + + ret = omap_framebuffer_replace(pinned_fb, fb, plane, unpin); + + if (pinned_fb) + drm_framebuffer_unreference(pinned_fb); + + if (ret) { + dev_err(plane->dev->dev, "could not swap %p -> %p\n", + omap_plane->pinned_fb, fb); + if (fb) + drm_framebuffer_unreference(fb); + omap_plane->pinned_fb = NULL; + return ret; + } + + omap_plane->pinned_fb = fb; + } + + return 0; +} + +static void omap_plane_pre_apply(struct omap_drm_apply *apply) +{ + struct omap_plane *omap_plane = + container_of(apply, struct omap_plane, apply); + struct omap_drm_window *win = &omap_plane->win; + struct drm_plane *plane = &omap_plane->base; + struct drm_device *dev = plane->dev; + struct omap_overlay_info *info = &omap_plane->info; + struct drm_crtc *crtc = plane->crtc; + enum omap_channel channel; + bool enabled = omap_plane->enabled && crtc; + bool ilace, replication; + int ret; + + DBG("%s, enabled=%d", omap_plane->name, enabled); + + /* if fb has changed, pin new fb: */ + update_pin(plane, enabled ? plane->fb : NULL); + + if (!enabled) { + dispc_ovl_enable(omap_plane->id, false); + return; + } + + channel = omap_crtc_channel(crtc); + + /* update scanout: */ + omap_framebuffer_update_scanout(plane->fb, win, info); + + DBG("%dx%d -> %dx%d (%d)", info->width, info->height, + info->out_width, info->out_height, + info->screen_width); + DBG("%d,%d %08x %08x", info->pos_x, info->pos_y, + info->paddr, info->p_uv_addr); + + /* TODO: */ + ilace = false; + replication = false; + + /* and finally, update omapdss: */ + ret = dispc_ovl_setup(omap_plane->id, info, + replication, omap_crtc_timings(crtc), false); + if (ret) { + dev_err(dev->dev, "dispc_ovl_setup failed: %d\n", ret); + return; + } + + dispc_ovl_enable(omap_plane->id, true); + dispc_ovl_set_channel_out(omap_plane->id, channel); +} + +static void omap_plane_post_apply(struct omap_drm_apply *apply) +{ + struct omap_plane *omap_plane = + container_of(apply, struct omap_plane, apply); + struct drm_plane *plane = &omap_plane->base; + struct omap_overlay_info *info = &omap_plane->info; + struct drm_gem_object *bo = NULL; + struct callback cb; + + cb = omap_plane->apply_done_cb; + omap_plane->apply_done_cb.fxn = NULL; + + while (kfifo_get(&omap_plane->unpin_fifo, &bo)) { + omap_gem_put_paddr(bo); + drm_gem_object_unreference_unlocked(bo); + } + + if (cb.fxn) + cb.fxn(cb.arg); + + if (omap_plane->enabled) { + omap_framebuffer_flush(plane->fb, info->pos_x, info->pos_y, + info->out_width, info->out_height); + } +} + +static int apply(struct drm_plane *plane) +{ + if (plane->crtc) { + struct omap_plane *omap_plane = to_omap_plane(plane); + return omap_crtc_apply(plane->crtc, &omap_plane->apply); + } + return 0; +} + +int omap_plane_mode_set(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h, + void (*fxn)(void *), void *arg) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + struct omap_drm_window *win = &omap_plane->win; + + win->crtc_x = crtc_x; + win->crtc_y = crtc_y; + win->crtc_w = crtc_w; + win->crtc_h = crtc_h; + + /* src values are in Q16 fixed point, convert to integer: */ + win->src_x = src_x >> 16; + win->src_y = src_y >> 16; + win->src_w = src_w >> 16; + win->src_h = src_h >> 16; + + if (fxn) { + /* omap_crtc should ensure that a new page flip + * isn't permitted while there is one pending: + */ + BUG_ON(omap_plane->apply_done_cb.fxn); + + omap_plane->apply_done_cb.fxn = fxn; + omap_plane->apply_done_cb.arg = arg; + } + + plane->fb = fb; + plane->crtc = crtc; + + return apply(plane); +} + +static int omap_plane_update(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + omap_plane->enabled = true; + return omap_plane_mode_set(plane, crtc, fb, + crtc_x, crtc_y, crtc_w, crtc_h, + src_x, src_y, src_w, src_h, + NULL, NULL); +} + +static int omap_plane_disable(struct drm_plane *plane) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + omap_plane->win.rotation = BIT(DRM_ROTATE_0); + return omap_plane_dpms(plane, DRM_MODE_DPMS_OFF); +} + +static void omap_plane_destroy(struct drm_plane *plane) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + + DBG("%s", omap_plane->name); + + omap_irq_unregister(plane->dev, &omap_plane->error_irq); + + omap_plane_disable(plane); + drm_plane_cleanup(plane); + + WARN_ON(!kfifo_is_empty(&omap_plane->unpin_fifo)); + kfifo_free(&omap_plane->unpin_fifo); + + kfree(omap_plane); +} + +int omap_plane_dpms(struct drm_plane *plane, int mode) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + bool enabled = (mode == DRM_MODE_DPMS_ON); + int ret = 0; + + if (enabled != omap_plane->enabled) { + omap_plane->enabled = enabled; + ret = apply(plane); + } + + return ret; +} + +/* helper to install properties which are common to planes and crtcs */ +void omap_plane_install_properties(struct drm_plane *plane, + struct drm_mode_object *obj) +{ + struct drm_device *dev = plane->dev; + struct omap_drm_private *priv = dev->dev_private; + struct drm_property *prop; + + if (priv->has_dmm) { + prop = priv->rotation_prop; + if (!prop) { + const struct drm_prop_enum_list props[] = { + { DRM_ROTATE_0, "rotate-0" }, + { DRM_ROTATE_90, "rotate-90" }, + { DRM_ROTATE_180, "rotate-180" }, + { DRM_ROTATE_270, "rotate-270" }, + { DRM_REFLECT_X, "reflect-x" }, + { DRM_REFLECT_Y, "reflect-y" }, + }; + prop = drm_property_create_bitmask(dev, 0, "rotation", + props, ARRAY_SIZE(props)); + if (prop == NULL) + return; + priv->rotation_prop = prop; + } + drm_object_attach_property(obj, prop, 0); + } + + prop = priv->zorder_prop; + if (!prop) { + prop = drm_property_create_range(dev, 0, "zorder", 0, 3); + if (prop == NULL) + return; + priv->zorder_prop = prop; + } + drm_object_attach_property(obj, prop, 0); +} + +int omap_plane_set_property(struct drm_plane *plane, + struct drm_property *property, uint64_t val) +{ + struct omap_plane *omap_plane = to_omap_plane(plane); + struct omap_drm_private *priv = plane->dev->dev_private; + int ret = -EINVAL; + + if (property == priv->rotation_prop) { + DBG("%s: rotation: %02x", omap_plane->name, (uint32_t)val); + omap_plane->win.rotation = val; + ret = apply(plane); + } else if (property == priv->zorder_prop) { + DBG("%s: zorder: %02x", omap_plane->name, (uint32_t)val); + omap_plane->info.zorder = val; + ret = apply(plane); + } + + return ret; +} + +static const struct drm_plane_funcs omap_plane_funcs = { + .update_plane = omap_plane_update, + .disable_plane = omap_plane_disable, + .destroy = omap_plane_destroy, + .set_property = omap_plane_set_property, +}; + +static void omap_plane_error_irq(struct omap_drm_irq *irq, uint32_t irqstatus) +{ + struct omap_plane *omap_plane = + container_of(irq, struct omap_plane, error_irq); + DRM_ERROR("%s: errors: %08x\n", omap_plane->name, irqstatus); +} + +static const char *plane_names[] = { + [OMAP_DSS_GFX] = "gfx", + [OMAP_DSS_VIDEO1] = "vid1", + [OMAP_DSS_VIDEO2] = "vid2", + [OMAP_DSS_VIDEO3] = "vid3", +}; + +static const uint32_t error_irqs[] = { + [OMAP_DSS_GFX] = DISPC_IRQ_GFX_FIFO_UNDERFLOW, + [OMAP_DSS_VIDEO1] = DISPC_IRQ_VID1_FIFO_UNDERFLOW, + [OMAP_DSS_VIDEO2] = DISPC_IRQ_VID2_FIFO_UNDERFLOW, + [OMAP_DSS_VIDEO3] = DISPC_IRQ_VID3_FIFO_UNDERFLOW, +}; + +/* initialize plane */ +struct drm_plane *omap_plane_init(struct drm_device *dev, + int id, bool private_plane) +{ + struct omap_drm_private *priv = dev->dev_private; + struct drm_plane *plane = NULL; + struct omap_plane *omap_plane; + struct omap_overlay_info *info; + int ret; + + DBG("%s: priv=%d", plane_names[id], private_plane); + + omap_plane = kzalloc(sizeof(*omap_plane), GFP_KERNEL); + if (!omap_plane) { + dev_err(dev->dev, "could not allocate plane\n"); + goto fail; + } + + ret = kfifo_alloc(&omap_plane->unpin_fifo, 16, GFP_KERNEL); + if (ret) { + dev_err(dev->dev, "could not allocate unpin FIFO\n"); + goto fail; + } + + omap_plane->nformats = omap_framebuffer_get_formats( + omap_plane->formats, ARRAY_SIZE(omap_plane->formats), + dss_feat_get_supported_color_modes(id)); + omap_plane->id = id; + omap_plane->name = plane_names[id]; + + plane = &omap_plane->base; + + omap_plane->apply.pre_apply = omap_plane_pre_apply; + omap_plane->apply.post_apply = omap_plane_post_apply; + + omap_plane->error_irq.irqmask = error_irqs[id]; + omap_plane->error_irq.irq = omap_plane_error_irq; + omap_irq_register(dev, &omap_plane->error_irq); + + drm_plane_init(dev, plane, (1 << priv->num_crtcs) - 1, &omap_plane_funcs, + omap_plane->formats, omap_plane->nformats, private_plane); + + omap_plane_install_properties(plane, &plane->base); + + /* get our starting configuration, set defaults for parameters + * we don't currently use, etc: + */ + info = &omap_plane->info; + info->rotation_type = OMAP_DSS_ROT_DMA; + info->rotation = OMAP_DSS_ROT_0; + info->global_alpha = 0xff; + info->mirror = 0; + + /* Set defaults depending on whether we are a CRTC or overlay + * layer. + * TODO add ioctl to give userspace an API to change this.. this + * will come in a subsequent patch. + */ + if (private_plane) + omap_plane->info.zorder = 0; + else + omap_plane->info.zorder = id; + + return plane; + +fail: + if (plane) + omap_plane_destroy(plane); + + return NULL; +} diff --git a/drivers/gpu/drm/omapdrm/tcm-sita.c b/drivers/gpu/drm/omapdrm/tcm-sita.c new file mode 100644 index 000000000000..efb609510540 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/tcm-sita.c @@ -0,0 +1,703 @@ +/* + * tcm-sita.c + * + * SImple Tiler Allocator (SiTA): 2D and 1D allocation(reservation) algorithm + * + * Authors: Ravi Ramachandra <r.ramachandra@ti.com>, + * Lajos Molnar <molnar@ti.com> + * + * Copyright (C) 2009-2010 Texas Instruments, Inc. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include "tcm-sita.h" + +#define ALIGN_DOWN(value, align) ((value) & ~((align) - 1)) + +/* Individual selection criteria for different scan areas */ +static s32 CR_L2R_T2B = CR_BIAS_HORIZONTAL; +static s32 CR_R2L_T2B = CR_DIAGONAL_BALANCE; + +/********************************************* + * TCM API - Sita Implementation + *********************************************/ +static s32 sita_reserve_2d(struct tcm *tcm, u16 h, u16 w, u8 align, + struct tcm_area *area); +static s32 sita_reserve_1d(struct tcm *tcm, u32 slots, struct tcm_area *area); +static s32 sita_free(struct tcm *tcm, struct tcm_area *area); +static void sita_deinit(struct tcm *tcm); + +/********************************************* + * Main Scanner functions + *********************************************/ +static s32 scan_areas_and_find_fit(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *area); + +static s32 scan_l2r_t2b(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *field, struct tcm_area *area); + +static s32 scan_r2l_t2b(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *field, struct tcm_area *area); + +static s32 scan_r2l_b2t_one_dim(struct tcm *tcm, u32 num_slots, + struct tcm_area *field, struct tcm_area *area); + +/********************************************* + * Support Infrastructure Methods + *********************************************/ +static s32 is_area_free(struct tcm_area ***map, u16 x0, u16 y0, u16 w, u16 h); + +static s32 update_candidate(struct tcm *tcm, u16 x0, u16 y0, u16 w, u16 h, + struct tcm_area *field, s32 criteria, + struct score *best); + +static void get_nearness_factor(struct tcm_area *field, + struct tcm_area *candidate, + struct nearness_factor *nf); + +static void get_neighbor_stats(struct tcm *tcm, struct tcm_area *area, + struct neighbor_stats *stat); + +static void fill_area(struct tcm *tcm, + struct tcm_area *area, struct tcm_area *parent); + + +/*********************************************/ + +/********************************************* + * Utility Methods + *********************************************/ +struct tcm *sita_init(u16 width, u16 height, struct tcm_pt *attr) +{ + struct tcm *tcm; + struct sita_pvt *pvt; + struct tcm_area area = {0}; + s32 i; + + if (width == 0 || height == 0) + return NULL; + + tcm = kmalloc(sizeof(*tcm), GFP_KERNEL); + pvt = kmalloc(sizeof(*pvt), GFP_KERNEL); + if (!tcm || !pvt) + goto error; + + memset(tcm, 0, sizeof(*tcm)); + memset(pvt, 0, sizeof(*pvt)); + + /* Updating the pointers to SiTA implementation APIs */ + tcm->height = height; + tcm->width = width; + tcm->reserve_2d = sita_reserve_2d; + tcm->reserve_1d = sita_reserve_1d; + tcm->free = sita_free; + tcm->deinit = sita_deinit; + tcm->pvt = (void *)pvt; + + spin_lock_init(&(pvt->lock)); + + /* Creating tam map */ + pvt->map = kmalloc(sizeof(*pvt->map) * tcm->width, GFP_KERNEL); + if (!pvt->map) + goto error; + + for (i = 0; i < tcm->width; i++) { + pvt->map[i] = + kmalloc(sizeof(**pvt->map) * tcm->height, + GFP_KERNEL); + if (pvt->map[i] == NULL) { + while (i--) + kfree(pvt->map[i]); + kfree(pvt->map); + goto error; + } + } + + if (attr && attr->x <= tcm->width && attr->y <= tcm->height) { + pvt->div_pt.x = attr->x; + pvt->div_pt.y = attr->y; + + } else { + /* Defaulting to 3:1 ratio on width for 2D area split */ + /* Defaulting to 3:1 ratio on height for 2D and 1D split */ + pvt->div_pt.x = (tcm->width * 3) / 4; + pvt->div_pt.y = (tcm->height * 3) / 4; + } + + spin_lock(&(pvt->lock)); + assign(&area, 0, 0, width - 1, height - 1); + fill_area(tcm, &area, NULL); + spin_unlock(&(pvt->lock)); + return tcm; + +error: + kfree(tcm); + kfree(pvt); + return NULL; +} + +static void sita_deinit(struct tcm *tcm) +{ + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + struct tcm_area area = {0}; + s32 i; + + area.p1.x = tcm->width - 1; + area.p1.y = tcm->height - 1; + + spin_lock(&(pvt->lock)); + fill_area(tcm, &area, NULL); + spin_unlock(&(pvt->lock)); + + for (i = 0; i < tcm->height; i++) + kfree(pvt->map[i]); + kfree(pvt->map); + kfree(pvt); +} + +/** + * Reserve a 1D area in the container + * + * @param num_slots size of 1D area + * @param area pointer to the area that will be populated with the + * reserved area + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 sita_reserve_1d(struct tcm *tcm, u32 num_slots, + struct tcm_area *area) +{ + s32 ret; + struct tcm_area field = {0}; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + spin_lock(&(pvt->lock)); + + /* Scanning entire container */ + assign(&field, tcm->width - 1, tcm->height - 1, 0, 0); + + ret = scan_r2l_b2t_one_dim(tcm, num_slots, &field, area); + if (!ret) + /* update map */ + fill_area(tcm, area, area); + + spin_unlock(&(pvt->lock)); + return ret; +} + +/** + * Reserve a 2D area in the container + * + * @param w width + * @param h height + * @param area pointer to the area that will be populated with the reserved + * area + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 sita_reserve_2d(struct tcm *tcm, u16 h, u16 w, u8 align, + struct tcm_area *area) +{ + s32 ret; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + /* not supporting more than 64 as alignment */ + if (align > 64) + return -EINVAL; + + /* we prefer 1, 32 and 64 as alignment */ + align = align <= 1 ? 1 : align <= 32 ? 32 : 64; + + spin_lock(&(pvt->lock)); + ret = scan_areas_and_find_fit(tcm, w, h, align, area); + if (!ret) + /* update map */ + fill_area(tcm, area, area); + + spin_unlock(&(pvt->lock)); + return ret; +} + +/** + * Unreserve a previously allocated 2D or 1D area + * @param area area to be freed + * @return 0 - success + */ +static s32 sita_free(struct tcm *tcm, struct tcm_area *area) +{ + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + spin_lock(&(pvt->lock)); + + /* check that this is in fact an existing area */ + WARN_ON(pvt->map[area->p0.x][area->p0.y] != area || + pvt->map[area->p1.x][area->p1.y] != area); + + /* Clear the contents of the associated tiles in the map */ + fill_area(tcm, area, NULL); + + spin_unlock(&(pvt->lock)); + + return 0; +} + +/** + * Note: In general the cordinates in the scan field area relevant to the can + * sweep directions. The scan origin (e.g. top-left corner) will always be + * the p0 member of the field. Therfore, for a scan from top-left p0.x <= p1.x + * and p0.y <= p1.y; whereas, for a scan from bottom-right p1.x <= p0.x and p1.y + * <= p0.y + */ + +/** + * Raster scan horizontally right to left from top to bottom to find a place for + * a 2D area of given size inside a scan field. + * + * @param w width of desired area + * @param h height of desired area + * @param align desired area alignment + * @param area pointer to the area that will be set to the best position + * @param field area to scan (inclusive) + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 scan_r2l_t2b(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *field, struct tcm_area *area) +{ + s32 x, y; + s16 start_x, end_x, start_y, end_y, found_x = -1; + struct tcm_area ***map = ((struct sita_pvt *)tcm->pvt)->map; + struct score best = {{0}, {0}, {0}, 0}; + + start_x = field->p0.x; + end_x = field->p1.x; + start_y = field->p0.y; + end_y = field->p1.y; + + /* check scan area co-ordinates */ + if (field->p0.x < field->p1.x || + field->p1.y < field->p0.y) + return -EINVAL; + + /* check if allocation would fit in scan area */ + if (w > LEN(start_x, end_x) || h > LEN(end_y, start_y)) + return -ENOSPC; + + /* adjust start_x and end_y, as allocation would not fit beyond */ + start_x = ALIGN_DOWN(start_x - w + 1, align); /* - 1 to be inclusive */ + end_y = end_y - h + 1; + + /* check if allocation would still fit in scan area */ + if (start_x < end_x) + return -ENOSPC; + + /* scan field top-to-bottom, right-to-left */ + for (y = start_y; y <= end_y; y++) { + for (x = start_x; x >= end_x; x -= align) { + if (is_area_free(map, x, y, w, h)) { + found_x = x; + + /* update best candidate */ + if (update_candidate(tcm, x, y, w, h, field, + CR_R2L_T2B, &best)) + goto done; + + /* change upper x bound */ + end_x = x + 1; + break; + } else if (map[x][y] && map[x][y]->is2d) { + /* step over 2D areas */ + x = ALIGN(map[x][y]->p0.x - w + 1, align); + } + } + + /* break if you find a free area shouldering the scan field */ + if (found_x == start_x) + break; + } + + if (!best.a.tcm) + return -ENOSPC; +done: + assign(area, best.a.p0.x, best.a.p0.y, best.a.p1.x, best.a.p1.y); + return 0; +} + +/** + * Raster scan horizontally left to right from top to bottom to find a place for + * a 2D area of given size inside a scan field. + * + * @param w width of desired area + * @param h height of desired area + * @param align desired area alignment + * @param area pointer to the area that will be set to the best position + * @param field area to scan (inclusive) + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 scan_l2r_t2b(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *field, struct tcm_area *area) +{ + s32 x, y; + s16 start_x, end_x, start_y, end_y, found_x = -1; + struct tcm_area ***map = ((struct sita_pvt *)tcm->pvt)->map; + struct score best = {{0}, {0}, {0}, 0}; + + start_x = field->p0.x; + end_x = field->p1.x; + start_y = field->p0.y; + end_y = field->p1.y; + + /* check scan area co-ordinates */ + if (field->p1.x < field->p0.x || + field->p1.y < field->p0.y) + return -EINVAL; + + /* check if allocation would fit in scan area */ + if (w > LEN(end_x, start_x) || h > LEN(end_y, start_y)) + return -ENOSPC; + + start_x = ALIGN(start_x, align); + + /* check if allocation would still fit in scan area */ + if (w > LEN(end_x, start_x)) + return -ENOSPC; + + /* adjust end_x and end_y, as allocation would not fit beyond */ + end_x = end_x - w + 1; /* + 1 to be inclusive */ + end_y = end_y - h + 1; + + /* scan field top-to-bottom, left-to-right */ + for (y = start_y; y <= end_y; y++) { + for (x = start_x; x <= end_x; x += align) { + if (is_area_free(map, x, y, w, h)) { + found_x = x; + + /* update best candidate */ + if (update_candidate(tcm, x, y, w, h, field, + CR_L2R_T2B, &best)) + goto done; + /* change upper x bound */ + end_x = x - 1; + + break; + } else if (map[x][y] && map[x][y]->is2d) { + /* step over 2D areas */ + x = ALIGN_DOWN(map[x][y]->p1.x, align); + } + } + + /* break if you find a free area shouldering the scan field */ + if (found_x == start_x) + break; + } + + if (!best.a.tcm) + return -ENOSPC; +done: + assign(area, best.a.p0.x, best.a.p0.y, best.a.p1.x, best.a.p1.y); + return 0; +} + +/** + * Raster scan horizontally right to left from bottom to top to find a place + * for a 1D area of given size inside a scan field. + * + * @param num_slots size of desired area + * @param align desired area alignment + * @param area pointer to the area that will be set to the best + * position + * @param field area to scan (inclusive) + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 scan_r2l_b2t_one_dim(struct tcm *tcm, u32 num_slots, + struct tcm_area *field, struct tcm_area *area) +{ + s32 found = 0; + s16 x, y; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + struct tcm_area *p; + + /* check scan area co-ordinates */ + if (field->p0.y < field->p1.y) + return -EINVAL; + + /** + * Currently we only support full width 1D scan field, which makes sense + * since 1D slot-ordering spans the full container width. + */ + if (tcm->width != field->p0.x - field->p1.x + 1) + return -EINVAL; + + /* check if allocation would fit in scan area */ + if (num_slots > tcm->width * LEN(field->p0.y, field->p1.y)) + return -ENOSPC; + + x = field->p0.x; + y = field->p0.y; + + /* find num_slots consecutive free slots to the left */ + while (found < num_slots) { + if (y < 0) + return -ENOSPC; + + /* remember bottom-right corner */ + if (found == 0) { + area->p1.x = x; + area->p1.y = y; + } + + /* skip busy regions */ + p = pvt->map[x][y]; + if (p) { + /* move to left of 2D areas, top left of 1D */ + x = p->p0.x; + if (!p->is2d) + y = p->p0.y; + + /* start over */ + found = 0; + } else { + /* count consecutive free slots */ + found++; + if (found == num_slots) + break; + } + + /* move to the left */ + if (x == 0) + y--; + x = (x ? : tcm->width) - 1; + + } + + /* set top-left corner */ + area->p0.x = x; + area->p0.y = y; + return 0; +} + +/** + * Find a place for a 2D area of given size inside a scan field based on its + * alignment needs. + * + * @param w width of desired area + * @param h height of desired area + * @param align desired area alignment + * @param area pointer to the area that will be set to the best position + * + * @return 0 on success, non-0 error value on failure. + */ +static s32 scan_areas_and_find_fit(struct tcm *tcm, u16 w, u16 h, u16 align, + struct tcm_area *area) +{ + s32 ret = 0; + struct tcm_area field = {0}; + u16 boundary_x, boundary_y; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + if (align > 1) { + /* prefer top-left corner */ + boundary_x = pvt->div_pt.x - 1; + boundary_y = pvt->div_pt.y - 1; + + /* expand width and height if needed */ + if (w > pvt->div_pt.x) + boundary_x = tcm->width - 1; + if (h > pvt->div_pt.y) + boundary_y = tcm->height - 1; + + assign(&field, 0, 0, boundary_x, boundary_y); + ret = scan_l2r_t2b(tcm, w, h, align, &field, area); + + /* scan whole container if failed, but do not scan 2x */ + if (ret != 0 && (boundary_x != tcm->width - 1 || + boundary_y != tcm->height - 1)) { + /* scan the entire container if nothing found */ + assign(&field, 0, 0, tcm->width - 1, tcm->height - 1); + ret = scan_l2r_t2b(tcm, w, h, align, &field, area); + } + } else if (align == 1) { + /* prefer top-right corner */ + boundary_x = pvt->div_pt.x; + boundary_y = pvt->div_pt.y - 1; + + /* expand width and height if needed */ + if (w > (tcm->width - pvt->div_pt.x)) + boundary_x = 0; + if (h > pvt->div_pt.y) + boundary_y = tcm->height - 1; + + assign(&field, tcm->width - 1, 0, boundary_x, boundary_y); + ret = scan_r2l_t2b(tcm, w, h, align, &field, area); + + /* scan whole container if failed, but do not scan 2x */ + if (ret != 0 && (boundary_x != 0 || + boundary_y != tcm->height - 1)) { + /* scan the entire container if nothing found */ + assign(&field, tcm->width - 1, 0, 0, tcm->height - 1); + ret = scan_r2l_t2b(tcm, w, h, align, &field, + area); + } + } + + return ret; +} + +/* check if an entire area is free */ +static s32 is_area_free(struct tcm_area ***map, u16 x0, u16 y0, u16 w, u16 h) +{ + u16 x = 0, y = 0; + for (y = y0; y < y0 + h; y++) { + for (x = x0; x < x0 + w; x++) { + if (map[x][y]) + return false; + } + } + return true; +} + +/* fills an area with a parent tcm_area */ +static void fill_area(struct tcm *tcm, struct tcm_area *area, + struct tcm_area *parent) +{ + s32 x, y; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + struct tcm_area a, a_; + + /* set area's tcm; otherwise, enumerator considers it invalid */ + area->tcm = tcm; + + tcm_for_each_slice(a, *area, a_) { + for (x = a.p0.x; x <= a.p1.x; ++x) + for (y = a.p0.y; y <= a.p1.y; ++y) + pvt->map[x][y] = parent; + + } +} + +/** + * Compares a candidate area to the current best area, and if it is a better + * fit, it updates the best to this one. + * + * @param x0, y0, w, h top, left, width, height of candidate area + * @param field scan field + * @param criteria scan criteria + * @param best best candidate and its scores + * + * @return 1 (true) if the candidate area is known to be the final best, so no + * more searching should be performed + */ +static s32 update_candidate(struct tcm *tcm, u16 x0, u16 y0, u16 w, u16 h, + struct tcm_area *field, s32 criteria, + struct score *best) +{ + struct score me; /* score for area */ + + /* + * NOTE: For horizontal bias we always give the first found, because our + * scan is horizontal-raster-based and the first candidate will always + * have the horizontal bias. + */ + bool first = criteria & CR_BIAS_HORIZONTAL; + + assign(&me.a, x0, y0, x0 + w - 1, y0 + h - 1); + + /* calculate score for current candidate */ + if (!first) { + get_neighbor_stats(tcm, &me.a, &me.n); + me.neighs = me.n.edge + me.n.busy; + get_nearness_factor(field, &me.a, &me.f); + } + + /* the 1st candidate is always the best */ + if (!best->a.tcm) + goto better; + + BUG_ON(first); + + /* diagonal balance check */ + if ((criteria & CR_DIAGONAL_BALANCE) && + best->neighs <= me.neighs && + (best->neighs < me.neighs || + /* this implies that neighs and occupied match */ + best->n.busy < me.n.busy || + (best->n.busy == me.n.busy && + /* check the nearness factor */ + best->f.x + best->f.y > me.f.x + me.f.y))) + goto better; + + /* not better, keep going */ + return 0; + +better: + /* save current area as best */ + memcpy(best, &me, sizeof(me)); + best->a.tcm = tcm; + return first; +} + +/** + * Calculate the nearness factor of an area in a search field. The nearness + * factor is smaller if the area is closer to the search origin. + */ +static void get_nearness_factor(struct tcm_area *field, struct tcm_area *area, + struct nearness_factor *nf) +{ + /** + * Using signed math as field coordinates may be reversed if + * search direction is right-to-left or bottom-to-top. + */ + nf->x = (s32)(area->p0.x - field->p0.x) * 1000 / + (field->p1.x - field->p0.x); + nf->y = (s32)(area->p0.y - field->p0.y) * 1000 / + (field->p1.y - field->p0.y); +} + +/* get neighbor statistics */ +static void get_neighbor_stats(struct tcm *tcm, struct tcm_area *area, + struct neighbor_stats *stat) +{ + s16 x = 0, y = 0; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + /* Clearing any exisiting values */ + memset(stat, 0, sizeof(*stat)); + + /* process top & bottom edges */ + for (x = area->p0.x; x <= area->p1.x; x++) { + if (area->p0.y == 0) + stat->edge++; + else if (pvt->map[x][area->p0.y - 1]) + stat->busy++; + + if (area->p1.y == tcm->height - 1) + stat->edge++; + else if (pvt->map[x][area->p1.y + 1]) + stat->busy++; + } + + /* process left & right edges */ + for (y = area->p0.y; y <= area->p1.y; ++y) { + if (area->p0.x == 0) + stat->edge++; + else if (pvt->map[area->p0.x - 1][y]) + stat->busy++; + + if (area->p1.x == tcm->width - 1) + stat->edge++; + else if (pvt->map[area->p1.x + 1][y]) + stat->busy++; + } +} diff --git a/drivers/gpu/drm/omapdrm/tcm-sita.h b/drivers/gpu/drm/omapdrm/tcm-sita.h new file mode 100644 index 000000000000..0444f868671c --- /dev/null +++ b/drivers/gpu/drm/omapdrm/tcm-sita.h @@ -0,0 +1,95 @@ +/* + * tcm_sita.h + * + * SImple Tiler Allocator (SiTA) private structures. + * + * Author: Ravi Ramachandra <r.ramachandra@ti.com> + * + * Copyright (C) 2009-2011 Texas Instruments, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TCM_SITA_H +#define _TCM_SITA_H + +#include "tcm.h" + +/* length between two coordinates */ +#define LEN(a, b) ((a) > (b) ? (a) - (b) + 1 : (b) - (a) + 1) + +enum criteria { + CR_MAX_NEIGHS = 0x01, + CR_FIRST_FOUND = 0x10, + CR_BIAS_HORIZONTAL = 0x20, + CR_BIAS_VERTICAL = 0x40, + CR_DIAGONAL_BALANCE = 0x80 +}; + +/* nearness to the beginning of the search field from 0 to 1000 */ +struct nearness_factor { + s32 x; + s32 y; +}; + +/* + * Statistics on immediately neighboring slots. Edge is the number of + * border segments that are also border segments of the scan field. Busy + * refers to the number of neighbors that are occupied. + */ +struct neighbor_stats { + u16 edge; + u16 busy; +}; + +/* structure to keep the score of a potential allocation */ +struct score { + struct nearness_factor f; + struct neighbor_stats n; + struct tcm_area a; + u16 neighs; /* number of busy neighbors */ +}; + +struct sita_pvt { + spinlock_t lock; /* spinlock to protect access */ + struct tcm_pt div_pt; /* divider point splitting container */ + struct tcm_area ***map; /* pointers to the parent area for each slot */ +}; + +/* assign coordinates to area */ +static inline +void assign(struct tcm_area *a, u16 x0, u16 y0, u16 x1, u16 y1) +{ + a->p0.x = x0; + a->p0.y = y0; + a->p1.x = x1; + a->p1.y = y1; +} + +#endif diff --git a/drivers/gpu/drm/omapdrm/tcm.h b/drivers/gpu/drm/omapdrm/tcm.h new file mode 100644 index 000000000000..a8d5ce47686f --- /dev/null +++ b/drivers/gpu/drm/omapdrm/tcm.h @@ -0,0 +1,328 @@ +/* + * tcm.h + * + * TILER container manager specification and support functions for TI + * TILER driver. + * + * Author: Lajos Molnar <molnar@ti.com> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TCM_H +#define TCM_H + +struct tcm; + +/* point */ +struct tcm_pt { + u16 x; + u16 y; +}; + +/* 1d or 2d area */ +struct tcm_area { + bool is2d; /* whether area is 1d or 2d */ + struct tcm *tcm; /* parent */ + struct tcm_pt p0; + struct tcm_pt p1; +}; + +struct tcm { + u16 width, height; /* container dimensions */ + int lut_id; /* Lookup table identifier */ + + unsigned int y_offset; /* offset to use for y coordinates */ + + /* 'pvt' structure shall contain any tcm details (attr) along with + linked list of allocated areas and mutex for mutually exclusive access + to the list. It may also contain copies of width and height to notice + any changes to the publicly available width and height fields. */ + void *pvt; + + /* function table */ + s32 (*reserve_2d)(struct tcm *tcm, u16 height, u16 width, u8 align, + struct tcm_area *area); + s32 (*reserve_1d)(struct tcm *tcm, u32 slots, struct tcm_area *area); + s32 (*free) (struct tcm *tcm, struct tcm_area *area); + void (*deinit) (struct tcm *tcm); +}; + +/*============================================================================= + BASIC TILER CONTAINER MANAGER INTERFACE +=============================================================================*/ + +/* + * NOTE: + * + * Since some basic parameter checking is done outside the TCM algorithms, + * TCM implementation do NOT have to check the following: + * + * area pointer is NULL + * width and height fits within container + * number of pages is more than the size of the container + * + */ + +struct tcm *sita_init(u16 width, u16 height, struct tcm_pt *attr); + + +/** + * Deinitialize tiler container manager. + * + * @param tcm Pointer to container manager. + * + * @return 0 on success, non-0 error value on error. The call + * should free as much memory as possible and meaningful + * even on failure. Some error codes: -ENODEV: invalid + * manager. + */ +static inline void tcm_deinit(struct tcm *tcm) +{ + if (tcm) + tcm->deinit(tcm); +} + +/** + * Reserves a 2D area in the container. + * + * @param tcm Pointer to container manager. + * @param height Height(in pages) of area to be reserved. + * @param width Width(in pages) of area to be reserved. + * @param align Alignment requirement for top-left corner of area. Not + * all values may be supported by the container manager, + * but it must support 0 (1), 32 and 64. + * 0 value is equivalent to 1. + * @param area Pointer to where the reserved area should be stored. + * + * @return 0 on success. Non-0 error code on failure. Also, + * the tcm field of the area will be set to NULL on + * failure. Some error codes: -ENODEV: invalid manager, + * -EINVAL: invalid area, -ENOMEM: not enough space for + * allocation. + */ +static inline s32 tcm_reserve_2d(struct tcm *tcm, u16 width, u16 height, + u16 align, struct tcm_area *area) +{ + /* perform rudimentary error checking */ + s32 res = tcm == NULL ? -ENODEV : + (area == NULL || width == 0 || height == 0 || + /* align must be a 2 power */ + (align & (align - 1))) ? -EINVAL : + (height > tcm->height || width > tcm->width) ? -ENOMEM : 0; + + if (!res) { + area->is2d = true; + res = tcm->reserve_2d(tcm, height, width, align, area); + area->tcm = res ? NULL : tcm; + } + + return res; +} + +/** + * Reserves a 1D area in the container. + * + * @param tcm Pointer to container manager. + * @param slots Number of (contiguous) slots to reserve. + * @param area Pointer to where the reserved area should be stored. + * + * @return 0 on success. Non-0 error code on failure. Also, + * the tcm field of the area will be set to NULL on + * failure. Some error codes: -ENODEV: invalid manager, + * -EINVAL: invalid area, -ENOMEM: not enough space for + * allocation. + */ +static inline s32 tcm_reserve_1d(struct tcm *tcm, u32 slots, + struct tcm_area *area) +{ + /* perform rudimentary error checking */ + s32 res = tcm == NULL ? -ENODEV : + (area == NULL || slots == 0) ? -EINVAL : + slots > (tcm->width * (u32) tcm->height) ? -ENOMEM : 0; + + if (!res) { + area->is2d = false; + res = tcm->reserve_1d(tcm, slots, area); + area->tcm = res ? NULL : tcm; + } + + return res; +} + +/** + * Free a previously reserved area from the container. + * + * @param area Pointer to area reserved by a prior call to + * tcm_reserve_1d or tcm_reserve_2d call, whether + * it was successful or not. (Note: all fields of + * the structure must match.) + * + * @return 0 on success. Non-0 error code on failure. Also, the tcm + * field of the area is set to NULL on success to avoid subsequent + * freeing. This call will succeed even if supplying + * the area from a failed reserved call. + */ +static inline s32 tcm_free(struct tcm_area *area) +{ + s32 res = 0; /* free succeeds by default */ + + if (area && area->tcm) { + res = area->tcm->free(area->tcm, area); + if (res == 0) + area->tcm = NULL; + } + + return res; +} + +/*============================================================================= + HELPER FUNCTION FOR ANY TILER CONTAINER MANAGER +=============================================================================*/ + +/** + * This method slices off the topmost 2D slice from the parent area, and stores + * it in the 'slice' parameter. The 'parent' parameter will get modified to + * contain the remaining portion of the area. If the whole parent area can + * fit in a 2D slice, its tcm pointer is set to NULL to mark that it is no + * longer a valid area. + * + * @param parent Pointer to a VALID parent area that will get modified + * @param slice Pointer to the slice area that will get modified + */ +static inline void tcm_slice(struct tcm_area *parent, struct tcm_area *slice) +{ + *slice = *parent; + + /* check if we need to slice */ + if (slice->tcm && !slice->is2d && + slice->p0.y != slice->p1.y && + (slice->p0.x || (slice->p1.x != slice->tcm->width - 1))) { + /* set end point of slice (start always remains) */ + slice->p1.x = slice->tcm->width - 1; + slice->p1.y = (slice->p0.x) ? slice->p0.y : slice->p1.y - 1; + /* adjust remaining area */ + parent->p0.x = 0; + parent->p0.y = slice->p1.y + 1; + } else { + /* mark this as the last slice */ + parent->tcm = NULL; + } +} + +/* Verify if a tcm area is logically valid */ +static inline bool tcm_area_is_valid(struct tcm_area *area) +{ + return area && area->tcm && + /* coordinate bounds */ + area->p1.x < area->tcm->width && + area->p1.y < area->tcm->height && + area->p0.y <= area->p1.y && + /* 1D coordinate relationship + p0.x check */ + ((!area->is2d && + area->p0.x < area->tcm->width && + area->p0.x + area->p0.y * area->tcm->width <= + area->p1.x + area->p1.y * area->tcm->width) || + /* 2D coordinate relationship */ + (area->is2d && + area->p0.x <= area->p1.x)); +} + +/* see if a coordinate is within an area */ +static inline bool __tcm_is_in(struct tcm_pt *p, struct tcm_area *a) +{ + u16 i; + + if (a->is2d) { + return p->x >= a->p0.x && p->x <= a->p1.x && + p->y >= a->p0.y && p->y <= a->p1.y; + } else { + i = p->x + p->y * a->tcm->width; + return i >= a->p0.x + a->p0.y * a->tcm->width && + i <= a->p1.x + a->p1.y * a->tcm->width; + } +} + +/* calculate area width */ +static inline u16 __tcm_area_width(struct tcm_area *area) +{ + return area->p1.x - area->p0.x + 1; +} + +/* calculate area height */ +static inline u16 __tcm_area_height(struct tcm_area *area) +{ + return area->p1.y - area->p0.y + 1; +} + +/* calculate number of slots in an area */ +static inline u16 __tcm_sizeof(struct tcm_area *area) +{ + return area->is2d ? + __tcm_area_width(area) * __tcm_area_height(area) : + (area->p1.x - area->p0.x + 1) + (area->p1.y - area->p0.y) * + area->tcm->width; +} +#define tcm_sizeof(area) __tcm_sizeof(&(area)) +#define tcm_awidth(area) __tcm_area_width(&(area)) +#define tcm_aheight(area) __tcm_area_height(&(area)) +#define tcm_is_in(pt, area) __tcm_is_in(&(pt), &(area)) + +/* limit a 1D area to the first N pages */ +static inline s32 tcm_1d_limit(struct tcm_area *a, u32 num_pg) +{ + if (__tcm_sizeof(a) < num_pg) + return -ENOMEM; + if (!num_pg) + return -EINVAL; + + a->p1.x = (a->p0.x + num_pg - 1) % a->tcm->width; + a->p1.y = a->p0.y + ((a->p0.x + num_pg - 1) / a->tcm->width); + return 0; +} + +/** + * Iterate through 2D slices of a valid area. Behaves + * syntactically as a for(;;) statement. + * + * @param var Name of a local variable of type 'struct + * tcm_area *' that will get modified to + * contain each slice. + * @param area Pointer to the VALID parent area. This + * structure will not get modified + * throughout the loop. + * + */ +#define tcm_for_each_slice(var, area, safe) \ + for (safe = area, \ + tcm_slice(&safe, &var); \ + var.tcm; tcm_slice(&safe, &var)) + +#endif diff --git a/drivers/gpu/drm/radeon/atom.c b/drivers/gpu/drm/radeon/atom.c index 5ce9bf51a8de..46a9c3772850 100644 --- a/drivers/gpu/drm/radeon/atom.c +++ b/drivers/gpu/drm/radeon/atom.c @@ -1238,6 +1238,8 @@ static int atom_iio_len[] = { 1, 2, 3, 3, 3, 3, 4, 4, 4, 3 }; static void atom_index_iio(struct atom_context *ctx, int base) { ctx->iio = kzalloc(2 * 256, GFP_KERNEL); + if (!ctx->iio) + return; while (CU8(base) == ATOM_IIO_START) { ctx->iio[CU8(base + 1)] = base + 2; base += 2; @@ -1287,6 +1289,10 @@ struct atom_context *atom_parse(struct card_info *card, void *bios) ctx->cmd_table = CU16(base + ATOM_ROM_CMD_PTR); ctx->data_table = CU16(base + ATOM_ROM_DATA_PTR); atom_index_iio(ctx, CU16(ctx->data_table + ATOM_DATA_IIO_PTR) + 4); + if (!ctx->iio) { + atom_destroy(ctx); + return NULL; + } str = CSTR(CU16(base + ATOM_ROM_MSG_PTR)); while (*str && ((*str == '\n') || (*str == '\r'))) @@ -1335,8 +1341,7 @@ int atom_asic_init(struct atom_context *ctx) void atom_destroy(struct atom_context *ctx) { - if (ctx->iio) - kfree(ctx->iio); + kfree(ctx->iio); kfree(ctx); } diff --git a/drivers/gpu/drm/radeon/evergreen.c b/drivers/gpu/drm/radeon/evergreen.c index 2916de896a60..3c38ea46531c 100644 --- a/drivers/gpu/drm/radeon/evergreen.c +++ b/drivers/gpu/drm/radeon/evergreen.c @@ -403,6 +403,19 @@ void evergreen_pm_misc(struct radeon_device *rdev) rdev->pm.current_vddc = voltage->voltage; DRM_DEBUG("Setting: vddc: %d\n", voltage->voltage); } + + /* starting with BTC, there is one state that is used for both + * MH and SH. Difference is that we always use the high clock index for + * mclk and vddci. + */ + if ((rdev->pm.pm_method == PM_METHOD_PROFILE) && + (rdev->family >= CHIP_BARTS) && + rdev->pm.active_crtc_count && + ((rdev->pm.profile_index == PM_PROFILE_MID_MH_IDX) || + (rdev->pm.profile_index == PM_PROFILE_LOW_MH_IDX))) + voltage = &rdev->pm.power_state[req_ps_idx]. + clock_info[rdev->pm.profiles[PM_PROFILE_HIGH_MH_IDX].dpms_on_cm_idx].voltage; + /* 0xff01 is a flag rather then an actual voltage */ if (voltage->vddci == 0xff01) return; diff --git a/drivers/gpu/drm/radeon/r600.c b/drivers/gpu/drm/radeon/r600.c index dbcb0752f083..6d4b5611daf4 100644 --- a/drivers/gpu/drm/radeon/r600.c +++ b/drivers/gpu/drm/radeon/r600.c @@ -109,6 +109,19 @@ void r600_fini(struct radeon_device *rdev); void r600_irq_disable(struct radeon_device *rdev); static void r600_pcie_gen2_enable(struct radeon_device *rdev); +/** + * r600_get_xclk - get the xclk + * + * @rdev: radeon_device pointer + * + * Returns the reference clock used by the gfx engine + * (r6xx, IGPs, APUs). + */ +u32 r600_get_xclk(struct radeon_device *rdev) +{ + return rdev->clock.spll.reference_freq; +} + /* get temperature in millidegrees */ int rv6xx_get_temp(struct radeon_device *rdev) { @@ -4448,14 +4461,14 @@ static void r600_pcie_gen2_enable(struct radeon_device *rdev) } /** - * r600_get_gpu_clock - return GPU clock counter snapshot + * r600_get_gpu_clock_counter - return GPU clock counter snapshot * * @rdev: radeon_device pointer * * Fetches a GPU clock counter snapshot (R6xx-cayman). * Returns the 64 bit clock counter snapshot. */ -uint64_t r600_get_gpu_clock(struct radeon_device *rdev) +uint64_t r600_get_gpu_clock_counter(struct radeon_device *rdev) { uint64_t clock; diff --git a/drivers/gpu/drm/radeon/r600_hdmi.c b/drivers/gpu/drm/radeon/r600_hdmi.c index ff80efe9cb7d..95970ec47c45 100644 --- a/drivers/gpu/drm/radeon/r600_hdmi.c +++ b/drivers/gpu/drm/radeon/r600_hdmi.c @@ -544,7 +544,6 @@ void r600_hdmi_disable(struct drm_encoder *encoder) /* Called for ATOM_ENCODER_MODE_HDMI only */ if (!dig || !dig->afmt) { - WARN_ON(1); return; } if (!dig->afmt->enabled) diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h index bb43a849759b..8263af3fd832 100644 --- a/drivers/gpu/drm/radeon/radeon.h +++ b/drivers/gpu/drm/radeon/radeon.h @@ -1178,6 +1178,10 @@ struct radeon_asic { bool (*gui_idle)(struct radeon_device *rdev); /* wait for mc_idle */ int (*mc_wait_for_idle)(struct radeon_device *rdev); + /* get the reference clock */ + u32 (*get_xclk)(struct radeon_device *rdev); + /* get the gpu clock counter */ + uint64_t (*get_gpu_clock_counter)(struct radeon_device *rdev); /* gart */ struct { void (*tlb_flush)(struct radeon_device *rdev); @@ -1859,6 +1863,8 @@ void radeon_ring_write(struct radeon_ring *ring, uint32_t v); #define radeon_post_page_flip(rdev, crtc) (rdev)->asic->pflip.post_page_flip((rdev), (crtc)) #define radeon_wait_for_vblank(rdev, crtc) (rdev)->asic->display.wait_for_vblank((rdev), (crtc)) #define radeon_mc_wait_for_idle(rdev) (rdev)->asic->mc_wait_for_idle((rdev)) +#define radeon_get_xclk(rdev) (rdev)->asic->get_xclk((rdev)) +#define radeon_get_gpu_clock_counter(rdev) (rdev)->asic->get_gpu_clock_counter((rdev)) /* Common functions */ /* AGP */ diff --git a/drivers/gpu/drm/radeon/radeon_asic.c b/drivers/gpu/drm/radeon/radeon_asic.c index 67f008febec7..aba0a893ea98 100644 --- a/drivers/gpu/drm/radeon/radeon_asic.c +++ b/drivers/gpu/drm/radeon/radeon_asic.c @@ -934,6 +934,8 @@ static struct radeon_asic r600_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &r600_mc_wait_for_idle, + .get_xclk = &r600_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &r600_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1018,6 +1020,8 @@ static struct radeon_asic rs780_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &r600_mc_wait_for_idle, + .get_xclk = &r600_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &r600_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1102,6 +1106,8 @@ static struct radeon_asic rv770_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &r600_mc_wait_for_idle, + .get_xclk = &rv770_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &r600_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1186,6 +1192,8 @@ static struct radeon_asic evergreen_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &rv770_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &evergreen_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1270,6 +1278,8 @@ static struct radeon_asic sumo_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &r600_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &evergreen_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1354,6 +1364,8 @@ static struct radeon_asic btc_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &rv770_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &evergreen_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1438,6 +1450,8 @@ static struct radeon_asic cayman_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &rv770_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &cayman_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1565,6 +1579,8 @@ static struct radeon_asic trinity_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &r600_get_xclk, + .get_gpu_clock_counter = &r600_get_gpu_clock_counter, .gart = { .tlb_flush = &cayman_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, @@ -1692,6 +1708,8 @@ static struct radeon_asic si_asic = { .ioctl_wait_idle = r600_ioctl_wait_idle, .gui_idle = &r600_gui_idle, .mc_wait_for_idle = &evergreen_mc_wait_for_idle, + .get_xclk = &si_get_xclk, + .get_gpu_clock_counter = &si_get_gpu_clock_counter, .gart = { .tlb_flush = &si_pcie_gart_tlb_flush, .set_page = &rs600_gart_set_page, diff --git a/drivers/gpu/drm/radeon/radeon_asic.h b/drivers/gpu/drm/radeon/radeon_asic.h index f4134a823958..3535f73ad3e2 100644 --- a/drivers/gpu/drm/radeon/radeon_asic.h +++ b/drivers/gpu/drm/radeon/radeon_asic.h @@ -389,7 +389,8 @@ void r600_kms_blit_copy(struct radeon_device *rdev, unsigned num_gpu_pages, struct radeon_sa_bo *vb); int r600_mc_wait_for_idle(struct radeon_device *rdev); -uint64_t r600_get_gpu_clock(struct radeon_device *rdev); +u32 r600_get_xclk(struct radeon_device *rdev); +uint64_t r600_get_gpu_clock_counter(struct radeon_device *rdev); /* * rv770,rv730,rv710,rv740 @@ -407,6 +408,7 @@ int rv770_copy_dma(struct radeon_device *rdev, uint64_t src_offset, uint64_t dst_offset, unsigned num_gpu_pages, struct radeon_fence **fence); +u32 rv770_get_xclk(struct radeon_device *rdev); /* * evergreen @@ -515,11 +517,12 @@ void si_vm_set_page(struct radeon_device *rdev, uint32_t incr, uint32_t flags); void si_vm_flush(struct radeon_device *rdev, int ridx, struct radeon_vm *vm); int si_ib_parse(struct radeon_device *rdev, struct radeon_ib *ib); -uint64_t si_get_gpu_clock(struct radeon_device *rdev); int si_copy_dma(struct radeon_device *rdev, uint64_t src_offset, uint64_t dst_offset, unsigned num_gpu_pages, struct radeon_fence **fence); void si_dma_vm_flush(struct radeon_device *rdev, int ridx, struct radeon_vm *vm); +u32 si_get_xclk(struct radeon_device *rdev); +uint64_t si_get_gpu_clock_counter(struct radeon_device *rdev); #endif diff --git a/drivers/gpu/drm/radeon/radeon_atpx_handler.c b/drivers/gpu/drm/radeon/radeon_atpx_handler.c index 15f5ded65e0c..d96070bf8388 100644 --- a/drivers/gpu/drm/radeon/radeon_atpx_handler.c +++ b/drivers/gpu/drm/radeon/radeon_atpx_handler.c @@ -43,6 +43,12 @@ struct atpx_verify_interface { u32 function_bits; /* supported functions bit vector */ } __packed; +struct atpx_px_params { + u16 size; /* structure size in bytes (includes size field) */ + u32 valid_flags; /* which flags are valid */ + u32 flags; /* flags */ +} __packed; + struct atpx_power_control { u16 size; u8 dgpu_state; @@ -123,9 +129,61 @@ static void radeon_atpx_parse_functions(struct radeon_atpx_functions *f, u32 mas } /** + * radeon_atpx_validate_functions - validate ATPX functions + * + * @atpx: radeon atpx struct + * + * Validate that required functions are enabled (all asics). + * returns 0 on success, error on failure. + */ +static int radeon_atpx_validate(struct radeon_atpx *atpx) +{ + /* make sure required functions are enabled */ + /* dGPU power control is required */ + atpx->functions.power_cntl = true; + + if (atpx->functions.px_params) { + union acpi_object *info; + struct atpx_px_params output; + size_t size; + u32 valid_bits; + + info = radeon_atpx_call(atpx->handle, ATPX_FUNCTION_GET_PX_PARAMETERS, NULL); + if (!info) + return -EIO; + + memset(&output, 0, sizeof(output)); + + size = *(u16 *) info->buffer.pointer; + if (size < 10) { + printk("ATPX buffer is too small: %zu\n", size); + kfree(info); + return -EINVAL; + } + size = min(sizeof(output), size); + + memcpy(&output, info->buffer.pointer, size); + + valid_bits = output.flags & output.valid_flags; + /* if separate mux flag is set, mux controls are required */ + if (valid_bits & ATPX_SEPARATE_MUX_FOR_I2C) { + atpx->functions.i2c_mux_cntl = true; + atpx->functions.disp_mux_cntl = true; + } + /* if any outputs are muxed, mux controls are required */ + if (valid_bits & (ATPX_CRT1_RGB_SIGNAL_MUXED | + ATPX_TV_SIGNAL_MUXED | + ATPX_DFP_SIGNAL_MUXED)) + atpx->functions.disp_mux_cntl = true; + + kfree(info); + } + return 0; +} + +/** * radeon_atpx_verify_interface - verify ATPX * - * @handle: acpi handle * @atpx: radeon atpx struct * * Execute the ATPX_FUNCTION_VERIFY_INTERFACE ATPX function @@ -406,8 +464,19 @@ static bool radeon_atpx_pci_probe_handle(struct pci_dev *pdev) */ static int radeon_atpx_init(void) { + int r; + /* set up the ATPX handle */ - return radeon_atpx_verify_interface(&radeon_atpx_priv.atpx); + r = radeon_atpx_verify_interface(&radeon_atpx_priv.atpx); + if (r) + return r; + + /* validate the atpx setup */ + r = radeon_atpx_validate(&radeon_atpx_priv.atpx); + if (r) + return r; + + return 0; } /** diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c index 8794de10a6c7..44b8034a400d 100644 --- a/drivers/gpu/drm/radeon/radeon_device.c +++ b/drivers/gpu/drm/radeon/radeon_device.c @@ -759,6 +759,11 @@ int radeon_atombios_init(struct radeon_device *rdev) atom_card_info->pll_write = cail_pll_write; rdev->mode_info.atom_context = atom_parse(atom_card_info, rdev->bios); + if (!rdev->mode_info.atom_context) { + radeon_atombios_fini(rdev); + return -ENOMEM; + } + mutex_init(&rdev->mode_info.atom_context->mutex); radeon_atom_initialize_bios_scratch_regs(rdev->ddev); atom_allocate_fb_scratch(rdev->mode_info.atom_context); @@ -778,9 +783,11 @@ void radeon_atombios_fini(struct radeon_device *rdev) { if (rdev->mode_info.atom_context) { kfree(rdev->mode_info.atom_context->scratch); - kfree(rdev->mode_info.atom_context); } + kfree(rdev->mode_info.atom_context); + rdev->mode_info.atom_context = NULL; kfree(rdev->mode_info.atom_card_info); + rdev->mode_info.atom_card_info = NULL; } /* COMBIOS */ diff --git a/drivers/gpu/drm/radeon/radeon_fb.c b/drivers/gpu/drm/radeon/radeon_fb.c index 515e5ee1f9ee..b1746741bc59 100644 --- a/drivers/gpu/drm/radeon/radeon_fb.c +++ b/drivers/gpu/drm/radeon/radeon_fb.c @@ -187,9 +187,10 @@ out_unref: return ret; } -static int radeonfb_create(struct radeon_fbdev *rfbdev, +static int radeonfb_create(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { + struct radeon_fbdev *rfbdev = (struct radeon_fbdev *)helper; struct radeon_device *rdev = rfbdev->rdev; struct fb_info *info; struct drm_framebuffer *fb = NULL; @@ -300,22 +301,6 @@ out_unref: return ret; } -static int radeon_fb_find_or_create_single(struct drm_fb_helper *helper, - struct drm_fb_helper_surface_size *sizes) -{ - struct radeon_fbdev *rfbdev = (struct radeon_fbdev *)helper; - int new_fb = 0; - int ret; - - if (!helper->fb) { - ret = radeonfb_create(rfbdev, sizes); - if (ret) - return ret; - new_fb = 1; - } - return new_fb; -} - void radeon_fb_output_poll_changed(struct radeon_device *rdev) { drm_fb_helper_hotplug_event(&rdev->mode_info.rfbdev->helper); @@ -349,7 +334,7 @@ static int radeon_fbdev_destroy(struct drm_device *dev, struct radeon_fbdev *rfb static struct drm_fb_helper_funcs radeon_fb_helper_funcs = { .gamma_set = radeon_crtc_fb_gamma_set, .gamma_get = radeon_crtc_fb_gamma_get, - .fb_probe = radeon_fb_find_or_create_single, + .fb_probe = radeonfb_create, }; int radeon_fbdev_init(struct radeon_device *rdev) @@ -379,6 +364,10 @@ int radeon_fbdev_init(struct radeon_device *rdev) } drm_fb_helper_single_add_all_connectors(&rfbdev->helper); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(rdev->ddev); + drm_fb_helper_initial_config(&rfbdev->helper, bpp_sel); return 0; } diff --git a/drivers/gpu/drm/radeon/radeon_kms.c b/drivers/gpu/drm/radeon/radeon_kms.c index 9c312f9afb68..c75cb2c6ba71 100644 --- a/drivers/gpu/drm/radeon/radeon_kms.c +++ b/drivers/gpu/drm/radeon/radeon_kms.c @@ -185,11 +185,7 @@ int radeon_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) if (info->request == RADEON_INFO_TIMESTAMP) { if (rdev->family >= CHIP_R600) { value_ptr64 = (uint64_t*)((unsigned long)info->value); - if (rdev->family >= CHIP_TAHITI) { - value64 = si_get_gpu_clock(rdev); - } else { - value64 = r600_get_gpu_clock(rdev); - } + value64 = radeon_get_gpu_clock_counter(rdev); if (DRM_COPY_TO_USER(value_ptr64, &value64, sizeof(value64))) { DRM_ERROR("copy_to_user %s:%u\n", __func__, __LINE__); @@ -282,7 +278,10 @@ int radeon_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) break; case RADEON_INFO_CLOCK_CRYSTAL_FREQ: /* return clock value in KHz */ - value = rdev->clock.spll.reference_freq * 10; + if (rdev->asic->get_xclk) + value = radeon_get_xclk(rdev) * 10; + else + value = rdev->clock.spll.reference_freq * 10; break; case RADEON_INFO_NUM_BACKENDS: if (rdev->family >= CHIP_TAHITI) diff --git a/drivers/gpu/drm/radeon/radeon_pm.c b/drivers/gpu/drm/radeon/radeon_pm.c index 0bfa656aa87d..338fd6a74e87 100644 --- a/drivers/gpu/drm/radeon/radeon_pm.c +++ b/drivers/gpu/drm/radeon/radeon_pm.c @@ -169,7 +169,7 @@ static void radeon_set_power_state(struct radeon_device *rdev) /* starting with BTC, there is one state that is used for both * MH and SH. Difference is that we always use the high clock index for - * mclk. + * mclk and vddci. */ if ((rdev->pm.pm_method == PM_METHOD_PROFILE) && (rdev->family >= CHIP_BARTS) && diff --git a/drivers/gpu/drm/radeon/radeon_ttm.c b/drivers/gpu/drm/radeon/radeon_ttm.c index 1d8ff2f850ba..93f760e27a92 100644 --- a/drivers/gpu/drm/radeon/radeon_ttm.c +++ b/drivers/gpu/drm/radeon/radeon_ttm.c @@ -38,6 +38,7 @@ #include <drm/radeon_drm.h> #include <linux/seq_file.h> #include <linux/slab.h> +#include <linux/swiotlb.h> #include "radeon_reg.h" #include "radeon.h" diff --git a/drivers/gpu/drm/radeon/rv770.c b/drivers/gpu/drm/radeon/rv770.c index 1b2444f4d8f4..d63fe1d0f53f 100644 --- a/drivers/gpu/drm/radeon/rv770.c +++ b/drivers/gpu/drm/radeon/rv770.c @@ -43,6 +43,31 @@ static void rv770_gpu_init(struct radeon_device *rdev); void rv770_fini(struct radeon_device *rdev); static void rv770_pcie_gen2_enable(struct radeon_device *rdev); +#define PCIE_BUS_CLK 10000 +#define TCLK (PCIE_BUS_CLK / 10) + +/** + * rv770_get_xclk - get the xclk + * + * @rdev: radeon_device pointer + * + * Returns the reference clock used by the gfx engine + * (r7xx-cayman). + */ +u32 rv770_get_xclk(struct radeon_device *rdev) +{ + u32 reference_clock = rdev->clock.spll.reference_freq; + u32 tmp = RREG32(CG_CLKPIN_CNTL); + + if (tmp & MUX_TCLK_TO_XCLK) + return TCLK; + + if (tmp & XTALIN_DIVIDE) + return reference_clock / 4; + + return reference_clock; +} + u32 rv770_page_flip(struct radeon_device *rdev, int crtc_id, u64 crtc_base) { struct radeon_crtc *radeon_crtc = rdev->mode_info.crtcs[crtc_id]; diff --git a/drivers/gpu/drm/radeon/rv770d.h b/drivers/gpu/drm/radeon/rv770d.h index 20e29d23d348..c55f950a4af7 100644 --- a/drivers/gpu/drm/radeon/rv770d.h +++ b/drivers/gpu/drm/radeon/rv770d.h @@ -128,6 +128,10 @@ #define GUI_ACTIVE (1<<31) #define GRBM_STATUS2 0x8014 +#define CG_CLKPIN_CNTL 0x660 +# define MUX_TCLK_TO_XCLK (1 << 8) +# define XTALIN_DIVIDE (1 << 9) + #define CG_MULT_THERMAL_STATUS 0x740 #define ASIC_T(x) ((x) << 16) #define ASIC_T_MASK 0x3FF0000 diff --git a/drivers/gpu/drm/radeon/si.c b/drivers/gpu/drm/radeon/si.c index 719f03e061db..80979ed951eb 100644 --- a/drivers/gpu/drm/radeon/si.c +++ b/drivers/gpu/drm/radeon/si.c @@ -70,6 +70,33 @@ extern u32 evergreen_get_number_of_dram_channels(struct radeon_device *rdev); extern void evergreen_print_gpu_status_regs(struct radeon_device *rdev); extern bool evergreen_is_display_hung(struct radeon_device *rdev); +#define PCIE_BUS_CLK 10000 +#define TCLK (PCIE_BUS_CLK / 10) + +/** + * si_get_xclk - get the xclk + * + * @rdev: radeon_device pointer + * + * Returns the reference clock used by the gfx engine + * (SI). + */ +u32 si_get_xclk(struct radeon_device *rdev) +{ + u32 reference_clock = rdev->clock.spll.reference_freq; + u32 tmp; + + tmp = RREG32(CG_CLKPIN_CNTL_2); + if (tmp & MUX_TCLK_TO_XCLK) + return TCLK; + + tmp = RREG32(CG_CLKPIN_CNTL); + if (tmp & XTALIN_DIVIDE) + return reference_clock / 4; + + return reference_clock; +} + /* get temperature in millidegrees */ int si_get_temp(struct radeon_device *rdev) { @@ -4582,14 +4609,14 @@ void si_fini(struct radeon_device *rdev) } /** - * si_get_gpu_clock - return GPU clock counter snapshot + * si_get_gpu_clock_counter - return GPU clock counter snapshot * * @rdev: radeon_device pointer * * Fetches a GPU clock counter snapshot (SI). * Returns the 64 bit clock counter snapshot. */ -uint64_t si_get_gpu_clock(struct radeon_device *rdev) +uint64_t si_get_gpu_clock_counter(struct radeon_device *rdev) { uint64_t clock; diff --git a/drivers/gpu/drm/radeon/sid.h b/drivers/gpu/drm/radeon/sid.h index 07fc455e35ae..23fc08fc8e7f 100644 --- a/drivers/gpu/drm/radeon/sid.h +++ b/drivers/gpu/drm/radeon/sid.h @@ -58,6 +58,11 @@ #define VGA_HDP_CONTROL 0x328 #define VGA_MEMORY_DISABLE (1 << 4) +#define CG_CLKPIN_CNTL 0x660 +# define XTALIN_DIVIDE (1 << 1) +#define CG_CLKPIN_CNTL_2 0x664 +# define MUX_TCLK_TO_XCLK (1 << 8) + #define DMIF_ADDR_CONFIG 0xBD4 #define SRBM_STATUS 0xE50 diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 656b2e3334a6..bf8095c55bd1 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -18,26 +18,257 @@ #include "drm.h" #include "dc.h" -struct tegra_dc_window { - fixed20_12 x; - fixed20_12 y; - fixed20_12 w; - fixed20_12 h; - unsigned int outx; - unsigned int outy; - unsigned int outw; - unsigned int outh; - unsigned int stride; - unsigned int fmt; +struct tegra_plane { + struct drm_plane base; + unsigned int index; }; +static inline struct tegra_plane *to_tegra_plane(struct drm_plane *plane) +{ + return container_of(plane, struct tegra_plane, base); +} + +static int tegra_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, + struct drm_framebuffer *fb, int crtc_x, + int crtc_y, unsigned int crtc_w, + unsigned int crtc_h, uint32_t src_x, + uint32_t src_y, uint32_t src_w, uint32_t src_h) +{ + struct tegra_plane *p = to_tegra_plane(plane); + struct tegra_dc *dc = to_tegra_dc(crtc); + struct tegra_dc_window window; + unsigned int i; + + memset(&window, 0, sizeof(window)); + window.src.x = src_x >> 16; + window.src.y = src_y >> 16; + window.src.w = src_w >> 16; + window.src.h = src_h >> 16; + window.dst.x = crtc_x; + window.dst.y = crtc_y; + window.dst.w = crtc_w; + window.dst.h = crtc_h; + window.format = tegra_dc_format(fb->pixel_format); + window.bits_per_pixel = fb->bits_per_pixel; + + for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) { + struct drm_gem_cma_object *gem = drm_fb_cma_get_gem_obj(fb, i); + + window.base[i] = gem->paddr + fb->offsets[i]; + + /* + * Tegra doesn't support different strides for U and V planes + * so we display a warning if the user tries to display a + * framebuffer with such a configuration. + */ + if (i >= 2) { + if (fb->pitches[i] != window.stride[1]) + DRM_ERROR("unsupported UV-plane configuration\n"); + } else { + window.stride[i] = fb->pitches[i]; + } + } + + return tegra_dc_setup_window(dc, p->index, &window); +} + +static int tegra_plane_disable(struct drm_plane *plane) +{ + struct tegra_dc *dc = to_tegra_dc(plane->crtc); + struct tegra_plane *p = to_tegra_plane(plane); + unsigned long value; + + value = WINDOW_A_SELECT << p->index; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER); + + value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); + value &= ~WIN_ENABLE; + tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); + + tegra_dc_writel(dc, WIN_A_UPDATE << p->index, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, WIN_A_ACT_REQ << p->index, DC_CMD_STATE_CONTROL); + + return 0; +} + +static void tegra_plane_destroy(struct drm_plane *plane) +{ + tegra_plane_disable(plane); + drm_plane_cleanup(plane); +} + +static const struct drm_plane_funcs tegra_plane_funcs = { + .update_plane = tegra_plane_update, + .disable_plane = tegra_plane_disable, + .destroy = tegra_plane_destroy, +}; + +static const uint32_t plane_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_UYVY, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV422, +}; + +static int tegra_dc_add_planes(struct drm_device *drm, struct tegra_dc *dc) +{ + unsigned int i; + int err = 0; + + for (i = 0; i < 2; i++) { + struct tegra_plane *plane; + + plane = devm_kzalloc(drm->dev, sizeof(*plane), GFP_KERNEL); + if (!plane) + return -ENOMEM; + + plane->index = 1 + i; + + err = drm_plane_init(drm, &plane->base, 1 << dc->pipe, + &tegra_plane_funcs, plane_formats, + ARRAY_SIZE(plane_formats), false); + if (err < 0) + return err; + } + + return 0; +} + +static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y, + struct drm_framebuffer *fb) +{ + struct drm_gem_cma_object *gem = drm_fb_cma_get_gem_obj(fb, 0); + unsigned long value; + + tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER); + + value = fb->offsets[0] + y * fb->pitches[0] + + x * fb->bits_per_pixel / 8; + + tegra_dc_writel(dc, gem->paddr + value, DC_WINBUF_START_ADDR); + tegra_dc_writel(dc, fb->pitches[0], DC_WIN_LINE_STRIDE); + + value = GENERAL_UPDATE | WIN_A_UPDATE; + tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); + + value = GENERAL_ACT_REQ | WIN_A_ACT_REQ; + tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); + + return 0; +} + +void tegra_dc_enable_vblank(struct tegra_dc *dc) +{ + unsigned long value, flags; + + spin_lock_irqsave(&dc->lock, flags); + + value = tegra_dc_readl(dc, DC_CMD_INT_MASK); + value |= VBLANK_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_MASK); + + spin_unlock_irqrestore(&dc->lock, flags); +} + +void tegra_dc_disable_vblank(struct tegra_dc *dc) +{ + unsigned long value, flags; + + spin_lock_irqsave(&dc->lock, flags); + + value = tegra_dc_readl(dc, DC_CMD_INT_MASK); + value &= ~VBLANK_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_MASK); + + spin_unlock_irqrestore(&dc->lock, flags); +} + +static void tegra_dc_finish_page_flip(struct tegra_dc *dc) +{ + struct drm_device *drm = dc->base.dev; + struct drm_crtc *crtc = &dc->base; + struct drm_gem_cma_object *gem; + unsigned long flags, base; + + if (!dc->event) + return; + + gem = drm_fb_cma_get_gem_obj(crtc->fb, 0); + + /* check if new start address has been latched */ + tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS); + base = tegra_dc_readl(dc, DC_WINBUF_START_ADDR); + tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS); + + if (base == gem->paddr + crtc->fb->offsets[0]) { + spin_lock_irqsave(&drm->event_lock, flags); + drm_send_vblank_event(drm, dc->pipe, dc->event); + drm_vblank_put(drm, dc->pipe); + dc->event = NULL; + spin_unlock_irqrestore(&drm->event_lock, flags); + } +} + +void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file) +{ + struct tegra_dc *dc = to_tegra_dc(crtc); + struct drm_device *drm = crtc->dev; + unsigned long flags; + + spin_lock_irqsave(&drm->event_lock, flags); + + if (dc->event && dc->event->base.file_priv == file) { + dc->event->base.destroy(&dc->event->base); + drm_vblank_put(drm, dc->pipe); + dc->event = NULL; + } + + spin_unlock_irqrestore(&drm->event_lock, flags); +} + +static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct tegra_dc *dc = to_tegra_dc(crtc); + struct drm_device *drm = crtc->dev; + + if (dc->event) + return -EBUSY; + + if (event) { + event->pipe = dc->pipe; + dc->event = event; + drm_vblank_get(drm, dc->pipe); + } + + tegra_dc_set_base(dc, 0, 0, fb); + crtc->fb = fb; + + return 0; +} + static const struct drm_crtc_funcs tegra_crtc_funcs = { + .page_flip = tegra_dc_page_flip, .set_config = drm_crtc_helper_set_config, .destroy = drm_crtc_cleanup, }; -static void tegra_crtc_dpms(struct drm_crtc *crtc, int mode) +static void tegra_crtc_disable(struct drm_crtc *crtc) { + struct drm_device *drm = crtc->dev; + struct drm_plane *plane; + + list_for_each_entry(plane, &drm->mode_config.plane_list, head) { + if (plane->crtc == crtc) { + tegra_plane_disable(plane); + plane->crtc = NULL; + + if (plane->fb) { + drm_framebuffer_unreference(plane->fb); + plane->fb = NULL; + } + } + } } static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc, @@ -47,10 +278,11 @@ static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc, return true; } -static inline u32 compute_dda_inc(fixed20_12 inf, unsigned int out, bool v, +static inline u32 compute_dda_inc(unsigned int in, unsigned int out, bool v, unsigned int bpp) { fixed20_12 outf = dfixed_init(out); + fixed20_12 inf = dfixed_init(in); u32 dda_inc; int max; @@ -80,9 +312,10 @@ static inline u32 compute_dda_inc(fixed20_12 inf, unsigned int out, bool v, return dda_inc; } -static inline u32 compute_initial_dda(fixed20_12 in) +static inline u32 compute_initial_dda(unsigned int in) { - return dfixed_frac(in); + fixed20_12 inf = dfixed_init(in); + return dfixed_frac(inf); } static int tegra_dc_set_timings(struct tegra_dc *dc, @@ -153,18 +386,198 @@ static int tegra_crtc_setup_clk(struct drm_crtc *crtc, return 0; } +static bool tegra_dc_format_is_yuv(unsigned int format, bool *planar) +{ + switch (format) { + case WIN_COLOR_DEPTH_YCbCr422: + case WIN_COLOR_DEPTH_YUV422: + if (planar) + *planar = false; + + return true; + + case WIN_COLOR_DEPTH_YCbCr420P: + case WIN_COLOR_DEPTH_YUV420P: + case WIN_COLOR_DEPTH_YCbCr422P: + case WIN_COLOR_DEPTH_YUV422P: + case WIN_COLOR_DEPTH_YCbCr422R: + case WIN_COLOR_DEPTH_YUV422R: + case WIN_COLOR_DEPTH_YCbCr422RA: + case WIN_COLOR_DEPTH_YUV422RA: + if (planar) + *planar = true; + + return true; + } + + return false; +} + +int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, + const struct tegra_dc_window *window) +{ + unsigned h_offset, v_offset, h_size, v_size, h_dda, v_dda, bpp; + unsigned long value; + bool yuv, planar; + + /* + * For YUV planar modes, the number of bytes per pixel takes into + * account only the luma component and therefore is 1. + */ + yuv = tegra_dc_format_is_yuv(window->format, &planar); + if (!yuv) + bpp = window->bits_per_pixel / 8; + else + bpp = planar ? 1 : 2; + + value = WINDOW_A_SELECT << index; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER); + + tegra_dc_writel(dc, window->format, DC_WIN_COLOR_DEPTH); + tegra_dc_writel(dc, 0, DC_WIN_BYTE_SWAP); + + value = V_POSITION(window->dst.y) | H_POSITION(window->dst.x); + tegra_dc_writel(dc, value, DC_WIN_POSITION); + + value = V_SIZE(window->dst.h) | H_SIZE(window->dst.w); + tegra_dc_writel(dc, value, DC_WIN_SIZE); + + h_offset = window->src.x * bpp; + v_offset = window->src.y; + h_size = window->src.w * bpp; + v_size = window->src.h; + + value = V_PRESCALED_SIZE(v_size) | H_PRESCALED_SIZE(h_size); + tegra_dc_writel(dc, value, DC_WIN_PRESCALED_SIZE); + + /* + * For DDA computations the number of bytes per pixel for YUV planar + * modes needs to take into account all Y, U and V components. + */ + if (yuv && planar) + bpp = 2; + + h_dda = compute_dda_inc(window->src.w, window->dst.w, false, bpp); + v_dda = compute_dda_inc(window->src.h, window->dst.h, true, bpp); + + value = V_DDA_INC(v_dda) | H_DDA_INC(h_dda); + tegra_dc_writel(dc, value, DC_WIN_DDA_INC); + + h_dda = compute_initial_dda(window->src.x); + v_dda = compute_initial_dda(window->src.y); + + tegra_dc_writel(dc, h_dda, DC_WIN_H_INITIAL_DDA); + tegra_dc_writel(dc, v_dda, DC_WIN_V_INITIAL_DDA); + + tegra_dc_writel(dc, 0, DC_WIN_UV_BUF_STRIDE); + tegra_dc_writel(dc, 0, DC_WIN_BUF_STRIDE); + + tegra_dc_writel(dc, window->base[0], DC_WINBUF_START_ADDR); + + if (yuv && planar) { + tegra_dc_writel(dc, window->base[1], DC_WINBUF_START_ADDR_U); + tegra_dc_writel(dc, window->base[2], DC_WINBUF_START_ADDR_V); + value = window->stride[1] << 16 | window->stride[0]; + tegra_dc_writel(dc, value, DC_WIN_LINE_STRIDE); + } else { + tegra_dc_writel(dc, window->stride[0], DC_WIN_LINE_STRIDE); + } + + tegra_dc_writel(dc, h_offset, DC_WINBUF_ADDR_H_OFFSET); + tegra_dc_writel(dc, v_offset, DC_WINBUF_ADDR_V_OFFSET); + + value = WIN_ENABLE; + + if (yuv) { + /* setup default colorspace conversion coefficients */ + tegra_dc_writel(dc, 0x00f0, DC_WIN_CSC_YOF); + tegra_dc_writel(dc, 0x012a, DC_WIN_CSC_KYRGB); + tegra_dc_writel(dc, 0x0000, DC_WIN_CSC_KUR); + tegra_dc_writel(dc, 0x0198, DC_WIN_CSC_KVR); + tegra_dc_writel(dc, 0x039b, DC_WIN_CSC_KUG); + tegra_dc_writel(dc, 0x032f, DC_WIN_CSC_KVG); + tegra_dc_writel(dc, 0x0204, DC_WIN_CSC_KUB); + tegra_dc_writel(dc, 0x0000, DC_WIN_CSC_KVB); + + value |= CSC_ENABLE; + } else if (window->bits_per_pixel < 24) { + value |= COLOR_EXPAND; + } + + tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); + + /* + * Disable blending and assume Window A is the bottom-most window, + * Window C is the top-most window and Window B is in the middle. + */ + tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_NOKEY); + tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_1WIN); + + switch (index) { + case 0: + tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_2WIN_X); + tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_2WIN_Y); + tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_3WIN_XY); + break; + + case 1: + tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_2WIN_X); + tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_2WIN_Y); + tegra_dc_writel(dc, 0x000000, DC_WIN_BLEND_3WIN_XY); + break; + + case 2: + tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_2WIN_X); + tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_2WIN_Y); + tegra_dc_writel(dc, 0xffff00, DC_WIN_BLEND_3WIN_XY); + break; + } + + tegra_dc_writel(dc, WIN_A_UPDATE << index, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, WIN_A_ACT_REQ << index, DC_CMD_STATE_CONTROL); + + return 0; +} + +unsigned int tegra_dc_format(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_XRGB8888: + return WIN_COLOR_DEPTH_B8G8R8A8; + + case DRM_FORMAT_RGB565: + return WIN_COLOR_DEPTH_B5G6R5; + + case DRM_FORMAT_UYVY: + return WIN_COLOR_DEPTH_YCbCr422; + + case DRM_FORMAT_YUV420: + return WIN_COLOR_DEPTH_YCbCr420P; + + case DRM_FORMAT_YUV422: + return WIN_COLOR_DEPTH_YCbCr422P; + + default: + break; + } + + WARN(1, "unsupported pixel format %u, using default\n", format); + return WIN_COLOR_DEPTH_B8G8R8A8; +} + static int tegra_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_display_mode *adjusted, int x, int y, struct drm_framebuffer *old_fb) { - struct tegra_framebuffer *fb = to_tegra_fb(crtc->fb); + struct drm_gem_cma_object *gem = drm_fb_cma_get_gem_obj(crtc->fb, 0); struct tegra_dc *dc = to_tegra_dc(crtc); - unsigned int h_dda, v_dda, bpp; - struct tegra_dc_window win; + struct tegra_dc_window window; unsigned long div, value; int err; + drm_vblank_pre_modeset(crtc->dev, dc->pipe); + err = tegra_crtc_setup_clk(crtc, mode, &div); if (err) { dev_err(dc->dev, "failed to setup clock for CRTC: %d\n", err); @@ -192,83 +605,33 @@ static int tegra_crtc_mode_set(struct drm_crtc *crtc, tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL); /* setup window parameters */ - memset(&win, 0, sizeof(win)); - win.x.full = dfixed_const(0); - win.y.full = dfixed_const(0); - win.w.full = dfixed_const(mode->hdisplay); - win.h.full = dfixed_const(mode->vdisplay); - win.outx = 0; - win.outy = 0; - win.outw = mode->hdisplay; - win.outh = mode->vdisplay; - - switch (crtc->fb->pixel_format) { - case DRM_FORMAT_XRGB8888: - win.fmt = WIN_COLOR_DEPTH_B8G8R8A8; - break; - - case DRM_FORMAT_RGB565: - win.fmt = WIN_COLOR_DEPTH_B5G6R5; - break; - - default: - win.fmt = WIN_COLOR_DEPTH_B8G8R8A8; - WARN_ON(1); - break; - } - - bpp = crtc->fb->bits_per_pixel / 8; - win.stride = crtc->fb->pitches[0]; - - /* program window registers */ - value = WINDOW_A_SELECT; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER); - - tegra_dc_writel(dc, win.fmt, DC_WIN_COLOR_DEPTH); - tegra_dc_writel(dc, 0, DC_WIN_BYTE_SWAP); - - value = V_POSITION(win.outy) | H_POSITION(win.outx); - tegra_dc_writel(dc, value, DC_WIN_POSITION); - - value = V_SIZE(win.outh) | H_SIZE(win.outw); - tegra_dc_writel(dc, value, DC_WIN_SIZE); - - value = V_PRESCALED_SIZE(dfixed_trunc(win.h)) | - H_PRESCALED_SIZE(dfixed_trunc(win.w) * bpp); - tegra_dc_writel(dc, value, DC_WIN_PRESCALED_SIZE); - - h_dda = compute_dda_inc(win.w, win.outw, false, bpp); - v_dda = compute_dda_inc(win.h, win.outh, true, bpp); - - value = V_DDA_INC(v_dda) | H_DDA_INC(h_dda); - tegra_dc_writel(dc, value, DC_WIN_DDA_INC); - - h_dda = compute_initial_dda(win.x); - v_dda = compute_initial_dda(win.y); - - tegra_dc_writel(dc, h_dda, DC_WIN_H_INITIAL_DDA); - tegra_dc_writel(dc, v_dda, DC_WIN_V_INITIAL_DDA); - - tegra_dc_writel(dc, 0, DC_WIN_UV_BUF_STRIDE); - tegra_dc_writel(dc, 0, DC_WIN_BUF_STRIDE); - - tegra_dc_writel(dc, fb->obj->paddr, DC_WINBUF_START_ADDR); - tegra_dc_writel(dc, win.stride, DC_WIN_LINE_STRIDE); - tegra_dc_writel(dc, dfixed_trunc(win.x) * bpp, - DC_WINBUF_ADDR_H_OFFSET); - tegra_dc_writel(dc, dfixed_trunc(win.y), DC_WINBUF_ADDR_V_OFFSET); - - value = WIN_ENABLE; - - if (bpp < 24) - value |= COLOR_EXPAND; + memset(&window, 0, sizeof(window)); + window.src.x = 0; + window.src.y = 0; + window.src.w = mode->hdisplay; + window.src.h = mode->vdisplay; + window.dst.x = 0; + window.dst.y = 0; + window.dst.w = mode->hdisplay; + window.dst.h = mode->vdisplay; + window.format = tegra_dc_format(crtc->fb->pixel_format); + window.bits_per_pixel = crtc->fb->bits_per_pixel; + window.stride[0] = crtc->fb->pitches[0]; + window.base[0] = gem->paddr; + + err = tegra_dc_setup_window(dc, 0, &window); + if (err < 0) + dev_err(dc->dev, "failed to enable root plane\n"); - tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); + return 0; +} - tegra_dc_writel(dc, 0xff00, DC_WIN_BLEND_NOKEY); - tegra_dc_writel(dc, 0xff00, DC_WIN_BLEND_1WIN); +static int tegra_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct tegra_dc *dc = to_tegra_dc(crtc); - return 0; + return tegra_dc_set_base(dc, x, y, crtc->fb); } static void tegra_crtc_prepare(struct drm_crtc *crtc) @@ -315,31 +678,24 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc) tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER); value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_MASK); - - value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT; tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE); + + value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_MASK); } static void tegra_crtc_commit(struct drm_crtc *crtc) { struct tegra_dc *dc = to_tegra_dc(crtc); - unsigned long update_mask; unsigned long value; - update_mask = GENERAL_ACT_REQ | WIN_A_ACT_REQ; - - tegra_dc_writel(dc, update_mask << 8, DC_CMD_STATE_CONTROL); + value = GENERAL_UPDATE | WIN_A_UPDATE; + tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); - value = tegra_dc_readl(dc, DC_CMD_INT_ENABLE); - value |= FRAME_END_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE); - - value = tegra_dc_readl(dc, DC_CMD_INT_MASK); - value |= FRAME_END_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_MASK); + value = GENERAL_ACT_REQ | WIN_A_ACT_REQ; + tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL); + drm_vblank_post_modeset(crtc->dev, dc->pipe); } static void tegra_crtc_load_lut(struct drm_crtc *crtc) @@ -347,15 +703,16 @@ static void tegra_crtc_load_lut(struct drm_crtc *crtc) } static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = { - .dpms = tegra_crtc_dpms, + .disable = tegra_crtc_disable, .mode_fixup = tegra_crtc_mode_fixup, .mode_set = tegra_crtc_mode_set, + .mode_set_base = tegra_crtc_mode_set_base, .prepare = tegra_crtc_prepare, .commit = tegra_crtc_commit, .load_lut = tegra_crtc_load_lut, }; -static irqreturn_t tegra_drm_irq(int irq, void *data) +static irqreturn_t tegra_dc_irq(int irq, void *data) { struct tegra_dc *dc = data; unsigned long status; @@ -374,6 +731,7 @@ static irqreturn_t tegra_drm_irq(int irq, void *data) dev_dbg(dc->dev, "%s(): vertical blank\n", __func__); */ drm_handle_vblank(dc->base.dev, dc->pipe); + tegra_dc_finish_page_flip(dc); } if (status & (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT)) { @@ -588,7 +946,7 @@ static int tegra_dc_show_regs(struct seq_file *s, void *data) DUMP_REG(DC_WIN_BLEND_1WIN); DUMP_REG(DC_WIN_BLEND_2WIN_X); DUMP_REG(DC_WIN_BLEND_2WIN_Y); - DUMP_REG(DC_WIN_BLEND32WIN_XY); + DUMP_REG(DC_WIN_BLEND_3WIN_XY); DUMP_REG(DC_WIN_HP_FETCH_CONTROL); DUMP_REG(DC_WINBUF_START_ADDR); DUMP_REG(DC_WINBUF_START_ADDR_NS); @@ -690,13 +1048,17 @@ static int tegra_dc_drm_init(struct host1x_client *client, return err; } + err = tegra_dc_add_planes(drm, dc); + if (err < 0) + return err; + if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_dc_debugfs_init(dc, drm->primary); if (err < 0) dev_err(dc->dev, "debugfs setup failed: %d\n", err); } - err = devm_request_irq(dc->dev, dc->irq, tegra_drm_irq, 0, + err = devm_request_irq(dc->dev, dc->irq, tegra_dc_irq, 0, dev_name(dc->dev), dc); if (err < 0) { dev_err(dc->dev, "failed to request IRQ#%u: %d\n", dc->irq, @@ -745,6 +1107,7 @@ static int tegra_dc_probe(struct platform_device *pdev) if (!dc) return -ENOMEM; + spin_lock_init(&dc->lock); INIT_LIST_HEAD(&dc->list); dc->dev = &pdev->dev; diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h index 99977b5d5c36..79eaec9aac77 100644 --- a/drivers/gpu/drm/tegra/dc.h +++ b/drivers/gpu/drm/tegra/dc.h @@ -58,6 +58,8 @@ #define DC_CMD_SIGNAL_RAISE3 0x03e #define DC_CMD_STATE_ACCESS 0x040 +#define READ_MUX (1 << 0) +#define WRITE_MUX (1 << 2) #define DC_CMD_STATE_CONTROL 0x041 #define GENERAL_ACT_REQ (1 << 0) @@ -290,8 +292,18 @@ #define DC_DISP_SD_HW_K_VALUES 0x4dd #define DC_DISP_SD_MAN_K_VALUES 0x4de +#define DC_WIN_CSC_YOF 0x611 +#define DC_WIN_CSC_KYRGB 0x612 +#define DC_WIN_CSC_KUR 0x613 +#define DC_WIN_CSC_KVR 0x614 +#define DC_WIN_CSC_KUG 0x615 +#define DC_WIN_CSC_KVG 0x616 +#define DC_WIN_CSC_KUB 0x617 +#define DC_WIN_CSC_KVB 0x618 + #define DC_WIN_WIN_OPTIONS 0x700 #define COLOR_EXPAND (1 << 6) +#define CSC_ENABLE (1 << 18) #define WIN_ENABLE (1 << 30) #define DC_WIN_BYTE_SWAP 0x701 @@ -359,7 +371,7 @@ #define DC_WIN_BLEND_1WIN 0x710 #define DC_WIN_BLEND_2WIN_X 0x711 #define DC_WIN_BLEND_2WIN_Y 0x712 -#define DC_WIN_BLEND32WIN_XY 0x713 +#define DC_WIN_BLEND_3WIN_XY 0x713 #define DC_WIN_HP_FETCH_CONTROL 0x714 diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 3a503c9e4686..181a370c56c1 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -40,6 +40,10 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) if (err < 0) return err; + err = drm_vblank_init(drm, drm->mode_config.num_crtc); + if (err < 0) + return err; + err = tegra_drm_fb_init(drm); if (err < 0) return err; @@ -89,13 +93,112 @@ static const struct file_operations tegra_drm_fops = { .llseek = noop_llseek, }; +static struct drm_crtc *tegra_crtc_from_pipe(struct drm_device *drm, int pipe) +{ + struct drm_crtc *crtc; + + list_for_each_entry(crtc, &drm->mode_config.crtc_list, head) { + struct tegra_dc *dc = to_tegra_dc(crtc); + + if (dc->pipe == pipe) + return crtc; + } + + return NULL; +} + +static u32 tegra_drm_get_vblank_counter(struct drm_device *dev, int crtc) +{ + /* TODO: implement real hardware counter using syncpoints */ + return drm_vblank_count(dev, crtc); +} + +static int tegra_drm_enable_vblank(struct drm_device *drm, int pipe) +{ + struct drm_crtc *crtc = tegra_crtc_from_pipe(drm, pipe); + struct tegra_dc *dc = to_tegra_dc(crtc); + + if (!crtc) + return -ENODEV; + + tegra_dc_enable_vblank(dc); + + return 0; +} + +static void tegra_drm_disable_vblank(struct drm_device *drm, int pipe) +{ + struct drm_crtc *crtc = tegra_crtc_from_pipe(drm, pipe); + struct tegra_dc *dc = to_tegra_dc(crtc); + + if (crtc) + tegra_dc_disable_vblank(dc); +} + +static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file) +{ + struct drm_crtc *crtc; + + list_for_each_entry(crtc, &drm->mode_config.crtc_list, head) + tegra_dc_cancel_page_flip(crtc, file); +} + +#ifdef CONFIG_DEBUG_FS +static int tegra_debugfs_framebuffers(struct seq_file *s, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *)s->private; + struct drm_device *drm = node->minor->dev; + struct drm_framebuffer *fb; + + mutex_lock(&drm->mode_config.fb_lock); + + list_for_each_entry(fb, &drm->mode_config.fb_list, head) { + seq_printf(s, "%3d: user size: %d x %d, depth %d, %d bpp, refcount %d\n", + fb->base.id, fb->width, fb->height, fb->depth, + fb->bits_per_pixel, + atomic_read(&fb->refcount.refcount)); + } + + mutex_unlock(&drm->mode_config.fb_lock); + + return 0; +} + +static struct drm_info_list tegra_debugfs_list[] = { + { "framebuffers", tegra_debugfs_framebuffers, 0 }, +}; + +static int tegra_debugfs_init(struct drm_minor *minor) +{ + return drm_debugfs_create_files(tegra_debugfs_list, + ARRAY_SIZE(tegra_debugfs_list), + minor->debugfs_root, minor); +} + +static void tegra_debugfs_cleanup(struct drm_minor *minor) +{ + drm_debugfs_remove_files(tegra_debugfs_list, + ARRAY_SIZE(tegra_debugfs_list), minor); +} +#endif + struct drm_driver tegra_drm_driver = { .driver_features = DRIVER_BUS_PLATFORM | DRIVER_MODESET | DRIVER_GEM, .load = tegra_drm_load, .unload = tegra_drm_unload, .open = tegra_drm_open, + .preclose = tegra_drm_preclose, .lastclose = tegra_drm_lastclose, + .get_vblank_counter = tegra_drm_get_vblank_counter, + .enable_vblank = tegra_drm_enable_vblank, + .disable_vblank = tegra_drm_disable_vblank, + +#if defined(CONFIG_DEBUG_FS) + .debugfs_init = tegra_debugfs_init, + .debugfs_cleanup = tegra_debugfs_cleanup, +#endif + .gem_free_object = drm_gem_cma_free_object, .gem_vm_ops = &drm_gem_cma_vm_ops, .dumb_create = drm_gem_cma_dumb_create, diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 741b5dc2742c..6dd75a2600eb 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -18,16 +18,6 @@ #include <drm/drm_fb_cma_helper.h> #include <drm/drm_fixed.h> -struct tegra_framebuffer { - struct drm_framebuffer base; - struct drm_gem_cma_object *obj; -}; - -static inline struct tegra_framebuffer *to_tegra_fb(struct drm_framebuffer *fb) -{ - return container_of(fb, struct tegra_framebuffer, base); -} - struct host1x { struct drm_device *drm; struct device *dev; @@ -44,7 +34,6 @@ struct host1x { struct list_head clients; struct drm_fbdev_cma *fbdev; - struct tegra_framebuffer fb; }; struct host1x_client; @@ -75,6 +64,7 @@ struct tegra_output; struct tegra_dc { struct host1x_client client; + spinlock_t lock; struct host1x *host1x; struct device *dev; @@ -94,6 +84,9 @@ struct tegra_dc { struct drm_info_list *debugfs_files; struct drm_minor *minor; struct dentry *debugfs; + + /* page-flip handling */ + struct drm_pending_vblank_event *event; }; static inline struct tegra_dc *host1x_client_to_dc(struct host1x_client *client) @@ -118,6 +111,34 @@ static inline unsigned long tegra_dc_readl(struct tegra_dc *dc, return readl(dc->regs + (reg << 2)); } +struct tegra_dc_window { + struct { + unsigned int x; + unsigned int y; + unsigned int w; + unsigned int h; + } src; + struct { + unsigned int x; + unsigned int y; + unsigned int w; + unsigned int h; + } dst; + unsigned int bits_per_pixel; + unsigned int format; + unsigned int stride[2]; + unsigned long base[3]; +}; + +/* from dc.c */ +extern unsigned int tegra_dc_format(uint32_t format); +extern int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, + const struct tegra_dc_window *window); +extern void tegra_dc_enable_vblank(struct tegra_dc *dc); +extern void tegra_dc_disable_vblank(struct tegra_dc *dc); +extern void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, + struct drm_file *file); + struct tegra_output_ops { int (*enable)(struct tegra_output *output); int (*disable)(struct tegra_output *output); diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c index 97993c6835fd..03914953cb1c 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -39,10 +39,6 @@ int tegra_drm_fb_init(struct drm_device *drm) if (IS_ERR(fbdev)) return PTR_ERR(fbdev); -#ifndef CONFIG_FRAMEBUFFER_CONSOLE - drm_fbdev_cma_restore_mode(fbdev); -#endif - host1x->fbdev = fbdev; return 0; diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig new file mode 100644 index 000000000000..ae14fd6ea924 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/Kconfig @@ -0,0 +1,13 @@ +config DRM_TILCDC + tristate "DRM Support for TI LCDC Display Controller" + depends on DRM && OF + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_GEM_CMA_HELPER + select OF_VIDEOMODE + select OF_DISPLAY_TIMING + select BACKLIGHT_CLASS_DEVICE + help + Choose this option if you have an TI SoC with LCDC display + controller, for example AM33xx in beagle-bone, DA8xx, or + OMAP-L1xx. This driver replaces the FB_DA8XX fbdev driver. diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile new file mode 100644 index 000000000000..deda656b10e7 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/Makefile @@ -0,0 +1,10 @@ +ccflags-y := -Iinclude/drm -Werror + +tilcdc-y := \ + tilcdc_crtc.o \ + tilcdc_tfp410.o \ + tilcdc_slave.o \ + tilcdc_panel.o \ + tilcdc_drv.o + +obj-$(CONFIG_DRM_TILCDC) += tilcdc.o diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c new file mode 100644 index 000000000000..5dd3c7d031d5 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kfifo.h> + +#include "tilcdc_drv.h" +#include "tilcdc_regs.h" + +struct tilcdc_crtc { + struct drm_crtc base; + + const struct tilcdc_panel_info *info; + uint32_t dirty; + dma_addr_t start, end; + struct drm_pending_vblank_event *event; + int dpms; + wait_queue_head_t frame_done_wq; + bool frame_done; + + /* fb currently set to scanout 0/1: */ + struct drm_framebuffer *scanout[2]; + + /* for deferred fb unref's: */ + DECLARE_KFIFO_PTR(unref_fifo, struct drm_framebuffer *); + struct work_struct work; +}; +#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base) + +static void unref_worker(struct work_struct *work) +{ + struct tilcdc_crtc *tilcdc_crtc = container_of(work, struct tilcdc_crtc, work); + struct drm_device *dev = tilcdc_crtc->base.dev; + struct drm_framebuffer *fb; + + mutex_lock(&dev->mode_config.mutex); + while (kfifo_get(&tilcdc_crtc->unref_fifo, &fb)) + drm_framebuffer_unreference(fb); + mutex_unlock(&dev->mode_config.mutex); +} + +static void set_scanout(struct drm_crtc *crtc, int n) +{ + static const uint32_t base_reg[] = { + LCDC_DMA_FB_BASE_ADDR_0_REG, LCDC_DMA_FB_BASE_ADDR_1_REG, + }; + static const uint32_t ceil_reg[] = { + LCDC_DMA_FB_CEILING_ADDR_0_REG, LCDC_DMA_FB_CEILING_ADDR_1_REG, + }; + static const uint32_t stat[] = { + LCDC_END_OF_FRAME0, LCDC_END_OF_FRAME1, + }; + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + + pm_runtime_get_sync(dev->dev); + tilcdc_write(dev, base_reg[n], tilcdc_crtc->start); + tilcdc_write(dev, ceil_reg[n], tilcdc_crtc->end); + if (tilcdc_crtc->scanout[n]) { + if (kfifo_put(&tilcdc_crtc->unref_fifo, + (const struct drm_framebuffer **)&tilcdc_crtc->scanout[n])) { + struct tilcdc_drm_private *priv = dev->dev_private; + queue_work(priv->wq, &tilcdc_crtc->work); + } else { + dev_err(dev->dev, "unref fifo full!\n"); + drm_framebuffer_unreference(tilcdc_crtc->scanout[n]); + } + } + tilcdc_crtc->scanout[n] = crtc->fb; + drm_framebuffer_reference(tilcdc_crtc->scanout[n]); + tilcdc_crtc->dirty &= ~stat[n]; + pm_runtime_put_sync(dev->dev); +} + +static void update_scanout(struct drm_crtc *crtc) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct drm_framebuffer *fb = crtc->fb; + struct drm_gem_cma_object *gem; + unsigned int depth, bpp; + + drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp); + gem = drm_fb_cma_get_gem_obj(fb, 0); + + tilcdc_crtc->start = gem->paddr + fb->offsets[0] + + (crtc->y * fb->pitches[0]) + (crtc->x * bpp/8); + + tilcdc_crtc->end = tilcdc_crtc->start + + (crtc->mode.vdisplay * fb->pitches[0]); + + if (tilcdc_crtc->dpms == DRM_MODE_DPMS_ON) { + /* already enabled, so just mark the frames that need + * updating and they will be updated on vblank: + */ + tilcdc_crtc->dirty |= LCDC_END_OF_FRAME0 | LCDC_END_OF_FRAME1; + drm_vblank_get(dev, 0); + } else { + /* not enabled yet, so update registers immediately: */ + set_scanout(crtc, 0); + set_scanout(crtc, 1); + } +} + +static void start(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = dev->dev_private; + + if (priv->rev == 2) { + tilcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET); + msleep(1); + tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET); + msleep(1); + } + + tilcdc_set(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE); + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY)); + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE); +} + +static void stop(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE); +} + +static void tilcdc_crtc_destroy(struct drm_crtc *crtc) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + + WARN_ON(tilcdc_crtc->dpms == DRM_MODE_DPMS_ON); + + drm_crtc_cleanup(crtc); + WARN_ON(!kfifo_is_empty(&tilcdc_crtc->unref_fifo)); + kfifo_free(&tilcdc_crtc->unref_fifo); + kfree(tilcdc_crtc); +} + +static int tilcdc_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + + if (tilcdc_crtc->event) { + dev_err(dev->dev, "already pending page flip!\n"); + return -EBUSY; + } + + crtc->fb = fb; + tilcdc_crtc->event = event; + update_scanout(crtc); + + return 0; +} + +static void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = dev->dev_private; + + /* we really only care about on or off: */ + if (mode != DRM_MODE_DPMS_ON) + mode = DRM_MODE_DPMS_OFF; + + if (tilcdc_crtc->dpms == mode) + return; + + tilcdc_crtc->dpms = mode; + + pm_runtime_get_sync(dev->dev); + + if (mode == DRM_MODE_DPMS_ON) { + pm_runtime_forbid(dev->dev); + start(crtc); + } else { + tilcdc_crtc->frame_done = false; + stop(crtc); + + /* if necessary wait for framedone irq which will still come + * before putting things to sleep.. + */ + if (priv->rev == 2) { + int ret = wait_event_timeout( + tilcdc_crtc->frame_done_wq, + tilcdc_crtc->frame_done, + msecs_to_jiffies(50)); + if (ret == 0) + dev_err(dev->dev, "timeout waiting for framedone\n"); + } + pm_runtime_allow(dev->dev); + } + + pm_runtime_put_sync(dev->dev); +} + +static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void tilcdc_crtc_prepare(struct drm_crtc *crtc) +{ + tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +} + +static void tilcdc_crtc_commit(struct drm_crtc *crtc) +{ + tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static int tilcdc_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, + struct drm_framebuffer *old_fb) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = dev->dev_private; + const struct tilcdc_panel_info *info = tilcdc_crtc->info; + uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw; + int ret; + + ret = tilcdc_crtc_mode_valid(crtc, mode); + if (WARN_ON(ret)) + return ret; + + if (WARN_ON(!info)) + return -EINVAL; + + pm_runtime_get_sync(dev->dev); + + /* Configure the Burst Size and fifo threshold of DMA: */ + reg = tilcdc_read(dev, LCDC_DMA_CTRL_REG) & ~0x00000770; + switch (info->dma_burst_sz) { + case 1: + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_1); + break; + case 2: + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_2); + break; + case 4: + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_4); + break; + case 8: + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_8); + break; + case 16: + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_16); + break; + default: + return -EINVAL; + } + reg |= (info->fifo_th << 8); + tilcdc_write(dev, LCDC_DMA_CTRL_REG, reg); + + /* Configure timings: */ + hbp = mode->htotal - mode->hsync_end; + hfp = mode->hsync_start - mode->hdisplay; + hsw = mode->hsync_end - mode->hsync_start; + vbp = mode->vtotal - mode->vsync_end; + vfp = mode->vsync_start - mode->vdisplay; + vsw = mode->vsync_end - mode->vsync_start; + + DBG("%dx%d, hbp=%u, hfp=%u, hsw=%u, vbp=%u, vfp=%u, vsw=%u", + mode->hdisplay, mode->vdisplay, hbp, hfp, hsw, vbp, vfp, vsw); + + /* Configure the AC Bias Period and Number of Transitions per Interrupt: */ + reg = tilcdc_read(dev, LCDC_RASTER_TIMING_2_REG) & ~0x000fff00; + reg |= LCDC_AC_BIAS_FREQUENCY(info->ac_bias) | + LCDC_AC_BIAS_TRANSITIONS_PER_INT(info->ac_bias_intrpt); + if (priv->rev == 2) { + reg |= (hfp & 0x300) >> 8; + reg |= (hbp & 0x300) >> 4; + reg |= (hsw & 0x3c0) << 21; + } + tilcdc_write(dev, LCDC_RASTER_TIMING_2_REG, reg); + + reg = (((mode->hdisplay >> 4) - 1) << 4) | + ((hbp & 0xff) << 24) | + ((hfp & 0xff) << 16) | + ((hsw & 0x3f) << 10); + if (priv->rev == 2) + reg |= (((mode->hdisplay >> 4) - 1) & 0x40) >> 3; + tilcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg); + + reg = ((mode->vdisplay - 1) & 0x3ff) | + ((vbp & 0xff) << 24) | + ((vfp & 0xff) << 16) | + ((vsw & 0x3f) << 10); + tilcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg); + + /* Configure display type: */ + reg = tilcdc_read(dev, LCDC_RASTER_CTRL_REG) & + ~(LCDC_TFT_MODE | LCDC_MONO_8BIT_MODE | LCDC_MONOCHROME_MODE | + LCDC_V2_TFT_24BPP_MODE | LCDC_V2_TFT_24BPP_UNPACK | 0x000ff000); + reg |= LCDC_TFT_MODE; /* no monochrome/passive support */ + if (info->tft_alt_mode) + reg |= LCDC_TFT_ALT_ENABLE; + if (priv->rev == 2) { + unsigned int depth, bpp; + + drm_fb_get_bpp_depth(crtc->fb->pixel_format, &depth, &bpp); + switch (bpp) { + case 16: + break; + case 32: + reg |= LCDC_V2_TFT_24BPP_UNPACK; + /* fallthrough */ + case 24: + reg |= LCDC_V2_TFT_24BPP_MODE; + break; + default: + dev_err(dev->dev, "invalid pixel format\n"); + return -EINVAL; + } + } + reg |= info->fdd < 12; + tilcdc_write(dev, LCDC_RASTER_CTRL_REG, reg); + + if (info->invert_pxl_clk) + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK); + else + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK); + + if (info->sync_ctrl) + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL); + else + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL); + + if (info->sync_edge) + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE); + else + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE); + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC); + else + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC); + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC); + else + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC); + + if (info->raster_order) + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER); + else + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER); + + + update_scanout(crtc); + tilcdc_crtc_update_clk(crtc); + + pm_runtime_put_sync(dev->dev); + + return 0; +} + +static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + update_scanout(crtc); + return 0; +} + +static void tilcdc_crtc_load_lut(struct drm_crtc *crtc) +{ +} + +static const struct drm_crtc_funcs tilcdc_crtc_funcs = { + .destroy = tilcdc_crtc_destroy, + .set_config = drm_crtc_helper_set_config, + .page_flip = tilcdc_crtc_page_flip, +}; + +static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = { + .dpms = tilcdc_crtc_dpms, + .mode_fixup = tilcdc_crtc_mode_fixup, + .prepare = tilcdc_crtc_prepare, + .commit = tilcdc_crtc_commit, + .mode_set = tilcdc_crtc_mode_set, + .mode_set_base = tilcdc_crtc_mode_set_base, + .load_lut = tilcdc_crtc_load_lut, +}; + +int tilcdc_crtc_max_width(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = dev->dev_private; + int max_width = 0; + + if (priv->rev == 1) + max_width = 1024; + else if (priv->rev == 2) + max_width = 2048; + + return max_width; +} + +int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode) +{ + struct tilcdc_drm_private *priv = crtc->dev->dev_private; + unsigned int bandwidth; + + if (mode->hdisplay > tilcdc_crtc_max_width(crtc)) + return MODE_VIRTUAL_X; + + /* width must be multiple of 16 */ + if (mode->hdisplay & 0xf) + return MODE_VIRTUAL_X; + + if (mode->vdisplay > 2048) + return MODE_VIRTUAL_Y; + + /* filter out modes that would require too much memory bandwidth: */ + bandwidth = mode->hdisplay * mode->vdisplay * drm_mode_vrefresh(mode); + if (bandwidth > priv->max_bandwidth) + return MODE_BAD; + + return MODE_OK; +} + +void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc, + const struct tilcdc_panel_info *info) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + tilcdc_crtc->info = info; +} + +void tilcdc_crtc_update_clk(struct drm_crtc *crtc) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = dev->dev_private; + int dpms = tilcdc_crtc->dpms; + unsigned int lcd_clk, div; + int ret; + + pm_runtime_get_sync(dev->dev); + + if (dpms == DRM_MODE_DPMS_ON) + tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); + + /* in raster mode, minimum divisor is 2: */ + ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2); + if (ret) { + dev_err(dev->dev, "failed to set display clock rate to: %d\n", + crtc->mode.clock); + goto out; + } + + lcd_clk = clk_get_rate(priv->clk); + div = lcd_clk / (crtc->mode.clock * 1000); + + DBG("lcd_clk=%u, mode clock=%d, div=%u", lcd_clk, crtc->mode.clock, div); + DBG("fck=%lu, dpll_disp_ck=%lu", clk_get_rate(priv->clk), clk_get_rate(priv->disp_clk)); + + /* Configure the LCD clock divisor. */ + tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) | + LCDC_RASTER_MODE); + + if (priv->rev == 2) + tilcdc_set(dev, LCDC_CLK_ENABLE_REG, + LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN | + LCDC_V2_CORE_CLK_EN); + + if (dpms == DRM_MODE_DPMS_ON) + tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON); + +out: + pm_runtime_put_sync(dev->dev); +} + +irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = dev->dev_private; + uint32_t stat = tilcdc_read_irqstatus(dev); + + if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) { + stop(crtc); + dev_err(dev->dev, "error: %08x\n", stat); + tilcdc_clear_irqstatus(dev, stat); + start(crtc); + } else if (stat & LCDC_PL_LOAD_DONE) { + tilcdc_clear_irqstatus(dev, stat); + } else { + struct drm_pending_vblank_event *event; + unsigned long flags; + uint32_t dirty = tilcdc_crtc->dirty & stat; + + tilcdc_clear_irqstatus(dev, stat); + + if (dirty & LCDC_END_OF_FRAME0) + set_scanout(crtc, 0); + + if (dirty & LCDC_END_OF_FRAME1) + set_scanout(crtc, 1); + + drm_handle_vblank(dev, 0); + + spin_lock_irqsave(&dev->event_lock, flags); + event = tilcdc_crtc->event; + tilcdc_crtc->event = NULL; + if (event) + drm_send_vblank_event(dev, 0, event); + spin_unlock_irqrestore(&dev->event_lock, flags); + + if (dirty && !tilcdc_crtc->dirty) + drm_vblank_put(dev, 0); + } + + if (priv->rev == 2) { + if (stat & LCDC_FRAME_DONE) { + tilcdc_crtc->frame_done = true; + wake_up(&tilcdc_crtc->frame_done_wq); + } + tilcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0); + } + + return IRQ_HANDLED; +} + +void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_pending_vblank_event *event; + struct drm_device *dev = crtc->dev; + unsigned long flags; + + /* Destroy the pending vertical blanking event associated with the + * pending page flip, if any, and disable vertical blanking interrupts. + */ + spin_lock_irqsave(&dev->event_lock, flags); + event = tilcdc_crtc->event; + if (event && event->base.file_priv == file) { + tilcdc_crtc->event = NULL; + event->base.destroy(&event->base); + drm_vblank_put(dev, 0); + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev) +{ + struct tilcdc_crtc *tilcdc_crtc; + struct drm_crtc *crtc; + int ret; + + tilcdc_crtc = kzalloc(sizeof(*tilcdc_crtc), GFP_KERNEL); + if (!tilcdc_crtc) { + dev_err(dev->dev, "allocation failed\n"); + return NULL; + } + + crtc = &tilcdc_crtc->base; + + tilcdc_crtc->dpms = DRM_MODE_DPMS_OFF; + init_waitqueue_head(&tilcdc_crtc->frame_done_wq); + + ret = kfifo_alloc(&tilcdc_crtc->unref_fifo, 16, GFP_KERNEL); + if (ret) { + dev_err(dev->dev, "could not allocate unref FIFO\n"); + goto fail; + } + + INIT_WORK(&tilcdc_crtc->work, unref_worker); + + ret = drm_crtc_init(dev, crtc, &tilcdc_crtc_funcs); + if (ret < 0) + goto fail; + + drm_crtc_helper_add(crtc, &tilcdc_crtc_helper_funcs); + + return crtc; + +fail: + tilcdc_crtc_destroy(crtc); + return NULL; +} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c new file mode 100644 index 000000000000..c5b592dc1970 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* LCDC DRM driver, based on da8xx-fb */ + +#include "tilcdc_drv.h" +#include "tilcdc_regs.h" +#include "tilcdc_tfp410.h" +#include "tilcdc_slave.h" +#include "tilcdc_panel.h" + +#include "drm_fb_helper.h" + +static LIST_HEAD(module_list); + +void tilcdc_module_init(struct tilcdc_module *mod, const char *name, + const struct tilcdc_module_ops *funcs) +{ + mod->name = name; + mod->funcs = funcs; + INIT_LIST_HEAD(&mod->list); + list_add(&mod->list, &module_list); +} + +void tilcdc_module_cleanup(struct tilcdc_module *mod) +{ + list_del(&mod->list); +} + +static struct of_device_id tilcdc_of_match[]; + +static struct drm_framebuffer *tilcdc_fb_create(struct drm_device *dev, + struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd) +{ + return drm_fb_cma_create(dev, file_priv, mode_cmd); +} + +static void tilcdc_fb_output_poll_changed(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + if (priv->fbdev) + drm_fbdev_cma_hotplug_event(priv->fbdev); +} + +static const struct drm_mode_config_funcs mode_config_funcs = { + .fb_create = tilcdc_fb_create, + .output_poll_changed = tilcdc_fb_output_poll_changed, +}; + +static int modeset_init(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + struct tilcdc_module *mod; + + drm_mode_config_init(dev); + + priv->crtc = tilcdc_crtc_create(dev); + + list_for_each_entry(mod, &module_list, list) { + DBG("loading module: %s", mod->name); + mod->funcs->modeset_init(mod, dev); + } + + if ((priv->num_encoders = 0) || (priv->num_connectors == 0)) { + /* oh nos! */ + dev_err(dev->dev, "no encoders/connectors found\n"); + return -ENXIO; + } + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = tilcdc_crtc_max_width(priv->crtc); + dev->mode_config.max_height = 2048; + dev->mode_config.funcs = &mode_config_funcs; + + return 0; +} + +#ifdef CONFIG_CPU_FREQ +static int cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct tilcdc_drm_private *priv = container_of(nb, + struct tilcdc_drm_private, freq_transition); + if (val == CPUFREQ_POSTCHANGE) { + if (priv->lcd_fck_rate != clk_get_rate(priv->clk)) { + priv->lcd_fck_rate = clk_get_rate(priv->clk); + tilcdc_crtc_update_clk(priv->crtc); + } + } + + return 0; +} +#endif + +/* + * DRM operations: + */ + +static int tilcdc_unload(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + struct tilcdc_module *mod, *cur; + + drm_kms_helper_poll_fini(dev); + drm_mode_config_cleanup(dev); + drm_vblank_cleanup(dev); + + pm_runtime_get_sync(dev->dev); + drm_irq_uninstall(dev); + pm_runtime_put_sync(dev->dev); + +#ifdef CONFIG_CPU_FREQ + cpufreq_unregister_notifier(&priv->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +#endif + + if (priv->clk) + clk_put(priv->clk); + + if (priv->mmio) + iounmap(priv->mmio); + + flush_workqueue(priv->wq); + destroy_workqueue(priv->wq); + + dev->dev_private = NULL; + + pm_runtime_disable(dev->dev); + + list_for_each_entry_safe(mod, cur, &module_list, list) { + DBG("destroying module: %s", mod->name); + mod->funcs->destroy(mod); + } + + kfree(priv); + + return 0; +} + +static int tilcdc_load(struct drm_device *dev, unsigned long flags) +{ + struct platform_device *pdev = dev->platformdev; + struct device_node *node = pdev->dev.of_node; + struct tilcdc_drm_private *priv; + struct resource *res; + int ret; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(dev->dev, "failed to allocate private data\n"); + return -ENOMEM; + } + + dev->dev_private = priv; + + priv->wq = alloc_ordered_workqueue("tilcdc", 0); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev->dev, "failed to get memory resource\n"); + ret = -EINVAL; + goto fail; + } + + priv->mmio = ioremap_nocache(res->start, resource_size(res)); + if (!priv->mmio) { + dev_err(dev->dev, "failed to ioremap\n"); + ret = -ENOMEM; + goto fail; + } + + priv->clk = clk_get(dev->dev, "fck"); + if (IS_ERR(priv->clk)) { + dev_err(dev->dev, "failed to get functional clock\n"); + ret = -ENODEV; + goto fail; + } + + priv->disp_clk = clk_get(dev->dev, "dpll_disp_ck"); + if (IS_ERR(priv->clk)) { + dev_err(dev->dev, "failed to get display clock\n"); + ret = -ENODEV; + goto fail; + } + +#ifdef CONFIG_CPU_FREQ + priv->lcd_fck_rate = clk_get_rate(priv->clk); + priv->freq_transition.notifier_call = cpufreq_transition; + ret = cpufreq_register_notifier(&priv->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); + if (ret) { + dev_err(dev->dev, "failed to register cpufreq notifier\n"); + goto fail; + } +#endif + + if (of_property_read_u32(node, "max-bandwidth", &priv->max_bandwidth)) + priv->max_bandwidth = 1280 * 1024 * 60; + + pm_runtime_enable(dev->dev); + + /* Determine LCD IP Version */ + pm_runtime_get_sync(dev->dev); + switch (tilcdc_read(dev, LCDC_PID_REG)) { + case 0x4c100102: + priv->rev = 1; + break; + case 0x4f200800: + case 0x4f201000: + priv->rev = 2; + break; + default: + dev_warn(dev->dev, "Unknown PID Reg value 0x%08x, " + "defaulting to LCD revision 1\n", + tilcdc_read(dev, LCDC_PID_REG)); + priv->rev = 1; + break; + } + + pm_runtime_put_sync(dev->dev); + + ret = modeset_init(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to initialize mode setting\n"); + goto fail; + } + + ret = drm_vblank_init(dev, 1); + if (ret < 0) { + dev_err(dev->dev, "failed to initialize vblank\n"); + goto fail; + } + + pm_runtime_get_sync(dev->dev); + ret = drm_irq_install(dev); + pm_runtime_put_sync(dev->dev); + if (ret < 0) { + dev_err(dev->dev, "failed to install IRQ handler\n"); + goto fail; + } + + platform_set_drvdata(pdev, dev); + + priv->fbdev = drm_fbdev_cma_init(dev, 16, + dev->mode_config.num_crtc, + dev->mode_config.num_connector); + + drm_kms_helper_poll_init(dev); + + return 0; + +fail: + tilcdc_unload(dev); + return ret; +} + +static void tilcdc_preclose(struct drm_device *dev, struct drm_file *file) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + + tilcdc_crtc_cancel_page_flip(priv->crtc, file); +} + +static void tilcdc_lastclose(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + drm_fbdev_cma_restore_mode(priv->fbdev); +} + +static irqreturn_t tilcdc_irq(DRM_IRQ_ARGS) +{ + struct drm_device *dev = arg; + struct tilcdc_drm_private *priv = dev->dev_private; + return tilcdc_crtc_irq(priv->crtc); +} + +static void tilcdc_irq_preinstall(struct drm_device *dev) +{ + tilcdc_clear_irqstatus(dev, 0xffffffff); +} + +static int tilcdc_irq_postinstall(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + + /* enable FIFO underflow irq: */ + if (priv->rev == 1) { + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_UNDERFLOW_INT_ENA); + } else { + tilcdc_set(dev, LCDC_INT_ENABLE_SET_REG, LCDC_V2_UNDERFLOW_INT_ENA); + } + + return 0; +} + +static void tilcdc_irq_uninstall(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + + /* disable irqs that we might have enabled: */ + if (priv->rev == 1) { + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, + LCDC_V1_UNDERFLOW_INT_ENA | LCDC_V1_PL_INT_ENA); + tilcdc_clear(dev, LCDC_DMA_CTRL_REG, LCDC_V1_END_OF_FRAME_INT_ENA); + } else { + tilcdc_clear(dev, LCDC_INT_ENABLE_SET_REG, + LCDC_V2_UNDERFLOW_INT_ENA | LCDC_V2_PL_INT_ENA | + LCDC_V2_END_OF_FRAME0_INT_ENA | LCDC_V2_END_OF_FRAME1_INT_ENA | + LCDC_FRAME_DONE); + } + +} + +static void enable_vblank(struct drm_device *dev, bool enable) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + u32 reg, mask; + + if (priv->rev == 1) { + reg = LCDC_DMA_CTRL_REG; + mask = LCDC_V1_END_OF_FRAME_INT_ENA; + } else { + reg = LCDC_INT_ENABLE_SET_REG; + mask = LCDC_V2_END_OF_FRAME0_INT_ENA | + LCDC_V2_END_OF_FRAME1_INT_ENA | LCDC_FRAME_DONE; + } + + if (enable) + tilcdc_set(dev, reg, mask); + else + tilcdc_clear(dev, reg, mask); +} + +static int tilcdc_enable_vblank(struct drm_device *dev, int crtc) +{ + enable_vblank(dev, true); + return 0; +} + +static void tilcdc_disable_vblank(struct drm_device *dev, int crtc) +{ + enable_vblank(dev, false); +} + +#if defined(CONFIG_DEBUG_FS) || defined(CONFIG_PM_SLEEP) +static const struct { + const char *name; + uint8_t rev; + uint8_t save; + uint32_t reg; +} registers[] = { +#define REG(rev, save, reg) { #reg, rev, save, reg } + /* exists in revision 1: */ + REG(1, false, LCDC_PID_REG), + REG(1, true, LCDC_CTRL_REG), + REG(1, false, LCDC_STAT_REG), + REG(1, true, LCDC_RASTER_CTRL_REG), + REG(1, true, LCDC_RASTER_TIMING_0_REG), + REG(1, true, LCDC_RASTER_TIMING_1_REG), + REG(1, true, LCDC_RASTER_TIMING_2_REG), + REG(1, true, LCDC_DMA_CTRL_REG), + REG(1, true, LCDC_DMA_FB_BASE_ADDR_0_REG), + REG(1, true, LCDC_DMA_FB_CEILING_ADDR_0_REG), + REG(1, true, LCDC_DMA_FB_BASE_ADDR_1_REG), + REG(1, true, LCDC_DMA_FB_CEILING_ADDR_1_REG), + /* new in revision 2: */ + REG(2, false, LCDC_RAW_STAT_REG), + REG(2, false, LCDC_MASKED_STAT_REG), + REG(2, false, LCDC_INT_ENABLE_SET_REG), + REG(2, false, LCDC_INT_ENABLE_CLR_REG), + REG(2, false, LCDC_END_OF_INT_IND_REG), + REG(2, true, LCDC_CLK_ENABLE_REG), + REG(2, true, LCDC_INT_ENABLE_SET_REG), +#undef REG +}; +#endif + +#ifdef CONFIG_DEBUG_FS +static int tilcdc_regs_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct tilcdc_drm_private *priv = dev->dev_private; + unsigned i; + + pm_runtime_get_sync(dev->dev); + + seq_printf(m, "revision: %d\n", priv->rev); + + for (i = 0; i < ARRAY_SIZE(registers); i++) + if (priv->rev >= registers[i].rev) + seq_printf(m, "%s:\t %08x\n", registers[i].name, + tilcdc_read(dev, registers[i].reg)); + + pm_runtime_put_sync(dev->dev); + + return 0; +} + +static int tilcdc_mm_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + return drm_mm_dump_table(m, dev->mm_private); +} + +static struct drm_info_list tilcdc_debugfs_list[] = { + { "regs", tilcdc_regs_show, 0 }, + { "mm", tilcdc_mm_show, 0 }, + { "fb", drm_fb_cma_debugfs_show, 0 }, +}; + +static int tilcdc_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *dev = minor->dev; + struct tilcdc_module *mod; + int ret; + + ret = drm_debugfs_create_files(tilcdc_debugfs_list, + ARRAY_SIZE(tilcdc_debugfs_list), + minor->debugfs_root, minor); + + list_for_each_entry(mod, &module_list, list) + if (mod->funcs->debugfs_init) + mod->funcs->debugfs_init(mod, minor); + + if (ret) { + dev_err(dev->dev, "could not install tilcdc_debugfs_list\n"); + return ret; + } + + return ret; +} + +static void tilcdc_debugfs_cleanup(struct drm_minor *minor) +{ + struct tilcdc_module *mod; + drm_debugfs_remove_files(tilcdc_debugfs_list, + ARRAY_SIZE(tilcdc_debugfs_list), minor); + + list_for_each_entry(mod, &module_list, list) + if (mod->funcs->debugfs_cleanup) + mod->funcs->debugfs_cleanup(mod, minor); +} +#endif + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .fasync = drm_fasync, + .llseek = no_llseek, + .mmap = drm_gem_cma_mmap, +}; + +static struct drm_driver tilcdc_driver = { + .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET, + .load = tilcdc_load, + .unload = tilcdc_unload, + .preclose = tilcdc_preclose, + .lastclose = tilcdc_lastclose, + .irq_handler = tilcdc_irq, + .irq_preinstall = tilcdc_irq_preinstall, + .irq_postinstall = tilcdc_irq_postinstall, + .irq_uninstall = tilcdc_irq_uninstall, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = tilcdc_enable_vblank, + .disable_vblank = tilcdc_disable_vblank, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_cma_dumb_destroy, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = tilcdc_debugfs_init, + .debugfs_cleanup = tilcdc_debugfs_cleanup, +#endif + .fops = &fops, + .name = "tilcdc", + .desc = "TI LCD Controller DRM", + .date = "20121205", + .major = 1, + .minor = 0, +}; + +/* + * Power management: + */ + +#ifdef CONFIG_PM_SLEEP +static int tilcdc_pm_suspend(struct device *dev) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct tilcdc_drm_private *priv = ddev->dev_private; + unsigned i, n = 0; + + drm_kms_helper_poll_disable(ddev); + + /* Save register state: */ + for (i = 0; i < ARRAY_SIZE(registers); i++) + if (registers[i].save && (priv->rev >= registers[i].rev)) + priv->saved_register[n++] = tilcdc_read(ddev, registers[i].reg); + + return 0; +} + +static int tilcdc_pm_resume(struct device *dev) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct tilcdc_drm_private *priv = ddev->dev_private; + unsigned i, n = 0; + + /* Restore register state: */ + for (i = 0; i < ARRAY_SIZE(registers); i++) + if (registers[i].save && (priv->rev >= registers[i].rev)) + tilcdc_write(ddev, registers[i].reg, priv->saved_register[n++]); + + drm_kms_helper_poll_enable(ddev); + + return 0; +} +#endif + +static const struct dev_pm_ops tilcdc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tilcdc_pm_suspend, tilcdc_pm_resume) +}; + +/* + * Platform driver: + */ + +static int tilcdc_pdev_probe(struct platform_device *pdev) +{ + /* bail out early if no DT data: */ + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "device-tree data is missing\n"); + return -ENXIO; + } + + return drm_platform_init(&tilcdc_driver, pdev); +} + +static int tilcdc_pdev_remove(struct platform_device *pdev) +{ + drm_platform_exit(&tilcdc_driver, pdev); + + return 0; +} + +static struct of_device_id tilcdc_of_match[] = { + { .compatible = "ti,am33xx-tilcdc", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tilcdc_of_match); + +static struct platform_driver tilcdc_platform_driver = { + .probe = tilcdc_pdev_probe, + .remove = tilcdc_pdev_remove, + .driver = { + .owner = THIS_MODULE, + .name = "tilcdc", + .pm = &tilcdc_pm_ops, + .of_match_table = tilcdc_of_match, + }, +}; + +static int __init tilcdc_drm_init(void) +{ + DBG("init"); + tilcdc_tfp410_init(); + tilcdc_slave_init(); + tilcdc_panel_init(); + return platform_driver_register(&tilcdc_platform_driver); +} + +static void __exit tilcdc_drm_fini(void) +{ + DBG("fini"); + tilcdc_tfp410_fini(); + tilcdc_slave_fini(); + tilcdc_panel_fini(); + platform_driver_unregister(&tilcdc_platform_driver); +} + +late_initcall(tilcdc_drm_init); +module_exit(tilcdc_drm_fini); + +MODULE_AUTHOR("Rob Clark <robdclark@gmail.com"); +MODULE_DESCRIPTION("TI LCD Controller DRM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.h b/drivers/gpu/drm/tilcdc/tilcdc_drv.h new file mode 100644 index 000000000000..8242b5a4307b --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TILCDC_DRV_H__ +#define __TILCDC_DRV_H__ + +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/list.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> + +struct tilcdc_drm_private { + void __iomem *mmio; + + struct clk *disp_clk; /* display dpll */ + struct clk *clk; /* functional clock */ + int rev; /* IP revision */ + + /* don't attempt resolutions w/ higher W * H * Hz: */ + uint32_t max_bandwidth; + + /* register contents saved across suspend/resume: */ + u32 saved_register[12]; + +#ifdef CONFIG_CPU_FREQ + struct notifier_block freq_transition; + unsigned int lcd_fck_rate; +#endif + + struct workqueue_struct *wq; + + struct drm_fbdev_cma *fbdev; + + struct drm_crtc *crtc; + + unsigned int num_encoders; + struct drm_encoder *encoders[8]; + + unsigned int num_connectors; + struct drm_connector *connectors[8]; +}; + +/* Sub-module for display. Since we don't know at compile time what panels + * or display adapter(s) might be present (for ex, off chip dvi/tfp410, + * hdmi encoder, various lcd panels), the connector/encoder(s) are split into + * separate drivers. If they are probed and found to be present, they + * register themselves with tilcdc_register_module(). + */ +struct tilcdc_module; + +struct tilcdc_module_ops { + /* create appropriate encoders/connectors: */ + int (*modeset_init)(struct tilcdc_module *mod, struct drm_device *dev); + void (*destroy)(struct tilcdc_module *mod); +#ifdef CONFIG_DEBUG_FS + /* create debugfs nodes (can be NULL): */ + int (*debugfs_init)(struct tilcdc_module *mod, struct drm_minor *minor); + /* cleanup debugfs nodes (can be NULL): */ + void (*debugfs_cleanup)(struct tilcdc_module *mod, struct drm_minor *minor); +#endif +}; + +struct tilcdc_module { + const char *name; + struct list_head list; + const struct tilcdc_module_ops *funcs; +}; + +void tilcdc_module_init(struct tilcdc_module *mod, const char *name, + const struct tilcdc_module_ops *funcs); +void tilcdc_module_cleanup(struct tilcdc_module *mod); + + +/* Panel config that needs to be set in the crtc, but is not coming from + * the mode timings. The display module is expected to call + * tilcdc_crtc_set_panel_info() to set this during modeset. + */ +struct tilcdc_panel_info { + + /* AC Bias Pin Frequency */ + uint32_t ac_bias; + + /* AC Bias Pin Transitions per Interrupt */ + uint32_t ac_bias_intrpt; + + /* DMA burst size */ + uint32_t dma_burst_sz; + + /* Bits per pixel */ + uint32_t bpp; + + /* FIFO DMA Request Delay */ + uint32_t fdd; + + /* TFT Alternative Signal Mapping (Only for active) */ + bool tft_alt_mode; + + /* Invert pixel clock */ + bool invert_pxl_clk; + + /* Horizontal and Vertical Sync Edge: 0=rising 1=falling */ + uint32_t sync_edge; + + /* Horizontal and Vertical Sync: Control: 0=ignore */ + uint32_t sync_ctrl; + + /* Raster Data Order Select: 1=Most-to-least 0=Least-to-most */ + uint32_t raster_order; + + /* DMA FIFO threshold */ + uint32_t fifo_th; +}; + +#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) + +struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev); +void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file); +irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc); +void tilcdc_crtc_update_clk(struct drm_crtc *crtc); +void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc, + const struct tilcdc_panel_info *info); +int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode); +int tilcdc_crtc_max_width(struct drm_crtc *crtc); + +#endif /* __TILCDC_DRV_H__ */ diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel.c b/drivers/gpu/drm/tilcdc/tilcdc_panel.c new file mode 100644 index 000000000000..580b74e2022b --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_panel.c @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/pinctrl/pinmux.h> +#include <linux/pinctrl/consumer.h> +#include <linux/backlight.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include "tilcdc_drv.h" + +struct panel_module { + struct tilcdc_module base; + struct tilcdc_panel_info *info; + struct display_timings *timings; + struct backlight_device *backlight; +}; +#define to_panel_module(x) container_of(x, struct panel_module, base) + + +/* + * Encoder: + */ + +struct panel_encoder { + struct drm_encoder base; + struct panel_module *mod; +}; +#define to_panel_encoder(x) container_of(x, struct panel_encoder, base) + + +static void panel_encoder_destroy(struct drm_encoder *encoder) +{ + struct panel_encoder *panel_encoder = to_panel_encoder(encoder); + drm_encoder_cleanup(encoder); + kfree(panel_encoder); +} + +static void panel_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct panel_encoder *panel_encoder = to_panel_encoder(encoder); + struct backlight_device *backlight = panel_encoder->mod->backlight; + + if (!backlight) + return; + + backlight->props.power = mode == DRM_MODE_DPMS_ON + ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; + backlight_update_status(backlight); +} + +static bool panel_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ + return true; +} + +static void panel_encoder_prepare(struct drm_encoder *encoder) +{ + struct panel_encoder *panel_encoder = to_panel_encoder(encoder); + panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); + tilcdc_crtc_set_panel_info(encoder->crtc, panel_encoder->mod->info); +} + +static void panel_encoder_commit(struct drm_encoder *encoder) +{ + panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void panel_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ +} + +static const struct drm_encoder_funcs panel_encoder_funcs = { + .destroy = panel_encoder_destroy, +}; + +static const struct drm_encoder_helper_funcs panel_encoder_helper_funcs = { + .dpms = panel_encoder_dpms, + .mode_fixup = panel_encoder_mode_fixup, + .prepare = panel_encoder_prepare, + .commit = panel_encoder_commit, + .mode_set = panel_encoder_mode_set, +}; + +static struct drm_encoder *panel_encoder_create(struct drm_device *dev, + struct panel_module *mod) +{ + struct panel_encoder *panel_encoder; + struct drm_encoder *encoder; + int ret; + + panel_encoder = kzalloc(sizeof(*panel_encoder), GFP_KERNEL); + if (!panel_encoder) { + dev_err(dev->dev, "allocation failed\n"); + return NULL; + } + + panel_encoder->mod = mod; + + encoder = &panel_encoder->base; + encoder->possible_crtcs = 1; + + ret = drm_encoder_init(dev, encoder, &panel_encoder_funcs, + DRM_MODE_ENCODER_LVDS); + if (ret < 0) + goto fail; + + drm_encoder_helper_add(encoder, &panel_encoder_helper_funcs); + + return encoder; + +fail: + panel_encoder_destroy(encoder); + return NULL; +} + +/* + * Connector: + */ + +struct panel_connector { + struct drm_connector base; + + struct drm_encoder *encoder; /* our connected encoder */ + struct panel_module *mod; +}; +#define to_panel_connector(x) container_of(x, struct panel_connector, base) + + +static void panel_connector_destroy(struct drm_connector *connector) +{ + struct panel_connector *panel_connector = to_panel_connector(connector); + drm_connector_cleanup(connector); + kfree(panel_connector); +} + +static enum drm_connector_status panel_connector_detect( + struct drm_connector *connector, + bool force) +{ + return connector_status_connected; +} + +static int panel_connector_get_modes(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct panel_connector *panel_connector = to_panel_connector(connector); + struct display_timings *timings = panel_connector->mod->timings; + int i; + + for (i = 0; i < timings->num_timings; i++) { + struct drm_display_mode *mode = drm_mode_create(dev); + struct videomode vm; + + if (videomode_from_timing(timings, &vm, i)) + break; + + drm_display_mode_from_videomode(&vm, mode); + + mode->type = DRM_MODE_TYPE_DRIVER; + + if (timings->native_mode == i) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int panel_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct tilcdc_drm_private *priv = connector->dev->dev_private; + /* our only constraints are what the crtc can generate: */ + return tilcdc_crtc_mode_valid(priv->crtc, mode); +} + +static struct drm_encoder *panel_connector_best_encoder( + struct drm_connector *connector) +{ + struct panel_connector *panel_connector = to_panel_connector(connector); + return panel_connector->encoder; +} + +static const struct drm_connector_funcs panel_connector_funcs = { + .destroy = panel_connector_destroy, + .dpms = drm_helper_connector_dpms, + .detect = panel_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, +}; + +static const struct drm_connector_helper_funcs panel_connector_helper_funcs = { + .get_modes = panel_connector_get_modes, + .mode_valid = panel_connector_mode_valid, + .best_encoder = panel_connector_best_encoder, +}; + +static struct drm_connector *panel_connector_create(struct drm_device *dev, + struct panel_module *mod, struct drm_encoder *encoder) +{ + struct panel_connector *panel_connector; + struct drm_connector *connector; + int ret; + + panel_connector = kzalloc(sizeof(*panel_connector), GFP_KERNEL); + if (!panel_connector) { + dev_err(dev->dev, "allocation failed\n"); + return NULL; + } + + panel_connector->encoder = encoder; + panel_connector->mod = mod; + + connector = &panel_connector->base; + + drm_connector_init(dev, connector, &panel_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_connector_helper_add(connector, &panel_connector_helper_funcs); + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto fail; + + drm_sysfs_connector_add(connector); + + return connector; + +fail: + panel_connector_destroy(connector); + return NULL; +} + +/* + * Module: + */ + +static int panel_modeset_init(struct tilcdc_module *mod, struct drm_device *dev) +{ + struct panel_module *panel_mod = to_panel_module(mod); + struct tilcdc_drm_private *priv = dev->dev_private; + struct drm_encoder *encoder; + struct drm_connector *connector; + + encoder = panel_encoder_create(dev, panel_mod); + if (!encoder) + return -ENOMEM; + + connector = panel_connector_create(dev, panel_mod, encoder); + if (!connector) + return -ENOMEM; + + priv->encoders[priv->num_encoders++] = encoder; + priv->connectors[priv->num_connectors++] = connector; + + return 0; +} + +static void panel_destroy(struct tilcdc_module *mod) +{ + struct panel_module *panel_mod = to_panel_module(mod); + + if (panel_mod->timings) { + display_timings_release(panel_mod->timings); + kfree(panel_mod->timings); + } + + tilcdc_module_cleanup(mod); + kfree(panel_mod->info); + kfree(panel_mod); +} + +static const struct tilcdc_module_ops panel_module_ops = { + .modeset_init = panel_modeset_init, + .destroy = panel_destroy, +}; + +/* + * Device: + */ + +/* maybe move this somewhere common if it is needed by other outputs? */ +static struct tilcdc_panel_info * of_get_panel_info(struct device_node *np) +{ + struct device_node *info_np; + struct tilcdc_panel_info *info; + int ret = 0; + + if (!np) { + pr_err("%s: no devicenode given\n", __func__); + return NULL; + } + + info_np = of_get_child_by_name(np, "panel-info"); + if (!info_np) { + pr_err("%s: could not find panel-info node\n", __func__); + return NULL; + } + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + pr_err("%s: allocation failed\n", __func__); + return NULL; + } + + ret |= of_property_read_u32(info_np, "ac-bias", &info->ac_bias); + ret |= of_property_read_u32(info_np, "ac-bias-intrpt", &info->ac_bias_intrpt); + ret |= of_property_read_u32(info_np, "dma-burst-sz", &info->dma_burst_sz); + ret |= of_property_read_u32(info_np, "bpp", &info->bpp); + ret |= of_property_read_u32(info_np, "fdd", &info->fdd); + ret |= of_property_read_u32(info_np, "sync-edge", &info->sync_edge); + ret |= of_property_read_u32(info_np, "sync-ctrl", &info->sync_ctrl); + ret |= of_property_read_u32(info_np, "raster-order", &info->raster_order); + ret |= of_property_read_u32(info_np, "fifo-th", &info->fifo_th); + + /* optional: */ + info->tft_alt_mode = of_property_read_bool(info_np, "tft-alt-mode"); + info->invert_pxl_clk = of_property_read_bool(info_np, "invert-pxl-clk"); + + if (ret) { + pr_err("%s: error reading panel-info properties\n", __func__); + kfree(info); + return NULL; + } + + return info; +} + +static struct of_device_id panel_of_match[]; + +static int panel_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct panel_module *panel_mod; + struct tilcdc_module *mod; + struct pinctrl *pinctrl; + int ret = -EINVAL; + + + /* bail out early if no DT data: */ + if (!node) { + dev_err(&pdev->dev, "device-tree data is missing\n"); + return -ENXIO; + } + + panel_mod = kzalloc(sizeof(*panel_mod), GFP_KERNEL); + if (!panel_mod) + return -ENOMEM; + + mod = &panel_mod->base; + + tilcdc_module_init(mod, "panel", &panel_module_ops); + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) + dev_warn(&pdev->dev, "pins are not configured\n"); + + + panel_mod->timings = of_get_display_timings(node); + if (!panel_mod->timings) { + dev_err(&pdev->dev, "could not get panel timings\n"); + goto fail; + } + + panel_mod->info = of_get_panel_info(node); + if (!panel_mod->info) { + dev_err(&pdev->dev, "could not get panel info\n"); + goto fail; + } + + panel_mod->backlight = of_find_backlight_by_node(node); + if (panel_mod->backlight) + dev_info(&pdev->dev, "found backlight\n"); + + return 0; + +fail: + panel_destroy(mod); + return ret; +} + +static int panel_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct of_device_id panel_of_match[] = { + { .compatible = "ti,tilcdc,panel", }, + { }, +}; +MODULE_DEVICE_TABLE(of, panel_of_match); + +struct platform_driver panel_driver = { + .probe = panel_probe, + .remove = panel_remove, + .driver = { + .owner = THIS_MODULE, + .name = "panel", + .of_match_table = panel_of_match, + }, +}; + +int __init tilcdc_panel_init(void) +{ + return platform_driver_register(&panel_driver); +} + +void __exit tilcdc_panel_fini(void) +{ + platform_driver_unregister(&panel_driver); +} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel.h b/drivers/gpu/drm/tilcdc/tilcdc_panel.h new file mode 100644 index 000000000000..7db40aacc74a --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_panel.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TILCDC_PANEL_H__ +#define __TILCDC_PANEL_H__ + +/* sub-module for generic lcd panel output */ + +int tilcdc_panel_init(void); +void tilcdc_panel_fini(void); + +#endif /* __TILCDC_PANEL_H__ */ diff --git a/drivers/gpu/drm/tilcdc/tilcdc_regs.h b/drivers/gpu/drm/tilcdc/tilcdc_regs.h new file mode 100644 index 000000000000..17fd1b45428a --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_regs.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TILCDC_REGS_H__ +#define __TILCDC_REGS_H__ + +/* LCDC register definitions, based on da8xx-fb */ + +#include <linux/bitops.h> + +#include "tilcdc_drv.h" + +/* LCDC Status Register */ +#define LCDC_END_OF_FRAME1 BIT(9) +#define LCDC_END_OF_FRAME0 BIT(8) +#define LCDC_PL_LOAD_DONE BIT(6) +#define LCDC_FIFO_UNDERFLOW BIT(5) +#define LCDC_SYNC_LOST BIT(2) +#define LCDC_FRAME_DONE BIT(0) + +/* LCDC DMA Control Register */ +#define LCDC_DMA_BURST_SIZE(x) ((x) << 4) +#define LCDC_DMA_BURST_1 0x0 +#define LCDC_DMA_BURST_2 0x1 +#define LCDC_DMA_BURST_4 0x2 +#define LCDC_DMA_BURST_8 0x3 +#define LCDC_DMA_BURST_16 0x4 +#define LCDC_V1_END_OF_FRAME_INT_ENA BIT(2) +#define LCDC_V2_END_OF_FRAME0_INT_ENA BIT(8) +#define LCDC_V2_END_OF_FRAME1_INT_ENA BIT(9) +#define LCDC_DUAL_FRAME_BUFFER_ENABLE BIT(0) + +/* LCDC Control Register */ +#define LCDC_CLK_DIVISOR(x) ((x) << 8) +#define LCDC_RASTER_MODE 0x01 + +/* LCDC Raster Control Register */ +#define LCDC_PALETTE_LOAD_MODE(x) ((x) << 20) +#define PALETTE_AND_DATA 0x00 +#define PALETTE_ONLY 0x01 +#define DATA_ONLY 0x02 + +#define LCDC_MONO_8BIT_MODE BIT(9) +#define LCDC_RASTER_ORDER BIT(8) +#define LCDC_TFT_MODE BIT(7) +#define LCDC_V1_UNDERFLOW_INT_ENA BIT(6) +#define LCDC_V2_UNDERFLOW_INT_ENA BIT(5) +#define LCDC_V1_PL_INT_ENA BIT(4) +#define LCDC_V2_PL_INT_ENA BIT(6) +#define LCDC_MONOCHROME_MODE BIT(1) +#define LCDC_RASTER_ENABLE BIT(0) +#define LCDC_TFT_ALT_ENABLE BIT(23) +#define LCDC_STN_565_ENABLE BIT(24) +#define LCDC_V2_DMA_CLK_EN BIT(2) +#define LCDC_V2_LIDD_CLK_EN BIT(1) +#define LCDC_V2_CORE_CLK_EN BIT(0) +#define LCDC_V2_LPP_B10 26 +#define LCDC_V2_TFT_24BPP_MODE BIT(25) +#define LCDC_V2_TFT_24BPP_UNPACK BIT(26) + +/* LCDC Raster Timing 2 Register */ +#define LCDC_AC_BIAS_TRANSITIONS_PER_INT(x) ((x) << 16) +#define LCDC_AC_BIAS_FREQUENCY(x) ((x) << 8) +#define LCDC_SYNC_CTRL BIT(25) +#define LCDC_SYNC_EDGE BIT(24) +#define LCDC_INVERT_PIXEL_CLOCK BIT(22) +#define LCDC_INVERT_HSYNC BIT(21) +#define LCDC_INVERT_VSYNC BIT(20) + +/* LCDC Block */ +#define LCDC_PID_REG 0x0 +#define LCDC_CTRL_REG 0x4 +#define LCDC_STAT_REG 0x8 +#define LCDC_RASTER_CTRL_REG 0x28 +#define LCDC_RASTER_TIMING_0_REG 0x2c +#define LCDC_RASTER_TIMING_1_REG 0x30 +#define LCDC_RASTER_TIMING_2_REG 0x34 +#define LCDC_DMA_CTRL_REG 0x40 +#define LCDC_DMA_FB_BASE_ADDR_0_REG 0x44 +#define LCDC_DMA_FB_CEILING_ADDR_0_REG 0x48 +#define LCDC_DMA_FB_BASE_ADDR_1_REG 0x4c +#define LCDC_DMA_FB_CEILING_ADDR_1_REG 0x50 + +/* Interrupt Registers available only in Version 2 */ +#define LCDC_RAW_STAT_REG 0x58 +#define LCDC_MASKED_STAT_REG 0x5c +#define LCDC_INT_ENABLE_SET_REG 0x60 +#define LCDC_INT_ENABLE_CLR_REG 0x64 +#define LCDC_END_OF_INT_IND_REG 0x68 + +/* Clock registers available only on Version 2 */ +#define LCDC_CLK_ENABLE_REG 0x6c +#define LCDC_CLK_RESET_REG 0x70 +#define LCDC_CLK_MAIN_RESET BIT(3) + + +/* + * Helpers: + */ + +static inline void tilcdc_write(struct drm_device *dev, u32 reg, u32 data) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + iowrite32(data, priv->mmio + reg); +} + +static inline u32 tilcdc_read(struct drm_device *dev, u32 reg) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + return ioread32(priv->mmio + reg); +} + +static inline void tilcdc_set(struct drm_device *dev, u32 reg, u32 mask) +{ + tilcdc_write(dev, reg, tilcdc_read(dev, reg) | mask); +} + +static inline void tilcdc_clear(struct drm_device *dev, u32 reg, u32 mask) +{ + tilcdc_write(dev, reg, tilcdc_read(dev, reg) & ~mask); +} + +/* the register to read/clear irqstatus differs between v1 and v2 of the IP */ +static inline u32 tilcdc_irqstatus_reg(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + return (priv->rev == 2) ? LCDC_MASKED_STAT_REG : LCDC_STAT_REG; +} + +static inline u32 tilcdc_read_irqstatus(struct drm_device *dev) +{ + return tilcdc_read(dev, tilcdc_irqstatus_reg(dev)); +} + +static inline void tilcdc_clear_irqstatus(struct drm_device *dev, u32 mask) +{ + tilcdc_write(dev, tilcdc_irqstatus_reg(dev), mask); +} + +#endif /* __TILCDC_REGS_H__ */ diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.c b/drivers/gpu/drm/tilcdc/tilcdc_slave.c new file mode 100644 index 000000000000..568dc1c08e6c --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.c @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/i2c.h> +#include <linux/of_i2c.h> +#include <linux/pinctrl/pinmux.h> +#include <linux/pinctrl/consumer.h> +#include <drm/drm_encoder_slave.h> + +#include "tilcdc_drv.h" + +struct slave_module { + struct tilcdc_module base; + struct i2c_adapter *i2c; +}; +#define to_slave_module(x) container_of(x, struct slave_module, base) + +static const struct tilcdc_panel_info slave_info = { + .bpp = 16, + .ac_bias = 255, + .ac_bias_intrpt = 0, + .dma_burst_sz = 16, + .fdd = 0x80, + .tft_alt_mode = 0, + .sync_edge = 0, + .sync_ctrl = 1, + .raster_order = 0, +}; + + +/* + * Encoder: + */ + +struct slave_encoder { + struct drm_encoder_slave base; + struct slave_module *mod; +}; +#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base) + +static inline struct drm_encoder_slave_funcs * +get_slave_funcs(struct drm_encoder *enc) +{ + return to_encoder_slave(enc)->slave_funcs; +} + +static void slave_encoder_destroy(struct drm_encoder *encoder) +{ + struct slave_encoder *slave_encoder = to_slave_encoder(encoder); + if (get_slave_funcs(encoder)) + get_slave_funcs(encoder)->destroy(encoder); + drm_encoder_cleanup(encoder); + kfree(slave_encoder); +} + +static void slave_encoder_prepare(struct drm_encoder *encoder) +{ + drm_i2c_encoder_prepare(encoder); + tilcdc_crtc_set_panel_info(encoder->crtc, &slave_info); +} + +static const struct drm_encoder_funcs slave_encoder_funcs = { + .destroy = slave_encoder_destroy, +}; + +static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = { + .dpms = drm_i2c_encoder_dpms, + .mode_fixup = drm_i2c_encoder_mode_fixup, + .prepare = slave_encoder_prepare, + .commit = drm_i2c_encoder_commit, + .mode_set = drm_i2c_encoder_mode_set, + .save = drm_i2c_encoder_save, + .restore = drm_i2c_encoder_restore, +}; + +static const struct i2c_board_info info = { + I2C_BOARD_INFO("tda998x", 0x70) +}; + +static struct drm_encoder *slave_encoder_create(struct drm_device *dev, + struct slave_module *mod) +{ + struct slave_encoder *slave_encoder; + struct drm_encoder *encoder; + int ret; + + slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL); + if (!slave_encoder) { + dev_err(dev->dev, "allocation failed\n"); + return NULL; + } + + slave_encoder->mod = mod; + + encoder = &slave_encoder->base.base; + encoder->possible_crtcs = 1; + + ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (ret) + goto fail; + + drm_encoder_helper_add(encoder, &slave_encoder_helper_funcs); + + ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), mod->i2c, &info); + if (ret) + goto fail; + + return encoder; + +fail: + slave_encoder_destroy(encoder); + return NULL; +} + +/* + * Connector: + */ + +struct slave_connector { + struct drm_connector base; + + struct drm_encoder *encoder; /* our connected encoder */ + struct slave_module *mod; +}; +#define to_slave_connector(x) container_of(x, struct slave_connector, base) + +static void slave_connector_destroy(struct drm_connector *connector) +{ + struct slave_connector *slave_connector = to_slave_connector(connector); + drm_connector_cleanup(connector); + kfree(slave_connector); +} + +static enum drm_connector_status slave_connector_detect( + struct drm_connector *connector, + bool force) +{ + struct drm_encoder *encoder = to_slave_connector(connector)->encoder; + return get_slave_funcs(encoder)->detect(encoder, connector); +} + +static int slave_connector_get_modes(struct drm_connector *connector) +{ + struct drm_encoder *encoder = to_slave_connector(connector)->encoder; + return get_slave_funcs(encoder)->get_modes(encoder, connector); +} + +static int slave_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_encoder *encoder = to_slave_connector(connector)->encoder; + struct tilcdc_drm_private *priv = connector->dev->dev_private; + int ret; + + ret = tilcdc_crtc_mode_valid(priv->crtc, mode); + if (ret != MODE_OK) + return ret; + + return get_slave_funcs(encoder)->mode_valid(encoder, mode); +} + +static struct drm_encoder *slave_connector_best_encoder( + struct drm_connector *connector) +{ + struct slave_connector *slave_connector = to_slave_connector(connector); + return slave_connector->encoder; +} + +static int slave_connector_set_property(struct drm_connector *connector, + struct drm_property *property, uint64_t value) +{ + struct drm_encoder *encoder = to_slave_connector(connector)->encoder; + return get_slave_funcs(encoder)->set_property(encoder, + connector, property, value); +} + +static const struct drm_connector_funcs slave_connector_funcs = { + .destroy = slave_connector_destroy, + .dpms = drm_helper_connector_dpms, + .detect = slave_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = slave_connector_set_property, +}; + +static const struct drm_connector_helper_funcs slave_connector_helper_funcs = { + .get_modes = slave_connector_get_modes, + .mode_valid = slave_connector_mode_valid, + .best_encoder = slave_connector_best_encoder, +}; + +static struct drm_connector *slave_connector_create(struct drm_device *dev, + struct slave_module *mod, struct drm_encoder *encoder) +{ + struct slave_connector *slave_connector; + struct drm_connector *connector; + int ret; + + slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL); + if (!slave_connector) { + dev_err(dev->dev, "allocation failed\n"); + return NULL; + } + + slave_connector->encoder = encoder; + slave_connector->mod = mod; + + connector = &slave_connector->base; + + drm_connector_init(dev, connector, &slave_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + drm_connector_helper_add(connector, &slave_connector_helper_funcs); + + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + get_slave_funcs(encoder)->create_resources(encoder, connector); + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto fail; + + drm_sysfs_connector_add(connector); + + return connector; + +fail: + slave_connector_destroy(connector); + return NULL; +} + +/* + * Module: + */ + +static int slave_modeset_init(struct tilcdc_module *mod, struct drm_device *dev) +{ + struct slave_module *slave_mod = to_slave_module(mod); + struct tilcdc_drm_private *priv = dev->dev_private; + struct drm_encoder *encoder; + struct drm_connector *connector; + + encoder = slave_encoder_create(dev, slave_mod); + if (!encoder) + return -ENOMEM; + + connector = slave_connector_create(dev, slave_mod, encoder); + if (!connector) + return -ENOMEM; + + priv->encoders[priv->num_encoders++] = encoder; + priv->connectors[priv->num_connectors++] = connector; + + return 0; +} + +static void slave_destroy(struct tilcdc_module *mod) +{ + struct slave_module *slave_mod = to_slave_module(mod); + + tilcdc_module_cleanup(mod); + kfree(slave_mod); +} + +static const struct tilcdc_module_ops slave_module_ops = { + .modeset_init = slave_modeset_init, + .destroy = slave_destroy, +}; + +/* + * Device: + */ + +static struct of_device_id slave_of_match[]; + +static int slave_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device_node *i2c_node; + struct slave_module *slave_mod; + struct tilcdc_module *mod; + struct pinctrl *pinctrl; + uint32_t i2c_phandle; + int ret = -EINVAL; + + /* bail out early if no DT data: */ + if (!node) { + dev_err(&pdev->dev, "device-tree data is missing\n"); + return -ENXIO; + } + + slave_mod = kzalloc(sizeof(*slave_mod), GFP_KERNEL); + if (!slave_mod) + return -ENOMEM; + + mod = &slave_mod->base; + + tilcdc_module_init(mod, "slave", &slave_module_ops); + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) + dev_warn(&pdev->dev, "pins are not configured\n"); + + if (of_property_read_u32(node, "i2c", &i2c_phandle)) { + dev_err(&pdev->dev, "could not get i2c bus phandle\n"); + goto fail; + } + + i2c_node = of_find_node_by_phandle(i2c_phandle); + if (!i2c_node) { + dev_err(&pdev->dev, "could not get i2c bus node\n"); + goto fail; + } + + slave_mod->i2c = of_find_i2c_adapter_by_node(i2c_node); + if (!slave_mod->i2c) { + dev_err(&pdev->dev, "could not get i2c\n"); + goto fail; + } + + of_node_put(i2c_node); + + return 0; + +fail: + slave_destroy(mod); + return ret; +} + +static int slave_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct of_device_id slave_of_match[] = { + { .compatible = "ti,tilcdc,slave", }, + { }, +}; +MODULE_DEVICE_TABLE(of, slave_of_match); + +struct platform_driver slave_driver = { + .probe = slave_probe, + .remove = slave_remove, + .driver = { + .owner = THIS_MODULE, + .name = "slave", + .of_match_table = slave_of_match, + }, +}; + +int __init tilcdc_slave_init(void) +{ + return platform_driver_register(&slave_driver); +} + +void __exit tilcdc_slave_fini(void) +{ + platform_driver_unregister(&slave_driver); +} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.h b/drivers/gpu/drm/tilcdc/tilcdc_slave.h new file mode 100644 index 000000000000..2f8504848320 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TILCDC_SLAVE_H__ +#define __TILCDC_SLAVE_H__ + +/* sub-module for i2c slave encoder output */ + +int tilcdc_slave_init(void); +void tilcdc_slave_fini(void); + +#endif /* __TILCDC_SLAVE_H__ */ diff --git a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c new file mode 100644 index 000000000000..58d487ba2414 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/i2c.h> +#include <linux/of_i2c.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/pinctrl/pinmux.h> +#include <linux/pinctrl/consumer.h> + +#include "tilcdc_drv.h" + +struct tfp410_module { + struct tilcdc_module base; + struct i2c_adapter *i2c; + int gpio; +}; +#define to_tfp410_module(x) container_of(x, struct tfp410_module, base) + + +static const struct tilcdc_panel_info dvi_info = { + .ac_bias = 255, + .ac_bias_intrpt = 0, + .dma_burst_sz = 16, + .bpp = 16, + .fdd = 0x80, + .tft_alt_mode = 0, + .sync_edge = 0, + .sync_ctrl = 1, + .raster_order = 0, +}; + +/* + * Encoder: + */ + +struct tfp410_encoder { + struct drm_encoder base; + struct tfp410_module *mod; + int dpms; +}; +#define to_tfp410_encoder(x) container_of(x, struct tfp410_encoder, base) + + +static void tfp410_encoder_destroy(struct drm_encoder *encoder) +{ + struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder); + drm_encoder_cleanup(encoder); + kfree(tfp410_encoder); +} + +static void tfp410_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder); + + if (tfp410_encoder->dpms == mode) + return; + + if (mode == DRM_MODE_DPMS_ON) { + DBG("Power on"); + gpio_direction_output(tfp410_encoder->mod->gpio, 1); + } else { + DBG("Power off"); + gpio_direction_output(tfp410_encoder->mod->gpio, 0); + } + + tfp410_encoder->dpms = mode; +} + +static bool tfp410_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ + return true; +} + +static void tfp410_encoder_prepare(struct drm_encoder *encoder) +{ + tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); + tilcdc_crtc_set_panel_info(encoder->crtc, &dvi_info); +} + +static void tfp410_encoder_commit(struct drm_encoder *encoder) +{ + tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void tfp410_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ +} + +static const struct drm_encoder_funcs tfp410_encoder_funcs = { + .destroy = tfp410_encoder_destroy, +}; + +static const struct drm_encoder_helper_funcs tfp410_encoder_helper_funcs = { + .dpms = tfp410_encoder_dpms, + .mode_fixup = tfp410_encoder_mode_fixup, + .prepare = tfp410_encoder_prepare, + .commit = tfp410_encoder_commit, + .mode_set = tfp410_encoder_mode_set, +}; + +static struct drm_encoder *tfp410_encoder_create(struct drm_device *dev, + struct tfp410_module *mod) +{ + struct tfp410_encoder *tfp410_encoder; + struct drm_encoder *encoder; + int ret; + + tfp410_encoder = kzalloc(sizeof(*tfp410_encoder), GFP_KERNEL); + if (!tfp410_encoder) { + dev_err(dev->dev, "allocation failed\n"); + return NULL; + } + + tfp410_encoder->dpms = DRM_MODE_DPMS_OFF; + tfp410_encoder->mod = mod; + + encoder = &tfp410_encoder->base; + encoder->possible_crtcs = 1; + + ret = drm_encoder_init(dev, encoder, &tfp410_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (ret < 0) + goto fail; + + drm_encoder_helper_add(encoder, &tfp410_encoder_helper_funcs); + + return encoder; + +fail: + tfp410_encoder_destroy(encoder); + return NULL; +} + +/* + * Connector: + */ + +struct tfp410_connector { + struct drm_connector base; + + struct drm_encoder *encoder; /* our connected encoder */ + struct tfp410_module *mod; +}; +#define to_tfp410_connector(x) container_of(x, struct tfp410_connector, base) + + +static void tfp410_connector_destroy(struct drm_connector *connector) +{ + struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector); + drm_connector_cleanup(connector); + kfree(tfp410_connector); +} + +static enum drm_connector_status tfp410_connector_detect( + struct drm_connector *connector, + bool force) +{ + struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector); + + if (drm_probe_ddc(tfp410_connector->mod->i2c)) + return connector_status_connected; + + return connector_status_unknown; +} + +static int tfp410_connector_get_modes(struct drm_connector *connector) +{ + struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector); + struct edid *edid; + int ret = 0; + + edid = drm_get_edid(connector, tfp410_connector->mod->i2c); + + drm_mode_connector_update_edid_property(connector, edid); + + if (edid) { + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + } + + return ret; +} + +static int tfp410_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct tilcdc_drm_private *priv = connector->dev->dev_private; + /* our only constraints are what the crtc can generate: */ + return tilcdc_crtc_mode_valid(priv->crtc, mode); +} + +static struct drm_encoder *tfp410_connector_best_encoder( + struct drm_connector *connector) +{ + struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector); + return tfp410_connector->encoder; +} + +static const struct drm_connector_funcs tfp410_connector_funcs = { + .destroy = tfp410_connector_destroy, + .dpms = drm_helper_connector_dpms, + .detect = tfp410_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, +}; + +static const struct drm_connector_helper_funcs tfp410_connector_helper_funcs = { + .get_modes = tfp410_connector_get_modes, + .mode_valid = tfp410_connector_mode_valid, + .best_encoder = tfp410_connector_best_encoder, +}; + +static struct drm_connector *tfp410_connector_create(struct drm_device *dev, + struct tfp410_module *mod, struct drm_encoder *encoder) +{ + struct tfp410_connector *tfp410_connector; + struct drm_connector *connector; + int ret; + + tfp410_connector = kzalloc(sizeof(*tfp410_connector), GFP_KERNEL); + if (!tfp410_connector) { + dev_err(dev->dev, "allocation failed\n"); + return NULL; + } + + tfp410_connector->encoder = encoder; + tfp410_connector->mod = mod; + + connector = &tfp410_connector->base; + + drm_connector_init(dev, connector, &tfp410_connector_funcs, + DRM_MODE_CONNECTOR_DVID); + drm_connector_helper_add(connector, &tfp410_connector_helper_funcs); + + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto fail; + + drm_sysfs_connector_add(connector); + + return connector; + +fail: + tfp410_connector_destroy(connector); + return NULL; +} + +/* + * Module: + */ + +static int tfp410_modeset_init(struct tilcdc_module *mod, struct drm_device *dev) +{ + struct tfp410_module *tfp410_mod = to_tfp410_module(mod); + struct tilcdc_drm_private *priv = dev->dev_private; + struct drm_encoder *encoder; + struct drm_connector *connector; + + encoder = tfp410_encoder_create(dev, tfp410_mod); + if (!encoder) + return -ENOMEM; + + connector = tfp410_connector_create(dev, tfp410_mod, encoder); + if (!connector) + return -ENOMEM; + + priv->encoders[priv->num_encoders++] = encoder; + priv->connectors[priv->num_connectors++] = connector; + + return 0; +} + +static void tfp410_destroy(struct tilcdc_module *mod) +{ + struct tfp410_module *tfp410_mod = to_tfp410_module(mod); + + if (tfp410_mod->i2c) + i2c_put_adapter(tfp410_mod->i2c); + + if (!IS_ERR_VALUE(tfp410_mod->gpio)) + gpio_free(tfp410_mod->gpio); + + tilcdc_module_cleanup(mod); + kfree(tfp410_mod); +} + +static const struct tilcdc_module_ops tfp410_module_ops = { + .modeset_init = tfp410_modeset_init, + .destroy = tfp410_destroy, +}; + +/* + * Device: + */ + +static struct of_device_id tfp410_of_match[]; + +static int tfp410_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device_node *i2c_node; + struct tfp410_module *tfp410_mod; + struct tilcdc_module *mod; + struct pinctrl *pinctrl; + uint32_t i2c_phandle; + int ret = -EINVAL; + + /* bail out early if no DT data: */ + if (!node) { + dev_err(&pdev->dev, "device-tree data is missing\n"); + return -ENXIO; + } + + tfp410_mod = kzalloc(sizeof(*tfp410_mod), GFP_KERNEL); + if (!tfp410_mod) + return -ENOMEM; + + mod = &tfp410_mod->base; + + tilcdc_module_init(mod, "tfp410", &tfp410_module_ops); + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) + dev_warn(&pdev->dev, "pins are not configured\n"); + + if (of_property_read_u32(node, "i2c", &i2c_phandle)) { + dev_err(&pdev->dev, "could not get i2c bus phandle\n"); + goto fail; + } + + i2c_node = of_find_node_by_phandle(i2c_phandle); + if (!i2c_node) { + dev_err(&pdev->dev, "could not get i2c bus node\n"); + goto fail; + } + + tfp410_mod->i2c = of_find_i2c_adapter_by_node(i2c_node); + if (!tfp410_mod->i2c) { + dev_err(&pdev->dev, "could not get i2c\n"); + goto fail; + } + + of_node_put(i2c_node); + + tfp410_mod->gpio = of_get_named_gpio_flags(node, "powerdn-gpio", + 0, NULL); + if (IS_ERR_VALUE(tfp410_mod->gpio)) { + dev_warn(&pdev->dev, "No power down GPIO\n"); + } else { + ret = gpio_request(tfp410_mod->gpio, "DVI_PDn"); + if (ret) { + dev_err(&pdev->dev, "could not get DVI_PDn gpio\n"); + goto fail; + } + } + + return 0; + +fail: + tfp410_destroy(mod); + return ret; +} + +static int tfp410_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct of_device_id tfp410_of_match[] = { + { .compatible = "ti,tilcdc,tfp410", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tfp410_of_match); + +struct platform_driver tfp410_driver = { + .probe = tfp410_probe, + .remove = tfp410_remove, + .driver = { + .owner = THIS_MODULE, + .name = "tfp410", + .of_match_table = tfp410_of_match, + }, +}; + +int __init tilcdc_tfp410_init(void) +{ + return platform_driver_register(&tfp410_driver); +} + +void __exit tilcdc_tfp410_fini(void) +{ + platform_driver_unregister(&tfp410_driver); +} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.h b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.h new file mode 100644 index 000000000000..5b800f1f6aa5 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TILCDC_TFP410_H__ +#define __TILCDC_TFP410_H__ + +/* sub-module for tfp410 dvi adaptor */ + +int tilcdc_tfp410_init(void); +void tilcdc_tfp410_fini(void); + +#endif /* __TILCDC_TFP410_H__ */ diff --git a/drivers/gpu/drm/udl/udl_fb.c b/drivers/gpu/drm/udl/udl_fb.c index b9feec9d08d3..9f4be3d4a02e 100644 --- a/drivers/gpu/drm/udl/udl_fb.c +++ b/drivers/gpu/drm/udl/udl_fb.c @@ -476,9 +476,10 @@ udl_framebuffer_init(struct drm_device *dev, } -static int udlfb_create(struct udl_fbdev *ufbdev, +static int udlfb_create(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { + struct udl_fbdev *ufbdev = (struct udl_fbdev *)helper; struct drm_device *dev = ufbdev->helper.dev; struct fb_info *info; struct device *device = &dev->usbdev->dev; @@ -556,27 +557,10 @@ out: return ret; } -static int udl_fb_find_or_create_single(struct drm_fb_helper *helper, - struct drm_fb_helper_surface_size *sizes) -{ - struct udl_fbdev *ufbdev = (struct udl_fbdev *)helper; - int new_fb = 0; - int ret; - - if (!helper->fb) { - ret = udlfb_create(ufbdev, sizes); - if (ret) - return ret; - - new_fb = 1; - } - return new_fb; -} - static struct drm_fb_helper_funcs udl_fb_helper_funcs = { .gamma_set = udl_crtc_fb_gamma_set, .gamma_get = udl_crtc_fb_gamma_get, - .fb_probe = udl_fb_find_or_create_single, + .fb_probe = udlfb_create, }; static void udl_fbdev_destroy(struct drm_device *dev, @@ -619,6 +603,10 @@ int udl_fbdev_init(struct drm_device *dev) } drm_fb_helper_single_add_all_connectors(&ufbdev->helper); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + drm_fb_helper_initial_config(&ufbdev->helper, bpp_sel); return 0; } diff --git a/drivers/gpu/stub/Kconfig b/drivers/gpu/stub/Kconfig deleted file mode 100644 index 419917955bf6..000000000000 --- a/drivers/gpu/stub/Kconfig +++ /dev/null @@ -1,18 +0,0 @@ -config STUB_POULSBO - tristate "Intel GMA500 Stub Driver" - depends on PCI - depends on NET # for THERMAL - # Poulsbo stub depends on ACPI_VIDEO when ACPI is enabled - # but for select to work, need to select ACPI_VIDEO's dependencies, ick - select BACKLIGHT_CLASS_DEVICE if ACPI - select VIDEO_OUTPUT_CONTROL if ACPI - select INPUT if ACPI - select ACPI_VIDEO if ACPI - select THERMAL if ACPI - help - Choose this option if you have a system that has Intel GMA500 - (Poulsbo) integrated graphics. If M is selected, the module will - be called Poulsbo. This driver is a stub driver for Poulsbo that - will call poulsbo.ko to enable the acpi backlight control sysfs - entry file because there have no poulsbo native driver can support - intel opregion. diff --git a/drivers/gpu/stub/Makefile b/drivers/gpu/stub/Makefile deleted file mode 100644 index cd940cc9d36d..000000000000 --- a/drivers/gpu/stub/Makefile +++ /dev/null @@ -1 +0,0 @@ -obj-$(CONFIG_STUB_POULSBO) += poulsbo.o diff --git a/drivers/gpu/stub/poulsbo.c b/drivers/gpu/stub/poulsbo.c deleted file mode 100644 index 7edfd27b8dee..000000000000 --- a/drivers/gpu/stub/poulsbo.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Intel Poulsbo Stub driver - * - * Copyright (C) 2010 Novell <jlee@novell.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - * - */ - -#include <linux/module.h> -#include <linux/pci.h> -#include <linux/acpi.h> -#include <acpi/video.h> - -#define DRIVER_NAME "poulsbo" - -enum { - CHIP_PSB_8108 = 0, - CHIP_PSB_8109 = 1, -}; - -static DEFINE_PCI_DEVICE_TABLE(pciidlist) = { - {0x8086, 0x8108, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PSB_8108}, \ - {0x8086, 0x8109, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PSB_8109}, \ - {0, 0, 0} -}; - -static int poulsbo_probe(struct pci_dev *pdev, const struct pci_device_id *id) -{ - return acpi_video_register(); -} - -static void poulsbo_remove(struct pci_dev *pdev) -{ - acpi_video_unregister(); -} - -static struct pci_driver poulsbo_driver = { - .name = DRIVER_NAME, - .id_table = pciidlist, - .probe = poulsbo_probe, - .remove = poulsbo_remove, -}; - -static int __init poulsbo_init(void) -{ - return pci_register_driver(&poulsbo_driver); -} - -static void __exit poulsbo_exit(void) -{ - pci_unregister_driver(&poulsbo_driver); -} - -module_init(poulsbo_init); -module_exit(poulsbo_exit); - -MODULE_AUTHOR("Lee, Chun-Yi <jlee@novell.com>"); -MODULE_DESCRIPTION("Poulsbo Stub Driver"); -MODULE_LICENSE("GPL"); - -MODULE_DEVICE_TABLE(pci, pciidlist); |