linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/4] mfd: Kontron PLD mfd driver
@ 2013-04-08 17:15 Kevin Strasser
  2013-04-08 17:15 ` [PATCH 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
                   ` (5 more replies)
  0 siblings, 6 replies; 48+ messages in thread
From: Kevin Strasser @ 2013-04-08 17:15 UTC (permalink / raw)
  To: linux-kernel
  Cc: Michael Brunner, Samuel Ortiz, Wolfram Sang, Ben Dooks,
	linux-i2c, Grant Likely, Linus Walleij, Wim Van Sebroeck,
	linux-watchdog, Darren Hart, Michael Brunner, Greg Kroah-Hartman,
	Kevin Strasser

From: Michael Brunner <michael.brunner@kontron.com>

Add core MFD driver for the on-board PLD found on some Kontron embedded
modules. The PLD device may provide functions like watchdog, GPIO, UART
and I2C bus.

The following modules are supported:
	* COMe-bIP#
	* COMe-bPC2 (ETXexpress-PC)
	* COMe-bSC# (ETXexpress-SC T#)
	* COMe-cCT6
	* COMe-cDC2 (microETXexpress-DC)
	* COMe-cPC2 (microETXexpress-PC)
	* COMe-mCT10
	* COMe-mSP1 (nanoETXexpress-SP)
	* COMe-mTT10 (nanoETXexpress-TT)
	* ETX-OH

Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
---
 drivers/mfd/Kconfig        |   23 +
 drivers/mfd/Makefile       |    1 +
 drivers/mfd/kempld-core.c  | 1088 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/kempld.h |  152 +++++++
 4 files changed, 1264 insertions(+)
 create mode 100644 drivers/mfd/kempld-core.c
 create mode 100644 include/linux/mfd/kempld.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index c346941..2491843 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -912,6 +912,29 @@ config MFD_TIMBERDALE
 	The timberdale FPGA can be found on the Intel Atom development board
 	for in-vehicle infontainment, called Russellville.
 
+config MFD_KEMPLD
+	tristate "Support for Kontron module PLD device"
+	select MFD_CORE
+	help
+	  This is the core driver for the PLD device found on some Kontron
+	  ETX and COMexpress (ETXexpress) modules. The PLD device may provide
+	  functions like watchdog, GPIO, UART and I2C bus.
+
+	  The following modules are supported:
+		* COMe-bIP#
+		* COMe-bPC2 (ETXexpress-PC)
+		* COMe-bSC# (ETXexpress-SC T#)
+		* COMe-cCT6
+		* COMe-cDC2 (microETXexpress-DC)
+		* COMe-cPC2 (microETXexpress-PC)
+		* COMe-mCT10
+		* COMe-mSP1 (nanoETXexpress-SP)
+		* COMe-mTT10 (nanoETXexpress-TT)
+		* ETX-OH
+
+	  This driver can also be built as a module. If so, the module
+	  will be called kempld-core.
+
 config LPC_SCH
 	tristate "Intel SCH LPC"
 	depends on PCI && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index b90409c..cb1cb16 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -123,6 +123,7 @@ obj-$(CONFIG_MFD_DB8500_PRCMU)	+= db8500-prcmu.o
 obj-$(CONFIG_AB8500_CORE)	+= ab8500-core.o ab8500-sysctrl.o
 obj-$(CONFIG_MFD_TIMBERDALE)    += timberdale.o
 obj-$(CONFIG_PMIC_ADP5520)	+= adp5520.o
+obj-$(CONFIG_MFD_KEMPLD)    += kempld-core.o
 obj-$(CONFIG_LPC_SCH)		+= lpc_sch.o
 obj-$(CONFIG_LPC_ICH)		+= lpc_ich.o
 obj-$(CONFIG_MFD_RDC321X)	+= rdc321x-southbridge.o
diff --git a/drivers/mfd/kempld-core.c b/drivers/mfd/kempld-core.c
new file mode 100644
index 0000000..778e26b
--- /dev/null
+++ b/drivers/mfd/kempld-core.c
@@ -0,0 +1,1088 @@
+/*
+ *  kempld-core.c - Kontron PLD MFD core driver
+ *
+ *  Copyright (c) 2010-2013 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/dmi.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/kempld.h>
+
+#define KEMPLD_MAINTAIN_EFT_COMPATIBILITY	1
+
+static int kempld_platform_device_register(const struct dmi_system_id *id);
+static int kempld_get_mutex_set_index_generic(struct kempld_device_data *pld,
+					u8 index, unsigned int timeout);
+static void kempld_release_mutex_generic(struct kempld_device_data *pld);
+
+static int kempld_get_info(struct kempld_device_data *pld);
+static int kempld_get_info_NOW1(struct kempld_device_data *pld);
+static int kempld_get_info_generic(struct kempld_device_data *pld);
+static int kempld_get_features(struct kempld_device_data *pld);
+static int kempld_register_cells_generic(struct kempld_device_data *pld);
+static int kempld_register_cells_NOW1(struct kempld_device_data *pld);
+
+#define MAX_IDENT_LEN 4
+static char force_ident[MAX_IDENT_LEN + 1] = "";
+module_param_string(force_ident, force_ident, sizeof(force_ident), 0);
+MODULE_PARM_DESC(force_ident, "Force detection of specific product");
+
+/* this option is only here for debugging and should never be needed in
+ * production environments */
+static bool force_unlock;
+module_param(force_unlock, bool, 0);
+MODULE_PARM_DESC(force_unlock, "Force breaking the semaphore on driver load");
+
+/* this is the default CPLD configuration unless something else is defined */
+static const struct kempld_platform_data kempld_platform_data_generic = {
+	.pld_clock		= 33333333,
+	.ioport			= 0xa80,
+	.force_index_write	= 0,
+	.get_mutex_set_index	= kempld_get_mutex_set_index_generic,
+	.release_mutex		= kempld_release_mutex_generic,
+	.get_info		= kempld_get_info_generic,
+	.register_cells		= kempld_register_cells_generic,
+};
+
+/* the COMe-mSP1 (nanoETXexpress-SP) has an earlier version of the CPLD that
+ * works a bit different */
+static const struct kempld_platform_data kempld_platform_data_NOW1 = {
+	.pld_clock		= 33333333,
+	.ioport			= 0xa80,
+	.force_index_write	= 1,
+	.get_mutex_set_index	= NULL,
+	.release_mutex		= NULL,
+	.get_info		= kempld_get_info_NOW1,
+	.register_cells		= kempld_register_cells_NOW1,
+};
+
+static const struct dmi_system_id kempld_boardids[] = {
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CCR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bIP2"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CCR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bIP6"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CHR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-SC T2"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CHR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXe-SC T2"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CHR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bSC2"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CHR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-SC T6"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CHR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXe-SC T6"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CHR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bSC6"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-PC"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bPC2"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "CNTX",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "PXT"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "FRI2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BIOS_VERSION, "FRI2"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "FRI2",
+		.matches = {
+			DMI_MATCH(DMI_PRODUCT_NAME, "Fish River Island II"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "MBR1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETX-OH"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "NOW1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "nanoETXexpress-SP"),
+		},
+		.driver_data = (void *)&kempld_platform_data_NOW1,
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "NOW1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-mSP1"),
+		},
+		.driver_data = (void *)&kempld_platform_data_NOW1,
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "NTC1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "nanoETXexpress-TT"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "NTC1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "nETXe-TT"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "NTC1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-mTT"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "NUP1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-mCT"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "UNP1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "microETXexpress-DC"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "UNP1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-cDC2"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "UNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "microETXexpress-PC"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "UNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-cPC2"),
+		},
+	},
+	{
+		.callback = kempld_platform_device_register,
+		.ident = "UUP6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-cCT6"),
+		},
+	},
+	{}
+};
+
+static struct kempld_platform_data kempld_platform_data;
+
+static struct mfd_cell kempld_cell_i2c = {
+	.name = "kempld-i2c",
+};
+
+static struct mfd_cell kempld_cell_wdt = {
+	.name = "kempld-wdt",
+};
+
+static struct mfd_cell kempld_cell_NOW1_wdt = {
+	.name = "kempld_now1-wdt",
+};
+
+static struct mfd_cell kempld_cell_gpio = {
+	.name = "kempld-gpio",
+};
+
+static struct mfd_cell kempld_cell_uart = {
+	.name = "kempld-uart",
+};
+
+static struct mfd_cell kempld_cell_NOW1_gpio = {
+	.name = "kempld_now1-gpio",
+};
+
+static struct platform_device *kempld_pdev;
+
+/**
+ * kempld_read8 - read 8 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * This function reads an 8 bit register of the PLD and returns its value.
+ *
+ * In order for this function to work correctly, kempld_try_get_mutex_set_index
+ * or kempld_get_mutex_set_index has to be called before calling the function
+ * to acquire the mutex. Afterwards the mutex has to be released with
+ * kempld_release_mutex.
+ */
+u8 kempld_read8(struct kempld_device_data *pld, u8 index)
+{
+	kempld_set_index(pld, index);
+
+	return ioread8(pld->io_data);
+}
+EXPORT_SYMBOL(kempld_read8);
+
+/**
+ * kempld_write8 - write 8 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ * @data: new register value
+ *
+ * This function writes an 8 bit register of the PLD.
+ *
+ * In order for this function to work correctly, kempld_try_get_mutex_set_index
+ * or kempld_get_mutex_set_index has to be called before calling the function
+ * to acquire the mutex. Afterwards the mutex has to be released with
+ * kempld_release_mutex.
+ */
+void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data)
+{
+	kempld_set_index(pld, index);
+
+	iowrite8(data, pld->io_data);
+}
+EXPORT_SYMBOL(kempld_write8);
+
+/**
+ * kempld_read16 - read 16 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * This function reads a 16 bit register of the PLD and returns its value.
+ *
+ * In order for this function to work correctly, kempld_try_get_mutex_set_index
+ * or kempld_get_mutex_set_index has to be called before calling the function
+ * to acquire the mutex. Afterwards the mutex has to be released with
+ * kempld_release_mutex.
+ */
+u16 kempld_read16(struct kempld_device_data *pld, u8 index)
+{
+	BUG_ON(index+1 < index);
+
+	return kempld_read8(pld, index) | kempld_read8(pld, index+1) << 8;
+}
+EXPORT_SYMBOL(kempld_read16);
+
+/**
+ * kempld_write16 - write 16 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ * @data: new register value
+ *
+ * This function writes a 16 bit register of the PLD.
+ *
+ * In order for this function to work correctly, kempld_try_get_mutex_set_index
+ * or kempld_get_mutex_set_index has to be called before calling the function
+ * to acquire the mutex. Afterwards the mutex has to be released with
+ * kempld_release_mutex.
+ */
+void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data)
+{
+	BUG_ON(index+1 < index);
+
+	kempld_write8(pld, index, (u8)data);
+	kempld_write8(pld, index+1, (u8)(data>>8));
+}
+EXPORT_SYMBOL(kempld_write16);
+
+/**
+ * kempld_read32 - read 32 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * This function reads a 32 bit register of the PLD and returns its value.
+ *
+ * In order for this function to work correctly, kempld_try_get_mutex_set_index
+ * or kempld_get_mutex_set_index has to be called before calling the function
+ * to acquire the mutex. Afterwards the mutex has to be released with
+ * kempld_release_mutex.
+ */
+u32 kempld_read32(struct kempld_device_data *pld, u8 index)
+{
+	BUG_ON(index+3 < index);
+
+	return kempld_read16(pld, index) | kempld_read16(pld, index+2) << 16;
+}
+EXPORT_SYMBOL(kempld_read32);
+
+/**
+ * kempld_write32 - write 32 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ * @data: new register value
+ *
+ * This function writes a 32 bit register of the PLD.
+ *
+ * In order for this function to work correctly, kempld_try_get_mutex_set_index
+ * or kempld_get_mutex_set_index has to be called before calling the function
+ * to acquire the mutex. Afterwards the mutex has to be released with
+ * kempld_release_mutex.
+ */
+void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data)
+{
+	BUG_ON(index+3 < index);
+
+	kempld_write16(pld, index, (u16)data);
+	kempld_write16(pld, index+2, (u16)(data>>16));
+}
+EXPORT_SYMBOL(kempld_write32);
+
+/**
+ * kempld_set_index - change the current register index of the PLD
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * This function changes the register index of the PLD.
+ *
+ * If the PLD mutex has been acquired the whole time and the desired index is
+ * already set there might be no actual hardware access done in this function.
+ *
+ * In order for this function to work correctly, kempld_try_get_mutex_set_index
+ * or kempld_get_mutex_set_index has to be called before calling the function
+ * to acquire the mutex. Afterwards the mutex has to be released with
+ * kempld_release_mutex.
+ */
+void kempld_set_index(struct kempld_device_data *pld, u8 index)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	BUG_ON(pld->have_mutex == 0);
+
+	if (pld->last_index != index || pdata->force_index_write) {
+		iowrite8(index, pld->io_index);
+		pld->last_index = index;
+	}
+}
+EXPORT_SYMBOL(kempld_set_index);
+
+static int kempld_get_mutex_set_index_generic(struct kempld_device_data *pld,
+					u8 index, unsigned int timeout)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+	int data;
+
+	if (!pld->have_mutex) {
+		unsigned long loop_timeout = jiffies + (HZ*timeout)/1000;
+
+		while ((((data = ioread8(pld->io_index)) & KEMPLD_MUTEX_KEY)
+				== KEMPLD_MUTEX_KEY)) {
+			if (timeout != KEMPLD_MUTEX_NOTIMEOUT)
+				if (!time_before(jiffies, loop_timeout))
+					return -ETIMEDOUT;
+
+			/* we will have to wait until mutex is free */
+			spin_unlock_irqrestore(&pld->lock, pld->lock_flags);
+
+			/* give other tasks a chance to release the mutex */
+			schedule_timeout_interruptible(0);
+
+			spin_lock_irqsave(&pld->lock, pld->lock_flags);
+		}
+	} else
+		data = ioread8(pld->io_index);
+
+	if (KEMPLD_MAINTAIN_EFT_COMPATIBILITY
+		 || ((pld->last_index != (data & ~KEMPLD_MUTEX_KEY))
+		 || pdata->force_index_write)) {
+		iowrite8(index, pld->io_index);
+		pld->last_index = index;
+	}
+
+	return 0;
+}
+
+/**
+ * kempld_get_mutex_set_index - acquire the PLD mutex and set register index
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * This function acquires a PLD spinlock and the PLD mutex, additionally it
+ * also changes the register index. In order to do no unnecessary write cycles
+ * the index provided to this function should be the same that will be used
+ * with the first PLD access that is done afterwards.
+ *
+ * The function will block for at least 10 seconds if the mutex can't be
+ * acquired and issue a warning in that case. In order to not lock the device,
+ * the function assumes that the mutex has been acquired in that case.
+ *
+ * To release the spinlock and mutex kempld_release_mutex can be called.
+ * The spinlock and mutex should only be kept for a few milliseconds, in order
+ * to give other drivers a chance to work with the PLD.
+ */
+inline void kempld_get_mutex_set_index(struct kempld_device_data *pld,
+					u8 index)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	spin_lock_irqsave(&pld->lock, pld->lock_flags);
+
+	if (pdata->get_mutex_set_index) {
+		/* use a long timeout here as this shouldn't fail */
+		if (pdata->get_mutex_set_index(pld, index, 10000))
+			dev_warn(pld->dev, "semaphore broken!\n");
+
+		pld->have_mutex = 1;
+	} else {
+		pld->have_mutex = 1;
+		kempld_set_index(pld, index);
+	}
+}
+EXPORT_SYMBOL(kempld_get_mutex_set_index);
+
+/**
+ * kempld_try_get_mutex_set_index - try to acquire the PLD mutex and set
+ *                                   register index
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ * @timeout: timeout value
+ *
+ * This function tries to acquire a PLD spinlock and the PLD mutex,
+ * additionally it also changes the register index. In order to do no
+ * unnecessary write cycles the index provided to this function should be the
+ * same that will be used with the first PLD access that is done afterwards.
+ *
+ * The function will try to get the mutex for the time defined with the timeout
+ * parameter until it returns with -ETIMEDOUT. When the mutex is successfully
+ * acquired the function returns immediately with the return value 0.
+ *
+ * To release the spinlock and mutex kempld_release_mutex can be called.
+ * The spinlock and mutex should only be kept for a few milliseconds, in order
+ * to give other drivers a chance to work with the PLD.
+ */
+inline int kempld_try_get_mutex_set_index(struct kempld_device_data *pld,
+					u8 index, unsigned int timeout)
+{
+	int ret;
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	spin_lock_irqsave(&pld->lock, pld->lock_flags);
+
+	if (pdata->get_mutex_set_index) {
+		ret = pdata->get_mutex_set_index(pld, index, timeout);
+		if (ret == 0)
+			pld->have_mutex = 1;
+	} else {
+		pld->have_mutex = 1;
+		kempld_set_index(pld, index);
+		ret = 0;
+	}
+
+	if (ret != 0)
+		spin_unlock_irqrestore(&pld->lock, pld->lock_flags);
+
+	return ret;
+}
+EXPORT_SYMBOL(kempld_try_get_mutex_set_index);
+
+static void kempld_release_mutex_generic(struct kempld_device_data *pld)
+{
+	iowrite8(pld->last_index | KEMPLD_MUTEX_KEY, pld->io_index);
+}
+
+/**
+ * kempld_release_mutex - release PLD mutex
+ * @pld: kempld_device_data structure describing the PLD
+ *
+ * This function releases the spinlock und mutex previously acquired with the
+ * kempld_get_mutex_set_index or kempld_try_get_mutex_set_index functions.
+ */
+inline void kempld_release_mutex(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	BUG_ON(pld->have_mutex == 0);
+
+	if (pdata->release_mutex)
+		pdata->release_mutex(pld);
+
+	pld->have_mutex = 0;
+
+	spin_unlock_irqrestore(&pld->lock, pld->lock_flags);
+}
+EXPORT_SYMBOL(kempld_release_mutex);
+
+/**
+ * kempld_get_info - update device specific information
+ * @pld: kempld_device_data structure describing the PLD
+ *
+ * This function calls the configured board specific kempld_get_info_XXXX
+ * function which is responsible for gathering information about the specific
+ * hardware. The information is then stored within the pld structure.
+ */
+static int kempld_get_info(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	BUG_ON(pdata->get_info == NULL);
+
+	if (pdata->get_info)
+		return pdata->get_info(pld);
+
+	return -EIO;
+}
+
+static int kempld_get_info_NOW1(struct kempld_device_data *pld)
+{
+	kempld_get_mutex_set_index(pld, KEMPLD_VERSION_NOW1);
+
+	pld->info.major = kempld_read8(pld, KEMPLD_VERSION_NOW1);
+	pld->info.minor = 0;
+
+	pld->info.buildnr = kempld_read8(pld, KEMPLD_BUILDNR_NOW1);
+
+	pld->info.number = 0;
+	pld->info.type = 0x0;
+
+	pld->info.spec_major = 0;
+	pld->info.spec_minor = 0;
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_get_info_generic(struct kempld_device_data *pld)
+{
+	u16 data;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_VERSION);
+
+	data = kempld_read16(pld, KEMPLD_VERSION);
+	pld->info.minor = KEMPLD_VERSION_GET_MINOR(data);
+	pld->info.major = KEMPLD_VERSION_GET_MAJOR(data);
+	pld->info.number = KEMPLD_VERSION_GET_NUMBER(data);
+	pld->info.type = KEMPLD_VERSION_GET_TYPE(data);
+	pld->info.buildnr = kempld_read16(pld, KEMPLD_BUILDNR);
+
+	data = kempld_read8(pld, KEMPLD_SPEC);
+	if (data == 0xff) {
+		pld->info.spec_minor = 0;
+		pld->info.spec_major = 1;
+	} else {
+		pld->info.spec_minor = KEMPLD_SPEC_GET_MINOR(data);
+		pld->info.spec_major = KEMPLD_SPEC_GET_MAJOR(data);
+	}
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+/**
+ * kempld_get_features - retrieve features supported by device
+ * @pld: kempld_device_data structure describing the PLD
+ *
+ * This function tries to detect the features supported by the PLD device
+ */
+static int kempld_get_features(struct kempld_device_data *pld)
+{
+	if (pld->info.spec_major > 0) {
+		kempld_get_mutex_set_index(pld, KEMPLD_FEATURE);
+
+		pld->feature_mask = kempld_read16(pld, KEMPLD_FEATURE);
+
+		kempld_release_mutex(pld);
+	} else {
+		/* No way to automatically detect the features, they have to
+		 * be specified during cell registration */
+		pld->feature_mask = 0;
+	}
+
+	return 0;
+}
+
+/*
+ * kempld_register_cells - register cell drivers
+ *
+ * This function registers cell drivers for the detected hardware by calling
+ * the configured kempld_register_cells_XXXX function which is responsible
+ * to detect and register the needed cell drivers.
+ */
+static int kempld_register_cells(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	if (pdata->register_cells)
+		return pdata->register_cells(pld);
+
+	dev_warn(pld->dev, "no subdevices are supported\n");
+
+	return -ENODEV;
+}
+
+static int kempld_register_cells_NOW1(struct kempld_device_data *pld)
+{
+
+	/* The NOW1 has a fixed feature set that cannot be detected */
+
+	pld->feature_mask = KEMPLD_FEATURE_BIT_WATCHDOG
+		| KEMPLD_FEATURE_BIT_GPIO;
+
+	mfd_add_devices(pld->dev, -1, &kempld_cell_NOW1_wdt,
+		1, NULL, 0, NULL);
+	dev_info(pld->dev, "registered watchdog support\n");
+
+	mfd_add_devices(pld->dev, -1, &kempld_cell_NOW1_gpio,
+		1, NULL, 0, NULL);
+	dev_info(pld->dev, "registered GPIO support\n");
+
+	return 0;
+}
+
+static int kempld_register_cells_generic(struct kempld_device_data *pld)
+{
+	if (pld->feature_mask & KEMPLD_FEATURE_BIT_I2C) {
+		mfd_add_devices(pld->dev, -1, &kempld_cell_i2c,
+			1, NULL, 0, NULL);
+		dev_info(pld->dev, "registered I2C support\n");
+	}
+
+	if (pld->feature_mask & KEMPLD_FEATURE_BIT_WATCHDOG) {
+		mfd_add_devices(pld->dev, -1, &kempld_cell_wdt,
+			1, NULL, 0, NULL);
+		dev_info(pld->dev, "registered watchdog support\n");
+	}
+
+	if (pld->feature_mask & KEMPLD_FEATURE_BIT_GPIO) {
+		mfd_add_devices(pld->dev, -1, &kempld_cell_gpio,
+			1, NULL, 0, NULL);
+		dev_info(pld->dev, "registered GPIO support\n");
+	}
+
+	if (pld->feature_mask & KEMPLD_FEATURE_MASK_UART) {
+		mfd_add_devices(pld->dev, -1, &kempld_cell_uart,
+			1, NULL, 0, NULL);
+		dev_info(pld->dev, "registered UART support\n");
+	}
+
+	return 0;
+}
+
+static int kempld_detect_device(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+	int ret;
+	u8 index_reg;
+	int lock_broken = 0;
+	char *typestring;
+
+	spin_lock_irqsave(&pld->lock, pld->lock_flags);
+
+	/* Check for empty IO space */
+	index_reg = ioread8(pld->io_index);
+	if ((index_reg == 0xff) && (ioread8(pld->io_data) == 0xff)) {
+		ret = -ENODEV;
+		spin_unlock_irqrestore(&pld->lock, pld->lock_flags);
+		goto err_empty_io;
+	}
+
+	pld->last_index = index_reg & ~KEMPLD_MUTEX_KEY;
+
+	if ((index_reg & KEMPLD_MUTEX_KEY) == 0x00) {
+		/* lock is currently not acquired by anyone else */
+		/* on some PLD revisions we now already have acquired the
+		 * mutex, so release it before continuing */
+
+		/* We have to set the index first, to be sure to have the
+		 * lock in all HW revisions */
+		iowrite8(pld->last_index, pld->io_index);
+
+		if (pdata->release_mutex)
+			pdata->release_mutex(pld);
+	}
+
+	spin_unlock_irqrestore(&pld->lock, pld->lock_flags);
+
+	/* Now really try to get the mutex, but use a timeout for the case it
+	 * doesn't work */
+	ret = kempld_try_get_mutex_set_index(pld, pld->last_index, 1000);
+	if (ret) {
+		dev_warn(pld->dev,
+			 "timeout while waiting for device semaphore\n");
+		if (force_unlock) {
+			/* We pretend to have aqcuired the lock and go on in
+			 * the hope that it was only a single error */
+			dev_warn(pld->dev,
+				 "force_unlock enabled - ignoring semaphore\n");
+			spin_lock_irqsave(&pld->lock, pld->lock_flags);
+			pld->have_mutex = 1;
+			lock_broken = 1;
+		} else
+			goto err_get_mutex;
+	}
+
+	/* Check if the mutex works as expected */
+	/* This check is left out if the lock has been broken, as it may fail
+	 * if the lock gets released in the meantime by a parallel process */
+	if (pdata->get_mutex_set_index && !lock_broken) {
+		/* pretend to not have the mutex and try to get it, which
+		 * should fail if everything works */
+		pld->have_mutex = 0;
+		if (pdata->get_mutex_set_index(pld, pld->last_index, 0)
+			!= -ETIMEDOUT) {
+			dev_err(pld->dev, "semaphore function check failed\n");
+			ret = -EIO;
+
+			/* release the mutex anyway to be sure everything is
+			 * cleaned up */
+			kempld_release_mutex(pld);
+
+			goto err_check_mutex;
+		}
+		pld->have_mutex = 1;
+	}
+
+	kempld_release_mutex(pld);
+
+	/* from here on it should be save to rely on the device semaphore */
+
+	ret = kempld_get_info(pld);
+	if (ret)
+		goto err_get_info;
+
+	switch (pld->info.type) {
+	case 0:
+		typestring = "release";
+		break;
+	case 1:
+		typestring = "debug";
+		break;
+	case 2:
+		typestring = "custom";
+		break;
+	default:
+		dev_warn(pld->dev, "PLD type not specified");
+		typestring = "unspecified";
+	}
+
+	ret = kempld_get_features(pld);
+	if (ret)
+		goto err_get_features;
+
+	dev_info(pld->dev, "found Kontron PLD %d\n", pld->info.number);
+	dev_info(pld->dev, "%s version %d.%d build %d, specification %d.%d\n",
+		 typestring, pld->info.major, pld->info.minor,
+		 pld->info.buildnr, pld->info.spec_major,
+		 pld->info.spec_minor);
+
+	ret = kempld_register_cells(pld);
+	if (ret)
+		goto err_register_functions;
+
+	return 0;
+
+err_register_functions:
+err_get_features:
+err_get_info:
+err_check_mutex:
+err_get_mutex:
+err_empty_io:
+	return ret;
+}
+
+static int kempld_probe(struct platform_device *pdev)
+{
+	struct kempld_platform_data *pdata = pdev->dev.platform_data;
+	struct resource *ioport;
+	struct kempld_device_data *pld;
+	int ret;
+
+	dev_dbg(&pdev->dev, "probing for Kontron PLD on %s\n",
+		 pdata->board_id->ident);
+
+	pld = kzalloc(sizeof(struct kempld_device_data), GFP_KERNEL);
+	if (pld == NULL) {
+		dev_err(&pdev->dev, "unable to get memory for device data\n");
+		ret = -ENOMEM;
+		goto err_alloc_dev_data;
+	}
+
+	ioport = platform_get_resource(pdev, IORESOURCE_IO, 0);
+	if (!ioport) {
+		ret = -EINVAL;
+		goto err_get_resource;
+	}
+
+	pld->io_base = ioport_map(ioport->start, ioport->end - ioport->start);
+	if (!pld->io_base) {
+		ret = -ENOMEM;
+		goto err_iomap;
+	}
+
+	pld->io_index = pld->io_base;
+	pld->io_data = pld->io_base + 1;
+
+	pld->pld_clock = pdata->pld_clock;
+
+	spin_lock_init(&pld->lock);
+
+	pld->dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, pld);
+
+	ret = kempld_detect_device(pld);
+	if (ret)
+		goto err_detect_device;
+
+	return 0;
+
+err_detect_device:
+	ioport_unmap(pld->io_base);
+err_iomap:
+err_get_resource:
+	kfree(pld);
+err_alloc_dev_data:
+	return ret;
+}
+
+static int kempld_remove(struct platform_device *pdev)
+{
+	struct kempld_device_data *pld = platform_get_drvdata(pdev);
+
+	if (pld->have_mutex)
+		kempld_release_mutex(pld);
+
+	mfd_remove_devices(&pdev->dev);
+	platform_set_drvdata(pdev, NULL);
+
+	ioport_unmap(pld->io_base);
+
+	kfree(pld);
+
+	return 0;
+}
+
+static struct platform_driver kempld_driver = {
+	.driver		= {
+		.name	= "kempld",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= kempld_probe,
+	.remove		= kempld_remove,
+};
+
+static int kempld_platform_device_register(const struct dmi_system_id *id)
+{
+	struct platform_device *pdev;
+	static struct resource resource[1];
+	int ret = 0;
+
+	/* check if we already registered a kempld platform device,
+	 * there can only be one */
+	if (kempld_pdev != NULL) {
+		ret = -EINVAL;
+		goto err_device_already_registered;
+	}
+
+	/* use the generic platform data unless something else is specified */
+	if (id->driver_data != NULL)
+		memcpy(&kempld_platform_data, id->driver_data,
+			sizeof(kempld_platform_data));
+	else
+		memcpy(&kempld_platform_data, &kempld_platform_data_generic,
+			sizeof(kempld_platform_data));
+
+
+	pdev = platform_device_alloc("kempld", -1);
+	if (!pdev) {
+		ret = -ENOMEM;
+		goto err_device_alloc_failed;
+	}
+
+	kempld_platform_data.board_id = id;
+
+	ret = platform_device_add_data(pdev, &kempld_platform_data,
+					sizeof(kempld_platform_data));
+	if (ret)
+		goto err_add_data_failed;
+
+	resource[0].start = kempld_platform_data.ioport;
+	resource[0].end   = kempld_platform_data.ioport + 1;
+	resource[0].flags = IORESOURCE_IO;
+
+	ret = platform_device_add_resources(pdev, resource, 1);
+	if (ret)
+		goto err_device_add_resources;
+
+	ret = platform_device_add(pdev);
+	if (ret)
+		goto err_device_register_failed;
+
+	kempld_pdev = pdev;
+
+	return 0;
+
+err_device_register_failed:
+err_device_add_resources:
+err_add_data_failed:
+	platform_device_put(pdev);
+err_device_alloc_failed:
+err_device_already_registered:
+	return ret;
+}
+
+static int __init kempld_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&kempld_driver);
+	if (ret)
+		goto err_platform_driver_register;
+
+	/* check force parameter if a specific implementation should be
+	 * probed */
+	if (force_ident[0]) {
+		int found = 0;
+		const struct dmi_system_id *d;
+		for (d = kempld_boardids; d->matches[0].slot != DMI_NONE; d++)
+			if (strstr(d->ident, force_ident)) {
+				found++;
+				if (d->callback && d->callback(d))
+					break;
+			}
+
+		if (!found)
+			goto err_device_not_found;
+
+		return 0;
+	}
+
+	/* try to autodetect the board */
+	if (dmi_check_system(kempld_boardids))
+		return 0;
+
+err_device_not_found:
+	platform_driver_unregister(&kempld_driver);
+	ret = -ENODEV;
+err_platform_driver_register:
+	return ret;
+}
+
+static void __exit kempld_exit(void)
+{
+	/* unregister device first */
+	if (kempld_pdev) {
+		platform_device_unregister(kempld_pdev);
+		kempld_pdev = NULL;
+	}
+
+	platform_driver_unregister(&kempld_driver);
+}
+
+module_init(kempld_init);
+module_exit(kempld_exit);
+
+MODULE_DESCRIPTION("KEM PLD Core Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld-core");
diff --git a/include/linux/mfd/kempld.h b/include/linux/mfd/kempld.h
new file mode 100644
index 0000000..58ebba5
--- /dev/null
+++ b/include/linux/mfd/kempld.h
@@ -0,0 +1,152 @@
+/*
+ *  linux/mfd/kempld.h - Kontron PLD driver definitions
+ *
+ *  Copyright (c) 2010-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _LINUX_MFD_KEMPLD_H_
+#define _LINUX_MFD_KEMPLD_H_
+
+/* COMe-mSP1 (nanoETXexpress) specific definitions */
+#define KEMPLD_VERSION_NOW1	0x00
+#define KEMPLD_BUILDNR_NOW1	0x02
+
+/* generic register definitions */
+#define KEMPLD_MUTEX_KEY	0x80
+#define KEMPLD_VERSION		0x00
+#define		KEMPLD_VERSION_LSB		0x00
+#define		KEMPLD_VERSION_MSB		0x01
+#define		KEMPLD_VERSION_GET_MINOR(x)	(x & 0x1f)
+#define		KEMPLD_VERSION_GET_MAJOR(x)	((x >> 5) & 0x1f)
+#define		KEMPLD_VERSION_GET_NUMBER(x)	((x >> 10) & 0xf)
+#define		KEMPLD_VERSION_GET_TYPE(x)	((x >> 14) & 0x3)
+#define KEMPLD_BUILDNR		0x02
+#define		KEMPLD_BUILDNR_LSB		0x02
+#define		KEMPLD_BUILDNR_MSB		0x03
+#define KEMPLD_FEATURE		0x04
+#define		KEMPLD_FEATURE_LSB		0x04
+#define		KEMPLD_FEATURE_MSB		0x05
+#define		KEMPLD_FEATURE_BIT_I2C		(1 << 0)
+#define		KEMPLD_FEATURE_BIT_WATCHDOG	(1 << 1)
+#define		KEMPLD_FEATURE_BIT_GPIO		(1 << 2)
+#define		KEMPLD_FEATURE_MASK_UART	(7 << 3)
+#define		KEMPLD_FEATURE_BIT_NMI		(1 << 8)
+#define		KEMPLD_FEATURE_BIT_SMI		(1 << 9)
+#define		KEMPLD_FEATURE_BIT_SCI		(1 << 10)
+#define KEMPLD_SPEC		0x06
+#define		KEMPLD_SPEC_GET_MINOR(x)	(x & 0x0f)
+#define		KEMPLD_SPEC_GET_MAJOR(x)	((x >> 4) & 0x0f)
+#define KEMPLD_IRQ_GPIO				0x35
+#define KEMPLD_IRQ_I2C				0x36
+#define KEMPLD_CFG				0x37
+#define		KEMPLD_CFG_GPIO_I2C_MUX		(1<<0)
+#define		KEMPLD_CFG_BIOS_WP		(1<<7)
+
+#define	KEMPLD_TYPE_RELEASE	0x0
+#define	KEMPLD_TYPE_DEBUG	0x1
+#define	KEMPLD_TYPE_CUSTOM	0x2
+
+#define KEMPLD_MUTEX_NOTIMEOUT			((unsigned int)~0)
+
+/**
+ * struct kempld_info - PLD device information structure
+ * @major:	PLD major revision
+ * @minor:	PLD minor revision
+ * @buildnr:	PLD build number
+ * @number:	PLD board specific index
+ * @type:	PLD type
+ * @spec_major:	PLD FW specification major revision
+ * @spec_minor:	PLD FW specification minor revision
+ */
+struct kempld_info {
+	unsigned int major;
+	unsigned int minor;
+	unsigned int buildnr;
+	unsigned int number;
+	unsigned int type;
+	unsigned int spec_major;
+	unsigned int spec_minor;
+};
+
+/**
+ * struct kempld_device_data - Internal representation of the PLD device
+ * @io_base:		Pointer to the IO memory
+ * @io_index:		Pointer to the IO index register
+ * @io_data:		Pointer to the IO data register
+ * @pld_clock:		PLD clock frequency
+ * @feature_mask:	PLD feature mask
+ * @lock:		PLD spin-lock
+ * @lock_flags:		PLD spin-lock flags
+ * @have_mutex:		Bool value that indicates if mutex is aquired
+ * @last_index:		Last written index value
+ * @dev:		Pointer to kernel device structure
+ * @info:		KEMPLD info structure
+ */
+struct kempld_device_data {
+	void __iomem		*io_base;
+	void __iomem		*io_index;
+	void __iomem		*io_data;
+	u32			pld_clock;
+	u32			feature_mask;
+	spinlock_t		lock;
+	unsigned long		lock_flags;
+	int			have_mutex;
+	u8			last_index;
+	struct device		*dev;
+	struct kempld_info	info;
+};
+
+/**
+ * struct kempld_platform_data - PLD hardware configuration structure
+ * @pld_clock:		 PLD clock frequency
+ * @ioport:		 IO address of the PLD
+ * @force_index_write:	 Force writing the index register on every access
+ * @board_id:		 Board system ID structure
+ * @release_mutex:	 Pointer to PLD specific release_mutex function
+ * @get_mutex_set_index: Pointer to PLD specific get_mutex_set_index function
+ * @get_info:		 Pointer to PLD specific get_info function
+ * @register_cells:	 Pointer to PLD specific register_cells function
+ *
+ * This structure configures the PLD settings for a specific hardware platform.
+ */
+struct kempld_platform_data {
+	u32				pld_clock;
+	u32				ioport;
+	int				force_index_write;
+	const struct dmi_system_id	*board_id;
+	void (*release_mutex)(struct kempld_device_data *);
+	int (*get_mutex_set_index)(struct kempld_device_data *, u8,
+					unsigned int);
+	int (*get_info)(struct kempld_device_data *);
+	int (*register_cells)(struct kempld_device_data *);
+};
+
+extern void kempld_set_index(struct kempld_device_data *pld, u8 index);
+extern void kempld_get_mutex_set_index(struct kempld_device_data *pld,
+					u8 index);
+extern int kempld_try_get_mutex_set_index(struct kempld_device_data *pld,
+					u8 index, unsigned int timeout);
+extern void kempld_release_mutex(struct kempld_device_data *pld);
+
+extern u8 kempld_read8(struct kempld_device_data *pld, u8 index);
+extern void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data);
+extern u16 kempld_read16(struct kempld_device_data *pld, u8 index);
+extern void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data);
+extern u32 kempld_read32(struct kempld_device_data *pld, u8 index);
+extern void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data);
+
+#endif /* _LINUX_MFD_KEMPLD_H_ */
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 2/4] i2c: Kontron PLD i2c bus driver
  2013-04-08 17:15 [PATCH 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
@ 2013-04-08 17:15 ` Kevin Strasser
  2013-04-10 17:02   ` Guenter Roeck
  2013-04-08 17:15 ` [PATCH 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 48+ messages in thread
From: Kevin Strasser @ 2013-04-08 17:15 UTC (permalink / raw)
  To: linux-kernel
  Cc: Michael Brunner, Samuel Ortiz, Wolfram Sang, Ben Dooks,
	linux-i2c, Grant Likely, Linus Walleij, Wim Van Sebroeck,
	linux-watchdog, Darren Hart, Michael Brunner, Greg Kroah-Hartman,
	Kevin Strasser

From: Michael Brunner <michael.brunner@kontron.com>

Add i2c support for the on-board PLD found on some Kontron embedded
modules.

Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
---
 drivers/i2c/busses/Kconfig      |   20 ++
 drivers/i2c/busses/Makefile     |    1 +
 drivers/i2c/busses/i2c-kempld.c |  679 +++++++++++++++++++++++++++++++++++++++
 drivers/i2c/busses/i2c-kempld.h |   86 +++++
 4 files changed, 786 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-kempld.c
 create mode 100644 drivers/i2c/busses/i2c-kempld.h

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index adfee98..7aecd61 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -494,6 +494,26 @@ config I2C_IOP3XX
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-iop3xx.
 
+config I2C_KEMPLD
+	tristate "Kontron COM I2C"
+	depends on MFD_KEMPLD
+	help
+	  This enables support for the I2C bus interface on some Kontron ETX
+	  and COMexpress (ETXexpress) modules.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called i2c-kempld.
+
+config I2C_KEMPLD_MUX
+	bool "Enable MUXed I2C ports (EXPERIMENTAL)"
+	depends on I2C_KEMPLD && I2C_MUX
+	default n
+	help
+	  This enables support for additional I2C ports available on some
+	  modules. Usually those ports are for board internal usage and
+	  not routed outside the module.
+	  Do not use this option unless you know what you are doing!
+
 config I2C_MPC
 	tristate "MPC107/824x/85xx/512x/52xx/83xx/86xx"
 	depends on PPC
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 8f4fc23..411b8ce 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_I2C_IBM_IIC)	+= i2c-ibm_iic.o
 obj-$(CONFIG_I2C_IMX)		+= i2c-imx.o
 obj-$(CONFIG_I2C_INTEL_MID)	+= i2c-intel-mid.o
 obj-$(CONFIG_I2C_IOP3XX)	+= i2c-iop3xx.o
+obj-$(CONFIG_I2C_KEMPLD)    += i2c-kempld.o
 obj-$(CONFIG_I2C_MPC)		+= i2c-mpc.o
 obj-$(CONFIG_I2C_MV64XXX)	+= i2c-mv64xxx.o
 obj-$(CONFIG_I2C_MXS)		+= i2c-mxs.o
diff --git a/drivers/i2c/busses/i2c-kempld.c b/drivers/i2c/busses/i2c-kempld.c
new file mode 100644
index 0000000..c6b44e7
--- /dev/null
+++ b/drivers/i2c/busses/i2c-kempld.c
@@ -0,0 +1,679 @@
+/*
+ *  i2c-kempld.c: I2C bus driver for Kontron COM modules
+ *
+ *  Copyright (c) 2010-2013 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  The driver is based on the i2c-ocores driver by Peter Korsgaard.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#ifdef CONFIG_I2C_KEMPLD_MUX
+#include <linux/i2c-mux.h>
+#endif
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/mfd/kempld.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+
+#include "i2c-kempld.h"
+
+static int scl_frequency;
+static int i2c_bus = -1;
+static int i2c_mx_bus = -1;
+static bool force_polling;
+static int i2c_gpio_mux = -1;
+
+#ifdef CONFIG_I2C_KEMPLD_MUX
+static int kempld_i2cmux_select(struct i2c_adapter *adap, void *data, u32 chan)
+{
+	struct kempld_i2c_data *i2c = data;
+	struct kempld_device_data *pld = i2c->pld;
+	int ret = 0;
+
+	if ((i2c->state == STATE_DONE)
+		|| (i2c->state == STATE_ERROR)) {
+		if (i2c->mx != chan) {
+			kempld_get_mutex_set_index(pld, KEMPLD_I2C_MX);
+			i2c->mx = chan & 0x0f;
+			kempld_write8(pld, KEMPLD_I2C_MX, i2c->mx);
+			kempld_release_mutex(pld);
+		}
+
+		/* Reset controller if the last transfer ended with an error */
+		if (i2c->state == STATE_ERROR) {
+			u8 ctrl;
+
+			kempld_get_mutex_set_index(pld, KEMPLD_I2C_CMD);
+			ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
+			ctrl &= ~OCI2C_CTRL_EN;
+			kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
+			kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_IACK);
+			ctrl |= OCI2C_CTRL_EN;
+			kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
+			kempld_release_mutex(pld);
+		}
+
+	} else
+		ret = -EBUSY;
+
+	return ret;
+}
+
+static void kempld_i2cmux_del(struct kempld_i2c_data *i2c)
+{
+	int i;
+
+	for (i = 0; i <= i2c->mx_max; i++) {
+		if (i2c->mxadap[i]) {
+			i2c_del_mux_adapter(i2c->mxadap[i]);
+			i2c->mxadap[i] = NULL;
+		}
+	}
+}
+
+static int kempld_i2cmux_add(struct kempld_i2c_data *i2c)
+{
+	struct kempld_device_data *pld = i2c->pld;
+	int i;
+	int ret = -ENODEV;
+
+	for (i = 0; (i <= (i2c->mx_max)); i++) {
+		i2c->mxadap[i] = i2c_add_mux_adapter(&i2c->adap,
+							NULL, i2c, 0, i, 0,
+							kempld_i2cmux_select,
+							NULL);
+		if (!i2c->mxadap[i]) {
+			ret = -ENODEV;
+			dev_err(pld->dev,
+				"Failed to register MUX adapter %d\n", i);
+			goto add_mux_adapter_failed;
+		}
+	}
+
+	return 0;
+
+add_mux_adapter_failed:
+	kempld_i2cmux_del(i2c);
+
+	return ret;
+}
+
+#else
+#define kempld_i2cmux_add(x) (0)
+#define kempld_i2cmux_del(x) do {} while (0)
+#endif
+
+static int kempld_i2c_process(struct kempld_i2c_data *i2c)
+{
+	struct kempld_device_data *pld = i2c->pld;
+	struct i2c_msg *msg = i2c->msg;
+	u8 stat = kempld_read8(pld, KEMPLD_I2C_STATUS);
+
+	/* ready? */
+	if (stat & OCI2C_STAT_TIP)
+		return -EBUSY;
+
+	if ((i2c->state == STATE_DONE) || (i2c->state == STATE_ERROR)) {
+		/* stop has been sent */
+		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_IACK);
+		if (i2c->irq)
+			wake_up(&i2c->wait);
+		if (i2c->state == STATE_ERROR)
+			return -EIO;
+		else
+			return 0;
+	}
+
+	/* error? */
+	if (stat & OCI2C_STAT_ARBLOST) {
+		i2c->state = STATE_ERROR;
+		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_STOP);
+		return -EAGAIN;
+	}
+
+	if (i2c->state == STATE_INIT) {
+		/* check if bus is free */
+		if (stat & OCI2C_STAT_BUSY)
+			return -EBUSY;
+
+		i2c->state = STATE_ADDR;
+	}
+
+	if (i2c->state == STATE_ADDR) {
+		u8 addr;
+		/* 10 bit address? */
+		if (i2c->msg->flags & I2C_M_TEN) {
+			addr = 0xf0 | ((i2c->msg->addr >> 7) & 0x6);
+			i2c->state = STATE_ADDR10;
+		} else {
+			addr = (i2c->msg->addr << 1);
+			i2c->state = STATE_START;
+		}
+
+		/* set read bit if necessary */
+		addr |= (i2c->msg->flags & I2C_M_RD) ? 1 : 0;
+
+		kempld_write8(pld, KEMPLD_I2C_DATA, addr);
+		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_START);
+
+		return 0;
+	}
+
+	/* second part of 10 bit addressing */
+	if (i2c->state == STATE_ADDR10) {
+		kempld_write8(pld, KEMPLD_I2C_DATA, i2c->msg->addr & 0xff);
+		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_WRITE);
+
+		i2c->state = STATE_START;
+		return 0;
+	}
+
+	if ((i2c->state == STATE_START) || (i2c->state == STATE_WRITE)) {
+		i2c->state =
+			(msg->flags & I2C_M_RD) ? STATE_READ : STATE_WRITE;
+
+		if (stat & OCI2C_STAT_NACK) {
+			i2c->state = STATE_ERROR;
+			kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_STOP);
+			return -ENXIO;
+		}
+	} else
+		msg->buf[i2c->pos++] = kempld_read8(pld, KEMPLD_I2C_DATA);
+
+	/* end of msg? */
+	if (i2c->pos >= msg->len) {
+		i2c->nmsgs--;
+		i2c->msg++;
+		i2c->pos = 0;
+		msg = i2c->msg;
+
+		if (i2c->nmsgs) {	/* end? */
+			/* send start? */
+			if (!(msg->flags & I2C_M_NOSTART)) {
+				i2c->state = STATE_ADDR;
+				if (i2c->irq)
+					wake_up(&i2c->wait);
+				return 0;
+			} else
+				i2c->state = (msg->flags & I2C_M_RD)
+					? STATE_READ : STATE_WRITE;
+		} else {
+			i2c->state = STATE_DONE;
+			kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_STOP);
+			return 0;
+		}
+	}
+
+	if (i2c->state == STATE_READ) {
+		kempld_write8(pld, KEMPLD_I2C_CMD, i2c->pos == (msg->len-1) ?
+			OCI2C_CMD_READ_NACK : OCI2C_CMD_READ_ACK);
+	} else {
+		kempld_write8(pld, KEMPLD_I2C_DATA, msg->buf[i2c->pos++]);
+		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_WRITE);
+	}
+
+	return 0;
+}
+
+static irqreturn_t kempld_i2c_isr(int irq, void *dev_id)
+{
+	struct kempld_i2c_data *i2c = dev_id;
+
+	/* The actual ISR handler is put into a tasklet as it may block
+	 * and therefore rescheduling must be possible */
+	tasklet_schedule(&i2c->tasklet);
+
+	return IRQ_HANDLED;
+}
+
+void kempld_i2c_tasklet(unsigned long data)
+{
+	struct kempld_i2c_data *i2c = (struct kempld_i2c_data *)data;
+	struct kempld_device_data *pld = i2c->pld;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_I2C_STATUS);
+	kempld_i2c_process(i2c);
+	kempld_release_mutex(pld);
+}
+
+static int kempld_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+			int num)
+{
+	struct kempld_i2c_data *i2c = i2c_get_adapdata(adap);
+	struct kempld_device_data *pld = i2c->pld;
+	unsigned long timeout = jiffies + HZ;
+	int ret;
+
+	i2c->msg = msgs;
+	i2c->pos = 0;
+	i2c->nmsgs = num;
+	i2c->state = STATE_INIT;
+
+	/* handle the transfer */
+	while (time_before(jiffies, timeout)) {
+		kempld_get_mutex_set_index(pld, KEMPLD_I2C_STATUS);
+		ret = kempld_i2c_process(i2c);
+		kempld_release_mutex(pld);
+
+		if (i2c->irq && ((i2c->state >= STATE_START)
+				 || (i2c->state == STATE_ERROR))) {
+			wait_event_timeout(i2c->wait,
+					(i2c->state == STATE_ERROR) ||
+					(i2c->state == STATE_DONE) ||
+					(i2c->state == STATE_ADDR), HZ);
+			if (i2c->state == STATE_ERROR)
+				ret = -EIO;
+		}
+
+		if ((i2c->state == STATE_DONE)
+			|| (i2c->state == STATE_ERROR))
+			return (i2c->state == STATE_DONE) ? num : ret;
+
+		if (ret == 0)
+			timeout = jiffies + HZ;
+
+		usleep_range(5, 15);
+	}
+
+	i2c->state = STATE_ERROR;
+
+	return -ETIMEDOUT;
+}
+
+static void kempld_i2c_device_init(struct kempld_i2c_data *i2c)
+{
+	struct kempld_device_data *pld = i2c->pld;
+	long prescale;
+	u16 prescale_corr;
+	u8 cfg;
+	u8 ctrl;
+	u8 stat;
+	u8 mx;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_I2C_CONTROL);
+
+	ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
+	if (ctrl & OCI2C_CTRL_EN)
+		i2c->was_active = 1;
+
+	/* set bus frequency */
+	if (scl_frequency > 0) {
+		/* make sure the device is disabled */
+		ctrl &= ~(OCI2C_CTRL_EN|OCI2C_CTRL_IEN);
+		kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
+
+		/* The clock frequency calculation has been changed a bit
+		 * between the spec. revisions */
+		if (pld->info.spec_major == 1)
+			prescale = (pld->pld_clock / (scl_frequency*5)) - 1000;
+		else
+			prescale = (pld->pld_clock / (scl_frequency*4)) - 3000;
+
+		/* Prevent negative prescaler values */
+		if (prescale < 0)
+			prescale = 0;
+
+		/* Round to the best matching value */
+		prescale_corr = prescale / 1000;
+		if ((prescale % 1000) >= 500)
+			prescale_corr++;
+
+		kempld_write8(pld, KEMPLD_I2C_PRELOW, prescale_corr & 0xff);
+		kempld_write8(pld, KEMPLD_I2C_PREHIGH, prescale_corr >> 8);
+	}
+
+	/* Activate I2C bus output on GPIO pins */
+	if (i2c_gpio_mux > -1) {
+		cfg = kempld_read8(pld, KEMPLD_CFG);
+		if (i2c_gpio_mux)
+			cfg |= KEMPLD_CFG_GPIO_I2C_MUX;
+		else
+			cfg &= ~KEMPLD_CFG_GPIO_I2C_MUX;
+		kempld_write8(pld, KEMPLD_CFG, cfg);
+	}
+	cfg = kempld_read8(pld, KEMPLD_CFG);
+	if (cfg & KEMPLD_CFG_GPIO_I2C_MUX)
+		i2c->gpio_mux = 1;
+	else
+		i2c->gpio_mux = 0;
+	if (((i2c_gpio_mux > 0) && (!i2c->gpio_mux))
+		|| ((i2c_gpio_mux == 0) && (i2c->gpio_mux)))
+		dev_warn(pld->dev, "Unable to change GPIO I2C MUX setting\n");
+
+	/* Check how much multiplexed I2C busses we have */
+	mx = kempld_read8(pld, KEMPLD_I2C_MX);
+	if (pld->info.spec_major > 1) {
+		i2c->mx_max = KEMPLD_I2C_MX_GET_MAX(mx);
+		if (i2c->mx_max == 0xf) /* No multiplexer available */
+			i2c->mx_max = 0;
+	} else
+		i2c->mx_max = 1;
+	/* 2 busses should be enough for all
+	 * boards using specification revision 1 */
+
+	/* Check which MX setting should be set */
+	if ((i2c_mx_bus == -1) || (i2c_mx_bus > i2c->mx_max)) {
+		if (i2c_mx_bus > i2c->mx_max) {
+			dev_err(pld->dev,
+				"bus selected with i2c_mx_bus not available "
+				"- leaving MX setting unchanged\n");
+		}
+		i2c->mx = mx & KEMPLD_I2C_MX_MASK;
+	} else
+		i2c->mx = i2c_mx_bus;
+
+	/* Connect the controller to the chosen bus output */
+	kempld_write8(pld, KEMPLD_I2C_MX, i2c->mx);
+
+	/* enable the device */
+	kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_IACK);
+	ctrl |= OCI2C_CTRL_EN;
+	kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
+
+	/* If bus is busy send a STOP signal to be sure the controller is
+	 * not hanging... */
+	stat = kempld_read8(pld, KEMPLD_I2C_STATUS);
+	if (stat & OCI2C_STAT_BUSY) {
+		dev_warn(pld->dev,
+			 "I2C bus is busy - generating stop signal\n");
+		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_STOP);
+	}
+
+	kempld_release_mutex(pld);
+
+	if ((pld->info.spec_major == 1) && (i2c->mx == 0xf))
+		i2c->mx = 0;
+}
+
+static void kempld_i2c_irq_enable(struct kempld_i2c_data *i2c)
+{
+	struct kempld_device_data *pld = i2c->pld;
+	u8 irq, ctrl;
+	int ret;
+
+	irq = i2c->irq;
+
+	/* This only has to be done once */
+	if (i2c->irq == 0) {
+		kempld_get_mutex_set_index(pld, KEMPLD_IRQ_I2C);
+		irq = kempld_read8(pld, KEMPLD_IRQ_I2C);
+		kempld_release_mutex(pld);
+
+		/* Leave if interrupts are not supported by the I2C core */
+		if ((irq & 0xf0) == 0xf0)
+			return;
+		irq &= 0x0f;
+		if (irq == 0)
+			return;
+
+		/* Initialize interrupt handlers if not already done */
+		init_waitqueue_head(&i2c->wait);
+		tasklet_init(&i2c->tasklet, kempld_i2c_tasklet,
+				(unsigned long)i2c);
+
+		ret = devm_request_irq(pld->dev, irq, kempld_i2c_isr,
+					IRQF_SHARED, i2c->adap.name, i2c);
+		if (ret) {
+			dev_err(pld->dev,
+				"Unable to claim IRQ - using polling mode\n");
+			return;
+		}
+	}
+
+	/* Now enable interrupts in the controller */
+	kempld_get_mutex_set_index(pld, KEMPLD_I2C_CONTROL);
+	ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
+	ctrl |= OCI2C_CTRL_IEN;
+	kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
+	kempld_release_mutex(pld);
+
+	i2c->irq = irq & 0x0f;
+}
+
+static void kempld_i2c_irq_disable(struct kempld_i2c_data *i2c)
+{
+	struct kempld_device_data *pld = i2c->pld;
+	u8 ctrl;
+	int irq;
+
+	if (i2c->irq == 0)
+		return;
+
+	irq = i2c->irq;
+	i2c->irq = 0;
+
+	tasklet_kill(&i2c->tasklet);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_I2C_CONTROL);
+	ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
+	ctrl &= ~OCI2C_CTRL_IEN;
+	kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
+	kempld_release_mutex(pld);
+
+	devm_free_irq(pld->dev, irq, i2c);
+}
+
+static u32 kempld_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR
+		| I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm kempld_i2c_algorithm = {
+	.master_xfer	= kempld_i2c_xfer,
+	.functionality	= kempld_i2c_func,
+};
+
+static struct i2c_adapter kempld_i2c_adapter = {
+	.owner		= THIS_MODULE,
+	.name		= "i2c-kempld",
+	.class		= I2C_CLASS_HWMON | I2C_CLASS_SPD,
+	.algo		= &kempld_i2c_algorithm,
+};
+
+static int kempld_i2c_get_scl_frequency(struct kempld_i2c_data *i2c)
+{
+	struct kempld_device_data *pld = i2c->pld;
+	int frequency;
+	u16 prescale;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_I2C_PRELOW);
+
+	prescale = kempld_read8(pld, KEMPLD_I2C_PRELOW)
+		| kempld_read8(pld, KEMPLD_I2C_PREHIGH)<<8;
+
+	kempld_release_mutex(pld);
+
+	/* The clock frequency calculation has been changed a bit
+	 * between the spec. revisions */
+	if (pld->info.spec_major == 1)
+		frequency = (pld->pld_clock / (prescale + 1)) / 5000;
+	else
+		frequency = (pld->pld_clock / (prescale + 3)) / 4000;
+
+	return frequency;
+}
+
+static int kempld_i2c_probe(struct platform_device *pdev)
+{
+	struct kempld_i2c_data *i2c;
+	struct kempld_device_data *pld;
+	int ret;
+
+	pld = dev_get_drvdata(pdev->dev.parent);
+
+	i2c = kzalloc(sizeof(*i2c), GFP_KERNEL);
+	if (!i2c)
+		return -ENOMEM;
+
+	i2c->pld = pld;
+
+	kempld_i2c_device_init(i2c);
+
+	/* hook up driver to tree */
+	platform_set_drvdata(pdev, i2c);
+	i2c->adap = kempld_i2c_adapter;
+	i2c_set_adapdata(&i2c->adap, i2c);
+	i2c->adap.dev.parent = &pdev->dev;
+
+	i2c->irq = 0;
+	if (!force_polling)
+		kempld_i2c_irq_enable(i2c);
+
+	/* add I2C adapter to I2C tree */
+	i2c->adap.nr = i2c_bus;
+	ret = i2c_add_numbered_adapter(&i2c->adap);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to add adapter\n");
+		goto add_adapter_failed;
+	}
+
+	ret = kempld_i2cmux_add(i2c);
+	if (ret)
+		goto add_mux_adapters_failed;
+
+	dev_info(pld->dev, "I2C bus initialized with %d kHz SCL frequency\n",
+		 kempld_i2c_get_scl_frequency(i2c));
+	dev_info(pld->dev, "I2C MUX connected to bus %d (available: %s%d)\n",
+		 i2c->mx, i2c->mx_max ? "0-" : "", i2c->mx_max);
+	dev_info(pld->dev, "I2C IRQs %s\n", i2c->irq ? "enabled" : "disabled");
+	if (i2c->gpio_mux)
+		dev_info(pld->dev, "GPIO I2C MUX pins enabled\n");
+
+	return 0;
+
+add_mux_adapters_failed:
+	i2c_del_adapter(&i2c->adap);
+add_adapter_failed:
+	kfree(i2c);
+
+	return ret;
+}
+
+static int kempld_i2c_remove(struct platform_device *pdev)
+{
+	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = i2c->pld;
+	u8 ctrl;
+
+	kempld_i2c_irq_disable(i2c);
+
+	if (!i2c->was_active) {
+		/* disable I2C logic if it was not activated before the
+		 * driver loaded */
+		kempld_get_mutex_set_index(pld, KEMPLD_I2C_CONTROL);
+		ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
+		ctrl &= ~OCI2C_CTRL_EN;
+		kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
+		kempld_release_mutex(pld);
+	}
+
+	/* remove adapter & data */
+	kempld_i2cmux_del(i2c);
+	i2c_del_adapter(&i2c->adap);
+
+	platform_set_drvdata(pdev, NULL);
+
+	kfree(i2c);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int kempld_i2c_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = i2c->pld;
+	u8 ctrl;
+
+	kempld_i2c_irq_disable(i2c);
+
+	if (!i2c->was_active) {
+		/* make sure the device is disabled */
+		kempld_get_mutex_set_index(pld, KEMPLD_I2C_CONTROL);
+		ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
+		ctrl &= ~OCI2C_CTRL_EN;
+		kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
+		kempld_release_mutex(pld);
+	}
+
+	return 0;
+}
+
+static int kempld_i2c_resume(struct platform_device *pdev)
+{
+	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
+
+	kempld_i2c_device_init(i2c);
+	kempld_i2c_irq_enable(i2c);
+
+	return 0;
+}
+#else
+#define kempld_i2c_suspend	NULL
+#define kempld_i2c_resume	NULL
+#endif
+
+static struct platform_driver kempld_i2c_driver = {
+	.driver = {
+		.name = "kempld-i2c",
+		.owner = THIS_MODULE,
+	},
+	.probe		= kempld_i2c_probe,
+	.remove		= kempld_i2c_remove,
+	.suspend	= kempld_i2c_suspend,
+	.resume		= kempld_i2c_resume,
+};
+
+static int __init kempld_i2c_init(void)
+{
+	/* Check if a valid value for the i2c_mx_bus parameter is provided */
+	if ((i2c_mx_bus != -1) && (i2c_mx_bus & ~KEMPLD_I2C_MX_MASK))
+		return -EINVAL;
+
+	return platform_driver_register(&kempld_i2c_driver);
+}
+
+static void __exit kempld_i2c_exit(void)
+{
+	platform_driver_unregister(&kempld_i2c_driver);
+}
+
+module_init(kempld_i2c_init);
+module_exit(kempld_i2c_exit);
+
+module_param(scl_frequency, int, 0);
+module_param(i2c_bus, int, 0);
+module_param(i2c_mx_bus, int, 0);
+module_param(force_polling, bool, 0);
+module_param(i2c_gpio_mux, int, 0);
+
+MODULE_DESCRIPTION("KEM PLD I2C Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld_i2c");
+MODULE_PARM_DESC(scl_frequency, "Set I2C SCL frequency (in kHz) default=0");
+MODULE_PARM_DESC(i2c_bus, "Set I2C bus (-1 for dynamic assignment");
+MODULE_PARM_DESC(i2c_mx_bus, "Set I2C MX bus (0-15, default=-1 (FW default))");
+MODULE_PARM_DESC(force_polling, "Force polling mode");
+MODULE_PARM_DESC(i2c_gpio_mux, "Enable I2C port on GPIO out");
diff --git a/drivers/i2c/busses/i2c-kempld.h b/drivers/i2c/busses/i2c-kempld.h
new file mode 100644
index 0000000..2229662
--- /dev/null
+++ b/drivers/i2c/busses/i2c-kempld.h
@@ -0,0 +1,86 @@
+/*
+ *  i2c-kempld.h - Kontron PLD I2C driver definitions
+ *
+ *  Copyright (c) 2010-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _KEMPLD_I2C_H_
+#define _KEMPLD_I2C_H_
+
+struct kempld_i2c_data {
+	struct i2c_adapter		adap;
+	struct i2c_adapter		*mxadap[15];
+	struct i2c_msg			*msg;
+	int				pos;
+	int				nmsgs;
+	int				state; /* see STATE_ */
+	int				was_active;
+	int				mx;
+	int				mx_max;
+	int				gpio_mux;
+	wait_queue_head_t		wait;
+	struct tasklet_struct		tasklet;
+	int				irq;
+	struct kempld_device_data	*pld;
+};
+
+/* I2C register definitions */
+#define KEMPLD_I2C_PRELOW	0x0b
+#define KEMPLD_I2C_PREHIGH	0x0c
+#define KEMPLD_I2C_CONTROL	0x0d
+#define KEMPLD_I2C_DATA		0x0e
+#define KEMPLD_I2C_CMD		0x0f /* write only */
+#define		KEMPLD_I2C_CMD_STA	0x80
+#define		KEMPLD_I2C_CMD_STO	0x40
+#define		KEMPLD_I2C_CMD_RD	0x20
+#define		KEMPLD_I2C_CMD_WR	0x10
+#define		KEMPLD_I2C_CMD_NACK	0x08
+#define		KEMPLD_I2C_CMD_IACK	0x01
+#define KEMPLD_I2C_STATUS	0x0f /* read only, same address as
+					KEMPLD_I2C_CMD */
+#define KEMPLD_I2C_MX		0x15
+#define		KEMPLD_I2C_MX_GET_MAX(x)	((x & 0xf0)>>4)
+#define		KEMPLD_I2C_MX_MASK		0x0f
+
+#define STATE_DONE		0
+#define STATE_INIT		1
+#define STATE_ADDR		2
+#define STATE_ADDR10		3
+#define STATE_START		4
+#define STATE_WRITE		5
+#define STATE_READ		6
+#define STATE_ERROR		7
+
+/* defines taken from i2c-ocores */
+#define OCI2C_CTRL_IEN		0x40
+#define OCI2C_CTRL_EN		0x80
+
+#define OCI2C_CMD_START		0x91
+#define OCI2C_CMD_STOP		0x41
+#define OCI2C_CMD_READ		0x21
+#define OCI2C_CMD_WRITE		0x11
+#define OCI2C_CMD_READ_ACK	0x21
+#define OCI2C_CMD_READ_NACK	0x29
+#define OCI2C_CMD_IACK		0x01
+
+#define OCI2C_STAT_IF		0x01
+#define OCI2C_STAT_TIP		0x02
+#define OCI2C_STAT_ARBLOST	0x20
+#define OCI2C_STAT_BUSY		0x40
+#define OCI2C_STAT_NACK		0x80
+
+#endif /* _KEMPLD_I2C_H_ */
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 3/4] gpio: Kontron PLD gpio driver
  2013-04-08 17:15 [PATCH 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
  2013-04-08 17:15 ` [PATCH 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
@ 2013-04-08 17:15 ` Kevin Strasser
  2013-04-09  8:46   ` Linus Walleij
  2013-04-10 20:45   ` Linus Walleij
  2013-04-08 17:15 ` [PATCH 4/4] watchdog: Kontron PLD watchdog timer Kevin Strasser
                   ` (3 subsequent siblings)
  5 siblings, 2 replies; 48+ messages in thread
From: Kevin Strasser @ 2013-04-08 17:15 UTC (permalink / raw)
  To: linux-kernel
  Cc: Michael Brunner, Samuel Ortiz, Wolfram Sang, Ben Dooks,
	linux-i2c, Grant Likely, Linus Walleij, Wim Van Sebroeck,
	linux-watchdog, Darren Hart, Michael Brunner, Greg Kroah-Hartman,
	Kevin Strasser

From: Michael Brunner <michael.brunner@kontron.com>

Add gpio support for the on-board PLD found on some Kontron embedded
modules.

Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
---
 drivers/gpio/Kconfig            |   22 ++
 drivers/gpio/Makefile           |    2 +
 drivers/gpio/gpio-kempld.c      |  476 +++++++++++++++++++++++++++++++++++++++
 drivers/gpio/gpio-kempld.h      |   50 ++++
 drivers/gpio/gpio-kempld_now1.c |  280 +++++++++++++++++++++++
 5 files changed, 830 insertions(+)
 create mode 100644 drivers/gpio/gpio-kempld.c
 create mode 100644 drivers/gpio/gpio-kempld.h
 create mode 100644 drivers/gpio/gpio-kempld_now1.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 93aaadf..88e1bc9 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -658,6 +658,28 @@ config GPIO_UCB1400
 	  This enables support for the Philips UCB1400 GPIO pins.
 	  The UCB1400 is an AC97 audio codec.
 
+comment "LPC GPIO expanders:"
+
+config GPIO_KEMPLD
+	tristate "Kontron COM GPIO"
+	depends on MFD_KEMPLD
+	help
+	  This enables support for the PLD GPIO interface on some Kontron ETX
+	  and COMexpress (ETXexpress) modules.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called gpio-kempld.
+
+config GPIO_NOW1_KEMPLD
+	tristate "Kontron COMe-mSP1 (nanoETXexpress-SP) GPIO"
+	depends on MFD_KEMPLD
+	help
+	  This enables support for the PLD GPIO interface on the Kontron
+	  COMe-mSP1 (nanoETXexpress-SP) module.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called gpio-kempld_now1.
+
 comment "MODULbus GPIO expanders:"
 
 config GPIO_JANZ_TTL
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 22e07bc..59919db 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -28,6 +28,8 @@ obj-$(CONFIG_GPIO_GE_FPGA)	+= gpio-ge.o
 obj-$(CONFIG_GPIO_ICH)		+= gpio-ich.o
 obj-$(CONFIG_GPIO_IT8761E)	+= gpio-it8761e.o
 obj-$(CONFIG_GPIO_JANZ_TTL)	+= gpio-janz-ttl.o
+obj-$(CONFIG_GPIO_KEMPLD)   += gpio-kempld.o
+obj-$(CONFIG_GPIO_NOW1_KEMPLD) += gpio-kempld_now1.o
 obj-$(CONFIG_ARCH_KS8695)	+= gpio-ks8695.o
 obj-$(CONFIG_GPIO_LANGWELL)	+= gpio-langwell.o
 obj-$(CONFIG_ARCH_LPC32XX)	+= gpio-lpc32xx.o
diff --git a/drivers/gpio/gpio-kempld.c b/drivers/gpio/gpio-kempld.c
new file mode 100644
index 0000000..e18967d
--- /dev/null
+++ b/drivers/gpio/gpio-kempld.c
@@ -0,0 +1,476 @@
+/*
+ *  kempld_gpio.c - Kontron PLD GPIO driver
+ *
+ *  Copyright (c) 2010-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/mfd/kempld.h>
+#include <linux/seq_file.h>
+
+#include "gpio-kempld.h"
+
+static int gpiobase = -1;
+static int gpioien = 0x00;
+static int gpioevt_lvl_edge = -1;
+static int gpioevt_low_high = -1;
+static int gpionmien = 0x00;
+
+static int kempld_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+	int status;
+
+	pld = gpio->pld;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_GPIO_LVL_NUM(offset));
+
+	status = kempld_read8(pld, KEMPLD_GPIO_LVL_NUM(offset));
+	status &= KEMPLD_GPIO_MASK(offset);
+
+	kempld_release_mutex(pld);
+
+	return status;
+}
+
+static void kempld_gpio_set(struct gpio_chip *chip, unsigned offset,
+				int value)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+	int status;
+
+
+	kempld_get_mutex_set_index(pld, KEMPLD_GPIO_LVL_NUM(offset));
+
+	status = kempld_read8(pld, KEMPLD_GPIO_LVL_NUM(offset));
+	if (value)
+		status |= KEMPLD_GPIO_MASK(offset);
+	else
+		status &= ~KEMPLD_GPIO_MASK(offset);
+	kempld_write8(pld, KEMPLD_GPIO_LVL_NUM(offset), status);
+
+	kempld_release_mutex(pld);
+}
+
+static int kempld_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+	int status;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_GPIO_DIR_NUM(offset));
+
+	status = kempld_read8(pld, KEMPLD_GPIO_DIR_NUM(offset));
+	status &= ~KEMPLD_GPIO_MASK(offset);
+	kempld_write8(pld, KEMPLD_GPIO_DIR_NUM(offset), status);
+
+	kempld_release_mutex(pld);
+
+
+	return 0;
+}
+
+static int kempld_gpio_direction_output(struct gpio_chip *chip,
+					 unsigned offset, int value)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+	int status;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_GPIO_LVL_NUM(offset));
+
+	status = kempld_read8(pld, KEMPLD_GPIO_LVL_NUM(offset));
+	if (value)
+		status |= KEMPLD_GPIO_MASK(offset);
+	else
+		status &= ~KEMPLD_GPIO_MASK(offset);
+	kempld_write8(pld, KEMPLD_GPIO_LVL_NUM(offset), status);
+
+	status = kempld_read8(pld, KEMPLD_GPIO_DIR_NUM(offset));
+	status |= KEMPLD_GPIO_MASK(offset);
+	kempld_write8(pld, KEMPLD_GPIO_DIR_NUM(offset), status);
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	return gpio->irq;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int kempld_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+	int status;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_GPIO_DIR_NUM(offset));
+
+	status = kempld_read8(pld, KEMPLD_GPIO_DIR_NUM(offset));
+	status &= KEMPLD_GPIO_MASK(offset);
+
+	kempld_release_mutex(pld);
+
+
+	return status ? 1 : 0;
+}
+
+static int kempld_gpio_get_ien(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+	int status;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_GPIO_IEN_NUM(offset));
+
+	status = kempld_read8(pld, KEMPLD_GPIO_IEN_NUM(offset));
+	status &= KEMPLD_GPIO_MASK(offset);
+
+	kempld_release_mutex(pld);
+
+
+	return status ? 1 : 0;
+}
+
+static int kempld_gpio_get_evt_lvl_edge(struct gpio_chip *chip,
+					unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+	int status;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_GPIO_EVT_LVL_EDGE_NUM(offset));
+
+	status = kempld_read8(pld, KEMPLD_GPIO_EVT_LVL_EDGE_NUM(offset));
+	status &= KEMPLD_GPIO_MASK(offset);
+
+	kempld_release_mutex(pld);
+
+
+	return status ? 1 : 0;
+}
+
+static int kempld_gpio_get_evt_high_low(struct gpio_chip *chip,
+					unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+	int status;
+
+	gpio = dev_get_drvdata(chip->dev);
+	pld = gpio->pld;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_GPIO_EVT_LOW_HIGH_NUM(offset));
+
+	status = kempld_read8(pld, KEMPLD_GPIO_EVT_LOW_HIGH_NUM(offset));
+	status &= KEMPLD_GPIO_MASK(offset);
+
+	kempld_release_mutex(pld);
+
+
+	return status ? 1 : 0;
+}
+
+static int kempld_gpio_get_nmien(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+	int status;
+
+	gpio = dev_get_drvdata(chip->dev);
+	pld = gpio->pld;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_GPIO_NMIEN_NUM(offset));
+
+	status = kempld_read8(pld, KEMPLD_GPIO_NMIEN_NUM(offset));
+	status &= KEMPLD_GPIO_MASK(offset);
+
+	kempld_release_mutex(pld);
+
+
+	return status ? 1 : 0;
+}
+
+static void kempld_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+	int i;
+
+	for (i = 0; i < chip->ngpio; i++) {
+		int gpio = i + chip->base;
+
+		seq_printf(s, " gpio-%-3d %s %s", gpio,
+				kempld_gpio_get_direction(chip, i)
+				? "out" : "in",
+				kempld_gpio_get(chip, i)
+				? "hi" : "lo");
+		seq_printf(s, ", event on %s (irq %s, nmi %s)\n",
+				(kempld_gpio_get_evt_lvl_edge(chip, i)
+				? (kempld_gpio_get_evt_high_low(chip, i)
+				? "rising edge" : "falling edge") :
+				(kempld_gpio_get_evt_high_low(chip, i)
+				? "high level" : "low level")),
+				kempld_gpio_get_ien(chip, i)
+				? "enabled" : "disabled",
+				kempld_gpio_get_nmien(chip, i)
+				? "enabled" : "disabled");
+	}
+}
+#else
+#define kempld_gpio_dbg_show NULL
+#endif
+
+static int kempld_gpio_setup_event(struct kempld_gpio_data *gpio)
+{
+	struct kempld_device_data *pld = gpio->pld;
+	struct gpio_chip *chip = &gpio->chip;
+	int irq;
+
+	irq = gpio->irq;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_IRQ_GPIO);
+	irq = kempld_read8(pld, KEMPLD_IRQ_GPIO);
+
+	/* Leave if interrupts are not supported by the GPIO core */
+	if ((irq & 0xf0) == 0xf0)
+		return 0;
+
+	gpio->irq = irq & 0x0f;
+
+	if (gpioien & !gpio->mask) {
+		dev_err(chip->dev, "gpioien parameter is invalid");
+		gpio->irq = 0;
+	}
+
+	if (gpionmien & !gpio->mask) {
+		dev_err(chip->dev, "gpionmien parameter is invalid");
+		gpio->irq = 0;
+	}
+
+	if ((gpioevt_lvl_edge & !gpio->mask) && (gpioevt_lvl_edge != -1)) {
+		dev_err(chip->dev, "gpioevt_lvl_edge parameter is invalid");
+		gpio->irq = 0;
+	}
+
+	if ((gpioevt_low_high & !gpio->mask) && (gpioevt_low_high != -1)) {
+		dev_err(chip->dev, "gpioevt_low_high parameter is invalid");
+		gpio->irq = 0;
+	}
+
+	if (!gpio->irq)
+		return -EIO;
+
+	if (gpioevt_lvl_edge != -1)
+		kempld_write8(pld, KEMPLD_GPIO_EVT_LVL_EDGE, gpioevt_lvl_edge);
+
+	if (gpioevt_low_high != -1)
+		kempld_write8(pld, KEMPLD_GPIO_EVT_LOW_HIGH, gpioevt_low_high);
+
+	kempld_write8(pld, KEMPLD_GPIO_NMIEN, gpionmien);
+	kempld_write8(pld, KEMPLD_GPIO_IEN, gpioien);
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+
+static int kempld_gpio_detect(struct kempld_gpio_data *gpio)
+{
+	struct kempld_device_data *pld = gpio->pld;
+	struct gpio_chip *chip = &gpio->chip;
+	u16 evt, evt_back;
+	int i;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_GPIO_IEN);
+
+	/* Backup event register as it might be already initialized */
+	evt_back = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE);
+
+	/* Disable interrupt enables and set event register to zero */
+	kempld_write16(pld, KEMPLD_GPIO_IEN, 0x0000);
+	kempld_write16(pld, KEMPLD_GPIO_NMIEN, 0x0000);
+	kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, 0x0000);
+
+	/* Read back event register */
+	evt = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE);
+
+	/* Restore event register */
+	kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, evt_back);
+
+	kempld_release_mutex(pld);
+
+	gpio->mask = ~evt;
+
+	/* Now check how many GPIO pins we have */
+	for (i = 0; i < KEMPLD_GPIO_MAX_NUM; i++) {
+		if (evt & 0x1)
+			break;
+		evt >>= 1;
+	}
+
+	chip->ngpio = i;
+
+	return 0;
+}
+
+static int kempld_gpio_probe(struct platform_device *pdev)
+{
+	struct kempld_device_data *pld;
+	struct kempld_gpio_data *gpio;
+	struct gpio_chip *chip;
+	int ret;
+
+	pld = dev_get_drvdata(pdev->dev.parent);
+
+	if (pld->info.spec_major < 2) {
+		dev_err(&pdev->dev,
+			"driver only supports GPIO devices "
+			"compatible to PLD spec. rev. 2.0 or higher\n");
+		ret = -ENXIO;
+		goto err_check_rev;
+	}
+
+	gpio = kzalloc(sizeof(*gpio), GFP_KERNEL);
+	if (gpio == NULL) {
+		dev_err(&pdev->dev, "unable to get memory for device data\n");
+		ret = -ENOMEM;
+		goto err_alloc_dev_data;
+	}
+
+	gpio->pld = pld;
+
+	platform_set_drvdata(pdev, gpio);
+
+	chip = &gpio->chip;
+	chip->label =		"kempld-gpio";
+	chip->owner =		THIS_MODULE;
+	chip->dev =		&pdev->dev;
+	chip->can_sleep =	1;
+	chip->base =		gpiobase;
+	chip->ngpio =		KEMPLD_GPIO_MAX_NUM;
+
+	if (kempld_gpio_detect(gpio))
+		dev_err(&pdev->dev, "GPIO detection failed\n");
+	if (kempld_gpio_setup_event(gpio))
+		dev_err(&pdev->dev, "Initializing events failed\n");
+
+	chip->direction_input =		kempld_gpio_direction_input;
+	chip->direction_output =	kempld_gpio_direction_output;
+	chip->get =			kempld_gpio_get;
+	chip->set =			kempld_gpio_set;
+	chip->dbg_show =		kempld_gpio_dbg_show;
+	if (gpio->irq)
+		chip->to_irq =		kempld_gpio_to_irq;
+
+	ret = gpiochip_add(chip);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not register GPIO chip\n");
+		goto err_gpiochip_add;
+	}
+
+	dev_info(&pdev->dev,
+		 "GPIO functionality initialized with %d IOs mask (0x%04x)\n",
+		 chip->ngpio, gpio->mask);
+
+	return 0;
+
+err_gpiochip_add:
+	kfree(gpio);
+err_alloc_dev_data:
+err_check_rev:
+
+	return ret;
+}
+
+static int kempld_gpio_remove(struct platform_device *pdev)
+{
+	struct kempld_gpio_data *gpio = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = gpiochip_remove(&gpio->chip);
+	if (ret == 0) {
+		kfree(gpio);
+		platform_set_drvdata(pdev, NULL);
+	}
+
+	return ret;
+}
+
+static struct platform_driver kempld_gpio_driver = {
+	.driver = {
+		.name = "kempld-gpio",
+		.owner = THIS_MODULE,
+	},
+	.probe		= kempld_gpio_probe,
+	.remove		= kempld_gpio_remove,
+};
+
+static int __init kempld_gpio_init(void)
+{
+	return platform_driver_register(&kempld_gpio_driver);
+}
+
+static void __exit kempld_gpio_exit(void)
+{
+	platform_driver_unregister(&kempld_gpio_driver);
+}
+
+module_init(kempld_gpio_init);
+module_exit(kempld_gpio_exit);
+
+module_param(gpiobase, int, 0444);
+module_param(gpioien, int, 0444);
+module_param(gpioevt_lvl_edge, int, 0444);
+module_param(gpioevt_low_high, int, 0444);
+module_param(gpionmien, int, 0444);
+
+MODULE_DESCRIPTION("KEM PLD GPIO Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld_gpio");
+MODULE_PARM_DESC(gpiobase, "Set GPIO base (default -1=dynamic)");
+MODULE_PARM_DESC(gpioien, "Set GPIO IEN register (default 0x00)");
+MODULE_PARM_DESC(gpioevt_lvl_edge,
+			"Set GPIO EVT_LVL_EDGE register (default -1=no change)");
+MODULE_PARM_DESC(gpioevt_low_high,
+			"Set GPIO EVT_LOW_HIGH register (default -1=no change)");
+MODULE_PARM_DESC(gpionmien, "Set GPIO NMIEN register (default 0x00)");
+
diff --git a/drivers/gpio/gpio-kempld.h b/drivers/gpio/gpio-kempld.h
new file mode 100644
index 0000000..173932c
--- /dev/null
+++ b/drivers/gpio/gpio-kempld.h
@@ -0,0 +1,50 @@
+/*
+ *  kempld_gpio.h - Kontron PLD GPIO driver definitions
+ *
+ *  Copyright (c) 2010-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _KEMPLD_GPIO_H_
+#define _KEMPLD_GPIO_H_
+
+#define KEMPLD_GPIO_MAX_NUM		16
+
+#define KEMPLD_GPIO_MASK(x)		(1<<(x%8))
+
+#define KEMPLD_GPIO_DIR			0x40
+#define KEMPLD_GPIO_DIR_NUM(x)		(0x40+x/8)
+#define KEMPLD_GPIO_LVL			0x42
+#define KEMPLD_GPIO_LVL_NUM(x)		(0x42+x/8)
+#define KEMPLD_GPIO_STS			0x44
+#define KEMPLD_GPIO_STS_NUM(x)		(0x44+x/8)
+#define KEMPLD_GPIO_EVT_LVL_EDGE	0x46
+#define KEMPLD_GPIO_EVT_LVL_EDGE_NUM(x)	(0x46+x/8)
+#define KEMPLD_GPIO_EVT_LOW_HIGH	0x48
+#define KEMPLD_GPIO_EVT_LOW_HIGH_NUM(x)	(0x48+x/8)
+#define KEMPLD_GPIO_IEN			0x4A
+#define KEMPLD_GPIO_IEN_NUM(x)		(0x4A+x/8)
+#define KEMPLD_GPIO_NMIEN		0x4C
+#define KEMPLD_GPIO_NMIEN_NUM(x)	(0x4C+x/8)
+
+struct kempld_gpio_data {
+	struct gpio_chip		chip;
+	int				irq;
+	struct kempld_device_data	*pld;
+	uint16_t			mask;
+};
+
+#endif /* _KEMPLD_GPIO_H_ */
diff --git a/drivers/gpio/gpio-kempld_now1.c b/drivers/gpio/gpio-kempld_now1.c
new file mode 100644
index 0000000..50ebd67
--- /dev/null
+++ b/drivers/gpio/gpio-kempld_now1.c
@@ -0,0 +1,280 @@
+/*
+ *  kempld_now1_gpio.c - Kontron PLD GPIO driver for COMe-mSP1
+ *
+ *  Copyright (c) 2011-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/mfd/kempld.h>
+#include <linux/seq_file.h>
+
+#include "gpio-kempld.h"
+
+#define KEMPLD_NOW1_GPIO_MAX_NUM	8
+
+#define KEMPLD_NOW1_FUNCTION		0x70
+#define		KEMPLD_NOW1_FUNCTION_ALF_SDIO	0x01
+#define		KEMPLD_NOW1_FUNCTION_USBCC	0x02
+#define KEMPLD_NOW1_GPIO_DIR		0xA0
+#define KEMPLD_NOW1_GPIO_LVL		0xA1
+
+static int kempld_now1_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_device_data *pld;
+	int status;
+
+	pld = dev_get_drvdata(chip->dev->parent);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_NOW1_GPIO_LVL);
+
+	status = kempld_read8(pld, KEMPLD_NOW1_GPIO_LVL);
+
+	kempld_release_mutex(pld);
+
+	status &= KEMPLD_GPIO_MASK(offset);
+
+	return status ? 1 : 0;
+}
+
+static void kempld_now1_gpio_set(struct gpio_chip *chip, unsigned offset,
+				int value)
+{
+	struct kempld_device_data *pld;
+	int status;
+
+	pld = dev_get_drvdata(chip->dev->parent);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_NOW1_GPIO_LVL);
+
+	status = kempld_read8(pld, KEMPLD_NOW1_GPIO_LVL);
+	if (value)
+		status |= KEMPLD_GPIO_MASK(offset);
+	else
+		status &= ~KEMPLD_GPIO_MASK(offset);
+	kempld_write8(pld, KEMPLD_NOW1_GPIO_LVL, status);
+
+	kempld_release_mutex(pld);
+}
+
+static int kempld_now1_gpio_direction_input(struct gpio_chip *chip,
+						unsigned offset)
+{
+	struct kempld_device_data *pld;
+	int status;
+
+	pld = dev_get_drvdata(chip->dev->parent);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_NOW1_GPIO_DIR);
+
+	status = kempld_read8(pld, KEMPLD_NOW1_GPIO_DIR);
+	status &= ~KEMPLD_GPIO_MASK(offset);
+	kempld_write8(pld, KEMPLD_NOW1_GPIO_DIR, status);
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_now1_gpio_direction_output(struct gpio_chip *chip,
+					 unsigned offset, int value)
+{
+	struct kempld_device_data *pld;
+	int status;
+
+	pld = dev_get_drvdata(chip->dev->parent);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_NOW1_GPIO_DIR);
+
+	status = kempld_read8(pld, KEMPLD_NOW1_GPIO_LVL);
+	if (value)
+		status |= KEMPLD_GPIO_MASK(offset);
+	else
+		status &= ~KEMPLD_GPIO_MASK(offset);
+	kempld_write8(pld, KEMPLD_NOW1_GPIO_LVL, status);
+
+	status = kempld_read8(pld, KEMPLD_NOW1_GPIO_DIR);
+	status |= KEMPLD_GPIO_MASK(offset);
+	kempld_write8(pld, KEMPLD_NOW1_GPIO_DIR, status);
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int kempld_now1_gpio_get_direction(struct gpio_chip *chip,
+					unsigned offset)
+{
+	struct kempld_device_data *pld;
+	int status;
+
+	pld = dev_get_drvdata(chip->dev->parent);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_NOW1_GPIO_DIR);
+
+	status = kempld_read8(pld, KEMPLD_NOW1_GPIO_DIR);
+
+	kempld_release_mutex(pld);
+
+	status &= KEMPLD_GPIO_MASK(offset);
+
+	return status ? 1 : 0;
+}
+
+static void kempld_now1_gpio_dbg_show(struct seq_file *s,
+				struct gpio_chip *chip)
+{
+	struct kempld_device_data *pld;
+	int function;
+	int i;
+
+	pld = dev_get_drvdata(chip->dev->parent);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_NOW1_FUNCTION);
+
+	function = kempld_read8(pld, KEMPLD_NOW1_FUNCTION);
+
+	kempld_release_mutex(pld);
+
+	for (i = 0; i < chip->ngpio; i++) {
+		int gpio = i + chip->base;
+
+		seq_printf(s, " gpio-%-3d %s %s %s\n", gpio,
+				kempld_now1_gpio_get_direction(chip, i)
+				? "out" : "in",
+				kempld_now1_gpio_get(chip, i)
+				? "hi" : "lo",
+				function & KEMPLD_NOW1_FUNCTION_ALF_SDIO
+				? "sdio" :
+				((function & KEMPLD_NOW1_FUNCTION_USBCC)
+					&& (i == 0))
+				? "usbcc" : "gpio");
+	}
+}
+#else
+#define kempld_now1_gpio_dbg_show NULL
+#endif
+
+static int kempld_now1_gpio_probe(struct platform_device *pdev)
+{
+	struct kempld_device_data *pld;
+	struct gpio_chip *chip;
+	int function;
+	int ret;
+
+	pld = dev_get_drvdata(pdev->dev.parent);
+
+	chip = kzalloc(sizeof(struct gpio_chip), GFP_KERNEL);
+	if (chip == NULL) {
+		dev_err(&pdev->dev, "unable to get memory for device data\n");
+		ret = -ENOMEM;
+		goto err_alloc_dev_data;
+	}
+
+	chip->label =		"kempld_now1-gpio";
+	chip->owner =		THIS_MODULE;
+	chip->can_sleep =	1;
+	chip->dev =		&pdev->dev;
+	chip->base =		-1;
+	chip->ngpio =		KEMPLD_NOW1_GPIO_MAX_NUM;
+
+	chip->direction_input =		kempld_now1_gpio_direction_input;
+	chip->direction_output =	kempld_now1_gpio_direction_output;
+	chip->get =			kempld_now1_gpio_get;
+	chip->set =			kempld_now1_gpio_set;
+	chip->dbg_show =		kempld_now1_gpio_dbg_show;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_NOW1_FUNCTION);
+
+	function = kempld_read8(pld, KEMPLD_NOW1_FUNCTION);
+
+	kempld_release_mutex(pld);
+
+	if (function & KEMPLD_NOW1_FUNCTION_ALF_SDIO)
+		dev_warn(&pdev->dev, "GPIO pins are used for SDIO\n");
+	if (function & KEMPLD_NOW1_FUNCTION_USBCC) {
+		dev_info(&pdev->dev,
+			 "GPI[0] is forwarded to USB cable connect\n");
+	}
+
+	platform_set_drvdata(pdev, chip);
+
+	ret = gpiochip_add(chip);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not register GPIO chip\n");
+		goto err_gpiochip_add;
+	}
+
+	dev_info(&pdev->dev, "GPIO functionality initialized\n");
+
+	return 0;
+
+err_gpiochip_add:
+	kfree(chip);
+err_alloc_dev_data:
+
+	return ret;
+}
+
+static int kempld_now1_gpio_remove(struct platform_device *pdev)
+{
+	struct gpio_chip *chip = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = gpiochip_remove(chip);
+	if (ret == 0) {
+		kfree(chip);
+		platform_set_drvdata(pdev, NULL);
+	}
+
+	return ret;
+}
+
+static struct platform_driver kempld_now1_gpio_driver = {
+	.driver = {
+		.name = "kempld_now1-gpio",
+		.owner = THIS_MODULE,
+	},
+	.probe		= kempld_now1_gpio_probe,
+	.remove		= kempld_now1_gpio_remove,
+};
+
+static int __init kempld_now1_gpio_init(void)
+{
+	return platform_driver_register(&kempld_now1_gpio_driver);
+}
+
+static void __exit kempld_now1_gpio_exit(void)
+{
+	platform_driver_unregister(&kempld_now1_gpio_driver);
+}
+
+module_init(kempld_now1_gpio_init);
+module_exit(kempld_now1_gpio_exit);
+
+MODULE_DESCRIPTION("KEM PLD nanoETXexpress-SP GPIO Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld_now1-gpio");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 4/4] watchdog: Kontron PLD watchdog timer
  2013-04-08 17:15 [PATCH 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
  2013-04-08 17:15 ` [PATCH 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
  2013-04-08 17:15 ` [PATCH 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
@ 2013-04-08 17:15 ` Kevin Strasser
  2013-04-10 16:47   ` Guenter Roeck
  2013-04-13 20:38 ` [PATCH 1/4] mfd: Kontron PLD mfd driver Thomas Gleixner
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 48+ messages in thread
From: Kevin Strasser @ 2013-04-08 17:15 UTC (permalink / raw)
  To: linux-kernel
  Cc: Michael Brunner, Samuel Ortiz, Wolfram Sang, Ben Dooks,
	linux-i2c, Grant Likely, Linus Walleij, Wim Van Sebroeck,
	linux-watchdog, Darren Hart, Michael Brunner, Greg Kroah-Hartman,
	Kevin Strasser

From: Michael Brunner <michael.brunner@kontron.com>

Add watchdog timer support for the on-board PLD found on some Kontron
embedded modules.

Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
---
 drivers/watchdog/Kconfig           |   20 +
 drivers/watchdog/Makefile          |    2 +
 drivers/watchdog/kempld_now1_wdt.c |  602 +++++++++++++++++++++++++++
 drivers/watchdog/kempld_wdt.c      |  796 ++++++++++++++++++++++++++++++++++++
 drivers/watchdog/kempld_wdt.h      |   75 ++++
 5 files changed, 1495 insertions(+)
 create mode 100644 drivers/watchdog/kempld_now1_wdt.c
 create mode 100644 drivers/watchdog/kempld_wdt.c
 create mode 100644 drivers/watchdog/kempld_wdt.h

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 9fcc70c..9ac71ca 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -687,6 +687,26 @@ config HP_WATCHDOG
 	  To compile this driver as a module, choose M here: the module will be
 	  called hpwdt.
 
+config KEMPLD_WDT
+	tristate "Kontron COM watchdog"
+	depends on MFD_KEMPLD
+	help
+	  Support for the PLD watchdog on some Kontron ETX and COMexpress
+	  (ETXexpress) modules
+
+	  This driver can also be built as a module. If so, the module will be
+	  called kempld_wdt.
+
+config KEMPLD_NOW1_WDT
+	tristate "Kontron COMe-mSP1 (nanoETXexpress-SP) watchdog"
+	depends on MFD_KEMPLD
+	help
+	  Support for the PLD watchdog on the Kontron COMe-mSP1
+	  (nanoETXexpress-SP) module.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called kempld_now1_wdt.
+
 config HPWDT_NMI_DECODING
 	bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer"
 	depends on HP_WATCHDOG
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index a300b94..a029930 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -90,6 +90,8 @@ endif
 obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
 obj-$(CONFIG_IT87_WDT) += it87_wdt.o
 obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
+obj-$(COnFIG_KEMPLD_NOW1_WDT) += kempld_now1_wdt.o
 obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
 obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
 obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
diff --git a/drivers/watchdog/kempld_now1_wdt.c b/drivers/watchdog/kempld_now1_wdt.c
new file mode 100644
index 0000000..19b7272
--- /dev/null
+++ b/drivers/watchdog/kempld_now1_wdt.c
@@ -0,0 +1,602 @@
+/*
+ *  kempld_now1_wdt.c - Kontron PLD watchdog driver for COMe-mSP1
+ *
+ *  Copyright (c) 2011-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  Note: The watchdog of the COMe-mSP1 (nanoETXexpress-SP) has one stage and
+ *        only supports predefined watchdog timeout values.
+ *
+ *        The supported timeouts are:
+ *              1 s, 5 s, 10 s, 30 s, 1 min, 5 min, 10 min, 15 min
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/kempld.h>
+
+#include "kempld_wdt.h"
+
+#define WATCHDOG_TIMEOUT 30
+static int timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+		 "Watchdog timeout in seconds. (1, 5, 10, 30, 60, 300, 600, "
+		 "900, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+		 "Watchdog cannot be stopped once started (default="
+		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+/* nanoETXexpress-SP watchdog register definitions */
+#define KEMPLD_WDT_NOW1					0xA2
+#define		KEMPLD_WDT_NOW1_KICK_MASK		0x80
+#define		KEMPLD_WDT_NOW1_ENABLE_MASK		0x40
+#define		KEMPLD_WDT_NOW1_TIMEOUT_MASK		0x1f
+#define			KEMPLD_WDT_NOW1_TIMEOUT_1SEC	0x00
+#define			KEMPLD_WDT_NOW1_TIMEOUT_5SEC	0x01
+#define			KEMPLD_WDT_NOW1_TIMEOUT_10SEC	0x02
+#define			KEMPLD_WDT_NOW1_TIMEOUT_30SEC	0x03
+#define			KEMPLD_WDT_NOW1_TIMEOUT_1MIN	0x10
+#define			KEMPLD_WDT_NOW1_TIMEOUT_5MIN	0x11
+#define			KEMPLD_WDT_NOW1_TIMEOUT_10MIN	0x12
+#define			KEMPLD_WDT_NOW1_TIMEOUT_15MIN	0x13
+
+/* delay in us necessary due to clock domain sync */
+#define		KEMPLD_WDT_NOW1_SYNC_DELAY		31
+
+static struct kempld_watchdog_data *kempld_now1_wdt;
+
+static int kempld_now1_wdt_read_supported;
+static int kempld_now1_wdt_reg_cache;
+
+static int kempld_now1_wdt_start(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int wdt_reg;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+	if (kempld_now1_wdt_read_supported)
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	else
+		wdt_reg = kempld_now1_wdt_reg_cache;
+	wdt_reg |= KEMPLD_WDT_NOW1_ENABLE_MASK;
+	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
+	kempld_now1_wdt_reg_cache = wdt_reg;
+	kempld_release_mutex(pld);
+
+	if (kempld_now1_wdt_read_supported) {
+		/* read out the register again to check if everything worked */
+		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+		kempld_release_mutex(pld);
+
+		/* check if the watchdog was disabled */
+		if (!(wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK))
+			return -EACCES;
+	}
+
+	return 0;
+}
+
+static int kempld_now1_wdt_stop(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int wdt_reg;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+	if (kempld_now1_wdt_read_supported) {
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	} else
+		wdt_reg = kempld_now1_wdt_reg_cache;
+	wdt_reg &= ~KEMPLD_WDT_NOW1_ENABLE_MASK;
+	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
+	kempld_now1_wdt_reg_cache = wdt_reg;
+	kempld_release_mutex(pld);
+
+	if (kempld_now1_wdt_read_supported) {
+		/* read out the register again to check if everything worked */
+		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+		kempld_release_mutex(pld);
+
+		/* check if the watchdog was disabled */
+		if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
+			return -EACCES;
+	}
+
+	return 0;
+}
+
+static int kempld_now1_wdt_keepalive(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int wdt_reg;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+
+	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+
+	if (kempld_now1_wdt_read_supported) {
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	} else {
+		wdt_reg = kempld_now1_wdt_reg_cache;
+		/* write the state again to be sure the trigger register has
+		 * the right level */
+		kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
+	}
+
+	if (wdt_reg & KEMPLD_WDT_NOW1_KICK_MASK)
+		wdt_reg &= ~KEMPLD_WDT_NOW1_KICK_MASK;
+	else
+		wdt_reg |= KEMPLD_WDT_NOW1_KICK_MASK;
+
+	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+
+	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
+
+	kempld_now1_wdt_reg_cache = wdt_reg;
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+
+static int kempld_now1_wdt_settimeout(struct kempld_watchdog_data *wdt,
+					int check_only)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int wdt_reg;
+	int ret = 0;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+
+	if (kempld_now1_wdt_read_supported) {
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	} else
+		wdt_reg = kempld_now1_wdt_reg_cache;
+
+	wdt_reg &= ~KEMPLD_WDT_NOW1_TIMEOUT_MASK;
+
+	switch (wdt->timeout) {
+	case 1:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1SEC;
+		break;
+	case 5:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5SEC;
+		break;
+	case 10:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10SEC;
+		break;
+	case 30:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_30SEC;
+		break;
+	case 60:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1MIN;
+		break;
+	case 300:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5MIN;
+		break;
+	case 600:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10MIN;
+		break;
+	case 900:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_15MIN;
+		break;
+	default:
+		ret = -EINVAL;
+		dev_err(wdt->pld->dev,
+			"Invalid timeout value given!\n");
+	}
+
+	if (!check_only) {
+		if (ret == 0) {
+			udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+
+			kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
+		}
+	}
+
+	kempld_now1_wdt_reg_cache = wdt_reg;
+
+	kempld_release_mutex(pld);
+
+	return ret;
+}
+
+static int kempld_now1_wdt_gettimeout(struct kempld_watchdog_data *wdt,
+				 struct kempld_watchdog_stage *stage)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int wdt_reg;
+	int timeout;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+
+	if (kempld_now1_wdt_read_supported) {
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+		kempld_now1_wdt_reg_cache = wdt_reg;
+	} else
+		wdt_reg = kempld_now1_wdt_reg_cache;
+
+	kempld_release_mutex(pld);
+
+	switch (wdt_reg&KEMPLD_WDT_NOW1_TIMEOUT_MASK) {
+	case KEMPLD_WDT_NOW1_TIMEOUT_1SEC:
+		timeout = 1;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_5SEC:
+		timeout = 5;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_10SEC:
+		timeout = 10;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_30SEC:
+		timeout = 30;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_1MIN:
+		timeout = 60;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_5MIN:
+		timeout = 300;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_10MIN:
+		timeout = 600;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_15MIN:
+		timeout = 900;
+		break;
+	default:
+		timeout = -ERANGE;
+	}
+
+	return timeout;
+}
+
+static ssize_t kempld_now1_wdt_write(struct file *file, const char __user
+					*data, size_t count, loff_t *ppos)
+{
+	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
+
+	BUG_ON(wdt == NULL);
+
+	if (count) {
+		kempld_now1_wdt_keepalive(wdt);
+
+		if (!nowayout) {
+			size_t i;
+
+			wdt->expect_close = 0;
+
+			for (i = 0; i < count; i++) {
+				char c;
+				if (get_user(c, data+i))
+					return -EFAULT;
+				if (c == 'V')
+					wdt->expect_close = 42;
+			}
+		}
+	}
+
+	return count;
+}
+
+static long kempld_now1_wdt_ioctl(struct file *file, unsigned int cmd,
+				unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
+	int options;
+	int value;
+	int ret = 0;
+
+	BUG_ON(wdt == NULL);
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
+			ret = -EFAULT;
+		break;
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(0, p);
+		break;
+	case WDIOC_SETOPTIONS:
+		if (get_user(options, p)) {
+			ret = -EFAULT;
+			break;
+		}
+		if (options & WDIOS_DISABLECARD)
+			ret = kempld_now1_wdt_stop(wdt);
+		if (options & WDIOS_ENABLECARD) {
+			ret = kempld_now1_wdt_start(wdt);
+			kempld_now1_wdt_keepalive(wdt);
+		}
+		break;
+	case WDIOC_KEEPALIVE:
+		kempld_now1_wdt_keepalive(wdt);
+		break;
+	case WDIOC_SETTIMEOUT:
+		if (get_user(value, p)) {
+			ret = -EFAULT;
+			break;
+		}
+		wdt->timeout = value;
+		ret = kempld_now1_wdt_settimeout(wdt, 0);
+		kempld_now1_wdt_keepalive(wdt);
+		break;
+	case WDIOC_GETTIMEOUT:
+		value = kempld_now1_wdt_gettimeout(wdt, wdt->timeout_stage);
+		if (timeout < 0)
+			ret = ERANGE;
+		else
+			ret = put_user(timeout, p);
+		break;
+	default:
+		ret = -ENOTTY;
+	}
+
+	return ret;
+}
+
+static int kempld_now1_wdt_release(struct inode *inode, struct file *file)
+{
+	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
+
+	BUG_ON(wdt == NULL);
+
+	if (wdt->expect_close)
+		kempld_now1_wdt_stop(wdt);
+	else {
+		dev_warn(wdt->pld->dev,
+			 "Unexpected close, not stopping watchdog!\n");
+		kempld_now1_wdt_keepalive(wdt);
+	}
+
+	kempld_now1_wdt->expect_close = 0;
+
+	clear_bit(0, &wdt->is_open);
+
+	return 0;
+}
+
+static int kempld_now1_wdt_open(struct inode *inode, struct file *file)
+{
+	int ret;
+	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
+	struct kempld_device_data *pld = wdt->pld;
+	u8 wdt_reg;
+
+	BUG_ON(wdt == NULL);
+
+	if (test_and_set_bit(0, &wdt->is_open))
+		return -EBUSY;
+
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+	if (kempld_now1_wdt_read_supported) {
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	} else {
+		wdt_reg = kempld_now1_wdt_reg_cache;
+	}
+	kempld_now1_wdt_reg_cache = wdt_reg;
+	kempld_release_mutex(pld);
+
+	/* kick the watchdog if it is already enabled, otherwise start it */
+	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK) {
+		kempld_now1_wdt_keepalive(wdt);
+	} else {
+		ret = kempld_now1_wdt_settimeout(wdt, 0);
+		if (ret)
+			goto err_enable_wdt;
+		ret = kempld_now1_wdt_start(wdt);
+		if (ret)
+			goto err_enable_wdt;
+	}
+
+	return nonseekable_open(inode, file);
+
+err_enable_wdt:
+	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
+	wdt->expect_close = 1;
+	kempld_now1_wdt_release(inode, file);
+
+	return ret;
+}
+
+static const struct file_operations kempld_now1_wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= kempld_now1_wdt_write,
+	.unlocked_ioctl	= kempld_now1_wdt_ioctl,
+	.open		= kempld_now1_wdt_open,
+	.release	= kempld_now1_wdt_release,
+};
+
+static struct miscdevice kempld_now1_wdt_miscdev = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &kempld_now1_wdt_fops,
+};
+
+static int kempld_now1_wdt_probe(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt;
+	struct kempld_device_data *pld;
+	u8 wdt_reg;
+	int ret;
+
+	if (kempld_now1_wdt != NULL) {
+		dev_err(&pdev->dev,
+			"unable to support more than one watchdog devices\n");
+		return -EMFILE;
+	}
+
+	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
+	if (wdt == NULL) {
+		dev_err(&pdev->dev, "unable to get memory for device data\n");
+		ret = -ENOMEM;
+		goto err_alloc_dev_data;
+	}
+
+	wdt->timeout_stage = kzalloc(sizeof(struct kempld_watchdog_stage),
+					GFP_KERNEL);
+	if (wdt->timeout_stage == NULL) {
+		dev_err(&pdev->dev,
+			"unable to get memory for watchdog stage\n");
+		ret = -ENOMEM;
+		goto err_alloc_dev_data;
+	}
+
+	wdt->stages = 1;
+	wdt->stage[0] = wdt->timeout_stage;
+
+	pld = dev_get_drvdata(pdev->dev.parent);
+	wdt->pld = pld;
+
+	platform_set_drvdata(pdev, wdt);
+
+	strncpy(wdt->ident.identity, "nanoETXexpress-SP Watchdog",
+		sizeof(wdt->ident.identity));
+
+	/* set default values for the case we start the watchdog or change
+	 * the configuration */
+	wdt->timeout = timeout;
+
+	/* use settimeout to check if the timeout parameter is valid */
+	ret = kempld_now1_wdt_settimeout(wdt, 1);
+	if (ret)
+		goto err_check_timeout;
+	wdt->ident.firmware_version = (pld->info.major<<8) + pld->info.minor;
+	if (pld->info.major > 67)
+		kempld_now1_wdt_read_supported = 1;
+	else
+		dev_info(wdt->pld->dev,
+			 "Watchdog revision does not support read - "
+			 "unable to get watchdog state!\n");
+
+	/* get initial watchdog status */
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+	if (kempld_now1_wdt_read_supported) {
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	} else {
+		wdt_reg = 0x0;
+	}
+	kempld_now1_wdt_reg_cache = wdt_reg;
+	kempld_release_mutex(wdt->pld);
+
+	/* check if watchdog is enabled */
+	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
+		dev_info(wdt->pld->dev, "Watchdog is already enabled!\n");
+
+	wdt->ident.options = WDIOF_KEEPALIVEPING;
+	wdt->ident.options |= WDIOF_SETTIMEOUT;
+	if (!nowayout)
+		wdt->ident.options |= WDIOF_MAGICCLOSE;
+
+	kempld_now1_wdt = wdt;
+
+	ret = misc_register(&kempld_now1_wdt_miscdev);
+	if (ret)
+		goto err_misc_register;
+
+	dev_info(wdt->pld->dev, "watchdog initialized\n");
+
+	return 0;
+
+err_misc_register:
+	kfree(kempld_now1_wdt);
+	kempld_now1_wdt = NULL;
+err_check_timeout:
+err_alloc_dev_data:
+	return ret;
+}
+
+static int kempld_now1_wdt_remove(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
+
+	BUG_ON(wdt != kempld_now1_wdt);
+
+	/* stop or at least keepalive the watchdog before we leave */
+	if (wdt != NULL) {
+		if (!nowayout)
+			kempld_now1_wdt_stop(wdt);
+		else
+			kempld_now1_wdt_keepalive(wdt);
+	}
+
+	misc_deregister(&kempld_now1_wdt_miscdev);
+
+	kfree(wdt);
+	kempld_now1_wdt = NULL;
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver kempld_now1_wdt_driver = {
+	.driver = {
+		.name = "kempld_now1-wdt",
+		.owner = THIS_MODULE,
+	},
+	.probe = kempld_now1_wdt_probe,
+	.remove = kempld_now1_wdt_remove,
+};
+
+static int __init kempld_now1_wdt_init(void)
+{
+	return platform_driver_register(&kempld_now1_wdt_driver);
+}
+
+static void __exit kempld_now1_wdt_exit(void)
+{
+	platform_driver_unregister(&kempld_now1_wdt_driver);
+}
+
+module_init(kempld_now1_wdt_init);
+module_exit(kempld_now1_wdt_exit);
+
+MODULE_DESCRIPTION("KEM PLD nanoETXexpress-SP Watchdog Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/watchdog/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c
new file mode 100644
index 0000000..bc150e5
--- /dev/null
+++ b/drivers/watchdog/kempld_wdt.c
@@ -0,0 +1,796 @@
+/*
+ *  kempld_wdt.c - Kontron PLD watchdog driver
+ *
+ *  Copyright (c) 2010-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  Note: From the PLD watchdog point of view timeout and pretimeout are
+ *        defined differently than in the kernel.
+ *        First the pretimeout stage runs out before the timeout stage gets
+ *        active. This has to be kept in mind.
+ *
+ *  Kernel/API:                     P-----| pretimeout
+ *                |-----------------------T timeout
+ *  Watchdog:     |-----------------P       pretimeout_stage
+ *                                  |-----T timeout_stage
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/kempld.h>
+
+#include "kempld_wdt.h"
+
+#define WATCHDOG_DEFAULT_TIMEOUT 20
+#define WATCHDOG_DEFAULT_PRETIMEOUT 0
+static int timeout = -1;
+static int pretimeout = -1;
+/* The maximum timeout values have to be probed */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+		 "Watchdog timeout in seconds. (>0, default="
+		__MODULE_STRING(WATCHDOG_DEFAULT_TIMEOUT) ")");
+module_param(pretimeout, int, 0);
+MODULE_PARM_DESC(pretimeout,
+		 "Watchdog pretimeout in seconds. (>=0, default="
+		__MODULE_STRING(WATCHDOG_DEFAULT_PRETIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+		 "Watchdog cannot be stopped once started (default="
+		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static struct kempld_watchdog_data *kempld_wdt;
+
+static int kempld_wdt_start(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	u8 status;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	status |= KEMPLD_WDT_CFG_ENABLE;
+	kempld_write8(pld, KEMPLD_WDT_CFG, status);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+
+	kempld_release_mutex(pld);
+
+	/* check if the watchdog was enabled */
+	if (!(status & KEMPLD_WDT_CFG_ENABLE))
+		return -EACCES;
+
+	return 0;
+}
+
+static int kempld_wdt_stop(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	u8 status;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	status &= ~KEMPLD_WDT_CFG_ENABLE;
+	kempld_write8(pld, KEMPLD_WDT_CFG, status);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+
+	kempld_release_mutex(pld);
+
+	/* check if the watchdog was disabled */
+	if (status & KEMPLD_WDT_CFG_ENABLE)
+		return -EACCES;
+
+	return 0;
+}
+
+static int kempld_wdt_keepalive(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+
+	kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_wdt_gettimeout(struct kempld_watchdog_data *wdt,
+				 struct kempld_watchdog_stage *stage)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	u8 stage_cfg;
+	int bits;
+	u64 timeout;
+	u32 remainder;
+
+	if (stage == NULL)
+		return 0;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+	timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num));
+
+	kempld_release_mutex(pld);
+
+	bits = KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(stage_cfg);
+	timeout = (timeout & stage->timeout_mask) * KEMPLD_PRESCALER(bits);
+	remainder = do_div(timeout, pld->pld_clock);
+
+	/* Round up the return value if necessary */
+	if ((timeout > 0) && (remainder >= (pld->pld_clock/2)))
+		timeout++;
+
+	return timeout;
+}
+
+static int kempld_wdt_setstageaction(struct kempld_watchdog_data *wdt,
+				 struct kempld_watchdog_stage *stage,
+				 int action)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	u8 stage_cfg;
+
+	if (stage == NULL)
+		return -EINVAL;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ACTION_MASK;
+	stage_cfg |= (action & KEMPLD_WDT_STAGE_CFG_ACTION_MASK);
+	if (action == KEMPLD_WDT_ACTION_RESET)
+		stage_cfg |= KEMPLD_WDT_STAGE_CFG_ASSERT;
+	else
+		stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ASSERT;
+
+	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_wdt_setstagetimeout(struct kempld_watchdog_data *wdt,
+				 struct kempld_watchdog_stage *stage,
+				 int timeout)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	u8 stage_cfg;
+	u8 prescaler;
+	u64 stage_timeout64;
+	u32 stage_timeout;
+	u32 remainder;
+
+	if (stage == NULL)
+		return -EINVAL;
+
+	prescaler = KEMPLD_WDT_PRESCALER_21BIT;
+
+	stage_timeout64 = ((u64)timeout*pld->pld_clock);
+	remainder = do_div(stage_timeout64, KEMPLD_PRESCALER(prescaler));
+	if (remainder)
+		stage_timeout64++;
+	stage_timeout = stage_timeout64 & stage->timeout_mask;
+
+	if (stage_timeout64 != (u64)stage_timeout)
+		return -EINVAL;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK;
+	stage_cfg |= KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(prescaler);
+	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
+	kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num),
+			stage_timeout);
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static void kempld_wdt_update_timeouts(struct kempld_watchdog_data *wdt)
+{
+	int pretimeout_stage;
+	int timeout_stage;
+
+	pretimeout_stage = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
+	timeout_stage = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
+
+	if (pretimeout_stage)
+		wdt->pretimeout = timeout_stage;
+	else
+		wdt->pretimeout = 0;
+
+	wdt->timeout = pretimeout_stage + timeout_stage;
+
+	if (wdt->pretimeout < 0) {
+		wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
+		dev_err(wdt->pld->dev, "failed to get valid pretimeout value\n"
+			" -> using driver default\n");
+	}
+	if (wdt->timeout < 0) {
+		wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
+		dev_err(wdt->pld->dev, "failed to get valid timeout value\n"
+			" -> using driver default\n");
+	}
+}
+
+static int kempld_wdt_settimeout(struct kempld_watchdog_data *wdt)
+{
+	int stage_timeout;
+	int stage_pretimeout;
+	int ret;
+
+	if ((wdt->timeout <= 0) ||
+		(wdt->pretimeout < 0) ||
+		(wdt->pretimeout > wdt->timeout)) {
+		ret = -EINVAL;
+		goto err_check_values;
+	}
+
+	if ((wdt->pretimeout == 0) || (wdt->pretimeout_stage == NULL)) {
+		if (wdt->pretimeout != 0)
+			dev_warn(wdt->pld->dev,
+				 "no pretimeout stage available\n"
+				 " -> only enabling reset\n");
+		stage_pretimeout = 0;
+		stage_timeout = wdt->timeout;
+	} else {
+		stage_pretimeout = wdt->timeout - wdt->pretimeout;
+		stage_timeout = wdt->pretimeout;
+	}
+
+	if (stage_pretimeout != 0) {
+		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
+						KEMPLD_WDT_ACTION_NMI);
+	} else if ((stage_pretimeout == 0)
+			&& (wdt->pretimeout_stage != NULL)) {
+		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
+						KEMPLD_WDT_ACTION_NONE);
+	} else
+		ret = 0;
+	if (ret)
+		goto err_setstage;
+
+	if (stage_pretimeout != 0) {
+		ret = kempld_wdt_setstagetimeout(wdt, wdt->pretimeout_stage,
+						 stage_pretimeout);
+		if (ret)
+			goto err_setstage;
+	}
+
+	ret = kempld_wdt_setstageaction(wdt, wdt->timeout_stage,
+					KEMPLD_WDT_ACTION_RESET);
+	if (ret)
+		goto err_setstage;
+
+	ret = kempld_wdt_setstagetimeout(wdt, wdt->timeout_stage,
+					 stage_timeout);
+	if (ret)
+		goto err_setstage;
+
+	return 0;
+err_setstage:
+err_check_values:
+	return ret;
+}
+
+static ssize_t kempld_wdt_write(struct file *file, const char __user *data,
+				size_t count, loff_t *ppos)
+{
+	struct kempld_watchdog_data *wdt = kempld_wdt;
+
+	BUG_ON(wdt == NULL);
+
+	if (count) {
+		kempld_wdt_keepalive(wdt);
+
+		if (!nowayout) {
+			size_t i;
+
+			wdt->expect_close = 0;
+
+			for (i = 0; i < count; i++) {
+				char c;
+				if (get_user(c, data+i))
+					return -EFAULT;
+				if (c == 'V')
+					wdt->expect_close = 42;
+			}
+		}
+	}
+
+	return count;
+}
+
+static long kempld_wdt_ioctl(struct file *file, unsigned int cmd,
+				unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	struct kempld_watchdog_data *wdt = kempld_wdt;
+	int options;
+	int value;
+	int ret = 0;
+
+	BUG_ON(wdt == NULL);
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
+			ret = -EFAULT;
+		break;
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(0, p);
+		break;
+	case WDIOC_SETOPTIONS:
+		if (get_user(options, p)) {
+			ret = -EFAULT;
+			break;
+		}
+		if (options & WDIOS_DISABLECARD)
+			ret = kempld_wdt_stop(wdt);
+		if (options & WDIOS_ENABLECARD) {
+			ret = kempld_wdt_start(wdt);
+			kempld_wdt_keepalive(wdt);
+		}
+		break;
+	case WDIOC_KEEPALIVE:
+		kempld_wdt_keepalive(wdt);
+		break;
+	case WDIOC_SETTIMEOUT:
+		if (get_user(value, p)) {
+			ret = -EFAULT;
+			break;
+		}
+		kempld_wdt_update_timeouts(wdt);
+		wdt->timeout = value;
+		ret = kempld_wdt_settimeout(wdt);
+		kempld_wdt_keepalive(wdt);
+		break;
+	case WDIOC_GETTIMEOUT:
+		value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
+		value += kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
+		if (value < 0)
+			ret = ERANGE;
+		else
+			ret = put_user(value, p);
+		break;
+	case WDIOC_SETPRETIMEOUT:
+		if (get_user(value, p)) {
+			ret = -EFAULT;
+			break;
+		}
+		kempld_wdt_update_timeouts(wdt);
+		wdt->pretimeout = value;
+		ret = kempld_wdt_settimeout(wdt);
+		kempld_wdt_keepalive(wdt);
+		break;
+	case WDIOC_GETPRETIMEOUT:
+		value = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
+		if (value)
+			value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
+		if (value < 0)
+			ret = ERANGE;
+		else
+			ret = put_user(value, p);
+		break;
+	default:
+		ret = -ENOTTY;
+	}
+
+	return ret;
+}
+
+static int kempld_wdt_release(struct inode *inode, struct file *file)
+{
+	struct kempld_watchdog_data *wdt = kempld_wdt;
+
+	BUG_ON(wdt == NULL);
+
+	if (wdt->expect_close)
+		kempld_wdt_stop(wdt);
+	else {
+		dev_warn(wdt->pld->dev,
+			 "Unexpected close, not stopping watchdog!\n");
+		kempld_wdt_keepalive(wdt);
+	}
+
+	kempld_wdt->expect_close = 0;
+
+	clear_bit(0, &wdt->is_open);
+
+	return 0;
+}
+
+static int kempld_wdt_open(struct inode *inode, struct file *file)
+{
+	int ret;
+	struct kempld_watchdog_data *wdt = kempld_wdt;
+	struct kempld_device_data *pld = wdt->pld;
+	u8 status;
+
+	BUG_ON(wdt == NULL);
+
+	if (test_and_set_bit(0, &wdt->is_open))
+		return -EBUSY;
+
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	/* kick the watchdog if it is already enabled, otherwise start it */
+	if (status & KEMPLD_WDT_CFG_ENABLE) {
+		kempld_wdt_keepalive(wdt);
+	} else {
+		ret = kempld_wdt_settimeout(wdt);
+		if (ret)
+			goto err_enable_wdt;
+		ret = kempld_wdt_start(wdt);
+		if (ret)
+			goto err_enable_wdt;
+	}
+
+	return nonseekable_open(inode, file);
+
+err_enable_wdt:
+	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
+	wdt->expect_close = 1;
+	kempld_wdt_release(inode, file);
+
+	return ret;
+}
+
+static void kempld_wdt_release_stages(struct kempld_watchdog_data *wdt)
+{
+	int stage;
+
+	wdt->timeout_stage = NULL;
+	wdt->pretimeout_stage = NULL;
+
+	for (stage = 0; stage < KEMPLD_WDT_MAX_STAGES; stage++) {
+		kfree(wdt->stage[stage]);
+		wdt->stage[stage] = NULL;
+	}
+}
+
+static int kempld_wdt_probe_stages(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int i, ret;
+	u32 timeout_mask;
+	struct kempld_watchdog_stage *stage;
+
+	wdt->stages = 0;
+	wdt->timeout_stage = NULL;
+	wdt->pretimeout_stage = NULL;
+
+	for (i = 0; i < KEMPLD_WDT_MAX_STAGES; i++) {
+		int j;
+		u8 index, data, data_orig;
+
+		index = KEMPLD_WDT_STAGE_TIMEOUT(i);
+		timeout_mask = ~0;
+
+		kempld_get_mutex_set_index(pld, index);
+
+		/* Probe each byte individually according to new spec revision.
+		 * Register content is restored afterwards. */
+		for (j = 0; j < 4; j++) {
+			data_orig = kempld_read8(pld, index);
+			kempld_write8(pld, index, 0x00);
+			data = kempld_read8(pld, index);
+			kempld_write8(pld, index, data_orig);
+			*(((u8 *)&timeout_mask)+j) &= data;
+			if (data != 0x0)
+				break;
+			index++;
+		}
+
+		kempld_release_mutex(pld);
+
+		if ((timeout_mask & 0xff) != 0xff) {
+			stage = kzalloc(sizeof(struct kempld_watchdog_stage),
+					GFP_KERNEL);
+			if (stage == NULL) {
+				ret = -ENOMEM;
+				goto err_alloc_stages;
+			}
+			stage->num = i;
+			stage->timeout_mask = ~timeout_mask;
+			wdt->stage[i] = stage;
+			wdt->stages++;
+
+			/* assign available stages to timeout and pretimeout */
+			if (wdt->timeout_stage == NULL) {
+				wdt->timeout_stage = stage;
+			} else if ((wdt->pretimeout_stage == NULL) &&
+				(pld->feature_mask & KEMPLD_FEATURE_BIT_NMI)) {
+				wdt->pretimeout_stage = wdt->timeout_stage;
+				wdt->timeout_stage = stage;
+			}
+		} else
+			wdt->stage[i] = NULL;
+	}
+
+	return 0;
+
+err_alloc_stages:
+	kempld_wdt_release_stages(wdt);
+
+	return ret;
+}
+
+static const struct file_operations kempld_wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= kempld_wdt_write,
+	.unlocked_ioctl	= kempld_wdt_ioctl,
+	.open		= kempld_wdt_open,
+	.release	= kempld_wdt_release,
+};
+
+static struct miscdevice kempld_wdt_miscdev = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &kempld_wdt_fops,
+};
+
+static int kempld_wdt_probe(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt;
+	struct kempld_device_data *pld;
+	u8 status;
+	int ret;
+
+	if (kempld_wdt != NULL) {
+		dev_err(&pdev->dev,
+			"unable to support more than one watchdog devices\n");
+		return -EMFILE;
+	}
+
+	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
+	if (wdt == NULL) {
+		dev_err(&pdev->dev, "unable to get memory for device data\n");
+		ret = -ENOMEM;
+		goto err_alloc_dev_data;
+	}
+
+	pld = dev_get_drvdata(pdev->dev.parent);
+	wdt->pld = pld;
+
+	platform_set_drvdata(pdev, wdt);
+
+	strncpy(wdt->ident.identity, "KEMPLD Watchdog",
+		sizeof(wdt->ident.identity));
+
+	/* watchdog firmware version is identical to the CPLD version */
+	wdt->ident.firmware_version = (pld->info.major<<24)
+		| (pld->info.minor<<16) | pld->info.buildnr;
+
+	/* probe how many usable stages we have */
+	ret = kempld_wdt_probe_stages(wdt);
+	if (ret)
+		goto err_probe_stages;
+
+	/* get initial watchdog status */
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(wdt->pld);
+
+	/* check if the watchdog is already locked and enable the nowayout
+	 * option in that case */
+	if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
+			KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
+		if (!nowayout)
+			dev_warn(wdt->pld->dev,
+				 "Forcing nowayout - watchdog lock enabled!\n");
+		nowayout = 1;
+	}
+
+	/* set default values for the case we start the watchdog or change
+	 * the configuration */
+	wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
+	wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
+
+	/* check if watchdog is enabled */
+	if (status & KEMPLD_WDT_CFG_ENABLE) {
+		/* Get current watchdog settings */
+		kempld_wdt_update_timeouts(wdt);
+
+		dev_info(wdt->pld->dev, "Watchdog is already enabled:\n"
+			 "%d s timeout and %d s pretimeout!\n",
+			 wdt->timeout, wdt->pretimeout);
+	}
+
+	/* update the timeout settings if requested by module parameters */
+	if (timeout > 0)
+		wdt->timeout = timeout;
+	if (pretimeout >= 0)
+		wdt->pretimeout = pretimeout;
+
+	dev_info(wdt->pld->dev, "watchdog will be set on (re)start\n"
+		 "new settings: %d s timeout and %d s pretimeout\n",
+		 wdt->timeout, wdt->pretimeout);
+
+	wdt->ident.options = WDIOF_KEEPALIVEPING;
+	if ((wdt->timeout_stage) && !(status & KEMPLD_WDT_CFG_GLOBAL_LOCK))
+		wdt->ident.options |= WDIOF_SETTIMEOUT;
+	if (wdt->pretimeout_stage)
+		wdt->ident.options |= WDIOF_PRETIMEOUT;
+	if (!nowayout)
+		wdt->ident.options |= WDIOF_MAGICCLOSE;
+
+	kempld_wdt = wdt;
+
+	ret = misc_register(&kempld_wdt_miscdev);
+	if (ret)
+		goto err_misc_register;
+
+	dev_info(wdt->pld->dev,
+		 "%d stage watchdog initialized, pretimeout %ssupported\n",
+		 wdt->stages, wdt->pretimeout_stage ? "" : "not ");
+
+	return 0;
+
+err_probe_stages:
+err_misc_register:
+	kfree(kempld_wdt);
+	kempld_wdt = NULL;
+err_alloc_dev_data:
+	return ret;
+}
+
+static void kempld_wdt_shutdown(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
+
+	BUG_ON(wdt != kempld_wdt);
+
+	/* stop or at least keepalive the watchdog before we leave */
+	if (wdt != NULL) {
+		if (!nowayout)
+			kempld_wdt_stop(wdt);
+		else
+			kempld_wdt_keepalive(wdt);
+	}
+}
+
+static int kempld_wdt_remove(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
+
+	BUG_ON(wdt != kempld_wdt);
+
+	/* stop or at least keepalive the watchdog before we leave */
+	kempld_wdt_shutdown(pdev);
+
+	misc_deregister(&kempld_wdt_miscdev);
+
+	kempld_wdt_release_stages(wdt);
+
+	kfree(wdt);
+	kempld_wdt = NULL;
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int wdt_pm_status_store;
+
+/* Disable watchdog if it is active during suspend */
+static int kempld_wdt_suspend(struct platform_device *pdev,
+				pm_message_t message)
+{
+	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+	wdt_pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	kempld_wdt_update_timeouts(wdt);
+
+	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE)
+		kempld_wdt_shutdown(pdev);
+
+	return 0;
+}
+
+/* Enable watchdog and configure it if necessary */
+static int kempld_wdt_resume(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
+	int ret;
+
+	/* if watchdog was stopped before suspend be sure it gets disabled
+	 * again, for the case BIOS has enabled it during resume */
+	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE) {
+		ret = kempld_wdt_settimeout(wdt);
+		if (ret)
+			goto err_enable_wdt;
+		ret = kempld_wdt_start(wdt);
+		if (ret)
+			goto err_enable_wdt;
+
+		dev_info(wdt->pld->dev, "Resuming watchdog operation:\n"
+			 "%d s timeout and %d s pretimeout\n", wdt->timeout,
+			 wdt->pretimeout);
+	} else
+		kempld_wdt_shutdown(pdev);
+
+	return 0;
+
+err_enable_wdt:
+	dev_err(wdt->pld->dev,
+		"Failed to reenable the watchdog timer after resume!\n");
+
+	return ret;
+}
+#else
+#define kempld_wdt_suspend	NULL
+#define kempld_wdt_resume	NULL
+#endif
+
+static struct platform_driver kempld_wdt_driver = {
+	.driver = {
+		.name = "kempld-wdt",
+		.owner = THIS_MODULE,
+	},
+	.probe = kempld_wdt_probe,
+	.remove = kempld_wdt_remove,
+	.shutdown = kempld_wdt_shutdown,
+	.suspend = kempld_wdt_suspend,
+	.resume = kempld_wdt_resume,
+};
+
+static int __init kempld_wdt_init(void)
+{
+	return platform_driver_register(&kempld_wdt_driver);
+}
+
+static void __exit kempld_wdt_exit(void)
+{
+	platform_driver_unregister(&kempld_wdt_driver);
+}
+
+module_init(kempld_wdt_init);
+module_exit(kempld_wdt_exit);
+
+MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/watchdog/kempld_wdt.h b/drivers/watchdog/kempld_wdt.h
new file mode 100644
index 0000000..80f68f6
--- /dev/null
+++ b/drivers/watchdog/kempld_wdt.h
@@ -0,0 +1,75 @@
+/*
+ *  kempld_wdt.h - Kontron PLD watchdog driver definitions
+ *
+ *  Copyright (c) 2010-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _KEMPLD_WDT_H_
+#define _KEMPLD_WDT_H_
+
+/* watchdog register definitions */
+#define KEMPLD_WDT_KICK			0x16
+#define KEMPLD_WDT_CFG			0x17
+#define		KEMPLD_WDT_CFG_STAGE_TIMEOUT_OCCURED(x)	(1<<x)
+#define		KEMPLD_WDT_CFG_ENABLE_LOCK		0x8
+#define		KEMPLD_WDT_CFG_ENABLE			0x10
+#define		KEMPLD_WDT_CFG_AUTO_RELOAD		0x40
+#define		KEMPLD_WDT_CFG_GLOBAL_LOCK		0x80
+#define KEMPLD_WDT_STAGE_CFG(x)		(0x18+x)
+#define		KEMPLD_WDT_STAGE_CFG_ACTION_MASK	0x7
+#define		KEMPLD_WDT_STAGE_CFG_GET_ACTION(x)	(x & 0x7)
+#define		KEMPLD_WDT_STAGE_CFG_ASSERT		(1<<3)
+#define		KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK	0x30
+#define		KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(x)	((x & 0x30)>>4)
+#define		KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(x)	((x & 0x30)<<4)
+#define KEMPLD_WDT_STAGE_TIMEOUT(x)	(0x1b+x*4)
+#define KEMPLD_WDT_MAX_STAGES		3
+
+#define	KEMPLD_WDT_ACTION_NONE		0x0
+#define	KEMPLD_WDT_ACTION_RESET		0x1
+#define	KEMPLD_WDT_ACTION_NMI		0x2
+#define	KEMPLD_WDT_ACTION_SMI		0x3
+#define	KEMPLD_WDT_ACTION_SCI		0x4
+#define	KEMPLD_WDT_ACTION_DELAY		0x5
+
+#define	KEMPLD_WDT_PRESCALER_21BIT	0x0
+#define	KEMPLD_WDT_PRESCALER_17BIT	0x1
+#define	KEMPLD_WDT_PRESCALER_12BIT	0x2
+
+const int kempld_prescaler_bits[] = { 21, 17, 12 };
+#define KEMPLD_PRESCALER(x)	(0xffffffff>>(32-kempld_prescaler_bits[x]))
+
+
+struct kempld_watchdog_stage {
+	int	num;
+	u32	timeout_mask;
+};
+
+struct kempld_watchdog_data {
+	int				timeout;
+	int				pretimeout;
+	unsigned long			is_open;
+	unsigned long			expect_close;
+	int				stages;
+	struct kempld_watchdog_stage	*timeout_stage;
+	struct kempld_watchdog_stage	*pretimeout_stage;
+	struct kempld_device_data	*pld;
+	struct watchdog_info		ident;
+	struct kempld_watchdog_stage	*stage[KEMPLD_WDT_MAX_STAGES];
+};
+
+#endif /* _KEMPLD_WDT_H_ */
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCH 3/4] gpio: Kontron PLD gpio driver
  2013-04-08 17:15 ` [PATCH 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
@ 2013-04-09  8:46   ` Linus Walleij
  2013-04-09 16:41     ` Guenter Roeck
  2013-04-10 20:45   ` Linus Walleij
  1 sibling, 1 reply; 48+ messages in thread
From: Linus Walleij @ 2013-04-09  8:46 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Michael Brunner, Samuel Ortiz, Wolfram Sang,
	Ben Dooks, linux-i2c, Grant Likely, Wim Van Sebroeck,
	linux-watchdog, Darren Hart, Michael Brunner, Greg Kroah-Hartman

On Mon, Apr 8, 2013 at 7:15 PM, Kevin Strasser
<kevin.strasser@linux.intel.com> wrote:

> From: Michael Brunner <michael.brunner@kontron.com>
>
> Add gpio support for the on-board PLD found on some Kontron embedded
> modules.
>
> Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>

This looks very generic, setting and clearing bits in bytesized
registers.

Can you please attempt to use generic GPIO for this?

drivers/gpio/gpio-generic.c
<linux/basic_mmio_gpio.h>

See for example:
gpio-ep93xx.c, gpio-sodaville.c ...

Since you don't even have IRQ support in this it will be even simpler.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 3/4] gpio: Kontron PLD gpio driver
  2013-04-09  8:46   ` Linus Walleij
@ 2013-04-09 16:41     ` Guenter Roeck
  2013-04-10 20:06       ` Linus Walleij
  0 siblings, 1 reply; 48+ messages in thread
From: Guenter Roeck @ 2013-04-09 16:41 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Kevin Strasser, linux-kernel, Michael Brunner, Samuel Ortiz,
	Wolfram Sang, Ben Dooks, linux-i2c, Grant Likely,
	Wim Van Sebroeck, linux-watchdog, Darren Hart, Michael Brunner,
	Greg Kroah-Hartman

On Tue, Apr 09, 2013 at 10:46:15AM +0200, Linus Walleij wrote:
> On Mon, Apr 8, 2013 at 7:15 PM, Kevin Strasser
> <kevin.strasser@linux.intel.com> wrote:
> 
> > From: Michael Brunner <michael.brunner@kontron.com>
> >
> > Add gpio support for the on-board PLD found on some Kontron embedded
> > modules.
> >
> > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> 
> This looks very generic, setting and clearing bits in bytesized
> registers.
> 
> Can you please attempt to use generic GPIO for this?
> 
Linus,

I looked into it, but for my part I seem to be missing how the generic GPIO code
permits locking access to the hardware (PLD) and setting the PLD's page register.
In other words, I don't immediately see how to call kempld_get_mutex_set_index()
from the generic GPIO code. The other drivers using generic GPIO code don't
seem to have that requirement.

Thanks,
Guenter

> drivers/gpio/gpio-generic.c
> <linux/basic_mmio_gpio.h>
> 
> See for example:
> gpio-ep93xx.c, gpio-sodaville.c ...
> 
> Since you don't even have IRQ support in this it will be even simpler.
> 
> Yours,
> Linus Walleij
> --
> To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 4/4] watchdog: Kontron PLD watchdog timer
  2013-04-08 17:15 ` [PATCH 4/4] watchdog: Kontron PLD watchdog timer Kevin Strasser
@ 2013-04-10 16:47   ` Guenter Roeck
  2013-04-10 16:57     ` Kevin Strasser
  0 siblings, 1 reply; 48+ messages in thread
From: Guenter Roeck @ 2013-04-10 16:47 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Michael Brunner, Samuel Ortiz, Wolfram Sang,
	Ben Dooks, linux-i2c, Grant Likely, Linus Walleij,
	Wim Van Sebroeck, linux-watchdog, Darren Hart, Michael Brunner,
	Greg Kroah-Hartman

On Mon, Apr 08, 2013 at 10:15:21AM -0700, Kevin Strasser wrote:
> From: Michael Brunner <michael.brunner@kontron.com>
> 
> Add watchdog timer support for the on-board PLD found on some Kontron
> embedded modules.
> 
> Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>

Personally I would prefer two separate patches for the two drivers,
and to have the drivers converted to the watchdog infrastructure.
Wim's call, of course.

Thanks,
Guenter

> ---
>  drivers/watchdog/Kconfig           |   20 +
>  drivers/watchdog/Makefile          |    2 +
>  drivers/watchdog/kempld_now1_wdt.c |  602 +++++++++++++++++++++++++++
>  drivers/watchdog/kempld_wdt.c      |  796 ++++++++++++++++++++++++++++++++++++
>  drivers/watchdog/kempld_wdt.h      |   75 ++++
>  5 files changed, 1495 insertions(+)
>  create mode 100644 drivers/watchdog/kempld_now1_wdt.c
>  create mode 100644 drivers/watchdog/kempld_wdt.c
>  create mode 100644 drivers/watchdog/kempld_wdt.h
> 
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index 9fcc70c..9ac71ca 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -687,6 +687,26 @@ config HP_WATCHDOG
>  	  To compile this driver as a module, choose M here: the module will be
>  	  called hpwdt.
>  
> +config KEMPLD_WDT
> +	tristate "Kontron COM watchdog"
> +	depends on MFD_KEMPLD
> +	help
> +	  Support for the PLD watchdog on some Kontron ETX and COMexpress
> +	  (ETXexpress) modules
> +
> +	  This driver can also be built as a module. If so, the module will be
> +	  called kempld_wdt.
> +
> +config KEMPLD_NOW1_WDT
> +	tristate "Kontron COMe-mSP1 (nanoETXexpress-SP) watchdog"
> +	depends on MFD_KEMPLD
> +	help
> +	  Support for the PLD watchdog on the Kontron COMe-mSP1
> +	  (nanoETXexpress-SP) module.
> +
> +	  This driver can also be built as a module. If so, the module will
> +	  be called kempld_now1_wdt.
> +
>  config HPWDT_NMI_DECODING
>  	bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer"
>  	depends on HP_WATCHDOG
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index a300b94..a029930 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -90,6 +90,8 @@ endif
>  obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
>  obj-$(CONFIG_IT87_WDT) += it87_wdt.o
>  obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
> +obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
> +obj-$(COnFIG_KEMPLD_NOW1_WDT) += kempld_now1_wdt.o
>  obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
>  obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
>  obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
> diff --git a/drivers/watchdog/kempld_now1_wdt.c b/drivers/watchdog/kempld_now1_wdt.c
> new file mode 100644
> index 0000000..19b7272
> --- /dev/null
> +++ b/drivers/watchdog/kempld_now1_wdt.c
> @@ -0,0 +1,602 @@
> +/*
> + *  kempld_now1_wdt.c - Kontron PLD watchdog driver for COMe-mSP1
> + *
> + *  Copyright (c) 2011-2012 Kontron Europe GmbH
> + *  Author: Michael Brunner <michael.brunner@kontron.com>
> + *
> + *  Note: The watchdog of the COMe-mSP1 (nanoETXexpress-SP) has one stage and
> + *        only supports predefined watchdog timeout values.
> + *
> + *        The supported timeouts are:
> + *              1 s, 5 s, 10 s, 30 s, 1 min, 5 min, 10 min, 15 min
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License 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
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; see the file COPYING.  If not, write to
> + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/types.h>
> +#include <linux/miscdevice.h>
> +#include <linux/watchdog.h>
> +#include <linux/fs.h>
> +#include <linux/ioport.h>
> +#include <linux/init.h>
> +#include <linux/uaccess.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/kempld.h>
> +
> +#include "kempld_wdt.h"
> +
> +#define WATCHDOG_TIMEOUT 30
> +static int timeout = WATCHDOG_TIMEOUT;
> +module_param(timeout, int, 0);
> +MODULE_PARM_DESC(timeout,
> +		 "Watchdog timeout in seconds. (1, 5, 10, 30, 60, 300, 600, "
> +		 "900, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +module_param(nowayout, bool, 0);
> +MODULE_PARM_DESC(nowayout,
> +		 "Watchdog cannot be stopped once started (default="
> +		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +/* nanoETXexpress-SP watchdog register definitions */
> +#define KEMPLD_WDT_NOW1					0xA2
> +#define		KEMPLD_WDT_NOW1_KICK_MASK		0x80
> +#define		KEMPLD_WDT_NOW1_ENABLE_MASK		0x40
> +#define		KEMPLD_WDT_NOW1_TIMEOUT_MASK		0x1f
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_1SEC	0x00
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_5SEC	0x01
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_10SEC	0x02
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_30SEC	0x03
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_1MIN	0x10
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_5MIN	0x11
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_10MIN	0x12
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_15MIN	0x13
> +
> +/* delay in us necessary due to clock domain sync */
> +#define		KEMPLD_WDT_NOW1_SYNC_DELAY		31
> +
> +static struct kempld_watchdog_data *kempld_now1_wdt;
> +
> +static int kempld_now1_wdt_read_supported;
> +static int kempld_now1_wdt_reg_cache;
> +
> +static int kempld_now1_wdt_start(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int wdt_reg;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +	if (kempld_now1_wdt_read_supported)
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	else
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +	wdt_reg |= KEMPLD_WDT_NOW1_ENABLE_MASK;
> +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +	kempld_release_mutex(pld);
> +
> +	if (kempld_now1_wdt_read_supported) {
> +		/* read out the register again to check if everything worked */
> +		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +		kempld_release_mutex(pld);
> +
> +		/* check if the watchdog was disabled */
> +		if (!(wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK))
> +			return -EACCES;
> +	}
> +
> +	return 0;
> +}
> +
> +static int kempld_now1_wdt_stop(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int wdt_reg;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +	if (kempld_now1_wdt_read_supported) {
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	} else
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +	wdt_reg &= ~KEMPLD_WDT_NOW1_ENABLE_MASK;
> +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +	kempld_release_mutex(pld);
> +
> +	if (kempld_now1_wdt_read_supported) {
> +		/* read out the register again to check if everything worked */
> +		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +		kempld_release_mutex(pld);
> +
> +		/* check if the watchdog was disabled */
> +		if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
> +			return -EACCES;
> +	}
> +
> +	return 0;
> +}
> +
> +static int kempld_now1_wdt_keepalive(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int wdt_reg;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +
> +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +
> +	if (kempld_now1_wdt_read_supported) {
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	} else {
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +		/* write the state again to be sure the trigger register has
> +		 * the right level */
> +		kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> +	}
> +
> +	if (wdt_reg & KEMPLD_WDT_NOW1_KICK_MASK)
> +		wdt_reg &= ~KEMPLD_WDT_NOW1_KICK_MASK;
> +	else
> +		wdt_reg |= KEMPLD_WDT_NOW1_KICK_MASK;
> +
> +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +
> +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> +
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +
> +	kempld_release_mutex(pld);
> +
> +	return 0;
> +}
> +
> +
> +static int kempld_now1_wdt_settimeout(struct kempld_watchdog_data *wdt,
> +					int check_only)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int wdt_reg;
> +	int ret = 0;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +
> +	if (kempld_now1_wdt_read_supported) {
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	} else
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +
> +	wdt_reg &= ~KEMPLD_WDT_NOW1_TIMEOUT_MASK;
> +
> +	switch (wdt->timeout) {
> +	case 1:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1SEC;
> +		break;
> +	case 5:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5SEC;
> +		break;
> +	case 10:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10SEC;
> +		break;
> +	case 30:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_30SEC;
> +		break;
> +	case 60:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1MIN;
> +		break;
> +	case 300:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5MIN;
> +		break;
> +	case 600:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10MIN;
> +		break;
> +	case 900:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_15MIN;
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		dev_err(wdt->pld->dev,
> +			"Invalid timeout value given!\n");
> +	}
> +
> +	if (!check_only) {
> +		if (ret == 0) {
> +			udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +
> +			kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> +		}
> +	}
> +
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +
> +	kempld_release_mutex(pld);
> +
> +	return ret;
> +}
> +
> +static int kempld_now1_wdt_gettimeout(struct kempld_watchdog_data *wdt,
> +				 struct kempld_watchdog_stage *stage)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int wdt_reg;
> +	int timeout;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +
> +	if (kempld_now1_wdt_read_supported) {
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +		kempld_now1_wdt_reg_cache = wdt_reg;
> +	} else
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +
> +	kempld_release_mutex(pld);
> +
> +	switch (wdt_reg&KEMPLD_WDT_NOW1_TIMEOUT_MASK) {
> +	case KEMPLD_WDT_NOW1_TIMEOUT_1SEC:
> +		timeout = 1;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_5SEC:
> +		timeout = 5;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_10SEC:
> +		timeout = 10;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_30SEC:
> +		timeout = 30;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_1MIN:
> +		timeout = 60;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_5MIN:
> +		timeout = 300;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_10MIN:
> +		timeout = 600;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_15MIN:
> +		timeout = 900;
> +		break;
> +	default:
> +		timeout = -ERANGE;
> +	}
> +
> +	return timeout;
> +}
> +
> +static ssize_t kempld_now1_wdt_write(struct file *file, const char __user
> +					*data, size_t count, loff_t *ppos)
> +{
> +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (count) {
> +		kempld_now1_wdt_keepalive(wdt);
> +
> +		if (!nowayout) {
> +			size_t i;
> +
> +			wdt->expect_close = 0;
> +
> +			for (i = 0; i < count; i++) {
> +				char c;
> +				if (get_user(c, data+i))
> +					return -EFAULT;
> +				if (c == 'V')
> +					wdt->expect_close = 42;
> +			}
> +		}
> +	}
> +
> +	return count;
> +}
> +
> +static long kempld_now1_wdt_ioctl(struct file *file, unsigned int cmd,
> +				unsigned long arg)
> +{
> +	void __user *argp = (void __user *)arg;
> +	int __user *p = argp;
> +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> +	int options;
> +	int value;
> +	int ret = 0;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	switch (cmd) {
> +	case WDIOC_GETSUPPORT:
> +		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
> +			ret = -EFAULT;
> +		break;
> +	case WDIOC_GETSTATUS:
> +	case WDIOC_GETBOOTSTATUS:
> +		ret = put_user(0, p);
> +		break;
> +	case WDIOC_SETOPTIONS:
> +		if (get_user(options, p)) {
> +			ret = -EFAULT;
> +			break;
> +		}
> +		if (options & WDIOS_DISABLECARD)
> +			ret = kempld_now1_wdt_stop(wdt);
> +		if (options & WDIOS_ENABLECARD) {
> +			ret = kempld_now1_wdt_start(wdt);
> +			kempld_now1_wdt_keepalive(wdt);
> +		}
> +		break;
> +	case WDIOC_KEEPALIVE:
> +		kempld_now1_wdt_keepalive(wdt);
> +		break;
> +	case WDIOC_SETTIMEOUT:
> +		if (get_user(value, p)) {
> +			ret = -EFAULT;
> +			break;
> +		}
> +		wdt->timeout = value;
> +		ret = kempld_now1_wdt_settimeout(wdt, 0);
> +		kempld_now1_wdt_keepalive(wdt);
> +		break;
> +	case WDIOC_GETTIMEOUT:
> +		value = kempld_now1_wdt_gettimeout(wdt, wdt->timeout_stage);
> +		if (timeout < 0)
> +			ret = ERANGE;
> +		else
> +			ret = put_user(timeout, p);
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +	}
> +
> +	return ret;
> +}
> +
> +static int kempld_now1_wdt_release(struct inode *inode, struct file *file)
> +{
> +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (wdt->expect_close)
> +		kempld_now1_wdt_stop(wdt);
> +	else {
> +		dev_warn(wdt->pld->dev,
> +			 "Unexpected close, not stopping watchdog!\n");
> +		kempld_now1_wdt_keepalive(wdt);
> +	}
> +
> +	kempld_now1_wdt->expect_close = 0;
> +
> +	clear_bit(0, &wdt->is_open);
> +
> +	return 0;
> +}
> +
> +static int kempld_now1_wdt_open(struct inode *inode, struct file *file)
> +{
> +	int ret;
> +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 wdt_reg;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (test_and_set_bit(0, &wdt->is_open))
> +		return -EBUSY;
> +
> +	if (nowayout)
> +		__module_get(THIS_MODULE);
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +	if (kempld_now1_wdt_read_supported) {
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	} else {
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +	}
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +	kempld_release_mutex(pld);
> +
> +	/* kick the watchdog if it is already enabled, otherwise start it */
> +	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK) {
> +		kempld_now1_wdt_keepalive(wdt);
> +	} else {
> +		ret = kempld_now1_wdt_settimeout(wdt, 0);
> +		if (ret)
> +			goto err_enable_wdt;
> +		ret = kempld_now1_wdt_start(wdt);
> +		if (ret)
> +			goto err_enable_wdt;
> +	}
> +
> +	return nonseekable_open(inode, file);
> +
> +err_enable_wdt:
> +	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
> +	wdt->expect_close = 1;
> +	kempld_now1_wdt_release(inode, file);
> +
> +	return ret;
> +}
> +
> +static const struct file_operations kempld_now1_wdt_fops = {
> +	.owner		= THIS_MODULE,
> +	.llseek		= no_llseek,
> +	.write		= kempld_now1_wdt_write,
> +	.unlocked_ioctl	= kempld_now1_wdt_ioctl,
> +	.open		= kempld_now1_wdt_open,
> +	.release	= kempld_now1_wdt_release,
> +};
> +
> +static struct miscdevice kempld_now1_wdt_miscdev = {
> +	.minor	= WATCHDOG_MINOR,
> +	.name	= "watchdog",
> +	.fops	= &kempld_now1_wdt_fops,
> +};
> +
> +static int kempld_now1_wdt_probe(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt;
> +	struct kempld_device_data *pld;
> +	u8 wdt_reg;
> +	int ret;
> +
> +	if (kempld_now1_wdt != NULL) {
> +		dev_err(&pdev->dev,
> +			"unable to support more than one watchdog devices\n");
> +		return -EMFILE;
> +	}
> +
> +	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
> +	if (wdt == NULL) {
> +		dev_err(&pdev->dev, "unable to get memory for device data\n");
> +		ret = -ENOMEM;
> +		goto err_alloc_dev_data;
> +	}
> +
> +	wdt->timeout_stage = kzalloc(sizeof(struct kempld_watchdog_stage),
> +					GFP_KERNEL);
> +	if (wdt->timeout_stage == NULL) {
> +		dev_err(&pdev->dev,
> +			"unable to get memory for watchdog stage\n");
> +		ret = -ENOMEM;
> +		goto err_alloc_dev_data;
> +	}
> +
> +	wdt->stages = 1;
> +	wdt->stage[0] = wdt->timeout_stage;
> +
> +	pld = dev_get_drvdata(pdev->dev.parent);
> +	wdt->pld = pld;
> +
> +	platform_set_drvdata(pdev, wdt);
> +
> +	strncpy(wdt->ident.identity, "nanoETXexpress-SP Watchdog",
> +		sizeof(wdt->ident.identity));
> +
> +	/* set default values for the case we start the watchdog or change
> +	 * the configuration */
> +	wdt->timeout = timeout;
> +
> +	/* use settimeout to check if the timeout parameter is valid */
> +	ret = kempld_now1_wdt_settimeout(wdt, 1);
> +	if (ret)
> +		goto err_check_timeout;
> +	wdt->ident.firmware_version = (pld->info.major<<8) + pld->info.minor;
> +	if (pld->info.major > 67)
> +		kempld_now1_wdt_read_supported = 1;
> +	else
> +		dev_info(wdt->pld->dev,
> +			 "Watchdog revision does not support read - "
> +			 "unable to get watchdog state!\n");
> +
> +	/* get initial watchdog status */
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +	if (kempld_now1_wdt_read_supported) {
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	} else {
> +		wdt_reg = 0x0;
> +	}
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +	kempld_release_mutex(wdt->pld);
> +
> +	/* check if watchdog is enabled */
> +	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
> +		dev_info(wdt->pld->dev, "Watchdog is already enabled!\n");
> +
> +	wdt->ident.options = WDIOF_KEEPALIVEPING;
> +	wdt->ident.options |= WDIOF_SETTIMEOUT;
> +	if (!nowayout)
> +		wdt->ident.options |= WDIOF_MAGICCLOSE;
> +
> +	kempld_now1_wdt = wdt;
> +
> +	ret = misc_register(&kempld_now1_wdt_miscdev);
> +	if (ret)
> +		goto err_misc_register;
> +
> +	dev_info(wdt->pld->dev, "watchdog initialized\n");
> +
> +	return 0;
> +
> +err_misc_register:
> +	kfree(kempld_now1_wdt);
> +	kempld_now1_wdt = NULL;
> +err_check_timeout:
> +err_alloc_dev_data:
> +	return ret;
> +}
> +
> +static int kempld_now1_wdt_remove(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> +
> +	BUG_ON(wdt != kempld_now1_wdt);
> +
> +	/* stop or at least keepalive the watchdog before we leave */
> +	if (wdt != NULL) {
> +		if (!nowayout)
> +			kempld_now1_wdt_stop(wdt);
> +		else
> +			kempld_now1_wdt_keepalive(wdt);
> +	}
> +
> +	misc_deregister(&kempld_now1_wdt_miscdev);
> +
> +	kfree(wdt);
> +	kempld_now1_wdt = NULL;
> +	platform_set_drvdata(pdev, NULL);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver kempld_now1_wdt_driver = {
> +	.driver = {
> +		.name = "kempld_now1-wdt",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe = kempld_now1_wdt_probe,
> +	.remove = kempld_now1_wdt_remove,
> +};
> +
> +static int __init kempld_now1_wdt_init(void)
> +{
> +	return platform_driver_register(&kempld_now1_wdt_driver);
> +}
> +
> +static void __exit kempld_now1_wdt_exit(void)
> +{
> +	platform_driver_unregister(&kempld_now1_wdt_driver);
> +}
> +
> +module_init(kempld_now1_wdt_init);
> +module_exit(kempld_now1_wdt_exit);
> +
> +MODULE_DESCRIPTION("KEM PLD nanoETXexpress-SP Watchdog Driver");
> +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
> diff --git a/drivers/watchdog/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c
> new file mode 100644
> index 0000000..bc150e5
> --- /dev/null
> +++ b/drivers/watchdog/kempld_wdt.c
> @@ -0,0 +1,796 @@
> +/*
> + *  kempld_wdt.c - Kontron PLD watchdog driver
> + *
> + *  Copyright (c) 2010-2012 Kontron Europe GmbH
> + *  Author: Michael Brunner <michael.brunner@kontron.com>
> + *
> + *  Note: From the PLD watchdog point of view timeout and pretimeout are
> + *        defined differently than in the kernel.
> + *        First the pretimeout stage runs out before the timeout stage gets
> + *        active. This has to be kept in mind.
> + *
> + *  Kernel/API:                     P-----| pretimeout
> + *                |-----------------------T timeout
> + *  Watchdog:     |-----------------P       pretimeout_stage
> + *                                  |-----T timeout_stage
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License 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
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; see the file COPYING.  If not, write to
> + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/types.h>
> +#include <linux/miscdevice.h>
> +#include <linux/watchdog.h>
> +#include <linux/fs.h>
> +#include <linux/ioport.h>
> +#include <linux/init.h>
> +#include <linux/uaccess.h>
> +#include <linux/slab.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/kempld.h>
> +
> +#include "kempld_wdt.h"
> +
> +#define WATCHDOG_DEFAULT_TIMEOUT 20
> +#define WATCHDOG_DEFAULT_PRETIMEOUT 0
> +static int timeout = -1;
> +static int pretimeout = -1;
> +/* The maximum timeout values have to be probed */
> +module_param(timeout, int, 0);
> +MODULE_PARM_DESC(timeout,
> +		 "Watchdog timeout in seconds. (>0, default="
> +		__MODULE_STRING(WATCHDOG_DEFAULT_TIMEOUT) ")");
> +module_param(pretimeout, int, 0);
> +MODULE_PARM_DESC(pretimeout,
> +		 "Watchdog pretimeout in seconds. (>=0, default="
> +		__MODULE_STRING(WATCHDOG_DEFAULT_PRETIMEOUT) ")");
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +module_param(nowayout, bool, 0);
> +MODULE_PARM_DESC(nowayout,
> +		 "Watchdog cannot be stopped once started (default="
> +		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +static struct kempld_watchdog_data *kempld_wdt;
> +
> +static int kempld_wdt_start(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 status;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +	status |= KEMPLD_WDT_CFG_ENABLE;
> +	kempld_write8(pld, KEMPLD_WDT_CFG, status);
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +
> +	kempld_release_mutex(pld);
> +
> +	/* check if the watchdog was enabled */
> +	if (!(status & KEMPLD_WDT_CFG_ENABLE))
> +		return -EACCES;
> +
> +	return 0;
> +}
> +
> +static int kempld_wdt_stop(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 status;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +	status &= ~KEMPLD_WDT_CFG_ENABLE;
> +	kempld_write8(pld, KEMPLD_WDT_CFG, status);
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +
> +	kempld_release_mutex(pld);
> +
> +	/* check if the watchdog was disabled */
> +	if (status & KEMPLD_WDT_CFG_ENABLE)
> +		return -EACCES;
> +
> +	return 0;
> +}
> +
> +static int kempld_wdt_keepalive(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +
> +	kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
> +
> +	kempld_release_mutex(pld);
> +
> +	return 0;
> +}
> +
> +static int kempld_wdt_gettimeout(struct kempld_watchdog_data *wdt,
> +				 struct kempld_watchdog_stage *stage)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 stage_cfg;
> +	int bits;
> +	u64 timeout;
> +	u32 remainder;
> +
> +	if (stage == NULL)
> +		return 0;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +
> +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +	timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num));
> +
> +	kempld_release_mutex(pld);
> +
> +	bits = KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(stage_cfg);
> +	timeout = (timeout & stage->timeout_mask) * KEMPLD_PRESCALER(bits);
> +	remainder = do_div(timeout, pld->pld_clock);
> +
> +	/* Round up the return value if necessary */
> +	if ((timeout > 0) && (remainder >= (pld->pld_clock/2)))
> +		timeout++;
> +
> +	return timeout;
> +}
> +
> +static int kempld_wdt_setstageaction(struct kempld_watchdog_data *wdt,
> +				 struct kempld_watchdog_stage *stage,
> +				 int action)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 stage_cfg;
> +
> +	if (stage == NULL)
> +		return -EINVAL;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +
> +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ACTION_MASK;
> +	stage_cfg |= (action & KEMPLD_WDT_STAGE_CFG_ACTION_MASK);
> +	if (action == KEMPLD_WDT_ACTION_RESET)
> +		stage_cfg |= KEMPLD_WDT_STAGE_CFG_ASSERT;
> +	else
> +		stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ASSERT;
> +
> +	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
> +
> +	kempld_release_mutex(pld);
> +
> +	return 0;
> +}
> +
> +static int kempld_wdt_setstagetimeout(struct kempld_watchdog_data *wdt,
> +				 struct kempld_watchdog_stage *stage,
> +				 int timeout)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 stage_cfg;
> +	u8 prescaler;
> +	u64 stage_timeout64;
> +	u32 stage_timeout;
> +	u32 remainder;
> +
> +	if (stage == NULL)
> +		return -EINVAL;
> +
> +	prescaler = KEMPLD_WDT_PRESCALER_21BIT;
> +
> +	stage_timeout64 = ((u64)timeout*pld->pld_clock);
> +	remainder = do_div(stage_timeout64, KEMPLD_PRESCALER(prescaler));
> +	if (remainder)
> +		stage_timeout64++;
> +	stage_timeout = stage_timeout64 & stage->timeout_mask;
> +
> +	if (stage_timeout64 != (u64)stage_timeout)
> +		return -EINVAL;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +
> +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK;
> +	stage_cfg |= KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(prescaler);
> +	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
> +	kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num),
> +			stage_timeout);
> +
> +	kempld_release_mutex(pld);
> +
> +	return 0;
> +}
> +
> +static void kempld_wdt_update_timeouts(struct kempld_watchdog_data *wdt)
> +{
> +	int pretimeout_stage;
> +	int timeout_stage;
> +
> +	pretimeout_stage = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> +	timeout_stage = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> +
> +	if (pretimeout_stage)
> +		wdt->pretimeout = timeout_stage;
> +	else
> +		wdt->pretimeout = 0;
> +
> +	wdt->timeout = pretimeout_stage + timeout_stage;
> +
> +	if (wdt->pretimeout < 0) {
> +		wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
> +		dev_err(wdt->pld->dev, "failed to get valid pretimeout value\n"
> +			" -> using driver default\n");
> +	}
> +	if (wdt->timeout < 0) {
> +		wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
> +		dev_err(wdt->pld->dev, "failed to get valid timeout value\n"
> +			" -> using driver default\n");
> +	}
> +}
> +
> +static int kempld_wdt_settimeout(struct kempld_watchdog_data *wdt)
> +{
> +	int stage_timeout;
> +	int stage_pretimeout;
> +	int ret;
> +
> +	if ((wdt->timeout <= 0) ||
> +		(wdt->pretimeout < 0) ||
> +		(wdt->pretimeout > wdt->timeout)) {
> +		ret = -EINVAL;
> +		goto err_check_values;
> +	}
> +
> +	if ((wdt->pretimeout == 0) || (wdt->pretimeout_stage == NULL)) {
> +		if (wdt->pretimeout != 0)
> +			dev_warn(wdt->pld->dev,
> +				 "no pretimeout stage available\n"
> +				 " -> only enabling reset\n");
> +		stage_pretimeout = 0;
> +		stage_timeout = wdt->timeout;
> +	} else {
> +		stage_pretimeout = wdt->timeout - wdt->pretimeout;
> +		stage_timeout = wdt->pretimeout;
> +	}
> +
> +	if (stage_pretimeout != 0) {
> +		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
> +						KEMPLD_WDT_ACTION_NMI);
> +	} else if ((stage_pretimeout == 0)
> +			&& (wdt->pretimeout_stage != NULL)) {
> +		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
> +						KEMPLD_WDT_ACTION_NONE);
> +	} else
> +		ret = 0;
> +	if (ret)
> +		goto err_setstage;
> +
> +	if (stage_pretimeout != 0) {
> +		ret = kempld_wdt_setstagetimeout(wdt, wdt->pretimeout_stage,
> +						 stage_pretimeout);
> +		if (ret)
> +			goto err_setstage;
> +	}
> +
> +	ret = kempld_wdt_setstageaction(wdt, wdt->timeout_stage,
> +					KEMPLD_WDT_ACTION_RESET);
> +	if (ret)
> +		goto err_setstage;
> +
> +	ret = kempld_wdt_setstagetimeout(wdt, wdt->timeout_stage,
> +					 stage_timeout);
> +	if (ret)
> +		goto err_setstage;
> +
> +	return 0;
> +err_setstage:
> +err_check_values:
> +	return ret;
> +}
> +
> +static ssize_t kempld_wdt_write(struct file *file, const char __user *data,
> +				size_t count, loff_t *ppos)
> +{
> +	struct kempld_watchdog_data *wdt = kempld_wdt;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (count) {
> +		kempld_wdt_keepalive(wdt);
> +
> +		if (!nowayout) {
> +			size_t i;
> +
> +			wdt->expect_close = 0;
> +
> +			for (i = 0; i < count; i++) {
> +				char c;
> +				if (get_user(c, data+i))
> +					return -EFAULT;
> +				if (c == 'V')
> +					wdt->expect_close = 42;
> +			}
> +		}
> +	}
> +
> +	return count;
> +}
> +
> +static long kempld_wdt_ioctl(struct file *file, unsigned int cmd,
> +				unsigned long arg)
> +{
> +	void __user *argp = (void __user *)arg;
> +	int __user *p = argp;
> +	struct kempld_watchdog_data *wdt = kempld_wdt;
> +	int options;
> +	int value;
> +	int ret = 0;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	switch (cmd) {
> +	case WDIOC_GETSUPPORT:
> +		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
> +			ret = -EFAULT;
> +		break;
> +	case WDIOC_GETSTATUS:
> +	case WDIOC_GETBOOTSTATUS:
> +		ret = put_user(0, p);
> +		break;
> +	case WDIOC_SETOPTIONS:
> +		if (get_user(options, p)) {
> +			ret = -EFAULT;
> +			break;
> +		}
> +		if (options & WDIOS_DISABLECARD)
> +			ret = kempld_wdt_stop(wdt);
> +		if (options & WDIOS_ENABLECARD) {
> +			ret = kempld_wdt_start(wdt);
> +			kempld_wdt_keepalive(wdt);
> +		}
> +		break;
> +	case WDIOC_KEEPALIVE:
> +		kempld_wdt_keepalive(wdt);
> +		break;
> +	case WDIOC_SETTIMEOUT:
> +		if (get_user(value, p)) {
> +			ret = -EFAULT;
> +			break;
> +		}
> +		kempld_wdt_update_timeouts(wdt);
> +		wdt->timeout = value;
> +		ret = kempld_wdt_settimeout(wdt);
> +		kempld_wdt_keepalive(wdt);
> +		break;
> +	case WDIOC_GETTIMEOUT:
> +		value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> +		value += kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> +		if (value < 0)
> +			ret = ERANGE;
> +		else
> +			ret = put_user(value, p);
> +		break;
> +	case WDIOC_SETPRETIMEOUT:
> +		if (get_user(value, p)) {
> +			ret = -EFAULT;
> +			break;
> +		}
> +		kempld_wdt_update_timeouts(wdt);
> +		wdt->pretimeout = value;
> +		ret = kempld_wdt_settimeout(wdt);
> +		kempld_wdt_keepalive(wdt);
> +		break;
> +	case WDIOC_GETPRETIMEOUT:
> +		value = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> +		if (value)
> +			value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> +		if (value < 0)
> +			ret = ERANGE;
> +		else
> +			ret = put_user(value, p);
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +	}
> +
> +	return ret;
> +}
> +
> +static int kempld_wdt_release(struct inode *inode, struct file *file)
> +{
> +	struct kempld_watchdog_data *wdt = kempld_wdt;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (wdt->expect_close)
> +		kempld_wdt_stop(wdt);
> +	else {
> +		dev_warn(wdt->pld->dev,
> +			 "Unexpected close, not stopping watchdog!\n");
> +		kempld_wdt_keepalive(wdt);
> +	}
> +
> +	kempld_wdt->expect_close = 0;
> +
> +	clear_bit(0, &wdt->is_open);
> +
> +	return 0;
> +}
> +
> +static int kempld_wdt_open(struct inode *inode, struct file *file)
> +{
> +	int ret;
> +	struct kempld_watchdog_data *wdt = kempld_wdt;
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 status;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (test_and_set_bit(0, &wdt->is_open))
> +		return -EBUSY;
> +
> +	if (nowayout)
> +		__module_get(THIS_MODULE);
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +	kempld_release_mutex(pld);
> +
> +	/* kick the watchdog if it is already enabled, otherwise start it */
> +	if (status & KEMPLD_WDT_CFG_ENABLE) {
> +		kempld_wdt_keepalive(wdt);
> +	} else {
> +		ret = kempld_wdt_settimeout(wdt);
> +		if (ret)
> +			goto err_enable_wdt;
> +		ret = kempld_wdt_start(wdt);
> +		if (ret)
> +			goto err_enable_wdt;
> +	}
> +
> +	return nonseekable_open(inode, file);
> +
> +err_enable_wdt:
> +	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
> +	wdt->expect_close = 1;
> +	kempld_wdt_release(inode, file);
> +
> +	return ret;
> +}
> +
> +static void kempld_wdt_release_stages(struct kempld_watchdog_data *wdt)
> +{
> +	int stage;
> +
> +	wdt->timeout_stage = NULL;
> +	wdt->pretimeout_stage = NULL;
> +
> +	for (stage = 0; stage < KEMPLD_WDT_MAX_STAGES; stage++) {
> +		kfree(wdt->stage[stage]);
> +		wdt->stage[stage] = NULL;
> +	}
> +}
> +
> +static int kempld_wdt_probe_stages(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int i, ret;
> +	u32 timeout_mask;
> +	struct kempld_watchdog_stage *stage;
> +
> +	wdt->stages = 0;
> +	wdt->timeout_stage = NULL;
> +	wdt->pretimeout_stage = NULL;
> +
> +	for (i = 0; i < KEMPLD_WDT_MAX_STAGES; i++) {
> +		int j;
> +		u8 index, data, data_orig;
> +
> +		index = KEMPLD_WDT_STAGE_TIMEOUT(i);
> +		timeout_mask = ~0;
> +
> +		kempld_get_mutex_set_index(pld, index);
> +
> +		/* Probe each byte individually according to new spec revision.
> +		 * Register content is restored afterwards. */
> +		for (j = 0; j < 4; j++) {
> +			data_orig = kempld_read8(pld, index);
> +			kempld_write8(pld, index, 0x00);
> +			data = kempld_read8(pld, index);
> +			kempld_write8(pld, index, data_orig);
> +			*(((u8 *)&timeout_mask)+j) &= data;
> +			if (data != 0x0)
> +				break;
> +			index++;
> +		}
> +
> +		kempld_release_mutex(pld);
> +
> +		if ((timeout_mask & 0xff) != 0xff) {
> +			stage = kzalloc(sizeof(struct kempld_watchdog_stage),
> +					GFP_KERNEL);
> +			if (stage == NULL) {
> +				ret = -ENOMEM;
> +				goto err_alloc_stages;
> +			}
> +			stage->num = i;
> +			stage->timeout_mask = ~timeout_mask;
> +			wdt->stage[i] = stage;
> +			wdt->stages++;
> +
> +			/* assign available stages to timeout and pretimeout */
> +			if (wdt->timeout_stage == NULL) {
> +				wdt->timeout_stage = stage;
> +			} else if ((wdt->pretimeout_stage == NULL) &&
> +				(pld->feature_mask & KEMPLD_FEATURE_BIT_NMI)) {
> +				wdt->pretimeout_stage = wdt->timeout_stage;
> +				wdt->timeout_stage = stage;
> +			}
> +		} else
> +			wdt->stage[i] = NULL;
> +	}
> +
> +	return 0;
> +
> +err_alloc_stages:
> +	kempld_wdt_release_stages(wdt);
> +
> +	return ret;
> +}
> +
> +static const struct file_operations kempld_wdt_fops = {
> +	.owner		= THIS_MODULE,
> +	.llseek		= no_llseek,
> +	.write		= kempld_wdt_write,
> +	.unlocked_ioctl	= kempld_wdt_ioctl,
> +	.open		= kempld_wdt_open,
> +	.release	= kempld_wdt_release,
> +};
> +
> +static struct miscdevice kempld_wdt_miscdev = {
> +	.minor	= WATCHDOG_MINOR,
> +	.name	= "watchdog",
> +	.fops	= &kempld_wdt_fops,
> +};
> +
> +static int kempld_wdt_probe(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt;
> +	struct kempld_device_data *pld;
> +	u8 status;
> +	int ret;
> +
> +	if (kempld_wdt != NULL) {
> +		dev_err(&pdev->dev,
> +			"unable to support more than one watchdog devices\n");
> +		return -EMFILE;
> +	}
> +
> +	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
> +	if (wdt == NULL) {
> +		dev_err(&pdev->dev, "unable to get memory for device data\n");
> +		ret = -ENOMEM;
> +		goto err_alloc_dev_data;
> +	}
> +
> +	pld = dev_get_drvdata(pdev->dev.parent);
> +	wdt->pld = pld;
> +
> +	platform_set_drvdata(pdev, wdt);
> +
> +	strncpy(wdt->ident.identity, "KEMPLD Watchdog",
> +		sizeof(wdt->ident.identity));
> +
> +	/* watchdog firmware version is identical to the CPLD version */
> +	wdt->ident.firmware_version = (pld->info.major<<24)
> +		| (pld->info.minor<<16) | pld->info.buildnr;
> +
> +	/* probe how many usable stages we have */
> +	ret = kempld_wdt_probe_stages(wdt);
> +	if (ret)
> +		goto err_probe_stages;
> +
> +	/* get initial watchdog status */
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +	kempld_release_mutex(wdt->pld);
> +
> +	/* check if the watchdog is already locked and enable the nowayout
> +	 * option in that case */
> +	if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
> +			KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
> +		if (!nowayout)
> +			dev_warn(wdt->pld->dev,
> +				 "Forcing nowayout - watchdog lock enabled!\n");
> +		nowayout = 1;
> +	}
> +
> +	/* set default values for the case we start the watchdog or change
> +	 * the configuration */
> +	wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
> +	wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
> +
> +	/* check if watchdog is enabled */
> +	if (status & KEMPLD_WDT_CFG_ENABLE) {
> +		/* Get current watchdog settings */
> +		kempld_wdt_update_timeouts(wdt);
> +
> +		dev_info(wdt->pld->dev, "Watchdog is already enabled:\n"
> +			 "%d s timeout and %d s pretimeout!\n",
> +			 wdt->timeout, wdt->pretimeout);
> +	}
> +
> +	/* update the timeout settings if requested by module parameters */
> +	if (timeout > 0)
> +		wdt->timeout = timeout;
> +	if (pretimeout >= 0)
> +		wdt->pretimeout = pretimeout;
> +
> +	dev_info(wdt->pld->dev, "watchdog will be set on (re)start\n"
> +		 "new settings: %d s timeout and %d s pretimeout\n",
> +		 wdt->timeout, wdt->pretimeout);
> +
> +	wdt->ident.options = WDIOF_KEEPALIVEPING;
> +	if ((wdt->timeout_stage) && !(status & KEMPLD_WDT_CFG_GLOBAL_LOCK))
> +		wdt->ident.options |= WDIOF_SETTIMEOUT;
> +	if (wdt->pretimeout_stage)
> +		wdt->ident.options |= WDIOF_PRETIMEOUT;
> +	if (!nowayout)
> +		wdt->ident.options |= WDIOF_MAGICCLOSE;
> +
> +	kempld_wdt = wdt;
> +
> +	ret = misc_register(&kempld_wdt_miscdev);
> +	if (ret)
> +		goto err_misc_register;
> +
> +	dev_info(wdt->pld->dev,
> +		 "%d stage watchdog initialized, pretimeout %ssupported\n",
> +		 wdt->stages, wdt->pretimeout_stage ? "" : "not ");
> +
> +	return 0;
> +
> +err_probe_stages:
> +err_misc_register:
> +	kfree(kempld_wdt);
> +	kempld_wdt = NULL;
> +err_alloc_dev_data:
> +	return ret;
> +}
> +
> +static void kempld_wdt_shutdown(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> +
> +	BUG_ON(wdt != kempld_wdt);
> +
> +	/* stop or at least keepalive the watchdog before we leave */
> +	if (wdt != NULL) {
> +		if (!nowayout)
> +			kempld_wdt_stop(wdt);
> +		else
> +			kempld_wdt_keepalive(wdt);
> +	}
> +}
> +
> +static int kempld_wdt_remove(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> +
> +	BUG_ON(wdt != kempld_wdt);
> +
> +	/* stop or at least keepalive the watchdog before we leave */
> +	kempld_wdt_shutdown(pdev);
> +
> +	misc_deregister(&kempld_wdt_miscdev);
> +
> +	kempld_wdt_release_stages(wdt);
> +
> +	kfree(wdt);
> +	kempld_wdt = NULL;
> +	platform_set_drvdata(pdev, NULL);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int wdt_pm_status_store;
> +
> +/* Disable watchdog if it is active during suspend */
> +static int kempld_wdt_suspend(struct platform_device *pdev,
> +				pm_message_t message)
> +{
> +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> +	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +	wdt_pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
> +	kempld_release_mutex(pld);
> +
> +	kempld_wdt_update_timeouts(wdt);
> +
> +	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE)
> +		kempld_wdt_shutdown(pdev);
> +
> +	return 0;
> +}
> +
> +/* Enable watchdog and configure it if necessary */
> +static int kempld_wdt_resume(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> +	int ret;
> +
> +	/* if watchdog was stopped before suspend be sure it gets disabled
> +	 * again, for the case BIOS has enabled it during resume */
> +	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE) {
> +		ret = kempld_wdt_settimeout(wdt);
> +		if (ret)
> +			goto err_enable_wdt;
> +		ret = kempld_wdt_start(wdt);
> +		if (ret)
> +			goto err_enable_wdt;
> +
> +		dev_info(wdt->pld->dev, "Resuming watchdog operation:\n"
> +			 "%d s timeout and %d s pretimeout\n", wdt->timeout,
> +			 wdt->pretimeout);
> +	} else
> +		kempld_wdt_shutdown(pdev);
> +
> +	return 0;
> +
> +err_enable_wdt:
> +	dev_err(wdt->pld->dev,
> +		"Failed to reenable the watchdog timer after resume!\n");
> +
> +	return ret;
> +}
> +#else
> +#define kempld_wdt_suspend	NULL
> +#define kempld_wdt_resume	NULL
> +#endif
> +
> +static struct platform_driver kempld_wdt_driver = {
> +	.driver = {
> +		.name = "kempld-wdt",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe = kempld_wdt_probe,
> +	.remove = kempld_wdt_remove,
> +	.shutdown = kempld_wdt_shutdown,
> +	.suspend = kempld_wdt_suspend,
> +	.resume = kempld_wdt_resume,
> +};
> +
> +static int __init kempld_wdt_init(void)
> +{
> +	return platform_driver_register(&kempld_wdt_driver);
> +}
> +
> +static void __exit kempld_wdt_exit(void)
> +{
> +	platform_driver_unregister(&kempld_wdt_driver);
> +}
> +
> +module_init(kempld_wdt_init);
> +module_exit(kempld_wdt_exit);
> +
> +MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
> +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
> diff --git a/drivers/watchdog/kempld_wdt.h b/drivers/watchdog/kempld_wdt.h
> new file mode 100644
> index 0000000..80f68f6
> --- /dev/null
> +++ b/drivers/watchdog/kempld_wdt.h
> @@ -0,0 +1,75 @@
> +/*
> + *  kempld_wdt.h - Kontron PLD watchdog driver definitions
> + *
> + *  Copyright (c) 2010-2012 Kontron Europe GmbH
> + *  Author: Michael Brunner <michael.brunner@kontron.com>
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License 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
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; see the file COPYING.  If not, write to
> + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#ifndef _KEMPLD_WDT_H_
> +#define _KEMPLD_WDT_H_
> +
> +/* watchdog register definitions */
> +#define KEMPLD_WDT_KICK			0x16
> +#define KEMPLD_WDT_CFG			0x17
> +#define		KEMPLD_WDT_CFG_STAGE_TIMEOUT_OCCURED(x)	(1<<x)
> +#define		KEMPLD_WDT_CFG_ENABLE_LOCK		0x8
> +#define		KEMPLD_WDT_CFG_ENABLE			0x10
> +#define		KEMPLD_WDT_CFG_AUTO_RELOAD		0x40
> +#define		KEMPLD_WDT_CFG_GLOBAL_LOCK		0x80
> +#define KEMPLD_WDT_STAGE_CFG(x)		(0x18+x)
> +#define		KEMPLD_WDT_STAGE_CFG_ACTION_MASK	0x7
> +#define		KEMPLD_WDT_STAGE_CFG_GET_ACTION(x)	(x & 0x7)
> +#define		KEMPLD_WDT_STAGE_CFG_ASSERT		(1<<3)
> +#define		KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK	0x30
> +#define		KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(x)	((x & 0x30)>>4)
> +#define		KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(x)	((x & 0x30)<<4)
> +#define KEMPLD_WDT_STAGE_TIMEOUT(x)	(0x1b+x*4)
> +#define KEMPLD_WDT_MAX_STAGES		3
> +
> +#define	KEMPLD_WDT_ACTION_NONE		0x0
> +#define	KEMPLD_WDT_ACTION_RESET		0x1
> +#define	KEMPLD_WDT_ACTION_NMI		0x2
> +#define	KEMPLD_WDT_ACTION_SMI		0x3
> +#define	KEMPLD_WDT_ACTION_SCI		0x4
> +#define	KEMPLD_WDT_ACTION_DELAY		0x5
> +
> +#define	KEMPLD_WDT_PRESCALER_21BIT	0x0
> +#define	KEMPLD_WDT_PRESCALER_17BIT	0x1
> +#define	KEMPLD_WDT_PRESCALER_12BIT	0x2
> +
> +const int kempld_prescaler_bits[] = { 21, 17, 12 };
> +#define KEMPLD_PRESCALER(x)	(0xffffffff>>(32-kempld_prescaler_bits[x]))
> +
> +
> +struct kempld_watchdog_stage {
> +	int	num;
> +	u32	timeout_mask;
> +};
> +
> +struct kempld_watchdog_data {
> +	int				timeout;
> +	int				pretimeout;
> +	unsigned long			is_open;
> +	unsigned long			expect_close;
> +	int				stages;
> +	struct kempld_watchdog_stage	*timeout_stage;
> +	struct kempld_watchdog_stage	*pretimeout_stage;
> +	struct kempld_device_data	*pld;
> +	struct watchdog_info		ident;
> +	struct kempld_watchdog_stage	*stage[KEMPLD_WDT_MAX_STAGES];
> +};
> +
> +#endif /* _KEMPLD_WDT_H_ */
> -- 
> 1.7.9.5
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 4/4] watchdog: Kontron PLD watchdog timer
  2013-04-10 16:47   ` Guenter Roeck
@ 2013-04-10 16:57     ` Kevin Strasser
  2013-05-26 14:38       ` Wim Van Sebroeck
  0 siblings, 1 reply; 48+ messages in thread
From: Kevin Strasser @ 2013-04-10 16:57 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: linux-kernel, Michael Brunner, Samuel Ortiz, Wolfram Sang,
	Ben Dooks, linux-i2c, Grant Likely, Linus Walleij,
	Wim Van Sebroeck, linux-watchdog, Darren Hart, Michael Brunner,
	Greg Kroah-Hartman

On Wed, Apr 10, 2013 at 09:47:17AM -0700, Guenter Roeck wrote:
> On Mon, Apr 08, 2013 at 10:15:21AM -0700, Kevin Strasser wrote:
> > From: Michael Brunner <michael.brunner@kontron.com>
> > 
> > Add watchdog timer support for the on-board PLD found on some Kontron
> > embedded modules.
> > 
> > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> 
> Personally I would prefer two separate patches for the two drivers,
> and to have the drivers converted to the watchdog infrastructure.
> Wim's call, of course.
> 
Thanks for the feedback. I'm happy to do both if Wim thinks it's
necessary.

-Kevin

> Thanks,
> Guenter
> 
> > ---
> >  drivers/watchdog/Kconfig           |   20 +
> >  drivers/watchdog/Makefile          |    2 +
> >  drivers/watchdog/kempld_now1_wdt.c |  602 +++++++++++++++++++++++++++
> >  drivers/watchdog/kempld_wdt.c      |  796 ++++++++++++++++++++++++++++++++++++
> >  drivers/watchdog/kempld_wdt.h      |   75 ++++
> >  5 files changed, 1495 insertions(+)
> >  create mode 100644 drivers/watchdog/kempld_now1_wdt.c
> >  create mode 100644 drivers/watchdog/kempld_wdt.c
> >  create mode 100644 drivers/watchdog/kempld_wdt.h
> > 
> > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> > index 9fcc70c..9ac71ca 100644
> > --- a/drivers/watchdog/Kconfig
> > +++ b/drivers/watchdog/Kconfig
> > @@ -687,6 +687,26 @@ config HP_WATCHDOG
> >  	  To compile this driver as a module, choose M here: the module will be
> >  	  called hpwdt.
> >  
> > +config KEMPLD_WDT
> > +	tristate "Kontron COM watchdog"
> > +	depends on MFD_KEMPLD
> > +	help
> > +	  Support for the PLD watchdog on some Kontron ETX and COMexpress
> > +	  (ETXexpress) modules
> > +
> > +	  This driver can also be built as a module. If so, the module will be
> > +	  called kempld_wdt.
> > +
> > +config KEMPLD_NOW1_WDT
> > +	tristate "Kontron COMe-mSP1 (nanoETXexpress-SP) watchdog"
> > +	depends on MFD_KEMPLD
> > +	help
> > +	  Support for the PLD watchdog on the Kontron COMe-mSP1
> > +	  (nanoETXexpress-SP) module.
> > +
> > +	  This driver can also be built as a module. If so, the module will
> > +	  be called kempld_now1_wdt.
> > +
> >  config HPWDT_NMI_DECODING
> >  	bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer"
> >  	depends on HP_WATCHDOG
> > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> > index a300b94..a029930 100644
> > --- a/drivers/watchdog/Makefile
> > +++ b/drivers/watchdog/Makefile
> > @@ -90,6 +90,8 @@ endif
> >  obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
> >  obj-$(CONFIG_IT87_WDT) += it87_wdt.o
> >  obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
> > +obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
> > +obj-$(COnFIG_KEMPLD_NOW1_WDT) += kempld_now1_wdt.o
> >  obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
> >  obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
> >  obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
> > diff --git a/drivers/watchdog/kempld_now1_wdt.c b/drivers/watchdog/kempld_now1_wdt.c
> > new file mode 100644
> > index 0000000..19b7272
> > --- /dev/null
> > +++ b/drivers/watchdog/kempld_now1_wdt.c
> > @@ -0,0 +1,602 @@
> > +/*
> > + *  kempld_now1_wdt.c - Kontron PLD watchdog driver for COMe-mSP1
> > + *
> > + *  Copyright (c) 2011-2012 Kontron Europe GmbH
> > + *  Author: Michael Brunner <michael.brunner@kontron.com>
> > + *
> > + *  Note: The watchdog of the COMe-mSP1 (nanoETXexpress-SP) has one stage and
> > + *        only supports predefined watchdog timeout values.
> > + *
> > + *        The supported timeouts are:
> > + *              1 s, 5 s, 10 s, 30 s, 1 min, 5 min, 10 min, 15 min
> > + *
> > + *  This program is free software; you can redistribute it and/or modify
> > + *  it under the terms of the GNU General Public License 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
> > + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + *  GNU General Public License for more details.
> > + *
> > + *  You should have received a copy of the GNU General Public License
> > + *  along with this program; see the file COPYING.  If not, write to
> > + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/moduleparam.h>
> > +#include <linux/types.h>
> > +#include <linux/miscdevice.h>
> > +#include <linux/watchdog.h>
> > +#include <linux/fs.h>
> > +#include <linux/ioport.h>
> > +#include <linux/init.h>
> > +#include <linux/uaccess.h>
> > +#include <linux/slab.h>
> > +#include <linux/delay.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/mfd/kempld.h>
> > +
> > +#include "kempld_wdt.h"
> > +
> > +#define WATCHDOG_TIMEOUT 30
> > +static int timeout = WATCHDOG_TIMEOUT;
> > +module_param(timeout, int, 0);
> > +MODULE_PARM_DESC(timeout,
> > +		 "Watchdog timeout in seconds. (1, 5, 10, 30, 60, 300, 600, "
> > +		 "900, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
> > +
> > +static bool nowayout = WATCHDOG_NOWAYOUT;
> > +module_param(nowayout, bool, 0);
> > +MODULE_PARM_DESC(nowayout,
> > +		 "Watchdog cannot be stopped once started (default="
> > +		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> > +
> > +/* nanoETXexpress-SP watchdog register definitions */
> > +#define KEMPLD_WDT_NOW1					0xA2
> > +#define		KEMPLD_WDT_NOW1_KICK_MASK		0x80
> > +#define		KEMPLD_WDT_NOW1_ENABLE_MASK		0x40
> > +#define		KEMPLD_WDT_NOW1_TIMEOUT_MASK		0x1f
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_1SEC	0x00
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_5SEC	0x01
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_10SEC	0x02
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_30SEC	0x03
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_1MIN	0x10
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_5MIN	0x11
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_10MIN	0x12
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_15MIN	0x13
> > +
> > +/* delay in us necessary due to clock domain sync */
> > +#define		KEMPLD_WDT_NOW1_SYNC_DELAY		31
> > +
> > +static struct kempld_watchdog_data *kempld_now1_wdt;
> > +
> > +static int kempld_now1_wdt_read_supported;
> > +static int kempld_now1_wdt_reg_cache;
> > +
> > +static int kempld_now1_wdt_start(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int wdt_reg;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +	if (kempld_now1_wdt_read_supported)
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	else
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +	wdt_reg |= KEMPLD_WDT_NOW1_ENABLE_MASK;
> > +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +	kempld_release_mutex(pld);
> > +
> > +	if (kempld_now1_wdt_read_supported) {
> > +		/* read out the register again to check if everything worked */
> > +		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +		kempld_release_mutex(pld);
> > +
> > +		/* check if the watchdog was disabled */
> > +		if (!(wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK))
> > +			return -EACCES;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_now1_wdt_stop(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int wdt_reg;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +	if (kempld_now1_wdt_read_supported) {
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	} else
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +	wdt_reg &= ~KEMPLD_WDT_NOW1_ENABLE_MASK;
> > +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +	kempld_release_mutex(pld);
> > +
> > +	if (kempld_now1_wdt_read_supported) {
> > +		/* read out the register again to check if everything worked */
> > +		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +		kempld_release_mutex(pld);
> > +
> > +		/* check if the watchdog was disabled */
> > +		if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
> > +			return -EACCES;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_now1_wdt_keepalive(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int wdt_reg;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +
> > +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +
> > +	if (kempld_now1_wdt_read_supported) {
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	} else {
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +		/* write the state again to be sure the trigger register has
> > +		 * the right level */
> > +		kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> > +	}
> > +
> > +	if (wdt_reg & KEMPLD_WDT_NOW1_KICK_MASK)
> > +		wdt_reg &= ~KEMPLD_WDT_NOW1_KICK_MASK;
> > +	else
> > +		wdt_reg |= KEMPLD_WDT_NOW1_KICK_MASK;
> > +
> > +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +
> > +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> > +
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	return 0;
> > +}
> > +
> > +
> > +static int kempld_now1_wdt_settimeout(struct kempld_watchdog_data *wdt,
> > +					int check_only)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int wdt_reg;
> > +	int ret = 0;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +
> > +	if (kempld_now1_wdt_read_supported) {
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	} else
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +
> > +	wdt_reg &= ~KEMPLD_WDT_NOW1_TIMEOUT_MASK;
> > +
> > +	switch (wdt->timeout) {
> > +	case 1:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1SEC;
> > +		break;
> > +	case 5:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5SEC;
> > +		break;
> > +	case 10:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10SEC;
> > +		break;
> > +	case 30:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_30SEC;
> > +		break;
> > +	case 60:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1MIN;
> > +		break;
> > +	case 300:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5MIN;
> > +		break;
> > +	case 600:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10MIN;
> > +		break;
> > +	case 900:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_15MIN;
> > +		break;
> > +	default:
> > +		ret = -EINVAL;
> > +		dev_err(wdt->pld->dev,
> > +			"Invalid timeout value given!\n");
> > +	}
> > +
> > +	if (!check_only) {
> > +		if (ret == 0) {
> > +			udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +
> > +			kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> > +		}
> > +	}
> > +
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	return ret;
> > +}
> > +
> > +static int kempld_now1_wdt_gettimeout(struct kempld_watchdog_data *wdt,
> > +				 struct kempld_watchdog_stage *stage)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int wdt_reg;
> > +	int timeout;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +
> > +	if (kempld_now1_wdt_read_supported) {
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +		kempld_now1_wdt_reg_cache = wdt_reg;
> > +	} else
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	switch (wdt_reg&KEMPLD_WDT_NOW1_TIMEOUT_MASK) {
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_1SEC:
> > +		timeout = 1;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_5SEC:
> > +		timeout = 5;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_10SEC:
> > +		timeout = 10;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_30SEC:
> > +		timeout = 30;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_1MIN:
> > +		timeout = 60;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_5MIN:
> > +		timeout = 300;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_10MIN:
> > +		timeout = 600;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_15MIN:
> > +		timeout = 900;
> > +		break;
> > +	default:
> > +		timeout = -ERANGE;
> > +	}
> > +
> > +	return timeout;
> > +}
> > +
> > +static ssize_t kempld_now1_wdt_write(struct file *file, const char __user
> > +					*data, size_t count, loff_t *ppos)
> > +{
> > +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (count) {
> > +		kempld_now1_wdt_keepalive(wdt);
> > +
> > +		if (!nowayout) {
> > +			size_t i;
> > +
> > +			wdt->expect_close = 0;
> > +
> > +			for (i = 0; i < count; i++) {
> > +				char c;
> > +				if (get_user(c, data+i))
> > +					return -EFAULT;
> > +				if (c == 'V')
> > +					wdt->expect_close = 42;
> > +			}
> > +		}
> > +	}
> > +
> > +	return count;
> > +}
> > +
> > +static long kempld_now1_wdt_ioctl(struct file *file, unsigned int cmd,
> > +				unsigned long arg)
> > +{
> > +	void __user *argp = (void __user *)arg;
> > +	int __user *p = argp;
> > +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> > +	int options;
> > +	int value;
> > +	int ret = 0;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	switch (cmd) {
> > +	case WDIOC_GETSUPPORT:
> > +		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
> > +			ret = -EFAULT;
> > +		break;
> > +	case WDIOC_GETSTATUS:
> > +	case WDIOC_GETBOOTSTATUS:
> > +		ret = put_user(0, p);
> > +		break;
> > +	case WDIOC_SETOPTIONS:
> > +		if (get_user(options, p)) {
> > +			ret = -EFAULT;
> > +			break;
> > +		}
> > +		if (options & WDIOS_DISABLECARD)
> > +			ret = kempld_now1_wdt_stop(wdt);
> > +		if (options & WDIOS_ENABLECARD) {
> > +			ret = kempld_now1_wdt_start(wdt);
> > +			kempld_now1_wdt_keepalive(wdt);
> > +		}
> > +		break;
> > +	case WDIOC_KEEPALIVE:
> > +		kempld_now1_wdt_keepalive(wdt);
> > +		break;
> > +	case WDIOC_SETTIMEOUT:
> > +		if (get_user(value, p)) {
> > +			ret = -EFAULT;
> > +			break;
> > +		}
> > +		wdt->timeout = value;
> > +		ret = kempld_now1_wdt_settimeout(wdt, 0);
> > +		kempld_now1_wdt_keepalive(wdt);
> > +		break;
> > +	case WDIOC_GETTIMEOUT:
> > +		value = kempld_now1_wdt_gettimeout(wdt, wdt->timeout_stage);
> > +		if (timeout < 0)
> > +			ret = ERANGE;
> > +		else
> > +			ret = put_user(timeout, p);
> > +		break;
> > +	default:
> > +		ret = -ENOTTY;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static int kempld_now1_wdt_release(struct inode *inode, struct file *file)
> > +{
> > +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (wdt->expect_close)
> > +		kempld_now1_wdt_stop(wdt);
> > +	else {
> > +		dev_warn(wdt->pld->dev,
> > +			 "Unexpected close, not stopping watchdog!\n");
> > +		kempld_now1_wdt_keepalive(wdt);
> > +	}
> > +
> > +	kempld_now1_wdt->expect_close = 0;
> > +
> > +	clear_bit(0, &wdt->is_open);
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_now1_wdt_open(struct inode *inode, struct file *file)
> > +{
> > +	int ret;
> > +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 wdt_reg;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (test_and_set_bit(0, &wdt->is_open))
> > +		return -EBUSY;
> > +
> > +	if (nowayout)
> > +		__module_get(THIS_MODULE);
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +	if (kempld_now1_wdt_read_supported) {
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	} else {
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +	}
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +	kempld_release_mutex(pld);
> > +
> > +	/* kick the watchdog if it is already enabled, otherwise start it */
> > +	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK) {
> > +		kempld_now1_wdt_keepalive(wdt);
> > +	} else {
> > +		ret = kempld_now1_wdt_settimeout(wdt, 0);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +		ret = kempld_now1_wdt_start(wdt);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +	}
> > +
> > +	return nonseekable_open(inode, file);
> > +
> > +err_enable_wdt:
> > +	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
> > +	wdt->expect_close = 1;
> > +	kempld_now1_wdt_release(inode, file);
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct file_operations kempld_now1_wdt_fops = {
> > +	.owner		= THIS_MODULE,
> > +	.llseek		= no_llseek,
> > +	.write		= kempld_now1_wdt_write,
> > +	.unlocked_ioctl	= kempld_now1_wdt_ioctl,
> > +	.open		= kempld_now1_wdt_open,
> > +	.release	= kempld_now1_wdt_release,
> > +};
> > +
> > +static struct miscdevice kempld_now1_wdt_miscdev = {
> > +	.minor	= WATCHDOG_MINOR,
> > +	.name	= "watchdog",
> > +	.fops	= &kempld_now1_wdt_fops,
> > +};
> > +
> > +static int kempld_now1_wdt_probe(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt;
> > +	struct kempld_device_data *pld;
> > +	u8 wdt_reg;
> > +	int ret;
> > +
> > +	if (kempld_now1_wdt != NULL) {
> > +		dev_err(&pdev->dev,
> > +			"unable to support more than one watchdog devices\n");
> > +		return -EMFILE;
> > +	}
> > +
> > +	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
> > +	if (wdt == NULL) {
> > +		dev_err(&pdev->dev, "unable to get memory for device data\n");
> > +		ret = -ENOMEM;
> > +		goto err_alloc_dev_data;
> > +	}
> > +
> > +	wdt->timeout_stage = kzalloc(sizeof(struct kempld_watchdog_stage),
> > +					GFP_KERNEL);
> > +	if (wdt->timeout_stage == NULL) {
> > +		dev_err(&pdev->dev,
> > +			"unable to get memory for watchdog stage\n");
> > +		ret = -ENOMEM;
> > +		goto err_alloc_dev_data;
> > +	}
> > +
> > +	wdt->stages = 1;
> > +	wdt->stage[0] = wdt->timeout_stage;
> > +
> > +	pld = dev_get_drvdata(pdev->dev.parent);
> > +	wdt->pld = pld;
> > +
> > +	platform_set_drvdata(pdev, wdt);
> > +
> > +	strncpy(wdt->ident.identity, "nanoETXexpress-SP Watchdog",
> > +		sizeof(wdt->ident.identity));
> > +
> > +	/* set default values for the case we start the watchdog or change
> > +	 * the configuration */
> > +	wdt->timeout = timeout;
> > +
> > +	/* use settimeout to check if the timeout parameter is valid */
> > +	ret = kempld_now1_wdt_settimeout(wdt, 1);
> > +	if (ret)
> > +		goto err_check_timeout;
> > +	wdt->ident.firmware_version = (pld->info.major<<8) + pld->info.minor;
> > +	if (pld->info.major > 67)
> > +		kempld_now1_wdt_read_supported = 1;
> > +	else
> > +		dev_info(wdt->pld->dev,
> > +			 "Watchdog revision does not support read - "
> > +			 "unable to get watchdog state!\n");
> > +
> > +	/* get initial watchdog status */
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +	if (kempld_now1_wdt_read_supported) {
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	} else {
> > +		wdt_reg = 0x0;
> > +	}
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +	kempld_release_mutex(wdt->pld);
> > +
> > +	/* check if watchdog is enabled */
> > +	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
> > +		dev_info(wdt->pld->dev, "Watchdog is already enabled!\n");
> > +
> > +	wdt->ident.options = WDIOF_KEEPALIVEPING;
> > +	wdt->ident.options |= WDIOF_SETTIMEOUT;
> > +	if (!nowayout)
> > +		wdt->ident.options |= WDIOF_MAGICCLOSE;
> > +
> > +	kempld_now1_wdt = wdt;
> > +
> > +	ret = misc_register(&kempld_now1_wdt_miscdev);
> > +	if (ret)
> > +		goto err_misc_register;
> > +
> > +	dev_info(wdt->pld->dev, "watchdog initialized\n");
> > +
> > +	return 0;
> > +
> > +err_misc_register:
> > +	kfree(kempld_now1_wdt);
> > +	kempld_now1_wdt = NULL;
> > +err_check_timeout:
> > +err_alloc_dev_data:
> > +	return ret;
> > +}
> > +
> > +static int kempld_now1_wdt_remove(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> > +
> > +	BUG_ON(wdt != kempld_now1_wdt);
> > +
> > +	/* stop or at least keepalive the watchdog before we leave */
> > +	if (wdt != NULL) {
> > +		if (!nowayout)
> > +			kempld_now1_wdt_stop(wdt);
> > +		else
> > +			kempld_now1_wdt_keepalive(wdt);
> > +	}
> > +
> > +	misc_deregister(&kempld_now1_wdt_miscdev);
> > +
> > +	kfree(wdt);
> > +	kempld_now1_wdt = NULL;
> > +	platform_set_drvdata(pdev, NULL);
> > +
> > +	return 0;
> > +}
> > +
> > +static struct platform_driver kempld_now1_wdt_driver = {
> > +	.driver = {
> > +		.name = "kempld_now1-wdt",
> > +		.owner = THIS_MODULE,
> > +	},
> > +	.probe = kempld_now1_wdt_probe,
> > +	.remove = kempld_now1_wdt_remove,
> > +};
> > +
> > +static int __init kempld_now1_wdt_init(void)
> > +{
> > +	return platform_driver_register(&kempld_now1_wdt_driver);
> > +}
> > +
> > +static void __exit kempld_now1_wdt_exit(void)
> > +{
> > +	platform_driver_unregister(&kempld_now1_wdt_driver);
> > +}
> > +
> > +module_init(kempld_now1_wdt_init);
> > +module_exit(kempld_now1_wdt_exit);
> > +
> > +MODULE_DESCRIPTION("KEM PLD nanoETXexpress-SP Watchdog Driver");
> > +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
> > diff --git a/drivers/watchdog/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c
> > new file mode 100644
> > index 0000000..bc150e5
> > --- /dev/null
> > +++ b/drivers/watchdog/kempld_wdt.c
> > @@ -0,0 +1,796 @@
> > +/*
> > + *  kempld_wdt.c - Kontron PLD watchdog driver
> > + *
> > + *  Copyright (c) 2010-2012 Kontron Europe GmbH
> > + *  Author: Michael Brunner <michael.brunner@kontron.com>
> > + *
> > + *  Note: From the PLD watchdog point of view timeout and pretimeout are
> > + *        defined differently than in the kernel.
> > + *        First the pretimeout stage runs out before the timeout stage gets
> > + *        active. This has to be kept in mind.
> > + *
> > + *  Kernel/API:                     P-----| pretimeout
> > + *                |-----------------------T timeout
> > + *  Watchdog:     |-----------------P       pretimeout_stage
> > + *                                  |-----T timeout_stage
> > + *
> > + *  This program is free software; you can redistribute it and/or modify
> > + *  it under the terms of the GNU General Public License 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
> > + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + *  GNU General Public License for more details.
> > + *
> > + *  You should have received a copy of the GNU General Public License
> > + *  along with this program; see the file COPYING.  If not, write to
> > + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/moduleparam.h>
> > +#include <linux/types.h>
> > +#include <linux/miscdevice.h>
> > +#include <linux/watchdog.h>
> > +#include <linux/fs.h>
> > +#include <linux/ioport.h>
> > +#include <linux/init.h>
> > +#include <linux/uaccess.h>
> > +#include <linux/slab.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/mfd/kempld.h>
> > +
> > +#include "kempld_wdt.h"
> > +
> > +#define WATCHDOG_DEFAULT_TIMEOUT 20
> > +#define WATCHDOG_DEFAULT_PRETIMEOUT 0
> > +static int timeout = -1;
> > +static int pretimeout = -1;
> > +/* The maximum timeout values have to be probed */
> > +module_param(timeout, int, 0);
> > +MODULE_PARM_DESC(timeout,
> > +		 "Watchdog timeout in seconds. (>0, default="
> > +		__MODULE_STRING(WATCHDOG_DEFAULT_TIMEOUT) ")");
> > +module_param(pretimeout, int, 0);
> > +MODULE_PARM_DESC(pretimeout,
> > +		 "Watchdog pretimeout in seconds. (>=0, default="
> > +		__MODULE_STRING(WATCHDOG_DEFAULT_PRETIMEOUT) ")");
> > +
> > +static bool nowayout = WATCHDOG_NOWAYOUT;
> > +module_param(nowayout, bool, 0);
> > +MODULE_PARM_DESC(nowayout,
> > +		 "Watchdog cannot be stopped once started (default="
> > +		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> > +
> > +static struct kempld_watchdog_data *kempld_wdt;
> > +
> > +static int kempld_wdt_start(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 status;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +	status |= KEMPLD_WDT_CFG_ENABLE;
> > +	kempld_write8(pld, KEMPLD_WDT_CFG, status);
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	/* check if the watchdog was enabled */
> > +	if (!(status & KEMPLD_WDT_CFG_ENABLE))
> > +		return -EACCES;
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_wdt_stop(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 status;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +	status &= ~KEMPLD_WDT_CFG_ENABLE;
> > +	kempld_write8(pld, KEMPLD_WDT_CFG, status);
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	/* check if the watchdog was disabled */
> > +	if (status & KEMPLD_WDT_CFG_ENABLE)
> > +		return -EACCES;
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_wdt_keepalive(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +
> > +	kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_wdt_gettimeout(struct kempld_watchdog_data *wdt,
> > +				 struct kempld_watchdog_stage *stage)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 stage_cfg;
> > +	int bits;
> > +	u64 timeout;
> > +	u32 remainder;
> > +
> > +	if (stage == NULL)
> > +		return 0;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +
> > +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +	timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num));
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	bits = KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(stage_cfg);
> > +	timeout = (timeout & stage->timeout_mask) * KEMPLD_PRESCALER(bits);
> > +	remainder = do_div(timeout, pld->pld_clock);
> > +
> > +	/* Round up the return value if necessary */
> > +	if ((timeout > 0) && (remainder >= (pld->pld_clock/2)))
> > +		timeout++;
> > +
> > +	return timeout;
> > +}
> > +
> > +static int kempld_wdt_setstageaction(struct kempld_watchdog_data *wdt,
> > +				 struct kempld_watchdog_stage *stage,
> > +				 int action)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 stage_cfg;
> > +
> > +	if (stage == NULL)
> > +		return -EINVAL;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +
> > +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ACTION_MASK;
> > +	stage_cfg |= (action & KEMPLD_WDT_STAGE_CFG_ACTION_MASK);
> > +	if (action == KEMPLD_WDT_ACTION_RESET)
> > +		stage_cfg |= KEMPLD_WDT_STAGE_CFG_ASSERT;
> > +	else
> > +		stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ASSERT;
> > +
> > +	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_wdt_setstagetimeout(struct kempld_watchdog_data *wdt,
> > +				 struct kempld_watchdog_stage *stage,
> > +				 int timeout)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 stage_cfg;
> > +	u8 prescaler;
> > +	u64 stage_timeout64;
> > +	u32 stage_timeout;
> > +	u32 remainder;
> > +
> > +	if (stage == NULL)
> > +		return -EINVAL;
> > +
> > +	prescaler = KEMPLD_WDT_PRESCALER_21BIT;
> > +
> > +	stage_timeout64 = ((u64)timeout*pld->pld_clock);
> > +	remainder = do_div(stage_timeout64, KEMPLD_PRESCALER(prescaler));
> > +	if (remainder)
> > +		stage_timeout64++;
> > +	stage_timeout = stage_timeout64 & stage->timeout_mask;
> > +
> > +	if (stage_timeout64 != (u64)stage_timeout)
> > +		return -EINVAL;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +
> > +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK;
> > +	stage_cfg |= KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(prescaler);
> > +	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
> > +	kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num),
> > +			stage_timeout);
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	return 0;
> > +}
> > +
> > +static void kempld_wdt_update_timeouts(struct kempld_watchdog_data *wdt)
> > +{
> > +	int pretimeout_stage;
> > +	int timeout_stage;
> > +
> > +	pretimeout_stage = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> > +	timeout_stage = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> > +
> > +	if (pretimeout_stage)
> > +		wdt->pretimeout = timeout_stage;
> > +	else
> > +		wdt->pretimeout = 0;
> > +
> > +	wdt->timeout = pretimeout_stage + timeout_stage;
> > +
> > +	if (wdt->pretimeout < 0) {
> > +		wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
> > +		dev_err(wdt->pld->dev, "failed to get valid pretimeout value\n"
> > +			" -> using driver default\n");
> > +	}
> > +	if (wdt->timeout < 0) {
> > +		wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
> > +		dev_err(wdt->pld->dev, "failed to get valid timeout value\n"
> > +			" -> using driver default\n");
> > +	}
> > +}
> > +
> > +static int kempld_wdt_settimeout(struct kempld_watchdog_data *wdt)
> > +{
> > +	int stage_timeout;
> > +	int stage_pretimeout;
> > +	int ret;
> > +
> > +	if ((wdt->timeout <= 0) ||
> > +		(wdt->pretimeout < 0) ||
> > +		(wdt->pretimeout > wdt->timeout)) {
> > +		ret = -EINVAL;
> > +		goto err_check_values;
> > +	}
> > +
> > +	if ((wdt->pretimeout == 0) || (wdt->pretimeout_stage == NULL)) {
> > +		if (wdt->pretimeout != 0)
> > +			dev_warn(wdt->pld->dev,
> > +				 "no pretimeout stage available\n"
> > +				 " -> only enabling reset\n");
> > +		stage_pretimeout = 0;
> > +		stage_timeout = wdt->timeout;
> > +	} else {
> > +		stage_pretimeout = wdt->timeout - wdt->pretimeout;
> > +		stage_timeout = wdt->pretimeout;
> > +	}
> > +
> > +	if (stage_pretimeout != 0) {
> > +		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
> > +						KEMPLD_WDT_ACTION_NMI);
> > +	} else if ((stage_pretimeout == 0)
> > +			&& (wdt->pretimeout_stage != NULL)) {
> > +		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
> > +						KEMPLD_WDT_ACTION_NONE);
> > +	} else
> > +		ret = 0;
> > +	if (ret)
> > +		goto err_setstage;
> > +
> > +	if (stage_pretimeout != 0) {
> > +		ret = kempld_wdt_setstagetimeout(wdt, wdt->pretimeout_stage,
> > +						 stage_pretimeout);
> > +		if (ret)
> > +			goto err_setstage;
> > +	}
> > +
> > +	ret = kempld_wdt_setstageaction(wdt, wdt->timeout_stage,
> > +					KEMPLD_WDT_ACTION_RESET);
> > +	if (ret)
> > +		goto err_setstage;
> > +
> > +	ret = kempld_wdt_setstagetimeout(wdt, wdt->timeout_stage,
> > +					 stage_timeout);
> > +	if (ret)
> > +		goto err_setstage;
> > +
> > +	return 0;
> > +err_setstage:
> > +err_check_values:
> > +	return ret;
> > +}
> > +
> > +static ssize_t kempld_wdt_write(struct file *file, const char __user *data,
> > +				size_t count, loff_t *ppos)
> > +{
> > +	struct kempld_watchdog_data *wdt = kempld_wdt;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (count) {
> > +		kempld_wdt_keepalive(wdt);
> > +
> > +		if (!nowayout) {
> > +			size_t i;
> > +
> > +			wdt->expect_close = 0;
> > +
> > +			for (i = 0; i < count; i++) {
> > +				char c;
> > +				if (get_user(c, data+i))
> > +					return -EFAULT;
> > +				if (c == 'V')
> > +					wdt->expect_close = 42;
> > +			}
> > +		}
> > +	}
> > +
> > +	return count;
> > +}
> > +
> > +static long kempld_wdt_ioctl(struct file *file, unsigned int cmd,
> > +				unsigned long arg)
> > +{
> > +	void __user *argp = (void __user *)arg;
> > +	int __user *p = argp;
> > +	struct kempld_watchdog_data *wdt = kempld_wdt;
> > +	int options;
> > +	int value;
> > +	int ret = 0;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	switch (cmd) {
> > +	case WDIOC_GETSUPPORT:
> > +		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
> > +			ret = -EFAULT;
> > +		break;
> > +	case WDIOC_GETSTATUS:
> > +	case WDIOC_GETBOOTSTATUS:
> > +		ret = put_user(0, p);
> > +		break;
> > +	case WDIOC_SETOPTIONS:
> > +		if (get_user(options, p)) {
> > +			ret = -EFAULT;
> > +			break;
> > +		}
> > +		if (options & WDIOS_DISABLECARD)
> > +			ret = kempld_wdt_stop(wdt);
> > +		if (options & WDIOS_ENABLECARD) {
> > +			ret = kempld_wdt_start(wdt);
> > +			kempld_wdt_keepalive(wdt);
> > +		}
> > +		break;
> > +	case WDIOC_KEEPALIVE:
> > +		kempld_wdt_keepalive(wdt);
> > +		break;
> > +	case WDIOC_SETTIMEOUT:
> > +		if (get_user(value, p)) {
> > +			ret = -EFAULT;
> > +			break;
> > +		}
> > +		kempld_wdt_update_timeouts(wdt);
> > +		wdt->timeout = value;
> > +		ret = kempld_wdt_settimeout(wdt);
> > +		kempld_wdt_keepalive(wdt);
> > +		break;
> > +	case WDIOC_GETTIMEOUT:
> > +		value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> > +		value += kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> > +		if (value < 0)
> > +			ret = ERANGE;
> > +		else
> > +			ret = put_user(value, p);
> > +		break;
> > +	case WDIOC_SETPRETIMEOUT:
> > +		if (get_user(value, p)) {
> > +			ret = -EFAULT;
> > +			break;
> > +		}
> > +		kempld_wdt_update_timeouts(wdt);
> > +		wdt->pretimeout = value;
> > +		ret = kempld_wdt_settimeout(wdt);
> > +		kempld_wdt_keepalive(wdt);
> > +		break;
> > +	case WDIOC_GETPRETIMEOUT:
> > +		value = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> > +		if (value)
> > +			value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> > +		if (value < 0)
> > +			ret = ERANGE;
> > +		else
> > +			ret = put_user(value, p);
> > +		break;
> > +	default:
> > +		ret = -ENOTTY;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static int kempld_wdt_release(struct inode *inode, struct file *file)
> > +{
> > +	struct kempld_watchdog_data *wdt = kempld_wdt;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (wdt->expect_close)
> > +		kempld_wdt_stop(wdt);
> > +	else {
> > +		dev_warn(wdt->pld->dev,
> > +			 "Unexpected close, not stopping watchdog!\n");
> > +		kempld_wdt_keepalive(wdt);
> > +	}
> > +
> > +	kempld_wdt->expect_close = 0;
> > +
> > +	clear_bit(0, &wdt->is_open);
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_wdt_open(struct inode *inode, struct file *file)
> > +{
> > +	int ret;
> > +	struct kempld_watchdog_data *wdt = kempld_wdt;
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 status;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (test_and_set_bit(0, &wdt->is_open))
> > +		return -EBUSY;
> > +
> > +	if (nowayout)
> > +		__module_get(THIS_MODULE);
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +	kempld_release_mutex(pld);
> > +
> > +	/* kick the watchdog if it is already enabled, otherwise start it */
> > +	if (status & KEMPLD_WDT_CFG_ENABLE) {
> > +		kempld_wdt_keepalive(wdt);
> > +	} else {
> > +		ret = kempld_wdt_settimeout(wdt);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +		ret = kempld_wdt_start(wdt);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +	}
> > +
> > +	return nonseekable_open(inode, file);
> > +
> > +err_enable_wdt:
> > +	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
> > +	wdt->expect_close = 1;
> > +	kempld_wdt_release(inode, file);
> > +
> > +	return ret;
> > +}
> > +
> > +static void kempld_wdt_release_stages(struct kempld_watchdog_data *wdt)
> > +{
> > +	int stage;
> > +
> > +	wdt->timeout_stage = NULL;
> > +	wdt->pretimeout_stage = NULL;
> > +
> > +	for (stage = 0; stage < KEMPLD_WDT_MAX_STAGES; stage++) {
> > +		kfree(wdt->stage[stage]);
> > +		wdt->stage[stage] = NULL;
> > +	}
> > +}
> > +
> > +static int kempld_wdt_probe_stages(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int i, ret;
> > +	u32 timeout_mask;
> > +	struct kempld_watchdog_stage *stage;
> > +
> > +	wdt->stages = 0;
> > +	wdt->timeout_stage = NULL;
> > +	wdt->pretimeout_stage = NULL;
> > +
> > +	for (i = 0; i < KEMPLD_WDT_MAX_STAGES; i++) {
> > +		int j;
> > +		u8 index, data, data_orig;
> > +
> > +		index = KEMPLD_WDT_STAGE_TIMEOUT(i);
> > +		timeout_mask = ~0;
> > +
> > +		kempld_get_mutex_set_index(pld, index);
> > +
> > +		/* Probe each byte individually according to new spec revision.
> > +		 * Register content is restored afterwards. */
> > +		for (j = 0; j < 4; j++) {
> > +			data_orig = kempld_read8(pld, index);
> > +			kempld_write8(pld, index, 0x00);
> > +			data = kempld_read8(pld, index);
> > +			kempld_write8(pld, index, data_orig);
> > +			*(((u8 *)&timeout_mask)+j) &= data;
> > +			if (data != 0x0)
> > +				break;
> > +			index++;
> > +		}
> > +
> > +		kempld_release_mutex(pld);
> > +
> > +		if ((timeout_mask & 0xff) != 0xff) {
> > +			stage = kzalloc(sizeof(struct kempld_watchdog_stage),
> > +					GFP_KERNEL);
> > +			if (stage == NULL) {
> > +				ret = -ENOMEM;
> > +				goto err_alloc_stages;
> > +			}
> > +			stage->num = i;
> > +			stage->timeout_mask = ~timeout_mask;
> > +			wdt->stage[i] = stage;
> > +			wdt->stages++;
> > +
> > +			/* assign available stages to timeout and pretimeout */
> > +			if (wdt->timeout_stage == NULL) {
> > +				wdt->timeout_stage = stage;
> > +			} else if ((wdt->pretimeout_stage == NULL) &&
> > +				(pld->feature_mask & KEMPLD_FEATURE_BIT_NMI)) {
> > +				wdt->pretimeout_stage = wdt->timeout_stage;
> > +				wdt->timeout_stage = stage;
> > +			}
> > +		} else
> > +			wdt->stage[i] = NULL;
> > +	}
> > +
> > +	return 0;
> > +
> > +err_alloc_stages:
> > +	kempld_wdt_release_stages(wdt);
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct file_operations kempld_wdt_fops = {
> > +	.owner		= THIS_MODULE,
> > +	.llseek		= no_llseek,
> > +	.write		= kempld_wdt_write,
> > +	.unlocked_ioctl	= kempld_wdt_ioctl,
> > +	.open		= kempld_wdt_open,
> > +	.release	= kempld_wdt_release,
> > +};
> > +
> > +static struct miscdevice kempld_wdt_miscdev = {
> > +	.minor	= WATCHDOG_MINOR,
> > +	.name	= "watchdog",
> > +	.fops	= &kempld_wdt_fops,
> > +};
> > +
> > +static int kempld_wdt_probe(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt;
> > +	struct kempld_device_data *pld;
> > +	u8 status;
> > +	int ret;
> > +
> > +	if (kempld_wdt != NULL) {
> > +		dev_err(&pdev->dev,
> > +			"unable to support more than one watchdog devices\n");
> > +		return -EMFILE;
> > +	}
> > +
> > +	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
> > +	if (wdt == NULL) {
> > +		dev_err(&pdev->dev, "unable to get memory for device data\n");
> > +		ret = -ENOMEM;
> > +		goto err_alloc_dev_data;
> > +	}
> > +
> > +	pld = dev_get_drvdata(pdev->dev.parent);
> > +	wdt->pld = pld;
> > +
> > +	platform_set_drvdata(pdev, wdt);
> > +
> > +	strncpy(wdt->ident.identity, "KEMPLD Watchdog",
> > +		sizeof(wdt->ident.identity));
> > +
> > +	/* watchdog firmware version is identical to the CPLD version */
> > +	wdt->ident.firmware_version = (pld->info.major<<24)
> > +		| (pld->info.minor<<16) | pld->info.buildnr;
> > +
> > +	/* probe how many usable stages we have */
> > +	ret = kempld_wdt_probe_stages(wdt);
> > +	if (ret)
> > +		goto err_probe_stages;
> > +
> > +	/* get initial watchdog status */
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +	kempld_release_mutex(wdt->pld);
> > +
> > +	/* check if the watchdog is already locked and enable the nowayout
> > +	 * option in that case */
> > +	if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
> > +			KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
> > +		if (!nowayout)
> > +			dev_warn(wdt->pld->dev,
> > +				 "Forcing nowayout - watchdog lock enabled!\n");
> > +		nowayout = 1;
> > +	}
> > +
> > +	/* set default values for the case we start the watchdog or change
> > +	 * the configuration */
> > +	wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
> > +	wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
> > +
> > +	/* check if watchdog is enabled */
> > +	if (status & KEMPLD_WDT_CFG_ENABLE) {
> > +		/* Get current watchdog settings */
> > +		kempld_wdt_update_timeouts(wdt);
> > +
> > +		dev_info(wdt->pld->dev, "Watchdog is already enabled:\n"
> > +			 "%d s timeout and %d s pretimeout!\n",
> > +			 wdt->timeout, wdt->pretimeout);
> > +	}
> > +
> > +	/* update the timeout settings if requested by module parameters */
> > +	if (timeout > 0)
> > +		wdt->timeout = timeout;
> > +	if (pretimeout >= 0)
> > +		wdt->pretimeout = pretimeout;
> > +
> > +	dev_info(wdt->pld->dev, "watchdog will be set on (re)start\n"
> > +		 "new settings: %d s timeout and %d s pretimeout\n",
> > +		 wdt->timeout, wdt->pretimeout);
> > +
> > +	wdt->ident.options = WDIOF_KEEPALIVEPING;
> > +	if ((wdt->timeout_stage) && !(status & KEMPLD_WDT_CFG_GLOBAL_LOCK))
> > +		wdt->ident.options |= WDIOF_SETTIMEOUT;
> > +	if (wdt->pretimeout_stage)
> > +		wdt->ident.options |= WDIOF_PRETIMEOUT;
> > +	if (!nowayout)
> > +		wdt->ident.options |= WDIOF_MAGICCLOSE;
> > +
> > +	kempld_wdt = wdt;
> > +
> > +	ret = misc_register(&kempld_wdt_miscdev);
> > +	if (ret)
> > +		goto err_misc_register;
> > +
> > +	dev_info(wdt->pld->dev,
> > +		 "%d stage watchdog initialized, pretimeout %ssupported\n",
> > +		 wdt->stages, wdt->pretimeout_stage ? "" : "not ");
> > +
> > +	return 0;
> > +
> > +err_probe_stages:
> > +err_misc_register:
> > +	kfree(kempld_wdt);
> > +	kempld_wdt = NULL;
> > +err_alloc_dev_data:
> > +	return ret;
> > +}
> > +
> > +static void kempld_wdt_shutdown(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> > +
> > +	BUG_ON(wdt != kempld_wdt);
> > +
> > +	/* stop or at least keepalive the watchdog before we leave */
> > +	if (wdt != NULL) {
> > +		if (!nowayout)
> > +			kempld_wdt_stop(wdt);
> > +		else
> > +			kempld_wdt_keepalive(wdt);
> > +	}
> > +}
> > +
> > +static int kempld_wdt_remove(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> > +
> > +	BUG_ON(wdt != kempld_wdt);
> > +
> > +	/* stop or at least keepalive the watchdog before we leave */
> > +	kempld_wdt_shutdown(pdev);
> > +
> > +	misc_deregister(&kempld_wdt_miscdev);
> > +
> > +	kempld_wdt_release_stages(wdt);
> > +
> > +	kfree(wdt);
> > +	kempld_wdt = NULL;
> > +	platform_set_drvdata(pdev, NULL);
> > +
> > +	return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM
> > +static int wdt_pm_status_store;
> > +
> > +/* Disable watchdog if it is active during suspend */
> > +static int kempld_wdt_suspend(struct platform_device *pdev,
> > +				pm_message_t message)
> > +{
> > +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> > +	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +	wdt_pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +	kempld_release_mutex(pld);
> > +
> > +	kempld_wdt_update_timeouts(wdt);
> > +
> > +	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE)
> > +		kempld_wdt_shutdown(pdev);
> > +
> > +	return 0;
> > +}
> > +
> > +/* Enable watchdog and configure it if necessary */
> > +static int kempld_wdt_resume(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> > +	int ret;
> > +
> > +	/* if watchdog was stopped before suspend be sure it gets disabled
> > +	 * again, for the case BIOS has enabled it during resume */
> > +	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE) {
> > +		ret = kempld_wdt_settimeout(wdt);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +		ret = kempld_wdt_start(wdt);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +
> > +		dev_info(wdt->pld->dev, "Resuming watchdog operation:\n"
> > +			 "%d s timeout and %d s pretimeout\n", wdt->timeout,
> > +			 wdt->pretimeout);
> > +	} else
> > +		kempld_wdt_shutdown(pdev);
> > +
> > +	return 0;
> > +
> > +err_enable_wdt:
> > +	dev_err(wdt->pld->dev,
> > +		"Failed to reenable the watchdog timer after resume!\n");
> > +
> > +	return ret;
> > +}
> > +#else
> > +#define kempld_wdt_suspend	NULL
> > +#define kempld_wdt_resume	NULL
> > +#endif
> > +
> > +static struct platform_driver kempld_wdt_driver = {
> > +	.driver = {
> > +		.name = "kempld-wdt",
> > +		.owner = THIS_MODULE,
> > +	},
> > +	.probe = kempld_wdt_probe,
> > +	.remove = kempld_wdt_remove,
> > +	.shutdown = kempld_wdt_shutdown,
> > +	.suspend = kempld_wdt_suspend,
> > +	.resume = kempld_wdt_resume,
> > +};
> > +
> > +static int __init kempld_wdt_init(void)
> > +{
> > +	return platform_driver_register(&kempld_wdt_driver);
> > +}
> > +
> > +static void __exit kempld_wdt_exit(void)
> > +{
> > +	platform_driver_unregister(&kempld_wdt_driver);
> > +}
> > +
> > +module_init(kempld_wdt_init);
> > +module_exit(kempld_wdt_exit);
> > +
> > +MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
> > +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
> > diff --git a/drivers/watchdog/kempld_wdt.h b/drivers/watchdog/kempld_wdt.h
> > new file mode 100644
> > index 0000000..80f68f6
> > --- /dev/null
> > +++ b/drivers/watchdog/kempld_wdt.h
> > @@ -0,0 +1,75 @@
> > +/*
> > + *  kempld_wdt.h - Kontron PLD watchdog driver definitions
> > + *
> > + *  Copyright (c) 2010-2012 Kontron Europe GmbH
> > + *  Author: Michael Brunner <michael.brunner@kontron.com>
> > + *
> > + *  This program is free software; you can redistribute it and/or modify
> > + *  it under the terms of the GNU General Public License 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
> > + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + *  GNU General Public License for more details.
> > + *
> > + *  You should have received a copy of the GNU General Public License
> > + *  along with this program; see the file COPYING.  If not, write to
> > + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> > + */
> > +
> > +#ifndef _KEMPLD_WDT_H_
> > +#define _KEMPLD_WDT_H_
> > +
> > +/* watchdog register definitions */
> > +#define KEMPLD_WDT_KICK			0x16
> > +#define KEMPLD_WDT_CFG			0x17
> > +#define		KEMPLD_WDT_CFG_STAGE_TIMEOUT_OCCURED(x)	(1<<x)
> > +#define		KEMPLD_WDT_CFG_ENABLE_LOCK		0x8
> > +#define		KEMPLD_WDT_CFG_ENABLE			0x10
> > +#define		KEMPLD_WDT_CFG_AUTO_RELOAD		0x40
> > +#define		KEMPLD_WDT_CFG_GLOBAL_LOCK		0x80
> > +#define KEMPLD_WDT_STAGE_CFG(x)		(0x18+x)
> > +#define		KEMPLD_WDT_STAGE_CFG_ACTION_MASK	0x7
> > +#define		KEMPLD_WDT_STAGE_CFG_GET_ACTION(x)	(x & 0x7)
> > +#define		KEMPLD_WDT_STAGE_CFG_ASSERT		(1<<3)
> > +#define		KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK	0x30
> > +#define		KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(x)	((x & 0x30)>>4)
> > +#define		KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(x)	((x & 0x30)<<4)
> > +#define KEMPLD_WDT_STAGE_TIMEOUT(x)	(0x1b+x*4)
> > +#define KEMPLD_WDT_MAX_STAGES		3
> > +
> > +#define	KEMPLD_WDT_ACTION_NONE		0x0
> > +#define	KEMPLD_WDT_ACTION_RESET		0x1
> > +#define	KEMPLD_WDT_ACTION_NMI		0x2
> > +#define	KEMPLD_WDT_ACTION_SMI		0x3
> > +#define	KEMPLD_WDT_ACTION_SCI		0x4
> > +#define	KEMPLD_WDT_ACTION_DELAY		0x5
> > +
> > +#define	KEMPLD_WDT_PRESCALER_21BIT	0x0
> > +#define	KEMPLD_WDT_PRESCALER_17BIT	0x1
> > +#define	KEMPLD_WDT_PRESCALER_12BIT	0x2
> > +
> > +const int kempld_prescaler_bits[] = { 21, 17, 12 };
> > +#define KEMPLD_PRESCALER(x)	(0xffffffff>>(32-kempld_prescaler_bits[x]))
> > +
> > +
> > +struct kempld_watchdog_stage {
> > +	int	num;
> > +	u32	timeout_mask;
> > +};
> > +
> > +struct kempld_watchdog_data {
> > +	int				timeout;
> > +	int				pretimeout;
> > +	unsigned long			is_open;
> > +	unsigned long			expect_close;
> > +	int				stages;
> > +	struct kempld_watchdog_stage	*timeout_stage;
> > +	struct kempld_watchdog_stage	*pretimeout_stage;
> > +	struct kempld_device_data	*pld;
> > +	struct watchdog_info		ident;
> > +	struct kempld_watchdog_stage	*stage[KEMPLD_WDT_MAX_STAGES];
> > +};
> > +
> > +#endif /* _KEMPLD_WDT_H_ */
> > -- 
> > 1.7.9.5
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> > 

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 2/4] i2c: Kontron PLD i2c bus driver
  2013-04-08 17:15 ` [PATCH 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
@ 2013-04-10 17:02   ` Guenter Roeck
  2013-04-16  9:53     ` Wolfram Sang
  0 siblings, 1 reply; 48+ messages in thread
From: Guenter Roeck @ 2013-04-10 17:02 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Michael Brunner, Samuel Ortiz, Wolfram Sang,
	Ben Dooks, linux-i2c, Grant Likely, Linus Walleij,
	Wim Van Sebroeck, linux-watchdog, Darren Hart, Michael Brunner,
	Greg Kroah-Hartman

On Mon, Apr 08, 2013 at 10:15:19AM -0700, Kevin Strasser wrote:
> From: Michael Brunner <michael.brunner@kontron.com>
> 
> Add i2c support for the on-board PLD found on some Kontron embedded
> modules.
> 
> Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>

Overall well written, though I have a couple of nitpicks.

I would prefer two separate drivers, one for the mux and one for the i2c bus.
If that is possible, it would help getting rid of the #ifdef in the code, which
is frowned upon in the kernel.

I dislike unnecessary ( ). Maintainer's call, though.

Couple of places have missing spaces around operators (checkpatch doesn't catch
all those).

As far as I know, devm_ functions are supposed to print an error message on
failure, so it should be unnecessary to print another one if that happens (this
might need some confirmation).

Thanks,
Guenter

> ---
>  drivers/i2c/busses/Kconfig      |   20 ++
>  drivers/i2c/busses/Makefile     |    1 +
>  drivers/i2c/busses/i2c-kempld.c |  679 +++++++++++++++++++++++++++++++++++++++
>  drivers/i2c/busses/i2c-kempld.h |   86 +++++
>  4 files changed, 786 insertions(+)
>  create mode 100644 drivers/i2c/busses/i2c-kempld.c
>  create mode 100644 drivers/i2c/busses/i2c-kempld.h
> 
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index adfee98..7aecd61 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -494,6 +494,26 @@ config I2C_IOP3XX
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called i2c-iop3xx.
>  
> +config I2C_KEMPLD
> +	tristate "Kontron COM I2C"
> +	depends on MFD_KEMPLD
> +	help
> +	  This enables support for the I2C bus interface on some Kontron ETX
> +	  and COMexpress (ETXexpress) modules.
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called i2c-kempld.
> +
> +config I2C_KEMPLD_MUX
> +	bool "Enable MUXed I2C ports (EXPERIMENTAL)"
> +	depends on I2C_KEMPLD && I2C_MUX
> +	default n
> +	help
> +	  This enables support for additional I2C ports available on some
> +	  modules. Usually those ports are for board internal usage and
> +	  not routed outside the module.
> +	  Do not use this option unless you know what you are doing!
> +
>  config I2C_MPC
>  	tristate "MPC107/824x/85xx/512x/52xx/83xx/86xx"
>  	depends on PPC
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 8f4fc23..411b8ce 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -48,6 +48,7 @@ obj-$(CONFIG_I2C_IBM_IIC)	+= i2c-ibm_iic.o
>  obj-$(CONFIG_I2C_IMX)		+= i2c-imx.o
>  obj-$(CONFIG_I2C_INTEL_MID)	+= i2c-intel-mid.o
>  obj-$(CONFIG_I2C_IOP3XX)	+= i2c-iop3xx.o
> +obj-$(CONFIG_I2C_KEMPLD)    += i2c-kempld.o
>  obj-$(CONFIG_I2C_MPC)		+= i2c-mpc.o
>  obj-$(CONFIG_I2C_MV64XXX)	+= i2c-mv64xxx.o
>  obj-$(CONFIG_I2C_MXS)		+= i2c-mxs.o
> diff --git a/drivers/i2c/busses/i2c-kempld.c b/drivers/i2c/busses/i2c-kempld.c
> new file mode 100644
> index 0000000..c6b44e7
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-kempld.c
> @@ -0,0 +1,679 @@
> +/*
> + *  i2c-kempld.c: I2C bus driver for Kontron COM modules
> + *
> + *  Copyright (c) 2010-2013 Kontron Europe GmbH
> + *  Author: Michael Brunner <michael.brunner@kontron.com>
> + *
> + *  The driver is based on the i2c-ocores driver by Peter Korsgaard.
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License 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
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; see the file COPYING.  If not, write to
> + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/errno.h>
> +#include <linux/platform_device.h>
> +#include <linux/i2c.h>
> +#ifdef CONFIG_I2C_KEMPLD_MUX
> +#include <linux/i2c-mux.h>
> +#endif
> +#include <linux/delay.h>
> +#include <linux/slab.h>
> +#include <linux/mfd/kempld.h>
> +#include <linux/interrupt.h>
> +#include <linux/time.h>
> +
> +#include "i2c-kempld.h"
> +
> +static int scl_frequency;
> +static int i2c_bus = -1;
> +static int i2c_mx_bus = -1;
> +static bool force_polling;
> +static int i2c_gpio_mux = -1;
> +
> +#ifdef CONFIG_I2C_KEMPLD_MUX
> +static int kempld_i2cmux_select(struct i2c_adapter *adap, void *data, u32 chan)
> +{
> +	struct kempld_i2c_data *i2c = data;
> +	struct kempld_device_data *pld = i2c->pld;
> +	int ret = 0;
> +
> +	if ((i2c->state == STATE_DONE)
> +		|| (i2c->state == STATE_ERROR)) {
> +		if (i2c->mx != chan) {
> +			kempld_get_mutex_set_index(pld, KEMPLD_I2C_MX);
> +			i2c->mx = chan & 0x0f;
> +			kempld_write8(pld, KEMPLD_I2C_MX, i2c->mx);
> +			kempld_release_mutex(pld);
> +		}
> +
> +		/* Reset controller if the last transfer ended with an error */
> +		if (i2c->state == STATE_ERROR) {
> +			u8 ctrl;
> +
> +			kempld_get_mutex_set_index(pld, KEMPLD_I2C_CMD);
> +			ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
> +			ctrl &= ~OCI2C_CTRL_EN;
> +			kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
> +			kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_IACK);
> +			ctrl |= OCI2C_CTRL_EN;
> +			kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
> +			kempld_release_mutex(pld);
> +		}
> +
> +	} else
> +		ret = -EBUSY;
> +
> +	return ret;
> +}
> +
> +static void kempld_i2cmux_del(struct kempld_i2c_data *i2c)
> +{
> +	int i;
> +
> +	for (i = 0; i <= i2c->mx_max; i++) {
> +		if (i2c->mxadap[i]) {
> +			i2c_del_mux_adapter(i2c->mxadap[i]);
> +			i2c->mxadap[i] = NULL;
> +		}
> +	}
> +}
> +
> +static int kempld_i2cmux_add(struct kempld_i2c_data *i2c)
> +{
> +	struct kempld_device_data *pld = i2c->pld;
> +	int i;
> +	int ret = -ENODEV;
> +
> +	for (i = 0; (i <= (i2c->mx_max)); i++) {
> +		i2c->mxadap[i] = i2c_add_mux_adapter(&i2c->adap,
> +							NULL, i2c, 0, i, 0,
> +							kempld_i2cmux_select,
> +							NULL);
> +		if (!i2c->mxadap[i]) {
> +			ret = -ENODEV;
> +			dev_err(pld->dev,
> +				"Failed to register MUX adapter %d\n", i);
> +			goto add_mux_adapter_failed;
> +		}
> +	}
> +
> +	return 0;
> +
> +add_mux_adapter_failed:
> +	kempld_i2cmux_del(i2c);
> +
> +	return ret;
> +}
> +
> +#else
> +#define kempld_i2cmux_add(x) (0)
> +#define kempld_i2cmux_del(x) do {} while (0)
> +#endif
> +
> +static int kempld_i2c_process(struct kempld_i2c_data *i2c)
> +{
> +	struct kempld_device_data *pld = i2c->pld;
> +	struct i2c_msg *msg = i2c->msg;
> +	u8 stat = kempld_read8(pld, KEMPLD_I2C_STATUS);
> +
> +	/* ready? */
> +	if (stat & OCI2C_STAT_TIP)
> +		return -EBUSY;
> +
> +	if ((i2c->state == STATE_DONE) || (i2c->state == STATE_ERROR)) {
> +		/* stop has been sent */
> +		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_IACK);
> +		if (i2c->irq)
> +			wake_up(&i2c->wait);
> +		if (i2c->state == STATE_ERROR)
> +			return -EIO;
> +		else
> +			return 0;
> +	}
> +
> +	/* error? */
> +	if (stat & OCI2C_STAT_ARBLOST) {
> +		i2c->state = STATE_ERROR;
> +		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_STOP);
> +		return -EAGAIN;
> +	}
> +
> +	if (i2c->state == STATE_INIT) {
> +		/* check if bus is free */
> +		if (stat & OCI2C_STAT_BUSY)
> +			return -EBUSY;
> +
> +		i2c->state = STATE_ADDR;
> +	}
> +
> +	if (i2c->state == STATE_ADDR) {
> +		u8 addr;
> +		/* 10 bit address? */
> +		if (i2c->msg->flags & I2C_M_TEN) {
> +			addr = 0xf0 | ((i2c->msg->addr >> 7) & 0x6);
> +			i2c->state = STATE_ADDR10;
> +		} else {
> +			addr = (i2c->msg->addr << 1);
> +			i2c->state = STATE_START;
> +		}
> +
> +		/* set read bit if necessary */
> +		addr |= (i2c->msg->flags & I2C_M_RD) ? 1 : 0;
> +
> +		kempld_write8(pld, KEMPLD_I2C_DATA, addr);
> +		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_START);
> +
> +		return 0;
> +	}
> +
> +	/* second part of 10 bit addressing */
> +	if (i2c->state == STATE_ADDR10) {
> +		kempld_write8(pld, KEMPLD_I2C_DATA, i2c->msg->addr & 0xff);
> +		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_WRITE);
> +
> +		i2c->state = STATE_START;
> +		return 0;
> +	}
> +
> +	if ((i2c->state == STATE_START) || (i2c->state == STATE_WRITE)) {
> +		i2c->state =
> +			(msg->flags & I2C_M_RD) ? STATE_READ : STATE_WRITE;
> +
> +		if (stat & OCI2C_STAT_NACK) {
> +			i2c->state = STATE_ERROR;
> +			kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_STOP);
> +			return -ENXIO;
> +		}
> +	} else
> +		msg->buf[i2c->pos++] = kempld_read8(pld, KEMPLD_I2C_DATA);
> +
> +	/* end of msg? */
> +	if (i2c->pos >= msg->len) {
> +		i2c->nmsgs--;
> +		i2c->msg++;
> +		i2c->pos = 0;
> +		msg = i2c->msg;
> +
> +		if (i2c->nmsgs) {	/* end? */
> +			/* send start? */
> +			if (!(msg->flags & I2C_M_NOSTART)) {
> +				i2c->state = STATE_ADDR;
> +				if (i2c->irq)
> +					wake_up(&i2c->wait);
> +				return 0;
> +			} else
> +				i2c->state = (msg->flags & I2C_M_RD)
> +					? STATE_READ : STATE_WRITE;
> +		} else {
> +			i2c->state = STATE_DONE;
> +			kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_STOP);
> +			return 0;
> +		}
> +	}
> +
> +	if (i2c->state == STATE_READ) {
> +		kempld_write8(pld, KEMPLD_I2C_CMD, i2c->pos == (msg->len-1) ?
> +			OCI2C_CMD_READ_NACK : OCI2C_CMD_READ_ACK);
> +	} else {
> +		kempld_write8(pld, KEMPLD_I2C_DATA, msg->buf[i2c->pos++]);
> +		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_WRITE);
> +	}
> +
> +	return 0;
> +}
> +
> +static irqreturn_t kempld_i2c_isr(int irq, void *dev_id)
> +{
> +	struct kempld_i2c_data *i2c = dev_id;
> +
> +	/* The actual ISR handler is put into a tasklet as it may block
> +	 * and therefore rescheduling must be possible */
> +	tasklet_schedule(&i2c->tasklet);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +void kempld_i2c_tasklet(unsigned long data)
> +{
> +	struct kempld_i2c_data *i2c = (struct kempld_i2c_data *)data;
> +	struct kempld_device_data *pld = i2c->pld;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_I2C_STATUS);
> +	kempld_i2c_process(i2c);
> +	kempld_release_mutex(pld);
> +}
> +
> +static int kempld_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
> +			int num)
> +{
> +	struct kempld_i2c_data *i2c = i2c_get_adapdata(adap);
> +	struct kempld_device_data *pld = i2c->pld;
> +	unsigned long timeout = jiffies + HZ;
> +	int ret;
> +
> +	i2c->msg = msgs;
> +	i2c->pos = 0;
> +	i2c->nmsgs = num;
> +	i2c->state = STATE_INIT;
> +
> +	/* handle the transfer */
> +	while (time_before(jiffies, timeout)) {
> +		kempld_get_mutex_set_index(pld, KEMPLD_I2C_STATUS);
> +		ret = kempld_i2c_process(i2c);
> +		kempld_release_mutex(pld);
> +
> +		if (i2c->irq && ((i2c->state >= STATE_START)
> +				 || (i2c->state == STATE_ERROR))) {
> +			wait_event_timeout(i2c->wait,
> +					(i2c->state == STATE_ERROR) ||
> +					(i2c->state == STATE_DONE) ||
> +					(i2c->state == STATE_ADDR), HZ);
> +			if (i2c->state == STATE_ERROR)
> +				ret = -EIO;
> +		}
> +
> +		if ((i2c->state == STATE_DONE)
> +			|| (i2c->state == STATE_ERROR))
> +			return (i2c->state == STATE_DONE) ? num : ret;
> +
> +		if (ret == 0)
> +			timeout = jiffies + HZ;
> +
> +		usleep_range(5, 15);
> +	}
> +
> +	i2c->state = STATE_ERROR;
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static void kempld_i2c_device_init(struct kempld_i2c_data *i2c)
> +{
> +	struct kempld_device_data *pld = i2c->pld;
> +	long prescale;
> +	u16 prescale_corr;
> +	u8 cfg;
> +	u8 ctrl;
> +	u8 stat;
> +	u8 mx;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_I2C_CONTROL);
> +
> +	ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
> +	if (ctrl & OCI2C_CTRL_EN)
> +		i2c->was_active = 1;
> +
> +	/* set bus frequency */
> +	if (scl_frequency > 0) {
> +		/* make sure the device is disabled */
> +		ctrl &= ~(OCI2C_CTRL_EN|OCI2C_CTRL_IEN);
> +		kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
> +
> +		/* The clock frequency calculation has been changed a bit
> +		 * between the spec. revisions */
> +		if (pld->info.spec_major == 1)
> +			prescale = (pld->pld_clock / (scl_frequency*5)) - 1000;
> +		else
> +			prescale = (pld->pld_clock / (scl_frequency*4)) - 3000;
> +
> +		/* Prevent negative prescaler values */
> +		if (prescale < 0)
> +			prescale = 0;
> +
> +		/* Round to the best matching value */
> +		prescale_corr = prescale / 1000;
> +		if ((prescale % 1000) >= 500)
> +			prescale_corr++;
> +
> +		kempld_write8(pld, KEMPLD_I2C_PRELOW, prescale_corr & 0xff);
> +		kempld_write8(pld, KEMPLD_I2C_PREHIGH, prescale_corr >> 8);
> +	}
> +
> +	/* Activate I2C bus output on GPIO pins */
> +	if (i2c_gpio_mux > -1) {
> +		cfg = kempld_read8(pld, KEMPLD_CFG);
> +		if (i2c_gpio_mux)
> +			cfg |= KEMPLD_CFG_GPIO_I2C_MUX;
> +		else
> +			cfg &= ~KEMPLD_CFG_GPIO_I2C_MUX;
> +		kempld_write8(pld, KEMPLD_CFG, cfg);
> +	}
> +	cfg = kempld_read8(pld, KEMPLD_CFG);
> +	if (cfg & KEMPLD_CFG_GPIO_I2C_MUX)
> +		i2c->gpio_mux = 1;
> +	else
> +		i2c->gpio_mux = 0;
> +	if (((i2c_gpio_mux > 0) && (!i2c->gpio_mux))
> +		|| ((i2c_gpio_mux == 0) && (i2c->gpio_mux)))
> +		dev_warn(pld->dev, "Unable to change GPIO I2C MUX setting\n");
> +
> +	/* Check how much multiplexed I2C busses we have */
> +	mx = kempld_read8(pld, KEMPLD_I2C_MX);
> +	if (pld->info.spec_major > 1) {
> +		i2c->mx_max = KEMPLD_I2C_MX_GET_MAX(mx);
> +		if (i2c->mx_max == 0xf) /* No multiplexer available */
> +			i2c->mx_max = 0;
> +	} else
> +		i2c->mx_max = 1;
> +	/* 2 busses should be enough for all
> +	 * boards using specification revision 1 */
> +
> +	/* Check which MX setting should be set */
> +	if ((i2c_mx_bus == -1) || (i2c_mx_bus > i2c->mx_max)) {
> +		if (i2c_mx_bus > i2c->mx_max) {
> +			dev_err(pld->dev,
> +				"bus selected with i2c_mx_bus not available "
> +				"- leaving MX setting unchanged\n");
> +		}
> +		i2c->mx = mx & KEMPLD_I2C_MX_MASK;
> +	} else
> +		i2c->mx = i2c_mx_bus;
> +
> +	/* Connect the controller to the chosen bus output */
> +	kempld_write8(pld, KEMPLD_I2C_MX, i2c->mx);
> +
> +	/* enable the device */
> +	kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_IACK);
> +	ctrl |= OCI2C_CTRL_EN;
> +	kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
> +
> +	/* If bus is busy send a STOP signal to be sure the controller is
> +	 * not hanging... */
> +	stat = kempld_read8(pld, KEMPLD_I2C_STATUS);
> +	if (stat & OCI2C_STAT_BUSY) {
> +		dev_warn(pld->dev,
> +			 "I2C bus is busy - generating stop signal\n");
> +		kempld_write8(pld, KEMPLD_I2C_CMD, OCI2C_CMD_STOP);
> +	}
> +
> +	kempld_release_mutex(pld);
> +
> +	if ((pld->info.spec_major == 1) && (i2c->mx == 0xf))
> +		i2c->mx = 0;
> +}
> +
> +static void kempld_i2c_irq_enable(struct kempld_i2c_data *i2c)
> +{
> +	struct kempld_device_data *pld = i2c->pld;
> +	u8 irq, ctrl;
> +	int ret;
> +
> +	irq = i2c->irq;
> +
> +	/* This only has to be done once */
> +	if (i2c->irq == 0) {
> +		kempld_get_mutex_set_index(pld, KEMPLD_IRQ_I2C);
> +		irq = kempld_read8(pld, KEMPLD_IRQ_I2C);
> +		kempld_release_mutex(pld);
> +
> +		/* Leave if interrupts are not supported by the I2C core */
> +		if ((irq & 0xf0) == 0xf0)
> +			return;
> +		irq &= 0x0f;
> +		if (irq == 0)
> +			return;
> +
> +		/* Initialize interrupt handlers if not already done */
> +		init_waitqueue_head(&i2c->wait);
> +		tasklet_init(&i2c->tasklet, kempld_i2c_tasklet,
> +				(unsigned long)i2c);
> +
> +		ret = devm_request_irq(pld->dev, irq, kempld_i2c_isr,
> +					IRQF_SHARED, i2c->adap.name, i2c);
> +		if (ret) {
> +			dev_err(pld->dev,
> +				"Unable to claim IRQ - using polling mode\n");
> +			return;
> +		}
> +	}
> +
> +	/* Now enable interrupts in the controller */
> +	kempld_get_mutex_set_index(pld, KEMPLD_I2C_CONTROL);
> +	ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
> +	ctrl |= OCI2C_CTRL_IEN;
> +	kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
> +	kempld_release_mutex(pld);
> +
> +	i2c->irq = irq & 0x0f;
> +}
> +
> +static void kempld_i2c_irq_disable(struct kempld_i2c_data *i2c)
> +{
> +	struct kempld_device_data *pld = i2c->pld;
> +	u8 ctrl;
> +	int irq;
> +
> +	if (i2c->irq == 0)
> +		return;
> +
> +	irq = i2c->irq;
> +	i2c->irq = 0;
> +
> +	tasklet_kill(&i2c->tasklet);
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_I2C_CONTROL);
> +	ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
> +	ctrl &= ~OCI2C_CTRL_IEN;
> +	kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
> +	kempld_release_mutex(pld);
> +
> +	devm_free_irq(pld->dev, irq, i2c);
> +}
> +
> +static u32 kempld_i2c_func(struct i2c_adapter *adap)
> +{
> +	return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR
> +		| I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm kempld_i2c_algorithm = {
> +	.master_xfer	= kempld_i2c_xfer,
> +	.functionality	= kempld_i2c_func,
> +};
> +
> +static struct i2c_adapter kempld_i2c_adapter = {
> +	.owner		= THIS_MODULE,
> +	.name		= "i2c-kempld",
> +	.class		= I2C_CLASS_HWMON | I2C_CLASS_SPD,
> +	.algo		= &kempld_i2c_algorithm,
> +};
> +
> +static int kempld_i2c_get_scl_frequency(struct kempld_i2c_data *i2c)
> +{
> +	struct kempld_device_data *pld = i2c->pld;
> +	int frequency;
> +	u16 prescale;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_I2C_PRELOW);
> +
> +	prescale = kempld_read8(pld, KEMPLD_I2C_PRELOW)
> +		| kempld_read8(pld, KEMPLD_I2C_PREHIGH)<<8;
> +
> +	kempld_release_mutex(pld);
> +
> +	/* The clock frequency calculation has been changed a bit
> +	 * between the spec. revisions */
> +	if (pld->info.spec_major == 1)
> +		frequency = (pld->pld_clock / (prescale + 1)) / 5000;
> +	else
> +		frequency = (pld->pld_clock / (prescale + 3)) / 4000;
> +
> +	return frequency;
> +}
> +
> +static int kempld_i2c_probe(struct platform_device *pdev)
> +{
> +	struct kempld_i2c_data *i2c;
> +	struct kempld_device_data *pld;
> +	int ret;
> +
> +	pld = dev_get_drvdata(pdev->dev.parent);
> +
> +	i2c = kzalloc(sizeof(*i2c), GFP_KERNEL);
> +	if (!i2c)
> +		return -ENOMEM;
> +
> +	i2c->pld = pld;
> +
> +	kempld_i2c_device_init(i2c);
> +
> +	/* hook up driver to tree */
> +	platform_set_drvdata(pdev, i2c);
> +	i2c->adap = kempld_i2c_adapter;
> +	i2c_set_adapdata(&i2c->adap, i2c);
> +	i2c->adap.dev.parent = &pdev->dev;
> +
> +	i2c->irq = 0;
> +	if (!force_polling)
> +		kempld_i2c_irq_enable(i2c);
> +
> +	/* add I2C adapter to I2C tree */
> +	i2c->adap.nr = i2c_bus;
> +	ret = i2c_add_numbered_adapter(&i2c->adap);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to add adapter\n");
> +		goto add_adapter_failed;
> +	}
> +
> +	ret = kempld_i2cmux_add(i2c);
> +	if (ret)
> +		goto add_mux_adapters_failed;
> +
> +	dev_info(pld->dev, "I2C bus initialized with %d kHz SCL frequency\n",
> +		 kempld_i2c_get_scl_frequency(i2c));
> +	dev_info(pld->dev, "I2C MUX connected to bus %d (available: %s%d)\n",
> +		 i2c->mx, i2c->mx_max ? "0-" : "", i2c->mx_max);
> +	dev_info(pld->dev, "I2C IRQs %s\n", i2c->irq ? "enabled" : "disabled");
> +	if (i2c->gpio_mux)
> +		dev_info(pld->dev, "GPIO I2C MUX pins enabled\n");
> +
> +	return 0;
> +
> +add_mux_adapters_failed:
> +	i2c_del_adapter(&i2c->adap);
> +add_adapter_failed:
> +	kfree(i2c);
> +
> +	return ret;
> +}
> +
> +static int kempld_i2c_remove(struct platform_device *pdev)
> +{
> +	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
> +	struct kempld_device_data *pld = i2c->pld;
> +	u8 ctrl;
> +
> +	kempld_i2c_irq_disable(i2c);
> +
> +	if (!i2c->was_active) {
> +		/* disable I2C logic if it was not activated before the
> +		 * driver loaded */
> +		kempld_get_mutex_set_index(pld, KEMPLD_I2C_CONTROL);
> +		ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
> +		ctrl &= ~OCI2C_CTRL_EN;
> +		kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
> +		kempld_release_mutex(pld);
> +	}
> +
> +	/* remove adapter & data */
> +	kempld_i2cmux_del(i2c);
> +	i2c_del_adapter(&i2c->adap);
> +
> +	platform_set_drvdata(pdev, NULL);
> +
> +	kfree(i2c);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int kempld_i2c_suspend(struct platform_device *pdev, pm_message_t state)
> +{
> +	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
> +	struct kempld_device_data *pld = i2c->pld;
> +	u8 ctrl;
> +
> +	kempld_i2c_irq_disable(i2c);
> +
> +	if (!i2c->was_active) {
> +		/* make sure the device is disabled */
> +		kempld_get_mutex_set_index(pld, KEMPLD_I2C_CONTROL);
> +		ctrl = kempld_read8(pld, KEMPLD_I2C_CONTROL);
> +		ctrl &= ~OCI2C_CTRL_EN;
> +		kempld_write8(pld, KEMPLD_I2C_CONTROL, ctrl);
> +		kempld_release_mutex(pld);
> +	}
> +
> +	return 0;
> +}
> +
> +static int kempld_i2c_resume(struct platform_device *pdev)
> +{
> +	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
> +
> +	kempld_i2c_device_init(i2c);
> +	kempld_i2c_irq_enable(i2c);
> +
> +	return 0;
> +}
> +#else
> +#define kempld_i2c_suspend	NULL
> +#define kempld_i2c_resume	NULL
> +#endif
> +
> +static struct platform_driver kempld_i2c_driver = {
> +	.driver = {
> +		.name = "kempld-i2c",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe		= kempld_i2c_probe,
> +	.remove		= kempld_i2c_remove,
> +	.suspend	= kempld_i2c_suspend,
> +	.resume		= kempld_i2c_resume,
> +};
> +
> +static int __init kempld_i2c_init(void)
> +{
> +	/* Check if a valid value for the i2c_mx_bus parameter is provided */
> +	if ((i2c_mx_bus != -1) && (i2c_mx_bus & ~KEMPLD_I2C_MX_MASK))
> +		return -EINVAL;
> +
> +	return platform_driver_register(&kempld_i2c_driver);
> +}
> +
> +static void __exit kempld_i2c_exit(void)
> +{
> +	platform_driver_unregister(&kempld_i2c_driver);
> +}
> +
> +module_init(kempld_i2c_init);
> +module_exit(kempld_i2c_exit);
> +
> +module_param(scl_frequency, int, 0);
> +module_param(i2c_bus, int, 0);
> +module_param(i2c_mx_bus, int, 0);
> +module_param(force_polling, bool, 0);
> +module_param(i2c_gpio_mux, int, 0);
> +
> +MODULE_DESCRIPTION("KEM PLD I2C Driver");
> +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:kempld_i2c");
> +MODULE_PARM_DESC(scl_frequency, "Set I2C SCL frequency (in kHz) default=0");
> +MODULE_PARM_DESC(i2c_bus, "Set I2C bus (-1 for dynamic assignment");
> +MODULE_PARM_DESC(i2c_mx_bus, "Set I2C MX bus (0-15, default=-1 (FW default))");
> +MODULE_PARM_DESC(force_polling, "Force polling mode");
> +MODULE_PARM_DESC(i2c_gpio_mux, "Enable I2C port on GPIO out");
> diff --git a/drivers/i2c/busses/i2c-kempld.h b/drivers/i2c/busses/i2c-kempld.h
> new file mode 100644
> index 0000000..2229662
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-kempld.h
> @@ -0,0 +1,86 @@
> +/*
> + *  i2c-kempld.h - Kontron PLD I2C driver definitions
> + *
> + *  Copyright (c) 2010-2012 Kontron Europe GmbH
> + *  Author: Michael Brunner <michael.brunner@kontron.com>
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License 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
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; see the file COPYING.  If not, write to
> + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#ifndef _KEMPLD_I2C_H_
> +#define _KEMPLD_I2C_H_
> +
> +struct kempld_i2c_data {
> +	struct i2c_adapter		adap;
> +	struct i2c_adapter		*mxadap[15];
> +	struct i2c_msg			*msg;
> +	int				pos;
> +	int				nmsgs;
> +	int				state; /* see STATE_ */
> +	int				was_active;
> +	int				mx;
> +	int				mx_max;
> +	int				gpio_mux;
> +	wait_queue_head_t		wait;
> +	struct tasklet_struct		tasklet;
> +	int				irq;
> +	struct kempld_device_data	*pld;
> +};
> +
> +/* I2C register definitions */
> +#define KEMPLD_I2C_PRELOW	0x0b
> +#define KEMPLD_I2C_PREHIGH	0x0c
> +#define KEMPLD_I2C_CONTROL	0x0d
> +#define KEMPLD_I2C_DATA		0x0e
> +#define KEMPLD_I2C_CMD		0x0f /* write only */
> +#define		KEMPLD_I2C_CMD_STA	0x80
> +#define		KEMPLD_I2C_CMD_STO	0x40
> +#define		KEMPLD_I2C_CMD_RD	0x20
> +#define		KEMPLD_I2C_CMD_WR	0x10
> +#define		KEMPLD_I2C_CMD_NACK	0x08
> +#define		KEMPLD_I2C_CMD_IACK	0x01
> +#define KEMPLD_I2C_STATUS	0x0f /* read only, same address as
> +					KEMPLD_I2C_CMD */
> +#define KEMPLD_I2C_MX		0x15
> +#define		KEMPLD_I2C_MX_GET_MAX(x)	((x & 0xf0)>>4)
> +#define		KEMPLD_I2C_MX_MASK		0x0f
> +
> +#define STATE_DONE		0
> +#define STATE_INIT		1
> +#define STATE_ADDR		2
> +#define STATE_ADDR10		3
> +#define STATE_START		4
> +#define STATE_WRITE		5
> +#define STATE_READ		6
> +#define STATE_ERROR		7
> +
> +/* defines taken from i2c-ocores */
> +#define OCI2C_CTRL_IEN		0x40
> +#define OCI2C_CTRL_EN		0x80
> +
> +#define OCI2C_CMD_START		0x91
> +#define OCI2C_CMD_STOP		0x41
> +#define OCI2C_CMD_READ		0x21
> +#define OCI2C_CMD_WRITE		0x11
> +#define OCI2C_CMD_READ_ACK	0x21
> +#define OCI2C_CMD_READ_NACK	0x29
> +#define OCI2C_CMD_IACK		0x01
> +
> +#define OCI2C_STAT_IF		0x01
> +#define OCI2C_STAT_TIP		0x02
> +#define OCI2C_STAT_ARBLOST	0x20
> +#define OCI2C_STAT_BUSY		0x40
> +#define OCI2C_STAT_NACK		0x80
> +
> +#endif /* _KEMPLD_I2C_H_ */
> -- 
> 1.7.9.5
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 3/4] gpio: Kontron PLD gpio driver
  2013-04-09 16:41     ` Guenter Roeck
@ 2013-04-10 20:06       ` Linus Walleij
  0 siblings, 0 replies; 48+ messages in thread
From: Linus Walleij @ 2013-04-10 20:06 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Kevin Strasser, linux-kernel, Michael Brunner, Samuel Ortiz,
	Wolfram Sang, Ben Dooks, linux-i2c, Grant Likely,
	Wim Van Sebroeck, linux-watchdog, Darren Hart, Michael Brunner,
	Greg Kroah-Hartman

On Tue, Apr 9, 2013 at 6:41 PM, Guenter Roeck <linux@roeck-us.net> wrote:
> On Tue, Apr 09, 2013 at 10:46:15AM +0200, Linus Walleij wrote:
>> On Mon, Apr 8, 2013 at 7:15 PM, Kevin Strasser
>> <kevin.strasser@linux.intel.com> wrote:
>>
>> > From: Michael Brunner <michael.brunner@kontron.com>
>> >
>> > Add gpio support for the on-board PLD found on some Kontron embedded
>> > modules.
>> >
>> > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
>> > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
>>
>> This looks very generic, setting and clearing bits in bytesized
>> registers.
>>
>> Can you please attempt to use generic GPIO for this?
>>
> Linus,
>
> I looked into it, but for my part I seem to be missing how the generic GPIO code
> permits locking access to the hardware (PLD) and setting the PLD's page register.
> In other words, I don't immediately see how to call kempld_get_mutex_set_index()
> from the generic GPIO code. The other drivers using generic GPIO code don't
> seem to have that requirement.

Ah yes, I was totally wrong here.

I thought it was MMIO while it is indeed through an MFD proxy.

I'll have a second look then...

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 3/4] gpio: Kontron PLD gpio driver
  2013-04-08 17:15 ` [PATCH 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
  2013-04-09  8:46   ` Linus Walleij
@ 2013-04-10 20:45   ` Linus Walleij
  2013-04-12 11:09     ` Michael Brunner
  1 sibling, 1 reply; 48+ messages in thread
From: Linus Walleij @ 2013-04-10 20:45 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Michael Brunner, Samuel Ortiz, Wolfram Sang,
	Ben Dooks, linux-i2c, Grant Likely, Wim Van Sebroeck,
	linux-watchdog, Darren Hart, Michael Brunner, Greg Kroah-Hartman,
	Thomas Gleixner

On Mon, Apr 8, 2013 at 7:15 PM, Kevin Strasser
<kevin.strasser@linux.intel.com> wrote:

> From: Michael Brunner <michael.brunner@kontron.com>
>
> Add gpio support for the on-board PLD found on some Kontron embedded
> modules.
>
> Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>

Trying to do some real review...

(...)
> +++ b/drivers/gpio/gpio-kempld.c
> +#include <linux/acpi.h>

Is this used?

> +#include <linux/platform_device.h>
> +#include <linux/gpio.h>
> +#include <linux/mfd/kempld.h>
> +#include <linux/seq_file.h>
> +
> +#include "gpio-kempld.h"
> +
> +static int gpiobase = -1;
> +static int gpioien = 0x00;
> +static int gpioevt_lvl_edge = -1;
> +static int gpioevt_low_high = -1;
> +static int gpionmien = 0x00;

(...)

+static int kempld_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+       struct kempld_gpio_data *gpio
+               = container_of(chip, struct kempld_gpio_data, chip);
+       return gpio->irq;
+}

I don't understand this *at all* so help me out here.

.gpio_to_irq() should return a *Linux* IRQ number, usually we take
the event offset (in this case) and map to a Linux IRQ using the
irqdomain helper library. Can you explain how we can be sure that
this number (apparently just a read from a register on the device)
can be made to correspond to a Linux IRQ?

Also if this thing can generate IRQs, are these one line to the CPU
per IRQ really? Don't you need to demux the status register and
create a cascades irqchip?

Maybe it's just me not understanding x86 & ACPI so bear with me...

> +static int kempld_gpio_setup_event(struct kempld_gpio_data *gpio)
> +{
> +       struct kempld_device_data *pld = gpio->pld;
> +       struct gpio_chip *chip = &gpio->chip;
> +       int irq;
> +
> +       irq = gpio->irq;
> +
> +       kempld_get_mutex_set_index(pld, KEMPLD_IRQ_GPIO);
> +       irq = kempld_read8(pld, KEMPLD_IRQ_GPIO);
> +
> +       /* Leave if interrupts are not supported by the GPIO core */
> +       if ((irq & 0xf0) == 0xf0)
> +               return 0;
> +
> +       gpio->irq = irq & 0x0f;

So you read the IRQ from some plug-n-play here, and it's some
system-wide IRQ number?

(...)
> +       if (gpio->irq)
> +               chip->to_irq =          kempld_gpio_to_irq;

So that is this mystery with the IRQs and how they turn into
Linux IRQs.

> +module_param(gpiobase, int, 0444);

Why do you need to be able to configure this?
It must be a real usecase, debugging can be done by patching
the code.

> +module_param(gpioien, int, 0444);
> +module_param(gpioevt_lvl_edge, int, 0444);
> +module_param(gpioevt_low_high, int, 0444);
> +module_param(gpionmien, int, 0444);

Argh how can anyone possibly make this out ... do you really
need them or can we get rid of some and rely on autodetect?

> +MODULE_DESCRIPTION("KEM PLD GPIO Driver");
> +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:kempld_gpio");
> +MODULE_PARM_DESC(gpiobase, "Set GPIO base (default -1=dynamic)");
> +MODULE_PARM_DESC(gpioien, "Set GPIO IEN register (default 0x00)");
> +MODULE_PARM_DESC(gpioevt_lvl_edge,
> +                       "Set GPIO EVT_LVL_EDGE register (default -1=no change)");
> +MODULE_PARM_DESC(gpioevt_low_high,
> +                       "Set GPIO EVT_LOW_HIGH register (default -1=no change)");
> +MODULE_PARM_DESC(gpionmien, "Set GPIO NMIEN register (default 0x00)");


So I don't really like that interrupt enablement and edge and low/high
is done with module parameters instead of just creating an irqchip and
have it implement the operations to do exactly these things at runtime
instead.

Again maybe some x86 thing I don't get...

> diff --git a/drivers/gpio/gpio-kempld.h b/drivers/gpio/gpio-kempld.h
(...)
> +struct kempld_gpio_data {
> +       struct gpio_chip                chip;
> +       int                             irq;
> +       struct kempld_device_data       *pld;
> +       uint16_t                        mask;

Just u16?

> +};

(...)
> diff --git a/drivers/gpio/gpio-kempld_now1.c b/drivers/gpio/gpio-kempld_now1.c
> +#include <linux/io.h>

Do you use this?

> +#include <linux/slab.h>
> +#include <linux/errno.h>
> +#include <linux/acpi.h>

And this?

> +#include <linux/platform_device.h>
> +#include <linux/gpio.h>
> +#include <linux/mfd/kempld.h>
> +#include <linux/seq_file.h>
> +
> +#include "gpio-kempld.h"
(...)
> +

Most comments concern the other driver too.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 3/4] gpio: Kontron PLD gpio driver
  2013-04-10 20:45   ` Linus Walleij
@ 2013-04-12 11:09     ` Michael Brunner
  2013-04-12 22:05       ` Linus Walleij
  0 siblings, 1 reply; 48+ messages in thread
From: Michael Brunner @ 2013-04-12 11:09 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Kevin Strasser, linux-kernel, Michael Brunner, Samuel Ortiz,
	Wolfram Sang, Ben Dooks, linux-i2c, Grant Likely,
	Wim Van Sebroeck, linux-watchdog, Darren Hart,
	Greg Kroah-Hartman, Thomas Gleixner

Hi Linus,

As this code is from me I will comment on your review.

On Wed, 10 Apr 2013 22:45:51 +0200
Linus Walleij <linus.walleij@linaro.org> wrote:
(...)
> Trying to do some real review...
> 
> (...)  
> > +++ b/drivers/gpio/gpio-kempld.c
> > +#include <linux/acpi.h>  
> 
> Is this used?  

Actually not, this can be removed.

> > +#include <linux/platform_device.h>
> > +#include <linux/gpio.h>
> > +#include <linux/mfd/kempld.h>
> > +#include <linux/seq_file.h>
> > +
> > +#include "gpio-kempld.h"
> > +
> > +static int gpiobase = -1;
> > +static int gpioien = 0x00;
> > +static int gpioevt_lvl_edge = -1;
> > +static int gpioevt_low_high = -1;
> > +static int gpionmien = 0x00;  
> 
> (...)
> 
> +static int kempld_gpio_to_irq(struct gpio_chip *chip, unsigned
> offset) +{
> +       struct kempld_gpio_data *gpio
> +               = container_of(chip, struct kempld_gpio_data, chip);
> +       return gpio->irq;
> +}
> 
> I don't understand this *at all* so help me out here.
> 
> .gpio_to_irq() should return a *Linux* IRQ number, usually we take
> the event offset (in this case) and map to a Linux IRQ using the
> irqdomain helper library. Can you explain how we can be sure that
> this number (apparently just a read from a register on the device)
> can be made to correspond to a Linux IRQ?
> 
> Also if this thing can generate IRQs, are these one line to the CPU
> per IRQ really? Don't you need to demux the status register and
> create a cascades irqchip?
> 
> Maybe it's just me not understanding x86 & ACPI so bear with me...  

The chip is connected to the CPU through a serial IRQ line and IRQs
are managed through the (A)PIC which is configured by the
firmware. I never saw a difference between Linux and HW IRQ numbers
for the legacy IRQs (0-15) this chip generates. But I will take
another look at the IRQ handling of this driver.

> > +static int kempld_gpio_setup_event(struct kempld_gpio_data *gpio)
> > +{
> > +       struct kempld_device_data *pld = gpio->pld;
> > +       struct gpio_chip *chip = &gpio->chip;
> > +       int irq;
> > +
> > +       irq = gpio->irq;
> > +
> > +       kempld_get_mutex_set_index(pld, KEMPLD_IRQ_GPIO);
> > +       irq = kempld_read8(pld, KEMPLD_IRQ_GPIO);
> > +
> > +       /* Leave if interrupts are not supported by the GPIO core */
> > +       if ((irq & 0xf0) == 0xf0)
> > +               return 0;
> > +
> > +       gpio->irq = irq & 0x0f;  
> 
> So you read the IRQ from some plug-n-play here, and it's some
> system-wide IRQ number?  

Correct.

> (...)  
> > +       if (gpio->irq)
> > +               chip->to_irq =          kempld_gpio_to_irq;  
> 
> So that is this mystery with the IRQs and how they turn into
> Linux IRQs.
>   
> > +module_param(gpiobase, int, 0444);  
> 
> Why do you need to be able to configure this?
> It must be a real usecase, debugging can be done by patching
> the code.  

This was intended to help developing userspace applications or scripts.
For this parameter I had in mind that one configures a static
GPIO base and then maps the GPIOs with the help of the sysfs interface
without the need to first find out which is the actual GPIO base. If you think this shouldn't be done this way I won't insist
to keep this parameter.

> > +module_param(gpioien, int, 0444);
> > +module_param(gpioevt_lvl_edge, int, 0444);
> > +module_param(gpioevt_low_high, int, 0444);
> > +module_param(gpionmien, int, 0444);  
> 
> Argh how can anyone possibly make this out ... do you really
> need them or can we get rid of some and rely on autodetect?  

As the chip sits on a computer module that is usually only configured
generically, it is not possible to auto detect the needed configuration.
Those parameters are intended to let the developer configure the chip
without having to touch the driver code.
You are right anyway, doing it this way might not be the best way. So if
there is a good way to configure this stuff at runtime by using a
generic interface I would also prefer this.

> > +MODULE_DESCRIPTION("KEM PLD GPIO Driver");
> > +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:kempld_gpio");
> > +MODULE_PARM_DESC(gpiobase, "Set GPIO base (default -1=dynamic)");
> > +MODULE_PARM_DESC(gpioien, "Set GPIO IEN register (default 0x00)");
> > +MODULE_PARM_DESC(gpioevt_lvl_edge,
> > +                       "Set GPIO EVT_LVL_EDGE register (default
> > -1=no change)"); +MODULE_PARM_DESC(gpioevt_low_high,
> > +                       "Set GPIO EVT_LOW_HIGH register (default
> > -1=no change)"); +MODULE_PARM_DESC(gpionmien, "Set GPIO NMIEN
> > register (default 0x00)");  
> 
> 
> So I don't really like that interrupt enablement and edge and low/high
> is done with module parameters instead of just creating an irqchip and
> have it implement the operations to do exactly these things at runtime
> instead.
> 
> Again maybe some x86 thing I don't get...  

Possibly not. I am not very familiar with irqchip so far, therefore I
will have a look at it and check if the whole IRQ handling can be
ported to this framework.

> > diff --git a/drivers/gpio/gpio-kempld.h
> > b/drivers/gpio/gpio-kempld.h  
> (...)  
> > +struct kempld_gpio_data {
> > +       struct gpio_chip                chip;
> > +       int                             irq;
> > +       struct kempld_device_data       *pld;
> > +       uint16_t                        mask;  
> 
> Just u16?  

The specification allows 16 GPIOs for this device, therefore this seems
to be the right size. Would it be better to use another type instead?

> > +};  
> 
> (...)  
> > diff --git a/drivers/gpio/gpio-kempld_now1.c
> > b/drivers/gpio/gpio-kempld_now1.c +#include <linux/io.h>  
> 
> Do you use this?  

This can be removed.

> > +#include <linux/slab.h>
> > +#include <linux/errno.h>
> > +#include <linux/acpi.h>  
> 
> And this?  

linux/slab.h is necessary for kzalloc, but the rest can be removed.

> > +#include <linux/platform_device.h>
> > +#include <linux/gpio.h>
> > +#include <linux/mfd/kempld.h>
> > +#include <linux/seq_file.h>
> > +
> > +#include "gpio-kempld.h"  
> (...)  
> > +  
> 
> Most comments concern the other driver too.
> 
> Yours,
> Linus Walleij  

Thank you for the review!

Best regards,
  Michael

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 3/4] gpio: Kontron PLD gpio driver
  2013-04-12 11:09     ` Michael Brunner
@ 2013-04-12 22:05       ` Linus Walleij
  0 siblings, 0 replies; 48+ messages in thread
From: Linus Walleij @ 2013-04-12 22:05 UTC (permalink / raw)
  To: Michael Brunner
  Cc: Kevin Strasser, linux-kernel, Michael Brunner, Samuel Ortiz,
	Wolfram Sang, Ben Dooks, linux-i2c, Grant Likely,
	Wim Van Sebroeck, linux-watchdog, Darren Hart,
	Greg Kroah-Hartman, Thomas Gleixner

On Fri, Apr 12, 2013 at 1:09 PM, Michael Brunner <mibru@gmx.de> wrote:

>> (...)
>> > +struct kempld_gpio_data {
>> > +       struct gpio_chip                chip;
>> > +       int                             irq;
>> > +       struct kempld_device_data       *pld;
>> > +       uint16_t                        mask;
>>
>> Just u16?
>
> The specification allows 16 GPIOs for this device, therefore this seems
> to be the right size. Would it be better to use another type instead?

Ah, I was just asking you to use "u16" instead of "uint16_t".

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 1/4] mfd: Kontron PLD mfd driver
  2013-04-08 17:15 [PATCH 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
                   ` (2 preceding siblings ...)
  2013-04-08 17:15 ` [PATCH 4/4] watchdog: Kontron PLD watchdog timer Kevin Strasser
@ 2013-04-13 20:38 ` Thomas Gleixner
  2013-04-18  4:19   ` Guenter Roeck
  2013-06-18 21:04 ` [PATCH v2 0/4] Kontron PLD drivers Kevin Strasser
  2013-06-24  4:00 ` [PATCH v3 0/4] Kontron PLD drivers Kevin Strasser
  5 siblings, 1 reply; 48+ messages in thread
From: Thomas Gleixner @ 2013-04-13 20:38 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Michael Brunner, Samuel Ortiz, Wolfram Sang,
	Ben Dooks, linux-i2c, Grant Likely, Linus Walleij,
	Wim Van Sebroeck, linux-watchdog, Darren Hart, Michael Brunner,
	Greg Kroah-Hartman

On Mon, 8 Apr 2013, Kevin Strasser wrote:
> --- /dev/null
> +++ b/drivers/mfd/kempld-core.c
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/list.h>
> +#include <linux/device.h>
> +#include <linux/platform_device.h>
> +#include <linux/dmi.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/sched.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/kempld.h>

I seriously doubt, that all these includes are required.

> +#define KEMPLD_MAINTAIN_EFT_COMPATIBILITY	1

What's the point of this define ?

> +static int kempld_platform_device_register(const struct dmi_system_id *id);
> +static int kempld_get_mutex_set_index_generic(struct kempld_device_data *pld,
> +					u8 index, unsigned int timeout);
> +static void kempld_release_mutex_generic(struct kempld_device_data *pld);
> +
> +static int kempld_get_info(struct kempld_device_data *pld);
> +static int kempld_get_info_NOW1(struct kempld_device_data *pld);

Can you please get rid of the CamelCase ?

> +static int kempld_get_info_generic(struct kempld_device_data *pld);
> +static int kempld_get_features(struct kempld_device_data *pld);
> +static int kempld_register_cells_generic(struct kempld_device_data *pld);
> +static int kempld_register_cells_NOW1(struct kempld_device_data *pld);

Can you please reshuffle the code, so that we can get rid of all these
forward declarations ?

> +#define MAX_IDENT_LEN 4
> +static char force_ident[MAX_IDENT_LEN + 1] = "";
> +module_param_string(force_ident, force_ident, sizeof(force_ident), 0);
> +MODULE_PARM_DESC(force_ident, "Force detection of specific product");

Please change this to something which is ad hoc understandable w/o
reading the code. e.g. "kempld_device_id". 

> +/* this option is only here for debugging and should never be needed in
> + * production environments */

/*
 * Please use standard multiline comment style and proper
 * sentences starting with a capital letter
 */

> +static bool force_unlock;
> +module_param(force_unlock, bool, 0);
> +MODULE_PARM_DESC(force_unlock, "Force breaking the semaphore on driver load");

Is it really necessary to carry this in the kernel? If yes, then please put it under

#ifdef DEBUG

We really can do without random debug code. And the comment should be
a little more elaborate about what the heck this is doing. "Force
breaking the semaphore ..." makes me shudder, w/o reading the code
which uses this.

> +/**
> + * kempld_read8 - read 8 bit register
> + * @pld: kempld_device_data structure describing the PLD
> + * @index: register index on the chip
> + *
> + * This function reads an 8 bit register of the PLD and returns its value.
> + *
> + * In order for this function to work correctly, kempld_try_get_mutex_set_index
> + * or kempld_get_mutex_set_index has to be called before calling the function
> + * to acquire the mutex. Afterwards the mutex has to be released with
> + * kempld_release_mutex.
> + */
> +u8 kempld_read8(struct kempld_device_data *pld, u8 index)
> +{
> +	kempld_set_index(pld, index);
> +
> +	return ioread8(pld->io_data);
> +}
> +EXPORT_SYMBOL(kempld_read8);

EXPORT_SYMBOL_GPL please. All over the place.

> +/**
> + * kempld_read16 - read 16 bit register
> + * @pld: kempld_device_data structure describing the PLD
> + * @index: register index on the chip
> + *
> + * This function reads a 16 bit register of the PLD and returns its value.
> + *
> + * In order for this function to work correctly, kempld_try_get_mutex_set_index
> + * or kempld_get_mutex_set_index has to be called before calling the function
> + * to acquire the mutex. Afterwards the mutex has to be released with
> + * kempld_release_mutex.
> + */
> +u16 kempld_read16(struct kempld_device_data *pld, u8 index)
> +{
> +	BUG_ON(index+1 < index);

Yuck. What kind of problem are you catching here? Just the corner case
that someone hands in 0xff as index?

I'd rather assume that you tried to catch the case where someone hand
in an index with BIT0 set. So that would be:
   
	BUG_ON(index & 0x01);

Aside of that, do you really want to kill the machine here? A
WARN_ON[_ONCE] would be more appropriate.

	WARN_ON_ONCE(index & 0x01);

> +	return kempld_read8(pld, index) | kempld_read8(pld, index+1) << 8;

  	       			 	  		    index + 1)
Please

> +void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data)
> +{
> +	BUG_ON(index+1 < index);

See above. And all other functions which use that silly BUG_ON as well.

> +/**
> + * kempld_set_index - change the current register index of the PLD
> + * @pld: kempld_device_data structure describing the PLD
> + * @index: register index on the chip
> + *
> + * This function changes the register index of the PLD.

That's really important information after reading the above function
descriptor...

> + *
> + * If the PLD mutex has been acquired the whole time and the desired index is

-ENOPARSE

> + * already set there might be no actual hardware access done in this function.
> + *
> + * In order for this function to work correctly, kempld_try_get_mutex_set_index
> + * or kempld_get_mutex_set_index has to be called before calling the function
> + * to acquire the mutex. Afterwards the mutex has to be released with
> + * kempld_release_mutex.
> + */
> +void kempld_set_index(struct kempld_device_data *pld, u8 index)
> +{
> +	struct kempld_platform_data *pdata = pld->dev->platform_data;
> +
> +	BUG_ON(pld->have_mutex == 0);

What the heck is this? Does pld->have_mutex indicate that there is a
mutex associated with that PLD or what?

If you want to check whether the caller has acquired the mutex which
is always associated to that PLD then you should perhaps read the
mutex documentation^Wcode and figure out how to do that correctly.

> +	if (pld->last_index != index || pdata->force_index_write) {
> +		iowrite8(index, pld->io_index);
> +		pld->last_index = index;
> +	}
> +}
> +EXPORT_SYMBOL(kempld_set_index);
> +
> +static int kempld_get_mutex_set_index_generic(struct kempld_device_data *pld,
> +					u8 index, unsigned int timeout)
> +{
> +	struct kempld_platform_data *pdata = pld->dev->platform_data;
> +	int data;
> +
> +	if (!pld->have_mutex) {

So you use a boolean value to maintain concurrency? OMG. So any task
which calls this code and observes pld->have_mutex != 0 can
proceed. Brilliant design.

> +		unsigned long loop_timeout = jiffies + (HZ*timeout)/1000;
> +
> +		while ((((data = ioread8(pld->io_index)) & KEMPLD_MUTEX_KEY)
> +				== KEMPLD_MUTEX_KEY)) {

So there is a hardware concurrency control, which has a single KEY for
everyone? At least that's what I read from that code.

> +			if (timeout != KEMPLD_MUTEX_NOTIMEOUT)

WTF are you introducing another constant fpor INFINITE timeout?

> +				if (!time_before(jiffies, loop_timeout))
> +					return -ETIMEDOUT;
> +
> +			/* we will have to wait until mutex is free */
> +			spin_unlock_irqrestore(&pld->lock, pld->lock_flags);

Where the heck is documented that this function needs to be called
with pld->lock held and interrupts disabled?

> +			/* give other tasks a chance to release the mutex */
> +			schedule_timeout_interruptible(0);

Creative avoidance of yield? Not that yield is a good idea, but this
is f*cking absurd.

> +			spin_lock_irqsave(&pld->lock, pld->lock_flags);

What's the point of this exercise? 

> +		}
> +	} else
> +		data = ioread8(pld->io_index);
> +
> +	if (KEMPLD_MAINTAIN_EFT_COMPATIBILITY

Evaluates to TRUE unconditionally. What's the point ?

> +		 || ((pld->last_index != (data & ~KEMPLD_MUTEX_KEY))
> +		 || pdata->force_index_write)) {
> +		iowrite8(index, pld->io_index);
> +		pld->last_index = index;
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * kempld_get_mutex_set_index - acquire the PLD mutex and set register index
> + * @pld: kempld_device_data structure describing the PLD
> + * @index: register index on the chip
> + *
> + * This function acquires a PLD spinlock and the PLD mutex, additionally it
> + * also changes the register index. In order to do no unnecessary write cycles
> + * the index provided to this function should be the same that will be used
> + * with the first PLD access that is done afterwards.
> + *
> + * The function will block for at least 10 seconds if the mutex can't be
> + * acquired and issue a warning in that case. In order to not lock the device,
> + * the function assumes that the mutex has been acquired in that case.

What the heck? We do not do that in the kernel. Either you get your
locking correct, or you don't. There is no point of 10 seconds timeout
to get a "mutex" which is actually not a mutex. You call that code
with the spinlock held and irqs disabled. Pretty much not mutex
semantics.

> + * To release the spinlock and mutex kempld_release_mutex can be called.
> + * The spinlock and mutex should only be kept for a few milliseconds, in order
> + * to give other drivers a chance to work with the PLD.
> + */
> +inline void kempld_get_mutex_set_index(struct kempld_device_data *pld,
> +					u8 index)
> +{
> +	struct kempld_platform_data *pdata = pld->dev->platform_data;
> +
> +	spin_lock_irqsave(&pld->lock, pld->lock_flags);
> +
> +	if (pdata->get_mutex_set_index) {
> +		/* use a long timeout here as this shouldn't fail */
> +		if (pdata->get_mutex_set_index(pld, index, 10000))
> +			dev_warn(pld->dev, "semaphore broken!\n");
> +
> +		pld->have_mutex = 1;

Now I really start to go berserk. What's the point of this timeout
thing and what is the point of pdata->get_mutex_set_index ?

Either you have the need for a hardware controlled serialization or
you do not. I have the feeling that your understanding of concurrency
control is close to ZERO. 

I'm stopping the review here as this is just a f*cking nightmare and
going further down the patch is just a pointless exercise.

NAK to the whole patch series.

Thanks,

	tglx

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 2/4] i2c: Kontron PLD i2c bus driver
  2013-04-10 17:02   ` Guenter Roeck
@ 2013-04-16  9:53     ` Wolfram Sang
  0 siblings, 0 replies; 48+ messages in thread
From: Wolfram Sang @ 2013-04-16  9:53 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Kevin Strasser, linux-kernel, Michael Brunner, Samuel Ortiz,
	Ben Dooks, linux-i2c, Grant Likely, Linus Walleij,
	Wim Van Sebroeck, linux-watchdog, Darren Hart, Michael Brunner,
	Greg Kroah-Hartman

On Wed, Apr 10, 2013 at 10:02:12AM -0700, Guenter Roeck wrote:
> On Mon, Apr 08, 2013 at 10:15:19AM -0700, Kevin Strasser wrote:
> > From: Michael Brunner <michael.brunner@kontron.com>
> > 
> > Add i2c support for the on-board PLD found on some Kontron embedded
> > modules.
> > 
> > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> 
> Overall well written, though I have a couple of nitpicks.
> 
> I would prefer two separate drivers, one for the mux and one for the i2c bus.
> If that is possible, it would help getting rid of the #ifdef in the code, which
> is frowned upon in the kernel.
> 
> I dislike unnecessary ( ). Maintainer's call, though.
> 
> Couple of places have missing spaces around operators (checkpatch doesn't catch
> all those).
> 
> As far as I know, devm_ functions are supposed to print an error message on
> failure, so it should be unnecessary to print another one if that happens (this
> might need some confirmation).

Haven't done a full review due to tglx NACK. I agree to the points
mentioned here by Guenter, though.


^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 1/4] mfd: Kontron PLD mfd driver
  2013-04-13 20:38 ` [PATCH 1/4] mfd: Kontron PLD mfd driver Thomas Gleixner
@ 2013-04-18  4:19   ` Guenter Roeck
  2013-04-18  4:40     ` Joe Perches
  0 siblings, 1 reply; 48+ messages in thread
From: Guenter Roeck @ 2013-04-18  4:19 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: Kevin Strasser, linux-kernel, Michael Brunner, Samuel Ortiz,
	Wolfram Sang, Ben Dooks, linux-i2c, Grant Likely, Linus Walleij,
	Wim Van Sebroeck, linux-watchdog, Darren Hart, Michael Brunner,
	Greg Kroah-Hartman, Joe Perches

On Sat, Apr 13, 2013 at 10:38:07PM +0200, Thomas Gleixner wrote:

[ ... ]

> 
> > +	return kempld_read8(pld, index) | kempld_read8(pld, index+1) << 8;
> 
>   	       			 	  		    index + 1)
> Please
> 
Wondering .... why does checkpatch not report those ? I just reviewed another
driver with the same kind of problem, and checkpatch is just as silent. There
seems to be a whole class of expressions where it does not complain about
missing spaces.

Joe, any idea ?

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 1/4] mfd: Kontron PLD mfd driver
  2013-04-18  4:19   ` Guenter Roeck
@ 2013-04-18  4:40     ` Joe Perches
  2013-04-18 13:35       ` Guenter Roeck
  0 siblings, 1 reply; 48+ messages in thread
From: Joe Perches @ 2013-04-18  4:40 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Thomas Gleixner, Kevin Strasser, linux-kernel, Michael Brunner,
	Samuel Ortiz, Wolfram Sang, Ben Dooks, linux-i2c, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, linux-watchdog, Darren Hart,
	Michael Brunner, Greg Kroah-Hartman

On Wed, 2013-04-17 at 21:19 -0700, Guenter Roeck wrote:
> On Sat, Apr 13, 2013 at 10:38:07PM +0200, Thomas Gleixner wrote:
> > > + return kempld_read8(pld, index) | kempld_read8(pld, index+1) << 8;
> >                                                         index + 1)
> > Please
> Wondering .... why does checkpatch not report those ?

because checkpatch doesn't care about spacing around
arithmetic as long as it's consistent.

Documentation/CodingStyle doesn't say anything about
it either.

Look around checkpatch line 2654

                                } elsif ($op eq '<<' or $op eq '>>' or
                                         $op eq '&' or $op eq '^' or $op eq '|' or
                                         $op eq '+' or $op eq '-' or
                                         $op eq '*' or $op eq '/' or
                                         $op eq '%')
                                {
                                        if ($ctx =~ /Wx[^WCE]|[^WCE]xW/) {
                                                ERROR("SPACING",
                                                      "need consistent spacing around '$op' $at\n" .
                                                        $hereptr);
                                        }



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 1/4] mfd: Kontron PLD mfd driver
  2013-04-18  4:40     ` Joe Perches
@ 2013-04-18 13:35       ` Guenter Roeck
  2013-04-18 16:42         ` Joe Perches
  0 siblings, 1 reply; 48+ messages in thread
From: Guenter Roeck @ 2013-04-18 13:35 UTC (permalink / raw)
  To: Joe Perches
  Cc: Thomas Gleixner, Kevin Strasser, linux-kernel, Michael Brunner,
	Samuel Ortiz, Wolfram Sang, Ben Dooks, linux-i2c, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, linux-watchdog, Darren Hart,
	Michael Brunner, Greg Kroah-Hartman

On Wed, Apr 17, 2013 at 09:40:53PM -0700, Joe Perches wrote:
> On Wed, 2013-04-17 at 21:19 -0700, Guenter Roeck wrote:
> > On Sat, Apr 13, 2013 at 10:38:07PM +0200, Thomas Gleixner wrote:
> > > > + return kempld_read8(pld, index) | kempld_read8(pld, index+1) << 8;
> > >                                                         index + 1)
> > > Please
> > Wondering .... why does checkpatch not report those ?
> 
> because checkpatch doesn't care about spacing around
> arithmetic as long as it's consistent.
> 
> Documentation/CodingStyle doesn't say anything about
> it either.
> 
Hi Joe,

"Use one space around (on each side of) most binary and ternary operators"

doesn't apply, then ? When does it apply ? I always thought it would apply
to cases such as the above.

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 1/4] mfd: Kontron PLD mfd driver
  2013-04-18 13:35       ` Guenter Roeck
@ 2013-04-18 16:42         ` Joe Perches
  2013-04-18 18:40           ` Guenter Roeck
  0 siblings, 1 reply; 48+ messages in thread
From: Joe Perches @ 2013-04-18 16:42 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Thomas Gleixner, Kevin Strasser, linux-kernel, Michael Brunner,
	Samuel Ortiz, Wolfram Sang, Ben Dooks, linux-i2c, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, linux-watchdog, Darren Hart,
	Michael Brunner, Greg Kroah-Hartman

On Thu, 2013-04-18 at 06:35 -0700, Guenter Roeck wrote:
> On Wed, Apr 17, 2013 at 09:40:53PM -0700, Joe Perches wrote:
> > On Wed, 2013-04-17 at 21:19 -0700, Guenter Roeck wrote:
> > > On Sat, Apr 13, 2013 at 10:38:07PM +0200, Thomas Gleixner wrote:
> > > > > + return kempld_read8(pld, index) | kempld_read8(pld, index+1) << 8;
> > > >                                                         index + 1)
> > > > Please
> > > Wondering .... why does checkpatch not report those ?
> > 
> > because checkpatch doesn't care about spacing around
> > arithmetic as long as it's consistent.
> > 
> > Documentation/CodingStyle doesn't say anything about
> > it either.
> > 
> Hi Joe,
> 
> "Use one space around (on each side of) most binary and ternary operators"
> 
> doesn't apply, then ? When does it apply ? I always thought it would apply
> to cases such as the above.

There's a _lot_ of code that doesn't follow the
"single space around binary operators" style,
it's contentious, and was determined when Andy
did the original checkpatch implementation to not
be a valuable addition or worth the complaint pain.



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 1/4] mfd: Kontron PLD mfd driver
  2013-04-18 16:42         ` Joe Perches
@ 2013-04-18 18:40           ` Guenter Roeck
  0 siblings, 0 replies; 48+ messages in thread
From: Guenter Roeck @ 2013-04-18 18:40 UTC (permalink / raw)
  To: Joe Perches
  Cc: Thomas Gleixner, Kevin Strasser, linux-kernel, Michael Brunner,
	Samuel Ortiz, Wolfram Sang, Ben Dooks, linux-i2c, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, linux-watchdog, Darren Hart,
	Michael Brunner, Greg Kroah-Hartman

On Thu, Apr 18, 2013 at 09:42:17AM -0700, Joe Perches wrote:
> On Thu, 2013-04-18 at 06:35 -0700, Guenter Roeck wrote:
> > On Wed, Apr 17, 2013 at 09:40:53PM -0700, Joe Perches wrote:
> > > On Wed, 2013-04-17 at 21:19 -0700, Guenter Roeck wrote:
> > > > On Sat, Apr 13, 2013 at 10:38:07PM +0200, Thomas Gleixner wrote:
> > > > > > + return kempld_read8(pld, index) | kempld_read8(pld, index+1) << 8;
> > > > >                                                         index + 1)
> > > > > Please
> > > > Wondering .... why does checkpatch not report those ?
> > > 
> > > because checkpatch doesn't care about spacing around
> > > arithmetic as long as it's consistent.
> > > 
> > > Documentation/CodingStyle doesn't say anything about
> > > it either.
> > > 
> > Hi Joe,
> > 
> > "Use one space around (on each side of) most binary and ternary operators"
> > 
> > doesn't apply, then ? When does it apply ? I always thought it would apply
> > to cases such as the above.
> 
> There's a _lot_ of code that doesn't follow the
> "single space around binary operators" style,
> it's contentious, and was determined when Andy
> did the original checkpatch implementation to not
> be a valuable addition or worth the complaint pain.
> 
Looks like it is contentious either way.

Thanks a lot for the clarification.

Guenter

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH 4/4] watchdog: Kontron PLD watchdog timer
  2013-04-10 16:57     ` Kevin Strasser
@ 2013-05-26 14:38       ` Wim Van Sebroeck
  0 siblings, 0 replies; 48+ messages in thread
From: Wim Van Sebroeck @ 2013-05-26 14:38 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: Guenter Roeck, linux-kernel, Michael Brunner, Samuel Ortiz,
	Wolfram Sang, Ben Dooks, linux-i2c, Grant Likely, Linus Walleij,
	linux-watchdog, Darren Hart, Michael Brunner, Greg Kroah-Hartman

Hi Kevin,

> On Wed, Apr 10, 2013 at 09:47:17AM -0700, Guenter Roeck wrote:
> > On Mon, Apr 08, 2013 at 10:15:21AM -0700, Kevin Strasser wrote:
> > > From: Michael Brunner <michael.brunner@kontron.com>
> > > 
> > > Add watchdog timer support for the on-board PLD found on some Kontron
> > > embedded modules.
> > > 
> > > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> > > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> > 
> > Personally I would prefer two separate patches for the two drivers,
> > and to have the drivers converted to the watchdog infrastructure.
> > Wim's call, of course.
> > 
> Thanks for the feedback. I'm happy to do both if Wim thinks it's
> necessary.

Yes, 2 patches with conversion to the new watchdog infrastructure please.

Kind regards,
Wim.


^ permalink raw reply	[flat|nested] 48+ messages in thread

* [PATCH v2 0/4] Kontron PLD drivers
  2013-04-08 17:15 [PATCH 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
                   ` (3 preceding siblings ...)
  2013-04-13 20:38 ` [PATCH 1/4] mfd: Kontron PLD mfd driver Thomas Gleixner
@ 2013-06-18 21:04 ` Kevin Strasser
  2013-06-18 21:04   ` [PATCH v2 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
                     ` (3 more replies)
  2013-06-24  4:00 ` [PATCH v3 0/4] Kontron PLD drivers Kevin Strasser
  5 siblings, 4 replies; 48+ messages in thread
From: Kevin Strasser @ 2013-06-18 21:04 UTC (permalink / raw)
  To: linux-kernel
  Cc: Kevin Strasser, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, linux-i2c, linux-watchdog

mfd changes since v1:
- Use a mutex instead of spinlock
- Poll for hardware mutex without timeout
- Restructure mfd cell structs, only call mfd_add_devices once
- Drop pointless BUG_ONs
- EXPORT_SYMBOL -> EXPORT_SYMBOL_GPL
- kempld_*_mutex_set_index -> kempld_*_mutex
- Removed kempld_try_get_mutex
- Drop last_index
- Remove EFT code
- Drop unused includes
- Use devm_ioport_map
- Restructure to get rid of forward function prototypes
- Renamed module parameter force_ident -> force_device_id
- Dropped force_unlock module parameter
- Formatting fixes

i2c changes since v1:
- Always disable bus during suspend,
- Detect was_active in probe
- Remove i2c-kempld.h
- Clean up register definitions
- Use the correct device for printing
- Set default bus frequency to 100kHz
- Drop irq support
- Drop now1 driver
- Remove i2c-mux code
- Clean up includes
- Use devm_kzalloc
- Formatting fixes

gpio changes since v1:
- Change label from kempld-gpio -> gpio-kempld
- Drop unnecessary include seq_file.h
- Register and value parameters to kempld_get_bit and kempld_bitop are now u8
- Status variable in kempld_bitop is now u8
- Fix kempld_gpio_pincount(). hweight16 doesn't work, but __ffs does
- Drop interrupt support
- Drop gpio-kempld.h
- Use helper functions for bit and read operations
- Use generic DEBUG_FS code
- Use devm_kzalloc
- Get gpio_base from platform data
- Drop all module parameters
- Use module_platform_driver
- Drop unused includes
- Include device.h instead of slab.h for devm_kzalloc
- Some cleanup here and there

watchdog changes since v1:
- Use watchdog framework
- Allocate stages statically
- Drop now1 driver
- Use devm_kzalloc
- Change default timeout to 30 seconds
- Drop unused includes
- General formatting cleanup

Kevin Strasser (4):
  mfd: Kontron PLD mfd driver
  i2c: Kontron PLD i2c bus driver
  gpio: Kontron PLD gpio driver
  watchdog: Kontron PLD watchdog timer driver

 drivers/gpio/Kconfig            |   12 +
 drivers/gpio/Makefile           |    1 +
 drivers/gpio/gpio-kempld.c      |  225 ++++++++++++++
 drivers/i2c/busses/Kconfig      |   10 +
 drivers/i2c/busses/Makefile     |    1 +
 drivers/i2c/busses/i2c-kempld.c |  410 +++++++++++++++++++++++++
 drivers/mfd/Kconfig             |   21 ++
 drivers/mfd/Makefile            |    1 +
 drivers/mfd/kempld-core.c       |  642 +++++++++++++++++++++++++++++++++++++++
 drivers/watchdog/Kconfig        |   11 +
 drivers/watchdog/Makefile       |    1 +
 drivers/watchdog/kempld_wdt.c   |  580 +++++++++++++++++++++++++++++++++++
 include/linux/mfd/kempld.h      |  125 ++++++++
 13 files changed, 2040 insertions(+)
 create mode 100644 drivers/gpio/gpio-kempld.c
 create mode 100644 drivers/i2c/busses/i2c-kempld.c
 create mode 100644 drivers/mfd/kempld-core.c
 create mode 100644 drivers/watchdog/kempld_wdt.c
 create mode 100644 include/linux/mfd/kempld.h

-- 
1.7.9.5


^ permalink raw reply	[flat|nested] 48+ messages in thread

* [PATCH v2 1/4] mfd: Kontron PLD mfd driver
  2013-06-18 21:04 ` [PATCH v2 0/4] Kontron PLD drivers Kevin Strasser
@ 2013-06-18 21:04   ` Kevin Strasser
  2013-06-19  8:40     ` Linus Walleij
  2013-06-19  9:12     ` Thomas Gleixner
  2013-06-18 21:04   ` [PATCH v2 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
                     ` (2 subsequent siblings)
  3 siblings, 2 replies; 48+ messages in thread
From: Kevin Strasser @ 2013-06-18 21:04 UTC (permalink / raw)
  To: linux-kernel
  Cc: Kevin Strasser, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, Michael Brunner

Add core MFD driver for the on-board PLD found on some Kontron embedded
modules. The PLD device may provide functions like watchdog, GPIO, UART
and I2C bus.

The following modules are supported:
	* COMe-bIP#
	* COMe-bPC2 (ETXexpress-PC)
	* COMe-bSC# (ETXexpress-SC T#)
	* COMe-cCT6
	* COMe-cDC2 (microETXexpress-DC)
	* COMe-cPC2 (microETXexpress-PC)
	* COMe-mCT10
	* ETX-OH

Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Darren Hart <dvhart@linux.intel.com>
---
 drivers/mfd/Kconfig        |   21 ++
 drivers/mfd/Makefile       |    1 +
 drivers/mfd/kempld-core.c  |  642 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/kempld.h |  125 +++++++++
 4 files changed, 789 insertions(+)
 create mode 100644 drivers/mfd/kempld-core.c
 create mode 100644 include/linux/mfd/kempld.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index c346941..eea3737 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -912,6 +912,27 @@ config MFD_TIMBERDALE
 	The timberdale FPGA can be found on the Intel Atom development board
 	for in-vehicle infontainment, called Russellville.
 
+config MFD_KEMPLD
+	tristate "Support for Kontron module PLD device"
+	select MFD_CORE
+	help
+	  This is the core driver for the PLD (Programmable Logic Device) found
+	  on some Kontron ETX and COMexpress (ETXexpress) modules. The PLD
+	  device may provide functions like watchdog, GPIO, UART and I2C bus.
+
+	  The following modules are supported:
+		* COMe-bIP#
+		* COMe-bPC2 (ETXexpress-PC)
+		* COMe-bSC# (ETXexpress-SC T#)
+		* COMe-cCT6
+		* COMe-cDC2 (microETXexpress-DC)
+		* COMe-cPC2 (microETXexpress-PC)
+		* COMe-mCT10
+		* ETX-OH
+
+	  This driver can also be built as a module. If so, the module
+	  will be called kempld-core.
+
 config LPC_SCH
 	tristate "Intel SCH LPC"
 	depends on PCI && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index b90409c..a2f6a9f 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -123,6 +123,7 @@ obj-$(CONFIG_MFD_DB8500_PRCMU)	+= db8500-prcmu.o
 obj-$(CONFIG_AB8500_CORE)	+= ab8500-core.o ab8500-sysctrl.o
 obj-$(CONFIG_MFD_TIMBERDALE)    += timberdale.o
 obj-$(CONFIG_PMIC_ADP5520)	+= adp5520.o
+obj-$(CONFIG_MFD_KEMPLD)	+= kempld-core.o
 obj-$(CONFIG_LPC_SCH)		+= lpc_sch.o
 obj-$(CONFIG_LPC_ICH)		+= lpc_ich.o
 obj-$(CONFIG_MFD_RDC321X)	+= rdc321x-southbridge.o
diff --git a/drivers/mfd/kempld-core.c b/drivers/mfd/kempld-core.c
new file mode 100644
index 0000000..85b437e
--- /dev/null
+++ b/drivers/mfd/kempld-core.c
@@ -0,0 +1,642 @@
+/*
+ * Kontron PLD MFD core driver
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/kempld.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#define MAX_ID_LEN 4
+static char force_device_id[MAX_ID_LEN + 1] = "";
+module_param_string(force_device_id, force_device_id, sizeof(force_device_id), 0);
+MODULE_PARM_DESC(force_device_id, "Override detected product");
+
+/*
+ * Get hardware mutex to block firmware from accessing the pld.
+ * It is possible for the firmware may hold the mutex for an extended length of
+ * time. This function will block until access has been granted.
+ */
+static void kempld_get_hardware_mutex(struct kempld_device_data *pld)
+{
+	/* The mutex bit will read 1 until access has been granted */
+	while (ioread8(pld->io_index) & KEMPLD_MUTEX_KEY)
+		msleep(1);
+}
+
+static void kempld_release_hardware_mutex(struct kempld_device_data *pld)
+{
+	/* The harware mutex is released when 1 is written to the mutex bit. */
+	iowrite8(KEMPLD_MUTEX_KEY, pld->io_index);
+}
+
+static int kempld_get_info_generic(struct kempld_device_data *pld)
+{
+	u16 version;
+	u8 spec;
+
+	kempld_get_mutex(pld);
+
+	version = kempld_read16(pld, KEMPLD_VERSION);
+	spec = kempld_read8(pld, KEMPLD_SPEC);
+	pld->info.buildnr = kempld_read16(pld, KEMPLD_BUILDNR);
+
+	pld->info.minor = KEMPLD_VERSION_GET_MINOR(version);
+	pld->info.major = KEMPLD_VERSION_GET_MAJOR(version);
+	pld->info.number = KEMPLD_VERSION_GET_NUMBER(version);
+	pld->info.type = KEMPLD_VERSION_GET_TYPE(version);
+
+	if (spec == 0xff) {
+		pld->info.spec_minor = 0;
+		pld->info.spec_major = 1;
+	} else {
+		pld->info.spec_minor = KEMPLD_SPEC_GET_MINOR(spec);
+		pld->info.spec_major = KEMPLD_SPEC_GET_MAJOR(spec);
+	}
+
+	if (pld->info.spec_major > 0)
+		pld->feature_mask = kempld_read16(pld, KEMPLD_FEATURE);
+	else
+		pld->feature_mask = 0;
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+enum kempld_cells {
+	KEMPLD_I2C = 0,
+	KEMPLD_WDT,
+	KEMPLD_GPIO,
+	KEMPLD_UART,
+};
+
+static struct mfd_cell kempld_devs[] = {
+	[KEMPLD_I2C] = {
+		.name = "kempld-i2c",
+	},
+	[KEMPLD_WDT] = {
+		.name = "kempld-wdt",
+	},
+	[KEMPLD_GPIO] = {
+		.name = "kempld-gpio",
+	},
+	[KEMPLD_UART] = {
+		.name = "kempld-uart",
+	},
+};
+
+#define KEMPLD_MAX_DEVS	ARRAY_SIZE(kempld_devs)
+
+static int kempld_register_cells_generic(struct kempld_device_data *pld)
+{
+	struct mfd_cell devs[KEMPLD_MAX_DEVS];
+	int i = 0;
+
+	if (pld->feature_mask & KEMPLD_FEATURE_BIT_I2C)
+		devs[i++] = kempld_devs[KEMPLD_I2C];
+
+	if (pld->feature_mask & KEMPLD_FEATURE_BIT_WATCHDOG)
+		devs[i++] = kempld_devs[KEMPLD_WDT];
+
+	if (pld->feature_mask & KEMPLD_FEATURE_BIT_GPIO)
+		devs[i++] = kempld_devs[KEMPLD_GPIO];
+
+	if (pld->feature_mask & KEMPLD_FEATURE_MASK_UART)
+		devs[i++] = kempld_devs[KEMPLD_UART];
+
+	return mfd_add_devices(pld->dev, -1, devs, i, NULL, 0, NULL);
+}
+
+static struct resource kempld_ioresource = {
+	.start	= KEMPLD_IOINDEX,
+	.end	= KEMPLD_IODATA,
+	.flags	= IORESOURCE_IO,
+};
+
+static const struct kempld_platform_data kempld_platform_data_generic = {
+	.pld_clock		= KEMPLD_CLK,
+	.ioresource		= &kempld_ioresource,
+	.get_hardware_mutex	= kempld_get_hardware_mutex,
+	.release_hardware_mutex	= kempld_release_hardware_mutex,
+	.get_info		= kempld_get_info_generic,
+	.register_cells		= kempld_register_cells_generic,
+};
+
+static struct platform_device *kempld_pdev;
+
+static int kempld_create_platform_device(const struct dmi_system_id *id)
+{
+	struct kempld_platform_data *pdata = id->driver_data;
+	int ret;
+
+	kempld_pdev = platform_device_alloc("kempld", -1);
+	if (!kempld_pdev)
+		return -ENOMEM;
+
+	ret = platform_device_add_data(kempld_pdev, pdata, sizeof(*pdata));
+	if (ret)
+		goto err;
+
+	ret = platform_device_add_resources(kempld_pdev, pdata->ioresource, 1);
+	if (ret)
+		goto err;
+
+	ret = platform_device_add(kempld_pdev);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	platform_device_put(kempld_pdev);
+	return ret;
+}
+
+/**
+ * kempld_read8 - read 8 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+u8 kempld_read8(struct kempld_device_data *pld, u8 index)
+{
+	iowrite8(index, pld->io_index);
+	return ioread8(pld->io_data);
+}
+EXPORT_SYMBOL_GPL(kempld_read8);
+
+/**
+ * kempld_write8 - write 8 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ * @data: new register value
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data)
+{
+	iowrite8(index, pld->io_index);
+	iowrite8(data, pld->io_data);
+}
+EXPORT_SYMBOL_GPL(kempld_write8);
+
+/**
+ * kempld_read16 - read 16 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+u16 kempld_read16(struct kempld_device_data *pld, u8 index)
+{
+	return kempld_read8(pld, index) | kempld_read8(pld, index + 1) << 8;
+}
+EXPORT_SYMBOL_GPL(kempld_read16);
+
+/**
+ * kempld_write16 - write 16 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ * @data: new register value
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data)
+{
+	kempld_write8(pld, index, (u8)data);
+	kempld_write8(pld, index + 1, (u8)(data >> 8));
+}
+EXPORT_SYMBOL_GPL(kempld_write16);
+
+/**
+ * kempld_read32 - read 32 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+u32 kempld_read32(struct kempld_device_data *pld, u8 index)
+{
+	return kempld_read16(pld, index) | kempld_read16(pld, index + 2) << 16;
+}
+EXPORT_SYMBOL_GPL(kempld_read32);
+
+/**
+ * kempld_write32 - write 32 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ * @data: new register value
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data)
+{
+	kempld_write16(pld, index, (u16)data);
+	kempld_write16(pld, index + 2, (u16)(data >> 16));
+}
+EXPORT_SYMBOL_GPL(kempld_write32);
+
+/**
+ * kempld_get_mutex - acquire PLD mutex
+ * @pld: kempld_device_data structure describing the PLD
+ */
+void kempld_get_mutex(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	mutex_lock(&pld->lock);
+	pdata->get_hardware_mutex(pld);
+}
+EXPORT_SYMBOL_GPL(kempld_get_mutex);
+
+/**
+ * kempld_release_mutex - release PLD mutex
+ * @pld: kempld_device_data structure describing the PLD
+ */
+void kempld_release_mutex(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	pdata->release_hardware_mutex(pld);
+	mutex_unlock(&pld->lock);
+}
+EXPORT_SYMBOL_GPL(kempld_release_mutex);
+
+/**
+ * kempld_get_info - update device specific information
+ * @pld: kempld_device_data structure describing the PLD
+ *
+ * This function calls the configured board specific kempld_get_info_XXXX
+ * function which is responsible for gathering information about the specific
+ * hardware. The information is then stored within the pld structure.
+ */
+static int kempld_get_info(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	return pdata->get_info(pld);
+}
+
+/*
+ * kempld_register_cells - register cell drivers
+ *
+ * This function registers cell drivers for the detected hardware by calling
+ * the configured kempld_register_cells_XXXX function which is responsible
+ * to detect and register the needed cell drivers.
+ */
+static int kempld_register_cells(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	return pdata->register_cells(pld);
+}
+
+static int kempld_detect_device(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+	char *version_type;
+	u8 index_reg;
+	int ret;
+
+	mutex_lock(&pld->lock);
+
+	/* Check for empty IO space */
+	index_reg = ioread8(pld->io_index);
+	if (index_reg == 0xff && ioread8(pld->io_data) == 0xff) {
+		mutex_unlock(&pld->lock);
+		return -ENODEV;
+	}
+
+	/* Release hardware mutex if aquired */
+	if (!(index_reg & KEMPLD_MUTEX_KEY))
+		iowrite8(KEMPLD_MUTEX_KEY, pld->io_index);
+
+	mutex_unlock(&pld->lock);
+
+	ret = kempld_get_info(pld);
+	if (ret)
+		return ret;
+
+	switch (pld->info.type) {
+	case 0:
+		version_type = "release";
+		break;
+	case 1:
+		version_type = "debug";
+		break;
+	case 2:
+		version_type = "custom";
+		break;
+	default:
+		version_type = "unspecified";
+	}
+
+	dev_info(pld->dev, "Found Kontron PLD %d\n", pld->info.number);
+	dev_info(pld->dev, "%s version %d.%d build %d, specification %d.%d\n",
+		 version_type, pld->info.major, pld->info.minor,
+		 pld->info.buildnr, pld->info.spec_major,
+		 pld->info.spec_minor);
+
+	return kempld_register_cells(pld);
+}
+
+static int kempld_probe(struct platform_device *pdev)
+{
+	struct kempld_platform_data *pdata = pdev->dev.platform_data;
+	struct device *dev = &pdev->dev;
+	struct kempld_device_data *pld;
+	struct resource *ioport;
+	int ret;
+
+	pld = devm_kzalloc(dev, sizeof(*pld), GFP_KERNEL);
+	if (!pld)
+		return -ENOMEM;
+
+	ioport = platform_get_resource(pdev, IORESOURCE_IO, 0);
+	if (!ioport)
+		return -EINVAL;
+
+	pld->io_base = devm_ioport_map(dev, ioport->start,
+					ioport->end - ioport->start);
+	if (!pld->io_base)
+		return -ENOMEM;
+
+	pld->io_index = pld->io_base;
+	pld->io_data = pld->io_base + 1;
+	pld->pld_clock = pdata->pld_clock;
+	pld->dev = dev;
+
+	mutex_init(&pld->lock);
+	platform_set_drvdata(pdev, pld);
+
+	ret = kempld_detect_device(pld);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int kempld_remove(struct platform_device *pdev)
+{
+	struct kempld_device_data *pld = platform_get_drvdata(pdev);
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	mfd_remove_devices(&pdev->dev);
+	pdata->release_hardware_mutex(pld);
+
+	return 0;
+}
+
+static struct platform_driver kempld_driver = {
+	.driver		= {
+		.name	= "kempld",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= kempld_probe,
+	.remove		= kempld_remove,
+};
+
+static struct dmi_system_id __initdata kempld_dmi_table[] = {
+	{
+		.ident = "CCR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bIP2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CCR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bIP6"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-SC T2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXe-SC T2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bSC2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-SC T6"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXe-SC T6"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bSC6"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-PC"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bPC2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CNTX",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "PXT"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "FRI2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BIOS_VERSION, "FRI2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "FRI2",
+		.matches = {
+			DMI_MATCH(DMI_PRODUCT_NAME, "Fish River Island II"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "MBR1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETX-OH"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "NTC1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "nanoETXexpress-TT"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "NTC1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "nETXe-TT"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "NTC1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-mTT"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "NUP1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-mCT"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "UNP1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "microETXexpress-DC"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "UNP1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-cDC2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "UNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "microETXexpress-PC"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "UNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-cPC2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "UUP6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-cCT6"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(dmi, kempld_dmi_table);
+
+static int __init kempld_init(void)
+{
+	const struct dmi_system_id *id;
+	int ret;
+
+	if (force_device_id[0]) {
+		for (id = kempld_dmi_table; id->matches[0].slot != DMI_NONE; id++)
+			if (strstr(id->ident, force_device_id))
+				if (id->callback && id->callback(id))
+					break;
+		if (id->matches[0].slot == DMI_NONE)
+			return -ENODEV;
+	} else {
+		if (!dmi_check_system(kempld_dmi_table))
+			return -ENODEV;
+	}
+
+	ret = platform_driver_register(&kempld_driver);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void __exit kempld_exit(void)
+{
+	if (kempld_pdev)
+		platform_device_unregister(kempld_pdev);
+
+	platform_driver_unregister(&kempld_driver);
+}
+
+module_init(kempld_init);
+module_exit(kempld_exit);
+
+MODULE_DESCRIPTION("KEM PLD Core Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld-core");
diff --git a/include/linux/mfd/kempld.h b/include/linux/mfd/kempld.h
new file mode 100644
index 0000000..b911ef3
--- /dev/null
+++ b/include/linux/mfd/kempld.h
@@ -0,0 +1,125 @@
+/*
+ * Kontron PLD driver definitions
+ *
+ * Copyright (c) 2010-2012 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 2 as published
+ * by the Free Software Foundation.
+ */
+
+#ifndef _LINUX_MFD_KEMPLD_H_
+#define _LINUX_MFD_KEMPLD_H_
+
+/* kempld register definitions */
+#define KEMPLD_IOINDEX			0xa80
+#define KEMPLD_IODATA			0xa81
+#define KEMPLD_MUTEX_KEY		0x80
+#define KEMPLD_VERSION			0x00
+#define KEMPLD_VERSION_LSB		0x00
+#define KEMPLD_VERSION_MSB		0x01
+#define KEMPLD_VERSION_GET_MINOR(x)	(x & 0x1f)
+#define KEMPLD_VERSION_GET_MAJOR(x)	((x >> 5) & 0x1f)
+#define KEMPLD_VERSION_GET_NUMBER(x)	((x >> 10) & 0xf)
+#define KEMPLD_VERSION_GET_TYPE(x)	((x >> 14) & 0x3)
+#define KEMPLD_BUILDNR			0x02
+#define KEMPLD_BUILDNR_LSB		0x02
+#define KEMPLD_BUILDNR_MSB		0x03
+#define KEMPLD_FEATURE			0x04
+#define KEMPLD_FEATURE_LSB		0x04
+#define KEMPLD_FEATURE_MSB		0x05
+#define KEMPLD_FEATURE_BIT_I2C		(1 << 0)
+#define KEMPLD_FEATURE_BIT_WATCHDOG	(1 << 1)
+#define KEMPLD_FEATURE_BIT_GPIO		(1 << 2)
+#define KEMPLD_FEATURE_MASK_UART	(7 << 3)
+#define KEMPLD_FEATURE_BIT_NMI		(1 << 8)
+#define KEMPLD_FEATURE_BIT_SMI		(1 << 9)
+#define KEMPLD_FEATURE_BIT_SCI		(1 << 10)
+#define KEMPLD_SPEC			0x06
+#define KEMPLD_SPEC_GET_MINOR(x)	(x & 0x0f)
+#define KEMPLD_SPEC_GET_MAJOR(x)	((x >> 4) & 0x0f)
+#define KEMPLD_IRQ_GPIO			0x35
+#define KEMPLD_IRQ_I2C			0x36
+#define KEMPLD_CFG			0x37
+#define KEMPLD_CFG_GPIO_I2C_MUX		(1 << 0)
+#define KEMPLD_CFG_BIOS_WP		(1 << 7)
+
+#define KEMPLD_CLK			33333333
+
+#define	KEMPLD_TYPE_RELEASE		0x0
+#define	KEMPLD_TYPE_DEBUG		0x1
+#define	KEMPLD_TYPE_CUSTOM		0x2
+
+/**
+ * struct kempld_info - PLD device information structure
+ * @major:	PLD major revision
+ * @minor:	PLD minor revision
+ * @buildnr:	PLD build number
+ * @number:	PLD board specific index
+ * @type:	PLD type
+ * @spec_major:	PLD FW specification major revision
+ * @spec_minor:	PLD FW specification minor revision
+ */
+struct kempld_info {
+	unsigned int major;
+	unsigned int minor;
+	unsigned int buildnr;
+	unsigned int number;
+	unsigned int type;
+	unsigned int spec_major;
+	unsigned int spec_minor;
+};
+
+/**
+ * struct kempld_device_data - Internal representation of the PLD device
+ * @io_base:		Pointer to the IO memory
+ * @io_index:		Pointer to the IO index register
+ * @io_data:		Pointer to the IO data register
+ * @pld_clock:		PLD clock frequency
+ * @feature_mask:	PLD feature mask
+ * @dev:		Pointer to kernel device structure
+ * @info:		KEMPLD info structure
+ * @lock:		PLD mutex
+ */
+struct kempld_device_data {
+	void __iomem		*io_base;
+	void __iomem		*io_index;
+	void __iomem		*io_data;
+	u32			pld_clock;
+	u32			feature_mask;
+	struct device		*dev;
+	struct kempld_info	info;
+	struct mutex		lock;
+};
+
+/**
+ * struct kempld_platform_data - PLD hardware configuration structure
+ * @pld_clock:			PLD clock frequency
+ * @gpio_base			GPIO base pin number
+ * @ioresource:			IO addresses of the PLD
+ * @get_mutex:			PLD specific get_mutex callback
+ * @release_mutex:		PLD specific release_mutex callback
+ * @get_info:			PLD specific get_info callback
+ * @register_cells:		PLD specific register_cells callback
+ */
+struct kempld_platform_data {
+	u32				pld_clock;
+	int				gpio_base;
+	struct resource			*ioresource;
+	void (*get_hardware_mutex)	(struct kempld_device_data *);
+	void (*release_hardware_mutex)	(struct kempld_device_data *);
+	int (*get_info)			(struct kempld_device_data *);
+	int (*register_cells)		(struct kempld_device_data *);
+};
+
+extern void kempld_get_mutex(struct kempld_device_data *pld);
+extern void kempld_release_mutex(struct kempld_device_data *pld);
+extern u8 kempld_read8(struct kempld_device_data *pld, u8 index);
+extern void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data);
+extern u16 kempld_read16(struct kempld_device_data *pld, u8 index);
+extern void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data);
+extern u32 kempld_read32(struct kempld_device_data *pld, u8 index);
+extern void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data);
+
+#endif /* _LINUX_MFD_KEMPLD_H_ */
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 2/4] i2c: Kontron PLD i2c bus driver
  2013-06-18 21:04 ` [PATCH v2 0/4] Kontron PLD drivers Kevin Strasser
  2013-06-18 21:04   ` [PATCH v2 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
@ 2013-06-18 21:04   ` Kevin Strasser
  2013-06-18 21:04   ` [PATCH v2 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
  2013-06-18 21:04   ` [PATCH v2 4/4] watchdog: Kontron PLD watchdog timer driver Kevin Strasser
  3 siblings, 0 replies; 48+ messages in thread
From: Kevin Strasser @ 2013-06-18 21:04 UTC (permalink / raw)
  To: linux-kernel
  Cc: Kevin Strasser, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, linux-i2c, Michael Brunner

Add i2c support for the on-board PLD found on some Kontron embedded
modules.

Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Darren Hart <dvhart@linux.intel.com>
---
 drivers/i2c/busses/Kconfig      |   10 +
 drivers/i2c/busses/Makefile     |    1 +
 drivers/i2c/busses/i2c-kempld.c |  410 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 421 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-kempld.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index adfee98..3e1457d 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -494,6 +494,16 @@ config I2C_IOP3XX
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-iop3xx.
 
+config I2C_KEMPLD
+	tristate "Kontron COM I2C Controller"
+	depends on MFD_KEMPLD
+	help
+	  This enables support for the I2C bus interface on some Kontron ETX
+	  and COMexpress (ETXexpress) modules.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called i2c-kempld.
+
 config I2C_MPC
 	tristate "MPC107/824x/85xx/512x/52xx/83xx/86xx"
 	depends on PPC
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 8f4fc23..150fa15 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_I2C_IBM_IIC)	+= i2c-ibm_iic.o
 obj-$(CONFIG_I2C_IMX)		+= i2c-imx.o
 obj-$(CONFIG_I2C_INTEL_MID)	+= i2c-intel-mid.o
 obj-$(CONFIG_I2C_IOP3XX)	+= i2c-iop3xx.o
+obj-$(CONFIG_I2C_KEMPLD)	+= i2c-kempld.o
 obj-$(CONFIG_I2C_MPC)		+= i2c-mpc.o
 obj-$(CONFIG_I2C_MV64XXX)	+= i2c-mv64xxx.o
 obj-$(CONFIG_I2C_MXS)		+= i2c-mxs.o
diff --git a/drivers/i2c/busses/i2c-kempld.c b/drivers/i2c/busses/i2c-kempld.c
new file mode 100644
index 0000000..ccec916
--- /dev/null
+++ b/drivers/i2c/busses/i2c-kempld.c
@@ -0,0 +1,410 @@
+/*
+ * I2C bus driver for Kontron COM modules
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * The driver is based on the i2c-ocores driver by Peter Korsgaard.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/mfd/kempld.h>
+
+#define KEMPLD_I2C_PRELOW	0x0b
+#define KEMPLD_I2C_PREHIGH	0x0c
+#define KEMPLD_I2C_DATA		0x0e
+
+#define KEMPLD_I2C_CTRL		0x0d
+#define I2C_CTRL_IEN		0x40
+#define I2C_CTRL_EN		0x80
+
+#define KEMPLD_I2C_STAT		0x0f
+#define I2C_STAT_IF		0x01
+#define I2C_STAT_TIP		0x02
+#define I2C_STAT_ARBLOST	0x20
+#define I2C_STAT_BUSY		0x40
+#define I2C_STAT_NACK		0x80
+
+#define KEMPLD_I2C_CMD		0x0f
+#define I2C_CMD_START		0x91
+#define I2C_CMD_STOP		0x41
+#define I2C_CMD_READ		0x21
+#define I2C_CMD_WRITE		0x11
+#define I2C_CMD_READ_ACK	0x21
+#define I2C_CMD_READ_NACK	0x29
+#define I2C_CMD_IACK		0x01
+
+#define KEMPLD_I2C_FREQ_MAX	2700	/* 2.7 mHz */
+#define KEMPLD_I2C_FREQ_STD	100	/* 100 kHz */
+
+enum {
+	STATE_DONE = 0,
+	STATE_INIT,
+	STATE_ADDR,
+	STATE_ADDR10,
+	STATE_START,
+	STATE_WRITE,
+	STATE_READ,
+	STATE_ERROR,
+};
+
+struct kempld_i2c_data {
+	struct device			*dev;
+	struct kempld_device_data	*pld;
+	struct i2c_adapter		adap;
+	struct i2c_msg			*msg;
+	int				pos;
+	int				nmsgs;
+	int				state;
+	bool				was_active;
+};
+
+static unsigned int bus_frequency = KEMPLD_I2C_FREQ_STD;
+module_param(bus_frequency, uint, 0);
+MODULE_PARM_DESC(bus_frequency, "Set I2C bus frequency in kHz (default="
+				__MODULE_STRING(KEMPLD_I2C_FREQ_STD)")");
+
+static int i2c_bus = -1;
+module_param(i2c_bus, int, 0);
+MODULE_PARM_DESC(i2c_bus, "Set I2C bus number (default=-1 for dynamic assignment)");
+
+static bool i2c_gpio_mux;
+module_param(i2c_gpio_mux, bool, 0);
+MODULE_PARM_DESC(i2c_gpio_mux, "Enable I2C port on GPIO out (default=false)");
+
+/*
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static int kempld_i2c_process(struct kempld_i2c_data *i2c)
+{
+	struct kempld_device_data *pld = i2c->pld;
+	u8 stat = kempld_read8(pld, KEMPLD_I2C_STAT);
+	struct i2c_msg *msg = i2c->msg;
+	u8 addr;
+
+	/* Ready? */
+	if (stat & I2C_STAT_TIP)
+		return -EBUSY;
+
+	if (i2c->state == STATE_DONE || i2c->state == STATE_ERROR) {
+		/* Stop has been sent */
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_IACK);
+		if (i2c->state == STATE_ERROR)
+			return -EIO;
+		return 0;
+	}
+
+	/* Error? */
+	if (stat & I2C_STAT_ARBLOST) {
+		i2c->state = STATE_ERROR;
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_STOP);
+		return -EAGAIN;
+	}
+
+	if (i2c->state == STATE_INIT) {
+		if (stat & I2C_STAT_BUSY)
+			return -EBUSY;
+
+		i2c->state = STATE_ADDR;
+	}
+
+	if (i2c->state == STATE_ADDR) {
+		/* 10 bit address? */
+		if (i2c->msg->flags & I2C_M_TEN) {
+			addr = 0xf0 | ((i2c->msg->addr >> 7) & 0x6);
+			i2c->state = STATE_ADDR10;
+		} else {
+			addr = (i2c->msg->addr << 1);
+			i2c->state = STATE_START;
+		}
+
+		/* Set read bit if necessary */
+		addr |= (i2c->msg->flags & I2C_M_RD) ? 1 : 0;
+
+		kempld_write8(pld, KEMPLD_I2C_DATA, addr);
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_START);
+
+		return 0;
+	}
+
+	/* Second part of 10 bit addressing */
+	if (i2c->state == STATE_ADDR10) {
+		kempld_write8(pld, KEMPLD_I2C_DATA, i2c->msg->addr & 0xff);
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_WRITE);
+
+		i2c->state = STATE_START;
+		return 0;
+	}
+
+	if (i2c->state == STATE_START || i2c->state == STATE_WRITE) {
+		i2c->state = (msg->flags & I2C_M_RD) ? STATE_READ : STATE_WRITE;
+
+		if (stat & I2C_STAT_NACK) {
+			i2c->state = STATE_ERROR;
+			kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_STOP);
+			return -ENXIO;
+		}
+	} else {
+		msg->buf[i2c->pos++] = kempld_read8(pld, KEMPLD_I2C_DATA);
+	}
+
+	if (i2c->pos >= msg->len) {
+		i2c->nmsgs--;
+		i2c->msg++;
+		i2c->pos = 0;
+		msg = i2c->msg;
+
+		if (i2c->nmsgs) {
+			if (!(msg->flags & I2C_M_NOSTART)) {
+				i2c->state = STATE_ADDR;
+				return 0;
+			} else {
+				i2c->state = (msg->flags & I2C_M_RD)
+					? STATE_READ : STATE_WRITE;
+			}
+		} else {
+			i2c->state = STATE_DONE;
+			kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_STOP);
+			return 0;
+		}
+	}
+
+	if (i2c->state == STATE_READ) {
+		kempld_write8(pld, KEMPLD_I2C_CMD, i2c->pos == (msg->len - 1) ?
+			      I2C_CMD_READ_NACK : I2C_CMD_READ_ACK);
+	} else {
+		kempld_write8(pld, KEMPLD_I2C_DATA, msg->buf[i2c->pos++]);
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_WRITE);
+	}
+
+	return 0;
+}
+
+static int kempld_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+				int num)
+{
+	struct kempld_i2c_data *i2c = i2c_get_adapdata(adap);
+	struct kempld_device_data *pld = i2c->pld;
+	unsigned long timeout = jiffies + HZ;
+	int ret;
+
+	i2c->msg = msgs;
+	i2c->pos = 0;
+	i2c->nmsgs = num;
+	i2c->state = STATE_INIT;
+
+	/* Handle the transfer */
+	while (time_before(jiffies, timeout)) {
+		kempld_get_mutex(pld);
+		ret = kempld_i2c_process(i2c);
+		kempld_release_mutex(pld);
+
+		if (i2c->state == STATE_DONE || i2c->state == STATE_ERROR)
+			return (i2c->state == STATE_DONE) ? num : ret;
+
+		if (ret == 0)
+			timeout = jiffies + HZ;
+
+		usleep_range(5, 15);
+	}
+
+	i2c->state = STATE_ERROR;
+
+	return -ETIMEDOUT;
+}
+
+/*
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static void kempld_i2c_device_init(struct kempld_i2c_data *i2c)
+{
+	struct kempld_device_data *pld = i2c->pld;
+	u16 prescale_corr;
+	long prescale;
+	u8 ctrl;
+	u8 stat;
+	u8 cfg;
+
+	/* Make sure the device is disabled */
+	ctrl = kempld_read8(pld, KEMPLD_I2C_CTRL);
+	ctrl &= ~(I2C_CTRL_EN | I2C_CTRL_IEN);
+	kempld_write8(pld, KEMPLD_I2C_CTRL, ctrl);
+
+	if (bus_frequency > KEMPLD_I2C_FREQ_MAX)
+		bus_frequency = KEMPLD_I2C_FREQ_MAX;
+
+	if (pld->info.spec_major == 1)
+		prescale = pld->pld_clock / bus_frequency * 5 - 1000;
+	else
+		prescale = pld->pld_clock / bus_frequency * 4 - 3000;
+
+	if (prescale < 0)
+		prescale = 0;
+
+	/* Round to the best matching value */
+	prescale_corr = prescale / 1000;
+	if (prescale % 1000 >= 500)
+		prescale_corr++;
+
+	kempld_write8(pld, KEMPLD_I2C_PRELOW, prescale_corr & 0xff);
+	kempld_write8(pld, KEMPLD_I2C_PREHIGH, prescale_corr >> 8);
+
+	/* Activate I2C bus output on GPIO pins */
+	cfg = kempld_read8(pld, KEMPLD_CFG);
+	if (i2c_gpio_mux)
+		cfg |= KEMPLD_CFG_GPIO_I2C_MUX;
+	else
+		cfg &= ~KEMPLD_CFG_GPIO_I2C_MUX;
+	kempld_write8(pld, KEMPLD_CFG, cfg);
+
+	/* Enable the device */
+	kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_IACK);
+	ctrl |= I2C_CTRL_EN;
+	kempld_write8(pld, KEMPLD_I2C_CTRL, ctrl);
+
+	stat = kempld_read8(pld, KEMPLD_I2C_STAT);
+	if (stat & I2C_STAT_BUSY)
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_STOP);
+}
+
+static u32 kempld_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm kempld_i2c_algorithm = {
+	.master_xfer	= kempld_i2c_xfer,
+	.functionality	= kempld_i2c_func,
+};
+
+static struct i2c_adapter kempld_i2c_adapter = {
+	.owner		= THIS_MODULE,
+	.name		= "i2c-kempld",
+	.class		= I2C_CLASS_HWMON | I2C_CLASS_SPD,
+	.algo		= &kempld_i2c_algorithm,
+};
+
+static int kempld_i2c_probe(struct platform_device *pdev)
+{
+	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
+	struct kempld_i2c_data *i2c;
+	int ret;
+	u8 ctrl;
+
+	i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
+	if (!i2c)
+		return -ENOMEM;
+
+	i2c->pld = pld;
+	i2c->dev = &pdev->dev;
+	i2c->adap = kempld_i2c_adapter;
+	i2c->adap.dev.parent = i2c->dev;
+	i2c_set_adapdata(&i2c->adap, i2c);
+	platform_set_drvdata(pdev, i2c);
+
+	kempld_get_mutex(pld);
+	ctrl = kempld_read8(pld, KEMPLD_I2C_CTRL);
+
+	if (ctrl & I2C_CTRL_EN)
+		i2c->was_active = true;
+
+	kempld_i2c_device_init(i2c);
+	kempld_release_mutex(pld);
+
+	/* Add I2C adapter to I2C tree */
+	if (i2c_bus >= -1)
+		i2c->adap.nr = i2c_bus;
+	ret = i2c_add_numbered_adapter(&i2c->adap);
+	if (ret)
+		return ret;
+
+	dev_info(i2c->dev, "I2C bus initialized at %dkHz\n",
+		 bus_frequency);
+
+	return 0;
+}
+
+static int kempld_i2c_remove(struct platform_device *pdev)
+{
+	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = i2c->pld;
+	u8 ctrl;
+
+	kempld_get_mutex(pld);
+	/*
+	 * Disable I2C logic if it was not activated before the
+	 * driver loaded
+	 */
+	if (!i2c->was_active) {
+		ctrl = kempld_read8(pld, KEMPLD_I2C_CTRL);
+		ctrl &= ~I2C_CTRL_EN;
+		kempld_write8(pld, KEMPLD_I2C_CTRL, ctrl);
+	}
+	kempld_release_mutex(pld);
+
+	i2c_del_adapter(&i2c->adap);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int kempld_i2c_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = i2c->pld;
+	u8 ctrl;
+
+	kempld_get_mutex(pld);
+	ctrl = kempld_read8(pld, KEMPLD_I2C_CTRL);
+	ctrl &= ~I2C_CTRL_EN;
+	kempld_write8(pld, KEMPLD_I2C_CTRL, ctrl);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_i2c_resume(struct platform_device *pdev)
+{
+	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = i2c->pld;
+
+	kempld_get_mutex(pld);
+	kempld_i2c_device_init(i2c);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+#else
+#define kempld_i2c_suspend	NULL
+#define kempld_i2c_resume	NULL
+#endif
+
+static struct platform_driver kempld_i2c_driver = {
+	.driver = {
+		.name = "kempld-i2c",
+		.owner = THIS_MODULE,
+	},
+	.probe		= kempld_i2c_probe,
+	.remove		= kempld_i2c_remove,
+	.suspend	= kempld_i2c_suspend,
+	.resume		= kempld_i2c_resume,
+};
+
+module_platform_driver(kempld_i2c_driver);
+
+MODULE_DESCRIPTION("KEM PLD I2C Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld_i2c");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 3/4] gpio: Kontron PLD gpio driver
  2013-06-18 21:04 ` [PATCH v2 0/4] Kontron PLD drivers Kevin Strasser
  2013-06-18 21:04   ` [PATCH v2 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
  2013-06-18 21:04   ` [PATCH v2 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
@ 2013-06-18 21:04   ` Kevin Strasser
  2013-06-19  8:36     ` Linus Walleij
  2013-06-18 21:04   ` [PATCH v2 4/4] watchdog: Kontron PLD watchdog timer driver Kevin Strasser
  3 siblings, 1 reply; 48+ messages in thread
From: Kevin Strasser @ 2013-06-18 21:04 UTC (permalink / raw)
  To: linux-kernel
  Cc: Kevin Strasser, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, Michael Brunner

Add gpio support for the on-board PLD found on some Kontron embedded modules.

Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
Acked-by: Darren Hart <dvhart@linux.intel.com>
---
 drivers/gpio/Kconfig       |   12 +++
 drivers/gpio/Makefile      |    1 +
 drivers/gpio/gpio-kempld.c |  225 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 238 insertions(+)
 create mode 100644 drivers/gpio/gpio-kempld.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 93aaadf..e94b266 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -658,6 +658,18 @@ config GPIO_UCB1400
 	  This enables support for the Philips UCB1400 GPIO pins.
 	  The UCB1400 is an AC97 audio codec.
 
+comment "LPC GPIO expanders:"
+
+config GPIO_KEMPLD
+	tristate "Kontron ETX / COMexpress GPIO"
+	depends on MFD_KEMPLD
+	help
+	  This enables support for the PLD GPIO interface on some Kontron ETX
+	  and COMexpress (ETXexpress) modules.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called gpio-kempld.
+
 comment "MODULbus GPIO expanders:"
 
 config GPIO_JANZ_TTL
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 22e07bc..758c348 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_GPIO_GE_FPGA)	+= gpio-ge.o
 obj-$(CONFIG_GPIO_ICH)		+= gpio-ich.o
 obj-$(CONFIG_GPIO_IT8761E)	+= gpio-it8761e.o
 obj-$(CONFIG_GPIO_JANZ_TTL)	+= gpio-janz-ttl.o
+obj-$(CONFIG_GPIO_KEMPLD)	+= gpio-kempld.o
 obj-$(CONFIG_ARCH_KS8695)	+= gpio-ks8695.o
 obj-$(CONFIG_GPIO_LANGWELL)	+= gpio-langwell.o
 obj-$(CONFIG_ARCH_LPC32XX)	+= gpio-lpc32xx.o
diff --git a/drivers/gpio/gpio-kempld.c b/drivers/gpio/gpio-kempld.c
new file mode 100644
index 0000000..1bdc3a2
--- /dev/null
+++ b/drivers/gpio/gpio-kempld.c
@@ -0,0 +1,225 @@
+/*
+ * Kontron PLD GPIO driver
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/mfd/kempld.h>
+
+#define KEMPLD_GPIO_MAX_NUM		16
+#define KEMPLD_GPIO_MASK(x)		(1 << ((x) % 8))
+#define KEMPLD_GPIO_DIR_NUM(x)		(0x40 + (x) / 8)
+#define KEMPLD_GPIO_LVL_NUM(x)		(0x42 + (x) / 8)
+#define KEMPLD_GPIO_EVT_LVL_EDGE	0x46
+#define KEMPLD_GPIO_IEN			0x4A
+
+struct kempld_gpio_data {
+	struct gpio_chip		chip;
+	struct kempld_device_data	*pld;
+};
+
+/*
+ * Set or clear GPIO bit
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static void kempld_gpio_bitop(struct kempld_device_data *pld,
+			      u8 reg, u8 bit, u8 val)
+{
+	u8 status;
+
+	status = kempld_read8(pld, reg);
+	if (val)
+		status |= (1 << bit);
+	else
+		status &= ~(1 << bit);
+	kempld_write8(pld, reg, status);
+}
+
+static int kempld_gpio_get_bit(struct kempld_device_data *pld, u8 reg, u8 bit)
+{
+	u8 status;
+
+	kempld_get_mutex(pld);
+	status = kempld_read8(pld, reg);
+	kempld_release_mutex(pld);
+
+	return !!(status & (1 << bit));
+}
+
+static int kempld_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+
+	return kempld_gpio_get_bit(pld, KEMPLD_GPIO_LVL_NUM(offset),
+				   KEMPLD_GPIO_MASK(offset));
+}
+
+static void kempld_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+
+	kempld_get_mutex(pld);
+	kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL_NUM(offset),
+			  KEMPLD_GPIO_MASK(offset), value);
+	kempld_release_mutex(pld);
+}
+
+static int kempld_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+
+	kempld_get_mutex(pld);
+	kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR_NUM(offset),
+			  KEMPLD_GPIO_MASK(offset), 0);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+					int value)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+
+	kempld_get_mutex(pld);
+	kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL_NUM(offset),
+			  KEMPLD_GPIO_MASK(offset), value);
+	kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR_NUM(offset),
+			  KEMPLD_GPIO_MASK(offset), 1);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+
+	return kempld_gpio_get_bit(pld, KEMPLD_GPIO_DIR_NUM(offset),
+				   KEMPLD_GPIO_MASK(offset));
+}
+
+static int kempld_gpio_pincount(struct kempld_device_data *pld)
+{
+	u16 evt, evt_back;
+
+	kempld_get_mutex(pld);
+
+	/* Backup event register as it might be already initialized */
+	evt_back = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE);
+	/* Clear event register */
+	kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, 0x0000);
+	/* Read back event register */
+	evt = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE);
+	/* Restore event register */
+	kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, evt_back);
+
+	kempld_release_mutex(pld);
+
+	return evt ? __ffs(evt) : 16;
+}
+
+static int kempld_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct kempld_device_data *pld = dev_get_drvdata(dev->parent);
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+	struct kempld_gpio_data *gpio;
+	struct gpio_chip *chip;
+	int ret;
+
+	if (pld->info.spec_major < 2) {
+		dev_err(dev,
+			"Driver only supports GPIO devices compatible to PLD spec. rev. 2.0 or higher\n");
+		return -ENODEV;
+	}
+
+	gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+	if (gpio == NULL)
+		return -ENOMEM;
+
+	gpio->pld = pld;
+
+	platform_set_drvdata(pdev, gpio);
+
+	chip = &gpio->chip;
+	chip->label = "gpio-kempld";
+	chip->owner = THIS_MODULE;
+	chip->dev = dev;
+	chip->can_sleep = 1;
+	if (pdata && pdata->gpio_base)
+		chip->base = pdata->gpio_base;
+	else
+		chip->base = -1;
+	chip->direction_input = kempld_gpio_direction_input;
+	chip->direction_output = kempld_gpio_direction_output;
+	chip->get_direction = kempld_gpio_get_direction;
+	chip->get = kempld_gpio_get;
+	chip->set = kempld_gpio_set;
+	chip->ngpio = kempld_gpio_pincount(pld);
+	if (chip->ngpio == 0) {
+		dev_err(dev, "No GPIO pins detected\n");
+		return -ENODEV;
+	}
+
+	ret = gpiochip_add(chip);
+	if (ret) {
+		dev_err(dev, "Could not register GPIO chip\n");
+		return ret;
+	}
+
+	dev_info(dev, "GPIO functionality initialized with %d pins\n",
+		 chip->ngpio);
+
+	return 0;
+}
+
+static int kempld_gpio_remove(struct platform_device *pdev)
+{
+	struct kempld_gpio_data *gpio = platform_get_drvdata(pdev);
+
+	return gpiochip_remove(&gpio->chip);
+}
+
+static struct platform_driver kempld_gpio_driver = {
+	.driver = {
+		.name = "gpio-kempld",
+		.owner = THIS_MODULE,
+	},
+	.probe		= kempld_gpio_probe,
+	.remove		= kempld_gpio_remove,
+};
+
+module_platform_driver(kempld_gpio_driver);
+
+MODULE_DESCRIPTION("KEM PLD GPIO Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-kempld");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 4/4] watchdog: Kontron PLD watchdog timer driver
  2013-06-18 21:04 ` [PATCH v2 0/4] Kontron PLD drivers Kevin Strasser
                     ` (2 preceding siblings ...)
  2013-06-18 21:04   ` [PATCH v2 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
@ 2013-06-18 21:04   ` Kevin Strasser
  3 siblings, 0 replies; 48+ messages in thread
From: Kevin Strasser @ 2013-06-18 21:04 UTC (permalink / raw)
  To: linux-kernel
  Cc: Kevin Strasser, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, linux-watchdog, Michael Brunner

Add watchdog timer support for the on-board PLD found on some Kontron embedded
modules.

Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Darren Hart <dvhart@linux.intel.com>
---
 drivers/watchdog/Kconfig      |   11 +
 drivers/watchdog/Makefile     |    1 +
 drivers/watchdog/kempld_wdt.c |  580 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 592 insertions(+)
 create mode 100644 drivers/watchdog/kempld_wdt.c

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index e89fc31..7460d34 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -687,6 +687,17 @@ config HP_WATCHDOG
 	  To compile this driver as a module, choose M here: the module will be
 	  called hpwdt.
 
+config KEMPLD_WDT
+	tristate "Kontron COM Watchdog Timer"
+	depends on MFD_KEMPLD
+	select WATCHDOG_CORE
+	help
+	  Support for the PLD watchdog on some Kontron ETX and COMexpress
+	  (ETXexpress) modules
+
+	  This driver can also be built as a module. If so, the module will be
+	  called kempld_wdt.
+
 config HPWDT_NMI_DECODING
 	bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer"
 	depends on HP_WATCHDOG
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index a300b94..ec26899 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -90,6 +90,7 @@ endif
 obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
 obj-$(CONFIG_IT87_WDT) += it87_wdt.o
 obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
 obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
 obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
 obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
diff --git a/drivers/watchdog/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c
new file mode 100644
index 0000000..96e9a48
--- /dev/null
+++ b/drivers/watchdog/kempld_wdt.c
@@ -0,0 +1,580 @@
+/*
+ * Kontron PLD watchdog driver
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Note: From the PLD watchdog point of view timeout and pretimeout are
+ *       defined differently than in the kernel.
+ *       First the pretimeout stage runs out before the timeout stage gets
+ *       active.
+ *
+ * Kernel/API:                     P-----| pretimeout
+ *               |-----------------------T timeout
+ * Watchdog:     |-----------------P       pretimeout_stage
+ *                                 |-----T timeout_stage
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/kempld.h>
+
+#define KEMPLD_WDT_STAGE_TIMEOUT(x)	(0x1b + (x) * 4)
+#define KEMPLD_WDT_STAGE_CFG(x)		(0x18 + (x))
+#define STAGE_CFG_GET_PRESCALER(x)	(((x) & 0x30) >> 4)
+#define STAGE_CFG_SET_PRESCALER(x)	(((x) & 0x30) << 4)
+#define STAGE_CFG_PRESCALER_MASK	0x30
+#define STAGE_CFG_ACTION_MASK		0x7
+#define STAGE_CFG_ASSERT		(1 << 3)
+
+#define KEMPLD_WDT_MAX_STAGES		2
+#define KEMPLD_WDT_KICK			0x16
+#define KEMPLD_WDT_CFG			0x17
+#define KEMPLD_WDT_CFG_ENABLE		0x10
+#define KEMPLD_WDT_CFG_ENABLE_LOCK	0x8
+#define KEMPLD_WDT_CFG_GLOBAL_LOCK	0x80
+
+enum {
+	ACTION_NONE = 0,
+	ACTION_RESET,
+	ACTION_NMI,
+	ACTION_SMI,
+	ACTION_SCI,
+	ACTION_DELAY,
+};
+
+enum {
+	STAGE_TIMEOUT = 0,
+	STAGE_PRETIMEOUT,
+};
+
+enum {
+	PRESCALER_21 = 0,
+	PRESCALER_17,
+	PRESCALER_12,
+};
+
+const u32 kempld_prescaler[] = {
+	[PRESCALER_21] = (1 << 21) - 1,
+	[PRESCALER_17] = (1 << 17) - 1,
+	[PRESCALER_12] = (1 << 12) - 1,
+	0,
+};
+
+struct kempld_wdt_stage {
+	unsigned int	id;
+	u32		mask;
+};
+
+struct kempld_wdt_data {
+	struct kempld_device_data	*pld;
+	struct watchdog_device		wdd;
+	unsigned int			pretimeout;
+	struct kempld_wdt_stage		stage[KEMPLD_WDT_MAX_STAGES];
+#ifdef CONFIG_PM
+	u8				pm_status_store;
+#endif
+};
+
+#define DEFAULT_TIMEOUT		30 /* seconds */
+#define DEFAULT_PRETIMEOUT	0
+
+static unsigned int timeout = DEFAULT_TIMEOUT;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+	"Watchdog timeout in seconds. (>=0, default="
+	__MODULE_STRING(DEFAULT_TIMEOUT) ")");
+
+static unsigned int pretimeout = DEFAULT_PRETIMEOUT;
+module_param(pretimeout, uint, 0);
+MODULE_PARM_DESC(pretimeout,
+	"Watchdog pretimeout in seconds. (>=0, default="
+	__MODULE_STRING(DEFAULT_PRETIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+	"Watchdog cannot be stopped once started (default="
+	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static int kempld_wdt_set_stage_action(struct kempld_wdt_data *wdt_data,
+					struct kempld_wdt_stage *stage,
+					u8 action)
+{
+	struct kempld_device_data *pld = wdt_data->pld;
+	u8 stage_cfg;
+
+	if (!stage || !stage->mask)
+		return -EINVAL;
+
+	kempld_get_mutex(pld);
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
+	stage_cfg &= ~STAGE_CFG_ACTION_MASK;
+	stage_cfg |= (action & STAGE_CFG_ACTION_MASK);
+
+	if (action == ACTION_RESET)
+		stage_cfg |= STAGE_CFG_ASSERT;
+	else
+		stage_cfg &= ~STAGE_CFG_ASSERT;
+
+	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_wdt_set_stage_timeout(struct kempld_wdt_data *wdt_data,
+					struct kempld_wdt_stage *stage,
+					unsigned int timeout)
+{
+	struct kempld_device_data *pld = wdt_data->pld;
+	u32 prescaler = kempld_prescaler[PRESCALER_21];
+	u64 stage_timeout64;
+	u32 stage_timeout;
+	u32 remainder;
+	u8 stage_cfg;
+
+	if (!stage)
+		return -EINVAL;
+
+	stage_timeout64 = (u64)timeout * pld->pld_clock;
+	remainder = do_div(stage_timeout64, prescaler);
+	if (remainder)
+		stage_timeout64++;
+
+	if (stage_timeout64 > stage->mask)
+		return -EINVAL;
+
+	stage_timeout = stage_timeout64 & stage->mask;
+
+	kempld_get_mutex(pld);
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
+	stage_cfg &= ~STAGE_CFG_PRESCALER_MASK;
+	stage_cfg |= STAGE_CFG_SET_PRESCALER(prescaler);
+	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
+	kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id),
+			stage_timeout);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+/*
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static unsigned int kempld_wdt_get_timeout(struct kempld_wdt_data *wdt_data,
+						struct kempld_wdt_stage *stage)
+{
+	struct kempld_device_data *pld = wdt_data->pld;
+	unsigned int timeout;
+	u64 stage_timeout;
+	u32 prescaler;
+	u32 remainder;
+	u8 stage_cfg;
+
+	if (!stage->mask)
+		return 0;
+
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
+	stage_timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id));
+	prescaler = kempld_prescaler[STAGE_CFG_GET_PRESCALER(stage_cfg)];
+
+	stage_timeout = (stage_timeout & stage->mask) * prescaler;
+	remainder = do_div(stage_timeout, pld->pld_clock);
+	if (remainder)
+		stage_timeout++;
+
+	timeout = stage_timeout;
+	WARN_ON_ONCE(timeout != stage_timeout);
+
+	return timeout;
+}
+
+static int kempld_wdt_set_timeout(struct watchdog_device *wdd,
+					unsigned int timeout)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_wdt_stage *pretimeout_stage;
+	struct kempld_wdt_stage *timeout_stage;
+	int ret;
+
+	timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
+	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+
+	if (pretimeout_stage->mask && wdt_data->pretimeout > 0)
+		timeout = wdt_data->pretimeout;
+
+	ret = kempld_wdt_set_stage_action(wdt_data, timeout_stage,
+						ACTION_RESET);
+	if (ret)
+		return ret;
+	ret = kempld_wdt_set_stage_timeout(wdt_data, timeout_stage,
+						timeout);
+	if (ret)
+		return ret;
+
+	wdd->timeout = timeout;
+	return 0;
+}
+
+static int kempld_wdt_set_pretimeout(struct watchdog_device *wdd,
+					unsigned int pretimeout)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_wdt_stage *pretimeout_stage;
+	u8 action = ACTION_NONE;
+	int ret;
+
+	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+
+	if (!pretimeout_stage->mask)
+		return -ENXIO;
+
+	if (pretimeout > wdd->timeout)
+		return -EINVAL;
+
+	if (pretimeout > 0)
+		action = ACTION_NMI;
+
+	ret = kempld_wdt_set_stage_action(wdt_data, pretimeout_stage,
+						action);
+	if (ret)
+		return ret;
+	ret = kempld_wdt_set_stage_timeout(wdt_data, pretimeout_stage,
+						wdd->timeout - pretimeout);
+	if (ret)
+		return ret;
+
+	wdt_data->pretimeout = pretimeout;
+	return 0;
+}
+
+static void kempld_wdt_update_timeouts(struct kempld_wdt_data *wdt_data)
+{
+	struct kempld_device_data *pld = wdt_data->pld;
+	struct kempld_wdt_stage *pretimeout_stage;
+	struct kempld_wdt_stage *timeout_stage;
+	unsigned int pretimeout, timeout;
+
+	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+	timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
+
+	kempld_get_mutex(pld);
+	pretimeout = kempld_wdt_get_timeout(wdt_data, pretimeout_stage);
+	timeout = kempld_wdt_get_timeout(wdt_data, timeout_stage);
+	kempld_release_mutex(pld);
+
+	if (pretimeout)
+		wdt_data->pretimeout = timeout;
+	else
+		wdt_data->pretimeout = 0;
+
+	wdt_data->wdd.timeout = pretimeout + timeout;
+}
+
+static int kempld_wdt_start(struct watchdog_device *wdd)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_device_data *pld = wdt_data->pld;
+	u8 status;
+	int ret;
+
+	ret = kempld_wdt_set_timeout(wdd, wdd->timeout);
+	if (ret)
+		return ret;
+
+	kempld_get_mutex(pld);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	status |= KEMPLD_WDT_CFG_ENABLE;
+	kempld_write8(pld, KEMPLD_WDT_CFG, status);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	/* Check if the watchdog was enabled */
+	if (!(status & KEMPLD_WDT_CFG_ENABLE))
+		return -EACCES;
+
+	return 0;
+}
+
+static int kempld_wdt_stop(struct watchdog_device *wdd)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_device_data *pld = wdt_data->pld;
+	u8 status;
+
+	kempld_get_mutex(pld);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	status &= ~KEMPLD_WDT_CFG_ENABLE;
+	kempld_write8(pld, KEMPLD_WDT_CFG, status);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	/* Check if the watchdog was disabled */
+	if (status & KEMPLD_WDT_CFG_ENABLE)
+		return -EACCES;
+
+	return 0;
+}
+
+static int kempld_wdt_keepalive(struct watchdog_device *wdd)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_device_data *pld = wdt_data->pld;
+
+	kempld_get_mutex(pld);
+	kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static long kempld_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd,
+				unsigned long arg)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	void __user *argp = (void __user *)arg;
+	int ret = -ENOIOCTLCMD;
+	int __user *p = argp;
+	int new_value;
+
+	switch (cmd) {
+	case WDIOC_SETPRETIMEOUT:
+		if (get_user(new_value, p))
+			return -EFAULT;
+		ret = kempld_wdt_set_pretimeout(wdd, new_value);
+		if (ret)
+			return ret;
+		ret = kempld_wdt_keepalive(wdd);
+		break;
+	case WDIOC_GETPRETIMEOUT:
+		ret = put_user(wdt_data->pretimeout, (int *)arg);
+		break;
+	}
+
+	return ret;
+}
+
+static int kempld_wdt_probe_stages(struct watchdog_device *wdd)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_device_data *pld = wdt_data->pld;
+	struct kempld_wdt_stage *pretimeout_stage;
+	struct kempld_wdt_stage *timeout_stage;
+	u8 index, data, data_orig;
+	u32 mask;
+	int i, j;
+
+	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+	timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
+
+	pretimeout_stage->mask = 0;
+	timeout_stage->mask = 0;
+
+	for (i = 0; i < 3; i++) {
+		index = KEMPLD_WDT_STAGE_TIMEOUT(i);
+		mask = 0;
+
+		kempld_get_mutex(pld);
+		/* Probe each byte individually. */
+		for (j = 0; j < 4; j++) {
+			data_orig = kempld_read8(pld, index + j);
+			kempld_write8(pld, index + j, 0x00);
+			data = kempld_read8(pld, index + j);
+			/* A failed write means this byte is reserved */
+			if (data != 0x00)
+				break;
+			kempld_write8(pld, index + j, data_orig);
+			mask |= 0xff << (j * 8);
+		}
+		kempld_release_mutex(pld);
+
+		/* Assign available stages to timeout and pretimeout */
+		if (!timeout_stage->mask) {
+			timeout_stage->mask = mask;
+			timeout_stage->id = i;
+		} else {
+			if (pld->feature_mask & KEMPLD_FEATURE_BIT_NMI) {
+				pretimeout_stage->mask = timeout_stage->mask;
+				timeout_stage->mask = mask;
+				pretimeout_stage->id = timeout_stage->id;
+				timeout_stage->id = i;
+			}
+			break;
+		}
+	}
+
+	if (!timeout_stage->mask)
+		return -ENODEV;
+
+	return 0;
+}
+
+static struct watchdog_info kempld_wdt_info = {
+	.identity	= "KEMPLD Watchdog",
+	.options	= WDIOF_SETTIMEOUT |
+			WDIOF_KEEPALIVEPING |
+			WDIOF_MAGICCLOSE |
+			WDIOF_PRETIMEOUT
+};
+
+static struct watchdog_ops kempld_wdt_ops = {
+	.owner		= THIS_MODULE,
+	.start		= kempld_wdt_start,
+	.stop		= kempld_wdt_stop,
+	.ping		= kempld_wdt_keepalive,
+	.set_timeout	= kempld_wdt_set_timeout,
+	.ioctl		= kempld_wdt_ioctl,
+};
+
+static int kempld_wdt_probe(struct platform_device *pdev)
+{
+	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
+	struct kempld_wdt_data *wdt_data;
+	struct device *dev = &pdev->dev;
+	struct watchdog_device *wdd;
+	u8 status;
+	int ret = 0;
+
+	wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL);
+	if (!wdt_data)
+		return -ENOMEM;
+
+	wdt_data->pld = pld;
+	wdd = &wdt_data->wdd;
+	wdd->parent = dev;
+
+	kempld_get_mutex(pld);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	/* Enable nowayout if watchdog is already locked */
+	if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
+			KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
+		if (!nowayout)
+			dev_warn(dev,
+				"Forcing nowayout - watchdog lock enabled!\n");
+		nowayout = true;
+	}
+
+	wdd->info = &kempld_wdt_info;
+	wdd->ops = &kempld_wdt_ops;
+
+	watchdog_set_drvdata(wdd, wdt_data);
+	watchdog_set_nowayout(wdd, nowayout);
+
+	ret = kempld_wdt_probe_stages(wdd);
+	if (ret)
+		return ret;
+
+	kempld_wdt_set_timeout(wdd, timeout);
+	kempld_wdt_set_pretimeout(wdd, pretimeout);
+
+	/* Check if watchdog is already enabled */
+	if (status & KEMPLD_WDT_CFG_ENABLE) {
+		/* Get current watchdog settings */
+		kempld_wdt_update_timeouts(wdt_data);
+		dev_info(dev, "Watchdog was already enabled\n");
+	}
+
+	platform_set_drvdata(pdev, wdt_data);
+	ret = watchdog_register_device(wdd);
+	if (ret)
+		return ret;
+
+	dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout);
+
+	return 0;
+}
+
+static void kempld_wdt_shutdown(struct platform_device *pdev)
+{
+	struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
+
+	kempld_wdt_stop(&wdt_data->wdd);
+}
+
+static int kempld_wdt_remove(struct platform_device *pdev)
+{
+	struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
+	struct watchdog_device *wdd = &wdt_data->wdd;
+	int ret = 0;
+
+	if (!nowayout)
+		ret = kempld_wdt_stop(wdd);
+	watchdog_unregister_device(wdd);
+
+	return ret;
+}
+
+#ifdef CONFIG_PM
+/* Disable watchdog if it is active during suspend */
+static int kempld_wdt_suspend(struct platform_device *pdev,
+				pm_message_t message)
+{
+	struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = wdt_data->pld;
+
+	kempld_get_mutex(pld);
+	wdt_data->pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	kempld_wdt_update_timeouts(wdt_data);
+
+	if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
+		return kempld_wdt_stop(pdev);
+
+	return 0;
+}
+
+/* Enable watchdog and configure it if necessary */
+static int kempld_wdt_resume(struct platform_device *pdev)
+{
+	struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
+	struct watchdog_device *wdd = &wdt_data->wdd;
+
+	/*
+	 * If watchdog was stopped before suspend be sure it gets disabled
+	 * again, for the case BIOS has enabled it during resume
+	 */
+	if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
+		return kempld_wdt_start(wdd);
+	else
+		return kempld_wdt_stop(wdd);
+}
+#else
+#define kempld_wdt_suspend	NULL
+#define kempld_wdt_resume	NULL
+#endif
+
+static struct platform_driver kempld_wdt_driver = {
+	.driver		= {
+		.name	= "kempld-wdt",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= kempld_wdt_probe,
+	.remove		= kempld_wdt_remove,
+	.shutdown	= kempld_wdt_shutdown,
+	.suspend	= kempld_wdt_suspend,
+	.resume		= kempld_wdt_resume,
+};
+
+module_platform_driver(kempld_wdt_driver);
+
+MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 3/4] gpio: Kontron PLD gpio driver
  2013-06-18 21:04   ` [PATCH v2 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
@ 2013-06-19  8:36     ` Linus Walleij
  2013-06-27 22:14       ` Kevin Strasser
  0 siblings, 1 reply; 48+ messages in thread
From: Linus Walleij @ 2013-06-19  8:36 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Wim Van Sebroeck

On Tue, Jun 18, 2013 at 11:04 PM, Kevin Strasser
<kevin.strasser@linux.intel.com> wrote:

> Add gpio support for the on-board PLD found on some Kontron embedded modules.
>
> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> Acked-by: Darren Hart <dvhart@linux.intel.com>

This is looking good.
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 1/4] mfd: Kontron PLD mfd driver
  2013-06-18 21:04   ` [PATCH v2 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
@ 2013-06-19  8:40     ` Linus Walleij
  2013-06-19  9:11       ` Samuel Ortiz
  2013-06-19  9:12     ` Thomas Gleixner
  1 sibling, 1 reply; 48+ messages in thread
From: Linus Walleij @ 2013-06-19  8:40 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Wim Van Sebroeck, Mark Brown

On Tue, Jun 18, 2013 at 11:04 PM, Kevin Strasser
<kevin.strasser@linux.intel.com> wrote:
> Add core MFD driver for the on-board PLD found on some Kontron embedded
> modules. The PLD device may provide functions like watchdog, GPIO, UART
> and I2C bus.
>
> The following modules are supported:
>         * COMe-bIP#
>         * COMe-bPC2 (ETXexpress-PC)
>         * COMe-bSC# (ETXexpress-SC T#)
>         * COMe-cCT6
>         * COMe-cDC2 (microETXexpress-DC)
>         * COMe-cPC2 (microETXexpress-PC)
>         * COMe-mCT10
>         * ETX-OH
>
> Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> Acked-by: Guenter Roeck <linux@roeck-us.net>
> Acked-by: Darren Hart <dvhart@linux.intel.com>

(...)
> +/**
> + * kempld_read8 - read 8 bit register
> + * @pld: kempld_device_data structure describing the PLD
> + * @index: register index on the chip
> + *
> + * kempld_get_mutex must be called prior to calling this function.
> + */
> +u8 kempld_read8(struct kempld_device_data *pld, u8 index)
> +{
> +       iowrite8(index, pld->io_index);
> +       return ioread8(pld->io_data);
> +}
> +EXPORT_SYMBOL_GPL(kempld_read8);
> +
> +/**
> + * kempld_write8 - write 8 bit register
> + * @pld: kempld_device_data structure describing the PLD
> + * @index: register index on the chip
> + * @data: new register value
> + *
> + * kempld_get_mutex must be called prior to calling this function.
> + */
> +void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data)
> +{
> +       iowrite8(index, pld->io_index);
> +       iowrite8(data, pld->io_data);
> +}
> +EXPORT_SYMBOL_GPL(kempld_write8);
> +
> +/**
> + * kempld_read16 - read 16 bit register
> + * @pld: kempld_device_data structure describing the PLD
> + * @index: register index on the chip
> + *
> + * kempld_get_mutex must be called prior to calling this function.
> + */
> +u16 kempld_read16(struct kempld_device_data *pld, u8 index)
> +{
> +       return kempld_read8(pld, index) | kempld_read8(pld, index + 1) << 8;
> +}
> +EXPORT_SYMBOL_GPL(kempld_read16);
> +
> +/**
> + * kempld_write16 - write 16 bit register
> + * @pld: kempld_device_data structure describing the PLD
> + * @index: register index on the chip
> + * @data: new register value
> + *
> + * kempld_get_mutex must be called prior to calling this function.
> + */
> +void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data)
> +{
> +       kempld_write8(pld, index, (u8)data);
> +       kempld_write8(pld, index + 1, (u8)(data >> 8));
> +}
> +EXPORT_SYMBOL_GPL(kempld_write16);
> +
> +/**
> + * kempld_read32 - read 32 bit register
> + * @pld: kempld_device_data structure describing the PLD
> + * @index: register index on the chip
> + *
> + * kempld_get_mutex must be called prior to calling this function.
> + */
> +u32 kempld_read32(struct kempld_device_data *pld, u8 index)
> +{
> +       return kempld_read16(pld, index) | kempld_read16(pld, index + 2) << 16;
> +}
> +EXPORT_SYMBOL_GPL(kempld_read32);
> +
> +/**
> + * kempld_write32 - write 32 bit register
> + * @pld: kempld_device_data structure describing the PLD
> + * @index: register index on the chip
> + * @data: new register value
> + *
> + * kempld_get_mutex must be called prior to calling this function.
> + */
> +void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data)
> +{
> +       kempld_write16(pld, index, (u16)data);
> +       kempld_write16(pld, index + 2, (u16)(data >> 16));
> +}
> +EXPORT_SYMBOL_GPL(kempld_write32);
(...)
> +extern u8 kempld_read8(struct kempld_device_data *pld, u8 index);
> +extern void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data);
> +extern u16 kempld_read16(struct kempld_device_data *pld, u8 index);
> +extern void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data);
> +extern u32 kempld_read32(struct kempld_device_data *pld, u8 index);
> +extern void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data);

Not really my business, but I think if I was to implement this
inter-module API I would use regmap for this read/write marshalling
right here.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 1/4] mfd: Kontron PLD mfd driver
  2013-06-19  8:40     ` Linus Walleij
@ 2013-06-19  9:11       ` Samuel Ortiz
  2013-06-19  9:48         ` Mark Brown
  0 siblings, 1 reply; 48+ messages in thread
From: Samuel Ortiz @ 2013-06-19  9:11 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Kevin Strasser, linux-kernel, Darren Hart, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Wim Van Sebroeck, Mark Brown

Hi Linus,

On Wed, Jun 19, 2013 at 10:40:04AM +0200, Linus Walleij wrote:
> On Tue, Jun 18, 2013 at 11:04 PM, Kevin Strasser
> <kevin.strasser@linux.intel.com> wrote:
> > Add core MFD driver for the on-board PLD found on some Kontron embedded
> > modules. The PLD device may provide functions like watchdog, GPIO, UART
> > and I2C bus.
> >
> > The following modules are supported:
> >         * COMe-bIP#
> >         * COMe-bPC2 (ETXexpress-PC)
> >         * COMe-bSC# (ETXexpress-SC T#)
> >         * COMe-cCT6
> >         * COMe-cDC2 (microETXexpress-DC)
> >         * COMe-cPC2 (microETXexpress-PC)
> >         * COMe-mCT10
> >         * ETX-OH
> >
> > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> > Acked-by: Guenter Roeck <linux@roeck-us.net>
> > Acked-by: Darren Hart <dvhart@linux.intel.com>
> 
> (...)
> > +/**
> > + * kempld_read8 - read 8 bit register
> > + * @pld: kempld_device_data structure describing the PLD
> > + * @index: register index on the chip
> > + *
> > + * kempld_get_mutex must be called prior to calling this function.
> > + */
> > +u8 kempld_read8(struct kempld_device_data *pld, u8 index)
> > +{
> > +       iowrite8(index, pld->io_index);
> > +       return ioread8(pld->io_data);
> > +}
> > +EXPORT_SYMBOL_GPL(kempld_read8);
> > +
> > +/**
> > + * kempld_write8 - write 8 bit register
> > + * @pld: kempld_device_data structure describing the PLD
> > + * @index: register index on the chip
> > + * @data: new register value
> > + *
> > + * kempld_get_mutex must be called prior to calling this function.
> > + */
> > +void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data)
> > +{
> > +       iowrite8(index, pld->io_index);
> > +       iowrite8(data, pld->io_data);
> > +}
> > +EXPORT_SYMBOL_GPL(kempld_write8);
> > +
> > +/**
> > + * kempld_read16 - read 16 bit register
> > + * @pld: kempld_device_data structure describing the PLD
> > + * @index: register index on the chip
> > + *
> > + * kempld_get_mutex must be called prior to calling this function.
> > + */
> > +u16 kempld_read16(struct kempld_device_data *pld, u8 index)
> > +{
> > +       return kempld_read8(pld, index) | kempld_read8(pld, index + 1) << 8;
> > +}
> > +EXPORT_SYMBOL_GPL(kempld_read16);
> > +
> > +/**
> > + * kempld_write16 - write 16 bit register
> > + * @pld: kempld_device_data structure describing the PLD
> > + * @index: register index on the chip
> > + * @data: new register value
> > + *
> > + * kempld_get_mutex must be called prior to calling this function.
> > + */
> > +void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data)
> > +{
> > +       kempld_write8(pld, index, (u8)data);
> > +       kempld_write8(pld, index + 1, (u8)(data >> 8));
> > +}
> > +EXPORT_SYMBOL_GPL(kempld_write16);
> > +
> > +/**
> > + * kempld_read32 - read 32 bit register
> > + * @pld: kempld_device_data structure describing the PLD
> > + * @index: register index on the chip
> > + *
> > + * kempld_get_mutex must be called prior to calling this function.
> > + */
> > +u32 kempld_read32(struct kempld_device_data *pld, u8 index)
> > +{
> > +       return kempld_read16(pld, index) | kempld_read16(pld, index + 2) << 16;
> > +}
> > +EXPORT_SYMBOL_GPL(kempld_read32);
> > +
> > +/**
> > + * kempld_write32 - write 32 bit register
> > + * @pld: kempld_device_data structure describing the PLD
> > + * @index: register index on the chip
> > + * @data: new register value
> > + *
> > + * kempld_get_mutex must be called prior to calling this function.
> > + */
> > +void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data)
> > +{
> > +       kempld_write16(pld, index, (u16)data);
> > +       kempld_write16(pld, index + 2, (u16)(data >> 16));
> > +}
> > +EXPORT_SYMBOL_GPL(kempld_write32);
> (...)
> > +extern u8 kempld_read8(struct kempld_device_data *pld, u8 index);
> > +extern void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data);
> > +extern u16 kempld_read16(struct kempld_device_data *pld, u8 index);
> > +extern void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data);
> > +extern u32 kempld_read32(struct kempld_device_data *pld, u8 index);
> > +extern void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data);
> 
> Not really my business, but I think if I was to implement this
> inter-module API I would use regmap for this read/write marshalling
> right here.
I thought about this one and was under the impression that the regmap
API would not let us implement this kind of stuff. I don't see how the
reg_read and reg_write hooks would allow us to implement that, but I may
be missing something here. Maybe Mark can provide some more input.

Cheers,
Samuel.

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 1/4] mfd: Kontron PLD mfd driver
  2013-06-18 21:04   ` [PATCH v2 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
  2013-06-19  8:40     ` Linus Walleij
@ 2013-06-19  9:12     ` Thomas Gleixner
  2013-06-19 18:03       ` Kevin Strasser
  1 sibling, 1 reply; 48+ messages in thread
From: Thomas Gleixner @ 2013-06-19  9:12 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Dirk Hohndel,
	Wolfram Sang, Ben Dooks, Grant Likely, Linus Walleij,
	Wim Van Sebroeck, Michael Brunner

On Tue, 18 Jun 2013, Kevin Strasser wrote:

This patch set looks reasonable now, except a formal issue.
 
> Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> Signed-off-by: Michael Brunner <michael.brunner@kontron.com>

This is wrong, as it says:

 Kevin authored the code, sent it to Michael and Michael sent it to
 LKML.


The original code was authored by Michael. This version was authored
by Kevin based on Michaels code.

So either you keep Michael as the author, then you want 

 From: Michael Brunner <michael.brunner@kontron.com>

 <patch description/>

 Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
 Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>

or you decide that it's a major rewrite by you, then you want:

 This patch is based on the original version authored by
 Michael Brunner <michael.brunner@kontron.com>.

 Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>

 Or 

 Originally-From: Michael Brunner <michael.brunner@kontron.com>.
 Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>

Other than that.

Acked-by: Thomas Gleixner <tglx@linutronix.de>

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 1/4] mfd: Kontron PLD mfd driver
  2013-06-19  9:11       ` Samuel Ortiz
@ 2013-06-19  9:48         ` Mark Brown
  0 siblings, 0 replies; 48+ messages in thread
From: Mark Brown @ 2013-06-19  9:48 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: Linus Walleij, Kevin Strasser, linux-kernel, Darren Hart,
	Guenter Roeck, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Wolfram Sang, Ben Dooks,
	Grant Likely, Wim Van Sebroeck

[-- Attachment #1: Type: text/plain, Size: 907 bytes --]

On Wed, Jun 19, 2013 at 11:11:27AM +0200, Samuel Ortiz wrote:
> On Wed, Jun 19, 2013 at 10:40:04AM +0200, Linus Walleij wrote:

> > Not really my business, but I think if I was to implement this
> > inter-module API I would use regmap for this read/write marshalling
> > right here.

> I thought about this one and was under the impression that the regmap
> API would not let us implement this kind of stuff. I don't see how the
> reg_read and reg_write hooks would allow us to implement that, but I may
> be missing something here. Maybe Mark can provide some more input.

You should be able to use reg_read() and reg_write() but you're right it
would be a bit hoop jumping.  Really the code looks like a new generic
bus type for ioread()/iowrite() users though - I guess there will be
other similar devices connected in the same way and they probably don't
have radically different ideas about registers.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 1/4] mfd: Kontron PLD mfd driver
  2013-06-19  9:12     ` Thomas Gleixner
@ 2013-06-19 18:03       ` Kevin Strasser
  2013-06-19 20:35         ` Guenter Roeck
  0 siblings, 1 reply; 48+ messages in thread
From: Kevin Strasser @ 2013-06-19 18:03 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: linux-kernel, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Dirk Hohndel,
	Wolfram Sang, Ben Dooks, Grant Likely, Linus Walleij,
	Wim Van Sebroeck

On Wed, Jun 19, 2013 at 11:12:09AM +0200, Thomas Gleixner wrote:
> On Tue, 18 Jun 2013, Kevin Strasser wrote:
> 
> This patch set looks reasonable now, except a formal issue.
>  
> > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> 
> This is wrong, as it says:
> 
>  Kevin authored the code, sent it to Michael and Michael sent it to
>  LKML.
> 
> 
> The original code was authored by Michael. This version was authored
> by Kevin based on Michaels code.
> 
> So either you keep Michael as the author, then you want 
> 
>  From: Michael Brunner <michael.brunner@kontron.com>
> 
>  <patch description/>
> 
>  Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
>  Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> 
> or you decide that it's a major rewrite by you, then you want:
> 
>  This patch is based on the original version authored by
>  Michael Brunner <michael.brunner@kontron.com>.
> 
>  Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> 
>  Or 
> 
>  Originally-From: Michael Brunner <michael.brunner@kontron.com>.
>  Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>

Ok, I think this will make sense for all the patches except gpio,
which is a major rewrite by Guenter.

So maybe this would be appropriate:

 From: Guenter Roeck <linux@roeck-us.net>

 <patch description>

 Originally-From: Michael Brunner <michael.brunner@kontron.com>
 Signed-off-by: Guenter Roeck <linux@roeck-us.net>
 Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
 ...

Thanks,
Kevin

> 
> Other than that.
> 
> Acked-by: Thomas Gleixner <tglx@linutronix.de>

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 1/4] mfd: Kontron PLD mfd driver
  2013-06-19 18:03       ` Kevin Strasser
@ 2013-06-19 20:35         ` Guenter Roeck
  0 siblings, 0 replies; 48+ messages in thread
From: Guenter Roeck @ 2013-06-19 20:35 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: Thomas Gleixner, linux-kernel, Darren Hart, Samuel Ortiz,
	Michael Brunner, Michael Brunner, Chris Healy, Dirk Hohndel,
	Wolfram Sang, Ben Dooks, Grant Likely, Linus Walleij,
	Wim Van Sebroeck

On Wed, Jun 19, 2013 at 11:03:46AM -0700, Kevin Strasser wrote:
> On Wed, Jun 19, 2013 at 11:12:09AM +0200, Thomas Gleixner wrote:
> > On Tue, 18 Jun 2013, Kevin Strasser wrote:
> > 
> > This patch set looks reasonable now, except a formal issue.
> >  
> > > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> > > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> > 
> > This is wrong, as it says:
> > 
> >  Kevin authored the code, sent it to Michael and Michael sent it to
> >  LKML.
> > 
> > 
> > The original code was authored by Michael. This version was authored
> > by Kevin based on Michaels code.
> > 
> > So either you keep Michael as the author, then you want 
> > 
> >  From: Michael Brunner <michael.brunner@kontron.com>
> > 
> >  <patch description/>
> > 
> >  Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> >  Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> > 
> > or you decide that it's a major rewrite by you, then you want:
> > 
> >  This patch is based on the original version authored by
> >  Michael Brunner <michael.brunner@kontron.com>.
> > 
> >  Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> > 
> >  Or 
> > 
> >  Originally-From: Michael Brunner <michael.brunner@kontron.com>.
> >  Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> 
> Ok, I think this will make sense for all the patches except gpio,
> which is a major rewrite by Guenter.
> 
> So maybe this would be appropriate:
> 
>  From: Guenter Roeck <linux@roeck-us.net>
> 
>  <patch description>
> 
>  Originally-From: Michael Brunner <michael.brunner@kontron.com>
>  Signed-off-by: Guenter Roeck <linux@roeck-us.net>
>  Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
>  ...

Ok with me. Always happy to take the blame :)

Thanks,
Guenter

> 
> Thanks,
> Kevin
> 
> > 
> > Other than that.
> > 
> > Acked-by: Thomas Gleixner <tglx@linutronix.de>
> 

^ permalink raw reply	[flat|nested] 48+ messages in thread

* [PATCH v3 0/4] Kontron PLD drivers
  2013-04-08 17:15 [PATCH 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
                   ` (4 preceding siblings ...)
  2013-06-18 21:04 ` [PATCH v2 0/4] Kontron PLD drivers Kevin Strasser
@ 2013-06-24  4:00 ` Kevin Strasser
  2013-06-24  4:00   ` [PATCH v3 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
                     ` (4 more replies)
  5 siblings, 5 replies; 48+ messages in thread
From: Kevin Strasser @ 2013-06-24  4:00 UTC (permalink / raw)
  To: linux-kernel
  Cc: Kevin Strasser, Kevin Strasser, Darren Hart, Samuel Ortiz,
	Guenter Roeck, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Wolfram Sang, Ben Dooks,
	Grant Likely, Linus Walleij, Wim Van Sebroeck, linux-i2c,
	linux-watchdog

Changes since v2:
-Change Michael's "Signed-off-by" to "Originally-From" in all patches
-Add "From: Guenter Roeck <linux@roeck-us.net>" to gpio patch

Guenter Roeck (1):
  gpio: Kontron PLD gpio driver

Kevin Strasser (3):
  mfd: Kontron PLD mfd driver
  i2c: Kontron PLD i2c bus driver
  watchdog: Kontron PLD watchdog timer driver

 drivers/gpio/Kconfig            |   12 +
 drivers/gpio/Makefile           |    1 +
 drivers/gpio/gpio-kempld.c      |  225 ++++++++++++++
 drivers/i2c/busses/Kconfig      |   10 +
 drivers/i2c/busses/Makefile     |    1 +
 drivers/i2c/busses/i2c-kempld.c |  410 +++++++++++++++++++++++++
 drivers/mfd/Kconfig             |   21 ++
 drivers/mfd/Makefile            |    1 +
 drivers/mfd/kempld-core.c       |  642 +++++++++++++++++++++++++++++++++++++++
 drivers/watchdog/Kconfig        |   11 +
 drivers/watchdog/Makefile       |    1 +
 drivers/watchdog/kempld_wdt.c   |  580 +++++++++++++++++++++++++++++++++++
 include/linux/mfd/kempld.h      |  125 ++++++++
 13 files changed, 2040 insertions(+)
 create mode 100644 drivers/gpio/gpio-kempld.c
 create mode 100644 drivers/i2c/busses/i2c-kempld.c
 create mode 100644 drivers/mfd/kempld-core.c
 create mode 100644 drivers/watchdog/kempld_wdt.c
 create mode 100644 include/linux/mfd/kempld.h

-- 
1.7.9.5


^ permalink raw reply	[flat|nested] 48+ messages in thread

* [PATCH v3 1/4] mfd: Kontron PLD mfd driver
  2013-06-24  4:00 ` [PATCH v3 0/4] Kontron PLD drivers Kevin Strasser
@ 2013-06-24  4:00   ` Kevin Strasser
  2013-06-24  4:00   ` [PATCH v3 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 48+ messages in thread
From: Kevin Strasser @ 2013-06-24  4:00 UTC (permalink / raw)
  To: linux-kernel
  Cc: Kevin Strasser, Kevin Strasser, Darren Hart, Samuel Ortiz,
	Guenter Roeck, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Wolfram Sang, Ben Dooks,
	Grant Likely, Linus Walleij, Wim Van Sebroeck

Add core MFD driver for the on-board PLD found on some Kontron embedded
modules. The PLD device may provide functions like watchdog, GPIO, UART
and I2C bus.

The following modules are supported:
	* COMe-bIP#
	* COMe-bPC2 (ETXexpress-PC)
	* COMe-bSC# (ETXexpress-SC T#)
	* COMe-cCT6
	* COMe-cDC2 (microETXexpress-DC)
	* COMe-cPC2 (microETXexpress-PC)
	* COMe-mCT10
	* ETX-OH

Originally-From: Michael Brunner <michael.brunner@kontron.com>
Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Darren Hart <dvhart@linux.intel.com>
Acked-by: Thomas Gleixner <tglx@linutronix.de>
---
 drivers/mfd/Kconfig        |   21 ++
 drivers/mfd/Makefile       |    1 +
 drivers/mfd/kempld-core.c  |  642 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/kempld.h |  125 +++++++++
 4 files changed, 789 insertions(+)
 create mode 100644 drivers/mfd/kempld-core.c
 create mode 100644 include/linux/mfd/kempld.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index c346941..eea3737 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -912,6 +912,27 @@ config MFD_TIMBERDALE
 	The timberdale FPGA can be found on the Intel Atom development board
 	for in-vehicle infontainment, called Russellville.
 
+config MFD_KEMPLD
+	tristate "Support for Kontron module PLD device"
+	select MFD_CORE
+	help
+	  This is the core driver for the PLD (Programmable Logic Device) found
+	  on some Kontron ETX and COMexpress (ETXexpress) modules. The PLD
+	  device may provide functions like watchdog, GPIO, UART and I2C bus.
+
+	  The following modules are supported:
+		* COMe-bIP#
+		* COMe-bPC2 (ETXexpress-PC)
+		* COMe-bSC# (ETXexpress-SC T#)
+		* COMe-cCT6
+		* COMe-cDC2 (microETXexpress-DC)
+		* COMe-cPC2 (microETXexpress-PC)
+		* COMe-mCT10
+		* ETX-OH
+
+	  This driver can also be built as a module. If so, the module
+	  will be called kempld-core.
+
 config LPC_SCH
 	tristate "Intel SCH LPC"
 	depends on PCI && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index b90409c..a2f6a9f 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -123,6 +123,7 @@ obj-$(CONFIG_MFD_DB8500_PRCMU)	+= db8500-prcmu.o
 obj-$(CONFIG_AB8500_CORE)	+= ab8500-core.o ab8500-sysctrl.o
 obj-$(CONFIG_MFD_TIMBERDALE)    += timberdale.o
 obj-$(CONFIG_PMIC_ADP5520)	+= adp5520.o
+obj-$(CONFIG_MFD_KEMPLD)	+= kempld-core.o
 obj-$(CONFIG_LPC_SCH)		+= lpc_sch.o
 obj-$(CONFIG_LPC_ICH)		+= lpc_ich.o
 obj-$(CONFIG_MFD_RDC321X)	+= rdc321x-southbridge.o
diff --git a/drivers/mfd/kempld-core.c b/drivers/mfd/kempld-core.c
new file mode 100644
index 0000000..85b437e
--- /dev/null
+++ b/drivers/mfd/kempld-core.c
@@ -0,0 +1,642 @@
+/*
+ * Kontron PLD MFD core driver
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/kempld.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#define MAX_ID_LEN 4
+static char force_device_id[MAX_ID_LEN + 1] = "";
+module_param_string(force_device_id, force_device_id, sizeof(force_device_id), 0);
+MODULE_PARM_DESC(force_device_id, "Override detected product");
+
+/*
+ * Get hardware mutex to block firmware from accessing the pld.
+ * It is possible for the firmware may hold the mutex for an extended length of
+ * time. This function will block until access has been granted.
+ */
+static void kempld_get_hardware_mutex(struct kempld_device_data *pld)
+{
+	/* The mutex bit will read 1 until access has been granted */
+	while (ioread8(pld->io_index) & KEMPLD_MUTEX_KEY)
+		msleep(1);
+}
+
+static void kempld_release_hardware_mutex(struct kempld_device_data *pld)
+{
+	/* The harware mutex is released when 1 is written to the mutex bit. */
+	iowrite8(KEMPLD_MUTEX_KEY, pld->io_index);
+}
+
+static int kempld_get_info_generic(struct kempld_device_data *pld)
+{
+	u16 version;
+	u8 spec;
+
+	kempld_get_mutex(pld);
+
+	version = kempld_read16(pld, KEMPLD_VERSION);
+	spec = kempld_read8(pld, KEMPLD_SPEC);
+	pld->info.buildnr = kempld_read16(pld, KEMPLD_BUILDNR);
+
+	pld->info.minor = KEMPLD_VERSION_GET_MINOR(version);
+	pld->info.major = KEMPLD_VERSION_GET_MAJOR(version);
+	pld->info.number = KEMPLD_VERSION_GET_NUMBER(version);
+	pld->info.type = KEMPLD_VERSION_GET_TYPE(version);
+
+	if (spec == 0xff) {
+		pld->info.spec_minor = 0;
+		pld->info.spec_major = 1;
+	} else {
+		pld->info.spec_minor = KEMPLD_SPEC_GET_MINOR(spec);
+		pld->info.spec_major = KEMPLD_SPEC_GET_MAJOR(spec);
+	}
+
+	if (pld->info.spec_major > 0)
+		pld->feature_mask = kempld_read16(pld, KEMPLD_FEATURE);
+	else
+		pld->feature_mask = 0;
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+enum kempld_cells {
+	KEMPLD_I2C = 0,
+	KEMPLD_WDT,
+	KEMPLD_GPIO,
+	KEMPLD_UART,
+};
+
+static struct mfd_cell kempld_devs[] = {
+	[KEMPLD_I2C] = {
+		.name = "kempld-i2c",
+	},
+	[KEMPLD_WDT] = {
+		.name = "kempld-wdt",
+	},
+	[KEMPLD_GPIO] = {
+		.name = "kempld-gpio",
+	},
+	[KEMPLD_UART] = {
+		.name = "kempld-uart",
+	},
+};
+
+#define KEMPLD_MAX_DEVS	ARRAY_SIZE(kempld_devs)
+
+static int kempld_register_cells_generic(struct kempld_device_data *pld)
+{
+	struct mfd_cell devs[KEMPLD_MAX_DEVS];
+	int i = 0;
+
+	if (pld->feature_mask & KEMPLD_FEATURE_BIT_I2C)
+		devs[i++] = kempld_devs[KEMPLD_I2C];
+
+	if (pld->feature_mask & KEMPLD_FEATURE_BIT_WATCHDOG)
+		devs[i++] = kempld_devs[KEMPLD_WDT];
+
+	if (pld->feature_mask & KEMPLD_FEATURE_BIT_GPIO)
+		devs[i++] = kempld_devs[KEMPLD_GPIO];
+
+	if (pld->feature_mask & KEMPLD_FEATURE_MASK_UART)
+		devs[i++] = kempld_devs[KEMPLD_UART];
+
+	return mfd_add_devices(pld->dev, -1, devs, i, NULL, 0, NULL);
+}
+
+static struct resource kempld_ioresource = {
+	.start	= KEMPLD_IOINDEX,
+	.end	= KEMPLD_IODATA,
+	.flags	= IORESOURCE_IO,
+};
+
+static const struct kempld_platform_data kempld_platform_data_generic = {
+	.pld_clock		= KEMPLD_CLK,
+	.ioresource		= &kempld_ioresource,
+	.get_hardware_mutex	= kempld_get_hardware_mutex,
+	.release_hardware_mutex	= kempld_release_hardware_mutex,
+	.get_info		= kempld_get_info_generic,
+	.register_cells		= kempld_register_cells_generic,
+};
+
+static struct platform_device *kempld_pdev;
+
+static int kempld_create_platform_device(const struct dmi_system_id *id)
+{
+	struct kempld_platform_data *pdata = id->driver_data;
+	int ret;
+
+	kempld_pdev = platform_device_alloc("kempld", -1);
+	if (!kempld_pdev)
+		return -ENOMEM;
+
+	ret = platform_device_add_data(kempld_pdev, pdata, sizeof(*pdata));
+	if (ret)
+		goto err;
+
+	ret = platform_device_add_resources(kempld_pdev, pdata->ioresource, 1);
+	if (ret)
+		goto err;
+
+	ret = platform_device_add(kempld_pdev);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	platform_device_put(kempld_pdev);
+	return ret;
+}
+
+/**
+ * kempld_read8 - read 8 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+u8 kempld_read8(struct kempld_device_data *pld, u8 index)
+{
+	iowrite8(index, pld->io_index);
+	return ioread8(pld->io_data);
+}
+EXPORT_SYMBOL_GPL(kempld_read8);
+
+/**
+ * kempld_write8 - write 8 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ * @data: new register value
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data)
+{
+	iowrite8(index, pld->io_index);
+	iowrite8(data, pld->io_data);
+}
+EXPORT_SYMBOL_GPL(kempld_write8);
+
+/**
+ * kempld_read16 - read 16 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+u16 kempld_read16(struct kempld_device_data *pld, u8 index)
+{
+	return kempld_read8(pld, index) | kempld_read8(pld, index + 1) << 8;
+}
+EXPORT_SYMBOL_GPL(kempld_read16);
+
+/**
+ * kempld_write16 - write 16 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ * @data: new register value
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data)
+{
+	kempld_write8(pld, index, (u8)data);
+	kempld_write8(pld, index + 1, (u8)(data >> 8));
+}
+EXPORT_SYMBOL_GPL(kempld_write16);
+
+/**
+ * kempld_read32 - read 32 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+u32 kempld_read32(struct kempld_device_data *pld, u8 index)
+{
+	return kempld_read16(pld, index) | kempld_read16(pld, index + 2) << 16;
+}
+EXPORT_SYMBOL_GPL(kempld_read32);
+
+/**
+ * kempld_write32 - write 32 bit register
+ * @pld: kempld_device_data structure describing the PLD
+ * @index: register index on the chip
+ * @data: new register value
+ *
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data)
+{
+	kempld_write16(pld, index, (u16)data);
+	kempld_write16(pld, index + 2, (u16)(data >> 16));
+}
+EXPORT_SYMBOL_GPL(kempld_write32);
+
+/**
+ * kempld_get_mutex - acquire PLD mutex
+ * @pld: kempld_device_data structure describing the PLD
+ */
+void kempld_get_mutex(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	mutex_lock(&pld->lock);
+	pdata->get_hardware_mutex(pld);
+}
+EXPORT_SYMBOL_GPL(kempld_get_mutex);
+
+/**
+ * kempld_release_mutex - release PLD mutex
+ * @pld: kempld_device_data structure describing the PLD
+ */
+void kempld_release_mutex(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	pdata->release_hardware_mutex(pld);
+	mutex_unlock(&pld->lock);
+}
+EXPORT_SYMBOL_GPL(kempld_release_mutex);
+
+/**
+ * kempld_get_info - update device specific information
+ * @pld: kempld_device_data structure describing the PLD
+ *
+ * This function calls the configured board specific kempld_get_info_XXXX
+ * function which is responsible for gathering information about the specific
+ * hardware. The information is then stored within the pld structure.
+ */
+static int kempld_get_info(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	return pdata->get_info(pld);
+}
+
+/*
+ * kempld_register_cells - register cell drivers
+ *
+ * This function registers cell drivers for the detected hardware by calling
+ * the configured kempld_register_cells_XXXX function which is responsible
+ * to detect and register the needed cell drivers.
+ */
+static int kempld_register_cells(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	return pdata->register_cells(pld);
+}
+
+static int kempld_detect_device(struct kempld_device_data *pld)
+{
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+	char *version_type;
+	u8 index_reg;
+	int ret;
+
+	mutex_lock(&pld->lock);
+
+	/* Check for empty IO space */
+	index_reg = ioread8(pld->io_index);
+	if (index_reg == 0xff && ioread8(pld->io_data) == 0xff) {
+		mutex_unlock(&pld->lock);
+		return -ENODEV;
+	}
+
+	/* Release hardware mutex if aquired */
+	if (!(index_reg & KEMPLD_MUTEX_KEY))
+		iowrite8(KEMPLD_MUTEX_KEY, pld->io_index);
+
+	mutex_unlock(&pld->lock);
+
+	ret = kempld_get_info(pld);
+	if (ret)
+		return ret;
+
+	switch (pld->info.type) {
+	case 0:
+		version_type = "release";
+		break;
+	case 1:
+		version_type = "debug";
+		break;
+	case 2:
+		version_type = "custom";
+		break;
+	default:
+		version_type = "unspecified";
+	}
+
+	dev_info(pld->dev, "Found Kontron PLD %d\n", pld->info.number);
+	dev_info(pld->dev, "%s version %d.%d build %d, specification %d.%d\n",
+		 version_type, pld->info.major, pld->info.minor,
+		 pld->info.buildnr, pld->info.spec_major,
+		 pld->info.spec_minor);
+
+	return kempld_register_cells(pld);
+}
+
+static int kempld_probe(struct platform_device *pdev)
+{
+	struct kempld_platform_data *pdata = pdev->dev.platform_data;
+	struct device *dev = &pdev->dev;
+	struct kempld_device_data *pld;
+	struct resource *ioport;
+	int ret;
+
+	pld = devm_kzalloc(dev, sizeof(*pld), GFP_KERNEL);
+	if (!pld)
+		return -ENOMEM;
+
+	ioport = platform_get_resource(pdev, IORESOURCE_IO, 0);
+	if (!ioport)
+		return -EINVAL;
+
+	pld->io_base = devm_ioport_map(dev, ioport->start,
+					ioport->end - ioport->start);
+	if (!pld->io_base)
+		return -ENOMEM;
+
+	pld->io_index = pld->io_base;
+	pld->io_data = pld->io_base + 1;
+	pld->pld_clock = pdata->pld_clock;
+	pld->dev = dev;
+
+	mutex_init(&pld->lock);
+	platform_set_drvdata(pdev, pld);
+
+	ret = kempld_detect_device(pld);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int kempld_remove(struct platform_device *pdev)
+{
+	struct kempld_device_data *pld = platform_get_drvdata(pdev);
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+
+	mfd_remove_devices(&pdev->dev);
+	pdata->release_hardware_mutex(pld);
+
+	return 0;
+}
+
+static struct platform_driver kempld_driver = {
+	.driver		= {
+		.name	= "kempld",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= kempld_probe,
+	.remove		= kempld_remove,
+};
+
+static struct dmi_system_id __initdata kempld_dmi_table[] = {
+	{
+		.ident = "CCR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bIP2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CCR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bIP6"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-SC T2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXe-SC T2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bSC2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-SC T6"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXe-SC T6"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CHR6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bSC6"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETXexpress-PC"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-bPC2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "CNTX",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "PXT"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "FRI2",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BIOS_VERSION, "FRI2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "FRI2",
+		.matches = {
+			DMI_MATCH(DMI_PRODUCT_NAME, "Fish River Island II"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "MBR1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "ETX-OH"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "NTC1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "nanoETXexpress-TT"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "NTC1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "nETXe-TT"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "NTC1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-mTT"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "NUP1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-mCT"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "UNP1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "microETXexpress-DC"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "UNP1",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-cDC2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "UNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "microETXexpress-PC"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "UNTG",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-cPC2"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	}, {
+		.ident = "UUP6",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Kontron"),
+			DMI_MATCH(DMI_BOARD_NAME, "COMe-cCT6"),
+		},
+		.driver_data = (void *)&kempld_platform_data_generic,
+		.callback = kempld_create_platform_device,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(dmi, kempld_dmi_table);
+
+static int __init kempld_init(void)
+{
+	const struct dmi_system_id *id;
+	int ret;
+
+	if (force_device_id[0]) {
+		for (id = kempld_dmi_table; id->matches[0].slot != DMI_NONE; id++)
+			if (strstr(id->ident, force_device_id))
+				if (id->callback && id->callback(id))
+					break;
+		if (id->matches[0].slot == DMI_NONE)
+			return -ENODEV;
+	} else {
+		if (!dmi_check_system(kempld_dmi_table))
+			return -ENODEV;
+	}
+
+	ret = platform_driver_register(&kempld_driver);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void __exit kempld_exit(void)
+{
+	if (kempld_pdev)
+		platform_device_unregister(kempld_pdev);
+
+	platform_driver_unregister(&kempld_driver);
+}
+
+module_init(kempld_init);
+module_exit(kempld_exit);
+
+MODULE_DESCRIPTION("KEM PLD Core Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld-core");
diff --git a/include/linux/mfd/kempld.h b/include/linux/mfd/kempld.h
new file mode 100644
index 0000000..b911ef3
--- /dev/null
+++ b/include/linux/mfd/kempld.h
@@ -0,0 +1,125 @@
+/*
+ * Kontron PLD driver definitions
+ *
+ * Copyright (c) 2010-2012 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 2 as published
+ * by the Free Software Foundation.
+ */
+
+#ifndef _LINUX_MFD_KEMPLD_H_
+#define _LINUX_MFD_KEMPLD_H_
+
+/* kempld register definitions */
+#define KEMPLD_IOINDEX			0xa80
+#define KEMPLD_IODATA			0xa81
+#define KEMPLD_MUTEX_KEY		0x80
+#define KEMPLD_VERSION			0x00
+#define KEMPLD_VERSION_LSB		0x00
+#define KEMPLD_VERSION_MSB		0x01
+#define KEMPLD_VERSION_GET_MINOR(x)	(x & 0x1f)
+#define KEMPLD_VERSION_GET_MAJOR(x)	((x >> 5) & 0x1f)
+#define KEMPLD_VERSION_GET_NUMBER(x)	((x >> 10) & 0xf)
+#define KEMPLD_VERSION_GET_TYPE(x)	((x >> 14) & 0x3)
+#define KEMPLD_BUILDNR			0x02
+#define KEMPLD_BUILDNR_LSB		0x02
+#define KEMPLD_BUILDNR_MSB		0x03
+#define KEMPLD_FEATURE			0x04
+#define KEMPLD_FEATURE_LSB		0x04
+#define KEMPLD_FEATURE_MSB		0x05
+#define KEMPLD_FEATURE_BIT_I2C		(1 << 0)
+#define KEMPLD_FEATURE_BIT_WATCHDOG	(1 << 1)
+#define KEMPLD_FEATURE_BIT_GPIO		(1 << 2)
+#define KEMPLD_FEATURE_MASK_UART	(7 << 3)
+#define KEMPLD_FEATURE_BIT_NMI		(1 << 8)
+#define KEMPLD_FEATURE_BIT_SMI		(1 << 9)
+#define KEMPLD_FEATURE_BIT_SCI		(1 << 10)
+#define KEMPLD_SPEC			0x06
+#define KEMPLD_SPEC_GET_MINOR(x)	(x & 0x0f)
+#define KEMPLD_SPEC_GET_MAJOR(x)	((x >> 4) & 0x0f)
+#define KEMPLD_IRQ_GPIO			0x35
+#define KEMPLD_IRQ_I2C			0x36
+#define KEMPLD_CFG			0x37
+#define KEMPLD_CFG_GPIO_I2C_MUX		(1 << 0)
+#define KEMPLD_CFG_BIOS_WP		(1 << 7)
+
+#define KEMPLD_CLK			33333333
+
+#define	KEMPLD_TYPE_RELEASE		0x0
+#define	KEMPLD_TYPE_DEBUG		0x1
+#define	KEMPLD_TYPE_CUSTOM		0x2
+
+/**
+ * struct kempld_info - PLD device information structure
+ * @major:	PLD major revision
+ * @minor:	PLD minor revision
+ * @buildnr:	PLD build number
+ * @number:	PLD board specific index
+ * @type:	PLD type
+ * @spec_major:	PLD FW specification major revision
+ * @spec_minor:	PLD FW specification minor revision
+ */
+struct kempld_info {
+	unsigned int major;
+	unsigned int minor;
+	unsigned int buildnr;
+	unsigned int number;
+	unsigned int type;
+	unsigned int spec_major;
+	unsigned int spec_minor;
+};
+
+/**
+ * struct kempld_device_data - Internal representation of the PLD device
+ * @io_base:		Pointer to the IO memory
+ * @io_index:		Pointer to the IO index register
+ * @io_data:		Pointer to the IO data register
+ * @pld_clock:		PLD clock frequency
+ * @feature_mask:	PLD feature mask
+ * @dev:		Pointer to kernel device structure
+ * @info:		KEMPLD info structure
+ * @lock:		PLD mutex
+ */
+struct kempld_device_data {
+	void __iomem		*io_base;
+	void __iomem		*io_index;
+	void __iomem		*io_data;
+	u32			pld_clock;
+	u32			feature_mask;
+	struct device		*dev;
+	struct kempld_info	info;
+	struct mutex		lock;
+};
+
+/**
+ * struct kempld_platform_data - PLD hardware configuration structure
+ * @pld_clock:			PLD clock frequency
+ * @gpio_base			GPIO base pin number
+ * @ioresource:			IO addresses of the PLD
+ * @get_mutex:			PLD specific get_mutex callback
+ * @release_mutex:		PLD specific release_mutex callback
+ * @get_info:			PLD specific get_info callback
+ * @register_cells:		PLD specific register_cells callback
+ */
+struct kempld_platform_data {
+	u32				pld_clock;
+	int				gpio_base;
+	struct resource			*ioresource;
+	void (*get_hardware_mutex)	(struct kempld_device_data *);
+	void (*release_hardware_mutex)	(struct kempld_device_data *);
+	int (*get_info)			(struct kempld_device_data *);
+	int (*register_cells)		(struct kempld_device_data *);
+};
+
+extern void kempld_get_mutex(struct kempld_device_data *pld);
+extern void kempld_release_mutex(struct kempld_device_data *pld);
+extern u8 kempld_read8(struct kempld_device_data *pld, u8 index);
+extern void kempld_write8(struct kempld_device_data *pld, u8 index, u8 data);
+extern u16 kempld_read16(struct kempld_device_data *pld, u8 index);
+extern void kempld_write16(struct kempld_device_data *pld, u8 index, u16 data);
+extern u32 kempld_read32(struct kempld_device_data *pld, u8 index);
+extern void kempld_write32(struct kempld_device_data *pld, u8 index, u32 data);
+
+#endif /* _LINUX_MFD_KEMPLD_H_ */
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v3 2/4] i2c: Kontron PLD i2c bus driver
  2013-06-24  4:00 ` [PATCH v3 0/4] Kontron PLD drivers Kevin Strasser
  2013-06-24  4:00   ` [PATCH v3 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
@ 2013-06-24  4:00   ` Kevin Strasser
  2013-07-01  6:40     ` Wolfram Sang
  2013-06-24  4:00   ` [PATCH v3 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
                     ` (2 subsequent siblings)
  4 siblings, 1 reply; 48+ messages in thread
From: Kevin Strasser @ 2013-06-24  4:00 UTC (permalink / raw)
  To: linux-kernel
  Cc: Kevin Strasser, Kevin Strasser, Darren Hart, Samuel Ortiz,
	Guenter Roeck, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Wolfram Sang, Ben Dooks,
	Grant Likely, Linus Walleij, Wim Van Sebroeck, linux-i2c

Add i2c support for the on-board PLD found on some Kontron embedded
modules.

Originally-From: Michael Brunner <michael.brunner@kontron.com>
Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Darren Hart <dvhart@linux.intel.com>
---
 drivers/i2c/busses/Kconfig      |   10 +
 drivers/i2c/busses/Makefile     |    1 +
 drivers/i2c/busses/i2c-kempld.c |  410 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 421 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-kempld.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index adfee98..3e1457d 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -494,6 +494,16 @@ config I2C_IOP3XX
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-iop3xx.
 
+config I2C_KEMPLD
+	tristate "Kontron COM I2C Controller"
+	depends on MFD_KEMPLD
+	help
+	  This enables support for the I2C bus interface on some Kontron ETX
+	  and COMexpress (ETXexpress) modules.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called i2c-kempld.
+
 config I2C_MPC
 	tristate "MPC107/824x/85xx/512x/52xx/83xx/86xx"
 	depends on PPC
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 8f4fc23..150fa15 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_I2C_IBM_IIC)	+= i2c-ibm_iic.o
 obj-$(CONFIG_I2C_IMX)		+= i2c-imx.o
 obj-$(CONFIG_I2C_INTEL_MID)	+= i2c-intel-mid.o
 obj-$(CONFIG_I2C_IOP3XX)	+= i2c-iop3xx.o
+obj-$(CONFIG_I2C_KEMPLD)	+= i2c-kempld.o
 obj-$(CONFIG_I2C_MPC)		+= i2c-mpc.o
 obj-$(CONFIG_I2C_MV64XXX)	+= i2c-mv64xxx.o
 obj-$(CONFIG_I2C_MXS)		+= i2c-mxs.o
diff --git a/drivers/i2c/busses/i2c-kempld.c b/drivers/i2c/busses/i2c-kempld.c
new file mode 100644
index 0000000..ccec916
--- /dev/null
+++ b/drivers/i2c/busses/i2c-kempld.c
@@ -0,0 +1,410 @@
+/*
+ * I2C bus driver for Kontron COM modules
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * The driver is based on the i2c-ocores driver by Peter Korsgaard.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/mfd/kempld.h>
+
+#define KEMPLD_I2C_PRELOW	0x0b
+#define KEMPLD_I2C_PREHIGH	0x0c
+#define KEMPLD_I2C_DATA		0x0e
+
+#define KEMPLD_I2C_CTRL		0x0d
+#define I2C_CTRL_IEN		0x40
+#define I2C_CTRL_EN		0x80
+
+#define KEMPLD_I2C_STAT		0x0f
+#define I2C_STAT_IF		0x01
+#define I2C_STAT_TIP		0x02
+#define I2C_STAT_ARBLOST	0x20
+#define I2C_STAT_BUSY		0x40
+#define I2C_STAT_NACK		0x80
+
+#define KEMPLD_I2C_CMD		0x0f
+#define I2C_CMD_START		0x91
+#define I2C_CMD_STOP		0x41
+#define I2C_CMD_READ		0x21
+#define I2C_CMD_WRITE		0x11
+#define I2C_CMD_READ_ACK	0x21
+#define I2C_CMD_READ_NACK	0x29
+#define I2C_CMD_IACK		0x01
+
+#define KEMPLD_I2C_FREQ_MAX	2700	/* 2.7 mHz */
+#define KEMPLD_I2C_FREQ_STD	100	/* 100 kHz */
+
+enum {
+	STATE_DONE = 0,
+	STATE_INIT,
+	STATE_ADDR,
+	STATE_ADDR10,
+	STATE_START,
+	STATE_WRITE,
+	STATE_READ,
+	STATE_ERROR,
+};
+
+struct kempld_i2c_data {
+	struct device			*dev;
+	struct kempld_device_data	*pld;
+	struct i2c_adapter		adap;
+	struct i2c_msg			*msg;
+	int				pos;
+	int				nmsgs;
+	int				state;
+	bool				was_active;
+};
+
+static unsigned int bus_frequency = KEMPLD_I2C_FREQ_STD;
+module_param(bus_frequency, uint, 0);
+MODULE_PARM_DESC(bus_frequency, "Set I2C bus frequency in kHz (default="
+				__MODULE_STRING(KEMPLD_I2C_FREQ_STD)")");
+
+static int i2c_bus = -1;
+module_param(i2c_bus, int, 0);
+MODULE_PARM_DESC(i2c_bus, "Set I2C bus number (default=-1 for dynamic assignment)");
+
+static bool i2c_gpio_mux;
+module_param(i2c_gpio_mux, bool, 0);
+MODULE_PARM_DESC(i2c_gpio_mux, "Enable I2C port on GPIO out (default=false)");
+
+/*
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static int kempld_i2c_process(struct kempld_i2c_data *i2c)
+{
+	struct kempld_device_data *pld = i2c->pld;
+	u8 stat = kempld_read8(pld, KEMPLD_I2C_STAT);
+	struct i2c_msg *msg = i2c->msg;
+	u8 addr;
+
+	/* Ready? */
+	if (stat & I2C_STAT_TIP)
+		return -EBUSY;
+
+	if (i2c->state == STATE_DONE || i2c->state == STATE_ERROR) {
+		/* Stop has been sent */
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_IACK);
+		if (i2c->state == STATE_ERROR)
+			return -EIO;
+		return 0;
+	}
+
+	/* Error? */
+	if (stat & I2C_STAT_ARBLOST) {
+		i2c->state = STATE_ERROR;
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_STOP);
+		return -EAGAIN;
+	}
+
+	if (i2c->state == STATE_INIT) {
+		if (stat & I2C_STAT_BUSY)
+			return -EBUSY;
+
+		i2c->state = STATE_ADDR;
+	}
+
+	if (i2c->state == STATE_ADDR) {
+		/* 10 bit address? */
+		if (i2c->msg->flags & I2C_M_TEN) {
+			addr = 0xf0 | ((i2c->msg->addr >> 7) & 0x6);
+			i2c->state = STATE_ADDR10;
+		} else {
+			addr = (i2c->msg->addr << 1);
+			i2c->state = STATE_START;
+		}
+
+		/* Set read bit if necessary */
+		addr |= (i2c->msg->flags & I2C_M_RD) ? 1 : 0;
+
+		kempld_write8(pld, KEMPLD_I2C_DATA, addr);
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_START);
+
+		return 0;
+	}
+
+	/* Second part of 10 bit addressing */
+	if (i2c->state == STATE_ADDR10) {
+		kempld_write8(pld, KEMPLD_I2C_DATA, i2c->msg->addr & 0xff);
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_WRITE);
+
+		i2c->state = STATE_START;
+		return 0;
+	}
+
+	if (i2c->state == STATE_START || i2c->state == STATE_WRITE) {
+		i2c->state = (msg->flags & I2C_M_RD) ? STATE_READ : STATE_WRITE;
+
+		if (stat & I2C_STAT_NACK) {
+			i2c->state = STATE_ERROR;
+			kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_STOP);
+			return -ENXIO;
+		}
+	} else {
+		msg->buf[i2c->pos++] = kempld_read8(pld, KEMPLD_I2C_DATA);
+	}
+
+	if (i2c->pos >= msg->len) {
+		i2c->nmsgs--;
+		i2c->msg++;
+		i2c->pos = 0;
+		msg = i2c->msg;
+
+		if (i2c->nmsgs) {
+			if (!(msg->flags & I2C_M_NOSTART)) {
+				i2c->state = STATE_ADDR;
+				return 0;
+			} else {
+				i2c->state = (msg->flags & I2C_M_RD)
+					? STATE_READ : STATE_WRITE;
+			}
+		} else {
+			i2c->state = STATE_DONE;
+			kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_STOP);
+			return 0;
+		}
+	}
+
+	if (i2c->state == STATE_READ) {
+		kempld_write8(pld, KEMPLD_I2C_CMD, i2c->pos == (msg->len - 1) ?
+			      I2C_CMD_READ_NACK : I2C_CMD_READ_ACK);
+	} else {
+		kempld_write8(pld, KEMPLD_I2C_DATA, msg->buf[i2c->pos++]);
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_WRITE);
+	}
+
+	return 0;
+}
+
+static int kempld_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+				int num)
+{
+	struct kempld_i2c_data *i2c = i2c_get_adapdata(adap);
+	struct kempld_device_data *pld = i2c->pld;
+	unsigned long timeout = jiffies + HZ;
+	int ret;
+
+	i2c->msg = msgs;
+	i2c->pos = 0;
+	i2c->nmsgs = num;
+	i2c->state = STATE_INIT;
+
+	/* Handle the transfer */
+	while (time_before(jiffies, timeout)) {
+		kempld_get_mutex(pld);
+		ret = kempld_i2c_process(i2c);
+		kempld_release_mutex(pld);
+
+		if (i2c->state == STATE_DONE || i2c->state == STATE_ERROR)
+			return (i2c->state == STATE_DONE) ? num : ret;
+
+		if (ret == 0)
+			timeout = jiffies + HZ;
+
+		usleep_range(5, 15);
+	}
+
+	i2c->state = STATE_ERROR;
+
+	return -ETIMEDOUT;
+}
+
+/*
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static void kempld_i2c_device_init(struct kempld_i2c_data *i2c)
+{
+	struct kempld_device_data *pld = i2c->pld;
+	u16 prescale_corr;
+	long prescale;
+	u8 ctrl;
+	u8 stat;
+	u8 cfg;
+
+	/* Make sure the device is disabled */
+	ctrl = kempld_read8(pld, KEMPLD_I2C_CTRL);
+	ctrl &= ~(I2C_CTRL_EN | I2C_CTRL_IEN);
+	kempld_write8(pld, KEMPLD_I2C_CTRL, ctrl);
+
+	if (bus_frequency > KEMPLD_I2C_FREQ_MAX)
+		bus_frequency = KEMPLD_I2C_FREQ_MAX;
+
+	if (pld->info.spec_major == 1)
+		prescale = pld->pld_clock / bus_frequency * 5 - 1000;
+	else
+		prescale = pld->pld_clock / bus_frequency * 4 - 3000;
+
+	if (prescale < 0)
+		prescale = 0;
+
+	/* Round to the best matching value */
+	prescale_corr = prescale / 1000;
+	if (prescale % 1000 >= 500)
+		prescale_corr++;
+
+	kempld_write8(pld, KEMPLD_I2C_PRELOW, prescale_corr & 0xff);
+	kempld_write8(pld, KEMPLD_I2C_PREHIGH, prescale_corr >> 8);
+
+	/* Activate I2C bus output on GPIO pins */
+	cfg = kempld_read8(pld, KEMPLD_CFG);
+	if (i2c_gpio_mux)
+		cfg |= KEMPLD_CFG_GPIO_I2C_MUX;
+	else
+		cfg &= ~KEMPLD_CFG_GPIO_I2C_MUX;
+	kempld_write8(pld, KEMPLD_CFG, cfg);
+
+	/* Enable the device */
+	kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_IACK);
+	ctrl |= I2C_CTRL_EN;
+	kempld_write8(pld, KEMPLD_I2C_CTRL, ctrl);
+
+	stat = kempld_read8(pld, KEMPLD_I2C_STAT);
+	if (stat & I2C_STAT_BUSY)
+		kempld_write8(pld, KEMPLD_I2C_CMD, I2C_CMD_STOP);
+}
+
+static u32 kempld_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm kempld_i2c_algorithm = {
+	.master_xfer	= kempld_i2c_xfer,
+	.functionality	= kempld_i2c_func,
+};
+
+static struct i2c_adapter kempld_i2c_adapter = {
+	.owner		= THIS_MODULE,
+	.name		= "i2c-kempld",
+	.class		= I2C_CLASS_HWMON | I2C_CLASS_SPD,
+	.algo		= &kempld_i2c_algorithm,
+};
+
+static int kempld_i2c_probe(struct platform_device *pdev)
+{
+	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
+	struct kempld_i2c_data *i2c;
+	int ret;
+	u8 ctrl;
+
+	i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
+	if (!i2c)
+		return -ENOMEM;
+
+	i2c->pld = pld;
+	i2c->dev = &pdev->dev;
+	i2c->adap = kempld_i2c_adapter;
+	i2c->adap.dev.parent = i2c->dev;
+	i2c_set_adapdata(&i2c->adap, i2c);
+	platform_set_drvdata(pdev, i2c);
+
+	kempld_get_mutex(pld);
+	ctrl = kempld_read8(pld, KEMPLD_I2C_CTRL);
+
+	if (ctrl & I2C_CTRL_EN)
+		i2c->was_active = true;
+
+	kempld_i2c_device_init(i2c);
+	kempld_release_mutex(pld);
+
+	/* Add I2C adapter to I2C tree */
+	if (i2c_bus >= -1)
+		i2c->adap.nr = i2c_bus;
+	ret = i2c_add_numbered_adapter(&i2c->adap);
+	if (ret)
+		return ret;
+
+	dev_info(i2c->dev, "I2C bus initialized at %dkHz\n",
+		 bus_frequency);
+
+	return 0;
+}
+
+static int kempld_i2c_remove(struct platform_device *pdev)
+{
+	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = i2c->pld;
+	u8 ctrl;
+
+	kempld_get_mutex(pld);
+	/*
+	 * Disable I2C logic if it was not activated before the
+	 * driver loaded
+	 */
+	if (!i2c->was_active) {
+		ctrl = kempld_read8(pld, KEMPLD_I2C_CTRL);
+		ctrl &= ~I2C_CTRL_EN;
+		kempld_write8(pld, KEMPLD_I2C_CTRL, ctrl);
+	}
+	kempld_release_mutex(pld);
+
+	i2c_del_adapter(&i2c->adap);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int kempld_i2c_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = i2c->pld;
+	u8 ctrl;
+
+	kempld_get_mutex(pld);
+	ctrl = kempld_read8(pld, KEMPLD_I2C_CTRL);
+	ctrl &= ~I2C_CTRL_EN;
+	kempld_write8(pld, KEMPLD_I2C_CTRL, ctrl);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_i2c_resume(struct platform_device *pdev)
+{
+	struct kempld_i2c_data *i2c = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = i2c->pld;
+
+	kempld_get_mutex(pld);
+	kempld_i2c_device_init(i2c);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+#else
+#define kempld_i2c_suspend	NULL
+#define kempld_i2c_resume	NULL
+#endif
+
+static struct platform_driver kempld_i2c_driver = {
+	.driver = {
+		.name = "kempld-i2c",
+		.owner = THIS_MODULE,
+	},
+	.probe		= kempld_i2c_probe,
+	.remove		= kempld_i2c_remove,
+	.suspend	= kempld_i2c_suspend,
+	.resume		= kempld_i2c_resume,
+};
+
+module_platform_driver(kempld_i2c_driver);
+
+MODULE_DESCRIPTION("KEM PLD I2C Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld_i2c");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v3 3/4] gpio: Kontron PLD gpio driver
  2013-06-24  4:00 ` [PATCH v3 0/4] Kontron PLD drivers Kevin Strasser
  2013-06-24  4:00   ` [PATCH v3 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
  2013-06-24  4:00   ` [PATCH v3 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
@ 2013-06-24  4:00   ` Kevin Strasser
  2013-07-21 14:31     ` Linus Walleij
  2013-06-24  4:00   ` [PATCH v3 4/4] watchdog: Kontron PLD watchdog timer driver Kevin Strasser
  2013-06-24 12:06   ` [PATCH v3 0/4] Kontron PLD drivers Samuel Ortiz
  4 siblings, 1 reply; 48+ messages in thread
From: Kevin Strasser @ 2013-06-24  4:00 UTC (permalink / raw)
  To: linux-kernel
  Cc: Guenter Roeck, Kevin Strasser, Kevin Strasser, Darren Hart,
	Samuel Ortiz, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Wolfram Sang, Ben Dooks,
	Grant Likely, Linus Walleij, Wim Van Sebroeck

From: Guenter Roeck <linux@roeck-us.net>

Add gpio support for the on-board PLD found on some Kontron embedded modules.

Originally-From: Michael Brunner <michael.brunner@kontron.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
Acked-by: Darren Hart <dvhart@linux.intel.com>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
---
 drivers/gpio/Kconfig       |   12 +++
 drivers/gpio/Makefile      |    1 +
 drivers/gpio/gpio-kempld.c |  225 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 238 insertions(+)
 create mode 100644 drivers/gpio/gpio-kempld.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 93aaadf..e94b266 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -658,6 +658,18 @@ config GPIO_UCB1400
 	  This enables support for the Philips UCB1400 GPIO pins.
 	  The UCB1400 is an AC97 audio codec.
 
+comment "LPC GPIO expanders:"
+
+config GPIO_KEMPLD
+	tristate "Kontron ETX / COMexpress GPIO"
+	depends on MFD_KEMPLD
+	help
+	  This enables support for the PLD GPIO interface on some Kontron ETX
+	  and COMexpress (ETXexpress) modules.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called gpio-kempld.
+
 comment "MODULbus GPIO expanders:"
 
 config GPIO_JANZ_TTL
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 22e07bc..758c348 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_GPIO_GE_FPGA)	+= gpio-ge.o
 obj-$(CONFIG_GPIO_ICH)		+= gpio-ich.o
 obj-$(CONFIG_GPIO_IT8761E)	+= gpio-it8761e.o
 obj-$(CONFIG_GPIO_JANZ_TTL)	+= gpio-janz-ttl.o
+obj-$(CONFIG_GPIO_KEMPLD)	+= gpio-kempld.o
 obj-$(CONFIG_ARCH_KS8695)	+= gpio-ks8695.o
 obj-$(CONFIG_GPIO_LANGWELL)	+= gpio-langwell.o
 obj-$(CONFIG_ARCH_LPC32XX)	+= gpio-lpc32xx.o
diff --git a/drivers/gpio/gpio-kempld.c b/drivers/gpio/gpio-kempld.c
new file mode 100644
index 0000000..1bdc3a2
--- /dev/null
+++ b/drivers/gpio/gpio-kempld.c
@@ -0,0 +1,225 @@
+/*
+ * Kontron PLD GPIO driver
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/mfd/kempld.h>
+
+#define KEMPLD_GPIO_MAX_NUM		16
+#define KEMPLD_GPIO_MASK(x)		(1 << ((x) % 8))
+#define KEMPLD_GPIO_DIR_NUM(x)		(0x40 + (x) / 8)
+#define KEMPLD_GPIO_LVL_NUM(x)		(0x42 + (x) / 8)
+#define KEMPLD_GPIO_EVT_LVL_EDGE	0x46
+#define KEMPLD_GPIO_IEN			0x4A
+
+struct kempld_gpio_data {
+	struct gpio_chip		chip;
+	struct kempld_device_data	*pld;
+};
+
+/*
+ * Set or clear GPIO bit
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static void kempld_gpio_bitop(struct kempld_device_data *pld,
+			      u8 reg, u8 bit, u8 val)
+{
+	u8 status;
+
+	status = kempld_read8(pld, reg);
+	if (val)
+		status |= (1 << bit);
+	else
+		status &= ~(1 << bit);
+	kempld_write8(pld, reg, status);
+}
+
+static int kempld_gpio_get_bit(struct kempld_device_data *pld, u8 reg, u8 bit)
+{
+	u8 status;
+
+	kempld_get_mutex(pld);
+	status = kempld_read8(pld, reg);
+	kempld_release_mutex(pld);
+
+	return !!(status & (1 << bit));
+}
+
+static int kempld_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+
+	return kempld_gpio_get_bit(pld, KEMPLD_GPIO_LVL_NUM(offset),
+				   KEMPLD_GPIO_MASK(offset));
+}
+
+static void kempld_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+
+	kempld_get_mutex(pld);
+	kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL_NUM(offset),
+			  KEMPLD_GPIO_MASK(offset), value);
+	kempld_release_mutex(pld);
+}
+
+static int kempld_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+
+	kempld_get_mutex(pld);
+	kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR_NUM(offset),
+			  KEMPLD_GPIO_MASK(offset), 0);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+					int value)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+
+	kempld_get_mutex(pld);
+	kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL_NUM(offset),
+			  KEMPLD_GPIO_MASK(offset), value);
+	kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR_NUM(offset),
+			  KEMPLD_GPIO_MASK(offset), 1);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+	struct kempld_gpio_data *gpio
+		= container_of(chip, struct kempld_gpio_data, chip);
+	struct kempld_device_data *pld = gpio->pld;
+
+	return kempld_gpio_get_bit(pld, KEMPLD_GPIO_DIR_NUM(offset),
+				   KEMPLD_GPIO_MASK(offset));
+}
+
+static int kempld_gpio_pincount(struct kempld_device_data *pld)
+{
+	u16 evt, evt_back;
+
+	kempld_get_mutex(pld);
+
+	/* Backup event register as it might be already initialized */
+	evt_back = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE);
+	/* Clear event register */
+	kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, 0x0000);
+	/* Read back event register */
+	evt = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE);
+	/* Restore event register */
+	kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, evt_back);
+
+	kempld_release_mutex(pld);
+
+	return evt ? __ffs(evt) : 16;
+}
+
+static int kempld_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct kempld_device_data *pld = dev_get_drvdata(dev->parent);
+	struct kempld_platform_data *pdata = pld->dev->platform_data;
+	struct kempld_gpio_data *gpio;
+	struct gpio_chip *chip;
+	int ret;
+
+	if (pld->info.spec_major < 2) {
+		dev_err(dev,
+			"Driver only supports GPIO devices compatible to PLD spec. rev. 2.0 or higher\n");
+		return -ENODEV;
+	}
+
+	gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+	if (gpio == NULL)
+		return -ENOMEM;
+
+	gpio->pld = pld;
+
+	platform_set_drvdata(pdev, gpio);
+
+	chip = &gpio->chip;
+	chip->label = "gpio-kempld";
+	chip->owner = THIS_MODULE;
+	chip->dev = dev;
+	chip->can_sleep = 1;
+	if (pdata && pdata->gpio_base)
+		chip->base = pdata->gpio_base;
+	else
+		chip->base = -1;
+	chip->direction_input = kempld_gpio_direction_input;
+	chip->direction_output = kempld_gpio_direction_output;
+	chip->get_direction = kempld_gpio_get_direction;
+	chip->get = kempld_gpio_get;
+	chip->set = kempld_gpio_set;
+	chip->ngpio = kempld_gpio_pincount(pld);
+	if (chip->ngpio == 0) {
+		dev_err(dev, "No GPIO pins detected\n");
+		return -ENODEV;
+	}
+
+	ret = gpiochip_add(chip);
+	if (ret) {
+		dev_err(dev, "Could not register GPIO chip\n");
+		return ret;
+	}
+
+	dev_info(dev, "GPIO functionality initialized with %d pins\n",
+		 chip->ngpio);
+
+	return 0;
+}
+
+static int kempld_gpio_remove(struct platform_device *pdev)
+{
+	struct kempld_gpio_data *gpio = platform_get_drvdata(pdev);
+
+	return gpiochip_remove(&gpio->chip);
+}
+
+static struct platform_driver kempld_gpio_driver = {
+	.driver = {
+		.name = "gpio-kempld",
+		.owner = THIS_MODULE,
+	},
+	.probe		= kempld_gpio_probe,
+	.remove		= kempld_gpio_remove,
+};
+
+module_platform_driver(kempld_gpio_driver);
+
+MODULE_DESCRIPTION("KEM PLD GPIO Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-kempld");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v3 4/4] watchdog: Kontron PLD watchdog timer driver
  2013-06-24  4:00 ` [PATCH v3 0/4] Kontron PLD drivers Kevin Strasser
                     ` (2 preceding siblings ...)
  2013-06-24  4:00   ` [PATCH v3 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
@ 2013-06-24  4:00   ` Kevin Strasser
  2013-06-27 18:23     ` Kevin Strasser
  2013-06-24 12:06   ` [PATCH v3 0/4] Kontron PLD drivers Samuel Ortiz
  4 siblings, 1 reply; 48+ messages in thread
From: Kevin Strasser @ 2013-06-24  4:00 UTC (permalink / raw)
  To: linux-kernel
  Cc: Kevin Strasser, Kevin Strasser, Darren Hart, Samuel Ortiz,
	Guenter Roeck, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Wolfram Sang, Ben Dooks,
	Grant Likely, Linus Walleij, Wim Van Sebroeck, linux-watchdog

Add watchdog timer support for the on-board PLD found on some Kontron embedded
modules.

Originally-From: Michael Brunner <michael.brunner@kontron.com>
Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Darren Hart <dvhart@linux.intel.com>
---
 drivers/watchdog/Kconfig      |   11 +
 drivers/watchdog/Makefile     |    1 +
 drivers/watchdog/kempld_wdt.c |  580 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 592 insertions(+)
 create mode 100644 drivers/watchdog/kempld_wdt.c

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index e89fc31..7460d34 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -687,6 +687,17 @@ config HP_WATCHDOG
 	  To compile this driver as a module, choose M here: the module will be
 	  called hpwdt.
 
+config KEMPLD_WDT
+	tristate "Kontron COM Watchdog Timer"
+	depends on MFD_KEMPLD
+	select WATCHDOG_CORE
+	help
+	  Support for the PLD watchdog on some Kontron ETX and COMexpress
+	  (ETXexpress) modules
+
+	  This driver can also be built as a module. If so, the module will be
+	  called kempld_wdt.
+
 config HPWDT_NMI_DECODING
 	bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer"
 	depends on HP_WATCHDOG
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index a300b94..ec26899 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -90,6 +90,7 @@ endif
 obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
 obj-$(CONFIG_IT87_WDT) += it87_wdt.o
 obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
 obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
 obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
 obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
diff --git a/drivers/watchdog/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c
new file mode 100644
index 0000000..96e9a48
--- /dev/null
+++ b/drivers/watchdog/kempld_wdt.c
@@ -0,0 +1,580 @@
+/*
+ * Kontron PLD watchdog driver
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Note: From the PLD watchdog point of view timeout and pretimeout are
+ *       defined differently than in the kernel.
+ *       First the pretimeout stage runs out before the timeout stage gets
+ *       active.
+ *
+ * Kernel/API:                     P-----| pretimeout
+ *               |-----------------------T timeout
+ * Watchdog:     |-----------------P       pretimeout_stage
+ *                                 |-----T timeout_stage
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/kempld.h>
+
+#define KEMPLD_WDT_STAGE_TIMEOUT(x)	(0x1b + (x) * 4)
+#define KEMPLD_WDT_STAGE_CFG(x)		(0x18 + (x))
+#define STAGE_CFG_GET_PRESCALER(x)	(((x) & 0x30) >> 4)
+#define STAGE_CFG_SET_PRESCALER(x)	(((x) & 0x30) << 4)
+#define STAGE_CFG_PRESCALER_MASK	0x30
+#define STAGE_CFG_ACTION_MASK		0x7
+#define STAGE_CFG_ASSERT		(1 << 3)
+
+#define KEMPLD_WDT_MAX_STAGES		2
+#define KEMPLD_WDT_KICK			0x16
+#define KEMPLD_WDT_CFG			0x17
+#define KEMPLD_WDT_CFG_ENABLE		0x10
+#define KEMPLD_WDT_CFG_ENABLE_LOCK	0x8
+#define KEMPLD_WDT_CFG_GLOBAL_LOCK	0x80
+
+enum {
+	ACTION_NONE = 0,
+	ACTION_RESET,
+	ACTION_NMI,
+	ACTION_SMI,
+	ACTION_SCI,
+	ACTION_DELAY,
+};
+
+enum {
+	STAGE_TIMEOUT = 0,
+	STAGE_PRETIMEOUT,
+};
+
+enum {
+	PRESCALER_21 = 0,
+	PRESCALER_17,
+	PRESCALER_12,
+};
+
+const u32 kempld_prescaler[] = {
+	[PRESCALER_21] = (1 << 21) - 1,
+	[PRESCALER_17] = (1 << 17) - 1,
+	[PRESCALER_12] = (1 << 12) - 1,
+	0,
+};
+
+struct kempld_wdt_stage {
+	unsigned int	id;
+	u32		mask;
+};
+
+struct kempld_wdt_data {
+	struct kempld_device_data	*pld;
+	struct watchdog_device		wdd;
+	unsigned int			pretimeout;
+	struct kempld_wdt_stage		stage[KEMPLD_WDT_MAX_STAGES];
+#ifdef CONFIG_PM
+	u8				pm_status_store;
+#endif
+};
+
+#define DEFAULT_TIMEOUT		30 /* seconds */
+#define DEFAULT_PRETIMEOUT	0
+
+static unsigned int timeout = DEFAULT_TIMEOUT;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+	"Watchdog timeout in seconds. (>=0, default="
+	__MODULE_STRING(DEFAULT_TIMEOUT) ")");
+
+static unsigned int pretimeout = DEFAULT_PRETIMEOUT;
+module_param(pretimeout, uint, 0);
+MODULE_PARM_DESC(pretimeout,
+	"Watchdog pretimeout in seconds. (>=0, default="
+	__MODULE_STRING(DEFAULT_PRETIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+	"Watchdog cannot be stopped once started (default="
+	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static int kempld_wdt_set_stage_action(struct kempld_wdt_data *wdt_data,
+					struct kempld_wdt_stage *stage,
+					u8 action)
+{
+	struct kempld_device_data *pld = wdt_data->pld;
+	u8 stage_cfg;
+
+	if (!stage || !stage->mask)
+		return -EINVAL;
+
+	kempld_get_mutex(pld);
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
+	stage_cfg &= ~STAGE_CFG_ACTION_MASK;
+	stage_cfg |= (action & STAGE_CFG_ACTION_MASK);
+
+	if (action == ACTION_RESET)
+		stage_cfg |= STAGE_CFG_ASSERT;
+	else
+		stage_cfg &= ~STAGE_CFG_ASSERT;
+
+	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_wdt_set_stage_timeout(struct kempld_wdt_data *wdt_data,
+					struct kempld_wdt_stage *stage,
+					unsigned int timeout)
+{
+	struct kempld_device_data *pld = wdt_data->pld;
+	u32 prescaler = kempld_prescaler[PRESCALER_21];
+	u64 stage_timeout64;
+	u32 stage_timeout;
+	u32 remainder;
+	u8 stage_cfg;
+
+	if (!stage)
+		return -EINVAL;
+
+	stage_timeout64 = (u64)timeout * pld->pld_clock;
+	remainder = do_div(stage_timeout64, prescaler);
+	if (remainder)
+		stage_timeout64++;
+
+	if (stage_timeout64 > stage->mask)
+		return -EINVAL;
+
+	stage_timeout = stage_timeout64 & stage->mask;
+
+	kempld_get_mutex(pld);
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
+	stage_cfg &= ~STAGE_CFG_PRESCALER_MASK;
+	stage_cfg |= STAGE_CFG_SET_PRESCALER(prescaler);
+	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
+	kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id),
+			stage_timeout);
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+/*
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static unsigned int kempld_wdt_get_timeout(struct kempld_wdt_data *wdt_data,
+						struct kempld_wdt_stage *stage)
+{
+	struct kempld_device_data *pld = wdt_data->pld;
+	unsigned int timeout;
+	u64 stage_timeout;
+	u32 prescaler;
+	u32 remainder;
+	u8 stage_cfg;
+
+	if (!stage->mask)
+		return 0;
+
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
+	stage_timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id));
+	prescaler = kempld_prescaler[STAGE_CFG_GET_PRESCALER(stage_cfg)];
+
+	stage_timeout = (stage_timeout & stage->mask) * prescaler;
+	remainder = do_div(stage_timeout, pld->pld_clock);
+	if (remainder)
+		stage_timeout++;
+
+	timeout = stage_timeout;
+	WARN_ON_ONCE(timeout != stage_timeout);
+
+	return timeout;
+}
+
+static int kempld_wdt_set_timeout(struct watchdog_device *wdd,
+					unsigned int timeout)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_wdt_stage *pretimeout_stage;
+	struct kempld_wdt_stage *timeout_stage;
+	int ret;
+
+	timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
+	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+
+	if (pretimeout_stage->mask && wdt_data->pretimeout > 0)
+		timeout = wdt_data->pretimeout;
+
+	ret = kempld_wdt_set_stage_action(wdt_data, timeout_stage,
+						ACTION_RESET);
+	if (ret)
+		return ret;
+	ret = kempld_wdt_set_stage_timeout(wdt_data, timeout_stage,
+						timeout);
+	if (ret)
+		return ret;
+
+	wdd->timeout = timeout;
+	return 0;
+}
+
+static int kempld_wdt_set_pretimeout(struct watchdog_device *wdd,
+					unsigned int pretimeout)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_wdt_stage *pretimeout_stage;
+	u8 action = ACTION_NONE;
+	int ret;
+
+	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+
+	if (!pretimeout_stage->mask)
+		return -ENXIO;
+
+	if (pretimeout > wdd->timeout)
+		return -EINVAL;
+
+	if (pretimeout > 0)
+		action = ACTION_NMI;
+
+	ret = kempld_wdt_set_stage_action(wdt_data, pretimeout_stage,
+						action);
+	if (ret)
+		return ret;
+	ret = kempld_wdt_set_stage_timeout(wdt_data, pretimeout_stage,
+						wdd->timeout - pretimeout);
+	if (ret)
+		return ret;
+
+	wdt_data->pretimeout = pretimeout;
+	return 0;
+}
+
+static void kempld_wdt_update_timeouts(struct kempld_wdt_data *wdt_data)
+{
+	struct kempld_device_data *pld = wdt_data->pld;
+	struct kempld_wdt_stage *pretimeout_stage;
+	struct kempld_wdt_stage *timeout_stage;
+	unsigned int pretimeout, timeout;
+
+	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+	timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
+
+	kempld_get_mutex(pld);
+	pretimeout = kempld_wdt_get_timeout(wdt_data, pretimeout_stage);
+	timeout = kempld_wdt_get_timeout(wdt_data, timeout_stage);
+	kempld_release_mutex(pld);
+
+	if (pretimeout)
+		wdt_data->pretimeout = timeout;
+	else
+		wdt_data->pretimeout = 0;
+
+	wdt_data->wdd.timeout = pretimeout + timeout;
+}
+
+static int kempld_wdt_start(struct watchdog_device *wdd)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_device_data *pld = wdt_data->pld;
+	u8 status;
+	int ret;
+
+	ret = kempld_wdt_set_timeout(wdd, wdd->timeout);
+	if (ret)
+		return ret;
+
+	kempld_get_mutex(pld);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	status |= KEMPLD_WDT_CFG_ENABLE;
+	kempld_write8(pld, KEMPLD_WDT_CFG, status);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	/* Check if the watchdog was enabled */
+	if (!(status & KEMPLD_WDT_CFG_ENABLE))
+		return -EACCES;
+
+	return 0;
+}
+
+static int kempld_wdt_stop(struct watchdog_device *wdd)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_device_data *pld = wdt_data->pld;
+	u8 status;
+
+	kempld_get_mutex(pld);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	status &= ~KEMPLD_WDT_CFG_ENABLE;
+	kempld_write8(pld, KEMPLD_WDT_CFG, status);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	/* Check if the watchdog was disabled */
+	if (status & KEMPLD_WDT_CFG_ENABLE)
+		return -EACCES;
+
+	return 0;
+}
+
+static int kempld_wdt_keepalive(struct watchdog_device *wdd)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_device_data *pld = wdt_data->pld;
+
+	kempld_get_mutex(pld);
+	kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static long kempld_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd,
+				unsigned long arg)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	void __user *argp = (void __user *)arg;
+	int ret = -ENOIOCTLCMD;
+	int __user *p = argp;
+	int new_value;
+
+	switch (cmd) {
+	case WDIOC_SETPRETIMEOUT:
+		if (get_user(new_value, p))
+			return -EFAULT;
+		ret = kempld_wdt_set_pretimeout(wdd, new_value);
+		if (ret)
+			return ret;
+		ret = kempld_wdt_keepalive(wdd);
+		break;
+	case WDIOC_GETPRETIMEOUT:
+		ret = put_user(wdt_data->pretimeout, (int *)arg);
+		break;
+	}
+
+	return ret;
+}
+
+static int kempld_wdt_probe_stages(struct watchdog_device *wdd)
+{
+	struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+	struct kempld_device_data *pld = wdt_data->pld;
+	struct kempld_wdt_stage *pretimeout_stage;
+	struct kempld_wdt_stage *timeout_stage;
+	u8 index, data, data_orig;
+	u32 mask;
+	int i, j;
+
+	pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+	timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
+
+	pretimeout_stage->mask = 0;
+	timeout_stage->mask = 0;
+
+	for (i = 0; i < 3; i++) {
+		index = KEMPLD_WDT_STAGE_TIMEOUT(i);
+		mask = 0;
+
+		kempld_get_mutex(pld);
+		/* Probe each byte individually. */
+		for (j = 0; j < 4; j++) {
+			data_orig = kempld_read8(pld, index + j);
+			kempld_write8(pld, index + j, 0x00);
+			data = kempld_read8(pld, index + j);
+			/* A failed write means this byte is reserved */
+			if (data != 0x00)
+				break;
+			kempld_write8(pld, index + j, data_orig);
+			mask |= 0xff << (j * 8);
+		}
+		kempld_release_mutex(pld);
+
+		/* Assign available stages to timeout and pretimeout */
+		if (!timeout_stage->mask) {
+			timeout_stage->mask = mask;
+			timeout_stage->id = i;
+		} else {
+			if (pld->feature_mask & KEMPLD_FEATURE_BIT_NMI) {
+				pretimeout_stage->mask = timeout_stage->mask;
+				timeout_stage->mask = mask;
+				pretimeout_stage->id = timeout_stage->id;
+				timeout_stage->id = i;
+			}
+			break;
+		}
+	}
+
+	if (!timeout_stage->mask)
+		return -ENODEV;
+
+	return 0;
+}
+
+static struct watchdog_info kempld_wdt_info = {
+	.identity	= "KEMPLD Watchdog",
+	.options	= WDIOF_SETTIMEOUT |
+			WDIOF_KEEPALIVEPING |
+			WDIOF_MAGICCLOSE |
+			WDIOF_PRETIMEOUT
+};
+
+static struct watchdog_ops kempld_wdt_ops = {
+	.owner		= THIS_MODULE,
+	.start		= kempld_wdt_start,
+	.stop		= kempld_wdt_stop,
+	.ping		= kempld_wdt_keepalive,
+	.set_timeout	= kempld_wdt_set_timeout,
+	.ioctl		= kempld_wdt_ioctl,
+};
+
+static int kempld_wdt_probe(struct platform_device *pdev)
+{
+	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
+	struct kempld_wdt_data *wdt_data;
+	struct device *dev = &pdev->dev;
+	struct watchdog_device *wdd;
+	u8 status;
+	int ret = 0;
+
+	wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL);
+	if (!wdt_data)
+		return -ENOMEM;
+
+	wdt_data->pld = pld;
+	wdd = &wdt_data->wdd;
+	wdd->parent = dev;
+
+	kempld_get_mutex(pld);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	/* Enable nowayout if watchdog is already locked */
+	if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
+			KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
+		if (!nowayout)
+			dev_warn(dev,
+				"Forcing nowayout - watchdog lock enabled!\n");
+		nowayout = true;
+	}
+
+	wdd->info = &kempld_wdt_info;
+	wdd->ops = &kempld_wdt_ops;
+
+	watchdog_set_drvdata(wdd, wdt_data);
+	watchdog_set_nowayout(wdd, nowayout);
+
+	ret = kempld_wdt_probe_stages(wdd);
+	if (ret)
+		return ret;
+
+	kempld_wdt_set_timeout(wdd, timeout);
+	kempld_wdt_set_pretimeout(wdd, pretimeout);
+
+	/* Check if watchdog is already enabled */
+	if (status & KEMPLD_WDT_CFG_ENABLE) {
+		/* Get current watchdog settings */
+		kempld_wdt_update_timeouts(wdt_data);
+		dev_info(dev, "Watchdog was already enabled\n");
+	}
+
+	platform_set_drvdata(pdev, wdt_data);
+	ret = watchdog_register_device(wdd);
+	if (ret)
+		return ret;
+
+	dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout);
+
+	return 0;
+}
+
+static void kempld_wdt_shutdown(struct platform_device *pdev)
+{
+	struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
+
+	kempld_wdt_stop(&wdt_data->wdd);
+}
+
+static int kempld_wdt_remove(struct platform_device *pdev)
+{
+	struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
+	struct watchdog_device *wdd = &wdt_data->wdd;
+	int ret = 0;
+
+	if (!nowayout)
+		ret = kempld_wdt_stop(wdd);
+	watchdog_unregister_device(wdd);
+
+	return ret;
+}
+
+#ifdef CONFIG_PM
+/* Disable watchdog if it is active during suspend */
+static int kempld_wdt_suspend(struct platform_device *pdev,
+				pm_message_t message)
+{
+	struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = wdt_data->pld;
+
+	kempld_get_mutex(pld);
+	wdt_data->pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	kempld_wdt_update_timeouts(wdt_data);
+
+	if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
+		return kempld_wdt_stop(pdev);
+
+	return 0;
+}
+
+/* Enable watchdog and configure it if necessary */
+static int kempld_wdt_resume(struct platform_device *pdev)
+{
+	struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
+	struct watchdog_device *wdd = &wdt_data->wdd;
+
+	/*
+	 * If watchdog was stopped before suspend be sure it gets disabled
+	 * again, for the case BIOS has enabled it during resume
+	 */
+	if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
+		return kempld_wdt_start(wdd);
+	else
+		return kempld_wdt_stop(wdd);
+}
+#else
+#define kempld_wdt_suspend	NULL
+#define kempld_wdt_resume	NULL
+#endif
+
+static struct platform_driver kempld_wdt_driver = {
+	.driver		= {
+		.name	= "kempld-wdt",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= kempld_wdt_probe,
+	.remove		= kempld_wdt_remove,
+	.shutdown	= kempld_wdt_shutdown,
+	.suspend	= kempld_wdt_suspend,
+	.resume		= kempld_wdt_resume,
+};
+
+module_platform_driver(kempld_wdt_driver);
+
+MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCH v3 0/4] Kontron PLD drivers
  2013-06-24  4:00 ` [PATCH v3 0/4] Kontron PLD drivers Kevin Strasser
                     ` (3 preceding siblings ...)
  2013-06-24  4:00   ` [PATCH v3 4/4] watchdog: Kontron PLD watchdog timer driver Kevin Strasser
@ 2013-06-24 12:06   ` Samuel Ortiz
  2013-06-24 16:09     ` Wolfram Sang
  2013-06-27 20:34     ` Wim Van Sebroeck
  4 siblings, 2 replies; 48+ messages in thread
From: Samuel Ortiz @ 2013-06-24 12:06 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Kevin Strasser, Darren Hart, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, linux-i2c, linux-watchdog

Hi Kevin,

On Sun, Jun 23, 2013 at 09:00:02PM -0700, Kevin Strasser wrote:
> Changes since v2:
> -Change Michael's "Signed-off-by" to "Originally-From" in all patches
> -Add "From: Guenter Roeck <linux@roeck-us.net>" to gpio patch
> 
> Guenter Roeck (1):
>   gpio: Kontron PLD gpio driver
> 
> Kevin Strasser (3):
>   mfd: Kontron PLD mfd driver
>   i2c: Kontron PLD i2c bus driver
>   watchdog: Kontron PLD watchdog timer driver
I just applied (after a warning fix) and pushed the MFD driver, thanks.

I am happy to take the sub devices driver as well, although the
respective maintainers can safely carry them as they all have a Kconfig
dependency on MFD_KEMPLD. So it's up to them.
If I have to take them, I'd need Wolfram's ACK for the i2c one and Wim's
for the watchdog one. Linus already ACKed the GPIO one.

Cheers,
Samuel.

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v3 0/4] Kontron PLD drivers
  2013-06-24 12:06   ` [PATCH v3 0/4] Kontron PLD drivers Samuel Ortiz
@ 2013-06-24 16:09     ` Wolfram Sang
  2013-06-27 20:34     ` Wim Van Sebroeck
  1 sibling, 0 replies; 48+ messages in thread
From: Wolfram Sang @ 2013-06-24 16:09 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: Kevin Strasser, linux-kernel, Kevin Strasser, Darren Hart,
	Guenter Roeck, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Ben Dooks, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, linux-i2c, linux-watchdog

[-- Attachment #1: Type: text/plain, Size: 237 bytes --]


> I am happy to take the sub devices driver as well, although the
> respective maintainers can safely carry them as they all have a Kconfig
> dependency on MFD_KEMPLD. So it's up to them.

I'd like to push it via the i2c tree. Thanks!


[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v3 4/4] watchdog: Kontron PLD watchdog timer driver
  2013-06-24  4:00   ` [PATCH v3 4/4] watchdog: Kontron PLD watchdog timer driver Kevin Strasser
@ 2013-06-27 18:23     ` Kevin Strasser
  2013-06-27 21:47       ` Samuel Ortiz
  0 siblings, 1 reply; 48+ messages in thread
From: Kevin Strasser @ 2013-06-27 18:23 UTC (permalink / raw)
  To: Wim Van Sebroeck
  Cc: Kevin Strasser, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Linus Walleij, linux-watchdog, linux-kernel

Hi Wim,

This driver hasn't received any further feedback. Are you ready to take it
into watchdog-next, or do you still need to review it?

Thanks,
Kevin

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v3 0/4] Kontron PLD drivers
  2013-06-24 12:06   ` [PATCH v3 0/4] Kontron PLD drivers Samuel Ortiz
  2013-06-24 16:09     ` Wolfram Sang
@ 2013-06-27 20:34     ` Wim Van Sebroeck
  2013-06-27 21:48       ` Samuel Ortiz
  1 sibling, 1 reply; 48+ messages in thread
From: Wim Van Sebroeck @ 2013-06-27 20:34 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: Kevin Strasser, linux-kernel, Kevin Strasser, Darren Hart,
	Guenter Roeck, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Wolfram Sang, Ben Dooks,
	Grant Likely, Linus Walleij, linux-i2c, linux-watchdog

Hi All,

> > Changes since v2:
> > -Change Michael's "Signed-off-by" to "Originally-From" in all patches
> > -Add "From: Guenter Roeck <linux@roeck-us.net>" to gpio patch
> > 
> > Guenter Roeck (1):
> >   gpio: Kontron PLD gpio driver
> > 
> > Kevin Strasser (3):
> >   mfd: Kontron PLD mfd driver
> >   i2c: Kontron PLD i2c bus driver
> >   watchdog: Kontron PLD watchdog timer driver
> I just applied (after a warning fix) and pushed the MFD driver, thanks.
> 
> I am happy to take the sub devices driver as well, although the
> respective maintainers can safely carry them as they all have a Kconfig
> dependency on MFD_KEMPLD. So it's up to them.
> If I have to take them, I'd need Wolfram's ACK for the i2c one and Wim's
> for the watchdog one. Linus already ACKed the GPIO one.

Acked-by: Wim Van Sebroeck <wim@iguana.be>

I'm OK that itgoes via the MFD tree.

Kind regards,
Wim.


^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v3 4/4] watchdog: Kontron PLD watchdog timer driver
  2013-06-27 18:23     ` Kevin Strasser
@ 2013-06-27 21:47       ` Samuel Ortiz
  2013-06-27 22:05         ` Kevin Strasser
  0 siblings, 1 reply; 48+ messages in thread
From: Samuel Ortiz @ 2013-06-27 21:47 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: Wim Van Sebroeck, Kevin Strasser, Darren Hart, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Linus Walleij, linux-watchdog, linux-kernel

Hi Kevin,

On Thu, Jun 27, 2013 at 11:23:15AM -0700, Kevin Strasser wrote:
> Hi Wim,
> 
> This driver hasn't received any further feedback. Are you ready to take it
> into watchdog-next, or do you still need to review it?
I applied the watchdog patch to mfd-next and got the following warning:

 CC [M]  drivers/watchdog/kempld_wdt.o
drivers/watchdog/kempld_wdt.c: In function ‘kempld_wdt_suspend’:
drivers/watchdog/kempld_wdt.c:538:3: warning: passing argument 1 of
‘kempld_wdt_stop’ from incompatible pointer type [enabled by default]
drivers/watchdog/kempld_wdt.c:313:12: note: expected ‘struct
watchdog_device *’ but argument is of type ‘struct platform_device *’

I fixed it up, but I'd appreciate if you could double check that
kempld_wdt_suspend() is ok.

Cheers,
Samuel.

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v3 0/4] Kontron PLD drivers
  2013-06-27 20:34     ` Wim Van Sebroeck
@ 2013-06-27 21:48       ` Samuel Ortiz
  0 siblings, 0 replies; 48+ messages in thread
From: Samuel Ortiz @ 2013-06-27 21:48 UTC (permalink / raw)
  To: Wim Van Sebroeck
  Cc: Kevin Strasser, linux-kernel, Kevin Strasser, Darren Hart,
	Guenter Roeck, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Wolfram Sang, Ben Dooks,
	Grant Likely, Linus Walleij, linux-i2c, linux-watchdog

Hi Wim,

On Thu, Jun 27, 2013 at 10:34:36PM +0200, Wim Van Sebroeck wrote:
> Hi All,
> 
> > > Changes since v2:
> > > -Change Michael's "Signed-off-by" to "Originally-From" in all patches
> > > -Add "From: Guenter Roeck <linux@roeck-us.net>" to gpio patch
> > > 
> > > Guenter Roeck (1):
> > >   gpio: Kontron PLD gpio driver
> > > 
> > > Kevin Strasser (3):
> > >   mfd: Kontron PLD mfd driver
> > >   i2c: Kontron PLD i2c bus driver
> > >   watchdog: Kontron PLD watchdog timer driver
> > I just applied (after a warning fix) and pushed the MFD driver, thanks.
> > 
> > I am happy to take the sub devices driver as well, although the
> > respective maintainers can safely carry them as they all have a Kconfig
> > dependency on MFD_KEMPLD. So it's up to them.
> > If I have to take them, I'd need Wolfram's ACK for the i2c one and Wim's
> > for the watchdog one. Linus already ACKed the GPIO one.
> 
> Acked-by: Wim Van Sebroeck <wim@iguana.be>
> 
> I'm OK that itgoes via the MFD tree.
I took it, thanks for your ACK.

Cheers,
Samuel.

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v3 4/4] watchdog: Kontron PLD watchdog timer driver
  2013-06-27 21:47       ` Samuel Ortiz
@ 2013-06-27 22:05         ` Kevin Strasser
  0 siblings, 0 replies; 48+ messages in thread
From: Kevin Strasser @ 2013-06-27 22:05 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: Wim Van Sebroeck, Kevin Strasser, Darren Hart, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Linus Walleij, linux-watchdog, linux-kernel

Hi Samuel,

On Thu, Jun 27, 2013 at 11:47:05PM +0200, Samuel Ortiz wrote:
> Hi Kevin,
> 
> On Thu, Jun 27, 2013 at 11:23:15AM -0700, Kevin Strasser wrote:
> > Hi Wim,
> > 
> > This driver hasn't received any further feedback. Are you ready to take it
> > into watchdog-next, or do you still need to review it?
> I applied the watchdog patch to mfd-next and got the following warning:
> 
>  CC [M]  drivers/watchdog/kempld_wdt.o
> drivers/watchdog/kempld_wdt.c: In function ‘kempld_wdt_suspend’:
> drivers/watchdog/kempld_wdt.c:538:3: warning: passing argument 1 of
> ‘kempld_wdt_stop’ from incompatible pointer type [enabled by default]
> drivers/watchdog/kempld_wdt.c:313:12: note: expected ‘struct
> watchdog_device *’ but argument is of type ‘struct platform_device *’
> 
> I fixed it up, but I'd appreciate if you could double check that
> kempld_wdt_suspend() is ok.

Ah, yes you made the appropriate change. This used to be a call to 
kempld_wdt_shutdown, which I changed to kempld_wdt_stop to avoid the
pointless redirection. Somehow I forgot to convert the argument.

Thanks,
Kevin

> 
> Cheers,
> Samuel.
> 
> -- 
> Intel Open Source Technology Centre
> http://oss.intel.com/

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 3/4] gpio: Kontron PLD gpio driver
  2013-06-19  8:36     ` Linus Walleij
@ 2013-06-27 22:14       ` Kevin Strasser
  0 siblings, 0 replies; 48+ messages in thread
From: Kevin Strasser @ 2013-06-27 22:14 UTC (permalink / raw)
  To: Linus Walleij
  Cc: linux-kernel, Darren Hart, Samuel Ortiz, Guenter Roeck,
	Michael Brunner, Michael Brunner, Chris Healy, Thomas Gleixner,
	Dirk Hohndel, Wolfram Sang, Ben Dooks, Grant Likely,
	Wim Van Sebroeck

Hi Linus,

On Wed, Jun 19, 2013 at 10:36:34AM +0200, Linus Walleij wrote:
> On Tue, Jun 18, 2013 at 11:04 PM, Kevin Strasser
> <kevin.strasser@linux.intel.com> wrote:
> 
> > Add gpio support for the on-board PLD found on some Kontron embedded modules.
> >
> > Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> > Acked-by: Darren Hart <dvhart@linux.intel.com>
> 
> This is looking good.
> Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Thanks for the review. Are you planning on taking this in gpio-next,
or would you prefer that it goes through mfd?

Thanks,
Kevin

> 
> Yours,
> Linus Walleij

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v3 2/4] i2c: Kontron PLD i2c bus driver
  2013-06-24  4:00   ` [PATCH v3 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
@ 2013-07-01  6:40     ` Wolfram Sang
  0 siblings, 0 replies; 48+ messages in thread
From: Wolfram Sang @ 2013-07-01  6:40 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Kevin Strasser, Darren Hart, Samuel Ortiz,
	Guenter Roeck, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Ben Dooks, Grant Likely,
	Linus Walleij, Wim Van Sebroeck, linux-i2c

[-- Attachment #1: Type: text/plain, Size: 416 bytes --]

On Sun, Jun 23, 2013 at 09:00:04PM -0700, Kevin Strasser wrote:
> Add i2c support for the on-board PLD found on some Kontron embedded
> modules.
> 
> Originally-From: Michael Brunner <michael.brunner@kontron.com>
> Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> Acked-by: Guenter Roeck <linux@roeck-us.net>
> Acked-by: Darren Hart <dvhart@linux.intel.com>

Applied to for-next, thanks!


[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v3 3/4] gpio: Kontron PLD gpio driver
  2013-06-24  4:00   ` [PATCH v3 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
@ 2013-07-21 14:31     ` Linus Walleij
  0 siblings, 0 replies; 48+ messages in thread
From: Linus Walleij @ 2013-07-21 14:31 UTC (permalink / raw)
  To: Kevin Strasser
  Cc: linux-kernel, Guenter Roeck, Kevin Strasser, Darren Hart,
	Samuel Ortiz, Michael Brunner, Michael Brunner, Chris Healy,
	Thomas Gleixner, Dirk Hohndel, Wolfram Sang, Ben Dooks,
	Grant Likely, Wim Van Sebroeck

On Mon, Jun 24, 2013 at 6:00 AM, Kevin Strasser
<kevin.strasser@linux.intel.com> wrote:

> From: Guenter Roeck <linux@roeck-us.net>
>
> Add gpio support for the on-board PLD found on some Kontron embedded modules.
>
> Originally-From: Michael Brunner <michael.brunner@kontron.com>
> Signed-off-by: Guenter Roeck <linux@roeck-us.net>
> Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> Acked-by: Darren Hart <dvhart@linux.intel.com>
> Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Applied this to the GPIO tree now that the deps are upstream.

I did actually think that Sam would take this through MFD but
maybe I was not clear enough :-(

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 48+ messages in thread

end of thread, other threads:[~2013-07-21 14:31 UTC | newest]

Thread overview: 48+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-04-08 17:15 [PATCH 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
2013-04-08 17:15 ` [PATCH 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
2013-04-10 17:02   ` Guenter Roeck
2013-04-16  9:53     ` Wolfram Sang
2013-04-08 17:15 ` [PATCH 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
2013-04-09  8:46   ` Linus Walleij
2013-04-09 16:41     ` Guenter Roeck
2013-04-10 20:06       ` Linus Walleij
2013-04-10 20:45   ` Linus Walleij
2013-04-12 11:09     ` Michael Brunner
2013-04-12 22:05       ` Linus Walleij
2013-04-08 17:15 ` [PATCH 4/4] watchdog: Kontron PLD watchdog timer Kevin Strasser
2013-04-10 16:47   ` Guenter Roeck
2013-04-10 16:57     ` Kevin Strasser
2013-05-26 14:38       ` Wim Van Sebroeck
2013-04-13 20:38 ` [PATCH 1/4] mfd: Kontron PLD mfd driver Thomas Gleixner
2013-04-18  4:19   ` Guenter Roeck
2013-04-18  4:40     ` Joe Perches
2013-04-18 13:35       ` Guenter Roeck
2013-04-18 16:42         ` Joe Perches
2013-04-18 18:40           ` Guenter Roeck
2013-06-18 21:04 ` [PATCH v2 0/4] Kontron PLD drivers Kevin Strasser
2013-06-18 21:04   ` [PATCH v2 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
2013-06-19  8:40     ` Linus Walleij
2013-06-19  9:11       ` Samuel Ortiz
2013-06-19  9:48         ` Mark Brown
2013-06-19  9:12     ` Thomas Gleixner
2013-06-19 18:03       ` Kevin Strasser
2013-06-19 20:35         ` Guenter Roeck
2013-06-18 21:04   ` [PATCH v2 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
2013-06-18 21:04   ` [PATCH v2 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
2013-06-19  8:36     ` Linus Walleij
2013-06-27 22:14       ` Kevin Strasser
2013-06-18 21:04   ` [PATCH v2 4/4] watchdog: Kontron PLD watchdog timer driver Kevin Strasser
2013-06-24  4:00 ` [PATCH v3 0/4] Kontron PLD drivers Kevin Strasser
2013-06-24  4:00   ` [PATCH v3 1/4] mfd: Kontron PLD mfd driver Kevin Strasser
2013-06-24  4:00   ` [PATCH v3 2/4] i2c: Kontron PLD i2c bus driver Kevin Strasser
2013-07-01  6:40     ` Wolfram Sang
2013-06-24  4:00   ` [PATCH v3 3/4] gpio: Kontron PLD gpio driver Kevin Strasser
2013-07-21 14:31     ` Linus Walleij
2013-06-24  4:00   ` [PATCH v3 4/4] watchdog: Kontron PLD watchdog timer driver Kevin Strasser
2013-06-27 18:23     ` Kevin Strasser
2013-06-27 21:47       ` Samuel Ortiz
2013-06-27 22:05         ` Kevin Strasser
2013-06-24 12:06   ` [PATCH v3 0/4] Kontron PLD drivers Samuel Ortiz
2013-06-24 16:09     ` Wolfram Sang
2013-06-27 20:34     ` Wim Van Sebroeck
2013-06-27 21:48       ` Samuel Ortiz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).