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 | ||
48 | void omap_irq_register(struct drm_device *dev, struct omap_drm_irq *irq) | |
49 | { | |
50 | struct omap_drm_private *priv = dev->dev_private; | |
51 | unsigned long flags; | |
52 | ||
53 | dispc_runtime_get(); | |
54 | spin_lock_irqsave(&list_lock, flags); | |
55 | ||
56 | if (!WARN_ON(irq->registered)) { | |
57 | irq->registered = true; | |
58 | list_add(&irq->node, &priv->irq_list); | |
59 | omap_irq_update(dev); | |
60 | } | |
61 | ||
62 | spin_unlock_irqrestore(&list_lock, flags); | |
63 | dispc_runtime_put(); | |
64 | } | |
65 | ||
66 | void omap_irq_unregister(struct drm_device *dev, struct omap_drm_irq *irq) | |
67 | { | |
68 | unsigned long flags; | |
69 | ||
70 | dispc_runtime_get(); | |
71 | spin_lock_irqsave(&list_lock, flags); | |
72 | ||
73 | if (!WARN_ON(!irq->registered)) { | |
74 | irq->registered = false; | |
75 | list_del(&irq->node); | |
76 | omap_irq_update(dev); | |
77 | } | |
78 | ||
79 | spin_unlock_irqrestore(&list_lock, flags); | |
80 | dispc_runtime_put(); | |
81 | } | |
82 | ||
83 | struct omap_irq_wait { | |
84 | struct omap_drm_irq irq; | |
85 | int count; | |
86 | }; | |
87 | ||
88 | static DECLARE_WAIT_QUEUE_HEAD(wait_event); | |
89 | ||
90 | static void wait_irq(struct omap_drm_irq *irq, uint32_t irqstatus) | |
91 | { | |
92 | struct omap_irq_wait *wait = | |
93 | container_of(irq, struct omap_irq_wait, irq); | |
94 | wait->count--; | |
95 | wake_up_all(&wait_event); | |
96 | } | |
97 | ||
98 | struct omap_irq_wait * omap_irq_wait_init(struct drm_device *dev, | |
99 | uint32_t irqmask, int count) | |
100 | { | |
101 | struct omap_irq_wait *wait = kzalloc(sizeof(*wait), GFP_KERNEL); | |
102 | wait->irq.irq = wait_irq; | |
103 | wait->irq.irqmask = irqmask; | |
104 | wait->count = count; | |
105 | omap_irq_register(dev, &wait->irq); | |
106 | return wait; | |
107 | } | |
108 | ||
109 | int omap_irq_wait(struct drm_device *dev, struct omap_irq_wait *wait, | |
110 | unsigned long timeout) | |
111 | { | |
112 | int ret = wait_event_timeout(wait_event, (wait->count <= 0), timeout); | |
113 | omap_irq_unregister(dev, &wait->irq); | |
114 | kfree(wait); | |
115 | if (ret == 0) | |
116 | return -1; | |
117 | return 0; | |
118 | } | |
119 | ||
120 | /** | |
121 | * enable_vblank - enable vblank interrupt events | |
122 | * @dev: DRM device | |
123 | * @crtc: which irq to enable | |
124 | * | |
125 | * Enable vblank interrupts for @crtc. If the device doesn't have | |
126 | * a hardware vblank counter, this routine should be a no-op, since | |
127 | * interrupts will have to stay on to keep the count accurate. | |
128 | * | |
129 | * RETURNS | |
130 | * Zero on success, appropriate errno if the given @crtc's vblank | |
131 | * interrupt cannot be enabled. | |
132 | */ | |
0d8f371f | 133 | int omap_irq_enable_vblank(struct drm_device *dev, int crtc_id) |
f5f9454c RC |
134 | { |
135 | struct omap_drm_private *priv = dev->dev_private; | |
0d8f371f | 136 | struct drm_crtc *crtc = priv->crtcs[crtc_id]; |
f5f9454c RC |
137 | unsigned long flags; |
138 | ||
0d8f371f | 139 | DBG("dev=%p, crtc=%d", dev, crtc_id); |
f5f9454c RC |
140 | |
141 | dispc_runtime_get(); | |
142 | spin_lock_irqsave(&list_lock, flags); | |
143 | priv->vblank_mask |= pipe2vbl(crtc); | |
144 | omap_irq_update(dev); | |
145 | spin_unlock_irqrestore(&list_lock, flags); | |
146 | dispc_runtime_put(); | |
147 | ||
148 | return 0; | |
149 | } | |
150 | ||
151 | /** | |
152 | * disable_vblank - disable vblank interrupt events | |
153 | * @dev: DRM device | |
154 | * @crtc: which irq to enable | |
155 | * | |
156 | * Disable vblank interrupts for @crtc. If the device doesn't have | |
157 | * a hardware vblank counter, this routine should be a no-op, since | |
158 | * interrupts will have to stay on to keep the count accurate. | |
159 | */ | |
0d8f371f | 160 | void omap_irq_disable_vblank(struct drm_device *dev, int crtc_id) |
f5f9454c RC |
161 | { |
162 | struct omap_drm_private *priv = dev->dev_private; | |
0d8f371f | 163 | struct drm_crtc *crtc = priv->crtcs[crtc_id]; |
f5f9454c RC |
164 | unsigned long flags; |
165 | ||
0d8f371f | 166 | DBG("dev=%p, crtc=%d", dev, crtc_id); |
f5f9454c RC |
167 | |
168 | dispc_runtime_get(); | |
169 | spin_lock_irqsave(&list_lock, flags); | |
170 | priv->vblank_mask &= ~pipe2vbl(crtc); | |
171 | omap_irq_update(dev); | |
172 | spin_unlock_irqrestore(&list_lock, flags); | |
173 | dispc_runtime_put(); | |
174 | } | |
175 | ||
176 | irqreturn_t omap_irq_handler(DRM_IRQ_ARGS) | |
177 | { | |
178 | struct drm_device *dev = (struct drm_device *) arg; | |
179 | struct omap_drm_private *priv = dev->dev_private; | |
180 | struct omap_drm_irq *handler, *n; | |
181 | unsigned long flags; | |
182 | unsigned int id; | |
183 | u32 irqstatus; | |
184 | ||
185 | irqstatus = dispc_read_irqstatus(); | |
186 | dispc_clear_irqstatus(irqstatus); | |
187 | dispc_read_irqstatus(); /* flush posted write */ | |
188 | ||
189 | VERB("irqs: %08x", irqstatus); | |
190 | ||
0d8f371f AT |
191 | for (id = 0; id < priv->num_crtcs; id++) { |
192 | struct drm_crtc *crtc = priv->crtcs[id]; | |
193 | ||
194 | if (irqstatus & pipe2vbl(crtc)) | |
f5f9454c | 195 | drm_handle_vblank(dev, id); |
0d8f371f | 196 | } |
f5f9454c RC |
197 | |
198 | spin_lock_irqsave(&list_lock, flags); | |
199 | list_for_each_entry_safe(handler, n, &priv->irq_list, node) { | |
200 | if (handler->irqmask & irqstatus) { | |
201 | spin_unlock_irqrestore(&list_lock, flags); | |
202 | handler->irq(handler, handler->irqmask & irqstatus); | |
203 | spin_lock_irqsave(&list_lock, flags); | |
204 | } | |
205 | } | |
206 | spin_unlock_irqrestore(&list_lock, flags); | |
207 | ||
208 | return IRQ_HANDLED; | |
209 | } | |
210 | ||
211 | void omap_irq_preinstall(struct drm_device *dev) | |
212 | { | |
213 | DBG("dev=%p", dev); | |
214 | dispc_runtime_get(); | |
215 | dispc_clear_irqstatus(0xffffffff); | |
216 | dispc_runtime_put(); | |
217 | } | |
218 | ||
219 | int omap_irq_postinstall(struct drm_device *dev) | |
220 | { | |
221 | struct omap_drm_private *priv = dev->dev_private; | |
222 | struct omap_drm_irq *error_handler = &priv->error_handler; | |
223 | ||
224 | DBG("dev=%p", dev); | |
225 | ||
226 | INIT_LIST_HEAD(&priv->irq_list); | |
227 | ||
228 | error_handler->irq = omap_irq_error_handler; | |
229 | error_handler->irqmask = DISPC_IRQ_OCP_ERR; | |
230 | ||
231 | /* for now ignore DISPC_IRQ_SYNC_LOST_DIGIT.. really I think | |
232 | * we just need to ignore it while enabling tv-out | |
233 | */ | |
234 | error_handler->irqmask &= ~DISPC_IRQ_SYNC_LOST_DIGIT; | |
235 | ||
236 | omap_irq_register(dev, error_handler); | |
237 | ||
238 | return 0; | |
239 | } | |
240 | ||
241 | void omap_irq_uninstall(struct drm_device *dev) | |
242 | { | |
243 | DBG("dev=%p", dev); | |
244 | // TODO prolly need to call drm_irq_uninstall() somewhere too | |
245 | } | |
246 | ||
247 | /* | |
248 | * We need a special version, instead of just using drm_irq_install(), | |
249 | * because we need to register the irq via omapdss. Once omapdss and | |
250 | * omapdrm are merged together we can assign the dispc hwmod data to | |
251 | * ourselves and drop these and just use drm_irq_{install,uninstall}() | |
252 | */ | |
253 | ||
254 | int omap_drm_irq_install(struct drm_device *dev) | |
255 | { | |
256 | int ret; | |
257 | ||
258 | mutex_lock(&dev->struct_mutex); | |
259 | ||
260 | if (dev->irq_enabled) { | |
261 | mutex_unlock(&dev->struct_mutex); | |
262 | return -EBUSY; | |
263 | } | |
4423843c | 264 | dev->irq_enabled = true; |
f5f9454c RC |
265 | mutex_unlock(&dev->struct_mutex); |
266 | ||
267 | /* Before installing handler */ | |
268 | if (dev->driver->irq_preinstall) | |
269 | dev->driver->irq_preinstall(dev); | |
270 | ||
271 | ret = dispc_request_irq(dev->driver->irq_handler, dev); | |
272 | ||
273 | if (ret < 0) { | |
274 | mutex_lock(&dev->struct_mutex); | |
4423843c | 275 | dev->irq_enabled = false; |
f5f9454c RC |
276 | mutex_unlock(&dev->struct_mutex); |
277 | return ret; | |
278 | } | |
279 | ||
280 | /* After installing handler */ | |
281 | if (dev->driver->irq_postinstall) | |
282 | ret = dev->driver->irq_postinstall(dev); | |
283 | ||
284 | if (ret < 0) { | |
285 | mutex_lock(&dev->struct_mutex); | |
4423843c | 286 | dev->irq_enabled = false; |
f5f9454c RC |
287 | mutex_unlock(&dev->struct_mutex); |
288 | dispc_free_irq(dev); | |
289 | } | |
290 | ||
291 | return ret; | |
292 | } | |
293 | ||
294 | int omap_drm_irq_uninstall(struct drm_device *dev) | |
295 | { | |
296 | unsigned long irqflags; | |
4423843c VS |
297 | bool irq_enabled; |
298 | int i; | |
f5f9454c RC |
299 | |
300 | mutex_lock(&dev->struct_mutex); | |
301 | irq_enabled = dev->irq_enabled; | |
4423843c | 302 | dev->irq_enabled = false; |
f5f9454c RC |
303 | mutex_unlock(&dev->struct_mutex); |
304 | ||
305 | /* | |
306 | * Wake up any waiters so they don't hang. | |
307 | */ | |
308 | if (dev->num_crtcs) { | |
309 | spin_lock_irqsave(&dev->vbl_lock, irqflags); | |
310 | for (i = 0; i < dev->num_crtcs; i++) { | |
5380e929 VS |
311 | DRM_WAKEUP(&dev->vblank[i].queue); |
312 | dev->vblank[i].enabled = false; | |
313 | dev->vblank[i].last = | |
f5f9454c RC |
314 | dev->driver->get_vblank_counter(dev, i); |
315 | } | |
316 | spin_unlock_irqrestore(&dev->vbl_lock, irqflags); | |
317 | } | |
318 | ||
319 | if (!irq_enabled) | |
320 | return -EINVAL; | |
321 | ||
322 | if (dev->driver->irq_uninstall) | |
323 | dev->driver->irq_uninstall(dev); | |
324 | ||
325 | dispc_free_irq(dev); | |
326 | ||
327 | return 0; | |
328 | } |