xref: /linux/drivers/gpu/host1x/intr.c (revision 570f7e331f5febb30f1384817463c7e42b65ca7d)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Tegra host1x Interrupt Management
4  *
5  * Copyright (c) 2010-2021, NVIDIA Corporation.
6  */
7 
8 #include <linux/clk.h>
9 #include <linux/interrupt.h>
10 #include <linux/overflow.h>
11 #include "dev.h"
12 #include "fence.h"
13 #include "intr.h"
14 
15 static void host1x_intr_add_fence_to_list(struct host1x_fence_list *list,
16 					  struct host1x_syncpt_fence *fence)
17 {
18 	struct host1x_syncpt_fence *fence_in_list;
19 
20 	list_for_each_entry_reverse(fence_in_list, &list->list, list) {
21 		if ((s32)wrapping_sub(u32, fence_in_list->threshold, fence->threshold) <= 0) {
22 			/* Fence in list is before us, we can insert here */
23 			list_add(&fence->list, &fence_in_list->list);
24 			return;
25 		}
26 	}
27 
28 	/* Add as first in list */
29 	list_add(&fence->list, &list->list);
30 }
31 
32 static void host1x_intr_update_hw_state(struct host1x *host, struct host1x_syncpt *sp)
33 {
34 	struct host1x_syncpt_fence *fence;
35 
36 	if (!list_empty(&sp->fences.list)) {
37 		fence = list_first_entry(&sp->fences.list, struct host1x_syncpt_fence, list);
38 
39 		host1x_hw_intr_set_syncpt_threshold(host, sp->id, fence->threshold);
40 		host1x_hw_intr_enable_syncpt_intr(host, sp->id);
41 	} else {
42 		host1x_hw_intr_disable_syncpt_intr(host, sp->id);
43 	}
44 }
45 
46 void host1x_intr_add_fence_locked(struct host1x *host, struct host1x_syncpt_fence *fence)
47 {
48 	struct host1x_fence_list *fence_list = &fence->sp->fences;
49 
50 	INIT_LIST_HEAD(&fence->list);
51 
52 	host1x_intr_add_fence_to_list(fence_list, fence);
53 	host1x_intr_update_hw_state(host, fence->sp);
54 }
55 
56 bool host1x_intr_remove_fence(struct host1x *host, struct host1x_syncpt_fence *fence)
57 {
58 	struct host1x_fence_list *fence_list = &fence->sp->fences;
59 	unsigned long irqflags;
60 
61 	spin_lock_irqsave(&fence_list->lock, irqflags);
62 
63 	if (list_empty(&fence->list)) {
64 		spin_unlock_irqrestore(&fence_list->lock, irqflags);
65 		return false;
66 	}
67 
68 	list_del_init(&fence->list);
69 	host1x_intr_update_hw_state(host, fence->sp);
70 
71 	spin_unlock_irqrestore(&fence_list->lock, irqflags);
72 
73 	return true;
74 }
75 
76 void host1x_intr_handle_interrupt(struct host1x *host, unsigned int id)
77 {
78 	struct host1x_syncpt *sp = &host->syncpt[id];
79 	struct host1x_syncpt_fence *fence, *tmp;
80 	unsigned int value;
81 
82 	value = host1x_syncpt_load(sp);
83 
84 	spin_lock(&sp->fences.lock);
85 
86 	list_for_each_entry_safe(fence, tmp, &sp->fences.list, list) {
87 		if ((wrapping_sub(u32, value, fence->threshold) & 0x80000000U) != 0U) {
88 			/* Fence is not yet expired, we are done */
89 			break;
90 		}
91 
92 		list_del_init(&fence->list);
93 		host1x_fence_signal(fence);
94 	}
95 
96 	/*
97 	 * Re-enable interrupt if necessary. The ISR already disabled the interrupt,
98 	 * so if no fences remain, no update is needed.
99 	 */
100 	if (!list_empty(&sp->fences.list))
101 		host1x_intr_update_hw_state(host, sp);
102 
103 	spin_unlock(&sp->fences.lock);
104 }
105 
106 int host1x_intr_init(struct host1x *host)
107 {
108 	struct host1x_intr_irq_data *irq_data;
109 	unsigned int id;
110 	int i, err;
111 
112 	for (id = 0; id < host1x_syncpt_nb_pts(host); ++id) {
113 		struct host1x_syncpt *syncpt = &host->syncpt[id];
114 
115 		spin_lock_init(&syncpt->fences.lock);
116 		INIT_LIST_HEAD(&syncpt->fences.list);
117 	}
118 
119 	irq_data = devm_kcalloc(host->dev, host->num_syncpt_irqs, sizeof(irq_data[0]), GFP_KERNEL);
120 	if (!irq_data)
121 		return -ENOMEM;
122 
123 	host1x_hw_intr_disable_all_syncpt_intrs(host);
124 
125 	for (i = 0; i < host->num_syncpt_irqs; i++) {
126 		irq_data[i].host = host;
127 		irq_data[i].offset = i;
128 
129 		err = devm_request_irq(host->dev, host->syncpt_irqs[i],
130 				       host->intr_op->isr, IRQF_SHARED,
131 				       "host1x_syncpt", &irq_data[i]);
132 		if (err < 0)
133 			return err;
134 	}
135 
136 	return 0;
137 }
138 
139 void host1x_intr_deinit(struct host1x *host)
140 {
141 }
142 
143 void host1x_intr_start(struct host1x *host)
144 {
145 	u32 hz = clk_get_rate(host->clk);
146 	int err;
147 
148 	mutex_lock(&host->intr_mutex);
149 	err = host1x_hw_intr_init_host_sync(host, DIV_ROUND_UP(hz, 1000000));
150 	if (err) {
151 		mutex_unlock(&host->intr_mutex);
152 		return;
153 	}
154 	mutex_unlock(&host->intr_mutex);
155 }
156 
157 void host1x_intr_stop(struct host1x *host)
158 {
159 	host1x_hw_intr_disable_all_syncpt_intrs(host);
160 }
161