blob: bd72f104df0f891aa0ec058614c4a951829e558d [file] [log] [blame]
/*
* linux/drivers/usb/host/ehci-berlin.c
*
* Authors: Hongzhan Chen <hzchen@marvell.com>
* Copyright (C) 2011 Marvell Ltd.
*
* Based on "ehci-au1xxx.c"
*
* 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/of.h>
#include <linux/of_platform.h>
#include <mach/gpio.h>
#include <mach/galois_platform.h>
#define MV_USB_CHIP_ID 0
#define MV_USB_CHIP_ID_MASK 0x3F
#define MV_USB_CHIP_USB20 0x05
#define MV_SBUSCFG 0x090
#define MV_USB_CHIP_CAP 0x100
#define MV_USBMODE 0x1a8
#define USBMODE_CM_HOST 3
#define MV_USB_PHY_PLL_REG 0x04
#define MV_USB_PHY_PLL_CONTROL_REG 0x08
#define MV_USB_PHY_ANALOG_REG 0x34
#define MV_USB_PHY_RX_CTRL_REG 0x20
#define PHY_PLL 0x54C0
#define MV_USB_RESET_TRIGGER 0x0178 /* RA_Gbl_ResetTrigger */
#define USB2_OTG_REG0 0x34
#define USB2_CHARGER_REG0 0x38
#define USB2_PLL_REG0 0x0
#define USB2_PLL_REG1 0x4
#define USB2_DIG_REG0 0x1c
#define USB2_TX_CH_CTRL0 0x0c
#define USB2_CAL_CTRL 8
struct berlin_ehci_hcd {
struct ehci_hcd *ehci;
void __iomem *phy_base;
int reset;
int pwr_gpio;
};
static int berlin_cdp_reinit_phy(void __iomem *base)
{
u32 data, timeout;
/* powering up OTG */
data = readl( base + USB2_OTG_REG0);
data |= 1<<4;
writel(data, (base + USB2_OTG_REG0));
/* powering Charger detector circuit */
data = readl( base + USB2_CHARGER_REG0);
data |= 1<<5;
writel(data, (base + USB2_CHARGER_REG0));
/* Power up analog port */
writel(0x03BE7F6F, (base + USB2_TX_CH_CTRL0));
/* Squelch setting */
writel(0xC39F16CE, (base + USB2_DIG_REG0));
/* Impedance calibration */
writel(0xf5930488, (base + USB2_CAL_CTRL));
/* Configuring FBDIV for SOC Clk 25 Mhz */
data = readl( base + USB2_PLL_REG0);
data &= 0xce00ff80;
data |= 5 | (0x60<<16) | (0<<28);
writel(data, (base + USB2_PLL_REG0));
/* Power up PLL, Reset, Suspen_en disable */
writel(0x407, (base + USB2_PLL_REG1));
udelay(100);
/* Deassert Reset */
writel(0x403, (base + USB2_PLL_REG1));
/* Wait for PLL Lock */
timeout = 0x1000000;
do {
data = readl( base + USB2_PLL_REG0);
if (!--timeout)
break;
} while ( !(data&0x80000000));
if (!timeout)
printk(KERN_ERR "ERROR: USB PHY PLL NOT LOCKED!\n");
return 0;
}
static void mv_start_ehc(struct usb_hcd *hcd)
{
struct berlin_ehci_hcd *berlin = dev_get_drvdata(hcd->self.controller);
if (berlin->pwr_gpio >= 0)
GPIO_PortWrite(berlin->pwr_gpio, 0);
#ifdef CONFIG_BERLIN2CDP
berlin_cdp_reinit_phy(berlin->phy_base);
#else
writel(PHY_PLL, berlin->phy_base + MV_USB_PHY_PLL_REG);
writel(0x2235, berlin->phy_base + MV_USB_PHY_PLL_CONTROL_REG);
writel(0x5680, berlin->phy_base + MV_USB_PHY_ANALOG_REG);
writel(0xAA79, berlin->phy_base + MV_USB_PHY_RX_CTRL_REG);
if (berlin->reset >= 0) {
u32 temp;
temp = 1 << berlin->reset;
writel(temp, IOMEM(MEMMAP_CHIP_CTRL_REG_BASE + MV_USB_RESET_TRIGGER));
}
#endif
if (berlin->pwr_gpio >= 0)
GPIO_PortWrite(berlin->pwr_gpio, 1);
}
static void mv_stop_ehc(struct usb_hcd *hcd)
{
struct berlin_ehci_hcd *berlin = dev_get_drvdata(hcd->self.controller);
if (berlin->pwr_gpio >= 0)
GPIO_PortWrite(berlin->pwr_gpio, 0);
}
static int ehci_mv_setup(struct usb_hcd *hcd)
{
mv_start_ehc(hcd);
hcd->has_tt = 1;
return ehci_setup(hcd);
}
static int ehci_mv_start( struct usb_hcd *hcd)
{
int retval;
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
retval = ehci_run(hcd);
ehci_writel(ehci, 0x07, hcd->regs + MV_SBUSCFG);
ehci_writel(ehci, 0x13, hcd->regs + MV_USBMODE);
return retval;
}
static const struct hc_driver ehci_mv_hc_driver = {
.description = hcd_name,
.product_desc = "Marvell Berlin SoC EHCI Host Controller",
.hcd_priv_size = sizeof(struct ehci_hcd),
/*
* generic hardware linkage
*/
.irq = ehci_irq,
.flags = HCD_MEMORY | HCD_USB2,
/*
* basic lifecycle operations
*/
.reset = ehci_mv_setup,
.start = ehci_mv_start,
.stop = ehci_stop,
.shutdown = ehci_shutdown,
/*
* managing i/o requests and associated device resources
*/
.urb_enqueue = ehci_urb_enqueue,
.urb_dequeue = ehci_urb_dequeue,
.endpoint_disable = ehci_endpoint_disable,
.endpoint_reset = ehci_endpoint_reset,
/*
* scheduling support
*/
.get_frame_number = ehci_get_frame,
/*
* root hub support
*/
.hub_status_data = ehci_hub_status_data,
.hub_control = ehci_hub_control,
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
.relinquish_port = ehci_relinquish_port,
.port_handed_over = ehci_port_handed_over,
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
};
static int berlin_ehci_probe(struct platform_device *pdev)
{
int retval, irq;
u32 val;
struct usb_hcd *hcd;
struct ehci_hcd *ehci;
struct resource *res;
struct berlin_ehci_hcd *berlin;
struct device_node *np = pdev->dev.of_node;
if (usb_disabled()){
return -ENODEV;
}
berlin = devm_kzalloc(&pdev->dev, sizeof(*berlin), GFP_KERNEL);
if (!berlin)
return -ENOMEM;
if (of_property_read_u32(np, "phy-base", &val)) {
dev_err(&pdev->dev, "no phy base set\n");
return -EINVAL;
}
berlin->phy_base = (void __iomem *)val;
if (of_property_read_u32(np, "reset-bit", &val)) {
/* berlin2cdp don't need reset */
berlin->reset = -1;
} else {
berlin->reset = val;
}
if (of_property_read_u32(np, "pwr-gpio", &val)) {
/* some platform don't have pwr gpio */
berlin->pwr_gpio = -1;
} else {
berlin->pwr_gpio = val;
}
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(&pdev->dev,
"Found HC with no IRQ. Check %s setup!\n",
dev_name(&pdev->dev));
return -ENODEV;
}
irq = res->start;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev,
"Found HC with no register addr. Check %s setup!\n",
dev_name(&pdev->dev));
return -ENODEV;
}
pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
hcd = usb_create_hcd(&ehci_mv_hc_driver, &pdev->dev,
dev_name(&pdev->dev));
if (!hcd) {
dev_err(&pdev->dev, "Unable to create HCD\n");
retval = -ENOMEM;
goto err1;
}
platform_set_drvdata(pdev, berlin);
hcd->rsrc_start = res->start;
hcd->rsrc_len = resource_size(res);
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
dev_err(&pdev->dev, "request_mem_region failed\n");
retval = -EBUSY;
goto err2;
}
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
if (!hcd->regs) {
dev_err(&pdev->dev, "ioremap failed \n");
retval = -EFAULT;
goto err3;
}
berlin->ehci = ehci = hcd_to_ehci(hcd);
ehci->caps = hcd->regs + MV_USB_CHIP_CAP;
ehci->regs = hcd->regs + MV_USB_CHIP_CAP + HC_LENGTH(ehci, readl(&ehci->caps->hc_capbase));
/* cache this readonly data; minimize chip reads */
ehci->hcs_params = readl(&ehci->caps->hcs_params);
retval = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED);
if (retval == 0) {
dev_info(&pdev->dev, "usb_add_hcd successful\n");
return retval;
}
iounmap(hcd->regs);
err3:
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
err2:
usb_put_hcd(hcd);
err1:
return retval;
}
static int berlin_ehci_remove(struct platform_device *pdev)
{
struct berlin_ehci_hcd *berlin = platform_get_drvdata(pdev);
struct usb_hcd *hcd = ehci_to_hcd(berlin->ehci);
mv_stop_ehc(hcd);
usb_remove_hcd(hcd);
iounmap(hcd->regs);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
usb_put_hcd(hcd);
return 0;
}
static void berlin_ehci_shutdown(struct platform_device *pdev)
{
struct berlin_ehci_hcd *berlin = platform_get_drvdata(pdev);
struct usb_hcd *hcd = ehci_to_hcd(berlin->ehci);
if (hcd->driver->shutdown)
hcd->driver->shutdown(hcd);
}
static const struct of_device_id berlin_ehci_of_match[] = {
{.compatible = "mrvl,berlin-ehci",},
{},
};
MODULE_DEVICE_TABLE(of, berlin_ehci_of_match);
MODULE_ALIAS("berlin-ehci");
static struct platform_driver berlin_ehci_driver = {
.probe = berlin_ehci_probe,
.remove = berlin_ehci_remove,
.shutdown = berlin_ehci_shutdown,
.driver = {
.name = "berlin-ehci",
.of_match_table = berlin_ehci_of_match,
}
};