LPC4350 SGPIO Experimentation

The NXP LPC43xx microcontrollers have an interesting, programmable serial peripheral called the SGPIO (Serial GPIO). It consists of a slew of counters and shift registers that can be configured to serialize and deserialize many channels of data. Channels can be grouped to create multi-bit parallel data streams.

The current HackRF design entails using the SGPIO peripheral to move quadrature baseband receive and transmit data between the USB interface and the baseband ADC/DAC IC. Because the baseband ADC/DAC IC (MAX5864) uses DDR signaling, we expect to use a CPLD to convert bus signaling. The CPLD may also help manage bus turnaround (between transmit and receive modes) or interfacing two narrower but faster interfaces to the LPC43xx to facilitate full-duplex.

Because the Jellybean board wasn’t completed at the time of these experiments, I used the Diolan LPC-4350-DB1-A development board. Despite using an LPC4350 in an BGA256 package, the SGPIO peripheral’s signals can be mapped to many different pins. So reworking code to a new set of SGPIO pins should be a trivial matter of switching the SGU configuration for the affected pins.

SGPIO Examples

Some SGPIO peripheral examples can be found in the LPCWare repository. All source I’ve found so far is focused on generating many I2S interfaces, which is not very similar to HackRF’s needs. But reviewing the code is still valuable in grasping how the SGPIO peripheral operates.

There are a few common details to setting up the SGPIO peripheral:

// Configure the PLL to generate a reasonable clock. The SGPIO
// will operate with a clock of up to 204MHz (the same as the
// M4 clock.
CGU_SetPLL1(12);

// Set the BASE_PERIPH clock to come from PLL1.
CGU_EnableEntity(CGU_BASE_PERIPH, ENABLE);
CGU_EntityConnect(CGU_CLKSRC_PLL1, CGU_BASE_PERIPH);

// Compute and commit clock configuration changes.
CGU_UpdateClock();

Jiggle SGPIO Pins From GPIO Mode

My first test was to ensure I had the right pin(s) hooked up to my scope:

// Jiggle one of the SGPIO pins in GPIO mode, to make sure
// I'm looking at the right pin on the scope.
scu_pinmux(9,  0, MD_PLN_FAST, 0);

GPIO_SetDir(4, 1L << 12, 1);

while(1) {
    volatile int i;
    GPIO_SetValue(4, 1L << 12);
    for(i=0; i<1000; i++);
    GPIO_ClearValue(4, 1L << 12);
    for(i=0; i<1000; i++);
}

Jiggle Pins from SGPIO Mode

You can also control SGPIO pins, GPIO-style, from within the SGPIO peripheral. This helped me understand the basics of operating the SGPIO output mux.

// Set pin to SGPIO mode, toggle output using SGPIO
// peripheral registers.
scu_pinmux(9,  0, MD_PLN_FAST, 6);  // SGPIO0

// P_OUT_CFG = 4, gpio_out
// P_OE_CFG = X
LPC_SGPIO->OUT_MUX_CFG[0] = (0L << 4) | (4L << 0);
LPC_SGPIO->GPIO_OENREG |= (1L << 0);

while(1) {
    volatile int i;
    LPC_SGPIO->GPIO_OUTREG |= (1L << 0);
    for(i=0; i<1000; i++);
    LPC_SGPIO->GPIO_OUTREG &= ~(1L << 0);
    for(i=0; i<1000; i++);
}

Serializing Data With Slice Clock Source

My first full-on SGPIO experiment involved serializing a data pattern from slice A, using slice D to generate a SGPIO_CLK/2 data rate. I derived the code from examples that configured the SGPIO as I2S interfaces:

// Disable all counters during configuration
LPC_SGPIO->CTRL_ENABLED = 0;

// Configure pin functions.
scu_pinmux(9,  0, MD_PLN_FAST, 6);  // SGPIO0
scu_pinmux(2,  3, MD_PLN_FAST, 0);  // SGPIO12

// Enable SGPIO pin outputs.
LPC_SGPIO->GPIO_OENREG =
    (1L << 12) |    // SGPIO12
    (1L <<  0);     // SGPIO0

// SGPIO pin 0 outputs slice A bit 0.
LPC_SGPIO->OUT_MUX_CFG[0] =
    (0L <<  4) |    // P_OE_CFG = X
    (0L <<  0);     // P_OUT_CFG = 0, dout_doutm1 (1-bit mode)

// SGPIO pin 12 outputs slice D bit 0.
LPC_SGPIO->OUT_MUX_CFG[12] =
    (0L <<  4) |    // P_OE_CFG = X
    (0L <<  0);     // P_OUT_CFG = 0, dout_doutm1 (1-bit mode)

// Slice A
LPC_SGPIO->SGPIO_MUX_CFG[0] =
    (0L << 12) |    // CONCAT_ORDER = 0 (self-loop)
    (1L << 11) |    // CONCAT_ENABLE = 1 (concatenate data)
    (0L <<  9) |    // QUALIFIER_SLICE_MODE = X
    (0L <<  7) |    // QUALIFIER_PIN_MODE = X
    (0L <<  5) |    // QUALIFIER_MODE = 0 (enable)
    (0L <<  3) |    // CLK_SOURCE_SLICE_MODE = 0, slice D
    (0L <<  1) |    // CLK_SOURCE_PIN_MODE = X
    (0L <<  0);     // EXT_CLK_ENABLE = 0, internal clock signal (slice)

LPC_SGPIO->SLICE_MUX_CFG[0] =
    (0L <<  8) |    // INV_QUALIFIER = 0 (use normal qualifier)
    (0L <<  6) |    // PARALLEL_MODE = 0 (shift 1 bit per clock)
    (0L <<  4) |    // DATA_CAPTURE_MODE = 0 (detect rising edge)
    (0L <<  3) |    // INV_OUT_CLK = 0 (normal clock)
    (0L <<  2) |    // CLKGEN_MODE = 0 (use clock from COUNTER)
    (0L <<  1) |    // CLK_CAPTURE_MODE = 0 (use rising clock edge)
    (0L <<  0);     // MATCH_MODE = 0 (do not match data)

LPC_SGPIO->PRESET[0] = 1;
LPC_SGPIO->COUNT[0] = 0;
LPC_SGPIO->POS[0] = (0x1FL << 8) | (0x1FL << 0);
LPC_SGPIO->REG[0] = 0xAAAAAAAA;     // Primary output data register
LPC_SGPIO->REG_SS[0] = 0xAAAAAAAA;  // Shadow output data register

// Slice D (clock for Slice A)
LPC_SGPIO->SGPIO_MUX_CFG[3] =
    (0L << 12) |    // CONCAT_ORDER = 0 (self-loop)
    (1L << 11) |    // CONCAT_ENABLE = 1 (concatenate data)
    (0L <<  9) |    // QUALIFIER_SLICE_MODE = X
    (0L <<  7) |    // QUALIFIER_PIN_MODE = X
    (0L <<  5) |    // QUALIFIER_MODE = 0 (enable)
    (0L <<  3) |    // CLK_SOURCE_SLICE_MODE = 0, slice D
    (0L <<  1) |    // CLK_SOURCE_PIN_MODE = X
    (0L <<  0);     // EXT_CLK_ENABLE = 0, internal clock signal (slice)

LPC_SGPIO->SLICE_MUX_CFG[3] =
    (0L <<  8) |    // INV_QUALIFIER = 0 (use normal qualifier)
    (0L <<  6) |    // PARALLEL_MODE = 0 (shift 1 bit per clock)
    (0L <<  4) |    // DATA_CAPTURE_MODE = 0 (detect rising edge)
    (0L <<  3) |    // INV_OUT_CLK = 0 (normal clock)
    (0L <<  2) |    // CLKGEN_MODE = 0 (use clock from COUNTER)
    (0L <<  1) |    // CLK_CAPTURE_MODE = 0 (use rising clock edge)
    (0L <<  0);     // MATCH_MODE = 0 (do not match data)

LPC_SGPIO->PRESET[3] = 0;
LPC_SGPIO->COUNT[3] = 0;
LPC_SGPIO->POS[3] = (0x1FL << 8) | (0x1FL << 0);
LPC_SGPIO->REG[0] = 0xAAAAAAAA;     // Primary output data register
LPC_SGPIO->REG_SS[0] = 0xAAAAAAAA;  // Shadow output data register

// Start SGPIO operation by enabling slice clocks.
LPC_SGPIO->CTRL_ENABLED =
    (1L <<  3) |    // Slice D
    (1L <<  0);     // Slice A