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,