From bcb942fc5b117031fae5bf82e9d8cf4b0e648812 Mon Sep 17 00:00:00 2001 From: Alex Gonzalez Date: Mon, 1 Apr 2019 17:16:24 +0200 Subject: [PATCH] meta-digi-arm: linux-fslc: ccimx6ulsbc: Add IOEXP support Signed-off-by: Alex Gonzalez --- ...6ulsbcpro-Add-IOEXP-core-I2C-support.patch | 892 ++++++++++++++++++ ...cimx6ulsbcpro-Add-IOEXP-GPIO-support.patch | 47 + ...ccimx6ulsbcpro-Add-IOEXP-ADC-support.patch | 35 + ...sbcpro-Configure-touch-GPIO-reset-li.patch | 38 + .../linux/linux-fslc_%.bbappend | 7 + 5 files changed, 1019 insertions(+) create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0001-ccimx6ulsbcpro-Add-IOEXP-core-I2C-support.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0002-ccimx6ulsbcpro-Add-IOEXP-GPIO-support.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0003-ccimx6ulsbcpro-Add-IOEXP-ADC-support.patch create mode 100644 meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0004-ARM-dts-ccimx6ulsbcpro-Configure-touch-GPIO-reset-li.patch diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0001-ccimx6ulsbcpro-Add-IOEXP-core-I2C-support.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0001-ccimx6ulsbcpro-Add-IOEXP-core-I2C-support.patch new file mode 100644 index 000000000..71664b50d --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0001-ccimx6ulsbcpro-Add-IOEXP-core-I2C-support.patch @@ -0,0 +1,892 @@ +From f56dbb1e8b6a501e84d69ac5f18ec7a66cb4faa7 Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +Date: Mon, 23 Apr 2018 11:46:52 +0200 +Subject: [PATCH] ccimx6ulsbcpro: Add IOEXP core I2C support + +Synched with v4.14.78/master at: +3f8b03950b323db4ca89b1cdc1c2288f79facaa3 + +Signed-off-by: Alex Gonzalez +--- + arch/arm/configs/imx_v6_v7_defconfig | 1 + + drivers/mfd/Kconfig | 10 + + drivers/mfd/Makefile | 4 +- + drivers/mfd/mca-ioexp-core.c | 415 ++++++++++++++++++++++++++++++++ + drivers/mfd/mca-ioexp-i2c.c | 186 ++++++++++++++ + drivers/mfd/mca-ioexp-irq.c | 89 +++++++ + include/linux/mfd/mca-ioexp/core.h | 87 +++++++ + include/linux/mfd/mca-ioexp/registers.h | 15 ++ + 8 files changed, 805 insertions(+), 2 deletions(-) + create mode 100644 drivers/mfd/mca-ioexp-core.c + create mode 100644 drivers/mfd/mca-ioexp-i2c.c + create mode 100644 drivers/mfd/mca-ioexp-irq.c + create mode 100644 include/linux/mfd/mca-ioexp/core.h + create mode 100644 include/linux/mfd/mca-ioexp/registers.h + +diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig +index ed31623082ac..a269983e3cba 100644 +--- a/arch/arm/configs/imx_v6_v7_defconfig ++++ b/arch/arm/configs/imx_v6_v7_defconfig +@@ -242,6 +242,7 @@ CONFIG_MFD_DA9063=y + CONFIG_MFD_MC13XXX_SPI=y + CONFIG_MFD_MC13XXX_I2C=y + CONFIG_MFD_RN5T618=y ++CONFIG_MFD_MCA_IOEXP=y + CONFIG_MFD_STMPE=y + CONFIG_REGULATOR=y + CONFIG_REGULATOR_FIXED_VOLTAGE=y +diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +index 42c72334d645..5b87d592aece 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -458,6 +458,16 @@ config MFD_MCA_CC8X + Additional drivers must be enabled in order to use the functionality + of the device (RTC, watchdog, ...). + ++config MFD_MCA_IOEXP ++ bool "Digi IO Expander" ++ select MFD_CORE ++ select REGMAP_I2C ++ select REGMAP_IRQ ++ depends on I2C=y ++ help ++ Select this option to enable support for the Digi IO Expander. ++ This includes the GPIO and ADC drivers. ++ + 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 28443313ba92..bfbaaba0d74f 100644 +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -249,5 +249,5 @@ 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 ++mca-ioexp-objs := mca-ioexp-core.o mca-ioexp-irq.o mca-ioexp-i2c.o ++obj-$(CONFIG_MFD_MCA_IOEXP) += mca-ioexp.o +diff --git a/drivers/mfd/mca-ioexp-core.c b/drivers/mfd/mca-ioexp-core.c +new file mode 100644 +index 000000000000..19b3c7f2b4be +--- /dev/null ++++ b/drivers/mfd/mca-ioexp-core.c +@@ -0,0 +1,415 @@ ++/* ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++ ++static struct resource mca_ioexp_gpios_resources[] = { ++ { ++ .name = MCA_IRQ_GPIO_BANK_0_NAME, ++ .start = MCA_IOEXP_IRQ_GPIO_BANK_0, ++ .end = MCA_IOEXP_IRQ_GPIO_BANK_0, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_GPIO_BANK_1_NAME, ++ .start = MCA_IOEXP_IRQ_GPIO_BANK_1, ++ .end = MCA_IOEXP_IRQ_GPIO_BANK_1, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_GPIO_BANK_2_NAME, ++ .start = MCA_IOEXP_IRQ_GPIO_BANK_2, ++ .end = MCA_IOEXP_IRQ_GPIO_BANK_2, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_GPIO_BANK_3_NAME, ++ .start = MCA_IOEXP_IRQ_GPIO_BANK_3, ++ .end = MCA_IOEXP_IRQ_GPIO_BANK_3, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_GPIO_BANK_4_NAME, ++ .start = MCA_IOEXP_IRQ_GPIO_BANK_4, ++ .end = MCA_IOEXP_IRQ_GPIO_BANK_4, ++ .flags = IORESOURCE_IRQ, ++ }, ++ { ++ .name = MCA_IRQ_GPIO_BANK_5_NAME, ++ .start = MCA_IOEXP_IRQ_GPIO_BANK_5, ++ .end = MCA_IOEXP_IRQ_GPIO_BANK_5, ++ .flags = IORESOURCE_IRQ, ++ }, ++}; ++ ++static const struct mfd_cell mca_ioexp_devs[] = { ++ { ++ .name = MCA_IOEXP_DRVNAME_GPIO, ++ .num_resources = ARRAY_SIZE(mca_ioexp_gpios_resources), ++ .resources = mca_ioexp_gpios_resources, ++ .of_compatible = "digi,mca-ioexp-gpio", ++ }, ++ { ++ .name = MCA_IOEXP_DRVNAME_ADC, ++ .of_compatible = "digi,mca-ioexp-adc", ++ }, ++}; ++ ++static ssize_t hwver_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mca_ioexp *ioexp = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d\n", ioexp->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_ioexp *ioexp = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%d.%02d %s\n", MCA_FW_VER_MAJOR(ioexp->fw_version), ++ MCA_FW_VER_MINOR(ioexp->fw_version), ++ ioexp->fw_is_alpha ? "(alpha)" : ""); ++} ++static DEVICE_ATTR(fw_version, S_IRUGO, fwver_show, NULL); ++ ++static struct attribute *mca_ioexp_sysfs_entries[] = { ++ &dev_attr_hw_version.attr, ++ &dev_attr_fw_version.attr, ++ NULL, ++}; ++ ++static struct attribute_group mca_ioexp_attr_group = { ++ .name = NULL, /* put in device directory */ ++ .attrs = mca_ioexp_sysfs_entries, ++}; ++ ++static int read_reg_group(struct mca_ioexp *ioexp, ++ unsigned int start_addr, ++ unsigned int count, ++ uint8_t *dest) ++{ ++ unsigned int i; ++ unsigned int error; ++ ++ if (count == 0 || !dest) ++ return -1; ++ ++ for (i = 0; i < count; i++) { ++ const unsigned int reg_addr = start_addr + i; ++ unsigned int value; ++ ++ error = regmap_read(ioexp->regmap, reg_addr, &value); ++ if (error) { ++ dev_err(ioexp->dev, ++ "Error reading register %02X (%d)\n", ++ reg_addr, error); ++ return -1; ++ } ++ dest[i] = value; ++ } ++ ++ return 0; ++} ++ ++int mca_ioexp_suspend(struct device *dev) ++{ ++ struct mca_ioexp *ioexp = dev_get_drvdata(dev); ++ int error; ++ ++ if (!ioexp->preserved_regs) ++ return 0; ++ ++ ++ error = read_reg_group(ioexp, MCA_GPIO_DIR_0, ++ ioexp->preserved_regs->gpio_dir.cnt, ++ ioexp->preserved_regs->gpio_dir.values); ++ if (error) { ++ dev_err(ioexp->dev, ++ "Failed to preserve MCA_GPIO_DIR registers.\n"); ++ goto exit; ++ } ++ ++ error = read_reg_group(ioexp, MCA_GPIO_DATA_0, ++ ioexp->preserved_regs->gpio_data.cnt, ++ ioexp->preserved_regs->gpio_data.values); ++ if (error) { ++ dev_err(ioexp->dev, ++ "Failed to preserve MCA_GPIO_DATA registers.\n"); ++ goto exit; ++ } ++ ++ error = read_reg_group(ioexp, MCA_GPIO_IRQ_CFG_0, ++ ioexp->preserved_regs->irq_cfg.cnt, ++ ioexp->preserved_regs->irq_cfg.values); ++ if (error) { ++ dev_err(ioexp->dev, ++ "Failed to preserve MCA_GPIO_IRQ_CFG registers.\n"); ++ goto exit; ++ } ++ ++ error = read_reg_group(ioexp, MCA_IRQ_MASK_0, ++ ioexp->preserved_regs->irq_mask.cnt, ++ ioexp->preserved_regs->irq_mask.values); ++ if (error) { ++ dev_err(ioexp->dev, ++ "Failed to preserve MCA_IRQ_MASK registers.\n"); ++ goto exit; ++ } ++ ++ error = read_reg_group(ioexp, MCA_REG_ADC_CFG0_0, ++ ioexp->preserved_regs->adc_cfg.cnt, ++ ioexp->preserved_regs->adc_cfg.values); ++ if (error) { ++ dev_err(ioexp->dev, ++ "Failed to preserve MCA_REG_ADC_CFG registers.\n"); ++ goto exit; ++ } ++ ++ disable_irq(ioexp->chip_irq); ++ return 0; ++ ++exit: ++ if (error) ++ dev_err(ioexp->dev, "Configuration will be lost on resume. The IOs in use might need to be reconfigured in order to work properly.\n"); ++ ++ disable_irq(ioexp->chip_irq); ++ ++ /* Do not return errors or the device will not go to sleep. */ ++ return 0; ++} ++ ++static int write_reg_group(struct mca_ioexp *ioexp, ++ unsigned int start_addr, ++ unsigned int count, ++ uint8_t *values) ++{ ++ unsigned int i; ++ unsigned int error; ++ ++ if (count == 0 || !values) ++ return -1; ++ ++ for (i = 0; i < count; i++) { ++ const unsigned int reg_addr = start_addr + i; ++ const unsigned int value = values[i]; ++ ++ error = regmap_write(ioexp->regmap, reg_addr, value); ++ if (error) { ++ dev_err(ioexp->dev, ++ "Error writing register %02X (%d)\n", ++ reg_addr, error); ++ } ++ } ++ ++ return error; ++} ++ ++int mca_ioexp_resume(struct device *dev) ++{ ++ struct mca_ioexp *ioexp = dev_get_drvdata(dev); ++ int error; ++ ++ if (!ioexp->preserved_regs) ++ return 0; ++ ++ error = write_reg_group(ioexp, MCA_GPIO_DIR_0, ++ ioexp->preserved_regs->gpio_dir.cnt, ++ ioexp->preserved_regs->gpio_dir.values); ++ if (error) ++ dev_err(ioexp->dev, ++ "Failed to restore MCA_GPIO_DIR registers.\n"); ++ ++ error = write_reg_group(ioexp, MCA_GPIO_DATA_0, ++ ioexp->preserved_regs->gpio_data.cnt, ++ ioexp->preserved_regs->gpio_data.values); ++ if (error) ++ dev_err(ioexp->dev, ++ "Failed to restore MCA_GPIO_DATA registers.\n"); ++ ++ error = write_reg_group(ioexp, MCA_GPIO_IRQ_CFG_0, ++ ioexp->preserved_regs->irq_cfg.cnt, ++ ioexp->preserved_regs->irq_cfg.values); ++ if (error) ++ dev_err(ioexp->dev, ++ "Failed to restore MCA_GPIO_IRQ_CFG registers.\n"); ++ ++ error = write_reg_group(ioexp, MCA_IRQ_MASK_0, ++ ioexp->preserved_regs->irq_mask.cnt, ++ ioexp->preserved_regs->irq_mask.values); ++ if (error) ++ dev_err(ioexp->dev, ++ "Failed to restore MCA_IRQ_MASK registers.\n"); ++ ++ error = write_reg_group(ioexp, MCA_REG_ADC_CFG0_0, ++ ioexp->preserved_regs->adc_cfg.cnt, ++ ioexp->preserved_regs->adc_cfg.values); ++ if (error) ++ dev_err(ioexp->dev, ++ "Failed to restore MCA_REG_ADC_CFG registers.\n"); ++ ++ enable_irq(ioexp->chip_irq); ++ return 0; ++} ++ ++int mca_ioexp_device_init(struct mca_ioexp *ioexp, u32 irq) ++{ ++ int ret; ++ unsigned int val; ++ ++ ret = regmap_read(ioexp->regmap, MCA_DEVICE_ID, &val); ++ if (ret) { ++ dev_err(ioexp->dev, ++ "Cannot read MCA IO Expander Device ID (%d)\n", ++ ret); ++ return ret; ++ } ++ ioexp->dev_id = (u8)val; ++ ++ if (ioexp->dev_id != MCA_IOEXP_DEVICE_ID_VAL) { ++ dev_err(ioexp->dev, "Invalid MCA IO Expander Device ID (%x)\n", ++ ioexp->dev_id); ++ return -ENODEV; ++ } ++ ++ ret = regmap_read(ioexp->regmap, MCA_HW_VER, &val); ++ if (ret) { ++ dev_err(ioexp->dev, "Cannot read MCA Hardware Version (%d)\n", ++ ret); ++ return ret; ++ } ++ ioexp->hw_version = (u8)val; ++ ++ ret = regmap_bulk_read(ioexp->regmap, MCA_FW_VER_L, &val, 2); ++ if (ret) { ++ dev_err(ioexp->dev, ++ "Cannot read MCA IO Expander Firmware Version (%d)\n", ++ ret); ++ return ret; ++ } ++ ioexp->fw_version = (u16)(val & ~MCA_FW_VER_ALPHA_MASK); ++ ioexp->fw_is_alpha = val & MCA_FW_VER_ALPHA_MASK ? true : false; ++ ++ ioexp->chip_irq = irq; ++ ioexp->gpio_base = -1; ++ ++ if (of_find_property(ioexp->dev->of_node, "restore-config-on-resume", ++ NULL)) { ++ unsigned int gpio_num; ++ ++ ioexp->preserved_regs = kzalloc(sizeof *ioexp->preserved_regs, ++ GFP_KERNEL | GFP_DMA); ++ if (!ioexp->preserved_regs) { ++ dev_err(ioexp->dev, ++ "Failed to allocate memory for preserved registers.\n"); ++ return -ENOMEM; ++ } ++ ++ ret = regmap_read(ioexp->regmap, MCA_GPIO_NUM, &gpio_num); ++ if (ret) { ++ dev_err(ioexp->dev, ++ "Error reading MCA_GPIO_NUM (%d)\n", ret); ++ return ret; ++ } ++ ++ ioexp->preserved_regs->gpio_dir.cnt = (gpio_num + 7) / 8; ++ ioexp->preserved_regs->gpio_dir.values = ++ kzalloc(ioexp->preserved_regs->gpio_dir.cnt, ++ GFP_KERNEL | GFP_DMA); ++ ioexp->preserved_regs->gpio_data.cnt = (gpio_num + 7) / 8; ++ ioexp->preserved_regs->gpio_data.values = ++ kzalloc(ioexp->preserved_regs->gpio_data.cnt, ++ GFP_KERNEL | GFP_DMA); ++ ioexp->preserved_regs->irq_cfg.cnt = gpio_num; ++ ioexp->preserved_regs->irq_cfg.values = ++ kzalloc(ioexp->preserved_regs->irq_cfg.cnt, ++ GFP_KERNEL | GFP_DMA); ++ ioexp->preserved_regs->irq_mask.cnt = 4; ++ ioexp->preserved_regs->irq_mask.values = ++ kzalloc(ioexp->preserved_regs->irq_mask.cnt, ++ GFP_KERNEL | GFP_DMA); ++ ioexp->preserved_regs->adc_cfg.cnt = gpio_num; ++ ioexp->preserved_regs->adc_cfg.values = ++ kzalloc(ioexp->preserved_regs->adc_cfg.cnt, ++ GFP_KERNEL | GFP_DMA); ++ ++ } else { ++ ioexp->preserved_regs = NULL; ++ } ++ ret = mca_ioexp_irq_init(ioexp); ++ if (ret) { ++ dev_err(ioexp->dev, "Cannot initialize interrupts (%d)\n", ret); ++ return ret; ++ } ++ ++ ret = mfd_add_devices(ioexp->dev, -1, mca_ioexp_devs, ++ ARRAY_SIZE(mca_ioexp_devs), NULL, ioexp->irq_base, ++ regmap_irq_get_domain(ioexp->regmap_irq)); ++ ++ if (ret) { ++ dev_err(ioexp->dev, "Cannot add MFD cells (%d)\n", ret); ++ goto out_irq; ++ } ++ ++ ret = sysfs_create_group(&ioexp->dev->kobj, &mca_ioexp_attr_group); ++ if (ret) { ++ dev_err(ioexp->dev, "Cannot create sysfs entries (%d)\n", ret); ++ goto out_dev; ++ } ++ ++ return 0; ++ ++out_dev: ++ mfd_remove_devices(ioexp->dev); ++out_irq: ++ mca_ioexp_irq_exit(ioexp); ++ ++ return ret; ++} ++ ++void mca_ioexp_device_exit(struct mca_ioexp *ioexp) ++{ ++ sysfs_remove_group(&ioexp->dev->kobj, &mca_ioexp_attr_group); ++ mfd_remove_devices(ioexp->dev); ++ mca_ioexp_irq_exit(ioexp); ++ ++ if (!ioexp->preserved_regs) { ++ kfree(ioexp->preserved_regs->gpio_dir.values); ++ kfree(ioexp->preserved_regs->gpio_data.values); ++ kfree(ioexp->preserved_regs->irq_cfg.values); ++ kfree(ioexp->preserved_regs->irq_mask.values); ++ kfree(ioexp->preserved_regs->adc_cfg.values); ++ kfree(ioexp->preserved_regs); ++ ++ ioexp->preserved_regs = NULL; ++ } ++} ++ ++MODULE_AUTHOR("Digi International Inc"); ++MODULE_DESCRIPTION("MCA IO Expander driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/mfd/mca-ioexp-i2c.c b/drivers/mfd/mca-ioexp-i2c.c +new file mode 100644 +index 000000000000..29a5587ce683 +--- /dev/null ++++ b/drivers/mfd/mca-ioexp-i2c.c +@@ -0,0 +1,186 @@ ++/* ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++static const struct regmap_range mca_ioexp_readable_ranges[] = { ++}; ++ ++static const struct regmap_range mca_ioexp_writeable_ranges[] = { ++ regmap_reg_range(MCA_IRQ_STATUS_0, MCA_IRQ_MASK_3), ++ regmap_reg_range(MCA_GPIO_DIR_0, MCA_GPIO_IRQ_CFG_63), ++ regmap_reg_range(MCA_REG_ADC_CFG0_0, MCA_REG_ADC_CFG0_31), ++ regmap_reg_range(MCA_REG_ADC_CFG1_0, MCA_REG_ADC_CFG1_31), ++ regmap_reg_range(MCA_REG_ADC_CFG2_0, MCA_REG_ADC_CFG2_31), ++}; ++ ++static const struct regmap_range mca_ioexp_volatile_ranges[] = { ++ /* Real volatile registers */ ++ regmap_reg_range(MCA_IRQ_STATUS_0, MCA_IRQ_STATUS_3), ++ 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_REG_ADC_VAL_L_0, MCA_REG_ADC_VAL_H_31), ++ ++ /* ++ * 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_DEVICE_ID, MCA_UID_9), ++ regmap_reg_range(MCA_IRQ_MASK_0, MCA_IRQ_MASK_3), ++ 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_31), ++ regmap_reg_range(MCA_REG_ADC_CFG1_0, MCA_REG_ADC_CFG1_31), ++ regmap_reg_range(MCA_REG_ADC_CFG2_0, MCA_REG_ADC_CFG2_31), ++}; ++ ++static const struct regmap_access_table mca_ioexp_readable_table = { ++ .yes_ranges = mca_ioexp_readable_ranges, ++ .n_yes_ranges = ARRAY_SIZE(mca_ioexp_readable_ranges), ++}; ++ ++static const struct regmap_access_table mca_ioexp_writeable_table = { ++ .yes_ranges = mca_ioexp_writeable_ranges, ++ .n_yes_ranges = ARRAY_SIZE(mca_ioexp_writeable_ranges), ++}; ++ ++static const struct regmap_access_table mca_ioexp_volatile_table = { ++ .yes_ranges = mca_ioexp_volatile_ranges, ++ .n_yes_ranges = ARRAY_SIZE(mca_ioexp_volatile_ranges), ++}; ++ ++static struct regmap_config mca_ioexp_regmap_config = { ++ .reg_bits = 16, ++ .val_bits = 8, ++ .max_register = 0xFFFF, ++ ++ .rd_table = &mca_ioexp_readable_table, ++ .wr_table = &mca_ioexp_writeable_table, ++ .volatile_table = &mca_ioexp_volatile_table, ++ ++ .cache_type = REGCACHE_RBTREE, ++}; ++ ++static const struct of_device_id mca_ioexp_dt_ids[] = { ++ { .compatible = "digi,mca_ioexp", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, mca_ioexp_dt_ids); ++ ++static int mca_ioexp_i2c_probe(struct i2c_client *i2c, ++ const struct i2c_device_id *id) ++{ ++ struct mca_ioexp *ioexp; ++ int ret; ++ ++ ioexp = devm_kzalloc(&i2c->dev, sizeof(struct mca_ioexp), GFP_KERNEL); ++ if (ioexp == NULL) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(i2c, ioexp); ++ ioexp->dev = &i2c->dev; ++ ioexp->chip_irq = i2c->irq; ++ ioexp->regmap = devm_regmap_init_i2c(i2c, &mca_ioexp_regmap_config); ++ if (IS_ERR(ioexp->regmap)) { ++ ret = PTR_ERR(ioexp->regmap); ++ dev_err(ioexp->dev, "Failed to allocate register map (%d)\n", ret); ++ goto err_regmap; ++ } ++ ++ ret = mca_ioexp_device_init(ioexp, i2c->irq); ++ if (ret) { ++ dev_err(ioexp->dev, "Failed to init i2c device (%d)\n", ret); ++ goto err_regmap; ++ } ++ ++ return 0; ++ ++err_regmap: ++ devm_kfree(ioexp->dev, ioexp); ++ ++ return ret; ++} ++ ++static int mca_ioexp_i2c_remove(struct i2c_client *i2c) ++{ ++ struct mca_ioexp *ioexp = i2c_get_clientdata(i2c); ++ ++ mca_ioexp_device_exit(ioexp); ++ devm_kfree(ioexp->dev, ioexp); ++ ++ return 0; ++} ++ ++static void mca_ioexp_i2c_shutdown(struct i2c_client *i2c) ++{ ++ struct mca_ioexp *ioexp = i2c_get_clientdata(i2c); ++ ++ /* ++ * Disable the IRQ so that the I/O Expander does not wake-up the MCA ++ * when powered off. ++ */ ++ disable_irq(ioexp->chip_irq); ++} ++ ++#ifdef CONFIG_PM ++static int mca_ioexp_i2c_suspend(struct device *dev) ++{ ++ return mca_ioexp_suspend(dev); ++} ++ ++static int mca_ioexp_i2c_resume(struct device *dev) ++{ ++ return mca_ioexp_resume(dev); ++} ++ ++static SIMPLE_DEV_PM_OPS(mca_ioexp_i2c_pm_ops, ++ mca_ioexp_i2c_suspend, ++ mca_ioexp_i2c_resume); ++#endif ++ ++static const struct i2c_device_id mca_ioexp_i2c_id[] = { ++ {"mca_ioexp", 0}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, mca_ioexp_i2c_id); ++ ++static struct i2c_driver mca_ioexp_i2c_driver = { ++ .driver = { ++ .name = "mca_ioexp", ++ .of_match_table = of_match_ptr(mca_ioexp_dt_ids), ++#ifdef CONFIG_PM ++ .pm = &mca_ioexp_i2c_pm_ops, ++#endif ++ }, ++ .probe = mca_ioexp_i2c_probe, ++ .remove = mca_ioexp_i2c_remove, ++ .shutdown = mca_ioexp_i2c_shutdown, ++ .id_table = mca_ioexp_i2c_id, ++}; ++ ++module_i2c_driver(mca_ioexp_i2c_driver); +diff --git a/drivers/mfd/mca-ioexp-irq.c b/drivers/mfd/mca-ioexp-irq.c +new file mode 100644 +index 000000000000..439149765dd0 +--- /dev/null ++++ b/drivers/mfd/mca-ioexp-irq.c +@@ -0,0 +1,89 @@ ++/* ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MCA_IOEXP_IRQ_0_OFFSET 0 ++#define MCA_IOEXP_IRQ_1_OFFSET 1 ++#define MCA_IOEXP_IRQ_2_OFFSET 2 ++#define MCA_IOEXP_IRQ_3_OFFSET 3 ++ ++static const struct regmap_irq mca_ioexp_irqs[] = { ++ [MCA_IOEXP_IRQ_GPIO_BANK_0] = { ++ .reg_offset = MCA_IOEXP_IRQ_1_OFFSET, ++ .mask = MCA_GPIO_BANK_0, ++ }, ++ [MCA_IOEXP_IRQ_GPIO_BANK_1] = { ++ .reg_offset = MCA_IOEXP_IRQ_1_OFFSET, ++ .mask = MCA_GPIO_BANK_1, ++ }, ++ [MCA_IOEXP_IRQ_GPIO_BANK_2] = { ++ .reg_offset = MCA_IOEXP_IRQ_1_OFFSET, ++ .mask = MCA_GPIO_BANK_2, ++ }, ++ [MCA_IOEXP_IRQ_GPIO_BANK_3] = { ++ .reg_offset = MCA_IOEXP_IRQ_1_OFFSET, ++ .mask = MCA_GPIO_BANK_3, ++ }, ++ [MCA_IOEXP_IRQ_GPIO_BANK_4] = { ++ .reg_offset = MCA_IOEXP_IRQ_1_OFFSET, ++ .mask = MCA_GPIO_BANK_4, ++ }, ++ [MCA_IOEXP_IRQ_GPIO_BANK_5] = { ++ .reg_offset = MCA_IOEXP_IRQ_1_OFFSET, ++ .mask = MCA_GPIO_BANK_5, ++ }, ++}; ++ ++static const struct regmap_irq_chip mca_ioexp_irq_chip = { ++ .name = "mca-ioexp-irq", ++ .irqs = mca_ioexp_irqs, ++ .num_irqs = ARRAY_SIZE(mca_ioexp_irqs), ++ .num_regs = MCA_IOEXP_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_ioexp_irq_init(struct mca_ioexp *ioexp) ++{ ++ int ret; ++ const int irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED; ++ ++ if (!ioexp->chip_irq) { ++ dev_err(ioexp->dev, "No IRQ configured\n"); ++ return -EINVAL; ++ } ++ ++ ioexp->irq_base = -1; ++ ret = regmap_add_irq_chip(ioexp->regmap, ioexp->chip_irq, ++ irq_flags, ++ ioexp->irq_base, &mca_ioexp_irq_chip, ++ &ioexp->regmap_irq); ++ if (ret) { ++ dev_err(ioexp->dev, "Failed to request IRQ %d (%d)\n", ++ ioexp->chip_irq, ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++void mca_ioexp_irq_exit(struct mca_ioexp *ioexp) ++{ ++ regmap_del_irq_chip(ioexp->chip_irq, ioexp->regmap_irq); ++} +diff --git a/include/linux/mfd/mca-ioexp/core.h b/include/linux/mfd/mca-ioexp/core.h +new file mode 100644 +index 000000000000..cb9969262da5 +--- /dev/null ++++ b/include/linux/mfd/mca-ioexp/core.h +@@ -0,0 +1,87 @@ ++/* ++ * 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_IOEXP_CORE_H_ ++#define MFD_MCA_IOEXP_CORE_H_ ++ ++#include ++#include ++ ++#define MCA_IOEXP_DRVNAME_ADC "mca-ioexp-adc" ++#define MCA_IOEXP_DRVNAME_GPIO "mca-ioexp-gpio" ++ ++#define MCA_IOEXP_DEVICE_ID_VAL 0x37 ++#define MCA_IOEXP_ADDR_LEN 2 ++#define MCA_IOEXP_MAX_FRAME_DATA_LEN 256 ++ ++/* Interrupts */ ++enum mca_ioexp_irqs { ++ MCA_IOEXP_IRQ_GPIO_BANK_0, ++ MCA_IOEXP_IRQ_GPIO_BANK_1, ++ MCA_IOEXP_IRQ_GPIO_BANK_2, ++ MCA_IOEXP_IRQ_GPIO_BANK_3, ++ MCA_IOEXP_IRQ_GPIO_BANK_4, ++ MCA_IOEXP_IRQ_GPIO_BANK_5, ++ /* ... */ ++ ++ MCA_IOEXP_NUM_IRQS, ++}; ++ ++/* Number of interrupt registers */ ++#define MCA_IOEXP_NUM_IRQ_REGS 4 ++ ++struct mca_ioexp { ++ 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; ++ int chip_irq; ++ u32 irq_base; ++ int gpio_base; ++ ++ struct { ++ struct { ++ uint8_t *values; ++ uint8_t cnt; ++ } gpio_dir; ++ ++ struct { ++ uint8_t *values; ++ uint8_t cnt; ++ } gpio_data; ++ ++ struct { ++ uint8_t *values; ++ uint8_t cnt; ++ } irq_cfg; ++ ++ struct { ++ uint8_t *values; ++ uint8_t cnt; ++ } irq_mask; ++ ++ struct { ++ uint8_t *values; ++ uint8_t cnt; ++ } adc_cfg; ++ } *preserved_regs; ++}; ++ ++int mca_ioexp_device_init(struct mca_ioexp *ioexp, u32 irq); ++int mca_ioexp_irq_init(struct mca_ioexp *ioexp); ++void mca_ioexp_device_exit(struct mca_ioexp *ioexp); ++void mca_ioexp_irq_exit(struct mca_ioexp *ioexp); ++int mca_ioexp_suspend(struct device *dev); ++int mca_ioexp_resume(struct device *dev); ++ ++#endif /* MFD_MCA_IOEXP_CORE_H_ */ +diff --git a/include/linux/mfd/mca-ioexp/registers.h b/include/linux/mfd/mca-ioexp/registers.h +new file mode 100644 +index 000000000000..9359c9953e0d +--- /dev/null ++++ b/include/linux/mfd/mca-ioexp/registers.h +@@ -0,0 +1,15 @@ ++/* ++ * 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_IOEXP_REGISTERS_H_ ++#define MCA_IOEXP_REGISTERS_H_ ++ ++#include ++ ++#endif /* MCA_IOEXP_REGISTERS_H_ */ diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0002-ccimx6ulsbcpro-Add-IOEXP-GPIO-support.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0002-ccimx6ulsbcpro-Add-IOEXP-GPIO-support.patch new file mode 100644 index 000000000..bd237aa42 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0002-ccimx6ulsbcpro-Add-IOEXP-GPIO-support.patch @@ -0,0 +1,47 @@ +From 4a2907834799eda9bd681ccd85557d6433666903 Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +Date: Mon, 23 Apr 2018 11:47:38 +0200 +Subject: [PATCH] ccimx6ulsbcpro: Add IOEXP GPIO support + +Synched with v4.14.78/master at: +3f8b03950b323db4ca89b1cdc1c2288f79facaa3 + +Signed-off-by: Alex Gonzalez +--- + arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts | 23 +++++++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts +index afc87b34f441..a818aec68959 100644 +--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts ++++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts +@@ -130,6 +130,29 @@ + irq-gpios = <&gpio5 2 GPIO_ACTIVE_HIGH>; + status = "okay"; + }; ++ ++ mca_ioexp: mca_io@6e { ++ compatible = "digi,mca_ioexp"; ++ reg = <0x6e>; ++ interrupt-parent = <&mca_gpio>; ++ interrupts = <0 IRQ_TYPE_EDGE_FALLING>; ++ restore-config-on-resume; ++ status = "okay"; ++ ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ pinctrl-names = "default"; ++ ++ mca_ioexp_gpio: gpio { ++ compatible = "digi,mca-ioexp-gpio"; ++ gpio-controller; ++ #gpio-cells = <2>; ++ ++ interrupt-parent = <&mca_ioexp>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ }; + }; + + &lcdif { diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0003-ccimx6ulsbcpro-Add-IOEXP-ADC-support.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0003-ccimx6ulsbcpro-Add-IOEXP-ADC-support.patch new file mode 100644 index 000000000..f84a59121 --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0003-ccimx6ulsbcpro-Add-IOEXP-ADC-support.patch @@ -0,0 +1,35 @@ +From d39028a304c0059ee16c65462e3e653670482aca Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +Date: Mon, 23 Apr 2018 11:48:15 +0200 +Subject: [PATCH] ccimx6ulsbcpro: Add IOEXP ADC support + +Synched with v4.14.78/master at: +3f8b03950b323db4ca89b1cdc1c2288f79facaa3 + +Signed-off-by: Alex Gonzalez +--- + arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts +index 7df31112cabf..b2b6a49391f4 100644 +--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts ++++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts +@@ -153,6 +153,12 @@ + interrupt-controller; + #interrupt-cells = <2>; + }; ++ ++ mca_ioexp_adc: adc { ++ compatible = "digi,mca-ioexp-adc"; ++ digi,adc-ch-list = <3 4 5>; ++ digi,adc-vref = <3300000>; ++ }; + }; + }; + +@@ -482,4 +488,3 @@ + >; + }; + }; +- diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0004-ARM-dts-ccimx6ulsbcpro-Configure-touch-GPIO-reset-li.patch b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0004-ARM-dts-ccimx6ulsbcpro-Configure-touch-GPIO-reset-li.patch new file mode 100644 index 000000000..10e92666a --- /dev/null +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc/ccimx6ulsbc/0004-ARM-dts-ccimx6ulsbcpro-Configure-touch-GPIO-reset-li.patch @@ -0,0 +1,38 @@ +From ac65a7be2a14e2c5f90319674527257a53e59402 Mon Sep 17 00:00:00 2001 +From: Alex Gonzalez +Date: Tue, 8 Jan 2019 16:42:40 +0100 +Subject: [PATCH] ARM: dts: ccimx6ulsbcpro: Configure touch GPIO reset line + +The Goodix touch requires both the INT and reset lines in order to fix +an I2C address at startup. + +This commit uses the IOEXP GPIO29 to control the DISP_5V supply which +controls the voltage source for the reset line pull-up. It also sets the +internal pull-up on the INT line so the line is not left floating. + +Signed-off-by: Alex Gonzalez +--- + arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts +index 14274a0d6435..62133ede2736 100644 +--- a/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts ++++ b/arch/arm/boot/dts/imx6ul-ccimx6ulsbcpro.dts +@@ -128,6 +128,7 @@ + interrupt-parent = <&gpio5>; + interrupts = <2 IRQ_TYPE_EDGE_RISING>; + irq-gpios = <&gpio5 2 GPIO_ACTIVE_HIGH>; ++ reset-gpios = <&mca_ioexp_gpio 29 GPIO_ACTIVE_HIGH>; + status = "okay"; + }; + +@@ -366,7 +367,7 @@ + + pinctrl_goodix_touch: goodixgrp{ + fsl,pins = < +- MX6UL_PAD_SNVS_TAMPER2__GPIO5_IO02 0x1020 ++ MX6UL_PAD_SNVS_TAMPER2__GPIO5_IO02 0x1b0b0 + >; + }; + diff --git a/meta-digi-arm/recipes-kernel/linux/linux-fslc_%.bbappend b/meta-digi-arm/recipes-kernel/linux/linux-fslc_%.bbappend index 29b788986..6eb422b06 100644 --- a/meta-digi-arm/recipes-kernel/linux/linux-fslc_%.bbappend +++ b/meta-digi-arm/recipes-kernel/linux/linux-fslc_%.bbappend @@ -13,3 +13,10 @@ SRC_URI_append_ccimx6ul_use-mainline-bsp = " \ file://0010-imx6ul-Add-RTC-MCA-support-for-ConnectCore-6UL-SOM.patch \ file://0011-imx6ul-Add-MCA-power-key-support-for-ConnectCore-6UL.patch \ " + +SRC_URI_append_ccimx6ulsbc_use-mainline-bsp = " \ + file://0001-ccimx6ulsbcpro-Add-IOEXP-core-I2C-support.patch \ + file://0002-ccimx6ulsbcpro-Add-IOEXP-GPIO-support.patch \ + file://0003-ccimx6ulsbcpro-Add-IOEXP-ADC-support.patch \ + file://0004-ARM-dts-ccimx6ulsbcpro-Configure-touch-GPIO-reset-li.patch \ +"