This is a core mfd driver for the on board embedded controllers found on the
Data Modul embedded CPU modules. The embedded controller may provide the
following functions: i2c bus, gpio, watchdog, uart and rtm.

Signed-off-by: Zahari Doychev <zahari.doyc...@linux.com>
---
 drivers/staging/Kconfig          |   2 +-
 drivers/staging/Makefile         |   1 +-
 drivers/staging/dmec/Kconfig     |   9 +-
 drivers/staging/dmec/Makefile    |   1 +-
 drivers/staging/dmec/dmec-core.c | 500 ++++++++++++++++++++++++++++++++-
 drivers/staging/dmec/dmec.h      |   6 +-
 6 files changed, 519 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/dmec/Kconfig
 create mode 100644 drivers/staging/dmec/Makefile
 create mode 100644 drivers/staging/dmec/dmec-core.c
 create mode 100644 drivers/staging/dmec/dmec.h

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 58a7b35..d95bee2 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -106,4 +106,6 @@ source "drivers/staging/greybus/Kconfig"
 
 source "drivers/staging/vc04_services/Kconfig"
 
+source "drivers/staging/dmec/Kconfig"
+
 endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 2fa9745..73b4833 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -42,3 +42,4 @@ obj-$(CONFIG_ISDN_I4L)                += i4l/
 obj-$(CONFIG_KS7010)           += ks7010/
 obj-$(CONFIG_GREYBUS)          += greybus/
 obj-$(CONFIG_BCM2708_VCHIQ)    += vc04_services/
+obj-$(CONFIG_MFD_DMEC)         += dmec/
diff --git a/drivers/staging/dmec/Kconfig b/drivers/staging/dmec/Kconfig
new file mode 100644
index 0000000..3641907
--- /dev/null
+++ b/drivers/staging/dmec/Kconfig
@@ -0,0 +1,9 @@
+config MFD_DMEC
+       tristate "Data Modul Embedded Controller MFD"
+       select MFD_CORE
+       help
+           Say Y here to enable support for a Data Module embedded controller
+           found on Data Module CPU Modules
+
+           To compile this driver as a module, say M here: the module will be
+            called dmec
diff --git a/drivers/staging/dmec/Makefile b/drivers/staging/dmec/Makefile
new file mode 100644
index 0000000..859163b
--- /dev/null
+++ b/drivers/staging/dmec/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_MFD_DMEC)         += dmec-core.o
diff --git a/drivers/staging/dmec/dmec-core.c b/drivers/staging/dmec/dmec-core.c
new file mode 100644
index 0000000..40f5481
--- /dev/null
+++ b/drivers/staging/dmec/dmec-core.c
@@ -0,0 +1,500 @@
+/*
+ * dmec-core: Data Modul AG mfd embedded controller driver
+ *
+ * Zahari Doychev  <zahari.doyc...@linux.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/acpi.h>
+#include <linux/nls.h>
+#include "dmec.h"
+
+#define DMEC_LBAR                      0x00 /* Index addressing 4 bytes */
+#define DMEC_ECVER0                    0x04
+#define DMEC_ECVER1                    0x05
+#define DMEC_ECFTR0                    0x07
+#define DMEC_ECFTR1                    0x08
+#define DMEC_FPGAVER0                  0x0c
+#define DMEC_FPGAVER1                  0x0d
+#define DMEC_FPGABLD0                  0x0e
+#define DMEC_FPGABLD1                  0x0f
+#define DMEC_IRQCFG0                   0x10
+#define DMEC_IRQCFG1                   0x11
+#define DMEC_RTM_START                 0x60
+#define DMEC_RTM_END                   0x6e
+
+#define DMEC_MAX_GPIO_CHIPS            2
+
+#define DMEC_VERSION_LEN               32
+
+#define DMEC_FEATURE_BIT_I2C           BIT(0)
+#define DMEC_FEATURE_BIT_WDT           BIT(4)
+#define DMEC_FEATURE_BIT_GPIO          (3 << 6)
+
+#define DMEC_REG_MAX           0x7f
+#define DMEC_MAX_DEVS          ARRAY_SIZE(dmec_devs)
+#define DMEC_MAX_IO_RES                2
+#define DMEC_STR_SZ            128
+
+static bool i_addr;
+module_param(i_addr, bool, 0644);
+MODULE_PARM_DESC(i_addr, "Enable register index addressing usage");
+
+static const char * const fw_types[] = {"release", "custom",
+                                       "debug", "reserved"};
+
+enum dmec_cells {
+       DMEC_I2C = 0,
+       DMEC_GPIOA,
+       DMEC_GPIOB,
+       DMEC_WDT,
+       DMEC_RTM
+};
+
+struct dmec_features {
+       unsigned int i2c_buses:2;
+       unsigned int uarts:2;
+       unsigned int wdt:1;
+       unsigned int rsvd1:1;
+       unsigned int gpio_chips:2;
+       unsigned int spi_buses:2;
+       unsigned int can_buses:2;
+       unsigned int rsvd2:1;
+       unsigned int nmi:1;
+       unsigned int sci:1;
+       unsigned int smi:1;
+};
+
+struct dmec_info {
+       unsigned int ec_ver:4;
+       unsigned int ec_rev:4;
+       unsigned int ec_num:2;
+       unsigned int ec_type:2;
+       unsigned int ec_dbg:4;
+       u16 fpga_ver;
+       u16 fpga_bld;
+       char version[DMEC_VERSION_LEN];
+};
+
+struct dmec_device_data {
+       void __iomem *io_base;
+       void __iomem *io_index;
+       void __iomem *io_data;
+       union {
+               u16 feature_mask;
+               struct dmec_features ftr;
+       } u;
+       struct device *dev;
+       struct dmec_info info;
+       struct regmap *regmap;
+       /* use index addressing for register access if set*/
+       bool i_addr;
+};
+
+struct dmec_platform_data {
+       int (*get_info)(struct dmec_device_data *);
+       int (*register_cells)(struct dmec_device_data *);
+};
+
+static struct dmec_i2c_platform_data dmec_i2c_data = {
+       .reg_shift      = 0,            /* two bytes between registers */
+       .reg_io_width   = 1,            /* register io read/write width */
+       .clock_khz      = 50000,        /* input clock of 50MHz */
+};
+
+static struct dmec_gpio_platform_data dmec_gpio_pdata[DMEC_MAX_GPIO_CHIPS] = {
+       {
+               .gpio_base = -1,
+               .chip_num = 0,
+       },
+       {
+               .gpio_base = -1,
+               .chip_num = 1,
+       },
+};
+
+/* The gpio block can use up to DMEC_GPIO_MAX_IRQS APIC irqs */
+static struct resource dmec_gpio_irq_resources[DMEC_MAX_GPIO_CHIPS];
+static struct resource dmec_wdt_irq_resource;
+static struct resource dmec_i2c_irq_resource;
+
+static struct mfd_cell dmec_devs[] = {
+       [DMEC_I2C] = {
+               .name = "dmec-i2c",
+               .platform_data = &dmec_i2c_data,
+               .pdata_size = sizeof(dmec_i2c_data),
+               .resources = &dmec_i2c_irq_resource,
+               .num_resources = 1,
+       },
+       [DMEC_GPIOA] = {
+               .name = "dmec-gpio",
+               .platform_data = &dmec_gpio_pdata[0],
+               .pdata_size = sizeof(struct dmec_gpio_platform_data),
+               .resources = &dmec_gpio_irq_resources[0],
+               .num_resources = 1,
+       },
+       [DMEC_GPIOB] = {
+               .name = "dmec-gpio",
+               .platform_data = &dmec_gpio_pdata[1],
+               .pdata_size = sizeof(struct dmec_gpio_platform_data),
+               .resources = &dmec_gpio_irq_resources[1],
+               .num_resources = 1,
+       },
+       [DMEC_WDT] = {
+               .name = "dmec-wdt",
+               .resources = &dmec_wdt_irq_resource,
+               .num_resources = 1,
+       },
+       [DMEC_RTM] = {
+               .name = "dmec-rtm",
+       },
+};
+
+static void dmec_get_gpio_irqs(struct dmec_device_data *ec)
+{
+       unsigned int irq, val;
+
+       regmap_read(ec->regmap, DMEC_IRQCFG1, &val);
+       irq = (val >> 4) & 0xf;
+       dmec_gpio_irq_resources[0].start = irq;
+       dmec_gpio_irq_resources[0].end = irq;
+       dmec_gpio_irq_resources[0].flags = IORESOURCE_IRQ;
+       irq = val & 0xf;
+       dmec_gpio_irq_resources[1].start = irq;
+       dmec_gpio_irq_resources[1].end = irq;
+       dmec_gpio_irq_resources[1].flags = IORESOURCE_IRQ;
+}
+
+static void dmec_get_wdt_irq(struct dmec_device_data *ec)
+{
+       unsigned int irq, val;
+
+       regmap_read(ec->regmap, DMEC_IRQCFG0, &val);
+       irq = val & 0xf;
+       dmec_wdt_irq_resource.start = irq;
+       dmec_wdt_irq_resource.end = irq;
+       dmec_wdt_irq_resource.flags = IORESOURCE_IRQ;
+}
+
+static void dmec_get_i2c_irq(struct dmec_device_data *ec)
+{
+       unsigned int irq, val;
+
+       regmap_read(ec->regmap, DMEC_IRQCFG0, &val);
+       irq = (val >> 4) & 0xf;
+       dmec_i2c_irq_resource.start = irq;
+       dmec_i2c_irq_resource.end = irq;
+       dmec_i2c_irq_resource.flags = IORESOURCE_IRQ;
+}
+
+static int dmec_rtm_detect(struct dmec_device_data *ec)
+{
+       unsigned int val, n;
+
+       for (n = DMEC_RTM_START; n <= DMEC_RTM_END; n++) {
+               regmap_read(ec->regmap, n, &val);
+               if (val != 0xff)
+                       return 1;
+       }
+
+       return 0;
+}
+
+static int dmec_register_cells(struct dmec_device_data *ec)
+{
+       struct mfd_cell cells[DMEC_MAX_DEVS];
+       u8 n_dev = 0;
+
+       if (ec->u.feature_mask & DMEC_FEATURE_BIT_I2C) {
+               dmec_get_i2c_irq(ec);
+               dmec_devs[DMEC_I2C].id = n_dev;
+               cells[n_dev++] = dmec_devs[DMEC_I2C];
+       }
+
+       if (ec->u.feature_mask & DMEC_FEATURE_BIT_GPIO) {
+               dmec_get_gpio_irqs(ec);
+               dmec_devs[DMEC_GPIOA].id = n_dev;
+               cells[n_dev++] = dmec_devs[DMEC_GPIOA];
+               if (ec->u.ftr.gpio_chips > 1) {
+                       dmec_devs[DMEC_GPIOB].id = n_dev;
+                       cells[n_dev++] = dmec_devs[DMEC_GPIOB];
+               }
+       }
+
+       if (ec->u.feature_mask & DMEC_FEATURE_BIT_WDT) {
+               dmec_get_wdt_irq(ec);
+               dmec_devs[DMEC_WDT].id = n_dev;
+               cells[n_dev++] = dmec_devs[DMEC_WDT];
+       }
+
+       if (dmec_rtm_detect(ec)) {
+               dmec_devs[DMEC_RTM].id = n_dev;
+               cells[n_dev++] = dmec_devs[DMEC_RTM];
+       }
+
+       return devm_mfd_add_devices(ec->dev, 0,
+                                   cells, n_dev, NULL, 0, NULL);
+}
+
+static int dmec_read16(struct dmec_device_data *ec, u8 reg)
+{
+       unsigned int lsb, msb;
+       int ret;
+
+       ret = regmap_read(ec->regmap, reg, &lsb);
+       ret = regmap_read(ec->regmap, reg + 0x1, &msb);
+
+       return (msb << 8) | lsb;
+}
+
+static int dmec_get_info(struct dmec_device_data *ec)
+{
+       unsigned int ver0, ver1;
+
+       regmap_read(ec->regmap, DMEC_ECVER0, &ver0);
+       regmap_read(ec->regmap, DMEC_ECVER1, &ver1);
+       if (ver0 == 0xff && ver1 == 0xff)
+               return -ENODEV;
+
+       ec->u.feature_mask = dmec_read16(ec, DMEC_ECFTR0);
+
+       ec->info.ec_ver = (ver0 >> 4) & 0xf;
+       ec->info.ec_rev = ver0 & 0xf;
+       ec->info.ec_num = ver1 & 0x3;
+       ec->info.ec_type = (ver1 >> 2) & 0x3;
+       ec->info.ec_dbg = (ver1 >> 4) & 0xf;
+
+       ec->info.fpga_ver = dmec_read16(ec, DMEC_FPGAVER0);
+       ec->info.fpga_bld = dmec_read16(ec, DMEC_FPGABLD0);
+
+       return 0;
+}
+
+static int dmec_regmap_reg_read(void *context,
+                               unsigned int reg, unsigned int *val)
+{
+       struct dmec_device_data *ec = context;
+
+       if (ec->i_addr) {
+               iowrite8(reg, ec->io_index);
+               *val = ioread8(ec->io_data);
+       } else {
+               *val = ioread8(ec->io_base + reg);
+       }
+
+       return 0;
+}
+
+static int dmec_regmap_reg_write(void *context,
+                                unsigned int reg, unsigned int val)
+{
+       struct dmec_device_data *ec = context;
+
+       if (ec->i_addr) {
+               iowrite8(reg, ec->io_index);
+               iowrite8(val, ec->io_data);
+       } else {
+               iowrite8(val, ec->io_base + reg);
+       }
+
+       return 0;
+}
+
+struct regmap *dmec_get_regmap(struct device *dev)
+{
+       struct dmec_device_data *ec = dev_get_drvdata(dev);
+
+       return ec->regmap;
+}
+EXPORT_SYMBOL(dmec_get_regmap);
+
+static ssize_t dmec_version_show(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       struct dmec_device_data *ec = dev_get_drvdata(dev);
+
+       return scnprintf(buf, PAGE_SIZE, "%s\n", ec->info.version);
+}
+
+static ssize_t dmec_fw_type_show(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       struct dmec_device_data *ec = dev_get_drvdata(dev);
+
+       return scnprintf(buf, PAGE_SIZE,
+                       "ver:        %u.%u\n"
+                       "ec:         %u\n"
+                       "type:       %s\n"
+                       "debug:      %u\n"
+                       "fpga ver:   %x\n"
+                       "fpga build: %x\n",
+                       ec->info.ec_ver,
+                       ec->info.ec_rev,
+                       ec->info.ec_num,
+                       fw_types[ec->info.ec_type % (ARRAY_SIZE(fw_types) - 1)],
+                       ec->info.ec_dbg,
+                       ec->info.fpga_ver,
+                       ec->info.fpga_bld);
+}
+
+static ssize_t dmec_features_show(struct device *dev,
+                                 struct device_attribute *attr,
+                                 char *buf)
+{
+       struct dmec_device_data *ec = dev_get_drvdata(dev);
+       struct dmec_features *ftr = &ec->u.ftr;
+
+       return scnprintf(buf, PAGE_SIZE,
+                        "i2c buses:    %2u\n"
+                        "uarts:        %2u\n"
+                        "watchdog:     %2u\n"
+                        "gpio chips:   %2u\n"
+                        "spi buses:    %2u\n"
+                        "can buses:    %2u\n"
+                        "nmi:          %2u\n"
+                        "sci:          %2u\n"
+                        "smi:          %2u\n",
+                        ftr->i2c_buses, ftr->uarts, ftr->wdt,
+                        ftr->gpio_chips, ftr->spi_buses, ftr->can_buses,
+                        ftr->nmi, ftr->sci, ftr->smi);
+}
+
+static DEVICE_ATTR(version, 0444, dmec_version_show, NULL);
+static DEVICE_ATTR(fw_type, 0444, dmec_fw_type_show, NULL);
+static DEVICE_ATTR(features, 0444, dmec_features_show, NULL);
+
+static struct attribute *dmec_attributes[] = {
+       &dev_attr_version.attr,
+       &dev_attr_fw_type.attr,
+       &dev_attr_features.attr,
+       NULL
+};
+
+static const struct attribute_group ec_attr_group = {
+       .attrs = dmec_attributes,
+};
+
+static int dmec_detect_device(struct dmec_device_data *ec)
+{
+       int ret;
+
+       ret = dmec_get_info(ec);
+       if (ret)
+               return ret;
+
+       ret = scnprintf(ec->info.version, sizeof(ec->info.version),
+                       "%u.%u",
+                       ec->info.ec_ver, ec->info.ec_rev);
+       if (ret < 0)
+               return ret;
+
+       dev_info(ec->dev, "v%s (%s) features: %#x\n",
+                ec->info.version,
+                fw_types[ec->info.ec_type % (ARRAY_SIZE(fw_types) - 1)],
+                ec->u.feature_mask);
+
+       ret = dmec_register_cells(ec);
+       if (ret)
+               return ret;
+
+       return sysfs_create_group(&ec->dev->kobj, &ec_attr_group);
+}
+
+static const struct regmap_config dmec_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .reg_stride = 1,
+       .max_register = DMEC_REG_MAX,
+       .reg_write = dmec_regmap_reg_write,
+       .reg_read = dmec_regmap_reg_read,
+       .cache_type = REGCACHE_NONE,
+       .fast_io = true,
+};
+
+static int dmec_mfd_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct dmec_device_data *ec;
+       struct resource *io_idx, *io;
+       int ret;
+
+       ret = platform_device_add_data(pdev, NULL, 0);
+       if (ret)
+               return ret;
+
+       ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
+       if (!ec)
+               return -ENOMEM;
+
+       io_idx = platform_get_resource(pdev, IORESOURCE_IO, 0);
+       if (!io_idx)
+               return -EINVAL;
+
+       io = platform_get_resource(pdev, IORESOURCE_IO, 1);
+       if (!io) {
+               dev_info(dev, "falling back to index addressing\n");
+               ec->io_base = devm_ioport_map(dev, io_idx->start,
+                                             io_idx->end - io_idx->start);
+               i_addr = true;
+       } else
+               ec->io_base = devm_ioport_map(dev, io->start,
+                                             io->end - io->start);
+
+       if (IS_ERR(ec->io_base))
+               return PTR_ERR(ec->io_base);
+
+       /* In index mode registers @ 0x0 and 0x2 are used by the BIOS */
+       ec->i_addr = i_addr;
+       ec->io_index = ec->io_base + 1;
+       ec->io_data = ec->io_base + 3;
+       ec->dev = dev;
+
+       ec->regmap = devm_regmap_init(dev, NULL, ec, &dmec_regmap_config);
+       if (IS_ERR(ec->regmap))
+               return PTR_ERR(ec->regmap);
+       regcache_cache_bypass(ec->regmap, true);
+
+       platform_set_drvdata(pdev, ec);
+
+       return dmec_detect_device(ec);
+}
+
+static int dmec_mfd_remove(struct platform_device *pdev)
+{
+       struct dmec_device_data *ec = platform_get_drvdata(pdev);
+
+       sysfs_remove_group(&ec->dev->kobj, &ec_attr_group);
+
+       return 0;
+}
+
+static const struct acpi_device_id dmec_acpi_ids[] = {
+       {"DMEC0001", 0},
+       {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, dmec_acpi_ids);
+
+static struct platform_driver dmec_driver = {
+       .probe = dmec_mfd_probe,
+       .remove = dmec_mfd_remove,
+       .driver = {
+               .name = "dmec",
+               .acpi_match_table = ACPI_PTR(dmec_acpi_ids)
+       },
+};
+
+module_platform_driver(dmec_driver);
+
+MODULE_DESCRIPTION("DMO embedded controller core driver");
+MODULE_AUTHOR("Zahari Doychev <zahari.doyc...@linux.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dmec-core");
diff --git a/drivers/staging/dmec/dmec.h b/drivers/staging/dmec/dmec.h
new file mode 100644
index 0000000..4d8712d
--- /dev/null
+++ b/drivers/staging/dmec/dmec.h
@@ -0,0 +1,6 @@
+#ifndef _LINUX_MFD_DMEC_H
+#define _LINUX_MFD_DMEC_H
+
+struct regmap *dmec_get_regmap(struct device *dev);
+
+#endif
-- 
git-series 0.8.10

Reply via email to