From 4b2804c9fbbe84ecc997a292f95136d4e2140317 Mon Sep 17 00:00:00 2001 From: Dominik Sliwa Date: Wed, 18 Apr 2018 12:24:55 +0200 Subject: linux-toradex-mainline: support for k20 mfd on apalis tk1 Signed-off-by: Dominik Sliwa Acked-by: Stefan Agner --- .../0012-apalis-tk1-support-for-k20-mfd.patch | 3185 ++++++++++++++++++++ .../linux/linux-toradex-mainline_4.14.bb | 1 + 2 files changed, 3186 insertions(+) create mode 100644 recipes-kernel/linux/linux-toradex-mainline-4.14/0012-apalis-tk1-support-for-k20-mfd.patch diff --git a/recipes-kernel/linux/linux-toradex-mainline-4.14/0012-apalis-tk1-support-for-k20-mfd.patch b/recipes-kernel/linux/linux-toradex-mainline-4.14/0012-apalis-tk1-support-for-k20-mfd.patch new file mode 100644 index 0000000..e410e4c --- /dev/null +++ b/recipes-kernel/linux/linux-toradex-mainline-4.14/0012-apalis-tk1-support-for-k20-mfd.patch @@ -0,0 +1,3185 @@ +From babab7769aa903d7602c24d4422bca72b5903093 Mon Sep 17 00:00:00 2001 +Message-Id: +In-Reply-To: +References: +From: Dominik Sliwa +Date: Wed, 18 Apr 2018 12:22:26 +0200 +Subject: [PATCH 12/27] apalis-tk1: support for k20 mfd + +Signed-off-by: Dominik Sliwa +Acked-by: Marcel Ziswiler +--- + arch/arm/boot/dts/tegra124-apalis-v1.2.dtsi | 28 + + arch/arm/configs/tegra_defconfig | 7 + + drivers/gpio/Kconfig | 6 + + drivers/gpio/Makefile | 1 + + drivers/gpio/gpio-apalis-tk1-k20.c | 221 +++++ + 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 | 234 ++++++ + drivers/mfd/Kconfig | 14 + + drivers/mfd/Makefile | 1 + + drivers/mfd/apalis-tk1-k20-ezp.h | 47 ++ + drivers/mfd/apalis-tk1-k20.c | 1062 +++++++++++++++++++++++++ + drivers/net/can/Kconfig | 6 + + drivers/net/can/Makefile | 1 + + drivers/net/can/apalis-tk1-k20-can.c | 817 +++++++++++++++++++ + drivers/spi/spi-tegra114.c | 7 +- + include/linux/mfd/apalis-tk1-k20-api.h | 123 +++ + include/linux/mfd/apalis-tk1-k20.h | 114 +++ + 21 files changed, 2901 insertions(+), 6 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 drivers/net/can/apalis-tk1-k20-can.c + create mode 100644 include/linux/mfd/apalis-tk1-k20-api.h + create mode 100644 include/linux/mfd/apalis-tk1-k20.h + +diff --git a/arch/arm/boot/dts/tegra124-apalis-v1.2.dtsi b/arch/arm/boot/dts/tegra124-apalis-v1.2.dtsi +index c2b14e693cda..bca22e2a6dcb 100644 +--- a/arch/arm/boot/dts/tegra124-apalis-v1.2.dtsi ++++ b/arch/arm/boot/dts/tegra124-apalis-v1.2.dtsi +@@ -1792,6 +1792,34 @@ + spi@7000d600 { + status = "okay"; + spi-max-frequency = <25000000>; ++ ++ k20mcu: apalis-tk1-k20@1 { ++ compatible = "toradex,apalis-tk1-k20"; ++ reg = <1>; ++ spi-max-frequency = <6120000>; ++ interrupt-parent =<&gpio>; ++ interrupts = , ++ , /* INT3 CAN0 */ ++ ; /* INT4 CAN1 */ ++ 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>; ++ /* extra INT lines between K20 and TK1 */ ++ int2-gpio = <&gpio TEGRA_GPIO(J, 2) GPIO_ACTIVE_HIGH>; ++ ++ toradex,apalis-tk1-k20-uses-adc; ++ toradex,apalis-tk1-k20-uses-can; ++ toradex,apalis-tk1-k20-uses-gpio; ++ toradex,apalis-tk1-k20-uses-tsc; ++ ++ controller-data { ++ nvidia,enable-hw-based-cs; ++ nvidia,cs-setup-clk-count = <1>; ++ nvidia,cs-hold-clk-count = <1>; ++ }; ++ }; ++ + }; + + pmc@7000e400 { +diff --git a/arch/arm/configs/tegra_defconfig b/arch/arm/configs/tegra_defconfig +index cfe997c617fc..e8c9bdafa1b9 100644 +--- a/arch/arm/configs/tegra_defconfig ++++ b/arch/arm/configs/tegra_defconfig +@@ -63,6 +63,7 @@ CONFIG_IPV6_MIP6=y + CONFIG_IPV6_TUNNEL=y + CONFIG_IPV6_MULTIPLE_TABLES=y + CONFIG_CAN=y ++CONFIG_CAN_APALIS_TK1_K20=m + CONFIG_CAN_MCP251X=y + CONFIG_BT=y + CONFIG_BT_RFCOMM=y +@@ -126,6 +127,7 @@ CONFIG_KEYBOARD_TEGRA=y + CONFIG_KEYBOARD_CROS_EC=y + CONFIG_MOUSE_PS2_ELANTECH=y + CONFIG_INPUT_TOUCHSCREEN=y ++CONFIG_TOUCHSCREEN_APALIS_TK1_K20=m + CONFIG_TOUCHSCREEN_ATMEL_MXT=y + CONFIG_TOUCHSCREEN_WM97XX=y + # CONFIG_TOUCHSCREEN_WM9705 is not set +@@ -155,6 +157,7 @@ CONFIG_GPIO_PCA953X_IRQ=y + CONFIG_GPIO_PALMAS=y + CONFIG_GPIO_TPS6586X=y + CONFIG_GPIO_TPS65910=y ++CONFIG_GPIO_APALIS_TK1_K20=m + CONFIG_POWER_RESET=y + CONFIG_POWER_RESET_AS3722=y + CONFIG_POWER_RESET_GPIO=y +@@ -165,6 +168,8 @@ CONFIG_SENSORS_LM95245=y + CONFIG_WATCHDOG=y + CONFIG_TEGRA_WATCHDOG=y + CONFIG_MFD_AS3722=y ++CONFIG_MFD_APALIS_TK1_K20=m ++CONFIG_APALIS_TK1_K20_EZP=y + CONFIG_MFD_CROS_EC=y + CONFIG_MFD_CROS_EC_SPI=y + CONFIG_MFD_MAX8907=y +@@ -276,6 +281,7 @@ CONFIG_ARCH_TEGRA_114_SOC=y + CONFIG_ARCH_TEGRA_124_SOC=y + CONFIG_MEMORY=y + CONFIG_IIO=y ++CONFIG_APALIS_TK1_K20_ADC=m + CONFIG_MPU3050_I2C=y + CONFIG_SENSORS_ISL29018=y + CONFIG_SENSORS_ISL29028=y +@@ -309,6 +315,7 @@ CONFIG_ROOT_NFS=y + CONFIG_NLS_CODEPAGE_437=y + CONFIG_NLS_ISO8859_1=y + CONFIG_PRINTK_TIME=y ++CONFIG_DYNAMIC_DEBUG=y + CONFIG_DEBUG_INFO=y + CONFIG_MAGIC_SYSRQ=y + CONFIG_DEBUG_SLAB=y +diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig +index 3f80f167ed56..c5b006c10d37 100644 +--- a/drivers/gpio/Kconfig ++++ b/drivers/gpio/Kconfig +@@ -1248,6 +1248,12 @@ endmenu + menu "SPI GPIO expanders" + depends on SPI_MASTER + ++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_74X164 + tristate "74x164 serial-in/parallel-out 8-bits shift register" + depends on OF_GPIO +diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile +index 8a2dfba3b231..8ea42ecdf87e 100644 +--- a/drivers/gpio/Makefile ++++ b/drivers/gpio/Makefile +@@ -29,6 +29,7 @@ obj-$(CONFIG_GPIO_ALTERA) += gpio-altera.o + obj-$(CONFIG_GPIO_ALTERA_A10SR) += gpio-altera-a10sr.o + obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o + obj-$(CONFIG_GPIO_AMDPT) += gpio-amdpt.o ++obj-$(CONFIG_GPIO_APALIS_TK1_K20) += gpio-apalis-tk1-k20.o + obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o + obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o + obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.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..7b44f93df85b +--- /dev/null ++++ b/drivers/gpio/gpio-apalis-tk1-k20.c +@@ -0,0 +1,221 @@ ++/* ++ * Copyright 2016-2017 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 int 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 int 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 int 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 int offset) ++{ ++ struct apalis_tk1_k20_gpio *gpio = container_of(chip, ++ struct apalis_tk1_k20_gpio, chip); ++ int status = 0; ++ ++ dev_dbg(gpio->apalis_tk1_k20->dev, "APALIS TK1 K20 GPIO %d\n", ++ offset); ++ ++ 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 int 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 int 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 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.parent = &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 apalis_tk1_k20_gpio_remove(struct platform_device *pdev) ++{ ++ struct apalis_tk1_k20_gpio *priv = platform_get_drvdata(pdev); ++ ++ gpiochip_remove(&priv->chip); ++ return 0; ++} ++ ++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 = apalis_tk1_k20_gpio_remove, ++ .probe = apalis_tk1_k20_gpio_probe, ++ .driver = { ++ .name = "apalis-tk1-k20-gpio", ++ }, ++}; ++ ++module_platform_driver(apalis_tk1_k20_gpio_driver); ++ ++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 1d13bf03c758..38da40c0f19b 100644 +--- a/drivers/iio/adc/Kconfig ++++ b/drivers/iio/adc/Kconfig +@@ -116,6 +116,12 @@ config AD7923 + To compile this driver as a module, choose M here: the + module will be called ad7923. + ++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 AD799X + tristate "Analog Devices AD799x ADC driver" + depends on I2C +diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile +index 9572c1090f35..5f761384b305 100644 +--- a/drivers/iio/adc/Makefile ++++ b/drivers/iio/adc/Makefile +@@ -16,6 +16,7 @@ obj-$(CONFIG_AD7793) += ad7793.o + obj-$(CONFIG_AD7887) += ad7887.o + obj-$(CONFIG_AD799X) += ad799x.o + obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o ++obj-$(CONFIG_APALIS_TK1_K20_ADC) += apalis-tk1-k20_adc.o + obj-$(CONFIG_AT91_ADC) += at91_adc.o + obj-$(CONFIG_AT91_SAMA5D2_ADC) += at91-sama5d2_adc.o + obj-$(CONFIG_AXP20X_ADC) += axp20x_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..b45e51df5c81 +--- /dev/null ++++ b/drivers/iio/adc/apalis-tk1-k20_adc.c +@@ -0,0 +1,200 @@ ++/* ++ * Copyright 2016-2017 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 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 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 = apalis_tk1_k20_adc_remove, ++ .probe = apalis_tk1_k20_adc_probe, ++ .driver = { ++ .name = "apalis-tk1-k20-adc", ++ }, ++}; ++ ++module_platform_driver(apalis_tk1_k20_adc_driver); ++ ++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 64b30fe273fd..81e9c604f612 100644 +--- a/drivers/input/touchscreen/Kconfig ++++ b/drivers/input/touchscreen/Kconfig +@@ -92,6 +92,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_AR1021_I2C + tristate "Microchip AR1020/1021 i2c touchscreen" + depends on I2C && OF +diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile +index 850c1562555a..d9352ec30431 100644 +--- a/drivers/input/touchscreen/Makefile ++++ b/drivers/input/touchscreen/Makefile +@@ -14,6 +14,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_AR1021_I2C) += ar1021_i2c.o + obj-$(CONFIG_TOUCHSCREEN_ATMEL_MXT) += atmel_mxt_ts.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..ef9e56c94685 +--- /dev/null ++++ b/drivers/input/touchscreen/apalis-tk1-k20_ts.c +@@ -0,0 +1,234 @@ ++/* ++ * Copyright 2016-2017 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 / 25 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, btn; ++ ++ 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 ((yp != 0) && (xp != 0)) { ++ btn = 1; ++ 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 / 25); ++ } else { ++ dev_dbg(&idev->dev, "report release\n"); ++ btn = 0; ++ } ++ ++ input_report_abs(idev, ABS_PRESSURE, press); ++ input_report_key(idev, BTN_TOUCH, btn); ++ 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 < 4; i++) ++ priv->sample[i] = (buf[(2 * i) + 1] << 8) + buf[2 * 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 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, 0xfff, 0, 0); ++ input_set_abs_params(idev, ABS_Y, 0, 0xfff, 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 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, ++ .probe = apalis_tk1_k20_ts_probe, ++ .remove = apalis_tk1_k20_ts_remove, ++ .driver = { ++ .name = APALIS_TK1_K20_TS_NAME, ++ }, ++}; ++ ++module_platform_driver(apalis_tk1_k20_ts_driver); ++ ++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 fc5e4fef89d2..cd0a1dfd0a63 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -190,6 +190,20 @@ config MFD_AXP20X_RSB + components like regulators or the PEK (Power Enable Key) under the + corresponding menus. + ++config MFD_APALIS_TK1_K20 ++ tristate "K20 on Apalis TK1" ++ depends on SPI_MASTER ++ 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 8703ff17998e..ba4f60862f9f 100644 +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -9,6 +9,7 @@ obj-$(CONFIG_MFD_88PM800) += 88pm800.o 88pm80x.o + obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o + obj-$(CONFIG_MFD_ACT8945A) += act8945a.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_BCM590XX) += bcm590xx.o + obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.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..e89d6adbe471 +--- /dev/null ++++ b/drivers/mfd/apalis-tk1-k20-ezp.h +@@ -0,0 +1,47 @@ ++/* ++ * Copyright 2016-2017 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 4080000 ++#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..913be65c33e6 +--- /dev/null ++++ b/drivers/mfd/apalis-tk1-k20.c +@@ -0,0 +1,1062 @@ ++/* ++ * Copyright 2016-2017 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 ++ ++#include "apalis-tk1-k20-ezp.h" ++#define APALIS_TK1_K20_MAX_MSG 4 ++ ++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_LAST_REG, ++ ++ .cache_type = REGCACHE_NONE, ++ .use_single_rw = 0, ++}; ++ ++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[APALIS_TK1_K20_MAX_BULK] = {APALIS_TK1_K20_READ_INST, ++ *((unsigned char *) reg), val_size, 0x00, 0x00, 0x00, ++ 0x00}; ++ unsigned char r[APALIS_TK1_K20_MAX_BULK]; ++ unsigned char *p = val; ++ int i = 0, j = 0; ++ struct device *dev = context; ++ struct spi_device *spi = to_spi_device(dev); ++ struct spi_transfer t = { ++ .tx_buf = w, ++ .rx_buf = r, ++ .len = 8, ++ .cs_change = 0, ++ .delay_usecs = 0, ++ }; ++ ++ struct spi_message m; ++ int ret; ++ spi->mode = SPI_MODE_1; ++ ++ if (reg_size != 1) ++ return -ENOTSUPP; ++ ++ if (val_size == 1) { ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ ret = spi_sync(spi, &m); ++ ++ for (i = 3; i < 7; i++ ) ++ { ++ if (((unsigned char *)t.rx_buf)[i] == 0x55) { ++ *p = ((unsigned char *)t.rx_buf)[i + 1]; ++ return ret; ++ } ++ } ++ ++ for (j = 0; j < APALIS_TK1_MAX_RETRY_CNT; j++) { ++ udelay(250 * j * j); ++ t.tx_buf = w; ++ t.rx_buf = r; ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ ret = spi_sync(spi, &m); ++ for (i = 3; i < 7; i++ ) ++ { ++ if (((unsigned char *)t.rx_buf)[i] == 0x55) { ++ *p = ((unsigned char *)t.rx_buf)[i + 1]; ++ return ret; ++ } ++ } ++ } ++ ret = -EIO; ++ ++ } else if ((val_size > 1) && (val_size < APALIS_TK1_K20_MAX_BULK)) { ++ t.len = 5; ++ w[0] = APALIS_TK1_K20_BULK_READ_INST; ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ ret = spi_sync(spi, &m); ++ /* no need to reinit the message*/ ++ t.len = val_size; ++ t.rx_buf = p; ++ /* just use the same transfer */ ++ ret = spi_sync(spi, &m); ++ ++ } else { ++ return -ENOTSUPP; ++ } ++ ++ 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[APALIS_TK1_K20_MAX_BULK]; ++ int ret; ++ ++ ++ spi->mode = SPI_MODE_1; ++ ++ if (count == 2) { ++ out_data[0] = APALIS_TK1_K20_WRITE_INST; ++ out_data[1] = ((uint8_t *)data)[0]; ++ out_data[2] = ((uint8_t *)data)[1]; ++ ret = spi_write(spi, out_data, 3); ++ ++ } else if ((count > 2) && (count < APALIS_TK1_K20_MAX_BULK)) { ++ out_data[0] = APALIS_TK1_K20_BULK_WRITE_INST; ++ out_data[1] = ((uint8_t *)data)[0]; ++ out_data[2] = count - 1; ++ memcpy(&out_data[3], &((uint8_t *)data)[1], count - 1); ++ ret = spi_write(spi, out_data, count + 2); ++ } else { ++ dev_err(dev, "Apalis TK1 K20 MFD invalid write count = %d\n", ++ count); ++ return -ENOTSUPP; ++ } ++ return ret; ++} ++ ++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_dbg(apalis_tk1_k20->dev, "[0x%02x] -> 0x%02x ret = %d\n", offset, ++ *val, ret); ++ ++ 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) ++{ ++ int ret; ++ ++ if (val >= BIT(24)) ++ return -EINVAL; ++ ++ ret = regmap_write(apalis_tk1_k20->regmap, offset, val); ++ ++ dev_dbg(apalis_tk1_k20->dev, "[0x%02x] <- 0x%02x ret = %d\n", offset, val, ++ ret); ++ ++ return ret; ++} ++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_dbg(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_dbg(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_dbg(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 = -1; ++ if (irq != APALIS_TK1_K20_CAN1_IRQ && irq != APALIS_TK1_K20_CAN0_IRQ) { ++ virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq); ++ ++ } else { ++ virq = (irq == APALIS_TK1_K20_CAN0_IRQ) ? ++ apalis_tk1_k20->can0_irq:apalis_tk1_k20->can1_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 = -1; ++ if (irq != APALIS_TK1_K20_CAN1_IRQ && irq != APALIS_TK1_K20_CAN0_IRQ) { ++ virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq); ++ ++ } else { ++ virq = (irq == APALIS_TK1_K20_CAN0_IRQ) ? ++ apalis_tk1_k20->can0_irq:apalis_tk1_k20->can1_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 = -1; ++ int irq_flags = IRQF_ONESHOT; ++ if (irq != APALIS_TK1_K20_CAN1_IRQ && irq != APALIS_TK1_K20_CAN0_IRQ) { ++ virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq); ++ irq_flags = IRQF_ONESHOT; ++ } else { ++ virq = (irq == APALIS_TK1_K20_CAN0_IRQ) ? ++ apalis_tk1_k20->can0_irq:apalis_tk1_k20->can1_irq; ++ irq_flags = IRQF_ONESHOT | IRQF_TRIGGER_FALLING | ++ IRQF_TRIGGER_RISING; ++ } ++ return devm_request_threaded_irq(apalis_tk1_k20->dev, virq, ++ NULL, handler, irq_flags, 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 = -1; ++ if (irq != APALIS_TK1_K20_CAN1_IRQ && irq != APALIS_TK1_K20_CAN0_IRQ) { ++ virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq); ++ ++ } else { ++ virq = (irq == APALIS_TK1_K20_CAN0_IRQ) ? ++ apalis_tk1_k20->can0_irq:apalis_tk1_k20->can1_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_id( ++ struct apalis_tk1_k20_regmap *apalis_tk1_k20, const char *name, ++ void *pdata, size_t pdata_size, int id) ++{ ++ 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, id, &cell, 1, NULL, 0, ++ regmap_irq_get_domain(apalis_tk1_k20->irq_data)); ++} ++ ++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) ++{ ++ return apalis_tk1_k20_add_subdevice_pdata_id(apalis_tk1_k20, name, ++ pdata, pdata_size, -1); ++} ++ ++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); ++ msleep(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); ++ msleep(10); ++ apalis_tk1_k20_reset_chip(apalis_tk1_k20); ++ msleep(10); ++ 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_dbg(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; ++ udelay(2000); ++ 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) { ++ msleep(10); ++ if ((apalis_tk1_k20_read_ezport(apalis_tk1_k20, ++ APALIS_TK1_K20_EZP_RDSR, 0, 1, ++ &buffer) < 0) || (j > 10000)) ++ 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) ++ 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->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->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(10); ++ 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 == NULL) && (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 != NULL) { ++ 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 && ++ (fw_entry != NULL)) { ++ 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()) && ++ (fw_entry != NULL)) { ++ 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) && ++ (fw_entry != NULL)) { ++ i = 0; ++ while (apalis_tk1_k20_enter_ezport(apalis_tk1_k20) < 0 ++ && i++ < 5) { ++ msleep(50); ++ } ++ if (i >= 5) { ++ 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 = -EPROBE_DEFER; ++ 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 = -EPROBE_DEFER; ++ goto bad; ++ } ++ } ++ if (fw_entry != NULL) ++ release_firmware(fw_entry); ++ ++ msleep(10); ++ apalis_tk1_k20_reset_chip(apalis_tk1_k20); ++ msleep(10); ++ ++ 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) >> 4), (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 = 0; ++ 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 (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_CAN) { ++ apalis_tk1_k20->can0_irq = irq_of_parse_and_map( ++ apalis_tk1_k20->dev->of_node, 1); ++ apalis_tk1_k20->can1_irq = irq_of_parse_and_map( ++ apalis_tk1_k20->dev->of_node, 2); ++ if (apalis_tk1_k20->can0_irq == 0 || ++ apalis_tk1_k20->can1_irq == 0) { ++ apalis_tk1_k20->flags &= ~APALIS_TK1_K20_USES_CAN; ++ dev_err(apalis_tk1_k20->dev, ++ "Missing CAN interrupts.\n"); ++ } ++ } ++ ++ 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) { ++ /* We have 2 CAN devices inside K20 */ ++ 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) { ++ /* We have 2 CAN devices inside K20 */ ++ apalis_tk1_k20_add_subdevice_pdata_id(apalis_tk1_k20, ++ "apalis-tk1-k20-can", ++ NULL, 0, 0); ++ apalis_tk1_k20_add_subdevice_pdata_id(apalis_tk1_k20, ++ "apalis-tk1-k20-can", ++ NULL, 0, 1); ++ } ++ ++ 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. " ++ "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->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->int2_gpio >= 0) ++ gpio_free(apalis_tk1_k20->int2_gpio); ++ ++ kfree(spi->controller_data); ++ spi->controller_data = NULL; ++ ++ 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/drivers/net/can/Kconfig b/drivers/net/can/Kconfig +index ac4ff394bc56..16b2d8e2d16a 100644 +--- a/drivers/net/can/Kconfig ++++ b/drivers/net/can/Kconfig +@@ -88,6 +88,12 @@ config CAN_AT91 + This is a driver for the SoC CAN controller in Atmel's AT91SAM9263 + and AT91SAM9X5 processors. + ++config CAN_APALIS_TK1_K20 ++ tristate "Apalis TK1 K20 CAN controllers" ++ depends on MFD_APALIS_TK1_K20 ++ ---help--- ++ Driver for the Apalis TK1 K20 CAN controllers. ++ + config CAN_BFIN + depends on BF534 || BF536 || BF537 || BF538 || BF539 || BF54x + tristate "Analog Devices Blackfin on-chip CAN" +diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile +index 02b8ed794564..80ca9eba724a 100644 +--- a/drivers/net/can/Makefile ++++ b/drivers/net/can/Makefile +@@ -19,6 +19,7 @@ obj-y += usb/ + obj-y += softing/ + + obj-$(CONFIG_CAN_AT91) += at91_can.o ++obj-$(CONFIG_CAN_APALIS_TK1_K20) += apalis-tk1-k20-can.o + obj-$(CONFIG_CAN_BFIN) += bfin_can.o + obj-$(CONFIG_CAN_CC770) += cc770/ + obj-$(CONFIG_CAN_C_CAN) += c_can/ +diff --git a/drivers/net/can/apalis-tk1-k20-can.c b/drivers/net/can/apalis-tk1-k20-can.c +new file mode 100644 +index 000000000000..e24adbb35dfd +--- /dev/null ++++ b/drivers/net/can/apalis-tk1-k20-can.c +@@ -0,0 +1,817 @@ ++/* ++ * Copyright 2016-2017 Toradex AG ++ * Dominik Sliwa ++ * ++ * CAN bus driver for Apalis TK1 K20 CAN Controller over MFD device ++ * based on MCP251x CAN driver ++ * ++ * 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 ++#include ++#include ++#include ++ ++/* Buffer size required for the largest transfer (i.e., reading a ++ * frame) ++ */ ++#define CAN_FRAME_MAX_LEN 8 ++#define CAN_HEADER_MAX_LEN 5 ++#define CAN_TRANSFER_BUF_LEN (CAN_HEADER_MAX_LEN + CAN_FRAME_MAX_LEN) ++ ++#define MB_DLC_OFF 4 ++#define MB_EID_OFF 0 ++#define MB_RTR_SHIFT 4 ++#define MB_IDE_SHIFT 5 ++#define MB_DLC_MASK 0xF ++#define MB_EID_LEN 4 ++ ++#define CANCTRL_MODMASK 0x03 ++#define CANCTRL_INTEN BIT(2) ++#define CANINTF_RX BIT(3) ++#define CANINTF_TX BIT(4) ++#define CANINTF_ERR BIT(5) ++#define CANCTRL_INTMASK (CANINTF_RX | CANINTF_TX | CANINTF_ERR) ++ ++#define EFLG_EWARN 0x01 ++#define EFLG_RXWAR 0x02 ++#define EFLG_TXWAR 0x04 ++#define EFLG_RXEP 0x08 ++#define EFLG_TXEP 0x10 ++#define EFLG_TXBO 0x20 ++#define EFLG_RXOVR 0x40 ++ ++#define TX_ECHO_SKB_MAX 1 ++ ++#define K20_CAN_MAX_ID 1 ++ ++#define DEVICE_NAME "apalis-tk1-k20-can" ++ ++static const struct can_bittiming_const apalis_tk1_k20_can_bittiming_const = { ++ .name = "tk1-k20-can", ++ .tseg1_min = 3, ++ .tseg1_max = 16, ++ .tseg2_min = 2, ++ .tseg2_max = 8, ++ .sjw_max = 4, ++ .brp_min = 1, ++ .brp_max = 64, ++ .brp_inc = 1, ++}; ++ ++struct apalis_tk1_k20_priv { ++ struct can_priv can; ++ struct net_device *net; ++ struct apalis_tk1_k20_regmap *apalis_tk1_k20; ++ struct apalis_tk1_k20_can_platform_data *pdata; ++ ++ struct sk_buff *tx_skb; ++ int tx_len; ++ ++ struct workqueue_struct *wq; ++ struct work_struct tx_work; ++ struct work_struct restart_work; ++ struct mutex apalis_tk1_k20_can_lock; ++ ++ int force_quit; ++ int after_suspend; ++#define AFTER_SUSPEND_UP 1 ++#define AFTER_SUSPEND_DOWN 2 ++#define AFTER_SUSPEND_RESTART 4 ++ int restart_tx; ++}; ++ ++static void apalis_tk1_k20_can_clean(struct net_device *net) ++{ ++ struct apalis_tk1_k20_priv *priv = netdev_priv(net); ++ ++ if (priv->tx_skb || priv->tx_len) ++ net->stats.tx_errors++; ++ if (priv->tx_skb) ++ dev_kfree_skb(priv->tx_skb); ++ if (priv->tx_len) ++ can_free_echo_skb(priv->net, 0); ++ priv->tx_skb = NULL; ++ priv->tx_len = 0; ++} ++ ++static void apalis_tk1_k20_can_hw_tx_frame(struct net_device *net, u8 *buf, ++ int len, int tx_buf_idx) ++{ ++ /* TODO: Implement multiple TX buffer handling */ ++ struct apalis_tk1_k20_priv *priv = netdev_priv(net); ++ ++ apalis_tk1_k20_lock(priv->apalis_tk1_k20); ++ apalis_tk1_k20_reg_write_bulk(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN_OUT_BUF ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), buf, len); ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++} ++ ++static void apalis_tk1_k20_can_hw_tx(struct net_device *net, ++ struct can_frame *frame, int tx_buf_idx) ++{ ++ u8 buf[CAN_TRANSFER_BUF_LEN]; ++ ++ buf[MB_DLC_OFF] = frame->can_dlc; ++ memcpy(buf + MB_EID_OFF, &frame->can_id, MB_EID_LEN); ++ memcpy(buf + CAN_HEADER_MAX_LEN, frame->data, frame->can_dlc); ++ ++ apalis_tk1_k20_can_hw_tx_frame(net, buf, frame->can_dlc ++ + CAN_HEADER_MAX_LEN, tx_buf_idx); ++} ++ ++static void apalis_tk1_k20_can_hw_rx(struct net_device *net, int buf_idx) ++{ ++ int i = 0; ++ struct apalis_tk1_k20_priv *priv = netdev_priv(net); ++ struct sk_buff *skb; ++ struct can_frame *frame; ++ u8 buf[CAN_TRANSFER_BUF_LEN * APALIS_TK1_MAX_CAN_DMA_XREF]; ++ u32 frame_available = 0; ++ ++ apalis_tk1_k20_lock(priv->apalis_tk1_k20); ++ apalis_tk1_k20_reg_read(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN_IN_BUF_CNT ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), &frame_available); ++ frame_available = min(frame_available, APALIS_TK1_MAX_CAN_DMA_XREF); ++ apalis_tk1_k20_reg_read_bulk(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN_IN_BUF ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), buf, ++ CAN_TRANSFER_BUF_LEN * frame_available); ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ ++ for (i = 0; i < frame_available; i++) { ++ skb = alloc_can_skb(priv->net, &frame); ++ if (!skb) { ++ dev_err(&net->dev, "cannot allocate RX skb\n"); ++ priv->net->stats.rx_dropped++; ++ return; ++ } ++ memcpy(&frame->can_id, &buf[i * CAN_TRANSFER_BUF_LEN] ++ + MB_EID_OFF, MB_EID_LEN); ++ /* Data length */ ++ frame->can_dlc = get_can_dlc(buf[i * CAN_TRANSFER_BUF_LEN ++ + MB_DLC_OFF]); ++ memcpy(frame->data, &buf[i * CAN_TRANSFER_BUF_LEN] ++ + CAN_HEADER_MAX_LEN, frame->can_dlc); ++ ++ priv->net->stats.rx_packets++; ++ priv->net->stats.rx_bytes += frame->can_dlc; ++ ++ can_led_event(priv->net, CAN_LED_EVENT_RX); ++ ++ netif_rx_ni(skb); ++ } ++ ++ ++} ++ ++static netdev_tx_t apalis_tk1_k20_can_hard_start_xmit(struct sk_buff *skb, ++ struct net_device *net) ++{ ++ struct apalis_tk1_k20_priv *priv = netdev_priv(net); ++ ++ if (priv->tx_skb || priv->tx_len) { ++ dev_warn(&net->dev, "hard_xmit called while TX busy\n"); ++ return NETDEV_TX_BUSY; ++ } ++ ++ if (can_dropped_invalid_skb(net, skb)) ++ return NETDEV_TX_OK; ++ ++ netif_stop_queue(net); ++ priv->tx_skb = skb; ++ queue_work(priv->wq, &priv->tx_work); ++ ++ return NETDEV_TX_OK; ++} ++ ++static int apalis_tk1_k20_can_do_set_mode(struct net_device *net, ++ enum can_mode mode) ++{ ++ struct apalis_tk1_k20_priv *priv = netdev_priv(net); ++ ++ switch (mode) { ++ case CAN_MODE_START: ++ apalis_tk1_k20_can_clean(net); ++ /* We have to delay work since I/O may sleep */ ++ priv->can.state = CAN_STATE_ERROR_ACTIVE; ++ priv->restart_tx = 1; ++ if (priv->can.restart_ms == 0) ++ priv->after_suspend = AFTER_SUSPEND_RESTART; ++ queue_work(priv->wq, &priv->restart_work); ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int apalis_tk1_k20_can_set_normal_mode(struct net_device *net) ++{ ++ struct apalis_tk1_k20_priv *priv = netdev_priv(net); ++ ++ apalis_tk1_k20_lock(priv->apalis_tk1_k20); ++ /* Enable interrupts */ ++ apalis_tk1_k20_reg_rmw(priv->apalis_tk1_k20, APALIS_TK1_K20_CANREG ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), ++ CANCTRL_INTEN, CANCTRL_INTEN); ++ ++ if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) { ++ /* Put device into loopback mode */ ++ apalis_tk1_k20_reg_rmw(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CANREG ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), CANCTRL_MODMASK, ++ CAN_CTRLMODE_LOOPBACK); ++ } else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) { ++ /* Put device into listen-only mode */ ++ apalis_tk1_k20_reg_rmw(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CANREG ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), CANCTRL_MODMASK, ++ CAN_CTRLMODE_LISTENONLY); ++ } else { ++ /* Put device into normal mode */ ++ apalis_tk1_k20_reg_rmw(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CANREG ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), CANCTRL_MODMASK, ++ 0x00); ++ } ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ priv->can.state = CAN_STATE_ERROR_ACTIVE; ++ return 0; ++} ++ ++static int apalis_tk1_k20_can_do_set_bittiming(struct net_device *net) ++{ ++ struct apalis_tk1_k20_priv *priv = netdev_priv(net); ++ struct can_bittiming *bt = &priv->can.bittiming; ++ ++ if ((bt->bitrate / APALIS_TK1_CAN_CLK_UNIT) > 0xFF) ++ return -EINVAL; ++ ++ apalis_tk1_k20_lock(priv->apalis_tk1_k20); ++ apalis_tk1_k20_reg_write(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN_BAUD_REG ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), (bt->bitrate / ++ APALIS_TK1_CAN_CLK_UNIT) & 0xFF); ++ apalis_tk1_k20_reg_write(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN_BIT_1 ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), ++ ((bt->sjw & 0x3) << 6) | ++ ((bt->phase_seg2 & 0x7) << 3) | ++ (bt->phase_seg1 & 0x7)); ++ apalis_tk1_k20_reg_write(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN_BIT_2 ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), ++ (bt->prop_seg & 0x7)); ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ dev_dbg(priv->apalis_tk1_k20->dev, "Setting CAN%d bitrate " \ ++ "to %d (0x%X * 6.25kHz)\n", priv->pdata->id, bt->bitrate, ++ (bt->bitrate / APALIS_TK1_CAN_CLK_UNIT) & 0xFF); ++ dev_dbg(priv->apalis_tk1_k20->dev, "Setting CAN%d bit timing " \ ++ "RJW = %d, PSEG1 = %d, PSEG2 = %d, PROPSEG = %d\n", ++ priv->pdata->id, bt->sjw, bt->phase_seg1, ++ bt->phase_seg2, bt->prop_seg); ++ dev_dbg(priv->apalis_tk1_k20->dev, "Setting CAN%d bit timing " \ ++ "bitrate = %d\n", priv->pdata->id, bt->bitrate); ++ ++ return 0; ++} ++ ++static int apalis_tk1_k20_can_setup(struct net_device *net, ++ struct apalis_tk1_k20_priv *priv) ++{ ++ apalis_tk1_k20_can_do_set_bittiming(net); ++ ++ return 0; ++} ++ ++static int apalis_tk1_k20_can_hw_reset(struct net_device *net) ++{ ++ return 0; ++} ++ ++static void apalis_tk1_k20_can_open_clean(struct net_device *net) ++{ ++ struct apalis_tk1_k20_priv *priv = netdev_priv(net); ++ struct apalis_tk1_k20_can_platform_data *pdata = priv->pdata; ++ ++ if (pdata->id == 0) ++ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN0_IRQ, priv); ++ if (pdata->id == 1) ++ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN1_IRQ, priv); ++ close_candev(net); ++} ++ ++static int apalis_tk1_k20_can_stop(struct net_device *net) ++{ ++ struct apalis_tk1_k20_priv *priv = netdev_priv(net); ++ struct apalis_tk1_k20_can_platform_data *pdata = priv->pdata; ++ ++ close_candev(net); ++ ++ priv->force_quit = 1; ++ if (pdata->id == 0) ++ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN0_IRQ, priv); ++ if (pdata->id == 1) ++ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN1_IRQ, priv); ++ destroy_workqueue(priv->wq); ++ priv->wq = NULL; ++ ++ mutex_lock(&priv->apalis_tk1_k20_can_lock); ++ apalis_tk1_k20_lock(priv->apalis_tk1_k20); ++ if (pdata->id == 0) ++ apalis_tk1_k20_irq_mask(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN0_IRQ); ++ if (pdata->id == 1) ++ apalis_tk1_k20_irq_mask(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN1_IRQ); ++ /* Disable and clear pending interrupts */ ++ priv->can.state = CAN_STATE_STOPPED; ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ mutex_unlock(&priv->apalis_tk1_k20_can_lock); ++ ++ can_led_event(net, CAN_LED_EVENT_STOP); ++ ++ return 0; ++} ++ ++static void apalis_tk1_k20_can_error_skb(struct net_device *net, int can_id, ++ int data1) ++{ ++ struct sk_buff *skb; ++ struct can_frame *frame; ++ ++ skb = alloc_can_err_skb(net, &frame); ++ if (skb) { ++ frame->can_id |= can_id; ++ frame->data[1] = data1; ++ netif_rx_ni(skb); ++ } else { ++ netdev_err(net, "cannot allocate error skb\n"); ++ } ++} ++ ++static void apalis_tk1_k20_can_tx_work_handler(struct work_struct *ws) ++{ ++ struct apalis_tk1_k20_priv *priv = container_of(ws, ++ struct apalis_tk1_k20_priv, tx_work); ++ struct net_device *net = priv->net; ++ struct can_frame *frame; ++ ++ mutex_lock(&priv->apalis_tk1_k20_can_lock); ++ if (priv->tx_skb) { ++ if (priv->can.state == CAN_STATE_BUS_OFF) { ++ apalis_tk1_k20_can_clean(net); ++ } else { ++ frame = (struct can_frame *)priv->tx_skb->data; ++ ++ if (frame->can_dlc > CAN_FRAME_MAX_LEN) ++ frame->can_dlc = CAN_FRAME_MAX_LEN; ++ apalis_tk1_k20_can_hw_tx(net, frame, 0); ++ priv->tx_len = 1 + frame->can_dlc; ++ can_put_echo_skb(priv->tx_skb, net, 0); ++ priv->tx_skb = NULL; ++ } ++ } ++ mutex_unlock(&priv->apalis_tk1_k20_can_lock); ++} ++ ++#ifdef CONFIG_PM_SLEEP ++ ++static int apalis_tk1_k20_can_suspend(struct device *dev) ++{ ++ struct apalis_tk1_k20_priv *priv = dev_get_drvdata(dev); ++ struct apalis_tk1_k20_can_platform_data *pdata = priv->pdata; ++ ++ priv->force_quit = 1; ++ ++ mutex_lock(&priv->apalis_tk1_k20_can_lock); ++ apalis_tk1_k20_lock(priv->apalis_tk1_k20); ++ if (pdata->id == 0) ++ apalis_tk1_k20_irq_mask(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN0_IRQ); ++ if (pdata->id == 1) ++ apalis_tk1_k20_irq_mask(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN1_IRQ); ++ /* Disable interrupts */ ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ mutex_unlock(&priv->apalis_tk1_k20_can_lock); ++ /* Note: at this point neither IST nor workqueues are running. ++ * open/stop cannot be called anyway so locking is not needed ++ */ ++ if (netif_running(priv->net)) { ++ netif_device_detach(priv->net); ++ ++ priv->after_suspend = AFTER_SUSPEND_UP; ++ } else { ++ priv->after_suspend = AFTER_SUSPEND_DOWN; ++ } ++ ++ return 0; ++} ++ ++static int apalis_tk1_k20_can_resume(struct device *dev) ++{ ++ struct apalis_tk1_k20_priv *priv = dev_get_drvdata(dev); ++ struct apalis_tk1_k20_can_platform_data *pdata = priv->pdata; ++ ++ if (priv->after_suspend & AFTER_SUSPEND_UP) ++ queue_work(priv->wq, &priv->restart_work); ++ else ++ priv->after_suspend = 0; ++ ++ priv->force_quit = 0; ++ mutex_lock(&priv->apalis_tk1_k20_can_lock); ++ apalis_tk1_k20_lock(priv->apalis_tk1_k20); ++ if (pdata->id == 0) ++ apalis_tk1_k20_irq_unmask(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN0_IRQ); ++ if (pdata->id == 1) ++ apalis_tk1_k20_irq_unmask(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN1_IRQ); ++ /* Enable interrupts */ ++ priv->can.state = CAN_STATE_STOPPED; ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ mutex_unlock(&priv->apalis_tk1_k20_can_lock); ++ return 0; ++} ++ ++static SIMPLE_DEV_PM_OPS(apalis_tk1_k20_can_pm_ops, apalis_tk1_k20_can_suspend, ++ apalis_tk1_k20_can_resume); ++ ++#endif ++ ++static void apalis_tk1_k20_can_restart_work_handler(struct work_struct *ws) ++{ ++ struct apalis_tk1_k20_priv *priv = container_of(ws, ++ struct apalis_tk1_k20_priv, restart_work); ++ struct net_device *net = priv->net; ++ ++ mutex_lock(&priv->apalis_tk1_k20_can_lock); ++ if (priv->after_suspend) { ++ mdelay(10); ++ apalis_tk1_k20_can_hw_reset(net); ++ apalis_tk1_k20_can_setup(net, priv); ++ if (priv->after_suspend & AFTER_SUSPEND_RESTART) { ++ apalis_tk1_k20_can_set_normal_mode(net); ++ } else if (priv->after_suspend & AFTER_SUSPEND_UP) { ++ netif_device_attach(net); ++ apalis_tk1_k20_can_clean(net); ++ apalis_tk1_k20_can_set_normal_mode(net); ++ netif_wake_queue(net); ++ } ++ priv->after_suspend = 0; ++ priv->force_quit = 0; ++ } ++ ++ if (priv->restart_tx) { ++ priv->restart_tx = 0; ++ apalis_tk1_k20_can_clean(net); ++ netif_wake_queue(net); ++ apalis_tk1_k20_can_error_skb(net, CAN_ERR_RESTARTED, 0); ++ } ++ mutex_unlock(&priv->apalis_tk1_k20_can_lock); ++} ++ ++static irqreturn_t apalis_tk1_k20_can_ist(int irq, void *dev_id) ++{ ++ struct apalis_tk1_k20_priv *priv = dev_id; ++ struct net_device *net = priv->net; ++ ++ mutex_lock(&priv->apalis_tk1_k20_can_lock); ++ while (!priv->force_quit) { ++ enum can_state new_state = CAN_STATE_ERROR_ACTIVE; ++ int ret; ++ u32 intf, eflag; ++ u8 clear_intf = 0; ++ int can_id = 0, data1 = 0; ++ ++ apalis_tk1_k20_lock(priv->apalis_tk1_k20); ++ ret = apalis_tk1_k20_reg_read(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CANREG ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), &intf); ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ ++ if (ret) { ++ dev_err(&net->dev, "Communication error\n"); ++ break; ++ } ++ ++ intf &= CANCTRL_INTMASK; ++ /* receive */ ++ if (intf & CANINTF_RX) ++ apalis_tk1_k20_can_hw_rx(net, 0); ++ ++ /* any error interrupt we need to clear? */ ++ if (intf & CANINTF_ERR) ++ clear_intf |= intf & CANINTF_ERR; ++ apalis_tk1_k20_lock(priv->apalis_tk1_k20); ++ if (clear_intf) ++ ret = apalis_tk1_k20_reg_write(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CANREG_CLR ++ + APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id),clear_intf); ++ if (ret) { ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ dev_err(&net->dev, "Communication error\n"); ++ break; ++ } ++ ++ /* Update can state */ ++ if (intf & CANINTF_ERR) { ++ ret = apalis_tk1_k20_reg_read(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CANERR + ++ APALIS_TK1_K20_CAN_DEV_OFFSET( ++ priv->pdata->id), &eflag); ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ if (ret) { ++ dev_err(&net->dev, "Communication error\n"); ++ break; ++ } ++ if (eflag & EFLG_TXBO) { ++ new_state = CAN_STATE_BUS_OFF; ++ can_id |= CAN_ERR_BUSOFF; ++ } else if (eflag & EFLG_TXEP) { ++ new_state = CAN_STATE_ERROR_PASSIVE; ++ can_id |= CAN_ERR_CRTL; ++ data1 |= CAN_ERR_CRTL_TX_PASSIVE; ++ } else if (eflag & EFLG_RXEP) { ++ new_state = CAN_STATE_ERROR_PASSIVE; ++ can_id |= CAN_ERR_CRTL; ++ data1 |= CAN_ERR_CRTL_RX_PASSIVE; ++ } else if (eflag & EFLG_TXWAR) { ++ new_state = CAN_STATE_ERROR_WARNING; ++ can_id |= CAN_ERR_CRTL; ++ data1 |= CAN_ERR_CRTL_TX_WARNING; ++ } else if (eflag & EFLG_RXWAR) { ++ new_state = CAN_STATE_ERROR_WARNING; ++ can_id |= CAN_ERR_CRTL; ++ data1 |= CAN_ERR_CRTL_RX_WARNING; ++ } else { ++ new_state = CAN_STATE_ERROR_ACTIVE; ++ } ++ } ++ else { ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ } ++ ++ /* Update can state statistics */ ++ switch (priv->can.state) { ++ case CAN_STATE_ERROR_ACTIVE: ++ if (new_state >= CAN_STATE_ERROR_WARNING && ++ new_state <= CAN_STATE_BUS_OFF) ++ priv->can.can_stats.error_warning++; ++ case CAN_STATE_ERROR_WARNING: /* fallthrough */ ++ if (new_state >= CAN_STATE_ERROR_PASSIVE && ++ new_state <= CAN_STATE_BUS_OFF) ++ priv->can.can_stats.error_passive++; ++ break; ++ default: ++ break; ++ } ++ priv->can.state = new_state; ++ ++ if (intf & CANINTF_ERR) { ++ /* Handle overflow counters */ ++ if (eflag & EFLG_RXOVR) { ++ if (eflag & EFLG_RXOVR) { ++ net->stats.rx_over_errors++; ++ net->stats.rx_errors++; ++ } ++ can_id |= CAN_ERR_CRTL; ++ data1 |= CAN_ERR_CRTL_RX_OVERFLOW; ++ } ++ apalis_tk1_k20_can_error_skb(net, can_id, data1); ++ } ++ ++ if (priv->can.state == CAN_STATE_BUS_OFF && ++ priv->can.restart_ms == 0) { ++ priv->force_quit = 1; ++ can_bus_off(net); ++ break; ++ } ++ ++ if (intf == 0) ++ break; ++ ++ if (intf & CANINTF_TX) { ++ net->stats.tx_packets++; ++ net->stats.tx_bytes += priv->tx_len - 1; ++ can_led_event(net, CAN_LED_EVENT_TX); ++ if (priv->tx_len) { ++ can_get_echo_skb(net, 0); ++ priv->tx_len = 0; ++ } ++ netif_wake_queue(net); ++ if (!(intf & (CANINTF_RX | CANINTF_ERR))) ++ break; ++ } ++ } ++ mutex_unlock(&priv->apalis_tk1_k20_can_lock); ++ return IRQ_HANDLED; ++} ++ ++static int apalis_tk1_k20_can_open(struct net_device *net) ++{ ++ struct apalis_tk1_k20_priv *priv = netdev_priv(net); ++ struct apalis_tk1_k20_can_platform_data *pdata = priv->pdata; ++ int ret; ++ ++ ret = open_candev(net); ++ if (ret) { ++ dev_err(&net->dev, "unable to initialize CAN\n"); ++ return ret; ++ } ++ ++ mutex_lock(&priv->apalis_tk1_k20_can_lock); ++ ++ priv->force_quit = 0; ++ priv->tx_skb = NULL; ++ priv->tx_len = 0; ++ apalis_tk1_k20_lock(priv->apalis_tk1_k20); ++ if (pdata->id == 0) ++ ret = apalis_tk1_k20_irq_request(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN0_IRQ, ++ apalis_tk1_k20_can_ist, ++ DEVICE_NAME, priv); ++ if (pdata->id == 1) ++ ret = apalis_tk1_k20_irq_request(priv->apalis_tk1_k20, ++ APALIS_TK1_K20_CAN1_IRQ, ++ apalis_tk1_k20_can_ist, ++ DEVICE_NAME, priv); ++ apalis_tk1_k20_unlock(priv->apalis_tk1_k20); ++ if (ret) { ++ dev_err(&net->dev, "failed to acquire IRQ\n"); ++ close_candev(net); ++ goto open_unlock; ++ } ++ ++ priv->wq = create_freezable_workqueue("apalis_tk1_k20_wq"); ++ INIT_WORK(&priv->tx_work, apalis_tk1_k20_can_tx_work_handler); ++ INIT_WORK(&priv->restart_work, apalis_tk1_k20_can_restart_work_handler); ++ ++ ret = apalis_tk1_k20_can_hw_reset(net); ++ if (ret) { ++ apalis_tk1_k20_can_open_clean(net); ++ goto open_unlock; ++ } ++ ++ ret = apalis_tk1_k20_can_setup(net, priv); ++ if (ret) { ++ apalis_tk1_k20_can_open_clean(net); ++ goto open_unlock; ++ } ++ ++ ret = apalis_tk1_k20_can_set_normal_mode(net); ++ if (ret) { ++ apalis_tk1_k20_can_open_clean(net); ++ goto open_unlock; ++ } ++ ++ can_led_event(net, CAN_LED_EVENT_OPEN); ++ ++ netif_wake_queue(net); ++ ++open_unlock: ++ mutex_unlock(&priv->apalis_tk1_k20_can_lock); ++ return ret; ++} ++ ++static const struct net_device_ops apalis_tk1_k20_netdev_ops = { ++ .ndo_open = apalis_tk1_k20_can_open, ++ .ndo_stop = apalis_tk1_k20_can_stop, ++ .ndo_start_xmit = apalis_tk1_k20_can_hard_start_xmit, ++}; ++ ++static int apalis_tk1_k20_can_probe(struct platform_device *pdev) ++{ ++ struct net_device *net; ++ struct apalis_tk1_k20_priv *priv; ++ struct apalis_tk1_k20_can_platform_data *pdata = ++ pdev->dev.platform_data; ++ int ret = -ENODEV; ++ ++ if (!pdata) { ++ pdata = kmalloc(sizeof(struct apalis_tk1_k20_can_platform_data), ++ GFP_KERNEL); ++ if (pdev->id == -1) ++ pdata->id = 0; ++ if (pdev->id >= 0 && pdev->id <= K20_CAN_MAX_ID) ++ pdata->id = pdev->id; ++ else ++ goto error_out; ++ } ++ ++ if (pdata->id > K20_CAN_MAX_ID) ++ goto error_out; ++ /* Allocate can/net device */ ++ net = alloc_candev(sizeof(struct apalis_tk1_k20_priv), TX_ECHO_SKB_MAX); ++ if (!net) { ++ ret = -ENOMEM; ++ goto error_out; ++ } ++ ++ net->netdev_ops = &apalis_tk1_k20_netdev_ops; ++ net->flags |= IFF_ECHO; ++ ++ priv = netdev_priv(net); ++ priv->can.bittiming_const = &apalis_tk1_k20_can_bittiming_const; ++ priv->can.do_set_mode = apalis_tk1_k20_can_do_set_mode; ++ priv->can.clock.freq = 8000000; ++ priv->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES | ++ CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY; ++ priv->net = net; ++ priv->pdata = pdata; ++ priv->apalis_tk1_k20 = dev_get_drvdata(pdev->dev.parent); ++ ++ mutex_init(&priv->apalis_tk1_k20_can_lock); ++ ++ SET_NETDEV_DEV(net, &pdev->dev); ++ ++ platform_set_drvdata(pdev, priv); ++ ++ ret = register_candev(net); ++ if (ret) ++ goto error_probe; ++ ++ devm_can_led_init(net); ++ ++ dev_info(&pdev->dev, "probed %d\n", pdev->id); ++ ++ return ret; ++ ++error_probe: ++ free_candev(net); ++error_out: ++ return ret; ++} ++ ++static int apalis_tk1_k20_can_remove(struct platform_device *pdev) ++{ ++ struct apalis_tk1_k20_priv *priv = platform_get_drvdata(pdev); ++ struct net_device *net = priv->net; ++ ++ unregister_candev(net); ++ free_candev(net); ++ ++ return 0; ++} ++ ++static const struct platform_device_id apalis_tk1_k20_can_idtable[] = { ++ {.name = "apalis-tk1-k20-can", }, ++ { /* sentinel */} ++}; ++ ++MODULE_DEVICE_TABLE(platform, apalis_tk1_k20_can_idtable); ++ ++static struct platform_driver apalis_tk1_k20_can_driver = { ++ .id_table = apalis_tk1_k20_can_idtable, ++ .remove = apalis_tk1_k20_can_remove, ++ .probe = apalis_tk1_k20_can_probe, ++ .driver = { ++ .name = DEVICE_NAME, ++#ifdef CONFIG_PM_SLEEP ++ .pm = &apalis_tk1_k20_can_pm_ops, ++#endif ++ }, ++}; ++ ++module_platform_driver(apalis_tk1_k20_can_driver); ++ ++MODULE_DESCRIPTION("CAN driver for K20 MCU on Apalis TK1"); ++MODULE_AUTHOR("Dominik Sliwa "); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/spi/spi-tegra114.c b/drivers/spi/spi-tegra114.c +index 44550182a4a3..329dd49724a1 100644 +--- a/drivers/spi/spi-tegra114.c ++++ b/drivers/spi/spi-tegra114.c +@@ -703,12 +703,6 @@ static u32 tegra_spi_setup_transfer_one(struct spi_device *spi, + } else + tegra_spi_writel(tspi, command1, SPI_COMMAND1); + +- command1 |= SPI_CS_SW_HW; +- if (spi->mode & SPI_CS_HIGH) +- command1 |= SPI_CS_SS_VAL; +- else +- command1 &= ~SPI_CS_SS_VAL; +- + tegra_spi_writel(tspi, 0, SPI_COMMAND2); + } else { + command1 = tspi->command1_reg; +@@ -776,6 +770,7 @@ static int tegra_spi_setup(struct spi_device *spi) + + spin_lock_irqsave(&tspi->lock, flags); + val = tspi->def_command1_reg; ++ val |= SPI_CS_SEL(spi->chip_select); + if (spi->mode & SPI_CS_HIGH) + val &= ~SPI_CS_POL_INACTIVE(spi->chip_select); + else +diff --git a/include/linux/mfd/apalis-tk1-k20-api.h b/include/linux/mfd/apalis-tk1-k20-api.h +new file mode 100644 +index 000000000000..85bbf9f28ca4 +--- /dev/null ++++ b/include/linux/mfd/apalis-tk1-k20-api.h +@@ -0,0 +1,123 @@ ++/* ++ * Copyright 2016-2017 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_API_H ++#define __LINUX_MFD_APALIS_TK1_K20_API_H ++ ++/* 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 250u ++#define APALIS_TK1_K20_HEADER 4u ++ ++/* 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 RO(reset of read) */ ++#define APALIS_TK1_K20_CTRREG 0x03 /* general control register RW */ ++#define APALIS_TK1_K20_MSQREG 0x04 /* IRQ mask register RW */ ++ ++/* 0x05-0x0F Reserved */ ++ ++/* CAN Registers */ ++#define APALIS_TK1_K20_CANREG 0x10 /* CAN0 control & status register RW */ ++#define APALIS_TK1_K20_CANREG_CLR 0x11 /* CAN0 CANREG clear register WO */ ++#define APALIS_TK1_K20_CANERR 0x12 /* CAN0 error register RW */ ++#define APALIS_TK1_K20_CAN_BAUD_REG 0x13 /* CAN0 baud set register RW */ ++#define APALIS_TK1_K20_CAN_BIT_1 0x14 /* CAN0 bit timing register 1 RW */ ++#define APALIS_TK1_K20_CAN_BIT_2 0x15 /* CAN0 bit timing register 2 RW */ ++#define APALIS_TK1_K20_CAN_IN_BUF_CNT 0x16 /* CAN0 IN received data count RO */ ++#define APALIS_TK1_K20_CAN_IN_BUF 0x17 /* CAN0 IN RO */ ++/* buffer size is 13 bytes */ ++#define APALIS_TK1_K20_CAN_IN_BUF_END 0x23 /* CAN0 IN RO */ ++#define APALIS_TK1_K20_CAN_OUT_BUF 0x24 /* CAN0 OUT WO */ ++/* buffer size is 13 bytes */ ++#define APALIS_TK1_K20_CAN_OUT_BUF_END (APALIS_TK1_K20_CAN_OUT_BUF + 13 - 1)/* CAN OUT BUF END */ ++#define APALIS_TK1_K20_CAN_OFFSET 0x30 ++#define APALIS_TK1_K20_CAN_DEV_OFFSET(x) (x ? APALIS_TK1_K20_CAN_OFFSET : 0) ++ ++/* 0x30-0x3F Reserved */ ++/* 0x40-0x62 CAN1 registers same layout as CAN0*/ ++/* 0x63-0x6F Reserved */ ++ ++/* ADC Registers */ ++#define APALIS_TK1_K20_ADCREG 0x70 /* ADC control & status register RW */ ++#define APALIS_TK1_K20_ADC_CH0L 0x71 /* ADC Channel 0 LSB RO */ ++#define APALIS_TK1_K20_ADC_CH0H 0x72 /* ADC Channel 0 MSB RO */ ++#define APALIS_TK1_K20_ADC_CH1L 0x73 /* ADC Channel 1 LSB RO */ ++#define APALIS_TK1_K20_ADC_CH1H 0x74 /* ADC Channel 1 MSB RO */ ++#define APALIS_TK1_K20_ADC_CH2L 0x75 /* ADC Channel 2 LSB RO */ ++#define APALIS_TK1_K20_ADC_CH2H 0x76 /* ADC Channel 2 MSB RO */ ++#define APALIS_TK1_K20_ADC_CH3L 0x77 /* ADC Channel 3 LSB RO */ ++#define APALIS_TK1_K20_ADC_CH3H 0x78 /* ADC Channel 3 MSB RO */ ++/* Bulk read of LSB register can be use to read entire 16-bit in one command */ ++/* Bulk read of APALIS_TK1_K20_ADC_CH0L register can be use to read all ++ * ADC channels in one command */ ++ ++/* 0x79-0x7F reserved */ ++ ++/* TSC Register */ ++#define APALIS_TK1_K20_TSCREG 0x80 /* TSC control & status register RW */ ++#define APALIS_TK1_K20_TSC_XML 0x81 /* TSC X- data LSB RO */ ++#define APALIS_TK1_K20_TSC_XMH 0x82 /* TSC X- data MSB RO */ ++#define APALIS_TK1_K20_TSC_XPL 0x83 /* TSC X+ data LSB RO */ ++#define APALIS_TK1_K20_TSC_XPH 0x84 /* TSC X+ data MSB RO */ ++#define APALIS_TK1_K20_TSC_YML 0x85 /* TSC Y- data LSB RO */ ++#define APALIS_TK1_K20_TSC_YMH 0x86 /* TSC Y- data MSB RO */ ++#define APALIS_TK1_K20_TSC_YPL 0x87 /* TSC Y+ data LSB RO */ ++#define APALIS_TK1_K20_TSC_YPH 0x88 /* 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) ++ ++/* 0x89-0x8F Reserved */ ++ ++/* GPIO Registers */ ++#define APALIS_TK1_K20_GPIOREG 0x90 /* GPIO control & status register RW */ ++#define APALIS_TK1_K20_GPIO_NO 0x91 /* currently configured GPIO RW */ ++#define APALIS_TK1_K20_GPIO_STA 0x92 /* 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) ++ ++/* 0x93-0xFC Reserved */ ++#define APALIS_TK1_K20_LAST_REG 0xFD ++#define APALIS_TK1_K20_RET_REQ 0xFE ++/* 0xFF Reserved */ ++ ++/* 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 0x10 ++ ++#define FW_MINOR (APALIS_TK1_K20_FW_VER & 0x0F) ++#define FW_MAJOR ((APALIS_TK1_K20_FW_VER & 0xF0) >> 4) ++ ++#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_CAN_CLK_UNIT 6250 ++ ++#define APALIS_TK1_MAX_CAN_DMA_XREF 19u ++ ++#endif /* ifndef __LINUX_MFD_APALIS_TK1_K20_API_H */ +diff --git a/include/linux/mfd/apalis-tk1-k20.h b/include/linux/mfd/apalis-tk1-k20.h +new file mode 100644 +index 000000000000..6d9e42b6002e +--- /dev/null ++++ b/include/linux/mfd/apalis-tk1-k20.h +@@ -0,0 +1,114 @@ ++/* ++ * Copyright 2016-2017 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 ++#include ++ ++#define APALIS_TK1_MAX_RETRY_CNT 4 ++ ++#define APALIS_TK1_K20_MAX_SPI_SPEED 6120000 ++ ++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; ++ int can0_irq; ++ int can1_irq; ++ ++ 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 */ +-- +2.14.4 + diff --git a/recipes-kernel/linux/linux-toradex-mainline_4.14.bb b/recipes-kernel/linux/linux-toradex-mainline_4.14.bb index f90c5ea..54224ac 100644 --- a/recipes-kernel/linux/linux-toradex-mainline_4.14.bb +++ b/recipes-kernel/linux/linux-toradex-mainline_4.14.bb @@ -28,6 +28,7 @@ TK1-PATCHES = " \ file://0009-drm-tegra-gem-Reshuffle-declarations.patch \ file://0010-drm-tegra-gem-Make-__tegra_gem_mmap-available-more-w.patch \ file://0011-drm-tegra-fb-Implement-fb_mmap-callback.patch \ + file://0012-apalis-tk1-support-for-k20-mfd.patch \ " SRC_URI = " \ https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-${PV}.tar.xz \ -- cgit v1.2.3