// SPDX-License-Identifier: GPL-3.0-only #include #include #include //TODO: find best value #define I2C_TIMEOUT 10000 struct I2C { volatile uint8_t * hosta; volatile uint8_t * hoctl; volatile uint8_t * hoctl2; volatile uint8_t * hobdb; volatile uint8_t * trasla; }; struct I2C __code I2C_0 = { .hosta = HOSTAA, .hoctl = HOCTLA, .hoctl2 = HOCTL2A, .hobdb = HOBDBA, .trasla = TRASLAA, }; struct I2C __code I2C_1 = { .hosta = HOSTAB, .hoctl = HOCTLB, .hoctl2 = HOCTL2B, .hobdb = HOBDBB, .trasla = TRASLAB, }; #ifdef it5570e struct I2C __code I2C_4 = { .hosta = HOSTAE, .hoctl = HOCTLE, .hoctl2 = HOCTL2E, .hobdb = HOBDBE, .trasla = TRASLAE, }; #endif void i2c_reset(struct I2C * i2c, bool kill) { if (*(i2c->hosta) & HOSTA_BUSY) { // Set kill bit if (kill) *(i2c->hoctl) |= BIT(1); // Wait for host to finish while (*(i2c->hosta) & HOSTA_BUSY) {} } // Clear status register *(i2c->hosta) = *(i2c->hosta); // Clear current command *(i2c->hoctl) = 0; // Disable host interface *(i2c->hoctl2) = 0; } int16_t i2c_start(struct I2C * i2c, uint8_t addr, bool read) __reentrant { // If we are already in a transaction if (*(i2c->hosta) & HOSTA_BYTE_DONE) { // If we are switching direction if ((*(i2c->trasla) & 1) != read) { // If we are switching to read mode if (read) { // Enable direction switch *(i2c->hoctl2) |= BIT(3) | BIT(2); } else { // Unsupported! i2c_reset(i2c, true); return -1; } } } else { i2c_reset(i2c, true); // Enable host controller with i2c compatibility *(i2c->hoctl2) = BIT(1) | BIT(0); // Set address *(i2c->trasla) = (addr << 1) | read; } return 0; } void i2c_stop(struct I2C * i2c) { // Disable i2c compatibility *(i2c->hoctl2) &= ~BIT(1); // Clear status *(i2c->hosta) = *(i2c->hosta); i2c_reset(i2c, false); } static int16_t i2c_transaction(struct I2C * i2c, uint8_t * data, uint16_t length, bool read) { uint16_t i; for (i = 0; i < length; i++) { if (read) { // If last byte if ((i + 1) == length) { // Set last byte bit *(i2c->hoctl) |= BIT(5); } } else { // Write byte *(i2c->hobdb) = data[i]; } // If we are already in a transaction if (*(i2c->hosta) & HOSTA_BYTE_DONE) { // Clear status to process next byte *(i2c->hosta) = *(i2c->hosta); } else { // Start new transaction *(i2c->hoctl) = BIT(6) | (0b111 << 2); } // If we are waiting on direction switch if (*(i2c->hoctl2) & BIT(2)) { // Complete direction switch *(i2c->hoctl2) &= ~BIT(2); } // Wait for byte done, timeout, or error uint8_t status; uint32_t timeout = I2C_TIMEOUT; for(timeout = I2C_TIMEOUT; timeout > 0; timeout--) { status = *(i2c->hosta); // If error occured, kill transaction and return error if (status & HOSTA_ERR) { i2c_reset(i2c, true); return -(int16_t)(status); } else // If byte done, break if (status & HOSTA_BYTE_DONE) { break; } } // If timeout occured, kill transaction and return error if (timeout == 0) { i2c_reset(i2c, true); return -(0x1000 | (int16_t)status); } if (read) { // Read byte data[i] = *(i2c->hobdb); } } return i; } int16_t i2c_read(struct I2C * i2c, uint8_t * data, uint16_t length) __reentrant { return i2c_transaction(i2c, data, length, true); } int16_t i2c_write(struct I2C * i2c, uint8_t * data, uint16_t length) __reentrant { return i2c_transaction(i2c, data, length, false); }