diff options
11 files changed, 3676 insertions, 0 deletions
diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc.inc b/meta-phosphor/common/recipes-kernel/linux/linux-obmc.inc index 78da46967..92aaef2c6 100644 --- a/meta-phosphor/common/recipes-kernel/linux/linux-obmc.inc +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc.inc @@ -10,6 +10,16 @@ KSRC ?= "git://github.com/openbmc/linux;protocol=git;branch=${KBRANCH}" SRC_URI = "${KSRC}" SRC_URI += "file://phosphor-gpio-keys.scc" SRC_URI += "file://phosphor-gpio-keys.cfg" +SRC_URI += "file://0001-fsi-Match-fsi-slaves-engines-to-available-device-tre.patch" +SRC_URI += "file://linux-dev-4.10-v2-1-6-drivers-i2c-Add-FSI-attached-I2C-master-algorithm.patch" +SRC_URI += "file://linux-dev-4.10-v2-2-6-drivers-i2c-Add-port-structure-to-FSI-algorithm.patch" +SRC_URI += "file://linux-dev-4.10-v2-3-6-drivers-i2c-Add-transfer-implementation-for-FSI-algorithm.patch" +SRC_URI += "file://linux-dev-4.10-v2-4-6-drivers-i2c-Add-I2C-master-locking-to-FSI-algorithm.patch" +SRC_URI += "file://linux-dev-4.10-v2-5-6-drivers-i2c-Add-bus-recovery-for-FSI-algorithm.patch" +SRC_URI += "file://linux-dev-4.10-drivers-fsi-Add-FSI-SBEFIFO-driver.patch" +SRC_URI += "file://linux-dev-4.10-1-3-drivers-fsi-sbefifo-Add-in-kernel-API.patch" +SRC_URI += "file://linux-dev-4.10-2-3-drivers-fsi-sbefifo-Add-OCC-driver.patch" +SRC_URI += "file://0001-arm-dts-aspeed-Add-FSI-devices.patch" LINUX_VERSION_EXTENSION ?= "-${SRCREV}" diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/0001-arm-dts-aspeed-Add-FSI-devices.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/0001-arm-dts-aspeed-Add-FSI-devices.patch new file mode 100644 index 000000000..3b49685c8 --- /dev/null +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/0001-arm-dts-aspeed-Add-FSI-devices.patch @@ -0,0 +1,507 @@ +From e45cd4677a36eb759537f59c5f5bcf46b91728b3 Mon Sep 17 00:00:00 2001 +From: "Edward A. James" <eajames@us.ibm.com> +Date: Mon, 15 May 2017 11:02:48 -0500 +Subject: [PATCH linux dev-4.10] arm: dts: aspeed: Add FSI devices + +using Jeremy's new bindings + +Signed-off-by: Edward A. James <eajames@us.ibm.com> +--- + arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts | 75 ++++++++++ + arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts | 181 +++++++++++++++++++++++ + arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts | 181 +++++++++++++++++++++++ + 3 files changed, 437 insertions(+) + +diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts +index b4faa1d..6f23ce3 100644 +--- a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts ++++ b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts +@@ -85,12 +85,87 @@ + + gpio-fsi { + compatible = "fsi-master-gpio", "fsi-master"; ++ #address-cells = <2>; ++ #size-cells = <0>; + + clock-gpios = <&gpio ASPEED_GPIO(A, 4) GPIO_ACTIVE_HIGH>; + data-gpios = <&gpio ASPEED_GPIO(A, 5) GPIO_ACTIVE_HIGH>; + mux-gpios = <&gpio ASPEED_GPIO(A, 6) GPIO_ACTIVE_HIGH>; + enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>; + trans-gpios = <&gpio ASPEED_GPIO(H, 6) GPIO_ACTIVE_HIGH>; ++ ++ cfam@0,0 { ++ reg = <0 0>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ i2c-master@1800 { ++ compatible = "ibm,fsi-i2c-master"; ++ reg = <0x1800 0x400>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ port = <0>; ++ }; ++ ++ port@1 { ++ port = <1>; ++ }; ++ ++ port@2 { ++ port = <2>; ++ }; ++ ++ port@3 { ++ port = <3>; ++ }; ++ ++ port@4 { ++ port = <4>; ++ }; ++ ++ port@5 { ++ port = <5>; ++ }; ++ ++ port@6 { ++ port = <6>; ++ }; ++ ++ port@7 { ++ port = <7>; ++ }; ++ ++ port@8 { ++ port = <8>; ++ }; ++ ++ port@9 { ++ port = <9>; ++ }; ++ ++ port@10 { ++ port = <10>; ++ }; ++ ++ port@11 { ++ port = <11>; ++ }; ++ ++ port@12 { ++ port = <12>; ++ }; ++ ++ port@13 { ++ port = <13>; ++ }; ++ ++ port@14 { ++ port = <14>; ++ }; ++ }; ++ }; + }; + }; + +diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts b/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts +index f20aaf4..5d83a76 100644 +--- a/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts ++++ b/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts +@@ -65,6 +65,8 @@ + + gpio-fsi { + compatible = "fsi-master-gpio", "fsi-master"; ++ #address-cells = <2>; ++ #size-cells = <0>; + + status = "okay"; + +@@ -73,6 +75,185 @@ + mux-gpios = <&gpio ASPEED_GPIO(A, 6) GPIO_ACTIVE_HIGH>; + enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>; + trans-gpios = <&gpio ASPEED_GPIO(R, 2) GPIO_ACTIVE_HIGH>; ++ ++ cfam@0,0 { ++ reg = <0 0>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ i2c-master@1800 { ++ compatible = "ibm,fsi-i2c-master"; ++ reg = <0x1800 0x400>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ port = <0>; ++ }; ++ ++ port@1 { ++ port = <1>; ++ }; ++ ++ port@2 { ++ port = <2>; ++ }; ++ ++ port@3 { ++ port = <3>; ++ }; ++ ++ port@4 { ++ port = <4>; ++ }; ++ ++ port@5 { ++ port = <5>; ++ }; ++ ++ port@6 { ++ port = <6>; ++ }; ++ ++ port@7 { ++ port = <7>; ++ }; ++ ++ port@8 { ++ port = <8>; ++ }; ++ ++ port@9 { ++ port = <9>; ++ }; ++ ++ port@10 { ++ port = <10>; ++ }; ++ ++ port@11 { ++ port = <11>; ++ }; ++ ++ port@12 { ++ port = <12>; ++ }; ++ ++ port@13 { ++ port = <13>; ++ }; ++ ++ port@14 { ++ port = <14>; ++ }; ++ }; ++ ++ sbefifo@2400 { ++ compatible = "ibm,p9-sbefifo"; ++ reg = <0x2400 0x400>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ occ@1 { ++ compatible = "ibm,p9-occ"; ++ reg = <1>; ++ }; ++ }; ++ ++ hub@3400 { ++ compatible = "fsi-master-hub"; ++ reg = <0x3400 0x400>; ++ #address-cells = <2>; ++ #size-cells = <0>; ++ ++ cfam@1,0 { ++ reg = <1 0>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ i2c-master@1800 { ++ compatible = ++ "ibm,fsi-i2c-master"; ++ reg = <0x1800 0x400>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ port = <0>; ++ }; ++ ++ port@1 { ++ port = <1>; ++ }; ++ ++ port@2 { ++ port = <2>; ++ }; ++ ++ port@3 { ++ port = <3>; ++ }; ++ ++ port@4 { ++ port = <4>; ++ }; ++ ++ port@5 { ++ port = <5>; ++ }; ++ ++ port@6 { ++ port = <6>; ++ }; ++ ++ port@7 { ++ port = <7>; ++ }; ++ ++ port@8 { ++ port = <8>; ++ }; ++ ++ port@9 { ++ port = <9>; ++ }; ++ ++ port@10 { ++ port = <10>; ++ }; ++ ++ port@11 { ++ port = <11>; ++ }; ++ ++ port@12 { ++ port = <12>; ++ }; ++ ++ port@13 { ++ port = <13>; ++ }; ++ ++ port@14 { ++ port = <14>; ++ }; ++ }; ++ ++ sbefifo@2400 { ++ compatible = "ibm,p9-sbefifo"; ++ reg = <0x2400 0x400>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ occ@2 { ++ compatible = ++ "ibm,p9-occ"; ++ reg = <2>; ++ }; ++ }; ++ }; ++ }; ++ }; + }; + + iio-hwmon { +diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts b/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts +index b5c4c0f..6f36e56 100644 +--- a/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts ++++ b/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts +@@ -77,6 +77,8 @@ + + gpio-fsi { + compatible = "fsi-master-gpio", "fsi-master"; ++ #address-cells = <2>; ++ #size-cells = <0>; + + status = "okay"; + +@@ -85,6 +87,185 @@ + trans-gpios = <&gpio ASPEED_GPIO(O, 6) GPIO_ACTIVE_HIGH>; + enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>; + mux-gpios = <&gpio ASPEED_GPIO(P, 6) GPIO_ACTIVE_HIGH>; ++ ++ cfam@0,0 { ++ reg = <0 0>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ i2c-master@1800 { ++ compatible = "ibm,fsi-i2c-master"; ++ reg = <0x1800 0x400>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ port = <0>; ++ }; ++ ++ port@1 { ++ port = <1>; ++ }; ++ ++ port@2 { ++ port = <2>; ++ }; ++ ++ port@3 { ++ port = <3>; ++ }; ++ ++ port@4 { ++ port = <4>; ++ }; ++ ++ port@5 { ++ port = <5>; ++ }; ++ ++ port@6 { ++ port = <6>; ++ }; ++ ++ port@7 { ++ port = <7>; ++ }; ++ ++ port@8 { ++ port = <8>; ++ }; ++ ++ port@9 { ++ port = <9>; ++ }; ++ ++ port@10 { ++ port = <10>; ++ }; ++ ++ port@11 { ++ port = <11>; ++ }; ++ ++ port@12 { ++ port = <12>; ++ }; ++ ++ port@13 { ++ port = <13>; ++ }; ++ ++ port@14 { ++ port = <14>; ++ }; ++ }; ++ ++ sbefifo@2400 { ++ compatible = "ibm,p9-sbefifo"; ++ reg = <0x2400 0x400>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ occ@1 { ++ compatible = "ibm,p9-occ"; ++ reg = <1>; ++ }; ++ }; ++ ++ hub@3400 { ++ compatible = "fsi-master-hub"; ++ reg = <0x3400 0x400>; ++ #address-cells = <2>; ++ #size-cells = <0>; ++ ++ cfam@1,0 { ++ reg = <1 0>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ i2c-master@1800 { ++ compatible = ++ "ibm,fsi-i2c-master"; ++ reg = <0x1800 0x400>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ port = <0>; ++ }; ++ ++ port@1 { ++ port = <1>; ++ }; ++ ++ port@2 { ++ port = <2>; ++ }; ++ ++ port@3 { ++ port = <3>; ++ }; ++ ++ port@4 { ++ port = <4>; ++ }; ++ ++ port@5 { ++ port = <5>; ++ }; ++ ++ port@6 { ++ port = <6>; ++ }; ++ ++ port@7 { ++ port = <7>; ++ }; ++ ++ port@8 { ++ port = <8>; ++ }; ++ ++ port@9 { ++ port = <9>; ++ }; ++ ++ port@10 { ++ port = <10>; ++ }; ++ ++ port@11 { ++ port = <11>; ++ }; ++ ++ port@12 { ++ port = <12>; ++ }; ++ ++ port@13 { ++ port = <13>; ++ }; ++ ++ port@14 { ++ port = <14>; ++ }; ++ }; ++ ++ sbefifo@2400 { ++ compatible = "ibm,p9-sbefifo"; ++ reg = <0x2400 0x400>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ occ@2 { ++ compatible = ++ "ibm,p9-occ"; ++ reg = <2>; ++ }; ++ }; ++ }; ++ }; ++ }; + }; + + iio-hwmon { +-- +1.8.3.1 + diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/0001-fsi-Match-fsi-slaves-engines-to-available-device-tre.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/0001-fsi-Match-fsi-slaves-engines-to-available-device-tre.patch new file mode 100644 index 000000000..6fdfe93cd --- /dev/null +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/0001-fsi-Match-fsi-slaves-engines-to-available-device-tre.patch @@ -0,0 +1,236 @@ +From 8f10daa0cc99ce286b9a2f249f1fa0a245d4fc03 Mon Sep 17 00:00:00 2001 +From: "Edward A. James" <eajames@us.ibm.com> +Date: Wed, 17 May 2017 09:46:19 -0500 +Subject: [PATCH] fsi: Match fsi slaves & engines to available device tree + nodes + +This change populates device tree nodes for scanned FSI slaves and +engines. If the master populates ->of_node of the FSI master device, +we'll look for matching slaves, and under those slaves we'll look for +matching engines. + +This means that FSI drivers will have their ->of_node pointer populated +if there's a corresponding DT node, which they can use for further +device discover. + +Presence of device tree nodes is optional, and only required for +fsi device drivers that need extra properties, or subordinate devices, +to be enumerated. + +Signed-off-by: Jeremy Kerr <jk@ozlabs.org> +Signed-off-by: Edward A. James <eajames@us.ibm.com> +--- + drivers/fsi/fsi-core.c | 99 +++++++++++++++++++++++++++++++++++++++++++ + drivers/fsi/fsi-master-gpio.c | 4 ++ + drivers/fsi/fsi-master-hub.c | 4 ++ + 3 files changed, 107 insertions(+) + +diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c +index 7ca5b74..71e3af9 100644 +--- a/drivers/fsi/fsi-core.c ++++ b/drivers/fsi/fsi-core.c +@@ -17,6 +17,7 @@ + #include <linux/fsi.h> + #include <linux/idr.h> + #include <linux/module.h> ++#include <linux/of.h> + #include <linux/slab.h> + + #include "fsi-master.h" +@@ -125,6 +126,7 @@ static void fsi_device_release(struct device *_device) + { + struct fsi_device *device = to_fsi_dev(_device); + ++ of_node_put(device->dev.of_node); + kfree(device); + } + +@@ -337,6 +339,57 @@ extern void fsi_slave_release_range(struct fsi_slave *slave, + { + } + ++static bool fsi_device_node_matches(struct device *dev, struct device_node *np, ++ uint32_t addr, uint32_t size) ++{ ++ unsigned int len, na, ns; ++ const __be32 *prop; ++ uint32_t psize; ++ ++ na = of_n_addr_cells(np); ++ ns = of_n_size_cells(np); ++ ++ if (na != 1 || ns != 1) ++ return false; ++ ++ prop = of_get_property(np, "reg", &len); ++ if (!prop || len != 8) ++ return false; ++ ++ if (of_read_number(prop, 1) != addr) ++ return false; ++ ++ psize = of_read_number(prop + 1, 1); ++ if (psize != size) { ++ dev_warn(dev, ++ "node %s matches probed address, but not size (got 0x%x, expected 0x%x)", ++ of_node_full_name(np), psize, size); ++ } ++ ++ return true; ++} ++ ++/* Find a matching node for the slave engine at @address, using @size bytes ++ * of space. Returns NULL if not found, or a matching node with refcount ++ * already incremented. ++ */ ++static struct device_node *fsi_device_find_of_node(struct fsi_device *dev) ++{ ++ struct device_node *parent, *np; ++ ++ parent = dev_of_node(&dev->slave->dev); ++ if (!parent) ++ return NULL; ++ ++ for_each_child_of_node(parent, np) { ++ if (fsi_device_node_matches(&dev->dev, np, ++ dev->addr, dev->size)) ++ return np; ++ } ++ ++ return NULL; ++} ++ + static int fsi_slave_scan(struct fsi_slave *slave) + { + uint32_t engine_addr; +@@ -405,6 +458,7 @@ static int fsi_slave_scan(struct fsi_slave *slave) + dev_set_name(&dev->dev, "%02x:%02x:%02x:%02x", + slave->master->idx, slave->link, + slave->id, i - 2); ++ dev->dev.of_node = fsi_device_find_of_node(dev); + + rc = device_register(&dev->dev); + if (rc) { +@@ -561,9 +615,53 @@ static void fsi_slave_release(struct device *dev) + { + struct fsi_slave *slave = to_fsi_slave(dev); + ++ of_node_put(dev->of_node); + kfree(slave); + } + ++static bool fsi_slave_node_matches(struct device_node *np, ++ int link, uint8_t id) ++{ ++ unsigned int len, na, ns; ++ const __be32 *prop; ++ ++ na = of_n_addr_cells(np); ++ ns = of_n_size_cells(np); ++ ++ /* Ensure we have the correct format for addresses and sizes in ++ * reg properties ++ */ ++ if (na != 2 || ns != 0) ++ return false; ++ ++ prop = of_get_property(np, "reg", &len); ++ if (!prop || len != 8) ++ return false; ++ ++ return (of_read_number(prop, 1) == link) && ++ (of_read_number(prop + 1, 1) == id); ++} ++ ++/* Find a matching node for the slave at (link, id). Returns NULL if none ++ * found, or a matching node with refcount already incremented. ++ */ ++static struct device_node *fsi_slave_find_of_node(struct fsi_master *master, ++ int link, uint8_t id) ++{ ++ struct device_node *parent, *np; ++ ++ parent = dev_of_node(&master->dev); ++ if (!parent) ++ return NULL; ++ ++ for_each_child_of_node(parent, np) { ++ if (fsi_slave_node_matches(np, link, id)) ++ return np; ++ } ++ ++ return NULL; ++} ++ + static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) + { + uint32_t chip_id, llmode; +@@ -626,6 +724,7 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) + + slave->master = master; + slave->dev.parent = &master->dev; ++ slave->dev.of_node = fsi_slave_find_of_node(master, link, id); + slave->dev.release = fsi_slave_release; + slave->link = link; + slave->id = id; +diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c +index ef209ef..2a6a812 100644 +--- a/drivers/fsi/fsi-master-gpio.c ++++ b/drivers/fsi/fsi-master-gpio.c +@@ -5,6 +5,7 @@ + #include <linux/platform_device.h> + #include <linux/gpio/consumer.h> + #include <linux/module.h> ++#include <linux/of.h> + #include <linux/delay.h> + #include <linux/fsi.h> + #include <linux/device.h> +@@ -529,6 +530,7 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) + + master->dev = &pdev->dev; + master->master.dev.parent = master->dev; ++ master->master.dev.of_node = of_node_get(dev_of_node(master->dev)); + master->master.dev.release = fsi_master_gpio_release; + + gpio = devm_gpiod_get(&pdev->dev, "clock", 0); +@@ -598,6 +600,8 @@ static int fsi_master_gpio_remove(struct platform_device *pdev) + devm_gpiod_put(&pdev->dev, master->gpio_mux); + fsi_master_unregister(&master->master); + ++ of_node_put(master->master.dev.of_node); ++ + return 0; + } + +diff --git a/drivers/fsi/fsi-master-hub.c b/drivers/fsi/fsi-master-hub.c +index 133b9bf..3223a67 100644 +--- a/drivers/fsi/fsi-master-hub.c ++++ b/drivers/fsi/fsi-master-hub.c +@@ -16,6 +16,7 @@ + #include <linux/delay.h> + #include <linux/fsi.h> + #include <linux/module.h> ++#include <linux/of.h> + #include <linux/slab.h> + + #include "fsi-master.h" +@@ -274,6 +275,7 @@ static int hub_master_probe(struct device *dev) + + hub->master.dev.parent = dev; + hub->master.dev.release = hub_master_release; ++ hub->master.dev.of_node = of_node_get(dev_of_node(dev)); + + hub->master.n_links = links; + hub->master.read = hub_master_read; +@@ -302,6 +304,8 @@ static int hub_master_remove(struct device *dev) + + fsi_master_unregister(&hub->master); + fsi_slave_release_range(hub->upstream->slave, hub->addr, hub->size); ++ of_node_put(hub->master.dev.of_node); ++ + return 0; + } + +-- +1.8.3.1 + diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-1-3-drivers-fsi-sbefifo-Add-in-kernel-API.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-1-3-drivers-fsi-sbefifo-Add-in-kernel-API.patch new file mode 100644 index 000000000..70a11f265 --- /dev/null +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-1-3-drivers-fsi-sbefifo-Add-in-kernel-API.patch @@ -0,0 +1,338 @@ +From patchwork Fri May 12 19:38:18 2017 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [linux,dev-4.10,1/3] drivers: fsi: sbefifo: Add in-kernel API +From: eajames@linux.vnet.ibm.com +X-Patchwork-Id: 761836 +Message-Id: <1494617900-32369-2-git-send-email-eajames@linux.vnet.ibm.com> +To: openbmc@lists.ozlabs.org +Cc: "Edward A. James" <eajames@us.ibm.com>, bradleyb@fuzziesquirrel.com, + cbostic@linux.vnet.ibm.com +Date: Fri, 12 May 2017 14:38:18 -0500 + +From: "Edward A. James" <eajames@us.ibm.com> + +Add exported functions to the SBEFIFO driver to open/write/read/close +from within the kernel. + +Signed-off-by: Edward A. James <eajames@us.ibm.com> +--- + drivers/fsi/fsi-sbefifo.c | 161 +++++++++++++++++++++++++++++++++++--------- + include/linux/fsi-sbefifo.h | 30 +++++++++ + 2 files changed, 161 insertions(+), 30 deletions(-) + create mode 100644 include/linux/fsi-sbefifo.h + +diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c +index b49aec2..56e6331 100644 +--- a/drivers/fsi/fsi-sbefifo.c ++++ b/drivers/fsi/fsi-sbefifo.c +@@ -15,9 +15,12 @@ + #include <linux/errno.h> + #include <linux/idr.h> + #include <linux/fsi.h> ++#include <linux/fsi-sbefifo.h> + #include <linux/list.h> + #include <linux/miscdevice.h> + #include <linux/module.h> ++#include <linux/of.h> ++#include <linux/of_platform.h> + #include <linux/poll.h> + #include <linux/sched.h> + #include <linux/slab.h> +@@ -82,6 +85,7 @@ struct sbefifo_client { + struct list_head xfrs; + struct sbefifo *dev; + struct kref kref; ++ unsigned long f_flags; + }; + + static struct list_head sbefifo_fifos; +@@ -506,6 +510,7 @@ static int sbefifo_open(struct inode *inode, struct file *file) + return -ENOMEM; + + file->private_data = client; ++ client->f_flags = file->f_flags; + + return 0; + } +@@ -530,24 +535,18 @@ static unsigned int sbefifo_poll(struct file *file, poll_table *wait) + return mask; + } + +-static ssize_t sbefifo_read(struct file *file, char __user *buf, +- size_t len, loff_t *offset) ++static ssize_t sbefifo_read_common(struct sbefifo_client *client, ++ char __user *ubuf, char *kbuf, size_t len) + { +- struct sbefifo_client *client = file->private_data; + struct sbefifo *sbefifo = client->dev; + struct sbefifo_xfr *xfr; +- ssize_t ret = 0; + size_t n; +- +- WARN_ON(*offset); +- +- if (!access_ok(VERIFY_WRITE, buf, len)) +- return -EFAULT; ++ ssize_t ret = 0; + + if ((len >> 2) << 2 != len) + return -EINVAL; + +- if ((file->f_flags & O_NONBLOCK) && !sbefifo_xfr_rsp_pending(client)) ++ if ((client->f_flags & O_NONBLOCK) && !sbefifo_xfr_rsp_pending(client)) + return -EAGAIN; + + sbefifo_get_client(client); +@@ -566,10 +565,13 @@ static ssize_t sbefifo_read(struct file *file, char __user *buf, + + n = min_t(size_t, n, len); + +- if (copy_to_user(buf, READ_ONCE(client->rbuf.rpos), n)) { +- sbefifo_put_client(client); +- return -EFAULT; +- } ++ if (ubuf) { ++ if (copy_to_user(ubuf, READ_ONCE(client->rbuf.rpos), n)) { ++ sbefifo_put_client(client); ++ return -EFAULT; ++ } ++ } else ++ memcpy(kbuf, READ_ONCE(client->rbuf.rpos), n); + + if (sbefifo_buf_readnb(&client->rbuf, n)) { + xfr = sbefifo_client_next_xfr(client); +@@ -592,20 +594,28 @@ static ssize_t sbefifo_read(struct file *file, char __user *buf, + return n; + } + +-static ssize_t sbefifo_write(struct file *file, const char __user *buf, ++static ssize_t sbefifo_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) + { + struct sbefifo_client *client = file->private_data; +- struct sbefifo *sbefifo = client->dev; +- struct sbefifo_xfr *xfr; +- ssize_t ret = 0; +- size_t n; + + WARN_ON(*offset); + +- if (!access_ok(VERIFY_READ, buf, len)) ++ if (!access_ok(VERIFY_WRITE, buf, len)) + return -EFAULT; + ++ return sbefifo_read_common(client, buf, NULL, len); ++} ++ ++static ssize_t sbefifo_write_common(struct sbefifo_client *client, ++ const char __user *ubuf, const char *kbuf, ++ size_t len) ++{ ++ struct sbefifo *sbefifo = client->dev; ++ struct sbefifo_xfr *xfr; ++ ssize_t ret = 0; ++ size_t n; ++ + if ((len >> 2) << 2 != len) + return -EINVAL; + +@@ -617,7 +627,7 @@ static ssize_t sbefifo_write(struct file *file, const char __user *buf, + spin_lock_irq(&sbefifo->lock); + xfr = sbefifo_next_xfr(sbefifo); + +- if ((file->f_flags & O_NONBLOCK) && xfr && n < len) { ++ if ((client->f_flags & O_NONBLOCK) && xfr && n < len) { + spin_unlock_irq(&sbefifo->lock); + return -EAGAIN; + } +@@ -657,18 +667,25 @@ static ssize_t sbefifo_write(struct file *file, const char __user *buf, + + n = min_t(size_t, n, len); + +- if (copy_from_user(READ_ONCE(client->wbuf.wpos), buf, n)) { +- set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags); +- sbefifo_get(sbefifo); +- if (mod_timer(&sbefifo->poll_timer, jiffies)) +- sbefifo_put(sbefifo); +- sbefifo_put_client(client); +- return -EFAULT; ++ if (ubuf) { ++ if (copy_from_user(READ_ONCE(client->wbuf.wpos), ubuf, ++ n)) { ++ set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags); ++ sbefifo_get(sbefifo); ++ if (mod_timer(&sbefifo->poll_timer, jiffies)) ++ sbefifo_put(sbefifo); ++ sbefifo_put_client(client); ++ return -EFAULT; ++ } ++ ++ ubuf += n; ++ } else { ++ memcpy(READ_ONCE(client->wbuf.wpos), kbuf, n); ++ kbuf += n; + } + + sbefifo_buf_wrotenb(&client->wbuf, n); + len -= n; +- buf += n; + ret += n; + + /* +@@ -685,6 +702,19 @@ static ssize_t sbefifo_write(struct file *file, const char __user *buf, + return ret; + } + ++static ssize_t sbefifo_write(struct file *file, const char __user *buf, ++ size_t len, loff_t *offset) ++{ ++ struct sbefifo_client *client = file->private_data; ++ ++ WARN_ON(*offset); ++ ++ if (!access_ok(VERIFY_READ, buf, len)) ++ return -EFAULT; ++ ++ return sbefifo_write_common(client, buf, NULL, len); ++} ++ + static int sbefifo_release(struct inode *inode, struct file *file) + { + struct sbefifo_client *client = file->private_data; +@@ -704,12 +734,68 @@ static int sbefifo_release(struct inode *inode, struct file *file) + .release = sbefifo_release, + }; + ++struct sbefifo_client *sbefifo_drv_open(struct device *dev, ++ unsigned long flags) ++{ ++ struct sbefifo_client *client = NULL; ++ struct sbefifo *sbefifo; ++ struct fsi_device *fsi_dev = to_fsi_dev(dev); ++ ++ list_for_each_entry(sbefifo, &sbefifo_fifos, link) { ++ if (sbefifo->fsi_dev != fsi_dev) ++ continue; ++ ++ client = sbefifo_new_client(sbefifo); ++ if (client) ++ client->f_flags = flags; ++ } ++ ++ return client; ++} ++EXPORT_SYMBOL_GPL(sbefifo_drv_open); ++ ++int sbefifo_drv_read(struct sbefifo_client *client, char *buf, size_t len) ++{ ++ return sbefifo_read_common(client, NULL, buf, len); ++} ++EXPORT_SYMBOL_GPL(sbefifo_drv_read); ++ ++int sbefifo_drv_write(struct sbefifo_client *client, const char *buf, ++ size_t len) ++{ ++ return sbefifo_write_common(client, NULL, buf, len); ++} ++EXPORT_SYMBOL_GPL(sbefifo_drv_write); ++ ++void sbefifo_drv_release(struct sbefifo_client *client) ++{ ++ if (!client) ++ return; ++ ++ sbefifo_put_client(client); ++} ++EXPORT_SYMBOL_GPL(sbefifo_drv_release); ++ ++static int sbefifo_unregister_child(struct device *dev, void *data) ++{ ++ struct platform_device *child = to_platform_device(dev); ++ ++ of_device_unregister(child); ++ if (dev->of_node) ++ of_node_clear_flag(dev->of_node, OF_POPULATED); ++ ++ return 0; ++} ++ + static int sbefifo_probe(struct device *dev) + { + struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct sbefifo *sbefifo; ++ struct device_node *np; ++ struct platform_device *child; ++ char child_name[32]; + u32 sts; +- int ret; ++ int ret, child_idx = 0; + + dev_info(dev, "Found sbefifo device\n"); + sbefifo = kzalloc(sizeof(*sbefifo), GFP_KERNEL); +@@ -750,6 +836,18 @@ static int sbefifo_probe(struct device *dev) + init_waitqueue_head(&sbefifo->wait); + INIT_LIST_HEAD(&sbefifo->xfrs); + ++ if (dev->of_node) { ++ /* create platform devs for dts child nodes (occ, etc) */ ++ for_each_child_of_node(dev->of_node, np) { ++ snprintf(child_name, sizeof(child_name), "%s-dev%d", ++ sbefifo->name, child_idx++); ++ child = of_platform_device_create(np, child_name, dev); ++ if (!child) ++ dev_warn(&sbefifo->fsi_dev->dev, ++ "failed to create child node dev\n"); ++ } ++ } ++ + /* This bit of silicon doesn't offer any interrupts... */ + setup_timer(&sbefifo->poll_timer, sbefifo_poll_timer, + (unsigned long)sbefifo); +@@ -767,6 +865,9 @@ static int sbefifo_remove(struct device *dev) + list_for_each_entry_safe(sbefifo, sbefifo_tmp, &sbefifo_fifos, link) { + if (sbefifo->fsi_dev != fsi_dev) + continue; ++ ++ device_for_each_child(dev, NULL, sbefifo_unregister_child); ++ + misc_deregister(&sbefifo->mdev); + list_del(&sbefifo->link); + ida_simple_remove(&sbefifo_ida, sbefifo->idx); +diff --git a/include/linux/fsi-sbefifo.h b/include/linux/fsi-sbefifo.h +new file mode 100644 +index 0000000..1b46c63 +--- /dev/null ++++ b/include/linux/fsi-sbefifo.h +@@ -0,0 +1,30 @@ ++/* ++ * SBEFIFO FSI Client device driver ++ * ++ * Copyright (C) IBM Corporation 2017 ++ * ++ * 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. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#ifndef __FSI_SBEFIFO_H__ ++#define __FSI_SBEFIFO_H__ ++ ++struct device; ++struct sbefifo_client; ++ ++extern struct sbefifo_client *sbefifo_drv_open(struct device *dev, ++ unsigned long flags); ++extern int sbefifo_drv_read(struct sbefifo_client *client, char *buf, ++ size_t len); ++extern int sbefifo_drv_write(struct sbefifo_client *client, const char *buf, ++ size_t len); ++extern void sbefifo_drv_release(struct sbefifo_client *client); ++ ++#endif /* __FSI_SBEFIFO_H__ */ diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-2-3-drivers-fsi-sbefifo-Add-OCC-driver.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-2-3-drivers-fsi-sbefifo-Add-OCC-driver.patch new file mode 100644 index 000000000..5c53dadc4 --- /dev/null +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-2-3-drivers-fsi-sbefifo-Add-OCC-driver.patch @@ -0,0 +1,694 @@ +From patchwork Fri May 12 19:38:19 2017 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [linux,dev-4.10,2/3] drivers: fsi: sbefifo: Add OCC driver +From: eajames@linux.vnet.ibm.com +X-Patchwork-Id: 761838 +Message-Id: <1494617900-32369-3-git-send-email-eajames@linux.vnet.ibm.com> +To: openbmc@lists.ozlabs.org +Cc: "Edward A. James" <eajames@us.ibm.com>, bradleyb@fuzziesquirrel.com, + cbostic@linux.vnet.ibm.com +Date: Fri, 12 May 2017 14:38:19 -0500 + +From: "Edward A. James" <eajames@us.ibm.com> + +This driver provides an atomic communications channel between the OCC on +the POWER9 processor and a service processor (a BMC). The driver is +dependent on the FSI SBEIFO driver to get hardware access to the OCC +SRAM. + +The format of the communication is a command followed by a response. +Since the command and response must be performed atomically, the driver +will perform this operations asynchronously. In this way, a write +operation starts the command, and a read will gather the response data +once it is complete. + +Signed-off-by: Edward A. James <eajames@us.ibm.com> +--- + drivers/fsi/Kconfig | 9 + + drivers/fsi/Makefile | 1 + + drivers/fsi/occ.c | 625 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 635 insertions(+) + create mode 100644 drivers/fsi/occ.c + +diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig +index 39527fa..f3d8593 100644 +--- a/drivers/fsi/Kconfig ++++ b/drivers/fsi/Kconfig +@@ -36,6 +36,15 @@ config FSI_SBEFIFO + ---help--- + This option enables an FSI based SBEFIFO device driver. + ++if FSI_SBEFIFO ++ ++config OCCFIFO ++ tristate "OCC SBEFIFO client device driver" ++ ---help--- ++ This option enables an SBEFIFO based OCC device driver. ++ ++endif ++ + endif + + endmenu +diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile +index 851182e..336d9d5 100644 +--- a/drivers/fsi/Makefile ++++ b/drivers/fsi/Makefile +@@ -4,3 +4,4 @@ obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o + obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o + obj-$(CONFIG_FSI_SCOM) += fsi-scom.o + obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o ++obj-$(CONFIG_OCCFIFO) += occ.o +diff --git a/drivers/fsi/occ.c b/drivers/fsi/occ.c +new file mode 100644 +index 0000000..74272c8 +--- /dev/null ++++ b/drivers/fsi/occ.c +@@ -0,0 +1,625 @@ ++/* ++ * Copyright 2017 IBM Corp. ++ * ++ * 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; either version 2 of the License, or ++ * (at your option) any later version. ++ */ ++ ++#include <asm/unaligned.h> ++#include <linux/device.h> ++#include <linux/err.h> ++#include <linux/fsi-sbefifo.h> ++#include <linux/init.h> ++#include <linux/kernel.h> ++#include <linux/miscdevice.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <linux/slab.h> ++#include <linux/uaccess.h> ++#include <linux/wait.h> ++#include <linux/workqueue.h> ++ ++#define OCC_SRAM_BYTES 4096 ++#define OCC_CMD_DATA_BYTES 4090 ++#define OCC_RESP_DATA_BYTES 4089 ++ ++struct occ { ++ struct device *sbefifo; ++ char name[32]; ++ int idx; ++ struct miscdevice mdev; ++ struct list_head xfrs; ++ spinlock_t list_lock; ++ spinlock_t occ_lock; ++ struct work_struct work; ++}; ++ ++#define to_occ(x) container_of((x), struct occ, mdev) ++ ++struct occ_command { ++ u8 seq_no; ++ u8 cmd_type; ++ u16 data_length; ++ u8 data[OCC_CMD_DATA_BYTES]; ++ u16 checksum; ++}; ++ ++struct occ_response { ++ u8 seq_no; ++ u8 cmd_type; ++ u8 return_status; ++ u16 data_length; ++ u8 data[OCC_RESP_DATA_BYTES]; ++ u16 checksum; ++}; ++ ++struct occ_xfr; ++ ++enum { ++ CLIENT_NONBLOCKING, ++}; ++ ++struct occ_client { ++ struct occ *occ; ++ struct occ_xfr *xfr; ++ spinlock_t lock; ++ wait_queue_head_t wait; ++ size_t read_offset; ++ unsigned long flags; ++}; ++ ++enum { ++ XFR_IN_PROGRESS, ++ XFR_COMPLETE, ++ XFR_CANCELED, ++ XFR_WAITING, ++}; ++ ++struct occ_xfr { ++ struct list_head link; ++ struct occ_client *client; ++ int rc; ++ u8 buf[OCC_SRAM_BYTES]; ++ size_t cmd_data_length; ++ size_t resp_data_length; ++ unsigned long flags; ++}; ++ ++static struct workqueue_struct *occ_wq; ++ ++static DEFINE_IDA(occ_ida); ++ ++static void occ_enqueue_xfr(struct occ_xfr *xfr) ++{ ++ int empty; ++ struct occ *occ = xfr->client->occ; ++ ++ spin_lock_irq(&occ->list_lock); ++ empty = list_empty(&occ->xfrs); ++ list_add_tail(&xfr->link, &occ->xfrs); ++ spin_unlock(&occ->list_lock); ++ ++ if (empty) ++ queue_work(occ_wq, &occ->work); ++} ++ ++static int occ_open(struct inode *inode, struct file *file) ++{ ++ struct occ_client *client; ++ struct miscdevice *mdev = file->private_data; ++ struct occ *occ = to_occ(mdev); ++ ++ client = kzalloc(sizeof(*client), GFP_KERNEL); ++ if (!client) ++ return -ENOMEM; ++ ++ client->occ = occ; ++ spin_lock_init(&client->lock); ++ init_waitqueue_head(&client->wait); ++ ++ if (file->f_flags & O_NONBLOCK) ++ set_bit(CLIENT_NONBLOCKING, &client->flags); ++ ++ file->private_data = client; ++ ++ return 0; ++} ++ ++static ssize_t occ_read(struct file *file, char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ int rc; ++ size_t bytes; ++ struct occ_xfr *xfr; ++ struct occ_client *client = file->private_data; ++ ++ if (!access_ok(VERIFY_WRITE, buf, len)) ++ return -EFAULT; ++ ++ if (len > OCC_SRAM_BYTES) ++ return -EINVAL; ++ ++ spin_lock_irq(&client->lock); ++ if (!client->xfr) { ++ /* we just finished reading all data, return 0 */ ++ if (client->read_offset) { ++ rc = 0; ++ client->read_offset = 0; ++ } else ++ rc = -ENOMSG; ++ ++ goto done; ++ } ++ ++ xfr = client->xfr; ++ ++ if (!test_bit(XFR_COMPLETE, &xfr->flags)) { ++ if (client->flags & CLIENT_NONBLOCKING) { ++ rc = -ERESTARTSYS; ++ goto done; ++ } ++ ++ set_bit(XFR_WAITING, &xfr->flags); ++ spin_unlock(&client->lock); ++ ++ rc = wait_event_interruptible(client->wait, ++ test_bit(XFR_COMPLETE, &xfr->flags) || ++ test_bit(XFR_CANCELED, &xfr->flags)); ++ ++ spin_lock_irq(&client->lock); ++ if (test_bit(XFR_CANCELED, &xfr->flags)) { ++ kfree(xfr); ++ spin_unlock(&client->lock); ++ kfree(client); ++ return -EBADFD; ++ } ++ ++ clear_bit(XFR_WAITING, &xfr->flags); ++ if (!test_bit(XFR_COMPLETE, &xfr->flags)) { ++ rc = -EINTR; ++ goto done; ++ } ++ } ++ ++ if (xfr->rc) { ++ rc = xfr->rc; ++ goto done; ++ } ++ ++ bytes = min(len, xfr->resp_data_length - client->read_offset); ++ if (copy_to_user(buf, &xfr->buf[client->read_offset], bytes)) { ++ rc = -EFAULT; ++ goto done; ++ } ++ ++ client->read_offset += bytes; ++ ++ /* xfr done */ ++ if (client->read_offset == xfr->resp_data_length) { ++ kfree(xfr); ++ client->xfr = NULL; ++ } ++ ++ rc = bytes; ++ ++done: ++ spin_unlock(&client->lock); ++ return rc; ++} ++ ++static ssize_t occ_write(struct file *file, const char __user *buf, ++ size_t len, loff_t *offset) ++{ ++ int rc; ++ struct occ_xfr *xfr; ++ struct occ_client *client = file->private_data; ++ ++ if (!access_ok(VERIFY_READ, buf, len)) ++ return -EFAULT; ++ ++ if (len > OCC_SRAM_BYTES) ++ return -EINVAL; ++ ++ spin_lock_irq(&client->lock); ++ if (client->xfr) { ++ rc = -EDEADLK; ++ goto done; ++ } ++ ++ xfr = kzalloc(sizeof(*xfr), GFP_KERNEL); ++ if (!xfr) { ++ rc = -ENOMEM; ++ goto done; ++ } ++ ++ if (copy_from_user(xfr->buf, buf, len)) { ++ kfree(xfr); ++ rc = -EFAULT; ++ goto done; ++ } ++ ++ xfr->client = client; ++ xfr->cmd_data_length = len; ++ client->xfr = xfr; ++ client->read_offset = 0; ++ ++ occ_enqueue_xfr(xfr); ++ ++ rc = len; ++ ++done: ++ spin_unlock(&client->lock); ++ return rc; ++} ++ ++static int occ_release(struct inode *inode, struct file *file) ++{ ++ struct occ_xfr *xfr; ++ struct occ_client *client = file->private_data; ++ struct occ *occ = client->occ; ++ ++ spin_lock_irq(&client->lock); ++ xfr = client->xfr; ++ if (!xfr) { ++ spin_unlock(&client->lock); ++ kfree(client); ++ return 0; ++ } ++ ++ spin_lock_irq(&occ->list_lock); ++ set_bit(XFR_CANCELED, &xfr->flags); ++ if (!test_bit(XFR_IN_PROGRESS, &xfr->flags)) { ++ /* already deleted from list if complete */ ++ if (!test_bit(XFR_COMPLETE, &xfr->flags)) ++ list_del(&xfr->link); ++ ++ spin_unlock(&occ->list_lock); ++ ++ if (test_bit(XFR_WAITING, &xfr->flags)) { ++ /* blocking read; let reader clean up */ ++ wake_up_interruptible(&client->wait); ++ spin_unlock(&client->lock); ++ return 0; ++ } ++ ++ kfree(xfr); ++ spin_unlock(&client->lock); ++ kfree(client); ++ return 0; ++ } ++ ++ /* operation is in progress; let worker clean up*/ ++ spin_unlock(&occ->list_lock); ++ spin_unlock(&client->lock); ++ return 0; ++} ++ ++static const struct file_operations occ_fops = { ++ .owner = THIS_MODULE, ++ .open = occ_open, ++ .read = occ_read, ++ .write = occ_write, ++ .release = occ_release, ++}; ++ ++static int occ_getscom(struct device *sbefifo, u32 address, u8 *data) ++{ ++ int rc; ++ u32 buf[4]; ++ struct sbefifo_client *client; ++ const size_t len = sizeof(buf); ++ ++ buf[0] = cpu_to_be32(0x4); ++ buf[1] = cpu_to_be32(0xa201); ++ buf[2] = 0; ++ buf[3] = cpu_to_be32(address); ++ ++ client = sbefifo_drv_open(sbefifo, 0); ++ if (!client) ++ return -ENODEV; ++ ++ rc = sbefifo_drv_write(client, (const char *)buf, len); ++ if (rc < 0) ++ goto done; ++ else if (rc != len) { ++ rc = -EMSGSIZE; ++ goto done; ++ } ++ ++ rc = sbefifo_drv_read(client, (char *)buf, len); ++ if (rc < 0) ++ goto done; ++ else if (rc != len) { ++ rc = -EMSGSIZE; ++ goto done; ++ } ++ ++ /* check for good response */ ++ if ((be32_to_cpu(buf[2]) >> 16) != 0xC0DE) { ++ rc = -EFAULT; ++ goto done; ++ } ++ ++ rc = 0; ++ ++ memcpy(data, buf, sizeof(u64)); ++ ++done: ++ sbefifo_drv_release(client); ++ return rc; ++} ++ ++static int occ_putscom(struct device *sbefifo, u32 address, u8 *data) ++{ ++ int rc; ++ u32 buf[6]; ++ struct sbefifo_client *client; ++ const size_t len = sizeof(buf); ++ ++ buf[0] = cpu_to_be32(0x6); ++ buf[1] = cpu_to_be32(0xa202); ++ buf[2] = 0; ++ buf[3] = cpu_to_be32(address); ++ memcpy(&buf[4], data, sizeof(u64)); ++ ++ client = sbefifo_drv_open(sbefifo, 0); ++ if (!client) ++ return -ENODEV; ++ ++ rc = sbefifo_drv_write(client, (const char *)buf, len); ++ if (rc < 0) ++ goto done; ++ else if (rc != len) { ++ rc = -EMSGSIZE; ++ goto done; ++ } ++ ++ rc = 0; ++ ++ /* ignore response */ ++ sbefifo_drv_read(client, (char *)buf, len); ++ ++done: ++ sbefifo_drv_release(client); ++ return rc; ++} ++ ++static int occ_putscom_u32(struct device *sbefifo, u32 address, u32 data0, ++ u32 data1) ++{ ++ u8 buf[8]; ++ u32 raw_data0 = cpu_to_be32(data0), raw_data1 = cpu_to_be32(data1); ++ ++ memcpy(buf, &raw_data0, 4); ++ memcpy(buf + 4, &raw_data1, 4); ++ ++ return occ_putscom(sbefifo, address, buf); ++} ++ ++static void occ_worker(struct work_struct *work) ++{ ++ int i, empty, canceled, waiting, rc; ++ u16 resp_data_length; ++ struct occ *occ = container_of(work, struct occ, work); ++ struct device *sbefifo = occ->sbefifo; ++ struct occ_client *client; ++ struct occ_xfr *xfr; ++ struct occ_response *resp; ++ ++again: ++ spin_lock_irq(&occ->list_lock); ++ xfr = list_first_entry(&occ->xfrs, struct occ_xfr, link); ++ if (!xfr) { ++ spin_unlock(&occ->list_lock); ++ return; ++ } ++ ++ set_bit(XFR_IN_PROGRESS, &xfr->flags); ++ spin_unlock(&occ->list_lock); ++ ++ resp = (struct occ_response *)xfr->buf; ++ ++ spin_lock_irq(&occ->occ_lock); ++ ++ /* set address reg to occ sram command buffer */ ++ rc = occ_putscom_u32(sbefifo, 0x6D050, 0xFFFBE000, 0); ++ if (rc) ++ goto done; ++ ++ /* write cmd data */ ++ for (i = 0; i < xfr->cmd_data_length; i += 8) { ++ rc = occ_putscom(sbefifo, 0x6D055, &xfr->buf[i]); ++ if (rc) ++ goto done; ++ } ++ ++ /* trigger attention */ ++ rc = occ_putscom_u32(sbefifo, 0x6D035, 0x20010000, 0); ++ if (rc) ++ goto done; ++ ++ /* set address reg to occ sram response buffer */ ++ rc = occ_putscom_u32(sbefifo, 0x6D050, 0xFFFBF000, 0); ++ if (rc) ++ goto done; ++ ++ rc = occ_getscom(sbefifo, 0x6D055, xfr->buf); ++ if (rc) ++ goto done; ++ ++ xfr->resp_data_length += 8; ++ ++ resp_data_length = be16_to_cpu(get_unaligned(&resp->data_length)); ++ if (resp_data_length > OCC_RESP_DATA_BYTES) { ++ rc = -EFAULT; ++ goto done; ++ } ++ ++ /* already read 3 bytes of resp data, but also need 2 bytes chksum */ ++ for (i = 8; i < resp_data_length + 7; i += 8) { ++ rc = occ_getscom(sbefifo, 0x6D055, &xfr->buf[i]); ++ if (rc) ++ goto done; ++ ++ xfr->resp_data_length += 8; ++ } ++ ++ /* no errors, got all data */ ++ xfr->resp_data_length = resp_data_length + 7; ++ ++done: ++ spin_unlock(&occ->occ_lock); ++ ++ xfr->rc = rc; ++ client = xfr->client; ++ ++ /* lock client to prevent race with read() */ ++ spin_lock_irq(&client->lock); ++ set_bit(XFR_COMPLETE, &xfr->flags); ++ waiting = test_bit(XFR_WAITING, &xfr->flags); ++ spin_unlock(&client->lock); ++ ++ spin_lock_irq(&occ->list_lock); ++ clear_bit(XFR_IN_PROGRESS, &xfr->flags); ++ list_del(&xfr->link); ++ empty = list_empty(&occ->xfrs); ++ canceled = test_bit(XFR_CANCELED, &xfr->flags); ++ spin_unlock(&occ->list_lock); ++ ++ if (waiting) ++ wake_up_interruptible(&client->wait); ++ else if (canceled) { ++ kfree(xfr); ++ kfree(xfr->client); ++ } ++ ++ if (!empty) ++ goto again; ++} ++ ++static int occ_probe(struct platform_device *pdev) ++{ ++ int rc; ++ u32 reg; ++ struct occ *occ; ++ struct device *dev = &pdev->dev; ++ ++ dev_info(dev, "Found occ device\n"); ++ occ = devm_kzalloc(dev, sizeof(*occ), GFP_KERNEL); ++ if (!occ) ++ return -ENOMEM; ++ ++ occ->sbefifo = dev->parent; ++ INIT_LIST_HEAD(&occ->xfrs); ++ spin_lock_init(&occ->list_lock); ++ spin_lock_init(&occ->occ_lock); ++ INIT_WORK(&occ->work, occ_worker); ++ ++ if (dev->of_node) { ++ rc = of_property_read_u32(dev->of_node, "reg", ®); ++ if (!rc) { ++ /* make sure we don't have a duplicate from dts */ ++ occ->idx = ida_simple_get(&occ_ida, reg, reg + 1, ++ GFP_KERNEL); ++ if (occ->idx < 0) ++ occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX, ++ GFP_KERNEL); ++ } else ++ occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX, ++ GFP_KERNEL); ++ } else ++ occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX, GFP_KERNEL); ++ ++ snprintf(occ->name, sizeof(occ->name), "occ%d", occ->idx); ++ occ->mdev.fops = &occ_fops; ++ occ->mdev.minor = MISC_DYNAMIC_MINOR; ++ occ->mdev.name = occ->name; ++ occ->mdev.parent = dev; ++ ++ rc = misc_register(&occ->mdev); ++ if (rc) { ++ dev_err(dev, "failed to register miscdevice\n"); ++ return rc; ++ } ++ ++ platform_set_drvdata(pdev, occ); ++ ++ return 0; ++} ++ ++static int occ_remove(struct platform_device *pdev) ++{ ++ struct occ_xfr *xfr, *tmp; ++ struct occ *occ = platform_get_drvdata(pdev); ++ struct occ_client *client; ++ ++ misc_deregister(&occ->mdev); ++ ++ spin_lock_irq(&occ->list_lock); ++ list_for_each_entry_safe(xfr, tmp, &occ->xfrs, link) { ++ client = xfr->client; ++ set_bit(XFR_CANCELED, &xfr->flags); ++ ++ if (!test_bit(XFR_IN_PROGRESS, &xfr->flags)) { ++ list_del(&xfr->link); ++ ++ spin_lock_irq(&client->lock); ++ if (test_bit(XFR_WAITING, &xfr->flags)) { ++ wake_up_interruptible(&client->wait); ++ spin_unlock(&client->lock); ++ } else { ++ kfree(xfr); ++ spin_unlock(&client->lock); ++ kfree(client); ++ } ++ } ++ } ++ spin_unlock(&occ->list_lock); ++ ++ flush_work(&occ->work); ++ ++ ida_simple_remove(&occ_ida, occ->idx); ++ ++ return 0; ++} ++ ++static const struct of_device_id occ_match[] = { ++ { .compatible = "ibm,p9-occ" }, ++ { }, ++}; ++ ++static struct platform_driver occ_driver = { ++ .driver = { ++ .name = "occ", ++ .of_match_table = occ_match, ++ }, ++ .probe = occ_probe, ++ .remove = occ_remove, ++}; ++ ++static int occ_init(void) ++{ ++ occ_wq = create_singlethread_workqueue("occ"); ++ if (!occ_wq) ++ return -ENOMEM; ++ ++ return platform_driver_register(&occ_driver); ++} ++ ++static void occ_exit(void) ++{ ++ destroy_workqueue(occ_wq); ++ ++ platform_driver_unregister(&occ_driver); ++} ++ ++module_init(occ_init); ++module_exit(occ_exit); ++ ++MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>"); ++MODULE_DESCRIPTION("BMC P9 OCC driver"); ++MODULE_LICENSE("GPL"); ++ diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-drivers-fsi-Add-FSI-SBEFIFO-driver.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-drivers-fsi-Add-FSI-SBEFIFO-driver.patch new file mode 100644 index 000000000..b29e0e933 --- /dev/null +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-drivers-fsi-Add-FSI-SBEFIFO-driver.patch @@ -0,0 +1,899 @@ +From patchwork Thu May 11 02:52:53 2017 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [linux,dev-4.10] drivers: fsi: Add FSI SBEFIFO driver +From: eajames@linux.vnet.ibm.com +X-Patchwork-Id: 760920 +Message-Id: <1494471173-6077-1-git-send-email-eajames@linux.vnet.ibm.com> +To: openbmc@lists.ozlabs.org +Cc: "Edward A. James" <eajames@us.ibm.com>, bradleyb@fuzziesquirrel.com +Date: Wed, 10 May 2017 21:52:53 -0500 + +From: "Edward A. James" <eajames@us.ibm.com> + +IBM POWER9 processors contain some embedded hardware and software bits +collectively referred to as the self boot engine (SBE). One role of +the SBE is to act as a proxy that provides access to the registers of +the POWER chip from other (embedded) systems. + +The POWER9 chip contains a hardware frontend for communicating with +the SBE from remote systems called the SBEFIFO. The SBEFIFO logic +is contained within an FSI CFAM (see Documentation/fsi) and as such +the driver implements an FSI bus device. + +The SBE expects to communicate using a defined wire protocol; however, +the driver knows nothing of the protocol and only provides raw access +to the fifo device to userspace applications wishing to communicate with +the SBE using the wire protocol. + +The SBEFIFO consists of two hardware fifos. The upstream fifo is used +by the driver to transfer data to the SBE on the POWER chip, from the +system hosting the driver. The downstream fifo is used by the driver to +transfer data from the SBE on the power chip to the system hosting the +driver. + +Signed-off-by: Edward A. James <eajames@us.ibm.com> +Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com> +--- + drivers/fsi/Kconfig | 5 + + drivers/fsi/Makefile | 1 + + drivers/fsi/fsi-sbefifo.c | 824 ++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 830 insertions(+) + create mode 100644 drivers/fsi/fsi-sbefifo.c + +diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig +index fc031ac..39527fa 100644 +--- a/drivers/fsi/Kconfig ++++ b/drivers/fsi/Kconfig +@@ -31,6 +31,11 @@ config FSI_SCOM + ---help--- + This option enables an FSI based SCOM device driver. + ++config FSI_SBEFIFO ++ tristate "SBEFIFO FSI client device driver" ++ ---help--- ++ This option enables an FSI based SBEFIFO device driver. ++ + endif + + endmenu +diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile +index 65eb99d..851182e 100644 +--- a/drivers/fsi/Makefile ++++ b/drivers/fsi/Makefile +@@ -3,3 +3,4 @@ obj-$(CONFIG_FSI) += fsi-core.o + obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o + obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o + obj-$(CONFIG_FSI_SCOM) += fsi-scom.o ++obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o +diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c +new file mode 100644 +index 0000000..b49aec2 +--- /dev/null ++++ b/drivers/fsi/fsi-sbefifo.c +@@ -0,0 +1,824 @@ ++/* ++ * Copyright (C) IBM Corporation 2017 ++ * ++ * 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. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include <linux/delay.h> ++#include <linux/errno.h> ++#include <linux/idr.h> ++#include <linux/fsi.h> ++#include <linux/list.h> ++#include <linux/miscdevice.h> ++#include <linux/module.h> ++#include <linux/poll.h> ++#include <linux/sched.h> ++#include <linux/slab.h> ++#include <linux/timer.h> ++#include <linux/uaccess.h> ++ ++/* ++ * The SBEFIFO is a pipe-like FSI device for communicating with ++ * the self boot engine on POWER processors. ++ */ ++ ++#define DEVICE_NAME "sbefifo" ++#define FSI_ENGID_SBE 0x22 ++#define SBEFIFO_BUF_CNT 32 ++ ++#define SBEFIFO_UP 0x00 /* Up register offset */ ++#define SBEFIFO_DWN 0x40 /* Down register offset */ ++ ++#define SBEFIFO_STS 0x04 ++#define SBEFIFO_EMPTY BIT(20) ++#define SBEFIFO_EOT_RAISE 0x08 ++#define SBEFIFO_EOT_MAGIC 0xffffffff ++#define SBEFIFO_EOT_ACK 0x14 ++ ++struct sbefifo { ++ struct timer_list poll_timer; ++ struct fsi_device *fsi_dev; ++ struct miscdevice mdev; ++ wait_queue_head_t wait; ++ struct list_head link; ++ struct list_head xfrs; ++ struct kref kref; ++ spinlock_t lock; ++ char name[32]; ++ int idx; ++ int rc; ++}; ++ ++struct sbefifo_buf { ++ u32 buf[SBEFIFO_BUF_CNT]; ++ unsigned long flags; ++#define SBEFIFO_BUF_FULL 1 ++ u32 *rpos; ++ u32 *wpos; ++}; ++ ++struct sbefifo_xfr { ++ struct sbefifo_buf *rbuf; ++ struct sbefifo_buf *wbuf; ++ struct list_head client; ++ struct list_head xfrs; ++ unsigned long flags; ++#define SBEFIFO_XFR_WRITE_DONE 1 ++#define SBEFIFO_XFR_RESP_PENDING 2 ++#define SBEFIFO_XFR_COMPLETE 3 ++#define SBEFIFO_XFR_CANCEL 4 ++}; ++ ++struct sbefifo_client { ++ struct sbefifo_buf rbuf; ++ struct sbefifo_buf wbuf; ++ struct list_head xfrs; ++ struct sbefifo *dev; ++ struct kref kref; ++}; ++ ++static struct list_head sbefifo_fifos; ++ ++static DEFINE_IDA(sbefifo_ida); ++ ++static int sbefifo_inw(struct sbefifo *sbefifo, int reg, u32 *word) ++{ ++ int rc; ++ u32 raw_word; ++ ++ rc = fsi_device_read(sbefifo->fsi_dev, reg, &raw_word, ++ sizeof(raw_word)); ++ if (rc) ++ return rc; ++ ++ *word = be32_to_cpu(raw_word); ++ return 0; ++} ++ ++static int sbefifo_outw(struct sbefifo *sbefifo, int reg, u32 word) ++{ ++ u32 raw_word = cpu_to_be32(word); ++ ++ return fsi_device_write(sbefifo->fsi_dev, reg, &raw_word, ++ sizeof(raw_word)); ++} ++ ++static int sbefifo_readw(struct sbefifo *sbefifo, u32 *word) ++{ ++ return fsi_device_read(sbefifo->fsi_dev, SBEFIFO_DWN, word, ++ sizeof(*word)); ++} ++ ++static int sbefifo_writew(struct sbefifo *sbefifo, u32 word) ++{ ++ return fsi_device_write(sbefifo->fsi_dev, SBEFIFO_UP, &word, ++ sizeof(word)); ++} ++ ++static int sbefifo_ack_eot(struct sbefifo *sbefifo) ++{ ++ u32 discard; ++ int ret; ++ ++ /* Discard the EOT word. */ ++ ret = sbefifo_readw(sbefifo, &discard); ++ if (ret) ++ return ret; ++ ++ return sbefifo_outw(sbefifo, SBEFIFO_DWN | SBEFIFO_EOT_ACK, ++ SBEFIFO_EOT_MAGIC); ++} ++ ++static size_t sbefifo_dev_nwreadable(u32 sts) ++{ ++ static const u32 FIFO_NTRY_CNT_MSK = 0x000f0000; ++ static const unsigned int FIFO_NTRY_CNT_SHIFT = 16; ++ ++ return (sts & FIFO_NTRY_CNT_MSK) >> FIFO_NTRY_CNT_SHIFT; ++} ++ ++static size_t sbefifo_dev_nwwriteable(u32 sts) ++{ ++ static const size_t FIFO_DEPTH = 8; ++ ++ return FIFO_DEPTH - sbefifo_dev_nwreadable(sts); ++} ++ ++static void sbefifo_buf_init(struct sbefifo_buf *buf) ++{ ++ WRITE_ONCE(buf->rpos, buf->buf); ++ WRITE_ONCE(buf->wpos, buf->buf); ++} ++ ++static size_t sbefifo_buf_nbreadable(struct sbefifo_buf *buf) ++{ ++ size_t n; ++ u32 *rpos = READ_ONCE(buf->rpos); ++ u32 *wpos = READ_ONCE(buf->wpos); ++ ++ if (test_bit(SBEFIFO_BUF_FULL, &buf->flags)) ++ n = SBEFIFO_BUF_CNT; ++ else if (rpos <= wpos) ++ n = wpos - rpos; ++ else ++ n = (buf->buf + SBEFIFO_BUF_CNT) - rpos; ++ ++ return n << 2; ++} ++ ++static size_t sbefifo_buf_nbwriteable(struct sbefifo_buf *buf) ++{ ++ size_t n; ++ u32 *rpos = READ_ONCE(buf->rpos); ++ u32 *wpos = READ_ONCE(buf->wpos); ++ ++ if (test_bit(SBEFIFO_BUF_FULL, &buf->flags)) ++ n = 0; ++ else if (wpos < rpos) ++ n = rpos - wpos; ++ else ++ n = (buf->buf + SBEFIFO_BUF_CNT) - wpos; ++ ++ return n << 2; ++} ++ ++/* ++ * Update pointers and flags after doing a buffer read. Return true if the ++ * buffer is now empty; ++ */ ++static bool sbefifo_buf_readnb(struct sbefifo_buf *buf, size_t n) ++{ ++ u32 *rpos = READ_ONCE(buf->rpos); ++ u32 *wpos = READ_ONCE(buf->wpos); ++ ++ if (n) ++ clear_bit(SBEFIFO_BUF_FULL, &buf->flags); ++ ++ rpos += (n >> 2); ++ if (rpos == buf->buf + SBEFIFO_BUF_CNT) ++ rpos = buf->buf; ++ ++ WRITE_ONCE(buf->rpos, rpos); ++ return rpos == wpos; ++} ++ ++/* ++ * Update pointers and flags after doing a buffer write. Return true if the ++ * buffer is now full. ++ */ ++static bool sbefifo_buf_wrotenb(struct sbefifo_buf *buf, size_t n) ++{ ++ u32 *rpos = READ_ONCE(buf->rpos); ++ u32 *wpos = READ_ONCE(buf->wpos); ++ ++ wpos += (n >> 2); ++ if (wpos == buf->buf + SBEFIFO_BUF_CNT) ++ wpos = buf->buf; ++ if (wpos == rpos) ++ set_bit(SBEFIFO_BUF_FULL, &buf->flags); ++ ++ WRITE_ONCE(buf->wpos, wpos); ++ return rpos == wpos; ++} ++ ++static void sbefifo_free(struct kref *kref) ++{ ++ struct sbefifo *sbefifo; ++ ++ sbefifo = container_of(kref, struct sbefifo, kref); ++ kfree(sbefifo); ++} ++ ++static void sbefifo_get(struct sbefifo *sbefifo) ++{ ++ kref_get(&sbefifo->kref); ++} ++ ++static void sbefifo_put(struct sbefifo *sbefifo) ++{ ++ kref_put(&sbefifo->kref, sbefifo_free); ++} ++ ++static struct sbefifo_xfr *sbefifo_enq_xfr(struct sbefifo_client *client) ++{ ++ struct sbefifo *sbefifo = client->dev; ++ struct sbefifo_xfr *xfr; ++ ++ xfr = kzalloc(sizeof(*xfr), GFP_KERNEL); ++ if (!xfr) ++ return NULL; ++ ++ xfr->rbuf = &client->rbuf; ++ xfr->wbuf = &client->wbuf; ++ list_add_tail(&xfr->xfrs, &sbefifo->xfrs); ++ list_add_tail(&xfr->client, &client->xfrs); ++ ++ return xfr; ++} ++ ++static struct sbefifo_xfr *sbefifo_client_next_xfr( ++ struct sbefifo_client *client) ++{ ++ if (list_empty(&client->xfrs)) ++ return NULL; ++ ++ return container_of(client->xfrs.next, struct sbefifo_xfr, ++ client); ++} ++ ++static bool sbefifo_xfr_rsp_pending(struct sbefifo_client *client) ++{ ++ struct sbefifo_xfr *xfr; ++ ++ xfr = sbefifo_client_next_xfr(client); ++ if (xfr && test_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags)) ++ return true; ++ ++ return false; ++} ++ ++static struct sbefifo_client *sbefifo_new_client(struct sbefifo *sbefifo) ++{ ++ struct sbefifo_client *client; ++ ++ client = kzalloc(sizeof(*client), GFP_KERNEL); ++ if (!client) ++ return NULL; ++ ++ kref_init(&client->kref); ++ client->dev = sbefifo; ++ sbefifo_buf_init(&client->rbuf); ++ sbefifo_buf_init(&client->wbuf); ++ INIT_LIST_HEAD(&client->xfrs); ++ ++ sbefifo_get(sbefifo); ++ ++ return client; ++} ++ ++static void sbefifo_client_release(struct kref *kref) ++{ ++ struct sbefifo_client *client; ++ struct sbefifo_xfr *xfr; ++ ++ client = container_of(kref, struct sbefifo_client, kref); ++ list_for_each_entry(xfr, &client->xfrs, client) { ++ /* ++ * The client left with pending or running xfrs. ++ * Cancel them. ++ */ ++ set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags); ++ sbefifo_get(client->dev); ++ if (mod_timer(&client->dev->poll_timer, jiffies)) ++ sbefifo_put(client->dev); ++ } ++ ++ sbefifo_put(client->dev); ++ kfree(client); ++} ++ ++static void sbefifo_get_client(struct sbefifo_client *client) ++{ ++ kref_get(&client->kref); ++} ++ ++static void sbefifo_put_client(struct sbefifo_client *client) ++{ ++ kref_put(&client->kref, sbefifo_client_release); ++} ++ ++static struct sbefifo_xfr *sbefifo_next_xfr(struct sbefifo *sbefifo) ++{ ++ struct sbefifo_xfr *xfr, *tmp; ++ ++ list_for_each_entry_safe(xfr, tmp, &sbefifo->xfrs, xfrs) { ++ if (unlikely(test_bit(SBEFIFO_XFR_CANCEL, &xfr->flags))) { ++ /* Discard cancelled transfers. */ ++ list_del(&xfr->xfrs); ++ kfree(xfr); ++ continue; ++ } ++ return xfr; ++ } ++ ++ return NULL; ++} ++ ++static void sbefifo_poll_timer(unsigned long data) ++{ ++ static const unsigned long EOT_MASK = 0x000000ff; ++ struct sbefifo *sbefifo = (void *)data; ++ struct sbefifo_buf *rbuf, *wbuf; ++ struct sbefifo_xfr *xfr = NULL; ++ struct sbefifo_buf drain; ++ size_t devn, bufn; ++ int eot = 0; ++ int ret = 0; ++ u32 sts; ++ int i; ++ ++ spin_lock(&sbefifo->lock); ++ xfr = list_first_entry_or_null(&sbefifo->xfrs, struct sbefifo_xfr, ++ xfrs); ++ if (!xfr) ++ goto out_unlock; ++ ++ rbuf = xfr->rbuf; ++ wbuf = xfr->wbuf; ++ ++ if (unlikely(test_bit(SBEFIFO_XFR_CANCEL, &xfr->flags))) { ++ /* The client left. */ ++ rbuf = &drain; ++ wbuf = &drain; ++ sbefifo_buf_init(&drain); ++ if (!test_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags)) ++ set_bit(SBEFIFO_XFR_WRITE_DONE, &xfr->flags); ++ } ++ ++ /* Drain the write buffer. */ ++ while ((bufn = sbefifo_buf_nbreadable(wbuf))) { ++ ret = sbefifo_inw(sbefifo, SBEFIFO_UP | SBEFIFO_STS, ++ &sts); ++ if (ret) ++ goto out; ++ ++ devn = sbefifo_dev_nwwriteable(sts); ++ if (devn == 0) { ++ /* No open slot for write. Reschedule. */ ++ sbefifo->poll_timer.expires = jiffies + ++ msecs_to_jiffies(500); ++ add_timer(&sbefifo->poll_timer); ++ goto out_unlock; ++ } ++ ++ devn = min_t(size_t, devn, bufn >> 2); ++ for (i = 0; i < devn; i++) { ++ ret = sbefifo_writew(sbefifo, *wbuf->rpos); ++ if (ret) ++ goto out; ++ ++ sbefifo_buf_readnb(wbuf, 1 << 2); ++ } ++ } ++ ++ /* Send EOT if the writer is finished. */ ++ if (test_and_clear_bit(SBEFIFO_XFR_WRITE_DONE, &xfr->flags)) { ++ ret = sbefifo_outw(sbefifo, ++ SBEFIFO_UP | SBEFIFO_EOT_RAISE, ++ SBEFIFO_EOT_MAGIC); ++ if (ret) ++ goto out; ++ ++ /* Inform reschedules that the writer is finished. */ ++ set_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags); ++ } ++ ++ /* Nothing left to do if the writer is not finished. */ ++ if (!test_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags)) ++ goto out; ++ ++ /* Fill the read buffer. */ ++ while ((bufn = sbefifo_buf_nbwriteable(rbuf))) { ++ ret = sbefifo_inw(sbefifo, SBEFIFO_DWN | SBEFIFO_STS, &sts); ++ if (ret) ++ goto out; ++ ++ devn = sbefifo_dev_nwreadable(sts); ++ if (devn == 0) { ++ /* No data yet. Reschedule. */ ++ sbefifo->poll_timer.expires = jiffies + ++ msecs_to_jiffies(500); ++ add_timer(&sbefifo->poll_timer); ++ goto out_unlock; ++ } ++ ++ /* Fill. The EOT word is discarded. */ ++ devn = min_t(size_t, devn, bufn >> 2); ++ eot = (sts & EOT_MASK) != 0; ++ if (eot) ++ devn--; ++ ++ for (i = 0; i < devn; i++) { ++ ret = sbefifo_readw(sbefifo, rbuf->wpos); ++ if (ret) ++ goto out; ++ ++ if (likely(!test_bit(SBEFIFO_XFR_CANCEL, &xfr->flags))) ++ sbefifo_buf_wrotenb(rbuf, 1 << 2); ++ } ++ ++ if (eot) { ++ ret = sbefifo_ack_eot(sbefifo); ++ if (ret) ++ goto out; ++ ++ set_bit(SBEFIFO_XFR_COMPLETE, &xfr->flags); ++ list_del(&xfr->xfrs); ++ if (unlikely(test_bit(SBEFIFO_XFR_CANCEL, ++ &xfr->flags))) ++ kfree(xfr); ++ break; ++ } ++ } ++ ++out: ++ if (unlikely(ret)) { ++ sbefifo->rc = ret; ++ dev_err(&sbefifo->fsi_dev->dev, ++ "Fatal bus access failure: %d\n", ret); ++ list_for_each_entry(xfr, &sbefifo->xfrs, xfrs) ++ kfree(xfr); ++ INIT_LIST_HEAD(&sbefifo->xfrs); ++ ++ } else if (eot && sbefifo_next_xfr(sbefifo)) { ++ sbefifo_get(sbefifo); ++ sbefifo->poll_timer.expires = jiffies; ++ add_timer(&sbefifo->poll_timer); ++ } ++ ++ sbefifo_put(sbefifo); ++ wake_up(&sbefifo->wait); ++ ++out_unlock: ++ spin_unlock(&sbefifo->lock); ++} ++ ++static int sbefifo_open(struct inode *inode, struct file *file) ++{ ++ struct sbefifo *sbefifo = container_of(file->private_data, ++ struct sbefifo, mdev); ++ struct sbefifo_client *client; ++ int ret; ++ ++ ret = READ_ONCE(sbefifo->rc); ++ if (ret) ++ return ret; ++ ++ client = sbefifo_new_client(sbefifo); ++ if (!client) ++ return -ENOMEM; ++ ++ file->private_data = client; ++ ++ return 0; ++} ++ ++static unsigned int sbefifo_poll(struct file *file, poll_table *wait) ++{ ++ struct sbefifo_client *client = file->private_data; ++ struct sbefifo *sbefifo = client->dev; ++ unsigned int mask = 0; ++ ++ poll_wait(file, &sbefifo->wait, wait); ++ ++ if (READ_ONCE(sbefifo->rc)) ++ mask |= POLLERR; ++ ++ if (sbefifo_buf_nbreadable(&client->rbuf)) ++ mask |= POLLIN; ++ ++ if (sbefifo_buf_nbwriteable(&client->wbuf)) ++ mask |= POLLOUT; ++ ++ return mask; ++} ++ ++static ssize_t sbefifo_read(struct file *file, char __user *buf, ++ size_t len, loff_t *offset) ++{ ++ struct sbefifo_client *client = file->private_data; ++ struct sbefifo *sbefifo = client->dev; ++ struct sbefifo_xfr *xfr; ++ ssize_t ret = 0; ++ size_t n; ++ ++ WARN_ON(*offset); ++ ++ if (!access_ok(VERIFY_WRITE, buf, len)) ++ return -EFAULT; ++ ++ if ((len >> 2) << 2 != len) ++ return -EINVAL; ++ ++ if ((file->f_flags & O_NONBLOCK) && !sbefifo_xfr_rsp_pending(client)) ++ return -EAGAIN; ++ ++ sbefifo_get_client(client); ++ if (wait_event_interruptible(sbefifo->wait, ++ (ret = READ_ONCE(sbefifo->rc)) || ++ (n = sbefifo_buf_nbreadable( ++ &client->rbuf)))) { ++ sbefifo_put_client(client); ++ return -ERESTARTSYS; ++ } ++ if (ret) { ++ INIT_LIST_HEAD(&client->xfrs); ++ sbefifo_put_client(client); ++ return ret; ++ } ++ ++ n = min_t(size_t, n, len); ++ ++ if (copy_to_user(buf, READ_ONCE(client->rbuf.rpos), n)) { ++ sbefifo_put_client(client); ++ return -EFAULT; ++ } ++ ++ if (sbefifo_buf_readnb(&client->rbuf, n)) { ++ xfr = sbefifo_client_next_xfr(client); ++ if (!test_bit(SBEFIFO_XFR_COMPLETE, &xfr->flags)) { ++ /* ++ * Fill the read buffer back up. ++ */ ++ sbefifo_get(sbefifo); ++ if (mod_timer(&client->dev->poll_timer, jiffies)) ++ sbefifo_put(sbefifo); ++ } else { ++ kfree(xfr); ++ list_del(&xfr->client); ++ wake_up(&sbefifo->wait); ++ } ++ } ++ ++ sbefifo_put_client(client); ++ ++ return n; ++} ++ ++static ssize_t sbefifo_write(struct file *file, const char __user *buf, ++ size_t len, loff_t *offset) ++{ ++ struct sbefifo_client *client = file->private_data; ++ struct sbefifo *sbefifo = client->dev; ++ struct sbefifo_xfr *xfr; ++ ssize_t ret = 0; ++ size_t n; ++ ++ WARN_ON(*offset); ++ ++ if (!access_ok(VERIFY_READ, buf, len)) ++ return -EFAULT; ++ ++ if ((len >> 2) << 2 != len) ++ return -EINVAL; ++ ++ if (!len) ++ return 0; ++ ++ n = sbefifo_buf_nbwriteable(&client->wbuf); ++ ++ spin_lock_irq(&sbefifo->lock); ++ xfr = sbefifo_next_xfr(sbefifo); ++ ++ if ((file->f_flags & O_NONBLOCK) && xfr && n < len) { ++ spin_unlock_irq(&sbefifo->lock); ++ return -EAGAIN; ++ } ++ ++ xfr = sbefifo_enq_xfr(client); ++ if (!xfr) { ++ spin_unlock_irq(&sbefifo->lock); ++ return -ENOMEM; ++ } ++ spin_unlock_irq(&sbefifo->lock); ++ ++ sbefifo_get_client(client); ++ ++ /* ++ * Partial writes are not really allowed in that EOT is sent exactly ++ * once per write. ++ */ ++ while (len) { ++ if (wait_event_interruptible(sbefifo->wait, ++ READ_ONCE(sbefifo->rc) || ++ (sbefifo_client_next_xfr(client) == xfr && ++ (n = sbefifo_buf_nbwriteable( ++ &client->wbuf))))) { ++ set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags); ++ sbefifo_get(sbefifo); ++ if (mod_timer(&sbefifo->poll_timer, jiffies)) ++ sbefifo_put(sbefifo); ++ ++ sbefifo_put_client(client); ++ return -ERESTARTSYS; ++ } ++ if (sbefifo->rc) { ++ INIT_LIST_HEAD(&client->xfrs); ++ sbefifo_put_client(client); ++ return sbefifo->rc; ++ } ++ ++ n = min_t(size_t, n, len); ++ ++ if (copy_from_user(READ_ONCE(client->wbuf.wpos), buf, n)) { ++ set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags); ++ sbefifo_get(sbefifo); ++ if (mod_timer(&sbefifo->poll_timer, jiffies)) ++ sbefifo_put(sbefifo); ++ sbefifo_put_client(client); ++ return -EFAULT; ++ } ++ ++ sbefifo_buf_wrotenb(&client->wbuf, n); ++ len -= n; ++ buf += n; ++ ret += n; ++ ++ /* ++ * Drain the write buffer. ++ */ ++ sbefifo_get(sbefifo); ++ if (mod_timer(&client->dev->poll_timer, jiffies)) ++ sbefifo_put(sbefifo); ++ } ++ ++ set_bit(SBEFIFO_XFR_WRITE_DONE, &xfr->flags); ++ sbefifo_put_client(client); ++ ++ return ret; ++} ++ ++static int sbefifo_release(struct inode *inode, struct file *file) ++{ ++ struct sbefifo_client *client = file->private_data; ++ struct sbefifo *sbefifo = client->dev; ++ ++ sbefifo_put_client(client); ++ ++ return READ_ONCE(sbefifo->rc); ++} ++ ++static const struct file_operations sbefifo_fops = { ++ .owner = THIS_MODULE, ++ .open = sbefifo_open, ++ .read = sbefifo_read, ++ .write = sbefifo_write, ++ .poll = sbefifo_poll, ++ .release = sbefifo_release, ++}; ++ ++static int sbefifo_probe(struct device *dev) ++{ ++ struct fsi_device *fsi_dev = to_fsi_dev(dev); ++ struct sbefifo *sbefifo; ++ u32 sts; ++ int ret; ++ ++ dev_info(dev, "Found sbefifo device\n"); ++ sbefifo = kzalloc(sizeof(*sbefifo), GFP_KERNEL); ++ if (!sbefifo) ++ return -ENOMEM; ++ ++ sbefifo->fsi_dev = fsi_dev; ++ ++ ret = sbefifo_inw(sbefifo, ++ SBEFIFO_UP | SBEFIFO_STS, &sts); ++ if (ret) ++ return ret; ++ if (!(sts & SBEFIFO_EMPTY)) { ++ dev_err(&sbefifo->fsi_dev->dev, ++ "Found data in upstream fifo\n"); ++ return -EIO; ++ } ++ ++ ret = sbefifo_inw(sbefifo, SBEFIFO_DWN | SBEFIFO_STS, &sts); ++ if (ret) ++ return ret; ++ if (!(sts & SBEFIFO_EMPTY)) { ++ dev_err(&sbefifo->fsi_dev->dev, ++ "Found data in downstream fifo\n"); ++ return -EIO; ++ } ++ ++ sbefifo->mdev.minor = MISC_DYNAMIC_MINOR; ++ sbefifo->mdev.fops = &sbefifo_fops; ++ sbefifo->mdev.name = sbefifo->name; ++ sbefifo->mdev.parent = dev; ++ spin_lock_init(&sbefifo->lock); ++ kref_init(&sbefifo->kref); ++ ++ sbefifo->idx = ida_simple_get(&sbefifo_ida, 1, INT_MAX, GFP_KERNEL); ++ snprintf(sbefifo->name, sizeof(sbefifo->name), "sbefifo%d", ++ sbefifo->idx); ++ init_waitqueue_head(&sbefifo->wait); ++ INIT_LIST_HEAD(&sbefifo->xfrs); ++ ++ /* This bit of silicon doesn't offer any interrupts... */ ++ setup_timer(&sbefifo->poll_timer, sbefifo_poll_timer, ++ (unsigned long)sbefifo); ++ ++ list_add(&sbefifo->link, &sbefifo_fifos); ++ return misc_register(&sbefifo->mdev); ++} ++ ++static int sbefifo_remove(struct device *dev) ++{ ++ struct fsi_device *fsi_dev = to_fsi_dev(dev); ++ struct sbefifo *sbefifo, *sbefifo_tmp; ++ struct sbefifo_xfr *xfr; ++ ++ list_for_each_entry_safe(sbefifo, sbefifo_tmp, &sbefifo_fifos, link) { ++ if (sbefifo->fsi_dev != fsi_dev) ++ continue; ++ misc_deregister(&sbefifo->mdev); ++ list_del(&sbefifo->link); ++ ida_simple_remove(&sbefifo_ida, sbefifo->idx); ++ ++ if (del_timer_sync(&sbefifo->poll_timer)) ++ sbefifo_put(sbefifo); ++ ++ spin_lock(&sbefifo->lock); ++ list_for_each_entry(xfr, &sbefifo->xfrs, xfrs) ++ kfree(xfr); ++ spin_unlock(&sbefifo->lock); ++ ++ WRITE_ONCE(sbefifo->rc, -ENODEV); ++ ++ wake_up(&sbefifo->wait); ++ sbefifo_put(sbefifo); ++ } ++ ++ return 0; ++} ++ ++static struct fsi_device_id sbefifo_ids[] = { ++ { ++ .engine_type = FSI_ENGID_SBE, ++ .version = FSI_VERSION_ANY, ++ }, ++ { 0 } ++}; ++ ++static struct fsi_driver sbefifo_drv = { ++ .id_table = sbefifo_ids, ++ .drv = { ++ .name = DEVICE_NAME, ++ .bus = &fsi_bus_type, ++ .probe = sbefifo_probe, ++ .remove = sbefifo_remove, ++ } ++}; ++ ++static int sbefifo_init(void) ++{ ++ INIT_LIST_HEAD(&sbefifo_fifos); ++ return fsi_driver_register(&sbefifo_drv); ++} ++ ++static void sbefifo_exit(void) ++{ ++ fsi_driver_unregister(&sbefifo_drv); ++} ++ ++module_init(sbefifo_init); ++module_exit(sbefifo_exit); ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Brad Bishop <bradleyb@fuzziesquirrel.com>"); ++MODULE_DESCRIPTION("Linux device interface to the POWER self boot engine"); diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-1-6-drivers-i2c-Add-FSI-attached-I2C-master-algorithm.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-1-6-drivers-i2c-Add-FSI-attached-I2C-master-algorithm.patch new file mode 100644 index 000000000..f38a6a49a --- /dev/null +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-1-6-drivers-i2c-Add-FSI-attached-I2C-master-algorithm.patch @@ -0,0 +1,326 @@ +From patchwork Wed May 10 15:52:37 2017 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [linux, dev-4.10, v2, + 1/6] drivers: i2c: Add FSI-attached I2C master algorithm +From: eajames@linux.vnet.ibm.com +X-Patchwork-Id: 760697 +Message-Id: <1494431562-25101-2-git-send-email-eajames@linux.vnet.ibm.com> +To: openbmc@lists.ozlabs.org +Cc: "Edward A. James" <eajames@us.ibm.com>, cbostic@linux.vnet.ibm.com +Date: Wed, 10 May 2017 10:52:37 -0500 + +From: "Edward A. James" <eajames@us.ibm.com> + +Initial startup code for the I2C algorithm to drive the I2C master +located on POWER CPUs over FSI bus. + +Signed-off-by: Edward A. James <eajames@us.ibm.com> +--- + drivers/Makefile | 2 +- + drivers/i2c/busses/Kconfig | 11 ++ + drivers/i2c/busses/Makefile | 1 + + drivers/i2c/busses/i2c-fsi.c | 245 +++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 258 insertions(+), 1 deletion(-) + create mode 100644 drivers/i2c/busses/i2c-fsi.c + +diff --git a/drivers/Makefile b/drivers/Makefile +index 67ce51d..278f109 100644 +--- a/drivers/Makefile ++++ b/drivers/Makefile +@@ -105,6 +105,7 @@ obj-$(CONFIG_SERIO) += input/serio/ + obj-$(CONFIG_GAMEPORT) += input/gameport/ + obj-$(CONFIG_INPUT) += input/ + obj-$(CONFIG_RTC_LIB) += rtc/ ++obj-$(CONFIG_FSI) += fsi/ + obj-y += i2c/ media/ + obj-$(CONFIG_PPS) += pps/ + obj-y += ptp/ +@@ -173,4 +174,3 @@ obj-$(CONFIG_STM) += hwtracing/stm/ + obj-$(CONFIG_ANDROID) += android/ + obj-$(CONFIG_NVMEM) += nvmem/ + obj-$(CONFIG_FPGA) += fpga/ +-obj-$(CONFIG_FSI) += fsi/ +diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig +index 7e34901d..0c714e0 100644 +--- a/drivers/i2c/busses/Kconfig ++++ b/drivers/i2c/busses/Kconfig +@@ -1245,4 +1245,15 @@ config I2C_OPAL + This driver can also be built as a module. If so, the module will be + called as i2c-opal. + ++config I2C_FSI ++ tristate "FSI I2C driver" ++ depends on FSI ++ help ++ Driver for FSI bus attached I2C masters. These are I2C masters that ++ are connected to the system over a FSI bus, instead of the more ++ common PCI or MMIO interface. ++ ++ This driver can also be built as a module. If so, the module will be ++ called as i2c-fsi. ++ + endmenu +diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile +index 5798b4e..547773b 100644 +--- a/drivers/i2c/busses/Makefile ++++ b/drivers/i2c/busses/Makefile +@@ -124,5 +124,6 @@ obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o + obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o + obj-$(CONFIG_I2C_XGENE_SLIMPRO) += i2c-xgene-slimpro.o + obj-$(CONFIG_SCx200_ACB) += scx200_acb.o ++obj-$(CONFIG_I2C_FSI) += i2c-fsi.o + + ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG +diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c +new file mode 100644 +index 0000000..3c1087d +--- /dev/null ++++ b/drivers/i2c/busses/i2c-fsi.c +@@ -0,0 +1,245 @@ ++/* ++ * Copyright 2017 IBM Corporation ++ * ++ * Eddie James <eajames@us.ibm.com> ++ * ++ * 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; either version ++ * 2 of the License, or (at your option) any later version. ++ */ ++ ++#include <linux/fsi.h> ++#include <linux/i2c.h> ++#include <linux/jiffies.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/sched.h> ++#include <linux/semaphore.h> ++#include <linux/wait.h> ++ ++#define FSI_ENGID_I2C_FSI 0x7 ++ ++/* Find left shift from first set bit in m */ ++#define MASK_TO_LSH(m) (__builtin_ffsll(m) - 1ULL) ++ ++/* Extract field m from v */ ++#define GETFIELD(m, v) (((v) & (m)) >> MASK_TO_LSH(m)) ++ ++/* Set field m of v to val */ ++#define SETFIELD(m, v, val) \ ++ (((v) & ~(m)) | ((((typeof(v))(val)) << MASK_TO_LSH(m)) & (m))) ++ ++#define I2C_DEFAULT_CLK_DIV 6 ++ ++/* i2c registers */ ++#define I2C_FSI_FIFO 0x00 ++#define I2C_FSI_CMD 0x04 ++#define I2C_FSI_MODE 0x08 ++#define I2C_FSI_WATER_MARK 0x0C ++#define I2C_FSI_INT_MASK 0x10 ++#define I2C_FSI_INT_COND 0x14 ++#define I2C_FSI_OR_INT_MASK 0x14 ++#define I2C_FSI_INTS 0x18 ++#define I2C_FSI_AND_INT_MASK 0x18 ++#define I2C_FSI_STAT 0x1C ++#define I2C_FSI_RESET_I2C 0x1C ++#define I2C_FSI_ESTAT 0x20 ++#define I2C_FSI_RESET_ERR 0x20 ++#define I2C_FSI_RESID_LEN 0x24 ++#define I2C_FSI_SET_SCL 0x24 ++#define I2C_FSI_PORT_BUSY 0x28 ++#define I2C_FSI_RESET_SCL 0x2C ++#define I2C_FSI_SET_SDA 0x30 ++#define I2C_FSI_RESET_SDA 0x34 ++ ++/* cmd register */ ++#define I2C_CMD_WITH_START 0x80000000 ++#define I2C_CMD_WITH_ADDR 0x40000000 ++#define I2C_CMD_RD_CONT 0x20000000 ++#define I2C_CMD_WITH_STOP 0x10000000 ++#define I2C_CMD_FORCELAUNCH 0x08000000 ++#define I2C_CMD_ADDR 0x00fe0000 ++#define I2C_CMD_READ 0x00010000 ++#define I2C_CMD_LEN 0x0000ffff ++ ++/* mode register */ ++#define I2C_MODE_CLKDIV 0xffff0000 ++#define I2C_MODE_PORT 0x0000fc00 ++#define I2C_MODE_ENHANCED 0x00000008 ++#define I2C_MODE_DIAG 0x00000004 ++#define I2C_MODE_PACE_ALLOW 0x00000002 ++#define I2C_MODE_WRAP 0x00000001 ++ ++/* watermark register */ ++#define I2C_WATERMARK_HI 0x0000f000 ++#define I2C_WATERMARK_LO 0x000000f0 ++ ++#define I2C_FIFO_HI_LVL 4 ++#define I2C_FIFO_LO_LVL 4 ++ ++/* interrupt register */ ++#define I2C_INT_INV_CMD 0x00008000 ++#define I2C_INT_PARITY 0x00004000 ++#define I2C_INT_BE_OVERRUN 0x00002000 ++#define I2C_INT_BE_ACCESS 0x00001000 ++#define I2C_INT_LOST_ARB 0x00000800 ++#define I2C_INT_NACK 0x00000400 ++#define I2C_INT_DAT_REQ 0x00000200 ++#define I2C_INT_CMD_COMP 0x00000100 ++#define I2C_INT_STOP_ERR 0x00000080 ++#define I2C_INT_BUSY 0x00000040 ++#define I2C_INT_IDLE 0x00000020 ++ ++#define I2C_INT_ENABLE 0x0000ff80 ++#define I2C_INT_ERR 0x0000fcc0 ++ ++/* status register */ ++#define I2C_STAT_INV_CMD 0x80000000 ++#define I2C_STAT_PARITY 0x40000000 ++#define I2C_STAT_BE_OVERRUN 0x20000000 ++#define I2C_STAT_BE_ACCESS 0x10000000 ++#define I2C_STAT_LOST_ARB 0x08000000 ++#define I2C_STAT_NACK 0x04000000 ++#define I2C_STAT_DAT_REQ 0x02000000 ++#define I2C_STAT_CMD_COMP 0x01000000 ++#define I2C_STAT_STOP_ERR 0x00800000 ++#define I2C_STAT_MAX_PORT 0x000f0000 ++#define I2C_STAT_ANY_INT 0x00008000 ++#define I2C_STAT_SCL_IN 0x00000800 ++#define I2C_STAT_SDA_IN 0x00000400 ++#define I2C_STAT_PORT_BUSY 0x00000200 ++#define I2C_STAT_SELF_BUSY 0x00000100 ++#define I2C_STAT_FIFO_COUNT 0x000000ff ++ ++#define I2C_STAT_ERR 0xfc800000 ++#define I2C_STAT_ANY_RESP 0xff800000 ++ ++/* extended status register */ ++#define I2C_ESTAT_FIFO_SZ 0xff000000 ++#define I2C_ESTAT_SCL_IN_SY 0x00008000 ++#define I2C_ESTAT_SDA_IN_SY 0x00004000 ++#define I2C_ESTAT_S_SCL 0x00002000 ++#define I2C_ESTAT_S_SDA 0x00001000 ++#define I2C_ESTAT_M_SCL 0x00000800 ++#define I2C_ESTAT_M_SDA 0x00000400 ++#define I2C_ESTAT_HI_WATER 0x00000200 ++#define I2C_ESTAT_LO_WATER 0x00000100 ++#define I2C_ESTAT_PORT_BUSY 0x00000080 ++#define I2C_ESTAT_SELF_BUSY 0x00000040 ++#define I2C_ESTAT_VERSION 0x0000001f ++ ++struct fsi_i2c_master { ++ struct fsi_device *fsi; ++ u8 fifo_size; ++}; ++ ++static int fsi_i2c_read_reg(struct fsi_device *fsi, unsigned int reg, ++ u32 *data) ++{ ++ int rc; ++ u32 raw_data; ++ ++ rc = fsi_device_read(fsi, reg, &raw_data, sizeof(raw_data)); ++ if (rc) ++ return rc; ++ ++ *data = be32_to_cpu(raw_data); ++ ++ return 0; ++} ++ ++static int fsi_i2c_write_reg(struct fsi_device *fsi, unsigned int reg, ++ u32 *data) ++{ ++ u32 raw_data = cpu_to_be32(*data); ++ ++ return fsi_device_write(fsi, reg, &raw_data, sizeof(raw_data)); ++} ++ ++static int fsi_i2c_dev_init(struct fsi_i2c_master *i2c) ++{ ++ int rc; ++ u32 mode = I2C_MODE_ENHANCED, extended_status, watermark = 0; ++ u32 interrupt = 0; ++ ++ /* since we use polling, disable interrupts */ ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_INT_MASK, &interrupt); ++ if (rc) ++ return rc; ++ ++ mode = SETFIELD(I2C_MODE_CLKDIV, mode, I2C_DEFAULT_CLK_DIV); ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); ++ if (rc) ++ return rc; ++ ++ rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_ESTAT, &extended_status); ++ if (rc) ++ return rc; ++ ++ i2c->fifo_size = GETFIELD(I2C_ESTAT_FIFO_SZ, extended_status); ++ watermark = SETFIELD(I2C_WATERMARK_HI, watermark, ++ i2c->fifo_size - I2C_FIFO_HI_LVL); ++ watermark = SETFIELD(I2C_WATERMARK_LO, watermark, ++ I2C_FIFO_LO_LVL); ++ ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_WATER_MARK, &watermark); ++ ++ return rc; ++} ++ ++static int fsi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, ++ int num) ++{ ++ return -ENOSYS; ++} ++ ++static u32 fsi_i2c_functionality(struct i2c_adapter *adap) ++{ ++ return I2C_FUNC_I2C | I2C_FUNC_PROTOCOL_MANGLING | I2C_FUNC_10BIT_ADDR; ++} ++ ++static const struct i2c_algorithm fsi_i2c_algorithm = { ++ .master_xfer = fsi_i2c_xfer, ++ .functionality = fsi_i2c_functionality, ++}; ++ ++static int fsi_i2c_probe(struct device *dev) ++{ ++ struct fsi_i2c_master *i2c; ++ int rc; ++ ++ i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); ++ if (!i2c) ++ return -ENOMEM; ++ ++ i2c->fsi = to_fsi_dev(dev); ++ ++ rc = fsi_i2c_dev_init(i2c); ++ if (rc) ++ return rc; ++ ++ dev_set_drvdata(dev, i2c); ++ ++ return 0; ++} ++ ++static const struct fsi_device_id fsi_i2c_ids[] = { ++ { FSI_ENGID_I2C_FSI, FSI_VERSION_ANY }, ++ { 0 } ++}; ++ ++static struct fsi_driver fsi_i2c_driver = { ++ .id_table = fsi_i2c_ids, ++ .drv = { ++ .name = "i2c_master_fsi", ++ .bus = &fsi_bus_type, ++ .probe = fsi_i2c_probe, ++ }, ++}; ++ ++module_fsi_driver(fsi_i2c_driver); ++ ++MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>"); ++MODULE_DESCRIPTION("FSI attached I2C master"); ++MODULE_LICENSE("GPL"); diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-2-6-drivers-i2c-Add-port-structure-to-FSI-algorithm.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-2-6-drivers-i2c-Add-port-structure-to-FSI-algorithm.patch new file mode 100644 index 000000000..20f66bfa7 --- /dev/null +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-2-6-drivers-i2c-Add-port-structure-to-FSI-algorithm.patch @@ -0,0 +1,194 @@ +From patchwork Wed May 10 15:52:38 2017 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [linux, dev-4.10, v2, + 2/6] drivers: i2c: Add port structure to FSI algorithm +From: eajames@linux.vnet.ibm.com +X-Patchwork-Id: 760696 +Message-Id: <1494431562-25101-3-git-send-email-eajames@linux.vnet.ibm.com> +To: openbmc@lists.ozlabs.org +Cc: "Edward A. James" <eajames@us.ibm.com>, cbostic@linux.vnet.ibm.com +Date: Wed, 10 May 2017 10:52:38 -0500 + +From: "Edward A. James" <eajames@us.ibm.com> + +Add and initialize I2C adapters for each port on the FSI-attached I2C +master. Ports are defined in the devicetree. + +Signed-off-by: Edward A. James <eajames@us.ibm.com> +--- + drivers/i2c/busses/i2c-fsi.c | 113 ++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 112 insertions(+), 1 deletion(-) + +diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c +index 3c1087d..cdebc99 100644 +--- a/drivers/i2c/busses/i2c-fsi.c ++++ b/drivers/i2c/busses/i2c-fsi.c +@@ -30,6 +30,7 @@ + #define SETFIELD(m, v, val) \ + (((v) & ~(m)) | ((((typeof(v))(val)) << MASK_TO_LSH(m)) & (m))) + ++#define I2C_MASTER_NR_OFFSET 100 + #define I2C_DEFAULT_CLK_DIV 6 + + /* i2c registers */ +@@ -131,9 +132,21 @@ + + struct fsi_i2c_master { + struct fsi_device *fsi; ++ int idx; + u8 fifo_size; ++ struct list_head ports; ++ struct ida ida; + }; + ++struct fsi_i2c_port { ++ struct list_head list; ++ struct i2c_adapter adapter; ++ struct fsi_i2c_master *master; ++ u16 port; ++}; ++ ++static DEFINE_IDA(fsi_i2c_ida); ++ + static int fsi_i2c_read_reg(struct fsi_device *fsi, unsigned int reg, + u32 *data) + { +@@ -188,9 +201,44 @@ static int fsi_i2c_dev_init(struct fsi_i2c_master *i2c) + return rc; + } + ++static int fsi_i2c_set_port(struct fsi_i2c_port *port) ++{ ++ int rc; ++ struct fsi_device *fsi = port->master->fsi; ++ u32 mode, dummy = 0; ++ u16 old_port; ++ ++ rc = fsi_i2c_read_reg(fsi, I2C_FSI_MODE, &mode); ++ if (rc) ++ return rc; ++ ++ old_port = GETFIELD(I2C_MODE_PORT, mode); ++ ++ if (old_port != port->port) { ++ mode = SETFIELD(I2C_MODE_PORT, mode, port->port); ++ rc = fsi_i2c_write_reg(fsi, I2C_FSI_MODE, &mode); ++ if (rc) ++ return rc; ++ ++ /* reset engine when port is changed */ ++ rc = fsi_i2c_write_reg(fsi, I2C_FSI_RESET_ERR, &dummy); ++ if (rc) ++ return rc; ++ } ++ ++ return rc; ++} ++ + static int fsi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) + { ++ int rc; ++ struct fsi_i2c_port *port = adap->algo_data; ++ ++ rc = fsi_i2c_set_port(port); ++ if (rc) ++ return rc; ++ + return -ENOSYS; + } + +@@ -207,13 +255,59 @@ static u32 fsi_i2c_functionality(struct i2c_adapter *adap) + static int fsi_i2c_probe(struct device *dev) + { + struct fsi_i2c_master *i2c; +- int rc; ++ struct fsi_i2c_port *port; ++ struct device_node *np; ++ int rc, idx; ++ u32 port_no; + + i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + i2c->fsi = to_fsi_dev(dev); ++ i2c->idx = ida_simple_get(&fsi_i2c_ida, 1, INT_MAX, GFP_KERNEL); ++ ida_init(&i2c->ida); ++ INIT_LIST_HEAD(&i2c->ports); ++ ++ if (dev->of_node) { ++ /* add adapter for each i2c port of the master */ ++ for_each_child_of_node(dev->of_node, np) { ++ rc = of_property_read_u32(np, "port", &port_no); ++ if (rc || port_no > 0xFFFF) ++ continue; ++ ++ /* make sure we don't overlap index with a buggy dts */ ++ idx = ida_simple_get(&i2c->ida, port_no, ++ port_no + 1, GFP_KERNEL); ++ if (idx < 0) ++ continue; ++ ++ port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); ++ if (!port) ++ return -ENOMEM; ++ ++ port->master = i2c; ++ port->port = (u16)port_no; ++ ++ port->adapter.owner = THIS_MODULE; ++ port->adapter.dev.parent = dev; ++ port->adapter.algo = &fsi_i2c_algorithm; ++ port->adapter.algo_data = port; ++ /* number ports uniquely */ ++ port->adapter.nr = (i2c->idx * I2C_MASTER_NR_OFFSET) + ++ port_no; ++ ++ snprintf(port->adapter.name, ++ sizeof(port->adapter.name), ++ "fsi_i2c-%u", port_no); ++ ++ rc = i2c_add_numbered_adapter(&port->adapter); ++ if (rc < 0) ++ return rc; ++ ++ list_add(&port->list, &i2c->ports); ++ } ++ } + + rc = fsi_i2c_dev_init(i2c); + if (rc) +@@ -224,6 +318,22 @@ static int fsi_i2c_probe(struct device *dev) + return 0; + } + ++static int fsi_i2c_remove(struct device *dev) ++{ ++ struct fsi_i2c_master *i2c = dev_get_drvdata(dev); ++ struct fsi_i2c_port *port; ++ ++ list_for_each_entry(port, &i2c->ports, list) { ++ i2c_del_adapter(&port->adapter); ++ } ++ ++ ida_destroy(&i2c->ida); ++ ++ ida_simple_remove(&fsi_i2c_ida, i2c->idx); ++ ++ return 0; ++} ++ + static const struct fsi_device_id fsi_i2c_ids[] = { + { FSI_ENGID_I2C_FSI, FSI_VERSION_ANY }, + { 0 } +@@ -235,6 +345,7 @@ static int fsi_i2c_probe(struct device *dev) + .name = "i2c_master_fsi", + .bus = &fsi_bus_type, + .probe = fsi_i2c_probe, ++ .remove = fsi_i2c_remove, + }, + }; + diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-3-6-drivers-i2c-Add-transfer-implementation-for-FSI-algorithm.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-3-6-drivers-i2c-Add-transfer-implementation-for-FSI-algorithm.patch new file mode 100644 index 000000000..50410ffc8 --- /dev/null +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-3-6-drivers-i2c-Add-transfer-implementation-for-FSI-algorithm.patch @@ -0,0 +1,243 @@ +From patchwork Wed May 10 15:52:39 2017 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [linux, dev-4.10, v2, + 3/6] drivers: i2c: Add transfer implementation for FSI algorithm +From: eajames@linux.vnet.ibm.com +X-Patchwork-Id: 760702 +Message-Id: <1494431562-25101-4-git-send-email-eajames@linux.vnet.ibm.com> +To: openbmc@lists.ozlabs.org +Cc: "Edward A. James" <eajames@us.ibm.com>, cbostic@linux.vnet.ibm.com +Date: Wed, 10 May 2017 10:52:39 -0500 + +From: "Edward A. James" <eajames@us.ibm.com> + +Execute I2C transfers from the FSI-attached I2C master. Use polling +instead of interrupts as we have no hardware IRQ over FSI. + +Signed-off-by: Edward A. James <eajames@us.ibm.com> +--- + drivers/i2c/busses/i2c-fsi.c | 193 ++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 191 insertions(+), 2 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c +index cdebc99..f690b16 100644 +--- a/drivers/i2c/busses/i2c-fsi.c ++++ b/drivers/i2c/busses/i2c-fsi.c +@@ -143,6 +143,7 @@ struct fsi_i2c_port { + struct i2c_adapter adapter; + struct fsi_i2c_master *master; + u16 port; ++ u16 xfrd; + }; + + static DEFINE_IDA(fsi_i2c_ida); +@@ -229,17 +230,205 @@ static int fsi_i2c_set_port(struct fsi_i2c_port *port) + return rc; + } + ++static int fsi_i2c_start(struct fsi_i2c_port *port, struct i2c_msg *msg, ++ bool stop) ++{ ++ int rc; ++ struct fsi_i2c_master *i2c = port->master; ++ u32 cmd = I2C_CMD_WITH_START | I2C_CMD_WITH_ADDR; ++ ++ port->xfrd = 0; ++ ++ if (msg->flags & I2C_M_RD) ++ cmd |= I2C_CMD_READ; ++ ++ if (stop || msg->flags & I2C_M_STOP) ++ cmd |= I2C_CMD_WITH_STOP; ++ ++ cmd = SETFIELD(I2C_CMD_ADDR, cmd, msg->addr >> 1); ++ cmd = SETFIELD(I2C_CMD_LEN, cmd, msg->len); ++ ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_CMD, &cmd); ++ ++ return rc; ++} ++ ++static int fsi_i2c_write_fifo(struct fsi_i2c_port *port, struct i2c_msg *msg, ++ u8 fifo_count) ++{ ++ int write; ++ int rc = 0; ++ struct fsi_i2c_master *i2c = port->master; ++ int bytes_to_write = i2c->fifo_size - fifo_count; ++ int bytes_remaining = msg->len - port->xfrd; ++ ++ if (bytes_to_write > bytes_remaining) ++ bytes_to_write = bytes_remaining; ++ ++ while (bytes_to_write > 0) { ++ write = bytes_to_write; ++ /* fsi limited to max 4 byte aligned ops */ ++ if (bytes_to_write > 4) ++ write = 4; ++ else if (write == 3) ++ write = 2; ++ ++ rc = fsi_device_write(i2c->fsi, I2C_FSI_FIFO, ++ &msg->buf[port->xfrd], write); ++ if (rc) ++ return rc; ++ ++ port->xfrd += write; ++ bytes_to_write -= write; ++ } ++ ++ return rc; ++} ++ ++static int fsi_i2c_read_fifo(struct fsi_i2c_port *port, struct i2c_msg *msg, ++ u8 fifo_count) ++{ ++ int read; ++ int rc = 0; ++ struct fsi_i2c_master *i2c = port->master; ++ int xfr_remaining = msg->len - port->xfrd; ++ u32 dummy; ++ ++ while (fifo_count) { ++ read = fifo_count; ++ /* fsi limited to max 4 byte aligned ops */ ++ if (fifo_count > 4) ++ read = 4; ++ else if (read == 3) ++ read = 2; ++ ++ if (xfr_remaining) { ++ if (xfr_remaining < read) ++ read = xfr_remaining; ++ ++ rc = fsi_device_read(i2c->fsi, I2C_FSI_FIFO, ++ &msg->buf[port->xfrd], read); ++ if (rc) ++ return rc; ++ ++ port->xfrd += read; ++ xfr_remaining -= read; ++ } else { ++ /* no more buffer but data in fifo, need to clear it */ ++ rc = fsi_device_read(i2c->fsi, I2C_FSI_FIFO, &dummy, ++ read); ++ if (rc) ++ return rc; ++ } ++ ++ fifo_count -= read; ++ } ++ ++ return rc; ++} ++ ++static int fsi_i2c_handle_status(struct fsi_i2c_port *port, ++ struct i2c_msg *msg, u32 status) ++{ ++ int rc; ++ u8 fifo_count; ++ struct fsi_i2c_master *i2c = port->master; ++ u32 dummy = 0; ++ ++ if (status & I2C_STAT_ERR) { ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); ++ if (rc) ++ return rc; ++ ++ if (status & I2C_STAT_NACK) ++ return -EFAULT; ++ ++ return -EIO; ++ } ++ ++ if (status & I2C_STAT_DAT_REQ) { ++ fifo_count = GETFIELD(I2C_STAT_FIFO_COUNT, status); ++ ++ if (msg->flags & I2C_M_RD) ++ rc = fsi_i2c_read_fifo(port, msg, fifo_count); ++ else ++ rc = fsi_i2c_write_fifo(port, msg, fifo_count); ++ ++ return rc; ++ } ++ ++ if (status & I2C_STAT_CMD_COMP) { ++ if (port->xfrd < msg->len) ++ rc = -ENODATA; ++ else ++ rc = msg->len; ++ } ++ ++ return rc; ++} ++ ++static int fsi_i2c_wait(struct fsi_i2c_port *port, struct i2c_msg *msg, ++ unsigned long timeout) ++{ ++ const unsigned long local_timeout = 2; /* jiffies */ ++ u32 status = 0; ++ int rc; ++ ++ do { ++ rc = fsi_i2c_read_reg(port->master->fsi, I2C_FSI_STAT, ++ &status); ++ if (rc) ++ return rc; ++ ++ if (status & I2C_STAT_ANY_RESP) { ++ rc = fsi_i2c_handle_status(port, msg, status); ++ if (rc < 0) ++ return rc; ++ ++ /* cmd complete and all data xfrd */ ++ if (rc == msg->len) ++ return 0; ++ ++ /* need to xfr more data, but maybe don't need wait */ ++ continue; ++ } ++ ++ set_current_state(TASK_UNINTERRUPTIBLE); ++ schedule_timeout(local_timeout); ++ timeout = (timeout < local_timeout) ? 0 : ++ timeout - local_timeout; ++ } while (timeout); ++ ++ return -ETIME; ++} ++ + static int fsi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) + { +- int rc; ++ int i, rc; ++ unsigned long start_time; + struct fsi_i2c_port *port = adap->algo_data; ++ struct i2c_msg *msg; + + rc = fsi_i2c_set_port(port); + if (rc) + return rc; + +- return -ENOSYS; ++ for (i = 0; i < num; ++i) { ++ msg = msgs + i; ++ start_time = jiffies; ++ ++ rc = fsi_i2c_start(port, msg, i == num - 1); ++ if (rc) ++ return rc; ++ ++ rc = fsi_i2c_wait(port, msg, ++ adap->timeout - (jiffies - start_time)); ++ if (rc) ++ return rc; ++ } ++ ++ return 0; + } + + static u32 fsi_i2c_functionality(struct i2c_adapter *adap) diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-4-6-drivers-i2c-Add-I2C-master-locking-to-FSI-algorithm.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-4-6-drivers-i2c-Add-I2C-master-locking-to-FSI-algorithm.patch new file mode 100644 index 000000000..e649d628f --- /dev/null +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-4-6-drivers-i2c-Add-I2C-master-locking-to-FSI-algorithm.patch @@ -0,0 +1,112 @@ +From patchwork Wed May 10 15:52:40 2017 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [linux, dev-4.10, v2, + 4/6] drivers: i2c: Add I2C master locking to FSI algorithm +From: eajames@linux.vnet.ibm.com +X-Patchwork-Id: 760699 +Message-Id: <1494431562-25101-5-git-send-email-eajames@linux.vnet.ibm.com> +To: openbmc@lists.ozlabs.org +Cc: "Edward A. James" <eajames@us.ibm.com>, cbostic@linux.vnet.ibm.com +Date: Wed, 10 May 2017 10:52:40 -0500 + +From: "Edward A. James" <eajames@us.ibm.com> + +Since there are many ports per master, each with it's own adapter and +chardev, we need some locking to prevent xfers from changing the master +state while other xfers are in progress. + +Signed-off-by: Edward A. James <eajames@us.ibm.com> +--- + drivers/i2c/busses/i2c-fsi.c | 41 +++++++++++++++++++++++++++++++++++++---- + 1 file changed, 37 insertions(+), 4 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c +index f690b16..d757aee 100644 +--- a/drivers/i2c/busses/i2c-fsi.c ++++ b/drivers/i2c/busses/i2c-fsi.c +@@ -136,6 +136,8 @@ struct fsi_i2c_master { + u8 fifo_size; + struct list_head ports; + struct ida ida; ++ wait_queue_head_t wait; ++ struct semaphore lock; + }; + + struct fsi_i2c_port { +@@ -171,6 +173,29 @@ static int fsi_i2c_write_reg(struct fsi_device *fsi, unsigned int reg, + return fsi_device_write(fsi, reg, &raw_data, sizeof(raw_data)); + } + ++static int fsi_i2c_lock_master(struct fsi_i2c_master *i2c, int timeout) ++{ ++ int rc; ++ ++ rc = down_trylock(&i2c->lock); ++ if (!rc) ++ return 0; ++ ++ rc = wait_event_interruptible_timeout(i2c->wait, ++ !down_trylock(&i2c->lock), ++ timeout); ++ if (rc > 0) ++ return 0; ++ ++ return -EBUSY; ++} ++ ++static void fsi_i2c_unlock_master(struct fsi_i2c_master *i2c) ++{ ++ up(&i2c->lock); ++ wake_up(&i2c->wait); ++} ++ + static int fsi_i2c_dev_init(struct fsi_i2c_master *i2c) + { + int rc; +@@ -410,25 +435,31 @@ static int fsi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + struct fsi_i2c_port *port = adap->algo_data; + struct i2c_msg *msg; + +- rc = fsi_i2c_set_port(port); ++ rc = fsi_i2c_lock_master(port->master, adap->timeout); + if (rc) + return rc; + ++ rc = fsi_i2c_set_port(port); ++ if (rc) ++ goto unlock; ++ + for (i = 0; i < num; ++i) { + msg = msgs + i; + start_time = jiffies; + + rc = fsi_i2c_start(port, msg, i == num - 1); + if (rc) +- return rc; ++ goto unlock; + + rc = fsi_i2c_wait(port, msg, + adap->timeout - (jiffies - start_time)); + if (rc) +- return rc; ++ goto unlock; + } + +- return 0; ++unlock: ++ fsi_i2c_unlock_master(port->master); ++ return rc; + } + + static u32 fsi_i2c_functionality(struct i2c_adapter *adap) +@@ -453,6 +484,8 @@ static int fsi_i2c_probe(struct device *dev) + if (!i2c) + return -ENOMEM; + ++ init_waitqueue_head(&i2c->wait); ++ sema_init(&i2c->lock, 1); + i2c->fsi = to_fsi_dev(dev); + i2c->idx = ida_simple_get(&fsi_i2c_ida, 1, INT_MAX, GFP_KERNEL); + ida_init(&i2c->ida); diff --git a/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-5-6-drivers-i2c-Add-bus-recovery-for-FSI-algorithm.patch b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-5-6-drivers-i2c-Add-bus-recovery-for-FSI-algorithm.patch new file mode 100644 index 000000000..f9fc75d4a --- /dev/null +++ b/meta-phosphor/common/recipes-kernel/linux/linux-obmc/linux-dev-4.10-v2-5-6-drivers-i2c-Add-bus-recovery-for-FSI-algorithm.patch @@ -0,0 +1,117 @@ +From patchwork Wed May 10 15:52:41 2017 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [linux, dev-4.10, v2, + 5/6] drivers: i2c: Add bus recovery for FSI algorithm +From: eajames@linux.vnet.ibm.com +X-Patchwork-Id: 760701 +Message-Id: <1494431562-25101-6-git-send-email-eajames@linux.vnet.ibm.com> +To: openbmc@lists.ozlabs.org +Cc: "Edward A. James" <eajames@us.ibm.com>, cbostic@linux.vnet.ibm.com +Date: Wed, 10 May 2017 10:52:41 -0500 + +From: "Edward A. James" <eajames@us.ibm.com> + +Bus recovery should reset the engine and force block the bus 9 times +to recover most situations. + +Signed-off-by: Edward A. James <eajames@us.ibm.com> +--- + drivers/i2c/busses/i2c-fsi.c | 76 ++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 76 insertions(+) + +diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c +index d757aee..4689479 100644 +--- a/drivers/i2c/busses/i2c-fsi.c ++++ b/drivers/i2c/busses/i2c-fsi.c +@@ -467,6 +467,80 @@ static u32 fsi_i2c_functionality(struct i2c_adapter *adap) + return I2C_FUNC_I2C | I2C_FUNC_PROTOCOL_MANGLING | I2C_FUNC_10BIT_ADDR; + } + ++static int fsi_i2c_low_level_recover_bus(struct fsi_i2c_master *i2c) ++{ ++ int i, rc; ++ u32 mode, dummy = 0; ++ ++ rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); ++ if (rc) ++ return rc; ++ ++ mode |= I2C_MODE_DIAG; ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); ++ if (rc) ++ return rc; ++ ++ for (i = 0; i < 9; ++i) { ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); ++ if (rc) ++ return rc; ++ ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); ++ if (rc) ++ return rc; ++ } ++ ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); ++ if (rc) ++ return rc; ++ ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SDA, &dummy); ++ if (rc) ++ return rc; ++ ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); ++ if (rc) ++ return rc; ++ ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SDA, &dummy); ++ if (rc) ++ return rc; ++ ++ mode &= ~I2C_MODE_DIAG; ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); ++ ++ return rc; ++} ++ ++static int fsi_i2c_recover_bus(struct i2c_adapter *adap) ++{ ++ int rc; ++ u32 dummy = 0; ++ struct fsi_i2c_port *port = adap->algo_data; ++ struct fsi_i2c_master *i2c = port->master; ++ ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); ++ if (rc) ++ return rc; ++ ++ rc = fsi_i2c_dev_init(i2c); ++ if (rc) ++ return rc; ++ ++ rc = fsi_i2c_low_level_recover_bus(i2c); ++ if (rc) ++ return rc; ++ ++ rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); ++ ++ return rc; ++} ++ ++static struct i2c_bus_recovery_info fsi_i2c_bus_recovery_info = { ++ .recover_bus = fsi_i2c_recover_bus, ++}; ++ + static const struct i2c_algorithm fsi_i2c_algorithm = { + .master_xfer = fsi_i2c_xfer, + .functionality = fsi_i2c_functionality, +@@ -514,6 +588,8 @@ static int fsi_i2c_probe(struct device *dev) + port->adapter.owner = THIS_MODULE; + port->adapter.dev.parent = dev; + port->adapter.algo = &fsi_i2c_algorithm; ++ port->adapter.bus_recovery_info = ++ &fsi_i2c_bus_recovery_info; + port->adapter.algo_data = port; + /* number ports uniquely */ + port->adapter.nr = (i2c->idx * I2C_MASTER_NR_OFFSET) + |