Netdev List
 help / color / mirror / Atom feed
* [PATCH 04/10] i2c: i2c-sam: Add device tree bindings
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd, li
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add binding document for the i2c driver of SAM FPGA.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../devicetree/bindings/i2c/i2c-sam-mux.txt        | 20 ++++++++++
 Documentation/devicetree/bindings/i2c/i2c-sam.txt  | 44 ++++++++++++++++++++++
 2 files changed, 64 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sam.txt

diff --git a/Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt b/Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt
new file mode 100644
index 0000000..10ddffa
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt
@@ -0,0 +1,20 @@
+Juniper's SAM FPGA I2C accelerator mux
+
+The SAM FPGA I2C mux is present only on Juniper SAM FPGA PTX series
+of routers.
+
+The definition of the i2c sam bus is located in the i2c-sam.txt document.
+
+Required properties:
+- compatible: should be "jnx,i2c-sam-mux".
+- reg: master number and mux number.
+
+Optional properties:
+- speed: If present must be either 100000 or 400000. No other values supported.
+
+Examples:
+
+pe1i2c: i2c-sam-mux@1,0 {
+	compatible = "jnx,i2c-sam-mux";
+	reg = <1 0>;
+};
diff --git a/Documentation/devicetree/bindings/i2c/i2c-sam.txt b/Documentation/devicetree/bindings/i2c/i2c-sam.txt
new file mode 100644
index 0000000..4830b48
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-sam.txt
@@ -0,0 +1,44 @@
+Juniper's SAM FPGA I2C accelerator
+
+The SAM FPGA accelerator is used to connect the large number of
+I2C muxes that are present on Juniper PTX series of routers.
+While it's an i2c bus, no other devices are located besides
+i2c-sam-mux devices.
+
+The definition of the i2c sam mux is located in the i2c-sam-mux.txt document.
+
+Required properties:
+- compatible: should be "jnx,i2c-sam".
+- #address-cells: should be 2.
+- #size-cells: should be 0.
+- mux-channels: number of mux channels present
+
+Optional properties:
+- reg: offset and length of the register set for the device are optional since
+  typically the register range is provided by the parent SAM MFD device.
+- master-offset: Offset of where the master register memory starts.
+  Default value is 0x8000.
+- reverse-fill: Fill the start entries of transactions in reverse order
+- priority-tables: Use the pre-programmed priority tables in the FPGA
+- i2c-options: list of options to be written to the option field in the
+  FPGA controlling things like SCL push-pull drives, hold-times, etc.
+- bus-range: start of bus master range and number of masters.
+
+Examples:
+
+i2c-sam {
+	compatible = "jnx,i2c-sam";
+	mux-channels = <2>;
+	#size-cells = <0>;
+	#address-cells = <2>;
+
+	/* PE0 */ pe0i2c: i2c-sam-mux@0,0 {
+		compatible = "jnx,i2c-sam-mux";
+		reg = <0 0>;
+	};
+
+	/* PE1 */ pe1i2c: i2c-sam-mux@1,0 {
+		compatible = "jnx,i2c-sam-mux";
+		reg = <1 0>;
+	};
+};
-- 
1.9.1

^ permalink raw reply related

* [PATCH 03/10] i2c: Juniper SAM I2C driver
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, li
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>

From: Maryam Seraj <mseraj-3r7Miqu9kMnR7s880joybQ@public.gmane.org>

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

Signed-off-by: Maryam Seraj <mseraj-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Debjit Ghosh <dghosh-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Guenter Roeck <groeck-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Rajat Jain <rajatjain-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>
---
 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-3r7Miqu9kMnR7s880joybQ@public.gmane.org>"
+
+#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

--
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

* [PATCH 02/10] mfd: sam: Add documentation for the SAM FPGA
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, li
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>

From: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>

Add DT bindings document for the SAM MFD device.

Signed-off-by: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>
---
 Documentation/devicetree/bindings/mfd/jnx-sam.txt | 94 +++++++++++++++++++++++
 1 file changed, 94 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/jnx-sam.txt

diff --git a/Documentation/devicetree/bindings/mfd/jnx-sam.txt b/Documentation/devicetree/bindings/mfd/jnx-sam.txt
new file mode 100644
index 0000000..b4af7ea
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/jnx-sam.txt
@@ -0,0 +1,94 @@
+Device-Tree bindings for Juniper Networks SAM MFD
+
+Required properties:
+
+- compatible - Must be: "jnx,sam"
+
+Optional properties:
+
+- pma-coefficients: A set of tupples containing the configuration of the PMA.
+
+Device                   Description
+------                   -----------
+jnx,i2c-sam		: I2C mux driver
+jnx,gpio-sam		: GPIO block
+jnx,flash-sam		: MTD Flash
+jnx,mdio-sam		: MDIO interfaces
+
+All these optional nodes are described in their respective binding
+documents.
+
+Example node:
+
+pci-0000-10-00.0 {
+	compatible = "jnx,sam";
+	#address-cells = <1>;
+	#size-cells = <0>;
+	pma-coefficients = <4 0x0>;
+
+	i2c-sam@10 {
+		compatible = "jnx,i2c-sam";
+		mux-channels = <2>;
+		master-offset = <0x10000>;
+	};
+
+	gpiogpqam0: gpio-sam@10 {
+		compatible = "jnx,gpio-sam";
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-count = <297>;
+		interrupt-controller;
+		/*
+		* 1st cell: gpio interrupt status bit
+		* 2nd cell: 1st pin
+		* 3rd cell: # of pins
+		*/
+		gpio-interrupts =
+			<0 0 12>,   /* phy_int_monitor_en [16] */
+			<1 235 24>, /* qsfpp_fpga_int_monitor [17] */
+			<2 259 24>, /* qsfpp_fpga_modprs_monitor [18] */
+			<3 295 1>,  /* si5345_fpga_monitor [19] */
+			<4 294 1>;  /* fpc_pic_int_monitor [20] */
+	};
+
+	flash-sam@10 {
+		compatible = "jnx,flash-sam";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		partition@0 {
+		reg = <0x0 0x400000>;
+			label = "pic0-golden";
+			read-only;
+		};
+		partition@400000 {
+			reg = <0x400000 0x400000>;
+			label = "pic0-user";
+		};
+	};
+
+	mdio-sam@10 {
+		compatible = "jnx,mdio-sam";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		reg = <0x40000>;
+
+		/* mii_bus types */
+		mdio0: mdio-sam@0 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x0>;
+		};
+
+		mdio1: mdio-sam@4000 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x4000>;
+		};
+
+		mdio2: mdio-sam@8000 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x8000>;
+		};
+	};
+};
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-watchdog" 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

* [PATCH 01/10] mfd: Add Juniper SAM FPGA MFD driver
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	Wim Van Sebroeck, linux-mtd, linux-i2c, Frank Rowand,
	Alexandre Courbot, Florian Fainelli, Maryam Seraj, Guenter Roeck,
	devicetree, linux-watchdog, linux-gpio, Rob Herring, Debjit Ghosh,
	Georgi Vlaev, netdev, linux-kernel, Brian Norris, David Woodhouse,
	Peter Rosin
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Maryam Seraj <mseraj@juniper.net>

Add Juniper's SAM FPGA multi-function driver.

The SAM FPGAs are present on different FPC/SIB cards from the Juniper's
PTX series of routers. Depending on the card type and FPGA revision,
they include the following functional blocks:

* I2C SAM accelerator - multiple I2C masters and multiplexers
* GPIO
* Flash - hardware wrapper interface for the Altera's EPCS flashes
	(used for configuration flash updates)
* MDIO - multiple MDIO masters

Signed-off-by: Maryam Seraj <mseraj@juniper.net>
Signed-off-by: Debjit Ghosh <dghosh@juniper.net>
Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
Signed-off-by: Rajat Jain <rajatjain@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/mfd/Kconfig     |  16 +
 drivers/mfd/Makefile    |   1 +
 drivers/mfd/sam-core.c  | 997 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/sam.h |  30 ++
 4 files changed, 1044 insertions(+)
 create mode 100644 drivers/mfd/sam-core.c
 create mode 100644 include/linux/mfd/sam.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 438666a..75b46a1 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1355,6 +1355,22 @@ config MFD_JUNIPER_CPLD
 	  This driver can be built as a module. If built as a module it will be
 	  called "ptxpmb-cpld"
 
+config MFD_JUNIPER_SAM
+	tristate "Juniper SAM FPGA"
+	depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
+	default y if (PTXPMB_COMMON || JNX_PTX_NGPMB)
+	select MFD_CORE
+	select I2C_SAM
+	select GPIO_SAM
+	select MTD_SAM_FLASH
+	select MDIO_SAM
+	help
+	  Select this to enable the SAM FPGA multi-function kernel driver.
+	  This FPGA is used on the PTX FPC board.
+
+	  This driver can be built as a module. If built as a module it will be
+	  called "sam-core"
+
 config MFD_TWL4030_AUDIO
 	bool "TI TWL4030 Audio"
 	depends on TWL4030_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 62decc9..71a8ba6 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -149,6 +149,7 @@ obj-$(CONFIG_AB3100_OTP)	+= ab3100-otp.o
 obj-$(CONFIG_AB8500_DEBUG)	+= ab8500-debugfs.o
 obj-$(CONFIG_AB8500_GPADC)	+= ab8500-gpadc.o
 obj-$(CONFIG_MFD_JUNIPER_CPLD)	+= ptxpmb-cpld-core.o
+obj-$(CONFIG_MFD_JUNIPER_SAM)	+= sam-core.o
 obj-$(CONFIG_MFD_DB8500_PRCMU)	+= db8500-prcmu.o
 # ab8500-core need to come after db8500-prcmu (which provides the channel)
 obj-$(CONFIG_AB8500_CORE)	+= ab8500-core.o ab8500-sysctrl.o
diff --git a/drivers/mfd/sam-core.c b/drivers/mfd/sam-core.c
new file mode 100644
index 0000000..2ea2b1b
--- /dev/null
+++ b/drivers/mfd/sam-core.c
@@ -0,0 +1,997 @@
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/sam.h>
+#include <linux/jnx/pci_ids.h>
+#include <linux/of.h>
+
+#define DRIVER_DESC			"SAM FPGA MFD core driver"
+#define DRIVER_VERSION			"0.02.2"
+#define DRIVER_AUTHOR			"Maryam Seraj <mseraj@juniper.net>"
+
+#define SAM_FPGA_MODULE_NAME		"sam-mfd-core"
+#define FPGA_MEM_SIZE			0x20000
+
+#define SAM_NUM_IRQ			2
+#define SAM_NUM_MFD_CELLS		3
+#define SAM_NUM_RESOURCES		2
+#define SAM_NUM_RESOURCES_NOIRQ		1
+
+/* Minimum SAM revisions needed for i2c irq and PMA support */
+#define SAM_REVISION_I2C_IRQ_MIN	0x0065
+#define SAM_REVISION_PMA_MIN		0x004d
+#define SAM_REVISION_MASK		0x0000ffff
+#define SAM_REVISION(s)			((s)->fpga_rev & SAM_REVISION_MASK)
+
+#define SAM_BOARD_ID_MASK		0x000000ff
+#define SAM_BOARD_ID(s)			((s)->board_id & SAM_BOARD_ID_MASK)
+#define SAM_BOARD_ID_HENDRICKS_FPC	0x00
+#define SAM_BOARD_ID_CFP4		0x00
+#define SAM_BOARD_ID_QSFPP		0x00
+#define SAM_BOARD_ID_GPCAM		0x01
+#define SAM_BOARD_ID_SANGRIA_FPC	0x03
+#define SAM_BOARD_ID_QSFPP_OLD		0x03
+#define SAM_BOARD_ID_24x10GE_PIC	0x0B
+#define SAM_BOARD_ID_GLADIATOR_3T	0x11
+#define SAM_BOARD_ID_MLC		0x21
+
+#define SAM_IMG_ID_MASK			0x000000ff
+#define SAM_IMG_ID_SHIFT		16
+#define SAM_IMG_ID(s)			(((s)->board_id >> SAM_IMG_ID_SHIFT) & \
+					 SAM_IMG_ID_MASK)
+
+#define SAM_IMG_ID_QSFPP		0x00
+#define SAM_IMG_ID_GPCAM		0x01
+#define SAM_IMG_ID_SANGRIA_FPC		0x03
+#define SAM_IMG_ID_QSFPP_OLD		0x03
+#define SAM_IMG_ID_GLADIATOR_3T		0x03
+#define SAM_IMG_ID_HENDRICKS_FPC	0x05
+#define SAM_IMG_ID_24x10GE_PIC		0x0B
+#define SAM_IMG_ID_MLC			0x21
+#define SAM_IMG_ID_CFP4			0x22
+
+#define SANGRIA_FPC_PCIE_BUS		0x20
+
+struct sam_fpga_data {
+	void __iomem *membase;
+	struct pci_dev *pdev;
+	u32 fpga_rev;
+	u32 board_id;
+	u32 pma_lanes;
+	u32 pma_coefficients;
+	int irq_base;
+	u32 i2c_irq_mask;
+	u32 gpio_irq_mask;
+	int gpio_irq_shift;
+	spinlock_t irq_lock;
+	struct mfd_cell mfd_cells[SAM_NUM_MFD_CELLS];
+	struct resource mfd_i2c_resources[SAM_NUM_RESOURCES];
+	struct resource mfd_gpio_resources[SAM_NUM_RESOURCES];
+	struct resource mfd_mtd_resources[SAM_NUM_RESOURCES_NOIRQ];
+};
+
+#define VERSION_ADDR(s)		((s)->membase + 0x000)
+#define BOARD_ID_ADDR(s)	((s)->membase + 0x004)
+#define ICTRL_ADDR(s)		((s)->membase + 0x104)
+#define ISTAT_ADDR(s)		((s)->membase + 0x108)
+
+/* PMA */
+#define SAM_PMA_CONTROL_REG(s)	((s)->membase + 0x40)
+#define SAM_PMA_STATUS_REG(s)	((s)->membase + 0x44)
+
+#define SAM_PMA_CONTROL_WRITE	(1 << 31)
+#define SAM_PMA_CONTROL_READ	(1 << 30)
+#define SAM_PMA_LANE(lane)	(((lane) & 0x07) << 25)
+#define SAM_PMA_COEFF_MASK	((1 << 25) - 1)
+
+#define SAM_PMA_STATUS_BUSY	(1 << 31)
+#define SAM_PMA_STATUS_VALID	(1 << 30)
+
+#define SAM_PMA_RETRIES		40	/* observed to take 20 - 40 uS typ. */
+#define SAM_PMA_WAIT_TIME	10	/* uS */
+
+/* Constants used for FPGA upgrades */
+
+#define SAM_FPGA_FLASH_VALID_BIT		0xA5A5A5A5
+#define SAM_FPGA_FLASH_VALID_BIT_ADDR		0x7F0000
+
+/* FPGA remote upgrade registers */
+#define SAM_FPGA_REMOTE_UPGRADE_TRIG_BIT	0x08000000
+#define SAM_FPGA_REMOTE_UPGRADE_STATUS_BUSY	0x01000000
+#define SAM_FPGA_REMOTE_UPGRADE_READ_PARAM	0x80000000
+#define SAM_FPGA_REMOTE_UPGRADE_WRITE_PARAM	0x40000000
+#define SAM_FPGA_REMOTE_UPGRADE_CONTROL_RESET	0x10000000
+
+#define SAM_FPGA_REMOTE_UPGRADE_PAGE_SEL	(0x04 << 24)
+#define SAM_FPGA_REMOTE_UPGRADE_ANF		(0x05 << 24)
+#define SAM_FPGA_USER_IMAGE_BASE		0x400000
+
+#define SAM_FLASH_BASE				0x0300
+
+#define FLASH_ADDR_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x000)
+#define FLASH_COUNTER_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x004)
+#define FLASH_CONTROL_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x008)
+#define FLASH_STATUS_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x00c)
+#define FLASH_WRITE_DATA_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x100)
+#define FLASH_READ_DATA_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x200)
+
+#define FLASH_STATUS_BUSY	0x01
+
+#define SAM_FLASH_IF_CONTROL_READ	0x00000001
+
+/* Upgrade control and management */
+#define SAM_UPGRADE_BASE	0x0200
+#define UPGRADE_CONTROL_REG(x)	((x)->membase + SAM_UPGRADE_BASE)
+#define UPGRADE_STATUS_REG(x)	((x)->membase + SAM_UPGRADE_BASE + 0x0004)
+
+/* List of discovered SAM devices */
+struct sam_core_list {
+	struct list_head node;
+	unsigned long insertion_time;
+	int bus;
+	int devfn;
+	u32 rev;
+	u32 id;
+};
+
+LIST_HEAD(sam_core_list);
+DEFINE_MUTEX(sam_core_list_mutex);
+
+static int sam_core_update_entry(struct sam_fpga_data *sam)
+{
+	struct sam_core_list *entry = NULL, *e;
+	int ret = 0;
+
+	mutex_lock(&sam_core_list_mutex);
+	list_for_each_entry(e, &sam_core_list, node) {
+		if (e->devfn == sam->pdev->devfn &&
+		    e->bus == sam->pdev->bus->number) {
+			entry = e;
+			break;
+		}
+	}
+
+	if (!entry) {
+		entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+		if (!entry) {
+			ret = -ENOMEM;
+			goto abort;
+		}
+		list_add(&entry->node, &sam_core_list);
+	}
+
+	entry->bus = sam->pdev->bus->number;
+	entry->devfn = sam->pdev->devfn;
+	entry->rev = sam->fpga_rev;
+	entry->id = sam->board_id;
+	entry->insertion_time = jiffies;
+abort:
+	mutex_unlock(&sam_core_list_mutex);
+	return ret;
+}
+
+static bool sam_supports_i2c_irq(struct sam_fpga_data *sam)
+{
+	switch (sam->pdev->device) {
+	case PCI_DEVICE_ID_JNX_SAM_OMEGA:
+		/* Sochu SHAM, Gladiator SIB, Omega SIB */
+		return true;
+	case PCI_DEVICE_ID_JNX_SAM_X:
+		/* Gladiator 3T FPC */
+		return true;
+	case PCI_DEVICE_ID_JNX_PAM:
+		if (SAM_REVISION(sam) >= SAM_REVISION_I2C_IRQ_MIN)
+			return true;
+		return false;
+	case PCI_DEVICE_ID_JNX_SAM:
+	default:
+		/* others depend on image/board ID and FPGA version */
+		break;
+	}
+
+	switch (SAM_IMG_ID(sam)) {
+	case SAM_IMG_ID_QSFPP:
+		/* QSFPP, GPQAM */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_QSFPP)
+			return true;
+		break;
+	case SAM_IMG_ID_GPCAM:
+		/* GPCAM, GPQ28 */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_GPCAM)
+			return true;
+		break;
+	case SAM_IMG_ID_HENDRICKS_FPC:
+		/* Hendricks SAM FPGA version 15 still fails */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_HENDRICKS_FPC)
+			return false;
+		break;
+	case SAM_IMG_ID_24x10GE_PIC:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC &&
+		    SAM_REVISION(sam) >= SAM_REVISION_I2C_IRQ_MIN)
+			return true;
+		break;
+	case SAM_IMG_ID_SANGRIA_FPC:
+		/*
+		 * Image and board IDs for Sangria FPC and QSFPP are the same.
+		 * Use PCIe bus number for disambiguation.
+		 * Bus number for QSFPP would be 0x10 or 0x30 (PIC bus numbers).
+		 */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC &&
+		    SAM_REVISION(sam) >= SAM_REVISION_I2C_IRQ_MIN &&
+		    sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)
+			return true;
+		break;
+	default:
+		/*
+		 * For all others, play safe and assume that i2c interrupts
+		 * don't work.
+		 */
+		break;
+	}
+	return false;
+}
+
+static bool sam_supports_msi(struct sam_fpga_data *sam)
+{
+	switch (sam->pdev->device) {
+	case PCI_DEVICE_ID_JNX_SAM_OMEGA:
+		/* Sochu SHAM, Gladiator SIB, Omega SIB */
+		return true;
+	case PCI_DEVICE_ID_JNX_SAM_X:
+		/* Gladiator 3T FPC */
+		return true;
+	case PCI_DEVICE_ID_JNX_PAM:
+		/* unknown */
+		return false;
+	case PCI_DEVICE_ID_JNX_SAM:
+	default:
+		break;
+	}
+
+	switch (SAM_IMG_ID(sam)) {
+	case SAM_IMG_ID_HENDRICKS_FPC:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_HENDRICKS_FPC)
+			return false;
+		break;
+	case SAM_IMG_ID_24x10GE_PIC:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC)
+			return false;
+		break;
+	case SAM_IMG_ID_SANGRIA_FPC:
+		/*
+		 * Image and board IDs for Sangria FPC and QSFPP are the same.
+		 * Use PCIe bus number for disambiguation.
+		 */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC
+		    && sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool sam_supports_pma(struct sam_fpga_data *sam)
+{
+	switch (sam->pdev->device) {
+	case PCI_DEVICE_ID_JNX_SAM_OMEGA:
+		/* Sochu SHAM, Gladiator SIB, Omega SIB */
+		/* Note: marked HW use only on SHAM */
+		return true;
+	case PCI_DEVICE_ID_JNX_SAM_X:
+		/* Gladiator 3T FPC */
+		return true;
+	case PCI_DEVICE_ID_JNX_PAM:
+		return false;
+	case PCI_DEVICE_ID_JNX_SAM:
+		break;
+	default:
+		/* play safe */
+		return false;
+	}
+
+	switch (SAM_IMG_ID(sam)) {
+	case SAM_IMG_ID_QSFPP:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_QSFPP)
+			return true;
+		break;
+	case SAM_IMG_ID_CFP4:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_CFP4)
+			return true;
+		break;
+	case SAM_IMG_ID_HENDRICKS_FPC:
+		break;
+	case SAM_IMG_ID_24x10GE_PIC:
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC &&
+		    SAM_REVISION(sam) >= SAM_REVISION_PMA_MIN)
+			return true;
+		break;
+	case SAM_IMG_ID_SANGRIA_FPC:
+		/*
+		 * Image and board IDs for Sangria FPC and QSFPP (old)
+		 * are the same. Use PCIe bus number for disambiguation.
+		 */
+		if (SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC &&
+		    SAM_REVISION(sam) >= SAM_REVISION_PMA_MIN &&
+		    sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)
+			return true;
+		break;
+	default:
+		/* unknown, play safe */
+		break;
+	}
+
+	return false;
+}
+
+/*
+ * Flash access commands (simplified)
+ */
+static bool sam_flash_busy(struct sam_fpga_data *sam)
+{
+	return ioread32(FLASH_STATUS_REG(sam)) & FLASH_STATUS_BUSY;
+}
+
+static int
+sam_flash_read(struct sam_fpga_data *sam, u_int32_t offset, u32 *data)
+{
+	if (sam_flash_busy(sam))
+		return -ETIMEDOUT;
+
+	iowrite32(sizeof(u32) - 1, FLASH_COUNTER_REG(sam));
+	iowrite32(offset, FLASH_ADDR_REG(sam));
+
+	/* trigger the read */
+	iowrite32(SAM_FLASH_IF_CONTROL_READ, FLASH_CONTROL_REG(sam));
+	ioread32(FLASH_CONTROL_REG(sam));
+
+	udelay(50);
+
+	if (sam_flash_busy(sam))
+		return  -ETIMEDOUT;
+
+	*data = ioread32(FLASH_READ_DATA_REG(sam));
+	return 0;
+}
+
+static u32 sam_irq_mask(struct sam_fpga_data *sam, enum sam_irq_type type,
+			u32 mask)
+{
+	switch (type) {
+	case SAM_IRQ_I2C:
+		mask &= sam->i2c_irq_mask;
+		break;
+	case SAM_IRQ_GPIO:
+		mask <<= sam->gpio_irq_shift;
+		mask &= sam->gpio_irq_mask;
+		break;
+	}
+	return mask;
+}
+
+static void sam_enable_irq(struct device *dev, enum sam_irq_type type, int irq,
+			   u32 mask)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+	unsigned long flags;
+	u32 s;
+
+	/* irq is one of the virqs passed to the driver */
+
+	mask = sam_irq_mask(sam, type, mask);
+
+	spin_lock_irqsave(&sam->irq_lock, flags);
+	s = ioread32(ICTRL_ADDR(sam));
+	s |= mask;
+	iowrite32(s, ICTRL_ADDR(sam));
+	ioread32(ICTRL_ADDR(sam));
+	spin_unlock_irqrestore(&sam->irq_lock, flags);
+}
+
+static void sam_disable_irq(struct device *dev, enum sam_irq_type type, int irq,
+			    u32 mask)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+	unsigned long flags;
+	u32 s;
+
+	mask = sam_irq_mask(sam, type, mask);
+
+	spin_lock_irqsave(&sam->irq_lock, flags);
+	s = ioread32(ICTRL_ADDR(sam));
+	s &= ~mask;
+	iowrite32(s, ICTRL_ADDR(sam));
+	ioread32(ICTRL_ADDR(sam));
+	spin_unlock_irqrestore(&sam->irq_lock, flags);
+}
+
+static u32 sam_irq_status(struct device *dev, enum sam_irq_type type, int irq)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+	u32 status;
+
+	status = ioread32(ISTAT_ADDR(sam));
+
+	switch (type) {
+	case SAM_IRQ_I2C:
+		status &= sam->i2c_irq_mask;
+		break;
+	case SAM_IRQ_GPIO:
+		status &= sam->gpio_irq_mask;
+		status >>= sam->gpio_irq_shift;
+		break;
+	}
+	return status;
+}
+
+static void sam_irq_status_clear(struct device *dev, enum sam_irq_type type,
+				 int irq, u32 mask)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+
+	mask = sam_irq_mask(sam, type, mask);
+
+	iowrite32(mask, ISTAT_ADDR(sam));
+	ioread32(ISTAT_ADDR(sam));
+}
+
+static irqreturn_t sam_irq_handler(int irq, void *data)
+{
+	struct sam_fpga_data *sam = data;
+	u32 status, mask;
+	int ret = IRQ_NONE;
+
+	/*
+	 * Shared interrupt handlers are called from the interrupt release
+	 * function, so be careful not to access already released resources.
+	 */
+	if (sam->irq_base) {
+		mask = ioread32(ICTRL_ADDR(sam));
+		status = ioread32(ISTAT_ADDR(sam));
+		status &= mask;
+		if (status & sam->i2c_irq_mask) {	/* i2c interrupt */
+			handle_nested_irq(sam->irq_base);
+			ret = IRQ_HANDLED;
+		}
+		if (status & sam->gpio_irq_mask) {	/* gpio interrupt */
+			handle_nested_irq(sam->irq_base + 1);
+			ret = IRQ_HANDLED;
+		}
+	}
+	return ret;
+}
+
+static int sam_irq_set_affinity(struct irq_data *d,
+				const struct cpumask *affinity, bool force)
+{
+	return 0;
+}
+
+static void noop(struct irq_data *data) { }
+
+static struct irq_chip sam_irq_chip = {
+	.name = "sam-core",
+	.irq_mask = noop,
+	.irq_unmask = noop,
+	.irq_set_affinity = sam_irq_set_affinity,
+};
+
+static int sam_attach_irq(struct sam_fpga_data *sam)
+{
+	int irq_base = irq_alloc_descs(-1, 0, SAM_NUM_IRQ, 0);
+	int irq;
+
+	if (irq_base < 0)
+		return irq_base;
+
+	for (irq = irq_base; irq < irq_base + SAM_NUM_IRQ; irq++) {
+		irq_set_noprobe(irq);
+		irq_set_chip_and_handler(irq, &sam_irq_chip, handle_level_irq);
+		irq_set_chip_data(irq, sam);
+		irq_set_status_flags(irq, IRQ_LEVEL);
+		irq_clear_status_flags(irq, IRQ_NOREQUEST);
+		irq_set_nested_thread(irq, true);
+	}
+	if (sam_supports_i2c_irq(sam)) {
+		sam->mfd_i2c_resources[1].start
+		  = sam->mfd_i2c_resources[1].end = irq_base;
+		sam->mfd_cells[0].num_resources = SAM_NUM_RESOURCES;
+	}
+	sam->mfd_gpio_resources[1].start = sam->mfd_gpio_resources[1].end
+	  = irq_base + 1;
+	sam->mfd_cells[1].num_resources = SAM_NUM_RESOURCES;
+	sam->irq_base = irq_base;
+
+	return 0;
+}
+
+static void sam_detach_irq(struct sam_fpga_data *sam)
+{
+	int irq, irq_base = sam->irq_base;
+
+	if (irq_base) {
+		sam->irq_base = 0;
+		for (irq = irq_base; irq < irq_base + SAM_NUM_IRQ; irq++) {
+			irq_set_handler_data(irq, NULL);
+			irq_set_chip(irq, NULL);
+			irq_set_chip_data(irq, NULL);
+		}
+		irq_free_descs(irq_base, SAM_NUM_IRQ);
+	}
+}
+
+static int sam_fpga_load_wait(struct sam_fpga_data *sam)
+{
+	unsigned long start = jiffies;
+	unsigned long timeout = start + msecs_to_jiffies(2000);
+
+	/* wait for up to two seconds for the command to complete */
+	do {
+		u32 status = ioread32(UPGRADE_STATUS_REG(sam));
+
+		if (!(status & SAM_FPGA_REMOTE_UPGRADE_STATUS_BUSY))
+			return 0;
+
+		usleep_range(500, 1000);
+	} while (time_before(jiffies, timeout));
+
+	return -ETIMEDOUT;
+}
+
+/*
+ * FPGA image download
+ */
+static int sam_fpga_load_image(struct device *dev, struct sam_fpga_data *sam)
+{
+	int ret;
+	u32 valid;
+	struct sam_core_list *entry;
+
+	/*
+	 * If the node exists, we have seen this device before.
+	 * Don't try to re-load it again unless the FPGA version changed,
+	 * a different board was inserted, or the board was inserted
+	 * more than a minute ago.
+	 * This check is necessary to ensure that we don't end up
+	 * in endless attempts to re-load SAM.
+	 */
+	mutex_lock(&sam_core_list_mutex);
+	list_for_each_entry(entry, &sam_core_list, node) {
+		if (entry->devfn == sam->pdev->devfn &&
+		    entry->bus == sam->pdev->bus->number &&
+		    entry->id == sam->board_id &&
+		    entry->rev == sam->fpga_rev &&
+		    time_before(entry->insertion_time, jiffies + HZ * 60)) {
+			mutex_unlock(&sam_core_list_mutex);
+			return 0;
+		}
+	}
+	mutex_unlock(&sam_core_list_mutex);
+
+	ret = sam_flash_read(sam, SAM_FPGA_FLASH_VALID_BIT_ADDR, &valid);
+	if (ret < 0 || valid != SAM_FPGA_FLASH_VALID_BIT)
+		return 0;
+
+	/* reset state machine and request upgrade */
+	iowrite32(SAM_FPGA_REMOTE_UPGRADE_CONTROL_RESET,
+		  UPGRADE_CONTROL_REG(sam));
+	usleep_range(10000, 20000);
+
+	iowrite32(SAM_FPGA_REMOTE_UPGRADE_WRITE_PARAM |
+		  SAM_FPGA_REMOTE_UPGRADE_PAGE_SEL |
+		  SAM_FPGA_USER_IMAGE_BASE,
+		  UPGRADE_CONTROL_REG(sam));
+	ioread32(UPGRADE_CONTROL_REG(sam));
+
+	ret = sam_fpga_load_wait(sam);
+	if (ret)
+		return 0;
+
+#if 0
+	/*
+	 * Request fallback to golden image if upgrade fails
+	 * Commented out in Sangria code, kept for reference
+	 */
+	iowrite32(SAM_FPGA_REMOTE_UPGRADE_WRITE_PARAM |
+		  SAM_FPGA_REMOTE_UPGRADE_ANF | 1,
+		  UPGRADE_CONTROL_REG(sam));
+
+	ret = sam_fpga_load_wait(sam);
+	if (ret)
+		return ret;
+#endif
+
+	/* Trigger reconfiguration */
+	iowrite32(SAM_FPGA_REMOTE_UPGRADE_TRIG_BIT, UPGRADE_CONTROL_REG(sam));
+
+	sam_core_update_entry(sam);
+
+	/*
+	 * With a clean infrastructure, we could return -EPROBEDEFER here and
+	 * leave it up to the PCIe hotplug driver to detect that the device has
+	 * been removed and re-inserted. Without such a driver, user space
+	 * will have to take care of it.
+	 */
+	return -ENODEV;
+}
+
+static ssize_t version_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+
+	return sprintf(buf, "0x%x\n", sam->fpga_rev);
+}
+
+static ssize_t board_id_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct sam_fpga_data *sam = dev_get_drvdata(dev);
+
+	return sprintf(buf, "0x%x\n", sam->board_id);
+}
+
+static DEVICE_ATTR(version, S_IRUGO, version_show, NULL);
+static DEVICE_ATTR(board_id, S_IRUGO, board_id_show, NULL);
+
+/* Initialize PMA coefficients */
+
+static int sam_pma_wait(struct sam_fpga_data *sam)
+{
+	int i;
+	u32 status;
+
+	for (i = 0; i < SAM_PMA_RETRIES; i++) {
+		udelay(SAM_PMA_WAIT_TIME);
+		status = ioread32(SAM_PMA_STATUS_REG(sam));
+		if (!(status & SAM_PMA_STATUS_BUSY))
+			return (status & SAM_PMA_STATUS_VALID) ? 0 : -EIO;
+	}
+	return -ETIMEDOUT;
+}
+
+static int sam_pma_write(struct sam_fpga_data *sam, u32 data, void *addr)
+{
+	iowrite32(data, addr);
+	ioread32(addr);
+	return sam_pma_wait(sam);
+}
+
+static int sam_pma_init(struct sam_fpga_data *sam)
+{
+	int lane;
+	int err;
+
+	for (lane = 0; lane < sam->pma_lanes; lane++) {
+		err = sam_pma_write(sam,
+				    SAM_PMA_CONTROL_READ | SAM_PMA_LANE(lane),
+				    SAM_PMA_CONTROL_REG(sam));
+		if (err)
+			return err;
+		err = sam_pma_write(sam,
+				    SAM_PMA_CONTROL_WRITE | SAM_PMA_LANE(lane) |
+				    sam->pma_coefficients,
+				    SAM_PMA_CONTROL_REG(sam));
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+static int sam_fpga_of_init(struct device *dev, struct sam_fpga_data *sam)
+{
+	int len;
+	const __be32 *pma_coefficients;
+
+	if (!dev->of_node) {
+		dev_warn(dev, "No SAM FDT node\n");
+		return of_have_populated_dt() ? -ENODEV : 0;
+	}
+
+	pma_coefficients = of_get_property(of_get_parent(dev->of_node),
+					   "pma-coefficients", &len);
+	if (pma_coefficients) {
+		if (len != 2 * sizeof(u32))
+			return -EINVAL;
+		sam->pma_lanes = be32_to_cpu(pma_coefficients[0]);
+		sam->pma_coefficients = be32_to_cpu(pma_coefficients[1]);
+
+		if (sam->pma_lanes > 7 ||
+		    (sam->pma_coefficients & ~SAM_PMA_COEFF_MASK))
+			return -EINVAL;
+	}
+	return 0;
+}
+
+/* SAM drivers interrupt handling */
+static struct sam_platform_data sam_plat_data = {
+	.enable_irq = sam_enable_irq,
+	.disable_irq = sam_disable_irq,
+	.irq_status = sam_irq_status,
+	.irq_status_clear = sam_irq_status_clear,
+	.i2c_mux_channels = 2,		/* MLC default */
+};
+
+/* Add a single cell from OF */
+static int sam_mfd_of_add_cell(struct device *dev, const char *compatible,
+				int id, struct resource *base)
+{
+	struct device_node *np;
+	struct mfd_cell cell = {0};
+	struct resource res = {0};
+	u32 reg;
+	int ret;
+
+	/* Note:
+	 * Due to limitations in the MFD core, we can't have more
+	 * than one compatible node - we can't match properly and bind
+	 * the of_nodes to mfd cells.
+	 */
+	np = of_find_compatible_node(dev->of_node, NULL, compatible);
+	if (!np)
+		return -ENODEV;
+
+	ret = of_property_read_u32(np, "reg", &reg);
+	if (ret)
+		return ret;
+
+	res.start = reg;
+	res.end = resource_size(base) - 1 - reg;
+	res.flags = IORESOURCE_MEM;
+	cell.name = np->name;
+	cell.of_compatible = compatible;
+	cell.resources = &res;
+	cell.num_resources = 1;
+
+	return mfd_add_devices(dev, id, &cell, 1, base, 0, NULL);
+}
+
+static void sam_init_irq_masks(struct sam_fpga_data *sam)
+{
+	if ((SAM_IMG_ID(sam) == SAM_IMG_ID_HENDRICKS_FPC &&
+	     SAM_BOARD_ID(sam) == SAM_BOARD_ID_HENDRICKS_FPC) ||
+	    (SAM_IMG_ID(sam) == SAM_IMG_ID_24x10GE_PIC &&
+	     SAM_BOARD_ID(sam) == SAM_BOARD_ID_24x10GE_PIC) ||
+	    (SAM_IMG_ID(sam) == SAM_IMG_ID_SANGRIA_FPC &&
+	     SAM_BOARD_ID(sam) == SAM_BOARD_ID_SANGRIA_FPC &&
+	     sam->pdev->bus->number == SANGRIA_FPC_PCIE_BUS)) {
+		sam->i2c_irq_mask = 0x000000ff;
+		sam->gpio_irq_mask = 0x1ffff000;
+		sam->gpio_irq_shift = 12;
+	} else {
+		sam->i2c_irq_mask = 0x0000ffff;
+		sam->gpio_irq_mask = 0xffff0000;
+		sam->gpio_irq_shift = 16;
+	}
+}
+
+static int sam_fpga_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	int err;
+	struct sam_fpga_data *sam;
+	struct device *dev = &pdev->dev;
+
+	sam = devm_kzalloc(dev, sizeof(*sam), GFP_KERNEL);
+	if (sam == NULL)
+		return -ENOMEM;
+
+	err = sam_fpga_of_init(dev, sam);
+	if (err < 0)
+		return err;
+
+	err = pci_enable_device(pdev);
+	if (err < 0) {
+		dev_err(dev, "pci_enable_device() failed: %d\n", err);
+		return err;
+	}
+
+	err = pci_request_regions(pdev, SAM_FPGA_MODULE_NAME);
+	if (err < 0) {
+		dev_err(dev, "pci_request_regions() failed: %d\n",
+			err);
+		goto err_disable;
+	}
+
+	sam->membase = pci_ioremap_bar(pdev, 0);
+	if (!sam->membase) {
+		dev_err(dev, "pci_ioremap_bar() failed\n");
+		err = -ENOMEM;
+		goto err_release;
+	}
+
+	sam->pdev = pdev;
+	pci_set_drvdata(pdev, sam);
+
+	/*
+	 * Try to upgrade FPGA image to user image if revision is too old
+	 * to support I2C interrupts
+	 */
+	sam->fpga_rev = ioread32(VERSION_ADDR(sam));
+	sam->board_id = ioread32(BOARD_ID_ADDR(sam));
+
+	if (!sam_supports_i2c_irq(sam)) {
+		dev_info(dev,
+			 "FPGA revision %u.%u doesn't support I2C interrupts, attempting firmware download\n",
+			 (sam->fpga_rev >> 8) & 0xff, sam->fpga_rev & 0xff);
+		err = sam_fpga_load_image(dev, sam);
+		if (err)
+			goto err_unmap;
+	}
+
+	if (sam_supports_pma(sam) && sam->pma_lanes) {
+		err = sam_pma_init(sam);
+		if (err < 0)
+			goto err_unmap;
+	}
+
+	sam_init_irq_masks(sam);
+
+	spin_lock_init(&sam->irq_lock);
+
+	sam->mfd_cells[0].name = "i2c-sam";
+	sam->mfd_cells[0].num_resources = SAM_NUM_RESOURCES_NOIRQ;
+	sam->mfd_cells[0].resources = sam->mfd_i2c_resources;
+	sam->mfd_cells[0].of_compatible = "jnx,i2c-sam";
+	sam->mfd_cells[0].platform_data = &sam_plat_data;
+	sam->mfd_cells[0].pdata_size = sizeof(sam_plat_data);
+
+	sam->mfd_i2c_resources[0].end = FPGA_MEM_SIZE - 1;
+	sam->mfd_i2c_resources[0].flags = IORESOURCE_MEM;
+	sam->mfd_i2c_resources[1].flags = IORESOURCE_IRQ;
+
+	sam->mfd_cells[1].name = "gpio-sam";
+	sam->mfd_cells[1].num_resources = SAM_NUM_RESOURCES_NOIRQ;
+	sam->mfd_cells[1].resources = sam->mfd_gpio_resources;
+	sam->mfd_cells[1].of_compatible = "jnx,gpio-sam";
+	sam->mfd_cells[1].platform_data = &sam_plat_data;
+	sam->mfd_cells[1].pdata_size = sizeof(sam_plat_data);
+
+	sam->mfd_gpio_resources[0].end = FPGA_MEM_SIZE - 1;
+	sam->mfd_gpio_resources[0].flags = IORESOURCE_MEM;
+	sam->mfd_gpio_resources[1].flags = IORESOURCE_IRQ;
+
+	sam->mfd_cells[2].name = "flash-sam";
+	sam->mfd_cells[2].num_resources = SAM_NUM_RESOURCES_NOIRQ;
+	sam->mfd_cells[2].resources = sam->mfd_mtd_resources;
+	sam->mfd_cells[2].of_compatible = "jnx,flash-sam";
+
+	sam->mfd_mtd_resources[0].end = FPGA_MEM_SIZE - 1;
+	sam->mfd_mtd_resources[0].flags = IORESOURCE_MEM;
+
+	/* Enable MSI, if it is supported by this version of SAM */
+	if (sam_supports_msi(sam) && !pci_enable_msi(pdev))
+		pci_set_master(pdev);
+
+	if (pdev->irq) {
+		err = devm_request_threaded_irq(dev, pdev->irq, NULL,
+						sam_irq_handler,
+						IRQF_ONESHOT,
+						dev_driver_string(dev), sam);
+		if (err) {
+			dev_err(dev, "failed to request irq %d\n", pdev->irq);
+			goto err_unmap;
+		}
+		err = sam_attach_irq(sam);
+		if (err) {
+			dev_err(dev, "failed to attach irq %d\n", pdev->irq);
+			goto err_irq;
+		}
+	}
+
+	err = mfd_add_devices(dev, pdev->bus->number, sam->mfd_cells,
+			      ARRAY_SIZE(sam->mfd_cells), &pdev->resource[0],
+			      0, NULL);
+	if (err < 0)
+		goto err_irq_attach;
+
+	/*
+	 * We don't know if this SAM supports MDIO.
+	 * Add client only if compatible node exists.
+	 */
+	sam_mfd_of_add_cell(dev, "jnx,mdio-sam", pdev->bus->number,
+				&pdev->resource[0]);
+
+	err = device_create_file(&pdev->dev, &dev_attr_version);
+	if (err < 0)
+		goto err_remove;
+
+	err = device_create_file(&pdev->dev, &dev_attr_board_id);
+	if (err < 0)
+		goto err_remove_files;
+
+	dev_info(dev,
+		 "SAM Jspec version %u.%u FPGA version %u.%u image 0x%x board 0x%x inserted\n",
+		 (sam->fpga_rev >> 24) & 0xff, (sam->fpga_rev >> 16) & 0xff,
+		 (sam->fpga_rev >> 8) & 0xff, sam->fpga_rev & 0xff,
+		 (sam->board_id >> 16) & 0xff,
+		 sam->board_id & 0xff);
+
+	return 0;
+
+err_remove_files:
+	device_remove_file(&pdev->dev, &dev_attr_version);
+	device_remove_file(&pdev->dev, &dev_attr_board_id);
+err_remove:
+	mfd_remove_devices(dev);
+err_irq_attach:
+	if (pdev->irq)
+		sam_detach_irq(sam);
+err_irq:
+	/* Call free_irq() before pci_disable_msi() */
+	if (pdev->irq)
+		devm_free_irq(&pdev->dev, pdev->irq, sam);
+err_unmap:
+	pci_disable_msi(pdev);
+	pci_iounmap(pdev, sam->membase);
+err_release:
+	pci_release_regions(pdev);
+err_disable:
+	pci_disable_device(pdev);
+	return err;
+}
+
+static void sam_fpga_remove(struct pci_dev *pdev)
+{
+	struct sam_fpga_data *sam = pci_get_drvdata(pdev);
+
+	mfd_remove_devices(&pdev->dev);
+	device_remove_file(&pdev->dev, &dev_attr_version);
+	device_remove_file(&pdev->dev, &dev_attr_board_id);
+	sam_disable_irq(&pdev->dev, SAM_IRQ_I2C, sam->irq_base, 0xffffffff);
+	sam_disable_irq(&pdev->dev, SAM_IRQ_GPIO, sam->irq_base, 0xffffffff);
+	if (pdev->irq) {
+		sam_detach_irq(sam);
+		devm_free_irq(&pdev->dev, pdev->irq, sam);
+	}
+	pci_disable_msi(pdev);
+	pci_iounmap(pdev, sam->membase);
+	pci_release_regions(pdev);
+	pci_disable_device(pdev);
+}
+
+static struct pci_device_id sam_fpga_ids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_SAM) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_SAM_X) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_SAM_OMEGA) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_JUNIPER, PCI_DEVICE_ID_JNX_PAM) },
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, sam_fpga_ids);
+
+static struct pci_driver sam_fpga_driver = {
+	.name = SAM_FPGA_MODULE_NAME,
+	.id_table = sam_fpga_ids,
+	.probe = sam_fpga_probe,
+	.remove = sam_fpga_remove,
+};
+
+static int __init sam_fpga_init(void)
+{
+	pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
+
+	return pci_register_driver(&sam_fpga_driver);
+}
+
+static void __exit sam_fpga_exit(void)
+{
+	struct sam_core_list *entry, *t;
+
+	list_for_each_entry_safe(entry, t, &sam_core_list, node) {
+		list_del(&entry->node);
+		kfree(entry);
+	}
+	pci_unregister_driver(&sam_fpga_driver);
+}
+
+module_init(sam_fpga_init);
+module_exit(sam_fpga_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR(DRIVER_AUTHOR);
diff --git a/include/linux/mfd/sam.h b/include/linux/mfd/sam.h
new file mode 100644
index 0000000..d41b9fb
--- /dev/null
+++ b/include/linux/mfd/sam.h
@@ -0,0 +1,30 @@
+/*
+ * Functions exported from SAM mfd driver
+ *     Copyright (c) 2013 Juniper Networks <groeck@juniper.net>
+ *
+ * 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.
+ */
+
+#ifndef SAM_H
+#define SAM_H
+
+struct device;
+
+enum sam_irq_type {
+	SAM_IRQ_I2C = 0,
+	SAM_IRQ_GPIO,
+};
+
+struct sam_platform_data {
+	void (*enable_irq)(struct device *dev, enum sam_irq_type, int irq,
+			   u32 mask);
+	void (*disable_irq)(struct device *dev, enum sam_irq_type, int irq,
+			    u32 mask);
+	u32 (*irq_status)(struct device *dev, enum sam_irq_type, int irq);
+	void (*irq_status_clear)(struct device *dev, enum sam_irq_type, int irq,
+				 u32 mask);
+	int i2c_mux_channels;
+};
+#endif /* SAM_H */
-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply related

* [PATCH 00/10] Introduce Juniper SAM FPGA driver
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Mark Rutland, Wolfram Sang, Linus Walleij, Pantelis Antoniou,
	Wim Van Sebroeck, linux-mtd, linux-i2c, Frank Rowand,
	Alexandre Courbot, Florian Fainelli, Maryam Seraj, Guenter Roeck,
	devicetree, linux-watchdog, linux-gpio, Rob Herring, Debjit Ghosh,
	Georgi Vlaev, netdev, linux-kernel, Brian Norris, David Woodhouse,
	Peter Rosin

Add Juniper's SAM FPGA driver. Those FPGAs
are present in Juniper's PTX series of routers.

The MFD driver provices i2c/gpio/mtd/mdio devices.

There are full device tree binding documents for the
master mfd driver and for all slave drivers.

This patchset is against mainline as of today: v4.8-9431-g3477d16
and is dependent on the "Juniper prerequisites" and
"Juniper infrastructure" patchsets sent earlier.

Georgi Vlaev (6):
  mfd: sam: Add documentation for the SAM FPGA
  i2c: i2c-sam: Add device tree bindings
  gpio: sam: Document bindings of SAM FPGA GPIO block
  mtd: flash-sam: Bindings for Juniper's SAM FPGA flash
  net: phy: Add MDIO driver for Juniper's SAM FPGA
  net: mdio-sam: Add device tree documentation for SAM MDIO

Guenter Roeck (2):
  gpio: Introduce SAM gpio driver
  mtd: Add SAM Flash driver

Maryam Seraj (2):
  mfd: Add Juniper SAM FPGA MFD driver
  i2c: Juniper SAM I2C driver

 .../devicetree/bindings/gpio/jnx,gpio-sam.txt      | 110 +++
 .../devicetree/bindings/i2c/i2c-sam-mux.txt        |  20 +
 Documentation/devicetree/bindings/i2c/i2c-sam.txt  |  44 +
 Documentation/devicetree/bindings/mfd/jnx-sam.txt  |  94 ++
 .../devicetree/bindings/mtd/flash-sam.txt          |  31 +
 Documentation/devicetree/bindings/net/mdio-sam.txt |  48 +
 drivers/gpio/Kconfig                               |  11 +
 drivers/gpio/Makefile                              |   1 +
 drivers/gpio/gpio-sam.c                            | 707 +++++++++++++++
 drivers/i2c/busses/Kconfig                         |  11 +
 drivers/i2c/busses/Makefile                        |   1 +
 drivers/i2c/busses/i2c-sam.c                       | 942 +++++++++++++++++++
 drivers/mfd/Kconfig                                |  16 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/sam-core.c                             | 997 +++++++++++++++++++++
 drivers/mtd/devices/Kconfig                        |  11 +
 drivers/mtd/devices/Makefile                       |   1 +
 drivers/mtd/devices/sam-flash.c                    | 642 +++++++++++++
 drivers/net/phy/Kconfig                            |   8 +
 drivers/net/phy/Makefile                           |   1 +
 drivers/net/phy/mdio-sam.c                         | 564 ++++++++++++
 include/linux/mfd/sam.h                            |  30 +
 22 files changed, 4291 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sam.txt
 create mode 100644 Documentation/devicetree/bindings/mfd/jnx-sam.txt
 create mode 100644 Documentation/devicetree/bindings/mtd/flash-sam.txt
 create mode 100644 Documentation/devicetree/bindings/net/mdio-sam.txt
 create mode 100644 drivers/gpio/gpio-sam.c
 create mode 100644 drivers/i2c/busses/i2c-sam.c
 create mode 100644 drivers/mfd/sam-core.c
 create mode 100644 drivers/mtd/devices/sam-flash.c
 create mode 100644 drivers/net/phy/mdio-sam.c
 create mode 100644 include/linux/mfd/sam.h

-- 
1.9.1


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply

* [PATCH] net: macb: NULL out phydev after removing mdio bus
From: Xander Huff @ 2016-10-07 15:13 UTC (permalink / raw)
  To: nicolas.ferre
  Cc: netdev, Nathan Sullivan, linux-kernel, xander.huff, Brad Mouring

From: Nathan Sullivan <nathan.sullivan@ni.com>

To ensure the dev->phydev pointer is not used after becoming invalid in
mdiobus_unregister, set it to NULL. This happens when removing the macb
driver without first taking its interface down, since unregister_netdev
will end up calling macb_close.

Signed-off-by: Xander Huff <xander.huff@ni.com>
Signed-off-by: Nathan Sullivan <nathan.sullivan@ni.com>
Signed-off-by: Brad Mouring <brad.mouring@ni.com>
---
 drivers/net/ethernet/cadence/macb.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/ethernet/cadence/macb.c b/drivers/net/ethernet/cadence/macb.c
index 63144bb..b32444a 100644
--- a/drivers/net/ethernet/cadence/macb.c
+++ b/drivers/net/ethernet/cadence/macb.c
@@ -3117,6 +3117,7 @@ static int macb_remove(struct platform_device *pdev)
 		if (dev->phydev)
 			phy_disconnect(dev->phydev);
 		mdiobus_unregister(bp->mii_bus);
+		dev->phydev = NULL;
 		mdiobus_free(bp->mii_bus);
 
 		/* Shutdown the PHY if there is a GPIO reset */
-- 
1.9.1

^ permalink raw reply related

* Re: [PATCH v4 01/10] ethernet: add sun8i-emac driver
From: Joe Perches @ 2016-10-07 15:02 UTC (permalink / raw)
  To: Corentin Labbe, robh+dt, mark.rutland, maxime.ripard, wens, linux,
	davem, f.fainelli, andrew
  Cc: netdev, devicetree, linux-arm-kernel, linux-kernel
In-Reply-To: <1475828757-926-2-git-send-email-clabbe.montjoie@gmail.com>

On Fri, 2016-10-07 at 10:25 +0200, Corentin Labbe wrote:
> This patch add support for sun8i-emac ethernet MAC hardware.
> It could be found in Allwinner H3/A83T/A64 SoCs.

trivial notes:

> diff --git a/drivers/net/ethernet/allwinner/sun8i-emac.c b/drivers/net/ethernet/allwinner/sun8i-emac.c
[]
> +static const char const estats_str[][ETH_GSTRING_LEN] = {

one too many const

> +/* MAGIC value for knowing if a descriptor is available or not */
> +#define DCLEAN cpu_to_le32(BIT(16) | BIT(14) | BIT(12) | BIT(10) | BIT(9))

Aren't there #defines for these bits?

> +static void sun8i_emac_flow_ctrl(struct sun8i_emac_priv *priv, int duplex,
> +				 int fc)
> +{
> +	u32 flow = 0;
> +
> +	flow = readl(priv->base + EMAC_RX_CTL0);
> +	if (fc & EMAC_FLOW_RX)
> +		flow |= BIT(16);
> +	else
> +		flow &= ~BIT(16);
> +	writel(flow, priv->base + EMAC_RX_CTL0);
> +
> +	flow = readl(priv->base + EMAC_TX_FLOW_CTL);
> +	if (fc & EMAC_FLOW_TX)
> +		flow |= BIT(0);
> +	else
> +		flow &= ~BIT(0);

more magic bits that could be #defines

> +static int sun8i_emac_rx_from_ddesc(struct net_device *ndev, int i)
> +{
> []
> +	/* the checksum or length of received frame's payload is wrong*/
> +	if (dstatus & BIT(0)) {
[]
> +	if (dstatus & BIT(1)) {
[]
> +	if ((dstatus & BIT(3))) {

etc...

^ permalink raw reply

* HSO driver patch again [2/2]
From: Matej Kupljen @ 2016-10-07 14:45 UTC (permalink / raw)
  To: Greg KH; +Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA

Hi,

second patch:
0002-Stop-also-serial-queue-when-device-is-unplugged.patch
Serial device was not stopped when the device got unplugged so
there are continues error messages that the URB cannot be submitted.

Thanks,
Matej

From: Matej Kupljen <matej.kupljen-ovEwHRmg3fY@public.gmane.org>
Date: Wed, 5 Oct 2016 13:08:31 +0200
Subject: [PATCH] Stop also serial queue when device is unplugged

We need to also stop serial port when the device is unplugged,
the network part is already stopped.

Signed-off-by: Matej Kupljen <matej.kupljen-ovEwHRmg3fY@public.gmane.org>
---
 drivers/net/usb/hso.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c
index 16aef06..efd1fef 100644
--- a/drivers/net/usb/hso.c
+++ b/drivers/net/usb/hso.c
@@ -3157,6 +3157,7 @@ static void hso_free_interface(struct
usb_interface *interface)
  mutex_lock(&serial->parent->mutex);
  serial->parent->usb_gone = 1;
  mutex_unlock(&serial->parent->mutex);
+ hso_stop_serial_device(serial->parent);
  cancel_work_sync(&serial_table[i]->async_put_intf);
  cancel_work_sync(&serial_table[i]->async_get_intf);
  hso_serial_tty_unregister(serial);
-- 
2.7.4
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" 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

* HSO driver patch again [1/2]
From: Matej Kupljen @ 2016-10-07 14:44 UTC (permalink / raw)
  To: Greg KH; +Cc: linux-usb, netdev

Hi,

I am using hso driver for my 3G modem (although different manufacturer
with different USB VID/PID) but I have some problems with it. It is
working fine, but if you disconnect the USB connection while the data
is transferring trough AT interface, we get a crash.

First patch
0001-Reverse-the-order-in-disconnect.patch
This patch first sets the interface to NULL and only then starts
removing the allocated resources. This is according to documentation and
it is also more similar to other drivers.

Thanks,
Matej

From: Matej Kupljen <matej.kupljen@norik.si>
Date: Wed, 28 Sep 2016 18:37:23 +0200
Subject: [PATCH] Reverse the order in disconnect

When the device is disconnected, we first need to set interface
data to NULL and then do the freeing of resources to avoid race
conditions.

Signed-off-by: Matej Kupljen <matej.kupljen@norik.si>
---
 drivers/net/usb/hso.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c
index b0df61f..16aef06 100644
--- a/drivers/net/usb/hso.c
+++ b/drivers/net/usb/hso.c
@@ -3013,10 +3013,10 @@ exit:
 /* device removed, cleaning up */
 static void hso_disconnect(struct usb_interface *interface)
 {
- hso_free_interface(interface);
-
  /* remove reference of our private data */
  usb_set_intfdata(interface, NULL);
+
+ hso_free_interface(interface);
 }

 static void async_get_intf(struct work_struct *data)
-- 
2.7.4

^ permalink raw reply related

* Re: HSO driver patch [1/2]
From: Greg KH @ 2016-10-07 14:38 UTC (permalink / raw)
  To: Matej Kupljen
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <CAHMF36FfubzNOcaud613oVuM4YZoC_y29GtMC_h7HDNLOSm8Ew-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>

On Fri, Oct 07, 2016 at 04:29:42PM +0200, Matej Kupljen wrote:
> Hi,
> 
> 
> I am using hso driver for my 3G modem (although different manufacturer
> with different USB VID/PID) but I have some problems with it. It is
> working fine, but if you disconnect the USB connection while the data
> is transferring trough AT interface, we get a crash.
> 
> There are two patches:
>  1.) 0001-Reverse-the-order-in-disconnect.patch
>       This patch first sets the interface to NULL and only then starts
> removing the allocated
>       resources. This is according to documentation and it is also
> more similar to other drivers.
>  2.) 0002-Stop-also-serial-queue-when-device-is-unplugged.patch
>       Serial device was not stopped when the device got unplugged so
> there are continues error
>       messages that the URB cannot be submitted.

Again, patches can not be attached, they just need to be inline, with
the needed information.

Look at the patches on the mailing lists for plenty of examples of how
to do this.

thanks,

greg k-h
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" 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

* Re: HSO driver patch [2/2]
From: Matej Kupljen @ 2016-10-07 14:30 UTC (permalink / raw)
  To: Greg KH; +Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA

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

Hi,

second patch..

Thanks,
Matej

[-- Attachment #2: 0002-Stop-also-serial-queue-when-device-is-unplugged.patch --]
[-- Type: text/x-patch, Size: 993 bytes --]

From 95cfca26bbcbb021e9327c03f8edf2ed0878de75 Mon Sep 17 00:00:00 2001
From: Matej Kupljen <matej.kupljen@norik.si>
Date: Wed, 5 Oct 2016 13:08:31 +0200
Subject: [PATCH] Stop also serial queue when device is unplugged

We need to also stop serial port when the device is unplugged,
the network part is already stopped.

Signed-off-by: Matej Kupljen <matej.kupljen@norik.si>
---
 drivers/net/usb/hso.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c
index 16aef06..efd1fef 100644
--- a/drivers/net/usb/hso.c
+++ b/drivers/net/usb/hso.c
@@ -3157,6 +3157,7 @@ static void hso_free_interface(struct usb_interface *interface)
 			mutex_lock(&serial->parent->mutex);
 			serial->parent->usb_gone = 1;
 			mutex_unlock(&serial->parent->mutex);
+			hso_stop_serial_device(serial->parent);
 			cancel_work_sync(&serial_table[i]->async_put_intf);
 			cancel_work_sync(&serial_table[i]->async_get_intf);
 			hso_serial_tty_unregister(serial);
-- 
2.7.4


^ permalink raw reply related

* HSO driver patch [1/2]
From: Matej Kupljen @ 2016-10-07 14:29 UTC (permalink / raw)
  To: Greg KH; +Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA

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

Hi,


I am using hso driver for my 3G modem (although different manufacturer
with different USB VID/PID) but I have some problems with it. It is
working fine, but if you disconnect the USB connection while the data
is transferring trough AT interface, we get a crash.

There are two patches:
 1.) 0001-Reverse-the-order-in-disconnect.patch
      This patch first sets the interface to NULL and only then starts
removing the allocated
      resources. This is according to documentation and it is also
more similar to other drivers.
 2.) 0002-Stop-also-serial-queue-when-device-is-unplugged.patch
      Serial device was not stopped when the device got unplugged so
there are continues error
      messages that the URB cannot be submitted.

Another problem is that we can hit the WARN macro in hso_serial_open by:
1.) Plug in the USB modem
2.) Load hso.ko module
3.) Create the /dev nodes
4.) do a less -f /dev/ttyHS2
5.) unplug the USB modem
6.) Re-plug it and back
7.) Quit previous less by pressing q
8.) start less again like in 4 and you'll get

[  198.828136] ------------[ cut here ]------------
[  198.832788] WARNING: CPU: 2 PID: 1799 at include/linux/kref.h:47
kobject_get+0x9c/0xa8()
[  198.840886] Modules linked in: hso
[  198.844337] CPU: 2 PID: 1799 Comm: less Not tainted
4.1.13-00130-g126c250-dirty #5
[  198.851917] Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree)
[  198.858481] [<80017b70>] (unwind_backtrace) from [<800137d0>]
(show_stack+0x10/0x14)
[  198.866246] [<800137d0>] (show_stack) from [<8063caa0>]
(dump_stack+0x84/0xc4)
[  198.873492] [<8063caa0>] (dump_stack) from [<8002857c>]
(warn_slowpath_common+0x84/0xb4)
[  198.881597] [<8002857c>] (warn_slowpath_common) from [<80028648>]
(warn_slowpath_null+0x1c/0x24)
[  198.890398] [<80028648>] (warn_slowpath_null) from [<802533a8>]
(kobject_get+0x9c/0xa8)
[  198.898420] [<802533a8>] (kobject_get) from [<80110b90>] (cdev_get+0x2c/0x4c)
[  198.905571] [<80110b90>] (cdev_get) from [<80110eb0>]
(chrdev_open+0x2c/0x178)
[  198.912812] [<80110eb0>] (chrdev_open) from [<8010aff0>]
(do_dentry_open+0x1d8/0x2f8)
[  198.920666] [<8010aff0>] (do_dentry_open) from [<80118958>]
(do_last+0x564/0xce4)
[  198.928164] [<80118958>] (do_last) from [<8011b0fc>] (path_openat+0x80/0x5cc)
[  198.935330] [<8011b0fc>] (path_openat) from [<8011c0f8>]
(do_filp_open+0x2c/0x88)
[  198.942832] [<8011c0f8>] (do_filp_open) from [<8010c36c>]
(do_sys_open+0x108/0x1cc)
[  198.950508] [<8010c36c>] (do_sys_open) from [<80010140>]
(ret_fast_syscall+0x0/0x60)
[  198.958292] ---[ end trace d0b32f14e1047616 ]---

Thanks,
Matej

[-- Attachment #2: 0001-Reverse-the-order-in-disconnect.patch --]
[-- Type: text/x-patch, Size: 981 bytes --]

From f560b127ca9bb7c52106f0f83091e6c5e0e90b09 Mon Sep 17 00:00:00 2001
From: Matej Kupljen <matej.kupljen@norik.si>
Date: Wed, 28 Sep 2016 18:37:23 +0200
Subject: [PATCH] Reverse the order in disconnect

When the device is disconnected, we first need to set interface
data to NULL and then do the freeing of resources to avoid race
conditions.

Signed-off-by: Matej Kupljen <matej.kupljen@norik.si>
---
 drivers/net/usb/hso.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c
index b0df61f..16aef06 100644
--- a/drivers/net/usb/hso.c
+++ b/drivers/net/usb/hso.c
@@ -3013,10 +3013,10 @@ exit:
 /* device removed, cleaning up */
 static void hso_disconnect(struct usb_interface *interface)
 {
-	hso_free_interface(interface);
-
 	/* remove reference of our private data */
 	usb_set_intfdata(interface, NULL);
+
+	hso_free_interface(interface);
 }
 
 static void async_get_intf(struct work_struct *data)
-- 
2.7.4


^ permalink raw reply related

* [PATCH] net: Reset skb to network header in neigh_hh_output
From: Abdelrhman Ahmed @ 2016-10-07 14:14 UTC (permalink / raw)
  To: davem; +Cc: netdev, linux-kernel

When hardware header is added without using cached one, neigh_resolve_output
and neigh_connected_output reset skb to network header before adding it.
When cached one is used, neigh_hh_output does not reset the skb to network
header.

The fix is to reset skb to network header before adding cached hardware header
to keep the behavior consistent in all cases.

Signed-off-by: Abdelrhman Ahmed <ab@abahmed.com>
---
 include/net/neighbour.h | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/include/net/neighbour.h b/include/net/neighbour.h
index 8b68384..4d89fc2 100644
--- a/include/net/neighbour.h
+++ b/include/net/neighbour.h
@@ -424,7 +424,7 @@ static inline struct neighbour * neigh_clone(struct neighbour *neigh)
 static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
 {
        unsigned long now = jiffies;
-       
+
        if (neigh->used != now)
                neigh->used = now;
        if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
@@ -451,6 +451,8 @@ static inline int neigh_hh_output(const struct hh_cache *hh, struct sk_buff *skb
        unsigned int seq;
        int hh_len;
 
+       __skb_pull(skb, skb_network_offset(skb));
+
        do {
                seq = read_seqbegin(&hh->hh_lock);
                hh_len = hh->hh_len;
-- 
1.9.1

^ permalink raw reply related

* Re: [RFC 0/3] Add support for led triggers on phy link state change
From: Pavel Machek @ 2016-10-07 14:13 UTC (permalink / raw)
  To: Zach Brown
  Cc: devel, florian.c.schilhabel, f.fainelli, netdev, linux-kernel,
	Larry.Finger, mlindner
In-Reply-To: <1473890149-2066-1-git-send-email-zach.brown@ni.com>


[-- Attachment #1.1: Type: text/plain, Size: 682 bytes --]

Hi!

> Some drivers that include phy.h defined LED_OFF which conflicts with
> definition in leds.h. phy led support uses leds.h so the two namespaces are no
> longer isolated.
> The first two patches fix the two net drivers that declared enum constants that
> conflict with enum constants in linux/leds.h.

Perhaps led patches should be cced to led mainainters?

LED SUBSYSTEM
M:      Richard Purdie <rpurdie@rpsys.net>
M:      Jacek Anaszewski <j.anaszewski@samsung.com>
L:      linux-leds@vger.kernel.org

									Pavel


-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

[-- Attachment #2: Type: text/plain, Size: 169 bytes --]

_______________________________________________
devel mailing list
devel@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

^ permalink raw reply

* [PATCH net 1/2] Subject: [PATCH] af_iucv: enable control sends in case of SEND_SHUTDOWN
From: Ursula Braun @ 2016-10-07 13:51 UTC (permalink / raw)
  To: davem; +Cc: netdev, linux-s390, schwidefsky, heiko.carstens, ubraun
In-Reply-To: <cover.1475846599.git.ursula.braun@de.ibm.com>

If a socket program has shut down the socket for sending, it can still
receive an undetermined number of packets. The AF_IUCV protocol for
HIPER transport requires sending of a WIN flag from time to time
from the receiver to the sender, otherwise the peer cannot continue
sending. That means sending of control flags must still work, even
though the AF_IUCV socket is shutdown for sending data.
sock_alloc_send_skb() returns with error EPIPE, if socket sk_shutdown
is SEND_SHUTDOWN. Thus this patch temporarily removes the send
shutdown attribute from the socket to enable transfer of control
flags.

Signed-off-by: Ursula Braun <ubraun@linux.vnet.ibm.com>
---
 net/iucv/af_iucv.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c
index 02b45a8..88d0eaa 100644
--- a/net/iucv/af_iucv.c
+++ b/net/iucv/af_iucv.c
@@ -453,19 +453,27 @@ static void iucv_sever_path(struct sock *sk, int with_user_data)
 	}
 }
 
-/* Send FIN through an IUCV socket for HIPER transport */
+/* Send controlling flags through an IUCV socket for HIPER transport */
 static int iucv_send_ctrl(struct sock *sk, u8 flags)
 {
 	int err = 0;
 	int blen;
 	struct sk_buff *skb;
+	u8 shutdown = 0;
 
 	blen = sizeof(struct af_iucv_trans_hdr) + ETH_HLEN;
+	if (sk->sk_shutdown & SEND_SHUTDOWN) {
+		/* controlling flags should be sent anyway */
+		shutdown = sk->sk_shutdown;
+		sk->sk_shutdown &= RCV_SHUTDOWN;
+	}
 	skb = sock_alloc_send_skb(sk, blen, 1, &err);
 	if (skb) {
 		skb_reserve(skb, blen);
 		err = afiucv_hs_send(NULL, sk, skb, flags);
 	}
+	if (shutdown)
+		sk->sk_shutdown = shutdown;
 	return err;
 }
 
-- 
2.8.4

^ permalink raw reply related

* [PATCH net 0/2] s390: af_iucv patches
From: Ursula Braun @ 2016-10-07 13:51 UTC (permalink / raw)
  To: davem; +Cc: netdev, linux-s390, schwidefsky, heiko.carstens, ubraun

Hi Dave,

here are 2 patches for the s390-only af_iucv socket family code.

Thanks, Ursula

Ursula Braun (2):
  Subject: [PATCH] af_iucv: enable control sends in case of
    SEND_SHUTDOWN
  Subject: [PATCH] af_iucv: drop skbs rejected by filter

 net/iucv/af_iucv.c | 34 ++++++++++++++++++++++++++--------
 1 file changed, 26 insertions(+), 8 deletions(-)

-- 
2.8.4

^ permalink raw reply

* [PATCH net 2/2] Subject: [PATCH] af_iucv: drop skbs rejected by filter
From: Ursula Braun @ 2016-10-07 13:51 UTC (permalink / raw)
  To: davem; +Cc: netdev, linux-s390, schwidefsky, heiko.carstens, ubraun
In-Reply-To: <cover.1475846599.git.ursula.braun@de.ibm.com>

A packet filter might be installed for instance with setsockopt
SO_ATTACH_FILTER. af_iucv currently queues skbs rejected by filter
into the backlog queue. This does not make sense, since packets
rejected by filter can be dropped immediately. This patch adds
separate sk_filter return code checking, and dropping of packets
if applicable.

Signed-off-by: Ursula Braun <ubraun@linux.vnet.ibm.com>
Reported-by: Daniel Borkmann <daniel@iogearbox.net>
---
 net/iucv/af_iucv.c | 24 +++++++++++++++++-------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c
index 88d0eaa..cfb9e5f 100644
--- a/net/iucv/af_iucv.c
+++ b/net/iucv/af_iucv.c
@@ -1323,8 +1323,13 @@ static void iucv_process_message(struct sock *sk, struct sk_buff *skb,
 	}
 
 	IUCV_SKB_CB(skb)->offset = 0;
-	if (sock_queue_rcv_skb(sk, skb))
-		skb_queue_head(&iucv_sk(sk)->backlog_skb_q, skb);
+	if (sk_filter(sk, skb)) {
+		atomic_inc(&sk->sk_drops);	/* skb rejected by filter */
+		kfree_skb(skb);
+		return;
+	}
+	if (__sock_queue_rcv_skb(sk, skb))	/* handle rcv queue full */
+		skb_queue_tail(&iucv_sk(sk)->backlog_skb_q, skb);
 }
 
 /* iucv_process_message_q() - Process outstanding IUCV messages
@@ -1438,13 +1443,13 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg,
 		rskb = skb_dequeue(&iucv->backlog_skb_q);
 		while (rskb) {
 			IUCV_SKB_CB(rskb)->offset = 0;
-			if (sock_queue_rcv_skb(sk, rskb)) {
+			if (__sock_queue_rcv_skb(sk, rskb)) {
+				/* handle rcv queue full */
 				skb_queue_head(&iucv->backlog_skb_q,
 						rskb);
 				break;
-			} else {
-				rskb = skb_dequeue(&iucv->backlog_skb_q);
 			}
+			rskb = skb_dequeue(&iucv->backlog_skb_q);
 		}
 		if (skb_queue_empty(&iucv->backlog_skb_q)) {
 			if (!list_empty(&iucv->message_q.list))
@@ -2124,12 +2129,17 @@ static int afiucv_hs_callback_rx(struct sock *sk, struct sk_buff *skb)
 	skb_reset_transport_header(skb);
 	skb_reset_network_header(skb);
 	IUCV_SKB_CB(skb)->offset = 0;
+	if (sk_filter(sk, skb)) {
+		atomic_inc(&sk->sk_drops);	/* skb rejected by filter */
+		kfree_skb(skb);
+		return NET_RX_SUCCESS;
+	}
+
 	spin_lock(&iucv->message_q.lock);
 	if (skb_queue_empty(&iucv->backlog_skb_q)) {
-		if (sock_queue_rcv_skb(sk, skb)) {
+		if (__sock_queue_rcv_skb(sk, skb))
 			/* handle rcv queue full */
 			skb_queue_tail(&iucv->backlog_skb_q, skb);
-		}
 	} else
 		skb_queue_tail(&iucv_sk(sk)->backlog_skb_q, skb);
 	spin_unlock(&iucv->message_q.lock);
-- 
2.8.4

^ permalink raw reply related

* [PATCH v4 2/4] mac80211: filter multicast data packets on AP / AP_VLAN
From: Michael Braun @ 2016-10-07 13:12 UTC (permalink / raw)
  To: johannes; +Cc: Michael Braun, linux-wireless, projekt-wlan, netdev
In-Reply-To: <1475845944-27680-1-git-send-email-michael-dev@fami-braun.de>

This patch adds filtering for multicast data packets on AP_VLAN interfaces
that have no authorized station connected and changes filtering on AP
interfaces to not count stations assigned to AP_VLAN interfaces.

This saves airtime and avoids waking up other stations currently authorized
in this BSS. When using WPA, the packets dropped could not be decrypted by
any station.

The behaviour when there are no AP_VLAN interfaces is left unchanged.
When there are AP_VLAN interfaces, this patch
1. adds filtering multicast data packets sent on AP_VLAN interfaces that
   have no authorized station connected.
   No filtering happens on 4addr AP_VLAN interfaces.
2. makes filtering of multicast data packets sent on AP interfaces depend
   on the number of authorized stations in this bss not assigned to an
   AP_VLAN interface.

Therefore, a new num_mcast_sta counter is added for AP_VLAN interfaces.
The existing one for AP interfaces is altered to not track stations
assigned to an AP_VLAN interface.

The new counter is exposed in debugfs.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>

--
v4:
 - update description
v3:
 - reuse existing num_mcast_sta
v2:
 - use separate function to inc/dec mcast_sta counters
 - do not filter in 4addr mode
 - change description
 - change filtering on AP interface (do not count AP_VLAN sta)
 - use new counters regardless of 4addr or not
 - simplify cfg.c:change_station
 - remove no-op change in __cleanup_single_sta
---
 net/mac80211/cfg.c            | 20 ++++++--------------
 net/mac80211/debugfs_netdev.c | 11 +++++++++++
 net/mac80211/ieee80211_i.h    | 33 +++++++++++++++++++++++++++++++++
 net/mac80211/rx.c             |  5 +++--
 net/mac80211/sta_info.c       | 10 ++--------
 net/mac80211/tx.c             |  5 ++---
 6 files changed, 57 insertions(+), 27 deletions(-)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 24133f5..1edb017 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1357,9 +1357,6 @@ static int ieee80211_change_station(struct wiphy *wiphy,
 		goto out_err;
 
 	if (params->vlan && params->vlan != sta->sdata->dev) {
-		bool prev_4addr = false;
-		bool new_4addr = false;
-
 		vlansdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);
 
 		if (params->vlan->ieee80211_ptr->use_4addr) {
@@ -1369,26 +1366,21 @@ static int ieee80211_change_station(struct wiphy *wiphy,
 			}
 
 			rcu_assign_pointer(vlansdata->u.vlan.sta, sta);
-			new_4addr = true;
 			__ieee80211_check_fast_rx_iface(vlansdata);
 		}
 
 		if (sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN &&
-		    sta->sdata->u.vlan.sta) {
+		    sta->sdata->u.vlan.sta)
 			RCU_INIT_POINTER(sta->sdata->u.vlan.sta, NULL);
-			prev_4addr = true;
-		}
+
+		if (test_sta_flag(sta, WLAN_STA_AUTHORIZED))
+			ieee80211_vif_dec_num_mcast(sta->sdata);
 
 		sta->sdata = vlansdata;
 		ieee80211_check_fast_xmit(sta);
 
-		if (sta->sta_state == IEEE80211_STA_AUTHORIZED &&
-		    prev_4addr != new_4addr) {
-			if (new_4addr)
-				atomic_dec(&sta->sdata->bss->num_mcast_sta);
-			else
-				atomic_inc(&sta->sdata->bss->num_mcast_sta);
-		}
+		if (test_sta_flag(sta, WLAN_STA_AUTHORIZED))
+			ieee80211_vif_inc_num_mcast(sta->sdata);
 
 		ieee80211_send_layer2_update(sta);
 	}
diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c
index a5ba739..ed7bff4 100644
--- a/net/mac80211/debugfs_netdev.c
+++ b/net/mac80211/debugfs_netdev.c
@@ -477,6 +477,7 @@ IEEE80211_IF_FILE_RW(tdls_wider_bw);
 IEEE80211_IF_FILE(num_mcast_sta, u.ap.num_mcast_sta, ATOMIC);
 IEEE80211_IF_FILE(num_sta_ps, u.ap.ps.num_sta_ps, ATOMIC);
 IEEE80211_IF_FILE(dtim_count, u.ap.ps.dtim_count, DEC);
+IEEE80211_IF_FILE(num_mcast_sta_vlan, u.vlan.num_mcast_sta, ATOMIC);
 
 static ssize_t ieee80211_if_fmt_num_buffered_multicast(
 	const struct ieee80211_sub_if_data *sdata, char *buf, int buflen)
@@ -643,6 +644,13 @@ static void add_ap_files(struct ieee80211_sub_if_data *sdata)
 	DEBUGFS_ADD_MODE(tkip_mic_test, 0200);
 }
 
+static void add_vlan_files(struct ieee80211_sub_if_data *sdata)
+{
+	/* add num_mcast_sta_vlan using name num_mcast_sta */
+	debugfs_create_file("num_mcast_sta", 0400, sdata->vif.debugfs_dir,
+			    sdata, &num_mcast_sta_vlan_ops);
+}
+
 static void add_ibss_files(struct ieee80211_sub_if_data *sdata)
 {
 	DEBUGFS_ADD_MODE(tsf, 0600);
@@ -746,6 +754,9 @@ static void add_files(struct ieee80211_sub_if_data *sdata)
 	case NL80211_IFTYPE_AP:
 		add_ap_files(sdata);
 		break;
+	case NL80211_IFTYPE_AP_VLAN:
+		add_vlan_files(sdata);
+		break;
 	case NL80211_IFTYPE_WDS:
 		add_wds_files(sdata);
 		break;
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index f56d342..70c0963 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -305,6 +305,7 @@ struct ieee80211_if_vlan {
 
 	/* used for all tx if the VLAN is configured to 4-addr mode */
 	struct sta_info __rcu *sta;
+	atomic_t num_mcast_sta; /* number of stations receiving multicast */
 };
 
 struct mesh_stats {
@@ -1496,6 +1497,38 @@ ieee80211_have_rx_timestamp(struct ieee80211_rx_status *status)
 	return false;
 }
 
+static inline void
+ieee80211_vif_inc_num_mcast(struct ieee80211_sub_if_data *sdata)
+{
+	if (sdata->vif.type == NL80211_IFTYPE_AP)
+		atomic_inc(&sdata->u.ap.num_mcast_sta);
+	else if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+		atomic_inc(&sdata->u.vlan.num_mcast_sta);
+}
+
+static inline void
+ieee80211_vif_dec_num_mcast(struct ieee80211_sub_if_data *sdata)
+{
+	if (sdata->vif.type == NL80211_IFTYPE_AP)
+		atomic_dec(&sdata->u.ap.num_mcast_sta);
+	else if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+		atomic_dec(&sdata->u.vlan.num_mcast_sta);
+}
+
+/* This function returns the number of multicast stations connected to this
+ * interface. It returns -1 if that number is not tracked, that is for netdevs
+ * not in AP or AP_VLAN mode or when using 4addr.
+ */
+static inline int
+ieee80211_vif_get_num_mcast_if(struct ieee80211_sub_if_data *sdata)
+{
+	if (sdata->vif.type == NL80211_IFTYPE_AP)
+		return atomic_read(&sdata->u.ap.num_mcast_sta);
+	if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN && !sdata->u.vlan.sta)
+		return atomic_read(&sdata->u.vlan.num_mcast_sta);
+	return -1;
+}
+
 u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
 				     struct ieee80211_rx_status *status,
 				     unsigned int mpdu_len,
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 3d8d889..7e5086a 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -2161,7 +2161,8 @@ ieee80211_deliver_skb(struct ieee80211_rx_data *rx)
 	     sdata->vif.type == NL80211_IFTYPE_AP_VLAN) &&
 	    !(sdata->flags & IEEE80211_SDATA_DONT_BRIDGE_PACKETS) &&
 	    (sdata->vif.type != NL80211_IFTYPE_AP_VLAN || !sdata->u.vlan.sta)) {
-		if (is_multicast_ether_addr(ehdr->h_dest)) {
+		if (is_multicast_ether_addr(ehdr->h_dest) &&
+		    ieee80211_vif_get_num_mcast_if(sdata) != 0) {
 			/*
 			 * send multicast frames both to higher layers in
 			 * local net stack and back to the wireless medium
@@ -2170,7 +2171,7 @@ ieee80211_deliver_skb(struct ieee80211_rx_data *rx)
 			if (!xmit_skb)
 				net_info_ratelimited("%s: failed to clone multicast frame\n",
 						    dev->name);
-		} else {
+		} else if (!is_multicast_ether_addr(ehdr->h_dest)) {
 			dsta = sta_info_get(sdata, skb->data);
 			if (dsta) {
 				/*
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 76b737d..216ef65 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -1882,10 +1882,7 @@ int sta_info_move_state(struct sta_info *sta,
 			if (!sta->sta.support_p2p_ps)
 				ieee80211_recalc_p2p_go_ps_allowed(sta->sdata);
 		} else if (sta->sta_state == IEEE80211_STA_AUTHORIZED) {
-			if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
-			    (sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN &&
-			     !sta->sdata->u.vlan.sta))
-				atomic_dec(&sta->sdata->bss->num_mcast_sta);
+			ieee80211_vif_dec_num_mcast(sta->sdata);
 			clear_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
 			ieee80211_clear_fast_xmit(sta);
 			ieee80211_clear_fast_rx(sta);
@@ -1893,10 +1890,7 @@ int sta_info_move_state(struct sta_info *sta,
 		break;
 	case IEEE80211_STA_AUTHORIZED:
 		if (sta->sta_state == IEEE80211_STA_ASSOC) {
-			if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
-			    (sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN &&
-			     !sta->sdata->u.vlan.sta))
-				atomic_inc(&sta->sdata->bss->num_mcast_sta);
+			ieee80211_vif_inc_num_mcast(sta->sdata);
 			set_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
 			ieee80211_check_fast_xmit(sta);
 			ieee80211_check_fast_rx(sta);
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 5023966..c3ce86e 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -331,9 +331,8 @@ ieee80211_tx_h_check_assoc(struct ieee80211_tx_data *tx)
 			I802_DEBUG_INC(tx->local->tx_handlers_drop_not_assoc);
 			return TX_DROP;
 		}
-	} else if (unlikely(tx->sdata->vif.type == NL80211_IFTYPE_AP &&
-			    ieee80211_is_data(hdr->frame_control) &&
-			    !atomic_read(&tx->sdata->u.ap.num_mcast_sta))) {
+	} else if (unlikely(ieee80211_is_data(hdr->frame_control) &&
+			    ieee80211_vif_get_num_mcast_if(tx->sdata) == 0)) {
 		/*
 		 * No associated STAs - no need to send multicast
 		 * frames.
-- 
2.1.4

^ permalink raw reply related

* [PATCH v4 4/4] mac80211: multicast to unicast conversion
From: Michael Braun @ 2016-10-07 13:12 UTC (permalink / raw)
  To: johannes; +Cc: Michael Braun, linux-wireless, projekt-wlan, netdev
In-Reply-To: <1475845944-27680-1-git-send-email-michael-dev@fami-braun.de>

This patch adds support for sending multicast data packets with ARP, IPv4
and IPv6 payload (possible 802.1q tagged) as 802.11 unicast frames to all
stations.

IEEE 802.11 multicast has well known issues, among them:
 1. packets are not acked and hence not retransmitted, resulting in
    decreased reliablity
 2. packets are send at low rate, increasing time required on air

When used with AP_VLAN, there is another disadvantage:
 3. all stations in the BSS are woken up, regardsless of their AP_VLAN
    assignment.

By doing multicast to unicast conversion, all three issus are solved.

IEEE802.11-2012 proposes directed multicast service (DMS) using A-MSDU
frames and a station initiated control protocol. It has the advantage that
the station can recover the destination multicast mac address, but it is
not backward compatible with non QOS stations and does not enable the
administrator of a BSS to force this mode of operation within a BSS.
Additionally, it would require both the ap and the station to implement
the control protocol, which is optional on both ends. Furthermore, I've
seen a few mobile phone stations locally that indicate qos support but
won't complete DHCP if their broadcasts are encapsulated as A-MSDU. Though
they work fine with this series approach.

This patch therefore does not opt to implement DMS but instead just
replicates the packet and changes the destination address. As this works
fine with ARP, IPv4 and IPv6, it is limited to these protocols and normal
802.11 multicast frames are send out for all other payload protocols.

There is a runtime toggle to enable multicast conversion in a per-bss
fashion.

When there is only a single station assigned to the AP_VLAN interface, no
packet replication will occur. 4addr mode of operation is unchanged.

This change opts for iterating all BSS stations for finding the stations
assigned to this AP/AP_VLAN interface, as there currently is no per
AP_VLAN list to iterate and multicast packets are expected to be few.
If needed, such a list could be added later.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>

--
v4:
  - rename MULTICAST_TO_UNICAST to MULTICAST_TO_UNICAST
v3: fix compile error for trace.h
v2: add nl80211 toggle
    rename tx_dnat to change_da
    change int to bool unicast
---
 net/mac80211/cfg.c            |  15 +++++++
 net/mac80211/debugfs_netdev.c |   3 ++
 net/mac80211/ieee80211_i.h    |   1 +
 net/mac80211/tx.c             | 101 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 120 insertions(+)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 1edb017..23f711a 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2242,6 +2242,20 @@ static int ieee80211_set_wds_peer(struct wiphy *wiphy, struct net_device *dev,
 	return 0;
 }
 
+static int ieee80211_set_multicast_to_unicast(struct wiphy *wiphy,
+					      struct net_device *dev,
+					      const bool enabled)
+{
+	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+	if (sdata->vif.type != NL80211_IFTYPE_AP)
+		return -1;
+
+	sdata->u.ap.unicast = enabled;
+
+	return 0;
+}
+
 static void ieee80211_rfkill_poll(struct wiphy *wiphy)
 {
 	struct ieee80211_local *local = wiphy_priv(wiphy);
@@ -3400,6 +3414,7 @@ const struct cfg80211_ops mac80211_config_ops = {
 	.set_tx_power = ieee80211_set_tx_power,
 	.get_tx_power = ieee80211_get_tx_power,
 	.set_wds_peer = ieee80211_set_wds_peer,
+	.set_multicast_to_unicast = ieee80211_set_multicast_to_unicast,
 	.rfkill_poll = ieee80211_rfkill_poll,
 	CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd)
 	CFG80211_TESTMODE_DUMP(ieee80211_testmode_dump)
diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c
index ed7bff4..a5554cc 100644
--- a/net/mac80211/debugfs_netdev.c
+++ b/net/mac80211/debugfs_netdev.c
@@ -487,6 +487,8 @@ static ssize_t ieee80211_if_fmt_num_buffered_multicast(
 }
 IEEE80211_IF_FILE_R(num_buffered_multicast);
 
+IEEE80211_IF_FILE(multicast_to_unicast, u.ap.unicast, HEX);
+
 /* IBSS attributes */
 static ssize_t ieee80211_if_fmt_tsf(
 	const struct ieee80211_sub_if_data *sdata, char *buf, int buflen)
@@ -642,6 +644,7 @@ static void add_ap_files(struct ieee80211_sub_if_data *sdata)
 	DEBUGFS_ADD(dtim_count);
 	DEBUGFS_ADD(num_buffered_multicast);
 	DEBUGFS_ADD_MODE(tkip_mic_test, 0200);
+	DEBUGFS_ADD_MODE(multicast_to_unicast, 0600);
 }
 
 static void add_vlan_files(struct ieee80211_sub_if_data *sdata)
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 70c0963..b23bc21 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -293,6 +293,7 @@ struct ieee80211_if_ap {
 			 driver_smps_mode; /* smps mode request */
 
 	struct work_struct request_smps_work;
+	bool unicast;
 };
 
 struct ieee80211_if_wds {
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index c3ce86e..6de9549 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -16,6 +16,7 @@
 #include <linux/kernel.h>
 #include <linux/slab.h>
 #include <linux/skbuff.h>
+#include <linux/if_vlan.h>
 #include <linux/etherdevice.h>
 #include <linux/bitmap.h>
 #include <linux/rcupdate.h>
@@ -1770,6 +1771,102 @@ bool ieee80211_tx_prepare_skb(struct ieee80211_hw *hw,
 }
 EXPORT_SYMBOL(ieee80211_tx_prepare_skb);
 
+/* rewrite destination mac address */
+static int ieee80211_change_da(struct sk_buff *skb, struct sta_info *sta)
+{
+	struct ethhdr *eth;
+	int err;
+
+	err = skb_ensure_writable(skb, ETH_HLEN);
+	if (unlikely(err))
+		return err;
+
+	eth = (void *)skb->data;
+	ether_addr_copy(eth->h_dest, sta->sta.addr);
+
+	return 0;
+}
+
+/* Check if multicast to unicast conversion is needed and do it.
+ * Returns 1 if skb was freed and should not be send out.
+ */
+static int
+ieee80211_tx_multicast_to_unicast(struct ieee80211_sub_if_data *sdata,
+				  struct sk_buff *skb, u32  info_flags)
+{
+	struct ieee80211_local *local = sdata->local;
+	const struct ethhdr *eth = (void *)skb->data;
+	const struct vlan_ethhdr *ethvlan = (void *)skb->data;
+	struct sta_info *sta, *prev = NULL;
+	struct sk_buff *cloned_skb;
+	u16 ethertype;
+
+	/* check if this is a multicast frame */
+	if (!is_multicast_ether_addr(eth->h_dest))
+		return 0;
+
+	/* check runtime toggle for this bss */
+	if (!sdata->bss->unicast)
+		return 0;
+
+	/* multicast to unicast conversion only for AP interfaces */
+	switch (sdata->vif.type) {
+	case NL80211_IFTYPE_AP_VLAN:
+		sta = rcu_dereference(sdata->u.vlan.sta);
+		if (sta) /* 4addr */
+			return 0;
+	case NL80211_IFTYPE_AP:
+		break;
+	default:
+		return 0;
+	}
+
+	/* info_flags would not get preserved, used only by TLDS */
+	if (info_flags)
+		return 0;
+
+	/* multicast to unicast conversion only for some payload */
+	ethertype = ntohs(eth->h_proto);
+	if (ethertype == ETH_P_8021Q && skb->len >= VLAN_ETH_HLEN)
+		ethertype = ntohs(ethvlan->h_vlan_encapsulated_proto);
+	switch (ethertype) {
+	case ETH_P_ARP:
+	case ETH_P_IP:
+	case ETH_P_IPV6:
+		break;
+	default:
+		return 0;
+	}
+
+	/* clone packets and update destination mac */
+	list_for_each_entry_rcu(sta, &local->sta_list, list) {
+		if (sdata != sta->sdata)
+			continue;
+		if (unlikely(!ether_addr_equal(eth->h_source, sta->sta.addr)))
+			/* do not send back to source */
+			continue;
+		if (!prev) {
+			prev = sta;
+			continue;
+		}
+		cloned_skb = skb_clone(skb, GFP_ATOMIC);
+		if (unlikely(ieee80211_change_da(cloned_skb, prev))) {
+			dev_kfree_skb(cloned_skb);
+			continue;
+		}
+		__ieee80211_subif_start_xmit(cloned_skb, cloned_skb->dev, 0);
+		prev = sta;
+	}
+
+	if (likely(prev)) {
+		ieee80211_change_da(skb, prev);
+		return 0;
+	}
+
+	/* no STA connected, drop */
+	return 1;
+}
+
 /*
  * Returns false if the frame couldn't be transmitted but was queued instead.
  */
@@ -3353,6 +3450,10 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb,
 
 	rcu_read_lock();
 
+	/* AP multicast to unicast conversion */
+	if (ieee80211_tx_multicast_to_unicast(sdata, skb, info_flags))
+		goto out_free;
+
 	if (ieee80211_lookup_ra_sta(sdata, skb, &sta))
 		goto out_free;
 
-- 
2.1.4

^ permalink raw reply related

* [PATCH v4 3/4] cfg80211: configure multicast to unicast for AP interfaces
From: Michael Braun @ 2016-10-07 13:12 UTC (permalink / raw)
  To: johannes; +Cc: Michael Braun, linux-wireless, projekt-wlan, netdev
In-Reply-To: <1475845944-27680-1-git-send-email-michael-dev@fami-braun.de>

This add a userspace toggle to configure multicast to unicast.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
---
 include/net/cfg80211.h       |  6 ++++++
 include/uapi/linux/nl80211.h | 10 ++++++++++
 net/wireless/nl80211.c       | 36 ++++++++++++++++++++++++++++++++++++
 net/wireless/rdev-ops.h      | 12 ++++++++++++
 net/wireless/trace.h         | 19 +++++++++++++++++++
 5 files changed, 83 insertions(+)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index b550314..6ab8940 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -2460,6 +2460,8 @@ struct cfg80211_qos_map {
  *
  * @set_wds_peer: set the WDS peer for a WDS interface
  *
+ * @set_multicast_to_unicast: configure multicast to unicast conversion for BSS
+ *
  * @rfkill_poll: polls the hw rfkill line, use cfg80211 reporting
  *	functions to adjust rfkill hw state
  *
@@ -2722,6 +2724,10 @@ struct cfg80211_ops {
 	int	(*set_wds_peer)(struct wiphy *wiphy, struct net_device *dev,
 				const u8 *addr);
 
+	int	(*set_multicast_to_unicast)(struct wiphy *wiphy,
+					    struct net_device *dev,
+					    const bool enabled);
+
 	void	(*rfkill_poll)(struct wiphy *wiphy);
 
 #ifdef CONFIG_NL80211_TESTMODE
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 2206941..1b9e87b 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -599,6 +599,9 @@
  *
  * @NL80211_CMD_SET_WDS_PEER: Set the MAC address of the peer on a WDS interface.
  *
+ * @NL80211_CMD_SET_MULTICAST_TO_UNICAST: Configure if AP interface should
+ *      perform multicast to unicast conversion (per-BSS).
+ *
  * @NL80211_CMD_JOIN_MESH: Join a mesh. The mesh ID must be given, and initial
  *	mesh config parameters may be given.
  * @NL80211_CMD_LEAVE_MESH: Leave the mesh network -- no special arguments, the
@@ -1026,6 +1029,8 @@ enum nl80211_commands {
 
 	NL80211_CMD_ABORT_SCAN,
 
+	NL80211_CMD_SET_MULTICAST_TO_UNICAST,
+
 	/* add new commands above here */
 
 	/* used to define NL80211_CMD_MAX below */
@@ -1867,6 +1872,9 @@ enum nl80211_commands {
  * @NL80211_ATTR_MESH_PEER_AID: Association ID for the mesh peer (u16). This is
  *	used to pull the stored data for mesh peer in power save state.
  *
+ * @NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED: Multicast packets should be
+ *      send out as unicast to all stations.
+ *
  * @NUM_NL80211_ATTR: total number of nl80211_attrs available
  * @NL80211_ATTR_MAX: highest attribute number currently defined
  * @__NL80211_ATTR_AFTER_LAST: internal use
@@ -2261,6 +2269,8 @@ enum nl80211_attrs {
 
 	NL80211_ATTR_MESH_PEER_AID,
 
+	NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED,
+
 	/* add attributes here, update the policy in nl80211.c */
 
 	__NL80211_ATTR_AFTER_LAST,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index f02653a..57b5d70 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -409,6 +409,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
 		.len = VHT_MUMIMO_GROUPS_DATA_LEN
 	},
 	[NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR] = { .len = ETH_ALEN },
+	[NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED] = { .type = NLA_U8, },
 };
 
 /* policy for the key attributes */
@@ -1538,6 +1539,7 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev,
 				goto nla_put_failure;
 		}
 		CMD(set_wds_peer, SET_WDS_PEER);
+		CMD(set_multicast_to_unicast, SET_MULTICAST_TO_UNICAST);
 		if (rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_TDLS) {
 			CMD(tdls_mgmt, TDLS_MGMT);
 			CMD(tdls_oper, TDLS_OPER);
@@ -2164,6 +2166,32 @@ static int nl80211_set_wds_peer(struct sk_buff *skb, struct genl_info *info)
 	return rdev_set_wds_peer(rdev, dev, bssid);
 }
 
+static int nl80211_set_multicast_to_unicast(struct sk_buff *skb,
+					    struct genl_info *info)
+{
+	struct cfg80211_registered_device *rdev = info->user_ptr[0];
+	struct net_device *dev = info->user_ptr[1];
+	struct wireless_dev *wdev = dev->ieee80211_ptr;
+	const struct nlattr *nla;
+	bool enabled;
+
+	if (!info->attrs[NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED])
+		return -EINVAL;
+
+	if (netif_running(dev))
+		return -EBUSY;
+
+	if (!rdev->ops->set_multicast_to_unicast)
+		return -EOPNOTSUPP;
+
+	if (wdev->iftype != NL80211_IFTYPE_AP)
+		return -EOPNOTSUPP;
+
+	nla = info->attrs[NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED];
+	enabled = nla_get_flag(nla);
+	return rdev_set_multicast_to_unicast(rdev, dev, enabled);
+}
+
 static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
 {
 	struct cfg80211_registered_device *rdev;
@@ -11574,6 +11602,14 @@ static const struct genl_ops nl80211_ops[] = {
 				  NL80211_FLAG_NEED_RTNL,
 	},
 	{
+		.cmd = NL80211_CMD_SET_MULTICAST_TO_UNICAST,
+		.doit = nl80211_set_multicast_to_unicast,
+		.policy = nl80211_policy,
+		.flags = GENL_UNS_ADMIN_PERM,
+		.internal_flags = NL80211_FLAG_NEED_NETDEV |
+				  NL80211_FLAG_NEED_RTNL,
+	},
+	{
 		.cmd = NL80211_CMD_JOIN_MESH,
 		.doit = nl80211_join_mesh,
 		.policy = nl80211_policy,
diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
index 85ff30b..7d93c3d 100644
--- a/net/wireless/rdev-ops.h
+++ b/net/wireless/rdev-ops.h
@@ -562,6 +562,18 @@ static inline int rdev_set_wds_peer(struct cfg80211_registered_device *rdev,
 	return ret;
 }
 
+static inline int
+rdev_set_multicast_to_unicast(struct cfg80211_registered_device *rdev,
+			      struct net_device *dev,
+			      const bool enabled)
+{
+	int ret;
+	trace_rdev_set_multicast_to_unicast(&rdev->wiphy, dev, enabled);
+	ret = rdev->ops->set_multicast_to_unicast(&rdev->wiphy, dev, enabled);
+	trace_rdev_return_int(&rdev->wiphy, ret);
+	return ret;
+}
+
 static inline void rdev_rfkill_poll(struct cfg80211_registered_device *rdev)
 {
 	trace_rdev_rfkill_poll(&rdev->wiphy);
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index 72b5255..8c9aa57 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -2940,6 +2940,25 @@ DEFINE_EVENT(wiphy_wdev_evt, rdev_abort_scan,
 	TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev),
 	TP_ARGS(wiphy, wdev)
 );
+
+TRACE_EVENT(rdev_set_multicast_to_unicast,
+	TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+		 const bool enabled),
+	TP_ARGS(wiphy, netdev, enabled),
+	TP_STRUCT__entry(
+		WIPHY_ENTRY
+		NETDEV_ENTRY
+		__field(bool, enabled)
+	),
+	TP_fast_assign(
+		WIPHY_ASSIGN;
+		NETDEV_ASSIGN;
+		__entry->enabled = enabled;
+	),
+	TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", unicast: %s",
+		  WIPHY_PR_ARG, NETDEV_PR_ARG,
+		  BOOL_TO_STR(__entry->enabled))
+);
 #endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */
 
 #undef TRACE_INCLUDE_PATH
-- 
2.1.4

^ permalink raw reply related

* [PATCH v4 1/4] mac80211: remove unnecessary num_mcast_sta user
From: Michael Braun @ 2016-10-07 13:12 UTC (permalink / raw)
  To: johannes; +Cc: Michael Braun, linux-wireless, projekt-wlan, netdev

Checking for num_mcast_sta in __ieee80211_request_smps_ap() is unnecessary,
as sta list will be empty in this case anyway, so list_for_each_entry(sta,
...) will exit immediately.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
---
 net/mac80211/cfg.c | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 543b1d4..24133f5 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2313,13 +2313,6 @@ int __ieee80211_request_smps_ap(struct ieee80211_sub_if_data *sdata,
 	    smps_mode == IEEE80211_SMPS_AUTOMATIC)
 		return 0;
 
-	 /* If no associated stations, there's no need to do anything */
-	if (!atomic_read(&sdata->u.ap.num_mcast_sta)) {
-		sdata->smps_mode = smps_mode;
-		ieee80211_queue_work(&sdata->local->hw, &sdata->recalc_smps);
-		return 0;
-	}
-
 	ht_dbg(sdata,
 	       "SMPS %d requested in AP mode, sending Action frame to %d stations\n",
 	       smps_mode, atomic_read(&sdata->u.ap.num_mcast_sta));
-- 
2.1.4

^ permalink raw reply related

* [PATCH net v2] Fixing a bug in team driver due to incorrect 'unsigned int' to 'int' conversion
From: Alex Sidorenko @ 2016-10-07 13:02 UTC (permalink / raw)
  To: netdev; +Cc: eric.dumazet, David Miller

Roundrobin runner of team driver uses 'unsigned int' variable to count
the number of sent_packets. Later it is passed to a subroutine
team_num_to_port_index(struct team *team, int num) as 'num' and when
we reach MAXINT (2**31-1), 'num' becomes negative.

This leads to using incorrect hash-bucket for port lookup
and as a result, packets are dropped. The fix consists of changing 
'int num' to 'unsigned int num'. Testing of a fixed kernel shows that
there is no packet drop anymore.


Signed-off-by: Alex Sidorenko <alexandre.sidorenko@hpe.com>


---
v2: fixed formatting

 include/linux/if_team.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/linux/if_team.h b/include/linux/if_team.h
index 174f43f..c05216a 100644
--- a/include/linux/if_team.h
+++ b/include/linux/if_team.h
@@ -245,7 +245,7 @@ static inline struct team_port *team_get_port_by_index(struct team *team,
 	return NULL;
 }
 
-static inline int team_num_to_port_index(struct team *team, int num)
+static inline int team_num_to_port_index(struct team *team, unsigned int num)
 {
 	int en_port_count = ACCESS_ONCE(team->en_port_count);
 
-- 
2.7.4

^ permalink raw reply related

* Re: [PATCH net-next] MAINTAINERS: add myself as a maintainer of xen-netback
From: Wei Liu @ 2016-10-07 10:54 UTC (permalink / raw)
  To: Paul Durrant; +Cc: netdev, xen-devel, Wei Liu
In-Reply-To: <1475836417-4479-1-git-send-email-paul.durrant@citrix.com>

On Fri, Oct 07, 2016 at 11:33:37AM +0100, Paul Durrant wrote:
> Signed-off-by: Paul Durrant <paul.durrant@citrix.com>
> Cc: Wei Liu <wei.liu2@citrix.com>

Acked-by: Wei Liu <wei.liu2@citrix.com>

Thanks for stepping up!

> ---
>  MAINTAINERS | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 464437d..4491841 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13061,6 +13061,7 @@ F:	arch/arm64/include/asm/xen/
>  
>  XEN NETWORK BACKEND DRIVER
>  M:	Wei Liu <wei.liu2@citrix.com>
> +M:	Paul Durrant <paul.durrant@citrix.com>
>  L:	xen-devel@lists.xenproject.org (moderated for non-subscribers)
>  L:	netdev@vger.kernel.org
>  S:	Supported
> -- 
> 2.1.4
> 

^ permalink raw reply

* [PATCH net-next] MAINTAINERS: add myself as a maintainer of xen-netback
From: Paul Durrant @ 2016-10-07 10:33 UTC (permalink / raw)
  To: netdev, xen-devel; +Cc: Paul Durrant, Wei Liu

Signed-off-by: Paul Durrant <paul.durrant@citrix.com>
Cc: Wei Liu <wei.liu2@citrix.com>
---
 MAINTAINERS | 1 +
 1 file changed, 1 insertion(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 464437d..4491841 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13061,6 +13061,7 @@ F:	arch/arm64/include/asm/xen/
 
 XEN NETWORK BACKEND DRIVER
 M:	Wei Liu <wei.liu2@citrix.com>
+M:	Paul Durrant <paul.durrant@citrix.com>
 L:	xen-devel@lists.xenproject.org (moderated for non-subscribers)
 L:	netdev@vger.kernel.org
 S:	Supported
-- 
2.1.4

^ permalink raw reply related

* Reversion of "xen-netback: create a debugfs node for hash information"
From: Paul Durrant @ 2016-10-07  9:50 UTC (permalink / raw)
  To: David Miller (davem@davemloft.net); +Cc: netdev@vger.kernel.org, Wei Liu

Dave,

  I notice that you have made the above reversion of commit c0c64c15 (debugfs node) due to a build failure, despite the failure being caused by commit 0364a882 (switch to threaded irq) which was made subsequently. I assume you want me to re-spin a new patch for the debugfs node to fix the build problem?

  Cheers,

    Paul

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox