[PATCH 3/3] staging: jnx: pex8xxx I2C interface driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Rajat Jain <rajatjain@xxxxxxxxxxx>

Some Juniper platforms contain an Avago PEX8614, PEX8616 or PEX8713
PCI express switch which is controlled via a I2C interface.

This driver provides a sysfs interface for configuration from
user-space.

Signed-off-by: Guenter Roeck <groeck@xxxxxxxxxxx>
Signed-off-by: Rajat Jain <rajatjain@xxxxxxxxxxx>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@xxxxxxxxxxxx>
---
 drivers/staging/jnx/Kconfig       |   7 +
 drivers/staging/jnx/Makefile      |   1 +
 drivers/staging/jnx/pex8xxx_i2c.c | 509 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 517 insertions(+)
 create mode 100644 drivers/staging/jnx/pex8xxx_i2c.c

diff --git a/drivers/staging/jnx/Kconfig b/drivers/staging/jnx/Kconfig
index 507f10d..b57e93b 100644
--- a/drivers/staging/jnx/Kconfig
+++ b/drivers/staging/jnx/Kconfig
@@ -33,4 +33,11 @@ config JNX_CHIP_PCI_QUIRKS
 	---help---
 	  PCI quirks for the Juniper chips (ASICs, CPLDs, FPGAs)
 
+config JNX_PEX8XXX_I2C
+	tristate "PLX PEX8xxx switch I2C driver"
+	depends on I2C
+	---help---
+	  Driver for the I2C interface of PLX PEX8XXX devices
+	  (Currently supports PEX8614, PEX8618, PEX8713)
+
 endif # JNX_DEVICES
diff --git a/drivers/staging/jnx/Makefile b/drivers/staging/jnx/Makefile
index 0171476..90526b1 100644
--- a/drivers/staging/jnx/Makefile
+++ b/drivers/staging/jnx/Makefile
@@ -5,3 +5,4 @@
 obj-$(CONFIG_JNX_SYSTEM)	+= jnx-subsys.o jnx-board-core.o
 obj-$(CONFIG_JNX_CHIP_PCI_QUIRKS)+= jnx-chip-pci-quirks.o
 obj-$(CONFIG_JNX_COMMON_PCI)	+= jnx_common_pci.o
+obj-$(CONFIG_JNX_PEX8XXX_I2C)	+= pex8xxx_i2c.o
diff --git a/drivers/staging/jnx/pex8xxx_i2c.c b/drivers/staging/jnx/pex8xxx_i2c.c
new file mode 100644
index 0000000..2414121
--- /dev/null
+++ b/drivers/staging/jnx/pex8xxx_i2c.c
@@ -0,0 +1,509 @@
+/*
+ * An I2C driver for the PEX8xxx I2C slave interface
+ *
+ * Rajat Jain <rajatjain@xxxxxxxxxxx>
+ * Copyright 2014 Juniper Networks
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/pci.h>
+#include <linux/jnx/pci_ids.h>
+
+/* Supported devices */
+enum chips { pex8614, pex8618, pex8713 };
+
+#define MAXSTN			    2
+#define MAXMODE			    4
+
+/* Common Register defines */
+#define PEX8XXX_CMD(val)	    (((val) & 7) << 24)
+#define PEX8XXX_CMD_WR		    0x03
+#define PEX8XXX_CMD_RD		    0x04
+
+#define PEX8XXX_BYTE_ENA(val)	    (((val) & 0xF) << 10)
+#define BYTE0			    0x01
+#define BYTE1			    0x02
+#define BYTE2			    0x04
+#define BYTE3			    0x08
+#define BYTE_ALL		    0x0F
+
+#define PEX8XXX_REG(val)	    (((val) >> 2) & 0x3FF)
+
+/* PEX8614/8618 Device specific register defines */
+#define PEX861X_PORT(val)	    (((val) & 0x1F) << 15)
+
+#define PEX861X_I2C_CMD(cmd, port, mode, stn, reg, mask)	\
+	(PEX8XXX_CMD(cmd) |					\
+	 PEX861X_PORT(port) |					\
+	 PEX8XXX_BYTE_ENA(mask) |				\
+	 PEX8XXX_REG(reg))
+
+/* PEX8713 Device specific register defines */
+#define PEX8713_MODE(val)	    (((val) & 3) << 20)
+#define PEX8713_MODE_TRANSPARENT    0x00
+#define PEX8713_MODE_NT_LINK	    0x01
+#define PEX8713_MODE_NT_VIRT	    0x02
+#define PEX8713_MODE_DMA	    0x03
+
+#define PEX8713_STN(val)	    (((val) & 3) << 18)
+
+#define PEX8713_PORT(val)	    (((val) & 7) << 15)
+
+#define PEX8713_I2C_CMD(cmd, port, mode, stn, reg, mask)	\
+	(PEX8XXX_CMD(cmd) |					\
+	 PEX8713_MODE(mode) |					\
+	 PEX8713_STN(stn) |					\
+	 PEX8713_PORT(port) |					\
+	 PEX8XXX_BYTE_ENA(mask) |				\
+	 PEX8XXX_REG(reg))
+
+struct pex8xxx_dev {
+	enum chips	        devtype;
+	u32			reg_addr;
+	u8                      port_num;
+	u8                      port_mode;      /* PEX8713 only */
+	u8                      port_stn;       /* PEX8713 only */
+	struct attribute_group  attr_group;
+	bool (*port_is_valid)(u8 port);
+};
+
+static inline struct pex8xxx_dev *pex8xxx_get_drvdata(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	return i2c_get_clientdata(client);
+}
+
+static int pex8xxx_read(struct i2c_client *client, u8 stn, u8 mode, u8 mask,
+			u8 port, u32 reg, u32 *val)
+{
+	struct pex8xxx_dev *pex8xxx = i2c_get_clientdata(client);
+	__be32 cmd, data;
+	int ret;
+
+	struct i2c_msg msgs[2] = {
+		{
+			.addr = client->addr,
+			.len = 4,
+			.flags = 0,
+			.buf = (u8 *)&cmd,
+		},
+		{
+			.addr = client->addr,
+			.len = 4,
+			.flags = I2C_M_RD,
+			.buf = (u8 *)&data,
+		},
+	};
+
+	switch (pex8xxx->devtype) {
+	case pex8614:
+	case pex8618:
+		cmd = cpu_to_be32(PEX861X_I2C_CMD(PEX8XXX_CMD_RD, port, mode,
+						  stn, reg, mask));
+		break;
+	case pex8713:
+		cmd = cpu_to_be32(PEX8713_I2C_CMD(PEX8XXX_CMD_RD, port, mode,
+						  stn, reg, mask));
+		break;
+	default: /* Unknown device */
+		return -ENODEV;
+	}
+
+	ret = i2c_transfer(client->adapter, msgs, 2);
+	*val = be32_to_cpu(data);
+
+	if (ret < 0)
+		return ret;
+	else if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	return 0;
+}
+
+static int pex8xxx_write(struct i2c_client *client, u8 stn, u8 mode, u8 mask,
+			 u8 port, u32 reg, u32 val)
+{
+	struct pex8xxx_dev *pex8xxx = i2c_get_clientdata(client);
+	__be32 msgbuf[2];
+	int ret;
+
+	struct i2c_msg msg = {
+		.addr = client->addr,
+		.len = 8,
+		.flags = 0,
+		.buf = (u8 *)msgbuf,
+	};
+
+	switch (pex8xxx->devtype) {
+	case pex8614:
+	case pex8618:
+		msgbuf[0] = cpu_to_be32(PEX861X_I2C_CMD(PEX8XXX_CMD_WR, port,
+							mode, stn, reg, mask));
+		break;
+	case pex8713:
+		msgbuf[0] = cpu_to_be32(PEX8713_I2C_CMD(PEX8XXX_CMD_WR, port,
+							mode, stn, reg, mask));
+		break;
+	default: /* Unknown device */
+		return -ENODEV;
+	}
+	msgbuf[1] = cpu_to_be32(val);
+
+	ret = i2c_transfer(client->adapter, &msg, 1);
+
+	if (ret < 0)
+		return ret;
+	else if (ret != 1)
+		return -EIO;
+
+	return 0;
+}
+
+/*
+ * Different PCIe switch can have different port validators.
+ */
+static bool pex8618_port_is_valid(u8 port)
+{
+	return port <= 15;
+}
+
+static bool pex8713_port_is_valid(u8 port)
+{
+	return port <= 5 || (port >= 8 && port <= 13);
+}
+
+static bool pex8614_port_is_valid(u8 port)
+{
+	return port <= 2 ||
+	    (port >= 4 && port <= 10) ||
+	    port == 12 ||
+	    port == 14;
+}
+
+static ssize_t port_num_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct pex8xxx_dev *pex8xxx = pex8xxx_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", pex8xxx->port_num);
+}
+
+static ssize_t port_num_store(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct pex8xxx_dev *pex8xxx = pex8xxx_get_drvdata(dev);
+	u8 port_num;
+
+	if (kstrtou8(buf, 0, &port_num))
+		return -EINVAL;
+
+	if (!pex8xxx->port_is_valid(port_num))
+		return -EINVAL;
+
+	pex8xxx->port_num = port_num;
+	return count;
+}
+static DEVICE_ATTR_RW(port_num);
+
+static ssize_t port_mode_show(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	struct pex8xxx_dev *pex8xxx = pex8xxx_get_drvdata(dev);
+	char *str;
+
+	switch (pex8xxx->port_mode) {
+	case PEX8713_MODE_TRANSPARENT:
+		str = "transparent";
+		break;
+	case PEX8713_MODE_NT_LINK:
+		str = "nt-link";
+		break;
+	case PEX8713_MODE_NT_VIRT:
+		str = "nt-virtual";
+		break;
+	case PEX8713_MODE_DMA:
+		str = "dma";
+		break;
+	default:
+		str = "unknown";
+		break;
+	}
+	return sprintf(buf, "%s\n", str);
+}
+
+static ssize_t port_mode_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct pex8xxx_dev *pex8xxx = pex8xxx_get_drvdata(dev);
+
+	if (!strcmp(buf, "transparent\n"))
+		pex8xxx->port_mode = PEX8713_MODE_TRANSPARENT;
+	else if (!strcmp(buf, "nt-link\n"))
+		pex8xxx->port_mode = PEX8713_MODE_NT_LINK;
+	else if (!strcmp(buf, "nt-virtual\n"))
+		pex8xxx->port_mode = PEX8713_MODE_NT_VIRT;
+	else if (!strcmp(buf, "dma\n"))
+		pex8xxx->port_mode = PEX8713_MODE_DMA;
+	else
+		return -EINVAL;
+
+	return count;
+}
+static DEVICE_ATTR_RW(port_mode);
+
+static ssize_t port_stn_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct pex8xxx_dev *pex8xxx = pex8xxx_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n", pex8xxx->port_stn);
+}
+
+static ssize_t port_stn_store(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct pex8xxx_dev *pex8xxx = pex8xxx_get_drvdata(dev);
+	u8 stn;
+
+	if (kstrtou8(buf, 0, &stn) || (stn >= MAXSTN))
+		return -EINVAL;
+
+	pex8xxx->port_stn = stn;
+
+	return count;
+}
+static DEVICE_ATTR_RW(port_stn);
+
+static ssize_t reg_addr_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct pex8xxx_dev *pex8xxx = pex8xxx_get_drvdata(dev);
+
+	return sprintf(buf, "0x%X\n", pex8xxx->reg_addr);
+}
+
+static ssize_t reg_addr_store(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct pex8xxx_dev *pex8xxx = pex8xxx_get_drvdata(dev);
+	unsigned long reg_addr;
+
+	/* PEX8xxx devices support 4K memory per port */
+	if (kstrtoul(buf, 0, &reg_addr) || reg_addr >= 4096 || reg_addr % 4)
+		return -EINVAL;
+
+	pex8xxx->reg_addr = reg_addr;
+
+	return count;
+}
+static DEVICE_ATTR_RW(reg_addr);
+
+static ssize_t reg_value_show(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	struct pex8xxx_dev *pex8xxx;
+	struct i2c_client *client;
+	u32 regval = 0;
+	int ret;
+
+	client = to_i2c_client(dev);
+	pex8xxx = i2c_get_clientdata(client);
+
+	ret = pex8xxx_read(client, pex8xxx->port_stn, pex8xxx->port_mode,
+			   BYTE_ALL, pex8xxx->port_num, pex8xxx->reg_addr,
+			   &regval);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "0x%08X\n", regval);
+}
+
+static ssize_t reg_value_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct pex8xxx_dev *pex8xxx;
+	struct i2c_client *client;
+	unsigned long reg_val;
+	int retval;
+
+	client = to_i2c_client(dev);
+	pex8xxx = i2c_get_clientdata(client);
+
+	if (kstrtoul(buf, 0, &reg_val))
+		return -EINVAL;
+
+	retval = pex8xxx_write(client, pex8xxx->port_stn, pex8xxx->port_mode,
+			       BYTE_ALL, pex8xxx->port_num, pex8xxx->reg_addr,
+			       reg_val);
+	if (retval)
+		return retval;
+
+	return count;
+}
+static DEVICE_ATTR_RW(reg_value);
+
+static ssize_t
+port_config_regs_read_bin(struct file *filp, struct kobject *kobj,
+			  struct bin_attribute *bin_attr,
+			  char *buf, loff_t off, size_t count)
+{
+	unsigned int size = 4096;
+	loff_t init_off = off;
+	u32 *buf32 = (u32 *)buf;
+	u32 regval;
+	int ret;
+
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct i2c_client *client = to_i2c_client(dev);
+	struct pex8xxx_dev *pex8xxx = i2c_get_clientdata(client);
+
+	if (off > size || off & 3)
+		return 0;
+	if (off + count > size) {
+		size -= off;
+		count = size;
+	} else {
+		size = count;
+	}
+
+	while (size) {
+		ret = pex8xxx_read(client, pex8xxx->port_stn,
+				   pex8xxx->port_mode, BYTE_ALL,
+				   pex8xxx->port_num, off, &regval);
+		if (ret)
+			regval = 0xDEADBEEF;
+
+		buf32[(off - init_off) / 4] = regval;
+		off += 4;
+		size -= 4;
+	}
+
+	return count;
+}
+static BIN_ATTR(port_config_regs, S_IRUGO, port_config_regs_read_bin,
+		NULL, 4096);
+
+static struct attribute *pex861x_attrs[] = {
+	&dev_attr_port_num.attr,
+	&dev_attr_reg_addr.attr,
+	&dev_attr_reg_value.attr,
+	NULL,
+};
+
+static struct attribute *pex8713_attrs[] = {
+	&dev_attr_port_num.attr,
+	&dev_attr_port_mode.attr,
+	&dev_attr_port_stn.attr,
+	&dev_attr_reg_addr.attr,
+	&dev_attr_reg_value.attr,
+	NULL,
+};
+
+static struct bin_attribute *pex8xxx_bin_attrs[] = {
+	&bin_attr_port_config_regs,
+	NULL,
+};
+
+static int pex8xxx_verify_device(struct pex8xxx_dev *pex8xxx,
+				 struct i2c_client *client)
+{
+	u8 stn, mode;
+	bool found = false;
+	u32 data = 0;
+
+	for (stn = 0; stn < MAXSTN; stn++) {
+		for (mode = 0; mode < MAXMODE; mode++) {
+			if (!pex8xxx_read(client, stn, mode, BYTE_ALL, 0,
+					  PCI_VENDOR_ID, &data)) {
+				found = true;
+				break;
+			}
+		}
+	}
+
+	if (!found || (data & 0xFFFF) != PCI_VENDOR_ID_PLX)
+		return -ENODEV;
+
+	switch (data >> 16) {
+	case PCI_DEVICE_ID_PLX_8614:
+		pex8xxx->devtype = pex8614;
+		pex8xxx->port_is_valid = pex8614_port_is_valid;
+		pex8xxx->attr_group.attrs = pex861x_attrs;
+		break;
+	case PCI_DEVICE_ID_PLX_8618:
+		pex8xxx->devtype = pex8618;
+		pex8xxx->port_is_valid = pex8618_port_is_valid;
+		pex8xxx->attr_group.attrs = pex861x_attrs;
+		break;
+	case PCI_DEVICE_ID_PLX_8713:
+		pex8xxx->devtype = pex8713;
+		pex8xxx->port_is_valid = pex8713_port_is_valid;
+		pex8xxx->attr_group.attrs = pex8713_attrs;
+		break;
+	default:    /* Unsupported PLX device */
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int pex8xxx_probe(struct i2c_client *client,
+			 const struct i2c_device_id *dev_id)
+{
+	struct pex8xxx_dev *pex8xxx;
+	int retval;
+
+	pex8xxx = devm_kzalloc(&client->dev, sizeof(*pex8xxx), GFP_KERNEL);
+	if (!pex8xxx)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, pex8xxx);
+
+	if (pex8xxx_verify_device(pex8xxx, client))
+		return -ENODEV;
+
+	pex8xxx->attr_group.bin_attrs = pex8xxx_bin_attrs;
+
+	retval =  sysfs_create_group(&client->dev.kobj, &pex8xxx->attr_group);
+
+	return retval;
+}
+
+static int pex8xxx_remove(struct i2c_client *client)
+{
+	struct pex8xxx_dev *pex8xxx = i2c_get_clientdata(client);
+
+	sysfs_remove_group(&client->dev.kobj, &pex8xxx->attr_group);
+	return 0;
+}
+
+static const struct i2c_device_id pex8xxx_id[] = {
+	{ "pex8614", pex8614 },
+	{ "pex8618", pex8618 },
+	{ "pex8713", pex8713 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, pex8xxx_id);
+
+static struct i2c_driver pex8xxx_driver = {
+	.driver = {
+		.name	= "pex8xxx",
+	},
+	.probe	= pex8xxx_probe,
+	.remove	= pex8xxx_remove,
+	.id_table = pex8xxx_id,
+};
+
+module_i2c_driver(pex8xxx_driver);
+
+MODULE_DESCRIPTION("PLX PEX8xxx switch I2C interface driver");
+MODULE_AUTHOR("Rajat Jain <rajatjain@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
-- 
1.9.1

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



[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux