#ifndef _CH32V307GIGABIT_H #define _CH32V307GIGABIT_H /* This file is written against the RTL8211E */ // #define CH32V307GIGABIT_MCO25 1 // #define CH32V307GIGABIT_PHYADDRESS 0 #define CH32V307GIGABIT_RXBUFNB 8 #define CH32V307GIGABIT_TXBUFNB 8 #define CH32V307GIGABIT_BUFFSIZE 1524 // 1518 + 4, Rounded up. #define CH32V307GIGABIT_CFG_CLOCK_DELAY 4 // 0..7 #define CH32V307GIGABIT_CFG_CLOCK_PHASE 0 #ifndef CH32V307GIGABIT_MCO25 #define CH32V307GIGABIT_MCO25 1 #endif #ifndef CH32V307GIGABIT_PHYADDRESS #define CH32V307GIGABIT_PHYADDRESS 0 #endif #ifndef CH32V307GIGABIT_PHY_TIMEOUT #define CH32V307GIGABIT_PHY_TIMEOUT 0x10000 #endif // Additional definitions, not part of ch32v003fun.h #ifndef CH32V307GIGABIT_PHY_RSTB #define CH32V307GIGABIT_PHY_RSTB PA10 #endif // ETH DMA structure definition (From ch32v30x_eth.c typedef struct { uint32_t volatile Status; /* Status */ uint32_t ControlBufferSize; /* Control and Buffer1, Buffer2 lengths */ uint32_t Buffer1Addr; /* Buffer1 address pointer */ uint32_t Buffer2NextDescAddr; /* Buffer2 or next descriptor address pointer */ } ETH_DMADESCTypeDef; // You must provide: void ch32v307ethHandleReconfig( int link, int speed, int duplex ); // Return non-zero to suppress OWN return (for if you are still holding onto the buffer) int ch32v307ethInitHandlePacket( uint8_t * data, int frame_length, ETH_DMADESCTypeDef * dmadesc ); void ch32v307ethInitHandleTXC( void ); // This library provides: static void ch32v307ethGetMacInUC( uint8_t * mac ); static int ch32v307ethInit( void ); static int ch32v307ethTransmitStatic(uint8_t * buffer, uint32_t length, int enable_txc); // Does not copy. static int ch32v307ethTickPhy( void ); // Data pursuent to ethernet. uint8_t ch32v307eth_mac[6] = { 0 }; uint16_t ch32v307eth_phyid = 0; // 0xc916 = RTL8211FS / 0xc915 = RTL8211E-VB ETH_DMADESCTypeDef ch32v307eth_DMARxDscrTab[CH32V307GIGABIT_RXBUFNB] __attribute__((aligned(4))); // MAC receive descriptor, 4-byte aligned ETH_DMADESCTypeDef ch32v307eth_DMATxDscrTab[CH32V307GIGABIT_TXBUFNB] __attribute__((aligned(4))); // MAC send descriptor, 4-byte aligned uint8_t ch32v307eth_MACRxBuf[CH32V307GIGABIT_RXBUFNB*CH32V307GIGABIT_BUFFSIZE] __attribute__((aligned(4))); // MAC receive buffer, 4-byte aligned ETH_DMADESCTypeDef * pDMARxGet; ETH_DMADESCTypeDef * pDMATxSet; // Internal functions static int ch32v307ethPHYRegWrite( uint32_t reg, uint32_t val ); static int ch32v307ethPHYRegAsyncRead( int reg, int * value ); static int ch32v307ethPHYRegRead( uint32_t reg ); static int ch32v307ethPHYRegAsyncRead( int reg, int * value ) { static uint8_t reg_request_count = 0; uint32_t miiar = ETH->MACMIIAR; if( miiar & ETH_MACMIIAR_MB ) { return -1; } if( ( ( miiar & ETH_MACMIIAR_MR ) >> 6 ) != reg || reg_request_count < 2 ) { ETH->MACMIIAR = ETH_MACMIIAR_CR_Div42 /* = 0, per 27.1.8.1.4 */ | ((uint32_t)CH32V307GIGABIT_PHYADDRESS << 11) | // ETH_MACMIIAR_PA (((uint32_t)reg << 6) & ETH_MACMIIAR_MR) | (0 /*!ETH_MACMIIAR_MW*/) | ETH_MACMIIAR_MB; reg_request_count++; return -1; } reg_request_count = 0; *value = ETH->MACMIIDR; ETH->MACMIIAR |= ETH_MACMIIAR_MR; // Poison register. return 0; } static int ch32v307ethTickPhy(void) { int speed, linked, duplex; const int reg = (ch32v307eth_phyid == 0xc916) ? 0x1a : 0x11; // PHYSR (different on each part) int miidr; if( ch32v307ethPHYRegAsyncRead( reg, &miidr ) ) return -1; printf( "REG: %02x / %04x / %04x\n", reg, miidr, ch32v307eth_phyid ); if( reg == 0x1a ) { speed = ((miidr>>4)&3); linked = ((miidr>>2)&1); duplex = ((miidr>>3)&1); } else { speed = ((miidr>>14)&3); linked = ((miidr>>10)&1); duplex = ((miidr>>13)&1); } printf( "LINK INFO: %d %d %d\n", speed, linked, duplex ); if( linked ) { uint32_t oldmaccr = ETH->MACCR; uint32_t newmaccr = (oldmaccr & ~( ( 1<<11 ) | (3<<14) ) ) | (speed<<14) | ( duplex<<11); if( newmaccr != oldmaccr ) { ETH->MACCR = newmaccr; ch32v307ethHandleReconfig( linked, speed, duplex ); return 1; } } return 0; } // Based on ETH_WritePHYRegister static int ch32v307ethPHYRegWrite( uint32_t reg, uint32_t val ) { ETH->MACMIIDR = val; ETH->MACMIIAR = ETH_MACMIIAR_CR_Div42 /* = 0, per 27.1.8.1.4 */ | (((uint32_t)CH32V307GIGABIT_PHYADDRESS << 11)) | // ETH_MACMIIAR_PA (((uint32_t)reg << 6) & ETH_MACMIIAR_MR) | ETH_MACMIIAR_MW | ETH_MACMIIAR_MB; uint32_t timeout = 0x100000; while( ( ETH->MACMIIAR & ETH_MACMIIAR_MB ) && --timeout ); // If timeout = 0, is an error. return timeout ? 0 : -1; } static int ch32v307ethPHYRegRead( uint32_t reg ) { ETH->MACMIIAR = ETH_MACMIIAR_CR_Div42 /* = 0, per 27.1.8.1.4 */ | ((uint32_t)CH32V307GIGABIT_PHYADDRESS << 11) | // ETH_MACMIIAR_PA (((uint32_t)reg << 6) & ETH_MACMIIAR_MR) | (0 /*!ETH_MACMIIAR_MW*/) | ETH_MACMIIAR_MB; uint32_t timeout = 0x100000; while( ( ETH->MACMIIAR & ETH_MACMIIAR_MB ) && --timeout ); // If timeout = 0, is an error. return timeout ? ETH->MACMIIDR : -1; } static void ch32v307ethGetMacInUC( uint8_t * mac ) { // Mac is backwards. const uint8_t *macaddr = (const uint8_t *)(ROM_CFG_USERADR_ID+5); for( int i = 0; i < 6; i++ ) { mac[i] = *(macaddr--); } } static int ch32v307ethInit( void ) { int i; #ifdef CH32V307GIGABIT_PHY_RSTB funPinMode( CH32V307GIGABIT_PHY_RSTB, GPIO_CFGLR_OUT_50Mhz_PP ); //PHY_RSTB (For reset) funDigitalWrite( CH32V307GIGABIT_PHY_RSTB, FUN_LOW ); #endif // Configure strapping. funPinMode( PA1, GPIO_CFGLR_IN_PUPD ); // GMII_RXD3 funPinMode( PA0, GPIO_CFGLR_IN_PUPD ); // GMII_RXD2 funPinMode( PC3, GPIO_CFGLR_IN_PUPD ); // GMII_RXD1 funPinMode( PC2, GPIO_CFGLR_IN_PUPD ); // GMII_RXD0 funDigitalWrite( PA1, FUN_HIGH ); funDigitalWrite( PA0, FUN_HIGH ); funDigitalWrite( PC3, FUN_HIGH ); // No TX Delay funDigitalWrite( PC2, FUN_HIGH ); // Pull-up MDIO funPinMode( PD9, GPIO_CFGLR_OUT_50Mhz_PP ); //Pull-up control (DO NOT CHECK IN, ADD RESISTOR) funDigitalWrite( PD9, FUN_HIGH ); // Will be required later. RCC->APB2PCENR |= RCC_APB2Periph_AFIO; // https://cnlohr.github.io/microclockoptimizer/?chipSelect=ch32vx05_7%2Cd8c&HSI=1,8&HSE=0,8&PREDIV2=1,1&PLL2CLK=1,7&PLL2VCO=0,72&PLL3CLK=1,1&PLL3VCO=0,100&PREDIV1SRC=1,0&PREDIV1=1,2&PLLSRC=1,0&PLL=0,4&PLLVCO=1,144&SYSCLK=1,2& // Clock Tree: // 8MHz Input // PREDIV2 = 2 (1 in register) = 4MHz // PLL2 = 9 (7 in register) = 36MHz / PLL2VCO = 72MHz // PLL3CLK = 12.5 (1 in register) = 50MHz = 100MHz VCO // PREDIV1SRC = HSE (1 in register) = 8MHz // PREDIV1 = 2 (1 in register). // PLLSRC = PREDIV1 (0 in register) = 4MHz // PLL = 18 (0 in register) = 72MHz // PLLVCO = 144MHz // SYSCLK = PLLVCO = 144MHz // Use EXT_125M (ETH1G_SRC) // Switch processor back to HSI so we don't eat dirt. RCC->CFGR0 = (RCC->CFGR0 & ~RCC_SW) | RCC_SW_HSI; // Setup clock tree. RCC->CFGR2 |= (1<CTLR |= RCC_PLL3ON | RCC_PLL2ON; int timeout; for( timeout = 10000; timeout > 0; timeout--) if (RCC->CTLR & RCC_PLL3RDY) break; if( timeout == 0 ) return -5; for( timeout = 10000; timeout > 0; timeout--) if (RCC->CTLR & RCC_PLL2RDY) break; if( timeout == 0 ) return -6; // PLL = x18 (0 in register) RCC->CFGR0 = ( RCC->CFGR0 & ~(0xf<<18)) | 0; RCC->CTLR |= RCC_PLLON; for( timeout = 10000; timeout > 0; timeout--) if (RCC->CTLR & RCC_PLLRDY) break; if( timeout == 0 ) return -7; // Switch to PLL. #ifdef CH32V307GIGABIT_MCO25 RCC->CFGR0 = (RCC->CFGR0 & ~RCC_SW) | RCC_SW_PLL | (9<<24); // And output clock on PA8 #else RCC->CFGR0 = (RCC->CFGR0 & ~RCC_SW) | RCC_SW_PLL; #endif // For clock in. funPinMode( PB1, GPIO_CFGLR_IN_FLOAT ); //GMII_CLK125 RCC->CFGR2 |= RCC_ETH1G_125M_EN; // Enable 125MHz clock. // Power on and reset. RCC->AHBPCENR |= RCC_ETHMACEN | RCC_ETHMACTXEN | RCC_ETHMACRXEN; RCC->AHBRSTR |= RCC_ETHMACRST; RCC->AHBRSTR &=~RCC_ETHMACRST; ETH->DMABMR |= ETH_DMABMR_SR; // Wait for reset to complete. for( timeout = 10000; timeout > 0 && (ETH->DMABMR & ETH_DMABMR_SR); timeout-- ) { Delay_Us(10); } // Use RGMII EXTEN->EXTEN_CTR |= EXTEN_ETH_RGMII_SEL; //EXTEN_ETH_RGMII_SEL; funPinMode( PB13, GPIO_CFGLR_OUT_50Mhz_AF_PP ); //GMII_MDIO funPinMode( PB12, GPIO_CFGLR_OUT_50Mhz_AF_PP ); //GMII_MDC // For clock output to Ethernet module. funPinMode( PA8, GPIO_CFGLR_OUT_50Mhz_AF_PP ); // PHY_CKTAL // Release PHY from reset. #ifdef CH32V307GIGABIT_PHY_RSTB funDigitalWrite( CH32V307GIGABIT_PHY_RSTB, FUN_HIGH ); #endif Delay_Ms(25); // Waiting for PHY to exit sleep. This is inconsistent at 23ms (But only on the RTL8211FS) None is needed on the RTL8211E funPinMode( PB0, GPIO_CFGLR_OUT_50Mhz_AF_PP ); // GMII_TXD3 funPinMode( PC5, GPIO_CFGLR_OUT_50Mhz_AF_PP ); // GMII_TXD2 funPinMode( PC4, GPIO_CFGLR_OUT_50Mhz_AF_PP ); // GMII_TXD1 funPinMode( PA7, GPIO_CFGLR_OUT_50Mhz_AF_PP ); // GMII_TXD0 funPinMode( PA3, GPIO_CFGLR_OUT_50Mhz_AF_PP ); // GMII_TXCTL funPinMode( PA2, GPIO_CFGLR_OUT_50Mhz_AF_PP ); // GMII_TXC funPinMode( PA1, GPIO_CFGLR_IN_PUPD ); // GMII_RXD3 funPinMode( PA0, GPIO_CFGLR_IN_PUPD ); // GMII_RXD2 funPinMode( PC3, GPIO_CFGLR_IN_PUPD ); // GMII_RXD1 funPinMode( PC2, GPIO_CFGLR_IN_PUPD ); // GMII_RXD0 funPinMode( PC1, GPIO_CFGLR_IN_PUPD ); // GMII_RXCTL funPinMode( PC0, GPIO_CFGLR_IN_FLOAT ); // GMII_RXC funDigitalWrite( PA1, FUN_HIGH ); // SELGRV = 3.3V funDigitalWrite( PA0, FUN_HIGH ); // TXDelay = 1 funDigitalWrite( PC3, FUN_HIGH ); // AN[0] = 1 funDigitalWrite( PC2, FUN_HIGH ); // AN[1] = 1 funDigitalWrite( PC1, FUN_LOW ); // PHYAD[0] // Configure MDC/MDIO // Conflicting notes - some say /42, others don't. ETH->MACMIIAR = ETH_MACMIIAR_CR_Div42; // Update MACCR ETH->MACCR = ( CH32V307GIGABIT_CFG_CLOCK_DELAY << 29 ) | // No clock delay ( 0 << 23 ) | // Max RX = 2kB (Revisit if looking into jumbo frames) ( 0 << 22 ) | // Max TX = 2kB (Revisit if looking into jumbo frames) ( 0 << 21 ) | // Rated Drive (instead of energy savings mode) (10M PHY only) ( 1 << 20 ) | // Bizarre re-use of termination resistor terminology? (10M PHY Only) ( 0 << 17 ) | // IFG = 0, 96-bit guard time. ( 0 << 14 ) | // FES = 2 = GBE, 1=100MBit/s (UNSET TO START) ( 0 << 12 ) | // Self Loop = 0 ( 0 << 11 ) | // Full-Duplex Mode (UNSET TO START) ( 1 << 10 ) | // IPCO = 1, Check TCP, UDP, ICMP header checksums. ( 1 << 7 ) | // APCS (automatically strip frames) ( 1 << 3 ) | // TE (Transmit enable!) ( 1 << 2 ) | // RE (Receive Enable) ( CH32V307GIGABIT_CFG_CLOCK_PHASE << 1 ) | // TCF = 0 (Potentailly change if clocking is wrong) 0; Delay_Ms(25); // Waiting for PHY to exit sleep. This is inconsistent at 19ms. // Reset the physical layer ch32v307ethPHYRegWrite( PHY_BCR, PHY_Reset | 1<<12 | // Auto negotiate 1<<8 | // Duplex 1<<6 | // Speed Bit. 0 ); // De-assert reset. ch32v307ethPHYRegWrite( PHY_BCR, 1<<12 | // Auto negotiate 1<<8 | // Duplex 1<<6 | // Speed Bit. 0 ); ch32v307ethPHYRegRead( 0x03 ); ch32v307eth_phyid = ch32v307ethPHYRegRead( 0x03 ); // Read twice to be safe. if( ch32v307eth_phyid == 0xc916 ) ch32v307ethPHYRegWrite( 0x1F, 0x0a43 ); // RTL8211FS needs page select. ch32v307ethGetMacInUC( ch32v307eth_mac ); ETH->MACA0HR = (uint32_t)((ch32v307eth_mac[5]<<8) | ch32v307eth_mac[4]); ETH->MACA0LR = (uint32_t)(ch32v307eth_mac[0] | (ch32v307eth_mac[1]<<8) | (ch32v307eth_mac[2]<<16) | (ch32v307eth_mac[3]<<24)); ETH->MACFFR = (uint32_t)(ETH_ReceiveAll_Disable | ETH_SourceAddrFilter_Disable | ETH_PassControlFrames_BlockAll | ETH_BroadcastFramesReception_Enable | ETH_DestinationAddrFilter_Normal | ETH_PromiscuousMode_Disable | ETH_MulticastFramesFilter_Perfect | ETH_UnicastFramesFilter_Perfect); ETH->MACHTHR = (uint32_t)0; ETH->MACHTLR = (uint32_t)0; ETH->MACVLANTR = (uint32_t)(ETH_VLANTagComparison_16Bit); ETH->MACFCR = 0; // No pause frames. // Configure RX/TX chains. ETH_DMADESCTypeDef *tdesc; for(i = 0; i < CH32V307GIGABIT_TXBUFNB; i++) { tdesc = ch32v307eth_DMATxDscrTab + i; tdesc->ControlBufferSize = 0; tdesc->Status = ETH_DMATxDesc_TCH | ETH_DMATxDesc_IC | ETH_DMATxDesc_FS; tdesc->Buffer1Addr = (uint32_t)0; // Populate with data. tdesc->Buffer2NextDescAddr = (i < CH32V307GIGABIT_TXBUFNB - 1) ? ((uint32_t)(ch32v307eth_DMATxDscrTab + i + 1)) : (uint32_t)ch32v307eth_DMATxDscrTab; } ETH->DMATDLAR = (uint32_t)ch32v307eth_DMATxDscrTab; for(i = 0; i < CH32V307GIGABIT_RXBUFNB; i++) { tdesc = ch32v307eth_DMARxDscrTab + i; tdesc->Status = ETH_DMARxDesc_OWN; tdesc->ControlBufferSize = ETH_DMARxDesc_RCH | (uint32_t)CH32V307GIGABIT_BUFFSIZE; tdesc->Buffer1Addr = (uint32_t)(&ch32v307eth_MACRxBuf[i * CH32V307GIGABIT_BUFFSIZE]); tdesc->Buffer2NextDescAddr = (i < CH32V307GIGABIT_RXBUFNB - 1) ? (uint32_t)(ch32v307eth_DMARxDscrTab + i + 1) : (uint32_t)(ch32v307eth_DMARxDscrTab); } ETH->DMARDLAR = (uint32_t)ch32v307eth_DMARxDscrTab; pDMARxGet = ch32v307eth_DMARxDscrTab; pDMATxSet = ch32v307eth_DMATxDscrTab; // Receive a good frame half interrupt mask. // Receive CRC error frame half interrupt mask. // For the future: Why do we want this? ETH->MMCTIMR = ETH_MMCTIMR_TGFM; ETH->MMCRIMR = ETH_MMCRIMR_RGUFM | ETH_MMCRIMR_RFCEM; ETH->DMAIER = ETH_DMA_IT_NIS | // Normal interrupt enable. ETH_DMA_IT_R | // Receive ETH_DMA_IT_T | // Transmit ETH_DMA_IT_AIS | // Abnormal interrupt ETH_DMA_IT_RBU; // Receive buffer unavailable interrupt enable NVIC_EnableIRQ( ETH_IRQn ); // Actually enable receiving process. ETH->DMAOMR = ETH_DMAOMR_SR | ETH_DMAOMR_ST | ETH_DMAOMR_TSF | ETH_DMAOMR_FEF; return 0; } void ETH_IRQHandler( void ) __attribute__((interrupt)); void ETH_IRQHandler( void ) { uint32_t int_sta; do { int_sta = ETH->DMASR; if ( ( int_sta & ( ETH_DMA_IT_AIS | ETH_DMA_IT_NIS ) ) == 0 ) { break; } // Off nominal situations. if (int_sta & ETH_DMA_IT_AIS) { // Receive buffer unavailable interrupt enable. if (int_sta & ETH_DMA_IT_RBU) { ETH->DMASR = ETH_DMA_IT_RBU; if((INFO->CHIPID & 0xf0) == 0x10) { ((ETH_DMADESCTypeDef *)(((ETH_DMADESCTypeDef *)(ETH->DMACHRDR))->Buffer2NextDescAddr))->Status = ETH_DMARxDesc_OWN; ETH->DMARPDR = 0; } } ETH->DMASR = ETH_DMA_IT_AIS; } // Nominal interrupts. if( int_sta & ETH_DMA_IT_NIS ) { if( int_sta & ETH_DMA_IT_R ) { // Received a packet, normally. // Status is in Table 27-17 Definitions of RDes0 do { // XXX TODO: Is this a good place to acknowledge? REVISIT: Should this go lower? // XXX TODO: Restructure this to allow for ETH->DMASR = ETH_DMA_IT_R; uint32_t status = pDMARxGet->Status; if( status & ETH_DMARxDesc_OWN ) break; // We only have a valid packet in a specific situation. // So, we take the status, then mask off the bits we care about // And see if they're equal to the ones that need to be set/unset. const uint32_t mask = ETH_DMARxDesc_OWN | ETH_DMARxDesc_LS | ETH_DMARxDesc_ES | ETH_DMARxDesc_FS; const uint32_t eq = 0 | ETH_DMARxDesc_LS | 0 | ETH_DMARxDesc_FS; int suppress_own = 0; if( ( status & mask ) == eq ) { int32_t frame_length = ((status & ETH_DMARxDesc_FL) >> ETH_DMARXDESC_FRAME_LENGTHSHIFT) - 4; if( frame_length > 0 ) { uint8_t * data = (uint8_t*)pDMARxGet->Buffer1Addr; suppress_own = ch32v307ethInitHandlePacket( data, frame_length, pDMARxGet ); } } // Otherwise, Invalid Packet // Relinquish control back to underlying hardware. if( !suppress_own ) pDMARxGet->Status = ETH_DMARxDesc_OWN; // Tricky logic for figuring out the next packet. Originally // discussed in ch32v30x_eth.c in ETH_DropRxPkt if((pDMARxGet->ControlBufferSize & ETH_DMARxDesc_RCH) != (uint32_t)RESET) pDMARxGet = (ETH_DMADESCTypeDef *)(pDMARxGet->Buffer2NextDescAddr); else { if((pDMARxGet->ControlBufferSize & ETH_DMARxDesc_RER) != (uint32_t)RESET) pDMARxGet = (ETH_DMADESCTypeDef *)(ETH->DMARDLAR); else pDMARxGet = (ETH_DMADESCTypeDef *)((uint32_t)pDMARxGet + 0x10 + ((ETH->DMABMR & ETH_DMABMR_DSL) >> 2)); } } while( 1 ); } if( int_sta & ETH_DMA_IT_T ) { ch32v307ethInitHandleTXC(); ETH->DMASR = ETH_DMA_IT_T; } ETH->DMASR = ETH_DMA_IT_NIS; } } while( 1 ); } static int ch32v307ethTransmitStatic(uint8_t * buffer, uint32_t length, int enable_txc) { // The official SDK waits until ETH_DMATxDesc_TTSS is set. // This also provides a transmit timestamp, which could be // used for PTP. // But we don't want to do that. // We just want to go. If anyone cares, they can check later. if( pDMATxSet->Status & ETH_DMATxDesc_OWN ) { ETH->DMATPDR = 0; return -1; } pDMATxSet->ControlBufferSize = (length & ETH_DMATxDesc_TBS1); pDMATxSet->Buffer1Addr = (uint32_t)buffer; // Status is in Table 27-12 "Definitions of TDes0 bits" enable_txc = enable_txc ? ETH_DMATxDesc_IC : 0; pDMATxSet->Status = ETH_DMATxDesc_LS | // Last Segment (This is all you need to have to transmit) ETH_DMATxDesc_FS | // First Segment (Beginning of transmission) enable_txc | // Interrupt when complete ETH_DMATxDesc_TCH | // Next Descriptor Address Valid ETH_DMATxDesc_CIC_TCPUDPICMP_Full | // Do all header checksums. ETH_DMATxDesc_OWN; // Own back to hardware pDMATxSet = (ETH_DMADESCTypeDef*)pDMATxSet->Buffer2NextDescAddr; ETH->DMASR = ETH_DMASR_TBUS; // This resets the transmit process (or "starts" it) ETH->DMATPDR = 0; return 0; } #endif