firmware/misc/gpu/main.c
2023-12-14 10:20:23 -05:00

323 lines
9.3 KiB
C

/*
* (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
*/
#include "main.h"
#include "lcd.h"
#include "version.h"
#include <string.h>
// LL/CMSIS internal vars
uint32_t SystemCoreClock = 12000000UL;
#define MY_I2C_ADDR 0x65
// gpio_setup()
//
void
gpio_setup(void)
{
LL_GPIO_InitTypeDef init = {0};
// see external/stm32c0xx_hal_driver/Inc/stm32c0xx_ll_gpio.h
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
//LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_3);
// Setup input pins -- which is all!
// - make SPI stuff input at this point as well.
init.Pin = INPUT_PINS | SPI_PINS | SPI_CTRL_PINS;
init.Mode = LL_GPIO_MODE_INPUT;
init.Speed = LL_GPIO_SPEED_FREQ_HIGH;
init.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(GPIOA, &init);
// one output PIN: tells CPU we are controlling the bus
init.Pin = OUTPUT_PINS;
init.Mode = LL_GPIO_MODE_OUTPUT;
init.Speed = LL_GPIO_SPEED_FREQ_HIGH;
init.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(GPIOA, &init);
// clear output signal
LL_GPIO_ResetOutputPin(GPIOA, PIN_GPU_BUSY);
}
// i2c_setup()
//
void
i2c_setup(void)
{
// useful <https://github.com/STMicroelectronics/STM32CubeG0/blob/ae31d181b3244190d7d5bc0d91e66a82ce4270ce/Projects/NUCLEO-G031K8/Examples_LL/I2C/I2C_TwoBoards_MasterRx_SlaveTx_IT_Init/Src/main.c#L205>
LL_RCC_SetI2CClockSource(LL_RCC_I2C1_CLKSOURCE_PCLK1);
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB);
{
// setup pins PB6/7
LL_GPIO_InitTypeDef init = {0};
init.Pin = I2C_PINS;
init.Mode = LL_GPIO_MODE_ALTERNATE;
init.Speed = LL_GPIO_SPEED_FREQ_LOW;
init.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
init.Pull = LL_GPIO_PULL_NO;
init.Alternate = LL_GPIO_AF_6;
LL_GPIO_Init(GPIOB, &init);
}
{
LL_I2C_InitTypeDef init = {0};
init.PeripheralMode = LL_I2C_MODE_I2C;
// from MXCube w/ FastMode @ 12Mhz main micro
init.Timing = 0x00100413;
init.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE;
init.DigitalFilter = 0x0; // disabled
init.OwnAddress1 = MY_I2C_ADDR << 1;
init.TypeAcknowledge = LL_I2C_ACK;
init.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT;
LL_I2C_Init(I2C1, &init);
LL_I2C_EnableAutoEndMode(I2C1);
LL_I2C_SetOwnAddress2(I2C1, 0, LL_I2C_OWNADDRESS2_NOMASK);
LL_I2C_DisableOwnAddress2(I2C1);
LL_I2C_DisableGeneralCall(I2C1);
LL_I2C_EnableClockStretching(I2C1);
}
}
// enter_bootloader()
//
void
enter_bootloader(void)
{
// Force entry into bootloader on next reset
SET_BIT(FLASH->ACR, FLASH_ACR_PROGEMPTY);
}
// i2c_poll()
//
void
i2c_poll(void)
{
static uint8_t cmd, respLen, argLen;
static uint8_t args[8];
static const char *resp;
static bool isRead;
// do we have work from I2C port?
if(I2C1->ISR & I2C_ISR_ADDR) {
// we are selected; note DIR and ADDR
I2C1->ICR |= I2C_ICR_ADDRCF;
isRead = !!(I2C1->ISR & I2C_ISR_DIR);
// reset our state
if(!isRead) {
cmd = 0;
argLen = 0;
} else {
// during write to us, allow any length, but we might ignore stuff
CLEAR_BIT(I2C1->CR1, I2C_CR1_SBC); // start bit control BROKEN?
}
}
if(I2C1->ISR & I2C_ISR_STOPF) {
// master is done sending us bytes
I2C1->ICR |= I2C_ICR_STOPCF;
// Implement the command logic after STOP of the sending request
// - sending strings as zero-terminated
// - for other responses, master will need to know true length of response
if(!isRead) {
respLen = 0;
switch(cmd) {
case 'V': // full version
if(argLen != 0) goto bad_args;
resp = version_string;
respLen = strlen(version_string)+1;
break;
case 'v': // short version
if(argLen != 0) goto bad_args;
resp = RELEASE_VERSION;
respLen = strlen(RELEASE_VERSION)+1;
break;
case 'p': // ping
resp = (const char *)args;
respLen = argLen;
break;
case 'b': // enter bootloader (follow w/ hard reset)
if(argLen != 0) goto bad_args;
enter_bootloader();
resp = "OK";
respLen = 3;
break;
case 'c': // enable cursor: args=x,y,ctype
if(argLen != 3) goto bad_args;
lcd_state.activity_bar = false;
lcd_state.cursor_x = args[0];
lcd_state.cursor_y = args[1];
lcd_state.cursor_type = args[2];
lcd_state.cur_flash = false;
break;
case 'a': // disable cursor (implied: enable activity bar)
if(argLen != 0) goto bad_args;
lcd_state.activity_bar = true;
lcd_state.cursor_type = NO_CURSOR;
break;
case 't': // test feature: draw a single test pattern
if(argLen != 0) goto bad_args;
lcd_state.test_pattern = true;
lcd_state.activity_bar = false;
lcd_state.cursor_type = NO_CURSOR;
break;
case 0:
default:
resp = "Bad cmd?";
respLen = strlen(resp);
break;
bad_args:
resp = "Bad args?";
respLen = strlen(resp);
break;
}
// critical: flush old data
SET_BIT(I2C1->ISR, I2C_ISR_TXE);
// prepare for N byte response (respLen)
//BROKEN//SET_BIT(I2C1->CR1, I2C_CR1_SBC); // start bit control
//BROKEN//MODIFY_REG(I2C1->CR2, I2C_CR2_NBYTES_Msk, (respLen << I2C_CR2_NBYTES_Pos));
//BROKEN//SET_BIT(I2C1->CR2, I2C_CR2_RELOAD);
}
}
while(I2C1->ISR & I2C_ISR_RXNE) {
// master sent us a byte
uint8_t rx = I2C1->RXDR;
if(cmd == 0) {
cmd = rx;
} else {
if(argLen < sizeof(args)) {
args[argLen++] = rx;
}
}
}
while(isRead && (I2C1->ISR & I2C_ISR_TXIS)) {
// master wants to read a byte from us
if(respLen) {
// keep sending response
I2C1->TXDR = *resp;
resp++;
respLen--;
} else {
// send NACK -- Doesn't work...
//BROKEN//SET_BIT(I2C1->CR2, I2C_CR2_NACK);
//BROKEN//SET_BIT(I2C1->ISR, I2C_ISR_TXE);
// workaround: give it something
I2C1->TXDR = 0xff;
}
}
}
// clock_setup()
//
// Called from startup.S, before C runtime setup
//
void
clock_setup(void)
{
// Vector Table Relocation in Internal FLASH (see interrupts.c)
SCB->VTOR = FLASH_BASE;
// HSI configuration and activation
LL_RCC_HSI_Enable();
while(LL_RCC_HSI_IsReady() != 1)
;
LL_RCC_HSI_SetCalibTrimming(64);
LL_RCC_SetHSIDiv(LL_RCC_HSI_DIV_4);
LL_RCC_SetAHBPrescaler(LL_RCC_HCLK_DIV_1);
// Sysclk activation on the HSI
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI);
while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI)
;
// Set APB1 prescaler
LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
// WAS:
// LL_Init1msTick(12000000);
// LL_SetSystemCoreClock(12000000);
// but, this saves 296-324 bytes because it avoids a division that pulls in a math helper
//SysTick->LOAD = (uint32_t)((12000000 / 1000) - 1UL); // set reload register
SysTick->LOAD = 11999;
SysTick->VAL = 0; // Load the SysTick Counter Value
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_ENABLE_Msk; // Enable the Systick Timer
}
// mainloop()
//
// TODO: add naked attr and debug why that kills the code, or waste stack space forever
//
void __attribute__((noreturn))
mainloop(void)
{
// Reset & enable of all peripherals we are using.
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG | LL_APB2_GRP1_PERIPH_SPI1);
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR | LL_APB1_GRP1_PERIPH_I2C1);
// Our setup code.
gpio_setup();
lcd_setup();
i2c_setup();
// If we started ok, flash isn't empty and we don't need to force
// entry into bootloader anymore.
CLEAR_BIT(FLASH->ACR, FLASH_ACR_PROGEMPTY);
while(1) {
i2c_poll();
// G_CTRL must be low, and TEAR high, and if so we can write to LCD.
if(!LL_GPIO_IsInputPinSet(GPIOA, PIN_G_CTRL)
&& LL_GPIO_IsInputPinSet(GPIOA, PIN_TEAR)
) {
lcd_animate();
// wait until start of next frame before looking again
while(LL_GPIO_IsInputPinSet(GPIOA, PIN_TEAR)) {
i2c_poll();
}
}
}
}
// fatal_error()
//
void __attribute__((noreturn))
fatal_error(const char *msg)
{
while(1) ;
}
// EOF