diff options
author | Rafaël Carré <rafael.carre@gmail.com> | 2011-12-31 21:22:48 +0000 |
---|---|---|
committer | Rafaël Carré <rafael.carre@gmail.com> | 2011-12-31 21:22:48 +0000 |
commit | 7b22ea0d377f69ec7a0d7be7215ef3c560ce13e7 (patch) | |
tree | 72666d2b75270d5809bbd31f436fab3a9c730e36 /firmware/target/arm/usb-s3c6400x.c | |
parent | fd5cb720c4b922979e854ee3d07979b81f60e776 (diff) |
usb-drv-as3525v2.c: merge in usb-s3c6400x.c
as3525v2 specific part is 400 LoC, ipod specific less than 300
(comments and whitespace included)
TODO: merge properly
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@31509 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/target/arm/usb-s3c6400x.c')
-rw-r--r-- | firmware/target/arm/usb-s3c6400x.c | 408 |
1 files changed, 407 insertions, 1 deletions
diff --git a/firmware/target/arm/usb-s3c6400x.c b/firmware/target/arm/usb-s3c6400x.c index 43c9f2fd77..2b221b1963 100644 --- a/firmware/target/arm/usb-s3c6400x.c +++ b/firmware/target/arm/usb-s3c6400x.c @@ -36,6 +36,9 @@ #include <inttypes.h> #include "power.h" +//#define LOGF_ENABLE +#include "logf.h" + /* store per endpoint, per direction, information */ struct ep_type { @@ -107,7 +110,410 @@ void usb_drv_set_test_mode(int mode) } #if CONFIG_CPU == AS3525v2 /* FIXME FIXME FIXME */ -# include "as3525/usb-drv-as3525v2.c" +static const uint8_t in_ep_list[] = {0, 1, 3, 5}; +static const uint8_t out_ep_list[] = {0, 2, 4}; + +/* state of EP0 (to correctly schedule setup packet enqueing) */ +enum ep0state +{ + /* Setup packet is enqueud, waiting for actual data */ + EP0_WAIT_SETUP = 0, + /* Waiting for ack (either IN or OUT) */ + EP0_WAIT_ACK = 1, + /* Ack complete, waiting for data (either IN or OUT) + * This state is necessary because if both ack and data complete in the + * same interrupt, we might process data completion before ack completion + * so we need this bizarre state */ + EP0_WAIT_DATA = 2, + /* Setup packet complete, waiting for ack and data */ + EP0_WAIT_DATA_ACK = 3, +}; + +/* endpoints[ep_num][DIR_IN/DIR_OUT] */ +static struct ep_type endpoints[USB_NUM_ENDPOINTS][2]; +/* setup packet for EP0 */ + +/* USB control requests may be up to 64 bytes in size. + Even though we never use anything more than the 8 header bytes, + we are required to accept request packets of up to 64 bytes size. + Provide buffer space for these additional payload bytes so that + e.g. write descriptor requests (which are rejected by us, but the + payload is transferred anyway) do not cause memory corruption. + Fixes FS#12310. -- Michael Sparmann (theseven) */ +static union { + struct usb_ctrlrequest header; /* 8 bytes */ + unsigned char payload[64]; +} _ep0_setup_pkt USB_DEVBSS_ATTR; + +static struct usb_ctrlrequest *ep0_setup_pkt = AS3525_UNCACHED_ADDR(&_ep0_setup_pkt.header); + +/* state of EP0 */ +static enum ep0state ep0_state; + +void usb_attach(void) +{ + /* Nothing to do */ +} + +static void prepare_setup_ep0(void) +{ + DEPDMA(0, true) = (void*)AS3525_PHYSICAL_ADDR(&_ep0_setup_pkt); + DEPTSIZ(0, true) = (1 << DEPTSIZ0_supcnt_bitp) + | (1 << DEPTSIZ0_pkcnt_bitp) + | 8; + DEPCTL(0, true) |= DEPCTL_epena | DEPCTL_cnak; + + ep0_state = EP0_WAIT_SETUP; +} + +static size_t num_eps(bool out) +{ + return out ? sizeof(out_ep_list) : sizeof(in_ep_list); +} + +static void reset_endpoints(void) +{ + for (int dir = 0; dir < 2; dir++) + { + bool out = dir == DIR_OUT; + for (unsigned i = 0; i < num_eps(dir == DIR_OUT); i++) + { + int ep = ((dir == DIR_IN) ? in_ep_list : out_ep_list)[i]; + struct ep_type *endpoint = &endpoints[ep][out]; + endpoint->active = false; + endpoint->busy = false; + endpoint->status = -1; + endpoint->done = false; + semaphore_release(&endpoint->complete); + + if (i != 0) + DEPCTL(ep, out) = DEPCTL_setd0pid; + } + DEPCTL(0, out) = /*(DEPCTL_MPS_64 << DEPCTL_mps_bitp) | */ DEPCTL_usbactep; + } + + /* Setup next chain for IN eps */ + for (unsigned i = 0; i < num_eps(false); i++) + { + int ep = in_ep_list[i]; + int next_ep = in_ep_list[(i + 1) % num_eps(false)]; + DEPCTL(ep, false) |= next_ep << DEPCTL_nextep_bitp; + } + + prepare_setup_ep0(); +} + +static void cancel_all_transfers(bool cancel_ep0) +{ + int flags = disable_irq_save(); + + for (int dir = 0; dir < 2; dir++) + for (unsigned i = !!cancel_ep0; i < num_eps(dir == DIR_OUT); i++) + { + int ep = ((dir == DIR_IN) ? in_ep_list : out_ep_list)[i]; + struct ep_type *endpoint = &endpoints[ep][dir == DIR_OUT]; + endpoint->status = -1; + endpoint->busy = false; + endpoint->done = false; + semaphore_release(&endpoint->complete); + DEPCTL(ep, dir) = (DEPCTL(ep, dir) & ~DEPCTL_usbactep); + } + + restore_irq(flags); +} + +void usb_drv_init(void) +{ + for (int i = 0; i < USB_NUM_ENDPOINTS; i++) + for (int dir = 0; dir < 2; dir++) + semaphore_init(&endpoints[i][dir].complete, 1, 0); + + bitset32(&CGU_PERI, CGU_USB_CLOCK_ENABLE); + CCU_USB = (CCU_USB & ~(3<<24)) | (1 << 24); /* ?? */ + /* PHY clock */ + CGU_USB = 1<<5 /* enable */ + | 0 << 2 + | 0; /* source = ? (24MHz crystal?) */ + + PCGCCTL = 0; + DCTL = DCTL_pwronprgdone | DCTL_sftdiscon; + + GRSTCTL = GRSTCTL_csftrst; + while (GRSTCTL & GRSTCTL_csftrst); /* Wait for OTG to ack reset */ + while (!(GRSTCTL & GRSTCTL_ahbidle)); /* Wait for OTG AHB master idle */ + + GRXFSIZ = 512; + GNPTXFSIZ = MAKE_FIFOSIZE_DATA(512); + + /* FIXME: the current code is for internal DMA only, the clip+ architecture + * defines the internal DMA model */ + GAHBCFG = (GAHBCFG_INT_DMA_BURST_INCR << GAHBCFG_hburstlen_bitp) + | GAHBCFG_dma_enable | GAHBCFG_glblintrmsk; + + /* Select UTMI+ 16 */ + GUSBCFG = GUSBCFG_force_device_mode | GUSBCFG_phy_if | 7 << GUSBCFG_toutcal_bitp; + + /* Do something that is probably CCU related but undocumented*/ + CCU_USB |= 0x1000; + CCU_USB &= ~0x300000; + + DCFG = DCFG_nzstsouthshk | DCFG_devspd_hs_phy_hs; /* Address 0, high speed */ + DCTL = DCTL_pwronprgdone; + + /* Check hardware capabilities */ + if(extract(GHWCFG2, arch) != GHWCFG2_ARCH_INTERNAL_DMA) + panicf("usb-drv: wrong architecture (%ld)", extract(GHWCFG2, arch)); + if(extract(GHWCFG2, hs_phy_type) != GHWCFG2_PHY_TYPE_UTMI) + panicf("usb-drv: wrong HS phy type (%ld)", extract(GHWCFG2, hs_phy_type)); + if(extract(GHWCFG2, fs_phy_type) != GHWCFG2_PHY_TYPE_UNSUPPORTED) + panicf("usb-drv: wrong FS phy type (%ld)", extract(GHWCFG2, fs_phy_type)); + if(extract(GHWCFG4, utmi_phy_data_width) != 0x2) + panicf("usb-drv: wrong utmi data width (%ld)", extract(GHWCFG4, utmi_phy_data_width)); + if(!(GHWCFG4 & GHWCFG4_ded_fifo_en)) /* it seems to be multiple tx fifo support */ + panicf("usb-drv: no multiple tx fifo"); + + if(USB_NUM_ENDPOINTS != extract(GHWCFG2, num_ep)) + panicf("usb-drv: wrong endpoint number"); + + for (int dir = 0; dir < 2; dir++) + for (unsigned i = 0; i < num_eps(dir == DIR_OUT); i++) + { + int ep = ((dir == DIR_IN) ? in_ep_list : out_ep_list)[i]; + int type = (GHWCFG1 >> GHWCFG1_epdir_bitp(ep)) & GHWCFG1_epdir_bits; + int flag = (dir == DIR_IN) ? GHWCFG1_EPDIR_IN : GHWCFG1_EPDIR_OUT; + if(type != GHWCFG1_EPDIR_BIDIR && type != flag) + panicf("usb-drv: EP%d not in correct direction", ep); + } + + DOEPMSK = DEPINT_xfercompl | DEPINT_ahberr | DOEPINT_setup; + DIEPMSK = DEPINT_xfercompl | DEPINT_ahberr | DIEPINT_timeout; + DAINTMSK = 0xffffffff; + + reset_endpoints(); + + GINTMSK = GINTMSK_usbreset + | GINTMSK_enumdone + | GINTMSK_inepintr + | GINTMSK_outepintr + | GINTMSK_disconnect; + + VIC_INT_ENABLE = INTERRUPT_USB; +} + +void usb_drv_exit(void) +{ + DCTL = DCTL_pwronprgdone | DCTL_sftdiscon; + + VIC_INT_EN_CLEAR = INTERRUPT_USB; + + sleep(HZ/20); + + CGU_USB = 0; + bitclr32(&CGU_PERI, CGU_USB_CLOCK_ENABLE); +} + +static void handle_ep_int(int ep, bool out) +{ + unsigned long sts = DEPINT(ep, out); + logf("%s(%d %s): sts = 0x%lx", __func__, ep, out?"OUT":"IN", sts); + + if(sts & DEPINT_ahberr) + panicf("usb-drv: ahb error on EP%d %s", ep, out ? "OUT" : "IN"); + + if(sts & DEPINT_xfercompl) + { + struct ep_type *endpoint = &endpoints[ep][out ? DIR_OUT : DIR_IN]; + if(endpoint->busy) + { + endpoint->busy = false; + endpoint->status = 0; + /* works even for EP0 */ + int size = (DEPTSIZ(ep, out) & DEPTSIZ_xfersize_bits); + int transfered = endpoint->size - size; + if(ep == 0) + { + bool is_ack = endpoint->size == 0; + switch(ep0_state) + { + case EP0_WAIT_SETUP: + panicf("usb-drv: EP0 completion while waiting for SETUP"); + case EP0_WAIT_DATA_ACK: + ep0_state = is_ack ? EP0_WAIT_DATA : EP0_WAIT_ACK; + break; + case EP0_WAIT_ACK: + case EP0_WAIT_DATA: + if((!is_ack && ep0_state == EP0_WAIT_ACK) || (is_ack && ep0_state == EP0_WAIT_DATA)) + panicf("usb-drv: bad EP0 state"); + + prepare_setup_ep0(); + break; + } + } + if (!out) + endpoint->size = size; + usb_core_transfer_complete(ep, out ? USB_DIR_OUT : USB_DIR_IN, 0, transfered); + endpoint->done = true; + semaphore_release(&endpoint->complete); + } + } + + if(!out && (sts & DIEPINT_timeout)) + panicf("usb-drv: timeout on EP%d IN", ep); + + if(out && (sts & DOEPINT_setup)) + { + if(ep != 0) + panicf("usb-drv: setup not on EP0, this is impossible"); + if((DEPTSIZ(ep, true) & DEPTSIZ_xfersize_bits) != 0) + { + logf("usb-drv: ignore spurious setup (xfersize=%ld)", DOEPTSIZ(ep) & DEPTSIZ_xfersize_bits); + prepare_setup_ep0(); + } + else + { + if(ep0_state == EP0_WAIT_SETUP) + { + bool data_phase = ep0_setup_pkt->wLength != 0; + ep0_state = data_phase ? EP0_WAIT_DATA_ACK : EP0_WAIT_ACK; + } + + logf(" rt=%x r=%x", ep0_setup_pkt->bRequestType, ep0_setup_pkt->bRequest); + + if(ep0_setup_pkt->bRequestType == USB_TYPE_STANDARD && + ep0_setup_pkt->bRequest == USB_REQ_SET_ADDRESS) + DCFG = (DCFG & ~bitm(DCFG, devadr)) | (ep0_setup_pkt->wValue << DCFG_devadr_bitp); + + usb_core_control_request(ep0_setup_pkt); + } + } + + DEPINT(ep, out) = sts; +} + +void INT_USB(void) +{ + /* some bits in GINTSTS can be set even though we didn't enable the interrupt source + * so AND it with the actual mask */ + unsigned long sts = GINTSTS & GINTMSK; + logf("usb-drv: INT 0x%lx", sts); + + if(sts & GINTMSK_usbreset) + { + DCFG &= ~bitm(DCFG, devadr); /* Address 0 */ + reset_endpoints(); + usb_core_bus_reset(); + } + + if(sts & GINTMSK_enumdone) /* enumeration done, we now know the speed */ + { + /* Set up the maximum packet sizes accordingly */ + uint32_t maxpacket = (usb_drv_port_speed() ? 512 : 64) << DEPCTL_mps_bitp; + for (int dir = 0; dir < 2; dir++) + { + bool out = dir == DIR_OUT; + for (unsigned i = 1; i < num_eps(out); i++) + { + int ep = (out ? out_ep_list : in_ep_list)[i]; + DEPCTL(ep, out) &= ~(DEPCTL_mps_bits << DEPCTL_mps_bitp); + DEPCTL(ep, out) |= maxpacket; + } + } + } + + if(sts & (GINTMSK_outepintr | GINTMSK_inepintr)) + { + unsigned long daint = DAINT; + + for (int i = 0; i < USB_NUM_ENDPOINTS; i++) + { + if (daint & DAINT_IN_EP(i)) + handle_ep_int(i, false); + if (daint & DAINT_OUT_EP(i)) + handle_ep_int(i, true); + } + + DAINT = daint; + } + + if(sts & GINTMSK_disconnect) + cancel_all_transfers(true); + + GINTSTS = sts; +} + +int usb_drv_request_endpoint(int type, int dir) +{ + bool out = dir == USB_DIR_OUT; + for (unsigned i = 1; i < num_eps(out); i++) + { + int ep = (out ? out_ep_list : in_ep_list)[i]; + bool *active = &endpoints[ep][out ? DIR_OUT : DIR_IN].active; + if(*active) + continue; + *active = true; + DEPCTL(ep, out) = (DEPCTL(ep, out) & ~(DEPCTL_eptype_bits << DEPCTL_eptype_bitp)) + | DEPCTL_setd0pid | (type << DEPCTL_eptype_bitp) | DEPCTL_usbactep; + return ep | dir; + } + + return -1; +} + +void usb_drv_release_endpoint(int ep) +{ + endpoints[EP_NUM(ep)][EP_DIR(ep)].active = false; +} + +void usb_drv_cancel_all_transfers() +{ + cancel_all_transfers(false); +} + +static void ep_transfer(int ep, void *ptr, int len, bool out) +{ + /* disable interrupts to avoid any race */ + int oldlevel = disable_irq_save(); + + struct ep_type *endpoint = &endpoints[ep][out ? DIR_OUT : DIR_IN]; + endpoint->busy = true; + endpoint->size = len; + endpoint->status = -1; + + if (out) + DEPCTL(ep, out) &= ~DEPCTL_stall; + DEPCTL(ep, out) |= DEPCTL_usbactep; + + int mps = usb_drv_port_speed() ? 512 : 64; + int nb_packets = (len + mps - 1) / mps; + if (nb_packets == 0) + nb_packets = 1; + + DEPDMA(ep, out) = len + ? (void*)AS3525_PHYSICAL_ADDR(ptr) + : (void*)0x10000000; + DEPTSIZ(ep, out) = (nb_packets << DEPTSIZ_pkcnt_bitp) | len; + if(out) + discard_dcache_range(ptr, len); + else + commit_dcache_range(ptr, len); + + logf("pkt=%d dma=%lx", nb_packets, DEPDMA(ep, out)); + + DEPCTL(ep, out) |= DEPCTL_epena | DEPCTL_cnak; + + restore_irq(oldlevel); +} + +int usb_drv_send(int ep, void *ptr, int len) +{ + ep = EP_NUM(ep); + struct ep_type *endpoint = &endpoints[ep][1]; + endpoint->done = false; + ep_transfer(ep, ptr, len, false); + while (endpoint->busy && !endpoint->done) + semaphore_wait(&endpoint->complete, TIMEOUT_BLOCK); + return endpoint->status; +} #else static struct ep_type endpoints[USB_NUM_ENDPOINTS]; |