diff --git a/firmware/export/jz4760b.h b/firmware/export/jz4760b.h index e61689105c..589f67800a 100644 --- a/firmware/export/jz4760b.h +++ b/firmware/export/jz4760b.h @@ -6992,8 +6992,12 @@ do { \ #define USB_OUTCSRH (USB_BASE + 0x17) /* EP1-15 OUT CSR MSB 8-bit */ #define USB_OUTCOUNT (USB_BASE + 0x18) /* EP1-15 OUT FIFO count 16-bit */ +#define USB_CONFIGDATA (USB_BASE + 0x1f) /* Fixed config */ + #define USB_FIFO_EP(n) (USB_BASE + (n)*4 + 0x20) +#define USB_HWVERS (USB_BASE + 0x6c) + #define USB_EPINFO (USB_BASE + 0x78) /* Endpoint information */ #define USB_RAMINFO (USB_BASE + 0x79) /* RAM information */ @@ -7034,6 +7038,7 @@ do { \ #define USB_INCSRH_DMAREQENAB 0x10 #define USB_INCSRH_FRCDATATOG 0x08 #define USB_INCSRH_DMAREQMODE 0x04 +#define USB_INCSR_INCOMPTX 0x80 #define USB_INCSR_CDT 0x40 #define USB_INCSR_SENTSTALL 0x20 #define USB_INCSR_SENDSTALL 0x10 @@ -7077,6 +7082,10 @@ do { \ #define USB_CNTL_BURST_8 (2 << 9) #define USB_CNTL_BURST_16 (3 << 9) +/* USB HW revision */ +#define USB_HWVERS_MAJOR(x) ((x >> 10) & 0x1f) +#define USB_HWVERS_MINOR(x) (x & 0x3ff) + /* DMA interrupt bits */ #define USB_INTR_DMA_BULKIN 1 #define USB_INTR_DMA_BULKOUT 2 @@ -7104,6 +7113,7 @@ do { \ #define REG_USB_OUTCSRH REG8(USB_OUTCSRH) #define REG_USB_OUTCOUNT REG16(USB_OUTCOUNT) +#define REG_USB_CONFIGDATA REG8(USB_CONFIGDATA) #define REG_USB_FIFO_EP(n) REG32(USB_FIFO_EP(n)) #define REG_USB_INTR REG8(USB_INTR) @@ -7111,6 +7121,8 @@ do { \ #define REG_USB_ADDR(n) REG32(USB_ADDR(n)) #define REG_USB_COUNT(n) REG32(USB_COUNT(n)) +#define REG_USB_HWVERS REG16(USB_HWVERS) + #define REG_USB_EPINFO REG8(USB_EPINFO) #define REG_USB_RAMINFO REG8(USB_RAMINFO) diff --git a/firmware/target/mips/ingenic_jz47xx/system-target.h b/firmware/target/mips/ingenic_jz47xx/system-target.h index 30c1668bf7..9dc1a5c8c8 100644 --- a/firmware/target/mips/ingenic_jz47xx/system-target.h +++ b/firmware/target/mips/ingenic_jz47xx/system-target.h @@ -83,6 +83,7 @@ static inline void restore_interrupt(int status) #define UNCACHED_ADDRESS(addr) ((unsigned int)(addr) | 0xA0000000) #define UNCACHED_ADDR(x) UNCACHED_ADDRESS((x)) #define PHYSADDR(x) ((x) & 0x1fffffff) +#define VIRTADDR(x) ((x) | 0xA0000000) void system_enable_irq(unsigned int irq); void udelay(unsigned int usec); diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c index 275fd3fd2b..8562d9253c 100644 --- a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c +++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c @@ -7,7 +7,8 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2016 by Roman Stolyarov + * Copyright (C) 2016 Roman Stolyarov + * Copyright (C) 2020 Solomon Peachy * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,7 +21,7 @@ ****************************************************************************/ #include "config.h" -/*#define LOGF_ENABLE*/ +//#define LOGF_ENABLE #include "logf.h" #include "system.h" #include "usb_ch9.h" @@ -29,6 +30,8 @@ #include "cpu.h" #include "thread.h" +#define USE_USB_DMA + #define PIN_USB_DET (32*4+19) #define IRQ_USB_DET GPIO_IRQ(PIN_USB_DET) #define GPIO_USB_DET GPIO147 @@ -43,6 +46,13 @@ #define TOTAL_EP() (sizeof(endpoints)/sizeof(struct usb_endpoint)) #define EP_IS_IN(ep) (EP_NUMBER((ep))%2) +#define TXCSR_WZC_BITS (USB_INCSR_SENTSTALL | USB_INCSR_UNDERRUN | USB_INCSR_FFNOTEMPT | USB_INCSR_INCOMPTX) + +/* NOTE: IN/OUT is from the HOST perspective. We're a peripheral, so: + IN = DEV->HOST, (ie we send) + OUT = HOST->DEV, (ie we recv) +*/ + enum ep_type { ep_control, @@ -53,6 +63,16 @@ enum ep_type struct usb_endpoint { + const enum ep_type type; + const long fifo_addr; + unsigned short fifo_size; + bool allocated; + int use_dma; /* -1 = no, 0 = mode_0, 1 = mode_1 */ + + struct semaphore complete; + + uint8_t config; + volatile void *buf; volatile size_t length; union @@ -60,24 +80,14 @@ struct usb_endpoint volatile size_t sent; volatile size_t received; }; - volatile bool busy; - - const enum ep_type type; - const bool use_dma; - - const long fifo_addr; - unsigned short fifo_size; - - volatile bool wait; - struct semaphore complete; - volatile int rc; - bool allocated; + volatile bool busy; + volatile bool wait; }; -#define EP_INIT(_type, _fifo_addr, _fifo_size, _buf, _use_dma) \ +#define EP_INIT(_type, _fifo_addr, _fifo_size, _buf) \ { .type = (_type), .fifo_addr = (_fifo_addr), .fifo_size = (_fifo_size), \ - .buf = (_buf), .use_dma = (_use_dma), \ + .buf = (_buf), .use_dma = -1, \ .length = 0, .busy = false, .wait = false, .allocated = false } static union @@ -91,12 +101,12 @@ static volatile bool ep0_data_requested = false; static struct usb_endpoint endpoints[] = { - EP_INIT(ep_control, USB_FIFO_EP(0), 64, NULL, false), - EP_INIT(ep_control, USB_FIFO_EP(0), 64, &ep0_rx.buf, false), - EP_INIT(ep_bulk, USB_FIFO_EP(1), 512, NULL, false), - EP_INIT(ep_bulk, USB_FIFO_EP(1), 512, NULL, false), - EP_INIT(ep_interrupt, USB_FIFO_EP(2), 512, NULL, false), - EP_INIT(ep_interrupt, USB_FIFO_EP(2), 512, NULL, false), + EP_INIT(ep_control, USB_FIFO_EP(0), 64, NULL), + EP_INIT(ep_control, USB_FIFO_EP(0), 64, &ep0_rx.buf), + EP_INIT(ep_bulk, USB_FIFO_EP(1), 512, NULL), + EP_INIT(ep_bulk, USB_FIFO_EP(1), 512, NULL), + EP_INIT(ep_interrupt, USB_FIFO_EP(2), 512, NULL), + EP_INIT(ep_interrupt, USB_FIFO_EP(2), 512, NULL), }; static inline void select_endpoint(int ep) @@ -329,7 +339,8 @@ static void EP0_handler(void) EP0_send(); } -static void EPIN_handler(unsigned int endpoint) +/* Does new work */ +static void EPIN_send(unsigned int endpoint) { struct usb_endpoint* ep = &endpoints[endpoint*2]; unsigned int length, csr; @@ -338,45 +349,153 @@ static void EPIN_handler(unsigned int endpoint) csr = REG_USB_INCSR; logf("%s(%d): 0x%x", __func__, endpoint, csr); - if(!ep->busy) - { + if (!ep->busy) { logf("Entered EPIN handler without work!"); return; } - if(csr & USB_INCSR_SENTSTALL) - { + if (csr & USB_INCSR_INPKTRDY) { + logf("PKTRDY %d", endpoint); + return; + } + + if (csr & USB_INCSR_SENTSTALL) { + logf("SENDSTALL %d", endpoint); REG_USB_INCSR = csr & ~USB_INCSR_SENTSTALL; return; } - if(ep->use_dma) - return; - - if(csr & USB_INCSR_FFNOTEMPT) - { + if (csr & USB_INCSR_FFNOTEMPT) { logf("FIFO is not empty! 0x%x", csr); return; } +#ifdef USE_USB_DMA + if(ep->use_dma >= 0) { + logf("DMA busy(%x %x %x)", REG_USB_ADDR(USB_INTR_DMA_BULKIN), REG_USB_COUNT(USB_INTR_DMA_BULKIN),REG_USB_CNTL(USB_INTR_DMA_BULKIN)); + + return; + } +#endif + logf("EP%d: %d -> %d", endpoint, ep->sent, ep->length); +#ifdef USE_USB_DMA + /* Can we use DMA? */ + if (ep->type == ep_bulk && ep->length && (!(((unsigned long)ep->buf + ep->sent) % 4)) && !button_hold()) { + if (ep->length >= ep->fifo_size) + ep->use_dma = 1; + else + ep->use_dma = 0; + } else { + ep->use_dma = -1; + } + + if (ep->use_dma >= 0) { + commit_discard_dcache_range((void*)ep->buf + ep->sent, ep->length - ep->sent); + /* Set up DMA */ + uint16_t dmacr = USB_CNTL_BURST_16 | USB_CNTL_EP(EP_NUMBER2(ep)) | USB_CNTL_ENA | USB_CNTL_INTR_EN | USB_CNTL_DIR_IN ; + if (ep->use_dma > 0) + dmacr |= USB_CNTL_MODE_1; + + REG_USB_ADDR(USB_INTR_DMA_BULKIN) = PHYSADDR((unsigned long)ep->buf + ep->sent); + REG_USB_COUNT(USB_INTR_DMA_BULKIN) = ep->length - ep->sent; + REG_USB_CNTL(USB_INTR_DMA_BULKIN) = dmacr; + + uint16_t csr = REG_USB_INCSR; + if (ep->use_dma == 0) { + csr &= ~((USB_INCSRH_AUTOSET | USB_INCSRH_DMAREQENAB) << 8); + REG_USB_INCSR = csr | TXCSR_WZC_BITS; + csr &= ~((USB_INCSRH_DMAREQMODE) << 8); + csr |= ((USB_INCSRH_DMAREQENAB | USB_INCSRH_MODE) << 8); + } else { + csr |= ((USB_INCSRH_DMAREQENAB | USB_INCSRH_MODE | USB_INCSRH_DMAREQMODE) << 8); + csr |= ((USB_INCSRH_AUTOSET) << 8); + } + csr &= ~USB_INCSR_UNDERRUN; + + logf("DMA setup(%d: %x %x %x %x - %d)", EP_NUMBER2(ep), (unsigned int)PHYSADDR((unsigned long)ep->buf), ep->length, dmacr, csr, ep->use_dma); + + REG_USB_INCSR = csr; + + return; + } +#endif + + /* Non-DMA code */ if(ep->sent == 0) length = MIN(ep->length, ep->fifo_size); else length = MIN(EP_BUF_LEFT(ep), ep->fifo_size); writeFIFO(ep, length); - REG_USB_INCSR = csr | USB_INCSR_INPKTRDY; ep->sent += length; + csr &= ~USB_INCSR_UNDERRUN; + csr |= USB_INCSR_INPKTRDY; + logf("Non-DMA TX %x", csr); + REG_USB_INCSR = csr; +} - if(ep->sent >= ep->length) - { +static void EPIN_complete(unsigned int endpoint) +{ + struct usb_endpoint* ep = &endpoints[endpoint*2]; + uint16_t csr; + + select_endpoint(endpoint); + csr = REG_USB_INCSR; + logf("%s(%d): 0x%x", __func__, endpoint, csr); + + if (csr & USB_INCSR_SENTSTALL) { + logf("SENDSTALL %d\n", endpoint); + REG_USB_INCSR = csr & ~USB_INCSR_SENTSTALL; // XXX TXCSR_P_WZC_BITS + return; + } + + if (csr & USB_INCSR_UNDERRUN) { + csr |= TXCSR_WZC_BITS; + csr &= ~(USB_INCSR_UNDERRUN | USB_INCSR_INPKTRDY); + REG_USB_INCSR = csr; + logf("underrun! %x", csr); + } + + if (!ep->busy) { + logf("Entered EPIN_complete without work!"); + return; + } + + if (ep->use_dma >= 0) { + logf("DMA status (%x %x %x)", REG_USB_ADDR(USB_INTR_DMA_BULKIN), REG_USB_COUNT(USB_INTR_DMA_BULKIN),REG_USB_CNTL(USB_INTR_DMA_BULKIN)); + return; + } + + /* If we get here, the operation is completed, and we need to clean up */ + + /* Make sure DMA engine is idle */ + if (csr & (USB_INCSRH_DMAREQENAB << 8)) { + csr |= TXCSR_WZC_BITS; + csr &= ~(USB_INCSR_UNDERRUN | USB_INCSR_INPKTRDY | + ((USB_INCSRH_DMAREQENAB | USB_INCSRH_AUTOSET) << 8)); + REG_USB_INCSR = csr; + csr = REG_USB_INCSR; + logf("DMA cleanup %x", csr); + } + + // XXX send a zero-length packet if necessary. + // if tx complete, and ep->length > 0 and ep->length % fifo == 0, + // REG_USB_INCSR = MODE | PKTRDY; + // Not needed for mass storage as it counts packets but + // if we ever enable other protocls... + + logf("EP%d: %d -> %d", endpoint, ep->sent, ep->length); + + if(ep->sent >= ep->length) { if (!ep->wait) usb_core_transfer_complete(endpoint, USB_DIR_IN, 0, ep->sent); ep->rc = 0; ep_transfer_completed(ep); - logf("sent complete"); + logf("send complete"); + } else { + EPIN_send(endpoint); } } @@ -403,9 +522,6 @@ static void EPOUT_handler(unsigned int endpoint) return; } - if(ep->use_dma) - return; - if(csr & USB_OUTCSR_OUTPKTRDY) /* There is a packet in the fifo */ { size = REG_USB_OUTCOUNT; @@ -430,59 +546,55 @@ static void EPOUT_handler(unsigned int endpoint) } } +#ifdef USE_USB_DMA static void EPDMA_handler(int number) { int endpoint = -1; - unsigned int size = 0; + int size = 0; struct usb_endpoint* ep = NULL; - if(number == USB_INTR_DMA_BULKIN) - { - endpoint = (REG_USB_CNTL(0) >> 4) & 0xF; - ep = &endpoints[endpoint*2]; - size = (unsigned int)ep->buf - REG_USB_ADDR(0); - } - else if(number == USB_INTR_DMA_BULKOUT) - { - endpoint = (REG_USB_CNTL(1) >> 4) & 0xF; - ep = &endpoints[endpoint*2+1]; - size = (unsigned int)ep->buf - REG_USB_ADDR(1); - } + endpoint = (REG_USB_CNTL(number) >> 4) & 0xF; + ep = &endpoints[endpoint*2]; + if (!(REG_USB_CNTL(number) & USB_CNTL_DIR_IN)) + ep++; /* RX endpoint is +1 in the array */ + size = VIRTADDR(REG_USB_ADDR(number)) - ((unsigned int)ep->buf + ep->sent); - logf("DMA_BULK%d %d", number, endpoint); + if (number == USB_INTR_DMA_BULKIN) { + if ((ep->use_dma == 0) || (size % ep->fifo_size)) { + /* DMA is completed, but the final (short) packet needs to + be manually initiated! */ + uint16_t incsr; + select_endpoint(endpoint); + incsr = REG_USB_INCSR; - if(number == USB_INTR_DMA_BULKOUT) - { - /* Disable DMA */ - REG_USB_CNTL(1) = 0; - - commit_discard_dcache(); // XXX range? - - select_endpoint(endpoint); - /* Read out last packet manually */ - unsigned int lpack_size = REG_USB_OUTCOUNT; - if(lpack_size > 0) - { - ep->buf += ep->length - lpack_size; - readFIFO(ep, lpack_size); - REG_USB_OUTCSR &= ~USB_OUTCSR_OUTPKTRDY; + if (ep->use_dma == 1) { + /* Switch to Mode 0 DMA */ + incsr &= ~((USB_INCSRH_AUTOSET | USB_INCSRH_DMAREQENAB) << 8); + REG_USB_INCSR = incsr; + incsr &= ~((USB_INCSRH_DMAREQMODE) << 8); + incsr |= ((USB_INCSRH_DMAREQENAB) << 8); + } + incsr |= USB_INCSR_INPKTRDY; + logf("DMA dangling %x", incsr); + REG_USB_INCSR = incsr; } - } - else if(number == USB_INTR_DMA_BULKIN && size % ep->fifo_size) - { - /* If the last packet is less than MAXP, set INPKTRDY manually */ - REG_USB_INCSR |= USB_INCSR_INPKTRDY; - } + logf("DMA TX%d %d @%d/%d", number, size, ep->sent, ep->length); + ep->sent += size; + ep->use_dma = -1; /* DMA is complete, mark channel as idle */ - if (ep) - { - int dir = EP_IS_IN(ep) ? USB_DIR_IN : USB_DIR_OUT; - if ((dir == USB_DIR_OUT) || !ep->wait) - usb_core_transfer_complete(endpoint, dir, 0, ep->length); - ep->rc = 0; - ep_transfer_completed(ep); + EPIN_complete(endpoint); + } else if (number == USB_INTR_DMA_BULKOUT) { + /* RX DMA completed */ + logf("DMA RX%d %d @%d/%d", number, size, ep->received, ep->length); + ep->received += size; + ep->use_dma = -1; + + EPOUT_handler(endpoint); + } else if (ep) { + ep->use_dma = -1; } } +#endif static void setup_endpoint(struct usb_endpoint *ep) { @@ -510,14 +622,13 @@ static void setup_endpoint(struct usb_endpoint *ep) if(ep->type != ep_control) ep->fifo_size = usb_drv_port_speed() ? 512 : 64; + ep->config = REG_USB_CONFIGDATA; + if(EP_IS_IN(ep)) { csr = (USB_INCSR_FF | USB_INCSR_CDT); csrh = USB_INCSRH_MODE; - if(ep->use_dma) - csrh |= (USB_INCSRH_DMAREQENAB | USB_INCSRH_AUTOSET | USB_INCSRH_DMAREQMODE); - if(ep->type == ep_interrupt) csrh |= USB_INCSRH_FRCDATATOG; @@ -525,6 +636,8 @@ static void setup_endpoint(struct usb_endpoint *ep) REG_USB_INCSR = csr; REG_USB_INCSRH = csrh; + logf("IN %d (%x %x %x)", endpoint, ep->fifo_size, csr, csrh); + if (ep->allocated) REG_USB_INTRINE |= USB_INTR_EP(EP_NUMBER2(ep)); } @@ -536,13 +649,12 @@ static void setup_endpoint(struct usb_endpoint *ep) if(ep->type == ep_interrupt) csrh |= USB_OUTCSRH_DNYT; - if(ep->use_dma) - csrh |= (USB_OUTCSRH_DMAREQENAB | USB_OUTCSRH_AUTOCLR | USB_OUTCSRH_DMAREQMODE); - REG_USB_OUTMAXP = ep->fifo_size; REG_USB_OUTCSR = csr; REG_USB_OUTCSRH = csrh; + logf("OUT %d (%x %x %x)", endpoint, ep->fifo_size, csr, csrh); + if (ep->allocated) REG_USB_INTROUTE |= USB_INTR_EP(EP_NUMBER2(ep)); } @@ -574,8 +686,8 @@ static void udc_reset(void) REG_USB_INTRUSBE = 0; /* Disable DMA */ - REG_USB_CNTL(0) = 0; - REG_USB_CNTL(1) = 0; + REG_USB_CNTL(USB_INTR_DMA_BULKIN) = 0; + REG_USB_CNTL(USB_INTR_DMA_BULKOUT) = 0; /* High speed, softconnect */ REG_USB_POWER = (USB_POWER_SOFTCONN | USB_POWER_HSENAB); @@ -584,6 +696,9 @@ static void udc_reset(void) select_endpoint(0); REG_USB_CSR0 = (USB_CSR0_SVDOUTPKTRDY | USB_CSR0_SVDSETUPEND | USB_CSR0_FLUSHFIFO); + endpoints[0].config = REG_USB_CONFIGDATA; + endpoints[1].config = REG_USB_CONFIGDATA; + if (endpoints[0].busy) { if (endpoints[0].wait) @@ -627,17 +742,19 @@ void OTG(void) unsigned char intrUSB = REG_USB_INTRUSB; unsigned short intrIn = REG_USB_INTRIN; unsigned short intrOut = REG_USB_INTROUT; +#ifdef USE_USB_DMA unsigned char intrDMA = REG_USB_INTR; +#endif - logf("%x %x %x %x", intrUSB, intrIn, intrOut, intrDMA); + logf("IRQ %x %x %x %x", intrUSB, intrIn, intrOut, intrDMA); /* EPIN & EPOUT are all handled in DMA */ if(intrIn & USB_INTR_EP(0)) EP0_handler(); if(intrIn & USB_INTR_EP(1)) - EPIN_handler(1); + EPIN_complete(1); if(intrIn & USB_INTR_EP(2)) - EPIN_handler(2); + EPIN_complete(2); if(intrOut & USB_INTR_EP(1)) EPOUT_handler(1); if(intrOut & USB_INTR_EP(2)) @@ -648,10 +765,12 @@ void OTG(void) logf("USB suspend"); if(intrUSB & USB_INTR_RESUME) logf("USB resume"); - if(intrDMA & USB_INTR_DMA_BULKIN) +#ifdef USE_USB_DMA + if(intrDMA & (1<type == ep_control) - { - if ((ptr == NULL && length == 0) || !ep0_data_requested) - { + if (ep->type == ep_control) { + if ((ptr == NULL && length == 0) || !ep0_data_requested) { restore_irq(flags); return; } @@ -831,36 +954,22 @@ static void usb_drv_send_internal(struct usb_endpoint* ep, void* ptr, int length ep->sent = 0; ep->length = length; ep->busy = true; - if(blocking) - { + if(blocking) { ep->rc = -1; ep->wait = true; + } else { + ep->rc = 0; } - else ep->rc = 0; - if(ep->type == ep_control) - { + if (ep->type == ep_control) { EP0_send(); - } - else - { - if(ep->use_dma) - { - commit_discard_dcache_range(ptr, length); - REG_USB_ADDR(0) = PHYSADDR((unsigned long)ptr); - REG_USB_COUNT(0) = length; - REG_USB_CNTL(0) = (USB_CNTL_INTR_EN | USB_CNTL_MODE_1 | - USB_CNTL_DIR_IN | USB_CNTL_ENA | - USB_CNTL_EP(EP_NUMBER2(ep)) | USB_CNTL_BURST_16); - } - else - EPIN_handler(EP_NUMBER2(ep)); + } else { + EPIN_send(EP_NUMBER2(ep)); } restore_irq(flags); - if(blocking) - { + if(blocking) { semaphore_wait(&ep->complete, HZ); ep->wait = false; } @@ -918,23 +1027,12 @@ int usb_drv_recv(int endpoint, void* ptr, int length) ep->received = 0; ep->length = length; ep->busy = true; - if(ep->use_dma) - { - discard_dcache_range(ptr, length); - REG_USB_ADDR(1) = PHYSADDR((unsigned long)ptr); - REG_USB_COUNT(1) = length; - REG_USB_CNTL(1) = (USB_CNTL_INTR_EN | USB_CNTL_MODE_1 | - USB_CNTL_ENA | USB_CNTL_EP(endpoint) | - USB_CNTL_BURST_16); - } - else - { - if (endpoint == EP_CONTROL) - { - ep0_data_supplied = false; - EP0_handler(); - } - else EPOUT_handler(endpoint); + + if (endpoint == EP_CONTROL) { + ep0_data_supplied = false; + EP0_handler(); + } else { + EPOUT_handler(endpoint); } restore_irq(flags); @@ -976,6 +1074,12 @@ void usb_drv_cancel_all_transfers(void) unsigned int i, flags = disable_irq_save(); +#ifdef USE_USB_DMA + /* Disable DMA */ + REG_USB_CNTL(USB_INTR_DMA_BULKIN) = 0; + REG_USB_CNTL(USB_INTR_DMA_BULKOUT) = 0; +#endif + for(i=0; i