blob: 9fbed4765e60b4ad2aae7e4e8a03a25acc955d90 [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",
70 };
71 unsigned int i;
72 int err;
73
74 for (i = 0; i < ARRAY_SIZE(compat); i++) {
75 struct device_node *np;
76
77 for_each_child_of_node(host1x->dev->of_node, np) {
78 if (of_device_is_compatible(np, compat[i]) &&
79 of_device_is_available(np)) {
80 err = host1x_add_drm_client(host1x, np);
81 if (err < 0)
82 return err;
83 }
84 }
85 }
86
87 return 0;
88}
89
90static int tegra_host1x_probe(struct platform_device *pdev)
91{
92 struct host1x *host1x;
93 struct resource *regs;
94 int err;
95
96 host1x = devm_kzalloc(&pdev->dev, sizeof(*host1x), GFP_KERNEL);
97 if (!host1x)
98 return -ENOMEM;
99
100 mutex_init(&host1x->drm_clients_lock);
101 INIT_LIST_HEAD(&host1x->drm_clients);
102 INIT_LIST_HEAD(&host1x->drm_active);
103 mutex_init(&host1x->clients_lock);
104 INIT_LIST_HEAD(&host1x->clients);
105 host1x->dev = &pdev->dev;
106
107 err = host1x_parse_dt(host1x);
108 if (err < 0) {
109 dev_err(&pdev->dev, "failed to parse DT: %d\n", err);
110 return err;
111 }
112
113 host1x->clk = devm_clk_get(&pdev->dev, NULL);
114 if (IS_ERR(host1x->clk))
115 return PTR_ERR(host1x->clk);
116
117 err = clk_prepare_enable(host1x->clk);
118 if (err < 0)
119 return err;
120
121 regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
122 if (!regs) {
123 err = -ENXIO;
124 goto err;
125 }
126
127 err = platform_get_irq(pdev, 0);
128 if (err < 0)
129 goto err;
130
131 host1x->syncpt = err;
132
133 err = platform_get_irq(pdev, 1);
134 if (err < 0)
135 goto err;
136
137 host1x->irq = err;
138
139 host1x->regs = devm_request_and_ioremap(&pdev->dev, regs);
140 if (!host1x->regs) {
141 err = -EADDRNOTAVAIL;
142 goto err;
143 }
144
145 platform_set_drvdata(pdev, host1x);
146
147 return 0;
148
149err:
150 clk_disable_unprepare(host1x->clk);
151 return err;
152}
153
154static int tegra_host1x_remove(struct platform_device *pdev)
155{
156 struct host1x *host1x = platform_get_drvdata(pdev);
157
158 clk_disable_unprepare(host1x->clk);
159
160 return 0;
161}
162
163int host1x_drm_init(struct host1x *host1x, struct drm_device *drm)
164{
165 struct host1x_client *client;
166
167 mutex_lock(&host1x->clients_lock);
168
169 list_for_each_entry(client, &host1x->clients, list) {
170 if (client->ops && client->ops->drm_init) {
171 int err = client->ops->drm_init(client, drm);
172 if (err < 0) {
173 dev_err(host1x->dev,
174 "DRM setup failed for %s: %d\n",
175 dev_name(client->dev), err);
176 return err;
177 }
178 }
179 }
180
181 mutex_unlock(&host1x->clients_lock);
182
183 return 0;
184}
185
186int host1x_drm_exit(struct host1x *host1x)
187{
188 struct platform_device *pdev = to_platform_device(host1x->dev);
189 struct host1x_client *client;
190
191 if (!host1x->drm)
192 return 0;
193
194 mutex_lock(&host1x->clients_lock);
195
196 list_for_each_entry_reverse(client, &host1x->clients, list) {
197 if (client->ops && client->ops->drm_exit) {
198 int err = client->ops->drm_exit(client);
199 if (err < 0) {
200 dev_err(host1x->dev,
201 "DRM cleanup failed for %s: %d\n",
202 dev_name(client->dev), err);
203 return err;
204 }
205 }
206 }
207
208 mutex_unlock(&host1x->clients_lock);
209
210 drm_platform_exit(&tegra_drm_driver, pdev);
211 host1x->drm = NULL;
212
213 return 0;
214}
215
216int host1x_register_client(struct host1x *host1x, struct host1x_client *client)
217{
218 struct host1x_drm_client *drm, *tmp;
219 int err;
220
221 mutex_lock(&host1x->clients_lock);
222 list_add_tail(&client->list, &host1x->clients);
223 mutex_unlock(&host1x->clients_lock);
224
225 list_for_each_entry_safe(drm, tmp, &host1x->drm_clients, list)
226 if (drm->np == client->dev->of_node)
227 host1x_activate_drm_client(host1x, drm, client);
228
229 if (list_empty(&host1x->drm_clients)) {
230 struct platform_device *pdev = to_platform_device(host1x->dev);
231
232 err = drm_platform_init(&tegra_drm_driver, pdev);
233 if (err < 0) {
234 dev_err(host1x->dev, "drm_platform_init(): %d\n", err);
235 return err;
236 }
237 }
238
239 return 0;
240}
241
242int host1x_unregister_client(struct host1x *host1x,
243 struct host1x_client *client)
244{
245 struct host1x_drm_client *drm, *tmp;
246 int err;
247
248 list_for_each_entry_safe(drm, tmp, &host1x->drm_active, list) {
249 if (drm->client == client) {
250 err = host1x_drm_exit(host1x);
251 if (err < 0) {
252 dev_err(host1x->dev, "host1x_drm_exit(): %d\n",
253 err);
254 return err;
255 }
256
257 host1x_remove_drm_client(host1x, drm);
258 break;
259 }
260 }
261
262 mutex_lock(&host1x->clients_lock);
263 list_del_init(&client->list);
264 mutex_unlock(&host1x->clients_lock);
265
266 return 0;
267}
268
269static struct of_device_id tegra_host1x_of_match[] = {
270 { .compatible = "nvidia,tegra20-host1x", },
271 { },
272};
273MODULE_DEVICE_TABLE(of, tegra_host1x_of_match);
274
275struct platform_driver tegra_host1x_driver = {
276 .driver = {
277 .name = "tegra-host1x",
278 .owner = THIS_MODULE,
279 .of_match_table = tegra_host1x_of_match,
280 },
281 .probe = tegra_host1x_probe,
282 .remove = tegra_host1x_remove,
283};
284
285static int __init tegra_host1x_init(void)
286{
287 int err;
288
289 err = platform_driver_register(&tegra_host1x_driver);
290 if (err < 0)
291 return err;
292
293 err = platform_driver_register(&tegra_dc_driver);
294 if (err < 0)
295 goto unregister_host1x;
296
297 return 0;
298
299unregister_host1x:
300 platform_driver_unregister(&tegra_host1x_driver);
301 return err;
302}
303module_init(tegra_host1x_init);
304
305static void __exit tegra_host1x_exit(void)
306{
307 platform_driver_unregister(&tegra_dc_driver);
308 platform_driver_unregister(&tegra_host1x_driver);
309}
310module_exit(tegra_host1x_exit);
311
312MODULE_AUTHOR("Thierry Reding <thierry.reding@avionic-design.de>");
313MODULE_DESCRIPTION("NVIDIA Tegra DRM driver");
314MODULE_LICENSE("GPL");