From d52931aac9516724d3067516ca1c4ef762f0daea Mon Sep 17 00:00:00 2001 From: Dominik Sliwa Date: Thu, 1 Dec 2016 14:18:47 +0100 Subject: apalis-tk1-k20: can and spi improvements This patch includes CAN driver and improvements in SPI communications for Apalis TK1 k20 based MFD. Requires firmware version 0.9. Signed-off-by: Dominik Sliwa Acked-by: Marcel Ziswiler --- arch/arm/boot/dts/tegra124-apalis-eval.dts | 17 +- arch/arm/configs/apalis-tk1_defconfig | 1 + drivers/input/touchscreen/apalis-tk1-k20_ts.c | 6 +- drivers/mfd/apalis-tk1-k20-ezp.h | 2 +- drivers/mfd/apalis-tk1-k20.c | 255 ++++++--- drivers/net/can/Kconfig | 6 + drivers/net/can/Makefile | 1 + drivers/net/can/apalis-tk1-k20-can.c | 792 ++++++++++++++++++++++++++ include/linux/mfd/apalis-tk1-k20.h | 90 +-- 9 files changed, 1061 insertions(+), 109 deletions(-) create mode 100644 drivers/net/can/apalis-tk1-k20-can.c diff --git a/arch/arm/boot/dts/tegra124-apalis-eval.dts b/arch/arm/boot/dts/tegra124-apalis-eval.dts index 9e32f873f949..a3f3f019a059 100644 --- a/arch/arm/boot/dts/tegra124-apalis-eval.dts +++ b/arch/arm/boot/dts/tegra124-apalis-eval.dts @@ -134,28 +134,33 @@ /* SPI2: MCU SPI */ spi@7000d600 { status = "okay"; - spi-max-frequency = <25000000>; + spi-max-frequency = <6000000>; k20mcu: apalis-tk1-k20@1 { compatible = "toradex,apalis-tk1-k20"; reg = <1>; - spi-max-frequency = <10000000>; + spi-max-frequency = <6000000>; interrupt-parent =<&gpio>; interrupts = ; rst-gpio = <&gpio TEGRA_GPIO(BB, 6) GPIO_ACTIVE_HIGH>; /* GPIO based CS used to enter K20 EzPort mode */ ezport-cs-gpio = <&gpio TEGRA_GPIO(W, 2) GPIO_ACTIVE_HIGH>; - /* SPI CS under GPIO controll due to K20 quirks */ - spi-cs-gpio = <&gpio TEGRA_GPIO(X, 6) GPIO_ACTIVE_HIGH>; /* extra INT lines between K20 and TK1 */ int2-gpio = <&gpio TEGRA_GPIO(J, 2) GPIO_ACTIVE_HIGH>; int3-gpio = <&gpio TEGRA_GPIO(I, 5) GPIO_ACTIVE_HIGH>; int4-gpio = <&gpio TEGRA_GPIO(J, 0) GPIO_ACTIVE_HIGH>; - toradex,apalis-tk1-k20-uses-gpio; toradex,apalis-tk1-k20-uses-adc; + toradex,apalis-tk1-k20-uses-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 = <12>; + nvidia,cs-hold-clk-count = <12>; + }; }; /* spidev on K20 bus, can be used with custom firmware for userspace @@ -163,7 +168,7 @@ spidev2: spidev@2 { compatible = "spidev"; reg = <2>; - spi-max-frequency = <3500000>; + spi-max-frequency = <2000000>; }; }; diff --git a/arch/arm/configs/apalis-tk1_defconfig b/arch/arm/configs/apalis-tk1_defconfig index bf53b49ea7fe..2a6c23a37d52 100644 --- a/arch/arm/configs/apalis-tk1_defconfig +++ b/arch/arm/configs/apalis-tk1_defconfig @@ -147,6 +147,7 @@ CONFIG_NET_ACT_GACT=y CONFIG_NET_ACT_MIRRED=y CONFIG_CAN=y CONFIG_CAN_MCP251X=y +CONFIG_CAN_APALIS_TK1_K20=m CONFIG_BT=m CONFIG_BT_RFCOMM=m CONFIG_BT_BNEP=m diff --git a/drivers/input/touchscreen/apalis-tk1-k20_ts.c b/drivers/input/touchscreen/apalis-tk1-k20_ts.c index 43cb25fa8fc9..38e4a3003c8d 100644 --- a/drivers/input/touchscreen/apalis-tk1-k20_ts.c +++ b/drivers/input/touchscreen/apalis-tk1-k20_ts.c @@ -37,7 +37,7 @@ static irqreturn_t apalis_tk1_k20_ts_handler(int irq, void *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. + * delay is HZ / 25 which is acceptable. */ queue_delayed_work(priv->workq, &priv->work, 0); @@ -167,8 +167,8 @@ static int __init apalis_tk1_k20_ts_probe(struct platform_device *pdev) 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_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; diff --git a/drivers/mfd/apalis-tk1-k20-ezp.h b/drivers/mfd/apalis-tk1-k20-ezp.h index 4a7262c85d8f..b2c65e4b60fd 100644 --- a/drivers/mfd/apalis-tk1-k20-ezp.h +++ b/drivers/mfd/apalis-tk1-k20-ezp.h @@ -36,7 +36,7 @@ #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_SPEED 2000000 #define APALIS_TK1_K20_EZP_MAX_DATA 32 #define APALIS_TK1_K20_EZP_WRITE_SIZE 32 diff --git a/drivers/mfd/apalis-tk1-k20.c b/drivers/mfd/apalis-tk1-k20.c index ef8d64a64b71..8d3227a18912 100644 --- a/drivers/mfd/apalis-tk1-k20.c +++ b/drivers/mfd/apalis-tk1-k20.c @@ -53,16 +53,20 @@ static const struct regmap_config apalis_tk1_k20_regmap_spi_config = { .max_register = APALIS_TK1_K20_NUMREGS, .cache_type = REGCACHE_NONE, - +#ifdef CONFIG_EXPERIMENTAL_K20_HSMODE + .use_single_rw = 0, +#else .use_single_rw = 1, +#endif + }; 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 w[APALIS_TK1_K20_MAX_BULK] = {APALIS_TK1_K20_READ_INST, + val_size, *((unsigned char *) reg)}; + unsigned char r[APALIS_TK1_K20_MAX_BULK]; unsigned char *p = val; struct device *dev = context; struct spi_device *spi = to_spi_device(dev); @@ -70,29 +74,83 @@ static int apalis_tk1_k20_spi_read(void *context, const void *reg, .tx_buf = w, .rx_buf = r, .len = 3, + .cs_change = 0, + .delay_usecs = 0, }; +#ifdef CONFIG_EXPERIMENTAL_K20_HSMODE + struct spi_transfer ts[APALIS_TK1_K20_MAX_BULK / APALIS_TK1_K20_MAX_MSG]; + int i = 0; +#endif 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); - spi_message_init(&m); - t.len = 3; - spi_message_add_tail(&t, &m); - ret = spi_sync(spi, &m); + if (reg_size != 1) + return -ENOTSUPP; - dev_vdbg(dev, "Apalis TK1 K20 MFD SPI read reg 0x%X: 0x%X 0x%X\n", - *((unsigned char *) reg), r[1], r[2]); + if (val_size == 1) { + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + /* no need to reinit the message*/ + t.len = 2; + /* just use the same transfer */ + ret = spi_sync(spi, &m); +#ifdef CONFIG_EXPERIMENTAL_K20_HSMODE + } else if ((val_size > 1) && (val_size < APALIS_TK1_K20_MAX_BULK)) { + spi_message_init(&m); + w[0] = APALIS_TK1_K20_BULK_READ_INST; + t.len = 3; + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + + if (val_size == 2) { + ret = spi_sync(spi, &m); + } else { + spi_message_init(&m); + for (i = 0; (4 * i) < (val_size - 2); i++) { + ts[i].tx_buf = NULL; + ts[i].rx_buf = &r[2 + (4 * i)]; + ts[i].len = (val_size - 2 - (4 * i) >= 4) ? + 4 : (val_size - 2 - (4 * i)); + ts[i].cs_change = 0; + ts[i].delay_usecs = 2; + + spi_message_add_tail(&ts[i], &m); + } + ret = spi_sync(spi, &m); + } +#endif + } else { + return -ENOTSUPP; + } - if (r[1] == TK1_K20_SENTINEL) - memcpy(p, &r[2], 1); - else + if (r[0] == TK1_K20_SENTINEL) + memcpy(p, &r[1], val_size); + else { + if (r[0] != TK1_K20_INVAL) { + dev_err(dev, "K20 FIFO Bug!"); + /* we've probably hit the K20 FIFO bug, try to resync */ + spi_message_init(&m); + w[0] = APALIS_TK1_K20_READ_INST; + w[1] = 0x01; + w[2] = APALIS_TK1_K20_RET_REQ; + w[3] = 0x00; + t.tx_buf = w; + t.len = 4; + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + spi_message_init(&m); + t.len = val_size + 1; + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + if (r[0] == TK1_K20_SENTINEL) { + memcpy(p, &r[1], val_size); + return ret; + } + } return -EIO; + } return ret; } @@ -102,21 +160,63 @@ static int apalis_tk1_k20_spi_write(void *context, const void *data, { struct device *dev = context; struct spi_device *spi = to_spi_device(dev); - uint8_t out_data[3]; + uint8_t out_data[APALIS_TK1_K20_MAX_BULK]; + int ret; +#ifdef CONFIG_EXPERIMENTAL_K20_HSMODE + struct spi_device *spi = to_spi_device(dev); + struct spi_transfer t = { + .tx_buf = out_data, + .rx_buf = NULL, + .cs_change = 0, + .delay_usecs = 2, + }; + struct spi_transfer ts[APALIS_TK1_K20_MAX_BULK / APALIS_TK1_K20_MAX_MSG]; + int i = 0; +#endif spi->mode = SPI_MODE_1; - if (count != 2) { - dev_err(dev, "Apalis TK1 K20 MFD write count = %d\n", count); + 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); +#ifdef CONFIG_EXPERIMENTAL_K20_HSMODE + } else if (count == 2) { + out_data[0] = APALIS_TK1_K20_BULK_WRITE_INST; + out_data[1] = count - 1; + memcpy(&out_data[2], data, count); + ret = spi_write(spi, out_data, 4); + } else if ( (count > 2 ) && (count < APALIS_TK1_K20_MAX_BULK)) { + + spi_message_init(&m); + out_data[0] = APALIS_TK1_K20_BULK_WRITE_INST; + out_data[1] = count -1; + memcpy(&out_data[2], data, count); + t.tx_buf = out_data; + t.len = 4; + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + + spi_message_init(&m); + for (i = 1; (4 * i) < (count - 2); i++) { + ts[i].tx_buf = &out_data[i * 4]; + ts[i].len = (count - 2 - (4 * i) >= 4) ? + 4 : (count - 2 - (4 * i)); + ts[i].cs_change = 0; + ts[i].delay_usecs = 2; + + spi_message_add_tail(&ts[i], &m); + } + ret = spi_sync(spi, &m); + } +#endif + } else { + dev_err(dev, "Apalis TK1 K20 MFD Invalid 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); + return ret; } static struct regmap_bus regmap_apalis_tk1_k20_bus = { @@ -287,9 +387,10 @@ int apalis_tk1_k20_irq_free(struct apalis_tk1_k20_regmap *apalis_tk1_k20, } EXPORT_SYMBOL(apalis_tk1_k20_irq_free); -static int apalis_tk1_k20_add_subdevice_pdata( + +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) + void *pdata, size_t pdata_size, int id) { struct mfd_cell cell = { .platform_data = pdata, @@ -300,10 +401,18 @@ static int apalis_tk1_k20_add_subdevice_pdata( if (!cell.name) return -ENOMEM; - return mfd_add_devices(apalis_tk1_k20->dev, -1, &cell, 1, NULL, 0, + 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) { @@ -316,7 +425,7 @@ static void apalis_tk1_k20_reset_chip( { udelay(10); gpio_set_value(apalis_tk1_k20->reset_gpio, 0); - udelay(10); + msleep(10); gpio_set_value(apalis_tk1_k20->reset_gpio, 1); udelay(10); } @@ -339,6 +448,7 @@ static int apalis_tk1_k20_read_ezport( int ret; spi->mode = SPI_MODE_0; + if (count > APALIS_TK1_K20_EZP_MAX_DATA) return -ENOSPC; @@ -392,6 +502,7 @@ static int apalis_tk1_k20_write_ezport( int ret; spi->mode = SPI_MODE_0; + if (count > APALIS_TK1_K20_EZP_MAX_DATA) return -ENOSPC; @@ -460,8 +571,9 @@ static int apalis_tk1_k20_enter_ezport( 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(30); + 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) @@ -534,17 +646,17 @@ static int apalis_tk1_k20_flash_chip_ezport( 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) { - udelay(100); + msleep(10); if ((apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR, 0, 1, - &buffer) < 0) || (j > 1000)) + &buffer) < 0) || (j > 10000)) goto bad; j++; } @@ -617,12 +729,6 @@ static int apalis_tk1_k20_probe_gpios_dt( 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; @@ -659,7 +765,6 @@ int apalis_tk1_k20_dev_init(struct device *dev) 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"); @@ -667,10 +772,8 @@ int apalis_tk1_k20_dev_init(struct device *dev) goto bad; } } - apalis_tk1_k20_reset_chip(apalis_tk1_k20); - msleep(100); - gpio_set_value(apalis_tk1_k20->appcs_gpio, 0); + msleep(10); ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, APALIS_TK1_K20_REVREG, &revision); @@ -686,11 +789,24 @@ int apalis_tk1_k20_dev_init(struct device *dev) goto bad; } - if (fw_entry->size == 1) - erase_only = 1; + 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) { + (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"); @@ -700,7 +816,8 @@ int apalis_tk1_k20_dev_init(struct device *dev) } if ((revision != APALIS_TK1_K20_FW_VER) && !erase_only && - (!apalis_tk1_k20_fw_ezport_status())) { + (!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"); @@ -709,8 +826,14 @@ int apalis_tk1_k20_dev_init(struct device *dev) goto bad; } - if ((revision != APALIS_TK1_K20_FW_VER) || erase_only) { - if (apalis_tk1_k20_enter_ezport(apalis_tk1_k20) < 0) { + 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); @@ -739,12 +862,12 @@ int apalis_tk1_k20_dev_init(struct device *dev) goto bad; } } - release_firmware(fw_entry); + if (fw_entry != NULL) + release_firmware(fw_entry); - gpio_set_value(apalis_tk1_k20->appcs_gpio, 1); + msleep(10); apalis_tk1_k20_reset_chip(apalis_tk1_k20); - msleep(100); - gpio_set_value(apalis_tk1_k20->appcs_gpio, 0); + msleep(10); ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, APALIS_TK1_K20_REVREG, &revision); @@ -758,7 +881,7 @@ int apalis_tk1_k20_dev_init(struct device *dev) if (revision != APALIS_TK1_K20_FW_VER) { dev_err(apalis_tk1_k20->dev, "Unsupported firmware version %d.%d.\n", - ((revision & 0xF0) >> 8), (revision & 0x0F)); + ((revision & 0xF0) >> 4), (revision & 0x0F)); ret = -ENOTSUPP; goto bad; } @@ -803,6 +926,7 @@ int apalis_tk1_k20_dev_init(struct device *dev) &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, @@ -826,8 +950,13 @@ int apalis_tk1_k20_dev_init(struct device *dev) "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"); + /* 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) @@ -845,8 +974,6 @@ bad: 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); @@ -870,7 +997,7 @@ static int apalis_tk1_k20_spi_probe(struct spi_device *spi) apalis_tk1_k20->irq = spi->irq; - spi->max_speed_hz = (spi->max_speed_hz > APALIS_TK1_K20_MAX_SPI_SPEED) ? + 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); @@ -898,8 +1025,6 @@ static int apalis_tk1_k20_spi_remove(struct spi_device *spi) 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); diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index e456b70933c2..c8287d04a51b 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -83,6 +83,12 @@ config CAN_MCP251X ---help--- Driver for the Microchip MCP251x SPI CAN controllers. +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 c7440392adbb..940782b20494 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_CAN_CC770) += cc770/ obj-$(CONFIG_CAN_AT91) += at91_can.o obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o obj-$(CONFIG_CAN_MCP251X) += mcp251x.o +obj-$(CONFIG_CAN_APALIS_TK1_K20) += apalis-tk1-k20-can.o obj-$(CONFIG_CAN_BFIN) += bfin_can.o obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o obj-$(CONFIG_CAN_FLEXCAN) += flexcan.o 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..cff3dca7bf20 --- /dev/null +++ b/drivers/net/can/apalis-tk1-k20-can.c @@ -0,0 +1,792 @@ +/* + * CAN bus driver for Apalis TK1 K20 CAN Controller over MFD device + * based on MCP251x CAN driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GET_BYTE(val, byte) \ + (((val) >> ((byte) * 8)) & 0xff) +#define SET_BYTE(val, byte) \ + (((val) & 0xff) << ((byte) * 8)) + +/* 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 CAN_FRAME_MAX_BITS 128 +#define CAN_MAX_CONTINOUS_READ 8 + +#define MB_DLC_OFF 0 +#define MB_EID_OFF 1 +#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_INTMASK 0x38 +#define CANCTRL_INTEN BIT(2) +#define CANINTF_RX BIT(3) +#define CANINTF_TX BIT(4) +#define CANINTF_ERR BIT(5) + +#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, tx_buf_idx); + +} + +static void apalis_tk1_k20_can_hw_rx_frame(struct net_device *net, u8 *buf, + int buf_idx) +{ + /* TODO: Implement multiple RX buffer handling */ + struct apalis_tk1_k20_priv *priv = netdev_priv(net); + + apalis_tk1_k20_lock(priv->apalis_tk1_k20); + + apalis_tk1_k20_reg_read_bulk(priv->apalis_tk1_k20, + APALIS_TK1_K20_CAN_OUT_BUF + + APALIS_TK1_K20_CAN_DEV_OFFSET(priv->pdata->id), buf, + CAN_TRANSFER_BUF_LEN); + apalis_tk1_k20_unlock(priv->apalis_tk1_k20); +} + +static void apalis_tk1_k20_can_hw_rx(struct net_device *net, int buf_idx) +{ + struct apalis_tk1_k20_priv *priv = netdev_priv(net); + struct sk_buff *skb; + struct can_frame *frame; + u8 buf[CAN_TRANSFER_BUF_LEN]; + + skb = alloc_can_skb(priv->net, &frame); + if (!skb) { + dev_err(&net->dev, "cannot allocate RX skb\n"); + priv->net->stats.rx_dropped++; + return; + } + + apalis_tk1_k20_can_hw_rx_frame(net, buf, buf_idx); + memcpy(&frame->can_id, buf + MB_EID_OFF, MB_EID_LEN); + /* Data length */ + frame->can_dlc = get_can_dlc(buf[MB_DLC_OFF]); + memcpy(frame->data, buf + 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; + + 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_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_seg2 & 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_vdbg(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_vdbg(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); + 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; + + 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); + 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 */ + + 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); + 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; + + 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; + int max_continous_read = CAN_MAX_CONTINOUS_READ; + + mutex_lock(&priv->apalis_tk1_k20_can_lock); + while (!priv->force_quit && max_continous_read) { + enum can_state new_state; + 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; + } + + max_continous_read--; + + intf &= CANCTRL_INTMASK; + /* receive */ + if (intf & CANINTF_RX) + apalis_tk1_k20_can_hw_rx(net, 0); + + + /* any error or TX interrupt we need to clear? */ + if (intf & (CANINTF_ERR | CANINTF_TX)) + clear_intf |= intf & (CANINTF_ERR | CANINTF_TX); + apalis_tk1_k20_lock(priv->apalis_tk1_k20); + if (clear_intf) + ret = apalis_tk1_k20_reg_rmw(priv->apalis_tk1_k20, + APALIS_TK1_K20_CANREG + + APALIS_TK1_K20_CAN_DEV_OFFSET( + priv->pdata->id), + CANCTRL_INTMASK, 0x00); + if (ret) { + apalis_tk1_k20_unlock(priv->apalis_tk1_k20); + dev_err(&net->dev, "Communication error\n"); + break; + } + 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; + } + /* Update can state */ + if (intf & CANINTF_ERR) { + 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; + } + } + + /* 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); + } + + } + 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); + + 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: + apalis_tk1_k20_unlock(priv->apalis_tk1_k20); + 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.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES | + CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY; + priv->net = net; + priv->pdata = pdata; + + 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 = __exit_p(apalis_tk1_k20_can_remove), + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM_SLEEP + .pm = &apalis_tk1_k20_can_pm_ops, +#endif + }, +}; + +module_platform_driver_probe(apalis_tk1_k20_can_driver, + &apalis_tk1_k20_can_probe); + +MODULE_DESCRIPTION("CAN driver for K20 MCU on Apalis TK1"); +MODULE_AUTHOR("Dominik Sliwa "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/apalis-tk1-k20.h b/include/linux/mfd/apalis-tk1-k20.h index 29229a09d8c4..48ae8030c8f5 100644 --- a/include/linux/mfd/apalis-tk1-k20.h +++ b/include/linux/mfd/apalis-tk1-k20.h @@ -24,56 +24,78 @@ #define APALIS_TK1_K20_MAX_BULK (64) /* General registers*/ -#define APALIS_TK1_K20_STAREG 0x00 /* General status register RO */ +#define APALIS_TK1_K20_STAREG 0x00 /* general status register RO */ #define APALIS_TK1_K20_REVREG 0x01 /* FW revision register RO*/ #define APALIS_TK1_K20_IRQREG 0x02 /* IRQ status RW(write of 1 will reset the bit) */ -#define APALIS_TK1_K20_CTRREG 0x03 /* General control register RW */ +#define APALIS_TK1_K20_CTRREG 0x03 /* general control register RW */ #define APALIS_TK1_K20_MSQREG 0x04 /* IRQ mask register RW */ -/* CAN Registers */ -#define APALIS_TK1_K20_CANREG 0x10 /* CAN control & status register RW */ -#define APALIS_TK1_K20_CAN_BAUD_REG 0x11 /* CAN Baud set register RW */ -#define APALIS_TK1_K20_CAN_IN_BUF_CNT 0x12 /* CAN IN BUF Received Data Count RO */ -#define APALIS_TK1_K20_CAN_IN_BUF 0x13 /* CAN IN BUF RO */ -#define APALIS_TK1_K20_CAN_OUT_BUF_CNT 0x14 /* CAN OUT BUF Data Count WO, must be written before bulk write to APALIS_TK1_K20_CAN0_OUT_BUF_CNT */ -#define APALIS_TK1_K20_CAN_OUT_FIF0 0x15 /* CAN OUT BUF WO */ +/* 0x05-0x0F Reserved */ -#define APALIS_TK1_K20_CAN_DEV_OFFSET(x) (x ? 0:0x10) +/* CAN Registers */ +#define APALIS_TK1_K20_CANREG 0x10 /* CAN0 control & status register RW */ +#define APALIS_TK1_K20_CANERR 0x11 /* CAN0 error register RW */ +#define APALIS_TK1_K20_CAN_BAUD_REG 0x12 /* CAN0 baud set register RW */ +#define APALIS_TK1_K20_CAN_BIT_1 0x13 /* CAN0 bit timing register 1 RW */ +#define APALIS_TK1_K20_CAN_BIT_2 0x14 /* CAN0 bit timing register 2 RW */ +#define APALIS_TK1_K20_CAN_IN_BUF_CNT 0x15 /* CAN0 IN received data count RO */ +#define APALIS_TK1_K20_CAN_IN_BUF 0x16 /* CAN0 IN RO */ +/* buffer size is 13 bytes */ +#define APALIS_TK1_K20_CAN_IN_BUF_END 0x22 /* CAN0 IN RO */ +#define APALIS_TK1_K20_CAN_OUT_BUF_CNT 0x23 /* CAN0 OUT data Count WO */ +#define APALIS_TK1_K20_CAN_OUT_BUF 0x26 /* 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_DEV_OFFSET(x) (x ? 0x30 : 0) + +/* 0x33-0x3F Reserved */ +/* 0x40-0x62 CAN1 registers same layout as CAN0*/ +/* 0x63-0x6F Reserved */ /* ADC Registers */ -#define APALIS_TK1_K20_ADCREG 0x30 /* ADC control & status register RW */ -#define APALIS_TK1_K20_ADC_CH0L 0x31 /* ADC Channel 0 LSB RO */ -#define APALIS_TK1_K20_ADC_CH0H 0x32 /* ADC Channel 0 MSB RO */ -#define APALIS_TK1_K20_ADC_CH1L 0x33 /* ADC Channel 1 LSB RO */ -#define APALIS_TK1_K20_ADC_CH1H 0x34 /* ADC Channel 1 MSB RO */ -#define APALIS_TK1_K20_ADC_CH2L 0x35 /* ADC Channel 2 LSB RO */ -#define APALIS_TK1_K20_ADC_CH2H 0x36 /* ADC Channel 2 MSB RO */ -#define APALIS_TK1_K20_ADC_CH3L 0x37 /* ADC Channel 3 LSB RO */ -#define APALIS_TK1_K20_ADC_CH3H 0x38 /* ADC Channel 3 MSB RO */ +#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 0x40 /* TSC control & status register RW */ -#define APALIS_TK1_K20_TSC_XML 0x41 /* TSC X- data LSB RO */ -#define APALIS_TK1_K20_TSC_XMH 0x42 /* TSC X- data MSB RO */ -#define APALIS_TK1_K20_TSC_XPL 0x43 /* TSC X+ data LSB RO */ -#define APALIS_TK1_K20_TSC_XPH 0x44 /* TSC X+ data MSB RO */ -#define APALIS_TK1_K20_TSC_YML 0x45 /* TSC Y- data LSB RO */ -#define APALIS_TK1_K20_TSC_YMH 0x46 /* TSC Y- data MSB RO */ -#define APALIS_TK1_K20_TSC_YPL 0x47 /* TSC Y+ data LSB RO */ -#define APALIS_TK1_K20_TSC_YPH 0x48 /* TSC Y+ data MSB RO */ +#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 0x50 /* GPIO control & status register RW */ -#define APALIS_TK1_K20_GPIO_NO 0x51 /* currently configured GPIO RW */ -#define APALIS_TK1_K20_GPIO_STA 0x52 /* Status register for the APALIS_TK1_K20_GPIO_NO GPIO RW */ +#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-0xFD Reserved */ +#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 @@ -82,10 +104,10 @@ #define APALIS_TK1_K20_TSC_IRQ 4 #define APALIS_TK1_K20_GPIO_IRQ 5 -#define APALIS_TK1_K20_FW_VER 0x05 +#define APALIS_TK1_K20_FW_VER 0x09 #define FW_MINOR (APALIS_TK1_K20_FW_VER & 0x0F) -#define FW_MAJOR ((APALIS_TK1_K20_FW_VER & 0xF0) >> 8) +#define FW_MAJOR ((APALIS_TK1_K20_FW_VER & 0xF0) >> 4) #define TK1_K20_SENTINEL 0x55 #define TK1_K20_INVAL 0xAA @@ -94,7 +116,7 @@ #define APALIS_TK1_K20_IRQ_REG_CNT 1 #define APALIS_TK1_K20_IRQ_PER_REG 8 -#define APALIS_TK1_K20_MAX_SPI_SPEED 10000000 +#define APALIS_TK1_K20_MAX_SPI_SPEED 6000000 struct apalis_tk1_k20_regmap { struct regmap *regmap; -- cgit v1.2.3