/* * 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 #include #include #include #include #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 "); MODULE_DESCRIPTION("NVIDIA Tegra DRM driver"); MODULE_LICENSE("GPL");