diff --git a/devices/i2c.h b/devices/i2c.h index b01816f..1761a52 100644 --- a/devices/i2c.h +++ b/devices/i2c.h @@ -27,12 +27,15 @@ along with this program. If not, see . #ifndef I2C_H #define I2C_H -#include +#include +#include +#include /** 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 dev_list; /* list of registered I2C devices */ + I2CDevice *dev_list[128]; /* list of registered I2C devices */ }; #endif /* I2C_H */ diff --git a/devices/viacuda.cpp b/devices/viacuda.cpp index bd5c2ef..cdcba77 100644 --- a/devices/viacuda.cpp +++ b/devices/viacuda.cpp @@ -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; } } diff --git a/devices/viacuda.h b/devices/viacuda.h index c1eaf34..9fba475 100644 --- a/devices/viacuda.h +++ b/devices/viacuda.h @@ -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,