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