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
;
129 static void action_wakeup_interruptible(struct host1x_waitlist
*waiter
)
131 wait_queue_head_t
*wq
= waiter
->data
;
133 wake_up_interruptible(wq
);
136 typedef void (*action_handler
)(struct host1x_waitlist
*waiter
);
138 static const action_handler action_handlers
[HOST1X_INTR_ACTION_COUNT
] = {
139 action_submit_complete
,
141 action_wakeup_interruptible
,
144 static void run_handlers(struct list_head completed
[HOST1X_INTR_ACTION_COUNT
])
146 struct list_head
*head
= completed
;
149 for (i
= 0; i
< HOST1X_INTR_ACTION_COUNT
; ++i
, ++head
) {
150 action_handler handler
= action_handlers
[i
];
151 struct host1x_waitlist
*waiter
, *next
;
153 list_for_each_entry_safe(waiter
, next
, head
, list
) {
154 list_del(&waiter
->list
);
156 WARN_ON(atomic_xchg(&waiter
->state
, WLS_HANDLED
) !=
158 kref_put(&waiter
->refcount
, waiter_release
);
164 * Remove & handle all waiters that have completed for the given syncpt
166 static int process_wait_list(struct host1x
*host
,
167 struct host1x_syncpt
*syncpt
,
170 struct list_head completed
[HOST1X_INTR_ACTION_COUNT
];
174 for (i
= 0; i
< HOST1X_INTR_ACTION_COUNT
; ++i
)
175 INIT_LIST_HEAD(completed
+ i
);
177 spin_lock(&syncpt
->intr
.lock
);
179 remove_completed_waiters(&syncpt
->intr
.wait_head
, threshold
,
182 empty
= list_empty(&syncpt
->intr
.wait_head
);
184 host1x_hw_intr_disable_syncpt_intr(host
, syncpt
->id
);
186 reset_threshold_interrupt(host
, &syncpt
->intr
.wait_head
,
189 spin_unlock(&syncpt
->intr
.lock
);
191 run_handlers(completed
);
197 * Sync point threshold interrupt service thread function
198 * Handles sync point threshold triggers, in thread context
201 static void syncpt_thresh_work(struct work_struct
*work
)
203 struct host1x_syncpt_intr
*syncpt_intr
=
204 container_of(work
, struct host1x_syncpt_intr
, work
);
205 struct host1x_syncpt
*syncpt
=
206 container_of(syncpt_intr
, struct host1x_syncpt
, intr
);
207 unsigned int id
= syncpt
->id
;
208 struct host1x
*host
= syncpt
->host
;
210 (void)process_wait_list(host
, syncpt
,
211 host1x_syncpt_load(host
->syncpt
+ id
));
214 int host1x_intr_add_action(struct host1x
*host
, struct host1x_syncpt
*syncpt
,
215 u32 thresh
, enum host1x_intr_action action
,
216 void *data
, struct host1x_waitlist
*waiter
,
221 if (waiter
== NULL
) {
222 pr_warn("%s: NULL waiter\n", __func__
);
226 /* initialize a new waiter */
227 INIT_LIST_HEAD(&waiter
->list
);
228 kref_init(&waiter
->refcount
);
230 kref_get(&waiter
->refcount
);
231 waiter
->thresh
= thresh
;
232 waiter
->action
= action
;
233 atomic_set(&waiter
->state
, WLS_PENDING
);
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
, syncpt
->id
, thresh
);
245 /* added as first waiter - enable interrupt */
247 host1x_hw_intr_enable_syncpt_intr(host
, syncpt
->id
);
250 spin_unlock(&syncpt
->intr
.lock
);
257 void host1x_intr_put_ref(struct host1x
*host
, unsigned int 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
;
281 for (id
= 0; id
< nb_pts
; ++id
) {
282 struct host1x_syncpt
*syncpt
= host
->syncpt
+ id
;
284 spin_lock_init(&syncpt
->intr
.lock
);
285 INIT_LIST_HEAD(&syncpt
->intr
.wait_head
);
286 snprintf(syncpt
->intr
.thresh_irq_name
,
287 sizeof(syncpt
->intr
.thresh_irq_name
),
288 "host1x_sp_%02u", id
);
291 host1x_intr_start(host
);
296 void host1x_intr_deinit(struct host1x
*host
)
298 host1x_intr_stop(host
);
301 void host1x_intr_start(struct host1x
*host
)
303 u32 hz
= clk_get_rate(host
->clk
);
306 mutex_lock(&host
->intr_mutex
);
307 err
= host1x_hw_intr_init_host_sync(host
, DIV_ROUND_UP(hz
, 1000000),
310 mutex_unlock(&host
->intr_mutex
);
313 mutex_unlock(&host
->intr_mutex
);
316 void host1x_intr_stop(struct host1x
*host
)
319 struct host1x_syncpt
*syncpt
= host
->syncpt
;
320 u32 nb_pts
= host1x_syncpt_nb_pts(host
);
322 mutex_lock(&host
->intr_mutex
);
324 host1x_hw_intr_disable_all_syncpt_intrs(host
);
326 for (id
= 0; id
< nb_pts
; ++id
) {
327 struct host1x_waitlist
*waiter
, *next
;
329 list_for_each_entry_safe(waiter
, next
,
330 &syncpt
[id
].intr
.wait_head
, list
) {
331 if (atomic_cmpxchg(&waiter
->state
,
332 WLS_CANCELLED
, WLS_HANDLED
) == WLS_CANCELLED
) {
333 list_del(&waiter
->list
);
334 kref_put(&waiter
->refcount
, waiter_release
);
338 if (!list_empty(&syncpt
[id
].intr
.wait_head
)) {
339 /* output diagnostics */
340 mutex_unlock(&host
->intr_mutex
);
341 pr_warn("%s cannot stop syncpt intr id=%u\n",
347 host1x_hw_intr_free_syncpt_irq(host
);
349 mutex_unlock(&host
->intr_mutex
);