From 536e1715226c94037df12f7c6280cbe0f6009f92 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 5 Nov 2014 11:43:26 +0100 Subject: [PATCH 01/54] gpu: host1x: Call ->remove() only when a device is bound When a driver's ->probe() function fails, the host1x bus must not call its ->remove() function because the driver will already have cleaned up in the error handling path in ->probe(). Signed-off-by: Thierry Reding --- drivers/gpu/host1x/bus.c | 9 +++++++-- include/linux/host1x.h | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c index aaf54859adb0..e4182e68e29c 100644 --- a/drivers/gpu/host1x/bus.c +++ b/drivers/gpu/host1x/bus.c @@ -116,7 +116,10 @@ static void host1x_subdev_register(struct host1x_device *device, if (list_empty(&device->subdevs)) { err = device->driver->probe(device); if (err < 0) - dev_err(&device->dev, "probe failed: %d\n", err); + dev_err(&device->dev, "probe failed for %ps: %d\n", + device->driver, err); + else + device->bound = true; } } @@ -130,10 +133,12 @@ static void __host1x_subdev_unregister(struct host1x_device *device, * If all subdevices have been activated, we're about to remove the * first active subdevice, so unload the driver first. */ - if (list_empty(&device->subdevs)) { + if (list_empty(&device->subdevs) && device->bound) { err = device->driver->remove(device); if (err < 0) dev_err(&device->dev, "remove failed: %d\n", err); + + device->bound = false; } /* diff --git a/include/linux/host1x.h b/include/linux/host1x.h index bb9840fd1e18..7890b553d12e 100644 --- a/include/linux/host1x.h +++ b/include/linux/host1x.h @@ -272,6 +272,8 @@ struct host1x_device { struct mutex clients_lock; struct list_head clients; + + bool bound; }; static inline struct host1x_device *to_host1x_device(struct device *dev) From 38d98de4332fcdaa72fc83443c1e3268e4b2214b Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 18 Dec 2014 15:06:56 +0100 Subject: [PATCH 02/54] gpu: host1x: Call host1x_device_add() under lock Instead of locking within host1x_device_add(), call it under the lock to make the locking more consistent. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/bus.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c index e4182e68e29c..769116dba797 100644 --- a/drivers/gpu/host1x/bus.c +++ b/drivers/gpu/host1x/bus.c @@ -323,9 +323,7 @@ static int host1x_device_add(struct host1x *host1x, return err; } - mutex_lock(&host1x->devices_lock); list_add_tail(&device->list, &host1x->devices); - mutex_unlock(&host1x->devices_lock); mutex_lock(&clients_lock); @@ -414,11 +412,11 @@ static void host1x_attach_driver(struct host1x *host1x, } } - mutex_unlock(&host1x->devices_lock); - err = host1x_device_add(host1x, driver); if (err < 0) dev_err(host1x->dev, "failed to allocate device: %d\n", err); + + mutex_unlock(&host1x->devices_lock); } static void host1x_detach_driver(struct host1x *host1x, From 99d2cd81d7261e6ddd325189134faf752206bfe7 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 18 Dec 2014 15:26:02 +0100 Subject: [PATCH 03/54] gpu: host1x: Factor out __host1x_device_del() This function is needed in several places, so factor it out. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/bus.c | 93 +++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c index 769116dba797..0b52f0ea8871 100644 --- a/drivers/gpu/host1x/bus.c +++ b/drivers/gpu/host1x/bus.c @@ -279,10 +279,59 @@ void host1x_bus_exit(void) bus_unregister(&host1x_bus_type); } +static void __host1x_device_del(struct host1x_device *device) +{ + struct host1x_subdev *subdev, *sd; + struct host1x_client *client, *cl; + + mutex_lock(&device->subdevs_lock); + + /* unregister subdevices */ + list_for_each_entry_safe(subdev, sd, &device->active, list) { + /* + * host1x_subdev_unregister() will remove the client from + * any lists, so we'll need to manually add it back to the + * list of idle clients. + * + * XXX: Alternatively, perhaps don't remove the client from + * any lists in host1x_subdev_unregister() and instead do + * that explicitly from host1x_unregister_client()? + */ + client = subdev->client; + + __host1x_subdev_unregister(device, subdev); + + /* add the client to the list of idle clients */ + mutex_lock(&clients_lock); + list_add_tail(&client->list, &clients); + mutex_unlock(&clients_lock); + } + + /* remove subdevices */ + list_for_each_entry_safe(subdev, sd, &device->subdevs, list) + host1x_subdev_del(subdev); + + mutex_unlock(&device->subdevs_lock); + + /* move clients to idle list */ + mutex_lock(&clients_lock); + mutex_lock(&device->clients_lock); + + list_for_each_entry_safe(client, cl, &device->clients, list) + list_move_tail(&client->list, &clients); + + mutex_unlock(&device->clients_lock); + mutex_unlock(&clients_lock); + + /* finally remove the device */ + list_del_init(&device->list); +} + static void host1x_device_release(struct device *dev) { struct host1x_device *device = to_host1x_device(dev); + __host1x_device_del(device); kfree(device); } @@ -350,50 +399,6 @@ static int host1x_device_add(struct host1x *host1x, static void host1x_device_del(struct host1x *host1x, struct host1x_device *device) { - struct host1x_subdev *subdev, *sd; - struct host1x_client *client, *cl; - - mutex_lock(&device->subdevs_lock); - - /* unregister subdevices */ - list_for_each_entry_safe(subdev, sd, &device->active, list) { - /* - * host1x_subdev_unregister() will remove the client from - * any lists, so we'll need to manually add it back to the - * list of idle clients. - * - * XXX: Alternatively, perhaps don't remove the client from - * any lists in host1x_subdev_unregister() and instead do - * that explicitly from host1x_unregister_client()? - */ - client = subdev->client; - - __host1x_subdev_unregister(device, subdev); - - /* add the client to the list of idle clients */ - mutex_lock(&clients_lock); - list_add_tail(&client->list, &clients); - mutex_unlock(&clients_lock); - } - - /* remove subdevices */ - list_for_each_entry_safe(subdev, sd, &device->subdevs, list) - host1x_subdev_del(subdev); - - mutex_unlock(&device->subdevs_lock); - - /* move clients to idle list */ - mutex_lock(&clients_lock); - mutex_lock(&device->clients_lock); - - list_for_each_entry_safe(client, cl, &device->clients, list) - list_move_tail(&client->list, &clients); - - mutex_unlock(&device->clients_lock); - mutex_unlock(&clients_lock); - - /* finally remove the device */ - list_del_init(&device->list); device_unregister(&device->dev); } From f4c5cf88fbd50e4779042268947b2e2f90c20484 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 18 Dec 2014 15:29:14 +0100 Subject: [PATCH 04/54] gpu: host1x: Provide a proper struct bus_type Previously the struct bus_type exported by the host1x infrastructure was only a very basic skeleton. Turn that implementation into a more full- fledged bus to support proper probe ordering and power management. Note that the bus infrastructure needs to be available before any of the drivers can be registered. This is automatically ensured if all drivers are built as loadable modules (via symbol dependencies). If all drivers are built-in there are no such guarantees and the link order determines the initcall ordering. Adjust drivers/gpu/Makefile to make sure that the host1x bus infrastructure is initialized prior to any of its users (only drm/tegra currently). v2: Fix building host1x and tegra-drm as modules Reported-by: Dave Airlie Reviewed-by: Sean Paul Reviewed-by: Mark Zhang Signed-off-by: Thierry Reding --- drivers/gpu/Makefile | 5 +- drivers/gpu/drm/tegra/drm.c | 4 +- drivers/gpu/host1x/bus.c | 119 ++++++++++++++++++++++++------------ drivers/gpu/host1x/bus.h | 4 +- drivers/gpu/host1x/dev.c | 6 +- include/linux/host1x.h | 18 +++++- 6 files changed, 107 insertions(+), 49 deletions(-) diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile index 70da9eb52a42..e9ed439a5b65 100644 --- a/drivers/gpu/Makefile +++ b/drivers/gpu/Makefile @@ -1,3 +1,6 @@ -obj-y += drm/ vga/ +# drm/tegra depends on host1x, so if both drivers are built-in care must be +# taken to initialize them in the correct order. Link order is the only way +# to ensure this currently. obj-$(CONFIG_TEGRA_HOST1X) += host1x/ +obj-y += drm/ vga/ obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/ diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index d4f827593dfa..a0c18ae79029 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -912,7 +912,9 @@ static const struct of_device_id host1x_drm_subdevs[] = { }; static struct host1x_driver host1x_drm_driver = { - .name = "drm", + .driver = { + .name = "drm", + }, .probe = host1x_drm_probe, .remove = host1x_drm_remove, .subdevs = host1x_drm_subdevs, diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c index 0b52f0ea8871..4a99c6416e6a 100644 --- a/drivers/gpu/host1x/bus.c +++ b/drivers/gpu/host1x/bus.c @@ -72,13 +72,14 @@ static void host1x_subdev_del(struct host1x_subdev *subdev) /** * host1x_device_parse_dt() - scan device tree and add matching subdevices */ -static int host1x_device_parse_dt(struct host1x_device *device) +static int host1x_device_parse_dt(struct host1x_device *device, + struct host1x_driver *driver) { struct device_node *np; int err; for_each_child_of_node(device->dev.parent->of_node, np) { - if (of_match_node(device->driver->subdevs, np) && + if (of_match_node(driver->subdevs, np) && of_device_is_available(np)) { err = host1x_subdev_add(device, np); if (err < 0) @@ -109,17 +110,12 @@ static void host1x_subdev_register(struct host1x_device *device, mutex_unlock(&device->clients_lock); mutex_unlock(&device->subdevs_lock); - /* - * When all subdevices have been registered, the composite device is - * ready to be probed. - */ if (list_empty(&device->subdevs)) { - err = device->driver->probe(device); + err = device_add(&device->dev); if (err < 0) - dev_err(&device->dev, "probe failed for %ps: %d\n", - device->driver, err); + dev_err(&device->dev, "failed to add: %d\n", err); else - device->bound = true; + device->registered = true; } } @@ -127,18 +123,16 @@ static void __host1x_subdev_unregister(struct host1x_device *device, struct host1x_subdev *subdev) { struct host1x_client *client = subdev->client; - int err; /* * If all subdevices have been activated, we're about to remove the * first active subdevice, so unload the driver first. */ - if (list_empty(&device->subdevs) && device->bound) { - err = device->driver->remove(device); - if (err < 0) - dev_err(&device->dev, "remove failed: %d\n", err); - - device->bound = false; + if (list_empty(&device->subdevs)) { + if (device->registered) { + device->registered = false; + device_del(&device->dev); + } } /* @@ -265,19 +259,59 @@ static int host1x_del_client(struct host1x *host1x, return -ENODEV; } -static struct bus_type host1x_bus_type = { - .name = "host1x", +static int host1x_device_match(struct device *dev, struct device_driver *drv) +{ + return strcmp(dev_name(dev), drv->name) == 0; +} + +static int host1x_device_probe(struct device *dev) +{ + struct host1x_driver *driver = to_host1x_driver(dev->driver); + struct host1x_device *device = to_host1x_device(dev); + + if (driver->probe) + return driver->probe(device); + + return 0; +} + +static int host1x_device_remove(struct device *dev) +{ + struct host1x_driver *driver = to_host1x_driver(dev->driver); + struct host1x_device *device = to_host1x_device(dev); + + if (driver->remove) + return driver->remove(device); + + return 0; +} + +static void host1x_device_shutdown(struct device *dev) +{ + struct host1x_driver *driver = to_host1x_driver(dev->driver); + struct host1x_device *device = to_host1x_device(dev); + + if (driver->shutdown) + driver->shutdown(device); +} + +static const struct dev_pm_ops host1x_device_pm_ops = { + .suspend = pm_generic_suspend, + .resume = pm_generic_resume, + .freeze = pm_generic_freeze, + .thaw = pm_generic_thaw, + .poweroff = pm_generic_poweroff, + .restore = pm_generic_restore, }; -int host1x_bus_init(void) -{ - return bus_register(&host1x_bus_type); -} - -void host1x_bus_exit(void) -{ - bus_unregister(&host1x_bus_type); -} +struct bus_type host1x_bus_type = { + .name = "host1x", + .match = host1x_device_match, + .probe = host1x_device_probe, + .remove = host1x_device_remove, + .shutdown = host1x_device_shutdown, + .pm = &host1x_device_pm_ops, +}; static void __host1x_device_del(struct host1x_device *device) { @@ -347,6 +381,8 @@ static int host1x_device_add(struct host1x *host1x, if (!device) return -ENOMEM; + device_initialize(&device->dev); + mutex_init(&device->subdevs_lock); INIT_LIST_HEAD(&device->subdevs); INIT_LIST_HEAD(&device->active); @@ -357,18 +393,14 @@ static int host1x_device_add(struct host1x *host1x, device->dev.coherent_dma_mask = host1x->dev->coherent_dma_mask; device->dev.dma_mask = &device->dev.coherent_dma_mask; + dev_set_name(&device->dev, "%s", driver->driver.name); device->dev.release = host1x_device_release; - dev_set_name(&device->dev, "%s", driver->name); device->dev.bus = &host1x_bus_type; device->dev.parent = host1x->dev; - err = device_register(&device->dev); - if (err < 0) - return err; - - err = host1x_device_parse_dt(device); + err = host1x_device_parse_dt(device, driver); if (err < 0) { - device_unregister(&device->dev); + kfree(device); return err; } @@ -399,7 +431,12 @@ static int host1x_device_add(struct host1x *host1x, static void host1x_device_del(struct host1x *host1x, struct host1x_device *device) { - device_unregister(&device->dev); + if (device->registered) { + device->registered = false; + device_del(&device->dev); + } + + put_device(&device->dev); } static void host1x_attach_driver(struct host1x *host1x, @@ -474,7 +511,8 @@ int host1x_unregister(struct host1x *host1x) return 0; } -int host1x_driver_register(struct host1x_driver *driver) +int host1x_driver_register_full(struct host1x_driver *driver, + struct module *owner) { struct host1x *host1x; @@ -491,9 +529,12 @@ int host1x_driver_register(struct host1x_driver *driver) mutex_unlock(&devices_lock); - return 0; + driver->driver.bus = &host1x_bus_type; + driver->driver.owner = owner; + + return driver_register(&driver->driver); } -EXPORT_SYMBOL(host1x_driver_register); +EXPORT_SYMBOL(host1x_driver_register_full); void host1x_driver_unregister(struct host1x_driver *driver) { diff --git a/drivers/gpu/host1x/bus.h b/drivers/gpu/host1x/bus.h index 4099e99212c8..88fb1c4aac68 100644 --- a/drivers/gpu/host1x/bus.h +++ b/drivers/gpu/host1x/bus.h @@ -18,10 +18,10 @@ #ifndef HOST1X_BUS_H #define HOST1X_BUS_H +struct bus_type; struct host1x; -int host1x_bus_init(void); -void host1x_bus_exit(void); +extern struct bus_type host1x_bus_type; int host1x_register(struct host1x *host1x); int host1x_unregister(struct host1x *host1x); diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c index 2529908d304b..53d3d1d45b48 100644 --- a/drivers/gpu/host1x/dev.c +++ b/drivers/gpu/host1x/dev.c @@ -216,7 +216,7 @@ static int __init tegra_host1x_init(void) { int err; - err = host1x_bus_init(); + err = bus_register(&host1x_bus_type); if (err < 0) return err; @@ -233,7 +233,7 @@ static int __init tegra_host1x_init(void) unregister_host1x: platform_driver_unregister(&tegra_host1x_driver); unregister_bus: - host1x_bus_exit(); + bus_unregister(&host1x_bus_type); return err; } module_init(tegra_host1x_init); @@ -242,7 +242,7 @@ static void __exit tegra_host1x_exit(void) { platform_driver_unregister(&tegra_mipi_driver); platform_driver_unregister(&tegra_host1x_driver); - host1x_bus_exit(); + bus_unregister(&host1x_bus_type); } module_exit(tegra_host1x_exit); diff --git a/include/linux/host1x.h b/include/linux/host1x.h index 7890b553d12e..464f33814a94 100644 --- a/include/linux/host1x.h +++ b/include/linux/host1x.h @@ -250,17 +250,29 @@ void host1x_job_unpin(struct host1x_job *job); struct host1x_device; struct host1x_driver { + struct device_driver driver; + const struct of_device_id *subdevs; struct list_head list; - const char *name; int (*probe)(struct host1x_device *device); int (*remove)(struct host1x_device *device); + void (*shutdown)(struct host1x_device *device); }; -int host1x_driver_register(struct host1x_driver *driver); +static inline struct host1x_driver * +to_host1x_driver(struct device_driver *driver) +{ + return container_of(driver, struct host1x_driver, driver); +} + +int host1x_driver_register_full(struct host1x_driver *driver, + struct module *owner); void host1x_driver_unregister(struct host1x_driver *driver); +#define host1x_driver_register(driver) \ + host1x_driver_register_full(driver, THIS_MODULE) + struct host1x_device { struct host1x_driver *driver; struct list_head list; @@ -273,7 +285,7 @@ struct host1x_device { struct mutex clients_lock; struct list_head clients; - bool bound; + bool registered; }; static inline struct host1x_device *to_host1x_device(struct device *dev) From 8c8cb58ed6429da3cd41c1b44770c7a4b1b33933 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 17 Dec 2014 16:46:37 +0100 Subject: [PATCH 05/54] drm/tegra: gem: Use iommu_map_sg() The iommu_map_sg() function is now available in the IOMMU API, so drop the open-coded variant. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/gem.c | 34 ++-------------------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index 8777b7f75791..1ccde09d01c8 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -92,36 +92,6 @@ static const struct host1x_bo_ops tegra_bo_ops = { .kunmap = tegra_bo_kunmap, }; -/* - * A generic iommu_map_sg() function is being reviewed and will hopefully be - * merged soon. At that point this function can be dropped in favour of the - * one provided by the IOMMU API. - */ -static ssize_t __iommu_map_sg(struct iommu_domain *domain, unsigned long iova, - struct scatterlist *sg, unsigned int nents, - int prot) -{ - struct scatterlist *s; - size_t offset = 0; - unsigned int i; - int err; - - for_each_sg(sg, s, nents, i) { - phys_addr_t phys = page_to_phys(sg_page(s)); - size_t length = s->offset + s->length; - - err = iommu_map(domain, iova + offset, phys, length, prot); - if (err < 0) { - iommu_unmap(domain, iova, offset); - return err; - } - - offset += length; - } - - return offset; -} - static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo) { int prot = IOMMU_READ | IOMMU_WRITE; @@ -144,8 +114,8 @@ static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo) bo->paddr = bo->mm->start; - err = __iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl, - bo->sgt->nents, prot); + err = iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl, + bo->sgt->nents, prot); if (err < 0) { dev_err(tegra->drm->dev, "failed to map buffer: %zd\n", err); goto remove; From aa942f6ac43ab7373a025fe54780c8a852f33348 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 10 Dec 2014 11:41:05 +0100 Subject: [PATCH 06/54] drm/tegra: Remove redundant zeroing out of memory The DRM core now zeroes out the memory associated with CRTC, encoder and connector objects upon cleanup, so there's no need to explicitly do that in drivers anymore. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 6 ------ drivers/gpu/drm/tegra/output.c | 12 ------------ 2 files changed, 18 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index ae26cc054fff..3a8a7c227051 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -895,15 +895,9 @@ static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, return 0; } -static void drm_crtc_clear(struct drm_crtc *crtc) -{ - memset(crtc, 0, sizeof(*crtc)); -} - static void tegra_dc_destroy(struct drm_crtc *crtc) { drm_crtc_cleanup(crtc); - drm_crtc_clear(crtc); } static const struct drm_crtc_funcs tegra_crtc_funcs = { diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 6a5c7b81fbc5..0e4042ce904f 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -98,16 +98,10 @@ tegra_connector_detect(struct drm_connector *connector, bool force) return status; } -static void drm_connector_clear(struct drm_connector *connector) -{ - memset(connector, 0, sizeof(*connector)); -} - static void tegra_connector_destroy(struct drm_connector *connector) { drm_connector_unregister(connector); drm_connector_cleanup(connector); - drm_connector_clear(connector); } static const struct drm_connector_funcs connector_funcs = { @@ -117,15 +111,9 @@ static const struct drm_connector_funcs connector_funcs = { .destroy = tegra_connector_destroy, }; -static void drm_encoder_clear(struct drm_encoder *encoder) -{ - memset(encoder, 0, sizeof(*encoder)); -} - static void tegra_encoder_destroy(struct drm_encoder *encoder) { drm_encoder_cleanup(encoder); - drm_encoder_clear(encoder); } static const struct drm_encoder_funcs encoder_funcs = { From 518e6227afe076a7facd9a9b5ce1d5ded98732d5 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 16 Dec 2014 18:04:08 +0100 Subject: [PATCH 07/54] drm/tegra: plane: Use proper possible_crtcs mask The possible_crtcs mask needs to be a mask of CRTC indices. There is no guarantee that the DRM indices match the hardware pipe number, so the mask must be computed from the CRTC index. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 3a8a7c227051..5bab53e0f990 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -435,6 +435,19 @@ static const struct drm_plane_funcs tegra_primary_plane_funcs = { static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, struct tegra_dc *dc) { + /* + * Ideally this would use drm_crtc_mask(), but that would require the + * CRTC to already be in the mode_config's list of CRTCs. However, it + * will only be added to that list in the drm_crtc_init_with_planes() + * (in tegra_dc_init()), which in turn requires registration of these + * planes. So we have ourselves a nice little chicken and egg problem + * here. + * + * We work around this by manually creating the mask from the number + * of CRTCs that have been registered, and should therefore always be + * the same as drm_crtc_index() after registration. + */ + unsigned long possible_crtcs = 1 << drm->mode_config.num_crtc; struct tegra_plane *plane; unsigned int num_formats; const u32 *formats; @@ -447,7 +460,7 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, num_formats = ARRAY_SIZE(tegra_primary_plane_formats); formats = tegra_primary_plane_formats; - err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe, + err = drm_universal_plane_init(drm, &plane->base, possible_crtcs, &tegra_primary_plane_funcs, formats, num_formats, DRM_PLANE_TYPE_PRIMARY); if (err < 0) { From bf19b885f956eb0fec7dd7c02400b679dca7312e Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 4 Dec 2014 14:00:35 +0300 Subject: [PATCH 08/54] drm/tegra: Check for NULL pointer instead of IS_ERR() iommu_domain_alloc() returns NULL on error, it never returns error pointers. Signed-off-by: Dan Carpenter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index a0c18ae79029..219f314cc162 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -36,8 +36,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) if (iommu_present(&platform_bus_type)) { tegra->domain = iommu_domain_alloc(&platform_bus_type); - if (IS_ERR(tegra->domain)) { - err = PTR_ERR(tegra->domain); + if (!tegra->domain) { + err = -ENOMEM; goto free; } From 42d0659ba76956665c704d2a7cee9fa0f54a7acf Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 8 Dec 2014 15:45:39 +0100 Subject: [PATCH 09/54] drm/tegra: dc: Initialize border color Tegra114 and earlier support specifying the color of the border (i.e. the active area of the screen that is not covered by any of the overlay windows). By default this is set to a light blue, so set it to black to comply with the requirements set by atomic modesetting. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 5bab53e0f990..dbb2d00268b2 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -21,6 +21,7 @@ #include struct tegra_dc_soc_info { + bool supports_border_color; bool supports_interlacing; bool supports_cursor; bool supports_block_linear; @@ -1036,6 +1037,9 @@ static int tegra_crtc_mode_set(struct drm_crtc *crtc, /* program display mode */ tegra_dc_set_timings(dc, mode); + if (dc->soc->supports_border_color) + tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR); + /* interlacing isn't supported yet, so disable it */ if (dc->soc->supports_interlacing) { value = tegra_dc_readl(dc, DC_DISP_INTERLACE_CONTROL); @@ -1578,6 +1582,7 @@ static const struct host1x_client_ops dc_client_ops = { }; static const struct tegra_dc_soc_info tegra20_dc_soc_info = { + .supports_border_color = true, .supports_interlacing = false, .supports_cursor = false, .supports_block_linear = false, @@ -1586,6 +1591,7 @@ static const struct tegra_dc_soc_info tegra20_dc_soc_info = { }; static const struct tegra_dc_soc_info tegra30_dc_soc_info = { + .supports_border_color = true, .supports_interlacing = false, .supports_cursor = false, .supports_block_linear = false, @@ -1594,6 +1600,7 @@ static const struct tegra_dc_soc_info tegra30_dc_soc_info = { }; static const struct tegra_dc_soc_info tegra114_dc_soc_info = { + .supports_border_color = true, .supports_interlacing = false, .supports_cursor = false, .supports_block_linear = false, @@ -1602,6 +1609,7 @@ static const struct tegra_dc_soc_info tegra114_dc_soc_info = { }; static const struct tegra_dc_soc_info tegra124_dc_soc_info = { + .supports_border_color = false, .supports_interlacing = true, .supports_cursor = true, .supports_block_linear = true, From d700ba7a6657d7c2be76b56996f54f02783456a6 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 8 Dec 2014 15:50:04 +0100 Subject: [PATCH 10/54] drm/tegra: dc: Describe register copies Most of the display controller's registers are double-buffered, a few of them are triple-buffered. The ASSEMBLY shadow copy is latched intto the ACTIVE copy for double-buffered registers. For triple-buffered registers the ASSEMBLY copy is first latched into the ARM copy. Latching into the ACTIVE copy happens immediately if the controller is inactive. Otherwise the latching happens on the next frame boundary. The latching of the ASSEMBLY into the ARM copy happens immediately. Latching is controlled by a set of *_ACT_REQ and *_UPDATE bits in the DC_CMD_STATE_CONTROL register. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index dbb2d00268b2..a56fccc428d5 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -53,6 +53,18 @@ static void tegra_dc_cursor_commit(struct tegra_dc *dc) tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); } +/* + * Double-buffered registers have two copies: ASSEMBLY and ACTIVE. When the + * *_ACT_REQ bits are set the ASSEMBLY copy is latched into the ACTIVE copy. + * Latching happens mmediately if the display controller is in STOP mode or + * on the next frame boundary otherwise. + * + * Triple-buffered registers have three copies: ASSEMBLY, ARM and ACTIVE. The + * ASSEMBLY copy is latched into the ARM copy immediately after *_UPDATE bits + * are written. When the *_ACT_REQ bits are written, the ARM copy is latched + * into the ACTIVE copy, either immediately if the display controller is in + * STOP mode, or at the next frame boundary otherwise. + */ static void tegra_dc_commit(struct tegra_dc *dc) { tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); From fb35c6b60e6db1e8ae0d7d358b76f3f511bf2707 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 8 Dec 2014 15:55:28 +0100 Subject: [PATCH 11/54] drm/tegra: dc: Return planar flag for non-YUV modes This prevents the compiler from warning about using a variable that is possibly uninitialized. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index a56fccc428d5..c2ab1a2da6a3 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -134,6 +134,9 @@ static bool tegra_dc_format_is_yuv(unsigned int format, bool *planar) return true; } + if (planar) + *planar = false; + return false; } From 4ee8cee0c5d149939664b25d8fea63ad76493e30 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 8 Dec 2014 16:25:14 +0100 Subject: [PATCH 12/54] drm/tegra: hdmi: Registers are 32-bit Use a sized unsigned 32-bit data type (u32) to store register contents. The HDMI registers are 32 bits wide irrespective of the architecture's data width. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/hdmi.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index ffe26547328d..0f122eae7c64 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -31,7 +31,7 @@ struct tegra_hdmi_config { unsigned int num_tmds; unsigned long fuse_override_offset; - unsigned long fuse_override_value; + u32 fuse_override_value; bool has_sor_io_peak_current; }; @@ -85,16 +85,16 @@ enum { HDA, }; -static inline unsigned long tegra_hdmi_readl(struct tegra_hdmi *hdmi, - unsigned long reg) +static inline u32 tegra_hdmi_readl(struct tegra_hdmi *hdmi, + unsigned long offset) { - return readl(hdmi->regs + (reg << 2)); + return readl(hdmi->regs + (offset << 2)); } -static inline void tegra_hdmi_writel(struct tegra_hdmi *hdmi, unsigned long val, - unsigned long reg) +static inline void tegra_hdmi_writel(struct tegra_hdmi *hdmi, u32 value, + unsigned long offset) { - writel(val, hdmi->regs + (reg << 2)); + writel(value, hdmi->regs + (offset << 2)); } struct tegra_hdmi_audio_config { @@ -455,8 +455,8 @@ static void tegra_hdmi_setup_audio_fs_tables(struct tegra_hdmi *hdmi) for (i = 0; i < ARRAY_SIZE(freqs); i++) { unsigned int f = freqs[i]; unsigned int eight_half; - unsigned long value; unsigned int delta; + u32 value; if (f > 96000) delta = 2; @@ -477,7 +477,7 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk) struct device_node *node = hdmi->dev->of_node; const struct tegra_hdmi_audio_config *config; unsigned int offset = 0; - unsigned long value; + u32 value; switch (hdmi->audio_source) { case HDA: @@ -571,9 +571,9 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk) return 0; } -static inline unsigned long tegra_hdmi_subpack(const u8 *ptr, size_t size) +static inline u32 tegra_hdmi_subpack(const u8 *ptr, size_t size) { - unsigned long value = 0; + u32 value = 0; size_t i; for (i = size; i > 0; i--) @@ -587,8 +587,8 @@ static void tegra_hdmi_write_infopack(struct tegra_hdmi *hdmi, const void *data, { const u8 *ptr = data; unsigned long offset; - unsigned long value; size_t i, j; + u32 value; switch (ptr[0]) { case HDMI_INFOFRAME_TYPE_AVI: @@ -707,9 +707,9 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi) static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi) { struct hdmi_vendor_infoframe frame; - unsigned long value; u8 buffer[10]; ssize_t err; + u32 value; if (!hdmi->stereo) { value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); @@ -738,7 +738,7 @@ static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi) static void tegra_hdmi_setup_tmds(struct tegra_hdmi *hdmi, const struct tmds_config *tmds) { - unsigned long value; + u32 value; tegra_hdmi_writel(hdmi, tmds->pll0, HDMI_NV_PDISP_SOR_PLL0); tegra_hdmi_writel(hdmi, tmds->pll1, HDMI_NV_PDISP_SOR_PLL1); @@ -776,8 +776,8 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) struct tegra_hdmi *hdmi = to_hdmi(output); struct device_node *node = hdmi->dev->of_node; unsigned int pulse_start, div82, pclk; - unsigned long value; int retries = 1000; + u32 value; int err; if (hdmi->enabled) @@ -1011,7 +1011,7 @@ static int tegra_output_hdmi_disable(struct tegra_output *output) { struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct tegra_hdmi *hdmi = to_hdmi(output); - unsigned long value; + u32 value; if (!hdmi->enabled) return 0; @@ -1117,8 +1117,8 @@ static int tegra_hdmi_show_regs(struct seq_file *s, void *data) return err; #define DUMP_REG(name) \ - seq_printf(s, "%-56s %#05x %08lx\n", #name, name, \ - tegra_hdmi_readl(hdmi, name)) + seq_printf(s, "%-56s %#05x %08x\n", #name, name, \ + tegra_hdmi_readl(hdmi, name)) DUMP_REG(HDMI_CTXSW); DUMP_REG(HDMI_NV_PDISP_SOR_STATE0); From 9c0b4ca1123e5b05daeabaa1879ff1fcebd5f9e1 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 24 Nov 2014 12:27:59 +0100 Subject: [PATCH 13/54] drm/tegra: dsi: Registers are 32-bit Use a sized unsigned 32-bit data type (u32) to store register contents. The DSI registers are 32 bits wide irrespective of the architecture's data width. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 33f67fd601c6..e967ae1d6ca6 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -77,13 +77,12 @@ static inline struct tegra_dsi *to_dsi(struct tegra_output *output) return container_of(output, struct tegra_dsi, output); } -static inline unsigned long tegra_dsi_readl(struct tegra_dsi *dsi, - unsigned long reg) +static inline u32 tegra_dsi_readl(struct tegra_dsi *dsi, unsigned long reg) { return readl(dsi->regs + (reg << 2)); } -static inline void tegra_dsi_writel(struct tegra_dsi *dsi, unsigned long value, +static inline void tegra_dsi_writel(struct tegra_dsi *dsi, u32 value, unsigned long reg) { writel(value, dsi->regs + (reg << 2)); @@ -95,7 +94,7 @@ static int tegra_dsi_show_regs(struct seq_file *s, void *data) struct tegra_dsi *dsi = node->info_ent->data; #define DUMP_REG(name) \ - seq_printf(s, "%-32s %#05x %08lx\n", #name, name, \ + seq_printf(s, "%-32s %#05x %08x\n", #name, name, \ tegra_dsi_readl(dsi, name)) DUMP_REG(DSI_INCR_SYNCPT); @@ -341,7 +340,8 @@ static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = { static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) { struct mipi_dphy_timing timing; - unsigned long value, period; + unsigned long period; + u32 value; long rate; int err; @@ -728,7 +728,7 @@ static int tegra_output_dsi_disable(struct tegra_output *output) { struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct tegra_dsi *dsi = to_dsi(output); - unsigned long value; + u32 value; int err; if (!dsi->enabled) @@ -888,7 +888,7 @@ static const struct tegra_output_ops dsi_ops = { static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) { - unsigned long value; + u32 value; value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0); From 92f0e073ed213a1af673a9ee414339feb9738809 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 24 Nov 2014 16:29:40 +0100 Subject: [PATCH 14/54] drm/tegra: dsi: Soft-reset controller on ->disable This reset is necessary to properly clean up the internal state of the controller. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index e967ae1d6ca6..60b802205546 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -724,6 +724,30 @@ static void tegra_dsi_disable(struct tegra_dsi *dsi) usleep_range(5000, 10000); } +static void tegra_dsi_soft_reset(struct tegra_dsi *dsi) +{ + u32 value; + + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value &= ~DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + usleep_range(300, 1000); + + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value |= DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + usleep_range(300, 1000); + + value = tegra_dsi_readl(dsi, DSI_TRIGGER); + if (value) + tegra_dsi_writel(dsi, 0, DSI_TRIGGER); + + if (dsi->slave) + tegra_dsi_soft_reset(dsi->slave); +} + static int tegra_output_dsi_disable(struct tegra_output *output) { struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); @@ -762,6 +786,7 @@ static int tegra_output_dsi_disable(struct tegra_output *output) if (err < 0) dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err); + tegra_dsi_soft_reset(dsi); tegra_dsi_disable(dsi); dsi->enabled = false; From 201106d83ee4dc54342e4d8d31a202bf6711a25e Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 24 Nov 2014 16:31:48 +0100 Subject: [PATCH 15/54] drm/tegra: dsi: Reset across ->exit()/->init() This allows a DRM driver unload/reload cycle to completely reset the DSI controller and may help in situations where it's broken. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 60b802205546..748727bc175b 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -948,6 +948,14 @@ static int tegra_dsi_init(struct host1x_client *client) struct tegra_dsi *dsi = host1x_client_to_dsi(client); int err; + reset_control_deassert(dsi->rst); + + err = tegra_dsi_pad_calibrate(dsi); + if (err < 0) { + dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); + goto reset; + } + /* Gangsters must not register their own outputs. */ if (!dsi->master) { dsi->output.type = TEGRA_OUTPUT_DSI; @@ -968,6 +976,10 @@ static int tegra_dsi_init(struct host1x_client *client) } return 0; + +reset: + reset_control_assert(dsi->rst); + return err; } static int tegra_dsi_exit(struct host1x_client *client) @@ -997,6 +1009,8 @@ static int tegra_dsi_exit(struct host1x_client *client) } } + reset_control_assert(dsi->rst); + return 0; } @@ -1423,13 +1437,6 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (IS_ERR(dsi->rst)) return PTR_ERR(dsi->rst); - err = reset_control_deassert(dsi->rst); - if (err < 0) { - dev_err(&pdev->dev, "failed to bring DSI out of reset: %d\n", - err); - return err; - } - dsi->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(dsi->clk)) { dev_err(&pdev->dev, "cannot get DSI clock\n"); @@ -1495,12 +1502,6 @@ static int tegra_dsi_probe(struct platform_device *pdev) goto disable_vdd; } - err = tegra_dsi_pad_calibrate(dsi); - if (err < 0) { - dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); - goto mipi_free; - } - dsi->host.ops = &tegra_dsi_host_ops; dsi->host.dev = &pdev->dev; From 2dafd63682d0e2be20b913f370d9f29eb0d483f7 Mon Sep 17 00:00:00 2001 From: David Ung Date: Fri, 5 Dec 2014 15:30:05 -0800 Subject: [PATCH 16/54] drm/tegra: dsi: Adjust D-PHY timing Compliance testing shows that HS Trail is off by -12%. Increase the HS Trail time to make this test pass. Signed-off-by: David Ung [treding@nvidia.com: update specification references, add comment] Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/mipi-phy.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/tegra/mipi-phy.c b/drivers/gpu/drm/tegra/mipi-phy.c index 486d19d589c8..ba2ae6511957 100644 --- a/drivers/gpu/drm/tegra/mipi-phy.c +++ b/drivers/gpu/drm/tegra/mipi-phy.c @@ -12,9 +12,9 @@ #include "mipi-phy.h" /* - * Default D-PHY timings based on MIPI D-PHY specification. Derived from - * the valid ranges specified in Section 5.9 of the D-PHY specification - * with minor adjustments. + * Default D-PHY timings based on MIPI D-PHY specification. Derived from the + * valid ranges specified in Section 6.9, Table 14, Page 40 of the D-PHY + * specification (v1.2) with minor adjustments. */ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, unsigned long period) @@ -34,7 +34,20 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, timing->hszero = 145 + 5 * period; timing->hssettle = 85 + 6 * period; timing->hsskip = 40; - timing->hstrail = max(8 * period, 60 + 4 * period); + + /* + * The MIPI D-PHY specification (Section 6.9, v1.2, Table 14, Page 40) + * contains this formula as: + * + * T_HS-TRAIL = max(n * 8 * period, 60 + n * 4 * period) + * + * where n = 1 for forward-direction HS mode and n = 4 for reverse- + * direction HS mode. There's only one setting and this function does + * not parameterize on anything other that period, so this code will + * assumes that reverse-direction HS mode is supported and uses n = 4. + */ + timing->hstrail = max(4 * 8 * period, 60 + 4 * 4 * period); + timing->init = 100000; timing->lpx = 60; timing->taget = 5 * timing->lpx; @@ -46,8 +59,8 @@ int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, } /* - * Validate D-PHY timing according to MIPI Alliance Specification for D-PHY, - * Section 5.9 "Global Operation Timing Parameters". + * Validate D-PHY timing according to MIPI D-PHY specification (v1.2, Section + * Section 6.9 "Global Operation Timing Parameters"). */ int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing, unsigned long period) From 8643bc6d8e3701734a86d2ae2da32bbfaf1d4535 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 7 Jan 2015 14:01:26 +0300 Subject: [PATCH 17/54] drm/tegra: dc: Fix bad irqsave/restore in tegra_dc_finish_page_flip() We can't save two values to the IRQ flags at the same time so the IRQs are not enabled at the end. This kind of bug is easy to miss in testing if the function is normally called with IRQs disabled so we wouldn't enable IRQs anyway. Signed-off-by: Dan Carpenter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index c2ab1a2da6a3..aca886e03b28 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -867,7 +867,7 @@ static void tegra_dc_finish_page_flip(struct tegra_dc *dc) bo = tegra_fb_get_plane(crtc->primary->fb, 0); - spin_lock_irqsave(&dc->lock, flags); + spin_lock(&dc->lock); /* check if new start address has been latched */ tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER); @@ -875,7 +875,7 @@ static void tegra_dc_finish_page_flip(struct tegra_dc *dc) base = tegra_dc_readl(dc, DC_WINBUF_START_ADDR); tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS); - spin_unlock_irqrestore(&dc->lock, flags); + spin_unlock(&dc->lock); if (base == bo->paddr + crtc->primary->fb->offsets[0]) { drm_crtc_send_vblank_event(crtc, dc->event); From 2cb207e413a9ace502edbce51c604e2088b0acb2 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 16 Jan 2015 13:43:42 +0300 Subject: [PATCH 18/54] drm/tegra: gem: oops in error handling kfree(ERR_PTR(-ENOMEM)) will not work very well. Signed-off-by: Dan Carpenter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/gem.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index 1ccde09d01c8..cfb481943b6b 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -214,10 +214,8 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo) for_each_sg(sgt->sgl, s, sgt->nents, i) sg_dma_address(s) = sg_phys(s); - if (dma_map_sg(drm->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE) == 0) { - sgt = ERR_PTR(-ENOMEM); + if (dma_map_sg(drm->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE) == 0) goto release_sgt; - } bo->sgt = sgt; @@ -226,6 +224,7 @@ static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo) release_sgt: sg_free_table(sgt); kfree(sgt); + sgt = ERR_PTR(-ENOMEM); put_pages: drm_gem_put_pages(&bo->gem, bo->pages, false, false); return PTR_ERR(sgt); From 3cad4b6887cd3d2b4e7f2c0ad6cb557b8eda4084 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 25 Nov 2014 13:05:12 +0100 Subject: [PATCH 19/54] drm/plane: Make ->atomic_update() mandatory There is no use-case where it would be useful for drivers not to implement this function and the transitional plane helpers already require drivers to provide an implementation. v2: add new requirement to kerneldoc Reviewed-by: Daniel Vetter Signed-off-by: Thierry Reding --- drivers/gpu/drm/drm_atomic_helper.c | 2 +- include/drm/drm_plane_helper.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 541ba833ed36..6c72c57b2244 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1108,7 +1108,7 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev, funcs = plane->helper_private; - if (!funcs || !funcs->atomic_update) + if (!funcs) continue; old_plane_state = old_state->plane_states[i]; diff --git a/include/drm/drm_plane_helper.h b/include/drm/drm_plane_helper.h index a185392cafeb..06d0314a1352 100644 --- a/include/drm/drm_plane_helper.h +++ b/include/drm/drm_plane_helper.h @@ -52,7 +52,7 @@ extern int drm_crtc_init(struct drm_device *dev, * @prepare_fb: prepare a framebuffer for use by the plane * @cleanup_fb: cleanup a framebuffer when it's no longer used by the plane * @atomic_check: check that a given atomic state is valid and can be applied - * @atomic_update: apply an atomic state to the plane + * @atomic_update: apply an atomic state to the plane (mandatory) * * The helper operations are called by the mid-layer CRTC helper. */ From 407b8bd9f5d284ffa13a9f9a709e6289bb4ae47e Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 20 Nov 2014 12:05:50 +0100 Subject: [PATCH 20/54] drm/plane: Add optional ->atomic_disable() callback In order to prevent drivers from having to perform the same checks over and over again, add an optional ->atomic_disable callback which the core calls under the right circumstances. v2: pass old state and detect edges to avoid calling ->atomic_disable on already disabled planes, remove redundant comment (Daniel Vetter) v3: rename helper to drm_atomic_plane_disabling() to clarify that it is checking for transitions, move helper to drm_atomic_helper.h, clarify check for !old_state and its relation to transitional helpers Here's an extract from some discussion rationalizing the behaviour (for a full version, see the reference below): > > Hm, thinking about this some more this will result in a slight difference > > in behaviour, at least when drivers just use the helper ->reset functions > > but don't disable everything: > > - With transitional helpers we assume we know nothing and call > > ->atomic_disable. > > - With atomic old_state->crtc == NULL in the same situation right after > > boot-up, but we asssume the plane is really off and _dont_ call > > ->atomic_disable. > > > > Should we instead check for (old_state && old_state->crtc) and state that > > drivers need to make sure they don't have stuff hanging around? > > I don't think we can check for old_state because otherwise this will > always return false, whereas we really want it to force-disable planes > that could be on (lacking any more accurate information). For > transitional helpers anyway. > > For the atomic helpers, old_state will never be NULL, but I'd assume > that the driver would reconstruct the current state in ->reset(). By the way, the reason for why old_state can be NULL with transitional helpers is the ordering of the steps in the atomic transition. Currently the Tegra patches do this (based on your blog post and the Exynos proto- type): 1) atomic conversion, phase 1: - implement ->atomic_{check,update,disable}() - use drm_plane_helper_{update,disable}() 2) atomic conversion, phase 2: - call drm_mode_config_reset() from ->load() - implement ->reset() That's only a partial list of what's done in these steps, but that's the only relevant pieces for why old_state is NULL. What happens is that without ->reset() implemented there won't be any initial state, hence plane->state (the old_state here) will be NULL the first time atomic state is applied. We could of course reorder the sequence such that drivers are required to hook up ->reset() before they can (or at the same as they) hook up the transitional helpers. We could add an appropriate WARN_ON to this helper to make that more obvious. However, that will not solve the problem because it only gets rid of the special case. We still don't know whether old_state->crtc == NULL is the current state or just the initial default. So no matter which way we do this, I don't see a way to get away without requiring specific semantics from drivers. They would be that: - drivers recreate the correct state in ->reset() so that old_state->crtc != NULL if the plane is really enabled or - drivers have to ensure that the real state in fact mirrors the initial default as encoded in the state (plane disabled) References: http://lists.freedesktop.org/archives/dri-devel/2015-January/075578.html Reviewed-by: Daniel Vetter Reviewed-by: Gustavo Padovan Signed-off-by: Thierry Reding --- drivers/gpu/drm/drm_atomic_helper.c | 9 ++++++- drivers/gpu/drm/drm_plane_helper.c | 10 +++++++- include/drm/drm_atomic_helper.h | 37 +++++++++++++++++++++++++++++ include/drm/drm_plane_helper.h | 3 +++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 6c72c57b2244..af5f539ed147 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1113,7 +1113,14 @@ void drm_atomic_helper_commit_planes(struct drm_device *dev, old_plane_state = old_state->plane_states[i]; - funcs->atomic_update(plane, old_plane_state); + /* + * Special-case disabling the plane if drivers support it. + */ + if (drm_atomic_plane_disabling(plane, old_plane_state) && + funcs->atomic_disable) + funcs->atomic_disable(plane, old_plane_state); + else + funcs->atomic_update(plane, old_plane_state); } for (i = 0; i < ncrtcs; i++) { diff --git a/drivers/gpu/drm/drm_plane_helper.c b/drivers/gpu/drm/drm_plane_helper.c index f24c4cfe674b..4dcdcad9f16d 100644 --- a/drivers/gpu/drm/drm_plane_helper.c +++ b/drivers/gpu/drm/drm_plane_helper.c @@ -449,7 +449,15 @@ int drm_plane_helper_commit(struct drm_plane *plane, crtc_funcs[i]->atomic_begin(crtc[i]); } - plane_funcs->atomic_update(plane, plane_state); + /* + * Drivers may optionally implement the ->atomic_disable callback, so + * special-case that here. + */ + if (drm_atomic_plane_disabling(plane, plane_state) && + plane_funcs->atomic_disable) + plane_funcs->atomic_disable(plane, plane_state); + else + plane_funcs->atomic_update(plane, plane_state); for (i = 0; i < 2; i++) { if (crtc_funcs[i] && crtc_funcs[i]->atomic_flush) diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h index 2095917ff8c7..a0ea4ded3cb5 100644 --- a/include/drm/drm_atomic_helper.h +++ b/include/drm/drm_atomic_helper.h @@ -127,4 +127,41 @@ void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector, #define drm_atomic_crtc_state_for_each_plane(plane, crtc_state) \ drm_for_each_plane_mask(plane, (crtc_state)->state->dev, (crtc_state)->plane_mask) +/* + * drm_atomic_plane_disabling - check whether a plane is being disabled + * @plane: plane object + * @old_state: previous atomic state + * + * Checks the atomic state of a plane to determine whether it's being disabled + * or not. This also WARNs if it detects an invalid state (both CRTC and FB + * need to either both be NULL or both be non-NULL). + * + * RETURNS: + * True if the plane is being disabled, false otherwise. + */ +static inline bool +drm_atomic_plane_disabling(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + /* + * When disabling a plane, CRTC and FB should always be NULL together. + * Anything else should be considered a bug in the atomic core, so we + * gently warn about it. + */ + WARN_ON((plane->state->crtc == NULL && plane->state->fb != NULL) || + (plane->state->crtc != NULL && plane->state->fb == NULL)); + + /* + * When using the transitional helpers, old_state may be NULL. If so, + * we know nothing about the current state and have to assume that it + * might be enabled. + * + * When using the atomic helpers, old_state won't be NULL. Therefore + * this check assumes that either the driver will have reconstructed + * the correct state in ->reset() or that the driver will have taken + * appropriate measures to disable all planes. + */ + return (!old_state || old_state->crtc) && !plane->state->crtc; +} + #endif /* DRM_ATOMIC_HELPER_H_ */ diff --git a/include/drm/drm_plane_helper.h b/include/drm/drm_plane_helper.h index 06d0314a1352..31c11d36fae6 100644 --- a/include/drm/drm_plane_helper.h +++ b/include/drm/drm_plane_helper.h @@ -53,6 +53,7 @@ extern int drm_crtc_init(struct drm_device *dev, * @cleanup_fb: cleanup a framebuffer when it's no longer used by the plane * @atomic_check: check that a given atomic state is valid and can be applied * @atomic_update: apply an atomic state to the plane (mandatory) + * @atomic_disable: disable the plane * * The helper operations are called by the mid-layer CRTC helper. */ @@ -66,6 +67,8 @@ struct drm_plane_helper_funcs { struct drm_plane_state *state); void (*atomic_update)(struct drm_plane *plane, struct drm_plane_state *old_state); + void (*atomic_disable)(struct drm_plane *plane, + struct drm_plane_state *old_state); }; static inline void drm_plane_helper_add(struct drm_plane *plane, From 4cd4df8080a3e0d9b5a75dd52fa2133738def213 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 3 Dec 2014 16:44:34 +0100 Subject: [PATCH 21/54] drm/atomic: Add ->atomic_check() to encoder helpers This callback can be used instead of the legacy ->mode_fixup() and is passed the CRTC and connector states. It can thus use these states to validate the modeset and cache values in the state to be used during the actual modeset. Reviewed-by: Daniel Vetter Signed-off-by: Thierry Reding --- drivers/gpu/drm/drm_atomic_helper.c | 23 ++++++++++++++++------- include/drm/drm_crtc_helper.h | 6 ++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index af5f539ed147..24c44c24dabe 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -297,13 +297,22 @@ mode_fixup(struct drm_atomic_state *state) } } - - ret = funcs->mode_fixup(encoder, &crtc_state->mode, - &crtc_state->adjusted_mode); - if (!ret) { - DRM_DEBUG_KMS("[ENCODER:%d:%s] fixup failed\n", - encoder->base.id, encoder->name); - return -EINVAL; + if (funcs->atomic_check) { + ret = funcs->atomic_check(encoder, crtc_state, + conn_state); + if (ret) { + DRM_DEBUG_KMS("[ENCODER:%d:%s] check failed\n", + encoder->base.id, encoder->name); + return ret; + } + } else { + ret = funcs->mode_fixup(encoder, &crtc_state->mode, + &crtc_state->adjusted_mode); + if (!ret) { + DRM_DEBUG_KMS("[ENCODER:%d:%s] fixup failed\n", + encoder->base.id, encoder->name); + return -EINVAL; + } } } diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h index e76828d81a8b..5810c027acdc 100644 --- a/include/drm/drm_crtc_helper.h +++ b/include/drm/drm_crtc_helper.h @@ -115,6 +115,7 @@ struct drm_crtc_helper_funcs { * @get_crtc: return CRTC that the encoder is currently attached to * @detect: connection status detection * @disable: disable encoder when not in use (overrides DPMS off) + * @atomic_check: check for validity of an atomic update * * The helper operations are called by the mid-layer CRTC helper. */ @@ -137,6 +138,11 @@ struct drm_encoder_helper_funcs { struct drm_connector *connector); /* disable encoder when not in use - more explicit than dpms off */ void (*disable)(struct drm_encoder *encoder); + + /* atomic helpers */ + int (*atomic_check)(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state); }; /** From 62b9e06321a254c3039966cff831487498e831a5 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 21 Nov 2014 17:33:33 +0100 Subject: [PATCH 22/54] drm/tegra: Use tegra_commit_dc() in output drivers All output drivers have open-coded variants of this function, so export it to remove some code duplication. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 2 +- drivers/gpu/drm/tegra/drm.h | 1 + drivers/gpu/drm/tegra/dsi.c | 6 ++---- drivers/gpu/drm/tegra/hdmi.c | 6 ++---- drivers/gpu/drm/tegra/rgb.c | 8 +++----- drivers/gpu/drm/tegra/sor.c | 6 ++---- 6 files changed, 11 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index aca886e03b28..dab7ea261e74 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -65,7 +65,7 @@ static void tegra_dc_cursor_commit(struct tegra_dc *dc) * into the ACTIVE copy, either immediately if the display controller is in * STOP mode, or at the next frame boundary otherwise. */ -static void tegra_dc_commit(struct tegra_dc *dc) +void tegra_dc_commit(struct tegra_dc *dc) { tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 3a3b2e7b5b3f..5a0e96debcb1 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -177,6 +177,7 @@ struct tegra_dc_window { void tegra_dc_enable_vblank(struct tegra_dc *dc); void tegra_dc_disable_vblank(struct tegra_dc *dc); void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file); +void tegra_dc_commit(struct tegra_dc *dc); struct tegra_output_ops { int (*enable)(struct tegra_output *output); diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 748727bc175b..0ca8ca3775fd 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -658,8 +658,7 @@ static int tegra_output_dsi_enable(struct tegra_output *output) PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + tegra_dc_commit(dc); /* enable DSI controller */ tegra_dsi_enable(dsi); @@ -778,8 +777,7 @@ static int tegra_output_dsi_disable(struct tegra_output *output) value &= ~DSI_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + tegra_dc_commit(dc); } err = tegra_dsi_wait_idle(dsi, 100); diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 0f122eae7c64..f118b914293e 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -997,8 +997,7 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + tegra_dc_commit(dc); /* TODO: add HDCP support */ @@ -1042,8 +1041,7 @@ static int tegra_output_hdmi_disable(struct tegra_output *output) value &= ~HDMI_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + tegra_dc_commit(dc); } clk_disable_unprepare(hdmi->clk); diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index d6af9be48f42..3b851abbccae 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -123,8 +123,7 @@ static int tegra_output_rgb_enable(struct tegra_output *output) PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + tegra_dc_commit(rgb->dc); rgb->enabled = true; @@ -148,11 +147,10 @@ static int tegra_output_rgb_disable(struct tegra_output *output) value &= ~DISP_CTRL_MODE_MASK; tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_COMMAND); - tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); - tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable)); + tegra_dc_commit(rgb->dc); + rgb->enabled = false; return 0; diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 7829e81f065d..6a341822abe9 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -267,8 +267,7 @@ static int tegra_sor_wakeup(struct tegra_sor *sor) PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + tegra_dc_commit(dc); timeout = jiffies + msecs_to_jiffies(250); @@ -1080,8 +1079,7 @@ static int tegra_output_sor_disable(struct tegra_output *output) value &= ~SOR_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + tegra_dc_commit(dc); } err = tegra_sor_power_down(sor); From 36904adf217ab0755cc2ef3fa186e01fd07a2aca Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 21 Nov 2014 17:35:54 +0100 Subject: [PATCH 23/54] drm/tegra: Stop CRTC at CRTC disable time Previously output drivers would all stop the display controller in their disable path. However with the transition to atomic modesetting the display controller needs to be kept running until all planes have been disabled so that software can properly determine (using VBLANK counts) when it is safe to remove the framebuffers associated with the planes. Moving this code into the display controller's disable path also gets rid of the duplication of this into all output drivers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 6 ++++++ drivers/gpu/drm/tegra/dsi.c | 4 ---- drivers/gpu/drm/tegra/hdmi.c | 4 ---- drivers/gpu/drm/tegra/rgb.c | 4 ---- drivers/gpu/drm/tegra/sor.c | 4 ---- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index dab7ea261e74..915bbdc350ac 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -940,6 +940,7 @@ static void tegra_crtc_disable(struct drm_crtc *crtc) struct tegra_dc *dc = to_tegra_dc(crtc); struct drm_device *drm = crtc->dev; struct drm_plane *plane; + u32 value; drm_for_each_legacy_plane(plane, &drm->mode_config.plane_list) { if (plane->crtc == crtc) { @@ -953,6 +954,11 @@ static void tegra_crtc_disable(struct drm_crtc *crtc) } } + /* stop the display controller */ + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + drm_crtc_vblank_off(crtc); tegra_dc_commit(dc); } diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 0ca8ca3775fd..4c0dfe92402a 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -769,10 +769,6 @@ static int tegra_output_dsi_disable(struct tegra_output *output) PW4_ENABLE | PM0_ENABLE | PM1_ENABLE); tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); - value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value &= ~DSI_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index f118b914293e..d4c635148cc7 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -1033,10 +1033,6 @@ static int tegra_output_hdmi_disable(struct tegra_output *output) tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); */ - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); - value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value &= ~HDMI_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index 3b851abbccae..39b8d5fe04b2 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -143,10 +143,6 @@ static int tegra_output_rgb_disable(struct tegra_output *output) PW4_ENABLE | PM0_ENABLE | PM1_ENABLE); tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_COMMAND); - tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable)); tegra_dc_commit(rgb->dc); diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 6a341822abe9..1fe801ee8eb0 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -1071,10 +1071,6 @@ static int tegra_output_sor_disable(struct tegra_output *output) tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); */ - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); - value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value &= ~SOR_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); From 86df256f301fc5f215160d8c8e7093f605d40a14 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 8 Dec 2014 16:03:53 +0100 Subject: [PATCH 24/54] drm/tegra: dc: Wait for idle when disabled When disabling the display controller, stop it and wait for it to become idle. Doing so ensures that no further accesses to the framebuffer occur and the buffers can be safely unmapped or freed. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 70 +++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 915bbdc350ac..f8ad5ed6e0e7 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -53,6 +53,26 @@ static void tegra_dc_cursor_commit(struct tegra_dc *dc) tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); } +/* + * Reads the active copy of a register. This takes the dc->lock spinlock to + * prevent races with the VBLANK processing which also needs access to the + * active copy of some registers. + */ +static u32 tegra_dc_readl_active(struct tegra_dc *dc, unsigned long offset) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&dc->lock, flags); + + tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS); + value = tegra_dc_readl(dc, offset); + tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS); + + spin_unlock_irqrestore(&dc->lock, flags); + return value; +} + /* * Double-buffered registers have two copies: ASSEMBLY and ACTIVE. When the * *_ACT_REQ bits are set the ASSEMBLY copy is latched into the ACTIVE copy. @@ -935,12 +955,47 @@ static const struct drm_crtc_funcs tegra_crtc_funcs = { .destroy = tegra_dc_destroy, }; +static void tegra_dc_stop(struct tegra_dc *dc) +{ + u32 value; + + /* stop the display controller */ + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + + tegra_dc_commit(dc); +} + +static bool tegra_dc_idle(struct tegra_dc *dc) +{ + u32 value; + + value = tegra_dc_readl_active(dc, DC_CMD_DISPLAY_COMMAND); + + return (value & DISP_CTRL_MODE_MASK) == 0; +} + +static int tegra_dc_wait_idle(struct tegra_dc *dc, unsigned long timeout) +{ + timeout = jiffies + msecs_to_jiffies(timeout); + + while (time_before(jiffies, timeout)) { + if (tegra_dc_idle(dc)) + return 0; + + usleep_range(1000, 2000); + } + + dev_dbg(dc->dev, "timeout waiting for DC to become idle\n"); + return -ETIMEDOUT; +} + static void tegra_crtc_disable(struct drm_crtc *crtc) { struct tegra_dc *dc = to_tegra_dc(crtc); struct drm_device *drm = crtc->dev; struct drm_plane *plane; - u32 value; drm_for_each_legacy_plane(plane, &drm->mode_config.plane_list) { if (plane->crtc == crtc) { @@ -954,10 +1009,15 @@ static void tegra_crtc_disable(struct drm_crtc *crtc) } } - /* stop the display controller */ - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + if (!tegra_dc_idle(dc)) { + tegra_dc_stop(dc); + + /* + * Ignore the return value, there isn't anything useful to do + * in case this fails. + */ + tegra_dc_wait_idle(dc, 100); + } drm_crtc_vblank_off(crtc); tegra_dc_commit(dc); From f99142149095b3172c255fc792e8ebb84f38f182 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 26 Nov 2014 13:03:57 +0100 Subject: [PATCH 25/54] drm/tegra: Move tegra_drm_mode_funcs to the core This structure will be extended using non-framebuffer related callbacks in subsequent patches, so it should move to a more central location. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.c | 15 +++++++++++++++ drivers/gpu/drm/tegra/drm.h | 4 ++++ drivers/gpu/drm/tegra/fb.c | 25 ++++--------------------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 219f314cc162..e29d9146b7c7 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -24,6 +24,13 @@ struct tegra_drm_file { struct list_head contexts; }; +static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { + .fb_create = tegra_fb_create, +#ifdef CONFIG_DRM_TEGRA_FBDEV + .output_poll_changed = tegra_fb_output_poll_changed, +#endif +}; + static int tegra_drm_load(struct drm_device *drm, unsigned long flags) { struct host1x_device *device = to_host1x_device(drm->dev); @@ -52,6 +59,14 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) drm_mode_config_init(drm); + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + + drm->mode_config.max_width = 4096; + drm->mode_config.max_height = 4096; + + drm->mode_config.funcs = &tegra_drm_mode_funcs; + err = tegra_drm_fb_prepare(drm); if (err < 0) goto config; diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 5a0e96debcb1..286a970a4432 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -292,12 +292,16 @@ struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer); int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer, struct tegra_bo_tiling *tiling); +struct drm_framebuffer *tegra_fb_create(struct drm_device *drm, + struct drm_file *file, + struct drm_mode_fb_cmd2 *cmd); int tegra_drm_fb_prepare(struct drm_device *drm); void tegra_drm_fb_free(struct drm_device *drm); int tegra_drm_fb_init(struct drm_device *drm); void tegra_drm_fb_exit(struct drm_device *drm); #ifdef CONFIG_DRM_TEGRA_FBDEV void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev); +void tegra_fb_output_poll_changed(struct drm_device *drm); #endif extern struct platform_driver tegra_dc_driver; diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c index e9c715d89261..397fb34d5d5b 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -129,9 +129,9 @@ static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm, return fb; } -static struct drm_framebuffer *tegra_fb_create(struct drm_device *drm, - struct drm_file *file, - struct drm_mode_fb_cmd2 *cmd) +struct drm_framebuffer *tegra_fb_create(struct drm_device *drm, + struct drm_file *file, + struct drm_mode_fb_cmd2 *cmd) { unsigned int hsub, vsub, i; struct tegra_bo *planes[4]; @@ -377,7 +377,7 @@ void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->base); } -static void tegra_fb_output_poll_changed(struct drm_device *drm) +void tegra_fb_output_poll_changed(struct drm_device *drm) { struct tegra_drm *tegra = drm->dev_private; @@ -386,28 +386,11 @@ static void tegra_fb_output_poll_changed(struct drm_device *drm) } #endif -static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { - .fb_create = tegra_fb_create, -#ifdef CONFIG_DRM_TEGRA_FBDEV - .output_poll_changed = tegra_fb_output_poll_changed, -#endif -}; - int tegra_drm_fb_prepare(struct drm_device *drm) { #ifdef CONFIG_DRM_TEGRA_FBDEV struct tegra_drm *tegra = drm->dev_private; -#endif - drm->mode_config.min_width = 0; - drm->mode_config.min_height = 0; - - drm->mode_config.max_width = 4096; - drm->mode_config.max_height = 4096; - - drm->mode_config.funcs = &tegra_drm_mode_funcs; - -#ifdef CONFIG_DRM_TEGRA_FBDEV tegra->fbdev = tegra_fbdev_create(drm); if (IS_ERR(tegra->fbdev)) return PTR_ERR(tegra->fbdev); From 50a246aa13b80c8310dc5a82be639a60868f667e Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 17 Nov 2014 15:14:48 +0100 Subject: [PATCH 26/54] drm/tegra: dc: No longer disable planes at CRTC disable The DRM core should take care of disabling all unneeded planes, so there is no need to do this explicitly. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index f8ad5ed6e0e7..d51173056a9e 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -994,20 +994,6 @@ static int tegra_dc_wait_idle(struct tegra_dc *dc, unsigned long timeout) static void tegra_crtc_disable(struct drm_crtc *crtc) { struct tegra_dc *dc = to_tegra_dc(crtc); - struct drm_device *drm = crtc->dev; - struct drm_plane *plane; - - drm_for_each_legacy_plane(plane, &drm->mode_config.plane_list) { - if (plane->crtc == crtc) { - tegra_window_plane_disable(plane); - plane->crtc = NULL; - - if (plane->fb) { - drm_framebuffer_unreference(plane->fb); - plane->fb = NULL; - } - } - } if (!tegra_dc_idle(dc)) { tegra_dc_stop(dc); From 132085d84fca65c46b56dc0f8233910906a15e8d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 28 Nov 2014 15:38:40 +0100 Subject: [PATCH 27/54] drm/tegra: Convert output midlayer to helpers The output layer was initially designed to help reduce the amount of code duplicated in output drivers. An unfortunate side-effect of that was that it turned into a midlayer and it became difficult to make the output drivers work without bending over backwards to fit into the midlayer. This commit starts to convert the midlayer into a helper library by exporting most of the common functions so that they can be used by the output drivers directly. Doing so will allow output drivers to reuse common code paths but more easily override them where necessary. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.h | 9 +++++++++ drivers/gpu/drm/tegra/output.c | 24 ++++++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 286a970a4432..bf749ac4a344 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -272,6 +272,15 @@ int tegra_output_remove(struct tegra_output *output); int tegra_output_init(struct drm_device *drm, struct tegra_output *output); int tegra_output_exit(struct tegra_output *output); +int tegra_output_connector_get_modes(struct drm_connector *connector); +struct drm_encoder * +tegra_output_connector_best_encoder(struct drm_connector *connector); +enum drm_connector_status +tegra_output_connector_detect(struct drm_connector *connector, bool force); +void tegra_output_connector_destroy(struct drm_connector *connector); + +void tegra_output_encoder_destroy(struct drm_encoder *encoder); + /* from dpaux.c */ struct tegra_dpaux; struct drm_dp_link; diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 0e4042ce904f..54f8392a7c3c 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -12,7 +12,7 @@ #include #include "drm.h" -static int tegra_connector_get_modes(struct drm_connector *connector) +int tegra_output_connector_get_modes(struct drm_connector *connector) { struct tegra_output *output = connector_to_output(connector); struct edid *edid = NULL; @@ -57,8 +57,8 @@ static int tegra_connector_mode_valid(struct drm_connector *connector, return status; } -static struct drm_encoder * -tegra_connector_best_encoder(struct drm_connector *connector) +struct drm_encoder * +tegra_output_connector_best_encoder(struct drm_connector *connector) { struct tegra_output *output = connector_to_output(connector); @@ -66,13 +66,13 @@ tegra_connector_best_encoder(struct drm_connector *connector) } static const struct drm_connector_helper_funcs connector_helper_funcs = { - .get_modes = tegra_connector_get_modes, + .get_modes = tegra_output_connector_get_modes, .mode_valid = tegra_connector_mode_valid, - .best_encoder = tegra_connector_best_encoder, + .best_encoder = tegra_output_connector_best_encoder, }; -static enum drm_connector_status -tegra_connector_detect(struct drm_connector *connector, bool force) +enum drm_connector_status +tegra_output_connector_detect(struct drm_connector *connector, bool force) { struct tegra_output *output = connector_to_output(connector); enum drm_connector_status status = connector_status_unknown; @@ -98,7 +98,7 @@ tegra_connector_detect(struct drm_connector *connector, bool force) return status; } -static void tegra_connector_destroy(struct drm_connector *connector) +void tegra_output_connector_destroy(struct drm_connector *connector) { drm_connector_unregister(connector); drm_connector_cleanup(connector); @@ -106,18 +106,18 @@ static void tegra_connector_destroy(struct drm_connector *connector) static const struct drm_connector_funcs connector_funcs = { .dpms = drm_helper_connector_dpms, - .detect = tegra_connector_detect, + .detect = tegra_output_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = tegra_connector_destroy, + .destroy = tegra_output_connector_destroy, }; -static void tegra_encoder_destroy(struct drm_encoder *encoder) +void tegra_output_encoder_destroy(struct drm_encoder *encoder) { drm_encoder_cleanup(encoder); } static const struct drm_encoder_funcs encoder_funcs = { - .destroy = tegra_encoder_destroy, + .destroy = tegra_output_encoder_destroy, }; static void tegra_encoder_dpms(struct drm_encoder *encoder, int mode) From d5bae6f33ee98cf4c6939c4b8db2fc76c1eed720 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 2 Dec 2014 15:12:28 +0100 Subject: [PATCH 28/54] drm/tegra: output: Make ->setup_clock() optional In order to transition output drivers to using the struct tegra_output as a helper rather than midlayer, make this callback optional. Instead drivers should implement the equivalent as part of ->mode_fixup(). For the conversion to atomic modesetting a new callback ->atomic_check() should be implemented that updates the display controller's state with the corresponding parent clock, rate and shift clock divider. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 10 +++++++++- drivers/gpu/drm/tegra/drm.h | 10 ---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index d51173056a9e..e35e10758556 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1066,11 +1066,19 @@ static int tegra_crtc_setup_clk(struct drm_crtc *crtc, if (!output) return -ENODEV; + /* + * The ->setup_clock() callback is optional, but if encoders don't + * implement it they most likely need to do the equivalent within the + * ->mode_fixup() callback. + */ + if (!output->ops || !output->ops->setup_clock) + return 0; + /* * This assumes that the parent clock is pll_d_out0 or pll_d2_out * respectively, each of which divides the base pll_d by 2. */ - err = tegra_output_setup_clock(output, dc->clk, pclk, &div); + err = output->ops->setup_clock(output, dc->clk, pclk, &div); if (err < 0) { dev_err(dc->dev, "failed to setup clock: %ld\n", err); return err; diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index bf749ac4a344..d7433976a40b 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -240,16 +240,6 @@ static inline int tegra_output_disable(struct tegra_output *output) return output ? -ENOSYS : -EINVAL; } -static inline int tegra_output_setup_clock(struct tegra_output *output, - struct clk *clk, unsigned long pclk, - unsigned int *div) -{ - if (output && output->ops && output->ops->setup_clock) - return output->ops->setup_clock(output, clk, pclk, div); - - return output ? -ENOSYS : -EINVAL; -} - static inline int tegra_output_check_mode(struct tegra_output *output, struct drm_display_mode *mode, enum drm_mode_status *status) From c5a107d3279734c3599136696b6790add9e8e798 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 2 Dec 2014 15:15:06 +0100 Subject: [PATCH 29/54] drm/tegra: Add tegra_dc_setup_clock() helper This is a small helper that performs the basic steps required by all output drivers to prepare the display controller for use with a given encoder. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 20 ++++++++++++++++++++ drivers/gpu/drm/tegra/drm.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index e35e10758556..90909883e739 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1092,6 +1092,26 @@ static int tegra_crtc_setup_clk(struct drm_crtc *crtc, return 0; } +int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent, + unsigned long pclk, unsigned int div) +{ + u32 value; + int err; + + err = clk_set_parent(dc->clk, parent); + if (err < 0) { + dev_err(dc->dev, "failed to set parent clock: %d\n", err); + return err; + } + + DRM_DEBUG_KMS("rate: %lu, div: %u\n", clk_get_rate(dc->clk), div); + + value = SHIFT_CLK_DIVIDER(div) | PIXEL_CLK_DIVIDER_PCD1; + tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL); + + return 0; +} + static int tegra_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_display_mode *adjusted, diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index d7433976a40b..e1374ec2b76e 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -178,6 +178,8 @@ void tegra_dc_enable_vblank(struct tegra_dc *dc); void tegra_dc_disable_vblank(struct tegra_dc *dc); void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file); void tegra_dc_commit(struct tegra_dc *dc); +int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent, + unsigned long pclk, unsigned int div); struct tegra_output_ops { int (*enable)(struct tegra_output *output); From 3b0e58554873d1034beef737f15c7aa46492ff98 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 16 Dec 2014 18:30:16 +0100 Subject: [PATCH 30/54] drm/tegra: rgb: Demidlayer Implement encoder and connector within the RGB driver itself using the Tegra output helpers rather than using the Tegra output as midlayer. By doing so one level of indirection is removed and output drivers become more flexible while keeping the majority of the advantages provided by the common output helpers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 24 ++++ drivers/gpu/drm/tegra/drm.h | 1 - drivers/gpu/drm/tegra/output.c | 5 - drivers/gpu/drm/tegra/rgb.c | 243 +++++++++++++++++++-------------- 4 files changed, 161 insertions(+), 112 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 90909883e739..286cc8ce0c66 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -994,6 +994,7 @@ static int tegra_dc_wait_idle(struct tegra_dc *dc, unsigned long timeout) static void tegra_crtc_disable(struct drm_crtc *crtc) { struct tegra_dc *dc = to_tegra_dc(crtc); + u32 value; if (!tegra_dc_idle(dc)) { tegra_dc_stop(dc); @@ -1005,6 +1006,29 @@ static void tegra_crtc_disable(struct drm_crtc *crtc) tegra_dc_wait_idle(dc, 100); } + /* + * This should really be part of the RGB encoder driver, but clearing + * these bits has the side-effect of stopping the display controller. + * When that happens no VBLANK interrupts will be raised. At the same + * time the encoder is disabled before the display controller, so the + * above code is always going to timeout waiting for the controller + * to go idle. + * + * Given the close coupling between the RGB encoder and the display + * controller doing it here is still kind of okay. None of the other + * encoder drivers require these bits to be cleared. + * + * XXX: Perhaps given that the display controller is switched off at + * this point anyway maybe clearing these bits isn't even useful for + * the RGB encoder? + */ + if (dc->rgb) { + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); + value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE); + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + } + drm_crtc_vblank_off(crtc); tegra_dc_commit(dc); } diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index e1374ec2b76e..dbc1f83327ea 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -193,7 +193,6 @@ struct tegra_output_ops { }; enum tegra_output_type { - TEGRA_OUTPUT_RGB, TEGRA_OUTPUT_HDMI, TEGRA_OUTPUT_DSI, TEGRA_OUTPUT_EDP, diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 54f8392a7c3c..f01ddfaa4c99 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -274,11 +274,6 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output) int connector, encoder; switch (output->type) { - case TEGRA_OUTPUT_RGB: - connector = DRM_MODE_CONNECTOR_LVDS; - encoder = DRM_MODE_ENCODER_LVDS; - break; - case TEGRA_OUTPUT_HDMI: connector = DRM_MODE_CONNECTOR_HDMIA; encoder = DRM_MODE_ENCODER_TMDS; diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index 39b8d5fe04b2..30d7ae02ace8 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -9,6 +9,8 @@ #include +#include + #include "drm.h" #include "dc.h" @@ -85,13 +87,99 @@ static void tegra_dc_write_regs(struct tegra_dc *dc, tegra_dc_writel(dc, table[i].value, table[i].offset); } -static int tegra_output_rgb_enable(struct tegra_output *output) +static void tegra_rgb_connector_dpms(struct drm_connector *connector, + int mode) { - struct tegra_rgb *rgb = to_rgb(output); - unsigned long value; +} - if (rgb->enabled) - return 0; +static const struct drm_connector_funcs tegra_rgb_connector_funcs = { + .dpms = tegra_rgb_connector_dpms, + .detect = tegra_output_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = tegra_output_connector_destroy, +}; + +static enum drm_mode_status +tegra_rgb_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + /* + * FIXME: For now, always assume that the mode is okay. There are + * unresolved issues with clk_round_rate(), which doesn't always + * reliably report whether a frequency can be set or not. + */ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs tegra_rgb_connector_helper_funcs = { + .get_modes = tegra_output_connector_get_modes, + .mode_valid = tegra_rgb_connector_mode_valid, + .best_encoder = tegra_output_connector_best_encoder, +}; + +static const struct drm_encoder_funcs tegra_rgb_encoder_funcs = { + .destroy = tegra_output_encoder_destroy, +}; + +static void tegra_rgb_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool tegra_rgb_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + struct tegra_output *output = encoder_to_output(encoder); + unsigned long pclk = mode->clock * 1000; + struct tegra_rgb *rgb = to_rgb(output); + unsigned int div; + int err; + + /* + * We may not want to change the frequency of the parent clock, since + * it may be a parent for other peripherals. This is due to the fact + * that on Tegra20 there's only a single clock dedicated to display + * (pll_d_out0), whereas later generations have a second one that can + * be used to independently drive a second output (pll_d2_out0). + * + * As a way to support multiple outputs on Tegra20 as well, pll_p is + * typically used as the parent clock for the display controllers. + * But this comes at a cost: pll_p is the parent of several other + * peripherals, so its frequency shouldn't change out of the blue. + * + * The best we can do at this point is to use the shift clock divider + * and hope that the desired frequency can be matched (or at least + * matched sufficiently close that the panel will still work). + */ + div = ((clk_get_rate(rgb->clk) * 2) / pclk) - 2; + + err = tegra_dc_setup_clock(rgb->dc, rgb->clk_parent, pclk, div); + if (err < 0) { + dev_err(output->dev, "failed to setup DC clock: %d\n", err); + return false; + } + + return true; +} + +static void tegra_rgb_encoder_prepare(struct drm_encoder *encoder) +{ +} + +static void tegra_rgb_encoder_commit(struct drm_encoder *encoder) +{ +} + +static void tegra_rgb_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_rgb *rgb = to_rgb(output); + u32 value; + + if (output->panel) + drm_panel_prepare(output->panel); tegra_dc_write_regs(rgb->dc, rgb_enable, ARRAY_SIZE(rgb_enable)); @@ -125,88 +213,31 @@ static int tegra_output_rgb_enable(struct tegra_output *output) tegra_dc_commit(rgb->dc); - rgb->enabled = true; - - return 0; + if (output->panel) + drm_panel_enable(output->panel); } -static int tegra_output_rgb_disable(struct tegra_output *output) +static void tegra_rgb_encoder_disable(struct drm_encoder *encoder) { + struct tegra_output *output = encoder_to_output(encoder); struct tegra_rgb *rgb = to_rgb(output); - unsigned long value; - if (!rgb->enabled) - return 0; - - value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_POWER_CONTROL); - value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE); - tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + if (output->panel) + drm_panel_disable(output->panel); tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable)); - tegra_dc_commit(rgb->dc); - - rgb->enabled = false; - - return 0; + if (output->panel) + drm_panel_unprepare(output->panel); } -static int tegra_output_rgb_setup_clock(struct tegra_output *output, - struct clk *clk, unsigned long pclk, - unsigned int *div) -{ - struct tegra_rgb *rgb = to_rgb(output); - int err; - - err = clk_set_parent(clk, rgb->clk_parent); - if (err < 0) { - dev_err(output->dev, "failed to set parent: %d\n", err); - return err; - } - - /* - * We may not want to change the frequency of the parent clock, since - * it may be a parent for other peripherals. This is due to the fact - * that on Tegra20 there's only a single clock dedicated to display - * (pll_d_out0), whereas later generations have a second one that can - * be used to independently drive a second output (pll_d2_out0). - * - * As a way to support multiple outputs on Tegra20 as well, pll_p is - * typically used as the parent clock for the display controllers. - * But this comes at a cost: pll_p is the parent of several other - * peripherals, so its frequency shouldn't change out of the blue. - * - * The best we can do at this point is to use the shift clock divider - * and hope that the desired frequency can be matched (or at least - * matched sufficiently close that the panel will still work). - */ - - *div = ((clk_get_rate(clk) * 2) / pclk) - 2; - - return 0; -} - -static int tegra_output_rgb_check_mode(struct tegra_output *output, - struct drm_display_mode *mode, - enum drm_mode_status *status) -{ - /* - * FIXME: For now, always assume that the mode is okay. There are - * unresolved issues with clk_round_rate(), which doesn't always - * reliably report whether a frequency can be set or not. - */ - - *status = MODE_OK; - - return 0; -} - -static const struct tegra_output_ops rgb_ops = { - .enable = tegra_output_rgb_enable, - .disable = tegra_output_rgb_disable, - .setup_clock = tegra_output_rgb_setup_clock, - .check_mode = tegra_output_rgb_check_mode, +static const struct drm_encoder_helper_funcs tegra_rgb_encoder_helper_funcs = { + .dpms = tegra_rgb_encoder_dpms, + .mode_fixup = tegra_rgb_encoder_mode_fixup, + .prepare = tegra_rgb_encoder_prepare, + .commit = tegra_rgb_encoder_commit, + .mode_set = tegra_rgb_encoder_mode_set, + .disable = tegra_rgb_encoder_disable, }; int tegra_dc_rgb_probe(struct tegra_dc *dc) @@ -265,55 +296,55 @@ int tegra_dc_rgb_remove(struct tegra_dc *dc) if (err < 0) return err; + dc->rgb = NULL; + return 0; } int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc) { - struct tegra_rgb *rgb = to_rgb(dc->rgb); + struct tegra_output *output = dc->rgb; int err; if (!dc->rgb) return -ENODEV; - rgb->output.type = TEGRA_OUTPUT_RGB; - rgb->output.ops = &rgb_ops; + drm_connector_init(drm, &output->connector, &tegra_rgb_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_connector_helper_add(&output->connector, + &tegra_rgb_connector_helper_funcs); + output->connector.dpms = DRM_MODE_DPMS_OFF; - err = tegra_output_init(dc->base.dev, &rgb->output); - if (err < 0) { - dev_err(dc->dev, "output setup failed: %d\n", err); - return err; + if (output->panel) { + err = drm_panel_attach(output->panel, &output->connector); + if (err < 0) + dev_err(output->dev, "failed to attach panel: %d\n", + err); } + drm_encoder_init(drm, &output->encoder, &tegra_rgb_encoder_funcs, + DRM_MODE_ENCODER_LVDS); + drm_encoder_helper_add(&output->encoder, + &tegra_rgb_encoder_helper_funcs); + + drm_mode_connector_attach_encoder(&output->connector, + &output->encoder); + drm_connector_register(&output->connector); + /* - * By default, outputs can be associated with each display controller. - * RGB outputs are an exception, so we make sure they can be attached - * to only their parent display controller. + * Other outputs can be attached to either display controller. The RGB + * outputs are an exception and work only with their parent display + * controller. */ - rgb->output.encoder.possible_crtcs = drm_crtc_mask(&dc->base); + output->encoder.possible_crtcs = drm_crtc_mask(&dc->base); return 0; } int tegra_dc_rgb_exit(struct tegra_dc *dc) { - if (dc->rgb) { - int err; + if (!dc->rgb) + return 0; - err = tegra_output_disable(dc->rgb); - if (err < 0) { - dev_err(dc->dev, "output failed to disable: %d\n", err); - return err; - } - - err = tegra_output_exit(dc->rgb); - if (err < 0) { - dev_err(dc->dev, "output cleanup failed: %d\n", err); - return err; - } - - dc->rgb = NULL; - } - - return 0; + return tegra_output_exit(dc->rgb); } From 596827196f5fad8ac77201058978128f6c06f234 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 28 Nov 2014 16:50:59 +0100 Subject: [PATCH 31/54] drm/tegra: hdmi: Demidlayer Implement encoder and connector within the HDMI driver itself using the Tegra output helpers rather than using the Tegra output as midlayer. By doing so one level of indirection is removed and output drivers become more flexible while keeping the majority of the advantages provided by the common output helpers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.h | 1 - drivers/gpu/drm/tegra/hdmi.c | 280 +++++++++++++++++---------------- drivers/gpu/drm/tegra/output.c | 5 - 3 files changed, 147 insertions(+), 139 deletions(-) diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index dbc1f83327ea..95b6aebfaf00 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -193,7 +193,6 @@ struct tegra_output_ops { }; enum tegra_output_type { - TEGRA_OUTPUT_HDMI, TEGRA_OUTPUT_DSI, TEGRA_OUTPUT_EDP, }; diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index d4c635148cc7..056bb2c1c426 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -9,10 +9,14 @@ #include #include +#include #include #include #include +#include +#include + #include "hdmi.h" #include "drm.h" #include "dc.h" @@ -40,7 +44,6 @@ struct tegra_hdmi { struct host1x_client client; struct tegra_output output; struct device *dev; - bool enabled; struct regulator *hdmi; struct regulator *pll; @@ -768,21 +771,101 @@ static bool tegra_output_is_hdmi(struct tegra_output *output) return drm_detect_hdmi_monitor(edid); } -static int tegra_output_hdmi_enable(struct tegra_output *output) +static void tegra_hdmi_connector_dpms(struct drm_connector *connector, + int mode) +{ +} + +static const struct drm_connector_funcs tegra_hdmi_connector_funcs = { + .dpms = tegra_hdmi_connector_dpms, + .detect = tegra_output_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = tegra_output_connector_destroy, +}; + +static enum drm_mode_status +tegra_hdmi_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct tegra_output *output = connector_to_output(connector); + struct tegra_hdmi *hdmi = to_hdmi(output); + unsigned long pclk = mode->clock * 1000; + enum drm_mode_status status = MODE_OK; + struct clk *parent; + long err; + + parent = clk_get_parent(hdmi->clk_parent); + + err = clk_round_rate(parent, pclk * 4); + if (err <= 0) + status = MODE_NOCLOCK; + + return status; +} + +static const struct drm_connector_helper_funcs +tegra_hdmi_connector_helper_funcs = { + .get_modes = tegra_output_connector_get_modes, + .mode_valid = tegra_hdmi_connector_mode_valid, + .best_encoder = tegra_output_connector_best_encoder, +}; + +static const struct drm_encoder_funcs tegra_hdmi_encoder_funcs = { + .destroy = tegra_output_encoder_destroy, +}; + +static void tegra_hdmi_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool tegra_hdmi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); + struct tegra_hdmi *hdmi = to_hdmi(output); + unsigned long pclk = mode->clock * 1000; + int err; + + err = tegra_dc_setup_clock(dc, hdmi->clk_parent, pclk, 0); + if (err < 0) { + dev_err(output->dev, "failed to setup DC clock: %d\n", err); + return false; + } + + err = clk_set_rate(hdmi->clk_parent, pclk); + if (err < 0) { + dev_err(output->dev, "failed to set clock rate to %lu Hz\n", + pclk); + return false; + } + + return true; +} + +static void tegra_hdmi_encoder_prepare(struct drm_encoder *encoder) +{ +} + +static void tegra_hdmi_encoder_commit(struct drm_encoder *encoder) +{ +} + +static void tegra_hdmi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) { unsigned int h_sync_width, h_front_porch, h_back_porch, i, rekey; - struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); - struct drm_display_mode *mode = &dc->base.mode; + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); + struct device_node *node = output->dev->of_node; struct tegra_hdmi *hdmi = to_hdmi(output); - struct device_node *node = hdmi->dev->of_node; unsigned int pulse_start, div82, pclk; int retries = 1000; u32 value; int err; - if (hdmi->enabled) - return 0; - hdmi->dvi = !tegra_output_is_hdmi(output); pclk = mode->clock * 1000; @@ -790,32 +873,6 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) h_back_porch = mode->htotal - mode->hsync_end; h_front_porch = mode->hsync_start - mode->hdisplay; - err = regulator_enable(hdmi->pll); - if (err < 0) { - dev_err(hdmi->dev, "failed to enable PLL regulator: %d\n", err); - return err; - } - - err = regulator_enable(hdmi->vdd); - if (err < 0) { - dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err); - return err; - } - - err = clk_set_rate(hdmi->clk, pclk); - if (err < 0) - return err; - - err = clk_prepare_enable(hdmi->clk); - if (err < 0) { - dev_err(hdmi->dev, "failed to enable clock: %d\n", err); - return err; - } - - reset_control_assert(hdmi->rst); - usleep_range(1000, 2000); - reset_control_deassert(hdmi->rst); - /* power up sequence */ value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PLL0); value &= ~SOR_PLL_PDBG; @@ -1000,104 +1057,33 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) tegra_dc_commit(dc); /* TODO: add HDCP support */ - - hdmi->enabled = true; - - return 0; } -static int tegra_output_hdmi_disable(struct tegra_output *output) +static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder) { - struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); - struct tegra_hdmi *hdmi = to_hdmi(output); + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); u32 value; - if (!hdmi->enabled) - return 0; - /* * The following accesses registers of the display controller, so make * sure it's only executed when the output is attached to one. */ if (dc) { - /* - * XXX: We can't do this here because it causes HDMI to go - * into an erroneous state with the result that HDMI won't - * properly work once disabled. See also a similar symptom - * for the SOR output. - */ - /* - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); - value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE); - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - */ - value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value &= ~HDMI_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); tegra_dc_commit(dc); } - - clk_disable_unprepare(hdmi->clk); - reset_control_assert(hdmi->rst); - regulator_disable(hdmi->vdd); - regulator_disable(hdmi->pll); - - hdmi->enabled = false; - - return 0; } -static int tegra_output_hdmi_setup_clock(struct tegra_output *output, - struct clk *clk, unsigned long pclk, - unsigned int *div) -{ - struct tegra_hdmi *hdmi = to_hdmi(output); - int err; - - err = clk_set_parent(clk, hdmi->clk_parent); - if (err < 0) { - dev_err(output->dev, "failed to set parent: %d\n", err); - return err; - } - - err = clk_set_rate(hdmi->clk_parent, pclk); - if (err < 0) - dev_err(output->dev, "failed to set clock rate to %lu Hz\n", - pclk); - - *div = 0; - - return 0; -} - -static int tegra_output_hdmi_check_mode(struct tegra_output *output, - struct drm_display_mode *mode, - enum drm_mode_status *status) -{ - struct tegra_hdmi *hdmi = to_hdmi(output); - unsigned long pclk = mode->clock * 1000; - struct clk *parent; - long err; - - parent = clk_get_parent(hdmi->clk_parent); - - err = clk_round_rate(parent, pclk * 4); - if (err <= 0) - *status = MODE_NOCLOCK; - else - *status = MODE_OK; - - return 0; -} - -static const struct tegra_output_ops hdmi_ops = { - .enable = tegra_output_hdmi_enable, - .disable = tegra_output_hdmi_disable, - .setup_clock = tegra_output_hdmi_setup_clock, - .check_mode = tegra_output_hdmi_check_mode, +static const struct drm_encoder_helper_funcs tegra_hdmi_encoder_helper_funcs = { + .dpms = tegra_hdmi_encoder_dpms, + .mode_fixup = tegra_hdmi_encoder_mode_fixup, + .prepare = tegra_hdmi_encoder_prepare, + .commit = tegra_hdmi_encoder_commit, + .mode_set = tegra_hdmi_encoder_mode_set, + .disable = tegra_hdmi_encoder_disable, }; static int tegra_hdmi_show_regs(struct seq_file *s, void *data) @@ -1345,15 +1331,28 @@ static int tegra_hdmi_init(struct host1x_client *client) struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); int err; - hdmi->output.type = TEGRA_OUTPUT_HDMI; hdmi->output.dev = client->dev; - hdmi->output.ops = &hdmi_ops; - err = tegra_output_init(drm, &hdmi->output); - if (err < 0) { - dev_err(client->dev, "output setup failed: %d\n", err); - return err; - } + drm_connector_init(drm, &hdmi->output.connector, + &tegra_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + drm_connector_helper_add(&hdmi->output.connector, + &tegra_hdmi_connector_helper_funcs); + hdmi->output.connector.dpms = DRM_MODE_DPMS_OFF; + + drm_encoder_init(drm, &hdmi->output.encoder, &tegra_hdmi_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(&hdmi->output.encoder, + &tegra_hdmi_encoder_helper_funcs); + + drm_mode_connector_attach_encoder(&hdmi->output.connector, + &hdmi->output.encoder); + drm_connector_register(&hdmi->output.connector); + + hdmi->output.encoder.possible_crtcs = 0x3; + + if (gpio_is_valid(hdmi->output.hpd_gpio)) + enable_irq(hdmi->output.hpd_irq); if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_hdmi_debugfs_init(hdmi, drm->primary); @@ -1368,6 +1367,26 @@ static int tegra_hdmi_init(struct host1x_client *client) return err; } + err = regulator_enable(hdmi->pll); + if (err < 0) { + dev_err(hdmi->dev, "failed to enable PLL regulator: %d\n", err); + return err; + } + + err = regulator_enable(hdmi->vdd); + if (err < 0) { + dev_err(hdmi->dev, "failed to enable VDD regulator: %d\n", err); + return err; + } + + err = clk_prepare_enable(hdmi->clk); + if (err < 0) { + dev_err(hdmi->dev, "failed to enable clock: %d\n", err); + return err; + } + + reset_control_deassert(hdmi->rst); + return 0; } @@ -1376,6 +1395,13 @@ static int tegra_hdmi_exit(struct host1x_client *client) struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); int err; + tegra_output_exit(&hdmi->output); + + clk_disable_unprepare(hdmi->clk); + reset_control_assert(hdmi->rst); + + regulator_disable(hdmi->vdd); + regulator_disable(hdmi->pll); regulator_disable(hdmi->hdmi); if (IS_ENABLED(CONFIG_DEBUG_FS)) { @@ -1385,18 +1411,6 @@ static int tegra_hdmi_exit(struct host1x_client *client) err); } - err = tegra_output_disable(&hdmi->output); - if (err < 0) { - dev_err(client->dev, "output failed to disable: %d\n", err); - return err; - } - - err = tegra_output_exit(&hdmi->output); - if (err < 0) { - dev_err(client->dev, "output cleanup failed: %d\n", err); - return err; - } - return 0; } diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index f01ddfaa4c99..c7ebe8ea3868 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -274,11 +274,6 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output) int connector, encoder; switch (output->type) { - case TEGRA_OUTPUT_HDMI: - connector = DRM_MODE_CONNECTOR_HDMIA; - encoder = DRM_MODE_ENCODER_TMDS; - break; - case TEGRA_OUTPUT_DSI: connector = DRM_MODE_CONNECTOR_DSI; encoder = DRM_MODE_ENCODER_DSI; From 5b901e78b24539f4a1e194e8058f26ab38623c37 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 2 Dec 2014 17:30:23 +0100 Subject: [PATCH 32/54] drm/tegra: dsi: Demidlayer Implement encoder and connector within the DSI driver itself using the Tegra output helpers rather than using the Tegra output as midlayer. By doing so one level of indirection is removed and output drivers become more flexible while keeping the majority of the advantages provided by the common output helpers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.h | 1 - drivers/gpu/drm/tegra/dsi.c | 374 ++++++++++++++++++--------------- drivers/gpu/drm/tegra/output.c | 5 - 3 files changed, 203 insertions(+), 177 deletions(-) diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 95b6aebfaf00..ae3daa436ee6 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -193,7 +193,6 @@ struct tegra_output_ops { }; enum tegra_output_type { - TEGRA_OUTPUT_DSI, TEGRA_OUTPUT_EDP, }; diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 4c0dfe92402a..0b476e1c005a 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -51,7 +51,6 @@ struct tegra_dsi { struct mipi_dsi_host host; struct regulator *vdd; - bool enabled; unsigned int video_fifo_depth; unsigned int host_fifo_depth; @@ -628,46 +627,6 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, return 0; } -static int tegra_output_dsi_enable(struct tegra_output *output) -{ - struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); - const struct drm_display_mode *mode = &dc->base.mode; - struct tegra_dsi *dsi = to_dsi(output); - u32 value; - int err; - - if (dsi->enabled) - return 0; - - err = tegra_dsi_configure(dsi, dc->pipe, mode); - if (err < 0) - return err; - - /* enable display controller */ - value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); - value |= DSI_ENABLE; - tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - value |= DISP_CTRL_MODE_C_DISPLAY; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); - - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); - value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - - tegra_dc_commit(dc); - - /* enable DSI controller */ - tegra_dsi_enable(dsi); - - dsi->enabled = true; - - return 0; -} - static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout) { u32 value; @@ -704,6 +663,29 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi) tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL); } +static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk, + unsigned int vrefresh) +{ + unsigned int timeout; + u32 value; + + /* one frame high-speed transmission timeout */ + timeout = (bclk / vrefresh) / 512; + value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout); + tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0); + + /* 2 ms peripheral timeout for panel */ + timeout = 2 * bclk / 512 * 1000; + value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000); + tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1); + + value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); + tegra_dsi_writel(dsi, value, DSI_TO_TALLY); + + if (dsi->slave) + tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh); +} + static void tegra_dsi_disable(struct tegra_dsi *dsi) { u32 value; @@ -747,82 +729,51 @@ static void tegra_dsi_soft_reset(struct tegra_dsi *dsi) tegra_dsi_soft_reset(dsi->slave); } -static int tegra_output_dsi_disable(struct tegra_output *output) +static void tegra_dsi_connector_dpms(struct drm_connector *connector, int mode) { - struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); - struct tegra_dsi *dsi = to_dsi(output); - u32 value; - int err; - - if (!dsi->enabled) - return 0; - - tegra_dsi_video_disable(dsi); - - /* - * The following accesses registers of the display controller, so make - * sure it's only executed when the output is attached to one. - */ - if (dc) { - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); - value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE); - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - - value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); - value &= ~DSI_ENABLE; - tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - - tegra_dc_commit(dc); - } - - err = tegra_dsi_wait_idle(dsi, 100); - if (err < 0) - dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err); - - tegra_dsi_soft_reset(dsi); - tegra_dsi_disable(dsi); - - dsi->enabled = false; - - return 0; } -static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk, - unsigned int vrefresh) +static const struct drm_connector_funcs tegra_dsi_connector_funcs = { + .dpms = tegra_dsi_connector_dpms, + .detect = tegra_output_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = tegra_output_connector_destroy, +}; + +static enum drm_mode_status +tegra_dsi_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) { - unsigned int timeout; - u32 value; - - /* one frame high-speed transmission timeout */ - timeout = (bclk / vrefresh) / 512; - value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout); - tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0); - - /* 2 ms peripheral timeout for panel */ - timeout = 2 * bclk / 512 * 1000; - value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000); - tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1); - - value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); - tegra_dsi_writel(dsi, value, DSI_TO_TALLY); - - if (dsi->slave) - tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh); + return MODE_OK; } -static int tegra_output_dsi_setup_clock(struct tegra_output *output, - struct clk *clk, unsigned long pclk, - unsigned int *divp) +static const struct drm_connector_helper_funcs tegra_dsi_connector_helper_funcs = { + .get_modes = tegra_output_connector_get_modes, + .mode_valid = tegra_dsi_connector_mode_valid, + .best_encoder = tegra_output_connector_best_encoder, +}; + +static const struct drm_encoder_funcs tegra_dsi_encoder_funcs = { + .destroy = tegra_output_encoder_destroy, +}; + +static void tegra_dsi_encoder_dpms(struct drm_encoder *encoder, int mode) { - struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); - struct drm_display_mode *mode = &dc->base.mode; +} + +static bool tegra_dsi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); + unsigned int mul, div, scdiv, vrefresh, lanes; struct tegra_dsi *dsi = to_dsi(output); - unsigned int mul, div, vrefresh, lanes; - unsigned long bclk, plld; + unsigned long pclk, bclk, plld; int err; lanes = tegra_dsi_get_lanes(dsi); + pclk = mode->clock * 1000; err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); if (err < 0) @@ -847,19 +798,6 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, */ plld /= 2; - err = clk_set_parent(clk, dsi->clk_parent); - if (err < 0) { - dev_err(dsi->dev, "failed to set parent clock: %d\n", err); - return err; - } - - err = clk_set_rate(dsi->clk_parent, plld); - if (err < 0) { - dev_err(dsi->dev, "failed to set base clock rate to %lu Hz\n", - plld); - return err; - } - /* * Derive pixel clock from bit clock using the shift clock divider. * Note that this is only half of what we would expect, but we need @@ -870,39 +808,132 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, * not working properly otherwise. Perhaps the PLLs cannot generate * frequencies sufficiently high. */ - *divp = ((8 * mul) / (div * lanes)) - 2; + scdiv = ((8 * mul) / (div * lanes)) - 2; + + err = tegra_dc_setup_clock(dc, dsi->clk_parent, plld, scdiv); + if (err < 0) { + dev_err(output->dev, "failed to setup DC clock: %d\n", err); + return false; + } + + err = clk_set_rate(dsi->clk_parent, plld); + if (err < 0) { + dev_err(dsi->dev, "failed to set clock rate to %lu Hz\n", + plld); + return false; + } - /* - * XXX: Move the below somewhere else so that we don't need to have - * access to the vrefresh in this function? - */ tegra_dsi_set_timeout(dsi, bclk, vrefresh); err = tegra_dsi_set_phy_timing(dsi); - if (err < 0) - return err; + if (err < 0) { + dev_err(dsi->dev, "failed to setup D-PHY timing: %d\n", err); + return false; + } - return 0; + return true; } -static int tegra_output_dsi_check_mode(struct tegra_output *output, - struct drm_display_mode *mode, - enum drm_mode_status *status) +static void tegra_dsi_encoder_prepare(struct drm_encoder *encoder) { - /* - * FIXME: For now, always assume that the mode is okay. - */ - - *status = MODE_OK; - - return 0; } -static const struct tegra_output_ops dsi_ops = { - .enable = tegra_output_dsi_enable, - .disable = tegra_output_dsi_disable, - .setup_clock = tegra_output_dsi_setup_clock, - .check_mode = tegra_output_dsi_check_mode, +static void tegra_dsi_encoder_commit(struct drm_encoder *encoder) +{ +} + +static void tegra_dsi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); + struct tegra_dsi *dsi = to_dsi(output); + u32 value; + int err; + + + err = tegra_dsi_configure(dsi, dc->pipe, mode); + if (err < 0) { + dev_err(dsi->dev, "failed to configure DSI: %d\n", err); + return; + } + + if (output->panel) + drm_panel_prepare(output->panel); + + /* enable display controller */ + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value |= DSI_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + value |= DISP_CTRL_MODE_C_DISPLAY; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); + value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + + tegra_dc_commit(dc); + + /* enable DSI controller */ + tegra_dsi_enable(dsi); + + if (output->panel) + drm_panel_enable(output->panel); + + return; +} + +static void tegra_dsi_encoder_disable(struct drm_encoder *encoder) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); + struct tegra_dsi *dsi = to_dsi(output); + u32 value; + int err; + + if (output->panel) + drm_panel_disable(output->panel); + + tegra_dsi_video_disable(dsi); + + /* + * The following accesses registers of the display controller, so make + * sure it's only executed when the output is attached to one. + */ + if (dc) { + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value &= ~DSI_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + tegra_dc_commit(dc); + } + + err = tegra_dsi_wait_idle(dsi, 100); + if (err < 0) + dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err); + + tegra_dsi_soft_reset(dsi); + + if (output->panel) + drm_panel_unprepare(output->panel); + + tegra_dsi_disable(dsi); + + return; +} + +static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = { + .dpms = tegra_dsi_encoder_dpms, + .mode_fixup = tegra_dsi_encoder_mode_fixup, + .prepare = tegra_dsi_encoder_prepare, + .commit = tegra_dsi_encoder_commit, + .mode_set = tegra_dsi_encoder_mode_set, + .disable = tegra_dsi_encoder_disable, }; static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) @@ -952,15 +983,30 @@ static int tegra_dsi_init(struct host1x_client *client) /* Gangsters must not register their own outputs. */ if (!dsi->master) { - dsi->output.type = TEGRA_OUTPUT_DSI; dsi->output.dev = client->dev; - dsi->output.ops = &dsi_ops; - err = tegra_output_init(drm, &dsi->output); - if (err < 0) { - dev_err(client->dev, "output setup failed: %d\n", err); - return err; - } + drm_connector_init(drm, &dsi->output.connector, + &tegra_dsi_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + drm_connector_helper_add(&dsi->output.connector, + &tegra_dsi_connector_helper_funcs); + dsi->output.connector.dpms = DRM_MODE_DPMS_OFF; + + if (dsi->output.panel) + drm_panel_attach(dsi->output.panel, + &dsi->output.connector); + + drm_encoder_init(drm, &dsi->output.encoder, + &tegra_dsi_encoder_funcs, + DRM_MODE_ENCODER_DSI); + drm_encoder_helper_add(&dsi->output.encoder, + &tegra_dsi_encoder_helper_funcs); + + drm_mode_connector_attach_encoder(&dsi->output.connector, + &dsi->output.encoder); + drm_connector_register(&dsi->output.connector); + + dsi->output.encoder.possible_crtcs = 0x3; } if (IS_ENABLED(CONFIG_DEBUG_FS)) { @@ -981,28 +1027,14 @@ static int tegra_dsi_exit(struct host1x_client *client) struct tegra_dsi *dsi = host1x_client_to_dsi(client); int err; + tegra_output_exit(&dsi->output); + if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_dsi_debugfs_exit(dsi); if (err < 0) dev_err(dsi->dev, "debugfs cleanup failed: %d\n", err); } - if (!dsi->master) { - err = tegra_output_disable(&dsi->output); - if (err < 0) { - dev_err(client->dev, "output failed to disable: %d\n", - err); - return err; - } - - err = tegra_output_exit(&dsi->output); - if (err < 0) { - dev_err(client->dev, "output cleanup failed: %d\n", - err); - return err; - } - } - reset_control_assert(dsi->rst); return 0; @@ -1547,6 +1579,12 @@ static int tegra_dsi_remove(struct platform_device *pdev) return err; } + err = tegra_output_remove(&dsi->output); + if (err < 0) { + dev_err(&pdev->dev, "failed to remove output: %d\n", err); + return err; + } + mipi_dsi_host_unregister(&dsi->host); tegra_mipi_free(dsi->mipi); @@ -1555,12 +1593,6 @@ static int tegra_dsi_remove(struct platform_device *pdev) clk_disable_unprepare(dsi->clk); reset_control_assert(dsi->rst); - err = tegra_output_remove(&dsi->output); - if (err < 0) { - dev_err(&pdev->dev, "failed to remove output: %d\n", err); - return err; - } - return 0; } diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index c7ebe8ea3868..81de885226ff 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -274,11 +274,6 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output) int connector, encoder; switch (output->type) { - case TEGRA_OUTPUT_DSI: - connector = DRM_MODE_CONNECTOR_DSI; - encoder = DRM_MODE_ENCODER_DSI; - break; - case TEGRA_OUTPUT_EDP: connector = DRM_MODE_CONNECTOR_eDP; encoder = DRM_MODE_ENCODER_TMDS; From 6fad8f66d7b5f2194f30c62ac40248e15a5b2af1 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 28 Nov 2014 15:41:34 +0100 Subject: [PATCH 33/54] drm/tegra: sor: Demidlayer Implement encoder and connector within the eDP driver itself using the Tegra output helpers rather than using the Tegra output as midlayer. By doing so one level of indirection is removed and output drivers become more flexible while keeping the majority of the advantages provided by the common output helpers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.h | 5 - drivers/gpu/drm/tegra/output.c | 15 +- drivers/gpu/drm/tegra/sor.c | 758 ++++++++++++++++++--------------- 3 files changed, 410 insertions(+), 368 deletions(-) diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index ae3daa436ee6..c74d5db47537 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -192,16 +192,11 @@ struct tegra_output_ops { enum drm_connector_status (*detect)(struct tegra_output *output); }; -enum tegra_output_type { - TEGRA_OUTPUT_EDP, -}; - struct tegra_output { struct device_node *of_node; struct device *dev; const struct tegra_output_ops *ops; - enum tegra_output_type type; struct drm_panel *panel; struct i2c_adapter *ddc; diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 81de885226ff..57313e3ac238 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -271,19 +271,8 @@ int tegra_output_remove(struct tegra_output *output) int tegra_output_init(struct drm_device *drm, struct tegra_output *output) { - int connector, encoder; - - switch (output->type) { - case TEGRA_OUTPUT_EDP: - connector = DRM_MODE_CONNECTOR_eDP; - encoder = DRM_MODE_ENCODER_TMDS; - break; - - default: - connector = DRM_MODE_CONNECTOR_Unknown; - encoder = DRM_MODE_ENCODER_NONE; - break; - } + int connector = DRM_MODE_CONNECTOR_Unknown; + int encoder = DRM_MODE_ENCODER_NONE; drm_connector_init(drm, &output->connector, &connector_funcs, connector); diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 1fe801ee8eb0..be1ad42c69be 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include +#include #include "dc.h" #include "drm.h" @@ -481,10 +483,342 @@ static int tegra_sor_calc_config(struct tegra_sor *sor, return 0; } -static int tegra_output_sor_enable(struct tegra_output *output) +static int tegra_sor_detach(struct tegra_sor *sor) { - struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); - struct drm_display_mode *mode = &dc->base.mode; + unsigned long value, timeout; + + /* switch to safe mode */ + value = tegra_sor_readl(sor, SOR_SUPER_STATE_1); + value &= ~SOR_SUPER_STATE_MODE_NORMAL; + tegra_sor_writel(sor, value, SOR_SUPER_STATE_1); + tegra_sor_super_update(sor); + + timeout = jiffies + msecs_to_jiffies(250); + + while (time_before(jiffies, timeout)) { + value = tegra_sor_readl(sor, SOR_PWR); + if (value & SOR_PWR_MODE_SAFE) + break; + } + + if ((value & SOR_PWR_MODE_SAFE) == 0) + return -ETIMEDOUT; + + /* go to sleep */ + value = tegra_sor_readl(sor, SOR_SUPER_STATE_1); + value &= ~SOR_SUPER_STATE_HEAD_MODE_MASK; + tegra_sor_writel(sor, value, SOR_SUPER_STATE_1); + tegra_sor_super_update(sor); + + /* detach */ + value = tegra_sor_readl(sor, SOR_SUPER_STATE_1); + value &= ~SOR_SUPER_STATE_ATTACHED; + tegra_sor_writel(sor, value, SOR_SUPER_STATE_1); + tegra_sor_super_update(sor); + + timeout = jiffies + msecs_to_jiffies(250); + + while (time_before(jiffies, timeout)) { + value = tegra_sor_readl(sor, SOR_TEST); + if ((value & SOR_TEST_ATTACHED) == 0) + break; + + usleep_range(25, 100); + } + + if ((value & SOR_TEST_ATTACHED) != 0) + return -ETIMEDOUT; + + return 0; +} + +static int tegra_sor_power_down(struct tegra_sor *sor) +{ + unsigned long value, timeout; + int err; + + value = tegra_sor_readl(sor, SOR_PWR); + value &= ~SOR_PWR_NORMAL_STATE_PU; + value |= SOR_PWR_TRIGGER; + tegra_sor_writel(sor, value, SOR_PWR); + + timeout = jiffies + msecs_to_jiffies(250); + + while (time_before(jiffies, timeout)) { + value = tegra_sor_readl(sor, SOR_PWR); + if ((value & SOR_PWR_TRIGGER) == 0) + return 0; + + usleep_range(25, 100); + } + + if ((value & SOR_PWR_TRIGGER) != 0) + return -ETIMEDOUT; + + err = clk_set_parent(sor->clk, sor->clk_safe); + if (err < 0) + dev_err(sor->dev, "failed to set safe parent clock: %d\n", err); + + value = tegra_sor_readl(sor, SOR_DP_PADCTL_0); + value &= ~(SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_0 | + SOR_DP_PADCTL_PD_TXD_1 | SOR_DP_PADCTL_PD_TXD_2); + tegra_sor_writel(sor, value, SOR_DP_PADCTL_0); + + /* stop lane sequencer */ + value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_UP | + SOR_LANE_SEQ_CTL_POWER_STATE_DOWN; + tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL); + + timeout = jiffies + msecs_to_jiffies(250); + + while (time_before(jiffies, timeout)) { + value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL); + if ((value & SOR_LANE_SEQ_CTL_TRIGGER) == 0) + break; + + usleep_range(25, 100); + } + + if ((value & SOR_LANE_SEQ_CTL_TRIGGER) != 0) + return -ETIMEDOUT; + + value = tegra_sor_readl(sor, SOR_PLL_2); + value |= SOR_PLL_2_PORT_POWERDOWN; + tegra_sor_writel(sor, value, SOR_PLL_2); + + usleep_range(20, 100); + + value = tegra_sor_readl(sor, SOR_PLL_0); + value |= SOR_PLL_0_POWER_OFF; + value |= SOR_PLL_0_VCOPD; + tegra_sor_writel(sor, value, SOR_PLL_0); + + value = tegra_sor_readl(sor, SOR_PLL_2); + value |= SOR_PLL_2_SEQ_PLLCAPPD; + value |= SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE; + tegra_sor_writel(sor, value, SOR_PLL_2); + + usleep_range(20, 100); + + return 0; +} + +static int tegra_sor_crc_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +static int tegra_sor_crc_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int tegra_sor_crc_wait(struct tegra_sor *sor, unsigned long timeout) +{ + u32 value; + + timeout = jiffies + msecs_to_jiffies(timeout); + + while (time_before(jiffies, timeout)) { + value = tegra_sor_readl(sor, SOR_CRC_A); + if (value & SOR_CRC_A_VALID) + return 0; + + usleep_range(100, 200); + } + + return -ETIMEDOUT; +} + +static ssize_t tegra_sor_crc_read(struct file *file, char __user *buffer, + size_t size, loff_t *ppos) +{ + struct tegra_sor *sor = file->private_data; + ssize_t num, err; + char buf[10]; + u32 value; + + mutex_lock(&sor->lock); + + if (!sor->enabled) { + err = -EAGAIN; + goto unlock; + } + + value = tegra_sor_readl(sor, SOR_STATE_1); + value &= ~SOR_STATE_ASY_CRC_MODE_MASK; + tegra_sor_writel(sor, value, SOR_STATE_1); + + value = tegra_sor_readl(sor, SOR_CRC_CNTRL); + value |= SOR_CRC_CNTRL_ENABLE; + tegra_sor_writel(sor, value, SOR_CRC_CNTRL); + + value = tegra_sor_readl(sor, SOR_TEST); + value &= ~SOR_TEST_CRC_POST_SERIALIZE; + tegra_sor_writel(sor, value, SOR_TEST); + + err = tegra_sor_crc_wait(sor, 100); + if (err < 0) + goto unlock; + + tegra_sor_writel(sor, SOR_CRC_A_RESET, SOR_CRC_A); + value = tegra_sor_readl(sor, SOR_CRC_B); + + num = scnprintf(buf, sizeof(buf), "%08x\n", value); + + err = simple_read_from_buffer(buffer, size, ppos, buf, num); + +unlock: + mutex_unlock(&sor->lock); + return err; +} + +static const struct file_operations tegra_sor_crc_fops = { + .owner = THIS_MODULE, + .open = tegra_sor_crc_open, + .read = tegra_sor_crc_read, + .release = tegra_sor_crc_release, +}; + +static int tegra_sor_debugfs_init(struct tegra_sor *sor, + struct drm_minor *minor) +{ + struct dentry *entry; + int err = 0; + + sor->debugfs = debugfs_create_dir("sor", minor->debugfs_root); + if (!sor->debugfs) + return -ENOMEM; + + entry = debugfs_create_file("crc", 0644, sor->debugfs, sor, + &tegra_sor_crc_fops); + if (!entry) { + dev_err(sor->dev, + "cannot create /sys/kernel/debug/dri/%s/sor/crc\n", + minor->debugfs_root->d_name.name); + err = -ENOMEM; + goto remove; + } + + return err; + +remove: + debugfs_remove(sor->debugfs); + sor->debugfs = NULL; + return err; +} + +static int tegra_sor_debugfs_exit(struct tegra_sor *sor) +{ + debugfs_remove_recursive(sor->debugfs); + sor->debugfs = NULL; + + return 0; +} + +static void tegra_sor_connector_dpms(struct drm_connector *connector, int mode) +{ +} + +static enum drm_connector_status +tegra_sor_connector_detect(struct drm_connector *connector, bool force) +{ + struct tegra_output *output = connector_to_output(connector); + struct tegra_sor *sor = to_sor(output); + + if (sor->dpaux) + return tegra_dpaux_detect(sor->dpaux); + + return connector_status_unknown; +} + +static const struct drm_connector_funcs tegra_sor_connector_funcs = { + .dpms = tegra_sor_connector_dpms, + .detect = tegra_sor_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = tegra_output_connector_destroy, +}; + +static int tegra_sor_connector_get_modes(struct drm_connector *connector) +{ + struct tegra_output *output = connector_to_output(connector); + struct tegra_sor *sor = to_sor(output); + int err; + + if (sor->dpaux) + tegra_dpaux_enable(sor->dpaux); + + err = tegra_output_connector_get_modes(connector); + + if (sor->dpaux) + tegra_dpaux_disable(sor->dpaux); + + return err; +} + +static enum drm_mode_status +tegra_sor_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs tegra_sor_connector_helper_funcs = { + .get_modes = tegra_sor_connector_get_modes, + .mode_valid = tegra_sor_connector_mode_valid, + .best_encoder = tegra_output_connector_best_encoder, +}; + +static const struct drm_encoder_funcs tegra_sor_encoder_funcs = { + .destroy = tegra_output_encoder_destroy, +}; + +static void tegra_sor_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool tegra_sor_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); + unsigned long pclk = mode->clock * 1000; + struct tegra_sor *sor = to_sor(output); + int err; + + err = tegra_dc_setup_clock(dc, sor->clk_parent, pclk, 0); + if (err < 0) { + dev_err(output->dev, "failed to setup DC clock: %d\n", err); + return false; + } + + err = clk_set_rate(sor->clk_parent, pclk); + if (err < 0) { + dev_err(output->dev, "failed to set clock rate to %lu Hz\n", + pclk); + return false; + } + + return true; +} + +static void tegra_sor_encoder_prepare(struct drm_encoder *encoder) +{ +} + +static void tegra_sor_encoder_commit(struct drm_encoder *encoder) +{ +} + +static void tegra_sor_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); unsigned int vbe, vse, hbe, hse, vbs, hbs, i; struct tegra_sor *sor = to_sor(output); struct tegra_sor_config config; @@ -504,6 +838,9 @@ static int tegra_output_sor_enable(struct tegra_output *output) reset_control_deassert(sor->rst); + if (output->panel) + drm_panel_prepare(output->panel); + /* FIXME: properly convert to struct drm_dp_aux */ aux = (struct drm_dp_aux *)sor->dpaux; @@ -903,145 +1240,31 @@ static int tegra_output_sor_enable(struct tegra_output *output) goto unlock; } + if (output->panel) + drm_panel_enable(output->panel); + sor->enabled = true; unlock: mutex_unlock(&sor->lock); - return err; } -static int tegra_sor_detach(struct tegra_sor *sor) +static void tegra_sor_encoder_disable(struct drm_encoder *encoder) { - unsigned long value, timeout; - - /* switch to safe mode */ - value = tegra_sor_readl(sor, SOR_SUPER_STATE_1); - value &= ~SOR_SUPER_STATE_MODE_NORMAL; - tegra_sor_writel(sor, value, SOR_SUPER_STATE_1); - tegra_sor_super_update(sor); - - timeout = jiffies + msecs_to_jiffies(250); - - while (time_before(jiffies, timeout)) { - value = tegra_sor_readl(sor, SOR_PWR); - if (value & SOR_PWR_MODE_SAFE) - break; - } - - if ((value & SOR_PWR_MODE_SAFE) == 0) - return -ETIMEDOUT; - - /* go to sleep */ - value = tegra_sor_readl(sor, SOR_SUPER_STATE_1); - value &= ~SOR_SUPER_STATE_HEAD_MODE_MASK; - tegra_sor_writel(sor, value, SOR_SUPER_STATE_1); - tegra_sor_super_update(sor); - - /* detach */ - value = tegra_sor_readl(sor, SOR_SUPER_STATE_1); - value &= ~SOR_SUPER_STATE_ATTACHED; - tegra_sor_writel(sor, value, SOR_SUPER_STATE_1); - tegra_sor_super_update(sor); - - timeout = jiffies + msecs_to_jiffies(250); - - while (time_before(jiffies, timeout)) { - value = tegra_sor_readl(sor, SOR_TEST); - if ((value & SOR_TEST_ATTACHED) == 0) - break; - - usleep_range(25, 100); - } - - if ((value & SOR_TEST_ATTACHED) != 0) - return -ETIMEDOUT; - - return 0; -} - -static int tegra_sor_power_down(struct tegra_sor *sor) -{ - unsigned long value, timeout; - int err; - - value = tegra_sor_readl(sor, SOR_PWR); - value &= ~SOR_PWR_NORMAL_STATE_PU; - value |= SOR_PWR_TRIGGER; - tegra_sor_writel(sor, value, SOR_PWR); - - timeout = jiffies + msecs_to_jiffies(250); - - while (time_before(jiffies, timeout)) { - value = tegra_sor_readl(sor, SOR_PWR); - if ((value & SOR_PWR_TRIGGER) == 0) - return 0; - - usleep_range(25, 100); - } - - if ((value & SOR_PWR_TRIGGER) != 0) - return -ETIMEDOUT; - - err = clk_set_parent(sor->clk, sor->clk_safe); - if (err < 0) - dev_err(sor->dev, "failed to set safe parent clock: %d\n", err); - - value = tegra_sor_readl(sor, SOR_DP_PADCTL_0); - value &= ~(SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_0 | - SOR_DP_PADCTL_PD_TXD_1 | SOR_DP_PADCTL_PD_TXD_2); - tegra_sor_writel(sor, value, SOR_DP_PADCTL_0); - - /* stop lane sequencer */ - value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_UP | - SOR_LANE_SEQ_CTL_POWER_STATE_DOWN; - tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL); - - timeout = jiffies + msecs_to_jiffies(250); - - while (time_before(jiffies, timeout)) { - value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL); - if ((value & SOR_LANE_SEQ_CTL_TRIGGER) == 0) - break; - - usleep_range(25, 100); - } - - if ((value & SOR_LANE_SEQ_CTL_TRIGGER) != 0) - return -ETIMEDOUT; - - value = tegra_sor_readl(sor, SOR_PLL_2); - value |= SOR_PLL_2_PORT_POWERDOWN; - tegra_sor_writel(sor, value, SOR_PLL_2); - - usleep_range(20, 100); - - value = tegra_sor_readl(sor, SOR_PLL_0); - value |= SOR_PLL_0_POWER_OFF; - value |= SOR_PLL_0_VCOPD; - tegra_sor_writel(sor, value, SOR_PLL_0); - - value = tegra_sor_readl(sor, SOR_PLL_2); - value |= SOR_PLL_2_SEQ_PLLCAPPD; - value |= SOR_PLL_2_SEQ_PLLCAPPD_ENFORCE; - tegra_sor_writel(sor, value, SOR_PLL_2); - - usleep_range(20, 100); - - return 0; -} - -static int tegra_output_sor_disable(struct tegra_output *output) -{ - struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); struct tegra_sor *sor = to_sor(output); - unsigned long value; - int err = 0; + u32 value; + int err; mutex_lock(&sor->lock); if (!sor->enabled) goto unlock; + if (output->panel) + drm_panel_disable(output->panel); + err = tegra_sor_detach(sor); if (err < 0) { dev_err(sor->dev, "failed to detach SOR: %d\n", err); @@ -1056,21 +1279,6 @@ static int tegra_output_sor_disable(struct tegra_output *output) * sure it's only executed when the output is attached to one. */ if (dc) { - /* - * XXX: We can't do this here because it causes the SOR to go - * into an erroneous state and the output will look scrambled - * the next time it is enabled. Presumably this is because we - * should be doing this only on the next VBLANK. A possible - * solution would be to queue a "power-off" event to trigger - * this code to be run during the next VBLANK. - */ - /* - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); - value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE); - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - */ - value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value &= ~SOR_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); @@ -1098,187 +1306,27 @@ static int tegra_output_sor_disable(struct tegra_output *output) goto unlock; } - reset_control_assert(sor->rst); + if (output->panel) + drm_panel_unprepare(output->panel); + clk_disable_unprepare(sor->clk); + reset_control_assert(sor->rst); sor->enabled = false; unlock: mutex_unlock(&sor->lock); - return err; } -static int tegra_output_sor_setup_clock(struct tegra_output *output, - struct clk *clk, unsigned long pclk, - unsigned int *div) -{ - struct tegra_sor *sor = to_sor(output); - int err; - - err = clk_set_parent(clk, sor->clk_parent); - if (err < 0) { - dev_err(sor->dev, "failed to set parent clock: %d\n", err); - return err; - } - - err = clk_set_rate(sor->clk_parent, pclk); - if (err < 0) { - dev_err(sor->dev, "failed to set clock rate to %lu Hz\n", pclk); - return err; - } - - *div = 0; - - return 0; -} - -static int tegra_output_sor_check_mode(struct tegra_output *output, - struct drm_display_mode *mode, - enum drm_mode_status *status) -{ - /* - * FIXME: For now, always assume that the mode is okay. - */ - - *status = MODE_OK; - - return 0; -} - -static enum drm_connector_status -tegra_output_sor_detect(struct tegra_output *output) -{ - struct tegra_sor *sor = to_sor(output); - - if (sor->dpaux) - return tegra_dpaux_detect(sor->dpaux); - - return connector_status_unknown; -} - -static const struct tegra_output_ops sor_ops = { - .enable = tegra_output_sor_enable, - .disable = tegra_output_sor_disable, - .setup_clock = tegra_output_sor_setup_clock, - .check_mode = tegra_output_sor_check_mode, - .detect = tegra_output_sor_detect, +static const struct drm_encoder_helper_funcs tegra_sor_encoder_helper_funcs = { + .dpms = tegra_sor_encoder_dpms, + .mode_fixup = tegra_sor_encoder_mode_fixup, + .prepare = tegra_sor_encoder_prepare, + .commit = tegra_sor_encoder_commit, + .mode_set = tegra_sor_encoder_mode_set, + .disable = tegra_sor_encoder_disable, }; -static int tegra_sor_crc_open(struct inode *inode, struct file *file) -{ - file->private_data = inode->i_private; - - return 0; -} - -static int tegra_sor_crc_release(struct inode *inode, struct file *file) -{ - return 0; -} - -static int tegra_sor_crc_wait(struct tegra_sor *sor, unsigned long timeout) -{ - u32 value; - - timeout = jiffies + msecs_to_jiffies(timeout); - - while (time_before(jiffies, timeout)) { - value = tegra_sor_readl(sor, SOR_CRC_A); - if (value & SOR_CRC_A_VALID) - return 0; - - usleep_range(100, 200); - } - - return -ETIMEDOUT; -} - -static ssize_t tegra_sor_crc_read(struct file *file, char __user *buffer, - size_t size, loff_t *ppos) -{ - struct tegra_sor *sor = file->private_data; - ssize_t num, err; - char buf[10]; - u32 value; - - mutex_lock(&sor->lock); - - if (!sor->enabled) { - err = -EAGAIN; - goto unlock; - } - - value = tegra_sor_readl(sor, SOR_STATE_1); - value &= ~SOR_STATE_ASY_CRC_MODE_MASK; - tegra_sor_writel(sor, value, SOR_STATE_1); - - value = tegra_sor_readl(sor, SOR_CRC_CNTRL); - value |= SOR_CRC_CNTRL_ENABLE; - tegra_sor_writel(sor, value, SOR_CRC_CNTRL); - - value = tegra_sor_readl(sor, SOR_TEST); - value &= ~SOR_TEST_CRC_POST_SERIALIZE; - tegra_sor_writel(sor, value, SOR_TEST); - - err = tegra_sor_crc_wait(sor, 100); - if (err < 0) - goto unlock; - - tegra_sor_writel(sor, SOR_CRC_A_RESET, SOR_CRC_A); - value = tegra_sor_readl(sor, SOR_CRC_B); - - num = scnprintf(buf, sizeof(buf), "%08x\n", value); - - err = simple_read_from_buffer(buffer, size, ppos, buf, num); - -unlock: - mutex_unlock(&sor->lock); - return err; -} - -static const struct file_operations tegra_sor_crc_fops = { - .owner = THIS_MODULE, - .open = tegra_sor_crc_open, - .read = tegra_sor_crc_read, - .release = tegra_sor_crc_release, -}; - -static int tegra_sor_debugfs_init(struct tegra_sor *sor, - struct drm_minor *minor) -{ - struct dentry *entry; - int err = 0; - - sor->debugfs = debugfs_create_dir("sor", minor->debugfs_root); - if (!sor->debugfs) - return -ENOMEM; - - entry = debugfs_create_file("crc", 0644, sor->debugfs, sor, - &tegra_sor_crc_fops); - if (!entry) { - dev_err(sor->dev, - "cannot create /sys/kernel/debug/dri/%s/sor/crc\n", - minor->debugfs_root->d_name.name); - err = -ENOMEM; - goto remove; - } - - return err; - -remove: - debugfs_remove(sor->debugfs); - sor->debugfs = NULL; - return err; -} - -static int tegra_sor_debugfs_exit(struct tegra_sor *sor) -{ - debugfs_remove_recursive(sor->debugfs); - sor->debugfs = NULL; - - return 0; -} - static int tegra_sor_init(struct host1x_client *client) { struct drm_device *drm = dev_get_drvdata(client->parent); @@ -1288,16 +1336,31 @@ static int tegra_sor_init(struct host1x_client *client) if (!sor->dpaux) return -ENODEV; - sor->output.type = TEGRA_OUTPUT_EDP; - sor->output.dev = sor->dev; - sor->output.ops = &sor_ops; - err = tegra_output_init(drm, &sor->output); - if (err < 0) { - dev_err(sor->dev, "output setup failed: %d\n", err); - return err; - } + drm_connector_init(drm, &sor->output.connector, + &tegra_sor_connector_funcs, + DRM_MODE_CONNECTOR_eDP); + drm_connector_helper_add(&sor->output.connector, + &tegra_sor_connector_helper_funcs); + sor->output.connector.dpms = DRM_MODE_DPMS_OFF; + + if (sor->output.panel) + drm_panel_attach(sor->output.panel, &sor->output.connector); + + drm_encoder_init(drm, &sor->output.encoder, &tegra_sor_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(&sor->output.encoder, + &tegra_sor_encoder_helper_funcs); + + drm_mode_connector_attach_encoder(&sor->output.connector, + &sor->output.encoder); + drm_connector_register(&sor->output.connector); + + sor->output.encoder.possible_crtcs = 0x3; + + if (gpio_is_valid(sor->output.hpd_gpio)) + enable_irq(sor->output.hpd_irq); if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_sor_debugfs_init(sor, drm->primary); @@ -1313,6 +1376,20 @@ static int tegra_sor_init(struct host1x_client *client) } } + err = clk_prepare_enable(sor->clk); + if (err < 0) { + dev_err(sor->dev, "failed to enable clock: %d\n", err); + return err; + } + + err = clk_prepare_enable(sor->clk_safe); + if (err < 0) + return err; + + err = clk_prepare_enable(sor->clk_dp); + if (err < 0) + return err; + return 0; } @@ -1321,12 +1398,6 @@ static int tegra_sor_exit(struct host1x_client *client) struct tegra_sor *sor = host1x_client_to_sor(client); int err; - err = tegra_output_disable(&sor->output); - if (err < 0) { - dev_err(sor->dev, "output failed to disable: %d\n", err); - return err; - } - if (sor->dpaux) { err = tegra_dpaux_detach(sor->dpaux); if (err < 0) { @@ -1335,18 +1406,16 @@ static int tegra_sor_exit(struct host1x_client *client) } } + clk_disable_unprepare(sor->clk_safe); + clk_disable_unprepare(sor->clk_dp); + clk_disable_unprepare(sor->clk); + if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_sor_debugfs_exit(sor); if (err < 0) dev_err(sor->dev, "debugfs cleanup failed: %d\n", err); } - err = tegra_output_exit(&sor->output); - if (err < 0) { - dev_err(sor->dev, "output cleanup failed: %d\n", err); - return err; - } - return 0; } @@ -1398,26 +1467,14 @@ static int tegra_sor_probe(struct platform_device *pdev) if (IS_ERR(sor->clk_parent)) return PTR_ERR(sor->clk_parent); - err = clk_prepare_enable(sor->clk_parent); - if (err < 0) - return err; - sor->clk_safe = devm_clk_get(&pdev->dev, "safe"); if (IS_ERR(sor->clk_safe)) return PTR_ERR(sor->clk_safe); - err = clk_prepare_enable(sor->clk_safe); - if (err < 0) - return err; - sor->clk_dp = devm_clk_get(&pdev->dev, "dp"); if (IS_ERR(sor->clk_dp)) return PTR_ERR(sor->clk_dp); - err = clk_prepare_enable(sor->clk_dp); - if (err < 0) - return err; - INIT_LIST_HEAD(&sor->client.list); sor->client.ops = &sor_client_ops; sor->client.dev = &pdev->dev; @@ -1448,10 +1505,11 @@ static int tegra_sor_remove(struct platform_device *pdev) return err; } - clk_disable_unprepare(sor->clk_parent); - clk_disable_unprepare(sor->clk_safe); - clk_disable_unprepare(sor->clk_dp); - clk_disable_unprepare(sor->clk); + err = tegra_output_remove(&sor->output); + if (err < 0) { + dev_err(&pdev->dev, "failed to remove output: %d\n", err); + return err; + } return 0; } From 4009c22420593cae6d99b4ba43d3864c5788cd77 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 19 Dec 2014 15:47:30 +0100 Subject: [PATCH 34/54] drm/tegra: debugfs cleanup cannot fail The debugfs cleanup code never fails, so no error is returned. Therefore the functions can all return void instead. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 12 +++--------- drivers/gpu/drm/tegra/hdmi.c | 13 +++---------- drivers/gpu/drm/tegra/sor.c | 11 +++-------- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 0b476e1c005a..a999478b2c47 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -228,7 +228,7 @@ remove: return err; } -static int tegra_dsi_debugfs_exit(struct tegra_dsi *dsi) +static void tegra_dsi_debugfs_exit(struct tegra_dsi *dsi) { drm_debugfs_remove_files(dsi->debugfs_files, ARRAY_SIZE(debugfs_files), dsi->minor); @@ -239,8 +239,6 @@ static int tegra_dsi_debugfs_exit(struct tegra_dsi *dsi) debugfs_remove(dsi->debugfs); dsi->debugfs = NULL; - - return 0; } #define PKT_ID0(id) ((((id) & 0x3f) << 3) | (1 << 9)) @@ -1025,15 +1023,11 @@ reset: static int tegra_dsi_exit(struct host1x_client *client) { struct tegra_dsi *dsi = host1x_client_to_dsi(client); - int err; tegra_output_exit(&dsi->output); - if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_dsi_debugfs_exit(dsi); - if (err < 0) - dev_err(dsi->dev, "debugfs cleanup failed: %d\n", err); - } + if (IS_ENABLED(CONFIG_DEBUG_FS)) + tegra_dsi_debugfs_exit(dsi); reset_control_assert(dsi->rst); diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 056bb2c1c426..b4fe90949f27 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -1310,7 +1310,7 @@ remove: return err; } -static int tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi) +static void tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi) { drm_debugfs_remove_files(hdmi->debugfs_files, ARRAY_SIZE(debugfs_files), hdmi->minor); @@ -1321,8 +1321,6 @@ static int tegra_hdmi_debugfs_exit(struct tegra_hdmi *hdmi) debugfs_remove(hdmi->debugfs); hdmi->debugfs = NULL; - - return 0; } static int tegra_hdmi_init(struct host1x_client *client) @@ -1393,7 +1391,6 @@ static int tegra_hdmi_init(struct host1x_client *client) static int tegra_hdmi_exit(struct host1x_client *client) { struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); - int err; tegra_output_exit(&hdmi->output); @@ -1404,12 +1401,8 @@ static int tegra_hdmi_exit(struct host1x_client *client) regulator_disable(hdmi->pll); regulator_disable(hdmi->hdmi); - if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_hdmi_debugfs_exit(hdmi); - if (err < 0) - dev_err(client->dev, "debugfs cleanup failed: %d\n", - err); - } + if (IS_ENABLED(CONFIG_DEBUG_FS)) + tegra_hdmi_debugfs_exit(hdmi); return 0; } diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index be1ad42c69be..a1c16c5c0cf6 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -710,12 +710,10 @@ remove: return err; } -static int tegra_sor_debugfs_exit(struct tegra_sor *sor) +static void tegra_sor_debugfs_exit(struct tegra_sor *sor) { debugfs_remove_recursive(sor->debugfs); sor->debugfs = NULL; - - return 0; } static void tegra_sor_connector_dpms(struct drm_connector *connector, int mode) @@ -1410,11 +1408,8 @@ static int tegra_sor_exit(struct host1x_client *client) clk_disable_unprepare(sor->clk_dp); clk_disable_unprepare(sor->clk); - if (IS_ENABLED(CONFIG_DEBUG_FS)) { - err = tegra_sor_debugfs_exit(sor); - if (err < 0) - dev_err(sor->dev, "debugfs cleanup failed: %d\n", err); - } + if (IS_ENABLED(CONFIG_DEBUG_FS)) + tegra_sor_debugfs_exit(sor); return 0; } From ea130b240de820559408eba12b00412326af36ec Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 19 Dec 2014 15:51:35 +0100 Subject: [PATCH 35/54] drm/tegra: Remove remnants of the output midlayer The tegra_output midlayer is now completely gone and output drivers use it purely as a helper library. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 52 ---------------- drivers/gpu/drm/tegra/drm.h | 39 ------------ drivers/gpu/drm/tegra/dsi.c | 12 ++-- drivers/gpu/drm/tegra/hdmi.c | 9 ++- drivers/gpu/drm/tegra/output.c | 107 ++------------------------------- drivers/gpu/drm/tegra/rgb.c | 13 ++-- drivers/gpu/drm/tegra/sor.c | 12 ++-- 7 files changed, 32 insertions(+), 212 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 286cc8ce0c66..c5197bdaf0bd 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1070,52 +1070,6 @@ static int tegra_dc_set_timings(struct tegra_dc *dc, return 0; } -static int tegra_crtc_setup_clk(struct drm_crtc *crtc, - struct drm_display_mode *mode) -{ - unsigned long pclk = mode->clock * 1000; - struct tegra_dc *dc = to_tegra_dc(crtc); - struct tegra_output *output = NULL; - struct drm_encoder *encoder; - unsigned int div; - u32 value; - long err; - - list_for_each_entry(encoder, &crtc->dev->mode_config.encoder_list, head) - if (encoder->crtc == crtc) { - output = encoder_to_output(encoder); - break; - } - - if (!output) - return -ENODEV; - - /* - * The ->setup_clock() callback is optional, but if encoders don't - * implement it they most likely need to do the equivalent within the - * ->mode_fixup() callback. - */ - if (!output->ops || !output->ops->setup_clock) - return 0; - - /* - * This assumes that the parent clock is pll_d_out0 or pll_d2_out - * respectively, each of which divides the base pll_d by 2. - */ - err = output->ops->setup_clock(output, dc->clk, pclk, &div); - if (err < 0) { - dev_err(dc->dev, "failed to setup clock: %ld\n", err); - return err; - } - - DRM_DEBUG_KMS("rate: %lu, div: %u\n", clk_get_rate(dc->clk), div); - - value = SHIFT_CLK_DIVIDER(div) | PIXEL_CLK_DIVIDER_PCD1; - tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL); - - return 0; -} - int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent, unsigned long pclk, unsigned int div) { @@ -1147,12 +1101,6 @@ static int tegra_crtc_mode_set(struct drm_crtc *crtc, u32 value; int err; - err = tegra_crtc_setup_clk(crtc, mode); - if (err) { - dev_err(dc->dev, "failed to setup clock for CRTC: %d\n", err); - return err; - } - /* program display mode */ tegra_dc_set_timings(dc, mode); diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index c74d5db47537..699211a89a2e 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -181,23 +181,10 @@ void tegra_dc_commit(struct tegra_dc *dc); int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent, unsigned long pclk, unsigned int div); -struct tegra_output_ops { - int (*enable)(struct tegra_output *output); - int (*disable)(struct tegra_output *output); - int (*setup_clock)(struct tegra_output *output, struct clk *clk, - unsigned long pclk, unsigned int *div); - int (*check_mode)(struct tegra_output *output, - struct drm_display_mode *mode, - enum drm_mode_status *status); - enum drm_connector_status (*detect)(struct tegra_output *output); -}; - struct tegra_output { struct device_node *of_node; struct device *dev; - const struct tegra_output_ops *ops; - struct drm_panel *panel; struct i2c_adapter *ddc; const struct edid *edid; @@ -218,32 +205,6 @@ static inline struct tegra_output *connector_to_output(struct drm_connector *c) return container_of(c, struct tegra_output, connector); } -static inline int tegra_output_enable(struct tegra_output *output) -{ - if (output && output->ops && output->ops->enable) - return output->ops->enable(output); - - return output ? -ENOSYS : -EINVAL; -} - -static inline int tegra_output_disable(struct tegra_output *output) -{ - if (output && output->ops && output->ops->disable) - return output->ops->disable(output); - - return output ? -ENOSYS : -EINVAL; -} - -static inline int tegra_output_check_mode(struct tegra_output *output, - struct drm_display_mode *mode, - enum drm_mode_status *status) -{ - if (output && output->ops && output->ops->check_mode) - return output->ops->check_mode(output, mode, status); - - return output ? -ENOSYS : -EINVAL; -} - /* from rgb.c */ int tegra_dc_rgb_probe(struct tegra_dc *dc); int tegra_dc_rgb_remove(struct tegra_dc *dc); diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index a999478b2c47..1401d0d7108a 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -990,10 +990,6 @@ static int tegra_dsi_init(struct host1x_client *client) &tegra_dsi_connector_helper_funcs); dsi->output.connector.dpms = DRM_MODE_DPMS_OFF; - if (dsi->output.panel) - drm_panel_attach(dsi->output.panel, - &dsi->output.connector); - drm_encoder_init(drm, &dsi->output.encoder, &tegra_dsi_encoder_funcs, DRM_MODE_ENCODER_DSI); @@ -1004,6 +1000,14 @@ static int tegra_dsi_init(struct host1x_client *client) &dsi->output.encoder); drm_connector_register(&dsi->output.connector); + err = tegra_output_init(drm, &dsi->output); + if (err < 0) { + dev_err(client->dev, + "failed to initialize output: %d\n", + err); + goto reset; + } + dsi->output.encoder.possible_crtcs = 0x3; } diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index b4fe90949f27..03ceb50b1dc9 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -1347,10 +1347,13 @@ static int tegra_hdmi_init(struct host1x_client *client) &hdmi->output.encoder); drm_connector_register(&hdmi->output.connector); - hdmi->output.encoder.possible_crtcs = 0x3; + err = tegra_output_init(drm, &hdmi->output); + if (err < 0) { + dev_err(client->dev, "failed to initialize output: %d\n", err); + return err; + } - if (gpio_is_valid(hdmi->output.hpd_gpio)) - enable_irq(hdmi->output.hpd_irq); + hdmi->output.encoder.possible_crtcs = 0x3; if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_hdmi_debugfs_init(hdmi, drm->primary); diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 57313e3ac238..2d3b656bfd22 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -43,20 +43,6 @@ int tegra_output_connector_get_modes(struct drm_connector *connector) return err; } -static int tegra_connector_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - struct tegra_output *output = connector_to_output(connector); - enum drm_mode_status status = MODE_OK; - int err; - - err = tegra_output_check_mode(output, mode, &status); - if (err < 0) - return MODE_ERROR; - - return status; -} - struct drm_encoder * tegra_output_connector_best_encoder(struct drm_connector *connector) { @@ -65,21 +51,12 @@ tegra_output_connector_best_encoder(struct drm_connector *connector) return &output->encoder; } -static const struct drm_connector_helper_funcs connector_helper_funcs = { - .get_modes = tegra_output_connector_get_modes, - .mode_valid = tegra_connector_mode_valid, - .best_encoder = tegra_output_connector_best_encoder, -}; - enum drm_connector_status tegra_output_connector_detect(struct drm_connector *connector, bool force) { struct tegra_output *output = connector_to_output(connector); enum drm_connector_status status = connector_status_unknown; - if (output->ops->detect) - return output->ops->detect(output); - if (gpio_is_valid(output->hpd_gpio)) { if (gpio_get_value(output->hpd_gpio) == 0) status = connector_status_disconnected; @@ -90,9 +67,6 @@ tegra_output_connector_detect(struct drm_connector *connector, bool force) status = connector_status_disconnected; else status = connector_status_connected; - - if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) - status = connector_status_connected; } return status; @@ -104,69 +78,11 @@ void tegra_output_connector_destroy(struct drm_connector *connector) drm_connector_cleanup(connector); } -static const struct drm_connector_funcs connector_funcs = { - .dpms = drm_helper_connector_dpms, - .detect = tegra_output_connector_detect, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = tegra_output_connector_destroy, -}; - void tegra_output_encoder_destroy(struct drm_encoder *encoder) { drm_encoder_cleanup(encoder); } -static const struct drm_encoder_funcs encoder_funcs = { - .destroy = tegra_output_encoder_destroy, -}; - -static void tegra_encoder_dpms(struct drm_encoder *encoder, int mode) -{ - struct tegra_output *output = encoder_to_output(encoder); - struct drm_panel *panel = output->panel; - - if (mode != DRM_MODE_DPMS_ON) { - drm_panel_disable(panel); - tegra_output_disable(output); - drm_panel_unprepare(panel); - } else { - drm_panel_prepare(panel); - tegra_output_enable(output); - drm_panel_enable(panel); - } -} - -static bool tegra_encoder_mode_fixup(struct drm_encoder *encoder, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted) -{ - return true; -} - -static void tegra_encoder_prepare(struct drm_encoder *encoder) -{ - tegra_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); -} - -static void tegra_encoder_commit(struct drm_encoder *encoder) -{ - tegra_encoder_dpms(encoder, DRM_MODE_DPMS_ON); -} - -static void tegra_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted) -{ -} - -static const struct drm_encoder_helper_funcs encoder_helper_funcs = { - .dpms = tegra_encoder_dpms, - .mode_fixup = tegra_encoder_mode_fixup, - .prepare = tegra_encoder_prepare, - .commit = tegra_encoder_commit, - .mode_set = tegra_encoder_mode_set, -}; - static irqreturn_t hpd_irq(int irq, void *data) { struct tegra_output *output = data; @@ -271,24 +187,13 @@ int tegra_output_remove(struct tegra_output *output) int tegra_output_init(struct drm_device *drm, struct tegra_output *output) { - int connector = DRM_MODE_CONNECTOR_Unknown; - int encoder = DRM_MODE_ENCODER_NONE; + int err; - drm_connector_init(drm, &output->connector, &connector_funcs, - connector); - drm_connector_helper_add(&output->connector, &connector_helper_funcs); - output->connector.dpms = DRM_MODE_DPMS_OFF; - - if (output->panel) - drm_panel_attach(output->panel, &output->connector); - - drm_encoder_init(drm, &output->encoder, &encoder_funcs, encoder); - drm_encoder_helper_add(&output->encoder, &encoder_helper_funcs); - - drm_mode_connector_attach_encoder(&output->connector, &output->encoder); - drm_connector_register(&output->connector); - - output->encoder.possible_crtcs = 0x3; + if (output->panel) { + err = drm_panel_attach(output->panel, &output->connector); + if (err < 0) + return err; + } /* * The connector is now registered and ready to receive hotplug events diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index 30d7ae02ace8..ab6093889be8 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -315,13 +315,6 @@ int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc) &tegra_rgb_connector_helper_funcs); output->connector.dpms = DRM_MODE_DPMS_OFF; - if (output->panel) { - err = drm_panel_attach(output->panel, &output->connector); - if (err < 0) - dev_err(output->dev, "failed to attach panel: %d\n", - err); - } - drm_encoder_init(drm, &output->encoder, &tegra_rgb_encoder_funcs, DRM_MODE_ENCODER_LVDS); drm_encoder_helper_add(&output->encoder, @@ -331,6 +324,12 @@ int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc) &output->encoder); drm_connector_register(&output->connector); + err = tegra_output_init(drm, output); + if (err < 0) { + dev_err(output->dev, "failed to initialize output: %d\n", err); + return err; + } + /* * Other outputs can be attached to either display controller. The RGB * outputs are an exception and work only with their parent display diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index a1c16c5c0cf6..ee18adf7f653 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -1343,9 +1343,6 @@ static int tegra_sor_init(struct host1x_client *client) &tegra_sor_connector_helper_funcs); sor->output.connector.dpms = DRM_MODE_DPMS_OFF; - if (sor->output.panel) - drm_panel_attach(sor->output.panel, &sor->output.connector); - drm_encoder_init(drm, &sor->output.encoder, &tegra_sor_encoder_funcs, DRM_MODE_ENCODER_TMDS); drm_encoder_helper_add(&sor->output.encoder, @@ -1355,10 +1352,13 @@ static int tegra_sor_init(struct host1x_client *client) &sor->output.encoder); drm_connector_register(&sor->output.connector); - sor->output.encoder.possible_crtcs = 0x3; + err = tegra_output_init(drm, &sor->output); + if (err < 0) { + dev_err(client->dev, "failed to initialize output: %d\n", err); + return err; + } - if (gpio_is_valid(sor->output.hpd_gpio)) - enable_irq(sor->output.hpd_irq); + sor->output.encoder.possible_crtcs = 0x3; if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_sor_debugfs_init(sor, drm->primary); From 328ec69e7f9e7192c3f7653a5ec46d6e9a5fe60d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 19 Dec 2014 15:55:08 +0100 Subject: [PATCH 36/54] drm/tegra: Output cleanup functions cannot fail The tegra_output_exit() and tegra_output_remove() functions cannot fail, so make them return void. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.h | 4 ++-- drivers/gpu/drm/tegra/dsi.c | 6 +----- drivers/gpu/drm/tegra/hdmi.c | 6 +----- drivers/gpu/drm/tegra/output.c | 8 ++------ drivers/gpu/drm/tegra/rgb.c | 13 ++++--------- drivers/gpu/drm/tegra/sor.c | 8 +++----- 6 files changed, 13 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 699211a89a2e..bf1c47ec46b6 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -213,9 +213,9 @@ int tegra_dc_rgb_exit(struct tegra_dc *dc); /* from output.c */ int tegra_output_probe(struct tegra_output *output); -int tegra_output_remove(struct tegra_output *output); +void tegra_output_remove(struct tegra_output *output); int tegra_output_init(struct drm_device *drm, struct tegra_output *output); -int tegra_output_exit(struct tegra_output *output); +void tegra_output_exit(struct tegra_output *output); int tegra_output_connector_get_modes(struct drm_connector *connector); struct drm_encoder * diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 1401d0d7108a..23f4b0fc9734 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -1577,11 +1577,7 @@ static int tegra_dsi_remove(struct platform_device *pdev) return err; } - err = tegra_output_remove(&dsi->output); - if (err < 0) { - dev_err(&pdev->dev, "failed to remove output: %d\n", err); - return err; - } + tegra_output_remove(&dsi->output); mipi_dsi_host_unregister(&dsi->host); tegra_mipi_free(dsi->mipi); diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 03ceb50b1dc9..d41530f1097c 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -1563,11 +1563,7 @@ static int tegra_hdmi_remove(struct platform_device *pdev) return err; } - err = tegra_output_remove(&hdmi->output); - if (err < 0) { - dev_err(&pdev->dev, "failed to remove output: %d\n", err); - return err; - } + tegra_output_remove(&hdmi->output); clk_disable_unprepare(hdmi->clk_parent); clk_disable_unprepare(hdmi->clk); diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 2d3b656bfd22..8bb66008b8aa 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -172,7 +172,7 @@ int tegra_output_probe(struct tegra_output *output) return 0; } -int tegra_output_remove(struct tegra_output *output) +void tegra_output_remove(struct tegra_output *output) { if (gpio_is_valid(output->hpd_gpio)) { free_irq(output->hpd_irq, output); @@ -181,8 +181,6 @@ int tegra_output_remove(struct tegra_output *output) if (output->ddc) put_device(&output->ddc->dev); - - return 0; } int tegra_output_init(struct drm_device *drm, struct tegra_output *output) @@ -205,7 +203,7 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output) return 0; } -int tegra_output_exit(struct tegra_output *output) +void tegra_output_exit(struct tegra_output *output) { /* * The connector is going away, so the interrupt must be disabled to @@ -216,6 +214,4 @@ int tegra_output_exit(struct tegra_output *output) if (output->panel) drm_panel_detach(output->panel); - - return 0; } diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index ab6093889be8..0c932f9dc12d 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -287,15 +287,10 @@ int tegra_dc_rgb_probe(struct tegra_dc *dc) int tegra_dc_rgb_remove(struct tegra_dc *dc) { - int err; - if (!dc->rgb) return 0; - err = tegra_output_remove(dc->rgb); - if (err < 0) - return err; - + tegra_output_remove(dc->rgb); dc->rgb = NULL; return 0; @@ -342,8 +337,8 @@ int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc) int tegra_dc_rgb_exit(struct tegra_dc *dc) { - if (!dc->rgb) - return 0; + if (dc->rgb) + tegra_output_exit(dc->rgb); - return tegra_output_exit(dc->rgb); + return 0; } diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index ee18adf7f653..9e67838c1562 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -1396,6 +1396,8 @@ static int tegra_sor_exit(struct host1x_client *client) struct tegra_sor *sor = host1x_client_to_sor(client); int err; + tegra_output_exit(&sor->output); + if (sor->dpaux) { err = tegra_dpaux_detach(sor->dpaux); if (err < 0) { @@ -1500,11 +1502,7 @@ static int tegra_sor_remove(struct platform_device *pdev) return err; } - err = tegra_output_remove(&sor->output); - if (err < 0) { - dev_err(&pdev->dev, "failed to remove output: %d\n", err); - return err; - } + tegra_output_remove(&sor->output); return 0; } From 05f175f4f38de4e89facbb1420978e072ed4870c Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 16 Jan 2015 16:30:55 +0100 Subject: [PATCH 37/54] drm/tegra: dc: Do not needlessly deassert reset Commit 9c0127004ff4 ("drm/tegra: dc: Add powergate support") changed the driver's ->probe() implementation to deassert the module reset, and with there being nobody else to assert it until ->remove() there is no need to deassert again later on. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index c5197bdaf0bd..03c9ccf7d273 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1153,10 +1153,6 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc) drm_crtc_vblank_off(crtc); - /* hardware initialization */ - reset_control_deassert(dc->rst); - usleep_range(10000, 20000); - if (dc->pipe) syncpt = SYNCPT_VBLANK1; else From 4aa3df7149a00cb061d2ba74e2136cd14a6d885a Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 24 Nov 2014 16:27:13 +0100 Subject: [PATCH 38/54] drm/tegra: Atomic conversion, phase 1 Implement initial atomic state handling. Hook up the CRTCs, planes' and connectors' ->atomic_destroy_state() callback to ensure that the atomic state objects don't leak. Furthermore the CRTC now implements the ->mode_set_nofb() callback that is used by new helpers to implement ->mode_set() and ->mode_set_base(). These new helpers also make use of the new plane helper functions which the driver now provides. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 401 ++++++++++++++++++--------------- drivers/gpu/drm/tegra/drm.c | 2 +- drivers/gpu/drm/tegra/dsi.c | 2 + drivers/gpu/drm/tegra/hdmi.c | 2 + drivers/gpu/drm/tegra/output.c | 1 + drivers/gpu/drm/tegra/rgb.c | 2 + drivers/gpu/drm/tegra/sor.c | 2 + 7 files changed, 225 insertions(+), 187 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 03c9ccf7d273..f7254ab7a5c7 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -18,6 +18,7 @@ #include "drm.h" #include "gem.h" +#include #include struct tegra_dc_soc_info { @@ -200,8 +201,8 @@ static inline u32 compute_initial_dda(unsigned int in) return dfixed_frac(inf); } -static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, - const struct tegra_dc_window *window) +static void 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, flags; @@ -310,9 +311,11 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, break; case TEGRA_BO_TILING_MODE_BLOCK: - DRM_ERROR("hardware doesn't support block linear mode\n"); - spin_unlock_irqrestore(&dc->lock, flags); - return -EINVAL; + /* + * No need to handle this here because ->atomic_check + * will already have filtered it out. + */ + break; } tegra_dc_writel(dc, value, DC_WIN_BUFFER_ADDR_MODE); @@ -371,34 +374,6 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, tegra_dc_window_commit(dc, index); spin_unlock_irqrestore(&dc->lock, flags); - - return 0; -} - -static int tegra_window_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 flags; - u32 value; - - if (!plane->crtc) - return 0; - - spin_lock_irqsave(&dc->lock, flags); - - 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_window_commit(dc, p->index); - - spin_unlock_irqrestore(&dc->lock, flags); - - return 0; } static void tegra_plane_destroy(struct drm_plane *plane) @@ -415,57 +390,139 @@ static const u32 tegra_primary_plane_formats[] = { DRM_FORMAT_RGB565, }; -static int tegra_primary_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) +static void tegra_primary_plane_destroy(struct drm_plane *plane) { - struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); - struct tegra_plane *p = to_tegra_plane(plane); - struct tegra_dc *dc = to_tegra_dc(crtc); - struct tegra_dc_window window; + tegra_plane_destroy(plane); +} + +static const struct drm_plane_funcs tegra_primary_plane_funcs = { + .update_plane = drm_plane_helper_update, + .disable_plane = drm_plane_helper_disable, + .destroy = tegra_primary_plane_destroy, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static int tegra_plane_prepare_fb(struct drm_plane *plane, + struct drm_framebuffer *fb) +{ + return 0; +} + +static void tegra_plane_cleanup_fb(struct drm_plane *plane, + struct drm_framebuffer *fb) +{ +} + +static int tegra_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct tegra_dc *dc = to_tegra_dc(state->crtc); + struct tegra_bo_tiling tiling; int err; + /* no need for further checks if the plane is being disabled */ + if (!state->crtc) + return 0; + + err = tegra_fb_get_tiling(state->fb, &tiling); + if (err < 0) + return err; + + if (tiling.mode == TEGRA_BO_TILING_MODE_BLOCK && + !dc->soc->supports_block_linear) { + DRM_ERROR("hardware doesn't support block linear mode\n"); + return -EINVAL; + } + + /* + * Tegra doesn't support different strides for U and V planes so we + * error out if the user tries to display a framebuffer with such a + * configuration. + */ + if (drm_format_num_planes(state->fb->pixel_format) > 2) { + if (state->fb->pitches[2] != state->fb->pitches[1]) { + DRM_ERROR("unsupported UV-plane configuration\n"); + return -EINVAL; + } + } + + return 0; +} + +static void tegra_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct tegra_dc *dc = to_tegra_dc(plane->state->crtc); + struct drm_framebuffer *fb = plane->state->fb; + struct tegra_plane *p = to_tegra_plane(plane); + struct tegra_dc_window window; + unsigned int i; + int err; + + /* rien ne va plus */ + if (!plane->state->crtc || !plane->state->fb) + return; + 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.src.x = plane->state->src_x >> 16; + window.src.y = plane->state->src_y >> 16; + window.src.w = plane->state->src_w >> 16; + window.src.h = plane->state->src_h >> 16; + window.dst.x = plane->state->crtc_x; + window.dst.y = plane->state->crtc_y; + window.dst.w = plane->state->crtc_w; + window.dst.h = plane->state->crtc_h; window.format = tegra_dc_format(fb->pixel_format, &window.swap); window.bits_per_pixel = fb->bits_per_pixel; window.bottom_up = tegra_fb_is_bottom_up(fb); err = tegra_fb_get_tiling(fb, &window.tiling); - if (err < 0) - return err; + WARN_ON(err < 0); - window.base[0] = bo->paddr + fb->offsets[0]; - window.stride[0] = fb->pitches[0]; + for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) { + struct tegra_bo *bo = tegra_fb_get_plane(fb, i); - err = tegra_dc_setup_window(dc, p->index, &window); - if (err < 0) - return err; + window.base[i] = bo->paddr + fb->offsets[i]; + window.stride[i] = fb->pitches[i]; + } - return 0; + tegra_dc_setup_window(dc, p->index, &window); } -static void tegra_primary_plane_destroy(struct drm_plane *plane) +static void tegra_plane_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) { - tegra_window_plane_disable(plane); - tegra_plane_destroy(plane); + struct tegra_plane *p = to_tegra_plane(plane); + struct tegra_dc *dc; + unsigned long flags; + u32 value; + + /* rien ne va plus */ + if (!old_state || !old_state->crtc) + return; + + dc = to_tegra_dc(old_state->crtc); + + spin_lock_irqsave(&dc->lock, flags); + + 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_window_commit(dc, p->index); + + spin_unlock_irqrestore(&dc->lock, flags); } -static const struct drm_plane_funcs tegra_primary_plane_funcs = { - .update_plane = tegra_primary_plane_update, - .disable_plane = tegra_window_plane_disable, - .destroy = tegra_primary_plane_destroy, +static const struct drm_plane_helper_funcs tegra_primary_plane_helper_funcs = { + .prepare_fb = tegra_plane_prepare_fb, + .cleanup_fb = tegra_plane_cleanup_fb, + .atomic_check = tegra_plane_atomic_check, + .atomic_update = tegra_plane_atomic_update, + .atomic_disable = tegra_plane_atomic_disable, }; static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, @@ -504,6 +561,8 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, return ERR_PTR(err); } + drm_plane_helper_add(&plane->base, &tegra_primary_plane_helper_funcs); + return &plane->base; } @@ -511,27 +570,42 @@ static const u32 tegra_cursor_plane_formats[] = { DRM_FORMAT_RGBA8888, }; -static int tegra_cursor_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) +static int tegra_cursor_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) { - struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); - struct tegra_dc *dc = to_tegra_dc(crtc); - u32 value = CURSOR_CLIP_DISPLAY; + /* no need for further checks if the plane is being disabled */ + if (!state->crtc) + return 0; /* scaling not supported for cursor */ - if ((src_w >> 16 != crtc_w) || (src_h >> 16 != crtc_h)) + if ((state->src_w >> 16 != state->crtc_w) || + (state->src_h >> 16 != state->crtc_h)) return -EINVAL; /* only square cursors supported */ - if (src_w != src_h) + if (state->src_w != state->src_h) return -EINVAL; - switch (crtc_w) { + if (state->crtc_w != 32 && state->crtc_w != 64 && + state->crtc_w != 128 && state->crtc_w != 256) + return -EINVAL; + + return 0; +} + +static void tegra_cursor_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct tegra_bo *bo = tegra_fb_get_plane(plane->state->fb, 0); + struct tegra_dc *dc = to_tegra_dc(plane->state->crtc); + struct drm_plane_state *state = plane->state; + u32 value = CURSOR_CLIP_DISPLAY; + + /* rien ne va plus */ + if (!plane->state->crtc || !plane->state->fb) + return; + + switch (state->crtc_w) { case 32: value |= CURSOR_SIZE_32x32; break; @@ -549,7 +623,9 @@ static int tegra_cursor_plane_update(struct drm_plane *plane, break; default: - return -EINVAL; + WARN(1, "cursor size %ux%u not supported\n", state->crtc_w, + state->crtc_h); + return; } value |= (bo->paddr >> 10) & 0x3fffff; @@ -575,23 +651,25 @@ static int tegra_cursor_plane_update(struct drm_plane *plane, tegra_dc_writel(dc, value, DC_DISP_BLEND_CURSOR_CONTROL); /* position the cursor */ - value = (crtc_y & 0x3fff) << 16 | (crtc_x & 0x3fff); + value = (state->crtc_y & 0x3fff) << 16 | (state->crtc_x & 0x3fff); tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION); /* apply changes */ tegra_dc_cursor_commit(dc); tegra_dc_commit(dc); - - return 0; } -static int tegra_cursor_plane_disable(struct drm_plane *plane) +static void tegra_cursor_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) { - struct tegra_dc *dc = to_tegra_dc(plane->crtc); + struct tegra_dc *dc; u32 value; - if (!plane->crtc) - return 0; + /* rien ne va plus */ + if (!old_state || !old_state->crtc) + return; + + dc = to_tegra_dc(old_state->crtc); value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value &= ~CURSOR_ENABLE; @@ -599,14 +677,21 @@ static int tegra_cursor_plane_disable(struct drm_plane *plane) tegra_dc_cursor_commit(dc); tegra_dc_commit(dc); - - return 0; } static const struct drm_plane_funcs tegra_cursor_plane_funcs = { - .update_plane = tegra_cursor_plane_update, - .disable_plane = tegra_cursor_plane_disable, + .update_plane = drm_plane_helper_update, + .disable_plane = drm_plane_helper_disable, .destroy = tegra_plane_destroy, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static const struct drm_plane_helper_funcs tegra_cursor_plane_helper_funcs = { + .prepare_fb = tegra_plane_prepare_fb, + .cleanup_fb = tegra_plane_cleanup_fb, + .atomic_check = tegra_cursor_atomic_check, + .atomic_update = tegra_cursor_atomic_update, + .atomic_disable = tegra_cursor_atomic_disable, }; static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, @@ -632,71 +717,21 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, return ERR_PTR(err); } + drm_plane_helper_add(&plane->base, &tegra_cursor_plane_helper_funcs); + return &plane->base; } -static int tegra_overlay_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; - int err; - - 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.swap); - window.bits_per_pixel = fb->bits_per_pixel; - window.bottom_up = tegra_fb_is_bottom_up(fb); - - err = tegra_fb_get_tiling(fb, &window.tiling); - if (err < 0) - return err; - - for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) { - struct tegra_bo *bo = tegra_fb_get_plane(fb, i); - - window.base[i] = bo->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 void tegra_overlay_plane_destroy(struct drm_plane *plane) { - tegra_window_plane_disable(plane); tegra_plane_destroy(plane); } static const struct drm_plane_funcs tegra_overlay_plane_funcs = { - .update_plane = tegra_overlay_plane_update, - .disable_plane = tegra_window_plane_disable, + .update_plane = drm_plane_helper_update, + .disable_plane = drm_plane_helper_disable, .destroy = tegra_overlay_plane_destroy, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; static const uint32_t tegra_overlay_plane_formats[] = { @@ -709,6 +744,14 @@ static const uint32_t tegra_overlay_plane_formats[] = { DRM_FORMAT_YUV422, }; +static const struct drm_plane_helper_funcs tegra_overlay_plane_helper_funcs = { + .prepare_fb = tegra_plane_prepare_fb, + .cleanup_fb = tegra_plane_cleanup_fb, + .atomic_check = tegra_plane_atomic_check, + .atomic_update = tegra_plane_atomic_update, + .atomic_disable = tegra_plane_atomic_disable, +}; + static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, struct tegra_dc *dc, unsigned int index) @@ -735,6 +778,8 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, return ERR_PTR(err); } + drm_plane_helper_add(&plane->base, &tegra_overlay_plane_helper_funcs); + return &plane->base; } @@ -953,6 +998,7 @@ static const struct drm_crtc_funcs tegra_crtc_funcs = { .page_flip = tegra_dc_page_flip, .set_config = drm_crtc_helper_set_config, .destroy = tegra_dc_destroy, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, }; static void tegra_dc_stop(struct tegra_dc *dc) @@ -1090,16 +1136,11 @@ int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent, return 0; } -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) +static void tegra_crtc_mode_set_nofb(struct drm_crtc *crtc) { - struct tegra_bo *bo = tegra_fb_get_plane(crtc->primary->fb, 0); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; struct tegra_dc *dc = to_tegra_dc(crtc); - struct tegra_dc_window window; u32 value; - int err; /* program display mode */ tegra_dc_set_timings(dc, mode); @@ -1113,36 +1154,6 @@ static int tegra_crtc_mode_set(struct drm_crtc *crtc, value &= ~INTERLACE_ENABLE; tegra_dc_writel(dc, value, DC_DISP_INTERLACE_CONTROL); } - - /* setup window parameters */ - 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->primary->fb->pixel_format, - &window.swap); - window.bits_per_pixel = crtc->primary->fb->bits_per_pixel; - window.stride[0] = crtc->primary->fb->pitches[0]; - window.base[0] = bo->paddr; - - err = tegra_dc_setup_window(dc, 0, &window); - if (err < 0) - dev_err(dc->dev, "failed to enable root plane\n"); - - return 0; -} - -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 tegra_dc_set_base(dc, x, y, crtc->primary->fb); } static void tegra_crtc_prepare(struct drm_crtc *crtc) @@ -1193,13 +1204,31 @@ static void tegra_crtc_commit(struct drm_crtc *crtc) tegra_dc_commit(dc); } +static int tegra_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + return 0; +} + +static void tegra_crtc_atomic_begin(struct drm_crtc *crtc) +{ +} + +static void tegra_crtc_atomic_flush(struct drm_crtc *crtc) +{ +} + static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = { .disable = tegra_crtc_disable, .mode_fixup = tegra_crtc_mode_fixup, - .mode_set = tegra_crtc_mode_set, - .mode_set_base = tegra_crtc_mode_set_base, + .mode_set = drm_helper_crtc_mode_set, + .mode_set_nofb = tegra_crtc_mode_set_nofb, + .mode_set_base = drm_helper_crtc_mode_set_base, .prepare = tegra_crtc_prepare, .commit = tegra_crtc_commit, + .atomic_check = tegra_crtc_atomic_check, + .atomic_begin = tegra_crtc_atomic_begin, + .atomic_flush = tegra_crtc_atomic_flush, }; static irqreturn_t tegra_dc_irq(int irq, void *data) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index e29d9146b7c7..0e0da86f627c 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -121,8 +121,8 @@ static int tegra_drm_unload(struct drm_device *drm) drm_kms_helper_poll_fini(drm); tegra_drm_fb_exit(drm); - drm_vblank_cleanup(drm); drm_mode_config_cleanup(drm); + drm_vblank_cleanup(drm); err = host1x_device_exit(device); if (err < 0) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 23f4b0fc9734..234cd1c3079f 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -17,6 +17,7 @@ #include +#include #include #include @@ -736,6 +737,7 @@ static const struct drm_connector_funcs tegra_dsi_connector_funcs = { .detect = tegra_output_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; static enum drm_mode_status diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index d41530f1097c..bf868d54ef7e 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -781,6 +782,7 @@ static const struct drm_connector_funcs tegra_hdmi_connector_funcs = { .detect = tegra_output_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; static enum drm_mode_status diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 8bb66008b8aa..37db47975d48 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -9,6 +9,7 @@ #include +#include #include #include "drm.h" diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index 0c932f9dc12d..23ba40370eb2 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -9,6 +9,7 @@ #include +#include #include #include "drm.h" @@ -97,6 +98,7 @@ static const struct drm_connector_funcs tegra_rgb_connector_funcs = { .detect = tegra_output_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; static enum drm_mode_status diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 9e67838c1562..e2f399363f09 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -15,6 +15,7 @@ #include +#include #include #include @@ -737,6 +738,7 @@ static const struct drm_connector_funcs tegra_sor_connector_funcs = { .detect = tegra_sor_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; static int tegra_sor_connector_get_modes(struct drm_connector *connector) From 9d44189f55c77face595982bad3310bd4078b9fe Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 24 Nov 2014 17:02:53 +0100 Subject: [PATCH 39/54] drm/tegra: Atomic conversion, phase 2 Hook up the default ->reset() and ->atomic_duplicate_state() helpers. This ensures that state objects are properly created and framebuffer reference counts correctly maintained. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 12 ++++++++++++ drivers/gpu/drm/tegra/drm.c | 2 ++ drivers/gpu/drm/tegra/dsi.c | 2 ++ drivers/gpu/drm/tegra/hdmi.c | 2 ++ drivers/gpu/drm/tegra/rgb.c | 2 ++ drivers/gpu/drm/tegra/sor.c | 2 ++ 6 files changed, 22 insertions(+) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index f7254ab7a5c7..6bf5014ad2dc 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -18,6 +18,7 @@ #include "drm.h" #include "gem.h" +#include #include #include @@ -399,6 +400,8 @@ static const struct drm_plane_funcs tegra_primary_plane_funcs = { .update_plane = drm_plane_helper_update, .disable_plane = drm_plane_helper_disable, .destroy = tegra_primary_plane_destroy, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; @@ -683,6 +686,8 @@ static const struct drm_plane_funcs tegra_cursor_plane_funcs = { .update_plane = drm_plane_helper_update, .disable_plane = drm_plane_helper_disable, .destroy = tegra_plane_destroy, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; @@ -731,6 +736,8 @@ static const struct drm_plane_funcs tegra_overlay_plane_funcs = { .update_plane = drm_plane_helper_update, .disable_plane = drm_plane_helper_disable, .destroy = tegra_overlay_plane_destroy, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; @@ -983,6 +990,9 @@ static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, drm_crtc_vblank_get(crtc); } + if (crtc->primary->state) + drm_atomic_set_fb_for_plane(crtc->primary->state, fb); + tegra_dc_set_base(dc, 0, 0, fb); crtc->primary->fb = fb; @@ -998,6 +1008,8 @@ static const struct drm_crtc_funcs tegra_crtc_funcs = { .page_flip = tegra_dc_page_flip, .set_config = drm_crtc_helper_set_config, .destroy = tegra_dc_destroy, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, }; diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 0e0da86f627c..c1bb0d901454 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -77,6 +77,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) if (err < 0) goto fbdev; + drm_mode_config_reset(drm); + /* * We don't use the drm_irq_install() helpers provided by the DRM * core, so we need to set this manually in order to allow the diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 234cd1c3079f..d98058dc4580 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -734,9 +734,11 @@ static void tegra_dsi_connector_dpms(struct drm_connector *connector, int mode) static const struct drm_connector_funcs tegra_dsi_connector_funcs = { .dpms = tegra_dsi_connector_dpms, + .reset = drm_atomic_helper_connector_reset, .detect = tegra_output_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index bf868d54ef7e..728681a3f8c9 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -779,9 +779,11 @@ static void tegra_hdmi_connector_dpms(struct drm_connector *connector, static const struct drm_connector_funcs tegra_hdmi_connector_funcs = { .dpms = tegra_hdmi_connector_dpms, + .reset = drm_atomic_helper_connector_reset, .detect = tegra_output_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index 23ba40370eb2..78e3cb1529d0 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -95,9 +95,11 @@ static void tegra_rgb_connector_dpms(struct drm_connector *connector, static const struct drm_connector_funcs tegra_rgb_connector_funcs = { .dpms = tegra_rgb_connector_dpms, + .reset = drm_atomic_helper_connector_reset, .detect = tegra_output_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index e2f399363f09..2793e75c6573 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -735,9 +735,11 @@ tegra_sor_connector_detect(struct drm_connector *connector, bool force) static const struct drm_connector_funcs tegra_sor_connector_funcs = { .dpms = tegra_sor_connector_dpms, + .reset = drm_atomic_helper_connector_reset, .detect = tegra_sor_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; From 07866963b675b358d82baf8df73dba545d967a1d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 24 Nov 2014 17:08:06 +0100 Subject: [PATCH 40/54] drm/tegra: Atomic conversion, phase 3, step 1 Switch out the regular plane helpers for the atomic plane helpers. Also use the default atomic helpers to implement the ->atomic_check() and ->atomic_commit() callbacks. The driver now exclusively uses the atomic interfaces. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 12 ++++++------ drivers/gpu/drm/tegra/drm.c | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 6bf5014ad2dc..4fb9048b2f65 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -397,8 +397,8 @@ static void tegra_primary_plane_destroy(struct drm_plane *plane) } static const struct drm_plane_funcs tegra_primary_plane_funcs = { - .update_plane = drm_plane_helper_update, - .disable_plane = drm_plane_helper_disable, + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, .destroy = tegra_primary_plane_destroy, .reset = drm_atomic_helper_plane_reset, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, @@ -683,8 +683,8 @@ static void tegra_cursor_atomic_disable(struct drm_plane *plane, } static const struct drm_plane_funcs tegra_cursor_plane_funcs = { - .update_plane = drm_plane_helper_update, - .disable_plane = drm_plane_helper_disable, + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, .destroy = tegra_plane_destroy, .reset = drm_atomic_helper_plane_reset, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, @@ -733,8 +733,8 @@ static void tegra_overlay_plane_destroy(struct drm_plane *plane) } static const struct drm_plane_funcs tegra_overlay_plane_funcs = { - .update_plane = drm_plane_helper_update, - .disable_plane = drm_plane_helper_disable, + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, .destroy = tegra_overlay_plane_destroy, .reset = drm_atomic_helper_plane_reset, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index c1bb0d901454..d01484cf3432 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -10,6 +10,8 @@ #include #include +#include + #include "drm.h" #include "gem.h" @@ -29,6 +31,8 @@ static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { #ifdef CONFIG_DRM_TEGRA_FBDEV .output_poll_changed = tegra_fb_output_poll_changed, #endif + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, }; static int tegra_drm_load(struct drm_device *drm, unsigned long flags) From ca915b108a39081edff1206ec00ecdb4136408ac Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 8 Dec 2014 16:14:45 +0100 Subject: [PATCH 41/54] drm/tegra: dc: Store clock setup in atomic state This allows the clock setup to be separated from the clock programming and better matches the expectations of the atomic modesetting where no code paths must fail during modeset. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 71 +++++++++++++++++++++++++++++++++++-- drivers/gpu/drm/tegra/drm.h | 4 +++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 4fb9048b2f65..b905a4e2bdf9 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -41,6 +41,22 @@ static inline struct tegra_plane *to_tegra_plane(struct drm_plane *plane) return container_of(plane, struct tegra_plane, base); } +struct tegra_dc_state { + struct drm_crtc_state base; + + struct clk *clk; + unsigned long pclk; + unsigned int div; +}; + +static inline struct tegra_dc_state *to_dc_state(struct drm_crtc_state *state) +{ + if (state) + return container_of(state, struct tegra_dc_state, base); + + return NULL; +} + static void tegra_dc_window_commit(struct tegra_dc *dc, unsigned int index) { u32 value = WIN_A_ACT_REQ << index; @@ -1004,13 +1020,48 @@ static void tegra_dc_destroy(struct drm_crtc *crtc) drm_crtc_cleanup(crtc); } +static void tegra_crtc_reset(struct drm_crtc *crtc) +{ + struct tegra_dc_state *state; + + kfree(crtc->state); + crtc->state = NULL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) + crtc->state = &state->base; +} + +static struct drm_crtc_state * +tegra_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct tegra_dc_state *state = to_dc_state(crtc->state); + struct tegra_dc_state *copy; + + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (!copy) + return NULL; + + copy->base.mode_changed = false; + copy->base.planes_changed = false; + copy->base.event = NULL; + + return ©->base; +} + +static void tegra_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + kfree(state); +} + static const struct drm_crtc_funcs tegra_crtc_funcs = { .page_flip = tegra_dc_page_flip, .set_config = drm_crtc_helper_set_config, .destroy = tegra_dc_destroy, - .reset = drm_atomic_helper_crtc_reset, - .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .reset = tegra_crtc_reset, + .atomic_duplicate_state = tegra_crtc_atomic_duplicate_state, + .atomic_destroy_state = tegra_crtc_atomic_destroy_state, }; static void tegra_dc_stop(struct tegra_dc *dc) @@ -1148,6 +1199,20 @@ int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent, return 0; } +int tegra_dc_state_setup_clock(struct tegra_dc *dc, + struct drm_crtc_state *crtc_state, + struct clk *clk, unsigned long pclk, + unsigned int div) +{ + struct tegra_dc_state *state = to_dc_state(crtc_state); + + state->clk = clk; + state->pclk = pclk; + state->div = div; + + return 0; +} + static void tegra_crtc_mode_set_nofb(struct drm_crtc *crtc) { struct drm_display_mode *mode = &crtc->state->adjusted_mode; diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index bf1c47ec46b6..3db719de312f 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -180,6 +180,10 @@ void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file); void tegra_dc_commit(struct tegra_dc *dc); int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent, unsigned long pclk, unsigned int div); +int tegra_dc_state_setup_clock(struct tegra_dc *dc, + struct drm_crtc_state *crtc_state, + struct clk *clk, unsigned long pclk, + unsigned int div); struct tegra_output { struct device_node *of_node; From 3cebae6737b100323baca21de6bce6647249e778 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 17 Dec 2014 17:04:36 +0100 Subject: [PATCH 42/54] drm/tegra: rgb: Implement ->atomic_check() The implementation of the ->atomic_check() callback precomputes all parameters to check if the given configuration can be applied. If so the precomputed values are stored in the atomic state object for the encoder and applied during modeset. In that way the modeset no longer needs to perform any checking but simply program values into registers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/rgb.c | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index 78e3cb1529d0..be1b38936dbe 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -235,6 +235,47 @@ static void tegra_rgb_encoder_disable(struct drm_encoder *encoder) drm_panel_unprepare(output->panel); } +static int +tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(conn_state->crtc); + unsigned long pclk = crtc_state->mode.clock * 1000; + struct tegra_rgb *rgb = to_rgb(output); + unsigned int div; + int err; + + /* + * We may not want to change the frequency of the parent clock, since + * it may be a parent for other peripherals. This is due to the fact + * that on Tegra20 there's only a single clock dedicated to display + * (pll_d_out0), whereas later generations have a second one that can + * be used to independently drive a second output (pll_d2_out0). + * + * As a way to support multiple outputs on Tegra20 as well, pll_p is + * typically used as the parent clock for the display controllers. + * But this comes at a cost: pll_p is the parent of several other + * peripherals, so its frequency shouldn't change out of the blue. + * + * The best we can do at this point is to use the shift clock divider + * and hope that the desired frequency can be matched (or at least + * matched sufficiently close that the panel will still work). + */ + div = ((clk_get_rate(rgb->clk) * 2) / pclk) - 2; + pclk = 0; + + err = tegra_dc_state_setup_clock(dc, crtc_state, rgb->clk_parent, + pclk, div); + if (err < 0) { + dev_err(output->dev, "failed to setup CRTC state: %d\n", err); + return err; + } + + return err; +} + static const struct drm_encoder_helper_funcs tegra_rgb_encoder_helper_funcs = { .dpms = tegra_rgb_encoder_dpms, .mode_fixup = tegra_rgb_encoder_mode_fixup, @@ -242,6 +283,7 @@ static const struct drm_encoder_helper_funcs tegra_rgb_encoder_helper_funcs = { .commit = tegra_rgb_encoder_commit, .mode_set = tegra_rgb_encoder_mode_set, .disable = tegra_rgb_encoder_disable, + .atomic_check = tegra_rgb_encoder_atomic_check, }; int tegra_dc_rgb_probe(struct tegra_dc *dc) From ebd14afe8fa7d3f00f2ab1c8c4befee2fa6c0504 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 8 Dec 2014 16:22:28 +0100 Subject: [PATCH 43/54] drm/tegra: dsi: Implement ->atomic_check() The implementation of the ->atomic_check() callback precomputes all parameters to check if the given configuration can be applied. If so the precomputed values are stored in the atomic state object for the encoder and applied during modeset. In that way the modeset no longer needs to perform any checking but simply program values into registers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 269 ++++++++++++++++++++++++++---------- 1 file changed, 196 insertions(+), 73 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index d98058dc4580..00b307120cdd 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -28,6 +28,28 @@ #include "dsi.h" #include "mipi-phy.h" +struct tegra_dsi_state { + struct drm_connector_state base; + + struct mipi_dphy_timing timing; + unsigned long period; + + unsigned int vrefresh; + unsigned int lanes; + unsigned long pclk; + unsigned long bclk; + + enum tegra_dsi_format format; + unsigned int mul; + unsigned int div; +}; + +static inline struct tegra_dsi_state * +to_dsi_state(struct drm_connector_state *state) +{ + return container_of(state, struct tegra_dsi_state, base); +} + struct tegra_dsi { struct host1x_client client; struct tegra_output output; @@ -77,6 +99,11 @@ static inline struct tegra_dsi *to_dsi(struct tegra_output *output) return container_of(output, struct tegra_dsi, output); } +static struct tegra_dsi_state *tegra_dsi_get_state(struct tegra_dsi *dsi) +{ + return to_dsi_state(dsi->output.connector.state); +} + static inline u32 tegra_dsi_readl(struct tegra_dsi *dsi, unsigned long reg) { return readl(dsi->regs + (reg << 2)); @@ -335,62 +362,36 @@ static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = { [11] = 0, }; -static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) +static void tegra_dsi_set_phy_timing(struct tegra_dsi *dsi, + unsigned long period, + const struct mipi_dphy_timing *timing) { - struct mipi_dphy_timing timing; - unsigned long period; u32 value; - long rate; - int err; - rate = clk_get_rate(dsi->clk); - if (rate < 0) - return rate; - - period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate * 2); - - err = mipi_dphy_timing_get_default(&timing, period); - if (err < 0) - return err; - - err = mipi_dphy_timing_validate(&timing, period); - if (err < 0) { - dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err); - return err; - } - - /* - * The D-PHY timing fields below are expressed in byte-clock cycles, - * so multiply the period by 8. - */ - period *= 8; - - value = DSI_TIMING_FIELD(timing.hsexit, period, 1) << 24 | - DSI_TIMING_FIELD(timing.hstrail, period, 0) << 16 | - DSI_TIMING_FIELD(timing.hszero, period, 3) << 8 | - DSI_TIMING_FIELD(timing.hsprepare, period, 1); + value = DSI_TIMING_FIELD(timing->hsexit, period, 1) << 24 | + DSI_TIMING_FIELD(timing->hstrail, period, 0) << 16 | + DSI_TIMING_FIELD(timing->hszero, period, 3) << 8 | + DSI_TIMING_FIELD(timing->hsprepare, period, 1); tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_0); - value = DSI_TIMING_FIELD(timing.clktrail, period, 1) << 24 | - DSI_TIMING_FIELD(timing.clkpost, period, 1) << 16 | - DSI_TIMING_FIELD(timing.clkzero, period, 1) << 8 | - DSI_TIMING_FIELD(timing.lpx, period, 1); + value = DSI_TIMING_FIELD(timing->clktrail, period, 1) << 24 | + DSI_TIMING_FIELD(timing->clkpost, period, 1) << 16 | + DSI_TIMING_FIELD(timing->clkzero, period, 1) << 8 | + DSI_TIMING_FIELD(timing->lpx, period, 1); tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_1); - value = DSI_TIMING_FIELD(timing.clkprepare, period, 1) << 16 | - DSI_TIMING_FIELD(timing.clkpre, period, 1) << 8 | + value = DSI_TIMING_FIELD(timing->clkprepare, period, 1) << 16 | + DSI_TIMING_FIELD(timing->clkpre, period, 1) << 8 | DSI_TIMING_FIELD(0xff * period, period, 0) << 0; tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_2); - value = DSI_TIMING_FIELD(timing.taget, period, 1) << 16 | - DSI_TIMING_FIELD(timing.tasure, period, 1) << 8 | - DSI_TIMING_FIELD(timing.tago, period, 1); + value = DSI_TIMING_FIELD(timing->taget, period, 1) << 16 | + DSI_TIMING_FIELD(timing->tasure, period, 1) << 8 | + DSI_TIMING_FIELD(timing->tago, period, 1); tegra_dsi_writel(dsi, value, DSI_BTA_TIMING); if (dsi->slave) - return tegra_dsi_set_phy_timing(dsi->slave); - - return 0; + tegra_dsi_set_phy_timing(dsi->slave, period, timing); } static int tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format, @@ -482,14 +483,22 @@ static unsigned int tegra_dsi_get_lanes(struct tegra_dsi *dsi) return dsi->lanes; } -static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, - const struct drm_display_mode *mode) +static void tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, + const struct drm_display_mode *mode) { unsigned int hact, hsw, hbp, hfp, i, mul, div; - enum tegra_dsi_format format; + struct tegra_dsi_state *state; const u32 *pkt_seq; u32 value; - int err; + + /* XXX: pass in state into this function? */ + if (dsi->master) + state = tegra_dsi_get_state(dsi->master); + else + state = tegra_dsi_get_state(dsi); + + mul = state->mul; + div = state->div; if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n"); @@ -502,15 +511,8 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, pkt_seq = pkt_seq_command_mode; } - err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); - if (err < 0) - return err; - - err = tegra_dsi_get_format(dsi->format, &format); - if (err < 0) - return err; - - value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) | + value = DSI_CONTROL_CHANNEL(0) | + DSI_CONTROL_FORMAT(state->format) | DSI_CONTROL_LANES(dsi->lanes - 1) | DSI_CONTROL_SOURCE(pipe); tegra_dsi_writel(dsi, value, DSI_CONTROL); @@ -589,8 +591,8 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, /* set SOL delay */ if (dsi->master || dsi->slave) { - unsigned int lanes = tegra_dsi_get_lanes(dsi); unsigned long delay, bclk, bclk_ganged; + unsigned int lanes = state->lanes; /* SOL to valid, valid to FIFO and FIFO write delay */ delay = 4 + 4 + 2; @@ -610,9 +612,7 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, } if (dsi->slave) { - err = tegra_dsi_configure(dsi->slave, pipe, mode); - if (err < 0) - return err; + tegra_dsi_configure(dsi->slave, pipe, mode); /* * TODO: Support modes other than symmetrical left-right @@ -622,8 +622,6 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, tegra_dsi_ganged_enable(dsi->slave, mode->hdisplay / 2, mode->hdisplay / 2); } - - return 0; } static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout) @@ -732,13 +730,38 @@ static void tegra_dsi_connector_dpms(struct drm_connector *connector, int mode) { } +static void tegra_dsi_connector_reset(struct drm_connector *connector) +{ + struct tegra_dsi_state *state; + + kfree(connector->state); + connector->state = NULL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) + connector->state = &state->base; +} + +static struct drm_connector_state * +tegra_dsi_connector_duplicate_state(struct drm_connector *connector) +{ + struct tegra_dsi_state *state = to_dsi_state(connector->state); + struct tegra_dsi_state *copy; + + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (!copy) + return NULL; + + return ©->base; +} + static const struct drm_connector_funcs tegra_dsi_connector_funcs = { .dpms = tegra_dsi_connector_dpms, - .reset = drm_atomic_helper_connector_reset, + .reset = tegra_dsi_connector_reset, .detect = tegra_output_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_duplicate_state = tegra_dsi_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; @@ -771,7 +794,9 @@ static bool tegra_dsi_encoder_mode_fixup(struct drm_encoder *encoder, struct tegra_dc *dc = to_tegra_dc(encoder->crtc); unsigned int mul, div, scdiv, vrefresh, lanes; struct tegra_dsi *dsi = to_dsi(output); + struct mipi_dphy_timing timing; unsigned long pclk, bclk, plld; + unsigned long period; int err; lanes = tegra_dsi_get_lanes(dsi); @@ -792,6 +817,7 @@ static bool tegra_dsi_encoder_mode_fixup(struct drm_encoder *encoder, * Compute bit clock and round up to the next MHz. */ plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC) * USEC_PER_SEC; + period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, plld); /* * We divide the frequency by two here, but we make up for that by @@ -827,12 +853,22 @@ static bool tegra_dsi_encoder_mode_fixup(struct drm_encoder *encoder, tegra_dsi_set_timeout(dsi, bclk, vrefresh); - err = tegra_dsi_set_phy_timing(dsi); + err = mipi_dphy_timing_get_default(&timing, period); + if (err < 0) + return err; + + err = mipi_dphy_timing_validate(&timing, period); if (err < 0) { - dev_err(dsi->dev, "failed to setup D-PHY timing: %d\n", err); - return false; + dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err); + return err; } + /* + * The D-PHY timing fields are expressed in byte-clock cycles, so + * multiply the period by 8. + */ + tegra_dsi_set_phy_timing(dsi, period * 8, &timing); + return true; } @@ -851,19 +887,24 @@ static void tegra_dsi_encoder_mode_set(struct drm_encoder *encoder, struct tegra_output *output = encoder_to_output(encoder); struct tegra_dc *dc = to_tegra_dc(encoder->crtc); struct tegra_dsi *dsi = to_dsi(output); + struct tegra_dsi_state *state; u32 value; - int err; + state = tegra_dsi_get_state(dsi); - err = tegra_dsi_configure(dsi, dc->pipe, mode); - if (err < 0) { - dev_err(dsi->dev, "failed to configure DSI: %d\n", err); - return; - } + tegra_dsi_set_timeout(dsi, state->bclk, state->vrefresh); + + /* + * The D-PHY timing fields are expressed in byte-clock cycles, so + * multiply the period by 8. + */ + tegra_dsi_set_phy_timing(dsi, state->period * 8, &state->timing); if (output->panel) drm_panel_prepare(output->panel); + tegra_dsi_configure(dsi, dc->pipe, mode); + /* enable display controller */ value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value |= DSI_ENABLE; @@ -929,6 +970,87 @@ static void tegra_dsi_encoder_disable(struct drm_encoder *encoder) return; } +static int +tegra_dsi_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dsi_state *state = to_dsi_state(conn_state); + struct tegra_dc *dc = to_tegra_dc(conn_state->crtc); + struct tegra_dsi *dsi = to_dsi(output); + unsigned int scdiv; + unsigned long plld; + int err; + + state->pclk = crtc_state->mode.clock * 1000; + + err = tegra_dsi_get_muldiv(dsi->format, &state->mul, &state->div); + if (err < 0) + return err; + + state->lanes = tegra_dsi_get_lanes(dsi); + + err = tegra_dsi_get_format(dsi->format, &state->format); + if (err < 0) + return err; + + state->vrefresh = drm_mode_vrefresh(&crtc_state->mode); + + /* compute byte clock */ + state->bclk = (state->pclk * state->mul) / (state->div * state->lanes); + + DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", state->mul, state->div, + state->lanes); + DRM_DEBUG_KMS("format: %u, vrefresh: %u\n", state->format, + state->vrefresh); + DRM_DEBUG_KMS("bclk: %lu\n", state->bclk); + + /* + * Compute bit clock and round up to the next MHz. + */ + plld = DIV_ROUND_UP(state->bclk * 8, USEC_PER_SEC) * USEC_PER_SEC; + state->period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, plld); + + err = mipi_dphy_timing_get_default(&state->timing, state->period); + if (err < 0) + return err; + + err = mipi_dphy_timing_validate(&state->timing, state->period); + if (err < 0) { + dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err); + return err; + } + + /* + * We divide the frequency by two here, but we make up for that by + * setting the shift clock divider (further below) to half of the + * correct value. + */ + plld /= 2; + + /* + * Derive pixel clock from bit clock using the shift clock divider. + * Note that this is only half of what we would expect, but we need + * that to make up for the fact that we divided the bit clock by a + * factor of two above. + * + * It's not clear exactly why this is necessary, but the display is + * not working properly otherwise. Perhaps the PLLs cannot generate + * frequencies sufficiently high. + */ + scdiv = ((8 * state->mul) / (state->div * state->lanes)) - 2; + + err = tegra_dc_state_setup_clock(dc, crtc_state, dsi->clk_parent, + plld, scdiv); + if (err < 0) { + dev_err(output->dev, "failed to setup CRTC state: %d\n", err); + return err; + } + + return err; +} + static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = { .dpms = tegra_dsi_encoder_dpms, .mode_fixup = tegra_dsi_encoder_mode_fixup, @@ -936,6 +1058,7 @@ static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = { .commit = tegra_dsi_encoder_commit, .mode_set = tegra_dsi_encoder_mode_set, .disable = tegra_dsi_encoder_disable, + .atomic_check = tegra_dsi_encoder_atomic_check, }; static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) From a9825a6e27a81dac1641ef13fae8b480d7d11d5f Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 8 Dec 2014 16:33:03 +0100 Subject: [PATCH 44/54] drm/tegra: hdmi: Implement ->atomic_check() The implementation of the ->atomic_check() callback precomputes all parameters to check if the given configuration can be applied. If so the precomputed values are stored in the atomic state object for the encoder and applied during modeset. In that way the modeset no longer needs to perform any checking but simply program values into registers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/hdmi.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 728681a3f8c9..abb1ea0385ec 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -1081,6 +1081,27 @@ static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder) } } +static int +tegra_hdmi_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(conn_state->crtc); + unsigned long pclk = crtc_state->mode.clock * 1000; + struct tegra_hdmi *hdmi = to_hdmi(output); + int err; + + err = tegra_dc_state_setup_clock(dc, crtc_state, hdmi->clk_parent, + pclk, 0); + if (err < 0) { + dev_err(output->dev, "failed to setup CRTC state: %d\n", err); + return err; + } + + return err; +} + static const struct drm_encoder_helper_funcs tegra_hdmi_encoder_helper_funcs = { .dpms = tegra_hdmi_encoder_dpms, .mode_fixup = tegra_hdmi_encoder_mode_fixup, @@ -1088,6 +1109,7 @@ static const struct drm_encoder_helper_funcs tegra_hdmi_encoder_helper_funcs = { .commit = tegra_hdmi_encoder_commit, .mode_set = tegra_hdmi_encoder_mode_set, .disable = tegra_hdmi_encoder_disable, + .atomic_check = tegra_hdmi_encoder_atomic_check, }; static int tegra_hdmi_show_regs(struct seq_file *s, void *data) From 82f1511c35643575e6f28013a3bb348e35676491 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 8 Dec 2014 17:26:46 +0100 Subject: [PATCH 45/54] drm/tegra: sor: Implement ->atomic_check() The implementation of the ->atomic_check() callback precomputes all parameters to check if the given configuration can be applied. If so the precomputed values are stored in the atomic state object for the encoder and applied during modeset. In that way the modeset no longer needs to perform any checking but simply program values into registers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 2793e75c6573..7463ea02a083 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -1320,6 +1320,27 @@ unlock: mutex_unlock(&sor->lock); } +static int +tegra_sor_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(conn_state->crtc); + unsigned long pclk = crtc_state->mode.clock * 1000; + struct tegra_sor *sor = to_sor(output); + int err; + + err = tegra_dc_state_setup_clock(dc, crtc_state, sor->clk_parent, + pclk, 0); + if (err < 0) { + dev_err(output->dev, "failed to setup CRTC state: %d\n", err); + return err; + } + + return 0; +} + static const struct drm_encoder_helper_funcs tegra_sor_encoder_helper_funcs = { .dpms = tegra_sor_encoder_dpms, .mode_fixup = tegra_sor_encoder_mode_fixup, @@ -1327,6 +1348,7 @@ static const struct drm_encoder_helper_funcs tegra_sor_encoder_helper_funcs = { .commit = tegra_sor_encoder_commit, .mode_set = tegra_sor_encoder_mode_set, .disable = tegra_sor_encoder_disable, + .atomic_check = tegra_sor_encoder_atomic_check, }; static int tegra_sor_init(struct host1x_client *client) From 76d59ed049197bdaaa24c0e061a105af6ce74457 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 19 Dec 2014 15:09:16 +0100 Subject: [PATCH 46/54] drm/tegra: dc: Use atomic clock state in modeset All clock state is now stored in the display controller's atomic state, so the output drivers no longer need to call back into the display controller driver to set up the clock. This is also required to make sure no hardware changes are made before validating a configuration. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index b905a4e2bdf9..5d6c00abd203 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1213,12 +1213,49 @@ int tegra_dc_state_setup_clock(struct tegra_dc *dc, return 0; } +static void tegra_dc_commit_state(struct tegra_dc *dc, + struct tegra_dc_state *state) +{ + u32 value; + int err; + + err = clk_set_parent(dc->clk, state->clk); + if (err < 0) + dev_err(dc->dev, "failed to set parent clock: %d\n", err); + + /* + * Outputs may not want to change the parent clock rate. This is only + * relevant to Tegra20 where only a single display PLL is available. + * Since that PLL would typically be used for HDMI, an internal LVDS + * panel would need to be driven by some other clock such as PLL_P + * which is shared with other peripherals. Changing the clock rate + * should therefore be avoided. + */ + if (state->pclk > 0) { + err = clk_set_rate(state->clk, state->pclk); + if (err < 0) + dev_err(dc->dev, + "failed to set clock rate to %lu Hz\n", + state->pclk); + } + + DRM_DEBUG_KMS("rate: %lu, div: %u\n", clk_get_rate(dc->clk), + state->div); + DRM_DEBUG_KMS("pclk: %lu\n", state->pclk); + + value = SHIFT_CLK_DIVIDER(state->div) | PIXEL_CLK_DIVIDER_PCD1; + tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL); +} + static void tegra_crtc_mode_set_nofb(struct drm_crtc *crtc) { struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct tegra_dc_state *state = to_dc_state(crtc->state); struct tegra_dc *dc = to_tegra_dc(crtc); u32 value; + tegra_dc_commit_state(dc, state); + /* program display mode */ tegra_dc_set_timings(dc, mode); From 74f48791ad69789bc70eb4d99233d83e016f5f14 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 24 Nov 2014 17:08:20 +0100 Subject: [PATCH 47/54] drm/tegra: Atomic conversion, phase 3, step 2 Replace drm_crtc_helper_set_config() by drm_atomic_helper_set_config(). All drivers have now been converted to use ->atomic_check() to set the atomic state, therefore the atomic mode setting helpers can be used. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 5d6c00abd203..aa66abb57b1c 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1057,7 +1057,7 @@ static void tegra_crtc_atomic_destroy_state(struct drm_crtc *crtc, static const struct drm_crtc_funcs tegra_crtc_funcs = { .page_flip = tegra_dc_page_flip, - .set_config = drm_crtc_helper_set_config, + .set_config = drm_atomic_helper_set_config, .destroy = tegra_dc_destroy, .reset = tegra_crtc_reset, .atomic_duplicate_state = tegra_crtc_atomic_duplicate_state, From 1503ca47d76e184eaeabe7cfa31de97b5ec36a04 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 24 Nov 2014 17:41:23 +0100 Subject: [PATCH 48/54] drm/tegra: Atomic conversion, phase 3, step 3 Provide a custom ->atomic_commit() implementation which supports async commits. The generic atomic page-flip helper can use this to implement page-flipping. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 129 +++--------------------------------- drivers/gpu/drm/tegra/drm.c | 84 ++++++++++++++++++++++- drivers/gpu/drm/tegra/drm.h | 6 ++ 3 files changed, 100 insertions(+), 119 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index aa66abb57b1c..2f9af13905da 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -820,99 +820,6 @@ static int tegra_dc_add_planes(struct drm_device *drm, struct tegra_dc *dc) return 0; } -static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y, - struct drm_framebuffer *fb) -{ - struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); - unsigned int h_offset = 0, v_offset = 0; - struct tegra_bo_tiling tiling; - unsigned long value, flags; - unsigned int format, swap; - int err; - - err = tegra_fb_get_tiling(fb, &tiling); - if (err < 0) - return err; - - spin_lock_irqsave(&dc->lock, flags); - - 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, bo->paddr + value, DC_WINBUF_START_ADDR); - tegra_dc_writel(dc, fb->pitches[0], DC_WIN_LINE_STRIDE); - - format = tegra_dc_format(fb->pixel_format, &swap); - tegra_dc_writel(dc, format, DC_WIN_COLOR_DEPTH); - tegra_dc_writel(dc, swap, DC_WIN_BYTE_SWAP); - - if (dc->soc->supports_block_linear) { - unsigned long height = tiling.value; - - switch (tiling.mode) { - case TEGRA_BO_TILING_MODE_PITCH: - value = DC_WINBUF_SURFACE_KIND_PITCH; - break; - - case TEGRA_BO_TILING_MODE_TILED: - value = DC_WINBUF_SURFACE_KIND_TILED; - break; - - case TEGRA_BO_TILING_MODE_BLOCK: - value = DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(height) | - DC_WINBUF_SURFACE_KIND_BLOCK; - break; - } - - tegra_dc_writel(dc, value, DC_WINBUF_SURFACE_KIND); - } else { - switch (tiling.mode) { - case TEGRA_BO_TILING_MODE_PITCH: - value = DC_WIN_BUFFER_ADDR_MODE_LINEAR_UV | - DC_WIN_BUFFER_ADDR_MODE_LINEAR; - break; - - case TEGRA_BO_TILING_MODE_TILED: - value = DC_WIN_BUFFER_ADDR_MODE_TILE_UV | - DC_WIN_BUFFER_ADDR_MODE_TILE; - break; - - case TEGRA_BO_TILING_MODE_BLOCK: - DRM_ERROR("hardware doesn't support block linear mode\n"); - spin_unlock_irqrestore(&dc->lock, flags); - return -EINVAL; - } - - tegra_dc_writel(dc, value, DC_WIN_BUFFER_ADDR_MODE); - } - - /* make sure bottom-up buffers are properly displayed */ - if (tegra_fb_is_bottom_up(fb)) { - value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); - value |= V_DIRECTION; - tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); - - v_offset += fb->height - 1; - } else { - value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); - value &= ~V_DIRECTION; - tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); - } - - tegra_dc_writel(dc, h_offset, DC_WINBUF_ADDR_H_OFFSET); - tegra_dc_writel(dc, v_offset, DC_WINBUF_ADDR_V_OFFSET); - - value = GENERAL_ACT_REQ | WIN_A_ACT_REQ; - tegra_dc_writel(dc, value << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); - - spin_unlock_irqrestore(&dc->lock, flags); - - return 0; -} - void tegra_dc_enable_vblank(struct tegra_dc *dc) { unsigned long value, flags; @@ -991,30 +898,6 @@ void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file) 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, uint32_t page_flip_flags) -{ - unsigned int pipe = drm_crtc_index(crtc); - struct tegra_dc *dc = to_tegra_dc(crtc); - - if (dc->event) - return -EBUSY; - - if (event) { - event->pipe = pipe; - dc->event = event; - drm_crtc_vblank_get(crtc); - } - - if (crtc->primary->state) - drm_atomic_set_fb_for_plane(crtc->primary->state, fb); - - tegra_dc_set_base(dc, 0, 0, fb); - crtc->primary->fb = fb; - - return 0; -} - static void tegra_dc_destroy(struct drm_crtc *crtc) { drm_crtc_cleanup(crtc); @@ -1056,7 +939,7 @@ static void tegra_crtc_atomic_destroy_state(struct drm_crtc *crtc, } static const struct drm_crtc_funcs tegra_crtc_funcs = { - .page_flip = tegra_dc_page_flip, + .page_flip = drm_atomic_helper_page_flip, .set_config = drm_atomic_helper_set_config, .destroy = tegra_dc_destroy, .reset = tegra_crtc_reset, @@ -1326,6 +1209,16 @@ static int tegra_crtc_atomic_check(struct drm_crtc *crtc, static void tegra_crtc_atomic_begin(struct drm_crtc *crtc) { + struct tegra_dc *dc = to_tegra_dc(crtc); + + if (crtc->state->event) { + crtc->state->event->pipe = drm_crtc_index(crtc); + + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + dc->event = crtc->state->event; + crtc->state->event = NULL; + } } static void tegra_crtc_atomic_flush(struct drm_crtc *crtc) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index d01484cf3432..0edfb5e0b374 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -10,6 +10,7 @@ #include #include +#include #include #include "drm.h" @@ -26,13 +27,90 @@ struct tegra_drm_file { struct list_head contexts; }; +static void tegra_atomic_schedule(struct tegra_drm *tegra, + struct drm_atomic_state *state) +{ + tegra->commit.state = state; + schedule_work(&tegra->commit.work); +} + +static void tegra_atomic_complete(struct tegra_drm *tegra, + struct drm_atomic_state *state) +{ + struct drm_device *drm = tegra->drm; + + /* + * Everything below can be run asynchronously without the need to grab + * any modeset locks at all under one condition: It must be guaranteed + * that the asynchronous work has either been cancelled (if the driver + * supports it, which at least requires that the framebuffers get + * cleaned up with drm_atomic_helper_cleanup_planes()) or completed + * before the new state gets committed on the software side with + * drm_atomic_helper_swap_state(). + * + * This scheme allows new atomic state updates to be prepared and + * checked in parallel to the asynchronous completion of the previous + * update. Which is important since compositors need to figure out the + * composition of the next frame right after having submitted the + * current layout. + */ + + drm_atomic_helper_commit_pre_planes(drm, state); + drm_atomic_helper_commit_planes(drm, state); + drm_atomic_helper_commit_post_planes(drm, state); + + drm_atomic_helper_wait_for_vblanks(drm, state); + + drm_atomic_helper_cleanup_planes(drm, state); + drm_atomic_state_free(state); +} + +static void tegra_atomic_work(struct work_struct *work) +{ + struct tegra_drm *tegra = container_of(work, struct tegra_drm, + commit.work); + + tegra_atomic_complete(tegra, tegra->commit.state); +} + +static int tegra_atomic_commit(struct drm_device *drm, + struct drm_atomic_state *state, bool async) +{ + struct tegra_drm *tegra = drm->dev_private; + int err; + + err = drm_atomic_helper_prepare_planes(drm, state); + if (err) + return err; + + /* serialize outstanding asynchronous commits */ + mutex_lock(&tegra->commit.lock); + flush_work(&tegra->commit.work); + + /* + * This is the point of no return - everything below never fails except + * when the hw goes bonghits. Which means we can commit the new state on + * the software side now. + */ + + drm_atomic_helper_swap_state(drm, state); + + if (async) + tegra_atomic_schedule(tegra, state); + else + tegra_atomic_complete(tegra, state); + + mutex_unlock(&tegra->commit.lock); + return 0; +} + static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { .fb_create = tegra_fb_create, #ifdef CONFIG_DRM_TEGRA_FBDEV .output_poll_changed = tegra_fb_output_poll_changed, #endif .atomic_check = drm_atomic_helper_check, - .atomic_commit = drm_atomic_helper_commit, + .atomic_commit = tegra_atomic_commit, }; static int tegra_drm_load(struct drm_device *drm, unsigned long flags) @@ -58,6 +136,10 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) mutex_init(&tegra->clients_lock); INIT_LIST_HEAD(&tegra->clients); + + mutex_init(&tegra->commit.lock); + INIT_WORK(&tegra->commit.work, tegra_atomic_work); + drm->dev_private = tegra; tegra->drm = drm; diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 3db719de312f..b1c7027b26e7 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -50,6 +50,12 @@ struct tegra_drm { #endif unsigned int pitch_align; + + struct { + struct drm_atomic_state *state; + struct work_struct work; + struct mutex lock; + } commit; }; struct tegra_drm_client; From 3f0fb52ef013e76159b35386f22924f99d8034a4 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 19 Dec 2014 15:19:21 +0100 Subject: [PATCH 49/54] drm/tegra: Remove unused ->mode_fixup() callbacks All output drivers have now been converted to use the ->atomic_check() callback, so the ->mode_fixup() callbacks are no longer used. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 87 ------------------------------------ drivers/gpu/drm/tegra/hdmi.c | 27 ----------- drivers/gpu/drm/tegra/rgb.c | 38 ---------------- drivers/gpu/drm/tegra/sor.c | 27 ----------- 4 files changed, 179 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 00b307120cdd..6875885a2dca 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -786,92 +786,6 @@ static void tegra_dsi_encoder_dpms(struct drm_encoder *encoder, int mode) { } -static bool tegra_dsi_encoder_mode_fixup(struct drm_encoder *encoder, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted) -{ - struct tegra_output *output = encoder_to_output(encoder); - struct tegra_dc *dc = to_tegra_dc(encoder->crtc); - unsigned int mul, div, scdiv, vrefresh, lanes; - struct tegra_dsi *dsi = to_dsi(output); - struct mipi_dphy_timing timing; - unsigned long pclk, bclk, plld; - unsigned long period; - int err; - - lanes = tegra_dsi_get_lanes(dsi); - pclk = mode->clock * 1000; - - err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); - if (err < 0) - return err; - - DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", mul, div, lanes); - vrefresh = drm_mode_vrefresh(mode); - DRM_DEBUG_KMS("vrefresh: %u\n", vrefresh); - - /* compute byte clock */ - bclk = (pclk * mul) / (div * lanes); - - /* - * Compute bit clock and round up to the next MHz. - */ - plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC) * USEC_PER_SEC; - period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, plld); - - /* - * We divide the frequency by two here, but we make up for that by - * setting the shift clock divider (further below) to half of the - * correct value. - */ - plld /= 2; - - /* - * Derive pixel clock from bit clock using the shift clock divider. - * Note that this is only half of what we would expect, but we need - * that to make up for the fact that we divided the bit clock by a - * factor of two above. - * - * It's not clear exactly why this is necessary, but the display is - * not working properly otherwise. Perhaps the PLLs cannot generate - * frequencies sufficiently high. - */ - scdiv = ((8 * mul) / (div * lanes)) - 2; - - err = tegra_dc_setup_clock(dc, dsi->clk_parent, plld, scdiv); - if (err < 0) { - dev_err(output->dev, "failed to setup DC clock: %d\n", err); - return false; - } - - err = clk_set_rate(dsi->clk_parent, plld); - if (err < 0) { - dev_err(dsi->dev, "failed to set clock rate to %lu Hz\n", - plld); - return false; - } - - tegra_dsi_set_timeout(dsi, bclk, vrefresh); - - err = mipi_dphy_timing_get_default(&timing, period); - if (err < 0) - return err; - - err = mipi_dphy_timing_validate(&timing, period); - if (err < 0) { - dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err); - return err; - } - - /* - * The D-PHY timing fields are expressed in byte-clock cycles, so - * multiply the period by 8. - */ - tegra_dsi_set_phy_timing(dsi, period * 8, &timing); - - return true; -} - static void tegra_dsi_encoder_prepare(struct drm_encoder *encoder) { } @@ -1053,7 +967,6 @@ tegra_dsi_encoder_atomic_check(struct drm_encoder *encoder, static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = { .dpms = tegra_dsi_encoder_dpms, - .mode_fixup = tegra_dsi_encoder_mode_fixup, .prepare = tegra_dsi_encoder_prepare, .commit = tegra_dsi_encoder_commit, .mode_set = tegra_dsi_encoder_mode_set, diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index abb1ea0385ec..07771956cc94 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -822,32 +822,6 @@ static void tegra_hdmi_encoder_dpms(struct drm_encoder *encoder, int mode) { } -static bool tegra_hdmi_encoder_mode_fixup(struct drm_encoder *encoder, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted) -{ - struct tegra_output *output = encoder_to_output(encoder); - struct tegra_dc *dc = to_tegra_dc(encoder->crtc); - struct tegra_hdmi *hdmi = to_hdmi(output); - unsigned long pclk = mode->clock * 1000; - int err; - - err = tegra_dc_setup_clock(dc, hdmi->clk_parent, pclk, 0); - if (err < 0) { - dev_err(output->dev, "failed to setup DC clock: %d\n", err); - return false; - } - - err = clk_set_rate(hdmi->clk_parent, pclk); - if (err < 0) { - dev_err(output->dev, "failed to set clock rate to %lu Hz\n", - pclk); - return false; - } - - return true; -} - static void tegra_hdmi_encoder_prepare(struct drm_encoder *encoder) { } @@ -1104,7 +1078,6 @@ tegra_hdmi_encoder_atomic_check(struct drm_encoder *encoder, static const struct drm_encoder_helper_funcs tegra_hdmi_encoder_helper_funcs = { .dpms = tegra_hdmi_encoder_dpms, - .mode_fixup = tegra_hdmi_encoder_mode_fixup, .prepare = tegra_hdmi_encoder_prepare, .commit = tegra_hdmi_encoder_commit, .mode_set = tegra_hdmi_encoder_mode_set, diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index be1b38936dbe..0c8b458b2364 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -129,43 +129,6 @@ static void tegra_rgb_encoder_dpms(struct drm_encoder *encoder, int mode) { } -static bool tegra_rgb_encoder_mode_fixup(struct drm_encoder *encoder, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted) -{ - struct tegra_output *output = encoder_to_output(encoder); - unsigned long pclk = mode->clock * 1000; - struct tegra_rgb *rgb = to_rgb(output); - unsigned int div; - int err; - - /* - * We may not want to change the frequency of the parent clock, since - * it may be a parent for other peripherals. This is due to the fact - * that on Tegra20 there's only a single clock dedicated to display - * (pll_d_out0), whereas later generations have a second one that can - * be used to independently drive a second output (pll_d2_out0). - * - * As a way to support multiple outputs on Tegra20 as well, pll_p is - * typically used as the parent clock for the display controllers. - * But this comes at a cost: pll_p is the parent of several other - * peripherals, so its frequency shouldn't change out of the blue. - * - * The best we can do at this point is to use the shift clock divider - * and hope that the desired frequency can be matched (or at least - * matched sufficiently close that the panel will still work). - */ - div = ((clk_get_rate(rgb->clk) * 2) / pclk) - 2; - - err = tegra_dc_setup_clock(rgb->dc, rgb->clk_parent, pclk, div); - if (err < 0) { - dev_err(output->dev, "failed to setup DC clock: %d\n", err); - return false; - } - - return true; -} - static void tegra_rgb_encoder_prepare(struct drm_encoder *encoder) { } @@ -278,7 +241,6 @@ tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder, static const struct drm_encoder_helper_funcs tegra_rgb_encoder_helper_funcs = { .dpms = tegra_rgb_encoder_dpms, - .mode_fixup = tegra_rgb_encoder_mode_fixup, .prepare = tegra_rgb_encoder_prepare, .commit = tegra_rgb_encoder_commit, .mode_set = tegra_rgb_encoder_mode_set, diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 7463ea02a083..e813df71e30c 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -781,32 +781,6 @@ static void tegra_sor_encoder_dpms(struct drm_encoder *encoder, int mode) { } -static bool tegra_sor_encoder_mode_fixup(struct drm_encoder *encoder, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted) -{ - struct tegra_output *output = encoder_to_output(encoder); - struct tegra_dc *dc = to_tegra_dc(encoder->crtc); - unsigned long pclk = mode->clock * 1000; - struct tegra_sor *sor = to_sor(output); - int err; - - err = tegra_dc_setup_clock(dc, sor->clk_parent, pclk, 0); - if (err < 0) { - dev_err(output->dev, "failed to setup DC clock: %d\n", err); - return false; - } - - err = clk_set_rate(sor->clk_parent, pclk); - if (err < 0) { - dev_err(output->dev, "failed to set clock rate to %lu Hz\n", - pclk); - return false; - } - - return true; -} - static void tegra_sor_encoder_prepare(struct drm_encoder *encoder) { } @@ -1343,7 +1317,6 @@ tegra_sor_encoder_atomic_check(struct drm_encoder *encoder, static const struct drm_encoder_helper_funcs tegra_sor_encoder_helper_funcs = { .dpms = tegra_sor_encoder_dpms, - .mode_fixup = tegra_sor_encoder_mode_fixup, .prepare = tegra_sor_encoder_prepare, .commit = tegra_sor_encoder_commit, .mode_set = tegra_sor_encoder_mode_set, From 47802b09a9c2d1f8a562c7fae2f61a8923ac8d06 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 26 Nov 2014 12:28:39 +0100 Subject: [PATCH 50/54] drm/tegra: Track active planes in CRTC state Wrap struct drm_crtc_state in a driver-specific structure and add the planes field which keeps track of which planes are updated or disabled during a modeset. This allows atomic updates of the the display engine at ->atomic_flush() time. v2: open-code getting the state of the CRTC that the plane is being attached to (Daniel Vetter) Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 72 +++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 2f9af13905da..4073a7b5bf26 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -47,6 +47,8 @@ struct tegra_dc_state { struct clk *clk; unsigned long pclk; unsigned int div; + + u32 planes; }; static inline struct tegra_dc_state *to_dc_state(struct drm_crtc_state *state) @@ -57,20 +59,6 @@ static inline struct tegra_dc_state *to_dc_state(struct drm_crtc_state *state) return NULL; } -static void tegra_dc_window_commit(struct tegra_dc *dc, unsigned int index) -{ - u32 value = WIN_A_ACT_REQ << index; - - tegra_dc_writel(dc, value << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); -} - -static void tegra_dc_cursor_commit(struct tegra_dc *dc) -{ - tegra_dc_writel(dc, CURSOR_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); -} - /* * Reads the active copy of a register. This takes the dc->lock spinlock to * prevent races with the VBLANK processing which also needs access to the @@ -388,8 +376,6 @@ static void tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, break; } - tegra_dc_window_commit(dc, index); - spin_unlock_irqrestore(&dc->lock, flags); } @@ -432,9 +418,28 @@ static void tegra_plane_cleanup_fb(struct drm_plane *plane, { } +static int tegra_plane_state_add(struct tegra_plane *plane, + struct drm_plane_state *state) +{ + struct drm_crtc_state *crtc_state; + struct tegra_dc_state *tegra; + + /* Propagate errors from allocation or locking failures. */ + crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + tegra = to_dc_state(crtc_state); + + tegra->planes |= WIN_A_ACT_REQ << plane->index; + + return 0; +} + static int tegra_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) { + struct tegra_plane *tegra = to_tegra_plane(plane); struct tegra_dc *dc = to_tegra_dc(state->crtc); struct tegra_bo_tiling tiling; int err; @@ -465,6 +470,10 @@ static int tegra_plane_atomic_check(struct drm_plane *plane, } } + err = tegra_plane_state_add(tegra, state); + if (err < 0) + return err; + return 0; } @@ -531,8 +540,6 @@ static void tegra_plane_atomic_disable(struct drm_plane *plane, value &= ~WIN_ENABLE; tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); - tegra_dc_window_commit(dc, p->index); - spin_unlock_irqrestore(&dc->lock, flags); } @@ -592,6 +599,9 @@ static const u32 tegra_cursor_plane_formats[] = { static int tegra_cursor_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) { + struct tegra_plane *tegra = to_tegra_plane(plane); + int err; + /* no need for further checks if the plane is being disabled */ if (!state->crtc) return 0; @@ -609,6 +619,10 @@ static int tegra_cursor_atomic_check(struct drm_plane *plane, state->crtc_w != 128 && state->crtc_w != 256) return -EINVAL; + err = tegra_plane_state_add(tegra, state); + if (err < 0) + return err; + return 0; } @@ -673,9 +687,6 @@ static void tegra_cursor_atomic_update(struct drm_plane *plane, value = (state->crtc_y & 0x3fff) << 16 | (state->crtc_x & 0x3fff); tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION); - /* apply changes */ - tegra_dc_cursor_commit(dc); - tegra_dc_commit(dc); } static void tegra_cursor_atomic_disable(struct drm_plane *plane, @@ -693,9 +704,6 @@ static void tegra_cursor_atomic_disable(struct drm_plane *plane, value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value &= ~CURSOR_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - - tegra_dc_cursor_commit(dc); - tegra_dc_commit(dc); } static const struct drm_plane_funcs tegra_cursor_plane_funcs = { @@ -727,6 +735,13 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, if (!plane) return ERR_PTR(-ENOMEM); + /* + * We'll treat the cursor as an overlay plane with index 6 here so + * that the update and activation request bits in DC_CMD_STATE_CONTROL + * match up. + */ + plane->index = 6; + num_formats = ARRAY_SIZE(tegra_cursor_plane_formats); formats = tegra_cursor_plane_formats; @@ -1022,7 +1037,6 @@ static void tegra_crtc_disable(struct drm_crtc *crtc) } drm_crtc_vblank_off(crtc); - tegra_dc_commit(dc); } static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc, @@ -1195,10 +1209,7 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc) static void tegra_crtc_commit(struct drm_crtc *crtc) { - struct tegra_dc *dc = to_tegra_dc(crtc); - drm_crtc_vblank_on(crtc); - tegra_dc_commit(dc); } static int tegra_crtc_atomic_check(struct drm_crtc *crtc, @@ -1223,6 +1234,11 @@ static void tegra_crtc_atomic_begin(struct drm_crtc *crtc) static void tegra_crtc_atomic_flush(struct drm_crtc *crtc) { + struct tegra_dc_state *state = to_dc_state(crtc->state); + struct tegra_dc *dc = to_tegra_dc(crtc); + + tegra_dc_writel(dc, state->planes << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, state->planes, DC_CMD_STATE_CONTROL); } static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = { From 8f604f8c4dd2d5383f567856450ba12764061c12 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 28 Nov 2014 13:14:55 +0100 Subject: [PATCH 51/54] drm/tegra: Track tiling and format in plane state Tracking these in the plane state allows them to be computed in the ->atomic_check() callback and reused when applying the configuration in ->atomic_update(). Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 127 ++++++++++++++++++++++++++++-------- drivers/gpu/drm/tegra/drm.h | 4 +- 2 files changed, 101 insertions(+), 30 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 4073a7b5bf26..0cceabd11798 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -59,6 +59,23 @@ static inline struct tegra_dc_state *to_dc_state(struct drm_crtc_state *state) return NULL; } +struct tegra_plane_state { + struct drm_plane_state base; + + struct tegra_bo_tiling tiling; + u32 format; + u32 swap; +}; + +static inline struct tegra_plane_state * +to_tegra_plane_state(struct drm_plane_state *state) +{ + if (state) + return container_of(state, struct tegra_plane_state, base); + + return NULL; +} + /* * Reads the active copy of a register. This takes the dc->lock spinlock to * prevent races with the VBLANK processing which also needs access to the @@ -97,43 +114,49 @@ void tegra_dc_commit(struct tegra_dc *dc) tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); } -static unsigned int tegra_dc_format(uint32_t format, uint32_t *swap) +static int tegra_dc_format(u32 fourcc, u32 *format, u32 *swap) { /* assume no swapping of fetched data */ if (swap) *swap = BYTE_SWAP_NOSWAP; - switch (format) { + switch (fourcc) { case DRM_FORMAT_XBGR8888: - return WIN_COLOR_DEPTH_R8G8B8A8; + *format = WIN_COLOR_DEPTH_R8G8B8A8; + break; case DRM_FORMAT_XRGB8888: - return WIN_COLOR_DEPTH_B8G8R8A8; + *format = WIN_COLOR_DEPTH_B8G8R8A8; + break; case DRM_FORMAT_RGB565: - return WIN_COLOR_DEPTH_B5G6R5; + *format = WIN_COLOR_DEPTH_B5G6R5; + break; case DRM_FORMAT_UYVY: - return WIN_COLOR_DEPTH_YCbCr422; + *format = WIN_COLOR_DEPTH_YCbCr422; + break; case DRM_FORMAT_YUYV: if (swap) *swap = BYTE_SWAP_SWAP2; - return WIN_COLOR_DEPTH_YCbCr422; + *format = WIN_COLOR_DEPTH_YCbCr422; + break; case DRM_FORMAT_YUV420: - return WIN_COLOR_DEPTH_YCbCr420P; + *format = WIN_COLOR_DEPTH_YCbCr420P; + break; case DRM_FORMAT_YUV422: - return WIN_COLOR_DEPTH_YCbCr422P; + *format = WIN_COLOR_DEPTH_YCbCr422P; + break; default: - break; + return -EINVAL; } - WARN(1, "unsupported pixel format %u, using default\n", format); - return WIN_COLOR_DEPTH_B8G8R8A8; + return 0; } static bool tegra_dc_format_is_yuv(unsigned int format, bool *planar) @@ -398,13 +421,54 @@ static void tegra_primary_plane_destroy(struct drm_plane *plane) tegra_plane_destroy(plane); } +static void tegra_plane_reset(struct drm_plane *plane) +{ + struct tegra_plane_state *state; + + if (plane->state && plane->state->fb) + drm_framebuffer_unreference(plane->state->fb); + + kfree(plane->state); + plane->state = NULL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) { + plane->state = &state->base; + plane->state->plane = plane; + } +} + +static struct drm_plane_state *tegra_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct tegra_plane_state *state = to_tegra_plane_state(plane->state); + struct tegra_plane_state *copy; + + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (!copy) + return NULL; + + if (copy->base.fb) + drm_framebuffer_reference(copy->base.fb); + + return ©->base; +} + +static void tegra_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + if (state->fb) + drm_framebuffer_unreference(state->fb); + + kfree(state); +} + static const struct drm_plane_funcs tegra_primary_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = tegra_primary_plane_destroy, - .reset = drm_atomic_helper_plane_reset, - .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .reset = tegra_plane_reset, + .atomic_duplicate_state = tegra_plane_atomic_duplicate_state, + .atomic_destroy_state = tegra_plane_atomic_destroy_state, }; static int tegra_plane_prepare_fb(struct drm_plane *plane, @@ -439,20 +503,26 @@ static int tegra_plane_state_add(struct tegra_plane *plane, static int tegra_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) { + struct tegra_plane_state *plane_state = to_tegra_plane_state(state); + struct tegra_bo_tiling *tiling = &plane_state->tiling; struct tegra_plane *tegra = to_tegra_plane(plane); struct tegra_dc *dc = to_tegra_dc(state->crtc); - struct tegra_bo_tiling tiling; int err; /* no need for further checks if the plane is being disabled */ if (!state->crtc) return 0; - err = tegra_fb_get_tiling(state->fb, &tiling); + err = tegra_dc_format(state->fb->pixel_format, &plane_state->format, + &plane_state->swap); if (err < 0) return err; - if (tiling.mode == TEGRA_BO_TILING_MODE_BLOCK && + err = tegra_fb_get_tiling(state->fb, tiling); + if (err < 0) + return err; + + if (tiling->mode == TEGRA_BO_TILING_MODE_BLOCK && !dc->soc->supports_block_linear) { DRM_ERROR("hardware doesn't support block linear mode\n"); return -EINVAL; @@ -480,12 +550,12 @@ static int tegra_plane_atomic_check(struct drm_plane *plane, static void tegra_plane_atomic_update(struct drm_plane *plane, struct drm_plane_state *old_state) { + struct tegra_plane_state *state = to_tegra_plane_state(plane->state); struct tegra_dc *dc = to_tegra_dc(plane->state->crtc); struct drm_framebuffer *fb = plane->state->fb; struct tegra_plane *p = to_tegra_plane(plane); struct tegra_dc_window window; unsigned int i; - int err; /* rien ne va plus */ if (!plane->state->crtc || !plane->state->fb) @@ -500,12 +570,13 @@ static void tegra_plane_atomic_update(struct drm_plane *plane, window.dst.y = plane->state->crtc_y; window.dst.w = plane->state->crtc_w; window.dst.h = plane->state->crtc_h; - window.format = tegra_dc_format(fb->pixel_format, &window.swap); window.bits_per_pixel = fb->bits_per_pixel; window.bottom_up = tegra_fb_is_bottom_up(fb); - err = tegra_fb_get_tiling(fb, &window.tiling); - WARN_ON(err < 0); + /* copy from state */ + window.tiling = state->tiling; + window.format = state->format; + window.swap = state->swap; for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) { struct tegra_bo *bo = tegra_fb_get_plane(fb, i); @@ -710,9 +781,9 @@ static const struct drm_plane_funcs tegra_cursor_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = tegra_plane_destroy, - .reset = drm_atomic_helper_plane_reset, - .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .reset = tegra_plane_reset, + .atomic_duplicate_state = tegra_plane_atomic_duplicate_state, + .atomic_destroy_state = tegra_plane_atomic_destroy_state, }; static const struct drm_plane_helper_funcs tegra_cursor_plane_helper_funcs = { @@ -767,9 +838,9 @@ static const struct drm_plane_funcs tegra_overlay_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = tegra_overlay_plane_destroy, - .reset = drm_atomic_helper_plane_reset, - .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .reset = tegra_plane_reset, + .atomic_duplicate_state = tegra_plane_atomic_duplicate_state, + .atomic_destroy_state = tegra_plane_atomic_destroy_state, }; static const uint32_t tegra_overlay_plane_formats[] = { diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index b1c7027b26e7..8cb2dfeaa957 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -170,13 +170,13 @@ struct tegra_dc_window { unsigned int h; } dst; unsigned int bits_per_pixel; - unsigned int format; - unsigned int swap; unsigned int stride[2]; unsigned long base[3]; bool bottom_up; struct tegra_bo_tiling tiling; + u32 format; + u32 swap; }; /* from dc.c */ From 666cb873328b5075eb511662858bab02d084ff64 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 8 Dec 2014 16:32:47 +0100 Subject: [PATCH 52/54] drm/tegra: dc: Unify enabling the display controller Previously output drivers would enable continuous display mode and power up the display controller at various points during the initialization. This is suboptimal because it accesses display controller registers in output drivers and duplicates a bit of code. Move this code into the display controller driver and enable the display controller as the final step of the ->mode_set_nofb() implementation. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 12 ++++++++++++ drivers/gpu/drm/tegra/dsi.c | 10 ---------- drivers/gpu/drm/tegra/hdmi.c | 10 ---------- drivers/gpu/drm/tegra/rgb.c | 11 +---------- drivers/gpu/drm/tegra/sor.c | 25 +++---------------------- 5 files changed, 16 insertions(+), 52 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 0cceabd11798..3aaa84ae2681 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1236,6 +1236,18 @@ static void tegra_crtc_mode_set_nofb(struct drm_crtc *crtc) value &= ~INTERLACE_ENABLE; tegra_dc_writel(dc, value, DC_DISP_INTERLACE_CONTROL); } + + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + value |= DISP_CTRL_MODE_C_DISPLAY; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); + value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + + tegra_dc_commit(dc); } static void tegra_crtc_prepare(struct drm_crtc *crtc) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 6875885a2dca..ed970f622903 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -824,16 +824,6 @@ static void tegra_dsi_encoder_mode_set(struct drm_encoder *encoder, value |= DSI_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - value |= DISP_CTRL_MODE_C_DISPLAY; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); - - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); - value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - tegra_dc_commit(dc); /* enable DSI controller */ diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 07771956cc94..7e06657ae58b 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -1022,16 +1022,6 @@ static void tegra_hdmi_encoder_mode_set(struct drm_encoder *encoder, value |= HDMI_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - value |= DISP_CTRL_MODE_C_DISPLAY; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); - - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); - value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - tegra_dc_commit(dc); /* TODO: add HDCP support */ diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index 0c8b458b2364..7cd833f5b5b5 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -168,16 +168,6 @@ static void tegra_rgb_encoder_mode_set(struct drm_encoder *encoder, value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE; tegra_dc_writel(rgb->dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS); - value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_COMMAND); - value &= ~DISP_CTRL_MODE_MASK; - value |= DISP_CTRL_MODE_C_DISPLAY; - tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_COMMAND); - - value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_POWER_CONTROL); - value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; - tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - tegra_dc_commit(rgb->dc); if (output->panel) @@ -193,6 +183,7 @@ static void tegra_rgb_encoder_disable(struct drm_encoder *encoder) drm_panel_disable(output->panel); tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable)); + tegra_dc_commit(rgb->dc); if (output->panel) drm_panel_unprepare(output->panel); diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index e813df71e30c..2afe478ded3b 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -261,17 +261,8 @@ static int tegra_sor_attach(struct tegra_sor *sor) static int tegra_sor_wakeup(struct tegra_sor *sor) { - struct tegra_dc *dc = to_tegra_dc(sor->output.encoder.crtc); unsigned long value, timeout; - /* enable display controller outputs */ - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); - value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - - tegra_dc_commit(dc); - timeout = jiffies + msecs_to_jiffies(250); /* wait for head to wake up */ @@ -1112,18 +1103,6 @@ static void tegra_sor_encoder_mode_set(struct drm_encoder *encoder, goto unlock; } - /* start display controller in continuous mode */ - value = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); - value |= WRITE_MUX; - tegra_dc_writel(dc, value, DC_CMD_STATE_ACCESS); - - tegra_dc_writel(dc, VSYNC_H_POSITION(1), DC_DISP_DISP_TIMING_OPTIONS); - tegra_dc_writel(dc, DISP_CTRL_MODE_C_DISPLAY, DC_CMD_DISPLAY_COMMAND); - - value = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); - value &= ~WRITE_MUX; - tegra_dc_writel(dc, value, DC_CMD_STATE_ACCESS); - /* * configure panel (24bpp, vsync-, hsync-, DP-A protocol, complete * raster, associate with display controller) @@ -1198,11 +1177,13 @@ static void tegra_sor_encoder_mode_set(struct drm_encoder *encoder, goto unlock; } + tegra_sor_update(sor); + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value |= SOR_ENABLE; tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - tegra_sor_update(sor); + tegra_dc_commit(dc); err = tegra_sor_attach(sor); if (err < 0) { From 359ae687dbd16c76f5519a5750444f5a3b00ad18 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 18 Dec 2014 17:15:25 +0100 Subject: [PATCH 53/54] drm/tegra: Add minimal power management For now only disable the KMS hotplug polling helper logic upon suspend and re-enable it on resume. Reviewed-by: Sean Paul Reviewed-by: Mark Zhang Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 0edfb5e0b374..5bccb20889f7 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -996,6 +996,30 @@ static int host1x_drm_remove(struct host1x_device *dev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int host1x_drm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + drm_kms_helper_poll_disable(drm); + + return 0; +} + +static int host1x_drm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + drm_kms_helper_poll_enable(drm); + + return 0; +} +#endif + +static const struct dev_pm_ops host1x_drm_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(host1x_drm_suspend, host1x_drm_resume) +}; + static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra20-dc", }, { .compatible = "nvidia,tegra20-hdmi", }, @@ -1017,6 +1041,7 @@ static const struct of_device_id host1x_drm_subdevs[] = { static struct host1x_driver host1x_drm_driver = { .driver = { .name = "drm", + .pm = &host1x_drm_pm_ops, }, .probe = host1x_drm_probe, .remove = host1x_drm_remove, From 31f40f86526b71009973854c1dfe799ee70f7588 Mon Sep 17 00:00:00 2001 From: David Ung Date: Tue, 20 Jan 2015 18:37:35 -0800 Subject: [PATCH 54/54] drm/tegra: Use correct relocation target offsets When copying a relocation from userspace, copy the correct target offset. Signed-off-by: David Ung Fixes: 961e3beae3b2 ("drm/tegra: Make job submission 64-bit safe") Cc: stable@vger.kernel.org [treding@nvidia.com: provide a better commit message] Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 5bccb20889f7..7dd328d77996 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -293,7 +293,7 @@ static int host1x_reloc_copy_from_user(struct host1x_reloc *dest, if (err < 0) return err; - err = get_user(dest->target.offset, &src->cmdbuf.offset); + err = get_user(dest->target.offset, &src->target.offset); if (err < 0) return err;