meta-digi-arm: linux-fslc: ccimx6ul: Add MCA support

Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
This commit is contained in:
Alex Gonzalez 2019-04-04 12:52:57 +02:00
parent 9d063498c8
commit 4cf572edf5
12 changed files with 10030 additions and 0 deletions

View File

@ -0,0 +1,167 @@
From 2313935f3b195aa7d930961bcd44a5fac61a945e Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
Date: Fri, 20 Apr 2018 19:13:02 +0200
Subject: [PATCH] ARM: Add support for the ConnectCore 6UL System-On-Module
* Getter functions for hwid information
* Define "digi,ccimx6ul" in the compatible property so the MCA can be
managed using the available tools.
Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
---
arch/arm/boot/dts/imx6ul-ccimx6ulsbcexpress.dts | 2 +-
arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts | 3 +-
arch/arm/configs/imx_v6_v7_defconfig | 2 +-
arch/arm/mach-imx/Kconfig | 8 +++
arch/arm/mach-imx/Makefile | 2 +-
arch/arm/mach-imx/som-ccimx6ul.c | 69 +++++++++++++++++++++++++
6 files changed, 82 insertions(+), 4 deletions(-)
create mode 100644 arch/arm/mach-imx/som-ccimx6ul.c
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcexpress.dts b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcexpress.dts
index 3792679c0c90..dc773f350999 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcexpress.dts
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcexpress.dts
@@ -15,7 +15,7 @@
/ {
model = "Digi International ConnectCore 6UL SBC Express.";
compatible = "digi,ccimx6ulsbcexpress", "digi,ccimx6ulsom",
- "fsl,imx6ul";
+ "digi,ccimx6ul", "fsl,imx6ul";
};
&adc1 {
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts
index 3749fdda3611..aed1db57ed3b 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts
@@ -14,7 +14,8 @@
/ {
model = "Digi International ConnectCore 6UL SBC Pro.";
- compatible = "digi,ccimx6ulsbcpro", "digi,ccimx6ulsom", "fsl,imx6ul";
+ compatible = "digi,ccimx6ulsbcpro", "digi,ccimx6ulsom",
+ "digi,ccimx6ul", "fsl,imx6ul";
lcd_backlight: backlight {
compatible = "pwm-backlight";
diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 6985d61eddb3..2d32ab117fb5 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -36,7 +36,7 @@ CONFIG_SOC_IMX6Q=y
CONFIG_SOC_IMX6SL=y
CONFIG_SOC_IMX6SLL=y
CONFIG_SOC_IMX6SX=y
-CONFIG_SOC_IMX6UL=y
+CONFIG_SOM_CC6UL=y
CONFIG_SOC_IMX7D=y
CONFIG_SOC_VF610=y
CONFIG_PCI=y
diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig
index abc337111eff..2451ee423496 100644
--- a/arch/arm/mach-imx/Kconfig
+++ b/arch/arm/mach-imx/Kconfig
@@ -523,6 +523,14 @@ config SOC_IMX6UL
help
This enables support for Freescale i.MX6 UltraLite processor.
+config SOM_CC6UL
+ bool "Digi ConnectCore 6UL System-On-Module support"
+ select SOC_IMX6UL
+ select SOC_IMX6
+
+ help
+ This enables support for Digi ConnectCore 6UL System-On-Module.
+
config SOC_LS1021A
bool "Freescale LS1021A support"
select ARM_GIC
diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile
index bae179af21f6..0cd7cdfc5e49 100644
--- a/arch/arm/mach-imx/Makefile
+++ b/arch/arm/mach-imx/Makefile
@@ -83,7 +83,7 @@ obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o
obj-$(CONFIG_SOC_IMX6UL) += mach-imx6ul.o
obj-$(CONFIG_SOC_IMX7D_CA7) += mach-imx7d.o
obj-$(CONFIG_SOC_IMX7D_CM4) += mach-imx7d-cm4.o
-
+obj-$(CONFIG_SOM_CC6UL) += som-ccimx6ul.o
ifeq ($(CONFIG_SUSPEND),y)
AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a
obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o
diff --git a/arch/arm/mach-imx/som-ccimx6ul.c b/arch/arm/mach-imx/som-ccimx6ul.c
new file mode 100644
index 000000000000..269b526ee020
--- /dev/null
+++ b/arch/arm/mach-imx/som-ccimx6ul.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 Digi International Inc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/errno.h>
+#include <linux/of.h>
+#include <linux/string.h>
+
+static int digi_board_version = -EINVAL;
+
+int digi_get_board_version(void)
+{
+ struct device_node *np = NULL;
+ const char *boardver_str;
+ char buf[4];
+
+ /* Only need to read the carrier board once */
+ if (digi_board_version > 0)
+ return digi_board_version;
+
+ np = of_find_node_by_path("/");
+ if (!np)
+ return -EPERM;
+
+ if (!of_property_read_string(np, "digi,carrierboard,version",
+ &boardver_str)) {
+ strncpy(buf, boardver_str, sizeof(buf));
+ if (!kstrtoint(boardver_str, 10, &digi_board_version))
+ pr_debug("Board version: %d\n", digi_board_version);
+ }
+ of_node_put(np);
+
+ return digi_board_version;
+}
+EXPORT_SYMBOL(digi_get_board_version);
+
+static int digi_som_hv = -EINVAL;
+
+int digi_get_som_hv(void)
+{
+ struct device_node *np = NULL;
+ const char *som_hv_str;
+ char buf[4];
+
+ /* Only need to read the HV once */
+ if (digi_som_hv > 0)
+ return digi_som_hv;
+
+ np = of_find_node_by_path("/");
+ if (!np)
+ return -EPERM;
+
+ if (!of_property_read_string(np, "digi,hwid,hv", &som_hv_str)) {
+ strncpy(buf, som_hv_str, sizeof(buf));
+ if (!kstrtoint(som_hv_str, 16, &digi_som_hv))
+ pr_debug("SOM HV: %d\n", digi_som_hv);
+ }
+ of_node_put(np);
+
+ return digi_som_hv;
+}
+EXPORT_SYMBOL(digi_get_som_hv);

View File

@ -0,0 +1,65 @@
From d0520a166cdeccf2821a45483602fc24a1772569 Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
Date: Fri, 20 Apr 2018 20:20:30 +0200
Subject: [PATCH] mach-imx: pm-imx6: Add hooks for board specific
implementation
This commit implements two new pm hooks in pm_imx6.c (begin & end) that,
optionally, can be implemented by the platform code.
This is needed on platforms like the CC6UL where the i.MX6UL has to
notify the MCA when suspending the system or resuming from suspend.
Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
Signed-off-by: Pedro Perez de Heredia <pedro.perez@digi.com>
---
arch/arm/mach-imx/pm-imx6.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/arch/arm/mach-imx/pm-imx6.c b/arch/arm/mach-imx/pm-imx6.c
index 87f45b926c78..1a4d1ea92687 100644
--- a/arch/arm/mach-imx/pm-imx6.c
+++ b/arch/arm/mach-imx/pm-imx6.c
@@ -69,6 +69,14 @@ static void __iomem *suspend_ocram_base;
static void (*imx6_suspend_in_ocram_fn)(void __iomem *ocram_vbase);
/*
+ * Function pointers to optional board pm functions
+ */
+int (*imx6_board_pm_begin)(suspend_state_t);
+void (*imx6_board_pm_end)(void);
+EXPORT_SYMBOL(imx6_board_pm_begin);
+EXPORT_SYMBOL(imx6_board_pm_end);
+
+/*
* suspend ocram space layout:
* ======================== high address ======================
* .
@@ -427,12 +435,28 @@ static int imx6q_pm_enter(suspend_state_t state)
return 0;
}
+static int imx6q_pm_begin(suspend_state_t state)
+{
+ if (imx6_board_pm_begin)
+ return imx6_board_pm_begin(state);
+
+ return 0;
+}
+
+static void imx6q_pm_end(void)
+{
+ if (imx6_board_pm_end)
+ imx6_board_pm_end();
+}
+
static int imx6q_pm_valid(suspend_state_t state)
{
return (state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM);
}
static const struct platform_suspend_ops imx6q_pm_ops = {
+ .begin = imx6q_pm_begin,
+ .end = imx6q_pm_end,
.enter = imx6q_pm_enter,
.valid = imx6q_pm_valid,
};

View File

@ -0,0 +1,701 @@
From a2b055852d963729002f48155d8bbee7e2858e0a Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
Date: Mon, 23 Apr 2018 11:37:46 +0200
Subject: [PATCH] imx6ul: Add MCA GPIO support for the ConnectCore 6UL SOM
Synched with v4.14.78/master at:
3f8b03950b323db4ca89b1cdc1c2288f79facaa3
Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
---
arch/arm/boot/dts/imx6ul-ccimx6ulsbcexpress.dts | 2 +
arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts | 2 +
arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi | 11 +
arch/arm/configs/imx_v6_v7_defconfig | 1 +
drivers/gpio/Kconfig | 9 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-mca.c | 583 ++++++++++++++++++++++++
7 files changed, 609 insertions(+)
create mode 100644 drivers/gpio/gpio-mca.c
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcexpress.dts b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcexpress.dts
index 3792679c0c90..148f1b95e46d 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcexpress.dts
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcexpress.dts
@@ -43,6 +43,8 @@
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <&ethphy0>;
+ phy-reset-gpios = <&mca_gpio 7 GPIO_ACTIVE_LOW>;
+ phy-reset-duration = <26>;
status = "okay";
mdio {
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts
index 3749fdda3611..5ad2c61276bc 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts
@@ -79,6 +79,8 @@
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <&ethphy0>;
+ phy-reset-gpios = <&mca_gpio 7 GPIO_ACTIVE_LOW>;
+ phy-reset-duration = <26>;
status = "okay";
};
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
index 03c62926ca2b..8d475051acf2 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
@@ -58,6 +58,17 @@
fw-update-gpio = <&gpio4 14 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_mca_cc6ul>;
+
+ mca_gpio: gpio {
+ compatible = "digi,mca-cc6ul-gpio";
+ gpio-controller;
+ #gpio-cells = <2>;
+
+ interrupt-parent = <&mca_cc6ul>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
};
pfuze3000: pmic@8 {
diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 6985d61eddb3..6c3ede35e643 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -207,6 +207,7 @@ CONFIG_SPI_GPIO=y
CONFIG_SPI_IMX=y
CONFIG_SPI_FSL_DSPI=y
CONFIG_GPIO_SYSFS=y
+CONFIG_GPIO_MCA=y
CONFIG_GPIO_MAX732X=y
CONFIG_GPIO_MC9S08DZ60=y
CONFIG_GPIO_PCA953X=y
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 833a1b51c948..f7daae95b2a7 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -305,6 +305,15 @@ config GPIO_MB86S7X
help
Say yes here to support the GPIO controller in Fujitsu MB86S70 SoCs.
+config GPIO_MCA
+ tristate "Digi ConnectCore SOMs Micro Controller Assist GPIO support"
+ select MFD_MCA_CC6UL if SOC_IMX6UL
+ select MFD_MCA_CC8X if ARCH_FSL_IMX8QXP
+ select GPIOLIB_IRQCHIP
+ help
+ If you say yes here you will get support for the GPIOs in the
+ Micro Controller Assist of Digi ConnectCore system-on-modules.
+
config GPIO_MENZ127
tristate "MEN 16Z127 GPIO support"
depends on MCB
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 671c4477c951..481b23e8c27e 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -82,6 +82,7 @@ obj-$(CONFIG_GPIO_MENZ127) += gpio-menz127.o
obj-$(CONFIG_GPIO_MERRIFIELD) += gpio-merrifield.o
obj-$(CONFIG_GPIO_MC33880) += gpio-mc33880.o
obj-$(CONFIG_GPIO_MC9S08DZ60) += gpio-mc9s08dz60.o
+obj-$(CONFIG_GPIO_MCA) += gpio-mca.o
obj-$(CONFIG_GPIO_ML_IOH) += gpio-ml-ioh.o
obj-$(CONFIG_GPIO_MM_LANTIQ) += gpio-mm-lantiq.o
obj-$(CONFIG_GPIO_MOCKUP) += gpio-mockup.o
diff --git a/drivers/gpio/gpio-mca.c b/drivers/gpio/gpio-mca.c
new file mode 100644
index 000000000000..75dd8af9f76e
--- /dev/null
+++ b/drivers/gpio/gpio-mca.c
@@ -0,0 +1,583 @@
+/* gpio-mca.c - GPIO driver for MCA devices.
+ *
+ * Copyright (C) 2017 - 2019 Digi International Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/mfd/mca-common/core.h>
+#include <linux/mfd/mca-common/registers.h>
+
+#define MCA_DRVNAME_GPIO "mca-gpio"
+
+/*
+ * The following macros return the register address to read/write for a given
+ * gpio number.
+ */
+#define GPIO_DIR_REG(x) (MCA_GPIO_DIR_0 + ((x) / 8))
+#define GPIO_DATA_REG(x) (MCA_GPIO_DATA_0 + ((x) / 8))
+#define GPIO_SET_REG(x) (MCA_GPIO_SET_0 + ((x) / 8))
+#define GPIO_CLEAR_REG(x) (MCA_GPIO_CLEAR_0 + ((x) / 8))
+#define GPIO_TOGGLE_REG(x) (MCA_GPIO_TOGGLE_0 + ((x) / 8))
+#define GPIO_IRQ_STATUS_REG(x) (MCA_GPIO_IRQ_STATUS_0 + (x))
+#define GPIO_IRQ_CFG_REG(x) (MCA_GPIO_IRQ_CFG_0 + (x))
+#define GPIO_DEB_CFG_REG(x) (MCA_GPIO_DEB_CFG_0 + ((x) / 8))
+#define GPIO_DEB_CNT_REG(x) (MCA_GPIO_DEB_CNT_0 + (x))
+
+#define GPIO_CFG_UPDATE BIT(6)
+#define GPIO_BYTE(i) ((i) / 8)
+#define BYTE_OFFSET(i) ((i) % 8)
+#define BIT_OFFSET(i) ((i) % 8)
+
+#ifdef CONFIG_OF
+enum mca_gpio_type {
+ CC6UL_MCA_GPIO,
+ CC8X_MCA_GPIO,
+ IOEXP_MCA_GPIO,
+};
+
+struct mca_gpio_data {
+ enum mca_gpio_type devtype;
+};
+#endif
+
+struct mca_gpio {
+ void * parent;
+ struct regmap *regmap;
+ struct device *dev;
+ struct gpio_chip gc;
+ struct mutex irq_lock;
+ uint8_t irq_cfg[MCA_MAX_IOS];
+ uint8_t irq_capable[MCA_MAX_IO_BYTES];
+ int irq[MCA_MAX_GPIO_IRQ_BANKS];
+ uint8_t deb_timer_cfg[MCA_MAX_GPIO_IRQ_BANKS];
+};
+
+static char const *const irq_gpio_bank_name[] = {
+ MCA_IRQ_GPIO_BANK_0_NAME,
+ MCA_IRQ_GPIO_BANK_1_NAME,
+ MCA_IRQ_GPIO_BANK_2_NAME,
+ MCA_IRQ_GPIO_BANK_3_NAME,
+ MCA_IRQ_GPIO_BANK_4_NAME,
+ MCA_IRQ_GPIO_BANK_5_NAME,
+};
+
+static inline struct mca_gpio *to_mca_gpio(struct gpio_chip *chip)
+{
+ return gpiochip_get_data(chip);
+}
+
+static inline bool mca_gpio_is_irq_capable(struct mca_gpio *gpio,
+ u32 offset)
+{
+ return ((gpio->irq_capable[GPIO_BYTE(offset)] &
+ (1 << BYTE_OFFSET(offset))) != 0);
+}
+
+static int mca_gpio_get(struct gpio_chip *gc, unsigned num)
+{
+ struct mca_gpio *gpio = to_mca_gpio(gc);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(gpio->regmap, GPIO_DATA_REG(num), &val);
+ if (ret < 0)
+ return ret;
+
+ return (val & (1 << BIT_OFFSET(num)) ? 1 : 0);
+}
+
+static void mca_gpio_set(struct gpio_chip *gc, unsigned num, int val)
+{
+ struct mca_gpio *gpio = to_mca_gpio(gc);
+ unsigned int reg = val ? GPIO_SET_REG(num) : GPIO_CLEAR_REG(num);
+
+ regmap_write(gpio->regmap, reg, 1 << BIT_OFFSET(num));
+}
+
+static int mca_gpio_direction_input(struct gpio_chip *gc, unsigned num)
+{
+ struct mca_gpio *gpio = to_mca_gpio(gc);
+
+ return regmap_update_bits(gpio->regmap, GPIO_DIR_REG(num),
+ 1 << BIT_OFFSET(num), 0);
+}
+
+static int mca_gpio_direction_output(struct gpio_chip *gc, unsigned num,
+ int val)
+{
+ struct mca_gpio *gpio = to_mca_gpio(gc);
+ int ret;
+
+ /* Set value before setting direction */
+ mca_gpio_set(gc, num, val);
+
+ ret = regmap_update_bits(gpio->regmap, GPIO_DIR_REG(num),
+ 1 << BIT_OFFSET(num), 1 << BIT_OFFSET(num));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+#define MCA_GPIO_MAX_DEB_VAL_TIMER_50MS (50 * 1000 * 255)
+#define MCA_GPIO_MAX_DEB_VAL_TIMER_1MS (255 * 1000)
+
+static int mca_gpio_set_debounce(struct gpio_chip *gc, unsigned int usecs,
+ unsigned int debounce)
+{
+ struct mca_gpio *mca_gc = to_mca_gpio(gc);
+ u8 deb_cnt;
+ int ret;
+
+ if (debounce > MCA_GPIO_MAX_DEB_VAL_TIMER_50MS) {
+ dev_warn(mca_gc->dev, "Value out of range %u, setting %u instead\n",
+ debounce, MCA_GPIO_MAX_DEB_VAL_TIMER_50MS);
+ debounce = MCA_GPIO_MAX_DEB_VAL_TIMER_50MS;
+ }
+
+ if (debounce > MCA_GPIO_MAX_DEB_VAL_TIMER_1MS) {
+ /* Set timer cfg period to 50ms */
+ mca_gc->deb_timer_cfg[GPIO_BYTE(usecs)] |= 1 << BIT_OFFSET(usecs);
+ deb_cnt = (debounce + 49999) / 50000;
+ } else {
+ /* Set timer cfg period to 1ms */
+ mca_gc->deb_timer_cfg[GPIO_BYTE(usecs)] &= ~(1 << BIT_OFFSET(usecs));
+ deb_cnt = (debounce + 999) / 1000;
+ }
+
+ ret = regmap_write(mca_gc->regmap, GPIO_DEB_CFG_REG(usecs),
+ mca_gc->deb_timer_cfg[GPIO_BYTE(usecs)]);
+ if (ret) {
+ dev_err(mca_gc->dev, "Failed to write GPIO_DEB_CFG_REG(%d) (%d)\n",
+ usecs, ret);
+ } else {
+ ret = regmap_write(mca_gc->regmap, GPIO_DEB_CNT_REG(usecs), deb_cnt);
+ if (ret)
+ dev_err(mca_gc->dev,
+ "Failed to write GPIO_DEB_CNT_REG(%d) (%d)\n",
+ usecs, ret);
+ }
+
+ return ret;
+}
+
+static int mca_gpio_set_config(struct gpio_chip *gc, unsigned int num,
+ unsigned long config)
+{
+ enum pin_config_param param = pinconf_to_config_param(config);
+ u32 arg = pinconf_to_config_argument(config);
+
+ if (param != PIN_CONFIG_INPUT_DEBOUNCE)
+ return -ENOTSUPP;
+
+ return mca_gpio_set_debounce(gc, num, arg);
+}
+
+static irqreturn_t mca_gpio_irq_handler(int irq, void *data)
+{
+ struct mca_gpio *gpio = data;
+ unsigned int pending_irqs, mask, this_irq;
+ int ret, i, j;
+
+ for (i = 0; i < (gpio->gc.ngpio + 7) / 8; i++) {
+ ret = regmap_read(gpio->regmap, GPIO_IRQ_STATUS_REG(i), &pending_irqs);
+ if (ret < 0) {
+ dev_err(gpio->dev,
+ "IRQ %d: Failed to read GPIO_IRQ_STATUS_REG (%d)\n",
+ irq, ret);
+ continue;
+ }
+
+ for (j = 0; j < 8; j++) {
+ mask = 1 << j;
+ if (pending_irqs & mask) {
+ /* Ack the irq and call the handler */
+ this_irq = irq_find_mapping(gpio->gc.irq.domain, j + i * 8);
+ ret = regmap_write(gpio->regmap,
+ GPIO_IRQ_STATUS_REG(i),
+ mask);
+ if (ret) {
+ dev_err(gpio->dev,
+ "Failed to ack IRQ %d (%d)\n",
+ this_irq, ret);
+ continue;
+ }
+
+ handle_nested_irq(this_irq);
+ }
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void mca_gpio_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mca_gpio *gpio = to_mca_gpio(gc);
+
+ /*
+ * Update the IRQ_EN bit and also set the CFG_UPDATE flag to mark what
+ * registers have to be written later to the MCA, once we are out of
+ * atomic context. Note that this flag is not cleared before writing
+ * the MCA regsister.
+ */
+ gpio->irq_cfg[d->hwirq] |= GPIO_CFG_UPDATE;
+ gpio->irq_cfg[d->hwirq] &= ~MCA_GPIO_IRQ_EN;
+}
+
+static void mca_gpio_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mca_gpio *gpio = to_mca_gpio(gc);
+
+ /*
+ * Update the IRQ_EN bit and also set the CFG_UPDATE flag to mark what
+ * registers have to be written later to the MCA, once we are out of
+ * atomic context. Note that this flag is not cleared before writing
+ * the MCA regsister.
+ */
+ gpio->irq_cfg[d->hwirq] |= GPIO_CFG_UPDATE | MCA_GPIO_IRQ_EN;
+}
+
+static void mca_gpio_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mca_gpio *gpio = to_mca_gpio(gc);
+
+ mutex_lock(&gpio->irq_lock);
+}
+
+static void mca_gpio_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mca_gpio *gpio = to_mca_gpio(gc);
+ int i, ret;
+
+ for (i = 0; i < gc->ngpio; i++) {
+ /* Update only those registers that were flagged (modified) */
+ if (!(gpio->irq_cfg[i] & GPIO_CFG_UPDATE))
+ continue;
+
+ gpio->irq_cfg[i] &= ~GPIO_CFG_UPDATE;
+
+ ret = regmap_write(gpio->regmap,
+ GPIO_IRQ_CFG_REG(i),
+ gpio->irq_cfg[i]);
+ if (ret) {
+ dev_err(gpio->dev,
+ "Failed to configure IRQ %d\n", d->irq);
+ }
+ }
+
+ mutex_unlock(&gpio->irq_lock);
+}
+
+static int mca_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mca_gpio *gpio = to_mca_gpio(gc);
+ u32 gpio_idx = d->hwirq;
+
+ if ((type & IRQ_TYPE_LEVEL_HIGH) || (type & IRQ_TYPE_LEVEL_LOW)) {
+ dev_err(gpio->dev,
+ "IRQ %d: level IRQs are not supported\n", d->irq);
+ return -EINVAL;
+ }
+
+ /*
+ * Update the edge flags based on type and set CFG_UPDATE to note that
+ * the register was modified and has to be written back to the MCA in
+ * mca_gpio_irq_bus_sync_unlock().
+ */
+ gpio->irq_cfg[gpio_idx] &= ~MCA_M_GPIO_IRQ_CFG;
+ gpio->irq_cfg[gpio_idx] |= GPIO_CFG_UPDATE;
+
+ if (type & IRQ_TYPE_EDGE_RISING)
+ gpio->irq_cfg[gpio_idx] |= MCA_GPIO_IRQ_EDGE_RISE;
+
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ gpio->irq_cfg[gpio_idx] |= MCA_GPIO_IRQ_EDGE_FALL;
+
+ return 0;
+}
+
+static int mca_gpio_to_irq(struct gpio_chip *gc, u32 offset)
+{
+ struct mca_gpio *gpio = to_mca_gpio(gc);
+
+ if (GPIO_BYTE(offset) >= MCA_MAX_IO_BYTES)
+ return -EINVAL;
+
+ /* Discard non irq capable gpios */
+ if (!mca_gpio_is_irq_capable(gpio, offset))
+ return -EINVAL;
+
+ return irq_create_mapping(gc->irq.domain, offset);
+}
+
+static struct irq_chip mca_gpio_irq_chip = {
+ .name = "mca-gpio-irq",
+ .irq_disable = mca_gpio_irq_disable,
+ .irq_enable = mca_gpio_irq_enable,
+ .irq_bus_lock = mca_gpio_irq_bus_lock,
+ .irq_bus_sync_unlock = mca_gpio_irq_bus_sync_unlock,
+ .irq_set_type = mca_gpio_irq_set_type,
+};
+
+static int mca_gpio_irq_setup(struct mca_gpio *gpio)
+{
+ unsigned int val;
+ int ret, i;
+
+ mutex_init(&gpio->irq_lock);
+
+ for (i = 0; i < gpio->gc.ngpio; i++) {
+ gpio->irq_cfg[i] = 0;
+
+ ret = regmap_read(gpio->regmap, GPIO_IRQ_CFG_REG(i), &val);
+ if (ret) {
+ dev_err(gpio->dev,
+ "Failed to read GPIO[%d] irq config (%d)\n",
+ i, ret);
+ continue;
+ }
+
+ if (val & MCA_GPIO_IRQ_CAPABLE)
+ gpio->irq_capable[GPIO_BYTE(i)] |= 1 << BYTE_OFFSET(i);
+ else
+ gpio->irq_capable[GPIO_BYTE(i)] &= ~(1 << BYTE_OFFSET(i));
+ }
+
+ for (i = 0; i < MCA_MAX_GPIO_IRQ_BANKS; i++) {
+ if (gpio->irq[i] < 0)
+ continue;
+ ret = devm_request_threaded_irq(gpio->dev, gpio->irq[i],
+ NULL, mca_gpio_irq_handler,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ mca_gpio_irq_chip.name,
+ gpio);
+ if (ret) {
+ dev_err(gpio->dev, "Failed to request %s IRQ (%d)\n",
+ irq_gpio_bank_name[i], gpio->irq[i]);
+ return ret;
+ }
+ }
+
+ ret = gpiochip_irqchip_add_nested(&gpio->gc,
+ &mca_gpio_irq_chip,
+ 0,
+ handle_edge_irq,
+ IRQ_TYPE_NONE);
+ if (ret) {
+ dev_err(gpio->dev,
+ "Failed to connect irqchip to gpiochip (%d)\n", ret);
+ return ret;
+ }
+
+ /*
+ * gpiochip_irqchip_add_nested() sets .to_irq with its own implementation but
+ * we have to use our own version because not all GPIOs are irq capable.
+ * Therefore, we overwrite it.
+ */
+ gpio->gc.to_irq = mca_gpio_to_irq;
+
+ for (i = 0; i < MCA_MAX_GPIO_IRQ_BANKS; i++) {
+ if (gpio->irq[i] < 0)
+ continue;
+ gpiochip_set_nested_irqchip(&gpio->gc,
+ &mca_gpio_irq_chip,
+ gpio->irq[i]);
+ }
+
+ return 0;
+}
+
+static struct gpio_chip reference_gc = {
+ .label = "mca-gpio",
+ .owner = THIS_MODULE,
+ .get = mca_gpio_get,
+ .set = mca_gpio_set,
+ .direction_input = mca_gpio_direction_input,
+ .direction_output = mca_gpio_direction_output,
+ .to_irq = mca_gpio_to_irq,
+ .set_config = mca_gpio_set_config,
+ .can_sleep = 1,
+ .base = -1,
+};
+
+static int mca_gpio_probe(struct platform_device *pdev)
+{
+ struct mca_drv *mca = dev_get_drvdata(pdev->dev.parent);
+ struct device *mca_dev = mca->dev;
+ struct regmap *regmap = mca->regmap;
+ int *gpio_base = &mca->gpio_base;
+ struct mca_gpio *gpio;
+ struct device_node *np;
+ unsigned int val;
+ int ret, i;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio) {
+ dev_err(mca_dev, "Failed to allocate GPIO device\n");
+ return -ENOMEM;
+ }
+
+ if (!mca_dev)
+ return -EPROBE_DEFER;
+
+ gpio->dev = mca_dev;
+ gpio->regmap = regmap;
+
+ for (i = 0; i < MCA_MAX_GPIO_IRQ_BANKS; i++) {
+ gpio->irq[i] = platform_get_irq_byname(pdev,
+ irq_gpio_bank_name[i]);
+ }
+ gpio->gc = reference_gc;
+ gpio->gc.of_node = pdev->dev.of_node;
+ gpio->gc.parent = &pdev->dev;
+ platform_set_drvdata(pdev, gpio);
+
+ /* Find entry in device-tree */
+ if (mca_dev->of_node) {
+ const struct mca_gpio_data *devdata =
+ of_device_get_match_data(&pdev->dev);
+ const char * compatible = pdev->dev.driver->
+ of_match_table[devdata->devtype].compatible;
+
+ /* Return if node does not exist or if it is disabled */
+ np = of_find_compatible_node(mca_dev->of_node, NULL, compatible);
+ if (!np) {
+ ret = -ENODEV;
+ goto err;
+ }
+ if (!of_device_is_available(np)) {
+ ret = -ENODEV;
+ goto err;
+ }
+ }
+
+ /* Get number of GPIOs from MCA firmware */
+ if (regmap_read(regmap, MCA_GPIO_NUM, &val)) {
+ ret = -EINVAL;
+ dev_err(mca_dev, "Could not read number of gpios.\n");
+ goto err;
+ }
+ gpio->gc.ngpio = val & MCA_GPIO_NUM_MASK;
+ if (gpio->gc.ngpio < 1 || gpio->gc.ngpio > MCA_MAX_IOS) {
+ ret = -EINVAL;
+ dev_err(mca_dev, "Read invalid number of gpios (%d). "
+ "Valid range is 1..%d.\n", gpio->gc.ngpio,
+ MCA_MAX_IOS);
+ goto err;
+ }
+
+ ret = gpiochip_add_data(&gpio->gc, gpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret);
+ goto err;
+ }
+
+ ret = mca_gpio_irq_setup(gpio);
+ if (ret) {
+ gpiochip_remove(&gpio->gc);
+ goto err;
+ }
+
+ if (gpio_base)
+ *gpio_base = gpio->gc.base;
+
+ return 0;
+
+err:
+ gpio = NULL;
+ return ret;
+}
+
+static int mca_gpio_remove(struct platform_device *pdev)
+{
+ struct mca_gpio *gpio = platform_get_drvdata(pdev);
+ struct mca_drv *mca = (struct mca_drv *)gpio->parent; /* TODO */
+
+ mca->gpio_base = -1;
+ gpiochip_remove(&gpio->gc);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static struct mca_gpio_data mca_gpio_devdata[] = {
+ [CC6UL_MCA_GPIO] = {
+ .devtype = CC6UL_MCA_GPIO,
+ },
+ [CC8X_MCA_GPIO] = {
+ .devtype = CC8X_MCA_GPIO,
+ },
+ [IOEXP_MCA_GPIO] = {
+ .devtype = IOEXP_MCA_GPIO,
+ },
+};
+
+static const struct of_device_id mca_gpio_dt_ids[] = {
+ { .compatible = "digi,mca-cc6ul-gpio",
+ .data = &mca_gpio_devdata[CC6UL_MCA_GPIO]},
+ { .compatible = "digi,mca-cc8x-gpio",
+ .data = &mca_gpio_devdata[CC8X_MCA_GPIO]},
+ { .compatible = "digi,mca-ioexp-gpio",
+ .data = &mca_gpio_devdata[IOEXP_MCA_GPIO]},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mca_gpio_dt_ids);
+#endif
+
+static struct platform_driver mca_gpio_driver = {
+ .probe = mca_gpio_probe,
+ .remove = mca_gpio_remove,
+ .driver = {
+ .name = MCA_DRVNAME_GPIO,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_OF
+ .of_match_table = mca_gpio_dt_ids,
+#endif
+ },
+};
+
+static int __init mca_gpio_init(void)
+{
+ return platform_driver_register(&mca_gpio_driver);
+}
+module_init(mca_gpio_init);
+
+static void __exit mca_gpio_exit(void)
+{
+ platform_driver_unregister(&mca_gpio_driver);
+}
+module_exit(mca_gpio_exit);
+
+/* Module information */
+MODULE_AUTHOR("Digi International Inc.");
+MODULE_DESCRIPTION("GPIO device driver for MCA of ConnectCore Modules");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" MCA_DRVNAME_GPIO);
+

View File

@ -0,0 +1,33 @@
From 7350cbc80f98eacb84a67049c2181f758ca3add6 Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
Date: Fri, 15 Jun 2018 09:18:15 +0200
Subject: [PATCH] imx6ul: Add MCA IOMUX support to the ConnectCore 6UL SOM
Synched with v4.14.78/master at.
3f8b03950b323db4ca89b1cdc1c2288f79facaa3
Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
---
arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
index 8d475051acf2..b96a0873cd88 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
@@ -286,6 +286,15 @@
MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x3000
>;
};
+
+ pinctrl_mca_cc6ul: mcagrp {
+ fsl,pins = <
+ /* MCA_nINT */
+ MX6UL_PAD_SNVS_TAMPER4__GPIO5_IO04 0xb0b1
+ /* MCA_FW_UPDATE */
+ MX6UL_PAD_NAND_CE1_B__GPIO4_IO14 0x30
+ >;
+ };
};
&reg_arm {

View File

@ -0,0 +1,467 @@
From 1440e9a7f2812ebe7ac1d74e8a3c7515bcd67fa8 Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
Date: Mon, 23 Apr 2018 11:39:40 +0200
Subject: [PATCH] imx6ul: Add MCA watchdog support for the ConnectCore 6UL SOM
Synched with v4.14.78/master at:
3f8b03950b323db4ca89b1cdc1c2288f79facaa3
Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
---
.../devicetree/bindings/watchdog/mca-cc6ul-wdt.txt | 22 ++
arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi | 5 +
arch/arm/configs/imx_v6_v7_defconfig | 1 +
drivers/watchdog/Kconfig | 8 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/mca_wdt.c | 354 +++++++++++++++++++++
6 files changed, 391 insertions(+)
create mode 100644 Documentation/devicetree/bindings/watchdog/mca-cc6ul-wdt.txt
create mode 100644 drivers/watchdog/mca_wdt.c
diff --git a/Documentation/devicetree/bindings/watchdog/mca-cc6ul-wdt.txt b/Documentation/devicetree/bindings/watchdog/mca-cc6ul-wdt.txt
new file mode 100644
index 000000000000..c6dd2cfdd316
--- /dev/null
+++ b/Documentation/devicetree/bindings/watchdog/mca-cc6ul-wdt.txt
@@ -0,0 +1,22 @@
+* Digi Watchdog Timer for MCA of ConnectCore 6UL
+
+Required properties:
+- compatible: must be "digi,mca-cc6ul-wdt".
+
+Optional properties:
+- digi,timeout-sec: contains the watchdog timeout in seconds.
+- digi,irq-no-reset: if present, the watchdog will generate an interrupt instead
+ of a system reset.
+- digi,full-reset: if present, the watchdog will perform a full system reset,
+ including the MCA. Otherwise, only the microprocessor is reset. Note
+ that this option requires the system to be configured to generate a
+ reset and not an interrupt.
+
+Example:
+ mca: mca-cc6ul@7e {
+ watchdog {
+ compatible = "digi,mca-cc6ul-wdt";
+ digi,timeout-sec = <15>;
+ digi,irq-no-reset;
+ };
+ };
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
index b96a0873cd88..848bf78dfceb 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
@@ -69,6 +69,11 @@
#interrupt-cells = <2>;
};
+ watchdog {
+ compatible = "digi,mca-cc6ul-wdt";
+ digi,full-reset;
+ };
+
};
pfuze3000: pmic@8 {
diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 6c3ede35e643..9c693c7778a4 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -226,6 +226,7 @@ CONFIG_IMX_THERMAL=y
CONFIG_WATCHDOG=y
CONFIG_DA9062_WATCHDOG=y
CONFIG_RN5T618_WATCHDOG=y
+CONFIG_MCA_WATCHDOG=y
CONFIG_IMX2_WDT=y
CONFIG_MFD_DA9052_I2C=y
CONFIG_MFD_DA9062=y
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 2d64333f4782..800821bfdd7d 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -171,6 +171,14 @@ config MENZ069_WATCHDOG
This driver can also be built as a module. If so the module
will be called menz069_wdt.
+config MCA_WATCHDOG
+ tristate "Digi ConnectCore SOMs Micro Controller Assist Watchdog"
+ select WATCHDOG_CORE
+ select MFD_MCA_CC6UL if SOC_IMX6UL
+ select MFD_MCA_CC8X if ARCH_FSL_IMX8QXP
+ help
+ If you say yes here you will get support for the watchdog in the Micro Controller Assist of Digi ConnectCore system-on-modules.
+
config TANGOX_WATCHDOG
tristate "Sigma Designs SMP86xx/SMP87xx watchdog"
select WATCHDOG_CORE
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index f69cdff5ad7f..36a275937216 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o
obj-$(CONFIG_MESON_GXBB_WATCHDOG) += meson_gxbb_wdt.o
obj-$(CONFIG_MESON_WATCHDOG) += meson_wdt.o
obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o
+obj-$(CONFIG_MCA_WATCHDOG) += mca_wdt.o
obj-$(CONFIG_DIGICOLOR_WATCHDOG) += digicolor_wdt.o
obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o
obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o
diff --git a/drivers/watchdog/mca_wdt.c b/drivers/watchdog/mca_wdt.c
new file mode 100644
index 000000000000..7d5e8b303ed5
--- /dev/null
+++ b/drivers/watchdog/mca_wdt.c
@@ -0,0 +1,354 @@
+/*
+ * Watchdog driver for MCA on ConnectCore modules
+ *
+ * Copyright(c) 2016 - 2018 Digi International Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#include <linux/mfd/mca-common/core.h>
+
+#define MCA_DRVNAME_WATCHDOG "mca-watchdog"
+
+#define WDT_REFRESH_LEN (MCA_WDT_REFRESH_3 - \
+ MCA_WDT_REFRESH_0 + 1)
+#define WDT_REFRESH_PATTERN "WDTP"
+#define WATCHDOG_NAME "MCA Watchdog"
+#define DEFAULT_TIMEOUT 30 /* 30 sec default timeout */
+
+#ifdef CONFIG_OF
+enum mca_wdt_type {
+ CC6UL_MCA_WDT,
+ CC8X_MCA_WDT,
+};
+
+struct mca_wdt_data {
+ enum mca_wdt_type devtype;
+};
+#endif
+
+struct mca_wdt {
+ struct watchdog_device wdd;
+ struct mca_drv *mca;
+ struct kref kref;
+ unsigned int default_timeout;
+ bool irqnoreset;
+ bool nowayout;
+ bool fullreset;
+ unsigned int irq_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) ")");
+
+static int mca_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct mca_wdt *wdt = watchdog_get_drvdata(wdd);
+ struct mca_drv *mca = wdt->mca;
+ int ret;
+
+ if (timeout < wdt->wdd.min_timeout ||
+ timeout > wdt->wdd.max_timeout) {
+ ret = -EINVAL;
+ } else {
+ ret = regmap_write(mca->regmap, MCA_WDT_TIMEOUT, timeout);
+ }
+
+ if (ret < 0) {
+ dev_err(mca->dev, "Failed to set timeout, %d\n", ret);
+ return ret;
+ }
+
+ wdd->timeout = timeout;
+
+ return 0;
+}
+
+static int mca_config_options(struct mca_wdt *wdt)
+{
+ int ret = 0;
+ u8 control = 0;
+
+ control |= wdt->nowayout ? MCA_WDT_NOWAYOUT : 0;
+ control |= wdt->irqnoreset ? MCA_WDT_IRQNORESET : 0;
+ control |= wdt->fullreset ? MCA_WDT_FULLRESET : 0;
+
+ ret = regmap_update_bits(wdt->mca->regmap, MCA_WDT_CONTROL,
+ MCA_WDT_NOWAYOUT | MCA_WDT_IRQNORESET |
+ MCA_WDT_FULLRESET, control);
+ if (ret)
+ goto err;
+
+ /* Set timeout */
+ ret = mca_wdt_set_timeout(&wdt->wdd, wdt->default_timeout);
+ if (ret) {
+ dev_err(wdt->mca->dev, "Could not set watchdog timeout (%d)\n",
+ ret);
+ goto err;
+ }
+
+err:
+ return ret;
+}
+
+static int mca_wdt_ping(struct watchdog_device *wdd)
+{
+ struct mca_wdt *wdt = watchdog_get_drvdata(wdd);
+ struct mca_drv *mca = wdt->mca;
+ const char *pattern = WDT_REFRESH_PATTERN;
+
+ /*
+ * Refresh the watchdog timer by writing refresh pattern to REFRESH_x
+ * registers
+ */
+ return regmap_bulk_write(mca->regmap, MCA_WDT_REFRESH_0,
+ pattern, WDT_REFRESH_LEN);
+}
+
+static void mca_wdt_release_resources(struct kref *r)
+{
+}
+
+static int mca_wdt_start(struct watchdog_device *wdd)
+{
+ struct mca_wdt *wdt = watchdog_get_drvdata(wdd);
+ int ret = 0;
+
+ /* Enable watchdog */
+ ret = regmap_update_bits(wdt->mca->regmap, MCA_WDT_CONTROL,
+ MCA_WDT_ENABLE, MCA_WDT_ENABLE);
+ if (ret) {
+ dev_err(wdt->mca->dev, "Could not enable watchdog (%d)\n", ret);
+ goto err;
+ }
+
+err:
+ return ret;
+}
+
+static int mca_wdt_stop(struct watchdog_device *wdd)
+{
+ struct mca_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ /* Disable watchdog */
+ return regmap_update_bits(wdt->mca->regmap, MCA_WDT_CONTROL,
+ MCA_WDT_ENABLE, 0);
+}
+
+static irqreturn_t mca_wdt_timeout_event(int irq, void *data)
+{
+ return IRQ_HANDLED;
+}
+
+static struct watchdog_info mca_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | \
+ WDIOF_MAGICCLOSE,
+ .identity = WATCHDOG_NAME,
+};
+
+static const struct watchdog_ops mca_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = mca_wdt_start,
+ .stop = mca_wdt_stop,
+ .ping = mca_wdt_ping,
+ .set_timeout = mca_wdt_set_timeout,
+};
+
+static int of_mca_wdt_init(struct device_node *np,
+ struct mca_wdt *wdt)
+{
+ unsigned int timeout;
+
+ /* parse options */
+ wdt->irqnoreset = of_property_read_bool(np, "digi,irq-no-reset");
+ wdt->fullreset = of_property_read_bool(np, "digi,full-reset");
+
+ if (!of_property_read_u32_index(np, "digi,timeout-sec", 0, &timeout)) {
+ if (timeout < wdt->wdd.min_timeout ||
+ timeout > wdt->wdd.max_timeout)
+ dev_warn(wdt->mca->dev,
+ "Invalid timeout-sec value. Using default.\n");
+ else
+ wdt->default_timeout = timeout;
+ }
+
+ return 0;
+}
+
+static int mca_wdt_probe(struct platform_device *pdev)
+{
+ struct mca_drv *mca = dev_get_drvdata(pdev->dev.parent);
+ struct mca_wdt *wdt;
+ const struct mca_wdt_data *devdata = (struct mca_wdt_data *)pdev->id_entry->driver_data;
+ struct device_node *np;
+ int ret;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt) {
+ dev_err(mca->dev, "Failed to allocate watchdog device\n");
+ return -ENOMEM;
+ }
+
+ wdt->mca = mca;
+ wdt->default_timeout = DEFAULT_TIMEOUT;
+ wdt->nowayout = nowayout;
+ wdt->wdd.min_timeout = 0;
+ wdt->wdd.max_timeout = 0xff;
+ wdt->wdd.info = &mca_wdt_info;
+ wdt->wdd.ops = &mca_wdt_ops;
+ wdt->wdd.parent = &pdev->dev;
+
+ watchdog_set_drvdata(&wdt->wdd, wdt);
+ kref_init(&wdt->kref);
+ platform_set_drvdata(pdev, wdt);
+
+ /* Find entry in device-tree */
+ if (mca->dev->of_node) {
+ const char * compatible = pdev->dev.driver->
+ of_match_table[devdata->devtype].compatible;
+
+ /*
+ * Return silently if watchdog node does not exist
+ * or if it is disabled
+ */
+ np = of_find_compatible_node(mca->dev->of_node, NULL, compatible);
+ if (!np) {
+ ret = -ENODEV;
+ goto err;
+ }
+ if (!of_device_is_available(np)) {
+ ret = -ENODEV;
+ goto err;
+ }
+
+ /* Parse DT properties */
+ ret = of_mca_wdt_init(np, wdt);
+ if (ret)
+ goto err;
+ }
+
+ /* Configure WDT options */
+ ret = mca_config_options(wdt);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to configure WDT options\n");
+ goto err;
+ }
+
+ /* Set nowayout option into watchdog device */
+ watchdog_set_nowayout(&wdt->wdd, nowayout);
+
+ /* Register interrupt if so configured */
+ if (wdt->irqnoreset) {
+ wdt->irq_timeout = platform_get_irq_byname(pdev,
+ MCA_IRQ_WATCHDOG_NAME);
+ ret = devm_request_threaded_irq(&pdev->dev, wdt->irq_timeout,
+ NULL, mca_wdt_timeout_event,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ MCA_IRQ_WATCHDOG_NAME, wdt);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to request %s IRQ. (%d)\n",
+ MCA_IRQ_WATCHDOG_NAME, wdt->irq_timeout);
+ wdt->irq_timeout = -ENXIO;
+ goto err;
+ }
+ }
+
+ ret = watchdog_register_device(&wdt->wdd);
+ if (ret != 0) {
+ dev_err(wdt->mca->dev,
+ "watchdog_register_device() failed: %d\n", ret);
+ goto err;
+ }
+
+ pr_info("Watchdog driver for MCA (timeout=%d sec, nowayout=%d, %s%s)\n",
+ wdt->default_timeout, nowayout,
+ wdt->irqnoreset ? "interrupt (no reset)" : "reset",
+ wdt->irqnoreset ? "" : wdt->fullreset ? " (full)" : " (MPU only)");
+ return 0;
+
+err:
+ wdt = NULL;
+ return ret;
+}
+
+static int mca_wdt_remove(struct platform_device *pdev)
+{
+ struct mca_wdt *wdt = platform_get_drvdata(pdev);
+
+ if(wdt->irq_timeout)
+ devm_free_irq(&pdev->dev, wdt->irq_timeout, wdt);
+ watchdog_unregister_device(&wdt->wdd);
+ kref_put(&wdt->kref, mca_wdt_release_resources);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static struct mca_wdt_data mca_wdt_devdata[] = {
+ [CC6UL_MCA_WDT] = {
+ .devtype = CC6UL_MCA_WDT,
+ },
+ [CC8X_MCA_WDT] = {
+ .devtype = CC8X_MCA_WDT,
+ },
+};
+
+static const struct platform_device_id mca_wdt_devtype[] = {
+ {
+ .name = "mca-cc6ul-watchdog",
+ .driver_data = (kernel_ulong_t)&mca_wdt_devdata[CC6UL_MCA_WDT],
+ }, {
+ .name = "mca-cc8x-watchdog",
+ .driver_data = (kernel_ulong_t)&mca_wdt_devdata[CC8X_MCA_WDT],
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(platform, mca_wdt_devtype);
+
+static const struct of_device_id mca_wdt_match[] = {
+ { .compatible = "digi,mca-cc6ul-wdt",
+ .data = &mca_wdt_devdata[CC6UL_MCA_WDT]},
+ { .compatible = "digi,mca-cc8x-wdt",
+ .data = &mca_wdt_devdata[CC8X_MCA_WDT]},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mca_wdt_match);
+#endif
+
+static struct platform_driver mca_wdt_driver = {
+ .probe = mca_wdt_probe,
+ .remove = mca_wdt_remove,
+ .id_table = mca_wdt_devtype,
+ .driver = {
+ .name = MCA_DRVNAME_WATCHDOG,
+ .of_match_table = of_match_ptr(mca_wdt_match),
+ },
+};
+
+module_platform_driver(mca_wdt_driver);
+
+MODULE_AUTHOR("Digi International Inc.");
+MODULE_DESCRIPTION("Watchdog device driver for MCA of ConnectCore Modules");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" MCA_DRVNAME_WATCHDOG);

View File

@ -0,0 +1,937 @@
From bfc63af24c96ca3206e3f8e6b0fd437944e6519e Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
Date: Mon, 23 Apr 2018 11:42:42 +0200
Subject: [PATCH] imx6ul: Add MCA tamper support for ConnectCore 6UL SOM and
SBCs
Synched with v4.14.78/master at:
3f8b03950b323db4ca89b1cdc1c2288f79facaa3
Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
---
arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts | 7 +
arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi | 4 +
arch/arm/configs/imx_v6_v7_defconfig | 1 +
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/tamper/Kconfig | 20 +
drivers/iio/tamper/Makefile | 6 +
drivers/iio/tamper/mca_tamper.c | 801 ++++++++++++++++++++++++++++
8 files changed, 841 insertions(+)
create mode 100644 drivers/iio/tamper/Kconfig
create mode 100644 drivers/iio/tamper/Makefile
create mode 100644 drivers/iio/tamper/mca_tamper.c
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts
index acea9a56971e..7cbc14d56680 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts
@@ -162,6 +162,13 @@
digi,adc-vref = <3000000>;
};
+/* Enable Tamper detection. There are 2 digital (0 and 1) and 2
+ * analog (2 and 3) tamper interfaces.
+ */
+&mca_tamper {
+ digi,tamper-if-list = <0 1 2 3>;
+};
+
&pwm1 {
status = "okay";
};
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
index b5efc58d362d..a20fb5ffc98b 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
@@ -79,6 +79,10 @@
digi,adc-vref = <3000000>;
};
+ mca_tamper: tamper {
+ compatible = "digi,mca-cc6ul-tamper";
+ };
+
};
pfuze3000: pmic@8 {
diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 060c1de356ca..188fb4309851 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -390,6 +390,7 @@ CONFIG_VF610_ADC=y
CONFIG_SENSORS_ISL29018=y
CONFIG_MAG3110=y
CONFIG_MPL3115=y
+CONFIG_TAMPER_MCA=y
CONFIG_PWM=y
CONFIG_PWM_FSL_FTM=y
CONFIG_PWM_IMX=y
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index d08aeb41cd07..e4c3fd62ca92 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -94,6 +94,7 @@ source "drivers/iio/potentiostat/Kconfig"
source "drivers/iio/pressure/Kconfig"
source "drivers/iio/proximity/Kconfig"
source "drivers/iio/resolver/Kconfig"
+source "drivers/iio/tamper/Kconfig"
source "drivers/iio/temperature/Kconfig"
endif # IIO
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index cb5993251381..004de1adaf82 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -37,5 +37,6 @@ obj-y += potentiostat/
obj-y += pressure/
obj-y += proximity/
obj-y += resolver/
+obj-y += tamper/
obj-y += temperature/
obj-y += trigger/
diff --git a/drivers/iio/tamper/Kconfig b/drivers/iio/tamper/Kconfig
new file mode 100644
index 000000000000..2a9a395becf4
--- /dev/null
+++ b/drivers/iio/tamper/Kconfig
@@ -0,0 +1,20 @@
+#
+# Tamper Devices
+#
+
+menu "Tamper devices"
+
+config TAMPER_MCA
+ tristate "Tamper support for MCA on Digi ConnectCore SOMs"
+ select MFD_MCA_CC6UL if SOC_IMX6UL
+ select MFD_MCA_CC8X if ARCH_FSL_IMX8QXP
+
+ help
+ Say Y here to build the tamper driver on the MCA of the
+ ConnectCore system-on-modules.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mca_tamper
+
+endmenu
+
diff --git a/drivers/iio/tamper/Makefile b/drivers/iio/tamper/Makefile
new file mode 100644
index 000000000000..9f422a829081
--- /dev/null
+++ b/drivers/iio/tamper/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for IIO Tamper devices
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_TAMPER_MCA) += mca_tamper.o
diff --git a/drivers/iio/tamper/mca_tamper.c b/drivers/iio/tamper/mca_tamper.c
new file mode 100644
index 000000000000..2c92624aed18
--- /dev/null
+++ b/drivers/iio/tamper/mca_tamper.c
@@ -0,0 +1,801 @@
+/* mca_tamper.c - Tamper driver for MCA on ConnectCore modules
+ *
+ * Copyright (C) 2016 - 2018 Digi International Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/acpi.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mfd/mca-common/core.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+
+#define MCA_DRVNAME_TAMPER "mca-tamper"
+
+#ifdef CONFIG_OF
+enum mca_tamper_type {
+ CC6UL_MCA_TAMPER,
+ CC8X_MCA_TAMPER,
+};
+
+struct mca_tamper_data {
+ enum mca_tamper_type devtype;
+ u16 num_tamper_ifaces;
+ u16 digital_tamper_cnt;
+};
+#endif
+
+struct mca_tamper {
+ struct mca_drv *mca;
+ struct iio_dev *iio;
+ char name[10];
+ uint8_t config0;
+ uint8_t config1;
+ uint8_t io_in;
+ uint8_t io_out;
+ uint8_t pwroff_delay_ms;
+ uint8_t event;
+ int irq;
+ int iface;
+};
+
+/* Tamper register offsets */
+enum {
+ CFG0 = 0,
+ CFG1 = MCA_TAMPER0_CFG1 - MCA_TAMPER0_CFG0,
+ IO_IN = MCA_TAMPER0_IO_IN - MCA_TAMPER0_CFG0,
+ IO_OUT = MCA_TAMPER0_IO_OUT - MCA_TAMPER0_CFG0,
+ DELAY_PWROFF = MCA_TAMPER0_DELAY_PWROFF - MCA_TAMPER0_CFG0,
+ DATE_YEAR_L = MCA_TAMPER0_DATE_START - MCA_TAMPER0_CFG0,
+ DATE_YEAR_H,
+ DATE_MONTH,
+ DATE_DAY,
+ DATE_HOUR,
+ DATE_MIN,
+ DATE_SEC,
+ EVENT = MCA_TAMPER0_EVENT - MCA_TAMPER0_CFG0,
+ TICKS = MCA_TAMPER2_TICKS_L - MCA_TAMPER2_CFG0,
+ THR_L = MCA_TAMPER2_THRESH_LO_L - MCA_TAMPER2_CFG0,
+ THR_H = MCA_TAMPER2_THRESH_HI_L - MCA_TAMPER2_CFG0,
+};
+
+static int mca_tamper_read_raw(struct iio_dev *iio,
+ struct iio_chan_spec const *ch,
+ int *val, int *val2, long mask)
+{
+ struct mca_tamper *tp = iio_priv(iio);
+ u32 value;
+ int ret;
+ unsigned int event_reg;
+
+ switch (tp->iface) {
+ case 0:
+ event_reg = MCA_TAMPER0_EVENT;
+ break;
+ case 1:
+ event_reg = MCA_TAMPER1_EVENT;
+ break;
+ case 2:
+ event_reg = MCA_TAMPER2_EVENT;
+ break;
+ case 3:
+ event_reg = MCA_TAMPER3_EVENT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_read(tp->mca->regmap, event_reg, &value);
+ if (ret < 0) {
+ dev_err(tp->mca->dev,
+ "Error reading Tamper%d event register (%d)\n",
+ ch->channel, ret);
+ return ret;
+ }
+
+ *val = value;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return IIO_VAL_INT;
+}
+
+static const struct iio_event_spec mca_tamper_events[] = {
+ {
+ .type = IIO_EV_TYPE_CHANGE,
+ .dir = IIO_EV_DIR_NONE,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+static void mca_tamper_get_time(u8 *data, struct rtc_time *tm)
+{
+ tm->tm_year = ((data[DATE_YEAR_H] << 8) | data[DATE_YEAR_L]) - 1900;
+ tm->tm_mon = (data[DATE_MONTH] & MCA_RTC_MONTH_MASK) - 1;
+ tm->tm_mday = (data[DATE_DAY] & MCA_RTC_DAY_MASK);
+ tm->tm_hour = (data[DATE_HOUR] & MCA_RTC_HOUR_MASK);
+ tm->tm_min = (data[DATE_MIN] & MCA_RTC_MIN_MASK);
+ tm->tm_sec = (data[DATE_SEC] & MCA_RTC_SEC_MASK);
+}
+
+static unsigned int get_tamper_base_reg(unsigned int iface)
+{
+ switch (iface) {
+ case 0:
+ return MCA_TAMPER0_CFG0;
+ case 1:
+ return MCA_TAMPER1_CFG0;
+ case 2:
+ return MCA_TAMPER2_CFG0;
+ case 3:
+ return MCA_TAMPER3_CFG0;
+ default:
+ break;
+ }
+
+ return ~0;
+}
+
+/* Sysfs interface */
+#define TAMPER_SYSFS_SHOW_REG(reg, addr, type) \
+static ssize_t show_##reg(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct iio_dev *iio = dev_to_iio_dev(dev); \
+ struct mca_tamper *tp = iio_priv(iio); \
+ int ret; \
+ type val; \
+ unsigned int tamper_base_reg = get_tamper_base_reg(tp->iface); \
+ \
+ if (tamper_base_reg == ~0) \
+ return -1; \
+ \
+ ret = regmap_bulk_read(tp->mca->regmap, \
+ tamper_base_reg + addr, \
+ &val, sizeof(val)); \
+ if (ret != 0) { \
+ dev_err(tp->mca->dev, \
+ "Failed reading Tamper%d #reg register (%d)\n", \
+ tp->iface, ret); \
+ return 0; \
+ } \
+ return sprintf(buf, "0x%04x\n", (type)val); \
+}
+
+#define TAMPER_SYSFS_STORE_REG(reg, addr, type) \
+static ssize_t store_##reg(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ struct iio_dev *iio = dev_to_iio_dev(dev); \
+ struct mca_tamper *tp = iio_priv(iio); \
+ int ret; \
+ unsigned long val; \
+ unsigned int tamper_base_reg = get_tamper_base_reg(tp->iface); \
+ \
+ if (tamper_base_reg == ~0) \
+ return -1; \
+ \
+ ret = kstrtoul(buf, 0, &val); \
+ if (ret) { \
+ dev_err(tp->mca->dev, \
+ "%s: error parsing input (%s)\n", \
+ __func__, buf); \
+ return ret; \
+ } \
+ ret = regmap_bulk_write(tp->mca->regmap, \
+ tamper_base_reg + addr, \
+ &val, sizeof(type)); \
+ if (ret != 0) { \
+ dev_err(tp->mca->dev, \
+ "Failed write Tamper%d #reg register (%d)\n", \
+ tp->iface, ret); \
+ return 0; \
+ } \
+ return count; \
+}
+
+#define MCA_TAMPER_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr)
+
+static ssize_t show_tp_events(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *iio = dev_to_iio_dev(dev);
+ struct mca_tamper *tp = iio_priv(iio);
+ int ret;
+ unsigned int val;
+ unsigned int tamper_base_reg = get_tamper_base_reg(tp->iface);
+
+ if (tamper_base_reg == ~0)
+ return -1;
+
+ ret = regmap_read(tp->mca->regmap, tamper_base_reg + EVENT, &val);
+ if (ret != 0) {
+ dev_err(tp->mca->dev,
+ "Failed reading Tamper%d #reg register (%d)\n",
+ tp->iface, ret);
+ return 0;
+ }
+
+ switch (val) {
+ case 0x00:
+ ret = sprintf(buf, "none\n");
+ break;
+ case MCA_TAMPER_SIGNALED:
+ ret = sprintf(buf, "signaled\n");
+ break;
+ case (MCA_TAMPER_ACKED | MCA_TAMPER_SIGNALED):
+ ret = sprintf(buf, "signaled+acked\n");
+ break;
+ default:
+ ret = sprintf(buf, "unknown (0x%04x)\n", (u8)val);
+ break;
+ }
+ return ret;
+}
+
+static ssize_t store_tp_events(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct iio_dev *iio = dev_to_iio_dev(dev);
+ struct mca_tamper *tp = iio_priv(iio);
+ int ret;
+ unsigned long val;
+ unsigned int tamper_base_reg = get_tamper_base_reg(tp->iface);
+
+ if (tamper_base_reg == ~0)
+ return -1;
+
+ if (!strncmp(buf, "ack", strlen("ack")))
+ val = MCA_TAMPER_ACKED;
+ else if (!strncmp(buf, "clear", strlen("clear")))
+ val = MCA_TAMPER_CLEAR;
+ else {
+ /* Check if string is a raw value */
+ ret = kstrtoul(buf, 0, &val);
+ if (ret || (val != MCA_TAMPER_CLEAR &&
+ val != MCA_TAMPER_ACKED)) {
+ dev_err(tp->mca->dev,
+ "%s: error parsing input (%s)\n",
+ __func__, buf);
+ return ret;
+ }
+ }
+
+ ret = regmap_write(tp->mca->regmap, tamper_base_reg + EVENT, val);
+ if (ret != 0) {
+ dev_err(tp->mca->dev,
+ "Failed write Tamper%d #reg register (%d)\n",
+ tp->iface, ret);
+ return 0;
+ }
+
+ return count;
+}
+
+static ssize_t show_timestamp(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *iio = dev_to_iio_dev(dev);
+ struct mca_tamper *tp = iio_priv(iio);
+ unsigned int tamper_base_reg = get_tamper_base_reg(tp->iface);
+ time64_t tamper_t64 = -1;
+ struct rtc_time tm;
+ u8 data[MCA_TAMPER_REGS_LEN];
+ int ret;
+
+ if (tamper_base_reg == ~0)
+ return -1;
+
+ ret = regmap_bulk_read(tp->mca->regmap, tamper_base_reg, data,
+ sizeof(data));
+ if (ret != 0) {
+ dev_err(tp->mca->dev, "Failed reading Tamper%d registers (%d)\n",
+ tp->iface, ret);
+ return ret;
+ }
+
+ /* Confirm the event and get the timestamp */
+ if (data[EVENT] & MCA_TAMPER_SIGNALED) {
+ mca_tamper_get_time(data, &tm);
+ tamper_t64 = rtc_tm_to_time64(&tm);
+ }
+
+ return sprintf(buf, "%lld\n", tamper_t64);
+}
+
+static IIO_DEVICE_ATTR(tamper_events, S_IRUGO | S_IWUSR, show_tp_events, store_tp_events, 0);
+static IIO_DEVICE_ATTR(timestamp, S_IRUGO, show_timestamp, NULL, 0);
+
+#ifdef MCA_TAMPER_HAS_EXTRA_SYSFS_ENTRIES
+TAMPER_SYSFS_SHOW_REG(config0, CFG0, u8)
+TAMPER_SYSFS_SHOW_REG(config1, CFG1, u8)
+TAMPER_SYSFS_SHOW_REG(io_in, IO_IN, u8)
+TAMPER_SYSFS_SHOW_REG(io_out, IO_OUT, u8)
+TAMPER_SYSFS_SHOW_REG(pwroff_delay_ms, DELAY_PWROFF, u8)
+TAMPER_SYSFS_SHOW_REG(srate, TICKS, u16)
+TAMPER_SYSFS_SHOW_REG(thr_l, THR_L, u16)
+TAMPER_SYSFS_SHOW_REG(thr_h, THR_H, u16)
+
+TAMPER_SYSFS_STORE_REG(config0, CFG0, u8)
+TAMPER_SYSFS_STORE_REG(config1, CFG1, u8)
+TAMPER_SYSFS_STORE_REG(io_in, IO_IN, u8)
+TAMPER_SYSFS_STORE_REG(io_out, IO_OUT, u8)
+TAMPER_SYSFS_STORE_REG(pwroff_delay_ms, DELAY_PWROFF, u8)
+TAMPER_SYSFS_STORE_REG(srate, TICKS, u16)
+TAMPER_SYSFS_STORE_REG(thr_l, THR_L, u16)
+TAMPER_SYSFS_STORE_REG(thr_h, THR_H, u16)
+
+static IIO_DEVICE_ATTR(config0, S_IRUGO | S_IWUSR, show_config0, store_config0, 0);
+static IIO_DEVICE_ATTR(config1, S_IRUGO | S_IWUSR, show_config1, store_config1, 0);
+static IIO_DEVICE_ATTR(io_in, S_IRUGO | S_IWUSR, show_io_in, store_io_in, 0);
+static IIO_DEVICE_ATTR(io_out, S_IRUGO | S_IWUSR, show_io_out, store_io_out, 0);
+static IIO_DEVICE_ATTR(pwroff_delay_ms, S_IRUGO | S_IWUSR, show_pwroff_delay_ms, store_pwroff_delay_ms, 0);
+static IIO_DEVICE_ATTR(srate, S_IRUGO | S_IWUSR, show_srate, store_srate, 0);
+static IIO_DEVICE_ATTR(thr_l, S_IRUGO | S_IWUSR, show_thr_l, store_thr_l, 0);
+static IIO_DEVICE_ATTR(thr_h, S_IRUGO | S_IWUSR, show_thr_h, store_thr_h, 0);
+
+static struct attribute *mca_tamper_attributes[] = {
+ MCA_TAMPER_DEV_ATTR(config0),
+ MCA_TAMPER_DEV_ATTR(io_in),
+ MCA_TAMPER_DEV_ATTR(io_out),
+ MCA_TAMPER_DEV_ATTR(pwroff_delay_ms),
+ MCA_TAMPER_DEV_ATTR(tamper_events),
+ NULL,
+};
+
+static struct attribute *mca_tamper_analog_attributes[] = {
+ MCA_TAMPER_DEV_ATTR(config0),
+ MCA_TAMPER_DEV_ATTR(config1),
+ MCA_TAMPER_DEV_ATTR(io_in),
+ MCA_TAMPER_DEV_ATTR(io_out),
+ MCA_TAMPER_DEV_ATTR(pwroff_delay_ms),
+ MCA_TAMPER_DEV_ATTR(tamper_events),
+ MCA_TAMPER_DEV_ATTR(timestamp),
+ MCA_TAMPER_DEV_ATTR(srate),
+ MCA_TAMPER_DEV_ATTR(thr_l),
+ MCA_TAMPER_DEV_ATTR(thr_h),
+ NULL,
+};
+
+static const struct attribute_group mca_tamper_analog_attribute_group = {
+ .name = "tamper_analog",
+ .attrs = mca_tamper_analog_attributes,
+};
+
+static const struct iio_info mca_tamper_analog_info = {
+ .attrs = &mca_tamper_analog_attribute_group,
+ .read_raw = &mca_tamper_read_raw,
+};
+
+#else
+static struct attribute *mca_tamper_attributes[] = {
+ MCA_TAMPER_DEV_ATTR(tamper_events),
+ MCA_TAMPER_DEV_ATTR(timestamp),
+ NULL,
+};
+#endif /* MCA_TAMPER_HAS_EXTRA_SYSFS_ENTRIES */
+
+static const struct attribute_group mca_tamper_attribute_group = {
+ .name = "tamper",
+ .attrs = mca_tamper_attributes,
+};
+
+static const struct iio_info mca_tamper_info = {
+ .attrs = &mca_tamper_attribute_group,
+ .read_raw = &mca_tamper_read_raw,
+};
+
+#ifdef MCA_TAMPER_DUMP_REGISTERS
+static void mca_tamper_regs_dump(u8 *data)
+{
+ printk("Tamper CFG0 = 0x%02x\n", data[CFG0]);
+ printk("Tamper CFG1 = 0x%02x\n", data[CFG1]);
+ printk("Tamper IO_IN = 0x%02x\n", data[IO_IN]);
+ printk("Tamper IO_OUT = 0x%02x\n", data[IO_OUT]);
+ printk("Tamper DELAY_PWROFF = 0x%02x\n", data[DELAY_PWROFF]);
+ printk("Tamper DATE_YEAR_L = 0x%02x\n", data[DATE_YEAR_L]);
+ printk("Tamper DATE_YEAR_H = 0x%02x\n", data[DATE_YEAR_H]);
+ printk("Tamper DATE_MONTH = 0x%02x\n", data[DATE_MONTH]);
+ printk("Tamper DATE_DAY = 0x%02x\n", data[DATE_DAY]);
+ printk("Tamper DATE_HOUR = 0x%02x\n", data[DATE_HOUR]);
+ printk("Tamper DATE_MIN = 0x%02x\n", data[DATE_MIN]);
+ printk("Tamper DATE_SEC = 0x%02x\n", data[DATE_SEC]);
+ printk("Tamper EVENT = 0x%02x\n", data[EVENT]);
+}
+#else
+static void mca_tamper_regs_dump(u8 *data) {}
+#endif
+
+static irqreturn_t mca_tamper_irq_handler(int irq, void *private)
+{
+ struct iio_dev *iio = private;
+ struct mca_tamper *tp = iio_priv(iio);
+ struct rtc_time tm;
+ u8 data[MCA_TAMPER_REGS_LEN];
+ time64_t tamper_t64;
+ u64 event;
+ int ret;
+ unsigned int tamper_base_reg = get_tamper_base_reg(tp->iface);
+
+ if (tamper_base_reg == ~0)
+ goto irq_out;
+
+ dev_dbg(tp->mca->dev, "Tamper %d IRQ (%d)\n", tp->iface, irq);
+
+ ret = regmap_bulk_read(tp->mca->regmap, tamper_base_reg, data,
+ sizeof(data));
+ if (ret != 0) {
+ dev_err(tp->mca->dev, "Failed reading Tamper%d registers (%d)\n",
+ tp->iface, ret);
+ goto irq_out;
+ }
+
+ /* Confirm the event and get the timestamp */
+ if (!(data[EVENT] & MCA_TAMPER_SIGNALED))
+ goto irq_out;
+
+ if (data[EVENT] & MCA_TAMPER_ACKED)
+ goto irq_out;
+
+ mca_tamper_regs_dump(data);
+ mca_tamper_get_time(data, &tm);
+ tamper_t64 = rtc_tm_to_time64(&tm);
+
+ /* Notify the event... */
+ event = IIO_MOD_EVENT_CODE(IIO_ACTIVITY, tp->iface, IIO_NO_MOD,
+ IIO_EV_TYPE_CHANGE, IIO_EV_DIR_NONE);
+ iio_push_event(iio, event, tamper_t64);
+
+irq_out:
+ return IRQ_HANDLED;
+}
+
+static int mca_tamper_is_enabled(struct mca_drv *mca, int iface)
+{
+ int ret;
+ u8 data[MCA_TAMPER_REGS_LEN];
+ unsigned int tamper_base_reg = get_tamper_base_reg(iface);
+
+ /* Get tamper configuration registers */
+ ret = regmap_bulk_read(mca->regmap,
+ tamper_base_reg,
+ data, sizeof(data));
+ if (ret != 0) {
+ dev_err(mca->dev,
+ "Failed reading tamper%d registers (%d)\n",
+ iface, ret);
+ return ret;
+ }
+
+ /* Verify if the tamper interface is enabled */
+ if (!(data[CFG0] & MCA_TAMPER_DET_EN))
+ return -1;
+ return 0;
+}
+
+static int mca_init_hardware(struct mca_tamper *tp)
+{
+ int ret;
+ u8 data[MCA_TAMPER_REGS_LEN];
+ unsigned int tamper_base_reg = get_tamper_base_reg(tp->iface);
+
+ /* Verify if the tamper interface is enabled */
+ ret = regmap_bulk_read(tp->mca->regmap,
+ tamper_base_reg,
+ data, sizeof(data));
+ if (ret != 0) {
+ dev_err(tp->mca->dev,
+ "Failed reading tamper%d registers (%d)\n",
+ tp->iface, ret);
+ return ret;
+ }
+
+ tp->config0 = data[CFG0];
+ tp->config1 = data[CFG1];
+ tp->io_in = data[IO_IN];
+ tp->io_out = data[IO_OUT];
+ tp->pwroff_delay_ms = data[DELAY_PWROFF];
+
+ if (!(data[CFG0] & MCA_TAMPER_DET_EN))
+ return -ENODEV;
+
+ if (tp->mca->gpio_base >= 0) {
+ ret = devm_gpio_request(tp->mca->dev,
+ tp->mca->gpio_base + tp->io_in,
+ "TAMPER IN");
+ if (ret != 0) {
+ dev_warn(tp->mca->dev,
+ "Error requesting GPIO %d for tamper %d input. It might be in use by other device.\n",
+ tp->mca->gpio_base + tp->io_in, tp->iface);
+ }
+
+ if (data[CFG0] & MCA_TAMPER_OUT_EN) {
+ ret = devm_gpio_request(tp->mca->dev,
+ tp->mca->gpio_base + tp->io_out,
+ "TAMPER OUT");
+ if (ret != 0) {
+ dev_warn(tp->mca->dev,
+ "Error requesting GPIO %d for tamper %d output. It might be in use by other device.\n",
+ tp->mca->gpio_base + tp->io_out,
+ tp->iface);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void mca_init_channel(struct iio_chan_spec *chan, int idx)
+{
+ chan->type = IIO_ACTIVITY;
+ chan->indexed = 1;
+ chan->channel = idx;
+ chan->scan_index = idx;
+ chan->scan_type.sign = 'u';
+ chan->scan_type.realbits = 1;
+ chan->scan_type.storagebits = 32;
+ chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+ chan->event_spec = mca_tamper_events;
+ chan->num_event_specs = ARRAY_SIZE(mca_tamper_events);
+}
+
+static int mca_tamper_probe(struct platform_device *pdev)
+{
+ struct mca_drv *mca = dev_get_drvdata(pdev->dev.parent);
+ struct mca_tamper **mca_tamper = NULL;
+ const struct mca_tamper_data *devdata = of_device_get_match_data(&pdev->dev);
+ struct device_node *np;
+ struct property *prop;
+ const __be32 *cur;
+ u32 iface;
+ int ret = 0;
+ u16 num_tamper_ifaces;
+ u16 digital_tamper_cnt;
+
+ if (!mca || !mca->dev || !mca->dev->of_node || !devdata)
+ return -EPROBE_DEFER;
+
+ pr_info("Tamper driver for MCA\n");
+
+ num_tamper_ifaces = devdata->num_tamper_ifaces;
+ digital_tamper_cnt = devdata->digital_tamper_cnt;
+
+ mca_tamper = devm_kzalloc(&pdev->dev,
+ sizeof(*mca_tamper) * num_tamper_ifaces,
+ GFP_KERNEL);
+ if (!mca_tamper) {
+ dev_err(&pdev->dev, "Failed to allocate memory for mca_tamper\n");
+ ret = -ENOMEM;
+ goto exit_error;
+ }
+
+ platform_set_drvdata(pdev, mca_tamper);
+
+ for (iface = 0; iface < num_tamper_ifaces; iface++)
+ mca_tamper[iface] = NULL;
+
+ /* Return silently if RTC node does not exist or if it is disabled */
+ {
+ const char * compatible = pdev->dev.driver->
+ of_match_table[devdata->devtype].compatible;
+ np = of_find_compatible_node(mca->dev->of_node, NULL, compatible);
+ }
+ if (!np || !of_device_is_available(np)) {
+ ret = -ENODEV;
+ goto exit_error;
+ }
+
+ of_property_for_each_u32(np, "digi,tamper-if-list",
+ prop, cur, iface) {
+ struct iio_chan_spec *channels;
+ struct iio_dev *iiod = NULL;
+
+ if (iface >= num_tamper_ifaces)
+ continue;
+
+ if (mca_tamper_is_enabled(mca, iface) != 0) {
+ dev_info(&pdev->dev, "Tamper %d disabled\n", iface);
+ continue;
+ }
+
+ iiod = devm_iio_device_alloc(&pdev->dev,
+ sizeof(*mca_tamper));
+ if (!iiod) {
+ dev_err(&pdev->dev, "Failed to allocate iio device\n");
+ ret = -ENOMEM;
+ goto exit_error;
+ }
+ mca_tamper[iface] = iio_priv(iiod);
+ mca_tamper[iface]->mca = mca;
+ mca_tamper[iface]->iface = iface;
+ mca_tamper[iface]->iio = iiod;
+
+ iiod->dev.parent = &pdev->dev;
+ sprintf(mca_tamper[iface]->name, "TAMPER%d", iface);
+ iiod->name = mca_tamper[iface]->name;
+ iiod->modes = INDIO_DIRECT_MODE;
+ channels = devm_kzalloc(&pdev->dev,
+ sizeof(struct iio_chan_spec),
+ GFP_KERNEL);
+ iiod->channels = channels;
+ if (!iiod->channels) {
+ dev_err(&pdev->dev, "Failed to allocate iio channels\n");
+ ret = -ENOMEM;
+ goto exit_error;
+ }
+
+#ifdef MCA_TAMPER_HAS_EXTRA_SYSFS_ENTRIES
+ if (iface < digital_tamper_cnt)
+ iiod->info = &mca_tamper_info;
+ else
+ iiod->info = &mca_tamper_analog_info;
+#else
+ iiod->info = &mca_tamper_info;
+#endif
+ iiod->num_channels = 1;
+ mca_init_channel(channels, iface);
+
+ ret = mca_init_hardware(mca_tamper[iface]);
+ if (ret != 0) {
+ /* Skip the error msg if interface is not enabled */
+ if (ret != -ENODEV)
+ dev_err(&pdev->dev, "Failed to init Tamper %d\n",
+ iface);
+ continue;
+ }
+
+ mca_tamper[iface]->irq = platform_get_irq_byname(pdev,
+ mca_tamper[iface]->name);
+ if (mca_tamper[iface]->irq) {
+ ret = devm_request_threaded_irq(&pdev->dev,
+ mca_tamper[iface]->irq,
+ NULL, mca_tamper_irq_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ mca_tamper[iface]->name,
+ iiod);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Requested TAMPER %d IRQ (%d).\n",
+ iface, mca_tamper[iface]->irq);
+ continue;
+ }
+ }
+
+ ret = iio_device_register(iiod);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to register mca tamper iio device\n");
+ continue;
+ }
+
+ dev_info(&pdev->dev, "Tamper %d enabled\n", iface);
+ }
+
+ return 0;
+
+exit_error:
+ for (iface = 0; iface < num_tamper_ifaces; iface++) {
+ if (mca_tamper[iface]) {
+ if (mca_tamper[iface]->iio)
+ kfree(mca_tamper[iface]->iio->channels);
+ kfree(mca_tamper[iface]->iio);
+ }
+ }
+ kfree(mca_tamper);
+
+ return ret;
+}
+
+static int mca_tamper_remove(struct platform_device *pdev)
+{
+ struct mca_tamper **mca_tamper = dev_get_drvdata(pdev->dev.parent);
+ struct mca_drv *mca;
+ const struct mca_tamper_data *devdata = of_device_get_match_data(&pdev->dev);
+ u16 num_tamper_ifaces = devdata->num_tamper_ifaces;
+ u32 iface;
+
+ for (iface = 0; iface < num_tamper_ifaces; iface++) {
+ if (mca_tamper[iface] == NULL)
+ continue;
+ /* Release the resources allocated */
+ mca = mca_tamper[iface]->mca;
+ if (mca->gpio_base >= 0) {
+ devm_gpio_free(&pdev->dev,
+ mca->gpio_base + mca_tamper[iface]->io_in);
+ if (mca_tamper[iface]->config0 & MCA_TAMPER_OUT_EN)
+ devm_gpio_free(&pdev->dev,
+ mca->gpio_base + mca_tamper[iface]->io_out);
+ }
+ devm_kfree(&pdev->dev, (void *)mca_tamper[iface]->iio->channels);
+ if (mca_tamper[iface]->irq)
+ devm_free_irq(&pdev->dev, mca_tamper[iface]->irq,
+ mca_tamper[iface]->iio);
+ iio_device_unregister(mca_tamper[iface]->iio);
+ devm_iio_device_free(&pdev->dev, mca_tamper[iface]->iio);
+ }
+ kfree(mca_tamper);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static struct mca_tamper_data mca_tamper_devdata[] = {
+ [CC6UL_MCA_TAMPER] = {
+ .devtype = CC6UL_MCA_TAMPER,
+ .num_tamper_ifaces = 4,
+ .digital_tamper_cnt = 2
+ },
+ [CC8X_MCA_TAMPER] = {
+ .devtype = CC8X_MCA_TAMPER,
+ .num_tamper_ifaces = 4,
+ .digital_tamper_cnt = 2
+ },
+};
+
+static const struct of_device_id mca_tamper_ids[] = {
+ { .compatible = "digi,mca-cc6ul-tamper",
+ .data = &mca_tamper_devdata[CC6UL_MCA_TAMPER]},
+ { .compatible = "digi,mca-cc8x-tamper",
+ .data = &mca_tamper_devdata[CC8X_MCA_TAMPER]},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mca_tamper_ids);
+#endif
+
+static struct platform_driver mca_tamper_driver = {
+ .probe = mca_tamper_probe,
+ .remove = mca_tamper_remove,
+ .driver = {
+ .name = MCA_DRVNAME_TAMPER,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(mca_tamper_ids),
+ },
+};
+
+static int __init mca_tamper_init(void)
+{
+ return platform_driver_register(&mca_tamper_driver);
+}
+module_init(mca_tamper_init);
+
+static void __exit mca_tamper_exit(void)
+{
+ platform_driver_unregister(&mca_tamper_driver);
+}
+module_exit(mca_tamper_exit);
+
+MODULE_AUTHOR("Digi International Inc");
+MODULE_DESCRIPTION("Tamper driver for MCA of ConnectCore Modules");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" MCA_DRVNAME_TAMPER);

View File

@ -0,0 +1,540 @@
From 33fb650af68bcc05f59181ae7a4f8424a0949f91 Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
Date: Mon, 23 Apr 2018 11:44:37 +0200
Subject: [PATCH] imx6ul: Add RTC MCA support for ConnectCore 6UL SOM
Synched with v4.14.78/master at:
3f8b03950b323db4ca89b1cdc1c2288f79facaa3
Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
---
arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi | 4 +
arch/arm/configs/imx_v6_v7_defconfig | 1 +
drivers/rtc/Kconfig | 12 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-mca.c | 454 ++++++++++++++++++++++++++++++
5 files changed, 472 insertions(+)
create mode 100644 drivers/rtc/rtc-mca.c
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
index c721320a4117..8163533c83b0 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
@@ -69,6 +69,10 @@
#interrupt-cells = <2>;
};
+ rtc {
+ compatible = "digi,mca-cc6ul-rtc";
+ };
+
watchdog {
compatible = "digi,mca-cc6ul-wdt";
digi,full-reset;
diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 0a07b13ce593..8a763ab6a365 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -370,6 +370,7 @@ CONFIG_RTC_DRV_PCF8523=y
CONFIG_RTC_DRV_PCF8563=y
CONFIG_RTC_DRV_M41T80=y
CONFIG_RTC_DRV_DA9063=y
+CONFIG_RTC_DRV_MCA=y
CONFIG_RTC_DRV_MC13XXX=y
CONFIG_RTC_DRV_MXC=y
CONFIG_RTC_DRV_MXC_V2=y
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index a819ef07b7ec..303c17e8a909 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1106,6 +1106,18 @@ config RTC_DRV_M48T59
This driver can also be built as a module, if so, the module
will be called "rtc-m48t59".
+config RTC_DRV_MCA
+ tristate "Digi ConnectCore SOMs Micro Controller Assist RTC"
+ select MFD_MCA_CC6UL if SOC_IMX6UL
+ select MFD_MCA_CC8X if ARCH_FSL_IMX8QXP
+
+ help
+ If you say Y here you will get support for the RTC in the
+ Micro Controller Assist of Digi ConnectCore system-on-modules.
+
+ This driver can also be built as a module, if so, the module
+ will be called "rtc-mca".
+
config RTC_DRV_MSM6242
tristate "Oki MSM6242"
depends on HAS_IOMEM
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 290c1730fb0a..cd1b13723479 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -98,6 +98,7 @@ obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o
obj-$(CONFIG_RTC_DRV_MAX8997) += rtc-max8997.o
obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o
obj-$(CONFIG_RTC_DRV_MC13XXX) += rtc-mc13xxx.o
+obj-$(CONFIG_RTC_DRV_MCA) += rtc-mca.o
obj-$(CONFIG_RTC_DRV_MCP795) += rtc-mcp795.o
obj-$(CONFIG_RTC_DRV_MOXART) += rtc-moxart.o
obj-$(CONFIG_RTC_DRV_MPC5121) += rtc-mpc5121.o
diff --git a/drivers/rtc/rtc-mca.c b/drivers/rtc/rtc-mca.c
new file mode 100644
index 000000000000..b0e3d17aa995
--- /dev/null
+++ b/drivers/rtc/rtc-mca.c
@@ -0,0 +1,454 @@
+/* rtc-mca.c - Real time clock device driver for MCA on ConnectCore modules
+ * Copyright (C) 2016 - 2018 Digi International
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/mfd/mca-common/core.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+
+#define MCA_DRVNAME_RTC "mca-rtc"
+
+#define CLOCK_DATA_LEN (MCA_RTC_COUNT_SEC - MCA_RTC_COUNT_YEAR_L + 1)
+#define ALARM_DATA_LEN (MCA_RTC_ALARM_SEC - MCA_RTC_ALARM_YEAR_L + 1)
+
+#ifdef CONFIG_OF
+enum mca_rtc_type {
+ CC6UL_MCA_RTC,
+ CC8X_MCA_RTC,
+};
+
+struct mca_rtc_data {
+ enum mca_rtc_type devtype;
+};
+#endif
+
+struct mca_rtc {
+ struct rtc_device *rtc_dev;
+ struct mca_drv *mca;
+ int irq_alarm;
+ bool alarm_enabled;
+};
+
+enum {
+ DATA_YEAR_L,
+ DATA_YEAR_H,
+ DATA_MONTH,
+ DATA_DAY,
+ DATA_HOUR,
+ DATA_MIN,
+ DATA_SEC,
+};
+
+static void mca_data_to_tm(u8 *data, struct rtc_time *tm)
+{
+ /* conversion from MCA RTC to struct time is month-1 and year-1900 */
+ tm->tm_year = (((data[DATA_YEAR_H] &
+ MCA_RTC_YEAR_H_MASK) << 8) |
+ (data[DATA_YEAR_L] & MCA_RTC_YEAR_L_MASK)) -
+ 1900;
+ tm->tm_mon = (data[DATA_MONTH] & MCA_RTC_MONTH_MASK) - 1;
+ tm->tm_mday = (data[DATA_DAY] & MCA_RTC_DAY_MASK);
+ tm->tm_hour = (data[DATA_HOUR] & MCA_RTC_HOUR_MASK);
+ tm->tm_min = (data[DATA_MIN] & MCA_RTC_MIN_MASK);
+ tm->tm_sec = (data[DATA_SEC] & MCA_RTC_SEC_MASK);
+}
+
+static void mca_tm_to_data(struct rtc_time *tm, u8 *data)
+{
+ /* conversion from struct time to MCA RTC is year+1900 */
+ data[DATA_YEAR_L] &= (u8)~MCA_RTC_YEAR_L_MASK;
+ data[DATA_YEAR_H] &= (u8)~MCA_RTC_YEAR_H_MASK;
+ data[DATA_YEAR_L] |= (tm->tm_year + 1900) &
+ MCA_RTC_YEAR_L_MASK;
+ data[DATA_YEAR_H] |= ((tm->tm_year + 1900) >> 8) &
+ MCA_RTC_YEAR_H_MASK;
+
+ /* conversion from struct time to MCA RTC is month+1 */
+ data[DATA_MONTH] &= ~MCA_RTC_MONTH_MASK;
+ data[DATA_MONTH] |= (tm->tm_mon + 1) & MCA_RTC_MONTH_MASK;
+
+ data[DATA_DAY] &= ~MCA_RTC_DAY_MASK;
+ data[DATA_DAY] |= tm->tm_mday & MCA_RTC_DAY_MASK;
+
+ data[DATA_HOUR] &= ~MCA_RTC_HOUR_MASK;
+ data[DATA_HOUR] |= tm->tm_hour & MCA_RTC_HOUR_MASK;
+
+ data[DATA_MIN] &= ~MCA_RTC_MIN_MASK;
+ data[DATA_MIN] |= tm->tm_min & MCA_RTC_MIN_MASK;
+
+ data[DATA_SEC] &= ~MCA_RTC_SEC_MASK;
+ data[DATA_SEC] |= tm->tm_sec & MCA_RTC_SEC_MASK;
+}
+
+static int mca_rtc_stop_alarm(struct device *dev)
+{
+ struct mca_rtc *rtc = dev_get_drvdata(dev);
+
+ return regmap_update_bits(rtc->mca->regmap, MCA_RTC_CONTROL,
+ MCA_RTC_ALARM_EN, 0);
+}
+
+static int mca_rtc_start_alarm(struct device *dev)
+{
+ struct mca_rtc *rtc = dev_get_drvdata(dev);
+
+ return regmap_update_bits(rtc->mca->regmap, MCA_RTC_CONTROL,
+ MCA_RTC_ALARM_EN,
+ MCA_RTC_ALARM_EN);
+}
+
+static int mca_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct mca_rtc *rtc = dev_get_drvdata(dev);
+ u8 data[CLOCK_DATA_LEN] = { [0 ... (CLOCK_DATA_LEN - 1)] = 0 };
+ int ret;
+
+ ret = regmap_bulk_read(rtc->mca->regmap, MCA_RTC_COUNT_YEAR_L,
+ data, CLOCK_DATA_LEN);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read RTC time data: %d\n", ret);
+ return ret;
+ }
+
+ mca_data_to_tm(data, tm);
+ return rtc_valid_tm(tm);
+}
+
+static int mca_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct mca_rtc *rtc = dev_get_drvdata(dev);
+ u8 data[CLOCK_DATA_LEN] = { [0 ... (CLOCK_DATA_LEN - 1)] = 0 };
+ int ret;
+
+ mca_tm_to_data(tm, data);
+
+ ret = regmap_bulk_write(rtc->mca->regmap, MCA_RTC_COUNT_YEAR_L,
+ data, CLOCK_DATA_LEN);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set RTC time data: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * The MCA RTC alarm expires (triggers the irq) when the RTC time matches the
+ * value programmed in the alarm register and the RTC counter increments.
+ * This means, one second after the programmed value. To correct this, the
+ * alarm value is adjusted when it is being written/read, decrementing/incremen-
+ * ting the value by 1 second.
+ */
+static void mca_rtc_adjust_alarm_time(struct rtc_wkalrm *alrm, bool inc)
+{
+ unsigned long time;
+
+ rtc_tm_to_time(&alrm->time, &time);
+ time = inc ? time + 1 : time - 1;
+ rtc_time_to_tm(time, &alrm->time);
+}
+
+static int mca_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct mca_rtc *rtc = dev_get_drvdata(dev);
+ u8 data[CLOCK_DATA_LEN] = { [0 ... (CLOCK_DATA_LEN - 1)] = 0 };
+ int ret;
+ unsigned int val;
+
+ ret = regmap_bulk_read(rtc->mca->regmap, MCA_RTC_ALARM_YEAR_L,
+ data, ALARM_DATA_LEN);
+ if (ret < 0)
+ return ret;
+
+ mca_data_to_tm(data, &alrm->time);
+ mca_rtc_adjust_alarm_time(alrm, true);
+
+ /* Enable status */
+ ret = regmap_read(rtc->mca->regmap, MCA_RTC_CONTROL, &val);
+ if (ret < 0)
+ return ret;
+
+ /* Pending status */
+ ret = regmap_read(rtc->mca->regmap, MCA_IRQ_STATUS_0, &val);
+ if (ret < 0)
+ return ret;
+ alrm->pending = (val & MCA_RTC_ALARM) ? 1 : 0;
+
+ return 0;
+}
+
+static int mca_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct mca_rtc *rtc = dev_get_drvdata(dev);
+ int ret;
+
+ if (enabled) {
+ ret = mca_rtc_start_alarm(dev);
+ if (ret != 0) {
+ dev_err(dev, "Failed to enable alarm IRQ (%d)\n", ret);
+ goto exit_alarm_irq;
+ }
+ rtc->alarm_enabled = 1;
+ } else {
+ ret = mca_rtc_stop_alarm(dev);
+ if (ret != 0) {
+ dev_err(dev, "Failed to disable alarm IRQ (%d)\n", ret);
+ goto exit_alarm_irq;
+ }
+ rtc->alarm_enabled = 0;
+ }
+
+exit_alarm_irq:
+ return ret;
+}
+
+static int mca_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct mca_rtc *rtc = dev_get_drvdata(dev);
+ u8 data[CLOCK_DATA_LEN] = { [0 ... (CLOCK_DATA_LEN - 1)] = 0 };
+ int ret;
+
+ mca_rtc_adjust_alarm_time(alrm, false);
+ mca_tm_to_data(&alrm->time, data);
+
+ ret = regmap_bulk_write(rtc->mca->regmap, MCA_RTC_ALARM_YEAR_L,
+ data, ALARM_DATA_LEN);
+ if (ret < 0)
+ return ret;
+
+ return mca_rtc_alarm_irq_enable(dev, alrm->enabled);
+}
+
+static irqreturn_t mca_alarm_event(int irq, void *data)
+{
+ struct mca_rtc *rtc = data;
+
+ rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops mca_rtc_ops = {
+ .read_time = mca_rtc_read_time,
+ .set_time = mca_rtc_set_time,
+ .read_alarm = mca_rtc_read_alarm,
+ .set_alarm = mca_rtc_set_alarm,
+ .alarm_irq_enable = mca_rtc_alarm_irq_enable,
+};
+
+static int mca_rtc_probe(struct platform_device *pdev)
+{
+ struct mca_drv *mca = dev_get_drvdata(pdev->dev.parent);
+ struct mca_rtc *rtc;
+ const struct mca_rtc_data *devdata =
+ of_device_get_match_data(&pdev->dev);
+ struct device_node *np = NULL;
+ int ret = 0;
+
+ if (!mca || !mca->dev->parent->of_node)
+ return -EPROBE_DEFER;
+
+ rtc = devm_kzalloc(&pdev->dev, sizeof *rtc, GFP_KERNEL);
+ if (!rtc)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, rtc);
+ device_init_wakeup(&pdev->dev, 1);
+ rtc->mca = mca;
+
+ /* Find entry in device-tree */
+ if (mca->dev->of_node) {
+ const char * compatible = pdev->dev.driver->
+ of_match_table[devdata->devtype].compatible;
+
+ /*
+ * Return silently if RTC node does not exist
+ * or if it is disabled
+ */
+ np = of_find_compatible_node(mca->dev->of_node, NULL, compatible);
+ if (!np) {
+ ret = -ENODEV;
+ goto err;
+ }
+ if (!of_device_is_available(np)) {
+ ret = -ENODEV;
+ goto err;
+ }
+ }
+
+ /* Enable RTC hardware */
+ ret = regmap_update_bits(mca->regmap, MCA_RTC_CONTROL,
+ MCA_RTC_EN, MCA_RTC_EN);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to enable RTC.\n");
+ goto err;
+ }
+
+ /* Register RTC device */
+ rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
+
+ if (IS_ERR(rtc->rtc_dev)) {
+ dev_err(&pdev->dev, "Failed to allocate RTC device: %ld\n",
+ PTR_ERR(rtc->rtc_dev));
+ ret = PTR_ERR(rtc->rtc_dev);
+ goto err;
+ }
+
+ rtc->rtc_dev->ops = &mca_rtc_ops;
+ ret = rtc_register_device(rtc->rtc_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to register RTC device.\n");
+ goto err;
+ }
+
+
+ /*
+ * Register interrupts. Complain on errors but let device
+ * to be registered at least for date/time.
+ */
+ rtc->irq_alarm = platform_get_irq_byname(pdev,
+ MCA_IRQ_RTC_ALARM_NAME);
+ ret = devm_request_threaded_irq(&pdev->dev, rtc->irq_alarm, NULL,
+ mca_alarm_event,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ MCA_IRQ_RTC_ALARM_NAME, rtc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request %s IRQ. (%d)\n",
+ MCA_IRQ_RTC_ALARM_NAME, rtc->irq_alarm);
+ rtc->irq_alarm = -ENXIO;
+ }
+
+ return 0;
+
+err:
+ rtc = NULL;
+ return ret;
+}
+
+static int mca_rtc_remove(struct platform_device *pdev)
+{
+ struct mca_rtc *rtc = platform_get_drvdata(pdev);
+
+ if (rtc->irq_alarm >= 0)
+ devm_free_irq(&pdev->dev, rtc->irq_alarm, rtc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mca_rtc_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mca_rtc *rtc = platform_get_drvdata(pdev);
+ int ret;
+
+ if (!device_may_wakeup(&pdev->dev) && rtc->alarm_enabled) {
+ /* Disable the alarm irq to avoid unwanted wakeups */
+ ret = mca_rtc_stop_alarm(&pdev->dev);
+ if (ret < 0)
+ dev_err(&pdev->dev, "Failed to disable RTC Alarm\n");
+ }
+
+ return 0;
+}
+
+static int mca_rtc_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mca_rtc *rtc = platform_get_drvdata(pdev);
+ int ret;
+
+ if (!device_may_wakeup(&pdev->dev) && rtc->alarm_enabled) {
+ /* Enable the alarm irq, just in case it was disabled suspending */
+ ret = mca_rtc_start_alarm(&pdev->dev);
+ if (ret < 0)
+ dev_err(&pdev->dev, "Failed to restart RTC Alarm\n");
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops mca_rtc_pm_ops = {
+ .suspend = mca_rtc_suspend,
+ .resume = mca_rtc_resume,
+ .poweroff = mca_rtc_suspend,
+};
+#endif
+
+#ifdef CONFIG_OF
+static struct mca_rtc_data mca_rtc_devdata[] = {
+ [CC6UL_MCA_RTC] = {
+ .devtype = CC6UL_MCA_RTC,
+ },
+ [CC8X_MCA_RTC] = {
+ .devtype = CC8X_MCA_RTC,
+ },
+};
+
+static const struct of_device_id mca_rtc_dt_ids[] = {
+ { .compatible = "digi,mca-cc6ul-rtc",
+ .data = &mca_rtc_devdata[CC6UL_MCA_RTC]},
+ { .compatible = "digi,mca-cc8x-rtc",
+ .data = &mca_rtc_devdata[CC8X_MCA_RTC]},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mca_rtc_dt_ids);
+#endif
+
+static struct platform_driver mca_rtc_driver = {
+ .probe = mca_rtc_probe,
+ .remove = mca_rtc_remove,
+ .driver = {
+ .name = MCA_DRVNAME_RTC,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &mca_rtc_pm_ops,
+#endif
+#ifdef CONFIG_OF
+ .of_match_table = mca_rtc_dt_ids,
+#endif
+ },
+};
+
+static int __init mca_rtc_init(void)
+{
+ return platform_driver_register(&mca_rtc_driver);
+}
+module_init(mca_rtc_init);
+
+static void __exit mca_rtc_exit(void)
+{
+ platform_driver_unregister(&mca_rtc_driver);
+}
+module_exit(mca_rtc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Digi International Inc.");
+MODULE_DESCRIPTION("Real time clock device driver for MCA of ConnectCore Modules");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" MCA_DRVNAME_RTC);

View File

@ -0,0 +1,490 @@
From 052464d671eb993264a78b3e35170c1d9fd44268 Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
Date: Mon, 23 Apr 2018 11:45:16 +0200
Subject: [PATCH] imx6ul: Add MCA power key support for ConnectCore 6UL SOM
Synched with v4.14.78/master at:
3f8b03950b323db4ca89b1cdc1c2288f79facaa3
Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
---
arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi | 9 +
arch/arm/configs/imx_v6_v7_defconfig | 1 +
drivers/input/misc/Kconfig | 12 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/pwrkey-mca.c | 401 ++++++++++++++++++++++++++++++
5 files changed, 424 insertions(+)
create mode 100644 drivers/input/misc/pwrkey-mca.c
diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
index 8163533c83b0..f2a17f9b88d1 100644
--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
+++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi
@@ -78,6 +78,15 @@
digi,full-reset;
};
+ pwrkey {
+ compatible = "digi,mca-cc6ul-pwrkey";
+ digi,key-power;
+ digi,key-sleep;
+ digi,debounce-ms = <100>;
+ digi,pwroff-delay-sec = <6>;
+ digi,pwroff-guard-sec = <30>;
+ };
+
mca_adc: adc {
compatible = "digi,mca-cc6ul-adc";
digi,adc-vref = <3000000>;
diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 8a763ab6a365..31d02719c51e 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -186,6 +186,7 @@ CONFIG_TOUCHSCREEN_SX8654=y
CONFIG_TOUCHSCREEN_COLIBRI_VF50=y
CONFIG_INPUT_MISC=y
CONFIG_INPUT_MMA8450=y
+CONFIG_INPUT_MCA_PWRKEY=y
CONFIG_SERIO_SERPORT=m
# CONFIG_LEGACY_PTYS is not set
CONFIG_SERIAL_IMX=y
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index ca59a2be9bc5..cef9795466f7 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -851,4 +851,16 @@ config INPUT_SC27XX_VIBRA
To compile this driver as a module, choose M here. The module will
be called sc27xx_vibra.
+config INPUT_MCA_PWRKEY
+ tristate "Digi ConnectCore SOMs Micro Controller Assist Power key"
+ select MFD_MCA_CC6UL if SOC_IMX6UL
+ select MFD_MCA_CC8X if ARCH_FSL_IMX8QXP
+ help
+ If you say Y here you will get support for the Power Key in the
+ Micro Controller Assist of Digi ConnectCore system-on-modules,
+ as an input device, reporting the power input/button status.
+
+ This driver can also be built as a module, if so, the module
+ will be called "pwrkey-mca".
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 9d0f9d1ff68f..1ed17d1b0c68 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
obj-$(CONFIG_INPUT_MAX8997_HAPTIC) += max8997_haptic.o
obj-$(CONFIG_INPUT_MC13783_PWRBUTTON) += mc13783-pwrbutton.o
+obj-$(CONFIG_INPUT_MCA_PWRKEY) += pwrkey-mca.o
obj-$(CONFIG_INPUT_MMA8450) += mma8450.o
obj-$(CONFIG_INPUT_PALMAS_PWRBUTTON) += palmas-pwrbutton.o
obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o
diff --git a/drivers/input/misc/pwrkey-mca.c b/drivers/input/misc/pwrkey-mca.c
new file mode 100644
index 000000000000..ddb83ef77045
--- /dev/null
+++ b/drivers/input/misc/pwrkey-mca.c
@@ -0,0 +1,401 @@
+/* pwrkey-mca.c - Power Key device driver for MCA on ConnectCore modules
+ * Copyright (C) 2016 - 2018 Digi International Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/mca-common/core.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+
+#define MCA_DRVNAME_PWRKEY "mca-pwrkey"
+
+#define DEFAULT_PWR_KEY_DEBOUNCE 150 /* 150 ms */
+#define DEFAULT_PWR_KEY_DELAY 4 /* 4 seconds */
+#define DEFAULT_PWR_KEY_GUARD 25 /* 25 seconds */
+#define MAX_PWR_KEY_DEBOUNCE 255
+#define MAX_PWR_KEY_DELAY 255
+#define MAX_PWR_KEY_GUARD 255
+
+#ifdef CONFIG_OF
+enum mca_pwrkey_type {
+ CC6UL_MCA_PWRKEY,
+ CC8X_MCA_PWRKEY,
+};
+
+struct mca_pwrkey_data {
+ enum mca_pwrkey_type devtype;
+ char drv_name_phys[40];
+};
+#endif
+
+struct mca_pwrkey {
+ struct mca_drv *mca;
+ struct input_dev *input;
+ int irq_power;
+ int irq_sleep;
+ bool key_power;
+ bool key_sleep;
+ bool suspended;
+ uint32_t debounce_ms;
+ uint32_t pwroff_delay_sec;
+ uint32_t pwroff_guard_sec;
+};
+
+#ifdef CONFIG_PM_SLEEP
+static DEFINE_SPINLOCK(lock);
+#endif
+
+static irqreturn_t mca_pwrkey_power_off_irq_handler(int irq, void *data)
+{
+ struct mca_pwrkey *pwrkey = data;
+
+ dev_notice(&pwrkey->input->dev, "Power Button - KEY_POWER\n");
+
+ input_report_key(pwrkey->input, KEY_POWER, 1);
+ input_sync(pwrkey->input);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mca_pwrkey_sleep_irq_handler(int irq, void *data)
+{
+ struct mca_pwrkey *pwrkey = data;
+
+ /* Report the event only if not coming from suspend */
+ if (!pwrkey->suspended) {
+ dev_notice(&pwrkey->input->dev, "Power button - KEY_SLEEP\n");
+
+ input_report_key(pwrkey->input, KEY_SLEEP, 1);
+ input_report_key(pwrkey->input, KEY_SLEEP, 0);
+ input_sync(pwrkey->input);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mca_pwrkey_initialize(struct mca_pwrkey *pwrkey)
+{
+ int ret;
+ uint8_t pwrctrl0 = 0;
+
+ ret = regmap_write(pwrkey->mca->regmap, MCA_PWR_KEY_DEBOUNCE,
+ (uint8_t)pwrkey->debounce_ms);
+ if (ret < 0) {
+ dev_err(pwrkey->mca->dev,
+ "Failed to set debounce time 0x%02x, %d\n",
+ (uint8_t)pwrkey->debounce_ms, ret);
+ return ret;
+ }
+
+ ret = regmap_write(pwrkey->mca->regmap, MCA_PWR_KEY_DELAY,
+ (uint8_t)pwrkey->pwroff_delay_sec);
+ if (ret < 0) {
+ dev_err(pwrkey->mca->dev,
+ "Failed to set delay time 0x%02x, %d\n",
+ (uint8_t)pwrkey->pwroff_delay_sec, ret);
+ return ret;
+ }
+
+ ret = regmap_write(pwrkey->mca->regmap, MCA_PWR_KEY_GUARD,
+ (uint8_t)pwrkey->pwroff_guard_sec);
+ if (ret < 0) {
+ dev_err(pwrkey->mca->dev,
+ "Failed to set guard time 0x%02x, %d\n",
+ (uint8_t)pwrkey->pwroff_guard_sec, ret);
+ return ret;
+ }
+
+ if (pwrkey->key_power)
+ pwrctrl0 |= MCA_PWR_KEY_OFF_EN;
+
+ if (pwrkey->key_sleep)
+ pwrctrl0 |= MCA_PWR_KEY_SLEEP_EN;
+
+ if (pwrkey->pwroff_guard_sec != 0)
+ pwrctrl0 |= MCA_PWR_GUARD_EN;
+
+ ret = regmap_write(pwrkey->mca->regmap, MCA_PWR_CTRL_0, pwrctrl0);
+ if (ret < 0) {
+ dev_err(pwrkey->mca->dev,
+ "Failed to set PWR_CTRL_0 0x%02x, %d\n",
+ pwrctrl0, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int of_mca_pwrkey_read_settings(struct device_node *np,
+ struct mca_pwrkey *pwrkey)
+{
+ uint32_t val;
+
+ /* Get driver configuration data from device tree */
+ pwrkey->debounce_ms = DEFAULT_PWR_KEY_DEBOUNCE;
+ pwrkey->pwroff_delay_sec = DEFAULT_PWR_KEY_DEBOUNCE;
+ pwrkey->pwroff_guard_sec = DEFAULT_PWR_KEY_GUARD;
+
+ pwrkey->key_power = of_property_read_bool(np, "digi,key-power");
+ pwrkey->key_sleep = of_property_read_bool(np, "digi,key-sleep");
+
+ if (!of_property_read_u32(np, "digi,debounce-ms", &val)) {
+ if (val <= MAX_PWR_KEY_DEBOUNCE)
+ pwrkey->debounce_ms = val;
+ else
+ dev_warn(pwrkey->mca->dev,
+ "Invalid debounce-ms value. Using default.\n");
+ }
+
+ if (!of_property_read_u32(np, "digi,pwroff-delay-sec", &val)) {
+ if (val <= MAX_PWR_KEY_DELAY)
+ pwrkey->pwroff_delay_sec = val;
+ else
+ dev_warn(pwrkey->mca->dev,
+ "Invalid pwroff-delay-sec value. Using default.\n");
+ }
+
+ if (!of_property_read_u32(np, "digi,pwroff-guard-sec", &val)) {
+ if (val <= MAX_PWR_KEY_GUARD)
+ pwrkey->pwroff_guard_sec = val;
+ else
+ dev_warn(pwrkey->mca->dev,
+ "Invalid pwroff-guard-sec value. Using default.\n");
+ }
+
+ return 0;
+}
+
+static int mca_pwrkey_probe(struct platform_device *pdev)
+{
+ struct mca_drv *mca = dev_get_drvdata(pdev->dev.parent);
+ struct mca_pwrkey *pwrkey;
+ const struct mca_pwrkey_data *devdata =
+ of_device_get_match_data(&pdev->dev);
+ struct device_node *np = NULL;
+ int ret = 0;
+
+ if (!mca || !mca->dev || !mca->dev->parent ||
+ !mca->dev->parent->of_node)
+ return -EPROBE_DEFER;
+
+ /* Find entry in device-tree */
+ if (mca->dev->of_node) {
+ const char * compatible = pdev->dev.driver->
+ of_match_table[devdata->devtype].compatible;
+
+ /* Return if pwrkey node does not exist or if it is disabled */
+ np = of_find_compatible_node(mca->dev->of_node, NULL, compatible);
+ if (!np || !of_device_is_available(np))
+ return -ENODEV;
+ }
+
+ pwrkey = devm_kzalloc(&pdev->dev, sizeof(struct mca_pwrkey),
+ GFP_KERNEL);
+ if (!pwrkey) {
+ dev_err(&pdev->dev, "Failed to allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ pwrkey->input = input_allocate_device();
+ if (!pwrkey->input) {
+ dev_err(&pdev->dev, "Failed to allocated input device.\n");
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ platform_set_drvdata(pdev, pwrkey);
+ pwrkey->mca = mca;
+ pwrkey->irq_power = platform_get_irq_byname(pdev,
+ MCA_IRQ_PWR_OFF_NAME);
+ pwrkey->irq_sleep = platform_get_irq_byname(pdev,
+ MCA_IRQ_PWR_SLEEP_NAME);
+ pwrkey->input->name = dev_name(&pdev->dev);
+ pwrkey->input->phys = devdata->drv_name_phys;
+ pwrkey->input->dev.parent = &pdev->dev;
+
+ input_set_capability(pwrkey->input, EV_KEY, KEY_POWER);
+ input_set_capability(pwrkey->input, EV_KEY, KEY_SLEEP);
+
+ /* Initialize driver settings from device tree */
+ ret = of_mca_pwrkey_read_settings(np, pwrkey);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to get %s dtb settings\n",
+ dev_name(&pdev->dev));
+ goto err_free_inputdev;
+ }
+
+ ret = mca_pwrkey_initialize(pwrkey);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to initilize pwrkey registers\n");
+ goto err_free_inputdev;
+ }
+
+ if (pwrkey->key_power) {
+ ret = devm_request_threaded_irq(&pdev->dev, pwrkey->irq_power, NULL,
+ mca_pwrkey_power_off_irq_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ MCA_IRQ_PWR_OFF_NAME, pwrkey);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request %s IRQ (%d).\n",
+ MCA_IRQ_PWR_OFF_NAME, pwrkey->irq_power);
+ goto err_free_inputdev;
+ }
+ }
+
+ if (pwrkey->key_sleep) {
+ ret = devm_request_threaded_irq(&pdev->dev, pwrkey->irq_sleep, NULL,
+ mca_pwrkey_sleep_irq_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ MCA_IRQ_PWR_SLEEP_NAME, pwrkey);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request %s IRQ (%d).\n",
+ MCA_IRQ_PWR_SLEEP_NAME, pwrkey->irq_sleep);
+ goto err_irq1;
+ }
+ enable_irq_wake(pwrkey->irq_sleep);
+ }
+
+ ret = input_register_device(pwrkey->input);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register input device (%d).\n",
+ ret);
+ goto err_irq2;
+ }
+
+ return 0;
+
+err_irq2:
+ if (pwrkey->key_sleep)
+ free_irq(pwrkey->mca->irq_base + pwrkey->irq_sleep, pwrkey);
+err_irq1:
+ if (pwrkey->key_power)
+ free_irq(pwrkey->mca->irq_base + pwrkey->irq_power, pwrkey);
+err_free_inputdev:
+ input_free_device(pwrkey->input);
+err_free:
+ kfree(pwrkey);
+
+ return ret;
+}
+
+static int mca_pwrkey_remove(struct platform_device *pdev)
+{
+ struct mca_pwrkey *pwrkey = platform_get_drvdata(pdev);
+
+ if (pwrkey->key_power)
+ free_irq(pwrkey->irq_power, pwrkey);
+ if (pwrkey->key_sleep)
+ free_irq(pwrkey->irq_sleep, pwrkey);
+ input_unregister_device(pwrkey->input);
+ kfree(pwrkey);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int __maybe_unused mca_pwrkey_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mca_pwrkey *pwrkey = platform_get_drvdata(pdev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&lock, flags);
+ pwrkey->suspended = false;
+ spin_unlock_irqrestore(&lock, flags);
+
+ return 0;
+}
+
+static int __maybe_unused mca_pwrkey_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mca_pwrkey *pwrkey = platform_get_drvdata(pdev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&lock, flags);
+ pwrkey->suspended = true;
+ spin_unlock_irqrestore(&lock, flags);
+
+ return 0;
+}
+
+SIMPLE_DEV_PM_OPS(mca_pwrkey_pm_ops, mca_pwrkey_suspend, mca_pwrkey_resume);
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_OF
+static struct mca_pwrkey_data mca_pwrkey_devdata[] = {
+ [CC6UL_MCA_PWRKEY] = {
+ .devtype = CC6UL_MCA_PWRKEY,
+ .drv_name_phys= "mca-cc6ul-pwrkey/input0"
+ },
+ [CC8X_MCA_PWRKEY] = {
+ .devtype = CC8X_MCA_PWRKEY,
+ .drv_name_phys= "mca-cc8x-pwrkey/input0"
+ },
+};
+
+static const struct of_device_id mca_pwrkey_ids[] = {
+ { .compatible = "digi,mca-cc6ul-pwrkey",
+ .data = &mca_pwrkey_devdata[CC6UL_MCA_PWRKEY]},
+ { .compatible = "digi,mca-cc8x-pwrkey",
+ .data = &mca_pwrkey_devdata[CC8X_MCA_PWRKEY]},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mca_pwrkey_ids);
+#endif
+
+static struct platform_driver mca_pwrkey_driver = {
+ .probe = mca_pwrkey_probe,
+ .remove = mca_pwrkey_remove,
+ .driver = {
+ .name = MCA_DRVNAME_PWRKEY,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(mca_pwrkey_ids),
+#ifdef CONFIG_PM_SLEEP
+ .pm = &mca_pwrkey_pm_ops,
+#endif
+ },
+};
+
+static int __init mca_pwrkey_init(void)
+{
+ return platform_driver_register(&mca_pwrkey_driver);
+}
+module_init(mca_pwrkey_init);
+
+static void __exit mca_pwrkey_exit(void)
+{
+ platform_driver_unregister(&mca_pwrkey_driver);
+}
+module_exit(mca_pwrkey_exit);
+
+MODULE_AUTHOR("Digi International Inc");
+MODULE_DESCRIPTION("pwrkey device driver for MCA of ConnectCore Modules");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" MCA_DRVNAME_PWRKEY);

View File

@ -0,0 +1,15 @@
FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
SRC_URI_append_ccimx6ul_use-mainline-bsp = " \
file://0001-ARM-Add-support-for-the-ConnectCore-6UL-System-On-Mo.patch \
file://0002-mach-imx-pm-imx6-Add-hooks-for-board-specific-implem.patch \
file://0003-imx6ul-Add-MCA-core-I2C-driver-support.patch \
file://0004-imx6ul-Add-MCA-GPIO-support-for-the-ConnectCore-6UL-.patch \
file://0005-imx6ul-Add-MCA-IOMUX-support-to-the-ConnectCore-6UL-.patch \
file://0006-imx6ul-Add-MCA-watchdog-support-for-the-ConnectCore-.patch \
file://0007-imx6ul-Add-MCA-ADC-support-for-ConnectCore-6UL-SOM-a.patch \
file://0008-imx6ul-Add-MCA-tamper-support-for-ConnectCore-6UL-SO.patch \
file://0009-imx6ul-Add-MCA-UART-support-for-ConnectCore-6UL-SOM-.patch \
file://0010-imx6ul-Add-RTC-MCA-support-for-ConnectCore-6UL-SOM.patch \
file://0011-imx6ul-Add-MCA-power-key-support-for-ConnectCore-6UL.patch \
"