/*
* Minimal SGTL5000 bring-up for i.MX RT1064 (custom board)
*
* Base project : SDK example "hello_world" for evkmimxrt1064
* What it does : 1. sets up audio PLL -> SAI1 MCLK = 12.288 MHz
* 2. muxes I2C + SAI pins exactly as on your schematic
* 3. starts SAI1 so MCLK/BCLK/LRCLK are running
* 4. reads SGTL5000 CHIP_ID over LPI2C1
* 5. runs CODEC_Init and plays a 1 kHz sine on headphone/line-out
*
* Pin map (from your schematic):
* GPIO_AD_B1_00 (J11) LPI2C1_SCL
* GPIO_AD_B1_01 (K11) LPI2C1_SDA
* GPIO_AD_B1_09 (M13) SAI1_MCLK
* GPIO_AD_B1_12 (H12) SAI1_RX_DATA00 (codec DOUT)
* GPIO_AD_B1_13 (H11) SAI1_TX_DATA00 (codec DIN)
* GPIO_AD_B1_11 (J13) SAI1_RX_BCLK
* GPIO_AD_B1_10 (L13) SAI1_RX_SYNC (LRCLK)
*/
#include "fsl_debug_console.h"
#include "board.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "fsl_iomuxc.h"
#include "fsl_clock.h"
#include "fsl_lpi2c.h"
#include "fsl_sai.h"
#include "fsl_codec_common.h"
#include "fsl_sgtl5000.h"
#include "fsl_codec_adapter.h"
#include "fsl_gpio.h"
/* ------------------------------------------------------------------ */
/* Settings */
/* ------------------------------------------------------------------ */
#define MCLK_HZ 12288000U /* 786.432 MHz / 4 / 16 */
#define SAMPLE_RATE 16000U
#define SGTL_ADDR 0x0AU /* 0x2A if CTRL_ADR0_CS is tied high */
#define LPI2C_CLK_HZ ((CLOCK_GetFreq(kCLOCK_Usb1PllClk) / 8U) / (5U + 1U)) /* 10 MHz */
#define CODEC_OUT_CH (kCODEC_PlayChannelHeadphoneLeft | kCODEC_PlayChannelHeadphoneRight | \
kCODEC_PlayChannelLineOutLeft | kCODEC_PlayChannelLineOutRight)
/* Audio PLL = 24 MHz * (32 + 768/1000) = 786.432 MHz */
static const clock_audio_pll_config_t s_audioPll = {
.loopDivider = 32,
.postDivider = 1,
.numerator = 768,
.denominator = 1000,
};
static sgtl_config_t s_sgtlConfig = {
.i2cConfig = {.codecI2CInstance = 1U, .codecI2CSourceClock = 10000000U},
.route = kSGTL_RoutePlayback,
.slaveAddress = SGTL_ADDR,
.bus = kSGTL_BusI2S,
.format = {.mclk_HZ = MCLK_HZ, .sampleRate = SAMPLE_RATE, .bitWidth = 16U},
.master_slave = false, /* codec is slave, RT1064 generates BCLK/LRCLK */
};
static codec_config_t s_codecConfig = {
.codecDevType = kCODEC_SGTL5000,
.codecDevConfig = &s_sgtlConfig,
};
static codec_handle_t s_codec;
/* 1 kHz sine at 16 kHz = 16 samples per period, stereo interleaved */
static const int16_t s_sineMono[16] = {
0, 3061, 5657, 7391, 8000, 7391, 5657, 3061,
0, -3061, -5657, -7391, -8000, -7391, -5657, -3061,
};
static int16_t s_tone[32];
/* ------------------------------------------------------------------ */
/* Pins */
/* ------------------------------------------------------------------ */
static void audio_pins_init(void)
{
CLOCK_EnableClock(kCLOCK_Iomuxc);
/* I2C: open-drain, SION on */
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_00_LPI2C1_SCL, 1U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_01_LPI2C1_SDA, 1U);
IOMUXC->SELECT_INPUT[kIOMUXC_LPI2C1_SCL_SELECT_INPUT] = 1U;
IOMUXC->SELECT_INPUT[kIOMUXC_LPI2C1_SDA_SELECT_INPUT] = 1U;
/* Same pad setting Teensy 4 Wire uses: 22k pull-up ENABLED (PUE=1),
* open-drain, DSE=4, speed 1, fast slew. */
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_00_LPI2C1_SCL, 0xF861U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_01_LPI2C1_SDA, 0xF861U);
/* SAI1 */
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_09_SAI1_MCLK, 1U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_11_SAI1_RX_BCLK, 1U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_10_SAI1_RX_SYNC, 1U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00, 1U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00, 1U);
/* Weak drive (DSE=1), slow slew: reduces MCLK/BCLK ringing and
* crosstalk into SDA/SCL on jumper wires. */
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_09_SAI1_MCLK, 0x10B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_11_SAI1_RX_BCLK, 0x1008U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_10_SAI1_RX_SYNC, 0x1008U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00, 0x1008U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_12_SAI1_RX_DATA00, 0x1008U);
}
/* ------------------------------------------------------------------ */
/* Teensy-style I2C scan: address-only write, same as */
/* Wire.beginTransmission(addr); Wire.endTransmission(); */
/* ------------------------------------------------------------------ */
static void lpi2c_scan(void)
{
lpi2c_master_config_t cfg;
LPI2C_MasterGetDefaultConfig(&cfg);
cfg.baudRate_Hz = 100000U;
LPI2C_MasterInit(LPI2C1, &cfg, LPI2C_CLK_HZ);
PRINTF("I2C scan (Teensy style)...\r\n");
int found = 0;
for (uint8_t addr = 1; addr < 127; addr++)
{
uint8_t dummy = 0;
lpi2c_master_transfer_t x = {0};
x.slaveAddress = addr;
x.direction = kLPI2C_Write;
x.data = &dummy;
x.dataSize = 0U; /* address only */
x.flags = kLPI2C_TransferDefaultFlag;
if (LPI2C_MasterTransferBlocking(LPI2C1, &x) == kStatus_Success)
{
PRINTF(" I2C device found at 0x%02X\r\n", addr);
found++;
}
}
PRINTF("Scan done: %d device(s)\r\n", found);
LPI2C_MasterDeinit(LPI2C1);
}
/* ------------------------------------------------------------------ */
/* SLOW bit-bang I2C (~1 kHz) on the same pins, no LPI2C involved. */
/* Also checks every bit: if a line we released does not go HIGH, or */
/* reads wrong, something else is fighting the bus -> reported. */
/* SCL = GPIO1_IO16 (AD_B1_00), SDA = GPIO1_IO17 (AD_B1_01) */
/* ------------------------------------------------------------------ */
#define BB_SCL 16U
#define BB_SDA 17U
static uint32_t s_bbErrors;
static void bb_wait(void) { SDK_DelayAtLeastUs(250U, SystemCoreClock); }
static void bb_low(uint32_t pin) { GPIO1->DR &= ~(1UL << pin); GPIO1->GDIR |= (1UL << pin); }
static void bb_release(uint32_t pin) { GPIO1->GDIR &= ~(1UL << pin); }
static uint32_t bb_get(uint32_t pin) { return (GPIO1->PSR >> pin) & 1UL; }
static void bb_scl_high(void)
{
bb_release(BB_SCL);
for (int i = 0; (i < 1000) && (bb_get(BB_SCL) == 0U); i++) /* allow clock stretching */
{
SDK_DelayAtLeastUs(10U, SystemCoreClock);
}
if (bb_get(BB_SCL) == 0U) { s_bbErrors++; }
}
static void bb_start(void)
{
bb_release(BB_SDA); bb_scl_high(); bb_wait();
if (bb_get(BB_SDA) == 0U) { s_bbErrors++; } /* SDA should be high before START */
bb_low(BB_SDA); bb_wait();
bb_low(BB_SCL); bb_wait();
}
static void bb_stop(void)
{
bb_low(BB_SDA); bb_wait();
bb_scl_high(); bb_wait();
bb_release(BB_SDA); bb_wait();
}
/* returns true if slave ACKed */
static bool bb_write_byte(uint8_t b)
{
for (int i = 7; i >= 0; i--)
{
if ((b >> i) & 1U) { bb_release(BB_SDA); } else { bb_low(BB_SDA); }
bb_wait();
bb_scl_high(); bb_wait();
if ((((b >> i) & 1U) != 0U) && (bb_get(BB_SDA) == 0U)) { s_bbErrors++; } /* released but low */
bb_low(BB_SCL); bb_wait();
}
bb_release(BB_SDA); bb_wait();
bb_scl_high(); bb_wait();
bool ack = (bb_get(BB_SDA) == 0U);
bb_low(BB_SCL); bb_wait();
return ack;
}
static uint8_t bb_read_byte(bool ack)
{
uint8_t b = 0;
bb_release(BB_SDA);
for (int i = 0; i < 8; i++)
{
bb_wait(); bb_scl_high(); bb_wait();
b = (uint8_t)((b << 1) | bb_get(BB_SDA));
bb_low(BB_SCL);
}
if (ack) { bb_low(BB_SDA); } else { bb_release(BB_SDA); }
bb_wait(); bb_scl_high(); bb_wait(); bb_low(BB_SCL); bb_wait();
bb_release(BB_SDA);
return b;
}
static void slow_bitbang_test(void)
{
gpio_pin_config_t in = {kGPIO_DigitalInput, 0U, kGPIO_NoIntmode};
/* GPIO mode, open-drain, hysteresis ON, 22k pull-up */
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_00_GPIO1_IO16, 1U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_01_GPIO1_IO17, 1U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_00_GPIO1_IO16, 0x1F8B0U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_01_GPIO1_IO17, 0x1F8B0U);
GPIO_PinInit(GPIO1, BB_SCL, &in);
GPIO_PinInit(GPIO1, BB_SDA, &in);
SDK_DelayAtLeastUs(1000U, SystemCoreClock);
PRINTF("\r\nSLOW bit-bang (1 kHz): idle SCL=%u SDA=%u (expect 1 1)\r\n",
(unsigned)bb_get(BB_SCL), (unsigned)bb_get(BB_SDA));
/* 9 clocks to free a stuck slave */
bb_release(BB_SDA);
for (int i = 0; i < 9; i++) { bb_low(BB_SCL); bb_wait(); bb_scl_high(); bb_wait(); }
bb_stop();
s_bbErrors = 0;
int found = 0;
for (uint8_t a = 0x08; a <= 0x77; a++)
{
bb_start();
bool ack = bb_write_byte((uint8_t)(a << 1));
bb_stop();
if (ack) { PRINTF(" bit-bang ACK at 0x%02X\r\n", a); found++; }
}
PRINTF("bit-bang scan: %d found, line errors = %u (expect 0)\r\n", found, (unsigned)s_bbErrors);
/* Direct CHIP_ID read at 0x0A */
s_bbErrors = 0;
bb_start();
bool a1 = bb_write_byte(0x0A << 1);
bool a2 = bb_write_byte(0x00);
bool a3 = bb_write_byte(0x00);
bb_start(); /* repeated START */
bool a4 = bb_write_byte((0x0A << 1) | 1U);
uint8_t hi = bb_read_byte(true);
uint8_t lo = bb_read_byte(false);
bb_stop();
PRINTF("bit-bang CHIP_ID @0x0A: acks=%d%d%d%d id=0x%02X%02X errors=%u\r\n",
a1, a2, a3, a4, hi, lo, (unsigned)s_bbErrors);
/* back to LPI2C1 */
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_00_LPI2C1_SCL, 1U);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_01_LPI2C1_SDA, 1U);
IOMUXC->SELECT_INPUT[kIOMUXC_LPI2C1_SCL_SELECT_INPUT] = 1U;
IOMUXC->SELECT_INPUT[kIOMUXC_LPI2C1_SDA_SELECT_INPUT] = 1U;
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_00_LPI2C1_SCL, 0xF861U);
IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_01_LPI2C1_SDA, 0xF861U);
}
/* ------------------------------------------------------------------ */
/* Clocks */
/* ------------------------------------------------------------------ */
static void audio_clocks_init(void)
{
CLOCK_InitAudioPll(&s_audioPll);
/* LPI2C root: PLL3/8 = 60 MHz, /6 = 10 MHz */
CLOCK_SetMux(kCLOCK_Lpi2cMux, 0U);
CLOCK_SetDiv(kCLOCK_Lpi2cDiv, 5U);
/* SAI1 root: Audio PLL / 4 / 16 = 12.288 MHz */
CLOCK_SetMux(kCLOCK_Sai1Mux, 2U);
CLOCK_SetDiv(kCLOCK_Sai1PreDiv, 3U);
CLOCK_SetDiv(kCLOCK_Sai1Div, 15U);
CLOCK_EnableClock(kCLOCK_Sai1);
/* MCLK pin = output, source = SAI1_CLK_ROOT */
IOMUXC_GPR->GPR1 = (IOMUXC_GPR->GPR1 & ~IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK) |
IOMUXC_GPR_GPR1_SAI1_MCLK_DIR_MASK;
PRINTF("Audio PLL = %u Hz\r\n", (unsigned)CLOCK_GetFreq(kCLOCK_AudioPllClk));
PRINTF("SAI1 mux=%u prediv=%u div=%u -> MCLK = %u Hz (expect 12288000)\r\n",
(unsigned)CLOCK_GetMux(kCLOCK_Sai1Mux),
(unsigned)CLOCK_GetDiv(kCLOCK_Sai1PreDiv),
(unsigned)CLOCK_GetDiv(kCLOCK_Sai1Div),
(unsigned)(CLOCK_GetFreq(kCLOCK_AudioPllClk) /
(CLOCK_GetDiv(kCLOCK_Sai1PreDiv) + 1U) /
(CLOCK_GetDiv(kCLOCK_Sai1Div) + 1U)));
PRINTF("LPI2C clock = %u Hz (expect 10000000)\r\n", (unsigned)LPI2C_CLK_HZ);
}
/* ------------------------------------------------------------------ */
/* SAI1: RX = clock master on RX_BCLK/RX_SYNC pins, TX synced to RX */
/* ------------------------------------------------------------------ */
static void sai_init(void)
{
sai_transceiver_t cfg;
SAI_Init(SAI1);
SAI_GetClassicI2SConfig(&cfg, kSAI_WordWidth16bits, kSAI_Stereo, 1U << 0);
cfg.masterSlave = kSAI_Master;
cfg.syncMode = kSAI_ModeAsync; /* RX drives BCLK + LRCLK */
SAI_RxSetConfig(SAI1, &cfg);
cfg.syncMode = kSAI_ModeSync; /* TX uses RX clocks */
SAI_TxSetConfig(SAI1, &cfg);
SAI_RxSetBitClockRate(SAI1, MCLK_HZ, SAMPLE_RATE, kSAI_WordWidth16bits, 2U);
SAI_TxSetBitClockRate(SAI1, MCLK_HZ, SAMPLE_RATE, kSAI_WordWidth16bits, 2U);
/* In sync mode this also enables RX, so BCLK/LRCLK start running */
SAI_TxEnable(SAI1, true);
}
/* ------------------------------------------------------------------ */
/* Direct CHIP_ID read, independent of the codec driver */
/* ------------------------------------------------------------------ */
static bool sgtl_read_chip_id(void)
{
lpi2c_master_config_t cfg;
LPI2C_MasterGetDefaultConfig(&cfg);
cfg.baudRate_Hz = 100000U;
LPI2C_MasterInit(LPI2C1, &cfg, LPI2C_CLK_HZ);
bool found = false;
const uint8_t addrs[2] = {0x0A, 0x2A};
for (int a = 0; a < 2; a++)
{
uint8_t id[2] = {0};
lpi2c_master_transfer_t x = {0};
x.slaveAddress = addrs[a];
x.direction = kLPI2C_Read;
x.subaddress = 0x0000U; /* CHIP_ID */
x.subaddressSize = 2U;
x.data = id;
x.dataSize = 2U;
x.flags = kLPI2C_TransferDefaultFlag;
status_t s = LPI2C_MasterTransferBlocking(LPI2C1, &x);
PRINTF("CHIP_ID @0x%02X: status=%d id=0x%02X%02X\r\n",
addrs[a], (int)s, id[0], id[1]);
if ((s == kStatus_Success) && (id[0] == 0xA0U))
{
found = true;
}
}
LPI2C_MasterDeinit(LPI2C1);
return found;
}
/* ------------------------------------------------------------------ */
int main(void)
{
BOARD_ConfigMPU();
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();
PRINTF("\r\n=== SGTL5000 bring-up test ===\r\n");
audio_pins_init();
audio_clocks_init();
sai_init();
/* Give the codec time with MCLK running before first I2C access */
SDK_DelayAtLeastUs(100000U, SystemCoreClock);
slow_bitbang_test();
lpi2c_scan();
bool ok = false;
for (int t = 1; (t <= 3) && !ok; t++)
{
PRINTF("-- try %d --\r\n", t);
ok = sgtl_read_chip_id();
if (!ok)
{
SDK_DelayAtLeastUs(1000000U, SystemCoreClock);
}
}
if (!ok)
{
PRINTF("SGTL5000 not answering. Stop.\r\n");
while (1)
{
}
}
if (CODEC_Init(&s_codec, &s_codecConfig) != kStatus_Success)
{
PRINTF("CODEC_Init FAILED\r\n");
while (1)
{
}
}
PRINTF("CODEC_Init OK\r\n");
CODEC_SetMute(&s_codec, CODEC_OUT_CH, false);
CODEC_SetVolume(&s_codec, CODEC_OUT_CH, 60U);
/* Build stereo tone buffer */
for (int i = 0; i < 16; i++)
{
s_tone[2 * i] = s_sineMono[i]; /* L */
s_tone[2 * i + 1] = s_sineMono[i]; /* R */
}
PRINTF("Playing 1 kHz tone on headphone / line out...\r\n");
while (1)
{
if ((SAI_TxGetStatusFlag(SAI1) & kSAI_FIFOErrorFlag) != 0U)
{
SAI_TxClearStatusFlags(SAI1, kSAI_FIFOErrorFlag);
SAI_TxSoftwareReset(SAI1, kSAI_ResetTypeFIFO);
}
SAI_WriteBlocking(SAI1, 0U, 16U, (uint8_t *)s_tone, sizeof(s_tone));
}
}