2 * Tegra host1x Interrupt Management
4 * Copyright (c) 2010-2013, NVIDIA Corporation.
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms and conditions of the GNU General Public License,
8 * version 2, as published by the Free Software Foundation.
10 * This program is distributed in the hope it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include <linux/clk.h>
20 #include <linux/interrupt.h>
21 #include <linux/slab.h>
22 #include <linux/irq.h>
24 #include <trace/events/host1x.h>
29 /* Wait list management */
38 static void waiter_release(struct kref
*kref
)
40 kfree(container_of(kref
, struct host1x_waitlist
, refcount
));
44 * add a waiter to a waiter queue, sorted by threshold
45 * returns true if it was added at the head of the queue
47 static bool add_waiter_to_queue(struct host1x_waitlist
*waiter
,
48 struct list_head
*queue
)
50 struct host1x_waitlist
*pos
;
51 u32 thresh
= waiter
->thresh
;
53 list_for_each_entry_reverse(pos
, queue
, list
)
54 if ((s32
)(pos
->thresh
- thresh
) <= 0) {
55 list_add(&waiter
->list
, &pos
->list
);
59 list_add(&waiter
->list
, queue
);
64 * run through a waiter queue for a single sync point ID
65 * and gather all completed waiters into lists by actions
67 static void remove_completed_waiters(struct list_head
*head
, u32 sync
,
68 struct list_head completed
[HOST1X_INTR_ACTION_COUNT
])
70 struct list_head
*dest
;
71 struct host1x_waitlist
*waiter
, *next
, *prev
;
73 list_for_each_entry_safe(waiter
, next
, head
, list
) {
74 if ((s32
)(waiter
->thresh
- sync
) > 0)
77 dest
= completed
+ waiter
->action
;
79 /* consolidate submit cleanups */
80 if (waiter
->action
== HOST1X_INTR_ACTION_SUBMIT_COMPLETE
&&
82 prev
= list_entry(dest
->prev
,
83 struct host1x_waitlist
, list
);
84 if (prev
->data
== waiter
->data
) {
90 /* PENDING->REMOVED or CANCELLED->HANDLED */
91 if (atomic_inc_return(&waiter
->state
) == WLS_HANDLED
|| !dest
) {
92 list_del(&waiter
->list
);
93 kref_put(&waiter
->refcount
, waiter_release
);
95 list_move_tail(&waiter
->list
, dest
);
99 static void reset_threshold_interrupt(struct host1x
*host
,
100 struct list_head
*head
,
104 list_first_entry(head
, struct host1x_waitlist
, list
)->thresh
;
106 host1x_hw_intr_set_syncpt_threshold(host
, id
, thresh
);
107 host1x_hw_intr_enable_syncpt_intr(host
, id
);
110 static void action_submit_complete(struct host1x_waitlist
*waiter
)
112 struct host1x_channel
*channel
= waiter
->data
;
114 host1x_cdma_update(&channel
->cdma
);
116 /* Add nr_completed to trace */
117 trace_host1x_channel_submit_complete(dev_name(channel
->dev
),
118 waiter
->count
, waiter
->thresh
);
122 static void action_wakeup(struct host1x_waitlist
*waiter
)
124 wait_queue_head_t
*wq
= waiter
->data
;
128 static void action_wakeup_interruptible(struct host1x_waitlist
*waiter
)
130 wait_queue_head_t
*wq
= waiter
->data
;
131 wake_up_interruptible(wq
);
134 typedef void (*action_handler
)(struct host1x_waitlist
*waiter
);
136 static action_handler action_handlers
[HOST1X_INTR_ACTION_COUNT
] = {
137 action_submit_complete
,
139 action_wakeup_interruptible
,
142 static void run_handlers(struct list_head completed
[HOST1X_INTR_ACTION_COUNT
])
144 struct list_head
*head
= completed
;
147 for (i
= 0; i
< HOST1X_INTR_ACTION_COUNT
; ++i
, ++head
) {
148 action_handler handler
= action_handlers
[i
];
149 struct host1x_waitlist
*waiter
, *next
;
151 list_for_each_entry_safe(waiter
, next
, head
, list
) {
152 list_del(&waiter
->list
);
154 WARN_ON(atomic_xchg(&waiter
->state
, WLS_HANDLED
) !=
156 kref_put(&waiter
->refcount
, waiter_release
);
162 * Remove & handle all waiters that have completed for the given syncpt
164 static int process_wait_list(struct host1x
*host
,
165 struct host1x_syncpt
*syncpt
,
168 struct list_head completed
[HOST1X_INTR_ACTION_COUNT
];
172 for (i
= 0; i
< HOST1X_INTR_ACTION_COUNT
; ++i
)
173 INIT_LIST_HEAD(completed
+ i
);
175 spin_lock(&syncpt
->intr
.lock
);
177 remove_completed_waiters(&syncpt
->intr
.wait_head
, threshold
,
180 empty
= list_empty(&syncpt
->intr
.wait_head
);
182 host1x_hw_intr_disable_syncpt_intr(host
, syncpt
->id
);
184 reset_threshold_interrupt(host
, &syncpt
->intr
.wait_head
,
187 spin_unlock(&syncpt
->intr
.lock
);
189 run_handlers(completed
);
195 * Sync point threshold interrupt service thread function
196 * Handles sync point threshold triggers, in thread context
199 static void syncpt_thresh_work(struct work_struct
*work
)
201 struct host1x_syncpt_intr
*syncpt_intr
=
202 container_of(work
, struct host1x_syncpt_intr
, work
);
203 struct host1x_syncpt
*syncpt
=
204 container_of(syncpt_intr
, struct host1x_syncpt
, intr
);
205 unsigned int id
= syncpt
->id
;
206 struct host1x
*host
= syncpt
->host
;
208 (void)process_wait_list(host
, syncpt
,
209 host1x_syncpt_load(host
->syncpt
+ id
));
212 int host1x_intr_add_action(struct host1x
*host
, u32 id
, u32 thresh
,
213 enum host1x_intr_action action
, void *data
,
214 struct host1x_waitlist
*waiter
, void **ref
)
216 struct host1x_syncpt
*syncpt
;
219 if (waiter
== NULL
) {
220 pr_warn("%s: NULL waiter\n", __func__
);
224 /* initialize a new waiter */
225 INIT_LIST_HEAD(&waiter
->list
);
226 kref_init(&waiter
->refcount
);
228 kref_get(&waiter
->refcount
);
229 waiter
->thresh
= thresh
;
230 waiter
->action
= action
;
231 atomic_set(&waiter
->state
, WLS_PENDING
);
235 syncpt
= host
->syncpt
+ id
;
237 spin_lock(&syncpt
->intr
.lock
);
239 queue_was_empty
= list_empty(&syncpt
->intr
.wait_head
);
241 if (add_waiter_to_queue(waiter
, &syncpt
->intr
.wait_head
)) {
242 /* added at head of list - new threshold value */
243 host1x_hw_intr_set_syncpt_threshold(host
, id
, thresh
);
245 /* added as first waiter - enable interrupt */
247 host1x_hw_intr_enable_syncpt_intr(host
, id
);
250 spin_unlock(&syncpt
->intr
.lock
);
257 void host1x_intr_put_ref(struct host1x
*host
, u32 id
, void *ref
)
259 struct host1x_waitlist
*waiter
= ref
;
260 struct host1x_syncpt
*syncpt
;
262 while (atomic_cmpxchg(&waiter
->state
, WLS_PENDING
, WLS_CANCELLED
) ==
266 syncpt
= host
->syncpt
+ id
;
267 (void)process_wait_list(host
, syncpt
,
268 host1x_syncpt_load(host
->syncpt
+ id
));
270 kref_put(&waiter
->refcount
, waiter_release
);
273 int host1x_intr_init(struct host1x
*host
, unsigned int irq_sync
)
276 u32 nb_pts
= host1x_syncpt_nb_pts(host
);
278 mutex_init(&host
->intr_mutex
);
279 host
->intr_syncpt_irq
= irq_sync
;
280 host
->intr_wq
= create_workqueue("host_syncpt");
284 for (id
= 0; id
< nb_pts
; ++id
) {
285 struct host1x_syncpt
*syncpt
= host
->syncpt
+ id
;
287 spin_lock_init(&syncpt
->intr
.lock
);
288 INIT_LIST_HEAD(&syncpt
->intr
.wait_head
);
289 snprintf(syncpt
->intr
.thresh_irq_name
,
290 sizeof(syncpt
->intr
.thresh_irq_name
),
291 "host1x_sp_%02d", id
);
294 host1x_intr_start(host
);
299 void host1x_intr_deinit(struct host1x
*host
)
301 host1x_intr_stop(host
);
302 destroy_workqueue(host
->intr_wq
);
305 void host1x_intr_start(struct host1x
*host
)
307 u32 hz
= clk_get_rate(host
->clk
);
310 mutex_lock(&host
->intr_mutex
);
311 err
= host1x_hw_intr_init_host_sync(host
, DIV_ROUND_UP(hz
, 1000000),
314 mutex_unlock(&host
->intr_mutex
);
317 mutex_unlock(&host
->intr_mutex
);
320 void host1x_intr_stop(struct host1x
*host
)
323 struct host1x_syncpt
*syncpt
= host
->syncpt
;
324 u32 nb_pts
= host1x_syncpt_nb_pts(host
);
326 mutex_lock(&host
->intr_mutex
);
328 host1x_hw_intr_disable_all_syncpt_intrs(host
);
330 for (id
= 0; id
< nb_pts
; ++id
) {
331 struct host1x_waitlist
*waiter
, *next
;
333 list_for_each_entry_safe(waiter
, next
,
334 &syncpt
[id
].intr
.wait_head
, list
) {
335 if (atomic_cmpxchg(&waiter
->state
,
336 WLS_CANCELLED
, WLS_HANDLED
) == WLS_CANCELLED
) {
337 list_del(&waiter
->list
);
338 kref_put(&waiter
->refcount
, waiter_release
);
342 if (!list_empty(&syncpt
[id
].intr
.wait_head
)) {
343 /* output diagnostics */
344 mutex_unlock(&host
->intr_mutex
);
345 pr_warn("%s cannot stop syncpt intr id=%d\n",
351 host1x_hw_intr_free_syncpt_irq(host
);
353 mutex_unlock(&host
->intr_mutex
);