[Linux1394-cvslog] rev 655 - trunk
Brought to you by:
aeb,
bencollins
From: SVN U. <dm...@li...> - 2002-10-29 02:21:56
|
Author: dmaas Date: 2002-10-28 21:21:52 -0500 (Mon, 28 Oct 2002) New Revision: 655 Modified: trunk/ohci1394.c Log: rawiso merge part 3 - OHCI rawiso implementation Modified: trunk/ohci1394.c ============================================================================== --- trunk/ohci1394.c (original) +++ trunk/ohci1394.c 2002-10-28 21:21:54.000000000 -0500 @@ -112,6 +112,8 @@ #include "ieee1394.h" #include "ieee1394_types.h" #include "hosts.h" +#include "dma.h" +#include "iso.h" #include "ieee1394_core.h" #include "highlevel.h" #include "ohci1394.h" @@ -977,6 +979,693 @@ return retval; } +/*********************************** + * rawiso ISO reception * + ***********************************/ + +struct ohci_iso_recv { + struct ti_ohci *ohci; + + /* memory and PCI mapping for the DMA descriptors */ + struct dma_prog_region prog; + + struct ohci1394_iso_tasklet task; + int task_active; + + /* index of next packet to arrive */ + int pkt_dma; + + u32 ContextControlSet; + u32 ContextControlClear; + u32 CommandPtr; + u32 ContextMatch; +}; + +static void ohci_iso_recv_task(unsigned long data); +static void ohci_iso_recv_stop(struct hpsb_iso *iso); +static void ohci_iso_recv_shutdown(struct hpsb_iso *iso); +static void ohci_iso_recv_program(struct hpsb_iso *iso); +static int ohci_iso_recv_start(struct hpsb_iso *iso, int cycle); + + +static int ohci_iso_recv_init(struct hpsb_iso *iso) +{ + struct ohci_iso_recv *recv; + unsigned int prog_size; + int ctx; + int ret = -ENOMEM; + + recv = kmalloc(sizeof(*recv), SLAB_KERNEL); + if(!recv) + return -ENOMEM; + + iso->hostdata = recv; + recv->ohci = iso->host->hostdata; + recv->task_active = 0; + recv->pkt_dma = iso->first_packet; + + dma_prog_region_init(&recv->prog); + + /* size of DMA program = one INPUT_LAST per packet in the buffer */ + prog_size = sizeof(struct dma_cmd) * iso->buf_packets; + + if(dma_prog_region_alloc(&recv->prog, prog_size, recv->ohci->dev)) + goto err; + + ohci1394_init_iso_tasklet(&recv->task, OHCI_ISO_RECEIVE, + ohci_iso_recv_task, (unsigned long) iso); + + if(ohci1394_register_iso_tasklet(recv->ohci, &recv->task) < 0) + goto err; + + recv->task_active = 1; + + /* recv context registers are spaced 32 bytes apart */ + ctx = recv->task.context; + recv->ContextControlSet = OHCI1394_IsoRcvContextControlSet + 32 * ctx; + recv->ContextControlClear = OHCI1394_IsoRcvContextControlClear + 32 * ctx; + recv->CommandPtr = OHCI1394_IsoRcvCommandPtr + 32 * ctx; + recv->ContextMatch = OHCI1394_IsoRcvContextMatch + 32 * ctx; + + /* enable interrupts */ + reg_write(recv->ohci, OHCI1394_IsoRecvIntMaskSet, 1 << ctx); + + /* write the DMA program */ + ohci_iso_recv_program(iso); + + return 0; + +err: + ohci_iso_recv_shutdown(iso); + return ret; +} + +static void ohci_iso_recv_stop(struct hpsb_iso *iso) +{ + struct ohci_iso_recv *recv = iso->hostdata; + + /* halt DMA */ + ohci1394_stop_context(recv->ohci, recv->ContextControlClear, NULL); +} + +static void ohci_iso_recv_shutdown(struct hpsb_iso *iso) +{ + struct ohci_iso_recv *recv = iso->hostdata; + + if(recv->task_active) { + /* halt DMA */ + ohci1394_stop_context(recv->ohci, recv->ContextControlClear, NULL); + + /* disable interrupts */ + reg_write(recv->ohci, OHCI1394_IsoRecvIntMaskClear, 1 << recv->task.context); + + ohci1394_unregister_iso_tasklet(recv->ohci, &recv->task); + recv->task_active = 0; + } + + dma_prog_region_free(&recv->prog); + kfree(recv); + iso->hostdata = NULL; +} + +static void ohci_iso_recv_program(struct hpsb_iso *iso) +{ + struct ohci_iso_recv *recv = iso->hostdata; + + /* address of 'branch' field in previous DMA descriptor */ + u32 *prev_branch = NULL; + + /* start at pkt_dma and go around the whole buffer */ + int pkt = recv->pkt_dma; + int i; + + for(i = 0; i < iso->buf_packets; i++) { + + /* pointer to the DMA descriptor */ + struct dma_cmd *il = ((struct dma_cmd*) recv->prog.kvirt) + pkt; + + /* offset of the DMA descriptor relative to the DMA prog buffer */ + unsigned long prog_offset = pkt * sizeof(struct dma_cmd); + + /* offset of this bus_cycle within the DMA buffer */ + unsigned long buf_offset = hpsb_iso_packet_data(iso, pkt) - iso->buf.kvirt; + + int want_interrupt; + unsigned int data_size = iso->max_packet_size; + + /* ask for an interrupt every now and then, and + always interrupt on the final descriptor */ + + if( ((i % iso->irq_interval) == 0) || + (i == (iso->buf_packets - 1)) ) { + want_interrupt = 1; + } else { + want_interrupt = 0; + } + + /* write the DMA descriptor */ + + il->control = 3 << 28; /* INPUT_LAST */ + il->control |= 8 << 24; /* s = 1, update xferStatus and resCount */ + if(want_interrupt) + il->control |= 3 << 20; + il->control |= 0xC << 16; /* enable branch to address */ + il->control |= data_size; + + il->address = dma_region_offset_to_bus(&iso->buf, buf_offset); + il->branchAddress = 0; /* filled in on next loop */ + il->status = data_size; + + /* link the previous descriptor to this one */ + if(prev_branch) { + *prev_branch = dma_prog_region_offset_to_bus(&recv->prog, prog_offset); + *prev_branch |= 1; /* set Z=1 */ + } + + prev_branch = &il->branchAddress; + + pkt = (pkt + 1) % iso->buf_packets; + } + + /* the final descriptor's branch address and Z should be left at 0 */ +} + +static int ohci_iso_recv_start(struct hpsb_iso *iso, int cycle) +{ + struct ohci_iso_recv *recv = iso->hostdata; + u32 command; + + reg_write(recv->ohci, recv->ContextControlClear, 0xFFFFFFFF); + wmb(); + + /* use packet-per-buffer mode, and do not keep ISO headers */ + reg_write(recv->ohci, recv->ContextControlSet, 0x00000000); + + /* match on all tags, listen on channel */ + reg_write(recv->ohci, recv->ContextMatch, 0xF0000000 | iso->channel); + + if(cycle != -1) { + /* XXX use cycleMatch */ + } + + /* address of first descriptor block */ + command = dma_prog_region_offset_to_bus(&recv->prog, recv->pkt_dma * sizeof(struct dma_cmd)); + command |= 1; /* Z=1 */ + + reg_write(recv->ohci, recv->CommandPtr, command); + wmb(); + + /* run */ + reg_write(recv->ohci, recv->ContextControlSet, 0x8000); + + /* issue a dummy read of the cycle timer register to force + all PCI writes to be posted immediately */ + mb(); + reg_read(recv->ohci, OHCI1394_IsochronousCycleTimer); + + /* check RUN */ + if(!(reg_read(recv->ohci, recv->ContextControlSet) & 0x8000)) { + PRINT(KERN_ERR, recv->ohci->id, "Error starting ISO DMA reception (ContextControl 0x%08x)\n", + reg_read(recv->ohci, recv->ContextControlSet)); + return -1; + } + + return 0; +} + +static void ohci_iso_recv_release_one(struct hpsb_iso *iso) +{ + struct ohci_iso_recv *recv = iso->hostdata; + + /* re-use the DMA descriptor for first_packet */ + /* by linking the previous descriptor to it */ + + int next_i = iso->first_packet; + int prev_i = (next_i == 0) ? (iso->buf_packets - 1) : (next_i - 1); + + struct dma_cmd *next = dma_region_i(&recv->prog, struct dma_cmd, next_i); + struct dma_cmd *prev = dma_region_i(&recv->prog, struct dma_cmd, prev_i); + + /* 'next' becomes the new end of the DMA chain */ + next->control |= 3 << 20; /* enable interrupt */ + next->branchAddress = 0; /* disable branch */ + + /* link prev to next */ + if(prev_i % iso->irq_interval) { + prev->control &= ~(3 << 20); /* no interrupt */ + } else { + prev->control |= 3 << 20; /* enable interrupt */ + } + prev->branchAddress = dma_prog_region_offset_to_bus(&recv->prog, + sizeof(struct dma_cmd) * next_i) | 1; + wmb(); + + /* wake up DMA in case it fell asleep */ + reg_write(recv->ohci, recv->ContextControlSet, (1 << 12)); + + /* advance packet cursors */ + iso->first_packet = (iso->first_packet+1) % iso->buf_packets; + atomic_inc(&iso->n_dma_packets); +} + +static void ohci_iso_recv_release(struct hpsb_iso *iso, int n_packets) +{ + int i; + for(i = 0; i < n_packets; i++) + ohci_iso_recv_release_one(iso); +} + +static void ohci_iso_recv_task(unsigned long data) +{ + struct hpsb_iso *iso = (struct hpsb_iso*) data; + struct ohci_iso_recv *recv = iso->hostdata; + + int count; + int wake = 0; + int good_packets = 0; + int bad_packets = 0; + + /* loop over the entire buffer */ + for(count = 0; count < iso->buf_packets; count++) { + u32 packet_len = 0; + struct hpsb_iso_packet_info *info; + + /* pointer to the DMA descriptor */ + struct dma_cmd *il = ((struct dma_cmd*) recv->prog.kvirt) + recv->pkt_dma; + + /* check the DMA descriptor for new writes to xferStatus */ + u16 xferstatus = il->status >> 16; + u16 rescount = il->status & 0xFFFF; + + unsigned char event = xferstatus & 0x1F; + + if(event == 0x11) { + /* packet received successfully! */ + good_packets++; + + /* rescount is the number of bytes *remaining* in the packet buffer, + after the packet was written */ + packet_len = iso->max_packet_size - rescount; + + } else if(event == 0x02) { + PRINT(KERN_ERR, recv->ohci->id, + "ISO DMA reception error - packet too long for buffer\n"); + bad_packets++; + } else if(event) { + PRINT(KERN_ERR, recv->ohci->id, + "ISO DMA reception error - OHCI error code 0x%02x\n", event); + bad_packets++; + } + + if(!event) { + /* this packet hasn't come in yet; we are done for now */ + goto out; + } + + /* sync our view of the buffer */ + dma_region_sync(&iso->buf); + + /* record the per-packet info */ + info = hpsb_iso_packet_info(iso, recv->pkt_dma); + info->len = packet_len; + + /* at least one packet came in, so wake up the reader */ + wake = 1; + + /* reset the DMA descriptor */ + il->status = iso->max_packet_size; + + /* advance DMA packet cursor */ + recv->pkt_dma = (recv->pkt_dma + 1) % iso->buf_packets; + + /* one more packet for the user, one less for us */ + if(atomic_dec_and_test(&iso->n_dma_packets)) { + /* if n_dma_packets reaches zero, we have an overflow */ + atomic_inc(&iso->overflows); + } + + } + +out: + if(wake && iso->waitq) { + wake_up_interruptible(iso->waitq); + } +} + + +/*********************************** + * rawiso ISO transmission * + ***********************************/ + +struct ohci_iso_xmit { + struct ti_ohci *ohci; + struct dma_prog_region prog; + struct ohci1394_iso_tasklet task; + int task_active; + int pkt_dma; + + u32 ContextControlSet; + u32 ContextControlClear; + u32 CommandPtr; +}; + +/* transmission DMA program: + one OUTPUT_MORE_IMMEDIATE for the IT header + one OUTPUT_LAST for the buffer data */ + +struct iso_xmit_cmd { + struct dma_cmd output_more_immediate; + u8 iso_hdr[8]; + u32 unused[2]; + struct dma_cmd output_last; +}; + +static int ohci_iso_xmit_init(struct hpsb_iso *iso); +static int ohci_iso_xmit_start(struct hpsb_iso *iso, int cycle); +static void ohci_iso_xmit_shutdown(struct hpsb_iso *iso); +static void ohci_iso_xmit_task(unsigned long data); + +static int ohci_iso_xmit_suggest_start_cycle(struct ti_ohci *ohci) +{ + u32 cycleTimer, sec, cyc, off; + + cycleTimer = reg_read(ohci, OHCI1394_IsochronousCycleTimer); + sec = cycleTimer >> 25; + cyc = (cycleTimer >> 12) & 0x1FFF; + off = cycleTimer & 0xFFF; + + /* go ahead 500 cycles - if the user doesn't xmit_produce() a + packet within this time, we will end up waiting until the + 4-bit second counter wraps around to start transmission... + is there a better way? */ + + cyc += 500; + + sec += cyc/8000; + cyc %= 8000; + + return ((sec & 0x3) << 13) | cyc; +} + +static int ohci_iso_xmit_init(struct hpsb_iso *iso) +{ + struct ohci_iso_xmit *xmit; + unsigned int prog_size; + int ctx; + int ret = -ENOMEM; + + xmit = kmalloc(sizeof(*xmit), SLAB_KERNEL); + if(!xmit) + return -ENOMEM; + + iso->hostdata = xmit; + xmit->ohci = iso->host->hostdata; + xmit->task_active = 0; + xmit->pkt_dma = iso->first_packet; + + dma_prog_region_init(&xmit->prog); + + prog_size = sizeof(struct iso_xmit_cmd) * iso->buf_packets; + + if(dma_prog_region_alloc(&xmit->prog, prog_size, xmit->ohci->dev)) + goto err; + + ohci1394_init_iso_tasklet(&xmit->task, OHCI_ISO_TRANSMIT, + ohci_iso_xmit_task, (unsigned long) iso); + + if(ohci1394_register_iso_tasklet(xmit->ohci, &xmit->task) < 0) + goto err; + + xmit->task_active = 1; + + /* xmit context registers are spaced 16 bytes apart */ + ctx = xmit->task.context; + xmit->ContextControlSet = OHCI1394_IsoXmitContextControlSet + 16 * ctx; + xmit->ContextControlClear = OHCI1394_IsoXmitContextControlClear + 16 * ctx; + xmit->CommandPtr = OHCI1394_IsoXmitCommandPtr + 16 * ctx; + + reg_write(xmit->ohci, xmit->ContextControlClear, 0xFFFFFFFF); + + /* enable interrupts */ + reg_write(xmit->ohci, OHCI1394_IsoXmitIntMaskSet, 1 << ctx); + + /* suggest a starting cycle */ + iso->first_packet_cycle = ohci_iso_xmit_suggest_start_cycle(xmit->ohci); + + return 0; + +err: + ohci_iso_xmit_shutdown(iso); + return ret; +} + +static void ohci_iso_xmit_stop(struct hpsb_iso *iso) +{ + struct ohci_iso_xmit *xmit = iso->hostdata; + ohci1394_stop_context(xmit->ohci, xmit->ContextControlClear, NULL); +} + +static void ohci_iso_xmit_shutdown(struct hpsb_iso *iso) +{ + struct ohci_iso_xmit *xmit = iso->hostdata; + + if(xmit->task_active) { + /* halt DMA */ + ohci1394_stop_context(xmit->ohci, xmit->ContextControlClear, NULL); + + /* disable interrupts */ + reg_write(xmit->ohci, OHCI1394_IsoXmitIntMaskClear, 1 << xmit->task.context); + + ohci1394_unregister_iso_tasklet(xmit->ohci, &xmit->task); + xmit->task_active = 0; + } + + dma_prog_region_free(&xmit->prog); + kfree(xmit); + iso->hostdata = NULL; +} + +static void ohci_iso_xmit_task(unsigned long data) +{ + struct hpsb_iso *iso = (struct hpsb_iso*) data; + struct ohci_iso_xmit *xmit = iso->hostdata; + int wake = 0; + unsigned int good_packets = 0; + unsigned int bad_packets = 0; + int count; + + /* check the whole buffer if necessary, starting at pkt_dma*/ + for(count = 0; count < iso->buf_packets; count++) { + struct iso_xmit_cmd *cmd = + dma_region_i(&xmit->prog, struct iso_xmit_cmd, xmit->pkt_dma); + + /* check for new writes to xferStatus */ + u16 xferstatus = cmd->output_last.status >> 16; + unsigned char event = xferstatus & 0x1F; + + if(event == 0x11) { + /* packet sent successfully */ + good_packets++; + } else if(event) { + PRINT(KERN_ERR, xmit->ohci->id, + "ISO DMA transmission error - OHCI error code 0x%02x\n", event); + bad_packets++; + } + + if(!event) { + /* packet hasn't been sent yet; we are done for now */ + goto out; + } + + /* at least one packet came in, so wake up the writer */ + wake = 1; + + /* reset the DMA descriptor for next time */ + cmd->output_last.status = 0; + + /* advance packet cursor */ + xmit->pkt_dma = (xmit->pkt_dma + 1) % iso->buf_packets; + + /* one less packet for us */ + if(atomic_dec_and_test(&iso->n_dma_packets)) { + /* underflow */ + atomic_inc(&iso->overflows); + } + } + +out: + if(wake && iso->waitq) { + wake_up_interruptible(iso->waitq); + } +} + +static void ohci_iso_xmit_queue_one(struct hpsb_iso *iso) +{ + struct ohci_iso_xmit *xmit = iso->hostdata; + struct hpsb_iso_packet_info *info; + int next_i, prev_i; + struct iso_xmit_cmd *next, *prev; + + /* sync up the card's view of the buffer */ + dma_region_sync(&iso->buf); + + /* append first_packet to the DMA chain */ + /* by linking the previous descriptor to it */ + /* (next will become the new end of the DMA chain) */ + + next_i = iso->first_packet; + prev_i = (next_i == 0) ? (iso->buf_packets - 1) : (next_i - 1); + + next = dma_region_i(&xmit->prog, struct iso_xmit_cmd, next_i); + prev = dma_region_i(&xmit->prog, struct iso_xmit_cmd, prev_i); + + /* retrieve the packet info stashed in the buffer */ + info = hpsb_iso_packet_info(iso, iso->first_packet); + + /* set up the OUTPUT_MORE_IMMEDIATE */ + memset(next, 0, sizeof(struct iso_xmit_cmd)); + next->output_more_immediate.control = 0x02000008; + + /* ISO packet header is embedded in the OUTPUT_MORE_IMMEDIATE */ + + /* tcode = 0xA, and sy */ + next->iso_hdr[0] = 0xA0 | (info->sy & 0xF); + + /* tag and channel number */ + next->iso_hdr[1] = (info->tag << 6) | (iso->channel & 0x3F); + + /* transmission speed */ + next->iso_hdr[2] = iso->speed & 0x7; + + /* payload size */ + next->iso_hdr[6] = info->len & 0xFF; + next->iso_hdr[7] = info->len >> 8; + + /* set up the OUTPUT_LAST */ + next->output_last.control = 1 << 28; + next->output_last.control |= 1 << 27; /* update timeStamp */ + next->output_last.control |= 3 << 20; /* want interrupt */ + next->output_last.control |= 3 << 18; /* enable branch */ + next->output_last.control |= info->len; + + /* payload bus address */ + next->output_last.address = dma_region_offset_to_bus(&iso->buf, + hpsb_iso_packet_data(iso, iso->first_packet) - iso->buf.kvirt); + + /* leave branchAddress at zero for now */ + + /* re-write the previous DMA descriptor to chain to this one */ + + /* set prev branch address to point to next (Z=3) */ + prev->output_last.branchAddress = + dma_prog_region_offset_to_bus(&xmit->prog, sizeof(struct iso_xmit_cmd) * next_i) | 3; + + /* disable interrupt, unless required by the IRQ interval */ + if(prev_i % iso->irq_interval) { + prev->output_last.control &= ~(3 << 20); /* no interrupt */ + } else { + prev->output_last.control |= 3 << 20; /* enable interrupt */ + } + + wmb(); + + /* wake DMA in case it is sleeping */ + reg_write(xmit->ohci, xmit->ContextControlSet, 1 << 12); + + /* issue a dummy read of the cycle timer to force all PCI + writes to be posted immediately */ + mb(); + reg_read(xmit->ohci, OHCI1394_IsochronousCycleTimer); + + /* increment cursors */ + iso->first_packet = (iso->first_packet+1) % iso->buf_packets; + iso->first_packet_cycle = (iso->first_packet_cycle+1) % iso->buf_packets; + + atomic_inc(&iso->n_dma_packets); +} + +static void ohci_iso_xmit_queue(struct hpsb_iso *iso, int n_packets) +{ + int i; + for(i = 0; i < n_packets; i++) + ohci_iso_xmit_queue_one(iso); +} + +static int ohci_iso_xmit_start(struct hpsb_iso *iso, int cycle) +{ + struct ohci_iso_xmit *xmit = iso->hostdata; + + /* clear out the control register */ + reg_write(xmit->ohci, xmit->ContextControlClear, 0xFFFFFFFF); + wmb(); + + /* address and length of first descriptor block (Z=3) */ + reg_write(xmit->ohci, xmit->CommandPtr, + dma_prog_region_offset_to_bus(&xmit->prog, xmit->pkt_dma * sizeof(struct iso_xmit_cmd)) | 3); + + /* cycle match */ + if(cycle != -1) { + /* XXX pick a cycle and set iso->first_packet_cycle */ + reg_write(xmit->ohci, xmit->ContextControlSet, 0x80000000 | (cycle << 16)); + } + + /* run */ + reg_write(xmit->ohci, xmit->ContextControlSet, 0x8000); + mb(); + + /* wait 100 usec to give the card time to go active */ + udelay(100); + + /* check the RUN bit */ + if(!(reg_read(xmit->ohci, xmit->ContextControlSet) & 0x8000)) { + PRINT(KERN_ERR, xmit->ohci->id, "Error starting ISO DMA transmission (ContextControl 0x%08x)\n", + reg_read(xmit->ohci, xmit->ContextControlSet)); + return -1; + } + + return 0; +} + +static int ohci_isoctl(struct hpsb_iso *iso, enum isoctl_cmd cmd, int arg) +{ + + switch(cmd) { + case XMIT_INIT: + return ohci_iso_xmit_init(iso); + case XMIT_START: + return ohci_iso_xmit_start(iso, arg); + case XMIT_STOP: + ohci_iso_xmit_stop(iso); + return 0; + case XMIT_QUEUE: + ohci_iso_xmit_queue(iso, arg); + return 0; + case XMIT_SHUTDOWN: + ohci_iso_xmit_shutdown(iso); + return 0; + + case RECV_INIT: + return ohci_iso_recv_init(iso); + case RECV_START: + return ohci_iso_recv_start(iso, arg); + case RECV_STOP: + ohci_iso_recv_stop(iso); + return 0; + case RECV_RELEASE: + ohci_iso_recv_release(iso, arg); + return 0; + case RECV_SHUTDOWN: + ohci_iso_recv_shutdown(iso); + return 0; + + default: + PRINT_G(KERN_ERR, "ohci_isoctl cmd %d not implemented yet", + cmd); + break; + } + return -EINVAL; +} + /*************************************** * IEEE-1394 functionality section END * ***************************************/ @@ -1944,7 +2633,7 @@ .get_rom = ohci_get_rom, .transmit_packet = ohci_transmit, .devctl = ohci_devctl, - .isoctl = NULL, + .isoctl = ohci_isoctl, .hw_csr_reg = ohci_hw_csr_reg, }; |