All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] i2c: aspeed: ready driver for upstream
@ 2016-08-21  1:34 Brendan Higgins
  2016-08-21  1:34 ` [PATCH 1/2] eeprom: at24: added quirk for 24c08 devices Brendan Higgins
  2016-08-21  1:34 ` [PATCH 2/2] i2c: aspeed: cleanup driver Brendan Higgins
  0 siblings, 2 replies; 21+ messages in thread
From: Brendan Higgins @ 2016-08-21  1:34 UTC (permalink / raw)
  To: openbmc

Did the cleanup of the i2c-aspeed driver. In the process, I found that the
EEPROM on the ast2500 evbs does not do ACK polling properly after a dummy write
before a read; so I added a quirk for that in the at24 EEPROM driver. It appears
that this problem was not apparent before because the driver took longer to
perform commands.

The goal of this patch set is to get the i2c-aspeed driver ready for upstream.
If you see any issues with the driver that would prevent upstreaming, please let
me know so I can fix it before the patch is applied.

I have tested my changes on the ast2500 evb, but not the ast2400 or any other
variants.

I have a TODO to add support for I2C protocol mangling: I added support for
sending stops between transfers (for the EEPROM quirk), but I did not yet add
support for any other types of I2C protocol mangling; I don't think that this
should cause any issues upstreaming, however.

I also have a change for i2c-core that I am working on for adding slave mode
support for SMBus Alert and I would like to use the i2c-aspeed driver and the
SSIF driver as the initial users of this new feature; I already mentioned this
to Wolfram Sang <wsa@the-dreams.de>.

Cheers,
Brendan

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 1/2] eeprom: at24: added quirk for 24c08 devices
  2016-08-21  1:34 [PATCH 0/2] i2c: aspeed: ready driver for upstream Brendan Higgins
@ 2016-08-21  1:34 ` Brendan Higgins
  2016-08-29  5:05   ` Joel Stanley
  2016-08-29 23:24   ` Xo Wang
  2016-08-21  1:34 ` [PATCH 2/2] i2c: aspeed: cleanup driver Brendan Higgins
  1 sibling, 2 replies; 21+ messages in thread
From: Brendan Higgins @ 2016-08-21  1:34 UTC (permalink / raw)
  To: openbmc; +Cc: Brendan Higgins

Some 24c08 devices exhibits a quirk where they will not NACK durring an
acknowledge poll. Added a STOP after the dummy write to address problem.
---
 drivers/misc/eeprom/at24.c         | 12 +++++++++++-
 include/linux/platform_data/at24.h |  1 +
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c
index 9ceb63b..c647efb 100644
--- a/drivers/misc/eeprom/at24.c
+++ b/drivers/misc/eeprom/at24.c
@@ -120,7 +120,11 @@ static const struct i2c_device_id at24_ids[] = {
 		AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
 	{ "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
 	/* 24rf08 quirk is handled at i2c-core */
-	{ "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
+	/* 24c08 exhibits a quirk where it will not NACK durring an
+	 * acknowledge poll so we have to send a stop after the dummy write in
+	 * a read.
+	 */
+	{ "24c08", AT24_DEVICE_MAGIC(8192 / 8, AT24_FLAG_STOPPOLL) },
 	{ "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
 	{ "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
 	{ "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
@@ -212,6 +216,12 @@ static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
 		msgbuf[i++] = offset;
 
 		msg[0].addr = client->addr;
+		/*
+		 * Some EEPROMs do not perform the acknowledge poll correctly
+		 * and have to send a STOP after the dummy write.
+		 */
+		if (at24->chip.flags & AT24_FLAG_STOPPOLL)
+			msg[0].flags = I2C_M_STOP;
 		msg[0].buf = msgbuf;
 		msg[0].len = i;
 
diff --git a/include/linux/platform_data/at24.h b/include/linux/platform_data/at24.h
index be830b1..1773822 100644
--- a/include/linux/platform_data/at24.h
+++ b/include/linux/platform_data/at24.h
@@ -47,6 +47,7 @@ struct at24_platform_data {
 #define AT24_FLAG_READONLY	0x40	/* sysfs-entry will be read-only */
 #define AT24_FLAG_IRUGO		0x20	/* sysfs-entry will be world-readable */
 #define AT24_FLAG_TAKE8ADDR	0x10	/* take always 8 addresses (24c00) */
+#define AT24_FLAG_STOPPOLL	0x08	/* Send stop before ACK poll */
 
 	void		(*setup)(struct nvmem_device *nvmem, void *context);
 	void		*context;
-- 
2.8.0.rc3.226.g39d4020

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 2/2] i2c: aspeed: cleanup driver
  2016-08-21  1:34 [PATCH 0/2] i2c: aspeed: ready driver for upstream Brendan Higgins
  2016-08-21  1:34 ` [PATCH 1/2] eeprom: at24: added quirk for 24c08 devices Brendan Higgins
@ 2016-08-21  1:34 ` Brendan Higgins
  2016-08-29  5:35   ` Joel Stanley
  1 sibling, 1 reply; 21+ messages in thread
From: Brendan Higgins @ 2016-08-21  1:34 UTC (permalink / raw)
  To: openbmc; +Cc: Brendan Higgins

Rewrote most of master IRQ handler, xfer functions, and error recovery.
Minor refactor of probing functions. Added remove functions.
---
 drivers/i2c/busses/i2c-aspeed.c | 932 ++++++++++++++--------------------------
 1 file changed, 318 insertions(+), 614 deletions(-)

diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
index ece5aa3..542ddef 100644
--- a/drivers/i2c/busses/i2c-aspeed.c
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -1,5 +1,5 @@
 /*
- *  I2C adapter for the ASPEED I2C bus access.
+ *  I2C adapter for the ASPEED I2C bus.
  *
  *  Copyright (C) 2012-2020  ASPEED Technology Inc.
  *  Copyright 2015 IBM Corporation
@@ -7,9 +7,6 @@
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
- *
- *  History:
- *    2012.07.26: Initial version [Ryan Chen]
  */
 
 #include <linux/kernel.h>
@@ -18,76 +15,29 @@
 #include <linux/irq.h>
 #include <linux/irqdomain.h>
 #include <linux/init.h>
-#include <linux/time.h>
-#include <linux/delay.h>
 #include <linux/errno.h>
 #include <linux/interrupt.h>
 #include <linux/completion.h>
 #include <linux/slab.h>
 #include <linux/of_address.h>
 #include <linux/of_platform.h>
-
 #include <linux/platform_device.h>
 #include <linux/err.h>
 #include <linux/clk.h>
 
-#include <linux/dma-mapping.h>
-
-#include <asm/irq.h>
 #include <asm/io.h>
 
-#if defined(CONFIG_COLDFIRE)
-#include <asm/arch/regs-iic.h>
-#include <asm/arch/ast_i2c.h>
-#else
-//#include <plat/regs-iic.h>
-//#include <plat/ast_i2c.h>
-#endif
-
-#define BYTE_MODE	0
-#define BUFF_MODE	1
-#define DEC_DMA_MODE	2
-#define INC_DMA_MODE	3
-
 /* I2C Register */
 #define  I2C_FUN_CTRL_REG    				0x00
-#define  I2C_AC_TIMING_REG1         		0x04
-#define  I2C_AC_TIMING_REG2         		0x08
-#define  I2C_INTR_CTRL_REG					0x0c
-#define  I2C_INTR_STS_REG					0x10
-#define  I2C_CMD_REG						0x14
-#define  I2C_DEV_ADDR_REG					0x18
-#define  I2C_BUF_CTRL_REG					0x1c
-#define  I2C_BYTE_BUF_REG					0x20
-#define  I2C_DMA_BASE_REG					0x24
-#define  I2C_DMA_LEN_REG					0x28
-
-#define AST_I2C_DMA_SIZE				0
-#define AST_I2C_PAGE_SIZE 				256
-#define MASTER_XFER_MODE				BUFF_MODE
-#define SLAVE_XFER_MODE				BYTE_MODE
-#define NUM_BUS 14
-
-/*AST I2C Register Definition */
-// if defined(AST_SOC_G4)
-#define AST_I2C_POOL_BUFF_2048	
-#define AST_I2C_GLOBAL_REG		0x00
-#define AST_I2C_DEVICE1			0x40
-#define AST_I2C_DEVICE2			0x80
-#define AST_I2C_DEVICE3			0xc0
-#define AST_I2C_DEVICE4			0x100
-#define AST_I2C_DEVICE5			0x140
-#define AST_I2C_DEVICE6			0x180
-#define AST_I2C_DEVICE7			0x1c0
-#define AST_I2C_BUFFER_POOL2	0x200
-#define AST_I2C_DEVICE8			0x300
-#define AST_I2C_DEVICE9			0x340
-#define AST_I2C_DEVICE10		0x380
-#define AST_I2C_DEVICE11		0x3c0
-#define AST_I2C_DEVICE12		0x400
-#define AST_I2C_DEVICE13		0x440
-#define AST_I2C_DEVICE14		0x480
-#define AST_I2C_BUFFER_POOL1	0x800
+#define  I2C_AC_TIMING_REG1         			0x04
+#define  I2C_AC_TIMING_REG2         			0x08
+#define  I2C_INTR_CTRL_REG				0x0c
+#define  I2C_INTR_STS_REG				0x10
+#define  I2C_CMD_REG					0x14
+#define  I2C_DEV_ADDR_REG				0x18
+#define  I2C_BYTE_BUF_REG				0x20
+
+#define AST_I2C_NUM_BUS 14
 
 /* Gloable Register Definition */
 /* 0x00 : I2C Interrupt Status Register  */
@@ -95,35 +45,15 @@
 
 /* Device Register Definition */
 /* 0x00 : I2CD Function Control Register  */
-#define AST_I2CD_BUFF_SEL_MASK				(0x7 << 20)
-#define AST_I2CD_BUFF_SEL(x) 				(x << 20)		// page 0 ~ 7
-#define AST_I2CD_M_SDA_LOCK_EN			(0x1 << 16)
 #define AST_I2CD_MULTI_MASTER_DIS			(0x1 << 15)
-#define AST_I2CD_M_SCL_DRIVE_EN		(0x1 << 14)
-#define AST_I2CD_MSB_STS					(0x1 << 9)
 #define AST_I2CD_SDA_DRIVE_1T_EN			(0x1 << 8)
-#define AST_I2CD_M_SDA_DRIVE_1T_EN		(0x1 << 7)
-#define AST_I2CD_M_HIGH_SPEED_EN		(0x1 << 6)
-#define AST_I2CD_DEF_ADDR_EN				(0x1 << 5)
-#define AST_I2CD_DEF_ALERT_EN				(0x1 << 4)
-#define AST_I2CD_DEF_ARP_EN					(0x1 << 3)
-#define AST_I2CD_DEF_GCALL_EN				(0x1 << 2)
-#define AST_I2CD_SLAVE_EN					(0x1 << 1)
-#define AST_I2CD_MASTER_EN					(0x1 )
-
-/* 0x04 : I2CD Clock and AC Timing Control Register #1 */
-#define AST_I2CD_tBUF						(0x1 << 28) 	// 0~7 
-#define AST_I2CD_tHDSTA						(0x1 << 24)		// 0~7 
-#define AST_I2CD_tACST						(0x1 << 20)		// 0~7 
-#define AST_I2CD_tCKHIGH					(0x1 << 16)		// 0~7 
-#define AST_I2CD_tCKLOW						(0x1 << 12)		// 0~7 
-#define AST_I2CD_tHDDAT						(0x1 << 10)		// 0~7 
-#define AST_I2CD_CLK_TO_BASE_DIV			(0x1 << 8)		// 0~3
-#define AST_I2CD_CLK_BASE_DIV				(0x1 )			// 0~0xf
+#define AST_I2CD_M_SDA_DRIVE_1T_EN			(0x1 << 7)
+#define AST_I2CD_M_HIGH_SPEED_EN			(0x1 << 6)
+#define AST_I2CD_SLAVE_EN				(0x1 << 1)
+#define AST_I2CD_MASTER_EN				(0x1)
 
 /* 0x08 : I2CD Clock and AC Timing Control Register #2 */
-#define AST_I2CD_tTIMEOUT					(0x1 )			// 0~7
-#define AST_NO_TIMEOUT_CTRL					0x0
+#define AST_NO_TIMEOUT_CTRL				0x0
 
 
 /* 0x0c : I2CD Interrupt Control Register &
@@ -134,11 +64,6 @@
  */
 #define AST_I2CD_INTR_SDA_DL_TIMEOUT			(0x1 << 14)
 #define AST_I2CD_INTR_BUS_RECOVER_DONE			(0x1 << 13)
-#define AST_I2CD_INTR_SMBUS_ALERT			(0x1 << 12)
-#define AST_I2CD_INTR_SMBUS_ARP_ADDR			(0x1 << 11)
-#define AST_I2CD_INTR_SMBUS_DEV_ALERT_ADDR		(0x1 << 10)
-#define AST_I2CD_INTR_SMBUS_DEF_ADDR			(0x1 << 9)
-#define AST_I2CD_INTR_GCALL_ADDR			(0x1 << 8)
 #define AST_I2CD_INTR_SLAVE_MATCH			(0x1 << 7)
 #define AST_I2CD_INTR_SCL_TIMEOUT			(0x1 << 6)
 #define AST_I2CD_INTR_ABNORMAL				(0x1 << 5)
@@ -149,82 +74,22 @@
 #define AST_I2CD_INTR_TX_ACK				(0x1 << 0)
 
 /* 0x14 : I2CD Command/Status Register   */
-#define AST_I2CD_SDA_OE					(0x1 << 28)
-#define AST_I2CD_SDA_O					(0x1 << 27)		
-#define AST_I2CD_SCL_OE					(0x1 << 26)		
-#define AST_I2CD_SCL_O					(0x1 << 25)		
-#define AST_I2CD_TX_TIMING				(0x1 << 24)		// 0 ~3
-#define AST_I2CD_TX_STATUS				(0x1 << 23)		
-// Tx State Machine 
-#define AST_I2CD_IDLE	 				0x0
-#define AST_I2CD_MACTIVE				0x8
-#define AST_I2CD_MSTART					0x9
-#define AST_I2CD_MSTARTR				0xa
-#define AST_I2CD_MSTOP					0xb
-#define AST_I2CD_MTXD					0xc
-#define AST_I2CD_MRXACK					0xd
-#define AST_I2CD_MRXD 					0xe
-#define AST_I2CD_MTXACK 				0xf
-#define AST_I2CD_SWAIT					0x1
-#define AST_I2CD_SRXD 					0x4
-#define AST_I2CD_STXACK 				0x5
-#define AST_I2CD_STXD					0x6
-#define AST_I2CD_SRXACK 				0x7
-#define AST_I2CD_RECOVER 				0x3
-
-#define AST_I2CD_SCL_LINE_STS				(0x1 << 18)		
-#define AST_I2CD_SDA_LINE_STS				(0x1 << 17)		
-#define AST_I2CD_BUS_BUSY_STS				(0x1 << 16)		
-#define AST_I2CD_SDA_OE_OUT_DIR				(0x1 << 15)		
-#define AST_I2CD_SDA_O_OUT_DIR				(0x1 << 14)		
-#define AST_I2CD_SCL_OE_OUT_DIR				(0x1 << 13)		
-#define AST_I2CD_SCL_O_OUT_DIR				(0x1 << 12)		
-#define AST_I2CD_BUS_RECOVER_CMD_EN			(0x1 << 11)		
-#define AST_I2CD_S_ALT_EN				(0x1 << 10)		
-// 0 : DMA Buffer, 1: Pool Buffer
-//AST1070 DMA register 
-#define AST_I2CD_RX_DMA_ENABLE				(0x1 << 9)		
-#define AST_I2CD_TX_DMA_ENABLE				(0x1 << 8)		
+#define AST_I2CD_SCL_LINE_STS				(0x1 << 18)
+#define AST_I2CD_SDA_LINE_STS				(0x1 << 17)
+#define AST_I2CD_BUS_BUSY_STS				(0x1 << 16)
+#define AST_I2CD_BUS_RECOVER_CMD			(0x1 << 11)
 
 /* Command Bit */
-#define AST_I2CD_RX_BUFF_ENABLE				(0x1 << 7)		
-#define AST_I2CD_TX_BUFF_ENABLE				(0x1 << 6)		
-#define AST_I2CD_M_STOP_CMD					(0x1 << 5)		
-#define AST_I2CD_M_S_RX_CMD_LAST			(0x1 << 4)		
-#define AST_I2CD_M_RX_CMD					(0x1 << 3)		
-#define AST_I2CD_S_TX_CMD					(0x1 << 2)		
-#define AST_I2CD_M_TX_CMD					(0x1 << 1)		
-#define AST_I2CD_M_START_CMD				(0x1 )		
+#define AST_I2CD_M_STOP_CMD				(0x1 << 5)
+#define AST_I2CD_M_S_RX_CMD_LAST			(0x1 << 4)
+#define AST_I2CD_M_RX_CMD				(0x1 << 3)
+#define AST_I2CD_S_TX_CMD				(0x1 << 2)
+#define AST_I2CD_M_TX_CMD				(0x1 << 1)
+#define AST_I2CD_M_START_CMD				(0x1)
 
 /* 0x18 : I2CD Slave Device Address Register   */
 #define AST_I2CD_DEV_ADDR_MASK                          ((0x1 << 7) - 1)
 
-/* 0x1C : I2CD Pool Buffer Control Register   */
-#define AST_I2CD_RX_BUF_ADDR_GET(x)				((x>> 24)& 0xff)
-#define AST_I2CD_RX_BUF_END_ADDR_SET(x)			(x << 16)		
-#define AST_I2CD_TX_DATA_BUF_END_SET(x)			((x&0xff) << 8)		
-#define AST_I2CD_TX_DATA_BUF_GET(x)			((x >>8) & 0xff)		
-#define AST_I2CD_BUF_BASE_ADDR_SET(x)			(x & 0x3f)		
-
-/* 0x20 : I2CD Transmit/Receive Byte Buffer Register   */
-#define AST_I2CD_GET_MODE(x)					((x >> 8) & 0x1)		
-
-#define AST_I2CD_RX_BYTE_BUFFER					(0xff << 8)		
-#define AST_I2CD_TX_BYTE_BUFFER					(0xff )		
-
-//1. usage flag , 2 size,	3. request address
-/* Use platform_data instead of module parameters */
-/* Fast Mode = 400 kHz, Standard = 100 kHz */
-//static int clock = 100; /* Default: 100 kHz */
-
-/* bitmask of commands that we wait for, in the cmd_pending mask */
-#define AST_I2CD_CMDS	(AST_I2CD_BUS_RECOVER_CMD_EN | \
-			 AST_I2CD_M_STOP_CMD | \
-			 AST_I2CD_M_RX_CMD | \
-			 AST_I2CD_M_TX_CMD)
-
-static const int ast_i2c_n_busses = 14;
-
 #if IS_ENABLED(CONFIG_I2C_SLAVE)
 enum ast_i2c_slave_state {
 	AST_I2C_SLAVE_START,
@@ -238,33 +103,20 @@ enum ast_i2c_slave_state {
 
 struct ast_i2c_bus {
 	/* TODO: find a better way to do this */
-	struct ast_i2c_dev *i2c_dev;
-	struct device	*dev;
-
-	void __iomem	*base;			/* virtual */	
-	u32 state;				//I2C xfer mode state matchine 
-	struct i2c_adapter adap;
+	struct i2c_adapter 		adap;
+	struct device			*dev;
+	void __iomem			*base;
+	spinlock_t			lock;
+	struct completion		cmd_complete;
+	int				irq;
+	/* Transaction state. */
+	struct i2c_msg			*msg;
+	int				msg_pos;
+	u32				cmd_err;
 #if IS_ENABLED(CONFIG_I2C_SLAVE)
-	struct i2c_client *slave;
-	enum ast_i2c_slave_state slave_state;
+	struct i2c_client		*slave;
+	enum ast_i2c_slave_state	slave_state;
 #endif
-	u32		bus_clk;
-	struct clk	*pclk;
-	int		irq;
-
-	/* i2c transfer state. this is accessed from both process and IRQ
-	 * context, so is protected by cmd_lock */
-	spinlock_t	cmd_lock;
-	bool		send_start;
-	bool		send_stop;	/* last message of an xfer? */
-	bool		query_len;
-	struct i2c_msg	*msg;		/* current tx/rx message */
-	int		msg_pos;	/* current byte position in message */
-
-	struct completion cmd_complete;
-	u32		cmd_sent;
-	u32		cmd_pending;
-	u32		cmd_err;
 };
 
 struct ast_i2c_controller {
@@ -284,263 +136,57 @@ static inline u32 ast_i2c_read(struct ast_i2c_bus *bus, u32 reg)
 	return readl(bus->base + reg);
 }
 
-static u32 select_i2c_clock(struct ast_i2c_bus *bus)
-{
-	unsigned int inc = 0, div, divider_ratio;
-	u32 SCL_Low, SCL_High, data;
-
-	divider_ratio = clk_get_rate(bus->pclk) / bus->bus_clk;
-	for (div = 0; divider_ratio >= 16; div++) {
-		inc |= (divider_ratio & 1);
-		divider_ratio >>= 1;
-	}
-	divider_ratio += inc;
-	SCL_Low = (divider_ratio >> 1) - 1;
-	SCL_High = divider_ratio - SCL_Low - 2;
-	data = 0x77700300 | (SCL_High << 16) | (SCL_Low << 12) | div;
-	return data;
-}
-
-static void ast_i2c_dev_init(struct ast_i2c_bus *bus)
-{
-	/* reset device: disable master & slave functions */
-	ast_i2c_write(bus, 0, I2C_FUN_CTRL_REG);
-
-	dev_dbg(bus->dev, "bus_clk %u, pclk %lu\n",
-			bus->bus_clk, clk_get_rate(bus->pclk));
-
-	/* Set AC Timing */
-	if(bus->bus_clk / 1000 > 400) {
-		ast_i2c_write(bus, ast_i2c_read(bus, I2C_FUN_CTRL_REG) |
-				AST_I2CD_M_HIGH_SPEED_EN |
-				AST_I2CD_M_SDA_DRIVE_1T_EN |
-				AST_I2CD_SDA_DRIVE_1T_EN,
-				I2C_FUN_CTRL_REG);
-
-		ast_i2c_write(bus, 0x3, I2C_AC_TIMING_REG2);
-		ast_i2c_write(bus, select_i2c_clock(bus), I2C_AC_TIMING_REG1);
-	} else {
-		ast_i2c_write(bus, select_i2c_clock(bus), I2C_AC_TIMING_REG1);
-		ast_i2c_write(bus, AST_NO_TIMEOUT_CTRL, I2C_AC_TIMING_REG2);
-	}
-
-	dev_dbg(bus->dev, "reg1: %x, reg2: %x, fun_ctrl: %x\n",
-			ast_i2c_read(bus, I2C_AC_TIMING_REG1),
-			ast_i2c_read(bus, I2C_AC_TIMING_REG2),
-			ast_i2c_read(bus, I2C_FUN_CTRL_REG));
-
-	/* Enable Master Mode */
-	ast_i2c_write(bus, ast_i2c_read(bus, I2C_FUN_CTRL_REG)
-			| AST_I2CD_MASTER_EN, I2C_FUN_CTRL_REG);
-
-	/* Set interrupt generation of I2C controller */
-	ast_i2c_write(bus, AST_I2CD_INTR_SDA_DL_TIMEOUT |
-			AST_I2CD_INTR_BUS_RECOVER_DONE |
-			AST_I2CD_INTR_SCL_TIMEOUT |
-			AST_I2CD_INTR_ABNORMAL |
-			AST_I2CD_INTR_NORMAL_STOP |
-			AST_I2CD_INTR_ARBIT_LOSS |
-			AST_I2CD_INTR_RX_DONE |
-			AST_I2CD_INTR_TX_NAK |
-			AST_I2CD_INTR_TX_ACK,
-			I2C_INTR_CTRL_REG);
-
-}
-
-static void ast_i2c_issue_cmd(struct ast_i2c_bus *bus, u32 cmd)
-{
-	dev_dbg(bus->dev, "issuing cmd: %x\n", cmd);
-	bus->cmd_err = 0;
-	bus->cmd_sent = cmd;
-	bus->cmd_pending = cmd & AST_I2CD_CMDS;
-	ast_i2c_write(bus, cmd, I2C_CMD_REG);
-}
-
-static int ast_i2c_issue_oob_command(struct ast_i2c_bus *bus, u32 cmd)
-{
-	spin_lock_irq(&bus->cmd_lock);
-	init_completion(&bus->cmd_complete);
-	ast_i2c_issue_cmd(bus, cmd);
-	spin_unlock_irq(&bus->cmd_lock);
-	return wait_for_completion_interruptible_timeout(&bus->cmd_complete,
-					      bus->adap.timeout*HZ);
-}
-
-static u8 ast_i2c_bus_error_recover(struct ast_i2c_bus *bus)
+static u8 ast_i2c_recover_bus(struct ast_i2c_bus *bus)
 {
-	u32 sts, i;
-	int r;
+	u32 sts;
+	unsigned long time_left;
+	unsigned long flags;
+	int ret = 0;
 
-	//Check 0x14's SDA and SCL status
+	spin_lock_irqsave(&bus->lock, flags);
 	sts = ast_i2c_read(bus,I2C_CMD_REG);
-
+	/* Bus is idle: no recovery needed. */
 	if ((sts & AST_I2CD_SDA_LINE_STS) && (sts & AST_I2CD_SCL_LINE_STS)) {
-		//Means bus is idle.
-		dev_dbg(bus->dev,
-				"I2C bus is idle. I2C slave doesn't exist?!\n");
-		return -1;
+		spin_unlock_irqrestore(&bus->lock, flags);
+		return 0;
 	}
+	dev_dbg(bus->dev, "bus hung (status %x), attempting recovery\n", sts);
 
-	dev_dbg(bus->dev, "I2C bus hung (status %x), attempting recovery\n",
-			sts);
-
+	/* Bus held: put bus in stop state. */
 	if ((sts & AST_I2CD_SDA_LINE_STS) && !(sts & AST_I2CD_SCL_LINE_STS)) {
-		//if SDA == 1 and SCL == 0, it means the master is locking the bus.
-		//Send a stop command to unlock the bus.
-		dev_dbg(bus->dev, "I2C's master is locking the bus, try to stop it.\n");
-
-		init_completion(&bus->cmd_complete);
-
 		ast_i2c_write(bus, AST_I2CD_M_STOP_CMD, I2C_CMD_REG);
-
-		r = wait_for_completion_interruptible_timeout(&bus->cmd_complete,
-							      bus->adap.timeout*HZ);
-
-		if (bus->cmd_err) {
-			dev_dbg(bus->dev, "recovery error \n");
-			return -1;
-		}
-
-		if (r == 0) {
-			 dev_dbg(bus->dev, "recovery timed out\n");
-			 return -1;
-		} else {
-			dev_dbg(bus->dev, "Recovery successfully\n");
-			return 0;
-		}
-
+		reinit_completion(&bus->cmd_complete);
+		spin_unlock_irqrestore(&bus->lock, flags);
+
+		time_left = wait_for_completion_interruptible_timeout(
+				&bus->cmd_complete, bus->adap.timeout * HZ);
+
+		spin_lock_irqsave(&bus->lock, flags);
+		if (time_left == 0)
+			ret = -ETIMEDOUT;
+		else if (bus->cmd_err)
+			ret = -EIO;
+	/* Bus error. */
 	} else if (!(sts & AST_I2CD_SDA_LINE_STS)) {
-		//else if SDA == 0, the device is dead. We need to reset the bus
-		//And do the recovery command.
-		dev_dbg(bus->dev, "I2C's slave is dead, try to recover it\n");
-		for (i = 0; i < 2; i++) {
-			ast_i2c_dev_init(bus);
-			ast_i2c_issue_oob_command(bus,
-					AST_I2CD_BUS_RECOVER_CMD_EN);
-			if (bus->cmd_err != 0) {
-				dev_dbg(bus->dev, "ERROR!! Failed to do recovery command(0x%08x)\n", bus->cmd_err);
-				return -1;
-			}
-			//Check 0x14's SDA and SCL status
-			sts = ast_i2c_read(bus,I2C_CMD_REG);
-			if (sts & AST_I2CD_SDA_LINE_STS) //Recover OK
-				break;
-		}
-		if (i == 2) {
-			dev_dbg(bus->dev, "ERROR!! recover failed\n");
-			return -1;
-		}
-	} else {
-		dev_dbg(bus->dev, "Don't know how to handle this case?!\n");
-		return -1;
+		ast_i2c_write(bus, AST_I2CD_BUS_RECOVER_CMD, I2C_CMD_REG);
+		reinit_completion(&bus->cmd_complete);
+		spin_unlock_irqrestore(&bus->lock, flags);
+
+		time_left = wait_for_completion_interruptible_timeout(
+				&bus->cmd_complete, bus->adap.timeout * HZ);
+
+		spin_lock_irqsave(&bus->lock, flags);
+		if (time_left == 0)
+			ret = -ETIMEDOUT;
+		else if (bus->cmd_err)
+			ret = -EIO;
+		/* Recovery failed. */
+		else if (!(ast_i2c_read(bus,I2C_CMD_REG) &
+			   AST_I2CD_SDA_LINE_STS))
+			ret = -EIO;
 	}
-	dev_dbg(bus->dev, "Recovery successfully\n");
-	return 0;
-}
-
-static int ast_i2c_wait_bus_not_busy(struct ast_i2c_bus *bus)
-{
-	int timeout = 2; //TODO number
-
-	while (ast_i2c_read(bus, I2C_CMD_REG) & AST_I2CD_BUS_BUSY_STS) {
-		ast_i2c_bus_error_recover(bus);
-		if(timeout <= 0)
-			break;
-		timeout--;
-		msleep(2);
-	}
-
-	return timeout <= 0 ? EAGAIN : 0;
-}
-
-static bool ast_i2c_do_byte_xfer(struct ast_i2c_bus *bus)
-{
-	u32 cmd, data;
-
-	if (bus->send_start) {
-		dev_dbg(bus->dev, "%s %c: addr %x start, len %d\n", __func__,
-			bus->msg->flags & I2C_M_RD ? 'R' : 'W',
-			bus->msg->addr,	bus->msg->len);
-
-		data = bus->msg->addr << 1;
-		if (bus->msg->flags & I2C_M_RD)
-			data |= 0x1;
-
-		cmd = AST_I2CD_M_TX_CMD | AST_I2CD_M_START_CMD;
-		if (bus->send_stop && bus->msg->len == 0)
-			cmd |= AST_I2CD_M_STOP_CMD;
-
-		ast_i2c_write(bus, data, I2C_BYTE_BUF_REG);
-		ast_i2c_issue_cmd(bus, cmd);
-
-	} else if (bus->msg_pos < bus->msg->len){
-		bool is_last = bus->msg_pos + 1 == bus->msg->len;
-
-		dev_dbg(bus->dev, "%s %c%c: addr %x xfer %d, len %d\n",
-			__func__,
-			bus->msg->flags & I2C_M_RD ? 'R' : 'W',
-			bus->send_stop && is_last ? 'T' : '-',
-			bus->msg->addr,
-			bus->msg_pos, bus->msg->len);
-
-		if (bus->msg->flags & I2C_M_RD) {
-			cmd = AST_I2CD_M_RX_CMD;
-			if (bus->send_stop && is_last && !bus->query_len)
-				cmd |= AST_I2CD_M_S_RX_CMD_LAST |
-					AST_I2CD_M_STOP_CMD;
-
-		} else {
-			cmd = AST_I2CD_M_TX_CMD;
-			ast_i2c_write(bus, bus->msg->buf[bus->msg_pos],
-					I2C_BYTE_BUF_REG);
-
-			if (bus->send_stop && is_last)
-				cmd |= AST_I2CD_M_STOP_CMD;
-		}
-		ast_i2c_issue_cmd(bus, cmd);
-
-	} else {
-		return false;
-	}
-
-	return true;
-}
-
-//TX/Rx Done
-static void ast_i2c_master_xfer_done(struct ast_i2c_bus *bus)
-{
-	bool next_msg_queued;
-
-	dev_dbg(bus->dev, "%s xfer %d%c\n", __func__,
-			bus->msg_pos,
-			bus->send_start ? 'S' : ' ');
-
-	if (bus->send_start) {
-		bus->send_start = false;
-	} else {
-
-		if (bus->msg->flags & I2C_M_RD) {
-			uint8_t data;
-
-			data = (ast_i2c_read(bus, I2C_BYTE_BUF_REG) &
-					AST_I2CD_RX_BYTE_BUFFER) >> 8;
-
-			if (bus->query_len) {
-				bus->msg->len += data;
-				bus->query_len = false;
-				dev_dbg(bus->dev, "got rx len: %d\n",
-						bus->msg->len -1);
-			}
-			bus->msg->buf[bus->msg_pos] = data;
-		}
-		bus->msg_pos++;
-	}
-
-	/* queue the next message. If there's none left, we notify the
-	 * waiter */
-	next_msg_queued = ast_i2c_do_byte_xfer(bus);
-	if (!next_msg_queued)
-		complete(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return ret;
 }
 
 #if IS_ENABLED(CONFIG_I2C_SLAVE)
@@ -554,7 +200,7 @@ static bool ast_i2c_slave_irq(struct ast_i2c_bus *bus)
 	enum i2c_slave_event event;
 	struct i2c_client *slave = bus->slave;
 
-	spin_lock(&bus->cmd_lock);
+	spin_lock(&bus->lock);
 	if (!slave) {
 		irq_handled = false;
 		goto out;
@@ -635,7 +281,7 @@ static bool ast_i2c_slave_irq(struct ast_i2c_bus *bus)
 	ast_i2c_write(bus, status_ack, I2C_INTR_STS_REG);
 
 out:
-	spin_unlock(&bus->cmd_lock);
+	spin_unlock(&bus->lock);
 	return irq_handled;
 }
 #endif
@@ -647,69 +293,43 @@ static bool ast_i2c_master_irq(struct ast_i2c_bus *bus)
 		AST_I2CD_INTR_SCL_TIMEOUT |
 		AST_I2CD_INTR_SDA_DL_TIMEOUT |
 		AST_I2CD_INTR_TX_NAK;
-	u32 sts, cmd;
-
-	spin_lock(&bus->cmd_lock);
-
-	cmd = ast_i2c_read(bus, I2C_CMD_REG);
-	sts = ast_i2c_read(bus, I2C_INTR_STS_REG);
-
-	dev_dbg(bus->dev, "irq! status 0x%08x, cmd 0x%08x\n", sts, cmd);
-
-	sts &= 0xffff;
-	bus->state = cmd >> 19 & 0xf;
-
-	/*
-	 * ack everything - this is safe because this driver makes master mode
-	 * and slave mode mutually exclusive on a single bus; thus, it is not
-	 * possible for a master to steal a slave IRQ in this way.
-	 */
-	ast_i2c_write(bus, sts, I2C_INTR_STS_REG);
-
-	bus->cmd_err |= sts & errs;
-
-	/**
-	 * Mask-out pending commands that this interrupt has indicated are
-	 * complete. These checks need to cover all of the possible bits set
-	 * in the AST_I2CD_CMDS bitmask.
-	 */
-	if (sts & AST_I2CD_INTR_TX_ACK)
-		bus->cmd_pending &= ~AST_I2CD_M_TX_CMD;
-
-	if (sts & AST_I2CD_INTR_RX_DONE)
-		bus->cmd_pending &= ~AST_I2CD_M_RX_CMD;
-
-	if (sts & AST_I2CD_INTR_NORMAL_STOP)
-		bus->cmd_pending &= ~AST_I2CD_M_STOP_CMD;
-
-	if (sts & AST_I2CD_INTR_BUS_RECOVER_DONE)
-		bus->cmd_pending &= ~AST_I2CD_BUS_RECOVER_CMD_EN;
-
-	/* if we've seen an error, notify our waiter */
-	if (bus->cmd_err) {
-		complete(&bus->cmd_complete);
+	u32 irq_status;
 
-	/* still have work to do? We'll wait for the corresponding IRQ(s) for
-	 * that to complete. */
-	} else if (bus->cmd_pending) {
-		dev_dbg(bus->dev, "cmds pending: 0x%x\n", bus->cmd_pending);
+	spin_lock(&bus->lock);
+	irq_status = ast_i2c_read(bus, I2C_INTR_STS_REG);
+	bus->cmd_err |= irq_status & errs;
 
-	/* message transfer complete */
-	} else if (bus->msg) {
-		ast_i2c_master_xfer_done(bus);
+	dev_dbg(bus->dev, "master irq status 0x%08x\n", irq_status);
 
-	/* other (non-message) command complete: recovery, error stop. Notify
-	 * waiters. */
-	} else if (bus->cmd_sent) {
+	/* No message to transfer. */
+	if (bus->cmd_err ||
+	    (irq_status & AST_I2CD_INTR_NORMAL_STOP) ||
+	    (irq_status & AST_I2CD_INTR_BUS_RECOVER_DONE)) {
 		complete(&bus->cmd_complete);
+		goto out;
+	} else if (!bus->msg || bus->msg_pos >= bus->msg->len)
+		goto out;
 
-	} else {
-		dev_err(bus->dev, "Invalid state (msg %p, pending %x)?",
-				bus->msg, bus->cmd_pending);
+	if ((bus->msg->flags & I2C_M_RD) && (irq_status & AST_I2CD_INTR_RX_DONE)) {
+		bus->msg->buf[bus->msg_pos++] =
+				ast_i2c_read(bus, I2C_BYTE_BUF_REG) >> 8;
+		if (bus->msg_pos + 1 < bus->msg->len)
+			ast_i2c_write(bus, AST_I2CD_M_RX_CMD, I2C_CMD_REG);
+		else if (bus->msg_pos < bus->msg->len)
+			ast_i2c_write(bus, AST_I2CD_M_RX_CMD |
+				      AST_I2CD_M_S_RX_CMD_LAST, I2C_CMD_REG);
+	} else if (!(bus->msg->flags & I2C_M_RD) && (irq_status & AST_I2CD_INTR_TX_ACK)) {
+		ast_i2c_write(bus, bus->msg->buf[bus->msg_pos++],
+			      I2C_BYTE_BUF_REG);
+		ast_i2c_write(bus, AST_I2CD_M_TX_CMD, I2C_CMD_REG);
 	}
 
-	spin_unlock(&bus->cmd_lock);
-
+	/* Transmission complete: notify caller. */
+	if (bus->msg_pos >= bus->msg->len)
+		complete(&bus->cmd_complete);
+out:
+	ast_i2c_write(bus, irq_status, I2C_INTR_STS_REG);
+	spin_unlock(&bus->lock);
 	return true;
 }
 
@@ -731,95 +351,94 @@ static irqreturn_t ast_i2c_bus_irq(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
-static int ast_i2c_do_msgs_xfer(struct ast_i2c_bus *bus,
-				struct i2c_msg *msgs, int num)
+static int ast_i2c_master_single_xfer(struct i2c_adapter *adap, struct i2c_msg *msg)
 {
+	struct ast_i2c_bus *bus = adap->algo_data;
 	unsigned long flags;
-	int i, ret = 0;
-	u32 err, cmd;
-
-	for (i = 0; i < num; i++) {
-
-		spin_lock_irqsave(&bus->cmd_lock, flags);
-		bus->msg = &msgs[i];
-		bus->msg_pos = 0;
-		bus->query_len = bus->msg->flags & I2C_M_RECV_LEN;
-		bus->send_start = !(bus->msg->flags & I2C_M_NOSTART);
-		bus->send_stop = !!(num == i+1);
-		init_completion(&bus->cmd_complete);
-
-		ast_i2c_do_byte_xfer(bus);
-		spin_unlock_irqrestore(&bus->cmd_lock, flags);
-
-		ret = wait_for_completion_interruptible_timeout(
-					&bus->cmd_complete,
-					bus->adap.timeout * HZ);
-
-		spin_lock_irqsave(&bus->cmd_lock, flags);
-		err = bus->cmd_err;
-		cmd = bus->cmd_sent;
-		bus->cmd_sent = 0;
-		bus->msg = NULL;
-		spin_unlock_irqrestore(&bus->cmd_lock, flags);
-
-		if (!ret) {
-			dev_dbg(bus->dev, "controller timed out\n");
-			return -EIO;
-		}
-
-		if (err != 0) {
-			if (cmd & AST_I2CD_M_STOP_CMD) {
-				return -ETIMEDOUT;
-			} else {
-				dev_dbg(bus->dev, "send stop\n");
-				ast_i2c_issue_oob_command(bus,
-						AST_I2CD_M_STOP_CMD);
-				return -EAGAIN;
-			}
-		}
+	u8 slave_addr;
+	u32 command = AST_I2CD_M_START_CMD | AST_I2CD_M_TX_CMD;
+	int ret = msg->len;
+	unsigned long time_left;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	bus->msg = msg;
+	bus->msg_pos = 0;
+	slave_addr = msg->addr << 1;
+	if (msg->flags & I2C_M_RD) {
+		slave_addr |= 1;
+		command |= AST_I2CD_M_RX_CMD;
 	}
+	ast_i2c_write(bus, slave_addr, I2C_BYTE_BUF_REG);
+	ast_i2c_write(bus, command, I2C_CMD_REG);
+	reinit_completion(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	time_left = wait_for_completion_interruptible_timeout(
+			&bus->cmd_complete, bus->adap.timeout * HZ * msg->len);
+	if (time_left == 0)
+		return -ETIMEDOUT;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	if (bus->cmd_err)
+		ret = -EIO;
+	bus->msg = NULL;
+	spin_unlock_irqrestore(&bus->lock, flags);
 
-	return num;
+	return ret;
 }
 
-static int ast_i2c_xfer(struct i2c_adapter *adap,
-			struct i2c_msg *msgs, int num)
+static int ast_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+			       int num)
 {
 	struct ast_i2c_bus *bus = adap->algo_data;
-	int ret, i;
-	int sts;
-
-	sts = ast_i2c_read(bus, I2C_CMD_REG);
-	dev_dbg(bus->dev, "state[%x], SCL[%d], SDA[%d], BUS[%d]\n",
-		(sts >> 19) & 0xf,
-		(sts >> 18) & 0x1,
-		(sts >> 17) & 0x1,
-		(sts >> 16) & 1);
-	/*
-	 * Wait for the bus to become free.
-	 */
+	int ret;
+	int i;
+	unsigned long flags;
+	unsigned long time_left;
 
-	ret = ast_i2c_wait_bus_not_busy(bus);
-	if (ret) {
-		dev_err(&adap->dev, "i2c_ast: timeout waiting for bus free\n");
-		goto out;
+	/* If bus is busy, attempt recovery. We assume a single master
+	 * environment.
+	 */
+	if (ast_i2c_read(bus, I2C_CMD_REG) & AST_I2CD_BUS_BUSY_STS) {
+		ret = ast_i2c_recover_bus(bus);
+		if (ret)
+			return ret;
 	}
 
-	for (i = adap->retries; i >= 0; i--) {
-		if (i != 0)
-			dev_dbg(&adap->dev, "Do retrying transmission [%d]\n",i);
-
-		ret = ast_i2c_do_msgs_xfer(bus, msgs, num);
-		if (ret != -EAGAIN)
-			goto out;
-
-		udelay(100);
+	for (i = 0; i < num; i++) {
+		ret = ast_i2c_master_single_xfer(adap, &msgs[i]);
+		if (ret < 0)
+			break;
+		/* TODO: Support other forms of I2C protocol mangling. */
+		if (msgs[i].flags & I2C_M_STOP) {
+			spin_lock_irqsave(&bus->lock, flags);
+			ast_i2c_write(bus, AST_I2CD_M_STOP_CMD, I2C_CMD_REG);
+			reinit_completion(&bus->cmd_complete);
+			spin_unlock_irqrestore(&bus->lock, flags);
+
+			time_left = wait_for_completion_interruptible_timeout(
+					&bus->cmd_complete,
+					bus->adap.timeout * HZ);
+			if (time_left == 0)
+				return -ETIMEDOUT;
+		}
 	}
 
-	ret = -EREMOTEIO;
-out:
+	spin_lock_irqsave(&bus->lock, flags);
+	ast_i2c_write(bus, AST_I2CD_M_STOP_CMD, I2C_CMD_REG);
+	reinit_completion(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
 
-	return ret;
+	time_left = wait_for_completion_interruptible_timeout(
+			&bus->cmd_complete, bus->adap.timeout * HZ);
+	if (time_left == 0)
+		return -ETIMEDOUT;
+
+	/* If nothing went wrong, return number of messages transferred. */
+	if (ret < 0)
+		return ret;
+	else
+		return i;
 }
 
 static u32 ast_i2c_functionality(struct i2c_adapter *adap)
@@ -836,9 +455,9 @@ static int ast_i2c_reg_slave(struct i2c_client *client)
 	u32 func_ctrl_reg_val;
 
 	bus = client->adapter->algo_data;
-	spin_lock_irqsave(&bus->cmd_lock, flags);
+	spin_lock_irqsave(&bus->lock, flags);
 	if (bus->slave) {
-		spin_unlock_irqrestore(&bus->cmd_lock, flags);
+		spin_unlock_irqrestore(&bus->lock, flags);
 		return -EINVAL;
 	}
 
@@ -856,7 +475,7 @@ static int ast_i2c_reg_slave(struct i2c_client *client)
 
 	bus->slave = client;
 	bus->slave_state = AST_I2C_SLAVE_STOP;
-	spin_unlock_irqrestore(&bus->cmd_lock, flags);
+	spin_unlock_irqrestore(&bus->lock, flags);
 	return 0;
 }
 
@@ -864,12 +483,11 @@ static int ast_i2c_unreg_slave(struct i2c_client *client)
 {
 	struct ast_i2c_bus *bus = client->adapter->algo_data;
 	unsigned long flags;
-	u32 addr_reg_val;
 	u32 func_ctrl_reg_val;
 
-	spin_lock_irqsave(&bus->cmd_lock, flags);
+	spin_lock_irqsave(&bus->lock, flags);
 	if (!bus->slave) {
-		spin_unlock_irqrestore(&bus->cmd_lock, flags);
+		spin_unlock_irqrestore(&bus->lock, flags);
 		return -EINVAL;
 	}
 
@@ -880,13 +498,13 @@ static int ast_i2c_unreg_slave(struct i2c_client *client)
 	ast_i2c_write(bus, func_ctrl_reg_val, I2C_FUN_CTRL_REG);
 
 	bus->slave = NULL;
-	spin_unlock_irqrestore(&bus->cmd_lock, flags);
+	spin_unlock_irqrestore(&bus->lock, flags);
 	return 0;
 }
 #endif
 
 static const struct i2c_algorithm i2c_ast_algorithm = {
-	.master_xfer	= ast_i2c_xfer,
+	.master_xfer	= ast_i2c_master_xfer,
 	.functionality	= ast_i2c_functionality,
 #if IS_ENABLED(CONFIG_I2C_SLAVE)
 	.reg_slave	= ast_i2c_reg_slave,
@@ -894,6 +512,65 @@ static const struct i2c_algorithm i2c_ast_algorithm = {
 #endif
 };
 
+static u32 get_clk_reg_val(u32 divider_ratio)
+{
+	unsigned int inc = 0, div;
+	u32 scl_low, scl_high, data;
+
+	for (div = 0; divider_ratio >= 16; div++) {
+		inc |= (divider_ratio & 1);
+		divider_ratio >>= 1;
+	}
+	divider_ratio += inc;
+	scl_low = (divider_ratio >> 1) - 1;
+	scl_high = divider_ratio - scl_low - 2;
+	data = 0x77700300 | (scl_high << 16) | (scl_low << 12) | div;
+	return data;
+}
+
+static int init_clk(struct ast_i2c_bus *bus, struct platform_device *pdev)
+{
+	struct clk *pclk;
+	u32 clk_freq;
+	u32 divider_ratio;
+	int ret;
+
+	pclk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(pclk)) {
+		dev_err(&pdev->dev, "clk_get failed\n");
+		return PTR_ERR(pclk);
+	}
+	ret = of_property_read_u32(pdev->dev.of_node,
+			"clock-frequency", &clk_freq);
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+				"Could not read clock-frequency property\n");
+		clk_freq = 100000;
+	}
+	divider_ratio = clk_get_rate(pclk) / clk_freq;
+	/* We just need the clock rate, we don't actually use the clk object. */
+	devm_clk_put(&pdev->dev, pclk);
+
+	/* Set AC Timing */
+	if(clk_freq / 1000 > 400) {
+		ast_i2c_write(bus, ast_i2c_read(bus, I2C_FUN_CTRL_REG) |
+				AST_I2CD_M_HIGH_SPEED_EN |
+				AST_I2CD_M_SDA_DRIVE_1T_EN |
+				AST_I2CD_SDA_DRIVE_1T_EN,
+				I2C_FUN_CTRL_REG);
+
+		ast_i2c_write(bus, 0x3, I2C_AC_TIMING_REG2);
+		ast_i2c_write(bus, get_clk_reg_val(divider_ratio),
+			      I2C_AC_TIMING_REG1);
+	} else {
+		ast_i2c_write(bus, get_clk_reg_val(divider_ratio),
+			      I2C_AC_TIMING_REG1);
+		ast_i2c_write(bus, AST_NO_TIMEOUT_CTRL, I2C_AC_TIMING_REG2);
+	}
+
+	return 0;
+}
+
 static int ast_i2c_probe_bus(struct platform_device *pdev)
 {
 	struct ast_i2c_bus *bus;
@@ -908,12 +585,6 @@ static int ast_i2c_probe_bus(struct platform_device *pdev)
 	if (ret)
 		return -ENXIO;
 
-	bus->pclk = devm_clk_get(&pdev->dev, NULL);
-	if (IS_ERR(bus->pclk)) {
-		dev_err(&pdev->dev, "clk_get failed\n");
-		return PTR_ERR(bus->pclk);
-	}
-
 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	bus->base = devm_ioremap_resource(&pdev->dev, res);
 	if (IS_ERR(bus->base))
@@ -933,7 +604,8 @@ static int ast_i2c_probe_bus(struct platform_device *pdev)
 	}
 
 	/* Initialize the I2C adapter */
-	spin_lock_init(&bus->cmd_lock);
+	spin_lock_init(&bus->lock);
+	init_completion(&bus->cmd_complete);
 	bus->adap.nr = bus_num;
 	bus->adap.owner = THIS_MODULE;
 	bus->adap.retries = 0;
@@ -947,26 +619,67 @@ static int ast_i2c_probe_bus(struct platform_device *pdev)
 
 	bus->dev = &pdev->dev;
 
-	ret = of_property_read_u32(pdev->dev.of_node,
-			"clock-frequency", &bus->bus_clk);
-	if (ret < 0) {
-		dev_err(&pdev->dev,
-				"Could not read clock-frequency property\n");
-		bus->bus_clk = 100000;
-	}
+	/* reset device: disable master & slave functions */
+	ast_i2c_write(bus, 0, I2C_FUN_CTRL_REG);
+
+	ret = init_clk(bus, pdev);
+	if (ret < 0)
+		return ret;
 
-	ast_i2c_dev_init(bus);
+	/* Enable Master Mode */
+	ast_i2c_write(bus, ast_i2c_read(bus, I2C_FUN_CTRL_REG) |
+		      AST_I2CD_MASTER_EN |
+		      AST_I2CD_MULTI_MASTER_DIS, I2C_FUN_CTRL_REG);
+
+	/* Set interrupt generation of I2C controller */
+	ast_i2c_write(bus, AST_I2CD_INTR_SDA_DL_TIMEOUT |
+			AST_I2CD_INTR_BUS_RECOVER_DONE |
+			AST_I2CD_INTR_SCL_TIMEOUT |
+			AST_I2CD_INTR_ABNORMAL |
+			AST_I2CD_INTR_NORMAL_STOP |
+			AST_I2CD_INTR_ARBIT_LOSS |
+			AST_I2CD_INTR_RX_DONE |
+			AST_I2CD_INTR_TX_NAK |
+			AST_I2CD_INTR_TX_ACK,
+			I2C_INTR_CTRL_REG);
 
 	ret = i2c_add_numbered_adapter(&bus->adap);
 	if (ret < 0)
 		return -ENXIO;
 
+	platform_set_drvdata(pdev, bus);
+
 	dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
 			bus->adap.nr, bus->irq);
 
 	return 0;
 }
 
+static int ast_i2c_remove_bus(struct platform_device *pdev)
+{
+	struct ast_i2c_bus *bus = platform_get_drvdata(pdev);
+
+	i2c_del_adapter(&bus->adap);
+	return 0;
+}
+
+static const struct of_device_id ast_i2c_bus_of_table[] = {
+	{ .compatible = "aspeed,ast2400-i2c-bus", },
+	{ .compatible = "aspeed,ast2500-i2c-bus", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, ast_i2c_of_table);
+
+static struct platform_driver ast_i2c_bus_driver = {
+	.probe		= ast_i2c_probe_bus,
+	.remove		= ast_i2c_remove_bus,
+	.driver         = {
+		.name   = "ast-i2c-bus",
+		.of_match_table = ast_i2c_bus_of_table,
+	},
+};
+module_platform_driver(ast_i2c_bus_driver);
+
 static void noop(struct irq_data *data) { }
 
 static struct irq_chip ast_i2c_irqchip = {
@@ -982,7 +695,7 @@ static void ast_i2c_controller_irq(struct irq_desc *desc)
 	unsigned int bus_irq;
 
 	status = readl(c->base);
-	for_each_set_bit(p, &status, ast_i2c_n_busses) {
+	for_each_set_bit(p, &status, AST_I2C_NUM_BUS) {
 		bus_irq = irq_find_mapping(c->irq_domain, p);
 		generic_handle_irq(bus_irq);
 	}
@@ -1011,14 +724,14 @@ static int ast_i2c_probe_controller(struct platform_device *pdev)
 	}
 
 	controller->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
-			ast_i2c_n_busses, &irq_domain_simple_ops, NULL);
+			AST_I2C_NUM_BUS, &irq_domain_simple_ops, NULL);
 	if (!controller->irq_domain) {
 		dev_err(&pdev->dev, "no IRQ domain\n");
 		return -ENXIO;
 	}
 	controller->irq_domain->name = "ast-i2c-domain";
 
-	for (i = 0; i < ast_i2c_n_busses; i++) {
+	for (i = 0; i < AST_I2C_NUM_BUS; i++) {
 		irq = irq_create_mapping(controller->irq_domain, i);
 		irq_set_chip_data(irq, controller);
 		irq_set_chip_and_handler(irq, &ast_i2c_irqchip,
@@ -1062,43 +775,34 @@ static int ast_i2c_probe_controller(struct platform_device *pdev)
 	return 0;
 }
 
-static int ast_i2c_probe(struct platform_device *pdev)
+static int ast_i2c_remove_controller(struct platform_device *pdev)
 {
-	if (of_device_is_compatible(pdev->dev.of_node,
-				"aspeed,ast2400-i2c-controller") ||
-	    of_device_is_compatible(pdev->dev.of_node,
-		    		"aspeed,ast2500-i2c-controller"))
-		return ast_i2c_probe_controller(pdev);
-
-	if (of_device_is_compatible(pdev->dev.of_node,
-				"aspeed,ast2400-i2c-bus") ||
-	    of_device_is_compatible(pdev->dev.of_node,
-				"aspeed,ast2500-i2c-bus"))
-		return ast_i2c_probe_bus(pdev);
-
-	return -ENODEV;
+	struct ast_i2c_controller *controller = platform_get_drvdata(pdev);
+
+	irq_domain_remove(controller->irq_domain);
+	return 0;
 }
 
-static const struct of_device_id ast_i2c_of_table[] = {
-	{ .compatible = "aspeed,ast2500-i2c-controller", },
-	{ .compatible = "aspeed,ast2500-i2c-bus", },
+static const struct of_device_id ast_i2c_controller_of_table[] = {
 	{ .compatible = "aspeed,ast2400-i2c-controller", },
-	{ .compatible = "aspeed,ast2400-i2c-bus", },
+	{ .compatible = "aspeed,ast2500-i2c-controller", },
 	{ },
 };
 MODULE_DEVICE_TABLE(of, ast_i2c_of_table);
 
-static struct platform_driver i2c_ast_driver = {
-	.probe		= ast_i2c_probe,
+static struct platform_driver ast_i2c_controller_driver = {
+	.probe		= ast_i2c_probe_controller,
+	.remove		= ast_i2c_remove_controller,
 	.driver         = {
-		.name   = KBUILD_MODNAME,
-		.of_match_table = ast_i2c_of_table,
+		.name   = "ast-i2c-controller",
+		.of_match_table = ast_i2c_controller_of_table,
 	},
 };
 
-module_platform_driver(i2c_ast_driver);
+module_platform_driver(ast_i2c_controller_driver);
 
 MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>");
+MODULE_AUTHOR("Brendan Higgins <brendanhiggins@google.com>");
 MODULE_DESCRIPTION("ASPEED AST I2C Bus Driver");
 MODULE_LICENSE("GPL");
 MODULE_ALIAS("platform:ast_i2c");
-- 
2.8.0.rc3.226.g39d4020

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] eeprom: at24: added quirk for 24c08 devices
  2016-08-21  1:34 ` [PATCH 1/2] eeprom: at24: added quirk for 24c08 devices Brendan Higgins
@ 2016-08-29  5:05   ` Joel Stanley
  2016-08-29 23:24   ` Xo Wang
  1 sibling, 0 replies; 21+ messages in thread
From: Joel Stanley @ 2016-08-29  5:05 UTC (permalink / raw)
  To: Brendan Higgins; +Cc: OpenBMC Maillist

Hi Brendan,

On Sun, Aug 21, 2016 at 11:04 AM, Brendan Higgins
<brendanhiggins@google.com> wrote:
> Some 24c08 devices exhibits a quirk where they will not NACK durring an
> acknowledge poll. Added a STOP after the dummy write to address problem.

I'm not an i2c nor a eeprom expert. Are we confident that this is this
generic enough that it should be applied to all at24c08 devices?

This needs a signed-off-by. You might want to briefly describe the
hardware you've tested this on.

Once you've added that, please rebase on Linus' tree and send it
directly upstream to the maintainer. You can find the maintainer's
email address and mailing list using scripts/get_maintainer.pl on your
patch.

You can cc me if you would like.

Cheers,

Joel

> ---
>  drivers/misc/eeprom/at24.c         | 12 +++++++++++-
>  include/linux/platform_data/at24.h |  1 +
>  2 files changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c
> index 9ceb63b..c647efb 100644
> --- a/drivers/misc/eeprom/at24.c
> +++ b/drivers/misc/eeprom/at24.c
> @@ -120,7 +120,11 @@ static const struct i2c_device_id at24_ids[] = {
>                 AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
>         { "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
>         /* 24rf08 quirk is handled at i2c-core */
> -       { "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
> +       /* 24c08 exhibits a quirk where it will not NACK durring an
> +        * acknowledge poll so we have to send a stop after the dummy write in
> +        * a read.
> +        */
> +       { "24c08", AT24_DEVICE_MAGIC(8192 / 8, AT24_FLAG_STOPPOLL) },
>         { "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
>         { "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
>         { "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
> @@ -212,6 +216,12 @@ static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
>                 msgbuf[i++] = offset;
>
>                 msg[0].addr = client->addr;
> +               /*
> +                * Some EEPROMs do not perform the acknowledge poll correctly
> +                * and have to send a STOP after the dummy write.
> +                */
> +               if (at24->chip.flags & AT24_FLAG_STOPPOLL)
> +                       msg[0].flags = I2C_M_STOP;
>                 msg[0].buf = msgbuf;
>                 msg[0].len = i;
>
> diff --git a/include/linux/platform_data/at24.h b/include/linux/platform_data/at24.h
> index be830b1..1773822 100644
> --- a/include/linux/platform_data/at24.h
> +++ b/include/linux/platform_data/at24.h
> @@ -47,6 +47,7 @@ struct at24_platform_data {
>  #define AT24_FLAG_READONLY     0x40    /* sysfs-entry will be read-only */
>  #define AT24_FLAG_IRUGO                0x20    /* sysfs-entry will be world-readable */
>  #define AT24_FLAG_TAKE8ADDR    0x10    /* take always 8 addresses (24c00) */
> +#define AT24_FLAG_STOPPOLL     0x08    /* Send stop before ACK poll */
>
>         void            (*setup)(struct nvmem_device *nvmem, void *context);
>         void            *context;
> --
> 2.8.0.rc3.226.g39d4020
>
> _______________________________________________
> openbmc mailing list
> openbmc@lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/openbmc

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/2] i2c: aspeed: cleanup driver
  2016-08-21  1:34 ` [PATCH 2/2] i2c: aspeed: cleanup driver Brendan Higgins
@ 2016-08-29  5:35   ` Joel Stanley
  2016-08-30 18:52     ` [PATCH v2] i2c: aspeed: added driver for Aspeed I2C Brendan Higgins
  2016-08-30 18:59     ` [PATCH 2/2] i2c: aspeed: cleanup driver Brendan Higgins
  0 siblings, 2 replies; 21+ messages in thread
From: Joel Stanley @ 2016-08-29  5:35 UTC (permalink / raw)
  To: Brendan Higgins; +Cc: OpenBMC Maillist, Andrew Jeffery, Jeremy Kerr

Hi Brendan,

On Sun, Aug 21, 2016 at 11:04 AM, Brendan Higgins
<brendanhiggins@google.com> wrote:
> Rewrote most of master IRQ handler, xfer functions, and error recovery.
> Minor refactor of probing functions. Added remove functions.

Thanks for the patch. The diff is quite noisy given you're deleting
lots of cruft from the vendor driver. It would be easier for us to
review if you send the patch that adds the driver from scratch.

One suggestion I did have is to rename the driver to use the aspeed_
prefix for all functions so that it matches the drivers that have
already gone upstream.

Given our goal is immediate upstream submission, how about you send a
complete patch that adds the driver to upstream (say 4.8-rc4?)? Send
it to the i2c lists and cc myself, jk@ozlabs.org, and andrew@aj.id.au.

I hope we can get this sent out for upstream review this week. Let me
know if there's anything I can do to help achieve that.

Cheers,

Joel

> ---
>  drivers/i2c/busses/i2c-aspeed.c | 932 ++++++++++++++--------------------------
>  1 file changed, 318 insertions(+), 614 deletions(-)

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] eeprom: at24: added quirk for 24c08 devices
  2016-08-21  1:34 ` [PATCH 1/2] eeprom: at24: added quirk for 24c08 devices Brendan Higgins
  2016-08-29  5:05   ` Joel Stanley
@ 2016-08-29 23:24   ` Xo Wang
  2016-08-29 23:33     ` Brendan Higgins
  1 sibling, 1 reply; 21+ messages in thread
From: Xo Wang @ 2016-08-29 23:24 UTC (permalink / raw)
  To: Brendan Higgins; +Cc: openbmc

On Sat, Aug 20, 2016 at 6:34 PM, Brendan Higgins
<brendanhiggins@google.com> wrote:
> Some 24c08 devices exhibits a quirk where they will not NACK durring an

BTW, the I2C EEPROM on our AST2500 EVBs are actually generic clones of
the 24C04, with 4K of storage. This isn't mentioned on the schematic,
which is why the DTS for dev-4.7 shows a 24C08-compatible device.

> acknowledge poll. Added a STOP after the dummy write to address problem.
> ---
>  drivers/misc/eeprom/at24.c         | 12 +++++++++++-
>  include/linux/platform_data/at24.h |  1 +
>  2 files changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c
> index 9ceb63b..c647efb 100644
> --- a/drivers/misc/eeprom/at24.c
> +++ b/drivers/misc/eeprom/at24.c
> @@ -120,7 +120,11 @@ static const struct i2c_device_id at24_ids[] = {
>                 AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
>         { "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
>         /* 24rf08 quirk is handled at i2c-core */
> -       { "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
> +       /* 24c08 exhibits a quirk where it will not NACK durring an
> +        * acknowledge poll so we have to send a stop after the dummy write in
> +        * a read.
> +        */
> +       { "24c08", AT24_DEVICE_MAGIC(8192 / 8, AT24_FLAG_STOPPOLL) },
>         { "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
>         { "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
>         { "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
> @@ -212,6 +216,12 @@ static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
>                 msgbuf[i++] = offset;
>
>                 msg[0].addr = client->addr;
> +               /*
> +                * Some EEPROMs do not perform the acknowledge poll correctly
> +                * and have to send a STOP after the dummy write.
> +                */
> +               if (at24->chip.flags & AT24_FLAG_STOPPOLL)
> +                       msg[0].flags = I2C_M_STOP;
>                 msg[0].buf = msgbuf;
>                 msg[0].len = i;
>
> diff --git a/include/linux/platform_data/at24.h b/include/linux/platform_data/at24.h
> index be830b1..1773822 100644
> --- a/include/linux/platform_data/at24.h
> +++ b/include/linux/platform_data/at24.h
> @@ -47,6 +47,7 @@ struct at24_platform_data {
>  #define AT24_FLAG_READONLY     0x40    /* sysfs-entry will be read-only */
>  #define AT24_FLAG_IRUGO                0x20    /* sysfs-entry will be world-readable */
>  #define AT24_FLAG_TAKE8ADDR    0x10    /* take always 8 addresses (24c00) */
> +#define AT24_FLAG_STOPPOLL     0x08    /* Send stop before ACK poll */
>
>         void            (*setup)(struct nvmem_device *nvmem, void *context);
>         void            *context;
> --
> 2.8.0.rc3.226.g39d4020
>
> _______________________________________________
> openbmc mailing list
> openbmc@lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/openbmc

cheers
xo

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] eeprom: at24: added quirk for 24c08 devices
  2016-08-29 23:24   ` Xo Wang
@ 2016-08-29 23:33     ` Brendan Higgins
  0 siblings, 0 replies; 21+ messages in thread
From: Brendan Higgins @ 2016-08-29 23:33 UTC (permalink / raw)
  To: Xo Wang; +Cc: openbmc

[-- Attachment #1: Type: text/plain, Size: 3304 bytes --]

Good call,

I guess this is probably something we do not want to apply to all 24C04
since the real ones probably work properly.

On Mon, Aug 29, 2016 at 4:24 PM Xo Wang <xow@google.com> wrote:

> On Sat, Aug 20, 2016 at 6:34 PM, Brendan Higgins
> <brendanhiggins@google.com> wrote:
> > Some 24c08 devices exhibits a quirk where they will not NACK durring an
>
> BTW, the I2C EEPROM on our AST2500 EVBs are actually generic clones of
> the 24C04, with 4K of storage. This isn't mentioned on the schematic,
> which is why the DTS for dev-4.7 shows a 24C08-compatible device.
>
> > acknowledge poll. Added a STOP after the dummy write to address problem.
> > ---
> >  drivers/misc/eeprom/at24.c         | 12 +++++++++++-
> >  include/linux/platform_data/at24.h |  1 +
> >  2 files changed, 12 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c
> > index 9ceb63b..c647efb 100644
> > --- a/drivers/misc/eeprom/at24.c
> > +++ b/drivers/misc/eeprom/at24.c
> > @@ -120,7 +120,11 @@ static const struct i2c_device_id at24_ids[] = {
> >                 AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
> >         { "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
> >         /* 24rf08 quirk is handled at i2c-core */
> > -       { "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
> > +       /* 24c08 exhibits a quirk where it will not NACK durring an
> > +        * acknowledge poll so we have to send a stop after the dummy
> write in
> > +        * a read.
> > +        */
> > +       { "24c08", AT24_DEVICE_MAGIC(8192 / 8, AT24_FLAG_STOPPOLL) },
> >         { "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
> >         { "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
> >         { "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
> > @@ -212,6 +216,12 @@ static ssize_t at24_eeprom_read(struct at24_data
> *at24, char *buf,
> >                 msgbuf[i++] = offset;
> >
> >                 msg[0].addr = client->addr;
> > +               /*
> > +                * Some EEPROMs do not perform the acknowledge poll
> correctly
> > +                * and have to send a STOP after the dummy write.
> > +                */
> > +               if (at24->chip.flags & AT24_FLAG_STOPPOLL)
> > +                       msg[0].flags = I2C_M_STOP;
> >                 msg[0].buf = msgbuf;
> >                 msg[0].len = i;
> >
> > diff --git a/include/linux/platform_data/at24.h
> b/include/linux/platform_data/at24.h
> > index be830b1..1773822 100644
> > --- a/include/linux/platform_data/at24.h
> > +++ b/include/linux/platform_data/at24.h
> > @@ -47,6 +47,7 @@ struct at24_platform_data {
> >  #define AT24_FLAG_READONLY     0x40    /* sysfs-entry will be read-only
> */
> >  #define AT24_FLAG_IRUGO                0x20    /* sysfs-entry will be
> world-readable */
> >  #define AT24_FLAG_TAKE8ADDR    0x10    /* take always 8 addresses
> (24c00) */
> > +#define AT24_FLAG_STOPPOLL     0x08    /* Send stop before ACK poll */
> >
> >         void            (*setup)(struct nvmem_device *nvmem, void
> *context);
> >         void            *context;
> > --
> > 2.8.0.rc3.226.g39d4020
> >
> > _______________________________________________
> > openbmc mailing list
> > openbmc@lists.ozlabs.org
> > https://lists.ozlabs.org/listinfo/openbmc
>
> cheers
> xo
>

[-- Attachment #2: Type: text/html, Size: 4537 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2] i2c: aspeed: added driver for Aspeed I2C
  2016-08-29  5:35   ` Joel Stanley
@ 2016-08-30 18:52     ` Brendan Higgins
  2016-09-07 23:54       ` Joel Stanley
  2016-08-30 18:59     ` [PATCH 2/2] i2c: aspeed: cleanup driver Brendan Higgins
  1 sibling, 1 reply; 21+ messages in thread
From: Brendan Higgins @ 2016-08-30 18:52 UTC (permalink / raw)
  To: openbmc; +Cc: Brendan Higgins

Added initial master and slave support for Aspeed I2C controller.
Supports fourteen busses present in ast24xx and ast25xx BMC SoCs by
Aspeed.

Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
---
 drivers/i2c/busses/Kconfig      |  10 +
 drivers/i2c/busses/Makefile     |   1 +
 drivers/i2c/busses/i2c-aspeed.c | 812 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 823 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-aspeed.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 5c3993b..0178c6c 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -998,6 +998,16 @@ config I2C_RCAR
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-rcar.
 
+config I2C_ASPEED
+	tristate "Aspeed AST2xxx SoC I2C Controller"
+	depends on (ARCH_ASPEED || COMPILE_TEST) && OF
+	help
+	  If you say yes to this option, support will be included for the
+	  Aspeed AST2xxx SoC I2C controller.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-aspeed.
+
 comment "External I2C/SMBus adapter drivers"
 
 config I2C_DIOLAN_U2C
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 37f2819..49631cd 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_I2C_XILINX)	+= i2c-xiic.o
 obj-$(CONFIG_I2C_XLR)		+= i2c-xlr.o
 obj-$(CONFIG_I2C_XLP9XX)	+= i2c-xlp9xx.o
 obj-$(CONFIG_I2C_RCAR)		+= i2c-rcar.o
+obj-$(CONFIG_I2C_ASPEED)	+= i2c-aspeed.o
 
 # External I2C/SMBus adapter drivers
 obj-$(CONFIG_I2C_DIOLAN_U2C)	+= i2c-diolan-u2c.o
diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
new file mode 100644
index 0000000..81c96e2
--- /dev/null
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -0,0 +1,812 @@
+/*
+ *  I2C adapter for the ASPEED I2C bus.
+ *
+ *  Copyright (C) 2012-2020  ASPEED Technology Inc.
+ *  Copyright 2015 IBM Corporation
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+
+/* I2C Register */
+#define AST_I2C_FUN_CTRL_REG				0x00
+#define AST_I2C_AC_TIMING_REG1				0x04
+#define AST_I2C_AC_TIMING_REG2				0x08
+#define AST_I2C_INTR_CTRL_REG				0x0c
+#define AST_I2C_INTR_STS_REG				0x10
+#define AST_I2C_CMD_REG				0x14
+#define AST_I2C_DEV_ADDR_REG				0x18
+#define AST_I2C_BYTE_BUF_REG				0x20
+
+#define AST_I2C_NUM_BUS 14
+
+/* Gloable Register Definition */
+/* 0x00 : I2C Interrupt Status Register  */
+/* 0x08 : I2C Interrupt Target Assignment  */
+
+/* Device Register Definition */
+/* 0x00 : I2CD Function Control Register  */
+#define AST_I2CD_MULTI_MASTER_DIS			(0x1 << 15)
+#define AST_I2CD_SDA_DRIVE_1T_EN			(0x1 << 8)
+#define AST_I2CD_M_SDA_DRIVE_1T_EN			(0x1 << 7)
+#define AST_I2CD_M_HIGH_SPEED_EN			(0x1 << 6)
+#define AST_I2CD_SLAVE_EN				(0x1 << 1)
+#define AST_I2CD_MASTER_EN				(0x1)
+
+/* 0x08 : I2CD Clock and AC Timing Control Register #2 */
+#define AST_NO_TIMEOUT_CTRL				0x0
+
+
+/* 0x0c : I2CD Interrupt Control Register &
+ * 0x10 : I2CD Interrupt Status Register
+ *
+ * These share bit definitions, so use the same values for the enable &
+ * status bits.
+ */
+#define AST_I2CD_INTR_SDA_DL_TIMEOUT			(0x1 << 14)
+#define AST_I2CD_INTR_BUS_RECOVER_DONE			(0x1 << 13)
+#define AST_I2CD_INTR_SLAVE_MATCH			(0x1 << 7)
+#define AST_I2CD_INTR_SCL_TIMEOUT			(0x1 << 6)
+#define AST_I2CD_INTR_ABNORMAL				(0x1 << 5)
+#define AST_I2CD_INTR_NORMAL_STOP			(0x1 << 4)
+#define AST_I2CD_INTR_ARBIT_LOSS			(0x1 << 3)
+#define AST_I2CD_INTR_RX_DONE				(0x1 << 2)
+#define AST_I2CD_INTR_TX_NAK				(0x1 << 1)
+#define AST_I2CD_INTR_TX_ACK				(0x1 << 0)
+
+/* 0x14 : I2CD Command/Status Register   */
+#define AST_I2CD_SCL_LINE_STS				(0x1 << 18)
+#define AST_I2CD_SDA_LINE_STS				(0x1 << 17)
+#define AST_I2CD_BUS_BUSY_STS				(0x1 << 16)
+#define AST_I2CD_BUS_RECOVER_CMD			(0x1 << 11)
+
+/* Command Bit */
+#define AST_I2CD_M_STOP_CMD				(0x1 << 5)
+#define AST_I2CD_M_S_RX_CMD_LAST			(0x1 << 4)
+#define AST_I2CD_M_RX_CMD				(0x1 << 3)
+#define AST_I2CD_S_TX_CMD				(0x1 << 2)
+#define AST_I2CD_M_TX_CMD				(0x1 << 1)
+#define AST_I2CD_M_START_CMD				(0x1)
+
+/* 0x18 : I2CD Slave Device Address Register   */
+#define AST_I2CD_DEV_ADDR_MASK                          ((0x1 << 7) - 1)
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+enum ast_i2c_slave_state {
+	AST_I2C_SLAVE_START,
+	AST_I2C_SLAVE_READ_REQUESTED,
+	AST_I2C_SLAVE_READ_PROCESSED,
+	AST_I2C_SLAVE_WRITE_REQUESTED,
+	AST_I2C_SLAVE_WRITE_RECEIVED,
+	AST_I2C_SLAVE_STOP,
+};
+#endif
+
+struct ast_i2c_bus {
+	struct i2c_adapter		adap;
+	struct device			*dev;
+	void __iomem			*base;
+	spinlock_t			lock;
+	struct completion		cmd_complete;
+	int				irq;
+	/* Transaction state. */
+	struct i2c_msg			*msg;
+	int				msg_pos;
+	u32				cmd_err;
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	struct i2c_client		*slave;
+	enum ast_i2c_slave_state	slave_state;
+#endif
+};
+
+struct ast_i2c_controller {
+	struct device		*dev;
+	void __iomem		*base;
+	int			irq;
+	struct irq_domain	*irq_domain;
+};
+
+static inline void ast_i2c_write(struct ast_i2c_bus *bus, u32 val, u32 reg)
+{
+	writel(val, bus->base + reg);
+}
+
+static inline u32 ast_i2c_read(struct ast_i2c_bus *bus, u32 reg)
+{
+	return readl(bus->base + reg);
+}
+
+static u8 ast_i2c_recover_bus(struct ast_i2c_bus *bus)
+{
+	u32 sts;
+	unsigned long time_left;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	sts = ast_i2c_read(bus, AST_I2C_CMD_REG);
+	/* Bus is idle: no recovery needed. */
+	if ((sts & AST_I2CD_SDA_LINE_STS) && (sts & AST_I2CD_SCL_LINE_STS)) {
+		spin_unlock_irqrestore(&bus->lock, flags);
+		return 0;
+	}
+	dev_dbg(bus->dev, "bus hung (status %x), attempting recovery\n", sts);
+
+	/* Bus held: put bus in stop state. */
+	if ((sts & AST_I2CD_SDA_LINE_STS) && !(sts & AST_I2CD_SCL_LINE_STS)) {
+		ast_i2c_write(bus, AST_I2CD_M_STOP_CMD, AST_I2C_CMD_REG);
+		reinit_completion(&bus->cmd_complete);
+		spin_unlock_irqrestore(&bus->lock, flags);
+
+		time_left = wait_for_completion_interruptible_timeout(
+				&bus->cmd_complete, bus->adap.timeout * HZ);
+
+		spin_lock_irqsave(&bus->lock, flags);
+		if (time_left == 0)
+			ret = -ETIMEDOUT;
+		else if (bus->cmd_err)
+			ret = -EIO;
+	/* Bus error. */
+	} else if (!(sts & AST_I2CD_SDA_LINE_STS)) {
+		ast_i2c_write(bus, AST_I2CD_BUS_RECOVER_CMD, AST_I2C_CMD_REG);
+		reinit_completion(&bus->cmd_complete);
+		spin_unlock_irqrestore(&bus->lock, flags);
+
+		time_left = wait_for_completion_interruptible_timeout(
+				&bus->cmd_complete, bus->adap.timeout * HZ);
+
+		spin_lock_irqsave(&bus->lock, flags);
+		if (time_left == 0)
+			ret = -ETIMEDOUT;
+		else if (bus->cmd_err)
+			ret = -EIO;
+		/* Recovery failed. */
+		else if (!(ast_i2c_read(bus, AST_I2C_CMD_REG) &
+			   AST_I2CD_SDA_LINE_STS))
+			ret = -EIO;
+	}
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return ret;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static bool ast_i2c_slave_irq(struct ast_i2c_bus *bus)
+{
+	bool irq_handled = true;
+	u32 command;
+	u32 irq_status;
+	u32 status_ack = 0;
+	u8 value;
+	enum i2c_slave_event event;
+	struct i2c_client *slave = bus->slave;
+
+	spin_lock(&bus->lock);
+	if (!slave) {
+		irq_handled = false;
+		goto out;
+	}
+	command = ast_i2c_read(bus, AST_I2C_CMD_REG);
+	irq_status = ast_i2c_read(bus, AST_I2C_INTR_STS_REG);
+
+	/* Slave was requested, restart state machine. */
+	if (irq_status & AST_I2CD_INTR_SLAVE_MATCH) {
+		status_ack |= AST_I2CD_INTR_SLAVE_MATCH;
+		bus->slave_state = AST_I2C_SLAVE_START;
+	}
+	/* Slave is not currently active, irq was for someone else. */
+	if (bus->slave_state == AST_I2C_SLAVE_STOP) {
+		irq_handled = false;
+		goto out;
+	}
+
+	dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
+		irq_status, command);
+
+	/* Slave was sent something. */
+	if (irq_status & AST_I2CD_INTR_RX_DONE) {
+		value = ast_i2c_read(bus, AST_I2C_BYTE_BUF_REG) >> 8;
+		/* Handle address frame. */
+		if (bus->slave_state == AST_I2C_SLAVE_START) {
+			if (value & 0x1)
+				bus->slave_state = AST_I2C_SLAVE_READ_REQUESTED;
+			else
+				bus->slave_state =
+						AST_I2C_SLAVE_WRITE_REQUESTED;
+		}
+		status_ack |= AST_I2CD_INTR_RX_DONE;
+	}
+
+	/* Slave was asked to stop. */
+	if (irq_status & AST_I2CD_INTR_NORMAL_STOP ||
+	    irq_status & AST_I2CD_INTR_TX_NAK) {
+		if (irq_status & AST_I2CD_INTR_NORMAL_STOP)
+			status_ack |= AST_I2CD_INTR_NORMAL_STOP;
+		else
+			status_ack |= AST_I2CD_INTR_TX_NAK;
+		bus->slave_state = AST_I2C_SLAVE_STOP;
+	}
+
+	if (bus->slave_state == AST_I2C_SLAVE_READ_REQUESTED ||
+	    bus->slave_state == AST_I2C_SLAVE_READ_PROCESSED) {
+		if (bus->slave_state == AST_I2C_SLAVE_READ_REQUESTED) {
+			event = I2C_SLAVE_READ_REQUESTED;
+			if (irq_status & AST_I2CD_INTR_TX_ACK)
+				dev_err(bus->dev,
+					"Unexpected ACK on read request.\n");
+		} else {
+			status_ack |= AST_I2CD_INTR_TX_ACK;
+			event = I2C_SLAVE_READ_PROCESSED;
+			if (!(irq_status & AST_I2CD_INTR_TX_ACK))
+				dev_err(bus->dev,
+					"Expected ACK after processed read.\n");
+		}
+		bus->slave_state = AST_I2C_SLAVE_READ_PROCESSED;
+
+		i2c_slave_event(slave, event, &value);
+		ast_i2c_write(bus, value, AST_I2C_BYTE_BUF_REG);
+		ast_i2c_write(bus, AST_I2CD_S_TX_CMD, AST_I2C_CMD_REG);
+	} else if (bus->slave_state == AST_I2C_SLAVE_WRITE_REQUESTED) {
+		bus->slave_state = AST_I2C_SLAVE_WRITE_RECEIVED;
+		i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value);
+	} else if (bus->slave_state == AST_I2C_SLAVE_WRITE_RECEIVED) {
+		i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value);
+	} else if (bus->slave_state == AST_I2C_SLAVE_STOP) {
+		i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
+	}
+
+	if (status_ack != irq_status)
+		dev_err(bus->dev,
+			"irq handled != irq. expected %x, but was %x\n",
+			irq_status, status_ack);
+	ast_i2c_write(bus, status_ack, AST_I2C_INTR_STS_REG);
+
+out:
+	spin_unlock(&bus->lock);
+	return irq_handled;
+}
+#endif
+
+static bool ast_i2c_master_irq(struct ast_i2c_bus *bus)
+{
+	const u32 errs = AST_I2CD_INTR_ARBIT_LOSS |
+		AST_I2CD_INTR_ABNORMAL |
+		AST_I2CD_INTR_SCL_TIMEOUT |
+		AST_I2CD_INTR_SDA_DL_TIMEOUT |
+		AST_I2CD_INTR_TX_NAK;
+	u32 irq_status;
+
+	spin_lock(&bus->lock);
+	irq_status = ast_i2c_read(bus, AST_I2C_INTR_STS_REG);
+	bus->cmd_err |= irq_status & errs;
+
+	dev_dbg(bus->dev, "master irq status 0x%08x\n", irq_status);
+
+	/* No message to transfer. */
+	if (bus->cmd_err ||
+	    (irq_status & AST_I2CD_INTR_NORMAL_STOP) ||
+	    (irq_status & AST_I2CD_INTR_BUS_RECOVER_DONE)) {
+		complete(&bus->cmd_complete);
+		goto out;
+	} else if (!bus->msg || bus->msg_pos >= bus->msg->len)
+		goto out;
+
+	if ((bus->msg->flags & I2C_M_RD) &&
+	    (irq_status & AST_I2CD_INTR_RX_DONE)) {
+		bus->msg->buf[bus->msg_pos++] =
+				ast_i2c_read(bus, AST_I2C_BYTE_BUF_REG) >> 8;
+		if (bus->msg_pos + 1 < bus->msg->len)
+			ast_i2c_write(bus, AST_I2CD_M_RX_CMD, AST_I2C_CMD_REG);
+		else if (bus->msg_pos < bus->msg->len)
+			ast_i2c_write(bus, AST_I2CD_M_RX_CMD |
+				      AST_I2CD_M_S_RX_CMD_LAST,
+				      AST_I2C_CMD_REG);
+	} else if (!(bus->msg->flags & I2C_M_RD) &&
+		   (irq_status & AST_I2CD_INTR_TX_ACK)) {
+		ast_i2c_write(bus, bus->msg->buf[bus->msg_pos++],
+			      AST_I2C_BYTE_BUF_REG);
+		ast_i2c_write(bus, AST_I2CD_M_TX_CMD, AST_I2C_CMD_REG);
+	}
+
+	/* Transmission complete: notify caller. */
+	if (bus->msg_pos >= bus->msg->len)
+		complete(&bus->cmd_complete);
+out:
+	ast_i2c_write(bus, irq_status, AST_I2C_INTR_STS_REG);
+	spin_unlock(&bus->lock);
+	return true;
+}
+
+static irqreturn_t ast_i2c_bus_irq(int irq, void *dev_id)
+{
+	struct ast_i2c_bus *bus = dev_id;
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	if (ast_i2c_slave_irq(bus)) {
+		dev_dbg(bus->dev, "irq handled by slave.\n");
+		return IRQ_HANDLED;
+	}
+#endif
+	if (ast_i2c_master_irq(bus)) {
+		dev_dbg(bus->dev, "irq handled by master.\n");
+		return IRQ_HANDLED;
+	}
+	dev_err(bus->dev, "irq not handled properly!\n");
+	return IRQ_HANDLED;
+}
+
+static int ast_i2c_master_single_xfer(struct i2c_adapter *adap,
+				      struct i2c_msg *msg)
+{
+	struct ast_i2c_bus *bus = adap->algo_data;
+	unsigned long flags;
+	u8 slave_addr;
+	u32 command = AST_I2CD_M_START_CMD | AST_I2CD_M_TX_CMD;
+	int ret = msg->len;
+	unsigned long time_left;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	bus->msg = msg;
+	bus->msg_pos = 0;
+	slave_addr = msg->addr << 1;
+	if (msg->flags & I2C_M_RD) {
+		slave_addr |= 1;
+		command |= AST_I2CD_M_RX_CMD;
+	}
+	ast_i2c_write(bus, slave_addr, AST_I2C_BYTE_BUF_REG);
+	ast_i2c_write(bus, command, AST_I2C_CMD_REG);
+	reinit_completion(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	time_left = wait_for_completion_interruptible_timeout(
+			&bus->cmd_complete, bus->adap.timeout * HZ * msg->len);
+	if (time_left == 0)
+		return -ETIMEDOUT;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	if (bus->cmd_err)
+		ret = -EIO;
+	bus->msg = NULL;
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	return ret;
+}
+
+static int ast_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+			       int num)
+{
+	struct ast_i2c_bus *bus = adap->algo_data;
+	int ret;
+	int i;
+	unsigned long flags;
+	unsigned long time_left;
+
+	/* If bus is busy, attempt recovery. We assume a single master
+	 * environment.
+	 */
+	if (ast_i2c_read(bus, AST_I2C_CMD_REG) & AST_I2CD_BUS_BUSY_STS) {
+		ret = ast_i2c_recover_bus(bus);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < num; i++) {
+		ret = ast_i2c_master_single_xfer(adap, &msgs[i]);
+		if (ret < 0)
+			break;
+		/* TODO: Support other forms of I2C protocol mangling. */
+		if (msgs[i].flags & I2C_M_STOP) {
+			spin_lock_irqsave(&bus->lock, flags);
+			ast_i2c_write(bus, AST_I2CD_M_STOP_CMD,
+				      AST_I2C_CMD_REG);
+			reinit_completion(&bus->cmd_complete);
+			spin_unlock_irqrestore(&bus->lock, flags);
+
+			time_left = wait_for_completion_interruptible_timeout(
+					&bus->cmd_complete,
+					bus->adap.timeout * HZ);
+			if (time_left == 0)
+				return -ETIMEDOUT;
+		}
+	}
+
+	spin_lock_irqsave(&bus->lock, flags);
+	ast_i2c_write(bus, AST_I2CD_M_STOP_CMD, AST_I2C_CMD_REG);
+	reinit_completion(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	time_left = wait_for_completion_interruptible_timeout(
+			&bus->cmd_complete, bus->adap.timeout * HZ);
+	if (time_left == 0)
+		return -ETIMEDOUT;
+
+	/* If nothing went wrong, return number of messages transferred. */
+	if (ret < 0)
+		return ret;
+	else
+		return i;
+}
+
+static u32 ast_i2c_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static int ast_i2c_reg_slave(struct i2c_client *client)
+{
+	struct ast_i2c_bus *bus;
+	unsigned long flags;
+	u32 addr_reg_val;
+	u32 func_ctrl_reg_val;
+
+	bus = client->adapter->algo_data;
+	spin_lock_irqsave(&bus->lock, flags);
+	if (bus->slave) {
+		spin_unlock_irqrestore(&bus->lock, flags);
+		return -EINVAL;
+	}
+
+	/* Set slave addr. */
+	addr_reg_val = ast_i2c_read(bus, AST_I2C_DEV_ADDR_REG);
+	addr_reg_val &= ~AST_I2CD_DEV_ADDR_MASK;
+	addr_reg_val |= client->addr & AST_I2CD_DEV_ADDR_MASK;
+	ast_i2c_write(bus, addr_reg_val, AST_I2C_DEV_ADDR_REG);
+
+	/* Switch from master mode to slave mode. */
+	func_ctrl_reg_val = ast_i2c_read(bus, AST_I2C_FUN_CTRL_REG);
+	func_ctrl_reg_val &= ~AST_I2CD_MASTER_EN;
+	func_ctrl_reg_val |= AST_I2CD_SLAVE_EN;
+	ast_i2c_write(bus, func_ctrl_reg_val, AST_I2C_FUN_CTRL_REG);
+
+	bus->slave = client;
+	bus->slave_state = AST_I2C_SLAVE_STOP;
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return 0;
+}
+
+static int ast_i2c_unreg_slave(struct i2c_client *client)
+{
+	struct ast_i2c_bus *bus = client->adapter->algo_data;
+	unsigned long flags;
+	u32 func_ctrl_reg_val;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	if (!bus->slave) {
+		spin_unlock_irqrestore(&bus->lock, flags);
+		return -EINVAL;
+	}
+
+	/* Switch from slave mode to master mode. */
+	func_ctrl_reg_val = ast_i2c_read(bus, AST_I2C_FUN_CTRL_REG);
+	func_ctrl_reg_val &= ~AST_I2CD_SLAVE_EN;
+	func_ctrl_reg_val |= AST_I2CD_MASTER_EN;
+	ast_i2c_write(bus, func_ctrl_reg_val, AST_I2C_FUN_CTRL_REG);
+
+	bus->slave = NULL;
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return 0;
+}
+#endif
+
+static const struct i2c_algorithm ast_i2c_algo = {
+	.master_xfer	= ast_i2c_master_xfer,
+	.functionality	= ast_i2c_functionality,
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	.reg_slave	= ast_i2c_reg_slave,
+	.unreg_slave	= ast_i2c_unreg_slave,
+#endif
+};
+
+static u32 ast_i2c_get_clk_reg_val(u32 divider_ratio)
+{
+	unsigned int inc = 0, div;
+	u32 scl_low, scl_high, data;
+
+	for (div = 0; divider_ratio >= 16; div++) {
+		inc |= (divider_ratio & 1);
+		divider_ratio >>= 1;
+	}
+	divider_ratio += inc;
+	scl_low = (divider_ratio >> 1) - 1;
+	scl_high = divider_ratio - scl_low - 2;
+	data = 0x77700300 | (scl_high << 16) | (scl_low << 12) | div;
+	return data;
+}
+
+static int ast_i2c_init_clk(struct ast_i2c_bus *bus,
+			    struct platform_device *pdev)
+{
+	struct clk *pclk;
+	u32 clk_freq;
+	u32 divider_ratio;
+	int ret;
+
+	pclk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(pclk)) {
+		dev_err(&pdev->dev, "clk_get failed\n");
+		return PTR_ERR(pclk);
+	}
+	ret = of_property_read_u32(pdev->dev.of_node,
+			"clock-frequency", &clk_freq);
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+				"Could not read clock-frequency property\n");
+		clk_freq = 100000;
+	}
+	divider_ratio = clk_get_rate(pclk) / clk_freq;
+	/* We just need the clock rate, we don't actually use the clk object. */
+	devm_clk_put(&pdev->dev, pclk);
+
+	/* Set AC Timing */
+	if (clk_freq / 1000 > 400) {
+		ast_i2c_write(bus, ast_i2c_read(bus, AST_I2C_FUN_CTRL_REG) |
+				AST_I2CD_M_HIGH_SPEED_EN |
+				AST_I2CD_M_SDA_DRIVE_1T_EN |
+				AST_I2CD_SDA_DRIVE_1T_EN,
+				AST_I2C_FUN_CTRL_REG);
+
+		ast_i2c_write(bus, 0x3, AST_I2C_AC_TIMING_REG2);
+		ast_i2c_write(bus, ast_i2c_get_clk_reg_val(divider_ratio),
+			      AST_I2C_AC_TIMING_REG1);
+	} else {
+		ast_i2c_write(bus, ast_i2c_get_clk_reg_val(divider_ratio),
+			      AST_I2C_AC_TIMING_REG1);
+		ast_i2c_write(bus, AST_NO_TIMEOUT_CTRL, AST_I2C_AC_TIMING_REG2);
+	}
+
+	return 0;
+}
+
+static int ast_i2c_probe_bus(struct platform_device *pdev)
+{
+	struct ast_i2c_bus *bus;
+	struct resource *res;
+	int ret, bus_num;
+
+	bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
+	if (!bus)
+		return -ENOMEM;
+
+	ret = of_property_read_u32(pdev->dev.of_node, "bus", &bus_num);
+	if (ret)
+		return -ENXIO;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	bus->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(bus->base))
+		return PTR_ERR(bus->base);
+
+	bus->irq = platform_get_irq(pdev, 0);
+	if (bus->irq < 0) {
+		dev_err(&pdev->dev, "platform_get_irq failed\n");
+		return -ENXIO;
+	}
+
+	ret = devm_request_irq(&pdev->dev, bus->irq, ast_i2c_bus_irq,
+			0, dev_name(&pdev->dev), bus);
+	if (ret) {
+		dev_err(&pdev->dev, "devm_request_irq failed\n");
+		return -ENXIO;
+	}
+
+	/* Initialize the I2C adapter */
+	spin_lock_init(&bus->lock);
+	init_completion(&bus->cmd_complete);
+	bus->adap.nr = bus_num;
+	bus->adap.owner = THIS_MODULE;
+	bus->adap.retries = 0;
+	bus->adap.timeout = 5;
+	bus->adap.algo = &ast_i2c_algo;
+	bus->adap.algo_data = bus;
+	bus->adap.dev.parent = &pdev->dev;
+	bus->adap.dev.of_node = pdev->dev.of_node;
+	snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c-%d",
+			bus_num);
+
+	bus->dev = &pdev->dev;
+
+	/* reset device: disable master & slave functions */
+	ast_i2c_write(bus, 0, AST_I2C_FUN_CTRL_REG);
+
+	ret = ast_i2c_init_clk(bus, pdev);
+	if (ret < 0)
+		return ret;
+
+	/* Enable Master Mode */
+	ast_i2c_write(bus, ast_i2c_read(bus, AST_I2C_FUN_CTRL_REG) |
+		      AST_I2CD_MASTER_EN |
+		      AST_I2CD_MULTI_MASTER_DIS, AST_I2C_FUN_CTRL_REG);
+
+	/* Set interrupt generation of I2C controller */
+	ast_i2c_write(bus, AST_I2CD_INTR_SDA_DL_TIMEOUT |
+			AST_I2CD_INTR_BUS_RECOVER_DONE |
+			AST_I2CD_INTR_SCL_TIMEOUT |
+			AST_I2CD_INTR_ABNORMAL |
+			AST_I2CD_INTR_NORMAL_STOP |
+			AST_I2CD_INTR_ARBIT_LOSS |
+			AST_I2CD_INTR_RX_DONE |
+			AST_I2CD_INTR_TX_NAK |
+			AST_I2CD_INTR_TX_ACK,
+			AST_I2C_INTR_CTRL_REG);
+
+	ret = i2c_add_numbered_adapter(&bus->adap);
+	if (ret < 0)
+		return -ENXIO;
+
+	platform_set_drvdata(pdev, bus);
+
+	dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
+			bus->adap.nr, bus->irq);
+
+	return 0;
+}
+
+static int ast_i2c_remove_bus(struct platform_device *pdev)
+{
+	struct ast_i2c_bus *bus = platform_get_drvdata(pdev);
+
+	i2c_del_adapter(&bus->adap);
+	return 0;
+}
+
+static const struct of_device_id ast_i2c_bus_of_table[] = {
+	{ .compatible = "aspeed,ast2400-i2c-bus", },
+	{ .compatible = "aspeed,ast2500-i2c-bus", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, ast_i2c_of_table);
+
+static struct platform_driver ast_i2c_bus_driver = {
+	.probe		= ast_i2c_probe_bus,
+	.remove		= ast_i2c_remove_bus,
+	.driver         = {
+		.name   = "ast-i2c-bus",
+		.of_match_table = ast_i2c_bus_of_table,
+	},
+};
+module_platform_driver(ast_i2c_bus_driver);
+
+static void noop(struct irq_data *data) { }
+
+static struct irq_chip ast_i2c_irqchip = {
+	.name		= "ast-i2c",
+	.irq_unmask	= noop,
+	.irq_mask	= noop,
+};
+
+static void ast_i2c_controller_irq(struct irq_desc *desc)
+{
+	struct ast_i2c_controller *c = irq_desc_get_handler_data(desc);
+	unsigned long p, status;
+	unsigned int bus_irq;
+
+	status = readl(c->base);
+	for_each_set_bit(p, &status, AST_I2C_NUM_BUS) {
+		bus_irq = irq_find_mapping(c->irq_domain, p);
+		generic_handle_irq(bus_irq);
+	}
+}
+
+static int ast_i2c_probe_controller(struct platform_device *pdev)
+{
+	struct ast_i2c_controller *controller;
+	struct device_node *np;
+	struct resource *res;
+	int i, irq;
+
+	controller = kzalloc(sizeof(*controller), GFP_KERNEL);
+	if (!controller)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	controller->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(controller->base))
+		return PTR_ERR(controller->base);
+
+	controller->irq = platform_get_irq(pdev, 0);
+	if (controller->irq < 0) {
+		dev_err(&pdev->dev, "no platform IRQ\n");
+		return -ENXIO;
+	}
+
+	controller->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
+			AST_I2C_NUM_BUS, &irq_domain_simple_ops, NULL);
+	if (!controller->irq_domain) {
+		dev_err(&pdev->dev, "no IRQ domain\n");
+		return -ENXIO;
+	}
+	controller->irq_domain->name = "ast-i2c-domain";
+
+	for (i = 0; i < AST_I2C_NUM_BUS; i++) {
+		irq = irq_create_mapping(controller->irq_domain, i);
+		irq_set_chip_data(irq, controller);
+		irq_set_chip_and_handler(irq, &ast_i2c_irqchip,
+				handle_simple_irq);
+	}
+
+	irq_set_chained_handler_and_data(controller->irq,
+			ast_i2c_controller_irq, controller);
+
+	controller->dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, controller);
+
+	dev_info(controller->dev, "i2c controller registered, irq %d\n",
+			controller->irq);
+
+	for_each_child_of_node(pdev->dev.of_node, np) {
+		int ret;
+		u32 bus_num;
+		char bus_id[sizeof("i2c-12345")];
+
+		/*
+		 * Set a useful name derived from the bus number; the device
+		 * tree should provide us with one that corresponds to the
+		 * hardware numbering.  If the property is missing the
+		 * probe would fail so just skip it here.
+		 */
+
+		ret = of_property_read_u32(np, "bus", &bus_num);
+		if (ret)
+			continue;
+
+		ret = snprintf(bus_id, sizeof(bus_id), "i2c-%u", bus_num);
+		if (ret >= sizeof(bus_id))
+			continue;
+
+		of_platform_device_create(np, bus_id, &pdev->dev);
+		of_node_put(np);
+	}
+
+	return 0;
+}
+
+static int ast_i2c_remove_controller(struct platform_device *pdev)
+{
+	struct ast_i2c_controller *controller = platform_get_drvdata(pdev);
+
+	irq_domain_remove(controller->irq_domain);
+	return 0;
+}
+
+static const struct of_device_id ast_i2c_controller_of_table[] = {
+	{ .compatible = "aspeed,ast2400-i2c-controller", },
+	{ .compatible = "aspeed,ast2500-i2c-controller", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, ast_i2c_of_table);
+
+static struct platform_driver ast_i2c_controller_driver = {
+	.probe		= ast_i2c_probe_controller,
+	.remove		= ast_i2c_remove_controller,
+	.driver         = {
+		.name   = "ast-i2c-controller",
+		.of_match_table = ast_i2c_controller_of_table,
+	},
+};
+
+module_platform_driver(ast_i2c_controller_driver);
+
+MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>");
+MODULE_AUTHOR("Brendan Higgins <brendanhiggins@google.com>");
+MODULE_DESCRIPTION("ASPEED AST I2C Bus Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ast_i2c");
-- 
2.8.0.rc3.226.g39d4020

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/2] i2c: aspeed: cleanup driver
  2016-08-29  5:35   ` Joel Stanley
  2016-08-30 18:52     ` [PATCH v2] i2c: aspeed: added driver for Aspeed I2C Brendan Higgins
@ 2016-08-30 18:59     ` Brendan Higgins
  2016-08-30 21:44       ` Brendan Higgins
  1 sibling, 1 reply; 21+ messages in thread
From: Brendan Higgins @ 2016-08-30 18:59 UTC (permalink / raw)
  To: Joel Stanley; +Cc: OpenBMC Maillist, Andrew Jeffery, Jeremy Kerr

[-- Attachment #1: Type: text/plain, Size: 1713 bytes --]

checkpatch seems to be upset about my device tree identifiers being
undocumented; do we have a coordinated effort for this? I did not even see
a directory for aspeed under arm.

checkpatch is also asked about updating MAINTAINERS, but looks like it
should be covered under existing line:
F: drivers/*/*aspeed*
Does it just ask this every time you add a file?

changelog:
 - Squashed driver commits as suggested by Joel
 - Renamed a couple functions for consistency
 - Fixed a couple style errors

On Sun, Aug 28, 2016 at 10:35 PM Joel Stanley <joel@jms.id.au> wrote:

> Hi Brendan,
>
> On Sun, Aug 21, 2016 at 11:04 AM, Brendan Higgins
> <brendanhiggins@google.com> wrote:
> > Rewrote most of master IRQ handler, xfer functions, and error recovery.
> > Minor refactor of probing functions. Added remove functions.
>
> Thanks for the patch. The diff is quite noisy given you're deleting
> lots of cruft from the vendor driver. It would be easier for us to
> review if you send the patch that adds the driver from scratch.
>
> One suggestion I did have is to rename the driver to use the aspeed_
> prefix for all functions so that it matches the drivers that have
> already gone upstream.
>
> Given our goal is immediate upstream submission, how about you send a
> complete patch that adds the driver to upstream (say 4.8-rc4?)? Send
> it to the i2c lists and cc myself, jk@ozlabs.org, and andrew@aj.id.au.
>
> I hope we can get this sent out for upstream review this week. Let me
> know if there's anything I can do to help achieve that.
>
> Cheers,
>
> Joel
>
> > ---
> >  drivers/i2c/busses/i2c-aspeed.c | 932
> ++++++++++++++--------------------------
> >  1 file changed, 318 insertions(+), 614 deletions(-)
>

[-- Attachment #2: Type: text/html, Size: 2399 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/2] i2c: aspeed: cleanup driver
  2016-08-30 18:59     ` [PATCH 2/2] i2c: aspeed: cleanup driver Brendan Higgins
@ 2016-08-30 21:44       ` Brendan Higgins
  2016-08-30 22:15         ` Joel Stanley
  0 siblings, 1 reply; 21+ messages in thread
From: Brendan Higgins @ 2016-08-30 21:44 UTC (permalink / raw)
  To: Joel Stanley; +Cc: OpenBMC Maillist, Andrew Jeffery, Jeremy Kerr

[-- Attachment #1: Type: text/plain, Size: 2005 bytes --]

Oh, and one more note: I moved out the eeprom quirk patch from this patch
set because I think it makes sense as a different submission upstream.

On Tue, Aug 30, 2016 at 11:59 AM Brendan Higgins <brendanhiggins@google.com>
wrote:

> checkpatch seems to be upset about my device tree identifiers being
> undocumented; do we have a coordinated effort for this? I did not even see
> a directory for aspeed under arm.
>
> checkpatch is also asked about updating MAINTAINERS, but looks like it
> should be covered under existing line:
> F: drivers/*/*aspeed*
> Does it just ask this every time you add a file?
>
> changelog:
>  - Squashed driver commits as suggested by Joel
>  - Renamed a couple functions for consistency
>  - Fixed a couple style errors
>
> On Sun, Aug 28, 2016 at 10:35 PM Joel Stanley <joel@jms.id.au> wrote:
>
>> Hi Brendan,
>>
>> On Sun, Aug 21, 2016 at 11:04 AM, Brendan Higgins
>> <brendanhiggins@google.com> wrote:
>> > Rewrote most of master IRQ handler, xfer functions, and error recovery.
>> > Minor refactor of probing functions. Added remove functions.
>>
>> Thanks for the patch. The diff is quite noisy given you're deleting
>> lots of cruft from the vendor driver. It would be easier for us to
>> review if you send the patch that adds the driver from scratch.
>>
>> One suggestion I did have is to rename the driver to use the aspeed_
>> prefix for all functions so that it matches the drivers that have
>> already gone upstream.
>>
>> Given our goal is immediate upstream submission, how about you send a
>> complete patch that adds the driver to upstream (say 4.8-rc4?)? Send
>> it to the i2c lists and cc myself, jk@ozlabs.org, and andrew@aj.id.au.
>>
>> I hope we can get this sent out for upstream review this week. Let me
>> know if there's anything I can do to help achieve that.
>>
>> Cheers,
>>
>> Joel
>>
>> > ---
>> >  drivers/i2c/busses/i2c-aspeed.c | 932
>> ++++++++++++++--------------------------
>> >  1 file changed, 318 insertions(+), 614 deletions(-)
>>
>

[-- Attachment #2: Type: text/html, Size: 2917 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/2] i2c: aspeed: cleanup driver
  2016-08-30 21:44       ` Brendan Higgins
@ 2016-08-30 22:15         ` Joel Stanley
  2016-08-30 22:24           ` Brendan Higgins
  0 siblings, 1 reply; 21+ messages in thread
From: Joel Stanley @ 2016-08-30 22:15 UTC (permalink / raw)
  To: Brendan Higgins; +Cc: OpenBMC Maillist, Andrew Jeffery, Jeremy Kerr

On Wed, Aug 31, 2016 at 7:14 AM, Brendan Higgins
<brendanhiggins@google.com> wrote:
> Oh, and one more note: I moved out the eeprom quirk patch from this patch
> set because I think it makes sense as a different submission upstream.

That makes sense to me.

>
> On Tue, Aug 30, 2016 at 11:59 AM Brendan Higgins <brendanhiggins@google.com>
> wrote:
>>
>> checkpatch seems to be upset about my device tree identifiers being
>> undocumented; do we have a coordinated effort for this? I did not even see a
>> directory for aspeed under arm.

As this is a driver, the binding should be added in
Documentation/devicetree/bindings/i2c/. Use one of the recently added
existing drivers as a template. Add the binding in a separate commit,
coming before the addition of the driver. When you send it upstream
you will cc the device tree maintainers, who will ack your bindings.
The driver maintainer will then merge the two patches into their tree.

>>
>> checkpatch is also asked about updating MAINTAINERS, but looks like it
>> should be covered under existing line:
>> F: drivers/*/*aspeed*
>> Does it just ask this every time you add a file?

Yeah, I think it just complains for the sake of it.

>>
>> changelog:
>>  - Squashed driver commits as suggested by Joel
>>  - Renamed a couple functions for consistency
>>  - Fixed a couple style errors

Did you send a new version?

Cheers,

Joel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/2] i2c: aspeed: cleanup driver
  2016-08-30 22:15         ` Joel Stanley
@ 2016-08-30 22:24           ` Brendan Higgins
  2016-08-30 22:38             ` Joel Stanley
  0 siblings, 1 reply; 21+ messages in thread
From: Brendan Higgins @ 2016-08-30 22:24 UTC (permalink / raw)
  To: Joel Stanley; +Cc: OpenBMC Maillist, Andrew Jeffery, Jeremy Kerr

[-- Attachment #1: Type: text/plain, Size: 1887 bytes --]

>> Did you send a new version?

Yeah, the subject is: [PATCH v2] i2c: aspeed: added driver for Aspeed I2C
I sent it --in-reply-to=<your previous message ID>, does it not show as a
child of the thread in your email client?

Sorry if changing the commit message title screwed things up for you, but I
felt that the clean up title was no longer fitting.

On Tue, Aug 30, 2016 at 3:15 PM Joel Stanley <joel@jms.id.au> wrote:

> On Wed, Aug 31, 2016 at 7:14 AM, Brendan Higgins
> <brendanhiggins@google.com> wrote:
> > Oh, and one more note: I moved out the eeprom quirk patch from this patch
> > set because I think it makes sense as a different submission upstream.
>
> That makes sense to me.
>
> >
> > On Tue, Aug 30, 2016 at 11:59 AM Brendan Higgins <
> brendanhiggins@google.com>
> > wrote:
> >>
> >> checkpatch seems to be upset about my device tree identifiers being
> >> undocumented; do we have a coordinated effort for this? I did not even
> see a
> >> directory for aspeed under arm.
>
> As this is a driver, the binding should be added in
> Documentation/devicetree/bindings/i2c/. Use one of the recently added
> existing drivers as a template. Add the binding in a separate commit,
> coming before the addition of the driver. When you send it upstream
> you will cc the device tree maintainers, who will ack your bindings.
> The driver maintainer will then merge the two patches into their tree.
>
> >>
> >> checkpatch is also asked about updating MAINTAINERS, but looks like it
> >> should be covered under existing line:
> >> F: drivers/*/*aspeed*
> >> Does it just ask this every time you add a file?
>
> Yeah, I think it just complains for the sake of it.
>
> >>
> >> changelog:
> >>  - Squashed driver commits as suggested by Joel
> >>  - Renamed a couple functions for consistency
> >>  - Fixed a couple style errors
>
> Did you send a new version?
>
> Cheers,
>
> Joel
>

[-- Attachment #2: Type: text/html, Size: 2684 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/2] i2c: aspeed: cleanup driver
  2016-08-30 22:24           ` Brendan Higgins
@ 2016-08-30 22:38             ` Joel Stanley
  0 siblings, 0 replies; 21+ messages in thread
From: Joel Stanley @ 2016-08-30 22:38 UTC (permalink / raw)
  To: Brendan Higgins; +Cc: OpenBMC Maillist, Andrew Jeffery, Jeremy Kerr

On Wed, Aug 31, 2016 at 7:54 AM, Brendan Higgins
<brendanhiggins@google.com> wrote:
>>> Did you send a new version?
>
> Yeah, the subject is: [PATCH v2] i2c: aspeed: added driver for Aspeed I2C
> I sent it --in-reply-to=<your previous message ID>, does it not show as a
> child of the thread in your email client?

I can see it in the list archives, but it never arrived in my inbox.
I'm a little surprised given that I'm using Gmail.

> Sorry if changing the commit message title screwed things up for you, but I
> felt that the clean up title was no longer fitting.

Nope, that's fine.

When you send it next with the bindings document, feel free to reset
the clock and drop the v2 or whatever and send it as a fresh patch.

Cheers,

Joel

>
> On Tue, Aug 30, 2016 at 3:15 PM Joel Stanley <joel@jms.id.au> wrote:
>>
>> On Wed, Aug 31, 2016 at 7:14 AM, Brendan Higgins
>> <brendanhiggins@google.com> wrote:
>> > Oh, and one more note: I moved out the eeprom quirk patch from this
>> > patch
>> > set because I think it makes sense as a different submission upstream.
>>
>> That makes sense to me.
>>
>> >
>> > On Tue, Aug 30, 2016 at 11:59 AM Brendan Higgins
>> > <brendanhiggins@google.com>
>> > wrote:
>> >>
>> >> checkpatch seems to be upset about my device tree identifiers being
>> >> undocumented; do we have a coordinated effort for this? I did not even
>> >> see a
>> >> directory for aspeed under arm.
>>
>> As this is a driver, the binding should be added in
>> Documentation/devicetree/bindings/i2c/. Use one of the recently added
>> existing drivers as a template. Add the binding in a separate commit,
>> coming before the addition of the driver. When you send it upstream
>> you will cc the device tree maintainers, who will ack your bindings.
>> The driver maintainer will then merge the two patches into their tree.
>>
>> >>
>> >> checkpatch is also asked about updating MAINTAINERS, but looks like it
>> >> should be covered under existing line:
>> >> F: drivers/*/*aspeed*
>> >> Does it just ask this every time you add a file?
>>
>> Yeah, I think it just complains for the sake of it.
>>
>> >>
>> >> changelog:
>> >>  - Squashed driver commits as suggested by Joel
>> >>  - Renamed a couple functions for consistency
>> >>  - Fixed a couple style errors
>>
>> Did you send a new version?
>>
>> Cheers,
>>
>> Joel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2] i2c: aspeed: added driver for Aspeed I2C
  2016-08-30 18:52     ` [PATCH v2] i2c: aspeed: added driver for Aspeed I2C Brendan Higgins
@ 2016-09-07 23:54       ` Joel Stanley
  2016-09-09  1:24         ` [PATCH v2 1/2] " Brendan Higgins
  0 siblings, 1 reply; 21+ messages in thread
From: Joel Stanley @ 2016-09-07 23:54 UTC (permalink / raw)
  To: Brendan Higgins; +Cc: OpenBMC Maillist

On Wed, Aug 31, 2016 at 4:22 AM, Brendan Higgins
<brendanhiggins@google.com> wrote:
> Added initial master and slave support for Aspeed I2C controller.
> Supports fourteen busses present in ast24xx and ast25xx BMC SoCs by
> Aspeed.
>
> Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
> ---
>  drivers/i2c/busses/Kconfig      |  10 +
>  drivers/i2c/busses/Makefile     |   1 +
>  drivers/i2c/busses/i2c-aspeed.c | 812 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 823 insertions(+)
>  create mode 100644 drivers/i2c/busses/i2c-aspeed.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 5c3993b..0178c6c 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -998,6 +998,16 @@ config I2C_RCAR
>           This driver can also be built as a module.  If so, the module
>           will be called i2c-rcar.
>
> +config I2C_ASPEED
> +       tristate "Aspeed AST2xxx SoC I2C Controller"
> +       depends on (ARCH_ASPEED || COMPILE_TEST) && OF
> +       help
> +         If you say yes to this option, support will be included for the
> +         Aspeed AST2xxx SoC I2C controller.
> +
> +         This driver can also be built as a module.  If so, the module
> +         will be called i2c-aspeed.
> +
>  comment "External I2C/SMBus adapter drivers"
>
>  config I2C_DIOLAN_U2C
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 37f2819..49631cd 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -96,6 +96,7 @@ obj-$(CONFIG_I2C_XILINX)      += i2c-xiic.o
>  obj-$(CONFIG_I2C_XLR)          += i2c-xlr.o
>  obj-$(CONFIG_I2C_XLP9XX)       += i2c-xlp9xx.o
>  obj-$(CONFIG_I2C_RCAR)         += i2c-rcar.o
> +obj-$(CONFIG_I2C_ASPEED)       += i2c-aspeed.o
>
>  # External I2C/SMBus adapter drivers
>  obj-$(CONFIG_I2C_DIOLAN_U2C)   += i2c-diolan-u2c.o
> diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
> new file mode 100644
> index 0000000..81c96e2
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-aspeed.c
> @@ -0,0 +1,812 @@
> +/*
> + *  I2C adapter for the ASPEED I2C bus.
> + *
> + *  Copyright (C) 2012-2020  ASPEED Technology Inc.
> + *  Copyright 2015 IBM Corporation

Can you update this to 2016? And add your own?

> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License version 2 as
> + *  published by the Free Software Foundation.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/errno.h>
> +#include <linux/interrupt.h>
> +#include <linux/completion.h>
> +#include <linux/slab.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +
> +/* I2C Register */
> +#define AST_I2C_FUN_CTRL_REG                           0x00
> +#define AST_I2C_AC_TIMING_REG1                         0x04
> +#define AST_I2C_AC_TIMING_REG2                         0x08
> +#define AST_I2C_INTR_CTRL_REG                          0x0c
> +#define AST_I2C_INTR_STS_REG                           0x10
> +#define AST_I2C_CMD_REG                                0x14
> +#define AST_I2C_DEV_ADDR_REG                           0x18
> +#define AST_I2C_BYTE_BUF_REG                           0x20

I would prefer using the ASPEED_ prefix for all #defines and functions
for consistency with other kernel code.

> +
> +#define AST_I2C_NUM_BUS 14

> +
> +/* Gloable Register Definition */

Typo: Global

> +/* 0x00 : I2C Interrupt Status Register  */
> +/* 0x08 : I2C Interrupt Target Assignment  */
> +
> +/* Device Register Definition */
> +/* 0x00 : I2CD Function Control Register  */
> +#define AST_I2CD_MULTI_MASTER_DIS                      (0x1 << 15)
> +#define AST_I2CD_SDA_DRIVE_1T_EN                       (0x1 << 8)
> +#define AST_I2CD_M_SDA_DRIVE_1T_EN                     (0x1 << 7)
> +#define AST_I2CD_M_HIGH_SPEED_EN                       (0x1 << 6)
> +#define AST_I2CD_SLAVE_EN                              (0x1 << 1)

If you want to convert these to use the BIT(1), etc notation.

> +#define AST_I2CD_MASTER_EN                             (0x1)
> +
> +/* 0x08 : I2CD Clock and AC Timing Control Register #2 */
> +#define AST_NO_TIMEOUT_CTRL                            0x0
> +
> +
> +/* 0x0c : I2CD Interrupt Control Register &
> + * 0x10 : I2CD Interrupt Status Register
> + *
> + * These share bit definitions, so use the same values for the enable &
> + * status bits.
> + */
> +#define AST_I2CD_INTR_SDA_DL_TIMEOUT                   (0x1 << 14)
> +#define AST_I2CD_INTR_BUS_RECOVER_DONE                 (0x1 << 13)
> +#define AST_I2CD_INTR_SLAVE_MATCH                      (0x1 << 7)
> +#define AST_I2CD_INTR_SCL_TIMEOUT                      (0x1 << 6)
> +#define AST_I2CD_INTR_ABNORMAL                         (0x1 << 5)
> +#define AST_I2CD_INTR_NORMAL_STOP                      (0x1 << 4)
> +#define AST_I2CD_INTR_ARBIT_LOSS                       (0x1 << 3)
> +#define AST_I2CD_INTR_RX_DONE                          (0x1 << 2)
> +#define AST_I2CD_INTR_TX_NAK                           (0x1 << 1)
> +#define AST_I2CD_INTR_TX_ACK                           (0x1 << 0)
> +
> +/* 0x14 : I2CD Command/Status Register   */
> +#define AST_I2CD_SCL_LINE_STS                          (0x1 << 18)
> +#define AST_I2CD_SDA_LINE_STS                          (0x1 << 17)
> +#define AST_I2CD_BUS_BUSY_STS                          (0x1 << 16)
> +#define AST_I2CD_BUS_RECOVER_CMD                       (0x1 << 11)
> +
> +/* Command Bit */
> +#define AST_I2CD_M_STOP_CMD                            (0x1 << 5)
> +#define AST_I2CD_M_S_RX_CMD_LAST                       (0x1 << 4)
> +#define AST_I2CD_M_RX_CMD                              (0x1 << 3)
> +#define AST_I2CD_S_TX_CMD                              (0x1 << 2)
> +#define AST_I2CD_M_TX_CMD                              (0x1 << 1)
> +#define AST_I2CD_M_START_CMD                           (0x1)
> +
> +/* 0x18 : I2CD Slave Device Address Register   */
> +#define AST_I2CD_DEV_ADDR_MASK                          ((0x1 << 7) - 1)
> +
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)

Can we get away without the guard here?

> +enum ast_i2c_slave_state {
> +       AST_I2C_SLAVE_START,
> +       AST_I2C_SLAVE_READ_REQUESTED,
> +       AST_I2C_SLAVE_READ_PROCESSED,
> +       AST_I2C_SLAVE_WRITE_REQUESTED,
> +       AST_I2C_SLAVE_WRITE_RECEIVED,
> +       AST_I2C_SLAVE_STOP,
> +};
> +#endif
> +
> +struct ast_i2c_bus {
> +       struct i2c_adapter              adap;
> +       struct device                   *dev;
> +       void __iomem                    *base;
> +       spinlock_t                      lock;
> +       struct completion               cmd_complete;
> +       int                             irq;
> +       /* Transaction state. */
> +       struct i2c_msg                  *msg;
> +       int                             msg_pos;
> +       u32                             cmd_err;
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +       struct i2c_client               *slave;
> +       enum ast_i2c_slave_state        slave_state;
> +#endif
> +};
> +
> +struct ast_i2c_controller {
> +       struct device           *dev;
> +       void __iomem            *base;
> +       int                     irq;
> +       struct irq_domain       *irq_domain;
> +};
> +
> +static inline void ast_i2c_write(struct ast_i2c_bus *bus, u32 val, u32 reg)
> +{
> +       writel(val, bus->base + reg);
> +}
> +
> +static inline u32 ast_i2c_read(struct ast_i2c_bus *bus, u32 reg)
> +{
> +       return readl(bus->base + reg);
> +}
> +
> +static u8 ast_i2c_recover_bus(struct ast_i2c_bus *bus)
> +{
> +       u32 sts;
> +       unsigned long time_left;
> +       unsigned long flags;
> +       int ret = 0;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       sts = ast_i2c_read(bus, AST_I2C_CMD_REG);

s/sts/status/

> +       /* Bus is idle: no recovery needed. */
> +       if ((sts & AST_I2CD_SDA_LINE_STS) && (sts & AST_I2CD_SCL_LINE_STS)) {
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +               return 0;

Would goto out, with out being just above the _ireqrestore at the
bottom of this function, make it clearer that we are doing the correct
lock/unlocking?

> +       }
> +       dev_dbg(bus->dev, "bus hung (status %x), attempting recovery\n", sts);
> +
> +       /* Bus held: put bus in stop state. */
> +       if ((sts & AST_I2CD_SDA_LINE_STS) && !(sts & AST_I2CD_SCL_LINE_STS)) {
> +               ast_i2c_write(bus, AST_I2CD_M_STOP_CMD, AST_I2C_CMD_REG);
> +               reinit_completion(&bus->cmd_complete);
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +
> +               time_left = wait_for_completion_interruptible_timeout(
> +                               &bus->cmd_complete, bus->adap.timeout * HZ);
> +
> +               spin_lock_irqsave(&bus->lock, flags);

I assume we are releasing the lock to wait for the completion?

> +               if (time_left == 0)
> +                       ret = -ETIMEDOUT;
> +               else if (bus->cmd_err)
> +                       ret = -EIO;
> +       /* Bus error. */
> +       } else if (!(sts & AST_I2CD_SDA_LINE_STS)) {
> +               ast_i2c_write(bus, AST_I2CD_BUS_RECOVER_CMD, AST_I2C_CMD_REG);
> +               reinit_completion(&bus->cmd_complete);
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +
> +               time_left = wait_for_completion_interruptible_timeout(
> +                               &bus->cmd_complete, bus->adap.timeout * HZ);
> +
> +               spin_lock_irqsave(&bus->lock, flags);
> +               if (time_left == 0)
> +                       ret = -ETIMEDOUT;
> +               else if (bus->cmd_err)
> +                       ret = -EIO;
> +               /* Recovery failed. */
> +               else if (!(ast_i2c_read(bus, AST_I2C_CMD_REG) &
> +                          AST_I2CD_SDA_LINE_STS))
> +                       ret = -EIO;
> +       }
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +       return ret;
> +}
> +
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +static bool ast_i2c_slave_irq(struct ast_i2c_bus *bus)
> +{
> +       bool irq_handled = true;
> +       u32 command;
> +       u32 irq_status;

Above we call it "sts", down here we call it "irq_status". I suggest a
consistent name.

> +       u32 status_ack = 0;
> +       u8 value;
> +       enum i2c_slave_event event;
> +       struct i2c_client *slave = bus->slave;
> +
> +       spin_lock(&bus->lock);
> +       if (!slave) {
> +               irq_handled = false;
> +               goto out;
> +       }
> +       command = ast_i2c_read(bus, AST_I2C_CMD_REG);
> +       irq_status = ast_i2c_read(bus, AST_I2C_INTR_STS_REG);
> +
> +       /* Slave was requested, restart state machine. */
> +       if (irq_status & AST_I2CD_INTR_SLAVE_MATCH) {
> +               status_ack |= AST_I2CD_INTR_SLAVE_MATCH;
> +               bus->slave_state = AST_I2C_SLAVE_START;
> +       }
> +       /* Slave is not currently active, irq was for someone else. */
> +       if (bus->slave_state == AST_I2C_SLAVE_STOP) {
> +               irq_handled = false;
> +               goto out;
> +       }
> +
> +       dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
> +               irq_status, command);
> +
> +       /* Slave was sent something. */
> +       if (irq_status & AST_I2CD_INTR_RX_DONE) {
> +               value = ast_i2c_read(bus, AST_I2C_BYTE_BUF_REG) >> 8;

What is "value"? Perhaps "request"?

> +               /* Handle address frame. */
> +               if (bus->slave_state == AST_I2C_SLAVE_START) {
> +                       if (value & 0x1)
> +                               bus->slave_state = AST_I2C_SLAVE_READ_REQUESTED;
> +                       else
> +                               bus->slave_state =
> +                                               AST_I2C_SLAVE_WRITE_REQUESTED;
> +               }
> +               status_ack |= AST_I2CD_INTR_RX_DONE;
> +       }
> +
> +       /* Slave was asked to stop. */
> +       if (irq_status & AST_I2CD_INTR_NORMAL_STOP ||
> +           irq_status & AST_I2CD_INTR_TX_NAK) {
> +               if (irq_status & AST_I2CD_INTR_NORMAL_STOP)
> +                       status_ack |= AST_I2CD_INTR_NORMAL_STOP;

You're testing for the same thing in both if loops. Could you
rearrange the logic?

> +               else
> +                       status_ack |= AST_I2CD_INTR_TX_NAK;
> +               bus->slave_state = AST_I2C_SLAVE_STOP;
> +       }
> +
> +       if (bus->slave_state == AST_I2C_SLAVE_READ_REQUESTED ||
> +           bus->slave_state == AST_I2C_SLAVE_READ_PROCESSED) {
> +               if (bus->slave_state == AST_I2C_SLAVE_READ_REQUESTED) {

As above.

> +                       event = I2C_SLAVE_READ_REQUESTED;
> +                       if (irq_status & AST_I2CD_INTR_TX_ACK)
> +                               dev_err(bus->dev,
> +                                       "Unexpected ACK on read request.\n");
> +               } else {
> +                       status_ack |= AST_I2CD_INTR_TX_ACK;
> +                       event = I2C_SLAVE_READ_PROCESSED;
> +                       if (!(irq_status & AST_I2CD_INTR_TX_ACK))
> +                               dev_err(bus->dev,
> +                                       "Expected ACK after processed read.\n");
> +               }
> +               bus->slave_state = AST_I2C_SLAVE_READ_PROCESSED;
> +
> +               i2c_slave_event(slave, event, &value);
> +               ast_i2c_write(bus, value, AST_I2C_BYTE_BUF_REG);
> +               ast_i2c_write(bus, AST_I2CD_S_TX_CMD, AST_I2C_CMD_REG);
> +       } else if (bus->slave_state == AST_I2C_SLAVE_WRITE_REQUESTED) {
> +               bus->slave_state = AST_I2C_SLAVE_WRITE_RECEIVED;
> +               i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value);
> +       } else if (bus->slave_state == AST_I2C_SLAVE_WRITE_RECEIVED) {
> +               i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value);
> +       } else if (bus->slave_state == AST_I2C_SLAVE_STOP) {
> +               i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
> +       }
> +
> +       if (status_ack != irq_status)
> +               dev_err(bus->dev,
> +                       "irq handled != irq. expected %x, but was %x\n",
> +                       irq_status, status_ack);
> +       ast_i2c_write(bus, status_ack, AST_I2C_INTR_STS_REG);
> +
> +out:
> +       spin_unlock(&bus->lock);
> +       return irq_handled;
> +}
> +#endif
> +
> +static bool ast_i2c_master_irq(struct ast_i2c_bus *bus)
> +{
> +       const u32 errs = AST_I2CD_INTR_ARBIT_LOSS |
> +               AST_I2CD_INTR_ABNORMAL |
> +               AST_I2CD_INTR_SCL_TIMEOUT |
> +               AST_I2CD_INTR_SDA_DL_TIMEOUT |
> +               AST_I2CD_INTR_TX_NAK;
> +       u32 irq_status;
> +
> +       spin_lock(&bus->lock);
> +       irq_status = ast_i2c_read(bus, AST_I2C_INTR_STS_REG);
> +       bus->cmd_err |= irq_status & errs;
> +
> +       dev_dbg(bus->dev, "master irq status 0x%08x\n", irq_status);
> +
> +       /* No message to transfer. */
> +       if (bus->cmd_err ||
> +           (irq_status & AST_I2CD_INTR_NORMAL_STOP) ||
> +           (irq_status & AST_I2CD_INTR_BUS_RECOVER_DONE)) {
> +               complete(&bus->cmd_complete);
> +               goto out;
> +       } else if (!bus->msg || bus->msg_pos >= bus->msg->len)
> +               goto out;
> +
> +       if ((bus->msg->flags & I2C_M_RD) &&
> +           (irq_status & AST_I2CD_INTR_RX_DONE)) {
> +               bus->msg->buf[bus->msg_pos++] =
> +                               ast_i2c_read(bus, AST_I2C_BYTE_BUF_REG) >> 8;
> +               if (bus->msg_pos + 1 < bus->msg->len)
> +                       ast_i2c_write(bus, AST_I2CD_M_RX_CMD, AST_I2C_CMD_REG);
> +               else if (bus->msg_pos < bus->msg->len)
> +                       ast_i2c_write(bus, AST_I2CD_M_RX_CMD |
> +                                     AST_I2CD_M_S_RX_CMD_LAST,
> +                                     AST_I2C_CMD_REG);
> +       } else if (!(bus->msg->flags & I2C_M_RD) &&
> +                  (irq_status & AST_I2CD_INTR_TX_ACK)) {
> +               ast_i2c_write(bus, bus->msg->buf[bus->msg_pos++],
> +                             AST_I2C_BYTE_BUF_REG);
> +               ast_i2c_write(bus, AST_I2CD_M_TX_CMD, AST_I2C_CMD_REG);
> +       }
> +
> +       /* Transmission complete: notify caller. */
> +       if (bus->msg_pos >= bus->msg->len)
> +               complete(&bus->cmd_complete);
> +out:
> +       ast_i2c_write(bus, irq_status, AST_I2C_INTR_STS_REG);
> +       spin_unlock(&bus->lock);
> +       return true;
> +}
> +
> +static irqreturn_t ast_i2c_bus_irq(int irq, void *dev_id)
> +{
> +       struct ast_i2c_bus *bus = dev_id;
> +
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +       if (ast_i2c_slave_irq(bus)) {
> +               dev_dbg(bus->dev, "irq handled by slave.\n");
> +               return IRQ_HANDLED;
> +       }
> +#endif
> +       if (ast_i2c_master_irq(bus)) {
> +               dev_dbg(bus->dev, "irq handled by master.\n");
> +               return IRQ_HANDLED;
> +       }
> +       dev_err(bus->dev, "irq not handled properly!\n");
> +       return IRQ_HANDLED;
> +}
> +
> +static int ast_i2c_master_single_xfer(struct i2c_adapter *adap,
> +                                     struct i2c_msg *msg)
> +{
> +       struct ast_i2c_bus *bus = adap->algo_data;
> +       unsigned long flags;
> +       u8 slave_addr;
> +       u32 command = AST_I2CD_M_START_CMD | AST_I2CD_M_TX_CMD;
> +       int ret = msg->len;
> +       unsigned long time_left;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       bus->msg = msg;
> +       bus->msg_pos = 0;
> +       slave_addr = msg->addr << 1;
> +       if (msg->flags & I2C_M_RD) {
> +               slave_addr |= 1;
> +               command |= AST_I2CD_M_RX_CMD;
> +       }
> +       ast_i2c_write(bus, slave_addr, AST_I2C_BYTE_BUF_REG);
> +       ast_i2c_write(bus, command, AST_I2C_CMD_REG);
> +       reinit_completion(&bus->cmd_complete);
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +       time_left = wait_for_completion_interruptible_timeout(
> +                       &bus->cmd_complete, bus->adap.timeout * HZ * msg->len);
> +       if (time_left == 0)
> +               return -ETIMEDOUT;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       if (bus->cmd_err)
> +               ret = -EIO;
> +       bus->msg = NULL;
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +       return ret;
> +}
> +
> +static int ast_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
> +                              int num)
> +{
> +       struct ast_i2c_bus *bus = adap->algo_data;
> +       int ret;
> +       int i;
> +       unsigned long flags;
> +       unsigned long time_left;
> +
> +       /* If bus is busy, attempt recovery. We assume a single master
> +        * environment.
> +        */
> +       if (ast_i2c_read(bus, AST_I2C_CMD_REG) & AST_I2CD_BUS_BUSY_STS) {
> +               ret = ast_i2c_recover_bus(bus);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       for (i = 0; i < num; i++) {
> +               ret = ast_i2c_master_single_xfer(adap, &msgs[i]);
> +               if (ret < 0)
> +                       break;
> +               /* TODO: Support other forms of I2C protocol mangling. */
> +               if (msgs[i].flags & I2C_M_STOP) {
> +                       spin_lock_irqsave(&bus->lock, flags);
> +                       ast_i2c_write(bus, AST_I2CD_M_STOP_CMD,
> +                                     AST_I2C_CMD_REG);
> +                       reinit_completion(&bus->cmd_complete);
> +                       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +                       time_left = wait_for_completion_interruptible_timeout(
> +                                       &bus->cmd_complete,
> +                                       bus->adap.timeout * HZ);
> +                       if (time_left == 0)
> +                               return -ETIMEDOUT;
> +               }
> +       }
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       ast_i2c_write(bus, AST_I2CD_M_STOP_CMD, AST_I2C_CMD_REG);
> +       reinit_completion(&bus->cmd_complete);
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +       time_left = wait_for_completion_interruptible_timeout(
> +                       &bus->cmd_complete, bus->adap.timeout * HZ);
> +       if (time_left == 0)
> +               return -ETIMEDOUT;
> +
> +       /* If nothing went wrong, return number of messages transferred. */
> +       if (ret < 0)
> +               return ret;
> +       else
> +               return i;
> +}
> +
> +static u32 ast_i2c_functionality(struct i2c_adapter *adap)
> +{
> +       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
> +}
> +
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +static int ast_i2c_reg_slave(struct i2c_client *client)
> +{
> +       struct ast_i2c_bus *bus;
> +       unsigned long flags;
> +       u32 addr_reg_val;
> +       u32 func_ctrl_reg_val;
> +
> +       bus = client->adapter->algo_data;
> +       spin_lock_irqsave(&bus->lock, flags);
> +       if (bus->slave) {
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +               return -EINVAL;
> +       }
> +
> +       /* Set slave addr. */
> +       addr_reg_val = ast_i2c_read(bus, AST_I2C_DEV_ADDR_REG);
> +       addr_reg_val &= ~AST_I2CD_DEV_ADDR_MASK;
> +       addr_reg_val |= client->addr & AST_I2CD_DEV_ADDR_MASK;
> +       ast_i2c_write(bus, addr_reg_val, AST_I2C_DEV_ADDR_REG);
> +
> +       /* Switch from master mode to slave mode. */
> +       func_ctrl_reg_val = ast_i2c_read(bus, AST_I2C_FUN_CTRL_REG);
> +       func_ctrl_reg_val &= ~AST_I2CD_MASTER_EN;
> +       func_ctrl_reg_val |= AST_I2CD_SLAVE_EN;
> +       ast_i2c_write(bus, func_ctrl_reg_val, AST_I2C_FUN_CTRL_REG);
> +
> +       bus->slave = client;
> +       bus->slave_state = AST_I2C_SLAVE_STOP;
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +       return 0;
> +}
> +
> +static int ast_i2c_unreg_slave(struct i2c_client *client)
> +{
> +       struct ast_i2c_bus *bus = client->adapter->algo_data;
> +       unsigned long flags;
> +       u32 func_ctrl_reg_val;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       if (!bus->slave) {
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +               return -EINVAL;
> +       }
> +
> +       /* Switch from slave mode to master mode. */
> +       func_ctrl_reg_val = ast_i2c_read(bus, AST_I2C_FUN_CTRL_REG);
> +       func_ctrl_reg_val &= ~AST_I2CD_SLAVE_EN;
> +       func_ctrl_reg_val |= AST_I2CD_MASTER_EN;
> +       ast_i2c_write(bus, func_ctrl_reg_val, AST_I2C_FUN_CTRL_REG);
> +
> +       bus->slave = NULL;
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +       return 0;
> +}
> +#endif
> +
> +static const struct i2c_algorithm ast_i2c_algo = {
> +       .master_xfer    = ast_i2c_master_xfer,
> +       .functionality  = ast_i2c_functionality,
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +       .reg_slave      = ast_i2c_reg_slave,
> +       .unreg_slave    = ast_i2c_unreg_slave,
> +#endif
> +};
> +
> +static u32 ast_i2c_get_clk_reg_val(u32 divider_ratio)
> +{
> +       unsigned int inc = 0, div;
> +       u32 scl_low, scl_high, data;
> +
> +       for (div = 0; divider_ratio >= 16; div++) {
> +               inc |= (divider_ratio & 1);
> +               divider_ratio >>= 1;
> +       }
> +       divider_ratio += inc;
> +       scl_low = (divider_ratio >> 1) - 1;
> +       scl_high = divider_ratio - scl_low - 2;
> +       data = 0x77700300 | (scl_high << 16) | (scl_low << 12) | div;
> +       return data;
> +}
> +
> +static int ast_i2c_init_clk(struct ast_i2c_bus *bus,
> +                           struct platform_device *pdev)
> +{
> +       struct clk *pclk;
> +       u32 clk_freq;
> +       u32 divider_ratio;
> +       int ret;
> +
> +       pclk = devm_clk_get(&pdev->dev, NULL);
> +       if (IS_ERR(pclk)) {
> +               dev_err(&pdev->dev, "clk_get failed\n");
> +               return PTR_ERR(pclk);
> +       }
> +       ret = of_property_read_u32(pdev->dev.of_node,
> +                       "clock-frequency", &clk_freq);
> +       if (ret < 0) {
> +               dev_err(&pdev->dev,
> +                               "Could not read clock-frequency property\n");
> +               clk_freq = 100000;
> +       }
> +       divider_ratio = clk_get_rate(pclk) / clk_freq;
> +       /* We just need the clock rate, we don't actually use the clk object. */
> +       devm_clk_put(&pdev->dev, pclk);
> +
> +       /* Set AC Timing */
> +       if (clk_freq / 1000 > 400) {
> +               ast_i2c_write(bus, ast_i2c_read(bus, AST_I2C_FUN_CTRL_REG) |
> +                               AST_I2CD_M_HIGH_SPEED_EN |
> +                               AST_I2CD_M_SDA_DRIVE_1T_EN |
> +                               AST_I2CD_SDA_DRIVE_1T_EN,
> +                               AST_I2C_FUN_CTRL_REG);
> +
> +               ast_i2c_write(bus, 0x3, AST_I2C_AC_TIMING_REG2);
> +               ast_i2c_write(bus, ast_i2c_get_clk_reg_val(divider_ratio),
> +                             AST_I2C_AC_TIMING_REG1);
> +       } else {
> +               ast_i2c_write(bus, ast_i2c_get_clk_reg_val(divider_ratio),
> +                             AST_I2C_AC_TIMING_REG1);
> +               ast_i2c_write(bus, AST_NO_TIMEOUT_CTRL, AST_I2C_AC_TIMING_REG2);
> +       }
> +
> +       return 0;
> +}
> +
> +static int ast_i2c_probe_bus(struct platform_device *pdev)
> +{
> +       struct ast_i2c_bus *bus;
> +       struct resource *res;
> +       int ret, bus_num;
> +
> +       bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
> +       if (!bus)
> +               return -ENOMEM;
> +
> +       ret = of_property_read_u32(pdev->dev.of_node, "bus", &bus_num);
> +       if (ret)
> +               return -ENXIO;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       bus->base = devm_ioremap_resource(&pdev->dev, res);
> +       if (IS_ERR(bus->base))
> +               return PTR_ERR(bus->base);
> +
> +       bus->irq = platform_get_irq(pdev, 0);
> +       if (bus->irq < 0) {
> +               dev_err(&pdev->dev, "platform_get_irq failed\n");
> +               return -ENXIO;
> +       }
> +
> +       ret = devm_request_irq(&pdev->dev, bus->irq, ast_i2c_bus_irq,
> +                       0, dev_name(&pdev->dev), bus);
> +       if (ret) {
> +               dev_err(&pdev->dev, "devm_request_irq failed\n");
> +               return -ENXIO;
> +       }
> +
> +       /* Initialize the I2C adapter */
> +       spin_lock_init(&bus->lock);
> +       init_completion(&bus->cmd_complete);
> +       bus->adap.nr = bus_num;
> +       bus->adap.owner = THIS_MODULE;
> +       bus->adap.retries = 0;
> +       bus->adap.timeout = 5;
> +       bus->adap.algo = &ast_i2c_algo;
> +       bus->adap.algo_data = bus;
> +       bus->adap.dev.parent = &pdev->dev;
> +       bus->adap.dev.of_node = pdev->dev.of_node;
> +       snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c-%d",
> +                       bus_num);
> +
> +       bus->dev = &pdev->dev;
> +
> +       /* reset device: disable master & slave functions */
> +       ast_i2c_write(bus, 0, AST_I2C_FUN_CTRL_REG);
> +
> +       ret = ast_i2c_init_clk(bus, pdev);
> +       if (ret < 0)
> +               return ret;
> +
> +       /* Enable Master Mode */
> +       ast_i2c_write(bus, ast_i2c_read(bus, AST_I2C_FUN_CTRL_REG) |
> +                     AST_I2CD_MASTER_EN |
> +                     AST_I2CD_MULTI_MASTER_DIS, AST_I2C_FUN_CTRL_REG);
> +
> +       /* Set interrupt generation of I2C controller */
> +       ast_i2c_write(bus, AST_I2CD_INTR_SDA_DL_TIMEOUT |
> +                       AST_I2CD_INTR_BUS_RECOVER_DONE |
> +                       AST_I2CD_INTR_SCL_TIMEOUT |
> +                       AST_I2CD_INTR_ABNORMAL |
> +                       AST_I2CD_INTR_NORMAL_STOP |
> +                       AST_I2CD_INTR_ARBIT_LOSS |
> +                       AST_I2CD_INTR_RX_DONE |
> +                       AST_I2CD_INTR_TX_NAK |
> +                       AST_I2CD_INTR_TX_ACK,
> +                       AST_I2C_INTR_CTRL_REG);
> +
> +       ret = i2c_add_numbered_adapter(&bus->adap);
> +       if (ret < 0)
> +               return -ENXIO;
> +
> +       platform_set_drvdata(pdev, bus);
> +
> +       dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
> +                       bus->adap.nr, bus->irq);
> +
> +       return 0;
> +}
> +
> +static int ast_i2c_remove_bus(struct platform_device *pdev)
> +{
> +       struct ast_i2c_bus *bus = platform_get_drvdata(pdev);
> +
> +       i2c_del_adapter(&bus->adap);
> +       return 0;
> +}
> +
> +static const struct of_device_id ast_i2c_bus_of_table[] = {
> +       { .compatible = "aspeed,ast2400-i2c-bus", },
> +       { .compatible = "aspeed,ast2500-i2c-bus", },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, ast_i2c_of_table);
> +
> +static struct platform_driver ast_i2c_bus_driver = {
> +       .probe          = ast_i2c_probe_bus,
> +       .remove         = ast_i2c_remove_bus,
> +       .driver         = {
> +               .name   = "ast-i2c-bus",
> +               .of_match_table = ast_i2c_bus_of_table,
> +       },
> +};
> +module_platform_driver(ast_i2c_bus_driver);
> +
> +static void noop(struct irq_data *data) { }
> +
> +static struct irq_chip ast_i2c_irqchip = {
> +       .name           = "ast-i2c",
> +       .irq_unmask     = noop,
> +       .irq_mask       = noop,

Not sure about this. Are we able to leave them as null?

> +};
> +
> +static void ast_i2c_controller_irq(struct irq_desc *desc)
> +{
> +       struct ast_i2c_controller *c = irq_desc_get_handler_data(desc);
> +       unsigned long p, status;
> +       unsigned int bus_irq;
> +
> +       status = readl(c->base);
> +       for_each_set_bit(p, &status, AST_I2C_NUM_BUS) {
> +               bus_irq = irq_find_mapping(c->irq_domain, p);
> +               generic_handle_irq(bus_irq);
> +       }
> +}
> +
> +static int ast_i2c_probe_controller(struct platform_device *pdev)
> +{
> +       struct ast_i2c_controller *controller;
> +       struct device_node *np;
> +       struct resource *res;
> +       int i, irq;
> +
> +       controller = kzalloc(sizeof(*controller), GFP_KERNEL);
> +       if (!controller)
> +               return -ENOMEM;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       controller->base = devm_ioremap_resource(&pdev->dev, res);
> +       if (IS_ERR(controller->base))
> +               return PTR_ERR(controller->base);
> +
> +       controller->irq = platform_get_irq(pdev, 0);
> +       if (controller->irq < 0) {
> +               dev_err(&pdev->dev, "no platform IRQ\n");
> +               return -ENXIO;
> +       }
> +
> +       controller->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
> +                       AST_I2C_NUM_BUS, &irq_domain_simple_ops, NULL);
> +       if (!controller->irq_domain) {
> +               dev_err(&pdev->dev, "no IRQ domain\n");
> +               return -ENXIO;
> +       }
> +       controller->irq_domain->name = "ast-i2c-domain";
> +
> +       for (i = 0; i < AST_I2C_NUM_BUS; i++) {

Could we just create the buses that are defined as enabled in the device tree?

> +               irq = irq_create_mapping(controller->irq_domain, i);
> +               irq_set_chip_data(irq, controller);
> +               irq_set_chip_and_handler(irq, &ast_i2c_irqchip,
> +                               handle_simple_irq);
> +       }
> +
> +       irq_set_chained_handler_and_data(controller->irq,
> +                       ast_i2c_controller_irq, controller);
> +
> +       controller->dev = &pdev->dev;
> +
> +       platform_set_drvdata(pdev, controller);
> +
> +       dev_info(controller->dev, "i2c controller registered, irq %d\n",
> +                       controller->irq);
> +
> +       for_each_child_of_node(pdev->dev.of_node, np) {
> +               int ret;
> +               u32 bus_num;
> +               char bus_id[sizeof("i2c-12345")];
> +
> +               /*
> +                * Set a useful name derived from the bus number; the device
> +                * tree should provide us with one that corresponds to the
> +                * hardware numbering.  If the property is missing the
> +                * probe would fail so just skip it here.
> +                */
> +
> +               ret = of_property_read_u32(np, "bus", &bus_num);
> +               if (ret)
> +                       continue;
> +
> +               ret = snprintf(bus_id, sizeof(bus_id), "i2c-%u", bus_num);
> +               if (ret >= sizeof(bus_id))
> +                       continue;
> +
> +               of_platform_device_create(np, bus_id, &pdev->dev);
> +               of_node_put(np);
> +       }
> +
> +       return 0;
> +}
> +
> +static int ast_i2c_remove_controller(struct platform_device *pdev)
> +{
> +       struct ast_i2c_controller *controller = platform_get_drvdata(pdev);
> +
> +       irq_domain_remove(controller->irq_domain);
> +       return 0;
> +}
> +
> +static const struct of_device_id ast_i2c_controller_of_table[] = {
> +       { .compatible = "aspeed,ast2400-i2c-controller", },
> +       { .compatible = "aspeed,ast2500-i2c-controller", },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, ast_i2c_of_table);
> +
> +static struct platform_driver ast_i2c_controller_driver = {
> +       .probe          = ast_i2c_probe_controller,
> +       .remove         = ast_i2c_remove_controller,
> +       .driver         = {
> +               .name   = "ast-i2c-controller",
> +               .of_match_table = ast_i2c_controller_of_table,
> +       },
> +};
> +
> +module_platform_driver(ast_i2c_controller_driver);
> +
> +MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>");

Drop this. We've rewritten it since we took Ryan's code from the BSP.

> +MODULE_AUTHOR("Brendan Higgins <brendanhiggins@google.com>");

Not sure about this? Keep it if you feel comfortable answering
questions about the design of the entire driver.

> +MODULE_DESCRIPTION("ASPEED AST I2C Bus Driver");

Aspeed I2C Bus driver?

> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:ast_i2c");

Not sure that we need this.

> --
> 2.8.0.rc3.226.g39d4020
>
> _______________________________________________
> openbmc mailing list
> openbmc@lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/openbmc

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 1/2] i2c: aspeed: added driver for Aspeed I2C
  2016-09-07 23:54       ` Joel Stanley
@ 2016-09-09  1:24         ` Brendan Higgins
  2016-09-09  1:24           ` Brendan Higgins
  0 siblings, 1 reply; 21+ messages in thread
From: Brendan Higgins @ 2016-09-09  1:24 UTC (permalink / raw)
  To: openbmc

> Can you update this to 2016? And add your own?

Done.

> I would prefer using the ASPEED_ prefix for all #defines and functions
> for consistency with other kernel code.

Done.

> Typo: Global

Done.

> If you want to convert these to use the BIT(1), etc notation.

Done.

> Can we get away without the guard here?

Done.

> s/sts/status/

Actually this is the command register, but I fixed it.

> Would goto out, with out being just above the _ireqrestore at the
> bottom of this function, make it clearer that we are doing the correct
> lock/unlocking?

I was on the fence about this, so your comment gave me reason enough to fix it.

> I assume we are releasing the lock to wait for the completion?

Yep, I do not think a comment is appropriate here: it is pretty clear that the
lock is will be held by the IRQ handler.

> Above we call it "sts", down here we call it "irq_status". I suggest a
> consistent name.

I renamed the above to "command", so this is fixed.

> What is "value"? Perhaps "request"?

So this is actually the value that gets passed in/out of the slave callback; it
is just an arbitrary byte. I agree that the name is not *great*, but I could not
think of a better one (could be an in or out). This does follow the convention
set by Wolfram in his i2c-slave-eeprom driver which is the example driver for
the slave calback framework (although he used val, I like value better than
val).

> You're testing for the same thing in both if loops. Could you
> rearrange the logic?

Done.

> As above.

Done.

> Not sure about this. Are we able to leave them as null?

Nope. I tried it.

> Could we just create the buses that are defined as enabled in the device tree?

Done.

> Drop this. We've rewritten it since we took Ryan's code from the BSP.

Done.

> Not sure about this? Keep it if you feel comfortable answering
> questions about the design of the entire driver.

I don't mind. I expect to clean up after my own mistakes.

> Aspeed I2C Bus driver?

Done.

> Not sure that we need this.

Done.

--------------------------------------------------------------------------------

Changes:
  - Addressed comments above.
  - Found and fixed bug in error reporting (did not clear .cmd_err).
  - Found and fixed bug in unreg_slave (did not clean up after self properly).

Cheers

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 1/2] i2c: aspeed: added driver for Aspeed I2C
  2016-09-09  1:24         ` [PATCH v2 1/2] " Brendan Higgins
@ 2016-09-09  1:24           ` Brendan Higgins
  2016-09-09  2:57             ` Joel Stanley
  0 siblings, 1 reply; 21+ messages in thread
From: Brendan Higgins @ 2016-09-09  1:24 UTC (permalink / raw)
  To: openbmc; +Cc: Brendan Higgins

Added initial master and slave support for Aspeed I2C controller.
Supports fourteen busses present in ast24xx and ast25xx BMC SoCs by
Aspeed.

Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
---
 drivers/i2c/busses/Kconfig      |  10 +
 drivers/i2c/busses/Makefile     |   1 +
 drivers/i2c/busses/i2c-aspeed.c | 816 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 827 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-aspeed.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 5c3993b..0178c6c 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -998,6 +998,16 @@ config I2C_RCAR
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-rcar.
 
+config I2C_ASPEED
+	tristate "Aspeed AST2xxx SoC I2C Controller"
+	depends on (ARCH_ASPEED || COMPILE_TEST) && OF
+	help
+	  If you say yes to this option, support will be included for the
+	  Aspeed AST2xxx SoC I2C controller.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-aspeed.
+
 comment "External I2C/SMBus adapter drivers"
 
 config I2C_DIOLAN_U2C
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 37f2819..49631cd 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_I2C_XILINX)	+= i2c-xiic.o
 obj-$(CONFIG_I2C_XLR)		+= i2c-xlr.o
 obj-$(CONFIG_I2C_XLP9XX)	+= i2c-xlp9xx.o
 obj-$(CONFIG_I2C_RCAR)		+= i2c-rcar.o
+obj-$(CONFIG_I2C_ASPEED)	+= i2c-aspeed.o
 
 # External I2C/SMBus adapter drivers
 obj-$(CONFIG_I2C_DIOLAN_U2C)	+= i2c-diolan-u2c.o
diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
new file mode 100644
index 0000000..9a5ca04
--- /dev/null
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -0,0 +1,816 @@
+/*
+ *  I2C adapter for the ASPEED I2C bus.
+ *
+ *  Copyright (C) 2012-2020  ASPEED Technology Inc.
+ *  Copyright 2016 IBM Corporation
+ *  Copyright 2016 Google, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+
+/* I2C Register */
+#define ASPEED_I2C_FUN_CTRL_REG				0x00
+#define ASPEED_I2C_AC_TIMING_REG1			0x04
+#define ASPEED_I2C_AC_TIMING_REG2			0x08
+#define ASPEED_I2C_INTR_CTRL_REG			0x0c
+#define ASPEED_I2C_INTR_STS_REG				0x10
+#define ASPEED_I2C_CMD_REG				0x14
+#define ASPEED_I2C_DEV_ADDR_REG				0x18
+#define ASPEED_I2C_BYTE_BUF_REG				0x20
+
+#define ASPEED_I2C_NUM_BUS 14
+
+/* Global Register Definition */
+/* 0x00 : I2C Interrupt Status Register  */
+/* 0x08 : I2C Interrupt Target Assignment  */
+
+/* Device Register Definition */
+/* 0x00 : I2CD Function Control Register  */
+#define ASPEED_I2CD_MULTI_MASTER_DIS			BIT(15)
+#define ASPEED_I2CD_SDA_DRIVE_1T_EN			BIT(8)
+#define ASPEED_I2CD_M_SDA_DRIVE_1T_EN			BIT(7)
+#define ASPEED_I2CD_M_HIGH_SPEED_EN			BIT(6)
+#define ASPEED_I2CD_SLAVE_EN				BIT(1)
+#define ASPEED_I2CD_MASTER_EN				BIT(0)
+
+/* 0x08 : I2CD Clock and AC Timing Control Register #2 */
+#define ASPEED_NO_TIMEOUT_CTRL				0
+
+
+/* 0x0c : I2CD Interrupt Control Register &
+ * 0x10 : I2CD Interrupt Status Register
+ *
+ * These share bit definitions, so use the same values for the enable &
+ * status bits.
+ */
+#define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT			BIT(14)
+#define ASPEED_I2CD_INTR_BUS_RECOVER_DONE		BIT(13)
+#define ASPEED_I2CD_INTR_SLAVE_MATCH			BIT(7)
+#define ASPEED_I2CD_INTR_SCL_TIMEOUT			BIT(6)
+#define ASPEED_I2CD_INTR_ABNORMAL			BIT(5)
+#define ASPEED_I2CD_INTR_NORMAL_STOP			BIT(4)
+#define ASPEED_I2CD_INTR_ARBIT_LOSS			BIT(3)
+#define ASPEED_I2CD_INTR_RX_DONE			BIT(2)
+#define ASPEED_I2CD_INTR_TX_NAK				BIT(1)
+#define ASPEED_I2CD_INTR_TX_ACK				BIT(0)
+
+/* 0x14 : I2CD Command/Status Register   */
+#define ASPEED_I2CD_SCL_LINE_STS			BIT(18)
+#define ASPEED_I2CD_SDA_LINE_STS			BIT(17)
+#define ASPEED_I2CD_BUS_BUSY_STS			BIT(16)
+#define ASPEED_I2CD_BUS_RECOVER_CMD			BIT(11)
+
+/* Command Bit */
+#define ASPEED_I2CD_M_STOP_CMD				BIT(5)
+#define ASPEED_I2CD_M_S_RX_CMD_LAST			BIT(4)
+#define ASPEED_I2CD_M_RX_CMD				BIT(3)
+#define ASPEED_I2CD_S_TX_CMD				BIT(2)
+#define ASPEED_I2CD_M_TX_CMD				BIT(1)
+#define ASPEED_I2CD_M_START_CMD				BIT(0)
+
+/* 0x18 : I2CD Slave Device Address Register   */
+#define ASPEED_I2CD_DEV_ADDR_MASK			GENMASK(6, 0)
+
+enum aspeed_i2c_slave_state {
+	ASPEED_I2C_SLAVE_START,
+	ASPEED_I2C_SLAVE_READ_REQUESTED,
+	ASPEED_I2C_SLAVE_READ_PROCESSED,
+	ASPEED_I2C_SLAVE_WRITE_REQUESTED,
+	ASPEED_I2C_SLAVE_WRITE_RECEIVED,
+	ASPEED_I2C_SLAVE_STOP,
+};
+
+struct aspeed_i2c_bus {
+	struct i2c_adapter		adap;
+	struct device			*dev;
+	void __iomem			*base;
+	spinlock_t			lock;
+	struct completion		cmd_complete;
+	int				irq;
+	/* Transaction state. */
+	struct i2c_msg			*msg;
+	int				msg_pos;
+	u32				cmd_err;
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	struct i2c_client		*slave;
+	enum aspeed_i2c_slave_state	slave_state;
+#endif
+};
+
+struct aspeed_i2c_controller {
+	struct device		*dev;
+	void __iomem		*base;
+	int			irq;
+	struct irq_domain	*irq_domain;
+};
+
+static inline void aspeed_i2c_write(struct aspeed_i2c_bus *bus, u32 val,
+				    u32 reg)
+{
+	writel(val, bus->base + reg);
+}
+
+static inline u32 aspeed_i2c_read(struct aspeed_i2c_bus *bus, u32 reg)
+{
+	return readl(bus->base + reg);
+}
+
+static u8 aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
+{
+	u32 command;
+	unsigned long time_left;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
+	/* Bus is idle: no recovery needed. */
+	if ((command & ASPEED_I2CD_SDA_LINE_STS) &&
+	    (command & ASPEED_I2CD_SCL_LINE_STS))
+		goto out;
+
+	dev_dbg(bus->dev, "bus hung (state %x), attempting recovery\n",
+		command);
+
+	/* Bus held: put bus in stop state. */
+	if ((command & ASPEED_I2CD_SDA_LINE_STS) &&
+	    !(command & ASPEED_I2CD_SCL_LINE_STS)) {
+		aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
+				 ASPEED_I2C_CMD_REG);
+		reinit_completion(&bus->cmd_complete);
+		spin_unlock_irqrestore(&bus->lock, flags);
+
+		time_left = wait_for_completion_interruptible_timeout(
+				&bus->cmd_complete, bus->adap.timeout * HZ);
+
+		spin_lock_irqsave(&bus->lock, flags);
+		if (time_left == 0)
+			ret = -ETIMEDOUT;
+		else if (bus->cmd_err)
+			ret = -EIO;
+	/* Bus error. */
+	} else if (!(command & ASPEED_I2CD_SDA_LINE_STS)) {
+		aspeed_i2c_write(bus, ASPEED_I2CD_BUS_RECOVER_CMD,
+				 ASPEED_I2C_CMD_REG);
+		reinit_completion(&bus->cmd_complete);
+		spin_unlock_irqrestore(&bus->lock, flags);
+
+		time_left = wait_for_completion_interruptible_timeout(
+				&bus->cmd_complete, bus->adap.timeout * HZ);
+
+		spin_lock_irqsave(&bus->lock, flags);
+		if (time_left == 0)
+			ret = -ETIMEDOUT;
+		else if (bus->cmd_err)
+			ret = -EIO;
+		/* Recovery failed. */
+		else if (!(aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
+			   ASPEED_I2CD_SDA_LINE_STS))
+			ret = -EIO;
+	}
+
+out:
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return ret;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static bool aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus)
+{
+	bool irq_handled = true;
+	u32 command;
+	u32 irq_status;
+	u32 status_ack = 0;
+	u8 value;
+	struct i2c_client *slave = bus->slave;
+
+	spin_lock(&bus->lock);
+	if (!slave) {
+		irq_handled = false;
+		goto out;
+	}
+	command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
+	irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
+
+	/* Slave was requested, restart state machine. */
+	if (irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH) {
+		status_ack |= ASPEED_I2CD_INTR_SLAVE_MATCH;
+		bus->slave_state = ASPEED_I2C_SLAVE_START;
+	}
+	/* Slave is not currently active, irq was for someone else. */
+	if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
+		irq_handled = false;
+		goto out;
+	}
+
+	dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
+		irq_status, command);
+
+	/* Slave was sent something. */
+	if (irq_status & ASPEED_I2CD_INTR_RX_DONE) {
+		value = aspeed_i2c_read(bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
+		/* Handle address frame. */
+		if (bus->slave_state == ASPEED_I2C_SLAVE_START) {
+			if (value & 0x1)
+				bus->slave_state =
+						ASPEED_I2C_SLAVE_READ_REQUESTED;
+			else
+				bus->slave_state =
+						ASPEED_I2C_SLAVE_WRITE_REQUESTED;
+		}
+		status_ack |= ASPEED_I2CD_INTR_RX_DONE;
+	}
+
+	/* Slave was asked to stop. */
+	if (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) {
+		status_ack |= ASPEED_I2CD_INTR_NORMAL_STOP;
+		bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+	}
+	if (irq_status & ASPEED_I2CD_INTR_TX_NAK) {
+		status_ack |= ASPEED_I2CD_INTR_TX_NAK;
+		bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+	}
+
+	if (bus->slave_state == ASPEED_I2C_SLAVE_READ_REQUESTED) {
+		if (irq_status & ASPEED_I2CD_INTR_TX_ACK)
+			dev_err(bus->dev, "Unexpected ACK on read request.\n");
+		bus->slave_state = ASPEED_I2C_SLAVE_READ_PROCESSED;
+
+		i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value);
+		aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
+		aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_READ_PROCESSED) {
+		status_ack |= ASPEED_I2CD_INTR_TX_ACK;
+		if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK))
+			dev_err(bus->dev,
+				"Expected ACK after processed read.\n");
+		i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value);
+		aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
+		aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_REQUESTED) {
+		bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED;
+		i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED) {
+		i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
+		i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
+	}
+
+	if (status_ack != irq_status)
+		dev_err(bus->dev,
+			"irq handled != irq. expected %x, but was %x\n",
+			irq_status, status_ack);
+	aspeed_i2c_write(bus, status_ack, ASPEED_I2C_INTR_STS_REG);
+
+out:
+	spin_unlock(&bus->lock);
+	return irq_handled;
+}
+#endif
+
+static bool aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus)
+{
+	const u32 errs = ASPEED_I2CD_INTR_ARBIT_LOSS |
+		ASPEED_I2CD_INTR_ABNORMAL |
+		ASPEED_I2CD_INTR_SCL_TIMEOUT |
+		ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |
+		ASPEED_I2CD_INTR_TX_NAK;
+	u32 irq_status;
+
+	spin_lock(&bus->lock);
+	irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
+	bus->cmd_err = irq_status & errs;
+
+	dev_dbg(bus->dev, "master irq status 0x%08x\n", irq_status);
+
+	/* No message to transfer. */
+	if (bus->cmd_err ||
+	    (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) ||
+	    (irq_status & ASPEED_I2CD_INTR_BUS_RECOVER_DONE)) {
+		complete(&bus->cmd_complete);
+		goto out;
+	} else if (!bus->msg || bus->msg_pos >= bus->msg->len)
+		goto out;
+
+	if ((bus->msg->flags & I2C_M_RD) &&
+	    (irq_status & ASPEED_I2CD_INTR_RX_DONE)) {
+		bus->msg->buf[bus->msg_pos++] = aspeed_i2c_read(
+				bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
+		if (bus->msg_pos + 1 < bus->msg->len)
+			aspeed_i2c_write(bus, ASPEED_I2CD_M_RX_CMD,
+					 ASPEED_I2C_CMD_REG);
+		else if (bus->msg_pos < bus->msg->len)
+			aspeed_i2c_write(bus, ASPEED_I2CD_M_RX_CMD |
+				      ASPEED_I2CD_M_S_RX_CMD_LAST,
+				      ASPEED_I2C_CMD_REG);
+	} else if (!(bus->msg->flags & I2C_M_RD) &&
+		   (irq_status & ASPEED_I2CD_INTR_TX_ACK)) {
+		aspeed_i2c_write(bus, bus->msg->buf[bus->msg_pos++],
+			      ASPEED_I2C_BYTE_BUF_REG);
+		aspeed_i2c_write(bus, ASPEED_I2CD_M_TX_CMD, ASPEED_I2C_CMD_REG);
+	}
+
+	/* Transmission complete: notify caller. */
+	if (bus->msg_pos >= bus->msg->len)
+		complete(&bus->cmd_complete);
+out:
+	aspeed_i2c_write(bus, irq_status, ASPEED_I2C_INTR_STS_REG);
+	spin_unlock(&bus->lock);
+	return true;
+}
+
+static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id)
+{
+	struct aspeed_i2c_bus *bus = dev_id;
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	if (aspeed_i2c_slave_irq(bus)) {
+		dev_dbg(bus->dev, "irq handled by slave.\n");
+		return IRQ_HANDLED;
+	}
+#endif
+	if (aspeed_i2c_master_irq(bus)) {
+		dev_dbg(bus->dev, "irq handled by master.\n");
+		return IRQ_HANDLED;
+	}
+	dev_err(bus->dev, "irq not handled properly!\n");
+	return IRQ_HANDLED;
+}
+
+static int aspeed_i2c_master_single_xfer(struct i2c_adapter *adap,
+				      struct i2c_msg *msg)
+{
+	struct aspeed_i2c_bus *bus = adap->algo_data;
+	unsigned long flags;
+	u8 slave_addr;
+	u32 command = ASPEED_I2CD_M_START_CMD | ASPEED_I2CD_M_TX_CMD;
+	int ret = msg->len;
+	unsigned long time_left;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	bus->msg = msg;
+	bus->msg_pos = 0;
+	slave_addr = msg->addr << 1;
+	if (msg->flags & I2C_M_RD) {
+		slave_addr |= 1;
+		command |= ASPEED_I2CD_M_RX_CMD;
+		if (msg->len == 1)
+			command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+	}
+	aspeed_i2c_write(bus, slave_addr, ASPEED_I2C_BYTE_BUF_REG);
+	aspeed_i2c_write(bus, command, ASPEED_I2C_CMD_REG);
+	reinit_completion(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	time_left = wait_for_completion_interruptible_timeout(
+			&bus->cmd_complete, bus->adap.timeout * HZ * msg->len);
+	if (time_left == 0)
+		return -ETIMEDOUT;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	if (bus->cmd_err)
+		ret = -EIO;
+	bus->msg = NULL;
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	return ret;
+}
+
+static int aspeed_i2c_master_xfer(struct i2c_adapter *adap,
+				  struct i2c_msg *msgs, int num)
+{
+	struct aspeed_i2c_bus *bus = adap->algo_data;
+	int ret;
+	int i;
+	unsigned long flags;
+	unsigned long time_left;
+
+	/* If bus is busy, attempt recovery. We assume a single master
+	 * environment.
+	 */
+	if (aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
+	    ASPEED_I2CD_BUS_BUSY_STS) {
+		ret = aspeed_i2c_recover_bus(bus);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < num; i++) {
+		ret = aspeed_i2c_master_single_xfer(adap, &msgs[i]);
+		if (ret < 0)
+			break;
+		/* TODO: Support other forms of I2C protocol mangling. */
+		if (msgs[i].flags & I2C_M_STOP) {
+			spin_lock_irqsave(&bus->lock, flags);
+			aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
+				      ASPEED_I2C_CMD_REG);
+			reinit_completion(&bus->cmd_complete);
+			spin_unlock_irqrestore(&bus->lock, flags);
+
+			time_left = wait_for_completion_interruptible_timeout(
+					&bus->cmd_complete,
+					bus->adap.timeout * HZ);
+			if (time_left == 0)
+				return -ETIMEDOUT;
+		}
+	}
+
+	spin_lock_irqsave(&bus->lock, flags);
+	aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD, ASPEED_I2C_CMD_REG);
+	reinit_completion(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	time_left = wait_for_completion_interruptible_timeout(
+			&bus->cmd_complete, bus->adap.timeout * HZ);
+	if (time_left == 0)
+		return -ETIMEDOUT;
+
+	/* If nothing went wrong, return number of messages transferred. */
+	if (ret < 0)
+		return ret;
+	else
+		return i;
+}
+
+static u32 aspeed_i2c_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static int aspeed_i2c_reg_slave(struct i2c_client *client)
+{
+	struct aspeed_i2c_bus *bus;
+	unsigned long flags;
+	u32 addr_reg_val;
+	u32 func_ctrl_reg_val;
+
+	bus = client->adapter->algo_data;
+	spin_lock_irqsave(&bus->lock, flags);
+	if (bus->slave) {
+		spin_unlock_irqrestore(&bus->lock, flags);
+		return -EINVAL;
+	}
+
+	/* Set slave addr. */
+	addr_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_DEV_ADDR_REG);
+	addr_reg_val &= ~ASPEED_I2CD_DEV_ADDR_MASK;
+	addr_reg_val |= client->addr & ASPEED_I2CD_DEV_ADDR_MASK;
+	aspeed_i2c_write(bus, addr_reg_val, ASPEED_I2C_DEV_ADDR_REG);
+
+	/* Switch from master mode to slave mode. */
+	func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
+	func_ctrl_reg_val &= ~ASPEED_I2CD_MASTER_EN;
+	func_ctrl_reg_val |= ASPEED_I2CD_SLAVE_EN;
+	aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
+
+	bus->slave = client;
+	bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return 0;
+}
+
+static int aspeed_i2c_unreg_slave(struct i2c_client *client)
+{
+	struct aspeed_i2c_bus *bus = client->adapter->algo_data;
+	unsigned long flags;
+	u32 func_ctrl_reg_val;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	if (!bus->slave) {
+		spin_unlock_irqrestore(&bus->lock, flags);
+		return -EINVAL;
+	}
+
+	/* Switch from slave mode to master mode. */
+	func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
+	func_ctrl_reg_val &= ~ASPEED_I2CD_SLAVE_EN;
+	func_ctrl_reg_val |= ASPEED_I2CD_MASTER_EN;
+	aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
+
+	bus->slave = NULL;
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return 0;
+}
+#endif
+
+static const struct i2c_algorithm aspeed_i2c_algo = {
+	.master_xfer	= aspeed_i2c_master_xfer,
+	.functionality	= aspeed_i2c_functionality,
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	.reg_slave	= aspeed_i2c_reg_slave,
+	.unreg_slave	= aspeed_i2c_unreg_slave,
+#endif
+};
+
+static u32 aspeed_i2c_get_clk_reg_val(u32 divider_ratio)
+{
+	unsigned int inc = 0, div;
+	u32 scl_low, scl_high, data;
+
+	for (div = 0; divider_ratio >= 16; div++) {
+		inc |= (divider_ratio & 1);
+		divider_ratio >>= 1;
+	}
+	divider_ratio += inc;
+	scl_low = (divider_ratio >> 1) - 1;
+	scl_high = divider_ratio - scl_low - 2;
+	data = 0x77700300 | (scl_high << 16) | (scl_low << 12) | div;
+	return data;
+}
+
+static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus,
+			    struct platform_device *pdev)
+{
+	struct clk *pclk;
+	u32 clk_freq;
+	u32 divider_ratio;
+	int ret;
+
+	pclk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(pclk)) {
+		dev_err(&pdev->dev, "clk_get failed\n");
+		return PTR_ERR(pclk);
+	}
+	ret = of_property_read_u32(pdev->dev.of_node,
+			"clock-frequency", &clk_freq);
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+				"Could not read clock-frequency property\n");
+		clk_freq = 100000;
+	}
+	divider_ratio = clk_get_rate(pclk) / clk_freq;
+	/* We just need the clock rate, we don't actually use the clk object. */
+	devm_clk_put(&pdev->dev, pclk);
+
+	/* Set AC Timing */
+	if (clk_freq / 1000 > 400) {
+		aspeed_i2c_write(bus, aspeed_i2c_read(bus,
+						      ASPEED_I2C_FUN_CTRL_REG) |
+				ASPEED_I2CD_M_HIGH_SPEED_EN |
+				ASPEED_I2CD_M_SDA_DRIVE_1T_EN |
+				ASPEED_I2CD_SDA_DRIVE_1T_EN,
+				ASPEED_I2C_FUN_CTRL_REG);
+
+		aspeed_i2c_write(bus, 0x3, ASPEED_I2C_AC_TIMING_REG2);
+		aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divider_ratio),
+			      ASPEED_I2C_AC_TIMING_REG1);
+	} else {
+		aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divider_ratio),
+			      ASPEED_I2C_AC_TIMING_REG1);
+		aspeed_i2c_write(bus, ASPEED_NO_TIMEOUT_CTRL,
+				 ASPEED_I2C_AC_TIMING_REG2);
+	}
+
+	return 0;
+}
+
+static void noop(struct irq_data *data) { }
+
+static struct irq_chip aspeed_i2c_irqchip = {
+	.name		= "ast-i2c",
+	.irq_unmask	= noop,
+	.irq_mask	= noop,
+};
+
+static int aspeed_i2c_probe_bus(struct platform_device *pdev)
+{
+	struct aspeed_i2c_bus *bus;
+	struct aspeed_i2c_controller *controller =
+			dev_get_drvdata(pdev->dev.parent);
+	struct resource *res;
+	int ret, bus_num, irq;
+
+	bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
+	if (!bus)
+		return -ENOMEM;
+
+	ret = of_property_read_u32(pdev->dev.of_node, "bus", &bus_num);
+	if (ret)
+		return -ENXIO;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	bus->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(bus->base))
+		return PTR_ERR(bus->base);
+
+	bus->irq = platform_get_irq(pdev, 0);
+	if (bus->irq < 0) {
+		dev_err(&pdev->dev, "platform_get_irq failed\n");
+		return -ENXIO;
+	}
+
+	irq = irq_create_mapping(controller->irq_domain, bus_num);
+	irq_set_chip_data(irq, controller);
+	irq_set_chip_and_handler(irq, &aspeed_i2c_irqchip, handle_simple_irq);
+	ret = devm_request_irq(&pdev->dev, bus->irq, aspeed_i2c_bus_irq,
+			0, dev_name(&pdev->dev), bus);
+	if (ret) {
+		dev_err(&pdev->dev, "devm_request_irq failed\n");
+		return -ENXIO;
+	}
+
+	/* Initialize the I2C adapter */
+	spin_lock_init(&bus->lock);
+	init_completion(&bus->cmd_complete);
+	bus->adap.nr = bus_num;
+	bus->adap.owner = THIS_MODULE;
+	bus->adap.retries = 0;
+	bus->adap.timeout = 5;
+	bus->adap.algo = &aspeed_i2c_algo;
+	bus->adap.algo_data = bus;
+	bus->adap.dev.parent = &pdev->dev;
+	bus->adap.dev.of_node = pdev->dev.of_node;
+	snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c-%d",
+			bus_num);
+
+	bus->dev = &pdev->dev;
+
+	/* reset device: disable master & slave functions */
+	aspeed_i2c_write(bus, 0, ASPEED_I2C_FUN_CTRL_REG);
+
+	ret = aspeed_i2c_init_clk(bus, pdev);
+	if (ret < 0)
+		return ret;
+
+	/* Enable Master Mode */
+	aspeed_i2c_write(bus, aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG) |
+		      ASPEED_I2CD_MASTER_EN |
+		      ASPEED_I2CD_MULTI_MASTER_DIS, ASPEED_I2C_FUN_CTRL_REG);
+
+	/* Set interrupt generation of I2C controller */
+	aspeed_i2c_write(bus, ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |
+			ASPEED_I2CD_INTR_BUS_RECOVER_DONE |
+			ASPEED_I2CD_INTR_SCL_TIMEOUT |
+			ASPEED_I2CD_INTR_ABNORMAL |
+			ASPEED_I2CD_INTR_NORMAL_STOP |
+			ASPEED_I2CD_INTR_ARBIT_LOSS |
+			ASPEED_I2CD_INTR_RX_DONE |
+			ASPEED_I2CD_INTR_TX_NAK |
+			ASPEED_I2CD_INTR_TX_ACK,
+			ASPEED_I2C_INTR_CTRL_REG);
+
+	ret = i2c_add_numbered_adapter(&bus->adap);
+	if (ret < 0)
+		return -ENXIO;
+
+	platform_set_drvdata(pdev, bus);
+
+	dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
+			bus->adap.nr, bus->irq);
+
+	return 0;
+}
+
+static int aspeed_i2c_remove_bus(struct platform_device *pdev)
+{
+	struct aspeed_i2c_bus *bus = platform_get_drvdata(pdev);
+
+	i2c_del_adapter(&bus->adap);
+	return 0;
+}
+
+static const struct of_device_id aspeed_i2c_bus_of_table[] = {
+	{ .compatible = "aspeed,ast2400-i2c-bus", },
+	{ .compatible = "aspeed,ast2500-i2c-bus", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, aspeed_i2c_of_table);
+
+static struct platform_driver aspeed_i2c_bus_driver = {
+	.probe		= aspeed_i2c_probe_bus,
+	.remove		= aspeed_i2c_remove_bus,
+	.driver         = {
+		.name   = "ast-i2c-bus",
+		.of_match_table = aspeed_i2c_bus_of_table,
+	},
+};
+module_platform_driver(aspeed_i2c_bus_driver);
+
+static void aspeed_i2c_controller_irq(struct irq_desc *desc)
+{
+	struct aspeed_i2c_controller *c = irq_desc_get_handler_data(desc);
+	unsigned long p, status;
+	unsigned int bus_irq;
+
+	status = readl(c->base);
+	for_each_set_bit(p, &status, ASPEED_I2C_NUM_BUS) {
+		bus_irq = irq_find_mapping(c->irq_domain, p);
+		generic_handle_irq(bus_irq);
+	}
+}
+
+static int aspeed_i2c_probe_controller(struct platform_device *pdev)
+{
+	struct aspeed_i2c_controller *controller;
+	struct device_node *np;
+	struct resource *res;
+
+	controller = kzalloc(sizeof(*controller), GFP_KERNEL);
+	if (!controller)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	controller->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(controller->base))
+		return PTR_ERR(controller->base);
+
+	controller->irq = platform_get_irq(pdev, 0);
+	if (controller->irq < 0) {
+		dev_err(&pdev->dev, "no platform IRQ\n");
+		return -ENXIO;
+	}
+
+	controller->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
+			ASPEED_I2C_NUM_BUS, &irq_domain_simple_ops, NULL);
+	if (!controller->irq_domain) {
+		dev_err(&pdev->dev, "no IRQ domain\n");
+		return -ENXIO;
+	}
+	controller->irq_domain->name = "ast-i2c-domain";
+
+	irq_set_chained_handler_and_data(controller->irq,
+			aspeed_i2c_controller_irq, controller);
+
+	controller->dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, controller);
+
+	dev_info(controller->dev, "i2c controller registered, irq %d\n",
+			controller->irq);
+
+	for_each_child_of_node(pdev->dev.of_node, np) {
+		int ret;
+		u32 bus_num;
+		char bus_id[sizeof("i2c-12345")];
+
+		/*
+		 * Set a useful name derived from the bus number; the device
+		 * tree should provide us with one that corresponds to the
+		 * hardware numbering.  If the property is missing the
+		 * probe would fail so just skip it here.
+		 */
+
+		ret = of_property_read_u32(np, "bus", &bus_num);
+		if (ret)
+			continue;
+
+		ret = snprintf(bus_id, sizeof(bus_id), "i2c-%u", bus_num);
+		if (ret >= sizeof(bus_id))
+			continue;
+
+		of_platform_device_create(np, bus_id, &pdev->dev);
+		of_node_put(np);
+	}
+
+	return 0;
+}
+
+static int aspeed_i2c_remove_controller(struct platform_device *pdev)
+{
+	struct aspeed_i2c_controller *controller = platform_get_drvdata(pdev);
+
+	irq_domain_remove(controller->irq_domain);
+	return 0;
+}
+
+static const struct of_device_id aspeed_i2c_controller_of_table[] = {
+	{ .compatible = "aspeed,ast2400-i2c-controller", },
+	{ .compatible = "aspeed,ast2500-i2c-controller", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, aspeed_i2c_of_table);
+
+static struct platform_driver aspeed_i2c_controller_driver = {
+	.probe		= aspeed_i2c_probe_controller,
+	.remove		= aspeed_i2c_remove_controller,
+	.driver         = {
+		.name   = "ast-i2c-controller",
+		.of_match_table = aspeed_i2c_controller_of_table,
+	},
+};
+
+module_platform_driver(aspeed_i2c_controller_driver);
+
+MODULE_AUTHOR("Brendan Higgins <brendanhiggins@google.com>");
+MODULE_DESCRIPTION("Aspeed I2C Bus Driver");
+MODULE_LICENSE("GPL");
-- 
2.8.0.rc3.226.g39d4020

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 1/2] i2c: aspeed: added driver for Aspeed I2C
  2016-09-09  1:24           ` Brendan Higgins
@ 2016-09-09  2:57             ` Joel Stanley
  0 siblings, 0 replies; 21+ messages in thread
From: Joel Stanley @ 2016-09-09  2:57 UTC (permalink / raw)
  To: Brendan Higgins; +Cc: OpenBMC Maillist

On Fri, Sep 9, 2016 at 10:54 AM, Brendan Higgins
<brendanhiggins@google.com> wrote:
> Added initial master and slave support for Aspeed I2C controller.
> Supports fourteen busses present in ast24xx and ast25xx BMC SoCs by
> Aspeed.

Looking good! Would you like to submit this to the i2c list now?

Please cc me and jk@ozlabs.org as well as the people suggested by
scripts/get_maintainer.pl.

Cheers,

Joel


>
> Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
> ---
>  drivers/i2c/busses/Kconfig      |  10 +
>  drivers/i2c/busses/Makefile     |   1 +
>  drivers/i2c/busses/i2c-aspeed.c | 816 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 827 insertions(+)
>  create mode 100644 drivers/i2c/busses/i2c-aspeed.c
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 1/2] i2c: aspeed: added driver for Aspeed I2C
  2016-09-10  1:55   ` Brendan Higgins
@ 2016-09-19 17:56     ` Brendan Higgins
  -1 siblings, 0 replies; 21+ messages in thread
From: Brendan Higgins @ 2016-09-19 17:56 UTC (permalink / raw)
  To: Wolfram Sang, robh+dt, mark.rutland
  Cc: linux-i2c, devicetree, linux-kernel, OpenBMC Maillist,
	Joel Stanley, Jeremy Kerr, Brendan Higgins

Gentle ping

On Fri, Sep 9, 2016 at 6:55 PM, Brendan Higgins
<brendanhiggins@google.com> wrote:
> Added initial master and slave support for Aspeed I2C controller.
> Supports fourteen busses present in ast24xx and ast25xx BMC SoCs by
> Aspeed.
>
> Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
> ---
> Changes for v2:
>   - Added single module_init (multiple was breaking some builds).
> ---
>  drivers/i2c/busses/Kconfig      |  10 +
>  drivers/i2c/busses/Makefile     |   1 +
>  drivers/i2c/busses/i2c-aspeed.c | 831 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 842 insertions(+)
>  create mode 100644 drivers/i2c/busses/i2c-aspeed.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 5c3993b..0178c6c 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -998,6 +998,16 @@ config I2C_RCAR
>           This driver can also be built as a module.  If so, the module
>           will be called i2c-rcar.
>
> +config I2C_ASPEED
> +       tristate "Aspeed AST2xxx SoC I2C Controller"
> +       depends on (ARCH_ASPEED || COMPILE_TEST) && OF
> +       help
> +         If you say yes to this option, support will be included for the
> +         Aspeed AST2xxx SoC I2C controller.
> +
> +         This driver can also be built as a module.  If so, the module
> +         will be called i2c-aspeed.
> +
>  comment "External I2C/SMBus adapter drivers"
>
>  config I2C_DIOLAN_U2C
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 37f2819..49631cd 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -96,6 +96,7 @@ obj-$(CONFIG_I2C_XILINX)      += i2c-xiic.o
>  obj-$(CONFIG_I2C_XLR)          += i2c-xlr.o
>  obj-$(CONFIG_I2C_XLP9XX)       += i2c-xlp9xx.o
>  obj-$(CONFIG_I2C_RCAR)         += i2c-rcar.o
> +obj-$(CONFIG_I2C_ASPEED)       += i2c-aspeed.o
>
>  # External I2C/SMBus adapter drivers
>  obj-$(CONFIG_I2C_DIOLAN_U2C)   += i2c-diolan-u2c.o
> diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
> new file mode 100644
> index 0000000..b19f13c
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-aspeed.c
> @@ -0,0 +1,831 @@
> +/*
> + *  I2C adapter for the ASPEED I2C bus.
> + *
> + *  Copyright (C) 2012-2020  ASPEED Technology Inc.
> + *  Copyright 2016 IBM Corporation
> + *  Copyright 2016 Google, Inc.
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License version 2 as
> + *  published by the Free Software Foundation.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/errno.h>
> +#include <linux/interrupt.h>
> +#include <linux/completion.h>
> +#include <linux/slab.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +
> +/* I2C Register */
> +#define ASPEED_I2C_FUN_CTRL_REG                                0x00
> +#define ASPEED_I2C_AC_TIMING_REG1                      0x04
> +#define ASPEED_I2C_AC_TIMING_REG2                      0x08
> +#define ASPEED_I2C_INTR_CTRL_REG                       0x0c
> +#define ASPEED_I2C_INTR_STS_REG                                0x10
> +#define ASPEED_I2C_CMD_REG                             0x14
> +#define ASPEED_I2C_DEV_ADDR_REG                                0x18
> +#define ASPEED_I2C_BYTE_BUF_REG                                0x20
> +
> +#define ASPEED_I2C_NUM_BUS 14
> +
> +/* Global Register Definition */
> +/* 0x00 : I2C Interrupt Status Register  */
> +/* 0x08 : I2C Interrupt Target Assignment  */
> +
> +/* Device Register Definition */
> +/* 0x00 : I2CD Function Control Register  */
> +#define ASPEED_I2CD_MULTI_MASTER_DIS                   BIT(15)
> +#define ASPEED_I2CD_SDA_DRIVE_1T_EN                    BIT(8)
> +#define ASPEED_I2CD_M_SDA_DRIVE_1T_EN                  BIT(7)
> +#define ASPEED_I2CD_M_HIGH_SPEED_EN                    BIT(6)
> +#define ASPEED_I2CD_SLAVE_EN                           BIT(1)
> +#define ASPEED_I2CD_MASTER_EN                          BIT(0)
> +
> +/* 0x08 : I2CD Clock and AC Timing Control Register #2 */
> +#define ASPEED_NO_TIMEOUT_CTRL                         0
> +
> +
> +/* 0x0c : I2CD Interrupt Control Register &
> + * 0x10 : I2CD Interrupt Status Register
> + *
> + * These share bit definitions, so use the same values for the enable &
> + * status bits.
> + */
> +#define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT                        BIT(14)
> +#define ASPEED_I2CD_INTR_BUS_RECOVER_DONE              BIT(13)
> +#define ASPEED_I2CD_INTR_SLAVE_MATCH                   BIT(7)
> +#define ASPEED_I2CD_INTR_SCL_TIMEOUT                   BIT(6)
> +#define ASPEED_I2CD_INTR_ABNORMAL                      BIT(5)
> +#define ASPEED_I2CD_INTR_NORMAL_STOP                   BIT(4)
> +#define ASPEED_I2CD_INTR_ARBIT_LOSS                    BIT(3)
> +#define ASPEED_I2CD_INTR_RX_DONE                       BIT(2)
> +#define ASPEED_I2CD_INTR_TX_NAK                                BIT(1)
> +#define ASPEED_I2CD_INTR_TX_ACK                                BIT(0)
> +
> +/* 0x14 : I2CD Command/Status Register   */
> +#define ASPEED_I2CD_SCL_LINE_STS                       BIT(18)
> +#define ASPEED_I2CD_SDA_LINE_STS                       BIT(17)
> +#define ASPEED_I2CD_BUS_BUSY_STS                       BIT(16)
> +#define ASPEED_I2CD_BUS_RECOVER_CMD                    BIT(11)
> +
> +/* Command Bit */
> +#define ASPEED_I2CD_M_STOP_CMD                         BIT(5)
> +#define ASPEED_I2CD_M_S_RX_CMD_LAST                    BIT(4)
> +#define ASPEED_I2CD_M_RX_CMD                           BIT(3)
> +#define ASPEED_I2CD_S_TX_CMD                           BIT(2)
> +#define ASPEED_I2CD_M_TX_CMD                           BIT(1)
> +#define ASPEED_I2CD_M_START_CMD                                BIT(0)
> +
> +/* 0x18 : I2CD Slave Device Address Register   */
> +#define ASPEED_I2CD_DEV_ADDR_MASK                      GENMASK(6, 0)
> +
> +enum aspeed_i2c_slave_state {
> +       ASPEED_I2C_SLAVE_START,
> +       ASPEED_I2C_SLAVE_READ_REQUESTED,
> +       ASPEED_I2C_SLAVE_READ_PROCESSED,
> +       ASPEED_I2C_SLAVE_WRITE_REQUESTED,
> +       ASPEED_I2C_SLAVE_WRITE_RECEIVED,
> +       ASPEED_I2C_SLAVE_STOP,
> +};
> +
> +struct aspeed_i2c_bus {
> +       struct i2c_adapter              adap;
> +       struct device                   *dev;
> +       void __iomem                    *base;
> +       spinlock_t                      lock;
> +       struct completion               cmd_complete;
> +       int                             irq;
> +       /* Transaction state. */
> +       struct i2c_msg                  *msg;
> +       int                             msg_pos;
> +       u32                             cmd_err;
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +       struct i2c_client               *slave;
> +       enum aspeed_i2c_slave_state     slave_state;
> +#endif
> +};
> +
> +struct aspeed_i2c_controller {
> +       struct device           *dev;
> +       void __iomem            *base;
> +       int                     irq;
> +       struct irq_domain       *irq_domain;
> +};
> +
> +static inline void aspeed_i2c_write(struct aspeed_i2c_bus *bus, u32 val,
> +                                   u32 reg)
> +{
> +       writel(val, bus->base + reg);
> +}
> +
> +static inline u32 aspeed_i2c_read(struct aspeed_i2c_bus *bus, u32 reg)
> +{
> +       return readl(bus->base + reg);
> +}
> +
> +static u8 aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
> +{
> +       u32 command;
> +       unsigned long time_left;
> +       unsigned long flags;
> +       int ret = 0;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
> +       /* Bus is idle: no recovery needed. */
> +       if ((command & ASPEED_I2CD_SDA_LINE_STS) &&
> +           (command & ASPEED_I2CD_SCL_LINE_STS))
> +               goto out;
> +
> +       dev_dbg(bus->dev, "bus hung (state %x), attempting recovery\n",
> +               command);
> +
> +       /* Bus held: put bus in stop state. */
> +       if ((command & ASPEED_I2CD_SDA_LINE_STS) &&
> +           !(command & ASPEED_I2CD_SCL_LINE_STS)) {
> +               aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
> +                                ASPEED_I2C_CMD_REG);
> +               reinit_completion(&bus->cmd_complete);
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +
> +               time_left = wait_for_completion_interruptible_timeout(
> +                               &bus->cmd_complete, bus->adap.timeout * HZ);
> +
> +               spin_lock_irqsave(&bus->lock, flags);
> +               if (time_left == 0)
> +                       ret = -ETIMEDOUT;
> +               else if (bus->cmd_err)
> +                       ret = -EIO;
> +       /* Bus error. */
> +       } else if (!(command & ASPEED_I2CD_SDA_LINE_STS)) {
> +               aspeed_i2c_write(bus, ASPEED_I2CD_BUS_RECOVER_CMD,
> +                                ASPEED_I2C_CMD_REG);
> +               reinit_completion(&bus->cmd_complete);
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +
> +               time_left = wait_for_completion_interruptible_timeout(
> +                               &bus->cmd_complete, bus->adap.timeout * HZ);
> +
> +               spin_lock_irqsave(&bus->lock, flags);
> +               if (time_left == 0)
> +                       ret = -ETIMEDOUT;
> +               else if (bus->cmd_err)
> +                       ret = -EIO;
> +               /* Recovery failed. */
> +               else if (!(aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
> +                          ASPEED_I2CD_SDA_LINE_STS))
> +                       ret = -EIO;
> +       }
> +
> +out:
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +       return ret;
> +}
> +
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +static bool aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus)
> +{
> +       bool irq_handled = true;
> +       u32 command;
> +       u32 irq_status;
> +       u32 status_ack = 0;
> +       u8 value;
> +       struct i2c_client *slave = bus->slave;
> +
> +       spin_lock(&bus->lock);
> +       if (!slave) {
> +               irq_handled = false;
> +               goto out;
> +       }
> +       command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
> +       irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
> +
> +       /* Slave was requested, restart state machine. */
> +       if (irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH) {
> +               status_ack |= ASPEED_I2CD_INTR_SLAVE_MATCH;
> +               bus->slave_state = ASPEED_I2C_SLAVE_START;
> +       }
> +       /* Slave is not currently active, irq was for someone else. */
> +       if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
> +               irq_handled = false;
> +               goto out;
> +       }
> +
> +       dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
> +               irq_status, command);
> +
> +       /* Slave was sent something. */
> +       if (irq_status & ASPEED_I2CD_INTR_RX_DONE) {
> +               value = aspeed_i2c_read(bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
> +               /* Handle address frame. */
> +               if (bus->slave_state == ASPEED_I2C_SLAVE_START) {
> +                       if (value & 0x1)
> +                               bus->slave_state =
> +                                               ASPEED_I2C_SLAVE_READ_REQUESTED;
> +                       else
> +                               bus->slave_state =
> +                                               ASPEED_I2C_SLAVE_WRITE_REQUESTED;
> +               }
> +               status_ack |= ASPEED_I2CD_INTR_RX_DONE;
> +       }
> +
> +       /* Slave was asked to stop. */
> +       if (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) {
> +               status_ack |= ASPEED_I2CD_INTR_NORMAL_STOP;
> +               bus->slave_state = ASPEED_I2C_SLAVE_STOP;
> +       }
> +       if (irq_status & ASPEED_I2CD_INTR_TX_NAK) {
> +               status_ack |= ASPEED_I2CD_INTR_TX_NAK;
> +               bus->slave_state = ASPEED_I2C_SLAVE_STOP;
> +       }
> +
> +       if (bus->slave_state == ASPEED_I2C_SLAVE_READ_REQUESTED) {
> +               if (irq_status & ASPEED_I2CD_INTR_TX_ACK)
> +                       dev_err(bus->dev, "Unexpected ACK on read request.\n");
> +               bus->slave_state = ASPEED_I2C_SLAVE_READ_PROCESSED;
> +
> +               i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value);
> +               aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
> +               aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
> +       } else if (bus->slave_state == ASPEED_I2C_SLAVE_READ_PROCESSED) {
> +               status_ack |= ASPEED_I2CD_INTR_TX_ACK;
> +               if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK))
> +                       dev_err(bus->dev,
> +                               "Expected ACK after processed read.\n");
> +               i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value);
> +               aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
> +               aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
> +       } else if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_REQUESTED) {
> +               bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED;
> +               i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value);
> +       } else if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED) {
> +               i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value);
> +       } else if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
> +               i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
> +       }
> +
> +       if (status_ack != irq_status)
> +               dev_err(bus->dev,
> +                       "irq handled != irq. expected %x, but was %x\n",
> +                       irq_status, status_ack);
> +       aspeed_i2c_write(bus, status_ack, ASPEED_I2C_INTR_STS_REG);
> +
> +out:
> +       spin_unlock(&bus->lock);
> +       return irq_handled;
> +}
> +#endif
> +
> +static bool aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus)
> +{
> +       const u32 errs = ASPEED_I2CD_INTR_ARBIT_LOSS |
> +               ASPEED_I2CD_INTR_ABNORMAL |
> +               ASPEED_I2CD_INTR_SCL_TIMEOUT |
> +               ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |
> +               ASPEED_I2CD_INTR_TX_NAK;
> +       u32 irq_status;
> +
> +       spin_lock(&bus->lock);
> +       irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
> +       bus->cmd_err = irq_status & errs;
> +
> +       dev_dbg(bus->dev, "master irq status 0x%08x\n", irq_status);
> +
> +       /* No message to transfer. */
> +       if (bus->cmd_err ||
> +           (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) ||
> +           (irq_status & ASPEED_I2CD_INTR_BUS_RECOVER_DONE)) {
> +               complete(&bus->cmd_complete);
> +               goto out;
> +       } else if (!bus->msg || bus->msg_pos >= bus->msg->len)
> +               goto out;
> +
> +       if ((bus->msg->flags & I2C_M_RD) &&
> +           (irq_status & ASPEED_I2CD_INTR_RX_DONE)) {
> +               bus->msg->buf[bus->msg_pos++] = aspeed_i2c_read(
> +                               bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
> +               if (bus->msg_pos + 1 < bus->msg->len)
> +                       aspeed_i2c_write(bus, ASPEED_I2CD_M_RX_CMD,
> +                                        ASPEED_I2C_CMD_REG);
> +               else if (bus->msg_pos < bus->msg->len)
> +                       aspeed_i2c_write(bus, ASPEED_I2CD_M_RX_CMD |
> +                                     ASPEED_I2CD_M_S_RX_CMD_LAST,
> +                                     ASPEED_I2C_CMD_REG);
> +       } else if (!(bus->msg->flags & I2C_M_RD) &&
> +                  (irq_status & ASPEED_I2CD_INTR_TX_ACK)) {
> +               aspeed_i2c_write(bus, bus->msg->buf[bus->msg_pos++],
> +                             ASPEED_I2C_BYTE_BUF_REG);
> +               aspeed_i2c_write(bus, ASPEED_I2CD_M_TX_CMD, ASPEED_I2C_CMD_REG);
> +       }
> +
> +       /* Transmission complete: notify caller. */
> +       if (bus->msg_pos >= bus->msg->len)
> +               complete(&bus->cmd_complete);
> +out:
> +       aspeed_i2c_write(bus, irq_status, ASPEED_I2C_INTR_STS_REG);
> +       spin_unlock(&bus->lock);
> +       return true;
> +}
> +
> +static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id)
> +{
> +       struct aspeed_i2c_bus *bus = dev_id;
> +
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +       if (aspeed_i2c_slave_irq(bus)) {
> +               dev_dbg(bus->dev, "irq handled by slave.\n");
> +               return IRQ_HANDLED;
> +       }
> +#endif
> +       if (aspeed_i2c_master_irq(bus)) {
> +               dev_dbg(bus->dev, "irq handled by master.\n");
> +               return IRQ_HANDLED;
> +       }
> +       dev_err(bus->dev, "irq not handled properly!\n");
> +       return IRQ_HANDLED;
> +}
> +
> +static int aspeed_i2c_master_single_xfer(struct i2c_adapter *adap,
> +                                     struct i2c_msg *msg)
> +{
> +       struct aspeed_i2c_bus *bus = adap->algo_data;
> +       unsigned long flags;
> +       u8 slave_addr;
> +       u32 command = ASPEED_I2CD_M_START_CMD | ASPEED_I2CD_M_TX_CMD;
> +       int ret = msg->len;
> +       unsigned long time_left;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       bus->msg = msg;
> +       bus->msg_pos = 0;
> +       slave_addr = msg->addr << 1;
> +       if (msg->flags & I2C_M_RD) {
> +               slave_addr |= 1;
> +               command |= ASPEED_I2CD_M_RX_CMD;
> +               if (msg->len == 1)
> +                       command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
> +       }
> +       aspeed_i2c_write(bus, slave_addr, ASPEED_I2C_BYTE_BUF_REG);
> +       aspeed_i2c_write(bus, command, ASPEED_I2C_CMD_REG);
> +       reinit_completion(&bus->cmd_complete);
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +       time_left = wait_for_completion_interruptible_timeout(
> +                       &bus->cmd_complete, bus->adap.timeout * HZ * msg->len);
> +       if (time_left == 0)
> +               return -ETIMEDOUT;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       if (bus->cmd_err)
> +               ret = -EIO;
> +       bus->msg = NULL;
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +       return ret;
> +}
> +
> +static int aspeed_i2c_master_xfer(struct i2c_adapter *adap,
> +                                 struct i2c_msg *msgs, int num)
> +{
> +       struct aspeed_i2c_bus *bus = adap->algo_data;
> +       int ret;
> +       int i;
> +       unsigned long flags;
> +       unsigned long time_left;
> +
> +       /* If bus is busy, attempt recovery. We assume a single master
> +        * environment.
> +        */
> +       if (aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
> +           ASPEED_I2CD_BUS_BUSY_STS) {
> +               ret = aspeed_i2c_recover_bus(bus);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       for (i = 0; i < num; i++) {
> +               ret = aspeed_i2c_master_single_xfer(adap, &msgs[i]);
> +               if (ret < 0)
> +                       break;
> +               /* TODO: Support other forms of I2C protocol mangling. */
> +               if (msgs[i].flags & I2C_M_STOP) {
> +                       spin_lock_irqsave(&bus->lock, flags);
> +                       aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
> +                                     ASPEED_I2C_CMD_REG);
> +                       reinit_completion(&bus->cmd_complete);
> +                       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +                       time_left = wait_for_completion_interruptible_timeout(
> +                                       &bus->cmd_complete,
> +                                       bus->adap.timeout * HZ);
> +                       if (time_left == 0)
> +                               return -ETIMEDOUT;
> +               }
> +       }
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD, ASPEED_I2C_CMD_REG);
> +       reinit_completion(&bus->cmd_complete);
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +       time_left = wait_for_completion_interruptible_timeout(
> +                       &bus->cmd_complete, bus->adap.timeout * HZ);
> +       if (time_left == 0)
> +               return -ETIMEDOUT;
> +
> +       /* If nothing went wrong, return number of messages transferred. */
> +       if (ret < 0)
> +               return ret;
> +       else
> +               return i;
> +}
> +
> +static u32 aspeed_i2c_functionality(struct i2c_adapter *adap)
> +{
> +       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
> +}
> +
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +static int aspeed_i2c_reg_slave(struct i2c_client *client)
> +{
> +       struct aspeed_i2c_bus *bus;
> +       unsigned long flags;
> +       u32 addr_reg_val;
> +       u32 func_ctrl_reg_val;
> +
> +       bus = client->adapter->algo_data;
> +       spin_lock_irqsave(&bus->lock, flags);
> +       if (bus->slave) {
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +               return -EINVAL;
> +       }
> +
> +       /* Set slave addr. */
> +       addr_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_DEV_ADDR_REG);
> +       addr_reg_val &= ~ASPEED_I2CD_DEV_ADDR_MASK;
> +       addr_reg_val |= client->addr & ASPEED_I2CD_DEV_ADDR_MASK;
> +       aspeed_i2c_write(bus, addr_reg_val, ASPEED_I2C_DEV_ADDR_REG);
> +
> +       /* Switch from master mode to slave mode. */
> +       func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
> +       func_ctrl_reg_val &= ~ASPEED_I2CD_MASTER_EN;
> +       func_ctrl_reg_val |= ASPEED_I2CD_SLAVE_EN;
> +       aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
> +
> +       bus->slave = client;
> +       bus->slave_state = ASPEED_I2C_SLAVE_STOP;
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +       return 0;
> +}
> +
> +static int aspeed_i2c_unreg_slave(struct i2c_client *client)
> +{
> +       struct aspeed_i2c_bus *bus = client->adapter->algo_data;
> +       unsigned long flags;
> +       u32 func_ctrl_reg_val;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       if (!bus->slave) {
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +               return -EINVAL;
> +       }
> +
> +       /* Switch from slave mode to master mode. */
> +       func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
> +       func_ctrl_reg_val &= ~ASPEED_I2CD_SLAVE_EN;
> +       func_ctrl_reg_val |= ASPEED_I2CD_MASTER_EN;
> +       aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
> +
> +       bus->slave = NULL;
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +       return 0;
> +}
> +#endif
> +
> +static const struct i2c_algorithm aspeed_i2c_algo = {
> +       .master_xfer    = aspeed_i2c_master_xfer,
> +       .functionality  = aspeed_i2c_functionality,
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +       .reg_slave      = aspeed_i2c_reg_slave,
> +       .unreg_slave    = aspeed_i2c_unreg_slave,
> +#endif
> +};
> +
> +static u32 aspeed_i2c_get_clk_reg_val(u32 divider_ratio)
> +{
> +       unsigned int inc = 0, div;
> +       u32 scl_low, scl_high, data;
> +
> +       for (div = 0; divider_ratio >= 16; div++) {
> +               inc |= (divider_ratio & 1);
> +               divider_ratio >>= 1;
> +       }
> +       divider_ratio += inc;
> +       scl_low = (divider_ratio >> 1) - 1;
> +       scl_high = divider_ratio - scl_low - 2;
> +       data = 0x77700300 | (scl_high << 16) | (scl_low << 12) | div;
> +       return data;
> +}
> +
> +static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus,
> +                           struct platform_device *pdev)
> +{
> +       struct clk *pclk;
> +       u32 clk_freq;
> +       u32 divider_ratio;
> +       int ret;
> +
> +       pclk = devm_clk_get(&pdev->dev, NULL);
> +       if (IS_ERR(pclk)) {
> +               dev_err(&pdev->dev, "clk_get failed\n");
> +               return PTR_ERR(pclk);
> +       }
> +       ret = of_property_read_u32(pdev->dev.of_node,
> +                       "clock-frequency", &clk_freq);
> +       if (ret < 0) {
> +               dev_err(&pdev->dev,
> +                               "Could not read clock-frequency property\n");
> +               clk_freq = 100000;
> +       }
> +       divider_ratio = clk_get_rate(pclk) / clk_freq;
> +       /* We just need the clock rate, we don't actually use the clk object. */
> +       devm_clk_put(&pdev->dev, pclk);
> +
> +       /* Set AC Timing */
> +       if (clk_freq / 1000 > 400) {
> +               aspeed_i2c_write(bus, aspeed_i2c_read(bus,
> +                                                     ASPEED_I2C_FUN_CTRL_REG) |
> +                               ASPEED_I2CD_M_HIGH_SPEED_EN |
> +                               ASPEED_I2CD_M_SDA_DRIVE_1T_EN |
> +                               ASPEED_I2CD_SDA_DRIVE_1T_EN,
> +                               ASPEED_I2C_FUN_CTRL_REG);
> +
> +               aspeed_i2c_write(bus, 0x3, ASPEED_I2C_AC_TIMING_REG2);
> +               aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divider_ratio),
> +                             ASPEED_I2C_AC_TIMING_REG1);
> +       } else {
> +               aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divider_ratio),
> +                             ASPEED_I2C_AC_TIMING_REG1);
> +               aspeed_i2c_write(bus, ASPEED_NO_TIMEOUT_CTRL,
> +                                ASPEED_I2C_AC_TIMING_REG2);
> +       }
> +
> +       return 0;
> +}
> +
> +static void noop(struct irq_data *data) { }
> +
> +static struct irq_chip aspeed_i2c_irqchip = {
> +       .name           = "ast-i2c",
> +       .irq_unmask     = noop,
> +       .irq_mask       = noop,
> +};
> +
> +static int aspeed_i2c_probe_bus(struct platform_device *pdev)
> +{
> +       struct aspeed_i2c_bus *bus;
> +       struct aspeed_i2c_controller *controller =
> +                       dev_get_drvdata(pdev->dev.parent);
> +       struct resource *res;
> +       int ret, bus_num, irq;
> +
> +       bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
> +       if (!bus)
> +               return -ENOMEM;
> +
> +       ret = of_property_read_u32(pdev->dev.of_node, "bus", &bus_num);
> +       if (ret)
> +               return -ENXIO;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       bus->base = devm_ioremap_resource(&pdev->dev, res);
> +       if (IS_ERR(bus->base))
> +               return PTR_ERR(bus->base);
> +
> +       bus->irq = platform_get_irq(pdev, 0);
> +       if (bus->irq < 0) {
> +               dev_err(&pdev->dev, "platform_get_irq failed\n");
> +               return -ENXIO;
> +       }
> +
> +       irq = irq_create_mapping(controller->irq_domain, bus_num);
> +       irq_set_chip_data(irq, controller);
> +       irq_set_chip_and_handler(irq, &aspeed_i2c_irqchip, handle_simple_irq);
> +       ret = devm_request_irq(&pdev->dev, bus->irq, aspeed_i2c_bus_irq,
> +                       0, dev_name(&pdev->dev), bus);
> +       if (ret) {
> +               dev_err(&pdev->dev, "devm_request_irq failed\n");
> +               return -ENXIO;
> +       }
> +
> +       /* Initialize the I2C adapter */
> +       spin_lock_init(&bus->lock);
> +       init_completion(&bus->cmd_complete);
> +       bus->adap.nr = bus_num;
> +       bus->adap.owner = THIS_MODULE;
> +       bus->adap.retries = 0;
> +       bus->adap.timeout = 5;
> +       bus->adap.algo = &aspeed_i2c_algo;
> +       bus->adap.algo_data = bus;
> +       bus->adap.dev.parent = &pdev->dev;
> +       bus->adap.dev.of_node = pdev->dev.of_node;
> +       snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c-%d",
> +                       bus_num);
> +
> +       bus->dev = &pdev->dev;
> +
> +       /* reset device: disable master & slave functions */
> +       aspeed_i2c_write(bus, 0, ASPEED_I2C_FUN_CTRL_REG);
> +
> +       ret = aspeed_i2c_init_clk(bus, pdev);
> +       if (ret < 0)
> +               return ret;
> +
> +       /* Enable Master Mode */
> +       aspeed_i2c_write(bus, aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG) |
> +                     ASPEED_I2CD_MASTER_EN |
> +                     ASPEED_I2CD_MULTI_MASTER_DIS, ASPEED_I2C_FUN_CTRL_REG);
> +
> +       /* Set interrupt generation of I2C controller */
> +       aspeed_i2c_write(bus, ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |
> +                       ASPEED_I2CD_INTR_BUS_RECOVER_DONE |
> +                       ASPEED_I2CD_INTR_SCL_TIMEOUT |
> +                       ASPEED_I2CD_INTR_ABNORMAL |
> +                       ASPEED_I2CD_INTR_NORMAL_STOP |
> +                       ASPEED_I2CD_INTR_ARBIT_LOSS |
> +                       ASPEED_I2CD_INTR_RX_DONE |
> +                       ASPEED_I2CD_INTR_TX_NAK |
> +                       ASPEED_I2CD_INTR_TX_ACK,
> +                       ASPEED_I2C_INTR_CTRL_REG);
> +
> +       ret = i2c_add_numbered_adapter(&bus->adap);
> +       if (ret < 0)
> +               return -ENXIO;
> +
> +       platform_set_drvdata(pdev, bus);
> +
> +       dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
> +                       bus->adap.nr, bus->irq);
> +
> +       return 0;
> +}
> +
> +static int aspeed_i2c_remove_bus(struct platform_device *pdev)
> +{
> +       struct aspeed_i2c_bus *bus = platform_get_drvdata(pdev);
> +
> +       i2c_del_adapter(&bus->adap);
> +       return 0;
> +}
> +
> +static const struct of_device_id aspeed_i2c_bus_of_table[] = {
> +       { .compatible = "aspeed,ast2400-i2c-bus", },
> +       { .compatible = "aspeed,ast2500-i2c-bus", },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, aspeed_i2c_bus_of_table);
> +
> +static struct platform_driver aspeed_i2c_bus_driver = {
> +       .probe          = aspeed_i2c_probe_bus,
> +       .remove         = aspeed_i2c_remove_bus,
> +       .driver         = {
> +               .name           = "ast-i2c-bus",
> +               .of_match_table = aspeed_i2c_bus_of_table,
> +       },
> +};
> +
> +static void aspeed_i2c_controller_irq(struct irq_desc *desc)
> +{
> +       struct aspeed_i2c_controller *c = irq_desc_get_handler_data(desc);
> +       unsigned long p, status;
> +       unsigned int bus_irq;
> +
> +       status = readl(c->base);
> +       for_each_set_bit(p, &status, ASPEED_I2C_NUM_BUS) {
> +               bus_irq = irq_find_mapping(c->irq_domain, p);
> +               generic_handle_irq(bus_irq);
> +       }
> +}
> +
> +static int aspeed_i2c_probe_controller(struct platform_device *pdev)
> +{
> +       struct aspeed_i2c_controller *controller;
> +       struct device_node *np;
> +       struct resource *res;
> +
> +       controller = kzalloc(sizeof(*controller), GFP_KERNEL);
> +       if (!controller)
> +               return -ENOMEM;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       controller->base = devm_ioremap_resource(&pdev->dev, res);
> +       if (IS_ERR(controller->base))
> +               return PTR_ERR(controller->base);
> +
> +       controller->irq = platform_get_irq(pdev, 0);
> +       if (controller->irq < 0) {
> +               dev_err(&pdev->dev, "no platform IRQ\n");
> +               return -ENXIO;
> +       }
> +
> +       controller->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
> +                       ASPEED_I2C_NUM_BUS, &irq_domain_simple_ops, NULL);
> +       if (!controller->irq_domain) {
> +               dev_err(&pdev->dev, "no IRQ domain\n");
> +               return -ENXIO;
> +       }
> +       controller->irq_domain->name = "ast-i2c-domain";
> +
> +       irq_set_chained_handler_and_data(controller->irq,
> +                       aspeed_i2c_controller_irq, controller);
> +
> +       controller->dev = &pdev->dev;
> +
> +       platform_set_drvdata(pdev, controller);
> +
> +       dev_info(controller->dev, "i2c controller registered, irq %d\n",
> +                       controller->irq);
> +
> +       for_each_child_of_node(pdev->dev.of_node, np) {
> +               int ret;
> +               u32 bus_num;
> +               char bus_id[sizeof("i2c-12345")];
> +
> +               /*
> +                * Set a useful name derived from the bus number; the device
> +                * tree should provide us with one that corresponds to the
> +                * hardware numbering.  If the property is missing the
> +                * probe would fail so just skip it here.
> +                */
> +
> +               ret = of_property_read_u32(np, "bus", &bus_num);
> +               if (ret)
> +                       continue;
> +
> +               ret = snprintf(bus_id, sizeof(bus_id), "i2c-%u", bus_num);
> +               if (ret >= sizeof(bus_id))
> +                       continue;
> +
> +               of_platform_device_create(np, bus_id, &pdev->dev);
> +               of_node_put(np);
> +       }
> +
> +       return 0;
> +}
> +
> +static int aspeed_i2c_remove_controller(struct platform_device *pdev)
> +{
> +       struct aspeed_i2c_controller *controller = platform_get_drvdata(pdev);
> +
> +       irq_domain_remove(controller->irq_domain);
> +       return 0;
> +}
> +
> +static const struct of_device_id aspeed_i2c_controller_of_table[] = {
> +       { .compatible = "aspeed,ast2400-i2c-controller", },
> +       { .compatible = "aspeed,ast2500-i2c-controller", },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, aspeed_i2c_controller_of_table);
> +
> +static struct platform_driver aspeed_i2c_controller_driver = {
> +       .probe          = aspeed_i2c_probe_controller,
> +       .remove         = aspeed_i2c_remove_controller,
> +       .driver         = {
> +               .name           = "ast-i2c-controller",
> +               .of_match_table = aspeed_i2c_controller_of_table,
> +       },
> +};
> +
> +static int __init aspeed_i2c_driver_init(void)
> +{
> +       int ret;
> +
> +       ret = platform_driver_register(&aspeed_i2c_controller_driver);
> +       if (ret < 0)
> +               return ret;
> +       return platform_driver_register(&aspeed_i2c_bus_driver);
> +}
> +module_init(aspeed_i2c_driver_init);
> +
> +static void __exit aspeed_i2c_driver_exit(void)
> +{
> +       platform_driver_unregister(&aspeed_i2c_bus_driver);
> +       platform_driver_unregister(&aspeed_i2c_controller_driver);
> +}
> +module_exit(aspeed_i2c_driver_exit);
> +
> +MODULE_AUTHOR("Brendan Higgins <brendanhiggins@google.com>");
> +MODULE_DESCRIPTION("Aspeed I2C Bus Driver");
> +MODULE_LICENSE("GPL");
> --
> 2.8.0.rc3.226.g39d4020
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 1/2] i2c: aspeed: added driver for Aspeed I2C
@ 2016-09-19 17:56     ` Brendan Higgins
  0 siblings, 0 replies; 21+ messages in thread
From: Brendan Higgins @ 2016-09-19 17:56 UTC (permalink / raw)
  To: Wolfram Sang, robh+dt, mark.rutland
  Cc: linux-i2c, devicetree, linux-kernel, OpenBMC Maillist,
	Joel Stanley, Jeremy Kerr, Brendan Higgins

Gentle ping

On Fri, Sep 9, 2016 at 6:55 PM, Brendan Higgins
<brendanhiggins@google.com> wrote:
> Added initial master and slave support for Aspeed I2C controller.
> Supports fourteen busses present in ast24xx and ast25xx BMC SoCs by
> Aspeed.
>
> Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
> ---
> Changes for v2:
>   - Added single module_init (multiple was breaking some builds).
> ---
>  drivers/i2c/busses/Kconfig      |  10 +
>  drivers/i2c/busses/Makefile     |   1 +
>  drivers/i2c/busses/i2c-aspeed.c | 831 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 842 insertions(+)
>  create mode 100644 drivers/i2c/busses/i2c-aspeed.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 5c3993b..0178c6c 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -998,6 +998,16 @@ config I2C_RCAR
>           This driver can also be built as a module.  If so, the module
>           will be called i2c-rcar.
>
> +config I2C_ASPEED
> +       tristate "Aspeed AST2xxx SoC I2C Controller"
> +       depends on (ARCH_ASPEED || COMPILE_TEST) && OF
> +       help
> +         If you say yes to this option, support will be included for the
> +         Aspeed AST2xxx SoC I2C controller.
> +
> +         This driver can also be built as a module.  If so, the module
> +         will be called i2c-aspeed.
> +
>  comment "External I2C/SMBus adapter drivers"
>
>  config I2C_DIOLAN_U2C
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 37f2819..49631cd 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -96,6 +96,7 @@ obj-$(CONFIG_I2C_XILINX)      += i2c-xiic.o
>  obj-$(CONFIG_I2C_XLR)          += i2c-xlr.o
>  obj-$(CONFIG_I2C_XLP9XX)       += i2c-xlp9xx.o
>  obj-$(CONFIG_I2C_RCAR)         += i2c-rcar.o
> +obj-$(CONFIG_I2C_ASPEED)       += i2c-aspeed.o
>
>  # External I2C/SMBus adapter drivers
>  obj-$(CONFIG_I2C_DIOLAN_U2C)   += i2c-diolan-u2c.o
> diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
> new file mode 100644
> index 0000000..b19f13c
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-aspeed.c
> @@ -0,0 +1,831 @@
> +/*
> + *  I2C adapter for the ASPEED I2C bus.
> + *
> + *  Copyright (C) 2012-2020  ASPEED Technology Inc.
> + *  Copyright 2016 IBM Corporation
> + *  Copyright 2016 Google, Inc.
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License version 2 as
> + *  published by the Free Software Foundation.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/errno.h>
> +#include <linux/interrupt.h>
> +#include <linux/completion.h>
> +#include <linux/slab.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +
> +/* I2C Register */
> +#define ASPEED_I2C_FUN_CTRL_REG                                0x00
> +#define ASPEED_I2C_AC_TIMING_REG1                      0x04
> +#define ASPEED_I2C_AC_TIMING_REG2                      0x08
> +#define ASPEED_I2C_INTR_CTRL_REG                       0x0c
> +#define ASPEED_I2C_INTR_STS_REG                                0x10
> +#define ASPEED_I2C_CMD_REG                             0x14
> +#define ASPEED_I2C_DEV_ADDR_REG                                0x18
> +#define ASPEED_I2C_BYTE_BUF_REG                                0x20
> +
> +#define ASPEED_I2C_NUM_BUS 14
> +
> +/* Global Register Definition */
> +/* 0x00 : I2C Interrupt Status Register  */
> +/* 0x08 : I2C Interrupt Target Assignment  */
> +
> +/* Device Register Definition */
> +/* 0x00 : I2CD Function Control Register  */
> +#define ASPEED_I2CD_MULTI_MASTER_DIS                   BIT(15)
> +#define ASPEED_I2CD_SDA_DRIVE_1T_EN                    BIT(8)
> +#define ASPEED_I2CD_M_SDA_DRIVE_1T_EN                  BIT(7)
> +#define ASPEED_I2CD_M_HIGH_SPEED_EN                    BIT(6)
> +#define ASPEED_I2CD_SLAVE_EN                           BIT(1)
> +#define ASPEED_I2CD_MASTER_EN                          BIT(0)
> +
> +/* 0x08 : I2CD Clock and AC Timing Control Register #2 */
> +#define ASPEED_NO_TIMEOUT_CTRL                         0
> +
> +
> +/* 0x0c : I2CD Interrupt Control Register &
> + * 0x10 : I2CD Interrupt Status Register
> + *
> + * These share bit definitions, so use the same values for the enable &
> + * status bits.
> + */
> +#define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT                        BIT(14)
> +#define ASPEED_I2CD_INTR_BUS_RECOVER_DONE              BIT(13)
> +#define ASPEED_I2CD_INTR_SLAVE_MATCH                   BIT(7)
> +#define ASPEED_I2CD_INTR_SCL_TIMEOUT                   BIT(6)
> +#define ASPEED_I2CD_INTR_ABNORMAL                      BIT(5)
> +#define ASPEED_I2CD_INTR_NORMAL_STOP                   BIT(4)
> +#define ASPEED_I2CD_INTR_ARBIT_LOSS                    BIT(3)
> +#define ASPEED_I2CD_INTR_RX_DONE                       BIT(2)
> +#define ASPEED_I2CD_INTR_TX_NAK                                BIT(1)
> +#define ASPEED_I2CD_INTR_TX_ACK                                BIT(0)
> +
> +/* 0x14 : I2CD Command/Status Register   */
> +#define ASPEED_I2CD_SCL_LINE_STS                       BIT(18)
> +#define ASPEED_I2CD_SDA_LINE_STS                       BIT(17)
> +#define ASPEED_I2CD_BUS_BUSY_STS                       BIT(16)
> +#define ASPEED_I2CD_BUS_RECOVER_CMD                    BIT(11)
> +
> +/* Command Bit */
> +#define ASPEED_I2CD_M_STOP_CMD                         BIT(5)
> +#define ASPEED_I2CD_M_S_RX_CMD_LAST                    BIT(4)
> +#define ASPEED_I2CD_M_RX_CMD                           BIT(3)
> +#define ASPEED_I2CD_S_TX_CMD                           BIT(2)
> +#define ASPEED_I2CD_M_TX_CMD                           BIT(1)
> +#define ASPEED_I2CD_M_START_CMD                                BIT(0)
> +
> +/* 0x18 : I2CD Slave Device Address Register   */
> +#define ASPEED_I2CD_DEV_ADDR_MASK                      GENMASK(6, 0)
> +
> +enum aspeed_i2c_slave_state {
> +       ASPEED_I2C_SLAVE_START,
> +       ASPEED_I2C_SLAVE_READ_REQUESTED,
> +       ASPEED_I2C_SLAVE_READ_PROCESSED,
> +       ASPEED_I2C_SLAVE_WRITE_REQUESTED,
> +       ASPEED_I2C_SLAVE_WRITE_RECEIVED,
> +       ASPEED_I2C_SLAVE_STOP,
> +};
> +
> +struct aspeed_i2c_bus {
> +       struct i2c_adapter              adap;
> +       struct device                   *dev;
> +       void __iomem                    *base;
> +       spinlock_t                      lock;
> +       struct completion               cmd_complete;
> +       int                             irq;
> +       /* Transaction state. */
> +       struct i2c_msg                  *msg;
> +       int                             msg_pos;
> +       u32                             cmd_err;
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +       struct i2c_client               *slave;
> +       enum aspeed_i2c_slave_state     slave_state;
> +#endif
> +};
> +
> +struct aspeed_i2c_controller {
> +       struct device           *dev;
> +       void __iomem            *base;
> +       int                     irq;
> +       struct irq_domain       *irq_domain;
> +};
> +
> +static inline void aspeed_i2c_write(struct aspeed_i2c_bus *bus, u32 val,
> +                                   u32 reg)
> +{
> +       writel(val, bus->base + reg);
> +}
> +
> +static inline u32 aspeed_i2c_read(struct aspeed_i2c_bus *bus, u32 reg)
> +{
> +       return readl(bus->base + reg);
> +}
> +
> +static u8 aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
> +{
> +       u32 command;
> +       unsigned long time_left;
> +       unsigned long flags;
> +       int ret = 0;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
> +       /* Bus is idle: no recovery needed. */
> +       if ((command & ASPEED_I2CD_SDA_LINE_STS) &&
> +           (command & ASPEED_I2CD_SCL_LINE_STS))
> +               goto out;
> +
> +       dev_dbg(bus->dev, "bus hung (state %x), attempting recovery\n",
> +               command);
> +
> +       /* Bus held: put bus in stop state. */
> +       if ((command & ASPEED_I2CD_SDA_LINE_STS) &&
> +           !(command & ASPEED_I2CD_SCL_LINE_STS)) {
> +               aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
> +                                ASPEED_I2C_CMD_REG);
> +               reinit_completion(&bus->cmd_complete);
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +
> +               time_left = wait_for_completion_interruptible_timeout(
> +                               &bus->cmd_complete, bus->adap.timeout * HZ);
> +
> +               spin_lock_irqsave(&bus->lock, flags);
> +               if (time_left == 0)
> +                       ret = -ETIMEDOUT;
> +               else if (bus->cmd_err)
> +                       ret = -EIO;
> +       /* Bus error. */
> +       } else if (!(command & ASPEED_I2CD_SDA_LINE_STS)) {
> +               aspeed_i2c_write(bus, ASPEED_I2CD_BUS_RECOVER_CMD,
> +                                ASPEED_I2C_CMD_REG);
> +               reinit_completion(&bus->cmd_complete);
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +
> +               time_left = wait_for_completion_interruptible_timeout(
> +                               &bus->cmd_complete, bus->adap.timeout * HZ);
> +
> +               spin_lock_irqsave(&bus->lock, flags);
> +               if (time_left == 0)
> +                       ret = -ETIMEDOUT;
> +               else if (bus->cmd_err)
> +                       ret = -EIO;
> +               /* Recovery failed. */
> +               else if (!(aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
> +                          ASPEED_I2CD_SDA_LINE_STS))
> +                       ret = -EIO;
> +       }
> +
> +out:
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +       return ret;
> +}
> +
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +static bool aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus)
> +{
> +       bool irq_handled = true;
> +       u32 command;
> +       u32 irq_status;
> +       u32 status_ack = 0;
> +       u8 value;
> +       struct i2c_client *slave = bus->slave;
> +
> +       spin_lock(&bus->lock);
> +       if (!slave) {
> +               irq_handled = false;
> +               goto out;
> +       }
> +       command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
> +       irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
> +
> +       /* Slave was requested, restart state machine. */
> +       if (irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH) {
> +               status_ack |= ASPEED_I2CD_INTR_SLAVE_MATCH;
> +               bus->slave_state = ASPEED_I2C_SLAVE_START;
> +       }
> +       /* Slave is not currently active, irq was for someone else. */
> +       if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
> +               irq_handled = false;
> +               goto out;
> +       }
> +
> +       dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
> +               irq_status, command);
> +
> +       /* Slave was sent something. */
> +       if (irq_status & ASPEED_I2CD_INTR_RX_DONE) {
> +               value = aspeed_i2c_read(bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
> +               /* Handle address frame. */
> +               if (bus->slave_state == ASPEED_I2C_SLAVE_START) {
> +                       if (value & 0x1)
> +                               bus->slave_state =
> +                                               ASPEED_I2C_SLAVE_READ_REQUESTED;
> +                       else
> +                               bus->slave_state =
> +                                               ASPEED_I2C_SLAVE_WRITE_REQUESTED;
> +               }
> +               status_ack |= ASPEED_I2CD_INTR_RX_DONE;
> +       }
> +
> +       /* Slave was asked to stop. */
> +       if (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) {
> +               status_ack |= ASPEED_I2CD_INTR_NORMAL_STOP;
> +               bus->slave_state = ASPEED_I2C_SLAVE_STOP;
> +       }
> +       if (irq_status & ASPEED_I2CD_INTR_TX_NAK) {
> +               status_ack |= ASPEED_I2CD_INTR_TX_NAK;
> +               bus->slave_state = ASPEED_I2C_SLAVE_STOP;
> +       }
> +
> +       if (bus->slave_state == ASPEED_I2C_SLAVE_READ_REQUESTED) {
> +               if (irq_status & ASPEED_I2CD_INTR_TX_ACK)
> +                       dev_err(bus->dev, "Unexpected ACK on read request.\n");
> +               bus->slave_state = ASPEED_I2C_SLAVE_READ_PROCESSED;
> +
> +               i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value);
> +               aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
> +               aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
> +       } else if (bus->slave_state == ASPEED_I2C_SLAVE_READ_PROCESSED) {
> +               status_ack |= ASPEED_I2CD_INTR_TX_ACK;
> +               if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK))
> +                       dev_err(bus->dev,
> +                               "Expected ACK after processed read.\n");
> +               i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value);
> +               aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
> +               aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
> +       } else if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_REQUESTED) {
> +               bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED;
> +               i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value);
> +       } else if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED) {
> +               i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value);
> +       } else if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
> +               i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
> +       }
> +
> +       if (status_ack != irq_status)
> +               dev_err(bus->dev,
> +                       "irq handled != irq. expected %x, but was %x\n",
> +                       irq_status, status_ack);
> +       aspeed_i2c_write(bus, status_ack, ASPEED_I2C_INTR_STS_REG);
> +
> +out:
> +       spin_unlock(&bus->lock);
> +       return irq_handled;
> +}
> +#endif
> +
> +static bool aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus)
> +{
> +       const u32 errs = ASPEED_I2CD_INTR_ARBIT_LOSS |
> +               ASPEED_I2CD_INTR_ABNORMAL |
> +               ASPEED_I2CD_INTR_SCL_TIMEOUT |
> +               ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |
> +               ASPEED_I2CD_INTR_TX_NAK;
> +       u32 irq_status;
> +
> +       spin_lock(&bus->lock);
> +       irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
> +       bus->cmd_err = irq_status & errs;
> +
> +       dev_dbg(bus->dev, "master irq status 0x%08x\n", irq_status);
> +
> +       /* No message to transfer. */
> +       if (bus->cmd_err ||
> +           (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) ||
> +           (irq_status & ASPEED_I2CD_INTR_BUS_RECOVER_DONE)) {
> +               complete(&bus->cmd_complete);
> +               goto out;
> +       } else if (!bus->msg || bus->msg_pos >= bus->msg->len)
> +               goto out;
> +
> +       if ((bus->msg->flags & I2C_M_RD) &&
> +           (irq_status & ASPEED_I2CD_INTR_RX_DONE)) {
> +               bus->msg->buf[bus->msg_pos++] = aspeed_i2c_read(
> +                               bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
> +               if (bus->msg_pos + 1 < bus->msg->len)
> +                       aspeed_i2c_write(bus, ASPEED_I2CD_M_RX_CMD,
> +                                        ASPEED_I2C_CMD_REG);
> +               else if (bus->msg_pos < bus->msg->len)
> +                       aspeed_i2c_write(bus, ASPEED_I2CD_M_RX_CMD |
> +                                     ASPEED_I2CD_M_S_RX_CMD_LAST,
> +                                     ASPEED_I2C_CMD_REG);
> +       } else if (!(bus->msg->flags & I2C_M_RD) &&
> +                  (irq_status & ASPEED_I2CD_INTR_TX_ACK)) {
> +               aspeed_i2c_write(bus, bus->msg->buf[bus->msg_pos++],
> +                             ASPEED_I2C_BYTE_BUF_REG);
> +               aspeed_i2c_write(bus, ASPEED_I2CD_M_TX_CMD, ASPEED_I2C_CMD_REG);
> +       }
> +
> +       /* Transmission complete: notify caller. */
> +       if (bus->msg_pos >= bus->msg->len)
> +               complete(&bus->cmd_complete);
> +out:
> +       aspeed_i2c_write(bus, irq_status, ASPEED_I2C_INTR_STS_REG);
> +       spin_unlock(&bus->lock);
> +       return true;
> +}
> +
> +static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id)
> +{
> +       struct aspeed_i2c_bus *bus = dev_id;
> +
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +       if (aspeed_i2c_slave_irq(bus)) {
> +               dev_dbg(bus->dev, "irq handled by slave.\n");
> +               return IRQ_HANDLED;
> +       }
> +#endif
> +       if (aspeed_i2c_master_irq(bus)) {
> +               dev_dbg(bus->dev, "irq handled by master.\n");
> +               return IRQ_HANDLED;
> +       }
> +       dev_err(bus->dev, "irq not handled properly!\n");
> +       return IRQ_HANDLED;
> +}
> +
> +static int aspeed_i2c_master_single_xfer(struct i2c_adapter *adap,
> +                                     struct i2c_msg *msg)
> +{
> +       struct aspeed_i2c_bus *bus = adap->algo_data;
> +       unsigned long flags;
> +       u8 slave_addr;
> +       u32 command = ASPEED_I2CD_M_START_CMD | ASPEED_I2CD_M_TX_CMD;
> +       int ret = msg->len;
> +       unsigned long time_left;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       bus->msg = msg;
> +       bus->msg_pos = 0;
> +       slave_addr = msg->addr << 1;
> +       if (msg->flags & I2C_M_RD) {
> +               slave_addr |= 1;
> +               command |= ASPEED_I2CD_M_RX_CMD;
> +               if (msg->len == 1)
> +                       command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
> +       }
> +       aspeed_i2c_write(bus, slave_addr, ASPEED_I2C_BYTE_BUF_REG);
> +       aspeed_i2c_write(bus, command, ASPEED_I2C_CMD_REG);
> +       reinit_completion(&bus->cmd_complete);
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +       time_left = wait_for_completion_interruptible_timeout(
> +                       &bus->cmd_complete, bus->adap.timeout * HZ * msg->len);
> +       if (time_left == 0)
> +               return -ETIMEDOUT;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       if (bus->cmd_err)
> +               ret = -EIO;
> +       bus->msg = NULL;
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +       return ret;
> +}
> +
> +static int aspeed_i2c_master_xfer(struct i2c_adapter *adap,
> +                                 struct i2c_msg *msgs, int num)
> +{
> +       struct aspeed_i2c_bus *bus = adap->algo_data;
> +       int ret;
> +       int i;
> +       unsigned long flags;
> +       unsigned long time_left;
> +
> +       /* If bus is busy, attempt recovery. We assume a single master
> +        * environment.
> +        */
> +       if (aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
> +           ASPEED_I2CD_BUS_BUSY_STS) {
> +               ret = aspeed_i2c_recover_bus(bus);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       for (i = 0; i < num; i++) {
> +               ret = aspeed_i2c_master_single_xfer(adap, &msgs[i]);
> +               if (ret < 0)
> +                       break;
> +               /* TODO: Support other forms of I2C protocol mangling. */
> +               if (msgs[i].flags & I2C_M_STOP) {
> +                       spin_lock_irqsave(&bus->lock, flags);
> +                       aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
> +                                     ASPEED_I2C_CMD_REG);
> +                       reinit_completion(&bus->cmd_complete);
> +                       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +                       time_left = wait_for_completion_interruptible_timeout(
> +                                       &bus->cmd_complete,
> +                                       bus->adap.timeout * HZ);
> +                       if (time_left == 0)
> +                               return -ETIMEDOUT;
> +               }
> +       }
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD, ASPEED_I2C_CMD_REG);
> +       reinit_completion(&bus->cmd_complete);
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +
> +       time_left = wait_for_completion_interruptible_timeout(
> +                       &bus->cmd_complete, bus->adap.timeout * HZ);
> +       if (time_left == 0)
> +               return -ETIMEDOUT;
> +
> +       /* If nothing went wrong, return number of messages transferred. */
> +       if (ret < 0)
> +               return ret;
> +       else
> +               return i;
> +}
> +
> +static u32 aspeed_i2c_functionality(struct i2c_adapter *adap)
> +{
> +       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
> +}
> +
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +static int aspeed_i2c_reg_slave(struct i2c_client *client)
> +{
> +       struct aspeed_i2c_bus *bus;
> +       unsigned long flags;
> +       u32 addr_reg_val;
> +       u32 func_ctrl_reg_val;
> +
> +       bus = client->adapter->algo_data;
> +       spin_lock_irqsave(&bus->lock, flags);
> +       if (bus->slave) {
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +               return -EINVAL;
> +       }
> +
> +       /* Set slave addr. */
> +       addr_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_DEV_ADDR_REG);
> +       addr_reg_val &= ~ASPEED_I2CD_DEV_ADDR_MASK;
> +       addr_reg_val |= client->addr & ASPEED_I2CD_DEV_ADDR_MASK;
> +       aspeed_i2c_write(bus, addr_reg_val, ASPEED_I2C_DEV_ADDR_REG);
> +
> +       /* Switch from master mode to slave mode. */
> +       func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
> +       func_ctrl_reg_val &= ~ASPEED_I2CD_MASTER_EN;
> +       func_ctrl_reg_val |= ASPEED_I2CD_SLAVE_EN;
> +       aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
> +
> +       bus->slave = client;
> +       bus->slave_state = ASPEED_I2C_SLAVE_STOP;
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +       return 0;
> +}
> +
> +static int aspeed_i2c_unreg_slave(struct i2c_client *client)
> +{
> +       struct aspeed_i2c_bus *bus = client->adapter->algo_data;
> +       unsigned long flags;
> +       u32 func_ctrl_reg_val;
> +
> +       spin_lock_irqsave(&bus->lock, flags);
> +       if (!bus->slave) {
> +               spin_unlock_irqrestore(&bus->lock, flags);
> +               return -EINVAL;
> +       }
> +
> +       /* Switch from slave mode to master mode. */
> +       func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
> +       func_ctrl_reg_val &= ~ASPEED_I2CD_SLAVE_EN;
> +       func_ctrl_reg_val |= ASPEED_I2CD_MASTER_EN;
> +       aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
> +
> +       bus->slave = NULL;
> +       spin_unlock_irqrestore(&bus->lock, flags);
> +       return 0;
> +}
> +#endif
> +
> +static const struct i2c_algorithm aspeed_i2c_algo = {
> +       .master_xfer    = aspeed_i2c_master_xfer,
> +       .functionality  = aspeed_i2c_functionality,
> +#if IS_ENABLED(CONFIG_I2C_SLAVE)
> +       .reg_slave      = aspeed_i2c_reg_slave,
> +       .unreg_slave    = aspeed_i2c_unreg_slave,
> +#endif
> +};
> +
> +static u32 aspeed_i2c_get_clk_reg_val(u32 divider_ratio)
> +{
> +       unsigned int inc = 0, div;
> +       u32 scl_low, scl_high, data;
> +
> +       for (div = 0; divider_ratio >= 16; div++) {
> +               inc |= (divider_ratio & 1);
> +               divider_ratio >>= 1;
> +       }
> +       divider_ratio += inc;
> +       scl_low = (divider_ratio >> 1) - 1;
> +       scl_high = divider_ratio - scl_low - 2;
> +       data = 0x77700300 | (scl_high << 16) | (scl_low << 12) | div;
> +       return data;
> +}
> +
> +static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus,
> +                           struct platform_device *pdev)
> +{
> +       struct clk *pclk;
> +       u32 clk_freq;
> +       u32 divider_ratio;
> +       int ret;
> +
> +       pclk = devm_clk_get(&pdev->dev, NULL);
> +       if (IS_ERR(pclk)) {
> +               dev_err(&pdev->dev, "clk_get failed\n");
> +               return PTR_ERR(pclk);
> +       }
> +       ret = of_property_read_u32(pdev->dev.of_node,
> +                       "clock-frequency", &clk_freq);
> +       if (ret < 0) {
> +               dev_err(&pdev->dev,
> +                               "Could not read clock-frequency property\n");
> +               clk_freq = 100000;
> +       }
> +       divider_ratio = clk_get_rate(pclk) / clk_freq;
> +       /* We just need the clock rate, we don't actually use the clk object. */
> +       devm_clk_put(&pdev->dev, pclk);
> +
> +       /* Set AC Timing */
> +       if (clk_freq / 1000 > 400) {
> +               aspeed_i2c_write(bus, aspeed_i2c_read(bus,
> +                                                     ASPEED_I2C_FUN_CTRL_REG) |
> +                               ASPEED_I2CD_M_HIGH_SPEED_EN |
> +                               ASPEED_I2CD_M_SDA_DRIVE_1T_EN |
> +                               ASPEED_I2CD_SDA_DRIVE_1T_EN,
> +                               ASPEED_I2C_FUN_CTRL_REG);
> +
> +               aspeed_i2c_write(bus, 0x3, ASPEED_I2C_AC_TIMING_REG2);
> +               aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divider_ratio),
> +                             ASPEED_I2C_AC_TIMING_REG1);
> +       } else {
> +               aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divider_ratio),
> +                             ASPEED_I2C_AC_TIMING_REG1);
> +               aspeed_i2c_write(bus, ASPEED_NO_TIMEOUT_CTRL,
> +                                ASPEED_I2C_AC_TIMING_REG2);
> +       }
> +
> +       return 0;
> +}
> +
> +static void noop(struct irq_data *data) { }
> +
> +static struct irq_chip aspeed_i2c_irqchip = {
> +       .name           = "ast-i2c",
> +       .irq_unmask     = noop,
> +       .irq_mask       = noop,
> +};
> +
> +static int aspeed_i2c_probe_bus(struct platform_device *pdev)
> +{
> +       struct aspeed_i2c_bus *bus;
> +       struct aspeed_i2c_controller *controller =
> +                       dev_get_drvdata(pdev->dev.parent);
> +       struct resource *res;
> +       int ret, bus_num, irq;
> +
> +       bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
> +       if (!bus)
> +               return -ENOMEM;
> +
> +       ret = of_property_read_u32(pdev->dev.of_node, "bus", &bus_num);
> +       if (ret)
> +               return -ENXIO;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       bus->base = devm_ioremap_resource(&pdev->dev, res);
> +       if (IS_ERR(bus->base))
> +               return PTR_ERR(bus->base);
> +
> +       bus->irq = platform_get_irq(pdev, 0);
> +       if (bus->irq < 0) {
> +               dev_err(&pdev->dev, "platform_get_irq failed\n");
> +               return -ENXIO;
> +       }
> +
> +       irq = irq_create_mapping(controller->irq_domain, bus_num);
> +       irq_set_chip_data(irq, controller);
> +       irq_set_chip_and_handler(irq, &aspeed_i2c_irqchip, handle_simple_irq);
> +       ret = devm_request_irq(&pdev->dev, bus->irq, aspeed_i2c_bus_irq,
> +                       0, dev_name(&pdev->dev), bus);
> +       if (ret) {
> +               dev_err(&pdev->dev, "devm_request_irq failed\n");
> +               return -ENXIO;
> +       }
> +
> +       /* Initialize the I2C adapter */
> +       spin_lock_init(&bus->lock);
> +       init_completion(&bus->cmd_complete);
> +       bus->adap.nr = bus_num;
> +       bus->adap.owner = THIS_MODULE;
> +       bus->adap.retries = 0;
> +       bus->adap.timeout = 5;
> +       bus->adap.algo = &aspeed_i2c_algo;
> +       bus->adap.algo_data = bus;
> +       bus->adap.dev.parent = &pdev->dev;
> +       bus->adap.dev.of_node = pdev->dev.of_node;
> +       snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c-%d",
> +                       bus_num);
> +
> +       bus->dev = &pdev->dev;
> +
> +       /* reset device: disable master & slave functions */
> +       aspeed_i2c_write(bus, 0, ASPEED_I2C_FUN_CTRL_REG);
> +
> +       ret = aspeed_i2c_init_clk(bus, pdev);
> +       if (ret < 0)
> +               return ret;
> +
> +       /* Enable Master Mode */
> +       aspeed_i2c_write(bus, aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG) |
> +                     ASPEED_I2CD_MASTER_EN |
> +                     ASPEED_I2CD_MULTI_MASTER_DIS, ASPEED_I2C_FUN_CTRL_REG);
> +
> +       /* Set interrupt generation of I2C controller */
> +       aspeed_i2c_write(bus, ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |
> +                       ASPEED_I2CD_INTR_BUS_RECOVER_DONE |
> +                       ASPEED_I2CD_INTR_SCL_TIMEOUT |
> +                       ASPEED_I2CD_INTR_ABNORMAL |
> +                       ASPEED_I2CD_INTR_NORMAL_STOP |
> +                       ASPEED_I2CD_INTR_ARBIT_LOSS |
> +                       ASPEED_I2CD_INTR_RX_DONE |
> +                       ASPEED_I2CD_INTR_TX_NAK |
> +                       ASPEED_I2CD_INTR_TX_ACK,
> +                       ASPEED_I2C_INTR_CTRL_REG);
> +
> +       ret = i2c_add_numbered_adapter(&bus->adap);
> +       if (ret < 0)
> +               return -ENXIO;
> +
> +       platform_set_drvdata(pdev, bus);
> +
> +       dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
> +                       bus->adap.nr, bus->irq);
> +
> +       return 0;
> +}
> +
> +static int aspeed_i2c_remove_bus(struct platform_device *pdev)
> +{
> +       struct aspeed_i2c_bus *bus = platform_get_drvdata(pdev);
> +
> +       i2c_del_adapter(&bus->adap);
> +       return 0;
> +}
> +
> +static const struct of_device_id aspeed_i2c_bus_of_table[] = {
> +       { .compatible = "aspeed,ast2400-i2c-bus", },
> +       { .compatible = "aspeed,ast2500-i2c-bus", },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, aspeed_i2c_bus_of_table);
> +
> +static struct platform_driver aspeed_i2c_bus_driver = {
> +       .probe          = aspeed_i2c_probe_bus,
> +       .remove         = aspeed_i2c_remove_bus,
> +       .driver         = {
> +               .name           = "ast-i2c-bus",
> +               .of_match_table = aspeed_i2c_bus_of_table,
> +       },
> +};
> +
> +static void aspeed_i2c_controller_irq(struct irq_desc *desc)
> +{
> +       struct aspeed_i2c_controller *c = irq_desc_get_handler_data(desc);
> +       unsigned long p, status;
> +       unsigned int bus_irq;
> +
> +       status = readl(c->base);
> +       for_each_set_bit(p, &status, ASPEED_I2C_NUM_BUS) {
> +               bus_irq = irq_find_mapping(c->irq_domain, p);
> +               generic_handle_irq(bus_irq);
> +       }
> +}
> +
> +static int aspeed_i2c_probe_controller(struct platform_device *pdev)
> +{
> +       struct aspeed_i2c_controller *controller;
> +       struct device_node *np;
> +       struct resource *res;
> +
> +       controller = kzalloc(sizeof(*controller), GFP_KERNEL);
> +       if (!controller)
> +               return -ENOMEM;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       controller->base = devm_ioremap_resource(&pdev->dev, res);
> +       if (IS_ERR(controller->base))
> +               return PTR_ERR(controller->base);
> +
> +       controller->irq = platform_get_irq(pdev, 0);
> +       if (controller->irq < 0) {
> +               dev_err(&pdev->dev, "no platform IRQ\n");
> +               return -ENXIO;
> +       }
> +
> +       controller->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
> +                       ASPEED_I2C_NUM_BUS, &irq_domain_simple_ops, NULL);
> +       if (!controller->irq_domain) {
> +               dev_err(&pdev->dev, "no IRQ domain\n");
> +               return -ENXIO;
> +       }
> +       controller->irq_domain->name = "ast-i2c-domain";
> +
> +       irq_set_chained_handler_and_data(controller->irq,
> +                       aspeed_i2c_controller_irq, controller);
> +
> +       controller->dev = &pdev->dev;
> +
> +       platform_set_drvdata(pdev, controller);
> +
> +       dev_info(controller->dev, "i2c controller registered, irq %d\n",
> +                       controller->irq);
> +
> +       for_each_child_of_node(pdev->dev.of_node, np) {
> +               int ret;
> +               u32 bus_num;
> +               char bus_id[sizeof("i2c-12345")];
> +
> +               /*
> +                * Set a useful name derived from the bus number; the device
> +                * tree should provide us with one that corresponds to the
> +                * hardware numbering.  If the property is missing the
> +                * probe would fail so just skip it here.
> +                */
> +
> +               ret = of_property_read_u32(np, "bus", &bus_num);
> +               if (ret)
> +                       continue;
> +
> +               ret = snprintf(bus_id, sizeof(bus_id), "i2c-%u", bus_num);
> +               if (ret >= sizeof(bus_id))
> +                       continue;
> +
> +               of_platform_device_create(np, bus_id, &pdev->dev);
> +               of_node_put(np);
> +       }
> +
> +       return 0;
> +}
> +
> +static int aspeed_i2c_remove_controller(struct platform_device *pdev)
> +{
> +       struct aspeed_i2c_controller *controller = platform_get_drvdata(pdev);
> +
> +       irq_domain_remove(controller->irq_domain);
> +       return 0;
> +}
> +
> +static const struct of_device_id aspeed_i2c_controller_of_table[] = {
> +       { .compatible = "aspeed,ast2400-i2c-controller", },
> +       { .compatible = "aspeed,ast2500-i2c-controller", },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, aspeed_i2c_controller_of_table);
> +
> +static struct platform_driver aspeed_i2c_controller_driver = {
> +       .probe          = aspeed_i2c_probe_controller,
> +       .remove         = aspeed_i2c_remove_controller,
> +       .driver         = {
> +               .name           = "ast-i2c-controller",
> +               .of_match_table = aspeed_i2c_controller_of_table,
> +       },
> +};
> +
> +static int __init aspeed_i2c_driver_init(void)
> +{
> +       int ret;
> +
> +       ret = platform_driver_register(&aspeed_i2c_controller_driver);
> +       if (ret < 0)
> +               return ret;
> +       return platform_driver_register(&aspeed_i2c_bus_driver);
> +}
> +module_init(aspeed_i2c_driver_init);
> +
> +static void __exit aspeed_i2c_driver_exit(void)
> +{
> +       platform_driver_unregister(&aspeed_i2c_bus_driver);
> +       platform_driver_unregister(&aspeed_i2c_controller_driver);
> +}
> +module_exit(aspeed_i2c_driver_exit);
> +
> +MODULE_AUTHOR("Brendan Higgins <brendanhiggins@google.com>");
> +MODULE_DESCRIPTION("Aspeed I2C Bus Driver");
> +MODULE_LICENSE("GPL");
> --
> 2.8.0.rc3.226.g39d4020
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 1/2] i2c: aspeed: added driver for Aspeed I2C
@ 2016-09-10  1:55   ` Brendan Higgins
  0 siblings, 0 replies; 21+ messages in thread
From: Brendan Higgins @ 2016-09-10  1:55 UTC (permalink / raw)
  To: wsa, robh+dt, mark.rutland
  Cc: linux-i2c, devicetree, linux-kernel, openbmc, joel, jk, Brendan Higgins

Added initial master and slave support for Aspeed I2C controller.
Supports fourteen busses present in ast24xx and ast25xx BMC SoCs by
Aspeed.

Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
---
Changes for v2:
  - Added single module_init (multiple was breaking some builds).
---
 drivers/i2c/busses/Kconfig      |  10 +
 drivers/i2c/busses/Makefile     |   1 +
 drivers/i2c/busses/i2c-aspeed.c | 831 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 842 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-aspeed.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 5c3993b..0178c6c 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -998,6 +998,16 @@ config I2C_RCAR
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-rcar.
 
+config I2C_ASPEED
+	tristate "Aspeed AST2xxx SoC I2C Controller"
+	depends on (ARCH_ASPEED || COMPILE_TEST) && OF
+	help
+	  If you say yes to this option, support will be included for the
+	  Aspeed AST2xxx SoC I2C controller.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-aspeed.
+
 comment "External I2C/SMBus adapter drivers"
 
 config I2C_DIOLAN_U2C
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 37f2819..49631cd 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_I2C_XILINX)	+= i2c-xiic.o
 obj-$(CONFIG_I2C_XLR)		+= i2c-xlr.o
 obj-$(CONFIG_I2C_XLP9XX)	+= i2c-xlp9xx.o
 obj-$(CONFIG_I2C_RCAR)		+= i2c-rcar.o
+obj-$(CONFIG_I2C_ASPEED)	+= i2c-aspeed.o
 
 # External I2C/SMBus adapter drivers
 obj-$(CONFIG_I2C_DIOLAN_U2C)	+= i2c-diolan-u2c.o
diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
new file mode 100644
index 0000000..b19f13c
--- /dev/null
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -0,0 +1,831 @@
+/*
+ *  I2C adapter for the ASPEED I2C bus.
+ *
+ *  Copyright (C) 2012-2020  ASPEED Technology Inc.
+ *  Copyright 2016 IBM Corporation
+ *  Copyright 2016 Google, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+
+/* I2C Register */
+#define ASPEED_I2C_FUN_CTRL_REG				0x00
+#define ASPEED_I2C_AC_TIMING_REG1			0x04
+#define ASPEED_I2C_AC_TIMING_REG2			0x08
+#define ASPEED_I2C_INTR_CTRL_REG			0x0c
+#define ASPEED_I2C_INTR_STS_REG				0x10
+#define ASPEED_I2C_CMD_REG				0x14
+#define ASPEED_I2C_DEV_ADDR_REG				0x18
+#define ASPEED_I2C_BYTE_BUF_REG				0x20
+
+#define ASPEED_I2C_NUM_BUS 14
+
+/* Global Register Definition */
+/* 0x00 : I2C Interrupt Status Register  */
+/* 0x08 : I2C Interrupt Target Assignment  */
+
+/* Device Register Definition */
+/* 0x00 : I2CD Function Control Register  */
+#define ASPEED_I2CD_MULTI_MASTER_DIS			BIT(15)
+#define ASPEED_I2CD_SDA_DRIVE_1T_EN			BIT(8)
+#define ASPEED_I2CD_M_SDA_DRIVE_1T_EN			BIT(7)
+#define ASPEED_I2CD_M_HIGH_SPEED_EN			BIT(6)
+#define ASPEED_I2CD_SLAVE_EN				BIT(1)
+#define ASPEED_I2CD_MASTER_EN				BIT(0)
+
+/* 0x08 : I2CD Clock and AC Timing Control Register #2 */
+#define ASPEED_NO_TIMEOUT_CTRL				0
+
+
+/* 0x0c : I2CD Interrupt Control Register &
+ * 0x10 : I2CD Interrupt Status Register
+ *
+ * These share bit definitions, so use the same values for the enable &
+ * status bits.
+ */
+#define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT			BIT(14)
+#define ASPEED_I2CD_INTR_BUS_RECOVER_DONE		BIT(13)
+#define ASPEED_I2CD_INTR_SLAVE_MATCH			BIT(7)
+#define ASPEED_I2CD_INTR_SCL_TIMEOUT			BIT(6)
+#define ASPEED_I2CD_INTR_ABNORMAL			BIT(5)
+#define ASPEED_I2CD_INTR_NORMAL_STOP			BIT(4)
+#define ASPEED_I2CD_INTR_ARBIT_LOSS			BIT(3)
+#define ASPEED_I2CD_INTR_RX_DONE			BIT(2)
+#define ASPEED_I2CD_INTR_TX_NAK				BIT(1)
+#define ASPEED_I2CD_INTR_TX_ACK				BIT(0)
+
+/* 0x14 : I2CD Command/Status Register   */
+#define ASPEED_I2CD_SCL_LINE_STS			BIT(18)
+#define ASPEED_I2CD_SDA_LINE_STS			BIT(17)
+#define ASPEED_I2CD_BUS_BUSY_STS			BIT(16)
+#define ASPEED_I2CD_BUS_RECOVER_CMD			BIT(11)
+
+/* Command Bit */
+#define ASPEED_I2CD_M_STOP_CMD				BIT(5)
+#define ASPEED_I2CD_M_S_RX_CMD_LAST			BIT(4)
+#define ASPEED_I2CD_M_RX_CMD				BIT(3)
+#define ASPEED_I2CD_S_TX_CMD				BIT(2)
+#define ASPEED_I2CD_M_TX_CMD				BIT(1)
+#define ASPEED_I2CD_M_START_CMD				BIT(0)
+
+/* 0x18 : I2CD Slave Device Address Register   */
+#define ASPEED_I2CD_DEV_ADDR_MASK			GENMASK(6, 0)
+
+enum aspeed_i2c_slave_state {
+	ASPEED_I2C_SLAVE_START,
+	ASPEED_I2C_SLAVE_READ_REQUESTED,
+	ASPEED_I2C_SLAVE_READ_PROCESSED,
+	ASPEED_I2C_SLAVE_WRITE_REQUESTED,
+	ASPEED_I2C_SLAVE_WRITE_RECEIVED,
+	ASPEED_I2C_SLAVE_STOP,
+};
+
+struct aspeed_i2c_bus {
+	struct i2c_adapter		adap;
+	struct device			*dev;
+	void __iomem			*base;
+	spinlock_t			lock;
+	struct completion		cmd_complete;
+	int				irq;
+	/* Transaction state. */
+	struct i2c_msg			*msg;
+	int				msg_pos;
+	u32				cmd_err;
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	struct i2c_client		*slave;
+	enum aspeed_i2c_slave_state	slave_state;
+#endif
+};
+
+struct aspeed_i2c_controller {
+	struct device		*dev;
+	void __iomem		*base;
+	int			irq;
+	struct irq_domain	*irq_domain;
+};
+
+static inline void aspeed_i2c_write(struct aspeed_i2c_bus *bus, u32 val,
+				    u32 reg)
+{
+	writel(val, bus->base + reg);
+}
+
+static inline u32 aspeed_i2c_read(struct aspeed_i2c_bus *bus, u32 reg)
+{
+	return readl(bus->base + reg);
+}
+
+static u8 aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
+{
+	u32 command;
+	unsigned long time_left;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
+	/* Bus is idle: no recovery needed. */
+	if ((command & ASPEED_I2CD_SDA_LINE_STS) &&
+	    (command & ASPEED_I2CD_SCL_LINE_STS))
+		goto out;
+
+	dev_dbg(bus->dev, "bus hung (state %x), attempting recovery\n",
+		command);
+
+	/* Bus held: put bus in stop state. */
+	if ((command & ASPEED_I2CD_SDA_LINE_STS) &&
+	    !(command & ASPEED_I2CD_SCL_LINE_STS)) {
+		aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
+				 ASPEED_I2C_CMD_REG);
+		reinit_completion(&bus->cmd_complete);
+		spin_unlock_irqrestore(&bus->lock, flags);
+
+		time_left = wait_for_completion_interruptible_timeout(
+				&bus->cmd_complete, bus->adap.timeout * HZ);
+
+		spin_lock_irqsave(&bus->lock, flags);
+		if (time_left == 0)
+			ret = -ETIMEDOUT;
+		else if (bus->cmd_err)
+			ret = -EIO;
+	/* Bus error. */
+	} else if (!(command & ASPEED_I2CD_SDA_LINE_STS)) {
+		aspeed_i2c_write(bus, ASPEED_I2CD_BUS_RECOVER_CMD,
+				 ASPEED_I2C_CMD_REG);
+		reinit_completion(&bus->cmd_complete);
+		spin_unlock_irqrestore(&bus->lock, flags);
+
+		time_left = wait_for_completion_interruptible_timeout(
+				&bus->cmd_complete, bus->adap.timeout * HZ);
+
+		spin_lock_irqsave(&bus->lock, flags);
+		if (time_left == 0)
+			ret = -ETIMEDOUT;
+		else if (bus->cmd_err)
+			ret = -EIO;
+		/* Recovery failed. */
+		else if (!(aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
+			   ASPEED_I2CD_SDA_LINE_STS))
+			ret = -EIO;
+	}
+
+out:
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return ret;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static bool aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus)
+{
+	bool irq_handled = true;
+	u32 command;
+	u32 irq_status;
+	u32 status_ack = 0;
+	u8 value;
+	struct i2c_client *slave = bus->slave;
+
+	spin_lock(&bus->lock);
+	if (!slave) {
+		irq_handled = false;
+		goto out;
+	}
+	command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
+	irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
+
+	/* Slave was requested, restart state machine. */
+	if (irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH) {
+		status_ack |= ASPEED_I2CD_INTR_SLAVE_MATCH;
+		bus->slave_state = ASPEED_I2C_SLAVE_START;
+	}
+	/* Slave is not currently active, irq was for someone else. */
+	if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
+		irq_handled = false;
+		goto out;
+	}
+
+	dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
+		irq_status, command);
+
+	/* Slave was sent something. */
+	if (irq_status & ASPEED_I2CD_INTR_RX_DONE) {
+		value = aspeed_i2c_read(bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
+		/* Handle address frame. */
+		if (bus->slave_state == ASPEED_I2C_SLAVE_START) {
+			if (value & 0x1)
+				bus->slave_state =
+						ASPEED_I2C_SLAVE_READ_REQUESTED;
+			else
+				bus->slave_state =
+						ASPEED_I2C_SLAVE_WRITE_REQUESTED;
+		}
+		status_ack |= ASPEED_I2CD_INTR_RX_DONE;
+	}
+
+	/* Slave was asked to stop. */
+	if (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) {
+		status_ack |= ASPEED_I2CD_INTR_NORMAL_STOP;
+		bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+	}
+	if (irq_status & ASPEED_I2CD_INTR_TX_NAK) {
+		status_ack |= ASPEED_I2CD_INTR_TX_NAK;
+		bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+	}
+
+	if (bus->slave_state == ASPEED_I2C_SLAVE_READ_REQUESTED) {
+		if (irq_status & ASPEED_I2CD_INTR_TX_ACK)
+			dev_err(bus->dev, "Unexpected ACK on read request.\n");
+		bus->slave_state = ASPEED_I2C_SLAVE_READ_PROCESSED;
+
+		i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value);
+		aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
+		aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_READ_PROCESSED) {
+		status_ack |= ASPEED_I2CD_INTR_TX_ACK;
+		if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK))
+			dev_err(bus->dev,
+				"Expected ACK after processed read.\n");
+		i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value);
+		aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
+		aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_REQUESTED) {
+		bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED;
+		i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED) {
+		i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
+		i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
+	}
+
+	if (status_ack != irq_status)
+		dev_err(bus->dev,
+			"irq handled != irq. expected %x, but was %x\n",
+			irq_status, status_ack);
+	aspeed_i2c_write(bus, status_ack, ASPEED_I2C_INTR_STS_REG);
+
+out:
+	spin_unlock(&bus->lock);
+	return irq_handled;
+}
+#endif
+
+static bool aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus)
+{
+	const u32 errs = ASPEED_I2CD_INTR_ARBIT_LOSS |
+		ASPEED_I2CD_INTR_ABNORMAL |
+		ASPEED_I2CD_INTR_SCL_TIMEOUT |
+		ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |
+		ASPEED_I2CD_INTR_TX_NAK;
+	u32 irq_status;
+
+	spin_lock(&bus->lock);
+	irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
+	bus->cmd_err = irq_status & errs;
+
+	dev_dbg(bus->dev, "master irq status 0x%08x\n", irq_status);
+
+	/* No message to transfer. */
+	if (bus->cmd_err ||
+	    (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) ||
+	    (irq_status & ASPEED_I2CD_INTR_BUS_RECOVER_DONE)) {
+		complete(&bus->cmd_complete);
+		goto out;
+	} else if (!bus->msg || bus->msg_pos >= bus->msg->len)
+		goto out;
+
+	if ((bus->msg->flags & I2C_M_RD) &&
+	    (irq_status & ASPEED_I2CD_INTR_RX_DONE)) {
+		bus->msg->buf[bus->msg_pos++] = aspeed_i2c_read(
+				bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
+		if (bus->msg_pos + 1 < bus->msg->len)
+			aspeed_i2c_write(bus, ASPEED_I2CD_M_RX_CMD,
+					 ASPEED_I2C_CMD_REG);
+		else if (bus->msg_pos < bus->msg->len)
+			aspeed_i2c_write(bus, ASPEED_I2CD_M_RX_CMD |
+				      ASPEED_I2CD_M_S_RX_CMD_LAST,
+				      ASPEED_I2C_CMD_REG);
+	} else if (!(bus->msg->flags & I2C_M_RD) &&
+		   (irq_status & ASPEED_I2CD_INTR_TX_ACK)) {
+		aspeed_i2c_write(bus, bus->msg->buf[bus->msg_pos++],
+			      ASPEED_I2C_BYTE_BUF_REG);
+		aspeed_i2c_write(bus, ASPEED_I2CD_M_TX_CMD, ASPEED_I2C_CMD_REG);
+	}
+
+	/* Transmission complete: notify caller. */
+	if (bus->msg_pos >= bus->msg->len)
+		complete(&bus->cmd_complete);
+out:
+	aspeed_i2c_write(bus, irq_status, ASPEED_I2C_INTR_STS_REG);
+	spin_unlock(&bus->lock);
+	return true;
+}
+
+static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id)
+{
+	struct aspeed_i2c_bus *bus = dev_id;
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	if (aspeed_i2c_slave_irq(bus)) {
+		dev_dbg(bus->dev, "irq handled by slave.\n");
+		return IRQ_HANDLED;
+	}
+#endif
+	if (aspeed_i2c_master_irq(bus)) {
+		dev_dbg(bus->dev, "irq handled by master.\n");
+		return IRQ_HANDLED;
+	}
+	dev_err(bus->dev, "irq not handled properly!\n");
+	return IRQ_HANDLED;
+}
+
+static int aspeed_i2c_master_single_xfer(struct i2c_adapter *adap,
+				      struct i2c_msg *msg)
+{
+	struct aspeed_i2c_bus *bus = adap->algo_data;
+	unsigned long flags;
+	u8 slave_addr;
+	u32 command = ASPEED_I2CD_M_START_CMD | ASPEED_I2CD_M_TX_CMD;
+	int ret = msg->len;
+	unsigned long time_left;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	bus->msg = msg;
+	bus->msg_pos = 0;
+	slave_addr = msg->addr << 1;
+	if (msg->flags & I2C_M_RD) {
+		slave_addr |= 1;
+		command |= ASPEED_I2CD_M_RX_CMD;
+		if (msg->len == 1)
+			command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+	}
+	aspeed_i2c_write(bus, slave_addr, ASPEED_I2C_BYTE_BUF_REG);
+	aspeed_i2c_write(bus, command, ASPEED_I2C_CMD_REG);
+	reinit_completion(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	time_left = wait_for_completion_interruptible_timeout(
+			&bus->cmd_complete, bus->adap.timeout * HZ * msg->len);
+	if (time_left == 0)
+		return -ETIMEDOUT;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	if (bus->cmd_err)
+		ret = -EIO;
+	bus->msg = NULL;
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	return ret;
+}
+
+static int aspeed_i2c_master_xfer(struct i2c_adapter *adap,
+				  struct i2c_msg *msgs, int num)
+{
+	struct aspeed_i2c_bus *bus = adap->algo_data;
+	int ret;
+	int i;
+	unsigned long flags;
+	unsigned long time_left;
+
+	/* If bus is busy, attempt recovery. We assume a single master
+	 * environment.
+	 */
+	if (aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
+	    ASPEED_I2CD_BUS_BUSY_STS) {
+		ret = aspeed_i2c_recover_bus(bus);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < num; i++) {
+		ret = aspeed_i2c_master_single_xfer(adap, &msgs[i]);
+		if (ret < 0)
+			break;
+		/* TODO: Support other forms of I2C protocol mangling. */
+		if (msgs[i].flags & I2C_M_STOP) {
+			spin_lock_irqsave(&bus->lock, flags);
+			aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
+				      ASPEED_I2C_CMD_REG);
+			reinit_completion(&bus->cmd_complete);
+			spin_unlock_irqrestore(&bus->lock, flags);
+
+			time_left = wait_for_completion_interruptible_timeout(
+					&bus->cmd_complete,
+					bus->adap.timeout * HZ);
+			if (time_left == 0)
+				return -ETIMEDOUT;
+		}
+	}
+
+	spin_lock_irqsave(&bus->lock, flags);
+	aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD, ASPEED_I2C_CMD_REG);
+	reinit_completion(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	time_left = wait_for_completion_interruptible_timeout(
+			&bus->cmd_complete, bus->adap.timeout * HZ);
+	if (time_left == 0)
+		return -ETIMEDOUT;
+
+	/* If nothing went wrong, return number of messages transferred. */
+	if (ret < 0)
+		return ret;
+	else
+		return i;
+}
+
+static u32 aspeed_i2c_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static int aspeed_i2c_reg_slave(struct i2c_client *client)
+{
+	struct aspeed_i2c_bus *bus;
+	unsigned long flags;
+	u32 addr_reg_val;
+	u32 func_ctrl_reg_val;
+
+	bus = client->adapter->algo_data;
+	spin_lock_irqsave(&bus->lock, flags);
+	if (bus->slave) {
+		spin_unlock_irqrestore(&bus->lock, flags);
+		return -EINVAL;
+	}
+
+	/* Set slave addr. */
+	addr_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_DEV_ADDR_REG);
+	addr_reg_val &= ~ASPEED_I2CD_DEV_ADDR_MASK;
+	addr_reg_val |= client->addr & ASPEED_I2CD_DEV_ADDR_MASK;
+	aspeed_i2c_write(bus, addr_reg_val, ASPEED_I2C_DEV_ADDR_REG);
+
+	/* Switch from master mode to slave mode. */
+	func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
+	func_ctrl_reg_val &= ~ASPEED_I2CD_MASTER_EN;
+	func_ctrl_reg_val |= ASPEED_I2CD_SLAVE_EN;
+	aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
+
+	bus->slave = client;
+	bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return 0;
+}
+
+static int aspeed_i2c_unreg_slave(struct i2c_client *client)
+{
+	struct aspeed_i2c_bus *bus = client->adapter->algo_data;
+	unsigned long flags;
+	u32 func_ctrl_reg_val;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	if (!bus->slave) {
+		spin_unlock_irqrestore(&bus->lock, flags);
+		return -EINVAL;
+	}
+
+	/* Switch from slave mode to master mode. */
+	func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
+	func_ctrl_reg_val &= ~ASPEED_I2CD_SLAVE_EN;
+	func_ctrl_reg_val |= ASPEED_I2CD_MASTER_EN;
+	aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
+
+	bus->slave = NULL;
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return 0;
+}
+#endif
+
+static const struct i2c_algorithm aspeed_i2c_algo = {
+	.master_xfer	= aspeed_i2c_master_xfer,
+	.functionality	= aspeed_i2c_functionality,
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	.reg_slave	= aspeed_i2c_reg_slave,
+	.unreg_slave	= aspeed_i2c_unreg_slave,
+#endif
+};
+
+static u32 aspeed_i2c_get_clk_reg_val(u32 divider_ratio)
+{
+	unsigned int inc = 0, div;
+	u32 scl_low, scl_high, data;
+
+	for (div = 0; divider_ratio >= 16; div++) {
+		inc |= (divider_ratio & 1);
+		divider_ratio >>= 1;
+	}
+	divider_ratio += inc;
+	scl_low = (divider_ratio >> 1) - 1;
+	scl_high = divider_ratio - scl_low - 2;
+	data = 0x77700300 | (scl_high << 16) | (scl_low << 12) | div;
+	return data;
+}
+
+static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus,
+			    struct platform_device *pdev)
+{
+	struct clk *pclk;
+	u32 clk_freq;
+	u32 divider_ratio;
+	int ret;
+
+	pclk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(pclk)) {
+		dev_err(&pdev->dev, "clk_get failed\n");
+		return PTR_ERR(pclk);
+	}
+	ret = of_property_read_u32(pdev->dev.of_node,
+			"clock-frequency", &clk_freq);
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+				"Could not read clock-frequency property\n");
+		clk_freq = 100000;
+	}
+	divider_ratio = clk_get_rate(pclk) / clk_freq;
+	/* We just need the clock rate, we don't actually use the clk object. */
+	devm_clk_put(&pdev->dev, pclk);
+
+	/* Set AC Timing */
+	if (clk_freq / 1000 > 400) {
+		aspeed_i2c_write(bus, aspeed_i2c_read(bus,
+						      ASPEED_I2C_FUN_CTRL_REG) |
+				ASPEED_I2CD_M_HIGH_SPEED_EN |
+				ASPEED_I2CD_M_SDA_DRIVE_1T_EN |
+				ASPEED_I2CD_SDA_DRIVE_1T_EN,
+				ASPEED_I2C_FUN_CTRL_REG);
+
+		aspeed_i2c_write(bus, 0x3, ASPEED_I2C_AC_TIMING_REG2);
+		aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divider_ratio),
+			      ASPEED_I2C_AC_TIMING_REG1);
+	} else {
+		aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divider_ratio),
+			      ASPEED_I2C_AC_TIMING_REG1);
+		aspeed_i2c_write(bus, ASPEED_NO_TIMEOUT_CTRL,
+				 ASPEED_I2C_AC_TIMING_REG2);
+	}
+
+	return 0;
+}
+
+static void noop(struct irq_data *data) { }
+
+static struct irq_chip aspeed_i2c_irqchip = {
+	.name		= "ast-i2c",
+	.irq_unmask	= noop,
+	.irq_mask	= noop,
+};
+
+static int aspeed_i2c_probe_bus(struct platform_device *pdev)
+{
+	struct aspeed_i2c_bus *bus;
+	struct aspeed_i2c_controller *controller =
+			dev_get_drvdata(pdev->dev.parent);
+	struct resource *res;
+	int ret, bus_num, irq;
+
+	bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
+	if (!bus)
+		return -ENOMEM;
+
+	ret = of_property_read_u32(pdev->dev.of_node, "bus", &bus_num);
+	if (ret)
+		return -ENXIO;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	bus->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(bus->base))
+		return PTR_ERR(bus->base);
+
+	bus->irq = platform_get_irq(pdev, 0);
+	if (bus->irq < 0) {
+		dev_err(&pdev->dev, "platform_get_irq failed\n");
+		return -ENXIO;
+	}
+
+	irq = irq_create_mapping(controller->irq_domain, bus_num);
+	irq_set_chip_data(irq, controller);
+	irq_set_chip_and_handler(irq, &aspeed_i2c_irqchip, handle_simple_irq);
+	ret = devm_request_irq(&pdev->dev, bus->irq, aspeed_i2c_bus_irq,
+			0, dev_name(&pdev->dev), bus);
+	if (ret) {
+		dev_err(&pdev->dev, "devm_request_irq failed\n");
+		return -ENXIO;
+	}
+
+	/* Initialize the I2C adapter */
+	spin_lock_init(&bus->lock);
+	init_completion(&bus->cmd_complete);
+	bus->adap.nr = bus_num;
+	bus->adap.owner = THIS_MODULE;
+	bus->adap.retries = 0;
+	bus->adap.timeout = 5;
+	bus->adap.algo = &aspeed_i2c_algo;
+	bus->adap.algo_data = bus;
+	bus->adap.dev.parent = &pdev->dev;
+	bus->adap.dev.of_node = pdev->dev.of_node;
+	snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c-%d",
+			bus_num);
+
+	bus->dev = &pdev->dev;
+
+	/* reset device: disable master & slave functions */
+	aspeed_i2c_write(bus, 0, ASPEED_I2C_FUN_CTRL_REG);
+
+	ret = aspeed_i2c_init_clk(bus, pdev);
+	if (ret < 0)
+		return ret;
+
+	/* Enable Master Mode */
+	aspeed_i2c_write(bus, aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG) |
+		      ASPEED_I2CD_MASTER_EN |
+		      ASPEED_I2CD_MULTI_MASTER_DIS, ASPEED_I2C_FUN_CTRL_REG);
+
+	/* Set interrupt generation of I2C controller */
+	aspeed_i2c_write(bus, ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |
+			ASPEED_I2CD_INTR_BUS_RECOVER_DONE |
+			ASPEED_I2CD_INTR_SCL_TIMEOUT |
+			ASPEED_I2CD_INTR_ABNORMAL |
+			ASPEED_I2CD_INTR_NORMAL_STOP |
+			ASPEED_I2CD_INTR_ARBIT_LOSS |
+			ASPEED_I2CD_INTR_RX_DONE |
+			ASPEED_I2CD_INTR_TX_NAK |
+			ASPEED_I2CD_INTR_TX_ACK,
+			ASPEED_I2C_INTR_CTRL_REG);
+
+	ret = i2c_add_numbered_adapter(&bus->adap);
+	if (ret < 0)
+		return -ENXIO;
+
+	platform_set_drvdata(pdev, bus);
+
+	dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
+			bus->adap.nr, bus->irq);
+
+	return 0;
+}
+
+static int aspeed_i2c_remove_bus(struct platform_device *pdev)
+{
+	struct aspeed_i2c_bus *bus = platform_get_drvdata(pdev);
+
+	i2c_del_adapter(&bus->adap);
+	return 0;
+}
+
+static const struct of_device_id aspeed_i2c_bus_of_table[] = {
+	{ .compatible = "aspeed,ast2400-i2c-bus", },
+	{ .compatible = "aspeed,ast2500-i2c-bus", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, aspeed_i2c_bus_of_table);
+
+static struct platform_driver aspeed_i2c_bus_driver = {
+	.probe		= aspeed_i2c_probe_bus,
+	.remove		= aspeed_i2c_remove_bus,
+	.driver		= {
+		.name		= "ast-i2c-bus",
+		.of_match_table	= aspeed_i2c_bus_of_table,
+	},
+};
+
+static void aspeed_i2c_controller_irq(struct irq_desc *desc)
+{
+	struct aspeed_i2c_controller *c = irq_desc_get_handler_data(desc);
+	unsigned long p, status;
+	unsigned int bus_irq;
+
+	status = readl(c->base);
+	for_each_set_bit(p, &status, ASPEED_I2C_NUM_BUS) {
+		bus_irq = irq_find_mapping(c->irq_domain, p);
+		generic_handle_irq(bus_irq);
+	}
+}
+
+static int aspeed_i2c_probe_controller(struct platform_device *pdev)
+{
+	struct aspeed_i2c_controller *controller;
+	struct device_node *np;
+	struct resource *res;
+
+	controller = kzalloc(sizeof(*controller), GFP_KERNEL);
+	if (!controller)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	controller->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(controller->base))
+		return PTR_ERR(controller->base);
+
+	controller->irq = platform_get_irq(pdev, 0);
+	if (controller->irq < 0) {
+		dev_err(&pdev->dev, "no platform IRQ\n");
+		return -ENXIO;
+	}
+
+	controller->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
+			ASPEED_I2C_NUM_BUS, &irq_domain_simple_ops, NULL);
+	if (!controller->irq_domain) {
+		dev_err(&pdev->dev, "no IRQ domain\n");
+		return -ENXIO;
+	}
+	controller->irq_domain->name = "ast-i2c-domain";
+
+	irq_set_chained_handler_and_data(controller->irq,
+			aspeed_i2c_controller_irq, controller);
+
+	controller->dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, controller);
+
+	dev_info(controller->dev, "i2c controller registered, irq %d\n",
+			controller->irq);
+
+	for_each_child_of_node(pdev->dev.of_node, np) {
+		int ret;
+		u32 bus_num;
+		char bus_id[sizeof("i2c-12345")];
+
+		/*
+		 * Set a useful name derived from the bus number; the device
+		 * tree should provide us with one that corresponds to the
+		 * hardware numbering.  If the property is missing the
+		 * probe would fail so just skip it here.
+		 */
+
+		ret = of_property_read_u32(np, "bus", &bus_num);
+		if (ret)
+			continue;
+
+		ret = snprintf(bus_id, sizeof(bus_id), "i2c-%u", bus_num);
+		if (ret >= sizeof(bus_id))
+			continue;
+
+		of_platform_device_create(np, bus_id, &pdev->dev);
+		of_node_put(np);
+	}
+
+	return 0;
+}
+
+static int aspeed_i2c_remove_controller(struct platform_device *pdev)
+{
+	struct aspeed_i2c_controller *controller = platform_get_drvdata(pdev);
+
+	irq_domain_remove(controller->irq_domain);
+	return 0;
+}
+
+static const struct of_device_id aspeed_i2c_controller_of_table[] = {
+	{ .compatible = "aspeed,ast2400-i2c-controller", },
+	{ .compatible = "aspeed,ast2500-i2c-controller", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, aspeed_i2c_controller_of_table);
+
+static struct platform_driver aspeed_i2c_controller_driver = {
+	.probe		= aspeed_i2c_probe_controller,
+	.remove		= aspeed_i2c_remove_controller,
+	.driver		= {
+		.name		= "ast-i2c-controller",
+		.of_match_table	= aspeed_i2c_controller_of_table,
+	},
+};
+
+static int __init aspeed_i2c_driver_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&aspeed_i2c_controller_driver);
+	if (ret < 0)
+		return ret;
+	return platform_driver_register(&aspeed_i2c_bus_driver);
+}
+module_init(aspeed_i2c_driver_init);
+
+static void __exit aspeed_i2c_driver_exit(void)
+{
+	platform_driver_unregister(&aspeed_i2c_bus_driver);
+	platform_driver_unregister(&aspeed_i2c_controller_driver);
+}
+module_exit(aspeed_i2c_driver_exit);
+
+MODULE_AUTHOR("Brendan Higgins <brendanhiggins@google.com>");
+MODULE_DESCRIPTION("Aspeed I2C Bus Driver");
+MODULE_LICENSE("GPL");
-- 
2.8.0.rc3.226.g39d4020

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 1/2] i2c: aspeed: added driver for Aspeed I2C
@ 2016-09-10  1:55   ` Brendan Higgins
  0 siblings, 0 replies; 21+ messages in thread
From: Brendan Higgins @ 2016-09-10  1:55 UTC (permalink / raw)
  To: wsa-z923LK4zBo2bacvFa/9K2g, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	mark.rutland-5wv7dgnIgG8
  Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	openbmc-uLR06cmDAlY/bJ5BZ2RsiQ, joel-U3u1mxZcP9KHXe+LvDLADg,
	jk-mnsaURCQ41sdnm+yROfE0A, Brendan Higgins

Added initial master and slave support for Aspeed I2C controller.
Supports fourteen busses present in ast24xx and ast25xx BMC SoCs by
Aspeed.

Signed-off-by: Brendan Higgins <brendanhiggins-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
---
Changes for v2:
  - Added single module_init (multiple was breaking some builds).
---
 drivers/i2c/busses/Kconfig      |  10 +
 drivers/i2c/busses/Makefile     |   1 +
 drivers/i2c/busses/i2c-aspeed.c | 831 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 842 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-aspeed.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 5c3993b..0178c6c 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -998,6 +998,16 @@ config I2C_RCAR
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-rcar.
 
+config I2C_ASPEED
+	tristate "Aspeed AST2xxx SoC I2C Controller"
+	depends on (ARCH_ASPEED || COMPILE_TEST) && OF
+	help
+	  If you say yes to this option, support will be included for the
+	  Aspeed AST2xxx SoC I2C controller.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-aspeed.
+
 comment "External I2C/SMBus adapter drivers"
 
 config I2C_DIOLAN_U2C
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 37f2819..49631cd 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_I2C_XILINX)	+= i2c-xiic.o
 obj-$(CONFIG_I2C_XLR)		+= i2c-xlr.o
 obj-$(CONFIG_I2C_XLP9XX)	+= i2c-xlp9xx.o
 obj-$(CONFIG_I2C_RCAR)		+= i2c-rcar.o
+obj-$(CONFIG_I2C_ASPEED)	+= i2c-aspeed.o
 
 # External I2C/SMBus adapter drivers
 obj-$(CONFIG_I2C_DIOLAN_U2C)	+= i2c-diolan-u2c.o
diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
new file mode 100644
index 0000000..b19f13c
--- /dev/null
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -0,0 +1,831 @@
+/*
+ *  I2C adapter for the ASPEED I2C bus.
+ *
+ *  Copyright (C) 2012-2020  ASPEED Technology Inc.
+ *  Copyright 2016 IBM Corporation
+ *  Copyright 2016 Google, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+
+/* I2C Register */
+#define ASPEED_I2C_FUN_CTRL_REG				0x00
+#define ASPEED_I2C_AC_TIMING_REG1			0x04
+#define ASPEED_I2C_AC_TIMING_REG2			0x08
+#define ASPEED_I2C_INTR_CTRL_REG			0x0c
+#define ASPEED_I2C_INTR_STS_REG				0x10
+#define ASPEED_I2C_CMD_REG				0x14
+#define ASPEED_I2C_DEV_ADDR_REG				0x18
+#define ASPEED_I2C_BYTE_BUF_REG				0x20
+
+#define ASPEED_I2C_NUM_BUS 14
+
+/* Global Register Definition */
+/* 0x00 : I2C Interrupt Status Register  */
+/* 0x08 : I2C Interrupt Target Assignment  */
+
+/* Device Register Definition */
+/* 0x00 : I2CD Function Control Register  */
+#define ASPEED_I2CD_MULTI_MASTER_DIS			BIT(15)
+#define ASPEED_I2CD_SDA_DRIVE_1T_EN			BIT(8)
+#define ASPEED_I2CD_M_SDA_DRIVE_1T_EN			BIT(7)
+#define ASPEED_I2CD_M_HIGH_SPEED_EN			BIT(6)
+#define ASPEED_I2CD_SLAVE_EN				BIT(1)
+#define ASPEED_I2CD_MASTER_EN				BIT(0)
+
+/* 0x08 : I2CD Clock and AC Timing Control Register #2 */
+#define ASPEED_NO_TIMEOUT_CTRL				0
+
+
+/* 0x0c : I2CD Interrupt Control Register &
+ * 0x10 : I2CD Interrupt Status Register
+ *
+ * These share bit definitions, so use the same values for the enable &
+ * status bits.
+ */
+#define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT			BIT(14)
+#define ASPEED_I2CD_INTR_BUS_RECOVER_DONE		BIT(13)
+#define ASPEED_I2CD_INTR_SLAVE_MATCH			BIT(7)
+#define ASPEED_I2CD_INTR_SCL_TIMEOUT			BIT(6)
+#define ASPEED_I2CD_INTR_ABNORMAL			BIT(5)
+#define ASPEED_I2CD_INTR_NORMAL_STOP			BIT(4)
+#define ASPEED_I2CD_INTR_ARBIT_LOSS			BIT(3)
+#define ASPEED_I2CD_INTR_RX_DONE			BIT(2)
+#define ASPEED_I2CD_INTR_TX_NAK				BIT(1)
+#define ASPEED_I2CD_INTR_TX_ACK				BIT(0)
+
+/* 0x14 : I2CD Command/Status Register   */
+#define ASPEED_I2CD_SCL_LINE_STS			BIT(18)
+#define ASPEED_I2CD_SDA_LINE_STS			BIT(17)
+#define ASPEED_I2CD_BUS_BUSY_STS			BIT(16)
+#define ASPEED_I2CD_BUS_RECOVER_CMD			BIT(11)
+
+/* Command Bit */
+#define ASPEED_I2CD_M_STOP_CMD				BIT(5)
+#define ASPEED_I2CD_M_S_RX_CMD_LAST			BIT(4)
+#define ASPEED_I2CD_M_RX_CMD				BIT(3)
+#define ASPEED_I2CD_S_TX_CMD				BIT(2)
+#define ASPEED_I2CD_M_TX_CMD				BIT(1)
+#define ASPEED_I2CD_M_START_CMD				BIT(0)
+
+/* 0x18 : I2CD Slave Device Address Register   */
+#define ASPEED_I2CD_DEV_ADDR_MASK			GENMASK(6, 0)
+
+enum aspeed_i2c_slave_state {
+	ASPEED_I2C_SLAVE_START,
+	ASPEED_I2C_SLAVE_READ_REQUESTED,
+	ASPEED_I2C_SLAVE_READ_PROCESSED,
+	ASPEED_I2C_SLAVE_WRITE_REQUESTED,
+	ASPEED_I2C_SLAVE_WRITE_RECEIVED,
+	ASPEED_I2C_SLAVE_STOP,
+};
+
+struct aspeed_i2c_bus {
+	struct i2c_adapter		adap;
+	struct device			*dev;
+	void __iomem			*base;
+	spinlock_t			lock;
+	struct completion		cmd_complete;
+	int				irq;
+	/* Transaction state. */
+	struct i2c_msg			*msg;
+	int				msg_pos;
+	u32				cmd_err;
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	struct i2c_client		*slave;
+	enum aspeed_i2c_slave_state	slave_state;
+#endif
+};
+
+struct aspeed_i2c_controller {
+	struct device		*dev;
+	void __iomem		*base;
+	int			irq;
+	struct irq_domain	*irq_domain;
+};
+
+static inline void aspeed_i2c_write(struct aspeed_i2c_bus *bus, u32 val,
+				    u32 reg)
+{
+	writel(val, bus->base + reg);
+}
+
+static inline u32 aspeed_i2c_read(struct aspeed_i2c_bus *bus, u32 reg)
+{
+	return readl(bus->base + reg);
+}
+
+static u8 aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
+{
+	u32 command;
+	unsigned long time_left;
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
+	/* Bus is idle: no recovery needed. */
+	if ((command & ASPEED_I2CD_SDA_LINE_STS) &&
+	    (command & ASPEED_I2CD_SCL_LINE_STS))
+		goto out;
+
+	dev_dbg(bus->dev, "bus hung (state %x), attempting recovery\n",
+		command);
+
+	/* Bus held: put bus in stop state. */
+	if ((command & ASPEED_I2CD_SDA_LINE_STS) &&
+	    !(command & ASPEED_I2CD_SCL_LINE_STS)) {
+		aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
+				 ASPEED_I2C_CMD_REG);
+		reinit_completion(&bus->cmd_complete);
+		spin_unlock_irqrestore(&bus->lock, flags);
+
+		time_left = wait_for_completion_interruptible_timeout(
+				&bus->cmd_complete, bus->adap.timeout * HZ);
+
+		spin_lock_irqsave(&bus->lock, flags);
+		if (time_left == 0)
+			ret = -ETIMEDOUT;
+		else if (bus->cmd_err)
+			ret = -EIO;
+	/* Bus error. */
+	} else if (!(command & ASPEED_I2CD_SDA_LINE_STS)) {
+		aspeed_i2c_write(bus, ASPEED_I2CD_BUS_RECOVER_CMD,
+				 ASPEED_I2C_CMD_REG);
+		reinit_completion(&bus->cmd_complete);
+		spin_unlock_irqrestore(&bus->lock, flags);
+
+		time_left = wait_for_completion_interruptible_timeout(
+				&bus->cmd_complete, bus->adap.timeout * HZ);
+
+		spin_lock_irqsave(&bus->lock, flags);
+		if (time_left == 0)
+			ret = -ETIMEDOUT;
+		else if (bus->cmd_err)
+			ret = -EIO;
+		/* Recovery failed. */
+		else if (!(aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
+			   ASPEED_I2CD_SDA_LINE_STS))
+			ret = -EIO;
+	}
+
+out:
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return ret;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static bool aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus)
+{
+	bool irq_handled = true;
+	u32 command;
+	u32 irq_status;
+	u32 status_ack = 0;
+	u8 value;
+	struct i2c_client *slave = bus->slave;
+
+	spin_lock(&bus->lock);
+	if (!slave) {
+		irq_handled = false;
+		goto out;
+	}
+	command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
+	irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
+
+	/* Slave was requested, restart state machine. */
+	if (irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH) {
+		status_ack |= ASPEED_I2CD_INTR_SLAVE_MATCH;
+		bus->slave_state = ASPEED_I2C_SLAVE_START;
+	}
+	/* Slave is not currently active, irq was for someone else. */
+	if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
+		irq_handled = false;
+		goto out;
+	}
+
+	dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
+		irq_status, command);
+
+	/* Slave was sent something. */
+	if (irq_status & ASPEED_I2CD_INTR_RX_DONE) {
+		value = aspeed_i2c_read(bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
+		/* Handle address frame. */
+		if (bus->slave_state == ASPEED_I2C_SLAVE_START) {
+			if (value & 0x1)
+				bus->slave_state =
+						ASPEED_I2C_SLAVE_READ_REQUESTED;
+			else
+				bus->slave_state =
+						ASPEED_I2C_SLAVE_WRITE_REQUESTED;
+		}
+		status_ack |= ASPEED_I2CD_INTR_RX_DONE;
+	}
+
+	/* Slave was asked to stop. */
+	if (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) {
+		status_ack |= ASPEED_I2CD_INTR_NORMAL_STOP;
+		bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+	}
+	if (irq_status & ASPEED_I2CD_INTR_TX_NAK) {
+		status_ack |= ASPEED_I2CD_INTR_TX_NAK;
+		bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+	}
+
+	if (bus->slave_state == ASPEED_I2C_SLAVE_READ_REQUESTED) {
+		if (irq_status & ASPEED_I2CD_INTR_TX_ACK)
+			dev_err(bus->dev, "Unexpected ACK on read request.\n");
+		bus->slave_state = ASPEED_I2C_SLAVE_READ_PROCESSED;
+
+		i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value);
+		aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
+		aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_READ_PROCESSED) {
+		status_ack |= ASPEED_I2CD_INTR_TX_ACK;
+		if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK))
+			dev_err(bus->dev,
+				"Expected ACK after processed read.\n");
+		i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value);
+		aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
+		aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_REQUESTED) {
+		bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED;
+		i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED) {
+		i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value);
+	} else if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
+		i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
+	}
+
+	if (status_ack != irq_status)
+		dev_err(bus->dev,
+			"irq handled != irq. expected %x, but was %x\n",
+			irq_status, status_ack);
+	aspeed_i2c_write(bus, status_ack, ASPEED_I2C_INTR_STS_REG);
+
+out:
+	spin_unlock(&bus->lock);
+	return irq_handled;
+}
+#endif
+
+static bool aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus)
+{
+	const u32 errs = ASPEED_I2CD_INTR_ARBIT_LOSS |
+		ASPEED_I2CD_INTR_ABNORMAL |
+		ASPEED_I2CD_INTR_SCL_TIMEOUT |
+		ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |
+		ASPEED_I2CD_INTR_TX_NAK;
+	u32 irq_status;
+
+	spin_lock(&bus->lock);
+	irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
+	bus->cmd_err = irq_status & errs;
+
+	dev_dbg(bus->dev, "master irq status 0x%08x\n", irq_status);
+
+	/* No message to transfer. */
+	if (bus->cmd_err ||
+	    (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) ||
+	    (irq_status & ASPEED_I2CD_INTR_BUS_RECOVER_DONE)) {
+		complete(&bus->cmd_complete);
+		goto out;
+	} else if (!bus->msg || bus->msg_pos >= bus->msg->len)
+		goto out;
+
+	if ((bus->msg->flags & I2C_M_RD) &&
+	    (irq_status & ASPEED_I2CD_INTR_RX_DONE)) {
+		bus->msg->buf[bus->msg_pos++] = aspeed_i2c_read(
+				bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
+		if (bus->msg_pos + 1 < bus->msg->len)
+			aspeed_i2c_write(bus, ASPEED_I2CD_M_RX_CMD,
+					 ASPEED_I2C_CMD_REG);
+		else if (bus->msg_pos < bus->msg->len)
+			aspeed_i2c_write(bus, ASPEED_I2CD_M_RX_CMD |
+				      ASPEED_I2CD_M_S_RX_CMD_LAST,
+				      ASPEED_I2C_CMD_REG);
+	} else if (!(bus->msg->flags & I2C_M_RD) &&
+		   (irq_status & ASPEED_I2CD_INTR_TX_ACK)) {
+		aspeed_i2c_write(bus, bus->msg->buf[bus->msg_pos++],
+			      ASPEED_I2C_BYTE_BUF_REG);
+		aspeed_i2c_write(bus, ASPEED_I2CD_M_TX_CMD, ASPEED_I2C_CMD_REG);
+	}
+
+	/* Transmission complete: notify caller. */
+	if (bus->msg_pos >= bus->msg->len)
+		complete(&bus->cmd_complete);
+out:
+	aspeed_i2c_write(bus, irq_status, ASPEED_I2C_INTR_STS_REG);
+	spin_unlock(&bus->lock);
+	return true;
+}
+
+static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id)
+{
+	struct aspeed_i2c_bus *bus = dev_id;
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	if (aspeed_i2c_slave_irq(bus)) {
+		dev_dbg(bus->dev, "irq handled by slave.\n");
+		return IRQ_HANDLED;
+	}
+#endif
+	if (aspeed_i2c_master_irq(bus)) {
+		dev_dbg(bus->dev, "irq handled by master.\n");
+		return IRQ_HANDLED;
+	}
+	dev_err(bus->dev, "irq not handled properly!\n");
+	return IRQ_HANDLED;
+}
+
+static int aspeed_i2c_master_single_xfer(struct i2c_adapter *adap,
+				      struct i2c_msg *msg)
+{
+	struct aspeed_i2c_bus *bus = adap->algo_data;
+	unsigned long flags;
+	u8 slave_addr;
+	u32 command = ASPEED_I2CD_M_START_CMD | ASPEED_I2CD_M_TX_CMD;
+	int ret = msg->len;
+	unsigned long time_left;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	bus->msg = msg;
+	bus->msg_pos = 0;
+	slave_addr = msg->addr << 1;
+	if (msg->flags & I2C_M_RD) {
+		slave_addr |= 1;
+		command |= ASPEED_I2CD_M_RX_CMD;
+		if (msg->len == 1)
+			command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+	}
+	aspeed_i2c_write(bus, slave_addr, ASPEED_I2C_BYTE_BUF_REG);
+	aspeed_i2c_write(bus, command, ASPEED_I2C_CMD_REG);
+	reinit_completion(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	time_left = wait_for_completion_interruptible_timeout(
+			&bus->cmd_complete, bus->adap.timeout * HZ * msg->len);
+	if (time_left == 0)
+		return -ETIMEDOUT;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	if (bus->cmd_err)
+		ret = -EIO;
+	bus->msg = NULL;
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	return ret;
+}
+
+static int aspeed_i2c_master_xfer(struct i2c_adapter *adap,
+				  struct i2c_msg *msgs, int num)
+{
+	struct aspeed_i2c_bus *bus = adap->algo_data;
+	int ret;
+	int i;
+	unsigned long flags;
+	unsigned long time_left;
+
+	/* If bus is busy, attempt recovery. We assume a single master
+	 * environment.
+	 */
+	if (aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
+	    ASPEED_I2CD_BUS_BUSY_STS) {
+		ret = aspeed_i2c_recover_bus(bus);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < num; i++) {
+		ret = aspeed_i2c_master_single_xfer(adap, &msgs[i]);
+		if (ret < 0)
+			break;
+		/* TODO: Support other forms of I2C protocol mangling. */
+		if (msgs[i].flags & I2C_M_STOP) {
+			spin_lock_irqsave(&bus->lock, flags);
+			aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
+				      ASPEED_I2C_CMD_REG);
+			reinit_completion(&bus->cmd_complete);
+			spin_unlock_irqrestore(&bus->lock, flags);
+
+			time_left = wait_for_completion_interruptible_timeout(
+					&bus->cmd_complete,
+					bus->adap.timeout * HZ);
+			if (time_left == 0)
+				return -ETIMEDOUT;
+		}
+	}
+
+	spin_lock_irqsave(&bus->lock, flags);
+	aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD, ASPEED_I2C_CMD_REG);
+	reinit_completion(&bus->cmd_complete);
+	spin_unlock_irqrestore(&bus->lock, flags);
+
+	time_left = wait_for_completion_interruptible_timeout(
+			&bus->cmd_complete, bus->adap.timeout * HZ);
+	if (time_left == 0)
+		return -ETIMEDOUT;
+
+	/* If nothing went wrong, return number of messages transferred. */
+	if (ret < 0)
+		return ret;
+	else
+		return i;
+}
+
+static u32 aspeed_i2c_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static int aspeed_i2c_reg_slave(struct i2c_client *client)
+{
+	struct aspeed_i2c_bus *bus;
+	unsigned long flags;
+	u32 addr_reg_val;
+	u32 func_ctrl_reg_val;
+
+	bus = client->adapter->algo_data;
+	spin_lock_irqsave(&bus->lock, flags);
+	if (bus->slave) {
+		spin_unlock_irqrestore(&bus->lock, flags);
+		return -EINVAL;
+	}
+
+	/* Set slave addr. */
+	addr_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_DEV_ADDR_REG);
+	addr_reg_val &= ~ASPEED_I2CD_DEV_ADDR_MASK;
+	addr_reg_val |= client->addr & ASPEED_I2CD_DEV_ADDR_MASK;
+	aspeed_i2c_write(bus, addr_reg_val, ASPEED_I2C_DEV_ADDR_REG);
+
+	/* Switch from master mode to slave mode. */
+	func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
+	func_ctrl_reg_val &= ~ASPEED_I2CD_MASTER_EN;
+	func_ctrl_reg_val |= ASPEED_I2CD_SLAVE_EN;
+	aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
+
+	bus->slave = client;
+	bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return 0;
+}
+
+static int aspeed_i2c_unreg_slave(struct i2c_client *client)
+{
+	struct aspeed_i2c_bus *bus = client->adapter->algo_data;
+	unsigned long flags;
+	u32 func_ctrl_reg_val;
+
+	spin_lock_irqsave(&bus->lock, flags);
+	if (!bus->slave) {
+		spin_unlock_irqrestore(&bus->lock, flags);
+		return -EINVAL;
+	}
+
+	/* Switch from slave mode to master mode. */
+	func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
+	func_ctrl_reg_val &= ~ASPEED_I2CD_SLAVE_EN;
+	func_ctrl_reg_val |= ASPEED_I2CD_MASTER_EN;
+	aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
+
+	bus->slave = NULL;
+	spin_unlock_irqrestore(&bus->lock, flags);
+	return 0;
+}
+#endif
+
+static const struct i2c_algorithm aspeed_i2c_algo = {
+	.master_xfer	= aspeed_i2c_master_xfer,
+	.functionality	= aspeed_i2c_functionality,
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	.reg_slave	= aspeed_i2c_reg_slave,
+	.unreg_slave	= aspeed_i2c_unreg_slave,
+#endif
+};
+
+static u32 aspeed_i2c_get_clk_reg_val(u32 divider_ratio)
+{
+	unsigned int inc = 0, div;
+	u32 scl_low, scl_high, data;
+
+	for (div = 0; divider_ratio >= 16; div++) {
+		inc |= (divider_ratio & 1);
+		divider_ratio >>= 1;
+	}
+	divider_ratio += inc;
+	scl_low = (divider_ratio >> 1) - 1;
+	scl_high = divider_ratio - scl_low - 2;
+	data = 0x77700300 | (scl_high << 16) | (scl_low << 12) | div;
+	return data;
+}
+
+static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus,
+			    struct platform_device *pdev)
+{
+	struct clk *pclk;
+	u32 clk_freq;
+	u32 divider_ratio;
+	int ret;
+
+	pclk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(pclk)) {
+		dev_err(&pdev->dev, "clk_get failed\n");
+		return PTR_ERR(pclk);
+	}
+	ret = of_property_read_u32(pdev->dev.of_node,
+			"clock-frequency", &clk_freq);
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+				"Could not read clock-frequency property\n");
+		clk_freq = 100000;
+	}
+	divider_ratio = clk_get_rate(pclk) / clk_freq;
+	/* We just need the clock rate, we don't actually use the clk object. */
+	devm_clk_put(&pdev->dev, pclk);
+
+	/* Set AC Timing */
+	if (clk_freq / 1000 > 400) {
+		aspeed_i2c_write(bus, aspeed_i2c_read(bus,
+						      ASPEED_I2C_FUN_CTRL_REG) |
+				ASPEED_I2CD_M_HIGH_SPEED_EN |
+				ASPEED_I2CD_M_SDA_DRIVE_1T_EN |
+				ASPEED_I2CD_SDA_DRIVE_1T_EN,
+				ASPEED_I2C_FUN_CTRL_REG);
+
+		aspeed_i2c_write(bus, 0x3, ASPEED_I2C_AC_TIMING_REG2);
+		aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divider_ratio),
+			      ASPEED_I2C_AC_TIMING_REG1);
+	} else {
+		aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divider_ratio),
+			      ASPEED_I2C_AC_TIMING_REG1);
+		aspeed_i2c_write(bus, ASPEED_NO_TIMEOUT_CTRL,
+				 ASPEED_I2C_AC_TIMING_REG2);
+	}
+
+	return 0;
+}
+
+static void noop(struct irq_data *data) { }
+
+static struct irq_chip aspeed_i2c_irqchip = {
+	.name		= "ast-i2c",
+	.irq_unmask	= noop,
+	.irq_mask	= noop,
+};
+
+static int aspeed_i2c_probe_bus(struct platform_device *pdev)
+{
+	struct aspeed_i2c_bus *bus;
+	struct aspeed_i2c_controller *controller =
+			dev_get_drvdata(pdev->dev.parent);
+	struct resource *res;
+	int ret, bus_num, irq;
+
+	bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
+	if (!bus)
+		return -ENOMEM;
+
+	ret = of_property_read_u32(pdev->dev.of_node, "bus", &bus_num);
+	if (ret)
+		return -ENXIO;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	bus->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(bus->base))
+		return PTR_ERR(bus->base);
+
+	bus->irq = platform_get_irq(pdev, 0);
+	if (bus->irq < 0) {
+		dev_err(&pdev->dev, "platform_get_irq failed\n");
+		return -ENXIO;
+	}
+
+	irq = irq_create_mapping(controller->irq_domain, bus_num);
+	irq_set_chip_data(irq, controller);
+	irq_set_chip_and_handler(irq, &aspeed_i2c_irqchip, handle_simple_irq);
+	ret = devm_request_irq(&pdev->dev, bus->irq, aspeed_i2c_bus_irq,
+			0, dev_name(&pdev->dev), bus);
+	if (ret) {
+		dev_err(&pdev->dev, "devm_request_irq failed\n");
+		return -ENXIO;
+	}
+
+	/* Initialize the I2C adapter */
+	spin_lock_init(&bus->lock);
+	init_completion(&bus->cmd_complete);
+	bus->adap.nr = bus_num;
+	bus->adap.owner = THIS_MODULE;
+	bus->adap.retries = 0;
+	bus->adap.timeout = 5;
+	bus->adap.algo = &aspeed_i2c_algo;
+	bus->adap.algo_data = bus;
+	bus->adap.dev.parent = &pdev->dev;
+	bus->adap.dev.of_node = pdev->dev.of_node;
+	snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c-%d",
+			bus_num);
+
+	bus->dev = &pdev->dev;
+
+	/* reset device: disable master & slave functions */
+	aspeed_i2c_write(bus, 0, ASPEED_I2C_FUN_CTRL_REG);
+
+	ret = aspeed_i2c_init_clk(bus, pdev);
+	if (ret < 0)
+		return ret;
+
+	/* Enable Master Mode */
+	aspeed_i2c_write(bus, aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG) |
+		      ASPEED_I2CD_MASTER_EN |
+		      ASPEED_I2CD_MULTI_MASTER_DIS, ASPEED_I2C_FUN_CTRL_REG);
+
+	/* Set interrupt generation of I2C controller */
+	aspeed_i2c_write(bus, ASPEED_I2CD_INTR_SDA_DL_TIMEOUT |
+			ASPEED_I2CD_INTR_BUS_RECOVER_DONE |
+			ASPEED_I2CD_INTR_SCL_TIMEOUT |
+			ASPEED_I2CD_INTR_ABNORMAL |
+			ASPEED_I2CD_INTR_NORMAL_STOP |
+			ASPEED_I2CD_INTR_ARBIT_LOSS |
+			ASPEED_I2CD_INTR_RX_DONE |
+			ASPEED_I2CD_INTR_TX_NAK |
+			ASPEED_I2CD_INTR_TX_ACK,
+			ASPEED_I2C_INTR_CTRL_REG);
+
+	ret = i2c_add_numbered_adapter(&bus->adap);
+	if (ret < 0)
+		return -ENXIO;
+
+	platform_set_drvdata(pdev, bus);
+
+	dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
+			bus->adap.nr, bus->irq);
+
+	return 0;
+}
+
+static int aspeed_i2c_remove_bus(struct platform_device *pdev)
+{
+	struct aspeed_i2c_bus *bus = platform_get_drvdata(pdev);
+
+	i2c_del_adapter(&bus->adap);
+	return 0;
+}
+
+static const struct of_device_id aspeed_i2c_bus_of_table[] = {
+	{ .compatible = "aspeed,ast2400-i2c-bus", },
+	{ .compatible = "aspeed,ast2500-i2c-bus", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, aspeed_i2c_bus_of_table);
+
+static struct platform_driver aspeed_i2c_bus_driver = {
+	.probe		= aspeed_i2c_probe_bus,
+	.remove		= aspeed_i2c_remove_bus,
+	.driver		= {
+		.name		= "ast-i2c-bus",
+		.of_match_table	= aspeed_i2c_bus_of_table,
+	},
+};
+
+static void aspeed_i2c_controller_irq(struct irq_desc *desc)
+{
+	struct aspeed_i2c_controller *c = irq_desc_get_handler_data(desc);
+	unsigned long p, status;
+	unsigned int bus_irq;
+
+	status = readl(c->base);
+	for_each_set_bit(p, &status, ASPEED_I2C_NUM_BUS) {
+		bus_irq = irq_find_mapping(c->irq_domain, p);
+		generic_handle_irq(bus_irq);
+	}
+}
+
+static int aspeed_i2c_probe_controller(struct platform_device *pdev)
+{
+	struct aspeed_i2c_controller *controller;
+	struct device_node *np;
+	struct resource *res;
+
+	controller = kzalloc(sizeof(*controller), GFP_KERNEL);
+	if (!controller)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	controller->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(controller->base))
+		return PTR_ERR(controller->base);
+
+	controller->irq = platform_get_irq(pdev, 0);
+	if (controller->irq < 0) {
+		dev_err(&pdev->dev, "no platform IRQ\n");
+		return -ENXIO;
+	}
+
+	controller->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
+			ASPEED_I2C_NUM_BUS, &irq_domain_simple_ops, NULL);
+	if (!controller->irq_domain) {
+		dev_err(&pdev->dev, "no IRQ domain\n");
+		return -ENXIO;
+	}
+	controller->irq_domain->name = "ast-i2c-domain";
+
+	irq_set_chained_handler_and_data(controller->irq,
+			aspeed_i2c_controller_irq, controller);
+
+	controller->dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, controller);
+
+	dev_info(controller->dev, "i2c controller registered, irq %d\n",
+			controller->irq);
+
+	for_each_child_of_node(pdev->dev.of_node, np) {
+		int ret;
+		u32 bus_num;
+		char bus_id[sizeof("i2c-12345")];
+
+		/*
+		 * Set a useful name derived from the bus number; the device
+		 * tree should provide us with one that corresponds to the
+		 * hardware numbering.  If the property is missing the
+		 * probe would fail so just skip it here.
+		 */
+
+		ret = of_property_read_u32(np, "bus", &bus_num);
+		if (ret)
+			continue;
+
+		ret = snprintf(bus_id, sizeof(bus_id), "i2c-%u", bus_num);
+		if (ret >= sizeof(bus_id))
+			continue;
+
+		of_platform_device_create(np, bus_id, &pdev->dev);
+		of_node_put(np);
+	}
+
+	return 0;
+}
+
+static int aspeed_i2c_remove_controller(struct platform_device *pdev)
+{
+	struct aspeed_i2c_controller *controller = platform_get_drvdata(pdev);
+
+	irq_domain_remove(controller->irq_domain);
+	return 0;
+}
+
+static const struct of_device_id aspeed_i2c_controller_of_table[] = {
+	{ .compatible = "aspeed,ast2400-i2c-controller", },
+	{ .compatible = "aspeed,ast2500-i2c-controller", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, aspeed_i2c_controller_of_table);
+
+static struct platform_driver aspeed_i2c_controller_driver = {
+	.probe		= aspeed_i2c_probe_controller,
+	.remove		= aspeed_i2c_remove_controller,
+	.driver		= {
+		.name		= "ast-i2c-controller",
+		.of_match_table	= aspeed_i2c_controller_of_table,
+	},
+};
+
+static int __init aspeed_i2c_driver_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&aspeed_i2c_controller_driver);
+	if (ret < 0)
+		return ret;
+	return platform_driver_register(&aspeed_i2c_bus_driver);
+}
+module_init(aspeed_i2c_driver_init);
+
+static void __exit aspeed_i2c_driver_exit(void)
+{
+	platform_driver_unregister(&aspeed_i2c_bus_driver);
+	platform_driver_unregister(&aspeed_i2c_controller_driver);
+}
+module_exit(aspeed_i2c_driver_exit);
+
+MODULE_AUTHOR("Brendan Higgins <brendanhiggins-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>");
+MODULE_DESCRIPTION("Aspeed I2C Bus Driver");
+MODULE_LICENSE("GPL");
-- 
2.8.0.rc3.226.g39d4020

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2016-09-19 17:56 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-08-21  1:34 [PATCH 0/2] i2c: aspeed: ready driver for upstream Brendan Higgins
2016-08-21  1:34 ` [PATCH 1/2] eeprom: at24: added quirk for 24c08 devices Brendan Higgins
2016-08-29  5:05   ` Joel Stanley
2016-08-29 23:24   ` Xo Wang
2016-08-29 23:33     ` Brendan Higgins
2016-08-21  1:34 ` [PATCH 2/2] i2c: aspeed: cleanup driver Brendan Higgins
2016-08-29  5:35   ` Joel Stanley
2016-08-30 18:52     ` [PATCH v2] i2c: aspeed: added driver for Aspeed I2C Brendan Higgins
2016-09-07 23:54       ` Joel Stanley
2016-09-09  1:24         ` [PATCH v2 1/2] " Brendan Higgins
2016-09-09  1:24           ` Brendan Higgins
2016-09-09  2:57             ` Joel Stanley
2016-08-30 18:59     ` [PATCH 2/2] i2c: aspeed: cleanup driver Brendan Higgins
2016-08-30 21:44       ` Brendan Higgins
2016-08-30 22:15         ` Joel Stanley
2016-08-30 22:24           ` Brendan Higgins
2016-08-30 22:38             ` Joel Stanley
2016-09-10  0:48 [PATCH 1/2] i2c: aspeed: added driver for Aspeed I2C kbuild test robot
2016-09-10  1:55 ` [PATCH v2 " Brendan Higgins
2016-09-10  1:55   ` Brendan Higgins
2016-09-19 17:56   ` Brendan Higgins
2016-09-19 17:56     ` Brendan Higgins

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.