[coreboot-gerrit] New patch to review for coreboot: 5af09bc broadcom/cygnus: Implement I2C driver

Patrick Georgi (pgeorgi@google.com) gerrit at coreboot.org
Tue Apr 21 15:19:02 CEST 2015


Patrick Georgi (pgeorgi at google.com) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/9908

-gerrit

commit 5af09bc7cbc86290ea018328acdfc924e98e622d
Author: Anatol Pomazau <anatol at google.com>
Date:   Wed Feb 4 10:09:27 2015 -0800

    broadcom/cygnus: Implement I2C driver
    
    BUG=chrome-os-partner:35810
    BRANCH=purin
    TEST=Enable I2C1, reset devboard codec, read a register.
         Here is the code that demonstrates how I2C works:
    
    	i2c_init(1, 100*KHz);
    	mdelay(50);
    
    	int rc = i2c_writeb(1, 0x18, 1, 0x80); // reset codec
    	printk(BIOS_INFO, "I2C reset rc=%d\n", rc);
    	mdelay(50);
    	uint8_t data = 0;
    	rc = i2c_readb(1, 0x18, 43, &data);
    	printk(BIOS_INFO, "I2C read rc=%d data=%x\n", rc, data); // data == 0x80
    
    Change-Id: I0d202f8b0375b5ccd9f71b23fb0cadd5a70ae779
    Signed-off-by: Patrick Georgi <pgeorgi at chromium.org>
    Original-Commit-Id: 6bbe9afe3dccd104f39c2c286d3765a28ea20141
    Original-Signed-off-by: Anatol Pomazau <anatol at google.com>
    Original-Reviewed-on: https://chrome-internal-review.googlesource.com/195706
    Original-Reviewed-by: Daisuke Nojiri <dnojiri at google.com>
    Original-Reviewed-by: Anatol Pomazau <anatol at google.com>
    Original-Commit-Queue: Anatol Pomazau <anatol at google.com>
    Original-Tested-by: Anatol Pomazau <anatol at google.com>
    Original-Change-Id: I178acef9de18fa854983294edcd2c05886795e2a
    Original-Reviewed-on: https://chromium-review.googlesource.com/263496
    Original-Commit-Queue: Daisuke Nojiri <dnojiri at chromium.org>
    Original-Trybot-Ready: Daisuke Nojiri <dnojiri at chromium.org>
    Original-Tested-by: Daisuke Nojiri <dnojiri at chromium.org>
    Original-Reviewed-by: Daisuke Nojiri <dnojiri at chromium.org>
---
 src/soc/broadcom/cygnus/i2c.c | 240 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 238 insertions(+), 2 deletions(-)

diff --git a/src/soc/broadcom/cygnus/i2c.c b/src/soc/broadcom/cygnus/i2c.c
index a6865b9..e503224 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(&reg_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(&reg_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(&reg_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(&reg_addr->i2c_master_data_wr, val);
+	}
+	if (segment->len == 0)
+		write32(&reg_addr->i2c_master_data_wr, I2C_MASTER_WR_STATUS);
+
+	/*
+	 * Now we can activate the transfer.
+	 */
+	write32(&reg_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(&reg_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(&reg_addr->i2c_master_data_wr, segment->chip << 1 | 1);
+
+	/*
+	 * Now we can activate the transfer. Specify the number of bytes to read
+	 */
+	write32(&reg_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(&reg_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(&reg_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(&regs->i2c_con, I2C_SMB_RESET);
+	udelay(100); /* wait 100 usec per spec */
+	clrbits_le32(&regs->i2c_con, I2C_SMB_RESET);
+
+	switch (hz) {
+	case 100000:
+		clrbits_le32(&regs->i2c_timing_con, I2C_MODE_400);
+		break;
+	case 400000:
+		setbits_le32(&regs->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(&regs->i2c_int_en, 0);
+
+	/* clear all pending interrupts */
+	write32(&regs->i2c_int_status, 0xffffffff);
+
+	write32(&regs->i2c_con, I2C_SMB_EN);
 }



More information about the coreboot-gerrit mailing list