/**************** * usb_host.cpp * ****************/ /**************************************************************************** * Written By Marcio Teixeira 2018 - Aleph Objects, Inc. * * * * 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 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * To view a copy of the GNU General Public License, go to the following * * location: . * ****************************************************************************/ /* What follows is a modified version of the MAX3421e originally defined in * lib/usbhost.c". This has been rewritten to use SPI routines from the * Marlin HAL */ #include "../../../inc/MarlinConfigPre.h" #if ENABLED(USB_FLASH_DRIVE_SUPPORT) && DISABLED(USE_UHS3_USB) #if !PINS_EXIST(USB_CS, USB_INTR) #error "USB_FLASH_DRIVE_SUPPORT requires USB_CS_PIN and USB_INTR_PIN to be defined." #endif #include "Usb.h" #include "usbhost.h" uint8_t MAX3421e::vbusState = 0; // constructor void MAX3421e::cs() { WRITE(USB_CS_PIN, LOW); } void MAX3421e::ncs() { WRITE(USB_CS_PIN, HIGH); } // write single byte into MAX3421 register void MAX3421e::regWr(uint8_t reg, uint8_t data) { cs(); spiSend(reg | 0x02); spiSend(data); ncs(); } // multiple-byte write // return a pointer to memory position after last written uint8_t* MAX3421e::bytesWr(uint8_t reg, uint8_t nbytes, uint8_t *data_p) { cs(); spiSend(reg | 0x02); while (nbytes--) spiSend(*data_p++); ncs(); return data_p; } // GPIO write // GPIO byte is split between 2 registers, so two writes are needed to write one byte // GPOUT bits are in the low nybble. 0-3 in IOPINS1, 4-7 in IOPINS2 void MAX3421e::gpioWr(uint8_t data) { regWr(rIOPINS1, data); regWr(rIOPINS2, data >> 4); } // single host register read uint8_t MAX3421e::regRd(uint8_t reg) { cs(); spiSend(reg); uint8_t rv = spiRec(); ncs(); return rv; } // multiple-byte register read // return a pointer to a memory position after last read uint8_t* MAX3421e::bytesRd(uint8_t reg, uint8_t nbytes, uint8_t *data_p) { cs(); spiSend(reg); while (nbytes--) *data_p++ = spiRec(); ncs(); return data_p; } // GPIO read. See gpioWr for explanation // GPIN pins are in high nybbles of IOPINS1, IOPINS2 uint8_t MAX3421e::gpioRd() { return (regRd(rIOPINS2) & 0xF0) | // pins 4-7, clean lower nybble (regRd(rIOPINS1) >> 4); // shift low bits and OR with upper from previous operation. } // reset MAX3421e. Returns false if PLL failed to stabilize 1 second after reset bool MAX3421e::reset() { regWr(rUSBCTL, bmCHIPRES); regWr(rUSBCTL, 0x00); for (uint8_t i = 100; i--;) { if (regRd(rUSBIRQ) & bmOSCOKIRQ) return true; delay(10); } return false; } // initialize MAX3421e. Set Host mode, pullups, and stuff. Returns 0 if success, -1 if not bool MAX3421e::start() { // Initialize pins and SPI bus SET_OUTPUT(USB_CS_PIN); SET_INPUT_PULLUP(USB_INTR_PIN); ncs(); spiBegin(); spiInit(SD_SPI_SPEED); // MAX3421e - full-duplex, level interrupt, vbus off. regWr(rPINCTL, (bmFDUPSPI | bmINTLEVEL | GPX_VBDET)); const uint8_t revision = regRd(rREVISION); if (revision == 0x00 || revision == 0xFF) { SERIAL_ECHOLNPGM("Revision register appears incorrect on MAX3421e initialization. Got ", revision); return false; } if (!reset()) { SERIAL_ECHOLNPGM("OSCOKIRQ hasn't asserted in time"); return false; } // Delay a minimum of 1 second to ensure any capacitors are drained. // 1 second is required to make sure we do not smoke a Microdrive! delay(1000); regWr(rMODE, bmDPPULLDN | bmDMPULLDN | bmHOST); // set pull-downs, Host regWr(rHIEN, bmCONDETIE | bmFRAMEIE); // connection detection // check if device is connected regWr(rHCTL, bmSAMPLEBUS); // sample USB bus while (!(regRd(rHCTL) & bmSAMPLEBUS)) delay(10); // 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 // GPX pin on. This is done here so that busprobe will fail if we have a switch connected. regWr(rPINCTL, bmFDUPSPI | bmINTLEVEL); return true; } // Probe bus to determine device presence and speed. Switch host to this speed. void MAX3421e::busprobe() { // Switch on just the J & K bits switch (regRd(rHRSL) & (bmJSTATUS | bmKSTATUS)) { 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; } } // MAX3421 state change task and interrupt handler uint8_t MAX3421e::Task() { return READ(USB_INTR_PIN) ? 0 : IntHandler(); } uint8_t MAX3421e::IntHandler() { uint8_t HIRQ = regRd(rHIRQ), // determine interrupt source HIRQ_sendback = 0x00; if (HIRQ & bmCONDETIRQ) { busprobe(); HIRQ_sendback |= bmCONDETIRQ; } // End HIRQ interrupts handling, clear serviced IRQs regWr(rHIRQ, HIRQ_sendback); return HIRQ_sendback; } #endif // USB_FLASH_DRIVE_SUPPORT