diff options
Diffstat (limited to 'src/soc/broadcom/cygnus/i2c.c')
-rw-r--r-- | src/soc/broadcom/cygnus/i2c.c | 240 |
1 files changed, 238 insertions, 2 deletions
diff --git a/src/soc/broadcom/cygnus/i2c.c b/src/soc/broadcom/cygnus/i2c.c index a6865b9b33..e5032243fc 100644 --- a/src/soc/broadcom/cygnus/i2c.c +++ b/src/soc/broadcom/cygnus/i2c.c @@ -17,16 +17,252 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <arch/io.h> +#include <assert.h> #include <console/console.h> +#include <delay.h> #include <device/i2c.h> #include <soc/i2c.h> -int platform_i2c_transfer(unsigned bus, struct i2c_seg *segments, int seg_count) +struct cygnus_i2c_regs { + u32 i2c_con; + u32 i2c_timing_con; + u32 i2c_addr; + u32 i2c_fifo_master; + u32 i2c_fifo_slave; + u32 i2c_bit_bang; + u32 reserved0[(0x30 - 0x18) / 4]; + u32 i2c_master_comm; + u32 i2c_slave_comm; + u32 i2c_int_en; + u32 i2c_int_status; + u32 i2c_master_data_wr; + u32 i2c_master_data_rd; + u32 i2c_slave_data_wr; + u32 i2c_slave_data_rd; + u32 reserved1[(0xb0 - 0x50) / 4]; + u32 i2c_timing_con2; +}; + +static struct cygnus_i2c_regs *i2c_bus[] = { + (struct cygnus_i2c_regs *)0x18008000, + (struct cygnus_i2c_regs *)0x1800b000, +}; + +#define I2C_TIMEOUT_US 100000 /* 100ms */ +#define I2C_FIFO_MAX_SIZE 64 + +#define ETIMEDOUT 1 +#define EINVAL 2 +#define EBUSY 3 + +/* Configuration (0x0) */ +#define I2C_SMB_RESET (1 << 31) +#define I2C_SMB_EN (1 << 30) + +/* Timing configuration (0x4) */ +#define I2C_MODE_400 (1 << 31) + +/* Master FIFO control (0xc) */ +#define I2C_MASTER_RX_FIFO_FLUSH (1 << 31) +#define I2C_MASTER_TX_FIFO_FLUSH (1 << 30) + +/* Master command (0x30) */ +#define I2C_MASTER_START_BUSY (1 << 31) +#define I2C_MASTER_STATUS_SFT 25 +#define I2C_MASTER_STATUS_MASK (0x7 << I2C_MASTER_STATUS_SFT) +#define I2C_MASTER_PROT_SFT 9 +#define I2C_MASTER_PROT_BLK_WR (0x7 << I2C_MASTER_PROT_SFT) +#define I2C_MASTER_PROT_BLK_RD (0x8 << I2C_MASTER_PROT_SFT) + +/* Master data write (0x40) */ +#define I2C_MASTER_WR_STATUS (1 << 31) + +/* Master data read (0x44) */ +#define I2C_MASTER_RD_DATA_MASK 0xff + +static unsigned int i2c_bus_busy(struct cygnus_i2c_regs *reg_addr) +{ + return read32(®_addr->i2c_master_comm) & I2C_MASTER_START_BUSY; +} + +static int i2c_wait_bus_busy(struct cygnus_i2c_regs *reg_addr) +{ + int timeout = I2C_TIMEOUT_US; + while (timeout--) { + if (!i2c_bus_busy(reg_addr)) + break; + udelay(1); + } + + if (timeout <= 0) + return ETIMEDOUT; + + return 0; +} + +static void i2c_flush_fifo(struct cygnus_i2c_regs *reg_addr) +{ + write32(®_addr->i2c_fifo_master, + I2C_MASTER_RX_FIFO_FLUSH | I2C_MASTER_TX_FIFO_FLUSH); +} + +static int i2c_write(struct cygnus_i2c_regs *reg_addr, struct i2c_seg *segment) +{ + uint8_t *data = segment->buf; + unsigned int val, status; + int i, ret; + + write32(®_addr->i2c_master_data_wr, segment->chip << 1); + + for (i = 0; i < segment->len; i++) { + val = data[i]; + + /* mark the last byte */ + if (i == segment->len - 1) + val |= I2C_MASTER_WR_STATUS; + + write32(®_addr->i2c_master_data_wr, val); + } + if (segment->len == 0) + write32(®_addr->i2c_master_data_wr, I2C_MASTER_WR_STATUS); + + /* + * Now we can activate the transfer. + */ + write32(®_addr->i2c_master_comm, + I2C_MASTER_START_BUSY | I2C_MASTER_PROT_BLK_WR); + + ret = i2c_wait_bus_busy(reg_addr); + if (ret) { + printk(BIOS_ERR, "I2C bus timeout\n"); + goto flush_fifo; + } + + /* check transaction successful */ + status = read32(®_addr->i2c_master_comm); + ret = (status & I2C_MASTER_STATUS_MASK) >> I2C_MASTER_STATUS_SFT; + if (ret) { + printk(BIOS_ERR, "I2C write error %u\n", status); + goto flush_fifo; + } + + return 0; + +flush_fifo: + i2c_flush_fifo(reg_addr); + return ret; +} + +static int i2c_read(struct cygnus_i2c_regs *reg_addr, struct i2c_seg *segment) { + uint8_t *data = segment->buf; + int i, ret; + unsigned int status; + + write32(®_addr->i2c_master_data_wr, segment->chip << 1 | 1); + + /* + * Now we can activate the transfer. Specify the number of bytes to read + */ + write32(®_addr->i2c_master_comm, + I2C_MASTER_START_BUSY | I2C_MASTER_PROT_BLK_RD | segment->len); + + ret = i2c_wait_bus_busy(reg_addr); + if (ret) { + printk(BIOS_ERR, "I2C bus timeout\n"); + goto flush_fifo; + } + + /* check transaction successful */ + status = read32(®_addr->i2c_master_comm); + ret = (status & I2C_MASTER_STATUS_MASK) >> I2C_MASTER_STATUS_SFT; + if (ret) { + printk(BIOS_ERR, "I2C read error %u\n", status); + goto flush_fifo; + } + + for (i = 0; i < segment->len; i++) + data[i] = read32(®_addr->i2c_master_data_rd) & + I2C_MASTER_RD_DATA_MASK; + return 0; + +flush_fifo: + i2c_flush_fifo(reg_addr); + return ret; +} + +static int i2c_do_xfer(struct cygnus_i2c_regs *reg_addr, + struct i2c_seg *segment) +{ + int ret; + + if (segment->len > I2C_FIFO_MAX_SIZE - 1) { + printk(BIOS_ERR, + "I2C transfer error: segment size (%d) is larger than limit (%d)\n", + segment->len, I2C_FIFO_MAX_SIZE); + return EINVAL; + } + + if (i2c_bus_busy(reg_addr)) { + printk(BIOS_WARNING, "I2C transfer error: bus is busy\n"); + return EBUSY; + } + + if (segment->read) + ret = i2c_read(reg_addr, segment); + else + ret = i2c_write(reg_addr, segment); + + return ret; +} + +int platform_i2c_transfer(unsigned bus, struct i2c_seg *segments, int seg_count) +{ + int i; + int res = 0; + struct cygnus_i2c_regs *regs = i2c_bus[bus]; + struct i2c_seg *seg = segments; + + for (i = 0; i < seg_count; i++, seg++) { + res = i2c_do_xfer(regs, seg); + if (res) + break; + } + return res; } void i2c_init(unsigned int bus, unsigned int hz) { - printk(BIOS_INFO, "i2c initialization is not implemented\n"); + struct cygnus_i2c_regs *regs = i2c_bus[bus]; + + assert(bus >= 0 && bus <= 1); + + setbits_le32(®s->i2c_con, I2C_SMB_RESET); + udelay(100); /* wait 100 usec per spec */ + clrbits_le32(®s->i2c_con, I2C_SMB_RESET); + + switch (hz) { + case 100000: + clrbits_le32(®s->i2c_timing_con, I2C_MODE_400); + break; + case 400000: + setbits_le32(®s->i2c_timing_con, I2C_MODE_400); + break; + default: + printk(BIOS_ERR, "I2C bus does not support frequency %d Hz\n", + hz); + break; + } + + i2c_flush_fifo(regs); + + /* disable all interrupts */ + write32(®s->i2c_int_en, 0); + + /* clear all pending interrupts */ + write32(®s->i2c_int_status, 0xffffffff); + + write32(®s->i2c_con, I2C_SMB_EN); } |