From a9785995d5f22aaeb659285f8aeb64d8b56982e0 Mon Sep 17 00:00:00 2001 From: Arto Merilainen Date: Wed, 19 Mar 2014 09:38:25 +0200 Subject: 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 Reviewed-on: http://git-master/r/383722 Reviewed-by: Terje Bergstrom --- drivers/gpu/nvgpu/gk20a/channel_sync_gk20a.c | 356 +++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 drivers/gpu/nvgpu/gk20a/channel_sync_gk20a.c (limited to 'drivers/gpu/nvgpu/gk20a/channel_sync_gk20a.c') 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 @@ +/* + * drivers/video/tegra/host/gk20a/channel_sync_gk20a.c + * + * GK20A Channel Synchronization Abstraction + * + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include + +#include "channel_sync_gk20a.h" +#include "gk20a.h" + +#ifdef CONFIG_SYNC +#include "../../../staging/android/sync.h" +#endif + +#ifdef CONFIG_TEGRA_GK20A +#include +#endif + +#ifdef CONFIG_TEGRA_GK20A + +struct gk20a_channel_syncpt { + struct gk20a_channel_sync ops; + struct channel_gk20a *c; + struct platform_device *host1x_pdev; + u32 id; +}; + +static void add_wait_cmd(u32 *ptr, u32 id, u32 thresh) +{ + /* syncpoint_a */ + ptr[0] = 0x2001001C; + /* payload */ + ptr[1] = thresh; + /* syncpoint_b */ + ptr[2] = 0x2001001D; + /* syncpt_id, switch_en, wait */ + ptr[3] = (id << 8) | 0x10; +} + +int gk20a_channel_syncpt_wait_cpu(struct gk20a_channel_sync *s, + struct gk20a_channel_fence *fence, + int timeout) +{ + struct gk20a_channel_syncpt *sp = + container_of(s, struct gk20a_channel_syncpt, ops); + if (!fence->valid) + return 0; + return nvhost_syncpt_wait_timeout_ext( + sp->host1x_pdev, sp->id, fence->thresh, + timeout, NULL, NULL); +} + +bool gk20a_channel_syncpt_is_expired(struct gk20a_channel_sync *s, + struct gk20a_channel_fence *fence) +{ + struct gk20a_channel_syncpt *sp = + container_of(s, struct gk20a_channel_syncpt, ops); + if (!fence->valid) + return true; + return nvhost_syncpt_is_expired_ext(sp->host1x_pdev, sp->id, + fence->thresh); +} + +int gk20a_channel_syncpt_wait_syncpt(struct gk20a_channel_sync *s, u32 id, + u32 thresh, struct priv_cmd_entry **entry) +{ + struct gk20a_channel_syncpt *sp = + container_of(s, struct gk20a_channel_syncpt, ops); + struct priv_cmd_entry *wait_cmd = NULL; + + if (id >= nvhost_syncpt_nb_pts_ext(sp->host1x_pdev)) { + dev_warn(dev_from_gk20a(sp->c->g), + "invalid wait id in gpfifo submit, elided"); + return 0; + } + + if (nvhost_syncpt_is_expired_ext(sp->host1x_pdev, id, thresh)) + return 0; + + gk20a_channel_alloc_priv_cmdbuf(sp->c, 4, &wait_cmd); + if (wait_cmd == NULL) { + gk20a_err(dev_from_gk20a(sp->c->g), + "not enough priv cmd buffer space"); + return -EAGAIN; + } + + add_wait_cmd(&wait_cmd->ptr[0], id, thresh); + + *entry = wait_cmd; + return 0; +} + +int gk20a_channel_syncpt_wait_fd(struct gk20a_channel_sync *s, int fd, + struct priv_cmd_entry **entry) +{ +#ifdef CONFIG_SYNC + int i; + int num_wait_cmds; + struct sync_pt *pt; + struct sync_fence *sync_fence; + struct priv_cmd_entry *wait_cmd = NULL; + struct gk20a_channel_syncpt *sp = + container_of(s, struct gk20a_channel_syncpt, ops); + struct channel_gk20a *c = sp->c; + + sync_fence = nvhost_sync_fdget(fd); + if (!sync_fence) + return -EINVAL; + + num_wait_cmds = nvhost_sync_num_pts(sync_fence); + gk20a_channel_alloc_priv_cmdbuf(c, 4 * num_wait_cmds, &wait_cmd); + if (wait_cmd == NULL) { + gk20a_err(dev_from_gk20a(c->g), + "not enough priv cmd buffer space"); + sync_fence_put(sync_fence); + return -EAGAIN; + } + + i = 0; + list_for_each_entry(pt, &sync_fence->pt_list_head, pt_list) { + u32 wait_id = nvhost_sync_pt_id(pt); + u32 wait_value = nvhost_sync_pt_thresh(pt); + + if (nvhost_syncpt_is_expired_ext(sp->host1x_pdev, + wait_id, wait_value)) { + wait_cmd->ptr[i * 4 + 0] = 0; + wait_cmd->ptr[i * 4 + 1] = 0; + wait_cmd->ptr[i * 4 + 2] = 0; + wait_cmd->ptr[i * 4 + 3] = 0; + } else + add_wait_cmd(&wait_cmd->ptr[i * 4], wait_id, + wait_value); + i++; + } + WARN_ON(i != num_wait_cmds); + sync_fence_put(sync_fence); + + *entry = wait_cmd; + return 0; +#else + return -ENODEV; +#endif +} + +static void gk20a_channel_syncpt_update(void *priv, int nr_completed) +{ + struct channel_gk20a *ch20a = priv; + gk20a_channel_update(ch20a, nr_completed); +} + +static int __gk20a_channel_syncpt_incr(struct gk20a_channel_sync *s, + bool gfx_class, bool wfi_cmd, + struct priv_cmd_entry **entry, + struct gk20a_channel_fence *fence) +{ + u32 thresh; + int incr_cmd_size; + int j = 0; + int err; + struct priv_cmd_entry *incr_cmd = NULL; + struct gk20a_channel_syncpt *sp = + container_of(s, struct gk20a_channel_syncpt, ops); + struct channel_gk20a *c = sp->c; + + /* nvhost action_gpfifo_submit_complete releases this ref. */ + err = gk20a_channel_busy(c->g->dev); + if (err) + return err; + + incr_cmd_size = 4; + if (wfi_cmd) + incr_cmd_size += 2; + + gk20a_channel_alloc_priv_cmdbuf(c, incr_cmd_size, &incr_cmd); + if (incr_cmd == NULL) { + gk20a_channel_idle(c->g->dev); + gk20a_err(dev_from_gk20a(c->g), + "not enough priv cmd buffer space"); + return -EAGAIN; + } + + if (gfx_class) { + WARN_ON(wfi_cmd); /* No sense to use gfx class + wfi. */ + /* setobject KEPLER_C */ + incr_cmd->ptr[j++] = 0x20010000; + incr_cmd->ptr[j++] = KEPLER_C; + /* syncpt incr */ + incr_cmd->ptr[j++] = 0x200100B2; + incr_cmd->ptr[j++] = sp->id | + (0x1 << 20) | (0x1 << 16); + } else { + if (wfi_cmd) { + /* wfi */ + incr_cmd->ptr[j++] = 0x2001001E; + /* handle, ignored */ + incr_cmd->ptr[j++] = 0x00000000; + } + /* syncpoint_a */ + incr_cmd->ptr[j++] = 0x2001001C; + /* payload, ignored */ + incr_cmd->ptr[j++] = 0; + /* syncpoint_b */ + incr_cmd->ptr[j++] = 0x2001001D; + /* syncpt_id, incr */ + incr_cmd->ptr[j++] = (sp->id << 8) | 0x1; + } + WARN_ON(j != incr_cmd_size); + + thresh = nvhost_syncpt_incr_max_ext(sp->host1x_pdev, sp->id, 1); + + err = nvhost_intr_register_notifier(sp->host1x_pdev, sp->id, thresh, + gk20a_channel_syncpt_update, c); + + /* Adding interrupt action should never fail. A proper error handling + * here would require us to decrement the syncpt max back to its + * original value. */ + if (WARN(err, "failed to set submit complete interrupt")) { + gk20a_channel_idle(c->g->dev); + err = 0; /* Ignore this error. */ + } + + fence->thresh = thresh; + fence->valid = true; + fence->wfi = wfi_cmd; + *entry = incr_cmd; + return 0; +} + +int gk20a_channel_syncpt_incr_wfi(struct gk20a_channel_sync *s, + struct priv_cmd_entry **entry, + struct gk20a_channel_fence *fence) +{ + return __gk20a_channel_syncpt_incr(s, + false /* use host class */, + true /* wfi */, + entry, fence); +} + +int gk20a_channel_syncpt_incr(struct gk20a_channel_sync *s, + struct priv_cmd_entry **entry, + struct gk20a_channel_fence *fence) +{ + struct gk20a_channel_syncpt *sp = + container_of(s, struct gk20a_channel_syncpt, ops); + /* Don't put wfi cmd to this one since we're not returning + * a fence to user space. */ + return __gk20a_channel_syncpt_incr(s, + sp->c->obj_class == KEPLER_C /* may use gfx class */, + false /* no wfi */, + entry, fence); +} + +int gk20a_channel_syncpt_incr_user_syncpt(struct gk20a_channel_sync *s, + struct priv_cmd_entry **entry, + struct gk20a_channel_fence *fence, + u32 *id, u32 *thresh) +{ + struct gk20a_channel_syncpt *sp = + container_of(s, struct gk20a_channel_syncpt, ops); + /* Need to do 'host incr + wfi' or 'gfx incr' since we return the fence + * to user space. */ + int err = __gk20a_channel_syncpt_incr(s, + sp->c->obj_class == KEPLER_C /* use gfx class? */, + sp->c->obj_class != KEPLER_C /* wfi if host class */, + entry, fence); + if (err) + return err; + *id = sp->id; + *thresh = fence->thresh; + return 0; +} + +int gk20a_channel_syncpt_incr_user_fd(struct gk20a_channel_sync *s, + struct priv_cmd_entry **entry, + struct gk20a_channel_fence *fence, + int *fd) +{ +#ifdef CONFIG_SYNC + int err; + struct nvhost_ctrl_sync_fence_info pt; + struct gk20a_channel_syncpt *sp = + container_of(s, struct gk20a_channel_syncpt, ops); + err = gk20a_channel_syncpt_incr_user_syncpt(s, entry, fence, + &pt.id, &pt.thresh); + if (err) + return err; + return nvhost_sync_create_fence_fd(sp->host1x_pdev, &pt, 1, + "fence", fd); +#else + return -ENODEV; +#endif +} + +void gk20a_channel_syncpt_set_min_eq_max(struct gk20a_channel_sync *s) +{ + struct gk20a_channel_syncpt *sp = + container_of(s, struct gk20a_channel_syncpt, ops); + nvhost_syncpt_set_min_eq_max_ext(sp->host1x_pdev, sp->id); +} + +static void gk20a_channel_syncpt_destroy(struct gk20a_channel_sync *s) +{ + struct gk20a_channel_syncpt *sp = + container_of(s, struct gk20a_channel_syncpt, ops); + nvhost_free_syncpt(sp->id); + kfree(sp); +} + +static struct gk20a_channel_sync * +gk20a_channel_syncpt_create(struct channel_gk20a *c) +{ + struct gk20a_channel_syncpt *sp; + + sp = kzalloc(sizeof(*sp), GFP_KERNEL); + if (!sp) + return NULL; + + sp->c = c; + sp->host1x_pdev = to_platform_device(c->g->dev->dev.parent); + sp->id = nvhost_get_syncpt_host_managed(sp->host1x_pdev, c->hw_chid); + + sp->ops.wait_cpu = gk20a_channel_syncpt_wait_cpu; + sp->ops.is_expired = gk20a_channel_syncpt_is_expired; + sp->ops.wait_syncpt = gk20a_channel_syncpt_wait_syncpt; + sp->ops.wait_fd = gk20a_channel_syncpt_wait_fd; + sp->ops.incr = gk20a_channel_syncpt_incr; + sp->ops.incr_wfi = gk20a_channel_syncpt_incr_wfi; + sp->ops.incr_user_syncpt = gk20a_channel_syncpt_incr_user_syncpt; + sp->ops.incr_user_fd = gk20a_channel_syncpt_incr_user_fd; + sp->ops.set_min_eq_max = gk20a_channel_syncpt_set_min_eq_max; + sp->ops.destroy = gk20a_channel_syncpt_destroy; + return &sp->ops; +} +#endif /* CONFIG_TEGRA_GK20A */ + +struct gk20a_channel_sync *gk20a_channel_sync_create(struct channel_gk20a *c) +{ +#ifdef CONFIG_TEGRA_GK20A + if (gk20a_platform_has_syncpoints(c->g->dev)) + return gk20a_channel_syncpt_create(c); +#endif + WARN_ON(1); + return NULL; +} -- cgit v1.2.2