diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /drivers/video/tegra/host/bus_client.c | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'drivers/video/tegra/host/bus_client.c')
-rw-r--r-- | drivers/video/tegra/host/bus_client.c | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/drivers/video/tegra/host/bus_client.c b/drivers/video/tegra/host/bus_client.c new file mode 100644 index 00000000000..940f04a40e8 --- /dev/null +++ b/drivers/video/tegra/host/bus_client.c | |||
@@ -0,0 +1,606 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/host/bus_client.c | ||
3 | * | ||
4 | * Tegra Graphics Host Client Module | ||
5 | * | ||
6 | * Copyright (c) 2010-2012, NVIDIA Corporation. | ||
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 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | ||
20 | |||
21 | #include <linux/slab.h> | ||
22 | #include <linux/string.h> | ||
23 | #include <linux/spinlock.h> | ||
24 | #include <linux/fs.h> | ||
25 | #include <linux/cdev.h> | ||
26 | #include <linux/uaccess.h> | ||
27 | #include <linux/file.h> | ||
28 | #include <linux/clk.h> | ||
29 | #include <linux/hrtimer.h> | ||
30 | |||
31 | #include <trace/events/nvhost.h> | ||
32 | |||
33 | #include <linux/io.h> | ||
34 | #include <linux/string.h> | ||
35 | |||
36 | #include <linux/nvhost.h> | ||
37 | #include <linux/nvhost_ioctl.h> | ||
38 | |||
39 | #include <mach/nvmap.h> | ||
40 | #include <mach/gpufuse.h> | ||
41 | #include <mach/hardware.h> | ||
42 | #include <mach/iomap.h> | ||
43 | |||
44 | #include "debug.h" | ||
45 | #include "bus_client.h" | ||
46 | #include "dev.h" | ||
47 | |||
48 | void nvhost_read_module_regs(struct nvhost_device *ndev, | ||
49 | u32 offset, int count, u32 *values) | ||
50 | { | ||
51 | void __iomem *p = ndev->aperture + offset; | ||
52 | |||
53 | nvhost_module_busy(ndev); | ||
54 | while (count--) { | ||
55 | *(values++) = readl(p); | ||
56 | p += 4; | ||
57 | } | ||
58 | rmb(); | ||
59 | nvhost_module_idle(ndev); | ||
60 | } | ||
61 | |||
62 | void nvhost_write_module_regs(struct nvhost_device *ndev, | ||
63 | u32 offset, int count, const u32 *values) | ||
64 | { | ||
65 | void __iomem *p = ndev->aperture + offset; | ||
66 | |||
67 | nvhost_module_busy(ndev); | ||
68 | while (count--) { | ||
69 | writel(*(values++), p); | ||
70 | p += 4; | ||
71 | } | ||
72 | wmb(); | ||
73 | nvhost_module_idle(ndev); | ||
74 | } | ||
75 | |||
76 | struct nvhost_channel_userctx { | ||
77 | struct nvhost_channel *ch; | ||
78 | struct nvhost_hwctx *hwctx; | ||
79 | struct nvhost_submit_hdr_ext hdr; | ||
80 | int num_relocshifts; | ||
81 | struct nvhost_job *job; | ||
82 | struct nvmap_client *nvmap; | ||
83 | u32 timeout; | ||
84 | u32 priority; | ||
85 | int clientid; | ||
86 | }; | ||
87 | |||
88 | /* | ||
89 | * Write cmdbuf to ftrace output. Checks if cmdbuf contents should be output | ||
90 | * and mmaps the cmdbuf contents if required. | ||
91 | */ | ||
92 | static void trace_write_cmdbufs(struct nvhost_job *job) | ||
93 | { | ||
94 | struct nvmap_handle_ref handle; | ||
95 | void *mem = NULL; | ||
96 | int i = 0; | ||
97 | |||
98 | for (i = 0; i < job->num_gathers; i++) { | ||
99 | struct nvhost_channel_gather *gather = &job->gathers[i]; | ||
100 | if (nvhost_debug_trace_cmdbuf) { | ||
101 | handle.handle = nvmap_id_to_handle(gather->mem_id); | ||
102 | mem = nvmap_mmap(&handle); | ||
103 | if (IS_ERR_OR_NULL(mem)) | ||
104 | mem = NULL; | ||
105 | }; | ||
106 | |||
107 | if (mem) { | ||
108 | u32 i; | ||
109 | /* | ||
110 | * Write in batches of 128 as there seems to be a limit | ||
111 | * of how much you can output to ftrace at once. | ||
112 | */ | ||
113 | for (i = 0; i < gather->words; i += TRACE_MAX_LENGTH) { | ||
114 | trace_nvhost_channel_write_cmdbuf_data( | ||
115 | job->ch->dev->name, | ||
116 | gather->mem_id, | ||
117 | min(gather->words - i, | ||
118 | TRACE_MAX_LENGTH), | ||
119 | gather->offset + i * sizeof(u32), | ||
120 | mem); | ||
121 | } | ||
122 | nvmap_munmap(&handle, mem); | ||
123 | } | ||
124 | } | ||
125 | } | ||
126 | |||
127 | static int nvhost_channelrelease(struct inode *inode, struct file *filp) | ||
128 | { | ||
129 | struct nvhost_channel_userctx *priv = filp->private_data; | ||
130 | |||
131 | trace_nvhost_channel_release(priv->ch->dev->name); | ||
132 | |||
133 | filp->private_data = NULL; | ||
134 | |||
135 | nvhost_module_remove_client(priv->ch->dev, priv); | ||
136 | nvhost_putchannel(priv->ch, priv->hwctx); | ||
137 | |||
138 | if (priv->hwctx) | ||
139 | priv->ch->ctxhandler->put(priv->hwctx); | ||
140 | |||
141 | if (priv->job) | ||
142 | nvhost_job_put(priv->job); | ||
143 | |||
144 | nvmap_client_put(priv->nvmap); | ||
145 | kfree(priv); | ||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | static int nvhost_channelopen(struct inode *inode, struct file *filp) | ||
150 | { | ||
151 | struct nvhost_channel_userctx *priv; | ||
152 | struct nvhost_channel *ch; | ||
153 | |||
154 | ch = container_of(inode->i_cdev, struct nvhost_channel, cdev); | ||
155 | ch = nvhost_getchannel(ch); | ||
156 | if (!ch) | ||
157 | return -ENOMEM; | ||
158 | trace_nvhost_channel_open(ch->dev->name); | ||
159 | |||
160 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | ||
161 | if (!priv) { | ||
162 | nvhost_putchannel(ch, NULL); | ||
163 | return -ENOMEM; | ||
164 | } | ||
165 | filp->private_data = priv; | ||
166 | priv->ch = ch; | ||
167 | nvhost_module_add_client(ch->dev, priv); | ||
168 | |||
169 | if (ch->ctxhandler && ch->ctxhandler->alloc) { | ||
170 | priv->hwctx = ch->ctxhandler->alloc(ch->ctxhandler, ch); | ||
171 | if (!priv->hwctx) | ||
172 | goto fail; | ||
173 | } | ||
174 | priv->priority = NVHOST_PRIORITY_MEDIUM; | ||
175 | priv->clientid = atomic_add_return(1, | ||
176 | &nvhost_get_host(ch->dev)->clientid); | ||
177 | |||
178 | priv->job = nvhost_job_alloc(ch, priv->hwctx, &priv->hdr, | ||
179 | NULL, priv->priority, priv->clientid); | ||
180 | if (!priv->job) | ||
181 | goto fail; | ||
182 | |||
183 | return 0; | ||
184 | fail: | ||
185 | nvhost_channelrelease(inode, filp); | ||
186 | return -ENOMEM; | ||
187 | } | ||
188 | |||
189 | static int set_submit(struct nvhost_channel_userctx *ctx) | ||
190 | { | ||
191 | struct device *device = &ctx->ch->dev->dev; | ||
192 | |||
193 | /* submit should have at least 1 cmdbuf */ | ||
194 | if (!ctx->hdr.num_cmdbufs) | ||
195 | return -EIO; | ||
196 | |||
197 | if (!ctx->nvmap) { | ||
198 | dev_err(device, "no nvmap context set\n"); | ||
199 | return -EFAULT; | ||
200 | } | ||
201 | |||
202 | ctx->job = nvhost_job_realloc(ctx->job, | ||
203 | ctx->hwctx, | ||
204 | &ctx->hdr, | ||
205 | ctx->nvmap, | ||
206 | ctx->priority, | ||
207 | ctx->clientid); | ||
208 | if (!ctx->job) | ||
209 | return -ENOMEM; | ||
210 | ctx->job->timeout = ctx->timeout; | ||
211 | |||
212 | if (ctx->hdr.submit_version >= NVHOST_SUBMIT_VERSION_V2) | ||
213 | ctx->num_relocshifts = ctx->hdr.num_relocs; | ||
214 | |||
215 | return 0; | ||
216 | } | ||
217 | |||
218 | static void reset_submit(struct nvhost_channel_userctx *ctx) | ||
219 | { | ||
220 | ctx->hdr.num_cmdbufs = 0; | ||
221 | ctx->hdr.num_relocs = 0; | ||
222 | ctx->num_relocshifts = 0; | ||
223 | ctx->hdr.num_waitchks = 0; | ||
224 | } | ||
225 | |||
226 | static ssize_t nvhost_channelwrite(struct file *filp, const char __user *buf, | ||
227 | size_t count, loff_t *offp) | ||
228 | { | ||
229 | struct nvhost_channel_userctx *priv = filp->private_data; | ||
230 | size_t remaining = count; | ||
231 | int err = 0; | ||
232 | struct nvhost_job *job = priv->job; | ||
233 | struct nvhost_submit_hdr_ext *hdr = &priv->hdr; | ||
234 | const char *chname = priv->ch->dev->name; | ||
235 | |||
236 | if (!job) | ||
237 | return -EIO; | ||
238 | |||
239 | while (remaining) { | ||
240 | size_t consumed; | ||
241 | if (!hdr->num_relocs && | ||
242 | !priv->num_relocshifts && | ||
243 | !hdr->num_cmdbufs && | ||
244 | !hdr->num_waitchks) { | ||
245 | consumed = sizeof(struct nvhost_submit_hdr); | ||
246 | if (remaining < consumed) | ||
247 | break; | ||
248 | if (copy_from_user(hdr, buf, consumed)) { | ||
249 | err = -EFAULT; | ||
250 | break; | ||
251 | } | ||
252 | hdr->submit_version = NVHOST_SUBMIT_VERSION_V0; | ||
253 | err = set_submit(priv); | ||
254 | if (err) | ||
255 | break; | ||
256 | trace_nvhost_channel_write_submit(chname, | ||
257 | count, hdr->num_cmdbufs, hdr->num_relocs, | ||
258 | hdr->syncpt_id, hdr->syncpt_incrs); | ||
259 | } else if (hdr->num_cmdbufs) { | ||
260 | struct nvhost_cmdbuf cmdbuf; | ||
261 | consumed = sizeof(cmdbuf); | ||
262 | if (remaining < consumed) | ||
263 | break; | ||
264 | if (copy_from_user(&cmdbuf, buf, consumed)) { | ||
265 | err = -EFAULT; | ||
266 | break; | ||
267 | } | ||
268 | trace_nvhost_channel_write_cmdbuf(chname, | ||
269 | cmdbuf.mem, cmdbuf.words, cmdbuf.offset); | ||
270 | nvhost_job_add_gather(job, | ||
271 | cmdbuf.mem, cmdbuf.words, cmdbuf.offset); | ||
272 | hdr->num_cmdbufs--; | ||
273 | } else if (hdr->num_relocs) { | ||
274 | consumed = sizeof(struct nvhost_reloc); | ||
275 | if (remaining < consumed) | ||
276 | break; | ||
277 | if (copy_from_user(&job->pinarray[job->num_pins], | ||
278 | buf, consumed)) { | ||
279 | err = -EFAULT; | ||
280 | break; | ||
281 | } | ||
282 | trace_nvhost_channel_write_reloc(chname); | ||
283 | job->num_pins++; | ||
284 | hdr->num_relocs--; | ||
285 | } else if (hdr->num_waitchks) { | ||
286 | int numwaitchks = | ||
287 | (remaining / sizeof(struct nvhost_waitchk)); | ||
288 | if (!numwaitchks) | ||
289 | break; | ||
290 | numwaitchks = min_t(int, | ||
291 | numwaitchks, hdr->num_waitchks); | ||
292 | consumed = numwaitchks * sizeof(struct nvhost_waitchk); | ||
293 | if (copy_from_user(&job->waitchk[job->num_waitchk], | ||
294 | buf, consumed)) { | ||
295 | err = -EFAULT; | ||
296 | break; | ||
297 | } | ||
298 | trace_nvhost_channel_write_waitchks( | ||
299 | chname, numwaitchks, | ||
300 | hdr->waitchk_mask); | ||
301 | job->num_waitchk += numwaitchks; | ||
302 | hdr->num_waitchks -= numwaitchks; | ||
303 | } else if (priv->num_relocshifts) { | ||
304 | int next_shift = | ||
305 | job->num_pins - priv->num_relocshifts; | ||
306 | consumed = sizeof(struct nvhost_reloc_shift); | ||
307 | if (remaining < consumed) | ||
308 | break; | ||
309 | if (copy_from_user( | ||
310 | &job->pinarray[next_shift].reloc_shift, | ||
311 | buf, consumed)) { | ||
312 | err = -EFAULT; | ||
313 | break; | ||
314 | } | ||
315 | priv->num_relocshifts--; | ||
316 | } else { | ||
317 | err = -EFAULT; | ||
318 | break; | ||
319 | } | ||
320 | remaining -= consumed; | ||
321 | buf += consumed; | ||
322 | } | ||
323 | |||
324 | if (err < 0) { | ||
325 | dev_err(&priv->ch->dev->dev, "channel write error\n"); | ||
326 | reset_submit(priv); | ||
327 | return err; | ||
328 | } | ||
329 | |||
330 | return count - remaining; | ||
331 | } | ||
332 | |||
333 | static int nvhost_ioctl_channel_flush( | ||
334 | struct nvhost_channel_userctx *ctx, | ||
335 | struct nvhost_get_param_args *args, | ||
336 | int null_kickoff) | ||
337 | { | ||
338 | struct device *device = &ctx->ch->dev->dev; | ||
339 | int err; | ||
340 | |||
341 | trace_nvhost_ioctl_channel_flush(ctx->ch->dev->name); | ||
342 | |||
343 | if (!ctx->job || | ||
344 | ctx->hdr.num_relocs || | ||
345 | ctx->hdr.num_cmdbufs || | ||
346 | ctx->hdr.num_waitchks) { | ||
347 | reset_submit(ctx); | ||
348 | dev_err(device, "channel submit out of sync\n"); | ||
349 | return -EFAULT; | ||
350 | } | ||
351 | |||
352 | err = nvhost_job_pin(ctx->job); | ||
353 | if (err) { | ||
354 | dev_warn(device, "nvhost_job_pin failed: %d\n", err); | ||
355 | return err; | ||
356 | } | ||
357 | |||
358 | if (nvhost_debug_null_kickoff_pid == current->tgid) | ||
359 | null_kickoff = 1; | ||
360 | ctx->job->null_kickoff = null_kickoff; | ||
361 | |||
362 | if ((nvhost_debug_force_timeout_pid == current->tgid) && | ||
363 | (nvhost_debug_force_timeout_channel == ctx->ch->chid)) { | ||
364 | ctx->timeout = nvhost_debug_force_timeout_val; | ||
365 | } | ||
366 | |||
367 | trace_write_cmdbufs(ctx->job); | ||
368 | |||
369 | /* context switch if needed, and submit user's gathers to the channel */ | ||
370 | err = nvhost_channel_submit(ctx->job); | ||
371 | args->value = ctx->job->syncpt_end; | ||
372 | if (err) | ||
373 | nvhost_job_unpin(ctx->job); | ||
374 | |||
375 | return err; | ||
376 | } | ||
377 | |||
378 | static int nvhost_ioctl_channel_read_3d_reg( | ||
379 | struct nvhost_channel_userctx *ctx, | ||
380 | struct nvhost_read_3d_reg_args *args) | ||
381 | { | ||
382 | BUG_ON(!channel_op(ctx->ch).read3dreg); | ||
383 | return channel_op(ctx->ch).read3dreg(ctx->ch, ctx->hwctx, | ||
384 | args->offset, &args->value); | ||
385 | } | ||
386 | |||
387 | static long nvhost_channelctl(struct file *filp, | ||
388 | unsigned int cmd, unsigned long arg) | ||
389 | { | ||
390 | struct nvhost_channel_userctx *priv = filp->private_data; | ||
391 | u8 buf[NVHOST_IOCTL_CHANNEL_MAX_ARG_SIZE]; | ||
392 | int err = 0; | ||
393 | |||
394 | if ((_IOC_TYPE(cmd) != NVHOST_IOCTL_MAGIC) || | ||
395 | (_IOC_NR(cmd) == 0) || | ||
396 | (_IOC_NR(cmd) > NVHOST_IOCTL_CHANNEL_LAST)) | ||
397 | return -EFAULT; | ||
398 | |||
399 | BUG_ON(_IOC_SIZE(cmd) > NVHOST_IOCTL_CHANNEL_MAX_ARG_SIZE); | ||
400 | |||
401 | if (_IOC_DIR(cmd) & _IOC_WRITE) { | ||
402 | if (copy_from_user(buf, (void __user *)arg, _IOC_SIZE(cmd))) | ||
403 | return -EFAULT; | ||
404 | } | ||
405 | |||
406 | switch (cmd) { | ||
407 | case NVHOST_IOCTL_CHANNEL_FLUSH: | ||
408 | err = nvhost_ioctl_channel_flush(priv, (void *)buf, 0); | ||
409 | break; | ||
410 | case NVHOST_IOCTL_CHANNEL_NULL_KICKOFF: | ||
411 | err = nvhost_ioctl_channel_flush(priv, (void *)buf, 1); | ||
412 | break; | ||
413 | case NVHOST_IOCTL_CHANNEL_SUBMIT_EXT: | ||
414 | { | ||
415 | struct nvhost_submit_hdr_ext *hdr; | ||
416 | |||
417 | if (priv->hdr.num_relocs || | ||
418 | priv->num_relocshifts || | ||
419 | priv->hdr.num_cmdbufs || | ||
420 | priv->hdr.num_waitchks) { | ||
421 | reset_submit(priv); | ||
422 | dev_err(&priv->ch->dev->dev, | ||
423 | "channel submit out of sync\n"); | ||
424 | err = -EIO; | ||
425 | break; | ||
426 | } | ||
427 | |||
428 | hdr = (struct nvhost_submit_hdr_ext *)buf; | ||
429 | if (hdr->submit_version > NVHOST_SUBMIT_VERSION_MAX_SUPPORTED) { | ||
430 | dev_err(&priv->ch->dev->dev, | ||
431 | "submit version %d > max supported %d\n", | ||
432 | hdr->submit_version, | ||
433 | NVHOST_SUBMIT_VERSION_MAX_SUPPORTED); | ||
434 | err = -EINVAL; | ||
435 | break; | ||
436 | } | ||
437 | memcpy(&priv->hdr, hdr, sizeof(struct nvhost_submit_hdr_ext)); | ||
438 | err = set_submit(priv); | ||
439 | trace_nvhost_ioctl_channel_submit(priv->ch->dev->name, | ||
440 | priv->hdr.submit_version, | ||
441 | priv->hdr.num_cmdbufs, priv->hdr.num_relocs, | ||
442 | priv->hdr.num_waitchks, | ||
443 | priv->hdr.syncpt_id, priv->hdr.syncpt_incrs); | ||
444 | break; | ||
445 | } | ||
446 | case NVHOST_IOCTL_CHANNEL_GET_SYNCPOINTS: | ||
447 | /* host syncpt ID is used by the RM (and never be given out) */ | ||
448 | BUG_ON(priv->ch->dev->syncpts & (1 << NVSYNCPT_GRAPHICS_HOST)); | ||
449 | ((struct nvhost_get_param_args *)buf)->value = | ||
450 | priv->ch->dev->syncpts; | ||
451 | break; | ||
452 | case NVHOST_IOCTL_CHANNEL_GET_WAITBASES: | ||
453 | ((struct nvhost_get_param_args *)buf)->value = | ||
454 | priv->ch->dev->waitbases; | ||
455 | break; | ||
456 | case NVHOST_IOCTL_CHANNEL_GET_MODMUTEXES: | ||
457 | ((struct nvhost_get_param_args *)buf)->value = | ||
458 | priv->ch->dev->modulemutexes; | ||
459 | break; | ||
460 | case NVHOST_IOCTL_CHANNEL_SET_NVMAP_FD: | ||
461 | { | ||
462 | int fd = (int)((struct nvhost_set_nvmap_fd_args *)buf)->fd; | ||
463 | struct nvmap_client *new_client = nvmap_client_get_file(fd); | ||
464 | |||
465 | if (IS_ERR(new_client)) { | ||
466 | err = PTR_ERR(new_client); | ||
467 | break; | ||
468 | } | ||
469 | |||
470 | if (priv->nvmap) | ||
471 | nvmap_client_put(priv->nvmap); | ||
472 | |||
473 | priv->nvmap = new_client; | ||
474 | break; | ||
475 | } | ||
476 | case NVHOST_IOCTL_CHANNEL_READ_3D_REG: | ||
477 | err = nvhost_ioctl_channel_read_3d_reg(priv, (void *)buf); | ||
478 | break; | ||
479 | case NVHOST_IOCTL_CHANNEL_GET_CLK_RATE: | ||
480 | { | ||
481 | unsigned long rate; | ||
482 | struct nvhost_clk_rate_args *arg = | ||
483 | (struct nvhost_clk_rate_args *)buf; | ||
484 | |||
485 | err = nvhost_module_get_rate(priv->ch->dev, &rate, 0); | ||
486 | if (err == 0) | ||
487 | arg->rate = rate; | ||
488 | break; | ||
489 | } | ||
490 | case NVHOST_IOCTL_CHANNEL_SET_CLK_RATE: | ||
491 | { | ||
492 | struct nvhost_clk_rate_args *arg = | ||
493 | (struct nvhost_clk_rate_args *)buf; | ||
494 | unsigned long rate = (unsigned long)arg->rate; | ||
495 | |||
496 | err = nvhost_module_set_rate(priv->ch->dev, priv, rate, 0); | ||
497 | break; | ||
498 | } | ||
499 | case NVHOST_IOCTL_CHANNEL_SET_TIMEOUT: | ||
500 | priv->timeout = | ||
501 | (u32)((struct nvhost_set_timeout_args *)buf)->timeout; | ||
502 | dev_dbg(&priv->ch->dev->dev, | ||
503 | "%s: setting buffer timeout (%d ms) for userctx 0x%p\n", | ||
504 | __func__, priv->timeout, priv); | ||
505 | break; | ||
506 | case NVHOST_IOCTL_CHANNEL_GET_TIMEDOUT: | ||
507 | ((struct nvhost_get_param_args *)buf)->value = | ||
508 | priv->hwctx->has_timedout; | ||
509 | break; | ||
510 | case NVHOST_IOCTL_CHANNEL_SET_PRIORITY: | ||
511 | priv->priority = | ||
512 | (u32)((struct nvhost_set_priority_args *)buf)->priority; | ||
513 | break; | ||
514 | default: | ||
515 | err = -ENOTTY; | ||
516 | break; | ||
517 | } | ||
518 | |||
519 | if ((err == 0) && (_IOC_DIR(cmd) & _IOC_READ)) | ||
520 | err = copy_to_user((void __user *)arg, buf, _IOC_SIZE(cmd)); | ||
521 | |||
522 | return err; | ||
523 | } | ||
524 | |||
525 | static const struct file_operations nvhost_channelops = { | ||
526 | .owner = THIS_MODULE, | ||
527 | .release = nvhost_channelrelease, | ||
528 | .open = nvhost_channelopen, | ||
529 | .write = nvhost_channelwrite, | ||
530 | .unlocked_ioctl = nvhost_channelctl | ||
531 | }; | ||
532 | |||
533 | int nvhost_client_user_init(struct nvhost_device *dev) | ||
534 | { | ||
535 | int err, devno; | ||
536 | |||
537 | struct nvhost_channel *ch = dev->channel; | ||
538 | |||
539 | cdev_init(&ch->cdev, &nvhost_channelops); | ||
540 | ch->cdev.owner = THIS_MODULE; | ||
541 | |||
542 | devno = MKDEV(nvhost_major, nvhost_minor + dev->index); | ||
543 | err = cdev_add(&ch->cdev, devno, 1); | ||
544 | if (err < 0) { | ||
545 | dev_err(&dev->dev, | ||
546 | "failed to add chan %i cdev\n", dev->index); | ||
547 | goto fail; | ||
548 | } | ||
549 | ch->node = device_create(nvhost_get_host(dev)->nvhost_class, NULL, devno, NULL, | ||
550 | IFACE_NAME "-%s", dev->name); | ||
551 | if (IS_ERR(ch->node)) { | ||
552 | err = PTR_ERR(ch->node); | ||
553 | dev_err(&dev->dev, | ||
554 | "failed to create %s channel device\n", dev->name); | ||
555 | goto fail; | ||
556 | } | ||
557 | |||
558 | return 0; | ||
559 | fail: | ||
560 | return err; | ||
561 | } | ||
562 | |||
563 | int nvhost_client_device_init(struct nvhost_device *dev) | ||
564 | { | ||
565 | int err; | ||
566 | struct nvhost_master *nvhost_master = nvhost_get_host(dev); | ||
567 | struct nvhost_channel *ch = &nvhost_master->channels[dev->index]; | ||
568 | |||
569 | /* store the pointer to this device for channel */ | ||
570 | ch->dev = dev; | ||
571 | |||
572 | err = nvhost_channel_init(ch, nvhost_master, dev->index); | ||
573 | if (err) | ||
574 | goto fail; | ||
575 | |||
576 | err = nvhost_client_user_init(dev); | ||
577 | if (err) | ||
578 | goto fail; | ||
579 | |||
580 | err = nvhost_module_init(dev); | ||
581 | if (err) | ||
582 | goto fail; | ||
583 | |||
584 | dev_info(&dev->dev, "initialized\n"); | ||
585 | |||
586 | return 0; | ||
587 | |||
588 | fail: | ||
589 | /* Add clean-up */ | ||
590 | return err; | ||
591 | } | ||
592 | |||
593 | int nvhost_client_device_suspend(struct nvhost_device *dev) | ||
594 | { | ||
595 | int ret = 0; | ||
596 | |||
597 | dev_info(&dev->dev, "suspending\n"); | ||
598 | |||
599 | ret = nvhost_channel_suspend(dev->channel); | ||
600 | if (ret) | ||
601 | return ret; | ||
602 | |||
603 | dev_info(&dev->dev, "suspend status: %d\n", ret); | ||
604 | |||
605 | return ret; | ||
606 | } | ||