@@ -115,4 +115,15 @@ config PCI_LABEL
def_bool y if (DMI || ACPI)
select NLS
+config PEX8XXX_I2C
+ tristate "PLX PEX8xxx Switch I2C interface driver"
+ depends on I2C
+ help
+ 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).
+
+ If built as a module, the driver will be called pex8xxx_i2c.
+
source "drivers/pci/host/Kconfig"
@@ -63,3 +63,5 @@ ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG
# PCI host controller drivers
obj-y += host/
+
+obj-$(CONFIG_PEX8XXX_I2C) += pex8xxx_i2c.o
new file mode 100644
@@ -0,0 +1,258 @@
+/*
+ * Driver for the PEX8xxx I2C slave interface
+ *
+ * Rajat Jain <rajatjain@juniper.net>
+ * 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/pci.h>
+#include <linux/i2c/pex8xxx_i2c.h>
+
+#define PCI_DEVICE_ID_PLX_8614 0x8614
+#define PCI_DEVICE_ID_PLX_8618 0x8618
+#define PCI_DEVICE_ID_PLX_8713 0x8713
+
+/* 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 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, byte_mask) \
+ (PEX8XXX_CMD(cmd) | \
+ PEX861X_PORT(port) | \
+ PEX8XXX_BYTE_ENA(byte_mask) | \
+ PEX8XXX_REG(reg))
+
+/* PEX8713 Device specific register defines */
+#define PEX8713_MODE(val) (((val) & 3) << 20)
+#define PEX8713_STN(val) (((val) & 3) << 18)
+#define PEX8713_PORT(val) (((val) & 7) << 15)
+
+#define PEX8713_I2C_CMD(cmd, port, mode, stn, reg, byte_mask) \
+ (PEX8XXX_CMD(cmd) | \
+ PEX8713_MODE(mode) | \
+ PEX8713_STN(stn) | \
+ PEX8713_PORT(port) | \
+ PEX8XXX_BYTE_ENA(byte_mask) | \
+ PEX8XXX_REG(reg))
+
+struct pex8xxx_dev {
+ enum chips devtype;
+};
+
+/**
+ * pex8xxx_read() - Read a (32 bit) register from the PEX8xxx device.
+ * @client: struct i2c_client*, representing the pex8xxx device.
+ * @stn: Station number (Used on some PLX switches such as PEX8713 that
+ * support multi stations. Ignored on switches that don't support
+ * it)
+ * @mode: Port mode (Transparent / Non-transparent etc)
+ * @byte_mask: Byte enable mask.
+ * @port: Port number
+ * @reg: Register offset to read.
+ * @val: Pointer where the result is to be written.
+ *
+ * Return: 0 on Success, Error value otherwise.
+ */
+int pex8xxx_read(struct i2c_client *client, u8 stn, u8 mode, u8 byte_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, byte_mask));
+ break;
+ case pex8713:
+ cmd = cpu_to_be32(PEX8713_I2C_CMD(PEX8XXX_CMD_RD, port, mode,
+ stn, reg, byte_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;
+}
+EXPORT_SYMBOL(pex8xxx_read);
+
+/**
+ * pex8xxx_write() - Write a (32 bit) register to the PEX8xxx device.
+ * @client: struct i2c_client*, representing the pex8xxx device.
+ * @stn: Station number (Used on some PLX switches such as PEX8713 that
+ * support multi stations. Ignored on switches that don't support
+ * it)
+ * @mode: Port mode (Transparent / Non-transparent etc)
+ * @byte_mask: Byte enable mask.
+ * @port: Port number
+ * @reg: Register offset to write.
+ * @val: Value to be written.
+ *
+ * Return: 0 on Success, Error value otherwise.
+ */
+int pex8xxx_write(struct i2c_client *client, u8 stn, u8 mode, u8 byte_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,
+ byte_mask));
+ break;
+ case pex8713:
+ msgbuf[0] = cpu_to_be32(PEX8713_I2C_CMD(PEX8XXX_CMD_WR, port,
+ mode, stn, reg,
+ byte_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;
+}
+EXPORT_SYMBOL(pex8xxx_write);
+
+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, MASK_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;
+ break;
+ case PCI_DEVICE_ID_PLX_8618:
+ pex8xxx->devtype = pex8618;
+ break;
+ case PCI_DEVICE_ID_PLX_8713:
+ pex8xxx->devtype = pex8713;
+ 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;
+
+ 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;
+
+ return 0;
+}
+
+static int pex8xxx_remove(struct i2c_client *client)
+{
+ 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@juniper.net>");
+MODULE_LICENSE("GPL v2");
new file mode 100644
@@ -0,0 +1,36 @@
+/*
+ * Interface for the PEX8xxx I2C slave interface
+ *
+ * Rajat Jain <rajatjain@juniper.net>
+ * 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.
+ */
+
+#ifndef __PEX8XXX_I2C_H__
+#define __PEX8XXX_I2C_H__
+
+#include <linux/i2c.h>
+
+/* Values for "mode" argument */
+#define MODE_TRANSPARENT 0x00
+#define MODE_NT_LINK 0x01
+#define MODE_NT_VIRT 0x02
+#define MODE_DMA 0x03
+
+/* Values for "byte_mask" argument */
+#define MASK_BYTE0 0x01
+#define MASK_BYTE1 0x02
+#define MASK_BYTE2 0x04
+#define MASK_BYTE3 0x08
+#define MASK_BYTE_ALL (MASK_BYTE0 | MASK_BYTE1 |\
+ MASK_BYTE2 | MASK_BYTE3)
+
+int pex8xxx_read(struct i2c_client *client, u8 stn, u8 mode, u8 byte_mask,
+ u8 port, u32 reg, u32 *val);
+int pex8xxx_write(struct i2c_client *client, u8 stn, u8 mode, u8 byte_mask,
+ u8 port, u32 reg, u32 val);
+
+#endif /* __PEX8XXX_I2C_H__ */