mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-13 14:53:06 +00:00
83d290c56f
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
360 lines
8.3 KiB
C
360 lines
8.3 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (c) 2015 Sanchayan Maity <sanchayan.maity@toradex.com>
|
|
* Copyright (C) 2015 Toradex AG
|
|
*
|
|
* Based on ehci-mx6 driver
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <usb.h>
|
|
#include <errno.h>
|
|
#include <linux/compiler.h>
|
|
#include <asm/io.h>
|
|
#include <asm-generic/gpio.h>
|
|
#include <asm/arch/clock.h>
|
|
#include <asm/arch/imx-regs.h>
|
|
#include <asm/arch/crm_regs.h>
|
|
#include <asm/mach-imx/iomux-v3.h>
|
|
#include <asm/mach-imx/regs-usbphy.h>
|
|
#include <usb/ehci-ci.h>
|
|
#include <linux/libfdt.h>
|
|
#include <fdtdec.h>
|
|
|
|
#include "ehci.h"
|
|
|
|
#define USB_NC_REG_OFFSET 0x00000800
|
|
|
|
#define ANADIG_PLL_CTRL_EN_USB_CLKS (1 << 6)
|
|
|
|
#define UCTRL_OVER_CUR_POL (1 << 8) /* OTG Polarity of Overcurrent */
|
|
#define UCTRL_OVER_CUR_DIS (1 << 7) /* Disable OTG Overcurrent Detection */
|
|
|
|
/* USBCMD */
|
|
#define UCMD_RUN_STOP (1 << 0) /* controller run/stop */
|
|
#define UCMD_RESET (1 << 1) /* controller reset */
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
static const unsigned phy_bases[] = {
|
|
USB_PHY0_BASE_ADDR,
|
|
USB_PHY1_BASE_ADDR,
|
|
};
|
|
|
|
static const unsigned nc_reg_bases[] = {
|
|
USBC0_BASE_ADDR,
|
|
USBC1_BASE_ADDR,
|
|
};
|
|
|
|
static void usb_internal_phy_clock_gate(int index)
|
|
{
|
|
void __iomem *phy_reg;
|
|
|
|
phy_reg = (void __iomem *)phy_bases[index];
|
|
clrbits_le32(phy_reg + USBPHY_CTRL, USBPHY_CTRL_CLKGATE);
|
|
}
|
|
|
|
static void usb_power_config(int index)
|
|
{
|
|
struct anadig_reg __iomem *anadig =
|
|
(struct anadig_reg __iomem *)ANADIG_BASE_ADDR;
|
|
void __iomem *pll_ctrl;
|
|
|
|
switch (index) {
|
|
case 0:
|
|
pll_ctrl = &anadig->pll3_ctrl;
|
|
clrbits_le32(pll_ctrl, ANADIG_PLL3_CTRL_BYPASS);
|
|
setbits_le32(pll_ctrl, ANADIG_PLL3_CTRL_ENABLE
|
|
| ANADIG_PLL3_CTRL_POWERDOWN
|
|
| ANADIG_PLL_CTRL_EN_USB_CLKS);
|
|
break;
|
|
case 1:
|
|
pll_ctrl = &anadig->pll7_ctrl;
|
|
clrbits_le32(pll_ctrl, ANADIG_PLL7_CTRL_BYPASS);
|
|
setbits_le32(pll_ctrl, ANADIG_PLL7_CTRL_ENABLE
|
|
| ANADIG_PLL7_CTRL_POWERDOWN
|
|
| ANADIG_PLL_CTRL_EN_USB_CLKS);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void usb_phy_enable(int index, struct usb_ehci *ehci)
|
|
{
|
|
void __iomem *phy_reg;
|
|
void __iomem *phy_ctrl;
|
|
void __iomem *usb_cmd;
|
|
|
|
phy_reg = (void __iomem *)phy_bases[index];
|
|
phy_ctrl = (void __iomem *)(phy_reg + USBPHY_CTRL);
|
|
usb_cmd = (void __iomem *)&ehci->usbcmd;
|
|
|
|
/* Stop then Reset */
|
|
clrbits_le32(usb_cmd, UCMD_RUN_STOP);
|
|
while (readl(usb_cmd) & UCMD_RUN_STOP)
|
|
;
|
|
|
|
setbits_le32(usb_cmd, UCMD_RESET);
|
|
while (readl(usb_cmd) & UCMD_RESET)
|
|
;
|
|
|
|
/* Reset USBPHY module */
|
|
setbits_le32(phy_ctrl, USBPHY_CTRL_SFTRST);
|
|
udelay(10);
|
|
|
|
/* Remove CLKGATE and SFTRST */
|
|
clrbits_le32(phy_ctrl, USBPHY_CTRL_CLKGATE | USBPHY_CTRL_SFTRST);
|
|
udelay(10);
|
|
|
|
/* Power up the PHY */
|
|
writel(0, phy_reg + USBPHY_PWD);
|
|
|
|
/* Enable FS/LS device */
|
|
setbits_le32(phy_ctrl, USBPHY_CTRL_ENUTMILEVEL2 |
|
|
USBPHY_CTRL_ENUTMILEVEL3);
|
|
}
|
|
|
|
static void usb_oc_config(int index)
|
|
{
|
|
void __iomem *ctrl;
|
|
|
|
ctrl = (void __iomem *)(nc_reg_bases[index] + USB_NC_REG_OFFSET);
|
|
|
|
setbits_le32(ctrl, UCTRL_OVER_CUR_POL);
|
|
setbits_le32(ctrl, UCTRL_OVER_CUR_DIS);
|
|
}
|
|
|
|
int __weak board_usb_phy_mode(int port)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int __weak board_ehci_hcd_init(int port)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int ehci_vf_common_init(struct usb_ehci *ehci, int index)
|
|
{
|
|
int ret;
|
|
|
|
/* Do board specific initialisation */
|
|
ret = board_ehci_hcd_init(index);
|
|
if (ret)
|
|
return ret;
|
|
|
|
usb_power_config(index);
|
|
usb_oc_config(index);
|
|
usb_internal_phy_clock_gate(index);
|
|
usb_phy_enable(index, ehci);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef CONFIG_DM_USB
|
|
int ehci_hcd_init(int index, enum usb_init_type init,
|
|
struct ehci_hccr **hccr, struct ehci_hcor **hcor)
|
|
{
|
|
struct usb_ehci *ehci;
|
|
enum usb_init_type type;
|
|
int ret;
|
|
|
|
if (index >= ARRAY_SIZE(nc_reg_bases))
|
|
return -EINVAL;
|
|
|
|
ehci = (struct usb_ehci *)nc_reg_bases[index];
|
|
|
|
ret = ehci_vf_common_init(index);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*hccr = (struct ehci_hccr *)((uint32_t)&ehci->caplength);
|
|
*hcor = (struct ehci_hcor *)((uint32_t)*hccr +
|
|
HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));
|
|
|
|
type = board_usb_phy_mode(index);
|
|
if (type != init)
|
|
return -ENODEV;
|
|
|
|
if (init == USB_INIT_DEVICE) {
|
|
setbits_le32(&ehci->usbmode, CM_DEVICE);
|
|
writel((PORT_PTS_UTMI | PORT_PTS_PTW), &ehci->portsc);
|
|
setbits_le32(&ehci->portsc, USB_EN);
|
|
} else if (init == USB_INIT_HOST) {
|
|
setbits_le32(&ehci->usbmode, CM_HOST);
|
|
writel((PORT_PTS_UTMI | PORT_PTS_PTW), &ehci->portsc);
|
|
setbits_le32(&ehci->portsc, USB_EN);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ehci_hcd_stop(int index)
|
|
{
|
|
return 0;
|
|
}
|
|
#else
|
|
/* Possible port types (dual role mode) */
|
|
enum dr_mode {
|
|
DR_MODE_NONE = 0,
|
|
DR_MODE_HOST, /* supports host operation */
|
|
DR_MODE_DEVICE, /* supports device operation */
|
|
DR_MODE_OTG, /* supports both */
|
|
};
|
|
|
|
struct ehci_vf_priv_data {
|
|
struct ehci_ctrl ctrl;
|
|
struct usb_ehci *ehci;
|
|
struct gpio_desc cdet_gpio;
|
|
enum usb_init_type init_type;
|
|
enum dr_mode dr_mode;
|
|
u32 portnr;
|
|
};
|
|
|
|
static int vf_usb_ofdata_to_platdata(struct udevice *dev)
|
|
{
|
|
struct ehci_vf_priv_data *priv = dev_get_priv(dev);
|
|
const void *dt_blob = gd->fdt_blob;
|
|
int node = dev_of_offset(dev);
|
|
const char *mode;
|
|
|
|
priv->portnr = dev->seq;
|
|
|
|
priv->ehci = (struct usb_ehci *)devfdt_get_addr(dev);
|
|
mode = fdt_getprop(dt_blob, node, "dr_mode", NULL);
|
|
if (mode) {
|
|
if (0 == strcmp(mode, "host")) {
|
|
priv->dr_mode = DR_MODE_HOST;
|
|
priv->init_type = USB_INIT_HOST;
|
|
} else if (0 == strcmp(mode, "peripheral")) {
|
|
priv->dr_mode = DR_MODE_DEVICE;
|
|
priv->init_type = USB_INIT_DEVICE;
|
|
} else if (0 == strcmp(mode, "otg")) {
|
|
priv->dr_mode = DR_MODE_OTG;
|
|
/*
|
|
* We set init_type to device by default when OTG
|
|
* mode is requested. If a valid gpio is provided
|
|
* we will switch the init_type based on the state
|
|
* of the gpio pin.
|
|
*/
|
|
priv->init_type = USB_INIT_DEVICE;
|
|
} else {
|
|
debug("%s: Cannot decode dr_mode '%s'\n",
|
|
__func__, mode);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
priv->dr_mode = DR_MODE_HOST;
|
|
priv->init_type = USB_INIT_HOST;
|
|
}
|
|
|
|
if (priv->dr_mode == DR_MODE_OTG) {
|
|
gpio_request_by_name_nodev(offset_to_ofnode(node),
|
|
"fsl,cdet-gpio", 0, &priv->cdet_gpio,
|
|
GPIOD_IS_IN);
|
|
if (dm_gpio_is_valid(&priv->cdet_gpio)) {
|
|
if (dm_gpio_get_value(&priv->cdet_gpio))
|
|
priv->init_type = USB_INIT_DEVICE;
|
|
else
|
|
priv->init_type = USB_INIT_HOST;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vf_init_after_reset(struct ehci_ctrl *dev)
|
|
{
|
|
struct ehci_vf_priv_data *priv = dev->priv;
|
|
enum usb_init_type type = priv->init_type;
|
|
struct usb_ehci *ehci = priv->ehci;
|
|
int ret;
|
|
|
|
ret = ehci_vf_common_init(priv->ehci, priv->portnr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (type == USB_INIT_DEVICE)
|
|
return 0;
|
|
|
|
setbits_le32(&ehci->usbmode, CM_HOST);
|
|
writel((PORT_PTS_UTMI | PORT_PTS_PTW), &ehci->portsc);
|
|
setbits_le32(&ehci->portsc, USB_EN);
|
|
|
|
mdelay(10);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct ehci_ops vf_ehci_ops = {
|
|
.init_after_reset = vf_init_after_reset
|
|
};
|
|
|
|
static int vf_usb_bind(struct udevice *dev)
|
|
{
|
|
static int num_controllers;
|
|
|
|
/*
|
|
* Without this hack, if we return ENODEV for USB Controller 0, on
|
|
* probe for the next controller, USB Controller 1 will be given a
|
|
* sequence number of 0. This conflicts with our requirement of
|
|
* sequence numbers while initialising the peripherals.
|
|
*/
|
|
dev->req_seq = num_controllers;
|
|
num_controllers++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ehci_usb_probe(struct udevice *dev)
|
|
{
|
|
struct usb_platdata *plat = dev_get_platdata(dev);
|
|
struct ehci_vf_priv_data *priv = dev_get_priv(dev);
|
|
struct usb_ehci *ehci = priv->ehci;
|
|
struct ehci_hccr *hccr;
|
|
struct ehci_hcor *hcor;
|
|
int ret;
|
|
|
|
ret = ehci_vf_common_init(ehci, priv->portnr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (priv->init_type != plat->init_type)
|
|
return -ENODEV;
|
|
|
|
if (priv->init_type == USB_INIT_HOST) {
|
|
setbits_le32(&ehci->usbmode, CM_HOST);
|
|
writel((PORT_PTS_UTMI | PORT_PTS_PTW), &ehci->portsc);
|
|
setbits_le32(&ehci->portsc, USB_EN);
|
|
}
|
|
|
|
mdelay(10);
|
|
|
|
hccr = (struct ehci_hccr *)((uint32_t)&ehci->caplength);
|
|
hcor = (struct ehci_hcor *)((uint32_t)hccr +
|
|
HC_LENGTH(ehci_readl(&hccr->cr_capbase)));
|
|
|
|
return ehci_register(dev, hccr, hcor, &vf_ehci_ops, 0, priv->init_type);
|
|
}
|
|
|
|
static const struct udevice_id vf_usb_ids[] = {
|
|
{ .compatible = "fsl,vf610-usb" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(usb_ehci) = {
|
|
.name = "ehci_vf",
|
|
.id = UCLASS_USB,
|
|
.of_match = vf_usb_ids,
|
|
.bind = vf_usb_bind,
|
|
.probe = ehci_usb_probe,
|
|
.remove = ehci_deregister,
|
|
.ops = &ehci_usb_ops,
|
|
.ofdata_to_platdata = vf_usb_ofdata_to_platdata,
|
|
.platdata_auto_alloc_size = sizeof(struct usb_platdata),
|
|
.priv_auto_alloc_size = sizeof(struct ehci_vf_priv_data),
|
|
.flags = DM_FLAG_ALLOC_PRIV_DMA,
|
|
};
|
|
#endif
|