summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/cpufreq/cpufreq-dt-platdev.c2
-rw-r--r--drivers/firmware/psci/psci.c17
-rw-r--r--drivers/gpio/Kconfig6
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-apalis-tk1-k20.c247
-rw-r--r--drivers/gpu/drm/bridge/dumb-vga-dac.c22
-rw-r--r--drivers/gpu/drm/imx/parallel-display.c3
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_crtc.c31
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_drv.c10
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_drv.h3
-rw-r--r--drivers/gpu/drm/panel/panel-lvds.c46
-rw-r--r--drivers/gpu/drm/panel/panel-simple.c8
-rw-r--r--drivers/hwmon/sht3x.c28
-rw-r--r--drivers/i2c/busses/i2c-imx.c128
-rw-r--r--drivers/iio/adc/Kconfig6
-rw-r--r--drivers/iio/adc/Makefile1
-rw-r--r--drivers/iio/adc/apalis-tk1-k20_adc.c199
-rw-r--r--drivers/input/touchscreen/Kconfig10
-rw-r--r--drivers/input/touchscreen/Makefile1
-rw-r--r--drivers/input/touchscreen/apalis-tk1-k20_ts.c234
-rw-r--r--drivers/mfd/Kconfig14
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/apalis-tk1-k20-ezp.h47
-rw-r--r--drivers/mfd/apalis-tk1-k20.c1090
-rw-r--r--drivers/mfd/stmpe.c2
-rw-r--r--drivers/mmc/core/block.c4
-rw-r--r--drivers/mmc/core/card.h4
-rw-r--r--drivers/mmc/core/mmc_ops.c8
-rw-r--r--drivers/mmc/core/quirks.h8
-rw-r--r--drivers/mmc/host/sdhci-tegra.c15
-rw-r--r--drivers/net/can/Kconfig6
-rw-r--r--drivers/net/can/Makefile1
-rw-r--r--drivers/net/can/apalis-tk1-k20-can.c867
-rw-r--r--drivers/net/ethernet/intel/igb/e1000_82575.c2
-rw-r--r--drivers/net/ethernet/intel/igb/e1000_hw.h2
-rw-r--r--drivers/net/ethernet/intel/igb/igb_main.c64
-rw-r--r--drivers/net/phy/micrel.c24
-rw-r--r--drivers/net/wireless/marvell/mwifiex/cfg80211.c2
-rw-r--r--drivers/net/wireless/marvell/mwifiex/fw.h1
-rw-r--r--drivers/net/wireless/marvell/mwifiex/ioctl.h1
-rw-r--r--drivers/net/wireless/marvell/mwifiex/uap_cmd.c8
-rw-r--r--drivers/pci/controller/dwc/pci-imx6.c68
-rw-r--r--drivers/pci/controller/pci-tegra.c190
-rw-r--r--drivers/spi/spidev.c1
-rw-r--r--drivers/thermal/imx_thermal.c4
-rw-r--r--drivers/tty/serial/imx.c17
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c3
47 files changed, 3380 insertions, 77 deletions
diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c
index 5d28553b69f5..23ff9a07d0b6 100644
--- a/drivers/cpufreq/cpufreq-dt-platdev.c
+++ b/drivers/cpufreq/cpufreq-dt-platdev.c
@@ -109,6 +109,8 @@ static const struct of_device_id blacklist[] __initconst = {
{ .compatible = "calxeda,ecx-2000", },
{ .compatible = "fsl,imx7d", },
+ { .compatible = "fsl,imx7s", },
+
{ .compatible = "fsl,imx8mq", },
{ .compatible = "fsl,imx8mm", },
{ .compatible = "fsl,imx8mn", },
diff --git a/drivers/firmware/psci/psci.c b/drivers/firmware/psci/psci.c
index eb797081d159..a8d385bb51dc 100644
--- a/drivers/firmware/psci/psci.c
+++ b/drivers/firmware/psci/psci.c
@@ -97,6 +97,7 @@ static u32 psci_function_id[PSCI_FN_MAX];
static u32 psci_cpu_suspend_feature;
static bool psci_system_reset2_supported;
+static struct notifier_block psci_restart_handler;
static inline bool psci_has_ext_power_state(void)
{
@@ -265,7 +266,8 @@ static int get_set_conduit_method(struct device_node *np)
return 0;
}
-static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd)
+static int psci_sys_reset(struct notifier_block *this,
+ unsigned long reboot_mode, void *cmd)
{
if ((reboot_mode == REBOOT_WARM || reboot_mode == REBOOT_SOFT) &&
psci_system_reset2_supported) {
@@ -278,6 +280,8 @@ static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd)
} else {
invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
}
+
+ return NOTIFY_DONE;
}
static void psci_sys_poweroff(void)
@@ -426,6 +430,8 @@ static void __init psci_init_smccc(void)
static void __init psci_0_2_set_functions(void)
{
+ int ret;
+
pr_info("Using standard PSCI v0.2 function IDs\n");
psci_ops.get_version = psci_get_version;
@@ -446,7 +452,14 @@ static void __init psci_0_2_set_functions(void)
psci_ops.migrate_info_type = psci_migrate_info_type;
- arm_pm_restart = psci_sys_reset;
+ psci_restart_handler.notifier_call = psci_sys_reset;
+ psci_restart_handler.priority = 160;
+
+ ret = register_restart_handler(&psci_restart_handler);
+ if (ret) {
+ pr_err("Cannot register restart handler, %d\n", ret);
+ return;
+ }
pm_power_off = psci_sys_poweroff;
}
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index ae414045a750..ce012b40d572 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1421,6 +1421,12 @@ config GPIO_74X164
shift registers. This driver can be used to provide access
to more gpio outputs.
+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_MAX3191X
tristate "Maxim MAX3191x industrial serializer"
select CRC8
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index d2fd19c15bae..bfaeb2779527 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_GPIO_ALTERA) += gpio-altera.o
obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o
obj-$(CONFIG_GPIO_AMD_FCH) += gpio-amd-fch.o
obj-$(CONFIG_GPIO_AMDPT) += gpio-amdpt.o
+obj-$(CONFIG_GPIO_APALIS_TK1_K20) += gpio-apalis-tk1-k20.o
obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o
obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o
obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.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..7d950c018db8
--- /dev/null
+++ b/drivers/gpio/gpio-apalis-tk1-k20.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2016-2017 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/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/platform_device.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 int offset)
+{
+ struct apalis_tk1_k20_gpio *gpio = container_of(chip,
+ struct apalis_tk1_k20_gpio, chip);
+
+ apalis_tk1_k20_lock(gpio->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_NO,
+ offset);
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_STA,
+ 0);
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_gpio_output(struct gpio_chip *chip,
+ unsigned int offset,
+ int value)
+{
+ struct apalis_tk1_k20_gpio *gpio = container_of(chip,
+ struct apalis_tk1_k20_gpio, chip);
+ int status;
+
+ apalis_tk1_k20_lock(gpio->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_NO,
+ offset);
+ status = APALIS_TK1_K20_GPIO_STA_OE;
+ status += value ? APALIS_TK1_K20_GPIO_STA_VAL : 0;
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_STA,
+ status);
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_get_status(struct gpio_chip *chip,
+ unsigned int offset, int mask)
+{
+ 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;
+ }
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+
+ return (value & mask) > 0;
+}
+
+static int apalis_tk1_k20_gpio_get_dir(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ /* K20 returns a 1 for output, gpiolib uses 0 for output */
+ return !apalis_tk1_k20_get_status(chip, offset,
+ APALIS_TK1_K20_GPIO_STA_OE);
+}
+
+static int apalis_tk1_k20_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ return apalis_tk1_k20_get_status(chip, offset,
+ APALIS_TK1_K20_GPIO_STA_VAL);
+}
+
+static int apalis_tk1_k20_gpio_request(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct apalis_tk1_k20_gpio *gpio = container_of(chip,
+ struct apalis_tk1_k20_gpio, chip);
+ int status = 0;
+
+ dev_dbg(gpio->apalis_tk1_k20->dev, "APALIS TK1 K20 GPIO %d\n",
+ offset);
+
+ apalis_tk1_k20_lock(gpio->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_NO,
+ offset);
+ if ((apalis_tk1_k20_reg_read(gpio->apalis_tk1_k20,
+ APALIS_TK1_K20_GPIO_NO, &status) < 0) ||
+ (status == 0xff)) {
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+ return -ENODEV;
+ }
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+
+ return 0;
+}
+
+static void apalis_tk1_k20_gpio_free(struct gpio_chip *chip,
+ unsigned int offset)
+{
+/*
+ * Keep the current GPIO state after free, libgpiod cli tools free pins after
+ * each access.
+ */
+#if 0
+ 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);
+#endif
+}
+
+
+static void apalis_tk1_k20_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct apalis_tk1_k20_gpio *gpio = container_of(chip,
+ struct apalis_tk1_k20_gpio, chip);
+ int status;
+
+ apalis_tk1_k20_lock(gpio->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_NO,
+ offset);
+ if (apalis_tk1_k20_reg_read(gpio->apalis_tk1_k20,
+ APALIS_TK1_K20_GPIO_STA, &status) < 0) {
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+ return;
+ }
+
+ status &= ~APALIS_TK1_K20_GPIO_STA_VAL;
+ status += value ? APALIS_TK1_K20_GPIO_STA_VAL : 0;
+ apalis_tk1_k20_reg_write(gpio->apalis_tk1_k20, APALIS_TK1_K20_GPIO_STA,
+ status);
+
+ apalis_tk1_k20_unlock(gpio->apalis_tk1_k20);
+}
+
+static int apalis_tk1_k20_gpio_probe(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_gpio *priv;
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20;
+ int status;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ apalis_tk1_k20 = dev_get_drvdata(pdev->dev.parent);
+ if (!apalis_tk1_k20)
+ return -ENODEV;
+ priv->apalis_tk1_k20 = apalis_tk1_k20;
+
+ platform_set_drvdata(pdev, priv);
+
+ apalis_tk1_k20_lock(apalis_tk1_k20);
+
+ /* TBD: some code */
+
+ apalis_tk1_k20_unlock(apalis_tk1_k20);
+
+ priv->chip.base = -1;
+ priv->chip.can_sleep = 1;
+ priv->chip.label = "k20-gpio";
+ priv->chip.parent = &pdev->dev;
+ priv->chip.owner = THIS_MODULE;
+ priv->chip.get = apalis_tk1_k20_gpio_get;
+ priv->chip.set = apalis_tk1_k20_gpio_set;
+ priv->chip.direction_input = apalis_tk1_k20_gpio_input;
+ priv->chip.direction_output = apalis_tk1_k20_gpio_output;
+ priv->chip.get_direction = apalis_tk1_k20_gpio_get_dir;
+ 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;
+#if defined(CONFIG_OF_GPIO)
+ priv->chip.of_node = pdev->dev.parent->of_node;
+#endif
+
+ status = gpiochip_add(&priv->chip);
+
+ return status;
+}
+
+static int apalis_tk1_k20_gpio_remove(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_gpio *priv = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&priv->chip);
+ return 0;
+}
+
+static const struct platform_device_id apalis_tk1_k20_gpio_idtable[] = {
+ {
+ .name = "apalis-tk1-k20-gpio",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, apalis_tk1_k20_gpio_idtable);
+
+static struct platform_driver apalis_tk1_k20_gpio_driver = {
+ .id_table = apalis_tk1_k20_gpio_idtable,
+ .remove = apalis_tk1_k20_gpio_remove,
+ .probe = apalis_tk1_k20_gpio_probe,
+ .driver = {
+ .name = "apalis-tk1-k20-gpio",
+ },
+};
+
+module_platform_driver(apalis_tk1_k20_gpio_driver);
+
+MODULE_DESCRIPTION("GPIO driver for K20 MCU on Apalis TK1");
+MODULE_AUTHOR("Dominik Sliwa <dominik.sliwa@toradex.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/bridge/dumb-vga-dac.c b/drivers/gpu/drm/bridge/dumb-vga-dac.c
index 7aa789c35882..16bdff01bfff 100644
--- a/drivers/gpu/drm/bridge/dumb-vga-dac.c
+++ b/drivers/gpu/drm/bridge/dumb-vga-dac.c
@@ -177,6 +177,7 @@ static struct i2c_adapter *dumb_vga_retrieve_ddc(struct device *dev)
static int dumb_vga_probe(struct platform_device *pdev)
{
struct dumb_vga *vga;
+ u32 de;
vga = devm_kzalloc(&pdev->dev, sizeof(*vga), GFP_KERNEL);
if (!vga)
@@ -192,6 +193,23 @@ static int dumb_vga_probe(struct platform_device *pdev)
dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
}
+ vga->bridge.funcs = &dumb_vga_bridge_funcs;
+ vga->bridge.of_node = pdev->dev.of_node;
+ vga->bridge.timings = of_device_get_match_data(&pdev->dev);
+
+ if (!vga->bridge.timings &&
+ !of_property_read_u32(pdev->dev.of_node, "de-active", &de)) {
+ struct drm_bridge_timings *timings;
+
+ timings = devm_kzalloc(&pdev->dev, sizeof(*timings), GFP_KERNEL);
+ if (!timings)
+ return -ENOMEM;
+
+ timings->input_bus_flags = de ? DRM_BUS_FLAG_DE_HIGH :
+ DRM_BUS_FLAG_DE_LOW;
+ vga->bridge.timings = timings;
+ }
+
vga->ddc = dumb_vga_retrieve_ddc(&pdev->dev);
if (IS_ERR(vga->ddc)) {
if (PTR_ERR(vga->ddc) == -ENODEV) {
@@ -204,10 +222,6 @@ static int dumb_vga_probe(struct platform_device *pdev)
}
}
- vga->bridge.funcs = &dumb_vga_bridge_funcs;
- vga->bridge.of_node = pdev->dev.of_node;
- vga->bridge.timings = of_device_get_match_data(&pdev->dev);
-
drm_bridge_add(&vga->bridge);
return 0;
diff --git a/drivers/gpu/drm/imx/parallel-display.c b/drivers/gpu/drm/imx/parallel-display.c
index e24272428744..0ba30460f8a4 100644
--- a/drivers/gpu/drm/imx/parallel-display.c
+++ b/drivers/gpu/drm/imx/parallel-display.c
@@ -231,6 +231,9 @@ static int imx_pd_bind(struct device *dev, struct device *master, void *data)
if (ret && ret != -ENODEV)
return ret;
+ if (imxpd->bridge && imxpd->bridge->timings)
+ imxpd->bus_flags = imxpd->bridge->timings->input_bus_flags;
+
imxpd->dev = dev;
ret = imx_pd_register(drm, imxpd);
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_crtc.c b/drivers/gpu/drm/mxsfb/mxsfb_crtc.c
index 12421567af89..ab48009bf0af 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_crtc.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_crtc.c
@@ -299,6 +299,37 @@ void mxsfb_crtc_disable(struct mxsfb_drm_private *mxsfb)
mxsfb_disable_axi_clk(mxsfb);
}
+int mxsfb_plane_atomic_check(struct mxsfb_drm_private *mxsfb,
+ struct drm_plane_state *plane_state,
+ struct drm_crtc_state *crtc_state)
+{
+ struct drm_crtc *crtc = &mxsfb->pipe.crtc;
+ struct drm_device *drm = crtc->dev;
+ unsigned int pitch;
+
+ if (!plane_state->crtc)
+ return 0;
+
+ if (WARN_ON(!plane_state->fb))
+ return -EINVAL;
+
+ if (plane_state->crtc_x || plane_state->crtc_y) {
+ dev_err(drm->dev, "%s: crtc position must be zero.",
+ __func__);
+ return -EINVAL;
+ }
+
+ pitch = crtc_state->mode.hdisplay *
+ plane_state->fb->format->cpp[0];
+ if (plane_state->fb->pitches[0] != pitch) {
+ dev_err(drm->dev,
+ "Invalid pitch: fb and crtc widths must be the same");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
void mxsfb_plane_atomic_update(struct mxsfb_drm_private *mxsfb,
struct drm_plane_state *state)
{
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
index 1694a7deb913..ab1a5d069ae3 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
@@ -150,6 +150,15 @@ static void mxsfb_pipe_disable(struct drm_simple_display_pipe *pipe)
spin_unlock_irq(&drm->event_lock);
}
+static int mxsfb_pipe_check(struct drm_simple_display_pipe *pipe,
+ struct drm_plane_state *plane_state,
+ struct drm_crtc_state *crtc_state)
+{
+ struct mxsfb_drm_private *mxsfb = drm_pipe_to_mxsfb_drm_private(pipe);
+
+ return mxsfb_plane_atomic_check(mxsfb, plane_state, crtc_state);
+}
+
static void mxsfb_pipe_update(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *plane_state)
{
@@ -185,6 +194,7 @@ static void mxsfb_pipe_disable_vblank(struct drm_simple_display_pipe *pipe)
static struct drm_simple_display_pipe_funcs mxsfb_funcs = {
.enable = mxsfb_pipe_enable,
.disable = mxsfb_pipe_disable,
+ .check = mxsfb_pipe_check,
.update = mxsfb_pipe_update,
.prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb,
.enable_vblank = mxsfb_pipe_enable_vblank,
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.h b/drivers/gpu/drm/mxsfb/mxsfb_drv.h
index d975300dca05..c405d83de862 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.h
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.h
@@ -39,6 +39,9 @@ void mxsfb_disable_axi_clk(struct mxsfb_drm_private *mxsfb);
void mxsfb_crtc_enable(struct mxsfb_drm_private *mxsfb);
void mxsfb_crtc_disable(struct mxsfb_drm_private *mxsfb);
+int mxsfb_plane_atomic_check(struct mxsfb_drm_private *mxsfb,
+ struct drm_plane_state *state,
+ struct drm_crtc_state *crtc_state);
void mxsfb_plane_atomic_update(struct mxsfb_drm_private *mxsfb,
struct drm_plane_state *state);
diff --git a/drivers/gpu/drm/panel/panel-lvds.c b/drivers/gpu/drm/panel/panel-lvds.c
index 2405f26e5d31..4e616f705eee 100644
--- a/drivers/gpu/drm/panel/panel-lvds.c
+++ b/drivers/gpu/drm/panel/panel-lvds.c
@@ -23,6 +23,11 @@
#include <drm/drm_crtc.h>
#include <drm/drm_panel.h>
+enum panel_type {
+ PANEL_LVDS,
+ PANEL_DPI
+};
+
struct panel_lvds {
struct drm_panel panel;
struct device *dev;
@@ -124,7 +129,9 @@ static int panel_lvds_get_modes(struct drm_panel *panel)
connector->display_info.height_mm = lvds->height;
drm_display_info_set_bus_formats(&connector->display_info,
&lvds->bus_format, 1);
- connector->display_info.bus_flags = lvds->data_mirror
+ drm_bus_flags_from_videomode(&lvds->video_mode,
+ &connector->display_info.bus_flags);
+ connector->display_info.bus_flags |= lvds->data_mirror
? DRM_BUS_FLAG_DATA_LSB_TO_MSB
: DRM_BUS_FLAG_DATA_MSB_TO_LSB;
@@ -145,6 +152,7 @@ static int panel_lvds_parse_dt(struct panel_lvds *lvds)
struct display_timing timing;
const char *mapping;
int ret;
+ enum panel_type type;
ret = of_get_display_timing(np, "panel-timing", &timing);
if (ret < 0) {
@@ -177,13 +185,30 @@ static int panel_lvds_parse_dt(struct panel_lvds *lvds)
return -ENODEV;
}
- if (!strcmp(mapping, "jeida-18")) {
- lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
- } else if (!strcmp(mapping, "jeida-24")) {
- lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
- } else if (!strcmp(mapping, "vesa-24")) {
- lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
- } else {
+ type = (enum panel_type)of_device_get_match_data(lvds->dev);
+ switch (type) {
+ case PANEL_LVDS:
+ if (!strcmp(mapping, "jeida-18")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
+ } else if (!strcmp(mapping, "jeida-24")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
+ } else if (!strcmp(mapping, "vesa-24")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
+ }
+ break;
+ case PANEL_DPI:
+ if (!strcmp(mapping, "rgb24")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ } else if (!strcmp(mapping, "rgb565")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB565_1X16;
+ } else if (!strcmp(mapping, "bgr666")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+ } else if (!strcmp(mapping, "lvds666")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X24_CPADHI;
+ }
+ };
+
+ if (!lvds->bus_format) {
dev_err(lvds->dev, "%pOF: invalid or missing %s DT property\n",
np, "data-mapping");
return -EINVAL;
@@ -277,7 +302,8 @@ static int panel_lvds_remove(struct platform_device *pdev)
}
static const struct of_device_id panel_lvds_of_table[] = {
- { .compatible = "panel-lvds", },
+ { .compatible = "panel-lvds", .data = (void *)PANEL_LVDS },
+ { .compatible = "panel-dpi", .data = (void *)PANEL_DPI },
{ /* Sentinel */ },
};
@@ -287,7 +313,7 @@ static struct platform_driver panel_lvds_driver = {
.probe = panel_lvds_probe,
.remove = panel_lvds_remove,
.driver = {
- .name = "panel-lvds",
+ .name = "panel-generic",
.of_match_table = panel_lvds_of_table,
},
};
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
index fd8b16dcf13e..7967ffc981f1 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -1298,12 +1298,12 @@ static const struct drm_display_mode edt_et057090dhu_mode = {
.clock = 25175,
.hdisplay = 640,
.hsync_start = 640 + 16,
- .hsync_end = 640 + 16 + 30,
- .htotal = 640 + 16 + 30 + 114,
+ .hsync_end = 640 + 16 + 48,
+ .htotal = 640 + 16 + 48 + 96,
.vdisplay = 480,
.vsync_start = 480 + 10,
- .vsync_end = 480 + 10 + 3,
- .vtotal = 480 + 10 + 3 + 32,
+ .vsync_end = 480 + 10 + 2,
+ .vtotal = 480 + 10 + 2 + 33,
.vrefresh = 60,
.flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
};
diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c
index 7364764baaeb..b1065c9cb292 100644
--- a/drivers/hwmon/sht3x.c
+++ b/drivers/hwmon/sht3x.c
@@ -21,6 +21,7 @@
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/platform_data/sht3x.h>
+#include <linux/of.h>
/* commands (high precision mode) */
static const unsigned char sht3x_cmd_measure_blocking_hpm[] = { 0x2c, 0x06 };
@@ -671,6 +672,7 @@ static int sht3x_probe(struct i2c_client *client,
struct i2c_adapter *adap = client->adapter;
struct device *dev = &client->dev;
const struct attribute_group **attribute_groups;
+ struct device_node *np = client->dev.of_node;
/*
* we require full i2c support since the sht3x uses multi-byte read and
@@ -696,8 +698,16 @@ static int sht3x_probe(struct i2c_client *client,
data->client = client;
crc8_populate_msb(sht3x_crc8_table, SHT3X_CRC8_POLYNOMIAL);
- if (client->dev.platform_data)
- data->setup = *(struct sht3x_platform_data *)dev->platform_data;
+ if (np) {
+ if (of_property_read_bool(np, "sensirion,blocking-io"))
+ data->setup.blocking_io = true;
+ if (of_property_read_bool(np, "sensirion,no-high-precision"))
+ data->setup.high_precision = false;
+ } else {
+ if (client->dev.platform_data)
+ data->setup = *(struct sht3x_platform_data *)
+ dev->platform_data;
+ }
sht3x_select_command(data);
@@ -740,8 +750,20 @@ static const struct i2c_device_id sht3x_ids[] = {
MODULE_DEVICE_TABLE(i2c, sht3x_ids);
+#ifdef CONFIG_OF
+static const struct of_device_id sht3x_dt_match[] = {
+ { .compatible = "sensirion,sht3x" },
+ { .compatible = "sensirion,sts3x" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sht3x_dt_match);
+#endif
+
static struct i2c_driver sht3x_i2c_driver = {
- .driver.name = "sht3x",
+ .driver = {
+ .name = "sht3x",
+ .of_match_table = of_match_ptr(sht3x_dt_match),
+ },
.probe = sht3x_probe,
.id_table = sht3x_ids,
};
diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
index 9d3f42fd6352..28615c0013c7 100644
--- a/drivers/i2c/busses/i2c-imx.c
+++ b/drivers/i2c/busses/i2c-imx.c
@@ -427,7 +427,7 @@ static void i2c_imx_clear_irq(struct imx_i2c_struct *i2c_imx, unsigned int bits)
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
}
-static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy)
+static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy, bool atomic)
{
unsigned long orig_jiffies = jiffies;
unsigned int temp;
@@ -456,15 +456,39 @@ static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy)
"<%s> I2C bus is busy\n", __func__);
return -ETIMEDOUT;
}
- schedule();
+ if (!atomic)
+ schedule();
+ else
+ udelay(100);
}
return 0;
}
-static int i2c_imx_trx_complete(struct imx_i2c_struct *i2c_imx)
+static int i2c_imx_trx_complete(struct imx_i2c_struct *i2c_imx, bool atomic)
{
- wait_event_timeout(i2c_imx->queue, i2c_imx->i2csr & I2SR_IIF, HZ / 10);
+ if (!atomic) {
+ wait_event_timeout(i2c_imx->queue, i2c_imx->i2csr & I2SR_IIF, HZ / 10);
+ } else {
+ int counter = 0;
+
+ while (1) {
+ unsigned int reg;
+
+ reg = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ i2c_imx->i2csr = reg;
+ if (reg & I2SR_IIF)
+ break;
+
+ if (counter > 1000) {
+ dev_err(&i2c_imx->adapter.dev, "<%s> TXR timeout\n", __func__);
+ return -EIO;
+ }
+ udelay(100);
+ counter++;
+ }
+ imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2SR);
+ }
if (unlikely(!(i2c_imx->i2csr & I2SR_IIF))) {
dev_dbg(&i2c_imx->adapter.dev, "<%s> Timeout\n", __func__);
@@ -552,7 +576,7 @@ static int i2c_imx_clk_notifier_call(struct notifier_block *nb,
return NOTIFY_OK;
}
-static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
+static int i2c_imx_start(struct imx_i2c_struct *i2c_imx, bool atomic)
{
unsigned int temp = 0;
int result;
@@ -565,23 +589,32 @@ static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode, i2c_imx, IMX_I2C_I2CR);
/* Wait controller to be stable */
- usleep_range(50, 150);
+ if (!atomic)
+ usleep_range(50, 150);
+ else
+ udelay(50);
/* Start I2C transaction */
temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
temp |= I2CR_MSTA;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
- result = i2c_imx_bus_busy(i2c_imx, 1);
+ result = i2c_imx_bus_busy(i2c_imx, 1, atomic);
if (result)
return result;
- temp |= I2CR_IIEN | I2CR_MTX | I2CR_TXAK;
+ if (!atomic) {
+ temp |= I2CR_IIEN | I2CR_MTX | I2CR_TXAK;
+ } else {
+ temp |= I2CR_MTX | I2CR_TXAK;
+ temp &= ~I2CR_IIEN; /* Disable interrupt */
+ }
+
temp &= ~I2CR_DMAEN;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
return result;
}
-static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
+static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx, bool atomic)
{
unsigned int temp = 0;
@@ -605,7 +638,7 @@ static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
}
if (!i2c_imx->stopped)
- i2c_imx_bus_busy(i2c_imx, 0);
+ i2c_imx_bus_busy(i2c_imx, 0, atomic);
/* Disable I2C controller */
temp = i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
@@ -684,7 +717,7 @@ static int i2c_imx_dma_write(struct imx_i2c_struct *i2c_imx,
/* The last data byte must be transferred by the CPU. */
imx_i2c_write_reg(msgs->buf[msgs->len-1],
i2c_imx, IMX_I2C_I2DR);
- result = i2c_imx_trx_complete(i2c_imx);
+ result = i2c_imx_trx_complete(i2c_imx, false);
if (result)
return result;
@@ -743,7 +776,7 @@ static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx,
msgs->buf[msgs->len-2] = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
/* read n byte data */
- result = i2c_imx_trx_complete(i2c_imx);
+ result = i2c_imx_trx_complete(i2c_imx, false);
if (result)
return result;
@@ -759,7 +792,7 @@ static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx,
temp &= ~(I2CR_MSTA | I2CR_MTX);
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
if (!i2c_imx->stopped)
- i2c_imx_bus_busy(i2c_imx, 0);
+ i2c_imx_bus_busy(i2c_imx, 0, false);
} else {
/*
* For i2c master receiver repeat restart operation like:
@@ -777,7 +810,7 @@ static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx,
return 0;
}
-static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs)
+static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bool atomic)
{
int i, result;
@@ -786,7 +819,7 @@ static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs)
/* write slave address */
imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR);
- result = i2c_imx_trx_complete(i2c_imx);
+ result = i2c_imx_trx_complete(i2c_imx, atomic);
if (result)
return result;
result = i2c_imx_acked(i2c_imx);
@@ -800,7 +833,7 @@ static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs)
"<%s> write byte: B%d=0x%X\n",
__func__, i, msgs->buf[i]);
imx_i2c_write_reg(msgs->buf[i], i2c_imx, IMX_I2C_I2DR);
- result = i2c_imx_trx_complete(i2c_imx);
+ result = i2c_imx_trx_complete(i2c_imx, atomic);
if (result)
return result;
result = i2c_imx_acked(i2c_imx);
@@ -810,7 +843,7 @@ static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs)
return 0;
}
-static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bool is_lastmsg)
+static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bool is_lastmsg, bool atomic)
{
int i, result;
unsigned int temp;
@@ -823,7 +856,7 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bo
/* write slave address */
imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR);
- result = i2c_imx_trx_complete(i2c_imx);
+ result = i2c_imx_trx_complete(i2c_imx, atomic);
if (result)
return result;
result = i2c_imx_acked(i2c_imx);
@@ -856,7 +889,7 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bo
for (i = 0; i < msgs->len; i++) {
u8 len = 0;
- result = i2c_imx_trx_complete(i2c_imx);
+ result = i2c_imx_trx_complete(i2c_imx, atomic);
if (result)
return result;
/*
@@ -887,7 +920,7 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bo
temp &= ~(I2CR_MSTA | I2CR_MTX);
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
if (!i2c_imx->stopped)
- i2c_imx_bus_busy(i2c_imx, 0);
+ i2c_imx_bus_busy(i2c_imx, 0, atomic);
} else {
/*
* For i2c master receiver repeat restart operation like:
@@ -918,8 +951,8 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bo
return 0;
}
-static int i2c_imx_xfer(struct i2c_adapter *adapter,
- struct i2c_msg *msgs, int num)
+static int i2c_imx_xfer_common(struct i2c_adapter *adapter,
+ struct i2c_msg *msgs, int num, bool atomic)
{
unsigned int i, temp;
int result;
@@ -933,11 +966,11 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter,
goto out;
/* Start I2C transfer */
- result = i2c_imx_start(i2c_imx);
+ result = i2c_imx_start(i2c_imx, atomic);
if (result) {
if (i2c_imx->adapter.bus_recovery_info) {
i2c_recover_bus(&i2c_imx->adapter);
- result = i2c_imx_start(i2c_imx);
+ result = i2c_imx_start(i2c_imx, atomic);
}
}
@@ -955,7 +988,7 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter,
temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
temp |= I2CR_RSTA;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
- result = i2c_imx_bus_busy(i2c_imx, 1);
+ result = i2c_imx_bus_busy(i2c_imx, 1, atomic);
if (result)
goto fail0;
}
@@ -979,13 +1012,17 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter,
(temp & I2SR_SRW ? 1 : 0), (temp & I2SR_IIF ? 1 : 0),
(temp & I2SR_RXAK ? 1 : 0));
#endif
- if (msgs[i].flags & I2C_M_RD)
- result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
- else {
- if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
- result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
- else
- result = i2c_imx_write(i2c_imx, &msgs[i]);
+ if (msgs[i].flags & I2C_M_RD) {
+ result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg, atomic);
+ } else {
+ if (!atomic) {
+ if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
+ result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
+ else
+ result = i2c_imx_write(i2c_imx, &msgs[i], atomic);
+ } else {
+ result = i2c_imx_write(i2c_imx, &msgs[i], atomic);
+ }
}
if (result)
goto fail0;
@@ -993,7 +1030,7 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter,
fail0:
/* Stop I2C transfer */
- i2c_imx_stop(i2c_imx);
+ i2c_imx_stop(i2c_imx, atomic);
pm_runtime_mark_last_busy(i2c_imx->adapter.dev.parent);
pm_runtime_put_autosuspend(i2c_imx->adapter.dev.parent);
@@ -1005,6 +1042,18 @@ out:
return (result < 0) ? result : num;
}
+static int i2c_imx_xfer(struct i2c_adapter *adapter,
+ struct i2c_msg *msgs, int num)
+{
+ return i2c_imx_xfer_common(adapter, msgs, num, false);
+}
+
+static int i2c_imx_xfer_atomic(struct i2c_adapter *adapter,
+ struct i2c_msg *msgs, int num)
+{
+ return i2c_imx_xfer_common(adapter, msgs, num, true);
+}
+
static void i2c_imx_prepare_recovery(struct i2c_adapter *adap)
{
struct imx_i2c_struct *i2c_imx;
@@ -1077,8 +1126,9 @@ static u32 i2c_imx_func(struct i2c_adapter *adapter)
}
static const struct i2c_algorithm i2c_imx_algo = {
- .master_xfer = i2c_imx_xfer,
- .functionality = i2c_imx_func,
+ .master_xfer = i2c_imx_xfer,
+ .master_xfer_atomic = i2c_imx_xfer_atomic,
+ .functionality = i2c_imx_func,
};
static int i2c_imx_probe(struct platform_device *pdev)
@@ -1149,6 +1199,14 @@ static int i2c_imx_probe(struct platform_device *pdev)
/* Set up platform driver data */
platform_set_drvdata(pdev, i2c_imx);
+ /*
+ * Driver's PM callbacks are safe to be called in atomic contexts.
+ * Providing this information to the PM subsystem is required for the
+ * 'master_xfer_atomic' implementation that calls PM routines in IRQ
+ * less/atomic contexts.
+ */
+ pm_runtime_irq_safe(&pdev->dev);
+
pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index b39d5ad15744..968cf62616c2 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -207,6 +207,12 @@ config AD799X
To compile this driver as a module, choose M here: the module will be
called ad799x.
+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 ASPEED_ADC
tristate "Aspeed ADC"
depends on ARCH_ASPEED || COMPILE_TEST
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index d0b11502102e..d471bc461fc4 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_AD7793) += ad7793.o
obj-$(CONFIG_AD7887) += ad7887.o
obj-$(CONFIG_AD7949) += ad7949.o
obj-$(CONFIG_AD799X) += ad799x.o
+obj-$(CONFIG_APALIS_TK1_K20_ADC) += apalis-tk1-k20_adc.o
obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o
obj-$(CONFIG_AT91_ADC) += at91_adc.o
obj-$(CONFIG_AT91_SAMA5D2_ADC) += at91-sama5d2_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..3d1edcc1a58f
--- /dev/null
+++ b/drivers/iio/adc/apalis-tk1-k20_adc.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2016-2017 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,
+};
+
+#define APALIS_TK1_K20_CHAN(_id, _type) { \
+ .type = _type, \
+ .indexed = 1, \
+ .channel = APALIS_TK1_K20_##_id, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .datasheet_name = #_id, \
+}
+
+static const struct iio_chan_spec apalis_tk1_k20_adc_channels[] = {
+ [APALIS_TK1_K20_ADC1] = APALIS_TK1_K20_CHAN(ADC1, IIO_VOLTAGE),
+ [APALIS_TK1_K20_ADC2] = APALIS_TK1_K20_CHAN(ADC2, IIO_VOLTAGE),
+ [APALIS_TK1_K20_ADC3] = APALIS_TK1_K20_CHAN(ADC3, IIO_VOLTAGE),
+ [APALIS_TK1_K20_ADC4] = APALIS_TK1_K20_CHAN(ADC4, IIO_VOLTAGE),
+};
+
+
+static int apalis_tk1_k20_adc_probe(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_adc *priv;
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = iio_device_alloc(sizeof(*priv));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ apalis_tk1_k20 = dev_get_drvdata(pdev->dev.parent);
+ if (!apalis_tk1_k20) {
+ ret = -ENODEV;
+ goto err_iio_device;
+ }
+
+ priv = iio_priv(indio_dev);
+ priv->apalis_tk1_k20 = apalis_tk1_k20;
+
+ apalis_tk1_k20_lock(apalis_tk1_k20);
+
+ /* Enable ADC */
+ apalis_tk1_k20_reg_write(apalis_tk1_k20, APALIS_TK1_K20_ADCREG, 0x01);
+
+ apalis_tk1_k20_unlock(apalis_tk1_k20);
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ indio_dev->dev.of_node = pdev->dev.of_node;
+ indio_dev->dev.parent = &pdev->dev;
+ indio_dev->name = pdev->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &apalis_tk1_k20_adc_info;
+ indio_dev->channels = apalis_tk1_k20_adc_channels;
+ indio_dev->num_channels = ARRAY_SIZE(apalis_tk1_k20_adc_channels);
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "iio dev register err: %d\n", ret);
+ goto err_iio_device;
+ }
+
+ return 0;
+
+err_iio_device:
+ iio_device_free(indio_dev);
+ return ret;
+}
+
+static int apalis_tk1_k20_adc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20 = dev_get_drvdata(
+ pdev->dev.parent);
+
+ apalis_tk1_k20_lock(apalis_tk1_k20);
+
+ /* Disable ADC */
+ apalis_tk1_k20_reg_write(apalis_tk1_k20, APALIS_TK1_K20_ADCREG, 0x00);
+
+ apalis_tk1_k20_unlock(apalis_tk1_k20);
+
+ iio_device_unregister(indio_dev);
+ iio_device_free(indio_dev);
+
+ return 0;
+}
+
+static const struct platform_device_id apalis_tk1_k20_adc_idtable[] = {
+ {
+ .name = "apalis-tk1-k20-adc",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, apalis_tk1_k20_adc_idtable);
+
+static struct platform_driver apalis_tk1_k20_adc_driver = {
+ .id_table = apalis_tk1_k20_adc_idtable,
+ .remove = apalis_tk1_k20_adc_remove,
+ .probe = apalis_tk1_k20_adc_probe,
+ .driver = {
+ .name = "apalis-tk1-k20-adc",
+ },
+};
+
+module_platform_driver(apalis_tk1_k20_adc_driver);
+
+MODULE_DESCRIPTION("K20 ADCs on Apalis TK1");
+MODULE_AUTHOR("Dominik Sliwa");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 1e812a193ce7..0ebf66fe63b6 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -107,6 +107,16 @@ config TOUCHSCREEN_ADC
To compile this driver as a module, choose M here: the
module will be called resistive-adc-touch.ko.
+config TOUCHSCREEN_APALIS_TK1_K20
+ tristate "K20 based touchscreen controller on Apalis TK1"
+ depends on MFD_APALIS_TK1_K20
+ help
+ Say Y here if you want to support the touchscreen controller
+ implemented in the K20 found on Apalis TK1.
+
+ To compile this driver as a module, choose M here: the module will be
+ called apalis-tk1-k20_ts.
+
config TOUCHSCREEN_AR1021_I2C
tristate "Microchip AR1020/1021 i2c touchscreen"
depends on I2C && OF
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 94c6162409b3..659f0e119214 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_TOUCHSCREEN_AD7879_I2C) += ad7879-i2c.o
obj-$(CONFIG_TOUCHSCREEN_AD7879_SPI) += ad7879-spi.o
obj-$(CONFIG_TOUCHSCREEN_ADC) += resistive-adc-touch.o
obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o
+obj-$(CONFIG_TOUCHSCREEN_APALIS_TK1_K20) += apalis-tk1-k20_ts.o
obj-$(CONFIG_TOUCHSCREEN_AR1021_I2C) += ar1021_i2c.o
obj-$(CONFIG_TOUCHSCREEN_ATMEL_MXT) += atmel_mxt_ts.o
obj-$(CONFIG_TOUCHSCREEN_AUO_PIXCIR) += auo-pixcir-ts.o
diff --git a/drivers/input/touchscreen/apalis-tk1-k20_ts.c b/drivers/input/touchscreen/apalis-tk1-k20_ts.c
new file mode 100644
index 000000000000..ef9e56c94685
--- /dev/null
+++ b/drivers/input/touchscreen/apalis-tk1-k20_ts.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2016-2017 Toradex AG
+ * Dominik Sliwa <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 / 25 which is acceptable.
+ */
+ queue_delayed_work(priv->workq, &priv->work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static void apalis_tk1_k20_ts_report_sample(struct apalis_tk1_k20_ts *priv)
+{
+ struct input_dev *idev = priv->idev;
+ int xp, xm, yp, ym;
+ int x, y, press, btn;
+
+ xp = priv->sample[1];
+ xm = priv->sample[0];
+ yp = priv->sample[3];
+ ym = priv->sample[2];
+
+ x = (xp + xm) / 2;
+ y = (yp + ym) / 2;
+ press = (abs(yp - ym) + abs(xp - xm)) / 2;
+
+ if ((yp != 0) && (xp != 0)) {
+ btn = 1;
+ input_report_abs(idev, ABS_X, x);
+ input_report_abs(idev, ABS_Y, y);
+
+ dev_dbg(&idev->dev, "report (%d, %d, %d)\n",
+ x, y, press);
+ queue_delayed_work(priv->workq, &priv->work, HZ / 25);
+ } else {
+ dev_dbg(&idev->dev, "report release\n");
+ btn = 0;
+ }
+
+ input_report_abs(idev, ABS_PRESSURE, press);
+ input_report_key(idev, BTN_TOUCH, btn);
+ input_sync(idev);
+}
+
+static void apalis_tk1_k20_ts_work(struct work_struct *work)
+{
+ struct apalis_tk1_k20_ts *priv =
+ container_of(work, struct apalis_tk1_k20_ts, work.work);
+ uint8_t buf[8], i;
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+
+ if (apalis_tk1_k20_reg_read_bulk(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_TSC_XML, buf, 8) < 0) {
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+ dev_err(&priv->idev->dev, "Error reading data\n");
+ return;
+ }
+
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+
+ for (i = 0; i < 4; i++)
+ priv->sample[i] = (buf[(2 * i) + 1] << 8) + buf[2 * i];
+
+ apalis_tk1_k20_ts_report_sample(priv);
+}
+
+static int apalis_tk1_k20_ts_open(struct input_dev *dev)
+{
+ struct apalis_tk1_k20_ts *priv = input_get_drvdata(dev);
+ int ret;
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+
+ ret = apalis_tk1_k20_irq_request(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_TSC_IRQ, apalis_tk1_k20_ts_handler,
+ APALIS_TK1_K20_TS_NAME, priv);
+ if (ret)
+ goto out;
+
+ ret = apalis_tk1_k20_reg_rmw(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_TSCREG, APALIS_TK1_K20_TSC_ENA_MASK,
+ APALIS_TK1_K20_TSC_ENA);
+ if (ret)
+ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_TSC_IRQ, priv);
+
+out:
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+
+ return ret;
+}
+
+static void apalis_tk1_k20_ts_close(struct input_dev *dev)
+{
+ struct apalis_tk1_k20_ts *priv = input_get_drvdata(dev);
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+
+ apalis_tk1_k20_reg_rmw(priv->apalis_tk1_k20, APALIS_TK1_K20_TSCREG,
+ APALIS_TK1_K20_TSC_ENA_MASK, 0);
+ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20, APALIS_TK1_K20_TSC_IRQ,
+ priv);
+
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+
+ cancel_delayed_work_sync(&priv->work);
+}
+
+static int apalis_tk1_k20_ts_probe(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_ts *priv;
+ struct input_dev *idev;
+ int ret = -ENOMEM;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ idev = input_allocate_device();
+ if (!priv || !idev)
+ goto err_free_mem;
+
+ INIT_DELAYED_WORK(&priv->work, apalis_tk1_k20_ts_work);
+
+ priv->apalis_tk1_k20 = dev_get_drvdata(pdev->dev.parent);
+ priv->idev = idev;
+
+ priv->workq = create_singlethread_workqueue("apalis_tk1_k20_ts");
+ if (!priv->workq)
+ goto err_free_mem;
+
+ idev->name = APALIS_TK1_K20_TS_NAME;
+ idev->dev.parent = &pdev->dev;
+
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(idev, ABS_X, 0, 0xfff, 0, 0);
+ input_set_abs_params(idev, ABS_Y, 0, 0xfff, 0, 0);
+ input_set_abs_params(idev, ABS_PRESSURE, 0, 0xfff, 0, 0);
+
+ idev->open = apalis_tk1_k20_ts_open;
+ idev->close = apalis_tk1_k20_ts_close;
+
+ input_set_drvdata(idev, priv);
+
+ ret = input_register_device(priv->idev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "register input device failed with %d\n", ret);
+ goto err_destroy_wq;
+ }
+
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+
+err_destroy_wq:
+ destroy_workqueue(priv->workq);
+err_free_mem:
+ input_free_device(idev);
+ kfree(priv);
+
+ return ret;
+}
+
+static int apalis_tk1_k20_ts_remove(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_ts *priv = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ destroy_workqueue(priv->workq);
+ input_unregister_device(priv->idev);
+ kfree(priv);
+
+ return 0;
+}
+
+static const struct platform_device_id apalis_tk1_k20_ts_idtable[] = {
+ {
+ .name = "apalis-tk1-k20-ts",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, apalis_tk1_k20_ts_idtable);
+
+static struct platform_driver apalis_tk1_k20_ts_driver = {
+ .id_table = apalis_tk1_k20_ts_idtable,
+ .probe = apalis_tk1_k20_ts_probe,
+ .remove = apalis_tk1_k20_ts_remove,
+ .driver = {
+ .name = APALIS_TK1_K20_TS_NAME,
+ },
+};
+
+module_platform_driver(apalis_tk1_k20_ts_driver);
+
+MODULE_DESCRIPTION("K20 touchscreen controller on Apalis TK1");
+MODULE_AUTHOR("Dominik Sliwa <dominik.sliwa@toradex.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 43169f25da1f..eaf5d5419893 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -178,6 +178,20 @@ config MFD_AC100
This driver include only the core APIs. You have to select individual
components like codecs or RTC under the corresponding menus.
+config MFD_APALIS_TK1_K20
+ tristate "K20 on Apalis TK1"
+ depends on SPI_MASTER
+ select MFD_CORE
+ help
+ The Kinetis MK20DN512 companion micro controller found on Apalis TK1
+ supports CAN, resistive touch, GPIOs and analog inputs.
+
+config APALIS_TK1_K20_EZP
+ bool "K20 on Apalis TK1 programming via EZ Port"
+ depends on MFD_APALIS_TK1_K20
+ help
+ Support for flashing new K20 firmware using EZ-Port functionality.
+
config MFD_AXP20X
tristate
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index c1067ea46204..f3a394b20773 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -141,6 +141,7 @@ obj-$(CONFIG_MFD_DA9052_SPI) += da9052-spi.o
obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o
obj-$(CONFIG_MFD_AC100) += ac100.o
+obj-$(CONFIG_MFD_APALIS_TK1_K20) += apalis-tk1-k20.o
obj-$(CONFIG_MFD_AXP20X) += axp20x.o
obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o
obj-$(CONFIG_MFD_AXP20X_RSB) += axp20x-rsb.o
diff --git a/drivers/mfd/apalis-tk1-k20-ezp.h b/drivers/mfd/apalis-tk1-k20-ezp.h
new file mode 100644
index 000000000000..e89d6adbe471
--- /dev/null
+++ b/drivers/mfd/apalis-tk1-k20-ezp.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2017 Toradex AG
+ * Dominik Sliwa <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 4080000
+#define APALIS_TK1_K20_EZP_MAX_DATA 32
+#define APALIS_TK1_K20_EZP_WRITE_SIZE 32
+
+static const struct firmware *fw_entry;
+#endif /* CONFIG_APALIS_TK1_K20_EZP */
+
+#endif /* __DRIVERS_MFD_APALIS_TK1_K20_H */
diff --git a/drivers/mfd/apalis-tk1-k20.c b/drivers/mfd/apalis-tk1-k20.c
new file mode 100644
index 000000000000..bf7efdc8b4de
--- /dev/null
+++ b/drivers/mfd/apalis-tk1-k20.c
@@ -0,0 +1,1090 @@
+/*
+ * Copyright 2016-2017 Toradex AG
+ * Dominik Sliwa <dominik.sliwa@toradex.com>
+ *
+ * based on a 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/of_irq.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+
+#include "apalis-tk1-k20-ezp.h"
+#define APALIS_TK1_K20_MAX_MSG 4
+
+static unsigned int fw_ignore = 0;
+module_param(fw_ignore , uint, 0);
+MODULE_PARM_DESC(fw_ignore, "Assume that K20 is running valid fw version. "
+ "Don't verify, don't erase, don't update");
+
+static unsigned int force_fw_reload = 0;
+module_param(force_fw_reload , uint, 0);
+MODULE_PARM_DESC(force_fw_reload, "Update K20 fw even when the same version"
+ " is already flashed.");
+
+static const struct spi_device_id apalis_tk1_k20_device_ids[] = {
+ {
+ .name = "apalis-tk1-k20",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(spi, apalis_tk1_k20_device_ids);
+
+static const struct of_device_id apalis_tk1_k20_dt_ids[] = {
+ {
+ .compatible = "toradex,apalis-tk1-k20",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, apalis_tk1_k20_dt_ids);
+
+static const struct regmap_config apalis_tk1_k20_regmap_spi_config = {
+ .reg_bits = 8,
+ .pad_bits = 0,
+ .val_bits = 8,
+
+ .max_register = APALIS_TK1_K20_LAST_REG,
+
+ .cache_type = REGCACHE_NONE,
+ .use_single_read = false,
+ .use_single_write = false,
+};
+
+static int apalis_tk1_k20_spi_read(void *context, const void *reg,
+ size_t reg_size, void *val, size_t val_size)
+{
+ unsigned char w[APALIS_TK1_K20_MAX_BULK] = {APALIS_TK1_K20_READ_INST,
+ *((unsigned char *) reg), val_size, 0x00, 0x00, 0x00,
+ 0x00};
+ unsigned char r[APALIS_TK1_K20_MAX_BULK];
+ unsigned char *p = val;
+ int i = 0, j = 0;
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ struct spi_transfer t = {
+ .tx_buf = w,
+ .rx_buf = r,
+ .len = 8,
+ .cs_change = 0,
+ .delay_usecs = 0,
+ };
+
+ struct spi_message m;
+ int ret;
+ spi->mode = SPI_MODE_1;
+
+ if (reg_size != 1)
+ return -ENOTSUPP;
+
+ if (val_size == 1) {
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync(spi, &m);
+
+ for (i = 3; i < 7; i++ )
+ {
+ if (((unsigned char *)t.rx_buf)[i] == 0x55) {
+ *p = ((unsigned char *)t.rx_buf)[i + 1];
+ return ret;
+ }
+ }
+
+ for (j = 0; j < APALIS_TK1_MAX_RETRY_CNT; j++) {
+ udelay(250 * j * j);
+ t.tx_buf = w;
+ t.rx_buf = r;
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync(spi, &m);
+ for (i = 3; i < 7; i++ )
+ {
+ if (((unsigned char *)t.rx_buf)[i] == 0x55) {
+ *p = ((unsigned char *)t.rx_buf)[i + 1];
+ return ret;
+ }
+ }
+ }
+ ret = -EIO;
+
+ } else if ((val_size > 1) && (val_size < APALIS_TK1_K20_MAX_BULK)) {
+ t.len = 12;
+ w[0] = APALIS_TK1_K20_BULK_READ_INST;
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync(spi, &m);
+ /* no need to reinit the message*/
+ t.len = val_size;
+ t.rx_buf = p;
+ /* just use the same transfer */
+ ret = spi_sync(spi, &m);
+
+ } else {
+ return -ENOTSUPP;
+ }
+
+ return ret;
+}
+
+
+static int apalis_tk1_k20_spi_write(void *context, const void *data,
+ size_t count)
+{
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ uint8_t out_data[APALIS_TK1_K20_MAX_BULK];
+ int ret;
+
+ spi->mode = SPI_MODE_1;
+
+ if (count == 2) {
+ out_data[0] = APALIS_TK1_K20_WRITE_INST;
+ out_data[1] = ((uint8_t *)data)[0];
+ out_data[2] = ((uint8_t *)data)[1];
+ ret = spi_write(spi, out_data, 3);
+
+ } else if ((count > 2) && (count < APALIS_TK1_K20_MAX_BULK)) {
+ out_data[0] = APALIS_TK1_K20_BULK_WRITE_INST;
+ out_data[1] = ((uint8_t *)data)[0];
+ out_data[2] = count - 1;
+ memcpy(&out_data[3], &((uint8_t *)data)[1], count - 1);
+ ret = spi_write(spi, out_data, count + 2);
+ } else {
+ dev_err(dev, "Apalis TK1 K20 MFD invalid write count = %d\n",
+ count);
+ return -ENOTSUPP;
+ }
+ return ret;
+}
+
+static struct regmap_bus regmap_apalis_tk1_k20_bus = {
+ .write = apalis_tk1_k20_spi_write,
+ .read = apalis_tk1_k20_spi_read,
+};
+
+void apalis_tk1_k20_lock(struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ if (!mutex_trylock(&apalis_tk1_k20->lock)) {
+ dev_dbg(apalis_tk1_k20->dev, "wait for %s from %ps\n",
+ __func__, __builtin_return_address(0));
+
+ mutex_lock(&apalis_tk1_k20->lock);
+ }
+ dev_dbg(apalis_tk1_k20->dev, "%s from %ps\n",
+ __func__, __builtin_return_address(0));
+}
+EXPORT_SYMBOL(apalis_tk1_k20_lock);
+
+void apalis_tk1_k20_unlock(struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ dev_dbg(apalis_tk1_k20->dev, "%s from %ps\n",
+ __func__, __builtin_return_address(0));
+ mutex_unlock(&apalis_tk1_k20->lock);
+}
+EXPORT_SYMBOL(apalis_tk1_k20_unlock);
+
+int apalis_tk1_k20_reg_read(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ unsigned int offset, u32 *val)
+{
+ int ret;
+
+ ret = regmap_read(apalis_tk1_k20->regmap, offset, val);
+ dev_dbg(apalis_tk1_k20->dev, "[0x%02x] -> 0x%02x ret = %d\n", offset,
+ *val, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_reg_read);
+
+int apalis_tk1_k20_reg_write(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ unsigned int offset, u32 val)
+{
+ int ret;
+
+ if (val >= BIT(24))
+ return -EINVAL;
+
+ ret = regmap_write(apalis_tk1_k20->regmap, offset, val);
+
+ dev_dbg(apalis_tk1_k20->dev, "[0x%02x] <- 0x%02x ret = %d\n", offset, val,
+ ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_reg_write);
+
+int apalis_tk1_k20_reg_read_bulk(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ unsigned int offset,
+ uint8_t *val, size_t size)
+{
+ int ret;
+
+ if (size > APALIS_TK1_K20_MAX_BULK)
+ return -EINVAL;
+
+ ret = regmap_bulk_read(apalis_tk1_k20->regmap, offset, val, size);
+ dev_dbg(apalis_tk1_k20->dev, "bulk read %d bytes from [0x%02x]\n",
+ size, offset);
+
+ return ret;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_reg_read_bulk);
+
+int apalis_tk1_k20_reg_write_bulk(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ unsigned int offset, uint8_t *buf, size_t size)
+{
+ dev_dbg(apalis_tk1_k20->dev, "bulk write %d bytes to [0x%02x]\n",
+ (unsigned int)size, offset);
+
+ if (size > APALIS_TK1_K20_MAX_BULK)
+ return -EINVAL;
+ return regmap_bulk_write(apalis_tk1_k20->regmap, offset, buf, size);
+}
+EXPORT_SYMBOL(apalis_tk1_k20_reg_write_bulk);
+
+int apalis_tk1_k20_reg_rmw(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ unsigned int offset, u32 mask, u32 val)
+{
+ dev_dbg(apalis_tk1_k20->dev, "[0x%02x] <- 0x%06x (mask: 0x%06x)\n",
+ offset, val, mask);
+
+ return regmap_update_bits(apalis_tk1_k20->regmap, offset, mask, val);
+}
+EXPORT_SYMBOL(apalis_tk1_k20_reg_rmw);
+
+int apalis_tk1_k20_irq_mask(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ int irq)
+{
+ int virq = -1;
+ if (irq != APALIS_TK1_K20_CAN1_IRQ && irq != APALIS_TK1_K20_CAN0_IRQ) {
+ virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq);
+
+ } else {
+ virq = (irq == APALIS_TK1_K20_CAN0_IRQ) ?
+ apalis_tk1_k20->can0_irq:apalis_tk1_k20->can1_irq;
+ }
+
+ disable_irq_nosync(virq);
+
+ return 0;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_irq_mask);
+
+int apalis_tk1_k20_irq_unmask(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ int irq)
+{
+ int virq = -1;
+ if (irq != APALIS_TK1_K20_CAN1_IRQ && irq != APALIS_TK1_K20_CAN0_IRQ) {
+ virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq);
+
+ } else {
+ virq = (irq == APALIS_TK1_K20_CAN0_IRQ) ?
+ apalis_tk1_k20->can0_irq:apalis_tk1_k20->can1_irq;
+ }
+
+ enable_irq(virq);
+
+ return 0;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_irq_unmask);
+
+int apalis_tk1_k20_irq_status(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ int irq, int *enabled, int *pending)
+{
+ int ret;
+ unsigned int offmask = APALIS_TK1_K20_MSQREG;
+ unsigned int offstat = APALIS_TK1_K20_IRQREG;
+ u32 irqbit = 1 << irq;
+
+ if (irq < 0 || irq >= ARRAY_SIZE(apalis_tk1_k20->irqs))
+ return -EINVAL;
+
+ if (enabled) {
+ u32 mask;
+
+ ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, offmask, &mask);
+ if (ret)
+ return ret;
+
+ *enabled = mask & irqbit;
+ }
+
+ if (pending) {
+ u32 stat;
+
+ ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, offstat, &stat);
+ if (ret)
+ return ret;
+
+ *pending = stat & irqbit;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_irq_status);
+
+int apalis_tk1_k20_irq_request(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ int irq, irq_handler_t handler, const char *name, void *dev)
+{
+ int virq = -1;
+ int irq_flags = IRQF_ONESHOT;
+ if (irq != APALIS_TK1_K20_CAN1_IRQ && irq != APALIS_TK1_K20_CAN0_IRQ) {
+ virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq);
+ irq_flags = IRQF_ONESHOT;
+ } else {
+ virq = (irq == APALIS_TK1_K20_CAN0_IRQ) ?
+ apalis_tk1_k20->can0_irq:apalis_tk1_k20->can1_irq;
+ irq_flags = IRQF_ONESHOT | IRQF_TRIGGER_HIGH;
+ }
+ return devm_request_threaded_irq(apalis_tk1_k20->dev, virq,
+ NULL, handler, irq_flags, name, dev);
+}
+EXPORT_SYMBOL(apalis_tk1_k20_irq_request);
+
+int apalis_tk1_k20_irq_free(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ int irq, void *dev)
+{
+ int virq = -1;
+ if (irq != APALIS_TK1_K20_CAN1_IRQ && irq != APALIS_TK1_K20_CAN0_IRQ) {
+ virq = regmap_irq_get_virq(apalis_tk1_k20->irq_data, irq);
+
+ } else {
+ virq = (irq == APALIS_TK1_K20_CAN0_IRQ) ?
+ apalis_tk1_k20->can0_irq:apalis_tk1_k20->can1_irq;
+ }
+
+ devm_free_irq(apalis_tk1_k20->dev, virq, dev);
+
+ return 0;
+}
+EXPORT_SYMBOL(apalis_tk1_k20_irq_free);
+
+
+static int apalis_tk1_k20_add_subdevice_pdata_id(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20, const char *name,
+ void *pdata, size_t pdata_size, int id)
+{
+ struct mfd_cell cell = {
+ .platform_data = pdata,
+ .pdata_size = pdata_size,
+ };
+
+ cell.name = kmemdup(name, strlen(name) + 1, GFP_KERNEL);
+ if (!cell.name)
+ return -ENOMEM;
+
+ return mfd_add_devices(apalis_tk1_k20->dev, id, &cell, 1, NULL, 0,
+ regmap_irq_get_domain(apalis_tk1_k20->irq_data));
+}
+
+static int apalis_tk1_k20_add_subdevice_pdata(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20, const char *name,
+ void *pdata, size_t pdata_size)
+{
+ return apalis_tk1_k20_add_subdevice_pdata_id(apalis_tk1_k20, name,
+ pdata, pdata_size, -1);
+}
+
+static int apalis_tk1_k20_add_subdevice(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20, const char *name)
+{
+ return apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20, name, NULL,
+ 0);
+}
+
+static void apalis_tk1_k20_reset_chip(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ udelay(10);
+ gpio_set_value(apalis_tk1_k20->reset_gpio, 0);
+ msleep(10);
+ gpio_set_value(apalis_tk1_k20->reset_gpio, 1);
+ udelay(10);
+}
+
+#ifdef CONFIG_APALIS_TK1_K20_EZP
+static int apalis_tk1_k20_read_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20, uint8_t command,
+ int addr, int count, uint8_t *buffer)
+{
+ uint8_t w[4 + APALIS_TK1_K20_EZP_MAX_DATA];
+ uint8_t r[4 + APALIS_TK1_K20_EZP_MAX_DATA];
+ uint8_t *out;
+ struct spi_message m;
+ struct spi_device *spi = to_spi_device(apalis_tk1_k20->dev);
+ struct spi_transfer t = {
+ .tx_buf = w,
+ .rx_buf = r,
+ .speed_hz = APALIS_TK1_K20_EZP_MAX_SPEED,
+ };
+ int ret;
+
+ spi->mode = SPI_MODE_0;
+
+ if (count > APALIS_TK1_K20_EZP_MAX_DATA)
+ return -ENOSPC;
+
+ memset(w, 0, 4 + count);
+
+ switch (command) {
+ case APALIS_TK1_K20_EZP_READ:
+ case APALIS_TK1_K20_EZP_FREAD:
+ t.len = 4 + count;
+ w[1] = (addr & 0xFF0000) >> 16;
+ w[2] = (addr & 0xFF00) >> 8;
+ w[3] = (addr & 0xFC);
+ out = &r[4];
+ break;
+ case APALIS_TK1_K20_EZP_RDSR:
+ case APALIS_TK1_K20_EZP_FRDFCOOB:
+ t.len = 1 + count;
+ out = &r[1];
+ break;
+ default:
+ return -EINVAL;
+ }
+ w[0] = command;
+
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 0);
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync(spi, &m);
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 1);
+ if (ret != 0)
+ return ret;
+
+ memcpy(buffer, out, count);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_write_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20, uint8_t command,
+ int addr, int count, const uint8_t *buffer)
+{
+ uint8_t w[4 + APALIS_TK1_K20_EZP_MAX_DATA];
+ uint8_t r[4 + APALIS_TK1_K20_EZP_MAX_DATA];
+ struct spi_message m;
+ struct spi_device *spi = to_spi_device(apalis_tk1_k20->dev);
+ struct spi_transfer t = {
+ .tx_buf = w,
+ .rx_buf = r,
+ .speed_hz = APALIS_TK1_K20_EZP_MAX_SPEED,
+ };
+ int ret;
+
+ spi->mode = SPI_MODE_0;
+
+ if (count > APALIS_TK1_K20_EZP_MAX_DATA)
+ return -ENOSPC;
+
+ switch (command) {
+ case APALIS_TK1_K20_EZP_SP:
+ case APALIS_TK1_K20_EZP_SE:
+ t.len = 4 + count;
+ w[1] = (addr & 0xFF0000) >> 16;
+ w[2] = (addr & 0xFF00) >> 8;
+ w[3] = (addr & 0xF8);
+ memcpy(&w[4], buffer, count);
+ break;
+ case APALIS_TK1_K20_EZP_WREN:
+ case APALIS_TK1_K20_EZP_WRDI:
+ case APALIS_TK1_K20_EZP_BE:
+ case APALIS_TK1_K20_EZP_RESET:
+ case APALIS_TK1_K20_EZP_WRFCCOB:
+ t.len = 1 + count;
+ memcpy(&w[1], buffer, count);
+ break;
+ default:
+ return -EINVAL;
+ }
+ w[0] = command;
+
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 0);
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ ret = spi_sync(spi, &m);
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 1);
+
+ return ret;
+}
+
+static int apalis_tk1_k20_set_wren_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ uint8_t buffer;
+
+ if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_WREN,
+ 0, 0, NULL) < 0)
+ return -EIO;
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR,
+ 0, 1, &buffer) < 0)
+ return -EIO;
+ if ((buffer & APALIS_TK1_K20_EZP_STA_WEN))
+ return 0;
+
+ /* If it failed try one last time */
+ if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_WREN,
+ 0, 0, NULL) < 0)
+ return -EIO;
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR,
+ 0, 1, &buffer) < 0)
+ return -EIO;
+ if ((buffer & APALIS_TK1_K20_EZP_STA_WEN))
+ return 0;
+
+ return -EIO;
+
+}
+
+static int apalis_tk1_k20_enter_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ uint8_t status = 0x00;
+ uint8_t buffer;
+
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 0);
+ msleep(10);
+ apalis_tk1_k20_reset_chip(apalis_tk1_k20);
+ msleep(10);
+ gpio_set_value(apalis_tk1_k20->ezpcs_gpio, 1);
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR,
+ 0, 1, &buffer) < 0)
+ goto bad;
+ status = buffer;
+ if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_WREN,
+ 0, 0, NULL) < 0)
+ goto bad;
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR,
+ 0, 1, &buffer) < 0)
+ goto bad;
+ if ((buffer & APALIS_TK1_K20_EZP_STA_WEN) && buffer != status)
+ return 0;
+
+bad:
+ dev_err(apalis_tk1_k20->dev, "Error entering EZ Port mode.\n");
+ return -EIO;
+}
+
+static int apalis_tk1_k20_erase_chip_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ uint8_t buffer[16];
+ int i;
+
+ if (apalis_tk1_k20_set_wren_ezport(apalis_tk1_k20))
+ goto bad;
+ if (apalis_tk1_k20_write_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_BE,
+ 0, 0, NULL) < 0)
+ goto bad;
+
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20, APALIS_TK1_K20_EZP_RDSR,
+ 0, 1, buffer) < 0)
+ goto bad;
+
+ i = 0;
+ while (buffer[0] & APALIS_TK1_K20_EZP_STA_WIP) {
+ msleep(20);
+ if ((apalis_tk1_k20_read_ezport(apalis_tk1_k20,
+ APALIS_TK1_K20_EZP_RDSR, 0, 1, buffer) < 0) || (i > 50))
+ goto bad;
+ i++;
+ }
+
+ return 0;
+
+bad:
+ dev_err(apalis_tk1_k20->dev, "Error erasing the chip.\n");
+ return -EIO;
+}
+
+static int apalis_tk1_k20_flash_chip_ezport(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ uint8_t buffer;
+ const uint8_t *fw_chunk;
+ int i, j, transfer_size;
+
+ for (i = 0; i < fw_entry->size;) {
+ if (apalis_tk1_k20_set_wren_ezport(apalis_tk1_k20))
+ goto bad;
+
+ fw_chunk = fw_entry->data + i;
+ transfer_size = (i + APALIS_TK1_K20_EZP_WRITE_SIZE <
+ fw_entry->size) ? APALIS_TK1_K20_EZP_WRITE_SIZE
+ : (fw_entry->size - i - 1);
+ dev_dbg(apalis_tk1_k20->dev,
+ "Apalis TK1 K20 MFD transfer_size = %d addr = 0x%X\n",
+ transfer_size, i);
+ if (apalis_tk1_k20_write_ezport(apalis_tk1_k20,
+ APALIS_TK1_K20_EZP_SP, i,
+ transfer_size, fw_chunk) < 0)
+ goto bad;
+ udelay(2000);
+ if (apalis_tk1_k20_read_ezport(apalis_tk1_k20,
+ APALIS_TK1_K20_EZP_RDSR, 0, 1, &buffer) < 0)
+ goto bad;
+
+ j = 0;
+ while (buffer & APALIS_TK1_K20_EZP_STA_WIP) {
+ msleep(10);
+ if ((apalis_tk1_k20_read_ezport(apalis_tk1_k20,
+ APALIS_TK1_K20_EZP_RDSR, 0, 1,
+ &buffer) < 0) || (j > 10000))
+ goto bad;
+ j++;
+ }
+ i += APALIS_TK1_K20_EZP_WRITE_SIZE;
+ }
+
+ return 0;
+
+bad:
+ dev_err(apalis_tk1_k20->dev, "Error writing to the chip.\n");
+ return -EIO;
+}
+
+static uint8_t apalis_tk1_k20_fw_ezport_status(void)
+{
+ return fw_entry->data[APALIS_TK1_K20_FW_FOPT_ADDR] &
+ APALIS_TK1_K20_FOPT_EZP_ENA;
+}
+
+static uint8_t apalis_tk1_k20_get_fw_revision(void)
+{
+ if (fw_entry)
+ if (fw_entry->size > APALIS_TK1_K20_FW_VER_ADDR)
+ return fw_entry->data[APALIS_TK1_K20_FW_VER_ADDR];
+ return 0;
+}
+#endif /* CONFIG_APALIS_TK1_K20_EZP */
+
+
+#ifdef CONFIG_OF
+static int apalis_tk1_k20_probe_flags_dt(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ struct device_node *np = apalis_tk1_k20->dev->of_node;
+
+ if (!np)
+ return -ENODEV;
+
+ if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-adc"))
+ apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_ADC;
+
+ if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-tsc"))
+ apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_TSC;
+
+ if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-can"))
+ apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_CAN;
+
+ if (of_property_read_bool(np, "toradex,apalis-tk1-k20-uses-gpio"))
+ apalis_tk1_k20->flags |= APALIS_TK1_K20_USES_GPIO;
+
+ return 0;
+}
+
+static int apalis_tk1_k20_probe_gpios_dt(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ struct device_node *np = apalis_tk1_k20->dev->of_node;
+
+ if (!np)
+ return -ENODEV;
+
+ apalis_tk1_k20->reset_gpio = of_get_named_gpio(np, "rst-gpio", 0);
+ if (apalis_tk1_k20->reset_gpio < 0)
+ return apalis_tk1_k20->reset_gpio;
+
+ gpio_request(apalis_tk1_k20->reset_gpio, "apalis-tk1-k20-reset");
+ gpio_direction_output(apalis_tk1_k20->reset_gpio, 1);
+
+ apalis_tk1_k20->ezpcs_gpio = of_get_named_gpio(np, "ezport-cs-gpio", 0);
+ if (apalis_tk1_k20->ezpcs_gpio < 0)
+ return apalis_tk1_k20->ezpcs_gpio;
+
+ gpio_request(apalis_tk1_k20->ezpcs_gpio, "apalis-tk1-k20-ezpcs");
+ gpio_direction_output(apalis_tk1_k20->ezpcs_gpio, 1);
+
+ apalis_tk1_k20->int2_gpio = of_get_named_gpio(np, "int2-gpio", 0);
+ if (apalis_tk1_k20->int2_gpio < 0)
+ return apalis_tk1_k20->int2_gpio;
+
+ gpio_request(apalis_tk1_k20->int2_gpio, "apalis-tk1-k20-int2");
+ gpio_direction_output(apalis_tk1_k20->int2_gpio, 1);
+
+ return 0;
+}
+#else
+static inline int apalis_tk1_k20_probe_flags_dt(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ return -ENODEV;
+}
+static inline int apalis_tk1_k20_probe_gpios_dt(
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20)
+{
+ return -ENODEV;
+}
+#endif
+
+int apalis_tk1_k20_fw_update(struct apalis_tk1_k20_regmap *apalis_tk1_k20,
+ uint32_t revision)
+{
+ int erase_only = 0;
+
+ if ((request_firmware(&fw_entry, "apalis-tk1-k20.bin",
+ apalis_tk1_k20->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));
+ return -ENOTSUPP;
+ }
+
+ 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));
+ return -ENOTSUPP;
+ }
+
+ if (fw_entry != NULL) {
+ if (fw_entry->size == 1)
+ erase_only = 1;
+ }
+
+ if ((apalis_tk1_k20_get_fw_revision() != APALIS_TK1_K20_FW_VER)
+ && (revision != APALIS_TK1_K20_FW_VER) && !erase_only
+ && (fw_entry != NULL)) {
+ dev_err(apalis_tk1_k20->dev,
+ "Unsupported firmware version in both the device "
+ "as well as the local firmware file.\n");
+ release_firmware(fw_entry);
+ return -ENOTSUPP;
+ }
+
+ if ((revision != APALIS_TK1_K20_FW_VER) && !erase_only
+ && (!apalis_tk1_k20_fw_ezport_status()) && (fw_entry != NULL)) {
+ dev_err(apalis_tk1_k20->dev,
+ "Unsupported firmware version in the device and the "
+ "local firmware file disables the EZ Port.\n");
+ release_firmware(fw_entry);
+ return -ENOTSUPP;
+ }
+
+ if (((revision != APALIS_TK1_K20_FW_VER) || erase_only
+ || force_fw_reload)
+ && (fw_entry != NULL)) {
+ int 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);
+ return -EIO;
+ }
+ if (apalis_tk1_k20_erase_chip_ezport(apalis_tk1_k20) < 0) {
+ dev_err(apalis_tk1_k20->dev,
+ "Problem erasing the chip. Deferring...\n");
+ release_firmware(fw_entry);
+ return -EPROBE_DEFER;
+ }
+ if (erase_only) {
+ dev_err(apalis_tk1_k20->dev, "Chip fully erased.\n");
+ release_firmware(fw_entry);
+ return -EIO;
+ }
+ if (apalis_tk1_k20_flash_chip_ezport(apalis_tk1_k20) < 0) {
+ dev_err(apalis_tk1_k20->dev,
+ "Problem flashing new firmware. Deferring...\n");
+ release_firmware(fw_entry);
+ return -EPROBE_DEFER;
+ }
+ }
+
+ if (fw_entry != NULL)
+ release_firmware(fw_entry);
+
+ return 1;
+}
+
+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;
+
+ apalis_tk1_k20->dev = dev;
+
+ ret = apalis_tk1_k20_probe_gpios_dt(apalis_tk1_k20);
+ if ((ret < 0) && pdata) {
+ if (pdata) {
+ apalis_tk1_k20->ezpcs_gpio = pdata->ezpcs_gpio;
+ apalis_tk1_k20->reset_gpio = pdata->reset_gpio;
+ apalis_tk1_k20->int2_gpio = pdata->int2_gpio;
+ } else {
+ dev_err(dev, "Error claiming GPIOs\n");
+ ret = -EINVAL;
+ goto bad;
+ }
+ }
+ apalis_tk1_k20_reset_chip(apalis_tk1_k20);
+ msleep(10);
+ ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, APALIS_TK1_K20_REVREG,
+ &revision);
+
+#ifdef CONFIG_APALIS_TK1_K20_EZP
+
+ if (fw_ignore == 0) {
+ ret = apalis_tk1_k20_fw_update(apalis_tk1_k20, revision);
+
+ if (ret < 0)
+ goto bad;
+ }
+ if (ret) {
+ msleep(10);
+ apalis_tk1_k20_reset_chip(apalis_tk1_k20);
+ msleep(10);
+
+ ret = apalis_tk1_k20_reg_read(apalis_tk1_k20, APALIS_TK1_K20_REVREG,
+ &revision);
+ }
+#endif /* CONFIG_APALIS_TK1_K20_EZP */
+
+ if (ret) {
+ dev_err(apalis_tk1_k20->dev, "Device is not answering.\n");
+ goto bad;
+ }
+
+ if ((revision != APALIS_TK1_K20_FW_VER) && (fw_ignore == 0)) {
+ dev_err(apalis_tk1_k20->dev,
+ "Unsupported firmware version %d.%d.\n",
+ ((revision & 0xF0) >> 4), (revision & 0x0F));
+ ret = -ENOTSUPP;
+ goto bad;
+ }
+
+ if (fw_ignore == 1) {
+ dev_err(apalis_tk1_k20->dev, "fw_ignore == 1. Detected "
+ "firmware %d.%d. Driver expected %d.%d\n",
+ ((revision & 0xF0) >> 4), (revision & 0x0F),
+ ((APALIS_TK1_K20_FW_VER & 0xF0) >> 4),
+ (APALIS_TK1_K20_FW_VER & 0x0F));
+ }
+
+ for (i = 0; i < ARRAY_SIZE(apalis_tk1_k20->irqs); i++) {
+ apalis_tk1_k20->irqs[i].reg_offset = i /
+ APALIS_TK1_K20_IRQ_PER_REG;
+ apalis_tk1_k20->irqs[i].mask = BIT(i %
+ APALIS_TK1_K20_IRQ_PER_REG);
+ }
+
+ apalis_tk1_k20->irq_chip.name = dev_name(dev);
+ apalis_tk1_k20->irq_chip.status_base = APALIS_TK1_K20_IRQREG;
+ apalis_tk1_k20->irq_chip.mask_base = APALIS_TK1_K20_MSQREG;
+ apalis_tk1_k20->irq_chip.ack_base = 0;
+ apalis_tk1_k20->irq_chip.irq_reg_stride = 0;
+ apalis_tk1_k20->irq_chip.num_regs = APALIS_TK1_K20_IRQ_REG_CNT;
+ apalis_tk1_k20->irq_chip.irqs = apalis_tk1_k20->irqs;
+ apalis_tk1_k20->irq_chip.num_irqs = ARRAY_SIZE(apalis_tk1_k20->irqs);
+
+ ret = regmap_add_irq_chip(apalis_tk1_k20->regmap, apalis_tk1_k20->irq,
+ IRQF_ONESHOT | IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING, 0, &apalis_tk1_k20->irq_chip,
+ &apalis_tk1_k20->irq_data);
+ if (ret)
+ goto bad;
+
+ mutex_init(&apalis_tk1_k20->lock);
+
+ if (apalis_tk1_k20_probe_flags_dt(apalis_tk1_k20) < 0 && pdata)
+ apalis_tk1_k20->flags = pdata->flags;
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_CAN) {
+ apalis_tk1_k20->can0_irq = irq_of_parse_and_map(
+ apalis_tk1_k20->dev->of_node, 1);
+ apalis_tk1_k20->can1_irq = irq_of_parse_and_map(
+ apalis_tk1_k20->dev->of_node, 2);
+ if (apalis_tk1_k20->can0_irq == 0 ||
+ apalis_tk1_k20->can1_irq == 0) {
+ apalis_tk1_k20->flags &= ~APALIS_TK1_K20_USES_CAN;
+ dev_err(apalis_tk1_k20->dev,
+ "Missing CAN interrupts.\n");
+ }
+ }
+
+ if (pdata) {
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_TSC)
+ apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20,
+ "apalis-tk1-k20-ts",
+ &pdata->touch, sizeof(pdata->touch));
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_ADC)
+ apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20,
+ "apalis-tk1-k20-adc",
+ &pdata->adc, sizeof(pdata->adc));
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_CAN) {
+ /* We have 2 CAN devices inside K20 */
+ pdata->can0.id = 0;
+ pdata->can1.id = 1;
+ apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20,
+ "apalis-tk1-k20-can",
+ &pdata->can0, sizeof(pdata->can0));
+ apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20,
+ "apalis-tk1-k20-can",
+ &pdata->can1, sizeof(pdata->can1));
+ }
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_GPIO)
+ apalis_tk1_k20_add_subdevice_pdata(apalis_tk1_k20,
+ "apalis-tk1-k20-gpio",
+ &pdata->gpio, sizeof(pdata->gpio));
+ } else {
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_TSC)
+ apalis_tk1_k20_add_subdevice(apalis_tk1_k20,
+ "apalis-tk1-k20-ts");
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_ADC)
+ apalis_tk1_k20_add_subdevice(apalis_tk1_k20,
+ "apalis-tk1-k20-adc");
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_CAN) {
+ /* We have 2 CAN devices inside K20 */
+ apalis_tk1_k20_add_subdevice_pdata_id(apalis_tk1_k20,
+ "apalis-tk1-k20-can",
+ NULL, 0, 0);
+ apalis_tk1_k20_add_subdevice_pdata_id(apalis_tk1_k20,
+ "apalis-tk1-k20-can",
+ NULL, 0, 1);
+ }
+
+ if (apalis_tk1_k20->flags & APALIS_TK1_K20_USES_GPIO)
+ apalis_tk1_k20_add_subdevice(apalis_tk1_k20,
+ "apalis-tk1-k20-gpio");
+ }
+
+ dev_info(apalis_tk1_k20->dev, "Apalis TK1 K20 MFD driver. "
+ "Firmware version %d.%d.\n", FW_MAJOR, FW_MINOR);
+
+ return 0;
+
+bad:
+ if (apalis_tk1_k20->ezpcs_gpio >= 0)
+ gpio_free(apalis_tk1_k20->ezpcs_gpio);
+ if (apalis_tk1_k20->reset_gpio >= 0)
+ gpio_free(apalis_tk1_k20->reset_gpio);
+ if (apalis_tk1_k20->int2_gpio >= 0)
+ gpio_free(apalis_tk1_k20->int2_gpio);
+
+ return ret;
+}
+
+
+static int apalis_tk1_k20_spi_probe(struct spi_device *spi)
+{
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20;
+ int ret;
+
+ apalis_tk1_k20 = devm_kzalloc(&spi->dev, sizeof(*apalis_tk1_k20),
+ GFP_KERNEL);
+ if (!apalis_tk1_k20)
+ return -ENOMEM;
+
+ dev_set_drvdata(&spi->dev, apalis_tk1_k20);
+
+ spi->mode = SPI_MODE_1;
+
+ apalis_tk1_k20->irq = spi->irq;
+
+ spi->max_speed_hz = (spi->max_speed_hz >= APALIS_TK1_K20_MAX_SPI_SPEED)
+ ? APALIS_TK1_K20_MAX_SPI_SPEED : spi->max_speed_hz;
+
+ ret = spi_setup(spi);
+ if (ret)
+ return ret;
+
+ apalis_tk1_k20->regmap = devm_regmap_init(&spi->dev,
+ &regmap_apalis_tk1_k20_bus, &spi->dev,
+ &apalis_tk1_k20_regmap_spi_config);
+ if (IS_ERR(apalis_tk1_k20->regmap)) {
+ ret = PTR_ERR(apalis_tk1_k20->regmap);
+ dev_err(&spi->dev, "Failed to initialize regmap: %d\n", ret);
+ return ret;
+ }
+
+ return apalis_tk1_k20_dev_init(&spi->dev);
+}
+
+static int apalis_tk1_k20_spi_remove(struct spi_device *spi)
+{
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20 =
+ dev_get_drvdata(&spi->dev);
+
+ if (apalis_tk1_k20->ezpcs_gpio >= 0)
+ gpio_free(apalis_tk1_k20->ezpcs_gpio);
+ if (apalis_tk1_k20->reset_gpio >= 0)
+ gpio_free(apalis_tk1_k20->reset_gpio);
+ if (apalis_tk1_k20->int2_gpio >= 0)
+ gpio_free(apalis_tk1_k20->int2_gpio);
+
+ kfree(spi->controller_data);
+ spi->controller_data = NULL;
+
+ mfd_remove_devices(&spi->dev);
+ regmap_del_irq_chip(apalis_tk1_k20->irq, apalis_tk1_k20->irq_data);
+ mutex_destroy(&apalis_tk1_k20->lock);
+
+ return 0;
+}
+
+static struct spi_driver apalis_tk1_k20_spi_driver = {
+ .id_table = apalis_tk1_k20_device_ids,
+ .driver = {
+ .name = "apalis-tk1-k20",
+ .of_match_table = apalis_tk1_k20_dt_ids,
+ },
+ .probe = apalis_tk1_k20_spi_probe,
+ .remove = apalis_tk1_k20_spi_remove,
+};
+
+static int __init apalis_tk1_k20_init(void)
+{
+ return spi_register_driver(&apalis_tk1_k20_spi_driver);
+}
+subsys_initcall(apalis_tk1_k20_init);
+
+static void __exit apalis_tk1_k20_exit(void)
+{
+ spi_unregister_driver(&apalis_tk1_k20_spi_driver);
+}
+module_exit(apalis_tk1_k20_exit);
+
+MODULE_DESCRIPTION("MFD driver for Kinetis MK20DN512 MCU on Apalis TK1");
+MODULE_AUTHOR("Dominik Sliwa <dominik.sliwa@toradex.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c
index 7f758fb60c1f..92b24cd00d35 100644
--- a/drivers/mfd/stmpe.c
+++ b/drivers/mfd/stmpe.c
@@ -1357,7 +1357,7 @@ static void stmpe_of_probe(struct stmpe_platform_data *pdata,
pdata->autosleep = (pdata->autosleep_timeout) ? true : false;
- for_each_child_of_node(np, child) {
+ for_each_available_child_of_node(np, child) {
if (of_node_name_eq(child, "stmpe_gpio")) {
pdata->blocks |= STMPE_BLOCK_GPIO;
} else if (of_node_name_eq(child, "stmpe_keypad")) {
diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c
index 010a6dc0e7cf..458a4408a724 100644
--- a/drivers/mmc/core/block.c
+++ b/drivers/mmc/core/block.c
@@ -2245,8 +2245,10 @@ enum mmc_issued mmc_blk_mq_issue_rq(struct mmc_queue *mq, struct request *req)
}
ret = mmc_blk_cqe_issue_flush(mq, req);
break;
- case REQ_OP_READ:
case REQ_OP_WRITE:
+ card->written_flag = true;
+ fallthrough;
+ case REQ_OP_READ:
if (mq->use_cqe)
ret = mmc_blk_cqe_issue_rw_rq(mq, req);
else
diff --git a/drivers/mmc/core/card.h b/drivers/mmc/core/card.h
index 7bd392d55cfa..eaccbf797a53 100644
--- a/drivers/mmc/core/card.h
+++ b/drivers/mmc/core/card.h
@@ -222,4 +222,8 @@ static inline int mmc_card_broken_hpi(const struct mmc_card *c)
return c->quirks & MMC_QUIRK_BROKEN_HPI;
}
+static inline int mmc_card_broken_cache_flush(const struct mmc_card *c)
+{
+ return c->quirks & MMC_QUIRK_BROKEN_CACHE_FLUSH;
+}
#endif
diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
index d495ba2f368c..7e60fd0361f4 100644
--- a/drivers/mmc/core/mmc_ops.c
+++ b/drivers/mmc/core/mmc_ops.c
@@ -959,13 +959,17 @@ int mmc_flush_cache(struct mmc_card *card)
{
int err = 0;
+ if (mmc_card_broken_cache_flush(card) && !card->written_flag)
+ return 0;
+
if (mmc_cache_enabled(card->host)) {
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_FLUSH_CACHE, 1,
MMC_CACHE_FLUSH_TIMEOUT_MS);
if (err)
- pr_err("%s: cache flush error %d\n",
- mmc_hostname(card->host), err);
+ pr_err("%s: cache flush error %d\n", mmc_hostname(card->host), err);
+ else
+ card->written_flag = false;
}
return err;
diff --git a/drivers/mmc/core/quirks.h b/drivers/mmc/core/quirks.h
index 9a253324c95a..5e0e6c5eea81 100644
--- a/drivers/mmc/core/quirks.h
+++ b/drivers/mmc/core/quirks.h
@@ -113,6 +113,14 @@ static const struct mmc_fixup mmc_blk_fixups[] = {
MMC_FIXUP("V10016", CID_MANFID_KINGSTON, CID_OEMID_ANY, add_quirk_mmc,
MMC_QUIRK_TRIM_BROKEN),
+ /*
+ * Micron MTFC4GACAJCN-1M supports TRIM but does not appear to support
+ * WRITE_ZEROES offloading. It also supports caching, but the cache can
+ * only be flushed after a write has occurred.
+ */
+ MMC_FIXUP("Q2J54A", CID_MANFID_MICRON, 0x014e, add_quirk_mmc,
+ MMC_QUIRK_TRIM_BROKEN | MMC_QUIRK_BROKEN_CACHE_FLUSH),
+
END_FIXUP
};
diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c
index d58383767a6e..b6aa22e72127 100644
--- a/drivers/mmc/host/sdhci-tegra.c
+++ b/drivers/mmc/host/sdhci-tegra.c
@@ -50,6 +50,7 @@
#define SDHCI_MISC_CTRL_ENABLE_SDR50 0x10
#define SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300 0x20
#define SDHCI_MISC_CTRL_ENABLE_DDR50 0x200
+#define SDHCI_MISC_CTRL_ENABLE_EXT_LOOPBACK 0x20000
#define SDHCI_TEGRA_VENDOR_DLLCAL_CFG 0x1b0
#define SDHCI_TEGRA_DLLCAL_CALIBRATE BIT(31)
@@ -383,6 +384,16 @@ static void tegra_sdhci_reset(struct sdhci_host *host, u8 mask)
clk_ctrl |= tegra_host->default_trim << SDHCI_CLOCK_CTRL_TRIM_SHIFT;
+#define CONFIG_MACH_APALIS_TK1
+#ifdef CONFIG_MACH_APALIS_TK1
+ /*
+ * Disable the external loopback and use the internal loopback as per
+ * SDMMC_VENDOR_MISC_CNTRL_0 register's SDMMC_SPARE1 bits being set to
+ * 0xfffd according to the TRM.
+ */
+ misc_ctrl &= ~SDHCI_MISC_CTRL_ENABLE_EXT_LOOPBACK;
+#endif /* CONFIG_MACH_APALIS_TK1 */
+
sdhci_writel(host, misc_ctrl, SDHCI_TEGRA_VENDOR_MISC_CTRL);
sdhci_writel(host, clk_ctrl, SDHCI_TEGRA_VENDOR_CLOCK_CTRL);
@@ -1369,6 +1380,10 @@ static const struct sdhci_pltfm_data sdhci_tegra124_pdata = {
static const struct sdhci_tegra_soc_data soc_data_tegra124 = {
.pdata = &sdhci_tegra124_pdata,
.dma_mask = DMA_BIT_MASK(34),
+ .nvquirks = NVQUIRK_ENABLE_SDR50 |
+ NVQUIRK_ENABLE_DDR50 |
+ NVQUIRK_ENABLE_SDR104 |
+ NVQUIRK_HAS_PADCALIB,
};
static const struct sdhci_ops tegra210_sdhci_ops = {
diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index e4d944770cca..385a786c4386 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -88,6 +88,12 @@ config CAN_LEDS
Say Y here if you are working on a system with led-class supported
LEDs and you want to use them as canbus activity indicators.
+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_AT91
tristate "Atmel AT91 onchip CAN controller"
depends on (ARCH_AT91 || COMPILE_TEST) && HAS_IOMEM
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
index a2b4463d8480..add91bcc100e 100644
--- a/drivers/net/can/Makefile
+++ b/drivers/net/can/Makefile
@@ -13,6 +13,7 @@ obj-y += spi/
obj-y += usb/
obj-y += softing/
+obj-$(CONFIG_CAN_APALIS_TK1_K20) += apalis-tk1-k20-can.o
obj-$(CONFIG_CAN_AT91) += at91_can.o
obj-$(CONFIG_CAN_CC770) += cc770/
obj-$(CONFIG_CAN_C_CAN) += c_can/
diff --git a/drivers/net/can/apalis-tk1-k20-can.c b/drivers/net/can/apalis-tk1-k20-can.c
new file mode 100644
index 000000000000..4a66999e1435
--- /dev/null
+++ b/drivers/net/can/apalis-tk1-k20-can.c
@@ -0,0 +1,867 @@
+/*
+ * Copyright 2016-2017 Toradex AG
+ * Dominik Sliwa <dominik.sliwa@toradex.com>
+ *
+ * CAN bus driver for Apalis TK1 K20 CAN Controller over MFD device
+ * based on MCP251x CAN driver
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+
+#include <linux/can/core.h>
+#include <linux/can/dev.h>
+#include <linux/can/led.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/freezer.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/apalis-tk1-k20.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+/* Buffer size required for the largest transfer (i.e., reading a
+ * frame)
+ */
+#define CAN_FRAME_MAX_LEN 8
+#define CAN_HEADER_MAX_LEN 5
+#define CAN_TRANSFER_BUF_LEN (CAN_HEADER_MAX_LEN + CAN_FRAME_MAX_LEN)
+
+#define MB_DLC_OFF 4
+#define MB_EID_OFF 0
+#define MB_RTR_SHIFT 4
+#define MB_IDE_SHIFT 5
+#define MB_DLC_MASK 0xF
+#define MB_EID_LEN 4
+
+#define CANCTRL_MODMASK (BIT(1) | BIT(0))
+#define CANCTRL_INTEN BIT(2)
+#define CANINTF_RX BIT(3)
+#define CANINTF_TX BIT(4)
+#define CANINTF_ERR BIT(5)
+#define CANCTRL_ENABLE BIT(6)
+#define CANCTRL_INTMASK (CANINTF_RX | CANINTF_TX | CANINTF_ERR)
+
+#define EFLG_EWARN 0x01
+#define EFLG_RXWAR 0x02
+#define EFLG_TXWAR 0x04
+#define EFLG_RXEP 0x08
+#define EFLG_TXEP 0x10
+#define EFLG_TXBO 0x20
+#define EFLG_RXOVR 0x40
+
+#define TX_ECHO_SKB_MAX 1
+
+#define K20_CAN_MAX_ID 1
+
+#define DEVICE_NAME "apalis-tk1-k20-can"
+
+static const struct can_bittiming_const apalis_tk1_k20_can_bittiming_const = {
+ .name = "tk1-k20-can",
+ .tseg1_min = 3,
+ .tseg1_max = 16,
+ .tseg2_min = 2,
+ .tseg2_max = 8,
+ .sjw_max = 4,
+ .brp_min = 1,
+ .brp_max = 64,
+ .brp_inc = 1,
+};
+
+struct apalis_tk1_k20_priv {
+ struct can_priv can;
+ struct net_device *net;
+ struct apalis_tk1_k20_regmap *apalis_tk1_k20;
+ struct apalis_tk1_k20_can_platform_data *pdata;
+
+ struct sk_buff *tx_skb;
+ int tx_len;
+
+ struct workqueue_struct *wq;
+ struct work_struct tx_work;
+ struct work_struct restart_work;
+ struct mutex apalis_tk1_k20_can_lock;
+
+ int force_quit;
+ int after_suspend;
+#define AFTER_SUSPEND_UP 1
+#define AFTER_SUSPEND_DOWN 2
+#define AFTER_SUSPEND_RESTART 4
+ int restart_tx;
+ int tx_frame;
+};
+
+static void apalis_tk1_k20_can_clean(struct net_device *net)
+{
+ struct apalis_tk1_k20_priv *priv = netdev_priv(net);
+
+ if (priv->tx_skb || priv->tx_len)
+ net->stats.tx_errors++;
+ if (priv->tx_skb)
+ dev_kfree_skb(priv->tx_skb);
+ if (priv->tx_len)
+ can_free_echo_skb(priv->net, 0);
+ priv->tx_skb = NULL;
+ priv->tx_len = 0;
+}
+
+static void apalis_tk1_k20_can_hw_tx_frame(struct net_device *net, u8 *buf,
+ int len, int tx_buf_idx)
+{
+ /* TODO: Implement multiple TX buffer handling */
+ struct apalis_tk1_k20_priv *priv = netdev_priv(net);
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+ apalis_tk1_k20_reg_write_bulk(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN_OUT_BUF
+ + APALIS_TK1_K20_CAN_DEV_OFFSET(
+ priv->pdata->id), buf, len);
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+}
+
+static void apalis_tk1_k20_can_hw_tx(struct net_device *net,
+ struct can_frame *frame, int tx_buf_idx)
+{
+ u8 buf[CAN_TRANSFER_BUF_LEN];
+
+ buf[MB_DLC_OFF] = frame->can_dlc;
+ memcpy(buf + MB_EID_OFF, &frame->can_id, MB_EID_LEN);
+ memcpy(buf + CAN_HEADER_MAX_LEN, frame->data, frame->can_dlc);
+
+ apalis_tk1_k20_can_hw_tx_frame(net, buf, frame->can_dlc
+ + CAN_HEADER_MAX_LEN, tx_buf_idx);
+}
+
+static int apalis_tk1_k20_can_hw_rx(struct net_device *net, int buf_idx)
+{
+ int i = 0;
+ struct apalis_tk1_k20_priv *priv = netdev_priv(net);
+ struct sk_buff *skb;
+ struct can_frame *frame;
+ u8 buf[CAN_TRANSFER_BUF_LEN * APALIS_TK1_MAX_CAN_DMA_XREF];
+ u32 frame_available = 0;
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+ apalis_tk1_k20_reg_read(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN_IN_BUF_CNT
+ + APALIS_TK1_K20_CAN_DEV_OFFSET(
+ priv->pdata->id), &frame_available);
+ frame_available = min(frame_available, APALIS_TK1_MAX_CAN_DMA_XREF);
+ apalis_tk1_k20_reg_read_bulk(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN_IN_BUF
+ + APALIS_TK1_K20_CAN_DEV_OFFSET(
+ priv->pdata->id), buf,
+ CAN_TRANSFER_BUF_LEN * frame_available);
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+
+ for (i = 0; i < frame_available; i++) {
+ skb = alloc_can_skb(priv->net, &frame);
+ if (!skb) {
+ dev_err(&net->dev, "cannot allocate RX skb\n");
+ priv->net->stats.rx_dropped++;
+ return -ENOMEM;
+ }
+ memcpy(&frame->can_id, &buf[i * CAN_TRANSFER_BUF_LEN]
+ + MB_EID_OFF, MB_EID_LEN);
+ /* Data length */
+ frame->can_dlc = get_can_dlc(buf[i * CAN_TRANSFER_BUF_LEN
+ + MB_DLC_OFF]);
+ memcpy(frame->data, &buf[i * CAN_TRANSFER_BUF_LEN]
+ + CAN_HEADER_MAX_LEN, frame->can_dlc);
+
+ priv->net->stats.rx_packets++;
+ priv->net->stats.rx_bytes += frame->can_dlc;
+
+ can_led_event(priv->net, CAN_LED_EVENT_RX);
+
+ netif_rx_ni(skb);
+ }
+
+ return frame_available;
+
+}
+
+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);
+
+ priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+ 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);
+ priv->can.state = CAN_STATE_ERROR_PASSIVE;
+ } else if (priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES) {
+ /* Put device into triple sampling 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,
+ 0x03);
+ } 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);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_can_enable(struct net_device *net,
+ bool enable)
+{
+ 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, (enable) ? CANCTRL_INTEN:0);
+ /* Enable CAN */
+ apalis_tk1_k20_reg_rmw(priv->apalis_tk1_k20, APALIS_TK1_K20_CANREG
+ + APALIS_TK1_K20_CAN_DEV_OFFSET(
+ priv->pdata->id),
+ CANCTRL_ENABLE, (enable) ? CANCTRL_ENABLE:0);
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_can_do_set_bittiming(struct net_device *net)
+{
+ struct apalis_tk1_k20_priv *priv = netdev_priv(net);
+ struct can_bittiming *bt = &priv->can.bittiming;
+
+ if ((bt->bitrate / APALIS_TK1_CAN_CLK_UNIT) > 0xFF)
+ return -EINVAL;
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+ apalis_tk1_k20_reg_write(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN_BAUD_REG
+ + APALIS_TK1_K20_CAN_DEV_OFFSET(
+ priv->pdata->id), (bt->bitrate /
+ APALIS_TK1_CAN_CLK_UNIT) & 0xFF);
+ apalis_tk1_k20_reg_write(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN_BIT_1
+ + APALIS_TK1_K20_CAN_DEV_OFFSET(
+ priv->pdata->id),
+ ((bt->sjw & 0x3) << 6) |
+ ((bt->phase_seg2 & 0x7) << 3) |
+ (bt->phase_seg1 & 0x7));
+ apalis_tk1_k20_reg_write(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN_BIT_2
+ + APALIS_TK1_K20_CAN_DEV_OFFSET(
+ priv->pdata->id),
+ (bt->prop_seg & 0x7));
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+ dev_dbg(priv->apalis_tk1_k20->dev, "Setting CAN%d bitrate " \
+ "to %d (0x%X * 6.25kHz)\n", priv->pdata->id, bt->bitrate,
+ (bt->bitrate / APALIS_TK1_CAN_CLK_UNIT) & 0xFF);
+ dev_dbg(priv->apalis_tk1_k20->dev, "Setting CAN%d bit timing " \
+ "RJW = %d, PSEG1 = %d, PSEG2 = %d, PROPSEG = %d\n",
+ priv->pdata->id, bt->sjw, bt->phase_seg1,
+ bt->phase_seg2, bt->prop_seg);
+ dev_dbg(priv->apalis_tk1_k20->dev, "Setting CAN%d bit timing " \
+ "bitrate = %d\n", priv->pdata->id, bt->bitrate);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_can_setup(struct net_device *net,
+ struct apalis_tk1_k20_priv *priv)
+{
+ apalis_tk1_k20_can_do_set_bittiming(net);
+
+ return 0;
+}
+
+static int apalis_tk1_k20_can_hw_reset(struct net_device *net)
+{
+ return 0;
+}
+
+static void apalis_tk1_k20_can_open_clean(struct net_device *net)
+{
+ struct apalis_tk1_k20_priv *priv = netdev_priv(net);
+ struct apalis_tk1_k20_can_platform_data *pdata = priv->pdata;
+
+ if (pdata->id == 0)
+ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN0_IRQ, priv);
+ if (pdata->id == 1)
+ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN1_IRQ, priv);
+ close_candev(net);
+}
+
+static int apalis_tk1_k20_can_stop(struct net_device *net)
+{
+ struct apalis_tk1_k20_priv *priv = netdev_priv(net);
+ struct apalis_tk1_k20_can_platform_data *pdata = priv->pdata;
+
+ close_candev(net);
+
+ priv->force_quit = 1;
+ if (pdata->id == 0)
+ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN0_IRQ, priv);
+ if (pdata->id == 1)
+ apalis_tk1_k20_irq_free(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN1_IRQ, priv);
+ destroy_workqueue(priv->wq);
+ priv->wq = NULL;
+
+ apalis_tk1_k20_can_enable(net, false);
+
+ mutex_lock(&priv->apalis_tk1_k20_can_lock);
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+ if (pdata->id == 0)
+ apalis_tk1_k20_irq_mask(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN0_IRQ);
+ if (pdata->id == 1)
+ apalis_tk1_k20_irq_mask(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN1_IRQ);
+
+ priv->can.state = CAN_STATE_STOPPED;
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+ mutex_unlock(&priv->apalis_tk1_k20_can_lock);
+
+ can_led_event(net, CAN_LED_EVENT_STOP);
+
+ return 0;
+}
+
+static void apalis_tk1_k20_can_error_skb(struct net_device *net, int can_id,
+ int data1)
+{
+ struct sk_buff *skb;
+ struct can_frame *frame;
+
+ skb = alloc_can_err_skb(net, &frame);
+ if (skb) {
+ frame->can_id |= can_id;
+ frame->data[1] = data1;
+ netif_rx_ni(skb);
+ } else {
+ netdev_err(net, "cannot allocate error skb\n");
+ }
+}
+
+static void apalis_tk1_k20_can_tx_work_handler(struct work_struct *ws)
+{
+ struct apalis_tk1_k20_priv *priv = container_of(ws,
+ struct apalis_tk1_k20_priv, tx_work);
+ struct net_device *net = priv->net;
+ struct can_frame *frame;
+
+ mutex_lock(&priv->apalis_tk1_k20_can_lock);
+ if (priv->tx_skb) {
+ if (priv->can.state == CAN_STATE_BUS_OFF) {
+ apalis_tk1_k20_can_clean(net);
+ } else {
+ frame = (struct can_frame *)priv->tx_skb->data;
+
+ if (frame->can_dlc > CAN_FRAME_MAX_LEN)
+ frame->can_dlc = CAN_FRAME_MAX_LEN;
+ apalis_tk1_k20_can_hw_tx(net, frame, 0);
+ priv->tx_len = 1 + frame->can_dlc;
+ can_put_echo_skb(priv->tx_skb, net, 0);
+ priv->tx_skb = NULL;
+ priv->tx_frame = 1;
+ }
+ }
+ mutex_unlock(&priv->apalis_tk1_k20_can_lock);
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int apalis_tk1_k20_can_suspend(struct device *dev)
+{
+ struct apalis_tk1_k20_priv *priv = dev_get_drvdata(dev);
+ struct apalis_tk1_k20_can_platform_data *pdata = priv->pdata;
+
+ priv->force_quit = 1;
+
+ mutex_lock(&priv->apalis_tk1_k20_can_lock);
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+ if (pdata->id == 0)
+ apalis_tk1_k20_irq_mask(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN0_IRQ);
+ if (pdata->id == 1)
+ apalis_tk1_k20_irq_mask(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN1_IRQ);
+ /* Disable interrupts */
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+ mutex_unlock(&priv->apalis_tk1_k20_can_lock);
+ /* Note: at this point neither IST nor workqueues are running.
+ * open/stop cannot be called anyway so locking is not needed
+ */
+ if (netif_running(priv->net)) {
+ netif_device_detach(priv->net);
+
+ priv->after_suspend = AFTER_SUSPEND_UP;
+ } else {
+ priv->after_suspend = AFTER_SUSPEND_DOWN;
+ }
+
+ return 0;
+}
+
+static int apalis_tk1_k20_can_resume(struct device *dev)
+{
+ struct apalis_tk1_k20_priv *priv = dev_get_drvdata(dev);
+ struct apalis_tk1_k20_can_platform_data *pdata = priv->pdata;
+
+ if (priv->after_suspend & AFTER_SUSPEND_UP)
+ queue_work(priv->wq, &priv->restart_work);
+ else
+ priv->after_suspend = 0;
+
+ priv->force_quit = 0;
+ mutex_lock(&priv->apalis_tk1_k20_can_lock);
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+ if (pdata->id == 0)
+ apalis_tk1_k20_irq_unmask(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN0_IRQ);
+ if (pdata->id == 1)
+ apalis_tk1_k20_irq_unmask(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN1_IRQ);
+
+ priv->can.state = CAN_STATE_STOPPED;
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+ mutex_unlock(&priv->apalis_tk1_k20_can_lock);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(apalis_tk1_k20_can_pm_ops, apalis_tk1_k20_can_suspend,
+ apalis_tk1_k20_can_resume);
+
+#endif
+
+static void apalis_tk1_k20_can_restart_work_handler(struct work_struct *ws)
+{
+ struct apalis_tk1_k20_priv *priv = container_of(ws,
+ struct apalis_tk1_k20_priv, restart_work);
+ struct net_device *net = priv->net;
+
+ mutex_lock(&priv->apalis_tk1_k20_can_lock);
+ if (priv->after_suspend) {
+ mdelay(10);
+ apalis_tk1_k20_can_hw_reset(net);
+ apalis_tk1_k20_can_setup(net, priv);
+ if (priv->after_suspend & AFTER_SUSPEND_RESTART) {
+ apalis_tk1_k20_can_set_normal_mode(net);
+ } else if (priv->after_suspend & AFTER_SUSPEND_UP) {
+ netif_device_attach(net);
+ apalis_tk1_k20_can_clean(net);
+ apalis_tk1_k20_can_set_normal_mode(net);
+ netif_wake_queue(net);
+ }
+ priv->after_suspend = 0;
+ priv->force_quit = 0;
+ }
+
+ if (priv->restart_tx) {
+ priv->restart_tx = 0;
+ apalis_tk1_k20_can_clean(net);
+ netif_wake_queue(net);
+ apalis_tk1_k20_can_error_skb(net, CAN_ERR_RESTARTED, 0);
+ }
+ mutex_unlock(&priv->apalis_tk1_k20_can_lock);
+}
+
+static irqreturn_t apalis_tk1_k20_can_ist(int irq, void *dev_id)
+{
+ struct apalis_tk1_k20_priv *priv = dev_id;
+ struct net_device *net = priv->net;
+
+ mutex_lock(&priv->apalis_tk1_k20_can_lock);
+ while (!priv->force_quit) {
+ enum can_state new_state = CAN_STATE_ERROR_ACTIVE;
+ int ret, rx_cnt = 0;
+ u32 intf, eflag;
+ u8 clear_intf = 0;
+ int can_id = 0, data1 = 0;
+
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+ ret = apalis_tk1_k20_reg_read(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CANREG
+ + APALIS_TK1_K20_CAN_DEV_OFFSET(
+ priv->pdata->id), &intf);
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+
+ if (ret) {
+ dev_err(&net->dev, "Communication error\n");
+ break;
+ }
+
+ intf &= CANCTRL_INTMASK;
+
+ if (intf == 0)
+ break;
+
+ /* TX complete */
+ if ((intf & CANINTF_TX) &&
+ (priv->tx_frame == 1)) {
+ priv->tx_frame = 0;
+ net->stats.tx_packets++;
+ if (priv->tx_len) {
+ net->stats.tx_bytes += priv->tx_len - 1;
+ can_led_event(net, CAN_LED_EVENT_TX);
+ can_get_echo_skb(net, 0);
+ priv->tx_len = 0;
+ }
+ netif_wake_queue(net);
+ clear_intf |= CANINTF_TX;
+ }
+ /* receive */
+ if (intf & CANINTF_RX)
+ rx_cnt = apalis_tk1_k20_can_hw_rx(net, 0);
+
+ /* any error interrupt we need to clear? */
+ if (intf & CANINTF_ERR)
+ clear_intf |= CANINTF_ERR;
+
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+ if (clear_intf)
+ ret = apalis_tk1_k20_reg_write(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CANREG_CLR
+ + APALIS_TK1_K20_CAN_DEV_OFFSET(
+ priv->pdata->id),clear_intf);
+ if (ret) {
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+ dev_err(&net->dev, "Communication error\n");
+ break;
+ }
+
+ /* Update can state */
+ if (intf & CANINTF_ERR) {
+ ret = apalis_tk1_k20_reg_read(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CANERR +
+ APALIS_TK1_K20_CAN_DEV_OFFSET(
+ priv->pdata->id), &eflag);
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+ if (ret) {
+ dev_err(&net->dev, "Communication error\n");
+ break;
+ }
+ if (eflag & EFLG_TXBO) {
+ new_state = CAN_STATE_BUS_OFF;
+ can_id |= CAN_ERR_BUSOFF;
+ } else if (eflag & EFLG_TXEP) {
+ new_state = CAN_STATE_ERROR_PASSIVE;
+ can_id |= CAN_ERR_CRTL;
+ data1 |= CAN_ERR_CRTL_TX_PASSIVE;
+ } else if (eflag & EFLG_RXEP) {
+ new_state = CAN_STATE_ERROR_PASSIVE;
+ can_id |= CAN_ERR_CRTL;
+ data1 |= CAN_ERR_CRTL_RX_PASSIVE;
+ } else if (eflag & EFLG_TXWAR) {
+ new_state = CAN_STATE_ERROR_WARNING;
+ can_id |= CAN_ERR_CRTL;
+ data1 |= CAN_ERR_CRTL_TX_WARNING;
+ } else if (eflag & EFLG_RXWAR) {
+ new_state = CAN_STATE_ERROR_WARNING;
+ can_id |= CAN_ERR_CRTL;
+ data1 |= CAN_ERR_CRTL_RX_WARNING;
+ } else {
+ new_state = CAN_STATE_ERROR_ACTIVE;
+ }
+ }
+ else {
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+ }
+
+ /* Update can state statistics */
+ switch (priv->can.state) {
+ case CAN_STATE_ERROR_ACTIVE:
+ if (new_state >= CAN_STATE_ERROR_WARNING &&
+ new_state <= CAN_STATE_BUS_OFF)
+ priv->can.can_stats.error_warning++;
+ /* fall through */
+ case CAN_STATE_ERROR_WARNING:
+ 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 (priv->tx_skb != NULL) {
+ break;
+ }
+
+ }
+ mutex_unlock(&priv->apalis_tk1_k20_can_lock);
+ return IRQ_HANDLED;
+}
+
+static int apalis_tk1_k20_can_open(struct net_device *net)
+{
+ struct apalis_tk1_k20_priv *priv = netdev_priv(net);
+ struct apalis_tk1_k20_can_platform_data *pdata = priv->pdata;
+ int ret;
+
+ ret = open_candev(net);
+ if (ret) {
+ dev_err(&net->dev, "unable to initialize CAN\n");
+ return ret;
+ }
+
+ mutex_lock(&priv->apalis_tk1_k20_can_lock);
+
+ priv->force_quit = 0;
+ priv->tx_skb = NULL;
+ priv->tx_len = 0;
+ priv->tx_frame = 0;
+ apalis_tk1_k20_lock(priv->apalis_tk1_k20);
+ if (pdata->id == 0)
+ ret = apalis_tk1_k20_irq_request(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN0_IRQ,
+ apalis_tk1_k20_can_ist,
+ DEVICE_NAME, priv);
+ if (pdata->id == 1)
+ ret = apalis_tk1_k20_irq_request(priv->apalis_tk1_k20,
+ APALIS_TK1_K20_CAN1_IRQ,
+ apalis_tk1_k20_can_ist,
+ DEVICE_NAME, priv);
+ apalis_tk1_k20_unlock(priv->apalis_tk1_k20);
+ if (ret) {
+ dev_err(&net->dev, "failed to acquire IRQ\n");
+ close_candev(net);
+ goto open_unlock;
+ }
+
+ priv->wq = create_freezable_workqueue("apalis_tk1_k20_wq");
+ INIT_WORK(&priv->tx_work, apalis_tk1_k20_can_tx_work_handler);
+ INIT_WORK(&priv->restart_work, apalis_tk1_k20_can_restart_work_handler);
+
+ ret = apalis_tk1_k20_can_hw_reset(net);
+ if (ret) {
+ apalis_tk1_k20_can_open_clean(net);
+ goto open_unlock;
+ }
+
+ ret = apalis_tk1_k20_can_setup(net, priv);
+ if (ret) {
+ apalis_tk1_k20_can_open_clean(net);
+ goto open_unlock;
+ }
+
+ ret = apalis_tk1_k20_can_set_normal_mode(net);
+ if (ret) {
+ apalis_tk1_k20_can_open_clean(net);
+ goto open_unlock;
+ }
+ ret = apalis_tk1_k20_can_enable(net, true);
+ if (ret) {
+ apalis_tk1_k20_can_open_clean(net);
+ goto open_unlock;
+ }
+
+ can_led_event(net, CAN_LED_EVENT_OPEN);
+
+ netif_wake_queue(net);
+
+open_unlock:
+ mutex_unlock(&priv->apalis_tk1_k20_can_lock);
+ return ret;
+}
+
+static const struct net_device_ops apalis_tk1_k20_netdev_ops = {
+ .ndo_open = apalis_tk1_k20_can_open,
+ .ndo_stop = apalis_tk1_k20_can_stop,
+ .ndo_start_xmit = apalis_tk1_k20_can_hard_start_xmit,
+};
+
+static int apalis_tk1_k20_can_probe(struct platform_device *pdev)
+{
+ struct net_device *net;
+ struct apalis_tk1_k20_priv *priv;
+ struct apalis_tk1_k20_can_platform_data *pdata =
+ pdev->dev.platform_data;
+ int ret = -ENODEV;
+
+ if (!pdata) {
+ pdata = kmalloc(sizeof(struct apalis_tk1_k20_can_platform_data),
+ GFP_KERNEL);
+ if (pdev->id == -1)
+ pdata->id = 0;
+ if (pdev->id >= 0 && pdev->id <= K20_CAN_MAX_ID)
+ pdata->id = pdev->id;
+ else
+ goto error_out;
+ }
+
+ if (pdata->id > K20_CAN_MAX_ID)
+ goto error_out;
+ /* Allocate can/net device */
+ net = alloc_candev(sizeof(struct apalis_tk1_k20_priv), TX_ECHO_SKB_MAX);
+ if (!net) {
+ ret = -ENOMEM;
+ goto error_out;
+ }
+
+ net->netdev_ops = &apalis_tk1_k20_netdev_ops;
+ net->flags |= IFF_ECHO;
+
+ priv = netdev_priv(net);
+ priv->can.bittiming_const = &apalis_tk1_k20_can_bittiming_const;
+ priv->can.do_set_mode = apalis_tk1_k20_can_do_set_mode;
+ priv->can.clock.freq = 8000000;
+ priv->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES |
+ CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY;
+ priv->net = net;
+ priv->pdata = pdata;
+ priv->apalis_tk1_k20 = dev_get_drvdata(pdev->dev.parent);
+
+ mutex_init(&priv->apalis_tk1_k20_can_lock);
+
+ SET_NETDEV_DEV(net, &pdev->dev);
+
+ platform_set_drvdata(pdev, priv);
+
+ ret = register_candev(net);
+
+ if (ret)
+ goto error_probe;
+
+ devm_can_led_init(net);
+
+ dev_info(&pdev->dev, "probed %d\n", pdev->id);
+
+ return ret;
+
+error_probe:
+ free_candev(net);
+error_out:
+ return ret;
+}
+
+static int apalis_tk1_k20_can_remove(struct platform_device *pdev)
+{
+ struct apalis_tk1_k20_priv *priv = platform_get_drvdata(pdev);
+ struct net_device *net = priv->net;
+
+ unregister_candev(net);
+ free_candev(net);
+
+ return 0;
+}
+
+static const struct platform_device_id apalis_tk1_k20_can_idtable[] = {
+ {.name = "apalis-tk1-k20-can", },
+ { /* sentinel */}
+};
+
+MODULE_DEVICE_TABLE(platform, apalis_tk1_k20_can_idtable);
+
+static struct platform_driver apalis_tk1_k20_can_driver = {
+ .id_table = apalis_tk1_k20_can_idtable,
+ .remove = apalis_tk1_k20_can_remove,
+ .probe = apalis_tk1_k20_can_probe,
+ .driver = {
+ .name = DEVICE_NAME,
+#ifdef CONFIG_PM_SLEEP
+ .pm = &apalis_tk1_k20_can_pm_ops,
+#endif
+ },
+};
+
+module_platform_driver(apalis_tk1_k20_can_driver);
+
+MODULE_DESCRIPTION("CAN driver for K20 MCU on Apalis TK1");
+MODULE_AUTHOR("Dominik Sliwa <dominik.sliwa@toradex.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/intel/igb/e1000_82575.c b/drivers/net/ethernet/intel/igb/e1000_82575.c
index 438b42ce2cd9..e292db3892d4 100644
--- a/drivers/net/ethernet/intel/igb/e1000_82575.c
+++ b/drivers/net/ethernet/intel/igb/e1000_82575.c
@@ -593,6 +593,7 @@ static s32 igb_get_invariants_82575(struct e1000_hw *hw)
case E1000_DEV_ID_I350_SGMII:
mac->type = e1000_i350;
break;
+ case E1000_DEV_ID_I210_TOOLS_ONLY:
case E1000_DEV_ID_I210_COPPER:
case E1000_DEV_ID_I210_FIBER:
case E1000_DEV_ID_I210_SERDES:
@@ -601,6 +602,7 @@ static s32 igb_get_invariants_82575(struct e1000_hw *hw)
case E1000_DEV_ID_I210_SERDES_FLASHLESS:
mac->type = e1000_i210;
break;
+ case E1000_DEV_ID_I211_TOOLS_ONLY:
case E1000_DEV_ID_I211_COPPER:
mac->type = e1000_i211;
break;
diff --git a/drivers/net/ethernet/intel/igb/e1000_hw.h b/drivers/net/ethernet/intel/igb/e1000_hw.h
index 5d87957b2627..3cfca5a22806 100644
--- a/drivers/net/ethernet/intel/igb/e1000_hw.h
+++ b/drivers/net/ethernet/intel/igb/e1000_hw.h
@@ -39,6 +39,8 @@ struct e1000_hw;
#define E1000_DEV_ID_I350_FIBER 0x1522
#define E1000_DEV_ID_I350_SERDES 0x1523
#define E1000_DEV_ID_I350_SGMII 0x1524
+#define E1000_DEV_ID_I210_TOOLS_ONLY 0x1531
+#define E1000_DEV_ID_I211_TOOLS_ONLY 0x1532
#define E1000_DEV_ID_I210_COPPER 0x1533
#define E1000_DEV_ID_I210_FIBER 0x1536
#define E1000_DEV_ID_I210_SERDES 0x1537
diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c
index 6638d314c811..d0527456976e 100644
--- a/drivers/net/ethernet/intel/igb/igb_main.c
+++ b/drivers/net/ethernet/intel/igb/igb_main.c
@@ -36,6 +36,7 @@
#include <linux/dca.h>
#endif
#include <linux/i2c.h>
+#include <linux/ctype.h>
#include "igb.h"
#define MAJ 5
@@ -61,6 +62,9 @@ static const char igb_driver_string[] =
static const char igb_copyright[] =
"Copyright (c) 2007-2014 Intel Corporation.";
+static char g_mac_addr[ETH_ALEN];
+static int g_usr_mac = 0;
+
static const struct e1000_info *igb_info_tbl[] = {
[board_82575] = &e1000_82575_info,
};
@@ -69,7 +73,9 @@ static const struct pci_device_id igb_pci_tbl[] = {
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_BACKPLANE_1GBPS) },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_SGMII) },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_BACKPLANE_2_5GBPS) },
+ { PCI_VDEVICE(INTEL, E1000_DEV_ID_I211_TOOLS_ONLY), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I211_COPPER), board_82575 },
+ { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_TOOLS_ONLY), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_COPPER), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_FIBER), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SERDES), board_82575 },
@@ -247,6 +253,37 @@ static int debug = -1;
module_param(debug, int, 0);
MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)");
+/* Retrieve user set MAC address */
+static int __init setup_igb_mac(char *macstr)
+{
+ int i, j;
+ unsigned char result, value;
+
+ for (i = 0; i < ETH_ALEN; i++) {
+ result = 0;
+
+ if (i != 5 && *(macstr + 2) != ':')
+ return -1;
+
+ for (j = 0; j < 2; j++) {
+ if (isxdigit(*macstr) && (value = isdigit(*macstr) ?
+ *macstr - '0' : toupper(*macstr) - 'A' + 10) < 16) {
+ result = result * 16 + value;
+ macstr++;
+ } else
+ return -1;
+ }
+
+ macstr++;
+ g_mac_addr[i] = result;
+ }
+
+ g_usr_mac = 1;
+
+ return 0;
+}
+__setup("igb_mac=", setup_igb_mac);
+
struct igb_reg_info {
u32 ofs;
char *name;
@@ -3193,7 +3230,7 @@ static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
case e1000_i210:
case e1000_i211:
if (igb_get_flash_presence_i210(hw)) {
- if (hw->nvm.ops.validate(hw) < 0) {
+ if ((hw->nvm.ops.validate(hw) < 0) && !g_usr_mac) {
dev_err(&pdev->dev,
"The NVM Checksum Is Not Valid\n");
err = -EIO;
@@ -3216,12 +3253,31 @@ static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
dev_err(&pdev->dev, "NVM Read Error\n");
}
+ if (g_usr_mac && (g_usr_mac < 3)) {
+ /* Get user set MAC address */
+ if (g_usr_mac == 2) {
+ /* 0x100000 offset for 2nd Ethernet MAC */
+ g_mac_addr[3] += 0x10;
+ if (g_mac_addr[3] < 0x10)
+ dev_warn(&pdev->dev,
+ "MAC addr byte 3 (0x%02x) wrap around"
+ "\n",
+ g_mac_addr[3]);
+ }
+ memcpy(hw->mac.addr, g_mac_addr, ETH_ALEN);
+ g_usr_mac++;
+ }
+
memcpy(netdev->dev_addr, hw->mac.addr, netdev->addr_len);
if (!is_valid_ether_addr(netdev->dev_addr)) {
- dev_err(&pdev->dev, "Invalid MAC Address\n");
- err = -EIO;
- goto err_eeprom;
+ /* Use Toradex OUI as default */
+ char default_mac_addr[ETH_ALEN] = {
+ 0x0, 0x14, 0x2d, 0x0, 0x0, 0x0
+ };
+ dev_warn(&pdev->dev, "using Toradex OUI as default igb MAC\n");
+ memcpy(hw->mac.addr, default_mac_addr, ETH_ALEN);
+ memcpy(netdev->dev_addr, hw->mac.addr, netdev->addr_len);
}
igb_set_default_mac_filter(adapter);
diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c
index 721153dcfd15..5299d79c3424 100644
--- a/drivers/net/phy/micrel.c
+++ b/drivers/net/phy/micrel.c
@@ -705,6 +705,26 @@ static int ksz9131_of_load_skew_values(struct phy_device *phydev,
return phy_write_mmd(phydev, 2, reg, newval);
}
+/* Silicon Errata DS80000693B
+ *
+ * When LEDs are configured in Individual Mode, LED1 is ON in a no-link
+ * condition. Workaround is to set register 0x1e, bit 9, this way LED1 behaves
+ * according to the datasheet (off if there is no link).
+ */
+static int ksz9131_led_errata(struct phy_device *phydev)
+{
+ int reg;
+
+ reg = phy_read_mmd(phydev, 2, 0);
+ if (reg < 0)
+ return reg;
+
+ if (!(reg & BIT(4)))
+ return 0;
+
+ return phy_set_bits(phydev, 0x1e, BIT(9));
+}
+
static int ksz9131_config_init(struct phy_device *phydev)
{
const struct device *dev = &phydev->mdio.dev;
@@ -755,6 +775,10 @@ static int ksz9131_config_init(struct phy_device *phydev)
if (ret < 0)
return ret;
+ ret = ksz9131_led_errata(phydev);
+ if (ret < 0)
+ return ret;
+
return 0;
}
diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
index 1599ae74b066..857b61db9d2c 100644
--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
@@ -1984,6 +1984,8 @@ static int mwifiex_cfg80211_start_ap(struct wiphy *wiphy,
mwifiex_set_sys_config_invalid_data(bss_cfg);
+ memcpy(bss_cfg->mac_addr, priv->curr_addr, ETH_ALEN);
+
if (params->beacon_interval)
bss_cfg->beacon_period = params->beacon_interval;
if (params->dtim_period)
diff --git a/drivers/net/wireless/marvell/mwifiex/fw.h b/drivers/net/wireless/marvell/mwifiex/fw.h
index 076ea1c4b921..3e3134bcc2b0 100644
--- a/drivers/net/wireless/marvell/mwifiex/fw.h
+++ b/drivers/net/wireless/marvell/mwifiex/fw.h
@@ -177,6 +177,7 @@ enum MWIFIEX_802_11_PRIVACY_FILTER {
#define TLV_TYPE_STA_MAC_ADDR (PROPRIETARY_TLV_BASE_ID + 32)
#define TLV_TYPE_BSSID (PROPRIETARY_TLV_BASE_ID + 35)
#define TLV_TYPE_CHANNELBANDLIST (PROPRIETARY_TLV_BASE_ID + 42)
+#define TLV_TYPE_UAP_MAC_ADDRESS (PROPRIETARY_TLV_BASE_ID + 43)
#define TLV_TYPE_UAP_BEACON_PERIOD (PROPRIETARY_TLV_BASE_ID + 44)
#define TLV_TYPE_UAP_DTIM_PERIOD (PROPRIETARY_TLV_BASE_ID + 45)
#define TLV_TYPE_UAP_BCAST_SSID (PROPRIETARY_TLV_BASE_ID + 48)
diff --git a/drivers/net/wireless/marvell/mwifiex/ioctl.h b/drivers/net/wireless/marvell/mwifiex/ioctl.h
index 0dd592ea6e83..96ff91655a77 100644
--- a/drivers/net/wireless/marvell/mwifiex/ioctl.h
+++ b/drivers/net/wireless/marvell/mwifiex/ioctl.h
@@ -119,6 +119,7 @@ struct mwifiex_uap_bss_param {
u8 qos_info;
u8 power_constraint;
struct mwifiex_types_wmm_info wmm_info;
+ u8 mac_addr[ETH_ALEN];
};
enum {
diff --git a/drivers/net/wireless/marvell/mwifiex/uap_cmd.c b/drivers/net/wireless/marvell/mwifiex/uap_cmd.c
index 0939a8c8f3ab..1ab253c97c14 100644
--- a/drivers/net/wireless/marvell/mwifiex/uap_cmd.c
+++ b/drivers/net/wireless/marvell/mwifiex/uap_cmd.c
@@ -479,6 +479,7 @@ void mwifiex_config_uap_11d(struct mwifiex_private *priv,
static int
mwifiex_uap_bss_param_prepare(u8 *tlv, void *cmd_buf, u16 *param_size)
{
+ struct host_cmd_tlv_mac_addr *mac_tlv;
struct host_cmd_tlv_dtim_period *dtim_period;
struct host_cmd_tlv_beacon_period *beacon_period;
struct host_cmd_tlv_ssid *ssid;
@@ -498,6 +499,13 @@ mwifiex_uap_bss_param_prepare(u8 *tlv, void *cmd_buf, u16 *param_size)
int i;
u16 cmd_size = *param_size;
+ mac_tlv = (struct host_cmd_tlv_mac_addr *)tlv;
+ mac_tlv->header.type = cpu_to_le16(TLV_TYPE_UAP_MAC_ADDRESS);
+ mac_tlv->header.len = cpu_to_le16(ETH_ALEN);
+ memcpy(mac_tlv->mac_addr, bss_cfg->mac_addr, ETH_ALEN);
+ cmd_size += sizeof(struct host_cmd_tlv_mac_addr);
+ tlv += sizeof(struct host_cmd_tlv_mac_addr);
+
if (bss_cfg->ssid.ssid_len) {
ssid = (struct host_cmd_tlv_ssid *)tlv;
ssid->header.type = cpu_to_le16(TLV_TYPE_UAP_SSID);
diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c
index 30c259f63239..79f311ec647a 100644
--- a/drivers/pci/controller/dwc/pci-imx6.c
+++ b/drivers/pci/controller/dwc/pci-imx6.c
@@ -31,6 +31,7 @@
#include <linux/reset.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
+#include <asm/opcodes.h>
#include "pcie-designware.h"
@@ -304,8 +305,14 @@ static int imx6q_pcie_abort_handler(unsigned long addr,
unsigned int fsr, struct pt_regs *regs)
{
unsigned long pc = instruction_pointer(regs);
- unsigned long instr = *(unsigned long *)pc;
- int reg = (instr >> 12) & 15;
+ unsigned long instr;
+ int reg;
+
+ if (user_mode(regs))
+ return 1;
+
+ instr = *(unsigned long *)pc;
+ reg = (instr >> 12) & 15;
/*
* If the instruction being executed was a read,
@@ -374,6 +381,59 @@ static int imx6_pcie_attach_pd(struct device *dev)
return 0;
}
+static int imx6q_pcie_abort_handler_thumb2(unsigned long addr,
+ unsigned int fsr, struct pt_regs *regs)
+{
+ unsigned long pc = instruction_pointer(regs);
+ unsigned long instr;
+
+ if (user_mode(regs))
+ return 1;
+
+ instr = __mem_to_opcode_thumb32(*(unsigned long *)pc);
+
+ if (__opcode_is_thumb32(instr)) {
+ /* Load word/byte and halfword immediate offset */
+ if ((instr & 0xff100000UL) == 0xf8100000UL) {
+ int reg = (instr >> 12) & 0xf;
+ unsigned long val;
+
+ if ((instr & 0x00700000UL) == 0x00100000UL)
+ val = 0xff;
+ else if ((instr & 0x00700000UL) == 0x00300000UL)
+ val = 0xffff;
+ else
+ val = 0xffffffffUL;
+
+ regs->uregs[reg] = val;
+ regs->ARM_pc += 4;
+ return 0;
+ }
+ } else {
+ instr = __mem_to_opcode_thumb16(*(unsigned long *)pc);
+
+ /* Load word/byte and halfword immediate offset */
+ if (((instr & 0xe800) == 0x6800) ||
+ ((instr & 0xf800) == 0x8800)) {
+ int reg = instr & 0x7;
+ unsigned long val;
+
+ if (instr & 0x1000)
+ val = 0xff;
+ else if (instr & 0x8000)
+ val = 0xffff;
+ else
+ val = 0xffffffffUL;
+
+ regs->uregs[reg] = val;
+ regs->ARM_pc += 2;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie)
{
struct device *dev = imx6_pcie->pci->dev;
@@ -1295,6 +1355,7 @@ DECLARE_PCI_FIXUP_CLASS_HEADER(PCI_VENDOR_ID_SYNOPSYS, 0xabcd,
static int __init imx6_pcie_init(void)
{
#ifdef CONFIG_ARM
+ bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL);
struct device_node *np;
np = of_find_matching_node(NULL, imx6_pcie_of_match);
@@ -1309,7 +1370,8 @@ static int __init imx6_pcie_init(void)
* we can install the handler here without risking it
* accessing some uninitialized driver state.
*/
- hook_fault_code(8, imx6q_pcie_abort_handler, SIGBUS, 0,
+ hook_fault_code(8, thumb2 ? imx6q_pcie_abort_handler_thumb2 :
+ imx6q_pcie_abort_handler, SIGBUS, 0,
"external abort on non-linefetch");
#endif
diff --git a/drivers/pci/controller/pci-tegra.c b/drivers/pci/controller/pci-tegra.c
index 99d505a85067..df145000dca8 100644
--- a/drivers/pci/controller/pci-tegra.c
+++ b/drivers/pci/controller/pci-tegra.c
@@ -44,6 +44,38 @@
#include "../pci.h"
+//#define CONFIG_MACH_APALIS_T30
+#define CONFIG_MACH_APALIS_TK1
+#if defined(CONFIG_MACH_APALIS_T30) || defined(CONFIG_MACH_APALIS_TK1)
+#include <linux/gpio.h>
+
+#include "../../../include/dt-bindings/gpio/tegra-gpio.h"
+
+#ifdef CONFIG_MACH_APALIS_T30
+#define APALIS_GPIO7 TEGRA_GPIO(S, 7)
+
+#define LAN_RESET_N -1
+
+#define PEX_PERST_N APALIS_GPIO7
+
+#define RESET_MOCI_N TEGRA_GPIO(I, 4)
+#endif
+
+#ifdef CONFIG_MACH_APALIS_TK1
+#define APALIS_GPIO7 TEGRA_GPIO(DD, 1)
+
+#define LAN_DEV_OFF_N TEGRA_GPIO(O, 6)
+
+#define LAN_RESET_N TEGRA_GPIO(S, 2)
+
+#define LAN_WAKE_N TEGRA_GPIO(O, 5)
+
+#define PEX_PERST_N APALIS_GPIO7
+
+#define RESET_MOCI_N TEGRA_GPIO(U, 4)
+#endif
+#endif
+
#define INT_PCI_MSI_NR (8 * 32)
/* register definitions */
@@ -378,6 +410,14 @@ struct tegra_pcie {
struct regulator_bulk_data *supplies;
unsigned int num_supplies;
+#ifdef CONFIG_MACH_APALIS_T30
+ int pci_reset_status;
+#endif
+#ifdef CONFIG_MACH_APALIS_TK1
+ struct regulator *regulator_apalis_tk1_ldo9;
+ struct regulator *regulator_apalis_tk1_ldo10;
+#endif /* CONFIG_MACH_APALIS_TK1 */
+
const struct tegra_pcie_soc *soc;
struct dentry *debugfs;
};
@@ -401,6 +441,21 @@ struct tegra_pcie_bus {
unsigned int nr;
};
+#if defined(CONFIG_MACH_APALIS_T30) || defined(CONFIG_MACH_APALIS_TK1)
+/* To disable the PCIe switch reset errata workaround */
+int g_pex_perst = 1;
+
+/* To disable the PCIe switch reset errata workaround */
+static int __init disable_pex_perst(char *s)
+{
+ if (!(*s) || !strcmp(s, "0"))
+ g_pex_perst = 0;
+
+ return 0;
+}
+__setup("pex_perst=", disable_pex_perst);
+#endif /* CONFIG_MACH_APALIS_T30 || CONFIG_MACH_APALIS_TK1 */
+
static inline void afi_writel(struct tegra_pcie *pcie, u32 value,
unsigned long offset)
{
@@ -538,6 +593,87 @@ static void tegra_pcie_port_reset(struct tegra_pcie_port *port)
unsigned long ctrl = tegra_pcie_port_get_pex_ctrl(port);
unsigned long value;
+#if defined(CONFIG_MACH_APALIS_T30) || defined(CONFIG_MACH_APALIS_TK1)
+#ifdef CONFIG_MACH_APALIS_T30
+ /*
+ * Apalis PCIe aka port 1 and Apalis Type Specific 4 Lane PCIe aka port
+ * 0 share the same RESET_MOCI therefore only assert it once for both
+ * ports to avoid loosing the previously brought up port again.
+ */
+ if ((port->index == 1) || (port->index == 0)) {
+ /* only do it once per init cycle */
+ if (port->pcie->pci_reset_status % 2 == 0) {
+#endif
+#ifdef CONFIG_MACH_APALIS_TK1
+ if (port->index == 0) { /* Apalis PCIe */
+#endif
+ /*
+ * Reset PLX PEX 8605 PCIe Switch plus PCIe devices on Apalis Evaluation
+ * Board
+ */
+ if (g_pex_perst)
+ gpio_request(PEX_PERST_N, "PEX_PERST_N");
+ gpio_request(RESET_MOCI_N, "RESET_MOCI_N");
+ if (g_pex_perst)
+ gpio_direction_output(PEX_PERST_N, 0);
+ gpio_direction_output(RESET_MOCI_N, 0);
+#ifdef CONFIG_MACH_APALIS_T30
+ }
+#endif
+ }
+#ifdef CONFIG_MACH_APALIS_TK1
+ if (port->index == 1) { /* I210 Gigabit Ethernet Controller (On-module) */
+ /* Reset I210 Gigabit Ethernet Controller */
+ if (LAN_RESET_N >= 0) {
+ gpio_request(LAN_RESET_N, "LAN_RESET_N");
+ gpio_direction_output(LAN_RESET_N, 0);
+ }
+
+ /*
+ * Make sure we don't get any back feeding from LAN_WAKE_N resp.
+ * DEV_OFF_N
+ */
+ gpio_request(LAN_WAKE_N, "LAN_WAKE_N");
+ gpio_request(LAN_DEV_OFF_N, "LAN_DEV_OFF_N");
+ gpio_direction_output(LAN_WAKE_N, 0);
+ gpio_direction_output(LAN_DEV_OFF_N, 0);
+
+ /* Make sure LDO9 and LDO10 are initially disabled @ 0V */
+ if (regulator_is_enabled(port->pcie->regulator_apalis_tk1_ldo9)) {
+ value = regulator_enable(port->pcie->regulator_apalis_tk1_ldo9);
+ if (regulator_disable(port->pcie->regulator_apalis_tk1_ldo9) < 0)
+ pr_err("failed disabling +V3.3_ETH(ldo9)\n");
+ }
+ if (regulator_is_enabled(port->pcie->regulator_apalis_tk1_ldo10)) {
+ value = regulator_enable(port->pcie->regulator_apalis_tk1_ldo10);
+ if (regulator_disable(port->pcie->regulator_apalis_tk1_ldo10) <0)
+ pr_err("failed disabling +V3.3_ETH(ldo10)\n");
+ }
+
+ mdelay(100);
+
+ /* Make sure LAN_WAKE_N gets re-configured as a GPIO input */
+ gpio_direction_input(LAN_WAKE_N);
+
+ /* Make sure controller gets enabled by disabling DEV_OFF_N */
+ gpio_set_value(LAN_DEV_OFF_N, 1);
+
+ /*
+ * Enable LDO9 and LDO10 for +V3.3_ETH on patched prototype
+ * V1.0A and sample V1.0B and newer modules
+ */
+ if (regulator_enable(port->pcie->regulator_apalis_tk1_ldo9) < 0) {
+ pr_err("pcie: couldn't enable regulator +V3.3_ETH(ldo9)\n");
+ return;
+ }
+ if (regulator_enable(port->pcie->regulator_apalis_tk1_ldo10) < 0) {
+ pr_err("pcie: couldn't enable regulator +V3.3_ETH(ldo10)\n");
+ return;
+ }
+ }
+#endif /* CONFIG_MACH_APALIS_TK1 */
+#endif /* CONFIG_MACH_APALIS_T30 || CONFIG_MACH_APALIS_TK1 */
+
/* pulse reset signal */
if (port->reset_gpio) {
gpiod_set_value(port->reset_gpio, 1);
@@ -556,6 +692,42 @@ static void tegra_pcie_port_reset(struct tegra_pcie_port *port)
value |= AFI_PEX_CTRL_RST;
afi_writel(port->pcie, value, ctrl);
}
+
+#if defined(CONFIG_MACH_APALIS_T30) || defined(CONFIG_MACH_APALIS_TK1)
+#ifdef CONFIG_MACH_APALIS_T30
+ if ((port->index == 1) || (port->index == 0)) {
+ /* only do it once per init cycle */
+ if (port->pcie->pci_reset_status % 2 == 0) {
+#endif
+#ifdef CONFIG_MACH_APALIS_TK1
+ if (port->index == 0) { /* Apalis PCIe */
+#endif
+ /* Must be asserted for 100 ms after power and clocks are stable */
+ if (g_pex_perst)
+ gpio_set_value(PEX_PERST_N, 1);
+ /*
+ * Err_5: PEX_REFCLK_OUTpx/nx Clock Outputs is not Guaranteed Until
+ * 900 us After PEX_PERST# De-assertion
+ */
+ if (g_pex_perst)
+ mdelay(1);
+ gpio_set_value(RESET_MOCI_N, 1);
+#ifdef CONFIG_MACH_APALIS_T30
+ }
+ port->pcie->pci_reset_status++;
+#endif
+ }
+
+#ifdef CONFIG_MACH_APALIS_TK1
+ mdelay(5);
+
+ if (port->index == 1) { /* I210 Gigabit Ethernet Controller (On-module) */
+ /* Release I210 Gigabit Ethernet Controller Reset */
+ if (LAN_RESET_N >= 0)
+ gpio_set_value(LAN_RESET_N, 1);
+ }
+#endif /* CONFIG_MACH_APALIS_TK1 */
+#endif /* CONFIG_MACH_APALIS_T30 || CONFIG_MACH_APALIS_TK1 */
}
static void tegra_pcie_enable_rp_features(struct tegra_pcie_port *port)
@@ -1269,6 +1441,24 @@ static int tegra_pcie_power_on(struct tegra_pcie *pcie)
goto disable_cml_clk;
}
+#ifdef CONFIG_MACH_APALIS_TK1
+ if (pcie->regulator_apalis_tk1_ldo9 == NULL) {
+ pcie->regulator_apalis_tk1_ldo9 = regulator_get(pcie->dev, "+V3.3_ETH(ldo9)");
+ if (IS_ERR(pcie->regulator_apalis_tk1_ldo9)) {
+ pr_err("pcie: couldn't get regulator +V3.3_ETH(ldo9)\n");
+ pcie->regulator_apalis_tk1_ldo9 = 0;
+ }
+ }
+
+ if (pcie->regulator_apalis_tk1_ldo10 == NULL) {
+ pcie->regulator_apalis_tk1_ldo10 = regulator_get(pcie->dev, "+V3.3_ETH(ldo10)");
+ if (IS_ERR(pcie->regulator_apalis_tk1_ldo10)) {
+ pr_err("pcie: couldn't get regulator +V3.3_ETH(ldo10)\n");
+ pcie->regulator_apalis_tk1_ldo10 = 0;
+ }
+ }
+#endif /* CONFIG_MACH_APALIS_TK1 */
+
reset_control_deassert(pcie->afi_rst);
return 0;
diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c
index 6d6fc7de9cf3..86731ef152f2 100644
--- a/drivers/spi/spidev.c
+++ b/drivers/spi/spidev.c
@@ -687,6 +687,7 @@ static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "lwn,bk4" },
{ .compatible = "dh,dhcom-board" },
{ .compatible = "menlo,m53cpld" },
+ { .compatible = "toradex,evalspi" },
{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
index 85511c1160b7..481b602bf54d 100644
--- a/drivers/thermal/imx_thermal.c
+++ b/drivers/thermal/imx_thermal.c
@@ -557,10 +557,10 @@ static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0)
}
/*
- * Set the critical trip point at 5 °C under max
+ * Set the critical trip point at max
* Set the passive trip point at 10 °C under max (changeable via sysfs)
*/
- data->temp_critical = data->temp_max - (1000 * 5);
+ data->temp_critical = data->temp_max;
data->temp_passive = data->temp_max - (1000 * 10);
}
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index 3f5878e367c7..86a0f670d747 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -1983,7 +1983,7 @@ imx_uart_console_write(struct console *co, const char *s, unsigned int count)
* If the port was already initialised (eg, by a boot loader),
* try to determine the current setup.
*/
-static void __init
+static void
imx_uart_console_get_options(struct imx_port *sport, int *baud,
int *parity, int *bits)
{
@@ -2042,7 +2042,7 @@ imx_uart_console_get_options(struct imx_port *sport, int *baud,
}
}
-static int __init
+static int
imx_uart_console_setup(struct console *co, char *options)
{
struct imx_port *sport;
@@ -2101,7 +2101,7 @@ static struct console imx_uart_console = {
.data = &imx_uart_uart_driver,
};
-#define IMX_CONSOLE &imx_uart_console
+#define IMX_CONSOLE (&imx_uart_console)
#ifdef CONFIG_OF
static void imx_uart_console_early_putchar(struct uart_port *port, int ch)
@@ -2394,8 +2394,17 @@ static int imx_uart_probe(struct platform_device *pdev)
static int imx_uart_remove(struct platform_device *pdev)
{
struct imx_port *sport = platform_get_drvdata(pdev);
+ int ret;
- return uart_remove_one_port(&imx_uart_uart_driver, &sport->port);
+ ret = uart_remove_one_port(&imx_uart_uart_driver, &sport->port);
+
+ if (IS_ENABLED(CONFIG_SERIAL_IMX_CONSOLE) && IMX_CONSOLE->index >= 0) {
+ clk_unprepare(sport->clk_ipg);
+ clk_unprepare(sport->clk_per);
+ IMX_CONSOLE->index = -1;
+ }
+
+ return ret;
}
static void imx_uart_restore_context(struct imx_port *sport)
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c
index 0fe545815c5c..9efc47c09e1d 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.c
+++ b/drivers/usb/chipidea/ci_hdrc_imx.c
@@ -56,8 +56,7 @@ static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = {
};
static const struct ci_hdrc_imx_platform_flag imx6ul_usb_data = {
- .flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
- CI_HDRC_TURN_VBUS_EARLY_ON |
+ .flags = CI_HDRC_TURN_VBUS_EARLY_ON |
CI_HDRC_DISABLE_DEVICE_STREAMING,
};