[PATCH RFC 1/4] usb: add Marvell MVEBU USB support
Sebastian Hesselbarth
sebastian.hesselbarth at gmail.com
Wed Jun 25 07:08:45 PDT 2014
This adds support for Marvell specific implementation of ChipIdea
dual role USB controllers found on Marvell MVEBU SoCs (Armada 370,
XP, Dove, Kirkwood).
Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth at gmail.com>
---
Cc: barebox at lists.infradead.org
Cc: Jason Cooper <jason at lakedaemon.net>
Cc: Andrew Lunn <andrew at lunn.ch>
Cc: Gregory Clement <gregory.clement at free-electrons.com>
Cc: Thomas Petazzoni <thomas.petazzoni at free-electrons.com>
Cc: Ezequiel Garcia <ezequiel.garcia at free-electrons.com>
---
drivers/usb/Kconfig | 1 +
drivers/usb/Makefile | 1 +
drivers/usb/gadget/Kconfig | 4 +-
drivers/usb/mvebu/Kconfig | 35 ++++++
drivers/usb/mvebu/Makefile | 2 +
drivers/usb/mvebu/core.c | 155 +++++++++++++++++++++++
drivers/usb/mvebu/phy.c | 301 +++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 497 insertions(+), 2 deletions(-)
create mode 100644 drivers/usb/mvebu/Kconfig
create mode 100644 drivers/usb/mvebu/Makefile
create mode 100644 drivers/usb/mvebu/core.c
create mode 100644 drivers/usb/mvebu/phy.c
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 0b349bf619d3..a9275308eb80 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -4,6 +4,7 @@ menuconfig USB
if USB
source drivers/usb/imx/Kconfig
+source drivers/usb/mvebu/Kconfig
source drivers/usb/host/Kconfig
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 3cefab7131a6..8e61f96eaa96 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -1,5 +1,6 @@
obj-$(CONFIG_USB) += core/
obj-$(CONFIG_USB_IMX_CHIPIDEA) += imx/
+obj-$(CONFIG_USB_MVEBU) += mvebu/
obj-$(CONFIG_USB_GADGET) += gadget/
obj-$(CONFIG_USB_STORAGE) += storage/
obj-y += host/
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 97a7d215bc5b..a5151edb23d3 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -1,6 +1,6 @@
config USB_HAVE_GADGET_DRIVER
bool
- default y if ARCH_IMX || ARCH_MXS || ARCH_AT91 || ARCH_PXA
+ default y if ARCH_IMX || ARCH_MVEBU || ARCH_MXS || ARCH_AT91 || ARCH_PXA
menuconfig USB_GADGET
depends on USB_HAVE_GADGET_DRIVER
@@ -17,7 +17,7 @@ choice
config USB_GADGET_DRIVER_ARC
bool
prompt "Arc OTG device core"
- depends on ARCH_IMX || ARCH_MXS
+ depends on ARCH_IMX || ARCH_MVEBU || ARCH_MXS
select USB_GADGET_DUALSPEED
select POLLER
diff --git a/drivers/usb/mvebu/Kconfig b/drivers/usb/mvebu/Kconfig
new file mode 100644
index 000000000000..ef452a9a1528
--- /dev/null
+++ b/drivers/usb/mvebu/Kconfig
@@ -0,0 +1,35 @@
+config USB_MVEBU_PHY_40NM
+ bool
+
+config USB_MVEBU_PHY_65NM
+ bool
+
+config USB_MVEBU_PHY
+ bool
+ depends on USB_MVEBU_HOST || USB_MVEBU_DEVICE
+ select USB_MVEBU_PHY_40NM if ARCH_ARMADA_370
+ select USB_MVEBU_PHY_40NM if ARCH_ARMADA_XP
+ select USB_MVEBU_PHY_65NM if ARCH_DOVE
+ select USB_MVEBU_PHY_65NM if ARCH_KIRKWOOD
+
+config USB_MVEBU
+ select USB_MVEBU_PHY
+ bool
+
+config USB_MVEBU_HOST
+ bool "Marvell MVEBU USB host support"
+ depends on ARCH_MVEBU
+ select USB_MVEBU
+ select USB_EHCI
+ help
+ Enables USB host support for the ChipIdea USB controller found on
+ Marvell Orion5x, Kirkwood, Dove, Armada 370, and XP SoCs.
+
+config USB_MVEBU_DEVICE
+ bool "Marvell MVEBU USB device support"
+ depends on ARCH_MVEBU
+ select USB_MVEBU
+ select USB_GADGET_DRIVER_ARC
+ help
+ Enables USB device support for the ChipIdea USB controller found on
+ Marvell Orion5x, Kirkwood, Dove, Armada 370, and XP SoCs.
diff --git a/drivers/usb/mvebu/Makefile b/drivers/usb/mvebu/Makefile
new file mode 100644
index 000000000000..e2569bb2fd42
--- /dev/null
+++ b/drivers/usb/mvebu/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_USB_MVEBU) += core.o
+obj-$(CONFIG_USB_MVEBU_PHY) += phy.o
diff --git a/drivers/usb/mvebu/core.c b/drivers/usb/mvebu/core.c
new file mode 100644
index 000000000000..b8222d26d972
--- /dev/null
+++ b/drivers/usb/mvebu/core.c
@@ -0,0 +1,155 @@
+/*
+ * Marvell MVEBU USB PHY driver
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth at gmail.com>
+ *
+ * Based on BSP code (C) Marvell International Ltd.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <linux/clk.h>
+#include <linux/mbus.h>
+#include <mach/socid.h>
+#include <regulator.h>
+#include <usb/ehci.h>
+#include <usb/fsl_usb2.h>
+#include <usb/usb.h>
+
+#define EHCI_REGS_OFFSET 0x100
+
+#define BRIDGE_CTRL 0x300
+#define BRIDGE_INTR_CAUSE 0x310
+#define BRIDGE_INTR_MASK 0x314
+#define BRIDGE_ERR_ACCESS 0x31c
+#define WINDOW_CTRL(i) (0x320 + ((i) << 4))
+#define WINDOW_BASE(i) (0x324 + ((i) << 4))
+#define BRIDGE_IPG 0x360
+#define START_IPG(x) ((x) << 0)
+#define START_IPG_MASK START_IPG(0x3f)
+#define NON_START_IPG(x) ((x) << 8)
+#define NON_START_IPG_MASK NON_START_IPG(0x3f)
+
+struct mvebu_usb {
+ struct ehci_data ehci;
+ struct device_d *dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct regulator *vbus;
+ u16 devid;
+ u16 revid;
+ enum usb_dr_mode mode;
+};
+
+static void mvebu_usb_mbus_setup(struct mvebu_usb *usb)
+{
+ const struct mbus_dram_target_info *dram = mvebu_mbus_dram_info();
+ int n;
+
+ for (n = 0; n < 4; n++) {
+ writel(0, usb->base + WINDOW_CTRL(n));
+ writel(0, usb->base + WINDOW_BASE(n));
+ }
+
+ for (n = 0; n < dram->num_cs; n++) {
+ const struct mbus_dram_window *w = &dram->cs[n];
+ u32 reg;
+
+ writel(w->base, usb->base + WINDOW_BASE(n));
+ reg = ((w->size - 1) & 0xffff0000) | (w->mbus_attr << 8) |
+ (dram->mbus_dram_target_id << 4) | 1;
+ writel(reg, usb->base + WINDOW_CTRL(n));
+ }
+}
+
+static void mvebu_usb_ipg_setup(struct mvebu_usb *usb)
+{
+ u32 reg;
+
+ /* IPG Metal fix register not available on below SoCs */
+ if ((usb->devid == DEVID_F5180 && usb->revid <= REVID_F5180N_B1) ||
+ (usb->devid == DEVID_F5181 && usb->revid <= REVID_F5181_B1) ||
+ (usb->devid == DEVID_F5181 && usb->revid == REVID_F5181L) ||
+ (usb->devid == DEVID_F5182 && usb->revid <= REVID_F5182_A1))
+ return;
+
+ reg = readl(usb->base + BRIDGE_IPG);
+ /* Change reserved bits [31:30] from 1 to 0 */
+ reg &= ~(BIT(31) | BIT(30));
+ /* Change NON_START_IPG to 0xd */
+ reg &= ~NON_START_IPG_MASK;
+ reg |= NON_START_IPG(0xd);
+ writel(reg, usb->base + BRIDGE_IPG);
+}
+
+static struct of_device_id mvebu_usb_dt_ids[] = {
+ { .compatible = "marvell,mvebu-usb", },
+};
+
+static int mvebu_usb_probe(struct device_d *dev)
+{
+ struct mvebu_usb *usb;
+ int ret;
+
+ usb = xzalloc(sizeof(*usb));
+
+ usb->base = dev_request_mem_region(dev, 0);
+ if (!usb->base)
+ return -ENOMEM;
+
+ usb->clk = clk_get(dev, NULL);
+ if (IS_ERR(usb->clk))
+ return PTR_ERR(usb->clk);
+
+ usb->vbus = regulator_get(dev, "vbus");
+ if (IS_ERR(usb->vbus))
+ return PTR_ERR(usb->vbus);
+
+ usb->dev = dev;
+ usb->devid = mvebu_get_soc_devid();
+ usb->revid = mvebu_get_soc_revid();
+ usb->mode = of_usb_get_dr_mode(dev->device_node, NULL);
+ if (usb->mode == USB_DR_MODE_UNKNOWN)
+ usb->mode = USB_DR_MODE_HOST;
+
+ usb->ehci.hccr = usb->base + EHCI_REGS_OFFSET;
+ usb->ehci.flags = EHCI_HAS_TT;
+
+ clk_enable(usb->clk);
+
+ mvebu_usb_ipg_setup(usb);
+ mvebu_usb_mbus_setup(usb);
+
+ if (usb->mode == USB_DR_MODE_HOST &&
+ IS_ENABLED(CONFIG_USB_MVEBU_HOST)) {
+ ret = regulator_enable(usb->vbus);
+ if (ret)
+ return ret;
+ ret = ehci_register(dev, &usb->ehci);
+ if (ret)
+ regulator_disable(usb->vbus);
+ } else if (usb->mode == USB_DR_MODE_PERIPHERAL &&
+ IS_ENABLED(CONFIG_USB_MVEBU_DEVICE)) {
+ ret = regulator_disable(usb->vbus);
+ if (ret)
+ return ret;
+ ret = ci_udc_register(dev, usb->base);
+ } else {
+ dev_err(dev, "Unsupported USB role\n");
+ ret = -ENODEV;
+ }
+
+ return ret;
+}
+
+static struct driver_d mvebu_usb_driver = {
+ .name = "mvebu-usb",
+ .probe = mvebu_usb_probe,
+ .of_compatible = mvebu_usb_dt_ids,
+};
+device_platform_driver(mvebu_usb_driver);
diff --git a/drivers/usb/mvebu/phy.c b/drivers/usb/mvebu/phy.c
new file mode 100644
index 000000000000..3eb9dcc12ec0
--- /dev/null
+++ b/drivers/usb/mvebu/phy.c
@@ -0,0 +1,301 @@
+/*
+ * Marvell MVEBU USB PHY driver
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth at gmail.com>
+ *
+ * Based on BSP code (C) Marvell International Ltd.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <linux/clk.h>
+#include <mach/socid.h>
+
+/* 40nm USB PHY registers */
+#define PHY40N_PLL_REG(x) (0x00 + ((x) * 0x04))
+#define PHY40N_PLL_POWERUP BIT(9)
+#define PHY40N_VCO_CALIBRATE BIT(21)
+#define PHY40N_CHANNEL_REG(c,x) (0x40 + ((c) * 0x40) + ((x) * 0x04))
+#define PHY40N_CH_RECALIBRATE BIT(12)
+
+/* 65nm+ USB PHY registers */
+#define PHY_POWER_CTRL 0x00
+#define PHY_PLL_CTRL 0x10
+#define KVCO_EXT BIT(22)
+#define VCO_CALIBRATE BIT(21)
+#define ICP(x) ((x) << 12)
+#define ICP_MASK ICP(0x7)
+#define PHY_TX_CTRL 0x20
+#define HS_STRESS_CTRL BIT(31)
+#define TX_BLOCK_EN BIT(21)
+#define IMP_CAL_VTH(x) ((x) << 14)
+#define IMP_CAL_VTH_MASK IMP_CAL_VTH(0x7)
+#define TX_CALIBRATE BIT(12)
+#define LOWVDD_EN BIT(11)
+#define TX_AMP(x) ((x) << 0)
+#define TX_AMP_MASK TX_AMP(0x7)
+#define PHY_RX_CTRL 0x30
+#define EDGE_DET(x) ((x) << 26)
+#define EDGE_DET_1T EDGE_DET(0x0)
+#define EDGE_DET_2T EDGE_DET(0x1)
+#define EDGE_DET_3T EDGE_DET(0x2)
+#define EDGE_DET_4T EDGE_DET(0x3)
+#define EDGE_DET_MASK EDGE_DET(0x3)
+#define CDR_FASTLOCK_EN BIT(21)
+#define SQ_LENGTH(x) ((x) << 15)
+#define SQ_LENGTH_MASK SQ_LENGTH(0x3)
+#define SQ_THRESH(x) ((x) << 4)
+#define SQ_THRESH_MASK SQ_THRESH(0xf)
+#define LPF_COEFF(x) ((x) << 2)
+#define LPF_COEFF_1_8 LPF_COEFF(0x0)
+#define LPF_COEFF_1_4 LPF_COEFF(0x1)
+#define LPF_COEFF_1_2 LPF_COEFF(0x2)
+#define LPF_COEFF_1_1 LPF_COEFF(0x3)
+#define LPF_COEFF_MASK LPF_COEFF(0x3)
+#define PHY_IVREF_CTRL 0x440
+#define TXVDD12(x) ((x) << 8)
+#define TXVDD12_VDD TXVDD12(0x0)
+#define TXVDD12_1V2 TXVDD12(0x1)
+#define TXVDD12_1V3 TXVDD12(0x2)
+#define TXVDD12_1V4 TXVDD12(0x3)
+#define TXVDD12_MASK TXVDD12(0x3)
+#define PHY_TESTGRP0_CTRL 0x50
+#define FIFO_SQ_RST BIT(15)
+#define PHY_TESTGRP1_CTRL 0x54
+#define PHY_TESTGRP2_CTRL 0x58
+#define PHY_TESTGRP3_CTRL 0x5c
+
+struct mvebu_usbphy {
+ struct device_d *dev;
+ void __iomem *base;
+ struct clk *clk;
+ u16 devid;
+ u16 revid;
+ int (*setup)(struct mvebu_usbphy *phy);
+};
+
+static __maybe_unused int mvebu_usbphy_setup_40nm(struct mvebu_usbphy *phy)
+{
+ struct device_node *cnp;
+ u32 reg;
+
+ /* Set USB PLL REF frequency to 25MHz */
+ reg = readl(phy->base + PHY40N_PLL_REG(1));
+ reg &= ~0x3ff;
+ reg |= 0x605;
+ writel(reg, phy->base + PHY40N_PLL_REG(1));
+
+ /* Power up PLL and PHY channel */
+ reg = readl(phy->base + PHY40N_PLL_REG(2));
+ reg |= PHY40N_PLL_POWERUP;
+ writel(reg, phy->base + PHY40N_PLL_REG(2));
+
+ /* Calibrate VCO */
+ reg = readl(phy->base + PHY40N_PLL_REG(1));
+ reg |= PHY40N_VCO_CALIBRATE;
+ writel(reg, phy->base + PHY40N_PLL_REG(1));
+ udelay(1000);
+
+ /* Setup all individual PHYs */
+ for_each_child_of_node(phy->dev->device_node, cnp) {
+ u32 n;
+
+ if (of_property_read_u32(cnp, "reg", &n))
+ continue;
+
+ reg = readl(phy->base + PHY40N_CHANNEL_REG(n, 3));
+ reg |= BIT(15);
+ writel(reg, phy->base + PHY40N_CHANNEL_REG(n, 3));
+
+ reg = readl(phy->base + PHY40N_CHANNEL_REG(n, 1));
+ reg |= PHY40N_CH_RECALIBRATE;
+ writel(reg, phy->base + PHY40N_CHANNEL_REG(n, 1));
+
+ udelay(40);
+
+ reg = readl(phy->base + PHY40N_CHANNEL_REG(n, 1));
+ reg &= ~PHY40N_CH_RECALIBRATE;
+ writel(reg, phy->base + PHY40N_CHANNEL_REG(n, 1));
+
+ switch (phy->devid) {
+ case DEVID_F6707:
+ case DEVID_F6710:
+ writel(0x20000131, phy->base + PHY40N_CHANNEL_REG(n, 4));
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static __maybe_unused int mvebu_usbphy_setup_65nm(struct mvebu_usbphy *phy)
+{
+ u32 reg;
+
+ /* USB PHY PLL */
+ reg = readl(phy->base + PHY_PLL_CTRL);
+ writel(reg | VCO_CALIBRATE, phy->base + PHY_PLL_CTRL);
+ udelay(100);
+ writel(reg & ~VCO_CALIBRATE, phy->base + PHY_PLL_CTRL);
+
+ /* USB PHY Tx */
+ reg = readl(phy->base + PHY_TX_CTRL);
+ reg &= ~TX_CALIBRATE;
+ writel(reg | TX_CALIBRATE, phy->base + PHY_TX_CTRL);
+ udelay(100);
+ writel(reg & ~TX_CALIBRATE, phy->base + PHY_TX_CTRL);
+
+ switch (phy->devid) {
+ case DEVID_AP510:
+ case DEVID_F6781:
+ reg &= ~(TX_BLOCK_EN | HS_STRESS_CTRL);
+ reg |= LOWVDD_EN;
+ break;
+ }
+
+ switch (phy->devid) {
+ case DEVID_AP510:
+ case DEVID_F6280:
+ reg = (reg & ~IMP_CAL_VTH_MASK) | IMP_CAL_VTH(0x5);
+ break;
+ }
+
+ reg &= ~TX_AMP_MASK;
+ switch (phy->devid) {
+ case DEVID_F6321:
+ case DEVID_F6322:
+ case DEVID_F6323:
+ case DEVID_MV76100:
+ case DEVID_MV78100:
+ case DEVID_MV78200:
+ reg |= TX_AMP(0x4);
+ break;
+ default:
+ reg |= TX_AMP(0x3);
+ break;
+ }
+ writel(reg, phy->base + PHY_TX_CTRL);
+
+ /* USB PHY Rx */
+ reg = readl(phy->base + PHY_RX_CTRL);
+
+ reg = (reg & ~LPF_COEFF_MASK) | LPF_COEFF_1_4;
+
+ reg &= ~SQ_THRESH_MASK;
+ switch (phy->devid) {
+ case DEVID_AP510:
+ case DEVID_F6282:
+ reg |= SQ_THRESH(0xc);
+ break;
+ case DEVID_F6781:
+ reg |= SQ_THRESH(0x7);
+ break;
+ default:
+ reg |= SQ_THRESH(0x8);
+ break;
+ }
+
+ if (phy->devid == DEVID_AP510 ||
+ phy->devid == DEVID_F6781) {
+ reg = (reg & ~SQ_LENGTH_MASK) | SQ_LENGTH(0x1);
+ reg = (reg & ~EDGE_DET_MASK) | EDGE_DET_1T;
+ reg &= ~CDR_FASTLOCK_EN;
+ }
+ writel(reg, phy->base + PHY_RX_CTRL);
+
+ /* USB PHY IVREF */
+ reg = readl(phy->base + PHY_IVREF_CTRL);
+ reg &= ~TXVDD12_MASK;
+ switch (phy->devid) {
+ case DEVID_AP510:
+ case DEVID_F6180:
+ case DEVID_F6190:
+ case DEVID_F6192:
+ case DEVID_F6280:
+ case DEVID_F6281:
+ case DEVID_F6282:
+ case DEVID_F6781:
+ reg |= TXVDD12_1V4;
+ break;
+ default:
+ reg |= TXVDD12_1V2;
+ break;
+ }
+ writel(reg, phy->base + PHY_IVREF_CTRL);
+
+ /* USB PHY Test Group */
+ reg = readl(phy->base + PHY_TESTGRP0_CTRL);
+ if (phy->devid == DEVID_AP510 ||
+ phy->devid == DEVID_F6781)
+ reg &= ~FIFO_SQ_RST;
+ writel(reg, phy->base + PHY_TESTGRP0_CTRL);
+
+ return 0;
+}
+
+static __maybe_unused int mvebu_usbphy_setup_90nm(struct mvebu_usbphy *phy)
+{
+ return -ENODEV;
+}
+
+static __maybe_unused int mvebu_usbphy_setup_150nm(struct mvebu_usbphy *phy)
+{
+ return -ENODEV;
+}
+
+static struct of_device_id mvebu_usbphy_dt_ids[] = {
+#if defined(CONFIG_USB_MVEBU_PHY_40NM)
+ { .compatible = "marvell,mvebu-usb-phy-40nm",
+ .data = (u32)mvebu_usbphy_setup_40nm },
+#endif
+#if defined(CONFIG_USB_MVEBU_PHY_65NM)
+ { .compatible = "marvell,mvebu-usb-phy-65nm",
+ .data = (u32)mvebu_usbphy_setup_65nm },
+#endif
+#if defined(CONFIG_USB_MVEBU_PHY_90NM)
+ { .compatible = "marvell,mvebu-usb-phy-90nm",
+ .data = (u32)mvebu_usbphy_setup_90nm },
+#endif
+#if defined(CONFIG_USB_MVEBU_PHY_150NM)
+ { .compatible = "marvell,mvebu-usb-phy-150nm",
+ .data = (u32)mvebu_usbphy_setup_150nm },
+ {},
+#endif
+};
+
+static int mvebu_usbphy_probe(struct device_d *dev)
+{
+ struct mvebu_usbphy *phy;
+ const struct of_device_id *match =
+ of_match_node(mvebu_usbphy_dt_ids, dev->device_node);
+
+ phy = xzalloc(sizeof(*phy));
+
+ phy->base = dev_request_mem_region(dev, 0);
+ if (!phy->base)
+ return -ENOMEM;
+
+ phy->clk = clk_get(dev, NULL);
+ if (IS_ERR(phy->clk))
+ return PTR_ERR(phy->clk);
+
+ phy->dev = dev;
+ phy->devid = mvebu_get_soc_devid();
+ phy->revid = mvebu_get_soc_revid();
+ phy->setup = (void *)match->data;
+
+ clk_enable(phy->clk);
+ return phy->setup(phy);
+}
+
+static struct driver_d mvebu_usbphy_driver = {
+ .name = "mvebu-usbphy",
+ .probe = mvebu_usbphy_probe,
+ .of_compatible = mvebu_usbphy_dt_ids,
+};
+device_platform_driver(mvebu_usbphy_driver);
--
2.0.0
More information about the barebox
mailing list