Finish I2C bus emulation.

This commit also cleans up and improve I2C commands
in Cuda. Also removes some related hacks.
This commit is contained in:
Maxim Poliakovski 2020-03-15 13:28:35 +01:00
parent 753e445b4b
commit 9c8548c238
3 changed files with 136 additions and 71 deletions

View File

@ -27,12 +27,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef I2C_H
#define I2C_H
#include <vector>
#include <thirdparty/loguru.hpp>
#include <string>
#include <stdexcept>
/** Base class for I2C devices */
class I2CDevice {
public:
virtual void start_transaction() = 0;
virtual bool send_subaddress(uint8_t sub_addr) = 0;
virtual bool send_byte(uint8_t data) = 0;
virtual bool receive_byte(uint8_t *p_data) = 0;
};
@ -40,23 +43,33 @@ public:
/** Base class for I2C hosts */
class I2CBus {
public:
I2CBus() { this->dev_list.clear(); };
~I2CBus() { this->dev_list.clear(); };
I2CBus() { std::memset(this->dev_list, 0, sizeof(this->dev_list)); };
~I2CBus() { std::memset(this->dev_list, 0, sizeof(this->dev_list)); };
virtual bool register_device(uint8_t dev_addr, I2CDevice *dev_obj) {
if (!this->dev_list[dev_addr]) {
return false; /* device address already taken */
virtual void register_device(uint8_t dev_addr, I2CDevice *dev_obj) {
if (this->dev_list[dev_addr]) {
throw std::invalid_argument(std::string("I2C address already taken!"));
}
this->dev_list[dev_addr] = dev_obj;
return true;
LOG_F(INFO, "New I2C device, address = 0x%X", dev_addr);
};
virtual void start_transaction(uint8_t dev_addr) {
virtual bool start_transaction(uint8_t dev_addr) {
if (this->dev_list[dev_addr]) {
this->dev_list[dev_addr]->start_transaction();
return true;
} else {
return false;
}
};
virtual bool send_subaddress(uint8_t dev_addr, uint8_t sub_addr) {
if (!this->dev_list[dev_addr]) {
return false; /* no device -> no acknowledge */
}
return this->dev_list[dev_addr]->send_subaddress(sub_addr);
};
virtual bool send_byte(uint8_t dev_addr, uint8_t data) {
if (!this->dev_list[dev_addr]) {
return false; /* no device -> no acknowledge */
@ -72,7 +85,7 @@ public:
};
protected:
std::vector<I2CDevice *> dev_list; /* list of registered I2C devices */
I2CDevice *dev_list[128]; /* list of registered I2C devices */
};
#endif /* I2C_H */

View File

@ -202,22 +202,39 @@ void ViaCuda::write(uint8_t new_state)
}
}
else { /* data transfer: Cuda --> Host */
if (this->out_count) {
this->via_regs[VIA_SR] = this->out_buf[this->out_pos++];
if (this->out_pos >= this->out_count) {
LOG_F(9, "Cuda: sending last byte \n");
this->out_count = 0;
this->via_regs[VIA_B] |= CUDA_TREQ; /* negate TREQ */
this->treq = 1;
}
assert_sr_int(); /* tell the system we've written the data */
}
(this->*out_handler)();
assert_sr_int(); /* tell the system we've written the data */
}
}
}
/* sends zeros to host at infinitum */
void ViaCuda::null_out_handler()
{
this->via_regs[VIA_SR] = 0;
}
/* sends data from out_buf until exhausted, then switches to next_out_handler */
void ViaCuda::out_buf_handler()
{
if (this->out_pos < this->out_count) {
LOG_F(9, "OutBufHandler: sending next byte 0x%X", this->out_buf[this->out_pos]);
this->via_regs[VIA_SR] = this->out_buf[this->out_pos++];
}
else if (this->is_open_ended) {
LOG_F(9, "OutBufHandler: switching to next handler");
this->out_handler = this->next_out_handler;
this->next_out_handler = &ViaCuda::null_out_handler;
(this->*out_handler)();
}
else {
LOG_F(9, "Sending last byte");
this->out_count = 0;
this->via_regs[VIA_B] |= CUDA_TREQ; /* negate TREQ */
this->treq = 1;
}
}
void ViaCuda::response_header(uint32_t pkt_type, uint32_t pkt_flag)
{
this->out_buf[0] = pkt_type;
@ -225,6 +242,9 @@ void ViaCuda::response_header(uint32_t pkt_type, uint32_t pkt_flag)
this->out_buf[2] = this->in_buf[1]; /* copy original cmd */
this->out_count = 3;
this->out_pos = 0;
this->out_handler = &ViaCuda::out_buf_handler;
this->next_out_handler = &ViaCuda::null_out_handler;
this->is_open_ended = false;
}
void ViaCuda::error_response(uint32_t error)
@ -235,12 +255,16 @@ void ViaCuda::error_response(uint32_t error)
this->out_buf[3] = this->in_buf[1]; /* copy original cmd */
this->out_count = 4;
this->out_pos = 0;
this->out_handler = &ViaCuda::out_buf_handler;
this->next_out_handler = &ViaCuda::null_out_handler;
this->is_open_ended = false;
}
void ViaCuda::process_packet()
{
if (this->in_count < 2) {
LOG_F(ERROR, "Cuda: invalid packet (too few data)! \n");
LOG_F(ERROR, "Cuda: invalid packet (too few data)!\n");
error_response(CUDA_ERR_BAD_SIZE);
return;
}
@ -260,6 +284,7 @@ void ViaCuda::process_packet()
break;
default:
LOG_F(ERROR, "Cuda: unsupported packet type = %d \n", (uint32_t)(this->in_buf[0]));
error_response(CUDA_ERR_BAD_PKT);
}
}
@ -302,20 +327,9 @@ void ViaCuda::pseudo_command(int cmd, int data_count)
break;
case CUDA_READ_WRITE_I2C:
response_header(CUDA_PKT_PSEUDO, 0);
/* bit 0 of the I2C address byte indicates operation kind:
0 - write to device, 1 - read from device
In the case of reading, Cuda will append one-byte result
to the response packet header */
i2c_simple_transaction(this->in_buf[2], &this->in_buf[3], this->in_count - 3);
break;
case CUDA_COMB_FMT_I2C:
/* HACK:
This command performs the so-called open-ended transaction, i.e.
Cuda will continue to send data as long as handshaking is completed
for each byte. To support that, we'd need another emulation approach.
Fortunately, HWInit is known to read/write max. 4 bytes at once
so we're going to use a prefilled buffer to make it work.
*/
response_header(CUDA_PKT_PSEUDO, 0);
if (this->in_count >= 5) {
i2c_comb_transaction(this->in_buf[2], this->in_buf[3], this->in_buf[4],
@ -332,58 +346,84 @@ void ViaCuda::pseudo_command(int cmd, int data_count)
}
}
/* sends data from the current I2C to host ad infinitum */
void ViaCuda::i2c_handler()
{
this->receive_byte(this->curr_i2c_addr, &this->via_regs[VIA_SR]);
}
void ViaCuda::i2c_simple_transaction(uint8_t dev_addr, const uint8_t* in_buf,
int in_bytes)
{
int tr_type = dev_addr & 1;
int op_type = dev_addr & 1; /* 0 - write to device, 1 - read from device */
switch (dev_addr & 0xFE) {
case 0x50: /* unknown device on the Gossamer board */
if (tr_type) { /* read */
/* send dummy byte for now */
this->out_buf[this->out_count++] = 0xDD;
}
else {
/* ignore writes */
}
break;
default:
LOG_F(ERROR, "Unsupported I2C device 0x%x \n", (int)dev_addr);
dev_addr >>= 1; /* strip RD/WR bit */
if (!this->start_transaction(dev_addr)) {
LOG_F(WARNING, "Unsupported I2C device 0x%X \n", (int)(dev_addr));
error_response(CUDA_ERR_I2C);
return;
}
/* send data to the target I2C device until there is no more data to send
or the target device doesn't acknowledge that indicates an error */
for (int i = 0; i < in_bytes; i++) {
if (!this->send_byte(dev_addr, in_buf[i])) {
LOG_F(WARNING, "NO_ACK during sending, device 0x%X \n", (int)(dev_addr));
error_response(CUDA_ERR_I2C);
return;
}
}
if (op_type) { /* read request initiate an open ended transaction */
this->curr_i2c_addr = dev_addr;
this->out_handler = &ViaCuda::out_buf_handler;
this->next_out_handler = &ViaCuda::i2c_handler;
this->is_open_ended = true;
}
}
void ViaCuda::i2c_comb_transaction(uint8_t dev_addr, uint8_t sub_addr,
uint8_t dev_addr1, const uint8_t* in_buf, int in_bytes)
{
int tr_type = dev_addr1 & 1;
int op_type = dev_addr1 & 1; /* 0 - write to device, 1 - read from device */
if ((dev_addr & 0xFE) != (dev_addr1 & 0xFE)) {
LOG_F(ERROR, "I2C combined, dev_addr mismatch! \n");
LOG_F(ERROR, "Combined I2C: dev_addr mismatch!\n");
error_response(CUDA_ERR_I2C);
return;
}
switch (dev_addr1 & 0xFE) {
case 0xAE: /* SDRAM EEPROM, no clue which one */
if (tr_type) { /* read */
if (sub_addr != 2) {
LOG_F(ERROR, "Unsupported read position 0x%x in SDRAM EEPROM 0x%x", \
(int)sub_addr, (int)dev_addr1);
return;
}
/* FIXME: hardcoded SPD EEPROM values! This should be a proper
I2C device with user-configurable params */
this->out_buf[this->out_count++] = 0x04; /* memory type = SDRAM */
this->out_buf[this->out_count++] = 0x0B; /* row address bits per bank */
this->out_buf[this->out_count++] = 0x09; /* col address bits per bank */
this->out_buf[this->out_count++] = 0x02; /* num of RAM banks */
}
else {
/* ignore writes */
}
break;
default:
LOG_F(ERROR, "Unsupported I2C device 0x%x \n", (int)dev_addr1);
dev_addr >>= 1; /* strip RD/WR bit */
if (!this->start_transaction(dev_addr)) {
LOG_F(WARNING, "Unsupported I2C device 0x%X \n", (int)(dev_addr));
error_response(CUDA_ERR_I2C);
return;
}
if (!this->send_subaddress(dev_addr, sub_addr)) {
LOG_F(WARNING, "NO_ACK while sending subaddress, device 0x%X \n", (int)(dev_addr));
error_response(CUDA_ERR_I2C);
return;
}
/* send data to the target I2C device until there is no more data to send
or the target device doesn't acknowledge that indicates an error */
for (int i = 0; i < in_bytes; i++) {
if (!this->send_byte(dev_addr, in_buf[i])) {
LOG_F(WARNING, "NO_ACK during sending, device 0x%X \n", (int)(dev_addr));
error_response(CUDA_ERR_I2C);
return;
}
}
if (!op_type) { /* return dummy response for writes */
LOG_F(WARNING, "Combined I2C - write request!");
} else {
this->curr_i2c_addr = dev_addr;
this->out_handler = &ViaCuda::out_buf_handler;
this->next_out_handler = &ViaCuda::i2c_handler;
this->is_open_ended = true;
}
}

View File

@ -96,8 +96,10 @@ enum {
/** Cuda error codes. */
enum {
CUDA_ERR_BAD_CMD = 2, /* invalid pseudo command */
CUDA_ERR_I2C = 5 /* invalid I2C data or no acknowledge */
CUDA_ERR_BAD_PKT = 1, /* invalid packet type */
CUDA_ERR_BAD_CMD = 2, /* invalid pseudo command */
CUDA_ERR_BAD_SIZE = 3, /* invalid packet size */
CUDA_ERR_I2C = 5 /* invalid I2C data or no acknowledge */
};
@ -127,6 +129,12 @@ private:
int32_t out_count;
int32_t out_pos;
bool is_open_ended;
uint8_t curr_i2c_addr;
void (ViaCuda::*out_handler)(void);
void (ViaCuda::*next_out_handler)(void);
NVram* pram_obj;
void print_enabled_ints(); /* print enabled VIA interrupts and their sources */
@ -142,6 +150,10 @@ private:
void process_adb_command(uint8_t cmd_byte, int data_count);
void pseudo_command(int cmd, int data_count);
void null_out_handler(void);
void out_buf_handler(void);
void i2c_handler(void);
/* I2C related methods */
void i2c_simple_transaction(uint8_t dev_addr, const uint8_t* in_buf, int in_bytes);
void i2c_comb_transaction(uint8_t dev_addr, uint8_t sub_addr, uint8_t dev_addr1,