From 19722d4d90e711d7826ec7f6461216aa61f2f937 Mon Sep 17 00:00:00 2001 From: Dominik Sliwa Date: Tue, 9 Aug 2016 13:43:10 +0200 Subject: apalis_tk1: Support for K20 based MFD On Apalis TK1 boards K20 MCU is used for CAN, GPIO, ADC and touch screen. This patch includes support for core MFD device, GPIO, ADC and touch screen. Signed-off-by: Dominik Sliwa Acked-by: Marcel Ziswiler --- drivers/mfd/apalis-tk1-k20.c | 940 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 940 insertions(+) create mode 100644 drivers/mfd/apalis-tk1-k20.c (limited to 'drivers/mfd/apalis-tk1-k20.c') diff --git a/drivers/mfd/apalis-tk1-k20.c b/drivers/mfd/apalis-tk1-k20.c new file mode 100644 index 000000000000..cbd1afe5d718 --- /dev/null +++ b/drivers/mfd/apalis-tk1-k20.c @@ -0,0 +1,940 @@ +/* + * Copyright 2016 Toradex AG + * Dominik Sliwa + * + * based on an driver for MC13xxx by: + * Copyright 2009-2010 Pengutronix + * Uwe Kleine-Koenig + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 2 as published by the + * Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "apalis-tk1-k20-ezp.h" + +static const struct spi_device_id apalis_tk1_k20_device_ids[] = { + { + .name = "apalis-tk1-k20", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(spi, apalis_tk1_k20_device_ids); + +static const struct of_device_id apalis_tk1_k20_dt_ids[] = { + { + .compatible = "toradex,apalis-tk1-k20", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, apalis_tk1_k20_dt_ids); + +static const struct regmap_config apalis_tk1_k20_regmap_spi_config = { + .reg_bits = 8, + .pad_bits = 0, + .val_bits = 8, + + .max_register = APALIS_TK1_K20_NUMREGS, + + .cache_type = REGCACHE_NONE, + + .use_single_rw = 1, +}; + +static int apalis_tk1_k20_spi_read(void *context, const void *reg, + size_t reg_size, void *val, size_t val_size) +{ + unsigned char w[3] = {APALIS_TK1_K20_READ_INST, + *((unsigned char *) reg), 0}; + unsigned char r[3]; + unsigned char *p = val; + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + struct spi_transfer t = { + .tx_buf = w, + .rx_buf = r, + .len = 3, + }; + struct spi_message m; + int ret; + + spi->mode = SPI_MODE_1; + if (val_size != 1 || reg_size != 1) + return -ENOTSUPP; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + + dev_vdbg(dev, "Apalis TK1 K20 MFD SPI send done reg = 0x%X\n", + *((unsigned char *) reg)); + + spi_message_init(&m); + t.len = 3; + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + + dev_vdbg(dev, "Apalis TK1 K20 MFD SPI read 0x%X 0x%X\n", r[1], r[2]); + + if (r[1] == TK1_K20_SENTINEL) + memcpy(p, &r[2], 1); + else + return -EIO; + + return ret; +} + +static int apalis_tk1_k20_spi_write(void *context, const void *data, + size_t count) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + uint8_t out_data[3]; + + spi->mode = SPI_MODE_1; + + if (count != 2) { + dev_err(dev, "Apalis TK1 K20 MFD write count = %d\n", count); + return -ENOTSUPP; + } + out_data[0] = APALIS_TK1_K20_WRITE_INST; + out_data[1] = ((uint8_t *)data)[0]; + out_data[2] = ((uint8_t *)data)[1]; + dev_vdbg(dev, "Apalis TK1 K20 MFD SPI write 0x%X 0x%X\n", out_data[1], + out_data[2]); + + return spi_write(spi, out_data, 3); +} + +static struct regmap_bus regmap_apalis_tk1_k20_bus = { + .write = apalis_tk1_k20_spi_write, + .read = apalis_tk1_k20_spi_read, +}; + +void apalis_tk1_k20_lock(struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + if (!mutex_trylock(&apalis_tk1_k20->lock)) { + dev_dbg(apalis_tk1_k20->dev, "wait for %s from %ps\n", + __func__, __builtin_return_address(0)); + + mutex_lock(&apalis_tk1_k20->lock); + } + dev_dbg(apalis_tk1_k20->dev, "%s from %ps\n", + __func__, __builtin_return_address(0)); +} +EXPORT_SYMBOL(apalis_tk1_k20_lock); + +void apalis_tk1_k20_unlock(struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + dev_dbg(apalis_tk1_k20->dev, "%s from %ps\n", + __func__, __builtin_return_address(0)); + mutex_unlock(&apalis_tk1_k20->lock); +} +EXPORT_SYMBOL(apalis_tk1_k20_unlock); + +int apalis_tk1_k20_reg_read(struct apalis_tk1_k20_regmap *apalis_tk1_k20, + unsigned int offset, u32 *val) +{ + int ret; + + ret = regmap_read(apalis_tk1_k20->regmap, offset, val); + dev_vdbg(apalis_tk1_k20->dev, "[0x%02x] -> 0x%06x\n", offset, *val); + + return ret; +} +EXPORT_SYMBOL(apalis_tk1_k20_reg_read); + +int apalis_tk1_k20_reg_write(struct apalis_tk1_k20_regmap *apalis_tk1_k20, + unsigned int offset, u32 val) +{ + dev_vdbg(apalis_tk1_k20->dev, "[0x%02x] <- 0x%06x\n", offset, val); + + if (val >= BIT(24)) + return -EINVAL; + + return regmap_write(apalis_tk1_k20->regmap, offset, val); +} +EXPORT_SYMBOL(apalis_tk1_k20_reg_write); + +int apalis_tk1_k20_reg_read_bulk(struct apalis_tk1_k20_regmap *apalis_tk1_k20, + unsigned int offset, + uint8_t *val, size_t size) +{ + int ret; + + if (size > APALIS_TK1_K20_MAX_BULK) + return -EINVAL; + + ret = regmap_bulk_read(apalis_tk1_k20->regmap, offset, val, size); + dev_vdbg(apalis_tk1_k20->dev, "bulk read %d bytes from [0x%02x]\n", + size, offset); + + return ret; +} +EXPORT_SYMBOL(apalis_tk1_k20_reg_read_bulk); + +int apalis_tk1_k20_reg_write_bulk(struct apalis_tk1_k20_regmap *apalis_tk1_k20, + unsigned int offset, uint8_t *buf, size_t size) +{ + dev_vdbg(apalis_tk1_k20->dev, "bulk write %d bytes to [0x%02x]\n", + (unsigned int)size, offset); + + if (size > APALIS_TK1_K20_MAX_BULK) + return -EINVAL; + + return regmap_bulk_write(apalis_tk1_k20->regmap, offset, buf, size); +} +EXPORT_SYMBOL(apalis_tk1_k20_reg_write_bulk); + +int apalis_tk1_k20_reg_rmw(struct apalis_tk1_k20_regmap *apalis_tk1_k20, + unsigned int offset, u32 mask, u32 val) +{ + dev_vdbg(apalis_tk1_k20->dev, "[0x%02x] <- 0x%06x (mask: 0x%06x)\n", + offset, val, mask); + + return regmap_update_bits(apalis_tk1_k20->regmap, offset, mask, val); +} +EXPORT_SYMBOL(apalis_tk1_k20_reg_rmw); + +int apalis_tk1_k20_irq_mask(struct apalis_tk1_k20_regmap *apalis_tk1_k20, + int irq) +{ + int virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq); + + disable_irq_nosync(virq); + + return 0; +} +EXPORT_SYMBOL(apalis_tk1_k20_irq_mask); + +int apalis_tk1_k20_irq_unmask(struct apalis_tk1_k20_regmap *apalis_tk1_k20, + int irq) +{ + int virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq); + + enable_irq(virq); + + return 0; +} +EXPORT_SYMBOL(apalis_tk1_k20_irq_unmask); + +int apalis_tk1_k20_irq_status(struct apalis_tk1_k20_regmap *apalis_tk1_k20, + int irq, int *enabled, int *pending) +{ + int ret; + unsigned int offmask = APALIS_TK1_K20_MSQREG; + unsigned int offstat = APALIS_TK1_K20_IRQREG; + u32 irqbit = 1 << irq; + + if (irq < 0 || irq >= ARRAY_SIZE(apalis_tk1_k20->irqs)) + return -EINVAL; + + if (enabled) { + u32 mask; + + ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, offmask, &mask); + if (ret) + return ret; + + *enabled = mask & irqbit; + } + + if (pending) { + u32 stat; + + ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, offstat, &stat); + if (ret) + return ret; + + *pending = stat & irqbit; + } + + return 0; +} +EXPORT_SYMBOL(apalis_tk1_k20_irq_status); + +int apalis_tk1_k20_irq_request(struct apalis_tk1_k20_regmap *apalis_tk1_k20, + int irq, irq_handler_t handler, const char *name, void *dev) +{ + int virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq); + + return devm_request_threaded_irq(apalis_tk1_k20->dev, virq, NULL, + handler, IRQF_ONESHOT, name, dev); +} +EXPORT_SYMBOL(apalis_tk1_k20_irq_request); + +int apalis_tk1_k20_irq_free(struct apalis_tk1_k20_regmap *apalis_tk1_k20, + int irq, void *dev) +{ + int virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq); + + devm_free_irq(apalis_tk1_k20->dev, virq, dev); + + return 0; +} +EXPORT_SYMBOL(apalis_tk1_k20_irq_free); + +static int apalis_tk1_k20_add_subdevice_pdata( + struct apalis_tk1_k20_regmap *apalis_tk1_k20, const char *name, + void *pdata, size_t pdata_size) +{ + struct mfd_cell cell = { + .platform_data = pdata, + .pdata_size = pdata_size, + }; + + cell.name = kmemdup(name, strlen(name) + 1, GFP_KERNEL); + if (!cell.name) + return -ENOMEM; + + return mfd_add_devices(apalis_tk1_k20->dev, -1, &cell, 1, NULL, 0, + regmap_irq_get_domain(apalis_tk1_k20->irq_data)); +} + +static int apalis_tk1_k20_add_subdevice( + struct apalis_tk1_k20_regmap *apalis_tk1_k20, const char *name) +{ + return apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20, name, NULL, + 0); +} + +static void apalis_tk1_k20_reset_chip( + struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + udelay(10); + gpio_set_value(apalis_tk1_k20->reset_gpio, 0); + udelay(10); + gpio_set_value(apalis_tk1_k20->reset_gpio, 1); + udelay(10); +} + +#ifdef CONFIG_APALIS_TK1_K20_EZP +static int apalis_tk1_k20_read_ezport( + struct apalis_tk1_k20_regmap *apalis_tk1_k20, uint8_t command, + int addr, int count, uint8_t *buffer) +{ + uint8_t w[4 + APALIS_TK1_K20_EZP_MAX_DATA]; + uint8_t r[4 + APALIS_TK1_K20_EZP_MAX_DATA]; + uint8_t *out; + struct spi_message m; + struct spi_device *spi = to_spi_device(apalis_tk1_k20->dev); + struct spi_transfer t = { + .tx_buf = w, + .rx_buf = r, + .speed_hz = APALIS_TK1_K20_EZP_MAX_SPEED, + }; + int ret; + + spi->mode = SPI_MODE_0; + if (count > APALIS_TK1_K20_EZP_MAX_DATA) + return -ENOSPC; + + memset(w, 0, 4 + count); + + switch (command) { + case APALIS_TK1_K20_EZP_READ: + case APALIS_TK1_K20_EZP_FREAD: + t.len = 4 + count; + w[1] = (addr & 0xFF0000) >> 16; + w[2] = (addr & 0xFF00) >> 8; + w[3] = (addr & 0xFC); + out = &r[4]; + break; + case APALIS_TK1_K20_EZP_RDSR: + case APALIS_TK1_K20_EZP_FRDFCOOB: + t.len = 1 + count; + out = &r[1]; + break; + default: + return -EINVAL; + } + w[0] = command; + + gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 0); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 1); + if (ret != 0) + return ret; + + memcpy(buffer, out, count); + + return 0; +} + +static int apalis_tk1_k20_write_ezport( + struct apalis_tk1_k20_regmap *apalis_tk1_k20, uint8_t command, + int addr, int count, const uint8_t *buffer) +{ + uint8_t w[4 + APALIS_TK1_K20_EZP_MAX_DATA]; + uint8_t r[4 + APALIS_TK1_K20_EZP_MAX_DATA]; + struct spi_message m; + struct spi_device *spi = to_spi_device(apalis_tk1_k20->dev); + struct spi_transfer t = { + .tx_buf = w, + .rx_buf = r, + .speed_hz = APALIS_TK1_K20_EZP_MAX_SPEED, + }; + int ret; + + spi->mode = SPI_MODE_0; + if (count > APALIS_TK1_K20_EZP_MAX_DATA) + return -ENOSPC; + + switch (command) { + case APALIS_TK1_K20_EZP_SP: + case APALIS_TK1_K20_EZP_SE: + t.len = 4 + count; + w[1] = (addr & 0xFF0000) >> 16; + w[2] = (addr & 0xFF00) >> 8; + w[3] = (addr & 0xF8); + memcpy(&w[4], buffer, count); + break; + case APALIS_TK1_K20_EZP_WREN: + case APALIS_TK1_K20_EZP_WRDI: + case APALIS_TK1_K20_EZP_BE: + case APALIS_TK1_K20_EZP_RESET: + case APALIS_TK1_K20_EZP_WRFCCOB: + t.len = 1 + count; + memcpy(&w[1], buffer, count); + break; + default: + return -EINVAL; + } + w[0] = command; + + gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 0); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 1); + + return ret; +} + +static int apalis_tk1_k20_set_wren_ezport( + struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + uint8_t buffer; + + if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_WREN, + 0, 0, NULL) < 0) + return -EIO; + if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR, + 0, 1, &buffer) < 0) + return -EIO; + if ((buffer & APALIS_TK1_K20_EZP_STA_WEN)) + return 0; + + /* If it failed try one last time */ + if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_WREN, + 0, 0, NULL) < 0) + return -EIO; + if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR, + 0, 1, &buffer) < 0) + return -EIO; + if ((buffer & APALIS_TK1_K20_EZP_STA_WEN)) + return 0; + + return -EIO; + +} + +static int apalis_tk1_k20_enter_ezport( + struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + uint8_t status = 0x00; + uint8_t buffer; + gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 0); + apalis_tk1_k20_reset_chip(apalis_tk1_k20); + msleep(30); + gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 1); + if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR, + 0, 1, &buffer) < 0) + goto bad; + status = buffer; + if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_WREN, + 0, 0, NULL) < 0) + goto bad; + if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR, + 0, 1, &buffer) < 0) + goto bad; + if ((buffer & APALIS_TK1_K20_EZP_STA_WEN) && buffer != status) + return 0; + +bad: + dev_err(apalis_tk1_k20->dev, "Error entering EZ Port mode.\n"); + return -EIO; +} + +static int apalis_tk1_k20_erase_chip_ezport( + struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + uint8_t buffer[16]; + int i; + if (apalis_tk1_k20_set_wren_ezport(apalis_tk1_k20)) + goto bad; + if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_BE, + 0, 0, NULL) < 0) + goto bad; + + if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR, + 0, 1, buffer) < 0) + goto bad; + + i = 0; + while (buffer[0] & APALIS_TK1_K20_EZP_STA_WIP) { + msleep(20); + if ((apalis_tk1_k20_read_ezport(apalis_tk1_k20, + APALIS_TK1_K20_EZP_RDSR, 0, 1, buffer) < 0) || (i > 50)) + goto bad; + i++; + } + + return 0; + +bad: + dev_err(apalis_tk1_k20->dev, "Error erasing the chip.\n"); + return -EIO; +} + +static int apalis_tk1_k20_flash_chip_ezport( + struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + uint8_t buffer; + const uint8_t *fw_chunk; + int i, j, transfer_size; + + for (i = 0; i < fw_entry->size;) { + if (apalis_tk1_k20_set_wren_ezport(apalis_tk1_k20)) + goto bad; + + fw_chunk = fw_entry->data + i; + transfer_size = (i + APALIS_TK1_K20_EZP_WRITE_SIZE < + fw_entry->size) ? APALIS_TK1_K20_EZP_WRITE_SIZE + : (fw_entry->size - i - 1); + dev_vdbg(apalis_tk1_k20->dev, + "Apalis TK1 K20 MFD transfer_size = %d addr = 0x%X\n", + transfer_size, i); + if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, + APALIS_TK1_K20_EZP_SP, i, + transfer_size, fw_chunk) < 0) + goto bad; + + if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, + APALIS_TK1_K20_EZP_RDSR, 0, 1, &buffer) < 0) + goto bad; + + j = 0; + while (buffer & APALIS_TK1_K20_EZP_STA_WIP) { + udelay(100); + if ((apalis_tk1_k20_read_ezport(apalis_tk1_k20, + APALIS_TK1_K20_EZP_RDSR, 0, 1, + &buffer) < 0) || (j > 1000)) + goto bad; + j++; + } + i += APALIS_TK1_K20_EZP_WRITE_SIZE; + } + + return 0; + +bad: + dev_err(apalis_tk1_k20->dev, "Error writing to the chip.\n"); + return -EIO; +} + +static uint8_t apalis_tk1_k20_fw_ezport_status(void) +{ + return fw_entry->data[APALIS_TK1_K20_FW_FOPT_ADDR] & + APALIS_TK1_K20_FOPT_EZP_ENA; +} + +static uint8_t apalis_tk1_k20_get_fw_revision(void) +{ + if (fw_entry->size > APALIS_TK1_K20_FW_VER_ADDR) + return fw_entry->data[APALIS_TK1_K20_FW_VER_ADDR]; + return 0; +} +#endif /* CONFIG_APALIS_TK1_K20_EZP */ + + +#ifdef CONFIG_OF +static int apalis_tk1_k20_probe_flags_dt( + struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + struct device_node *np = apalis_tk1_k20->dev->of_node; + + if (!np) + return -ENODEV; + + if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-adc")) + apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_ADC; + + if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-tsc")) + apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_TSC; + + if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-can")) + apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_CAN; + + if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-gpio")) + apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_GPIO; + + return 0; +} + +static int apalis_tk1_k20_probe_gpios_dt( + struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + struct device_node *np = apalis_tk1_k20->dev->of_node; + + if (!np) + return -ENODEV; + + apalis_tk1_k20->reset_gpio = of_get_named_gpio(np, "rst-gpio", 0); + if (apalis_tk1_k20->reset_gpio < 0) + return apalis_tk1_k20->reset_gpio; + gpio_request(apalis_tk1_k20->reset_gpio, "apalis-tk1-k20-reset"); + gpio_direction_output(apalis_tk1_k20->reset_gpio, 1); + + apalis_tk1_k20->ezpcs_gpio = of_get_named_gpio(np, "ezport-cs-gpio", 0); + if (apalis_tk1_k20->ezpcs_gpio < 0) + return apalis_tk1_k20->ezpcs_gpio; + gpio_request(apalis_tk1_k20->ezpcs_gpio, "apalis-tk1-k20-ezpcs"); + gpio_direction_output(apalis_tk1_k20->ezpcs_gpio, 1); + + apalis_tk1_k20->appcs_gpio = of_get_named_gpio(np, "spi-cs-gpio", 0); + if (apalis_tk1_k20->appcs_gpio < 0) + return apalis_tk1_k20->appcs_gpio; + gpio_request(apalis_tk1_k20->appcs_gpio, "apalis-tk1-k20-appcs"); + gpio_direction_output(apalis_tk1_k20->appcs_gpio, 1); + + apalis_tk1_k20->int2_gpio = of_get_named_gpio(np, "int2-gpio", 0); + if (apalis_tk1_k20->int2_gpio < 0) + return apalis_tk1_k20->int2_gpio; + gpio_request(apalis_tk1_k20->int2_gpio, "apalis-tk1-k20-int2"); + gpio_direction_output(apalis_tk1_k20->int2_gpio, 1); + + return 0; +} +#else +static inline int apalis_tk1_k20_probe_flags_dt( + struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + return -ENODEV; +} +static inline int apalis_tk1_k20_probe_gpios_dt( + struct apalis_tk1_k20_regmap *apalis_tk1_k20) +{ + return -ENODEV; +} +#endif + +int apalis_tk1_k20_dev_init(struct device *dev) +{ + struct apalis_tk1_k20_platform_data *pdata = dev_get_platdata(dev); + struct apalis_tk1_k20_regmap *apalis_tk1_k20 = dev_get_drvdata(dev); + uint32_t revision = 0x00; + int ret, i; + int erase_only = 0; + + apalis_tk1_k20->dev = dev; + + ret = apalis_tk1_k20_probe_gpios_dt(apalis_tk1_k20); + if ((ret < 0) && pdata) { + if (pdata) { + apalis_tk1_k20->ezpcs_gpio = pdata->ezpcs_gpio; + apalis_tk1_k20->reset_gpio = pdata->reset_gpio; + apalis_tk1_k20->appcs_gpio = pdata->appcs_gpio; + apalis_tk1_k20->int2_gpio = pdata->int2_gpio; + } else { + dev_err(dev, "Error claiming GPIOs\n"); + ret = -EINVAL; + goto bad; + } + } + + apalis_tk1_k20_reset_chip(apalis_tk1_k20); + msleep(100); + gpio_set_value(apalis_tk1_k20->appcs_gpio, 0); + ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, APALIS_TK1_K20_REVREG, + &revision); + +#ifdef CONFIG_APALIS_TK1_K20_EZP + if ((request_firmware(&fw_entry, "apalis-tk1-k20.bin", dev) < 0) + && (revision != APALIS_TK1_K20_FW_VER)) { + dev_err(apalis_tk1_k20->dev, + "Unsupported firmware version %d.%d and no local" + " firmware file available.\n", + (revision & 0xF0 >> 8), + (revision & 0x0F)); + ret = -ENOTSUPP; + goto bad; + } + + if (fw_entry->size == 1) + erase_only = 1; + + if ((apalis_tk1_k20_get_fw_revision() != APALIS_TK1_K20_FW_VER) && + (revision != APALIS_TK1_K20_FW_VER) && !erase_only) { + dev_err(apalis_tk1_k20->dev, + "Unsupported firmware version in both the device as well" + " as the local firmware file.\n"); + release_firmware(fw_entry); + ret = -ENOTSUPP; + goto bad; + } + + if ((revision != APALIS_TK1_K20_FW_VER) && !erase_only && + (!apalis_tk1_k20_fw_ezport_status())) { + dev_err(apalis_tk1_k20->dev, + "Unsupported firmware version in the device and the " + "local firmware file disables the EZ Port.\n"); + release_firmware(fw_entry); + ret = -ENOTSUPP; + goto bad; + } + + if ((revision != APALIS_TK1_K20_FW_VER) || erase_only) { + if (apalis_tk1_k20_enter_ezport(apalis_tk1_k20) < 0) { + dev_err(apalis_tk1_k20->dev, + "Problem entering EZ port mode.\n"); + release_firmware(fw_entry); + ret = -EIO; + goto bad; + } + if (apalis_tk1_k20_erase_chip_ezport(apalis_tk1_k20) < 0) { + dev_err(apalis_tk1_k20->dev, + "Problem erasing the chip.\n"); + release_firmware(fw_entry); + ret = -EIO; + goto bad; + } + if (erase_only) { + dev_err(apalis_tk1_k20->dev, + "Chip fully erased.\n"); + release_firmware(fw_entry); + ret = -EIO; + goto bad; + } + if (apalis_tk1_k20_flash_chip_ezport(apalis_tk1_k20) < 0) { + dev_err(apalis_tk1_k20->dev, + "Problem flashing new firmware.\n"); + release_firmware(fw_entry); + ret = -EIO; + goto bad; + } + } + release_firmware(fw_entry); + + gpio_set_value(apalis_tk1_k20->appcs_gpio, 1); + apalis_tk1_k20_reset_chip(apalis_tk1_k20); + msleep(100); + gpio_set_value(apalis_tk1_k20->appcs_gpio, 0); + + ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, APALIS_TK1_K20_REVREG, + &revision); +#endif /* CONFIG_APALIS_TK1_K20_EZP */ + + if (ret) { + dev_err(apalis_tk1_k20->dev, "Device is not answering.\n"); + goto bad; + } + + if (revision != APALIS_TK1_K20_FW_VER) { + dev_err(apalis_tk1_k20->dev, + "Unsupported firmware version %d.%d.\n", + ((revision & 0xF0) >> 8), (revision & 0x0F)); + ret = -ENOTSUPP; + goto bad; + } + + for (i = 0; i < ARRAY_SIZE(apalis_tk1_k20->irqs); i++) { + apalis_tk1_k20->irqs[i].reg_offset = i / + APALIS_TK1_K20_IRQ_PER_REG; + apalis_tk1_k20->irqs[i].mask = BIT(i % + APALIS_TK1_K20_IRQ_PER_REG); + } + + apalis_tk1_k20->irq_chip.name = dev_name(dev); + apalis_tk1_k20->irq_chip.status_base = APALIS_TK1_K20_IRQREG; + apalis_tk1_k20->irq_chip.mask_base = APALIS_TK1_K20_MSQREG; + apalis_tk1_k20->irq_chip.ack_base = APALIS_TK1_K20_IRQREG; + apalis_tk1_k20->irq_chip.irq_reg_stride = 0; + apalis_tk1_k20->irq_chip.num_regs = APALIS_TK1_K20_IRQ_REG_CNT; + apalis_tk1_k20->irq_chip.irqs = apalis_tk1_k20->irqs; + apalis_tk1_k20->irq_chip.num_irqs = ARRAY_SIZE(apalis_tk1_k20->irqs); + + ret = regmap_add_irq_chip(apalis_tk1_k20->regmap, apalis_tk1_k20->irq, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING, 0, &apalis_tk1_k20->irq_chip, + &apalis_tk1_k20->irq_data); + if (ret) + goto bad; + + mutex_init(&apalis_tk1_k20->lock); + + if (apalis_tk1_k20_probe_flags_dt(apalis_tk1_k20) < 0 && pdata) + apalis_tk1_k20->flags = pdata->flags; + + if (pdata) { + if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_TSC) + apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20, + "apalis-tk1-k20-ts", + &pdata->touch, sizeof(pdata->touch)); + + if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_ADC) + apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20, + "apalis-tk1-k20-adc", + &pdata->adc, sizeof(pdata->adc)); + + if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_CAN) { + pdata->can0.id = 0; + pdata->can1.id = 1; + apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20, + "apalis-tk1-k20-can", + &pdata->can0, sizeof(pdata->can0)); + apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20, + "apalis-tk1-k20-can", + &pdata->can1, sizeof(pdata->can1)); + } + if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_GPIO) + apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20, + "apalis-tk1-k20-gpio", + &pdata->gpio, sizeof(pdata->gpio)); + } else { + if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_TSC) + apalis_tk1_k20_add_subdevice(apalis_tk1_k20, + "apalis-tk1-k20-ts"); + + if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_ADC) + apalis_tk1_k20_add_subdevice(apalis_tk1_k20, + "apalis-tk1-k20-adc"); + + if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_CAN) { + apalis_tk1_k20_add_subdevice(apalis_tk1_k20, + "apalis-tk1-k20-can"); + } + + if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_GPIO) + apalis_tk1_k20_add_subdevice(apalis_tk1_k20, + "apalis-tk1-k20-gpio"); + } + + dev_info(apalis_tk1_k20->dev, "Apalis TK1 K20 MFD driver.\n" + "Firmware version %d.%d.\n", FW_MAJOR, FW_MINOR); + + return 0; + +bad: + if (apalis_tk1_k20->ezpcs_gpio >= 0) + gpio_free(apalis_tk1_k20->ezpcs_gpio); + if (apalis_tk1_k20->reset_gpio >= 0) + gpio_free(apalis_tk1_k20->reset_gpio); + if (apalis_tk1_k20->appcs_gpio >= 0) + gpio_free(apalis_tk1_k20->appcs_gpio); + if (apalis_tk1_k20->int2_gpio >= 0) + gpio_free(apalis_tk1_k20->int2_gpio); + + return ret; +} + + +static int apalis_tk1_k20_spi_probe(struct spi_device *spi) +{ + struct apalis_tk1_k20_regmap *apalis_tk1_k20; + int ret; + + apalis_tk1_k20 = devm_kzalloc(&spi->dev, sizeof(*apalis_tk1_k20), + GFP_KERNEL); + if (!apalis_tk1_k20) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, apalis_tk1_k20); + + spi->mode = SPI_MODE_1; + + apalis_tk1_k20->irq = spi->irq; + + spi->max_speed_hz = (spi->max_speed_hz > APALIS_TK1_K20_MAX_SPI_SPEED) ? + APALIS_TK1_K20_MAX_SPI_SPEED : spi->max_speed_hz; + + ret = spi_setup(spi); + if (ret) + return ret; + + apalis_tk1_k20->regmap = devm_regmap_init(&spi->dev, + ®map_apalis_tk1_k20_bus, &spi->dev, + &apalis_tk1_k20_regmap_spi_config); + if (IS_ERR(apalis_tk1_k20->regmap)) { + ret = PTR_ERR(apalis_tk1_k20->regmap); + dev_err(&spi->dev, "Failed to initialize regmap: %d\n", ret); + return ret; + } + + return apalis_tk1_k20_dev_init(&spi->dev); +} + +static int apalis_tk1_k20_spi_remove(struct spi_device *spi) +{ + struct apalis_tk1_k20_regmap *apalis_tk1_k20 = + dev_get_drvdata(&spi->dev); + + if (apalis_tk1_k20->ezpcs_gpio >= 0) + gpio_free(apalis_tk1_k20->ezpcs_gpio); + if (apalis_tk1_k20->reset_gpio >= 0) + gpio_free(apalis_tk1_k20->reset_gpio); + if (apalis_tk1_k20->appcs_gpio >= 0) + gpio_free(apalis_tk1_k20->appcs_gpio); + if (apalis_tk1_k20->int2_gpio >= 0) + gpio_free(apalis_tk1_k20->int2_gpio); + + mfd_remove_devices(&spi->dev); + regmap_del_irq_chip(apalis_tk1_k20->irq, apalis_tk1_k20->irq_data); + mutex_destroy(&apalis_tk1_k20->lock); + + return 0; +} + +static struct spi_driver apalis_tk1_k20_spi_driver = { + .id_table = apalis_tk1_k20_device_ids, + .driver = { + .name = "apalis-tk1-k20", + .of_match_table = apalis_tk1_k20_dt_ids, + }, + .probe = apalis_tk1_k20_spi_probe, + .remove = apalis_tk1_k20_spi_remove, +}; + +static int __init apalis_tk1_k20_init(void) +{ + return spi_register_driver(&apalis_tk1_k20_spi_driver); +} +subsys_initcall(apalis_tk1_k20_init); + +static void __exit apalis_tk1_k20_exit(void) +{ + spi_unregister_driver(&apalis_tk1_k20_spi_driver); +} +module_exit(apalis_tk1_k20_exit); + +MODULE_DESCRIPTION("MFD driver for Kinetis MK20DN512 MCU on Apalis TK1"); +MODULE_AUTHOR("Dominik Sliwa "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3