save sink EDID

This commit is contained in:
King Kévin 2022-07-11 14:53:15 +02:00
parent e345b61860
commit 82e5d984bc
1 changed files with 121 additions and 17 deletions

138
main.c
View File

@ -11,6 +11,7 @@
#include "softi2c_master.h"
#define EEPROM_ADDR 0x4000 // EEPROM start address
static bool eeprom_valid = true; // if the EDID can be read from EEPROM
// LED pin (sink for on)
#define LED_PORT GPIO_PA
#define LED_PIN PA3
@ -102,7 +103,7 @@ structure shall also meet the requirements of CEA-861-D.
* 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
*/
static uint16_t edid_length(uint8_t* edid)
static uint16_t edid_length(const uint8_t* edid)
{
puts("EDID check: ");
// check EDID 1.3/1.4 fixed pattern header
@ -144,6 +145,36 @@ static uint16_t edid_length(uint8_t* edid)
return 0;
}
// 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);
}
}
}
void main(void)
{
sim(); // disable interrupts (while we reconfigure them)
@ -250,23 +281,19 @@ we will only be able to provide 1 extension block since we can only respond to o
HPD_PORT->ODR.reg |= HPD_PIN; // pull up HPD line to indicate EDID is ready
}
// 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 (0 == (EDID_PORT->ODR.reg & EDID_PIN)) { // EDID switched on
// TODO copy EDID from sink/monitor
/*
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.
*/
}
rim(); // re-enable interrupts
// 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");
// read sink EDID
if (!i2c_fwd) { // ensure we are the only master on the I²C sink lines
// 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 {
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
@ -297,13 +324,81 @@ i2c_end:
if (i2c_success) {
puts("success\r\n");
const uint16_t edid_len = edid_length(edid_sink); // get length
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
}
}
}
} else {
puts("fail\r\n");
}
}
bool action = false; // if an action has been performed
LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate we are ready
puts("loop\r\n");
while (true) {
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
@ -337,7 +432,12 @@ void i2c(void) __interrupt(IRQ_I2C) // auto wakeup
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
I2C_DR = *(uint8_t*)(EEPROM_ADDR + addr++); // transmit next byte
// transmit next byte
if (eeprom_valid) {
I2C_DR = *(uint8_t*)(EEPROM_ADDR + addr++);
} else {
I2C_DR = 0xff;
}
}
if (sr1 & I2C_SR1_RXNE) { // receive buffer is full
const uint8_t data = I2C_DR; // read data (also clears flag)
@ -352,7 +452,11 @@ void i2c(void) __interrupt(IRQ_I2C) // auto wakeup
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
I2C_DR = *(uint8_t*)(EEPROM_ADDR + addr++); // transmit selected byte
if (eeprom_valid) {
I2C_DR = *(uint8_t*)(EEPROM_ADDR + addr++); // transmit selected byte
} else {
I2C_DR = 0xff;
}
i2c_input_new = false; // notify we send data
} else { // we will receive data
I2C_CR2 |= I2C_CR2_ACK; // ACK next received byte