2020-09-30 16:59:31 +02:00
|
|
|
/* firmware template for STM8S microcontroller
|
2022-07-11 10:38:47 +02:00
|
|
|
* Copyright (C) 2019-2022 King Kévin <kingkevin@cuvoodoo.info>
|
2020-09-30 16:59:31 +02:00
|
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdbool.h>
|
2021-07-21 23:39:08 +02:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
2020-09-30 16:59:31 +02:00
|
|
|
#include "stm8s.h"
|
2021-07-21 23:39:08 +02:00
|
|
|
#include "main.h"
|
2022-07-11 12:25:48 +02:00
|
|
|
#include "softi2c_master.h"
|
2021-07-21 23:39:08 +02:00
|
|
|
|
2022-07-11 10:38:47 +02:00
|
|
|
#define EEPROM_ADDR 0x4000 // EEPROM start address
|
2022-07-11 14:53:15 +02:00
|
|
|
static bool eeprom_valid = true; // if the EDID can be read from EEPROM
|
2022-07-11 10:38:47 +02:00
|
|
|
// LED pin (sink for on)
|
|
|
|
#define LED_PORT GPIO_PA
|
|
|
|
#define LED_PIN PA3
|
|
|
|
// pull ups for device facing I²C port
|
|
|
|
#define SDA_PU_PORT GPIO_PC
|
|
|
|
#define SDA_PU_PIN PC3
|
|
|
|
#define SCL_PU_PORT GPIO_PC
|
|
|
|
#define SCL_PU_PIN PC4
|
2022-07-11 10:56:03 +02:00
|
|
|
static bool i2c_fwd = false; // if the I²C source lines are connected to sink
|
2022-07-11 10:38:47 +02:00
|
|
|
// external EEPROM write protect pin
|
|
|
|
#define WP_PORT GPIO_PC
|
|
|
|
#define WP_PIN PC5
|
|
|
|
// hot plug detect pull up
|
|
|
|
#define HPD_PORT GPIO_PC
|
|
|
|
#define HPD_PIN PC6
|
2022-07-11 12:26:28 +02:00
|
|
|
static bool hpd_fwd = false; // if the I²C source line is connected to sink
|
2022-07-11 10:38:47 +02:00
|
|
|
// copy EDID setting
|
|
|
|
#define EDID_PORT GPIO_PC
|
|
|
|
#define EDID_PIN PC7
|
2020-09-30 16:59:31 +02:00
|
|
|
|
2022-07-11 10:38:47 +02:00
|
|
|
// if an I²C transaction started
|
|
|
|
static volatile bool i2c_transaction_new = false;
|
|
|
|
// if an I²C transaction with new data started
|
|
|
|
static volatile bool i2c_input_new = false;
|
2020-09-30 16:59:31 +02:00
|
|
|
|
|
|
|
// blocking wait (in 10 us steps, up to UINT32_MAX / 10)
|
|
|
|
static void wait_10us(uint32_t us10)
|
|
|
|
{
|
|
|
|
us10 = ((us10 / (1 << CLK->CKDIVR.fields.HSIDIV)) * 1000) / 206; // calibrated for 1 ms
|
2021-08-17 08:47:46 +02:00
|
|
|
while (us10--); // burn energy
|
2020-09-30 16:59:31 +02:00
|
|
|
}
|
|
|
|
|
2022-07-11 10:39:09 +02:00
|
|
|
void putc(char c)
|
|
|
|
{
|
|
|
|
while (!UART1->SR.fields.TXE); // wait until TX buffer is empty
|
|
|
|
UART1->DR.reg = c; // put character in buffer to be transmitted
|
|
|
|
// don't wait until the transmission is complete
|
|
|
|
}
|
|
|
|
|
|
|
|
void puts(const char* s)
|
|
|
|
{
|
|
|
|
if (NULL == s) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
while (*s) {
|
|
|
|
putc(*s++);
|
|
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void putn(uint8_t n)
|
|
|
|
{
|
|
|
|
n &= 0x0f; // ensure it's a nibble
|
|
|
|
if (n < 0xa) {
|
|
|
|
putc('0' + n);
|
|
|
|
} else {
|
|
|
|
putc('a' + (n - 0x0a));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void puth(uint8_t h)
|
|
|
|
{
|
|
|
|
putn(h >> 4);
|
|
|
|
putn(h & 0x0f);
|
|
|
|
}
|
|
|
|
|
2022-07-11 12:58:01 +02:00
|
|
|
// verify (E-)EDID checksums
|
|
|
|
// the sum of the bytes (including checksum at the end) must be 0
|
|
|
|
static bool checksum_ok(const uint8_t* data, uint16_t length)
|
|
|
|
{
|
|
|
|
uint8_t checksum = 0;
|
|
|
|
for (uint16_t i = 0; i < length; i++) {
|
|
|
|
checksum += data[i];
|
|
|
|
}
|
|
|
|
return (0 == checksum);
|
|
|
|
}
|
|
|
|
|
|
|
|
// return if the current EDID is valid
|
|
|
|
/* from HDMI 1.3a specification
|
|
|
|
* All Sinks shall contain an CEA-861-D compliant E-EDID data structure.
|
|
|
|
* A Source shall read the EDID 1.3 and first CEA Extension to determine the capabilities supported
|
|
|
|
by the Sink.
|
|
|
|
* The first 128 bytes of the E-EDID shall contain an EDID 1.3 structure. The contents of this
|
|
|
|
structure shall also meet the requirements of CEA-861-D.
|
|
|
|
*
|
|
|
|
* HDMI 2.1 uses CTA-861-G
|
|
|
|
* this uses EDID 1.4 structure
|
|
|
|
* EDID 1.3/1.4 is 128 bytes long, and can point to 128 bytes extension
|
|
|
|
* EDID 2.0 with its 256 bytes does not seem to be used in HDMI at all
|
|
|
|
* DisplayID with its variable-length structure meant to replace E-EDID only seems to be used in DisplayPort
|
|
|
|
*/
|
2022-07-11 14:53:15 +02:00
|
|
|
static uint16_t edid_length(const uint8_t* edid)
|
2022-07-11 12:58:01 +02:00
|
|
|
{
|
|
|
|
puts("EDID check: ");
|
|
|
|
// check EDID 1.3/1.4 fixed pattern header
|
|
|
|
if (0x00 != edid[0] || 0xff != edid[1] || 0xff != edid[2] || 0xff != edid[3] || 0xff != edid[4] || 0xff != edid[5] || 0xff != edid[6] || 0x00 != edid[7]) {
|
|
|
|
puts("invalid header\r\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (1 == edid[18]) { // EDID 1.3/1.4 128-byte structure
|
|
|
|
if (checksum_ok(&edid[0], 128)) {
|
|
|
|
if (0 == edid[126]) { // no extension
|
|
|
|
puts("128 bytes\r\n");
|
|
|
|
return 128;
|
|
|
|
} else { // extension available
|
|
|
|
// the usual extension is CEA EDID Timing Extension (with extension tag 02), but we allow others
|
|
|
|
// no idea how more than 1 extension is supported
|
|
|
|
if (checksum_ok(&edid[128], 128)) {
|
|
|
|
puts("256 bytes (with extension)\r\n");
|
|
|
|
return 256;
|
|
|
|
} else {
|
|
|
|
puts("extension CRC error\r\n");
|
|
|
|
return 0; // EDID is broken
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
puts("CRC error\r\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} else if (2 == edid[18]) { // EDID 2.0 256-byte structure
|
|
|
|
if (checksum_ok(&edid[0], 256)) {
|
|
|
|
puts("256 bytes (no extension)\r\n");
|
|
|
|
return 256;
|
|
|
|
} else {
|
|
|
|
puts("CRC error\r\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-07-11 14:53:15 +02:00
|
|
|
// modify EDID to indicate firewall
|
|
|
|
static void edid_modify(uint8_t* edid)
|
|
|
|
{
|
|
|
|
// modify EDID to include the character indicating the firewall
|
|
|
|
const char firewall_indicator = '|'; // pipe/wall character to indicate the firewall
|
|
|
|
// ensure descriptor 4 is for Display name
|
|
|
|
if ((0 == edid[108]) && (0 == edid[109]) && (0 == edid[110]) && (0xfc == edid[111]) && (0 == edid[112])) { // ensure descriptor 4 is for Display name
|
|
|
|
uint8_t last_c; // position of last character
|
|
|
|
for (last_c = 113; last_c < 126 && edid[last_c] != '\n'; last_c++); // find position for inserting our character
|
|
|
|
if (firewall_indicator != edid[last_c - 1]) { // the last character is not yet the pipe
|
|
|
|
if (last_c > 125) { // ensure we insert as the last possible character
|
|
|
|
last_c = 125;
|
|
|
|
}
|
|
|
|
edid[last_c++] = firewall_indicator; // insert pipe
|
|
|
|
if (last_c < 126) {
|
|
|
|
edid[last_c++] = '\n'; // insert LF to terminate string
|
|
|
|
}
|
|
|
|
while (last_c < 126) {
|
|
|
|
edid[last_c++] = ' '; // insert padding space
|
|
|
|
}
|
|
|
|
// calculate new checksum
|
|
|
|
uint8_t checksum = 0;
|
|
|
|
for (uint8_t i = 0; i < 127; i++) {
|
|
|
|
checksum += edid[i];
|
|
|
|
}
|
|
|
|
edid[127] = (256 - checksum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-30 16:59:31 +02:00
|
|
|
void main(void)
|
|
|
|
{
|
|
|
|
sim(); // disable interrupts (while we reconfigure them)
|
|
|
|
|
|
|
|
CLK->CKDIVR.fields.HSIDIV = CLK_CKDIVR_HSIDIV_DIV0; // don't divide internal 16 MHz clock
|
|
|
|
CLK->CKDIVR.fields.CPUDIV = CLK_CKDIVR_CPUDIV_DIV0; // don't divide CPU frequency to 16 MHz
|
|
|
|
while (!CLK->ICKR.fields.HSIRDY); // wait for internal oscillator to be ready
|
|
|
|
|
2022-07-11 10:38:47 +02:00
|
|
|
// configure LED
|
|
|
|
LED_PORT->DDR.reg |= LED_PIN; // switch pin to output
|
|
|
|
LED_PORT->CR1.reg &= ~LED_PIN; // use in open-drain mode
|
|
|
|
LED_PORT->ODR.reg |= LED_PIN; // switch LED off
|
|
|
|
|
2020-09-30 16:59:31 +02:00
|
|
|
// configure auto-wakeup (AWU) to be able to refresh the watchdog
|
|
|
|
// 128 kHz LSI used by default in option bytes CKAWUSEL
|
|
|
|
// we skip measuring the LS clock frequency since there is no need to be precise
|
|
|
|
AWU->TBR.fields.AWUTB = 10; // interval range: 128-256 ms
|
|
|
|
AWU->APR.fields.APR = 0x3e; // set time to 256 ms
|
|
|
|
AWU_CSR |= AWU_CSR_AWUEN; // enable AWU (start only when entering wait or active halt mode)
|
|
|
|
|
|
|
|
// configure independent watchdog (very loose, just it case the firmware hangs)
|
|
|
|
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
|
|
IWDG->KR.fields.KEY = IWDG_KR_KEY_ENABLE; // start watchdog
|
|
|
|
IWDG->KR.fields.KEY = IWDG_KR_KEY_ACCESS; // allows changing the prescale
|
|
|
|
IWDG->PR.fields.PR = IWDG_PR_DIV256; // set prescale to longest time (1.02s)
|
|
|
|
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
|
|
|
2022-07-11 10:38:47 +02:00
|
|
|
// configure UART for debug output
|
|
|
|
UART1->CR1.fields.M = 0; // 8 data bits
|
|
|
|
UART1->CR3.fields.STOP = 0; // 1 stop bit
|
|
|
|
UART1->BRR2.reg = 0x0B; // set baud rate to 115200 (at 16 MHz)
|
|
|
|
UART1->BRR1.reg = 0x08; // set baud rate to 115200 (at 16 MHz)
|
|
|
|
UART1->CR2.fields.TEN = 1; // enable TX
|
|
|
|
|
|
|
|
// configure I²C pull-ups
|
|
|
|
SDA_PU_PORT->DDR.reg |= SDA_PU_PIN; // switch pin to output
|
2022-07-11 10:56:03 +02:00
|
|
|
SDA_PU_PORT->CR1.reg &= ~SDA_PU_PIN; // switch pin to open-drain
|
|
|
|
SDA_PU_PORT->ODR.reg &= ~SDA_PU_PIN; // pull SDA line low
|
|
|
|
wait_10us(0); // wait shortly to clear tristate
|
|
|
|
SDA_PU_PORT->DDR.reg &= ~SDA_PU_PIN; // switch pin to input (already floating)
|
|
|
|
if (SDA_PU_PORT->IDR.reg & SDA_PU_PIN) { // line is pulled up
|
|
|
|
i2c_fwd = true; // remember the I²C line(s) are forwarded
|
|
|
|
}
|
2022-07-11 10:38:47 +02:00
|
|
|
SCL_PU_PORT->DDR.reg |= SCL_PU_PIN; // switch pin to output
|
2022-07-11 10:56:03 +02:00
|
|
|
SCL_PU_PORT->CR1.reg &= ~SCL_PU_PIN; // switch pin to open-drain
|
|
|
|
SCL_PU_PORT->ODR.reg &= ~SCL_PU_PIN; // pull SDA line low
|
|
|
|
wait_10us(0); // wait shortly to clear tristate
|
|
|
|
SCL_PU_PORT->DDR.reg &= ~SCL_PU_PIN; // switch pin to input (already floating)
|
|
|
|
if (SCL_PU_PORT->IDR.reg & SCL_PU_PIN) { // line is pulled up
|
|
|
|
i2c_fwd = true; // remember the I²C line(s) are forwarded
|
|
|
|
}
|
|
|
|
if (!i2c_fwd) { // I²C lines are not pulled up
|
|
|
|
SDA_PU_PORT->DDR.reg |= SDA_PU_PIN; // switch pin to output
|
|
|
|
SDA_PU_PORT->CR1.reg |= SDA_PU_PIN; // switch pin to push pull
|
|
|
|
SDA_PU_PORT->ODR.reg |= SDA_PU_PIN; // pull up SDA line
|
|
|
|
SCL_PU_PORT->DDR.reg |= SCL_PU_PIN; // switch pin to output
|
|
|
|
SCL_PU_PORT->CR1.reg |= SCL_PU_PIN; // switch pin to push pull
|
|
|
|
SCL_PU_PORT->ODR.reg |= SCL_PU_PIN; // pull up SCL line
|
|
|
|
}
|
2022-07-11 10:38:47 +02:00
|
|
|
|
|
|
|
// configure I²C
|
2022-07-11 10:56:03 +02:00
|
|
|
if (!i2c_fwd) { // we will act as I²C slave EEPROM
|
2022-07-11 12:26:28 +02:00
|
|
|
/*
|
|
|
|
HDMI 1.3 spec:
|
|
|
|
The Sink shall be capable of responding with EDID 1.3 data and up to 255 extension blocks, each 128 bytes long (up to 32K bytes total E-EDID memory) whenever the Hot Plug Detect signal is asserted.
|
|
|
|
The Sink should be capable of providing E-EDID information over the Enhanced DDC channel whenever the +5V Power signal is provided. This should be available within 20msec after the +5V Power signal is provided.
|
|
|
|
|
|
|
|
firewall:
|
|
|
|
we will only be able to provide 1 extension block since we can only respond to one I²C address 0x50 for the EDID, and not to the 0x30 Segment Pointer (for the other pages/extension blocks)
|
|
|
|
*/
|
2022-07-11 10:56:03 +02:00
|
|
|
GPIO_PB->CR1.reg |= (PB4 | PB5); // enable internal pull-up on SCL/SDA
|
|
|
|
GPIO_PB->DDR.reg &= ~(PB4 | PB5); // set SCL/SDA as input before it is used as alternate function by the peripheral
|
|
|
|
I2C_CR1 |= I2C_CR1_PE; // enable I²C peripheral (must be done before any other register is written)
|
|
|
|
I2C_CR2 |= I2C_CR2_STOP; // release lines
|
|
|
|
I2C_CR2 |= I2C_CR2_SWRST; // reset peripheral, in case we got stuck and the dog bit
|
|
|
|
while (0 == (GPIO_PB->IDR.reg & PB4)); // wait for SCL line to be released
|
|
|
|
while (0 == (GPIO_PB->IDR.reg & PB5)); // wait for SDA line to be released
|
|
|
|
I2C_CR2 &= ~I2C_CR2_SWRST; // release reset
|
|
|
|
I2C_CR1 |= I2C_CR1_PE; // re-enable I²C peripheral
|
|
|
|
I2C_FREQR = 16; // the peripheral frequency is 4 MHz (must match CPU frequency)
|
|
|
|
I2C_CR2 |= I2C_CR2_ACK; // enable acknowledgement if address matches
|
|
|
|
// since we are slave and not master, we don't have to set CCR
|
|
|
|
I2C_OARL = (0x50U << 1U); // set slave address for EEPROM
|
|
|
|
I2C_ITR |= (I2C_ITR_ITBUFEN | I2C_ITR_ITEVTEN); // enable buffer and event interrupts
|
|
|
|
}
|
2022-07-11 10:38:47 +02:00
|
|
|
|
2022-07-11 12:26:28 +02:00
|
|
|
// configure external EEPROM (actually not used)
|
|
|
|
WP_PORT->DDR.reg |= WP_PIN; // switch pin to output
|
|
|
|
WP_PORT->CR1.reg &= ~WP_PIN; // switch pin to open drain
|
|
|
|
WP_PORT->ODR.reg |= WP_PIN; // let write protect pulled up
|
|
|
|
|
2022-07-11 10:38:47 +02:00
|
|
|
// configure hot plug detect
|
|
|
|
HPD_PORT->DDR.reg |= HPD_PIN; // switch pin to output
|
2022-07-11 12:26:28 +02:00
|
|
|
HPD_PORT->CR1.reg &= ~HPD_PIN; // switch pin to open drain
|
|
|
|
HPD_PORT->ODR.reg &= ~HPD_PIN; // drain line
|
|
|
|
wait_10us(0); // wait shortly to clear tristate
|
|
|
|
HPD_PORT->DDR.reg &= ~HPD_PIN; // switch pin to input (already floating)
|
|
|
|
if (HPD_PORT->IDR.reg & HPD_PIN) { // line is pulled up
|
|
|
|
hpd_fwd = true; // remember the HPD line is pulled up
|
|
|
|
}
|
|
|
|
if (!hpd_fwd) { // we have to pull up
|
|
|
|
HPD_PORT->DDR.reg |= HPD_PIN; // switch pin to output
|
|
|
|
HPD_PORT->CR1.reg |= HPD_PIN; // switch pin to push pull
|
|
|
|
HPD_PORT->ODR.reg |= HPD_PIN; // pull up HPD line to indicate EDID is ready
|
|
|
|
}
|
2022-07-11 10:38:47 +02:00
|
|
|
|
2020-09-30 16:59:31 +02:00
|
|
|
rim(); // re-enable interrupts
|
2022-07-11 12:25:48 +02:00
|
|
|
// even if we don't pull up HPD ourself, we should be able to respond to I²C within 20 ms
|
|
|
|
puts("I²C ready\r\n");
|
|
|
|
|
2022-07-11 14:53:15 +02:00
|
|
|
// check if EDID should be copied
|
|
|
|
EDID_PORT->DDR.reg &= ~EDID_PIN; // switch pin to output
|
|
|
|
EDID_PORT->CR1.reg |= EDID_PIN; // enable pull up
|
|
|
|
if (EDID_PORT->IDR.reg & EDID_PIN) { // EDID switched off
|
|
|
|
puts("EDID protected\r\n");
|
|
|
|
} else if (i2c_fwd) { // we are not the only master on the I²C sink lines
|
|
|
|
puts("can't read EDID: I²C lines forwarded\r\n");
|
|
|
|
LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate error
|
|
|
|
} else {
|
2022-07-11 12:25:48 +02:00
|
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
|
|
puts("reading sink EDID: ");
|
|
|
|
softi2c_master_setup(100); // start the I²C master to talk to sink
|
|
|
|
bool i2c_success = false; // if the complete read succeeded
|
|
|
|
if (!softi2c_master_start()) { // start transaction
|
|
|
|
goto i2c_end;
|
|
|
|
}
|
|
|
|
if (!softi2c_master_select_slave(0x50, true)) { // select EDID
|
|
|
|
goto i2c_end;
|
|
|
|
}
|
|
|
|
const uint8_t edid_addr = 0x00; // EDID address to read
|
|
|
|
if (!softi2c_master_write(&edid_addr, 1)) { // write address to read
|
|
|
|
goto i2c_end;
|
|
|
|
}
|
|
|
|
if (!softi2c_master_start()) { // re-start transaction
|
|
|
|
goto i2c_end;
|
|
|
|
}
|
|
|
|
if (!softi2c_master_select_slave(0x50, false)) { // re-select EDID
|
|
|
|
goto i2c_end;
|
|
|
|
}
|
|
|
|
uint8_t edid_sink[256]; // buffer for sink EDID
|
|
|
|
if (!softi2c_master_read(edid_sink, ARRAY_LENGTH(edid_sink))) { // read EDID
|
|
|
|
goto i2c_end;
|
|
|
|
}
|
|
|
|
i2c_success = true; // complete read succeeded
|
|
|
|
i2c_end:
|
|
|
|
softi2c_master_stop();
|
|
|
|
if (i2c_success) {
|
|
|
|
puts("success\r\n");
|
2022-07-11 12:58:01 +02:00
|
|
|
const uint16_t edid_len = edid_length(edid_sink); // get length
|
2022-07-11 14:53:15 +02:00
|
|
|
if (edid_len) { // EDID is valid
|
|
|
|
edid_modify(edid_sink); // modify EDID to include firewall indication
|
|
|
|
// compare saved/source and sink EDID
|
|
|
|
bool edid_equal = true;
|
|
|
|
for (uint16_t i = 0; i < edid_len && edid_equal; i++) {
|
|
|
|
if (*(uint8_t*)(EEPROM_ADDR + i) != edid_sink[i]) {
|
|
|
|
edid_equal = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (edid_equal) {
|
|
|
|
puts("EDID not changed\r\n");
|
|
|
|
} else {
|
|
|
|
LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate we are saving EDID
|
|
|
|
puts("saving EDID: ");
|
|
|
|
// note: the STM8S103 does not support RWW
|
|
|
|
// try to make fast small operations to not stall I²C communication
|
|
|
|
eeprom_valid = false; // invalidate saved EDID while re-programming it
|
|
|
|
// disable DATA (e.g. EEPROM) write protection
|
|
|
|
if (0 == (FLASH_IAPSR & FLASH_IAPSR_DUL)) {
|
|
|
|
FLASH_DUKR = FLASH_DUKR_KEY1;
|
|
|
|
FLASH_DUKR = FLASH_DUKR_KEY2;
|
|
|
|
}
|
|
|
|
// erase EEPROM (we don't do faster block erase since it needs to be done from RAM)
|
|
|
|
for (uint16_t i = 0; i < 256; i += 4U) { // go through word
|
|
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
|
|
FLASH_CR2 |= FLASH_CR2_WPRG; // set word programming
|
|
|
|
FLASH_NCR2 &= ~FLASH_NCR2_NWPRG; // set word programming
|
|
|
|
*(uint32_t*)(EEPROM_ADDR + i) = 0; // erase word
|
|
|
|
while (FLASH_CR2 & FLASH_CR2_WPRG); // wait for erase to complete
|
|
|
|
// check if programming failed
|
|
|
|
// we don't check for WR_PG_DIS (while checking EOP) because EEPROM isn't (and can't be) write protected
|
|
|
|
if (!(FLASH_IAPSR & FLASH_IAPSR_EOP)) {
|
|
|
|
FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection
|
|
|
|
puts("failed\r\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// save need EDID
|
|
|
|
for (uint16_t i = 0; i < edid_len; i += 4U) { // go through word
|
|
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
|
|
FLASH_CR2 |= FLASH_CR2_WPRG; // set word programming
|
|
|
|
FLASH_NCR2 &= ~FLASH_NCR2_NWPRG; // set word programming
|
|
|
|
*(uint32_t*)(EEPROM_ADDR + i) = *(uint32_t*)(&edid_sink[i]); // write word
|
|
|
|
while (FLASH_CR2 & FLASH_CR2_WPRG); // wait for write to complete
|
|
|
|
// check if programming failed
|
|
|
|
// we don't check for WR_PG_DIS (while checking EOP) because EEPROM isn't (and can't be) write protected
|
|
|
|
if (!(FLASH_IAPSR & FLASH_IAPSR_EOP)) {
|
|
|
|
FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection
|
|
|
|
puts("failed\r\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection
|
|
|
|
eeprom_valid = true; // re-enable EDID reading
|
|
|
|
LED_PORT->ODR.reg |= LED_PIN; // switch LED off to indicate we are completed saving EDID
|
|
|
|
puts("done\r\n");
|
|
|
|
// indicate there is a new EDID
|
|
|
|
if (!hpd_fwd) { // we have to pull up
|
|
|
|
/*
|
|
|
|
HDMI v1.3 spec:
|
|
|
|
An HDMI Sink shall indicate any change to the contents of the E-EDID by driving a low voltage
|
|
|
|
level pulse on the Hot Plug Detect pin. This pulse shall be at least 100 msec.
|
|
|
|
*/
|
|
|
|
HPD_PORT->ODR.reg &= ~HPD_PIN; // pull down HPD line to indicate EDID change
|
|
|
|
wait_10us(110 * 100); // wait over 100 ms
|
|
|
|
HPD_PORT->ODR.reg |= HPD_PIN; // pull up HPD line to indicate EDID is ready
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-11 12:25:48 +02:00
|
|
|
} else {
|
|
|
|
puts("fail\r\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-30 16:59:31 +02:00
|
|
|
bool action = false; // if an action has been performed
|
2022-07-11 12:25:48 +02:00
|
|
|
puts("loop\r\n");
|
2020-09-30 16:59:31 +02:00
|
|
|
while (true) {
|
|
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
2022-07-11 10:38:47 +02:00
|
|
|
if (i2c_transaction_new) { // an I²C transaction started
|
|
|
|
i2c_transaction_new = false; // clear flag
|
|
|
|
if (i2c_input_new) {
|
|
|
|
puts("I²C write\r\n");
|
|
|
|
} else {
|
|
|
|
puts("I²C read\r\n");
|
|
|
|
}
|
|
|
|
}
|
2020-09-30 16:59:31 +02:00
|
|
|
if (action) { // something has been performed, check if other flags have been set meanwhile
|
|
|
|
action = false; // clear flag
|
|
|
|
} else { // nothing down
|
|
|
|
wfi(); // go to sleep (wait for any interrupt, including periodic AWU)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void awu(void) __interrupt(IRQ_AWU) // auto wakeup
|
|
|
|
{
|
|
|
|
volatile uint8_t awuf = AWU_CSR; // clear interrupt flag by reading it (reading is required, and volatile prevents compiler optimization)
|
|
|
|
// let the main loop kick the dog
|
|
|
|
}
|
2022-07-11 10:38:47 +02:00
|
|
|
|
|
|
|
void i2c(void) __interrupt(IRQ_I2C) // auto wakeup
|
|
|
|
{
|
|
|
|
static uint8_t addr = 0; // EEPROM address to read
|
|
|
|
// make copies of status registers, since some bits might be cleared meanwhile
|
|
|
|
const uint8_t sr1 = I2C_SR1;
|
|
|
|
const uint8_t sr2 = I2C_SR2;
|
|
|
|
const uint8_t sr3 = I2C_SR3; // clears ADDR after reading SR1
|
|
|
|
if (sr1 & I2C_SR1_TXE) { // transmission buffer is empty
|
2022-07-11 14:53:15 +02:00
|
|
|
// transmit next byte
|
|
|
|
if (eeprom_valid) {
|
|
|
|
I2C_DR = *(uint8_t*)(EEPROM_ADDR + addr++);
|
|
|
|
} else {
|
|
|
|
I2C_DR = 0xff;
|
|
|
|
}
|
2022-07-11 10:38:47 +02:00
|
|
|
}
|
|
|
|
if (sr1 & I2C_SR1_RXNE) { // receive buffer is full
|
|
|
|
const uint8_t data = I2C_DR; // read data (also clears flag)
|
|
|
|
if (I2C_CR2 & I2C_CR2_ACK) {
|
|
|
|
addr = data; // we only take the first address byte
|
|
|
|
}
|
|
|
|
I2C_CR2 &= I2C_CR2_ACK; // NACK next received byte
|
|
|
|
}
|
|
|
|
if (sr1 & I2C_SR1_STOPF) { // stop received
|
|
|
|
I2C_CR2 |= I2C_CR2_ACK; // this is just to clear the flag
|
|
|
|
}
|
|
|
|
if (sr1 & I2C_SR1_ADDR) { // our slave address has been selected
|
|
|
|
i2c_transaction_new = true; // notify main loop transaction started
|
|
|
|
if (sr3 & I2C_SR3_TRA) { // start data transmission
|
2022-07-11 14:53:15 +02:00
|
|
|
if (eeprom_valid) {
|
|
|
|
I2C_DR = *(uint8_t*)(EEPROM_ADDR + addr++); // transmit selected byte
|
|
|
|
} else {
|
|
|
|
I2C_DR = 0xff;
|
|
|
|
}
|
2022-07-11 10:38:47 +02:00
|
|
|
i2c_input_new = false; // notify we send data
|
|
|
|
} else { // we will receive data
|
|
|
|
I2C_CR2 |= I2C_CR2_ACK; // ACK next received byte
|
|
|
|
i2c_input_new = true; // notify we get data
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (sr1 & I2C_SR1_BTF) { // byte transfer finished (only set when stretching has been enabled)
|
|
|
|
// cleared by reading/writing from/to DR or when stop is received
|
|
|
|
}
|
|
|
|
if (sr2 & I2C_SR2_AF) { // NACK received (e.g. end of read transaction)
|
|
|
|
I2C_SR2 &= ~I2C_SR2_AF; // clear flag
|
|
|
|
}
|
|
|
|
}
|