From 19722d4d90e711d7826ec7f6461216aa61f2f937 Mon Sep 17 00:00:00 2001 From: Dominik Sliwa Date: Tue, 9 Aug 2016 13:43:10 +0200 Subject: apalis_tk1: Support for K20 based MFD On Apalis TK1 boards K20 MCU is used for CAN, GPIO, ADC and touch screen. This patch includes support for core MFD device, GPIO, ADC and touch screen. Signed-off-by: Dominik Sliwa Acked-by: Marcel Ziswiler --- arch/arm/boot/dts/tegra124-apalis-eval.dts | 26 +- arch/arm/configs/apalis-tk1_defconfig | 8 +- drivers/gpio/Kconfig | 6 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-apalis-tk1-k20.c | 216 ++++++ drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/apalis-tk1-k20_adc.c | 200 ++++++ drivers/input/touchscreen/Kconfig | 10 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/apalis-tk1-k20_ts.c | 230 +++++++ drivers/mfd/Kconfig | 14 + drivers/mfd/Makefile | 1 + drivers/mfd/apalis-tk1-k20-ezp.h | 46 ++ drivers/mfd/apalis-tk1-k20.c | 940 ++++++++++++++++++++++++++ include/linux/mfd/apalis-tk1-k20.h | 189 ++++++ 16 files changed, 1888 insertions(+), 7 deletions(-) create mode 100644 drivers/gpio/gpio-apalis-tk1-k20.c create mode 100644 drivers/iio/adc/apalis-tk1-k20_adc.c create mode 100644 drivers/input/touchscreen/apalis-tk1-k20_ts.c create mode 100644 drivers/mfd/apalis-tk1-k20-ezp.h create mode 100644 drivers/mfd/apalis-tk1-k20.c create mode 100644 include/linux/mfd/apalis-tk1-k20.h diff --git a/arch/arm/boot/dts/tegra124-apalis-eval.dts b/arch/arm/boot/dts/tegra124-apalis-eval.dts index 231d0913e92e..c5ecf9301229 100644 --- a/arch/arm/boot/dts/tegra124-apalis-eval.dts +++ b/arch/arm/boot/dts/tegra124-apalis-eval.dts @@ -136,16 +136,34 @@ status = "okay"; spi-max-frequency = <25000000>; - spidev1: spidev@1 { - compatible = "spidev"; + k20mcu: apalis-tk1-k20@1 { + compatible = "toradex,apalis-tk1-k20"; reg = <1>; - spi-max-frequency = <12000000>; + spi-max-frequency = <10000000>; + interrupt-parent =<&gpio>; + interrupts = ; + rst-gpio = <&gpio TEGRA_GPIO(BB, 6) GPIO_ACTIVE_HIGH>; + + /* GPIO based CS used to enter K20 EzPort mode */ + ezport-cs-gpio = <&gpio TEGRA_GPIO(W, 2) GPIO_ACTIVE_HIGH>; + /* SPI CS under GPIO controll due to K20 quirks */ + spi-cs-gpio = <&gpio TEGRA_GPIO(X, 6) GPIO_ACTIVE_HIGH>; + /* extra INT lines between K20 and TK1 */ + int2-gpio = <&gpio TEGRA_GPIO(J, 2) GPIO_ACTIVE_HIGH>; + int3-gpio = <&gpio TEGRA_GPIO(I, 5) GPIO_ACTIVE_HIGH>; + int4-gpio = <&gpio TEGRA_GPIO(J, 0) GPIO_ACTIVE_HIGH>; + + toradex,apalis-tk1-k20-uses-gpio; + toradex,apalis-tk1-k20-uses-adc; + toradex,apalis-tk1-k20-uses-tsc; }; + /* spidev on K20 bus, can be used with custom firmware for userspace + * K20 applications */ spidev2: spidev@2 { compatible = "spidev"; reg = <2>; - spi-max-frequency = <2000000>; + spi-max-frequency = <3500000>; }; }; diff --git a/arch/arm/configs/apalis-tk1_defconfig b/arch/arm/configs/apalis-tk1_defconfig index 87a3d3901fbc..bf53b49ea7fe 100644 --- a/arch/arm/configs/apalis-tk1_defconfig +++ b/arch/arm/configs/apalis-tk1_defconfig @@ -225,7 +225,7 @@ CONFIG_KEYBOARD_GPIO=y CONFIG_INPUT_JOYSTICK=y CONFIG_JOYSTICK_XPAD=m CONFIG_INPUT_TOUCHSCREEN=y -#TBD: K20 Touch +CONFIG_TOUCHSCREEN_APALIS_TK1_K20=m CONFIG_TOUCHSCREEN_FUSION_F0710A=m CONFIG_INPUT_MISC=y CONFIG_INPUT_KEYCHORD=y @@ -252,6 +252,7 @@ CONFIG_SPI_SPIDEV=y CONFIG_PINCTRL_AS3722=y CONFIG_DEBUG_GPIO=y CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_APALIS_TK1_K20=m CONFIG_POWER_RESET_AS3722=y CONFIG_THERMAL_GOV_PID=y CONFIG_GENERIC_ADC_THERMAL=y @@ -262,8 +263,9 @@ CONFIG_WATCHDOG_CORE=y CONFIG_WATCHDOG_NOWAYOUT=y CONFIG_TEGRA_WATCHDOG=y CONFIG_TEGRA_WATCHDOG_ENABLE_ON_PROBE=y +CONFIG_MFD_APALIS_TK1_K20=m +CONFIG_APALIS_TK1_K20_EZP=y CONFIG_MFD_AS3722=y -#TBD: MFD K20 CONFIG_REGULATOR=y CONFIG_REGULATOR_FIXED_VOLTAGE=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y @@ -384,7 +386,7 @@ CONFIG_TEGRA_IOMMU_SMMU=y CONFIG_PM_DEVFREQ=y CONFIG_EXTCON=y CONFIG_IIO=y -#TBD: K20 ADC +CONFIG_APALIS_TK1_K20_ADC=m CONFIG_PWM=y CONFIG_PWM_TEGRA=y CONFIG_GK20A_PMU=y 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 + * + * 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 +#include +#include +#include +#include +#include + +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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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 + * + * 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 + * + * based on an driver for MC13xxx by: + * Copyright 2009-2010 Pengutronix + * Uwe Kleine-Koenig + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, + ®map_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 "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/apalis-tk1-k20.h b/include/linux/mfd/apalis-tk1-k20.h new file mode 100644 index 000000000000..29229a09d8c4 --- /dev/null +++ b/include/linux/mfd/apalis-tk1-k20.h @@ -0,0 +1,189 @@ +/* + * Copyright 2016 Toradex AG + * Dominik Sliwa + * + * 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 __LINUX_MFD_APALIS_TK1_K20_H +#define __LINUX_MFD_APALIS_TK1_K20_H + +#include +#include +#include + +/* Commands and registers used in SPI communication */ + +/* Commands*/ +#define APALIS_TK1_K20_READ_INST 0x0F +#define APALIS_TK1_K20_WRITE_INST 0xF0 +#define APALIS_TK1_K20_BULK_WRITE_INST 0x3C +#define APALIS_TK1_K20_BULK_READ_INST 0xC3 + +#define APALIS_TK1_K20_MAX_BULK (64) + +/* General registers*/ +#define APALIS_TK1_K20_STAREG 0x00 /* General status register RO */ +#define APALIS_TK1_K20_REVREG 0x01 /* FW revision register RO*/ +#define APALIS_TK1_K20_IRQREG 0x02 /* IRQ status RW(write of 1 will reset the bit) */ +#define APALIS_TK1_K20_CTRREG 0x03 /* General control register RW */ +#define APALIS_TK1_K20_MSQREG 0x04 /* IRQ mask register RW */ + +/* CAN Registers */ +#define APALIS_TK1_K20_CANREG 0x10 /* CAN control & status register RW */ +#define APALIS_TK1_K20_CAN_BAUD_REG 0x11 /* CAN Baud set register RW */ +#define APALIS_TK1_K20_CAN_IN_BUF_CNT 0x12 /* CAN IN BUF Received Data Count RO */ +#define APALIS_TK1_K20_CAN_IN_BUF 0x13 /* CAN IN BUF RO */ +#define APALIS_TK1_K20_CAN_OUT_BUF_CNT 0x14 /* CAN OUT BUF Data Count WO, must be written before bulk write to APALIS_TK1_K20_CAN0_OUT_BUF_CNT */ +#define APALIS_TK1_K20_CAN_OUT_FIF0 0x15 /* CAN OUT BUF WO */ + +#define APALIS_TK1_K20_CAN_DEV_OFFSET(x) (x ? 0:0x10) + +/* ADC Registers */ +#define APALIS_TK1_K20_ADCREG 0x30 /* ADC control & status register RW */ +#define APALIS_TK1_K20_ADC_CH0L 0x31 /* ADC Channel 0 LSB RO */ +#define APALIS_TK1_K20_ADC_CH0H 0x32 /* ADC Channel 0 MSB RO */ +#define APALIS_TK1_K20_ADC_CH1L 0x33 /* ADC Channel 1 LSB RO */ +#define APALIS_TK1_K20_ADC_CH1H 0x34 /* ADC Channel 1 MSB RO */ +#define APALIS_TK1_K20_ADC_CH2L 0x35 /* ADC Channel 2 LSB RO */ +#define APALIS_TK1_K20_ADC_CH2H 0x36 /* ADC Channel 2 MSB RO */ +#define APALIS_TK1_K20_ADC_CH3L 0x37 /* ADC Channel 3 LSB RO */ +#define APALIS_TK1_K20_ADC_CH3H 0x38 /* ADC Channel 3 MSB RO */ +/* Bulk read of LSB register can be use to read entire 16-bit in one command */ + +/* TSC Register */ +#define APALIS_TK1_K20_TSCREG 0x40 /* TSC control & status register RW */ +#define APALIS_TK1_K20_TSC_XML 0x41 /* TSC X- data LSB RO */ +#define APALIS_TK1_K20_TSC_XMH 0x42 /* TSC X- data MSB RO */ +#define APALIS_TK1_K20_TSC_XPL 0x43 /* TSC X+ data LSB RO */ +#define APALIS_TK1_K20_TSC_XPH 0x44 /* TSC X+ data MSB RO */ +#define APALIS_TK1_K20_TSC_YML 0x45 /* TSC Y- data LSB RO */ +#define APALIS_TK1_K20_TSC_YMH 0x46 /* TSC Y- data MSB RO */ +#define APALIS_TK1_K20_TSC_YPL 0x47 /* TSC Y+ data LSB RO */ +#define APALIS_TK1_K20_TSC_YPH 0x48 /* TSC Y+ data MSB RO */ +/* Bulk read of LSB register can be use to read entire 16-bit in one command */ +#define APALIS_TK1_K20_TSC_ENA BIT(0) +#define APALIS_TK1_K20_TSC_ENA_MASK BIT(0) + +/* GPIO Registers */ +#define APALIS_TK1_K20_GPIOREG 0x50 /* GPIO control & status register RW */ +#define APALIS_TK1_K20_GPIO_NO 0x51 /* currently configured GPIO RW */ +#define APALIS_TK1_K20_GPIO_STA 0x52 /* Status register for the APALIS_TK1_K20_GPIO_NO GPIO RW */ +/* MSB | 0 ... 0 | VALUE | Output-1 / Input-0 | LSB */ +#define APALIS_TK1_K20_GPIO_STA_OE BIT(0) +#define APALIS_TK1_K20_GPIO_STA_VAL BIT(1) + +/* Interrupt flags */ +#define APALIS_TK1_K20_GEN_IRQ 0 +#define APALIS_TK1_K20_CAN0_IRQ 1 +#define APALIS_TK1_K20_CAN1_IRQ 2 +#define APALIS_TK1_K20_ADC_IRQ 3 +#define APALIS_TK1_K20_TSC_IRQ 4 +#define APALIS_TK1_K20_GPIO_IRQ 5 + +#define APALIS_TK1_K20_FW_VER 0x05 + +#define FW_MINOR (APALIS_TK1_K20_FW_VER & 0x0F) +#define FW_MAJOR ((APALIS_TK1_K20_FW_VER & 0xF0) >> 8) + +#define TK1_K20_SENTINEL 0x55 +#define TK1_K20_INVAL 0xAA + +#define APALIS_TK1_K20_NUMREGS 0x3f +#define APALIS_TK1_K20_IRQ_REG_CNT 1 +#define APALIS_TK1_K20_IRQ_PER_REG 8 + +#define APALIS_TK1_K20_MAX_SPI_SPEED 10000000 + +struct apalis_tk1_k20_regmap { + struct regmap *regmap; + + struct device *dev; + + struct regmap_irq irqs[APALIS_TK1_K20_IRQ_REG_CNT * APALIS_TK1_K20_IRQ_PER_REG]; + struct regmap_irq_chip irq_chip; + struct regmap_irq_chip_data *irq_data; + + struct mutex lock; + int irq; + int flags; + + int ezpcs_gpio; + int reset_gpio; + int appcs_gpio; + int int2_gpio; +}; + +void apalis_tk1_k20_lock(struct apalis_tk1_k20_regmap *apalis_tk1_k20); +void apalis_tk1_k20_unlock(struct apalis_tk1_k20_regmap *apalis_tk1_k20); + +int apalis_tk1_k20_reg_read(struct apalis_tk1_k20_regmap *apalis_tk1_k20, unsigned int offset, u32 *val); +int apalis_tk1_k20_reg_write(struct apalis_tk1_k20_regmap *apalis_tk1_k20, unsigned int offset, u32 val); +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 apalis_tk1_k20_reg_write_bulk(struct apalis_tk1_k20_regmap *apalis_tk1_k20, unsigned int offset, + uint8_t *val, size_t size); +int apalis_tk1_k20_reg_rmw(struct apalis_tk1_k20_regmap *apalis_tk1_k20, unsigned int offset, + u32 mask, u32 val); + +int apalis_tk1_k20_irq_mask(struct apalis_tk1_k20_regmap *apalis_tk1_k20, int irq); +int apalis_tk1_k20_irq_unmask(struct apalis_tk1_k20_regmap *apalis_tk1_k20, int irq); +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 apalis_tk1_k20_irq_free(struct apalis_tk1_k20_regmap *apalis_tk1_k20, int irq, void *dev); + +int apalis_tk1_k20_irq_status(struct apalis_tk1_k20_regmap *apalis_tk1_k20, int irq, + int *enabled, int *pending); + +int apalis_tk1_k20_get_flags(struct apalis_tk1_k20_regmap *apalis_tk1_k20); + +struct apalis_tk1_k20_can_platform_data { + uint8_t id; + u16 status; +}; + +struct apalis_tk1_k20_tsc_platform_data { + u16 status; +}; + +struct apalis_tk1_k20_adc_platform_data { + u16 status; +}; + +struct apalis_tk1_k20_gpio_platform_data { + u16 status; +}; + +#define APALIS_TK1_K20_USES_TSC BIT(0) +#define APALIS_TK1_K20_USES_ADC BIT(1) +#define APALIS_TK1_K20_USES_CAN BIT(2) +#define APALIS_TK1_K20_USES_GPIO BIT(3) + +struct apalis_tk1_k20_platform_data { + unsigned int flags; + + struct apalis_tk1_k20_tsc_platform_data touch; + struct apalis_tk1_k20_adc_platform_data adc; + struct apalis_tk1_k20_can_platform_data can0; + struct apalis_tk1_k20_can_platform_data can1; + struct apalis_tk1_k20_gpio_platform_data gpio; + + int ezpcs_gpio; + int reset_gpio; + int appcs_gpio; + int int2_gpio; +}; + +#define APALIS_TK1_K20_ADC_CHANNELS 4 +#define APALIS_TK1_K20_ADC_BITS 16 +#define APALIS_TK1_K20_VADC_MILI 3300 + +enum apalis_tk1_k20_adc_id { + APALIS_TK1_K20_ADC1, + APALIS_TK1_K20_ADC2, + APALIS_TK1_K20_ADC3, + APALIS_TK1_K20_ADC4 +}; + +#endif /* ifndef __LINUX_MFD_APALIS_TK1_K20_H */ -- cgit v1.2.3