diff options
Diffstat (limited to 'drivers/media/video/cx88/cx88-mpeg.c')
-rw-r--r-- | drivers/media/video/cx88/cx88-mpeg.c | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/drivers/media/video/cx88/cx88-mpeg.c b/drivers/media/video/cx88/cx88-mpeg.c new file mode 100644 index 000000000000..07aae1899e17 --- /dev/null +++ b/drivers/media/video/cx88/cx88-mpeg.c | |||
@@ -0,0 +1,466 @@ | |||
1 | /* | ||
2 | * $Id: cx88-mpeg.c,v 1.25 2005/03/07 14:18:00 kraxel Exp $ | ||
3 | * | ||
4 | * Support for the mpeg transport stream transfers | ||
5 | * PCI function #2 of the cx2388x. | ||
6 | * | ||
7 | * (c) 2004 Jelle Foks <jelle@foks.8m.com> | ||
8 | * (c) 2004 Chris Pascoe <c.pascoe@itee.uq.edu.au> | ||
9 | * (c) 2004 Gerd Knorr <kraxel@bytesex.org> | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | * | ||
16 | * This program is distributed in the hope that it will be useful, | ||
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
19 | * GNU General Public License for more details. | ||
20 | * | ||
21 | * You should have received a copy of the GNU General Public License | ||
22 | * along with this program; if not, write to the Free Software | ||
23 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
24 | */ | ||
25 | |||
26 | #include <linux/module.h> | ||
27 | #include <linux/moduleparam.h> | ||
28 | #include <linux/init.h> | ||
29 | #include <linux/device.h> | ||
30 | #include <linux/interrupt.h> | ||
31 | #include <asm/delay.h> | ||
32 | |||
33 | #include "cx88.h" | ||
34 | |||
35 | /* ------------------------------------------------------------------ */ | ||
36 | |||
37 | MODULE_DESCRIPTION("mpeg driver for cx2388x based TV cards"); | ||
38 | MODULE_AUTHOR("Jelle Foks <jelle@foks.8m.com>"); | ||
39 | MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>"); | ||
40 | MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); | ||
41 | MODULE_LICENSE("GPL"); | ||
42 | |||
43 | static unsigned int debug = 0; | ||
44 | module_param(debug,int,0644); | ||
45 | MODULE_PARM_DESC(debug,"enable debug messages [mpeg]"); | ||
46 | |||
47 | #define dprintk(level,fmt, arg...) if (debug >= level) \ | ||
48 | printk(KERN_DEBUG "%s/2: " fmt, dev->core->name , ## arg) | ||
49 | |||
50 | /* ------------------------------------------------------------------ */ | ||
51 | |||
52 | static int cx8802_start_dma(struct cx8802_dev *dev, | ||
53 | struct cx88_dmaqueue *q, | ||
54 | struct cx88_buffer *buf) | ||
55 | { | ||
56 | struct cx88_core *core = dev->core; | ||
57 | |||
58 | dprintk(1, "cx8802_start_mpegport_dma %d\n", buf->vb.width); | ||
59 | |||
60 | /* setup fifo + format */ | ||
61 | cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28], | ||
62 | dev->ts_packet_size, buf->risc.dma); | ||
63 | |||
64 | /* write TS length to chip */ | ||
65 | cx_write(MO_TS_LNGTH, buf->vb.width); | ||
66 | |||
67 | #if 1 | ||
68 | /* FIXME: this needs a review. | ||
69 | * also: move to cx88-blackbird + cx88-dvb source files? */ | ||
70 | |||
71 | if (cx88_boards[core->board].dvb) { | ||
72 | /* negedge driven & software reset */ | ||
73 | cx_write(TS_GEN_CNTRL, 0x40); | ||
74 | udelay(100); | ||
75 | cx_write(MO_PINMUX_IO, 0x00); | ||
76 | cx_write(TS_HW_SOP_CNTRL,47<<16|188<<4|0x00); | ||
77 | cx_write(TS_SOP_STAT,0x00); | ||
78 | cx_write(TS_GEN_CNTRL, dev->ts_gen_cntrl); | ||
79 | udelay(100); | ||
80 | } | ||
81 | |||
82 | if (cx88_boards[core->board].blackbird) { | ||
83 | cx_write(MO_PINMUX_IO, 0x88); /* enable MPEG parallel IO */ | ||
84 | |||
85 | // cx_write(TS_F2_CMD_STAT_MM, 0x2900106); /* F2_CMD_STAT_MM defaults + master + memory space */ | ||
86 | cx_write(TS_GEN_CNTRL, 0x46); /* punctured clock TS & posedge driven & software reset */ | ||
87 | udelay(100); | ||
88 | |||
89 | cx_write(TS_HW_SOP_CNTRL, 0x408); /* mpeg start byte */ | ||
90 | //cx_write(TS_HW_SOP_CNTRL, 0x2F0BC0); /* mpeg start byte ts: 0x2F0BC0 ? */ | ||
91 | cx_write(TS_VALERR_CNTRL, 0x2000); | ||
92 | |||
93 | cx_write(TS_GEN_CNTRL, 0x06); /* punctured clock TS & posedge driven */ | ||
94 | udelay(100); | ||
95 | } | ||
96 | #endif | ||
97 | |||
98 | /* reset counter */ | ||
99 | cx_write(MO_TS_GPCNTRL, GP_COUNT_CONTROL_RESET); | ||
100 | q->count = 1; | ||
101 | |||
102 | /* enable irqs */ | ||
103 | cx_set(MO_PCI_INTMSK, core->pci_irqmask | 0x04); | ||
104 | cx_write(MO_TS_INTMSK, 0x1f0011); | ||
105 | |||
106 | /* start dma */ | ||
107 | cx_write(MO_DEV_CNTRL2, (1<<5)); /* FIXME: s/write/set/ ??? */ | ||
108 | cx_write(MO_TS_DMACNTRL, 0x11); | ||
109 | return 0; | ||
110 | } | ||
111 | |||
112 | static int cx8802_stop_dma(struct cx8802_dev *dev) | ||
113 | { | ||
114 | struct cx88_core *core = dev->core; | ||
115 | |||
116 | /* stop dma */ | ||
117 | cx_clear(MO_TS_DMACNTRL, 0x11); | ||
118 | |||
119 | /* disable irqs */ | ||
120 | cx_clear(MO_PCI_INTMSK, 0x000004); | ||
121 | cx_clear(MO_TS_INTMSK, 0x1f0011); | ||
122 | |||
123 | /* Reset the controller */ | ||
124 | cx_write(TS_GEN_CNTRL, 0xcd); | ||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static int cx8802_restart_queue(struct cx8802_dev *dev, | ||
129 | struct cx88_dmaqueue *q) | ||
130 | { | ||
131 | struct cx88_buffer *buf; | ||
132 | struct list_head *item; | ||
133 | |||
134 | if (list_empty(&q->active)) | ||
135 | return 0; | ||
136 | |||
137 | buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); | ||
138 | dprintk(2,"restart_queue [%p/%d]: restart dma\n", | ||
139 | buf, buf->vb.i); | ||
140 | cx8802_start_dma(dev, q, buf); | ||
141 | list_for_each(item,&q->active) { | ||
142 | buf = list_entry(item, struct cx88_buffer, vb.queue); | ||
143 | buf->count = q->count++; | ||
144 | } | ||
145 | mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); | ||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | /* ------------------------------------------------------------------ */ | ||
150 | |||
151 | int cx8802_buf_prepare(struct cx8802_dev *dev, struct cx88_buffer *buf) | ||
152 | { | ||
153 | int size = dev->ts_packet_size * dev->ts_packet_count; | ||
154 | int rc; | ||
155 | |||
156 | dprintk(1, "%s: %p\n", __FUNCTION__, buf); | ||
157 | if (0 != buf->vb.baddr && buf->vb.bsize < size) | ||
158 | return -EINVAL; | ||
159 | |||
160 | if (STATE_NEEDS_INIT == buf->vb.state) { | ||
161 | buf->vb.width = dev->ts_packet_size; | ||
162 | buf->vb.height = dev->ts_packet_count; | ||
163 | buf->vb.size = size; | ||
164 | buf->vb.field = V4L2_FIELD_TOP; | ||
165 | |||
166 | if (0 != (rc = videobuf_iolock(dev->pci,&buf->vb,NULL))) | ||
167 | goto fail; | ||
168 | cx88_risc_databuffer(dev->pci, &buf->risc, | ||
169 | buf->vb.dma.sglist, | ||
170 | buf->vb.width, buf->vb.height); | ||
171 | } | ||
172 | buf->vb.state = STATE_PREPARED; | ||
173 | return 0; | ||
174 | |||
175 | fail: | ||
176 | cx88_free_buffer(dev->pci,buf); | ||
177 | return rc; | ||
178 | } | ||
179 | |||
180 | void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf) | ||
181 | { | ||
182 | struct cx88_buffer *prev; | ||
183 | struct cx88_dmaqueue *q = &dev->mpegq; | ||
184 | |||
185 | /* add jump to stopper */ | ||
186 | buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); | ||
187 | buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); | ||
188 | |||
189 | if (list_empty(&q->active)) { | ||
190 | list_add_tail(&buf->vb.queue,&q->active); | ||
191 | cx8802_start_dma(dev, q, buf); | ||
192 | buf->vb.state = STATE_ACTIVE; | ||
193 | buf->count = q->count++; | ||
194 | mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); | ||
195 | dprintk(2,"[%p/%d] %s - first active\n", | ||
196 | buf, buf->vb.i, __FUNCTION__); | ||
197 | |||
198 | } else { | ||
199 | prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); | ||
200 | list_add_tail(&buf->vb.queue,&q->active); | ||
201 | buf->vb.state = STATE_ACTIVE; | ||
202 | buf->count = q->count++; | ||
203 | prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); | ||
204 | dprintk(2,"[%p/%d] %s - append to active\n", | ||
205 | buf, buf->vb.i, __FUNCTION__); | ||
206 | } | ||
207 | } | ||
208 | |||
209 | /* ----------------------------------------------------------- */ | ||
210 | |||
211 | static void do_cancel_buffers(struct cx8802_dev *dev, char *reason, int restart) | ||
212 | { | ||
213 | struct cx88_dmaqueue *q = &dev->mpegq; | ||
214 | struct cx88_buffer *buf; | ||
215 | unsigned long flags; | ||
216 | |||
217 | spin_lock_irqsave(&dev->slock,flags); | ||
218 | while (!list_empty(&q->active)) { | ||
219 | buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); | ||
220 | list_del(&buf->vb.queue); | ||
221 | buf->vb.state = STATE_ERROR; | ||
222 | wake_up(&buf->vb.done); | ||
223 | dprintk(1,"[%p/%d] %s - dma=0x%08lx\n", | ||
224 | buf, buf->vb.i, reason, (unsigned long)buf->risc.dma); | ||
225 | } | ||
226 | if (restart) | ||
227 | cx8802_restart_queue(dev,q); | ||
228 | spin_unlock_irqrestore(&dev->slock,flags); | ||
229 | } | ||
230 | |||
231 | void cx8802_cancel_buffers(struct cx8802_dev *dev) | ||
232 | { | ||
233 | struct cx88_dmaqueue *q = &dev->mpegq; | ||
234 | |||
235 | del_timer_sync(&q->timeout); | ||
236 | cx8802_stop_dma(dev); | ||
237 | do_cancel_buffers(dev,"cancel",0); | ||
238 | } | ||
239 | |||
240 | static void cx8802_timeout(unsigned long data) | ||
241 | { | ||
242 | struct cx8802_dev *dev = (struct cx8802_dev*)data; | ||
243 | |||
244 | dprintk(1, "%s\n",__FUNCTION__); | ||
245 | |||
246 | if (debug) | ||
247 | cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]); | ||
248 | cx8802_stop_dma(dev); | ||
249 | do_cancel_buffers(dev,"timeout",1); | ||
250 | } | ||
251 | |||
252 | static void cx8802_mpeg_irq(struct cx8802_dev *dev) | ||
253 | { | ||
254 | struct cx88_core *core = dev->core; | ||
255 | u32 status, mask, count; | ||
256 | |||
257 | status = cx_read(MO_TS_INTSTAT); | ||
258 | mask = cx_read(MO_TS_INTMSK); | ||
259 | if (0 == (status & mask)) | ||
260 | return; | ||
261 | |||
262 | cx_write(MO_TS_INTSTAT, status); | ||
263 | if (debug || (status & mask & ~0xff)) | ||
264 | cx88_print_irqbits(core->name, "irq mpeg ", | ||
265 | cx88_mpeg_irqs, status, mask); | ||
266 | |||
267 | /* risc op code error */ | ||
268 | if (status & (1 << 16)) { | ||
269 | printk(KERN_WARNING "%s: mpeg risc op code error\n",core->name); | ||
270 | cx_clear(MO_TS_DMACNTRL, 0x11); | ||
271 | cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]); | ||
272 | } | ||
273 | |||
274 | /* risc1 y */ | ||
275 | if (status & 0x01) { | ||
276 | spin_lock(&dev->slock); | ||
277 | count = cx_read(MO_TS_GPCNT); | ||
278 | cx88_wakeup(dev->core, &dev->mpegq, count); | ||
279 | spin_unlock(&dev->slock); | ||
280 | } | ||
281 | |||
282 | /* risc2 y */ | ||
283 | if (status & 0x10) { | ||
284 | spin_lock(&dev->slock); | ||
285 | cx8802_restart_queue(dev,&dev->mpegq); | ||
286 | spin_unlock(&dev->slock); | ||
287 | } | ||
288 | |||
289 | /* other general errors */ | ||
290 | if (status & 0x1f0100) { | ||
291 | spin_lock(&dev->slock); | ||
292 | cx8802_stop_dma(dev); | ||
293 | cx8802_restart_queue(dev,&dev->mpegq); | ||
294 | spin_unlock(&dev->slock); | ||
295 | } | ||
296 | } | ||
297 | |||
298 | static irqreturn_t cx8802_irq(int irq, void *dev_id, struct pt_regs *regs) | ||
299 | { | ||
300 | struct cx8802_dev *dev = dev_id; | ||
301 | struct cx88_core *core = dev->core; | ||
302 | u32 status; | ||
303 | int loop, handled = 0; | ||
304 | |||
305 | for (loop = 0; loop < 10; loop++) { | ||
306 | status = cx_read(MO_PCI_INTSTAT) & (core->pci_irqmask | 0x04); | ||
307 | if (0 == status) | ||
308 | goto out; | ||
309 | handled = 1; | ||
310 | cx_write(MO_PCI_INTSTAT, status); | ||
311 | |||
312 | if (status & core->pci_irqmask) | ||
313 | cx88_core_irq(core,status); | ||
314 | if (status & 0x04) | ||
315 | cx8802_mpeg_irq(dev); | ||
316 | }; | ||
317 | if (10 == loop) { | ||
318 | printk(KERN_WARNING "%s/0: irq loop -- clearing mask\n", | ||
319 | core->name); | ||
320 | cx_write(MO_PCI_INTMSK,0); | ||
321 | } | ||
322 | |||
323 | out: | ||
324 | return IRQ_RETVAL(handled); | ||
325 | } | ||
326 | |||
327 | /* ----------------------------------------------------------- */ | ||
328 | /* exported stuff */ | ||
329 | |||
330 | int cx8802_init_common(struct cx8802_dev *dev) | ||
331 | { | ||
332 | struct cx88_core *core = dev->core; | ||
333 | int err; | ||
334 | |||
335 | /* pci init */ | ||
336 | if (pci_enable_device(dev->pci)) | ||
337 | return -EIO; | ||
338 | pci_set_master(dev->pci); | ||
339 | if (!pci_dma_supported(dev->pci,0xffffffff)) { | ||
340 | printk("%s/2: Oops: no 32bit PCI DMA ???\n",dev->core->name); | ||
341 | return -EIO; | ||
342 | } | ||
343 | |||
344 | pci_read_config_byte(dev->pci, PCI_CLASS_REVISION, &dev->pci_rev); | ||
345 | pci_read_config_byte(dev->pci, PCI_LATENCY_TIMER, &dev->pci_lat); | ||
346 | printk(KERN_INFO "%s/2: found at %s, rev: %d, irq: %d, " | ||
347 | "latency: %d, mmio: 0x%lx\n", dev->core->name, | ||
348 | pci_name(dev->pci), dev->pci_rev, dev->pci->irq, | ||
349 | dev->pci_lat,pci_resource_start(dev->pci,0)); | ||
350 | |||
351 | /* initialize driver struct */ | ||
352 | init_MUTEX(&dev->lock); | ||
353 | spin_lock_init(&dev->slock); | ||
354 | |||
355 | /* init dma queue */ | ||
356 | INIT_LIST_HEAD(&dev->mpegq.active); | ||
357 | INIT_LIST_HEAD(&dev->mpegq.queued); | ||
358 | dev->mpegq.timeout.function = cx8802_timeout; | ||
359 | dev->mpegq.timeout.data = (unsigned long)dev; | ||
360 | init_timer(&dev->mpegq.timeout); | ||
361 | cx88_risc_stopper(dev->pci,&dev->mpegq.stopper, | ||
362 | MO_TS_DMACNTRL,0x11,0x00); | ||
363 | |||
364 | /* get irq */ | ||
365 | err = request_irq(dev->pci->irq, cx8802_irq, | ||
366 | SA_SHIRQ | SA_INTERRUPT, dev->core->name, dev); | ||
367 | if (err < 0) { | ||
368 | printk(KERN_ERR "%s: can't get IRQ %d\n", | ||
369 | dev->core->name, dev->pci->irq); | ||
370 | return err; | ||
371 | } | ||
372 | cx_set(MO_PCI_INTMSK, core->pci_irqmask); | ||
373 | |||
374 | /* everything worked */ | ||
375 | pci_set_drvdata(dev->pci,dev); | ||
376 | return 0; | ||
377 | } | ||
378 | |||
379 | void cx8802_fini_common(struct cx8802_dev *dev) | ||
380 | { | ||
381 | cx8802_stop_dma(dev); | ||
382 | pci_disable_device(dev->pci); | ||
383 | |||
384 | /* unregister stuff */ | ||
385 | free_irq(dev->pci->irq, dev); | ||
386 | pci_set_drvdata(dev->pci, NULL); | ||
387 | |||
388 | /* free memory */ | ||
389 | btcx_riscmem_free(dev->pci,&dev->mpegq.stopper); | ||
390 | } | ||
391 | |||
392 | /* ----------------------------------------------------------- */ | ||
393 | |||
394 | int cx8802_suspend_common(struct pci_dev *pci_dev, pm_message_t state) | ||
395 | { | ||
396 | struct cx8802_dev *dev = pci_get_drvdata(pci_dev); | ||
397 | struct cx88_core *core = dev->core; | ||
398 | |||
399 | /* stop mpeg dma */ | ||
400 | spin_lock(&dev->slock); | ||
401 | if (!list_empty(&dev->mpegq.active)) { | ||
402 | printk("%s: suspend mpeg\n", core->name); | ||
403 | cx8802_stop_dma(dev); | ||
404 | del_timer(&dev->mpegq.timeout); | ||
405 | } | ||
406 | spin_unlock(&dev->slock); | ||
407 | |||
408 | #if 1 | ||
409 | /* FIXME -- shutdown device */ | ||
410 | cx88_shutdown(dev->core); | ||
411 | #endif | ||
412 | |||
413 | pci_save_state(pci_dev); | ||
414 | if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) { | ||
415 | pci_disable_device(pci_dev); | ||
416 | dev->state.disabled = 1; | ||
417 | } | ||
418 | return 0; | ||
419 | } | ||
420 | |||
421 | int cx8802_resume_common(struct pci_dev *pci_dev) | ||
422 | { | ||
423 | struct cx8802_dev *dev = pci_get_drvdata(pci_dev); | ||
424 | struct cx88_core *core = dev->core; | ||
425 | |||
426 | if (dev->state.disabled) { | ||
427 | pci_enable_device(pci_dev); | ||
428 | dev->state.disabled = 0; | ||
429 | } | ||
430 | pci_set_power_state(pci_dev, PCI_D0); | ||
431 | pci_restore_state(pci_dev); | ||
432 | |||
433 | #if 1 | ||
434 | /* FIXME: re-initialize hardware */ | ||
435 | cx88_reset(dev->core); | ||
436 | #endif | ||
437 | |||
438 | /* restart video+vbi capture */ | ||
439 | spin_lock(&dev->slock); | ||
440 | if (!list_empty(&dev->mpegq.active)) { | ||
441 | printk("%s: resume mpeg\n", core->name); | ||
442 | cx8802_restart_queue(dev,&dev->mpegq); | ||
443 | } | ||
444 | spin_unlock(&dev->slock); | ||
445 | |||
446 | return 0; | ||
447 | } | ||
448 | |||
449 | /* ----------------------------------------------------------- */ | ||
450 | |||
451 | EXPORT_SYMBOL(cx8802_buf_prepare); | ||
452 | EXPORT_SYMBOL(cx8802_buf_queue); | ||
453 | EXPORT_SYMBOL(cx8802_cancel_buffers); | ||
454 | |||
455 | EXPORT_SYMBOL(cx8802_init_common); | ||
456 | EXPORT_SYMBOL(cx8802_fini_common); | ||
457 | |||
458 | EXPORT_SYMBOL(cx8802_suspend_common); | ||
459 | EXPORT_SYMBOL(cx8802_resume_common); | ||
460 | |||
461 | /* ----------------------------------------------------------- */ | ||
462 | /* | ||
463 | * Local variables: | ||
464 | * c-basic-offset: 8 | ||
465 | * End: | ||
466 | */ | ||