Using a spectrum analyzer on the power pins, I noticed that calibrating the oscillator using USB start of frame or a 32.768 kHz crystal both give a very accurate 48.0 MHz frequency (and corresponding EMI radiation...). However, if I don't wait for the crystal to be ready before setting the DFLLCTRL register, I get 49.8 MHz and it persists even when the crystal is ready. So it seems, the clocks logic faults and stays in that faulted state without calibration forever.
I prefer to skip the wait as it takes about a second for the crystal to stabilize and I want to start interacting with the user right away. I know I can be checking later in a an existing program loop and set the DFLLCTRL register there (when the osc is ready), but prefer to keep things encapsulated and was wondering if there is a more automatic or elegant way.
void clocks_init()
{
// setup the crystal
OSC.XOSCCTRL = OSC_X32KLPM_bm | OSC_XOSCSEL1_bm;
OSC.CTRL = OSC_XOSCEN_bm;
// setup the 48 MHz internal osc
DFLLRC32M.CALA = nvm_read_production_signature_row(NVM_PTR(USBRCOSCA));
DFLLRC32M.CALB = nvm_read_production_signature_row(NVM_PTR(USBRCOSC));
uint16_t cal = (U32)48000000 / 1024;
DFLLRC32M.COMP1 = LSB(cal);
DFLLRC32M.COMP2 = MSB(cal);
while(!(OSC.STATUS & OSC_XOSCRDY_bm)) { NOP; } // <------------ Removing this line make the clock permanently wrong, even after many seconds
OSC.DFLLCTRL = OSC_RC32MCREF_XOSC32K_gc; // calibrate using the crystal
OSC.CTRL |= OSC_RC32MEN_bm;
DFLLRC32M.CTRL = DFLL_ENABLE_bm;
while(!(OSC.STATUS & OSC_RC32MRDY_bm)) { NOP; }
ccp_write_io((uint8_t *)&CLK.PSCTRL, CLK_PSADIV_2_gc | CLK_PSBCDIV_1_1_gc);
ccp_write_io((uint8_t *)&CLK.USBCTRL, CLK_USBSRC_RC32M_gc | CLK_USBSEN_bm);
ccp_write_io((uint8_t *)&CLK.CTRL, CLK_SCLKSEL_RC32M_gc);
}