2013-03-13 12:35:53

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 00/10] Support for Tegra 2D hardware

This set of patches adds support for Tegra20 and Tegra30 host1x and
2D. It is based on linux-next-20130307.

Changes in this version:
* host1x memory data structures refactored
* Some "nvhost" leftovers renamed to host1x

Changes in previous version 6:
* Rebased on latest tegradrm
* Renamed tegradrm's host1x to host1x_drm
* Indentation and line split fixed to follow tegradrm convention
* Pointers to platform_device replaced with pointers to device
* Added host1x allocator, and wired it in
* Debug spew code fixed to access mem handles from host1x_job
* CDMA code doesn't keep the mem handles anymore
* Push buffer ops have been made generic code
* Removed the pin_array optimization in host1x_job to simplify code
* Large number of smaller changes

The driver implements an allocator using the DRM CMA helper. Each buffer is
assigned an ops structure to operate on it. In future the DRM CMA helper will
be replaced with an own allocator to implement IOMMU support.

host1x is the driver that controls host1x hardware. It supports
host1x command channels, synchronization, and memory management. It
is sectioned into logical driver under drivers/gpu/host1x and
physical driver under drivers/host1x/hw. The physical driver is
compiled with the hardware headers of the particular host1x version.

The hardware units are described (briefly) in the Tegra2 TRM. Wiki
page http://http.download.nvidia.com/tegra-public-appnotes/host1x.html
also contains a short description of the functionality.

The patch set merges tegradrm into host1x and adds 2D driver, which
uses host1x channels and sync points. The patch set also adds user
space API to tegradrm for accessing host1x and 2D.

The changes to add support to libdrm are in
[email protected]:linux-host1x/libdrm-host1x.git

Arto Merilainen (3):
gpu: drm: Support CMA object preallocation
gpu: host1x: drm: Rename host1x to host1x_drm
gpu: host1x: drm: Add CMA ops for host1x driver

Terje Bergstrom (7):
gpu: host1x: Add host1x driver
gpu: host1x: Add syncpoint wait and interrupts
gpu: host1x: Add channel support
gpu: host1x: Add debug support
drm: tegra: Move drm to live under host1x
gpu: host1x: Remove second host1x driver
drm: tegra: Add gr2d device

drivers/gpu/Makefile | 1 +
drivers/gpu/drm/Kconfig | 2 -
drivers/gpu/drm/Makefile | 1 -
drivers/gpu/drm/drm_gem_cma_helper.c | 78 ++-
drivers/gpu/drm/tegra/Makefile | 7 -
drivers/gpu/drm/tegra/drm.c | 217 --------
drivers/gpu/drm/tegra/host1x.c | 327 ------------
drivers/gpu/host1x/Kconfig | 23 +
drivers/gpu/host1x/Makefile | 20 +
drivers/gpu/host1x/cdma.c | 491 +++++++++++++++++
drivers/gpu/host1x/cdma.h | 100 ++++
drivers/gpu/host1x/channel.c | 120 +++++
drivers/gpu/host1x/channel.h | 52 ++
drivers/gpu/host1x/debug.c | 210 ++++++++
drivers/gpu/host1x/debug.h | 51 ++
drivers/gpu/host1x/dev.c | 248 +++++++++
drivers/gpu/host1x/dev.h | 316 +++++++++++
drivers/gpu/{drm/tegra => host1x/drm}/Kconfig | 2 +-
drivers/gpu/host1x/drm/cma.c | 93 ++++
drivers/gpu/host1x/drm/cma.h | 35 ++
drivers/gpu/{drm/tegra => host1x/drm}/dc.c | 5 +-
drivers/gpu/{drm/tegra => host1x/drm}/dc.h | 0
drivers/gpu/host1x/drm/drm.c | 673 ++++++++++++++++++++++++
drivers/gpu/{drm/tegra => host1x/drm}/drm.h | 51 +-
drivers/gpu/{drm/tegra => host1x/drm}/fb.c | 6 +-
drivers/gpu/host1x/drm/gr2d.c | 330 ++++++++++++
drivers/gpu/{drm/tegra => host1x/drm}/hdmi.c | 5 +-
drivers/gpu/{drm/tegra => host1x/drm}/hdmi.h | 0
drivers/gpu/{drm/tegra => host1x/drm}/output.c | 0
drivers/gpu/{drm/tegra => host1x/drm}/rgb.c | 0
drivers/gpu/host1x/host1x.h | 29 +
drivers/gpu/host1x/host1x_bo.h | 92 ++++
drivers/gpu/host1x/host1x_client.h | 35 ++
drivers/gpu/host1x/hw/Makefile | 6 +
drivers/gpu/host1x/hw/cdma_hw.c | 326 ++++++++++++
drivers/gpu/host1x/hw/channel_hw.c | 167 ++++++
drivers/gpu/host1x/hw/debug_hw.c | 322 ++++++++++++
drivers/gpu/host1x/hw/host1x01.c | 42 ++
drivers/gpu/host1x/hw/host1x01.h | 25 +
drivers/gpu/host1x/hw/host1x01_hardware.h | 143 +++++
drivers/gpu/host1x/hw/hw_host1x01_channel.h | 120 +++++
drivers/gpu/host1x/hw/hw_host1x01_sync.h | 243 +++++++++
drivers/gpu/host1x/hw/hw_host1x01_uclass.h | 174 ++++++
drivers/gpu/host1x/hw/intr_hw.c | 143 +++++
drivers/gpu/host1x/hw/syncpt_hw.c | 114 ++++
drivers/gpu/host1x/intr.c | 354 +++++++++++++
drivers/gpu/host1x/intr.h | 102 ++++
drivers/gpu/host1x/job.c | 597 +++++++++++++++++++++
drivers/gpu/host1x/job.h | 157 ++++++
drivers/gpu/host1x/syncpt.c | 387 ++++++++++++++
drivers/gpu/host1x/syncpt.h | 165 ++++++
drivers/video/Kconfig | 2 +
include/drm/drm_gem_cma_helper.h | 9 +
include/drm/tegra_drm.h | 131 +++++
include/trace/events/host1x.h | 272 ++++++++++
55 files changed, 7022 insertions(+), 599 deletions(-)
delete mode 100644 drivers/gpu/drm/tegra/Makefile
delete mode 100644 drivers/gpu/drm/tegra/drm.c
delete mode 100644 drivers/gpu/drm/tegra/host1x.c
create mode 100644 drivers/gpu/host1x/Kconfig
create mode 100644 drivers/gpu/host1x/Makefile
create mode 100644 drivers/gpu/host1x/cdma.c
create mode 100644 drivers/gpu/host1x/cdma.h
create mode 100644 drivers/gpu/host1x/channel.c
create mode 100644 drivers/gpu/host1x/channel.h
create mode 100644 drivers/gpu/host1x/debug.c
create mode 100644 drivers/gpu/host1x/debug.h
create mode 100644 drivers/gpu/host1x/dev.c
create mode 100644 drivers/gpu/host1x/dev.h
rename drivers/gpu/{drm/tegra => host1x/drm}/Kconfig (94%)
create mode 100644 drivers/gpu/host1x/drm/cma.c
create mode 100644 drivers/gpu/host1x/drm/cma.h
rename drivers/gpu/{drm/tegra => host1x/drm}/dc.c (99%)
rename drivers/gpu/{drm/tegra => host1x/drm}/dc.h (100%)
create mode 100644 drivers/gpu/host1x/drm/drm.c
rename drivers/gpu/{drm/tegra => host1x/drm}/drm.h (82%)
rename drivers/gpu/{drm/tegra => host1x/drm}/fb.c (89%)
create mode 100644 drivers/gpu/host1x/drm/gr2d.c
rename drivers/gpu/{drm/tegra => host1x/drm}/hdmi.c (99%)
rename drivers/gpu/{drm/tegra => host1x/drm}/hdmi.h (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/output.c (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/rgb.c (100%)
create mode 100644 drivers/gpu/host1x/host1x.h
create mode 100644 drivers/gpu/host1x/host1x_bo.h
create mode 100644 drivers/gpu/host1x/host1x_client.h
create mode 100644 drivers/gpu/host1x/hw/Makefile
create mode 100644 drivers/gpu/host1x/hw/cdma_hw.c
create mode 100644 drivers/gpu/host1x/hw/channel_hw.c
create mode 100644 drivers/gpu/host1x/hw/debug_hw.c
create mode 100644 drivers/gpu/host1x/hw/host1x01.c
create mode 100644 drivers/gpu/host1x/hw/host1x01.h
create mode 100644 drivers/gpu/host1x/hw/host1x01_hardware.h
create mode 100644 drivers/gpu/host1x/hw/hw_host1x01_channel.h
create mode 100644 drivers/gpu/host1x/hw/hw_host1x01_sync.h
create mode 100644 drivers/gpu/host1x/hw/hw_host1x01_uclass.h
create mode 100644 drivers/gpu/host1x/hw/intr_hw.c
create mode 100644 drivers/gpu/host1x/hw/syncpt_hw.c
create mode 100644 drivers/gpu/host1x/intr.c
create mode 100644 drivers/gpu/host1x/intr.h
create mode 100644 drivers/gpu/host1x/job.c
create mode 100644 drivers/gpu/host1x/job.h
create mode 100644 drivers/gpu/host1x/syncpt.c
create mode 100644 drivers/gpu/host1x/syncpt.h
create mode 100644 include/drm/tegra_drm.h
create mode 100644 include/trace/events/host1x.h

--
1.7.9.5


2013-03-13 12:35:58

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 09/10] gpu: host1x: drm: Add CMA ops for host1x driver

From: Arto Merilainen <[email protected]>

This patch implements a CMA memory handler for the host1x driver.

Signed-off-by: Arto Merilainen <[email protected]>
Signed-off-by: Terje Bergstrom <[email protected]>
---
drivers/gpu/host1x/Makefile | 1 +
drivers/gpu/host1x/drm/cma.c | 93 ++++++++++++++++++++++++++++++++++++++++++
drivers/gpu/host1x/drm/cma.h | 35 ++++++++++++++++
3 files changed, 129 insertions(+)
create mode 100644 drivers/gpu/host1x/drm/cma.c
create mode 100644 drivers/gpu/host1x/drm/cma.h

diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile
index 9a6fc76..e85db5a 100644
--- a/drivers/gpu/host1x/Makefile
+++ b/drivers/gpu/host1x/Makefile
@@ -15,4 +15,5 @@ ccflags-$(CONFIG_DRM_TEGRA_DEBUG) += -DDEBUG

host1x-$(CONFIG_DRM_TEGRA) += drm/drm.o drm/fb.o drm/dc.o
host1x-$(CONFIG_DRM_TEGRA) += drm/output.o drm/rgb.o drm/hdmi.o
+host1x-$(CONFIG_DRM_TEGRA) += drm/cma.o
obj-$(CONFIG_TEGRA_HOST1X) += host1x.o
diff --git a/drivers/gpu/host1x/drm/cma.c b/drivers/gpu/host1x/drm/cma.c
new file mode 100644
index 0000000..cf86fce
--- /dev/null
+++ b/drivers/gpu/host1x/drm/cma.c
@@ -0,0 +1,93 @@
+/*
+ * Tegra host1x CMA support
+ *
+ * Copyright (c) 2012-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <linux/mutex.h>
+
+#include "cma.h"
+#include "host1x_bo.h"
+
+static void cma_put(struct host1x_bo *bo)
+{
+ struct tegra_drm_bo *cma_bo =
+ container_of(bo, struct tegra_drm_bo, base);
+ struct drm_device *drm = cma_bo->cma_obj.base.dev;
+
+ mutex_lock(&drm->struct_mutex);
+ drm_gem_object_unreference(&cma_bo->cma_obj.base);
+ mutex_unlock(&drm->struct_mutex);
+}
+
+static dma_addr_t cma_pin(struct host1x_bo *bo, struct sg_table **sgt)
+{
+ struct tegra_drm_bo *cma_bo =
+ container_of(bo, struct tegra_drm_bo, base);
+ return cma_bo->cma_obj.paddr;
+}
+
+static void cma_unpin(struct host1x_bo *bo, struct sg_table *sgt)
+{
+}
+
+static void *cma_mmap(struct host1x_bo *bo)
+{
+ struct tegra_drm_bo *cma_bo =
+ container_of(bo, struct tegra_drm_bo, base);
+ return cma_bo->cma_obj.vaddr;
+}
+
+static void cma_munmap(struct host1x_bo *bo, void *addr)
+{
+}
+
+static void *cma_kmap(struct host1x_bo *bo, unsigned int pagenum)
+{
+ struct tegra_drm_bo *cma_bo =
+ container_of(bo, struct tegra_drm_bo, base);
+ return cma_bo->cma_obj.vaddr + pagenum * PAGE_SIZE;
+}
+
+static void cma_kunmap(struct host1x_bo *bo, unsigned int pagenum, void *addr)
+{
+}
+
+static struct host1x_bo *cma_get(struct host1x_bo *bo)
+{
+ struct tegra_drm_bo *cma_bo =
+ container_of(bo, struct tegra_drm_bo, base);
+ struct drm_device *drm = cma_bo->cma_obj.base.dev;
+
+ mutex_lock(&drm->struct_mutex);
+ drm_gem_object_reference(&cma_bo->cma_obj.base);
+ mutex_unlock(&drm->struct_mutex);
+
+ return bo;
+}
+
+const struct host1x_bo_ops tegra_drm_bo_ops = {
+ .get = cma_get,
+ .put = cma_put,
+ .pin = cma_pin,
+ .unpin = cma_unpin,
+ .mmap = cma_mmap,
+ .munmap = cma_munmap,
+ .kmap = cma_kmap,
+ .kunmap = cma_kunmap,
+};
diff --git a/drivers/gpu/host1x/drm/cma.h b/drivers/gpu/host1x/drm/cma.h
new file mode 100644
index 0000000..f35cebd
--- /dev/null
+++ b/drivers/gpu/host1x/drm/cma.h
@@ -0,0 +1,35 @@
+/*
+ * Tegra host1x cma memory manager
+ *
+ * Copyright (c) 2012-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HOST1X_CMA_H
+#define __HOST1X_CMA_H
+
+#include <drm/drm.h>
+#include <drm/drmP.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "host1x_bo.h"
+
+struct tegra_drm_bo {
+ struct host1x_bo base;
+ struct drm_gem_cma_object cma_obj;
+};
+
+extern const struct host1x_bo_ops tegra_drm_bo_ops;
+
+#endif
--
1.7.9.5

2013-03-13 12:36:13

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 05/10] gpu: host1x: Add debug support

Add support for host1x debugging. Adds debugfs entries, and dumps
channel state to UART in case of stuck job.

Signed-off-by: Arto Merilainen <[email protected]>
Signed-off-by: Terje Bergstrom <[email protected]>
---
drivers/gpu/host1x/Makefile | 1 +
drivers/gpu/host1x/cdma.c | 4 +
drivers/gpu/host1x/debug.c | 210 +++++++++++++++++
drivers/gpu/host1x/debug.h | 51 +++++
drivers/gpu/host1x/dev.c | 3 +
drivers/gpu/host1x/dev.h | 42 ++++
drivers/gpu/host1x/hw/cdma_hw.c | 2 +
drivers/gpu/host1x/hw/channel_hw.c | 26 ++-
drivers/gpu/host1x/hw/debug_hw.c | 322 +++++++++++++++++++++++++++
drivers/gpu/host1x/hw/host1x01.c | 2 +
drivers/gpu/host1x/hw/hw_host1x01_channel.h | 18 ++
drivers/gpu/host1x/hw/hw_host1x01_sync.h | 115 ++++++++++
drivers/gpu/host1x/hw/hw_host1x01_uclass.h | 6 +
drivers/gpu/host1x/hw/syncpt_hw.c | 1 +
drivers/gpu/host1x/syncpt.c | 5 +
15 files changed, 807 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/host1x/debug.c
create mode 100644 drivers/gpu/host1x/debug.h
create mode 100644 drivers/gpu/host1x/hw/debug_hw.c

diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile
index 06a995b..49fd580 100644
--- a/drivers/gpu/host1x/Makefile
+++ b/drivers/gpu/host1x/Makefile
@@ -7,6 +7,7 @@ host1x-y = \
cdma.o \
channel.o \
job.o \
+ debug.o \
hw/host1x01.o

obj-$(CONFIG_TEGRA_HOST1X) += host1x.o
diff --git a/drivers/gpu/host1x/cdma.c b/drivers/gpu/host1x/cdma.c
index 33935de..de72172 100644
--- a/drivers/gpu/host1x/cdma.c
+++ b/drivers/gpu/host1x/cdma.c
@@ -439,6 +439,10 @@ void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2)
struct push_buffer *pb = &cdma->push_buffer;
u32 slots_free = cdma->slots_free;

+ if (host1x_debug_trace_cmdbuf)
+ trace_host1x_cdma_push(dev_name(cdma_to_channel(cdma)->dev),
+ op1, op2);
+
if (slots_free == 0) {
host1x_hw_cdma_flush(host1x, cdma);
slots_free = host1x_cdma_wait_locked(cdma,
diff --git a/drivers/gpu/host1x/debug.c b/drivers/gpu/host1x/debug.c
new file mode 100644
index 0000000..cb8aff9
--- /dev/null
+++ b/drivers/gpu/host1x/debug.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Author: Erik Gilling <[email protected]>
+ *
+ * Copyright (C) 2011-2013 NVIDIA Corporation
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that 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 <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+
+#include <linux/io.h>
+
+#include "dev.h"
+#include "debug.h"
+#include "channel.h"
+
+unsigned int host1x_debug_trace_cmdbuf;
+
+static pid_t host1x_debug_force_timeout_pid;
+static u32 host1x_debug_force_timeout_val;
+static u32 host1x_debug_force_timeout_channel;
+
+void host1x_debug_output(struct output *o, const char *fmt, ...)
+{
+ va_list args;
+ int len;
+
+ va_start(args, fmt);
+ len = vsnprintf(o->buf, sizeof(o->buf), fmt, args);
+ va_end(args);
+ o->fn(o->ctx, o->buf, len);
+}
+
+static int show_channels(struct host1x_channel *ch, void *data, bool show_fifo)
+{
+ struct host1x *m = host1x_get_host(ch->dev);
+ struct output *o = data;
+
+ mutex_lock(&ch->reflock);
+ if (ch->refcount) {
+ mutex_lock(&ch->cdma.lock);
+ if (show_fifo)
+ host1x_hw_show_channel_fifo(m, ch, o);
+ host1x_hw_show_channel_cdma(m, ch, o);
+ mutex_unlock(&ch->cdma.lock);
+ }
+ mutex_unlock(&ch->reflock);
+
+ return 0;
+}
+
+static void show_syncpts(struct host1x *m, struct output *o)
+{
+ int i;
+ host1x_debug_output(o, "---- syncpts ----\n");
+ for (i = 0; i < host1x_syncpt_nb_pts(m); i++) {
+ u32 max = host1x_syncpt_read_max(m->syncpt + i);
+ u32 min = host1x_syncpt_load(m->syncpt + i);
+ if (!min && !max)
+ continue;
+ host1x_debug_output(o, "id %d (%s) min %d max %d\n",
+ i, m->syncpt[i].name, min, max);
+ }
+
+ for (i = 0; i < host1x_syncpt_nb_bases(m); i++) {
+ u32 base_val;
+ base_val = host1x_syncpt_load_wait_base(m->syncpt + i);
+ if (base_val)
+ host1x_debug_output(o, "waitbase id %d val %d\n", i,
+ base_val);
+ }
+
+ host1x_debug_output(o, "\n");
+}
+
+static void show_all(struct host1x *m, struct output *o)
+{
+ struct host1x_channel *ch;
+
+ host1x_hw_show_mlocks(m, o);
+ show_syncpts(m, o);
+ host1x_debug_output(o, "---- channels ----\n");
+
+ host1x_for_each_channel(m, ch)
+ show_channels(ch, o, true);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static void show_all_no_fifo(struct host1x *host1x, struct output *o)
+{
+ struct host1x_channel *ch;
+
+ host1x_hw_show_mlocks(host1x, o);
+ show_syncpts(host1x, o);
+ host1x_debug_output(o, "---- channels ----\n");
+
+ host1x_for_each_channel(host1x, ch)
+ show_channels(ch, o, false);
+}
+
+static int host1x_debug_show_all(struct seq_file *s, void *unused)
+{
+ struct output o = {
+ .fn = write_to_seqfile,
+ .ctx = s
+ };
+ show_all(s->private, &o);
+ return 0;
+}
+
+static int host1x_debug_show(struct seq_file *s, void *unused)
+{
+ struct output o = {
+ .fn = write_to_seqfile,
+ .ctx = s
+ };
+ show_all_no_fifo(s->private, &o);
+ return 0;
+}
+
+static int host1x_debug_open_all(struct inode *inode, struct file *file)
+{
+ return single_open(file, host1x_debug_show_all, inode->i_private);
+}
+
+static const struct file_operations host1x_debug_all_fops = {
+ .open = host1x_debug_open_all,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int host1x_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, host1x_debug_show, inode->i_private);
+}
+
+static const struct file_operations host1x_debug_fops = {
+ .open = host1x_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+void host1x_debug_init(struct host1x *host1x)
+{
+ struct dentry *de = debugfs_create_dir("tegra-host1x", NULL);
+
+ if (!de)
+ return;
+
+ /* Store the created entry */
+ host1x->debugfs = de;
+
+ debugfs_create_file("status", S_IRUGO, de, host1x, &host1x_debug_fops);
+ debugfs_create_file("status_all", S_IRUGO, de, host1x,
+ &host1x_debug_all_fops);
+
+ debugfs_create_u32("trace_cmdbuf", S_IRUGO|S_IWUSR, de,
+ &host1x_debug_trace_cmdbuf);
+
+ host1x_hw_debug_init(host1x, de);
+
+ debugfs_create_u32("force_timeout_pid", S_IRUGO|S_IWUSR, de,
+ &host1x_debug_force_timeout_pid);
+ debugfs_create_u32("force_timeout_val", S_IRUGO|S_IWUSR, de,
+ &host1x_debug_force_timeout_val);
+ debugfs_create_u32("force_timeout_channel", S_IRUGO|S_IWUSR, de,
+ &host1x_debug_force_timeout_channel);
+}
+
+void host1x_debug_deinit(struct host1x *host1x)
+{
+ debugfs_remove_recursive(host1x->debugfs);
+}
+#else
+void host1x_debug_init(struct host1x *host1x)
+{
+}
+void host1x_debug_deinit(struct host1x *host1x)
+{
+}
+#endif
+
+void host1x_debug_dump(struct host1x *host1x)
+{
+ struct output o = {
+ .fn = write_to_printk
+ };
+ show_all(host1x, &o);
+}
+
+void host1x_debug_dump_syncpts(struct host1x *host1x)
+{
+ struct output o = {
+ .fn = write_to_printk
+ };
+ show_syncpts(host1x, &o);
+}
diff --git a/drivers/gpu/host1x/debug.h b/drivers/gpu/host1x/debug.h
new file mode 100644
index 0000000..4595b2e
--- /dev/null
+++ b/drivers/gpu/host1x/debug.h
@@ -0,0 +1,51 @@
+/*
+ * Tegra host1x Debug
+ *
+ * Copyright (c) 2011-2013 NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __HOST1X_DEBUG_H
+#define __HOST1X_DEBUG_H
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+struct host1x;
+
+struct output {
+ void (*fn)(void *ctx, const char *str, size_t len);
+ void *ctx;
+ char buf[256];
+};
+
+static inline void write_to_seqfile(void *ctx, const char *str, size_t len)
+{
+ seq_write((struct seq_file *)ctx, str, len);
+}
+
+static inline void write_to_printk(void *ctx, const char *str, size_t len)
+{
+ pr_info("%s", str);
+}
+
+void __printf(2, 3) host1x_debug_output(struct output *o, const char *fmt, ...);
+
+extern unsigned int host1x_debug_trace_cmdbuf;
+
+void host1x_debug_init(struct host1x *host1x);
+void host1x_debug_deinit(struct host1x *host1x);
+void host1x_debug_dump(struct host1x *host1x);
+void host1x_debug_dump_syncpts(struct host1x *host1x);
+
+#endif
diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index bedd37b..d7c6e3e 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -30,6 +30,7 @@
#include "dev.h"
#include "intr.h"
#include "channel.h"
+#include "debug.h"
#include "hw/host1x01.h"

void host1x_sync_writel(struct host1x *host1x, u32 v, u32 r)
@@ -151,6 +152,8 @@ static int host1x_probe(struct platform_device *pdev)
goto fail_deinit_syncpt;
}

+ host1x_debug_init(host);
+
return 0;

fail_deinit_syncpt:
diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h
index 8606322..4a56233 100644
--- a/drivers/gpu/host1x/dev.h
+++ b/drivers/gpu/host1x/dev.h
@@ -31,6 +31,8 @@ struct host1x_channel;
struct host1x_cdma;
struct host1x_job;
struct push_buffer;
+struct output;
+struct dentry;

struct host1x_channel_ops {
int (*init)(struct host1x_channel *channel, struct host1x *host,
@@ -54,6 +56,18 @@ struct host1x_pushbuffer_ops {
void (*init)(struct push_buffer *pb);
};

+struct host1x_debug_ops {
+ void (*debug_init)(struct dentry *de);
+ void (*show_channel_cdma)(struct host1x *host,
+ struct host1x_channel *ch,
+ struct output *o);
+ void (*show_channel_fifo)(struct host1x *host,
+ struct host1x_channel *ch,
+ struct output *o);
+ void (*show_mlocks)(struct host1x *host, struct output *output);
+
+};
+
struct host1x_syncpt_ops {
void (*restore)(struct host1x_syncpt *syncpt);
void (*restore_wait_base)(struct host1x_syncpt *syncpt);
@@ -100,6 +114,7 @@ struct host1x {
const struct host1x_channel_ops *channel_op;
const struct host1x_cdma_ops *cdma_op;
const struct host1x_pushbuffer_ops *cdma_pb_op;
+ const struct host1x_debug_ops *debug_op;

struct host1x_syncpt *nop_sp;

@@ -107,6 +122,8 @@ struct host1x {
struct host1x_channel chlist;
unsigned long allocated_channels;
unsigned int num_allocated_channels;
+
+ struct dentry *debugfs;
};

static inline struct host1x *host1x_get_host(struct device *dev)
@@ -265,4 +282,29 @@ static inline void host1x_hw_pushbuffer_init(struct host1x *host,
host->cdma_pb_op->init(pb);
}

+static inline void host1x_hw_debug_init(struct host1x *host, struct dentry *de)
+{
+ if (host->debug_op && host->debug_op->debug_init)
+ host->debug_op->debug_init(de);
+}
+
+static inline void host1x_hw_show_channel_cdma(struct host1x *host,
+ struct host1x_channel *channel,
+ struct output *o)
+{
+ host->debug_op->show_channel_cdma(host, channel, o);
+}
+
+static inline void host1x_hw_show_channel_fifo(struct host1x *host,
+ struct host1x_channel *channel,
+ struct output *o)
+{
+ host->debug_op->show_channel_fifo(host, channel, o);
+}
+
+static inline void host1x_hw_show_mlocks(struct host1x *host, struct output *o)
+{
+ host->debug_op->show_mlocks(host, o);
+}
+
#endif
diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c
index 4eb22ef..590b69d 100644
--- a/drivers/gpu/host1x/hw/cdma_hw.c
+++ b/drivers/gpu/host1x/hw/cdma_hw.c
@@ -244,6 +244,8 @@ static void cdma_timeout_handler(struct work_struct *work)
host1x = cdma_to_host1x(cdma);
ch = cdma_to_channel(cdma);

+ host1x_debug_dump(cdma_to_host1x(cdma));
+
mutex_lock(&cdma->lock);

if (!cdma->timeout.client) {
diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c
index bf85a8e..3dbde65 100644
--- a/drivers/gpu/host1x/hw/channel_hw.c
+++ b/drivers/gpu/host1x/hw/channel_hw.c
@@ -29,6 +29,30 @@
#define HOST1X_CHANNEL_SIZE 16384
#define TRACE_MAX_LENGTH 128U

+static void trace_write_gather(struct host1x_cdma *cdma, struct host1x_bo *bo,
+ u32 offset, u32 words)
+{
+ void *mem = NULL;
+
+ if (host1x_debug_trace_cmdbuf)
+ mem = host1x_bo_mmap(bo);
+
+ if (mem) {
+ u32 i;
+ /*
+ * Write in batches of 128 as there seems to be a limit
+ * of how much you can output to ftrace at once.
+ */
+ for (i = 0; i < words; i += TRACE_MAX_LENGTH) {
+ trace_host1x_cdma_push_gather(
+ dev_name(cdma_to_channel(cdma)->dev),
+ (u32)bo, min(words - i, TRACE_MAX_LENGTH),
+ offset + i * sizeof(u32), mem);
+ }
+ host1x_bo_munmap(bo, mem);
+ }
+}
+
static void submit_gathers(struct host1x_job *job)
{
struct host1x_cdma *cdma = &job->channel->cdma;
@@ -38,6 +62,7 @@ static void submit_gathers(struct host1x_job *job)
struct host1x_job_gather *g = &job->gathers[i];
u32 op1 = host1x_opcode_gather(g->words);
u32 op2 = g->mem_base + g->offset;
+ trace_write_gather(cdma, g->mem_id, g->offset, op1 & 0xffff);
host1x_cdma_push(cdma, op1, op2);
}
}
@@ -51,7 +76,6 @@ static int channel_submit(struct host1x_job *job)
u32 syncval;
int err;
struct host1x_waitlist *completed_waiter = NULL;
-
sp = host1x_get_host(job->channel->dev)->syncpt + job->syncpt_id;
trace_host1x_channel_submit(dev_name(ch->dev),
job->num_gathers, job->num_relocs,
diff --git a/drivers/gpu/host1x/hw/debug_hw.c b/drivers/gpu/host1x/hw/debug_hw.c
new file mode 100644
index 0000000..69594bf
--- /dev/null
+++ b/drivers/gpu/host1x/hw/debug_hw.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Author: Erik Gilling <[email protected]>
+ *
+ * Copyright (C) 2011-2013 NVIDIA Corporation
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that 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 <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/mm.h>
+#include <linux/scatterlist.h>
+
+#include <linux/io.h>
+
+#include "dev.h"
+#include "debug.h"
+#include "cdma.h"
+#include "channel.h"
+#include "host1x_bo.h"
+
+#define HOST1X_DEBUG_MAX_PAGE_OFFSET 102400
+
+enum {
+ HOST1X_OPCODE_SETCLASS = 0x00,
+ HOST1X_OPCODE_INCR = 0x01,
+ HOST1X_OPCODE_NONINCR = 0x02,
+ HOST1X_OPCODE_MASK = 0x03,
+ HOST1X_OPCODE_IMM = 0x04,
+ HOST1X_OPCODE_RESTART = 0x05,
+ HOST1X_OPCODE_GATHER = 0x06,
+ HOST1X_OPCODE_EXTEND = 0x0e,
+};
+
+enum {
+ HOST1X_OPCODE_EXTEND_ACQUIRE_MLOCK = 0x00,
+ HOST1X_OPCODE_EXTEND_RELEASE_MLOCK = 0x01,
+};
+
+static unsigned int show_channel_command(struct output *o, u32 val)
+{
+ unsigned mask;
+ unsigned subop;
+
+ switch (val >> 28) {
+ case HOST1X_OPCODE_SETCLASS:
+ mask = val & 0x3f;
+ if (mask) {
+ host1x_debug_output(o, "SETCL(class=%03x, offset=%03x, mask=%02x, [",
+ val >> 6 & 0x3ff,
+ val >> 16 & 0xfff, mask);
+ return hweight8(mask);
+ } else {
+ host1x_debug_output(o, "SETCL(class=%03x)\n",
+ val >> 6 & 0x3ff);
+ return 0;
+ }
+
+ case HOST1X_OPCODE_INCR:
+ host1x_debug_output(o, "INCR(offset=%03x, [",
+ val >> 16 & 0xfff);
+ return val & 0xffff;
+
+ case HOST1X_OPCODE_NONINCR:
+ host1x_debug_output(o, "NONINCR(offset=%03x, [",
+ val >> 16 & 0xfff);
+ return val & 0xffff;
+
+ case HOST1X_OPCODE_MASK:
+ mask = val & 0xffff;
+ host1x_debug_output(o, "MASK(offset=%03x, mask=%03x, [",
+ val >> 16 & 0xfff, mask);
+ return hweight16(mask);
+
+ case HOST1X_OPCODE_IMM:
+ host1x_debug_output(o, "IMM(offset=%03x, data=%03x)\n",
+ val >> 16 & 0xfff, val & 0xffff);
+ return 0;
+
+ case HOST1X_OPCODE_RESTART:
+ host1x_debug_output(o, "RESTART(offset=%08x)\n", val << 4);
+ return 0;
+
+ case HOST1X_OPCODE_GATHER:
+ host1x_debug_output(o, "GATHER(offset=%03x, insert=%d, type=%d, count=%04x, addr=[",
+ val >> 16 & 0xfff, val >> 15 & 0x1,
+ val >> 14 & 0x1, val & 0x3fff);
+ return 1;
+
+ case HOST1X_OPCODE_EXTEND:
+ subop = val >> 24 & 0xf;
+ if (subop == HOST1X_OPCODE_EXTEND_ACQUIRE_MLOCK)
+ host1x_debug_output(o, "ACQUIRE_MLOCK(index=%d)\n",
+ val & 0xff);
+ else if (subop == HOST1X_OPCODE_EXTEND_RELEASE_MLOCK)
+ host1x_debug_output(o, "RELEASE_MLOCK(index=%d)\n",
+ val & 0xff);
+ else
+ host1x_debug_output(o, "EXTEND_UNKNOWN(%08x)\n", val);
+ return 0;
+
+ default:
+ return 0;
+ }
+}
+
+static void show_gather(struct output *o, phys_addr_t phys_addr,
+ unsigned int words, struct host1x_cdma *cdma,
+ phys_addr_t pin_addr, u32 *map_addr)
+{
+ /* Map dmaget cursor to corresponding mem handle */
+ u32 offset = phys_addr - pin_addr;
+ unsigned int data_count = 0, i;
+
+ /*
+ * Sometimes we're given different hardware address to the same
+ * page - in these cases the offset will get an invalid number and
+ * we just have to bail out.
+ */
+ if (offset > HOST1X_DEBUG_MAX_PAGE_OFFSET) {
+ host1x_debug_output(o, "[address mismatch]\n");
+ return;
+ }
+
+ for (i = 0; i < words; i++) {
+ u32 addr = phys_addr + i * 4;
+ u32 val = *(map_addr + offset / 4 + i);
+
+ if (!data_count) {
+ host1x_debug_output(o, "%08x: %08x:", addr, val);
+ data_count = show_channel_command(o, val);
+ } else {
+ host1x_debug_output(o, "%08x%s", val,
+ data_count > 0 ? ", " : "])\n");
+ data_count--;
+ }
+ }
+}
+
+static void show_channel_gathers(struct output *o, struct host1x_cdma *cdma)
+{
+ struct host1x_job *job;
+
+ list_for_each_entry(job, &cdma->sync_queue, list) {
+ int i;
+ host1x_debug_output(o, "\n%p: JOB, syncpt_id=%d, syncpt_val=%d, first_get=%08x, timeout=%d num_slots=%d, num_handles=%d\n",
+ job, job->syncpt_id, job->syncpt_end,
+ job->first_get, job->timeout,
+ job->num_slots, job->num_unpins);
+
+ for (i = 0; i < job->num_gathers; i++) {
+ struct host1x_job_gather *g = &job->gathers[i];
+ u32 *mapped;
+
+ if (job->gather_copy_mapped)
+ mapped = (u32 *)job->gather_copy_mapped;
+ else
+ mapped = host1x_bo_mmap(g->mem_id);
+
+ if (!mapped) {
+ host1x_debug_output(o, "[could not mmap]\n");
+ continue;
+ }
+
+ host1x_debug_output(o, " GATHER at %08x+%04x, %d words\n",
+ g->mem_base, g->offset, g->words);
+
+ show_gather(o, g->mem_base + g->offset, g->words,
+ cdma, g->mem_base, mapped);
+
+ if (!job->gather_copy_mapped)
+ host1x_bo_munmap(g->mem_id, mapped);
+ }
+ }
+}
+
+static void host1x_debug_show_channel_cdma(struct host1x *host,
+ struct host1x_channel *ch,
+ struct output *o)
+{
+ struct host1x_cdma *cdma = &ch->cdma;
+ u32 dmaput, dmaget, dmactrl;
+ u32 cbstat, cbread;
+ u32 val, base, baseval;
+
+ dmaput = host1x_ch_readl(ch, HOST1X_CHANNEL_DMAPUT);
+ dmaget = host1x_ch_readl(ch, HOST1X_CHANNEL_DMAGET);
+ dmactrl = host1x_ch_readl(ch, HOST1X_CHANNEL_DMACTRL);
+ cbread = host1x_sync_readl(host, HOST1X_SYNC_CBREAD(ch->id));
+ cbstat = host1x_sync_readl(host, HOST1X_SYNC_CBSTAT(ch->id));
+
+ host1x_debug_output(o, "%d-%s: ", ch->id, dev_name(ch->dev));
+
+ if (HOST1X_CHANNEL_DMACTRL_DMASTOP_V(dmactrl) ||
+ !ch->cdma.push_buffer.mapped) {
+ host1x_debug_output(o, "inactive\n\n");
+ return;
+ }
+
+ if (HOST1X_SYNC_CBSTAT_CBCLASS_V(cbstat) == HOST1X_CLASS_HOST1X &&
+ HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat) ==
+ HOST1X_UCLASS_WAIT_SYNCPT)
+ host1x_debug_output(o, "waiting on syncpt %d val %d\n",
+ cbread >> 24, cbread & 0xffffff);
+ else if (HOST1X_SYNC_CBSTAT_CBCLASS_V(cbstat) ==
+ HOST1X_CLASS_HOST1X &&
+ HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat) ==
+ HOST1X_UCLASS_WAIT_SYNCPT_BASE) {
+
+ base = (cbread >> 16) & 0xff;
+ baseval =
+ host1x_sync_readl(host, HOST1X_SYNC_SYNCPT_BASE(base));
+ val = cbread & 0xffff;
+ host1x_debug_output(o, "waiting on syncpt %d val %d (base %d = %d; offset = %d)\n",
+ cbread >> 24, baseval + val, base,
+ baseval, val);
+ } else
+ host1x_debug_output(o, "active class %02x, offset %04x, val %08x\n",
+ HOST1X_SYNC_CBSTAT_CBCLASS_V(cbstat),
+ HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat),
+ cbread);
+
+ host1x_debug_output(o, "DMAPUT %08x, DMAGET %08x, DMACTL %08x\n",
+ dmaput, dmaget, dmactrl);
+ host1x_debug_output(o, "CBREAD %08x, CBSTAT %08x\n", cbread, cbstat);
+
+ show_channel_gathers(o, cdma);
+ host1x_debug_output(o, "\n");
+}
+
+static void host1x_debug_show_channel_fifo(struct host1x *host,
+ struct host1x_channel *ch,
+ struct output *o)
+{
+ u32 val, rd_ptr, wr_ptr, start, end;
+ unsigned int data_count = 0;
+
+ host1x_debug_output(o, "%d: fifo:\n", ch->id);
+
+ val = host1x_ch_readl(ch, HOST1X_CHANNEL_FIFOSTAT);
+ host1x_debug_output(o, "FIFOSTAT %08x\n", val);
+ if (HOST1X_CHANNEL_FIFOSTAT_CFEMPTY_V(val)) {
+ host1x_debug_output(o, "[empty]\n");
+ return;
+ }
+
+ host1x_sync_writel(host, 0x0, HOST1X_SYNC_CFPEEK_CTRL);
+ host1x_sync_writel(host, HOST1X_SYNC_CFPEEK_CTRL_ENA_F(1) |
+ HOST1X_SYNC_CFPEEK_CTRL_CHANNR_F(ch->id),
+ HOST1X_SYNC_CFPEEK_CTRL);
+
+ val = host1x_sync_readl(host, HOST1X_SYNC_CFPEEK_PTRS);
+ rd_ptr = HOST1X_SYNC_CFPEEK_PTRS_CF_RD_PTR_V(val);
+ wr_ptr = HOST1X_SYNC_CFPEEK_PTRS_CF_WR_PTR_V(val);
+
+ val = host1x_sync_readl(host, HOST1X_SYNC_CF_SETUP(ch->id));
+ start = HOST1X_SYNC_CF_SETUP_BASE_V(val);
+ end = HOST1X_SYNC_CF_SETUP_LIMIT_V(val);
+
+ do {
+ host1x_sync_writel(host, 0x0, HOST1X_SYNC_CFPEEK_CTRL);
+ host1x_sync_writel(host, HOST1X_SYNC_CFPEEK_CTRL_ENA_F(1) |
+ HOST1X_SYNC_CFPEEK_CTRL_CHANNR_F(ch->id) |
+ HOST1X_SYNC_CFPEEK_CTRL_ADDR_F(rd_ptr),
+ HOST1X_SYNC_CFPEEK_CTRL);
+ val = host1x_sync_readl(host, HOST1X_SYNC_CFPEEK_READ);
+
+ if (!data_count) {
+ host1x_debug_output(o, "%08x:", val);
+ data_count = show_channel_command(o, val);
+ } else {
+ host1x_debug_output(o, "%08x%s", val,
+ data_count > 0 ? ", " : "])\n");
+ data_count--;
+ }
+
+ if (rd_ptr == end)
+ rd_ptr = start;
+ else
+ rd_ptr++;
+ } while (rd_ptr != wr_ptr);
+
+ if (data_count)
+ host1x_debug_output(o, ", ...])\n");
+ host1x_debug_output(o, "\n");
+
+ host1x_sync_writel(host, 0x0, HOST1X_SYNC_CFPEEK_CTRL);
+}
+
+static void host1x_debug_show_mlocks(struct host1x *host, struct output *o)
+{
+ int i;
+
+ host1x_debug_output(o, "---- mlocks ----\n");
+ for (i = 0; i < host1x_syncpt_nb_mlocks(host); i++) {
+ u32 owner =
+ host1x_sync_readl(host, HOST1X_SYNC_MLOCK_OWNER(i));
+ if (HOST1X_SYNC_MLOCK_OWNER_CH_OWNS_V(owner))
+ host1x_debug_output(o, "%d: locked by channel %d\n",
+ i, HOST1X_SYNC_MLOCK_OWNER_CHID_F(owner));
+ else if (HOST1X_SYNC_MLOCK_OWNER_CPU_OWNS_V(owner))
+ host1x_debug_output(o, "%d: locked by cpu\n", i);
+ else
+ host1x_debug_output(o, "%d: unlocked\n", i);
+ }
+ host1x_debug_output(o, "\n");
+}
+
+static const struct host1x_debug_ops host1x_debug_ops = {
+ .show_channel_cdma = host1x_debug_show_channel_cdma,
+ .show_channel_fifo = host1x_debug_show_channel_fifo,
+ .show_mlocks = host1x_debug_show_mlocks,
+};
diff --git a/drivers/gpu/host1x/hw/host1x01.c b/drivers/gpu/host1x/hw/host1x01.c
index 013ff38..a14e91c 100644
--- a/drivers/gpu/host1x/hw/host1x01.c
+++ b/drivers/gpu/host1x/hw/host1x01.c
@@ -23,6 +23,7 @@
/* include code */
#include "hw/cdma_hw.c"
#include "hw/channel_hw.c"
+#include "hw/debug_hw.c"
#include "hw/intr_hw.c"
#include "hw/syncpt_hw.c"

@@ -35,6 +36,7 @@ int host1x01_init(struct host1x *host)
host->cdma_pb_op = &host1x_pushbuffer_ops;
host->syncpt_op = &host1x_syncpt_ops;
host->intr_op = &host1x_intr_ops;
+ host->debug_op = &host1x_debug_ops;

return 0;
}
diff --git a/drivers/gpu/host1x/hw/hw_host1x01_channel.h b/drivers/gpu/host1x/hw/hw_host1x01_channel.h
index 9ba1332..b4bc7ca 100644
--- a/drivers/gpu/host1x/hw/hw_host1x01_channel.h
+++ b/drivers/gpu/host1x/hw/hw_host1x01_channel.h
@@ -51,6 +51,18 @@
#ifndef __hw_host1x_channel_host1x_h__
#define __hw_host1x_channel_host1x_h__

+static inline u32 host1x_channel_fifostat_r(void)
+{
+ return 0x0;
+}
+#define HOST1X_CHANNEL_FIFOSTAT \
+ host1x_channel_fifostat_r()
+static inline u32 host1x_channel_fifostat_cfempty_v(u32 r)
+{
+ return (r >> 10) & 0x1;
+}
+#define HOST1X_CHANNEL_FIFOSTAT_CFEMPTY_V(r) \
+ host1x_channel_fifostat_cfempty_v(r)
static inline u32 host1x_channel_dmastart_r(void)
{
return 0x14;
@@ -87,6 +99,12 @@ static inline u32 host1x_channel_dmactrl_dmastop(void)
}
#define HOST1X_CHANNEL_DMACTRL_DMASTOP \
host1x_channel_dmactrl_dmastop()
+static inline u32 host1x_channel_dmactrl_dmastop_v(u32 r)
+{
+ return (r >> 0) & 0x1;
+}
+#define HOST1X_CHANNEL_DMACTRL_DMASTOP_V(r) \
+ host1x_channel_dmactrl_dmastop_v(r)
static inline u32 host1x_channel_dmactrl_dmagetrst(void)
{
return 1 << 1;
diff --git a/drivers/gpu/host1x/hw/hw_host1x01_sync.h b/drivers/gpu/host1x/hw/hw_host1x01_sync.h
index 8f2a246..ac704e5 100644
--- a/drivers/gpu/host1x/hw/hw_host1x01_sync.h
+++ b/drivers/gpu/host1x/hw/hw_host1x01_sync.h
@@ -77,6 +77,24 @@ static inline u32 host1x_sync_syncpt_thresh_int_enable_cpu0_r(unsigned int id)
}
#define HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(id) \
host1x_sync_syncpt_thresh_int_enable_cpu0_r(id)
+static inline u32 host1x_sync_cf_setup_r(unsigned int channel)
+{
+ return 0x80 + channel * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_CF_SETUP(channel) \
+ host1x_sync_cf_setup_r(channel)
+static inline u32 host1x_sync_cf_setup_base_v(u32 r)
+{
+ return (r >> 0) & 0x1ff;
+}
+#define HOST1X_SYNC_CF_SETUP_BASE_V(r) \
+ host1x_sync_cf_setup_base_v(r)
+static inline u32 host1x_sync_cf_setup_limit_v(u32 r)
+{
+ return (r >> 16) & 0x1ff;
+}
+#define HOST1X_SYNC_CF_SETUP_LIMIT_V(r) \
+ host1x_sync_cf_setup_limit_v(r)
static inline u32 host1x_sync_cmdproc_stop_r(void)
{
return 0xac;
@@ -107,6 +125,30 @@ static inline u32 host1x_sync_ip_busy_timeout_r(void)
}
#define HOST1X_SYNC_IP_BUSY_TIMEOUT \
host1x_sync_ip_busy_timeout_r()
+static inline u32 host1x_sync_mlock_owner_r(unsigned int id)
+{
+ return 0x340 + id * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_MLOCK_OWNER(id) \
+ host1x_sync_mlock_owner_r(id)
+static inline u32 host1x_sync_mlock_owner_chid_f(u32 v)
+{
+ return (v & 0xf) << 8;
+}
+#define HOST1X_SYNC_MLOCK_OWNER_CHID_F(v) \
+ host1x_sync_mlock_owner_chid_f(v)
+static inline u32 host1x_sync_mlock_owner_cpu_owns_v(u32 r)
+{
+ return (r >> 1) & 0x1;
+}
+#define HOST1X_SYNC_MLOCK_OWNER_CPU_OWNS_V(r) \
+ host1x_sync_mlock_owner_cpu_owns_v(r)
+static inline u32 host1x_sync_mlock_owner_ch_owns_v(u32 r)
+{
+ return (r >> 0) & 0x1;
+}
+#define HOST1X_SYNC_MLOCK_OWNER_CH_OWNS_V(r) \
+ host1x_sync_mlock_owner_ch_owns_v(r)
static inline u32 host1x_sync_syncpt_int_thresh_r(unsigned int id)
{
return 0x500 + id * REGISTER_STRIDE;
@@ -125,4 +167,77 @@ static inline u32 host1x_sync_syncpt_cpu_incr_r(unsigned int id)
}
#define HOST1X_SYNC_SYNCPT_CPU_INCR(id) \
host1x_sync_syncpt_cpu_incr_r(id)
+static inline u32 host1x_sync_cbread_r(unsigned int channel)
+{
+ return 0x720 + channel * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_CBREAD(channel) \
+ host1x_sync_cbread_r(channel)
+static inline u32 host1x_sync_cfpeek_ctrl_r(void)
+{
+ return 0x74c;
+}
+#define HOST1X_SYNC_CFPEEK_CTRL \
+ host1x_sync_cfpeek_ctrl_r()
+static inline u32 host1x_sync_cfpeek_ctrl_addr_f(u32 v)
+{
+ return (v & 0x1ff) << 0;
+}
+#define HOST1X_SYNC_CFPEEK_CTRL_ADDR_F(v) \
+ host1x_sync_cfpeek_ctrl_addr_f(v)
+static inline u32 host1x_sync_cfpeek_ctrl_channr_f(u32 v)
+{
+ return (v & 0x7) << 16;
+}
+#define HOST1X_SYNC_CFPEEK_CTRL_CHANNR_F(v) \
+ host1x_sync_cfpeek_ctrl_channr_f(v)
+static inline u32 host1x_sync_cfpeek_ctrl_ena_f(u32 v)
+{
+ return (v & 0x1) << 31;
+}
+#define HOST1X_SYNC_CFPEEK_CTRL_ENA_F(v) \
+ host1x_sync_cfpeek_ctrl_ena_f(v)
+static inline u32 host1x_sync_cfpeek_read_r(void)
+{
+ return 0x750;
+}
+#define HOST1X_SYNC_CFPEEK_READ \
+ host1x_sync_cfpeek_read_r()
+static inline u32 host1x_sync_cfpeek_ptrs_r(void)
+{
+ return 0x754;
+}
+#define HOST1X_SYNC_CFPEEK_PTRS \
+ host1x_sync_cfpeek_ptrs_r()
+static inline u32 host1x_sync_cfpeek_ptrs_cf_rd_ptr_v(u32 r)
+{
+ return (r >> 0) & 0x1ff;
+}
+#define HOST1X_SYNC_CFPEEK_PTRS_CF_RD_PTR_V(r) \
+ host1x_sync_cfpeek_ptrs_cf_rd_ptr_v(r)
+static inline u32 host1x_sync_cfpeek_ptrs_cf_wr_ptr_v(u32 r)
+{
+ return (r >> 16) & 0x1ff;
+}
+#define HOST1X_SYNC_CFPEEK_PTRS_CF_WR_PTR_V(r) \
+ host1x_sync_cfpeek_ptrs_cf_wr_ptr_v(r)
+static inline u32 host1x_sync_cbstat_r(unsigned int channel)
+{
+ return 0x758 + channel * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_CBSTAT(channel) \
+ host1x_sync_cbstat_r(channel)
+static inline u32 host1x_sync_cbstat_cboffset_v(u32 r)
+{
+ return (r >> 0) & 0xffff;
+}
+#define HOST1X_SYNC_CBSTAT_CBOFFSET_V(r) \
+ host1x_sync_cbstat_cboffset_v(r)
+static inline u32 host1x_sync_cbstat_cbclass_v(u32 r)
+{
+ return (r >> 16) & 0x3ff;
+}
+#define HOST1X_SYNC_CBSTAT_CBCLASS_V(r) \
+ host1x_sync_cbstat_cbclass_v(r)
+
#endif /* __hw_host1x01_sync_h__ */
diff --git a/drivers/gpu/host1x/hw/hw_host1x01_uclass.h b/drivers/gpu/host1x/hw/hw_host1x01_uclass.h
index 7af6609..42f3ce1 100644
--- a/drivers/gpu/host1x/hw/hw_host1x01_uclass.h
+++ b/drivers/gpu/host1x/hw/hw_host1x01_uclass.h
@@ -87,6 +87,12 @@ static inline u32 host1x_uclass_wait_syncpt_thresh_f(u32 v)
}
#define HOST1X_UCLASS_WAIT_SYNCPT_THRESH_F(v) \
host1x_uclass_wait_syncpt_thresh_f(v)
+static inline u32 host1x_uclass_wait_syncpt_base_r(void)
+{
+ return 0x9;
+}
+#define HOST1X_UCLASS_WAIT_SYNCPT_BASE \
+ host1x_uclass_wait_syncpt_base_r()
static inline u32 host1x_uclass_wait_syncpt_base_indx_f(u32 v)
{
return (v & 0xff) << 24;
diff --git a/drivers/gpu/host1x/hw/syncpt_hw.c b/drivers/gpu/host1x/hw/syncpt_hw.c
index 2c1f4af..6117499 100644
--- a/drivers/gpu/host1x/hw/syncpt_hw.c
+++ b/drivers/gpu/host1x/hw/syncpt_hw.c
@@ -86,6 +86,7 @@ static void syncpt_cpu_incr(struct host1x_syncpt *sp)
host1x_syncpt_idle(sp)) {
dev_err(host->dev, "Trying to increment syncpoint id %d beyond max\n",
sp->id);
+ host1x_debug_dump(sp->host);
return;
}
host1x_sync_writel(host, BIT_MASK(sp->id),
diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c
index ac172dc..7390af8 100644
--- a/drivers/gpu/host1x/syncpt.c
+++ b/drivers/gpu/host1x/syncpt.c
@@ -25,6 +25,7 @@
#include "syncpt.h"
#include "dev.h"
#include "intr.h"
+#include "debug.h"

#define SYNCPT_CHECK_PERIOD (2 * HZ)
#define MAX_STUCK_CHECK_COUNT 15
@@ -231,6 +232,10 @@ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout,
"%s: syncpoint id %d (%s) stuck waiting %d, timeout=%ld\n",
current->comm, sp->id, sp->name,
thresh, timeout);
+
+ host1x_debug_dump_syncpts(sp->host);
+ if (check_count == MAX_STUCK_CHECK_COUNT)
+ host1x_debug_dump(sp->host);
check_count++;
}
}
--
1.7.9.5

2013-03-13 12:36:11

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 04/10] gpu: host1x: Add channel support

Add support for host1x client modules, and host1x channels to submit
work to the clients.

Signed-off-by: Arto Merilainen <[email protected]>
Signed-off-by: Terje Bergstrom <[email protected]>
---
drivers/gpu/host1x/Kconfig | 12 +
drivers/gpu/host1x/Makefile | 3 +
drivers/gpu/host1x/cdma.c | 487 ++++++++++++++++++++++
drivers/gpu/host1x/cdma.h | 100 +++++
drivers/gpu/host1x/channel.c | 120 ++++++
drivers/gpu/host1x/channel.h | 52 +++
drivers/gpu/host1x/dev.c | 17 +
drivers/gpu/host1x/dev.h | 113 +++++
drivers/gpu/host1x/host1x.h | 28 ++
drivers/gpu/host1x/host1x_bo.h | 92 +++++
drivers/gpu/host1x/hw/cdma_hw.c | 324 +++++++++++++++
drivers/gpu/host1x/hw/channel_hw.c | 143 +++++++
drivers/gpu/host1x/hw/host1x01.c | 5 +
drivers/gpu/host1x/hw/host1x01_hardware.h | 116 ++++++
drivers/gpu/host1x/hw/hw_host1x01_channel.h | 102 +++++
drivers/gpu/host1x/hw/hw_host1x01_sync.h | 12 +
drivers/gpu/host1x/hw/hw_host1x01_uclass.h | 168 ++++++++
drivers/gpu/host1x/hw/syncpt_hw.c | 11 +
drivers/gpu/host1x/intr.c | 28 +-
drivers/gpu/host1x/intr.h | 6 +
drivers/gpu/host1x/job.c | 597 +++++++++++++++++++++++++++
drivers/gpu/host1x/job.h | 157 +++++++
drivers/gpu/host1x/syncpt.c | 11 +
drivers/gpu/host1x/syncpt.h | 6 +
include/trace/events/host1x.h | 211 ++++++++++
25 files changed, 2920 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/host1x/cdma.c
create mode 100644 drivers/gpu/host1x/cdma.h
create mode 100644 drivers/gpu/host1x/channel.c
create mode 100644 drivers/gpu/host1x/channel.h
create mode 100644 drivers/gpu/host1x/host1x.h
create mode 100644 drivers/gpu/host1x/host1x_bo.h
create mode 100644 drivers/gpu/host1x/hw/cdma_hw.c
create mode 100644 drivers/gpu/host1x/hw/channel_hw.c
create mode 100644 drivers/gpu/host1x/hw/hw_host1x01_channel.h
create mode 100644 drivers/gpu/host1x/hw/hw_host1x01_uclass.h
create mode 100644 drivers/gpu/host1x/job.c
create mode 100644 drivers/gpu/host1x/job.h

diff --git a/drivers/gpu/host1x/Kconfig b/drivers/gpu/host1x/Kconfig
index c01c450..00f0859 100644
--- a/drivers/gpu/host1x/Kconfig
+++ b/drivers/gpu/host1x/Kconfig
@@ -7,3 +7,15 @@ config TEGRA_HOST1X
Tegra's graphics- and multimedia-related modules. The modules served
by host1x are referred to as clients. host1x includes some other
functionality, such as synchronization.
+
+if TEGRA_HOST1X
+
+config TEGRA_HOST1X_FIREWALL
+ bool "Enable HOST1X security firewall"
+ default y
+ help
+ Say yes if kernel should protect command streams from tampering.
+
+ If unsure, choose Y.
+
+endif
diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile
index 5ef47ff..06a995b 100644
--- a/drivers/gpu/host1x/Makefile
+++ b/drivers/gpu/host1x/Makefile
@@ -4,6 +4,9 @@ host1x-y = \
syncpt.o \
dev.o \
intr.o \
+ cdma.o \
+ channel.o \
+ job.o \
hw/host1x01.o

obj-$(CONFIG_TEGRA_HOST1X) += host1x.o
diff --git a/drivers/gpu/host1x/cdma.c b/drivers/gpu/host1x/cdma.c
new file mode 100644
index 0000000..33935de
--- /dev/null
+++ b/drivers/gpu/host1x/cdma.c
@@ -0,0 +1,487 @@
+/*
+ * Tegra host1x Command DMA
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <asm/cacheflush.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kfifo.h>
+#include <linux/slab.h>
+#include <trace/events/host1x.h>
+
+#include "cdma.h"
+#include "channel.h"
+#include "dev.h"
+#include "debug.h"
+#include "host1x_bo.h"
+#include "job.h"
+
+/*
+ * push_buffer
+ *
+ * The push buffer is a circular array of words to be fetched by command DMA.
+ * Note that it works slightly differently to the sync queue; fence == pos
+ * means that the push buffer is full, not empty.
+ */
+
+#define HOST1X_PUSHBUFFER_SLOTS 512
+
+/*
+ * Clean up push buffer resources
+ */
+static void host1x_pushbuffer_destroy(struct push_buffer *pb)
+{
+ struct host1x_cdma *cdma = pb_to_cdma(pb);
+ struct host1x *host1x = cdma_to_host1x(cdma);
+
+ if (pb->phys != 0)
+ dma_free_writecombine(host1x->dev, pb->size_bytes + 4,
+ pb->mapped, pb->phys);
+
+ pb->mapped = NULL;
+ pb->phys = 0;
+}
+
+/*
+ * Init push buffer resources
+ */
+static int host1x_pushbuffer_init(struct push_buffer *pb)
+{
+ struct host1x_cdma *cdma = pb_to_cdma(pb);
+ struct host1x *host1x = cdma_to_host1x(cdma);
+
+ pb->mapped = NULL;
+ pb->phys = 0;
+ pb->size_bytes = HOST1X_PUSHBUFFER_SLOTS * 8;
+
+ /* initialize buffer pointers */
+ pb->fence = pb->size_bytes - 8;
+ pb->pos = 0;
+
+ /* allocate and map pushbuffer memory */
+ pb->mapped = dma_alloc_writecombine(host1x->dev, pb->size_bytes + 4,
+ &pb->phys, GFP_KERNEL);
+ if (!pb->mapped)
+ goto fail;
+
+ host1x_hw_pushbuffer_init(host1x, pb);
+
+ return 0;
+
+fail:
+ host1x_pushbuffer_destroy(pb);
+ return -ENOMEM;
+}
+
+/*
+ * Push two words to the push buffer
+ * Caller must ensure push buffer is not full
+ */
+static void host1x_pushbuffer_push(struct push_buffer *pb, u32 op1, u32 op2)
+{
+ u32 pos = pb->pos;
+ u32 *p = (u32 *)((u32)pb->mapped + pos);
+ WARN_ON(pos == pb->fence);
+ *(p++) = op1;
+ *(p++) = op2;
+ pb->pos = (pos + 8) & (pb->size_bytes - 1);
+}
+
+/*
+ * Pop a number of two word slots from the push buffer
+ * Caller must ensure push buffer is not empty
+ */
+static void host1x_pushbuffer_pop(struct push_buffer *pb, unsigned int slots)
+{
+ /* Advance the next write position */
+ pb->fence = (pb->fence + slots * 8) & (pb->size_bytes - 1);
+}
+
+/*
+ * Return the number of two word slots free in the push buffer
+ */
+static u32 host1x_pushbuffer_space(struct push_buffer *pb)
+{
+ return ((pb->fence - pb->pos) & (pb->size_bytes - 1)) / 8;
+}
+
+/*
+ * Sleep (if necessary) until the requested event happens
+ * - CDMA_EVENT_SYNC_QUEUE_EMPTY : sync queue is completely empty.
+ * - Returns 1
+ * - CDMA_EVENT_PUSH_BUFFER_SPACE : there is space in the push buffer
+ * - Return the amount of space (> 0)
+ * Must be called with the cdma lock held.
+ */
+unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma,
+ enum cdma_event event)
+{
+ for (;;) {
+ unsigned int space;
+
+ if (event == CDMA_EVENT_SYNC_QUEUE_EMPTY)
+ space = list_empty(&cdma->sync_queue) ? 1 : 0;
+ else if (event == CDMA_EVENT_PUSH_BUFFER_SPACE) {
+ struct push_buffer *pb = &cdma->push_buffer;
+ space = host1x_pushbuffer_space(pb);
+ } else {
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ if (space)
+ return space;
+
+ trace_host1x_wait_cdma(dev_name(cdma_to_channel(cdma)->dev),
+ event);
+
+ /* If somebody has managed to already start waiting, yield */
+ if (cdma->event != CDMA_EVENT_NONE) {
+ mutex_unlock(&cdma->lock);
+ schedule();
+ mutex_lock(&cdma->lock);
+ continue;
+ }
+ cdma->event = event;
+
+ mutex_unlock(&cdma->lock);
+ down(&cdma->sem);
+ mutex_lock(&cdma->lock);
+ }
+ return 0;
+}
+
+/*
+ * Start timer that tracks the time spent by the job.
+ * Must be called with the cdma lock held.
+ */
+static void cdma_start_timer_locked(struct host1x_cdma *cdma,
+ struct host1x_job *job)
+{
+ struct host1x *host = cdma_to_host1x(cdma);
+
+ if (cdma->timeout.client) {
+ /* timer already started */
+ return;
+ }
+
+ cdma->timeout.client = job->client;
+ cdma->timeout.syncpt = host1x_syncpt_get(host, job->syncpt_id);
+ cdma->timeout.syncpt_val = job->syncpt_end;
+ cdma->timeout.start_ktime = ktime_get();
+
+ schedule_delayed_work(&cdma->timeout.wq,
+ msecs_to_jiffies(job->timeout));
+}
+
+/*
+ * Stop timer when a buffer submission completes.
+ * Must be called with the cdma lock held.
+ */
+static void stop_cdma_timer_locked(struct host1x_cdma *cdma)
+{
+ cancel_delayed_work(&cdma->timeout.wq);
+ cdma->timeout.client = 0;
+}
+
+/*
+ * For all sync queue entries that have already finished according to the
+ * current sync point registers:
+ * - unpin & unref their mems
+ * - pop their push buffer slots
+ * - remove them from the sync queue
+ * This is normally called from the host code's worker thread, but can be
+ * called manually if necessary.
+ * Must be called with the cdma lock held.
+ */
+static void update_cdma_locked(struct host1x_cdma *cdma)
+{
+ bool signal = false;
+ struct host1x *host1x = cdma_to_host1x(cdma);
+ struct host1x_job *job, *n;
+
+ /* If CDMA is stopped, queue is cleared and we can return */
+ if (!cdma->running)
+ return;
+
+ /*
+ * Walk the sync queue, reading the sync point registers as necessary,
+ * to consume as many sync queue entries as possible without blocking
+ */
+ list_for_each_entry_safe(job, n, &cdma->sync_queue, list) {
+ struct host1x_syncpt *sp =
+ host1x_syncpt_get(host1x, job->syncpt_id);
+
+ /* Check whether this syncpt has completed, and bail if not */
+ if (!host1x_syncpt_is_expired(sp, job->syncpt_end)) {
+ /* Start timer on next pending syncpt */
+ if (job->timeout)
+ cdma_start_timer_locked(cdma, job);
+ break;
+ }
+
+ /* Cancel timeout, when a buffer completes */
+ if (cdma->timeout.client)
+ stop_cdma_timer_locked(cdma);
+
+ /* Unpin the memory */
+ host1x_job_unpin(job);
+
+ /* Pop push buffer slots */
+ if (job->num_slots) {
+ struct push_buffer *pb = &cdma->push_buffer;
+ host1x_pushbuffer_pop(pb, job->num_slots);
+ if (cdma->event == CDMA_EVENT_PUSH_BUFFER_SPACE)
+ signal = true;
+ }
+
+ list_del(&job->list);
+ host1x_job_put(job);
+ }
+
+ if (cdma->event == CDMA_EVENT_SYNC_QUEUE_EMPTY &&
+ list_empty(&cdma->sync_queue))
+ signal = true;
+
+ if (signal) {
+ cdma->event = CDMA_EVENT_NONE;
+ up(&cdma->sem);
+ }
+}
+
+void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma,
+ struct device *dev)
+{
+ u32 restart_addr;
+ u32 syncpt_incrs;
+ struct host1x_job *job = NULL;
+ u32 syncpt_val;
+ struct host1x *host1x = cdma_to_host1x(cdma);
+
+ syncpt_val = host1x_syncpt_load(cdma->timeout.syncpt);
+
+ dev_dbg(dev, "%s: starting cleanup (thresh %d)\n",
+ __func__, syncpt_val);
+
+ /*
+ * Move the sync_queue read pointer to the first entry that hasn't
+ * completed based on the current HW syncpt value. It's likely there
+ * won't be any (i.e. we're still at the head), but covers the case
+ * where a syncpt incr happens just prior/during the teardown.
+ */
+
+ dev_dbg(dev, "%s: skip completed buffers still in sync_queue\n",
+ __func__);
+
+ list_for_each_entry(job, &cdma->sync_queue, list) {
+ if (syncpt_val < job->syncpt_end)
+ break;
+
+ host1x_job_dump(dev, job);
+ }
+
+ /*
+ * Walk the sync_queue, first incrementing with the CPU syncpts that
+ * are partially executed (the first buffer) or fully skipped while
+ * still in the current context (slots are also NOP-ed).
+ *
+ * At the point contexts are interleaved, syncpt increments must be
+ * done inline with the pushbuffer from a GATHER buffer to maintain
+ * the order (slots are modified to be a GATHER of syncpt incrs).
+ *
+ * Note: save in restart_addr the location where the timed out buffer
+ * started in the PB, so we can start the refetch from there (with the
+ * modified NOP-ed PB slots). This lets things appear to have completed
+ * properly for this buffer and resources are freed.
+ */
+
+ dev_dbg(dev, "%s: perform CPU incr on pending same ctx buffers\n",
+ __func__);
+
+ if (!list_empty(&cdma->sync_queue))
+ restart_addr = job->first_get;
+ else
+ restart_addr = cdma->last_pos;
+
+ /* do CPU increments as long as this context continues */
+ list_for_each_entry_from(job, &cdma->sync_queue, list) {
+ /* different context, gets us out of this loop */
+ if (job->client != cdma->timeout.client)
+ break;
+
+ /* won't need a timeout when replayed */
+ job->timeout = 0;
+
+ syncpt_incrs = job->syncpt_end - syncpt_val;
+ dev_dbg(dev, "%s: CPU incr (%d)\n", __func__, syncpt_incrs);
+
+ host1x_job_dump(dev, job);
+
+ /* safe to use CPU to incr syncpts */
+ host1x_hw_cdma_timeout_cpu_incr(host1x, cdma, job->first_get,
+ syncpt_incrs, job->syncpt_end,
+ job->num_slots);
+
+ syncpt_val += syncpt_incrs;
+ }
+
+ /* The following sumbits from the same client may be dependent on the
+ * failed submit and therefore they may fail. Force a small timeout
+ * to make the queue cleanup faster */
+
+ list_for_each_entry_from(job, &cdma->sync_queue, list)
+ if (job->client == cdma->timeout.client)
+ job->timeout = min_t(unsigned int, job->timeout, 500);
+
+ dev_dbg(dev, "%s: finished sync_queue modification\n", __func__);
+
+ /* roll back DMAGET and start up channel again */
+ host1x_hw_cdma_resume(host1x, cdma, restart_addr);
+}
+
+/*
+ * Create a cdma
+ */
+int host1x_cdma_init(struct host1x_cdma *cdma)
+{
+ int err;
+
+ mutex_init(&cdma->lock);
+ sema_init(&cdma->sem, 0);
+
+ INIT_LIST_HEAD(&cdma->sync_queue);
+
+ cdma->event = CDMA_EVENT_NONE;
+ cdma->running = false;
+ cdma->torndown = false;
+
+ err = host1x_pushbuffer_init(&cdma->push_buffer);
+ if (err)
+ return err;
+ return 0;
+}
+
+/*
+ * Destroy a cdma
+ */
+int host1x_cdma_deinit(struct host1x_cdma *cdma)
+{
+ struct push_buffer *pb = &cdma->push_buffer;
+ struct host1x *host1x = cdma_to_host1x(cdma);
+
+ if (cdma->running) {
+ pr_warn("%s: CDMA still running\n", __func__);
+ return -EBUSY;
+ }
+
+ host1x_pushbuffer_destroy(pb);
+ host1x_hw_cdma_timeout_destroy(host1x, cdma);
+
+ return 0;
+}
+
+/*
+ * Begin a cdma submit
+ */
+int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job)
+{
+ struct host1x *host1x = cdma_to_host1x(cdma);
+
+ mutex_lock(&cdma->lock);
+
+ if (job->timeout) {
+ /* init state on first submit with timeout value */
+ if (!cdma->timeout.initialized) {
+ int err;
+ err = host1x_hw_cdma_timeout_init(host1x, cdma,
+ job->syncpt_id);
+ if (err) {
+ mutex_unlock(&cdma->lock);
+ return err;
+ }
+ }
+ }
+ if (!cdma->running)
+ host1x_hw_cdma_start(host1x, cdma);
+
+ cdma->slots_free = 0;
+ cdma->slots_used = 0;
+ cdma->first_get = cdma->push_buffer.pos;
+
+ trace_host1x_cdma_begin(dev_name(job->channel->dev));
+ return 0;
+}
+
+/*
+ * Push two words into a push buffer slot
+ * Blocks as necessary if the push buffer is full.
+ */
+void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2)
+{
+ struct host1x *host1x = cdma_to_host1x(cdma);
+ struct push_buffer *pb = &cdma->push_buffer;
+ u32 slots_free = cdma->slots_free;
+
+ if (slots_free == 0) {
+ host1x_hw_cdma_flush(host1x, cdma);
+ slots_free = host1x_cdma_wait_locked(cdma,
+ CDMA_EVENT_PUSH_BUFFER_SPACE);
+ }
+ cdma->slots_free = slots_free - 1;
+ cdma->slots_used++;
+ host1x_pushbuffer_push(pb, op1, op2);
+}
+
+/*
+ * End a cdma submit
+ * Kick off DMA, add job to the sync queue, and a number of slots to be freed
+ * from the pushbuffer. The handles for a submit must all be pinned at the same
+ * time, but they can be unpinned in smaller chunks.
+ */
+void host1x_cdma_end(struct host1x_cdma *cdma,
+ struct host1x_job *job)
+{
+ struct host1x *host1x = cdma_to_host1x(cdma);
+ bool idle = list_empty(&cdma->sync_queue);
+
+ host1x_hw_cdma_flush(host1x, cdma);
+
+ job->first_get = cdma->first_get;
+ job->num_slots = cdma->slots_used;
+ host1x_job_get(job);
+ list_add_tail(&job->list, &cdma->sync_queue);
+
+ /* start timer on idle -> active transitions */
+ if (job->timeout && idle)
+ cdma_start_timer_locked(cdma, job);
+
+ trace_host1x_cdma_end(dev_name(job->channel->dev));
+ mutex_unlock(&cdma->lock);
+}
+
+/*
+ * Update cdma state according to current sync point values
+ */
+void host1x_cdma_update(struct host1x_cdma *cdma)
+{
+ mutex_lock(&cdma->lock);
+ update_cdma_locked(cdma);
+ mutex_unlock(&cdma->lock);
+}
diff --git a/drivers/gpu/host1x/cdma.h b/drivers/gpu/host1x/cdma.h
new file mode 100644
index 0000000..4b4b191
--- /dev/null
+++ b/drivers/gpu/host1x/cdma.h
@@ -0,0 +1,100 @@
+/*
+ * Tegra host1x Command DMA
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HOST1X_CDMA_H
+#define __HOST1X_CDMA_H
+
+#include <linux/sched.h>
+#include <linux/semaphore.h>
+#include <linux/list.h>
+
+struct host1x_syncpt;
+struct host1x_userctx_timeout;
+struct host1x_job;
+
+/*
+ * cdma
+ *
+ * This is in charge of a host command DMA channel.
+ * Sends ops to a push buffer, and takes responsibility for unpinning
+ * (& possibly freeing) of memory after those ops have completed.
+ * Producer:
+ * begin
+ * push - send ops to the push buffer
+ * end - start command DMA and enqueue handles to be unpinned
+ * Consumer:
+ * update - call to update sync queue and push buffer, unpin memory
+ */
+
+struct push_buffer {
+ u32 *mapped; /* mapped pushbuffer memory */
+ dma_addr_t phys; /* physical address of pushbuffer */
+ u32 fence; /* index we've written */
+ u32 pos; /* index to write to */
+ u32 size_bytes;
+};
+
+struct buffer_timeout {
+ struct delayed_work wq; /* work queue */
+ bool initialized; /* timer one-time setup flag */
+ struct host1x_syncpt *syncpt; /* buffer completion syncpt */
+ u32 syncpt_val; /* syncpt value when completed */
+ ktime_t start_ktime; /* starting time */
+ /* context timeout information */
+ int client;
+};
+
+enum cdma_event {
+ CDMA_EVENT_NONE, /* not waiting for any event */
+ CDMA_EVENT_SYNC_QUEUE_EMPTY, /* wait for empty sync queue */
+ CDMA_EVENT_PUSH_BUFFER_SPACE /* wait for space in push buffer */
+};
+
+struct host1x_cdma {
+ struct mutex lock; /* controls access to shared state */
+ struct semaphore sem; /* signalled when event occurs */
+ enum cdma_event event; /* event that sem is waiting for */
+ unsigned int slots_used; /* pb slots used in current submit */
+ unsigned int slots_free; /* pb slots free in current submit */
+ unsigned int first_get; /* DMAGET value, where submit begins */
+ unsigned int last_pos; /* last value written to DMAPUT */
+ struct push_buffer push_buffer; /* channel's push buffer */
+ struct list_head sync_queue; /* job queue */
+ struct buffer_timeout timeout; /* channel's timeout state/wq */
+ bool running;
+ bool torndown;
+};
+
+#define cdma_to_channel(cdma) container_of(cdma, struct host1x_channel, cdma)
+#define cdma_to_host1x(cdma) host1x_get_host(cdma_to_channel(cdma)->dev)
+#define pb_to_cdma(pb) container_of(pb, struct host1x_cdma, push_buffer)
+
+int host1x_cdma_init(struct host1x_cdma *cdma);
+int host1x_cdma_deinit(struct host1x_cdma *cdma);
+void host1x_cdma_stop(struct host1x_cdma *cdma);
+int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job);
+void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2);
+void host1x_cdma_end(struct host1x_cdma *cdma, struct host1x_job *job);
+void host1x_cdma_update(struct host1x_cdma *cdma);
+void host1x_cdma_peek(struct host1x_cdma *cdma, u32 dmaget, int slot,
+ u32 *out);
+unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma,
+ enum cdma_event event);
+void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma,
+ struct device *dev);
+#endif
diff --git a/drivers/gpu/host1x/channel.c b/drivers/gpu/host1x/channel.c
new file mode 100644
index 0000000..530eab9
--- /dev/null
+++ b/drivers/gpu/host1x/channel.c
@@ -0,0 +1,120 @@
+/*
+ * Tegra host1x Channel
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#include "channel.h"
+#include "dev.h"
+#include "job.h"
+
+/* Constructor for the host1x device list */
+int host1x_channel_list_init(struct host1x *host)
+{
+ INIT_LIST_HEAD(&host->chlist.list);
+ mutex_init(&host->chlist_mutex);
+
+ if (host->info->nb_channels >
+ sizeof(host->allocated_channels) * BITS_PER_LONG) {
+ WARN(1, "host1x hardware has more channels than supported by the driver\n");
+ return -ENOSYS;
+ }
+
+ return 0;
+}
+
+int host1x_job_submit(struct host1x_job *job)
+{
+ return host1x_hw_channel_submit(host1x_get_host(job->channel->dev),
+ job);
+}
+
+struct host1x_channel *host1x_channel_get(struct host1x_channel *ch)
+{
+ int err = 0;
+
+ mutex_lock(&ch->reflock);
+ if (ch->refcount == 0)
+ err = host1x_cdma_init(&ch->cdma);
+ if (!err)
+ ch->refcount++;
+ mutex_unlock(&ch->reflock);
+
+ return err ? NULL : ch;
+}
+
+void host1x_channel_put(struct host1x_channel *ch)
+{
+ mutex_lock(&ch->reflock);
+ if (ch->refcount == 1) {
+ host1x_hw_cdma_stop(host1x_get_host(ch->dev), &ch->cdma);
+ host1x_cdma_deinit(&ch->cdma);
+ }
+ ch->refcount--;
+ mutex_unlock(&ch->reflock);
+}
+
+struct host1x_channel *host1x_channel_alloc(struct device *dev)
+{
+ struct host1x_channel *ch = NULL;
+ struct host1x *host = host1x_get_host(dev);
+ int chindex;
+ int max_channels = host->info->nb_channels;
+ int err;
+
+ mutex_lock(&host->chlist_mutex);
+
+ chindex = find_first_zero_bit(&host->allocated_channels, max_channels);
+ if (chindex >= max_channels)
+ goto fail;
+
+ ch = kzalloc(sizeof(*ch), GFP_KERNEL);
+ if (ch == NULL)
+ goto fail;
+
+ err = host1x_hw_channel_init(host, ch, chindex);
+ if (err < 0)
+ goto fail;
+
+ /* Link device to host1x_channel */
+ ch->dev = dev;
+
+ /* Add to channel list */
+ list_add_tail(&ch->list, &host->chlist.list);
+
+ host->allocated_channels |= BIT_MASK(chindex);
+
+ mutex_unlock(&host->chlist_mutex);
+ return ch;
+
+fail:
+ dev_err(dev, "failed to init channel\n");
+ kfree(ch);
+ mutex_unlock(&host->chlist_mutex);
+ return NULL;
+}
+
+void host1x_channel_free(struct host1x_channel *ch)
+{
+ struct host1x *host = host1x_get_host(ch->dev);
+
+ list_del(&ch->list);
+ kfree(ch);
+
+ host->allocated_channels &= ~BIT_MASK(ch->id);
+}
diff --git a/drivers/gpu/host1x/channel.h b/drivers/gpu/host1x/channel.h
new file mode 100644
index 0000000..8597a07
--- /dev/null
+++ b/drivers/gpu/host1x/channel.h
@@ -0,0 +1,52 @@
+/*
+ * Tegra host1x Channel
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HOST1X_CHANNEL_H
+#define __HOST1X_CHANNEL_H
+
+#include <linux/io.h>
+
+#include "cdma.h"
+
+struct host1x;
+
+struct host1x_channel {
+ struct list_head list;
+
+ unsigned int refcount;
+ unsigned int id;
+ struct mutex reflock;
+ struct mutex submitlock;
+ void __iomem *regs;
+ struct device *dev;
+ struct host1x_cdma cdma;
+};
+
+/* channel list operations */
+int host1x_channel_list_init(struct host1x *host);
+
+struct host1x_channel *host1x_channel_alloc(struct device *dev);
+void host1x_channel_free(struct host1x_channel *ch);
+struct host1x_channel *host1x_channel_get(struct host1x_channel *ch);
+void host1x_channel_put(struct host1x_channel *ch);
+int host1x_job_submit(struct host1x_job *job);
+
+#define host1x_for_each_channel(host, channel) \
+ list_for_each_entry(channel, &host->chlist.list, list)
+
+#endif
diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index 4421e28..bedd37b 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -29,6 +29,7 @@

#include "dev.h"
#include "intr.h"
+#include "channel.h"
#include "hw/host1x01.h"

void host1x_sync_writel(struct host1x *host1x, u32 v, u32 r)
@@ -45,6 +46,16 @@ u32 host1x_sync_readl(struct host1x *host1x, u32 r)
return readl(sync_regs + r);
}

+void host1x_ch_writel(struct host1x_channel *ch, u32 v, u32 r)
+{
+ writel(v, ch->regs + r);
+}
+
+u32 host1x_ch_readl(struct host1x_channel *ch, u32 r)
+{
+ return readl(ch->regs + r);
+}
+
static const struct host1x_info host1x01_info = {
.nb_channels = 8,
.nb_pts = 32,
@@ -116,6 +127,12 @@ static int host1x_probe(struct platform_device *pdev)
return err;
}

+ err = host1x_channel_list_init(host);
+ if (err) {
+ dev_err(dev, "failed to init channel");
+ return err;
+ }
+
err = clk_prepare_enable(host->clk);
if (err < 0) {
dev_err(dev, "failed to enable clock\n");
diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h
index fb5f842..8606322 100644
--- a/drivers/gpu/host1x/dev.h
+++ b/drivers/gpu/host1x/dev.h
@@ -20,10 +20,39 @@
#include <linux/platform_device.h>
#include <linux/device.h>

+#include "channel.h"
#include "syncpt.h"
#include "intr.h"
+#include "cdma.h"
+#include "job.h"

struct host1x_syncpt;
+struct host1x_channel;
+struct host1x_cdma;
+struct host1x_job;
+struct push_buffer;
+
+struct host1x_channel_ops {
+ int (*init)(struct host1x_channel *channel, struct host1x *host,
+ unsigned int id);
+ int (*submit)(struct host1x_job *job);
+};
+
+struct host1x_cdma_ops {
+ void (*start)(struct host1x_cdma *cdma);
+ void (*stop)(struct host1x_cdma *cdma);
+ void (*flush)(struct host1x_cdma *cdma);
+ int (*timeout_init)(struct host1x_cdma *cdma, u32 syncpt_id);
+ void (*timeout_destroy)(struct host1x_cdma *cdma);
+ void (*freeze)(struct host1x_cdma *cdma);
+ void (*resume)(struct host1x_cdma *cdma, u32 getptr);
+ void (*timeout_cpu_incr)(struct host1x_cdma *cdma, u32 getptr,
+ u32 syncpt_incrs, u32 syncval, u32 nr_slots);
+};
+
+struct host1x_pushbuffer_ops {
+ void (*init)(struct push_buffer *pb);
+};

struct host1x_syncpt_ops {
void (*restore)(struct host1x_syncpt *syncpt);
@@ -68,7 +97,16 @@ struct host1x {

const struct host1x_syncpt_ops *syncpt_op;
const struct host1x_intr_ops *intr_op;
+ const struct host1x_channel_ops *channel_op;
+ const struct host1x_cdma_ops *cdma_op;
+ const struct host1x_pushbuffer_ops *cdma_pb_op;

+ struct host1x_syncpt *nop_sp;
+
+ struct mutex chlist_mutex;
+ struct host1x_channel chlist;
+ unsigned long allocated_channels;
+ unsigned int num_allocated_channels;
};

static inline struct host1x *host1x_get_host(struct device *dev)
@@ -81,6 +119,8 @@ static inline struct host1x *host1x_get_host(struct device *dev)

void host1x_sync_writel(struct host1x *host1x, u32 r, u32 v);
u32 host1x_sync_readl(struct host1x *host1x, u32 r);
+void host1x_ch_writel(struct host1x_channel *ch, u32 r, u32 v);
+u32 host1x_ch_readl(struct host1x_channel *ch, u32 r);

static inline void host1x_hw_syncpt_restore(struct host1x *host,
struct host1x_syncpt *sp)
@@ -152,4 +192,77 @@ static inline int host1x_hw_intr_free_syncpt_irq(struct host1x *host)
{
return host->intr_op->free_syncpt_irq(host);
}
+
+static inline int host1x_hw_channel_init(struct host1x *host,
+ struct host1x_channel *channel,
+ int chid)
+{
+ return host->channel_op->init(channel, host, chid);
+}
+
+static inline int host1x_hw_channel_submit(struct host1x *host,
+ struct host1x_job *job)
+{
+ return host->channel_op->submit(job);
+}
+
+static inline void host1x_hw_cdma_start(struct host1x *host,
+ struct host1x_cdma *cdma)
+{
+ host->cdma_op->start(cdma);
+}
+
+static inline void host1x_hw_cdma_stop(struct host1x *host,
+ struct host1x_cdma *cdma)
+{
+ host->cdma_op->stop(cdma);
+}
+
+static inline void host1x_hw_cdma_flush(struct host1x *host,
+ struct host1x_cdma *cdma)
+{
+ host->cdma_op->flush(cdma);
+}
+
+static inline int host1x_hw_cdma_timeout_init(struct host1x *host,
+ struct host1x_cdma *cdma,
+ u32 syncpt_id)
+{
+ return host->cdma_op->timeout_init(cdma, syncpt_id);
+}
+
+static inline void host1x_hw_cdma_timeout_destroy(struct host1x *host,
+ struct host1x_cdma *cdma)
+{
+ host->cdma_op->timeout_destroy(cdma);
+}
+
+static inline void host1x_hw_cdma_freeze(struct host1x *host,
+ struct host1x_cdma *cdma)
+{
+ host->cdma_op->freeze(cdma);
+}
+
+static inline void host1x_hw_cdma_resume(struct host1x *host,
+ struct host1x_cdma *cdma, u32 getptr)
+{
+ host->cdma_op->resume(cdma, getptr);
+}
+
+static inline void host1x_hw_cdma_timeout_cpu_incr(struct host1x *host,
+ struct host1x_cdma *cdma,
+ u32 getptr,
+ u32 syncpt_incrs,
+ u32 syncval, u32 nr_slots)
+{
+ host->cdma_op->timeout_cpu_incr(cdma, getptr, syncpt_incrs, syncval,
+ nr_slots);
+}
+
+static inline void host1x_hw_pushbuffer_init(struct host1x *host,
+ struct push_buffer *pb)
+{
+ host->cdma_pb_op->init(pb);
+}
+
#endif
diff --git a/drivers/gpu/host1x/host1x.h b/drivers/gpu/host1x/host1x.h
new file mode 100644
index 0000000..bca6563
--- /dev/null
+++ b/drivers/gpu/host1x/host1x.h
@@ -0,0 +1,28 @@
+/*
+ * Tegra host1x driver
+ *
+ * Copyright (c) 2009-2013, NVIDIA Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __LINUX_HOST1X_H
+#define __LINUX_HOST1X_H
+
+enum host1x_class {
+ HOST1X_CLASS_HOST1X = 0x1
+};
+
+#endif
diff --git a/drivers/gpu/host1x/host1x_bo.h b/drivers/gpu/host1x/host1x_bo.h
new file mode 100644
index 0000000..25e5d8b
--- /dev/null
+++ b/drivers/gpu/host1x/host1x_bo.h
@@ -0,0 +1,92 @@
+/*
+ * Tegra host1x Memory Management Abstraction header
+ *
+ * Copyright (c) 2012-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _HOST1X_BO_H
+#define _HOST1X_BO_H
+
+struct host1x_bo;
+
+struct host1x_job_unpin_data {
+ struct host1x_bo *h;
+ struct sg_table *mem;
+};
+
+struct host1x_bo_ops {
+ struct host1x_bo *(*get)(struct host1x_bo *bo);
+ void (*put)(struct host1x_bo *bo);
+ dma_addr_t (*pin)(struct host1x_bo *bo, struct sg_table **sgt);
+ void (*unpin)(struct host1x_bo *bo, struct sg_table *sgt);
+ void *(*mmap)(struct host1x_bo *bo);
+ void (*munmap)(struct host1x_bo *bo, void *addr);
+ void *(*kmap)(struct host1x_bo *bo, unsigned int pagenum);
+ void (*kunmap)(struct host1x_bo *bo, unsigned int pagenum, void *addr);
+};
+
+struct host1x_bo {
+ const struct host1x_bo_ops *ops;
+};
+
+static inline void host1x_bo_init(struct host1x_bo *bo,
+ const struct host1x_bo_ops *ops)
+{
+ bo->ops = ops;
+}
+
+static inline struct host1x_bo *host1x_bo_get(struct host1x_bo *bo)
+{
+ return bo->ops->get(bo);
+}
+
+static inline void host1x_bo_put(struct host1x_bo *bo)
+{
+ bo->ops->put(bo);
+}
+
+static inline dma_addr_t host1x_bo_pin(struct host1x_bo *bo,
+ struct sg_table **sgt)
+{
+ return bo->ops->pin(bo, sgt);
+}
+
+static inline void host1x_bo_unpin(struct host1x_bo *bo, struct sg_table *sgt)
+{
+ bo->ops->unpin(bo, sgt);
+}
+
+static inline void *host1x_bo_mmap(struct host1x_bo *bo)
+{
+ return bo->ops->mmap(bo);
+}
+
+static inline void host1x_bo_munmap(struct host1x_bo *bo, void *addr)
+{
+ bo->ops->munmap(bo, addr);
+}
+
+static inline void *host1x_bo_kmap(struct host1x_bo *bo, unsigned int pagenum)
+{
+ return bo->ops->kmap(bo, pagenum);
+}
+
+static inline void host1x_bo_kunmap(struct host1x_bo *bo,
+ unsigned int pagenum, void *addr)
+{
+ bo->ops->kunmap(bo, pagenum, addr);
+}
+
+#endif
diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c
new file mode 100644
index 0000000..4eb22ef
--- /dev/null
+++ b/drivers/gpu/host1x/hw/cdma_hw.c
@@ -0,0 +1,324 @@
+/*
+ * Tegra host1x Command DMA
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+#include <linux/dma-mapping.h>
+
+#include "cdma.h"
+#include "channel.h"
+#include "dev.h"
+#include "debug.h"
+
+/*
+ * Put the restart at the end of pushbuffer memor
+ */
+static void push_buffer_init(struct push_buffer *pb)
+{
+ *(pb->mapped + (pb->size_bytes >> 2)) = host1x_opcode_restart(0);
+}
+
+/*
+ * Increment timedout buffer's syncpt via CPU.
+ */
+static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr,
+ u32 syncpt_incrs, u32 syncval, u32 nr_slots)
+{
+ struct host1x *host1x = cdma_to_host1x(cdma);
+ struct push_buffer *pb = &cdma->push_buffer;
+ u32 i;
+
+ for (i = 0; i < syncpt_incrs; i++)
+ host1x_syncpt_cpu_incr(cdma->timeout.syncpt);
+
+ /* after CPU incr, ensure shadow is up to date */
+ host1x_syncpt_load(cdma->timeout.syncpt);
+
+ /* NOP all the PB slots */
+ while (nr_slots--) {
+ u32 *p = (u32 *)((u32)pb->mapped + getptr);
+ *(p++) = HOST1X_OPCODE_NOP;
+ *(p++) = HOST1X_OPCODE_NOP;
+ dev_dbg(host1x->dev, "%s: NOP at 0x%x\n", __func__,
+ pb->phys + getptr);
+ getptr = (getptr + 8) & (pb->size_bytes - 1);
+ }
+ wmb();
+}
+
+/*
+ * Start channel DMA
+ */
+static void cdma_start(struct host1x_cdma *cdma)
+{
+ struct host1x_channel *ch = cdma_to_channel(cdma);
+
+ if (cdma->running)
+ return;
+
+ cdma->last_pos = cdma->push_buffer.pos;
+
+ host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
+ HOST1X_CHANNEL_DMACTRL);
+
+ /* set base, put and end pointer */
+ host1x_ch_writel(ch, cdma->push_buffer.phys, HOST1X_CHANNEL_DMASTART);
+ host1x_ch_writel(ch, cdma->push_buffer.pos, HOST1X_CHANNEL_DMAPUT);
+ host1x_ch_writel(ch, cdma->push_buffer.phys +
+ cdma->push_buffer.size_bytes + 4,
+ HOST1X_CHANNEL_DMAEND);
+
+ /* reset GET */
+ host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP |
+ HOST1X_CHANNEL_DMACTRL_DMAGETRST |
+ HOST1X_CHANNEL_DMACTRL_DMAINITGET,
+ HOST1X_CHANNEL_DMACTRL);
+
+ /* start the command DMA */
+ host1x_ch_writel(ch, 0, HOST1X_CHANNEL_DMACTRL);
+
+ cdma->running = true;
+}
+
+/*
+ * Similar to cdma_start(), but rather than starting from an idle
+ * state (where DMA GET is set to DMA PUT), on a timeout we restore
+ * DMA GET from an explicit value (so DMA may again be pending).
+ */
+static void cdma_timeout_restart(struct host1x_cdma *cdma, u32 getptr)
+{
+ struct host1x *host1x = cdma_to_host1x(cdma);
+ struct host1x_channel *ch = cdma_to_channel(cdma);
+
+ if (cdma->running)
+ return;
+
+ cdma->last_pos = cdma->push_buffer.pos;
+
+ host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
+ HOST1X_CHANNEL_DMACTRL);
+
+ /* set base, end pointer (all of memory) */
+ host1x_ch_writel(ch, cdma->push_buffer.phys, HOST1X_CHANNEL_DMASTART);
+ host1x_ch_writel(ch, cdma->push_buffer.phys +
+ cdma->push_buffer.size_bytes,
+ HOST1X_CHANNEL_DMAEND);
+
+ /* set GET, by loading the value in PUT (then reset GET) */
+ host1x_ch_writel(ch, getptr, HOST1X_CHANNEL_DMAPUT);
+ host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP |
+ HOST1X_CHANNEL_DMACTRL_DMAGETRST |
+ HOST1X_CHANNEL_DMACTRL_DMAINITGET,
+ HOST1X_CHANNEL_DMACTRL);
+
+ dev_dbg(host1x->dev,
+ "%s: DMA GET 0x%x, PUT HW 0x%x / shadow 0x%x\n", __func__,
+ host1x_ch_readl(ch, HOST1X_CHANNEL_DMAGET),
+ host1x_ch_readl(ch, HOST1X_CHANNEL_DMAPUT),
+ cdma->last_pos);
+
+ /* deassert GET reset and set PUT */
+ host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
+ HOST1X_CHANNEL_DMACTRL);
+ host1x_ch_writel(ch, cdma->push_buffer.pos, HOST1X_CHANNEL_DMAPUT);
+
+ /* start the command DMA */
+ host1x_ch_writel(ch, 0, HOST1X_CHANNEL_DMACTRL);
+
+ cdma->running = true;
+}
+
+/*
+ * Kick channel DMA into action by writing its PUT offset (if it has changed)
+ */
+static void cdma_flush(struct host1x_cdma *cdma)
+{
+ struct host1x_channel *ch = cdma_to_channel(cdma);
+
+ if (cdma->push_buffer.pos != cdma->last_pos) {
+ host1x_ch_writel(ch, cdma->push_buffer.pos,
+ HOST1X_CHANNEL_DMAPUT);
+ cdma->last_pos = cdma->push_buffer.pos;
+ }
+}
+
+static void cdma_stop(struct host1x_cdma *cdma)
+{
+ struct host1x_channel *ch = cdma_to_channel(cdma);
+
+ mutex_lock(&cdma->lock);
+ if (cdma->running) {
+ host1x_cdma_wait_locked(cdma, CDMA_EVENT_SYNC_QUEUE_EMPTY);
+ host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
+ HOST1X_CHANNEL_DMACTRL);
+ cdma->running = false;
+ }
+ mutex_unlock(&cdma->lock);
+}
+
+/*
+ * Stops both channel's command processor and CDMA immediately.
+ * Also, tears down the channel and resets corresponding module.
+ */
+static void cdma_freeze(struct host1x_cdma *cdma)
+{
+ struct host1x *host = cdma_to_host1x(cdma);
+ struct host1x_channel *ch = cdma_to_channel(cdma);
+ u32 cmdproc_stop;
+
+ if (cdma->torndown && !cdma->running) {
+ dev_warn(host->dev, "Already torn down\n");
+ return;
+ }
+
+ dev_dbg(host->dev, "freezing channel (id %d)\n", ch->id);
+
+ cmdproc_stop = host1x_sync_readl(host, HOST1X_SYNC_CMDPROC_STOP);
+ cmdproc_stop |= BIT(ch->id);
+ host1x_sync_writel(host, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP);
+
+ dev_dbg(host->dev, "%s: DMA GET 0x%x, PUT HW 0x%x / shadow 0x%x\n",
+ __func__, host1x_ch_readl(ch, HOST1X_CHANNEL_DMAGET),
+ host1x_ch_readl(ch, HOST1X_CHANNEL_DMAPUT),
+ cdma->last_pos);
+
+ host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP,
+ HOST1X_CHANNEL_DMACTRL);
+
+ host1x_sync_writel(host, BIT(ch->id), HOST1X_SYNC_CH_TEARDOWN);
+
+ cdma->running = false;
+ cdma->torndown = true;
+}
+
+static void cdma_resume(struct host1x_cdma *cdma, u32 getptr)
+{
+ struct host1x *host1x = cdma_to_host1x(cdma);
+ struct host1x_channel *ch = cdma_to_channel(cdma);
+ u32 cmdproc_stop;
+
+ dev_dbg(host1x->dev,
+ "resuming channel (id %d, DMAGET restart = 0x%x)\n",
+ ch->id, getptr);
+
+ cmdproc_stop = host1x_sync_readl(host1x, HOST1X_SYNC_CMDPROC_STOP);
+ cmdproc_stop &= ~(BIT(ch->id));
+ host1x_sync_writel(host1x, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP);
+
+ cdma->torndown = false;
+ cdma_timeout_restart(cdma, getptr);
+}
+
+/*
+ * If this timeout fires, it indicates the current sync_queue entry has
+ * exceeded its TTL and the userctx should be timed out and remaining
+ * submits already issued cleaned up (future submits return an error).
+ */
+static void cdma_timeout_handler(struct work_struct *work)
+{
+ struct host1x_cdma *cdma;
+ struct host1x *host1x;
+ struct host1x_channel *ch;
+
+ u32 syncpt_val;
+
+ u32 prev_cmdproc, cmdproc_stop;
+
+ cdma = container_of(to_delayed_work(work), struct host1x_cdma,
+ timeout.wq);
+ host1x = cdma_to_host1x(cdma);
+ ch = cdma_to_channel(cdma);
+
+ mutex_lock(&cdma->lock);
+
+ if (!cdma->timeout.client) {
+ dev_dbg(host1x->dev,
+ "cdma_timeout: expired, but has no clientid\n");
+ mutex_unlock(&cdma->lock);
+ return;
+ }
+
+ /* stop processing to get a clean snapshot */
+ prev_cmdproc = host1x_sync_readl(host1x, HOST1X_SYNC_CMDPROC_STOP);
+ cmdproc_stop = prev_cmdproc | BIT(ch->id);
+ host1x_sync_writel(host1x, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP);
+
+ dev_dbg(host1x->dev, "cdma_timeout: cmdproc was 0x%x is 0x%x\n",
+ prev_cmdproc, cmdproc_stop);
+
+ syncpt_val = host1x_syncpt_load(cdma->timeout.syncpt);
+
+ /* has buffer actually completed? */
+ if ((s32)(syncpt_val - cdma->timeout.syncpt_val) >= 0) {
+ dev_dbg(host1x->dev,
+ "cdma_timeout: expired, but buffer had completed\n");
+ /* restore */
+ cmdproc_stop = prev_cmdproc & ~(BIT(ch->id));
+ host1x_sync_writel(host1x, cmdproc_stop,
+ HOST1X_SYNC_CMDPROC_STOP);
+ mutex_unlock(&cdma->lock);
+ return;
+ }
+
+ dev_warn(host1x->dev, "%s: timeout: %d (%s), HW thresh %d, done %d\n",
+ __func__, cdma->timeout.syncpt->id, cdma->timeout.syncpt->name,
+ syncpt_val, cdma->timeout.syncpt_val);
+
+ /* stop HW, resetting channel/module */
+ host1x_hw_cdma_freeze(host1x, cdma);
+
+ host1x_cdma_update_sync_queue(cdma, ch->dev);
+ mutex_unlock(&cdma->lock);
+}
+
+/*
+ * Init timeout resources
+ */
+static int cdma_timeout_init(struct host1x_cdma *cdma, u32 syncpt_id)
+{
+ INIT_DELAYED_WORK(&cdma->timeout.wq, cdma_timeout_handler);
+ cdma->timeout.initialized = true;
+
+ return 0;
+}
+
+/*
+ * Clean up timeout resources
+ */
+static void cdma_timeout_destroy(struct host1x_cdma *cdma)
+{
+ if (cdma->timeout.initialized)
+ cancel_delayed_work(&cdma->timeout.wq);
+ cdma->timeout.initialized = false;
+}
+
+static const struct host1x_cdma_ops host1x_cdma_ops = {
+ .start = cdma_start,
+ .stop = cdma_stop,
+ .flush = cdma_flush,
+
+ .timeout_init = cdma_timeout_init,
+ .timeout_destroy = cdma_timeout_destroy,
+ .freeze = cdma_freeze,
+ .resume = cdma_resume,
+ .timeout_cpu_incr = cdma_timeout_cpu_incr,
+};
+
+static const struct host1x_pushbuffer_ops host1x_pushbuffer_ops = {
+ .init = push_buffer_init,
+};
diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c
new file mode 100644
index 0000000..bf85a8e
--- /dev/null
+++ b/drivers/gpu/host1x/hw/channel_hw.c
@@ -0,0 +1,143 @@
+/*
+ * Tegra host1x Channel
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/slab.h>
+#include <trace/events/host1x.h>
+
+#include "host1x.h"
+#include "host1x_bo.h"
+#include "channel.h"
+#include "dev.h"
+#include "intr.h"
+#include "job.h"
+
+#define HOST1X_CHANNEL_SIZE 16384
+#define TRACE_MAX_LENGTH 128U
+
+static void submit_gathers(struct host1x_job *job)
+{
+ struct host1x_cdma *cdma = &job->channel->cdma;
+ unsigned int i;
+
+ for (i = 0; i < job->num_gathers; i++) {
+ struct host1x_job_gather *g = &job->gathers[i];
+ u32 op1 = host1x_opcode_gather(g->words);
+ u32 op2 = g->mem_base + g->offset;
+ host1x_cdma_push(cdma, op1, op2);
+ }
+}
+
+static int channel_submit(struct host1x_job *job)
+{
+ struct host1x_channel *ch = job->channel;
+ struct host1x_syncpt *sp;
+ u32 user_syncpt_incrs = job->syncpt_incrs;
+ u32 prev_max = 0;
+ u32 syncval;
+ int err;
+ struct host1x_waitlist *completed_waiter = NULL;
+
+ sp = host1x_get_host(job->channel->dev)->syncpt + job->syncpt_id;
+ trace_host1x_channel_submit(dev_name(ch->dev),
+ job->num_gathers, job->num_relocs,
+ job->num_waitchk, job->syncpt_id,
+ job->syncpt_incrs);
+
+ /* before error checks, return current max */
+ prev_max = job->syncpt_end = host1x_syncpt_read_max(sp);
+
+ /* get submit lock */
+ err = mutex_lock_interruptible(&ch->submitlock);
+ if (err)
+ goto error;
+
+ completed_waiter = kzalloc(sizeof(*completed_waiter), GFP_KERNEL);
+ if (!completed_waiter) {
+ mutex_unlock(&ch->submitlock);
+ err = -ENOMEM;
+ goto error;
+ }
+
+ /* begin a CDMA submit */
+ err = host1x_cdma_begin(&ch->cdma, job);
+ if (err) {
+ mutex_unlock(&ch->submitlock);
+ goto error;
+ }
+
+ if (job->serialize) {
+ /*
+ * Force serialization by inserting a host wait for the
+ * previous job to finish before this one can commence.
+ */
+ host1x_cdma_push(&ch->cdma,
+ host1x_opcode_setclass(HOST1X_CLASS_HOST1X,
+ host1x_uclass_wait_syncpt_r(), 1),
+ host1x_class_host_wait_syncpt(job->syncpt_id,
+ host1x_syncpt_read_max(sp)));
+ }
+
+ syncval = host1x_syncpt_incr_max(sp, user_syncpt_incrs);
+
+ job->syncpt_end = syncval;
+
+ /* add a setclass for modules that require it */
+ if (job->class)
+ host1x_cdma_push(&ch->cdma,
+ host1x_opcode_setclass(job->class, 0, 0),
+ HOST1X_OPCODE_NOP);
+
+ submit_gathers(job);
+
+ /* end CDMA submit & stash pinned hMems into sync queue */
+ host1x_cdma_end(&ch->cdma, job);
+
+ trace_host1x_channel_submitted(dev_name(ch->dev), prev_max, syncval);
+
+ /* schedule a submit complete interrupt */
+ err = host1x_intr_add_action(host1x_get_host(ch->dev), job->syncpt_id,
+ syncval,
+ HOST1X_INTR_ACTION_SUBMIT_COMPLETE, ch,
+ completed_waiter, NULL);
+ completed_waiter = NULL;
+ WARN(err, "Failed to set submit complete interrupt");
+
+ mutex_unlock(&ch->submitlock);
+
+ return 0;
+
+error:
+ kfree(completed_waiter);
+ return err;
+}
+
+static int host1x_channel_init(struct host1x_channel *ch, struct host1x *dev,
+ unsigned int index)
+{
+ ch->id = index;
+ mutex_init(&ch->reflock);
+ mutex_init(&ch->submitlock);
+
+ ch->regs = dev->regs + index * HOST1X_CHANNEL_SIZE;
+ return 0;
+}
+
+static const struct host1x_channel_ops host1x_channel_ops = {
+ .init = host1x_channel_init,
+ .submit = channel_submit,
+};
diff --git a/drivers/gpu/host1x/hw/host1x01.c b/drivers/gpu/host1x/hw/host1x01.c
index f5c35fa..013ff38 100644
--- a/drivers/gpu/host1x/hw/host1x01.c
+++ b/drivers/gpu/host1x/hw/host1x01.c
@@ -21,6 +21,8 @@
#include "hw/host1x01_hardware.h"

/* include code */
+#include "hw/cdma_hw.c"
+#include "hw/channel_hw.c"
#include "hw/intr_hw.c"
#include "hw/syncpt_hw.c"

@@ -28,6 +30,9 @@

int host1x01_init(struct host1x *host)
{
+ host->channel_op = &host1x_channel_ops;
+ host->cdma_op = &host1x_cdma_ops;
+ host->cdma_pb_op = &host1x_pushbuffer_ops;
host->syncpt_op = &host1x_syncpt_ops;
host->intr_op = &host1x_intr_ops;

diff --git a/drivers/gpu/host1x/hw/host1x01_hardware.h b/drivers/gpu/host1x/hw/host1x01_hardware.h
index 8cecbee..5f0fb86 100644
--- a/drivers/gpu/host1x/hw/host1x01_hardware.h
+++ b/drivers/gpu/host1x/hw/host1x01_hardware.h
@@ -22,6 +22,122 @@
#include <linux/types.h>
#include <linux/bitops.h>

+#include "hw_host1x01_channel.h"
#include "hw_host1x01_sync.h"
+#include "hw_host1x01_uclass.h"
+
+static inline u32 host1x_class_host_wait_syncpt(
+ unsigned indx, unsigned threshold)
+{
+ return host1x_uclass_wait_syncpt_indx_f(indx)
+ | host1x_uclass_wait_syncpt_thresh_f(threshold);
+}
+
+static inline u32 host1x_class_host_load_syncpt_base(
+ unsigned indx, unsigned threshold)
+{
+ return host1x_uclass_load_syncpt_base_base_indx_f(indx)
+ | host1x_uclass_load_syncpt_base_value_f(threshold);
+}
+
+static inline u32 host1x_class_host_wait_syncpt_base(
+ unsigned indx, unsigned base_indx, unsigned offset)
+{
+ return host1x_uclass_wait_syncpt_base_indx_f(indx)
+ | host1x_uclass_wait_syncpt_base_base_indx_f(base_indx)
+ | host1x_uclass_wait_syncpt_base_offset_f(offset);
+}
+
+static inline u32 host1x_class_host_incr_syncpt_base(
+ unsigned base_indx, unsigned offset)
+{
+ return host1x_uclass_incr_syncpt_base_base_indx_f(base_indx)
+ | host1x_uclass_incr_syncpt_base_offset_f(offset);
+}
+
+static inline u32 host1x_class_host_incr_syncpt(
+ unsigned cond, unsigned indx)
+{
+ return host1x_uclass_incr_syncpt_cond_f(cond)
+ | host1x_uclass_incr_syncpt_indx_f(indx);
+}
+
+static inline u32 host1x_class_host_indoff_reg_write(
+ unsigned mod_id, unsigned offset, bool auto_inc)
+{
+ u32 v = host1x_uclass_indoff_indbe_f(0xf)
+ | host1x_uclass_indoff_indmodid_f(mod_id)
+ | host1x_uclass_indoff_indroffset_f(offset);
+ if (auto_inc)
+ v |= host1x_uclass_indoff_autoinc_f(1);
+ return v;
+}
+
+static inline u32 host1x_class_host_indoff_reg_read(
+ unsigned mod_id, unsigned offset, bool auto_inc)
+{
+ u32 v = host1x_uclass_indoff_indmodid_f(mod_id)
+ | host1x_uclass_indoff_indroffset_f(offset)
+ | host1x_uclass_indoff_rwn_read_v();
+ if (auto_inc)
+ v |= host1x_uclass_indoff_autoinc_f(1);
+ return v;
+}
+
+
+/* cdma opcodes */
+static inline u32 host1x_opcode_setclass(
+ unsigned class_id, unsigned offset, unsigned mask)
+{
+ return (0 << 28) | (offset << 16) | (class_id << 6) | mask;
+}
+
+static inline u32 host1x_opcode_incr(unsigned offset, unsigned count)
+{
+ return (1 << 28) | (offset << 16) | count;
+}
+
+static inline u32 host1x_opcode_nonincr(unsigned offset, unsigned count)
+{
+ return (2 << 28) | (offset << 16) | count;
+}
+
+static inline u32 host1x_opcode_mask(unsigned offset, unsigned mask)
+{
+ return (3 << 28) | (offset << 16) | mask;
+}
+
+static inline u32 host1x_opcode_imm(unsigned offset, unsigned value)
+{
+ return (4 << 28) | (offset << 16) | value;
+}
+
+static inline u32 host1x_opcode_imm_incr_syncpt(unsigned cond, unsigned indx)
+{
+ return host1x_opcode_imm(host1x_uclass_incr_syncpt_r(),
+ host1x_class_host_incr_syncpt(cond, indx));
+}
+
+static inline u32 host1x_opcode_restart(unsigned address)
+{
+ return (5 << 28) | (address >> 4);
+}
+
+static inline u32 host1x_opcode_gather(unsigned count)
+{
+ return (6 << 28) | count;
+}
+
+static inline u32 host1x_opcode_gather_nonincr(unsigned offset, unsigned count)
+{
+ return (6 << 28) | (offset << 16) | BIT(15) | count;
+}
+
+static inline u32 host1x_opcode_gather_incr(unsigned offset, unsigned count)
+{
+ return (6 << 28) | (offset << 16) | BIT(15) | BIT(14) | count;
+}
+
+#define HOST1X_OPCODE_NOP host1x_opcode_nonincr(0, 0)

#endif
diff --git a/drivers/gpu/host1x/hw/hw_host1x01_channel.h b/drivers/gpu/host1x/hw/hw_host1x01_channel.h
new file mode 100644
index 0000000..9ba1332
--- /dev/null
+++ b/drivers/gpu/host1x/hw/hw_host1x01_channel.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2012-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+ /*
+ * Function naming determines intended use:
+ *
+ * <x>_r(void) : Returns the offset for register <x>.
+ *
+ * <x>_w(void) : Returns the word offset for word (4 byte) element <x>.
+ *
+ * <x>_<y>_s(void) : Returns size of field <y> of register <x> in bits.
+ *
+ * <x>_<y>_f(u32 v) : Returns a value based on 'v' which has been shifted
+ * and masked to place it at field <y> of register <x>. This value
+ * can be |'d with others to produce a full register value for
+ * register <x>.
+ *
+ * <x>_<y>_m(void) : Returns a mask for field <y> of register <x>. This
+ * value can be ~'d and then &'d to clear the value of field <y> for
+ * register <x>.
+ *
+ * <x>_<y>_<z>_f(void) : Returns the constant value <z> after being shifted
+ * to place it at field <y> of register <x>. This value can be |'d
+ * with others to produce a full register value for <x>.
+ *
+ * <x>_<y>_v(u32 r) : Returns the value of field <y> from a full register
+ * <x> value 'r' after being shifted to place its LSB at bit 0.
+ * This value is suitable for direct comparison with other unshifted
+ * values appropriate for use in field <y> of register <x>.
+ *
+ * <x>_<y>_<z>_v(void) : Returns the constant value for <z> defined for
+ * field <y> of register <x>. This value is suitable for direct
+ * comparison with unshifted values appropriate for use in field <y>
+ * of register <x>.
+ */
+
+#ifndef __hw_host1x_channel_host1x_h__
+#define __hw_host1x_channel_host1x_h__
+
+static inline u32 host1x_channel_dmastart_r(void)
+{
+ return 0x14;
+}
+#define HOST1X_CHANNEL_DMASTART \
+ host1x_channel_dmastart_r()
+static inline u32 host1x_channel_dmaput_r(void)
+{
+ return 0x18;
+}
+#define HOST1X_CHANNEL_DMAPUT \
+ host1x_channel_dmaput_r()
+static inline u32 host1x_channel_dmaget_r(void)
+{
+ return 0x1c;
+}
+#define HOST1X_CHANNEL_DMAGET \
+ host1x_channel_dmaget_r()
+static inline u32 host1x_channel_dmaend_r(void)
+{
+ return 0x20;
+}
+#define HOST1X_CHANNEL_DMAEND \
+ host1x_channel_dmaend_r()
+static inline u32 host1x_channel_dmactrl_r(void)
+{
+ return 0x24;
+}
+#define HOST1X_CHANNEL_DMACTRL \
+ host1x_channel_dmactrl_r()
+static inline u32 host1x_channel_dmactrl_dmastop(void)
+{
+ return 1 << 0;
+}
+#define HOST1X_CHANNEL_DMACTRL_DMASTOP \
+ host1x_channel_dmactrl_dmastop()
+static inline u32 host1x_channel_dmactrl_dmagetrst(void)
+{
+ return 1 << 1;
+}
+#define HOST1X_CHANNEL_DMACTRL_DMAGETRST \
+ host1x_channel_dmactrl_dmagetrst()
+static inline u32 host1x_channel_dmactrl_dmainitget(void)
+{
+ return 1 << 2;
+}
+#define HOST1X_CHANNEL_DMACTRL_DMAINITGET \
+ host1x_channel_dmactrl_dmainitget()
+#endif
diff --git a/drivers/gpu/host1x/hw/hw_host1x01_sync.h b/drivers/gpu/host1x/hw/hw_host1x01_sync.h
index eea0bb0..8f2a246 100644
--- a/drivers/gpu/host1x/hw/hw_host1x01_sync.h
+++ b/drivers/gpu/host1x/hw/hw_host1x01_sync.h
@@ -77,6 +77,18 @@ static inline u32 host1x_sync_syncpt_thresh_int_enable_cpu0_r(unsigned int id)
}
#define HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(id) \
host1x_sync_syncpt_thresh_int_enable_cpu0_r(id)
+static inline u32 host1x_sync_cmdproc_stop_r(void)
+{
+ return 0xac;
+}
+#define HOST1X_SYNC_CMDPROC_STOP \
+ host1x_sync_cmdproc_stop_r()
+static inline u32 host1x_sync_ch_teardown_r(void)
+{
+ return 0xb0;
+}
+#define HOST1X_SYNC_CH_TEARDOWN \
+ host1x_sync_ch_teardown_r()
static inline u32 host1x_sync_usec_clk_r(void)
{
return 0x1a4;
diff --git a/drivers/gpu/host1x/hw/hw_host1x01_uclass.h b/drivers/gpu/host1x/hw/hw_host1x01_uclass.h
new file mode 100644
index 0000000..7af6609
--- /dev/null
+++ b/drivers/gpu/host1x/hw/hw_host1x01_uclass.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2012-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+ /*
+ * Function naming determines intended use:
+ *
+ * <x>_r(void) : Returns the offset for register <x>.
+ *
+ * <x>_w(void) : Returns the word offset for word (4 byte) element <x>.
+ *
+ * <x>_<y>_s(void) : Returns size of field <y> of register <x> in bits.
+ *
+ * <x>_<y>_f(u32 v) : Returns a value based on 'v' which has been shifted
+ * and masked to place it at field <y> of register <x>. This value
+ * can be |'d with others to produce a full register value for
+ * register <x>.
+ *
+ * <x>_<y>_m(void) : Returns a mask for field <y> of register <x>. This
+ * value can be ~'d and then &'d to clear the value of field <y> for
+ * register <x>.
+ *
+ * <x>_<y>_<z>_f(void) : Returns the constant value <z> after being shifted
+ * to place it at field <y> of register <x>. This value can be |'d
+ * with others to produce a full register value for <x>.
+ *
+ * <x>_<y>_v(u32 r) : Returns the value of field <y> from a full register
+ * <x> value 'r' after being shifted to place its LSB at bit 0.
+ * This value is suitable for direct comparison with other unshifted
+ * values appropriate for use in field <y> of register <x>.
+ *
+ * <x>_<y>_<z>_v(void) : Returns the constant value for <z> defined for
+ * field <y> of register <x>. This value is suitable for direct
+ * comparison with unshifted values appropriate for use in field <y>
+ * of register <x>.
+ */
+
+#ifndef __hw_host1x_uclass_host1x_h__
+#define __hw_host1x_uclass_host1x_h__
+
+static inline u32 host1x_uclass_incr_syncpt_r(void)
+{
+ return 0x0;
+}
+#define HOST1X_UCLASS_INCR_SYNCPT \
+ host1x_uclass_incr_syncpt_r()
+static inline u32 host1x_uclass_incr_syncpt_cond_f(u32 v)
+{
+ return (v & 0xff) << 8;
+}
+#define HOST1X_UCLASS_INCR_SYNCPT_COND_F(v) \
+ host1x_uclass_incr_syncpt_cond_f(v)
+static inline u32 host1x_uclass_incr_syncpt_indx_f(u32 v)
+{
+ return (v & 0xff) << 0;
+}
+#define HOST1X_UCLASS_INCR_SYNCPT_INDX_F(v) \
+ host1x_uclass_incr_syncpt_indx_f(v)
+static inline u32 host1x_uclass_wait_syncpt_r(void)
+{
+ return 0x8;
+}
+#define HOST1X_UCLASS_WAIT_SYNCPT \
+ host1x_uclass_wait_syncpt_r()
+static inline u32 host1x_uclass_wait_syncpt_indx_f(u32 v)
+{
+ return (v & 0xff) << 24;
+}
+#define HOST1X_UCLASS_WAIT_SYNCPT_INDX_F(v) \
+ host1x_uclass_wait_syncpt_indx_f(v)
+static inline u32 host1x_uclass_wait_syncpt_thresh_f(u32 v)
+{
+ return (v & 0xffffff) << 0;
+}
+#define HOST1X_UCLASS_WAIT_SYNCPT_THRESH_F(v) \
+ host1x_uclass_wait_syncpt_thresh_f(v)
+static inline u32 host1x_uclass_wait_syncpt_base_indx_f(u32 v)
+{
+ return (v & 0xff) << 24;
+}
+#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_INDX_F(v) \
+ host1x_uclass_wait_syncpt_base_indx_f(v)
+static inline u32 host1x_uclass_wait_syncpt_base_base_indx_f(u32 v)
+{
+ return (v & 0xff) << 16;
+}
+#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_BASE_INDX_F(v) \
+ host1x_uclass_wait_syncpt_base_base_indx_f(v)
+static inline u32 host1x_uclass_wait_syncpt_base_offset_f(u32 v)
+{
+ return (v & 0xffff) << 0;
+}
+#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_OFFSET_F(v) \
+ host1x_uclass_wait_syncpt_base_offset_f(v)
+static inline u32 host1x_uclass_load_syncpt_base_base_indx_f(u32 v)
+{
+ return (v & 0xff) << 24;
+}
+#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_BASE_INDX_F(v) \
+ host1x_uclass_load_syncpt_base_base_indx_f(v)
+static inline u32 host1x_uclass_load_syncpt_base_value_f(u32 v)
+{
+ return (v & 0xffffff) << 0;
+}
+#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_VALUE_F(v) \
+ host1x_uclass_load_syncpt_base_value_f(v)
+static inline u32 host1x_uclass_incr_syncpt_base_base_indx_f(u32 v)
+{
+ return (v & 0xff) << 24;
+}
+#define HOST1X_UCLASS_INCR_SYNCPT_BASE_BASE_INDX_F(v) \
+ host1x_uclass_incr_syncpt_base_base_indx_f(v)
+static inline u32 host1x_uclass_incr_syncpt_base_offset_f(u32 v)
+{
+ return (v & 0xffffff) << 0;
+}
+#define HOST1X_UCLASS_INCR_SYNCPT_BASE_OFFSET_F(v) \
+ host1x_uclass_incr_syncpt_base_offset_f(v)
+static inline u32 host1x_uclass_indoff_r(void)
+{
+ return 0x2d;
+}
+#define HOST1X_UCLASS_INDOFF \
+ host1x_uclass_indoff_r()
+static inline u32 host1x_uclass_indoff_indbe_f(u32 v)
+{
+ return (v & 0xf) << 28;
+}
+#define HOST1X_UCLASS_INDOFF_INDBE_F(v) \
+ host1x_uclass_indoff_indbe_f(v)
+static inline u32 host1x_uclass_indoff_autoinc_f(u32 v)
+{
+ return (v & 0x1) << 27;
+}
+#define HOST1X_UCLASS_INDOFF_AUTOINC_F(v) \
+ host1x_uclass_indoff_autoinc_f(v)
+static inline u32 host1x_uclass_indoff_indmodid_f(u32 v)
+{
+ return (v & 0xff) << 18;
+}
+#define HOST1X_UCLASS_INDOFF_INDMODID_F(v) \
+ host1x_uclass_indoff_indmodid_f(v)
+static inline u32 host1x_uclass_indoff_indroffset_f(u32 v)
+{
+ return (v & 0xffff) << 2;
+}
+#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \
+ host1x_uclass_indoff_indroffset_f(v)
+static inline u32 host1x_uclass_indoff_rwn_read_v(void)
+{
+ return 1;
+}
+#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \
+ host1x_uclass_indoff_indroffset_f(v)
+#endif
diff --git a/drivers/gpu/host1x/hw/syncpt_hw.c b/drivers/gpu/host1x/hw/syncpt_hw.c
index 885b257..2c1f4af 100644
--- a/drivers/gpu/host1x/hw/syncpt_hw.c
+++ b/drivers/gpu/host1x/hw/syncpt_hw.c
@@ -93,10 +93,21 @@ static void syncpt_cpu_incr(struct host1x_syncpt *sp)
wmb();
}

+/* remove a wait pointed to by patch_addr */
+static int syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr)
+{
+ u32 override = host1x_class_host_wait_syncpt(
+ HOST1X_SYNCPT_RESERVED, 0);
+
+ *((u32 *)patch_addr) = override;
+ return 0;
+}
+
static const struct host1x_syncpt_ops host1x_syncpt_ops = {
.restore = syncpt_restore,
.restore_wait_base = syncpt_restore_wait_base,
.load_wait_base = syncpt_read_wait_base,
.load = syncpt_load,
.cpu_incr = syncpt_cpu_incr,
+ .patch_wait = syncpt_patch_wait,
};
diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c
index b1b5a80..2491bf8 100644
--- a/drivers/gpu/host1x/intr.c
+++ b/drivers/gpu/host1x/intr.c
@@ -21,6 +21,8 @@
#include <linux/slab.h>
#include <linux/irq.h>

+#include <trace/events/host1x.h>
+#include "channel.h"
#include "dev.h"
#include "intr.h"

@@ -66,7 +68,7 @@ static void remove_completed_waiters(struct list_head *head, u32 sync,
struct list_head completed[HOST1X_INTR_ACTION_COUNT])
{
struct list_head *dest;
- struct host1x_waitlist *waiter, *next;
+ struct host1x_waitlist *waiter, *next, *prev;

list_for_each_entry_safe(waiter, next, head, list) {
if ((s32)(waiter->thresh - sync) > 0)
@@ -74,6 +76,17 @@ static void remove_completed_waiters(struct list_head *head, u32 sync,

dest = completed + waiter->action;

+ /* consolidate submit cleanups */
+ if (waiter->action == HOST1X_INTR_ACTION_SUBMIT_COMPLETE &&
+ !list_empty(dest)) {
+ prev = list_entry(dest->prev,
+ struct host1x_waitlist, list);
+ if (prev->data == waiter->data) {
+ prev->count++;
+ dest = NULL;
+ }
+ }
+
/* PENDING->REMOVED or CANCELLED->HANDLED */
if (atomic_inc_return(&waiter->state) == WLS_HANDLED || !dest) {
list_del(&waiter->list);
@@ -94,6 +107,18 @@ static void reset_threshold_interrupt(struct host1x *host,
host1x_hw_intr_enable_syncpt_intr(host, id);
}

+static void action_submit_complete(struct host1x_waitlist *waiter)
+{
+ struct host1x_channel *channel = waiter->data;
+
+ host1x_cdma_update(&channel->cdma);
+
+ /* Add nr_completed to trace */
+ trace_host1x_channel_submit_complete(dev_name(channel->dev),
+ waiter->count, waiter->thresh);
+
+}
+
static void action_wakeup(struct host1x_waitlist *waiter)
{
wait_queue_head_t *wq = waiter->data;
@@ -109,6 +134,7 @@ static void action_wakeup_interruptible(struct host1x_waitlist *waiter)
typedef void (*action_handler)(struct host1x_waitlist *waiter);

static action_handler action_handlers[HOST1X_INTR_ACTION_COUNT] = {
+ action_submit_complete,
action_wakeup,
action_wakeup_interruptible,
};
diff --git a/drivers/gpu/host1x/intr.h b/drivers/gpu/host1x/intr.h
index a3f06ab..2b8adf0 100644
--- a/drivers/gpu/host1x/intr.h
+++ b/drivers/gpu/host1x/intr.h
@@ -26,6 +26,12 @@ struct host1x;

enum host1x_intr_action {
/*
+ * Perform cleanup after a submit has completed.
+ * 'data' points to a channel
+ */
+ HOST1X_INTR_ACTION_SUBMIT_COMPLETE = 0,
+
+ /*
* Wake up a task.
* 'data' points to a wait_queue_head_t
*/
diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c
new file mode 100644
index 0000000..87f8860
--- /dev/null
+++ b/drivers/gpu/host1x/job.c
@@ -0,0 +1,597 @@
+/*
+ * Tegra host1x Job
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/kref.h>
+#include <linux/module.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <trace/events/host1x.h>
+
+#include "channel.h"
+#include "dev.h"
+#include "host1x_bo.h"
+#include "job.h"
+#include "syncpt.h"
+
+struct host1x_job *host1x_job_alloc(struct host1x_channel *ch,
+ u32 num_cmdbufs, u32 num_relocs,
+ u32 num_waitchks)
+{
+ struct host1x_job *job = NULL;
+ unsigned int num_unpins = num_cmdbufs + num_relocs;
+ u64 total;
+ void *mem;
+
+ /* Check that we're not going to overflow */
+ total = sizeof(struct host1x_job) +
+ num_relocs * sizeof(struct host1x_reloc) +
+ num_unpins * sizeof(struct host1x_job_unpin_data) +
+ num_waitchks * sizeof(struct host1x_waitchk) +
+ num_cmdbufs * sizeof(struct host1x_job_gather) +
+ num_unpins * sizeof(dma_addr_t) +
+ num_unpins * sizeof(u32 *);
+ if (total > ULONG_MAX)
+ return NULL;
+
+ mem = job = kzalloc(total, GFP_KERNEL);
+ if (!job)
+ return NULL;
+
+ kref_init(&job->ref);
+ job->channel = ch;
+
+ /* Redistribute memory to the structs */
+ mem += sizeof(struct host1x_job);
+ job->relocarray = num_relocs ? mem : NULL;
+ mem += num_relocs * sizeof(struct host1x_reloc);
+ job->unpins = num_unpins ? mem : NULL;
+ mem += num_unpins * sizeof(struct host1x_job_unpin_data);
+ job->waitchk = num_waitchks ? mem : NULL;
+ mem += num_waitchks * sizeof(struct host1x_waitchk);
+ job->gathers = num_cmdbufs ? mem : NULL;
+ mem += num_cmdbufs * sizeof(struct host1x_job_gather);
+ job->addr_phys = num_unpins ? mem : NULL;
+
+ job->reloc_addr_phys = job->addr_phys;
+ job->gather_addr_phys = &job->addr_phys[num_relocs];
+
+ return job;
+}
+
+struct host1x_job *host1x_job_get(struct host1x_job *job)
+{
+ kref_get(&job->ref);
+ return job;
+}
+
+static void job_free(struct kref *ref)
+{
+ struct host1x_job *job = container_of(ref, struct host1x_job, ref);
+
+ kfree(job);
+}
+
+void host1x_job_put(struct host1x_job *job)
+{
+ kref_put(&job->ref, job_free);
+}
+
+void host1x_job_add_gather(struct host1x_job *job,
+ struct host1x_bo *mem_id, u32 words,
+ u32 offset)
+{
+ struct host1x_job_gather *cur_gather =
+ &job->gathers[job->num_gathers];
+
+ cur_gather->words = words;
+ cur_gather->mem_id = mem_id;
+ cur_gather->offset = offset;
+ job->num_gathers++;
+}
+
+/*
+ * NULL an already satisfied WAIT_SYNCPT host method, by patching its
+ * args in the command stream. The method data is changed to reference
+ * a reserved (never given out or incr) HOST1X_SYNCPT_RESERVED syncpt
+ * with a matching threshold value of 0, so is guaranteed to be popped
+ * by the host HW.
+ */
+static void host1x_syncpt_patch_offset(struct host1x_syncpt *sp,
+ struct host1x_bo *h, u32 offset)
+{
+ void *patch_addr = NULL;
+
+ /* patch the wait */
+ patch_addr = host1x_bo_kmap(h, offset >> PAGE_SHIFT);
+ if (patch_addr) {
+ host1x_syncpt_patch_wait(sp,
+ patch_addr + (offset & ~PAGE_MASK));
+ host1x_bo_kunmap(h, offset >> PAGE_SHIFT, patch_addr);
+ } else
+ pr_err("Could not map cmdbuf for wait check\n");
+}
+
+/*
+ * Check driver supplied waitchk structs for syncpt thresholds
+ * that have already been satisfied and NULL the comparison (to
+ * avoid a wrap condition in the HW).
+ */
+static int do_waitchks(struct host1x_job *job, struct host1x *host,
+ struct host1x_bo *patch_mem)
+{
+ int i;
+
+ /* compare syncpt vs wait threshold */
+ for (i = 0; i < job->num_waitchk; i++) {
+ struct host1x_waitchk *wait = &job->waitchk[i];
+ struct host1x_syncpt *sp =
+ host1x_syncpt_get(host, wait->syncpt_id);
+
+ /* validate syncpt id */
+ if (wait->syncpt_id > host1x_syncpt_nb_pts(host))
+ continue;
+
+ /* skip all other gathers */
+ if (patch_mem != wait->mem)
+ continue;
+
+ trace_host1x_syncpt_wait_check(wait->mem, wait->offset,
+ wait->syncpt_id, wait->thresh,
+ host1x_syncpt_read_min(sp));
+
+ if (host1x_syncpt_is_expired(sp, wait->thresh)) {
+ dev_dbg(host->dev,
+ "drop WAIT id %d (%s) thresh 0x%x, min 0x%x\n",
+ wait->syncpt_id, sp->name, wait->thresh,
+ host1x_syncpt_read_min(sp));
+
+ host1x_syncpt_patch_offset(sp, patch_mem, wait->offset);
+ }
+
+ wait->mem = 0;
+ }
+ return 0;
+}
+
+static unsigned int pin_job_mem(struct host1x_job *job)
+{
+ unsigned int i;
+
+ job->num_unpins = 0;
+
+ for (i = 0; i < job->num_relocs; i++) {
+ struct host1x_reloc *reloc = &job->relocarray[i];
+ struct sg_table *sgt;
+ dma_addr_t phys_addr;
+
+ reloc->target = host1x_bo_get(reloc->target);
+ if (!reloc->target)
+ goto unpin;
+ phys_addr = host1x_bo_pin(reloc->target, &sgt);
+ if (!phys_addr)
+ goto unpin;
+
+ job->addr_phys[job->num_unpins] = phys_addr;
+ job->unpins[job->num_unpins].mem = sgt;
+ job->unpins[job->num_unpins].h = reloc->target;
+ job->num_unpins++;
+ }
+
+ for (i = 0; i < job->num_gathers; i++) {
+ struct host1x_job_gather *g = &job->gathers[i];
+ struct sg_table *sgt;
+ dma_addr_t phys_addr;
+
+ g->mem_id = host1x_bo_get(g->mem_id);
+ if (!g->mem_id)
+ goto unpin;
+ phys_addr = host1x_bo_pin(g->mem_id, &sgt);
+ if (!phys_addr)
+ goto unpin;
+
+ job->addr_phys[job->num_unpins] = phys_addr;
+ job->unpins[job->num_unpins].mem = sgt;
+ job->unpins[job->num_unpins].h = g->mem_id;
+ job->num_unpins++;
+ }
+
+ return job->num_unpins;
+
+unpin:
+ host1x_job_unpin(job);
+ return 0;
+}
+
+static unsigned int do_relocs(struct host1x_job *job,
+ struct host1x_bo *cmdbuf_mem)
+{
+ int i = 0;
+ u32 last_page = ~0;
+ void *cmdbuf_page_addr = NULL;
+
+ /* pin & patch the relocs for one gather */
+ while (i < job->num_relocs) {
+ struct host1x_reloc *reloc = &job->relocarray[i];
+ u32 reloc_addr = (job->reloc_addr_phys[i] +
+ reloc->target_offset) >> reloc->shift;
+ u32 *target;
+
+ /* skip all other gathers */
+ if (!(reloc->cmdbuf_mem && cmdbuf_mem == reloc->cmdbuf_mem)) {
+ i++;
+ continue;
+ }
+
+ if (last_page != reloc->cmdbuf_offset >> PAGE_SHIFT) {
+ if (cmdbuf_page_addr)
+ host1x_bo_kunmap(cmdbuf_mem, last_page,
+ cmdbuf_page_addr);
+
+ cmdbuf_page_addr = host1x_bo_kmap(cmdbuf_mem,
+ reloc->cmdbuf_offset >> PAGE_SHIFT);
+ last_page = reloc->cmdbuf_offset >> PAGE_SHIFT;
+
+ if (unlikely(!cmdbuf_page_addr)) {
+ pr_err("Could not map cmdbuf for relocation\n");
+ return -ENOMEM;
+ }
+ }
+
+ target = cmdbuf_page_addr + (reloc->cmdbuf_offset & ~PAGE_MASK);
+ *target = reloc_addr;
+
+ /* mark this gather as handled */
+ reloc->cmdbuf_mem = 0;
+ }
+
+ if (cmdbuf_page_addr)
+ host1x_bo_kunmap(cmdbuf_mem, last_page, cmdbuf_page_addr);
+
+ return 0;
+}
+
+static int check_reloc(struct host1x_reloc *reloc, struct host1x_bo *cmdbuf_id,
+ unsigned int offset)
+{
+ offset *= sizeof(u32);
+
+ if (reloc->cmdbuf_mem != cmdbuf_id || reloc->cmdbuf_offset != offset)
+ return -EINVAL;
+
+ return 0;
+}
+
+struct host1x_firewall {
+ struct host1x_job *job;
+ struct device *dev;
+
+ unsigned int num_relocs;
+ struct host1x_reloc *reloc;
+
+ struct host1x_bo *cmdbuf_id;
+ unsigned int offset;
+
+ u32 words;
+ u32 class;
+ u32 reg;
+ u32 mask;
+ u32 count;
+};
+
+static int check_mask(struct host1x_firewall *fw)
+{
+ u32 mask = fw->mask;
+ u32 reg = fw->reg;
+
+ while (mask) {
+ if (fw->words == 0)
+ return -EINVAL;
+
+ if (mask & 1) {
+ if (fw->job->is_addr_reg(fw->dev, fw->class, reg)) {
+ bool bad_reloc = check_reloc(fw->reloc,
+ fw->cmdbuf_id,
+ fw->offset);
+ if (!fw->num_relocs || bad_reloc)
+ return -EINVAL;
+ fw->reloc++;
+ fw->num_relocs--;
+ }
+ fw->words--;
+ fw->offset++;
+ }
+ mask >>= 1;
+ reg++;
+ }
+
+ return 0;
+}
+
+static int check_incr(struct host1x_firewall *fw)
+{
+ u32 count = fw->count;
+ u32 reg = fw->reg;
+
+ while (fw) {
+ if (fw->words == 0)
+ return -EINVAL;
+
+ if (fw->job->is_addr_reg(fw->dev, fw->class, reg)) {
+ bool bad_reloc = check_reloc(fw->reloc, fw->cmdbuf_id,
+ fw->offset);
+ if (!fw->num_relocs || bad_reloc)
+ return -EINVAL;
+ fw->reloc++;
+ fw->num_relocs--;
+ }
+ reg++;
+ fw->words--;
+ fw->offset++;
+ count--;
+ }
+
+ return 0;
+}
+
+static int check_nonincr(struct host1x_firewall *fw)
+{
+ int is_addr_reg = fw->job->is_addr_reg(fw->dev, fw->class, fw->reg);
+ u32 count = fw->count;
+
+ while (count) {
+ if (fw->words == 0)
+ return -EINVAL;
+
+ if (is_addr_reg) {
+ bool bad_reloc = check_reloc(fw->reloc, fw->cmdbuf_id,
+ fw->offset);
+ if (!fw->num_relocs || bad_reloc)
+ return -EINVAL;
+ fw->reloc++;
+ fw->num_relocs--;
+ }
+ fw->words--;
+ fw->offset++;
+ count--;
+ }
+
+ return 0;
+}
+
+static int validate(struct host1x_job *job, struct device *dev,
+ struct host1x_job_gather *g)
+{
+ u32 *cmdbuf_base;
+ int err = 0;
+ struct host1x_firewall fw;
+
+ fw.job = job;
+ fw.dev = dev;
+ fw.reloc = job->relocarray;
+ fw.num_relocs = job->num_relocs;
+
+ fw.offset = 0;
+ fw.class = 0;
+
+ if (!job->is_addr_reg)
+ return 0;
+
+ cmdbuf_base = host1x_bo_mmap(g->mem_id);
+ if (!cmdbuf_base)
+ return -ENOMEM;
+
+ fw.words = g->words;
+ while (fw.words && !err) {
+ u32 word = cmdbuf_base[fw.offset];
+ u32 opcode = (word & 0xf0000000) >> 28;
+
+ fw.mask = 0;
+ fw.reg = 0;
+ fw.count = 0;
+ fw.words--;
+ fw.offset++;
+
+ switch (opcode) {
+ case 0:
+ fw.class = word >> 6 & 0x3ff;
+ fw.mask = word & 0x3f;
+ fw.reg = word >> 16 & 0xfff;
+ err = check_mask(&fw);
+ if (err)
+ goto out;
+ break;
+ case 1:
+ fw.reg = word >> 16 & 0xfff;
+ fw.count = word & 0xffff;
+ err = check_incr(&fw);
+ if (err)
+ goto out;
+ break;
+
+ case 2:
+ fw.reg = word >> 16 & 0xfff;
+ fw.count = word & 0xffff;
+ err = check_nonincr(&fw);
+ if (err)
+ goto out;
+ break;
+
+ case 3:
+ fw.mask = word & 0xffff;
+ fw.reg = word >> 16 & 0xfff;
+ err = check_mask(&fw);
+ if (err)
+ goto out;
+ break;
+ case 4:
+ case 5:
+ case 14:
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ }
+
+ /* No relocs should remain at this point */
+ if (fw.num_relocs)
+ err = -EINVAL;
+
+out:
+ host1x_bo_munmap(g->mem_id, cmdbuf_base);
+
+ return err;
+}
+
+static inline int copy_gathers(struct host1x_job *job, struct device *dev)
+{
+ size_t size = 0;
+ size_t offset = 0;
+ int i;
+
+ for (i = 0; i < job->num_gathers; i++) {
+ struct host1x_job_gather *g = &job->gathers[i];
+ size += g->words * sizeof(u32);
+ }
+
+ job->gather_copy_mapped = dma_alloc_writecombine(dev, size,
+ &job->gather_copy,
+ GFP_KERNEL);
+ if (!job->gather_copy_mapped) {
+ int err = PTR_ERR(job->gather_copy_mapped);
+ job->gather_copy_mapped = NULL;
+ return err;
+ }
+
+ job->gather_copy_size = size;
+
+ for (i = 0; i < job->num_gathers; i++) {
+ struct host1x_job_gather *g = &job->gathers[i];
+ void *gather;
+
+ gather = host1x_bo_mmap(g->mem_id);
+ memcpy(job->gather_copy_mapped + offset, gather + g->offset,
+ g->words * sizeof(u32));
+ host1x_bo_munmap(g->mem_id, gather);
+
+ g->mem_base = job->gather_copy;
+ g->offset = offset;
+ g->mem_id = 0;
+
+ offset += g->words * sizeof(u32);
+ }
+
+ return 0;
+}
+
+int host1x_job_pin(struct host1x_job *job, struct device *dev)
+{
+ int err;
+ unsigned int i, j;
+ struct host1x *host = host1x_get_host(dev);
+ DECLARE_BITMAP(waitchk_mask, host1x_syncpt_nb_pts(host));
+
+ bitmap_zero(waitchk_mask, host1x_syncpt_nb_pts(host));
+ for (i = 0; i < job->num_waitchk; i++) {
+ u32 syncpt_id = job->waitchk[i].syncpt_id;
+ if (syncpt_id < host1x_syncpt_nb_pts(host))
+ set_bit(syncpt_id, waitchk_mask);
+ }
+
+ /* get current syncpt values for waitchk */
+ for_each_set_bit(i, waitchk_mask, host1x_syncpt_nb_pts(host))
+ host1x_syncpt_load(host->syncpt + i);
+
+ /* pin memory */
+ err = pin_job_mem(job);
+ if (!err)
+ goto out;
+
+ /* patch gathers */
+ for (i = 0; i < job->num_gathers; i++) {
+ struct host1x_job_gather *g = &job->gathers[i];
+
+ /* process each gather mem only once */
+ if (g->handled)
+ continue;
+
+ g->mem_base = job->gather_addr_phys[i];
+
+ for (j = 0; j < job->num_gathers; j++)
+ if (job->gathers[j].mem_id == g->mem_id)
+ job->gathers[j].handled = true;
+
+ err = 0;
+ if (IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL))
+ err = validate(job, dev, g);
+ if (err)
+ dev_err(dev, "Job invalid (err=%d)\n", err);
+ if (!err)
+ err = do_relocs(job, g->mem_id);
+ if (!err)
+ err = do_waitchks(job, host, g->mem_id);
+ if (err)
+ break;
+ }
+
+ if (IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL) && !err) {
+ err = copy_gathers(job, dev);
+ if (err) {
+ host1x_job_unpin(job);
+ return err;
+ }
+ }
+
+out:
+ wmb();
+
+ return err;
+}
+
+void host1x_job_unpin(struct host1x_job *job)
+{
+ unsigned int i;
+
+ for (i = 0; i < job->num_unpins; i++) {
+ struct host1x_job_unpin_data *unpin = &job->unpins[i];
+ host1x_bo_unpin(unpin->h, unpin->mem);
+ host1x_bo_put(unpin->h);
+ }
+ job->num_unpins = 0;
+
+ if (job->gather_copy_size)
+ dma_free_writecombine(job->channel->dev, job->gather_copy_size,
+ job->gather_copy_mapped,
+ job->gather_copy);
+}
+
+/*
+ * Debug routine used to dump job entries
+ */
+void host1x_job_dump(struct device *dev, struct host1x_job *job)
+{
+ dev_dbg(dev, " SYNCPT_ID %d\n", job->syncpt_id);
+ dev_dbg(dev, " SYNCPT_VAL %d\n", job->syncpt_end);
+ dev_dbg(dev, " FIRST_GET 0x%x\n", job->first_get);
+ dev_dbg(dev, " TIMEOUT %d\n", job->timeout);
+ dev_dbg(dev, " NUM_SLOTS %d\n", job->num_slots);
+ dev_dbg(dev, " NUM_HANDLES %d\n", job->num_unpins);
+}
diff --git a/drivers/gpu/host1x/job.h b/drivers/gpu/host1x/job.h
new file mode 100644
index 0000000..bbba762
--- /dev/null
+++ b/drivers/gpu/host1x/job.h
@@ -0,0 +1,157 @@
+/*
+ * Tegra host1x Job
+ *
+ * Copyright (c) 2011-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HOST1X_JOB_H
+#define __HOST1X_JOB_H
+
+struct host1x_job_gather {
+ u32 words;
+ dma_addr_t mem_base;
+ struct host1x_bo *mem_id;
+ int offset;
+ bool handled;
+};
+
+struct host1x_cmdbuf {
+ u32 mem;
+ u32 offset;
+ u32 words;
+ u32 pad;
+};
+
+struct host1x_reloc {
+ struct host1x_bo *cmdbuf_mem;
+ u32 cmdbuf_offset;
+ struct host1x_bo *target;
+ u32 target_offset;
+ u32 shift;
+ u32 pad;
+};
+
+struct host1x_waitchk {
+ struct host1x_bo *mem;
+ u32 offset;
+ u32 syncpt_id;
+ u32 thresh;
+};
+
+/*
+ * Each submit is tracked as a host1x_job.
+ */
+struct host1x_job {
+ /* When refcount goes to zero, job can be freed */
+ struct kref ref;
+
+ /* List entry */
+ struct list_head list;
+
+ /* Channel where job is submitted to */
+ struct host1x_channel *channel;
+
+ u32 client;
+
+ /* Gathers and their memory */
+ struct host1x_job_gather *gathers;
+ unsigned int num_gathers;
+
+ /* Wait checks to be processed at submit time */
+ struct host1x_waitchk *waitchk;
+ unsigned int num_waitchk;
+ u32 waitchk_mask;
+
+ /* Array of handles to be pinned & unpinned */
+ struct host1x_reloc *relocarray;
+ unsigned int num_relocs;
+ struct host1x_job_unpin_data *unpins;
+ unsigned int num_unpins;
+
+ dma_addr_t *addr_phys;
+ dma_addr_t *gather_addr_phys;
+ dma_addr_t *reloc_addr_phys;
+
+ /* Sync point id, number of increments and end related to the submit */
+ u32 syncpt_id;
+ u32 syncpt_incrs;
+ u32 syncpt_end;
+
+ /* Maximum time to wait for this job */
+ unsigned int timeout;
+
+ /* Index and number of slots used in the push buffer */
+ unsigned int first_get;
+ unsigned int num_slots;
+
+ /* Copy of gathers */
+ size_t gather_copy_size;
+ dma_addr_t gather_copy;
+ u8 *gather_copy_mapped;
+
+ /* Check if register is marked as an address reg */
+ int (*is_addr_reg)(struct device *dev, u32 reg, u32 class);
+
+ /* Request a SETCLASS to this class */
+ u32 class;
+
+ /* Add a channel wait for previous ops to complete */
+ bool serialize;
+};
+/*
+ * Allocate memory for a job. Just enough memory will be allocated to
+ * accomodate the submit.
+ */
+struct host1x_job *host1x_job_alloc(struct host1x_channel *ch,
+ u32 num_cmdbufs, u32 num_relocs,
+ u32 num_waitchks);
+
+/*
+ * Add a gather to a job.
+ */
+void host1x_job_add_gather(struct host1x_job *job, struct host1x_bo *mem_id,
+ u32 words, u32 offset);
+
+/*
+ * Increment reference going to host1x_job.
+ */
+struct host1x_job *host1x_job_get(struct host1x_job *job);
+
+/*
+ * Decrement reference job, free if goes to zero.
+ */
+void host1x_job_put(struct host1x_job *job);
+
+/*
+ * Pin memory related to job. This handles relocation of addresses to the
+ * host1x address space. Handles both the gather memory and any other memory
+ * referred to from the gather buffers.
+ *
+ * Handles also patching out host waits that would wait for an expired sync
+ * point value.
+ */
+int host1x_job_pin(struct host1x_job *job, struct device *dev);
+
+/*
+ * Unpin memory related to job.
+ */
+void host1x_job_unpin(struct host1x_job *job);
+
+/*
+ * Dump contents of job to debug output.
+ */
+void host1x_job_dump(struct device *dev, struct host1x_job *job);
+
+#endif
diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c
index a8613eb..ac172dc 100644
--- a/drivers/gpu/host1x/syncpt.c
+++ b/drivers/gpu/host1x/syncpt.c
@@ -300,6 +300,12 @@ bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh)
return (s32)(current_val - thresh) >= 0;
}

+/* remove a wait pointed to by patch_addr */
+int host1x_syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr)
+{
+ return host1x_hw_syncpt_patch_wait(sp->host, sp, patch_addr);
+}
+
int host1x_syncpt_init(struct host1x *host)
{
struct host1x_syncpt *syncpt;
@@ -319,6 +325,11 @@ int host1x_syncpt_init(struct host1x *host)

host1x_syncpt_restore(host);

+ /* Allocate sync point to use for clearing waits for expired fences */
+ host->nop_sp = _host1x_syncpt_alloc(host, NULL, 0);
+ if (!host->nop_sp)
+ return -ENOMEM;
+
return 0;
}

diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h
index 17c1616..c998061 100644
--- a/drivers/gpu/host1x/syncpt.h
+++ b/drivers/gpu/host1x/syncpt.h
@@ -27,6 +27,9 @@

struct host1x;

+/* Reserved for replacing an expired wait with a NOP */
+#define HOST1X_SYNCPT_RESERVED 0
+
struct host1x_syncpt {
int id;
atomic_t min_val;
@@ -146,6 +149,9 @@ static inline int host1x_syncpt_is_valid(struct host1x_syncpt *sp)
return sp->id < host1x_syncpt_nb_pts(sp->host);
}

+/* Patch a wait by replacing it with a wait for syncpt 0 value 0 */
+int host1x_syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr);
+
/* Return id of the sync point */
u32 host1x_syncpt_id(struct host1x_syncpt *sp);

diff --git a/include/trace/events/host1x.h b/include/trace/events/host1x.h
index 4bd2b02..0b3944d 100644
--- a/include/trace/events/host1x.h
+++ b/include/trace/events/host1x.h
@@ -37,6 +37,190 @@ DECLARE_EVENT_CLASS(host1x,
TP_printk("name=%s", __entry->name)
);

+DEFINE_EVENT(host1x, host1x_channel_open,
+ TP_PROTO(const char *name),
+ TP_ARGS(name)
+);
+
+DEFINE_EVENT(host1x, host1x_channel_release,
+ TP_PROTO(const char *name),
+ TP_ARGS(name)
+);
+
+DEFINE_EVENT(host1x, host1x_cdma_begin,
+ TP_PROTO(const char *name),
+ TP_ARGS(name)
+);
+
+DEFINE_EVENT(host1x, host1x_cdma_end,
+ TP_PROTO(const char *name),
+ TP_ARGS(name)
+);
+
+TRACE_EVENT(host1x,
+ TP_PROTO(const char *name, int timeout),
+
+ TP_ARGS(name, timeout),
+
+ TP_STRUCT__entry(
+ __field(const char *, name)
+ __field(int, timeout)
+ ),
+
+ TP_fast_assign(
+ __entry->name = name;
+ __entry->timeout = timeout;
+ ),
+
+ TP_printk("name=%s, timeout=%d",
+ __entry->name, __entry->timeout)
+);
+
+TRACE_EVENT(host1x_cdma_push,
+ TP_PROTO(const char *name, u32 op1, u32 op2),
+
+ TP_ARGS(name, op1, op2),
+
+ TP_STRUCT__entry(
+ __field(const char *, name)
+ __field(u32, op1)
+ __field(u32, op2)
+ ),
+
+ TP_fast_assign(
+ __entry->name = name;
+ __entry->op1 = op1;
+ __entry->op2 = op2;
+ ),
+
+ TP_printk("name=%s, op1=%08x, op2=%08x",
+ __entry->name, __entry->op1, __entry->op2)
+);
+
+TRACE_EVENT(host1x_cdma_push_gather,
+ TP_PROTO(const char *name, u32 mem_id,
+ u32 words, u32 offset, void *cmdbuf),
+
+ TP_ARGS(name, mem_id, words, offset, cmdbuf),
+
+ TP_STRUCT__entry(
+ __field(const char *, name)
+ __field(u32, mem_id)
+ __field(u32, words)
+ __field(u32, offset)
+ __field(bool, cmdbuf)
+ __dynamic_array(u32, cmdbuf, words)
+ ),
+
+ TP_fast_assign(
+ if (cmdbuf) {
+ memcpy(__get_dynamic_array(cmdbuf), cmdbuf+offset,
+ words * sizeof(u32));
+ }
+ __entry->cmdbuf = cmdbuf;
+ __entry->name = name;
+ __entry->mem_id = mem_id;
+ __entry->words = words;
+ __entry->offset = offset;
+ ),
+
+ TP_printk("name=%s, mem_id=%08x, words=%u, offset=%d, contents=[%s]",
+ __entry->name, __entry->mem_id,
+ __entry->words, __entry->offset,
+ __print_hex(__get_dynamic_array(cmdbuf),
+ __entry->cmdbuf ? __entry->words * 4 : 0))
+);
+
+TRACE_EVENT(host1x_channel_submit,
+ TP_PROTO(const char *name, u32 cmdbufs, u32 relocs, u32 waitchks,
+ u32 syncpt_id, u32 syncpt_incrs),
+
+ TP_ARGS(name, cmdbufs, relocs, waitchks, syncpt_id, syncpt_incrs),
+
+ TP_STRUCT__entry(
+ __field(const char *, name)
+ __field(u32, cmdbufs)
+ __field(u32, relocs)
+ __field(u32, waitchks)
+ __field(u32, syncpt_id)
+ __field(u32, syncpt_incrs)
+ ),
+
+ TP_fast_assign(
+ __entry->name = name;
+ __entry->cmdbufs = cmdbufs;
+ __entry->relocs = relocs;
+ __entry->waitchks = waitchks;
+ __entry->syncpt_id = syncpt_id;
+ __entry->syncpt_incrs = syncpt_incrs;
+ ),
+
+ TP_printk("name=%s, cmdbufs=%u, relocs=%u, waitchks=%d,"
+ "syncpt_id=%u, syncpt_incrs=%u",
+ __entry->name, __entry->cmdbufs, __entry->relocs, __entry->waitchks,
+ __entry->syncpt_id, __entry->syncpt_incrs)
+);
+
+TRACE_EVENT(host1x_channel_submitted,
+ TP_PROTO(const char *name, u32 syncpt_base, u32 syncpt_max),
+
+ TP_ARGS(name, syncpt_base, syncpt_max),
+
+ TP_STRUCT__entry(
+ __field(const char *, name)
+ __field(u32, syncpt_base)
+ __field(u32, syncpt_max)
+ ),
+
+ TP_fast_assign(
+ __entry->name = name;
+ __entry->syncpt_base = syncpt_base;
+ __entry->syncpt_max = syncpt_max;
+ ),
+
+ TP_printk("name=%s, syncpt_base=%d, syncpt_max=%d",
+ __entry->name, __entry->syncpt_base, __entry->syncpt_max)
+);
+
+TRACE_EVENT(host1x_channel_submit_complete,
+ TP_PROTO(const char *name, int count, u32 thresh),
+
+ TP_ARGS(name, count, thresh),
+
+ TP_STRUCT__entry(
+ __field(const char *, name)
+ __field(int, count)
+ __field(u32, thresh)
+ ),
+
+ TP_fast_assign(
+ __entry->name = name;
+ __entry->count = count;
+ __entry->thresh = thresh;
+ ),
+
+ TP_printk("name=%s, count=%d, thresh=%d",
+ __entry->name, __entry->count, __entry->thresh)
+);
+
+TRACE_EVENT(host1x_wait_cdma,
+ TP_PROTO(const char *name, u32 eventid),
+
+ TP_ARGS(name, eventid),
+
+ TP_STRUCT__entry(
+ __field(const char *, name)
+ __field(u32, eventid)
+ ),
+
+ TP_fast_assign(
+ __entry->name = name;
+ __entry->eventid = eventid;
+ ),
+
+ TP_printk("name=%s, event=%d", __entry->name, __entry->eventid)
+);
+
TRACE_EVENT(host1x_syncpt_load_min,
TP_PROTO(u32 id, u32 val),

@@ -55,6 +239,33 @@ TRACE_EVENT(host1x_syncpt_load_min,
TP_printk("id=%d, val=%d", __entry->id, __entry->val)
);

+TRACE_EVENT(host1x_syncpt_wait_check,
+ TP_PROTO(void *mem_id, u32 offset, u32 syncpt_id, u32 thresh, u32 min),
+
+ TP_ARGS(mem_id, offset, syncpt_id, thresh, min),
+
+ TP_STRUCT__entry(
+ __field(void *, mem_id)
+ __field(u32, offset)
+ __field(u32, syncpt_id)
+ __field(u32, thresh)
+ __field(u32, min)
+ ),
+
+ TP_fast_assign(
+ __entry->mem_id = mem_id;
+ __entry->offset = offset;
+ __entry->syncpt_id = syncpt_id;
+ __entry->thresh = thresh;
+ __entry->min = min;
+ ),
+
+ TP_printk("mem_id=%p, offset=%05x, id=%d, thresh=%d, current=%d",
+ __entry->mem_id, __entry->offset,
+ __entry->syncpt_id, __entry->thresh,
+ __entry->min)
+);
+
#endif /* _TRACE_HOST1X_H */

/* This part must be outside protection */
--
1.7.9.5

2013-03-13 12:36:08

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 10/10] drm: tegra: Add gr2d device

Add client driver for 2D device, and IOCTLs to pass work to host1x
channel for 2D.

Also adds functions that can be called to access sync points from
DRM.

Signed-off-by: Arto Merilainen <[email protected]>
Signed-off-by: Terje Bergstrom <[email protected]>
---
drivers/gpu/host1x/Makefile | 1 +
drivers/gpu/host1x/dev.c | 7 +
drivers/gpu/host1x/drm/drm.c | 251 ++++++++++++++++++++++++++++++-
drivers/gpu/host1x/drm/drm.h | 28 +++-
drivers/gpu/host1x/drm/gr2d.c | 330 +++++++++++++++++++++++++++++++++++++++++
drivers/gpu/host1x/host1x.h | 3 +-
include/drm/tegra_drm.h | 131 ++++++++++++++++
7 files changed, 747 insertions(+), 4 deletions(-)
create mode 100644 drivers/gpu/host1x/drm/gr2d.c
create mode 100644 include/drm/tegra_drm.h

diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile
index e85db5a..29c0c6b 100644
--- a/drivers/gpu/host1x/Makefile
+++ b/drivers/gpu/host1x/Makefile
@@ -16,4 +16,5 @@ ccflags-$(CONFIG_DRM_TEGRA_DEBUG) += -DDEBUG
host1x-$(CONFIG_DRM_TEGRA) += drm/drm.o drm/fb.o drm/dc.o
host1x-$(CONFIG_DRM_TEGRA) += drm/output.o drm/rgb.o drm/hdmi.o
host1x-$(CONFIG_DRM_TEGRA) += drm/cma.o
+host1x-$(CONFIG_DRM_TEGRA) += drm/gr2d.o
obj-$(CONFIG_TEGRA_HOST1X) += host1x.o
diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index 6af8081..0091632 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -211,11 +211,17 @@ static int __init tegra_host1x_init(void)
err = platform_driver_register(&tegra_hdmi_driver);
if (err < 0)
goto unregister_dc;
+
+ err = platform_driver_register(&tegra_gr2d_driver);
+ if (err < 0)
+ goto unregister_hdmi;
#endif

return 0;

#ifdef CONFIG_DRM_TEGRA
+unregister_hdmi:
+ platform_driver_unregister(&tegra_hdmi_driver);
unregister_dc:
platform_driver_unregister(&tegra_dc_driver);
unregister_host1x:
@@ -228,6 +234,7 @@ module_init(tegra_host1x_init);
static void __exit tegra_host1x_exit(void)
{
#ifdef CONFIG_DRM_TEGRA
+ platform_driver_unregister(&tegra_gr2d_driver);
platform_driver_unregister(&tegra_hdmi_driver);
platform_driver_unregister(&tegra_dc_driver);
#endif
diff --git a/drivers/gpu/host1x/drm/drm.c b/drivers/gpu/host1x/drm/drm.c
index dbd4808..9f78f52 100644
--- a/drivers/gpu/host1x/drm/drm.c
+++ b/drivers/gpu/host1x/drm/drm.c
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2012 Avionic Design GmbH
- * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
+ * Copyright (C) 2012-2013 NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -8,13 +8,21 @@
*/

#include <asm/dma-iommu.h>
+#include <drm/drm.h>
+#include <drm/drmP.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/tegra_drm.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/dma-mapping.h>

+#include "cma.h"
+#include "dev.h"
#include "drm.h"
+#include "host1x_bo.h"
#include "host1x_client.h"
+#include "syncpt.h"

#define DRIVER_NAME "tegra"
#define DRIVER_DESC "NVIDIA Tegra graphics"
@@ -77,8 +85,10 @@ static int host1x_parse_dt(struct host1x_drm *host1x)
static const char * const compat[] = {
"nvidia,tegra20-dc",
"nvidia,tegra20-hdmi",
+ "nvidia,tegra20-gr2d",
"nvidia,tegra30-dc",
"nvidia,tegra30-hdmi",
+ "nvidia,tegra30-gr2d",
};
unsigned int i;
int err;
@@ -273,9 +283,24 @@ static int tegra_drm_unload(struct drm_device *drm)

static int tegra_drm_open(struct drm_device *drm, struct drm_file *filp)
{
+ struct host1x_drm_file *fpriv;
+
+ fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL);
+ if (!fpriv)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&fpriv->contexts);
+ filp->driver_priv = fpriv;
+
return 0;
}

+static void host1x_drm_context_free(struct host1x_drm_context *context)
+{
+ context->client->ops->close_channel(context);
+ kfree(context);
+}
+
static void tegra_drm_lastclose(struct drm_device *drm)
{
struct host1x_drm *host1x = drm->dev_private;
@@ -283,7 +308,222 @@ static void tegra_drm_lastclose(struct drm_device *drm)
drm_fbdev_cma_restore_mode(host1x->fbdev);
}

+static int tegra_drm_ioctl_syncpt_read(struct drm_device *drm, void *data,
+ struct drm_file *file_priv)
+{
+ struct tegra_drm_syncpt_read_args *args = data;
+ struct host1x *host = host1x_get_host(drm->dev);
+ struct host1x_syncpt *sp = host1x_syncpt_get(host, args->id);
+
+ if (!sp)
+ return -EINVAL;
+
+ args->value = host1x_syncpt_read_min(sp);
+ return 0;
+}
+
+static int tegra_drm_ioctl_syncpt_incr(struct drm_device *drm, void *data,
+ struct drm_file *file_priv)
+{
+ struct tegra_drm_syncpt_incr_args *args = data;
+ struct host1x *host = host1x_get_host(drm->dev);
+ struct host1x_syncpt *sp = host1x_syncpt_get(host, args->id);
+
+ if (!sp)
+ return -EINVAL;
+
+ host1x_syncpt_incr(sp);
+ return 0;
+}
+
+static int tegra_drm_ioctl_syncpt_wait(struct drm_device *drm, void *data,
+ struct drm_file *file_priv)
+{
+ struct tegra_drm_syncpt_wait_args *args = data;
+ struct host1x *host = host1x_get_host(drm->dev);
+ struct host1x_syncpt *sp = host1x_syncpt_get(host, args->id);
+
+ if (!sp)
+ return -EINVAL;
+
+ return host1x_syncpt_wait(sp, args->thresh, args->timeout,
+ &args->value);
+}
+
+static inline struct host1x_drm_context *tegra_drm_get_context(
+ struct list_head *contexts,
+ struct host1x_drm_context *_ctx)
+{
+ struct host1x_drm_context *ctx;
+
+ list_for_each_entry(ctx, contexts, list)
+ if (ctx == _ctx)
+ return ctx;
+ return NULL;
+}
+
+static int tegra_drm_ioctl_open_channel(struct drm_device *drm, void *data,
+ struct drm_file *file_priv)
+{
+ struct tegra_drm_open_channel_args *args = data;
+ struct host1x_client *client;
+ struct host1x_drm_context *context;
+ struct host1x_drm_file *fpriv = file_priv->driver_priv;
+ struct host1x_drm *host1x = drm->dev_private;
+ int err = -ENODEV;
+
+ context = kzalloc(sizeof(*context), GFP_KERNEL);
+ if (!context)
+ return -ENOMEM;
+
+ list_for_each_entry(client, &host1x->clients, list)
+ if (client->class == args->class) {
+ context->client = client;
+ err = client->ops->open_channel(client, context);
+ if (err)
+ break;
+
+ list_add(&context->list, &fpriv->contexts);
+ args->context = (uintptr_t)context;
+ return 0;
+ }
+
+ kfree(context);
+ return err;
+}
+
+static int tegra_drm_ioctl_close_channel(struct drm_device *drm, void *data,
+ struct drm_file *file_priv)
+{
+ struct tegra_drm_open_channel_args *args = data;
+ struct host1x_drm_file *fpriv = file_priv->driver_priv;
+ struct host1x_drm_context *context =
+ (struct host1x_drm_context *)(uintptr_t)args->context;
+
+ if (!tegra_drm_get_context(&fpriv->contexts, context))
+ return -EINVAL;
+
+ list_del(&context->list);
+ host1x_drm_context_free(context);
+
+ return 0;
+}
+
+static int tegra_drm_ioctl_get_syncpoint(struct drm_device *drm, void *data,
+ struct drm_file *file_priv)
+{
+ struct tegra_drm_get_syncpoint *args = data;
+ struct host1x_drm_file *fpriv = file_priv->driver_priv;
+ struct host1x_drm_context *context =
+ (struct host1x_drm_context *)(uintptr_t)args->context;
+
+ if (!tegra_drm_get_context(&fpriv->contexts, context))
+ return -ENODEV;
+
+ if (context->client->num_syncpts < args->param)
+ return -ENODEV;
+
+ args->value = host1x_syncpt_id(context->client->syncpts[args->param]);
+
+ return 0;
+}
+
+static int tegra_drm_ioctl_submit(struct drm_device *drm, void *data,
+ struct drm_file *file_priv)
+{
+ struct tegra_drm_submit_args *args = data;
+ struct host1x_drm_file *fpriv = file_priv->driver_priv;
+ struct host1x_drm_context *context =
+ (struct host1x_drm_context *)(uintptr_t)args->context;
+
+ if (!tegra_drm_get_context(&fpriv->contexts, context))
+ return -ENODEV;
+
+ return context->client->ops->submit(context, args, drm, file_priv);
+}
+
+static int tegra_drm_gem_create_ioctl(struct drm_device *drm, void *data,
+ struct drm_file *file_priv)
+{
+ struct tegra_drm_create *args = data;
+ struct drm_gem_cma_object *cma_obj;
+ struct tegra_drm_bo *cma_bo;
+ int ret;
+
+ cma_bo = kzalloc(sizeof(*cma_bo), GFP_KERNEL);
+ if (!cma_bo)
+ return -ENOMEM;
+
+ host1x_bo_init(&cma_bo->base, &tegra_drm_bo_ops);
+
+ cma_obj = &cma_bo->cma_obj;
+ ret = drm_gem_cma_object_init(drm, cma_obj, args->size);
+ if (ret)
+ goto err_cma_create;
+
+ ret = drm_gem_handle_create(file_priv, &cma_obj->base, &args->handle);
+ if (ret)
+ goto err_handle_create;
+
+ drm_gem_object_unreference(&cma_obj->base);
+
+ return 0;
+
+err_handle_create:
+ drm_gem_cma_free_object(&cma_obj->base);
+err_cma_create:
+ kfree(cma_bo);
+ return -ENOMEM;
+}
+
+static int tegra_drm_ioctl_get_offset(struct drm_device *drm, void *data,
+ struct drm_file *file_priv)
+{
+ struct tegra_drm_get_offset *args = data;
+ struct drm_gem_cma_object *cma_obj;
+ struct drm_gem_object *obj;
+
+ obj = drm_gem_object_lookup(drm, file_priv, args->handle);
+ if (!obj)
+ return -EINVAL;
+ cma_obj = to_drm_gem_cma_obj(obj);
+
+ args->offset = cma_obj->base.map_list.hash.key << PAGE_SHIFT;
+
+ drm_gem_object_unreference(&cma_obj->base);
+
+ return 0;
+}
+
+static void tegra_drm_gem_free_object(struct drm_gem_object *obj)
+{
+ struct drm_gem_cma_object *cma_obj = to_drm_gem_cma_obj(obj);
+ struct tegra_drm_bo *cma_bo =
+ container_of(cma_obj, struct tegra_drm_bo, cma_obj);
+
+ drm_gem_cma_object_deinit(obj);
+ kfree(cma_bo);
+}
+
static struct drm_ioctl_desc tegra_drm_ioctls[] = {
+ DRM_IOCTL_DEF_DRV(TEGRA_DRM_GEM_CREATE,
+ tegra_drm_gem_create_ioctl, DRM_UNLOCKED | DRM_AUTH),
+ DRM_IOCTL_DEF_DRV(TEGRA_DRM_SYNCPT_READ,
+ tegra_drm_ioctl_syncpt_read, DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(TEGRA_DRM_SYNCPT_INCR,
+ tegra_drm_ioctl_syncpt_incr, DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(TEGRA_DRM_SYNCPT_WAIT,
+ tegra_drm_ioctl_syncpt_wait, DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(TEGRA_DRM_OPEN_CHANNEL,
+ tegra_drm_ioctl_open_channel, DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(TEGRA_DRM_CLOSE_CHANNEL,
+ tegra_drm_ioctl_close_channel, DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(TEGRA_DRM_GET_SYNCPOINT,
+ tegra_drm_ioctl_get_syncpoint, DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(TEGRA_DRM_SUBMIT,
+ tegra_drm_ioctl_submit, DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(TEGRA_DRM_GEM_GET_OFFSET,
+ tegra_drm_ioctl_get_offset, DRM_UNLOCKED),
};

static const struct file_operations tegra_drm_fops = {
@@ -345,10 +585,17 @@ static void tegra_drm_disable_vblank(struct drm_device *drm, int pipe)

static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file)
{
+ struct host1x_drm_file *fpriv = file->driver_priv;
+ struct host1x_drm_context *context, *tmp;
struct drm_crtc *crtc;

list_for_each_entry(crtc, &drm->mode_config.crtc_list, head)
tegra_dc_cancel_page_flip(crtc, file);
+
+ list_for_each_entry_safe(context, tmp, &fpriv->contexts, list)
+ host1x_drm_context_free(context);
+ kfree(fpriv);
+
}

#ifdef CONFIG_DEBUG_FS
@@ -407,7 +654,7 @@ struct drm_driver tegra_drm_driver = {
.debugfs_cleanup = tegra_debugfs_cleanup,
#endif

- .gem_free_object = drm_gem_cma_free_object,
+ .gem_free_object = tegra_drm_gem_free_object,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.dumb_create = drm_gem_cma_dumb_create,
.dumb_map_offset = drm_gem_cma_dumb_map_offset,
diff --git a/drivers/gpu/host1x/drm/drm.h b/drivers/gpu/host1x/drm/drm.h
index 0b8738f..6a8d7e3 100644
--- a/drivers/gpu/host1x/drm/drm.h
+++ b/drivers/gpu/host1x/drm/drm.h
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2012 Avionic Design GmbH
- * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
+ * Copyright (C) 2012-2013 NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -17,6 +17,9 @@
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fixed.h>
+#include <drm/tegra_drm.h>
+
+#include "host1x.h"

struct host1x_drm {
struct drm_device *drm;
@@ -38,9 +41,26 @@ struct host1x_drm {

struct host1x_client;

+struct host1x_drm_context {
+ struct host1x_client *client;
+ struct host1x_channel *channel;
+ struct list_head list;
+};
+
struct host1x_client_ops {
int (*drm_init)(struct host1x_client *client, struct drm_device *drm);
int (*drm_exit)(struct host1x_client *client);
+ int (*open_channel)(struct host1x_client *client,
+ struct host1x_drm_context *context);
+ void (*close_channel)(struct host1x_drm_context *context);
+ int (*submit)(struct host1x_drm_context *context,
+ struct tegra_drm_submit_args *args,
+ struct drm_device *drm,
+ struct drm_file *file_priv);
+};
+
+struct host1x_drm_file {
+ struct list_head contexts;
};

struct host1x_client {
@@ -49,6 +69,12 @@ struct host1x_client {

const struct host1x_client_ops *ops;

+ enum host1x_class class;
+ struct host1x_channel *channel;
+
+ struct host1x_syncpt **syncpts;
+ unsigned int num_syncpts;
+
struct list_head list;
};

diff --git a/drivers/gpu/host1x/drm/gr2d.c b/drivers/gpu/host1x/drm/gr2d.c
new file mode 100644
index 0000000..f060069
--- /dev/null
+++ b/drivers/gpu/host1x/drm/gr2d.c
@@ -0,0 +1,330 @@
+/*
+ * drivers/video/tegra/host/gr2d/gr2d.c
+ *
+ * Tegra Graphics 2D
+ *
+ * Copyright (c) 2012-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/export.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/clk.h>
+#include <drm/tegra_drm.h>
+
+#include "channel.h"
+#include "drm.h"
+#include "drm/cma.h"
+#include "job.h"
+#include "host1x.h"
+#include "host1x_bo.h"
+#include "host1x_client.h"
+#include "syncpt.h"
+
+struct gr2d {
+ struct host1x_client client;
+ struct clk *clk;
+ struct host1x_channel *channel;
+ unsigned long *addr_regs;
+};
+
+static int gr2d_is_addr_reg(struct device *dev, u32 class, u32 reg);
+
+static int gr2d_client_init(struct host1x_client *client,
+ struct drm_device *drm)
+{
+ return 0;
+}
+
+static int gr2d_client_exit(struct host1x_client *client)
+{
+ return 0;
+}
+
+static int gr2d_open_channel(struct host1x_client *client,
+ struct host1x_drm_context *context)
+{
+ struct gr2d *gr2d = dev_get_drvdata(client->dev);
+ context->channel = host1x_channel_get(gr2d->channel);
+
+ if (!context->channel)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void gr2d_close_channel(struct host1x_drm_context *context)
+{
+ host1x_channel_put(context->channel);
+}
+
+static struct host1x_bo *handle_cma_to_host1x(struct drm_device *drm,
+ struct drm_file *file_priv,
+ u32 gem_handle)
+{
+ struct drm_gem_object *gem_obj;
+ struct drm_gem_cma_object *cma_obj;
+ struct tegra_drm_bo *cma_bo;
+
+ gem_obj = drm_gem_object_lookup(drm, file_priv, gem_handle);
+ if (!gem_obj)
+ return 0;
+
+ mutex_lock(&drm->struct_mutex);
+ drm_gem_object_unreference(gem_obj);
+ mutex_unlock(&drm->struct_mutex);
+
+ cma_obj = to_drm_gem_cma_obj(gem_obj);
+ cma_bo = container_of(cma_obj, struct tegra_drm_bo, cma_obj);
+
+ return &cma_bo->base;
+}
+
+static int gr2d_submit(struct host1x_drm_context *context,
+ struct tegra_drm_submit_args *args,
+ struct drm_device *drm,
+ struct drm_file *file_priv)
+{
+ struct host1x_job *job;
+ int num_cmdbufs = args->num_cmdbufs;
+ int num_relocs = args->num_relocs;
+ int num_waitchks = args->num_waitchks;
+ struct tegra_drm_cmdbuf __user *cmdbufs =
+ (void * __user)(uintptr_t)args->cmdbufs;
+ struct tegra_drm_reloc __user *relocs =
+ (void * __user)(uintptr_t)args->relocs;
+ struct tegra_drm_waitchk __user *waitchks =
+ (void * __user)(uintptr_t)args->waitchks;
+ struct tegra_drm_syncpt_incr syncpt_incr;
+ int err;
+
+ /* We don't yet support other than one syncpt_incr struct per submit */
+ if (args->num_syncpt_incrs != 1)
+ return -EINVAL;
+
+ job = host1x_job_alloc(context->channel, args->num_cmdbufs,
+ args->num_relocs, args->num_waitchks);
+ if (!job)
+ return -ENOMEM;
+
+ job->num_relocs = args->num_relocs;
+ job->num_waitchk = args->num_waitchks;
+ job->client = (u32)args->context;
+ job->class = context->client->class;
+ job->serialize = true;
+
+ while (num_cmdbufs) {
+ struct tegra_drm_cmdbuf cmdbuf;
+ struct host1x_bo *mem_handle;
+ err = copy_from_user(&cmdbuf, cmdbufs, sizeof(cmdbuf));
+ if (err)
+ goto fail;
+
+ mem_handle = handle_cma_to_host1x(drm, file_priv, cmdbuf.mem);
+ if (!mem_handle)
+ goto fail;
+
+ host1x_job_add_gather(job, mem_handle,
+ cmdbuf.words, cmdbuf.offset);
+ num_cmdbufs--;
+ cmdbufs++;
+ }
+
+ err = copy_from_user(job->relocarray, relocs,
+ sizeof(*relocs) * num_relocs);
+ if (err)
+ goto fail;
+
+ while (num_relocs--) {
+ job->relocarray[num_relocs].cmdbuf_mem =
+ handle_cma_to_host1x(drm, file_priv,
+ (u32)job->relocarray[num_relocs].cmdbuf_mem);
+ job->relocarray[num_relocs].target =
+ handle_cma_to_host1x(drm, file_priv,
+ (u32)job->relocarray[num_relocs].target);
+
+ if (!job->relocarray[num_relocs].target ||
+ !job->relocarray[num_relocs].cmdbuf_mem)
+ goto fail;
+ }
+
+ err = copy_from_user(job->waitchk, waitchks,
+ sizeof(*waitchks) * num_waitchks);
+ if (err)
+ goto fail;
+
+ err = host1x_job_pin(job, context->client->dev);
+ if (err)
+ goto fail;
+
+ err = copy_from_user(&syncpt_incr,
+ (void * __user)(uintptr_t)args->syncpt_incrs,
+ sizeof(syncpt_incr));
+ if (err)
+ goto fail;
+
+ job->syncpt_id = syncpt_incr.id;
+ job->syncpt_incrs = syncpt_incr.incrs;
+ job->timeout = 10000;
+ job->is_addr_reg = gr2d_is_addr_reg;
+ if (args->timeout && args->timeout < 10000)
+ job->timeout = args->timeout;
+
+ err = host1x_job_submit(job);
+ if (err)
+ goto fail_submit;
+
+ args->fence = job->syncpt_end;
+
+ host1x_job_put(job);
+ return 0;
+
+fail_submit:
+ host1x_job_unpin(job);
+fail:
+ host1x_job_put(job);
+ return err;
+}
+
+static struct host1x_client_ops gr2d_client_ops = {
+ .drm_init = gr2d_client_init,
+ .drm_exit = gr2d_client_exit,
+ .open_channel = gr2d_open_channel,
+ .close_channel = gr2d_close_channel,
+ .submit = gr2d_submit,
+};
+
+static void gr2d_init_addr_reg_map(struct device *dev, struct gr2d *gr2d)
+{
+ const u32 gr2d_addr_regs[] = {0x1a, 0x1b, 0x26, 0x2b, 0x2c, 0x2d, 0x31,
+ 0x32, 0x48, 0x49, 0x4a, 0x4b, 0x4c};
+ unsigned long *bitmap;
+ int i;
+
+ bitmap = devm_kzalloc(dev, DIV_ROUND_UP(256, BITS_PER_BYTE),
+ GFP_KERNEL);
+
+ for (i = 0; i < ARRAY_SIZE(gr2d_addr_regs); ++i) {
+ u32 reg = gr2d_addr_regs[i];
+ bitmap[BIT_WORD(reg)] |= BIT_MASK(reg);
+ }
+
+ gr2d->addr_regs = bitmap;
+}
+
+static int gr2d_is_addr_reg(struct device *dev, u32 class, u32 reg)
+{
+ struct gr2d *gr2d = dev_get_drvdata(dev);
+
+ switch (class) {
+ case HOST1X_CLASS_HOST1X:
+ return reg == 0x2b;
+ case HOST1X_CLASS_GR2D:
+ reg &= 0xff;
+ if (gr2d->addr_regs[BIT_WORD(reg)] & BIT_MASK(reg))
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static const struct of_device_id gr2d_match[] = {
+ { .compatible = "nvidia,tegra30-gr2d" },
+ { .compatible = "nvidia,tegra20-gr2d" },
+ { },
+};
+
+static int gr2d_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct host1x_drm *host1x = host1x_get_drm_data(dev->parent);
+ int err;
+ struct gr2d *gr2d = NULL;
+ struct host1x_syncpt **syncpts;
+
+ gr2d = devm_kzalloc(dev, sizeof(*gr2d), GFP_KERNEL);
+ if (!gr2d)
+ return -ENOMEM;
+
+ syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL);
+ if (!syncpts)
+ return -ENOMEM;
+
+ gr2d->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(gr2d->clk)) {
+ dev_err(dev, "cannot get clock\n");
+ return PTR_ERR(gr2d->clk);
+ }
+
+ err = clk_prepare_enable(gr2d->clk);
+ if (err) {
+ dev_err(dev, "cannot turn on clock\n");
+ return err;
+ }
+
+ gr2d->channel = host1x_channel_alloc(dev);
+ if (!gr2d->channel)
+ return -ENOMEM;
+
+ *syncpts = host1x_syncpt_request(dev, 0);
+ if (!(*syncpts)) {
+ host1x_channel_free(gr2d->channel);
+ return -ENOMEM;
+ }
+
+ gr2d->client.ops = &gr2d_client_ops;
+ gr2d->client.dev = dev;
+ gr2d->client.class = HOST1X_CLASS_GR2D;
+ gr2d->client.syncpts = syncpts;
+ gr2d->client.num_syncpts = 1;
+
+ err = host1x_register_client(host1x, &gr2d->client);
+ if (err < 0) {
+ dev_err(dev, "failed to register host1x client: %d\n", err);
+ return err;
+ }
+
+ gr2d_init_addr_reg_map(dev, gr2d);
+
+ dev_set_drvdata(dev, gr2d);
+
+ return 0;
+}
+
+static int __exit gr2d_remove(struct platform_device *pdev)
+{
+ struct host1x_drm *host1x = host1x_get_drm_data(pdev->dev.parent);
+ struct gr2d *gr2d = platform_get_drvdata(pdev);
+ int err;
+
+ err = host1x_unregister_client(host1x, &gr2d->client);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to unregister client: %d\n", err);
+ return err;
+ }
+
+ host1x_syncpt_free(*gr2d->client.syncpts);
+ return 0;
+}
+
+struct platform_driver tegra_gr2d_driver = {
+ .probe = gr2d_probe,
+ .remove = __exit_p(gr2d_remove),
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "gr2d",
+ .of_match_table = gr2d_match,
+ }
+};
diff --git a/drivers/gpu/host1x/host1x.h b/drivers/gpu/host1x/host1x.h
index bca6563..128cc9e 100644
--- a/drivers/gpu/host1x/host1x.h
+++ b/drivers/gpu/host1x/host1x.h
@@ -22,7 +22,8 @@
#define __LINUX_HOST1X_H

enum host1x_class {
- HOST1X_CLASS_HOST1X = 0x1
+ HOST1X_CLASS_HOST1X = 0x1,
+ HOST1X_CLASS_GR2D = 0x51
};

#endif
diff --git a/include/drm/tegra_drm.h b/include/drm/tegra_drm.h
new file mode 100644
index 0000000..6f7f7e6
--- /dev/null
+++ b/include/drm/tegra_drm.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2012-2013, 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TEGRA_DRM_H_
+#define _TEGRA_DRM_H_
+
+struct tegra_drm_create {
+ __u64 size;
+ u32 flags;
+ u32 handle;
+ u32 offset;
+ u32 padding;
+};
+
+struct tegra_drm_get_offset {
+ __u32 handle;
+ __u32 offset;
+};
+
+struct tegra_drm_syncpt_read_args {
+ __u32 id;
+ __u32 value;
+};
+
+struct tegra_drm_syncpt_incr_args {
+ __u32 id;
+ __u32 pad;
+};
+
+struct tegra_drm_syncpt_wait_args {
+ __u32 id;
+ __u32 thresh;
+ __u32 timeout;
+ __u32 value;
+};
+
+#define DRM_TEGRA_NO_TIMEOUT (0xffffffff)
+
+struct tegra_drm_open_channel_args {
+ __u32 class;
+ __u32 pad;
+ __u64 context;
+};
+
+struct tegra_drm_get_syncpoint {
+ __u64 context;
+ __u32 param;
+ __u32 value;
+};
+
+struct tegra_drm_syncpt_incr {
+ __u32 id;
+ __u32 incrs;
+};
+
+struct tegra_drm_cmdbuf {
+ __u32 mem;
+ __u32 offset;
+ __u32 words;
+ __u32 pad;
+};
+
+struct tegra_drm_reloc {
+ __u32 cmdbuf_mem;
+ __u32 cmdbuf_offset;
+ __u32 target;
+ __u32 target_offset;
+ __u32 shift;
+ __u32 pad;
+};
+
+struct tegra_drm_waitchk {
+ __u32 mem;
+ __u32 offset;
+ __u32 syncpt_id;
+ __u32 thresh;
+};
+
+struct tegra_drm_submit_args {
+ __u64 context;
+ __u32 num_syncpt_incrs;
+ __u32 num_cmdbufs;
+ __u32 num_relocs;
+ __u32 submit_version;
+ __u32 num_waitchks;
+ __u32 waitchk_mask;
+ __u32 timeout;
+ __u32 pad;
+ __u64 syncpt_incrs;
+ __u64 cmdbufs;
+ __u64 relocs;
+ __u64 waitchks;
+ __u32 fence; /* Return value */
+
+ __u32 reserved[5]; /* future expansion */
+};
+
+#define DRM_TEGRA_DRM_GEM_CREATE 0x00
+#define DRM_TEGRA_DRM_SYNCPT_READ 0x01
+#define DRM_TEGRA_DRM_SYNCPT_INCR 0x02
+#define DRM_TEGRA_DRM_SYNCPT_WAIT 0x03
+#define DRM_TEGRA_DRM_OPEN_CHANNEL 0x04
+#define DRM_TEGRA_DRM_CLOSE_CHANNEL 0x05
+#define DRM_TEGRA_DRM_GET_SYNCPOINT 0x06
+#define DRM_TEGRA_DRM_SUBMIT 0x08
+#define DRM_TEGRA_DRM_GEM_GET_OFFSET 0x09
+
+#define DRM_IOCTL_TEGRA_DRM_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_GEM_CREATE, struct tegra_drm_create)
+#define DRM_IOCTL_TEGRA_DRM_SYNCPT_READ DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SYNCPT_READ, struct tegra_drm_syncpt_read_args)
+#define DRM_IOCTL_TEGRA_DRM_SYNCPT_INCR DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SYNCPT_INCR, struct tegra_drm_syncpt_incr_args)
+#define DRM_IOCTL_TEGRA_DRM_SYNCPT_WAIT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SYNCPT_WAIT, struct tegra_drm_syncpt_wait_args)
+#define DRM_IOCTL_TEGRA_DRM_OPEN_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_OPEN_CHANNEL, struct tegra_drm_open_channel_args)
+#define DRM_IOCTL_TEGRA_DRM_CLOSE_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_CLOSE_CHANNEL, struct tegra_drm_open_channel_args)
+#define DRM_IOCTL_TEGRA_DRM_GET_SYNCPOINT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_GET_SYNCPOINT, struct tegra_drm_get_syncpoint)
+#define DRM_IOCTL_TEGRA_DRM_SUBMIT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SUBMIT, struct tegra_drm_submit_args)
+#define DRM_IOCTL_TEGRA_DRM_GEM_GET_OFFSET DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_GEM_GET_OFFSET, struct tegra_drm_get_offset)
+
+#endif
--
1.7.9.5

2013-03-13 12:36:06

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 02/10] gpu: host1x: Add host1x driver

Add host1x, the driver for host1x and its client unit 2D. The Tegra
host1x module is the DMA engine for register access to Tegra's
graphics- and multimedia-related modules. The modules served by
host1x are referred to as clients. host1x includes some other
functionality, such as synchronization.

Signed-off-by: Arto Merilainen <[email protected]>
Signed-off-by: Terje Bergstrom <[email protected]>
---
drivers/gpu/Makefile | 1 +
drivers/gpu/host1x/Kconfig | 9 ++
drivers/gpu/host1x/Makefile | 8 ++
drivers/gpu/host1x/dev.c | 155 +++++++++++++++++++++
drivers/gpu/host1x/dev.h | 104 ++++++++++++++
drivers/gpu/host1x/hw/Makefile | 6 +
drivers/gpu/host1x/hw/host1x01.c | 33 +++++
drivers/gpu/host1x/hw/host1x01.h | 25 ++++
drivers/gpu/host1x/hw/host1x01_hardware.h | 27 ++++
drivers/gpu/host1x/hw/hw_host1x01_sync.h | 74 ++++++++++
drivers/gpu/host1x/hw/syncpt_hw.c | 102 ++++++++++++++
drivers/gpu/host1x/syncpt.c | 212 +++++++++++++++++++++++++++++
drivers/gpu/host1x/syncpt.h | 147 ++++++++++++++++++++
drivers/video/Kconfig | 2 +
include/trace/events/host1x.h | 61 +++++++++
15 files changed, 966 insertions(+)
create mode 100644 drivers/gpu/host1x/Kconfig
create mode 100644 drivers/gpu/host1x/Makefile
create mode 100644 drivers/gpu/host1x/dev.c
create mode 100644 drivers/gpu/host1x/dev.h
create mode 100644 drivers/gpu/host1x/hw/Makefile
create mode 100644 drivers/gpu/host1x/hw/host1x01.c
create mode 100644 drivers/gpu/host1x/hw/host1x01.h
create mode 100644 drivers/gpu/host1x/hw/host1x01_hardware.h
create mode 100644 drivers/gpu/host1x/hw/hw_host1x01_sync.h
create mode 100644 drivers/gpu/host1x/hw/syncpt_hw.c
create mode 100644 drivers/gpu/host1x/syncpt.c
create mode 100644 drivers/gpu/host1x/syncpt.h
create mode 100644 include/trace/events/host1x.h

diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile
index 30879df..d8a22c2 100644
--- a/drivers/gpu/Makefile
+++ b/drivers/gpu/Makefile
@@ -1 +1,2 @@
obj-y += drm/ vga/
+obj-$(CONFIG_TEGRA_HOST1X) += host1x/
diff --git a/drivers/gpu/host1x/Kconfig b/drivers/gpu/host1x/Kconfig
new file mode 100644
index 0000000..c01c450
--- /dev/null
+++ b/drivers/gpu/host1x/Kconfig
@@ -0,0 +1,9 @@
+config TEGRA_HOST1X
+ tristate "NVIDIA Tegra host1x driver"
+ help
+ Driver for the NVIDIA Tegra host1x hardware.
+
+ The Tegra host1x module is the DMA engine for register access to
+ Tegra's graphics- and multimedia-related modules. The modules served
+ by host1x are referred to as clients. host1x includes some other
+ functionality, such as synchronization.
diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile
new file mode 100644
index 0000000..363e6ab
--- /dev/null
+++ b/drivers/gpu/host1x/Makefile
@@ -0,0 +1,8 @@
+ccflags-y = -Idrivers/gpu/host1x
+
+host1x-y = \
+ syncpt.o \
+ dev.o \
+ hw/host1x01.o
+
+obj-$(CONFIG_TEGRA_HOST1X) += host1x.o
diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
new file mode 100644
index 0000000..d95a9b2
--- /dev/null
+++ b/drivers/gpu/host1x/dev.c
@@ -0,0 +1,155 @@
+/*
+ * Tegra host1x driver
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/host1x.h>
+
+#include "dev.h"
+#include "hw/host1x01.h"
+
+void host1x_sync_writel(struct host1x *host1x, u32 v, u32 r)
+{
+ void __iomem *sync_regs = host1x->regs + host1x->info->sync_offset;
+
+ writel(v, sync_regs + r);
+}
+
+u32 host1x_sync_readl(struct host1x *host1x, u32 r)
+{
+ void __iomem *sync_regs = host1x->regs + host1x->info->sync_offset;
+
+ return readl(sync_regs + r);
+}
+
+static const struct host1x_info host1x01_info = {
+ .nb_channels = 8,
+ .nb_pts = 32,
+ .nb_mlocks = 16,
+ .nb_bases = 8,
+ .init = host1x01_init,
+ .sync_offset = 0x3000,
+};
+
+static struct of_device_id host1x_match[] = {
+ { .compatible = "nvidia,tegra30-host1x", .data = &host1x01_info, },
+ { .compatible = "nvidia,tegra20-host1x", .data = &host1x01_info, },
+ { },
+};
+
+static int host1x_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct host1x *host;
+ struct resource *regs;
+ int syncpt_irq;
+ int err;
+ const struct of_device_id *devid =
+ of_match_device(host1x_match, dev);
+
+ if (!devid)
+ return -EINVAL;
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!regs) {
+ dev_err(dev, "missing regs\n");
+ return -ENXIO;
+ }
+
+ syncpt_irq = platform_get_irq(pdev, 0);
+ if (syncpt_irq < 0) {
+ dev_err(dev, "missing irq\n");
+ return -ENXIO;
+ }
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host) {
+ dev_err(dev, "failed to alloc host1x\n");
+ return -ENOMEM;
+ }
+
+ host->dev = dev;
+ host->info = devid->data;
+
+ /* set common host1x device data */
+ dev_set_drvdata(dev, host);
+
+ host->regs = devm_ioremap_resource(&pdev->dev, regs);
+ if (IS_ERR(host->regs)) {
+ dev_err(dev, "failed to remap host registers\n");
+ return PTR_ERR(host->regs);
+ }
+
+ if (host->info->init) {
+ err = host->info->init(host);
+ if (err)
+ return err;
+ }
+
+ host->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(host->clk)) {
+ dev_err(dev, "failed to get clock\n");
+ err = PTR_ERR(host->clk);
+ return err;
+ }
+
+ err = clk_prepare_enable(host->clk);
+ if (err < 0) {
+ dev_err(dev, "failed to enable clock\n");
+ return err;
+ }
+
+ err = host1x_syncpt_init(host);
+ if (err) {
+ dev_err(dev, "failed to init syncpts");
+ return err;
+ }
+
+ return 0;
+}
+
+static int __exit host1x_remove(struct platform_device *pdev)
+{
+ struct host1x *host = platform_get_drvdata(pdev);
+ host1x_syncpt_deinit(host);
+ clk_disable_unprepare(host->clk);
+ return 0;
+}
+
+static struct platform_driver platform_driver = {
+ .probe = host1x_probe,
+ .remove = __exit_p(host1x_remove),
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "tegra-host1x",
+ .of_match_table = host1x_match,
+ },
+};
+
+module_platform_driver(platform_driver);
+
+MODULE_AUTHOR("Terje Bergstrom <[email protected]>");
+MODULE_DESCRIPTION("Host1x driver for Tegra products");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h
new file mode 100644
index 0000000..750daa3
--- /dev/null
+++ b/drivers/gpu/host1x/dev.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2012-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HOST1X_DEV_H
+#define HOST1X_DEV_H
+
+#include <linux/platform_device.h>
+#include <linux/device.h>
+
+#include "syncpt.h"
+
+struct host1x_syncpt;
+
+struct host1x_syncpt_ops {
+ void (*restore)(struct host1x_syncpt *syncpt);
+ void (*restore_wait_base)(struct host1x_syncpt *syncpt);
+ void (*load_wait_base)(struct host1x_syncpt *syncpt);
+ u32 (*load)(struct host1x_syncpt *syncpt);
+ void (*cpu_incr)(struct host1x_syncpt *syncpt);
+ int (*patch_wait)(struct host1x_syncpt *syncpt, void *patch_addr);
+};
+
+struct host1x_info {
+ int nb_channels; /* host1x: num channels supported */
+ int nb_pts; /* host1x: num syncpoints supported */
+ int nb_bases; /* host1x: num syncpoints supported */
+ int nb_mlocks; /* host1x: number of mlocks */
+ int (*init)(struct host1x *); /* initialize per SoC ops */
+ int sync_offset;
+};
+
+struct host1x {
+ const struct host1x_info *info;
+
+ void __iomem *regs;
+ struct host1x_syncpt *syncpt;
+ struct device *dev;
+ struct clk *clk;
+
+ const struct host1x_syncpt_ops *syncpt_op;
+};
+
+static inline struct host1x *host1x_get_host(struct device *dev)
+{
+ if (dev->parent && dev->parent != &platform_bus)
+ return dev_get_drvdata(dev->parent);
+ else
+ return dev_get_drvdata(dev);
+}
+
+void host1x_sync_writel(struct host1x *host1x, u32 r, u32 v);
+u32 host1x_sync_readl(struct host1x *host1x, u32 r);
+
+static inline void host1x_hw_syncpt_restore(struct host1x *host,
+ struct host1x_syncpt *sp)
+{
+ host->syncpt_op->restore(sp);
+}
+
+static inline void host1x_hw_syncpt_restore_wait_base(struct host1x *host,
+ struct host1x_syncpt *sp)
+{
+ host->syncpt_op->restore_wait_base(sp);
+}
+
+static inline void host1x_hw_syncpt_load_wait_base(struct host1x *host,
+ struct host1x_syncpt *sp)
+{
+ host->syncpt_op->load_wait_base(sp);
+}
+
+static inline u32 host1x_hw_syncpt_load(struct host1x *host,
+ struct host1x_syncpt *sp)
+{
+ return host->syncpt_op->load(sp);
+}
+
+static inline void host1x_hw_syncpt_cpu_incr(struct host1x *host,
+ struct host1x_syncpt *sp)
+{
+ host->syncpt_op->cpu_incr(sp);
+}
+
+static inline int host1x_hw_syncpt_patch_wait(struct host1x *host,
+ struct host1x_syncpt *sp,
+ void *patch_addr)
+{
+ return host->syncpt_op->patch_wait(sp, patch_addr);
+}
+
+#endif
diff --git a/drivers/gpu/host1x/hw/Makefile b/drivers/gpu/host1x/hw/Makefile
new file mode 100644
index 0000000..9b50863
--- /dev/null
+++ b/drivers/gpu/host1x/hw/Makefile
@@ -0,0 +1,6 @@
+ccflags-y = -Idrivers/gpu/host1x
+
+host1x-hw-objs = \
+ host1x01.o
+
+obj-$(CONFIG_TEGRA_HOST1X) += host1x-hw.o
diff --git a/drivers/gpu/host1x/hw/host1x01.c b/drivers/gpu/host1x/hw/host1x01.c
new file mode 100644
index 0000000..612b457
--- /dev/null
+++ b/drivers/gpu/host1x/hw/host1x01.c
@@ -0,0 +1,33 @@
+/*
+ * Host1x init for T20 and T30 Architecture Chips
+ *
+ * Copyright (c) 2011-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* include hw specification */
+#include "hw/host1x01.h"
+#include "hw/host1x01_hardware.h"
+
+/* include code */
+#include "hw/syncpt_hw.c"
+
+#include "dev.h"
+
+int host1x01_init(struct host1x *host)
+{
+ host->syncpt_op = &host1x_syncpt_ops;
+
+ return 0;
+}
diff --git a/drivers/gpu/host1x/hw/host1x01.h b/drivers/gpu/host1x/hw/host1x01.h
new file mode 100644
index 0000000..2706b67
--- /dev/null
+++ b/drivers/gpu/host1x/hw/host1x01.h
@@ -0,0 +1,25 @@
+/*
+ * Host1x init for T20 and T30 Architecture Chips
+ *
+ * Copyright (c) 2011-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef HOST1X_HOST1X01_H
+#define HOST1X_HOST1X01_H
+
+struct host1x;
+
+int host1x01_init(struct host1x *host);
+
+#endif /* HOST1X_HOST1X01_H_ */
diff --git a/drivers/gpu/host1x/hw/host1x01_hardware.h b/drivers/gpu/host1x/hw/host1x01_hardware.h
new file mode 100644
index 0000000..8cecbee
--- /dev/null
+++ b/drivers/gpu/host1x/hw/host1x01_hardware.h
@@ -0,0 +1,27 @@
+/*
+ * Tegra host1x Register Offsets for Tegra20 and Tegra30
+ *
+ * Copyright (c) 2010-2013 NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HOST1X_HOST1X01_HARDWARE_H
+#define __HOST1X_HOST1X01_HARDWARE_H
+
+#include <linux/types.h>
+#include <linux/bitops.h>
+
+#include "hw_host1x01_sync.h"
+
+#endif
diff --git a/drivers/gpu/host1x/hw/hw_host1x01_sync.h b/drivers/gpu/host1x/hw/hw_host1x01_sync.h
new file mode 100644
index 0000000..3af258b
--- /dev/null
+++ b/drivers/gpu/host1x/hw/hw_host1x01_sync.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+ /*
+ * Function naming determines intended use:
+ *
+ * <x>_r(void) : Returns the offset for register <x>.
+ *
+ * <x>_w(void) : Returns the word offset for word (4 byte) element <x>.
+ *
+ * <x>_<y>_s(void) : Returns size of field <y> of register <x> in bits.
+ *
+ * <x>_<y>_f(u32 v) : Returns a value based on 'v' which has been shifted
+ * and masked to place it at field <y> of register <x>. This value
+ * can be |'d with others to produce a full register value for
+ * register <x>.
+ *
+ * <x>_<y>_m(void) : Returns a mask for field <y> of register <x>. This
+ * value can be ~'d and then &'d to clear the value of field <y> for
+ * register <x>.
+ *
+ * <x>_<y>_<z>_f(void) : Returns the constant value <z> after being shifted
+ * to place it at field <y> of register <x>. This value can be |'d
+ * with others to produce a full register value for <x>.
+ *
+ * <x>_<y>_v(u32 r) : Returns the value of field <y> from a full register
+ * <x> value 'r' after being shifted to place its LSB at bit 0.
+ * This value is suitable for direct comparison with other unshifted
+ * values appropriate for use in field <y> of register <x>.
+ *
+ * <x>_<y>_<z>_v(void) : Returns the constant value for <z> defined for
+ * field <y> of register <x>. This value is suitable for direct
+ * comparison with unshifted values appropriate for use in field <y>
+ * of register <x>.
+ */
+
+#ifndef __hw_host1x01_sync_h__
+#define __hw_host1x01_sync_h__
+
+#define REGISTER_STRIDE 4
+
+static inline u32 host1x_sync_syncpt_r(unsigned int id)
+{
+ return 0x400 + id * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_SYNCPT(id) \
+ host1x_sync_syncpt_r(id)
+static inline u32 host1x_sync_syncpt_base_r(unsigned int id)
+{
+ return 0x600 + id * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_SYNCPT_BASE(id) \
+ host1x_sync_syncpt_base_r(id)
+static inline u32 host1x_sync_syncpt_cpu_incr_r(unsigned int id)
+{
+ return 0x700 + id * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_SYNCPT_CPU_INCR(id) \
+ host1x_sync_syncpt_cpu_incr_r(id)
+#endif /* __hw_host1x01_sync_h__ */
diff --git a/drivers/gpu/host1x/hw/syncpt_hw.c b/drivers/gpu/host1x/hw/syncpt_hw.c
new file mode 100644
index 0000000..885b257
--- /dev/null
+++ b/drivers/gpu/host1x/hw/syncpt_hw.c
@@ -0,0 +1,102 @@
+/*
+ * Tegra host1x Syncpoints
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/io.h>
+
+#include "dev.h"
+#include "syncpt.h"
+
+/*
+ * Write the current syncpoint value back to hw.
+ */
+static void syncpt_restore(struct host1x_syncpt *sp)
+{
+ struct host1x *host = sp->host;
+ int min = host1x_syncpt_read_min(sp);
+ host1x_sync_writel(host, min, HOST1X_SYNC_SYNCPT(sp->id));
+}
+
+/*
+ * Write the current waitbase value back to hw.
+ */
+static void syncpt_restore_wait_base(struct host1x_syncpt *sp)
+{
+ struct host1x *host = sp->host;
+ host1x_sync_writel(host, sp->base_val,
+ HOST1X_SYNC_SYNCPT_BASE(sp->id));
+}
+
+/*
+ * Read waitbase value from hw.
+ */
+static void syncpt_read_wait_base(struct host1x_syncpt *sp)
+{
+ struct host1x *host = sp->host;
+ sp->base_val =
+ host1x_sync_readl(host, HOST1X_SYNC_SYNCPT_BASE(sp->id));
+}
+
+/*
+ * Updates the last value read from hardware.
+ */
+static u32 syncpt_load(struct host1x_syncpt *sp)
+{
+ struct host1x *host = sp->host;
+ u32 old, live;
+
+ /* Loop in case there's a race writing to min_val */
+ do {
+ old = host1x_syncpt_read_min(sp);
+ live = host1x_sync_readl(host, HOST1X_SYNC_SYNCPT(sp->id));
+ } while ((u32)atomic_cmpxchg(&sp->min_val, old, live) != old);
+
+ if (!host1x_syncpt_check_max(sp, live))
+ dev_err(host->dev, "%s failed: id=%u, min=%d, max=%d\n",
+ __func__, sp->id, host1x_syncpt_read_min(sp),
+ host1x_syncpt_read_max(sp));
+
+ return live;
+}
+
+/*
+ * Write a cpu syncpoint increment to the hardware, without touching
+ * the cache.
+ */
+static void syncpt_cpu_incr(struct host1x_syncpt *sp)
+{
+ struct host1x *host = sp->host;
+ u32 reg_offset = sp->id / 32;
+
+ if (!host1x_syncpt_client_managed(sp) &&
+ host1x_syncpt_idle(sp)) {
+ dev_err(host->dev, "Trying to increment syncpoint id %d beyond max\n",
+ sp->id);
+ return;
+ }
+ host1x_sync_writel(host, BIT_MASK(sp->id),
+ HOST1X_SYNC_SYNCPT_CPU_INCR(reg_offset));
+ wmb();
+}
+
+static const struct host1x_syncpt_ops host1x_syncpt_ops = {
+ .restore = syncpt_restore,
+ .restore_wait_base = syncpt_restore_wait_base,
+ .load_wait_base = syncpt_read_wait_base,
+ .load = syncpt_load,
+ .cpu_incr = syncpt_cpu_incr,
+};
diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c
new file mode 100644
index 0000000..9df962c
--- /dev/null
+++ b/drivers/gpu/host1x/syncpt.c
@@ -0,0 +1,212 @@
+/*
+ * Tegra host1x Syncpoints
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+
+#include <trace/events/host1x.h>
+
+#include "syncpt.h"
+#include "dev.h"
+
+static struct host1x_syncpt *_host1x_syncpt_alloc(struct host1x *host,
+ struct device *dev,
+ int client_managed)
+{
+ int i;
+ struct host1x_syncpt *sp = host->syncpt;
+ char *name;
+
+ for (i = 0; i < host->info->nb_pts && sp->name; i++, sp++)
+ ;
+ if (sp->dev)
+ return NULL;
+
+ name = kasprintf(GFP_KERNEL, "%02d-%s", sp->id,
+ dev ? dev_name(dev) : NULL);
+ if (!name)
+ return NULL;
+
+ sp->dev = dev;
+ sp->name = name;
+ sp->client_managed = client_managed;
+
+ return sp;
+}
+
+u32 host1x_syncpt_id(struct host1x_syncpt *sp)
+{
+ return sp->id;
+}
+
+/*
+ * Updates the value sent to hardware.
+ */
+u32 host1x_syncpt_incr_max(struct host1x_syncpt *sp, u32 incrs)
+{
+ return (u32)atomic_add_return(incrs, &sp->max_val);
+}
+
+ /*
+ * Write cached syncpoint and waitbase values to hardware.
+ */
+void host1x_syncpt_restore(struct host1x *host)
+{
+ struct host1x_syncpt *sp_base = host->syncpt;
+ u32 i;
+
+ for (i = 0; i < host1x_syncpt_nb_pts(host); i++)
+ host1x_hw_syncpt_restore(host, sp_base + i);
+ for (i = 0; i < host1x_syncpt_nb_bases(host); i++)
+ host1x_hw_syncpt_restore_wait_base(host, sp_base + i);
+ wmb();
+}
+
+/*
+ * Update the cached syncpoint and waitbase values by reading them
+ * from the registers.
+ */
+void host1x_syncpt_save(struct host1x *host)
+{
+ struct host1x_syncpt *sp_base = host->syncpt;
+ u32 i;
+
+ for (i = 0; i < host1x_syncpt_nb_pts(host); i++) {
+ if (host1x_syncpt_client_managed(sp_base + i))
+ host1x_hw_syncpt_load(host, sp_base + i);
+ else
+ WARN_ON(!host1x_syncpt_idle(sp_base + i));
+ }
+
+ for (i = 0; i < host1x_syncpt_nb_bases(host); i++)
+ host1x_hw_syncpt_load_wait_base(host, sp_base + i);
+}
+
+/*
+ * Updates the cached syncpoint value by reading a new value from the hardware
+ * register
+ */
+u32 host1x_syncpt_load(struct host1x_syncpt *sp)
+{
+ u32 val;
+ val = host1x_hw_syncpt_load(sp->host, sp);
+ trace_host1x_syncpt_load_min(sp->id, val);
+
+ return val;
+}
+
+/*
+ * Get the current syncpoint base
+ */
+u32 host1x_syncpt_load_wait_base(struct host1x_syncpt *sp)
+{
+ u32 val;
+ host1x_hw_syncpt_load_wait_base(sp->host, sp);
+ val = sp->base_val;
+ return val;
+}
+
+/*
+ * Write a cpu syncpoint increment to the hardware, without touching
+ * the cache. Caller is responsible for host being powered.
+ */
+void host1x_syncpt_cpu_incr(struct host1x_syncpt *sp)
+{
+ host1x_hw_syncpt_cpu_incr(sp->host, sp);
+}
+
+/*
+ * Increment syncpoint value from cpu, updating cache
+ */
+void host1x_syncpt_incr(struct host1x_syncpt *sp)
+{
+ if (host1x_syncpt_client_managed(sp))
+ host1x_syncpt_incr_max(sp, 1);
+ host1x_syncpt_cpu_incr(sp);
+}
+
+int host1x_syncpt_init(struct host1x *host)
+{
+ struct host1x_syncpt *syncpt;
+ int i;
+
+ syncpt = devm_kzalloc(host->dev, sizeof(*syncpt) * host->info->nb_pts,
+ GFP_KERNEL);
+ if (!syncpt)
+ return -ENOMEM;
+
+ for (i = 0; i < host->info->nb_pts; ++i) {
+ syncpt[i].id = i;
+ syncpt[i].host = host;
+ }
+
+ host->syncpt = syncpt;
+
+ host1x_syncpt_restore(host);
+
+ return 0;
+}
+
+struct host1x_syncpt *host1x_syncpt_request(struct device *dev,
+ int client_managed)
+{
+ struct host1x *host = host1x_get_host(dev);
+ return _host1x_syncpt_alloc(host, dev, client_managed);
+}
+
+void host1x_syncpt_free(struct host1x_syncpt *sp)
+{
+ if (!sp)
+ return;
+
+ kfree(sp->name);
+ sp->dev = NULL;
+ sp->name = NULL;
+ sp->client_managed = 0;
+}
+
+void host1x_syncpt_deinit(struct host1x *host)
+{
+ int i;
+ struct host1x_syncpt *sp = host->syncpt;
+ for (i = 0; i < host->info->nb_pts; i++, sp++)
+ kfree(sp->name);
+}
+
+int host1x_syncpt_nb_pts(struct host1x *host)
+{
+ return host->info->nb_pts;
+}
+
+int host1x_syncpt_nb_bases(struct host1x *host)
+{
+ return host->info->nb_bases;
+}
+
+int host1x_syncpt_nb_mlocks(struct host1x *host)
+{
+ return host->info->nb_mlocks;
+}
+
+struct host1x_syncpt *host1x_syncpt_get(struct host1x *host, u32 id)
+{
+ if (host->info->nb_pts < id)
+ return NULL;
+ return host->syncpt + id;
+}
diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h
new file mode 100644
index 0000000..119ccd5
--- /dev/null
+++ b/drivers/gpu/host1x/syncpt.h
@@ -0,0 +1,147 @@
+/*
+ * Tegra host1x Syncpoints
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HOST1X_SYNCPT_H
+#define __HOST1X_SYNCPT_H
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/atomic.h>
+
+struct host1x;
+
+struct host1x_syncpt {
+ int id;
+ atomic_t min_val;
+ atomic_t max_val;
+ u32 base_val;
+ const char *name;
+ int client_managed;
+ struct host1x *host;
+ struct device *dev;
+};
+
+/* Initialize sync point array */
+int host1x_syncpt_init(struct host1x *host);
+
+/* Free sync point array */
+void host1x_syncpt_deinit(struct host1x *host);
+
+/*
+ * Read max. It indicates how many operations there are in queue, either in
+ * channel or in a software thread.
+ * */
+static inline u32 host1x_syncpt_read_max(struct host1x_syncpt *sp)
+{
+ smp_rmb();
+ return (u32)atomic_read(&sp->max_val);
+}
+
+/*
+ * Read min, which is a shadow of the current sync point value in hardware.
+ */
+static inline u32 host1x_syncpt_read_min(struct host1x_syncpt *sp)
+{
+ smp_rmb();
+ return (u32)atomic_read(&sp->min_val);
+}
+
+/* Return number of sync point supported. */
+int host1x_syncpt_nb_pts(struct host1x *host);
+
+/* Return number of wait bases supported. */
+int host1x_syncpt_nb_bases(struct host1x *host);
+
+/* Return number of mlocks supported. */
+int host1x_syncpt_nb_mlocks(struct host1x *host);
+
+/*
+ * Check sync point sanity. If max is larger than min, there have too many
+ * sync point increments.
+ *
+ * Client managed sync point are not tracked.
+ * */
+static inline bool host1x_syncpt_check_max(struct host1x_syncpt *sp, u32 real)
+{
+ u32 max;
+ if (sp->client_managed)
+ return true;
+ max = host1x_syncpt_read_max(sp);
+ return (s32)(max - real) >= 0;
+}
+
+/* Return true if sync point is client managed. */
+static inline int host1x_syncpt_client_managed(struct host1x_syncpt *sp)
+{
+ return sp->client_managed;
+}
+
+/*
+ * Returns true if syncpoint min == max, which means that there are no
+ * outstanding operations.
+ */
+static inline bool host1x_syncpt_idle(struct host1x_syncpt *sp)
+{
+ int min, max;
+ smp_rmb();
+ min = atomic_read(&sp->min_val);
+ max = atomic_read(&sp->max_val);
+ return (min == max);
+}
+
+/* Return pointer to struct denoting sync point id. */
+struct host1x_syncpt *host1x_syncpt_get(struct host1x *host, u32 id);
+
+/* Request incrementing a sync point. */
+void host1x_syncpt_cpu_incr(struct host1x_syncpt *sp);
+
+/* Load current value from hardware to the shadow register. */
+u32 host1x_syncpt_load(struct host1x_syncpt *sp);
+
+/* Save host1x sync point state into shadow registers. */
+void host1x_syncpt_save(struct host1x *host);
+
+/* Reset host1x sync point state from shadow registers. */
+void host1x_syncpt_restore(struct host1x *host);
+
+/* Read current wait base value into shadow register and return it. */
+u32 host1x_syncpt_load_wait_base(struct host1x_syncpt *sp);
+
+/* Increment sync point and its max. */
+void host1x_syncpt_incr(struct host1x_syncpt *sp);
+
+/* Indicate future operations by incrementing the sync point max. */
+u32 host1x_syncpt_incr_max(struct host1x_syncpt *sp, u32 incrs);
+
+/* Check if sync point id is valid. */
+static inline int host1x_syncpt_is_valid(struct host1x_syncpt *sp)
+{
+ return sp->id < host1x_syncpt_nb_pts(sp->host);
+}
+
+/* Return id of the sync point */
+u32 host1x_syncpt_id(struct host1x_syncpt *sp);
+
+/* Allocate a sync point for a device. */
+struct host1x_syncpt *host1x_syncpt_request(struct device *dev,
+ int client_managed);
+
+/* Free a sync point. */
+void host1x_syncpt_free(struct host1x_syncpt *sp);
+
+#endif
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 4c1546f..3e0e3f0 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -21,6 +21,8 @@ source "drivers/gpu/vga/Kconfig"

source "drivers/gpu/drm/Kconfig"

+source "drivers/gpu/host1x/Kconfig"
+
config VGASTATE
tristate
default n
diff --git a/include/trace/events/host1x.h b/include/trace/events/host1x.h
new file mode 100644
index 0000000..4bd2b02
--- /dev/null
+++ b/include/trace/events/host1x.h
@@ -0,0 +1,61 @@
+/*
+ * include/trace/events/host1x.h
+ *
+ * host1x event logging to ftrace.
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM host1x
+
+#if !defined(_TRACE_HOST1X_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_HOST1X_H
+
+#include <linux/ktime.h>
+#include <linux/tracepoint.h>
+
+DECLARE_EVENT_CLASS(host1x,
+ TP_PROTO(const char *name),
+ TP_ARGS(name),
+ TP_STRUCT__entry(__field(const char *, name)),
+ TP_fast_assign(__entry->name = name;),
+ TP_printk("name=%s", __entry->name)
+);
+
+TRACE_EVENT(host1x_syncpt_load_min,
+ TP_PROTO(u32 id, u32 val),
+
+ TP_ARGS(id, val),
+
+ TP_STRUCT__entry(
+ __field(u32, id)
+ __field(u32, val)
+ ),
+
+ TP_fast_assign(
+ __entry->id = id;
+ __entry->val = val;
+ ),
+
+ TP_printk("id=%d, val=%d", __entry->id, __entry->val)
+);
+
+#endif /* _TRACE_HOST1X_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
--
1.7.9.5

2013-03-13 12:36:01

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 07/10] gpu: host1x: drm: Rename host1x to host1x_drm

From: Arto Merilainen <[email protected]>

Both host1x and drm drivers have host1x structures. This patch
renames the host1x structure under drm to follow name host1x_drm.

Signed-off-by: Arto Merilainen <[email protected]>
Signed-off-by: Terje Bergstrom <[email protected]>
---
drivers/gpu/host1x/drm/dc.c | 4 ++--
drivers/gpu/host1x/drm/drm.c | 4 ++--
drivers/gpu/host1x/drm/drm.h | 14 +++++++-------
drivers/gpu/host1x/drm/fb.c | 6 +++---
drivers/gpu/host1x/drm/hdmi.c | 4 ++--
drivers/gpu/host1x/drm/host1x.c | 22 ++++++++++++----------
6 files changed, 28 insertions(+), 26 deletions(-)

diff --git a/drivers/gpu/host1x/drm/dc.c b/drivers/gpu/host1x/drm/dc.c
index de94707..d1f6609 100644
--- a/drivers/gpu/host1x/drm/dc.c
+++ b/drivers/gpu/host1x/drm/dc.c
@@ -1097,7 +1097,7 @@ static const struct host1x_client_ops dc_client_ops = {

static int tegra_dc_probe(struct platform_device *pdev)
{
- struct host1x *host1x = dev_get_drvdata(pdev->dev.parent);
+ struct host1x_drm *host1x = dev_get_drvdata(pdev->dev.parent);
struct resource *regs;
struct tegra_dc *dc;
int err;
@@ -1160,7 +1160,7 @@ static int tegra_dc_probe(struct platform_device *pdev)

static int tegra_dc_remove(struct platform_device *pdev)
{
- struct host1x *host1x = dev_get_drvdata(pdev->dev.parent);
+ struct host1x_drm *host1x = dev_get_drvdata(pdev->dev.parent);
struct tegra_dc *dc = platform_get_drvdata(pdev);
int err;

diff --git a/drivers/gpu/host1x/drm/drm.c b/drivers/gpu/host1x/drm/drm.c
index 9d452df..6c59bcd 100644
--- a/drivers/gpu/host1x/drm/drm.c
+++ b/drivers/gpu/host1x/drm/drm.c
@@ -26,7 +26,7 @@
static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
{
struct device *dev = drm->dev;
- struct host1x *host1x;
+ struct host1x_drm *host1x;
int err;

host1x = dev_get_drvdata(dev);
@@ -69,7 +69,7 @@ static int tegra_drm_open(struct drm_device *drm, struct drm_file *filp)

static void tegra_drm_lastclose(struct drm_device *drm)
{
- struct host1x *host1x = drm->dev_private;
+ struct host1x_drm *host1x = drm->dev_private;

drm_fbdev_cma_restore_mode(host1x->fbdev);
}
diff --git a/drivers/gpu/host1x/drm/drm.h b/drivers/gpu/host1x/drm/drm.h
index a6c011d..7fedb6c 100644
--- a/drivers/gpu/host1x/drm/drm.h
+++ b/drivers/gpu/host1x/drm/drm.h
@@ -18,7 +18,7 @@
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fixed.h>

-struct host1x {
+struct host1x_drm {
struct drm_device *drm;
struct device *dev;
void __iomem *regs;
@@ -44,7 +44,7 @@ struct host1x_client_ops {
};

struct host1x_client {
- struct host1x *host1x;
+ struct host1x_drm *host1x;
struct device *dev;

const struct host1x_client_ops *ops;
@@ -52,12 +52,12 @@ struct host1x_client {
struct list_head list;
};

-extern int host1x_drm_init(struct host1x *host1x, struct drm_device *drm);
-extern int host1x_drm_exit(struct host1x *host1x);
+extern int host1x_drm_init(struct host1x_drm *host1x, struct drm_device *drm);
+extern int host1x_drm_exit(struct host1x_drm *host1x);

-extern int host1x_register_client(struct host1x *host1x,
+extern int host1x_register_client(struct host1x_drm *host1x,
struct host1x_client *client);
-extern int host1x_unregister_client(struct host1x *host1x,
+extern int host1x_unregister_client(struct host1x_drm *host1x,
struct host1x_client *client);

struct tegra_output;
@@ -66,7 +66,7 @@ struct tegra_dc {
struct host1x_client client;
spinlock_t lock;

- struct host1x *host1x;
+ struct host1x_drm *host1x;
struct device *dev;

struct drm_crtc base;
diff --git a/drivers/gpu/host1x/drm/fb.c b/drivers/gpu/host1x/drm/fb.c
index 0391495..6ed885a 100644
--- a/drivers/gpu/host1x/drm/fb.c
+++ b/drivers/gpu/host1x/drm/fb.c
@@ -11,7 +11,7 @@

static void tegra_drm_fb_output_poll_changed(struct drm_device *drm)
{
- struct host1x *host1x = drm->dev_private;
+ struct host1x_drm *host1x = drm->dev_private;

drm_fbdev_cma_hotplug_event(host1x->fbdev);
}
@@ -23,7 +23,7 @@ static const struct drm_mode_config_funcs tegra_drm_mode_funcs = {

int tegra_drm_fb_init(struct drm_device *drm)
{
- struct host1x *host1x = drm->dev_private;
+ struct host1x_drm *host1x = drm->dev_private;
struct drm_fbdev_cma *fbdev;

drm->mode_config.min_width = 0;
@@ -46,7 +46,7 @@ int tegra_drm_fb_init(struct drm_device *drm)

void tegra_drm_fb_exit(struct drm_device *drm)
{
- struct host1x *host1x = drm->dev_private;
+ struct host1x_drm *host1x = drm->dev_private;

drm_fbdev_cma_fini(host1x->fbdev);
}
diff --git a/drivers/gpu/host1x/drm/hdmi.c b/drivers/gpu/host1x/drm/hdmi.c
index bb747f6..f438f80 100644
--- a/drivers/gpu/host1x/drm/hdmi.c
+++ b/drivers/gpu/host1x/drm/hdmi.c
@@ -1189,7 +1189,7 @@ static const struct host1x_client_ops hdmi_client_ops = {

static int tegra_hdmi_probe(struct platform_device *pdev)
{
- struct host1x *host1x = dev_get_drvdata(pdev->dev.parent);
+ struct host1x_drm *host1x = dev_get_drvdata(pdev->dev.parent);
struct tegra_hdmi *hdmi;
struct resource *regs;
int err;
@@ -1278,7 +1278,7 @@ static int tegra_hdmi_probe(struct platform_device *pdev)

static int tegra_hdmi_remove(struct platform_device *pdev)
{
- struct host1x *host1x = dev_get_drvdata(pdev->dev.parent);
+ struct host1x_drm *host1x = dev_get_drvdata(pdev->dev.parent);
struct tegra_hdmi *hdmi = platform_get_drvdata(pdev);
int err;

diff --git a/drivers/gpu/host1x/drm/host1x.c b/drivers/gpu/host1x/drm/host1x.c
index 92e25a7..c3d8ee5 100644
--- a/drivers/gpu/host1x/drm/host1x.c
+++ b/drivers/gpu/host1x/drm/host1x.c
@@ -21,7 +21,8 @@ struct host1x_drm_client {
struct list_head list;
};

-static int host1x_add_drm_client(struct host1x *host1x, struct device_node *np)
+static int host1x_add_drm_client(struct host1x_drm *host1x,
+ struct device_node *np)
{
struct host1x_drm_client *client;

@@ -37,7 +38,7 @@ static int host1x_add_drm_client(struct host1x *host1x, struct device_node *np)
return 0;
}

-static int host1x_activate_drm_client(struct host1x *host1x,
+static int host1x_activate_drm_client(struct host1x_drm *host1x,
struct host1x_drm_client *drm,
struct host1x_client *client)
{
@@ -50,7 +51,7 @@ static int host1x_activate_drm_client(struct host1x *host1x,
return 0;
}

-static int host1x_remove_drm_client(struct host1x *host1x,
+static int host1x_remove_drm_client(struct host1x_drm *host1x,
struct host1x_drm_client *client)
{
mutex_lock(&host1x->drm_clients_lock);
@@ -63,7 +64,7 @@ static int host1x_remove_drm_client(struct host1x *host1x,
return 0;
}

-static int host1x_parse_dt(struct host1x *host1x)
+static int host1x_parse_dt(struct host1x_drm *host1x)
{
static const char * const compat[] = {
"nvidia,tegra20-dc",
@@ -92,7 +93,7 @@ static int host1x_parse_dt(struct host1x *host1x)

static int tegra_host1x_probe(struct platform_device *pdev)
{
- struct host1x *host1x;
+ struct host1x_drm *host1x;
struct resource *regs;
int err;

@@ -156,14 +157,14 @@ err:

static int tegra_host1x_remove(struct platform_device *pdev)
{
- struct host1x *host1x = platform_get_drvdata(pdev);
+ struct host1x_drm *host1x = platform_get_drvdata(pdev);

clk_disable_unprepare(host1x->clk);

return 0;
}

-int host1x_drm_init(struct host1x *host1x, struct drm_device *drm)
+int host1x_drm_init(struct host1x_drm *host1x, struct drm_device *drm)
{
struct host1x_client *client;

@@ -186,7 +187,7 @@ int host1x_drm_init(struct host1x *host1x, struct drm_device *drm)
return 0;
}

-int host1x_drm_exit(struct host1x *host1x)
+int host1x_drm_exit(struct host1x_drm *host1x)
{
struct platform_device *pdev = to_platform_device(host1x->dev);
struct host1x_client *client;
@@ -216,7 +217,8 @@ int host1x_drm_exit(struct host1x *host1x)
return 0;
}

-int host1x_register_client(struct host1x *host1x, struct host1x_client *client)
+int host1x_register_client(struct host1x_drm *host1x,
+ struct host1x_client *client)
{
struct host1x_drm_client *drm, *tmp;
int err;
@@ -244,7 +246,7 @@ int host1x_register_client(struct host1x *host1x, struct host1x_client *client)
return 0;
}

-int host1x_unregister_client(struct host1x *host1x,
+int host1x_unregister_client(struct host1x_drm *host1x,
struct host1x_client *client)
{
struct host1x_drm_client *drm, *tmp;
--
1.7.9.5

2013-03-13 12:38:10

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 01/10] gpu: drm: Support CMA object preallocation

From: Arto Merilainen <[email protected]>

This patch adds helper functions drm_gem_cma_init() and
drm_gem_cma_deinit() for handling CMA structures that already have
been allocated. This allows embedding the CMA structure inside other
structures.

Signed-off-by: Arto Merilainen <[email protected]>
---
drivers/gpu/drm/drm_gem_cma_helper.c | 78 ++++++++++++++++++++++++----------
include/drm/drm_gem_cma_helper.h | 9 ++++
2 files changed, 64 insertions(+), 23 deletions(-)

diff --git a/drivers/gpu/drm/drm_gem_cma_helper.c b/drivers/gpu/drm/drm_gem_cma_helper.c
index 0a7e011..3b14280 100644
--- a/drivers/gpu/drm/drm_gem_cma_helper.c
+++ b/drivers/gpu/drm/drm_gem_cma_helper.c
@@ -2,6 +2,7 @@
* drm gem CMA (contiguous memory allocator) helper functions
*
* Copyright (C) 2012 Sascha Hauer, Pengutronix
+ * Copyright (C) 2013 NVIDIA CORPORATION, All rights reserved.
*
* Based on Samsung Exynos code
*
@@ -40,30 +41,25 @@ static void drm_gem_cma_buf_destroy(struct drm_device *drm,
}

/*
- * drm_gem_cma_create - allocate an object with the given size
+ * drm_gem_cma_object_init - allocate buffer and initialize given cma object
*
- * returns a struct drm_gem_cma_object* on success or ERR_PTR values
- * on failure.
+ * this function allocates memory for a cma buffer and initializes the given
+ * cma object to use the allocated buffer.
*/
-struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
- unsigned int size)
+
+int drm_gem_cma_object_init(struct drm_device *drm,
+ struct drm_gem_cma_object *cma_obj, unsigned int size)
{
- struct drm_gem_cma_object *cma_obj;
struct drm_gem_object *gem_obj;
int ret;

size = round_up(size, PAGE_SIZE);

- cma_obj = kzalloc(sizeof(*cma_obj), GFP_KERNEL);
- if (!cma_obj)
- return ERR_PTR(-ENOMEM);
-
cma_obj->vaddr = dma_alloc_writecombine(drm->dev, size,
&cma_obj->paddr, GFP_KERNEL | __GFP_NOWARN);
if (!cma_obj->vaddr) {
dev_err(drm->dev, "failed to allocate buffer with size %d\n", size);
- ret = -ENOMEM;
- goto err_dma_alloc;
+ return -ENOMEM;
}

gem_obj = &cma_obj->base;
@@ -76,7 +72,7 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
if (ret)
goto err_create_mmap_offset;

- return cma_obj;
+ return 0;

err_create_mmap_offset:
drm_gem_object_release(gem_obj);
@@ -84,10 +80,36 @@ err_create_mmap_offset:
err_obj_init:
drm_gem_cma_buf_destroy(drm, cma_obj);

-err_dma_alloc:
- kfree(cma_obj);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gem_cma_object_init);
+
+/*
+ * drm_gem_cma_create - allocate an object with the given size
+ *
+ * returns a struct drm_gem_cma_object* on success or ERR_PTR values
+ * on failure.
+ */
+struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
+ unsigned int size)
+{
+ struct drm_gem_cma_object *cma_obj;
+ int ret;
+
+ size = round_up(size, PAGE_SIZE);
+
+ cma_obj = kzalloc(sizeof(*cma_obj), GFP_KERNEL);
+ if (!cma_obj)
+ return ERR_PTR(-ENOMEM);
+
+ ret = drm_gem_cma_object_init(drm, cma_obj, size);
+ if (ret) {
+ kfree(cma_obj);
+ return ERR_PTR(ret);
+ }
+
+ return cma_obj;

- return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(drm_gem_cma_create);

@@ -133,22 +155,32 @@ err_handle_create:
}

/*
- * drm_gem_cma_free_object - (struct drm_driver)->gem_free_object callback
- * function
+ * drm_gem_cma_deinit_object - deinitialize cma object
+ *
+ * this function deinitializes the given cma object without releasing the
+ * object memory. this function is a counterpart for the function
+ * drm_gem_cma_object_init().
*/
-void drm_gem_cma_free_object(struct drm_gem_object *gem_obj)
+void drm_gem_cma_object_deinit(struct drm_gem_object *gem_obj)
{
- struct drm_gem_cma_object *cma_obj;
+ struct drm_gem_cma_object *cma_obj = to_drm_gem_cma_obj(gem_obj);

if (gem_obj->map_list.map)
drm_gem_free_mmap_offset(gem_obj);

drm_gem_object_release(gem_obj);
-
- cma_obj = to_drm_gem_cma_obj(gem_obj);
-
drm_gem_cma_buf_destroy(gem_obj->dev, cma_obj);
+}
+EXPORT_SYMBOL_GPL(drm_gem_cma_object_deinit);

+/*
+ * drm_gem_cma_free_object - (struct drm_driver)->gem_free_object callback
+ * function
+ */
+void drm_gem_cma_free_object(struct drm_gem_object *gem_obj)
+{
+ struct drm_gem_cma_object *cma_obj = to_drm_gem_cma_obj(gem_obj);
+ drm_gem_cma_object_deinit(gem_obj);
kfree(cma_obj);
}
EXPORT_SYMBOL_GPL(drm_gem_cma_free_object);
diff --git a/include/drm/drm_gem_cma_helper.h b/include/drm/drm_gem_cma_helper.h
index 63397ce..5fdccb3 100644
--- a/include/drm/drm_gem_cma_helper.h
+++ b/include/drm/drm_gem_cma_helper.h
@@ -13,6 +13,10 @@ to_drm_gem_cma_obj(struct drm_gem_object *gem_obj)
return container_of(gem_obj, struct drm_gem_cma_object, base);
}

+/* deinitialize gem object and release the buffer. this variant does not
+ * release the cma object memory */
+void drm_gem_cma_object_deinit(struct drm_gem_object *gem_obj);
+
/* free gem object. */
void drm_gem_cma_free_object(struct drm_gem_object *gem_obj);

@@ -35,6 +39,11 @@ int drm_gem_cma_mmap(struct file *filp, struct vm_area_struct *vma);
int drm_gem_cma_dumb_destroy(struct drm_file *file_priv,
struct drm_device *drm, unsigned int handle);

+/* allocate physical memory for a buffer and initialize already allocated
+ * to use the buffer */
+int drm_gem_cma_object_init(struct drm_device *drm,
+ struct drm_gem_cma_object *cma_obj, unsigned int size);
+
/* allocate physical memory. */
struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
unsigned int size);
--
1.7.9.5

2013-03-13 12:38:09

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 08/10] gpu: host1x: Remove second host1x driver

Remove second host1x driver, and bind tegra-drm to the new host1x
driver. The logic to parse device tree and track clients is moved
to drm.c.

Signed-off-by: Arto Merilainen <[email protected]>
Signed-off-by: Terje Bergstrom <[email protected]>
---
drivers/gpu/host1x/Makefile | 2 +-
drivers/gpu/host1x/dev.c | 58 ++++++-
drivers/gpu/host1x/dev.h | 6 +
drivers/gpu/host1x/drm/Kconfig | 2 +-
drivers/gpu/host1x/drm/dc.c | 5 +-
drivers/gpu/host1x/drm/drm.c | 217 +++++++++++++++++++++++-
drivers/gpu/host1x/drm/drm.h | 3 -
drivers/gpu/host1x/drm/hdmi.c | 5 +-
drivers/gpu/host1x/drm/host1x.c | 329 ------------------------------------
drivers/gpu/host1x/host1x_client.h | 35 ++++
10 files changed, 318 insertions(+), 344 deletions(-)
delete mode 100644 drivers/gpu/host1x/drm/host1x.c
create mode 100644 drivers/gpu/host1x/host1x_client.h

diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile
index 4761e8a..9a6fc76 100644
--- a/drivers/gpu/host1x/Makefile
+++ b/drivers/gpu/host1x/Makefile
@@ -13,6 +13,6 @@ host1x-y = \
ccflags-y += -Iinclude/drm
ccflags-$(CONFIG_DRM_TEGRA_DEBUG) += -DDEBUG

-host1x-$(CONFIG_DRM_TEGRA) += drm/drm.o drm/fb.o drm/dc.o drm/host1x.o
+host1x-$(CONFIG_DRM_TEGRA) += drm/drm.o drm/fb.o drm/dc.o
host1x-$(CONFIG_DRM_TEGRA) += drm/output.o drm/rgb.o drm/hdmi.o
obj-$(CONFIG_TEGRA_HOST1X) += host1x.o
diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index d7c6e3e..6af8081 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -32,6 +32,19 @@
#include "channel.h"
#include "debug.h"
#include "hw/host1x01.h"
+#include "host1x_client.h"
+
+void host1x_set_drm_data(struct device *dev, void *data)
+{
+ struct host1x *host1x = dev_get_drvdata(dev);
+ host1x->drm_data = data;
+}
+
+void *host1x_get_drm_data(struct device *dev)
+{
+ struct host1x *host1x = dev_get_drvdata(dev);
+ return host1x->drm_data;
+}

void host1x_sync_writel(struct host1x *host1x, u32 v, u32 r)
{
@@ -154,6 +167,8 @@ static int host1x_probe(struct platform_device *pdev)

host1x_debug_init(host);

+ host1x_drm_alloc(pdev);
+
return 0;

fail_deinit_syncpt:
@@ -170,7 +185,7 @@ static int __exit host1x_remove(struct platform_device *pdev)
return 0;
}

-static struct platform_driver platform_driver = {
+static struct platform_driver tegra_host1x_driver = {
.probe = host1x_probe,
.remove = __exit_p(host1x_remove),
.driver = {
@@ -180,8 +195,47 @@ static struct platform_driver platform_driver = {
},
};

-module_platform_driver(platform_driver);
+static int __init tegra_host1x_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&tegra_host1x_driver);
+ if (err < 0)
+ return err;
+
+#ifdef CONFIG_DRM_TEGRA
+ err = platform_driver_register(&tegra_dc_driver);
+ if (err < 0)
+ goto unregister_host1x;
+
+ err = platform_driver_register(&tegra_hdmi_driver);
+ if (err < 0)
+ goto unregister_dc;
+#endif
+
+ return 0;
+
+#ifdef CONFIG_DRM_TEGRA
+unregister_dc:
+ platform_driver_unregister(&tegra_dc_driver);
+unregister_host1x:
+ platform_driver_unregister(&tegra_host1x_driver);
+ return err;
+#endif
+}
+module_init(tegra_host1x_init);
+
+static void __exit tegra_host1x_exit(void)
+{
+#ifdef CONFIG_DRM_TEGRA
+ platform_driver_unregister(&tegra_hdmi_driver);
+ platform_driver_unregister(&tegra_dc_driver);
+#endif
+ platform_driver_unregister(&tegra_host1x_driver);
+}
+module_exit(tegra_host1x_exit);

+MODULE_AUTHOR("Thierry Reding <[email protected]>");
MODULE_AUTHOR("Terje Bergstrom <[email protected]>");
MODULE_DESCRIPTION("Host1x driver for Tegra products");
MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h
index 4a56233..ca2ba8a 100644
--- a/drivers/gpu/host1x/dev.h
+++ b/drivers/gpu/host1x/dev.h
@@ -124,6 +124,8 @@ struct host1x {
unsigned int num_allocated_channels;

struct dentry *debugfs;
+
+ void *drm_data;
};

static inline struct host1x *host1x_get_host(struct device *dev)
@@ -307,4 +309,8 @@ static inline void host1x_hw_show_mlocks(struct host1x *host, struct output *o)
host->debug_op->show_mlocks(host, o);
}

+extern struct platform_driver tegra_hdmi_driver;
+extern struct platform_driver tegra_dc_driver;
+extern struct platform_driver tegra_gr2d_driver;
+
#endif
diff --git a/drivers/gpu/host1x/drm/Kconfig b/drivers/gpu/host1x/drm/Kconfig
index c92955d..8fb6545 100644
--- a/drivers/gpu/host1x/drm/Kconfig
+++ b/drivers/gpu/host1x/drm/Kconfig
@@ -1,5 +1,5 @@
config DRM_TEGRA
- tristate "NVIDIA Tegra DRM"
+ bool "NVIDIA Tegra DRM"
depends on DRM && OF && ARCH_TEGRA
select DRM_KMS_HELPER
select DRM_GEM_CMA_HELPER
diff --git a/drivers/gpu/host1x/drm/dc.c b/drivers/gpu/host1x/drm/dc.c
index d1f6609..29a79b6 100644
--- a/drivers/gpu/host1x/drm/dc.c
+++ b/drivers/gpu/host1x/drm/dc.c
@@ -16,6 +16,7 @@

#include "drm.h"
#include "dc.h"
+#include "host1x_client.h"

struct tegra_plane {
struct drm_plane base;
@@ -1097,7 +1098,7 @@ static const struct host1x_client_ops dc_client_ops = {

static int tegra_dc_probe(struct platform_device *pdev)
{
- struct host1x_drm *host1x = dev_get_drvdata(pdev->dev.parent);
+ struct host1x_drm *host1x = host1x_get_drm_data(pdev->dev.parent);
struct resource *regs;
struct tegra_dc *dc;
int err;
@@ -1160,7 +1161,7 @@ static int tegra_dc_probe(struct platform_device *pdev)

static int tegra_dc_remove(struct platform_device *pdev)
{
- struct host1x_drm *host1x = dev_get_drvdata(pdev->dev.parent);
+ struct host1x_drm *host1x = host1x_get_drm_data(pdev->dev.parent);
struct tegra_dc *dc = platform_get_drvdata(pdev);
int err;

diff --git a/drivers/gpu/host1x/drm/drm.c b/drivers/gpu/host1x/drm/drm.c
index 6c59bcd..dbd4808 100644
--- a/drivers/gpu/host1x/drm/drm.c
+++ b/drivers/gpu/host1x/drm/drm.c
@@ -7,14 +7,14 @@
* published by the Free Software Foundation.
*/

+#include <asm/dma-iommu.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
-
#include <linux/dma-mapping.h>
-#include <asm/dma-iommu.h>

#include "drm.h"
+#include "host1x_client.h"

#define DRIVER_NAME "tegra"
#define DRIVER_DESC "NVIDIA Tegra graphics"
@@ -23,13 +23,222 @@
#define DRIVER_MINOR 0
#define DRIVER_PATCHLEVEL 0

+struct host1x_drm_client {
+ struct host1x_client *client;
+ struct device_node *np;
+ struct list_head list;
+};
+
+static int host1x_add_drm_client(struct host1x_drm *host1x,
+ struct device_node *np)
+{
+ struct host1x_drm_client *client;
+
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&client->list);
+ client->np = of_node_get(np);
+
+ list_add_tail(&client->list, &host1x->drm_clients);
+
+ return 0;
+}
+
+static int host1x_activate_drm_client(struct host1x_drm *host1x,
+ struct host1x_drm_client *drm,
+ struct host1x_client *client)
+{
+ mutex_lock(&host1x->drm_clients_lock);
+ list_del_init(&drm->list);
+ list_add_tail(&drm->list, &host1x->drm_active);
+ drm->client = client;
+ mutex_unlock(&host1x->drm_clients_lock);
+
+ return 0;
+}
+
+static int host1x_remove_drm_client(struct host1x_drm *host1x,
+ struct host1x_drm_client *client)
+{
+ mutex_lock(&host1x->drm_clients_lock);
+ list_del_init(&client->list);
+ mutex_unlock(&host1x->drm_clients_lock);
+
+ of_node_put(client->np);
+ kfree(client);
+
+ return 0;
+}
+
+static int host1x_parse_dt(struct host1x_drm *host1x)
+{
+ static const char * const compat[] = {
+ "nvidia,tegra20-dc",
+ "nvidia,tegra20-hdmi",
+ "nvidia,tegra30-dc",
+ "nvidia,tegra30-hdmi",
+ };
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(compat); i++) {
+ struct device_node *np;
+
+ for_each_child_of_node(host1x->dev->of_node, np) {
+ if (of_device_is_compatible(np, compat[i]) &&
+ of_device_is_available(np)) {
+ err = host1x_add_drm_client(host1x, np);
+ if (err < 0)
+ return err;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int host1x_drm_alloc(struct platform_device *pdev)
+{
+ struct host1x_drm *host1x;
+ int err;
+
+ host1x = devm_kzalloc(&pdev->dev, sizeof(*host1x), GFP_KERNEL);
+ if (!host1x)
+ return -ENOMEM;
+
+ mutex_init(&host1x->drm_clients_lock);
+ INIT_LIST_HEAD(&host1x->drm_clients);
+ INIT_LIST_HEAD(&host1x->drm_active);
+ mutex_init(&host1x->clients_lock);
+ INIT_LIST_HEAD(&host1x->clients);
+ host1x->dev = &pdev->dev;
+
+ err = host1x_parse_dt(host1x);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to parse DT: %d\n", err);
+ return err;
+ }
+
+ host1x_set_drm_data(&pdev->dev, host1x);
+
+ return 0;
+}
+
+int host1x_drm_init(struct host1x_drm *host1x, struct drm_device *drm)
+{
+ struct host1x_client *client;
+
+ mutex_lock(&host1x->clients_lock);
+
+ list_for_each_entry(client, &host1x->clients, list) {
+ if (client->ops && client->ops->drm_init) {
+ int err = client->ops->drm_init(client, drm);
+ if (err < 0) {
+ dev_err(host1x->dev,
+ "DRM setup failed for %s: %d\n",
+ dev_name(client->dev), err);
+ return err;
+ }
+ }
+ }
+
+ mutex_unlock(&host1x->clients_lock);
+
+ return 0;
+}
+
+int host1x_drm_exit(struct host1x_drm *host1x)
+{
+ struct platform_device *pdev = to_platform_device(host1x->dev);
+ struct host1x_client *client;
+
+ if (!host1x->drm)
+ return 0;
+
+ mutex_lock(&host1x->clients_lock);
+
+ list_for_each_entry_reverse(client, &host1x->clients, list) {
+ if (client->ops && client->ops->drm_exit) {
+ int err = client->ops->drm_exit(client);
+ if (err < 0) {
+ dev_err(host1x->dev,
+ "DRM cleanup failed for %s: %d\n",
+ dev_name(client->dev), err);
+ return err;
+ }
+ }
+ }
+
+ mutex_unlock(&host1x->clients_lock);
+
+ drm_platform_exit(&tegra_drm_driver, pdev);
+ host1x->drm = NULL;
+
+ return 0;
+}
+
+int host1x_register_client(struct host1x_drm *host1x,
+ struct host1x_client *client)
+{
+ struct host1x_drm_client *drm, *tmp;
+ int err;
+
+ mutex_lock(&host1x->clients_lock);
+ list_add_tail(&client->list, &host1x->clients);
+ mutex_unlock(&host1x->clients_lock);
+
+ list_for_each_entry_safe(drm, tmp, &host1x->drm_clients, list)
+ if (drm->np == client->dev->of_node)
+ host1x_activate_drm_client(host1x, drm, client);
+
+ if (list_empty(&host1x->drm_clients)) {
+ struct platform_device *pdev = to_platform_device(host1x->dev);
+
+ err = drm_platform_init(&tegra_drm_driver, pdev);
+ if (err < 0) {
+ dev_err(host1x->dev, "drm_platform_init(): %d\n", err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+int host1x_unregister_client(struct host1x_drm *host1x,
+ struct host1x_client *client)
+{
+ struct host1x_drm_client *drm, *tmp;
+ int err;
+
+ list_for_each_entry_safe(drm, tmp, &host1x->drm_active, list) {
+ if (drm->client == client) {
+ err = host1x_drm_exit(host1x);
+ if (err < 0) {
+ dev_err(host1x->dev, "host1x_drm_exit(): %d\n",
+ err);
+ return err;
+ }
+
+ host1x_remove_drm_client(host1x, drm);
+ break;
+ }
+ }
+
+ mutex_lock(&host1x->clients_lock);
+ list_del_init(&client->list);
+ mutex_unlock(&host1x->clients_lock);
+
+ return 0;
+}
+
static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
{
- struct device *dev = drm->dev;
struct host1x_drm *host1x;
int err;

- host1x = dev_get_drvdata(dev);
+ host1x = host1x_get_drm_data(drm->dev);
drm->dev_private = host1x;
host1x->drm = drm;

diff --git a/drivers/gpu/host1x/drm/drm.h b/drivers/gpu/host1x/drm/drm.h
index 7fedb6c..0b8738f 100644
--- a/drivers/gpu/host1x/drm/drm.h
+++ b/drivers/gpu/host1x/drm/drm.h
@@ -229,9 +229,6 @@ extern int tegra_output_exit(struct tegra_output *output);
extern int tegra_drm_fb_init(struct drm_device *drm);
extern void tegra_drm_fb_exit(struct drm_device *drm);

-extern struct platform_driver tegra_host1x_driver;
-extern struct platform_driver tegra_hdmi_driver;
-extern struct platform_driver tegra_dc_driver;
extern struct drm_driver tegra_drm_driver;

#endif /* HOST1X_DRM_H */
diff --git a/drivers/gpu/host1x/drm/hdmi.c b/drivers/gpu/host1x/drm/hdmi.c
index f438f80..01097da 100644
--- a/drivers/gpu/host1x/drm/hdmi.c
+++ b/drivers/gpu/host1x/drm/hdmi.c
@@ -22,6 +22,7 @@
#include "hdmi.h"
#include "drm.h"
#include "dc.h"
+#include "host1x_client.h"

struct tegra_hdmi {
struct host1x_client client;
@@ -1189,7 +1190,7 @@ static const struct host1x_client_ops hdmi_client_ops = {

static int tegra_hdmi_probe(struct platform_device *pdev)
{
- struct host1x_drm *host1x = dev_get_drvdata(pdev->dev.parent);
+ struct host1x_drm *host1x = host1x_get_drm_data(pdev->dev.parent);
struct tegra_hdmi *hdmi;
struct resource *regs;
int err;
@@ -1278,7 +1279,7 @@ static int tegra_hdmi_probe(struct platform_device *pdev)

static int tegra_hdmi_remove(struct platform_device *pdev)
{
- struct host1x_drm *host1x = dev_get_drvdata(pdev->dev.parent);
+ struct host1x_drm *host1x = host1x_get_drm_data(pdev->dev.parent);
struct tegra_hdmi *hdmi = platform_get_drvdata(pdev);
int err;

diff --git a/drivers/gpu/host1x/drm/host1x.c b/drivers/gpu/host1x/drm/host1x.c
deleted file mode 100644
index c3d8ee5..0000000
--- a/drivers/gpu/host1x/drm/host1x.c
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2012 Avionic Design GmbH
- * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
-
-#include <linux/clk.h>
-#include <linux/err.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/platform_device.h>
-
-#include "drm.h"
-
-struct host1x_drm_client {
- struct host1x_client *client;
- struct device_node *np;
- struct list_head list;
-};
-
-static int host1x_add_drm_client(struct host1x_drm *host1x,
- struct device_node *np)
-{
- struct host1x_drm_client *client;
-
- client = kzalloc(sizeof(*client), GFP_KERNEL);
- if (!client)
- return -ENOMEM;
-
- INIT_LIST_HEAD(&client->list);
- client->np = of_node_get(np);
-
- list_add_tail(&client->list, &host1x->drm_clients);
-
- return 0;
-}
-
-static int host1x_activate_drm_client(struct host1x_drm *host1x,
- struct host1x_drm_client *drm,
- struct host1x_client *client)
-{
- mutex_lock(&host1x->drm_clients_lock);
- list_del_init(&drm->list);
- list_add_tail(&drm->list, &host1x->drm_active);
- drm->client = client;
- mutex_unlock(&host1x->drm_clients_lock);
-
- return 0;
-}
-
-static int host1x_remove_drm_client(struct host1x_drm *host1x,
- struct host1x_drm_client *client)
-{
- mutex_lock(&host1x->drm_clients_lock);
- list_del_init(&client->list);
- mutex_unlock(&host1x->drm_clients_lock);
-
- of_node_put(client->np);
- kfree(client);
-
- return 0;
-}
-
-static int host1x_parse_dt(struct host1x_drm *host1x)
-{
- static const char * const compat[] = {
- "nvidia,tegra20-dc",
- "nvidia,tegra20-hdmi",
- "nvidia,tegra30-dc",
- "nvidia,tegra30-hdmi",
- };
- unsigned int i;
- int err;
-
- for (i = 0; i < ARRAY_SIZE(compat); i++) {
- struct device_node *np;
-
- for_each_child_of_node(host1x->dev->of_node, np) {
- if (of_device_is_compatible(np, compat[i]) &&
- of_device_is_available(np)) {
- err = host1x_add_drm_client(host1x, np);
- if (err < 0)
- return err;
- }
- }
- }
-
- return 0;
-}
-
-static int tegra_host1x_probe(struct platform_device *pdev)
-{
- struct host1x_drm *host1x;
- struct resource *regs;
- int err;
-
- host1x = devm_kzalloc(&pdev->dev, sizeof(*host1x), GFP_KERNEL);
- if (!host1x)
- return -ENOMEM;
-
- mutex_init(&host1x->drm_clients_lock);
- INIT_LIST_HEAD(&host1x->drm_clients);
- INIT_LIST_HEAD(&host1x->drm_active);
- mutex_init(&host1x->clients_lock);
- INIT_LIST_HEAD(&host1x->clients);
- host1x->dev = &pdev->dev;
-
- err = host1x_parse_dt(host1x);
- if (err < 0) {
- dev_err(&pdev->dev, "failed to parse DT: %d\n", err);
- return err;
- }
-
- host1x->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(host1x->clk))
- return PTR_ERR(host1x->clk);
-
- err = clk_prepare_enable(host1x->clk);
- if (err < 0)
- return err;
-
- regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!regs) {
- err = -ENXIO;
- goto err;
- }
-
- err = platform_get_irq(pdev, 0);
- if (err < 0)
- goto err;
-
- host1x->syncpt = err;
-
- err = platform_get_irq(pdev, 1);
- if (err < 0)
- goto err;
-
- host1x->irq = err;
-
- host1x->regs = devm_ioremap_resource(&pdev->dev, regs);
- if (IS_ERR(host1x->regs)) {
- err = PTR_ERR(host1x->regs);
- goto err;
- }
-
- platform_set_drvdata(pdev, host1x);
-
- return 0;
-
-err:
- clk_disable_unprepare(host1x->clk);
- return err;
-}
-
-static int tegra_host1x_remove(struct platform_device *pdev)
-{
- struct host1x_drm *host1x = platform_get_drvdata(pdev);
-
- clk_disable_unprepare(host1x->clk);
-
- return 0;
-}
-
-int host1x_drm_init(struct host1x_drm *host1x, struct drm_device *drm)
-{
- struct host1x_client *client;
-
- mutex_lock(&host1x->clients_lock);
-
- list_for_each_entry(client, &host1x->clients, list) {
- if (client->ops && client->ops->drm_init) {
- int err = client->ops->drm_init(client, drm);
- if (err < 0) {
- dev_err(host1x->dev,
- "DRM setup failed for %s: %d\n",
- dev_name(client->dev), err);
- return err;
- }
- }
- }
-
- mutex_unlock(&host1x->clients_lock);
-
- return 0;
-}
-
-int host1x_drm_exit(struct host1x_drm *host1x)
-{
- struct platform_device *pdev = to_platform_device(host1x->dev);
- struct host1x_client *client;
-
- if (!host1x->drm)
- return 0;
-
- mutex_lock(&host1x->clients_lock);
-
- list_for_each_entry_reverse(client, &host1x->clients, list) {
- if (client->ops && client->ops->drm_exit) {
- int err = client->ops->drm_exit(client);
- if (err < 0) {
- dev_err(host1x->dev,
- "DRM cleanup failed for %s: %d\n",
- dev_name(client->dev), err);
- return err;
- }
- }
- }
-
- mutex_unlock(&host1x->clients_lock);
-
- drm_platform_exit(&tegra_drm_driver, pdev);
- host1x->drm = NULL;
-
- return 0;
-}
-
-int host1x_register_client(struct host1x_drm *host1x,
- struct host1x_client *client)
-{
- struct host1x_drm_client *drm, *tmp;
- int err;
-
- mutex_lock(&host1x->clients_lock);
- list_add_tail(&client->list, &host1x->clients);
- mutex_unlock(&host1x->clients_lock);
-
- list_for_each_entry_safe(drm, tmp, &host1x->drm_clients, list)
- if (drm->np == client->dev->of_node)
- host1x_activate_drm_client(host1x, drm, client);
-
- if (list_empty(&host1x->drm_clients)) {
- struct platform_device *pdev = to_platform_device(host1x->dev);
-
- err = drm_platform_init(&tegra_drm_driver, pdev);
- if (err < 0) {
- dev_err(host1x->dev, "drm_platform_init(): %d\n", err);
- return err;
- }
- }
-
- client->host1x = host1x;
-
- return 0;
-}
-
-int host1x_unregister_client(struct host1x_drm *host1x,
- struct host1x_client *client)
-{
- struct host1x_drm_client *drm, *tmp;
- int err;
-
- list_for_each_entry_safe(drm, tmp, &host1x->drm_active, list) {
- if (drm->client == client) {
- err = host1x_drm_exit(host1x);
- if (err < 0) {
- dev_err(host1x->dev, "host1x_drm_exit(): %d\n",
- err);
- return err;
- }
-
- host1x_remove_drm_client(host1x, drm);
- break;
- }
- }
-
- mutex_lock(&host1x->clients_lock);
- list_del_init(&client->list);
- mutex_unlock(&host1x->clients_lock);
-
- return 0;
-}
-
-static struct of_device_id tegra_host1x_of_match[] = {
- { .compatible = "nvidia,tegra30-host1x", },
- { .compatible = "nvidia,tegra20-host1x", },
- { },
-};
-MODULE_DEVICE_TABLE(of, tegra_host1x_of_match);
-
-struct platform_driver tegra_host1x_driver = {
- .driver = {
- .name = "tegra-host1x",
- .owner = THIS_MODULE,
- .of_match_table = tegra_host1x_of_match,
- },
- .probe = tegra_host1x_probe,
- .remove = tegra_host1x_remove,
-};
-
-static int __init tegra_host1x_init(void)
-{
- int err;
-
- err = platform_driver_register(&tegra_host1x_driver);
- if (err < 0)
- return err;
-
- err = platform_driver_register(&tegra_dc_driver);
- if (err < 0)
- goto unregister_host1x;
-
- err = platform_driver_register(&tegra_hdmi_driver);
- if (err < 0)
- goto unregister_dc;
-
- return 0;
-
-unregister_dc:
- platform_driver_unregister(&tegra_dc_driver);
-unregister_host1x:
- platform_driver_unregister(&tegra_host1x_driver);
- return err;
-}
-module_init(tegra_host1x_init);
-
-static void __exit tegra_host1x_exit(void)
-{
- platform_driver_unregister(&tegra_hdmi_driver);
- platform_driver_unregister(&tegra_dc_driver);
- platform_driver_unregister(&tegra_host1x_driver);
-}
-module_exit(tegra_host1x_exit);
-
-MODULE_AUTHOR("Thierry Reding <[email protected]>");
-MODULE_DESCRIPTION("NVIDIA Tegra DRM driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/host1x/host1x_client.h b/drivers/gpu/host1x/host1x_client.h
new file mode 100644
index 0000000..9b85f10
--- /dev/null
+++ b/drivers/gpu/host1x/host1x_client.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HOST1X_CLIENT_H
+#define HOST1X_CLIENT_H
+
+struct device;
+struct platform_device;
+
+#ifdef CONFIG_DRM_TEGRA
+int host1x_drm_alloc(struct platform_device *pdev);
+#else
+static inline int host1x_drm_alloc(struct platform_device *pdev)
+{
+ return 0;
+}
+#endif
+
+void host1x_set_drm_data(struct device *dev, void *data);
+void *host1x_get_drm_data(struct device *dev);
+
+#endif
--
1.7.9.5

2013-03-13 12:38:55

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 06/10] drm: tegra: Move drm to live under host1x

Make drm part of host1x driver.

Signed-off-by: Arto Merilainen <[email protected]>
Signed-off-by: Terje Bergstrom <[email protected]>
---
drivers/gpu/drm/Kconfig | 2 --
drivers/gpu/drm/Makefile | 1 -
drivers/gpu/drm/tegra/Makefile | 7 -------
drivers/gpu/host1x/Kconfig | 2 ++
drivers/gpu/host1x/Makefile | 5 +++++
drivers/gpu/{drm/tegra => host1x/drm}/Kconfig | 0
drivers/gpu/{drm/tegra => host1x/drm}/dc.c | 0
drivers/gpu/{drm/tegra => host1x/drm}/dc.h | 0
drivers/gpu/{drm/tegra => host1x/drm}/drm.c | 0
drivers/gpu/{drm/tegra => host1x/drm}/drm.h | 6 +++---
drivers/gpu/{drm/tegra => host1x/drm}/fb.c | 0
drivers/gpu/{drm/tegra => host1x/drm}/hdmi.c | 0
drivers/gpu/{drm/tegra => host1x/drm}/hdmi.h | 0
drivers/gpu/{drm/tegra => host1x/drm}/host1x.c | 0
drivers/gpu/{drm/tegra => host1x/drm}/output.c | 0
drivers/gpu/{drm/tegra => host1x/drm}/rgb.c | 0
16 files changed, 10 insertions(+), 13 deletions(-)
delete mode 100644 drivers/gpu/drm/tegra/Makefile
rename drivers/gpu/{drm/tegra => host1x/drm}/Kconfig (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/dc.c (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/dc.h (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/drm.c (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/drm.h (98%)
rename drivers/gpu/{drm/tegra => host1x/drm}/fb.c (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/hdmi.c (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/hdmi.h (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/host1x.c (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/output.c (100%)
rename drivers/gpu/{drm/tegra => host1x/drm}/rgb.c (100%)

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 1e82882..9031bb7 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -215,8 +215,6 @@ source "drivers/gpu/drm/cirrus/Kconfig"

source "drivers/gpu/drm/shmobile/Kconfig"

-source "drivers/gpu/drm/tegra/Kconfig"
-
source "drivers/gpu/drm/omapdrm/Kconfig"

source "drivers/gpu/drm/tilcdc/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0d59b24..847b830 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -49,7 +49,6 @@ obj-$(CONFIG_DRM_GMA500) += gma500/
obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
-obj-$(CONFIG_DRM_TEGRA) += tegra/
obj-$(CONFIG_DRM_OMAP) += omapdrm/
obj-$(CONFIG_DRM_TILCDC) += tilcdc/
obj-y += i2c/
diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile
deleted file mode 100644
index 80f73d1..0000000
--- a/drivers/gpu/drm/tegra/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-ccflags-y := -Iinclude/drm
-ccflags-$(CONFIG_DRM_TEGRA_DEBUG) += -DDEBUG
-
-tegra-drm-y := drm.o fb.o dc.o host1x.o
-tegra-drm-y += output.o rgb.o hdmi.o
-
-obj-$(CONFIG_DRM_TEGRA) += tegra-drm.o
diff --git a/drivers/gpu/host1x/Kconfig b/drivers/gpu/host1x/Kconfig
index 00f0859..ee3af1e 100644
--- a/drivers/gpu/host1x/Kconfig
+++ b/drivers/gpu/host1x/Kconfig
@@ -18,4 +18,6 @@ config TEGRA_HOST1X_FIREWALL

If unsure, choose Y.

+source "drivers/gpu/host1x/drm/Kconfig"
+
endif
diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile
index 49fd580..4761e8a 100644
--- a/drivers/gpu/host1x/Makefile
+++ b/drivers/gpu/host1x/Makefile
@@ -10,4 +10,9 @@ host1x-y = \
debug.o \
hw/host1x01.o

+ccflags-y += -Iinclude/drm
+ccflags-$(CONFIG_DRM_TEGRA_DEBUG) += -DDEBUG
+
+host1x-$(CONFIG_DRM_TEGRA) += drm/drm.o drm/fb.o drm/dc.o drm/host1x.o
+host1x-$(CONFIG_DRM_TEGRA) += drm/output.o drm/rgb.o drm/hdmi.o
obj-$(CONFIG_TEGRA_HOST1X) += host1x.o
diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/host1x/drm/Kconfig
similarity index 100%
rename from drivers/gpu/drm/tegra/Kconfig
rename to drivers/gpu/host1x/drm/Kconfig
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/host1x/drm/dc.c
similarity index 100%
rename from drivers/gpu/drm/tegra/dc.c
rename to drivers/gpu/host1x/drm/dc.c
diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/host1x/drm/dc.h
similarity index 100%
rename from drivers/gpu/drm/tegra/dc.h
rename to drivers/gpu/host1x/drm/dc.h
diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/host1x/drm/drm.c
similarity index 100%
rename from drivers/gpu/drm/tegra/drm.c
rename to drivers/gpu/host1x/drm/drm.c
diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/host1x/drm/drm.h
similarity index 98%
rename from drivers/gpu/drm/tegra/drm.h
rename to drivers/gpu/host1x/drm/drm.h
index 6dd75a2..a6c011d 100644
--- a/drivers/gpu/drm/tegra/drm.h
+++ b/drivers/gpu/host1x/drm/drm.h
@@ -7,8 +7,8 @@
* published by the Free Software Foundation.
*/

-#ifndef TEGRA_DRM_H
-#define TEGRA_DRM_H 1
+#ifndef HOST1X_DRM_H
+#define HOST1X_DRM_H 1

#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
@@ -234,4 +234,4 @@ extern struct platform_driver tegra_hdmi_driver;
extern struct platform_driver tegra_dc_driver;
extern struct drm_driver tegra_drm_driver;

-#endif /* TEGRA_DRM_H */
+#endif /* HOST1X_DRM_H */
diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/host1x/drm/fb.c
similarity index 100%
rename from drivers/gpu/drm/tegra/fb.c
rename to drivers/gpu/host1x/drm/fb.c
diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/host1x/drm/hdmi.c
similarity index 100%
rename from drivers/gpu/drm/tegra/hdmi.c
rename to drivers/gpu/host1x/drm/hdmi.c
diff --git a/drivers/gpu/drm/tegra/hdmi.h b/drivers/gpu/host1x/drm/hdmi.h
similarity index 100%
rename from drivers/gpu/drm/tegra/hdmi.h
rename to drivers/gpu/host1x/drm/hdmi.h
diff --git a/drivers/gpu/drm/tegra/host1x.c b/drivers/gpu/host1x/drm/host1x.c
similarity index 100%
rename from drivers/gpu/drm/tegra/host1x.c
rename to drivers/gpu/host1x/drm/host1x.c
diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/host1x/drm/output.c
similarity index 100%
rename from drivers/gpu/drm/tegra/output.c
rename to drivers/gpu/host1x/drm/output.c
diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/host1x/drm/rgb.c
similarity index 100%
rename from drivers/gpu/drm/tegra/rgb.c
rename to drivers/gpu/host1x/drm/rgb.c
--
1.7.9.5

2013-03-13 12:38:54

by Terje Bergstrom

[permalink] [raw]
Subject: [PATCHv7 03/10] gpu: host1x: Add syncpoint wait and interrupts

Add support for sync point interrupts, and sync point wait. Sync
point wait used interrupts for unblocking wait.

Signed-off-by: Arto Merilainen <[email protected]>
Signed-off-by: Terje Bergstrom <[email protected]>
---
drivers/gpu/host1x/Makefile | 1 +
drivers/gpu/host1x/dev.c | 12 ++
drivers/gpu/host1x/dev.h | 51 +++++
drivers/gpu/host1x/hw/host1x01.c | 2 +
drivers/gpu/host1x/hw/hw_host1x01_sync.h | 42 ++++
drivers/gpu/host1x/hw/intr_hw.c | 143 +++++++++++++
drivers/gpu/host1x/intr.c | 328 ++++++++++++++++++++++++++++++
drivers/gpu/host1x/intr.h | 96 +++++++++
drivers/gpu/host1x/syncpt.c | 159 +++++++++++++++
drivers/gpu/host1x/syncpt.h | 14 +-
10 files changed, 847 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/host1x/hw/intr_hw.c
create mode 100644 drivers/gpu/host1x/intr.c
create mode 100644 drivers/gpu/host1x/intr.h

diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile
index 363e6ab..5ef47ff 100644
--- a/drivers/gpu/host1x/Makefile
+++ b/drivers/gpu/host1x/Makefile
@@ -3,6 +3,7 @@ ccflags-y = -Idrivers/gpu/host1x
host1x-y = \
syncpt.o \
dev.o \
+ intr.o \
hw/host1x01.o

obj-$(CONFIG_TEGRA_HOST1X) += host1x.o
diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index d95a9b2..4421e28 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -28,6 +28,7 @@
#include <trace/events/host1x.h>

#include "dev.h"
+#include "intr.h"
#include "hw/host1x01.h"

void host1x_sync_writel(struct host1x *host1x, u32 v, u32 r)
@@ -127,12 +128,23 @@ static int host1x_probe(struct platform_device *pdev)
return err;
}

+ err = host1x_intr_init(host, syncpt_irq);
+ if (err) {
+ dev_err(dev, "failed to init irq");
+ goto fail_deinit_syncpt;
+ }
+
return 0;
+
+fail_deinit_syncpt:
+ host1x_syncpt_deinit(host);
+ return err;
}

static int __exit host1x_remove(struct platform_device *pdev)
{
struct host1x *host = platform_get_drvdata(pdev);
+ host1x_intr_deinit(host);
host1x_syncpt_deinit(host);
clk_disable_unprepare(host->clk);
return 0;
diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h
index 750daa3..fb5f842 100644
--- a/drivers/gpu/host1x/dev.h
+++ b/drivers/gpu/host1x/dev.h
@@ -21,6 +21,7 @@
#include <linux/device.h>

#include "syncpt.h"
+#include "intr.h"

struct host1x_syncpt;

@@ -33,6 +34,17 @@ struct host1x_syncpt_ops {
int (*patch_wait)(struct host1x_syncpt *syncpt, void *patch_addr);
};

+struct host1x_intr_ops {
+ int (*init_host_sync)(struct host1x *host, u32 cpm,
+ void (*syncpt_thresh_work)(struct work_struct *work));
+ void (*set_syncpt_threshold)(
+ struct host1x *host, u32 id, u32 thresh);
+ void (*enable_syncpt_intr)(struct host1x *host, u32 id);
+ void (*disable_syncpt_intr)(struct host1x *host, u32 id);
+ void (*disable_all_syncpt_intrs)(struct host1x *host);
+ int (*free_syncpt_irq)(struct host1x *host);
+};
+
struct host1x_info {
int nb_channels; /* host1x: num channels supported */
int nb_pts; /* host1x: num syncpoints supported */
@@ -50,7 +62,13 @@ struct host1x {
struct device *dev;
struct clk *clk;

+ struct mutex intr_mutex;
+ struct workqueue_struct *intr_wq;
+ int intr_syncpt_irq;
+
const struct host1x_syncpt_ops *syncpt_op;
+ const struct host1x_intr_ops *intr_op;
+
};

static inline struct host1x *host1x_get_host(struct device *dev)
@@ -101,4 +119,37 @@ static inline int host1x_hw_syncpt_patch_wait(struct host1x *host,
return host->syncpt_op->patch_wait(sp, patch_addr);
}

+static inline int host1x_hw_intr_init_host_sync(struct host1x *host, u32 cpm,
+ void (*syncpt_thresh_work)(struct work_struct *))
+{
+ return host->intr_op->init_host_sync(host, cpm, syncpt_thresh_work);
+}
+
+static inline void host1x_hw_intr_set_syncpt_threshold(struct host1x *host,
+ u32 id, u32 thresh)
+{
+ host->intr_op->set_syncpt_threshold(host, id, thresh);
+}
+
+static inline void host1x_hw_intr_enable_syncpt_intr(struct host1x *host,
+ u32 id)
+{
+ host->intr_op->enable_syncpt_intr(host, id);
+}
+
+static inline void host1x_hw_intr_disable_syncpt_intr(struct host1x *host,
+ u32 id)
+{
+ host->intr_op->disable_syncpt_intr(host, id);
+}
+
+static inline void host1x_hw_intr_disable_all_syncpt_intrs(struct host1x *host)
+{
+ host->intr_op->disable_all_syncpt_intrs(host);
+}
+
+static inline int host1x_hw_intr_free_syncpt_irq(struct host1x *host)
+{
+ return host->intr_op->free_syncpt_irq(host);
+}
#endif
diff --git a/drivers/gpu/host1x/hw/host1x01.c b/drivers/gpu/host1x/hw/host1x01.c
index 612b457..f5c35fa 100644
--- a/drivers/gpu/host1x/hw/host1x01.c
+++ b/drivers/gpu/host1x/hw/host1x01.c
@@ -21,6 +21,7 @@
#include "hw/host1x01_hardware.h"

/* include code */
+#include "hw/intr_hw.c"
#include "hw/syncpt_hw.c"

#include "dev.h"
@@ -28,6 +29,7 @@
int host1x01_init(struct host1x *host)
{
host->syncpt_op = &host1x_syncpt_ops;
+ host->intr_op = &host1x_intr_ops;

return 0;
}
diff --git a/drivers/gpu/host1x/hw/hw_host1x01_sync.h b/drivers/gpu/host1x/hw/hw_host1x01_sync.h
index 3af258b..eea0bb0 100644
--- a/drivers/gpu/host1x/hw/hw_host1x01_sync.h
+++ b/drivers/gpu/host1x/hw/hw_host1x01_sync.h
@@ -59,6 +59,48 @@ static inline u32 host1x_sync_syncpt_r(unsigned int id)
}
#define HOST1X_SYNC_SYNCPT(id) \
host1x_sync_syncpt_r(id)
+static inline u32 host1x_sync_syncpt_thresh_cpu0_int_status_r(unsigned int id)
+{
+ return 0x40 + id * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(id) \
+ host1x_sync_syncpt_thresh_cpu0_int_status_r(id)
+static inline u32 host1x_sync_syncpt_thresh_int_disable_r(unsigned int id)
+{
+ return 0x60 + id * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(id) \
+ host1x_sync_syncpt_thresh_int_disable_r(id)
+static inline u32 host1x_sync_syncpt_thresh_int_enable_cpu0_r(unsigned int id)
+{
+ return 0x68 + id * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(id) \
+ host1x_sync_syncpt_thresh_int_enable_cpu0_r(id)
+static inline u32 host1x_sync_usec_clk_r(void)
+{
+ return 0x1a4;
+}
+#define HOST1X_SYNC_USEC_CLK \
+ host1x_sync_usec_clk_r()
+static inline u32 host1x_sync_ctxsw_timeout_cfg_r(void)
+{
+ return 0x1a8;
+}
+#define HOST1X_SYNC_CTXSW_TIMEOUT_CFG \
+ host1x_sync_ctxsw_timeout_cfg_r()
+static inline u32 host1x_sync_ip_busy_timeout_r(void)
+{
+ return 0x1bc;
+}
+#define HOST1X_SYNC_IP_BUSY_TIMEOUT \
+ host1x_sync_ip_busy_timeout_r()
+static inline u32 host1x_sync_syncpt_int_thresh_r(unsigned int id)
+{
+ return 0x500 + id * REGISTER_STRIDE;
+}
+#define HOST1X_SYNC_SYNCPT_INT_THRESH(id) \
+ host1x_sync_syncpt_int_thresh_r(id)
static inline u32 host1x_sync_syncpt_base_r(unsigned int id)
{
return 0x600 + id * REGISTER_STRIDE;
diff --git a/drivers/gpu/host1x/hw/intr_hw.c b/drivers/gpu/host1x/hw/intr_hw.c
new file mode 100644
index 0000000..b592eef
--- /dev/null
+++ b/drivers/gpu/host1x/hw/intr_hw.c
@@ -0,0 +1,143 @@
+/*
+ * Tegra host1x Interrupt Management
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <asm/mach/irq.h>
+
+#include "intr.h"
+#include "dev.h"
+
+/*
+ * Sync point threshold interrupt service function
+ * Handles sync point threshold triggers, in interrupt context
+ */
+static void host1x_intr_syncpt_handle(struct host1x_syncpt *syncpt)
+{
+ unsigned int id = syncpt->id;
+ struct host1x *host = syncpt->host;
+
+ host1x_sync_writel(host, BIT_MASK(id),
+ HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(BIT_WORD(id)));
+ host1x_sync_writel(host, BIT_MASK(id),
+ HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(BIT_WORD(id)));
+
+ queue_work(host->intr_wq, &syncpt->intr.work);
+}
+
+static irqreturn_t syncpt_thresh_isr(int irq, void *dev_id)
+{
+ struct host1x *host = dev_id;
+ unsigned long reg;
+ int i, id;
+
+ for (i = 0; i <= BIT_WORD(host->info->nb_pts); i++) {
+ reg = host1x_sync_readl(host,
+ HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(i));
+ for_each_set_bit(id, &reg, BITS_PER_LONG) {
+ struct host1x_syncpt *syncpt =
+ host->syncpt + (i * BITS_PER_LONG + id);
+ host1x_intr_syncpt_handle(syncpt);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void _host1x_intr_disable_all_syncpt_intrs(struct host1x *host)
+{
+ u32 i;
+
+ for (i = 0; i <= BIT_WORD(host->info->nb_pts); ++i) {
+ host1x_sync_writel(host, 0xffffffffu,
+ HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(i));
+ host1x_sync_writel(host, 0xffffffffu,
+ HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(i));
+ }
+}
+
+static int _host1x_intr_init_host_sync(struct host1x *host, u32 cpm,
+ void (*syncpt_thresh_work)(struct work_struct *))
+{
+ int i, err;
+
+ host1x_hw_intr_disable_all_syncpt_intrs(host);
+
+ for (i = 0; i < host->info->nb_pts; i++)
+ INIT_WORK(&host->syncpt[i].intr.work, syncpt_thresh_work);
+
+ err = devm_request_irq(host->dev, host->intr_syncpt_irq,
+ syncpt_thresh_isr, IRQF_SHARED,
+ "host1x_syncpt", host);
+ if (IS_ERR_VALUE(err)) {
+ WARN_ON(1);
+ return err;
+ }
+
+ /* disable the ip_busy_timeout. this prevents write drops */
+ host1x_sync_writel(host, 0, HOST1X_SYNC_IP_BUSY_TIMEOUT);
+
+ /*
+ * increase the auto-ack timout to the maximum value. 2d will hang
+ * otherwise on Tegra2.
+ */
+ host1x_sync_writel(host, 0xff, HOST1X_SYNC_CTXSW_TIMEOUT_CFG);
+
+ /* update host clocks per usec */
+ host1x_sync_writel(host, cpm, HOST1X_SYNC_USEC_CLK);
+
+ return 0;
+}
+
+static void _host1x_intr_set_syncpt_threshold(struct host1x *host,
+ u32 id, u32 thresh)
+{
+ host1x_sync_writel(host, thresh, HOST1X_SYNC_SYNCPT_INT_THRESH(id));
+}
+
+static void _host1x_intr_enable_syncpt_intr(struct host1x *host, u32 id)
+{
+ host1x_sync_writel(host, BIT_MASK(id),
+ HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(BIT_WORD(id)));
+}
+
+static void _host1x_intr_disable_syncpt_intr(struct host1x *host, u32 id)
+{
+ host1x_sync_writel(host, BIT_MASK(id),
+ HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(BIT_WORD(id)));
+ host1x_sync_writel(host, BIT_MASK(id),
+ HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(BIT_WORD(id)));
+}
+
+static int _host1x_free_syncpt_irq(struct host1x *host)
+{
+ devm_free_irq(host->dev, host->intr_syncpt_irq, host);
+ flush_workqueue(host->intr_wq);
+ return 0;
+}
+
+static const struct host1x_intr_ops host1x_intr_ops = {
+ .init_host_sync = _host1x_intr_init_host_sync,
+ .set_syncpt_threshold = _host1x_intr_set_syncpt_threshold,
+ .enable_syncpt_intr = _host1x_intr_enable_syncpt_intr,
+ .disable_syncpt_intr = _host1x_intr_disable_syncpt_intr,
+ .disable_all_syncpt_intrs = _host1x_intr_disable_all_syncpt_intrs,
+ .free_syncpt_irq = _host1x_free_syncpt_irq,
+};
diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c
new file mode 100644
index 0000000..b1b5a80
--- /dev/null
+++ b/drivers/gpu/host1x/intr.c
@@ -0,0 +1,328 @@
+/*
+ * Tegra host1x Interrupt Management
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+
+#include "dev.h"
+#include "intr.h"
+
+/* Wait list management */
+
+enum waitlist_state {
+ WLS_PENDING,
+ WLS_REMOVED,
+ WLS_CANCELLED,
+ WLS_HANDLED
+};
+
+static void waiter_release(struct kref *kref)
+{
+ kfree(container_of(kref, struct host1x_waitlist, refcount));
+}
+
+/*
+ * add a waiter to a waiter queue, sorted by threshold
+ * returns true if it was added at the head of the queue
+ */
+static bool add_waiter_to_queue(struct host1x_waitlist *waiter,
+ struct list_head *queue)
+{
+ struct host1x_waitlist *pos;
+ u32 thresh = waiter->thresh;
+
+ list_for_each_entry_reverse(pos, queue, list)
+ if ((s32)(pos->thresh - thresh) <= 0) {
+ list_add(&waiter->list, &pos->list);
+ return false;
+ }
+
+ list_add(&waiter->list, queue);
+ return true;
+}
+
+/*
+ * run through a waiter queue for a single sync point ID
+ * and gather all completed waiters into lists by actions
+ */
+static void remove_completed_waiters(struct list_head *head, u32 sync,
+ struct list_head completed[HOST1X_INTR_ACTION_COUNT])
+{
+ struct list_head *dest;
+ struct host1x_waitlist *waiter, *next;
+
+ list_for_each_entry_safe(waiter, next, head, list) {
+ if ((s32)(waiter->thresh - sync) > 0)
+ break;
+
+ dest = completed + waiter->action;
+
+ /* PENDING->REMOVED or CANCELLED->HANDLED */
+ if (atomic_inc_return(&waiter->state) == WLS_HANDLED || !dest) {
+ list_del(&waiter->list);
+ kref_put(&waiter->refcount, waiter_release);
+ } else
+ list_move_tail(&waiter->list, dest);
+ }
+}
+
+static void reset_threshold_interrupt(struct host1x *host,
+ struct list_head *head,
+ unsigned int id)
+{
+ u32 thresh =
+ list_first_entry(head, struct host1x_waitlist, list)->thresh;
+
+ host1x_hw_intr_set_syncpt_threshold(host, id, thresh);
+ host1x_hw_intr_enable_syncpt_intr(host, id);
+}
+
+static void action_wakeup(struct host1x_waitlist *waiter)
+{
+ wait_queue_head_t *wq = waiter->data;
+ wake_up(wq);
+}
+
+static void action_wakeup_interruptible(struct host1x_waitlist *waiter)
+{
+ wait_queue_head_t *wq = waiter->data;
+ wake_up_interruptible(wq);
+}
+
+typedef void (*action_handler)(struct host1x_waitlist *waiter);
+
+static action_handler action_handlers[HOST1X_INTR_ACTION_COUNT] = {
+ action_wakeup,
+ action_wakeup_interruptible,
+};
+
+static void run_handlers(struct list_head completed[HOST1X_INTR_ACTION_COUNT])
+{
+ struct list_head *head = completed;
+ int i;
+
+ for (i = 0; i < HOST1X_INTR_ACTION_COUNT; ++i, ++head) {
+ action_handler handler = action_handlers[i];
+ struct host1x_waitlist *waiter, *next;
+
+ list_for_each_entry_safe(waiter, next, head, list) {
+ list_del(&waiter->list);
+ handler(waiter);
+ WARN_ON(atomic_xchg(&waiter->state, WLS_HANDLED) !=
+ WLS_REMOVED);
+ kref_put(&waiter->refcount, waiter_release);
+ }
+ }
+}
+
+/*
+ * Remove & handle all waiters that have completed for the given syncpt
+ */
+static int process_wait_list(struct host1x *host,
+ struct host1x_syncpt *syncpt,
+ u32 threshold)
+{
+ struct list_head completed[HOST1X_INTR_ACTION_COUNT];
+ unsigned int i;
+ int empty;
+
+ for (i = 0; i < HOST1X_INTR_ACTION_COUNT; ++i)
+ INIT_LIST_HEAD(completed + i);
+
+ spin_lock(&syncpt->intr.lock);
+
+ remove_completed_waiters(&syncpt->intr.wait_head, threshold,
+ completed);
+
+ empty = list_empty(&syncpt->intr.wait_head);
+ if (empty)
+ host1x_hw_intr_disable_syncpt_intr(host, syncpt->id);
+ else
+ reset_threshold_interrupt(host, &syncpt->intr.wait_head,
+ syncpt->id);
+
+ spin_unlock(&syncpt->intr.lock);
+
+ run_handlers(completed);
+
+ return empty;
+}
+
+/*
+ * Sync point threshold interrupt service thread function
+ * Handles sync point threshold triggers, in thread context
+ */
+
+static void syncpt_thresh_work(struct work_struct *work)
+{
+ struct host1x_syncpt_intr *syncpt_intr =
+ container_of(work, struct host1x_syncpt_intr, work);
+ struct host1x_syncpt *syncpt =
+ container_of(syncpt_intr, struct host1x_syncpt, intr);
+ unsigned int id = syncpt->id;
+ struct host1x *host = syncpt->host;
+
+ (void)process_wait_list(host, syncpt,
+ host1x_syncpt_load(host->syncpt + id));
+}
+
+int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh,
+ enum host1x_intr_action action, void *data,
+ struct host1x_waitlist *waiter, void **ref)
+{
+ struct host1x_syncpt *syncpt;
+ int queue_was_empty;
+
+ if (waiter == NULL) {
+ pr_warn("%s: NULL waiter\n", __func__);
+ return -EINVAL;
+ }
+
+ /* initialize a new waiter */
+ INIT_LIST_HEAD(&waiter->list);
+ kref_init(&waiter->refcount);
+ if (ref)
+ kref_get(&waiter->refcount);
+ waiter->thresh = thresh;
+ waiter->action = action;
+ atomic_set(&waiter->state, WLS_PENDING);
+ waiter->data = data;
+ waiter->count = 1;
+
+ syncpt = host->syncpt + id;
+
+ spin_lock(&syncpt->intr.lock);
+
+ queue_was_empty = list_empty(&syncpt->intr.wait_head);
+
+ if (add_waiter_to_queue(waiter, &syncpt->intr.wait_head)) {
+ /* added at head of list - new threshold value */
+ host1x_hw_intr_set_syncpt_threshold(host, id, thresh);
+
+ /* added as first waiter - enable interrupt */
+ if (queue_was_empty)
+ host1x_hw_intr_enable_syncpt_intr(host, id);
+ }
+
+ spin_unlock(&syncpt->intr.lock);
+
+ if (ref)
+ *ref = waiter;
+ return 0;
+}
+
+void host1x_intr_put_ref(struct host1x *host, u32 id, void *ref)
+{
+ struct host1x_waitlist *waiter = ref;
+ struct host1x_syncpt *syncpt;
+
+ while (atomic_cmpxchg(&waiter->state, WLS_PENDING, WLS_CANCELLED) ==
+ WLS_REMOVED)
+ schedule();
+
+ syncpt = host->syncpt + id;
+ (void)process_wait_list(host, syncpt,
+ host1x_syncpt_load(host->syncpt + id));
+
+ kref_put(&waiter->refcount, waiter_release);
+}
+
+int host1x_intr_init(struct host1x *host, unsigned int irq_sync)
+{
+ unsigned int id;
+ u32 nb_pts = host1x_syncpt_nb_pts(host);
+
+ mutex_init(&host->intr_mutex);
+ host->intr_syncpt_irq = irq_sync;
+ host->intr_wq = create_workqueue("host_syncpt");
+ if (!host->intr_wq)
+ return -ENOMEM;
+
+ for (id = 0; id < nb_pts; ++id) {
+ struct host1x_syncpt *syncpt = host->syncpt + id;
+
+ spin_lock_init(&syncpt->intr.lock);
+ INIT_LIST_HEAD(&syncpt->intr.wait_head);
+ snprintf(syncpt->intr.thresh_irq_name,
+ sizeof(syncpt->intr.thresh_irq_name),
+ "host1x_sp_%02d", id);
+ }
+
+ host1x_intr_start(host);
+
+ return 0;
+}
+
+void host1x_intr_deinit(struct host1x *host)
+{
+ host1x_intr_stop(host);
+ destroy_workqueue(host->intr_wq);
+}
+
+void host1x_intr_start(struct host1x *host)
+{
+ u32 hz = clk_get_rate(host->clk);
+ int err;
+
+ mutex_lock(&host->intr_mutex);
+ err = host1x_hw_intr_init_host_sync(host, DIV_ROUND_UP(hz, 1000000),
+ syncpt_thresh_work);
+ if (err) {
+ mutex_unlock(&host->intr_mutex);
+ return;
+ }
+ mutex_unlock(&host->intr_mutex);
+}
+
+void host1x_intr_stop(struct host1x *host)
+{
+ unsigned int id;
+ struct host1x_syncpt *syncpt = host->syncpt;
+ u32 nb_pts = host1x_syncpt_nb_pts(host);
+
+ mutex_lock(&host->intr_mutex);
+
+ host1x_hw_intr_disable_all_syncpt_intrs(host);
+
+ for (id = 0; id < nb_pts; ++id) {
+ struct host1x_waitlist *waiter, *next;
+
+ list_for_each_entry_safe(waiter, next,
+ &syncpt[id].intr.wait_head, list) {
+ if (atomic_cmpxchg(&waiter->state,
+ WLS_CANCELLED, WLS_HANDLED) == WLS_CANCELLED) {
+ list_del(&waiter->list);
+ kref_put(&waiter->refcount, waiter_release);
+ }
+ }
+
+ if (!list_empty(&syncpt[id].intr.wait_head)) {
+ /* output diagnostics */
+ mutex_unlock(&host->intr_mutex);
+ pr_warn("%s cannot stop syncpt intr id=%d\n",
+ __func__, id);
+ return;
+ }
+ }
+
+ host1x_hw_intr_free_syncpt_irq(host);
+
+ mutex_unlock(&host->intr_mutex);
+}
diff --git a/drivers/gpu/host1x/intr.h b/drivers/gpu/host1x/intr.h
new file mode 100644
index 0000000..a3f06ab
--- /dev/null
+++ b/drivers/gpu/host1x/intr.h
@@ -0,0 +1,96 @@
+/*
+ * Tegra host1x Interrupt Management
+ *
+ * Copyright (c) 2010-2013, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HOST1X_INTR_H
+#define __HOST1X_INTR_H
+
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+
+struct host1x;
+
+enum host1x_intr_action {
+ /*
+ * Wake up a task.
+ * 'data' points to a wait_queue_head_t
+ */
+ HOST1X_INTR_ACTION_WAKEUP,
+
+ /*
+ * Wake up a interruptible task.
+ * 'data' points to a wait_queue_head_t
+ */
+ HOST1X_INTR_ACTION_WAKEUP_INTERRUPTIBLE,
+
+ HOST1X_INTR_ACTION_COUNT
+};
+
+struct host1x_syncpt_intr {
+ spinlock_t lock;
+ struct list_head wait_head;
+ char thresh_irq_name[12];
+ struct work_struct work;
+};
+
+struct host1x_waitlist {
+ struct list_head list;
+ struct kref refcount;
+ u32 thresh;
+ enum host1x_intr_action action;
+ atomic_t state;
+ void *data;
+ int count;
+};
+
+/*
+ * Schedule an action to be taken when a sync point reaches the given threshold.
+ *
+ * @id the sync point
+ * @thresh the threshold
+ * @action the action to take
+ * @data a pointer to extra data depending on action, see above
+ * @waiter waiter structure - assumes ownership
+ * @ref must be passed if cancellation is possible, else NULL
+ *
+ * This is a non-blocking api.
+ */
+int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh,
+ enum host1x_intr_action action, void *data,
+ struct host1x_waitlist *waiter, void **ref);
+
+/*
+ * Unreference an action submitted to host1x_intr_add_action().
+ * You must call this if you passed non-NULL as ref.
+ * @ref the ref returned from host1x_intr_add_action()
+ */
+void host1x_intr_put_ref(struct host1x *host, u32 id, void *ref);
+
+/* Initialize host1x sync point interrupt */
+int host1x_intr_init(struct host1x *host, unsigned int irq_sync);
+
+/* Deinitialize host1x sync point interrupt */
+void host1x_intr_deinit(struct host1x *host);
+
+/* Enable host1x sync point interrupt */
+void host1x_intr_start(struct host1x *host);
+
+/* Disable host1x sync point interrupt */
+void host1x_intr_stop(struct host1x *host);
+
+irqreturn_t host1x_syncpt_thresh_fn(void *dev_id);
+#endif
diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c
index 9df962c..a8613eb 100644
--- a/drivers/gpu/host1x/syncpt.c
+++ b/drivers/gpu/host1x/syncpt.c
@@ -24,6 +24,10 @@

#include "syncpt.h"
#include "dev.h"
+#include "intr.h"
+
+#define SYNCPT_CHECK_PERIOD (2 * HZ)
+#define MAX_STUCK_CHECK_COUNT 15

static struct host1x_syncpt *_host1x_syncpt_alloc(struct host1x *host,
struct device *dev,
@@ -141,6 +145,161 @@ void host1x_syncpt_incr(struct host1x_syncpt *sp)
host1x_syncpt_cpu_incr(sp);
}

+/*
+ * Updated sync point form hardware, and returns true if syncpoint is expired,
+ * false if we may need to wait
+ */
+static bool syncpt_load_min_is_expired(struct host1x_syncpt *sp, u32 thresh)
+{
+ host1x_hw_syncpt_load(sp->host, sp);
+ return host1x_syncpt_is_expired(sp, thresh);
+}
+
+/*
+ * Main entrypoint for syncpoint value waits.
+ */
+int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout,
+ u32 *value)
+{
+ DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
+ void *ref;
+ struct host1x_waitlist *waiter;
+ int err = 0, check_count = 0;
+ u32 val;
+
+ if (value)
+ *value = 0;
+
+ /* first check cache */
+ if (host1x_syncpt_is_expired(sp, thresh)) {
+ if (value)
+ *value = host1x_syncpt_load(sp);
+ return 0;
+ }
+
+ /* try to read from register */
+ val = host1x_hw_syncpt_load(sp->host, sp);
+ if (host1x_syncpt_is_expired(sp, thresh)) {
+ if (value)
+ *value = val;
+ goto done;
+ }
+
+ if (!timeout) {
+ err = -EAGAIN;
+ goto done;
+ }
+
+ /* allocate a waiter */
+ waiter = kzalloc(sizeof(*waiter), GFP_KERNEL);
+ if (!waiter) {
+ err = -ENOMEM;
+ goto done;
+ }
+
+ /* schedule a wakeup when the syncpoint value is reached */
+ err = host1x_intr_add_action(sp->host, sp->id, thresh,
+ HOST1X_INTR_ACTION_WAKEUP_INTERRUPTIBLE,
+ &wq, waiter, &ref);
+ if (err)
+ goto done;
+
+ err = -EAGAIN;
+ /* Caller-specified timeout may be impractically low */
+ if (timeout < 0)
+ timeout = LONG_MAX;
+
+ /* wait for the syncpoint, or timeout, or signal */
+ while (timeout) {
+ long check = min_t(long, SYNCPT_CHECK_PERIOD, timeout);
+ int remain = wait_event_interruptible_timeout(wq,
+ syncpt_load_min_is_expired(sp, thresh),
+ check);
+ if (remain > 0 || host1x_syncpt_is_expired(sp, thresh)) {
+ if (value)
+ *value = host1x_syncpt_load(sp);
+ err = 0;
+ break;
+ }
+ if (remain < 0) {
+ err = remain;
+ break;
+ }
+ timeout -= check;
+ if (timeout && check_count <= MAX_STUCK_CHECK_COUNT) {
+ dev_warn(sp->host->dev,
+ "%s: syncpoint id %d (%s) stuck waiting %d, timeout=%ld\n",
+ current->comm, sp->id, sp->name,
+ thresh, timeout);
+ check_count++;
+ }
+ }
+ host1x_intr_put_ref(sp->host, sp->id, ref);
+
+done:
+ return err;
+}
+EXPORT_SYMBOL(host1x_syncpt_wait);
+
+/*
+ * Returns true if syncpoint is expired, false if we may need to wait
+ */
+bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh)
+{
+ u32 current_val;
+ u32 future_val;
+ smp_rmb();
+ current_val = (u32)atomic_read(&sp->min_val);
+ future_val = (u32)atomic_read(&sp->max_val);
+
+ /* Note the use of unsigned arithmetic here (mod 1<<32).
+ *
+ * c = current_val = min_val = the current value of the syncpoint.
+ * t = thresh = the value we are checking
+ * f = future_val = max_val = the value c will reach when all
+ * outstanding increments have completed.
+ *
+ * Note that c always chases f until it reaches f.
+ *
+ * Dtf = (f - t)
+ * Dtc = (c - t)
+ *
+ * Consider all cases:
+ *
+ * A) .....c..t..f..... Dtf < Dtc need to wait
+ * B) .....c.....f..t.. Dtf > Dtc expired
+ * C) ..t..c.....f..... Dtf > Dtc expired (Dct very large)
+ *
+ * Any case where f==c: always expired (for any t). Dtf == Dcf
+ * Any case where t==c: always expired (for any f). Dtf >= Dtc (because Dtc==0)
+ * Any case where t==f!=c: always wait. Dtf < Dtc (because Dtf==0,
+ * Dtc!=0)
+ *
+ * Other cases:
+ *
+ * A) .....t..f..c..... Dtf < Dtc need to wait
+ * A) .....f..c..t..... Dtf < Dtc need to wait
+ * A) .....f..t..c..... Dtf > Dtc expired
+ *
+ * So:
+ * Dtf >= Dtc implies EXPIRED (return true)
+ * Dtf < Dtc implies WAIT (return false)
+ *
+ * Note: If t is expired then we *cannot* wait on it. We would wait
+ * forever (hang the system).
+ *
+ * Note: do NOT get clever and remove the -thresh from both sides. It
+ * is NOT the same.
+ *
+ * If future valueis zero, we have a client managed sync point. In that
+ * case we do a direct comparison.
+ */
+ if (!host1x_syncpt_client_managed(sp))
+ return future_val - thresh >= current_val - thresh;
+ else
+ return (s32)(current_val - thresh) >= 0;
+}
+
int host1x_syncpt_init(struct host1x *host)
{
struct host1x_syncpt *syncpt;
diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h
index 119ccd5..17c1616 100644
--- a/drivers/gpu/host1x/syncpt.h
+++ b/drivers/gpu/host1x/syncpt.h
@@ -19,9 +19,11 @@
#ifndef __HOST1X_SYNCPT_H
#define __HOST1X_SYNCPT_H

+#include <linux/atomic.h>
#include <linux/kernel.h>
#include <linux/sched.h>
-#include <linux/atomic.h>
+
+#include "intr.h"

struct host1x;

@@ -34,6 +36,9 @@ struct host1x_syncpt {
int client_managed;
struct host1x *host;
struct device *dev;
+
+ /* interrupt data */
+ struct host1x_syncpt_intr intr;
};

/* Initialize sync point array */
@@ -113,6 +118,9 @@ void host1x_syncpt_cpu_incr(struct host1x_syncpt *sp);
/* Load current value from hardware to the shadow register. */
u32 host1x_syncpt_load(struct host1x_syncpt *sp);

+/* Check if the given syncpoint value has already passed */
+bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh);
+
/* Save host1x sync point state into shadow registers. */
void host1x_syncpt_save(struct host1x *host);

@@ -128,6 +136,10 @@ void host1x_syncpt_incr(struct host1x_syncpt *sp);
/* Indicate future operations by incrementing the sync point max. */
u32 host1x_syncpt_incr_max(struct host1x_syncpt *sp, u32 incrs);

+/* Wait until sync point reaches a threshold value, or a timeout. */
+int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh,
+ long timeout, u32 *value);
+
/* Check if sync point id is valid. */
static inline int host1x_syncpt_is_valid(struct host1x_syncpt *sp)
{
--
1.7.9.5

2013-03-15 12:13:39

by Thierry Reding

[permalink] [raw]
Subject: Re: [PATCHv7 10/10] drm: tegra: Add gr2d device

On Wed, Mar 13, 2013 at 02:36:26PM +0200, Terje Bergstrom wrote:
[...]
> diff --git a/drivers/gpu/host1x/drm/drm.c b/drivers/gpu/host1x/drm/drm.c
[...]
> +static inline struct host1x_drm_context *tegra_drm_get_context(
> + struct list_head *contexts,
> + struct host1x_drm_context *_ctx)
> +{
> + struct host1x_drm_context *ctx;
> +
> + list_for_each_entry(ctx, contexts, list)
> + if (ctx == _ctx)
> + return ctx;
> + return NULL;
> +}

Maybe make this a little more high-level, such as:

static bool host1x_drm_file_owns_context(struct host1x_drm_file *file,
struct host1x_drm_context *context)

?

> +static int tegra_drm_ioctl_open_channel(struct drm_device *drm, void *data,
> + struct drm_file *file_priv)
> +{
> + struct tegra_drm_open_channel_args *args = data;
> + struct host1x_client *client;
> + struct host1x_drm_context *context;
> + struct host1x_drm_file *fpriv = file_priv->driver_priv;
> + struct host1x_drm *host1x = drm->dev_private;
> + int err = -ENODEV;
> +
> + context = kzalloc(sizeof(*context), GFP_KERNEL);
> + if (!context)
> + return -ENOMEM;
> +
> + list_for_each_entry(client, &host1x->clients, list)
> + if (client->class == args->class) {
> + context->client = client;

Why do you assign this here? Should it perhaps be assigned only when the
opening of the channel succeeds? The .open_channel() already receives a
pointer to the client as parameter, so it doesn't have to be associated
with the context.

> +static int tegra_drm_ioctl_get_syncpoint(struct drm_device *drm, void *data,
> + struct drm_file *file_priv)
> +{
> + struct tegra_drm_get_syncpoint *args = data;
> + struct host1x_drm_file *fpriv = file_priv->driver_priv;
> + struct host1x_drm_context *context =
> + (struct host1x_drm_context *)(uintptr_t)args->context;
> +
> + if (!tegra_drm_get_context(&fpriv->contexts, context))
> + return -ENODEV;
> +
> + if (context->client->num_syncpts < args->param)
> + return -ENODEV;

I think this might be easier to read as:

if (args->param >= context->client->num_syncpts)
return -ENODEV;

> + args->value = host1x_syncpt_id(context->client->syncpts[args->param]);

This could use a temporary variable "syncpt" to make it easier to read.

> +static int tegra_drm_gem_create_ioctl(struct drm_device *drm, void *data,
> + struct drm_file *file_priv)

tegra_drm_ioctl_gem_create()?

> +{
> + struct tegra_drm_create *args = data;
> + struct drm_gem_cma_object *cma_obj;
> + struct tegra_drm_bo *cma_bo;

These can probably just be named "cma"/"gem" and "bo" instead.

> +static int tegra_drm_ioctl_get_offset(struct drm_device *drm, void *data,
> + struct drm_file *file_priv)

I think this might be more generically named tegra_drm_ioctl_mmap()
which might come in handy if we ever need to do something more than just
retrieve the offset.

> +{
> + struct tegra_drm_get_offset *args = data;
> + struct drm_gem_cma_object *cma_obj;
> + struct drm_gem_object *obj;
> +
> + obj = drm_gem_object_lookup(drm, file_priv, args->handle);
> + if (!obj)
> + return -EINVAL;
> + cma_obj = to_drm_gem_cma_obj(obj);

The above two lines should be separated by a blank line.

> +
> + args->offset = cma_obj->base.map_list.hash.key << PAGE_SHIFT;

Perhaps a better way would be to export the get_gem_mmap_offset() from
drivers/gpu/drm/drm_gem_cma_helper.c and reuse that.

> static struct drm_ioctl_desc tegra_drm_ioctls[] = {
> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_GEM_CREATE,
> + tegra_drm_gem_create_ioctl, DRM_UNLOCKED | DRM_AUTH),
> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_SYNCPT_READ,
> + tegra_drm_ioctl_syncpt_read, DRM_UNLOCKED),
> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_SYNCPT_INCR,
> + tegra_drm_ioctl_syncpt_incr, DRM_UNLOCKED),
> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_SYNCPT_WAIT,
> + tegra_drm_ioctl_syncpt_wait, DRM_UNLOCKED),
> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_OPEN_CHANNEL,
> + tegra_drm_ioctl_open_channel, DRM_UNLOCKED),
> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_CLOSE_CHANNEL,
> + tegra_drm_ioctl_close_channel, DRM_UNLOCKED),
> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_GET_SYNCPOINT,
> + tegra_drm_ioctl_get_syncpoint, DRM_UNLOCKED),
> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_SUBMIT,
> + tegra_drm_ioctl_submit, DRM_UNLOCKED),
> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_GEM_GET_OFFSET,
> + tegra_drm_ioctl_get_offset, DRM_UNLOCKED),
> };

Maybe resort these to put the GEM-specific IOCTLs closer together?

> static const struct file_operations tegra_drm_fops = {
> @@ -345,10 +585,17 @@ static void tegra_drm_disable_vblank(struct drm_device *drm, int pipe)
>
> static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file)
> {
> + struct host1x_drm_file *fpriv = file->driver_priv;
> + struct host1x_drm_context *context, *tmp;
> struct drm_crtc *crtc;
>
> list_for_each_entry(crtc, &drm->mode_config.crtc_list, head)
> tegra_dc_cancel_page_flip(crtc, file);
> +
> + list_for_each_entry_safe(context, tmp, &fpriv->contexts, list)
> + host1x_drm_context_free(context);
> + kfree(fpriv);

Another missing blank line between these.

> diff --git a/drivers/gpu/host1x/drm/gr2d.c b/drivers/gpu/host1x/drm/gr2d.c
[...]
> +static struct host1x_bo *handle_cma_to_host1x(struct drm_device *drm,
> + struct drm_file *file_priv,
> + u32 gem_handle)
> +{
> + struct drm_gem_object *gem_obj;
> + struct drm_gem_cma_object *cma_obj;
> + struct tegra_drm_bo *cma_bo;
> +
> + gem_obj = drm_gem_object_lookup(drm, file_priv, gem_handle);
> + if (!gem_obj)
> + return 0;
> +
> + mutex_lock(&drm->struct_mutex);
> + drm_gem_object_unreference(gem_obj);
> + mutex_unlock(&drm->struct_mutex);
> +
> + cma_obj = to_drm_gem_cma_obj(gem_obj);
> + cma_bo = container_of(cma_obj, struct tegra_drm_bo, cma_obj);
> +
> + return &cma_bo->base;
> +}

Maybe rename this to host1x_bo_lookup()? *_to_* are usually used for
up- and downcasting; this function does a lot more.

> +static int gr2d_submit(struct host1x_drm_context *context,
> + struct tegra_drm_submit_args *args,
> + struct drm_device *drm,
> + struct drm_file *file_priv)
> +{
> + struct host1x_job *job;
> + int num_cmdbufs = args->num_cmdbufs;
> + int num_relocs = args->num_relocs;
> + int num_waitchks = args->num_waitchks;
> + struct tegra_drm_cmdbuf __user *cmdbufs =
> + (void * __user)(uintptr_t)args->cmdbufs;
> + struct tegra_drm_reloc __user *relocs =
> + (void * __user)(uintptr_t)args->relocs;
> + struct tegra_drm_waitchk __user *waitchks =
> + (void * __user)(uintptr_t)args->waitchks;
> + struct tegra_drm_syncpt_incr syncpt_incr;
> + int err;
> +
> + /* We don't yet support other than one syncpt_incr struct per submit */
> + if (args->num_syncpt_incrs != 1)
> + return -EINVAL;
> +
> + job = host1x_job_alloc(context->channel, args->num_cmdbufs,
> + args->num_relocs, args->num_waitchks);
> + if (!job)
> + return -ENOMEM;
> +
> + job->num_relocs = args->num_relocs;
> + job->num_waitchk = args->num_waitchks;
> + job->client = (u32)args->context;
> + job->class = context->client->class;
> + job->serialize = true;
> +
> + while (num_cmdbufs) {
> + struct tegra_drm_cmdbuf cmdbuf;
> + struct host1x_bo *mem_handle;
> + err = copy_from_user(&cmdbuf, cmdbufs, sizeof(cmdbuf));

Could use an blank line to separate variable declarations from the code.
Also maybe rename mem_handle to bo which is much shorter.

> + goto fail;
> +
> + err = host1x_job_pin(job, context->client->dev);
> + if (err)
> + goto fail;
> +
> + err = copy_from_user(&syncpt_incr,
> + (void * __user)(uintptr_t)args->syncpt_incrs,
> + sizeof(syncpt_incr));
> + if (err)
> + goto fail;
> +
> + job->syncpt_id = syncpt_incr.id;
> + job->syncpt_incrs = syncpt_incr.incrs;
> + job->timeout = 10000;
> + job->is_addr_reg = gr2d_is_addr_reg;
> + if (args->timeout && args->timeout < 10000)

Another missing blank line.

Also you now create a lookup table (or bitfield actually) as we
discussed but you still pass around a function to check the lookup table
against. What I originally intended was to not pass a function around at
all but directly use the lookup table/bitfield. However I think we can
leave this as-is for now and change it later if required.

> +static int gr2d_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct host1x_drm *host1x = host1x_get_drm_data(dev->parent);
> + int err;
> + struct gr2d *gr2d = NULL;
> + struct host1x_syncpt **syncpts;

I don't think there's a need for this temporary variable. You could just
use gr2d->client.syncpts directly.

> + gr2d = devm_kzalloc(dev, sizeof(*gr2d), GFP_KERNEL);
> + if (!gr2d)
> + return -ENOMEM;
> +
> + syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL);
> + if (!syncpts)
> + return -ENOMEM;
> +
> + gr2d->clk = devm_clk_get(dev, NULL);
> + if (IS_ERR(gr2d->clk)) {
> + dev_err(dev, "cannot get clock\n");
> + return PTR_ERR(gr2d->clk);
> + }
> +
> + err = clk_prepare_enable(gr2d->clk);
> + if (err) {
> + dev_err(dev, "cannot turn on clock\n");
> + return err;
> + }
> +
> + gr2d->channel = host1x_channel_alloc(dev);
> + if (!gr2d->channel)
> + return -ENOMEM;
> +
> + *syncpts = host1x_syncpt_request(dev, 0);
> + if (!(*syncpts)) {
> + host1x_channel_free(gr2d->channel);
> + return -ENOMEM;
> + }
> +
> + gr2d->client.ops = &gr2d_client_ops;
> + gr2d->client.dev = dev;
> + gr2d->client.class = HOST1X_CLASS_GR2D;
> + gr2d->client.syncpts = syncpts;
> + gr2d->client.num_syncpts = 1;
> +
> + err = host1x_register_client(host1x, &gr2d->client);
> + if (err < 0) {
> + dev_err(dev, "failed to register host1x client: %d\n", err);
> + return err;
> + }
> +
> + gr2d_init_addr_reg_map(dev, gr2d);
> +
> + dev_set_drvdata(dev, gr2d);

Nit: I think it'd be nicer to use platform_set_drvdata() here to mirror
the platform_get_drvdata() from gr2d_remove().

> diff --git a/include/drm/tegra_drm.h b/include/drm/tegra_drm.h
[...]
> +struct tegra_drm_create {

struct tegra_drm_gem_create?

> + __u64 size;
> + u32 flags;
> + u32 handle;
> + u32 offset;
> + u32 padding;

Also since we have a separate IOCTL for this, I think you can leave out
the offset field (and also padding since it isn't required anymore).

> +struct tegra_drm_get_offset {
> + __u32 handle;
> + __u32 offset;
> +};

struct tegra_drm_gem_mmap to go along with the name change of the IOCTL.

> +struct tegra_drm_syncpt_incr_args {
> + __u32 id;
> + __u32 pad;
> +};

Shouldn't this second field be something like "value" to allow this
IOCTL to increment by more than 1?

> +#define DRM_TEGRA_NO_TIMEOUT (0xffffffff)

No need for the parentheses.

> +struct tegra_drm_reloc {
> + __u32 cmdbuf_mem;
> + __u32 cmdbuf_offset;
> + __u32 target;
> + __u32 target_offset;
> + __u32 shift;
> + __u32 pad;
> +};

I've found this to be a little inconsistent. Why does the cmdbuf_mem
have the _mem suffix, but not the target field? Given that this will
eventually be used by userspace software once merged it will be fixed
forever. I had noticed the same for the in-kernel structures but didn't
think it important enough to hold up the patches. In this case I think
we should make them consistent, though.

> +#define DRM_TEGRA_DRM_GEM_CREATE 0x00
> +#define DRM_TEGRA_DRM_SYNCPT_READ 0x01
> +#define DRM_TEGRA_DRM_SYNCPT_INCR 0x02
> +#define DRM_TEGRA_DRM_SYNCPT_WAIT 0x03
> +#define DRM_TEGRA_DRM_OPEN_CHANNEL 0x04
> +#define DRM_TEGRA_DRM_CLOSE_CHANNEL 0x05
> +#define DRM_TEGRA_DRM_GET_SYNCPOINT 0x06
> +#define DRM_TEGRA_DRM_SUBMIT 0x08
> +#define DRM_TEGRA_DRM_GEM_GET_OFFSET 0x09
> +
> +#define DRM_IOCTL_TEGRA_DRM_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_GEM_CREATE, struct tegra_drm_create)
> +#define DRM_IOCTL_TEGRA_DRM_SYNCPT_READ DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SYNCPT_READ, struct tegra_drm_syncpt_read_args)
> +#define DRM_IOCTL_TEGRA_DRM_SYNCPT_INCR DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SYNCPT_INCR, struct tegra_drm_syncpt_incr_args)
> +#define DRM_IOCTL_TEGRA_DRM_SYNCPT_WAIT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SYNCPT_WAIT, struct tegra_drm_syncpt_wait_args)
> +#define DRM_IOCTL_TEGRA_DRM_OPEN_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_OPEN_CHANNEL, struct tegra_drm_open_channel_args)
> +#define DRM_IOCTL_TEGRA_DRM_CLOSE_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_CLOSE_CHANNEL, struct tegra_drm_open_channel_args)
> +#define DRM_IOCTL_TEGRA_DRM_GET_SYNCPOINT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_GET_SYNCPOINT, struct tegra_drm_get_syncpoint)
> +#define DRM_IOCTL_TEGRA_DRM_SUBMIT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SUBMIT, struct tegra_drm_submit_args)
> +#define DRM_IOCTL_TEGRA_DRM_GEM_GET_OFFSET DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_GEM_GET_OFFSET, struct tegra_drm_get_offset)

Same comment regarding reordering and GET_OFFSET -> MMAP rename.

Thierry


Attachments:
(No filename) (12.41 kB)
(No filename) (836.00 B)
Download all attachments

2013-03-15 13:22:17

by Terje Bergstrom

[permalink] [raw]
Subject: Re: [PATCHv7 10/10] drm: tegra: Add gr2d device

On 15.03.2013 14:13, Thierry Reding wrote:
> On Wed, Mar 13, 2013 at 02:36:26PM +0200, Terje Bergstrom wrote:
> [...]
>> diff --git a/drivers/gpu/host1x/drm/drm.c b/drivers/gpu/host1x/drm/drm.c
> [...]
>> +static inline struct host1x_drm_context *tegra_drm_get_context(
>> + struct list_head *contexts,
>> + struct host1x_drm_context *_ctx)
>> +{
>> + struct host1x_drm_context *ctx;
>> +
>> + list_for_each_entry(ctx, contexts, list)
>> + if (ctx == _ctx)
>> + return ctx;
>> + return NULL;
>> +}
>
> Maybe make this a little more high-level, such as:
>
> static bool host1x_drm_file_owns_context(struct host1x_drm_file *file,
> struct host1x_drm_context *context)
>
> ?

We only need the true/false value, so changing signature makes sense.

>
>> +static int tegra_drm_ioctl_open_channel(struct drm_device *drm, void *data,
>> + struct drm_file *file_priv)
>> +{
>> + struct tegra_drm_open_channel_args *args = data;
>> + struct host1x_client *client;
>> + struct host1x_drm_context *context;
>> + struct host1x_drm_file *fpriv = file_priv->driver_priv;
>> + struct host1x_drm *host1x = drm->dev_private;
>> + int err = -ENODEV;
>> +
>> + context = kzalloc(sizeof(*context), GFP_KERNEL);
>> + if (!context)
>> + return -ENOMEM;
>> +
>> + list_for_each_entry(client, &host1x->clients, list)
>> + if (client->class == args->class) {
>> + context->client = client;
>
> Why do you assign this here? Should it perhaps be assigned only when the
> opening of the channel succeeds? The .open_channel() already receives a
> pointer to the client as parameter, so it doesn't have to be associated
> with the context.

True, we can move the assignment to happen after checking the
open_channel result.

>
>> +static int tegra_drm_ioctl_get_syncpoint(struct drm_device *drm, void *data,
>> + struct drm_file *file_priv)
>> +{
>> + struct tegra_drm_get_syncpoint *args = data;
>> + struct host1x_drm_file *fpriv = file_priv->driver_priv;
>> + struct host1x_drm_context *context =
>> + (struct host1x_drm_context *)(uintptr_t)args->context;
>> +
>> + if (!tegra_drm_get_context(&fpriv->contexts, context))
>> + return -ENODEV;
>> +
>> + if (context->client->num_syncpts < args->param)
>> + return -ENODEV;
>
> I think this might be easier to read as:
>
> if (args->param >= context->client->num_syncpts)
> return -ENODEV;

Ok, will do.

>
>> + args->value = host1x_syncpt_id(context->client->syncpts[args->param]);
>
> This could use a temporary variable "syncpt" to make it easier to read.

Ok.

>
>> +static int tegra_drm_gem_create_ioctl(struct drm_device *drm, void *data,
>> + struct drm_file *file_priv)
>
> tegra_drm_ioctl_gem_create()?

Sure.

>
>> +{
>> + struct tegra_drm_create *args = data;
>> + struct drm_gem_cma_object *cma_obj;
>> + struct tegra_drm_bo *cma_bo;
>
> These can probably just be named "cma"/"gem" and "bo" instead.

Ok.

>
>> +static int tegra_drm_ioctl_get_offset(struct drm_device *drm, void *data,
>> + struct drm_file *file_priv)
>
> I think this might be more generically named tegra_drm_ioctl_mmap()
> which might come in handy if we ever need to do something more than just
> retrieve the offset.

Yeah, that changes the API semantics a bit, but in general there
shouldn't be a need to just get the offset without doing the actual mmap.

>
>> +{
>> + struct tegra_drm_get_offset *args = data;
>> + struct drm_gem_cma_object *cma_obj;
>> + struct drm_gem_object *obj;
>> +
>> + obj = drm_gem_object_lookup(drm, file_priv, args->handle);
>> + if (!obj)
>> + return -EINVAL;
>> + cma_obj = to_drm_gem_cma_obj(obj);
>
> The above two lines should be separated by a blank line.

Ok.

>
>> +
>> + args->offset = cma_obj->base.map_list.hash.key << PAGE_SHIFT;
>
> Perhaps a better way would be to export the get_gem_mmap_offset() from
> drivers/gpu/drm/drm_gem_cma_helper.c and reuse that.

Ok, we'll add that as a patch to the series.

>
>> static struct drm_ioctl_desc tegra_drm_ioctls[] = {
>> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_GEM_CREATE,
>> + tegra_drm_gem_create_ioctl, DRM_UNLOCKED | DRM_AUTH),
>> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_SYNCPT_READ,
>> + tegra_drm_ioctl_syncpt_read, DRM_UNLOCKED),
>> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_SYNCPT_INCR,
>> + tegra_drm_ioctl_syncpt_incr, DRM_UNLOCKED),
>> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_SYNCPT_WAIT,
>> + tegra_drm_ioctl_syncpt_wait, DRM_UNLOCKED),
>> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_OPEN_CHANNEL,
>> + tegra_drm_ioctl_open_channel, DRM_UNLOCKED),
>> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_CLOSE_CHANNEL,
>> + tegra_drm_ioctl_close_channel, DRM_UNLOCKED),
>> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_GET_SYNCPOINT,
>> + tegra_drm_ioctl_get_syncpoint, DRM_UNLOCKED),
>> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_SUBMIT,
>> + tegra_drm_ioctl_submit, DRM_UNLOCKED),
>> + DRM_IOCTL_DEF_DRV(TEGRA_DRM_GEM_GET_OFFSET,
>> + tegra_drm_ioctl_get_offset, DRM_UNLOCKED),
>> };
>
> Maybe resort these to put the GEM-specific IOCTLs closer together?

Ok.

>
>> static const struct file_operations tegra_drm_fops = {
>> @@ -345,10 +585,17 @@ static void tegra_drm_disable_vblank(struct drm_device *drm, int pipe)
>>
>> static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file)
>> {
>> + struct host1x_drm_file *fpriv = file->driver_priv;
>> + struct host1x_drm_context *context, *tmp;
>> struct drm_crtc *crtc;
>>
>> list_for_each_entry(crtc, &drm->mode_config.crtc_list, head)
>> tegra_dc_cancel_page_flip(crtc, file);
>> +
>> + list_for_each_entry_safe(context, tmp, &fpriv->contexts, list)
>> + host1x_drm_context_free(context);
>> + kfree(fpriv);
>
> Another missing blank line between these.

Will fix.

>
>> diff --git a/drivers/gpu/host1x/drm/gr2d.c b/drivers/gpu/host1x/drm/gr2d.c
> [...]
>> +static struct host1x_bo *handle_cma_to_host1x(struct drm_device *drm,
>> + struct drm_file *file_priv,
>> + u32 gem_handle)
>> +{
>> + struct drm_gem_object *gem_obj;
>> + struct drm_gem_cma_object *cma_obj;
>> + struct tegra_drm_bo *cma_bo;
>> +
>> + gem_obj = drm_gem_object_lookup(drm, file_priv, gem_handle);
>> + if (!gem_obj)
>> + return 0;
>> +
>> + mutex_lock(&drm->struct_mutex);
>> + drm_gem_object_unreference(gem_obj);
>> + mutex_unlock(&drm->struct_mutex);
>> +
>> + cma_obj = to_drm_gem_cma_obj(gem_obj);
>> + cma_bo = container_of(cma_obj, struct tegra_drm_bo, cma_obj);
>> +
>> + return &cma_bo->base;
>> +}
>
> Maybe rename this to host1x_bo_lookup()? *_to_* are usually used for
> up- and downcasting; this function does a lot more.

Yep, will do.

>
>> +static int gr2d_submit(struct host1x_drm_context *context,
>> + struct tegra_drm_submit_args *args,
>> + struct drm_device *drm,
>> + struct drm_file *file_priv)
>> +{
>> + struct host1x_job *job;
>> + int num_cmdbufs = args->num_cmdbufs;
>> + int num_relocs = args->num_relocs;
>> + int num_waitchks = args->num_waitchks;
>> + struct tegra_drm_cmdbuf __user *cmdbufs =
>> + (void * __user)(uintptr_t)args->cmdbufs;
>> + struct tegra_drm_reloc __user *relocs =
>> + (void * __user)(uintptr_t)args->relocs;
>> + struct tegra_drm_waitchk __user *waitchks =
>> + (void * __user)(uintptr_t)args->waitchks;
>> + struct tegra_drm_syncpt_incr syncpt_incr;
>> + int err;
>> +
>> + /* We don't yet support other than one syncpt_incr struct per submit */
>> + if (args->num_syncpt_incrs != 1)
>> + return -EINVAL;
>> +
>> + job = host1x_job_alloc(context->channel, args->num_cmdbufs,
>> + args->num_relocs, args->num_waitchks);
>> + if (!job)
>> + return -ENOMEM;
>> +
>> + job->num_relocs = args->num_relocs;
>> + job->num_waitchk = args->num_waitchks;
>> + job->client = (u32)args->context;
>> + job->class = context->client->class;
>> + job->serialize = true;
>> +
>> + while (num_cmdbufs) {
>> + struct tegra_drm_cmdbuf cmdbuf;
>> + struct host1x_bo *mem_handle;
>> + err = copy_from_user(&cmdbuf, cmdbufs, sizeof(cmdbuf));
>
> Could use an blank line to separate variable declarations from the code.
> Also maybe rename mem_handle to bo which is much shorter.

Sure, mem_handle is anyway legacy naming.

>
>> + goto fail;
>> +
>> + err = host1x_job_pin(job, context->client->dev);
>> + if (err)
>> + goto fail;
>> +
>> + err = copy_from_user(&syncpt_incr,
>> + (void * __user)(uintptr_t)args->syncpt_incrs,
>> + sizeof(syncpt_incr));
>> + if (err)
>> + goto fail;
>> +
>> + job->syncpt_id = syncpt_incr.id;
>> + job->syncpt_incrs = syncpt_incr.incrs;
>> + job->timeout = 10000;
>> + job->is_addr_reg = gr2d_is_addr_reg;
>> + if (args->timeout && args->timeout < 10000)
>
> Another missing blank line.
>
> Also you now create a lookup table (or bitfield actually) as we
> discussed but you still pass around a function to check the lookup table
> against. What I originally intended was to not pass a function around at
> all but directly use the lookup table/bitfield. However I think we can
> leave this as-is for now and change it later if required.

Oops, the intention was to pass the table around, but I and Arto both
missed that. We'll fix that.

>
>> +static int gr2d_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct host1x_drm *host1x = host1x_get_drm_data(dev->parent);
>> + int err;
>> + struct gr2d *gr2d = NULL;
>> + struct host1x_syncpt **syncpts;
>
> I don't think there's a need for this temporary variable. You could just
> use gr2d->client.syncpts directly.

Ok, it might make a bit longer lines, but it doesn't look too bad.

>
>> + gr2d = devm_kzalloc(dev, sizeof(*gr2d), GFP_KERNEL);
>> + if (!gr2d)
>> + return -ENOMEM;
>> +
>> + syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL);
>> + if (!syncpts)
>> + return -ENOMEM;
>> +
>> + gr2d->clk = devm_clk_get(dev, NULL);
>> + if (IS_ERR(gr2d->clk)) {
>> + dev_err(dev, "cannot get clock\n");
>> + return PTR_ERR(gr2d->clk);
>> + }
>> +
>> + err = clk_prepare_enable(gr2d->clk);
>> + if (err) {
>> + dev_err(dev, "cannot turn on clock\n");
>> + return err;
>> + }
>> +
>> + gr2d->channel = host1x_channel_alloc(dev);
>> + if (!gr2d->channel)
>> + return -ENOMEM;
>> +
>> + *syncpts = host1x_syncpt_request(dev, 0);
>> + if (!(*syncpts)) {
>> + host1x_channel_free(gr2d->channel);
>> + return -ENOMEM;
>> + }
>> +
>> + gr2d->client.ops = &gr2d_client_ops;
>> + gr2d->client.dev = dev;
>> + gr2d->client.class = HOST1X_CLASS_GR2D;
>> + gr2d->client.syncpts = syncpts;
>> + gr2d->client.num_syncpts = 1;
>> +
>> + err = host1x_register_client(host1x, &gr2d->client);
>> + if (err < 0) {
>> + dev_err(dev, "failed to register host1x client: %d\n", err);
>> + return err;
>> + }
>> +
>> + gr2d_init_addr_reg_map(dev, gr2d);
>> +
>> + dev_set_drvdata(dev, gr2d);
>
> Nit: I think it'd be nicer to use platform_set_drvdata() here to mirror
> the platform_get_drvdata() from gr2d_remove().

Yeah, we'll fix that.

>
>> diff --git a/include/drm/tegra_drm.h b/include/drm/tegra_drm.h
> [...]
>> +struct tegra_drm_create {
>
> struct tegra_drm_gem_create?

Yep.

>
>> + __u64 size;
>> + u32 flags;
>> + u32 handle;
>> + u32 offset;
>> + u32 padding;
>
> Also since we have a separate IOCTL for this, I think you can leave out
> the offset field (and also padding since it isn't required anymore).

This is a genuine mistake, offset should've been dropped.

>
>> +struct tegra_drm_get_offset {
>> + __u32 handle;
>> + __u32 offset;
>> +};
>
> struct tegra_drm_gem_mmap to go along with the name change of the IOCTL.

Ok.

>
>> +struct tegra_drm_syncpt_incr_args {
>> + __u32 id;
>> + __u32 pad;
>> +};
>
> Shouldn't this second field be something like "value" to allow this
> IOCTL to increment by more than 1?

This is actually an IOCTL that normally shouldn't be used. It's still
there for testing.

The intention with this is that user space can submit a job that waits
for a sync point value. The user space can then investigate the state of
the channel to verify that host1x driver is working correctly. After
investigation, user space can increment the sync point to unblock the
channel.

It's a bit tricky to find out the correct sync point value to wait for,
but for test cases we can usually hack it together by just shutting down
all other users of host1x and reading current value.

>> +#define DRM_TEGRA_NO_TIMEOUT (0xffffffff)
>
> No need for the parentheses.

Sure, will remove.

>
>> +struct tegra_drm_reloc {
>> + __u32 cmdbuf_mem;
>> + __u32 cmdbuf_offset;
>> + __u32 target;
>> + __u32 target_offset;
>> + __u32 shift;
>> + __u32 pad;
>> +};
>
> I've found this to be a little inconsistent. Why does the cmdbuf_mem
> have the _mem suffix, but not the target field? Given that this will
> eventually be used by userspace software once merged it will be fixed
> forever. I had noticed the same for the in-kernel structures but didn't
> think it important enough to hold up the patches. In this case I think
> we should make them consistent, though.

Ok. I propose we rename target to target_mem. We could also rename
tegra_drm_waitchk.mem to cmdbuf_mem and tegra_drm_waitchk.offset to
cmdbuf_offset for consistency.

>
>> +#define DRM_TEGRA_DRM_GEM_CREATE 0x00
>> +#define DRM_TEGRA_DRM_SYNCPT_READ 0x01
>> +#define DRM_TEGRA_DRM_SYNCPT_INCR 0x02
>> +#define DRM_TEGRA_DRM_SYNCPT_WAIT 0x03
>> +#define DRM_TEGRA_DRM_OPEN_CHANNEL 0x04
>> +#define DRM_TEGRA_DRM_CLOSE_CHANNEL 0x05
>> +#define DRM_TEGRA_DRM_GET_SYNCPOINT 0x06
>> +#define DRM_TEGRA_DRM_SUBMIT 0x08
>> +#define DRM_TEGRA_DRM_GEM_GET_OFFSET 0x09
>> +
>> +#define DRM_IOCTL_TEGRA_DRM_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_GEM_CREATE, struct tegra_drm_create)
>> +#define DRM_IOCTL_TEGRA_DRM_SYNCPT_READ DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SYNCPT_READ, struct tegra_drm_syncpt_read_args)
>> +#define DRM_IOCTL_TEGRA_DRM_SYNCPT_INCR DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SYNCPT_INCR, struct tegra_drm_syncpt_incr_args)
>> +#define DRM_IOCTL_TEGRA_DRM_SYNCPT_WAIT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SYNCPT_WAIT, struct tegra_drm_syncpt_wait_args)
>> +#define DRM_IOCTL_TEGRA_DRM_OPEN_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_OPEN_CHANNEL, struct tegra_drm_open_channel_args)
>> +#define DRM_IOCTL_TEGRA_DRM_CLOSE_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_CLOSE_CHANNEL, struct tegra_drm_open_channel_args)
>> +#define DRM_IOCTL_TEGRA_DRM_GET_SYNCPOINT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_GET_SYNCPOINT, struct tegra_drm_get_syncpoint)
>> +#define DRM_IOCTL_TEGRA_DRM_SUBMIT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_SUBMIT, struct tegra_drm_submit_args)
>> +#define DRM_IOCTL_TEGRA_DRM_GEM_GET_OFFSET DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_DRM_GEM_GET_OFFSET, struct tegra_drm_get_offset)
>
> Same comment regarding reordering and GET_OFFSET -> MMAP rename.

Ok.

Terje

2013-03-19 16:37:26

by Terje Bergstrom

[permalink] [raw]
Subject: Re: [PATCHv7 10/10] drm: tegra: Add gr2d device

On 15.03.2013 14:13, Thierry Reding wrote:
> Also you now create a lookup table (or bitfield actually) as we
> discussed but you still pass around a function to check the lookup table
> against. What I originally intended was to not pass a function around at
> all but directly use the lookup table/bitfield. However I think we can
> leave this as-is for now and change it later if required.

Yeah, I think it's better if we leave this as is now. We should actually
have one table for host1x and one for 2D. The host1x one should be
shared between the drivers, but the table for client unit should be
local to the driver.

Let's take this up again when we have another driver.

>
>> +static int gr2d_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct host1x_drm *host1x = host1x_get_drm_data(dev->parent);
>> + int err;
>> + struct gr2d *gr2d = NULL;
>> + struct host1x_syncpt **syncpts;
>
> I don't think there's a need for this temporary variable. You could just
> use gr2d->client.syncpts directly.

I actually ended up with pretty long lines when I tried this, so I hope
it's ok to leave as is.

>
>> + gr2d = devm_kzalloc(dev, sizeof(*gr2d), GFP_KERNEL);
>> + if (!gr2d)
>> + return -ENOMEM;
>> +
>> + syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL);

F.ex. this line would split two two lines.

Otherwise the changes should be now pretty much ok. You sent a bunch of
changes (thanks) that I merged to my tree. I'll just need to do some
testing and re-split the patches and send v8.

Terje