]>
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 | ||
f5f9454c RC |
24 | /* call with list_lock and dispc runtime held */ |
25 | static void omap_irq_update(struct drm_device *dev) | |
26 | { | |
27 | struct omap_drm_private *priv = dev->dev_private; | |
28 | struct omap_drm_irq *irq; | |
728ae8dd | 29 | uint32_t irqmask = priv->irq_mask; |
f5f9454c | 30 | |
8519c62c | 31 | assert_spin_locked(&list_lock); |
f5f9454c RC |
32 | |
33 | list_for_each_entry(irq, &priv->irq_list, node) | |
34 | irqmask |= irq->irqmask; | |
35 | ||
36 | DBG("irqmask=%08x", irqmask); | |
37 | ||
38 | dispc_write_irqenable(irqmask); | |
39 | dispc_read_irqenable(); /* flush posted write */ | |
40 | } | |
41 | ||
6da9f891 | 42 | void __omap_irq_register(struct drm_device *dev, struct omap_drm_irq *irq) |
f5f9454c RC |
43 | { |
44 | struct omap_drm_private *priv = dev->dev_private; | |
45 | unsigned long flags; | |
46 | ||
f5f9454c RC |
47 | spin_lock_irqsave(&list_lock, flags); |
48 | ||
49 | if (!WARN_ON(irq->registered)) { | |
50 | irq->registered = true; | |
51 | list_add(&irq->node, &priv->irq_list); | |
52 | omap_irq_update(dev); | |
53 | } | |
54 | ||
55 | spin_unlock_irqrestore(&list_lock, flags); | |
6da9f891 TV |
56 | } |
57 | ||
58 | void omap_irq_register(struct drm_device *dev, struct omap_drm_irq *irq) | |
59 | { | |
60 | dispc_runtime_get(); | |
61 | ||
62 | __omap_irq_register(dev, irq); | |
63 | ||
f5f9454c RC |
64 | dispc_runtime_put(); |
65 | } | |
66 | ||
6da9f891 | 67 | void __omap_irq_unregister(struct drm_device *dev, struct omap_drm_irq *irq) |
f5f9454c RC |
68 | { |
69 | unsigned long flags; | |
70 | ||
f5f9454c RC |
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); | |
6da9f891 TV |
80 | } |
81 | ||
82 | void omap_irq_unregister(struct drm_device *dev, struct omap_drm_irq *irq) | |
83 | { | |
84 | dispc_runtime_get(); | |
85 | ||
86 | __omap_irq_unregister(dev, irq); | |
87 | ||
f5f9454c RC |
88 | dispc_runtime_put(); |
89 | } | |
90 | ||
91 | struct omap_irq_wait { | |
92 | struct omap_drm_irq irq; | |
93 | int count; | |
94 | }; | |
95 | ||
96 | static DECLARE_WAIT_QUEUE_HEAD(wait_event); | |
97 | ||
98 | static void wait_irq(struct omap_drm_irq *irq, uint32_t irqstatus) | |
99 | { | |
100 | struct omap_irq_wait *wait = | |
101 | container_of(irq, struct omap_irq_wait, irq); | |
102 | wait->count--; | |
103 | wake_up_all(&wait_event); | |
104 | } | |
105 | ||
106 | struct omap_irq_wait * omap_irq_wait_init(struct drm_device *dev, | |
107 | uint32_t irqmask, int count) | |
108 | { | |
109 | struct omap_irq_wait *wait = kzalloc(sizeof(*wait), GFP_KERNEL); | |
110 | wait->irq.irq = wait_irq; | |
111 | wait->irq.irqmask = irqmask; | |
112 | wait->count = count; | |
113 | omap_irq_register(dev, &wait->irq); | |
114 | return wait; | |
115 | } | |
116 | ||
117 | int omap_irq_wait(struct drm_device *dev, struct omap_irq_wait *wait, | |
118 | unsigned long timeout) | |
119 | { | |
120 | int ret = wait_event_timeout(wait_event, (wait->count <= 0), timeout); | |
121 | omap_irq_unregister(dev, &wait->irq); | |
122 | kfree(wait); | |
123 | if (ret == 0) | |
124 | return -1; | |
125 | return 0; | |
126 | } | |
127 | ||
128 | /** | |
129 | * enable_vblank - enable vblank interrupt events | |
130 | * @dev: DRM device | |
88e72717 | 131 | * @pipe: which irq to enable |
f5f9454c RC |
132 | * |
133 | * Enable vblank interrupts for @crtc. If the device doesn't have | |
134 | * a hardware vblank counter, this routine should be a no-op, since | |
135 | * interrupts will have to stay on to keep the count accurate. | |
136 | * | |
137 | * RETURNS | |
138 | * Zero on success, appropriate errno if the given @crtc's vblank | |
139 | * interrupt cannot be enabled. | |
140 | */ | |
88e72717 | 141 | int omap_irq_enable_vblank(struct drm_device *dev, unsigned int pipe) |
f5f9454c RC |
142 | { |
143 | struct omap_drm_private *priv = dev->dev_private; | |
88e72717 | 144 | struct drm_crtc *crtc = priv->crtcs[pipe]; |
f5f9454c RC |
145 | unsigned long flags; |
146 | ||
88e72717 | 147 | DBG("dev=%p, crtc=%u", dev, pipe); |
f5f9454c | 148 | |
f5f9454c | 149 | spin_lock_irqsave(&list_lock, flags); |
728ae8dd | 150 | priv->irq_mask |= pipe2vbl(crtc); |
f5f9454c RC |
151 | omap_irq_update(dev); |
152 | spin_unlock_irqrestore(&list_lock, flags); | |
f5f9454c RC |
153 | |
154 | return 0; | |
155 | } | |
156 | ||
157 | /** | |
158 | * disable_vblank - disable vblank interrupt events | |
159 | * @dev: DRM device | |
88e72717 | 160 | * @pipe: which irq to enable |
f5f9454c RC |
161 | * |
162 | * Disable vblank interrupts for @crtc. If the device doesn't have | |
163 | * a hardware vblank counter, this routine should be a no-op, since | |
164 | * interrupts will have to stay on to keep the count accurate. | |
165 | */ | |
88e72717 | 166 | void omap_irq_disable_vblank(struct drm_device *dev, unsigned int pipe) |
f5f9454c RC |
167 | { |
168 | struct omap_drm_private *priv = dev->dev_private; | |
88e72717 | 169 | struct drm_crtc *crtc = priv->crtcs[pipe]; |
f5f9454c RC |
170 | unsigned long flags; |
171 | ||
88e72717 | 172 | DBG("dev=%p, crtc=%u", dev, pipe); |
f5f9454c | 173 | |
f5f9454c | 174 | spin_lock_irqsave(&list_lock, flags); |
728ae8dd | 175 | priv->irq_mask &= ~pipe2vbl(crtc); |
f5f9454c RC |
176 | omap_irq_update(dev); |
177 | spin_unlock_irqrestore(&list_lock, flags); | |
f5f9454c RC |
178 | } |
179 | ||
728ae8dd LP |
180 | static void omap_irq_fifo_underflow(struct omap_drm_private *priv, |
181 | u32 irqstatus) | |
182 | { | |
183 | static DEFINE_RATELIMIT_STATE(_rs, DEFAULT_RATELIMIT_INTERVAL, | |
184 | DEFAULT_RATELIMIT_BURST); | |
185 | static const struct { | |
186 | const char *name; | |
187 | u32 mask; | |
188 | } sources[] = { | |
189 | { "gfx", DISPC_IRQ_GFX_FIFO_UNDERFLOW }, | |
190 | { "vid1", DISPC_IRQ_VID1_FIFO_UNDERFLOW }, | |
191 | { "vid2", DISPC_IRQ_VID2_FIFO_UNDERFLOW }, | |
192 | { "vid3", DISPC_IRQ_VID3_FIFO_UNDERFLOW }, | |
193 | }; | |
194 | ||
195 | const u32 mask = DISPC_IRQ_GFX_FIFO_UNDERFLOW | |
196 | | DISPC_IRQ_VID1_FIFO_UNDERFLOW | |
197 | | DISPC_IRQ_VID2_FIFO_UNDERFLOW | |
198 | | DISPC_IRQ_VID3_FIFO_UNDERFLOW; | |
199 | unsigned int i; | |
200 | ||
201 | spin_lock(&list_lock); | |
202 | irqstatus &= priv->irq_mask & mask; | |
203 | spin_unlock(&list_lock); | |
204 | ||
205 | if (!irqstatus) | |
206 | return; | |
207 | ||
208 | if (!__ratelimit(&_rs)) | |
209 | return; | |
210 | ||
211 | DRM_ERROR("FIFO underflow on "); | |
212 | ||
213 | for (i = 0; i < ARRAY_SIZE(sources); ++i) { | |
214 | if (sources[i].mask & irqstatus) | |
215 | pr_cont("%s ", sources[i].name); | |
216 | } | |
217 | ||
218 | pr_cont("(0x%08x)\n", irqstatus); | |
219 | } | |
220 | ||
6b5538d4 LP |
221 | static void omap_irq_ocp_error_handler(u32 irqstatus) |
222 | { | |
223 | if (!(irqstatus & DISPC_IRQ_OCP_ERR)) | |
224 | return; | |
225 | ||
226 | DRM_ERROR("OCP error\n"); | |
227 | } | |
228 | ||
f13ab005 | 229 | static irqreturn_t omap_irq_handler(int irq, void *arg) |
f5f9454c RC |
230 | { |
231 | struct drm_device *dev = (struct drm_device *) arg; | |
232 | struct omap_drm_private *priv = dev->dev_private; | |
233 | struct omap_drm_irq *handler, *n; | |
234 | unsigned long flags; | |
235 | unsigned int id; | |
236 | u32 irqstatus; | |
237 | ||
238 | irqstatus = dispc_read_irqstatus(); | |
239 | dispc_clear_irqstatus(irqstatus); | |
240 | dispc_read_irqstatus(); /* flush posted write */ | |
241 | ||
242 | VERB("irqs: %08x", irqstatus); | |
243 | ||
0d8f371f AT |
244 | for (id = 0; id < priv->num_crtcs; id++) { |
245 | struct drm_crtc *crtc = priv->crtcs[id]; | |
e0519af7 | 246 | enum omap_channel channel = omap_crtc_channel(crtc); |
0d8f371f AT |
247 | |
248 | if (irqstatus & pipe2vbl(crtc)) | |
f5f9454c | 249 | drm_handle_vblank(dev, id); |
e0519af7 LP |
250 | |
251 | if (irqstatus & dispc_mgr_get_sync_lost_irq(channel)) | |
252 | omap_crtc_error_irq(crtc, irqstatus); | |
0d8f371f | 253 | } |
f5f9454c | 254 | |
6b5538d4 | 255 | omap_irq_ocp_error_handler(irqstatus); |
728ae8dd LP |
256 | omap_irq_fifo_underflow(priv, irqstatus); |
257 | ||
f5f9454c RC |
258 | spin_lock_irqsave(&list_lock, flags); |
259 | list_for_each_entry_safe(handler, n, &priv->irq_list, node) { | |
260 | if (handler->irqmask & irqstatus) { | |
261 | spin_unlock_irqrestore(&list_lock, flags); | |
262 | handler->irq(handler, handler->irqmask & irqstatus); | |
263 | spin_lock_irqsave(&list_lock, flags); | |
264 | } | |
265 | } | |
266 | spin_unlock_irqrestore(&list_lock, flags); | |
267 | ||
268 | return IRQ_HANDLED; | |
269 | } | |
270 | ||
728ae8dd LP |
271 | static const u32 omap_underflow_irqs[] = { |
272 | [OMAP_DSS_GFX] = DISPC_IRQ_GFX_FIFO_UNDERFLOW, | |
273 | [OMAP_DSS_VIDEO1] = DISPC_IRQ_VID1_FIFO_UNDERFLOW, | |
274 | [OMAP_DSS_VIDEO2] = DISPC_IRQ_VID2_FIFO_UNDERFLOW, | |
275 | [OMAP_DSS_VIDEO3] = DISPC_IRQ_VID3_FIFO_UNDERFLOW, | |
276 | }; | |
277 | ||
f13ab005 LP |
278 | /* |
279 | * We need a special version, instead of just using drm_irq_install(), | |
280 | * because we need to register the irq via omapdss. Once omapdss and | |
281 | * omapdrm are merged together we can assign the dispc hwmod data to | |
282 | * ourselves and drop these and just use drm_irq_{install,uninstall}() | |
283 | */ | |
f5f9454c | 284 | |
f13ab005 | 285 | int omap_drm_irq_install(struct drm_device *dev) |
f5f9454c RC |
286 | { |
287 | struct omap_drm_private *priv = dev->dev_private; | |
e0519af7 | 288 | unsigned int num_mgrs = dss_feat_get_num_mgrs(); |
728ae8dd LP |
289 | unsigned int max_planes; |
290 | unsigned int i; | |
f13ab005 | 291 | int ret; |
f5f9454c RC |
292 | |
293 | INIT_LIST_HEAD(&priv->irq_list); | |
294 | ||
6b5538d4 | 295 | priv->irq_mask = DISPC_IRQ_OCP_ERR; |
728ae8dd LP |
296 | |
297 | max_planes = min(ARRAY_SIZE(priv->planes), | |
298 | ARRAY_SIZE(omap_underflow_irqs)); | |
299 | for (i = 0; i < max_planes; ++i) { | |
300 | if (priv->planes[i]) | |
301 | priv->irq_mask |= omap_underflow_irqs[i]; | |
302 | } | |
303 | ||
e0519af7 LP |
304 | for (i = 0; i < num_mgrs; ++i) |
305 | priv->irq_mask |= dispc_mgr_get_sync_lost_irq(i); | |
306 | ||
f13ab005 LP |
307 | dispc_runtime_get(); |
308 | dispc_clear_irqstatus(0xffffffff); | |
309 | dispc_runtime_put(); | |
310 | ||
311 | ret = dispc_request_irq(omap_irq_handler, dev); | |
312 | if (ret < 0) | |
313 | return ret; | |
314 | ||
4423843c | 315 | dev->irq_enabled = true; |
f5f9454c | 316 | |
f13ab005 | 317 | return 0; |
f5f9454c RC |
318 | } |
319 | ||
f13ab005 | 320 | void omap_drm_irq_uninstall(struct drm_device *dev) |
f5f9454c RC |
321 | { |
322 | unsigned long irqflags; | |
4423843c | 323 | int i; |
f5f9454c | 324 | |
f13ab005 LP |
325 | if (!dev->irq_enabled) |
326 | return; | |
327 | ||
4423843c | 328 | dev->irq_enabled = false; |
f5f9454c | 329 | |
f13ab005 | 330 | /* Wake up any waiters so they don't hang. */ |
f5f9454c RC |
331 | if (dev->num_crtcs) { |
332 | spin_lock_irqsave(&dev->vbl_lock, irqflags); | |
333 | for (i = 0; i < dev->num_crtcs; i++) { | |
57ed0f7b | 334 | wake_up(&dev->vblank[i].queue); |
5380e929 VS |
335 | dev->vblank[i].enabled = false; |
336 | dev->vblank[i].last = | |
f5f9454c RC |
337 | dev->driver->get_vblank_counter(dev, i); |
338 | } | |
339 | spin_unlock_irqrestore(&dev->vbl_lock, irqflags); | |
340 | } | |
341 | ||
f5f9454c | 342 | dispc_free_irq(dev); |
f5f9454c | 343 | } |