static uint64_t aspeed_i2c_bus_read(void *opaque, hwaddr offset, unsigned size) { AspeedI2CBus *bus = opaque; switch (offset) { case I2CD_FUN_CTRL_REG: return bus->ctrl; case I2CD_AC_TIMING_REG1: return bus->timing[0]; case I2CD_AC_TIMING_REG2: return bus->timing[1]; case I2CD_INTR_CTRL_REG: return bus->intr_ctrl; case I2CD_INTR_STS_REG: return bus->intr_status; case I2CD_BYTE_BUF_REG: return bus->buf; case I2CD_CMD_REG: return bus->cmd | (i2c_bus_busy(bus->bus) << 16); default: qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, offset); return -1; } }
/* * The state machine needs some refinement. It is only used to track * invalid STOP commands for the moment. */ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) { bus->cmd &= ~0xFFFF; bus->cmd |= value & 0xFFFF; if (bus->cmd & I2CD_M_START_CMD) { uint8_t state = aspeed_i2c_get_state(bus) & I2CD_MACTIVE ? I2CD_MSTARTR : I2CD_MSTART; aspeed_i2c_set_state(bus, state); if (i2c_start_transfer(bus->bus, extract32(bus->buf, 1, 7), extract32(bus->buf, 0, 1))) { bus->intr_status |= I2CD_INTR_TX_NAK; } else { bus->intr_status |= I2CD_INTR_TX_ACK; } /* START command is also a TX command, as the slave address is * sent on the bus */ bus->cmd &= ~(I2CD_M_START_CMD | I2CD_M_TX_CMD); /* No slave found */ if (!i2c_bus_busy(bus->bus)) { return; } aspeed_i2c_set_state(bus, I2CD_MACTIVE); } if (bus->cmd & I2CD_M_TX_CMD) { aspeed_i2c_set_state(bus, I2CD_MTXD); if (i2c_send(bus->bus, bus->buf)) { bus->intr_status |= (I2CD_INTR_TX_NAK); i2c_end_transfer(bus->bus); } else { bus->intr_status |= I2CD_INTR_TX_ACK; } bus->cmd &= ~I2CD_M_TX_CMD; aspeed_i2c_set_state(bus, I2CD_MACTIVE); } if ((bus->cmd & (I2CD_M_RX_CMD | I2CD_M_S_RX_CMD_LAST)) && !(bus->intr_status & I2CD_INTR_RX_DONE)) { aspeed_i2c_handle_rx_cmd(bus); } if (bus->cmd & I2CD_M_STOP_CMD) { if (!(aspeed_i2c_get_state(bus) & I2CD_MACTIVE)) { qemu_log_mask(LOG_GUEST_ERROR, "%s: abnormal stop\n", __func__); bus->intr_status |= I2CD_INTR_ABNORMAL; } else { aspeed_i2c_set_state(bus, I2CD_MSTOP); i2c_end_transfer(bus->bus); bus->intr_status |= I2CD_INTR_NORMAL_STOP; } bus->cmd &= ~I2CD_M_STOP_CMD; aspeed_i2c_set_state(bus, I2CD_IDLE); } }
AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address, uint8_t len, uint8_t *data) { AUXReply ret = AUX_NACK; I2CBus *i2c_bus = aux_get_i2c_bus(bus); size_t i; bool is_write = false; DPRINTF("request at address 0x%" PRIX32 ", command %u, len %u\n", address, cmd, len); switch (cmd) { /* * Forward the request on the AUX bus.. */ case WRITE_AUX: case READ_AUX: is_write = cmd == READ_AUX ? false : true; for (i = 0; i < len; i++) { if (!address_space_rw(&bus->aux_addr_space, address++, MEMTXATTRS_UNSPECIFIED, data++, 1, is_write)) { ret = AUX_I2C_ACK; } else { ret = AUX_NACK; break; } } break; /* * Classic I2C transactions.. */ case READ_I2C: case WRITE_I2C: is_write = cmd == READ_I2C ? false : true; if (i2c_bus_busy(i2c_bus)) { i2c_end_transfer(i2c_bus); } if (i2c_start_transfer(i2c_bus, address, is_write)) { ret = AUX_I2C_NACK; break; } ret = AUX_I2C_ACK; while (len > 0) { if (i2c_send_recv(i2c_bus, data++, is_write) < 0) { ret = AUX_I2C_NACK; break; } len--; } i2c_end_transfer(i2c_bus); break; /* * I2C MOT transactions. * * Here we send a start when: * - We didn't start transaction yet. * - We had a READ and we do a WRITE. * - We changed the address. */ case WRITE_I2C_MOT: case READ_I2C_MOT: is_write = cmd == READ_I2C_MOT ? false : true; ret = AUX_I2C_NACK; if (!i2c_bus_busy(i2c_bus)) { /* * No transactions started.. */ if (i2c_start_transfer(i2c_bus, address, is_write)) { break; } } else if ((address != bus->last_i2c_address) || (bus->last_transaction != cmd)) { /* * Transaction started but we need to restart.. */ i2c_end_transfer(i2c_bus); if (i2c_start_transfer(i2c_bus, address, is_write)) { break; } } bus->last_transaction = cmd; bus->last_i2c_address = address; while (len > 0) { if (i2c_send_recv(i2c_bus, data++, is_write) < 0) { i2c_end_transfer(i2c_bus); break; } len--; } if (len == 0) { ret = AUX_I2C_ACK; } break; default: DPRINTF("Not implemented!\n"); return AUX_NACK; } DPRINTF("reply: %u\n", ret); return ret; }
int chip_i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_size, uint8_t *in, int in_size, int flags) { int ctrl = i2c_port_to_controller(port); volatile struct i2c_status *p_status = i2c_stsobjs + ctrl; if (out_size == 0 && in_size == 0) return EC_SUCCESS; interrupt_disable(); /* make sure bus is not occupied by the other task */ if (p_status->task_waiting != TASK_ID_INVALID) { interrupt_enable(); return EC_ERROR_BUSY; } /* Assign current task ID */ p_status->task_waiting = task_get_current(); interrupt_enable(); /* Select port for multi-ports i2c controller */ i2c_select_port(port); /* Copy data to controller struct */ p_status->flags = flags; p_status->tx_buf = out; p_status->sz_txbuf = out_size; p_status->rx_buf = in; p_status->sz_rxbuf = in_size; #if I2C_7BITS_ADDR /* Set slave address from 7-bits to 8-bits */ p_status->slave_addr = (slave_addr<<1); #else /* Set slave address (8-bits) */ p_status->slave_addr = slave_addr; #endif /* Reset index & error */ p_status->idx_buf = 0; p_status->err_code = SMB_OK; /* Make sure we're in a good state to start */ if ((flags & I2C_XFER_START) && (i2c_bus_busy(ctrl) || (i2c_get_line_levels(port) != I2C_LINE_IDLE))) { /* Attempt to unwedge the controller. */ i2c_unwedge(ctrl); /* recovery i2c controller */ i2c_recovery(ctrl); /* Select port again for recovery */ i2c_select_port(port); } CPUTS("\n"); /* Start master transaction */ i2c_master_transaction(ctrl); /* Reset task ID */ p_status->task_waiting = TASK_ID_INVALID; /* Disable SMB interrupt and New Address Match interrupt source */ i2c_interrupt(ctrl, 0); CPRINTS("-Err:0x%02x\n", p_status->err_code); return (p_status->err_code == SMB_OK) ? EC_SUCCESS : EC_ERROR_UNKNOWN; }