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
From: Maryam Seraj <[email protected]>
Introduce SAM I2C driver for the I2C interfaces on the Juniper
SAM FPGA.
Signed-off-by: Maryam Seraj <[email protected]>
Signed-off-by: Debjit Ghosh <[email protected]>
Signed-off-by: Georgi Vlaev <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
Signed-off-by: Rajat Jain <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
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 <[email protected]>"
+
+#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
From: Georgi Vlaev <[email protected]>
Add binding document for the i2c driver of SAM FPGA.
Signed-off-by: Georgi Vlaev <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
.../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
From: Georgi Vlaev <[email protected]>
Add DT bindings document for the SAM MFD device.
Signed-off-by: Georgi Vlaev <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
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
From: Guenter Roeck <[email protected]>
The SAM GPIO IP block is present in the Juniper PTX series
of routers as part of the SAM FPGA.
Signed-off-by: Georgi Vlaev <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
Signed-off-by: Rajat Jain <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
drivers/gpio/Kconfig | 11 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-sam.c | 707 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 719 insertions(+)
create mode 100644 drivers/gpio/gpio-sam.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 9c91de6..c25dbe9 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -384,6 +384,17 @@ config GPIO_RCAR
help
Say yes here to support GPIO on Renesas R-Car SoCs.
+config GPIO_SAM
+ tristate "SAM FPGA GPIO"
+ depends on MFD_JUNIPER_SAM
+ default y if MFD_JUNIPER_SAM
+ help
+ This driver supports the GPIO interfaces on the 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 gpio-sam.
+
config GPIO_SPEAR_SPICS
bool "ST SPEAr13xx SPI Chip Select as GPIO support"
depends on PLAT_SPEAR
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index d397ea5..6691d8c 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o
obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o
obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
+obj-$(CONFIG_GPIO_SAM) += gpio-sam.o
obj-$(CONFIG_GPIO_SCH) += gpio-sch.o
obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o
obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o
diff --git a/drivers/gpio/gpio-sam.c b/drivers/gpio/gpio-sam.c
new file mode 100644
index 0000000..5082050
--- /dev/null
+++ b/drivers/gpio/gpio-sam.c
@@ -0,0 +1,707 @@
+/*
+ * Copyright (C) 2012 - 2015 Juniper Networks
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mfd/sam.h>
+
+/* gpio status/configuration */
+#define SAM_GPIO_NEG_EDGE (1 << 8)
+#define SAM_GPIO_NEG_EDGE_EN (1 << 7)
+#define SAM_GPIO_POS_EDGE (1 << 6)
+#define SAM_GPIO_POS_EDGE_EN (1 << 5)
+#define SAM_GPIO_BLINK (1 << 4)
+#define SAM_GPIO_OUT (1 << 3)
+#define SAM_GPIO_OUT_TS (1 << 2)
+#define SAM_GPIO_DEBOUNCE_EN (1 << 1)
+#define SAM_GPIO_IN (1 << 0)
+
+#define SAM_GPIO_BASE 0x1000
+
+#define SAM_MAX_NGPIO 512
+
+#define SAM_GPIO_ADDR(addr, nr) ((addr) + SAM_GPIO_BASE + (nr) * sizeof(u32))
+
+struct sam_gpio_irq_group {
+ int start; /* 1st gpio pin */
+ int count; /* # of pins in group */
+ int num_enabled; /* # of enabled interrupts */
+};
+
+/**
+ * struct sam_gpio - GPIO private data structure.
+ * @base: PCI base address of Memory mapped I/O register.
+ * @dev: Pointer to device structure.
+ * @gpio: Data for GPIO infrastructure.
+ * @gpio_base: 1st gpio pin
+ * @gpio_count: # of gpio pins
+ * @irq_lock: Lock used by interrupt subsystem
+ * @domain: Pointer to interrupt domain
+ * @irq: Interrupt # from parent
+ * @irq_high: Second interrupt # from parent
+ * (currently unused)
+ * @irq_group: Interrupt group descriptions
+ * (one group per interrupt bit)
+ * @irq_type: The interrupt type for each gpio pin
+ */
+struct sam_gpio {
+ void __iomem *base;
+ struct device *dev;
+ struct gpio_chip gpio;
+ int gpio_base;
+ int gpio_count;
+ struct mutex irq_lock;
+ struct irq_domain *domain;
+ int irq;
+ int irq_high;
+ struct sam_gpio_irq_group irq_group[18];
+ u8 irq_type[SAM_MAX_NGPIO];
+ struct sam_platform_data *pdata;
+ const char **names;
+ u32 *export_flags;
+};
+#define to_sam(chip) container_of((chip), struct sam_gpio, gpio)
+
+static void sam_gpio_bitop(struct sam_gpio *sam, unsigned int nr,
+ u32 bit, bool set)
+{
+ u32 reg;
+
+ reg = ioread32(SAM_GPIO_ADDR(sam->base, nr));
+ if (set)
+ reg |= bit;
+ else
+ reg &= ~bit;
+ iowrite32(reg, SAM_GPIO_ADDR(sam->base, nr));
+ ioread32(SAM_GPIO_ADDR(sam->base, nr));
+}
+
+static int sam_gpio_debounce(struct gpio_chip *chip, unsigned int nr,
+ unsigned int debounce)
+{
+ struct sam_gpio *sam = to_sam(chip);
+
+ sam_gpio_bitop(sam, nr, SAM_GPIO_DEBOUNCE_EN, debounce);
+
+ return 0;
+}
+
+static void sam_gpio_set(struct gpio_chip *chip, unsigned int nr, int val)
+{
+ struct sam_gpio *sam = to_sam(chip);
+
+ sam_gpio_bitop(sam, nr, SAM_GPIO_OUT, val);
+}
+
+static int sam_gpio_get(struct gpio_chip *chip, unsigned int nr)
+{
+ struct sam_gpio *sam = to_sam(chip);
+
+ return !!(ioread32(SAM_GPIO_ADDR(sam->base, nr)) & SAM_GPIO_IN);
+}
+
+static int sam_gpio_direction_output(struct gpio_chip *chip, unsigned int nr,
+ int val)
+{
+ struct sam_gpio *sam = to_sam(chip);
+
+ sam_gpio_bitop(sam, nr, SAM_GPIO_OUT, val);
+ sam_gpio_bitop(sam, nr, SAM_GPIO_OUT_TS, false);
+ return 0;
+}
+
+static int sam_gpio_direction_input(struct gpio_chip *chip, unsigned int nr)
+{
+ struct sam_gpio *sam = to_sam(chip);
+
+ sam_gpio_bitop(sam, nr, SAM_GPIO_OUT_TS, true);
+ sam_gpio_bitop(sam, nr, SAM_GPIO_OUT, false);
+ return 0;
+}
+
+static void sam_gpio_setup(struct sam_gpio *sam)
+{
+ struct gpio_chip *chip = &sam->gpio;
+
+ chip->parent = sam->dev;
+ chip->label = dev_name(sam->dev);
+ chip->owner = THIS_MODULE;
+ chip->direction_input = sam_gpio_direction_input;
+ chip->get = sam_gpio_get;
+ chip->direction_output = sam_gpio_direction_output;
+ chip->set = sam_gpio_set;
+ chip->set_debounce = sam_gpio_debounce;
+ chip->dbg_show = NULL;
+ chip->base = sam->gpio_base;
+ chip->ngpio = sam->gpio_count;
+#ifdef CONFIG_OF_GPIO
+ chip->of_node = sam->dev->of_node;
+#endif
+ chip->names = sam->names;
+}
+
+static int sam_of_get_exports(struct device *dev, struct sam_gpio *sam)
+{
+ struct device_node *child, *exports;
+ int err = 0;
+
+ if (dev->of_node == NULL)
+ return 0; /* No FDT node, we are done */
+
+ exports = of_get_child_by_name(dev->of_node, "gpio-exports");
+ if (exports == NULL)
+ return 0; /* No exports, we are done */
+
+ if (of_get_child_count(exports) == 0)
+ return 0; /* No children, we are done */
+
+ sam->names = devm_kzalloc(dev, sizeof(char *) * sam->gpio_count,
+ GFP_KERNEL);
+ if (sam->names == NULL) {
+ err = -ENOMEM;
+ goto error;
+ }
+ sam->export_flags =
+ devm_kzalloc(dev, sizeof(u32) * sam->gpio_count, GFP_KERNEL);
+ if (sam->export_flags == NULL) {
+ err = -ENOMEM;
+ goto error;
+ }
+ for_each_child_of_node(exports, child) {
+ const char *label;
+ u32 pin, flags;
+
+ label = of_get_property(child, "label", NULL) ? : child->name;
+ err = of_property_read_u32_index(child, "pin", 0, &pin);
+ if (err)
+ break;
+ if (pin >= sam->gpio_count) {
+ err = -EINVAL;
+ break;
+ }
+ err = of_property_read_u32_index(child, "pin", 1, &flags);
+ if (err)
+ break;
+ /*
+ * flags:
+ * GPIOF_DIR_IN bit 0=1
+ * GPIOF_DIR_OUT bit 0=0
+ * GPIOF_INIT_HIGH bit 1=1
+ * GPIOF_ACTIVE_LOW bit 2=1
+ * GPIOF_OPEN_DRAIN bit 3=1
+ * GPIOF_OPEN_SOURCE bit 4=1
+ * GPIOF_EXPORT bit 5=1
+ * GPIOF_EXPORT_CHANGEABLE bit 6=1
+ */
+ sam->names[pin] = label;
+ sam->export_flags[pin] = flags;
+ }
+error:
+ of_node_put(exports);
+ return err;
+}
+
+static int sam_gpio_of_init(struct device *dev, struct sam_gpio *sam)
+{
+ int err;
+ u32 val;
+ const u32 *igroup;
+ u32 group, start, count;
+ int i, iglen, ngpio;
+
+ if (of_have_populated_dt() && !dev->of_node) {
+ dev_err(dev, "No device node\n");
+ return -ENODEV;
+ }
+
+ err = of_property_read_u32(dev->of_node, "gpio-base", &val);
+ if (err)
+ val = -1;
+ sam->gpio_base = val;
+
+ err = of_property_read_u32(dev->of_node, "gpio-count", &val);
+ if (!err) {
+ if (val > SAM_MAX_NGPIO)
+ val = SAM_MAX_NGPIO;
+ sam->gpio_count = val;
+ }
+ /* validate gpio_count against chip data. Abort if chip data is bad. */
+ ngpio = ioread32(sam->base + 2 * sizeof(u32)) & 0xffff;
+ if (!ngpio || ngpio > SAM_MAX_NGPIO)
+ return -ENODEV;
+
+ if (!sam->gpio_count || sam->gpio_count > ngpio)
+ sam->gpio_count = ngpio;
+
+ igroup = of_get_property(dev->of_node, "gpio-interrupts", &iglen);
+ if (igroup) {
+ iglen /= sizeof(u32);
+ if (iglen < 3 || iglen % 3)
+ return -EINVAL;
+ iglen /= 3;
+ for (i = 0; i < iglen; i++) {
+ group = be32_to_cpu(igroup[i * 3]);
+ if (group >= ARRAY_SIZE(sam->irq_group))
+ return -EINVAL;
+ start = be32_to_cpu(igroup[i * 3 + 1]);
+ count = be32_to_cpu(igroup[i * 3 + 2]);
+ if (start >= sam->gpio_count || count == 0 ||
+ start + count > sam->gpio_count)
+ return -EINVAL;
+ sam->irq_group[group].start = start;
+ sam->irq_group[group].count = count;
+ }
+ }
+
+ err = sam_of_get_exports(dev, sam);
+ return err;
+}
+
+static int sam_gpio_pin_to_irq_bit(struct sam_gpio *sam, int pin)
+{
+ int bit;
+
+ for (bit = 0; bit < ARRAY_SIZE(sam->irq_group); bit++) {
+ struct sam_gpio_irq_group *irq_group = &sam->irq_group[bit];
+
+ if (irq_group->count &&
+ pin >= irq_group->start &&
+ pin <= irq_group->start + irq_group->count)
+ return bit;
+ }
+ return -EINVAL;
+}
+
+static bool sam_gpio_irq_handle_group(struct sam_gpio *sam,
+ struct sam_gpio_irq_group *irq_group)
+{
+ unsigned int virq = 0;
+ bool handled = false;
+ bool repeat;
+ int i;
+
+ /* no irq_group for the interrupt bit */
+ if (!irq_group->count)
+ return false;
+
+ WARN_ON(irq_group->num_enabled == 0);
+ do {
+ repeat = false;
+ for (i = 0; i < irq_group->count; i++) {
+ int pin = irq_group->start + i;
+ bool low, high;
+ u32 regval;
+ u8 type;
+
+ regval = ioread32(SAM_GPIO_ADDR(sam->base, pin));
+ /*
+ * write back status to clear POS_EDGE and NEG_EDGE
+ * status for this GPIO pin (status bits are
+ * clear-on-one). This is necessary to clear the
+ * high level interrupt status.
+ * Also consider the interrupt to be handled in that
+ * case, even if there is no taker.
+ */
+ if (regval & (SAM_GPIO_POS_EDGE | SAM_GPIO_NEG_EDGE)) {
+ iowrite32(regval,
+ SAM_GPIO_ADDR(sam->base, pin));
+ ioread32(SAM_GPIO_ADDR(sam->base, pin));
+ handled = true;
+ }
+
+ /*
+ * Check if the pin changed its state.
+ * If it did, and if the expected condition applies,
+ * generate a virtual interrupt.
+ * A pin can only generate an interrupt if
+ * - interrupts are enabled for it
+ * - it is configured as input
+ */
+
+ if (!sam->irq_type[pin])
+ continue;
+ if (!(regval & SAM_GPIO_OUT_TS))
+ continue;
+
+ high = regval & (SAM_GPIO_IN | SAM_GPIO_POS_EDGE);
+ low = !(regval & SAM_GPIO_IN) ||
+ (regval & SAM_GPIO_NEG_EDGE);
+ type = sam->irq_type[pin];
+ if (((type & IRQ_TYPE_EDGE_RISING) &&
+ (regval & SAM_GPIO_POS_EDGE)) ||
+ ((type & IRQ_TYPE_EDGE_FALLING) &&
+ (regval & SAM_GPIO_NEG_EDGE)) ||
+ ((type & IRQ_TYPE_LEVEL_LOW) && low) ||
+ ((type & IRQ_TYPE_LEVEL_HIGH) && high)) {
+ virq = irq_find_mapping(sam->domain, pin);
+ handle_nested_irq(virq);
+ if (type & (IRQ_TYPE_LEVEL_LOW
+ | IRQ_TYPE_LEVEL_HIGH))
+ repeat = true;
+ }
+ }
+ schedule();
+ } while (repeat);
+
+ return handled;
+}
+
+static irqreturn_t sam_gpio_irq_handler(int irq, void *data)
+{
+ struct sam_gpio *sam = data;
+ struct sam_platform_data *pdata = sam->pdata;
+ irqreturn_t ret = IRQ_NONE;
+ bool handled;
+ u32 status;
+
+ do {
+ handled = false;
+ status = pdata->irq_status(sam->dev->parent, SAM_IRQ_GPIO,
+ sam->irq);
+ pdata->irq_status_clear(sam->dev->parent, SAM_IRQ_GPIO,
+ sam->irq, status);
+ while (status) {
+ unsigned int bit;
+
+ bit = __ffs(status);
+ status &= ~(1 << bit);
+ handled =
+ sam_gpio_irq_handle_group(sam, &sam->irq_group[bit]);
+ if (handled)
+ ret = IRQ_HANDLED;
+ }
+ } while (handled);
+
+ return ret;
+}
+
+static int sam_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
+{
+ struct sam_gpio *sam = to_sam(chip);
+
+ return irq_create_mapping(sam->domain, offset);
+}
+
+static void sam_irq_mask(struct irq_data *data)
+{
+ struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+ struct sam_platform_data *pdata = sam->pdata;
+ int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
+
+ if (bit < 0)
+ return;
+
+ if (--sam->irq_group[bit].num_enabled <= 0) {
+ pdata->disable_irq(sam->dev->parent, SAM_IRQ_GPIO, sam->irq,
+ 1 << bit);
+ }
+}
+
+static void sam_irq_unmask(struct irq_data *data)
+{
+ struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+ struct sam_platform_data *pdata = sam->pdata;
+ int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
+
+ if (bit < 0)
+ return;
+
+ sam->irq_group[bit].num_enabled++;
+ pdata->enable_irq(sam->dev->parent, SAM_IRQ_GPIO, sam->irq, 1 << bit);
+}
+
+static int sam_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+ int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
+
+ if (bit < 0)
+ return bit;
+
+ sam->irq_type[data->hwirq] = type & 0x0f;
+ sam_gpio_bitop(sam, data->hwirq, SAM_GPIO_OUT_TS, true);
+ sam_gpio_bitop(sam, data->hwirq, SAM_GPIO_DEBOUNCE_EN, type & 0x10);
+ sam_gpio_bitop(sam, data->hwirq,
+ SAM_GPIO_POS_EDGE_EN | SAM_GPIO_POS_EDGE,
+ type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_HIGH));
+ sam_gpio_bitop(sam, data->hwirq,
+ SAM_GPIO_NEG_EDGE_EN | SAM_GPIO_NEG_EDGE,
+ type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_LEVEL_LOW));
+
+ return 0;
+}
+
+static void sam_irq_bus_lock(struct irq_data *data)
+{
+ struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+
+ mutex_lock(&sam->irq_lock);
+}
+
+static void sam_irq_bus_unlock(struct irq_data *data)
+{
+ struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+
+ /* Synchronize interrupts to chip */
+
+ mutex_unlock(&sam->irq_lock);
+}
+
+static struct irq_chip sam_irq_chip = {
+ .name = "gpio-sam",
+ .irq_mask = sam_irq_mask,
+ .irq_unmask = sam_irq_unmask,
+ .irq_set_type = sam_irq_set_type,
+ .irq_bus_lock = sam_irq_bus_lock,
+ .irq_bus_sync_unlock = sam_irq_bus_unlock,
+};
+
+static int sam_gpio_irq_map(struct irq_domain *domain, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ irq_set_chip_data(irq, domain->host_data);
+ irq_set_chip(irq, &sam_irq_chip);
+ irq_set_nested_thread(irq, true);
+
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops sam_gpio_irq_domain_ops = {
+ .map = sam_gpio_irq_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+static int sam_gpio_irq_setup(struct device *dev, struct sam_gpio *sam)
+{
+ int ret;
+
+ sam->domain = irq_domain_add_linear(dev->of_node,
+ sam->gpio_count,
+ &sam_gpio_irq_domain_ops,
+ sam);
+ if (sam->domain == NULL)
+ return -ENOMEM;
+
+ ret = devm_request_threaded_irq(dev, sam->irq, NULL,
+ sam_gpio_irq_handler,
+ IRQF_ONESHOT,
+ dev_name(dev), sam);
+ if (ret)
+ goto out_remove_domain;
+
+ sam->gpio.to_irq = sam_gpio_to_irq;
+
+ if (!try_module_get(dev->parent->driver->owner)) {
+ ret = -EINVAL;
+ goto out_remove_domain;
+ }
+
+ return 0;
+
+out_remove_domain:
+ irq_domain_remove(sam->domain);
+ sam->domain = NULL;
+ return ret;
+}
+
+static void sam_gpio_irq_teardown(struct device *dev, struct sam_gpio *sam)
+{
+ int i, irq;
+ struct sam_platform_data *pdata = sam->pdata;
+
+ pdata->disable_irq(dev->parent, SAM_IRQ_GPIO, sam->irq, 0xffffffff);
+
+ for (i = 0; i < sam->gpio_count; i++) {
+ irq = irq_find_mapping(sam->domain, i);
+ if (irq > 0)
+ irq_dispose_mapping(irq);
+ }
+ irq_domain_remove(sam->domain);
+ module_put(dev->parent->driver->owner);
+}
+
+static int sam_gpio_unexport(struct sam_gpio *sam)
+{
+ int i;
+
+ if (!sam->export_flags)
+ return 0;
+
+ /* un-export all auto-exported pins */
+ for (i = 0; i < sam->gpio_count; i++) {
+ struct gpio_desc *desc = gpio_to_desc(sam->gpio.base + i);
+
+ if (desc == NULL)
+ continue;
+
+ if (sam->export_flags[i] & GPIOF_EXPORT)
+ gpiochip_free_own_desc(desc);
+ }
+ return 0;
+}
+
+static int sam_gpio_export(struct sam_gpio *sam)
+{
+ int i, ret;
+
+ if (!sam->export_flags)
+ return 0;
+
+ /* auto-export pins as requested */
+
+ for (i = 0; i < sam->gpio_count; i++) {
+ u32 flags = sam->export_flags[i];
+ struct gpio_desc *desc;
+
+ /* request and initialize exported pins */
+ if (!(flags & GPIOF_EXPORT))
+ continue;
+
+ desc = gpiochip_request_own_desc(&sam->gpio, i, "sam-export");
+ if (IS_ERR(desc)) {
+ ret = PTR_ERR(desc);
+ goto error;
+ }
+ if (flags & GPIOF_DIR_IN) {
+ ret = gpiod_direction_input(desc);
+ if (ret)
+ goto error;
+ } else {
+ ret = gpiod_direction_output(desc, flags &
+ (GPIOF_OUT_INIT_HIGH |
+ GPIOF_ACTIVE_LOW));
+ if (ret)
+ goto error;
+ }
+ ret = gpiod_export(desc, flags & GPIOF_EXPORT_CHANGEABLE);
+
+ if (ret)
+ goto error;
+ }
+ return 0;
+
+error:
+ sam_gpio_unexport(sam);
+ return ret;
+}
+
+static int sam_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sam_gpio *sam;
+ struct resource *res;
+ int ret;
+ struct sam_platform_data *pdata = dev_get_platdata(&pdev->dev);
+
+ sam = devm_kzalloc(dev, sizeof(*sam), GFP_KERNEL);
+ if (sam == NULL)
+ return -ENOMEM;
+
+ sam->dev = dev;
+ sam->pdata = pdata;
+ platform_set_drvdata(pdev, sam);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ sam->irq = platform_get_irq(pdev, 0);
+ sam->irq_high = platform_get_irq(pdev, 1);
+
+ sam->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+ if (!sam->base)
+ return -ENOMEM;
+
+ mutex_init(&sam->irq_lock);
+
+ ret = sam_gpio_of_init(dev, sam);
+ if (ret)
+ return ret;
+
+ sam_gpio_setup(sam);
+
+ if (pdata && sam->irq >= 0 && of_find_property(dev->of_node,
+ "interrupt-controller", NULL)) {
+ ret = sam_gpio_irq_setup(dev, sam);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = gpiochip_add(&sam->gpio);
+ if (ret)
+ goto teardown;
+
+ ret = sam_gpio_export(sam);
+ if (ret)
+ goto teardown_remove;
+
+ return 0;
+
+teardown_remove:
+ gpiochip_remove(&sam->gpio);
+
+teardown:
+ if (sam->domain)
+ sam_gpio_irq_teardown(dev, sam);
+ return ret;
+}
+
+static int sam_gpio_remove(struct platform_device *pdev)
+{
+ struct sam_gpio *sam = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ dev_info(dev, "remove\n");
+
+ sam_gpio_unexport(sam);
+
+ if (sam->domain)
+ sam_gpio_irq_teardown(dev, sam);
+
+ gpiochip_remove(&sam->gpio);
+
+ return 0;
+}
+
+static const struct of_device_id sam_gpio_ids[] = {
+ { .compatible = "jnx,gpio-sam", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sam_gpio_ids);
+
+static struct platform_driver sam_gpio_driver = {
+ .driver = {
+ .name = "gpio-sam",
+ .owner = THIS_MODULE,
+ .of_match_table = sam_gpio_ids,
+ },
+ .probe = sam_gpio_probe,
+ .remove = sam_gpio_remove,
+};
+
+module_platform_driver(sam_gpio_driver);
+
+MODULE_DESCRIPTION("SAM FPGA GPIO Driver");
+MODULE_LICENSE("GPL");
--
1.9.1
From: Georgi Vlaev <[email protected]>
Add device tree bindings document for the SAM MDIO block
present in Juniper's SAM FPGA.
Signed-off-by: Georgi Vlaev <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
Documentation/devicetree/bindings/net/mdio-sam.txt | 48 ++++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/mdio-sam.txt
diff --git a/Documentation/devicetree/bindings/net/mdio-sam.txt b/Documentation/devicetree/bindings/net/mdio-sam.txt
new file mode 100644
index 0000000..7d354e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/mdio-sam.txt
@@ -0,0 +1,48 @@
+Juniper SAM FPGA MFD MDIO bus properties.
+
+Required properties:
+- compatible : "jnx,mdio-sam"
+- reg : The start offset of the MDIO bus range
+- #address-cells = <1>;
+- #size-cells = <0>;
+
+Optional properties:
+
+Required properties for child nodes:
+- #address-cells = <1>;
+- #size-cells = <0>;
+- reg : The MDIO bus offset within the MDIO range.
+
+
+Example :
+
+ sam@10 {
+ compatible = "jnx,sam";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ mdio-sam@10 {
+ compatible = "jnx,mdio-sam";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x40000>;
+
+ 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
From: Georgi Vlaev <[email protected]>
Add driver for the MDIO IP block present in Juniper's
SAM FPGA.
This driver supports only Clause 45 of the 802.3 spec.
Note that due to the fact that there are no drivers for
Broadcom/Avago retimers on 10/40Ge path that are controlled
from the MDIO interface there is a method to have direct
access to registers via a debugfs interface.
Signed-off-by: Georgi Vlaev <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
drivers/net/phy/Kconfig | 8 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/mdio-sam.c | 564 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 573 insertions(+)
create mode 100644 drivers/net/phy/mdio-sam.c
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 5078a0d..7d7f265 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -122,6 +122,14 @@ config MDIO_OCTEON
buses. It is required by the Octeon and ThunderX ethernet device
drivers on some systems.
+config MDIO_SAM
+ tristate "Juniper Networks SAM FPGA MDIO controller"
+ depends on MFD_JUNIPER_SAM
+ help
+ This module provides a driver for the Juniper Network SAM FPGA MDIO
+ buses. This hardware can be found in the Gladiator PIC SAM FPGA. This
+ driver is client of the sam-core MFD driver.
+
config MDIO_SUN4I
tristate "Allwinner sun4i MDIO interface support"
depends on ARCH_SUNXI
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index e58667d..c7631cf 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o
obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o
obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
+obj-$(CONFIG_MDIO_SAM) += mdio-sam.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o
diff --git a/drivers/net/phy/mdio-sam.c b/drivers/net/phy/mdio-sam.c
new file mode 100644
index 0000000..73cefa1
--- /dev/null
+++ b/drivers/net/phy/mdio-sam.c
@@ -0,0 +1,564 @@
+/*
+ * Juniper Networks SAM FPGA MDIO driver.
+ *
+ * Copyright (c) 2015, Juniper Networks
+ * Author: Georgi Vlaev <[email protected]>
+ *
+ * The MDIO bus driver supports GPQAM, GPCAM, GPQ28 FPGAs found
+ * on Juniper's 10/40/100GE Gladiator PIC cards. Only Clause 45
+ * access is currently available natively.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#endif
+
+#define MDIO_CMD1 0x0000 /* Command Table 1 */
+#define MDIO_CMD2 0x0800 /* Command Table 2 */
+#define MDIO_RESULT 0x1000 /* Result Table (RO) */
+#define MDIO_PRI_CMD1 0x1800 /* Priority Command Table 1 */
+#define MDIO_PRI_CMD2 0x2000 /* Priority Command Table 1 */
+#define MDIO_PRI_RESULT 0x2800 /* Priority Result Table (RO) */
+#define MDIO_TBL_CMD 0x3000 /* Table Command Register (WO) */
+#define MDIO_STATUS 0x3008 /* Master Status (RO) */
+#define MDIO_STATUS_INT 0x3010 /* Master Status Interrupt Mask (W1C) */
+
+/* MDIO_TBL_CMD */
+#define TBL_CMD_REG_ABORT BIT(31) /* Regular Table ABORT */
+#define TBL_CMD_REG_GO BIT(30) /* Regular Table GO */
+#define TBL_CMD_PRI_ABORT BIT(29) /* Priority Table Abort */
+#define TBL_CMD_PRI_GO BIT(28) /* Priority Table GO */
+#define TBL_CMD_SOFT_RESET BIT(27) /* Soft Reset */
+
+/* MDIO_STATUS */
+#define STAT_REG_RDY BIT(31) /* READY for Programming Regular Table */
+#define STAT_REG_DONE BIT(30) /* DONE SUCCESSFULLY WITH REGULAR TABLE */
+#define STAT_PRI_RDY BIT(29) /* READY for Programming Priority Table */
+#define STAT_PRI_DONE BIT(28) /* DONE SUCCESSFULLY WITH PRIORITY TABLE */
+#define STAT_REG_ERR BIT(27) /* DONE WITH ERRORS for Regular Table */
+#define STAT_PRI_ERR BIT(26) /* DONE WITH ERRORS for Priority Table */
+#define STAT_REG_PROG_ERR BIT(25) /* Programming Err for Regular Table */
+#define STAT_PRI_PROG_ERR BIT(24) /* Programming Err for Priority Table */
+
+/* MDIO_CMD2, MDIO_PRI_CMD2 */
+#define CMD2_ENABLE BIT(17)
+#define CMD2_READ BIT(16)
+
+/* MDIO_RESULT, MDIO_PRI_RESULT */
+#define RES_SUCCESS BIT(17)
+#define RES_ERROR BIT(16)
+
+#define MDIO_RDY_TMO 30 /* in msec */
+
+struct mdio_sam_data {
+ void __iomem *base;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dir;
+ /* Clause 45 addressing
+ * addr[5]: PHYAD
+ * reg[21]: DEVAD 5 bits + REG 16 bits
+ * value[16]
+ */
+ u8 addr; /* phyad */
+ u32 reg; /* devad + reg = (devad & 0x1f) << 16 | reg */
+ u16 value; /* value */
+#endif
+};
+
+/* raw_io binary attribute: read/write any device on the bus */
+static ssize_t
+mdio_sam_sysfs_write_raw_io(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *attr,
+ char *buf, loff_t offset, size_t size)
+{
+ struct mii_bus *bus = to_mii_bus(container_of(kobj,
+ struct device, kobj));
+ int ret;
+ u32 reg = offset & 0x1fffff;
+ u8 phy = (offset >> 21) & 0x1f;
+
+ if (size != 2)
+ return -EINVAL;
+
+ ret = mdiobus_write(bus, phy, reg | MII_ADDR_C45, *(u16 *)buf);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static ssize_t
+mdio_sam_sysfs_read_raw_io(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *attr,
+ char *buf, loff_t offset, size_t size)
+{
+ struct mii_bus *bus = to_mii_bus(container_of(kobj,
+ struct device, kobj));
+ int ret;
+ u32 reg = offset & 0x1fffff;
+ u8 phy = (offset >> 21) & 0x1f;
+
+ if (size != 2)
+ return -EINVAL;
+
+ ret = mdiobus_read(bus, phy, reg | MII_ADDR_C45);
+ if (ret < 0)
+ return ret;
+
+ *(u16 *)buf = (u16)ret;
+
+ return size;
+}
+
+static struct bin_attribute bin_attr_raw_io = {
+ .attr = {.name = "raw_io", .mode = (S_IRUGO | S_IWUSR)},
+ .size = 0x4000000,
+ .read = mdio_sam_sysfs_read_raw_io,
+ .write = mdio_sam_sysfs_write_raw_io,
+};
+
+#ifdef CONFIG_DEBUG_FS
+/* debugfs: set/get register offset */
+static int mdio_sam_debugfs_addr_print(struct seq_file *s, void *p)
+{
+ struct mdio_sam_data *data = (struct mdio_sam_data *)s->private;
+
+ seq_printf(s, "0x%02x\n", data->addr);
+
+ return 0;
+}
+
+static int mdio_sam_debugfs_addr_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mdio_sam_debugfs_addr_print, inode->i_private);
+}
+
+static ssize_t
+mdio_sam_debugfs_addr_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct mdio_sam_data *data =
+ ((struct seq_file *)(file->private_data))->private;
+ unsigned long addr;
+ int err;
+
+ err = kstrtoul_from_user(user_buf, count, 0, &addr);
+ if (err)
+ return err;
+
+ if (addr > 0x1f)
+ return -EINVAL;
+
+ data->addr = (u8)(addr);
+
+ return count;
+}
+
+static const struct file_operations mdio_sam_debugfs_addr_fops = {
+ .open = mdio_sam_debugfs_addr_open,
+ .write = mdio_sam_debugfs_addr_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+/* debugfs: set/get register offset */
+static int mdio_sam_debugfs_reg_print(struct seq_file *s, void *p)
+{
+ struct mdio_sam_data *data = (struct mdio_sam_data *)s->private;
+
+ seq_printf(s, "0x%06X\n", data->reg);
+
+ return 0;
+}
+
+static int mdio_sam_debugfs_reg_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mdio_sam_debugfs_reg_print, inode->i_private);
+}
+
+static ssize_t
+mdio_sam_debugfs_reg_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct mdio_sam_data *data =
+ ((struct seq_file *)(file->private_data))->private;
+ unsigned long reg;
+ int err;
+
+ err = kstrtoul_from_user(user_buf, count, 0, ®);
+ if (err)
+ return err;
+
+ if (reg > 0x1fffff)
+ return -EINVAL;
+
+ data->reg = reg;
+
+ return count;
+}
+
+static const struct file_operations mdio_sam_debugfs_reg_fops = {
+ .open = mdio_sam_debugfs_reg_open,
+ .write = mdio_sam_debugfs_reg_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+/* debugfs: set/get register value */
+static int mdio_sam_debugfs_val_print(struct seq_file *s, void *p)
+{
+ struct mii_bus *bus = (struct mii_bus *)s->private;
+ struct mdio_sam_data *data = bus->priv;
+ int ret;
+
+ ret = mdiobus_read(bus, data->addr, data->reg | MII_ADDR_C45);
+ if (ret < 0)
+ return ret;
+
+ seq_printf(s, "0x%04X\n", ret);
+ return 0;
+}
+
+static int mdio_sam_debugfs_val_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mdio_sam_debugfs_val_print, inode->i_private);
+}
+
+static ssize_t
+mdio_sam_debugfs_val_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct mii_bus *bus =
+ ((struct seq_file *)(file->private_data))->private;
+ struct mdio_sam_data *data = bus->priv;
+ unsigned long value;
+ int ret;
+
+ ret = kstrtoul_from_user(user_buf, count, 0, &value);
+ if (ret)
+ return ret;
+
+ ret = mdiobus_write(bus, data->addr, data->reg | MII_ADDR_C45, value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static const struct file_operations mdio_sam_debugfs_val_fops = {
+ .open = mdio_sam_debugfs_val_open,
+ .write = mdio_sam_debugfs_val_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+static int mdio_sam_debugfs_init(struct mii_bus *bus)
+{
+ struct dentry *file;
+ struct mdio_sam_data *data = bus->priv;
+
+ data->dir = debugfs_create_dir(bus->id, NULL);
+ if (!data->dir)
+ return -ENOMEM;
+
+/* phy */
+ file = debugfs_create_file("addr", (S_IRUGO | S_IWUSR),
+ data->dir, data,
+ &mdio_sam_debugfs_addr_fops);
+ if (!file)
+ goto err;
+
+/* reg */
+ file = debugfs_create_file("reg", (S_IRUGO | S_IWUSR),
+ data->dir, data,
+ &mdio_sam_debugfs_reg_fops);
+ if (!file)
+ goto err;
+
+/* value */
+ file = debugfs_create_file("value", (S_IRUGO | S_IWUSR),
+ data->dir, bus,
+ &mdio_sam_debugfs_val_fops);
+ if (!file)
+ goto err;
+
+ return 0;
+err:
+ debugfs_remove_recursive(data->dir);
+ dev_err(&bus->dev, "failed to create debugfs entries.\n");
+
+ return -ENOMEM;
+}
+
+static void mdio_sam_debugfs_remove(struct mii_bus *bus)
+{
+ struct mdio_sam_data *data = bus->priv;
+
+ debugfs_remove_recursive(data->dir);
+}
+#endif
+
+static int mdio_sam_stat_wait(struct mii_bus *bus, u32 wait_mask)
+{
+ struct mdio_sam_data *data = bus->priv;
+ unsigned long timeout;
+ u32 stat;
+
+ timeout = jiffies + msecs_to_jiffies(MDIO_RDY_TMO);
+ do {
+ stat = ioread32(data->base + MDIO_STATUS);
+ if (stat & wait_mask)
+ return 0;
+
+ usleep_range(50, 100);
+ } while (time_before(jiffies, timeout));
+
+ return -EBUSY;
+}
+
+static int mdio_sam_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+ struct mdio_sam_data *data = bus->priv;
+ u32 command, res;
+ int ret;
+
+ /* mdiobus_read holds the bus->mdio_lock mutex */
+
+ if (!(regnum & MII_ADDR_C45))
+ return -ENXIO;
+
+ ret = mdio_sam_stat_wait(bus, STAT_REG_RDY);
+ if (ret < 0)
+ return ret;
+
+ command = regnum & 0x1fffff; /* regnum = (dev_id << 16) | reg */
+ command |= ((phy_id & 0x1f) << 21);
+
+ iowrite32(command, data->base + MDIO_CMD1);
+ ioread32(data->base + MDIO_CMD1);
+ iowrite32(CMD2_READ | CMD2_ENABLE, data->base + MDIO_CMD2);
+ ioread32(data->base + MDIO_CMD2);
+ iowrite32(TBL_CMD_REG_GO, data->base + MDIO_TBL_CMD);
+ ioread32(data->base + MDIO_TBL_CMD);
+
+ usleep_range(50, 100);
+
+ ret = mdio_sam_stat_wait(bus, (STAT_REG_DONE | STAT_REG_ERR));
+ if (ret < 0)
+ return ret;
+
+ res = ioread32(data->base + MDIO_RESULT);
+
+ if (res & RES_ERROR || !(res & RES_SUCCESS))
+ return -EIO;
+
+ return (res & 0xffff);
+}
+
+static int mdio_sam_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
+{
+ struct mdio_sam_data *data = bus->priv;
+ u32 command;
+ int ret;
+
+ /* mdiobus_write holds the bus->mdio_lock mutex */
+
+ if (!(regnum & MII_ADDR_C45))
+ return -ENXIO;
+
+ ret = mdio_sam_stat_wait(bus, STAT_REG_RDY);
+ if (ret < 0)
+ return ret;
+
+ command = regnum & 0x1fffff; /* regnum = (dev_id << 16) | reg */
+ command |= ((phy_id & 0x1f) << 21);
+
+ iowrite32(command, data->base + MDIO_CMD1);
+ ioread32(data->base + MDIO_CMD1);
+ iowrite32(CMD2_ENABLE | val, data->base + MDIO_CMD2);
+ ioread32(data->base + MDIO_CMD2);
+ iowrite32(TBL_CMD_REG_GO, data->base + MDIO_TBL_CMD);
+ ioread32(data->base + MDIO_TBL_CMD);
+
+ usleep_range(50, 100);
+
+ ret = mdio_sam_stat_wait(bus, (STAT_REG_DONE | STAT_REG_ERR));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int mdio_sam_reset(struct mii_bus *bus)
+{
+ struct mdio_sam_data *data = bus->priv;
+
+ iowrite32(TBL_CMD_SOFT_RESET, data->base + MDIO_TBL_CMD);
+ ioread32(data->base + MDIO_TBL_CMD);
+ mdelay(10);
+ iowrite32(0, data->base + MDIO_TBL_CMD);
+ ioread32(data->base + MDIO_TBL_CMD);
+
+ /* zero tables */
+ memset_io(data->base + MDIO_CMD1, 0, 0x1000);
+ memset_io(data->base + MDIO_PRI_CMD1, 0, 0x1000);
+
+ return 0;
+}
+
+static int mdio_sam_of_register_bus(struct platform_device *pdev,
+ struct device_node *np, void __iomem *base)
+{
+ struct mii_bus *bus;
+ struct mdio_sam_data *data;
+ u32 reg;
+ int ret;
+
+ bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*data));
+ if (!bus)
+ return -ENOMEM;
+
+ /* bus offset */
+ ret = of_property_read_u32(np, "reg", ®);
+ if (ret)
+ return -ENODEV;
+
+ data = bus->priv;
+ data->base = base + reg;
+
+ bus->parent = &pdev->dev;
+ bus->name = "mdio-sam";
+ bus->read = mdio_sam_read;
+ bus->write = mdio_sam_write;
+ bus->reset = mdio_sam_reset;
+ snprintf(bus->id, MII_BUS_ID_SIZE, "mdiosam-%x-%x", pdev->id, reg);
+
+ ret = of_mdiobus_register(bus, np);
+ if (ret < 0)
+ return ret;
+#ifdef CONFIG_DEBUG_FS
+ ret = mdio_sam_debugfs_init(bus);
+ if (ret < 0)
+ goto err_unregister;
+#endif
+ ret = device_create_bin_file(&bus->dev, &bin_attr_raw_io);
+ if (ret)
+ goto err_debugfs;
+
+ return 0;
+
+err_debugfs:
+#ifdef CONFIG_DEBUG_FS
+ mdio_sam_debugfs_remove(bus);
+#endif
+err_unregister:
+ mdiobus_unregister(bus);
+
+ return ret;
+}
+
+static int mdio_sam_of_unregister_bus(struct device_node *np)
+{
+ struct mii_bus *bus;
+
+ bus = of_mdio_find_bus(np);
+ if (bus) {
+ device_remove_bin_file(&bus->dev, &bin_attr_raw_io);
+#ifdef CONFIG_DEBUG_FS
+ mdio_sam_debugfs_remove(bus);
+#endif
+ mdiobus_unregister(bus);
+ }
+ return 0;
+}
+
+static int mdio_sam_probe(struct platform_device *pdev)
+{
+ struct device_node *np;
+ struct resource *res;
+ void __iomem *base;
+ int ret;
+
+ if (!pdev->dev.of_node)
+ return -ENODEV;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ for_each_available_child_of_node(pdev->dev.of_node, np) {
+ ret = mdio_sam_of_register_bus(pdev, np, base);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+err:
+ /* roll back everything */
+ for_each_available_child_of_node(pdev->dev.of_node, np)
+ mdio_sam_of_unregister_bus(np);
+
+ return ret;
+}
+
+static int mdio_sam_remove(struct platform_device *pdev)
+{
+ struct device_node *np;
+
+ for_each_available_child_of_node(pdev->dev.of_node, np)
+ mdio_sam_of_unregister_bus(np);
+
+ return 0;
+}
+
+static const struct of_device_id mdio_sam_of_match[] = {
+ { .compatible = "jnx,mdio-sam" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mdio_sam_of_match);
+
+static struct platform_driver mdio_sam_driver = {
+ .probe = mdio_sam_probe,
+ .remove = mdio_sam_remove,
+ .driver = {
+ .name = "mdio-sam",
+ .owner = THIS_MODULE,
+ .of_match_table = mdio_sam_of_match,
+ },
+};
+
+module_platform_driver(mdio_sam_driver);
+
+MODULE_ALIAS("platform:mdio-sam");
+MODULE_AUTHOR("Georgi Vlaev <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Juniper Networks SAM MDIO bus driver");
--
1.9.1
From: Georgi Vlaev <[email protected]>
Add device tree bindings document for the GPIO driver of
Juniper's SAM FPGA.
Signed-off-by: Georgi Vlaev <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
.../devicetree/bindings/gpio/jnx,gpio-sam.txt | 110 +++++++++++++++++++++
1 file changed, 110 insertions(+)
create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
diff --git a/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt b/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
new file mode 100644
index 0000000..514c350
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
@@ -0,0 +1,110 @@
+Juniper SAM FPGA GPIO block
+
+The controller's registers are organized as sets of eight 32-bit
+registers with each set controlling a bank of up to 32 pins. A single
+interrupt is shared for all of the banks handled by the controller.
+
+Required properties:
+
+- compatible:
+ Must be "jnx,gpio-sam"
+
+- #gpio-cells:
+ Should be <2>. The first cell is the pin number (within the controller's
+ pin space), and the second is used for the following flags:
+ bit[0]: direction (0 = out, 1 = in)
+ bit[1]: init high
+ bit[2]: active low
+ bit[3]: open drain
+ bit[4]: open drain
+
+- gpio-controller:
+ Specifies that the node is a GPIO controller.
+
+Optional properties:
+
+- reg:
+ This driver is part of the SAM FPGA MFD driver, so the
+ address range is supplied by that driver. However you can
+ override using this property.
+
+- gpio-base:
+ Base of the GPIO pins of this instance. If not present use system allocated.
+
+- gpio-count:
+ Number of GPIO pins of this instance. If not present read the number from
+ the one configured in the FPGA data. Maximum number is 512.
+
+- #interrupt-cells:
+ Should be <2>. The first cell is the GPIO number, the second should specify
+ flags. The following subset of flags is supported:
+ - bits[16,4:0] trigger type and level flags
+ bit 0: rising edge interrupt
+ bit 1: falling edge interrupt
+ bit 2: active high interrupt
+ bit 3: active low interrupt
+ bit 4: enable debounce
+ bit 16: signal is active low
+ See also Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+
+- gpio-interrupts:
+ A number of triples that define the mapping of interrupt groupsb to a range of
+ pins. The first cell defines the interrupt group, the second is the start of
+ the pin range and the third the number of pins in the range.
+
+- gpio-exports:
+ A subnode containing the list of pins that will be exported to user-space.
+ Each subnode contains:
+ Required properties:
+ - pin: The gpio to be exported and the relevant flags.
+ Optional properties:
+ - label: The label to use for export; if not supplied use the node name.
+
+Example:
+
+gpio20: gpio-sam {
+ compatible = "jnx,gpio-sam";
+ gpio-controller;
+ interrupt-controller;
+ /* 1st cell: gpio pin
+ * 2nd cell: flags (bit mask)
+ * bit 0: rising edge interrupt
+ * bit 1: falling edge interrupt
+ * bit 2: active high interrupt
+ * bit 3: active low interrupt
+ * bit 4: enable debounce
+ * bit 16: signal is active low
+ */
+ #interrupt-cells = <2>;
+ #gpio-cells = <2>;
+ gpio-count = <340>;
+ /* 1st cell: gpio interrupt status bit
+ * 2nd cell: 1st pin
+ * 3rd cell: # of pins
+ */
+ gpio-interrupts =
+ <0 0 32>, /* TL / TQ */
+ <1 32 32>, /* PIC 1 */
+ <2 32 32>, /* PIC 1 spare */
+ <7 148 32>, /* PIC 0 */
+ <8 170 32>, /* PIC 0 spare */
+ <16 318 22>; /* FPC */
+
+ gpio-exports {
+ /*
+ * flags:
+ * GPIOF_DIR_IN bit 0=1
+ * GPIOF_DIR_OUT bit 0=0
+ * GPIOF_INIT_HIGH bit 1=1
+ * GPIOF_INIT_HIGH is raw, not translated
+ * GPIOF_ACTIVE_LOW bit 2=1
+ * GPIOF_OPEN_DRAIN bit 3=1
+ * GPIOF_OPEN_SOURCE bit 4=1
+ * GPIOF_EXPORT bit 5=1
+ * GPIOF_EXPORT_CHANGEABLE bit 6=1
+ */
+ tl0-rst {
+ pin = < 8 0x24 >;
+ };
+ };
+};
--
1.9.1
From: Georgi Vlaev <[email protected]>
Add binding document for Junipers Flash IP block present
in the SAM FPGA on PTX series of routers.
Signed-off-by: Georgi Vlaev <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
.../devicetree/bindings/mtd/flash-sam.txt | 31 ++++++++++++++++++++++
1 file changed, 31 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/flash-sam.txt
diff --git a/Documentation/devicetree/bindings/mtd/flash-sam.txt b/Documentation/devicetree/bindings/mtd/flash-sam.txt
new file mode 100644
index 0000000..bdf1d78
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/flash-sam.txt
@@ -0,0 +1,31 @@
+Flash device on a Juniper SAM FPGA
+
+These flash chips are found in the PTX series of Juniper routers.
+
+They are regular CFI compatible (Intel or AMD extended) flash chips with
+some special write protect/VPP bits that can be controlled by the machine's
+system controller.
+
+Required properties:
+- compatible : must be "jnx,flash-sam"
+
+Optional properties:
+- reg : memory address for the flash chip, note that this is not
+required since usually the device is a subdevice of the SAM MFD
+driver which fills in the register fields.
+
+For the rest of the properties, see mtd-physmap.txt.
+
+The device tree may optionally contain sub-nodes describing partitions of the
+address space. See partition.txt for more detail.
+
+Example:
+
+flash_sam {
+ compatible = "jnx,flash-sam";
+ partition@0 {
+ reg = <0x0 0x400000>;
+ label = "pic0-golden";
+ read-only;
+ };
+};
--
1.9.1
From: Guenter Roeck <[email protected]>
Add driver for the flash block in Juniper's SAM FPGA.
This driver is used for updating the Altera's EPCS(64,256)
configuration flash devices via a Juniper defined hardware
interface.
Signed-off-by: Georgi Vlaev <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
drivers/mtd/devices/Kconfig | 11 +
drivers/mtd/devices/Makefile | 1 +
drivers/mtd/devices/sam-flash.c | 642 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 654 insertions(+)
create mode 100644 drivers/mtd/devices/sam-flash.c
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index d4255fb..f5a9032 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -144,6 +144,17 @@ config MTD_LART
not need any mapping/chip driver for LART. This one does it all
for you, so go disable all of those if you enabled some of them (:
+config MTD_SAM_FLASH
+ tristate "Juniper SAM Flash driver"
+ depends on MFD_JUNIPER_SAM || MFD_JUNIPER_CBC
+ default y if MFD_JUNIPER_SAM
+ help
+ This enables the flash driver for the SAM FPGA which is present
+ on relevant Juniper platforms.
+
+ This driver can also be built as a module. When it is so the name of
+ the module is flash-sam.
+
config JNX_PMB_NVRAM
tristate "Juniper FPC PMB NVRAM Driver"
depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index b407c5fc..7556311 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o
obj-$(CONFIG_MTD_ST_SPI_FSM) += st_spi_fsm.o
obj-$(CONFIG_MTD_POWERNV_FLASH) += powernv_flash.o
+obj-$(CONFIG_MTD_SAM_FLASH) += sam-flash.o
obj-$(CONFIG_JNX_PMB_NVRAM) += jnx_pmb_nvram.o
CFLAGS_docg3.o += -I$(src)
diff --git a/drivers/mtd/devices/sam-flash.c b/drivers/mtd/devices/sam-flash.c
new file mode 100644
index 0000000..5f071e6
--- /dev/null
+++ b/drivers/mtd/devices/sam-flash.c
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2012 Juniper networks
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#define SAM_FLASH_DEBUG_ENABLED
+#undef T5E_MAX_FLASH_READ_WAIT_TIME_FIXED
+#define SAM_FLASH_IF_READ_MAX_SIZE 32 /* 256?! */
+
+#define SAM_FLASH_BASE 0x300
+
+#define ADDR_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x000)
+#define COUNTER_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x004)
+#define CONTROL_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x008)
+#define STATUS_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x00c)
+#define WRITE_DATA_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x100)
+#define READ_DATA_REG(x) ((x)->membase + SAM_FLASH_BASE + 0x200)
+
+static int sam_flash_if_read_max_size = SAM_FLASH_IF_READ_MAX_SIZE;
+module_param(sam_flash_if_read_max_size, int, S_IRUSR | S_IRGRP | S_IWUSR);
+MODULE_PARM_DESC(sam_flash_if_read_max_size,
+ "maximum read size done by SAM flash IF");
+
+#ifdef SAM_FLASH_DEBUG_ENABLED
+
+static int sam_flash_debug;
+module_param(sam_flash_debug, int, S_IRUSR | S_IRGRP | S_IWUSR);
+MODULE_PARM_DESC(sam_flash_debug, "enable debugging information");
+
+#define SAM_FLASH_DEBUG(dev, fmt, args...) \
+ do { \
+ if (sam_flash_debug) { \
+ dev_info(dev, fmt, ## args); \
+ } \
+ } while (0)
+#else /* SAM_FLASH_DEBUG_ENABLED */
+#define SAM_FLASH_DEBUG(dev, fmt, args...) {}
+#endif /* SAM_FLASH_DEBUG_ENABLED */
+
+/*
+ * Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ */
+#define EPCS_EXT_STS_ID(sts) ((u8)((sts >> 8) & 0xff))
+#define EPCS_EXT_STS_RDSTS(sts) ((u8)((sts >> 16) & 0xff))
+#define EPCS_EXT_STS_SID(sts) ((u8)((sts >> 24) & 0xff))
+/* EPCS Device "read status" bits */
+#define EPCS_STS_WIP_BIT 0x01
+#define EPCS_STS_WLE_BIT 0x02
+#define EPCS_STS_BP_BITS(status) ((status >> 2) & 0x7)
+
+/*
+ * Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ */
+#define EPCS64_S_ID 0x16
+#define EPCS64_NAME "Altera EPCS64"
+#define EPCS64_SECT_SZ_SHIFT 16
+#define EPCS64_SECTOR_SIZE (1 << EPCS64_SECT_SZ_SHIFT)
+#define EPCS64_SECTORS 128
+#define EPCS64_ADDR_TO_SECTOR(_addr) ((_addr) >> EPCS64_SECT_SZ_SHIFT)
+#define EPCS64_PAGE_SIZE 256
+#define EPCS64_PAGES 32768
+#define EPCS64_SIZE (EPCS64_PAGE_SIZE * EPCS64_PAGES)
+#define EPCS64_MIN_SECT(bp_bits) (EPCS64_SECTORS - (1 << bp_bits))
+
+/*
+ * Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ * timeout for busy: t5e-pic/t5e_flash.c
+ */
+#define EPCS_TIMEOUT_BUSY 1
+#define EPCS_TIMEOUT_SINGLE_BYTE_READ 3
+#define EPCS_TIMEOUT_READ_ID 3
+#define EPCS_TIMEOUT_READ_STATUS 3
+
+/*
+ * Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ * timeout for waiting completion: t5e-pic/t5e_flash.c
+ */
+#define EPCS_RD_TIMEO 20
+#define EPCS_WR_TIMEO 25
+#define EPCS_BLK_WR_TIMEO 25
+#define EPCS_SC_ER_TIMEO (10 * 1000)
+#define EPCS_SC_PRT_TIMEO 35
+#define EPCS_CH_ER_TIMEO (200 * 1000)
+/* */
+#define EPCS_STS_BSY_BIT 0x01
+#define EPCS_ILLEGAL_WR_BIT 0x02
+#define EPCS_ILLEGAL_RD_BIT 0x04
+#define EPCS_ILLEGAL (EPCS_ILLEGAL_WR_BIT | EPCS_ILLEGAL_RD_BIT)
+#define EPCS_STATUS_BUSY(s) ((s) & EPCS_STS_BSY_BIT)
+
+/*
+ * Ref t5e-pic/t5e_flash.c
+ */
+#define EPCS_BUSY_POLLING_START_DELAY 100 /* us */
+#define EPCS_BUSY_POLLING_START_DELAY_CNT 10
+#define EPCS_BUSY_POLLING_DELAY (EPCS_BUSY_POLLING_START_DELAY_CNT * \
+ EPCS_BUSY_POLLING_START_DELAY)
+
+/*
+ * FPGA flash control register: t5e-pic/t5e_fpga.h
+ */
+#define SAM_FLASH_IF_CONTROL_READ_SID 0x00000080
+#define SAM_FLASH_IF_CONTROL_CHIP_ERASE 0x00000040
+#define SAM_FLASH_IF_CONTROL_SECTOR_ERASE 0x00000020
+#define SAM_FLASH_IF_CONTROL_SECTOR_PROTECT 0x00000010
+#define SAM_FLASH_IF_CONTROL_READ_STATUS 0x00000008
+#define SAM_FLASH_IF_CONTROL_READ_ID 0x00000004
+#define SAM_FLASH_IF_CONTROL_WRITE 0x00000002
+#define SAM_FLASH_IF_CONTROL_READ 0x00000001
+#define SAM_FLASH_IF_WRITE_REG_SIZE sizeof(u32)
+#define SAM_FLASH_IF_READ_REG_SIZE sizeof(u32)
+
+struct sam_flash_info {
+ const char *name;
+ u8 device_id;
+ size_t flash_size;
+ size_t page_size;
+ size_t nr_pages;
+ size_t nr_sectors;
+ size_t erasesize;
+ size_t writesize;
+ size_t writebufsize;
+};
+
+static struct sam_flash_info sam_flash_info_db[] = {
+ {
+ .name = EPCS64_NAME,
+ .device_id = EPCS64_S_ID,
+ .flash_size = EPCS64_SIZE,
+ .page_size = EPCS64_PAGE_SIZE,
+ .nr_pages = EPCS64_PAGES,
+ .nr_sectors = EPCS64_SECTORS,
+ .erasesize = EPCS64_SECTOR_SIZE,
+ .writesize = 4,
+ .writebufsize = 4,
+ },
+};
+
+#define SAM_FLASH_INFO_DB_SIZE ARRAY_SIZE(sam_flash_info_db)
+
+/**
+ * struct sam_flash - SAM FLASH private data structure.
+ * @membase: PCI base address of Memory mapped I/O register.
+ * @reg: Memory mapped PCH GPIO register list.
+ * @dev: Pointer to device structure.
+ */
+struct sam_flash {
+ void __iomem *membase;
+ struct mutex lock;
+ struct device *dev;
+ struct sam_flash_info *info;
+ struct mtd_info mtd_info;
+};
+
+#define mtd_to_sam_flash(mtd) container_of(mtd, struct sam_flash, mtd_info)
+
+static bool sam_flash_if_busy(struct sam_flash *sam_flash, int retry)
+{
+ u32 status;
+
+ do {
+ status = ioread32(STATUS_REG(sam_flash));
+ if (!EPCS_STATUS_BUSY(status))
+ return false;
+ if (retry <= 1)
+ return true;
+ usleep_range(50, 100);
+ } while (--retry >= 0);
+
+ return true;
+}
+
+static int
+sam_flash_if_busy_wait(struct sam_flash *sam_flash, unsigned int max_wait_msec)
+{
+ unsigned long timeout;
+ u32 status;
+
+ timeout = jiffies + msecs_to_jiffies(max_wait_msec);
+ udelay(50);
+
+ do {
+ status = ioread32(STATUS_REG(sam_flash));
+ if (!EPCS_STATUS_BUSY(status))
+ return 0;
+
+ if (status & EPCS_ILLEGAL)
+ return -EACCES;
+
+ usleep_range(50, 100);
+ } while (time_before(jiffies, timeout));
+
+ return -ETIMEDOUT;
+}
+
+static int
+sam_flash_mem_read(struct sam_flash *sam_flash, u32 offset,
+ u8 *data, size_t len)
+{
+ struct sam_flash_info *info = sam_flash->info;
+ void __iomem *io_addr;
+ u32 io_data;
+ int i, cnt;
+
+ if (offset >= info->flash_size || offset + len > info->flash_size)
+ return -EINVAL;
+
+ if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_BUSY))
+ return -ETIMEDOUT;
+
+ iowrite32(len - 1, COUNTER_REG(sam_flash));
+ iowrite32(offset, ADDR_REG(sam_flash));
+
+ /* trigger the read */
+ iowrite32(SAM_FLASH_IF_CONTROL_READ, CONTROL_REG(sam_flash));
+ ioread32(CONTROL_REG(sam_flash));
+
+ /*
+ * Before we start polling the busy bit, wait for some time,
+ * so that, the busy bit will go high
+ */
+#ifdef T5E_MAX_FLASH_READ_WAIT_TIME_FIXED
+ udelay(50);
+#else /* T5E_MAX_FLASH_READ_WAIT_TIME_FIXED */
+ udelay(50 * ((len >> 2) + 1)); /* 50 usec every 4 bytes */
+#endif /* T5E_MAX_FLASH_READ_WAIT_TIME_FIXED */
+
+ if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_SINGLE_BYTE_READ))
+ return -ETIMEDOUT;
+
+ io_data = ioread32(COUNTER_REG(sam_flash));
+ if (io_data != len - 1)
+ return -EIO;
+
+ SAM_FLASH_DEBUG(sam_flash->dev,
+ "%s BYTE_CNT: len: %u, io_data: %u.\n",
+ __func__, (unsigned int)len, io_data);
+
+ io_addr = READ_DATA_REG(sam_flash);
+ for (cnt = 0; cnt < len; io_addr += sizeof(u32)) {
+ io_data = ioread32(io_addr);
+ for (i = 0; i < sizeof(u32) && cnt < len; i++, cnt++)
+ *(data++) = (io_data >> (i << 3)) & 0xff;
+ }
+
+ return 0;
+}
+
+static int sam_flash_read_sid(struct sam_flash *sam_flash)
+{
+ u32 io_data;
+
+ iowrite32(SAM_FLASH_IF_CONTROL_READ_SID, CONTROL_REG(sam_flash));
+ ioread32(CONTROL_REG(sam_flash));
+
+ /*
+ * Before we start polling the busy bit, wait for some time
+ * to ensure that busy bit is high.
+ */
+ udelay(EPCS_BUSY_POLLING_DELAY);
+ if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_READ_ID))
+ return -ETIMEDOUT;
+
+ io_data = ioread32(STATUS_REG(sam_flash));
+
+ return EPCS_EXT_STS_SID(io_data);
+}
+
+static struct sam_flash_info *sam_flash_get_info(struct sam_flash *sam_flash)
+{
+ struct sam_flash_info *info;
+ u8 sid;
+ int idx;
+
+ sid = sam_flash_read_sid(sam_flash);
+ if (sid < 0)
+ return ERR_PTR(sid);
+
+ info = ERR_PTR(-EINVAL);
+ for (idx = 0; idx < SAM_FLASH_INFO_DB_SIZE; idx++) {
+ if (sam_flash_info_db[idx].device_id == sid) {
+ info = &sam_flash_info_db[idx];
+ break;
+ }
+ }
+ return info;
+}
+
+static inline int sam_flash_get_page_num(struct sam_flash *sam_flash,
+ u32 offset)
+{
+ return offset / sam_flash->info->page_size;
+}
+
+static int sam_flash_mem_write(struct sam_flash *sam_flash, u32 offset,
+ const u8 *data, size_t len)
+{
+ struct sam_flash_info *info = sam_flash->info;
+ int status, bytes_in_reg, cnt, cnt2;
+ int start_page, end_page;
+ void __iomem *io_addr;
+ u32 io_data;
+ const u8 *buf;
+
+ start_page = sam_flash_get_page_num(sam_flash, offset);
+ end_page = sam_flash_get_page_num(sam_flash, offset + len - 1);
+
+ /*
+ * Based on Altera EPCS Device Datasheet,
+ * Writing with multiple byte must be in the __SAME__ page.
+ * Not sure if SAM FPGA takes that so ...
+ */
+ if (len > info->page_size ||
+ start_page != end_page ||
+ start_page >= info->nr_pages) {
+ dev_err(sam_flash->dev, "Bad write length / offset\n");
+ return -EINVAL;
+ }
+
+ /* check if FPGA is ready to accept new command */
+ if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_BUSY)) {
+ dev_err(sam_flash->dev, "chip is busy\n");
+ return -ETIMEDOUT;
+ }
+
+ iowrite32(len - 1, COUNTER_REG(sam_flash));
+
+ /* copy the data to WRITE_DATA register */
+ io_addr = WRITE_DATA_REG(sam_flash);
+ for (buf = data, cnt = 0; cnt < len;) {
+ bytes_in_reg = len - cnt;
+ if (bytes_in_reg > SAM_FLASH_IF_WRITE_REG_SIZE)
+ bytes_in_reg = SAM_FLASH_IF_WRITE_REG_SIZE;
+ io_data = 0;
+ for (cnt2 = 0; cnt2 < bytes_in_reg; cnt2++, buf++)
+ io_data |= *buf << (cnt2 << 3);
+
+ iowrite32(io_data, io_addr);
+ cnt += bytes_in_reg;
+ io_addr += bytes_in_reg;
+ }
+
+ iowrite32(offset, ADDR_REG(sam_flash));
+ /* trigger the write */
+ iowrite32(SAM_FLASH_IF_CONTROL_WRITE, CONTROL_REG(sam_flash));
+ ioread32(CONTROL_REG(sam_flash));
+
+ status = sam_flash_if_busy_wait(sam_flash, EPCS_WR_TIMEO);
+ return status;
+}
+
+static int sam_flash_mem_is_protected(struct sam_flash *sam_flash,
+ u32 offset, size_t len)
+{
+ struct sam_flash_info *info = sam_flash->info;
+ u32 flash_status;
+ u32 sector;
+ u32 io_data;
+ int status = 0;
+
+ iowrite32(SAM_FLASH_IF_CONTROL_READ_STATUS, CONTROL_REG(sam_flash));
+ if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_READ_ID))
+ return -ETIMEDOUT;
+
+ io_data = ioread32(STATUS_REG(sam_flash));
+ flash_status = EPCS_EXT_STS_RDSTS(io_data);
+ sector = EPCS64_ADDR_TO_SECTOR(offset);
+ if (EPCS_STS_BP_BITS(flash_status) &&
+ sector < info->erasesize &&
+ sector >= EPCS64_MIN_SECT(EPCS_STS_BP_BITS(flash_status))) {
+ status = -EACCES;
+ SAM_FLASH_DEBUG(sam_flash->dev,
+ "%s offset: 0x%x, len: %u: PROTECTED(0x%x): %d.\n",
+ __func__, offset, (unsigned int)len,
+ flash_status, status);
+ }
+
+ return status;
+}
+
+static int sam_flash_erase_sector(struct sam_flash *sam_flash, u32 offset)
+{
+ iowrite32(offset, ADDR_REG(sam_flash));
+ iowrite32(SAM_FLASH_IF_CONTROL_SECTOR_ERASE, CONTROL_REG(sam_flash));
+
+ return sam_flash_if_busy_wait(sam_flash, EPCS_SC_ER_TIMEO);
+}
+
+static int sam_flash_erase(struct mtd_info *mtd_info,
+ struct erase_info *erase_info)
+{
+ struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+ u32 len, start, end, offset;
+ int status = 0;
+
+ len = (u32) erase_info->len;
+ start = (u32) erase_info->addr;
+ end = start + len - 1;
+
+ offset = start;
+ mutex_lock(&sam_flash->lock);
+ erase_info->state = MTD_ERASE_DONE;
+ while (offset < end) {
+ status = sam_flash_erase_sector(sam_flash, offset);
+ if (status) {
+ erase_info->state = MTD_ERASE_FAILED;
+ break;
+ }
+ offset += mtd_info->erasesize;
+ }
+ mutex_unlock(&sam_flash->lock);
+
+ mtd_erase_callback(erase_info);
+
+ return status;
+}
+
+static int sam_flash_read(struct mtd_info *mtd_info, loff_t from, size_t len,
+ size_t *retlen, unsigned char *buf)
+{
+ struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+ int cnt, max_cnt;
+ int status = 0;
+
+ *retlen = 0;
+
+ max_cnt = len / sam_flash_if_read_max_size;
+ if (len % sam_flash_if_read_max_size != 0)
+ max_cnt++;
+ mutex_lock(&sam_flash->lock);
+ for (cnt = 0; cnt < max_cnt; cnt++) {
+ u32 from2;
+ size_t len2;
+ u8 *buf2;
+
+ from2 = from + *retlen;
+ buf2 = buf + *retlen;
+ len2 = len - *retlen;
+ if (len2 > sam_flash_if_read_max_size)
+ len2 = sam_flash_if_read_max_size;
+
+ status = sam_flash_mem_read(sam_flash, from2, buf2, len2);
+ if (status != 0) {
+ dev_err(sam_flash->dev,
+ "RD: cnt: %04d(%04d): from: %u(%u), len: %u(%u) failed: %d.\n",
+ cnt, max_cnt, from2, (u32) from,
+ (unsigned int)len2, (unsigned int)len, status);
+ break;
+ }
+ *retlen += len2;
+ }
+ mutex_unlock(&sam_flash->lock);
+
+ return status;
+}
+
+static int sam_flash_write(struct mtd_info *mtd_info, loff_t to,
+ size_t len, size_t *retlen, const unsigned char *buf)
+{
+ struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+ int status, done, to_be_done;
+
+ mutex_lock(&sam_flash->lock);
+ status = sam_flash_mem_is_protected(sam_flash, to, len);
+ if (status)
+ goto abort;
+
+ for (done = 0; done < len; done += to_be_done) {
+ to_be_done = to & (sam_flash->info->page_size - 1);
+ if (to_be_done == 0) {
+ /* 'to' is page aligned */
+ to_be_done = len - done;
+ if (to_be_done > sam_flash->info->page_size)
+ to_be_done = sam_flash->info->page_size;
+ } else {
+ to_be_done = sam_flash->info->page_size - to_be_done;
+ }
+
+ SAM_FLASH_DEBUG(sam_flash->dev,
+ "%s to: 0x%x, buf: 0x%p, to_be_done: %d, done: %d, len: %d.\n",
+ __func__,
+ (u32) to, buf, to_be_done,
+ done, (unsigned int)len);
+ status = sam_flash_mem_write(sam_flash, to, buf, to_be_done);
+ if (status) {
+ dev_err(sam_flash->dev,
+ "WR: failed to 0x%x, buf: 0x%p, done: %d(%d), to_be_done: %d: %d.\n",
+ (u32)to, buf, done, (unsigned int)len,
+ to_be_done, status);
+ break;
+ }
+
+ to += to_be_done;
+ buf += to_be_done;
+ }
+
+ if (!status)
+ *retlen = len;
+
+abort:
+ mutex_unlock(&sam_flash->lock);
+ return status;
+}
+
+static int sam_flash_mtd_attach(struct platform_device *pdev,
+ struct sam_flash *sam_flash)
+{
+ struct mtd_part_parser_data ppdata = {};
+ struct sam_flash_info *info;
+ struct device *dev = sam_flash->dev;
+ struct mtd_info *mtd_info;
+ int ret;
+
+ info = sam_flash_get_info(sam_flash);
+ if (IS_ERR(info))
+ return PTR_ERR(info);
+
+ sam_flash->info = info;
+
+ mtd_info = &sam_flash->mtd_info;
+ mtd_info->name = dev_name(dev);
+ mtd_info->type = MTD_NORFLASH;
+ mtd_info->flags = MTD_CAP_NORFLASH;
+ mtd_info->erasesize = info->erasesize;
+ mtd_info->writesize = info->writesize;
+ mtd_info->writebufsize = info->writebufsize;
+ mtd_info->size = info->flash_size;
+ mtd_info->_erase = sam_flash_erase;
+ mtd_info->_read = sam_flash_read;
+ mtd_info->_write = sam_flash_write;
+
+ ret = mtd_device_parse_register(mtd_info, NULL, &ppdata, NULL, 0);
+ if (ret) {
+ dev_err(dev, "mtd_device_parse_register returned %d\n", ret);
+ return ret;
+ }
+
+ dev_info(dev,
+ "ATTACH: name: \"%s\" type: %d, flags: 0x%x.\n",
+ mtd_info->name, mtd_info->type, mtd_info->flags);
+ dev_info(dev,
+ "ATTACH: erasesize: %d, writesize: %d, writebufsize: %d\n",
+ mtd_info->erasesize, mtd_info->writesize,
+ mtd_info->writebufsize);
+ dev_info(dev,
+ "ATTACH: size: %llu.%u(%llu KB).\n",
+ mtd_info->size,
+ (unsigned int)info->flash_size,
+ (long long)mtd_info->size >> 10);
+
+ return 0;
+}
+
+static int sam_flash_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sam_flash *sam_flash;
+ struct resource *res;
+
+ sam_flash = devm_kzalloc(dev, sizeof(*sam_flash), GFP_KERNEL);
+ if (sam_flash == NULL)
+ return -ENOMEM;
+
+ sam_flash->dev = dev;
+ platform_set_drvdata(pdev, sam_flash);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOMEM;
+
+ sam_flash->membase = devm_ioremap(dev, res->start, resource_size(res));
+ if (!sam_flash->membase)
+ return -ENOMEM;
+
+ mutex_init(&sam_flash->lock);
+
+ return sam_flash_mtd_attach(pdev, sam_flash);
+}
+
+static int sam_flash_remove(struct platform_device *pdev)
+{
+ struct sam_flash *sam_flash = platform_get_drvdata(pdev);
+
+ mtd_device_unregister(&sam_flash->mtd_info);
+
+ return 0;
+}
+
+static const struct of_device_id sam_flash_ids[] = {
+ { .compatible = "jnx,flash-sam", },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, sam_flash_ids);
+
+static struct platform_driver sam_flash_driver = {
+ .driver = {
+ .name = "flash-sam",
+ .owner = THIS_MODULE,
+ .of_match_table = sam_flash_ids,
+ },
+ .probe = sam_flash_probe,
+ .remove = sam_flash_remove,
+};
+
+static int __init sam_flash_init(void)
+{
+ return platform_driver_register(&sam_flash_driver);
+}
+
+static void __exit sam_flash_exit(void)
+{
+ platform_driver_unregister(&sam_flash_driver);
+}
+
+module_init(sam_flash_init);
+module_exit(sam_flash_exit);
+
+MODULE_DESCRIPTION("SAM-FPGA FLASH Driver");
+MODULE_LICENSE("GPL");
--
1.9.1
From: Maryam Seraj <[email protected]>
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 <[email protected]>
Signed-off-by: Debjit Ghosh <[email protected]>
Signed-off-by: Georgi Vlaev <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
Signed-off-by: Rajat Jain <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
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 <[email protected]>"
+
+#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", ®);
+ 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 <[email protected]>
+ *
+ * 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
On Fri, Oct 07, 2016 at 06:18:37PM +0300, Pantelis Antoniou wrote:
> From: Georgi Vlaev <[email protected]>
>
> Add driver for the MDIO IP block present in Juniper's
> SAM FPGA.
>
> This driver supports only Clause 45 of the 802.3 spec.
>
> Note that due to the fact that there are no drivers for
> Broadcom/Avago retimers on 10/40Ge path that are controlled
> from the MDIO interface there is a method to have direct
> access to registers via a debugfs interface.
This seems to be the wrong solution. Why not write those drivers?
Controlling stuff from user space is generally frowned up. So i expect
DaveM will NACK this patch. Please remove all the debugfs stuff.
> +static int mdio_sam_stat_wait(struct mii_bus *bus, u32 wait_mask)
> +{
> + struct mdio_sam_data *data = bus->priv;
> + unsigned long timeout;
> + u32 stat;
> +
> + timeout = jiffies + msecs_to_jiffies(MDIO_RDY_TMO);
> + do {
> + stat = ioread32(data->base + MDIO_STATUS);
> + if (stat & wait_mask)
> + return 0;
> +
> + usleep_range(50, 100);
> + } while (time_before(jiffies, timeout));
> +
> + return -EBUSY;
I've recently had to fix a loop like this in another
driver. usleep_range(50, 100) can sleep for a lot longer. If it sleeps
for MDIO_RDY_TMO you exit out with -EBUSY after a single iteration,
which is not what you want. It is better to make a fixed number of
iterations rather than a timeout.
> +}
> +
> +static int mdio_sam_read(struct mii_bus *bus, int phy_id, int regnum)
> +{
> + struct mdio_sam_data *data = bus->priv;
> + u32 command, res;
> + int ret;
> +
> + /* mdiobus_read holds the bus->mdio_lock mutex */
> +
> + if (!(regnum & MII_ADDR_C45))
> + return -ENXIO;
> +
> + ret = mdio_sam_stat_wait(bus, STAT_REG_RDY);
> + if (ret < 0)
> + return ret;
> +
> + command = regnum & 0x1fffff; /* regnum = (dev_id << 16) | reg */
> + command |= ((phy_id & 0x1f) << 21);
> +
> + iowrite32(command, data->base + MDIO_CMD1);
> + ioread32(data->base + MDIO_CMD1);
> + iowrite32(CMD2_READ | CMD2_ENABLE, data->base + MDIO_CMD2);
> + ioread32(data->base + MDIO_CMD2);
Why do you need to read the values back? Hardware bug?
> + iowrite32(TBL_CMD_REG_GO, data->base + MDIO_TBL_CMD);
> + ioread32(data->base + MDIO_TBL_CMD);
Although not wrong, most drivers use writel().
> +
> + usleep_range(50, 100);
> +
> + ret = mdio_sam_stat_wait(bus, (STAT_REG_DONE | STAT_REG_ERR));
Do you really need a wait before calling mdio_sam_stat_wait()? Isn't
that what it is supposed to do, wait...
> + if (ret < 0)
> + return ret;
> +
> + res = ioread32(data->base + MDIO_RESULT);
> +
> + if (res & RES_ERROR || !(res & RES_SUCCESS))
> + return -EIO;
> +
> + return (res & 0xffff);
> +}
> +
> +static int mdio_sam_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
> +{
> + struct mdio_sam_data *data = bus->priv;
> + u32 command;
> + int ret;
> +
> + /* mdiobus_write holds the bus->mdio_lock mutex */
> +
> + if (!(regnum & MII_ADDR_C45))
> + return -ENXIO;
> +
> + ret = mdio_sam_stat_wait(bus, STAT_REG_RDY);
> + if (ret < 0)
> + return ret;
> +
> + command = regnum & 0x1fffff; /* regnum = (dev_id << 16) | reg */
> + command |= ((phy_id & 0x1f) << 21);
> +
> + iowrite32(command, data->base + MDIO_CMD1);
> + ioread32(data->base + MDIO_CMD1);
> + iowrite32(CMD2_ENABLE | val, data->base + MDIO_CMD2);
> + ioread32(data->base + MDIO_CMD2);
> + iowrite32(TBL_CMD_REG_GO, data->base + MDIO_TBL_CMD);
> + ioread32(data->base + MDIO_TBL_CMD);
> +
> + usleep_range(50, 100);
> +
> + ret = mdio_sam_stat_wait(bus, (STAT_REG_DONE | STAT_REG_ERR));
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int mdio_sam_reset(struct mii_bus *bus)
> +{
> + struct mdio_sam_data *data = bus->priv;
> +
> + iowrite32(TBL_CMD_SOFT_RESET, data->base + MDIO_TBL_CMD);
> + ioread32(data->base + MDIO_TBL_CMD);
> + mdelay(10);
> + iowrite32(0, data->base + MDIO_TBL_CMD);
> + ioread32(data->base + MDIO_TBL_CMD);
> +
> + /* zero tables */
> + memset_io(data->base + MDIO_CMD1, 0, 0x1000);
> + memset_io(data->base + MDIO_PRI_CMD1, 0, 0x1000);
What tables?
> +
> + return 0;
> +}
> +
> +static int mdio_sam_of_register_bus(struct platform_device *pdev,
> + struct device_node *np, void __iomem *base)
> +{
> + struct mii_bus *bus;
> + struct mdio_sam_data *data;
> + u32 reg;
> + int ret;
> +
> + bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*data));
> + if (!bus)
> + return -ENOMEM;
> +
> + /* bus offset */
> + ret = of_property_read_u32(np, "reg", ®);
> + if (ret)
> + return -ENODEV;
> +
> + data = bus->priv;
> + data->base = base + reg;
> +
> + bus->parent = &pdev->dev;
> + bus->name = "mdio-sam";
> + bus->read = mdio_sam_read;
> + bus->write = mdio_sam_write;
> + bus->reset = mdio_sam_reset;
> + snprintf(bus->id, MII_BUS_ID_SIZE, "mdiosam-%x-%x", pdev->id, reg);
> +
> + ret = of_mdiobus_register(bus, np);
> + if (ret < 0)
> + return ret;
> +#ifdef CONFIG_DEBUG_FS
> + ret = mdio_sam_debugfs_init(bus);
> + if (ret < 0)
> + goto err_unregister;
> +#endif
> + ret = device_create_bin_file(&bus->dev, &bin_attr_raw_io);
> + if (ret)
> + goto err_debugfs;
> +
> + return 0;
> +
> +err_debugfs:
> +#ifdef CONFIG_DEBUG_FS
> + mdio_sam_debugfs_remove(bus);
> +#endif
> +err_unregister:
> + mdiobus_unregister(bus);
> +
> + return ret;
> +}
> +
> +static int mdio_sam_of_unregister_bus(struct device_node *np)
> +{
> + struct mii_bus *bus;
> +
> + bus = of_mdio_find_bus(np);
> + if (bus) {
> + device_remove_bin_file(&bus->dev, &bin_attr_raw_io);
> +#ifdef CONFIG_DEBUG_FS
> + mdio_sam_debugfs_remove(bus);
> +#endif
> + mdiobus_unregister(bus);
> + }
> + return 0;
> +}
> +
> +static int mdio_sam_probe(struct platform_device *pdev)
> +{
> + struct device_node *np;
> + struct resource *res;
> + void __iomem *base;
> + int ret;
> +
> + if (!pdev->dev.of_node)
> + return -ENODEV;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + base = devm_ioremap_nocache(&pdev->dev, res->start,
> + resource_size(res));
Why nocache?
> + if (IS_ERR(base))
> + return PTR_ERR(base);
> +
> + for_each_available_child_of_node(pdev->dev.of_node, np) {
> + ret = mdio_sam_of_register_bus(pdev, np, base);
> + if (ret)
> + goto err;
> + }
This is odd. There does not seem to be any shared resources. So you
should really have one MDIO bus as one device in the device tree.
Andrew
> +
> + return 0;
> +err:
> + /* roll back everything */
> + for_each_available_child_of_node(pdev->dev.of_node, np)
> + mdio_sam_of_unregister_bus(np);
> +
> + return ret;
> +}
> +
> +static int mdio_sam_remove(struct platform_device *pdev)
> +{
> + struct device_node *np;
> +
> + for_each_available_child_of_node(pdev->dev.of_node, np)
> + mdio_sam_of_unregister_bus(np);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id mdio_sam_of_match[] = {
> + { .compatible = "jnx,mdio-sam" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, mdio_sam_of_match);
> +
> +static struct platform_driver mdio_sam_driver = {
> + .probe = mdio_sam_probe,
> + .remove = mdio_sam_remove,
> + .driver = {
> + .name = "mdio-sam",
> + .owner = THIS_MODULE,
> + .of_match_table = mdio_sam_of_match,
> + },
> +};
> +
> +module_platform_driver(mdio_sam_driver);
> +
> +MODULE_ALIAS("platform:mdio-sam");
> +MODULE_AUTHOR("Georgi Vlaev <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Juniper Networks SAM MDIO bus driver");
> --
> 1.9.1
>
Hi Andrew,
On 16-10-07 23:13:26, Andrew Lunn wrote:
> On Fri, Oct 07, 2016 at 06:18:37PM +0300, Pantelis Antoniou wrote:
> > From: Georgi Vlaev <[email protected]>
> >
> > Add driver for the MDIO IP block present in Juniper's
> > SAM FPGA.
> >
> > This driver supports only Clause 45 of the 802.3 spec.
> >
> > Note that due to the fact that there are no drivers for
> > Broadcom/Avago retimers on 10/40Ge path that are controlled
> > from the MDIO interface there is a method to have direct
> > access to registers via a debugfs interface.
>
> This seems to be the wrong solution. Why not write those drivers?
>
> Controlling stuff from user space is generally frowned up. So i expect
> DaveM will NACK this patch. Please remove all the debugfs stuff.
I'll drop the debugfs and sysfs attributes. This will remove the
access from the user space.
>
> > +static int mdio_sam_stat_wait(struct mii_bus *bus, u32 wait_mask)
> > +{
> > + struct mdio_sam_data *data = bus->priv;
> > + unsigned long timeout;
> > + u32 stat;
> > +
> > + timeout = jiffies + msecs_to_jiffies(MDIO_RDY_TMO);
> > + do {
> > + stat = ioread32(data->base + MDIO_STATUS);
> > + if (stat & wait_mask)
> > + return 0;
> > +
> > + usleep_range(50, 100);
> > + } while (time_before(jiffies, timeout));
> > +
> > + return -EBUSY;
>
> I've recently had to fix a loop like this in another
> driver. usleep_range(50, 100) can sleep for a lot longer. If it sleeps
> for MDIO_RDY_TMO you exit out with -EBUSY after a single iteration,
> which is not what you want. It is better to make a fixed number of
> iterations rather than a timeout.
>
OK, I'll change it.
> > +}
> > +
> > +static int mdio_sam_read(struct mii_bus *bus, int phy_id, int regnum)
> > +{
> > + struct mdio_sam_data *data = bus->priv;
> > + u32 command, res;
> > + int ret;
> > +
> > + /* mdiobus_read holds the bus->mdio_lock mutex */
> > +
> > + if (!(regnum & MII_ADDR_C45))
> > + return -ENXIO;
> > +
> > + ret = mdio_sam_stat_wait(bus, STAT_REG_RDY);
> > + if (ret < 0)
> > + return ret;
> > +
> > + command = regnum & 0x1fffff; /* regnum = (dev_id << 16) | reg */
> > + command |= ((phy_id & 0x1f) << 21);
> > +
> > + iowrite32(command, data->base + MDIO_CMD1);
> > + ioread32(data->base + MDIO_CMD1);
>
>
> > + iowrite32(CMD2_READ | CMD2_ENABLE, data->base + MDIO_CMD2);
> > + ioread32(data->base + MDIO_CMD2);
>
> Why do you need to read the values back? Hardware bug?
>
We have a lot of these 'SAM' type of FPGAs, that follow the
same spec, but implemented in a different way. Some software
revisions have issues.
> > + iowrite32(TBL_CMD_REG_GO, data->base + MDIO_TBL_CMD);
> > + ioread32(data->base + MDIO_TBL_CMD);
>
> Although not wrong, most drivers use writel().
>
> > +
> > + usleep_range(50, 100);
> > +
> > + ret = mdio_sam_stat_wait(bus, (STAT_REG_DONE | STAT_REG_ERR));
>
> Do you really need a wait before calling mdio_sam_stat_wait()? Isn't
> that what it is supposed to do, wait...
>
I'll remove it.
> > + if (ret < 0)
> > + return ret;
> > +
> > + res = ioread32(data->base + MDIO_RESULT);
> > +
> > + if (res & RES_ERROR || !(res & RES_SUCCESS))
> > + return -EIO;
> > +
> > + return (res & 0xffff);
> > +}
> > +
> > +static int mdio_sam_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
> > +{
> > + struct mdio_sam_data *data = bus->priv;
> > + u32 command;
> > + int ret;
> > +
> > + /* mdiobus_write holds the bus->mdio_lock mutex */
> > +
> > + if (!(regnum & MII_ADDR_C45))
> > + return -ENXIO;
> > +
> > + ret = mdio_sam_stat_wait(bus, STAT_REG_RDY);
> > + if (ret < 0)
> > + return ret;
> > +
> > + command = regnum & 0x1fffff; /* regnum = (dev_id << 16) | reg */
> > + command |= ((phy_id & 0x1f) << 21);
> > +
> > + iowrite32(command, data->base + MDIO_CMD1);
> > + ioread32(data->base + MDIO_CMD1);
> > + iowrite32(CMD2_ENABLE | val, data->base + MDIO_CMD2);
> > + ioread32(data->base + MDIO_CMD2);
> > + iowrite32(TBL_CMD_REG_GO, data->base + MDIO_TBL_CMD);
> > + ioread32(data->base + MDIO_TBL_CMD);
> > +
> > + usleep_range(50, 100);
> > +
> > + ret = mdio_sam_stat_wait(bus, (STAT_REG_DONE | STAT_REG_ERR));
> > + if (ret < 0)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +
> > +static int mdio_sam_reset(struct mii_bus *bus)
> > +{
> > + struct mdio_sam_data *data = bus->priv;
> > +
> > + iowrite32(TBL_CMD_SOFT_RESET, data->base + MDIO_TBL_CMD);
> > + ioread32(data->base + MDIO_TBL_CMD);
> > + mdelay(10);
> > + iowrite32(0, data->base + MDIO_TBL_CMD);
> > + ioread32(data->base + MDIO_TBL_CMD);
> > +
> > + /* zero tables */
> > + memset_io(data->base + MDIO_CMD1, 0, 0x1000);
> > + memset_io(data->base + MDIO_PRI_CMD1, 0, 0x1000);
>
> What tables?
>
For each bus, the MDIO block has 3 tables of 512 entries - 2
"command" and one "result" table. Addresses and data are
written in the "command" table, the returned value for a
read command is written in the "result" table. Once a start
is triggered, the FPGA will flush all entries in those tables,
starting from offset 0. We don't want to have stale entries
in the tables, as they might result in transactions on the bus,
so we have to clear everything at least once. Actually we're
using just a single entry (1/512) from those tables.
> > +
> > + return 0;
> > +}
> > +
> > +static int mdio_sam_of_register_bus(struct platform_device *pdev,
> > + struct device_node *np, void __iomem *base)
> > +{
> > + struct mii_bus *bus;
> > + struct mdio_sam_data *data;
> > + u32 reg;
> > + int ret;
> > +
> > + bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*data));
> > + if (!bus)
> > + return -ENOMEM;
> > +
> > + /* bus offset */
> > + ret = of_property_read_u32(np, "reg", ®);
> > + if (ret)
> > + return -ENODEV;
> > +
> > + data = bus->priv;
> > + data->base = base + reg;
> > +
> > + bus->parent = &pdev->dev;
> > + bus->name = "mdio-sam";
> > + bus->read = mdio_sam_read;
> > + bus->write = mdio_sam_write;
> > + bus->reset = mdio_sam_reset;
> > + snprintf(bus->id, MII_BUS_ID_SIZE, "mdiosam-%x-%x", pdev->id, reg);
> > +
> > + ret = of_mdiobus_register(bus, np);
> > + if (ret < 0)
> > + return ret;
> > +#ifdef CONFIG_DEBUG_FS
> > + ret = mdio_sam_debugfs_init(bus);
> > + if (ret < 0)
> > + goto err_unregister;
> > +#endif
> > + ret = device_create_bin_file(&bus->dev, &bin_attr_raw_io);
> > + if (ret)
> > + goto err_debugfs;
> > +
> > + return 0;
> > +
> > +err_debugfs:
> > +#ifdef CONFIG_DEBUG_FS
> > + mdio_sam_debugfs_remove(bus);
> > +#endif
> > +err_unregister:
> > + mdiobus_unregister(bus);
> > +
> > + return ret;
> > +}
> > +
> > +static int mdio_sam_of_unregister_bus(struct device_node *np)
> > +{
> > + struct mii_bus *bus;
> > +
> > + bus = of_mdio_find_bus(np);
> > + if (bus) {
> > + device_remove_bin_file(&bus->dev, &bin_attr_raw_io);
> > +#ifdef CONFIG_DEBUG_FS
> > + mdio_sam_debugfs_remove(bus);
> > +#endif
> > + mdiobus_unregister(bus);
> > + }
> > + return 0;
> > +}
> > +
> > +static int mdio_sam_probe(struct platform_device *pdev)
> > +{
> > + struct device_node *np;
> > + struct resource *res;
> > + void __iomem *base;
> > + int ret;
> > +
> > + if (!pdev->dev.of_node)
> > + return -ENODEV;
> > +
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + base = devm_ioremap_nocache(&pdev->dev, res->start,
> > + resource_size(res));
>
> Why nocache?
>
I'll change it to devm_ioremap().
> > + if (IS_ERR(base))
> > + return PTR_ERR(base);
> > +
> > + for_each_available_child_of_node(pdev->dev.of_node, np) {
> > + ret = mdio_sam_of_register_bus(pdev, np, base);
> > + if (ret)
> > + goto err;
> > + }
>
> This is odd. There does not seem to be any shared resources. So you
> should really have one MDIO bus as one device in the device tree.
>
Well, there's no shared resource, so probably it can be changed to
a single device/bus. This might require changing the parent MFD as
well.
>
>
> Andrew
>
> > +
> > + return 0;
> > +err:
> > + /* roll back everything */
> > + for_each_available_child_of_node(pdev->dev.of_node, np)
> > + mdio_sam_of_unregister_bus(np);
> > +
> > + return ret;
> > +}
> > +
> > +static int mdio_sam_remove(struct platform_device *pdev)
> > +{
> > + struct device_node *np;
> > +
> > + for_each_available_child_of_node(pdev->dev.of_node, np)
> > + mdio_sam_of_unregister_bus(np);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct of_device_id mdio_sam_of_match[] = {
> > + { .compatible = "jnx,mdio-sam" },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(of, mdio_sam_of_match);
> > +
> > +static struct platform_driver mdio_sam_driver = {
> > + .probe = mdio_sam_probe,
> > + .remove = mdio_sam_remove,
> > + .driver = {
> > + .name = "mdio-sam",
> > + .owner = THIS_MODULE,
> > + .of_match_table = mdio_sam_of_match,
> > + },
> > +};
> > +
> > +module_platform_driver(mdio_sam_driver);
> > +
> > +MODULE_ALIAS("platform:mdio-sam");
> > +MODULE_AUTHOR("Georgi Vlaev <[email protected]>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_DESCRIPTION("Juniper Networks SAM MDIO bus driver");
> > --
> > 1.9.1
> >
--
joe
On 10/07/2016 08:18 AM, Pantelis Antoniou wrote:
> From: Georgi Vlaev <[email protected]>
>
> Add device tree bindings document for the SAM MDIO block
> present in Juniper's SAM FPGA.
>
> Signed-off-by: Georgi Vlaev <[email protected]>
> [Ported from Juniper kernel]
> Signed-off-by: Pantelis Antoniou <[email protected]>
> ---
> Documentation/devicetree/bindings/net/mdio-sam.txt | 48 ++++++++++++++++++++++
> 1 file changed, 48 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/mdio-sam.txt
>
> diff --git a/Documentation/devicetree/bindings/net/mdio-sam.txt b/Documentation/devicetree/bindings/net/mdio-sam.txt
> new file mode 100644
> index 0000000..7d354e0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/mdio-sam.txt
> @@ -0,0 +1,48 @@
> +Juniper SAM FPGA MFD MDIO bus properties.
> +
> +Required properties:
> +- compatible : "jnx,mdio-sam"
> +- reg : The start offset of the MDIO bus range
> +- #address-cells = <1>;
> +- #size-cells = <0>;
> +
> +Optional properties:
> +
> +Required properties for child nodes:
> +- #address-cells = <1>;
> +- #size-cells = <0>;
> +- reg : The MDIO bus offset within the MDIO range.
I would just refer to Documentation/devicetree/bindings/net/phy.txt for
the child node properties.
Other than that:
Reviewed-by: Florian Fainelli <[email protected]>
--
Florian
On Fri, Oct 07, 2016 at 06:18:30PM +0300, Pantelis Antoniou wrote:
> From: Georgi Vlaev <[email protected]>
>
> Add DT bindings document for the SAM MFD device.
>
> Signed-off-by: Georgi Vlaev <[email protected]>
> [Ported from Juniper kernel]
> Signed-off-by: Pantelis Antoniou <[email protected]>
> ---
> 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"
Kind of generic. Only 1 FPGA version or some other way to tell the
version?
> +
> +Optional properties:
> +
> +- pma-coefficients: A set of tupples containing the configuration of the PMA.
What's a PMA? What type of configuration? How many entries?
> +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 {
What are the numbers?
> + compatible = "jnx,sam";
If this is a PCI device, then it should use PCI compatible string
syntax.
> + #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 {
gpio@...
Where does 10 come from?
> + 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
>
On Fri, Oct 07, 2016 at 06:18:32PM +0300, Pantelis Antoniou wrote:
> From: Georgi Vlaev <[email protected]>
>
> Add binding document for the i2c driver of SAM FPGA.
>
> Signed-off-by: Georgi Vlaev <[email protected]>
> [Ported from Juniper kernel]
> Signed-off-by: Pantelis Antoniou <[email protected]>
> ---
> .../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.
This is not how i2c muxes are done.
> +
> +Optional properties:
> +- speed: If present must be either 100000 or 400000. No other values supported.
> +
> +Examples:
> +
> +pe1i2c: i2c-sam-mux@1,0 {
i2c-mux@...
> + 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
What is this needed for?
> +
> +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.
Make this required.
> +- reverse-fill: Fill the start entries of transactions in reverse order
Needs a better explanation.
> +- priority-tables: Use the pre-programmed priority tables in the FPGA
What does not present mean?
> +- 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.
Needs a better explanation.
> +
> +Examples:
> +
> +i2c-sam {
> + compatible = "jnx,i2c-sam";
> + mux-channels = <2>;
> + #size-cells = <0>;
> + #address-cells = <2>;
> +
> + /* PE0 */ pe0i2c: i2c-sam-mux@0,0 {
i2c-mux@...
> + 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
>
On Fri, Oct 07, 2016 at 06:18:34PM +0300, Pantelis Antoniou wrote:
> From: Georgi Vlaev <[email protected]>
>
> Add device tree bindings document for the GPIO driver of
> Juniper's SAM FPGA.
>
> Signed-off-by: Georgi Vlaev <[email protected]>
> [Ported from Juniper kernel]
> Signed-off-by: Pantelis Antoniou <[email protected]>
> ---
> .../devicetree/bindings/gpio/jnx,gpio-sam.txt | 110 +++++++++++++++++++++
> 1 file changed, 110 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
>
> diff --git a/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt b/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
> new file mode 100644
> index 0000000..514c350
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
> @@ -0,0 +1,110 @@
> +Juniper SAM FPGA GPIO block
> +
> +The controller's registers are organized as sets of eight 32-bit
> +registers with each set controlling a bank of up to 32 pins. A single
> +interrupt is shared for all of the banks handled by the controller.
> +
> +Required properties:
> +
> +- compatible:
> + Must be "jnx,gpio-sam"
> +
> +- #gpio-cells:
> + Should be <2>. The first cell is the pin number (within the controller's
> + pin space), and the second is used for the following flags:
> + bit[0]: direction (0 = out, 1 = in)
> + bit[1]: init high
> + bit[2]: active low
> + bit[3]: open drain
> + bit[4]: open drain
Use and/or add to standard flags.
> +
> +- gpio-controller:
> + Specifies that the node is a GPIO controller.
> +
> +Optional properties:
> +
> +- reg:
> + This driver is part of the SAM FPGA MFD driver, so the
> + address range is supplied by that driver. However you can
> + override using this property.
> +
> +- gpio-base:
> + Base of the GPIO pins of this instance. If not present use system allocated.
This probably needs to go.
> +
> +- gpio-count:
ngpios instead.
> + Number of GPIO pins of this instance. If not present read the number from
> + the one configured in the FPGA data. Maximum number is 512.
> +
> +- #interrupt-cells:
> + Should be <2>. The first cell is the GPIO number, the second should specify
> + flags. The following subset of flags is supported:
> + - bits[16,4:0] trigger type and level flags
> + bit 0: rising edge interrupt
> + bit 1: falling edge interrupt
> + bit 2: active high interrupt
> + bit 3: active low interrupt
> + bit 4: enable debounce
> + bit 16: signal is active low
What does this mean?
> + See also Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
> +
> +- gpio-interrupts:
> + A number of triples that define the mapping of interrupt groupsb to a range of
> + pins. The first cell defines the interrupt group, the second is the start of
> + the pin range and the third the number of pins in the range.
Needs a vendor prefix.
> +
> +- gpio-exports:
> + A subnode containing the list of pins that will be exported to user-space.
DT doesn't know about userspace. Drop this.
> + Each subnode contains:
> + Required properties:
> + - pin: The gpio to be exported and the relevant flags.
> + Optional properties:
> + - label: The label to use for export; if not supplied use the node name.
> +
> +Example:
> +
> +gpio20: gpio-sam {
> + compatible = "jnx,gpio-sam";
> + gpio-controller;
> + interrupt-controller;
> + /* 1st cell: gpio pin
> + * 2nd cell: flags (bit mask)
> + * bit 0: rising edge interrupt
> + * bit 1: falling edge interrupt
> + * bit 2: active high interrupt
> + * bit 3: active low interrupt
> + * bit 4: enable debounce
> + * bit 16: signal is active low
> + */
> + #interrupt-cells = <2>;
> + #gpio-cells = <2>;
> + gpio-count = <340>;
> + /* 1st cell: gpio interrupt status bit
> + * 2nd cell: 1st pin
> + * 3rd cell: # of pins
> + */
> + gpio-interrupts =
> + <0 0 32>, /* TL / TQ */
> + <1 32 32>, /* PIC 1 */
> + <2 32 32>, /* PIC 1 spare */
> + <7 148 32>, /* PIC 0 */
> + <8 170 32>, /* PIC 0 spare */
> + <16 318 22>; /* FPC */
> +
> + gpio-exports {
> + /*
> + * flags:
> + * GPIOF_DIR_IN bit 0=1
> + * GPIOF_DIR_OUT bit 0=0
> + * GPIOF_INIT_HIGH bit 1=1
> + * GPIOF_INIT_HIGH is raw, not translated
> + * GPIOF_ACTIVE_LOW bit 2=1
> + * GPIOF_OPEN_DRAIN bit 3=1
> + * GPIOF_OPEN_SOURCE bit 4=1
> + * GPIOF_EXPORT bit 5=1
> + * GPIOF_EXPORT_CHANGEABLE bit 6=1
> + */
> + tl0-rst {
> + pin = < 8 0x24 >;
> + };
> + };
> +};
> --
> 1.9.1
>
gOn Fri, Oct 07, 2016 at 06:18:36PM +0300, Pantelis Antoniou wrote:
> From: Georgi Vlaev <[email protected]>
>
> Add binding document for Junipers Flash IP block present
> in the SAM FPGA on PTX series of routers.
>
> Signed-off-by: Georgi Vlaev <[email protected]>
> [Ported from Juniper kernel]
> Signed-off-by: Pantelis Antoniou <[email protected]>
> ---
> .../devicetree/bindings/mtd/flash-sam.txt | 31 ++++++++++++++++++++++
> 1 file changed, 31 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mtd/flash-sam.txt
>
> diff --git a/Documentation/devicetree/bindings/mtd/flash-sam.txt b/Documentation/devicetree/bindings/mtd/flash-sam.txt
> new file mode 100644
> index 0000000..bdf1d78
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/flash-sam.txt
> @@ -0,0 +1,31 @@
> +Flash device on a Juniper SAM FPGA
> +
> +These flash chips are found in the PTX series of Juniper routers.
> +
> +They are regular CFI compatible (Intel or AMD extended) flash chips with
> +some special write protect/VPP bits that can be controlled by the machine's
> +system controller.
And where's the description of the sys ctrlr?
> +
> +Required properties:
> +- compatible : must be "jnx,flash-sam"
> +
> +Optional properties:
> +- reg : memory address for the flash chip, note that this is not
> +required since usually the device is a subdevice of the SAM MFD
> +driver which fills in the register fields.
> +
> +For the rest of the properties, see mtd-physmap.txt.
> +
> +The device tree may optionally contain sub-nodes describing partitions of the
> +address space. See partition.txt for more detail.
> +
> +Example:
> +
> +flash_sam {
> + compatible = "jnx,flash-sam";
> + partition@0 {
This should have a heirarchy of a controller node, a flash child node,
partitions child node, and partition child nodes.
> + reg = <0x0 0x400000>;
> + label = "pic0-golden";
> + read-only;
> + };
> +};
> --
> 1.9.1
>
On 2016-10-07 17:18, Pantelis Antoniou wrote:
> From: Georgi Vlaev <[email protected]>
>
> Add device tree bindings document for the SAM MDIO block
> present in Juniper's SAM FPGA.
>
> Signed-off-by: Georgi Vlaev <[email protected]>
> [Ported from Juniper kernel]
> Signed-off-by: Pantelis Antoniou <[email protected]>
> ---
> Documentation/devicetree/bindings/net/mdio-sam.txt | 48 ++++++++++++++++++++++
> 1 file changed, 48 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/mdio-sam.txt
>
> diff --git a/Documentation/devicetree/bindings/net/mdio-sam.txt b/Documentation/devicetree/bindings/net/mdio-sam.txt
> new file mode 100644
> index 0000000..7d354e0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/mdio-sam.txt
> @@ -0,0 +1,48 @@
> +Juniper SAM FPGA MFD MDIO bus properties.
> +
> +Required properties:
> +- compatible : "jnx,mdio-sam"
> +- reg : The start offset of the MDIO bus range
> +- #address-cells = <1>;
> +- #size-cells = <0>;
> +
> +Optional properties:
> +
> +Required properties for child nodes:
> +- #address-cells = <1>;
> +- #size-cells = <0>;
> +- reg : The MDIO bus offset within the MDIO range.
> +
> +
> +Example :
> +
> + sam@10 {
> + compatible = "jnx,sam";
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + mdio-sam@10 {
This should be mdio-sam@40000, OR ...
> + compatible = "jnx,mdio-sam";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + reg = <0x40000>;
... this should be reg = <0x10>. AFAIK.
Cheers,
Peter
> +
> + 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>;
> + };
> + };
> + };
>
On 2016-10-10 21:54, Rob Herring wrote:
> On Fri, Oct 07, 2016 at 06:18:32PM +0300, Pantelis Antoniou wrote:
>> From: Georgi Vlaev <[email protected]>
>>
>> Add binding document for the i2c driver of SAM FPGA.
>>
>> Signed-off-by: Georgi Vlaev <[email protected]>
>> [Ported from Juniper kernel]
>> Signed-off-by: Pantelis Antoniou <[email protected]>
>> ---
>> .../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.
>
> This is not how i2c muxes are done.
>
>> +
>> +Optional properties:
>> +- speed: If present must be either 100000 or 400000. No other values supported.
>> +
>> +Examples:
>> +
>> +pe1i2c: i2c-sam-mux@1,0 {
>
> i2c-mux@...
>
>> + 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
>
> What is this needed for?
>
>> +
>> +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.
>
> Make this required.
>
>> +- reverse-fill: Fill the start entries of transactions in reverse order
>
> Needs a better explanation.
>
>> +- priority-tables: Use the pre-programmed priority tables in the FPGA
>
> What does not present mean?
>
>> +- 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.
>
> Needs a better explanation.
>
>> +
>> +Examples:
>> +
>> +i2c-sam {
>> + compatible = "jnx,i2c-sam";
>> + mux-channels = <2>;
>> + #size-cells = <0>;
>> + #address-cells = <2>;
>> +
>> + /* PE0 */ pe0i2c: i2c-sam-mux@0,0 {
>
> i2c-mux@...
Hmm, I actually think i2c@... is the usual naming for i2c-mux children.
Cheers,
Peter
>> + 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
>>
Hi Rob,
> On Oct 10, 2016, at 23:03 , Rob Herring <[email protected]> wrote:
>
> On Fri, Oct 07, 2016 at 06:18:34PM +0300, Pantelis Antoniou wrote:
>> From: Georgi Vlaev <[email protected]>
>>
>> Add device tree bindings document for the GPIO driver of
>> Juniper's SAM FPGA.
>>
>> Signed-off-by: Georgi Vlaev <[email protected]>
>> [Ported from Juniper kernel]
>> Signed-off-by: Pantelis Antoniou <[email protected]>
>> ---
>> .../devicetree/bindings/gpio/jnx,gpio-sam.txt | 110 +++++++++++++++++++++
>> 1 file changed, 110 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
>>
>> diff --git a/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt b/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
>> new file mode 100644
>> index 0000000..514c350
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
>> @@ -0,0 +1,110 @@
>> +Juniper SAM FPGA GPIO block
>> +
>> +The controller's registers are organized as sets of eight 32-bit
>> +registers with each set controlling a bank of up to 32 pins. A single
>> +interrupt is shared for all of the banks handled by the controller.
>> +
>> +Required properties:
>> +
>> +- compatible:
>> + Must be "jnx,gpio-sam"
>> +
>> +- #gpio-cells:
>> + Should be <2>. The first cell is the pin number (within the controller's
>> + pin space), and the second is used for the following flags:
>> + bit[0]: direction (0 = out, 1 = in)
>> + bit[1]: init high
>> + bit[2]: active low
>> + bit[3]: open drain
>> + bit[4]: open drain
>
> Use and/or add to standard flags.
>
OK.
>> +
>> +- gpio-controller:
>> + Specifies that the node is a GPIO controller.
>> +
>> +Optional properties:
>> +
>> +- reg:
>> + This driver is part of the SAM FPGA MFD driver, so the
>> + address range is supplied by that driver. However you can
>> + override using this property.
>> +
>> +- gpio-base:
>> + Base of the GPIO pins of this instance. If not present use system allocated.
>
> This probably needs to go.
>
OK.
>> +
>> +- gpio-count:
>
> ngpios instead.
>
OK.
>> + Number of GPIO pins of this instance. If not present read the number from
>> + the one configured in the FPGA data. Maximum number is 512.
>> +
>> +- #interrupt-cells:
>> + Should be <2>. The first cell is the GPIO number, the second should specify
>> + flags. The following subset of flags is supported:
>> + - bits[16,4:0] trigger type and level flags
>> + bit 0: rising edge interrupt
>> + bit 1: falling edge interrupt
>> + bit 2: active high interrupt
>> + bit 3: active low interrupt
>> + bit 4: enable debounce
>> + bit 16: signal is active low
>
> What does this mean?
>
I will reword.
>> + See also Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
>> +
>> +- gpio-interrupts:
>> + A number of triples that define the mapping of interrupt groupsb to a range of
>> + pins. The first cell defines the interrupt group, the second is the start of
>> + the pin range and the third the number of pins in the range.
>
> Needs a vendor prefix.
>
OK.
>> +
>> +- gpio-exports:
>> + A subnode containing the list of pins that will be exported to user-space.
>
> DT doesn't know about userspace. Drop this.
>
OK, the export bit should go.
>> + Each subnode contains:
>> + Required properties:
>> + - pin: The gpio to be exported and the relevant flags.
>> + Optional properties:
>> + - label: The label to use for export; if not supplied use the node name.
>> +
>> +Example:
>> +
>> +gpio20: gpio-sam {
>> + compatible = "jnx,gpio-sam";
>> + gpio-controller;
>> + interrupt-controller;
>> + /* 1st cell: gpio pin
>> + * 2nd cell: flags (bit mask)
>> + * bit 0: rising edge interrupt
>> + * bit 1: falling edge interrupt
>> + * bit 2: active high interrupt
>> + * bit 3: active low interrupt
>> + * bit 4: enable debounce
>> + * bit 16: signal is active low
>> + */
>> + #interrupt-cells = <2>;
>> + #gpio-cells = <2>;
>> + gpio-count = <340>;
>> + /* 1st cell: gpio interrupt status bit
>> + * 2nd cell: 1st pin
>> + * 3rd cell: # of pins
>> + */
>> + gpio-interrupts =
>> + <0 0 32>, /* TL / TQ */
>> + <1 32 32>, /* PIC 1 */
>> + <2 32 32>, /* PIC 1 spare */
>> + <7 148 32>, /* PIC 0 */
>> + <8 170 32>, /* PIC 0 spare */
>> + <16 318 22>; /* FPC */
>> +
>> + gpio-exports {
>> + /*
>> + * flags:
>> + * GPIOF_DIR_IN bit 0=1
>> + * GPIOF_DIR_OUT bit 0=0
>> + * GPIOF_INIT_HIGH bit 1=1
>> + * GPIOF_INIT_HIGH is raw, not translated
>> + * GPIOF_ACTIVE_LOW bit 2=1
>> + * GPIOF_OPEN_DRAIN bit 3=1
>> + * GPIOF_OPEN_SOURCE bit 4=1
>> + * GPIOF_EXPORT bit 5=1
>> + * GPIOF_EXPORT_CHANGEABLE bit 6=1
>> + */
>> + tl0-rst {
>> + pin = < 8 0x24 >;
>> + };
>> + };
>> +};
>> --
>> 1.9.1
Hi Rob,
> On Oct 10, 2016, at 23:07 , Rob Herring <[email protected]> wrote:
>
> gOn Fri, Oct 07, 2016 at 06:18:36PM +0300, Pantelis Antoniou wrote:
>> From: Georgi Vlaev <[email protected]>
>>
>> Add binding document for Junipers Flash IP block present
>> in the SAM FPGA on PTX series of routers.
>>
>> Signed-off-by: Georgi Vlaev <[email protected]>
>> [Ported from Juniper kernel]
>> Signed-off-by: Pantelis Antoniou <[email protected]>
>> ---
>> .../devicetree/bindings/mtd/flash-sam.txt | 31 ++++++++++++++++++++++
>> 1 file changed, 31 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/mtd/flash-sam.txt
>>
>> diff --git a/Documentation/devicetree/bindings/mtd/flash-sam.txt b/Documentation/devicetree/bindings/mtd/flash-sam.txt
>> new file mode 100644
>> index 0000000..bdf1d78
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mtd/flash-sam.txt
>> @@ -0,0 +1,31 @@
>> +Flash device on a Juniper SAM FPGA
>> +
>> +These flash chips are found in the PTX series of Juniper routers.
>> +
>> +They are regular CFI compatible (Intel or AMD extended) flash chips with
>> +some special write protect/VPP bits that can be controlled by the machine's
>> +system controller.
>
> And where's the description of the sys ctrlr?
>
The system controller is Juniper IP. We’ll have to ask around about
specifics, and it’s pretty uninspiring stuff.
>> +
>> +Required properties:
>> +- compatible : must be "jnx,flash-sam"
>> +
>> +Optional properties:
>> +- reg : memory address for the flash chip, note that this is not
>> +required since usually the device is a subdevice of the SAM MFD
>> +driver which fills in the register fields.
>> +
>> +For the rest of the properties, see mtd-physmap.txt.
>> +
>> +The device tree may optionally contain sub-nodes describing partitions of the
>> +address space. See partition.txt for more detail.
>> +
>> +Example:
>> +
>> +flash_sam {
>> + compatible = "jnx,flash-sam";
>> + partition@0 {
>
> This should have a heirarchy of a controller node, a flash child node,
> partitions child node, and partition child nodes.
>
OK.
>> + reg = <0x0 0x400000>;
>> + label = "pic0-golden";
>> + read-only;
>> + };
>> +};
>> --
>> 1.9.1
n Fri, Oct 7, 2016 at 5:18 PM, Pantelis Antoniou
<[email protected]> wrote:
> From: Guenter Roeck <[email protected]>
>
> The SAM GPIO IP block is present in the Juniper PTX series
> of routers as part of the SAM FPGA.
>
> Signed-off-by: Georgi Vlaev <[email protected]>
> Signed-off-by: Guenter Roeck <[email protected]>
> Signed-off-by: Rajat Jain <[email protected]>
> [Ported from Juniper kernel]
> Signed-off-by: Pantelis Antoniou <[email protected]>
First copy/paste my other review comments on the previous driver
I reviewed, this seems to have pretty much all the same issues.
> +config GPIO_SAM
> + tristate "SAM FPGA GPIO"
> + depends on MFD_JUNIPER_SAM
> + default y if MFD_JUNIPER_SAM
I suspect this should use
select GPIOLIB_IRQCHIP
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/pci.h>
> +#include <linux/gpio.h>
<linux/gpio/driver.h>
> +#include <linux/interrupt.h>
> +#include <linux/irqdomain.h>
Not needed with GPIOLIB_IRQCHIP
> +#include <linux/errno.h>
> +#include <linux/of_device.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_gpio.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/sched.h>
Why?
> +#include <linux/mfd/sam.h>
> +
> +/* gpio status/configuration */
> +#define SAM_GPIO_NEG_EDGE (1 << 8)
> +#define SAM_GPIO_NEG_EDGE_EN (1 << 7)
> +#define SAM_GPIO_POS_EDGE (1 << 6)
> +#define SAM_GPIO_POS_EDGE_EN (1 << 5)
Interrupt triggers I suppose.
> +#define SAM_GPIO_BLINK (1 << 4)
Cool, we don't support that in gpiolib as of now.
Maybe we should.
> +#define SAM_GPIO_OUT (1 << 3)
> +#define SAM_GPIO_OUT_TS (1 << 2)
OUT_TS ... what does TS mean here?
> +#define SAM_GPIO_DEBOUNCE_EN (1 << 1)
> +#define SAM_GPIO_IN (1 << 0)
> +
> +#define SAM_GPIO_BASE 0x1000
Base into what, and why is this not coming from PCI
or the device tree?
> +#define SAM_MAX_NGPIO 512
Why do we need to know this and what does it really mean?
That is the max number the GPIO subsystem can handle by
the way.
> +#define SAM_GPIO_ADDR(addr, nr) ((addr) + SAM_GPIO_BASE + (nr) * sizeof(u32))
Why can't we just offset the address earlier, ah well it's OK.
> +struct sam_gpio_irq_group {
> + int start; /* 1st gpio pin */
> + int count; /* # of pins in group */
> + int num_enabled; /* # of enabled interrupts */
> +};
> +
> +/**
> + * struct sam_gpio - GPIO private data structure.
> + * @base: PCI base address of Memory mapped I/O register.
> + * @dev: Pointer to device structure.
> + * @gpio: Data for GPIO infrastructure.
> + * @gpio_base: 1st gpio pin
> + * @gpio_count: # of gpio pins
> + * @irq_lock: Lock used by interrupt subsystem
> + * @domain: Pointer to interrupt domain
> + * @irq: Interrupt # from parent
> + * @irq_high: Second interrupt # from parent
> + * (currently unused)
> + * @irq_group: Interrupt group descriptions
> + * (one group per interrupt bit)
> + * @irq_type: The interrupt type for each gpio pin
> + */
Why do you need to keep all of this around? Is is all really
used? gpio_base makes me nervous we generally use dynamic
allocation of GPIO numbers these days.
> +struct sam_gpio {
> + void __iomem *base;
> + struct device *dev;
> + struct gpio_chip gpio;
> + int gpio_base;
> + int gpio_count;
> + struct mutex irq_lock;
> + struct irq_domain *domain;
> + int irq;
> + int irq_high;
> + struct sam_gpio_irq_group irq_group[18];
> + u8 irq_type[SAM_MAX_NGPIO];
> + struct sam_platform_data *pdata;
> + const char **names;
> + u32 *export_flags;
> +};
> +#define to_sam(chip) container_of((chip), struct sam_gpio, gpio)
Instead of this use gpiochip_get_data(). Applies everywhere.
> +static void sam_gpio_bitop(struct sam_gpio *sam, unsigned int nr,
> + u32 bit, bool set)
> +{
> + u32 reg;
> +
> + reg = ioread32(SAM_GPIO_ADDR(sam->base, nr));
> + if (set)
> + reg |= bit;
> + else
> + reg &= ~bit;
> + iowrite32(reg, SAM_GPIO_ADDR(sam->base, nr));
> + ioread32(SAM_GPIO_ADDR(sam->base, nr));
> +}
Does that rally need a helper function?
Use BIT() and inline like I explained in the previous patch.
> +static void sam_gpio_setup(struct sam_gpio *sam)
> +{
> + struct gpio_chip *chip = &sam->gpio;
> +
> + chip->parent = sam->dev;
> + chip->label = dev_name(sam->dev);
> + chip->owner = THIS_MODULE;
> + chip->direction_input = sam_gpio_direction_input;
> + chip->get = sam_gpio_get;
> + chip->direction_output = sam_gpio_direction_output;
Implement also chip->get_direction
> + chip->set = sam_gpio_set;
> + chip->set_debounce = sam_gpio_debounce;
> + chip->dbg_show = NULL;
> + chip->base = sam->gpio_base;
Oh no, why. Use -1 please and let gpiolib decide...
> + chip->ngpio = sam->gpio_count;
> +#ifdef CONFIG_OF_GPIO
> + chip->of_node = sam->dev->of_node;
> +#endif
I doubt this #ifdef actually. If the driver needs CONFIG_OF_GPIO to
work it should just depend on it in Kconfig.
> +static int sam_of_get_exports(struct device *dev, struct sam_gpio *sam)
> +{
> + struct device_node *child, *exports;
> + int err = 0;
> +
> + if (dev->of_node == NULL)
> + return 0; /* No FDT node, we are done */
> +
> + exports = of_get_child_by_name(dev->of_node, "gpio-exports");
> + if (exports == NULL)
> + return 0; /* No exports, we are done */
> +
> + if (of_get_child_count(exports) == 0)
> + return 0; /* No children, we are done */
> +
> + sam->names = devm_kzalloc(dev, sizeof(char *) * sam->gpio_count,
> + GFP_KERNEL);
> + if (sam->names == NULL) {
> + err = -ENOMEM;
> + goto error;
> + }
> + sam->export_flags =
> + devm_kzalloc(dev, sizeof(u32) * sam->gpio_count, GFP_KERNEL);
> + if (sam->export_flags == NULL) {
> + err = -ENOMEM;
> + goto error;
> + }
> + for_each_child_of_node(exports, child) {
> + const char *label;
> + u32 pin, flags;
> +
> + label = of_get_property(child, "label", NULL) ? : child->name;
> + err = of_property_read_u32_index(child, "pin", 0, &pin);
> + if (err)
> + break;
> + if (pin >= sam->gpio_count) {
> + err = -EINVAL;
> + break;
> + }
> + err = of_property_read_u32_index(child, "pin", 1, &flags);
> + if (err)
> + break;
> + /*
> + * flags:
> + * GPIOF_DIR_IN bit 0=1
> + * GPIOF_DIR_OUT bit 0=0
> + * GPIOF_INIT_HIGH bit 1=1
> + * GPIOF_ACTIVE_LOW bit 2=1
> + * GPIOF_OPEN_DRAIN bit 3=1
> + * GPIOF_OPEN_SOURCE bit 4=1
> + * GPIOF_EXPORT bit 5=1
> + * GPIOF_EXPORT_CHANGEABLE bit 6=1
> + */
> + sam->names[pin] = label;
> + sam->export_flags[pin] = flags;
> + }
> +error:
> + of_node_put(exports);
> + return err;
> +}
What? NAK never in my life. This looks like an old hack to
export stuff to userspace. We don't do that. The kernel supports
gpio-line-names to name lines in the device tree, and you can use
the new chardev ABI to access it from userspace, sysfs is dead.
Delete this function entirely.
> +static int sam_gpio_of_init(struct device *dev, struct sam_gpio *sam)
> +{
> + int err;
> + u32 val;
> + const u32 *igroup;
> + u32 group, start, count;
> + int i, iglen, ngpio;
> +
> + if (of_have_populated_dt() && !dev->of_node) {
> + dev_err(dev, "No device node\n");
> + return -ENODEV;
> + }
So obviously this driver Kconfig should depend on OF_GPIO.
> +
> + err = of_property_read_u32(dev->of_node, "gpio-base", &val);
> + if (err)
> + val = -1;
> + sam->gpio_base = val;
NAK, No Linux bases in the device tree. Only use -1.
> + err = of_property_read_u32(dev->of_node, "gpio-count", &val);
> + if (!err) {
> + if (val > SAM_MAX_NGPIO)
> + val = SAM_MAX_NGPIO;
> + sam->gpio_count = val;
> + }
As described in the generic bindings, use "ngpios" for this if you need it.
> + igroup = of_get_property(dev->of_node, "gpio-interrupts", &iglen);
NAK on that binding.
> + if (igroup) {
> + iglen /= sizeof(u32);
> + if (iglen < 3 || iglen % 3)
> + return -EINVAL;
> + iglen /= 3;
> + for (i = 0; i < iglen; i++) {
> + group = be32_to_cpu(igroup[i * 3]);
> + if (group >= ARRAY_SIZE(sam->irq_group))
> + return -EINVAL;
> + start = be32_to_cpu(igroup[i * 3 + 1]);
> + count = be32_to_cpu(igroup[i * 3 + 2]);
> + if (start >= sam->gpio_count || count == 0 ||
> + start + count > sam->gpio_count)
> + return -EINVAL;
> + sam->irq_group[group].start = start;
> + sam->irq_group[group].count = count;
> + }
> + }
Do not invent custom interrupt bindings like this. Use the
standard device tree mechanism to resolve IRQs from the parent
controller. Maybe you also need to use hierarchical irqdomain
in Linux.
> +static int sam_gpio_pin_to_irq_bit(struct sam_gpio *sam, int pin)
> +{
> + int bit;
> +
> + for (bit = 0; bit < ARRAY_SIZE(sam->irq_group); bit++) {
> + struct sam_gpio_irq_group *irq_group = &sam->irq_group[bit];
> +
> + if (irq_group->count &&
> + pin >= irq_group->start &&
> + pin <= irq_group->start + irq_group->count)
> + return bit;
> + }
> + return -EINVAL;
> +}
> +
> +static bool sam_gpio_irq_handle_group(struct sam_gpio *sam,
> + struct sam_gpio_irq_group *irq_group)
> +{
> + unsigned int virq = 0;
> + bool handled = false;
> + bool repeat;
> + int i;
> +
> + /* no irq_group for the interrupt bit */
> + if (!irq_group->count)
> + return false;
> +
> + WARN_ON(irq_group->num_enabled == 0);
> + do {
> + repeat = false;
> + for (i = 0; i < irq_group->count; i++) {
> + int pin = irq_group->start + i;
> + bool low, high;
> + u32 regval;
> + u8 type;
> +
> + regval = ioread32(SAM_GPIO_ADDR(sam->base, pin));
> + /*
> + * write back status to clear POS_EDGE and NEG_EDGE
> + * status for this GPIO pin (status bits are
> + * clear-on-one). This is necessary to clear the
> + * high level interrupt status.
> + * Also consider the interrupt to be handled in that
> + * case, even if there is no taker.
> + */
> + if (regval & (SAM_GPIO_POS_EDGE | SAM_GPIO_NEG_EDGE)) {
> + iowrite32(regval,
> + SAM_GPIO_ADDR(sam->base, pin));
> + ioread32(SAM_GPIO_ADDR(sam->base, pin));
> + handled = true;
> + }
> +
> + /*
> + * Check if the pin changed its state.
> + * If it did, and if the expected condition applies,
> + * generate a virtual interrupt.
> + * A pin can only generate an interrupt if
> + * - interrupts are enabled for it
> + * - it is configured as input
> + */
> +
> + if (!sam->irq_type[pin])
> + continue;
> + if (!(regval & SAM_GPIO_OUT_TS))
> + continue;
> +
> + high = regval & (SAM_GPIO_IN | SAM_GPIO_POS_EDGE);
> + low = !(regval & SAM_GPIO_IN) ||
> + (regval & SAM_GPIO_NEG_EDGE);
> + type = sam->irq_type[pin];
> + if (((type & IRQ_TYPE_EDGE_RISING) &&
> + (regval & SAM_GPIO_POS_EDGE)) ||
> + ((type & IRQ_TYPE_EDGE_FALLING) &&
> + (regval & SAM_GPIO_NEG_EDGE)) ||
> + ((type & IRQ_TYPE_LEVEL_LOW) && low) ||
> + ((type & IRQ_TYPE_LEVEL_HIGH) && high)) {
This if() clause does not look sane, you have to see that.
If you think this is proper then explain with detailed comments
what is going on.
> + virq = irq_find_mapping(sam->domain, pin);
> + handle_nested_irq(virq);
> + if (type & (IRQ_TYPE_LEVEL_LOW
> + | IRQ_TYPE_LEVEL_HIGH))
> + repeat = true;
> + }
> + }
> + schedule();
Why? Voluntary preemption? Just use a threaded interrupt handler
instead.
> + } while (repeat);
> +
> + return handled;
> +}
> +
> +static irqreturn_t sam_gpio_irq_handler(int irq, void *data)
> +{
> + struct sam_gpio *sam = data;
> + struct sam_platform_data *pdata = sam->pdata;
> + irqreturn_t ret = IRQ_NONE;
> + bool handled;
> + u32 status;
> +
> + do {
> + handled = false;
> + status = pdata->irq_status(sam->dev->parent, SAM_IRQ_GPIO,
> + sam->irq);
> + pdata->irq_status_clear(sam->dev->parent, SAM_IRQ_GPIO,
> + sam->irq, status);
> + while (status) {
> + unsigned int bit;
> +
> + bit = __ffs(status);
> + status &= ~(1 << bit);
> + handled =
> + sam_gpio_irq_handle_group(sam, &sam->irq_group[bit]);
> + if (handled)
> + ret = IRQ_HANDLED;
> + }
> + } while (handled);
This handled business looks fragile. But OK.
It is a simple IRQ handler, this driver should definately use
GPIOLIB_IRQCHIP. Please look at other drivers doing that
for inspiration. It is also well described in
Documenation/gpio/driver.txt
> +static int sam_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
> +{
> + struct sam_gpio *sam = to_sam(chip);
> +
> + return irq_create_mapping(sam->domain, offset);
> +}
With GPIOLIB_IRQCHIP you do not need to implement .to_irq()
> +static void sam_irq_mask(struct irq_data *data)
> +{
> + struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
> + struct sam_platform_data *pdata = sam->pdata;
> + int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
> +
> + if (bit < 0)
> + return;
> +
> + if (--sam->irq_group[bit].num_enabled <= 0) {
> + pdata->disable_irq(sam->dev->parent, SAM_IRQ_GPIO, sam->irq,
> + 1 << bit);
Just BIT(bit)
> +static void sam_irq_unmask(struct irq_data *data)
> +{
> + struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
> + struct sam_platform_data *pdata = sam->pdata;
> + int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
> +
> + if (bit < 0)
> + return;
Do you expect this to happen a lot? Else just delete the check or print
an error message.
> +
> + sam->irq_group[bit].num_enabled++;
> + pdata->enable_irq(sam->dev->parent, SAM_IRQ_GPIO, sam->irq, 1 << bit);
Dito.
> +static int sam_irq_set_type(struct irq_data *data, unsigned int type)
> +{
> + struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
> + int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
> +
> + if (bit < 0)
> + return bit;
> +
> + sam->irq_type[data->hwirq] = type & 0x0f;
Why storing this and going to all that trouble?
> + sam_gpio_bitop(sam, data->hwirq, SAM_GPIO_OUT_TS, true);
> + sam_gpio_bitop(sam, data->hwirq, SAM_GPIO_DEBOUNCE_EN, type & 0x10);
> + sam_gpio_bitop(sam, data->hwirq,
> + SAM_GPIO_POS_EDGE_EN | SAM_GPIO_POS_EDGE,
> + type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_HIGH));
> + sam_gpio_bitop(sam, data->hwirq,
> + SAM_GPIO_NEG_EDGE_EN | SAM_GPIO_NEG_EDGE,
> + type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_LEVEL_LOW));
> +
> + return 0;
> +}
Just set up different handlers depending on edge. This likely simplifies your
IRQ handler code as well, as a special function (.irq_ack()) gets called for
edge IRQs.
Look how drivers/gpio/gpio-pl061.c does it.
> +static void sam_irq_bus_lock(struct irq_data *data)
> +{
> + struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
> +
> + mutex_lock(&sam->irq_lock);
> +}
> +
> +static void sam_irq_bus_unlock(struct irq_data *data)
> +{
> + struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
> +
> + /* Synchronize interrupts to chip */
> +
> + mutex_unlock(&sam->irq_lock);
> +}
Aha OK if it's necessary then we need to do this.
> +static struct irq_chip sam_irq_chip = {
> + .name = "gpio-sam",
Maybe this should have an instance number or something.
Just an idea no requirement.
> + .irq_mask = sam_irq_mask,
> + .irq_unmask = sam_irq_unmask,
So I think this needs .irq_ack() to solve the mess above in
the interrupt handler.
> + .irq_set_type = sam_irq_set_type,
> + .irq_bus_lock = sam_irq_bus_lock,
> + .irq_bus_sync_unlock = sam_irq_bus_unlock,
> +};
> +
> +static int sam_gpio_irq_map(struct irq_domain *domain, unsigned int irq,
> + irq_hw_number_t hwirq)
> +{
> + irq_set_chip_data(irq, domain->host_data);
> + irq_set_chip(irq, &sam_irq_chip);
> + irq_set_nested_thread(irq, true);
> +
> + irq_set_noprobe(irq);
> +
> + return 0;
> +}
This will not be needed if you use GPIOLIB_IRQCHIP
> +static const struct irq_domain_ops sam_gpio_irq_domain_ops = {
> + .map = sam_gpio_irq_map,
> + .xlate = irq_domain_xlate_twocell,
> +};
Nor this.
> +static int sam_gpio_irq_setup(struct device *dev, struct sam_gpio *sam)
> +{
> + int ret;
> +
> + sam->domain = irq_domain_add_linear(dev->of_node,
> + sam->gpio_count,
> + &sam_gpio_irq_domain_ops,
> + sam);
> + if (sam->domain == NULL)
> + return -ENOMEM;
Nor this.
> + ret = devm_request_threaded_irq(dev, sam->irq, NULL,
> + sam_gpio_irq_handler,
> + IRQF_ONESHOT,
> + dev_name(dev), sam);
> + if (ret)
> + goto out_remove_domain;
Are you not setting .can_sleep on the gpiochip?
> +
> + sam->gpio.to_irq = sam_gpio_to_irq;
> +
> + if (!try_module_get(dev->parent->driver->owner)) {
> + ret = -EINVAL;
> + goto out_remove_domain;
> + }
Why? Is that the MFD device? Isn't the device core
handling this?
> +static int sam_gpio_unexport(struct sam_gpio *sam)
> +{
> + int i;
> +
> + if (!sam->export_flags)
> + return 0;
> +
> + /* un-export all auto-exported pins */
> + for (i = 0; i < sam->gpio_count; i++) {
> + struct gpio_desc *desc = gpio_to_desc(sam->gpio.base + i);
> +
> + if (desc == NULL)
> + continue;
> +
> + if (sam->export_flags[i] & GPIOF_EXPORT)
> + gpiochip_free_own_desc(desc);
> + }
> + return 0;
> +}
> +
> +static int sam_gpio_export(struct sam_gpio *sam)
> +{
> + int i, ret;
> +
> + if (!sam->export_flags)
> + return 0;
> +
> + /* auto-export pins as requested */
> +
> + for (i = 0; i < sam->gpio_count; i++) {
> + u32 flags = sam->export_flags[i];
> + struct gpio_desc *desc;
> +
> + /* request and initialize exported pins */
> + if (!(flags & GPIOF_EXPORT))
> + continue;
> +
> + desc = gpiochip_request_own_desc(&sam->gpio, i, "sam-export");
> + if (IS_ERR(desc)) {
> + ret = PTR_ERR(desc);
> + goto error;
> + }
> + if (flags & GPIOF_DIR_IN) {
> + ret = gpiod_direction_input(desc);
> + if (ret)
> + goto error;
> + } else {
> + ret = gpiod_direction_output(desc, flags &
> + (GPIOF_OUT_INIT_HIGH |
> + GPIOF_ACTIVE_LOW));
> + if (ret)
> + goto error;
> + }
> + ret = gpiod_export(desc, flags & GPIOF_EXPORT_CHANGEABLE);
> +
> + if (ret)
> + goto error;
> + }
> + return 0;
> +
> +error:
> + sam_gpio_unexport(sam);
> + return ret;
> +}
Just delete this. Use the new chardev ABI to access GPIOs from
userspace as explained earlier.
> +static int sam_gpio_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct sam_gpio *sam;
> + struct resource *res;
> + int ret;
> + struct sam_platform_data *pdata = dev_get_platdata(&pdev->dev);
> +
> + sam = devm_kzalloc(dev, sizeof(*sam), GFP_KERNEL);
> + if (sam == NULL)
> + return -ENOMEM;
if (!sam)
return -ENOMEM;
> + sam->dev = dev;
> + sam->pdata = pdata;
> + platform_set_drvdata(pdev, sam);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res)
> + return -ENODEV;
> +
> + sam->irq = platform_get_irq(pdev, 0);
> + sam->irq_high = platform_get_irq(pdev, 1);
> +
> + sam->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
> + if (!sam->base)
> + return -ENOMEM;
> +
> + mutex_init(&sam->irq_lock);
> +
> + ret = sam_gpio_of_init(dev, sam);
> + if (ret)
> + return ret;
> +
> + sam_gpio_setup(sam);
> +
> + if (pdata && sam->irq >= 0 && of_find_property(dev->of_node,
> + "interrupt-controller", NULL)) {
> + ret = sam_gpio_irq_setup(dev, sam);
> + if (ret < 0)
> + return ret;
> + }
This is fair, but do it after adding the gpiochip and use GPIOLIB_IRQCHIP
accessors gpiochip_irqchip_add() and gpiochip_set_chained_irqchip().
Yours,
Linus Walleij