diff options
Diffstat (limited to 'drivers/gpu/drm')
299 files changed, 22391 insertions, 5701 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index ed9e3af17b3..1e82882da9d 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -7,6 +7,7 @@  menuconfig DRM  	tristate "Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)"  	depends on (AGP || AGP=n) && !EMULATED_CMPXCHG && MMU +	select HDMI  	select I2C  	select I2C_ALGOBIT  	select DMA_SHARED_BUFFER @@ -215,3 +216,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 6f58c81cfcb..0d59b24f8d2 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/Kconfig b/drivers/gpu/drm/ast/Kconfig index a277b125788..da4a51eae82 100644 --- a/drivers/gpu/drm/ast/Kconfig +++ b/drivers/gpu/drm/ast/Kconfig @@ -1,6 +1,6 @@  config DRM_AST  	tristate "AST server chips" -	depends on DRM && PCI && EXPERIMENTAL +	depends on DRM && PCI  	select DRM_TTM  	select FB_SYS_COPYAREA  	select FB_SYS_FILLRECT diff --git a/drivers/gpu/drm/ast/ast_fb.c b/drivers/gpu/drm/ast/ast_fb.c index 3e6584b940d..34931fe7d2c 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/Kconfig b/drivers/gpu/drm/cirrus/Kconfig index fc154dd7529..bf67b22723f 100644 --- a/drivers/gpu/drm/cirrus/Kconfig +++ b/drivers/gpu/drm/cirrus/Kconfig @@ -1,6 +1,6 @@  config DRM_CIRRUS_QEMU  	tristate "Cirrus driver for QEMU emulated device" -	depends on DRM && PCI && EXPERIMENTAL +	depends on DRM && PCI  	select FB_SYS_FILLRECT  	select FB_SYS_COPYAREA  	select FB_SYS_IMAGEBLIT diff --git a/drivers/gpu/drm/cirrus/cirrus_fbdev.c b/drivers/gpu/drm/cirrus/cirrus_fbdev.c index 3daea0f638c..e25afccaf85 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_context.c b/drivers/gpu/drm/drm_context.c index 45adf97e678..725968d3897 100644 --- a/drivers/gpu/drm/drm_context.c +++ b/drivers/gpu/drm/drm_context.c @@ -74,24 +74,13 @@ void drm_ctxbitmap_free(struct drm_device * dev, int ctx_handle)   */  static int drm_ctxbitmap_next(struct drm_device * dev)  { -	int new_id;  	int ret; -again: -	if (idr_pre_get(&dev->ctx_idr, GFP_KERNEL) == 0) { -		DRM_ERROR("Out of memory expanding drawable idr\n"); -		return -ENOMEM; -	}  	mutex_lock(&dev->struct_mutex); -	ret = idr_get_new_above(&dev->ctx_idr, NULL, -				DRM_RESERVED_CONTEXTS, &new_id); +	ret = idr_alloc(&dev->ctx_idr, NULL, DRM_RESERVED_CONTEXTS, 0, +			GFP_KERNEL);  	mutex_unlock(&dev->struct_mutex); -	if (ret == -EAGAIN) -		goto again; -	else if (ret) -		return ret; - -	return new_id; +	return ret;  }  /** @@ -118,7 +107,7 @@ int drm_ctxbitmap_init(struct drm_device * dev)  void drm_ctxbitmap_cleanup(struct drm_device * dev)  {  	mutex_lock(&dev->struct_mutex); -	idr_remove_all(&dev->ctx_idr); +	idr_destroy(&dev->ctx_idr);  	mutex_unlock(&dev->struct_mutex);  } diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 826a5ca3595..792c3e3795c 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)					\ @@ -252,32 +266,21 @@ char *drm_get_connector_status_name(enum drm_connector_status status)  static int drm_mode_object_get(struct drm_device *dev,  			       struct drm_mode_object *obj, uint32_t obj_type)  { -	int new_id = 0;  	int ret; -again: -	if (idr_pre_get(&dev->mode_config.crtc_idr, GFP_KERNEL) == 0) { -		DRM_ERROR("Ran out memory getting a mode number\n"); -		return -ENOMEM; -	} -  	mutex_lock(&dev->mode_config.idr_mutex); -	ret = idr_get_new_above(&dev->mode_config.crtc_idr, obj, 1, &new_id); - -	if (!ret) { +	ret = idr_alloc(&dev->mode_config.crtc_idr, obj, 1, 0, GFP_KERNEL); +	if (ret >= 0) {  		/*  		 * Set up the object linking under the protection of the idr  		 * lock so that other users can't see inconsistent state.  		 */ -		obj->id = new_id; +		obj->id = ret;  		obj->type = obj_type;  	}  	mutex_unlock(&dev->mode_config.idr_mutex); -	if (ret == -EAGAIN) -		goto again; - -	return ret; +	return ret < 0 ? ret : 0;  }  /** @@ -1258,7 +1261,6 @@ void drm_mode_config_cleanup(struct drm_device *dev)  		crtc->funcs->destroy(crtc);  	} -	idr_remove_all(&dev->mode_config.crtc_idr);  	idr_destroy(&dev->mode_config.crtc_idr);  }  EXPORT_SYMBOL(drm_mode_config_cleanup); @@ -1982,9 +1984,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 +3780,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_drv.c b/drivers/gpu/drm/drm_drv.c index be174cab105..25f91cd23e6 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -297,7 +297,6 @@ static void __exit drm_core_exit(void)  	unregister_chrdev(DRM_MAJOR, "drm"); -	idr_remove_all(&drm_minors_idr);  	idr_destroy(&drm_minors_idr);  } diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 51324256a65..c194f4e680a 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -29,11 +29,11 @@   */  #include <linux/kernel.h>  #include <linux/slab.h> +#include <linux/hdmi.h>  #include <linux/i2c.h>  #include <linux/module.h>  #include <drm/drmP.h>  #include <drm/drm_edid.h> -#include "drm_edid_modes.h"  #define version_greater(edid, maj, min) \  	(((edid)->version > (maj)) || \ @@ -127,6 +127,746 @@ static struct edid_quirk {  	{ "VSC", 5020, EDID_QUIRK_FORCE_REDUCED_BLANKING },  }; +/* + * Autogenerated from the DMT spec. + * This table is copied from xfree86/modes/xf86EdidModes.c. + */ +static const struct drm_display_mode drm_dmt_modes[] = { +	/* 640x350@85Hz */ +	{ DRM_MODE("640x350", DRM_MODE_TYPE_DRIVER, 31500, 640, 672, +		   736, 832, 0, 350, 382, 385, 445, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 640x400@85Hz */ +	{ DRM_MODE("640x400", DRM_MODE_TYPE_DRIVER, 31500, 640, 672, +		   736, 832, 0, 400, 401, 404, 445, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 720x400@85Hz */ +	{ DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 35500, 720, 756, +		   828, 936, 0, 400, 401, 404, 446, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 640x480@60Hz */ +	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25175, 640, 656, +		   752, 800, 0, 480, 489, 492, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 640x480@72Hz */ +	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 664, +		   704, 832, 0, 480, 489, 492, 520, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 640x480@75Hz */ +	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 656, +		   720, 840, 0, 480, 481, 484, 500, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 640x480@85Hz */ +	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 36000, 640, 696, +		   752, 832, 0, 480, 481, 484, 509, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 800x600@56Hz */ +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 36000, 800, 824, +		   896, 1024, 0, 600, 601, 603, 625, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 800x600@60Hz */ +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 40000, 800, 840, +		   968, 1056, 0, 600, 601, 605, 628, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 800x600@72Hz */ +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 50000, 800, 856, +		   976, 1040, 0, 600, 637, 643, 666, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 800x600@75Hz */ +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 49500, 800, 816, +		   896, 1056, 0, 600, 601, 604, 625, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 800x600@85Hz */ +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 56250, 800, 832, +		   896, 1048, 0, 600, 601, 604, 631, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 800x600@120Hz RB */ +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 73250, 800, 848, +		   880, 960, 0, 600, 603, 607, 636, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 848x480@60Hz */ +	{ DRM_MODE("848x480", DRM_MODE_TYPE_DRIVER, 33750, 848, 864, +		   976, 1088, 0, 480, 486, 494, 517, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1024x768@43Hz, interlace */ +	{ DRM_MODE("1024x768i", DRM_MODE_TYPE_DRIVER, 44900, 1024, 1032, +		   1208, 1264, 0, 768, 768, 772, 817, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | +			DRM_MODE_FLAG_INTERLACE) }, +	/* 1024x768@60Hz */ +	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 65000, 1024, 1048, +		   1184, 1344, 0, 768, 771, 777, 806, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1024x768@70Hz */ +	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 75000, 1024, 1048, +		   1184, 1328, 0, 768, 771, 777, 806, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1024x768@75Hz */ +	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 78750, 1024, 1040, +		   1136, 1312, 0, 768, 769, 772, 800, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1024x768@85Hz */ +	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 94500, 1024, 1072, +		   1168, 1376, 0, 768, 769, 772, 808, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1024x768@120Hz RB */ +	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 115500, 1024, 1072, +		   1104, 1184, 0, 768, 771, 775, 813, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1152x864@75Hz */ +	{ DRM_MODE("1152x864", DRM_MODE_TYPE_DRIVER, 108000, 1152, 1216, +		   1344, 1600, 0, 864, 865, 868, 900, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1280x768@60Hz RB */ +	{ DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 68250, 1280, 1328, +		   1360, 1440, 0, 768, 771, 778, 790, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1280x768@60Hz */ +	{ DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 79500, 1280, 1344, +		   1472, 1664, 0, 768, 771, 778, 798, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1280x768@75Hz */ +	{ DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 102250, 1280, 1360, +		   1488, 1696, 0, 768, 771, 778, 805, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1280x768@85Hz */ +	{ DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 117500, 1280, 1360, +		   1496, 1712, 0, 768, 771, 778, 809, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1280x768@120Hz RB */ +	{ DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 140250, 1280, 1328, +		   1360, 1440, 0, 768, 771, 778, 813, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1280x800@60Hz RB */ +	{ DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 71000, 1280, 1328, +		   1360, 1440, 0, 800, 803, 809, 823, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1280x800@60Hz */ +	{ DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 83500, 1280, 1352, +		   1480, 1680, 0, 800, 803, 809, 831, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1280x800@75Hz */ +	{ DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 106500, 1280, 1360, +		   1488, 1696, 0, 800, 803, 809, 838, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1280x800@85Hz */ +	{ DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 122500, 1280, 1360, +		   1496, 1712, 0, 800, 803, 809, 843, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1280x800@120Hz RB */ +	{ DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 146250, 1280, 1328, +		   1360, 1440, 0, 800, 803, 809, 847, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1280x960@60Hz */ +	{ DRM_MODE("1280x960", DRM_MODE_TYPE_DRIVER, 108000, 1280, 1376, +		   1488, 1800, 0, 960, 961, 964, 1000, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1280x960@85Hz */ +	{ DRM_MODE("1280x960", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1344, +		   1504, 1728, 0, 960, 961, 964, 1011, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1280x960@120Hz RB */ +	{ DRM_MODE("1280x960", DRM_MODE_TYPE_DRIVER, 175500, 1280, 1328, +		   1360, 1440, 0, 960, 963, 967, 1017, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1280x1024@60Hz */ +	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 108000, 1280, 1328, +		   1440, 1688, 0, 1024, 1025, 1028, 1066, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1280x1024@75Hz */ +	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 135000, 1280, 1296, +		   1440, 1688, 0, 1024, 1025, 1028, 1066, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1280x1024@85Hz */ +	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 157500, 1280, 1344, +		   1504, 1728, 0, 1024, 1025, 1028, 1072, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1280x1024@120Hz RB */ +	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 187250, 1280, 1328, +		   1360, 1440, 0, 1024, 1027, 1034, 1084, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1360x768@60Hz */ +	{ DRM_MODE("1360x768", DRM_MODE_TYPE_DRIVER, 85500, 1360, 1424, +		   1536, 1792, 0, 768, 771, 777, 795, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1360x768@120Hz RB */ +	{ DRM_MODE("1360x768", DRM_MODE_TYPE_DRIVER, 148250, 1360, 1408, +		   1440, 1520, 0, 768, 771, 776, 813, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1400x1050@60Hz RB */ +	{ DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 101000, 1400, 1448, +		   1480, 1560, 0, 1050, 1053, 1057, 1080, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1400x1050@60Hz */ +	{ DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 121750, 1400, 1488, +		   1632, 1864, 0, 1050, 1053, 1057, 1089, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1400x1050@75Hz */ +	{ DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 156000, 1400, 1504, +		   1648, 1896, 0, 1050, 1053, 1057, 1099, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1400x1050@85Hz */ +	{ DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 179500, 1400, 1504, +		   1656, 1912, 0, 1050, 1053, 1057, 1105, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1400x1050@120Hz RB */ +	{ DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 208000, 1400, 1448, +		   1480, 1560, 0, 1050, 1053, 1057, 1112, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1440x900@60Hz RB */ +	{ DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 88750, 1440, 1488, +		   1520, 1600, 0, 900, 903, 909, 926, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1440x900@60Hz */ +	{ DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 106500, 1440, 1520, +		   1672, 1904, 0, 900, 903, 909, 934, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1440x900@75Hz */ +	{ DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 136750, 1440, 1536, +		   1688, 1936, 0, 900, 903, 909, 942, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1440x900@85Hz */ +	{ DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 157000, 1440, 1544, +		   1696, 1952, 0, 900, 903, 909, 948, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1440x900@120Hz RB */ +	{ DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 182750, 1440, 1488, +		   1520, 1600, 0, 900, 903, 909, 953, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1600x1200@60Hz */ +	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 162000, 1600, 1664, +		   1856, 2160, 0, 1200, 1201, 1204, 1250, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1600x1200@65Hz */ +	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 175500, 1600, 1664, +		   1856, 2160, 0, 1200, 1201, 1204, 1250, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1600x1200@70Hz */ +	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 189000, 1600, 1664, +		   1856, 2160, 0, 1200, 1201, 1204, 1250, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1600x1200@75Hz */ +	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 202500, 1600, 1664, +		   1856, 2160, 0, 1200, 1201, 1204, 1250, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1600x1200@85Hz */ +	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 229500, 1600, 1664, +		   1856, 2160, 0, 1200, 1201, 1204, 1250, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1600x1200@120Hz RB */ +	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 268250, 1600, 1648, +		   1680, 1760, 0, 1200, 1203, 1207, 1271, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1680x1050@60Hz RB */ +	{ DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 119000, 1680, 1728, +		   1760, 1840, 0, 1050, 1053, 1059, 1080, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1680x1050@60Hz */ +	{ DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 146250, 1680, 1784, +		   1960, 2240, 0, 1050, 1053, 1059, 1089, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1680x1050@75Hz */ +	{ DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 187000, 1680, 1800, +		   1976, 2272, 0, 1050, 1053, 1059, 1099, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1680x1050@85Hz */ +	{ DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 214750, 1680, 1808, +		   1984, 2288, 0, 1050, 1053, 1059, 1105, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1680x1050@120Hz RB */ +	{ DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 245500, 1680, 1728, +		   1760, 1840, 0, 1050, 1053, 1059, 1112, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1792x1344@60Hz */ +	{ DRM_MODE("1792x1344", DRM_MODE_TYPE_DRIVER, 204750, 1792, 1920, +		   2120, 2448, 0, 1344, 1345, 1348, 1394, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1792x1344@75Hz */ +	{ DRM_MODE("1792x1344", DRM_MODE_TYPE_DRIVER, 261000, 1792, 1888, +		   2104, 2456, 0, 1344, 1345, 1348, 1417, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1792x1344@120Hz RB */ +	{ DRM_MODE("1792x1344", DRM_MODE_TYPE_DRIVER, 333250, 1792, 1840, +		   1872, 1952, 0, 1344, 1347, 1351, 1423, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1856x1392@60Hz */ +	{ DRM_MODE("1856x1392", DRM_MODE_TYPE_DRIVER, 218250, 1856, 1952, +		   2176, 2528, 0, 1392, 1393, 1396, 1439, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1856x1392@75Hz */ +	{ DRM_MODE("1856x1392", DRM_MODE_TYPE_DRIVER, 288000, 1856, 1984, +		   2208, 2560, 0, 1392, 1395, 1399, 1500, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1856x1392@120Hz RB */ +	{ DRM_MODE("1856x1392", DRM_MODE_TYPE_DRIVER, 356500, 1856, 1904, +		   1936, 2016, 0, 1392, 1395, 1399, 1474, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1920x1200@60Hz RB */ +	{ DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 154000, 1920, 1968, +		   2000, 2080, 0, 1200, 1203, 1209, 1235, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1920x1200@60Hz */ +	{ DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 193250, 1920, 2056, +		   2256, 2592, 0, 1200, 1203, 1209, 1245, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1920x1200@75Hz */ +	{ DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 245250, 1920, 2056, +		   2264, 2608, 0, 1200, 1203, 1209, 1255, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1920x1200@85Hz */ +	{ DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 281250, 1920, 2064, +		   2272, 2624, 0, 1200, 1203, 1209, 1262, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1920x1200@120Hz RB */ +	{ DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 317000, 1920, 1968, +		   2000, 2080, 0, 1200, 1203, 1209, 1271, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 1920x1440@60Hz */ +	{ DRM_MODE("1920x1440", DRM_MODE_TYPE_DRIVER, 234000, 1920, 2048, +		   2256, 2600, 0, 1440, 1441, 1444, 1500, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1920x1440@75Hz */ +	{ DRM_MODE("1920x1440", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2064, +		   2288, 2640, 0, 1440, 1441, 1444, 1500, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 1920x1440@120Hz RB */ +	{ DRM_MODE("1920x1440", DRM_MODE_TYPE_DRIVER, 380500, 1920, 1968, +		   2000, 2080, 0, 1440, 1443, 1447, 1525, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 2560x1600@60Hz RB */ +	{ DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 268500, 2560, 2608, +		   2640, 2720, 0, 1600, 1603, 1609, 1646, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 2560x1600@60Hz */ +	{ DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 348500, 2560, 2752, +		   3032, 3504, 0, 1600, 1603, 1609, 1658, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 2560x1600@75HZ */ +	{ DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 443250, 2560, 2768, +		   3048, 3536, 0, 1600, 1603, 1609, 1672, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 2560x1600@85HZ */ +	{ DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 505250, 2560, 2768, +		   3048, 3536, 0, 1600, 1603, 1609, 1682, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 2560x1600@120Hz RB */ +	{ DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 552750, 2560, 2608, +		   2640, 2720, 0, 1600, 1603, 1609, 1694, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, +}; + +static const struct drm_display_mode edid_est_modes[] = { +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 40000, 800, 840, +		   968, 1056, 0, 600, 601, 605, 628, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@60Hz */ +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 36000, 800, 824, +		   896, 1024, 0, 600, 601, 603,  625, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@56Hz */ +	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 656, +		   720, 840, 0, 480, 481, 484, 500, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@75Hz */ +	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 664, +		   704,  832, 0, 480, 489, 491, 520, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@72Hz */ +	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 30240, 640, 704, +		   768,  864, 0, 480, 483, 486, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@67Hz */ +	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25200, 640, 656, +		   752, 800, 0, 480, 490, 492, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@60Hz */ +	{ DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 35500, 720, 738, +		   846, 900, 0, 400, 421, 423,  449, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 720x400@88Hz */ +	{ DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 28320, 720, 738, +		   846,  900, 0, 400, 412, 414, 449, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 720x400@70Hz */ +	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 135000, 1280, 1296, +		   1440, 1688, 0, 1024, 1025, 1028, 1066, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1280x1024@75Hz */ +	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 78800, 1024, 1040, +		   1136, 1312, 0,  768, 769, 772, 800, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1024x768@75Hz */ +	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 75000, 1024, 1048, +		   1184, 1328, 0,  768, 771, 777, 806, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 1024x768@70Hz */ +	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 65000, 1024, 1048, +		   1184, 1344, 0,  768, 771, 777, 806, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 1024x768@60Hz */ +	{ DRM_MODE("1024x768i", DRM_MODE_TYPE_DRIVER,44900, 1024, 1032, +		   1208, 1264, 0, 768, 768, 776, 817, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_INTERLACE) }, /* 1024x768@43Hz */ +	{ DRM_MODE("832x624", DRM_MODE_TYPE_DRIVER, 57284, 832, 864, +		   928, 1152, 0, 624, 625, 628, 667, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 832x624@75Hz */ +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 49500, 800, 816, +		   896, 1056, 0, 600, 601, 604,  625, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@75Hz */ +	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 50000, 800, 856, +		   976, 1040, 0, 600, 637, 643, 666, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@72Hz */ +	{ DRM_MODE("1152x864", DRM_MODE_TYPE_DRIVER, 108000, 1152, 1216, +		   1344, 1600, 0,  864, 865, 868, 900, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1152x864@75Hz */ +}; + +struct minimode { +	short w; +	short h; +	short r; +	short rb; +}; + +static const struct minimode est3_modes[] = { +	/* byte 6 */ +	{ 640, 350, 85, 0 }, +	{ 640, 400, 85, 0 }, +	{ 720, 400, 85, 0 }, +	{ 640, 480, 85, 0 }, +	{ 848, 480, 60, 0 }, +	{ 800, 600, 85, 0 }, +	{ 1024, 768, 85, 0 }, +	{ 1152, 864, 75, 0 }, +	/* byte 7 */ +	{ 1280, 768, 60, 1 }, +	{ 1280, 768, 60, 0 }, +	{ 1280, 768, 75, 0 }, +	{ 1280, 768, 85, 0 }, +	{ 1280, 960, 60, 0 }, +	{ 1280, 960, 85, 0 }, +	{ 1280, 1024, 60, 0 }, +	{ 1280, 1024, 85, 0 }, +	/* byte 8 */ +	{ 1360, 768, 60, 0 }, +	{ 1440, 900, 60, 1 }, +	{ 1440, 900, 60, 0 }, +	{ 1440, 900, 75, 0 }, +	{ 1440, 900, 85, 0 }, +	{ 1400, 1050, 60, 1 }, +	{ 1400, 1050, 60, 0 }, +	{ 1400, 1050, 75, 0 }, +	/* byte 9 */ +	{ 1400, 1050, 85, 0 }, +	{ 1680, 1050, 60, 1 }, +	{ 1680, 1050, 60, 0 }, +	{ 1680, 1050, 75, 0 }, +	{ 1680, 1050, 85, 0 }, +	{ 1600, 1200, 60, 0 }, +	{ 1600, 1200, 65, 0 }, +	{ 1600, 1200, 70, 0 }, +	/* byte 10 */ +	{ 1600, 1200, 75, 0 }, +	{ 1600, 1200, 85, 0 }, +	{ 1792, 1344, 60, 0 }, +	{ 1792, 1344, 85, 0 }, +	{ 1856, 1392, 60, 0 }, +	{ 1856, 1392, 75, 0 }, +	{ 1920, 1200, 60, 1 }, +	{ 1920, 1200, 60, 0 }, +	/* byte 11 */ +	{ 1920, 1200, 75, 0 }, +	{ 1920, 1200, 85, 0 }, +	{ 1920, 1440, 60, 0 }, +	{ 1920, 1440, 75, 0 }, +}; + +static const struct minimode extra_modes[] = { +	{ 1024, 576,  60, 0 }, +	{ 1366, 768,  60, 0 }, +	{ 1600, 900,  60, 0 }, +	{ 1680, 945,  60, 0 }, +	{ 1920, 1080, 60, 0 }, +	{ 2048, 1152, 60, 0 }, +	{ 2048, 1536, 60, 0 }, +}; + +/* + * Probably taken from CEA-861 spec. + * This table is converted from xorg's hw/xfree86/modes/xf86EdidModes.c. + */ +static const struct drm_display_mode edid_cea_modes[] = { +	/* 1 - 640x480@60Hz */ +	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25175, 640, 656, +		   752, 800, 0, 480, 490, 492, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 2 - 720x480@60Hz */ +	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, +		   798, 858, 0, 480, 489, 495, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 3 - 720x480@60Hz */ +	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, +		   798, 858, 0, 480, 489, 495, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 4 - 1280x720@60Hz */ +	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, +		   1430, 1650, 0, 720, 725, 730, 750, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 5 - 1920x1080i@60Hz */ +	{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008, +		   2052, 2200, 0, 1080, 1084, 1094, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | +			DRM_MODE_FLAG_INTERLACE) }, +	/* 6 - 1440x480i@60Hz */ +	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1478, +		   1602, 1716, 0, 480, 488, 494, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, +	/* 7 - 1440x480i@60Hz */ +	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1478, +		   1602, 1716, 0, 480, 488, 494, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, +	/* 8 - 1440x240@60Hz */ +	{ DRM_MODE("1440x240", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1478, +		   1602, 1716, 0, 240, 244, 247, 262, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_DBLCLK) }, +	/* 9 - 1440x240@60Hz */ +	{ DRM_MODE("1440x240", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1478, +		   1602, 1716, 0, 240, 244, 247, 262, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_DBLCLK) }, +	/* 10 - 2880x480i@60Hz */ +	{ DRM_MODE("2880x480i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, +		   3204, 3432, 0, 480, 488, 494, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE) }, +	/* 11 - 2880x480i@60Hz */ +	{ DRM_MODE("2880x480i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, +		   3204, 3432, 0, 480, 488, 494, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE) }, +	/* 12 - 2880x240@60Hz */ +	{ DRM_MODE("2880x240", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, +		   3204, 3432, 0, 240, 244, 247, 262, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 13 - 2880x240@60Hz */ +	{ DRM_MODE("2880x240", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, +		   3204, 3432, 0, 240, 244, 247, 262, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 14 - 1440x480@60Hz */ +	{ DRM_MODE("1440x480", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1472, +		   1596, 1716, 0, 480, 489, 495, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 15 - 1440x480@60Hz */ +	{ DRM_MODE("1440x480", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1472, +		   1596, 1716, 0, 480, 489, 495, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 16 - 1920x1080@60Hz */ +	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008, +		   2052, 2200, 0, 1080, 1084, 1089, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 17 - 720x576@50Hz */ +	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000, 720, 732, +		   796, 864, 0, 576, 581, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 18 - 720x576@50Hz */ +	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000, 720, 732, +		   796, 864, 0, 576, 581, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 19 - 1280x720@50Hz */ +	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720, +		   1760, 1980, 0, 720, 725, 730, 750, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 20 - 1920x1080i@50Hz */ +	{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448, +		   2492, 2640, 0, 1080, 1084, 1094, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | +			DRM_MODE_FLAG_INTERLACE) }, +	/* 21 - 1440x576i@50Hz */ +	{ DRM_MODE("1440x576i", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1464, +		   1590, 1728, 0, 576, 580, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, +	/* 22 - 1440x576i@50Hz */ +	{ DRM_MODE("1440x576i", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1464, +		   1590, 1728, 0, 576, 580, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, +	/* 23 - 1440x288@50Hz */ +	{ DRM_MODE("1440x288", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1464, +		   1590, 1728, 0, 288, 290, 293, 312, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_DBLCLK) }, +	/* 24 - 1440x288@50Hz */ +	{ DRM_MODE("1440x288", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1464, +		   1590, 1728, 0, 288, 290, 293, 312, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_DBLCLK) }, +	/* 25 - 2880x576i@50Hz */ +	{ DRM_MODE("2880x576i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, +		   3180, 3456, 0, 576, 580, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE) }, +	/* 26 - 2880x576i@50Hz */ +	{ DRM_MODE("2880x576i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, +		   3180, 3456, 0, 576, 580, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE) }, +	/* 27 - 2880x288@50Hz */ +	{ DRM_MODE("2880x288", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, +		   3180, 3456, 0, 288, 290, 293, 312, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 28 - 2880x288@50Hz */ +	{ DRM_MODE("2880x288", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, +		   3180, 3456, 0, 288, 290, 293, 312, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 29 - 1440x576@50Hz */ +	{ DRM_MODE("1440x576", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1464, +		   1592, 1728, 0, 576, 581, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 30 - 1440x576@50Hz */ +	{ DRM_MODE("1440x576", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1464, +		   1592, 1728, 0, 576, 581, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 31 - 1920x1080@50Hz */ +	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448, +		   2492, 2640, 0, 1080, 1084, 1089, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 32 - 1920x1080@24Hz */ +	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2558, +		   2602, 2750, 0, 1080, 1084, 1089, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 33 - 1920x1080@25Hz */ +	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448, +		   2492, 2640, 0, 1080, 1084, 1089, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 34 - 1920x1080@30Hz */ +	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008, +		   2052, 2200, 0, 1080, 1084, 1089, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 35 - 2880x480@60Hz */ +	{ DRM_MODE("2880x480", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2944, +		   3192, 3432, 0, 480, 489, 495, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 36 - 2880x480@60Hz */ +	{ DRM_MODE("2880x480", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2944, +		   3192, 3432, 0, 480, 489, 495, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 37 - 2880x576@50Hz */ +	{ DRM_MODE("2880x576", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2928, +		   3184, 3456, 0, 576, 581, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 38 - 2880x576@50Hz */ +	{ DRM_MODE("2880x576", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2928, +		   3184, 3456, 0, 576, 581, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 39 - 1920x1080i@50Hz */ +	{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 72000, 1920, 1952, +		   2120, 2304, 0, 1080, 1126, 1136, 1250, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE) }, +	/* 40 - 1920x1080i@100Hz */ +	{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448, +		   2492, 2640, 0, 1080, 1084, 1094, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | +			DRM_MODE_FLAG_INTERLACE) }, +	/* 41 - 1280x720@100Hz */ +	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1720, +		   1760, 1980, 0, 720, 725, 730, 750, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 42 - 720x576@100Hz */ +	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 54000, 720, 732, +		   796, 864, 0, 576, 581, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 43 - 720x576@100Hz */ +	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 54000, 720, 732, +		   796, 864, 0, 576, 581, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 44 - 1440x576i@100Hz */ +	{ DRM_MODE("1440x576", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1464, +		   1590, 1728, 0, 576, 580, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_DBLCLK) }, +	/* 45 - 1440x576i@100Hz */ +	{ DRM_MODE("1440x576", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1464, +		   1590, 1728, 0, 576, 580, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_DBLCLK) }, +	/* 46 - 1920x1080i@120Hz */ +	{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008, +		   2052, 2200, 0, 1080, 1084, 1094, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | +			DRM_MODE_FLAG_INTERLACE) }, +	/* 47 - 1280x720@120Hz */ +	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1390, +		   1430, 1650, 0, 720, 725, 730, 750, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 48 - 720x480@120Hz */ +	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 54000, 720, 736, +		   798, 858, 0, 480, 489, 495, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 49 - 720x480@120Hz */ +	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 54000, 720, 736, +		   798, 858, 0, 480, 489, 495, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 50 - 1440x480i@120Hz */ +	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1478, +		   1602, 1716, 0, 480, 488, 494, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, +	/* 51 - 1440x480i@120Hz */ +	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1478, +		   1602, 1716, 0, 480, 488, 494, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, +	/* 52 - 720x576@200Hz */ +	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 108000, 720, 732, +		   796, 864, 0, 576, 581, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 53 - 720x576@200Hz */ +	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 108000, 720, 732, +		   796, 864, 0, 576, 581, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 54 - 1440x576i@200Hz */ +	{ DRM_MODE("1440x576i", DRM_MODE_TYPE_DRIVER, 108000, 1440, 1464, +		   1590, 1728, 0, 576, 580, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, +	/* 55 - 1440x576i@200Hz */ +	{ DRM_MODE("1440x576i", DRM_MODE_TYPE_DRIVER, 108000, 1440, 1464, +		   1590, 1728, 0, 576, 580, 586, 625, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, +	/* 56 - 720x480@240Hz */ +	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 108000, 720, 736, +		   798, 858, 0, 480, 489, 495, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 57 - 720x480@240Hz */ +	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 108000, 720, 736, +		   798, 858, 0, 480, 489, 495, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, +	/* 58 - 1440x480i@240 */ +	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 108000, 1440, 1478, +		   1602, 1716, 0, 480, 488, 494, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, +	/* 59 - 1440x480i@240 */ +	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 108000, 1440, 1478, +		   1602, 1716, 0, 480, 488, 494, 525, 0, +		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | +			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, +	/* 60 - 1280x720@24Hz */ +	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 59400, 1280, 3040, +		   3080, 3300, 0, 720, 725, 730, 750, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 61 - 1280x720@25Hz */ +	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 3700, +		   3740, 3960, 0, 720, 725, 730, 750, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 62 - 1280x720@30Hz */ +	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 3040, +		   3080, 3300, 0, 720, 725, 730, 750, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 63 - 1920x1080@120Hz */ +	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2008, +		   2052, 2200, 0, 1080, 1084, 1089, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +	/* 64 - 1920x1080@100Hz */ +	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2448, +		   2492, 2640, 0, 1080, 1084, 1094, 1125, 0, +		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, +}; +  /*** DDC fetch and block validation ***/  static const u8 edid_header[] = { @@ -354,10 +1094,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]) { @@ -538,7 +1282,7 @@ struct drm_display_mode *drm_mode_find_dmt(struct drm_device *dev,  {  	int i; -	for (i = 0; i < drm_num_dmt_modes; i++) { +	for (i = 0; i < ARRAY_SIZE(drm_dmt_modes); i++) {  		const struct drm_display_mode *ptr = &drm_dmt_modes[i];  		if (hsize != ptr->hdisplay)  			continue; @@ -1079,7 +1823,7 @@ drm_dmt_modes_for_range(struct drm_connector *connector, struct edid *edid,  	struct drm_display_mode *newmode;  	struct drm_device *dev = connector->dev; -	for (i = 0; i < drm_num_dmt_modes; i++) { +	for (i = 0; i < ARRAY_SIZE(drm_dmt_modes); i++) {  		if (mode_in_range(drm_dmt_modes + i, edid, timing) &&  		    valid_inferred_mode(connector, drm_dmt_modes + i)) {  			newmode = drm_mode_duplicate(dev, &drm_dmt_modes[i]); @@ -1114,7 +1858,7 @@ drm_gtf_modes_for_range(struct drm_connector *connector, struct edid *edid,  	struct drm_display_mode *newmode;  	struct drm_device *dev = connector->dev; -	for (i = 0; i < num_extra_modes; i++) { +	for (i = 0; i < ARRAY_SIZE(extra_modes); i++) {  		const struct minimode *m = &extra_modes[i];  		newmode = drm_gtf_mode(dev, m->w, m->h, m->r, 0, 0);  		if (!newmode) @@ -1143,7 +1887,7 @@ drm_cvt_modes_for_range(struct drm_connector *connector, struct edid *edid,  	struct drm_device *dev = connector->dev;  	bool rb = drm_monitor_supports_rb(edid); -	for (i = 0; i < num_extra_modes; i++) { +	for (i = 0; i < ARRAY_SIZE(extra_modes); i++) {  		const struct minimode *m = &extra_modes[i];  		newmode = drm_cvt_mode(dev, m->w, m->h, m->r, rb, 0, 0);  		if (!newmode) @@ -1512,16 +2256,19 @@ u8 *drm_find_cea_extension(struct edid *edid)  }  EXPORT_SYMBOL(drm_find_cea_extension); -/* - * Looks for a CEA mode matching given drm_display_mode. - * Returns its CEA Video ID code, or 0 if not found. +/** + * drm_match_cea_mode - look for a CEA mode matching given mode + * @to_match: display mode + * + * Returns the CEA Video ID (VIC) of the mode or 0 if it isn't a CEA-861 + * mode.   */ -u8 drm_match_cea_mode(struct drm_display_mode *to_match) +u8 drm_match_cea_mode(const struct drm_display_mode *to_match)  {  	struct drm_display_mode *cea_mode;  	u8 mode; -	for (mode = 0; mode < drm_num_cea_modes; mode++) { +	for (mode = 0; mode < ARRAY_SIZE(edid_cea_modes); mode++) {  		cea_mode = (struct drm_display_mode *)&edid_cea_modes[mode];  		if (drm_mode_equal(to_match, cea_mode)) @@ -1541,7 +2288,7 @@ do_cea_modes (struct drm_connector *connector, u8 *db, u8 len)  	for (mode = db; mode < db + len; mode++) {  		cea_mode = (*mode & 127) - 1; /* CEA modes are numbered 1..127 */ -		if (cea_mode < drm_num_cea_modes) { +		if (cea_mode < ARRAY_SIZE(edid_cea_modes)) {  			struct drm_display_mode *newmode;  			newmode = drm_mode_duplicate(dev,  						     &edid_cea_modes[cea_mode]); @@ -2050,7 +2797,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)) @@ -2111,20 +2859,33 @@ int drm_add_modes_noedid(struct drm_connector *connector,  EXPORT_SYMBOL(drm_add_modes_noedid);  /** - * drm_mode_cea_vic - return the CEA-861 VIC of a given mode - * @mode: mode + * drm_hdmi_avi_infoframe_from_display_mode() - fill an HDMI AVI infoframe with + *                                              data from a DRM display mode + * @frame: HDMI AVI infoframe + * @mode: DRM display mode   * - * RETURNS: - * The VIC number, 0 in case it's not a CEA-861 mode. + * Returns 0 on success or a negative error code on failure.   */ -uint8_t drm_mode_cea_vic(const struct drm_display_mode *mode) +int +drm_hdmi_avi_infoframe_from_display_mode(struct hdmi_avi_infoframe *frame, +					 const struct drm_display_mode *mode)  { -	uint8_t i; +	int err; + +	if (!frame || !mode) +		return -EINVAL; + +	err = hdmi_avi_infoframe_init(frame); +	if (err < 0) +		return err; + +	frame->video_code = drm_match_cea_mode(mode); +	if (!frame->video_code) +		return 0; -	for (i = 0; i < drm_num_cea_modes; i++) -		if (drm_mode_equal(mode, &edid_cea_modes[i])) -			return i + 1; +	frame->picture_aspect = HDMI_PICTURE_ASPECT_NONE; +	frame->active_aspect = HDMI_ACTIVE_ASPECT_PICTURE;  	return 0;  } -EXPORT_SYMBOL(drm_mode_cea_vic); +EXPORT_SYMBOL(drm_hdmi_avi_infoframe_from_display_mode); diff --git a/drivers/gpu/drm/drm_edid_modes.h b/drivers/gpu/drm/drm_edid_modes.h deleted file mode 100644 index 5dbf7d2557b..00000000000 --- a/drivers/gpu/drm/drm_edid_modes.h +++ /dev/null @@ -1,774 +0,0 @@ -/* - * Copyright (c) 2007-2008 Intel Corporation - *   Jesse Barnes <jesse.barnes@intel.com> - * Copyright 2010 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, sub license, - * 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 NON-INFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS 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 <linux/kernel.h> -#include <drm/drmP.h> -#include <drm/drm_edid.h> - -/* - * Autogenerated from the DMT spec. - * This table is copied from xfree86/modes/xf86EdidModes.c. - */ -static const struct drm_display_mode drm_dmt_modes[] = { -	/* 640x350@85Hz */ -	{ DRM_MODE("640x350", DRM_MODE_TYPE_DRIVER, 31500, 640, 672, -		   736, 832, 0, 350, 382, 385, 445, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 640x400@85Hz */ -	{ DRM_MODE("640x400", DRM_MODE_TYPE_DRIVER, 31500, 640, 672, -		   736, 832, 0, 400, 401, 404, 445, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 720x400@85Hz */ -	{ DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 35500, 720, 756, -		   828, 936, 0, 400, 401, 404, 446, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 640x480@60Hz */ -	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25175, 640, 656, -		   752, 800, 0, 480, 489, 492, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 640x480@72Hz */ -	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 664, -		   704, 832, 0, 480, 489, 492, 520, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 640x480@75Hz */ -	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 656, -		   720, 840, 0, 480, 481, 484, 500, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 640x480@85Hz */ -	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 36000, 640, 696, -		   752, 832, 0, 480, 481, 484, 509, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 800x600@56Hz */ -	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 36000, 800, 824, -		   896, 1024, 0, 600, 601, 603, 625, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 800x600@60Hz */ -	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 40000, 800, 840, -		   968, 1056, 0, 600, 601, 605, 628, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 800x600@72Hz */ -	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 50000, 800, 856, -		   976, 1040, 0, 600, 637, 643, 666, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 800x600@75Hz */ -	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 49500, 800, 816, -		   896, 1056, 0, 600, 601, 604, 625, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 800x600@85Hz */ -	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 56250, 800, 832, -		   896, 1048, 0, 600, 601, 604, 631, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 800x600@120Hz RB */ -	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 73250, 800, 848, -		   880, 960, 0, 600, 603, 607, 636, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 848x480@60Hz */ -	{ DRM_MODE("848x480", DRM_MODE_TYPE_DRIVER, 33750, 848, 864, -		   976, 1088, 0, 480, 486, 494, 517, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1024x768@43Hz, interlace */ -	{ DRM_MODE("1024x768i", DRM_MODE_TYPE_DRIVER, 44900, 1024, 1032, -		   1208, 1264, 0, 768, 768, 772, 817, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | -			DRM_MODE_FLAG_INTERLACE) }, -	/* 1024x768@60Hz */ -	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 65000, 1024, 1048, -		   1184, 1344, 0, 768, 771, 777, 806, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1024x768@70Hz */ -	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 75000, 1024, 1048, -		   1184, 1328, 0, 768, 771, 777, 806, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1024x768@75Hz */ -	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 78750, 1024, 1040, -		   1136, 1312, 0, 768, 769, 772, 800, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1024x768@85Hz */ -	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 94500, 1024, 1072, -		   1168, 1376, 0, 768, 769, 772, 808, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1024x768@120Hz RB */ -	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 115500, 1024, 1072, -		   1104, 1184, 0, 768, 771, 775, 813, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1152x864@75Hz */ -	{ DRM_MODE("1152x864", DRM_MODE_TYPE_DRIVER, 108000, 1152, 1216, -		   1344, 1600, 0, 864, 865, 868, 900, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1280x768@60Hz RB */ -	{ DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 68250, 1280, 1328, -		   1360, 1440, 0, 768, 771, 778, 790, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1280x768@60Hz */ -	{ DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 79500, 1280, 1344, -		   1472, 1664, 0, 768, 771, 778, 798, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1280x768@75Hz */ -	{ DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 102250, 1280, 1360, -		   1488, 1696, 0, 768, 771, 778, 805, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1280x768@85Hz */ -	{ DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 117500, 1280, 1360, -		   1496, 1712, 0, 768, 771, 778, 809, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1280x768@120Hz RB */ -	{ DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 140250, 1280, 1328, -		   1360, 1440, 0, 768, 771, 778, 813, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1280x800@60Hz RB */ -	{ DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 71000, 1280, 1328, -		   1360, 1440, 0, 800, 803, 809, 823, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1280x800@60Hz */ -	{ DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 83500, 1280, 1352, -		   1480, 1680, 0, 800, 803, 809, 831, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1280x800@75Hz */ -	{ DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 106500, 1280, 1360, -		   1488, 1696, 0, 800, 803, 809, 838, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1280x800@85Hz */ -	{ DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 122500, 1280, 1360, -		   1496, 1712, 0, 800, 803, 809, 843, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1280x800@120Hz RB */ -	{ DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 146250, 1280, 1328, -		   1360, 1440, 0, 800, 803, 809, 847, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1280x960@60Hz */ -	{ DRM_MODE("1280x960", DRM_MODE_TYPE_DRIVER, 108000, 1280, 1376, -		   1488, 1800, 0, 960, 961, 964, 1000, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1280x960@85Hz */ -	{ DRM_MODE("1280x960", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1344, -		   1504, 1728, 0, 960, 961, 964, 1011, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1280x960@120Hz RB */ -	{ DRM_MODE("1280x960", DRM_MODE_TYPE_DRIVER, 175500, 1280, 1328, -		   1360, 1440, 0, 960, 963, 967, 1017, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1280x1024@60Hz */ -	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 108000, 1280, 1328, -		   1440, 1688, 0, 1024, 1025, 1028, 1066, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1280x1024@75Hz */ -	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 135000, 1280, 1296, -		   1440, 1688, 0, 1024, 1025, 1028, 1066, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1280x1024@85Hz */ -	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 157500, 1280, 1344, -		   1504, 1728, 0, 1024, 1025, 1028, 1072, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1280x1024@120Hz RB */ -	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 187250, 1280, 1328, -		   1360, 1440, 0, 1024, 1027, 1034, 1084, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1360x768@60Hz */ -	{ DRM_MODE("1360x768", DRM_MODE_TYPE_DRIVER, 85500, 1360, 1424, -		   1536, 1792, 0, 768, 771, 777, 795, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1360x768@120Hz RB */ -	{ DRM_MODE("1360x768", DRM_MODE_TYPE_DRIVER, 148250, 1360, 1408, -		   1440, 1520, 0, 768, 771, 776, 813, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1400x1050@60Hz RB */ -	{ DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 101000, 1400, 1448, -		   1480, 1560, 0, 1050, 1053, 1057, 1080, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1400x1050@60Hz */ -	{ DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 121750, 1400, 1488, -		   1632, 1864, 0, 1050, 1053, 1057, 1089, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1400x1050@75Hz */ -	{ DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 156000, 1400, 1504, -		   1648, 1896, 0, 1050, 1053, 1057, 1099, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1400x1050@85Hz */ -	{ DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 179500, 1400, 1504, -		   1656, 1912, 0, 1050, 1053, 1057, 1105, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1400x1050@120Hz RB */ -	{ DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 208000, 1400, 1448, -		   1480, 1560, 0, 1050, 1053, 1057, 1112, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1440x900@60Hz RB */ -	{ DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 88750, 1440, 1488, -		   1520, 1600, 0, 900, 903, 909, 926, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1440x900@60Hz */ -	{ DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 106500, 1440, 1520, -		   1672, 1904, 0, 900, 903, 909, 934, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1440x900@75Hz */ -	{ DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 136750, 1440, 1536, -		   1688, 1936, 0, 900, 903, 909, 942, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1440x900@85Hz */ -	{ DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 157000, 1440, 1544, -		   1696, 1952, 0, 900, 903, 909, 948, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1440x900@120Hz RB */ -	{ DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 182750, 1440, 1488, -		   1520, 1600, 0, 900, 903, 909, 953, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1600x1200@60Hz */ -	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 162000, 1600, 1664, -		   1856, 2160, 0, 1200, 1201, 1204, 1250, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1600x1200@65Hz */ -	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 175500, 1600, 1664, -		   1856, 2160, 0, 1200, 1201, 1204, 1250, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1600x1200@70Hz */ -	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 189000, 1600, 1664, -		   1856, 2160, 0, 1200, 1201, 1204, 1250, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1600x1200@75Hz */ -	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 202500, 1600, 1664, -		   1856, 2160, 0, 1200, 1201, 1204, 1250, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1600x1200@85Hz */ -	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 229500, 1600, 1664, -		   1856, 2160, 0, 1200, 1201, 1204, 1250, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1600x1200@120Hz RB */ -	{ DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 268250, 1600, 1648, -		   1680, 1760, 0, 1200, 1203, 1207, 1271, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1680x1050@60Hz RB */ -	{ DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 119000, 1680, 1728, -		   1760, 1840, 0, 1050, 1053, 1059, 1080, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1680x1050@60Hz */ -	{ DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 146250, 1680, 1784, -		   1960, 2240, 0, 1050, 1053, 1059, 1089, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1680x1050@75Hz */ -	{ DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 187000, 1680, 1800, -		   1976, 2272, 0, 1050, 1053, 1059, 1099, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1680x1050@85Hz */ -	{ DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 214750, 1680, 1808, -		   1984, 2288, 0, 1050, 1053, 1059, 1105, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1680x1050@120Hz RB */ -	{ DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 245500, 1680, 1728, -		   1760, 1840, 0, 1050, 1053, 1059, 1112, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1792x1344@60Hz */ -	{ DRM_MODE("1792x1344", DRM_MODE_TYPE_DRIVER, 204750, 1792, 1920, -		   2120, 2448, 0, 1344, 1345, 1348, 1394, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1792x1344@75Hz */ -	{ DRM_MODE("1792x1344", DRM_MODE_TYPE_DRIVER, 261000, 1792, 1888, -		   2104, 2456, 0, 1344, 1345, 1348, 1417, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1792x1344@120Hz RB */ -	{ DRM_MODE("1792x1344", DRM_MODE_TYPE_DRIVER, 333250, 1792, 1840, -		   1872, 1952, 0, 1344, 1347, 1351, 1423, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1856x1392@60Hz */ -	{ DRM_MODE("1856x1392", DRM_MODE_TYPE_DRIVER, 218250, 1856, 1952, -		   2176, 2528, 0, 1392, 1393, 1396, 1439, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1856x1392@75Hz */ -	{ DRM_MODE("1856x1392", DRM_MODE_TYPE_DRIVER, 288000, 1856, 1984, -		   2208, 2560, 0, 1392, 1395, 1399, 1500, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1856x1392@120Hz RB */ -	{ DRM_MODE("1856x1392", DRM_MODE_TYPE_DRIVER, 356500, 1856, 1904, -		   1936, 2016, 0, 1392, 1395, 1399, 1474, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1920x1200@60Hz RB */ -	{ DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 154000, 1920, 1968, -		   2000, 2080, 0, 1200, 1203, 1209, 1235, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1920x1200@60Hz */ -	{ DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 193250, 1920, 2056, -		   2256, 2592, 0, 1200, 1203, 1209, 1245, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1920x1200@75Hz */ -	{ DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 245250, 1920, 2056, -		   2264, 2608, 0, 1200, 1203, 1209, 1255, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1920x1200@85Hz */ -	{ DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 281250, 1920, 2064, -		   2272, 2624, 0, 1200, 1203, 1209, 1262, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1920x1200@120Hz RB */ -	{ DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 317000, 1920, 1968, -		   2000, 2080, 0, 1200, 1203, 1209, 1271, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 1920x1440@60Hz */ -	{ DRM_MODE("1920x1440", DRM_MODE_TYPE_DRIVER, 234000, 1920, 2048, -		   2256, 2600, 0, 1440, 1441, 1444, 1500, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1920x1440@75Hz */ -	{ DRM_MODE("1920x1440", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2064, -		   2288, 2640, 0, 1440, 1441, 1444, 1500, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 1920x1440@120Hz RB */ -	{ DRM_MODE("1920x1440", DRM_MODE_TYPE_DRIVER, 380500, 1920, 1968, -		   2000, 2080, 0, 1440, 1443, 1447, 1525, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 2560x1600@60Hz RB */ -	{ DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 268500, 2560, 2608, -		   2640, 2720, 0, 1600, 1603, 1609, 1646, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 2560x1600@60Hz */ -	{ DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 348500, 2560, 2752, -		   3032, 3504, 0, 1600, 1603, 1609, 1658, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 2560x1600@75HZ */ -	{ DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 443250, 2560, 2768, -		   3048, 3536, 0, 1600, 1603, 1609, 1672, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 2560x1600@85HZ */ -	{ DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 505250, 2560, 2768, -		   3048, 3536, 0, 1600, 1603, 1609, 1682, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 2560x1600@120Hz RB */ -	{ DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 552750, 2560, 2608, -		   2640, 2720, 0, 1600, 1603, 1609, 1694, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) }, - -}; -static const int drm_num_dmt_modes = -	sizeof(drm_dmt_modes) / sizeof(struct drm_display_mode); - -static const struct drm_display_mode edid_est_modes[] = { -	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 40000, 800, 840, -		   968, 1056, 0, 600, 601, 605, 628, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@60Hz */ -	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 36000, 800, 824, -		   896, 1024, 0, 600, 601, 603,  625, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@56Hz */ -	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 656, -		   720, 840, 0, 480, 481, 484, 500, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@75Hz */ -	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 664, -		   704,  832, 0, 480, 489, 491, 520, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@72Hz */ -	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 30240, 640, 704, -		   768,  864, 0, 480, 483, 486, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@67Hz */ -	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25200, 640, 656, -		   752, 800, 0, 480, 490, 492, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@60Hz */ -	{ DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 35500, 720, 738, -		   846, 900, 0, 400, 421, 423,  449, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 720x400@88Hz */ -	{ DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 28320, 720, 738, -		   846,  900, 0, 400, 412, 414, 449, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 720x400@70Hz */ -	{ DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 135000, 1280, 1296, -		   1440, 1688, 0, 1024, 1025, 1028, 1066, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1280x1024@75Hz */ -	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 78800, 1024, 1040, -		   1136, 1312, 0,  768, 769, 772, 800, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1024x768@75Hz */ -	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 75000, 1024, 1048, -		   1184, 1328, 0,  768, 771, 777, 806, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 1024x768@70Hz */ -	{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 65000, 1024, 1048, -		   1184, 1344, 0,  768, 771, 777, 806, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 1024x768@60Hz */ -	{ DRM_MODE("1024x768i", DRM_MODE_TYPE_DRIVER,44900, 1024, 1032, -		   1208, 1264, 0, 768, 768, 776, 817, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_INTERLACE) }, /* 1024x768@43Hz */ -	{ DRM_MODE("832x624", DRM_MODE_TYPE_DRIVER, 57284, 832, 864, -		   928, 1152, 0, 624, 625, 628, 667, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 832x624@75Hz */ -	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 49500, 800, 816, -		   896, 1056, 0, 600, 601, 604,  625, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@75Hz */ -	{ DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 50000, 800, 856, -		   976, 1040, 0, 600, 637, 643, 666, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@72Hz */ -	{ DRM_MODE("1152x864", DRM_MODE_TYPE_DRIVER, 108000, 1152, 1216, -		   1344, 1600, 0,  864, 865, 868, 900, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1152x864@75Hz */ -}; - -struct minimode { -	short w; -	short h; -	short r; -	short rb; -}; - -static const struct minimode est3_modes[] = { -	/* byte 6 */ -	{ 640, 350, 85, 0 }, -	{ 640, 400, 85, 0 }, -	{ 720, 400, 85, 0 }, -	{ 640, 480, 85, 0 }, -	{ 848, 480, 60, 0 }, -	{ 800, 600, 85, 0 }, -	{ 1024, 768, 85, 0 }, -	{ 1152, 864, 75, 0 }, -	/* byte 7 */ -	{ 1280, 768, 60, 1 }, -	{ 1280, 768, 60, 0 }, -	{ 1280, 768, 75, 0 }, -	{ 1280, 768, 85, 0 }, -	{ 1280, 960, 60, 0 }, -	{ 1280, 960, 85, 0 }, -	{ 1280, 1024, 60, 0 }, -	{ 1280, 1024, 85, 0 }, -	/* byte 8 */ -	{ 1360, 768, 60, 0 }, -	{ 1440, 900, 60, 1 }, -	{ 1440, 900, 60, 0 }, -	{ 1440, 900, 75, 0 }, -	{ 1440, 900, 85, 0 }, -	{ 1400, 1050, 60, 1 }, -	{ 1400, 1050, 60, 0 }, -	{ 1400, 1050, 75, 0 }, -	/* byte 9 */ -	{ 1400, 1050, 85, 0 }, -	{ 1680, 1050, 60, 1 }, -	{ 1680, 1050, 60, 0 }, -	{ 1680, 1050, 75, 0 }, -	{ 1680, 1050, 85, 0 }, -	{ 1600, 1200, 60, 0 }, -	{ 1600, 1200, 65, 0 }, -	{ 1600, 1200, 70, 0 }, -	/* byte 10 */ -	{ 1600, 1200, 75, 0 }, -	{ 1600, 1200, 85, 0 }, -	{ 1792, 1344, 60, 0 }, -	{ 1792, 1344, 85, 0 }, -	{ 1856, 1392, 60, 0 }, -	{ 1856, 1392, 75, 0 }, -	{ 1920, 1200, 60, 1 }, -	{ 1920, 1200, 60, 0 }, -	/* byte 11 */ -	{ 1920, 1200, 75, 0 }, -	{ 1920, 1200, 85, 0 }, -	{ 1920, 1440, 60, 0 }, -	{ 1920, 1440, 75, 0 }, -}; -static const int num_est3_modes = ARRAY_SIZE(est3_modes); - -static const struct minimode extra_modes[] = { -	{ 1024, 576,  60, 0 }, -	{ 1366, 768,  60, 0 }, -	{ 1600, 900,  60, 0 }, -	{ 1680, 945,  60, 0 }, -	{ 1920, 1080, 60, 0 }, -	{ 2048, 1152, 60, 0 }, -	{ 2048, 1536, 60, 0 }, -}; -static const int num_extra_modes = ARRAY_SIZE(extra_modes); - -/* - * Probably taken from CEA-861 spec. - * This table is converted from xorg's hw/xfree86/modes/xf86EdidModes.c. - */ -static const struct drm_display_mode edid_cea_modes[] = { -	/* 1 - 640x480@60Hz */ -	{ DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25175, 640, 656, -		   752, 800, 0, 480, 490, 492, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 2 - 720x480@60Hz */ -	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, -		   798, 858, 0, 480, 489, 495, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 3 - 720x480@60Hz */ -	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, -		   798, 858, 0, 480, 489, 495, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 4 - 1280x720@60Hz */ -	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, -		   1430, 1650, 0, 720, 725, 730, 750, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 5 - 1920x1080i@60Hz */ -	{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008, -		   2052, 2200, 0, 1080, 1084, 1094, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | -			DRM_MODE_FLAG_INTERLACE) }, -	/* 6 - 1440x480i@60Hz */ -	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1478, -		   1602, 1716, 0, 480, 488, 494, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, -	/* 7 - 1440x480i@60Hz */ -	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1478, -		   1602, 1716, 0, 480, 488, 494, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, -	/* 8 - 1440x240@60Hz */ -	{ DRM_MODE("1440x240", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1478, -		   1602, 1716, 0, 240, 244, 247, 262, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_DBLCLK) }, -	/* 9 - 1440x240@60Hz */ -	{ DRM_MODE("1440x240", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1478, -		   1602, 1716, 0, 240, 244, 247, 262, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_DBLCLK) }, -	/* 10 - 2880x480i@60Hz */ -	{ DRM_MODE("2880x480i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, -		   3204, 3432, 0, 480, 488, 494, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE) }, -	/* 11 - 2880x480i@60Hz */ -	{ DRM_MODE("2880x480i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, -		   3204, 3432, 0, 480, 488, 494, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE) }, -	/* 12 - 2880x240@60Hz */ -	{ DRM_MODE("2880x240", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, -		   3204, 3432, 0, 240, 244, 247, 262, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 13 - 2880x240@60Hz */ -	{ DRM_MODE("2880x240", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2956, -		   3204, 3432, 0, 240, 244, 247, 262, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 14 - 1440x480@60Hz */ -	{ DRM_MODE("1440x480", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1472, -		   1596, 1716, 0, 480, 489, 495, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 15 - 1440x480@60Hz */ -	{ DRM_MODE("1440x480", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1472, -		   1596, 1716, 0, 480, 489, 495, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 16 - 1920x1080@60Hz */ -	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008, -		   2052, 2200, 0, 1080, 1084, 1089, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 17 - 720x576@50Hz */ -	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000, 720, 732, -		   796, 864, 0, 576, 581, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 18 - 720x576@50Hz */ -	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000, 720, 732, -		   796, 864, 0, 576, 581, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 19 - 1280x720@50Hz */ -	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720, -		   1760, 1980, 0, 720, 725, 730, 750, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 20 - 1920x1080i@50Hz */ -	{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448, -		   2492, 2640, 0, 1080, 1084, 1094, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | -			DRM_MODE_FLAG_INTERLACE) }, -	/* 21 - 1440x576i@50Hz */ -	{ DRM_MODE("1440x576i", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1464, -		   1590, 1728, 0, 576, 580, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, -	/* 22 - 1440x576i@50Hz */ -	{ DRM_MODE("1440x576i", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1464, -		   1590, 1728, 0, 576, 580, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, -	/* 23 - 1440x288@50Hz */ -	{ DRM_MODE("1440x288", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1464, -		   1590, 1728, 0, 288, 290, 293, 312, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_DBLCLK) }, -	/* 24 - 1440x288@50Hz */ -	{ DRM_MODE("1440x288", DRM_MODE_TYPE_DRIVER, 27000, 1440, 1464, -		   1590, 1728, 0, 288, 290, 293, 312, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_DBLCLK) }, -	/* 25 - 2880x576i@50Hz */ -	{ DRM_MODE("2880x576i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, -		   3180, 3456, 0, 576, 580, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE) }, -	/* 26 - 2880x576i@50Hz */ -	{ DRM_MODE("2880x576i", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, -		   3180, 3456, 0, 576, 580, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE) }, -	/* 27 - 2880x288@50Hz */ -	{ DRM_MODE("2880x288", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, -		   3180, 3456, 0, 288, 290, 293, 312, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 28 - 2880x288@50Hz */ -	{ DRM_MODE("2880x288", DRM_MODE_TYPE_DRIVER, 54000, 2880, 2928, -		   3180, 3456, 0, 288, 290, 293, 312, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 29 - 1440x576@50Hz */ -	{ DRM_MODE("1440x576", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1464, -		   1592, 1728, 0, 576, 581, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 30 - 1440x576@50Hz */ -	{ DRM_MODE("1440x576", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1464, -		   1592, 1728, 0, 576, 581, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 31 - 1920x1080@50Hz */ -	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448, -		   2492, 2640, 0, 1080, 1084, 1089, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 32 - 1920x1080@24Hz */ -	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2558, -		   2602, 2750, 0, 1080, 1084, 1089, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 33 - 1920x1080@25Hz */ -	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448, -		   2492, 2640, 0, 1080, 1084, 1089, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 34 - 1920x1080@30Hz */ -	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008, -		   2052, 2200, 0, 1080, 1084, 1089, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 35 - 2880x480@60Hz */ -	{ DRM_MODE("2880x480", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2944, -		   3192, 3432, 0, 480, 489, 495, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 36 - 2880x480@60Hz */ -	{ DRM_MODE("2880x480", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2944, -		   3192, 3432, 0, 480, 489, 495, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 37 - 2880x576@50Hz */ -	{ DRM_MODE("2880x576", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2928, -		   3184, 3456, 0, 576, 581, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 38 - 2880x576@50Hz */ -	{ DRM_MODE("2880x576", DRM_MODE_TYPE_DRIVER, 108000, 2880, 2928, -		   3184, 3456, 0, 576, 581, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 39 - 1920x1080i@50Hz */ -	{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 72000, 1920, 1952, -		   2120, 2304, 0, 1080, 1126, 1136, 1250, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE) }, -	/* 40 - 1920x1080i@100Hz */ -	{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448, -		   2492, 2640, 0, 1080, 1084, 1094, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | -			DRM_MODE_FLAG_INTERLACE) }, -	/* 41 - 1280x720@100Hz */ -	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1720, -		   1760, 1980, 0, 720, 725, 730, 750, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 42 - 720x576@100Hz */ -	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 54000, 720, 732, -		   796, 864, 0, 576, 581, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 43 - 720x576@100Hz */ -	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 54000, 720, 732, -		   796, 864, 0, 576, 581, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 44 - 1440x576i@100Hz */ -	{ DRM_MODE("1440x576", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1464, -		   1590, 1728, 0, 576, 580, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_DBLCLK) }, -	/* 45 - 1440x576i@100Hz */ -	{ DRM_MODE("1440x576", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1464, -		   1590, 1728, 0, 576, 580, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_DBLCLK) }, -	/* 46 - 1920x1080i@120Hz */ -	{ DRM_MODE("1920x1080i", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008, -		   2052, 2200, 0, 1080, 1084, 1094, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | -			DRM_MODE_FLAG_INTERLACE) }, -	/* 47 - 1280x720@120Hz */ -	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1390, -		   1430, 1650, 0, 720, 725, 730, 750, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 48 - 720x480@120Hz */ -	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 54000, 720, 736, -		   798, 858, 0, 480, 489, 495, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 49 - 720x480@120Hz */ -	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 54000, 720, 736, -		   798, 858, 0, 480, 489, 495, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 50 - 1440x480i@120Hz */ -	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1478, -		   1602, 1716, 0, 480, 488, 494, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, -	/* 51 - 1440x480i@120Hz */ -	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 54000, 1440, 1478, -		   1602, 1716, 0, 480, 488, 494, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, -	/* 52 - 720x576@200Hz */ -	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 108000, 720, 732, -		   796, 864, 0, 576, 581, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 53 - 720x576@200Hz */ -	{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 108000, 720, 732, -		   796, 864, 0, 576, 581, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 54 - 1440x576i@200Hz */ -	{ DRM_MODE("1440x576i", DRM_MODE_TYPE_DRIVER, 108000, 1440, 1464, -		   1590, 1728, 0, 576, 580, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, -	/* 55 - 1440x576i@200Hz */ -	{ DRM_MODE("1440x576i", DRM_MODE_TYPE_DRIVER, 108000, 1440, 1464, -		   1590, 1728, 0, 576, 580, 586, 625, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, -	/* 56 - 720x480@240Hz */ -	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 108000, 720, 736, -		   798, 858, 0, 480, 489, 495, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 57 - 720x480@240Hz */ -	{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 108000, 720, 736, -		   798, 858, 0, 480, 489, 495, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, -	/* 58 - 1440x480i@240 */ -	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 108000, 1440, 1478, -		   1602, 1716, 0, 480, 488, 494, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, -	/* 59 - 1440x480i@240 */ -	{ DRM_MODE("1440x480i", DRM_MODE_TYPE_DRIVER, 108000, 1440, 1478, -		   1602, 1716, 0, 480, 488, 494, 525, 0, -		   DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC | -			DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLCLK) }, -	/* 60 - 1280x720@24Hz */ -	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 59400, 1280, 3040, -		   3080, 3300, 0, 720, 725, 730, 750, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 61 - 1280x720@25Hz */ -	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 3700, -		   3740, 3960, 0, 720, 725, 730, 750, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 62 - 1280x720@30Hz */ -	{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 3040, -		   3080, 3300, 0, 720, 725, 730, 750, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 63 - 1920x1080@120Hz */ -	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2008, -		   2052, 2200, 0, 1080, 1084, 1089, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -	/* 64 - 1920x1080@100Hz */ -	{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2448, -		   2492, 2640, 0, 1080, 1084, 1094, 1125, 0, -		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, -}; -static const int drm_num_cea_modes = ARRAY_SIZE(edid_cea_modes); diff --git a/drivers/gpu/drm/drm_encoder_slave.c b/drivers/gpu/drm/drm_encoder_slave.c index 63e733408b6..48c52f7df4e 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 3742bc96421..0b5af7d0edb 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 0c6e25e979d..59d6b9bf204 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.c b/drivers/gpu/drm/drm_gem.c index 24efae464e2..af779ae19eb 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -270,21 +270,19 @@ drm_gem_handle_create(struct drm_file *file_priv,  	int ret;  	/* -	 * Get the user-visible handle using idr. +	 * Get the user-visible handle using idr.  Preload and perform +	 * allocation under our spinlock.  	 */ -again: -	/* ensure there is space available to allocate a handle */ -	if (idr_pre_get(&file_priv->object_idr, GFP_KERNEL) == 0) -		return -ENOMEM; - -	/* do the allocation under our spinlock */ +	idr_preload(GFP_KERNEL);  	spin_lock(&file_priv->table_lock); -	ret = idr_get_new_above(&file_priv->object_idr, obj, 1, (int *)handlep); + +	ret = idr_alloc(&file_priv->object_idr, obj, 1, 0, GFP_NOWAIT); +  	spin_unlock(&file_priv->table_lock); -	if (ret == -EAGAIN) -		goto again; -	else if (ret) +	idr_preload_end(); +	if (ret < 0)  		return ret; +	*handlep = ret;  	drm_gem_object_handle_reference(obj); @@ -451,29 +449,25 @@ drm_gem_flink_ioctl(struct drm_device *dev, void *data,  	if (obj == NULL)  		return -ENOENT; -again: -	if (idr_pre_get(&dev->object_name_idr, GFP_KERNEL) == 0) { -		ret = -ENOMEM; -		goto err; -	} - +	idr_preload(GFP_KERNEL);  	spin_lock(&dev->object_name_lock);  	if (!obj->name) { -		ret = idr_get_new_above(&dev->object_name_idr, obj, 1, -					&obj->name); +		ret = idr_alloc(&dev->object_name_idr, obj, 1, 0, GFP_NOWAIT); +		obj->name = ret;  		args->name = (uint64_t) obj->name;  		spin_unlock(&dev->object_name_lock); +		idr_preload_end(); -		if (ret == -EAGAIN) -			goto again; -		else if (ret) +		if (ret < 0)  			goto err; +		ret = 0;  		/* Allocate a reference for the name table.  */  		drm_gem_object_reference(obj);  	} else {  		args->name = (uint64_t) obj->name;  		spin_unlock(&dev->object_name_lock); +		idr_preload_end();  		ret = 0;  	} @@ -561,8 +555,6 @@ drm_gem_release(struct drm_device *dev, struct drm_file *file_private)  {  	idr_for_each(&file_private->object_idr,  		     &drm_gem_object_release_handle, file_private); - -	idr_remove_all(&file_private->object_idr);  	idr_destroy(&file_private->object_idr);  } diff --git a/drivers/gpu/drm/drm_gem_cma_helper.c b/drivers/gpu/drm/drm_gem_cma_helper.c index 1aa8fee1e86..0a7e011509b 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_hashtab.c b/drivers/gpu/drm/drm_hashtab.c index 80254547a3f..7e4bae760e2 100644 --- a/drivers/gpu/drm/drm_hashtab.c +++ b/drivers/gpu/drm/drm_hashtab.c @@ -60,14 +60,13 @@ void drm_ht_verbose_list(struct drm_open_hash *ht, unsigned long key)  {  	struct drm_hash_item *entry;  	struct hlist_head *h_list; -	struct hlist_node *list;  	unsigned int hashed_key;  	int count = 0;  	hashed_key = hash_long(key, ht->order);  	DRM_DEBUG("Key is 0x%08lx, Hashed key is 0x%08x\n", key, hashed_key);  	h_list = &ht->table[hashed_key]; -	hlist_for_each_entry(entry, list, h_list, head) +	hlist_for_each_entry(entry, h_list, head)  		DRM_DEBUG("count %d, key: 0x%08lx\n", count++, entry->key);  } @@ -76,14 +75,13 @@ static struct hlist_node *drm_ht_find_key(struct drm_open_hash *ht,  {  	struct drm_hash_item *entry;  	struct hlist_head *h_list; -	struct hlist_node *list;  	unsigned int hashed_key;  	hashed_key = hash_long(key, ht->order);  	h_list = &ht->table[hashed_key]; -	hlist_for_each_entry(entry, list, h_list, head) { +	hlist_for_each_entry(entry, h_list, head) {  		if (entry->key == key) -			return list; +			return &entry->head;  		if (entry->key > key)  			break;  	} @@ -95,14 +93,13 @@ static struct hlist_node *drm_ht_find_key_rcu(struct drm_open_hash *ht,  {  	struct drm_hash_item *entry;  	struct hlist_head *h_list; -	struct hlist_node *list;  	unsigned int hashed_key;  	hashed_key = hash_long(key, ht->order);  	h_list = &ht->table[hashed_key]; -	hlist_for_each_entry_rcu(entry, list, h_list, head) { +	hlist_for_each_entry_rcu(entry, h_list, head) {  		if (entry->key == key) -			return list; +			return &entry->head;  		if (entry->key > key)  			break;  	} @@ -113,19 +110,19 @@ int drm_ht_insert_item(struct drm_open_hash *ht, struct drm_hash_item *item)  {  	struct drm_hash_item *entry;  	struct hlist_head *h_list; -	struct hlist_node *list, *parent; +	struct hlist_node *parent;  	unsigned int hashed_key;  	unsigned long key = item->key;  	hashed_key = hash_long(key, ht->order);  	h_list = &ht->table[hashed_key];  	parent = NULL; -	hlist_for_each_entry(entry, list, h_list, head) { +	hlist_for_each_entry(entry, h_list, head) {  		if (entry->key == key)  			return -EINVAL;  		if (entry->key > key)  			break; -		parent = list; +		parent = &entry->head;  	}  	if (parent) {  		hlist_add_after_rcu(parent, &item->head); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 38e79927b2d..a6a8643a6a7 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 86102a08f65..bd719e936e1 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/drm_stub.c b/drivers/gpu/drm/drm_stub.c index 200e104f1fa..7d30802a018 100644 --- a/drivers/gpu/drm/drm_stub.c +++ b/drivers/gpu/drm/drm_stub.c @@ -109,7 +109,6 @@ EXPORT_SYMBOL(drm_ut_debug_printk);  static int drm_minor_get_id(struct drm_device *dev, int type)  { -	int new_id;  	int ret;  	int base = 0, limit = 63; @@ -121,25 +120,11 @@ static int drm_minor_get_id(struct drm_device *dev, int type)                  limit = base + 255;          } -again: -	if (idr_pre_get(&drm_minors_idr, GFP_KERNEL) == 0) { -		DRM_ERROR("Out of memory expanding drawable idr\n"); -		return -ENOMEM; -	}  	mutex_lock(&dev->struct_mutex); -	ret = idr_get_new_above(&drm_minors_idr, NULL, -				base, &new_id); +	ret = idr_alloc(&drm_minors_idr, NULL, base, limit, GFP_KERNEL);  	mutex_unlock(&dev->struct_mutex); -	if (ret == -EAGAIN) -		goto again; -	else if (ret) -		return ret; -	if (new_id >= limit) { -		idr_remove(&drm_minors_idr, new_id); -		return -EINVAL; -	} -	return new_id; +	return ret == -ENOSPC ? -EINVAL : ret;  }  struct drm_master *drm_master_create(struct drm_minor *minor) diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.c b/drivers/gpu/drm/exynos/exynos_drm_fb.c index 294c0513f58..0e04f4ea441 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 90d335cfb8c..68f0045f86b 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_fimc.c b/drivers/gpu/drm/exynos/exynos_drm_fimc.c index 67a83e69544..411f69b76e8 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimc.c @@ -1785,11 +1785,9 @@ static int fimc_probe(struct platform_device *pdev)  	/* resource memory */  	ctx->regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	ctx->regs = devm_request_and_ioremap(dev, ctx->regs_res); -	if (!ctx->regs) { -		dev_err(dev, "failed to map registers.\n"); -		return -ENXIO; -	} +	ctx->regs = devm_ioremap_resource(dev, ctx->regs_res); +	if (IS_ERR(ctx->regs)) +		return PTR_ERR(ctx->regs);  	/* resource irq */  	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index 9537761931e..36493ce71f9 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -913,11 +913,9 @@ static int fimd_probe(struct platform_device *pdev)  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	ctx->regs = devm_request_and_ioremap(&pdev->dev, res); -	if (!ctx->regs) { -		dev_err(dev, "failed to map registers\n"); -		return -ENXIO; -	} +	ctx->regs = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(ctx->regs)) +		return PTR_ERR(ctx->regs);  	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);  	if (!res) { diff --git a/drivers/gpu/drm/exynos/exynos_drm_g2d.c b/drivers/gpu/drm/exynos/exynos_drm_g2d.c index 9a4c08e7453..3b0da0378ac 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; @@ -1136,10 +1137,9 @@ static int g2d_probe(struct platform_device *pdev)  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	g2d->regs = devm_request_and_ioremap(&pdev->dev, res); -	if (!g2d->regs) { -		dev_err(dev, "failed to remap I/O memory\n"); -		ret = -ENXIO; +	g2d->regs = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(g2d->regs)) { +		ret = PTR_ERR(g2d->regs);  		goto err_put_clk;  	} @@ -1240,6 +1240,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 +1255,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 47318077652..67e17ce112b 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_gsc.c b/drivers/gpu/drm/exynos/exynos_drm_gsc.c index 8140753ec9c..7841c3b8a20 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gsc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gsc.c @@ -1692,11 +1692,9 @@ static int gsc_probe(struct platform_device *pdev)  	/* resource memory */  	ctx->regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	ctx->regs = devm_request_and_ioremap(dev, ctx->regs_res); -	if (!ctx->regs) { -		dev_err(dev, "failed to map registers.\n"); -		return -ENXIO; -	} +	ctx->regs = devm_ioremap_resource(dev, ctx->regs_res); +	if (IS_ERR(ctx->regs)) +		return PTR_ERR(ctx->regs);  	/* resource irq */  	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.c b/drivers/gpu/drm/exynos/exynos_drm_hdmi.c index 28644539b30..7c27df03c9f 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 d80516fc9ed..b7faa366230 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 53b7deea8ab..598e60f57d4 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_drm_ipp.c b/drivers/gpu/drm/exynos/exynos_drm_ipp.c index 1a556354e92..1adce07ecb5 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_ipp.c +++ b/drivers/gpu/drm/exynos/exynos_drm_ipp.c @@ -137,21 +137,15 @@ static int ipp_create_id(struct idr *id_idr, struct mutex *lock, void *obj,  	DRM_DEBUG_KMS("%s\n", __func__); -again: -	/* ensure there is space available to allocate a handle */ -	if (idr_pre_get(id_idr, GFP_KERNEL) == 0) { -		DRM_ERROR("failed to get idr.\n"); -		return -ENOMEM; -	} -  	/* do the allocation under our mutexlock */  	mutex_lock(lock); -	ret = idr_get_new_above(id_idr, obj, 1, (int *)idp); +	ret = idr_alloc(id_idr, obj, 1, 0, GFP_KERNEL);  	mutex_unlock(lock); -	if (ret == -EAGAIN) -		goto again; +	if (ret < 0) +		return ret; -	return ret; +	*idp = ret; +	return 0;  }  static void *ipp_find_obj(struct idr *id_idr, struct mutex *lock, u32 id) @@ -1786,8 +1780,6 @@ err_iommu:  			drm_iommu_detach_device(drm_dev, ippdrv->dev);  err_idr: -	idr_remove_all(&ctx->ipp_idr); -	idr_remove_all(&ctx->prop_idr);  	idr_destroy(&ctx->ipp_idr);  	idr_destroy(&ctx->prop_idr);  	return ret; @@ -1965,8 +1957,6 @@ static int ipp_remove(struct platform_device *pdev)  	exynos_drm_subdrv_unregister(&ctx->subdrv);  	/* remove,destroy ipp idr */ -	idr_remove_all(&ctx->ipp_idr); -	idr_remove_all(&ctx->prop_idr);  	idr_destroy(&ctx->ipp_idr);  	idr_destroy(&ctx->prop_idr); diff --git a/drivers/gpu/drm/exynos/exynos_drm_rotator.c b/drivers/gpu/drm/exynos/exynos_drm_rotator.c index f976e29def6..a40b9fb6024 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_rotator.c +++ b/drivers/gpu/drm/exynos/exynos_drm_rotator.c @@ -656,11 +656,9 @@ static int rotator_probe(struct platform_device *pdev)  				platform_get_device_id(pdev)->driver_data;  	rot->regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	rot->regs = devm_request_and_ioremap(dev, rot->regs_res); -	if (!rot->regs) { -		dev_err(dev, "failed to map register\n"); -		return -ENXIO; -	} +	rot->regs = devm_ioremap_resource(dev, rot->regs_res); +	if (IS_ERR(rot->regs)) +		return PTR_ERR(rot->regs);  	rot->irq = platform_get_irq(pdev, 0);  	if (rot->irq < 0) { diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index fbab3c46860..2c5f266154a 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, +struct hdmiphy_config { +	int pixel_clock; +	u8 conf[32];  }; -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 */ -	}, -}; - -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); - -	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; +			check_timing->refresh, check_timing->pixclock, +			(check_timing->vmode & FB_VMODE_INTERLACED) ? +			true : false); -	/* 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, @@ -2501,11 +2226,9 @@ static int hdmi_probe(struct platform_device *pdev)  		return -ENOENT;  	} -	hdata->regs = devm_request_and_ioremap(&pdev->dev, res); -	if (!hdata->regs) { -		DRM_ERROR("failed to map registers\n"); -		return -ENXIO; -	} +	hdata->regs = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(hdata->regs)) +		return PTR_ERR(hdata->regs);  	ret = devm_gpio_request(&pdev->dev, hdata->hpd_gpio, "HPD");  	if (ret) { diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c index c414584bfba..e919aba29b3 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/Kconfig b/drivers/gpu/drm/gma500/Kconfig index 42e665c7e90..1188f0fe7e4 100644 --- a/drivers/gpu/drm/gma500/Kconfig +++ b/drivers/gpu/drm/gma500/Kconfig @@ -1,6 +1,6 @@  config DRM_GMA500  	tristate "Intel GMA5/600 KMS Framebuffer" -	depends on DRM && PCI && X86 && EXPERIMENTAL +	depends on DRM && PCI && X86  	select FB_CFB_COPYAREA          select FB_CFB_FILLRECT          select FB_CFB_IMAGEBLIT diff --git a/drivers/gpu/drm/gma500/cdv_intel_dp.c b/drivers/gpu/drm/gma500/cdv_intel_dp.c index 51044cc55cf..88d9ef6b5b4 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_dp.c +++ b/drivers/gpu/drm/gma500/cdv_intel_dp.c @@ -27,6 +27,7 @@  #include <linux/i2c.h>  #include <linux/slab.h> +#include <linux/module.h>  #include <drm/drmP.h>  #include <drm/drm_crtc.h>  #include <drm/drm_crtc_helper.h> diff --git a/drivers/gpu/drm/gma500/framebuffer.c b/drivers/gpu/drm/gma500/framebuffer.c index c1ef37e2efd..2590cac8425 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/gtt.c b/drivers/gpu/drm/gma500/gtt.c index 04a371aceb3..054e26e769e 100644 --- a/drivers/gpu/drm/gma500/gtt.c +++ b/drivers/gpu/drm/gma500/gtt.c @@ -202,7 +202,7 @@ static int psb_gtt_attach_pages(struct gtt_range *gt)  	WARN_ON(gt->pages);  	/* This is the shared memory object that backs the GEM resource */ -	inode = gt->gem.filp->f_path.dentry->d_inode; +	inode = file_inode(gt->gem.filp);  	mapping = inode->i_mapping;  	gt->pages = kmalloc(pages * sizeof(struct page *), GFP_KERNEL); diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c index 8033526bb53..9edb1902a09 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 16118363509..4d341db462a 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 92862563e7e..43aa33baebe 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 00000000000..e68b58a1aaf --- /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/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c index 26487d18b02..7df83512161 100644 --- a/drivers/gpu/drm/i915/i915_debugfs.c +++ b/drivers/gpu/drm/i915/i915_debugfs.c @@ -1533,7 +1533,7 @@ static const char *swizzle_string(unsigned swizzle)  	case I915_BIT_6_SWIZZLE_9_10_17:  		return "bit9/bit10/bit17";  	case I915_BIT_6_SWIZZLE_UNKNOWN: -		return "unkown"; +		return "unknown";  	}  	return "bug"; diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 1ebed9670ab..db309984587 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -379,15 +379,15 @@ static const struct pci_device_id pciidlist[] = {		/* aka */  	INTEL_VGA_DEVICE(0x0A06, &intel_haswell_m_info), /* ULT GT1 mobile */  	INTEL_VGA_DEVICE(0x0A16, &intel_haswell_m_info), /* ULT GT2 mobile */  	INTEL_VGA_DEVICE(0x0A26, &intel_haswell_m_info), /* ULT GT2 mobile */ -	INTEL_VGA_DEVICE(0x0D12, &intel_haswell_d_info), /* CRW GT1 desktop */ +	INTEL_VGA_DEVICE(0x0D02, &intel_haswell_d_info), /* CRW GT1 desktop */ +	INTEL_VGA_DEVICE(0x0D12, &intel_haswell_d_info), /* CRW GT2 desktop */  	INTEL_VGA_DEVICE(0x0D22, &intel_haswell_d_info), /* CRW GT2 desktop */ -	INTEL_VGA_DEVICE(0x0D32, &intel_haswell_d_info), /* CRW GT2 desktop */ -	INTEL_VGA_DEVICE(0x0D1A, &intel_haswell_d_info), /* CRW GT1 server */ +	INTEL_VGA_DEVICE(0x0D0A, &intel_haswell_d_info), /* CRW GT1 server */ +	INTEL_VGA_DEVICE(0x0D1A, &intel_haswell_d_info), /* CRW GT2 server */  	INTEL_VGA_DEVICE(0x0D2A, &intel_haswell_d_info), /* CRW GT2 server */ -	INTEL_VGA_DEVICE(0x0D3A, &intel_haswell_d_info), /* CRW GT2 server */ -	INTEL_VGA_DEVICE(0x0D16, &intel_haswell_m_info), /* CRW GT1 mobile */ +	INTEL_VGA_DEVICE(0x0D06, &intel_haswell_m_info), /* CRW GT1 mobile */ +	INTEL_VGA_DEVICE(0x0D16, &intel_haswell_m_info), /* CRW GT2 mobile */  	INTEL_VGA_DEVICE(0x0D26, &intel_haswell_m_info), /* CRW GT2 mobile */ -	INTEL_VGA_DEVICE(0x0D36, &intel_haswell_m_info), /* CRW GT2 mobile */  	INTEL_VGA_DEVICE(0x0f30, &intel_valleyview_m_info),  	INTEL_VGA_DEVICE(0x0157, &intel_valleyview_m_info),  	INTEL_VGA_DEVICE(0x0155, &intel_valleyview_d_info), @@ -495,6 +495,7 @@ static int i915_drm_freeze(struct drm_device *dev)  		intel_modeset_disable(dev);  		drm_irq_uninstall(dev); +		dev_priv->enable_hotplug_processing = false;  	}  	i915_save_state(dev); @@ -568,10 +569,20 @@ static int __i915_drm_thaw(struct drm_device *dev)  		error = i915_gem_init_hw(dev);  		mutex_unlock(&dev->struct_mutex); +		/* We need working interrupts for modeset enabling ... */ +		drm_irq_install(dev); +  		intel_modeset_init_hw(dev);  		intel_modeset_setup_hw_state(dev, false); -		drm_irq_install(dev); + +		/* +		 * ... but also need to make sure that hotplug processing +		 * doesn't cause havoc. Like in the driver load code we don't +		 * bother with the tiny race here where we might loose hotplug +		 * notifications. +		 * */  		intel_hpd_init(dev); +		dev_priv->enable_hotplug_processing = true;  	}  	intel_opregion_init(dev); diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index d554b2171df..30117dc5221 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -1618,7 +1618,7 @@ i915_gem_object_truncate(struct drm_i915_gem_object *obj)  	 * To do this we must instruct the shmfs to drop all of its  	 * backing pages, *now*.  	 */ -	inode = obj->base.filp->f_path.dentry->d_inode; +	inode = file_inode(obj->base.filp);  	shmem_truncate_range(inode, 0, (loff_t)-1);  	obj->madv = __I915_MADV_PURGED; @@ -1783,7 +1783,7 @@ i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj)  	 *  	 * Fail silently without starting the shrinker  	 */ -	mapping = obj->base.filp->f_path.dentry->d_inode->i_mapping; +	mapping = file_inode(obj->base.filp)->i_mapping;  	gfp = mapping_gfp_mask(mapping);  	gfp |= __GFP_NORETRY | __GFP_NOWARN | __GFP_NO_KSWAPD;  	gfp &= ~(__GFP_IO | __GFP_WAIT); @@ -3747,7 +3747,7 @@ struct drm_i915_gem_object *i915_gem_alloc_object(struct drm_device *dev,  		mask |= __GFP_DMA32;  	} -	mapping = obj->base.filp->f_path.dentry->d_inode->i_mapping; +	mapping = file_inode(obj->base.filp)->i_mapping;  	mapping_set_gfp_mask(mapping, mask);  	i915_gem_object_init(obj, &i915_gem_object_ops); @@ -4241,7 +4241,7 @@ void i915_gem_free_all_phys_object(struct drm_device *dev)  void i915_gem_detach_phys_object(struct drm_device *dev,  				 struct drm_i915_gem_object *obj)  { -	struct address_space *mapping = obj->base.filp->f_path.dentry->d_inode->i_mapping; +	struct address_space *mapping = file_inode(obj->base.filp)->i_mapping;  	char *vaddr;  	int i;  	int page_count; @@ -4277,7 +4277,7 @@ i915_gem_attach_phys_object(struct drm_device *dev,  			    int id,  			    int align)  { -	struct address_space *mapping = obj->base.filp->f_path.dentry->d_inode->i_mapping; +	struct address_space *mapping = file_inode(obj->base.filp)->i_mapping;  	drm_i915_private_t *dev_priv = dev->dev_private;  	int ret = 0;  	int page_count; diff --git a/drivers/gpu/drm/i915/i915_gem_context.c b/drivers/gpu/drm/i915/i915_gem_context.c index 21177d9df42..94d873a6cff 100644 --- a/drivers/gpu/drm/i915/i915_gem_context.c +++ b/drivers/gpu/drm/i915/i915_gem_context.c @@ -139,7 +139,7 @@ create_hw_context(struct drm_device *dev,  {  	struct drm_i915_private *dev_priv = dev->dev_private;  	struct i915_hw_context *ctx; -	int ret, id; +	int ret;  	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);  	if (ctx == NULL) @@ -164,22 +164,11 @@ create_hw_context(struct drm_device *dev,  	ctx->file_priv = file_priv; -again: -	if (idr_pre_get(&file_priv->context_idr, GFP_KERNEL) == 0) { -		ret = -ENOMEM; -		DRM_DEBUG_DRIVER("idr allocation failed\n"); -		goto err_out; -	} - -	ret = idr_get_new_above(&file_priv->context_idr, ctx, -				DEFAULT_CONTEXT_ID + 1, &id); -	if (ret == 0) -		ctx->id = id; - -	if (ret == -EAGAIN) -		goto again; -	else if (ret) +	ret = idr_alloc(&file_priv->context_idr, ctx, DEFAULT_CONTEXT_ID + 1, 0, +			GFP_KERNEL); +	if (ret < 0)  		goto err_out; +	ctx->id = ret;  	return ctx; diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 63abf2fa1fa..62285ff0308 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -705,7 +705,7 @@ static irqreturn_t ivybridge_irq_handler(int irq, void *arg)  {  	struct drm_device *dev = (struct drm_device *) arg;  	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; -	u32 de_iir, gt_iir, de_ier, pm_iir; +	u32 de_iir, gt_iir, de_ier, pm_iir, sde_ier;  	irqreturn_t ret = IRQ_NONE;  	int i; @@ -715,6 +715,15 @@ static irqreturn_t ivybridge_irq_handler(int irq, void *arg)  	de_ier = I915_READ(DEIER);  	I915_WRITE(DEIER, de_ier & ~DE_MASTER_IRQ_CONTROL); +	/* Disable south interrupts. We'll only write to SDEIIR once, so further +	 * interrupts will will be stored on its back queue, and then we'll be +	 * able to process them after we restore SDEIER (as soon as we restore +	 * it, we'll get an interrupt if SDEIIR still has something to process +	 * due to its back queue). */ +	sde_ier = I915_READ(SDEIER); +	I915_WRITE(SDEIER, 0); +	POSTING_READ(SDEIER); +  	gt_iir = I915_READ(GTIIR);  	if (gt_iir) {  		snb_gt_irq_handler(dev, dev_priv, gt_iir); @@ -763,6 +772,8 @@ static irqreturn_t ivybridge_irq_handler(int irq, void *arg)  	I915_WRITE(DEIER, de_ier);  	POSTING_READ(DEIER); +	I915_WRITE(SDEIER, sde_ier); +	POSTING_READ(SDEIER);  	return ret;  } @@ -782,7 +793,7 @@ static irqreturn_t ironlake_irq_handler(int irq, void *arg)  	struct drm_device *dev = (struct drm_device *) arg;  	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;  	int ret = IRQ_NONE; -	u32 de_iir, gt_iir, de_ier, pm_iir; +	u32 de_iir, gt_iir, de_ier, pm_iir, sde_ier;  	atomic_inc(&dev_priv->irq_received); @@ -791,6 +802,15 @@ static irqreturn_t ironlake_irq_handler(int irq, void *arg)  	I915_WRITE(DEIER, de_ier & ~DE_MASTER_IRQ_CONTROL);  	POSTING_READ(DEIER); +	/* Disable south interrupts. We'll only write to SDEIIR once, so further +	 * interrupts will will be stored on its back queue, and then we'll be +	 * able to process them after we restore SDEIER (as soon as we restore +	 * it, we'll get an interrupt if SDEIIR still has something to process +	 * due to its back queue). */ +	sde_ier = I915_READ(SDEIER); +	I915_WRITE(SDEIER, 0); +	POSTING_READ(SDEIER); +  	de_iir = I915_READ(DEIIR);  	gt_iir = I915_READ(GTIIR);  	pm_iir = I915_READ(GEN6_PMIIR); @@ -853,6 +873,8 @@ static irqreturn_t ironlake_irq_handler(int irq, void *arg)  done:  	I915_WRITE(DEIER, de_ier);  	POSTING_READ(DEIER); +	I915_WRITE(SDEIER, sde_ier); +	POSTING_READ(SDEIER);  	return ret;  } diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index f187b60739e..176cf5c5850 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -1617,9 +1617,9 @@  #define   ADPA_CRT_HOTPLUG_FORCE_TRIGGER (1<<16)  #define   ADPA_USE_VGA_HVPOLARITY (1<<15)  #define   ADPA_SETS_HVPOLARITY	0 -#define   ADPA_VSYNC_CNTL_DISABLE (1<<11) +#define   ADPA_VSYNC_CNTL_DISABLE (1<<10)  #define   ADPA_VSYNC_CNTL_ENABLE 0 -#define   ADPA_HSYNC_CNTL_DISABLE (1<<10) +#define   ADPA_HSYNC_CNTL_DISABLE (1<<11)  #define   ADPA_HSYNC_CNTL_ENABLE 0  #define   ADPA_VSYNC_ACTIVE_HIGH (1<<4)  #define   ADPA_VSYNC_ACTIVE_LOW	0 diff --git a/drivers/gpu/drm/i915/intel_crt.c b/drivers/gpu/drm/i915/intel_crt.c index cfc96878d74..32a3693905e 100644 --- a/drivers/gpu/drm/i915/intel_crt.c +++ b/drivers/gpu/drm/i915/intel_crt.c @@ -88,7 +88,7 @@ static void intel_disable_crt(struct intel_encoder *encoder)  	u32 temp;  	temp = I915_READ(crt->adpa_reg); -	temp &= ~(ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE); +	temp |= ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE;  	temp &= ~ADPA_DAC_ENABLE;  	I915_WRITE(crt->adpa_reg, temp);  } @@ -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 b49df7d9d23..bfcc58ffecb 100644 --- a/drivers/gpu/drm/i915/intel_ddi.c +++ b/drivers/gpu/drm/i915/intel_ddi.c @@ -1391,8 +1391,8 @@ void intel_ddi_prepare_link_retrain(struct drm_encoder *encoder)  	struct intel_dp *intel_dp = &intel_dig_port->dp;  	struct drm_i915_private *dev_priv = encoder->dev->dev_private;  	enum port port = intel_dig_port->port; -	bool wait;  	uint32_t val; +	bool wait = false;  	if (I915_READ(DP_TP_CTL(port)) & DP_TP_CTL_ENABLE) {  		val = I915_READ(DDI_BUF_CTL(port)); @@ -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 630a9677004..f73fc3df2bf 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -2014,18 +2014,29 @@ void intel_unpin_fb_obj(struct drm_i915_gem_object *obj)  /* Computes the linear offset to the base tile and adjusts x, y. bytes per pixel   * is assumed to be a power-of-two. */ -unsigned long intel_gen4_compute_offset_xtiled(int *x, int *y, -					       unsigned int bpp, -					       unsigned int pitch) +unsigned long intel_gen4_compute_page_offset(int *x, int *y, +					     unsigned int tiling_mode, +					     unsigned int cpp, +					     unsigned int pitch)  { -	int tile_rows, tiles; +	if (tiling_mode != I915_TILING_NONE) { +		unsigned int tile_rows, tiles; -	tile_rows = *y / 8; -	*y %= 8; -	tiles = *x / (512/bpp); -	*x %= 512/bpp; +		tile_rows = *y / 8; +		*y %= 8; -	return tile_rows * pitch * 8 + tiles * 4096; +		tiles = *x / (512/cpp); +		*x %= 512/cpp; + +		return tile_rows * pitch * 8 + tiles * 4096; +	} else { +		unsigned int offset; + +		offset = *y * pitch + *x * cpp; +		*y = 0; +		*x = (offset & 4095) / cpp; +		return offset & -4096; +	}  }  static int i9xx_update_plane(struct drm_crtc *crtc, struct drm_framebuffer *fb, @@ -2102,9 +2113,9 @@ static int i9xx_update_plane(struct drm_crtc *crtc, struct drm_framebuffer *fb,  	if (INTEL_INFO(dev)->gen >= 4) {  		intel_crtc->dspaddr_offset = -			intel_gen4_compute_offset_xtiled(&x, &y, -							 fb->bits_per_pixel / 8, -							 fb->pitches[0]); +			intel_gen4_compute_page_offset(&x, &y, obj->tiling_mode, +						       fb->bits_per_pixel / 8, +						       fb->pitches[0]);  		linear_offset -= intel_crtc->dspaddr_offset;  	} else {  		intel_crtc->dspaddr_offset = linear_offset; @@ -2195,9 +2206,9 @@ static int ironlake_update_plane(struct drm_crtc *crtc,  	linear_offset = y * fb->pitches[0] + x * (fb->bits_per_pixel / 8);  	intel_crtc->dspaddr_offset = -		intel_gen4_compute_offset_xtiled(&x, &y, -						 fb->bits_per_pixel / 8, -						 fb->pitches[0]); +		intel_gen4_compute_page_offset(&x, &y, obj->tiling_mode, +					       fb->bits_per_pixel / 8, +					       fb->pitches[0]);  	linear_offset -= intel_crtc->dspaddr_offset;  	DRM_DEBUG_KMS("Writing base %08X %08lX %d %d %d\n", @@ -3641,6 +3652,30 @@ static void intel_crtc_dpms_overlay(struct intel_crtc *intel_crtc, bool enable)  	 */  } +/** + * i9xx_fixup_plane - ugly workaround for G45 to fire up the hardware + * cursor plane briefly if not already running after enabling the display + * plane. + * This workaround avoids occasional blank screens when self refresh is + * enabled. + */ +static void +g4x_fixup_plane(struct drm_i915_private *dev_priv, enum pipe pipe) +{ +	u32 cntl = I915_READ(CURCNTR(pipe)); + +	if ((cntl & CURSOR_MODE) == 0) { +		u32 fw_bcl_self = I915_READ(FW_BLC_SELF); + +		I915_WRITE(FW_BLC_SELF, fw_bcl_self & ~FW_BLC_SELF_EN); +		I915_WRITE(CURCNTR(pipe), CURSOR_MODE_64_ARGB_AX); +		intel_wait_for_vblank(dev_priv->dev, pipe); +		I915_WRITE(CURCNTR(pipe), cntl); +		I915_WRITE(CURBASE(pipe), I915_READ(CURBASE(pipe))); +		I915_WRITE(FW_BLC_SELF, fw_bcl_self); +	} +} +  static void i9xx_crtc_enable(struct drm_crtc *crtc)  {  	struct drm_device *dev = crtc->dev; @@ -3666,6 +3701,8 @@ static void i9xx_crtc_enable(struct drm_crtc *crtc)  	intel_enable_pipe(dev_priv, pipe, false);  	intel_enable_plane(dev_priv, plane, pipe); +	if (IS_G4X(dev)) +		g4x_fixup_plane(dev_priv, pipe);  	intel_crtc_load_lut(crtc);  	intel_update_fbc(dev); @@ -3775,10 +3812,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; @@ -3827,10 +3860,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); @@ -7280,8 +7309,8 @@ static int intel_crtc_page_flip(struct drm_crtc *crtc,  {  	struct drm_device *dev = crtc->dev;  	struct drm_i915_private *dev_priv = dev->dev_private; -	struct intel_framebuffer *intel_fb; -	struct drm_i915_gem_object *obj; +	struct drm_framebuffer *old_fb = crtc->fb; +	struct drm_i915_gem_object *obj = to_intel_framebuffer(fb)->obj;  	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);  	struct intel_unpin_work *work;  	unsigned long flags; @@ -7306,8 +7335,7 @@ static int intel_crtc_page_flip(struct drm_crtc *crtc,  	work->event = event;  	work->crtc = crtc; -	intel_fb = to_intel_framebuffer(crtc->fb); -	work->old_fb_obj = intel_fb->obj; +	work->old_fb_obj = to_intel_framebuffer(old_fb)->obj;  	INIT_WORK(&work->work, intel_unpin_work_fn);  	ret = drm_vblank_get(dev, intel_crtc->pipe); @@ -7327,9 +7355,6 @@ static int intel_crtc_page_flip(struct drm_crtc *crtc,  	intel_crtc->unpin_work = work;  	spin_unlock_irqrestore(&dev->event_lock, flags); -	intel_fb = to_intel_framebuffer(fb); -	obj = intel_fb->obj; -  	if (atomic_read(&intel_crtc->unpin_work_count) >= 2)  		flush_workqueue(dev_priv->wq); @@ -7364,6 +7389,7 @@ static int intel_crtc_page_flip(struct drm_crtc *crtc,  cleanup_pending:  	atomic_dec(&intel_crtc->unpin_work_count); +	crtc->fb = old_fb;  	drm_gem_object_unreference(&work->old_fb_obj->base);  	drm_gem_object_unreference(&obj->base);  	mutex_unlock(&dev->struct_mutex); @@ -7383,7 +7409,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) @@ -8093,14 +8118,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 3921d879a91..662a185274e 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -728,7 +728,7 @@ intel_dp_mode_fixup(struct drm_encoder *encoder,  		 * CEA-861-E - 5.1 Default Encoding Parameters  		 * VESA DisplayPort Ver.1.2a - 5.1.1.1 Video Colorimetry  		 */ -		if (bpp != 18 && drm_mode_cea_vic(adjusted_mode) > 1) +		if (bpp != 18 && drm_match_cea_mode(adjusted_mode) > 1)  			intel_dp->color_range = DP_COLOR_RANGE_16_235;  		else  			intel_dp->color_range = 0; @@ -2529,7 +2529,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 010e998dda5..e6f84d0db03 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -526,7 +526,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); @@ -650,9 +649,10 @@ extern void intel_update_sprite_watermarks(struct drm_device *dev, int pipe,  extern void intel_update_linetime_watermarks(struct drm_device *dev, int pipe,  			 struct drm_display_mode *mode); -extern unsigned long intel_gen4_compute_offset_xtiled(int *x, int *y, -						      unsigned int bpp, -						      unsigned int pitch); +extern unsigned long intel_gen4_compute_page_offset(int *x, int *y, +						    unsigned int tiling_mode, +						    unsigned int bpp, +						    unsigned int pitch);  extern int intel_sprite_set_colorkey(struct drm_device *dev, void *data,  				     struct drm_file *file_priv); diff --git a/drivers/gpu/drm/i915/intel_dvo.c b/drivers/gpu/drm/i915/intel_dvo.c index 15da99533e5..00e70dbe82d 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 953ee7387d6..510a96047ae 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; @@ -179,26 +180,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 4d222ec58b8..c882839cd51 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c @@ -349,7 +349,7 @@ static void intel_hdmi_set_avi_infoframe(struct drm_encoder *encoder,  			avi_if.body.avi.ITC_EC_Q_SC |= DIP_AVI_RGB_QUANT_RANGE_FULL;  	} -	avi_if.body.avi.VIC = drm_mode_cea_vic(adjusted_mode); +	avi_if.body.avi.VIC = drm_match_cea_mode(adjusted_mode);  	intel_set_infoframe(encoder, &avi_if);  } @@ -777,7 +777,7 @@ bool intel_hdmi_mode_fixup(struct drm_encoder *encoder,  	if (intel_hdmi->color_range_auto) {  		/* See CEA-861-E - 5.1 Default Encoding Parameters */  		if (intel_hdmi->has_hdmi_sink && -		    drm_mode_cea_vic(adjusted_mode) > 1) +		    drm_match_cea_mode(adjusted_mode) > 1)  			intel_hdmi->color_range = HDMI_COLOR_RANGE_16_235;  		else  			intel_hdmi->color_range = 0; @@ -789,28 +789,6 @@ bool intel_hdmi_mode_fixup(struct drm_encoder *encoder,  	return true;  } -static bool g4x_hdmi_connected(struct intel_hdmi *intel_hdmi) -{ -	struct drm_device *dev = intel_hdmi_to_dev(intel_hdmi); -	struct drm_i915_private *dev_priv = dev->dev_private; -	struct intel_digital_port *intel_dig_port = hdmi_to_dig_port(intel_hdmi); -	uint32_t bit; - -	switch (intel_dig_port->port) { -	case PORT_B: -		bit = PORTB_HOTPLUG_LIVE_STATUS; -		break; -	case PORT_C: -		bit = PORTC_HOTPLUG_LIVE_STATUS; -		break; -	default: -		bit = 0; -		break; -	} - -	return I915_READ(PORT_HOTPLUG_STAT) & bit; -} -  static enum drm_connector_status  intel_hdmi_detect(struct drm_connector *connector, bool force)  { @@ -823,13 +801,6 @@ intel_hdmi_detect(struct drm_connector *connector, bool force)  	struct edid *edid;  	enum drm_connector_status status = connector_status_disconnected; - -	if (IS_G4X(dev) && !g4x_hdmi_connected(intel_hdmi)) -		return status; -	else if (HAS_PCH_SPLIT(dev) && -		 !ibx_digital_port_connected(dev_priv, intel_dig_port)) -		 return status; -  	intel_hdmi->has_hdmi_sink = false;  	intel_hdmi->has_audio = false;  	intel_hdmi->rgb_quant_range_selectable = false; @@ -968,7 +939,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 53bd5fd8402..6ff145f97e9 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_pm.c b/drivers/gpu/drm/i915/intel_pm.c index 44a23b9b8e5..c30e89a4523 100644 --- a/drivers/gpu/drm/i915/intel_pm.c +++ b/drivers/gpu/drm/i915/intel_pm.c @@ -2574,7 +2574,7 @@ static void gen6_enable_rps(struct drm_device *dev)  	I915_WRITE(GEN6_RC_SLEEP, 0);  	I915_WRITE(GEN6_RC1e_THRESHOLD, 1000);  	I915_WRITE(GEN6_RC6_THRESHOLD, 50000); -	I915_WRITE(GEN6_RC6p_THRESHOLD, 100000); +	I915_WRITE(GEN6_RC6p_THRESHOLD, 150000);  	I915_WRITE(GEN6_RC6pp_THRESHOLD, 64000); /* unused */  	/* Check if we are enabling RC6 */ diff --git a/drivers/gpu/drm/i915/intel_sdvo.c b/drivers/gpu/drm/i915/intel_sdvo.c index 38b8511ca59..53005260cd1 100644 --- a/drivers/gpu/drm/i915/intel_sdvo.c +++ b/drivers/gpu/drm/i915/intel_sdvo.c @@ -1079,7 +1079,7 @@ static bool intel_sdvo_mode_fixup(struct drm_encoder *encoder,  		/* FIXME: This bit is only valid when using TMDS encoding and 8  		 * bit per color mode. */  		if (intel_sdvo->has_hdmi_monitor && -		    drm_mode_cea_vic(adjusted_mode) > 1) +		    drm_match_cea_mode(adjusted_mode) > 1)  			intel_sdvo->color_range = HDMI_COLOR_RANGE_16_235;  		else  			intel_sdvo->color_range = 0; @@ -2041,7 +2041,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_sprite.c b/drivers/gpu/drm/i915/intel_sprite.c index d086e48a831..1b6eb76beb7 100644 --- a/drivers/gpu/drm/i915/intel_sprite.c +++ b/drivers/gpu/drm/i915/intel_sprite.c @@ -122,8 +122,8 @@ ivb_update_plane(struct drm_plane *plane, struct drm_framebuffer *fb,  	linear_offset = y * fb->pitches[0] + x * pixel_size;  	sprsurf_offset = -		intel_gen4_compute_offset_xtiled(&x, &y, -						 pixel_size, fb->pitches[0]); +		intel_gen4_compute_page_offset(&x, &y, obj->tiling_mode, +					       pixel_size, fb->pitches[0]);  	linear_offset -= sprsurf_offset;  	/* HSW consolidates SPRTILEOFF and SPRLINOFF into a single SPROFFSET @@ -295,8 +295,8 @@ ilk_update_plane(struct drm_plane *plane, struct drm_framebuffer *fb,  	linear_offset = y * fb->pitches[0] + x * pixel_size;  	dvssurf_offset = -		intel_gen4_compute_offset_xtiled(&x, &y, -						 pixel_size, fb->pitches[0]); +		intel_gen4_compute_page_offset(&x, &y, obj->tiling_mode, +					       pixel_size, fb->pitches[0]);  	linear_offset -= dvssurf_offset;  	if (obj->tiling_mode != I915_TILING_NONE) diff --git a/drivers/gpu/drm/i915/intel_tv.c b/drivers/gpu/drm/i915/intel_tv.c index 984a113c5d1..d808421c1c8 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/Kconfig b/drivers/gpu/drm/mgag200/Kconfig index d63013497f6..b487cdec5ee 100644 --- a/drivers/gpu/drm/mgag200/Kconfig +++ b/drivers/gpu/drm/mgag200/Kconfig @@ -1,6 +1,6 @@  config DRM_MGAG200  	tristate "Kernel modesetting driver for MGA G200 server engines" -	depends on DRM && PCI && EXPERIMENTAL +	depends on DRM && PCI  	select FB_SYS_FILLRECT  	select FB_SYS_COPYAREA  	select FB_SYS_IMAGEBLIT diff --git a/drivers/gpu/drm/mgag200/mgag200_drv.h b/drivers/gpu/drm/mgag200/mgag200_drv.h index 5ea5033eae0..4d932c46725 100644 --- a/drivers/gpu/drm/mgag200/mgag200_drv.h +++ b/drivers/gpu/drm/mgag200/mgag200_drv.h @@ -112,7 +112,6 @@ struct mga_framebuffer {  struct mga_fbdev {  	struct drm_fb_helper helper;  	struct mga_framebuffer mfb; -	struct list_head fbdev_list;  	void *sysram;  	int size;  	struct ttm_bo_kmap_obj mapping; diff --git a/drivers/gpu/drm/mgag200/mgag200_fb.c b/drivers/gpu/drm/mgag200/mgag200_fb.c index 5c69b432f99..d2253f63948 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/mgag200/mgag200_i2c.c b/drivers/gpu/drm/mgag200/mgag200_i2c.c index 5a88ec51b51..d3dcf54e623 100644 --- a/drivers/gpu/drm/mgag200/mgag200_i2c.c +++ b/drivers/gpu/drm/mgag200/mgag200_i2c.c @@ -92,6 +92,7 @@ struct mga_i2c_chan *mgag200_i2c_create(struct drm_device *dev)  	int ret;  	int data, clock; +	WREG_DAC(MGA1064_GEN_IO_CTL2, 1);  	WREG_DAC(MGA1064_GEN_IO_DATA, 0xff);  	WREG_DAC(MGA1064_GEN_IO_CTL, 0); diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c index d3d99a28dde..a274b9906ef 100644 --- a/drivers/gpu/drm/mgag200/mgag200_mode.c +++ b/drivers/gpu/drm/mgag200/mgag200_mode.c @@ -1406,6 +1406,14 @@ static int mga_vga_get_modes(struct drm_connector *connector)  static int mga_vga_mode_valid(struct drm_connector *connector,  				 struct drm_display_mode *mode)  { +	struct drm_device *dev = connector->dev; +	struct mga_device *mdev = (struct mga_device*)dev->dev_private; +	struct mga_fbdev *mfbdev = mdev->mfbdev; +	struct drm_fb_helper *fb_helper = &mfbdev->helper; +	struct drm_fb_helper_connector *fb_helper_conn = NULL; +	int bpp = 32; +	int i = 0; +  	/* FIXME: Add bandwidth and g200se limitations */  	if (mode->crtc_hdisplay > 2048 || mode->crtc_hsync_start > 4096 || @@ -1415,6 +1423,25 @@ static int mga_vga_mode_valid(struct drm_connector *connector,  		return MODE_BAD;  	} +	/* Validate the mode input by the user */ +	for (i = 0; i < fb_helper->connector_count; i++) { +		if (fb_helper->connector_info[i]->connector == connector) { +			/* Found the helper for this connector */ +			fb_helper_conn = fb_helper->connector_info[i]; +			if (fb_helper_conn->cmdline_mode.specified) { +				if (fb_helper_conn->cmdline_mode.bpp_specified) { +					bpp = fb_helper_conn->cmdline_mode.bpp; +				} +			} +		} +	} + +	if ((mode->hdisplay * mode->vdisplay * (bpp/8)) > mdev->mc.vram_size) { +		if (fb_helper_conn) +			fb_helper_conn->cmdline_mode.specified = false; +		return MODE_BAD; +	} +  	return MODE_OK;  } diff --git a/drivers/gpu/drm/nouveau/Kconfig b/drivers/gpu/drm/nouveau/Kconfig index 47ccc1ad540..a7ff6d5a34b 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 ab25752a0b1..90f9140eeef 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 8bbb58f94a1..295c22165ea 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 7cc7133d82d..dd434790ccc 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 00000000000..6d01e0f0fc8 --- /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/core/falcon.c b/drivers/gpu/drm/nouveau/core/core/falcon.c index 6b0843c3387..e05c1577758 100644 --- a/drivers/gpu/drm/nouveau/core/core/falcon.c +++ b/drivers/gpu/drm/nouveau/core/core/falcon.c @@ -73,8 +73,11 @@ _nouveau_falcon_init(struct nouveau_object *object)  	nv_debug(falcon, "data limit: %d\n", falcon->data.limit);  	/* wait for 'uc halted' to be signalled before continuing */ -	if (falcon->secret) { -		nv_wait(falcon, 0x008, 0x00000010, 0x00000010); +	if (falcon->secret && falcon->version < 4) { +		if (!falcon->version) +			nv_wait(falcon, 0x008, 0x00000010, 0x00000010); +		else +			nv_wait(falcon, 0x180, 0x80000000, 0);  		nv_wo32(falcon, 0x004, 0x00000010);  	} diff --git a/drivers/gpu/drm/nouveau/core/core/subdev.c b/drivers/gpu/drm/nouveau/core/core/subdev.c index f74c30aa33a..48f06378d3f 100644 --- a/drivers/gpu/drm/nouveau/core/core/subdev.c +++ b/drivers/gpu/drm/nouveau/core/core/subdev.c @@ -99,7 +99,7 @@ nouveau_subdev_create_(struct nouveau_object *parent,  	if (ret)  		return ret; -	mutex_init(&subdev->mutex); +	__mutex_init(&subdev->mutex, subname, &oclass->lock_class_key);  	subdev->name = subname;  	if (parent) { diff --git a/drivers/gpu/drm/nouveau/core/engine/copy/nva3.c b/drivers/gpu/drm/nouveau/core/engine/copy/nva3.c index 283248c7b05..d6dc2a65ccd 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 b9749051272..5bc021f471f 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 21986f3bf0c..8bf8955051d 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 00000000000..7a5cae42834 --- /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 00000000000..fa27b02ff82 --- /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 00000000000..0e1bbd18ff6 --- /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 1c919f2af89..05e903f08a3 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 ca1a7d76a95..02e369f8044 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, (4 << head), (4 << head)); +} + +static void +nv50_disp_base_vblank_disable(struct nouveau_event *event, int head) +{ +	nv_mask(event->priv, 0x61002c, (4 << head), 0); +} +  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; +		return conf; -	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: +	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; -	/* finish detaching encoder? */ -	head = ffs((super & 0x00000180) >> 7) - 1; -	if (head >= 0) -		exec_script(priv, head, 2); +			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; +			} -	/* 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; -			} +		exec_clkcmp(priv, head, 0, pclk, &outp); -			nv_mask(priv, addr, mask, data); +		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 a6bb931450f..1ae6ceb5670 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 fc84eacdfbe..d8c74c0883a 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 ba9dfd4669a..a66f949c1f8 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 5d63902cded..6cf8eefac36 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 e9192ca389f..b75413169ea 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 9e38ebff5fb..788dd34ccb5 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; -	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); -	} +		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; +			} -	nv_wr32(priv, 0x612200 + (head * 0x800), 0x00000000); +			nouveau_dp_train(&priv->base, priv->sor.dp, +					 &outp, head, pclk); +		} -	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; -					} +		exec_clkcmp(priv, head, 0, pclk, &outp); -					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; - -	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); -	} - -	nv_wr32(priv, 0x6101d4, 0x00000000); -	nv_wr32(priv, 0x6109d4, 0x00000000); -	nv_wr32(priv, 0x6101d0, 0x80000000); +	struct dcb_output outp; +	u32 pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000; +	exec_clkcmp(priv, head, 1, pclk, &outp);  } -static void -nvd0_disp_intr_vblank(struct nv50_disp_priv *priv, int crtc) +void +nvd0_disp_intr_supervisor(struct work_struct *work)  { -	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; +	struct nv50_disp_priv *priv = +		container_of(work, struct nv50_disp_priv, supervisor); +	u32 mask[4]; +	int head; -		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); +	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]); +	} -		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 259537c4587..20725b363d5 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 00000000000..2c8ce351b52 --- /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 39b6b67732d..ab1e918469a 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 f6edd009762..7ec4ee83fb6 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 c37ce7e29f5..9e1d435d728 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 c2b9db33581..7341ebe131f 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 a47a8548f9e..f877bd524a9 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 bd096364f68..840af617278 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 1eb1c512f50..094000e8787 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 b4365dde185..4f226afb559 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 c930da99c2c..4419e40d88e 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 e30a9c5ff1f..ad13dcdd15f 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 5c0f843ea24..23c143aaa55 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 5b20401bf91..0607b980174 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 0b36dd3deeb..17049d5c723 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 b1c3d835b4c..f2b1a7a124f 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 45aff5f5085..0de0dd724af 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 9f82e9702b4..4857f913efd 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;  	} @@ -346,7 +350,7 @@ nve0_graph_init_gpc_0(struct nvc0_graph_priv *priv)  		nv_wr32(priv, GPC_UNIT(gpc, 0x0918), magicgpc918);  	} -	nv_wr32(priv, GPC_BCAST(0x1bd4), magicgpc918); +	nv_wr32(priv, GPC_BCAST(0x3fd4), magicgpc918);  	nv_wr32(priv, GPC_BCAST(0x08ac), nv_rd32(priv, 0x100800));  } diff --git a/drivers/gpu/drm/nouveau/core/engine/mpeg/nv31.c b/drivers/gpu/drm/nouveau/core/engine/mpeg/nv31.c index 9fd86375f4c..49ecbb859b2 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 b0e7e1c01ce..c48e7495377 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 282a1cd1bc2..a523eaad47e 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 47c4b3a5bd3..92d3ab11d96 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 63acc0346ff..c66eac51380 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 e58b6f0984c..d351a4e5819 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 e7b1e181943..4fc62bb8c1f 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 00000000000..9e094408f14 --- /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 5982935ee23..62e68baef08 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/object.h +++ b/drivers/gpu/drm/nouveau/core/include/core/object.h @@ -50,10 +50,13 @@ int  nouveau_object_fini(struct nouveau_object *, bool suspend);  extern struct nouveau_ofuncs nouveau_object_ofuncs; +/* Don't allocate dynamically, because lockdep needs lock_class_keys to be in + * ".data". */  struct nouveau_oclass {  	u32 handle; -	struct nouveau_ofuncs *ofuncs; -	struct nouveau_omthds *omthds; +	struct nouveau_ofuncs * const ofuncs; +	struct nouveau_omthds * const omthds; +	struct lock_class_key lock_class_key;  };  #define nv_oclass(o)    nv_object(o)->oclass @@ -133,7 +136,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 +144,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 +152,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 1d629664f32..febed2ea5c8 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 46948285f3e..28da6772c09 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 f18846c8c6f..b46c197709f 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 c945691c856..45799487e57 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 b79025da581..123270e9813 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 e6563b5cb08..96d3364f6db 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 5079bedfd98..10b57a19a7d 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 a2c4296fc5f..083541dbe9c 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 00000000000..360baab52e4 --- /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 00000000000..7d88ec4a6d0 --- /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 b75e8f18e52..c85b9f1579a 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 b93ab01e378..888384c0bed 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 faee569fd45..6b17b614629 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 c24ec8ab3db..e465d158d35 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 cfe3b9cad15..eb496033b55 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 f621f69fa1a..e816f06637a 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 0fd87df99dd..2d9b9d7a799 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 5afb568b2d6..b2a676e5358 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 c84e93fa6d9..172a4f99999 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 ad577db8376..cfb9288c6d2 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 690ed438b2a..9c41b58d57e 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; @@ -864,7 +869,7 @@ init_idx_addr_latched(struct nvbios_init *init)  		init->offset += 2;  		init_wr32(init, dreg, idata); -		init_mask(init, creg, ~mask, data | idata); +		init_mask(init, creg, ~mask, data | iaddr);  	}  } @@ -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 862a08a2ae2..22a20573ed1 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 00000000000..e9b8e5d30a7 --- /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 00000000000..8c7f8057a18 --- /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 00000000000..34132aef34e --- /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 00000000000..f5b2117fa8c --- /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 00000000000..b192d624636 --- /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 f8a7ed4166c..3937ced5c75 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 8626d0d6cbb..473c5c03d3c 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 9c40b0fb23f..d0774f5bebe 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 74f88f48e1c..ab920e0dc45 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 0ac1b2c4f61..5f2110261b0 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 41d59689a02..f3d55efe9ac 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 6ccfd8585ba..5ed2fa51ddc 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 f0461685a42..4393eb4d656 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 03a652876e7..5c12391619f 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 ae7249b0979..4a857783841 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/base.c b/drivers/gpu/drm/nouveau/core/subdev/fb/base.c index d6d16007ec1..d62045f454b 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/fb/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/fb/base.c @@ -86,8 +86,8 @@ nouveau_fb_preinit(struct nouveau_fb *pfb)  			return ret;  	} -	if (!nouveau_mm_initialised(&pfb->tags) && tags) { -		ret = nouveau_mm_init(&pfb->tags, 0, ++tags, 1); +	if (!nouveau_mm_initialised(&pfb->tags)) { +		ret = nouveau_mm_init(&pfb->tags, 0, tags ? ++tags : 0, 1);  		if (ret)  			return ret;  	} diff --git a/drivers/gpu/drm/nouveau/core/subdev/fb/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/fb/nv50.c index 487cb8c6c20..0772ec97816 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> @@ -99,7 +101,7 @@ nv50_fb_vram_init(struct nouveau_fb *pfb)  	struct nouveau_bios *bios = nouveau_bios(device);  	const u32 rsvd_head = ( 256 * 1024) >> 12; /* vga memory */  	const u32 rsvd_tail = (1024 * 1024) >> 12; /* vbios etc */ -	u32 size; +	u32 size, tags = 0;  	int ret;  	pfb->ram.size = nv_rd32(pfb, 0x10020c); @@ -140,10 +142,11 @@ nv50_fb_vram_init(struct nouveau_fb *pfb)  			return ret;  		pfb->ram.ranks = (nv_rd32(pfb, 0x100200) & 0x4) ? 2 : 1; +		tags = nv_rd32(pfb, 0x100320);  		break;  	} -	return nv_rd32(pfb, 0x100320); +	return tags;  }  static int @@ -302,17 +305,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 +338,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 +372,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 9fb0f9b92d4..d422acc9af1 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 168d16a9a8e..76d5d5465dd 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 bf13a1200f2..bf489dcf46e 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 83e8b8f16e6..010431e3ace 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 00000000000..16b8c5bf5ef --- /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 00000000000..2ee1c895c78 --- /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 00000000000..dec94e9d776 --- /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 dc27e794a85..5de074ad170 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 dbfc2abf0cf..2e98e8a3f1a 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,137 @@   * 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(&parent->refcount); +		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 +176,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 +222,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); +	struct nouveau_i2c *i2c = (void *)object; +	struct nouveau_i2c_port *port; +	int ret; -	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); +	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); +	struct nouveau_i2c *i2c = (void *)object; +	struct nouveau_i2c_port *port, *temp; -	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); +	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 +310,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 1c4c9a5c8e2..a6e72d3b06b 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 00000000000..2ad18840fe6 --- /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 00000000000..f501ae25dbb --- /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 00000000000..378dfa324e5 --- /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 00000000000..4e5ba48ebf5 --- /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 00000000000..61b771670bf --- /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 00000000000..f761b8a610f --- /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 23ebe477a6f..89da8fa7ea0 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 8d759f83032..5965add6dae 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 ceb5c83f945..3a80b29dce0 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 92796682722..42bbf72023a 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 839ca1edc13..4bde7f7f7b8 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 1674c74a76c..f794dc89a3b 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 52317868518..c728380d3d6 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; +	struct nouveau_timer *ptimer = nouveau_timer(priv); +	unsigned long flags; +	int ret = 0; +	int duty; -	if (!priv->fan.pwm_get) -		return -ENODEV; +	/* 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; -		return gpio->get(gpio, 0, func.func, func.line) * 100; +	/* 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 -ENODEV; -} +	nv_debug(therm, "FAN update: %d\n", duty); +	ret = fan->set(therm, duty); +	if (ret) +		goto done; -int -nouveau_therm_fan_set(struct nouveau_therm *therm, int percent) -{ -	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; +	/* 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 (priv->fan.mode == FAN_CONTROL_NONE) -		return -EINVAL; +		if (duty > target) +			delay = slow_down_period; +		else if (duty == target) +			delay = min(bump_period, slow_down_period) ; +		else +			delay = bump_period; -	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; +		ptimer->alarm(ptimer, delay * 1000 * 1000, &fan->alarm); +	} -	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; -		} +done: +	spin_unlock_irqrestore(&fan->lock, flags); +	return ret; +} -		duty = ((divs * percent) + 99) / 100; -		if (card_type <= NV_40 || (func.log[0] & 1)) -			duty = divs - duty; +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); +} -		ret = priv->fan.pwm_set(therm, func.line, divs, duty); -		return ret; -	} +int +nouveau_therm_fan_get(struct nouveau_therm *therm) +{ +	struct nouveau_therm_priv *priv = (void *)therm; +	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 00000000000..b78c182e1d5 --- /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 00000000000..5f71db8e899 --- /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 00000000000..e601773ee47 --- /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 e512ff0aae6..e24090bac19 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 fcf2cfe731d..0f5363edb96 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 9360ddd469e..86632cbd65c 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 00000000000..2dcc5437116 --- /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 00000000000..d7d30ee8332 --- /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 1c3cd6abc36..06b98706b3f 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 204282301fb..b37624af829 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 c26ca9bef67..8e1bae4f12e 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_abi16.c b/drivers/gpu/drm/nouveau/nouveau_abi16.c index 41241922263..3b6dc883e15 100644 --- a/drivers/gpu/drm/nouveau/nouveau_abi16.c +++ b/drivers/gpu/drm/nouveau/nouveau_abi16.c @@ -116,6 +116,11 @@ nouveau_abi16_chan_fini(struct nouveau_abi16 *abi16,  {  	struct nouveau_abi16_ntfy *ntfy, *temp; +	/* wait for all activity to stop before releasing notify object, which +	 * may be still in use */ +	if (chan->chan && chan->ntfy) +		nouveau_channel_idle(chan->chan); +  	/* cleanup notifier state */  	list_for_each_entry_safe(ntfy, temp, &chan->notifiers, head) {  		nouveau_abi16_ntfy_fini(chan, ntfy); diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.h b/drivers/gpu/drm/nouveau/nouveau_acpi.h index d0da230d770..74acf0f8778 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_agp.c b/drivers/gpu/drm/nouveau/nouveau_agp.c index d28430cd2ba..6e7a55f93a8 100644 --- a/drivers/gpu/drm/nouveau/nouveau_agp.c +++ b/drivers/gpu/drm/nouveau/nouveau_agp.c @@ -47,6 +47,18 @@ nouveau_agp_enabled(struct nouveau_drm *drm)  	if (drm->agp.stat == UNKNOWN) {  		if (!nouveau_agpmode)  			return false; +#ifdef __powerpc__ +		/* Disable AGP by default on all PowerPC machines for +		 * now -- At least some UniNorth-2 AGP bridges are +		 * known to be broken: DMA from the host to the card +		 * works just fine, but writeback from the card to the +		 * host goes straight to memory untranslated bypassing +		 * the GATT somehow, making them quite painful to deal +		 * with... +		 */ +		if (nouveau_agpmode == -1) +			return false; +#endif  		return true;  	} diff --git a/drivers/gpu/drm/nouveau/nouveau_backlight.c b/drivers/gpu/drm/nouveau/nouveau_backlight.c index f65b20a375f..5d940302d2a 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 865eddfa30a..50a6dd02f7c 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; +	struct nouveau_bios *bios = nouveau_bios(drm->device); +	struct nvbios *legacy = &drm->vbios; -	memset(bios, 0, sizeof(struct nvbios)); -	spin_lock_init(&bios->lock); -	bios->dev = dev; +	memset(legacy, 0, sizeof(struct nvbios)); +	spin_lock_init(&legacy->lock); +	legacy->dev = dev; -	bios->data = nouveau_bios(drm->device)->data; -	bios->length = nouveau_bios(drm->device)->size; -	return true; -} - -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); +	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);  	} -	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 f68c54ca422..7ccd28f11ad 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 64d6e3047de..7ff10711a4d 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; @@ -800,7 +801,7 @@ nv50_bo_move_m2mf(struct nouveau_channel *chan, struct ttm_buffer_object *bo,  		stride  = 16 * 4;  		height  = amount / stride; -		if (new_mem->mem_type == TTM_PL_VRAM && +		if (old_mem->mem_type == TTM_PL_VRAM &&  		    nouveau_bo_tile_layout(nvbo)) {  			ret = RING_SPACE(chan, 8);  			if (ret) @@ -822,7 +823,7 @@ nv50_bo_move_m2mf(struct nouveau_channel *chan, struct ttm_buffer_object *bo,  			BEGIN_NV04(chan, NvSubCopy, 0x0200, 1);  			OUT_RING  (chan, 1);  		} -		if (old_mem->mem_type == TTM_PL_VRAM && +		if (new_mem->mem_type == TTM_PL_VRAM &&  		    nouveau_bo_tile_layout(nvbo)) {  			ret = RING_SPACE(chan, 8);  			if (ret) diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.c b/drivers/gpu/drm/nouveau/nouveau_chan.c index 174300b6a02..eaa80a2b81e 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 e620ba8271b..4dd7ae2ac6c 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 20eb84cce9e..6e399aad491 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 00000000000..5392e07edfc --- /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 00000000000..a62af6fb5f9 --- /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 d42c9e860c1..4610c3a29bb 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 722548bb3bd..1ea3e4734b6 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 5c2e22932d1..690d5930ce3 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 59838651ee8..36fd2250056 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 8e8e8ce7552..d1099365bfc 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); @@ -245,6 +275,8 @@ static int nouveau_drm_probe(struct pci_dev *pdev,  	return 0;  } +static struct lock_class_key drm_client_lock_class_key; +  static int  nouveau_drm_load(struct drm_device *dev, unsigned long flags)  { @@ -256,9 +288,11 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags)  	ret = nouveau_cli_create(pdev, "DRM", sizeof(*drm), (void**)&drm);  	if (ret)  		return ret; +	lockdep_set_class(&drm->client.mutex, &drm_client_lock_class_key);  	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 +432,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 +503,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 +577,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 +671,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 aa89eb938b4..b25df374c90 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 d0d95bd511a..e24341229d5 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 d4ecb4deb48..b0353178158 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 1d049be79f7..6c946837a0a 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 cdb83acdffe..c89943407b5 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 d98bee012ca..b4b4d0c1f4a 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 a701ff5ffa5..bb54098c6d9 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 39ffc07f906..7e24cdf1cb3 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 4c6e9f83fe8..ad48444c385 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 45322802e37..a0a031dad13 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 a220b94ba9f..94eadd1dd10 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 62e826a139b..4a69ccdef9b 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 03017f24d59..06f434f03fb 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 00000000000..e5d9204826c --- /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 00000000000..8e47a9bae8c --- /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 d4cbea19b89..2db57990f65 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 @@ -54,9 +55,9 @@  /* offsets in shared sync bo of various structures */  #define EVO_SYNC(c, o) ((c) * 0x0100 + (o)) -#define EVO_MAST_NTFY     EVO_SYNC(  0, 0x00) -#define EVO_FLIP_SEM0(c)  EVO_SYNC((c), 0x00) -#define EVO_FLIP_SEM1(c)  EVO_SYNC((c), 0x10) +#define EVO_MAST_NTFY     EVO_SYNC(      0, 0x00) +#define EVO_FLIP_SEM0(c)  EVO_SYNC((c) + 1, 0x00) +#define EVO_FLIP_SEM1(c)  EVO_SYNC((c) + 1, 0x10)  #define EVO_CORE_HANDLE      (0xd1500000)  #define EVO_CHAN_HANDLE(t,i) (0xd15c0000 | (((t) & 0x00ff) << 8) | (i)) @@ -340,10 +341,8 @@ struct nv50_curs {  struct nv50_sync {  	struct nv50_dmac base; -	struct { -		u32 offset; -		u16 value; -	} sem; +	u32 addr; +	u32 data;  };  struct nv50_ovly { @@ -433,7 +432,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 @@ -467,13 +469,33 @@ nv50_display_crtc_sema(struct drm_device *dev, int crtc)  	return nv50_disp(dev)->sync;  } +struct nv50_display_flip { +	struct nv50_disp *disp; +	struct nv50_sync *chan; +}; + +static bool +nv50_display_flip_wait(void *data) +{ +	struct nv50_display_flip *flip = data; +	if (nouveau_bo_rd32(flip->disp->sync, flip->chan->addr / 4) == +					      flip->chan->data); +		return true; +	usleep_range(1, 2); +	return false; +} +  void  nv50_display_flip_stop(struct drm_crtc *crtc)  { -	struct nv50_sync *sync = nv50_sync(crtc); +	struct nouveau_device *device = nouveau_dev(crtc->dev); +	struct nv50_display_flip flip = { +		.disp = nv50_disp(crtc->dev), +		.chan = nv50_sync(crtc), +	};  	u32 *push; -	push = evo_wait(sync, 8); +	push = evo_wait(flip.chan, 8);  	if (push) {  		evo_mthd(push, 0x0084, 1);  		evo_data(push, 0x00000000); @@ -483,8 +505,10 @@ nv50_display_flip_stop(struct drm_crtc *crtc)  		evo_data(push, 0x00000000);  		evo_mthd(push, 0x0080, 1);  		evo_data(push, 0x00000000); -		evo_kick(push, sync); +		evo_kick(push, flip.chan);  	} + +	nv_wait_cb(device, nv50_display_flip_wait, &flip);  }  int @@ -492,11 +516,10 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb,  		       struct nouveau_channel *chan, u32 swap_interval)  {  	struct nouveau_framebuffer *nv_fb = nouveau_framebuffer(fb); -	struct nv50_disp *disp = nv50_disp(crtc->dev);  	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);  	struct nv50_sync *sync = nv50_sync(crtc); +	int head = nv_crtc->index, ret;  	u32 *push; -	int ret;  	swap_interval <<= 4;  	if (swap_interval == 0) @@ -506,46 +529,64 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb,  	if (unlikely(push == NULL))  		return -EBUSY; -	/* synchronise with the rendering channel, if necessary */ -	if (likely(chan)) { -		ret = RING_SPACE(chan, 10); +	if (chan && nv_mclass(chan->object) < NV84_CHANNEL_IND_CLASS) { +		ret = RING_SPACE(chan, 8);  		if (ret)  			return ret; -		if (nv_mclass(chan->object) < NVC0_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); -			BEGIN_NV04(chan, 0, NV11_SUBCHAN_SEMAPHORE_RELEASE, 1); -			OUT_RING  (chan, 0xf00d0000 | sync->sem.value); -			BEGIN_NV04(chan, 0, NV11_SUBCHAN_SEMAPHORE_OFFSET, 2); -			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); -		} else { -			u64 offset = nvc0_fence_crtc(chan, nv_crtc->index); -			offset += sync->sem.offset; +		BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 2); +		OUT_RING  (chan, NvEvoSema0 + head); +		OUT_RING  (chan, sync->addr ^ 0x10); +		BEGIN_NV04(chan, 0, NV11_SUBCHAN_SEMAPHORE_RELEASE, 1); +		OUT_RING  (chan, sync->data + 1); +		BEGIN_NV04(chan, 0, NV11_SUBCHAN_SEMAPHORE_OFFSET, 2); +		OUT_RING  (chan, sync->addr); +		OUT_RING  (chan, sync->data); +	} else +	if (chan && nv_mclass(chan->object) < NVC0_CHANNEL_IND_CLASS) { +		u64 addr = nv84_fence_crtc(chan, head) + sync->addr; +		ret = RING_SPACE(chan, 12); +		if (ret) +			return ret; -			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); -			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); -		} +		BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1); +		OUT_RING  (chan, chan->vram); +		BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); +		OUT_RING  (chan, upper_32_bits(addr ^ 0x10)); +		OUT_RING  (chan, lower_32_bits(addr ^ 0x10)); +		OUT_RING  (chan, sync->data + 1); +		OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG); +		BEGIN_NV04(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, sync->data); +		OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_EQUAL); +	} else +	if (chan) { +		u64 addr = nv84_fence_crtc(chan, head) + sync->addr; +		ret = RING_SPACE(chan, 10); +		if (ret) +			return ret; +		BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); +		OUT_RING  (chan, upper_32_bits(addr ^ 0x10)); +		OUT_RING  (chan, lower_32_bits(addr ^ 0x10)); +		OUT_RING  (chan, sync->data + 1); +		OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG | +				 NVC0_SUBCHAN_SEMAPHORE_TRIGGER_YIELD); +		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, sync->data); +		OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_EQUAL | +				 NVC0_SUBCHAN_SEMAPHORE_TRIGGER_YIELD); +	} + +	if (chan) { +		sync->addr ^= 0x10; +		sync->data++;  		FIRE_RING (chan);  	} else { -		nouveau_bo_wr32(disp->sync, sync->sem.offset / 4, -				0xf00d0000 | sync->sem.value);  		evo_sync(crtc->dev);  	} @@ -559,9 +600,9 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb,  		evo_data(push, 0x40000000);  	}  	evo_mthd(push, 0x0088, 4); -	evo_data(push, sync->sem.offset); -	evo_data(push, 0xf00d0000 | sync->sem.value); -	evo_data(push, 0x74b1e000); +	evo_data(push, sync->addr); +	evo_data(push, sync->data++); +	evo_data(push, sync->data);  	evo_data(push, NvEvoSync);  	evo_mthd(push, 0x00a0, 2);  	evo_data(push, 0x00000000); @@ -589,9 +630,6 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb,  	evo_mthd(push, 0x0080, 1);  	evo_data(push, 0x00000000);  	evo_kick(push, sync); - -	sync->sem.offset ^= 0x10; -	sync->sem.value++;  	return 0;  } @@ -1363,7 +1401,8 @@ nv50_crtc_create(struct drm_device *dev, struct nouveau_object *core, int index)  	if (ret)  		goto out; -	head->sync.sem.offset = EVO_SYNC(1 + index, 0x00); +	head->sync.addr = EVO_FLIP_SEM0(index); +	head->sync.data = 0x00000000;  	/* allocate overlay resources */  	ret = nv50_pioc_create(disp->core, NV50_DISP_OIMM_CLASS, index, @@ -1503,9 +1542,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 +1588,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 +1713,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 +1755,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 +1766,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 +1860,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 +1902,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 +1916,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 +1950,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 @@ -1918,15 +2135,23 @@ nv50_display_fini(struct drm_device *dev)  int  nv50_display_init(struct drm_device *dev)  { -	u32 *push = evo_wait(nv50_mast(dev), 32); -	if (push) { -		evo_mthd(push, 0x0088, 1); -		evo_data(push, NvEvoSync); -		evo_kick(push, nv50_mast(dev)); -		return evo_sync(dev); +	struct nv50_disp *disp = nv50_disp(dev); +	struct drm_crtc *crtc; +	u32 *push; + +	push = evo_wait(nv50_mast(dev), 32); +	if (!push) +		return -EBUSY; + +	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { +		struct nv50_sync *sync = nv50_sync(crtc); +		nouveau_bo_wr32(disp->sync, sync->addr / 4, sync->data);  	} -	return -EBUSY; +	evo_mthd(push, 0x0088, 1); +	evo_data(push, NvEvoSync); +	evo_kick(push, nv50_mast(dev)); +	return 0;  }  void @@ -2029,25 +2254,29 @@ 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); +			ret = 0;  		}  	} diff --git a/drivers/gpu/drm/nouveau/nv50_fence.c b/drivers/gpu/drm/nouveau/nv50_fence.c index d889f3ac0d4..f9701e567db 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 c686650584b..9fd475c8982 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 2a56b1b551c..9566267fbc4 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 00000000000..09f65dc3d2c --- /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 +	depends on OMAP2_DSS +	select DRM_KMS_HELPER +	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 00000000000..d85e058f284 --- /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 00000000000..4d8c18aa5dd --- /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 00000000000..c451c41a7a7 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_connector.c @@ -0,0 +1,296 @@ +/* + * 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) +		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 00000000000..bec66a490b8 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_crtc.c @@ -0,0 +1,654 @@ +/* + * 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) +		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 00000000000..c27f59da7f2 --- /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 00000000000..58bcd6ae025 --- /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 00000000000..9b794c933c8 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_dmm_tiler.c @@ -0,0 +1,986 @@ +/* + * 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) +		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 = kcalloc(omap_dmm->num_engines, +				    sizeof(struct refill_engine), GFP_KERNEL); +	if (!omap_dmm->engines) { +		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 = kcalloc(omap_dmm->num_lut, sizeof(*omap_dmm->tcm), +				GFP_KERNEL); +	if (!omap_dmm->tcm) { +		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 00000000000..4fdd61e54bd --- /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 00000000000..079c54c6f94 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_drv.c @@ -0,0 +1,608 @@ +/* + * 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) +		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 00000000000..d4f997bb4ac --- /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 00000000000..21d126d0317 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_encoder.c @@ -0,0 +1,168 @@ +/* + * 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) +		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 00000000000..8031402e795 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_fb.c @@ -0,0 +1,471 @@ +/* + * 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) { +		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 00000000000..b11ce609fcc --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_fbdev.c @@ -0,0 +1,397 @@ +/* + * 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) +		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 00000000000..ebbdf4132e9 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_gem.c @@ -0,0 +1,1507 @@ +/* + * 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) +		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 = kcalloc(3, sizeof(*usergart), GFP_KERNEL); +	if (!usergart) +		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 00000000000..ac74d1bc67b --- /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 00000000000..f9eb679eb79 --- /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 = file_inode(obj->filp); +	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 00000000000..e01303ee00c --- /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 00000000000..2882cda6ea1 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_plane.c @@ -0,0 +1,448 @@ +/* + * 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) +		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 00000000000..efb60951054 --- /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 00000000000..0444f868671 --- /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 00000000000..a8d5ce47686 --- /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 5ce9bf51a8d..46a9c377285 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 2916de896a6..305a657bf21 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; @@ -2425,6 +2438,12 @@ static u32 evergreen_gpu_check_soft_reset(struct radeon_device *rdev)  	if (tmp & L2_BUSY)  		reset_mask |= RADEON_RESET_VMC; +	/* Skip MC reset as it's mostly likely not hung, just busy */ +	if (reset_mask & RADEON_RESET_MC) { +		DRM_DEBUG("MC busy: 0x%08X, clearing.\n", reset_mask); +		reset_mask &= ~RADEON_RESET_MC; +	} +  	return reset_mask;  } diff --git a/drivers/gpu/drm/radeon/evergreen_cs.c b/drivers/gpu/drm/radeon/evergreen_cs.c index d8f5d5fcd30..eb8ac315f92 100644 --- a/drivers/gpu/drm/radeon/evergreen_cs.c +++ b/drivers/gpu/drm/radeon/evergreen_cs.c @@ -834,7 +834,7 @@ static int evergreen_cs_track_validate_texture(struct radeon_cs_parser *p,  			 __func__, __LINE__, toffset, surf.base_align);  		return -EINVAL;  	} -	if (moffset & (surf.base_align - 1)) { +	if (surf.nsamples <= 1 && moffset & (surf.base_align - 1)) {  		dev_warn(p->dev, "%s:%d mipmap bo base %ld not aligned with %ld\n",  			 __func__, __LINE__, moffset, surf.base_align);  		return -EINVAL; @@ -2711,7 +2711,7 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  			switch (sub_cmd) {  			/* tiled */  			case 8: -				dst_offset = ib[idx+1]; +				dst_offset = radeon_get_ib_value(p, idx+1);  				dst_offset <<= 8;  				ib[idx+1] += (u32)(dst_reloc->lobj.gpu_offset >> 8); @@ -2719,15 +2719,15 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  				break;  			/* linear */  			case 0: -				dst_offset = ib[idx+1]; -				dst_offset |= ((u64)(ib[idx+2] & 0xff)) << 32; +				dst_offset = radeon_get_ib_value(p, idx+1); +				dst_offset |= ((u64)(radeon_get_ib_value(p, idx+2) & 0xff)) << 32;  				ib[idx+1] += (u32)(dst_reloc->lobj.gpu_offset & 0xfffffffc);  				ib[idx+2] += upper_32_bits(dst_reloc->lobj.gpu_offset) & 0xff;  				p->idx += count + 3;  				break;  			default: -				DRM_ERROR("bad DMA_PACKET_WRITE [%6d] 0x%08x sub cmd is not 0 or 8\n", idx, ib[idx+0]); +				DRM_ERROR("bad DMA_PACKET_WRITE [%6d] 0x%08x sub cmd is not 0 or 8\n", idx, header);  				return -EINVAL;  			}  			if ((dst_offset + (count * 4)) > radeon_bo_size(dst_reloc->robj)) { @@ -2751,10 +2751,10 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  			/* Copy L2L, DW aligned */  			case 0x00:  				/* L2L, dw */ -				src_offset = ib[idx+2]; -				src_offset |= ((u64)(ib[idx+4] & 0xff)) << 32; -				dst_offset = ib[idx+1]; -				dst_offset |= ((u64)(ib[idx+3] & 0xff)) << 32; +				src_offset = radeon_get_ib_value(p, idx+2); +				src_offset |= ((u64)(radeon_get_ib_value(p, idx+4) & 0xff)) << 32; +				dst_offset = radeon_get_ib_value(p, idx+1); +				dst_offset |= ((u64)(radeon_get_ib_value(p, idx+3) & 0xff)) << 32;  				if ((src_offset + (count * 4)) > radeon_bo_size(src_reloc->robj)) {  					dev_warn(p->dev, "DMA L2L, dw src buffer too small (%llu %lu)\n",  							src_offset + (count * 4), radeon_bo_size(src_reloc->robj)); @@ -2774,24 +2774,24 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  			/* Copy L2T/T2L */  			case 0x08:  				/* detile bit */ -				if (ib[idx + 2] & (1 << 31)) { +				if (radeon_get_ib_value(p, idx + 2) & (1 << 31)) {  					/* tiled src, linear dst */ -					src_offset = ib[idx+1]; +					src_offset = radeon_get_ib_value(p, idx+1);  					src_offset <<= 8;  					ib[idx+1] += (u32)(src_reloc->lobj.gpu_offset >> 8);  					dst_offset = radeon_get_ib_value(p, idx + 7); -					dst_offset |= ((u64)(ib[idx+8] & 0xff)) << 32; +					dst_offset |= ((u64)(radeon_get_ib_value(p, idx+8) & 0xff)) << 32;  					ib[idx+7] += (u32)(dst_reloc->lobj.gpu_offset & 0xfffffffc);  					ib[idx+8] += upper_32_bits(dst_reloc->lobj.gpu_offset) & 0xff;  				} else {  					/* linear src, tiled dst */ -					src_offset = ib[idx+7]; -					src_offset |= ((u64)(ib[idx+8] & 0xff)) << 32; +					src_offset = radeon_get_ib_value(p, idx+7); +					src_offset |= ((u64)(radeon_get_ib_value(p, idx+8) & 0xff)) << 32;  					ib[idx+7] += (u32)(src_reloc->lobj.gpu_offset & 0xfffffffc);  					ib[idx+8] += upper_32_bits(src_reloc->lobj.gpu_offset) & 0xff; -					dst_offset = ib[idx+1]; +					dst_offset = radeon_get_ib_value(p, idx+1);  					dst_offset <<= 8;  					ib[idx+1] += (u32)(dst_reloc->lobj.gpu_offset >> 8);  				} @@ -2810,10 +2810,10 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  			/* Copy L2L, byte aligned */  			case 0x40:  				/* L2L, byte */ -				src_offset = ib[idx+2]; -				src_offset |= ((u64)(ib[idx+4] & 0xff)) << 32; -				dst_offset = ib[idx+1]; -				dst_offset |= ((u64)(ib[idx+3] & 0xff)) << 32; +				src_offset = radeon_get_ib_value(p, idx+2); +				src_offset |= ((u64)(radeon_get_ib_value(p, idx+4) & 0xff)) << 32; +				dst_offset = radeon_get_ib_value(p, idx+1); +				dst_offset |= ((u64)(radeon_get_ib_value(p, idx+3) & 0xff)) << 32;  				if ((src_offset + count) > radeon_bo_size(src_reloc->robj)) {  					dev_warn(p->dev, "DMA L2L, byte src buffer too small (%llu %lu)\n",  							src_offset + count, radeon_bo_size(src_reloc->robj)); @@ -2852,12 +2852,12 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  					DRM_ERROR("bad L2L, dw, broadcast DMA_PACKET_COPY\n");  					return -EINVAL;  				} -				dst_offset = ib[idx+1]; -				dst_offset |= ((u64)(ib[idx+4] & 0xff)) << 32; -				dst2_offset = ib[idx+2]; -				dst2_offset |= ((u64)(ib[idx+5] & 0xff)) << 32; -				src_offset = ib[idx+3]; -				src_offset |= ((u64)(ib[idx+6] & 0xff)) << 32; +				dst_offset = radeon_get_ib_value(p, idx+1); +				dst_offset |= ((u64)(radeon_get_ib_value(p, idx+4) & 0xff)) << 32; +				dst2_offset = radeon_get_ib_value(p, idx+2); +				dst2_offset |= ((u64)(radeon_get_ib_value(p, idx+5) & 0xff)) << 32; +				src_offset = radeon_get_ib_value(p, idx+3); +				src_offset |= ((u64)(radeon_get_ib_value(p, idx+6) & 0xff)) << 32;  				if ((src_offset + (count * 4)) > radeon_bo_size(src_reloc->robj)) {  					dev_warn(p->dev, "DMA L2L, dw, broadcast src buffer too small (%llu %lu)\n",  							src_offset + (count * 4), radeon_bo_size(src_reloc->robj)); @@ -2883,7 +2883,7 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  				break;  			/* Copy L2T Frame to Field */  			case 0x48: -				if (ib[idx + 2] & (1 << 31)) { +				if (radeon_get_ib_value(p, idx + 2) & (1 << 31)) {  					DRM_ERROR("bad L2T, frame to fields DMA_PACKET_COPY\n");  					return -EINVAL;  				} @@ -2892,12 +2892,12 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  					DRM_ERROR("bad L2T, frame to fields DMA_PACKET_COPY\n");  					return -EINVAL;  				} -				dst_offset = ib[idx+1]; +				dst_offset = radeon_get_ib_value(p, idx+1);  				dst_offset <<= 8; -				dst2_offset = ib[idx+2]; +				dst2_offset = radeon_get_ib_value(p, idx+2);  				dst2_offset <<= 8; -				src_offset = ib[idx+8]; -				src_offset |= ((u64)(ib[idx+9] & 0xff)) << 32; +				src_offset = radeon_get_ib_value(p, idx+8); +				src_offset |= ((u64)(radeon_get_ib_value(p, idx+9) & 0xff)) << 32;  				if ((src_offset + (count * 4)) > radeon_bo_size(src_reloc->robj)) {  					dev_warn(p->dev, "DMA L2T, frame to fields src buffer too small (%llu %lu)\n",  							src_offset + (count * 4), radeon_bo_size(src_reloc->robj)); @@ -2927,7 +2927,7 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  					return -EINVAL;  				}  				/* detile bit */ -				if (ib[idx + 2 ] & (1 << 31)) { +				if (radeon_get_ib_value(p, idx + 2) & (1 << 31)) {  					/* tiled src, linear dst */  					ib[idx+1] += (u32)(src_reloc->lobj.gpu_offset >> 8); @@ -2945,7 +2945,7 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  			/* Copy L2T broadcast */  			case 0x4b:  				/* L2T, broadcast */ -				if (ib[idx + 2] & (1 << 31)) { +				if (radeon_get_ib_value(p, idx + 2) & (1 << 31)) {  					DRM_ERROR("bad L2T, broadcast DMA_PACKET_COPY\n");  					return -EINVAL;  				} @@ -2954,12 +2954,12 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  					DRM_ERROR("bad L2T, broadcast DMA_PACKET_COPY\n");  					return -EINVAL;  				} -				dst_offset = ib[idx+1]; +				dst_offset = radeon_get_ib_value(p, idx+1);  				dst_offset <<= 8; -				dst2_offset = ib[idx+2]; +				dst2_offset = radeon_get_ib_value(p, idx+2);  				dst2_offset <<= 8; -				src_offset = ib[idx+8]; -				src_offset |= ((u64)(ib[idx+9] & 0xff)) << 32; +				src_offset = radeon_get_ib_value(p, idx+8); +				src_offset |= ((u64)(radeon_get_ib_value(p, idx+9) & 0xff)) << 32;  				if ((src_offset + (count * 4)) > radeon_bo_size(src_reloc->robj)) {  					dev_warn(p->dev, "DMA L2T, broadcast src buffer too small (%llu %lu)\n",  							src_offset + (count * 4), radeon_bo_size(src_reloc->robj)); @@ -2985,24 +2985,24 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  			case 0x4c:  				/* L2T, T2L */  				/* detile bit */ -				if (ib[idx + 2] & (1 << 31)) { +				if (radeon_get_ib_value(p, idx + 2) & (1 << 31)) {  					/* tiled src, linear dst */ -					src_offset = ib[idx+1]; +					src_offset = radeon_get_ib_value(p, idx+1);  					src_offset <<= 8;  					ib[idx+1] += (u32)(src_reloc->lobj.gpu_offset >> 8); -					dst_offset = ib[idx+7]; -					dst_offset |= ((u64)(ib[idx+8] & 0xff)) << 32; +					dst_offset = radeon_get_ib_value(p, idx+7); +					dst_offset |= ((u64)(radeon_get_ib_value(p, idx+8) & 0xff)) << 32;  					ib[idx+7] += (u32)(dst_reloc->lobj.gpu_offset & 0xfffffffc);  					ib[idx+8] += upper_32_bits(dst_reloc->lobj.gpu_offset) & 0xff;  				} else {  					/* linear src, tiled dst */ -					src_offset = ib[idx+7]; -					src_offset |= ((u64)(ib[idx+8] & 0xff)) << 32; +					src_offset = radeon_get_ib_value(p, idx+7); +					src_offset |= ((u64)(radeon_get_ib_value(p, idx+8) & 0xff)) << 32;  					ib[idx+7] += (u32)(src_reloc->lobj.gpu_offset & 0xfffffffc);  					ib[idx+8] += upper_32_bits(src_reloc->lobj.gpu_offset) & 0xff; -					dst_offset = ib[idx+1]; +					dst_offset = radeon_get_ib_value(p, idx+1);  					dst_offset <<= 8;  					ib[idx+1] += (u32)(dst_reloc->lobj.gpu_offset >> 8);  				} @@ -3032,7 +3032,7 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  			/* Copy L2T broadcast (tile units) */  			case 0x4f:  				/* L2T, broadcast */ -				if (ib[idx + 2] & (1 << 31)) { +				if (radeon_get_ib_value(p, idx + 2) & (1 << 31)) {  					DRM_ERROR("bad L2T, broadcast DMA_PACKET_COPY\n");  					return -EINVAL;  				} @@ -3041,12 +3041,12 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  					DRM_ERROR("bad L2T, broadcast DMA_PACKET_COPY\n");  					return -EINVAL;  				} -				dst_offset = ib[idx+1]; +				dst_offset = radeon_get_ib_value(p, idx+1);  				dst_offset <<= 8; -				dst2_offset = ib[idx+2]; +				dst2_offset = radeon_get_ib_value(p, idx+2);  				dst2_offset <<= 8; -				src_offset = ib[idx+8]; -				src_offset |= ((u64)(ib[idx+9] & 0xff)) << 32; +				src_offset = radeon_get_ib_value(p, idx+8); +				src_offset |= ((u64)(radeon_get_ib_value(p, idx+9) & 0xff)) << 32;  				if ((src_offset + (count * 4)) > radeon_bo_size(src_reloc->robj)) {  					dev_warn(p->dev, "DMA L2T, broadcast src buffer too small (%llu %lu)\n",  							src_offset + (count * 4), radeon_bo_size(src_reloc->robj)); @@ -3069,7 +3069,7 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  				p->idx += 10;  				break;  			default: -				DRM_ERROR("bad DMA_PACKET_COPY [%6d] 0x%08x invalid sub cmd\n", idx, ib[idx+0]); +				DRM_ERROR("bad DMA_PACKET_COPY [%6d] 0x%08x invalid sub cmd\n", idx, header);  				return -EINVAL;  			}  			break; @@ -3079,8 +3079,8 @@ int evergreen_dma_cs_parse(struct radeon_cs_parser *p)  				DRM_ERROR("bad DMA_PACKET_CONSTANT_FILL\n");  				return -EINVAL;  			} -			dst_offset = ib[idx+1]; -			dst_offset |= ((u64)(ib[idx+3] & 0x00ff0000)) << 16; +			dst_offset = radeon_get_ib_value(p, idx+1); +			dst_offset |= ((u64)(radeon_get_ib_value(p, idx+3) & 0x00ff0000)) << 16;  			if ((dst_offset + (count * 4)) > radeon_bo_size(dst_reloc->robj)) {  				dev_warn(p->dev, "DMA constant fill buffer too small (%llu %lu)\n",  					 dst_offset, radeon_bo_size(dst_reloc->robj)); diff --git a/drivers/gpu/drm/radeon/evergreen_hdmi.c b/drivers/gpu/drm/radeon/evergreen_hdmi.c index 327c08b5418..4fdecc2b404 100644 --- a/drivers/gpu/drm/radeon/evergreen_hdmi.c +++ b/drivers/gpu/drm/radeon/evergreen_hdmi.c @@ -24,6 +24,7 @@   * Authors: Christian König   *          Rafał Miłecki   */ +#include <linux/hdmi.h>  #include <drm/drmP.h>  #include <drm/radeon_drm.h>  #include "radeon.h" @@ -54,79 +55,18 @@ static void evergreen_hdmi_update_ACR(struct drm_encoder *encoder, uint32_t cloc  }  /* - * calculate the crc for a given info frame - */ -static void evergreen_hdmi_infoframe_checksum(uint8_t packetType, -					 uint8_t versionNumber, -					 uint8_t length, -					 uint8_t *frame) -{ -	int i; -	frame[0] = packetType + versionNumber + length; -	for (i = 1; i <= length; i++) -		frame[0] += frame[i]; -	frame[0] = 0x100 - frame[0]; -} - -/*   * build a HDMI Video Info Frame   */ -static void evergreen_hdmi_videoinfoframe( -	struct drm_encoder *encoder, -	uint8_t color_format, -	int active_information_present, -	uint8_t active_format_aspect_ratio, -	uint8_t scan_information, -	uint8_t colorimetry, -	uint8_t ex_colorimetry, -	uint8_t quantization, -	int ITC, -	uint8_t picture_aspect_ratio, -	uint8_t video_format_identification, -	uint8_t pixel_repetition, -	uint8_t non_uniform_picture_scaling, -	uint8_t bar_info_data_valid, -	uint16_t top_bar, -	uint16_t bottom_bar, -	uint16_t left_bar, -	uint16_t right_bar -) +static void evergreen_hdmi_update_avi_infoframe(struct drm_encoder *encoder, +						void *buffer, size_t size)  {  	struct drm_device *dev = encoder->dev;  	struct radeon_device *rdev = dev->dev_private;  	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);  	struct radeon_encoder_atom_dig *dig = radeon_encoder->enc_priv;  	uint32_t offset = dig->afmt->offset; +	uint8_t *frame = buffer + 3; -	uint8_t frame[14]; - -	frame[0x0] = 0; -	frame[0x1] = -		(scan_information & 0x3) | -		((bar_info_data_valid & 0x3) << 2) | -		((active_information_present & 0x1) << 4) | -		((color_format & 0x3) << 5); -	frame[0x2] = -		(active_format_aspect_ratio & 0xF) | -		((picture_aspect_ratio & 0x3) << 4) | -		((colorimetry & 0x3) << 6); -	frame[0x3] = -		(non_uniform_picture_scaling & 0x3) | -		((quantization & 0x3) << 2) | -		((ex_colorimetry & 0x7) << 4) | -		((ITC & 0x1) << 7); -	frame[0x4] = (video_format_identification & 0x7F); -	frame[0x5] = (pixel_repetition & 0xF); -	frame[0x6] = (top_bar & 0xFF); -	frame[0x7] = (top_bar >> 8); -	frame[0x8] = (bottom_bar & 0xFF); -	frame[0x9] = (bottom_bar >> 8); -	frame[0xA] = (left_bar & 0xFF); -	frame[0xB] = (left_bar >> 8); -	frame[0xC] = (right_bar & 0xFF); -	frame[0xD] = (right_bar >> 8); - -	evergreen_hdmi_infoframe_checksum(0x82, 0x02, 0x0D, frame);  	/* Our header values (type, version, length) should be alright, Intel  	 * is using the same. Checksum function also seems to be OK, it works  	 * fine for audio infoframe. However calculated value is always lower @@ -154,7 +94,10 @@ void evergreen_hdmi_setmode(struct drm_encoder *encoder, struct drm_display_mode  	struct radeon_device *rdev = dev->dev_private;  	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);  	struct radeon_encoder_atom_dig *dig = radeon_encoder->enc_priv; +	u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE]; +	struct hdmi_avi_infoframe frame;  	uint32_t offset; +	ssize_t err;  	/* Silent, r600_hdmi_enable will raise WARN for us */  	if (!dig->afmt->enabled) @@ -200,9 +143,19 @@ void evergreen_hdmi_setmode(struct drm_encoder *encoder, struct drm_display_mode  	WREG32(HDMI_GC + offset, 0); /* unset HDMI_GC_AVMUTE */ -	evergreen_hdmi_videoinfoframe(encoder, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -				      0, 0, 0, 0, 0, 0); +	err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); +	if (err < 0) { +		DRM_ERROR("failed to setup AVI infoframe: %zd\n", err); +		return; +	} + +	err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer)); +	if (err < 0) { +		DRM_ERROR("failed to pack AVI infoframe: %zd\n", err); +		return; +	} +	evergreen_hdmi_update_avi_infoframe(encoder, buffer, sizeof(buffer));  	evergreen_hdmi_update_ACR(encoder, mode->clock);  	/* it's unknown what these bits do excatly, but it's indeed quite useful for debugging */ diff --git a/drivers/gpu/drm/radeon/ni.c b/drivers/gpu/drm/radeon/ni.c index 7cead763be9..d4c633e1286 100644 --- a/drivers/gpu/drm/radeon/ni.c +++ b/drivers/gpu/drm/radeon/ni.c @@ -1381,6 +1381,12 @@ static u32 cayman_gpu_check_soft_reset(struct radeon_device *rdev)  	if (tmp & L2_BUSY)  		reset_mask |= RADEON_RESET_VMC; +	/* Skip MC reset as it's mostly likely not hung, just busy */ +	if (reset_mask & RADEON_RESET_MC) { +		DRM_DEBUG("MC busy: 0x%08X, clearing.\n", reset_mask); +		reset_mask &= ~RADEON_RESET_MC; +	} +  	return reset_mask;  } diff --git a/drivers/gpu/drm/radeon/r600.c b/drivers/gpu/drm/radeon/r600.c index dbcb0752f08..0740db3fcd2 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)  { @@ -1381,6 +1394,12 @@ static u32 r600_gpu_check_soft_reset(struct radeon_device *rdev)  	if (r600_is_display_hung(rdev))  		reset_mask |= RADEON_RESET_DISPLAY; +	/* Skip MC reset as it's mostly likely not hung, just busy */ +	if (reset_mask & RADEON_RESET_MC) { +		DRM_DEBUG("MC busy: 0x%08X, clearing.\n", reset_mask); +		reset_mask &= ~RADEON_RESET_MC; +	} +  	return reset_mask;  } @@ -4448,14 +4467,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_cs.c b/drivers/gpu/drm/radeon/r600_cs.c index 931a7028903..01a3ec83f28 100644 --- a/drivers/gpu/drm/radeon/r600_cs.c +++ b/drivers/gpu/drm/radeon/r600_cs.c @@ -2491,14 +2491,14 @@ int r600_dma_cs_parse(struct radeon_cs_parser *p)  				return -EINVAL;  			}  			if (tiled) { -				dst_offset = ib[idx+1]; +				dst_offset = radeon_get_ib_value(p, idx+1);  				dst_offset <<= 8;  				ib[idx+1] += (u32)(dst_reloc->lobj.gpu_offset >> 8);  				p->idx += count + 5;  			} else { -				dst_offset = ib[idx+1]; -				dst_offset |= ((u64)(ib[idx+2] & 0xff)) << 32; +				dst_offset = radeon_get_ib_value(p, idx+1); +				dst_offset |= ((u64)(radeon_get_ib_value(p, idx+2) & 0xff)) << 32;  				ib[idx+1] += (u32)(dst_reloc->lobj.gpu_offset & 0xfffffffc);  				ib[idx+2] += upper_32_bits(dst_reloc->lobj.gpu_offset) & 0xff; @@ -2526,32 +2526,32 @@ int r600_dma_cs_parse(struct radeon_cs_parser *p)  				/* detile bit */  				if (idx_value & (1 << 31)) {  					/* tiled src, linear dst */ -					src_offset = ib[idx+1]; +					src_offset = radeon_get_ib_value(p, idx+1);  					src_offset <<= 8;  					ib[idx+1] += (u32)(src_reloc->lobj.gpu_offset >> 8); -					dst_offset = ib[idx+5]; -					dst_offset |= ((u64)(ib[idx+6] & 0xff)) << 32; +					dst_offset = radeon_get_ib_value(p, idx+5); +					dst_offset |= ((u64)(radeon_get_ib_value(p, idx+6) & 0xff)) << 32;  					ib[idx+5] += (u32)(dst_reloc->lobj.gpu_offset & 0xfffffffc);  					ib[idx+6] += upper_32_bits(dst_reloc->lobj.gpu_offset) & 0xff;  				} else {  					/* linear src, tiled dst */ -					src_offset = ib[idx+5]; -					src_offset |= ((u64)(ib[idx+6] & 0xff)) << 32; +					src_offset = radeon_get_ib_value(p, idx+5); +					src_offset |= ((u64)(radeon_get_ib_value(p, idx+6) & 0xff)) << 32;  					ib[idx+5] += (u32)(src_reloc->lobj.gpu_offset & 0xfffffffc);  					ib[idx+6] += upper_32_bits(src_reloc->lobj.gpu_offset) & 0xff; -					dst_offset = ib[idx+1]; +					dst_offset = radeon_get_ib_value(p, idx+1);  					dst_offset <<= 8;  					ib[idx+1] += (u32)(dst_reloc->lobj.gpu_offset >> 8);  				}  				p->idx += 7;  			} else {  				if (p->family >= CHIP_RV770) { -					src_offset = ib[idx+2]; -					src_offset |= ((u64)(ib[idx+4] & 0xff)) << 32; -					dst_offset = ib[idx+1]; -					dst_offset |= ((u64)(ib[idx+3] & 0xff)) << 32; +					src_offset = radeon_get_ib_value(p, idx+2); +					src_offset |= ((u64)(radeon_get_ib_value(p, idx+4) & 0xff)) << 32; +					dst_offset = radeon_get_ib_value(p, idx+1); +					dst_offset |= ((u64)(radeon_get_ib_value(p, idx+3) & 0xff)) << 32;  					ib[idx+1] += (u32)(dst_reloc->lobj.gpu_offset & 0xfffffffc);  					ib[idx+2] += (u32)(src_reloc->lobj.gpu_offset & 0xfffffffc); @@ -2559,10 +2559,10 @@ int r600_dma_cs_parse(struct radeon_cs_parser *p)  					ib[idx+4] += upper_32_bits(src_reloc->lobj.gpu_offset) & 0xff;  					p->idx += 5;  				} else { -					src_offset = ib[idx+2]; -					src_offset |= ((u64)(ib[idx+3] & 0xff)) << 32; -					dst_offset = ib[idx+1]; -					dst_offset |= ((u64)(ib[idx+3] & 0xff0000)) << 16; +					src_offset = radeon_get_ib_value(p, idx+2); +					src_offset |= ((u64)(radeon_get_ib_value(p, idx+3) & 0xff)) << 32; +					dst_offset = radeon_get_ib_value(p, idx+1); +					dst_offset |= ((u64)(radeon_get_ib_value(p, idx+3) & 0xff0000)) << 16;  					ib[idx+1] += (u32)(dst_reloc->lobj.gpu_offset & 0xfffffffc);  					ib[idx+2] += (u32)(src_reloc->lobj.gpu_offset & 0xfffffffc); @@ -2592,8 +2592,8 @@ int r600_dma_cs_parse(struct radeon_cs_parser *p)  				DRM_ERROR("bad DMA_PACKET_WRITE\n");  				return -EINVAL;  			} -			dst_offset = ib[idx+1]; -			dst_offset |= ((u64)(ib[idx+3] & 0x00ff0000)) << 16; +			dst_offset = radeon_get_ib_value(p, idx+1); +			dst_offset |= ((u64)(radeon_get_ib_value(p, idx+3) & 0x00ff0000)) << 16;  			if ((dst_offset + (count * 4)) > radeon_bo_size(dst_reloc->robj)) {  				dev_warn(p->dev, "DMA constant fill buffer too small (%llu %lu)\n",  					 dst_offset + (count * 4), radeon_bo_size(dst_reloc->robj)); diff --git a/drivers/gpu/drm/radeon/r600_hdmi.c b/drivers/gpu/drm/radeon/r600_hdmi.c index ff80efe9cb7..21ecc0e12dc 100644 --- a/drivers/gpu/drm/radeon/r600_hdmi.c +++ b/drivers/gpu/drm/radeon/r600_hdmi.c @@ -23,6 +23,7 @@   *   * Authors: Christian König   */ +#include <linux/hdmi.h>  #include <drm/drmP.h>  #include <drm/radeon_drm.h>  #include "radeon.h" @@ -121,79 +122,18 @@ static void r600_hdmi_update_ACR(struct drm_encoder *encoder, uint32_t clock)  }  /* - * calculate the crc for a given info frame - */ -static void r600_hdmi_infoframe_checksum(uint8_t packetType, -					 uint8_t versionNumber, -					 uint8_t length, -					 uint8_t *frame) -{ -	int i; -	frame[0] = packetType + versionNumber + length; -	for (i = 1; i <= length; i++) -		frame[0] += frame[i]; -	frame[0] = 0x100 - frame[0]; -} - -/*   * build a HDMI Video Info Frame   */ -static void r600_hdmi_videoinfoframe( -	struct drm_encoder *encoder, -	enum r600_hdmi_color_format color_format, -	int active_information_present, -	uint8_t active_format_aspect_ratio, -	uint8_t scan_information, -	uint8_t colorimetry, -	uint8_t ex_colorimetry, -	uint8_t quantization, -	int ITC, -	uint8_t picture_aspect_ratio, -	uint8_t video_format_identification, -	uint8_t pixel_repetition, -	uint8_t non_uniform_picture_scaling, -	uint8_t bar_info_data_valid, -	uint16_t top_bar, -	uint16_t bottom_bar, -	uint16_t left_bar, -	uint16_t right_bar -) +static void r600_hdmi_update_avi_infoframe(struct drm_encoder *encoder, +					   void *buffer, size_t size)  {  	struct drm_device *dev = encoder->dev;  	struct radeon_device *rdev = dev->dev_private;  	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);  	struct radeon_encoder_atom_dig *dig = radeon_encoder->enc_priv;  	uint32_t offset = dig->afmt->offset; +	uint8_t *frame = buffer + 3; -	uint8_t frame[14]; - -	frame[0x0] = 0; -	frame[0x1] = -		(scan_information & 0x3) | -		((bar_info_data_valid & 0x3) << 2) | -		((active_information_present & 0x1) << 4) | -		((color_format & 0x3) << 5); -	frame[0x2] = -		(active_format_aspect_ratio & 0xF) | -		((picture_aspect_ratio & 0x3) << 4) | -		((colorimetry & 0x3) << 6); -	frame[0x3] = -		(non_uniform_picture_scaling & 0x3) | -		((quantization & 0x3) << 2) | -		((ex_colorimetry & 0x7) << 4) | -		((ITC & 0x1) << 7); -	frame[0x4] = (video_format_identification & 0x7F); -	frame[0x5] = (pixel_repetition & 0xF); -	frame[0x6] = (top_bar & 0xFF); -	frame[0x7] = (top_bar >> 8); -	frame[0x8] = (bottom_bar & 0xFF); -	frame[0x9] = (bottom_bar >> 8); -	frame[0xA] = (left_bar & 0xFF); -	frame[0xB] = (left_bar >> 8); -	frame[0xC] = (right_bar & 0xFF); -	frame[0xD] = (right_bar >> 8); - -	r600_hdmi_infoframe_checksum(0x82, 0x02, 0x0D, frame);  	/* Our header values (type, version, length) should be alright, Intel  	 * is using the same. Checksum function also seems to be OK, it works  	 * fine for audio infoframe. However calculated value is always lower @@ -215,39 +155,15 @@ static void r600_hdmi_videoinfoframe(  /*   * build a Audio Info Frame   */ -static void r600_hdmi_audioinfoframe( -	struct drm_encoder *encoder, -	uint8_t channel_count, -	uint8_t coding_type, -	uint8_t sample_size, -	uint8_t sample_frequency, -	uint8_t format, -	uint8_t channel_allocation, -	uint8_t level_shift, -	int downmix_inhibit -) +static void r600_hdmi_update_audio_infoframe(struct drm_encoder *encoder, +					     const void *buffer, size_t size)  {  	struct drm_device *dev = encoder->dev;  	struct radeon_device *rdev = dev->dev_private;  	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);  	struct radeon_encoder_atom_dig *dig = radeon_encoder->enc_priv;  	uint32_t offset = dig->afmt->offset; - -	uint8_t frame[11]; - -	frame[0x0] = 0; -	frame[0x1] = (channel_count & 0x7) | ((coding_type & 0xF) << 4); -	frame[0x2] = (sample_size & 0x3) | ((sample_frequency & 0x7) << 2); -	frame[0x3] = format; -	frame[0x4] = channel_allocation; -	frame[0x5] = ((level_shift & 0xF) << 3) | ((downmix_inhibit & 0x1) << 7); -	frame[0x6] = 0; -	frame[0x7] = 0; -	frame[0x8] = 0; -	frame[0x9] = 0; -	frame[0xA] = 0; - -	r600_hdmi_infoframe_checksum(0x84, 0x01, 0x0A, frame); +	const u8 *frame = buffer + 3;  	WREG32(HDMI0_AUDIO_INFO0 + offset,  		frame[0x0] | (frame[0x1] << 8) | (frame[0x2] << 16) | (frame[0x3] << 24)); @@ -320,7 +236,10 @@ void r600_hdmi_setmode(struct drm_encoder *encoder, struct drm_display_mode *mod  	struct radeon_device *rdev = dev->dev_private;  	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);  	struct radeon_encoder_atom_dig *dig = radeon_encoder->enc_priv; +	u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE]; +	struct hdmi_avi_infoframe frame;  	uint32_t offset; +	ssize_t err;  	/* Silent, r600_hdmi_enable will raise WARN for us */  	if (!dig->afmt->enabled) @@ -371,9 +290,19 @@ void r600_hdmi_setmode(struct drm_encoder *encoder, struct drm_display_mode *mod  	WREG32(HDMI0_GC + offset, 0); /* unset HDMI0_GC_AVMUTE */ -	r600_hdmi_videoinfoframe(encoder, RGB, 0, 0, 0, 0, -		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); +	err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); +	if (err < 0) { +		DRM_ERROR("failed to setup AVI infoframe: %zd\n", err); +		return; +	} +	err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer)); +	if (err < 0) { +		DRM_ERROR("failed to pack AVI infoframe: %zd\n", err); +		return; +	} + +	r600_hdmi_update_avi_infoframe(encoder, buffer, sizeof(buffer));  	r600_hdmi_update_ACR(encoder, mode->clock);  	/* it's unknown what these bits do excatly, but it's indeed quite useful for debugging */ @@ -395,8 +324,11 @@ void r600_hdmi_update_audio_settings(struct drm_encoder *encoder)  	struct radeon_encoder *radeon_encoder = to_radeon_encoder(encoder);  	struct radeon_encoder_atom_dig *dig = radeon_encoder->enc_priv;  	struct r600_audio audio = r600_audio_status(rdev); +	uint8_t buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AUDIO_INFOFRAME_SIZE]; +	struct hdmi_audio_infoframe frame;  	uint32_t offset;  	uint32_t iec; +	ssize_t err;  	if (!dig->afmt || !dig->afmt->enabled)  		return; @@ -462,9 +394,21 @@ void r600_hdmi_update_audio_settings(struct drm_encoder *encoder)  		iec |= 0x5 << 16;  	WREG32_P(HDMI0_60958_1 + offset, iec, ~0x5000f); -	r600_hdmi_audioinfoframe(encoder, audio.channels - 1, 0, 0, 0, 0, 0, 0, -				 0); +	err = hdmi_audio_infoframe_init(&frame); +	if (err < 0) { +		DRM_ERROR("failed to setup audio infoframe\n"); +		return; +	} + +	frame.channels = audio.channels; + +	err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); +	if (err < 0) { +		DRM_ERROR("failed to pack audio infoframe\n"); +		return; +	} +	r600_hdmi_update_audio_infoframe(encoder, buffer, sizeof(buffer));  	r600_hdmi_audio_workaround(encoder);  } @@ -544,7 +488,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 bb43a849759..8263af3fd83 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 67f008febec..aba0a893ea9 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 f4134a82395..3535f73ad3e 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 15f5ded65e0..d96070bf838 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_combios.c b/drivers/gpu/drm/radeon/radeon_combios.c index 3e403bdda58..78edadc9e86 100644 --- a/drivers/gpu/drm/radeon/radeon_combios.c +++ b/drivers/gpu/drm/radeon/radeon_combios.c @@ -970,6 +970,15 @@ struct radeon_encoder_primary_dac *radeon_combios_get_primary_dac_info(struct  			found = 1;  	} +	/* quirks */ +	/* Radeon 9100 (R200) */ +	if ((dev->pdev->device == 0x514D) && +	    (dev->pdev->subsystem_vendor == 0x174B) && +	    (dev->pdev->subsystem_device == 0x7149)) { +		/* vbios value is bad, use the default */ +		found = 0; +	} +  	if (!found) /* fallback to defaults */  		radeon_legacy_get_primary_dac_info_from_table(rdev, p_dac); diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c index 8794de10a6c..44b8034a400 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_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c index 167758488ed..66a7f0fd962 100644 --- a/drivers/gpu/drm/radeon/radeon_drv.c +++ b/drivers/gpu/drm/radeon/radeon_drv.c @@ -70,9 +70,10 @@   *   2.27.0 - r600-SI: Add CS ioctl support for async DMA   *   2.28.0 - r600-eg: Add MEM_WRITE packet support   *   2.29.0 - R500 FP16 color clear registers + *   2.30.0 - fix for FMASK texturing   */  #define KMS_DRIVER_MAJOR	2 -#define KMS_DRIVER_MINOR	29 +#define KMS_DRIVER_MINOR	30  #define KMS_DRIVER_PATCHLEVEL	0  int radeon_driver_load_kms(struct drm_device *dev, unsigned long flags);  int radeon_driver_unload_kms(struct drm_device *dev); diff --git a/drivers/gpu/drm/radeon/radeon_fb.c b/drivers/gpu/drm/radeon/radeon_fb.c index 515e5ee1f9e..b1746741bc5 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_irq_kms.c b/drivers/gpu/drm/radeon/radeon_irq_kms.c index 90374dd7796..48f80cd42d8 100644 --- a/drivers/gpu/drm/radeon/radeon_irq_kms.c +++ b/drivers/gpu/drm/radeon/radeon_irq_kms.c @@ -400,6 +400,9 @@ void radeon_irq_kms_enable_afmt(struct radeon_device *rdev, int block)  {  	unsigned long irqflags; +	if (!rdev->ddev->irq_enabled) +		return; +  	spin_lock_irqsave(&rdev->irq.lock, irqflags);  	rdev->irq.afmt[block] = true;  	radeon_irq_set(rdev); @@ -419,6 +422,9 @@ void radeon_irq_kms_disable_afmt(struct radeon_device *rdev, int block)  {  	unsigned long irqflags; +	if (!rdev->ddev->irq_enabled) +		return; +  	spin_lock_irqsave(&rdev->irq.lock, irqflags);  	rdev->irq.afmt[block] = false;  	radeon_irq_set(rdev); @@ -438,6 +444,9 @@ void radeon_irq_kms_enable_hpd(struct radeon_device *rdev, unsigned hpd_mask)  	unsigned long irqflags;  	int i; +	if (!rdev->ddev->irq_enabled) +		return; +  	spin_lock_irqsave(&rdev->irq.lock, irqflags);  	for (i = 0; i < RADEON_MAX_HPD_PINS; ++i)  		rdev->irq.hpd[i] |= !!(hpd_mask & (1 << i)); @@ -458,6 +467,9 @@ void radeon_irq_kms_disable_hpd(struct radeon_device *rdev, unsigned hpd_mask)  	unsigned long irqflags;  	int i; +	if (!rdev->ddev->irq_enabled) +		return; +  	spin_lock_irqsave(&rdev->irq.lock, irqflags);  	for (i = 0; i < RADEON_MAX_HPD_PINS; ++i)  		rdev->irq.hpd[i] &= !(hpd_mask & (1 << i)); diff --git a/drivers/gpu/drm/radeon/radeon_kms.c b/drivers/gpu/drm/radeon/radeon_kms.c index 9c312f9afb6..c75cb2c6ba7 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 0bfa656aa87..338fd6a74e8 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 1d8ff2f850b..93f760e27a9 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 1b2444f4d8f..d63fe1d0f53 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 20e29d23d34..c55f950a4af 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 719f03e061d..9128120da04 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)  { @@ -2257,6 +2284,12 @@ static u32 si_gpu_check_soft_reset(struct radeon_device *rdev)  	if (tmp & L2_BUSY)  		reset_mask |= RADEON_RESET_VMC; +	/* Skip MC reset as it's mostly likely not hung, just busy */ +	if (reset_mask & RADEON_RESET_MC) { +		DRM_DEBUG("MC busy: 0x%08X, clearing.\n", reset_mask); +		reset_mask &= ~RADEON_RESET_MC; +	} +  	return reset_mask;  } @@ -4582,14 +4615,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 07fc455e35a..23fc08fc8e7 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/sis/sis_drv.c b/drivers/gpu/drm/sis/sis_drv.c index 841065b998a..5a5325e6b75 100644 --- a/drivers/gpu/drm/sis/sis_drv.c +++ b/drivers/gpu/drm/sis/sis_drv.c @@ -58,7 +58,6 @@ static int sis_driver_unload(struct drm_device *dev)  {  	drm_sis_private_t *dev_priv = dev->dev_private; -	idr_remove_all(&dev_priv->object_idr);  	idr_destroy(&dev_priv->object_idr);  	kfree(dev_priv); diff --git a/drivers/gpu/drm/sis/sis_mm.c b/drivers/gpu/drm/sis/sis_mm.c index 2b2f78c428a..9a43d98e500 100644 --- a/drivers/gpu/drm/sis/sis_mm.c +++ b/drivers/gpu/drm/sis/sis_mm.c @@ -128,17 +128,10 @@ static int sis_drm_alloc(struct drm_device *dev, struct drm_file *file,  	if (retval)  		goto fail_alloc; -again: -	if (idr_pre_get(&dev_priv->object_idr, GFP_KERNEL) == 0) { -		retval = -ENOMEM; -		goto fail_idr; -	} - -	retval = idr_get_new_above(&dev_priv->object_idr, item, 1, &user_key); -	if (retval == -EAGAIN) -		goto again; -	if (retval) +	retval = idr_alloc(&dev_priv->object_idr, item, 1, 0, GFP_KERNEL); +	if (retval < 0)  		goto fail_idr; +	user_key = retval;  	list_add(&item->owner_list, &file_priv->obj_list);  	mutex_unlock(&dev->struct_mutex); diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 656b2e3334a..de94707b9db 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -12,32 +12,262 @@  #include <linux/module.h>  #include <linux/of.h>  #include <linux/platform_device.h> - -#include <mach/clk.h> +#include <linux/clk/tegra.h>  #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 +277,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 +311,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 +385,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 +604,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); +	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; -	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; +	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 +677,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 = GENERAL_ACT_REQ | WIN_A_ACT_REQ; +	tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); -	value = tegra_dc_readl(dc, DC_CMD_INT_MASK); -	value |= FRAME_END_INT; -	tegra_dc_writel(dc, value, DC_CMD_INT_MASK); - -	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 +702,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 +730,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 +945,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 +1047,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 +1106,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; @@ -764,11 +1126,9 @@ static int tegra_dc_probe(struct platform_device *pdev)  		return -ENXIO;  	} -	dc->regs = devm_request_and_ioremap(&pdev->dev, regs); -	if (!dc->regs) { -		dev_err(&pdev->dev, "failed to remap registers\n"); -		return -ENXIO; -	} +	dc->regs = devm_ioremap_resource(&pdev->dev, regs); +	if (IS_ERR(dc->regs)) +		return PTR_ERR(dc->regs);  	dc->irq = platform_get_irq(pdev, 0);  	if (dc->irq < 0) { diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h index 99977b5d5c3..79eaec9aac7 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 3a503c9e468..9d452df5bca 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -11,7 +11,6 @@  #include <linux/of_address.h>  #include <linux/of_platform.h> -#include <mach/clk.h>  #include <linux/dma-mapping.h>  #include <asm/dma-iommu.h> @@ -40,6 +39,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 +92,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 741b5dc2742..6dd75a2600e 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 97993c6835f..03914953cb1 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/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index e060c7e6434..bb747f6cd1a 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -10,12 +10,14 @@  #include <linux/clk.h>  #include <linux/debugfs.h>  #include <linux/gpio.h> +#include <linux/hdmi.h>  #include <linux/module.h>  #include <linux/of.h>  #include <linux/platform_device.h>  #include <linux/regulator/consumer.h> +#include <linux/clk/tegra.h> -#include <mach/clk.h> +#include <drm/drm_edid.h>  #include "hdmi.h"  #include "drm.h" @@ -401,54 +403,65 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk)  	return 0;  } -static void tegra_hdmi_write_infopack(struct tegra_hdmi *hdmi, -				      unsigned int offset, u8 type, -				      u8 version, void *data, size_t size) +static inline unsigned long tegra_hdmi_subpack(const u8 *ptr, size_t size)  { -	unsigned long value; -	u8 *ptr = data; -	u32 subpack[2]; +	unsigned long value = 0;  	size_t i; -	u8 csum; -	/* first byte of data is the checksum */ -	csum = type + version + size - 1; +	for (i = size; i > 0; i--) +		value = (value << 8) | ptr[i - 1]; -	for (i = 1; i < size; i++) -		csum += ptr[i]; +	return value; +} -	ptr[0] = 0x100 - csum; +static void tegra_hdmi_write_infopack(struct tegra_hdmi *hdmi, const void *data, +				      size_t size) +{ +	const u8 *ptr = data; +	unsigned long offset; +	unsigned long value; +	size_t i, j; -	value = INFOFRAME_HEADER_TYPE(type) | -		INFOFRAME_HEADER_VERSION(version) | -		INFOFRAME_HEADER_LEN(size - 1); -	tegra_hdmi_writel(hdmi, value, offset); +	switch (ptr[0]) { +	case HDMI_INFOFRAME_TYPE_AVI: +		offset = HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_HEADER; +		break; -	/* The audio inforame only has one set of subpack registers.  The hdmi -	 * block pads the rest of the data as per the spec so we have to fixup -	 * the length before filling in the subpacks. -	 */ -	if (offset == HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER) -		size = 6; +	case HDMI_INFOFRAME_TYPE_AUDIO: +		offset = HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER; +		break; -	/* each subpack 7 bytes devided into: -	 *   subpack_low - bytes 0 - 3 -	 *   subpack_high - bytes 4 - 6 (with byte 7 padded to 0x00) -	 */ -	for (i = 0; i < size; i++) { -		size_t index = i % 7; +	case HDMI_INFOFRAME_TYPE_VENDOR: +		offset = HDMI_NV_PDISP_HDMI_GENERIC_HEADER; +		break; -		if (index == 0) -			memset(subpack, 0x0, sizeof(subpack)); +	default: +		dev_err(hdmi->dev, "unsupported infoframe type: %02x\n", +			ptr[0]); +		return; +	} -		((u8 *)subpack)[index] = ptr[i]; +	value = INFOFRAME_HEADER_TYPE(ptr[0]) | +		INFOFRAME_HEADER_VERSION(ptr[1]) | +		INFOFRAME_HEADER_LEN(ptr[2]); +	tegra_hdmi_writel(hdmi, value, offset); +	offset++; -		if (index == 6 || (i + 1 == size)) { -			unsigned int reg = offset + 1 + (i / 7) * 2; +	/* +	 * Each subpack contains 7 bytes, divided into: +	 * - subpack_low: bytes 0 - 3 +	 * - subpack_high: bytes 4 - 6 (with byte 7 padded to 0x00) +	 */ +	for (i = 3, j = 0; i < size; i += 7, j += 8) { +		size_t rem = size - i, num = min_t(size_t, rem, 4); -			tegra_hdmi_writel(hdmi, subpack[0], reg); -			tegra_hdmi_writel(hdmi, subpack[1], reg + 1); -		} +		value = tegra_hdmi_subpack(&ptr[i], num); +		tegra_hdmi_writel(hdmi, value, offset++); + +		num = min_t(size_t, rem - num, 3); + +		value = tegra_hdmi_subpack(&ptr[i + 4], num); +		tegra_hdmi_writel(hdmi, value, offset++);  	}  } @@ -456,9 +469,8 @@ static void tegra_hdmi_setup_avi_infoframe(struct tegra_hdmi *hdmi,  					   struct drm_display_mode *mode)  {  	struct hdmi_avi_infoframe frame; -	unsigned int h_front_porch; -	unsigned int hsize = 16; -	unsigned int vsize = 9; +	u8 buffer[17]; +	ssize_t err;  	if (hdmi->dvi) {  		tegra_hdmi_writel(hdmi, 0, @@ -466,69 +478,19 @@ static void tegra_hdmi_setup_avi_infoframe(struct tegra_hdmi *hdmi,  		return;  	} -	h_front_porch = mode->hsync_start - mode->hdisplay; -	memset(&frame, 0, sizeof(frame)); -	frame.r = HDMI_AVI_R_SAME; - -	switch (mode->vdisplay) { -	case 480: -		if (mode->hdisplay == 640) { -			frame.m = HDMI_AVI_M_4_3; -			frame.vic = 1; -		} else { -			frame.m = HDMI_AVI_M_16_9; -			frame.vic = 3; -		} -		break; - -	case 576: -		if (((hsize * 10) / vsize) > 14) { -			frame.m = HDMI_AVI_M_16_9; -			frame.vic = 18; -		} else { -			frame.m = HDMI_AVI_M_4_3; -			frame.vic = 17; -		} -		break; - -	case 720: -	case 1470: /* stereo mode */ -		frame.m = HDMI_AVI_M_16_9; - -		if (h_front_porch == 110) -			frame.vic = 4; -		else -			frame.vic = 19; -		break; - -	case 1080: -	case 2205: /* stereo mode */ -		frame.m = HDMI_AVI_M_16_9; - -		switch (h_front_porch) { -		case 88: -			frame.vic = 16; -			break; - -		case 528: -			frame.vic = 31; -			break; - -		default: -			frame.vic = 32; -			break; -		} -		break; +	err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to setup AVI infoframe: %zd\n", err); +		return; +	} -	default: -		frame.m = HDMI_AVI_M_16_9; -		frame.vic = 0; -		break; +	err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer)); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to pack AVI infoframe: %zd\n", err); +		return;  	} -	tegra_hdmi_write_infopack(hdmi, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_HEADER, -				  HDMI_INFOFRAME_TYPE_AVI, HDMI_AVI_VERSION, -				  &frame, sizeof(frame)); +	tegra_hdmi_write_infopack(hdmi, buffer, err);  	tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE,  			  HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); @@ -537,6 +499,8 @@ static void tegra_hdmi_setup_avi_infoframe(struct tegra_hdmi *hdmi,  static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)  {  	struct hdmi_audio_infoframe frame; +	u8 buffer[14]; +	ssize_t err;  	if (hdmi->dvi) {  		tegra_hdmi_writel(hdmi, 0, @@ -544,14 +508,29 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)  		return;  	} -	memset(&frame, 0, sizeof(frame)); -	frame.cc = HDMI_AUDIO_CC_2; +	err = hdmi_audio_infoframe_init(&frame); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to initialize audio infoframe: %d\n", +			err); +		return; +	} + +	frame.channels = 2; + +	err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to pack audio infoframe: %zd\n", +			err); +		return; +	} -	tegra_hdmi_write_infopack(hdmi, -				  HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER, -				  HDMI_INFOFRAME_TYPE_AUDIO, -				  HDMI_AUDIO_VERSION, -				  &frame, sizeof(frame)); +	/* +	 * The audio infoframe has only one set of subpack registers, so the +	 * infoframe needs to be truncated. One set of subpack registers can +	 * contain 7 bytes. Including the 3 byte header only the first 10 +	 * bytes can be programmed. +	 */ +	tegra_hdmi_write_infopack(hdmi, buffer, min(10, err));  	tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE,  			  HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); @@ -559,8 +538,10 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi)  static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi)  { -	struct hdmi_stereo_infoframe frame; +	struct hdmi_vendor_infoframe frame;  	unsigned long value; +	u8 buffer[10]; +	ssize_t err;  	if (!hdmi->stereo) {  		value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); @@ -570,22 +551,32 @@ static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi)  	}  	memset(&frame, 0, sizeof(frame)); -	frame.regid0 = 0x03; -	frame.regid1 = 0x0c; -	frame.regid2 = 0x00; -	frame.hdmi_video_format = 2; + +	frame.type = HDMI_INFOFRAME_TYPE_VENDOR; +	frame.version = 0x01; +	frame.length = 6; + +	frame.data[0] = 0x03; /* regid0 */ +	frame.data[1] = 0x0c; /* regid1 */ +	frame.data[2] = 0x00; /* regid2 */ +	frame.data[3] = 0x02 << 5; /* video format */  	/* TODO: 74 MHz limit? */  	if (1) { -		frame._3d_structure = 0; +		frame.data[4] = 0x00 << 4; /* 3D structure */  	} else { -		frame._3d_structure = 8; -		frame._3d_ext_data = 0; +		frame.data[4] = 0x08 << 4; /* 3D structure */ +		frame.data[5] = 0x00 << 4; /* 3D ext. data */ +	} + +	err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer)); +	if (err < 0) { +		dev_err(hdmi->dev, "failed to pack vendor infoframe: %zd\n", +			err); +		return;  	} -	tegra_hdmi_write_infopack(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_HEADER, -				  HDMI_INFOFRAME_TYPE_VENDOR, -				  HDMI_VENDOR_VERSION, &frame, 6); +	tegra_hdmi_write_infopack(hdmi, buffer, err);  	value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL);  	value |= GENERIC_CTRL_ENABLE; @@ -1259,9 +1250,9 @@ static int tegra_hdmi_probe(struct platform_device *pdev)  	if (!regs)  		return -ENXIO; -	hdmi->regs = devm_request_and_ioremap(&pdev->dev, regs); -	if (!hdmi->regs) -		return -EADDRNOTAVAIL; +	hdmi->regs = devm_ioremap_resource(&pdev->dev, regs); +	if (IS_ERR(hdmi->regs)) +		return PTR_ERR(hdmi->regs);  	err = platform_get_irq(pdev, 0);  	if (err < 0) diff --git a/drivers/gpu/drm/tegra/hdmi.h b/drivers/gpu/drm/tegra/hdmi.h index 1477f36eb45..52ac36e08cc 100644 --- a/drivers/gpu/drm/tegra/hdmi.h +++ b/drivers/gpu/drm/tegra/hdmi.h @@ -10,195 +10,6 @@  #ifndef TEGRA_HDMI_H  #define TEGRA_HDMI_H 1 -#define HDMI_INFOFRAME_TYPE_VENDOR   0x81 -#define HDMI_INFOFRAME_TYPE_AVI      0x82 -#define HDMI_INFOFRAME_TYPE_SPD      0x83 -#define HDMI_INFOFRAME_TYPE_AUDIO    0x84 -#define HDMI_INFOFRAME_TYPE_MPEG_SRC 0x85 -#define HDMI_INFOFRAME_TYPE_NTSC_VBI 0x86 - -/* all fields little endian */ -struct hdmi_avi_infoframe { -	/* PB0 */ -	u8 csum; - -	/* PB1 */ -	unsigned s:2; /* scan information */ -	unsigned b:2; /* bar info data valid */ -	unsigned a:1; /* active info present */ -	unsigned y:2; /* RGB or YCbCr */ -	unsigned res1:1; - -	/* PB2 */ -	unsigned r:4; /* active format aspect ratio */ -	unsigned m:2; /* picture aspect ratio */ -	unsigned c:2; /* colorimetry */ - -	/* PB3 */ -	unsigned sc:2;  /* scan information */ -	unsigned q:2;   /* quantization range */ -	unsigned ec:3;  /* extended colorimetry */ -	unsigned itc:1; /* it content */ - -	/* PB4 */ -	unsigned vic:7; /* video format id code */ -	unsigned res4:1; - -	/* PB5 */ -	unsigned pr:4; /* pixel repetition factor */ -	unsigned cn:2; /* it content type*/ -	unsigned yq:2; /* ycc quantization range */ - -	/* PB6-7 */ -	u16 top_bar_end_line; - -	/* PB8-9 */ -	u16 bot_bar_start_line; - -	/* PB10-11 */ -	u16 left_bar_end_pixel; - -	/* PB12-13 */ -	u16 right_bar_start_pixel; -} __packed; - -#define HDMI_AVI_VERSION 0x02 - -#define HDMI_AVI_Y_RGB       0x0 -#define HDMI_AVI_Y_YCBCR_422 0x1 -#define HDMI_AVI_Y_YCBCR_444 0x2 - -#define HDMI_AVI_B_VERT  0x1 -#define HDMI_AVI_B_HORIZ 0x2 - -#define HDMI_AVI_S_NONE      0x0 -#define HDMI_AVI_S_OVERSCAN  0x1 -#define HDMI_AVI_S_UNDERSCAN 0x2 - -#define HDMI_AVI_C_NONE     0x0 -#define HDMI_AVI_C_SMPTE    0x1 -#define HDMI_AVI_C_ITU_R    0x2 -#define HDMI_AVI_C_EXTENDED 0x4 - -#define HDMI_AVI_M_4_3  0x1 -#define HDMI_AVI_M_16_9 0x2 - -#define HDMI_AVI_R_SAME        0x8 -#define HDMI_AVI_R_4_3_CENTER  0x9 -#define HDMI_AVI_R_16_9_CENTER 0xa -#define HDMI_AVI_R_14_9_CENTER 0xb - -/* all fields little endian */ -struct hdmi_audio_infoframe { -	/* PB0 */ -	u8 csum; - -	/* PB1 */ -	unsigned cc:3; /* channel count */ -	unsigned res1:1; -	unsigned ct:4; /* coding type */ - -	/* PB2 */ -	unsigned ss:2; /* sample size */ -	unsigned sf:3; /* sample frequency */ -	unsigned res2:3; - -	/* PB3 */ -	unsigned cxt:5; /* coding extention type */ -	unsigned res3:3; - -	/* PB4 */ -	u8 ca; /* channel/speaker allocation */ - -	/* PB5 */ -	unsigned res5:3; -	unsigned lsv:4; /* level shift value */ -	unsigned dm_inh:1; /* downmix inhibit */ - -	/* PB6-10 reserved */ -	u8 res6; -	u8 res7; -	u8 res8; -	u8 res9; -	u8 res10; -} __packed; - -#define HDMI_AUDIO_VERSION 0x01 - -#define HDMI_AUDIO_CC_STREAM 0x0 /* specified by audio stream */ -#define HDMI_AUDIO_CC_2      0x1 -#define HDMI_AUDIO_CC_3      0x2 -#define HDMI_AUDIO_CC_4      0x3 -#define HDMI_AUDIO_CC_5      0x4 -#define HDMI_AUDIO_CC_6      0x5 -#define HDMI_AUDIO_CC_7      0x6 -#define HDMI_AUDIO_CC_8      0x7 - -#define HDMI_AUDIO_CT_STREAM  0x0 /* specified by audio stream */ -#define HDMI_AUDIO_CT_PCM     0x1 -#define HDMI_AUDIO_CT_AC3     0x2 -#define HDMI_AUDIO_CT_MPEG1   0x3 -#define HDMI_AUDIO_CT_MP3     0x4 -#define HDMI_AUDIO_CT_MPEG2   0x5 -#define HDMI_AUDIO_CT_AAC_LC  0x6 -#define HDMI_AUDIO_CT_DTS     0x7 -#define HDMI_AUDIO_CT_ATRAC   0x8 -#define HDMI_AUDIO_CT_DSD     0x9 -#define HDMI_AUDIO_CT_E_AC3   0xa -#define HDMI_AUDIO_CT_DTS_HD  0xb -#define HDMI_AUDIO_CT_MLP     0xc -#define HDMI_AUDIO_CT_DST     0xd -#define HDMI_AUDIO_CT_WMA_PRO 0xe -#define HDMI_AUDIO_CT_CXT     0xf - -#define HDMI_AUDIO_SF_STREAM 0x0 /* specified by audio stream */ -#define HDMI_AUIDO_SF_32K    0x1 -#define HDMI_AUDIO_SF_44_1K  0x2 -#define HDMI_AUDIO_SF_48K    0x3 -#define HDMI_AUDIO_SF_88_2K  0x4 -#define HDMI_AUDIO_SF_96K    0x5 -#define HDMI_AUDIO_SF_176_4K 0x6 -#define HDMI_AUDIO_SF_192K   0x7 - -#define HDMI_AUDIO_SS_STREAM 0x0 /* specified by audio stream */ -#define HDMI_AUDIO_SS_16BIT  0x1 -#define HDMI_AUDIO_SS_20BIT  0x2 -#define HDMI_AUDIO_SS_24BIT  0x3 - -#define HDMI_AUDIO_CXT_CT            0x0 /* refer to coding in CT */ -#define HDMI_AUDIO_CXT_HE_AAC        0x1 -#define HDMI_AUDIO_CXT_HE_AAC_V2     0x2 -#define HDMI_AUDIO_CXT_MPEG_SURROUND 0x3 - -/* all fields little endian */ -struct hdmi_stereo_infoframe { -	/* PB0 */ -	u8 csum; - -	/* PB1 */ -	u8 regid0; - -	/* PB2 */ -	u8 regid1; - -	/* PB3 */ -	u8 regid2; - -	/* PB4 */ -	unsigned res1:5; -	unsigned hdmi_video_format:3; - -	/* PB5 */ -	unsigned res2:4; -	unsigned _3d_structure:4; - -	/* PB6*/ -	unsigned res3:4; -	unsigned _3d_ext_data:4; -} __packed; - -#define HDMI_VENDOR_VERSION 0x01 -  /* register definitions */  #define HDMI_CTXSW						0x00 diff --git a/drivers/gpu/drm/tegra/host1x.c b/drivers/gpu/drm/tegra/host1x.c index 5d17b113a6f..92e25a7e00e 100644 --- a/drivers/gpu/drm/tegra/host1x.c +++ b/drivers/gpu/drm/tegra/host1x.c @@ -139,9 +139,9 @@ static int tegra_host1x_probe(struct platform_device *pdev)  	host1x->irq = err; -	host1x->regs = devm_request_and_ioremap(&pdev->dev, regs); -	if (!host1x->regs) { -		err = -EADDRNOTAVAIL; +	host1x->regs = devm_ioremap_resource(&pdev->dev, regs); +	if (IS_ERR(host1x->regs)) { +		err = PTR_ERR(host1x->regs);  		goto err;  	} diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig new file mode 100644 index 00000000000..d24d0401347 --- /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 && ARM +	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 00000000000..deda656b10e --- /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 00000000000..5dd3c7d031d --- /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 00000000000..c5b592dc197 --- /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 00000000000..8242b5a4307 --- /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 00000000000..580b74e2022 --- /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 00000000000..7db40aacc74 --- /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 00000000000..17fd1b45428 --- /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 00000000000..568dc1c08e6 --- /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 00000000000..2f850484832 --- /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 00000000000..58d487ba241 --- /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 00000000000..5b800f1f6aa --- /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/ttm/ttm_bo_util.c b/drivers/gpu/drm/ttm/ttm_bo_util.c index 44420fca7df..8be35c809c7 100644 --- a/drivers/gpu/drm/ttm/ttm_bo_util.c +++ b/drivers/gpu/drm/ttm/ttm_bo_util.c @@ -429,7 +429,7 @@ static int ttm_buffer_object_transfer(struct ttm_buffer_object *bo,  	struct ttm_bo_device *bdev = bo->bdev;  	struct ttm_bo_driver *driver = bdev->driver; -	fbo = kzalloc(sizeof(*fbo), GFP_KERNEL); +	fbo = kmalloc(sizeof(*fbo), GFP_KERNEL);  	if (!fbo)  		return -ENOMEM; @@ -448,7 +448,12 @@ static int ttm_buffer_object_transfer(struct ttm_buffer_object *bo,  	fbo->vm_node = NULL;  	atomic_set(&fbo->cpu_writers, 0); -	fbo->sync_obj = driver->sync_obj_ref(bo->sync_obj); +	spin_lock(&bdev->fence_lock); +	if (bo->sync_obj) +		fbo->sync_obj = driver->sync_obj_ref(bo->sync_obj); +	else +		fbo->sync_obj = NULL; +	spin_unlock(&bdev->fence_lock);  	kref_init(&fbo->list_kref);  	kref_init(&fbo->kref);  	fbo->destroy = &ttm_transfered_destroy; @@ -661,13 +666,11 @@ int ttm_bo_move_accel_cleanup(struct ttm_buffer_object *bo,  		 */  		set_bit(TTM_BO_PRIV_FLAG_MOVING, &bo->priv_flags); - -		/* ttm_buffer_object_transfer accesses bo->sync_obj */ -		ret = ttm_buffer_object_transfer(bo, &ghost_obj);  		spin_unlock(&bdev->fence_lock);  		if (tmp_obj)  			driver->sync_obj_unref(&tmp_obj); +		ret = ttm_buffer_object_transfer(bo, &ghost_obj);  		if (ret)  			return ret; diff --git a/drivers/gpu/drm/ttm/ttm_tt.c b/drivers/gpu/drm/ttm/ttm_tt.c index 7d759a43029..5e93a52d4f2 100644 --- a/drivers/gpu/drm/ttm/ttm_tt.c +++ b/drivers/gpu/drm/ttm/ttm_tt.c @@ -296,7 +296,7 @@ int ttm_tt_swapin(struct ttm_tt *ttm)  	swap_storage = ttm->swap_storage;  	BUG_ON(swap_storage == NULL); -	swap_space = swap_storage->f_path.dentry->d_inode->i_mapping; +	swap_space = file_inode(swap_storage)->i_mapping;  	for (i = 0; i < ttm->num_pages; ++i) {  		from_page = shmem_read_mapping_page(swap_space, i); @@ -345,7 +345,7 @@ int ttm_tt_swapout(struct ttm_tt *ttm, struct file *persistent_swap_storage)  	} else  		swap_storage = persistent_swap_storage; -	swap_space = swap_storage->f_path.dentry->d_inode->i_mapping; +	swap_space = file_inode(swap_storage)->i_mapping;  	for (i = 0; i < ttm->num_pages; ++i) {  		from_page = ttm->pages[i]; diff --git a/drivers/gpu/drm/udl/Kconfig b/drivers/gpu/drm/udl/Kconfig index 56e0bf31d42..6222af19f45 100644 --- a/drivers/gpu/drm/udl/Kconfig +++ b/drivers/gpu/drm/udl/Kconfig @@ -1,6 +1,6 @@  config DRM_UDL  	tristate "DisplayLink" -	depends on DRM && EXPERIMENTAL +	depends on DRM  	depends on USB_ARCH_HAS_HCD  	select DRM_USB  	select FB_SYS_FILLRECT diff --git a/drivers/gpu/drm/udl/udl_fb.c b/drivers/gpu/drm/udl/udl_fb.c index b9feec9d08d..9f4be3d4a02 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/drm/udl/udl_gem.c b/drivers/gpu/drm/udl/udl_gem.c index afd212c9921..3816270ba49 100644 --- a/drivers/gpu/drm/udl/udl_gem.c +++ b/drivers/gpu/drm/udl/udl_gem.c @@ -137,7 +137,7 @@ static int udl_gem_get_pages(struct udl_gem_object *obj, gfp_t gfpmask)  	if (obj->pages == NULL)  		return -ENOMEM; -	inode = obj->base.filp->f_path.dentry->d_inode; +	inode = file_inode(obj->base.filp);  	mapping = inode->i_mapping;  	gfpmask |= mapping_gfp_mask(mapping); diff --git a/drivers/gpu/drm/via/via_map.c b/drivers/gpu/drm/via/via_map.c index c0f1cc7f5ca..d0ab3fb32ac 100644 --- a/drivers/gpu/drm/via/via_map.c +++ b/drivers/gpu/drm/via/via_map.c @@ -120,7 +120,6 @@ int via_driver_unload(struct drm_device *dev)  {  	drm_via_private_t *dev_priv = dev->dev_private; -	idr_remove_all(&dev_priv->object_idr);  	idr_destroy(&dev_priv->object_idr);  	kfree(dev_priv); diff --git a/drivers/gpu/drm/via/via_mm.c b/drivers/gpu/drm/via/via_mm.c index 0d55432e02a..0ab93ff0987 100644 --- a/drivers/gpu/drm/via/via_mm.c +++ b/drivers/gpu/drm/via/via_mm.c @@ -148,17 +148,10 @@ int via_mem_alloc(struct drm_device *dev, void *data,  	if (retval)  		goto fail_alloc; -again: -	if (idr_pre_get(&dev_priv->object_idr, GFP_KERNEL) == 0) { -		retval = -ENOMEM; -		goto fail_idr; -	} - -	retval = idr_get_new_above(&dev_priv->object_idr, item, 1, &user_key); -	if (retval == -EAGAIN) -		goto again; -	if (retval) +	retval = idr_alloc(&dev_priv->object_idr, item, 1, 0, GFP_KERNEL); +	if (retval < 0)  		goto fail_idr; +	user_key = retval;  	list_add(&item->owner_list, &file_priv->obj_list);  	mutex_unlock(&dev->struct_mutex); diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c index 16556170fb3..bc784254e78 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c @@ -177,17 +177,16 @@ int vmw_resource_alloc_id(struct vmw_resource *res)  	BUG_ON(res->id != -1); -	do { -		if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0)) -			return -ENOMEM; - -		write_lock(&dev_priv->resource_lock); -		ret = idr_get_new_above(idr, res, 1, &res->id); -		write_unlock(&dev_priv->resource_lock); +	idr_preload(GFP_KERNEL); +	write_lock(&dev_priv->resource_lock); -	} while (ret == -EAGAIN); +	ret = idr_alloc(idr, res, 1, 0, GFP_NOWAIT); +	if (ret >= 0) +		res->id = ret; -	return ret; +	write_unlock(&dev_priv->resource_lock); +	idr_preload_end(); +	return ret < 0 ? ret : 0;  }  /**  |