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