From: Michael Brunner <[email protected]>
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 <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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
From: Michael Brunner <[email protected]>
Add i2c support for the on-board PLD found on some Kontron embedded
modules.
Signed-off-by: Michael Brunner <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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
From: Michael Brunner <[email protected]>
Add watchdog timer support for the on-board PLD found on some Kontron
embedded modules.
Signed-off-by: Michael Brunner <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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
From: Michael Brunner <[email protected]>
Add gpio support for the on-board PLD found on some Kontron embedded
modules.
Signed-off-by: Michael Brunner <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld_now1-gpio");
--
1.7.9.5
On Mon, Apr 8, 2013 at 7:15 PM, Kevin Strasser
<[email protected]> wrote:
> From: Michael Brunner <[email protected]>
>
> Add gpio support for the on-board PLD found on some Kontron embedded
> modules.
>
> Signed-off-by: Michael Brunner <[email protected]>
> Signed-off-by: Kevin Strasser <[email protected]>
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
On Tue, Apr 09, 2013 at 10:46:15AM +0200, Linus Walleij wrote:
> On Mon, Apr 8, 2013 at 7:15 PM, Kevin Strasser
> <[email protected]> wrote:
>
> > From: Michael Brunner <[email protected]>
> >
> > Add gpio support for the on-board PLD found on some Kontron embedded
> > modules.
> >
> > Signed-off-by: Michael Brunner <[email protected]>
> > Signed-off-by: Kevin Strasser <[email protected]>
>
> 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 [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
On Mon, Apr 08, 2013 at 10:15:21AM -0700, Kevin Strasser wrote:
> From: Michael Brunner <[email protected]>
>
> Add watchdog timer support for the on-board PLD found on some Kontron
> embedded modules.
>
> Signed-off-by: Michael Brunner <[email protected]>
> Signed-off-by: Kevin Strasser <[email protected]>
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 <[email protected]>
> + *
> + * 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 <[email protected]>");
> +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 <[email protected]>
> + *
> + * 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 <[email protected]>");
> +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 <[email protected]>
> + *
> + * 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 [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
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 <[email protected]>
> >
> > Add watchdog timer support for the on-board PLD found on some Kontron
> > embedded modules.
> >
> > Signed-off-by: Michael Brunner <[email protected]>
> > Signed-off-by: Kevin Strasser <[email protected]>
>
> 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 <[email protected]>
> > + *
> > + * 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 <[email protected]>");
> > +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 <[email protected]>
> > + *
> > + * 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 <[email protected]>");
> > +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 <[email protected]>
> > + *
> > + * 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 [email protected]
> > More majordomo info at http://vger.kernel.org/majordomo-info.html
> >
On Mon, Apr 08, 2013 at 10:15:19AM -0700, Kevin Strasser wrote:
> From: Michael Brunner <[email protected]>
>
> Add i2c support for the on-board PLD found on some Kontron embedded
> modules.
>
> Signed-off-by: Michael Brunner <[email protected]>
> Signed-off-by: Kevin Strasser <[email protected]>
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 <[email protected]>
> + *
> + * 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 <[email protected]>");
> +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 <[email protected]>
> + *
> + * 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 [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
On Tue, Apr 9, 2013 at 6:41 PM, Guenter Roeck <[email protected]> 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
>> <[email protected]> wrote:
>>
>> > From: Michael Brunner <[email protected]>
>> >
>> > Add gpio support for the on-board PLD found on some Kontron embedded
>> > modules.
>> >
>> > Signed-off-by: Michael Brunner <[email protected]>
>> > Signed-off-by: Kevin Strasser <[email protected]>
>>
>> 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
On Mon, Apr 8, 2013 at 7:15 PM, Kevin Strasser
<[email protected]> wrote:
> From: Michael Brunner <[email protected]>
>
> Add gpio support for the on-board PLD found on some Kontron embedded
> modules.
>
> Signed-off-by: Michael Brunner <[email protected]>
> Signed-off-by: Kevin Strasser <[email protected]>
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 <[email protected]>");
> +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
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 <[email protected]> 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 <[email protected]>");
> > +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
On Fri, Apr 12, 2013 at 1:09 PM, Michael Brunner <[email protected]> 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
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
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 <[email protected]>
> >
> > Add i2c support for the on-board PLD found on some Kontron embedded
> > modules.
> >
> > Signed-off-by: Michael Brunner <[email protected]>
> > Signed-off-by: Kevin Strasser <[email protected]>
>
> 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.
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
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);
}
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
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.
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
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 <[email protected]>
> > >
> > > Add watchdog timer support for the on-board PLD found on some Kontron
> > > embedded modules.
> > >
> > > Signed-off-by: Michael Brunner <[email protected]>
> > > Signed-off-by: Kevin Strasser <[email protected]>
> >
> > 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.
Add watchdog timer support for the on-board PLD found on some Kontron embedded
modules.
Signed-off-by: Kevin Strasser <[email protected]>
Signed-off-by: Michael Brunner <[email protected]>
Acked-by: Guenter Roeck <[email protected]>
Acked-by: Darren Hart <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
--
1.7.9.5
Add i2c support for the on-board PLD found on some Kontron embedded
modules.
Signed-off-by: Kevin Strasser <[email protected]>
Signed-off-by: Michael Brunner <[email protected]>
Acked-by: Guenter Roeck <[email protected]>
Acked-by: Darren Hart <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld_i2c");
--
1.7.9.5
Add gpio support for the on-board PLD found on some Kontron embedded modules.
Signed-off-by: Guenter Roeck <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
Signed-off-by: Michael Brunner <[email protected]>
Acked-by: Darren Hart <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-kempld");
--
1.7.9.5
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
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 <[email protected]>
Signed-off-by: Michael Brunner <[email protected]>
Acked-by: Guenter Roeck <[email protected]>
Acked-by: Darren Hart <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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
On Tue, Jun 18, 2013 at 11:04 PM, Kevin Strasser
<[email protected]> wrote:
> Add gpio support for the on-board PLD found on some Kontron embedded modules.
>
> Signed-off-by: Guenter Roeck <[email protected]>
> Signed-off-by: Kevin Strasser <[email protected]>
> Signed-off-by: Michael Brunner <[email protected]>
> Acked-by: Darren Hart <[email protected]>
This is looking good.
Reviewed-by: Linus Walleij <[email protected]>
Yours,
Linus Walleij
On Tue, Jun 18, 2013 at 11:04 PM, Kevin Strasser
<[email protected]> 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 <[email protected]>
> Signed-off-by: Michael Brunner <[email protected]>
> Acked-by: Guenter Roeck <[email protected]>
> Acked-by: Darren Hart <[email protected]>
(...)
> +/**
> + * 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
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
> <[email protected]> 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 <[email protected]>
> > Signed-off-by: Michael Brunner <[email protected]>
> > Acked-by: Guenter Roeck <[email protected]>
> > Acked-by: Darren Hart <[email protected]>
>
> (...)
> > +/**
> > + * 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/
On Tue, 18 Jun 2013, Kevin Strasser wrote:
This patch set looks reasonable now, except a formal issue.
> Signed-off-by: Kevin Strasser <[email protected]>
> Signed-off-by: Michael Brunner <[email protected]>
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 <[email protected]>
<patch description/>
Signed-off-by: Michael Brunner <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
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 <[email protected]>.
Signed-off-by: Kevin Strasser <[email protected]>
Or
Originally-From: Michael Brunner <[email protected]>.
Signed-off-by: Kevin Strasser <[email protected]>
Other than that.
Acked-by: Thomas Gleixner <[email protected]>
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.
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 <[email protected]>
> > Signed-off-by: Michael Brunner <[email protected]>
>
> 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 <[email protected]>
>
> <patch description/>
>
> Signed-off-by: Michael Brunner <[email protected]>
> Signed-off-by: Kevin Strasser <[email protected]>
>
> 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 <[email protected]>.
>
> Signed-off-by: Kevin Strasser <[email protected]>
>
> Or
>
> Originally-From: Michael Brunner <[email protected]>.
> Signed-off-by: Kevin Strasser <[email protected]>
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 <[email protected]>
<patch description>
Originally-From: Michael Brunner <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
...
Thanks,
Kevin
>
> Other than that.
>
> Acked-by: Thomas Gleixner <[email protected]>
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 <[email protected]>
> > > Signed-off-by: Michael Brunner <[email protected]>
> >
> > 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 <[email protected]>
> >
> > <patch description/>
> >
> > Signed-off-by: Michael Brunner <[email protected]>
> > Signed-off-by: Kevin Strasser <[email protected]>
> >
> > 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 <[email protected]>.
> >
> > Signed-off-by: Kevin Strasser <[email protected]>
> >
> > Or
> >
> > Originally-From: Michael Brunner <[email protected]>.
> > Signed-off-by: Kevin Strasser <[email protected]>
>
> 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 <[email protected]>
>
> <patch description>
>
> Originally-From: Michael Brunner <[email protected]>
> Signed-off-by: Guenter Roeck <[email protected]>
> Signed-off-by: Kevin Strasser <[email protected]>
> ...
Ok with me. Always happy to take the blame :)
Thanks,
Guenter
>
> Thanks,
> Kevin
>
> >
> > Other than that.
> >
> > Acked-by: Thomas Gleixner <[email protected]>
>
Changes since v2:
-Change Michael's "Signed-off-by" to "Originally-From" in all patches
-Add "From: Guenter Roeck <[email protected]>" 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
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 <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
Acked-by: Guenter Roeck <[email protected]>
Acked-by: Darren Hart <[email protected]>
Acked-by: Thomas Gleixner <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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
Add i2c support for the on-board PLD found on some Kontron embedded
modules.
Originally-From: Michael Brunner <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
Acked-by: Guenter Roeck <[email protected]>
Acked-by: Darren Hart <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld_i2c");
--
1.7.9.5
Add watchdog timer support for the on-board PLD found on some Kontron embedded
modules.
Originally-From: Michael Brunner <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
Acked-by: Guenter Roeck <[email protected]>
Acked-by: Darren Hart <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
--
1.7.9.5
From: Guenter Roeck <[email protected]>
Add gpio support for the on-board PLD found on some Kontron embedded modules.
Originally-From: Michael Brunner <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
Signed-off-by: Kevin Strasser <[email protected]>
Acked-by: Darren Hart <[email protected]>
Reviewed-by: Linus Walleij <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-kempld");
--
1.7.9.5
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 <[email protected]>" 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/
> 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!
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
Hi All,
> > Changes since v2:
> > -Change Michael's "Signed-off-by" to "Originally-From" in all patches
> > -Add "From: Guenter Roeck <[email protected]>" 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 <[email protected]>
I'm OK that itgoes via the MFD tree.
Kind regards,
Wim.
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/
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 <[email protected]>" 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 <[email protected]>
>
> 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/
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/
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
> <[email protected]> wrote:
>
> > Add gpio support for the on-board PLD found on some Kontron embedded modules.
> >
> > Signed-off-by: Guenter Roeck <[email protected]>
> > Signed-off-by: Kevin Strasser <[email protected]>
> > Signed-off-by: Michael Brunner <[email protected]>
> > Acked-by: Darren Hart <[email protected]>
>
> This is looking good.
> Reviewed-by: Linus Walleij <[email protected]>
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
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 <[email protected]>
> Signed-off-by: Kevin Strasser <[email protected]>
> Acked-by: Guenter Roeck <[email protected]>
> Acked-by: Darren Hart <[email protected]>
Applied to for-next, thanks!
On Mon, Jun 24, 2013 at 6:00 AM, Kevin Strasser
<[email protected]> wrote:
> From: Guenter Roeck <[email protected]>
>
> Add gpio support for the on-board PLD found on some Kontron embedded modules.
>
> Originally-From: Michael Brunner <[email protected]>
> Signed-off-by: Guenter Roeck <[email protected]>
> Signed-off-by: Kevin Strasser <[email protected]>
> Acked-by: Darren Hart <[email protected]>
> Reviewed-by: Linus Walleij <[email protected]>
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