lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Fri,  7 Oct 2016 18:18:31 +0300
From:   Pantelis Antoniou <pantelis.antoniou@...sulko.com>
To:     Lee Jones <lee.jones@...aro.org>
Cc:     Linus Walleij <linus.walleij@...aro.org>,
        Alexandre Courbot <gnurou@...il.com>,
        Rob Herring <robh+dt@...nel.org>,
        Mark Rutland <mark.rutland@....com>,
        Frank Rowand <frowand.list@...il.com>,
        Wolfram Sang <wsa@...-dreams.de>,
        David Woodhouse <dwmw2@...radead.org>,
        Brian Norris <computersforpeace@...il.com>,
        Florian Fainelli <f.fainelli@...il.com>,
        Wim Van Sebroeck <wim@...ana.be>,
        Peter Rosin <peda@...ntia.se>,
        Debjit Ghosh <dghosh@...iper.net>,
        Georgi Vlaev <gvlaev@...iper.net>,
        Guenter Roeck <linux@...ck-us.net>,
        Maryam Seraj <mseraj@...iper.net>,
        Pantelis Antoniou <pantelis.antoniou@...sulko.com>,
        devicetree@...r.kernel.org, linux-kernel@...r.kernel.org,
        linux-gpio@...r.kernel.org, linux-i2c@...r.kernel.org,
        linux-mtd@...ts.infradead.org, linux-watchdog@...r.kernel.org,
        netdev@...r.kernel.org
Subject: [PATCH 03/10] i2c: Juniper SAM I2C driver

From: Maryam Seraj <mseraj@...iper.net>

Introduce SAM I2C driver for the I2C interfaces on the Juniper
SAM FPGA.

Signed-off-by: Maryam Seraj <mseraj@...iper.net>
Signed-off-by: Debjit Ghosh <dghosh@...iper.net>
Signed-off-by: Georgi Vlaev <gvlaev@...iper.net>
Signed-off-by: Guenter Roeck <groeck@...iper.net>
Signed-off-by: Rajat Jain <rajatjain@...iper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@...sulko.com>
---
 drivers/i2c/busses/Kconfig   |  11 +
 drivers/i2c/busses/Makefile  |   1 +
 drivers/i2c/busses/i2c-sam.c | 942 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 954 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-sam.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 5c3993b..eeac4b2 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -833,6 +833,17 @@ config I2C_SH7760
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-sh7760.
 
+config I2C_SAM
+	tristate "Juniper SAM FPGA I2C Controller"
+	select I2C_MUX
+	depends on MFD_JUNIPER_SAM || MFD_JUNIPER_CBC
+	help
+	  This driver supports the I2C interfaces on the Juniper SAM FPGA
+	  which is present on the relevant Juniper platforms.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-sam.
+
 config I2C_SH_MOBILE
 	tristate "SuperH Mobile I2C Controller"
 	depends on HAS_DMA
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 37f2819..b99b229 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -79,6 +79,7 @@ obj-$(CONFIG_I2C_QUP)		+= i2c-qup.o
 obj-$(CONFIG_I2C_RIIC)		+= i2c-riic.o
 obj-$(CONFIG_I2C_RK3X)		+= i2c-rk3x.o
 obj-$(CONFIG_I2C_S3C2410)	+= i2c-s3c2410.o
+obj-$(CONFIG_I2C_SAM)		+= i2c-sam.o
 obj-$(CONFIG_I2C_SH7760)	+= i2c-sh7760.o
 obj-$(CONFIG_I2C_SH_MOBILE)	+= i2c-sh_mobile.o
 obj-$(CONFIG_I2C_SIMTEC)	+= i2c-simtec.o
diff --git a/drivers/i2c/busses/i2c-sam.c b/drivers/i2c/busses/i2c-sam.c
new file mode 100644
index 0000000..1ec930a
--- /dev/null
+++ b/drivers/i2c/busses/i2c-sam.c
@@ -0,0 +1,942 @@
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/i2c-mux.h>
+#include <linux/mfd/sam.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+
+#define SAM_DEB1(dev, fmt, args...) do { \
+	if (sam_debug >= 1) \
+		dev_err(dev, fmt, ## args); \
+	} while (0)
+#define SAM_DEB2(dev, fmt, args...) do { \
+	if (sam_debug >= 2) \
+		dev_err(dev, fmt, ## args); \
+	} while (0)
+#define SAM_DEB3(dev, fmt, args...) do { \
+	if (sam_debug >= 3) \
+		dev_err(dev, fmt, ## args); } \
+	while (0)
+
+static int sam_debug;
+
+#define DRIVER_DESC			"SAM FPGA I2C Driver"
+#define DRIVER_VERSION			"0.2"
+#define DRIVER_AUTHOR			"Maryam Seraj <mseraj@...iper.net>"
+
+#define SAM_FPGA_MODULE_NAME		"i2c-sam"
+
+#define SAM_I2C_MUX_MAX_CHAN		8
+
+#define SAM_I2C_DEV_ADDR_MASK		0x7f
+#define SAM_I2C_TBL_ENTRY_CMDS_NUM	2
+
+#define SAM_I2C_CMD_TABLE_SZ		256
+
+#define SAM_I2C_STS_DONE		(1 << 0)
+#define SAM_I2C_STS_PRIO_DONE		(1 << 1)
+#define SAM_I2C_STS_RUNNING		(1 << 2)
+#define SAM_I2C_STS_PRIO_RUNNING	(1 << 3)
+#define SAM_I2C_STS_ERR			(1 << 4)
+#define SAM_I2C_STS_PRIO_ERR		(1 << 5)
+#define SAM_I2C_STS_RDY			(1 << 6)
+#define SAM_I2C_STS_TR_TIMEOUT		(1 << 7)
+#define SAM_I2C_STS_CMD_TIMEOUT		(1 << 8)
+#define SAM_I2C_STS_CMD_TABLE_TIMEOUT	(1 << 9)
+
+#define SAM_I2C_STS_CLEAR_MASK		(SAM_I2C_STS_DONE \
+					 | SAM_I2C_STS_PRIO_DONE \
+					 | SAM_I2C_STS_TR_TIMEOUT \
+					 | SAM_I2C_STS_CMD_TIMEOUT \
+					 | SAM_I2C_STS_CMD_TABLE_TIMEOUT \
+					 | SAM_I2C_STS_ERR \
+					 | SAM_I2C_STS_PRIO_ERR)
+#define SAM_I2C_STS_CLEAR(x)	(((x) & ~0x3fb3) | SAM_I2C_STS_CLEAR_MASK)
+
+#define SAM_I2C_STS_TIMEOUT		(SAM_I2C_STS_TR_TIMEOUT \
+					 | SAM_I2C_STS_CMD_TIMEOUT \
+					 | SAM_I2C_STS_CMD_TABLE_TIMEOUT)
+
+#define SAM_I2C_DONE(s)		((s) & (SAM_I2C_STS_DONE | SAM_I2C_STS_ERR \
+					| SAM_I2C_STS_TIMEOUT))
+
+#define SAM_I2C_CTRL_RESET		(1 << 0)
+#define SAM_I2C_CTRL_GO			(1 << 1)
+#define SAM_I2C_CTRL_PRIO_GO		(1 << 2)
+#define SAM_I2C_CTRL_ABORT		(1 << 3)
+#define SAM_I2C_CTRL_PRIO_ABORT		(1 << 4)
+
+/* Priority ctrl & status bits are offset +1 from the regular */
+#define STS_DONE(p)			BIT(0 + (p))
+#define STS_RUNNING(p)			BIT(2 + (p))
+#define STS_ERR(p)			BIT(4 + (p))
+#define CTRL_GO(p)			BIT(1 + (p))
+#define CTRL_ABORT(p)			BIT(3 + (p))
+#define STS_I2C_DONE(s, p)		((s) & (STS_DONE(p) | STS_ERR(p) \
+					 | SAM_I2C_STS_TIMEOUT))
+
+struct sam_i2c_data {
+	void __iomem *membase;
+	void __iomem *masterbase;
+	int first_master;
+	int num_master;
+	int mux_channels;
+	u32 *speed;		/* bit mask, 1 for high speed */
+	bool prio;
+	bool reverse_fill;
+	struct i2c_adapter **adap;
+	struct device *dev;
+	int irq;
+	struct sam_platform_data *pdata;
+};
+
+struct sam_i2c_adapdata {
+	void __iomem *membase;
+	void __iomem *masterbase;
+	int channel;
+	int mux_channels;
+	int mux_select;
+	u32 speed;			/* bit mask, same as above */
+	int prio;
+	bool reverse_fill;
+	struct i2c_adapter adap;
+	struct i2c_mux_core *muxc;
+	wait_queue_head_t wait;
+	u32 status;
+	u32 control;
+	bool done;
+	bool polling;
+};
+
+/**************************** i2c stuff *****************************/
+
+#define SAM_FPGA_MUX_NAME    "sam-fpga-mux"
+
+enum i2c_accel_cmd_bits_s {
+	I2C_WRITE,		/* 000 */
+	I2C_READ,		/* 001 */
+	I2C_WRITE_STOP,		/* 010 */
+	I2C_READ_STOP,		/* 011 */
+	I2C_WRITE_REPSTART,	/* 100 */
+	I2C_READ_REPSTART	/* 101 */
+};
+
+#define I2C_CMD_STOP_BIT	(1 << 1)
+#define I2C_CMD_REPSTART_BIT	(1 << 2)
+
+#define PER_MASTER_MEM		0x1000
+#define I2C_OPTIONS_BASE	0x2000
+#define MASTER_MEM_BASE		0x8000
+
+#define CMD_ADDR(s, idx)	((s)->masterbase + 0x0000 + \
+				 PER_MASTER_MEM * (s)->channel + \
+				 (idx) * sizeof(u32))
+#define RES_ADDR(s, idx)	((s)->masterbase + 0x0400 + \
+				 PER_MASTER_MEM * (s)->channel + \
+				 (idx) * sizeof(u32))
+#define CMD_PRI_ADDR(s, idx)	((s)->masterbase + 0x0800 + \
+				 PER_MASTER_MEM * (s)->channel + \
+				 (idx) * sizeof(u32))
+#define RES_PRI_ADDR(s, idx)	((s)->masterbase + 0x0c00 + \
+				 PER_MASTER_MEM * (s)->channel + \
+				 (idx) * sizeof(u32))
+#define CTRL_ADDR(s)		((s)->membase + 0x2400 + 8 * (s)->channel)
+#define STAT_ADDR(s)		((s)->membase + 0x2404 + 8 * (s)->channel)
+
+#define sam_i2c_stat_clear(adata, val)					\
+	do {								\
+		iowrite32(SAM_I2C_STS_CLEAR(val), STAT_ADDR(adata));	\
+		ioread32(STAT_ADDR(adata));				\
+	} while (0)
+
+static int sam_i2c_wait_rdy(struct i2c_adapter *adap)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	u32 val;
+	unsigned long timeout;
+
+	val = ioread32(STAT_ADDR(adata));
+	if ((val & SAM_I2C_STS_RDY) && !(val & STS_RUNNING(adata->prio)))
+		return 0;
+
+	if (val & STS_RUNNING(adata->prio)) {
+		iowrite32(adata->control | CTRL_ABORT(adata->prio),
+			  CTRL_ADDR(adata));
+		ioread32(CTRL_ADDR(adata));
+		udelay(10);
+		iowrite32(adata->control, CTRL_ADDR(adata));
+		ioread32(CTRL_ADDR(adata));
+		udelay(100);
+	}
+
+	timeout = jiffies + adap->timeout;
+	adata->status = 0;
+	do {
+		val = ioread32(STAT_ADDR(adata)) | adata->status;
+		if ((val & SAM_I2C_STS_RDY) &&
+		    !(val & STS_RUNNING(adata->prio)))
+			return 0;
+
+		if (adata->polling) {
+			udelay(50);
+		} else {
+			adata->done = false;
+			adata->status = 0;
+			wait_event_timeout(adata->wait, adata->done,
+					   adap->timeout);
+		}
+
+	} while (time_before(jiffies, timeout));
+
+	return -EBUSY;
+}
+
+int sam_i2c_calc_start_entry(struct i2c_msg *msgs, int num, int reverse_fill)
+{
+	int i, len = 0;
+
+	/* Filling the table from start (offset 0) ? */
+	if (!reverse_fill)
+		return 0;
+
+	/* Calculate required table size from the message sizes */
+	if (num == 1 && msgs[0].len == 0)
+		len = 1;
+	else
+		for (i = 0; i < num; i++)
+			if (msgs[i].flags & I2C_M_RECV_LEN)
+				len += (I2C_SMBUS_BLOCK_MAX + 1);
+			else
+				len += msgs[i].len;
+
+	if (len > SAM_I2C_CMD_TABLE_SZ * 2)
+		return -E2BIG;
+
+	/* Always start from Command 0 */
+	return (SAM_I2C_CMD_TABLE_SZ * 2 - len) / 2;
+}
+
+static int sam_i2c_cmd_init(struct i2c_adapter *adap, struct i2c_msg *msgs,
+			    int num)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	struct device *dev = &adap->dev;
+	struct i2c_msg *msg;
+	int curmsg, cmds = 0;
+	int addr, i, len = 0, table_offset = 0;
+	u32 cmd_entry;
+	bool read;
+	u8 group;
+	u32 idx;
+	u8 cmd;
+
+	group = adata->mux_select;
+	addr = msgs[0].addr & SAM_I2C_DEV_ADDR_MASK;
+	cmd_entry = (group << 29) | (addr << 22);
+
+	/* Zero CMD table */
+	memset_io(CMD_ADDR(adata, 0), 0, SAM_I2C_CMD_TABLE_SZ * sizeof(u32));
+
+	idx = sam_i2c_calc_start_entry(msgs, num, adata->reverse_fill);
+	if (idx < 0)
+		return idx;
+
+	table_offset = idx;
+
+	for (curmsg = 0; curmsg < num; curmsg++) {
+		msg = &msgs[curmsg];
+		read = msg->flags & I2C_M_RD;
+
+		SAM_DEB1(dev, "  [%02d] %s %d bytes addr %#02x\n",
+			 curmsg, read ? "RD" : "WR",
+			 msg->len, addr);
+
+		len = msg->len;
+		if (len == 0 && curmsg == 0 && num == 1) {
+			/*
+			 * SMBus quick command, special case
+			 *
+			 * Always read; we don't want to risk that
+			 * a "WRITE_STOP" command as only command
+			 * would actually write anything into the chip.
+			 */
+			cmd = I2C_READ_STOP;
+			cmd_entry |= cmd << 19;
+			cmds = 1;
+			break;
+		}
+		/*
+		 * If the message is a SMBus block read message, read up to the
+		 * maximum block length. The command should succeed at least up
+		 * to the real block length, which will be returned in the first
+		 * data byte.
+		 */
+		if (read && (msg->flags & I2C_M_RECV_LEN))
+			len = I2C_SMBUS_BLOCK_MAX + 1;
+		for (i = 0; i < len; i++) {
+			cmd = read ? I2C_READ : I2C_WRITE;
+			if (i == len - 1) {
+				if (curmsg == num - 1)
+					cmd |= I2C_CMD_STOP_BIT;
+				else
+					cmd |= I2C_CMD_REPSTART_BIT;
+			}
+
+			if ((cmds % SAM_I2C_TBL_ENTRY_CMDS_NUM) == 0) {
+				/* cmd0/data0 */
+				cmd_entry |= cmd << 19;
+				if (!read)
+					cmd_entry |= (msg->buf[i] << 11);
+			} else {
+				/* cmd1/data1 */
+				cmd_entry |= cmd << 8;
+				if (!read)
+					cmd_entry |= msg->buf[i];
+			}
+			cmds++;
+			if (cmds % SAM_I2C_TBL_ENTRY_CMDS_NUM == 0) {
+				/*
+				 * One command entry is done!
+				 * Write it to the command table and start
+				 * putting together the next entry for
+				 * the same current i2c command, if needed.
+				 */
+				SAM_DEB2(dev,
+					 "reg-offset cmd_entry = 0x%08x, cmds = %d, @ %p\n",
+					 cmd_entry, cmds, CMD_ADDR(adata, idx));
+
+				iowrite32(cmd_entry, CMD_ADDR(adata, idx));
+				ioread32(CMD_ADDR(adata, idx));
+				idx++;
+
+				/* clean out everything but group and address */
+				cmd_entry &= 0xFFC00000;
+			}
+		}
+	}
+	/*
+	 * Zero out any remaining cmd/data part of the last
+	 * command entry into the command table of the given
+	 * master before kicking off the i2c engine for this
+	 * master.
+	 */
+	if (cmds % SAM_I2C_TBL_ENTRY_CMDS_NUM != 0) {
+		cmd_entry &= 0xFFFFF800;
+
+		SAM_DEB1(dev, "rest of cmd_entry = 0x%08x, cmds = %d, @ %p\n",
+			 cmd_entry, cmds, CMD_ADDR(adata, idx));
+
+		if (idx >= SAM_I2C_CMD_TABLE_SZ)
+			return -E2BIG;
+
+		iowrite32(cmd_entry, CMD_ADDR(adata, idx));
+		ioread32(CMD_ADDR(adata, idx));
+		idx++;
+		if (idx < SAM_I2C_CMD_TABLE_SZ) {
+			iowrite32(0, CMD_ADDR(adata, idx));
+			ioread32(CMD_ADDR(adata, idx));
+		}
+	}
+	return table_offset;
+}
+
+static u32 i2c_sam_wait_results(struct i2c_adapter *adap)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	struct device *dev = &adap->dev;
+	u32 val;
+
+	if (adata->polling) {
+		unsigned long timeout = jiffies + adap->timeout;
+
+		/*
+		 * Poll for results.
+		 * Only wait a short time per loop to avoid long access times.
+		 * At 100kHz, a single byte transfer takes about 100 uS,
+		 * so we don't want to wait much longer than that.
+		 */
+		do {
+			/*
+			 * We should really use usleep_range() here, but that
+			 * does not work and causes the system to lock up.
+			 * msleep() is slow, so use an active wait loop instead.
+			 */
+			udelay(50);
+			val = ioread32(STAT_ADDR(adata));
+			SAM_DEB1(dev, "status = 0x%08x @%p\n",
+				 val, STAT_ADDR(adata));
+			if (STS_I2C_DONE(val, adata->prio))
+				break;
+		} while (time_before(jiffies, timeout));
+	} else {
+		if (!wait_event_timeout(adata->wait, adata->done,
+					adap->timeout))
+			val = ioread32(STAT_ADDR(adata));
+		else
+			val = ioread32(STAT_ADDR(adata)) | adata->status;
+	}
+
+	sam_i2c_stat_clear(adata, val);
+
+	return val;
+}
+
+static int sam_i2c_read_data(struct i2c_adapter *adap, struct i2c_msg *msgs,
+			     int num, int table_offset)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	int offset = table_offset * 2;
+	struct device *dev = &adap->dev;
+	struct i2c_msg *msg;
+	u32 val, data;
+	bool valid;
+	int i, len;
+
+	msg = &msgs[num - 1];
+	len = msg->len;
+
+	if (num > 1)
+		offset += msgs[0].len;
+
+	SAM_DEB1(dev, "Reading %d bytes\n", len);
+
+	for (i = offset & 0xfffffffe; i < len + offset; i++) {
+		val = ioread32(RES_ADDR(adata, i / 2));
+		SAM_DEB2(dev, "data = 0x%08x @%p\n",
+			 val, RES_ADDR(adata, i / 2));
+		if (i >= offset) {
+			data = (val >> 11) & 0xff;	/* data_0  */
+			valid = val & 0x00200000;	/* valid_0 */
+			if (!valid)
+				return -EIO;
+			msg->buf[i - offset] = data;
+			if (i == offset &&
+			    (msg->flags & I2C_M_RECV_LEN)) {
+				if (data == 0 ||
+				    data > I2C_SMBUS_BLOCK_MAX)
+					return -EPROTO;
+				SAM_DEB1(dev, "SMBus block data, %d bytes\n",
+					 data);
+				len += data;
+			}
+		}
+		if (++i >= len + offset)
+			break;
+		if (i >= offset) {
+			data = val & 0xff;		/* data_1  */
+			valid = val & 0x00000400;	/* valid_1 */
+			if (!valid)
+				return -EIO;
+			msg->buf[i - offset] = data;
+			if (i == offset &&
+			    (msg->flags & I2C_M_RECV_LEN)) {
+				if (data == 0 ||
+				    data > I2C_SMBUS_BLOCK_MAX)
+					return -EPROTO;
+				SAM_DEB1(dev, "SMBus block data, %d bytes\n",
+					 data);
+				len += data;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int sam_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	int ret, table_offset;
+	u32 val;
+
+	ret = sam_i2c_wait_rdy(adap);
+	if (ret < 0)
+		return ret;
+
+	ret = sam_i2c_cmd_init(adap, msgs, num);
+	if (ret < 0)
+		return ret;
+	table_offset = ret & 0xff;
+
+	sam_i2c_stat_clear(adata, ioread32(STAT_ADDR(adata)));
+
+	/*
+	 * Done with setting up the command table, now kick
+	 * off this transaction before waiting for the results.
+	 */
+
+	adata->done = false;
+	adata->status = 0;
+
+	iowrite32(adata->control | CTRL_GO(adata->prio) | table_offset << 24,
+		  CTRL_ADDR(adata));
+	ioread32(CTRL_ADDR(adata));	/* read back to flush */
+
+	val = i2c_sam_wait_results(adap);
+	if (val & STS_ERR(adata->prio)) {
+		dev_err(&adap->dev, "i2c transaction error\n");
+		return -EIO;
+	}
+	if ((val & SAM_I2C_STS_TIMEOUT) || !(val & STS_DONE(adata->prio))) {
+		SAM_DEB1(&adap->dev,
+			 "i2c transaction timeout, status=0x%x\n", val);
+		return -ETIMEDOUT;
+	}
+
+	SAM_DEB1(&adap->dev, "i2c transaction completed!!!\n");
+
+	/* SMBus quick command, special case */
+	if (num == 1 && msgs[0].len == 0) {
+		val = ioread32(RES_ADDR(adata, table_offset));
+		SAM_DEB1(&adap->dev, "quick cmd: data = 0x%08x\n", val);
+		return val & 0x00200000 ? 1 : -EIO;
+	}
+	/*
+	 * If this was a "read" request, go get the data.
+	 * Otherwise, we're done here!
+	 */
+	if (msgs[num - 1].flags & I2C_M_RD) {
+		ret = sam_i2c_read_data(adap, msgs, num, table_offset);
+		if (ret < 0)
+			return ret;
+	}
+
+	SAM_DEB1(&adap->dev, "Returning %d\n", num);
+	return num;
+}
+
+static u32 sam_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
+	  | I2C_FUNC_SMBUS_READ_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm sam_i2c_algo = {
+	.master_xfer = sam_i2c_xfer,
+	.functionality = sam_i2c_func,
+};
+
+/*
+ * This is where the SAM I2C-accel needs to be initialized.
+ */
+static int sam_i2c_init_adap(struct i2c_adapter *adap)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	u32 val;
+
+	SAM_DEB1(&adap->dev, "bus_mstr = %d\n", adata->channel);
+
+	val = 0x00000f00;
+	val |= (adata->speed << 12) & 0x0000f000;
+	adata->control = val;
+	/*
+	 * Set the i2c speed for ALL ports of this master and enable them all
+	 */
+	iowrite32(val, CTRL_ADDR(adata));
+
+	return 0;
+}
+
+int sam_i2c_add_numbered_bus(struct i2c_adapter *adap)
+{
+	int rval;
+
+	SAM_DEB1(&adap->dev, "%s", __func__);
+
+	rval = sam_i2c_init_adap(adap);
+	if (rval)
+		return rval;
+
+	return i2c_add_numbered_adapter(adap);
+}
+/********** end of i2c adapter/accel stuff ************************************/
+
+/********** start of i2c accel mux/group stuff ********************************/
+static int sam_i2c_mux_select(struct i2c_mux_core *muxc, u32 chan)
+{
+	struct sam_i2c_adapdata *adata = i2c_mux_priv(muxc);
+
+	if (!adata)
+		return -ENODEV;
+	adata->mux_select = chan;
+
+	return 0;
+}
+
+static int sam_i2c_mux_init(struct i2c_adapter *adap)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+	int chan, ret;
+
+	SAM_DEB1(&adap->dev, "%s Begin\n", __func__);
+
+	adata->muxc = i2c_mux_alloc(adap, &adap->dev, adata->mux_channels, 0, 0,
+				   sam_i2c_mux_select, NULL);
+	if (!adata->muxc)
+		return -ENOMEM;
+	adata->muxc->priv = adata;
+
+	for (chan = 0; chan < adata->mux_channels; chan++) {
+		ret = i2c_mux_add_adapter(adata->muxc, 0, chan, 0);
+		if (ret) {
+			dev_err(&adap->dev, "Failed to add adapter %d\n", chan);
+			i2c_mux_del_adapters(adata->muxc);
+			return ret;
+		}
+	}
+
+	SAM_DEB1(&adap->dev, "%s End\n", __func__);
+
+	return 0;
+}
+
+/********** end of i2c accel mux/group stuff **********************************/
+
+static void sam_core_of_init_options(struct device *dev,
+				     struct sam_i2c_data *sam)
+{
+	const __be32 *opt;
+	int len, i;
+
+	opt = of_get_property(dev->of_node, "i2c-options", &len);
+	if (!opt || len > 4 * sizeof(u32))
+		return;
+
+	for (i = 0; i < len; i += sizeof(u32))
+		iowrite32(be32_to_cpup(opt++),
+			  sam->membase + I2C_OPTIONS_BASE + i);
+}
+
+static int sam_core_of_init(struct device *dev, struct sam_i2c_data *sam,
+			    struct resource *res)
+{
+	int err;
+	int num_master, max_masters;
+	u32 speed, mux_channels, val, master_offset = MASTER_MEM_BASE;
+	int i, len;
+	const __be32 *bus_range, *regs;
+	struct device_node *child;
+	u32 master, mux;
+
+	num_master = ioread32(sam->membase + 0x0c);
+	sam->first_master = -1;
+
+	err = of_property_read_u32(dev->of_node, "master-offset", &val);
+	if (!err)
+		if (val + PER_MASTER_MEM <= resource_size(res)) {
+			master_offset = val;
+			dev_info(dev, "Master offset changed to 0x%08x", val);
+		}
+
+	sam->masterbase = sam->membase + master_offset;
+	max_masters = (resource_size(res) - master_offset) / PER_MASTER_MEM;
+
+	if (num_master <= 0 || num_master > max_masters)
+		return -EINVAL;
+	sam->num_master = num_master;
+
+	bus_range = of_get_property(dev->of_node, "i2c-bus-range", &len);
+	if (bus_range) {
+		if (len != 2 * sizeof(u32))
+			return -EINVAL;
+		sam->first_master = be32_to_cpu(bus_range[0]);
+		num_master = be32_to_cpu(bus_range[1]) - sam->first_master + 1;
+		if (num_master <= 0 || num_master > sam->num_master)
+			return -EINVAL;
+		sam->num_master = num_master;
+	}
+
+	sam->speed = devm_kcalloc(dev, sam->num_master, sizeof(u32),
+				  GFP_KERNEL);
+	if (!sam->speed)
+		return -ENOMEM;
+
+	sam->adap = devm_kcalloc(dev, sam->num_master,
+				 sizeof(struct i2c_adapter *), GFP_KERNEL);
+	if (!sam->adap)
+		return -ENOMEM;
+
+	/* Set default i2c speed to 100kHz for all channels */
+	for (i = 0; i < sam->num_master; i++)
+		sam->speed[i] = (1 << SAM_I2C_MUX_MAX_CHAN) - 1;
+
+	if (!dev->of_node) {
+		/*
+		 * Use default from platform data if the there is no FDT.
+		 * TODO: Use FDT once it is available
+		 */
+		sam->mux_channels = sam->pdata ?
+			sam->pdata->i2c_mux_channels : 2;
+		dev_warn(dev,
+			 "No FDT node for SAM, using default (%d)\n",
+			 sam->mux_channels);
+		return 0;
+	}
+
+	err = of_property_read_u32(dev->of_node, "mux-channels", &mux_channels);
+	if (err || !mux_channels || mux_channels > SAM_I2C_MUX_MAX_CHAN)
+		return -EINVAL;
+	sam->mux_channels = mux_channels;
+	sam->reverse_fill = of_property_read_bool(dev->of_node, "reverse-fill");
+	sam->prio = of_property_read_bool(dev->of_node, "priority-tables");
+	/* Priority tables are offset with 0x800 from the regular tables */
+	if (sam->prio)
+		sam->masterbase += 0x800;
+
+	for_each_child_of_node(dev->of_node, child) {
+		regs = of_get_property(child, "reg", &len);
+		if (!regs || len != 2 * sizeof(u32)) {
+			dev_err(dev, "did not find reg property or bad size\n");
+			return -EINVAL;
+		}
+		err = of_property_read_u32(child, "speed", &speed);
+		if (err || !speed)
+			continue;
+		if (speed != 100000 && speed != 400000) {
+			dev_err(dev, "Bad speed %d\n", speed);
+			return -EINVAL;
+		}
+		master = be32_to_cpu(regs[0]);
+		mux = be32_to_cpu(regs[1]);
+		if (master >= sam->num_master || mux >= sam->mux_channels) {
+			dev_err(dev,
+				"master/mux %d/%d out of range\n",
+				master, mux);
+			return -EINVAL;
+		}
+		if (speed == 400000)
+			sam->speed[master] &= ~(1 << mux);
+	}
+
+	sam_core_of_init_options(dev, sam);
+
+	return 0;
+}
+
+static struct i2c_adapter *sam_i2c_init_one(struct sam_i2c_data *sam,
+					    int channel)
+{
+	struct device *dev = sam->dev;
+	struct sam_i2c_adapdata *adata;
+	int err;
+
+	adata = devm_kzalloc(dev, sizeof(*adata), GFP_KERNEL);
+	if (!adata)
+		return ERR_PTR(-ENOMEM);
+
+	init_waitqueue_head(&adata->wait);
+	adata->adap.owner = THIS_MODULE;
+	adata->adap.algo = &sam_i2c_algo;
+	adata->adap.nr = (sam->first_master >= 0) ?
+			  sam->first_master + channel : -1;
+	adata->adap.timeout = HZ / 5;
+	adata->channel = channel;
+	adata->mux_channels = sam->mux_channels;
+	adata->membase = sam->membase;
+	adata->masterbase = sam->masterbase;
+	adata->speed = sam->speed[channel];
+	adata->polling = (sam->irq < 0);
+	adata->reverse_fill = sam->reverse_fill;
+	adata->prio = sam->prio & 1;
+	i2c_set_adapdata(&adata->adap, adata);
+	snprintf(adata->adap.name, sizeof(adata->adap.name),
+		 "%s:%d", dev_name(dev), channel);
+
+	adata->adap.dev.parent = dev;
+	err = sam_i2c_add_numbered_bus(&adata->adap);
+	if (err)
+		goto error;
+
+	err = sam_i2c_mux_init(&adata->adap);
+	if (err)
+		goto err_remove;
+
+	return &adata->adap;
+
+err_remove:
+	i2c_del_adapter(&adata->adap);
+error:
+	return ERR_PTR(err);
+}
+
+static void sam_i2c_cleanup_one(struct i2c_adapter *adap)
+{
+	struct sam_i2c_adapdata *adata = i2c_get_adapdata(adap);
+
+	i2c_mux_del_adapters(adata->muxc);
+	i2c_del_adapter(adap);
+}
+
+static irqreturn_t sam_i2c_irq_handler(int irq, void *data)
+{
+	struct sam_i2c_data *sam = data;
+	struct sam_platform_data *pdata = sam->pdata;
+	struct sam_i2c_adapdata *adata;
+	int bit;
+	u32 val, status, wake_status;
+	u32 mask = (1 << sam->num_master) - 1;
+
+	status = pdata->irq_status(sam->dev->parent, SAM_IRQ_I2C,
+				   sam->irq) & mask;
+	do {
+		wake_status = status;
+		/* Clear the 'done' bits */
+		while (status) {
+			bit = __ffs(status);
+			status &= ~BIT(bit);
+			adata = i2c_get_adapdata(sam->adap[bit]);
+			val = ioread32(STAT_ADDR(adata));
+			if (STS_I2C_DONE(val, adata->prio)) {
+				sam_i2c_stat_clear(adata, val);
+				if (!adata->done) {
+					adata->done = true;
+					adata->status = val;
+				}
+			}
+		}
+		/*
+		 * Clear the status bits *after* the done status is cleared,
+		 * as otherwise this will generate another unused interrupt.
+		 * On the CBC this will also clear the MSI INT_ACCELL.
+		 */
+		pdata->irq_status_clear(sam->dev->parent, SAM_IRQ_I2C, sam->irq,
+					wake_status);
+
+		/* Now wake the blocked transactions */
+		while (wake_status) {
+			bit = __ffs(wake_status);
+			wake_status &= ~BIT(bit);
+			adata = i2c_get_adapdata(sam->adap[bit]);
+			wake_up(&adata->wait);
+		}
+
+		/* Recheck the status again, as we might miss an MSI in
+		 * the window from the last check and the clear of the
+		 * pending interrupts. This does not affect the SAM INTx.
+		 */
+		status = pdata->irq_status(sam->dev->parent, SAM_IRQ_I2C,
+					   sam->irq) & mask;
+	} while (status);
+
+	return IRQ_HANDLED;
+}
+
+static const struct of_device_id sam_i2c_of_match[] = {
+	{ .compatible = "jnx,i2c-sam",},
+	{},
+};
+MODULE_DEVICE_TABLE(of, sam_i2c_of_match);
+
+
+static int sam_i2c_probe(struct platform_device *pdev)
+{
+	int err;
+	int i;
+	struct sam_i2c_data *sam;
+	struct i2c_adapter *adap;
+	struct resource *res;
+	struct device *dev = &pdev->dev;
+	struct sam_platform_data *pdata = dev_get_platdata(&pdev->dev);
+
+	/*
+	 * Allocate memory for the SAM FPGA info
+	 */
+	sam = devm_kzalloc(dev, sizeof(*sam), GFP_KERNEL);
+	if (!sam)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, sam);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	if (pdata)
+		sam->irq = platform_get_irq(pdev, 0);
+
+	sam->membase = devm_ioremap_nocache(dev, res->start,
+					    resource_size(res));
+	if (!sam->membase)
+		return -ENOMEM;
+
+	sam->dev = dev;
+	sam->pdata = pdata;
+
+	err = sam_core_of_init(dev, sam, res);
+	if (err)
+		return err;
+
+	/***** i2c init *****/
+
+	for (i = 0; i < sam->num_master; i++) {
+		adap = sam_i2c_init_one(sam, i);
+		if (IS_ERR(adap)) {
+			err = PTR_ERR(adap);
+			dev_err(dev,
+				"Failed to initialize adapter %d [base %d, index %d]: %d\n",
+				sam->first_master + i, sam->first_master, i,
+				err);
+			goto err_remove;
+		}
+		sam->adap[i] = adap;
+	}
+
+	if (sam->irq >= 0) {
+		err = devm_request_any_context_irq(dev, sam->irq,
+						sam_i2c_irq_handler, 0,
+						dev_name(dev), sam);
+		if (err < 0) {
+			dev_err(dev, "Failed to request interrupt %d: %d\n",
+				sam->irq, err);
+			goto err_remove;
+		}
+		pdata->enable_irq(dev->parent, SAM_IRQ_I2C, sam->irq,
+				  (1 << sam->num_master) - 1);
+	}
+
+	return 0;
+
+err_remove:
+	for (i--; i >= 0; i--)
+		sam_i2c_cleanup_one(sam->adap[i]);
+	return err;
+}
+
+static int sam_i2c_remove(struct platform_device *pdev)
+{
+	struct sam_i2c_data *sam = platform_get_drvdata(pdev);
+	struct sam_platform_data *pdata = sam->pdata;
+	int i;
+
+	if (sam->irq >= 0 && pdata)
+		pdata->disable_irq(pdev->dev.parent, SAM_IRQ_I2C, sam->irq,
+			(1 << sam->num_master) - 1);
+	for (i = 0; i < sam->num_master; i++)
+		sam_i2c_cleanup_one(sam->adap[i]);
+
+	return 0;
+}
+
+static struct platform_driver sam_i2c_driver = {
+	.driver = {
+		.name   = "i2c-sam",
+		.owner  = THIS_MODULE,
+		.of_match_table = sam_i2c_of_match,
+	},
+	.probe  = sam_i2c_probe,
+	.remove = sam_i2c_remove,
+};
+
+module_platform_driver(sam_i2c_driver);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+
+module_param(sam_debug, int, S_IWUSR | S_IRUGO);
-- 
1.9.1

Powered by blists - more mailing lists