meta-digi-arm: linux-fslc: ccimx6ul: Add MCA support
Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
This commit is contained in:
parent
9d063498c8
commit
4cf572edf5
|
|
@ -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);
|
||||
|
|
@ -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,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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 = <ðphy0>;
|
||||
+ 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 = <ðphy0>;
|
||||
+ 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);
|
||||
+
|
||||
|
|
@ -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
|
||||
+ >;
|
||||
+ };
|
||||
};
|
||||
|
||||
®_arm {
|
||||
|
|
@ -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);
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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 \
|
||||
"
|
||||
Loading…
Reference in New Issue