diff options
Diffstat (limited to 'drivers')
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, + ®map_apalis_tk1_k20_bus, &spi->dev, + &apalis_tk1_k20_regmap_spi_config); + if (IS_ERR(apalis_tk1_k20->regmap)) { + ret = PTR_ERR(apalis_tk1_k20->regmap); + dev_err(&spi->dev, "Failed to initialize regmap: %d\n", ret); + return ret; + } + + return apalis_tk1_k20_dev_init(&spi->dev); +} + +static int apalis_tk1_k20_spi_remove(struct spi_device *spi) +{ + struct apalis_tk1_k20_regmap *apalis_tk1_k20 = + dev_get_drvdata(&spi->dev); + + if (apalis_tk1_k20->ezpcs_gpio >= 0) + gpio_free(apalis_tk1_k20->ezpcs_gpio); + if (apalis_tk1_k20->reset_gpio >= 0) + gpio_free(apalis_tk1_k20->reset_gpio); + if (apalis_tk1_k20->int2_gpio >= 0) + gpio_free(apalis_tk1_k20->int2_gpio); + + kfree(spi->controller_data); + spi->controller_data = NULL; + + mfd_remove_devices(&spi->dev); + regmap_del_irq_chip(apalis_tk1_k20->irq, apalis_tk1_k20->irq_data); + mutex_destroy(&apalis_tk1_k20->lock); + + return 0; +} + +static struct spi_driver apalis_tk1_k20_spi_driver = { + .id_table = apalis_tk1_k20_device_ids, + .driver = { + .name = "apalis-tk1-k20", + .of_match_table = apalis_tk1_k20_dt_ids, + }, + .probe = apalis_tk1_k20_spi_probe, + .remove = apalis_tk1_k20_spi_remove, +}; + +static int __init apalis_tk1_k20_init(void) +{ + return spi_register_driver(&apalis_tk1_k20_spi_driver); +} +subsys_initcall(apalis_tk1_k20_init); + +static void __exit apalis_tk1_k20_exit(void) +{ + spi_unregister_driver(&apalis_tk1_k20_spi_driver); +} +module_exit(apalis_tk1_k20_exit); + +MODULE_DESCRIPTION("MFD driver for Kinetis MK20DN512 MCU on Apalis TK1"); +MODULE_AUTHOR("Dominik Sliwa <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, }; |