diff options
author | Arto Merilainen <amerilainen@nvidia.com> | 2014-03-19 03:38:25 -0400 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2015-03-18 15:08:53 -0400 |
commit | a9785995d5f22aaeb659285f8aeb64d8b56982e0 (patch) | |
tree | cc75f75bcf43db316a002a7a240b81f299bf6d7f /drivers/gpu/nvgpu/gk20a/channel_sync_gk20a.c | |
parent | 61efaf843c22b85424036ec98015121c08f5f16c (diff) |
gpu: nvgpu: Add NVIDIA GPU Driver
This patch moves the NVIDIA GPU driver to a new location.
Bug 1482562
Change-Id: I24293810b9d0f1504fd9be00135e21dad656ccb6
Signed-off-by: Arto Merilainen <amerilainen@nvidia.com>
Reviewed-on: http://git-master/r/383722
Reviewed-by: Terje Bergstrom <tbergstrom@nvidia.com>
Diffstat (limited to 'drivers/gpu/nvgpu/gk20a/channel_sync_gk20a.c')
-rw-r--r-- | drivers/gpu/nvgpu/gk20a/channel_sync_gk20a.c | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/drivers/gpu/nvgpu/gk20a/channel_sync_gk20a.c b/drivers/gpu/nvgpu/gk20a/channel_sync_gk20a.c new file mode 100644 index 00000000..9f9c3ba7 --- /dev/null +++ b/drivers/gpu/nvgpu/gk20a/channel_sync_gk20a.c | |||
@@ -0,0 +1,356 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/host/gk20a/channel_sync_gk20a.c | ||
3 | * | ||
4 | * GK20A Channel Synchronization Abstraction | ||
5 | * | ||
6 | * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms and conditions of the GNU General Public License, | ||
10 | * version 2, as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
15 | * more details. | ||
16 | */ | ||
17 | |||
18 | #include <linux/gk20a.h> | ||
19 | |||
20 | #include "channel_sync_gk20a.h" | ||
21 | #include "gk20a.h" | ||
22 | |||
23 | #ifdef CONFIG_SYNC | ||
24 | #include "../../../staging/android/sync.h" | ||
25 | #endif | ||
26 | |||
27 | #ifdef CONFIG_TEGRA_GK20A | ||
28 | #include <linux/nvhost.h> | ||
29 | #endif | ||
30 | |||
31 | #ifdef CONFIG_TEGRA_GK20A | ||
32 | |||
33 | struct gk20a_channel_syncpt { | ||
34 | struct gk20a_channel_sync ops; | ||
35 | struct channel_gk20a *c; | ||
36 | struct platform_device *host1x_pdev; | ||
37 | u32 id; | ||
38 | }; | ||
39 | |||
40 | static void add_wait_cmd(u32 *ptr, u32 id, u32 thresh) | ||
41 | { | ||
42 | /* syncpoint_a */ | ||
43 | ptr[0] = 0x2001001C; | ||
44 | /* payload */ | ||
45 | ptr[1] = thresh; | ||
46 | /* syncpoint_b */ | ||
47 | ptr[2] = 0x2001001D; | ||
48 | /* syncpt_id, switch_en, wait */ | ||
49 | ptr[3] = (id << 8) | 0x10; | ||
50 | } | ||
51 | |||
52 | int gk20a_channel_syncpt_wait_cpu(struct gk20a_channel_sync *s, | ||
53 | struct gk20a_channel_fence *fence, | ||
54 | int timeout) | ||
55 | { | ||
56 | struct gk20a_channel_syncpt *sp = | ||
57 | container_of(s, struct gk20a_channel_syncpt, ops); | ||
58 | if (!fence->valid) | ||
59 | return 0; | ||
60 | return nvhost_syncpt_wait_timeout_ext( | ||
61 | sp->host1x_pdev, sp->id, fence->thresh, | ||
62 | timeout, NULL, NULL); | ||
63 | } | ||
64 | |||
65 | bool gk20a_channel_syncpt_is_expired(struct gk20a_channel_sync *s, | ||
66 | struct gk20a_channel_fence *fence) | ||
67 | { | ||
68 | struct gk20a_channel_syncpt *sp = | ||
69 | container_of(s, struct gk20a_channel_syncpt, ops); | ||
70 | if (!fence->valid) | ||
71 | return true; | ||
72 | return nvhost_syncpt_is_expired_ext(sp->host1x_pdev, sp->id, | ||
73 | fence->thresh); | ||
74 | } | ||
75 | |||
76 | int gk20a_channel_syncpt_wait_syncpt(struct gk20a_channel_sync *s, u32 id, | ||
77 | u32 thresh, struct priv_cmd_entry **entry) | ||
78 | { | ||
79 | struct gk20a_channel_syncpt *sp = | ||
80 | container_of(s, struct gk20a_channel_syncpt, ops); | ||
81 | struct priv_cmd_entry *wait_cmd = NULL; | ||
82 | |||
83 | if (id >= nvhost_syncpt_nb_pts_ext(sp->host1x_pdev)) { | ||
84 | dev_warn(dev_from_gk20a(sp->c->g), | ||
85 | "invalid wait id in gpfifo submit, elided"); | ||
86 | return 0; | ||
87 | } | ||
88 | |||
89 | if (nvhost_syncpt_is_expired_ext(sp->host1x_pdev, id, thresh)) | ||
90 | return 0; | ||
91 | |||
92 | gk20a_channel_alloc_priv_cmdbuf(sp->c, 4, &wait_cmd); | ||
93 | if (wait_cmd == NULL) { | ||
94 | gk20a_err(dev_from_gk20a(sp->c->g), | ||
95 | "not enough priv cmd buffer space"); | ||
96 | return -EAGAIN; | ||
97 | } | ||
98 | |||
99 | add_wait_cmd(&wait_cmd->ptr[0], id, thresh); | ||
100 | |||
101 | *entry = wait_cmd; | ||
102 | return 0; | ||
103 | } | ||
104 | |||
105 | int gk20a_channel_syncpt_wait_fd(struct gk20a_channel_sync *s, int fd, | ||
106 | struct priv_cmd_entry **entry) | ||
107 | { | ||
108 | #ifdef CONFIG_SYNC | ||
109 | int i; | ||
110 | int num_wait_cmds; | ||
111 | struct sync_pt *pt; | ||
112 | struct sync_fence *sync_fence; | ||
113 | struct priv_cmd_entry *wait_cmd = NULL; | ||
114 | struct gk20a_channel_syncpt *sp = | ||
115 | container_of(s, struct gk20a_channel_syncpt, ops); | ||
116 | struct channel_gk20a *c = sp->c; | ||
117 | |||
118 | sync_fence = nvhost_sync_fdget(fd); | ||
119 | if (!sync_fence) | ||
120 | return -EINVAL; | ||
121 | |||
122 | num_wait_cmds = nvhost_sync_num_pts(sync_fence); | ||
123 | gk20a_channel_alloc_priv_cmdbuf(c, 4 * num_wait_cmds, &wait_cmd); | ||
124 | if (wait_cmd == NULL) { | ||
125 | gk20a_err(dev_from_gk20a(c->g), | ||
126 | "not enough priv cmd buffer space"); | ||
127 | sync_fence_put(sync_fence); | ||
128 | return -EAGAIN; | ||
129 | } | ||
130 | |||
131 | i = 0; | ||
132 | list_for_each_entry(pt, &sync_fence->pt_list_head, pt_list) { | ||
133 | u32 wait_id = nvhost_sync_pt_id(pt); | ||
134 | u32 wait_value = nvhost_sync_pt_thresh(pt); | ||
135 | |||
136 | if (nvhost_syncpt_is_expired_ext(sp->host1x_pdev, | ||
137 | wait_id, wait_value)) { | ||
138 | wait_cmd->ptr[i * 4 + 0] = 0; | ||
139 | wait_cmd->ptr[i * 4 + 1] = 0; | ||
140 | wait_cmd->ptr[i * 4 + 2] = 0; | ||
141 | wait_cmd->ptr[i * 4 + 3] = 0; | ||
142 | } else | ||
143 | add_wait_cmd(&wait_cmd->ptr[i * 4], wait_id, | ||
144 | wait_value); | ||
145 | i++; | ||
146 | } | ||
147 | WARN_ON(i != num_wait_cmds); | ||
148 | sync_fence_put(sync_fence); | ||
149 | |||
150 | *entry = wait_cmd; | ||
151 | return 0; | ||
152 | #else | ||
153 | return -ENODEV; | ||
154 | #endif | ||
155 | } | ||
156 | |||
157 | static void gk20a_channel_syncpt_update(void *priv, int nr_completed) | ||
158 | { | ||
159 | struct channel_gk20a *ch20a = priv; | ||
160 | gk20a_channel_update(ch20a, nr_completed); | ||
161 | } | ||
162 | |||
163 | static int __gk20a_channel_syncpt_incr(struct gk20a_channel_sync *s, | ||
164 | bool gfx_class, bool wfi_cmd, | ||
165 | struct priv_cmd_entry **entry, | ||
166 | struct gk20a_channel_fence *fence) | ||
167 | { | ||
168 | u32 thresh; | ||
169 | int incr_cmd_size; | ||
170 | int j = 0; | ||
171 | int err; | ||
172 | struct priv_cmd_entry *incr_cmd = NULL; | ||
173 | struct gk20a_channel_syncpt *sp = | ||
174 | container_of(s, struct gk20a_channel_syncpt, ops); | ||
175 | struct channel_gk20a *c = sp->c; | ||
176 | |||
177 | /* nvhost action_gpfifo_submit_complete releases this ref. */ | ||
178 | err = gk20a_channel_busy(c->g->dev); | ||
179 | if (err) | ||
180 | return err; | ||
181 | |||
182 | incr_cmd_size = 4; | ||
183 | if (wfi_cmd) | ||
184 | incr_cmd_size += 2; | ||
185 | |||
186 | gk20a_channel_alloc_priv_cmdbuf(c, incr_cmd_size, &incr_cmd); | ||
187 | if (incr_cmd == NULL) { | ||
188 | gk20a_channel_idle(c->g->dev); | ||
189 | gk20a_err(dev_from_gk20a(c->g), | ||
190 | "not enough priv cmd buffer space"); | ||
191 | return -EAGAIN; | ||
192 | } | ||
193 | |||
194 | if (gfx_class) { | ||
195 | WARN_ON(wfi_cmd); /* No sense to use gfx class + wfi. */ | ||
196 | /* setobject KEPLER_C */ | ||
197 | incr_cmd->ptr[j++] = 0x20010000; | ||
198 | incr_cmd->ptr[j++] = KEPLER_C; | ||
199 | /* syncpt incr */ | ||
200 | incr_cmd->ptr[j++] = 0x200100B2; | ||
201 | incr_cmd->ptr[j++] = sp->id | | ||
202 | (0x1 << 20) | (0x1 << 16); | ||
203 | } else { | ||
204 | if (wfi_cmd) { | ||
205 | /* wfi */ | ||
206 | incr_cmd->ptr[j++] = 0x2001001E; | ||
207 | /* handle, ignored */ | ||
208 | incr_cmd->ptr[j++] = 0x00000000; | ||
209 | } | ||
210 | /* syncpoint_a */ | ||
211 | incr_cmd->ptr[j++] = 0x2001001C; | ||
212 | /* payload, ignored */ | ||
213 | incr_cmd->ptr[j++] = 0; | ||
214 | /* syncpoint_b */ | ||
215 | incr_cmd->ptr[j++] = 0x2001001D; | ||
216 | /* syncpt_id, incr */ | ||
217 | incr_cmd->ptr[j++] = (sp->id << 8) | 0x1; | ||
218 | } | ||
219 | WARN_ON(j != incr_cmd_size); | ||
220 | |||
221 | thresh = nvhost_syncpt_incr_max_ext(sp->host1x_pdev, sp->id, 1); | ||
222 | |||
223 | err = nvhost_intr_register_notifier(sp->host1x_pdev, sp->id, thresh, | ||
224 | gk20a_channel_syncpt_update, c); | ||
225 | |||
226 | /* Adding interrupt action should never fail. A proper error handling | ||
227 | * here would require us to decrement the syncpt max back to its | ||
228 | * original value. */ | ||
229 | if (WARN(err, "failed to set submit complete interrupt")) { | ||
230 | gk20a_channel_idle(c->g->dev); | ||
231 | err = 0; /* Ignore this error. */ | ||
232 | } | ||
233 | |||
234 | fence->thresh = thresh; | ||
235 | fence->valid = true; | ||
236 | fence->wfi = wfi_cmd; | ||
237 | *entry = incr_cmd; | ||
238 | return 0; | ||
239 | } | ||
240 | |||
241 | int gk20a_channel_syncpt_incr_wfi(struct gk20a_channel_sync *s, | ||
242 | struct priv_cmd_entry **entry, | ||
243 | struct gk20a_channel_fence *fence) | ||
244 | { | ||
245 | return __gk20a_channel_syncpt_incr(s, | ||
246 | false /* use host class */, | ||
247 | true /* wfi */, | ||
248 | entry, fence); | ||
249 | } | ||
250 | |||
251 | int gk20a_channel_syncpt_incr(struct gk20a_channel_sync *s, | ||
252 | struct priv_cmd_entry **entry, | ||
253 | struct gk20a_channel_fence *fence) | ||
254 | { | ||
255 | struct gk20a_channel_syncpt *sp = | ||
256 | container_of(s, struct gk20a_channel_syncpt, ops); | ||
257 | /* Don't put wfi cmd to this one since we're not returning | ||
258 | * a fence to user space. */ | ||
259 | return __gk20a_channel_syncpt_incr(s, | ||
260 | sp->c->obj_class == KEPLER_C /* may use gfx class */, | ||
261 | false /* no wfi */, | ||
262 | entry, fence); | ||
263 | } | ||
264 | |||
265 | int gk20a_channel_syncpt_incr_user_syncpt(struct gk20a_channel_sync *s, | ||
266 | struct priv_cmd_entry **entry, | ||
267 | struct gk20a_channel_fence *fence, | ||
268 | u32 *id, u32 *thresh) | ||
269 | { | ||
270 | struct gk20a_channel_syncpt *sp = | ||
271 | container_of(s, struct gk20a_channel_syncpt, ops); | ||
272 | /* Need to do 'host incr + wfi' or 'gfx incr' since we return the fence | ||
273 | * to user space. */ | ||
274 | int err = __gk20a_channel_syncpt_incr(s, | ||
275 | sp->c->obj_class == KEPLER_C /* use gfx class? */, | ||
276 | sp->c->obj_class != KEPLER_C /* wfi if host class */, | ||
277 | entry, fence); | ||
278 | if (err) | ||
279 | return err; | ||
280 | *id = sp->id; | ||
281 | *thresh = fence->thresh; | ||
282 | return 0; | ||
283 | } | ||
284 | |||
285 | int gk20a_channel_syncpt_incr_user_fd(struct gk20a_channel_sync *s, | ||
286 | struct priv_cmd_entry **entry, | ||
287 | struct gk20a_channel_fence *fence, | ||
288 | int *fd) | ||
289 | { | ||
290 | #ifdef CONFIG_SYNC | ||
291 | int err; | ||
292 | struct nvhost_ctrl_sync_fence_info pt; | ||
293 | struct gk20a_channel_syncpt *sp = | ||
294 | container_of(s, struct gk20a_channel_syncpt, ops); | ||
295 | err = gk20a_channel_syncpt_incr_user_syncpt(s, entry, fence, | ||
296 | &pt.id, &pt.thresh); | ||
297 | if (err) | ||
298 | return err; | ||
299 | return nvhost_sync_create_fence_fd(sp->host1x_pdev, &pt, 1, | ||
300 | "fence", fd); | ||
301 | #else | ||
302 | return -ENODEV; | ||
303 | #endif | ||
304 | } | ||
305 | |||
306 | void gk20a_channel_syncpt_set_min_eq_max(struct gk20a_channel_sync *s) | ||
307 | { | ||
308 | struct gk20a_channel_syncpt *sp = | ||
309 | container_of(s, struct gk20a_channel_syncpt, ops); | ||
310 | nvhost_syncpt_set_min_eq_max_ext(sp->host1x_pdev, sp->id); | ||
311 | } | ||
312 | |||
313 | static void gk20a_channel_syncpt_destroy(struct gk20a_channel_sync *s) | ||
314 | { | ||
315 | struct gk20a_channel_syncpt *sp = | ||
316 | container_of(s, struct gk20a_channel_syncpt, ops); | ||
317 | nvhost_free_syncpt(sp->id); | ||
318 | kfree(sp); | ||
319 | } | ||
320 | |||
321 | static struct gk20a_channel_sync * | ||
322 | gk20a_channel_syncpt_create(struct channel_gk20a *c) | ||
323 | { | ||
324 | struct gk20a_channel_syncpt *sp; | ||
325 | |||
326 | sp = kzalloc(sizeof(*sp), GFP_KERNEL); | ||
327 | if (!sp) | ||
328 | return NULL; | ||
329 | |||
330 | sp->c = c; | ||
331 | sp->host1x_pdev = to_platform_device(c->g->dev->dev.parent); | ||
332 | sp->id = nvhost_get_syncpt_host_managed(sp->host1x_pdev, c->hw_chid); | ||
333 | |||
334 | sp->ops.wait_cpu = gk20a_channel_syncpt_wait_cpu; | ||
335 | sp->ops.is_expired = gk20a_channel_syncpt_is_expired; | ||
336 | sp->ops.wait_syncpt = gk20a_channel_syncpt_wait_syncpt; | ||
337 | sp->ops.wait_fd = gk20a_channel_syncpt_wait_fd; | ||
338 | sp->ops.incr = gk20a_channel_syncpt_incr; | ||
339 | sp->ops.incr_wfi = gk20a_channel_syncpt_incr_wfi; | ||
340 | sp->ops.incr_user_syncpt = gk20a_channel_syncpt_incr_user_syncpt; | ||
341 | sp->ops.incr_user_fd = gk20a_channel_syncpt_incr_user_fd; | ||
342 | sp->ops.set_min_eq_max = gk20a_channel_syncpt_set_min_eq_max; | ||
343 | sp->ops.destroy = gk20a_channel_syncpt_destroy; | ||
344 | return &sp->ops; | ||
345 | } | ||
346 | #endif /* CONFIG_TEGRA_GK20A */ | ||
347 | |||
348 | struct gk20a_channel_sync *gk20a_channel_sync_create(struct channel_gk20a *c) | ||
349 | { | ||
350 | #ifdef CONFIG_TEGRA_GK20A | ||
351 | if (gk20a_platform_has_syncpoints(c->g->dev)) | ||
352 | return gk20a_channel_syncpt_create(c); | ||
353 | #endif | ||
354 | WARN_ON(1); | ||
355 | return NULL; | ||
356 | } | ||