gpu: host1x: Add syncpoint wait and interrupts
authorTerje Bergstrom <tbergstrom@nvidia.com>
Fri, 22 Mar 2013 14:34:02 +0000 (16:34 +0200)
committerThierry Reding <thierry.reding@avionic-design.de>
Mon, 22 Apr 2013 10:32:42 +0000 (12:32 +0200)
Add support for sync point interrupts, and sync point wait. Sync
point wait used interrupts for unblocking wait.

Signed-off-by: Arto Merilainen <amerilainen@nvidia.com>
Signed-off-by: Terje Bergstrom <tbergstrom@nvidia.com>
Reviewed-by: Thierry Reding <thierry.reding@avionic-design.de>
Tested-by: Thierry Reding <thierry.reding@avionic-design.de>
Tested-by: Erik Faye-Lund <kusmabite@gmail.com>
Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
drivers/gpu/host1x/Makefile
drivers/gpu/host1x/dev.c
drivers/gpu/host1x/dev.h
drivers/gpu/host1x/hw/host1x01.c
drivers/gpu/host1x/hw/hw_host1x01_sync.h
drivers/gpu/host1x/hw/intr_hw.c [new file with mode: 0644]
drivers/gpu/host1x/intr.c [new file with mode: 0644]
drivers/gpu/host1x/intr.h [new file with mode: 0644]
drivers/gpu/host1x/syncpt.c
drivers/gpu/host1x/syncpt.h

index 363e6ab31517417157189728bfbab1f899ed7888..5ef47ffce674205efe2dbc8eb72a565441ae0c79 100644 (file)
@@ -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
index 0d6002cb67c109f19f68714d9d53550ab07ef7f6..b967f6e8df552ee4909819800676a6ab8203f10a 100644 (file)
@@ -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)
@@ -123,13 +124,24 @@ static int host1x_probe(struct platform_device *pdev)
                return err;
        }
 
+       err = host1x_intr_init(host, syncpt_irq);
+       if (err) {
+               dev_err(&pdev->dev, "failed to initialize interrupts\n");
+               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);
 
index eaf602657f7644194bd75be94b35f77455fc0cea..caf9cc62eb1776f3acc389dd12d93bf32a55bac0 100644 (file)
@@ -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;
+
 };
 
 void host1x_sync_writel(struct host1x *host1x, u32 r, u32 v);
@@ -93,4 +111,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
index 612b4574c4b64030025b33b3d74f88e69c122157..f5c35fa66d05e86feb4d27af8077eca0f82bd68a 100644 (file)
@@ -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;
 }
index 3af258b46e622b85135b96d8131f3198cafd67be..eea0bb06052a8e971c3c7c83c1d52cba5ffac5af 100644 (file)
@@ -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 (file)
index 0000000..b592eef
--- /dev/null
@@ -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 (file)
index 0000000..b1b5a80
--- /dev/null
@@ -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 (file)
index 0000000..a3f06ab
--- /dev/null
@@ -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
index 30385f62daee14e32a0b8a5a1bad3fe0aed52971..07fad1412babfcbd35cda4b3a53f919bcabfbc4a 100644 (file)
 
 #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;
index efa2b6e659d6ffe8d5dc58f774b6dbda4133eeed..17c1616de100b47853b3750ad8760eedad91f812 100644 (file)
@@ -23,6 +23,8 @@
 #include <linux/kernel.h>
 #include <linux/sched.h>
 
+#include "intr.h"
+
 struct host1x;
 
 struct host1x_syncpt {
@@ -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)
 {