summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/gpio/Kconfig6
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-apalis-tk1-k20.c216
-rw-r--r--drivers/iio/adc/Kconfig6
-rw-r--r--drivers/iio/adc/Makefile1
-rw-r--r--drivers/iio/adc/apalis-tk1-k20_adc.c200
-rw-r--r--drivers/input/touchscreen/Kconfig10
-rw-r--r--drivers/input/touchscreen/Makefile1
-rw-r--r--drivers/input/touchscreen/apalis-tk1-k20_ts.c230
-rw-r--r--drivers/mfd/Kconfig14
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/apalis-tk1-k20-ezp.h46
-rw-r--r--drivers/mfd/apalis-tk1-k20.c940
13 files changed, 1672 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 59d5f24dc113..2bb28efe1e08 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -643,6 +643,12 @@ config GPIO_RDC321X
comment "SPI GPIO expanders:"
+config GPIO_APALIS_TK1_K20
+ tristate "GPIOs of K20 MCU on Apalis TK1"
+ depends on MFD_APALIS_TK1_K20
+ help
+ This enables support for GPIOs of K20 MCU found on Apalis TK1.
+
config GPIO_MAX7301
tristate "Maxim MAX7301 GPIO expander"
depends on SPI_MASTER
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index e5a7fe6f0ea8..1134650de379 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o
obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o
obj-$(CONFIG_GPIO_ADP5588) += gpio-adp5588.o
obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o
+obj-$(CONFIG_GPIO_APALIS_TK1_K20) += gpio-apalis-tk1-k20.o
obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o
obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o
diff --git a/drivers/gpio/gpio-apalis-tk1-k20.c b/drivers/gpio/gpio-apalis-tk1-k20.c
new file mode 100644
index 000000000000..ddad0153b88b
--- /dev/null
+++ b/drivers/gpio/gpio-apalis-tk1-k20.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2016 Toradex AG
+ * Dominik Sliwa <dominik.sliwa@toradex.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+
+#include <linux/mfd/apalis-tk1-k20.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+
+struct apalis_tk1_k20_gpio {
+ struct gpio_chip chip;
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20;
+};
+
+static int apalis_tk1_k20_gpio_input(struct gpio_chip *chip, unsigned offset)
+{
+ struct apalis_tk1_k20_gpio *gpio = container_of(chip,
+ struct apalis_tk1_k20_gpio, chip);
+
+ apalis_tk1_k20_lock(gpio->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_NO,
+ offset);
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_STA,
+ 0);
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_gpio_output(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct apalis_tk1_k20_gpio *gpio = container_of(chip,
+ struct apalis_tk1_k20_gpio, chip);
+ int status;
+
+ apalis_tk1_k20_lock(gpio->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_NO,
+ offset);
+ status = APALIS_TK1_K20_GPIO_STA_OE;
+ status += value ? APALIS_TK1_K20_GPIO_STA_VAL : 0;
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_STA,
+ status);
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct apalis_tk1_k20_gpio *gpio = container_of(chip,
+ struct apalis_tk1_k20_gpio, chip);
+ int value;
+
+ apalis_tk1_k20_lock(gpio->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_NO,
+ offset);
+ if (apalis_tk1_k20_reg_read(gpio->apalis_tk1_k20,
+ APALIS_TK1_K20_GPIO_STA, &value) < 0) {
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+ return -ENODEV;
+ }
+ value &= APALIS_TK1_K20_GPIO_STA_VAL;
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+
+ return value ? 1 : 0;
+}
+
+static int apalis_tk1_k20_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct apalis_tk1_k20_gpio *gpio = container_of(chip,
+ struct apalis_tk1_k20_gpio, chip);
+ int status = 0;
+
+ pr_debug("APALIS TK1 K20 GPIO %s\n", __func__);
+
+ apalis_tk1_k20_lock(gpio->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_NO,
+ offset);
+ if (apalis_tk1_k20_reg_read(gpio->apalis_tk1_k20,
+ APALIS_TK1_K20_GPIO_NO, &status) < 0) {
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+ return -ENODEV;
+ }
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+
+ return status;
+}
+
+static void apalis_tk1_k20_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct apalis_tk1_k20_gpio *gpio = container_of(chip,
+ struct apalis_tk1_k20_gpio, chip);
+
+ apalis_tk1_k20_lock(gpio->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_NO,
+ offset);
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20,
+ APALIS_TK1_K20_GPIO_STA, 0);
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+}
+
+
+static void apalis_tk1_k20_gpio_set(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct apalis_tk1_k20_gpio *gpio = container_of(chip,
+ struct apalis_tk1_k20_gpio, chip);
+ int status;
+
+ apalis_tk1_k20_lock(gpio->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_NO,
+ offset);
+ if (apalis_tk1_k20_reg_read(gpio->apalis_tk1_k20,
+ APALIS_TK1_K20_GPIO_STA, &status) < 0) {
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+ return;
+ }
+
+ status &= ~APALIS_TK1_K20_GPIO_STA_VAL;
+ status += value ? APALIS_TK1_K20_GPIO_STA_VAL : 0;
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_STA,
+ status);
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+}
+
+static int __init apalis_tk1_k20_gpio_probe(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_gpio *priv;
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20;
+ int status;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ apalis_tk1_k20 = dev_get_drvdata(pdev->dev.parent);
+ if (!apalis_tk1_k20)
+ return -ENODEV;
+ priv->apalis_tk1_k20 = apalis_tk1_k20;
+
+ platform_set_drvdata(pdev, priv);
+
+ apalis_tk1_k20_lock(apalis_tk1_k20);
+
+ /* TBD: some code */
+
+ apalis_tk1_k20_unlock(apalis_tk1_k20);
+
+ priv->chip.base = -1;
+ priv->chip.can_sleep = 1;
+ priv->chip.dev = &pdev->dev;
+ priv->chip.owner = THIS_MODULE;
+ priv->chip.get = apalis_tk1_k20_gpio_get;
+ priv->chip.set = apalis_tk1_k20_gpio_set;
+ priv->chip.direction_input = apalis_tk1_k20_gpio_input;
+ priv->chip.direction_output = apalis_tk1_k20_gpio_output;
+ priv->chip.request = apalis_tk1_k20_gpio_request;
+ priv->chip.free = apalis_tk1_k20_gpio_free;
+ /* TODO: include as a define somewhere */
+ priv->chip.ngpio = 160;
+
+ status = gpiochip_add(&priv->chip);
+
+ return status;
+}
+
+static int __exit apalis_tk1_k20_gpio_remove(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_gpio *priv = platform_get_drvdata(pdev);
+
+ return gpiochip_remove(&priv->chip);
+}
+
+static const struct platform_device_id apalis_tk1_k20_gpio_idtable[] = {
+ {
+ .name = "apalis-tk1-k20-gpio",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, apalis_tk1_k20_gpio_idtable);
+
+static struct platform_driver apalis_tk1_k20_gpio_driver = {
+ .id_table = apalis_tk1_k20_gpio_idtable,
+ .remove = __exit_p(apalis_tk1_k20_gpio_remove),
+ .driver = {
+ .name = "apalis-tk1-k20-gpio",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver_probe(apalis_tk1_k20_gpio_driver,
+ &apalis_tk1_k20_gpio_probe);
+
+MODULE_DESCRIPTION("GPIO driver for K20 MCU on Apalis TK1");
+MODULE_AUTHOR("Dominik Sliwa <dominik.sliwa@toradex.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index ab0767e6727e..5a69e23c4ffb 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -94,6 +94,12 @@ config AD7887
To compile this driver as a module, choose M here: the
module will be called ad7887.
+config APALIS_TK1_K20_ADC
+ tristate "ADCs of K20 MCU on Apalis TK1"
+ depends on MFD_APALIS_TK1_K20
+ help
+ This enables support for ADCs of K20 MCU found on Apalis TK1.
+
config AT91_ADC
tristate "Atmel AT91 ADC"
depends on ARCH_AT91
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 0a825bed43f6..c9150844cbc2 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_AD7476) += ad7476.o
obj-$(CONFIG_AD7791) += ad7791.o
obj-$(CONFIG_AD7793) += ad7793.o
obj-$(CONFIG_AD7887) += ad7887.o
+obj-$(CONFIG_APALIS_TK1_K20_ADC) += apalis-tk1-k20_adc.o
obj-$(CONFIG_AT91_ADC) += at91_adc.o
obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o
obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
diff --git a/drivers/iio/adc/apalis-tk1-k20_adc.c b/drivers/iio/adc/apalis-tk1-k20_adc.c
new file mode 100644
index 000000000000..b08f09141cdc
--- /dev/null
+++ b/drivers/iio/adc/apalis-tk1-k20_adc.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2016 Toradex AG
+ * Dominik Sliwa <dominik.sliwa@toradex.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+
+#include <linux/mfd/apalis-tk1-k20.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/driver.h>
+#include <linux/iio/machine.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct apalis_tk1_k20_adc {
+ struct iio_dev chip;
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20;
+};
+
+static int apalis_tk1_k20_get_adc_result(struct apalis_tk1_k20_adc *adc, int id,
+ int *val)
+{
+ uint8_t adc_read[2];
+ int adc_register;
+
+ switch (id) {
+ case APALIS_TK1_K20_ADC1:
+ adc_register = APALIS_TK1_K20_ADC_CH0L;
+ break;
+ case APALIS_TK1_K20_ADC2:
+ adc_register = APALIS_TK1_K20_ADC_CH1L;
+ break;
+ case APALIS_TK1_K20_ADC3:
+ adc_register = APALIS_TK1_K20_ADC_CH2L;
+ break;
+ case APALIS_TK1_K20_ADC4:
+ adc_register = APALIS_TK1_K20_ADC_CH3L;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ apalis_tk1_k20_lock(adc->apalis_tk1_k20);
+
+ if (apalis_tk1_k20_reg_read_bulk(adc->apalis_tk1_k20, adc_register,
+ adc_read, 2) < 0) {
+ apalis_tk1_k20_unlock(adc->apalis_tk1_k20);
+ return -EIO;
+ }
+ *val = (adc_read[1] << 8) + adc_read[0];
+
+ apalis_tk1_k20_unlock(adc->apalis_tk1_k20);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_adc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct apalis_tk1_k20_adc *adc = iio_priv(indio_dev);
+ enum apalis_tk1_k20_adc_id id = chan->channel;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = apalis_tk1_k20_get_adc_result(adc, id, val) ?
+ -EIO : IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ *val = APALIS_TK1_K20_VADC_MILI;
+ *val2 = APALIS_TK1_K20_ADC_BITS;
+ ret = IIO_VAL_FRACTIONAL_LOG2;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct iio_info apalis_tk1_k20_adc_info = {
+ .read_raw = &apalis_tk1_k20_adc_read_raw,
+ .driver_module = THIS_MODULE,
+};
+
+#define APALIS_TK1_K20_CHAN(_id, _type) { \
+ .type = _type, \
+ .indexed = 1, \
+ .channel = APALIS_TK1_K20_##_id, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .datasheet_name = #_id, \
+}
+
+static const struct iio_chan_spec apalis_tk1_k20_adc_channels[] = {
+ [APALIS_TK1_K20_ADC1] = APALIS_TK1_K20_CHAN(ADC1, IIO_VOLTAGE),
+ [APALIS_TK1_K20_ADC2] = APALIS_TK1_K20_CHAN(ADC2, IIO_VOLTAGE),
+ [APALIS_TK1_K20_ADC3] = APALIS_TK1_K20_CHAN(ADC3, IIO_VOLTAGE),
+ [APALIS_TK1_K20_ADC4] = APALIS_TK1_K20_CHAN(ADC4, IIO_VOLTAGE),
+};
+
+
+static int __init apalis_tk1_k20_adc_probe(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_adc *priv;
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = iio_device_alloc(sizeof(*priv));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ apalis_tk1_k20 = dev_get_drvdata(pdev->dev.parent);
+ if (!apalis_tk1_k20) {
+ ret = -ENODEV;
+ goto err_iio_device;
+ }
+
+ priv = iio_priv(indio_dev);
+ priv->apalis_tk1_k20 = apalis_tk1_k20;
+
+ apalis_tk1_k20_lock(apalis_tk1_k20);
+
+ /* Enable ADC */
+ apalis_tk1_k20_reg_write(apalis_tk1_k20, APALIS_TK1_K20_ADCREG, 0x01);
+
+ apalis_tk1_k20_unlock(apalis_tk1_k20);
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ indio_dev->dev.of_node = pdev->dev.of_node;
+ indio_dev->dev.parent = &pdev->dev;
+ indio_dev->name = pdev->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &apalis_tk1_k20_adc_info;
+ indio_dev->channels = apalis_tk1_k20_adc_channels;
+ indio_dev->num_channels = ARRAY_SIZE(apalis_tk1_k20_adc_channels);
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "iio dev register err: %d\n", ret);
+ goto err_iio_device;
+ }
+
+ return 0;
+
+err_iio_device:
+ iio_device_free(indio_dev);
+ return ret;
+}
+
+static int __exit apalis_tk1_k20_adc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20 = dev_get_drvdata(
+ pdev->dev.parent);
+
+ apalis_tk1_k20_lock(apalis_tk1_k20);
+
+ /* Disable ADC */
+ apalis_tk1_k20_reg_write(apalis_tk1_k20, APALIS_TK1_K20_ADCREG, 0x00);
+
+ apalis_tk1_k20_unlock(apalis_tk1_k20);
+
+ iio_device_unregister(indio_dev);
+ iio_device_free(indio_dev);
+
+ return 0;
+}
+
+static const struct platform_device_id apalis_tk1_k20_adc_idtable[] = {
+ {
+ .name = "apalis-tk1-k20-adc",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, apalis_tk1_k20_adc_idtable);
+
+static struct platform_driver apalis_tk1_k20_adc_driver = {
+ .id_table = apalis_tk1_k20_adc_idtable,
+ .remove = __exit_p(apalis_tk1_k20_adc_remove),
+ .driver = {
+ .name = "apalis-tk1-k20-adc",
+ .owner = THIS_MODULE,
+ },
+};
+module_platform_driver_probe(apalis_tk1_k20_adc_driver,
+ &apalis_tk1_k20_adc_probe);
+
+MODULE_DESCRIPTION("K20 ADCs on Apalis TK1");
+MODULE_AUTHOR("Dominik Sliwa");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 36604667f2ae..f8d6506917ed 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -86,6 +86,16 @@ config TOUCHSCREEN_AD7879_SPI
To compile this driver as a module, choose M here: the
module will be called ad7879-spi.
+config TOUCHSCREEN_APALIS_TK1_K20
+ tristate "K20 based touchscreen controller on Apalis TK1"
+ depends on MFD_APALIS_TK1_K20
+ help
+ Say Y here if you want to support the touchscreen controller
+ implemented in the K20 found on Apalis TK1.
+
+ To compile this driver as a module, choose M here: the module will be
+ called apalis-tk1-k20_ts.
+
config TOUCHSCREEN_ATMEL_MXT
tristate "Atmel mXT I2C Touchscreen"
depends on I2C
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 3d7467601b5c..bc35df0ee270 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_TOUCHSCREEN_AD7879) += ad7879.o
obj-$(CONFIG_TOUCHSCREEN_AD7879_I2C) += ad7879-i2c.o
obj-$(CONFIG_TOUCHSCREEN_AD7879_SPI) += ad7879-spi.o
obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o
+obj-$(CONFIG_TOUCHSCREEN_APALIS_TK1_K20) += apalis-tk1-k20_ts.o
obj-$(CONFIG_TOUCHSCREEN_ATMEL_MXT) += atmel_mxt_ts.o
obj-$(CONFIG_TOUCHSCREEN_ATMEL_TSADCC) += atmel_tsadcc.o
obj-$(CONFIG_TOUCHSCREEN_AUO_PIXCIR) += auo-pixcir-ts.o
diff --git a/drivers/input/touchscreen/apalis-tk1-k20_ts.c b/drivers/input/touchscreen/apalis-tk1-k20_ts.c
new file mode 100644
index 000000000000..f1fd11e87468
--- /dev/null
+++ b/drivers/input/touchscreen/apalis-tk1-k20_ts.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2016 Toradex AG
+ * Dominik Sliwa <dominik.sliwa@toradex.com>
+ *
+ * Based on driver for the Freescale Semiconductor MC13783 touchscreen by:
+ * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright (C) 2009 Sascha Hauer, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+#include <linux/platform_device.h>
+#include <linux/mfd/apalis-tk1-k20.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+
+#define APALIS_TK1_K20_TS_NAME "apalis-tk1-k20-ts"
+
+struct apalis_tk1_k20_ts {
+ struct input_dev *idev;
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20;
+ struct delayed_work work;
+ struct workqueue_struct *workq;
+ uint16_t sample[4];
+};
+
+static irqreturn_t apalis_tk1_k20_ts_handler(int irq, void *data)
+{
+ struct apalis_tk1_k20_ts *priv = data;
+
+ /*
+ * Kick off reading coordinates. Note that if work happens already
+ * be queued for future execution (it rearms itself) it will not
+ * be rescheduled for immediate execution here. However the rearm
+ * delay is HZ / 50 which is acceptable.
+ */
+ queue_delayed_work(priv->workq, &priv->work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static void apalis_tk1_k20_ts_report_sample(struct apalis_tk1_k20_ts *priv)
+{
+ struct input_dev *idev = priv->idev;
+ int xp, xm, yp, ym;
+ int x, y, press;
+
+ xp = priv->sample[1];
+ xm = priv->sample[0];
+ yp = priv->sample[3];
+ ym = priv->sample[2];
+
+ x = (xp + xm) / 2;
+ y = (yp + ym) / 2;
+ press = (abs(yp - ym) + abs(xp - xm)) / 2;
+
+ if (press) {
+ input_report_abs(idev, ABS_X, x);
+ input_report_abs(idev, ABS_Y, y);
+
+ dev_dbg(&idev->dev, "report (%d, %d, %d)\n",
+ x, y, press);
+ queue_delayed_work(priv->workq, &priv->work, HZ / 50);
+ } else
+ dev_dbg(&idev->dev, "report release\n");
+
+ input_report_abs(idev, ABS_PRESSURE, press);
+ input_report_key(idev, BTN_TOUCH, press);
+ input_sync(idev);
+}
+
+static void apalis_tk1_k20_ts_work(struct work_struct *work)
+{
+ struct apalis_tk1_k20_ts *priv =
+ container_of(work, struct apalis_tk1_k20_ts, work.work);
+ uint8_t buf[8], i;
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+
+ if (apalis_tk1_k20_reg_read_bulk(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_TSC_XML, buf, 8) < 0) {
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+ dev_err(&priv->idev->dev, "Error reading data\n");
+ return;
+ }
+
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+
+ for (i = 0; i < 7; i++)
+ priv->sample[(int)i/2] = (buf[i+1] << 8) + buf[i];
+
+ apalis_tk1_k20_ts_report_sample(priv);
+}
+
+static int apalis_tk1_k20_ts_open(struct input_dev *dev)
+{
+ struct apalis_tk1_k20_ts *priv = input_get_drvdata(dev);
+ int ret;
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+
+ ret = apalis_tk1_k20_irq_request(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_TSC_IRQ, apalis_tk1_k20_ts_handler,
+ APALIS_TK1_K20_TS_NAME, priv);
+ if (ret)
+ goto out;
+
+ ret = apalis_tk1_k20_reg_rmw(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_TSCREG, APALIS_TK1_K20_TSC_ENA_MASK,
+ APALIS_TK1_K20_TSC_ENA);
+ if (ret)
+ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_TSC_IRQ, priv);
+
+out:
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+
+ return ret;
+}
+
+static void apalis_tk1_k20_ts_close(struct input_dev *dev)
+{
+ struct apalis_tk1_k20_ts *priv = input_get_drvdata(dev);
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_rmw(priv->apalis_tk1_k20, APALIS_TK1_K20_TSCREG,
+ APALIS_TK1_K20_TSC_ENA_MASK, 0);
+ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20, APALIS_TK1_K20_TSC_IRQ,
+ priv);
+
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+
+ cancel_delayed_work_sync(&priv->work);
+}
+
+static int __init apalis_tk1_k20_ts_probe(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_ts *priv;
+ struct input_dev *idev;
+ int ret = -ENOMEM;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ idev = input_allocate_device();
+ if (!priv || !idev)
+ goto err_free_mem;
+
+ INIT_DELAYED_WORK(&priv->work, apalis_tk1_k20_ts_work);
+
+ priv->apalis_tk1_k20 = dev_get_drvdata(pdev->dev.parent);
+ priv->idev = idev;
+
+ priv->workq = create_singlethread_workqueue("apalis_tk1_k20_ts");
+ if (!priv->workq)
+ goto err_free_mem;
+
+ idev->name = APALIS_TK1_K20_TS_NAME;
+ idev->dev.parent = &pdev->dev;
+
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(idev, ABS_X, 0, 0xffff, 0, 0);
+ input_set_abs_params(idev, ABS_Y, 0, 0xffff, 0, 0);
+ input_set_abs_params(idev, ABS_PRESSURE, 0, 0xfff, 0, 0);
+
+ idev->open = apalis_tk1_k20_ts_open;
+ idev->close = apalis_tk1_k20_ts_close;
+
+ input_set_drvdata(idev, priv);
+
+ ret = input_register_device(priv->idev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "register input device failed with %d\n", ret);
+ goto err_destroy_wq;
+ }
+
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+
+err_destroy_wq:
+ destroy_workqueue(priv->workq);
+err_free_mem:
+ input_free_device(idev);
+ kfree(priv);
+
+ return ret;
+}
+
+static int __exit apalis_tk1_k20_ts_remove(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_ts *priv = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ destroy_workqueue(priv->workq);
+ input_unregister_device(priv->idev);
+ kfree(priv);
+
+ return 0;
+}
+
+static const struct platform_device_id apalis_tk1_k20_ts_idtable[] = {
+ {
+ .name = "apalis-tk1-k20-ts",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, apalis_tk1_k20_ts_idtable);
+
+static struct platform_driver apalis_tk1_k20_ts_driver = {
+ .id_table = apalis_tk1_k20_ts_idtable,
+ .remove = __exit_p(apalis_tk1_k20_ts_remove),
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = APALIS_TK1_K20_TS_NAME,
+ },
+};
+
+module_platform_driver_probe(apalis_tk1_k20_ts_driver, &apalis_tk1_k20_ts_probe);
+
+MODULE_DESCRIPTION("K20 touchscreen controller on Apalis TK1");
+MODULE_AUTHOR("Dominik Sliwa <dominik.sliwa@toradex.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index b0d27e114321..af82b3c047c6 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -76,6 +76,20 @@ config MFD_AIC3256_SPI
core functionality controlled via SPI.
Please select individual components.
+config MFD_APALIS_TK1_K20
+ tristate "K20 on Apalis TK1"
+ depends on SPI_MASTER && GENERIC_HARDIRQS
+ select MFD_CORE
+ help
+ The Kinetis MK20DN512 companion micro controller found on Apalis TK1
+ supports CAN, resistive touch, GPIOs and analog inputs.
+
+config APALIS_TK1_K20_EZP
+ bool "K20 on Apalis TK1 programming via EZ Port"
+ depends on MFD_APALIS_TK1_K20
+ help
+ Support for flashing new K20 firmware using EZ-Port functionality.
+
config MFD_CROS_EC
tristate "ChromeOS Embedded Controller"
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index bfb394426e5f..a141a1b30017 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_MFD_AIC3256) += tlv320aic3256-core.o tlv320aic3256-irq.o
obj-$(CONFIG_MFD_AIC3256_SPI) += tlv320aic3256-spi.o
obj-$(CONFIG_MFD_AIC3256_I2C) += tlv320aic3256-i2c.o
obj-$(CONFIG_MFD_SM501) += sm501.o
+obj-$(CONFIG_MFD_APALIS_TK1_K20) += apalis-tk1-k20.o
obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o
obj-$(CONFIG_MFD_CROS_EC) += cros_ec.o
obj-$(CONFIG_MFD_CROS_EC_I2C) += cros_ec_i2c.o
diff --git a/drivers/mfd/apalis-tk1-k20-ezp.h b/drivers/mfd/apalis-tk1-k20-ezp.h
new file mode 100644
index 000000000000..4a7262c85d8f
--- /dev/null
+++ b/drivers/mfd/apalis-tk1-k20-ezp.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 Toradex AG
+ * Dominik Sliwa <dominik.sliwa@toradex.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+#ifndef __DRIVERS_MFD_APALIS_TK1_K20_H
+#define __DRIVERS_MFD_APALIS_TK1_K20_H
+
+#ifdef CONFIG_APALIS_TK1_K20_EZP
+#define APALIS_TK1_K20_FW_FOPT_ADDR 0x40D
+#define APALIS_TK1_K20_FOPT_EZP_ENA BIT(1)
+#define APALIS_TK1_K20_FW_VER_ADDR 0x410
+
+#define APALIS_TK1_K20_FLASH_SIZE 0x80000
+
+/* EZ Port commands */
+#define APALIS_TK1_K20_EZP_WREN 0x06
+#define APALIS_TK1_K20_EZP_WRDI 0x04
+#define APALIS_TK1_K20_EZP_RDSR 0x05
+#define APALIS_TK1_K20_EZP_READ 0x03
+#define APALIS_TK1_K20_EZP_FREAD 0x0B
+#define APALIS_TK1_K20_EZP_SP 0x02
+#define APALIS_TK1_K20_EZP_SE 0xD8
+#define APALIS_TK1_K20_EZP_BE 0xC7
+#define APALIS_TK1_K20_EZP_RESET 0xB9
+#define APALIS_TK1_K20_EZP_WRFCCOB 0xBA
+#define APALIS_TK1_K20_EZP_FRDFCOOB 0xBB
+
+/* Bits of EZ Port Status register */
+#define APALIS_TK1_K20_EZP_STA_WIP BIT(0)
+#define APALIS_TK1_K20_EZP_STA_WEN BIT(1)
+#define APALIS_TK1_K20_EZP_STA_BEDIS BIT(2)
+#define APALIS_TK1_K20_EZP_STA_WEF BIT(6)
+#define APALIS_TK1_K20_EZP_STA_FS BIT(7)
+
+#define APALIS_TK1_K20_EZP_MAX_SPEED 3180000
+#define APALIS_TK1_K20_EZP_MAX_DATA 32
+#define APALIS_TK1_K20_EZP_WRITE_SIZE 32
+
+static const struct firmware *fw_entry;
+#endif /* CONFIG_APALIS_TK1_K20_EZP */
+
+#endif /* __DRIVERS_MFD_APALIS_TK1_K20_H */
diff --git a/drivers/mfd/apalis-tk1-k20.c b/drivers/mfd/apalis-tk1-k20.c
new file mode 100644
index 000000000000..cbd1afe5d718
--- /dev/null
+++ b/drivers/mfd/apalis-tk1-k20.c
@@ -0,0 +1,940 @@
+/*
+ * Copyright 2016 Toradex AG
+ * Dominik Sliwa <dominik.sliwa@toradex.com>
+ *
+ * based on an driver for MC13xxx by:
+ * Copyright 2009-2010 Pengutronix
+ * Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/apalis-tk1-k20.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+
+#include "apalis-tk1-k20-ezp.h"
+
+static const struct spi_device_id apalis_tk1_k20_device_ids[] = {
+ {
+ .name = "apalis-tk1-k20",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(spi, apalis_tk1_k20_device_ids);
+
+static const struct of_device_id apalis_tk1_k20_dt_ids[] = {
+ {
+ .compatible = "toradex,apalis-tk1-k20",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, apalis_tk1_k20_dt_ids);
+
+static const struct regmap_config apalis_tk1_k20_regmap_spi_config = {
+ .reg_bits = 8,
+ .pad_bits = 0,
+ .val_bits = 8,
+
+ .max_register = APALIS_TK1_K20_NUMREGS,
+
+ .cache_type = REGCACHE_NONE,
+
+ .use_single_rw = 1,
+};
+
+static int apalis_tk1_k20_spi_read(void *context, const void *reg,
+ size_t reg_size, void *val, size_t val_size)
+{
+ unsigned char w[3] = {APALIS_TK1_K20_READ_INST,
+ *((unsigned char *) reg), 0};
+ unsigned char r[3];
+ unsigned char *p = val;
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ struct spi_transfer t = {
+ .tx_buf = w,
+ .rx_buf = r,
+ .len = 3,
+ };
+ struct spi_message m;
+ int ret;
+
+ spi->mode = SPI_MODE_1;
+ if (val_size != 1 || reg_size != 1)
+ return -ENOTSUPP;
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync(spi, &m);
+
+ dev_vdbg(dev, "Apalis TK1 K20 MFD SPI send done reg = 0x%X\n",
+ *((unsigned char *) reg));
+
+ spi_message_init(&m);
+ t.len = 3;
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync(spi, &m);
+
+ dev_vdbg(dev, "Apalis TK1 K20 MFD SPI read 0x%X 0x%X\n", r[1], r[2]);
+
+ if (r[1] == TK1_K20_SENTINEL)
+ memcpy(p, &r[2], 1);
+ else
+ return -EIO;
+
+ return ret;
+}
+
+static int apalis_tk1_k20_spi_write(void *context, const void *data,
+ size_t count)
+{
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ uint8_t out_data[3];
+
+ spi->mode = SPI_MODE_1;
+
+ if (count != 2) {
+ dev_err(dev, "Apalis TK1 K20 MFD write count = %d\n", count);
+ return -ENOTSUPP;
+ }
+ out_data[0] = APALIS_TK1_K20_WRITE_INST;
+ out_data[1] = ((uint8_t *)data)[0];
+ out_data[2] = ((uint8_t *)data)[1];
+ dev_vdbg(dev, "Apalis TK1 K20 MFD SPI write 0x%X 0x%X\n", out_data[1],
+ out_data[2]);
+
+ return spi_write(spi, out_data, 3);
+}
+
+static struct regmap_bus regmap_apalis_tk1_k20_bus = {
+ .write = apalis_tk1_k20_spi_write,
+ .read = apalis_tk1_k20_spi_read,
+};
+
+void apalis_tk1_k20_lock(struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ if (!mutex_trylock(&apalis_tk1_k20->lock)) {
+ dev_dbg(apalis_tk1_k20->dev, "wait for %s from %ps\n",
+ __func__, __builtin_return_address(0));
+
+ mutex_lock(&apalis_tk1_k20->lock);
+ }
+ dev_dbg(apalis_tk1_k20->dev, "%s from %ps\n",
+ __func__, __builtin_return_address(0));
+}
+EXPORT_SYMBOL(apalis_tk1_k20_lock);
+
+void apalis_tk1_k20_unlock(struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ dev_dbg(apalis_tk1_k20->dev, "%s from %ps\n",
+ __func__, __builtin_return_address(0));
+ mutex_unlock(&apalis_tk1_k20->lock);
+}
+EXPORT_SYMBOL(apalis_tk1_k20_unlock);
+
+int apalis_tk1_k20_reg_read(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ unsigned int offset, u32 *val)
+{
+ int ret;
+
+ ret = regmap_read(apalis_tk1_k20->regmap, offset, val);
+ dev_vdbg(apalis_tk1_k20->dev, "[0x%02x] -> 0x%06x\n", offset, *val);
+
+ return ret;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_reg_read);
+
+int apalis_tk1_k20_reg_write(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ unsigned int offset, u32 val)
+{
+ dev_vdbg(apalis_tk1_k20->dev, "[0x%02x] <- 0x%06x\n", offset, val);
+
+ if (val >= BIT(24))
+ return -EINVAL;
+
+ return regmap_write(apalis_tk1_k20->regmap, offset, val);
+}
+EXPORT_SYMBOL(apalis_tk1_k20_reg_write);
+
+int apalis_tk1_k20_reg_read_bulk(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ unsigned int offset,
+ uint8_t *val, size_t size)
+{
+ int ret;
+
+ if (size > APALIS_TK1_K20_MAX_BULK)
+ return -EINVAL;
+
+ ret = regmap_bulk_read(apalis_tk1_k20->regmap, offset, val, size);
+ dev_vdbg(apalis_tk1_k20->dev, "bulk read %d bytes from [0x%02x]\n",
+ size, offset);
+
+ return ret;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_reg_read_bulk);
+
+int apalis_tk1_k20_reg_write_bulk(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ unsigned int offset, uint8_t *buf, size_t size)
+{
+ dev_vdbg(apalis_tk1_k20->dev, "bulk write %d bytes to [0x%02x]\n",
+ (unsigned int)size, offset);
+
+ if (size > APALIS_TK1_K20_MAX_BULK)
+ return -EINVAL;
+
+ return regmap_bulk_write(apalis_tk1_k20->regmap, offset, buf, size);
+}
+EXPORT_SYMBOL(apalis_tk1_k20_reg_write_bulk);
+
+int apalis_tk1_k20_reg_rmw(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ unsigned int offset, u32 mask, u32 val)
+{
+ dev_vdbg(apalis_tk1_k20->dev, "[0x%02x] <- 0x%06x (mask: 0x%06x)\n",
+ offset, val, mask);
+
+ return regmap_update_bits(apalis_tk1_k20->regmap, offset, mask, val);
+}
+EXPORT_SYMBOL(apalis_tk1_k20_reg_rmw);
+
+int apalis_tk1_k20_irq_mask(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ int irq)
+{
+ int virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq);
+
+ disable_irq_nosync(virq);
+
+ return 0;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_irq_mask);
+
+int apalis_tk1_k20_irq_unmask(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ int irq)
+{
+ int virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq);
+
+ enable_irq(virq);
+
+ return 0;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_irq_unmask);
+
+int apalis_tk1_k20_irq_status(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ int irq, int *enabled, int *pending)
+{
+ int ret;
+ unsigned int offmask = APALIS_TK1_K20_MSQREG;
+ unsigned int offstat = APALIS_TK1_K20_IRQREG;
+ u32 irqbit = 1 << irq;
+
+ if (irq < 0 || irq >= ARRAY_SIZE(apalis_tk1_k20->irqs))
+ return -EINVAL;
+
+ if (enabled) {
+ u32 mask;
+
+ ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, offmask, &mask);
+ if (ret)
+ return ret;
+
+ *enabled = mask & irqbit;
+ }
+
+ if (pending) {
+ u32 stat;
+
+ ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, offstat, &stat);
+ if (ret)
+ return ret;
+
+ *pending = stat & irqbit;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_irq_status);
+
+int apalis_tk1_k20_irq_request(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ int irq, irq_handler_t handler, const char *name, void *dev)
+{
+ int virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq);
+
+ return devm_request_threaded_irq(apalis_tk1_k20->dev, virq, NULL,
+ handler, IRQF_ONESHOT, name, dev);
+}
+EXPORT_SYMBOL(apalis_tk1_k20_irq_request);
+
+int apalis_tk1_k20_irq_free(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ int irq, void *dev)
+{
+ int virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq);
+
+ devm_free_irq(apalis_tk1_k20->dev, virq, dev);
+
+ return 0;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_irq_free);
+
+static int apalis_tk1_k20_add_subdevice_pdata(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20, const char *name,
+ void *pdata, size_t pdata_size)
+{
+ struct mfd_cell cell = {
+ .platform_data = pdata,
+ .pdata_size = pdata_size,
+ };
+
+ cell.name = kmemdup(name, strlen(name) + 1, GFP_KERNEL);
+ if (!cell.name)
+ return -ENOMEM;
+
+ return mfd_add_devices(apalis_tk1_k20->dev, -1, &cell, 1, NULL, 0,
+ regmap_irq_get_domain(apalis_tk1_k20->irq_data));
+}
+
+static int apalis_tk1_k20_add_subdevice(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20, const char *name)
+{
+ return apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20, name, NULL,
+ 0);
+}
+
+static void apalis_tk1_k20_reset_chip(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ udelay(10);
+ gpio_set_value(apalis_tk1_k20->reset_gpio, 0);
+ udelay(10);
+ gpio_set_value(apalis_tk1_k20->reset_gpio, 1);
+ udelay(10);
+}
+
+#ifdef CONFIG_APALIS_TK1_K20_EZP
+static int apalis_tk1_k20_read_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20, uint8_t command,
+ int addr, int count, uint8_t *buffer)
+{
+ uint8_t w[4 + APALIS_TK1_K20_EZP_MAX_DATA];
+ uint8_t r[4 + APALIS_TK1_K20_EZP_MAX_DATA];
+ uint8_t *out;
+ struct spi_message m;
+ struct spi_device *spi = to_spi_device(apalis_tk1_k20->dev);
+ struct spi_transfer t = {
+ .tx_buf = w,
+ .rx_buf = r,
+ .speed_hz = APALIS_TK1_K20_EZP_MAX_SPEED,
+ };
+ int ret;
+
+ spi->mode = SPI_MODE_0;
+ if (count > APALIS_TK1_K20_EZP_MAX_DATA)
+ return -ENOSPC;
+
+ memset(w, 0, 4 + count);
+
+ switch (command) {
+ case APALIS_TK1_K20_EZP_READ:
+ case APALIS_TK1_K20_EZP_FREAD:
+ t.len = 4 + count;
+ w[1] = (addr & 0xFF0000) >> 16;
+ w[2] = (addr & 0xFF00) >> 8;
+ w[3] = (addr & 0xFC);
+ out = &r[4];
+ break;
+ case APALIS_TK1_K20_EZP_RDSR:
+ case APALIS_TK1_K20_EZP_FRDFCOOB:
+ t.len = 1 + count;
+ out = &r[1];
+ break;
+ default:
+ return -EINVAL;
+ }
+ w[0] = command;
+
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 0);
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync(spi, &m);
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 1);
+ if (ret != 0)
+ return ret;
+
+ memcpy(buffer, out, count);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_write_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20, uint8_t command,
+ int addr, int count, const uint8_t *buffer)
+{
+ uint8_t w[4 + APALIS_TK1_K20_EZP_MAX_DATA];
+ uint8_t r[4 + APALIS_TK1_K20_EZP_MAX_DATA];
+ struct spi_message m;
+ struct spi_device *spi = to_spi_device(apalis_tk1_k20->dev);
+ struct spi_transfer t = {
+ .tx_buf = w,
+ .rx_buf = r,
+ .speed_hz = APALIS_TK1_K20_EZP_MAX_SPEED,
+ };
+ int ret;
+
+ spi->mode = SPI_MODE_0;
+ if (count > APALIS_TK1_K20_EZP_MAX_DATA)
+ return -ENOSPC;
+
+ switch (command) {
+ case APALIS_TK1_K20_EZP_SP:
+ case APALIS_TK1_K20_EZP_SE:
+ t.len = 4 + count;
+ w[1] = (addr & 0xFF0000) >> 16;
+ w[2] = (addr & 0xFF00) >> 8;
+ w[3] = (addr & 0xF8);
+ memcpy(&w[4], buffer, count);
+ break;
+ case APALIS_TK1_K20_EZP_WREN:
+ case APALIS_TK1_K20_EZP_WRDI:
+ case APALIS_TK1_K20_EZP_BE:
+ case APALIS_TK1_K20_EZP_RESET:
+ case APALIS_TK1_K20_EZP_WRFCCOB:
+ t.len = 1 + count;
+ memcpy(&w[1], buffer, count);
+ break;
+ default:
+ return -EINVAL;
+ }
+ w[0] = command;
+
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 0);
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync(spi, &m);
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 1);
+
+ return ret;
+}
+
+static int apalis_tk1_k20_set_wren_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ uint8_t buffer;
+
+ if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_WREN,
+ 0, 0, NULL) < 0)
+ return -EIO;
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR,
+ 0, 1, &buffer) < 0)
+ return -EIO;
+ if ((buffer & APALIS_TK1_K20_EZP_STA_WEN))
+ return 0;
+
+ /* If it failed try one last time */
+ if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_WREN,
+ 0, 0, NULL) < 0)
+ return -EIO;
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR,
+ 0, 1, &buffer) < 0)
+ return -EIO;
+ if ((buffer & APALIS_TK1_K20_EZP_STA_WEN))
+ return 0;
+
+ return -EIO;
+
+}
+
+static int apalis_tk1_k20_enter_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ uint8_t status = 0x00;
+ uint8_t buffer;
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 0);
+ apalis_tk1_k20_reset_chip(apalis_tk1_k20);
+ msleep(30);
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 1);
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR,
+ 0, 1, &buffer) < 0)
+ goto bad;
+ status = buffer;
+ if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_WREN,
+ 0, 0, NULL) < 0)
+ goto bad;
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR,
+ 0, 1, &buffer) < 0)
+ goto bad;
+ if ((buffer & APALIS_TK1_K20_EZP_STA_WEN) && buffer != status)
+ return 0;
+
+bad:
+ dev_err(apalis_tk1_k20->dev, "Error entering EZ Port mode.\n");
+ return -EIO;
+}
+
+static int apalis_tk1_k20_erase_chip_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ uint8_t buffer[16];
+ int i;
+ if (apalis_tk1_k20_set_wren_ezport(apalis_tk1_k20))
+ goto bad;
+ if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_BE,
+ 0, 0, NULL) < 0)
+ goto bad;
+
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR,
+ 0, 1, buffer) < 0)
+ goto bad;
+
+ i = 0;
+ while (buffer[0] & APALIS_TK1_K20_EZP_STA_WIP) {
+ msleep(20);
+ if ((apalis_tk1_k20_read_ezport(apalis_tk1_k20,
+ APALIS_TK1_K20_EZP_RDSR, 0, 1, buffer) < 0) || (i > 50))
+ goto bad;
+ i++;
+ }
+
+ return 0;
+
+bad:
+ dev_err(apalis_tk1_k20->dev, "Error erasing the chip.\n");
+ return -EIO;
+}
+
+static int apalis_tk1_k20_flash_chip_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ uint8_t buffer;
+ const uint8_t *fw_chunk;
+ int i, j, transfer_size;
+
+ for (i = 0; i < fw_entry->size;) {
+ if (apalis_tk1_k20_set_wren_ezport(apalis_tk1_k20))
+ goto bad;
+
+ fw_chunk = fw_entry->data + i;
+ transfer_size = (i + APALIS_TK1_K20_EZP_WRITE_SIZE <
+ fw_entry->size) ? APALIS_TK1_K20_EZP_WRITE_SIZE
+ : (fw_entry->size - i - 1);
+ dev_vdbg(apalis_tk1_k20->dev,
+ "Apalis TK1 K20 MFD transfer_size = %d addr = 0x%X\n",
+ transfer_size, i);
+ if (apalis_tk1_k20_write_ezport(apalis_tk1_k20,
+ APALIS_TK1_K20_EZP_SP, i,
+ transfer_size, fw_chunk) < 0)
+ goto bad;
+
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20,
+ APALIS_TK1_K20_EZP_RDSR, 0, 1, &buffer) < 0)
+ goto bad;
+
+ j = 0;
+ while (buffer & APALIS_TK1_K20_EZP_STA_WIP) {
+ udelay(100);
+ if ((apalis_tk1_k20_read_ezport(apalis_tk1_k20,
+ APALIS_TK1_K20_EZP_RDSR, 0, 1,
+ &buffer) < 0) || (j > 1000))
+ goto bad;
+ j++;
+ }
+ i += APALIS_TK1_K20_EZP_WRITE_SIZE;
+ }
+
+ return 0;
+
+bad:
+ dev_err(apalis_tk1_k20->dev, "Error writing to the chip.\n");
+ return -EIO;
+}
+
+static uint8_t apalis_tk1_k20_fw_ezport_status(void)
+{
+ return fw_entry->data[APALIS_TK1_K20_FW_FOPT_ADDR] &
+ APALIS_TK1_K20_FOPT_EZP_ENA;
+}
+
+static uint8_t apalis_tk1_k20_get_fw_revision(void)
+{
+ if (fw_entry->size > APALIS_TK1_K20_FW_VER_ADDR)
+ return fw_entry->data[APALIS_TK1_K20_FW_VER_ADDR];
+ return 0;
+}
+#endif /* CONFIG_APALIS_TK1_K20_EZP */
+
+
+#ifdef CONFIG_OF
+static int apalis_tk1_k20_probe_flags_dt(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ struct device_node *np = apalis_tk1_k20->dev->of_node;
+
+ if (!np)
+ return -ENODEV;
+
+ if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-adc"))
+ apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_ADC;
+
+ if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-tsc"))
+ apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_TSC;
+
+ if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-can"))
+ apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_CAN;
+
+ if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-gpio"))
+ apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_GPIO;
+
+ return 0;
+}
+
+static int apalis_tk1_k20_probe_gpios_dt(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ struct device_node *np = apalis_tk1_k20->dev->of_node;
+
+ if (!np)
+ return -ENODEV;
+
+ apalis_tk1_k20->reset_gpio = of_get_named_gpio(np, "rst-gpio", 0);
+ if (apalis_tk1_k20->reset_gpio < 0)
+ return apalis_tk1_k20->reset_gpio;
+ gpio_request(apalis_tk1_k20->reset_gpio, "apalis-tk1-k20-reset");
+ gpio_direction_output(apalis_tk1_k20->reset_gpio, 1);
+
+ apalis_tk1_k20->ezpcs_gpio = of_get_named_gpio(np, "ezport-cs-gpio", 0);
+ if (apalis_tk1_k20->ezpcs_gpio < 0)
+ return apalis_tk1_k20->ezpcs_gpio;
+ gpio_request(apalis_tk1_k20->ezpcs_gpio, "apalis-tk1-k20-ezpcs");
+ gpio_direction_output(apalis_tk1_k20->ezpcs_gpio, 1);
+
+ apalis_tk1_k20->appcs_gpio = of_get_named_gpio(np, "spi-cs-gpio", 0);
+ if (apalis_tk1_k20->appcs_gpio < 0)
+ return apalis_tk1_k20->appcs_gpio;
+ gpio_request(apalis_tk1_k20->appcs_gpio, "apalis-tk1-k20-appcs");
+ gpio_direction_output(apalis_tk1_k20->appcs_gpio, 1);
+
+ apalis_tk1_k20->int2_gpio = of_get_named_gpio(np, "int2-gpio", 0);
+ if (apalis_tk1_k20->int2_gpio < 0)
+ return apalis_tk1_k20->int2_gpio;
+ gpio_request(apalis_tk1_k20->int2_gpio, "apalis-tk1-k20-int2");
+ gpio_direction_output(apalis_tk1_k20->int2_gpio, 1);
+
+ return 0;
+}
+#else
+static inline int apalis_tk1_k20_probe_flags_dt(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ return -ENODEV;
+}
+static inline int apalis_tk1_k20_probe_gpios_dt(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ return -ENODEV;
+}
+#endif
+
+int apalis_tk1_k20_dev_init(struct device *dev)
+{
+ struct apalis_tk1_k20_platform_data *pdata = dev_get_platdata(dev);
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20 = dev_get_drvdata(dev);
+ uint32_t revision = 0x00;
+ int ret, i;
+ int erase_only = 0;
+
+ apalis_tk1_k20->dev = dev;
+
+ ret = apalis_tk1_k20_probe_gpios_dt(apalis_tk1_k20);
+ if ((ret < 0) && pdata) {
+ if (pdata) {
+ apalis_tk1_k20->ezpcs_gpio = pdata->ezpcs_gpio;
+ apalis_tk1_k20->reset_gpio = pdata->reset_gpio;
+ apalis_tk1_k20->appcs_gpio = pdata->appcs_gpio;
+ apalis_tk1_k20->int2_gpio = pdata->int2_gpio;
+ } else {
+ dev_err(dev, "Error claiming GPIOs\n");
+ ret = -EINVAL;
+ goto bad;
+ }
+ }
+
+ apalis_tk1_k20_reset_chip(apalis_tk1_k20);
+ msleep(100);
+ gpio_set_value(apalis_tk1_k20->appcs_gpio, 0);
+ ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, APALIS_TK1_K20_REVREG,
+ &revision);
+
+#ifdef CONFIG_APALIS_TK1_K20_EZP
+ if ((request_firmware(&fw_entry, "apalis-tk1-k20.bin", dev) < 0)
+ && (revision != APALIS_TK1_K20_FW_VER)) {
+ dev_err(apalis_tk1_k20->dev,
+ "Unsupported firmware version %d.%d and no local"
+ " firmware file available.\n",
+ (revision & 0xF0 >> 8),
+ (revision & 0x0F));
+ ret = -ENOTSUPP;
+ goto bad;
+ }
+
+ if (fw_entry->size == 1)
+ erase_only = 1;
+
+ if ((apalis_tk1_k20_get_fw_revision() != APALIS_TK1_K20_FW_VER) &&
+ (revision != APALIS_TK1_K20_FW_VER) && !erase_only) {
+ dev_err(apalis_tk1_k20->dev,
+ "Unsupported firmware version in both the device as well"
+ " as the local firmware file.\n");
+ release_firmware(fw_entry);
+ ret = -ENOTSUPP;
+ goto bad;
+ }
+
+ if ((revision != APALIS_TK1_K20_FW_VER) && !erase_only &&
+ (!apalis_tk1_k20_fw_ezport_status())) {
+ dev_err(apalis_tk1_k20->dev,
+ "Unsupported firmware version in the device and the "
+ "local firmware file disables the EZ Port.\n");
+ release_firmware(fw_entry);
+ ret = -ENOTSUPP;
+ goto bad;
+ }
+
+ if ((revision != APALIS_TK1_K20_FW_VER) || erase_only) {
+ if (apalis_tk1_k20_enter_ezport(apalis_tk1_k20) < 0) {
+ dev_err(apalis_tk1_k20->dev,
+ "Problem entering EZ port mode.\n");
+ release_firmware(fw_entry);
+ ret = -EIO;
+ goto bad;
+ }
+ if (apalis_tk1_k20_erase_chip_ezport(apalis_tk1_k20) < 0) {
+ dev_err(apalis_tk1_k20->dev,
+ "Problem erasing the chip.\n");
+ release_firmware(fw_entry);
+ ret = -EIO;
+ goto bad;
+ }
+ if (erase_only) {
+ dev_err(apalis_tk1_k20->dev,
+ "Chip fully erased.\n");
+ release_firmware(fw_entry);
+ ret = -EIO;
+ goto bad;
+ }
+ if (apalis_tk1_k20_flash_chip_ezport(apalis_tk1_k20) < 0) {
+ dev_err(apalis_tk1_k20->dev,
+ "Problem flashing new firmware.\n");
+ release_firmware(fw_entry);
+ ret = -EIO;
+ goto bad;
+ }
+ }
+ release_firmware(fw_entry);
+
+ gpio_set_value(apalis_tk1_k20->appcs_gpio, 1);
+ apalis_tk1_k20_reset_chip(apalis_tk1_k20);
+ msleep(100);
+ gpio_set_value(apalis_tk1_k20->appcs_gpio, 0);
+
+ ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, APALIS_TK1_K20_REVREG,
+ &revision);
+#endif /* CONFIG_APALIS_TK1_K20_EZP */
+
+ if (ret) {
+ dev_err(apalis_tk1_k20->dev, "Device is not answering.\n");
+ goto bad;
+ }
+
+ if (revision != APALIS_TK1_K20_FW_VER) {
+ dev_err(apalis_tk1_k20->dev,
+ "Unsupported firmware version %d.%d.\n",
+ ((revision & 0xF0) >> 8), (revision & 0x0F));
+ ret = -ENOTSUPP;
+ goto bad;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(apalis_tk1_k20->irqs); i++) {
+ apalis_tk1_k20->irqs[i].reg_offset = i /
+ APALIS_TK1_K20_IRQ_PER_REG;
+ apalis_tk1_k20->irqs[i].mask = BIT(i %
+ APALIS_TK1_K20_IRQ_PER_REG);
+ }
+
+ apalis_tk1_k20->irq_chip.name = dev_name(dev);
+ apalis_tk1_k20->irq_chip.status_base = APALIS_TK1_K20_IRQREG;
+ apalis_tk1_k20->irq_chip.mask_base = APALIS_TK1_K20_MSQREG;
+ apalis_tk1_k20->irq_chip.ack_base = APALIS_TK1_K20_IRQREG;
+ apalis_tk1_k20->irq_chip.irq_reg_stride = 0;
+ apalis_tk1_k20->irq_chip.num_regs = APALIS_TK1_K20_IRQ_REG_CNT;
+ apalis_tk1_k20->irq_chip.irqs = apalis_tk1_k20->irqs;
+ apalis_tk1_k20->irq_chip.num_irqs = ARRAY_SIZE(apalis_tk1_k20->irqs);
+
+ ret = regmap_add_irq_chip(apalis_tk1_k20->regmap, apalis_tk1_k20->irq,
+ IRQF_ONESHOT | IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING, 0, &apalis_tk1_k20->irq_chip,
+ &apalis_tk1_k20->irq_data);
+ if (ret)
+ goto bad;
+
+ mutex_init(&apalis_tk1_k20->lock);
+
+ if (apalis_tk1_k20_probe_flags_dt(apalis_tk1_k20) < 0 && pdata)
+ apalis_tk1_k20->flags = pdata->flags;
+
+ if (pdata) {
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_TSC)
+ apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20,
+ "apalis-tk1-k20-ts",
+ &pdata->touch, sizeof(pdata->touch));
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_ADC)
+ apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20,
+ "apalis-tk1-k20-adc",
+ &pdata->adc, sizeof(pdata->adc));
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_CAN) {
+ pdata->can0.id = 0;
+ pdata->can1.id = 1;
+ apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20,
+ "apalis-tk1-k20-can",
+ &pdata->can0, sizeof(pdata->can0));
+ apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20,
+ "apalis-tk1-k20-can",
+ &pdata->can1, sizeof(pdata->can1));
+ }
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_GPIO)
+ apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20,
+ "apalis-tk1-k20-gpio",
+ &pdata->gpio, sizeof(pdata->gpio));
+ } else {
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_TSC)
+ apalis_tk1_k20_add_subdevice(apalis_tk1_k20,
+ "apalis-tk1-k20-ts");
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_ADC)
+ apalis_tk1_k20_add_subdevice(apalis_tk1_k20,
+ "apalis-tk1-k20-adc");
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_CAN) {
+ apalis_tk1_k20_add_subdevice(apalis_tk1_k20,
+ "apalis-tk1-k20-can");
+ }
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_GPIO)
+ apalis_tk1_k20_add_subdevice(apalis_tk1_k20,
+ "apalis-tk1-k20-gpio");
+ }
+
+ dev_info(apalis_tk1_k20->dev, "Apalis TK1 K20 MFD driver.\n"
+ "Firmware version %d.%d.\n", FW_MAJOR, FW_MINOR);
+
+ return 0;
+
+bad:
+ if (apalis_tk1_k20->ezpcs_gpio >= 0)
+ gpio_free(apalis_tk1_k20->ezpcs_gpio);
+ if (apalis_tk1_k20->reset_gpio >= 0)
+ gpio_free(apalis_tk1_k20->reset_gpio);
+ if (apalis_tk1_k20->appcs_gpio >= 0)
+ gpio_free(apalis_tk1_k20->appcs_gpio);
+ if (apalis_tk1_k20->int2_gpio >= 0)
+ gpio_free(apalis_tk1_k20->int2_gpio);
+
+ return ret;
+}
+
+
+static int apalis_tk1_k20_spi_probe(struct spi_device *spi)
+{
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20;
+ int ret;
+
+ apalis_tk1_k20 = devm_kzalloc(&spi->dev, sizeof(*apalis_tk1_k20),
+ GFP_KERNEL);
+ if (!apalis_tk1_k20)
+ return -ENOMEM;
+
+ dev_set_drvdata(&spi->dev, apalis_tk1_k20);
+
+ spi->mode = SPI_MODE_1;
+
+ apalis_tk1_k20->irq = spi->irq;
+
+ spi->max_speed_hz = (spi->max_speed_hz > APALIS_TK1_K20_MAX_SPI_SPEED) ?
+ APALIS_TK1_K20_MAX_SPI_SPEED : spi->max_speed_hz;
+
+ ret = spi_setup(spi);
+ if (ret)
+ return ret;
+
+ apalis_tk1_k20->regmap = devm_regmap_init(&spi->dev,
+ &regmap_apalis_tk1_k20_bus, &spi->dev,
+ &apalis_tk1_k20_regmap_spi_config);
+ if (IS_ERR(apalis_tk1_k20->regmap)) {
+ ret = PTR_ERR(apalis_tk1_k20->regmap);
+ dev_err(&spi->dev, "Failed to initialize regmap: %d\n", ret);
+ return ret;
+ }
+
+ return apalis_tk1_k20_dev_init(&spi->dev);
+}
+
+static int apalis_tk1_k20_spi_remove(struct spi_device *spi)
+{
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20 =
+ dev_get_drvdata(&spi->dev);
+
+ if (apalis_tk1_k20->ezpcs_gpio >= 0)
+ gpio_free(apalis_tk1_k20->ezpcs_gpio);
+ if (apalis_tk1_k20->reset_gpio >= 0)
+ gpio_free(apalis_tk1_k20->reset_gpio);
+ if (apalis_tk1_k20->appcs_gpio >= 0)
+ gpio_free(apalis_tk1_k20->appcs_gpio);
+ if (apalis_tk1_k20->int2_gpio >= 0)
+ gpio_free(apalis_tk1_k20->int2_gpio);
+
+ mfd_remove_devices(&spi->dev);
+ regmap_del_irq_chip(apalis_tk1_k20->irq, apalis_tk1_k20->irq_data);
+ mutex_destroy(&apalis_tk1_k20->lock);
+
+ return 0;
+}
+
+static struct spi_driver apalis_tk1_k20_spi_driver = {
+ .id_table = apalis_tk1_k20_device_ids,
+ .driver = {
+ .name = "apalis-tk1-k20",
+ .of_match_table = apalis_tk1_k20_dt_ids,
+ },
+ .probe = apalis_tk1_k20_spi_probe,
+ .remove = apalis_tk1_k20_spi_remove,
+};
+
+static int __init apalis_tk1_k20_init(void)
+{
+ return spi_register_driver(&apalis_tk1_k20_spi_driver);
+}
+subsys_initcall(apalis_tk1_k20_init);
+
+static void __exit apalis_tk1_k20_exit(void)
+{
+ spi_unregister_driver(&apalis_tk1_k20_spi_driver);
+}
+module_exit(apalis_tk1_k20_exit);
+
+MODULE_DESCRIPTION("MFD driver for Kinetis MK20DN512 MCU on Apalis TK1");
+MODULE_AUTHOR("Dominik Sliwa <dominik.sliwa@toradex.com>");
+MODULE_LICENSE("GPL v2");