Add Juniper's PTX1K Boot FPGA driver. Those FPGAs
are present in Juniper's PTX series of routers.
The MFD driver provices watchdog/mtd/hwmon devices.
There are full device tree binding documents for the
master mfd driver and for the slave driver.
This patchset is against mainline as of today: v4.8-9431-g3477d16
and is dependent on the "Juniper prerequisites" and
"Juniper infrastructure" patchsets sent earlier.
Georgi Vlaev (7):
mfd: Add PTX1K I2CS BootFPGA MFD driver
mfd: dt-bindings: Add bindings for the Juniper PTX1K Boot FPGA
watchdog: Add PTX1K I2CS BootFPGA watchdog driver.
watchdog: ptx1kbf-wdt: Add ptx1kbf device tree bindings
mtd: ptx1kbf: Add PTX1K BootFPGA MTD driver
mtd: ptx1kbf: Bindings for Juniper's PTX1K Boot FPGA
hwmon: ptx1kbf: Add PTX1K I2CS BootFPGA sensor driver
.../devicetree/bindings/mfd/jnx-ptx1k-bootfpga.txt | 34 ++
.../devicetree/bindings/mtd/ptx1kbf-mtd.txt | 23 +
.../bindings/watchdog/jnx-ptx1kbf-wdt.txt | 17 +
drivers/hwmon/Kconfig | 17 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/jnx_ptx1kbf_hwmon.c | 123 ++++
drivers/mfd/Kconfig | 11 +
drivers/mfd/Makefile | 1 +
drivers/mfd/ptx1k-bootfpga.c | 462 ++++++++++++++
drivers/mtd/devices/Kconfig | 10 +
drivers/mtd/devices/Makefile | 1 +
drivers/mtd/devices/jnx_ptx1kbf_mtd.c | 677 +++++++++++++++++++++
drivers/watchdog/Kconfig | 15 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/jnx_ptx1kbf_wdt.c | 229 +++++++
include/linux/mfd/ptx1k-bootfpga.h | 93 +++
16 files changed, 1715 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/jnx-ptx1k-bootfpga.txt
create mode 100644 Documentation/devicetree/bindings/mtd/ptx1kbf-mtd.txt
create mode 100644 Documentation/devicetree/bindings/watchdog/jnx-ptx1kbf-wdt.txt
create mode 100644 drivers/hwmon/jnx_ptx1kbf_hwmon.c
create mode 100644 drivers/mfd/ptx1k-bootfpga.c
create mode 100644 drivers/mtd/devices/jnx_ptx1kbf_mtd.c
create mode 100644 drivers/watchdog/jnx_ptx1kbf_wdt.c
create mode 100644 include/linux/mfd/ptx1k-bootfpga.h
--
1.9.1
From: Georgi Vlaev <[email protected]>
The drivers allows reading CPU, PCH and DIMM modules teperatures
from the I2CS FPGA via the SMLink1 to the PCH.
Signed-off-by: Georgi Vlaev <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
drivers/hwmon/Kconfig | 17 ++++++
drivers/hwmon/Makefile | 1 +
drivers/hwmon/jnx_ptx1kbf_hwmon.c | 123 ++++++++++++++++++++++++++++++++++++++
3 files changed, 141 insertions(+)
create mode 100644 drivers/hwmon/jnx_ptx1kbf_hwmon.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b9348d2..5b66c7b 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -674,6 +674,23 @@ config SENSORS_JNX_FAN
This driver can also be built as a module. If so, the module
will be called jnx-fan.
+config SENSORS_JNX_PTX1KBF
+ tristate "Juniper Networks PTX1000 I2CS BootFPGA thermal sensors"
+ depends on MFD_JUNIPER_PTX1KBF
+ help
+ If you say yes here you get support for the hardware
+ monitoring features of the Juniper Networks PTX1000 I2CS BootFPGA
+ on the LPC bus. The CPU's digital thermal sensor can be read over
+ the PECI interface. PECI is connected to the PCH and the PCH's
+ Management Engine (ME) can be configured to poll CPU and DIMM
+ temperatures over the PECI interface. Then, it is possible for an
+ external I2C master (in the I2CS FPGA) to read CPU, DIMM and PCH
+ temperatures from the PCH over SMLink1 (which is connected to the board
+ I2C tree).
+
+ This driver can also be built as a module. If so, the module
+ will be called jnx_ptx1kbf_hwmon.
+
config SENSORS_POWR1220
tristate "Lattice POWR1220 Power Monitoring"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index eea631e..a3dedef 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -82,6 +82,7 @@ obj-$(CONFIG_SENSORS_INA3221) += ina3221.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
obj-$(CONFIG_SENSORS_JC42) += jc42.o
obj-$(CONFIG_SENSORS_JNX_FAN) += jnx-fan.o
+obj-$(CONFIG_SENSORS_JNX_PTX1KBF) += jnx_ptx1kbf_hwmon.o
obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
diff --git a/drivers/hwmon/jnx_ptx1kbf_hwmon.c b/drivers/hwmon/jnx_ptx1kbf_hwmon.c
new file mode 100644
index 0000000..a9d6562
--- /dev/null
+++ b/drivers/hwmon/jnx_ptx1kbf_hwmon.c
@@ -0,0 +1,123 @@
+/*
+ * Juniper Networks PTX1K RCB I2CS Boot FPGA hwmon driver
+ *
+ * Copyright (C) 2014 Juniper Networks. All rights reserved.
+ * Author: Georgi Vlaev <[email protected]>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mfd/ptx1k-bootfpga.h>
+
+#define DRVNAME "jnx-ptx1kbf-hwmon"
+
+struct bf_hwmon {
+ void __iomem *base;
+};
+
+static const char *const bf_temp_label[] = {
+ "CPU", "PCH", "DIMM1", "DIMM2"
+};
+
+static ssize_t bf_show_temp(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct bf_hwmon *hwmon = dev_get_drvdata(dev);
+ int channel = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%d\n", be16_to_cpu(ioread16(hwmon->base +
+ BOOT_FPGA_CPU_TEMP_MSB + (channel * 2))));
+}
+
+static ssize_t bf_show_label(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int channel = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%s\n", bf_temp_label[channel]);
+}
+
+/* CPU Temp */
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, bf_show_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, bf_show_label, NULL, 0);
+/* PCH Temp */
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, bf_show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, bf_show_label, NULL, 1);
+/* DIMM1 Temp */
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, bf_show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, bf_show_label, NULL, 2);
+/* DIMM2 Temp */
+static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, bf_show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, bf_show_label, NULL, 3);
+
+static struct attribute *bf_attrs[] = {
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_label.dev_attr.attr,
+ &sensor_dev_attr_temp2_input.dev_attr.attr,
+ &sensor_dev_attr_temp2_label.dev_attr.attr,
+ &sensor_dev_attr_temp3_input.dev_attr.attr,
+ &sensor_dev_attr_temp3_label.dev_attr.attr,
+ &sensor_dev_attr_temp4_input.dev_attr.attr,
+ &sensor_dev_attr_temp4_label.dev_attr.attr,
+ NULL
+};
+
+ATTRIBUTE_GROUPS(bf);
+
+static int bf_hwmon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device *hwmon_dev;
+ struct bf_hwmon *hwmon;
+ struct resource *mem;
+
+ hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
+ if (!hwmon)
+ return -ENOMEM;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
+ return -ENOENT;
+ }
+
+ hwmon->base = devm_ioremap_nocache(dev, mem->start, resource_size(mem));
+ if (IS_ERR(hwmon->base)) {
+ dev_err(dev, "Failed to ioremap mmio memory\n");
+ return PTR_ERR(hwmon->base);
+ }
+
+ hwmon_dev = devm_hwmon_device_register_with_groups(dev, "jnx_ptx1kbf",
+ hwmon, bf_groups);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver bf_hwmon_driver = {
+ .probe = bf_hwmon_probe,
+ .driver = {
+ .name = DRVNAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(bf_hwmon_driver);
+
+MODULE_DESCRIPTION("Juniper Networks PTX1K RCB I2CS Boot FPGA hwmon driver");
+MODULE_AUTHOR("Georgi Vlaev <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRVNAME);
--
1.9.1
From: Georgi Vlaev <[email protected]>
Add binding document for Junipers Flash IP block present
in the PTX1K Boot FPGA on PTX series of routers.
Signed-off-by: Georgi Vlaev <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
.../devicetree/bindings/mtd/ptx1kbf-mtd.txt | 23 ++++++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/ptx1kbf-mtd.txt
diff --git a/Documentation/devicetree/bindings/mtd/ptx1kbf-mtd.txt b/Documentation/devicetree/bindings/mtd/ptx1kbf-mtd.txt
new file mode 100644
index 0000000..4e25060
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/ptx1kbf-mtd.txt
@@ -0,0 +1,23 @@
+Flash device on a PTX1K Boot FPGA
+
+These flash chips are found in the PTX series of Juniper routers.
+
+Required properties:
+- compatible : must be "jnx,flash-sam"
+
+Optional properties:
+- reg : memory address for the flash chip, note that this is not
+required since usually the device is a subdevice of the SAM MFD
+driver which fills in the register fields.
+
+For the rest of the properties, see mtd-physmap.txt.
+
+The device tree may optionally contain sub-nodes describing partitions of the
+address space. See partition.txt for more detail.
+
+Example:
+
+ptx1kbf_mtd {
+ compatible = "jnx,ptx1kbf-mtd";
+ /* usually the whole flash is used */
+};
--
1.9.1
From: Georgi Vlaev <[email protected]>
This driver allows using the I2CS watchdog on PTX1K. The
BootFPGA implements a watchdog that allows setting timeout
from predefined set of values from 15 msec to 491 sec.
Signed-off-by: Georgi Vlaev <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
Signed-off-by: JawaharBalaji Thirumalaisamy <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
drivers/watchdog/Kconfig | 15 +++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/jnx_ptx1kbf_wdt.c | 229 +++++++++++++++++++++++++++++++++++++
3 files changed, 245 insertions(+)
create mode 100644 drivers/watchdog/jnx_ptx1kbf_wdt.c
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 2554f47..a3761eb 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1033,6 +1033,21 @@ config IT87_WDT
To compile this driver as a module, choose M here: the module will
be called it87_wdt.
+config JNX_PTX1KBF_WDT
+ tristate "Juniper Networks PTX1000 I2CS BootFPGA Watchdog"
+ depends on X86 && MFD_JUNIPER_PTX1KBF
+ ---help---
+ This is the driver for the watchdog timer built-in on the Juniper
+ Networks PTX1000 I2CS/BootFPGA and acessible from the LPC bus. The
+ BootFPGA watchdog allows several predefined thresholds, so this
+ driver will select a value close to these static values: 15 ms,
+ 30 ms, 60 ms, 120 ms, 240 ms, 480 ms, 960 ms, 1920 ms, 3840 ms,
+ 7680 ms, 15360 ms, 30720 ms, 61440 ms, 122880 ms, 245760 ms and
+ 516096 ms.
+
+ To compile this driver as a module, choose M here: the
+ module will be called jnx_ptx1kbf_wdt.
+
config HP_WATCHDOG
tristate "HP ProLiant iLO2+ Hardware Watchdog Timer"
depends on X86 && PCI
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 942b795..f9fafdf 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -111,6 +111,7 @@ endif
obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
obj-$(CONFIG_IT87_WDT) += it87_wdt.o
obj-$(CONFIG_JNX_PTXPMB_WDT) += ptxpmb_wdt.o
+obj-$(CONFIG_JNX_PTX1KBF_WDT) += jnx_ptx1kbf_wdt.o
obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
diff --git a/drivers/watchdog/jnx_ptx1kbf_wdt.c b/drivers/watchdog/jnx_ptx1kbf_wdt.c
new file mode 100644
index 0000000..7d3e0b7
--- /dev/null
+++ b/drivers/watchdog/jnx_ptx1kbf_wdt.c
@@ -0,0 +1,229 @@
+/*
+ * Juniper Networks PTX1K RCB I2CS Boot FPGA watchdog driver
+ *
+ * Copyright (C) 2014 Juniper Networks. All rights reserved.
+ * Author: Georgi Vlaev <[email protected]>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/watchdog.h>
+#include <linux/mfd/ptx1k-bootfpga.h>
+
+#define WDT_MIN_TIMEOUT 1
+#define WDT_MAX_TIMEOUT 491
+#define WDT_DEFAULT_TIMEOUT 60 /* default heartbeat in seconds */
+
+#define DRVNAME "jnx-ptx1kbf-wdt"
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+
+#define MSW_NOWAYOUT __MODULE_STRING(WATCHDOG_NOWAYOUT)
+MODULE_PARM_DESC(nowayout,
+ "Cannot be stopped once started (def=" MSW_NOWAYOUT ")");
+
+struct ptx1kbf_wdt {
+ void __iomem *base;
+ struct watchdog_device wdt_dev;
+};
+
+/*
+ * The BOOT_FPGA_WATCHDOG_TIMER_THRESHOLD register defines the watchdog
+ * timer thresholds in ms: 0x0 = 15 ms, 0x1 = 30 ms, ... 0xD = 122880 ms,
+ * 0xE = 245760 ms, 0xF = 492520 ms, or value = (2^index * 15)
+ */
+static int ptx1kbf_index_to_timeout(u8 reg)
+{
+ return ((1 << (reg & WTT_THRESHOLD_MASK)) * 15) / 1000;
+}
+
+static u8 ptx1kbf_timeout_to_index(unsigned int timeout)
+{
+ return fls(timeout * 1000 / 15) & WTT_THRESHOLD_MASK;
+}
+
+static int ptx1kbf_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct ptx1kbf_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ u8 reg, index;
+
+ if (timeout > WDT_MAX_TIMEOUT)
+ return -EINVAL;
+
+ index = ptx1kbf_timeout_to_index(timeout);
+
+ reg = ioread8(wdt->base + BOOT_FPGA_WATCHDOG_TIMER_THRESHOLD);
+ reg &= ~WTT_THRESHOLD_MASK;
+ reg |= index;
+ iowrite8(reg, wdt->base + BOOT_FPGA_WATCHDOG_TIMER_THRESHOLD);
+
+ /* Set the actual timeout supported by the watchdog */
+ wdt->wdt_dev.timeout = ptx1kbf_index_to_timeout(index);
+
+ return 0;
+}
+
+static int ptx1kbf_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ return ptx1kbf_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+}
+
+static int ptx1kbf_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct ptx1kbf_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ int ret;
+ u8 reg;
+
+ ret = ptx1kbf_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+ if (ret < 0)
+ return ret;
+
+ reg = ioread8(wdt->base + BOOT_FPGA_BOOT_CONTROL);
+ reg |= BC_WDT_ENA;
+ iowrite8(reg, wdt->base + BOOT_FPGA_BOOT_CONTROL);
+
+ return 0;
+}
+
+static int ptx1kbf_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct ptx1kbf_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ u8 reg;
+
+ reg = ioread8(wdt->base + BOOT_FPGA_BOOT_CONTROL);
+ reg &= ~BC_WDT_ENA;
+ iowrite8(reg, wdt->base + BOOT_FPGA_BOOT_CONTROL);
+
+ return 0;
+}
+
+static struct watchdog_info ptx1kbf_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .identity = "PTX1K BootFPGA Watchdog",
+ .firmware_version = 0,
+};
+
+static const struct watchdog_ops ptx1kbf_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = ptx1kbf_wdt_start,
+ .stop = ptx1kbf_wdt_stop,
+ .ping = ptx1kbf_wdt_ping,
+ .set_timeout = ptx1kbf_wdt_set_timeout,
+};
+
+static int ptx1kbf_wdt_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *res;
+ struct ptx1kbf_wdt *wdt;
+ u8 reset_reason1, reset_reason2;
+ unsigned int timeout;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ wdt->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ platform_set_drvdata(pdev, wdt);
+ wdt->wdt_dev.info = &ptx1kbf_wdt_info;
+ wdt->wdt_dev.ops = &ptx1kbf_wdt_ops;
+ wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
+ wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
+ wdt->wdt_dev.timeout = WDT_DEFAULT_TIMEOUT;
+ wdt->wdt_dev.parent = &pdev->dev;
+
+ reset_reason1 = ioread8(wdt->base + BOOT_FPGA_RESET_REASON1);
+ reset_reason2 = ioread8(wdt->base + BOOT_FPGA_RESET_REASON2);
+ if (reset_reason1 & RR1_MSTR)
+ wdt->wdt_dev.bootstatus |= WDIOF_EXTERN1;
+ else if (reset_reason1 & RR1_BUTTON)
+ wdt->wdt_dev.bootstatus |= WDIOF_EXTERN2;
+ else if (reset_reason1 & RR1_WDOG)
+ wdt->wdt_dev.bootstatus |= WDIOF_CARDRESET;
+ else if (reset_reason1 & RR1_POWER_FAIL)
+ wdt->wdt_dev.bootstatus |= WDIOF_POWERUNDER;
+ else if (reset_reason1 & RR1_THERM_TRIP)
+ wdt->wdt_dev.bootstatus |= WDIOF_OVERHEAT;
+ else if (reset_reason1 & RR2_AUTO_POWER_OFF)
+ wdt->wdt_dev.bootstatus |= WDIOF_FANFAULT;
+
+ /* Report the FPGA/WDT version */
+ ptx1kbf_wdt_info.firmware_version =
+ ioread8(wdt->base + BOOT_FPGA_VERSION);
+
+ /*
+ * The PTX1K BIOS configures the timeout when the FPGA watchdog
+ * is enabled. Index values < 0x07 set thresholds bellow 1s,
+ * so we can ignore them
+ */
+ timeout = ptx1kbf_index_to_timeout(ioread8(wdt->base +
+ BOOT_FPGA_WATCHDOG_TIMER_THRESHOLD));
+
+ watchdog_init_timeout(&wdt->wdt_dev, timeout, &pdev->dev);
+ watchdog_set_nowayout(&wdt->wdt_dev, nowayout);
+ watchdog_set_drvdata(&wdt->wdt_dev, wdt);
+
+ /* Stop the watchdog if BIOS has enabled it */
+ ptx1kbf_wdt_stop(&wdt->wdt_dev);
+
+ ret = watchdog_register_device(&wdt->wdt_dev);
+ if (ret)
+ return ret;
+
+ dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
+ wdt->wdt_dev.timeout, nowayout);
+
+ return 0;
+}
+
+static int ptx1kbf_wdt_remove(struct platform_device *pdev)
+{
+ struct ptx1kbf_wdt *wdt = platform_get_drvdata(pdev);
+
+ watchdog_unregister_device(&wdt->wdt_dev);
+
+ return 0;
+}
+
+static const struct of_device_id ptx1kbf_wdt_of_ids[] = {
+ { .compatible = "jnx,ptx1kbf-wdt", NULL },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ptx1kbf_wdt_of_ids);
+
+static struct platform_driver ptx1kbf_wdt_driver = {
+ .probe = ptx1kbf_wdt_probe,
+ .remove = ptx1kbf_wdt_remove,
+ .driver = {
+ .name = DRVNAME,
+ .owner = THIS_MODULE,
+ .of_match_table = ptx1kbf_wdt_of_ids,
+ },
+};
+
+module_platform_driver(ptx1kbf_wdt_driver);
+
+MODULE_DESCRIPTION("Juniper Networks PTX1K RCB I2CS Boot FPGA watchdog driver");
+MODULE_AUTHOR("Georgi Vlaev <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRVNAME);
--
1.9.1
From: Georgi Vlaev <[email protected]>
Add binding document for the watchdog driver of PTX1K Boot FPGA.
Signed-off-by: Georgi Vlaev <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
.../devicetree/bindings/watchdog/jnx-ptx1kbf-wdt.txt | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100644 Documentation/devicetree/bindings/watchdog/jnx-ptx1kbf-wdt.txt
diff --git a/Documentation/devicetree/bindings/watchdog/jnx-ptx1kbf-wdt.txt b/Documentation/devicetree/bindings/watchdog/jnx-ptx1kbf-wdt.txt
new file mode 100644
index 0000000..0aa8086
--- /dev/null
+++ b/Documentation/devicetree/bindings/watchdog/jnx-ptx1kbf-wdt.txt
@@ -0,0 +1,17 @@
+Juniper's PTX1K Boot FPGA watchdog driver
+
+Required properties:
+
+- compatible: Should be "jnx,ptx1kbf-wdt"
+
+Optional properties:
+
+- reg : Specifies base physical address and size of the registers. It is
+ optional since the MFD parent driver supplies it, but can be overridden.
+
+Example:
+
+wdt {
+ compatible = "jnx,ptx1kbf-wdt";
+ /* no properties defined */
+};
--
1.9.1
From: Georgi Vlaev <[email protected]>
This patch adds a MTD driver for configuration updates of the
Xilinx Spartan 3AN based FPGAs on the PTX1K RE boards. The
driver is client of the ptx1k-bootfpga driver.
Signed-off-by: Georgi Vlaev <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
drivers/mtd/devices/Kconfig | 10 +
drivers/mtd/devices/Makefile | 1 +
drivers/mtd/devices/jnx_ptx1kbf_mtd.c | 677 ++++++++++++++++++++++++++++++++++
3 files changed, 688 insertions(+)
create mode 100644 drivers/mtd/devices/jnx_ptx1kbf_mtd.c
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index f5a9032..25a1c4a 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -155,6 +155,16 @@ config MTD_SAM_FLASH
This driver can also be built as a module. When it is so the name of
the module is flash-sam.
+config MTD_JNX_PTX1KBF
+ tristate "Juniper I2CS BootFPGA MTD driver"
+ depends on MFD_JUNIPER_PTX1KBF
+ help
+ This enables the MTD driver for the Juniper I2CS BootFPGA.
+ The driver is used to perform software upgrades of the RE's
+ Spartan 3AN XC3S50AN, XC3S200AN, XCS400AN, XC3S700AN and
+ XC3S1400AN based I2CS. The I2CS BootFPGA must implement the
+ remote upgrade software interface.
+
config JNX_PMB_NVRAM
tristate "Juniper FPC PMB NVRAM Driver"
depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index 7556311..e1adfa5 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_MTD_ST_SPI_FSM) += st_spi_fsm.o
obj-$(CONFIG_MTD_POWERNV_FLASH) += powernv_flash.o
obj-$(CONFIG_MTD_SAM_FLASH) += sam-flash.o
+obj-$(CONFIG_MTD_JNX_PTX1KBF) += jnx_ptx1kbf_mtd.o
obj-$(CONFIG_JNX_PMB_NVRAM) += jnx_pmb_nvram.o
CFLAGS_docg3.o += -I$(src)
diff --git a/drivers/mtd/devices/jnx_ptx1kbf_mtd.c b/drivers/mtd/devices/jnx_ptx1kbf_mtd.c
new file mode 100644
index 0000000..3bc17be
--- /dev/null
+++ b/drivers/mtd/devices/jnx_ptx1kbf_mtd.c
@@ -0,0 +1,677 @@
+/*
+ * Juniper Networks PTX1K RCB I2CS Boot FPGA MTD driver
+ * FPGA upgrades of the Spartan3AN/XC3S700 based I2CS.
+ *
+ * Copyright (C) 2015 Juniper Networks. All rights reserved.
+ * Author: Georgi Vlaev <[email protected]>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mfd/ptx1k-bootfpga.h>
+
+#define DEFAULT_BUSY_TIMEOUT 5 /* default timeout (msec) */
+#define PAGE_BUFFER_TXFER 400 /* page->sram transfer (usec) */
+
+/* FLASH_IF_CONTROL commands */
+#define CONTROL_READ_SID 0x80
+#define CONTROL_READ_DID 0x04
+#define CONTROL_READ_STATUS 0x08
+#define CONTROL_SECTOR_ERASE 0x20
+#define CONTROL_PAGE_ERASE 0x40
+#define CONTROL_PAGE_WRITE 0x02
+#define CONTROL_PAGE_READ 0x01
+#define CONTROL_SP_READ 0x02
+#define CONTROL_SP_WRITE 0x08
+#define CONTROL_SP_UALL 0x01
+
+/* FLASH_IF_STATUS bits */
+#define STATUS_BUSY BIT(0)
+#define STATUS_ILLEGAL_WRITE BIT(1)
+#define STATUS_ILLEGAL_ERASE BIT(2)
+#define STATUS_ILLEGAL (STATUS_ILLEGAL_WRITE | STATUS_ILLEGAL_ERASE)
+#define STATUS_POWER_2_ADDR BIT(3)
+#define STATUS_SID_I2CS 0x1F /* I2CS Spartan3AN FPGA */
+
+/* RU_CONFIG_CONTROL_STATUS bits */
+#define RU_CCS_USER_IMAGE BIT(7)
+#define RU_CCS_RECONFIG BIT(5)
+
+#define MASK_SPARE_PAGE 0x1f
+#define MASK_SPARE_SECTOR 0x1fff
+
+#define SECTOR_UNPROTECT 0x00
+#define SECTOR_PROTECT 0xff
+
+struct bfmtd_info {
+ const char *name;
+ u8 device_id; /* flash id as reported by the fpga */
+ size_t flash_size; /* total flash size */
+ size_t page_size; /* read/write page size */
+ size_t sector_size; /* erase sector size */
+ size_t nr_pages; /* total number of pages */
+ size_t nr_sectors; /* total number of sectors */
+ size_t writesize; /* write size */
+ size_t writebufsize; /* internal fpga sram buffer size (bytes) */
+ size_t spare_size; /* additional bytes per page in 'default' mode*/
+ /*
+ * Timeouts are defined in the Xilinx "In-System Flash (ISF) Upgrade
+ * User Guide". Our values will add 20% on top of ISF ones
+ */
+ u16 page_erase_tmo; /* page erase timeout (msec) */
+ u16 sector_erase_tmo; /* sector erase timeout (msec) */
+ u16 buffer_program_tmo; /* sram buffer program timeout (msec) */
+};
+
+/*
+ * Definitions in power-of-2 addressing mode.
+ * Reconfigure sizes if default mode is detected.
+ */
+static struct bfmtd_info bfmtd_info_db[] = {
+ {
+ .name = "XC3S50AN",
+ .device_id = 0x22,
+ .flash_size = 256 * 512,
+ .page_size = 256,
+ .nr_pages = 512,
+ .nr_sectors = 4,
+ .sector_size = 256 * 128,
+ .writesize = 1,
+ .writebufsize = 256,
+ .spare_size = 8,
+ .page_erase_tmo = 38,
+ .sector_erase_tmo = 3000,
+ .buffer_program_tmo = 5,
+ },
+ {
+ .name = "XC3S200AN/XC3S400AN",
+ .device_id = 0x24,
+ .flash_size = 256 * 2048,
+ .page_size = 256,
+ .nr_pages = 2048,
+ .nr_sectors = 8,
+ .sector_size = 256 * 256,
+ .writesize = 1,
+ .writebufsize = 256,
+ .spare_size = 8,
+ .page_erase_tmo = 38,
+ .sector_erase_tmo = 6000,
+ .buffer_program_tmo = 5,
+ },
+ {
+ .name = "XC3S700AN",
+ .device_id = 0x25,
+ .flash_size = 256 * 4096,
+ .page_size = 256,
+ .nr_pages = 4096,
+ .nr_sectors = 16,
+ .sector_size = 256 * 256,
+ .writesize = 1,
+ .writebufsize = 256,
+ .spare_size = 8,
+ .page_erase_tmo = 42,
+ .sector_erase_tmo = 6000,
+ .buffer_program_tmo = 8,
+ },
+ {
+ .name = "XC3S1400AN",
+ .device_id = 0x26,
+ .flash_size = 512 * 4096,
+ .page_size = 512,
+ .nr_pages = 4096,
+ .nr_sectors = 16,
+ .sector_size = 512 * 256,
+ .writesize = 1,
+ .writebufsize = 512,
+ .spare_size = 8,
+ .page_erase_tmo = 42,
+ .sector_erase_tmo = 6000,
+ .buffer_program_tmo = 8,
+ },
+};
+
+struct bf_mtd {
+ void __iomem *base;
+ struct device *dev;
+ struct mtd_info mtd;
+ struct bfmtd_info *info;
+ struct mutex lock;
+};
+
+/*
+ * bfmtd_busy_wait()
+ * Wait for busy[0] flag in the FLASH_IF_STATUS register
+ */
+static int bfmtd_busy_wait(struct bf_mtd *bfmtd, unsigned long max_wait)
+{
+ u8 status;
+ unsigned long timeout = jiffies + msecs_to_jiffies(max_wait);
+
+ udelay(50);
+ do {
+ status = ioread8(bfmtd->base + BOOT_FPGA_FLASH_IF_STATUS(3));
+ if (!(status & STATUS_BUSY))
+ return 0;
+
+ if (status & STATUS_ILLEGAL)
+ return -EACCES;
+
+ usleep_range(50, 100);
+ } while (time_before(jiffies, timeout));
+
+ return -ETIMEDOUT;
+}
+
+/*
+ * bfmtd_read_status()
+ * Read FLASH_IF_STATUS regsters
+ */
+static int bfmtd_read_status(struct bf_mtd *bfmtd, u8 control_cmd,
+ u8 status_index)
+{
+ int ret;
+
+ iowrite8(control_cmd,
+ bfmtd->base + BOOT_FPGA_FLASH_IF_CONTROL(3));
+
+ ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT);
+ if (ret)
+ return ret;
+
+ return ioread8(bfmtd->base +
+ BOOT_FPGA_FLASH_IF_STATUS(status_index));
+}
+
+/* Device ID */
+static inline int bfmtd_read_did(struct bf_mtd *bfmtd)
+{
+ return bfmtd_read_status(bfmtd, CONTROL_READ_DID, 1);
+}
+
+/* Silicon ID */
+static inline int bfmtd_read_sid(struct bf_mtd *bfmtd)
+{
+ return bfmtd_read_status(bfmtd, CONTROL_READ_SID, 2);
+}
+
+/* ^2 addressing mode */
+static inline int bfmtd_read_p2addr(struct bf_mtd *bfmtd)
+{
+ return bfmtd_read_status(bfmtd, CONTROL_READ_STATUS, 3);
+}
+
+/*
+ * bfmtd_calc_flash_addr()
+ *
+ * We have to support 2 different addressing schemes.
+ * The page_size can be 256, 264, 512, 528.
+ * The page_size in the default addressing mode are 264 and 528.
+ * The same is valid for the sector addresses.
+ * Calculate the page/sector address from the page_size/sectro_size.
+ *
+ * Example (XC3S700AN):
+ * Page# Default addressing Power of 2 addressing
+ * 0 0x000 - 0x107 0x000 - 0xFF
+ * 1 0x200 - 0x307 0x100 - 0x1FF
+ * 2 0x400 - 0x507 0x200 - 0x2FF
+ */
+static u32 bfmtd_calc_flash_addr(u32 addr, u32 size, u32 mask)
+{
+ /* spare = 0,8,16 for pages and 0,1024,2048,4096 for sectors */
+ u32 spare = size & mask;
+
+ if (!spare) /* power-of-2 mode */
+ return addr & ~(size - 1);
+
+ /* default mode, use spare to get the page/sector address */
+ addr /= size;
+ addr *= (spare << 6);
+
+ return addr;
+}
+
+/*
+ * bfmtd_read_page()
+ * Transfer a page from the flash into the sram buffer
+ */
+static int bfmtd_read_page(struct bf_mtd *bfmtd, u32 addr, u8 *buf,
+ size_t len)
+{
+ int ret;
+ u32 page_addr, page_ofs;
+ u16 buff_addr;
+ size_t retlen, page_size = bfmtd->info->page_size;
+
+ if (len > page_size)
+ len = page_size;
+
+ page_ofs = addr % page_size;
+ retlen = len - page_ofs;
+
+ if (!retlen || addr + retlen > bfmtd->info->flash_size)
+ return -EINVAL;
+
+ ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Calculate the page address */
+ page_addr = bfmtd_calc_flash_addr(addr, page_size, MASK_SPARE_PAGE);
+
+ /* Set page address, byte count and trigger write */
+ iowrite32(be32_to_cpu(page_addr),
+ bfmtd->base + BOOT_FPGA_FLASH_IF_ADDR(0));
+ /* byte count (N), denotes (N + 1) */
+ iowrite32(be32_to_cpu(len - 1),
+ bfmtd->base + BOOT_FPGA_FLASH_IF_BYTE_COUNT(0));
+ iowrite8(CONTROL_PAGE_READ,
+ bfmtd->base + BOOT_FPGA_FLASH_IF_CONTROL(3));
+
+ /* Wait the fpga the fetch the page into the sram buffer (400 usec) */
+ ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT);
+ if (ret)
+ return ret;
+
+ for (buff_addr = page_ofs; buff_addr < len; buff_addr++) {
+ iowrite16(be16_to_cpu(buff_addr),
+ bfmtd->base + BOOT_FPGA_FLASH_IF_READ_BUF_ADDR_MSB);
+ *buf = ioread8(bfmtd->base + BOOT_FPGA_FLASH_IF_READ_BUF_DATA);
+ buf++;
+ }
+
+ return retlen;
+}
+
+/*
+ * bfmtd_is_protected()
+ * Check if the sector @addr is protected
+ */
+static int bfmtd_is_protected(struct bf_mtd *bfmtd, u32 addr)
+{
+ u8 protected_sectors[16];
+ int n_sector, ret = 0;
+ u16 buff_addr;
+
+ if (addr > bfmtd->info->flash_size)
+ return -EINVAL;
+
+ n_sector = addr / bfmtd->info->sector_size;
+
+ /* Read the protected sector array */
+ iowrite8(CONTROL_SP_READ,
+ bfmtd->base + BOOT_FPGA_FLASH_IF_CONTROL(2));
+
+ ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT);
+ if (ret)
+ return ret;
+
+ for (buff_addr = 0; buff_addr < bfmtd->info->nr_sectors; buff_addr++) {
+ iowrite16(be16_to_cpu(buff_addr),
+ bfmtd->base + BOOT_FPGA_FLASH_IF_READ_BUF_ADDR_MSB);
+ protected_sectors[buff_addr] =
+ ioread8(bfmtd->base + BOOT_FPGA_FLASH_IF_READ_BUF_DATA);
+ }
+
+ if (protected_sectors[n_sector] == SECTOR_PROTECT) {
+ dev_err(bfmtd->dev, "Sector #%d is protected\n", n_sector);
+ return -EACCES;
+ }
+
+ return 0;
+}
+
+/*
+ * bfmtd_erase_region()
+ * Erase pages or sectors.
+ * Note: The control (0xa5a5a5a5) words are written on page boundary.
+ */
+static int bfmtd_erase_region(struct bf_mtd *bfmtd, u32 addr)
+{
+ struct mtd_info *mtd = &bfmtd->mtd;
+ u32 flash_addr, mask, timeout;
+ u8 cmd;
+ int ret;
+
+ if (addr > bfmtd->info->flash_size)
+ return -EINVAL;
+
+ ret = bfmtd_is_protected(bfmtd, addr);
+ if (ret)
+ return ret;
+
+ ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* sector/page erase mode ? */
+ if (bfmtd->info->page_size == mtd->erasesize) {
+ mask = MASK_SPARE_PAGE;
+ timeout = bfmtd->info->page_erase_tmo;
+ cmd = CONTROL_PAGE_ERASE;
+ } else {
+ mask = MASK_SPARE_SECTOR;
+ timeout = bfmtd->info->sector_erase_tmo;
+ cmd = CONTROL_SECTOR_ERASE;
+ }
+
+ /* Calculate the page/sector address */
+ flash_addr = bfmtd_calc_flash_addr(addr, mtd->erasesize, mask);
+
+ /* Set page/sector address, byte count and trigger erase */
+ iowrite32(be32_to_cpu(flash_addr),
+ bfmtd->base + BOOT_FPGA_FLASH_IF_ADDR(0));
+ iowrite8(cmd, bfmtd->base + BOOT_FPGA_FLASH_IF_CONTROL(3));
+
+ /* Wait the fpga the flush the buffer */
+ ret = bfmtd_busy_wait(bfmtd, timeout);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * bfmtd_write_page()
+ * Write a page content in the sram buffer and then to flash device
+ */
+static int bfmtd_write_page(struct bf_mtd *bfmtd, u32 addr, const u8 *buf,
+ size_t len)
+{
+ int ret;
+ u32 page_addr, page_ofs;
+ u16 buff_addr;
+ size_t retlen, page_size = bfmtd->info->page_size;
+
+ if (len > page_size)
+ len = page_size;
+
+ page_ofs = addr % page_size;
+ retlen = len - page_ofs;
+
+ if (!retlen || addr + retlen > bfmtd->info->flash_size)
+ return -EINVAL;
+
+ ret = bfmtd_busy_wait(bfmtd, DEFAULT_BUSY_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Fill the internal fpga buffer */
+ for (buff_addr = page_ofs; buff_addr < len; buff_addr++) {
+ iowrite16(be16_to_cpu(buff_addr),
+ bfmtd->base + BOOT_FPGA_FLASH_IF_WRITE_BUF_ADDR_MSB);
+ iowrite8(*buf, bfmtd->base + BOOT_FPGA_FLASH_IF_WRITE_BUF_DATA);
+ buf++;
+ }
+
+ /* Calculate the page address */
+ page_addr = bfmtd_calc_flash_addr(addr, page_size, MASK_SPARE_PAGE);
+
+ /* Set page address, byte count and trigger write */
+ iowrite32(be32_to_cpu(page_addr),
+ bfmtd->base + BOOT_FPGA_FLASH_IF_ADDR(0));
+ iowrite32(be32_to_cpu(len - 1),
+ bfmtd->base + BOOT_FPGA_FLASH_IF_BYTE_COUNT(0));
+ iowrite8(CONTROL_PAGE_WRITE,
+ bfmtd->base + BOOT_FPGA_FLASH_IF_CONTROL(3));
+
+ /* Wait the fpga the flush the buffer */
+ ret = bfmtd_busy_wait(bfmtd, bfmtd->info->buffer_program_tmo);
+ if (ret)
+ return ret;
+
+ return retlen;
+}
+
+/*
+ * bfmtd_read()
+ * MTD read
+ */
+static int bfmtd_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u8 *buf)
+{
+ struct bf_mtd *bfmtd = container_of(mtd, struct bf_mtd, mtd);
+ int ret, read = 0;
+
+ mutex_lock(&bfmtd->lock);
+ while (len) {
+ ret = bfmtd_read_page(bfmtd, from, buf, len);
+ if (ret < 0) {
+ dev_err(bfmtd->dev, "RD @0x%llx, size %ld failed (%d)",
+ from, len, ret);
+ mutex_unlock(&bfmtd->lock);
+ return ret;
+ }
+ read += ret;
+ len -= ret;
+ from += ret;
+ buf += ret;
+ }
+ mutex_unlock(&bfmtd->lock);
+
+ *retlen = read;
+
+ return 0;
+}
+
+/*
+ * bfmtd_write()
+ * MTD write
+ */
+static int bfmtd_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u8 *buf)
+{
+ struct bf_mtd *bfmtd = container_of(mtd, struct bf_mtd, mtd);
+ int ret, written = 0;
+
+ mutex_lock(&bfmtd->lock);
+ while (len) {
+ ret = bfmtd_write_page(bfmtd, to, buf, len);
+ if (ret < 0) {
+ dev_err(bfmtd->dev, "WR @0x%llx, size %ld failed (%d)",
+ to, len, ret);
+ mutex_unlock(&bfmtd->lock);
+ return ret;
+ }
+ written += ret;
+ len -= ret;
+ to += ret;
+ buf += ret;
+ }
+ mutex_unlock(&bfmtd->lock);
+
+ *retlen = written;
+
+ return 0;
+}
+
+/*
+ * bfmtd_erase()
+ * MTD erase
+ */
+static int bfmtd_erase(struct mtd_info *mtd, struct erase_info *ei)
+{
+ struct bf_mtd *bfmtd = container_of(mtd, struct bf_mtd, mtd);
+ u32 start_addr, end_addr, addr;
+ int len, ret = 0;
+
+ len = ei->len;
+ start_addr = ei->addr;
+ end_addr = start_addr + len - 1;
+
+ ei->state = MTD_ERASE_DONE;
+ addr = start_addr;
+ mutex_lock(&bfmtd->lock);
+ while (addr < end_addr) {
+ ret = bfmtd_erase_region(bfmtd, addr);
+ if (ret < 0) {
+ dev_err(bfmtd->dev, "Erase @0x%x, size %d failed (%d)",
+ addr, mtd->erasesize, ret);
+ ei->state = MTD_ERASE_FAILED;
+ break;
+ }
+ addr += mtd->erasesize;
+ }
+ mutex_unlock(&bfmtd->lock);
+
+ mtd_erase_callback(ei);
+
+ return ret;
+}
+
+static int bfmtd_init_mtd(struct bf_mtd *bfmtd)
+{
+ struct mtd_info *mtd;
+ struct bfmtd_info *info = NULL;
+ int ret, i;
+
+ ret = bfmtd_read_sid(bfmtd);
+ if (ret < 0)
+ return ret;
+
+ if (ret != STATUS_SID_I2CS) {
+ dev_err(bfmtd->dev, "Unsupported silicon id: %u", ret);
+ return -ENODEV;
+ }
+
+ ret = bfmtd_read_did(bfmtd);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(bfmtd_info_db); i++) {
+ if (bfmtd_info_db[i].device_id == ret) {
+ info = &bfmtd_info_db[i];
+ break;
+ }
+ }
+
+ if (!info) {
+ dev_err(bfmtd->dev, "Unsupported device id: %u", ret);
+ return -ENODEV;
+ }
+
+ ret = bfmtd_read_p2addr(bfmtd);
+ if (ret < 0)
+ return ret;
+
+ /* Reconfigure sizes if not in "power-of-2" addressing mode */
+ if (!(ret & STATUS_POWER_2_ADDR)) {
+ info->page_size += info->spare_size;
+ info->writebufsize = info->page_size;
+ info->flash_size = info->page_size * info->nr_pages;
+ info->sector_size = info->flash_size / info->nr_sectors;
+ }
+
+ dev_info(bfmtd->dev, "%s configuration flash in \'%s\' addressing mode\n",
+ info->name,
+ (ret & STATUS_POWER_2_ADDR) ? "power-of-2" : "default");
+
+ ret = ioread8(bfmtd->base + BOOT_FPGA_RU_CONFIG_CONTROL_STATUS);
+ dev_info(bfmtd->dev, "active FPGA configuration: %s\n",
+ (ret & RU_CCS_USER_IMAGE) ? "user" : "golden");
+
+ bfmtd->info = info;
+ mtd = &bfmtd->mtd;
+ mtd->name = dev_name(bfmtd->dev);
+ mtd->type = MTD_NORFLASH;
+ mtd->flags = MTD_CAP_NORFLASH;
+ mtd->erasesize = info->page_size;
+ mtd->writesize = info->writesize;
+ mtd->writebufsize = info->writebufsize;
+ mtd->size = info->flash_size;
+ mtd->_erase = bfmtd_erase;
+ mtd->_read = bfmtd_read;
+ mtd->_write = bfmtd_write;
+
+ return 0;
+}
+
+static int bfmtd_probe(struct platform_device *pdev)
+{
+ struct mtd_part_parser_data ppdata = {};
+ struct bf_mtd *bfmtd;
+ struct resource *mem;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ bfmtd = devm_kzalloc(dev, sizeof(*bfmtd), GFP_KERNEL);
+ if (!bfmtd)
+ return -ENOMEM;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(dev, "Failed to get platform mmio resource\n");
+ return -ENOENT;
+ }
+
+ bfmtd->base = devm_ioremap_nocache(dev, mem->start, resource_size(mem));
+ if (IS_ERR(bfmtd->base)) {
+ dev_err(dev, "Failed to ioremap mmio memory\n");
+ return PTR_ERR(bfmtd->base);
+ }
+
+ bfmtd->dev = dev;
+ ret = bfmtd_init_mtd(bfmtd);
+ if (ret)
+ return ret;
+
+ ret = mtd_device_parse_register(&bfmtd->mtd, NULL, &ppdata, NULL, 0);
+ if (ret) {
+ dev_err(dev, "Failed to register MTD device (%d)\n", ret);
+ return ret;
+ }
+
+ mutex_init(&bfmtd->lock);
+
+ platform_set_drvdata(pdev, bfmtd);
+
+ return ret;
+}
+
+static int bfmtd_remove(struct platform_device *pdev)
+{
+ struct bf_mtd *bfmtd = platform_get_drvdata(pdev);
+
+ mtd_device_unregister(&bfmtd->mtd);
+
+ return 0;
+}
+
+static const struct of_device_id ptx1kbf_mtd_ids[] = {
+ { .compatible = "jnx,ptx1kbf-mtd", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ptx1kbf_mtd_ids);
+
+static struct platform_driver bfmtd_driver = {
+ .probe = bfmtd_probe,
+ .remove = bfmtd_remove,
+ .driver = {
+ .name = "jnx-ptx1kbf-mtd",
+ .owner = THIS_MODULE,
+ .of_match_table = ptx1kbf_mtd_ids,
+ },
+};
+
+module_platform_driver(bfmtd_driver);
+
+MODULE_DESCRIPTION("Juniper Networks PTX1K RCB I2CS Boot FPGA MTD driver");
+MODULE_AUTHOR("Georgi Vlaev <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jnx-ptx1kbf-mtd");
--
1.9.1
From: Georgi Vlaev <[email protected]>
Add device tree bindings for the Juniper PTX1K Boot FPGA MFD driver.
Signed-off-by: Georgi Vlaev <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
.../devicetree/bindings/mfd/jnx-ptx1k-bootfpga.txt | 34 ++++++++++++++++++++++
1 file changed, 34 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/jnx-ptx1k-bootfpga.txt
diff --git a/Documentation/devicetree/bindings/mfd/jnx-ptx1k-bootfpga.txt b/Documentation/devicetree/bindings/mfd/jnx-ptx1k-bootfpga.txt
new file mode 100644
index 0000000..596dec7
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/jnx-ptx1k-bootfpga.txt
@@ -0,0 +1,34 @@
+* Device tree bindings for Juniper's PTX1K boot FPGA MFD driver
+
+The device supports a hardware watchdog, a flash MTD device and
+a hwmon sensor device. The bindings of those drivers are described
+in the jnx-ptx1kbf-wdt, jnx-ptx1kbf-mtd and jnx-ptx1kbf-hwmon documents.
+
+Required properties:
+
+- compatible: "ptx1k-bootfpga"
+
+- reg: contains offset/length value for device state control
+ registers space.
+
+Example:
+
+bootfpga@10000 {
+ compatible = "jnx,ptx1k-bootfpga";
+ reg = <0x10000 0x100>;
+
+ wdt {
+ compatible = "jnx,ptx1kbf-wdt";
+ /* no properties required */
+ };
+
+ mtd {
+ compatible = "jnx,ptx1kbf-mtd";
+ /* use the full device */
+ };
+
+ hwmon {
+ compatible = "jnx,ptx1kbf-hwmon";
+ /* no properties required */
+ };
+};
--
1.9.1
From: Georgi Vlaev <[email protected]>
The driver allows access to the BootFPGA logic of the I2CS FPGA
@0xfed5000 from the LPC bus. The driver exports debugfs entries
for manipulating registers and device attrs for reading board and
FPGA IDs and switching the active BIOS flash. The client devices
created by the MFD driver depend on the PCB and FPGA version
numbers. PTX1K (etch1) RE boards support only watchdog clients.
Warn on using engineering releases (FPGA version = 0xEE) and
disable clients on revisions known to cause problems.
- watchdog - jnx_ptx1kbf_wdt
- mtd - jnx_ptx1kbf_mtd (etch2)
- hwmon - jnx_ptx1kbf_hwmon (etch2)
Signed-off-by: Georgi Vlaev <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
Signed-off-by: JawaharBalaji Thirumalaisamy <[email protected]>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <[email protected]>
---
drivers/mfd/Kconfig | 11 +
drivers/mfd/Makefile | 1 +
drivers/mfd/ptx1k-bootfpga.c | 462 +++++++++++++++++++++++++++++++++++++
include/linux/mfd/ptx1k-bootfpga.h | 93 ++++++++
4 files changed, 567 insertions(+)
create mode 100644 drivers/mfd/ptx1k-bootfpga.c
create mode 100644 include/linux/mfd/ptx1k-bootfpga.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 82493d5..a2564ba 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1417,6 +1417,17 @@ config MFD_JUNIPER_I2CS
This driver can be built as a module. If built as a module it will be
called "jnx_i2cs"
+config MFD_JUNIPER_PTX1KBF
+ tristate "Juniper PTX1K RCB I2CS BootFPGA"
+ depends on JNX_PTX1K_RCB
+ select MFD_CORE
+ help
+ Select this to enable the I2CS Boot FPGA multi-function kernel driver.
+ This FPGA is present on the PTX1K RCB.
+
+ This driver can be built as a module. If built as a module it will be
+ called "ptx1k-bootfpga"
+
config MFD_TWL4030_AUDIO
bool "TI TWL4030 Audio"
depends on TWL4030_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 215d9cf..1661b82 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -153,6 +153,7 @@ obj-$(CONFIG_MFD_JUNIPER_SAM) += sam-core.o
obj-$(CONFIG_MFD_JUNIPER_EXT_CPLD) += ptxpmb-ext-cpld-core.o
obj-$(CONFIG_MFD_JUNIPER_CBC) += cbc-core.o
obj-$(CONFIG_MFD_JUNIPER_I2CS) += jnx-i2cs-core.o
+obj-$(CONFIG_MFD_JUNIPER_PTX1KBF) += ptx1k-bootfpga.o
obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o
# ab8500-core need to come after db8500-prcmu (which provides the channel)
obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o
diff --git a/drivers/mfd/ptx1k-bootfpga.c b/drivers/mfd/ptx1k-bootfpga.c
new file mode 100644
index 0000000..5ab2ed2
--- /dev/null
+++ b/drivers/mfd/ptx1k-bootfpga.c
@@ -0,0 +1,462 @@
+/*
+ * Juniper Networks PTX1K RCB I2CS Boot FPGA multi-function core driver
+ *
+ * Copyright (C) 2014 Juniper Networks. All rights reserved.
+ * Author: Georgi Vlaev <[email protected]>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/acpi.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/ptx1k-bootfpga.h>
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#endif
+
+struct ptx1kbf_core {
+ struct device *dev;
+ void __iomem *base;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dir;
+ u8 addr; /* any register offsset */
+ struct debugfs_blob_wrapper blob; /* regspace page blob */
+#endif
+};
+
+static struct resource ptx1kbf_resources[] = {
+ {
+ .start = 0,
+ .end = 0xff,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct mfd_cell ptx1kbf_cells[] = {
+ {
+ .name = "jnx-ptx1kbf-wdt",
+ .num_resources = ARRAY_SIZE(ptx1kbf_resources),
+ .resources = ptx1kbf_resources,
+ .of_compatible = "jnx,ptx1kbf-wdt",
+ }, {
+ .name = "jnx-ptx1kbf-mtd",
+ .num_resources = ARRAY_SIZE(ptx1kbf_resources),
+ .resources = ptx1kbf_resources,
+ .of_compatible = "jnx,ptx1kbf-mtd",
+ }, {
+ .name = "jnx-ptx1kbf-hwmon",
+ .num_resources = ARRAY_SIZE(ptx1kbf_resources),
+ .resources = ptx1kbf_resources,
+ .of_compatible = "jnx,ptx1kbf-hwmon",
+ },
+};
+
+/* ptx1k-bootfpga debugfs */
+#ifdef CONFIG_DEBUG_FS
+/* debugfs: set/get register offset */
+static int bf_debugfs_addr_print(struct seq_file *s, void *p)
+{
+ struct ptx1kbf_core *bf = (struct ptx1kbf_core *)s->private;
+
+ seq_printf(s, "0x%02X\n", bf->addr);
+
+ return 0;
+}
+
+static int bf_debugfs_addr_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, bf_debugfs_addr_print, inode->i_private);
+}
+
+static ssize_t bf_debugfs_addr_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct ptx1kbf_core *bf =
+ ((struct seq_file *)(file->private_data))->private;
+ unsigned long addr;
+ int err;
+
+ err = kstrtoul_from_user(user_buf, count, 0, &addr);
+ if (err)
+ return err;
+
+ if (addr > ptx1kbf_resources[0].end) {
+ dev_err(bf->dev, "register offset out of range\n");
+ return -EINVAL;
+ }
+ bf->addr = addr;
+
+ return count;
+}
+
+static const struct file_operations bf_debugfs_addr_fops = {
+ .open = bf_debugfs_addr_open,
+ .write = bf_debugfs_addr_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+/* debugfs: set/get register value */
+static int bf_debugfs_val_print(struct seq_file *s, void *p)
+{
+ struct ptx1kbf_core *bf = (struct ptx1kbf_core *)s->private;
+
+ seq_printf(s, "0x%02X\n", ioread8(bf->base + bf->addr));
+
+ return 0;
+}
+
+static int bf_debugfs_val_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, bf_debugfs_val_print, inode->i_private);
+}
+
+static ssize_t bf_debugfs_val_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ struct ptx1kbf_core *bf =
+ ((struct seq_file *)(file->private_data))->private;
+ unsigned long value;
+ int err;
+
+ err = kstrtoul_from_user(user_buf, count, 0, &value);
+ if (err)
+ return err;
+
+ iowrite8(value & 0xff, bf->base + bf->addr);
+
+ return count;
+}
+
+static const struct file_operations bf_debugfs_val_fops = {
+ .open = bf_debugfs_val_open,
+ .write = bf_debugfs_val_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+static int bf_debugfs_init(struct ptx1kbf_core *bf)
+{
+ struct dentry *file;
+
+ bf->dir = debugfs_create_dir("ptx1k-bootfpga", NULL);
+ if (!bf->dir)
+ return -ENOMEM;
+
+/* Register dump */
+ bf->blob.size = resource_size(&ptx1kbf_resources[0]);
+ bf->blob.data = bf->base;
+
+ file = debugfs_create_blob("reg-dump", S_IFREG | S_IRUSR, bf->dir,
+ &bf->blob);
+ if (!file)
+ goto err;
+
+/* Any register @base */
+ file = debugfs_create_file("reg-address", (S_IRUGO | S_IWUSR),
+ bf->dir, bf, &bf_debugfs_addr_fops);
+ if (!file)
+ goto err;
+
+ file = debugfs_create_file("reg-value", (S_IRUGO | S_IWUSR),
+ bf->dir, bf, &bf_debugfs_val_fops);
+ if (!file)
+ goto err;
+
+ return 0;
+err:
+ debugfs_remove_recursive(bf->dir);
+ dev_err(bf->dev, "failed to create debugfs entries.\n");
+
+ return -ENOMEM;
+}
+
+static void bf_debugfs_remove(struct ptx1kbf_core *bf)
+{
+ debugfs_remove_recursive(bf->dir);
+}
+#endif /* CONFIG_DEBUG_FS */
+
+/* Export FPGA board revisions and status as device attrs */
+static ssize_t bf_reg_show(struct device *dev, char *buf, u8 reg)
+{
+ struct ptx1kbf_core *bf = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%02x\n", ioread8(bf->base + reg));
+}
+
+static ssize_t bf_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return bf_reg_show(dev, buf, BOOT_FPGA_VERSION);
+}
+
+static ssize_t bf_board_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return bf_reg_show(dev, buf, BOOT_FPGA_BOARD_ID);
+}
+
+static ssize_t bf_jspec_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return bf_reg_show(dev, buf, BOOT_FPGA_JSPEC_VERSION);
+}
+
+static ssize_t bf_pcb_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return bf_reg_show(dev, buf, BOOT_FPGA_PCB_VERSION);
+}
+
+static ssize_t bf_chassis_type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return bf_reg_show(dev, buf, BOOT_FPGA_CHASSIS_TYPE);
+}
+
+/* active_flash: (BIOS) Switch bethween FlashA and FlashB */
+static ssize_t bf_active_flash_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ptx1kbf_core *bf = dev_get_drvdata(dev);
+ u8 reg = ioread8(bf->base + BOOT_FPGA_BOOT_CONTROL);
+
+ return sprintf(buf, "%u\n", (u8)(reg & BC_FLASH_SELECT) >> 4);
+}
+
+static ssize_t bf_active_flash_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptx1kbf_core *bf = dev_get_drvdata(dev);
+ unsigned long val;
+ int err;
+ u8 reg;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err)
+ return err;
+
+ if (val > 1)
+ return -EINVAL;
+
+ reg = ioread8(bf->base + BOOT_FPGA_BOOT_CONTROL);
+ reg &= ~BC_FLASH_SELECT;
+ reg |= (u8)((val << 4) & BC_FLASH_SELECT);
+ iowrite8(reg, bf->base + BOOT_FPGA_BOOT_CONTROL);
+
+ return count;
+}
+
+static DEVICE_ATTR(version, S_IRUGO, bf_version_show, NULL);
+static DEVICE_ATTR(board_id, S_IRUGO, bf_board_id_show, NULL);
+static DEVICE_ATTR(jspec_version, S_IRUGO, bf_jspec_version_show, NULL);
+static DEVICE_ATTR(pcb_version, S_IRUGO, bf_pcb_version_show, NULL);
+static DEVICE_ATTR(chassis_type, S_IRUGO, bf_chassis_type_show, NULL);
+static DEVICE_ATTR(active_flash, S_IRUGO | S_IWUSR, bf_active_flash_show,
+ bf_active_flash_store);
+
+static struct attribute *bf_attrs[] = {
+ &dev_attr_version.attr,
+ &dev_attr_board_id.attr,
+ &dev_attr_jspec_version.attr,
+ &dev_attr_pcb_version.attr,
+ &dev_attr_chassis_type.attr,
+ &dev_attr_active_flash.attr,
+ NULL,
+};
+
+static struct attribute_group bf_attr_group = {
+ .attrs = bf_attrs,
+};
+
+/* Check if scratch regs are usable */
+static int bf_scratch_test(struct ptx1kbf_core *bf)
+{
+ u8 i, ii;
+
+ for (i = 0; i < 0xff; i++) {
+ iowrite8(i, bf->base + BOOT_FPGA_SCRATCH1);
+ ii = ioread8(bf->base + BOOT_FPGA_SCRATCH1);
+ if (ii != i) {
+ dev_err(bf->dev, "Scratch(1) write failed: %02x->%02x",
+ i, ii);
+ return -EIO;
+ }
+ }
+
+ for (i = 0; i < 0xff; i++) {
+ iowrite8(i, bf->base + BOOT_FPGA_SCRATCH2);
+ ii = ioread8(bf->base + BOOT_FPGA_SCRATCH2);
+ if (ii != i) {
+ dev_err(bf->dev, "Scratch(2) write failed: %02x->%02x",
+ i, ii);
+ return -EIO;
+ }
+ }
+
+ iowrite8(0, bf->base + BOOT_FPGA_SCRATCH1);
+ iowrite8(0, bf->base + BOOT_FPGA_SCRATCH2);
+
+ return 0;
+}
+
+static int ptx1kbf_probe(struct platform_device *pdev)
+{
+ static struct ptx1kbf_core *bf;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int err, ncells = 0;
+ u8 pcb_version, fpga_version;
+
+ bf = devm_kzalloc(dev, sizeof(struct ptx1kbf_core), GFP_KERNEL);
+ if (!bf)
+ return -ENOMEM;
+
+ bf->dev = dev;
+ dev_set_drvdata(dev, bf);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ bf->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+ if (IS_ERR(bf->base))
+ return -EADDRNOTAVAIL;
+
+ /* Get the FPGA & PCB versions to filter supported features */
+ fpga_version = ioread8(bf->base + BOOT_FPGA_VERSION),
+ pcb_version = ioread8(bf->base + BOOT_FPGA_PCB_VERSION);
+
+ dev_info(dev, "FPGA version: 0x%02X, PCB version: 0x%02X\n",
+ fpga_version, pcb_version);
+
+ /* Exit if scratch loop fails */
+ err = bf_scratch_test(bf);
+ if (err)
+ return err;
+
+ /*
+ * Support matrix.
+ * PCB version #0 (ETCH1) = Spartan XC3S400AN
+ * PCB version #1 (ETCH2) = Spartan XC3S700AN
+ *
+ * PCB version #0 supported devices
+ * - watchdog (FPGA version >= 0x0E)
+ * - hwmon & mtd unsupported due to FPGA limitations
+ *
+ * PCB version #1 supported devices
+ * - watchdog (FPGA version >= 0x0E)
+ * - mtd devices (FPGA version > 0x0E)
+ * - hwmon (FPGA version > 0x0F ?)
+ *
+ * FPGA version 0xEE is "engineering release", warn on usage.
+ * FPGA version 0xC0 and < 0x0E are unsupported.
+ */
+
+ /* Engineering release - allow it to run for testing/debug */
+ if (fpga_version == 0xEE) {
+ dev_warn(dev,
+ "0x%02X is engineering release, consider FPGA update",
+ fpga_version);
+ }
+
+ if (fpga_version != 0xC0 && fpga_version >= 0x0E) {
+ /* wdt - any pcb, fpga >= 0x0E && !0xC0 */
+ ncells = 1;
+
+ /* mtd - pcb > 0, fpga >= 0x0F
+ * Versions bellow 0x0F may cause damage of the flash (!)
+ */
+ if (pcb_version > 0 && fpga_version > 0x0E)
+ ncells = 2;
+
+ /* hwmon - pcb > 0, fpga > 0x0F (assumption) */
+ if (pcb_version > 0 && fpga_version > 0x0F)
+ ncells = 3;
+
+ err = mfd_add_devices(dev, pdev->id, ptx1kbf_cells,
+ ncells, res, 0, NULL);
+ if (err)
+ return err;
+ } else {
+ /* Unsupported - known to cause problems: 0xC0, < 0x0E */
+ dev_err(dev,
+ "0x%02X is unsupported version, consider FPGA update",
+ fpga_version);
+ }
+
+ err = sysfs_create_group(&dev->kobj, &bf_attr_group);
+ if (err) {
+ sysfs_remove_group(&dev->kobj, &bf_attr_group);
+ goto err_mfd;
+ }
+
+#ifdef CONFIG_DEBUG_FS
+ bf_debugfs_init(bf);
+#endif
+
+ return 0;
+
+err_mfd:
+ mfd_remove_devices(&pdev->dev);
+
+ return err;
+}
+
+static int ptx1kbf_remove(struct platform_device *pdev)
+{
+#ifdef CONFIG_DEBUG_FS
+ struct ptx1kbf_core *bf = dev_get_drvdata(&pdev->dev);
+
+ bf_debugfs_remove(bf);
+#endif
+ sysfs_remove_group(&pdev->dev.kobj, &bf_attr_group);
+ mfd_remove_devices(&pdev->dev);
+ return 0;
+}
+
+static const struct of_device_id ptx1kbf_of_ids[] = {
+ { .compatible = "jnx,ptx1k-bootfpga" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ptx1kbf_of_ids);
+
+static struct platform_driver ptx1kbf_driver = {
+ .driver = {
+ .name = "ptx1k-bootfpga",
+ .of_match_table = ptx1kbf_of_ids,
+ .owner = THIS_MODULE,
+ },
+ .probe = ptx1kbf_probe,
+ .remove = ptx1kbf_remove,
+};
+
+module_platform_driver(ptx1kbf_driver);
+
+MODULE_DESCRIPTION("Juniper Networks PTX1K RCB I2CS Boot FPGA Driver");
+MODULE_AUTHOR("Georgi Vlaev <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ptx1k-bootfpga");
diff --git a/include/linux/mfd/ptx1k-bootfpga.h b/include/linux/mfd/ptx1k-bootfpga.h
new file mode 100644
index 0000000..6a40c95
--- /dev/null
+++ b/include/linux/mfd/ptx1k-bootfpga.h
@@ -0,0 +1,93 @@
+/*
+ * PTX1K I2CS Boot FPGA registers
+ *
+ * Copyright (C) 2014 Juniper Networks. All rights reserved.
+ * Author: Georgi Vlaev <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef __PTX1K_BOOTFPGA_H__
+#define __PTX1K_BOOTFPGA_H__
+
+#include <linux/bitops.h>
+
+#define BOOT_FPGA_SCRATCH1 0x00
+#define BOOT_FPGA_SCRATCH2 0x01
+#define BOOT_FPGA_VERSION 0x02
+#define BOOT_FPGA_JSPEC_VERSION 0x03
+#define BOOT_FPGA_PCB_VERSION 0x04
+#define BOOT_FPGA_BOARD_ID 0x05
+#define BOOT_FPGA_RESET_REASON1 0x06
+#define BOOT_FPGA_RESET_REASON2 0x07
+#define BOOT_FPGA_RESET_CONTROL_AND_STATUS1 0x08
+#define BOOT_FPGA_RESET_CONTROL_AND_STATUS2 0x09
+#define BOOT_FPGA_RESET_CONTROL_AND_STATUS3 0x0a
+#define BOOT_FPGA_BOOT_CONTROL 0x0b
+#define BOOT_FPGA_WATCHDOG_TIMER_THRESHOLD 0x0c
+#define BOOT_FPGA_ERROR_STATUS1 0x0d
+#define BOOT_FPGA_ERROR_STATUS2 0x0e
+#define BOOT_FPGA_ERROR_STATUS3 0x0f
+#define BOOT_FPGA_IRQ_ENABLE1 0x10
+#define BOOT_FPGA_IRQ_STATUS1 0x11
+#define BOOT_FPGA_IRQ_ENABLE2 0x12
+#define BOOT_FPGA_IRQ_STATUS2 0x13
+#define BOOT_FPGA_CHASSIS_TYPE 0x14
+#define BOOT_FPGA_POST_CODE 0x15
+#define BOOT_FPGA_MISC_CONTROL 0x16
+#define BOOT_FPGA_MISC_STATUS 0x17
+#define BOOT_FPGA_CPU_TEMP_MSB 0x18
+#define BOOT_FPGA_CPU_TEMP_LSB 0x19
+#define BOOT_FPGA_PCH_TEMP_MSB 0x1a
+#define BOOT_FPGA_PCH_TEMP_LSB 0x1b
+#define BOOT_FPGA_DIMM1_TEMP_MSB 0x1c
+#define BOOT_FPGA_DIMM1_TEMP_LSB 0x1d
+#define BOOT_FPGA_DIMM2_TEMP_MSB 0x1e
+#define BOOT_FPGA_DIMM2_TEMP_LSB 0x1f
+
+#define BOOT_FPGA_SFPP_USB_SSD_PWR_CONTROL 0x20
+#define BOOT_FPGA_FPGA_ID 0x21
+#define BOOT_FPGA_SPI_FLASH_CONTROL_AND_STATUS 0x22
+#define BOOT_FPGA_PCH_NMI_SLEEP_RT_STATUS 0x23
+#define BOOT_FPGA_PCH_NMI_SLEEP_LATCHED_STATUS 0x24
+
+#define BOOT_FPGA_FLASH_IF_ADDR(a) (0xe0 + (a))
+#define BOOT_FPGA_FLASH_IF_BYTE_COUNT(a) (0xe4 + (a))
+#define BOOT_FPGA_FLASH_IF_CONTROL(a) (0xe8 + (a))
+#define BOOT_FPGA_FLASH_IF_STATUS(a) (0xec + (a))
+#define BOOT_FPGA_FLASH_IF_WRITE_BUF_ADDR_MSB 0xf0
+#define BOOT_FPGA_FLASH_IF_WRITE_BUF_ADDR_LSB 0xf1
+#define BOOT_FPGA_FLASH_IF_WRITE_BUF_DATA 0xf2
+#define BOOT_FPGA_FLASH_IF_READ_BUF_ADDR_MSB 0xf3
+#define BOOT_FPGA_FLASH_IF_READ_BUF_ADDR_LSB 0xf4
+#define BOOT_FPGA_FLASH_IF_READ_BUF_DATA 0xf5
+
+#define BOOT_FPGA_RU_CONFIG_CONTROL_STATUS 0xf6
+#define BOOT_FPGA_RU_CONFIG_STATUS_DATA_MSB 0xf7
+#define BOOT_FPGA_RU_CONFIG_STATUS_DATA_LSB 0xf8
+#define BOOT_FPGA_RU_CONFIG_ADDR_23_DOWNTO_16 0xf9
+#define BOOT_FPGA_RU_CONFIG_ADDR_15_DOWNTO_8 0xfa
+#define BOOT_FPGA_RU_CONFIG_ADDR_7_DOWNTO_0 0xfb
+
+#define RR1_SW BIT(7) /* SW initiated reset */
+#define RR1_WDOG BIT(6) /* Watchdog induced reset */
+#define RR1_MSTR BIT(5) /* System host (Master RE) initiated reset */
+#define RR1_BUTTON BIT(4) /* Front panel button reset */
+#define RR1_POWER_CYCLE BIT(3) /* Natural power cycle */
+#define RR1_POWER_FAIL BIT(2) /* Power fail */
+#define RR1_THERM_TRIP BIT(1) /* CPU thermal trip */
+#define RR1_PCH BIT(0) /* PCH initiated reset, e.g TCO timer expiry */
+
+#define RR2_MSMI_FATAL BIT(3) /* CPU MSMI# fatal reset */
+#define RR2_CATERR_IERR BIT(2) /* CPU CATERR_IERR# induced reset */
+#define RR2_XDP BIT(1) /* XDP request reset */
+#define RR2_AUTO_POWER_OFF BIT(0) /* Auto power off */
+
+#define BC_WDT_ENA BIT(6) /* Watchdog timer enable */
+#define BC_FLASH_SELECT BIT(4) /* Flash select enable/status */
+#define WTT_THRESHOLD_MASK 0x3f
+
+#endif /*__PTX1K_BOOTFPGA_H__*/
--
1.9.1
On Fri, Oct 07, 2016 at 06:21:57PM +0300, Pantelis Antoniou wrote:
> Add Juniper's PTX1K Boot FPGA driver. Those FPGAs
> are present in Juniper's PTX series of routers.
>
> The MFD driver provices watchdog/mtd/hwmon devices.
>
> There are full device tree binding documents for the
> master mfd driver and for the slave driver.
>
> This patchset is against mainline as of today: v4.8-9431-g3477d16
> and is dependent on the "Juniper prerequisites" and
> "Juniper infrastructure" patchsets sent earlier.
You've exceeded the number of patches I'm willing to review at once for
one person and I'm tired of making the same comments. So fix anything
I've already mentioned elsewhere and send this series again.
Rob