xref: /linux/drivers/gpu/host1x/intr.c (revision 4dd10fbbeb9c33709edc89916e19c03c780a0751)
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_kzalloc_objs(host->dev, irq_data[0],
120 				     host->num_syncpt_irqs);
121 	if (!irq_data)
122 		return -ENOMEM;
123 
124 	host1x_hw_intr_disable_all_syncpt_intrs(host);
125 
126 	for (i = 0; i < host->num_syncpt_irqs; i++) {
127 		irq_data[i].host = host;
128 		irq_data[i].offset = i;
129 
130 		err = devm_request_irq(host->dev, host->syncpt_irqs[i],
131 				       host->intr_op->isr, IRQF_SHARED,
132 				       "host1x_syncpt", &irq_data[i]);
133 		if (err < 0)
134 			return err;
135 	}
136 
137 	return 0;
138 }
139 
140 void host1x_intr_deinit(struct host1x *host)
141 {
142 }
143 
144 void host1x_intr_start(struct host1x *host)
145 {
146 	u32 hz = clk_get_rate(host->clk);
147 	int err;
148 
149 	mutex_lock(&host->intr_mutex);
150 	err = host1x_hw_intr_init_host_sync(host, DIV_ROUND_UP(hz, 1000000));
151 	if (err) {
152 		mutex_unlock(&host->intr_mutex);
153 		return;
154 	}
155 	mutex_unlock(&host->intr_mutex);
156 }
157 
158 void host1x_intr_stop(struct host1x *host)
159 {
160 	host1x_hw_intr_disable_all_syncpt_intrs(host);
161 }
162