blob: 1f728cd7f61a1176e0c192e8aefeb9751a625254 [file] [log] [blame]
Thierry Redingd8f4a9e2012-11-15 21:28:22 +00001/*
2 * Copyright (C) 2012 Avionic Design GmbH
3 * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 */
9
10#include <linux/clk.h>
11#include <linux/err.h>
12#include <linux/module.h>
13#include <linux/of.h>
14#include <linux/platform_device.h>
15
16#include "drm.h"
17
18struct host1x_drm_client {
19 struct host1x_client *client;
20 struct device_node *np;
21 struct list_head list;
22};
23
24static int host1x_add_drm_client(struct host1x *host1x, struct device_node *np)
25{
26 struct host1x_drm_client *client;
27
28 client = kzalloc(sizeof(*client), GFP_KERNEL);
29 if (!client)
30 return -ENOMEM;
31
32 INIT_LIST_HEAD(&client->list);
33 client->np = of_node_get(np);
34
35 list_add_tail(&client->list, &host1x->drm_clients);
36
37 return 0;
38}
39
40static int host1x_activate_drm_client(struct host1x *host1x,
41 struct host1x_drm_client *drm,
42 struct host1x_client *client)
43{
44 mutex_lock(&host1x->drm_clients_lock);
45 list_del_init(&drm->list);
46 list_add_tail(&drm->list, &host1x->drm_active);
47 drm->client = client;
48 mutex_unlock(&host1x->drm_clients_lock);
49
50 return 0;
51}
52
53static int host1x_remove_drm_client(struct host1x *host1x,
54 struct host1x_drm_client *client)
55{
56 mutex_lock(&host1x->drm_clients_lock);
57 list_del_init(&client->list);
58 mutex_unlock(&host1x->drm_clients_lock);
59
60 of_node_put(client->np);
61 kfree(client);
62
63 return 0;
64}
65
66static int host1x_parse_dt(struct host1x *host1x)
67{
68 static const char * const compat[] = {
69 "nvidia,tegra20-dc",
Thierry Redingedec4af2012-11-15 21:28:23 +000070 "nvidia,tegra20-hdmi",
Thierry Redingd8f4a9e2012-11-15 21:28:22 +000071 };
72 unsigned int i;
73 int err;
74
75 for (i = 0; i < ARRAY_SIZE(compat); i++) {
76 struct device_node *np;
77
78 for_each_child_of_node(host1x->dev->of_node, np) {
79 if (of_device_is_compatible(np, compat[i]) &&
80 of_device_is_available(np)) {
81 err = host1x_add_drm_client(host1x, np);
82 if (err < 0)
83 return err;
84 }
85 }
86 }
87
88 return 0;
89}
90
91static int tegra_host1x_probe(struct platform_device *pdev)
92{
93 struct host1x *host1x;
94 struct resource *regs;
95 int err;
96
97 host1x = devm_kzalloc(&pdev->dev, sizeof(*host1x), GFP_KERNEL);
98 if (!host1x)
99 return -ENOMEM;
100
101 mutex_init(&host1x->drm_clients_lock);
102 INIT_LIST_HEAD(&host1x->drm_clients);
103 INIT_LIST_HEAD(&host1x->drm_active);
104 mutex_init(&host1x->clients_lock);
105 INIT_LIST_HEAD(&host1x->clients);
106 host1x->dev = &pdev->dev;
107
108 err = host1x_parse_dt(host1x);
109 if (err < 0) {
110 dev_err(&pdev->dev, "failed to parse DT: %d\n", err);
111 return err;
112 }
113
114 host1x->clk = devm_clk_get(&pdev->dev, NULL);
115 if (IS_ERR(host1x->clk))
116 return PTR_ERR(host1x->clk);
117
118 err = clk_prepare_enable(host1x->clk);
119 if (err < 0)
120 return err;
121
122 regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
123 if (!regs) {
124 err = -ENXIO;
125 goto err;
126 }
127
128 err = platform_get_irq(pdev, 0);
129 if (err < 0)
130 goto err;
131
132 host1x->syncpt = err;
133
134 err = platform_get_irq(pdev, 1);
135 if (err < 0)
136 goto err;
137
138 host1x->irq = err;
139
140 host1x->regs = devm_request_and_ioremap(&pdev->dev, regs);
141 if (!host1x->regs) {
142 err = -EADDRNOTAVAIL;
143 goto err;
144 }
145
146 platform_set_drvdata(pdev, host1x);
147
148 return 0;
149
150err:
151 clk_disable_unprepare(host1x->clk);
152 return err;
153}
154
155static int tegra_host1x_remove(struct platform_device *pdev)
156{
157 struct host1x *host1x = platform_get_drvdata(pdev);
158
159 clk_disable_unprepare(host1x->clk);
160
161 return 0;
162}
163
164int host1x_drm_init(struct host1x *host1x, struct drm_device *drm)
165{
166 struct host1x_client *client;
167
168 mutex_lock(&host1x->clients_lock);
169
170 list_for_each_entry(client, &host1x->clients, list) {
171 if (client->ops && client->ops->drm_init) {
172 int err = client->ops->drm_init(client, drm);
173 if (err < 0) {
174 dev_err(host1x->dev,
175 "DRM setup failed for %s: %d\n",
176 dev_name(client->dev), err);
177 return err;
178 }
179 }
180 }
181
182 mutex_unlock(&host1x->clients_lock);
183
184 return 0;
185}
186
187int host1x_drm_exit(struct host1x *host1x)
188{
189 struct platform_device *pdev = to_platform_device(host1x->dev);
190 struct host1x_client *client;
191
192 if (!host1x->drm)
193 return 0;
194
195 mutex_lock(&host1x->clients_lock);
196
197 list_for_each_entry_reverse(client, &host1x->clients, list) {
198 if (client->ops && client->ops->drm_exit) {
199 int err = client->ops->drm_exit(client);
200 if (err < 0) {
201 dev_err(host1x->dev,
202 "DRM cleanup failed for %s: %d\n",
203 dev_name(client->dev), err);
204 return err;
205 }
206 }
207 }
208
209 mutex_unlock(&host1x->clients_lock);
210
211 drm_platform_exit(&tegra_drm_driver, pdev);
212 host1x->drm = NULL;
213
214 return 0;
215}
216
217int host1x_register_client(struct host1x *host1x, struct host1x_client *client)
218{
219 struct host1x_drm_client *drm, *tmp;
220 int err;
221
222 mutex_lock(&host1x->clients_lock);
223 list_add_tail(&client->list, &host1x->clients);
224 mutex_unlock(&host1x->clients_lock);
225
226 list_for_each_entry_safe(drm, tmp, &host1x->drm_clients, list)
227 if (drm->np == client->dev->of_node)
228 host1x_activate_drm_client(host1x, drm, client);
229
230 if (list_empty(&host1x->drm_clients)) {
231 struct platform_device *pdev = to_platform_device(host1x->dev);
232
233 err = drm_platform_init(&tegra_drm_driver, pdev);
234 if (err < 0) {
235 dev_err(host1x->dev, "drm_platform_init(): %d\n", err);
236 return err;
237 }
238 }
239
240 return 0;
241}
242
243int host1x_unregister_client(struct host1x *host1x,
244 struct host1x_client *client)
245{
246 struct host1x_drm_client *drm, *tmp;
247 int err;
248
249 list_for_each_entry_safe(drm, tmp, &host1x->drm_active, list) {
250 if (drm->client == client) {
251 err = host1x_drm_exit(host1x);
252 if (err < 0) {
253 dev_err(host1x->dev, "host1x_drm_exit(): %d\n",
254 err);
255 return err;
256 }
257
258 host1x_remove_drm_client(host1x, drm);
259 break;
260 }
261 }
262
263 mutex_lock(&host1x->clients_lock);
264 list_del_init(&client->list);
265 mutex_unlock(&host1x->clients_lock);
266
267 return 0;
268}
269
270static struct of_device_id tegra_host1x_of_match[] = {
271 { .compatible = "nvidia,tegra20-host1x", },
272 { },
273};
274MODULE_DEVICE_TABLE(of, tegra_host1x_of_match);
275
276struct platform_driver tegra_host1x_driver = {
277 .driver = {
278 .name = "tegra-host1x",
279 .owner = THIS_MODULE,
280 .of_match_table = tegra_host1x_of_match,
281 },
282 .probe = tegra_host1x_probe,
283 .remove = tegra_host1x_remove,
284};
285
286static int __init tegra_host1x_init(void)
287{
288 int err;
289
290 err = platform_driver_register(&tegra_host1x_driver);
291 if (err < 0)
292 return err;
293
294 err = platform_driver_register(&tegra_dc_driver);
295 if (err < 0)
296 goto unregister_host1x;
297
Thierry Redingedec4af2012-11-15 21:28:23 +0000298 err = platform_driver_register(&tegra_hdmi_driver);
299 if (err < 0)
300 goto unregister_dc;
301
Thierry Redingd8f4a9e2012-11-15 21:28:22 +0000302 return 0;
303
Thierry Redingedec4af2012-11-15 21:28:23 +0000304unregister_dc:
305 platform_driver_unregister(&tegra_dc_driver);
Thierry Redingd8f4a9e2012-11-15 21:28:22 +0000306unregister_host1x:
307 platform_driver_unregister(&tegra_host1x_driver);
308 return err;
309}
310module_init(tegra_host1x_init);
311
312static void __exit tegra_host1x_exit(void)
313{
Thierry Redingedec4af2012-11-15 21:28:23 +0000314 platform_driver_unregister(&tegra_hdmi_driver);
Thierry Redingd8f4a9e2012-11-15 21:28:22 +0000315 platform_driver_unregister(&tegra_dc_driver);
316 platform_driver_unregister(&tegra_host1x_driver);
317}
318module_exit(tegra_host1x_exit);
319
320MODULE_AUTHOR("Thierry Reding <thierry.reding@avionic-design.de>");
321MODULE_DESCRIPTION("NVIDIA Tegra DRM driver");
322MODULE_LICENSE("GPL");