/* * Copyright 2009-2011 Oleg Mazurov, Circuits At Home, http://www.circuitsathome.com * MAX3421E USB host controller support * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the authors nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* MAX3421E USB host controller support */ #include "Max3421e.h" // #include "Max3421e_constants.h" static byte vbusState; /* Functions */ #define INT PE6 #define INT_PORT PORTE #define INT_DDR DDRE #define INT_PIN PINE #define RST PJ2 #define RST_PORT PORTJ #define RST_DDR DDRJ #define RST_PIN PINJ #define GPX PJ3 #define GPX_PORT PORTJ #define GPX_DDR DDRJ #define GPX_PIN PINJ void MAX3421E::setRST(uint8_t val) { if (val == LOW) RST_PORT &= ~_BV(RST); else RST_PORT |= _BV(RST); } uint8_t MAX3421E::readINT(void) { return INT_PIN & _BV(INT) ? HIGH : LOW; } uint8_t MAX3421E::readGPX(void) { // return GPX_PIN & _BV(GPX) ? HIGH : LOW; return LOW; } void MAX3421E::pinInit(void) { INT_DDR &= ~_BV(INT); RST_DDR |= _BV(RST); digitalWrite(MAX_SS,HIGH); setRST(HIGH); } /* Constructor */ MAX3421E::MAX3421E() { spi_init(); pinInit(); } byte MAX3421E::getVbusState( void ) { return( vbusState ); } /* initialization */ //void MAX3421E::init() //{ // /* setup pins */ // pinMode( MAX_INT, INPUT); // pinMode( MAX_GPX, INPUT ); // pinMode( MAX_SS, OUTPUT ); // //pinMode( BPNT_0, OUTPUT ); // //pinMode( BPNT_1, OUTPUT ); // //digitalWrite( BPNT_0, LOW ); // //digitalWrite( BPNT_1, LOW ); // Deselect_MAX3421E; // pinMode( MAX_RESET, OUTPUT ); // digitalWrite( MAX_RESET, HIGH ); //release MAX3421E from reset //} //byte MAX3421E::getVbusState( void ) //{ // return( vbusState ); //} //void MAX3421E::toggle( byte pin ) //{ // digitalWrite( pin, HIGH ); // digitalWrite( pin, LOW ); //} /* Single host register write */ void MAX3421E::regWr( byte reg, byte val) { digitalWrite(MAX_SS,LOW); SPDR = ( reg | 0x02 ); while(!( SPSR & ( 1 << SPIF ))); SPDR = val; while(!( SPSR & ( 1 << SPIF ))); digitalWrite(MAX_SS,HIGH); return; } /* multiple-byte write */ /* returns a pointer to a memory position after last written */ char * MAX3421E::bytesWr( byte reg, byte nbytes, char * data ) { digitalWrite(MAX_SS,LOW); SPDR = ( reg | 0x02 ); while( nbytes-- ) { while(!( SPSR & ( 1 << SPIF ))); //check if previous byte was sent SPDR = ( *data ); // send next data byte data++; // advance data pointer } while(!( SPSR & ( 1 << SPIF ))); digitalWrite(MAX_SS,HIGH); return( data ); } /* GPIO write. GPIO byte is split between 2 registers, so two writes are needed to write one byte */ /* GPOUT bits are in the low nibble. 0-3 in IOPINS1, 4-7 in IOPINS2 */ /* upper 4 bits of IOPINS1, IOPINS2 are read-only, so no masking is necessary */ void MAX3421E::gpioWr( byte val ) { regWr( rIOPINS1, val ); val = val >>4; regWr( rIOPINS2, val ); return; } /* Single host register read */ byte MAX3421E::regRd( byte reg ) { byte tmp; digitalWrite(MAX_SS,LOW); SPDR = reg; while(!( SPSR & ( 1 << SPIF ))); SPDR = 0; //send empty byte while(!( SPSR & ( 1 << SPIF ))); digitalWrite(MAX_SS,HIGH); return( SPDR ); } /* multiple-bytes register read */ /* returns a pointer to a memory position after last read */ char * MAX3421E::bytesRd ( byte reg, byte nbytes, char * data ) { digitalWrite(MAX_SS,LOW); SPDR = reg; while(!( SPSR & ( 1 << SPIF ))); //wait while( nbytes ) { SPDR = 0; //send empty byte nbytes--; while(!( SPSR & ( 1 << SPIF ))); *data = SPDR; data++; } digitalWrite(MAX_SS,HIGH); return( data ); } /* GPIO read. See gpioWr for explanation */ /* GPIN pins are in high nibbles of IOPINS1, IOPINS2 */ byte MAX3421E::gpioRd( void ) { byte tmpbyte = 0; tmpbyte = regRd( rIOPINS2 ); //pins 4-7 tmpbyte &= 0xf0; //clean lower nibble tmpbyte |= ( regRd( rIOPINS1 ) >>4 ) ; //shift low bits and OR with upper from previous operation. Upper nibble zeroes during shift, at least with this compiler return( tmpbyte ); } /* reset MAX3421E using chip reset bit. SPI configuration is not affected */ boolean MAX3421E::reset() { byte tmp = 0; regWr( rUSBCTL, bmCHIPRES ); //Chip reset. This stops the oscillator regWr( rUSBCTL, 0x00 ); //Remove the reset while(!(regRd( rUSBIRQ ) & bmOSCOKIRQ )) { //wait until the PLL is stable tmp++; //timeout after 256 attempts if( tmp == 0 ) { return( false ); } } return( true ); } /* turn USB power on/off */ /* does nothing, returns TRUE. Left for compatibility with old sketches */ /* will be deleted eventually */ ///* ON pin of VBUS switch (MAX4793 or similar) is connected to GPOUT7 */ ///* OVERLOAD pin of Vbus switch is connected to GPIN7 */ ///* OVERLOAD state low. NO OVERLOAD or VBUS OFF state high. */ boolean MAX3421E::vbusPwr ( boolean action ) { // byte tmp; // tmp = regRd( rIOPINS2 ); //copy of IOPINS2 // if( action ) { //turn on by setting GPOUT7 // tmp |= bmGPOUT7; // } // else { //turn off by clearing GPOUT7 // tmp &= ~bmGPOUT7; // } // regWr( rIOPINS2, tmp ); //send GPOUT7 // if( action ) { // delay( 60 ); // } // if (( regRd( rIOPINS2 ) & bmGPIN7 ) == 0 ) { // check if overload is present. MAX4793 /FLAG ( pin 4 ) goes low if overload // return( false ); // } return( true ); // power on/off successful } /* probe bus to determine device presense and speed and switch host to this speed */ void MAX3421E::busprobe( void ) { byte bus_sample; bus_sample = regRd( rHRSL ); //Get J,K status bus_sample &= ( bmJSTATUS|bmKSTATUS ); //zero the rest of the byte switch( bus_sample ) { //start full-speed or low-speed host case( bmJSTATUS ): if(( regRd( rMODE ) & bmLOWSPEED ) == 0 ) { regWr( rMODE, MODE_FS_HOST ); //start full-speed host vbusState = FSHOST; } else { regWr( rMODE, MODE_LS_HOST); //start low-speed host vbusState = LSHOST; } break; case( bmKSTATUS ): if(( regRd( rMODE ) & bmLOWSPEED ) == 0 ) { regWr( rMODE, MODE_LS_HOST ); //start low-speed host vbusState = LSHOST; } else { regWr( rMODE, MODE_FS_HOST ); //start full-speed host vbusState = FSHOST; } break; case( bmSE1 ): //illegal state vbusState = SE1; break; case( bmSE0 ): //disconnected state regWr( rMODE, bmDPPULLDN|bmDMPULLDN|bmHOST|bmSEPIRQ); vbusState = SE0; break; }//end switch( bus_sample ) } /* MAX3421E initialization after power-on */ void MAX3421E::powerOn() { /* Configure full-duplex SPI, interrupt pulse */ regWr( rPINCTL,( bmFDUPSPI + bmINTLEVEL + bmGPXB )); //Full-duplex SPI, level interrupt, GPX if( reset() == false ) { //stop/start the oscillator Serial.println("Error: OSCOKIRQ failed to assert"); } /* configure host operation */ regWr( rMODE, bmDPPULLDN|bmDMPULLDN|bmHOST|bmSEPIRQ ); // set pull-downs, Host, Separate GPIN IRQ on GPX regWr( rHIEN, bmCONDETIE|bmFRAMEIE ); //connection detection /* check if device is connected */ regWr( rHCTL,bmSAMPLEBUS ); // sample USB bus while(!(regRd( rHCTL ) & bmSAMPLEBUS )); //wait for sample operation to finish busprobe(); //check if anything is connected regWr( rHIRQ, bmCONDETIRQ ); //clear connection detect interrupt regWr( rCPUCTL, 0x01 ); //enable interrupt pin } /* MAX3421 state change task and interrupt handler */ byte MAX3421E::Task( void ) { byte rcode = 0; byte pinvalue; //Serial.print("Vbus state: "); //Serial.println( vbusState, HEX ); pinvalue = readINT(); if( pinvalue == LOW ) { rcode = IntHandler(); } pinvalue = readGPX(); if( pinvalue == LOW ) { GpxHandler(); } // usbSM(); //USB state machine return( rcode ); } byte MAX3421E::IntHandler() { byte HIRQ; byte HIRQ_sendback = 0x00; HIRQ = regRd( rHIRQ ); //determine interrupt source //if( HIRQ & bmFRAMEIRQ ) { //->1ms SOF interrupt handler // HIRQ_sendback |= bmFRAMEIRQ; //}//end FRAMEIRQ handling if( HIRQ & bmCONDETIRQ ) { busprobe(); HIRQ_sendback |= bmCONDETIRQ; } /* End HIRQ interrupts handling, clear serviced IRQs */ regWr( rHIRQ, HIRQ_sendback ); return( HIRQ_sendback ); } byte MAX3421E::GpxHandler() { byte GPINIRQ = regRd( rGPINIRQ ); //read GPIN IRQ register // if( GPINIRQ & bmGPINIRQ7 ) { //vbus overload // vbusPwr( OFF ); //attempt powercycle // delay( 1000 ); // vbusPwr( ON ); // regWr( rGPINIRQ, bmGPINIRQ7 ); // } return( GPINIRQ ); } //void MAX3421E::usbSM( void ) //USB state machine //{ // // //}