From 4a483bb1bf69a0f0a1bf99aaa9bb47c6f79ab990 Mon Sep 17 00:00:00 2001 From: Karl Kurbjun Date: Mon, 8 Jun 2009 00:19:16 +0000 Subject: M:Robe 500: Mostly complete USB driver, supports BULK mode currently and gets about 2 MB/s writes vs 1.1 MB/s on the OF. Mostly tested against Linux, preliminary testing in Windows appears to work. There is currently a bug in the attach process where it only works once per boot that needs to be fixed. There are a few other minor M:Robe 500 changes as well. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@21208 a1c6a512-1295-4272-9138-f99709370657 --- firmware/drivers/m66591.c | 847 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 847 insertions(+) create mode 100644 firmware/drivers/m66591.c (limited to 'firmware/drivers') diff --git a/firmware/drivers/m66591.c b/firmware/drivers/m66591.c new file mode 100644 index 0000000000..9a93985580 --- /dev/null +++ b/firmware/drivers/m66591.c @@ -0,0 +1,847 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: usb-mr500.c 18487 2008-09-10 20:14:22Z bertrik $ + * + * Copyright (C) 2009 by Karl Kurbjun + * Portions Copyright (C) 2007 by Catalin Patulea + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#define LOGF_ENABLE + +#include "system.h" +#include "config.h" +#include "string.h" +#include "usb_ch9.h" +#include "usb_core.h" +#include "kernel.h" +#include "panic.h" +#include "usb_drv.h" +#include "logf.h" + +#include "config.h" +#include "cpu.h" +#include "ata.h" +#include "usb.h" +#include "usb-target.h" +#include "m66591.h" + +/******************************************************************************* + * These are the driver specific defines. + ******************************************************************************/ +#define HISPEED + +/* Right now sending blocks till the full transfer has completed, this needs to + * be fixed so that it does not require a block. (USB_TRAN_LOCK ideally would + * not be set). + */ +#define USB_TRAN_BLOCK + +/******************************************************************************* + * The following functions are all helpers which should not be called directly + * from the USB stack. They should only be called by eachother, or the USB + * stack visible functions. + ******************************************************************************/ + +static volatile unsigned short * pipe_ctrl_addr(int pipe); +static void pipe_handshake(int pipe, int handshake); +static void pipe_c_select (int pipe, bool dir); +#if !defined(USB_TRAN_BLOCK) +static int pipe_buffer_size (int pipe); +#endif +static int pipe_maxpack_size (int pipe); +static void control_received(void); +static void transfer_complete(int endpoint); +static int mxx_transmit_receive(int endpoint); +static int mxx_queue(int endpoint, void * ptr, int length, bool send); + +struct M66591_epstat { + unsigned char dir; /* endpoint direction */ + char *buf; /* user buffer to store data */ + int length; /* how match data will fit */ + volatile int count; /* actual data count */ + bool waiting; /* is there data to transfer? */ + bool busy; /* has the pipe been requested for use? */ +} ; + +static struct M66591_epstat M66591_eps[USB_NUM_ENDPOINTS]; + +/* This function is used to return the control address for each pipe */ +static volatile unsigned short * pipe_ctrl_addr(int pipe) { + if(pipe==0) { + return &M66591_DCPCTRL; + } else { + return &M66591_PIPECTRL1 + (pipe-1); + } +} + +/* This function sets the pipe/endpoint handshake */ +static void pipe_handshake(int pipe, int handshake) { + handshake&=0x03; + + if(handshake == PIPE_SHAKE_STALL) { + if( *(pipe_ctrl_addr(pipe)) & 0x03 ) { + *(pipe_ctrl_addr(pipe)) = 0x03; + } else { + *(pipe_ctrl_addr(pipe)) = 0x02; + } + } else { + *(pipe_ctrl_addr(pipe)) = handshake; + } +} + +/* This function chooses the pipe desired and waits the required time before + * warites/reads are valid */ +static void pipe_c_select (int pipe, bool dir) { + M66591_CPORT_CTRL0 = pipe | (1<<10) | (dir<<5); + + // Wait for the Pipe to be valid; + udelay(2); +} + +#if !defined(USB_TRAN_BLOCK) +/* This returns the maximum buffer size of each pipe. On this device the size + * is fixed. + */ +static int pipe_buffer_size (int pipe) { + switch(pipe) { + case 0: + return 256; + case 1: + case 2: + return 1024; + case 3: + case 4: + return 512; + case 5: + case 6: + return 64; + default: + return 0; + } +} +#endif + +/* This function returns the maximum packet size for each endpoint/pipe. It is + * Currently only setup to support Highspeed mode. + */ +static int pipe_maxpack_size (int pipe) { + switch(pipe) { + case 0: + /* DCP max packet size is configurable */ + return M66591_DCP_MXPKSZ; + case 1: + case 2: + case 3: + case 4: + return 512; + case 5: + case 6: + return 64; + default: + return 0; + } +} + +/* This is a helper function that is only called from the interupt handler. It + * copies the control packet information from the PHY and notifies the stack. + */ +static void control_received(void) { + /* copy setup data from packet */ + static struct usb_ctrlrequest temp; + + memcpy(&temp, (unsigned char*)&M66591_USB_REQ0, 8); + + logf("mxx: bReqType=0x%02x bReq=0x%02x wVal=0x%04x" + " wIdx=0x%04x wLen=0x%04x", + temp.bRequestType, temp.bRequest, temp.wValue, + temp.wIndex, temp.wLength); + + /* acknowledge packet recieved (clear valid) */ + M66591_INTSTAT_MAIN &= ~(1<<3); + + usb_core_control_request(&temp); +} + +/* This is a helper function, it is used to notife the stack that a transfer is + * done. + */ +static void transfer_complete(int endpoint) { + M66591_INTCFG_EMP &= ~(1 << endpoint); + logf("mxx: ep %d transfer complete", endpoint); + int temp=M66591_eps[endpoint].dir ? USB_DIR_IN : USB_DIR_OUT; + usb_core_transfer_complete(endpoint, temp, 0, + M66591_eps[endpoint].length); +} + +/* This is the main transmit routine that is typically called from the interrupt + * handler (the queue function calls it in some situations) + */ +static int mxx_transmit_receive(int endpoint) { + logf("mxx: do start"); + + /* Only the lower 15 bits of the endpoint correlate to the pipe number. + * For example pipe 2 will corelate to endpoint 0x82, so the upper bits + * need to be masked out. + */ + endpoint &= 0x7F; + + int i; /* Used as a loop counter */ + int length; /* Used in transfers to determine the amount to send/receive */ + + bool send=M66591_eps[endpoint].dir; + + /* This is used as the internal buffer pointer */ + unsigned short *ptrs; + + /* Choose the pipe that data is being transfered on */ + pipe_c_select(endpoint, send); + + /* Check to see if the endpoint is ready and give it some time to become + * ready. If it runs out of time exit out as an error. + */ + i = 0; + while (!(M66591_CPORT_CTRL1&(1<<13))) { + if (i++ > 100000) { + logf("mxx: FIFO %d not ready", endpoint); + return -1; + } + } + + /* Write to FIFO */ + if(send) { + int maxpack=pipe_maxpack_size(endpoint); +#if defined(USB_TRAN_BLOCK) + length = M66591_eps[endpoint].length; +#else + int bufsize=pipe_buffer_size(endpoint); + length=MIN(M66591_eps[endpoint].length, bufsize); +#endif + + /* Calculate the position in the buffer, all transfers should be 2-byte + * aligned till the last packet or short packet. + */ + ptrs = (unsigned short *)(M66591_eps[endpoint].buf + + M66591_eps[endpoint].count); + + /* Start sending data in 16-bit words */ + for (i = 0; i < (length>>1); i++) { + /* This wait is dangerous in the event htat something happens to + * the PHY pipe where it never becomes ready again, should probably + * add a timeout, and ideally completely remove. + */ + while(!(M66591_CPORT_CTRL1&(1<<13))){}; + + M66591_CPORT = *ptrs++; + M66591_eps[endpoint].count+=2; + } + + /* If the length is odd, send the last byte after setting the byte width + * of the FIFO. + */ + if(length & 0x01) { + /* Unset MBW (8-bit transfer) */ + M66591_CPORT_CTRL0 &= ~(1<<10); + M66591_CPORT = *((unsigned char *)ptrs - 1); + M66591_eps[endpoint].count++; + } + + /* Set BVAL if length is not a multiple of the maximum packet size */ + if( (length == 0) || (length % maxpack != 0) ) { + logf("mxx: do set BVAL"); + M66591_CPORT_CTRL1 |= (1<<15); + } + + /* If the transfer is complete set up interrupts to notify when FIFO is + * EMPTY, disable READY and let the handler know that there is nothing + * left to transfer on this pipe. + */ + if(M66591_eps[endpoint].count == M66591_eps[endpoint].length) { + /* Enable Empty flag */ + M66591_INTCFG_EMP |= 1 << endpoint; + /* Disable ready flag */ + M66591_INTCFG_RDY &= ~(1 << endpoint); + /* Nothing left to transfer */ + M66591_eps[endpoint].waiting=false; + } else { + /* There is still data to transfer, make sure READY is enabled */ + M66591_INTCFG_RDY |= 1 << endpoint; + } + } else { + /* Read data from FIFO */ + + /* Read the number of bytes that the PHY received */ + int receive_length=M66591_CPORT_CTRL1 & 0x03FF; + + /* The number of bytes to actually read is either what's left of the + * amount requested, or the amount that the PHY received. Choose the + * smaller of the two. + */ + length = MIN(M66591_eps[endpoint].length - M66591_eps[endpoint].count, + receive_length); + + /* If the length is zero, just clear the buffer as specified in the + * datasheet. Otherwise read in the data (in 16-bit pieces */ + if(length==0) { + /* Set the BCLR bit */ + M66591_CPORT_CTRL1 |= 1<<14; + } else { + /* Set the position in the buffer */ + ptrs = (unsigned short *)(M66591_eps[endpoint].buf + + M66591_eps[endpoint].count); + + /* Read in the data (buffer size should be even). The PHY cannot + * switch from 16-bit mode to 8-bit mode on an OUT buffer. + */ + for (i = 0; i < ((length+1)>>1); i++) { + *ptrs++ = M66591_CPORT; + M66591_eps[endpoint].count+=2; + } + } + + /* If the length was odd subtract 1 from the count */ + M66591_eps[endpoint].count -= (length&0x01); + + /* If the requested size of data was received, or the data received was + * less than the maximum packet size end the transfer. + */ + if( (M66591_eps[endpoint].count == M66591_eps[endpoint].length) + || (length % pipe_maxpack_size(endpoint)) ) { + + /* If the host tries to send anything else the FIFO is not ready/ + * enabled yet (NAK). + */ + pipe_handshake(endpoint, PIPE_SHAKE_NAK); + /* Tell the interrupt handler that transfer is complete. */ + M66591_eps[endpoint].waiting=false; + /* Disable ready */ + M66591_INTCFG_RDY &= ~(1 << endpoint); + + /* Let the stack know that the transfer is complete */ + if(endpoint!=0) + transfer_complete(endpoint); + } + } + + logf("mxx: do done ep %d %s len: %d cnt: %d", endpoint, + send ? "out" : "in", length, M66591_eps[endpoint].count); + + return 0; +} + +/* This function is used to start transfers. It is a helper function for the + * usb_drv_send_nonblocking, usb_drv_send, and usb_drv_receive functions. + */ +static int mxx_queue(int endpoint, void * ptr, int length, bool send) { + /* Disable IRQs */ + int flags = disable_irq_save(); + + /* Only the lower 15 bits of the endpoint correlate to the pipe number. + * For example pipe 2 will corelate to endpoint 0x82, so the upper bits + * need to be masked out. + */ + endpoint &= 0x7F; + + /* Initialize the enpoint status registers used for the transfer */ + M66591_eps[endpoint].buf=ptr; + M66591_eps[endpoint].length=length; + M66591_eps[endpoint].count=0; + M66591_eps[endpoint].dir=send; + M66591_eps[endpoint].waiting=true; + + logf("mxx: queue ep %d %s, len: %d", endpoint, send ? "out" : "in", length); + + /* Pick the pipe that communications are happening on */ + pipe_c_select(endpoint, send); + + /* All transfers start with a BUF handshake */ + pipe_handshake(endpoint, PIPE_SHAKE_BUF); + + /* This USB PHY takes care of control completion packets by setting the + * CCPL bit in EP0 (endpoint 0, or DCP). If the control state is "write no + * data tranfer" then we just need to set the CCPL bit (hopefully) + * regardless of what the stack said to send. + */ + int control_state = (M66591_INTSTAT_MAIN & 0x07); + if(endpoint==0 && control_state==CTRL_WTND) { + logf("mxx: queue ep 0 ctls: 5, set ccpl"); + + /* Set CCPL */ + M66591_DCPCTRL |= 1<<2; + } else { + /* This is the standard case for transmitting data */ + if(send) { + /* If the pipe is not ready don't try and send right away; instead + * just set the READY interrupt so that the handler can initiate + * the transfer. + */ + if((M66591_CPORT_CTRL1&(1<<13))) { + mxx_transmit_receive(endpoint); + } else { + M66591_INTCFG_RDY |= 1 << endpoint; + } + + if(length==0) { + transfer_complete(endpoint); + } + } else { + /* When receiving data, just enable the ready interrupt, the PHY + * will trigger it and then the reads can start. + */ + M66591_INTCFG_RDY |= 1 << endpoint; + } + } + + /* Re-enable IRQs */ + restore_irq(flags); + return 0; +} + +/******************************************************************************* + * This is the interrupt handler for this driver. It should be called from the + * target interrupt handler routine (eg. GPIO3 on M:Robe 500). + ******************************************************************************/ +void USB_DEVICE(void) { + int pipe_restore=M66591_CPORT_CTRL0; + logf("mxx: INT BEGIN tick: %d\n", (int) current_tick); + + logf("mxx: sMAIN0: 0x%04x, sRDY: 0x%04x", + M66591_INTSTAT_MAIN, M66591_INTSTAT_RDY); + logf("mxx: sNRDY: 0x%04x, sEMP: 0x%04x", + M66591_INTSTAT_NRDY, M66591_INTSTAT_EMP); + + /* VBUS (connected) interrupt */ + while ( M66591_INTSTAT_MAIN & (1<<15) ) { + M66591_INTSTAT_MAIN &= ~(1<<15); + + /* If device is not clocked, interrupt flag must be set manually */ + if ( !(M66591_TRN_CTRL & (1<<10)) ) { + M66591_INTSTAT_MAIN |= (1<<15); + } + } + + /* Resume interrupt: This is not used. Extra logic needs to be added similar + * to the VBUS interrupt incase the PHY clock is not running. + */ + if(M66591_INTSTAT_MAIN & (1<<14)) { + M66591_INTSTAT_MAIN &= ~(1<<14); + logf("mxx: RESUME"); + } + + /* Device state transition interrupt: Not used, but useful for debugging */ + if(M66591_INTSTAT_MAIN & (1<<12)) { + M66591_INTSTAT_MAIN &= ~(1<<12); + logf("mxx: DEV state CHANGE=%d", + ((M66591_INTSTAT_MAIN & (0x07<<4)) >> 4) ); + } + + /* Control transfer stage interrupt */ + if(M66591_INTSTAT_MAIN & (1<<11)) { + M66591_INTSTAT_MAIN &= ~(1<<11); + int control_state = (M66591_INTSTAT_MAIN & 0x07); + + logf("mxx: CTRT with CTSQ=%d", control_state); + + switch ( control_state ) { + case CTRL_IDLE: + transfer_complete(0); + break; + case CTRL_RTDS: + case CTRL_WTDS: + case CTRL_WTND: + // If data is not valid stop + if(!(M66591_INTSTAT_MAIN & (1<<3)) ) { + logf("mxx: CTRT interrupt but VALID is false"); + break; + } + control_received(); + break; + case CTRL_RTSS: + case CTRL_WTSS: + pipe_handshake(0, PIPE_SHAKE_BUF); + M66591_DCPCTRL |= 1<<2; // Set CCPL + break; + default: + logf("mxx: CTRT with unknown CTSQ"); + break; + } + } + + /* FIFO EMPTY interrupt: when this happens the transfer should be complete. + * When the interrupt occurs notify the stack. + */ + if(M66591_INTSTAT_MAIN & (1<<10)) { + int i; + logf("mxx: INT EMPTY: 0x%04x", M66591_INTSTAT_EMP); + + for(i=0; i USB_NUM_ENDPOINTS || M66591_eps[ep].busy == false) + return ; + + flags = disable_irq_save(); + + logf("mxx: ep %d release", ep); + + M66591_eps[ep].busy = false; + M66591_eps[ep].dir = -1; + + restore_irq(flags); +} + +/* Periodically called to check if a cable was plugged into the device */ +inline int usb_detect(void) +{ + if(M66591_INTSTAT_MAIN&(1<<7)) + return USB_INSERTED; + else + return USB_EXTRACTED; +} + +void usb_enable(bool on) { + logf("mxx: %s: %s", __FUNCTION__, on ? "true" : "false"); + if (on) + usb_core_init(); + else + usb_core_exit(); +} + +/* This is where the driver stuff starts */ +void usb_drv_init(void) { + logf("mxx: Device Init"); + + /* State left behind by m:robe 500i original firmware */ + M66591_TRN_CTRL = 0x8001; /* External 48 MHz clock */ + M66591_TRN_LNSTAT = 0x0040; /* "Reserved. Set it to '1'." */ + + M66591_PIN_CFG0 = 0x0000; + M66591_PIN_CFG1 = 0x8000; /* Drive Current: 3.3V setting */ + M66591_PIN_CFG2 = 0x0000; + + M66591_INTCFG_MAIN = 0x0000; /* All Interrupts Disable for now */ + M66591_INTCFG_OUT = 0x0000; /* Sense is edge, polarity is low */ + M66591_INTCFG_RDY = 0x0000; + M66591_INTCFG_NRDY = 0x0000; + M66591_INTCFG_EMP = 0x0000; + + M66591_INTSTAT_MAIN = 0; + M66591_INTSTAT_RDY = 0; + M66591_INTSTAT_NRDY = 0; + M66591_INTSTAT_EMP = 0; +} + +/* fully enable driver */ +void usb_attach(void) { + int i; + + /* Reset Endpoint states */ + for(i=0; i