diff options
Diffstat (limited to 'drivers/gpu/drm/tegra/host1x.c')
-rw-r--r-- | drivers/gpu/drm/tegra/host1x.c | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/drivers/gpu/drm/tegra/host1x.c b/drivers/gpu/drm/tegra/host1x.c new file mode 100644 index 000000000000..5d17b113a6fc --- /dev/null +++ b/drivers/gpu/drm/tegra/host1x.c @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include "drm.h" + +struct host1x_drm_client { + struct host1x_client *client; + struct device_node *np; + struct list_head list; +}; + +static int host1x_add_drm_client(struct host1x *host1x, struct device_node *np) +{ + struct host1x_drm_client *client; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + + INIT_LIST_HEAD(&client->list); + client->np = of_node_get(np); + + list_add_tail(&client->list, &host1x->drm_clients); + + return 0; +} + +static int host1x_activate_drm_client(struct host1x *host1x, + struct host1x_drm_client *drm, + struct host1x_client *client) +{ + mutex_lock(&host1x->drm_clients_lock); + list_del_init(&drm->list); + list_add_tail(&drm->list, &host1x->drm_active); + drm->client = client; + mutex_unlock(&host1x->drm_clients_lock); + + return 0; +} + +static int host1x_remove_drm_client(struct host1x *host1x, + struct host1x_drm_client *client) +{ + mutex_lock(&host1x->drm_clients_lock); + list_del_init(&client->list); + mutex_unlock(&host1x->drm_clients_lock); + + of_node_put(client->np); + kfree(client); + + return 0; +} + +static int host1x_parse_dt(struct host1x *host1x) +{ + static const char * const compat[] = { + "nvidia,tegra20-dc", + "nvidia,tegra20-hdmi", + "nvidia,tegra30-dc", + "nvidia,tegra30-hdmi", + }; + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(compat); i++) { + struct device_node *np; + + for_each_child_of_node(host1x->dev->of_node, np) { + if (of_device_is_compatible(np, compat[i]) && + of_device_is_available(np)) { + err = host1x_add_drm_client(host1x, np); + if (err < 0) + return err; + } + } + } + + return 0; +} + +static int tegra_host1x_probe(struct platform_device *pdev) +{ + struct host1x *host1x; + struct resource *regs; + int err; + + host1x = devm_kzalloc(&pdev->dev, sizeof(*host1x), GFP_KERNEL); + if (!host1x) + return -ENOMEM; + + mutex_init(&host1x->drm_clients_lock); + INIT_LIST_HEAD(&host1x->drm_clients); + INIT_LIST_HEAD(&host1x->drm_active); + mutex_init(&host1x->clients_lock); + INIT_LIST_HEAD(&host1x->clients); + host1x->dev = &pdev->dev; + + err = host1x_parse_dt(host1x); + if (err < 0) { + dev_err(&pdev->dev, "failed to parse DT: %d\n", err); + return err; + } + + host1x->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host1x->clk)) + return PTR_ERR(host1x->clk); + + err = clk_prepare_enable(host1x->clk); + if (err < 0) + return err; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + err = -ENXIO; + goto err; + } + + err = platform_get_irq(pdev, 0); + if (err < 0) + goto err; + + host1x->syncpt = err; + + err = platform_get_irq(pdev, 1); + if (err < 0) + goto err; + + host1x->irq = err; + + host1x->regs = devm_request_and_ioremap(&pdev->dev, regs); + if (!host1x->regs) { + err = -EADDRNOTAVAIL; + goto err; + } + + platform_set_drvdata(pdev, host1x); + + return 0; + +err: + clk_disable_unprepare(host1x->clk); + return err; +} + +static int tegra_host1x_remove(struct platform_device *pdev) +{ + struct host1x *host1x = platform_get_drvdata(pdev); + + clk_disable_unprepare(host1x->clk); + + return 0; +} + +int host1x_drm_init(struct host1x *host1x, struct drm_device *drm) +{ + struct host1x_client *client; + + mutex_lock(&host1x->clients_lock); + + list_for_each_entry(client, &host1x->clients, list) { + if (client->ops && client->ops->drm_init) { + int err = client->ops->drm_init(client, drm); + if (err < 0) { + dev_err(host1x->dev, + "DRM setup failed for %s: %d\n", + dev_name(client->dev), err); + return err; + } + } + } + + mutex_unlock(&host1x->clients_lock); + + return 0; +} + +int host1x_drm_exit(struct host1x *host1x) +{ + struct platform_device *pdev = to_platform_device(host1x->dev); + struct host1x_client *client; + + if (!host1x->drm) + return 0; + + mutex_lock(&host1x->clients_lock); + + list_for_each_entry_reverse(client, &host1x->clients, list) { + if (client->ops && client->ops->drm_exit) { + int err = client->ops->drm_exit(client); + if (err < 0) { + dev_err(host1x->dev, + "DRM cleanup failed for %s: %d\n", + dev_name(client->dev), err); + return err; + } + } + } + + mutex_unlock(&host1x->clients_lock); + + drm_platform_exit(&tegra_drm_driver, pdev); + host1x->drm = NULL; + + return 0; +} + +int host1x_register_client(struct host1x *host1x, struct host1x_client *client) +{ + struct host1x_drm_client *drm, *tmp; + int err; + + mutex_lock(&host1x->clients_lock); + list_add_tail(&client->list, &host1x->clients); + mutex_unlock(&host1x->clients_lock); + + list_for_each_entry_safe(drm, tmp, &host1x->drm_clients, list) + if (drm->np == client->dev->of_node) + host1x_activate_drm_client(host1x, drm, client); + + if (list_empty(&host1x->drm_clients)) { + struct platform_device *pdev = to_platform_device(host1x->dev); + + err = drm_platform_init(&tegra_drm_driver, pdev); + if (err < 0) { + dev_err(host1x->dev, "drm_platform_init(): %d\n", err); + return err; + } + } + + client->host1x = host1x; + + return 0; +} + +int host1x_unregister_client(struct host1x *host1x, + struct host1x_client *client) +{ + struct host1x_drm_client *drm, *tmp; + int err; + + list_for_each_entry_safe(drm, tmp, &host1x->drm_active, list) { + if (drm->client == client) { + err = host1x_drm_exit(host1x); + if (err < 0) { + dev_err(host1x->dev, "host1x_drm_exit(): %d\n", + err); + return err; + } + + host1x_remove_drm_client(host1x, drm); + break; + } + } + + mutex_lock(&host1x->clients_lock); + list_del_init(&client->list); + mutex_unlock(&host1x->clients_lock); + + return 0; +} + +static struct of_device_id tegra_host1x_of_match[] = { + { .compatible = "nvidia,tegra30-host1x", }, + { .compatible = "nvidia,tegra20-host1x", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_host1x_of_match); + +struct platform_driver tegra_host1x_driver = { + .driver = { + .name = "tegra-host1x", + .owner = THIS_MODULE, + .of_match_table = tegra_host1x_of_match, + }, + .probe = tegra_host1x_probe, + .remove = tegra_host1x_remove, +}; + +static int __init tegra_host1x_init(void) +{ + int err; + + err = platform_driver_register(&tegra_host1x_driver); + if (err < 0) + return err; + + err = platform_driver_register(&tegra_dc_driver); + if (err < 0) + goto unregister_host1x; + + err = platform_driver_register(&tegra_hdmi_driver); + if (err < 0) + goto unregister_dc; + + return 0; + +unregister_dc: + platform_driver_unregister(&tegra_dc_driver); +unregister_host1x: + platform_driver_unregister(&tegra_host1x_driver); + return err; +} +module_init(tegra_host1x_init); + +static void __exit tegra_host1x_exit(void) +{ + platform_driver_unregister(&tegra_hdmi_driver); + platform_driver_unregister(&tegra_dc_driver); + platform_driver_unregister(&tegra_host1x_driver); +} +module_exit(tegra_host1x_exit); + +MODULE_AUTHOR("Thierry Reding <thierry.reding@avionic-design.de>"); +MODULE_DESCRIPTION("NVIDIA Tegra DRM driver"); +MODULE_LICENSE("GPL"); |