forked from Minki/linux
edec4af4c3
This commit adds support for the HDMI output on the Tegra20 SoC. Only one such output is available, but it can be driven by either of the two display controllers. A lot of work on this patch has been contributed by NVIDIA's Mark Zhang <markz@nvidia.com> and many other people at NVIDIA were very helpful in getting the HDMI support and surrounding infrastructure to work. Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de> Tested-by: Stephen Warren <swarren@nvidia.com> Acked-by: Mark Zhang <markz@nvidia.com> Reviewed-by: Mark Zhang <markz@nvidia.com> Tested-by: Mark Zhang <markz@nvidia.com> Tested-and-acked-by: Alexandre Courbot <acourbot@nvidia.com> Acked-by: Terje Bergstrom <tbergstrom@nvidia.com> Tested-by: Terje Bergstrom <tbergstrom@nvidia.com> Signed-off-by: Dave Airlie <airlied@redhat.com>
323 lines
6.8 KiB
C
323 lines
6.8 KiB
C
/*
|
|
* 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",
|
|
};
|
|
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;
|
|
}
|
|
}
|
|
|
|
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,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");
|