meta-digi-arm: linux-fslc: ccimx6ulsbc: Add IOEXP support

Signed-off-by: Alex Gonzalez <alex.gonzalez@digi.com>
This commit is contained in:
Alex Gonzalez 2019-04-01 17:16:24 +02:00
parent 4cf572edf5
commit bcb942fc5b
5 changed files with 1019 additions and 0 deletions

View File

@ -0,0 +1,892 @@
From f56dbb1e8b6a501e84d69ac5f18ec7a66cb4faa7 Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
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 <alex.gonzalez@digi.com>
---
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 <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/suspend.h>
+#include <linux/proc_fs.h>
+#include <linux/kthread.h>
+#include <linux/uaccess.h>
+#include <linux/reboot.h>
+
+#include <linux/mfd/mca-common/core.h>
+#include <linux/mfd/mca-ioexp/core.h>
+
+#include <asm/unaligned.h>
+
+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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/of.h>
+
+#include <linux/mfd/core.h>
+#include <linux/mfd/mca-common/core.h>
+#include <linux/mfd/mca-ioexp/core.h>
+
+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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/mfd/core.h>
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/mfd/mca-ioexp/core.h>
+#include <linux/mfd/mca-common/core.h>
+
+#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 <linux/interrupt.h>
+#include <linux/mfd/mca-ioexp/registers.h>
+
+#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 <linux/bitops.h>
+
+#endif /* MCA_IOEXP_REGISTERS_H_ */

View File

@ -0,0 +1,47 @@
From 4a2907834799eda9bd681ccd85557d6433666903 Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
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 <alex.gonzalez@digi.com>
---
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 {

View File

@ -0,0 +1,35 @@
From d39028a304c0059ee16c65462e3e653670482aca Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
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 <alex.gonzalez@digi.com>
---
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 @@
>;
};
};
-

View File

@ -0,0 +1,38 @@
From ac65a7be2a14e2c5f90319674527257a53e59402 Mon Sep 17 00:00:00 2001
From: Alex Gonzalez <alex.gonzalez@digi.com>
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 <alex.gonzalez@digi.com>
---
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
>;
};

View File

@ -13,3 +13,10 @@ SRC_URI_append_ccimx6ul_use-mainline-bsp = " \
file://0010-imx6ul-Add-RTC-MCA-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 \ 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 \
"