AnsweredAssumed Answered

Seeking help with K64's IRC 48MHz, or "cross clocking and you"

Question asked by Max Piepenbrink on Jun 24, 2014
Latest reply on Jul 21, 2014 by Max Piepenbrink

Hello,

 

I'm currently attempting to get the a MK64FN1M0VLL12 to do quite a few things! I have a 50MHz reference clock coming in on EXTAL which I am using in tandem with Processor Expert to generate my clocking pathway to get the core clocked to 120Mhz. The 50MHz is imperative for RMII and Ethernet, which isn't the issue at hand. I'm developing this on a custom and in-house board at my employment.

 

The K64 family has a wonderful feature, an internal 48MHz reference clock that's part of the USB module. I thought "sure, let's use that for USB!", since RMII needs 50Mhz, every other peripheral is happy except USB. I was immediately face to face with a problem: Processor Expert doesn't seem to know about or acknowledge the 48MHz IRC as a clock source to use with the USB Logical Device Drivers. If you try to use the 48Mhz IRC for anything except a source for the MCG's PLL/FLL you are SOL. The documentation clearly shows USB as being able to run independently of the core clock, effectively putting it in its own clock domain. So I said "okay well I just need to write a simple USB CDC driver, couldn't be too bad" and I set down on a turbid path of wrapping my head around just enough of the processor internals and the USB 2.0 spec to get this thing moving.

 

I have USB primed and ready, I've verified my Buffer Descriptor Tables are set up properly, that my Buffer Descriptors themselves are ready (Endpoint 0 configured and ready). I get and handle my USB_RST interrupts appropriately and make sure EP0 is ready to receive the SETUP packet(s) from the host. I got it to the point where you plug it in and everything is off and running, the host computer sees the presence of a USB device, and through a serial log I get a TON of Start of Frames, but I almost never get a simple TOKDNE, or "Token Done", in fact I was getting USB Error interrupts with DMA errors more often than not (USB_ERRSTAT = 0x20, meaning "DMA Error" more or less). I didn't know much about this sort of problem set so I dug deeper.

 

The Kinetis K64 is a crossbar based architecture, which I understand to mean that all participants of the crossbar have basically zero or 1 cycle access to other participants across the crossbar, the documentation also strongly suggests that the USB module has its own DMA controller built into it. The idea that popped into my head is that I'm experiencing resource contention! The Core is running at 120MHz, USB is running in its own domain at 48MHz, USB's DMA must be failing to get to SRAM to interrogate the BDT and put things where they need to be! There's two modes of crossbar arbitration for all the crossbar slaves, Round Robin and Fixed Priority, the default being Fixed Priority. So I went ahead and tried to fiddle with the priority of the masters on the crossbar switch.

 

  AXBS_PRS0 = 0x504321;

  AXBS_PRS1 = 0x504321;

  AXBS_PRS2 = 0x504321;

 

This seems to have stopped the DMA errors, and after I initially tried this I was pleased to see a consistent round of TOKDNE interrupts reaching my code! Strangely the buffer descriptors were untouched and empty (I've triple checked and audited the values of all the relevant registers and triple checked the way the pointer math plays out with the USB module and where it wants things to be) I went ahead and added some more verbosity to my debug prints so I could see more. Suddenly no more TOKDNEs, just an ocean of SOFs, a few RSTs, a few SLEEPs and eventually the host gives up. No more TOKDNEs! I quickly undid my exact changes (they were minor) -- still no more TOKDNEs! Confounded I come to you, Kinetis experts.

 

Is Cross Clocking USB via the IRC48MHz or via USB_CLKIN even possible? Where should I go next? I'd like to use the Freescale SDK and the USB stack but it is a BEHEMOTH that seems to really really want me to use an ocean of code I'm hoping to avoid for a simple USB peripheral that I'm inches away from finishing implementing.

 

I haven't posted any code really yet, and I'm happy to do so but I was hoping to hazard some discussion and figure out what is worth sharing.

 

Edit:

I do want to share my findings with getting the darn IRC48MHz to drive USB in the first place though It is heavily dependent on examples I've found elsewhere, mainly the TeensyDuino 3.1 code, where Paul has written his own minimal USB stack more or less.

 

Here's the snippet used to select the IRC48M, I put it in a function because you need to do this more than once when initializing USB from what I could tell!

 

void USB_ClockInit() {
 // This selects the muxing of the high frequency clock options for
 // extra peripherals (USB included of course).
 // See Figure 5-1 "Clocking Diagram" in the Kinetis K64 Sub-Family reference manual
 // You should see a muxer with the following options:
 // MCGPLLCLK, MCGFLLCLK, IRC48MCLK, we need to use the 48MHz
 // which is what the PLLFLLSEL bitfield does.
 // The USBSRC is pretty self explanatory, it can either use the aforementioned
 // mux pathway, or an external pin as a reference. We want the internal 48
 // Select 48MHz IRC as the optional peripheral clock
  SIM_SOPT2 |= SIM_SOPT2_SET_PLLFLLSEL;


 // Select the USB src as the IRC48M
  SIM_SOPT2 |= SIM_SOPT2_SET_USBSRC;

 // Enables the 48MHz internal reference recovery
 // clock feature and also the 48MHz regulator
  USB0_CLK_RECOVER_IRC_EN |= (USB0_CLK_RECOVER_IRC_EN_SET_IRC_EN | 0x1);
  USB0_CLK_RECOVER_CTRL |= (USB0_BIT_CLOCK_RECOVER_EN);

 // temporarily output the 48mhz irc onto pin 73
  SIM_SOPT2 |= CLKOUTSEL_IRC48MHZ;
 //SIM_SOPT2 |= ( 0x40 );
}

 

Then here's the code where I actually initialize USB:

void USBInitialize() {


  for ( int i = 0 ; i < NUM_OF_ENDPOINTS * 4 ; i++ ) {
  BufferDescriptorTable[i].buffer_descriptor = 0;
  BufferDescriptorTable[i].buffer_address = 0;
  }

 // Enable USB
  SCGC4 |= SCGC4_BITFIELD_USBOTG;

  USB_ClockInit();

 // Documentation says "wait two USB cycles" but we can just wait for it to read 0
  while (( USB0_USBTRC0 & USB0_USBTRC0_USBRESET ) != 0) ;

 // Reset the USB SIE
  USB0_USBTRC0 |= USB0_USBTRC0_USBRESET;

 // Resetting USB actually kills the IRC48M so we need
 // to turn it back on!
  USB_ClockInit();

 // Documentation says "wait two USB cycles" but we can just wait for it to read 0
  while (( USB0_USBTRC0 & USB0_USBTRC0_USBRESET ) != 0) ;

 // Let the Buffer Descriptor Pages get set
 // Basically, the BDT needs to be aligned on 512 bytes
 // We can truncate the lower 8 bits because of this fact
 // So if the "BufferDescriptorTable" variable inside of
 // USBStructures.h isn't aligned to have an address that starts
 // with, you'll be in bad shape!
  USB0_BDTPAGE1 = ( ( (uint32_t)BufferDescriptorTable  ) >> 8  ) & 255; // Bits 8-15
  USB0_BDTPAGE2 = ( ( (uint32_t)BufferDescriptorTable  ) >> 16 ) & 255; // Bits 16-23
  USB0_BDTPAGE3 = ( ( (uint32_t)BufferDescriptorTable  ) >> 24 ) & 255; // Bits 24-31

 // Clear ISTAT, ERRSTAT and OTGISTAT flags
  USB0_ISTAT = 0xFF;
  USB0_ERRSTAT = 0xFF;
  USB0_OTGISTAT = 0xFF;

 // I've witnessed a USB0_USBTRC0 |= 0x40 here
 // and that field is specifically undocumented
 // but that is the "USB_CLK_ RECOVERY_INT" field, it's possible
 // this happens to clear the USB_CLK_ RECOVERY_INT flag even
 // though it's declared as read only, currently I'm not doing
 // that because I don't want to code in undefined behavior
 // USB0_USBTRC0 |= 0x40;

 // Enable the USB peripheral
  USB0_CTL = USB0_CTL_USBENSOFEN;

 // This makes sure the pulldowns are disabled and the transceiver isn't suspended
  USB0_USBCTRL = 0;

  USB0_INTEN = USB_INTEN_USBRSTEN;

  NVIC_ENABLE_IRQ( IRQ_USBOTG );

 // enable pull up
  USB0_CONTROL = USB0_CONTROL_DLLPULLUPNONOTG;

 // The clock is configured!
 // Things should start happening now
}

Outcomes