From 4cf572edf541cd14dcfa6c572f942ebf775bde1f Mon Sep 17 00:00:00 2001 From: Alex Gonzalez Date: Thu, 4 Apr 2019 12:52:57 +0200 Subject: [PATCH] meta-digi-arm: linux-fslc: ccimx6ul: Add MCA support Signed-off-by: Alex Gonzalez --- ...for-the-ConnectCore-6UL-System-On-Mo.patch | 167 + ...-Add-hooks-for-board-specific-implem.patch | 65 + ...x6ul-Add-MCA-core-I2C-driver-support.patch | 4452 +++++++++++++++++ ...PIO-support-for-the-ConnectCore-6UL-.patch | 701 +++ ...OMUX-support-to-the-ConnectCore-6UL-.patch | 33 + ...atchdog-support-for-the-ConnectCore-.patch | 467 ++ ...DC-support-for-ConnectCore-6UL-SOM-a.patch | 1066 ++++ ...amper-support-for-ConnectCore-6UL-SO.patch | 937 ++++ ...ART-support-for-ConnectCore-6UL-SOM-.patch | 1097 ++++ ...-MCA-support-for-ConnectCore-6UL-SOM.patch | 540 ++ ...ower-key-support-for-ConnectCore-6UL.patch | 490 ++ .../linux/linux-fslc_%.bbappend | 15 + 12 files changed, 10030 insertions(+) create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0001-ARM-Add-support-for-the-ConnectCore-6UL-System-On-Mo.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0002-mach-imx-pm-imx6-Add-hooks-for-board-specific-implem.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0003-imx6ul-Add-MCA-core-I2C-driver-support.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0004-imx6ul-Add-MCA-GPIO-support-for-the-ConnectCore-6UL-.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0005-imx6ul-Add-MCA-IOMUX-support-to-the-ConnectCore-6UL-.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0006-imx6ul-Add-MCA-watchdog-support-for-the-ConnectCore-.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0007-imx6ul-Add-MCA-ADC-support-for-ConnectCore-6UL-SOM-a.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0008-imx6ul-Add-MCA-tamper-support-for-ConnectCore-6UL-SO.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0009-imx6ul-Add-MCA-UART-support-for-ConnectCore-6UL-SOM-.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0010-imx6ul-Add-RTC-MCA-support-for-ConnectCore-6UL-SOM.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0011-imx6ul-Add-MCA-power-key-support-for-ConnectCore-6UL.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc_%.bbappend diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0001-ARM-Add-support-for-the-ConnectCore-6UL-System-On-Mo.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0001-ARM-Add-support-for-the-ConnectCore-6UL-System-On-Mo.patch new file mode 100644 index 000000000..76709c900 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0001-ARM-Add-support-for-the-ConnectCore-6UL-System-On-Mo.patch @@ -0,0 +1,167 @@ +From 2313935f3b195aa7d930961bcd44a5fac61a945e Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +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 +--- + 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 ++#include ++#include ++#include ++#include ++ ++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); diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0002-mach-imx-pm-imx6-Add-hooks-for-board-specific-implem.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0002-mach-imx-pm-imx6-Add-hooks-for-board-specific-implem.patch new file mode 100644 index 000000000..725ea67f5 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0002-mach-imx-pm-imx6-Add-hooks-for-board-specific-implem.patch @@ -0,0 +1,65 @@ +From d0520a166cdeccf2821a45483602fc24a1772569 Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +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 +Signed-off-by: Pedro Perez de Heredia +--- + 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, + }; diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0003-imx6ul-Add-MCA-core-I2C-driver-support.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0003-imx6ul-Add-MCA-core-I2C-driver-support.patch new file mode 100644 index 000000000..da5b0e8b0 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0003-imx6ul-Add-MCA-core-I2C-driver-support.patch @@ -0,0 +1,4452 @@ +From 2269c7056da80faf5821787373d72e1d9b3edadc Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +Date: Mon, 1 Apr 2019 13:52:06 +0200 +Subject: [PATCH] imx6ul: Add MCA core I2C driver support + +Synched with v4.14.78/master at +3f8b03950b323db4ca89b1cdc1c2288f79facaa3 + +Signed-off-by: Alex Gonzalez +--- + arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi | 12 + + drivers/mfd/Kconfig | 24 + + drivers/mfd/Makefile | 4 + + drivers/mfd/mca-cc6ul-core.c | 989 +++++++++++++++++++++ + drivers/mfd/mca-cc6ul-i2c.c | 219 +++++ + drivers/mfd/mca-cc6ul-irq.c | 113 +++ + drivers/mfd/mca-cc8x-core.c | 1040 ++++++++++++++++++++++ + drivers/mfd/mca-cc8x-i2c.c | 222 +++++ + drivers/mfd/mca-cc8x-irq.c | 143 +++ + include/linux/mfd/mca-cc6ul/core.h | 56 ++ + include/linux/mfd/mca-cc6ul/registers.h | 61 ++ + include/linux/mfd/mca-common/core.h | 71 ++ + include/linux/mfd/mca-common/registers.h | 1373 +++++++++++++++++++++++++++++ + 13 files changed, 4327 insertions(+) + create mode 100644 drivers/mfd/mca-cc6ul-core.c + create mode 100644 drivers/mfd/mca-cc6ul-i2c.c + create mode 100644 drivers/mfd/mca-cc6ul-irq.c + create mode 100644 drivers/mfd/mca-cc8x-core.c + create mode 100644 drivers/mfd/mca-cc8x-i2c.c + create mode 100644 drivers/mfd/mca-cc8x-irq.c + create mode 100644 include/linux/mfd/mca-cc6ul/core.h + create mode 100644 include/linux/mfd/mca-cc6ul/registers.h + create mode 100644 include/linux/mfd/mca-common/core.h + create mode 100644 include/linux/mfd/mca-common/registers.h + +diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi +index 82fa176bf138..03c62926ca2b 100644 +--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi ++++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi +@@ -48,6 +48,18 @@ + pinctrl-0 = <&pinctrl_i2c1>; + status = "okay"; + ++ mca_cc6ul: mca@7e { ++ compatible = "digi,mca_cc6ul_dt_ids"; ++ reg = <0x7e>; ++ interrupt-parent = <&gpio5>; ++ interrupts = <4 IRQ_TYPE_EDGE_FALLING>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ fw-update-gpio = <&gpio4 14 GPIO_ACTIVE_LOW>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_mca_cc6ul>; ++ }; ++ + pfuze3000: pmic@8 { + compatible = "fsl,pfuze3000"; + reg = <0x08>; +diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +index 8c5dfdce4326..25f1acff2d0a 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -434,6 +434,30 @@ config MFD_MXS_LRADC + This driver can also be built as a module. If so, the module will be + called mxs-lradc. + ++config MFD_MCA_CC6UL ++ bool "Digi Micro Controller Assist for ConnectCore 6UL" ++ select MFD_CORE ++ select REGMAP_I2C ++ select REGMAP_IRQ ++ depends on I2C=y ++ help ++ Select this option to enable support for MCA on the ConnectCore 6UL. ++ This includes the I2C driver and core APIs. ++ Additional drivers must be enabled in order to use the functionality ++ of the device (RTC, watchdog, ...). ++ ++config MFD_MCA_CC8X ++ bool "Digi Micro Controller Assist for ConnectCore 8X" ++ select MFD_CORE ++ select REGMAP_I2C ++ select REGMAP_IRQ ++ depends on I2C=y ++ help ++ Select this option to enable support for MCA on the ConnectCore 8X. ++ This includes the I2C driver and core APIs. ++ Additional drivers must be enabled in order to use the functionality ++ of the device (RTC, watchdog, ...). ++ + config MFD_MX25_TSADC + tristate "Freescale i.MX25 integrated Touchscreen and ADC unit" + select REGMAP_MMIO +diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile +index 12980a4ad460..28998c947184 100644 +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -242,3 +242,7 @@ obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o + obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o + obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o + ++mca-cc6ul-objs := mca-cc6ul-core.o mca-cc6ul-irq.o mca-cc6ul-i2c.o ++obj-$(CONFIG_MFD_MCA_CC6UL) += mca-cc6ul.o ++mca-cc8x-objs := mca-cc8x-core.o mca-cc8x-irq.o mca-cc8x-i2c.o ++obj-$(CONFIG_MFD_MCA_CC8X) += mca-cc8x.o +diff --git a/drivers/mfd/mca-cc6ul-core.c b/drivers/mfd/mca-cc6ul-core.c +new file mode 100644 +index 000000000000..443cfe7e3ca1 +--- /dev/null ++++ b/drivers/mfd/mca-cc6ul-core.c +@@ -0,0 +1,989 @@ ++/* ++ * Copyright 2016 - 2019 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++ ++#define MCA_CC6UL_NVRAM_SIZE (MCA_CC6UL_MPU_NVRAM_END - MCA_CC6UL_MPU_NVRAM_START + 1) ++ ++extern int digi_get_som_hv(void); ++ ++struct dyn_attribute { ++ u16 since; /* Minimum firmware version required */ ++ struct attribute *attr; ++}; ++ ++struct mca_reason { ++ u32 flag; ++ const char *text; ++}; ++ ++static const struct mca_reason last_mca_reset[] = { ++ {MCA_CC6UL_LAST_MCA_RST_LLW, "LL Wakeup"}, ++ {MCA_CC6UL_LAST_MCA_RST_LVD, "Low Voltage"}, ++ {MCA_CC6UL_LAST_MCA_RST_WD, "Watchdog"}, ++ {MCA_CC6UL_LAST_MCA_RST_PIN, "Reset Pin"}, ++ {MCA_CC6UL_LAST_MCA_RST_PWRON, "Power On"}, ++ {MCA_CC6UL_LAST_MCA_RST_LOCKUP, "Core Lockup"}, ++ {MCA_CC6UL_LAST_MCA_RST_SW, "Software"}, ++ {MCA_CC6UL_LAST_MCA_RST_MDMAPP, "MDM-APP debuger"}, ++ {MCA_CC6UL_LAST_MCA_RST_SMAE, "Stop Mode Ack Error"}, ++}; ++ ++static const struct mca_reason last_mpu_reset[] = { ++ {MCA_CC6UL_LAST_MPU_RST_PWRON, "Power On"}, ++ {MCA_CC6UL_LAST_MPU_RST_SYSR, "System Reset"}, ++ {MCA_CC6UL_LAST_MPU_RST_WD, "Watchdog"}, ++ {MCA_CC6UL_LAST_MPU_RST_OFFWAKE,"Off wakeup"}, ++ {MCA_CC6UL_LAST_MPU_RST_MCARST, "MCA reset"}, ++}; ++ ++static const struct mca_reason last_wakeup[] = { ++ {MCA_CC6UL_LAST_WAKEUP_PWRIO, "Power IO"}, ++ {MCA_CC6UL_LAST_WAKEUP_TIMER, "Timer"}, ++ {MCA_CC6UL_LAST_WAKEUP_RTC, "RTC"}, ++ {MCA_CC6UL_LAST_WAKEUP_LPUART, "LP UART"}, ++ {MCA_CC6UL_LAST_WAKEUP_TAMPER0, "Tamper0"}, ++ {MCA_CC6UL_LAST_WAKEUP_TAMPER1, "Tamper1"}, ++ {MCA_CC6UL_LAST_WAKEUP_TAMPER2, "Tamper2"}, ++ {MCA_CC6UL_LAST_WAKEUP_TAMPER3, "Tamper3"}, ++ {MCA_CC6UL_LAST_WAKEUP_IO0, "IO0"}, ++ {MCA_CC6UL_LAST_WAKEUP_IO1, "IO1"}, ++ {MCA_CC6UL_LAST_WAKEUP_IO2, "IO2"}, ++ {MCA_CC6UL_LAST_WAKEUP_IO3, "IO3"}, ++ {MCA_CC6UL_LAST_WAKEUP_IO4, "IO4"}, ++ {MCA_CC6UL_LAST_WAKEUP_IO5, "IO5"}, ++ {MCA_CC6UL_LAST_WAKEUP_IO6, "IO6"}, ++ {MCA_CC6UL_LAST_WAKEUP_IO7, "IO7"}, ++ {MCA_CC6UL_LAST_WAKEUP_VCC, "Vcc"}, ++ {MCA_CC6UL_LAST_WAKEUP_CPU, "CPU"}, ++}; ++ ++static struct mca_drv *pmca; ++ ++static const char _enabled[] = "enabled"; ++static const char _disabled[] = "disabled"; ++ ++static struct resource mca_cc6ul_rtc_resources[] = { ++ { ++ .name = MCA_IRQ_RTC_ALARM_NAME, ++ .start = MCA_CC6UL_IRQ_RTC_ALARM, ++ .end = MCA_CC6UL_IRQ_RTC_ALARM, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_RTC_1HZ_NAME, ++ .start = MCA_CC6UL_IRQ_RTC_1HZ, ++ .end = MCA_CC6UL_IRQ_RTC_1HZ, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc6ul_watchdog_resources[] = { ++ { ++ .name = MCA_IRQ_WATCHDOG_NAME, ++ .start = MCA_CC6UL_IRQ_WATCHDOG, ++ .end = MCA_CC6UL_IRQ_WATCHDOG, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc6ul_pwrkey_resources[] = { ++ { ++ .name = MCA_IRQ_PWR_SLEEP_NAME, ++ .start = MCA_CC6UL_IRQ_PWR_SLEEP, ++ .end = MCA_CC6UL_IRQ_PWR_SLEEP, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_PWR_OFF_NAME, ++ .start = MCA_CC6UL_IRQ_PWR_OFF, ++ .end = MCA_CC6UL_IRQ_PWR_OFF, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc6ul_adc_resources[] = { ++ { ++ .name = MCA_IRQ_ADC_NAME, ++ .start = MCA_CC6UL_IRQ_ADC, ++ .end = MCA_CC6UL_IRQ_ADC, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc6ul_tamper_resources[] = { ++ { ++ .name = MCA_IRQ_TAMPER0_NAME, ++ .start = MCA_CC6UL_IRQ_TAMPER0, ++ .end = MCA_CC6UL_IRQ_TAMPER0, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_TAMPER1_NAME, ++ .start = MCA_CC6UL_IRQ_TAMPER1, ++ .end = MCA_CC6UL_IRQ_TAMPER1, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_TAMPER2_NAME, ++ .start = MCA_CC6UL_IRQ_TAMPER2, ++ .end = MCA_CC6UL_IRQ_TAMPER2, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_TAMPER3_NAME, ++ .start = MCA_CC6UL_IRQ_TAMPER3, ++ .end = MCA_CC6UL_IRQ_TAMPER3, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc6ul_gpios_resources[] = { ++ { ++ .name = MCA_IRQ_GPIO_BANK_0_NAME, ++ .start = MCA_CC6UL_IRQ_GPIO_BANK_0, ++ .end = MCA_CC6UL_IRQ_GPIO_BANK_0, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc6ul_uart_resources[] = { ++ { ++ .name = MCA_IRQ_UART_NAME, ++ .start = MCA_CC6UL_IRQ_UART, ++ .end = MCA_CC6UL_IRQ_UART, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static const struct mfd_cell mca_cc6ul_devs[] = { ++ { ++ .name = MCA_CC6UL_DRVNAME_RTC, ++ .num_resources = ARRAY_SIZE(mca_cc6ul_rtc_resources), ++ .resources = mca_cc6ul_rtc_resources, ++ .of_compatible = "digi,mca-cc6ul-rtc", ++ }, ++ { ++ .name = MCA_CC6UL_DRVNAME_WATCHDOG, ++ .num_resources = ARRAY_SIZE(mca_cc6ul_watchdog_resources), ++ .resources = mca_cc6ul_watchdog_resources, ++ .of_compatible = "digi,mca-cc6ul-watchdog", ++ }, ++ { ++ .name = MCA_CC6UL_DRVNAME_GPIO, ++ .num_resources = ARRAY_SIZE(mca_cc6ul_gpios_resources), ++ .resources = mca_cc6ul_gpios_resources, ++ .of_compatible = "digi,mca-cc6ul-gpio", ++ }, ++ { ++ .name = MCA_CC6UL_DRVNAME_PWRKEY, ++ .num_resources = ARRAY_SIZE(mca_cc6ul_pwrkey_resources), ++ .resources = mca_cc6ul_pwrkey_resources, ++ .of_compatible = "digi,mca-cc6ul-pwrkey", ++ }, ++ { ++ .name = MCA_CC6UL_DRVNAME_ADC, ++ .of_compatible = "digi,mca-cc6ul-adc", ++ .num_resources = ARRAY_SIZE(mca_cc6ul_adc_resources), ++ .resources = mca_cc6ul_adc_resources, ++ }, ++ { ++ .name = MCA_CC6UL_DRVNAME_TAMPER, ++ .num_resources = ARRAY_SIZE(mca_cc6ul_tamper_resources), ++ .resources = mca_cc6ul_tamper_resources, ++ .of_compatible = "digi,mca-cc6ul-tamper", ++ }, ++ { ++ .name = MCA_CC6UL_DRVNAME_UART, ++ .num_resources = ARRAY_SIZE(mca_cc6ul_uart_resources), ++ .resources = mca_cc6ul_uart_resources, ++ .of_compatible = "digi,mca-cc6ul-uart", ++ }, ++}; ++ ++/* Read a block of registers */ ++int mca_cc6ul_read_block(struct mca_drv *mca, u16 addr, u8 *data, ++ size_t nregs) ++{ ++ int ret; ++ ++ /* TODO, check limits nregs... */ ++ ++ ret = regmap_raw_read(mca->regmap, addr, data, nregs); ++ if (ret != 0) ++ return ret; ++ ++ return ret; ++ ++} ++EXPORT_SYMBOL_GPL(mca_cc6ul_read_block); ++ ++/* Write a block of data into MCA registers */ ++int mca_cc6ul_write_block(struct mca_drv *mca , u16 addr, u8 *data, ++ size_t nregs) ++{ ++ u8 *frame; /* register address + payload */ ++ u8 *payload; ++ int ret; ++ ++ /* TODO, check limits nregs... */ ++ ++ frame = kzalloc(sizeof(addr) + nregs, GFP_KERNEL | GFP_DMA); ++ if (!frame) ++ return -ENOMEM; ++ ++ payload = frame + sizeof(addr); ++ memcpy(payload, data, nregs); ++ ++ /* Write payload */ ++ ret = regmap_raw_write(mca->regmap, addr, payload, nregs); ++ ++ kfree(frame); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(mca_cc6ul_write_block); ++ ++static int mca_cc6ul_unlock_ctrl(struct mca_drv *mca) ++{ ++ int ret; ++ const uint8_t unlock_pattern[] = {'C', 'T', 'R', 'U'}; ++ ++ ret = regmap_bulk_write(mca->regmap, MCA_CTRL_UNLOCK_0, ++ unlock_pattern, sizeof(unlock_pattern)); ++ if (ret) ++ dev_warn(mca->dev, "failed to unlock CTRL registers (%d)\n", ++ ret); ++ ++ return ret; ++} ++ ++static int mca_cc6ul_get_tick_cnt(struct mca_drv *mca, u32 *tick) ++{ ++ return regmap_bulk_read(mca->regmap, MCA_TIMER_TICK_0, ++ tick, sizeof(*tick)); ++} ++ ++/* sysfs attributes */ ++static ssize_t ext_32khz_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ unsigned int val; ++ int ret; ++ ++ ret = regmap_read(mca->regmap, MCA_CTRL_0, &val); ++ if (ret) { ++ dev_err(mca->dev, "Cannot read MCA CTRL_0 register(%d)\n", ++ ret); ++ return 0; ++ } ++ ++ return sprintf(buf, "%s\n", val & MCA_EXT32K_EN ? ++ _enabled : _disabled); ++} ++ ++static ssize_t ext_32khz_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ bool enable; ++ int ret; ++ ++ if (!strncmp(buf, _enabled, sizeof(_enabled) - 1)) ++ enable = true; ++ else if (!strncmp(buf, _disabled, sizeof(_disabled) - 1)) ++ enable = false; ++ else ++ return -EINVAL; ++ ++ ret = mca_cc6ul_unlock_ctrl(mca); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(mca->regmap, MCA_CTRL_0, ++ MCA_EXT32K_EN, ++ enable ? MCA_EXT32K_EN : 0); ++ if (ret) { ++ dev_err(mca->dev, "Cannot update MCA CTRL_0 register (%d)\n", ret); ++ return ret; ++ } ++ ++ return count; ++} ++static DEVICE_ATTR(ext_32khz, 0600, ext_32khz_show, ext_32khz_store); ++ ++static ssize_t vref_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ unsigned int val; ++ int ret; ++ ++ ret = regmap_read(mca->regmap, MCA_CTRL_0, &val); ++ if (ret) { ++ dev_err(mca->dev, "Cannot read MCA CTRL_0 register(%d)\n", ++ ret); ++ return 0; ++ } ++ ++ return sprintf(buf, "%s\n", val & MCA_VREF_EN ? ++ _enabled : _disabled); ++} ++ ++static ssize_t vref_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ bool enable; ++ int ret; ++ ++ if (!strncmp(buf, _enabled, sizeof(_enabled) - 1)) ++ enable = true; ++ else if (!strncmp(buf, _disabled, sizeof(_disabled) - 1)) ++ enable = false; ++ else ++ return -EINVAL; ++ ++ ret = mca_cc6ul_unlock_ctrl(mca); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(mca->regmap, MCA_CTRL_0, ++ MCA_VREF_EN, ++ enable ? MCA_VREF_EN : 0); ++ if (ret) { ++ dev_err(mca->dev, "Cannot update MCA CTRL_0 register (%d)\n", ret); ++ return ret; ++ } ++ ++ return count; ++} ++static DEVICE_ATTR(vref, 0600, vref_show, vref_store); ++ ++static ssize_t hwver_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d\n", mca->hw_version); ++} ++static DEVICE_ATTR(hw_version, S_IRUGO, hwver_show, NULL); ++ ++static ssize_t fwver_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d.%02d %s\n", MCA_FW_VER_MAJOR(mca->fw_version), ++ MCA_FW_VER_MINOR(mca->fw_version), ++ mca->fw_is_alpha ? "(alpha)" : ""); ++} ++static DEVICE_ATTR(fw_version, S_IRUGO, fwver_show, NULL); ++ ++static ssize_t tick_cnt_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ u32 tick_cnt; ++ int ret; ++ ++ ret = mca_cc6ul_get_tick_cnt(mca, &tick_cnt); ++ if (ret) { ++ dev_err(mca->dev, "Cannot read MCA tick counter(%d)\n", ret); ++ return ret; ++ } ++ ++ return sprintf(buf, "%u\n", tick_cnt); ++} ++static DEVICE_ATTR(tick_cnt, S_IRUGO, tick_cnt_show, NULL); ++ ++static ssize_t fw_update_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ ++ if (!gpio_is_valid(mca->fw_update_gpio)) ++ return -EINVAL; ++ ++ return sprintf(buf, "%d\n", ++ gpio_get_value_cansleep(mca->fw_update_gpio)); ++} ++ ++static ssize_t fw_update_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ ssize_t status; ++ long value; ++ ++ if (!gpio_is_valid(mca->fw_update_gpio)) ++ return -EINVAL; ++ ++ status = kstrtol(buf, 0, &value); ++ if (status == 0) { ++ gpio_set_value_cansleep(mca->fw_update_gpio, value); ++ status = count; ++ } ++ ++ return status; ++} ++static DEVICE_ATTR(fw_update, 0600, fw_update_show, fw_update_store); ++ ++static ssize_t last_wakeup_reason_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ bool comma = false; ++ u32 last_wakeup_val; ++ int ret, i; ++ ++ ret = regmap_bulk_read(mca->regmap, MCA_LAST_WAKEUP_REASON_0, ++ &last_wakeup_val, sizeof(last_wakeup_val)); ++ if (ret) { ++ dev_err(mca->dev, ++ "Cannot read last MCA wakeup reason (%d)\n", ++ ret); ++ return ret; ++ } ++ ++ buf[0] = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(last_wakeup); i++) { ++ if (last_wakeup[i].flag & last_wakeup_val) { ++ if (comma) ++ strcat(buf, ", "); ++ strcat(buf, last_wakeup[i].text); ++ comma = true; ++ } ++ } ++ ++ if (comma) ++ strcat(buf, "\n"); ++ ++ return strlen(buf); ++} ++static DEVICE_ATTR(last_wakeup_reason, S_IRUGO, last_wakeup_reason_show, NULL); ++ ++static ssize_t last_mca_reset_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ bool comma = false; ++ int i; ++ ++ buf[0] = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(last_mca_reset); i++) { ++ if (last_mca_reset[i].flag & mca->last_mca_reset) { ++ if (comma) ++ strcat(buf, ", "); ++ strcat(buf, last_mca_reset[i].text); ++ comma = true; ++ } ++ } ++ ++ if (comma) ++ strcat(buf, "\n"); ++ ++ return strlen(buf); ++} ++static DEVICE_ATTR(last_mca_reset, S_IRUGO, last_mca_reset_show, NULL); ++ ++static ssize_t last_mpu_reset_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ bool comma = false; ++ int i; ++ ++ buf[0] = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(last_mpu_reset); i++) { ++ if (last_mpu_reset[i].flag & mca->last_mpu_reset) { ++ if (comma) ++ strcat(buf, ", "); ++ strcat(buf, last_mpu_reset[i].text); ++ comma = true; ++ } ++ } ++ ++ if (comma) ++ strcat(buf, "\n"); ++ ++ return strlen(buf); ++} ++static DEVICE_ATTR(last_mpu_reset, S_IRUGO, last_mpu_reset_show, NULL); ++ ++static ssize_t nvram_read(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, char *buf, loff_t off, ++ size_t count) ++{ ++ struct device *dev = kobj_to_dev(kobj); ++ struct mca_drv *mca; ++ int ret; ++ ++ if (!dev || (mca = dev_get_drvdata(dev)) == NULL) ++ return -ENODEV; ++ ++ if (unlikely(off >= MCA_CC6UL_NVRAM_SIZE) || unlikely(!count)) ++ return 0; ++ if ((off + count) > MCA_CC6UL_NVRAM_SIZE) ++ count = MCA_CC6UL_NVRAM_SIZE - off; ++ ++ ret = regmap_bulk_read(mca->regmap, ++ MCA_CC6UL_MPU_NVRAM_START + off, buf, count); ++ if (ret) { ++ dev_err(mca->dev, "%s error (%d)\n", __func__, ret); ++ return ret; ++ } ++ ++ return count; ++} ++ ++static ssize_t nvram_write(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, char *buf, loff_t off, ++ size_t count) ++{ ++ struct device *dev = kobj_to_dev(kobj); ++ struct mca_drv *mca; ++ int ret; ++ ++ if (!dev || (mca = dev_get_drvdata(dev)) == NULL) ++ return -ENODEV; ++ ++ if (unlikely(off >= MCA_CC6UL_NVRAM_SIZE)) ++ return -EFBIG; ++ if ((off + count) > MCA_CC6UL_NVRAM_SIZE) ++ count = MCA_CC6UL_NVRAM_SIZE - off; ++ if (unlikely(!count)) ++ return count; ++ ++ ret = regmap_bulk_write(mca->regmap, ++ MCA_CC6UL_MPU_NVRAM_START + off, buf, count); ++ if (ret) { ++ dev_err(mca->dev, "%s error (%d)\n", __func__, ret); ++ return ret; ++ } ++ ++ return count; ++} ++ ++static struct attribute *mca_cc6ul_sysfs_entries[] = { ++ &dev_attr_ext_32khz.attr, ++ &dev_attr_hw_version.attr, ++ &dev_attr_fw_version.attr, ++ &dev_attr_fw_update.attr, ++ NULL, ++}; ++ ++static struct attribute_group mca_cc6ul_attr_group = { ++ .name = NULL, /* put in device directory */ ++ .attrs = mca_cc6ul_sysfs_entries, ++}; ++ ++static struct dyn_attribute mca_cc6ul_sysfs_dyn_entries[] = { ++ { ++ .since = MCA_MAKE_FW_VER(0,15), ++ .attr = &dev_attr_tick_cnt.attr, ++ }, ++ { ++ .since = MCA_MAKE_FW_VER(0,15), ++ .attr = &dev_attr_vref.attr, ++ }, ++ { ++ .since = MCA_MAKE_FW_VER(1,2), ++ .attr = &dev_attr_last_wakeup_reason.attr, ++ }, ++ { ++ .since = MCA_MAKE_FW_VER(1,2), ++ .attr = &dev_attr_last_mca_reset.attr, ++ }, ++ { ++ .since = MCA_MAKE_FW_VER(1,2), ++ .attr = &dev_attr_last_mpu_reset.attr, ++ }, ++}; ++ ++int mca_cc6ul_suspend(struct device *dev) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ ++ if (!mca) { ++ dev_err(dev, " mca was null in %s\n", __func__); ++ return -ENODEV; ++ } ++ ++ /* Set the suspend bit in PWR_CTRL_0 */ ++ return regmap_update_bits(pmca->regmap, MCA_PWR_CTRL_0, ++ MCA_PWR_GO_SUSPEND, ++ MCA_PWR_GO_SUSPEND); ++} ++ ++#define MCA_MAX_RESUME_RD_RETRIES 10 ++int mca_cc6ul_resume(struct device *dev) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ unsigned int val; ++ int ret, retries = 0; ++ ++ if (!mca) { ++ dev_err(dev, " mca was null in %s\n", __func__); ++ return -ENODEV; ++ } ++ ++ /* ++ * Generate traffic on the i2c bus to wakeup the MCA, in case it was in ++ * low power ++ */ ++ do { ++ ret = regmap_read(mca->regmap, MCA_DEVICE_ID, &val); ++ if (!ret && mca->dev_id == (u8)val) ++ break; ++ udelay(50); ++ } while (++retries < MCA_MAX_RESUME_RD_RETRIES); ++ ++ if (retries == MCA_MAX_RESUME_RD_RETRIES) { ++ dev_err(mca->dev, "unable to wake up MCA (%d)\n", ret); ++ return ret; ++ } ++ ++ /* Reset the suspend bit in PWR_CTRL_0 */ ++ return regmap_update_bits(pmca->regmap, MCA_PWR_CTRL_0, ++ MCA_PWR_GO_SUSPEND, ++ 0); ++} ++ ++#define MCA_MAX_PWROFF_TRIES 5 ++static void mca_cc6ul_power_off(void) ++{ ++ int try = 0; ++ int ret; ++ ++ if (!pmca) { ++ printk(KERN_ERR "ERROR: unable to power off [%s:%d/%s()]!\n", ++ __FILE__, __LINE__, __func__); ++ return; ++ } ++ ++ do { ++ /* Set power off bit in PWR_CTRL_0 register to shutdown */ ++ ret = regmap_update_bits(pmca->regmap, MCA_PWR_CTRL_0, ++ MCA_PWR_GO_OFF, ++ MCA_PWR_GO_OFF); ++ if (ret) ++ printk(KERN_ERR "ERROR: accesing PWR_CTRL_0 register " ++ "[%s:%d/%s()]!\n", __FILE__, __LINE__, __func__); ++ ++ /* ++ * Even if the regmap update returned with success, retry... ++ * we are powering off, so there is nothing bad by doing it. ++ */ ++ mdelay(50); ++ } while (++try < MCA_MAX_PWROFF_TRIES); ++ ++ /* Print a warning and return, so at least userland can log the issue */ ++ printk(KERN_ERR "ERROR: unable to power off [%s:%d/%s()]!\n", ++ __FILE__, __LINE__, __func__); ++} ++ ++#define MCA_MAX_RESET_TRIES 5 ++static int mca_cc6ul_restart_handler(struct notifier_block *nb, ++ unsigned long mode, void *cmd) ++{ ++ int ret; ++ int try = 0; ++ struct mca_drv *mca = container_of(nb, struct mca_drv, ++ restart_handler); ++ const uint8_t unlock_pattern[] = {'C', 'T', 'R', 'U'}; ++ ++ do { ++ ret = regmap_bulk_write(mca->regmap, MCA_CTRL_UNLOCK_0, ++ unlock_pattern, sizeof(unlock_pattern)); ++ if (ret) { ++ dev_err(mca->dev, "failed to unlock ctrl regs (%d)\n", ++ ret); ++ goto reset_retry; ++ } ++ ++ ret = regmap_write(pmca->regmap, MCA_CTRL_0, ++ MCA_RESET); ++ if (ret) ++ dev_err(mca->dev, "failed to reset (%d)\n", ret); ++ ++ /* ++ * The MCA will reset the cpu, so the retry should not happen... ++ * and if it happens, something went wrong, and retrying is the ++ * right thing to do. ++ */ ++reset_retry: ++ mdelay(10); ++ } while (++try < MCA_MAX_RESET_TRIES); ++ ++ dev_err(mca->dev, "failed to reboot!\n"); ++ ++ return NOTIFY_DONE; ++} ++ ++static int mca_cc6ul_add_dyn_sysfs_entries(struct mca_drv *mca, ++ const struct dyn_attribute *dattr, ++ int num_entries, ++ const struct attribute_group *grp) ++{ ++ int ret, i; ++ ++ if (!mca || !dattr || !grp) ++ return -EINVAL; ++ ++ for (i = 0; i < num_entries; i++, dattr++) { ++ if (!dattr->attr) ++ continue; ++ ++ /* Create the sysfs files if the MCA fw supports the feature*/ ++ if (mca->fw_version >= dattr->since) { ++ ret = sysfs_add_file_to_group(&mca->dev->kobj, ++ dattr->attr, ++ grp->name); ++ if (ret) ++ dev_warn(mca->dev, ++ "Cannot create sysfs file %s (%d)\n", ++ dattr->attr->name, ret); ++ } ++ } ++ ++ return 0; ++} ++ ++int mca_cc6ul_device_init(struct mca_drv *mca, u32 irq) ++{ ++ int ret; ++ unsigned int val; ++ ++ ret = regmap_read(mca->regmap, MCA_DEVICE_ID, &val); ++ if (ret != 0) { ++ dev_err(mca->dev, "Cannot read MCA Device ID (%d)\n", ret); ++ return ret; ++ } ++ mca->dev_id = (u8)val; ++ ++ if (mca->dev_id != MCA_CC6UL_DEVICE_ID_VAL) { ++ dev_err(mca->dev, "Invalid MCA Device ID (%x)\n", mca->dev_id); ++ return -ENODEV; ++ } ++ ++ ret = regmap_read(mca->regmap, MCA_HW_VER, &val); ++ if (ret != 0) { ++ dev_err(mca->dev, "Cannot read MCA Hardware Version (%d)\n", ++ ret); ++ return ret; ++ } ++ mca->hw_version = (u8)val; ++ ++ ret = regmap_bulk_read(mca->regmap, MCA_FW_VER_L, &val, 2); ++ if (ret != 0) { ++ dev_err(mca->dev, "Cannot read MCA Firmware Version (%d)\n", ++ ret); ++ return ret; ++ } ++ mca->fw_version = (u16)(val & ~MCA_FW_VER_ALPHA_MASK); ++ mca->fw_is_alpha = val & MCA_FW_VER_ALPHA_MASK ? true : false; ++ ++ if (mca->fw_version >= MCA_MAKE_FW_VER(1, 2)) { ++ ret = regmap_bulk_read(mca->regmap, MCA_LAST_MCA_RESET_0, ++ &mca->last_mca_reset, ++ sizeof(mca->last_mca_reset)); ++ if (ret) { ++ dev_err(mca->dev, ++ "Cannot read MCA last reset (%d)\n", ret); ++ return ret; ++ } ++ ++ ret = regmap_bulk_read(mca->regmap, MCA_LAST_MPU_RESET_0, ++ &mca->last_mpu_reset, ++ sizeof(mca->last_mpu_reset)); ++ if (ret) { ++ dev_err(mca->dev, ++ "Cannot read MPU last reset (%d)\n", ret); ++ return ret; ++ } ++ } ++ ++ /* Write the SOM hardware version to MCA register */ ++ mca->som_hv = digi_get_som_hv(); ++ if (mca->som_hv > 0) { ++ ret = regmap_write(mca->regmap, MCA_HWVER_SOM, ++ mca->som_hv); ++ if (ret != 0) ++ dev_warn(mca->dev, ++ "Cannot set SOM hardware version (%d)\n", ret); ++ } ++ ++ mca->fw_update_gpio = of_get_named_gpio(mca->dev->of_node, ++ "fw-update-gpio", 0); ++ if (gpio_is_valid(mca->fw_update_gpio) && mca->som_hv >= 4) { ++ /* ++ * On the CC6UL HV >= 4 this GPIO must be driven low ++ * so that the CPU resets together with the reset button. ++ */ ++ if (devm_gpio_request_one(mca->dev, mca->fw_update_gpio, ++ GPIOF_OUT_INIT_LOW, "mca-fw-update")) ++ dev_warn(mca->dev, "failed to get fw-update-gpio: %d\n", ++ ret); ++ } else { ++ /* Invalidate GPIO */ ++ mca->fw_update_gpio = -EINVAL; ++ } ++ ++ mca->chip_irq = irq; ++ mca->gpio_base = -1; ++ ++ ret = mca_cc6ul_irq_init(mca); ++ if (ret != 0) { ++ dev_err(mca->dev, "Cannot initialize interrupts (%d)\n", ret); ++ return ret; ++ } ++ ++ ret = mfd_add_devices(mca->dev, -1, mca_cc6ul_devs, ++ ARRAY_SIZE(mca_cc6ul_devs), NULL, mca->irq_base, ++ regmap_irq_get_domain(mca->regmap_irq)); ++ if (ret) { ++ dev_err(mca->dev, "Cannot add MFD cells (%d)\n", ret); ++ goto out_irq; ++ } ++ ++ ret = sysfs_create_group(&mca->dev->kobj, &mca_cc6ul_attr_group); ++ if (ret) { ++ dev_err(mca->dev, "Cannot create sysfs entries (%d)\n", ret); ++ goto out_dev; ++ } ++ if (mca->fw_update_gpio == -EINVAL) { ++ /* Remove fw_update entry */ ++ sysfs_remove_file(&mca->dev->kobj, &dev_attr_fw_update.attr); ++ } ++ ++ ret = mca_cc6ul_add_dyn_sysfs_entries(mca, mca_cc6ul_sysfs_dyn_entries, ++ ARRAY_SIZE(mca_cc6ul_sysfs_dyn_entries), ++ &mca_cc6ul_attr_group); ++ if (ret) { ++ dev_err(mca->dev, "Cannot create sysfs dynamic entries (%d)\n", ++ ret); ++ goto out_sysfs_remove; ++ } ++ ++ pmca = mca; ++ ++ if (pm_power_off != NULL) { ++ dev_warn(mca->dev, "pm_power_off function already registered. " ++ "Will be override by MCA function.\n"); ++ } ++ pm_power_off = mca_cc6ul_power_off; ++ ++ /* ++ * To avoid error messages when resuming from suspend, increase the I2C ++ * bus' usage counter so the linux pm_runtime framework wakes it from ++ * suspend before trying to read the MCA's IRQ status. This indicates that ++ * the bus is in use when the system is going to suspend, making linux wake ++ * it up as soon as possible so any operations that were halted continue ++ * without issues after resuming. ++ * ++ * The device hierarchy is the following: ++ * ++ * mca_cc8x 0-0063 -> i2c i2c-0 -> imx-lpi2c 5a800000.i2c ++ */ ++ pm_runtime_get_noresume(mca->dev->parent->parent); ++ ++ /* ++ * Register the MCA restart handler with high priority to ensure it is ++ * called first ++ */ ++ mca->restart_handler.notifier_call = mca_cc6ul_restart_handler; ++ mca->restart_handler.priority = 200; ++ ret = register_restart_handler(&mca->restart_handler); ++ if (ret) { ++ dev_err(mca->dev, ++ "failed to register restart handler (%d)\n", ret); ++ goto out_pwr_off; ++ } ++ ++ if (mca->fw_version >= MCA_MAKE_FW_VER(1, 2)) { ++ mca->nvram = devm_kzalloc(mca->dev, sizeof(struct bin_attribute), ++ GFP_KERNEL); ++ if (!mca->nvram) { ++ dev_err(mca->dev, "Cannot allocate memory for nvram\n"); ++ goto out_pwr_off; ++ } ++ ++ sysfs_bin_attr_init(mca->nvram); ++ ++ mca->nvram->attr.name = "nvram"; ++ mca->nvram->attr.mode = S_IRUGO | S_IWUSR; ++ ++ mca->nvram->read = nvram_read; ++ mca->nvram->write = nvram_write; ++ ++ ret = sysfs_create_bin_file(&mca->dev->kobj, mca->nvram); ++ if (ret) { ++ dev_err(mca->dev, "Cannot create sysfs file: %s\n", ++ mca->nvram->attr.name); ++ goto out_nvram; ++ } ++ } ++ ++ return 0; ++ ++out_nvram: ++ kfree(mca->nvram); ++out_pwr_off: ++ pm_power_off = NULL; ++out_sysfs_remove: ++ pmca = NULL; ++ sysfs_remove_group(&mca->dev->kobj, &mca_cc6ul_attr_group); ++out_dev: ++ mfd_remove_devices(mca->dev); ++out_irq: ++ mca_cc6ul_irq_exit(mca); ++ ++ return ret; ++} ++ ++void mca_cc6ul_device_exit(struct mca_drv *mca) ++{ ++ unregister_restart_handler(&mca->restart_handler); ++ pm_power_off = NULL; ++ pmca = NULL; ++ sysfs_remove_group(&mca->dev->kobj, &mca_cc6ul_attr_group); ++ mfd_remove_devices(mca->dev); ++ mca_cc6ul_irq_exit(mca); ++ kfree(mca->nvram); ++} ++ ++MODULE_AUTHOR("Digi International Inc"); ++MODULE_DESCRIPTION("MCA driver for ConnectCore 6UL"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/mfd/mca-cc6ul-i2c.c b/drivers/mfd/mca-cc6ul-i2c.c +new file mode 100644 +index 000000000000..59ba56a97d9d +--- /dev/null ++++ b/drivers/mfd/mca-cc6ul-i2c.c +@@ -0,0 +1,219 @@ ++/* ++ * Copyright 2016 - 2019 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++static const struct regmap_range mca_cc6ul_readable_ranges[] = { ++}; ++ ++static const struct regmap_range mca_cc6ul_writeable_ranges[] = { ++ regmap_reg_range(MCA_HWVER_SOM, MCA_HWVER_SOM), ++ regmap_reg_range(MCA_IRQ_STATUS_0, MCA_IRQ_MASK_3), ++ regmap_reg_range(MCA_PWR_CTRL_0, MCA_PWR_KEY_GUARD), ++ regmap_reg_range(MCA_CTRL_UNLOCK_0, MCA_CTRL_UNLOCK_3), ++ regmap_reg_range(MCA_CTRL_0, MCA_CTRL_0), ++ regmap_reg_range(MCA_TAMPER0_CFG0, MCA_TAMPER0_EVENT), ++ regmap_reg_range(MCA_TAMPER1_CFG0, MCA_TAMPER1_EVENT), ++ regmap_reg_range(MCA_TAMPER2_CFG0, MCA_TAMPER2_THRESH_HI_H), ++ regmap_reg_range(MCA_TAMPER3_CFG0, MCA_TAMPER3_THRESH_HI_H), ++ regmap_reg_range(MCA_RTC_CONTROL, MCA_RTC_CONTROL), ++ regmap_reg_range(MCA_RTC_COUNT_YEAR_L, MCA_RTC_ALARM_SEC), ++ regmap_reg_range(MCA_WDT_CONTROL, MCA_WDT_REFRESH_3), ++ regmap_reg_range(MCA_GPIO_DIR_0, MCA_GPIO_DEB_CNT_63), ++ regmap_reg_range(MCA_REG_ADC_CFG0_0, MCA_REG_ADC_CFG0_7), ++ regmap_reg_range(MCA_REG_ADC_CFG1_0, MCA_REG_ADC_CFG1_7), ++ regmap_reg_range(MCA_REG_ADC_CFG2_0, MCA_REG_ADC_CFG2_7), ++ regmap_reg_range(MCA_REG_ADC_SAMPLES_CNT_0, MCA_REG_ADC_SAMPLES_CNT_7), ++ regmap_reg_range(MCA_REG_ADC_THRESH_LO_L_0, MCA_REG_ADC_THRESH_LO_H_7), ++ regmap_reg_range(MCA_REG_ADC_THRESH_HI_L_0, MCA_REG_ADC_THRESH_HI_H_7), ++ regmap_reg_range(MCA_REG_ADC_TICKS_L_0, MCA_REG_ADC_TICKS_H_7), ++ regmap_reg_range(MCA_REG_ADC_IRQ_0, MCA_REG_ADC_IRQ_7), ++ regmap_reg_range(MCA_REG_ADC_CFG_0, MCA_REG_ADC_CFG_2), ++ regmap_reg_range(MCA_REG_ADC_BUFF_CH, MCA_REG_ADC_BUFF_SAMPLE_7), ++ regmap_reg_range(MCA_REG_UART_THR, MCA_REG_UART_RTSPIN), ++ regmap_reg_range(MCA_CC6UL_MPU_NVRAM_START, MCA_CC6UL_MPU_NVRAM_END), ++}; ++ ++static const struct regmap_range mca_cc6ul_volatile_ranges[] = { ++ /* Real volatile registers */ ++ regmap_reg_range(MCA_IRQ_STATUS_0, MCA_IRQ_STATUS_3), ++ regmap_reg_range(MCA_TAMPER0_DATE_START, MCA_TAMPER0_EVENT), ++ regmap_reg_range(MCA_TAMPER1_DATE_START, MCA_TAMPER1_EVENT), ++ regmap_reg_range(MCA_TAMPER2_DATE_START, MCA_TAMPER2_EVENT), ++ regmap_reg_range(MCA_TAMPER3_DATE_START, MCA_TAMPER3_EVENT), ++ regmap_reg_range(MCA_TIMER_TICK_0, MCA_TIMER_TICK_3), ++ regmap_reg_range(MCA_LAST_MCA_RESET_0, MCA_LAST_MCA_RESET_3), ++ regmap_reg_range(MCA_LAST_MPU_RESET_0, MCA_LAST_MPU_RESET_3), ++ regmap_reg_range(MCA_LAST_WAKEUP_REASON_0, MCA_LAST_WAKEUP_REASON_3), ++ regmap_reg_range(MCA_CC6UL_MPU_NVRAM_START, MCA_CC6UL_MPU_NVRAM_END), ++ regmap_reg_range(MCA_RTC_COUNT_YEAR_L, MCA_RTC_COUNT_SEC), ++ regmap_reg_range(MCA_GPIO_DATA_0, MCA_GPIO_DATA_7), ++ regmap_reg_range(MCA_GPIO_IRQ_STATUS_0, MCA_GPIO_IRQ_STATUS_7), ++ regmap_reg_range(MCA_PWR_CTRL_0, MCA_PWR_STATUS_0), ++ regmap_reg_range(MCA_REG_ADC_VAL_L_0, MCA_REG_ADC_VAL_H_7), ++ ++ /* ++ * Fake volatile registers. ++ * ++ * These registers could be cached but non-volatile registers makes ++ * regmap access each register one by one which has some drawbacks: ++ * - Breaks CRC in the protocol. ++ * - Requires the MCA firmware to process each access as a separate ++ * access, even when the data requested must be returned in bulk. ++ * ++ * For this reasons we will consider all registers volatile. ++ */ ++ regmap_reg_range(MCA_HWVER_SOM, MCA_HWVER_SOM), ++ regmap_reg_range(MCA_DEVICE_ID, MCA_UID_9), ++ regmap_reg_range(MCA_IRQ_MASK_0, MCA_IRQ_MASK_3), ++ regmap_reg_range(MCA_PWR_KEY_DEBOUNCE, MCA_PWR_KEY_GUARD), ++ regmap_reg_range(MCA_CTRL_0, MCA_CTRL_0), ++ regmap_reg_range(MCA_TAMPER0_CFG0, MCA_TAMPER0_DELAY_PWROFF), ++ regmap_reg_range(MCA_TAMPER1_CFG0, MCA_TAMPER1_DELAY_PWROFF), ++ regmap_reg_range(MCA_TAMPER2_CFG0, MCA_TAMPER2_THRESH_HI_H), ++ regmap_reg_range(MCA_TAMPER3_CFG0, MCA_TAMPER3_THRESH_HI_H), ++ regmap_reg_range(MCA_RTC_CONTROL, MCA_RTC_CONTROL), ++ regmap_reg_range(MCA_RTC_ALARM_YEAR_L, MCA_RTC_ALARM_SEC), ++ regmap_reg_range(MCA_WDT_CONTROL, MCA_WDT_TIMEOUT), ++ regmap_reg_range(MCA_GPIO_NUM, MCA_GPIO_DIR_7), ++ regmap_reg_range(MCA_GPIO_IRQ_CFG_0, MCA_GPIO_IRQ_CFG_63), ++ regmap_reg_range(MCA_REG_ADC_NUM_CH, MCA_REG_ADC_NUM_BYTES), ++ regmap_reg_range(MCA_REG_ADC_CFG0_0, MCA_REG_ADC_CFG0_7), ++ regmap_reg_range(MCA_REG_ADC_CFG1_0, MCA_REG_ADC_CFG1_7), ++ regmap_reg_range(MCA_REG_ADC_CFG2_0, MCA_REG_ADC_CFG2_7), ++ regmap_reg_range(MCA_REG_ADC_SAMPLES_CNT_0, MCA_REG_ADC_SAMPLES_CNT_7), ++ regmap_reg_range(MCA_REG_ADC_THRESH_LO_L_0, MCA_REG_ADC_THRESH_LO_H_7), ++ regmap_reg_range(MCA_REG_ADC_THRESH_HI_L_0, MCA_REG_ADC_THRESH_HI_H_7), ++ regmap_reg_range(MCA_REG_ADC_TICKS_L_0, MCA_REG_ADC_TICKS_H_7), ++ regmap_reg_range(MCA_REG_ADC_IRQ_0, MCA_REG_ADC_IRQ_7), ++ regmap_reg_range(MCA_REG_ADC_CFG_0, MCA_REG_ADC_CFG_2), ++ regmap_reg_range(MCA_REG_ADC_BUFF_CH, MCA_REG_ADC_BUFF_SAMPLE_7), ++ regmap_reg_range(MCA_REG_UART_RHR, MCA_REG_UART_RTSPIN), ++}; ++ ++static const struct regmap_access_table mca_cc6ul_readable_table = { ++ .yes_ranges = mca_cc6ul_readable_ranges, ++ .n_yes_ranges = ARRAY_SIZE(mca_cc6ul_readable_ranges), ++}; ++ ++static const struct regmap_access_table mca_cc6ul_writeable_table = { ++ .yes_ranges = mca_cc6ul_writeable_ranges, ++ .n_yes_ranges = ARRAY_SIZE(mca_cc6ul_writeable_ranges), ++}; ++ ++static const struct regmap_access_table mca_cc6ul_volatile_table = { ++ .yes_ranges = mca_cc6ul_volatile_ranges, ++ .n_yes_ranges = ARRAY_SIZE(mca_cc6ul_volatile_ranges), ++}; ++ ++static struct regmap_config mca_cc6ul_regmap_config = { ++ .reg_bits = 16, ++ .val_bits = 8, ++ .max_register = 0xFFFF, ++ ++ .rd_table = &mca_cc6ul_readable_table, ++ .wr_table = &mca_cc6ul_writeable_table, ++ .volatile_table = &mca_cc6ul_volatile_table, ++ ++ .cache_type = REGCACHE_NONE, ++}; ++ ++static const struct of_device_id mca_cc6ul_dt_ids[] = { ++ { .compatible = "digi,mca_cc6ul_dt_ids", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, mca_cc6ul_dt_ids); ++ ++static int mca_cc6ul_i2c_probe(struct i2c_client *i2c, ++ const struct i2c_device_id *id) ++{ ++ struct mca_drv *mca; ++ int ret; ++ ++ mca = devm_kzalloc(&i2c->dev, sizeof(struct mca_drv), GFP_KERNEL); ++ if (mca == NULL) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(i2c, mca); ++ mca->dev = &i2c->dev; ++ mca->chip_irq = i2c->irq; ++ ++ mca->regmap = devm_regmap_init_i2c(i2c, &mca_cc6ul_regmap_config); ++ if (IS_ERR(mca->regmap)) { ++ ret = PTR_ERR(mca->regmap); ++ dev_err(mca->dev, "Failed to allocate register map: %d\n", ret); ++ return ret; ++ } ++ ++ return mca_cc6ul_device_init(mca, i2c->irq); ++} ++ ++static int mca_cc6ul_i2c_remove(struct i2c_client *i2c) ++{ ++ struct mca_drv *mca = i2c_get_clientdata(i2c); ++ ++ mca_cc6ul_device_exit(mca); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM ++static int mca_cc6ul_i2c_suspend(struct device *dev) ++{ ++ return mca_cc6ul_suspend(dev); ++} ++ ++static int mca_cc6ul_i2c_resume(struct device *dev) ++{ ++ return mca_cc6ul_resume(dev); ++} ++ ++/* ++ * Use suspend_late/resume_early so the mca_cc6ul continues being functional ++ * during the regular suspend/resume callbacks of other drivers, just in case ++ * they use any functionality of the mca. ++ */ ++static const struct dev_pm_ops mca_cc6ul_i2c_pm_ops = { ++ SET_LATE_SYSTEM_SLEEP_PM_OPS(mca_cc6ul_i2c_suspend, mca_cc6ul_i2c_resume) ++}; ++#endif ++ ++static const struct i2c_device_id mca_cc6ul_i2c_id[] = { ++ {"mca_cc6ul", 0}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, mca_cc6ul_i2c_id); ++ ++static struct i2c_driver mca_cc6ul_i2c_driver = { ++ .driver = { ++ .name = "mca_cc6ul", ++ .of_match_table = of_match_ptr(mca_cc6ul_dt_ids), ++#ifdef CONFIG_PM ++ .pm = &mca_cc6ul_i2c_pm_ops, ++#endif ++ }, ++ .probe = mca_cc6ul_i2c_probe, ++ .remove = mca_cc6ul_i2c_remove, ++ .id_table = mca_cc6ul_i2c_id, ++}; ++ ++module_i2c_driver(mca_cc6ul_i2c_driver); +diff --git a/drivers/mfd/mca-cc6ul-irq.c b/drivers/mfd/mca-cc6ul-irq.c +new file mode 100644 +index 000000000000..0807e974cb60 +--- /dev/null ++++ b/drivers/mfd/mca-cc6ul-irq.c +@@ -0,0 +1,113 @@ ++/* ++ * Copyright 2016 - 2019 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MCA_IRQ_0_OFFSET 0 ++#define MCA_IRQ_1_OFFSET 1 ++#define MCA_IRQ_2_OFFSET 2 ++#define MCA_IRQ_3_OFFSET 3 ++ ++static const struct regmap_irq mca_cc6ul_irqs[] = { ++ /* MCA irqs A register */ ++ [MCA_CC6UL_IRQ_RTC_ALARM] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_RTC_ALARM, ++ }, ++ [MCA_CC6UL_IRQ_RTC_1HZ] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_RTC_1HZ, ++ }, ++ [MCA_CC6UL_IRQ_WATCHDOG] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_WATCHDOG, ++ }, ++ [MCA_CC6UL_IRQ_PWR_SLEEP] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_PWR_SLEEP, ++ }, ++ [MCA_CC6UL_IRQ_PWR_OFF] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_PWR_OFF, ++ }, ++ [MCA_CC6UL_IRQ_TAMPER0] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_TAMPER0, ++ }, ++ [MCA_CC6UL_IRQ_TAMPER1] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_TAMPER1, ++ }, ++ [MCA_CC6UL_IRQ_ADC] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_ADC, ++ }, ++ [MCA_CC6UL_IRQ_GPIO_BANK_0] = { ++ .reg_offset = MCA_IRQ_1_OFFSET, ++ .mask = MCA_GPIO_BANK_0, ++ }, ++ [MCA_CC6UL_IRQ_TAMPER2] = { ++ .reg_offset = MCA_IRQ_2_OFFSET, ++ .mask = MCA_M_TAMPER2, ++ }, ++ [MCA_CC6UL_IRQ_TAMPER3] = { ++ .reg_offset = MCA_IRQ_2_OFFSET, ++ .mask = MCA_M_TAMPER3, ++ }, ++ [MCA_CC6UL_IRQ_UART] = { ++ .reg_offset = MCA_IRQ_2_OFFSET, ++ .mask = MCA_M_UART, ++ }, ++}; ++ ++static const struct regmap_irq_chip mca_cc6ul_irq_chip = { ++ .name = "mca-cc6ul-irq", ++ .irqs = mca_cc6ul_irqs, ++ .num_irqs = ARRAY_SIZE(mca_cc6ul_irqs), ++ .num_regs = MCA_NUM_IRQ_REGS, ++ .status_base = MCA_IRQ_STATUS_0, ++ .mask_base = MCA_IRQ_MASK_0, ++ .ack_base = MCA_IRQ_STATUS_0, ++ .init_ack_masked = true, ++}; ++ ++int mca_cc6ul_irq_init(struct mca_drv *mca) ++{ ++ int ret; ++ ++ if (!mca->chip_irq) { ++ dev_err(mca->dev, "No IRQ configured\n"); ++ return -EINVAL; ++ } ++ ++ mca->irq_base = -1; ++ ret = regmap_add_irq_chip(mca->regmap, mca->chip_irq, ++ IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED, ++ mca->irq_base, &mca_cc6ul_irq_chip, ++ &mca->regmap_irq); ++ if (ret) { ++ dev_err(mca->dev, "Failed to reguest IRQ %d: %d\n", ++ mca->chip_irq, ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++void mca_cc6ul_irq_exit(struct mca_drv *mca) ++{ ++ regmap_del_irq_chip(mca->chip_irq, mca->regmap_irq); ++} +diff --git a/drivers/mfd/mca-cc8x-core.c b/drivers/mfd/mca-cc8x-core.c +new file mode 100644 +index 000000000000..19931618b683 +--- /dev/null ++++ b/drivers/mfd/mca-cc8x-core.c +@@ -0,0 +1,1040 @@ ++/* ++ * Copyright 2018 - 2019 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++ ++#define MCA_CC8X_NVRAM_SIZE (MCA_CC8X_MPU_NVRAM_END - MCA_CC8X_MPU_NVRAM_START + 1) ++ ++struct dyn_attribute { ++ u16 since; /* Minimum firmware version required */ ++ struct attribute *attr; ++}; ++ ++struct mca_reason { ++ u32 flag; ++ const char *text; ++}; ++ ++enum lpi2c_imx_mode { ++ STANDARD, /* 100+Kbps */ ++ FAST, /* 400+Kbps */ ++ FAST_PLUS, /* 1.0+Mbps */ ++ HS, /* 3.4+Mbps */ ++ ULTRA_FAST, /* 5.0+Mbps */ ++}; ++ ++struct lpi2c_imx_struct { ++ struct i2c_adapter adapter; ++ int irq; ++ struct clk *clk_per; ++ struct clk *clk_ipg; ++ void __iomem *base; ++ __u8 *rx_buf; ++ __u8 *tx_buf; ++ struct completion complete; ++ unsigned int msglen; ++ unsigned int delivered; ++ unsigned int block_data; ++ unsigned int bitrate; ++ enum lpi2c_imx_mode mode; ++ unsigned int hold_time; ++}; ++ ++static const struct mca_reason last_mca_reset[] = { ++ {MCA_CC8X_LAST_MCA_RST_LLW, "LL Wakeup"}, ++ {MCA_CC8X_LAST_MCA_RST_LVD, "Low Voltage"}, ++ {MCA_CC8X_LAST_MCA_RST_WD, "Watchdog"}, ++ {MCA_CC8X_LAST_MCA_RST_PIN, "Reset Pin"}, ++ {MCA_CC8X_LAST_MCA_RST_PWRON, "Power On"}, ++ {MCA_CC8X_LAST_MCA_RST_LOCKUP, "Core Lockup"}, ++ {MCA_CC8X_LAST_MCA_RST_SW, "Software"}, ++ {MCA_CC8X_LAST_MCA_RST_MDMAPP, "MDM-APP debuger"}, ++ {MCA_CC8X_LAST_MCA_RST_SMAE, "Stop Mode Ack Error"}, ++}; ++ ++static const struct mca_reason last_mpu_reset[] = { ++ {MCA_CC8X_LAST_MPU_RST_PWRON, "Power On"}, ++ {MCA_CC8X_LAST_MPU_RST_SYSR, "System Reset"}, ++ {MCA_CC8X_LAST_MPU_RST_WD, "Watchdog"}, ++ {MCA_CC8X_LAST_MPU_RST_OFFWAKE,"Off wakeup"}, ++ {MCA_CC8X_LAST_MPU_RST_MCARST, "MCA reset"}, ++}; ++ ++static const struct mca_reason last_wakeup[] = { ++ {MCA_CC8X_LAST_WAKEUP_PWRIO, "Power IO"}, ++ {MCA_CC8X_LAST_WAKEUP_TIMER, "Timer"}, ++ {MCA_CC8X_LAST_WAKEUP_RTC, "RTC"}, ++ {MCA_CC8X_LAST_WAKEUP_LPUART, "LP UART"}, ++ {MCA_CC8X_LAST_WAKEUP_TAMPER0, "Tamper0"}, ++ {MCA_CC8X_LAST_WAKEUP_TAMPER1, "Tamper1"}, ++ {MCA_CC8X_LAST_WAKEUP_TAMPER2, "Tamper2"}, ++ {MCA_CC8X_LAST_WAKEUP_TAMPER3, "Tamper3"}, ++ {MCA_CC8X_LAST_WAKEUP_IO0, "IO0"}, ++ {MCA_CC8X_LAST_WAKEUP_IO1, "IO1"}, ++ {MCA_CC8X_LAST_WAKEUP_IO2, "IO2"}, ++ {MCA_CC8X_LAST_WAKEUP_IO3, "IO3"}, ++ {MCA_CC8X_LAST_WAKEUP_IO4, "IO4"}, ++ {MCA_CC8X_LAST_WAKEUP_IO5, "IO5"}, ++ {MCA_CC8X_LAST_WAKEUP_IO6, "IO6"}, ++ {MCA_CC8X_LAST_WAKEUP_IO7, "IO7"}, ++ {MCA_CC8X_LAST_WAKEUP_IO8, "IO8"}, ++ {MCA_CC8X_LAST_WAKEUP_IO9, "IO9"}, ++ {MCA_CC8X_LAST_WAKEUP_IO10, "IO10"}, ++ {MCA_CC8X_LAST_WAKEUP_IO11, "IO11"}, ++ {MCA_CC8X_LAST_WAKEUP_IO12, "IO12"}, ++ {MCA_CC8X_LAST_WAKEUP_IO13, "IO13"}, ++ {MCA_CC8X_LAST_WAKEUP_IO14, "IO14"}, ++ {MCA_CC8X_LAST_WAKEUP_IO15, "IO15"}, ++ {MCA_CC8X_LAST_WAKEUP_IO16, "IO16"}, ++ {MCA_CC8X_LAST_WAKEUP_IO17, "IO17"}, ++ {MCA_CC8X_LAST_WAKEUP_IO18, "IO18"}, ++ {MCA_CC8X_LAST_WAKEUP_IO19, "IO19"}, ++ {MCA_CC8X_LAST_WAKEUP_IO20, "IO20"}, ++ {MCA_CC8X_LAST_WAKEUP_IO21, "IO21"}, ++ {MCA_CC8X_LAST_WAKEUP_VCC, "Vcc"}, ++ {MCA_CC8X_LAST_WAKEUP_CPU, "CPU"}, ++}; ++ ++static struct mca_drv *pmca; ++ ++static const char _enabled[] = "enabled"; ++static const char _disabled[] = "disabled"; ++ ++static struct resource mca_cc8x_rtc_resources[] = { ++ { ++ .name = MCA_IRQ_RTC_ALARM_NAME, ++ .start = MCA_CC8X_IRQ_RTC_ALARM, ++ .end = MCA_CC8X_IRQ_RTC_ALARM, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_RTC_1HZ_NAME, ++ .start = MCA_CC8X_IRQ_RTC_1HZ, ++ .end = MCA_CC8X_IRQ_RTC_1HZ, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc8x_watchdog_resources[] = { ++ { ++ .name = MCA_IRQ_WATCHDOG_NAME, ++ .start = MCA_CC8X_IRQ_WATCHDOG, ++ .end = MCA_CC8X_IRQ_WATCHDOG, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc8x_pwrkey_resources[] = { ++ { ++ .name = MCA_IRQ_PWR_SLEEP_NAME, ++ .start = MCA_CC8X_IRQ_PWR_SLEEP, ++ .end = MCA_CC8X_IRQ_PWR_SLEEP, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_PWR_OFF_NAME, ++ .start = MCA_CC8X_IRQ_PWR_OFF, ++ .end = MCA_CC8X_IRQ_PWR_OFF, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc8x_adc_resources[] = { ++ { ++ .name = MCA_IRQ_ADC_NAME, ++ .start = MCA_CC8X_IRQ_ADC, ++ .end = MCA_CC8X_IRQ_ADC, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc8x_tamper_resources[] = { ++ { ++ .name = MCA_IRQ_TAMPER0_NAME, ++ .start = MCA_CC8X_IRQ_TAMPER0, ++ .end = MCA_CC8X_IRQ_TAMPER0, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_TAMPER1_NAME, ++ .start = MCA_CC8X_IRQ_TAMPER1, ++ .end = MCA_CC8X_IRQ_TAMPER1, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_TAMPER2_NAME, ++ .start = MCA_CC8X_IRQ_TAMPER2, ++ .end = MCA_CC8X_IRQ_TAMPER2, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_TAMPER3_NAME, ++ .start = MCA_CC8X_IRQ_TAMPER3, ++ .end = MCA_CC8X_IRQ_TAMPER3, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc8x_gpios_resources[] = { ++ { ++ .name = MCA_IRQ_GPIO_BANK_0_NAME, ++ .start = MCA_CC8X_IRQ_GPIO_BANK_0, ++ .end = MCA_CC8X_IRQ_GPIO_BANK_0, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_GPIO_BANK_1_NAME, ++ .start = MCA_CC8X_IRQ_GPIO_BANK_1, ++ .end = MCA_CC8X_IRQ_GPIO_BANK_1, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_GPIO_BANK_2_NAME, ++ .start = MCA_CC8X_IRQ_GPIO_BANK_2, ++ .end = MCA_CC8X_IRQ_GPIO_BANK_2, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static struct resource mca_cc8x_uart_resources[] = { ++ { ++ .name = MCA_IRQ_UART_NAME, ++ .start = MCA_CC8X_IRQ_UART, ++ .end = MCA_CC8X_IRQ_UART, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static const struct mfd_cell mca_cc8x_devs[] = { ++ { ++ .name = MCA_CC8X_DRVNAME_RTC, ++ .num_resources = ARRAY_SIZE(mca_cc8x_rtc_resources), ++ .resources = mca_cc8x_rtc_resources, ++ .of_compatible = "digi,mca-cc8x-rtc", ++ }, ++ { ++ .name = MCA_CC8X_DRVNAME_WATCHDOG, ++ .num_resources = ARRAY_SIZE(mca_cc8x_watchdog_resources), ++ .resources = mca_cc8x_watchdog_resources, ++ .of_compatible = "digi,mca-cc8x-watchdog", ++ }, ++ { ++ .name = MCA_CC8X_DRVNAME_GPIO, ++ .num_resources = ARRAY_SIZE(mca_cc8x_gpios_resources), ++ .resources = mca_cc8x_gpios_resources, ++ .of_compatible = "digi,mca-cc8x-gpio", ++ }, ++ { ++ .name = MCA_CC8X_DRVNAME_PWRKEY, ++ .num_resources = ARRAY_SIZE(mca_cc8x_pwrkey_resources), ++ .resources = mca_cc8x_pwrkey_resources, ++ .of_compatible = "digi,mca-cc8x-pwrkey", ++ }, ++ { ++ .name = MCA_CC8X_DRVNAME_ADC, ++ .of_compatible = "digi,mca-cc8x-adc", ++ .num_resources = ARRAY_SIZE(mca_cc8x_adc_resources), ++ .resources = mca_cc8x_adc_resources, ++ }, ++ { ++ .name = MCA_CC8X_DRVNAME_TAMPER, ++ .num_resources = ARRAY_SIZE(mca_cc8x_tamper_resources), ++ .resources = mca_cc8x_tamper_resources, ++ .of_compatible = "digi,mca-cc8x-tamper", ++ }, ++ { ++ .name = MCA_CC8X_DRVNAME_UART, ++ .num_resources = ARRAY_SIZE(mca_cc8x_uart_resources), ++ .resources = mca_cc8x_uart_resources, ++ .of_compatible = "digi,mca-cc8x-uart", ++ }, ++ { ++ .name = MCA_CC8X_DRVNAME_PWM, ++ .of_compatible = "digi,mca-pwm", ++ }, ++}; ++ ++/* Read a block of registers */ ++int mca_cc8x_read_block(struct mca_drv *mca, u16 addr, u8 *data, ++ size_t nregs) ++{ ++ int ret; ++ ++ /* TODO, check limits nregs... */ ++ ++ ret = regmap_raw_read(mca->regmap, addr, data, nregs); ++ if (ret != 0) ++ return ret; ++ ++ return ret; ++ ++} ++EXPORT_SYMBOL_GPL(mca_cc8x_read_block); ++ ++/* Write a block of data into MCA registers */ ++int mca_cc8x_write_block(struct mca_drv *mca , u16 addr, u8 *data, ++ size_t nregs) ++{ ++ u8 *frame; /* register address + payload */ ++ u8 *payload; ++ int ret; ++ ++ /* TODO, check limits nregs... */ ++ ++ frame = kzalloc(sizeof(addr) + nregs, GFP_KERNEL | GFP_DMA); ++ if (!frame) ++ return -ENOMEM; ++ ++ payload = frame + sizeof(addr); ++ memcpy(payload, data, nregs); ++ ++ /* Write payload */ ++ ret = regmap_raw_write(mca->regmap, addr, payload, nregs); ++ ++ kfree(frame); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(mca_cc8x_write_block); ++ ++static int mca_cc8x_unlock_ctrl(struct mca_drv *mca) ++{ ++ int ret; ++ const uint8_t unlock_pattern[] = {'C', 'T', 'R', 'U'}; ++ ++ ret = regmap_bulk_write(mca->regmap, MCA_CTRL_UNLOCK_0, ++ unlock_pattern, sizeof(unlock_pattern)); ++ if (ret) ++ dev_warn(mca->dev, "failed to unlock CTRL registers (%d)\n", ++ ret); ++ ++ return ret; ++} ++ ++static int mca_cc8x_get_tick_cnt(struct mca_drv *mca, u32 *tick) ++{ ++ return regmap_bulk_read(mca->regmap, MCA_TIMER_TICK_0, ++ tick, sizeof(*tick)); ++} ++ ++/* sysfs attributes */ ++static ssize_t ext_32khz_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ unsigned int val; ++ int ret; ++ ++ ret = regmap_read(mca->regmap, MCA_CTRL_0, &val); ++ if (ret) { ++ dev_err(mca->dev, "Cannot read MCA CTRL_0 register(%d)\n", ++ ret); ++ return 0; ++ } ++ ++ return sprintf(buf, "%s\n", val & MCA_EXT32K_EN ? ++ _enabled : _disabled); ++} ++ ++static ssize_t ext_32khz_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ bool enable; ++ int ret; ++ ++ if (!strncmp(buf, _enabled, sizeof(_enabled) - 1)) ++ enable = true; ++ else if (!strncmp(buf, _disabled, sizeof(_disabled) - 1)) ++ enable = false; ++ else ++ return -EINVAL; ++ ++ ret = mca_cc8x_unlock_ctrl(mca); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(mca->regmap, MCA_CTRL_0, ++ MCA_EXT32K_EN, ++ enable ? MCA_EXT32K_EN : 0); ++ if (ret) { ++ dev_err(mca->dev, "Cannot update MCA CTRL_0 register (%d)\n", ret); ++ return ret; ++ } ++ ++ return count; ++} ++static DEVICE_ATTR(ext_32khz, 0600, ext_32khz_show, ext_32khz_store); ++ ++static ssize_t vref_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ unsigned int val; ++ int ret; ++ ++ ret = regmap_read(mca->regmap, MCA_CTRL_0, &val); ++ if (ret) { ++ dev_err(mca->dev, "Cannot read MCA CTRL_0 register(%d)\n", ++ ret); ++ return 0; ++ } ++ ++ return sprintf(buf, "%s\n", val & MCA_VREF_EN ? ++ _enabled : _disabled); ++} ++ ++static ssize_t vref_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ bool enable; ++ int ret; ++ ++ if (!strncmp(buf, _enabled, sizeof(_enabled) - 1)) ++ enable = true; ++ else if (!strncmp(buf, _disabled, sizeof(_disabled) - 1)) ++ enable = false; ++ else ++ return -EINVAL; ++ ++ ret = mca_cc8x_unlock_ctrl(mca); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(mca->regmap, MCA_CTRL_0, ++ MCA_VREF_EN, ++ enable ? MCA_VREF_EN : 0); ++ if (ret) { ++ dev_err(mca->dev, "Cannot update MCA CTRL_0 register (%d)\n", ret); ++ return ret; ++ } ++ ++ return count; ++} ++static DEVICE_ATTR(vref, 0600, vref_show, vref_store); ++ ++static ssize_t hwver_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d\n", mca->hw_version); ++} ++static DEVICE_ATTR(hw_version, S_IRUGO, hwver_show, NULL); ++ ++static ssize_t fwver_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d.%02d %s\n", MCA_FW_VER_MAJOR(mca->fw_version), ++ MCA_FW_VER_MINOR(mca->fw_version), ++ mca->fw_is_alpha ? "(alpha)" : ""); ++} ++static DEVICE_ATTR(fw_version, S_IRUGO, fwver_show, NULL); ++ ++static ssize_t tick_cnt_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ u32 tick_cnt; ++ int ret; ++ ++ ret = mca_cc8x_get_tick_cnt(mca, &tick_cnt); ++ if (ret) { ++ dev_err(mca->dev, "Cannot read MCA tick counter(%d)\n", ret); ++ return ret; ++ } ++ ++ return sprintf(buf, "%u\n", tick_cnt); ++} ++static DEVICE_ATTR(tick_cnt, S_IRUGO, tick_cnt_show, NULL); ++ ++static ssize_t fw_update_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ ++ if (!gpio_is_valid(mca->fw_update_gpio)) ++ return -EINVAL; ++ ++ return sprintf(buf, "%d\n", ++ gpio_get_value_cansleep(mca->fw_update_gpio)); ++} ++ ++static ssize_t fw_update_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ ssize_t status; ++ long value; ++ struct lpi2c_imx_struct *lpi2c_imx = dev_get_drvdata(mca->i2c_adapter_dev); ++ ++ if (!gpio_is_valid(mca->fw_update_gpio)) ++ return -EINVAL; ++ ++ /* Set i2c bus speed to 100kbps during firmware update */ ++ lpi2c_imx->bitrate = 100000; ++ ++ status = kstrtol(buf, 0, &value); ++ if (status == 0) { ++ gpio_set_value_cansleep(mca->fw_update_gpio, value); ++ status = count; ++ } ++ ++ return status; ++} ++static DEVICE_ATTR(fw_update, 0600, fw_update_show, fw_update_store); ++ ++static ssize_t last_wakeup_reason_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ bool comma = false; ++ u32 last_wakeup_val; ++ int ret, i; ++ ++ ret = regmap_bulk_read(mca->regmap, MCA_LAST_WAKEUP_REASON_0, ++ &last_wakeup_val, sizeof(last_wakeup_val)); ++ if (ret) { ++ dev_err(mca->dev, ++ "Cannot read last MCA wakeup reason (%d)\n", ++ ret); ++ return ret; ++ } ++ ++ buf[0] = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(last_wakeup); i++) { ++ if (last_wakeup[i].flag & last_wakeup_val) { ++ if (comma) ++ strcat(buf, ", "); ++ strcat(buf, last_wakeup[i].text); ++ comma = true; ++ } ++ } ++ ++ if (comma) ++ strcat(buf, "\n"); ++ ++ return strlen(buf); ++} ++static DEVICE_ATTR(last_wakeup_reason, S_IRUGO, last_wakeup_reason_show, NULL); ++ ++static ssize_t last_mca_reset_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ bool comma = false; ++ int i; ++ ++ buf[0] = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(last_mca_reset); i++) { ++ if (last_mca_reset[i].flag & mca->last_mca_reset) { ++ if (comma) ++ strcat(buf, ", "); ++ strcat(buf, last_mca_reset[i].text); ++ comma = true; ++ } ++ } ++ ++ if (comma) ++ strcat(buf, "\n"); ++ ++ return strlen(buf); ++} ++static DEVICE_ATTR(last_mca_reset, S_IRUGO, last_mca_reset_show, NULL); ++ ++static ssize_t last_mpu_reset_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ bool comma = false; ++ int i; ++ ++ buf[0] = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(last_mpu_reset); i++) { ++ if (last_mpu_reset[i].flag & mca->last_mpu_reset) { ++ if (comma) ++ strcat(buf, ", "); ++ strcat(buf, last_mpu_reset[i].text); ++ comma = true; ++ } ++ } ++ ++ if (comma) ++ strcat(buf, "\n"); ++ ++ return strlen(buf); ++} ++static DEVICE_ATTR(last_mpu_reset, S_IRUGO, last_mpu_reset_show, NULL); ++ ++static ssize_t nvram_read(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, char *buf, loff_t off, ++ size_t count) ++{ ++ struct device *dev = kobj_to_dev(kobj); ++ struct mca_drv *mca; ++ int ret; ++ ++ if (!dev || (mca = dev_get_drvdata(dev)) == NULL) ++ return -ENODEV; ++ ++ if (unlikely(off >= MCA_CC8X_NVRAM_SIZE) || unlikely(!count)) ++ return 0; ++ if ((off + count) > MCA_CC8X_NVRAM_SIZE) ++ count = MCA_CC8X_NVRAM_SIZE - off; ++ ++ ret = regmap_bulk_read(mca->regmap, ++ MCA_CC8X_MPU_NVRAM_START + off, buf, count); ++ if (ret) { ++ dev_err(mca->dev, "%s error (%d)\n", __func__, ret); ++ return ret; ++ } ++ ++ return count; ++} ++ ++static ssize_t nvram_write(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, char *buf, loff_t off, ++ size_t count) ++{ ++ struct device *dev = kobj_to_dev(kobj); ++ struct mca_drv *mca; ++ int ret; ++ ++ if (!dev || (mca = dev_get_drvdata(dev)) == NULL) ++ return -ENODEV; ++ ++ if (unlikely(off >= MCA_CC8X_NVRAM_SIZE)) ++ return -EFBIG; ++ if ((off + count) > MCA_CC8X_NVRAM_SIZE) ++ count = MCA_CC8X_NVRAM_SIZE - off; ++ if (unlikely(!count)) ++ return count; ++ ++ ret = regmap_bulk_write(mca->regmap, ++ MCA_CC8X_MPU_NVRAM_START + off, buf, count); ++ if (ret) { ++ dev_err(mca->dev, "%s error (%d)\n", __func__, ret); ++ return ret; ++ } ++ ++ return count; ++} ++ ++static struct attribute *mca_cc8x_sysfs_entries[] = { ++ &dev_attr_ext_32khz.attr, ++ &dev_attr_hw_version.attr, ++ &dev_attr_fw_version.attr, ++ &dev_attr_fw_update.attr, ++ NULL, ++}; ++ ++static struct attribute_group mca_cc8x_attr_group = { ++ .name = NULL, /* put in device directory */ ++ .attrs = mca_cc8x_sysfs_entries, ++}; ++ ++static struct dyn_attribute mca_cc8x_sysfs_dyn_entries[] = { ++ { ++ .since = MCA_MAKE_FW_VER(0,0), ++ .attr = &dev_attr_tick_cnt.attr, ++ }, ++ { ++ .since = MCA_MAKE_FW_VER(0,11), ++ .attr = &dev_attr_vref.attr, ++ }, ++ { ++ .since = MCA_MAKE_FW_VER(0,4), ++ .attr = &dev_attr_last_wakeup_reason.attr, ++ }, ++ { ++ .since = MCA_MAKE_FW_VER(0,4), ++ .attr = &dev_attr_last_mca_reset.attr, ++ }, ++ { ++ .since = MCA_MAKE_FW_VER(0,4), ++ .attr = &dev_attr_last_mpu_reset.attr, ++ }, ++}; ++ ++int mca_cc8x_suspend(struct device *dev) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ ++ if (!mca) { ++ dev_err(dev, " mca was null in %s\n", __func__); ++ return -ENODEV; ++ } ++ ++ /* Set the suspend bit in PWR_CTRL_0 */ ++ return regmap_update_bits(pmca->regmap, MCA_PWR_CTRL_0, ++ MCA_PWR_GO_SUSPEND, ++ MCA_PWR_GO_SUSPEND); ++} ++ ++#define MCA_MAX_RESUME_RD_RETRIES 10 ++int mca_cc8x_resume(struct device *dev) ++{ ++ struct mca_drv *mca = dev_get_drvdata(dev); ++ unsigned int val; ++ int ret, retries = 0; ++ ++ if (!mca) { ++ dev_err(dev, " mca was null in %s\n", __func__); ++ return -ENODEV; ++ } ++ ++ /* ++ * Generate traffic on the i2c bus to wakeup the MCA, in case it was in ++ * low power ++ */ ++ do { ++ ret = regmap_read(mca->regmap, MCA_DEVICE_ID, &val); ++ if (!ret && mca->dev_id == (u8)val) ++ break; ++ udelay(50); ++ } while (++retries < MCA_MAX_RESUME_RD_RETRIES); ++ ++ if (retries == MCA_MAX_RESUME_RD_RETRIES) { ++ dev_err(mca->dev, "unable to wake up MCA (%d)\n", ret); ++ return ret; ++ } ++ ++ /* Reset the suspend bit in PWR_CTRL_0 */ ++ return regmap_update_bits(pmca->regmap, MCA_PWR_CTRL_0, ++ MCA_PWR_GO_SUSPEND, ++ 0); ++} ++ ++#define MCA_MAX_PWROFF_TRIES 5 ++static void mca_cc8x_power_off(void) ++{ ++ int try = 0; ++ int ret; ++ ++ if (!pmca) { ++ printk(KERN_ERR "ERROR: unable to power off [%s:%d/%s()]!\n", ++ __FILE__, __LINE__, __func__); ++ return; ++ } ++ ++ do { ++ /* Set power off bit in PWR_CTRL_0 register to shutdown */ ++ ret = regmap_update_bits(pmca->regmap, MCA_PWR_CTRL_0, ++ MCA_PWR_GO_OFF, ++ MCA_PWR_GO_OFF); ++ if (ret) ++ printk(KERN_ERR "ERROR: accesing PWR_CTRL_0 register " ++ "[%s:%d/%s()]!\n", __FILE__, __LINE__, __func__); ++ ++ /* ++ * Even if the regmap update returned with success, retry... ++ * we are powering off, so there is nothing bad by doing it. ++ */ ++ mdelay(50); ++ } while (++try < MCA_MAX_PWROFF_TRIES); ++ ++ /* Print a warning and return, so at least userland can log the issue */ ++ printk(KERN_ERR "ERROR: unable to power off [%s:%d/%s()]!\n", ++ __FILE__, __LINE__, __func__); ++} ++ ++#define MCA_MAX_RESET_TRIES 5 ++static int mca_cc8x_restart_handler(struct notifier_block *nb, ++ unsigned long mode, void *cmd) ++{ ++ int ret; ++ int try = 0; ++ struct mca_drv *mca = container_of(nb, struct mca_drv, ++ restart_handler); ++ const uint8_t unlock_pattern[] = {'C', 'T', 'R', 'U'}; ++ ++ do { ++ ret = regmap_bulk_write(mca->regmap, MCA_CTRL_UNLOCK_0, ++ unlock_pattern, sizeof(unlock_pattern)); ++ if (ret) { ++ dev_err(mca->dev, "failed to unlock ctrl regs (%d)\n", ++ ret); ++ goto reset_retry; ++ } ++ ++ ret = regmap_write(pmca->regmap, MCA_CTRL_0, ++ MCA_RESET); ++ if (ret) ++ dev_err(mca->dev, "failed to reset (%d)\n", ret); ++ ++ /* ++ * The MCA will reset the cpu, so the retry should not happen... ++ * and if it happens, something went wrong, and retrying is the ++ * right thing to do. ++ */ ++reset_retry: ++ mdelay(10); ++ } while (++try < MCA_MAX_RESET_TRIES); ++ ++ dev_err(mca->dev, "failed to reboot!\n"); ++ ++ return NOTIFY_DONE; ++} ++ ++static int mca_cc8x_add_dyn_sysfs_entries(struct mca_drv *mca, ++ const struct dyn_attribute *dattr, ++ int num_entries, ++ const struct attribute_group *grp) ++{ ++ int ret, i; ++ ++ if (!mca || !dattr || !grp) ++ return -EINVAL; ++ ++ for (i = 0; i < num_entries; i++, dattr++) { ++ if (!dattr->attr) ++ continue; ++ ++ /* Create the sysfs files if the MCA fw supports the feature*/ ++ if (mca->fw_version >= dattr->since) { ++ ret = sysfs_add_file_to_group(&mca->dev->kobj, ++ dattr->attr, ++ grp->name); ++ if (ret) ++ dev_warn(mca->dev, ++ "Cannot create sysfs file %s (%d)\n", ++ dattr->attr->name, ret); ++ } ++ } ++ ++ return 0; ++} ++ ++int mca_cc8x_device_init(struct mca_drv *mca, u32 irq) ++{ ++ int ret; ++ unsigned int val; ++ ++ ret = regmap_read(mca->regmap, MCA_DEVICE_ID, &val); ++ if (ret != 0) { ++ dev_err(mca->dev, "Cannot read MCA Device ID (%d)\n", ret); ++ return ret; ++ } ++ mca->dev_id = (u8)val; ++ ++ if (mca->dev_id != MCA_CC8X_DEVICE_ID_VAL) { ++ dev_err(mca->dev, "Invalid MCA Device ID (%x)\n", mca->dev_id); ++ return -ENODEV; ++ } ++ ++ ret = regmap_read(mca->regmap, MCA_HW_VER, &val); ++ if (ret != 0) { ++ dev_err(mca->dev, "Cannot read MCA Hardware Version (%d)\n", ++ ret); ++ return ret; ++ } ++ mca->hw_version = (u8)val; ++ ++ ret = regmap_bulk_read(mca->regmap, MCA_FW_VER_L, &val, 2); ++ if (ret != 0) { ++ dev_err(mca->dev, "Cannot read MCA Firmware Version (%d)\n", ++ ret); ++ return ret; ++ } ++ mca->fw_version = (u16)(val & ~MCA_FW_VER_ALPHA_MASK); ++ mca->fw_is_alpha = val & MCA_FW_VER_ALPHA_MASK ? true : false; ++ ++ if (mca->fw_version >= MCA_MAKE_FW_VER(0, 4)) { ++ ret = regmap_bulk_read(mca->regmap, MCA_LAST_MCA_RESET_0, ++ &mca->last_mca_reset, ++ sizeof(mca->last_mca_reset)); ++ if (ret) { ++ dev_err(mca->dev, ++ "Cannot read MCA last reset (%d)\n", ret); ++ return ret; ++ } ++ ++ ret = regmap_bulk_read(mca->regmap, MCA_LAST_MPU_RESET_0, ++ &mca->last_mpu_reset, ++ sizeof(mca->last_mpu_reset)); ++ if (ret) { ++ dev_err(mca->dev, ++ "Cannot read MPU last reset (%d)\n", ret); ++ return ret; ++ } ++ } ++ ++ /* ++ * Read the SOM hardware version the MCA is using. For CC8X module it ++ * is set by uboot ++ */ ++ ret = regmap_read(mca->regmap, MCA_HWVER_SOM, &mca->som_hv); ++ if (ret != 0) ++ dev_warn(mca->dev, ++ "Cannot read SOM hardware version (%d)\n", ret); ++ ++ mca->fw_update_gpio = of_get_named_gpio(mca->dev->of_node, ++ "fw-update-gpio", 0); ++ if (!gpio_is_valid(mca->fw_update_gpio) || ++ devm_gpio_request_one(mca->dev, mca->fw_update_gpio, ++ GPIOF_OUT_INIT_LOW, "mca-fw-update")) { ++ dev_warn(mca->dev, "failed to get fw-update-gpio: %d\n", ret); ++ mca->fw_update_gpio = -EINVAL; ++ } ++ ++ mca->chip_irq = irq; ++ mca->gpio_base = -1; ++ ++ ret = mca_cc8x_irq_init(mca); ++ if (ret != 0) { ++ dev_err(mca->dev, "Cannot initialize interrupts (%d)\n", ret); ++ return ret; ++ } ++ ++ ret = mfd_add_devices(mca->dev, -1, mca_cc8x_devs, ++ ARRAY_SIZE(mca_cc8x_devs), NULL, mca->irq_base, ++ regmap_irq_get_domain(mca->regmap_irq)); ++ if (ret) { ++ dev_err(mca->dev, "Cannot add MFD cells (%d)\n", ret); ++ goto out_irq; ++ } ++ ++ ret = sysfs_create_group(&mca->dev->kobj, &mca_cc8x_attr_group); ++ if (ret) { ++ dev_err(mca->dev, "Cannot create sysfs entries (%d)\n", ret); ++ goto out_dev; ++ } ++ if (mca->fw_update_gpio == -EINVAL) { ++ /* Remove fw_update entry */ ++ sysfs_remove_file(&mca->dev->kobj, &dev_attr_fw_update.attr); ++ } ++ ++ ret = mca_cc8x_add_dyn_sysfs_entries(mca, mca_cc8x_sysfs_dyn_entries, ++ ARRAY_SIZE(mca_cc8x_sysfs_dyn_entries), ++ &mca_cc8x_attr_group); ++ if (ret) { ++ dev_err(mca->dev, "Cannot create sysfs dynamic entries (%d)\n", ++ ret); ++ goto out_sysfs_remove; ++ } ++ ++ pmca = mca; ++ ++ if (pm_power_off != NULL) { ++ dev_warn(mca->dev, "pm_power_off function already registered. " ++ "Will be override by MCA function.\n"); ++ } ++ pm_power_off = mca_cc8x_power_off; ++ ++ /* ++ * To avoid error messages when resuming from suspend, increase the I2C ++ * bus' usage counter so the linux pm_runtime framework wakes it from ++ * suspend before trying to read the MCA's IRQ status. This indicates that ++ * the bus is in use when the system is going to suspend, making linux wake ++ * it up as soon as possible so any operations that were halted continue ++ * without issues after resuming. ++ * ++ * The device hierarchy is the following: ++ * ++ * mca_cc8x 0-0063 -> i2c i2c-0 -> imx-lpi2c 5a800000.i2c ++ */ ++ pm_runtime_get_noresume(mca->dev->parent->parent); ++ ++ /* ++ * Register the MCA restart handler with high priority to ensure it is ++ * called first ++ */ ++ mca->restart_handler.notifier_call = mca_cc8x_restart_handler; ++ mca->restart_handler.priority = 200; ++ ret = register_restart_handler(&mca->restart_handler); ++ if (ret) { ++ dev_err(mca->dev, ++ "failed to register restart handler (%d)\n", ret); ++ goto out_pwr_off; ++ } ++ ++ if (mca->fw_version >= MCA_MAKE_FW_VER(0, 8)) { ++ mca->nvram = devm_kzalloc(mca->dev, sizeof(struct bin_attribute), ++ GFP_KERNEL); ++ if (!mca->nvram) { ++ dev_err(mca->dev, "Cannot allocate memory for nvram\n"); ++ goto out_pwr_off; ++ } ++ ++ sysfs_bin_attr_init(mca->nvram); ++ ++ mca->nvram->attr.name = "nvram"; ++ mca->nvram->attr.mode = S_IRUGO | S_IWUSR; ++ ++ mca->nvram->read = nvram_read; ++ mca->nvram->write = nvram_write; ++ ++ ret = sysfs_create_bin_file(&mca->dev->kobj, mca->nvram); ++ if (ret) { ++ dev_err(mca->dev, "Cannot create sysfs file: %s\n", ++ mca->nvram->attr.name); ++ goto out_nvram; ++ } ++ } ++ ++ return 0; ++ ++out_nvram: ++ kfree(mca->nvram); ++out_pwr_off: ++ pm_power_off = NULL; ++out_sysfs_remove: ++ pmca = NULL; ++ sysfs_remove_group(&mca->dev->kobj, &mca_cc8x_attr_group); ++out_dev: ++ mfd_remove_devices(mca->dev); ++out_irq: ++ mca_cc8x_irq_exit(mca); ++ ++ return ret; ++} ++ ++void mca_cc8x_device_exit(struct mca_drv *mca) ++{ ++ unregister_restart_handler(&mca->restart_handler); ++ pm_power_off = NULL; ++ pmca = NULL; ++ pm_runtime_put_noidle(mca->dev->parent->parent); ++ sysfs_remove_group(&mca->dev->kobj, &mca_cc8x_attr_group); ++ mfd_remove_devices(mca->dev); ++ mca_cc8x_irq_exit(mca); ++ kfree(mca->nvram); ++} ++ ++MODULE_AUTHOR("Digi International Inc"); ++MODULE_DESCRIPTION("MCA driver for ConnectCore 8X"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/mfd/mca-cc8x-i2c.c b/drivers/mfd/mca-cc8x-i2c.c +new file mode 100644 +index 000000000000..43e1b9e9d9d8 +--- /dev/null ++++ b/drivers/mfd/mca-cc8x-i2c.c +@@ -0,0 +1,222 @@ ++/* ++ * Copyright 2018 - 2019 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++static const struct regmap_range mca_cc8x_readable_ranges[] = { ++}; ++ ++static const struct regmap_range mca_cc8x_writeable_ranges[] = { ++ regmap_reg_range(MCA_HWVER_SOM, MCA_HWVER_SOM), ++ regmap_reg_range(MCA_IRQ_STATUS_0, MCA_IRQ_MASK_3), ++ regmap_reg_range(MCA_PWR_CTRL_0, MCA_PWR_KEY_GUARD), ++ regmap_reg_range(MCA_CTRL_UNLOCK_0, MCA_CTRL_UNLOCK_3), ++ regmap_reg_range(MCA_CTRL_0, MCA_CTRL_0), ++ regmap_reg_range(MCA_TAMPER0_CFG0, MCA_TAMPER0_EVENT), ++ regmap_reg_range(MCA_TAMPER1_CFG0, MCA_TAMPER1_EVENT), ++ regmap_reg_range(MCA_TAMPER2_CFG0, MCA_TAMPER2_THRESH_HI_H), ++ regmap_reg_range(MCA_TAMPER3_CFG0, MCA_TAMPER3_THRESH_HI_H), ++ regmap_reg_range(MCA_RTC_CONTROL, MCA_RTC_CONTROL), ++ regmap_reg_range(MCA_RTC_COUNT_YEAR_L, MCA_RTC_ALARM_SEC), ++ regmap_reg_range(MCA_WDT_CONTROL, MCA_WDT_REFRESH_3), ++ regmap_reg_range(MCA_GPIO_DIR_0, MCA_GPIO_DEB_CNT_63), ++ regmap_reg_range(MCA_REG_ADC_CFG0_0, MCA_REG_ADC_CFG0_21), ++ regmap_reg_range(MCA_REG_ADC_CFG1_0, MCA_REG_ADC_CFG1_21), ++ regmap_reg_range(MCA_REG_ADC_CFG2_0, MCA_REG_ADC_CFG2_21), ++ regmap_reg_range(MCA_REG_ADC_SAMPLES_CNT_0, MCA_REG_ADC_SAMPLES_CNT_21), ++ regmap_reg_range(MCA_REG_ADC_THRESH_LO_L_0, MCA_REG_ADC_THRESH_LO_H_21), ++ regmap_reg_range(MCA_REG_ADC_THRESH_HI_L_0, MCA_REG_ADC_THRESH_HI_H_21), ++ regmap_reg_range(MCA_REG_ADC_TICKS_L_0, MCA_REG_ADC_TICKS_H_21), ++ regmap_reg_range(MCA_REG_ADC_IRQ_0, MCA_REG_ADC_IRQ_7), ++ regmap_reg_range(MCA_REG_ADC_CFG_0, MCA_REG_ADC_CFG_2), ++ regmap_reg_range(MCA_REG_ADC_BUFF_CH, MCA_REG_ADC_BUFF_SAMPLE_21), ++ regmap_reg_range(MCA_REG_UART_THR, MCA_REG_UART_RTSPIN), ++ regmap_reg_range(MCA_CC8X_MPU_NVRAM_START, MCA_CC8X_MPU_NVRAM_END), ++ regmap_reg_range(MCA_REG_TPM0_CFG0, MCA_REG_TPM2_CH7_CNT1), ++}; ++ ++static const struct regmap_range mca_cc8x_volatile_ranges[] = { ++ /* Real volatile registers */ ++ regmap_reg_range(MCA_IRQ_STATUS_0, MCA_IRQ_STATUS_3), ++ regmap_reg_range(MCA_TAMPER0_DATE_START, MCA_TAMPER0_EVENT), ++ regmap_reg_range(MCA_TAMPER1_DATE_START, MCA_TAMPER1_EVENT), ++ regmap_reg_range(MCA_TAMPER2_DATE_START, MCA_TAMPER2_EVENT), ++ regmap_reg_range(MCA_TAMPER3_DATE_START, MCA_TAMPER3_EVENT), ++ regmap_reg_range(MCA_TIMER_TICK_0, MCA_TIMER_TICK_3), ++ regmap_reg_range(MCA_LAST_MCA_RESET_0, MCA_LAST_MCA_RESET_3), ++ regmap_reg_range(MCA_LAST_MPU_RESET_0, MCA_LAST_MPU_RESET_3), ++ regmap_reg_range(MCA_LAST_WAKEUP_REASON_0, MCA_LAST_WAKEUP_REASON_3), ++ regmap_reg_range(MCA_CC8X_MPU_NVRAM_START, MCA_CC8X_MPU_NVRAM_END), ++ regmap_reg_range(MCA_RTC_COUNT_YEAR_L, MCA_RTC_COUNT_SEC), ++ regmap_reg_range(MCA_GPIO_DATA_0, MCA_GPIO_DATA_7), ++ regmap_reg_range(MCA_GPIO_IRQ_STATUS_0, MCA_GPIO_IRQ_STATUS_7), ++ regmap_reg_range(MCA_PWR_CTRL_0, MCA_PWR_STATUS_0), ++ regmap_reg_range(MCA_REG_ADC_VAL_L_0, MCA_REG_ADC_VAL_H_21), ++ ++ /* ++ * Fake volatile registers. ++ * ++ * These registers could be cached but non-volatile registers makes ++ * regmap access each register one by one which has some drawbacks: ++ * - Breaks CRC in the protocol. ++ * - Requires the MCA firmware to process each access as a separate ++ * access, even when the data requested must be returned in bulk. ++ * ++ * For this reasons we will consider all registers volatile. ++ */ ++ regmap_reg_range(MCA_HWVER_SOM, MCA_HWVER_SOM), ++ regmap_reg_range(MCA_DEVICE_ID, MCA_UID_9), ++ regmap_reg_range(MCA_IRQ_MASK_0, MCA_IRQ_MASK_3), ++ regmap_reg_range(MCA_PWR_KEY_DEBOUNCE, MCA_PWR_KEY_GUARD), ++ regmap_reg_range(MCA_CTRL_0, MCA_CTRL_0), ++ regmap_reg_range(MCA_TAMPER0_CFG0, MCA_TAMPER0_DELAY_PWROFF), ++ regmap_reg_range(MCA_TAMPER1_CFG0, MCA_TAMPER1_DELAY_PWROFF), ++ regmap_reg_range(MCA_TAMPER2_CFG0, MCA_TAMPER2_THRESH_HI_H), ++ regmap_reg_range(MCA_TAMPER3_CFG0, MCA_TAMPER3_THRESH_HI_H), ++ regmap_reg_range(MCA_RTC_CONTROL, MCA_RTC_CONTROL), ++ regmap_reg_range(MCA_RTC_ALARM_YEAR_L, MCA_RTC_ALARM_SEC), ++ regmap_reg_range(MCA_WDT_CONTROL, MCA_WDT_TIMEOUT), ++ regmap_reg_range(MCA_GPIO_NUM, MCA_GPIO_DIR_7), ++ regmap_reg_range(MCA_GPIO_IRQ_CFG_0, MCA_GPIO_IRQ_CFG_63), ++ regmap_reg_range(MCA_REG_ADC_NUM_CH, MCA_REG_ADC_NUM_BYTES), ++ regmap_reg_range(MCA_REG_ADC_CFG0_0, MCA_REG_ADC_CFG0_21), ++ regmap_reg_range(MCA_REG_ADC_CFG1_0, MCA_REG_ADC_CFG1_21), ++ regmap_reg_range(MCA_REG_ADC_CFG2_0, MCA_REG_ADC_CFG2_21), ++ regmap_reg_range(MCA_REG_ADC_SAMPLES_CNT_0, MCA_REG_ADC_SAMPLES_CNT_21), ++ regmap_reg_range(MCA_REG_ADC_THRESH_LO_L_0, MCA_REG_ADC_THRESH_LO_H_21), ++ regmap_reg_range(MCA_REG_ADC_THRESH_HI_L_0, MCA_REG_ADC_THRESH_HI_H_21), ++ regmap_reg_range(MCA_REG_ADC_TICKS_L_0, MCA_REG_ADC_TICKS_H_21), ++ regmap_reg_range(MCA_REG_ADC_IRQ_0, MCA_REG_ADC_IRQ_7), ++ regmap_reg_range(MCA_REG_ADC_CFG_0, MCA_REG_ADC_CFG_2), ++ regmap_reg_range(MCA_REG_ADC_BUFF_CH, MCA_REG_ADC_BUFF_SAMPLE_21), ++ regmap_reg_range(MCA_REG_UART_RHR, MCA_REG_UART_RTSPIN), ++ regmap_reg_range(MCA_REG_TPM0_CFG0, MCA_REG_TPM2_CH7_CNT1), ++}; ++ ++static const struct regmap_access_table mca_cc8x_readable_table = { ++ .yes_ranges = mca_cc8x_readable_ranges, ++ .n_yes_ranges = ARRAY_SIZE(mca_cc8x_readable_ranges), ++}; ++ ++static const struct regmap_access_table mca_cc8x_writeable_table = { ++ .yes_ranges = mca_cc8x_writeable_ranges, ++ .n_yes_ranges = ARRAY_SIZE(mca_cc8x_writeable_ranges), ++}; ++ ++static const struct regmap_access_table mca_cc8x_volatile_table = { ++ .yes_ranges = mca_cc8x_volatile_ranges, ++ .n_yes_ranges = ARRAY_SIZE(mca_cc8x_volatile_ranges), ++}; ++ ++static struct regmap_config mca_cc8x_regmap_config = { ++ .reg_bits = 16, ++ .val_bits = 8, ++ .max_register = 0xFFFF, ++ ++ .rd_table = &mca_cc8x_readable_table, ++ .wr_table = &mca_cc8x_writeable_table, ++ .volatile_table = &mca_cc8x_volatile_table, ++ ++ .cache_type = REGCACHE_NONE, ++}; ++ ++static const struct of_device_id mca_cc8x_dt_ids[] = { ++ { .compatible = "digi,mca_cc8x", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, mca_cc8x_dt_ids); ++ ++static int mca_cc8x_i2c_probe(struct i2c_client *i2c, ++ const struct i2c_device_id *id) ++{ ++ struct mca_drv *mca; ++ int ret; ++ ++ mca = devm_kzalloc(&i2c->dev, sizeof(struct mca_drv), GFP_KERNEL); ++ if (mca == NULL) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(i2c, mca); ++ mca->dev = &i2c->dev; ++ mca->chip_irq = i2c->irq; ++ mca->i2c_adapter_dev = &i2c->adapter->dev; ++ ++ mca->regmap = devm_regmap_init_i2c(i2c, &mca_cc8x_regmap_config); ++ if (IS_ERR(mca->regmap)) { ++ ret = PTR_ERR(mca->regmap); ++ dev_err(mca->dev, "Failed to allocate register map: %d\n", ret); ++ return ret; ++ } ++ ++ return mca_cc8x_device_init(mca, i2c->irq); ++} ++ ++static int mca_cc8x_i2c_remove(struct i2c_client *i2c) ++{ ++ struct mca_drv *mca = i2c_get_clientdata(i2c); ++ ++ mca_cc8x_device_exit(mca); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM ++static int mca_cc8x_i2c_suspend(struct device *dev) ++{ ++ return mca_cc8x_suspend(dev); ++} ++ ++static int mca_cc8x_i2c_resume(struct device *dev) ++{ ++ return mca_cc8x_resume(dev); ++} ++ ++/* ++ * Use suspend_late/resume_early so the mca_drv continues being functional ++ * during the regular suspend/resume callbacks of other drivers, just in case ++ * they use any functionality of the mca. ++ */ ++static const struct dev_pm_ops mca_cc8x_i2c_pm_ops = { ++ SET_LATE_SYSTEM_SLEEP_PM_OPS(mca_cc8x_i2c_suspend, mca_cc8x_i2c_resume) ++}; ++#endif ++ ++static const struct i2c_device_id mca_cc8x_i2c_id[] = { ++ {"mca_cc8x", 0}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, mca_cc8x_i2c_id); ++ ++static struct i2c_driver mca_cc8x_i2c_driver = { ++ .driver = { ++ .name = "mca_cc8x", ++ .of_match_table = of_match_ptr(mca_cc8x_dt_ids), ++#ifdef CONFIG_PM ++ .pm = &mca_cc8x_i2c_pm_ops, ++#endif ++ }, ++ .probe = mca_cc8x_i2c_probe, ++ .remove = mca_cc8x_i2c_remove, ++ .id_table = mca_cc8x_i2c_id, ++}; ++ ++module_i2c_driver(mca_cc8x_i2c_driver); +diff --git a/drivers/mfd/mca-cc8x-irq.c b/drivers/mfd/mca-cc8x-irq.c +new file mode 100644 +index 000000000000..1036e602d1f9 +--- /dev/null ++++ b/drivers/mfd/mca-cc8x-irq.c +@@ -0,0 +1,143 @@ ++/* ++ * Copyright 2018 - 2019 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define MCA_IRQ_0_OFFSET 0 ++#define MCA_IRQ_1_OFFSET 1 ++#define MCA_IRQ_2_OFFSET 2 ++#define MCA_IRQ_3_OFFSET 3 ++ ++static const struct regmap_irq mca_cc8x_irqs[] = { ++ /* MCA irqs A register */ ++ [MCA_CC8X_IRQ_RTC_ALARM] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_RTC_ALARM, ++ }, ++ [MCA_CC8X_IRQ_RTC_1HZ] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_RTC_1HZ, ++ }, ++ [MCA_CC8X_IRQ_WATCHDOG] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_WATCHDOG, ++ }, ++ [MCA_CC8X_IRQ_PWR_SLEEP] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_PWR_SLEEP, ++ }, ++ [MCA_CC8X_IRQ_PWR_OFF] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_PWR_OFF, ++ }, ++ [MCA_CC8X_IRQ_TAMPER0] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_TAMPER0, ++ }, ++ [MCA_CC8X_IRQ_TAMPER1] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_TAMPER1, ++ }, ++ [MCA_CC8X_IRQ_ADC] = { ++ .reg_offset = MCA_IRQ_0_OFFSET, ++ .mask = MCA_M_ADC, ++ }, ++ [MCA_CC8X_IRQ_GPIO_BANK_0] = { ++ .reg_offset = MCA_IRQ_1_OFFSET, ++ .mask = MCA_GPIO_BANK_0, ++ }, ++ [MCA_CC8X_IRQ_GPIO_BANK_1] = { ++ .reg_offset = MCA_IRQ_1_OFFSET, ++ .mask = MCA_GPIO_BANK_1, ++ }, ++ [MCA_CC8X_IRQ_GPIO_BANK_2] = { ++ .reg_offset = MCA_IRQ_1_OFFSET, ++ .mask = MCA_GPIO_BANK_2, ++ }, ++ [MCA_CC8X_IRQ_TAMPER2] = { ++ .reg_offset = MCA_IRQ_2_OFFSET, ++ .mask = MCA_M_TAMPER2, ++ }, ++ [MCA_CC8X_IRQ_TAMPER3] = { ++ .reg_offset = MCA_IRQ_2_OFFSET, ++ .mask = MCA_M_TAMPER3, ++ }, ++ [MCA_CC8X_IRQ_UART] = { ++ .reg_offset = MCA_IRQ_2_OFFSET, ++ .mask = MCA_M_UART, ++ }, ++}; ++ ++/* ++ * This hook is used to call the SCU MU interrupt handler to manage the SC ++ * interrupts that could have been signaled by the SCU. ++ * This is needed because the MCA ISR replaces the original SCU MU ISR because ++ * both can not be registered as shared irq handlers. ++ */ ++static int mca_cc8x_pre_irq(void *irq_drv_data) ++{ ++ return imx8_scu_mu_isr(0, NULL); ++} ++ ++static const struct regmap_irq_chip mca_cc8x_irq_chip = { ++ .name = "mca-cc8x-irq", ++ .irqs = mca_cc8x_irqs, ++ .num_irqs = ARRAY_SIZE(mca_cc8x_irqs), ++ .num_regs = MCA_NUM_IRQ_REGS, ++ .status_base = MCA_IRQ_STATUS_0, ++ .mask_base = MCA_IRQ_MASK_0, ++ .ack_base = MCA_IRQ_STATUS_0, ++ .handle_pre_irq = mca_cc8x_pre_irq, ++ .init_ack_masked = true, ++}; ++ ++int mca_cc8x_irq_init(struct mca_drv *mca) ++{ ++ int ret; ++ ++ if (!mca->chip_irq) { ++ dev_err(mca->dev, "No IRQ configured\n"); ++ return -EINVAL; ++ } ++ ++ mca->irq_base = -1; ++ ret = regmap_add_irq_chip(mca->regmap, mca->chip_irq, ++ IRQF_ONESHOT | IRQF_SHARED, ++ mca->irq_base, &mca_cc8x_irq_chip, ++ &mca->regmap_irq); ++ if (ret) { ++ dev_err(mca->dev, "Failed to reguest IRQ %d: %d\n", ++ mca->chip_irq, ret); ++ return ret; ++ } ++ ++ ret = imx8_mu_enable_sc_irqs(SC_IRQ_TEMP_MCA | SC_IRQ_TEMP_PMIC0_HIGH | \ ++ SC_IRQ_TEMP_PMIC1_HIGH, ++ SC_IRQ_RTC, ++ SC_IRQ_BUTTON, ++ SC_IRQ_WDOG); ++ if (ret) ++ dev_err(mca->dev, "Failed to reguest SC MU IRQs (%d)\n", ret); ++ ++ return ret; ++} ++ ++void mca_cc8x_irq_exit(struct mca_drv *mca) ++{ ++ regmap_del_irq_chip(mca->chip_irq, mca->regmap_irq); ++} +diff --git a/include/linux/mfd/mca-cc6ul/core.h b/include/linux/mfd/mca-cc6ul/core.h +new file mode 100644 +index 000000000000..497a79dfb419 +--- /dev/null ++++ b/include/linux/mfd/mca-cc6ul/core.h +@@ -0,0 +1,56 @@ ++/* ++ * Copyright 2016 - 2019 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. ++ */ ++ ++#ifndef MFD_MCA_CC6UL_CORE_H_ ++#define MFD_MCA_CC6UL_CORE_H_ ++ ++#include ++#include ++#include ++ ++/* MCA CC6UL modules */ ++#define MCA_CC6UL_DRVNAME_CORE "mca-cc6ul-core" ++#define MCA_CC6UL_DRVNAME_RTC "mca-cc6ul-rtc" ++#define MCA_CC6UL_DRVNAME_WATCHDOG "mca-cc6ul-watchdog" ++#define MCA_CC6UL_DRVNAME_GPIO "mca-cc6ul-gpio" ++#define MCA_CC6UL_DRVNAME_PWRKEY "mca-cc6ul-pwrkey" ++#define MCA_CC6UL_DRVNAME_ADC "mca-cc6ul-adc" ++#define MCA_CC6UL_DRVNAME_TAMPER "mca-cc6ul-tamper" ++#define MCA_CC6UL_DRVNAME_COMPARATOR "mca-cc6ul-comparator" ++#define MCA_CC6UL_DRVNAME_UART "mca-cc6ul-uart" ++ ++#define MCA_CC6UL_DEVICE_ID_VAL 0x61 ++ ++/* Interrupts */ ++enum mca_cc6ul_irqs { ++ MCA_CC6UL_IRQ_RTC_ALARM, ++ MCA_CC6UL_IRQ_RTC_1HZ, ++ MCA_CC6UL_IRQ_WATCHDOG, ++ MCA_CC6UL_IRQ_PWR_SLEEP, ++ MCA_CC6UL_IRQ_PWR_OFF, ++ MCA_CC6UL_IRQ_TAMPER0, ++ MCA_CC6UL_IRQ_TAMPER1, ++ MCA_CC6UL_IRQ_ADC, ++ MCA_CC6UL_IRQ_GPIO_BANK_0, ++ MCA_CC6UL_IRQ_TAMPER2, ++ MCA_CC6UL_IRQ_TAMPER3, ++ MCA_CC6UL_IRQ_UART, ++ /* ... */ ++ ++ MCA_CC6UL_NUM_IRQS, ++}; ++ ++int mca_cc6ul_device_init(struct mca_drv *mca, u32 irq); ++int mca_cc6ul_irq_init(struct mca_drv *mca); ++void mca_cc6ul_device_exit(struct mca_drv *mca); ++void mca_cc6ul_irq_exit(struct mca_drv *mca); ++int mca_cc6ul_suspend(struct device *dev); ++int mca_cc6ul_resume(struct device *dev); ++ ++#endif /* MFD_MCA_CC6UL_CORE_H_ */ +diff --git a/include/linux/mfd/mca-cc6ul/registers.h b/include/linux/mfd/mca-cc6ul/registers.h +new file mode 100644 +index 000000000000..21a1dd3e5168 +--- /dev/null ++++ b/include/linux/mfd/mca-cc6ul/registers.h +@@ -0,0 +1,61 @@ ++/* ++ * Copyright 2017 - 2019 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. ++ */ ++ ++#ifndef MCA_CC6UL_REGISTERS_H_ ++#define MCA_CC6UL_REGISTERS_H_ ++ ++#include ++ ++/* EP0: Control and status */ ++#define MCA_CC6UL_MPU_NVRAM_START 0x00b0 ++#define MCA_CC6UL_MPU_NVRAM_END 0x00b7 ++ ++/* ++ * MCA registers bitfields ++ */ ++ ++/* MCA_CC6UL_LAST_MCA_RESET_n (addr=0x0098 & 0x009b) */ ++#define MCA_CC6UL_LAST_MCA_RST_LLW BIT(0) ++#define MCA_CC6UL_LAST_MCA_RST_LVD BIT(1) ++#define MCA_CC6UL_LAST_MCA_RST_WD BIT(5) ++#define MCA_CC6UL_LAST_MCA_RST_PIN BIT(6) ++#define MCA_CC6UL_LAST_MCA_RST_PWRON BIT(7) ++#define MCA_CC6UL_LAST_MCA_RST_LOCKUP BIT(9) ++#define MCA_CC6UL_LAST_MCA_RST_SW BIT(10) ++#define MCA_CC6UL_LAST_MCA_RST_MDMAPP BIT(11) ++#define MCA_CC6UL_LAST_MCA_RST_SMAE BIT(13) ++ ++/* MCA_CC6UL_LAST_MPU_RESET_n (addr=0x009c & 0x009f) */ ++#define MCA_CC6UL_LAST_MPU_RST_PWRON BIT(0) ++#define MCA_CC6UL_LAST_MPU_RST_SYSR BIT(1) ++#define MCA_CC6UL_LAST_MPU_RST_WD BIT(2) ++#define MCA_CC6UL_LAST_MPU_RST_OFFWAKE BIT(3) ++#define MCA_CC6UL_LAST_MPU_RST_MCARST BIT(4) ++ ++/* MCA_CC6UL_LAST_WAKEUP_REASON_n (addr=0x00a0 & 0x00a3) */ ++#define MCA_CC6UL_LAST_WAKEUP_PWRIO BIT(0) ++#define MCA_CC6UL_LAST_WAKEUP_TIMER BIT(1) ++#define MCA_CC6UL_LAST_WAKEUP_RTC BIT(2) ++#define MCA_CC6UL_LAST_WAKEUP_LPUART BIT(3) ++#define MCA_CC6UL_LAST_WAKEUP_TAMPER0 BIT(4) ++#define MCA_CC6UL_LAST_WAKEUP_TAMPER1 BIT(5) ++#define MCA_CC6UL_LAST_WAKEUP_TAMPER2 BIT(6) ++#define MCA_CC6UL_LAST_WAKEUP_TAMPER3 BIT(7) ++#define MCA_CC6UL_LAST_WAKEUP_IO0 BIT(8) ++#define MCA_CC6UL_LAST_WAKEUP_IO1 BIT(9) ++#define MCA_CC6UL_LAST_WAKEUP_IO2 BIT(10) ++#define MCA_CC6UL_LAST_WAKEUP_IO3 BIT(11) ++#define MCA_CC6UL_LAST_WAKEUP_IO4 BIT(12) ++#define MCA_CC6UL_LAST_WAKEUP_IO5 BIT(13) ++#define MCA_CC6UL_LAST_WAKEUP_IO6 BIT(14) ++#define MCA_CC6UL_LAST_WAKEUP_IO7 BIT(15) ++#define MCA_CC6UL_LAST_WAKEUP_VCC BIT(16) ++#define MCA_CC6UL_LAST_WAKEUP_CPU BIT(17) ++ ++#endif /* MCA_CC6UL_REGISTERS_H_ */ +diff --git a/include/linux/mfd/mca-common/core.h b/include/linux/mfd/mca-common/core.h +new file mode 100644 +index 000000000000..f8b06dfab66b +--- /dev/null ++++ b/include/linux/mfd/mca-common/core.h +@@ -0,0 +1,71 @@ ++/* ++ * Copyright 2017 - 2019 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. ++ */ ++ ++#ifndef MFD_MCA_COMMON_CORE_H_ ++#define MFD_MCA_COMMON_CORE_H_ ++ ++#include ++#include ++#include ++#include ++ ++#define MCA_MAKE_FW_VER(a,b) (u16)(((a) << 8) | ((b) & 0xff)) ++#define MCA_FW_VER_MAJOR(v) (((v) >> 8) & 0xff) ++#define MCA_FW_VER_MINOR(v) ((v) & 0xff) ++#define MCA_FW_VER_ALPHA_MASK BIT(15) ++ ++#define MCA_IRQ_GPIO_BANK_0_NAME "GPIO_BANK0" ++#define MCA_IRQ_GPIO_BANK_1_NAME "GPIO_BANK1" ++#define MCA_IRQ_GPIO_BANK_2_NAME "GPIO_BANK2" ++#define MCA_IRQ_GPIO_BANK_3_NAME "GPIO_BANK3" ++#define MCA_IRQ_GPIO_BANK_4_NAME "GPIO_BANK4" ++#define MCA_IRQ_GPIO_BANK_5_NAME "GPIO_BANK5" ++#define MCA_IRQ_RTC_ALARM_NAME "RTC ALARM" ++#define MCA_IRQ_RTC_1HZ_NAME "RTC 1HZ" ++#define MCA_IRQ_PWR_SLEEP_NAME "SLEEP" ++#define MCA_IRQ_PWR_OFF_NAME "PWR OFF" ++#define MCA_IRQ_WATCHDOG_NAME "WATCHDOG" ++#define MCA_IRQ_TAMPER0_NAME "TAMPER0" ++#define MCA_IRQ_TAMPER1_NAME "TAMPER1" ++#define MCA_IRQ_TAMPER2_NAME "TAMPER2" ++#define MCA_IRQ_TAMPER3_NAME "TAMPER3" ++#define MCA_IRQ_ADC_NAME "ADC" ++#define MCA_IRQ_UART_NAME "UART" ++ ++/* Number of interrupt registers */ ++#define MCA_NUM_IRQ_REGS 4 ++/* Max number of IOs */ ++#define MCA_MAX_IOS 64 ++ ++#define MCA_MAX_GPIO_IRQ_BANKS 6 ++ ++#define MCA_MAX_IO_BYTES ((MCA_MAX_IOS + 7) / 8) ++ ++struct mca_drv { ++ struct device *dev; ++ u8 dev_id; ++ u8 hw_version; ++ bool fw_is_alpha; ++ u16 fw_version; ++ u32 flags; ++ struct regmap *regmap; ++ struct regmap_irq_chip_data *regmap_irq; ++ struct notifier_block restart_handler; ++ int chip_irq; ++ u32 irq_base; ++ int gpio_base; ++ int fw_update_gpio; ++ int som_hv; ++ u32 last_mca_reset; ++ u32 last_mpu_reset; ++ struct bin_attribute *nvram; ++ struct device *i2c_adapter_dev; ++}; ++ ++#endif /* MFD_MCA_COMMON_CORE_H_ */ +diff --git a/include/linux/mfd/mca-common/registers.h b/include/linux/mfd/mca-common/registers.h +new file mode 100644 +index 000000000000..913ba71c9738 +--- /dev/null ++++ b/include/linux/mfd/mca-common/registers.h +@@ -0,0 +1,1373 @@ ++/* ++ * Copyright 2017-2019 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. ++ */ ++ ++#ifndef MCA_COMMON_REGISTERS_H_ ++#define MCA_COMMON_REGISTERS_H_ ++ ++#include ++ ++/* EP0: Control and status */ ++#define MCA_DEVICE_ID 0x0001 ++#define MCA_HW_VER 0x0002 ++#define MCA_FW_VER_L 0x0003 ++#define MCA_FW_VER_H 0x0004 ++#define MCA_UID_0 0x0005 ++#define MCA_UID_1 0x0006 ++#define MCA_UID_2 0x0007 ++#define MCA_UID_3 0x0008 ++#define MCA_UID_4 0x0009 ++#define MCA_UID_5 0x000A ++#define MCA_UID_6 0x000B ++#define MCA_UID_7 0x000C ++#define MCA_UID_8 0x000D ++#define MCA_UID_9 0x000E ++#define MCA_HWVER_SOM 0x000F ++ ++#define MCA_IRQ_STATUS_0 0x0020 ++#define MCA_IRQ_STATUS_1 0x0021 ++#define MCA_IRQ_STATUS_2 0x0022 ++#define MCA_IRQ_STATUS_3 0x0023 ++ ++#define MCA_IRQ_MASK_0 0x0024 ++#define MCA_IRQ_MASK_1 0x0025 ++#define MCA_IRQ_MASK_2 0x0026 ++#define MCA_IRQ_MASK_3 0x0027 ++ ++#define MCA_PWR_CTRL_0 0x0028 ++#define MCA_PWR_STATUS_0 0x0029 ++#define MCA_PWR_KEY_DEBOUNCE 0x002a ++#define MCA_PWR_KEY_DELAY 0x002b ++#define MCA_PWR_KEY_GUARD 0x002c ++ ++#define MCA_CTRL_UNLOCK_0 0x002d ++#define MCA_CTRL_UNLOCK_1 0x002e ++#define MCA_CTRL_UNLOCK_2 0x002f ++#define MCA_CTRL_UNLOCK_3 0x0030 ++ ++#define MCA_CTRL_0 0x0031 ++#define MCA_CTRL_1 0x0032 ++ ++#define MCA_TAMPER0_CFG0 0x0037 ++#define MCA_TAMPER0_CFG1 0x0038 ++#define MCA_TAMPER0_IO_IN 0x0039 ++#define MCA_TAMPER0_IO_OUT 0x003a ++#define MCA_TAMPER0_DELAY_PWROFF 0x003b ++/* 1 byte for padding */ ++#define MCA_TAMPER0_DATE_START 0x003d ++#define MCA_TAMPER0_DATE_END 0x0043 ++/* 1 byte for padding */ ++#define MCA_TAMPER0_EVENT 0x0045 ++ ++#define MCA_TAMPER1_CFG0 0x0046 ++#define MCA_TAMPER1_CFG1 0x0047 ++#define MCA_TAMPER1_IO_IN 0x0048 ++#define MCA_TAMPER1_IO_OUT 0x0049 ++#define MCA_TAMPER1_DELAY_PWROFF 0x004a ++/* 1 byte for padding */ ++#define MCA_TAMPER1_DATE_START 0x004c ++#define MCA_TAMPER1_DATE_END 0x0052 ++/* 1 byte for padding */ ++#define MCA_TAMPER1_EVENT 0x0054 ++ ++#define MCA_TAMPER_REGS_LEN (MCA_TAMPER1_CFG0 - \ ++ MCA_TAMPER0_CFG0) ++ ++#define MCA_TIMER_TICK_0 0x0057 ++#define MCA_TIMER_TICK_1 0x0058 ++#define MCA_TIMER_TICK_2 0x0059 ++#define MCA_TIMER_TICK_3 0x005a ++ ++#define MCA_TAMPER2_CFG0 0x0060 ++#define MCA_TAMPER2_CFG1 0x0061 ++#define MCA_TAMPER2_IO_IN 0x0062 ++#define MCA_TAMPER2_IO_OUT 0x0063 ++#define MCA_TAMPER2_DELAY_PWROFF 0x0064 ++/* 1 byte for padding */ ++/* Tamper event time-stamp follows the same format used by the RTC registers*/ ++#define MCA_TAMPER2_DATE_START 0x0066 ++/* ... */ ++#define MCA_TAMPER2_DATE_END 0x006C ++/* 1 byte for padding */ ++#define MCA_TAMPER2_EVENT 0x006E ++/* 1 byte for padding */ ++#define MCA_TAMPER2_TICKS_L 0x0070 ++#define MCA_TAMPER2_TICKS_H 0x0071 ++#define MCA_TAMPER2_THRESH_LO_L 0x0072 ++#define MCA_TAMPER2_THRESH_LO_H 0x0073 ++#define MCA_TAMPER2_THRESH_HI_L 0x0074 ++#define MCA_TAMPER2_THRESH_HI_H 0x0075 ++ ++#define MCA_TAMPER3_CFG0 0x0080 ++#define MCA_TAMPER3_CFG1 0x0081 ++#define MCA_TAMPER3_IO_IN 0x0082 ++#define MCA_TAMPER3_IO_OUT 0x0083 ++#define MCA_TAMPER3_DELAY_PWROFF 0x0084 ++/* 1 byte for padding */ ++/* Tamper event time-stamp follows the same format used by the RTC registers*/ ++#define MCA_TAMPER3_DATE_START 0x0086 ++/* ... */ ++#define MCA_TAMPER3_DATE_END 0x008C ++/* 1 byte for padding */ ++#define MCA_TAMPER3_EVENT 0x008E ++/* 1 byte for padding */ ++#define MCA_TAMPER3_TICKS_L 0x0090 ++#define MCA_TAMPER3_TICKS_H 0x0091 ++#define MCA_TAMPER3_THRESH_LO_L 0x0092 ++#define MCA_TAMPER3_THRESH_LO_H 0x0093 ++#define MCA_TAMPER3_THRESH_HI_L 0x0094 ++#define MCA_TAMPER3_THRESH_HI_H 0x0095 ++ ++#define MCA_LAST_MCA_RESET_0 0x0098 ++#define MCA_LAST_MCA_RESET_3 0x009b ++#define MCA_LAST_MPU_RESET_0 0x009c ++#define MCA_LAST_MPU_RESET_3 0x009f ++#define MCA_LAST_WAKEUP_REASON_0 0x00a0 ++#define MCA_LAST_WAKEUP_REASON_3 0x00a3 ++ ++/* EP1: RTC */ ++#define MCA_RTC_CONTROL 0x0101 ++ ++#define MCA_RTC_COUNT_YEAR_L 0x0103 ++#define MCA_RTC_COUNT_YEAR_H 0x0104 ++#define MCA_RTC_COUNT_MONTH 0x0105 ++#define MCA_RTC_COUNT_DAY 0x0106 ++#define MCA_RTC_COUNT_HOUR 0x0107 ++#define MCA_RTC_COUNT_MIN 0x0108 ++#define MCA_RTC_COUNT_SEC 0x0109 ++#define MCA_RTC_ALARM_YEAR_L 0x010A ++#define MCA_RTC_ALARM_YEAR_H 0x010B ++#define MCA_RTC_ALARM_MONTH 0x010C ++#define MCA_RTC_ALARM_DAY 0x010D ++#define MCA_RTC_ALARM_HOUR 0x010E ++#define MCA_RTC_ALARM_MIN 0x010F ++#define MCA_RTC_ALARM_SEC 0x0110 ++ ++ ++/* EP2: Watchdog */ ++#define MCA_WDT_CONTROL 0x0201 ++#define MCA_WDT_TIMEOUT 0x0202 ++#define MCA_WDT_REFRESH_0 0x0203 ++#define MCA_WDT_REFRESH_1 0x0204 ++#define MCA_WDT_REFRESH_2 0x0205 ++#define MCA_WDT_REFRESH_3 0x0206 ++ ++ ++/* EP3: GPIO */ ++#define MCA_GPIO_NUM 0x0301 ++#define MCA_GPIO_DIR_0 0x0302 ++#define MCA_GPIO_DIR_1 0x0303 ++#define MCA_GPIO_DIR_2 0x0304 ++#define MCA_GPIO_DIR_3 0x0305 ++#define MCA_GPIO_DIR_4 0x0306 ++#define MCA_GPIO_DIR_5 0x0307 ++#define MCA_GPIO_DIR_6 0x0308 ++#define MCA_GPIO_DIR_7 0x0309 ++#define MCA_GPIO_DATA_0 0x030A ++#define MCA_GPIO_DATA_1 0x030B ++#define MCA_GPIO_DATA_2 0x030C ++#define MCA_GPIO_DATA_3 0x030D ++#define MCA_GPIO_DATA_4 0x030E ++#define MCA_GPIO_DATA_5 0x030F ++#define MCA_GPIO_DATA_6 0x0310 ++#define MCA_GPIO_DATA_7 0x0311 ++#define MCA_GPIO_SET_0 0x0312 ++#define MCA_GPIO_SET_1 0x0313 ++#define MCA_GPIO_SET_2 0x0314 ++#define MCA_GPIO_SET_3 0x0315 ++#define MCA_GPIO_SET_4 0x0316 ++#define MCA_GPIO_SET_5 0x0317 ++#define MCA_GPIO_SET_6 0x0318 ++#define MCA_GPIO_SET_7 0x0319 ++#define MCA_GPIO_CLEAR_0 0x031A ++#define MCA_GPIO_CLEAR_1 0x031B ++#define MCA_GPIO_CLEAR_2 0x031C ++#define MCA_GPIO_CLEAR_3 0x031D ++#define MCA_GPIO_CLEAR_4 0x031E ++#define MCA_GPIO_CLEAR_5 0x031F ++#define MCA_GPIO_CLEAR_6 0x0320 ++#define MCA_GPIO_CLEAR_7 0x0321 ++#define MCA_GPIO_TOGGLE_0 0x0322 ++#define MCA_GPIO_TOGGLE_1 0x0323 ++#define MCA_GPIO_TOGGLE_2 0x0324 ++#define MCA_GPIO_TOGGLE_3 0x0325 ++#define MCA_GPIO_TOGGLE_4 0x0326 ++#define MCA_GPIO_TOGGLE_5 0x0327 ++#define MCA_GPIO_TOGGLE_6 0x0328 ++#define MCA_GPIO_TOGGLE_7 0x0329 ++#define MCA_GPIO_IRQ_STATUS_0 0x032a ++#define MCA_GPIO_IRQ_STATUS_1 0x032b ++#define MCA_GPIO_IRQ_STATUS_2 0x032c ++#define MCA_GPIO_IRQ_STATUS_3 0x032d ++#define MCA_GPIO_IRQ_STATUS_4 0x032e ++#define MCA_GPIO_IRQ_STATUS_5 0x032f ++#define MCA_GPIO_IRQ_STATUS_6 0x0330 ++#define MCA_GPIO_IRQ_STATUS_7 0x0331 ++/* Note, there is one IRQ configuration register per GPIO pin */ ++#define MCA_GPIO_IRQ_CFG_0 0x0332 ++#define MCA_GPIO_IRQ_CFG_1 0x0333 ++#define MCA_GPIO_IRQ_CFG_2 0x0334 ++#define MCA_GPIO_IRQ_CFG_3 0x0335 ++#define MCA_GPIO_IRQ_CFG_4 0x0336 ++#define MCA_GPIO_IRQ_CFG_5 0x0337 ++#define MCA_GPIO_IRQ_CFG_6 0x0338 ++#define MCA_GPIO_IRQ_CFG_7 0x0339 ++/* ... */ ++#define MCA_GPIO_IRQ_CFG_63 0x0371 ++ ++#define MCA_GPIO_DEB_CFG_0 0x0372 ++/* ... */ ++#define MCA_GPIO_DEB_CFG_7 0x0379 ++ ++#define MCA_GPIO_DEB_CNT_0 0x037a ++/* ... */ ++#define MCA_GPIO_DEB_CNT_63 0x03b9 ++ ++/* EP4, ADCs Configuration (0 - 31) */ ++#define MCA_REG_ADC_NUM_CH 0x0401 ++#define MCA_REG_ADC_NUM_BYTES 0x0402 ++#define MCA_REG_ADC_CFG0_0 0x0404 ++#define MCA_REG_ADC_CFG0_1 0x0405 ++#define MCA_REG_ADC_CFG0_2 0x0406 ++#define MCA_REG_ADC_CFG0_3 0x0407 ++#define MCA_REG_ADC_CFG0_4 0x0408 ++#define MCA_REG_ADC_CFG0_5 0x0409 ++#define MCA_REG_ADC_CFG0_6 0x040a ++#define MCA_REG_ADC_CFG0_7 0x040b ++#define MCA_REG_ADC_CFG0_8 0x040c ++#define MCA_REG_ADC_CFG0_9 0x040d ++#define MCA_REG_ADC_CFG0_10 0x040e ++#define MCA_REG_ADC_CFG0_11 0x040f ++#define MCA_REG_ADC_CFG0_12 0x0410 ++#define MCA_REG_ADC_CFG0_13 0x0411 ++#define MCA_REG_ADC_CFG0_14 0x0412 ++#define MCA_REG_ADC_CFG0_15 0x0413 ++#define MCA_REG_ADC_CFG0_16 0x0414 ++#define MCA_REG_ADC_CFG0_17 0x0415 ++#define MCA_REG_ADC_CFG0_18 0x0416 ++#define MCA_REG_ADC_CFG0_19 0x0417 ++#define MCA_REG_ADC_CFG0_20 0x0418 ++#define MCA_REG_ADC_CFG0_21 0x0419 ++#define MCA_REG_ADC_CFG0_22 0x041a ++#define MCA_REG_ADC_CFG0_23 0x041b ++#define MCA_REG_ADC_CFG0_24 0x041c ++#define MCA_REG_ADC_CFG0_25 0x041d ++#define MCA_REG_ADC_CFG0_26 0x041e ++#define MCA_REG_ADC_CFG0_27 0x041f ++#define MCA_REG_ADC_CFG0_28 0x0420 ++#define MCA_REG_ADC_CFG0_29 0x0421 ++#define MCA_REG_ADC_CFG0_30 0x0422 ++#define MCA_REG_ADC_CFG0_31 0x0423 ++ ++#define MCA_REG_ADC_CFG1_0 0x0424 ++#define MCA_REG_ADC_CFG1_1 0x0425 ++#define MCA_REG_ADC_CFG1_2 0x0426 ++#define MCA_REG_ADC_CFG1_3 0x0427 ++#define MCA_REG_ADC_CFG1_4 0x0428 ++#define MCA_REG_ADC_CFG1_5 0x0429 ++#define MCA_REG_ADC_CFG1_6 0x042a ++#define MCA_REG_ADC_CFG1_7 0x042b ++#define MCA_REG_ADC_CFG1_8 0x042c ++#define MCA_REG_ADC_CFG1_9 0x042d ++#define MCA_REG_ADC_CFG1_10 0x042e ++#define MCA_REG_ADC_CFG1_11 0x042f ++#define MCA_REG_ADC_CFG1_12 0x0430 ++#define MCA_REG_ADC_CFG1_13 0x0431 ++#define MCA_REG_ADC_CFG1_14 0x0432 ++#define MCA_REG_ADC_CFG1_15 0x0433 ++#define MCA_REG_ADC_CFG1_16 0x0434 ++#define MCA_REG_ADC_CFG1_17 0x0435 ++#define MCA_REG_ADC_CFG1_18 0x0436 ++#define MCA_REG_ADC_CFG1_19 0x0437 ++#define MCA_REG_ADC_CFG1_20 0x0438 ++#define MCA_REG_ADC_CFG1_21 0x0439 ++#define MCA_REG_ADC_CFG1_22 0x043a ++#define MCA_REG_ADC_CFG1_23 0x043b ++#define MCA_REG_ADC_CFG1_24 0x043c ++#define MCA_REG_ADC_CFG1_25 0x043d ++#define MCA_REG_ADC_CFG1_26 0x043e ++#define MCA_REG_ADC_CFG1_27 0x043f ++#define MCA_REG_ADC_CFG1_28 0x0440 ++#define MCA_REG_ADC_CFG1_29 0x0441 ++#define MCA_REG_ADC_CFG1_30 0x0442 ++#define MCA_REG_ADC_CFG1_31 0x0443 ++ ++#define MCA_REG_ADC_CFG2_0 0x0444 ++#define MCA_REG_ADC_CFG2_1 0x0445 ++#define MCA_REG_ADC_CFG2_2 0x0446 ++#define MCA_REG_ADC_CFG2_3 0x0447 ++#define MCA_REG_ADC_CFG2_4 0x0448 ++#define MCA_REG_ADC_CFG2_5 0x0449 ++#define MCA_REG_ADC_CFG2_6 0x044a ++#define MCA_REG_ADC_CFG2_7 0x044b ++#define MCA_REG_ADC_CFG2_8 0x044c ++#define MCA_REG_ADC_CFG2_9 0x044d ++#define MCA_REG_ADC_CFG2_10 0x044e ++#define MCA_REG_ADC_CFG2_11 0x044f ++#define MCA_REG_ADC_CFG2_12 0x0450 ++#define MCA_REG_ADC_CFG2_13 0x0451 ++#define MCA_REG_ADC_CFG2_14 0x0452 ++#define MCA_REG_ADC_CFG2_15 0x0453 ++#define MCA_REG_ADC_CFG2_16 0x0454 ++#define MCA_REG_ADC_CFG2_17 0x0455 ++#define MCA_REG_ADC_CFG2_18 0x0456 ++#define MCA_REG_ADC_CFG2_19 0x0457 ++#define MCA_REG_ADC_CFG2_20 0x0458 ++#define MCA_REG_ADC_CFG2_21 0x0459 ++#define MCA_REG_ADC_CFG2_22 0x045a ++#define MCA_REG_ADC_CFG2_23 0x045b ++#define MCA_REG_ADC_CFG2_24 0x045c ++#define MCA_REG_ADC_CFG2_25 0x045d ++#define MCA_REG_ADC_CFG2_26 0x045e ++#define MCA_REG_ADC_CFG2_27 0x045f ++#define MCA_REG_ADC_CFG2_28 0x0460 ++#define MCA_REG_ADC_CFG2_29 0x0461 ++#define MCA_REG_ADC_CFG2_30 0x0462 ++#define MCA_REG_ADC_CFG2_31 0x0463 ++ ++#define MCA_REG_ADC_VAL_L_0 0x464 ++#define MCA_REG_ADC_VAL_H_0 0x465 ++#define MCA_REG_ADC_VAL_L_1 0x466 ++#define MCA_REG_ADC_VAL_H_1 0x467 ++#define MCA_REG_ADC_VAL_L_2 0x468 ++#define MCA_REG_ADC_VAL_H_2 0x469 ++#define MCA_REG_ADC_VAL_L_3 0x46a ++#define MCA_REG_ADC_VAL_H_3 0x46b ++#define MCA_REG_ADC_VAL_L_4 0x46c ++#define MCA_REG_ADC_VAL_H_4 0x46d ++#define MCA_REG_ADC_VAL_L_5 0x46e ++#define MCA_REG_ADC_VAL_H_5 0x46f ++#define MCA_REG_ADC_VAL_L_6 0x470 ++#define MCA_REG_ADC_VAL_H_6 0x471 ++#define MCA_REG_ADC_VAL_L_7 0x472 ++#define MCA_REG_ADC_VAL_H_7 0x473 ++#define MCA_REG_ADC_VAL_L_8 0x474 ++#define MCA_REG_ADC_VAL_H_8 0x475 ++#define MCA_REG_ADC_VAL_L_9 0x476 ++#define MCA_REG_ADC_VAL_H_9 0x477 ++#define MCA_REG_ADC_VAL_L_10 0x478 ++#define MCA_REG_ADC_VAL_H_10 0x479 ++#define MCA_REG_ADC_VAL_L_11 0x47a ++#define MCA_REG_ADC_VAL_H_11 0x47b ++#define MCA_REG_ADC_VAL_L_12 0x47c ++#define MCA_REG_ADC_VAL_H_12 0x47d ++#define MCA_REG_ADC_VAL_L_13 0x47e ++#define MCA_REG_ADC_VAL_H_13 0x47f ++#define MCA_REG_ADC_VAL_L_14 0x480 ++#define MCA_REG_ADC_VAL_H_14 0x481 ++#define MCA_REG_ADC_VAL_L_15 0x482 ++#define MCA_REG_ADC_VAL_H_15 0x483 ++#define MCA_REG_ADC_VAL_L_16 0x484 ++#define MCA_REG_ADC_VAL_H_16 0x485 ++#define MCA_REG_ADC_VAL_L_17 0x486 ++#define MCA_REG_ADC_VAL_H_17 0x487 ++#define MCA_REG_ADC_VAL_L_18 0x488 ++#define MCA_REG_ADC_VAL_H_18 0x489 ++#define MCA_REG_ADC_VAL_L_19 0x48a ++#define MCA_REG_ADC_VAL_H_19 0x48b ++#define MCA_REG_ADC_VAL_L_20 0x48c ++#define MCA_REG_ADC_VAL_H_20 0x48d ++#define MCA_REG_ADC_VAL_L_21 0x48e ++#define MCA_REG_ADC_VAL_H_21 0x48f ++#define MCA_REG_ADC_VAL_L_22 0x490 ++#define MCA_REG_ADC_VAL_H_22 0x491 ++#define MCA_REG_ADC_VAL_L_23 0x492 ++#define MCA_REG_ADC_VAL_H_23 0x493 ++#define MCA_REG_ADC_VAL_L_24 0x494 ++#define MCA_REG_ADC_VAL_H_24 0x495 ++#define MCA_REG_ADC_VAL_L_25 0x496 ++#define MCA_REG_ADC_VAL_H_25 0x497 ++#define MCA_REG_ADC_VAL_L_26 0x498 ++#define MCA_REG_ADC_VAL_H_26 0x499 ++#define MCA_REG_ADC_VAL_L_27 0x49a ++#define MCA_REG_ADC_VAL_H_27 0x49b ++#define MCA_REG_ADC_VAL_L_28 0x49c ++#define MCA_REG_ADC_VAL_H_28 0x49d ++#define MCA_REG_ADC_VAL_L_29 0x49e ++#define MCA_REG_ADC_VAL_H_29 0x49f ++#define MCA_REG_ADC_VAL_L_30 0x4a0 ++#define MCA_REG_ADC_VAL_H_30 0x4a1 ++#define MCA_REG_ADC_VAL_L_31 0x4a2 ++#define MCA_REG_ADC_VAL_H_31 0x4a3 ++ ++#define MCA_REG_ADC_SAMPLES_CNT_0 0x04a4 ++#define MCA_REG_ADC_SAMPLES_CNT_1 0x04a5 ++#define MCA_REG_ADC_SAMPLES_CNT_2 0x04a6 ++#define MCA_REG_ADC_SAMPLES_CNT_3 0x04a7 ++#define MCA_REG_ADC_SAMPLES_CNT_4 0x04a8 ++#define MCA_REG_ADC_SAMPLES_CNT_5 0x04a9 ++#define MCA_REG_ADC_SAMPLES_CNT_6 0x04aa ++#define MCA_REG_ADC_SAMPLES_CNT_7 0x04ab ++#define MCA_REG_ADC_SAMPLES_CNT_8 0x04ac ++#define MCA_REG_ADC_SAMPLES_CNT_9 0x04ad ++#define MCA_REG_ADC_SAMPLES_CNT_10 0x04ae ++#define MCA_REG_ADC_SAMPLES_CNT_11 0x04af ++#define MCA_REG_ADC_SAMPLES_CNT_12 0x04b0 ++#define MCA_REG_ADC_SAMPLES_CNT_13 0x04b1 ++#define MCA_REG_ADC_SAMPLES_CNT_14 0x04b2 ++#define MCA_REG_ADC_SAMPLES_CNT_15 0x04b3 ++#define MCA_REG_ADC_SAMPLES_CNT_16 0x04b4 ++#define MCA_REG_ADC_SAMPLES_CNT_17 0x04b5 ++#define MCA_REG_ADC_SAMPLES_CNT_18 0x04b6 ++#define MCA_REG_ADC_SAMPLES_CNT_19 0x04b7 ++#define MCA_REG_ADC_SAMPLES_CNT_20 0x04b8 ++#define MCA_REG_ADC_SAMPLES_CNT_21 0x04b9 ++#define MCA_REG_ADC_SAMPLES_CNT_22 0x04ba ++#define MCA_REG_ADC_SAMPLES_CNT_23 0x04bb ++#define MCA_REG_ADC_SAMPLES_CNT_24 0x04bc ++#define MCA_REG_ADC_SAMPLES_CNT_25 0x04bd ++#define MCA_REG_ADC_SAMPLES_CNT_26 0x04be ++#define MCA_REG_ADC_SAMPLES_CNT_27 0x04bf ++#define MCA_REG_ADC_SAMPLES_CNT_28 0x04c0 ++#define MCA_REG_ADC_SAMPLES_CNT_29 0x04c1 ++#define MCA_REG_ADC_SAMPLES_CNT_30 0x04c2 ++#define MCA_REG_ADC_SAMPLES_CNT_31 0x04c3 ++ ++/* EP5, ADCs Thresholds (0 - 31) */ ++#define MCA_REG_ADC_THRESH_LO_L_0 0x0500 ++#define MCA_REG_ADC_THRESH_LO_H_0 0x0501 ++#define MCA_REG_ADC_THRESH_LO_L_1 0x0502 ++#define MCA_REG_ADC_THRESH_LO_H_1 0x0503 ++#define MCA_REG_ADC_THRESH_LO_L_2 0x0504 ++#define MCA_REG_ADC_THRESH_LO_H_2 0x0505 ++#define MCA_REG_ADC_THRESH_LO_L_3 0x0506 ++#define MCA_REG_ADC_THRESH_LO_H_3 0x0507 ++#define MCA_REG_ADC_THRESH_LO_L_4 0x0508 ++#define MCA_REG_ADC_THRESH_LO_H_4 0x0509 ++#define MCA_REG_ADC_THRESH_LO_L_5 0x050a ++#define MCA_REG_ADC_THRESH_LO_H_5 0x050b ++#define MCA_REG_ADC_THRESH_LO_L_6 0x050c ++#define MCA_REG_ADC_THRESH_LO_H_6 0x050d ++#define MCA_REG_ADC_THRESH_LO_L_7 0x050e ++#define MCA_REG_ADC_THRESH_LO_H_7 0x050f ++#define MCA_REG_ADC_THRESH_LO_L_8 0x0510 ++#define MCA_REG_ADC_THRESH_LO_H_8 0x0511 ++#define MCA_REG_ADC_THRESH_LO_L_9 0x0512 ++#define MCA_REG_ADC_THRESH_LO_H_9 0x0513 ++#define MCA_REG_ADC_THRESH_LO_L_10 0x0514 ++#define MCA_REG_ADC_THRESH_LO_H_10 0x0515 ++#define MCA_REG_ADC_THRESH_LO_L_11 0x0516 ++#define MCA_REG_ADC_THRESH_LO_H_11 0x0517 ++#define MCA_REG_ADC_THRESH_LO_L_12 0x0518 ++#define MCA_REG_ADC_THRESH_LO_H_12 0x0519 ++#define MCA_REG_ADC_THRESH_LO_L_13 0x051a ++#define MCA_REG_ADC_THRESH_LO_H_13 0x051b ++#define MCA_REG_ADC_THRESH_LO_L_14 0x051c ++#define MCA_REG_ADC_THRESH_LO_H_14 0x051d ++#define MCA_REG_ADC_THRESH_LO_L_15 0x051e ++#define MCA_REG_ADC_THRESH_LO_H_15 0x051f ++#define MCA_REG_ADC_THRESH_LO_L_16 0x0520 ++#define MCA_REG_ADC_THRESH_LO_H_16 0x0521 ++#define MCA_REG_ADC_THRESH_LO_L_17 0x0522 ++#define MCA_REG_ADC_THRESH_LO_H_17 0x0523 ++#define MCA_REG_ADC_THRESH_LO_L_18 0x0524 ++#define MCA_REG_ADC_THRESH_LO_H_18 0x0525 ++#define MCA_REG_ADC_THRESH_LO_L_19 0x0526 ++#define MCA_REG_ADC_THRESH_LO_H_19 0x0527 ++#define MCA_REG_ADC_THRESH_LO_L_20 0x0528 ++#define MCA_REG_ADC_THRESH_LO_H_20 0x0529 ++#define MCA_REG_ADC_THRESH_LO_L_21 0x052a ++#define MCA_REG_ADC_THRESH_LO_H_21 0x052b ++#define MCA_REG_ADC_THRESH_LO_L_22 0x052c ++#define MCA_REG_ADC_THRESH_LO_H_22 0x052d ++#define MCA_REG_ADC_THRESH_LO_L_23 0x052e ++#define MCA_REG_ADC_THRESH_LO_H_23 0x052f ++#define MCA_REG_ADC_THRESH_LO_L_24 0x0530 ++#define MCA_REG_ADC_THRESH_LO_H_24 0x0531 ++#define MCA_REG_ADC_THRESH_LO_L_25 0x0532 ++#define MCA_REG_ADC_THRESH_LO_H_25 0x0533 ++#define MCA_REG_ADC_THRESH_LO_L_26 0x0534 ++#define MCA_REG_ADC_THRESH_LO_H_26 0x0535 ++#define MCA_REG_ADC_THRESH_LO_L_27 0x0536 ++#define MCA_REG_ADC_THRESH_LO_H_27 0x0537 ++#define MCA_REG_ADC_THRESH_LO_L_28 0x0538 ++#define MCA_REG_ADC_THRESH_LO_H_28 0x0539 ++#define MCA_REG_ADC_THRESH_LO_L_29 0x053a ++#define MCA_REG_ADC_THRESH_LO_H_29 0x053b ++#define MCA_REG_ADC_THRESH_LO_L_30 0x053c ++#define MCA_REG_ADC_THRESH_LO_H_30 0x053d ++#define MCA_REG_ADC_THRESH_LO_L_31 0x053e ++#define MCA_REG_ADC_THRESH_LO_H_31 0x053f ++ ++#define MCA_REG_ADC_THRESH_HI_L_0 0x0540 ++#define MCA_REG_ADC_THRESH_HI_H_0 0x0541 ++#define MCA_REG_ADC_THRESH_HI_L_1 0x0542 ++#define MCA_REG_ADC_THRESH_HI_H_1 0x0543 ++#define MCA_REG_ADC_THRESH_HI_L_2 0x0544 ++#define MCA_REG_ADC_THRESH_HI_H_2 0x0545 ++#define MCA_REG_ADC_THRESH_HI_L_3 0x0546 ++#define MCA_REG_ADC_THRESH_HI_H_3 0x0547 ++#define MCA_REG_ADC_THRESH_HI_L_4 0x0548 ++#define MCA_REG_ADC_THRESH_HI_H_4 0x0549 ++#define MCA_REG_ADC_THRESH_HI_L_5 0x054a ++#define MCA_REG_ADC_THRESH_HI_H_5 0x054b ++#define MCA_REG_ADC_THRESH_HI_L_6 0x054c ++#define MCA_REG_ADC_THRESH_HI_H_6 0x054d ++#define MCA_REG_ADC_THRESH_HI_L_7 0x054e ++#define MCA_REG_ADC_THRESH_HI_H_7 0x054f ++#define MCA_REG_ADC_THRESH_HI_L_8 0x0550 ++#define MCA_REG_ADC_THRESH_HI_H_8 0x0551 ++#define MCA_REG_ADC_THRESH_HI_L_9 0x0552 ++#define MCA_REG_ADC_THRESH_HI_H_9 0x0553 ++#define MCA_REG_ADC_THRESH_HI_L_10 0x0554 ++#define MCA_REG_ADC_THRESH_HI_H_10 0x0555 ++#define MCA_REG_ADC_THRESH_HI_L_11 0x0556 ++#define MCA_REG_ADC_THRESH_HI_H_11 0x0557 ++#define MCA_REG_ADC_THRESH_HI_L_12 0x0558 ++#define MCA_REG_ADC_THRESH_HI_H_12 0x0559 ++#define MCA_REG_ADC_THRESH_HI_L_13 0x055a ++#define MCA_REG_ADC_THRESH_HI_H_13 0x055b ++#define MCA_REG_ADC_THRESH_HI_L_14 0x055c ++#define MCA_REG_ADC_THRESH_HI_H_14 0x055d ++#define MCA_REG_ADC_THRESH_HI_L_15 0x055e ++#define MCA_REG_ADC_THRESH_HI_H_15 0x055f ++#define MCA_REG_ADC_THRESH_HI_L_16 0x0560 ++#define MCA_REG_ADC_THRESH_HI_H_16 0x0561 ++#define MCA_REG_ADC_THRESH_HI_L_17 0x0562 ++#define MCA_REG_ADC_THRESH_HI_H_17 0x0563 ++#define MCA_REG_ADC_THRESH_HI_L_18 0x0564 ++#define MCA_REG_ADC_THRESH_HI_H_18 0x0565 ++#define MCA_REG_ADC_THRESH_HI_L_19 0x0566 ++#define MCA_REG_ADC_THRESH_HI_H_19 0x0567 ++#define MCA_REG_ADC_THRESH_HI_L_20 0x0568 ++#define MCA_REG_ADC_THRESH_HI_H_20 0x0569 ++#define MCA_REG_ADC_THRESH_HI_L_21 0x056a ++#define MCA_REG_ADC_THRESH_HI_H_21 0x056b ++#define MCA_REG_ADC_THRESH_HI_L_22 0x056c ++#define MCA_REG_ADC_THRESH_HI_H_22 0x056d ++#define MCA_REG_ADC_THRESH_HI_L_23 0x056e ++#define MCA_REG_ADC_THRESH_HI_H_23 0x056f ++#define MCA_REG_ADC_THRESH_HI_L_24 0x0570 ++#define MCA_REG_ADC_THRESH_HI_H_24 0x0571 ++#define MCA_REG_ADC_THRESH_HI_L_25 0x0572 ++#define MCA_REG_ADC_THRESH_HI_H_25 0x0573 ++#define MCA_REG_ADC_THRESH_HI_L_26 0x0574 ++#define MCA_REG_ADC_THRESH_HI_H_26 0x0575 ++#define MCA_REG_ADC_THRESH_HI_L_27 0x0576 ++#define MCA_REG_ADC_THRESH_HI_H_27 0x0577 ++#define MCA_REG_ADC_THRESH_HI_L_28 0x0578 ++#define MCA_REG_ADC_THRESH_HI_H_28 0x0579 ++#define MCA_REG_ADC_THRESH_HI_L_29 0x057a ++#define MCA_REG_ADC_THRESH_HI_H_29 0x057b ++#define MCA_REG_ADC_THRESH_HI_L_30 0x057c ++#define MCA_REG_ADC_THRESH_HI_H_30 0x057d ++#define MCA_REG_ADC_THRESH_HI_L_31 0x057e ++#define MCA_REG_ADC_THRESH_HI_H_31 0x057f ++ ++#define MCA_REG_ADC_TICKS_L_0 0x0580 ++#define MCA_REG_ADC_TICKS_H_0 0x0581 ++#define MCA_REG_ADC_TICKS_L_1 0x0582 ++#define MCA_REG_ADC_TICKS_H_1 0x0583 ++#define MCA_REG_ADC_TICKS_L_2 0x0584 ++#define MCA_REG_ADC_TICKS_H_2 0x0585 ++#define MCA_REG_ADC_TICKS_L_3 0x0586 ++#define MCA_REG_ADC_TICKS_H_3 0x0587 ++#define MCA_REG_ADC_TICKS_L_4 0x0588 ++#define MCA_REG_ADC_TICKS_H_4 0x0589 ++#define MCA_REG_ADC_TICKS_L_5 0x058a ++#define MCA_REG_ADC_TICKS_H_5 0x058b ++#define MCA_REG_ADC_TICKS_L_6 0x058c ++#define MCA_REG_ADC_TICKS_H_6 0x058d ++#define MCA_REG_ADC_TICKS_L_7 0x058e ++#define MCA_REG_ADC_TICKS_H_7 0x058f ++#define MCA_REG_ADC_TICKS_L_8 0x0590 ++#define MCA_REG_ADC_TICKS_H_8 0x0591 ++#define MCA_REG_ADC_TICKS_L_9 0x0592 ++#define MCA_REG_ADC_TICKS_H_9 0x0593 ++#define MCA_REG_ADC_TICKS_L_10 0x0594 ++#define MCA_REG_ADC_TICKS_H_10 0x0595 ++#define MCA_REG_ADC_TICKS_L_11 0x0596 ++#define MCA_REG_ADC_TICKS_H_11 0x0597 ++#define MCA_REG_ADC_TICKS_L_12 0x0598 ++#define MCA_REG_ADC_TICKS_H_12 0x0599 ++#define MCA_REG_ADC_TICKS_L_13 0x059a ++#define MCA_REG_ADC_TICKS_H_13 0x059b ++#define MCA_REG_ADC_TICKS_L_14 0x059c ++#define MCA_REG_ADC_TICKS_H_14 0x059d ++#define MCA_REG_ADC_TICKS_L_15 0x059e ++#define MCA_REG_ADC_TICKS_H_15 0x059f ++#define MCA_REG_ADC_TICKS_L_16 0x05a0 ++#define MCA_REG_ADC_TICKS_H_16 0x05a1 ++#define MCA_REG_ADC_TICKS_L_17 0x05a2 ++#define MCA_REG_ADC_TICKS_H_17 0x05a3 ++#define MCA_REG_ADC_TICKS_L_18 0x05a4 ++#define MCA_REG_ADC_TICKS_H_18 0x05a5 ++#define MCA_REG_ADC_TICKS_L_19 0x05a6 ++#define MCA_REG_ADC_TICKS_H_19 0x05a7 ++#define MCA_REG_ADC_TICKS_L_20 0x05a8 ++#define MCA_REG_ADC_TICKS_H_20 0x05a9 ++#define MCA_REG_ADC_TICKS_L_21 0x05aa ++#define MCA_REG_ADC_TICKS_H_21 0x05ab ++#define MCA_REG_ADC_TICKS_L_22 0x05ac ++#define MCA_REG_ADC_TICKS_H_22 0x05ad ++#define MCA_REG_ADC_TICKS_L_23 0x05ae ++#define MCA_REG_ADC_TICKS_H_23 0x05af ++#define MCA_REG_ADC_TICKS_L_24 0x05b0 ++#define MCA_REG_ADC_TICKS_H_24 0x05b1 ++#define MCA_REG_ADC_TICKS_L_25 0x05b2 ++#define MCA_REG_ADC_TICKS_H_25 0x05b3 ++#define MCA_REG_ADC_TICKS_L_26 0x05b4 ++#define MCA_REG_ADC_TICKS_H_26 0x05b5 ++#define MCA_REG_ADC_TICKS_L_27 0x05b6 ++#define MCA_REG_ADC_TICKS_H_27 0x05b7 ++#define MCA_REG_ADC_TICKS_L_28 0x05b8 ++#define MCA_REG_ADC_TICKS_H_28 0x05b9 ++#define MCA_REG_ADC_TICKS_L_29 0x05ba ++#define MCA_REG_ADC_TICKS_H_29 0x05bb ++#define MCA_REG_ADC_TICKS_L_30 0x05bc ++#define MCA_REG_ADC_TICKS_H_30 0x05bd ++#define MCA_REG_ADC_TICKS_L_31 0x05be ++#define MCA_REG_ADC_TICKS_H_31 0x05bf ++ ++/* EP4, ADCs Configuration (32 - 63) */ ++#define MCA_REG_ADC_CFG0_32 0x0604 ++#define MCA_REG_ADC_CFG0_33 0x0605 ++#define MCA_REG_ADC_CFG0_34 0x0606 ++#define MCA_REG_ADC_CFG0_35 0x0607 ++#define MCA_REG_ADC_CFG0_36 0x0608 ++#define MCA_REG_ADC_CFG0_37 0x0609 ++#define MCA_REG_ADC_CFG0_38 0x060a ++#define MCA_REG_ADC_CFG0_39 0x060b ++#define MCA_REG_ADC_CFG0_40 0x060c ++#define MCA_REG_ADC_CFG0_41 0x060d ++#define MCA_REG_ADC_CFG0_42 0x060e ++#define MCA_REG_ADC_CFG0_43 0x060f ++#define MCA_REG_ADC_CFG0_44 0x0610 ++#define MCA_REG_ADC_CFG0_45 0x0611 ++#define MCA_REG_ADC_CFG0_46 0x0612 ++#define MCA_REG_ADC_CFG0_47 0x0613 ++#define MCA_REG_ADC_CFG0_48 0x0614 ++#define MCA_REG_ADC_CFG0_49 0x0615 ++#define MCA_REG_ADC_CFG0_50 0x0616 ++#define MCA_REG_ADC_CFG0_51 0x0617 ++#define MCA_REG_ADC_CFG0_52 0x0618 ++#define MCA_REG_ADC_CFG0_53 0x0619 ++#define MCA_REG_ADC_CFG0_54 0x061a ++#define MCA_REG_ADC_CFG0_55 0x061b ++#define MCA_REG_ADC_CFG0_56 0x061c ++#define MCA_REG_ADC_CFG0_57 0x061d ++#define MCA_REG_ADC_CFG0_58 0x061e ++#define MCA_REG_ADC_CFG0_59 0x061f ++#define MCA_REG_ADC_CFG0_60 0x0620 ++#define MCA_REG_ADC_CFG0_61 0x0621 ++#define MCA_REG_ADC_CFG0_62 0x0622 ++#define MCA_REG_ADC_CFG0_63 0x0623 ++ ++#define MCA_REG_ADC_CFG1_32 0x0644 ++#define MCA_REG_ADC_CFG1_33 0x0645 ++#define MCA_REG_ADC_CFG1_34 0x0646 ++#define MCA_REG_ADC_CFG1_35 0x0647 ++#define MCA_REG_ADC_CFG1_36 0x0648 ++#define MCA_REG_ADC_CFG1_37 0x0649 ++#define MCA_REG_ADC_CFG1_38 0x064a ++#define MCA_REG_ADC_CFG1_39 0x064b ++#define MCA_REG_ADC_CFG1_40 0x064c ++#define MCA_REG_ADC_CFG1_41 0x064d ++#define MCA_REG_ADC_CFG1_42 0x064e ++#define MCA_REG_ADC_CFG1_43 0x064f ++#define MCA_REG_ADC_CFG1_44 0x0650 ++#define MCA_REG_ADC_CFG1_45 0x0651 ++#define MCA_REG_ADC_CFG1_46 0x0652 ++#define MCA_REG_ADC_CFG1_47 0x0653 ++#define MCA_REG_ADC_CFG1_48 0x0654 ++#define MCA_REG_ADC_CFG1_49 0x0655 ++#define MCA_REG_ADC_CFG1_50 0x0656 ++#define MCA_REG_ADC_CFG1_51 0x0657 ++#define MCA_REG_ADC_CFG1_52 0x0658 ++#define MCA_REG_ADC_CFG1_53 0x0659 ++#define MCA_REG_ADC_CFG1_54 0x065a ++#define MCA_REG_ADC_CFG1_55 0x065b ++#define MCA_REG_ADC_CFG1_56 0x065c ++#define MCA_REG_ADC_CFG1_57 0x065d ++#define MCA_REG_ADC_CFG1_58 0x065e ++#define MCA_REG_ADC_CFG1_59 0x065f ++#define MCA_REG_ADC_CFG1_60 0x0660 ++#define MCA_REG_ADC_CFG1_61 0x0661 ++#define MCA_REG_ADC_CFG1_62 0x0662 ++#define MCA_REG_ADC_CFG1_63 0x0663 ++ ++#define MCA_REG_ADC_CFG2_32 0x0664 ++#define MCA_REG_ADC_CFG2_33 0x0665 ++#define MCA_REG_ADC_CFG2_34 0x0666 ++#define MCA_REG_ADC_CFG2_35 0x0667 ++#define MCA_REG_ADC_CFG2_36 0x0668 ++#define MCA_REG_ADC_CFG2_37 0x0669 ++#define MCA_REG_ADC_CFG2_38 0x066a ++#define MCA_REG_ADC_CFG2_39 0x066b ++#define MCA_REG_ADC_CFG2_40 0x066c ++#define MCA_REG_ADC_CFG2_41 0x066d ++#define MCA_REG_ADC_CFG2_42 0x066e ++#define MCA_REG_ADC_CFG2_43 0x066f ++#define MCA_REG_ADC_CFG2_44 0x0670 ++#define MCA_REG_ADC_CFG2_45 0x0671 ++#define MCA_REG_ADC_CFG2_46 0x0672 ++#define MCA_REG_ADC_CFG2_47 0x0673 ++#define MCA_REG_ADC_CFG2_48 0x0674 ++#define MCA_REG_ADC_CFG2_49 0x0675 ++#define MCA_REG_ADC_CFG2_50 0x0676 ++#define MCA_REG_ADC_CFG2_51 0x0677 ++#define MCA_REG_ADC_CFG2_52 0x0678 ++#define MCA_REG_ADC_CFG2_53 0x0679 ++#define MCA_REG_ADC_CFG2_54 0x067a ++#define MCA_REG_ADC_CFG2_55 0x067b ++#define MCA_REG_ADC_CFG2_56 0x067c ++#define MCA_REG_ADC_CFG2_57 0x067d ++#define MCA_REG_ADC_CFG2_58 0x067e ++#define MCA_REG_ADC_CFG2_59 0x067f ++#define MCA_REG_ADC_CFG2_60 0x0680 ++#define MCA_REG_ADC_CFG2_61 0x0681 ++#define MCA_REG_ADC_CFG2_62 0x0682 ++#define MCA_REG_ADC_CFG2_63 0x0683 ++ ++#define MCA_REG_ADC_SAMPLES_CNT_32 0x06a4 ++#define MCA_REG_ADC_SAMPLES_CNT_33 0x06a5 ++#define MCA_REG_ADC_SAMPLES_CNT_34 0x06a6 ++#define MCA_REG_ADC_SAMPLES_CNT_35 0x06a7 ++#define MCA_REG_ADC_SAMPLES_CNT_36 0x06a8 ++#define MCA_REG_ADC_SAMPLES_CNT_37 0x06a9 ++#define MCA_REG_ADC_SAMPLES_CNT_38 0x06aa ++#define MCA_REG_ADC_SAMPLES_CNT_39 0x06ab ++#define MCA_REG_ADC_SAMPLES_CNT_40 0x06ac ++#define MCA_REG_ADC_SAMPLES_CNT_41 0x06ad ++#define MCA_REG_ADC_SAMPLES_CNT_42 0x06ae ++#define MCA_REG_ADC_SAMPLES_CNT_43 0x06af ++#define MCA_REG_ADC_SAMPLES_CNT_44 0x06b0 ++#define MCA_REG_ADC_SAMPLES_CNT_45 0x06b1 ++#define MCA_REG_ADC_SAMPLES_CNT_46 0x06b2 ++#define MCA_REG_ADC_SAMPLES_CNT_47 0x06b3 ++#define MCA_REG_ADC_SAMPLES_CNT_48 0x06b4 ++#define MCA_REG_ADC_SAMPLES_CNT_49 0x06b5 ++#define MCA_REG_ADC_SAMPLES_CNT_50 0x06b6 ++#define MCA_REG_ADC_SAMPLES_CNT_51 0x06b7 ++#define MCA_REG_ADC_SAMPLES_CNT_52 0x06b8 ++#define MCA_REG_ADC_SAMPLES_CNT_53 0x06b9 ++#define MCA_REG_ADC_SAMPLES_CNT_54 0x06ba ++#define MCA_REG_ADC_SAMPLES_CNT_55 0x06bb ++#define MCA_REG_ADC_SAMPLES_CNT_56 0x06bc ++#define MCA_REG_ADC_SAMPLES_CNT_57 0x06bd ++#define MCA_REG_ADC_SAMPLES_CNT_58 0x06be ++#define MCA_REG_ADC_SAMPLES_CNT_59 0x06bf ++#define MCA_REG_ADC_SAMPLES_CNT_60 0x06c0 ++#define MCA_REG_ADC_SAMPLES_CNT_61 0x06c1 ++#define MCA_REG_ADC_SAMPLES_CNT_62 0x06c2 ++#define MCA_REG_ADC_SAMPLES_CNT_63 0x06c3 ++ ++/* EP7, ADCs Thresholds (32 - 63) */ ++#define MCA_REG_ADC_THRESH_LO_L_32 0x0700 ++#define MCA_REG_ADC_THRESH_LO_H_32 0x0701 ++#define MCA_REG_ADC_THRESH_LO_L_33 0x0702 ++#define MCA_REG_ADC_THRESH_LO_H_33 0x0703 ++#define MCA_REG_ADC_THRESH_LO_L_34 0x0704 ++#define MCA_REG_ADC_THRESH_LO_H_34 0x0705 ++#define MCA_REG_ADC_THRESH_LO_L_35 0x0706 ++#define MCA_REG_ADC_THRESH_LO_H_35 0x0707 ++#define MCA_REG_ADC_THRESH_LO_L_36 0x0708 ++#define MCA_REG_ADC_THRESH_LO_H_36 0x0709 ++#define MCA_REG_ADC_THRESH_LO_L_37 0x070a ++#define MCA_REG_ADC_THRESH_LO_H_37 0x070b ++#define MCA_REG_ADC_THRESH_LO_L_38 0x070c ++#define MCA_REG_ADC_THRESH_LO_H_38 0x070d ++#define MCA_REG_ADC_THRESH_LO_L_39 0x070e ++#define MCA_REG_ADC_THRESH_LO_H_39 0x070f ++#define MCA_REG_ADC_THRESH_LO_L_40 0x0710 ++#define MCA_REG_ADC_THRESH_LO_H_40 0x0711 ++#define MCA_REG_ADC_THRESH_LO_L_41 0x0712 ++#define MCA_REG_ADC_THRESH_LO_H_41 0x0713 ++#define MCA_REG_ADC_THRESH_LO_L_42 0x0714 ++#define MCA_REG_ADC_THRESH_LO_H_42 0x0715 ++#define MCA_REG_ADC_THRESH_LO_L_43 0x0716 ++#define MCA_REG_ADC_THRESH_LO_H_43 0x0717 ++#define MCA_REG_ADC_THRESH_LO_L_44 0x0718 ++#define MCA_REG_ADC_THRESH_LO_H_44 0x0719 ++#define MCA_REG_ADC_THRESH_LO_L_45 0x071a ++#define MCA_REG_ADC_THRESH_LO_H_45 0x071b ++#define MCA_REG_ADC_THRESH_LO_L_46 0x071c ++#define MCA_REG_ADC_THRESH_LO_H_46 0x071d ++#define MCA_REG_ADC_THRESH_LO_L_47 0x071e ++#define MCA_REG_ADC_THRESH_LO_H_47 0x071f ++#define MCA_REG_ADC_THRESH_LO_L_48 0x0720 ++#define MCA_REG_ADC_THRESH_LO_H_48 0x0721 ++#define MCA_REG_ADC_THRESH_LO_L_49 0x0722 ++#define MCA_REG_ADC_THRESH_LO_H_49 0x0723 ++#define MCA_REG_ADC_THRESH_LO_L_50 0x0724 ++#define MCA_REG_ADC_THRESH_LO_H_50 0x0725 ++#define MCA_REG_ADC_THRESH_LO_L_51 0x0726 ++#define MCA_REG_ADC_THRESH_LO_H_51 0x0727 ++#define MCA_REG_ADC_THRESH_LO_L_52 0x0728 ++#define MCA_REG_ADC_THRESH_LO_H_52 0x0729 ++#define MCA_REG_ADC_THRESH_LO_L_53 0x072a ++#define MCA_REG_ADC_THRESH_LO_H_53 0x072b ++#define MCA_REG_ADC_THRESH_LO_L_54 0x072c ++#define MCA_REG_ADC_THRESH_LO_H_54 0x072d ++#define MCA_REG_ADC_THRESH_LO_L_55 0x072e ++#define MCA_REG_ADC_THRESH_LO_H_55 0x072f ++#define MCA_REG_ADC_THRESH_LO_L_56 0x0730 ++#define MCA_REG_ADC_THRESH_LO_H_56 0x0731 ++#define MCA_REG_ADC_THRESH_LO_L_57 0x0732 ++#define MCA_REG_ADC_THRESH_LO_H_57 0x0733 ++#define MCA_REG_ADC_THRESH_LO_L_58 0x0734 ++#define MCA_REG_ADC_THRESH_LO_H_58 0x0735 ++#define MCA_REG_ADC_THRESH_LO_L_59 0x0736 ++#define MCA_REG_ADC_THRESH_LO_H_59 0x0737 ++#define MCA_REG_ADC_THRESH_LO_L_60 0x0738 ++#define MCA_REG_ADC_THRESH_LO_H_60 0x0739 ++#define MCA_REG_ADC_THRESH_LO_L_61 0x073a ++#define MCA_REG_ADC_THRESH_LO_H_61 0x073b ++#define MCA_REG_ADC_THRESH_LO_L_62 0x073c ++#define MCA_REG_ADC_THRESH_LO_H_62 0x073d ++#define MCA_REG_ADC_THRESH_LO_L_63 0x073e ++#define MCA_REG_ADC_THRESH_LO_H_63 0x073f ++ ++#define MCA_REG_ADC_THRESH_HI_L_32 0x0740 ++#define MCA_REG_ADC_THRESH_HI_H_32 0x0741 ++#define MCA_REG_ADC_THRESH_HI_L_33 0x0742 ++#define MCA_REG_ADC_THRESH_HI_H_33 0x0743 ++#define MCA_REG_ADC_THRESH_HI_L_34 0x0744 ++#define MCA_REG_ADC_THRESH_HI_H_34 0x0745 ++#define MCA_REG_ADC_THRESH_HI_L_35 0x0746 ++#define MCA_REG_ADC_THRESH_HI_H_35 0x0747 ++#define MCA_REG_ADC_THRESH_HI_L_36 0x0748 ++#define MCA_REG_ADC_THRESH_HI_H_36 0x0749 ++#define MCA_REG_ADC_THRESH_HI_L_37 0x074a ++#define MCA_REG_ADC_THRESH_HI_H_37 0x074b ++#define MCA_REG_ADC_THRESH_HI_L_38 0x074c ++#define MCA_REG_ADC_THRESH_HI_H_38 0x074d ++#define MCA_REG_ADC_THRESH_HI_L_39 0x074e ++#define MCA_REG_ADC_THRESH_HI_H_39 0x074f ++#define MCA_REG_ADC_THRESH_HI_L_40 0x0750 ++#define MCA_REG_ADC_THRESH_HI_H_40 0x0751 ++#define MCA_REG_ADC_THRESH_HI_L_41 0x0752 ++#define MCA_REG_ADC_THRESH_HI_H_41 0x0753 ++#define MCA_REG_ADC_THRESH_HI_L_42 0x0754 ++#define MCA_REG_ADC_THRESH_HI_H_42 0x0755 ++#define MCA_REG_ADC_THRESH_HI_L_43 0x0756 ++#define MCA_REG_ADC_THRESH_HI_H_43 0x0757 ++#define MCA_REG_ADC_THRESH_HI_L_44 0x0758 ++#define MCA_REG_ADC_THRESH_HI_H_44 0x0759 ++#define MCA_REG_ADC_THRESH_HI_L_45 0x075a ++#define MCA_REG_ADC_THRESH_HI_H_45 0x075b ++#define MCA_REG_ADC_THRESH_HI_L_46 0x075c ++#define MCA_REG_ADC_THRESH_HI_H_46 0x075d ++#define MCA_REG_ADC_THRESH_HI_L_47 0x075e ++#define MCA_REG_ADC_THRESH_HI_H_47 0x075f ++#define MCA_REG_ADC_THRESH_HI_L_48 0x0760 ++#define MCA_REG_ADC_THRESH_HI_H_48 0x0761 ++#define MCA_REG_ADC_THRESH_HI_L_49 0x0762 ++#define MCA_REG_ADC_THRESH_HI_H_49 0x0763 ++#define MCA_REG_ADC_THRESH_HI_L_50 0x0764 ++#define MCA_REG_ADC_THRESH_HI_H_50 0x0765 ++#define MCA_REG_ADC_THRESH_HI_L_51 0x0766 ++#define MCA_REG_ADC_THRESH_HI_H_51 0x0767 ++#define MCA_REG_ADC_THRESH_HI_L_52 0x0768 ++#define MCA_REG_ADC_THRESH_HI_H_52 0x0769 ++#define MCA_REG_ADC_THRESH_HI_L_53 0x076a ++#define MCA_REG_ADC_THRESH_HI_H_53 0x076b ++#define MCA_REG_ADC_THRESH_HI_L_54 0x076c ++#define MCA_REG_ADC_THRESH_HI_H_54 0x076d ++#define MCA_REG_ADC_THRESH_HI_L_55 0x076e ++#define MCA_REG_ADC_THRESH_HI_H_55 0x076f ++#define MCA_REG_ADC_THRESH_HI_L_56 0x0770 ++#define MCA_REG_ADC_THRESH_HI_H_56 0x0771 ++#define MCA_REG_ADC_THRESH_HI_L_57 0x0772 ++#define MCA_REG_ADC_THRESH_HI_H_57 0x0773 ++#define MCA_REG_ADC_THRESH_HI_L_58 0x0774 ++#define MCA_REG_ADC_THRESH_HI_H_58 0x0775 ++#define MCA_REG_ADC_THRESH_HI_L_59 0x0776 ++#define MCA_REG_ADC_THRESH_HI_H_59 0x0777 ++#define MCA_REG_ADC_THRESH_HI_L_60 0x0778 ++#define MCA_REG_ADC_THRESH_HI_H_60 0x0779 ++#define MCA_REG_ADC_THRESH_HI_L_61 0x077a ++#define MCA_REG_ADC_THRESH_HI_H_61 0x077b ++#define MCA_REG_ADC_THRESH_HI_L_62 0x077c ++#define MCA_REG_ADC_THRESH_HI_H_62 0x077d ++#define MCA_REG_ADC_THRESH_HI_L_63 0x077e ++#define MCA_REG_ADC_THRESH_HI_H_63 0x077f ++ ++#define MCA_REG_ADC_TICKS_L_32 0x0780 ++#define MCA_REG_ADC_TICKS_H_32 0x0781 ++#define MCA_REG_ADC_TICKS_L_33 0x0782 ++#define MCA_REG_ADC_TICKS_H_33 0x0783 ++#define MCA_REG_ADC_TICKS_L_34 0x0784 ++#define MCA_REG_ADC_TICKS_H_34 0x0785 ++#define MCA_REG_ADC_TICKS_L_35 0x0786 ++#define MCA_REG_ADC_TICKS_H_35 0x0787 ++#define MCA_REG_ADC_TICKS_L_36 0x0788 ++#define MCA_REG_ADC_TICKS_H_36 0x0789 ++#define MCA_REG_ADC_TICKS_L_37 0x078a ++#define MCA_REG_ADC_TICKS_H_37 0x078b ++#define MCA_REG_ADC_TICKS_L_38 0x078c ++#define MCA_REG_ADC_TICKS_H_38 0x078d ++#define MCA_REG_ADC_TICKS_L_39 0x078e ++#define MCA_REG_ADC_TICKS_H_39 0x078f ++#define MCA_REG_ADC_TICKS_L_40 0x0790 ++#define MCA_REG_ADC_TICKS_H_40 0x0791 ++#define MCA_REG_ADC_TICKS_L_41 0x0792 ++#define MCA_REG_ADC_TICKS_H_41 0x0793 ++#define MCA_REG_ADC_TICKS_L_42 0x0794 ++#define MCA_REG_ADC_TICKS_H_42 0x0795 ++#define MCA_REG_ADC_TICKS_L_43 0x0796 ++#define MCA_REG_ADC_TICKS_H_43 0x0797 ++#define MCA_REG_ADC_TICKS_L_44 0x0798 ++#define MCA_REG_ADC_TICKS_H_44 0x0799 ++#define MCA_REG_ADC_TICKS_L_45 0x079a ++#define MCA_REG_ADC_TICKS_H_45 0x079b ++#define MCA_REG_ADC_TICKS_L_46 0x079c ++#define MCA_REG_ADC_TICKS_H_46 0x079d ++#define MCA_REG_ADC_TICKS_L_47 0x079e ++#define MCA_REG_ADC_TICKS_H_47 0x079f ++#define MCA_REG_ADC_TICKS_L_48 0x07a0 ++#define MCA_REG_ADC_TICKS_H_48 0x07a1 ++#define MCA_REG_ADC_TICKS_L_49 0x07a2 ++#define MCA_REG_ADC_TICKS_H_49 0x07a3 ++#define MCA_REG_ADC_TICKS_L_50 0x07a4 ++#define MCA_REG_ADC_TICKS_H_50 0x07a5 ++#define MCA_REG_ADC_TICKS_L_51 0x07a6 ++#define MCA_REG_ADC_TICKS_H_51 0x07a7 ++#define MCA_REG_ADC_TICKS_L_52 0x07a8 ++#define MCA_REG_ADC_TICKS_H_52 0x07a9 ++#define MCA_REG_ADC_TICKS_L_53 0x07aa ++#define MCA_REG_ADC_TICKS_H_53 0x07ab ++#define MCA_REG_ADC_TICKS_L_54 0x07ac ++#define MCA_REG_ADC_TICKS_H_54 0x07ad ++#define MCA_REG_ADC_TICKS_L_55 0x07ae ++#define MCA_REG_ADC_TICKS_H_55 0x07af ++#define MCA_REG_ADC_TICKS_L_56 0x07b0 ++#define MCA_REG_ADC_TICKS_H_56 0x07b1 ++#define MCA_REG_ADC_TICKS_L_57 0x07b2 ++#define MCA_REG_ADC_TICKS_H_57 0x07b3 ++#define MCA_REG_ADC_TICKS_L_58 0x07b4 ++#define MCA_REG_ADC_TICKS_H_58 0x07b5 ++#define MCA_REG_ADC_TICKS_L_59 0x07b6 ++#define MCA_REG_ADC_TICKS_H_59 0x07b7 ++#define MCA_REG_ADC_TICKS_L_60 0x07b8 ++#define MCA_REG_ADC_TICKS_H_60 0x07b9 ++#define MCA_REG_ADC_TICKS_L_61 0x07ba ++#define MCA_REG_ADC_TICKS_H_61 0x07bb ++#define MCA_REG_ADC_TICKS_L_62 0x07bc ++#define MCA_REG_ADC_TICKS_H_62 0x07bd ++#define MCA_REG_ADC_TICKS_L_63 0x07be ++#define MCA_REG_ADC_TICKS_H_63 0x07bf ++ ++/* EP8, ADCs Buffered samples */ ++#define MCA_REG_ADC_IRQ_0 0x0800 ++#define MCA_REG_ADC_IRQ_1 0x0801 ++#define MCA_REG_ADC_IRQ_2 0x0802 ++#define MCA_REG_ADC_IRQ_3 0x0803 ++#define MCA_REG_ADC_IRQ_4 0x0804 ++#define MCA_REG_ADC_IRQ_5 0x0805 ++#define MCA_REG_ADC_IRQ_6 0x0806 ++#define MCA_REG_ADC_IRQ_7 0x0807 ++ ++#define MCA_REG_ADC_CFG_0 0x0808 ++#define MCA_REG_ADC_CFG_1 0x0809 ++#define MCA_REG_ADC_CFG_2 0x080A ++ ++#define MCA_REG_ADC_BUFF_CH 0x0810 ++#define MCA_REG_ADC_BUFF_CNT 0x0811 ++#define MCA_REG_ADC_BUFF_SAMPLE_0 0x8010 ++#define MCA_REG_ADC_BUFF_SAMPLE_1 0x8011 ++#define MCA_REG_ADC_BUFF_SAMPLE_2 0x8012 ++#define MCA_REG_ADC_BUFF_SAMPLE_3 0x8013 ++#define MCA_REG_ADC_BUFF_SAMPLE_4 0x8014 ++#define MCA_REG_ADC_BUFF_SAMPLE_5 0x8015 ++#define MCA_REG_ADC_BUFF_SAMPLE_6 0x8016 ++#define MCA_REG_ADC_BUFF_SAMPLE_7 0x8017 ++#define MCA_REG_ADC_BUFF_SAMPLE_8 0x8018 ++#define MCA_REG_ADC_BUFF_SAMPLE_9 0x8019 ++#define MCA_REG_ADC_BUFF_SAMPLE_10 0x801a ++#define MCA_REG_ADC_BUFF_SAMPLE_11 0x801b ++#define MCA_REG_ADC_BUFF_SAMPLE_12 0x801c ++#define MCA_REG_ADC_BUFF_SAMPLE_13 0x801d ++#define MCA_REG_ADC_BUFF_SAMPLE_14 0x801e ++#define MCA_REG_ADC_BUFF_SAMPLE_15 0x801f ++#define MCA_REG_ADC_BUFF_SAMPLE_16 0x8020 ++#define MCA_REG_ADC_BUFF_SAMPLE_17 0x8021 ++#define MCA_REG_ADC_BUFF_SAMPLE_18 0x8022 ++#define MCA_REG_ADC_BUFF_SAMPLE_19 0x8023 ++#define MCA_REG_ADC_BUFF_SAMPLE_20 0x8024 ++#define MCA_REG_ADC_BUFF_SAMPLE_21 0x8025 ++#define MCA_REG_ADC_BUFF_SAMPLE_22 0x8026 ++#define MCA_REG_ADC_BUFF_SAMPLE_23 0x8027 ++#define MCA_REG_ADC_BUFF_SAMPLE_24 0x8028 ++#define MCA_REG_ADC_BUFF_SAMPLE_25 0x8029 ++#define MCA_REG_ADC_BUFF_SAMPLE_26 0x802a ++#define MCA_REG_ADC_BUFF_SAMPLE_27 0x802b ++#define MCA_REG_ADC_BUFF_SAMPLE_28 0x802c ++#define MCA_REG_ADC_BUFF_SAMPLE_29 0x802d ++#define MCA_REG_ADC_BUFF_SAMPLE_30 0x802e ++#define MCA_REG_ADC_BUFF_SAMPLE_31 0x802f ++ ++/* EP9, UART */ ++#define MCA_REG_UART_THR 0x0900 ++#define MCA_REG_UART_RHR MCA_REG_UART_THR ++#define MCA_REG_UART_IER 0x0901 ++#define MCA_REG_UART_IIR 0x0902 ++#define MCA_REG_UART_LSR 0x0903 ++#define MCA_REG_UART_MSR 0x0904 ++#define MCA_REG_UART_RX_ERRORS 0x0905 ++#define MCA_REG_UART_TXLVL 0x0906 ++#define MCA_REG_UART_RXLVL 0x0907 ++#define MCA_REG_UART_RXWM 0x0908 ++#define MCA_REG_UART_TXWM 0x0909 ++#define MCA_REG_UART_RXSIZE 0x090A ++#define MCA_REG_UART_TXSIZE 0x090B ++#define MCA_REG_UART_CFG0 0x090C ++#define MCA_REG_UART_CFG1 0x090D ++#define MCA_REG_UART_BAUD 0x090E ++#define MCA_REG_UART_RXPIN 0x090F ++#define MCA_REG_UART_TXPIN 0x0910 ++#define MCA_REG_UART_CTSPIN 0x0911 ++#define MCA_REG_UART_RTSPIN 0x0912 ++ ++#define MCA_REG_UART_IER_RHR BIT(0) ++#define MCA_REG_UART_IER_THR BIT(1) ++#define MCA_REG_UART_IER_RLSE BIT(2) ++#define MCA_REG_UART_IER_MSR BIT(3) ++ ++#define MCA_REG_UART_IIR_RHR MCA_REG_UART_IER_RHR ++#define MCA_REG_UART_IIR_THR MCA_REG_UART_IER_THR ++#define MCA_REG_UART_IIR_RLSE MCA_REG_UART_IER_RLSE ++#define MCA_REG_UART_IIR_MSR MCA_REG_UART_IER_MSR ++ ++#define MCA_REG_UART_LSR_NO_ERROR 0 ++#define MCA_REG_UART_LSR_FRAMING_ERROR BIT(0) ++#define MCA_REG_UART_LSR_PARITY_ERROR BIT(1) ++#define MCA_REG_UART_LSR_FIFO_OR_ERROR BIT(2) ++#define MCA_REG_UART_LSR_HW_OR_ERROR BIT(3) ++#define MCA_REG_UART_LSR_BREAK BIT(4) ++ ++#define MCA_REG_UART_MSR_CTS BIT(0) ++#define MCA_REG_UART_MSR_RTS BIT(1) ++ ++#define MCA_REG_UART_CFG0_ENABLE BIT(0) ++#define MCA_REG_UART_CFG0_WAKEUP BIT(1) ++#define MCA_REG_UART_CFG0_FORCE_WAKEUP BIT(2) ++#define MCA_REG_UART_CFG0_PWR_ON BIT(3) ++#define MCA_REG_UART_CFG0_RXEN BIT(4) ++#define MCA_REG_UART_CFG0_TXEN BIT(5) ++#define MCA_REG_UART_CFG0_CRX BIT(6) ++#define MCA_REG_UART_CFG0_CTX BIT(7) ++ ++#define MCA_REG_UART_CFG1_PARITY_EN BIT(0) ++#define MCA_REG_UART_CFG1_PARITY_ODD BIT(1) ++#define MCA_REG_UART_CFG1_TWO_STOPBITS BIT(2) ++#define MCA_REG_UART_CFG1_CTS_EN BIT(3) ++#define MCA_REG_UART_CFG1_RTS_EN BIT(4) ++#define MCA_REG_UART_CFG1_THROTTLE BIT(5) ++ ++#define MCA_REG_UART_BAUD_1200 0 ++#define MCA_REG_UART_BAUD_2400 1 ++#define MCA_REG_UART_BAUD_4800 2 ++#define MCA_REG_UART_BAUD_9600 3 ++#define MCA_REG_UART_BAUD_19200 4 ++#define MCA_REG_UART_BAUD_38400 5 ++#define MCA_REG_UART_BAUD_57600 6 ++#define MCA_REG_UART_BAUD_115200 7 ++#define MCA_REG_UART_BAUD_230400 8 ++ ++/* EP10, TPMs */ ++/* Reserved 4 bytes */ ++#define MCA_REG_TPM0_CFG0 0x0a04 ++#define MCA_REG_TPM0_CFG1 0x0a05 ++#define MCA_REG_TPM0_CFG2 0x0a06 ++#define MCA_REG_TPM0_PRESCALER 0x0a07 ++/* ... */ ++#define MCA_REG_TPM0_FREQ_0 0x0a0c ++#define MCA_REG_TPM0_FREQ_1 0x0a0d ++#define MCA_REG_TPM0_FREQ_2 0x0a0e ++#define MCA_REG_TPM0_FREQ_3 0x0a0f ++#define MCA_REG_TPM0_CH0_CFG 0x0a10 ++#define MCA_REG_TPM0_CH0_DUTY 0x0a11 ++#define MCA_REG_TPM0_CH0_CNT0 0x0a12 ++#define MCA_REG_TPM0_CH0_CNT1 0x0a13 ++#define MCA_REG_TPM0_CH1_CFG 0x0a14 ++#define MCA_REG_TPM0_CH1_DUTY 0x0a15 ++#define MCA_REG_TPM0_CH1_CNT0 0x0a16 ++#define MCA_REG_TPM0_CH1_CNT1 0x0a17 ++#define MCA_REG_TPM0_CH2_CFG 0x0a18 ++#define MCA_REG_TPM0_CH2_DUTY 0x0a19 ++#define MCA_REG_TPM0_CH2_CNT0 0x0a1a ++#define MCA_REG_TPM0_CH2_CNT1 0x0a1b ++#define MCA_REG_TPM0_CH3_CFG 0x0a1c ++#define MCA_REG_TPM0_CH3_DUTY 0x0a1d ++#define MCA_REG_TPM0_CH3_CNT0 0x0a1e ++#define MCA_REG_TPM0_CH3_CNT1 0x0a1f ++#define MCA_REG_TPM0_CH4_CFG 0x0a20 ++#define MCA_REG_TPM0_CH4_DUTY 0x0a21 ++#define MCA_REG_TPM0_CH4_CNT0 0x0a22 ++#define MCA_REG_TPM0_CH4_CNT1 0x0a23 ++#define MCA_REG_TPM0_CH5_CFG 0x0a24 ++#define MCA_REG_TPM0_CH5_DUTY 0x0a25 ++#define MCA_REG_TPM0_CH5_CNT0 0x0a26 ++#define MCA_REG_TPM0_CH5_CNT1 0x0a27 ++#define MCA_REG_TPM0_CH6_CFG 0x0a28 ++#define MCA_REG_TPM0_CH6_DUTY 0x0a29 ++#define MCA_REG_TPM0_CH6_CNT0 0x0a2a ++#define MCA_REG_TPM0_CH6_CNT1 0x0a2b ++#define MCA_REG_TPM0_CH7_CFG 0x0a2c ++#define MCA_REG_TPM0_CH7_DUTY 0x0a2d ++#define MCA_REG_TPM0_CH7_CNT0 0x0a2e ++#define MCA_REG_TPM0_CH7_CNT1 0x0a2f ++#define MCA_REG_TPM1_CFG0 0x0a34 ++#define MCA_REG_TPM1_CFG1 0x0a35 ++#define MCA_REG_TPM1_CFG2 0x0a36 ++#define MCA_REG_TPM1_PRESCALER 0x0a37 ++/* ... */ ++#define MCA_REG_TPM1_FREQ_0 0x0a3c ++#define MCA_REG_TPM1_FREQ_1 0x0a3d ++#define MCA_REG_TPM1_FREQ_2 0x0a3e ++#define MCA_REG_TPM1_FREQ_3 0x0a3f ++#define MCA_REG_TPM1_CH0_CFG 0x0a40 ++#define MCA_REG_TPM1_CH0_DUTY 0x0a41 ++#define MCA_REG_TPM1_CH0_CNT0 0x0a42 ++#define MCA_REG_TPM1_CH0_CNT1 0x0a43 ++#define MCA_REG_TPM1_CH1_CFG 0x0a44 ++#define MCA_REG_TPM1_CH1_DUTY 0x0a45 ++#define MCA_REG_TPM1_CH1_CNT0 0x0a46 ++#define MCA_REG_TPM1_CH1_CNT1 0x0a47 ++#define MCA_REG_TPM1_CH2_CFG 0x0a48 ++#define MCA_REG_TPM1_CH2_DUTY 0x0a49 ++#define MCA_REG_TPM1_CH2_CNT0 0x0a4a ++#define MCA_REG_TPM1_CH2_CNT1 0x0a4b ++#define MCA_REG_TPM1_CH3_CFG 0x0a4c ++#define MCA_REG_TPM1_CH3_DUTY 0x0a4d ++#define MCA_REG_TPM1_CH3_CNT0 0x0a4e ++#define MCA_REG_TPM1_CH3_CNT1 0x0a4f ++#define MCA_REG_TPM1_CH4_CFG 0x0a50 ++#define MCA_REG_TPM1_CH4_DUTY 0x0a51 ++#define MCA_REG_TPM1_CH4_CNT0 0x0a52 ++#define MCA_REG_TPM1_CH4_CNT1 0x0a53 ++#define MCA_REG_TPM1_CH5_CFG 0x0a54 ++#define MCA_REG_TPM1_CH5_DUTY 0x0a55 ++#define MCA_REG_TPM1_CH5_CNT0 0x0a56 ++#define MCA_REG_TPM1_CH5_CNT1 0x0a57 ++#define MCA_REG_TPM1_CH6_CFG 0x0a58 ++#define MCA_REG_TPM1_CH6_DUTY 0x0a59 ++#define MCA_REG_TPM1_CH6_CNT0 0x0a5a ++#define MCA_REG_TPM1_CH6_CNT1 0x0a5b ++#define MCA_REG_TPM1_CH7_CFG 0x0a5c ++#define MCA_REG_TPM1_CH7_DUTY 0x0a5d ++#define MCA_REG_TPM1_CH7_CNT0 0x0a5e ++#define MCA_REG_TPM1_CH7_CNT1 0x0a5f ++#define MCA_REG_TPM2_CFG0 0x0a64 ++#define MCA_REG_TPM2_CFG1 0x0a65 ++#define MCA_REG_TPM2_CFG2 0x0a66 ++#define MCA_REG_TPM2_PRESCALER 0x0a67 ++/* ... */ ++#define MCA_REG_TPM2_FREQ_0 0x0a6c ++#define MCA_REG_TPM2_FREQ_1 0x0a6d ++#define MCA_REG_TPM2_FREQ_2 0x0a6e ++#define MCA_REG_TPM2_FREQ_3 0x0a6f ++#define MCA_REG_TPM2_CH0_CFG 0x0a70 ++#define MCA_REG_TPM2_CH0_DUTY 0x0a71 ++#define MCA_REG_TPM2_CH0_CNT0 0x0a72 ++#define MCA_REG_TPM2_CH0_CNT1 0x0a73 ++#define MCA_REG_TPM2_CH1_CFG 0x0a74 ++#define MCA_REG_TPM2_CH1_DUTY 0x0a75 ++#define MCA_REG_TPM2_CH1_CNT0 0x0a76 ++#define MCA_REG_TPM2_CH1_CNT1 0x0a77 ++#define MCA_REG_TPM2_CH2_CFG 0x0a78 ++#define MCA_REG_TPM2_CH2_DUTY 0x0a79 ++#define MCA_REG_TPM2_CH2_CNT0 0x0a7a ++#define MCA_REG_TPM2_CH2_CNT1 0x0a7b ++#define MCA_REG_TPM2_CH3_CFG 0x0a7c ++#define MCA_REG_TPM2_CH3_DUTY 0x0a7d ++#define MCA_REG_TPM2_CH3_CNT0 0x0a7e ++#define MCA_REG_TPM2_CH3_CNT1 0x0a7f ++#define MCA_REG_TPM2_CH4_CFG 0x0a80 ++#define MCA_REG_TPM2_CH4_DUTY 0x0a81 ++#define MCA_REG_TPM2_CH4_CNT0 0x0a82 ++#define MCA_REG_TPM2_CH4_CNT1 0x0a83 ++#define MCA_REG_TPM2_CH5_CFG 0x0a84 ++#define MCA_REG_TPM2_CH5_DUTY 0x0a85 ++#define MCA_REG_TPM2_CH5_CNT0 0x0a86 ++#define MCA_REG_TPM2_CH5_CNT1 0x0a87 ++#define MCA_REG_TPM2_CH6_CFG 0x0a88 ++#define MCA_REG_TPM2_CH6_DUTY 0x0a89 ++#define MCA_REG_TPM2_CH6_CNT0 0x0a8a ++#define MCA_REG_TPM2_CH6_CNT1 0x0a8b ++#define MCA_REG_TPM2_CH7_CFG 0x0a8c ++#define MCA_REG_TPM2_CH7_DUTY 0x0a8d ++#define MCA_REG_TPM2_CH7_CNT0 0x0a8e ++#define MCA_REG_TPM2_CH7_CNT1 0x0a8f ++ ++#define MCA_PWM_TPM_LEN (MCA_REG_TPM1_CFG0 - MCA_REG_TPM0_CFG0) ++#define MCA_PWM_CH_LEN (MCA_REG_TPM0_CH1_CFG - MCA_REG_TPM0_CH0_CFG) ++ ++/* ++ * MCA registers bitfields ++ */ ++ ++/* MCA_IRQ_STATUS_0 (addr=0x0020) */ ++#define MCA_RTC_ALARM BIT(0) ++#define MCA_RTC_1HZ BIT(1) ++#define MCA_WATCHDOG BIT(2) ++#define MCA_PWR_SLEEP BIT(3) ++#define MCA_PWR_OFF BIT(4) ++#define MCA_TAMPER0 BIT(5) ++#define MCA_TAMPER1 BIT(6) ++#define MCA_ADC BIT(7) ++ ++/* MCA_IRQ_STATUS_1 (addr=0x0021) */ ++#define MCA_GPIO_BANK_0 BIT(0) ++#define MCA_GPIO_BANK_1 BIT(1) ++#define MCA_GPIO_BANK_2 BIT(2) ++#define MCA_GPIO_BANK_3 BIT(3) ++#define MCA_GPIO_BANK_4 BIT(4) ++#define MCA_GPIO_BANK_5 BIT(5) ++#define MCA_GPIO_BANK_6 BIT(6) ++#define MCA_GPIO_BANK_7 BIT(7) ++ ++/* MCA_GPIO_IRQ_CFG_n (addr=0x0332... 0x0369) */ ++#define MCA_GPIO_IRQ_EN BIT(0) ++#define MCA_GPIO_IRQ_LEVEL BIT(1) ++#define MCA_GPIO_IRQ_EDGE_RISE BIT(2) ++#define MCA_GPIO_IRQ_EDGE_FALL BIT(3) ++#define MCA_GPIO_IRQ_EDGE_BOTH (MCA_GPIO_IRQ_EDGE_RISE | \ ++ MCA_GPIO_IRQ_EDGE_FALL) ++#define MCA_M_GPIO_IRQ_CFG (MCA_GPIO_IRQ_LEVEL | \ ++ MCA_GPIO_IRQ_EDGE_BOTH) ++#define MCA_GPIO_IRQ_CAPABLE BIT(7) ++ ++/* MCA_IRQ_MASK_0 (addr=0x0024) */ ++#define MCA_M_RTC_ALARM BIT(0) ++#define MCA_M_RTC_1HZ BIT(1) ++#define MCA_M_WATCHDOG BIT(2) ++#define MCA_M_PWR_SLEEP BIT(3) ++#define MCA_M_PWR_OFF BIT(4) ++#define MCA_M_TAMPER0 BIT(5) ++#define MCA_M_TAMPER1 BIT(6) ++#define MCA_M_ADC BIT(7) ++ ++/* MCA_IRQ_MASK_1 (addr=0x0025) */ ++#define MCA_M_GPIO_BANK_0 BIT(0) ++#define MCA_M_GPIO_BANK_1 BIT(1) ++#define MCA_M_GPIO_BANK_2 BIT(2) ++#define MCA_M_GPIO_BANK_3 BIT(3) ++#define MCA_M_GPIO_BANK_4 BIT(4) ++#define MCA_M_GPIO_BANK_5 BIT(5) ++#define MCA_M_GPIO_BANK_6 BIT(6) ++#define MCA_M_GPIO_BANK_7 BIT(7) ++ ++/* MCA_IRQ_MASK_2 */ ++#define MCA_M_TAMPER2 BIT(0) ++#define MCA_M_TAMPER3 BIT(1) ++#define MCA_M_UART BIT(2) ++ ++/* MCA_PWR_CTRL_0 (addr=0x0028) */ ++#define MCA_PWR_KEY_SLEEP_EN BIT(0) ++#define MCA_PWR_KEY_OFF_EN BIT(1) ++#define MCA_PWR_GUARD_EN BIT(2) ++#define MCA_PWR_GO_OFF BIT(3) ++#define MCA_PWR_GO_SUSPEND BIT(4) ++ ++/* MCA_CTRL_0 (addr=0x0031) */ ++#define MCA_RESET BIT(0) ++#define MCA_EXT32K_EN BIT(1) ++#define MCA_JMP_BL BIT(2) ++#define MCA_VREF_EN BIT(5) ++ ++/* MCA_TAMPERn_CFG0 (addr=0x0037 & 0x0046) */ ++#define MCA_TAMPER_DET_EN BIT(0) ++#define MCA_TAMPER_RECONF_EN BIT(1) ++#define MCA_TAMPER_IN_ACT_HIGH BIT(2) ++#define MCA_TAMPER_IRQ_EN BIT(3) ++#define MCA_TAMPER_OUT_EN BIT(4) ++#define MCA_TAMPER_OUT_ACT_HIGH BIT(5) ++#define MCA_TAMPER_PWROFF_EN BIT(6) ++#define MCA_TAMPER_CLR_EV_EN BIT(7) ++ ++/* MCA_TAMPERm_EVENT (addr=0x0045 & 0x0054) */ ++#define MCA_TAMPER_SIGNALED BIT(0) ++#define MCA_TAMPER_ACKED BIT(1) ++#define MCA_TAMPER_CLEAR 0 ++ ++/* MCA_WDT_CONTROL (addr=0x0201) */ ++#define MCA_WDT_ENABLE BIT(0) ++#define MCA_WDT_NOWAYOUT BIT(1) ++#define MCA_WDT_IRQNORESET BIT(2) ++#define MCA_WDT_PRETIMEOUT BIT(3) ++#define MCA_WDT_FULLRESET BIT(4) ++ ++/* MCA_WDT_TIMEOUT (addr=0x0202) */ ++#define MCA_WDT_TIMEOUT_MASK 0xFF ++ ++/* MCA_WDT_REFRESH_X (addr=0x0203..0x0206) */ ++#define MCA_WDT_REFRESH_X_MASK 0xFF ++ ++/* MCA_GPIO_NUM (addr=0x0302) */ ++#define MCA_GPIO_NUM_MASK 0x7F ++ ++/* MCA_ADC_CFG0 (addr=0x0404..0x040b) */ ++#define MCA_REG_ADC_CFG0_EN BIT(0) ++#define MCA_REG_ADC_CFG0_RUNS_LP BIT(1) ++#define MCA_REG_ADC_CFG0_MODE_0 BIT(2) ++#define MCA_REG_ADC_CFG0_MODE_1 BIT(3) ++#define MCA_REG_ADC_CFG0_AVG_0 BIT(4) ++#define MCA_REG_ADC_CFG0_AVG_1 BIT(5) ++#define MCA_REG_ADC_CFG0_IRQ_EN BIT(6) ++#define MCA_REG_ADC_CFG0_CAPABLE BIT(7) ++ ++#define MCA_REG_ADC_CFG0_AVG_MASK (MCA_REG_ADC_CFG0_AVG_0 |\ ++ MCA_REG_ADC_CFG0_AVG_1) ++#define MCA_REG_ADC_CFG0_AVG_SHIFT 4 ++ ++/* ADC CFG1 flags */ ++#define MCA_REG_ADC_CFG1_EN_RISING BIT(0) ++#define MCA_REG_ADC_CFG1_EN_FALLING BIT(1) ++#define MCA_REG_ADC_CFG1_LEVEL_EDGE BIT(2) ++#define MCA_REG_ADC_CFG1_INVERT_CMP BIT(3) ++#define MCA_REG_ADC_CFG1_CMP_OUT BIT(4) ++#define MCA_REG_ADC_CFG1_RISING_MASK BIT(5) ++#define MCA_REG_ADC_CFG1_FALLING_MASK BIT(6) ++#define MCA_REG_ADC_CFG1_IRQ BIT(7) ++#define MCA_REG_ADC_CFG1_IRQ_FLAGS_MASK 0xE0 ++#define MCA_REG_ADC_CFG1_RO_MASK 0xF0 ++ ++/* Global ADC CFG_0 flags */ ++#define MCA_REG_ADC_CFG_0_INT_VREF (1 << 0) ++ ++/* MCA_RTC_CONTROL (addr=0x0101) */ ++#define MCA_RTC_EN BIT(0) ++#define MCA_RTC_ALARM_EN BIT(1) ++#define MCA_RTC_1HZ_EN BIT(2) ++#define MCA_RTC_32KHZ_OUT_EN BIT(3) ++ ++/* MCA_RTC_COUNT_YEAR_L (addr=0x0103) */ ++/* MCA_RTC_ALARM_YEAR_L (addr=0x010A) */ ++#define MCA_RTC_YEAR_L_MASK 0xFF ++ ++/* MCA_RTC_COUNT_YEAR_H (addr=0x0104) */ ++/* MCA_RTC_ALARM_YEAR_H (addr=0x010B) */ ++#define MCA_RTC_YEAR_H_MASK 0xFF ++ ++/* MCA_RTC_COUNT_MONTH (addr=0x0105) */ ++/* MCA_RTC_ALARM_MONTH (addr=0x010C) */ ++#define MCA_RTC_MONTH_MASK 0x0F ++ ++/* MCA_RTC_COUNT_DAY (addr=0x0106) */ ++/* MCA_RTC_ALARM_DAY (addr=0x010D) */ ++#define MCA_RTC_DAY_MASK 0x1F ++ ++/* MCA_RTC_COUNT_HOUR (addr=0x0107) */ ++/* MCA_RTC_ALARM_HOUR (addr=0x010E) */ ++#define MCA_RTC_HOUR_MASK 0x1F ++ ++/* MCA_RTC_COUNT_MIN (addr=0x0108) */ ++/* MCA_RTC_ALARM_MIN (addr=0x010F) */ ++#define MCA_RTC_MIN_MASK 0x3F ++ ++/* MCA_RTC_COUNT_SEC (addr=0x0109) */ ++/* MCA_RTC_ALARM_SEC (addr=0x0110) */ ++#define MCA_RTC_SEC_MASK 0x3F ++ ++/* MCA_REG_TPMx_CH0_CFG (addr=0x0a10...) */ ++#define MCA_TPM_CH_EN BIT(0) ++#define MCA_TPM_CH_POL_HIGH BIT(1) ++ ++#endif /* MCA_COMMON_REGISTERS_H_ */ diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0004-imx6ul-Add-MCA-GPIO-support-for-the-ConnectCore-6UL-.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0004-imx6ul-Add-MCA-GPIO-support-for-the-ConnectCore-6UL-.patch new file mode 100644 index 000000000..f3896f554 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0004-imx6ul-Add-MCA-GPIO-support-for-the-ConnectCore-6UL-.patch @@ -0,0 +1,701 @@ +From a2b055852d963729002f48155d8bbee7e2858e0a Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#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); ++ diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0005-imx6ul-Add-MCA-IOMUX-support-to-the-ConnectCore-6UL-.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0005-imx6ul-Add-MCA-IOMUX-support-to-the-ConnectCore-6UL-.patch new file mode 100644 index 000000000..61f0d5167 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0005-imx6ul-Add-MCA-IOMUX-support-to-the-ConnectCore-6UL-.patch @@ -0,0 +1,33 @@ +From 7350cbc80f98eacb84a67049c2181f758ca3add6 Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +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 +--- + 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 { diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0006-imx6ul-Add-MCA-watchdog-support-for-the-ConnectCore-.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0006-imx6ul-Add-MCA-watchdog-support-for-the-ConnectCore-.patch new file mode 100644 index 000000000..abeae07e4 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0006-imx6ul-Add-MCA-watchdog-support-for-the-ConnectCore-.patch @@ -0,0 +1,467 @@ +From 1440e9a7f2812ebe7ac1d74e8a3c7515bcd67fa8 Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +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 +--- + .../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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#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); diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0007-imx6ul-Add-MCA-ADC-support-for-ConnectCore-6UL-SOM-a.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0007-imx6ul-Add-MCA-ADC-support-for-ConnectCore-6UL-SOM-a.patch new file mode 100644 index 000000000..bc8fb9413 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0007-imx6ul-Add-MCA-ADC-support-for-ConnectCore-6UL-SOM-a.patch @@ -0,0 +1,1066 @@ +From 01ad85bbe21de97cf560825d85523717e82e6979 Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +Date: Mon, 23 Apr 2018 11:40:45 +0200 +Subject: [PATCH] imx6ul: Add MCA ADC support for ConnectCore 6UL SOM and SBCs + +Synched with v4.14.78/master at: +3f8b03950b323db4ca89b1cdc1c2288f79facaa3 + +Signed-off-by: Alex Gonzalez +--- + arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts | 11 + + arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi | 5 + + arch/arm/configs/imx_v6_v7_defconfig | 1 + + drivers/iio/adc/Kconfig | 11 + + drivers/iio/adc/Makefile | 1 + + drivers/iio/adc/mca-adc.c | 957 ++++++++++++++++++++++++++++ + 6 files changed, 986 insertions(+) + create mode 100644 drivers/iio/adc/mca-adc.c + +diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts +index 5ad2c61276bc..acea9a56971e 100644 +--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts ++++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts +@@ -151,6 +151,17 @@ + regulator-max-microvolt = <1800000>; + }; + ++/* ++ * Enable MCA ADC channels on GPIO connector: ++ * - MCA_IO1 (channel 1) ++ * - MCA_IO3 (channel 3) ++ * Edit adc-ch-list to include the ADC channels that you want to enable. ++ */ ++&mca_adc { ++ digi,adc-ch-list = <1 3>; ++ digi,adc-vref = <3000000>; ++}; ++ + &pwm1 { + status = "okay"; + }; +diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi +index 848bf78dfceb..b5efc58d362d 100644 +--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi ++++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi +@@ -74,6 +74,11 @@ + digi,full-reset; + }; + ++ mca_adc: adc { ++ compatible = "digi,mca-cc6ul-adc"; ++ digi,adc-vref = <3000000>; ++ }; ++ + }; + + pfuze3000: pmic@8 { +diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig +index 9c693c7778a4..060c1de356ca 100644 +--- a/arch/arm/configs/imx_v6_v7_defconfig ++++ b/arch/arm/configs/imx_v6_v7_defconfig +@@ -385,6 +385,7 @@ CONFIG_COMMON_CLK_PWM=y + CONFIG_IIO=y + CONFIG_MMA8452=y + CONFIG_IMX7D_ADC=y ++CONFIG_MCA_ADC=y + CONFIG_VF610_ADC=y + CONFIG_SENSORS_ISL29018=y + CONFIG_MAG3110=y +diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig +index a52fea8749a9..5fe1cd036999 100644 +--- a/drivers/iio/adc/Kconfig ++++ b/drivers/iio/adc/Kconfig +@@ -468,6 +468,17 @@ config MAX1363 + To compile this driver as a module, choose M here: the module will be + called max1363. + ++config MCA_ADC ++ tristate "Digi ConnectCore SOMs Micro Controller Assist ADC" ++ 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 ADCs 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 "mca-adc". ++ + config MAX9611 + tristate "Maxim max9611/max9612 ADC driver" + depends on I2C +diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile +index a6e6a0b659e2..f132f8639002 100644 +--- a/drivers/iio/adc/Makefile ++++ b/drivers/iio/adc/Makefile +@@ -44,6 +44,7 @@ obj-$(CONFIG_MAX1027) += max1027.o + obj-$(CONFIG_MAX11100) += max11100.o + obj-$(CONFIG_MAX1118) += max1118.o + obj-$(CONFIG_MAX1363) += max1363.o ++obj-$(CONFIG_MCA_ADC) += mca-adc.o + obj-$(CONFIG_MAX9611) += max9611.o + obj-$(CONFIG_MCP320X) += mcp320x.o + obj-$(CONFIG_MCP3422) += mcp3422.o +diff --git a/drivers/iio/adc/mca-adc.c b/drivers/iio/adc/mca-adc.c +new file mode 100644 +index 000000000000..24dde433da20 +--- /dev/null ++++ b/drivers/iio/adc/mca-adc.c +@@ -0,0 +1,957 @@ ++/* mca-adc.c - ADC 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MCA_VREF_uV(v) ((u32)((v) * 1000 * 1000)) ++#define MCA_ADC_MIN_VREF MCA_VREF_uV(1.8) ++#define MCA_ADC_MAX_VREF MCA_VREF_uV(3.3) ++#define MCA_ADC_INT_VREF MCA_VREF_uV(1.2) ++#define MCA_ADC_DEF_VREF MCA_VREF_uV(3) ++ ++#define MCA_DRVNAME_ADC "mca-adc" ++ ++#ifdef CONFIG_OF ++enum mca_adc_type { ++ CC6UL_MCA_ADC, ++ CC8X_MCA_ADC, ++ IOEXP_MCA_ADC, ++}; ++ ++struct mca_adc_data { ++ enum mca_adc_type devtype; ++}; ++#endif ++ ++struct mca_adc { ++ struct device *dev; ++ struct regmap *regmap; ++ u32 vref; ++ int irq; ++}; ++ ++int mca_adc_read_raw(struct iio_dev *indio_dev, ++ struct iio_chan_spec const *channel, int *value, ++ int *shift, long mask) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ const int val_reg = MCA_REG_ADC_VAL_L_0 + channel->channel * 2; ++ u16 val; ++ int ret; ++ ++ switch (mask) { ++ case IIO_CHAN_INFO_RAW: ++ ret = regmap_bulk_read(adc->regmap, val_reg, &val, sizeof(val)); ++ if (ret < 0) { ++ dev_err(adc->dev, "Error reading ADC%d value (%d)\n", ++ channel->channel, ret); ++ return ret; ++ } ++ ++ dev_dbg(adc->dev, "ADC%d = 0x%04x\n", channel->channel, val); ++ *value = val; ++ return IIO_VAL_INT; ++ ++ case IIO_CHAN_INFO_SCALE: ++ *value = adc->vref / 1000; ++ *shift = channel->scan_type.realbits; ++ return IIO_VAL_FRACTIONAL_LOG2; ++ ++ default: ++ break; ++ } ++ ++ return -EINVAL; ++} ++ ++static const struct iio_info mca_adc_info = { ++ .read_raw = mca_adc_read_raw, ++}; ++ ++static u64 generate_comparator_event(struct mca_adc *adc, int ch) ++{ ++ ++ const u8 rising_mask = (MCA_REG_ADC_CFG1_EN_RISING | ++ MCA_REG_ADC_CFG1_RISING_MASK); ++ const u8 falling_mask = (MCA_REG_ADC_CFG1_EN_FALLING | ++ MCA_REG_ADC_CFG1_FALLING_MASK); ++ const u8 both_mask = rising_mask | falling_mask; ++ ++ u16 cfg1_base = ch < 32 ? MCA_REG_ADC_CFG1_0 : MCA_REG_ADC_CFG1_32; ++ u8 ch_offset = ch < 32 ? ch : ch - 32; ++ u8 cfg1; ++ enum iio_event_direction edge; ++ int ret; ++ ++ ret = regmap_raw_read(adc->regmap, cfg1_base + ch_offset, &cfg1, ++ sizeof(cfg1)); ++ if (ret) { ++ dev_err(adc->dev, "Error reading ADC%d CFG0 register (%d)\n", ++ ch, ret); ++ return ~0; ++ } ++ ++ if ((cfg1 & both_mask) == both_mask) ++ edge = IIO_EV_DIR_EITHER; ++ else if ((cfg1 & rising_mask) == rising_mask) ++ edge = IIO_EV_DIR_RISING; ++ else if ((cfg1 & falling_mask) == falling_mask) ++ edge = IIO_EV_DIR_FALLING; ++ else ++ return ~0; ++ ++ return IIO_EVENT_CODE(IIO_ACTIVITY, 0, IIO_NO_MOD, edge, ++ IIO_EV_TYPE_CHANGE, ch, 0, 0); ++} ++ ++static irqreturn_t comparator_irq_handler(int irq, void *private) ++{ ++ struct iio_dev *indio_dev = private; ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ret; ++ int i; ++ u8 adc_irqs[8]; ++ ++ ret = regmap_bulk_read(adc->regmap, MCA_REG_ADC_IRQ_0, ++ &adc_irqs, ARRAY_SIZE(adc_irqs)); ++ if (ret) { ++ dev_err(adc->dev, "Error reading ADC IRQ registers (%d)\n", ++ ret); ++ goto exit; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(adc_irqs); i++) { ++ const u8 adc_irq = adc_irqs[i]; ++ int j; ++ ++ for (j = 0; j < 8; j++) { ++ const u8 mask = 1 << j; ++ ++ if (mask & adc_irq) { ++ u8 ch = i * 8 + j; ++ u64 event = generate_comparator_event(adc, ch); ++ ++ if (event == ~0) ++ continue; ++ /* Notify the event */ ++ iio_push_event(indio_dev, event, ++ iio_get_time_ns(indio_dev)); ++ } ++ } ++ /* ACK the IRQs by writing a 1 in the flags */ ++ ret = regmap_write(adc->regmap, MCA_REG_ADC_IRQ_0 + i, adc_irq); ++ if (ret) { ++ dev_err(adc->dev, "Error ACKing IRQ %d (%d)\n", ++ i, ret); ++ continue; ++ } ++ } ++ ++exit: ++ return IRQ_HANDLED; ++} ++ ++static const struct iio_event_spec comparator_events[] = { ++ { ++ .type = IIO_EV_TYPE_THRESH, ++ .dir = IIO_EV_DIR_RISING, ++ .mask_separate = BIT(IIO_EV_INFO_VALUE) | ++ BIT(IIO_EV_INFO_ENABLE), ++ }, ++ { ++ .type = IIO_EV_TYPE_THRESH, ++ .dir = IIO_EV_DIR_FALLING, ++ .mask_separate = BIT(IIO_EV_INFO_VALUE) | ++ BIT(IIO_EV_INFO_ENABLE), ++ }, ++ { ++ .type = IIO_EV_TYPE_THRESH, ++ .dir = IIO_EV_DIR_EITHER, ++ .mask_separate = BIT(IIO_EV_INFO_VALUE) | ++ BIT(IIO_EV_INFO_ENABLE), ++ }, ++}; ++ ++static ssize_t cmp_thr_lo_read(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ char *buf) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ u16 threshold; ++ int ret; ++ ++ ret = regmap_bulk_read(adc->regmap, MCA_REG_ADC_THRESH_LO_L_0 + ch * 2, ++ &threshold, sizeof(threshold)); ++ if (ret) ++ dev_err(adc->dev, ++ "Error reading MCA_REG_ADC_THRESH_LO_L_%d (%d)\n", ++ ch, ret); ++ ++ return sprintf(buf, "%d\n", threshold); ++} ++ ++static ssize_t cmp_thr_lo_write(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ const char *buf, ++ size_t len) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ unsigned long threshold; ++ int ret; ++ ++ ret = kstrtoul(buf, 0, &threshold); ++ if (ret) { ++ dev_err(adc->dev, ++ "%s: error parsing input (%s)\n", ++ __func__, buf); ++ return ret; ++ } ++ ++ ret = regmap_bulk_write(adc->regmap, MCA_REG_ADC_THRESH_LO_L_0 + ch * 2, ++ (u16 *)&threshold, sizeof(u16)); ++ if (ret) ++ dev_err(adc->dev, ++ "Error writing MCA_REG_ADC_THRESH_LO_L_%d (%d)\n", ++ ch, ret); ++ ++ return len; ++} ++ ++static ssize_t cmp_thr_hi_read(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ char *buf) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ u16 threshold; ++ int ret; ++ ++ ret = regmap_bulk_read(adc->regmap, MCA_REG_ADC_THRESH_HI_L_0 + ch * 2, ++ &threshold, sizeof(threshold)); ++ if (ret) ++ dev_err(adc->dev, ++ "Error reading MCA_REG_ADC_THRESH_HI_L_%d (%d)\n", ++ ch, ret); ++ ++ return sprintf(buf, "%d\n", threshold); ++} ++ ++static ssize_t cmp_thr_hi_write(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ const char *buf, ++ size_t len) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ unsigned long threshold; ++ int ret; ++ ++ ret = kstrtoul(buf, 0, &threshold); ++ if (ret) { ++ dev_err(adc->dev, ++ "%s: error parsing input (%s)\n", ++ __func__, buf); ++ return ret; ++ } ++ ++ ret = regmap_bulk_write(adc->regmap, MCA_REG_ADC_THRESH_HI_L_0 + ch * 2, ++ (u16 *)&threshold, sizeof(u16)); ++ if (ret) ++ dev_err(adc->dev, ++ "Error writing MCA_REG_ADC_THRESH_HI_L_%d (%d)\n", ++ ch, ret); ++ ++ return len; ++} ++ ++static ssize_t cmp_sample_rate_read(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ char *buf) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ u16 ticks; ++ int ret; ++ ++ ret = regmap_bulk_read(adc->regmap, MCA_REG_ADC_TICKS_L_0 + ch * 2, ++ &ticks, sizeof(ticks)); ++ if (ret) ++ dev_err(adc->dev, "Error reading MCA_REG_ADC_TICKS_L_%d (%d)\n", ++ ch, ret); ++ ++ return sprintf(buf, "%d\n", ticks); ++} ++ ++static ssize_t cmp_sample_rate_write(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ const char *buf, ++ size_t len) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ unsigned long ticks; ++ int ret; ++ ++ ret = kstrtoul(buf, 0, &ticks); ++ if (ret) { ++ dev_err(adc->dev, ++ "%s: error parsing input (%s)\n", ++ __func__, buf); ++ return ret; ++ } ++ ++ ret = regmap_bulk_write(adc->regmap, MCA_REG_ADC_TICKS_L_0 + ch * 2, ++ (u16 *)&ticks, sizeof(u16)); ++ if (ret) ++ dev_err(adc->dev, "Error writing MCA_REG_ADC_TICKS_L_%d (%d)\n", ++ ch, ret); ++ ++ return len; ++} ++ ++static ssize_t cmp_irq_edge_read(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ char *buf) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ unsigned int cfg0; ++ int ret; ++ char const *edge_str; ++ ++ ret = regmap_read(adc->regmap, MCA_REG_ADC_CFG0_0 + ch, &cfg0); ++ if (ret) ++ dev_err(adc->dev, "Error reading MCA_REG_ADC_CFG0_%d (%d)\n", ++ ch, ret); ++ ++ if (cfg0 & MCA_REG_ADC_CFG0_IRQ_EN) { ++ unsigned int cfg1; ++ ++ ret = regmap_read(adc->regmap, MCA_REG_ADC_CFG1_0 + ch, &cfg1); ++ if (ret) ++ dev_err(adc->dev, ++ "Error reading MCA_REG_ADC_CFG1_%d (%d)\n", ++ ch, ret); ++ ++ if ((cfg1 & MCA_REG_ADC_CFG1_EN_RISING) && ++ (cfg1 & MCA_REG_ADC_CFG1_EN_FALLING)) ++ edge_str = "both"; ++ else if (cfg1 & MCA_REG_ADC_CFG1_EN_RISING) ++ edge_str = "rising"; ++ else if (cfg1 & MCA_REG_ADC_CFG1_EN_FALLING) ++ edge_str = "falling"; ++ else ++ edge_str = "none"; ++ } else { ++ edge_str = "none"; ++ } ++ ++ return sprintf(buf, "%s\n", edge_str); ++} ++ ++static ssize_t cmp_irq_edge_write(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ const char *buf, ++ size_t len) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ int ret; ++ unsigned int cfg1_val; ++ ++ if (!strncmp(buf, "both", strlen("both"))) ++ cfg1_val = MCA_REG_ADC_CFG1_EN_RISING | ++ MCA_REG_ADC_CFG1_EN_FALLING; ++ else if (!strncmp(buf, "rising", strlen("rising"))) ++ cfg1_val = MCA_REG_ADC_CFG1_EN_RISING; ++ else if (!strncmp(buf, "falling", strlen("falling"))) ++ cfg1_val = MCA_REG_ADC_CFG1_EN_FALLING; ++ else if (!strncmp(buf, "none", strlen("none"))) ++ cfg1_val = 0; ++ else ++ return -EINVAL; ++ ++ ret = regmap_update_bits(adc->regmap, MCA_REG_ADC_CFG1_0 + ch, ++ MCA_REG_ADC_CFG1_EN_RISING | ++ MCA_REG_ADC_CFG1_EN_FALLING, ++ cfg1_val); ++ if (ret) ++ dev_err(adc->dev, "Error writing MCA_REG_ADC_CFG1_%d (%d)\n", ++ ch, ret); ++ ++ /* Enable the IRQ if an edge is configured */ ++ ret = regmap_update_bits(adc->regmap, MCA_REG_ADC_CFG0_0 + ch, ++ MCA_REG_ADC_CFG0_IRQ_EN, ++ cfg1_val ? MCA_REG_ADC_CFG0_IRQ_EN : 0); ++ ++ if (ret) ++ dev_err(adc->dev, "Error writing MCA_REG_ADC_CFG0_%d (%d)\n", ++ ch, ret); ++ ++ return len; ++} ++ ++static ssize_t wakeup_read(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ char *buf) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ unsigned int cfg0; ++ int ret; ++ ++ ret = regmap_read(adc->regmap, MCA_REG_ADC_CFG0_0 + ch, &cfg0); ++ if (ret) ++ dev_err(adc->dev, "Error reading MCA_REG_ADC_CFG0_%d (%d)\n", ++ ch, ret); ++ ++ return sprintf(buf, "%s\n", ++ !!(cfg0 & MCA_REG_ADC_CFG0_RUNS_LP) ? ++ "enabled" : "disabled"); ++ ++} ++ ++static ssize_t wakeup_write(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ const char *buf, ++ size_t len) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ int ret; ++ ++ if (!strncmp(buf, "enabled", strlen("enabled"))) ++ ret = regmap_update_bits(adc->regmap, MCA_REG_ADC_CFG0_0 + ch, ++ MCA_REG_ADC_CFG0_RUNS_LP, ++ MCA_REG_ADC_CFG0_RUNS_LP); ++ else if (!strncmp(buf, "disabled", strlen("disabled"))) ++ ret = regmap_update_bits(adc->regmap, MCA_REG_ADC_CFG0_0 + ch, ++ MCA_REG_ADC_CFG0_RUNS_LP, ++ 0); ++ else ++ return -EINVAL; ++ ++ if (ret) ++ dev_err(adc->dev, "Error reading MCA_REG_ADC_CFG0_%d (%d)\n", ++ ch, ret); ++ ++ return len; ++} ++ ++static ssize_t averager_read(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ char *buf) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ u8 averager_samples; ++ unsigned int cfg0; ++ int ret; ++ ++ ret = regmap_read(adc->regmap, MCA_REG_ADC_CFG0_0 + ch, &cfg0); ++ if (ret) ++ dev_err(adc->dev, "Error reading MCA_REG_ADC_CFG0_%d (%d)\n", ++ ch, ret); ++ ++ averager_samples = (cfg0 & MCA_REG_ADC_CFG0_AVG_MASK) >> ++ MCA_REG_ADC_CFG0_AVG_SHIFT; ++ averager_samples = 1 << averager_samples; ++ ++ return sprintf(buf, "%d\n", averager_samples); ++} ++ ++static ssize_t averager_write(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ const char *buf, ++ size_t len) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ unsigned long averager_samples; ++ unsigned int cfg0_val; ++ int ret; ++ ++ ret = kstrtoul(buf, 0, &averager_samples); ++ if (ret) { ++ dev_err(adc->dev, ++ "%s: error parsing input (%s)\n", ++ __func__, buf); ++ return ret; ++ } ++ ++ switch (averager_samples) { ++ case 1: ++ cfg0_val = 0 << MCA_REG_ADC_CFG0_AVG_SHIFT; ++ break; ++ case 2: ++ cfg0_val = 1 << MCA_REG_ADC_CFG0_AVG_SHIFT; ++ break; ++ case 4: ++ cfg0_val = 2 << MCA_REG_ADC_CFG0_AVG_SHIFT; ++ break; ++ case 8: ++ cfg0_val = 3 << MCA_REG_ADC_CFG0_AVG_SHIFT; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ ret = regmap_update_bits(adc->regmap, MCA_REG_ADC_CFG0_0 + ch, ++ MCA_REG_ADC_CFG0_AVG_MASK, ++ cfg0_val); ++ if (ret) ++ dev_err(adc->dev, "Error reading MCA_REG_ADC_CFG0_%d (%d)\n", ++ ch, ret); ++ return len; ++} ++ ++static ssize_t cmp_out_read(struct iio_dev *indio_dev, ++ uintptr_t private, ++ struct iio_chan_spec const *iio_ch, ++ char *buf) ++{ ++ struct mca_adc *adc = iio_priv(indio_dev); ++ int ch = iio_ch->channel; ++ unsigned int cfg1; ++ int ret; ++ ++ ret = regmap_read(adc->regmap, MCA_REG_ADC_CFG1_0 + ch, &cfg1); ++ if (ret) ++ dev_err(adc->dev, "Error reading MCA_REG_ADC_CFG1_%d (%d)\n", ++ ch, ret); ++ ++ return sprintf(buf, "%d\n", !!(cfg1 & MCA_REG_ADC_CFG1_CMP_OUT)); ++} ++ ++static const struct iio_chan_spec_ext_info adc_comp_attr[] = { ++ { ++ .name = "cmp_thr_l", ++ .shared = IIO_SEPARATE, ++ .read = &cmp_thr_lo_read, ++ .write = &cmp_thr_lo_write, ++ }, ++ { ++ .name = "cmp_thr_h", ++ .shared = IIO_SEPARATE, ++ .read = &cmp_thr_hi_read, ++ .write = &cmp_thr_hi_write, ++ }, ++ { ++ .name = "cmp_rate", ++ .shared = IIO_SEPARATE, ++ .read = &cmp_sample_rate_read, ++ .write = &cmp_sample_rate_write, ++ }, ++ { ++ .name = "cmp_edge", ++ .shared = IIO_SEPARATE, ++ .read = &cmp_irq_edge_read, ++ .write = &cmp_irq_edge_write, ++ }, ++ { ++ .name = "cmp_out", ++ .shared = IIO_SEPARATE, ++ .read = &cmp_out_read, ++ .write = NULL, ++ }, ++ { ++ .name = "wakeup", ++ .shared = IIO_SEPARATE, ++ .read = &wakeup_read, ++ .write = &wakeup_write, ++ }, ++ { ++ .name = "averager", ++ .shared = IIO_SEPARATE, ++ .read = &averager_read, ++ .write = &averager_write, ++ }, ++ { ++ /* Centinel */ ++ .name = NULL, ++ }, ++ ++}; ++ ++static void init_adc_channels(struct platform_device *pdev, ++ struct regmap *regmap, ++ int gpio_base, struct iio_chan_spec *iio_ch, ++ u8 *ch_list, u8 cnt, bool is_comparator) ++{ ++ struct iio_chan_spec *chan; ++ u8 i; ++ ++ for (i = 0, chan = iio_ch; i < cnt; i++, chan++) { ++ unsigned int cfg0_mask = MCA_REG_ADC_CFG0_EN; ++ u8 ch = ch_list[i]; ++ int ret; ++ ++ /* ++ * Request the corresponding MCA GPIO so it can not be ++ * used as GPIO by user space ++ */ ++ if (gpio_base >= 0) { ++ ret = devm_gpio_request(&pdev->dev, gpio_base + ch, ++ "ADC"); ++ if (ret) { ++ dev_warn(&pdev->dev, ++ "Error requesting GPIO %d. Cannot use ADC%d (%d)\n", ++ gpio_base + ch, ch, ret); ++ continue; ++ } ++ } ++ ++ if (is_comparator) ++ cfg0_mask |= MCA_REG_ADC_CFG0_MODE_1; ++ ++ /* Enable the ADC channel as a comparator */ ++ ret = regmap_update_bits(regmap, MCA_REG_ADC_CFG0_0 + ch, ++ cfg0_mask, cfg0_mask); ++ if (ret) ++ dev_err(&pdev->dev, ++ "Error writing MCA_REG_ADC_CFG0_%d (%d)\n", ++ ch, ret); ++ ++ chan->type = IIO_VOLTAGE; ++ chan->indexed = 1; ++ chan->channel = ch; ++ chan->scan_index = i; ++ chan->scan_type.sign = 'u'; ++ chan->scan_type.realbits = 12; ++ chan->scan_type.storagebits = 16; ++ chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); ++ chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); ++ if (is_comparator) { ++ chan->event_spec = comparator_events; ++ chan->num_event_specs = ARRAY_SIZE(comparator_events); ++ chan->ext_info = adc_comp_attr; ++ } ++ } ++} ++ ++static u32 get_vref(struct device *mca_dev, struct regmap *regmap, ++ struct device_node *np) ++{ ++ int ret; ++ u32 vref = MCA_ADC_DEF_VREF; ++ ++ if (of_property_read_bool(np, "digi,internal-vref")) { ++ ret = regmap_write(regmap, MCA_REG_ADC_CFG_0, ++ MCA_REG_ADC_CFG_0_INT_VREF); ++ if (ret) ++ dev_err(mca_dev, ++ "Error configuring ADC internal VREF (%d)\n", ++ ret); ++ else ++ vref = MCA_ADC_INT_VREF; ++ } else { ++ ret = of_property_read_u32(np, "digi,adc-vref", &vref); ++ if (ret || vref < MCA_ADC_MIN_VREF || vref > MCA_ADC_MAX_VREF) ++ dev_warn(mca_dev, "adc-vref %s, using default %u uV\n", ++ ret ? "not provided" : "out of range", vref); ++ } ++ ++ return vref; ++} ++ ++static int mca_adc_probe(struct platform_device *pdev) ++{ ++ struct mca_drv *mca = dev_get_drvdata(pdev->dev.parent); ++ struct device *mca_dev = mca->dev; ++ int gpio_base = mca->gpio_base; ++ struct regmap *regmap = mca->regmap; ++ struct mca_adc *mca_adc; ++ struct iio_dev *indio_dev; ++ struct device_node *np; ++ struct iio_chan_spec *iio_ch_list; ++ u8 adc_ch_list[MCA_MAX_IOS]; ++ u8 num_adcs = 0; ++ u8 num_comps = 0; ++ struct property *prop; ++ const __be32 *cur; ++ u32 ch, cfg; ++ int ret = 0; ++ u8 adc_comp_ch_list[MCA_MAX_IOS]; ++ ++ if (!mca_dev || !mca_dev->parent || !mca_dev->parent->of_node) ++ return -EPROBE_DEFER; ++ ++ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*mca_adc)); ++ if (!indio_dev) { ++ dev_err(&pdev->dev, "Failed to allocate indio_dev device\n"); ++ return -ENOMEM; ++ } ++ mca_adc = iio_priv(indio_dev); ++ mca_adc->regmap = regmap; ++ mca_adc->dev = mca_dev; ++ mca_adc->irq = -1; ++ ++ /* Find entry in device-tree */ ++ if (mca_dev->of_node) { ++ const struct mca_adc_data *devdata = ++ of_device_get_match_data(&pdev->dev); ++ const char * compatible = pdev->dev.driver-> ++ of_match_table[devdata->devtype].compatible; ++ ++ /* Return if mca_adc 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; ++ ++ mca_adc->vref = get_vref(mca_dev, regmap, np); ++ ++ of_property_for_each_u32(np, "digi,adc-ch-list", ++ prop, cur, ch) { ++ if (ch >= MCA_MAX_IOS) ++ continue; ++ ++ /* ++ * Verify that the requested IOs are ADC capable and ++ * enable the channel for ADC operation ++ */ ++ ret = regmap_read(regmap, MCA_REG_ADC_CFG0_0 + ch, ++ &cfg); ++ if (ret) { ++ dev_err(mca_dev, ++ "Error reading ADC%d CFG register (%d)\n", ++ ch, ret); ++ goto error_dev_free; ++ } ++ ++ /* Remove the channel from the list if not capable */ ++ if (!(cfg & MCA_REG_ADC_CFG0_CAPABLE)) { ++ dev_warn(mca_dev, ++ "Removing ADC%d, IO not ADC capable\n", ++ ch); ++ continue; ++ } ++ ++ adc_ch_list[num_adcs] = (u8)ch; ++ num_adcs++; ++ } ++ ++ of_property_for_each_u32(np, "digi,comparator-ch-list", ++ prop, cur, ch) { ++ u32 cfg; ++ ++ if (ch >= MCA_MAX_IOS) ++ continue; ++ ++ /* ++ * Verify that the requested IOs are ADC capable ++ */ ++ ret = regmap_read(regmap, MCA_REG_ADC_CFG0_0 + ch, ++ &cfg); ++ if (ret) { ++ dev_err(&pdev->dev, ++ "Failed read ADC%d CFG register (%d)\n", ++ ch, ret); ++ continue; ++ } ++ ++ /* Remove the channel from the list if not capable */ ++ if (!(cfg & MCA_REG_ADC_CFG0_CAPABLE)) { ++ dev_warn(&pdev->dev, ++ "Removing ADC%d, IO no ADC capable\n", ++ ch); ++ continue; ++ } ++ ++ adc_comp_ch_list[num_comps] = (u8)ch; ++ num_comps++; ++ } ++ } ++ ++ if (!num_adcs && !num_comps) ++ goto error_dev_free; ++ ++ indio_dev->dev.parent = &pdev->dev; ++ indio_dev->name = dev_name(&pdev->dev); ++ indio_dev->modes = INDIO_DIRECT_MODE; ++ indio_dev->info = &mca_adc_info; ++ indio_dev->num_channels = num_adcs + num_comps; ++ iio_ch_list = devm_kzalloc(&pdev->dev, ++ indio_dev->num_channels * ++ sizeof(indio_dev->channels[0]), ++ GFP_KERNEL); ++ if (!iio_ch_list) ++ goto error_dev_free; ++ ++ indio_dev->channels = iio_ch_list; ++ ++ init_adc_channels(pdev, regmap, gpio_base, &iio_ch_list[0], adc_ch_list, ++ num_adcs, false); ++ init_adc_channels(pdev, regmap, gpio_base, &iio_ch_list[num_adcs], ++ adc_comp_ch_list, num_comps, true); ++ ++ if (num_comps) { ++ mca_adc->irq = platform_get_irq_byname(pdev, "ADC"); ++ if (mca_adc->irq) { ++ ret = devm_request_threaded_irq(&pdev->dev, ++ mca_adc->irq, ++ NULL, ++ &comparator_irq_handler, ++ IRQF_TRIGGER_LOW | ++ IRQF_ONESHOT, ++ dev_name(&pdev->dev), ++ indio_dev); ++ if (ret) { ++ dev_err(&pdev->dev, ++ "Requested Comparator IRQ (%d).\n", ++ mca_adc->irq); ++ goto error_free_ch; ++ } ++ } ++ } ++ ++ ret = iio_device_register(indio_dev); ++ if (ret) { ++ dev_err(&pdev->dev, "Failed to register mca adc indio_dev device\n"); ++ goto error_free_ch; ++ } ++ ++ platform_set_drvdata(pdev, indio_dev); ++ ++ pr_info("ADC driver for MCA\n"); ++ ++ return 0; ++ ++error_free_ch: ++ devm_kfree(&pdev->dev, (void *)iio_ch_list); ++ ++error_dev_free: ++ while (num_adcs && gpio_base >= 0) { ++ devm_gpio_free(&pdev->dev, ++ gpio_base + adc_ch_list[num_adcs - 1]); ++ num_adcs--; ++ } ++ devm_iio_device_free(&pdev->dev, indio_dev); ++ ++ return ret; ++} ++ ++static int mca_adc_remove(struct platform_device *pdev) ++{ ++ struct mca_drv *mca = dev_get_drvdata(pdev->dev.parent); ++ struct iio_dev *indio_dev = platform_get_drvdata(pdev); ++ struct iio_chan_spec *chan; ++ int i; ++ struct mca_adc *adc = iio_priv(indio_dev); ++ ++ if (!mca) { ++ dev_err(&pdev->dev, "Received a NULL pointer in mca\n"); ++ return -EINVAL; ++ } ++ ++ /* Release allocated resources */ ++ if (mca->gpio_base >= 0) { ++ for (i = 0, chan = (struct iio_chan_spec *)indio_dev->channels; ++ i < indio_dev->num_channels; ++ i++) { ++ devm_gpio_free(&pdev->dev, mca->gpio_base + chan->channel); ++ } ++ } ++ ++ if (adc->irq != -1) ++ devm_free_irq(&pdev->dev, adc->irq, indio_dev); ++ ++ devm_kfree(&pdev->dev, (void *)indio_dev->channels); ++ iio_device_unregister(indio_dev); ++ devm_iio_device_free(&pdev->dev, indio_dev); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_OF ++static struct mca_adc_data mca_adc_devdata[] = { ++ [CC6UL_MCA_ADC] = { ++ .devtype = CC6UL_MCA_ADC, ++ }, ++ [CC8X_MCA_ADC] = { ++ .devtype = CC8X_MCA_ADC, ++ }, ++ [IOEXP_MCA_ADC] = { ++ .devtype = IOEXP_MCA_ADC, ++ }, ++}; ++ ++static const struct of_device_id mca_adc_dt_ids[] = { ++ { .compatible = "digi,mca-cc6ul-adc", ++ .data = &mca_adc_devdata[CC6UL_MCA_ADC]}, ++ { .compatible = "digi,mca-cc8x-adc", ++ .data = &mca_adc_devdata[CC8X_MCA_ADC]}, ++ { .compatible = "digi,mca-ioexp-adc", ++ .data = &mca_adc_devdata[IOEXP_MCA_ADC]}, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, mca_adc_dt_ids); ++#endif ++ ++static struct platform_driver mca_adc_driver = { ++ .probe = mca_adc_probe, ++ .remove = mca_adc_remove, ++ .driver = { ++ .name = MCA_DRVNAME_ADC, ++ .owner = THIS_MODULE, ++#ifdef CONFIG_OF ++ .of_match_table = mca_adc_dt_ids, ++#endif ++ }, ++}; ++ ++static int __init mca_adc_init(void) ++{ ++ return platform_driver_register(&mca_adc_driver); ++} ++module_init(mca_adc_init); ++ ++static void __exit mca_adc_exit(void) ++{ ++ platform_driver_unregister(&mca_adc_driver); ++} ++module_exit(mca_adc_exit); ++ ++/* Module information */ ++MODULE_AUTHOR("Digi International Inc."); ++MODULE_DESCRIPTION("ADC device driver for MCA of ConnectCore Modules"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:" MCA_DRVNAME_ADC); diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0008-imx6ul-Add-MCA-tamper-support-for-ConnectCore-6UL-SO.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0008-imx6ul-Add-MCA-tamper-support-for-ConnectCore-6UL-SO.patch new file mode 100644 index 000000000..f3ed23fac --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0008-imx6ul-Add-MCA-tamper-support-for-ConnectCore-6UL-SO.patch @@ -0,0 +1,937 @@ +From bfc63af24c96ca3206e3f8e6b0fd437944e6519e Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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); diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0009-imx6ul-Add-MCA-UART-support-for-ConnectCore-6UL-SOM-.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0009-imx6ul-Add-MCA-UART-support-for-ConnectCore-6UL-SOM-.patch new file mode 100644 index 000000000..f7231ada6 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0009-imx6ul-Add-MCA-UART-support-for-ConnectCore-6UL-SOM-.patch @@ -0,0 +1,1097 @@ +From d7195e862d6d57351d180711b7ae9194e18bdcc3 Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +Date: Mon, 23 Apr 2018 11:43:44 +0200 +Subject: [PATCH] imx6ul: Add MCA UART support for ConnectCore 6UL SOM and SBCs + +Synched with v4.14.78/master at: +3f8b03950b323db4ca89b1cdc1c2288f79facaa3 + +Signed-off-by: Alex Gonzalez +--- + arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts | 14 + + arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi | 4 + + arch/arm/configs/imx_v6_v7_defconfig | 1 + + drivers/tty/serial/Kconfig | 10 + + drivers/tty/serial/Makefile | 1 + + drivers/tty/serial/mca-cc6ul-uart.c | 983 ++++++++++++++++++++++++++++ + 6 files changed, 1013 insertions(+) + create mode 100644 drivers/tty/serial/mca-cc6ul-uart.c + +diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts +index 7cbc14d56680..afc87b34f441 100644 +--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts ++++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts +@@ -169,6 +169,19 @@ + digi,tamper-if-list = <0 1 2 3>; + }; + ++/* ++ * Enable MCA UART: ++ * - MCA_IO1 (TX) ++ * - MCA_IO2 (RX) ++ */ ++&mca_uart { ++ status = "okay"; ++ ++ /* Edit to nable CTS and/or RTS in any MCA GPIO-capable line */ ++ rts-pin = <3>; ++ cts-pin = <5>; ++}; ++ + &pwm1 { + status = "okay"; + }; +@@ -445,3 +458,4 @@ + >; + }; + }; ++ +diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi +index a20fb5ffc98b..c721320a4117 100644 +--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi ++++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsom.dtsi +@@ -83,6 +83,10 @@ + compatible = "digi,mca-cc6ul-tamper"; + }; + ++ mca_uart: uart { ++ compatible = "digi,mca-cc6ul-uart"; ++ status = "disabled"; ++ }; + }; + + pfuze3000: pmic@8 { +diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig +index 188fb4309851..0a07b13ce593 100644 +--- a/arch/arm/configs/imx_v6_v7_defconfig ++++ b/arch/arm/configs/imx_v6_v7_defconfig +@@ -190,6 +190,7 @@ CONFIG_SERIO_SERPORT=m + # CONFIG_LEGACY_PTYS is not set + CONFIG_SERIAL_IMX=y + CONFIG_SERIAL_IMX_CONSOLE=y ++CONFIG_SERIAL_MCA_CC6UL=y + CONFIG_SERIAL_FSL_LPUART=y + CONFIG_SERIAL_FSL_LPUART_CONSOLE=y + CONFIG_SERIAL_DEV_BUS=y +diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig +index 32886c304641..9a113b96ad17 100644 +--- a/drivers/tty/serial/Kconfig ++++ b/drivers/tty/serial/Kconfig +@@ -745,6 +745,16 @@ config SERIAL_CORE_CONSOLE + config CONSOLE_POLL + bool + ++config SERIAL_MCA_CC6UL ++ tristate "Digi ConnectCore 6UL Micro Controller Assist UART" ++ select MFD_MCA_CC6UL ++ help ++ If you say Y here you will get support for the UART in the Micro ++ Controller Assist of Digi ConnectCore 6UL system-on-module. ++ ++ This driver can also be built as a module, if so, the module ++ will be called "mca-cc6ul-uart". ++ + config SERIAL_MCF + bool "Coldfire serial support" + depends on COLDFIRE +diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile +index daac675612df..00dadeaaf331 100644 +--- a/drivers/tty/serial/Makefile ++++ b/drivers/tty/serial/Makefile +@@ -85,6 +85,7 @@ obj-$(CONFIG_SERIAL_CONEXANT_DIGICOLOR) += digicolor-usart.o + obj-$(CONFIG_SERIAL_MEN_Z135) += men_z135_uart.o + obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o + obj-$(CONFIG_SERIAL_STM32) += stm32-usart.o ++obj-$(CONFIG_SERIAL_MCA_CC6UL) += mca-cc6ul-uart.o + obj-$(CONFIG_SERIAL_MVEBU_UART) += mvebu-uart.o + obj-$(CONFIG_SERIAL_PIC32) += pic32_uart.o + obj-$(CONFIG_SERIAL_MPS2_UART) += mps2-uart.o +diff --git a/drivers/tty/serial/mca-cc6ul-uart.c b/drivers/tty/serial/mca-cc6ul-uart.c +new file mode 100644 +index 000000000000..b7bd0b2a8149 +--- /dev/null ++++ b/drivers/tty/serial/mca-cc6ul-uart.c +@@ -0,0 +1,983 @@ ++/* mca-cc6ul-uart.c - UART driver for MCA devices. ++ * Based on sc16is7xx.c, by Jon Ringle ++ * ++ * Copyright (C) 2017 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MCA_UART_DEV_NAME "ttyMCA" ++#define MCA_UART_DEFAULT_BRATE 9600 ++#define MCA_UART_DEFAULT_BAUD_REG MCA_REG_UART_BAUD_9600 ++#define MCA_UART_MIN_BAUD 1200 ++#define MCA_UART_MAX_BAUD 230400 ++#define MCA_UART_RX_FIFO_SIZE 128 ++#define MCA_UART_TX_FIFO_SIZE 128 ++#define MCA_UART_CLK 24000000 ++#define MCA_UART_MIN_FW_VERSION MCA_MAKE_FW_VER(1, 1) ++ ++#define MCA_UART_HAS_RTS BIT(0) ++#define MCA_UART_HAS_CTS BIT(1) ++ ++#define to_mca_uart(p, e) (container_of((p), struct mca_uart, e)) ++ ++enum { ++ WORK_STOP_RX = BIT(0), ++ WORK_STOP_TX = BIT(1), ++ WORK_SET_RTS = BIT(2), ++ WORK_CLEAR_RTS = BIT(3), ++}; ++ ++struct mca_uart { ++ struct mca_drv *mca; ++ struct device *dev; ++ struct uart_driver uart; ++ struct uart_port port; ++ struct mutex mutex; ++ unsigned int pending_work; ++ struct work_struct tx_work; ++ struct work_struct delayed_work; ++ unsigned int has_rtscts; ++ int rts_pin; ++ int cts_pin; ++ bool enable_power_on; ++}; ++ ++static void mca_uart_stop_tx(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ /* Work is queued since we can't access I2C regmap in atomic context */ ++ mca_uart->pending_work |= WORK_STOP_TX; ++ if (!work_pending(&mca_uart->delayed_work)) ++ schedule_work(&mca_uart->delayed_work); ++} ++ ++static void mca_uart_stop_rx(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ /* Work is queued since we can't access I2C regmap in atomic context */ ++ mca_uart->pending_work |= WORK_STOP_RX; ++ if (!work_pending(&mca_uart->delayed_work)) ++ schedule_work(&mca_uart->delayed_work); ++} ++ ++static void mca_uart_start_tx(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = to_mca_uart(port, port); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ if (!work_pending(&mca_uart->tx_work)) ++ schedule_work(&mca_uart->tx_work); ++} ++ ++static unsigned int mca_uart_tx_empty(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = to_mca_uart(port, port); ++ struct regmap *regmap = mca_uart->mca->regmap; ++ unsigned int txlvl; ++ int ret; ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ ret = regmap_read(regmap, MCA_REG_UART_TXLVL, &txlvl); ++ if (ret) { ++ dev_err(mca_uart->dev, "Failed to read MCA_REG_UART_TXLVL\n"); ++ /* This is the behavior if not implemented */ ++ return TIOCSER_TEMT; ++ } ++ ++ return (txlvl == MCA_UART_TX_FIFO_SIZE) ? TIOCSER_TEMT : 0; ++} ++ ++static unsigned int mca_uart_get_mctrl(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = to_mca_uart(port, port); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ /* ++ * DCD and DSR are not wired and CTS/RTS is handled automatically ++ * so just indicate DSR and CAR asserted. Also regmap cannot be called ++ * from atomic context, so reading the status of the lines here is not ++ * possible. ++ */ ++ return TIOCM_DSR | TIOCM_CAR; ++} ++ ++static void mca_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) ++{ ++ struct mca_uart *mca_uart = to_mca_uart(port, port); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ /* ++ * Regmap cannot be called from atomic context, so this would require a ++ * work queue to set/clear RTS. However, that line is handled ++ * automatically by the hardware when using flow control, and the ++ * get_mctrl for reading CTS and RTS cannot be implemented for the same ++ * reason. If RTS/CTS are used for something different that hardware ++ * flow control, perhaps they should be declared as GPIOs. ++ */ ++} ++ ++static void mca_uart_break_ctl(struct uart_port *port, int break_state) ++{ ++ struct mca_uart *mca_uart = to_mca_uart(port, port); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ dev_warn(mca_uart->dev, "BREAK condition not supported\n"); ++} ++ ++static void mca_uart_set_termios(struct uart_port *port, ++ struct ktermios *termios, ++ struct ktermios *old) ++{ ++ struct mca_uart *mca_uart = to_mca_uart(port, port); ++ struct regmap *regmap = mca_uart->mca->regmap; ++ unsigned int cfg1 = 0; ++ unsigned int baudrate; ++ unsigned int baud_reg_val; ++ int ret; ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ /* Mask unsupported termios capabilities */ ++ if (!mca_uart->has_rtscts) ++ termios->c_cflag &= ~CRTSCTS; ++ ++ termios->c_iflag &= ~(IXON | IXOFF | IXANY | CMSPAR | CSIZE); ++ ++ /* Only 8-bit size supported */ ++ termios->c_cflag |= CS8; ++ ++ if (termios->c_cflag & CSTOPB) ++ cfg1 |= MCA_REG_UART_CFG1_TWO_STOPBITS; ++ if (termios->c_cflag & PARENB) ++ cfg1 |= MCA_REG_UART_CFG1_PARITY_EN; ++ if (termios->c_cflag & PARODD) ++ cfg1 |= MCA_REG_UART_CFG1_PARITY_ODD; ++ if (termios->c_cflag & CRTSCTS) { ++ if (mca_uart->has_rtscts & MCA_UART_HAS_CTS) { ++ cfg1 |= MCA_REG_UART_CFG1_CTS_EN; ++ port->status |= UPSTAT_AUTOCTS; ++ } ++ if (mca_uart->has_rtscts & MCA_UART_HAS_RTS) { ++ cfg1 |= MCA_REG_UART_CFG1_RTS_EN; ++ port->status |= UPSTAT_AUTORTS; ++ } ++ } else { ++ port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); ++ } ++ ++ ret = regmap_write(regmap, MCA_REG_UART_CFG1, cfg1); ++ if (ret) { ++ dev_err(mca_uart->dev, "Failed to write MCA_REG_UART_CFG1\n"); ++ return; ++ } ++ ++ baudrate = uart_get_baud_rate(port, termios, old, MCA_UART_MIN_BAUD, ++ MCA_UART_MAX_BAUD); ++ uart_update_timeout(port, termios->c_cflag, baudrate); ++ ++ switch (baudrate) { ++ case 1200: ++ baud_reg_val = MCA_REG_UART_BAUD_1200; ++ break; ++ case 2400: ++ baud_reg_val = MCA_REG_UART_BAUD_2400; ++ break; ++ case 4800: ++ baud_reg_val = MCA_REG_UART_BAUD_4800; ++ break; ++ case 9600: ++ baud_reg_val = MCA_REG_UART_BAUD_9600; ++ break; ++ case 19200: ++ baud_reg_val = MCA_REG_UART_BAUD_19200; ++ break; ++ case 38400: ++ baud_reg_val = MCA_REG_UART_BAUD_38400; ++ break; ++ case 57600: ++ baud_reg_val = MCA_REG_UART_BAUD_57600; ++ break; ++ case 115200: ++ baud_reg_val = MCA_REG_UART_BAUD_115200; ++ break; ++ case 230400: ++ baud_reg_val = MCA_REG_UART_BAUD_230400; ++ break; ++ default: ++ dev_warn(mca_uart->dev, ++ "Baud rate %d not supported, using default %d\n", ++ baudrate, MCA_UART_DEFAULT_BRATE); ++ baud_reg_val = MCA_UART_DEFAULT_BAUD_REG; ++ break; ++ } ++ ++ ret = regmap_write(regmap, MCA_REG_UART_BAUD, baud_reg_val); ++ if (ret) { ++ dev_err(mca_uart->dev, "Failed to write MCA_REG_UART_BAUD\n"); ++ return; ++ } ++} ++ ++static int mca_uart_startup(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ struct regmap *regmap = mca_uart->mca->regmap; ++ int ret; ++ unsigned int cfg_mask; ++ unsigned int ier_mask; ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ /* Reset RX and TX FIFOs and enable TX and RX */ ++ cfg_mask = MCA_REG_UART_CFG0_CTX | MCA_REG_UART_CFG0_CRX | ++ MCA_REG_UART_CFG0_TXEN | MCA_REG_UART_CFG0_RXEN; ++ ++ ret = regmap_update_bits(regmap, MCA_REG_UART_CFG0, cfg_mask, cfg_mask); ++ if (ret) { ++ dev_err(mca_uart->dev, "Failed to read MCA_REG_UART_CFG0\n"); ++ return ret; ++ } ++ ++ ier_mask = MCA_REG_UART_IER_THR | MCA_REG_UART_IER_RHR | ++ MCA_REG_UART_IER_RLSE; ++ ret = regmap_update_bits(regmap, MCA_REG_UART_IER, ier_mask, ier_mask); ++ if (ret) ++ dev_err(mca_uart->dev, "Failed to read MCA_REG_UART_IER\n"); ++ ++ return ret; ++} ++ ++static void mca_uart_shutdown(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ struct regmap *regmap = mca_uart->mca->regmap; ++ int ret; ++ unsigned int cfg_mask; ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ /* Reset RX and TX FIFOs and disable TX and RX */ ++ cfg_mask = MCA_REG_UART_CFG0_CTX | MCA_REG_UART_CFG0_CRX | ++ MCA_REG_UART_CFG0_TXEN | MCA_REG_UART_CFG0_RXEN; ++ ++ ret = regmap_update_bits(regmap, MCA_REG_UART_CFG0, cfg_mask, ++ MCA_REG_UART_CFG0_CTX | MCA_REG_UART_CFG0_CRX); ++ if (ret) ++ dev_err(mca_uart->dev, "Failed to read MCA_REG_UART_CFG0\n"); ++ ++ /* Disable all IRQs */ ++ ret = regmap_write(regmap, MCA_REG_UART_IER, 0); ++ if (ret) ++ dev_err(mca_uart->dev, "Failed to write MCA_REG_UART_IER\n"); ++ cancel_work_sync(&mca_uart->tx_work); ++ cancel_work_sync(&mca_uart->delayed_work); ++} ++ ++static const char *mca_uart_type(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ return "MCA UART"; ++} ++ ++static int mca_uart_request_port(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ /* Do nothing */ ++ return 0; ++} ++ ++static void mca_uart_config_port(struct uart_port *port, int flags) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ if (flags & UART_CONFIG_TYPE) ++ port->type = PORT_LPUART; ++} ++ ++static int mca_uart_verify_port(struct uart_port *port, ++ struct serial_struct *s) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ if ((s->type != PORT_UNKNOWN) && (s->type != PORT_LPUART)) ++ return -EINVAL; ++ if (s->irq != port->irq) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static void mca_uart_release_port(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ /* Do nothing */ ++} ++ ++static void mca_uart_throttle(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ struct regmap *regmap = mca_uart->mca->regmap; ++ int ret; ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ mutex_lock(&mca_uart->mutex); ++ ret = regmap_update_bits(regmap, MCA_REG_UART_CFG1, ++ MCA_REG_UART_CFG1_THROTTLE, ++ MCA_REG_UART_CFG1_THROTTLE); ++ if (ret) ++ dev_err(mca_uart->dev, "Failed to write MCA_REG_UART_CFG1\n"); ++ mutex_unlock(&mca_uart->mutex); ++} ++ ++static void mca_uart_unthrottle(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ struct regmap *regmap = mca_uart->mca->regmap; ++ int ret; ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ mutex_lock(&mca_uart->mutex); ++ ret = regmap_update_bits(regmap, MCA_REG_UART_CFG1, ++ MCA_REG_UART_CFG1_THROTTLE, 0); ++ if (ret) ++ dev_err(mca_uart->dev, "Failed to write MCA_REG_UART_CFG1\n"); ++ mutex_unlock(&mca_uart->mutex); ++} ++ ++static const struct uart_ops mca_uart_ops = { ++ .tx_empty = mca_uart_tx_empty, ++ .set_mctrl = mca_uart_set_mctrl, ++ .get_mctrl = mca_uart_get_mctrl, ++ .stop_tx = mca_uart_stop_tx, ++ .start_tx = mca_uart_start_tx, ++ .stop_rx = mca_uart_stop_rx, ++ .break_ctl = mca_uart_break_ctl, ++ .startup = mca_uart_startup, ++ .shutdown = mca_uart_shutdown, ++ .set_termios = mca_uart_set_termios, ++ .type = mca_uart_type, ++ .request_port = mca_uart_request_port, ++ .release_port = mca_uart_release_port, ++ .config_port = mca_uart_config_port, ++ .verify_port = mca_uart_verify_port, ++ .throttle = mca_uart_throttle, ++ .unthrottle = mca_uart_unthrottle, ++ .pm = NULL, ++}; ++ ++static void mca_uart_handle_tx(struct uart_port *port) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(port->dev); ++ struct regmap *regmap = mca_uart->mca->regmap; ++ struct circ_buf *xmit = &port->state->xmit; ++ unsigned int to_send; ++ uint8_t tx_buf[MCA_UART_TX_FIFO_SIZE]; ++ ++ /* ++ * There is a corner case in which the job is scheduled after the port ++ * has been shut down and port->state->port.tty is NULL. If not checked, ++ * uart_tx_stopped() would crash. ++ */ ++ if (!port->state->port.tty || uart_circ_empty(xmit) || ++ uart_tx_stopped(port)) ++ return; ++ ++ /* Get length of data pending in circular buffer */ ++ to_send = uart_circ_chars_pending(xmit); ++ if (likely(to_send)) { ++ unsigned int txlen; ++ unsigned int i; ++ int ret; ++ ++ /* Limit to size of TX FIFO */ ++ ret = regmap_read(regmap, MCA_REG_UART_TXLVL, &txlen); ++ if (ret) { ++ dev_err(mca_uart->dev, ++ "Failed to read MCA_REG_UART_TXLVL\n"); ++ txlen = 0; ++ } ++ ++ if (unlikely(!txlen)) { ++ dev_dbg(mca_uart->dev, "TX FIFO is full\n"); ++ if (!work_pending(&mca_uart->tx_work)) ++ schedule_work(&mca_uart->tx_work); ++ return; ++ } ++ ++ if (unlikely(txlen > sizeof(tx_buf))) { ++ dev_err(mca_uart->dev, ++ "Invalid MCA_REG_UART_TXLVL value %d\n", txlen); ++ if (!work_pending(&mca_uart->tx_work)) ++ schedule_work(&mca_uart->tx_work); ++ return; ++ } ++ ++ if (to_send > txlen) ++ to_send = txlen; ++ ++ port->icount.tx += to_send; ++ /* Convert to linear buffer */ ++ for (i = 0; i < to_send; ++i) { ++ tx_buf[i] = xmit->buf[xmit->tail]; ++ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); ++ } ++ ++ ret = regmap_bulk_write(regmap, MCA_REG_UART_THR, tx_buf, ++ to_send); ++ if (ret) ++ dev_err(mca_uart->dev, ++ "Failed to write MCA_REG_UART_THR\n"); ++ } ++ ++ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) ++ uart_write_wakeup(port); ++} ++ ++static void mca_uart_handle_rx(struct mca_uart *mca_uart, bool has_errors) ++{ ++ struct uart_port *port = &mca_uart->port; ++ struct regmap *regmap = mca_uart->mca->regmap; ++ unsigned int flag = TTY_NORMAL; ++ unsigned int lsr; ++ unsigned int rxlen; ++ unsigned int i; ++ int ret; ++ uint8_t rx_buf[MCA_UART_RX_FIFO_SIZE]; ++ uint8_t error_buf[MCA_UART_RX_FIFO_SIZE]; ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ ret = regmap_read(regmap, MCA_REG_UART_RXLVL, &rxlen); ++ if (ret) { ++ dev_err(mca_uart->dev, "Failed to read MCA_REG_UART_RXLVL\n"); ++ return; ++ } ++ ++ if (unlikely(!rxlen)) ++ return; ++ ++ if (unlikely(has_errors)) { ++ ret = regmap_read(regmap, MCA_REG_UART_LSR, &lsr); ++ if (ret) { ++ dev_err(mca_uart->dev, ++ "Failed to read MCA_REG_UART_LSR\n"); ++ return; ++ } ++ if (likely(lsr)) { ++ ret = regmap_bulk_read(regmap, MCA_REG_UART_RX_ERRORS, ++ error_buf, rxlen); ++ if (ret) { ++ dev_err(mca_uart->dev, ++ "Failed to read MCA_REG_UART_RX_ERRORS\n"); ++ return; ++ } ++ } else { ++ /* No errors */ ++ has_errors = false; ++ } ++ } ++ ++ ret = regmap_bulk_read(regmap, MCA_REG_UART_RHR, rx_buf, rxlen); ++ if (ret) { ++ dev_warn(mca_uart->dev, ++ "Failed to read MCA_REG_UART_RHR %d, retrying\n", ret); ++ ret = regmap_bulk_read(regmap, MCA_REG_UART_RHR, rx_buf, rxlen); ++ if (ret) { ++ dev_err(mca_uart->dev, ++ "Failed to read MCA_REG_UART_RHR %d\n", ret); ++ goto exit; ++ } ++ } ++ ++ port->icount.rx += rxlen; ++ for (i = 0; i < rxlen; i++) { ++ uint8_t const ch = rx_buf[i]; ++ ++ if (uart_handle_sysrq_char(port, ch)) ++ continue; ++ ++ if (unlikely(has_errors)) { ++ switch (error_buf[i]) { ++ case MCA_REG_UART_LSR_FRAMING_ERROR: ++ flag = TTY_FRAME; ++ port->icount.frame++; ++ break; ++ case MCA_REG_UART_LSR_PARITY_ERROR: ++ flag = TTY_PARITY; ++ port->icount.parity++; ++ break; ++ case MCA_REG_UART_LSR_FIFO_OR_ERROR: ++ /* MCA didn't read its UART fast enough */ ++ flag = TTY_OVERRUN; ++ port->icount.overrun++; ++ break; ++ case MCA_REG_UART_LSR_BREAK: ++ case MCA_REG_UART_LSR_HW_OR_ERROR: ++ flag = TTY_BREAK; ++ port->icount.brk++; ++ break; ++ case MCA_REG_UART_LSR_NO_ERROR: ++ default: ++ flag = TTY_NORMAL; ++ break; ++ } ++ } ++ ret = tty_insert_flip_char(&port->state->port, ch, flag); ++ if (!ret) { ++ dev_err(mca_uart->dev, ++ "tty_insert_flip_char failed for %x\n", ch); ++ port->icount.overrun++; ++ break; ++ } ++ } ++exit: ++ tty_flip_buffer_push(&port->state->port); ++} ++ ++static irqreturn_t mca_uart_irq_handler(int irq, void *private) ++{ ++ struct mca_uart *mca_uart = private; ++ struct regmap *regmap = mca_uart->mca->regmap; ++ unsigned int iir; ++ int ret; ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ mutex_lock(&mca_uart->mutex); ++ ++ ret = regmap_read(regmap, MCA_REG_UART_IIR, &iir); ++ if (ret) { ++ dev_err(mca_uart->dev, "Failed to read MCA_REG_UART_IIR\n"); ++ return IRQ_HANDLED; ++ } ++ ++ if (iir & MCA_REG_UART_IIR_RHR) { ++ bool has_errors = iir & MCA_REG_UART_IIR_RLSE; ++ ++ mca_uart_handle_rx(mca_uart, has_errors); ++ } ++ ++ if (iir & MCA_REG_UART_IIR_THR) { ++ if (!work_pending(&mca_uart->tx_work)) ++ schedule_work(&mca_uart->tx_work); ++ } ++ ++ mutex_unlock(&mca_uart->mutex); ++ return IRQ_HANDLED; ++} ++ ++static void mca_uart_delayed_work_proc(struct work_struct *ws) ++{ ++ struct mca_uart *mca_uart = to_mca_uart(ws, delayed_work); ++ struct regmap *regmap = mca_uart->mca->regmap; ++ int ret; ++ unsigned int ier_mask = 0; ++ unsigned int cfg0_mask = 0; ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ mutex_lock(&mca_uart->mutex); ++ ++ if (!mca_uart->pending_work) ++ return; ++ if (mca_uart->pending_work & WORK_STOP_RX) { ++ ier_mask |= MCA_REG_UART_IER_RHR; ++ cfg0_mask |= MCA_REG_UART_CFG0_CRX; ++ } ++ if (mca_uart->pending_work & WORK_STOP_TX) { ++ ier_mask |= MCA_REG_UART_IER_THR; ++ cfg0_mask |= MCA_REG_UART_CFG0_CTX; ++ } ++ ++ ret = regmap_update_bits(regmap, MCA_REG_UART_IER, ier_mask, 0); ++ if (ret) ++ dev_err(mca_uart->dev, "Failed to write MCA_REG_UART_IER\n"); ++ ++ ret = regmap_update_bits(regmap, MCA_REG_UART_CFG0, cfg0_mask, ++ MCA_REG_UART_CFG0_CRX | MCA_REG_UART_CFG0_CRX); ++ if (ret) ++ dev_err(mca_uart->dev, "Failed to write MCA_REG_UART_CFG0\n"); ++ ++ if ((mca_uart->pending_work & WORK_SET_RTS) || ++ (mca_uart->pending_work & WORK_CLEAR_RTS)) { ++ uint8_t msr_mask = mca_uart->pending_work & WORK_SET_RTS ? ++ MCA_REG_UART_MSR_RTS : 0; ++ ret = regmap_update_bits(regmap, MCA_REG_UART_MSR, ++ MCA_REG_UART_MSR_RTS, msr_mask); ++ if (ret) ++ dev_err(mca_uart->dev, ++ "Failed to write MCA_REG_UART_MSR\n"); ++ } ++ ++ mca_uart->pending_work = 0; ++ mutex_unlock(&mca_uart->mutex); ++} ++ ++static void mca_uart_tx_work_proc(struct work_struct *ws) ++{ ++ struct mca_uart *mca_uart = to_mca_uart(ws, tx_work); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ mutex_lock(&mca_uart->mutex); ++ mca_uart_handle_tx(&mca_uart->port); ++ mutex_unlock(&mca_uart->mutex); ++} ++ ++static ssize_t power_on_rx_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct tty_port *port = dev_get_drvdata(dev); ++ struct uart_state *state = container_of(port, struct uart_state, port); ++ struct uart_port *uart_port = state->uart_port; ++ struct mca_uart *mca_uart = container_of(uart_port, struct mca_uart, ++ port); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ return sprintf(buf, "%s\n", mca_uart->enable_power_on ? ++ "enabled" : "disabled"); ++} ++ ++static ssize_t power_on_rx_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct tty_port *port = dev_get_drvdata(dev); ++ struct uart_state *state = container_of(port, struct uart_state, port); ++ struct uart_port *uart_port = state->uart_port; ++ struct mca_uart *mca_uart = container_of(uart_port, struct mca_uart, ++ port); ++ struct regmap *regmap = mca_uart->mca->regmap; ++ int ret; ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ if (!strncmp(buf, "enabled", sizeof("enabled") - 1)) ++ mca_uart->enable_power_on = true; ++ else if (!strncmp(buf, "disabled", sizeof("disabled") - 1)) ++ mca_uart->enable_power_on = false; ++ else ++ return -EINVAL; ++ ++ ret = regmap_update_bits(regmap, MCA_REG_UART_CFG0, ++ MCA_REG_UART_CFG0_PWR_ON, ++ mca_uart->enable_power_on ? ++ MCA_REG_UART_CFG0_PWR_ON : 0); ++ if (ret < 0) ++ dev_err(mca_uart->dev, "Failed to write MCA_REG_UART_CFG0\n"); ++ return count; ++} ++static DEVICE_ATTR(power_on_rx, 0600, power_on_rx_show, power_on_rx_store); ++ ++static struct attribute *uart_sysfs_entries[] = { ++ &dev_attr_power_on_rx.attr, ++ NULL, ++}; ++ ++static struct attribute_group uart_port_extra_attr = { ++ .name = "power_extra_opts", ++ .attrs = uart_sysfs_entries, ++}; ++ ++static int mca_uart_probe(struct platform_device *pdev) ++{ ++ struct mca_drv *mca = dev_get_drvdata(pdev->dev.parent); ++ struct regmap *regmap = mca->regmap; ++ struct mca_uart *mca_uart; ++ struct device_node *np; ++ int ret; ++ ++ dev_dbg(&pdev->dev, "<%s>\n", __func__); ++ ++ if (IS_ERR(mca)) ++ return PTR_ERR(mca); ++ ++ /* Find entry in device-tree */ ++ if (!mca->dev->of_node) ++ return -ENODEV; ++ ++ /* Check if node does not exist or if it is disabled */ ++ np = of_find_compatible_node(mca->dev->of_node, NULL, ++ "digi,mca-cc6ul-uart"); ++ if (!np || !of_device_is_available(np)) ++ return -ENODEV; ++ ++ if (mca->fw_version < MCA_UART_MIN_FW_VERSION) { ++ dev_err(&pdev->dev, ++ "UART is not supported in MCA firmware v%d.%02d.\n", ++ MCA_FW_VER_MAJOR(mca->fw_version), ++ MCA_FW_VER_MINOR(mca->fw_version)); ++ return -ENODEV; ++ } ++ ++ mca_uart = devm_kzalloc(&pdev->dev, sizeof(*mca_uart), GFP_KERNEL); ++ if (!mca_uart) ++ return -ENOMEM; ++ ++ mca_uart->mca = mca; ++ mca_uart->dev = &pdev->dev; ++ platform_set_drvdata(pdev, mca_uart); ++ ++ mca_uart->enable_power_on = false; ++ mca_uart->has_rtscts = 0; ++ ++ ret = of_property_read_u32(np, "rts-pin", &mca_uart->rts_pin); ++ if (ret) { ++ dev_dbg(&pdev->dev, "No RTS pin provided\n"); ++ } else { ++ const int gpio_base = mca_uart->mca->gpio_base; ++ ++ ret = devm_gpio_request(&pdev->dev, ++ gpio_base + mca_uart->rts_pin, ++ "MCA UART RTS"); ++ if (ret) { ++ dev_err(&pdev->dev, ++ "Failed to allocate RTS pin\n"); ++ } else { ++ ret = regmap_write(regmap, MCA_REG_UART_RTSPIN, ++ mca_uart->rts_pin); ++ if (ret) ++ dev_err(mca_uart->dev, ++ "Failed to write MCA_REG_UART_RTSPIN\n"); ++ else ++ mca_uart->has_rtscts |= MCA_UART_HAS_RTS; ++ } ++ } ++ ++ ret = of_property_read_u32(np, "cts-pin", &mca_uart->cts_pin); ++ if (ret) { ++ dev_dbg(&pdev->dev, "No CTS pin provided\n"); ++ } else { ++ const int gpio_base = mca_uart->mca->gpio_base; ++ ++ ret = devm_gpio_request(&pdev->dev, ++ gpio_base + mca_uart->cts_pin, ++ "MCA UART CTS"); ++ if (ret) { ++ dev_err(&pdev->dev, ++ "Failed to allocate CTS pin\n"); ++ } else { ++ ret = regmap_write(regmap, MCA_REG_UART_CTSPIN, ++ mca_uart->cts_pin); ++ if (ret) ++ dev_err(mca_uart->dev, ++ "Failed to write MCA_REG_UART_CTSPIN\n"); ++ else ++ mca_uart->has_rtscts |= MCA_UART_HAS_CTS; ++ } ++ } ++ ++ /* Register UART driver */ ++ mca_uart->uart.owner = THIS_MODULE; ++ mca_uart->uart.dev_name = MCA_UART_DEV_NAME; ++ mca_uart->uart.nr = 1; ++ ret = uart_register_driver(&mca_uart->uart); ++ if (ret) { ++ dev_err(&pdev->dev, "Registering UART driver failed\n"); ++ goto error; ++ } ++ ++ mutex_init(&mca_uart->mutex); ++ ++ /* Initialize port data */ ++ mca_uart->port.line = 0; ++ mca_uart->port.dev = &pdev->dev; ++ mca_uart->port.irq = platform_get_irq_byname(pdev, ++ MCA_IRQ_UART_NAME); ++ mca_uart->port.type = PORT_LPUART; ++ mca_uart->port.fifosize = max(MCA_UART_TX_FIFO_SIZE, ++ MCA_UART_RX_FIFO_SIZE); ++ mca_uart->port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; ++ mca_uart->port.iotype = UPIO_PORT; ++ mca_uart->port.uartclk = MCA_UART_CLK; ++ mca_uart->port.rs485_config = NULL; ++ mca_uart->port.ops = &mca_uart_ops; ++ mca_uart->port.attr_group = &uart_port_extra_attr; ++ ++ /* Initialize queue for start TX */ ++ INIT_WORK(&mca_uart->tx_work, mca_uart_tx_work_proc); ++ INIT_WORK(&mca_uart->delayed_work, mca_uart_delayed_work_proc); ++ ++ /* Register port */ ++ ret = uart_add_one_port(&mca_uart->uart, &mca_uart->port); ++ if (ret) { ++ dev_err(mca_uart->dev, "Failed adding a port (%d)\n", ret); ++ goto error; ++ } ++ ++ /* Setup interrupt */ ++ ret = devm_request_threaded_irq(&pdev->dev, ++ mca_uart->port.irq, ++ NULL, mca_uart_irq_handler, ++ IRQF_ONESHOT, ++ MCA_IRQ_UART_NAME, ++ mca_uart); ++ if (ret) { ++ dev_err(mca_uart->dev, "Failed to register IRQ\n"); ++ goto error; ++ } ++ ++ ret = regmap_write(regmap, MCA_REG_UART_CFG0, MCA_REG_UART_CFG0_ENABLE); ++ if (ret) { ++ dev_err(mca_uart->dev, "Failed to write MCA_REG_UART_CFG0\n"); ++ goto error; ++ } ++ ++ dev_info(mca_uart->dev, "Registered successfully\n"); ++ return 0; ++error: ++ mutex_destroy(&mca_uart->mutex); ++ uart_remove_one_port(&mca_uart->uart, &mca_uart->port); ++ uart_unregister_driver(&mca_uart->uart); ++ devm_kfree(&pdev->dev, mca_uart); ++ ++ return ret; ++} ++ ++static int mca_uart_remove(struct platform_device *pdev) ++{ ++ struct mca_uart *mca_uart = dev_get_drvdata(pdev->dev.parent); ++ ++ dev_dbg(mca_uart->dev, "<%s>\n", __func__); ++ ++ cancel_work_sync(&mca_uart->tx_work); ++ cancel_work_sync(&mca_uart->delayed_work); ++ mutex_destroy(&mca_uart->mutex); ++ uart_remove_one_port(&mca_uart->uart, &mca_uart->port); ++ uart_unregister_driver(&mca_uart->uart); ++ devm_kfree(&pdev->dev, mca_uart); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM ++ ++/* ++ * The code snippet below was grabbed from drivers/tty/serial/serial_core.c ++ * It is used for retrieving the TTY layer struct device. This struct is used to ++ * check the value of /sys/class/tty/ttyMCAx/power/wakeup which is more standard ++ * than the one at /sys/bus/i2c/devices/0-007e/mca-cc6ul-uart/power/wakeup. ++ */ ++struct uart_match { ++ struct uart_port *port; ++ struct uart_driver *driver; ++}; ++ ++static int serial_match_port(struct device *dev, void *data) ++{ ++ struct uart_match *match = data; ++ struct tty_driver *tty_drv = match->driver->tty_driver; ++ dev_t devt = MKDEV(tty_drv->major, tty_drv->minor_start) + ++ match->port->line; ++ ++ return dev->devt == devt; /* Actually, only one tty per port */ ++} ++ ++static int mca_cc6ul_uart_suspend(struct device *d) ++{ ++ int ret; ++ struct mca_uart *mca_uart = platform_get_drvdata(to_platform_device(d)); ++ struct regmap *regmap = mca_uart->mca->regmap; ++ struct uart_match match = {&mca_uart->port, &mca_uart->uart}; ++ struct device *tty_dev = device_find_child(mca_uart->port.dev, &match, ++ serial_match_port); ++ int mask = MCA_REG_UART_CFG0_WAKEUP; ++ unsigned int new_value = 0; ++ ++ if (tty_dev && device_may_wakeup(tty_dev)) ++ new_value |= MCA_REG_UART_CFG0_WAKEUP; ++ ++ ret = regmap_update_bits(regmap, MCA_REG_UART_CFG0, mask, new_value); ++ if (ret < 0) ++ dev_err(mca_uart->dev, "Failed to write MCA_REG_UART_CFG0\n"); ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops mca_cc6ul_uart_pm_ops = { ++ .suspend = mca_cc6ul_uart_suspend, ++ .resume = NULL, ++ .poweroff = NULL, ++}; ++#endif ++ ++#ifdef CONFIG_OF ++static const struct of_device_id mca_uart_ids[] = { ++ { .compatible = "digi,mca-cc6ul-uart", }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, mca_uart_ids); ++#endif ++ ++static struct platform_driver mca_uart_driver = { ++ .probe = mca_uart_probe, ++ .remove = mca_uart_remove, ++ .driver = { ++ .name = MCA_CC6UL_DRVNAME_UART, ++ .of_match_table = of_match_ptr(mca_uart_ids), ++#ifdef CONFIG_PM ++ .pm = &mca_cc6ul_uart_pm_ops, ++#endif ++ }, ++}; ++ ++static int __init mca_uart_init(void) ++{ ++ return platform_driver_register(&mca_uart_driver); ++} ++module_init(mca_uart_init); ++ ++static void __exit mca_uart_exit(void) ++{ ++ platform_driver_unregister(&mca_uart_driver); ++} ++module_exit(mca_uart_exit); ++ ++MODULE_AUTHOR("Digi International Inc"); ++MODULE_DESCRIPTION("UART for MCA of ConnectCore 6UL"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:" MCA_CC6UL_DRVNAME_UART); diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0010-imx6ul-Add-RTC-MCA-support-for-ConnectCore-6UL-SOM.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0010-imx6ul-Add-RTC-MCA-support-for-ConnectCore-6UL-SOM.patch new file mode 100644 index 000000000..32698e587 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0010-imx6ul-Add-RTC-MCA-support-for-ConnectCore-6UL-SOM.patch @@ -0,0 +1,540 @@ +From 33fb650af68bcc05f59181ae7a4f8424a0949f91 Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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); diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0011-imx6ul-Add-MCA-power-key-support-for-ConnectCore-6UL.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0011-imx6ul-Add-MCA-power-key-support-for-ConnectCore-6UL.patch new file mode 100644 index 000000000..fcf1949c8 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ul/0011-imx6ul-Add-MCA-power-key-support-for-ConnectCore-6UL.patch @@ -0,0 +1,490 @@ +From 052464d671eb993264a78b3e35170c1d9fd44268 Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +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 +--- + 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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); diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc_%.bbappend b/meta-digi-arm/recipes-kernel/linux/linux-fslc_%.bbappend new file mode 100644 index 000000000..29b788986 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc_%.bbappend @@ -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 \ +"