| From bf05fe928085a2060fc4e1d7d4bee75d500388d8 Mon Sep 17 00:00:00 2001 |
| From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> |
| Date: Wed, 9 Jul 2014 10:08:52 +0900 |
| Subject: usb: host: xhci-plat: add support for the R-Car H2 and M2 xHCI |
| controllers |
| |
| The R-Car H2 and M2 SoCs come with an xHCI controller that requires |
| some specific initializations related to the firmware downloading and |
| some specific registers. This patch adds the support for this special |
| configuration as an xHCI quirk executed during probe and start. |
| |
| Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> |
| Cc: "mathias.nyman@intel.com" <mathias.nyman@intel.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| (cherry picked from commit 4ac8918f3a737c21d81e250e4194c12ea2b7eb04) |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| --- |
| drivers/usb/host/Kconfig | 8 ++ |
| drivers/usb/host/Makefile | 3 |
| drivers/usb/host/xhci-plat.c | 19 +++++ |
| drivers/usb/host/xhci-rcar.c | 148 +++++++++++++++++++++++++++++++++++++++++++ |
| drivers/usb/host/xhci-rcar.h | 27 +++++++ |
| 5 files changed, 205 insertions(+) |
| create mode 100644 drivers/usb/host/xhci-rcar.c |
| create mode 100644 drivers/usb/host/xhci-rcar.h |
| |
| --- a/drivers/usb/host/Kconfig |
| +++ b/drivers/usb/host/Kconfig |
| @@ -37,6 +37,14 @@ config USB_XHCI_MVEBU |
| Say 'Y' to enable the support for the xHCI host controller |
| found in Marvell Armada 375/38x ARM SOCs. |
| |
| +config USB_XHCI_RCAR |
| + tristate "xHCI support for Renesas R-Car SoCs" |
| + select USB_XHCI_PLATFORM |
| + depends on ARCH_SHMOBILE || COMPILE_TEST |
| + ---help--- |
| + Say 'Y' to enable the support for the xHCI host controller |
| + found in Renesas R-Car ARM SoCs. |
| + |
| endif # USB_XHCI_HCD |
| |
| config USB_EHCI_HCD |
| --- a/drivers/usb/host/Makefile |
| +++ b/drivers/usb/host/Makefile |
| @@ -22,6 +22,9 @@ ifneq ($(CONFIG_USB_XHCI_PLATFORM), ) |
| ifneq ($(CONFIG_USB_XHCI_MVEBU), ) |
| xhci-hcd-y += xhci-mvebu.o |
| endif |
| +ifneq ($(CONFIG_USB_XHCI_RCAR), ) |
| + xhci-hcd-y += xhci-rcar.o |
| +endif |
| endif |
| |
| obj-$(CONFIG_USB_WHCI_HCD) += whci/ |
| --- a/drivers/usb/host/xhci-plat.c |
| +++ b/drivers/usb/host/xhci-plat.c |
| @@ -21,6 +21,7 @@ |
| |
| #include "xhci.h" |
| #include "xhci-mvebu.h" |
| +#include "xhci-rcar.h" |
| |
| static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) |
| { |
| @@ -35,11 +36,27 @@ static void xhci_plat_quirks(struct devi |
| /* called during probe() after chip reset completes */ |
| static int xhci_plat_setup(struct usb_hcd *hcd) |
| { |
| + struct device_node *of_node = hcd->self.controller->of_node; |
| + int ret; |
| + |
| + if (of_device_is_compatible(of_node, "renesas,xhci-r8a7790") || |
| + of_device_is_compatible(of_node, "renesas,xhci-r8a7791")) { |
| + ret = xhci_rcar_init_quirk(hcd); |
| + if (ret) |
| + return ret; |
| + } |
| + |
| return xhci_gen_setup(hcd, xhci_plat_quirks); |
| } |
| |
| static int xhci_plat_start(struct usb_hcd *hcd) |
| { |
| + struct device_node *of_node = hcd->self.controller->of_node; |
| + |
| + if (of_device_is_compatible(of_node, "renesas,xhci-r8a7790") || |
| + of_device_is_compatible(of_node, "renesas,xhci-r8a7791")) |
| + xhci_rcar_start(hcd); |
| + |
| return xhci_run(hcd); |
| } |
| |
| @@ -268,6 +285,8 @@ static const struct of_device_id usb_xhc |
| { .compatible = "xhci-platform" }, |
| { .compatible = "marvell,armada-375-xhci"}, |
| { .compatible = "marvell,armada-380-xhci"}, |
| + { .compatible = "renesas,xhci-r8a7790"}, |
| + { .compatible = "renesas,xhci-r8a7791"}, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, usb_xhci_of_match); |
| --- /dev/null |
| +++ b/drivers/usb/host/xhci-rcar.c |
| @@ -0,0 +1,148 @@ |
| +/* |
| + * xHCI host controller driver for R-Car SoCs |
| + * |
| + * Copyright (C) 2014 Renesas Electronics Corporation |
| + * |
| + * 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/firmware.h> |
| +#include <linux/module.h> |
| +#include <linux/platform_device.h> |
| +#include <linux/usb/phy.h> |
| + |
| +#include "xhci.h" |
| +#include "xhci-rcar.h" |
| + |
| +#define FIRMWARE_NAME "r8a779x_usb3_v1.dlmem" |
| +MODULE_FIRMWARE(FIRMWARE_NAME); |
| + |
| +/*** Register Offset ***/ |
| +#define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */ |
| +#define RCAR_USB3_DL_CTRL 0x250 /* FW Download Control & Status */ |
| +#define RCAR_USB3_FW_DATA0 0x258 /* FW Data0 */ |
| + |
| +#define RCAR_USB3_LCLK 0xa44 /* LCLK Select */ |
| +#define RCAR_USB3_CONF1 0xa48 /* USB3.0 Configuration1 */ |
| +#define RCAR_USB3_CONF2 0xa5c /* USB3.0 Configuration2 */ |
| +#define RCAR_USB3_CONF3 0xaa8 /* USB3.0 Configuration3 */ |
| +#define RCAR_USB3_RX_POL 0xab0 /* USB3.0 RX Polarity */ |
| +#define RCAR_USB3_TX_POL 0xab8 /* USB3.0 TX Polarity */ |
| + |
| +/*** Register Settings ***/ |
| +/* Interrupt Enable */ |
| +#define RCAR_USB3_INT_XHC_ENA 0x00000001 |
| +#define RCAR_USB3_INT_PME_ENA 0x00000002 |
| +#define RCAR_USB3_INT_HSE_ENA 0x00000004 |
| +#define RCAR_USB3_INT_ENA_VAL (RCAR_USB3_INT_XHC_ENA | \ |
| + RCAR_USB3_INT_PME_ENA | RCAR_USB3_INT_HSE_ENA) |
| + |
| +/* FW Download Control & Status */ |
| +#define RCAR_USB3_DL_CTRL_ENABLE 0x00000001 |
| +#define RCAR_USB3_DL_CTRL_FW_SUCCESS 0x00000010 |
| +#define RCAR_USB3_DL_CTRL_FW_SET_DATA0 0x00000100 |
| + |
| +/* LCLK Select */ |
| +#define RCAR_USB3_LCLK_ENA_VAL 0x01030001 |
| + |
| +/* USB3.0 Configuration */ |
| +#define RCAR_USB3_CONF1_VAL 0x00030204 |
| +#define RCAR_USB3_CONF2_VAL 0x00030300 |
| +#define RCAR_USB3_CONF3_VAL 0x13802007 |
| + |
| +/* USB3.0 Polarity */ |
| +#define RCAR_USB3_RX_POL_VAL BIT(21) |
| +#define RCAR_USB3_TX_POL_VAL BIT(4) |
| + |
| +void xhci_rcar_start(struct usb_hcd *hcd) |
| +{ |
| + u32 temp; |
| + |
| + if (hcd->regs != NULL) { |
| + /* Interrupt Enable */ |
| + temp = readl(hcd->regs + RCAR_USB3_INT_ENA); |
| + temp |= RCAR_USB3_INT_ENA_VAL; |
| + writel(temp, hcd->regs + RCAR_USB3_INT_ENA); |
| + /* LCLK Select */ |
| + writel(RCAR_USB3_LCLK_ENA_VAL, hcd->regs + RCAR_USB3_LCLK); |
| + /* USB3.0 Configuration */ |
| + writel(RCAR_USB3_CONF1_VAL, hcd->regs + RCAR_USB3_CONF1); |
| + writel(RCAR_USB3_CONF2_VAL, hcd->regs + RCAR_USB3_CONF2); |
| + writel(RCAR_USB3_CONF3_VAL, hcd->regs + RCAR_USB3_CONF3); |
| + /* USB3.0 Polarity */ |
| + writel(RCAR_USB3_RX_POL_VAL, hcd->regs + RCAR_USB3_RX_POL); |
| + writel(RCAR_USB3_TX_POL_VAL, hcd->regs + RCAR_USB3_TX_POL); |
| + } |
| +} |
| + |
| +static int xhci_rcar_download_firmware(struct device *dev, void __iomem *regs) |
| +{ |
| + const struct firmware *fw; |
| + int retval, index, j, time; |
| + int timeout = 10000; |
| + u32 data, val, temp; |
| + |
| + /* request R-Car USB3.0 firmware */ |
| + retval = request_firmware(&fw, FIRMWARE_NAME, dev); |
| + if (retval) |
| + return retval; |
| + |
| + /* download R-Car USB3.0 firmware */ |
| + temp = readl(regs + RCAR_USB3_DL_CTRL); |
| + temp |= RCAR_USB3_DL_CTRL_ENABLE; |
| + writel(temp, regs + RCAR_USB3_DL_CTRL); |
| + |
| + for (index = 0; index < fw->size; index += 4) { |
| + /* to avoid reading beyond the end of the buffer */ |
| + for (data = 0, j = 3; j >= 0; j--) { |
| + if ((j + index) < fw->size) |
| + data |= fw->data[index + j] << (8 * j); |
| + } |
| + writel(data, regs + RCAR_USB3_FW_DATA0); |
| + temp = readl(regs + RCAR_USB3_DL_CTRL); |
| + temp |= RCAR_USB3_DL_CTRL_FW_SET_DATA0; |
| + writel(temp, regs + RCAR_USB3_DL_CTRL); |
| + |
| + for (time = 0; time < timeout; time++) { |
| + val = readl(regs + RCAR_USB3_DL_CTRL); |
| + if ((val & RCAR_USB3_DL_CTRL_FW_SET_DATA0) == 0) |
| + break; |
| + udelay(1); |
| + } |
| + if (time == timeout) { |
| + retval = -ETIMEDOUT; |
| + break; |
| + } |
| + } |
| + |
| + temp = readl(regs + RCAR_USB3_DL_CTRL); |
| + temp &= ~RCAR_USB3_DL_CTRL_ENABLE; |
| + writel(temp, regs + RCAR_USB3_DL_CTRL); |
| + |
| + for (time = 0; time < timeout; time++) { |
| + val = readl(regs + RCAR_USB3_DL_CTRL); |
| + if (val & RCAR_USB3_DL_CTRL_FW_SUCCESS) { |
| + retval = 0; |
| + break; |
| + } |
| + udelay(1); |
| + } |
| + if (time == timeout) |
| + retval = -ETIMEDOUT; |
| + |
| + release_firmware(fw); |
| + |
| + return retval; |
| +} |
| + |
| +/* This function needs to initialize a "phy" of usb before */ |
| +int xhci_rcar_init_quirk(struct usb_hcd *hcd) |
| +{ |
| + /* If hcd->regs is NULL, we don't just call the following function */ |
| + if (!hcd->regs) |
| + return 0; |
| + |
| + return xhci_rcar_download_firmware(hcd->self.controller, hcd->regs); |
| +} |
| --- /dev/null |
| +++ b/drivers/usb/host/xhci-rcar.h |
| @@ -0,0 +1,27 @@ |
| +/* |
| + * drivers/usb/host/xhci-rcar.h |
| + * |
| + * Copyright (C) 2014 Renesas Electronics Corporation |
| + * |
| + * 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 _XHCI_RCAR_H |
| +#define _XHCI_RCAR_H |
| + |
| +#if IS_ENABLED(CONFIG_USB_XHCI_RCAR) |
| +void xhci_rcar_start(struct usb_hcd *hcd); |
| +int xhci_rcar_init_quirk(struct usb_hcd *hcd); |
| +#else |
| +static inline void xhci_rcar_start(struct usb_hcd *hcd) |
| +{ |
| +} |
| + |
| +static inline int xhci_rcar_init_quirk(struct usb_hcd *hcd) |
| +{ |
| + return 0; |
| +} |
| +#endif |
| +#endif /* _XHCI_RCAR_H */ |