panda/board/stm32h7/lli2c.h

153 lines
3.8 KiB
C

// TODO: this driver relies heavily on polling,
// if we want it to be more async, we should use interrupts
#define I2C_TIMEOUT_US 100000U
// cppcheck-suppress misra-c2012-2.7; not sure why it triggers here?
bool i2c_status_wait(const volatile uint32_t *reg, uint32_t mask, uint32_t val) {
uint32_t start_time = microsecond_timer_get();
while(((*reg & mask) != val) && (get_ts_elapsed(microsecond_timer_get(), start_time) < I2C_TIMEOUT_US));
return ((*reg & mask) == val);
}
bool i2c_write_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t value) {
// Setup transfer and send START + addr
bool ret = false;
for(uint32_t i=0U; i<10U; i++) {
register_clear_bits(&I2C->CR2, I2C_CR2_ADD10);
I2C->CR2 = ((uint32_t)addr << 1U) & I2C_CR2_SADD_Msk;
register_clear_bits(&I2C->CR2, I2C_CR2_RD_WRN);
register_set_bits(&I2C->CR2, I2C_CR2_AUTOEND);
I2C->CR2 |= 2UL << I2C_CR2_NBYTES_Pos;
I2C->CR2 |= I2C_CR2_START;
if(!i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U)) {
continue;
}
// check if we lost arbitration
if ((I2C->ISR & I2C_ISR_ARLO) != 0U) {
register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF);
} else {
ret = true;
break;
}
}
if (!ret) {
goto end;
}
// Send data
ret = i2c_status_wait(&I2C->ISR, I2C_ISR_TXIS, I2C_ISR_TXIS);
if(!ret) {
goto end;
}
I2C->TXDR = reg;
ret = i2c_status_wait(&I2C->ISR, I2C_ISR_TXIS, I2C_ISR_TXIS);
if(!ret) {
goto end;
}
I2C->TXDR = value;
end:
return ret;
}
bool i2c_read_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t *value) {
// Setup transfer and send START + addr
bool ret = false;
for(uint32_t i=0U; i<10U; i++) {
register_clear_bits(&I2C->CR2, I2C_CR2_ADD10);
I2C->CR2 = ((uint32_t)addr << 1U) & I2C_CR2_SADD_Msk;
register_clear_bits(&I2C->CR2, I2C_CR2_RD_WRN);
register_clear_bits(&I2C->CR2, I2C_CR2_AUTOEND);
I2C->CR2 |= 1UL << I2C_CR2_NBYTES_Pos;
I2C->CR2 |= I2C_CR2_START;
if(!i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U)) {
continue;
}
// check if we lost arbitration
if ((I2C->ISR & I2C_ISR_ARLO) != 0U) {
register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF);
} else {
ret = true;
break;
}
}
if (!ret) {
goto end;
}
// Send data
ret = i2c_status_wait(&I2C->ISR, I2C_ISR_TXIS, I2C_ISR_TXIS);
if(!ret) {
goto end;
}
I2C->TXDR = reg;
// Restart
I2C->CR2 = (((addr << 1) | 0x1U) & I2C_CR2_SADD_Msk) | (1UL << I2C_CR2_NBYTES_Pos) | I2C_CR2_RD_WRN | I2C_CR2_START;
ret = i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U);
if(!ret) {
goto end;
}
// check if we lost arbitration
if ((I2C->ISR & I2C_ISR_ARLO) != 0U) {
register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF);
ret = false;
goto end;
}
// Read data
ret = i2c_status_wait(&I2C->ISR, I2C_ISR_RXNE, I2C_ISR_RXNE);
if(!ret) {
goto end;
}
*value = I2C->RXDR;
// Stop
I2C->CR2 |= I2C_CR2_STOP;
end:
return ret;
}
bool i2c_set_reg_bits(I2C_TypeDef *I2C, uint8_t address, uint8_t regis, uint8_t bits) {
uint8_t value;
bool ret = i2c_read_reg(I2C, address, regis, &value);
if(ret) {
ret = i2c_write_reg(I2C, address, regis, value | bits);
}
return ret;
}
bool i2c_clear_reg_bits(I2C_TypeDef *I2C, uint8_t address, uint8_t regis, uint8_t bits) {
uint8_t value;
bool ret = i2c_read_reg(I2C, address, regis, &value);
if(ret) {
ret = i2c_write_reg(I2C, address, regis, value & (uint8_t) (~bits));
}
return ret;
}
bool i2c_set_reg_mask(I2C_TypeDef *I2C, uint8_t address, uint8_t regis, uint8_t value, uint8_t mask) {
uint8_t old_value;
bool ret = i2c_read_reg(I2C, address, regis, &old_value);
if(ret) {
ret = i2c_write_reg(I2C, address, regis, (old_value & (uint8_t) (~mask)) | (value & mask));
}
return ret;
}
void i2c_init(I2C_TypeDef *I2C) {
// 100kHz clock speed
I2C->TIMINGR = 0x107075B0;
I2C->CR1 = I2C_CR1_PE;
}