* [PATCH 2/4] pci/pex8xxx: Add sysfs interface for userspace access.
@ 2014-09-30 4:47 Rajat Jain
0 siblings, 0 replies; only message in thread
From: Rajat Jain @ 2014-09-30 4:47 UTC (permalink / raw)
To: Bjorn Helgaas, Jiri Kosina, Andrew Morton, David S. Miller,
Greg Kroah-Hartman, linux-pci, linux-kernel, linux-i2c, linux-doc
Cc: rajatjain, groeck
Add the sysfs ABI for the I2C interface the PLX pex8xxx PCIe switch.
The ABI is documented, and a patch to "Documentation" accompanies this
patch.
Signed-off-by: Rajat Jain <rajatxjain@gmail.com>
Signed-off-by: Rajat Jain <rajatjain@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
---
drivers/pci/Kconfig | 4 +-
drivers/pci/pex8xxx_i2c.c | 282 ++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 284 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 29233aa..6a2b7dd 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -122,7 +122,9 @@ config PEX8XXX_I2C
Select this if you want I2C interface support for the PLX PEX8xxx
family of PCI express switches. Currently this I2C driver supports
PEX8614, PEX8618, PEX8713 switches. It provides read / write API
- calls to talk to the switch (over I2C).
+ calls to talk to the switch (over I2C), and a sysfs interface to
+ provide the ability to debug. This is documented in
+ Documentation/PCI/pex8xxx_i2c.txt.
If built as a module, the driver will be called pex8xxx_i2c.
diff --git a/drivers/pci/pex8xxx_i2c.c b/drivers/pci/pex8xxx_i2c.c
index ab59417..e38998e 100644
--- a/drivers/pci/pex8xxx_i2c.c
+++ b/drivers/pci/pex8xxx_i2c.c
@@ -17,6 +17,8 @@
#define PCI_DEVICE_ID_PLX_8618 0x8618
#define PCI_DEVICE_ID_PLX_8713 0x8713
+#define PEX8XXX_PORT_REG_SPACE 4096
+
/* Supported devices */
enum chips { pex8614, pex8618, pex8713 };
@@ -55,8 +57,21 @@ enum chips { pex8614, pex8618, pex8713 };
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);
+}
+
/**
* pex8xxx_read() - Read a (32 bit) register from the PEX8xxx device.
* @client: struct i2c_client*, representing the pex8xxx device.
@@ -175,6 +190,257 @@ int pex8xxx_write(struct i2c_client *client, u8 stn, u8 mode, u8 byte_mask,
}
EXPORT_SYMBOL(pex8xxx_write);
+/*
+ * Different PCIe switch can have different port validators.
+ * Also, some switches have discontinuous port number configurations.
+ */
+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 MODE_TRANSPARENT:
+ str = "transparent";
+ break;
+ case MODE_NT_LINK:
+ str = "nt-link";
+ break;
+ case MODE_NT_VIRT:
+ str = "nt-virtual";
+ break;
+ case 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 = MODE_TRANSPARENT;
+ else if (!strcmp(buf, "nt-link\n"))
+ pex8xxx->port_mode = MODE_NT_LINK;
+ else if (!strcmp(buf, "nt-virtual\n"))
+ pex8xxx->port_mode = MODE_NT_VIRT;
+ else if (!strcmp(buf, "dma\n"))
+ pex8xxx->port_mode = 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, ®_addr) ||
+ reg_addr >= PEX8XXX_PORT_REG_SPACE ||
+ 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,
+ MASK_BYTE_ALL, pex8xxx->port_num, pex8xxx->reg_addr,
+ ®val);
+ 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, ®_val))
+ return -EINVAL;
+
+ retval = pex8xxx_write(client, pex8xxx->port_stn, pex8xxx->port_mode,
+ MASK_BYTE_ALL, pex8xxx->port_num,
+ pex8xxx->reg_addr, reg_val);
+ if (retval)
+ return retval;
+
+ return count;
+}
+static DEVICE_ATTR_RW(reg_value);
+
+/*
+ * Dump the 4096 byte binary configuration space
+ */
+static ssize_t
+pex8xxx_read_full_config(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ unsigned int size = PEX8XXX_PORT_REG_SPACE;
+ struct pex8xxx_dev *pex8xxx;
+ struct i2c_client *client;
+ struct device *dev;
+ loff_t init_off = off;
+ u32 *buf32 = (u32 *)buf;
+ u32 regval;
+ int ret;
+
+ dev = container_of(kobj, struct device, kobj);
+ client = to_i2c_client(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, MASK_BYTE_ALL,
+ pex8xxx->port_num, off, ®val);
+ if (ret)
+ regval = 0xDEADBEEF;
+
+ buf32[(off - init_off)/4] = regval;
+ off += 4;
+ size -= 4;
+ }
+
+ return count;
+}
+static BIN_ATTR(port_config_regs, S_IRUGO, pex8xxx_read_full_config,
+ NULL, PEX8XXX_PORT_REG_SPACE);
+
+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)
{
@@ -198,12 +464,18 @@ static int pex8xxx_verify_device(struct pex8xxx_dev *pex8xxx,
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;
@@ -216,6 +488,7 @@ 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)
@@ -226,11 +499,18 @@ static int pex8xxx_probe(struct i2c_client *client,
if (pex8xxx_verify_device(pex8xxx, client))
return -ENODEV;
- return 0;
+ 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;
}
--
1.7.9.5
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2014-09-30 4:47 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-09-30 4:47 [PATCH 2/4] pci/pex8xxx: Add sysfs interface for userspace access Rajat Jain
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.