Commit | Line | Data |
---|---|---|
f5f9454c | 1 | /* |
8bb0daff | 2 | * drivers/gpu/drm/omapdrm/omap_irq.c |
f5f9454c RC |
3 | * |
4 | * Copyright (C) 2012 Texas Instruments | |
5 | * Author: Rob Clark <rob.clark@linaro.org> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License version 2 as published by | |
9 | * the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
14 | * more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along with | |
17 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include "omap_drv.h" | |
21 | ||
22 | static DEFINE_SPINLOCK(list_lock); | |
23 | ||
24 | static void omap_irq_error_handler(struct omap_drm_irq *irq, | |
25 | uint32_t irqstatus) | |
26 | { | |
27 | DRM_ERROR("errors: %08x\n", irqstatus); | |
28 | } | |
29 | ||
30 | /* call with list_lock and dispc runtime held */ | |
31 | static void omap_irq_update(struct drm_device *dev) | |
32 | { | |
33 | struct omap_drm_private *priv = dev->dev_private; | |
34 | struct omap_drm_irq *irq; | |
35 | uint32_t irqmask = priv->vblank_mask; | |
36 | ||
37 | BUG_ON(!spin_is_locked(&list_lock)); | |
38 | ||
39 | list_for_each_entry(irq, &priv->irq_list, node) | |
40 | irqmask |= irq->irqmask; | |
41 | ||
42 | DBG("irqmask=%08x", irqmask); | |
43 | ||
44 | dispc_write_irqenable(irqmask); | |
45 | dispc_read_irqenable(); /* flush posted write */ | |
46 | } | |
47 | ||
6da9f891 | 48 | void __omap_irq_register(struct drm_device *dev, struct omap_drm_irq *irq) |
f5f9454c RC |
49 | { |
50 | struct omap_drm_private *priv = dev->dev_private; | |
51 | unsigned long flags; | |
52 | ||
f5f9454c RC |
53 | spin_lock_irqsave(&list_lock, flags); |
54 | ||
55 | if (!WARN_ON(irq->registered)) { | |
56 | irq->registered = true; | |
57 | list_add(&irq->node, &priv->irq_list); | |
58 | omap_irq_update(dev); | |
59 | } | |
60 | ||
61 | spin_unlock_irqrestore(&list_lock, flags); | |
6da9f891 TV |
62 | } |
63 | ||
64 | void omap_irq_register(struct drm_device *dev, struct omap_drm_irq *irq) | |
65 | { | |
66 | dispc_runtime_get(); | |
67 | ||
68 | __omap_irq_register(dev, irq); | |
69 | ||
f5f9454c RC |
70 | dispc_runtime_put(); |
71 | } | |
72 | ||
6da9f891 | 73 | void __omap_irq_unregister(struct drm_device *dev, struct omap_drm_irq *irq) |
f5f9454c RC |
74 | { |
75 | unsigned long flags; | |
76 | ||
f5f9454c RC |
77 | spin_lock_irqsave(&list_lock, flags); |
78 | ||
79 | if (!WARN_ON(!irq->registered)) { | |
80 | irq->registered = false; | |
81 | list_del(&irq->node); | |
82 | omap_irq_update(dev); | |
83 | } | |
84 | ||
85 | spin_unlock_irqrestore(&list_lock, flags); | |
6da9f891 TV |
86 | } |
87 | ||
88 | void omap_irq_unregister(struct drm_device *dev, struct omap_drm_irq *irq) | |
89 | { | |
90 | dispc_runtime_get(); | |
91 | ||
92 | __omap_irq_unregister(dev, irq); | |
93 | ||
f5f9454c RC |
94 | dispc_runtime_put(); |
95 | } | |
96 | ||
97 | struct omap_irq_wait { | |
98 | struct omap_drm_irq irq; | |
99 | int count; | |
100 | }; | |
101 | ||
102 | static DECLARE_WAIT_QUEUE_HEAD(wait_event); | |
103 | ||
104 | static void wait_irq(struct omap_drm_irq *irq, uint32_t irqstatus) | |
105 | { | |
106 | struct omap_irq_wait *wait = | |
107 | container_of(irq, struct omap_irq_wait, irq); | |
108 | wait->count--; | |
109 | wake_up_all(&wait_event); | |
110 | } | |
111 | ||
112 | struct omap_irq_wait * omap_irq_wait_init(struct drm_device *dev, | |
113 | uint32_t irqmask, int count) | |
114 | { | |
115 | struct omap_irq_wait *wait = kzalloc(sizeof(*wait), GFP_KERNEL); | |
116 | wait->irq.irq = wait_irq; | |
117 | wait->irq.irqmask = irqmask; | |
118 | wait->count = count; | |
119 | omap_irq_register(dev, &wait->irq); | |
120 | return wait; | |
121 | } | |
122 | ||
123 | int omap_irq_wait(struct drm_device *dev, struct omap_irq_wait *wait, | |
124 | unsigned long timeout) | |
125 | { | |
126 | int ret = wait_event_timeout(wait_event, (wait->count <= 0), timeout); | |
127 | omap_irq_unregister(dev, &wait->irq); | |
128 | kfree(wait); | |
129 | if (ret == 0) | |
130 | return -1; | |
131 | return 0; | |
132 | } | |
133 | ||
134 | /** | |
135 | * enable_vblank - enable vblank interrupt events | |
136 | * @dev: DRM device | |
137 | * @crtc: which irq to enable | |
138 | * | |
139 | * Enable vblank interrupts for @crtc. If the device doesn't have | |
140 | * a hardware vblank counter, this routine should be a no-op, since | |
141 | * interrupts will have to stay on to keep the count accurate. | |
142 | * | |
143 | * RETURNS | |
144 | * Zero on success, appropriate errno if the given @crtc's vblank | |
145 | * interrupt cannot be enabled. | |
146 | */ | |
0d8f371f | 147 | int omap_irq_enable_vblank(struct drm_device *dev, int crtc_id) |
f5f9454c RC |
148 | { |
149 | struct omap_drm_private *priv = dev->dev_private; | |
0d8f371f | 150 | struct drm_crtc *crtc = priv->crtcs[crtc_id]; |
f5f9454c RC |
151 | unsigned long flags; |
152 | ||
0d8f371f | 153 | DBG("dev=%p, crtc=%d", dev, crtc_id); |
f5f9454c RC |
154 | |
155 | dispc_runtime_get(); | |
156 | spin_lock_irqsave(&list_lock, flags); | |
157 | priv->vblank_mask |= pipe2vbl(crtc); | |
158 | omap_irq_update(dev); | |
159 | spin_unlock_irqrestore(&list_lock, flags); | |
160 | dispc_runtime_put(); | |
161 | ||
162 | return 0; | |
163 | } | |
164 | ||
165 | /** | |
166 | * disable_vblank - disable vblank interrupt events | |
167 | * @dev: DRM device | |
168 | * @crtc: which irq to enable | |
169 | * | |
170 | * Disable vblank interrupts for @crtc. If the device doesn't have | |
171 | * a hardware vblank counter, this routine should be a no-op, since | |
172 | * interrupts will have to stay on to keep the count accurate. | |
173 | */ | |
0d8f371f | 174 | void omap_irq_disable_vblank(struct drm_device *dev, int crtc_id) |
f5f9454c RC |
175 | { |
176 | struct omap_drm_private *priv = dev->dev_private; | |
0d8f371f | 177 | struct drm_crtc *crtc = priv->crtcs[crtc_id]; |
f5f9454c RC |
178 | unsigned long flags; |
179 | ||
0d8f371f | 180 | DBG("dev=%p, crtc=%d", dev, crtc_id); |
f5f9454c RC |
181 | |
182 | dispc_runtime_get(); | |
183 | spin_lock_irqsave(&list_lock, flags); | |
184 | priv->vblank_mask &= ~pipe2vbl(crtc); | |
185 | omap_irq_update(dev); | |
186 | spin_unlock_irqrestore(&list_lock, flags); | |
187 | dispc_runtime_put(); | |
188 | } | |
189 | ||
e9f0d76f | 190 | irqreturn_t omap_irq_handler(int irq, void *arg) |
f5f9454c RC |
191 | { |
192 | struct drm_device *dev = (struct drm_device *) arg; | |
193 | struct omap_drm_private *priv = dev->dev_private; | |
194 | struct omap_drm_irq *handler, *n; | |
195 | unsigned long flags; | |
196 | unsigned int id; | |
197 | u32 irqstatus; | |
198 | ||
199 | irqstatus = dispc_read_irqstatus(); | |
200 | dispc_clear_irqstatus(irqstatus); | |
201 | dispc_read_irqstatus(); /* flush posted write */ | |
202 | ||
203 | VERB("irqs: %08x", irqstatus); | |
204 | ||
0d8f371f AT |
205 | for (id = 0; id < priv->num_crtcs; id++) { |
206 | struct drm_crtc *crtc = priv->crtcs[id]; | |
207 | ||
208 | if (irqstatus & pipe2vbl(crtc)) | |
f5f9454c | 209 | drm_handle_vblank(dev, id); |
0d8f371f | 210 | } |
f5f9454c RC |
211 | |
212 | spin_lock_irqsave(&list_lock, flags); | |
213 | list_for_each_entry_safe(handler, n, &priv->irq_list, node) { | |
214 | if (handler->irqmask & irqstatus) { | |
215 | spin_unlock_irqrestore(&list_lock, flags); | |
216 | handler->irq(handler, handler->irqmask & irqstatus); | |
217 | spin_lock_irqsave(&list_lock, flags); | |
218 | } | |
219 | } | |
220 | spin_unlock_irqrestore(&list_lock, flags); | |
221 | ||
222 | return IRQ_HANDLED; | |
223 | } | |
224 | ||
225 | void omap_irq_preinstall(struct drm_device *dev) | |
226 | { | |
227 | DBG("dev=%p", dev); | |
228 | dispc_runtime_get(); | |
229 | dispc_clear_irqstatus(0xffffffff); | |
230 | dispc_runtime_put(); | |
231 | } | |
232 | ||
233 | int omap_irq_postinstall(struct drm_device *dev) | |
234 | { | |
235 | struct omap_drm_private *priv = dev->dev_private; | |
236 | struct omap_drm_irq *error_handler = &priv->error_handler; | |
237 | ||
238 | DBG("dev=%p", dev); | |
239 | ||
240 | INIT_LIST_HEAD(&priv->irq_list); | |
241 | ||
242 | error_handler->irq = omap_irq_error_handler; | |
243 | error_handler->irqmask = DISPC_IRQ_OCP_ERR; | |
244 | ||
245 | /* for now ignore DISPC_IRQ_SYNC_LOST_DIGIT.. really I think | |
246 | * we just need to ignore it while enabling tv-out | |
247 | */ | |
248 | error_handler->irqmask &= ~DISPC_IRQ_SYNC_LOST_DIGIT; | |
249 | ||
250 | omap_irq_register(dev, error_handler); | |
251 | ||
252 | return 0; | |
253 | } | |
254 | ||
255 | void omap_irq_uninstall(struct drm_device *dev) | |
256 | { | |
257 | DBG("dev=%p", dev); | |
258 | // TODO prolly need to call drm_irq_uninstall() somewhere too | |
259 | } | |
260 | ||
261 | /* | |
262 | * We need a special version, instead of just using drm_irq_install(), | |
263 | * because we need to register the irq via omapdss. Once omapdss and | |
264 | * omapdrm are merged together we can assign the dispc hwmod data to | |
265 | * ourselves and drop these and just use drm_irq_{install,uninstall}() | |
266 | */ | |
267 | ||
268 | int omap_drm_irq_install(struct drm_device *dev) | |
269 | { | |
270 | int ret; | |
271 | ||
272 | mutex_lock(&dev->struct_mutex); | |
273 | ||
274 | if (dev->irq_enabled) { | |
275 | mutex_unlock(&dev->struct_mutex); | |
276 | return -EBUSY; | |
277 | } | |
4423843c | 278 | dev->irq_enabled = true; |
f5f9454c RC |
279 | mutex_unlock(&dev->struct_mutex); |
280 | ||
281 | /* Before installing handler */ | |
282 | if (dev->driver->irq_preinstall) | |
283 | dev->driver->irq_preinstall(dev); | |
284 | ||
285 | ret = dispc_request_irq(dev->driver->irq_handler, dev); | |
286 | ||
287 | if (ret < 0) { | |
288 | mutex_lock(&dev->struct_mutex); | |
4423843c | 289 | dev->irq_enabled = false; |
f5f9454c RC |
290 | mutex_unlock(&dev->struct_mutex); |
291 | return ret; | |
292 | } | |
293 | ||
294 | /* After installing handler */ | |
295 | if (dev->driver->irq_postinstall) | |
296 | ret = dev->driver->irq_postinstall(dev); | |
297 | ||
298 | if (ret < 0) { | |
299 | mutex_lock(&dev->struct_mutex); | |
4423843c | 300 | dev->irq_enabled = false; |
f5f9454c RC |
301 | mutex_unlock(&dev->struct_mutex); |
302 | dispc_free_irq(dev); | |
303 | } | |
304 | ||
305 | return ret; | |
306 | } | |
307 | ||
308 | int omap_drm_irq_uninstall(struct drm_device *dev) | |
309 | { | |
310 | unsigned long irqflags; | |
4423843c VS |
311 | bool irq_enabled; |
312 | int i; | |
f5f9454c RC |
313 | |
314 | mutex_lock(&dev->struct_mutex); | |
315 | irq_enabled = dev->irq_enabled; | |
4423843c | 316 | dev->irq_enabled = false; |
f5f9454c RC |
317 | mutex_unlock(&dev->struct_mutex); |
318 | ||
319 | /* | |
320 | * Wake up any waiters so they don't hang. | |
321 | */ | |
322 | if (dev->num_crtcs) { | |
323 | spin_lock_irqsave(&dev->vbl_lock, irqflags); | |
324 | for (i = 0; i < dev->num_crtcs; i++) { | |
57ed0f7b | 325 | wake_up(&dev->vblank[i].queue); |
5380e929 VS |
326 | dev->vblank[i].enabled = false; |
327 | dev->vblank[i].last = | |
f5f9454c RC |
328 | dev->driver->get_vblank_counter(dev, i); |
329 | } | |
330 | spin_unlock_irqrestore(&dev->vbl_lock, irqflags); | |
331 | } | |
332 | ||
333 | if (!irq_enabled) | |
334 | return -EINVAL; | |
335 | ||
336 | if (dev->driver->irq_uninstall) | |
337 | dev->driver->irq_uninstall(dev); | |
338 | ||
339 | dispc_free_irq(dev); | |
340 | ||
341 | return 0; | |
342 | } |