improve DDC forward and sink presence detection

This commit is contained in:
King Kévin 2022-07-11 18:44:51 +02:00
parent 2ffbe5da77
commit b3d2ea58e5
1 changed files with 72 additions and 29 deletions

101
main.c
View File

@ -15,12 +15,26 @@ 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
// 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
// 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
#define SDA_SNK_PU_PIN PD4
#define SCL_SNK_PU_PORT GPIO_PA
#define SCL_SNK_PU_PIN PA1
static bool i2c_fwd = false; // if the I²C source lines are connected to sink
static bool sink_present = false; // if he sink is present (tested using sink DDC)
// external EEPROM write protect pin
#define WP_PORT GPIO_PC
#define WP_PIN PC5
@ -213,30 +227,47 @@ void main(void)
UART1->BRR1.reg = 0x08; // set baud rate to 115200 (at 16 MHz)
UART1->CR2.fields.TEN = 1; // enable TX
// 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
// test I²C forwarding
SCL_SNK_PORT->ODR.reg &= ~SCL_SNK_PIN; // assert SCL line
SDA_SNK_PORT->ODR.reg &= ~SDA_SNK_PIN; // assert SDA line
if (0 == (SCL_SRC_PORT->IDR.reg & SCL_SRC_PIN) || 0 == (SDA_SRC_PORT->IDR.reg & SDA_SRC_PIN)) {
i2c_fwd = true; // remember the I²C line(s) are forwarded
puts("I²C lines forwarded\r\n");
}
SCL_SNK_PORT->ODR.reg |= SCL_SNK_PIN; // release SCL line
SDA_SNK_PORT->ODR.reg |= SDA_SNK_PIN; // release SDA line
if (!i2c_fwd) {
wait_10us(5); // let sink 47k pull-up take effect
if ((SCL_SNK_PORT->IDR.reg & SCL_SNK_PIN) && (SDA_SNK_PORT->IDR.reg & SDA_SNK_PIN)) {
sink_present = true;
}
}
// configure I²C pull-ups
SDA_PU_PORT->DDR.reg |= SDA_PU_PIN; // switch pin to output
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
}
SCL_PU_PORT->DDR.reg |= SCL_PU_PIN; // switch pin to output
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
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
}
// configure I²C
@ -287,7 +318,9 @@ we will only be able to provide 1 extension block since we can only respond to o
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");
if (!i2c_fwd) {
puts("I²C source ready\r\n");
}
// check if EDID should be copied
EDID_PORT->DDR.reg &= ~EDID_PIN; // switch pin to output
@ -297,9 +330,19 @@ we will only be able to provide 1 extension block since we can only respond to o
} 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 if (!sink_present) {
puts("can't read EDID: sink not present\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: ");
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
uint8_t i2c_rc = 1; // if the complete read succeeded
if (!softi2c_master_setup(1)) { // start the I²C master to talk to sink
i2c_rc = 2;