Compare commits

...

38 Commits

Author SHA1 Message Date
King Kévin 888a5af823 NAK I²C write when EDID protected 2022-12-07 11:18:40 +01:00
King Kévin 8bc856095e fix crc extensions checking 2022-12-07 11:18:18 +01:00
King Kévin 7ba053d34f enable debug per default 2022-12-07 11:17:45 +01:00
King Kévin 9e35864548 doc: add EDID I²C programming 2022-08-19 16:10:52 +02:00
King Kévin 41435806b8 use I²C NAK to indicate programming is disabled 2022-08-19 16:04:42 +02:00
King Kévin df611e4436 write i2c data to EEPROM 2022-08-19 15:56:54 +02:00
King Kévin d02facf754 minor, fix comment 2022-08-19 15:56:29 +02:00
King Kévin df482ca7c4 implement i2c write 2022-08-19 15:56:00 +02:00
King Kévin 695e5dc9f4 doc: write README 2022-08-09 10:47:08 +02:00
King Kévin bb00e9c67d make: verify programming 2022-08-06 10:27:30 +02:00
King Kévin 7a28b2abb5 disable debug output 2022-08-06 10:26:55 +02:00
King Kévin ed8a561430 incread HPD reset indication 2022-08-05 15:16:40 +02:00
King Kévin 3653c58772 indicate sink not present 2022-08-05 15:16:21 +02:00
King Kévin 87fda7e878 implement I²C bus clear 2022-08-05 15:15:59 +02:00
King Kévin af416909d1 serch for display name in other descriptors 2022-08-05 15:14:37 +02:00
King Kévin e211a5446a remove sink presence detection 2022-08-05 15:13:59 +02:00
King Kévin 5d76a67bd3 main: check EEPROM at boot 2022-08-05 12:48:37 +02:00
King Kévin 31815bd66e main: limit EDID length to 256 bytes 2022-08-05 12:45:52 +02:00
King Kévin 2f0ffb9c5a main: limit stored EEPROM to 1 extension 2022-08-05 12:45:30 +02:00
King Kévin f486521126 main: calculate EDID length of multiple extensions 2022-08-05 12:44:52 +02:00
King Kévin 298f0ea3ca main: define pinout for v2.37 2022-08-05 12:43:52 +02:00
King Kévin 319a783de0 increase I²C sink speed 2022-07-11 18:50:20 +02:00
King Kévin e7d3a86e45 softi2c_master: improve pin configuration 2022-07-11 18:47:36 +02:00
King Kévin ba1752a409 improved flash error indication 2022-07-11 18:46:19 +02:00
King Kévin 5bbe2eb5c7 improved EDID setting read 2022-07-11 18:45:51 +02:00
King Kévin b3d2ea58e5 improve DDC forward and sink presence detection 2022-07-11 18:44:51 +02:00
King Kévin 2ffbe5da77 remove unsued debug 2022-07-11 16:41:37 +02:00
King Kévin 03e6875a94 improve I²C master debugging 2022-07-11 16:40:47 +02:00
King Kévin 5be8874b3c remove unused lib 2022-07-11 14:56:24 +02:00
King Kévin 82e5d984bc save sink EDID 2022-07-11 14:53:15 +02:00
King Kévin e345b61860 add EDID check 2022-07-11 12:58:01 +02:00
King Kévin 2859bb7a09 check if HPD is forwarded 2022-07-11 12:26:28 +02:00
King Kévin 06c14750fc read sink EDID 2022-07-11 12:25:48 +02:00
King Kévin 1770362588 set I²C lines 2022-07-11 12:24:54 +02:00
King Kévin d247e2e1e8 check if I²C is forwarded 2022-07-11 10:56:03 +02:00
King Kévin e2cdc89a30 add EDID EEPROM flashing 2022-07-11 10:40:01 +02:00
King Kévin f7ed7670f3 add UART EEPROM 2022-07-11 10:39:09 +02:00
King Kévin 9e865cc0c7 init board and emulate I²C slave EEPROM 2022-07-11 10:38:47 +02:00
9 changed files with 610 additions and 679 deletions

View File

@ -15,7 +15,12 @@ $(FIRMWARE).ihx: $(OBJ_FILES)
$(CC) $(CFLAGS) --compile-only $<
flash: $(FIRMWARE).ihx
stm8flash -c stlinkv2 -p stm8s103f3 -w $<
stm8flash -c stlinkv2 -p stm8s103f3 -s flash -w $<
stm8flash -c stlinkv2 -p stm8s103f3 -s flash -v $<
eeprom: edid_cuvoodoo.bin
stm8flash -c stlinkv2 -p stm8s103f3 -s eeprom -w $<
stm8flash -c stlinkv2 -p stm8s103f3 -s eeprom -v $<
clean:
rm -f $(FIRMWARE).asm $(FIRMWARE).ihx $(FIRMWARE).cdb $(FIRMWARE).lst $(FIRMWARE).map $(FIRMWARE).lk $(FIRMWARE).rel $(FIRMWARE).rst $(FIRMWARE).sym $(OBJ_FILES)

View File

@ -1,2 +1,59 @@
firmware template for ST STM8S micro-controller.
includes register definitions using macros and structures.
firmware for [HDMI firewall v2](https://git.cuvoodoo.info/kingkevin/board/src/branch/hdmi_firewall).
usage
=====
functions:
- upon request by the device/source over the HDMI DDC interface, the HDMI firewall returns the EDID stored in its EEPROM
- if the EDID/7 switch is on the ALLOW/ON position, when powered (e.g. device plugs in), it will retrieve the monitors EDID and store it in EEPROM (ERROR LED will blink once)
- once the EDID copied, it will try to re-connect to device by pulsing the Hot Plug Detect (HPD)
- stored EDID adds a '|' at the end of the name to indicate firewall is used
- if DDC is forwarded (e.g. SCL and SDA switches are on the ALLOW position), the HDMI firewall does no interfere with the signals, does not return the stored EDID, and does not firewall the communication
if the ERROR LED is on, the possible cause is one of the following:
- no EDID might is present in the EEPROM
- tried reading the EDID from the monitor, but it is not connected
- communication with monitor failed, due to damaged cable
- monitor EDID is invalid
- storing EDID in EEPROM failed
the firewall only acts as an I²C EEPROM at address 0x50 toward the HDMI device to provide the EDID information.
if the EDID switch is on the BLOCK position, the EEPROM is read only.
if the EDID switch is on the ALLOW position, writing the EEPROM is possible over the HDMI connection using standard I²C write operations.
limitations
===========
stored EDID has only up 1 EDID extension.
some monitors might use more to offer additional features, but I haven't encountered this case yet.
flashing
========
the firmware is for an STM8S103.
the debug port on the HDMI firewall allows to flash and debug the firmware.
to compile the firmware using [SDCC](http://sdcc.sourceforge.net/):
~~~
make
~~~
to flash the firmware with [stm8flash](https://github.com/vdudouyt/stm8flash) using an ST-LINK/V2 (clone):
~~~
make flash
~~~
to store the generic HD EDID profile in EEPROM:
~~~
make eeprom
~~~
this uses the `edid_cuvoodoo.bin` binary EDID, which you can replace with your own.
to enable printf debugging, set `DEBUG` to `1` in `main.c`.
the serial configuration in 115200 8N1.

BIN
edid_cuvoodoo.bin Normal file

Binary file not shown.

View File

@ -1,69 +0,0 @@
/** library to program EEPROM using block programming
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2021
* @warning functions need to be put in and run from RAM (because block programming is used)
*/
// RAM code-putting from https://lujji.github.io/blog/executing-code-from-ram-on-stm8/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdbool.h> // boolean types
#include "stm8s.h" // STM8S definitions
#include "eeprom_blockprog.h" // own definitions
// start address of EEPROM
#define EEPROM_ADDR 0x4000
// block size from low-density devices (including STM8S103)
#define DATA_BLOCK_SIZE 64U
#pragma codeseg RAM_SEG
bool eeprom_blockprog(const uint8_t* data, uint16_t length)
{
if (0 == length) {
return true; // nothing to do
}
if (!data) {
return false; // data missing
}
if (0 != (length % DATA_BLOCK_SIZE)) {
return false; // we can only program whole blocks
}
// disable DATA (e.g. EEPROM) write protection
// don't check if it is locked this it does not save that much time and uses memory)
FLASH_DUKR = FLASH_DUKR_KEY1;
FLASH_DUKR = FLASH_DUKR_KEY2;
// don't verify if unlock succeeded to save memory
// if (!(FLASH_IAPSR & FLASH_IAPSR_DUL)) { // un-protecting failed
// return false;
// }
// program data
uint8_t* eeprom = (uint8_t*)(EEPROM_ADDR);
while (length) {
// enable standard block programming
FLASH_CR2 |= FLASH_CR2_PRG;
FLASH_NCR2 &= ~FLASH_NCR2_NPRG;
// program block
for (uint8_t i = 0; i < DATA_BLOCK_SIZE; i++) {
*(eeprom++) = *(data++);
}
length -= DATA_BLOCK_SIZE;
// wait until program completed
while (FLASH_CR2 & FLASH_CR2_PRG);
// 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
return false;
}
}
FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection
return true;
}

View File

@ -1,14 +0,0 @@
/** library to program EEPROM using block programming
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2021
* @warning functions need to be put in and run from RAM (because block programming is used)
*/
/** program EEPROM using block programming
* @param[in] data data to be programmed
* @param[in] length length of data to be programmed (must be a multiple of the block length)
* @return if program succeeded
*/
bool eeprom_blockprog(const uint8_t* data, uint16_t length);

View File

@ -1,469 +0,0 @@
/** library to communicate using I²C as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2021
* @note the I²C peripheral is not well specified and does not cover all cases. The following complexity is the best I could do to cope with it
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdbool.h> // boolean types
#include <stdlib.h> // general utilities
/* own libraries */
#include "stm8s.h" // STM8S definitions
#include "i2c_master.h" // I²C header and definitions
bool i2c_master_setup(uint16_t freq_khz)
{
// configure I²C peripheral
I2C_CR1 &= ~I2C_CR1_PE; // disable I²C peripheral to configure it
/*
if (!i2c_master_check_signals()) { // check the signal lines
return false;
}
*/
I2C_FREQR = 16; // the peripheral frequency (must match CPU frequency)
if (freq_khz > 100) {
uint16_t ccr = (I2C_FREQR * 1000) / (3 * freq_khz);
if (ccr > 0x0fff) {
ccr = 0x0fff;
}
I2C_CCRL = (ccr & 0xff); // set SCL at 320 kHz (for less error)
I2C_CCRH = ((ccr >> 8) & 0x0f) | (I2C_CCRH_FS); // set fast speed mode
I2C_TRISER = ((I2C_FREQR * 3 / 10) + 1); // set rise time
} else {
uint16_t ccr = (I2C_FREQR * 1000) / (2 * freq_khz);
if (ccr > 0x0fff) {
ccr = 0x0fff;
}
I2C_CCRL = (ccr & 0xff); // set SCL at 320 kHz (for less error)
I2C_CCRH = ((ccr >> 8) & 0x0f); // set fast speed mode
I2C_TRISER = (I2C_FREQR + 1); // set rise time
}
I2C_CR1 |= I2C_CR1_PE; // enable I²C peripheral
return true;
}
void i2c_master_release(void)
{
I2C_CR1 &= ~I2C_CR1_PE; // disable I²C peripheral
}
bool i2c_master_check_signals(void)
{
i2c_master_release(); // ensure PB4/PB5 are not used as alternate function
GPIO_PB->CR1.reg &= ~(PB4 | PB5); // operate in open-drain mode
GPIO_PB->DDR.reg |= (PB4 | PB5); // set SCL/SDA as output to test pull-up
GPIO_PB->ODR.reg |= PB4; // ensure SCL is high
GPIO_PB->ODR.reg &= ~PB5; // set SDA low (start condition)
for (volatile uint8_t t = 0; t < 10; t++); // wait a bit to be sure signal is low
GPIO_PB->ODR.reg |= PB5; // set SDA high (stop condition)
GPIO_PB->DDR.reg &= ~(PB4 | PB5); // set SCL/SDA as input before it is used as alternate function by the peripheral
for (volatile uint8_t t = 0; t < 50; t++); // wait 10 us for pull-up to take effect
return ((GPIO_PB->IDR.reg & PB4) && (GPIO_PB->IDR.reg & PB5)); // test if both lines are up
}
void i2c_master_reset(void)
{
I2C_CR2 |= I2C_CR2_STOP; // release lines
// don't check if BUSY is cleared since its state might be erroneous
// rewriting I2C_CR2 before I2C_CR2_STOP is cleared might cause a second STOP, but at this point we don't care
I2C_CR2 |= I2C_CR2_SWRST; // reset peripheral, in case we got stuck and the dog bit
// be sure a watchdog is present as this can take forever
while ((0 == (GPIO_PB->IDR.reg & PB4) && (0 == (GPIO_PB->IDR.reg & PB5)))); // wait for SDA/SCL line to be released
I2C_CR2 &= ~I2C_CR2_SWRST; // release reset
I2C_CR1 &= ~I2C_CR1_PE; // disable I²C peripheral to clear some bits
}
enum i2c_master_rc i2c_master_start(void)
{
// send (re-)start condition
if (I2C_CR2 & (I2C_CR2_START | I2C_CR2_STOP)) { // ensure start or stop operations are not in progress
return I2C_MASTER_RC_START_STOP_IN_PROGESS;
}
// don't check BUSY flag as this might be for a re-start
I2C_CR2 |= I2C_CR2_START; // sent start condition
I2C_SR2 = 0; // clear error flags
rim(); // enable interrupts
while ((I2C_CR2 & I2C_CR2_START) || !(I2C_SR1 & I2C_SR1_SB) || !(I2C_SR3 & I2C_SR3_MSL)) { // wait until start condition has been accepted, send, and we are in aster mode
if (I2C_SR2) {
return I2C_MASTER_RC_BUS_ERROR;
}
if (I2C_CR2 & I2C_CR2_STOP) {
return I2C_MASTER_RC_TIMEOUT;
}
I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable I²C interrupts
wfi(); // got to sleep to prevent EMI causing glitches
}
return I2C_MASTER_RC_NONE;
}
/** wait until stop is sent and bus is released
* @return I²C return code
*/
static enum i2c_master_rc i2c_master_wait_stop(void)
{
I2C_SR2 = 0; // clear error flags
while (I2C_CR2 & I2C_CR2_STOP) { // wait until stop condition is accepted and cleared
if (I2C_SR2) {
return I2C_MASTER_RC_BUS_ERROR;
}
// there is no interrupt flag we can use here
}
// this time we can't use I2C_CR2_STOP to check for timeout
if (I2C_SR3 & I2C_SR3_MSL) { // ensure we are not in master mode anymore
return I2C_MASTER_RC_BUS_ERROR;
}
if (I2C_SR3 & I2C_SR3_BUSY) { // ensure bus is released
return I2C_MASTER_RC_BUS_ERROR;
}
/*
if (!i2c_master_check_signals()) { // ensure lines are released
return I2C_MASTER_RC_BUS_ERROR;
}
*/
return I2C_MASTER_RC_NONE;
}
enum i2c_master_rc i2c_master_stop(void)
{
// sanity check
if (!(I2C_SR3 & I2C_SR3_BUSY)) { // ensure bus is not already released
return I2C_MASTER_RC_NONE; // bus has probably already been released
}
if (I2C_CR2 & (I2C_CR2_START | I2C_CR2_STOP)) { // ensure start or stop operations are not in progress
return I2C_MASTER_RC_START_STOP_IN_PROGESS;
}
I2C_CR2 |= I2C_CR2_STOP; // send stop to release bus
return i2c_master_wait_stop();
}
enum i2c_master_rc i2c_master_select_slave(uint16_t slave, bool address_10bit, bool write)
{
if (!(I2C_SR1 & I2C_SR1_SB)) { // start condition has not been sent yet
enum i2c_master_rc rc = i2c_master_start(); // send start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
}
if (!(I2C_SR3 & I2C_SR3_MSL)) { // I²C device is not in master mode
return I2C_MASTER_RC_NOT_MASTER;
}
// select slave
I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure
if (!address_10bit) { // 7-bit address
I2C_DR = (slave << 1) | (write ? 0 : 1); // select slave, with read/write flag
I2C_SR2 = 0; // clear error flags
rim(); // enable interrupts
while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address is transmitted (or error)
if (I2C_CR2 & I2C_CR2_STOP) {
return I2C_MASTER_RC_TIMEOUT;
}
if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged
return I2C_MASTER_RC_NAK;
} else if (I2C_SR2) {
return I2C_MASTER_RC_BUS_ERROR;
}
I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts
wfi(); // got to sleep to prevent EMI causing glitches
}
} else { // 10-bit address
// send first part of address
I2C_DR = 11110000 | (((slave >> 8 ) & 0x3) << 1); // send first header (11110xx0, where xx are 2 MSb of slave address)
I2C_SR2 = 0; // clear error flags
rim(); // enable interrupts
while (!(I2C_SR1 & I2C_SR1_ADD10)) { // wait until address is transmitted (or error)
if (I2C_CR2 & I2C_CR2_STOP) {
return I2C_MASTER_RC_TIMEOUT;
}
if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged
return I2C_MASTER_RC_NAK;
} else if (I2C_SR2) {
return I2C_MASTER_RC_BUS_ERROR;
}
I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts
wfi(); // got to sleep to prevent EMI causing glitches
}
// send second part of address
I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure
I2C_DR = (slave & 0xff); // send remaining of address
I2C_SR2 = 0; // clear error flags
rim(); // enable interrupts
while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address is transmitted (or error)
if (I2C_CR2 & I2C_CR2_STOP) {
return I2C_MASTER_RC_TIMEOUT;
}
if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged
return I2C_MASTER_RC_NAK;
} else if (I2C_SR2) {
return I2C_MASTER_RC_BUS_ERROR;
}
I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts
wfi(); // got to sleep to prevent EMI causing glitches
}
// go into receive mode if necessary
if (!write) {
enum i2c_master_rc rc = i2c_master_start(); // send start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
// send first part of address with receive flag
I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure
I2C_DR = 11110001 | (((slave >> 8) & 0x3) << 1); // send header (11110xx1, where xx are 2 MSb of slave address)
I2C_SR2 = 0; // clear error flags
while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address is transmitted (or error)
if (I2C_CR2 & I2C_CR2_STOP) {
return I2C_MASTER_RC_TIMEOUT;
}
if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged
return I2C_MASTER_RC_NAK;
} else if (I2C_SR2) {
return I2C_MASTER_RC_BUS_ERROR;
}
I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts
wfi(); // got to sleep to prevent EMI causing glitches
}
}
}
// I2C_SR3_TRA should be set after I2C_SR1_ADDR is cleared (end of address transmission), but this is not the case and the TRM/errata does not provide more info
// verify if we are in the right mode
// final check
if (write && !(I2C_SR3 & I2C_SR3_TRA)) {
return I2C_MASTER_RC_NOT_TRANSMIT;
} else if (!write && (I2C_SR3 & I2C_SR3_TRA)) {
return I2C_MASTER_RC_NOT_RECEIVE;
}
return I2C_MASTER_RC_NONE;
}
enum i2c_master_rc i2c_master_read(uint8_t* data, uint16_t data_size)
{
if (NULL == data || 0 == data_size) { // no data to read
return I2C_MASTER_RC_OTHER; // we indicate an error because we don't send a stop
}
if (!(I2C_SR3 & I2C_SR3_MSL)) { // I²C device is not in master mode
return I2C_MASTER_RC_NOT_MASTER;
}
// we can't check if the address phase it over since ADDR has been cleared when checking for mode
if (I2C_SR3 & I2C_SR3_TRA) { // ensure we are in receive mode
return I2C_MASTER_RC_NOT_RECEIVE;
}
// read data
I2C_CR2 |= I2C_CR2_ACK; // enable ACK by default
I2C_SR2 = 0; // clear error flags
rim(); // enable interrupts
for (uint16_t i = 0; i < data_size; i++) { // read bytes
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
// set (N)ACK (EV6_3, EV6_1)
if (1 == (data_size - i)) { // prepare to sent NACK for last byte
I2C_CR2 &= ~(I2C_CR2_ACK); // disable ACK
I2C_CR2 |= I2C_CR2_STOP; // prepare to send the stop
}
rim(); // enable interrupts
while (!(I2C_SR1 & I2C_SR1_RXNE)) { // wait until data is received (or error)
if (I2C_SR2) { // an error occurred
return I2C_MASTER_RC_BUS_ERROR;
}
I2C_ITR = (I2C_ITR_ITBUFEN | I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable all I²C interrupts
wfi(); // got to sleep to prevent EMI causing glitches
}
data[i] = I2C_DR; // read the received byte
}
return i2c_master_wait_stop();
}
enum i2c_master_rc i2c_master_write(const uint8_t* data, uint16_t data_size)
{
if (NULL == data || 0 == data_size) { // no data to read
return I2C_MASTER_RC_NONE; // we don't indicate an error because the stop is done separately
}
if (!(I2C_SR3 & I2C_SR3_MSL)) { // I²C device is not in master mode
return I2C_MASTER_RC_NOT_MASTER;
}
// we can't check if the address phase it over since ADDR has been cleared when checking for mode
if (!(I2C_SR3 & I2C_SR3_TRA)) { // ensure we are in transmit mode
return I2C_MASTER_RC_NOT_TRANSMIT;
}
// write data
for (uint16_t i = 0; i < data_size; i++) { // write bytes
I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure
(void)(I2C_SR1 & I2C_SR1_BTF); // clear BTF (when followed by write) in case the clock is stretched because there was no data to send on the next transmission slot
I2C_DR = data[i]; // send byte
I2C_SR2 = 0; // clear error flags
rim(); // enable interrupts
while (!(I2C_SR1 & I2C_SR1_TXE)) { // wait until byte has been transmitted
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
if (I2C_CR2 & I2C_CR2_STOP) {
return I2C_MASTER_RC_TIMEOUT;
}
if (I2C_SR2 & I2C_SR2_AF) { // data has not been acknowledged
return I2C_MASTER_RC_NAK;
} else if (I2C_SR2) {
return I2C_MASTER_RC_BUS_ERROR;
}
I2C_ITR = (I2C_ITR_ITBUFEN | I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable all I²C interrupts
wfi(); // got to sleep to prevent EMI causing glitches
}
}
return I2C_MASTER_RC_NONE;
}
enum i2c_master_rc i2c_master_slave_read(uint16_t slave, bool address_10bit, uint8_t* data, uint16_t data_size)
{
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(slave, address_10bit, false); // select slave to read
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
if (NULL != data && data_size > 0) { // only read data if needed
rc = i2c_master_read(data, data_size); // read data (includes stop)
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
} else {
i2c_master_stop(); // sent stop condition
}
rc = I2C_MASTER_RC_NONE; // all went well
error:
if (I2C_MASTER_RC_NONE != rc) {
i2c_master_stop(); // sent stop condition
}
return rc;
}
enum i2c_master_rc i2c_master_slave_write(uint16_t slave, bool address_10bit, const uint8_t* data, uint16_t data_size)
{
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
if (NULL != data && data_size > 0) { // write data only is some is available
rc = i2c_master_write(data, data_size); // write data
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
}
rc = I2C_MASTER_RC_NONE; // all went well
error:
i2c_master_stop(); // sent stop condition
return rc;
}
enum i2c_master_rc i2c_master_address_read(uint16_t slave, bool address_10bit, const uint8_t* address, uint16_t address_size, uint8_t* data, uint16_t data_size)
{
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
// write address
if (NULL != address && address_size > 0) {
rc = i2c_master_write(address, address_size); // send memory address
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
}
// read data
if (NULL != data && data_size > 0) {
rc = i2c_master_start(); // send re-start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(slave, address_10bit, false); // select slave to read
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
rc = i2c_master_read(data, data_size); // read memory (includes stop)
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
} else {
i2c_master_stop(); // sent stop condition
}
rc = I2C_MASTER_RC_NONE;
error:
if (I2C_MASTER_RC_NONE != rc) { // only send stop on error
i2c_master_stop(); // sent stop condition
}
return rc;
}
enum i2c_master_rc i2c_master_address_write(uint16_t slave, bool address_10bit, const uint8_t* address, uint16_t address_size, const uint8_t* data, uint16_t data_size)
{
if (UINT16_MAX - address_size < data_size) { // prevent integer overflow
return I2C_MASTER_RC_OTHER;
}
if (address_size > 0 && NULL == address) {
return I2C_MASTER_RC_OTHER;
}
if (data_size > 0 && NULL == data) {
return I2C_MASTER_RC_OTHER;
}
enum i2c_master_rc rc; // to store I²C return codes
rc = i2c_master_start(); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
if (address_size && address) {
rc = i2c_master_write(address, address_size); // send memory address
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
}
if (data_size && data) {
rc = i2c_master_write(data, data_size); // send memory data
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
}
rc = i2c_master_stop(); // sent stop condition
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
rc = I2C_MASTER_RC_NONE; // all went fine
error:
return rc;
}
void i2c_master_isr(void) __interrupt(IRQ_I2C) // I²C event or error happened
{
I2C_ITR = 0; // disable all interrupt sources to stop looping in ISR and let current loop check the right status flags
}

View File

@ -1,117 +0,0 @@
/** library to communicate using I²C as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2021
* @warning the I²C peripheral is very glitchy (sending random clock pulses), thus prefer the software implementation alternative, which is simpler, more flexible, smaller, and very stable (it just draws more energy)
*/
#pragma once
/** I²C return codes */
enum i2c_master_rc {
I2C_MASTER_RC_NONE = 0, /**< no error */
I2C_MASTER_RC_START_STOP_IN_PROGESS, /**< a start or stop condition is already in progress */
I2C_MASTER_RC_NOT_MASTER, /**< not in master mode */
I2C_MASTER_RC_NOT_TRANSMIT, /**< not in transmit mode */
I2C_MASTER_RC_NOT_RECEIVE, /**< not in receive mode */
I2C_MASTER_RC_NOT_READY, /**< slave is not read (previous operations has been NACKed) */
I2C_MASTER_RC_NAK, /**< not acknowledge received */
I2C_MASTER_RC_BUS_ERROR, /**< an error on the I²C bus occurred */
I2C_MASTER_RC_TIMEOUT, /**< a timeout has occurred because an operation has not completed in the expected time */
I2C_MASTER_RC_OTHER, /** any other error (does not have to be I²C related) */
};
/** setup I²C peripheral
* @param[in] freq_khz desired clock frequency, in kHz
* @return if I²C bus is ready to be used (same as i2c_master_check_signals)
*/
bool i2c_master_setup(uint16_t freq_khz);
/** release I²C peripheral */
void i2c_master_release(void);
/** reset I²C peripheral, fixing any locked state
* @warning the I²C peripheral needs to be re-setup
* @note to be used after failed start or stop, and bus error
*/
void i2c_master_reset(void);
/** check if SDA and SCL signals are pulled high
* @return if SDA and SCL signals are pulled high
*/
bool i2c_master_check_signals(void);
/** send start condition
* @return I2C return code
*/
enum i2c_master_rc i2c_master_start(void);
/** select I²C slave device
* @warning a start condition should be sent before this operation
* @param[in] slave I²C address of slave device to select
* @param[in] address_10bit if the I²C slave address is 10 bits wide
* @param[in] write this transaction will be followed by a read (false) or write (true) operation
* @return I²C return code
*/
enum i2c_master_rc i2c_master_select_slave(uint16_t slave, bool address_10bit, bool write);
/** read data over I²C
* @param[out] data array to store bytes read
* @param[in] data_size number of bytes to read
* @return I²C return code
* @warning the slave device must be selected before this operation
* @note a stop condition will be sent at the end (I²C does not permit multiple reads, and this is necessary for 1-byte transfer)
*/
enum i2c_master_rc i2c_master_read(uint8_t* data, uint16_t data_size);
/** write data over I²C
* @param[in] data array of byte to write to slave
* @param[in] data_size number of bytes to write
* @return I²C return code
* @warning the slave device must be selected before this operation
* @note no stop condition is sent at the end, allowing multiple writes
*/
enum i2c_master_rc i2c_master_write(const uint8_t* data, uint16_t data_size);
/** sent stop condition
* @param[in] i2c I²C base address
* @return I²C return code
*/
enum i2c_master_rc i2c_master_stop(void);
/** read data from slave device
* @param[in] slave I²C address of slave device to select
* @param[in] address_10bit if the I²C slave address is 10 bits wide
* @param[out] data array to store bytes read
* @param[in] data_size number of bytes to read
* @return I²C return code
* @note start and stop conditions are included
*/
enum i2c_master_rc i2c_master_slave_read(uint16_t slave, bool address_10bit, uint8_t* data, uint16_t data_size);
/** write data to slave device
* @param[in] slave I²C address of slave device to select
* @param[in] address_10bit if the I²C slave address is 10 bits wide
* @param[in] data array of byte to write to slave
* @param[in] data_size number of bytes to write
* @return I²C return code
* @note start and stop conditions are included
*/
enum i2c_master_rc i2c_master_slave_write(uint16_t slave, bool address_10bit, const uint8_t* data, uint16_t data_size);
/** read data at specific address from an I²C memory slave
* @param[in] slave I²C address of slave device to select
* @param[in] address_10bit if the I²C slave address is 10 bits wide
* @param[in] address memory address of slave to read from
* @param[in] address_size address size in bytes
* @param[out] data array to store bytes read
* @param[in] data_size number of bytes to read
* @return I²C return code
* @note start and stop conditions are included
*/
enum i2c_master_rc i2c_master_address_read(uint16_t slave, bool address_10bit, const uint8_t* address, uint16_t address_size, uint8_t* data, uint16_t data_size);
/** write data at specific address on an I²C memory slave
* @param[in] slave I²C address of slave device to select
* @param[in] address_10bit if the I²C slave address is 10 bits wide
* @param[in] address memory address of slave to write to
* @param[in] address_size address size in bytes
* @param[in] data array of byte to write to slave
* @param[in] data_size number of bytes to write
* @return I²C return code
* @note start and stop conditions are included
*/
enum i2c_master_rc i2c_master_address_write(uint16_t slave, bool address_10bit, const uint8_t* address, uint16_t address_size, const uint8_t* data, uint16_t data_size);
/** interrupt service routine used to wake up
* @note not sure why the declaration need to be in main for it to work
*/
void i2c_master_isr(void) __interrupt(IRQ_I2C);

540
main.c
View File

@ -1,6 +1,7 @@
/* firmware template for STM8S microcontroller
* Copyright (C) 2019-2020 King Kévin <kingkevin@cuvoodoo.info>
* Copyright (C) 2019-2022 King Kévin <kingkevin@cuvoodoo.info>
* SPDX-License-Identifier: GPL-3.0-or-later
* for HDMI firewall v2.37
*/
#include <stdint.h>
#include <stdbool.h>
@ -8,8 +9,53 @@
#include "stm8s.h"
#include "main.h"
#include "softi2c_master.h"
// enable UART debug
#define DEBUG 1
#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
// 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 PD6
#define SCL_SNK_PU_PORT GPIO_PC
#define SCL_SNK_PU_PIN PC5
static bool i2c_fwd = false; // if the I²C source lines are connected to sink
// hot plug detect pull up
#define HPD_PORT GPIO_PC
#define HPD_PIN PC6
static bool hpd_fwd = false; // if the I²C source line is connected to sink
// copy EDID setting
#define EDID_PORT GPIO_PC
#define EDID_PIN PC7
// if an I²C transaction started
static volatile bool i2c_transaction_new = false;
// if an I²C transaction with incoming data started
static volatile bool i2c_input_new = false;
// 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;
// blocking wait (in 10 us steps, up to UINT32_MAX / 10)
static void wait_10us(uint32_t us10)
@ -18,6 +64,150 @@ static void wait_10us(uint32_t us10)
while (us10--); // burn energy
}
void putc(char c)
{
(void)c;
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
#if DEBUG
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
#endif
}
void puts(const char* s)
{
if (NULL == s) {
return;
}
while (*s) {
putc(*s++);
}
}
void putn(uint8_t n)
{
n &= 0x0f; // ensure it's a nibble
if (n < 0xa) {
n += '0';
} else {
n = 'a' + (n - 0x0a);
}
putc(n);
}
void puth(uint8_t h)
{
putn(h >> 4);
putn(h & 0x0f);
}
// 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 its length (including extensions), or 0 if invalid
/* 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 (and is deprecated, replaced by E-EDID)
* DisplayID with its variable-length structure meant to replace E-EDID only seems to be used in DisplayPort
*/
static uint16_t edid_length(const uint8_t* edid)
{
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)) { // ensure checksum of base EDID is ok
const uint16_t length = 128 + edid[126] * 128; // get length with all extensions
puts("(v1.3/1.4, ");
putn(edid[126] / 100);
putn((edid[126] / 10) % 10);
putn(edid[126] % 10);
puts(" extension) ");
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
}
}
}
puts("CRC OK\r\n");
return length;
} else {
puts("base CRC error\r\n");
return 0;
}
} else if (2 == edid[18]) { // EDID 2.0 256-byte structure
if (checksum_ok(&edid[0], 256)) {
puts("(v2) CRC ok\r\n");
return 256;
} else {
puts("CRC error\r\n");
return 0;
}
}
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 we only have up to one extension
if (edid[126] > 1) {
edid[126] = 1;
}
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;
}
uint8_t last_c; // position of last character
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
if (firewall_indicator != edid[last_c - 1]) { // the last character is not yet the pipe
if (last_c > 54 + i * 18 + 17) { // ensure we insert as the last possible character
last_c = 54 + i * 18 + 17;
}
edid[last_c++] = firewall_indicator; // insert pipe
if (last_c < 54 + i * 18 + 17) {
edid[last_c++] = '\n'; // insert LF to terminate string
}
while (last_c < 54 + i * 18 + 18) {
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)
@ -26,6 +216,11 @@ void main(void)
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
// 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
// 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
@ -40,10 +235,307 @@ void main(void)
IWDG->PR.fields.PR = IWDG_PR_DIV256; // set prescale to longest time (1.02s)
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
// 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 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
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
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 (send stop condition)
SDA_SNK_PORT->ODR.reg |= SDA_SNK_PIN; // release SDA line (send stop condition)
// 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
}
// configure I²C
if (!i2c_fwd) { // we will act as I²C slave EEPROM
/*
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)
*/
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
}
// configure hot plug detect
HPD_PORT->DDR.reg |= HPD_PIN; // switch pin to output
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
}
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
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
EDID_PORT->CR1.reg |= EDID_PIN; // enable pull up
wait_10us(1); // wait for pull up to take effect
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: ");
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
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);
}
}
uint8_t i2c_rc = 1; // if the complete read succeeded
if (!softi2c_master_setup(400)) { // start the I²C master to talk to sink
i2c_rc = 2;
goto i2c_end;
}
if (!softi2c_master_start()) { // start transaction
i2c_rc = 3;
goto i2c_end;
}
if (!softi2c_master_select_slave(0x50, true)) { // select EDID
i2c_rc = 4;
goto i2c_end;
}
const uint8_t edid_addr = 0x00; // EDID address to read
if (!softi2c_master_write(&edid_addr, 1)) { // write address to read
i2c_rc = 5;
goto i2c_end;
}
if (!softi2c_master_start()) { // re-start transaction
i2c_rc = 6;
goto i2c_end;
}
if (!softi2c_master_select_slave(0x50, false)) { // re-select EDID
puts("sink not present ");
i2c_rc = 7;
goto i2c_end;
}
uint8_t edid_sink[256]; // buffer for sink EDID
if (!softi2c_master_read(edid_sink, ARRAY_LENGTH(edid_sink))) { // read EDID
i2c_rc = 8;
goto i2c_end;
}
i2c_rc = 0; // complete read succeeded
i2c_end:
softi2c_master_stop();
if (0 == i2c_rc) {
puts("success\r\n");
uint16_t edid_len = edid_length(edid_sink); // get length
if (edid_len > 256) { // we only support up to one extension
edid_len = 256;
}
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) {
LED_PORT->ODR.reg |= LED_PIN; // switch LED off to indicate EDID is same
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
bool flash_success = true;
// 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
flash_success = false; // remember we had a flashing error
puts("failed\r\n");
break;
}
}
// save need EDID
for (uint16_t i = 0; i < edid_len && flash_success; 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
flash_success = false; // remember we had a flashing error
puts("failed\r\n");
break;
}
}
FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection
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");
}
// 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(200 * 100); // wait over 100 ms
HPD_PORT->ODR.reg |= HPD_PIN; // pull up HPD line to indicate EDID is ready
}
}
}
} else {
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");
}
}
// verify stored EDID validity
if (!edid_length((uint8_t*)EEPROM_ADDR)) {
LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate EDID is invalid
puts("EEPROM EDID invalid\r\n");
}
bool action = false; // if an action has been performed
puts("loop\r\n");
while (true) {
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
/*
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");
}
}
*/
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");
I2C_CR2 &= I2C_CR2_ACK; // NACK next received byte to indicate programming it disabled
} 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
I2C_CR2 &= I2C_CR2_ACK; // NACK next received byte to indicate programming error
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
}
if (action) { // something has been performed, check if other flags have been set meanwhile
action = false; // clear flag
} else { // nothing down
@ -57,3 +549,49 @@ 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
}
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
I2C_DR = *(uint8_t*)(EEPROM_ADDR + i2c_addr++); // transmit next byte (even if invalid)
}
if (sr1 & I2C_SR1_RXNE) { // receive buffer is full
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
}
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
}
}
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
I2C_DR = *(uint8_t*)(EEPROM_ADDR + i2c_addr++); // transmit selected byte
i2c_input_new = false; // notify we send data
} else { // we will receive data
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
}
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
}
}

View File

@ -20,13 +20,13 @@
static uint16_t period = 0;
// port for data line
#define SDA_PORT GPIO_PB
#define SDA_PORT GPIO_PD
// pin for data line
#define SDA_PIN PB5
#define SDA_PIN PD2
// port for clock line
#define SCL_PORT GPIO_PB
#define SCL_PORT GPIO_PD
// pin for clock line
#define SCL_PIN PB4
#define SCL_PIN PD3
// operation timeout (in half period)
#define TIMEOUT 10U
@ -83,11 +83,11 @@ bool softi2c_master_setup(uint16_t freq_khz)
// switch pins to open drain
SCL_PORT->ODR.reg |= SCL_PIN; // ensure clock is high
SCL_PORT->DDR.reg |= SCL_PIN; // switch pin to output
SCL_PORT->CR1.reg &= ~SCL_PIN; // use in open-drain mode
SCL_PORT->DDR.reg |= SCL_PIN; // switch pin to output
SDA_PORT->ODR.reg |= SDA_PIN; // ensure data is high
SDA_PORT->DDR.reg |= SDA_PIN; // switch pin to output
SDA_PORT->CR1.reg &= ~SDA_PIN; // use in open-drain mode
SDA_PORT->DDR.reg |= SDA_PIN; // switch pin to output
I2C_delay(); // give time to get high
return (read_SCL() && read_SDA()); // line is ready when the two lines are high