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
2022-08-05 12:43:52 +02:00
* for HDMI firewall v2 .37
2020-09-30 16:59:31 +02:00
*/
# 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-08-06 10:26:55 +02:00
// enable UART debug
2022-12-07 11:17:45 +01:00
# define DEBUG 1
2022-08-06 10:26:55 +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
2022-07-11 18:44:51 +02:00
// I²C pins (sink and source)
# define SDA_SRC_PORT GPIO_PB
# define SDA_SRC_PIN PB5
# define SCL_SRC_PORT GPIO_PB
# define SCL_SRC_PIN PB4
# define SDA_SRC_PU_PORT GPIO_PC
# define SDA_SRC_PU_PIN PC3
# define SCL_SRC_PU_PORT GPIO_PC
# define SCL_SRC_PU_PIN PC4
# define SDA_SNK_PORT GPIO_PD
# define SDA_SNK_PIN PD2
# define SCL_SNK_PORT GPIO_PD
# define SCL_SNK_PIN PD3
# define SDA_SNK_PU_PORT GPIO_PD
2022-08-05 12:43:52 +02:00
# define SDA_SNK_PU_PIN PD6
# define SCL_SNK_PU_PORT GPIO_PC
# define SCL_SNK_PU_PIN PC5
2022-07-11 18:44:51 +02:00
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
// 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 ;
2022-08-19 15:56:00 +02:00
// if an I²C transaction with incoming data started
2022-07-11 10:38:47 +02:00
static volatile bool i2c_input_new = false ;
2022-08-19 15:56:00 +02:00
// if the byte should be programmed
static volatile bool i2c_prog = false ;
// the address of the byte to be read or programmed
static volatile uint8_t i2c_addr = 0 ;
// the data to be programmed
static volatile uint8_t i2c_data = 0 ;
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 )
{
2022-08-06 10:26:55 +02:00
( void ) c ;
IWDG_KR = IWDG_KR_KEY_REFRESH ; // reset watchdog
# if DEBUG
2022-07-11 10:39:09 +02:00
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
2022-08-06 10:26:55 +02:00
# endif
2022-07-11 10:39:09 +02:00
}
2022-08-06 10:26:55 +02:00
void puts ( const char * s )
2022-07-11 10:39:09 +02:00
{
if ( NULL = = s ) {
return ;
}
while ( * s ) {
putc ( * s + + ) ;
}
}
void putn ( uint8_t n )
{
n & = 0x0f ; // ensure it's a nibble
if ( n < 0xa ) {
2022-08-06 10:26:55 +02:00
n + = ' 0 ' ;
2022-07-11 10:39:09 +02:00
} else {
2022-08-06 10:26:55 +02:00
n = ' a ' + ( n - 0x0a ) ;
2022-07-11 10:39:09 +02:00
}
2022-08-06 10:26:55 +02:00
putc ( n ) ;
2022-07-11 10:39:09 +02:00
}
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 ) ;
}
2022-08-05 12:44:52 +02:00
// return if the current EDID its length (including extensions), or 0 if invalid
2022-07-11 12:58:01 +02:00
/* from HDMI 1.3a specification
* All Sinks shall contain an CEA - 861 - D compliant E - EDID data structure .
2022-08-05 12:44:52 +02:00
* 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 .
2022-07-11 12:58:01 +02:00
*
* 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
2022-08-05 12:44:52 +02:00
* EDID 2.0 with its 256 bytes does not seem to be used in HDMI at all ( and is deprecated , replaced by E - EDID )
2022-07-11 12:58:01 +02:00
* 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
2022-08-05 12:44:52 +02:00
if ( checksum_ok ( & edid [ 0 ] , 128 ) ) { // ensure checksum of base EDID is ok
2022-12-07 11:18:18 +01:00
const uint16_t length = 128 + edid [ 126 ] * 128 ; // get length with all extensions
puts ( " (v1.3/1.4, " ) ;
2022-08-05 12:44:52 +02:00
putn ( edid [ 126 ] / 100 ) ;
putn ( ( edid [ 126 ] / 10 ) % 10 ) ;
putn ( edid [ 126 ] % 10 ) ;
puts ( " extension) " ) ;
2022-12-07 11:18:18 +01:00
if ( edid [ 126 ] ) { // extensions are present
for ( uint16_t i = 128 ; i < length & & i < 256 ; i + = 128 ) { // verify checksum of each extension (we actually only support one extension)
if ( ! checksum_ok ( & edid [ i ] , 128 ) ) {
puts ( " extension CRC error \r \n " ) ;
return 0 ; // EDID is broken
}
2022-07-11 12:58:01 +02:00
}
}
2022-12-07 11:18:18 +01:00
puts ( " CRC OK \r \n " ) ;
2022-08-05 12:44:52 +02:00
return length ;
2022-07-11 12:58:01 +02:00
} else {
2022-08-05 12:44:52 +02:00
puts ( " base CRC error \r \n " ) ;
2022-07-11 12:58:01 +02:00
return 0 ;
}
} else if ( 2 = = edid [ 18 ] ) { // EDID 2.0 256-byte structure
if ( checksum_ok ( & edid [ 0 ] , 256 ) ) {
2022-08-05 12:44:52 +02:00
puts ( " (v2) CRC ok \r \n " ) ;
2022-07-11 12:58:01 +02:00
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
2022-08-05 12:45:30 +02:00
// ensure we only have up to one extension
if ( edid [ 126 ] > 1 ) {
edid [ 126 ] = 1 ;
}
2022-08-05 15:14:37 +02:00
for ( uint8_t i = 0 ; i < 4 ; i + + ) { // go through descriptors
if ( ( 0 ! = edid [ 54 + i * 18 + 0 ] ) | | ( 0 ! = edid [ 54 + i * 18 + 1 ] ) | | ( 0 ! = edid [ 54 + i * 18 + 2 ] ) | | ( 0xfc ! = edid [ 54 + i * 18 + 3 ] ) | | ( 0 ! = edid [ 54 + i * 18 + 4 ] ) ) { // ensure descriptor is for Display name
continue ;
}
2022-07-11 14:53:15 +02:00
uint8_t last_c ; // position of last character
2022-08-05 15:14:37 +02:00
for ( last_c = 54 + i * 18 + 5 ; last_c < 54 + i * 18 + 18 & & edid [ last_c ] ! = ' \n ' ; last_c + + ) ; // find position for inserting our character
2022-07-11 14:53:15 +02:00
if ( firewall_indicator ! = edid [ last_c - 1 ] ) { // the last character is not yet the pipe
2022-08-05 15:14:37 +02:00
if ( last_c > 54 + i * 18 + 17 ) { // ensure we insert as the last possible character
last_c = 54 + i * 18 + 17 ;
2022-07-11 14:53:15 +02:00
}
edid [ last_c + + ] = firewall_indicator ; // insert pipe
2022-08-05 15:14:37 +02:00
if ( last_c < 54 + i * 18 + 17 ) {
2022-07-11 14:53:15 +02:00
edid [ last_c + + ] = ' \n ' ; // insert LF to terminate string
}
2022-08-05 15:14:37 +02:00
while ( last_c < 54 + i * 18 + 18 ) {
2022-07-11 14:53:15 +02:00
edid [ last_c + + ] = ' ' ; // insert padding space
}
}
}
2022-08-05 12:45:30 +02:00
// calculate new checksum
uint8_t checksum = 0 ;
for ( uint8_t i = 0 ; i < 127 ; i + + ) {
checksum + = edid [ i ] ;
}
edid [ 127 ] = ( 256 - checksum ) ;
2022-07-11 14:53:15 +02:00
}
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
2022-07-11 18:44:51 +02:00
// configure I²C lines (sink and source sides)
SDA_SRC_PU_PORT - > ODR . reg | = SDA_SRC_PU_PIN ; // pull up SDA line
SDA_SRC_PU_PORT - > CR1 . reg | = SDA_SRC_PU_PIN ; // switch pin to push pull
SDA_SRC_PU_PORT - > DDR . reg | = SDA_SRC_PU_PIN ; // switch pin to output
SCL_SRC_PU_PORT - > ODR . reg | = SCL_SRC_PU_PIN ; // pull up SCL line
SCL_SRC_PU_PORT - > CR1 . reg | = SCL_SRC_PU_PIN ; // switch pin to push pull
SCL_SRC_PU_PORT - > DDR . reg | = SCL_SRC_PU_PIN ; // switch pin to output
SCL_SRC_PORT - > ODR . reg | = SCL_SRC_PIN ; // release SCL line
SCL_SRC_PORT - > CR1 . reg & = ~ SCL_SRC_PIN ; // switch pin to open-drain
SCL_SRC_PORT - > DDR . reg | = SCL_SRC_PIN ; // switch pin to output
SDA_SRC_PORT - > ODR . reg | = SDA_SRC_PIN ; // release SDA line
SDA_SRC_PORT - > CR1 . reg & = ~ SDA_SRC_PIN ; // switch pin to open-drain
SDA_SRC_PORT - > DDR . reg | = SDA_SRC_PIN ; // switch pin to output
SCL_SNK_PORT - > ODR . reg | = SCL_SNK_PIN ; // release SCL line
SCL_SNK_PORT - > CR1 . reg & = ~ SCL_SNK_PIN ; // switch pin to open-drain
SCL_SNK_PORT - > DDR . reg | = SCL_SNK_PIN ; // switch pin to output
SDA_SNK_PORT - > ODR . reg | = SDA_SNK_PIN ; // release SDA line
SDA_SNK_PORT - > CR1 . reg & = ~ SDA_SNK_PIN ; // switch pin to open-drain
SDA_SNK_PORT - > DDR . reg | = SDA_SNK_PIN ; // switch pin to output
2022-08-05 15:13:59 +02:00
2022-07-11 18:44:51 +02:00
// test I²C forwarding
2022-08-05 15:13:59 +02:00
SDA_SNK_PORT - > ODR . reg & = ~ SDA_SNK_PIN ; // assert SDA line (send start condition)
SCL_SNK_PORT - > ODR . reg & = ~ SCL_SNK_PIN ; // assert SCL line (send start condition)
wait_10us ( 1 ) ; // wait 1 clock cycle
2022-07-11 18:44:51 +02:00
if ( 0 = = ( SCL_SRC_PORT - > IDR . reg & SCL_SRC_PIN ) | | 0 = = ( SDA_SRC_PORT - > IDR . reg & SDA_SRC_PIN ) ) {
2022-07-11 10:56:03 +02:00
i2c_fwd = true ; // remember the I²C line(s) are forwarded
2022-07-11 18:44:51 +02:00
puts ( " I²C lines forwarded \r \n " ) ;
2022-07-11 10:56:03 +02:00
}
2022-08-05 15:13:59 +02:00
SCL_SNK_PORT - > ODR . reg | = SCL_SNK_PIN ; // release SCL line (send stop condition)
SDA_SNK_PORT - > ODR . reg | = SDA_SNK_PIN ; // release SDA line (send stop condition)
2022-07-11 18:44:51 +02:00
// configure I²C pull-ups
if ( i2c_fwd ) { // I²C lines are not pulled up
SDA_SRC_PU_PORT - > CR1 . reg & = ~ SDA_SRC_PU_PIN ; // disable pull up
SDA_SRC_PU_PORT - > DDR . reg & = ~ SDA_SRC_PU_PIN ; // switch pin to input
SCL_SRC_PU_PORT - > CR1 . reg & = ~ SCL_SRC_PU_PIN ; // disable pull up
SCL_SRC_PU_PORT - > DDR . reg & = ~ SCL_SRC_PU_PIN ; // switch pin to input
2022-07-11 10:56:03 +02:00
}
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 32 K 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 + 5 V Power signal is provided . This should be available within 20 msec after the + 5 V 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
// 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
2022-07-11 18:44:51 +02:00
if ( ! i2c_fwd ) {
puts ( " I²C source ready \r \n " ) ;
}
2022-07-11 12:25:48 +02:00
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
2022-07-11 18:45:51 +02:00
wait_10us ( 1 ) ; // wait for pull up to take effect
2022-07-11 14:53:15 +02:00
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: " ) ;
2022-07-11 18:44:51 +02:00
LED_PORT - > ODR . reg & = ~ LED_PIN ; // switch LED on to indicate we are saving EDID
SDA_SNK_PU_PORT - > ODR . reg | = SDA_SNK_PU_PIN ; // pull up SDA line
SDA_SNK_PU_PORT - > CR1 . reg | = SDA_SNK_PU_PIN ; // switch pin to push pull
SDA_SNK_PU_PORT - > DDR . reg | = SDA_SNK_PU_PIN ; // switch pin to output
SCL_SNK_PU_PORT - > ODR . reg | = SCL_SNK_PU_PIN ; // pull up SCL line
SCL_SNK_PU_PORT - > CR1 . reg | = SCL_SNK_PU_PIN ; // switch pin to push pull
SCL_SNK_PU_PORT - > DDR . reg | = SCL_SNK_PU_PIN ; // switch pin to output
2022-08-05 15:15:59 +02:00
if ( 0 = = ( SDA_SNK_PORT - > IDR . reg & SDA_SNK_PIN ) ) { // SDA line is stuck
puts ( " bus clear " ) ;
// perform bus clear
for ( uint8_t i = 0 ; i < 9 ; i + + ) {
SCL_SNK_PORT - > ODR . reg & = ~ SCL_SNK_PIN ;
wait_10us ( 1 ) ;
SCL_SNK_PORT - > ODR . reg | = SCL_SNK_PIN ;
wait_10us ( 1 ) ;
}
}
2022-07-11 16:40:47 +02:00
uint8_t i2c_rc = 1 ; // if the complete read succeeded
2022-07-11 18:50:20 +02:00
if ( ! softi2c_master_setup ( 400 ) ) { // start the I²C master to talk to sink
2022-07-11 16:40:47 +02:00
i2c_rc = 2 ;
goto i2c_end ;
}
2022-07-11 12:25:48 +02:00
if ( ! softi2c_master_start ( ) ) { // start transaction
2022-07-11 16:40:47 +02:00
i2c_rc = 3 ;
2022-07-11 12:25:48 +02:00
goto i2c_end ;
}
if ( ! softi2c_master_select_slave ( 0x50 , true ) ) { // select EDID
2022-07-11 16:40:47 +02:00
i2c_rc = 4 ;
2022-07-11 12:25:48 +02:00
goto i2c_end ;
}
const uint8_t edid_addr = 0x00 ; // EDID address to read
if ( ! softi2c_master_write ( & edid_addr , 1 ) ) { // write address to read
2022-07-11 16:40:47 +02:00
i2c_rc = 5 ;
2022-07-11 12:25:48 +02:00
goto i2c_end ;
}
if ( ! softi2c_master_start ( ) ) { // re-start transaction
2022-07-11 16:40:47 +02:00
i2c_rc = 6 ;
2022-07-11 12:25:48 +02:00
goto i2c_end ;
}
if ( ! softi2c_master_select_slave ( 0x50 , false ) ) { // re-select EDID
2022-08-05 15:16:21 +02:00
puts ( " sink not present " ) ;
2022-07-11 16:40:47 +02:00
i2c_rc = 7 ;
2022-07-11 12:25:48 +02:00
goto i2c_end ;
}
uint8_t edid_sink [ 256 ] ; // buffer for sink EDID
if ( ! softi2c_master_read ( edid_sink , ARRAY_LENGTH ( edid_sink ) ) ) { // read EDID
2022-07-11 16:40:47 +02:00
i2c_rc = 8 ;
2022-07-11 12:25:48 +02:00
goto i2c_end ;
}
2022-07-11 16:40:47 +02:00
i2c_rc = 0 ; // complete read succeeded
2022-07-11 12:25:48 +02:00
i2c_end :
softi2c_master_stop ( ) ;
2022-07-11 16:40:47 +02:00
if ( 0 = = i2c_rc ) {
2022-07-11 12:25:48 +02:00
puts ( " success \r \n " ) ;
2022-08-05 12:45:52 +02:00
uint16_t edid_len = edid_length ( edid_sink ) ; // get length
if ( edid_len > 256 ) { // we only support up to one extension
edid_len = 256 ;
}
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 ) {
2022-07-11 18:46:19 +02:00
LED_PORT - > ODR . reg | = LED_PIN ; // switch LED off to indicate EDID is same
2022-07-11 14:53:15 +02:00
puts ( " EDID not changed \r \n " ) ;
} else {
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
2022-07-11 18:46:19 +02:00
bool flash_success = true ;
2022-07-11 14:53:15 +02:00
// 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
2022-07-11 18:46:19 +02:00
flash_success = false ; // remember we had a flashing error
2022-07-11 14:53:15 +02:00
puts ( " failed \r \n " ) ;
break ;
}
}
// save need EDID
2022-07-11 18:46:19 +02:00
for ( uint16_t i = 0 ; i < edid_len & & flash_success ; i + = 4U ) { // go through word
2022-07-11 14:53:15 +02:00
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
2022-07-11 18:46:19 +02:00
flash_success = false ; // remember we had a flashing error
2022-07-11 14:53:15 +02:00
puts ( " failed \r \n " ) ;
break ;
}
}
FLASH_IAPSR & = ~ FLASH_IAPSR_DUL ; // re-enable write protection
2022-07-11 18:46:19 +02:00
if ( flash_success ) {
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 " ) ;
} else {
puts ( " flashing error \r \n " ) ;
}
2022-07-11 14:53:15 +02:00
// 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
2022-08-05 15:16:40 +02:00
wait_10us ( 200 * 100 ) ; // wait over 100 ms
2022-07-11 14:53:15 +02:00
HPD_PORT - > ODR . reg | = HPD_PIN ; // pull up HPD line to indicate EDID is ready
}
}
}
2022-07-11 12:25:48 +02:00
} else {
2022-07-11 16:40:47 +02:00
LED_PORT - > ODR . reg & = ~ LED_PIN ; // switch LED on to indicate I²C read fail
puts ( " fail (rc= " ) ;
putc ( ' 0 ' + i2c_rc ) ;
puts ( " ) \r \n " ) ;
2022-07-11 12:25:48 +02:00
}
}
2022-08-05 12:48:37 +02:00
// verify stored EDID validity
if ( ! edid_length ( ( uint8_t * ) EEPROM_ADDR ) ) {
2022-08-19 15:56:29 +02:00
LED_PORT - > ODR . reg & = ~ LED_PIN ; // switch LED on to indicate EDID is invalid
2022-08-05 12:48:37 +02:00
puts ( " EEPROM EDID invalid \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 16:41:37 +02:00
/*
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 " ) ;
}
}
2022-07-11 16:41:37 +02:00
*/
2022-08-19 15:56:54 +02:00
if ( i2c_prog ) { // received data over i2c to be programmed
if ( EDID_PORT - > IDR . reg & EDID_PIN ) { // EDID switched off
puts ( " I²C prog disabled \r \n " ) ;
2022-08-19 16:04:42 +02:00
I2C_CR2 & = I2C_CR2_ACK ; // NACK next received byte to indicate programming it disabled
2022-08-19 15:56:54 +02:00
} else { // EDID programming allowed
/*
puts ( " I²C prog " ) ;
puth ( i2c_data ) ;
puts ( " @ " ) ;
puth ( i2c_addr ) ;
puts ( " \r \n " ) ;
*/
IWDG_KR = IWDG_KR_KEY_REFRESH ; // reset watchdog
// disable DATA (e.g. EEPROM) write protection
if ( 0 = = ( FLASH_IAPSR & FLASH_IAPSR_DUL ) ) {
FLASH_DUKR = FLASH_DUKR_KEY1 ;
FLASH_DUKR = FLASH_DUKR_KEY2 ;
}
// save need EDID
* ( uint8_t * ) ( EEPROM_ADDR + i2c_addr ) = i2c_data ; // write byte
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
LED_PORT - > ODR . reg & = ~ LED_PIN ; // switch LED on to indicate programming failed
2022-08-19 16:04:42 +02:00
I2C_CR2 & = I2C_CR2_ACK ; // NACK next received byte to indicate programming error
2022-08-19 15:56:54 +02:00
puts ( " EEPROM byte prog failed \r \n " ) ;
}
FLASH_IAPSR & = ~ FLASH_IAPSR_DUL ; // re-enable write protection
}
action = true ; // re-run loop
i2c_prog = false ; // clear flag
}
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
{
// 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-08-19 15:56:00 +02:00
I2C_DR = * ( uint8_t * ) ( EEPROM_ADDR + i2c_addr + + ) ; // transmit next byte (even if invalid)
2022-07-11 10:38:47 +02:00
}
if ( sr1 & I2C_SR1_RXNE ) { // receive buffer is full
2022-08-19 15:56:00 +02:00
i2c_data = I2C_DR ; // read data (also clears flag)
if ( i2c_input_new ) { // we just received the first byte
i2c_addr = i2c_data ; // we only take the first address byte
i2c_input_new = false ; // next byte is not the first
} else { // received data byte
i2c_prog = true ; // notify main loop data needs to be programmed
2022-07-11 10:38:47 +02:00
}
2022-08-19 16:04:42 +02:00
if ( EDID_PORT - > IDR . reg & EDID_PIN ) { // EDID programming is not enabled
I2C_CR2 & = I2C_CR2_ACK ; // NACK next received byte to indicate programming it disabled
}
2022-07-11 10:38:47 +02:00
}
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-08-19 15:56:00 +02:00
I2C_DR = * ( uint8_t * ) ( EEPROM_ADDR + i2c_addr + + ) ; // transmit selected byte
2022-07-11 10:38:47 +02:00
i2c_input_new = false ; // notify we send data
} else { // we will receive data
2022-12-07 11:18:40 +01:00
if ( EDID_PORT - > IDR . reg & EDID_PIN ) { // EDID switched off
I2C_CR2 & = I2C_CR2_ACK ; // NACK next received byte to indicate programming it disabled
} else {
I2C_CR2 | = I2C_CR2_ACK ; // ACK next received byte
}
2022-07-11 10:38:47 +02:00
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
}
}