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, ®_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, + ®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, + 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, ®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, 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