diff --git a/main.c b/main.c index 9d891ea..2dfda61 100644 --- a/main.c +++ b/main.c @@ -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