forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
328 lines
7.4 KiB
328 lines
7.4 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* PMC MSP EHCI (Host Controller Driver) for USB. |
|
* |
|
* (C) Copyright 2006-2010 PMC-Sierra Inc |
|
*/ |
|
|
|
/* includes */ |
|
#include <linux/platform_device.h> |
|
#include <linux/gpio.h> |
|
#include <linux/usb.h> |
|
#include <msp_usb.h> |
|
|
|
/* stream disable*/ |
|
#define USB_CTRL_MODE_STREAM_DISABLE 0x10 |
|
|
|
/* threshold */ |
|
#define USB_CTRL_FIFO_THRESH 0x00300000 |
|
|
|
/* register offset for usb_mode */ |
|
#define USB_EHCI_REG_USB_MODE 0x68 |
|
|
|
/* register offset for usb fifo */ |
|
#define USB_EHCI_REG_USB_FIFO 0x24 |
|
|
|
/* register offset for usb status */ |
|
#define USB_EHCI_REG_USB_STATUS 0x44 |
|
|
|
/* serial/parallel transceiver */ |
|
#define USB_EHCI_REG_BIT_STAT_STS (1<<29) |
|
|
|
/* TWI USB0 host device pin */ |
|
#define MSP_PIN_USB0_HOST_DEV 49 |
|
|
|
/* TWI USB1 host device pin */ |
|
#define MSP_PIN_USB1_HOST_DEV 50 |
|
|
|
|
|
static void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci) |
|
{ |
|
u8 *base; |
|
u8 *statreg; |
|
u8 *fiforeg; |
|
u32 val; |
|
struct ehci_regs *reg_base = ehci->regs; |
|
|
|
/* get register base */ |
|
base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE; |
|
statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS; |
|
fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO; |
|
|
|
/* Disable controller mode stream */ |
|
val = ehci_readl(ehci, (u32 *)base); |
|
ehci_writel(ehci, (val | USB_CTRL_MODE_STREAM_DISABLE), |
|
(u32 *)base); |
|
|
|
/* clear STS to select parallel transceiver interface */ |
|
val = ehci_readl(ehci, (u32 *)statreg); |
|
val = val & ~USB_EHCI_REG_BIT_STAT_STS; |
|
ehci_writel(ehci, val, (u32 *)statreg); |
|
|
|
/* write to set the proper fifo threshold */ |
|
ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg); |
|
|
|
/* set TWI GPIO USB_HOST_DEV pin high */ |
|
gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1); |
|
} |
|
|
|
/* called during probe() after chip reset completes */ |
|
static int ehci_msp_setup(struct usb_hcd *hcd) |
|
{ |
|
struct ehci_hcd *ehci = hcd_to_ehci(hcd); |
|
int retval; |
|
|
|
ehci->big_endian_mmio = 1; |
|
ehci->big_endian_desc = 1; |
|
|
|
ehci->caps = hcd->regs; |
|
hcd->has_tt = 1; |
|
|
|
retval = ehci_setup(hcd); |
|
if (retval) |
|
return retval; |
|
|
|
usb_hcd_tdi_set_mode(ehci); |
|
|
|
return retval; |
|
} |
|
|
|
|
|
/* configure so an HC device and id are always provided |
|
* always called with process context; sleeping is OK |
|
*/ |
|
|
|
static int usb_hcd_msp_map_regs(struct mspusb_device *dev) |
|
{ |
|
struct resource *res; |
|
struct platform_device *pdev = &dev->dev; |
|
u32 res_len; |
|
int retval; |
|
|
|
/* MAB register space */ |
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
|
if (res == NULL) |
|
return -ENOMEM; |
|
res_len = resource_size(res); |
|
if (!request_mem_region(res->start, res_len, "mab regs")) |
|
return -EBUSY; |
|
|
|
dev->mab_regs = ioremap(res->start, res_len); |
|
if (dev->mab_regs == NULL) { |
|
retval = -ENOMEM; |
|
goto err1; |
|
} |
|
|
|
/* MSP USB register space */ |
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 2); |
|
if (res == NULL) { |
|
retval = -ENOMEM; |
|
goto err2; |
|
} |
|
res_len = resource_size(res); |
|
if (!request_mem_region(res->start, res_len, "usbid regs")) { |
|
retval = -EBUSY; |
|
goto err2; |
|
} |
|
dev->usbid_regs = ioremap(res->start, res_len); |
|
if (dev->usbid_regs == NULL) { |
|
retval = -ENOMEM; |
|
goto err3; |
|
} |
|
|
|
return 0; |
|
err3: |
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 2); |
|
res_len = resource_size(res); |
|
release_mem_region(res->start, res_len); |
|
err2: |
|
iounmap(dev->mab_regs); |
|
err1: |
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
|
res_len = resource_size(res); |
|
release_mem_region(res->start, res_len); |
|
dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n"); |
|
return retval; |
|
} |
|
|
|
/** |
|
* usb_hcd_msp_probe - initialize PMC MSP-based HCDs |
|
* @driver: Pointer to hc driver instance |
|
* @dev: USB controller to probe |
|
* |
|
* Context: task context, might sleep |
|
* |
|
* Allocates basic resources for this USB host controller, and |
|
* then invokes the start() method for the HCD associated with it |
|
* through the hotplug entry's driver_data. |
|
*/ |
|
int usb_hcd_msp_probe(const struct hc_driver *driver, |
|
struct platform_device *dev) |
|
{ |
|
int retval; |
|
struct usb_hcd *hcd; |
|
struct resource *res; |
|
struct ehci_hcd *ehci ; |
|
|
|
hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp"); |
|
if (!hcd) |
|
return -ENOMEM; |
|
|
|
res = platform_get_resource(dev, IORESOURCE_MEM, 0); |
|
if (res == NULL) { |
|
pr_debug("No IOMEM resource info for %s.\n", dev->name); |
|
retval = -ENOMEM; |
|
goto err1; |
|
} |
|
hcd->rsrc_start = res->start; |
|
hcd->rsrc_len = resource_size(res); |
|
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) { |
|
retval = -EBUSY; |
|
goto err1; |
|
} |
|
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); |
|
if (!hcd->regs) { |
|
pr_debug("ioremap failed"); |
|
retval = -ENOMEM; |
|
goto err2; |
|
} |
|
|
|
res = platform_get_resource(dev, IORESOURCE_IRQ, 0); |
|
if (res == NULL) { |
|
dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name); |
|
retval = -ENOMEM; |
|
goto err3; |
|
} |
|
|
|
/* Map non-EHCI register spaces */ |
|
retval = usb_hcd_msp_map_regs(to_mspusb_device(dev)); |
|
if (retval != 0) |
|
goto err3; |
|
|
|
ehci = hcd_to_ehci(hcd); |
|
ehci->big_endian_mmio = 1; |
|
ehci->big_endian_desc = 1; |
|
|
|
|
|
retval = usb_add_hcd(hcd, res->start, IRQF_SHARED); |
|
if (retval == 0) { |
|
device_wakeup_enable(hcd->self.controller); |
|
return 0; |
|
} |
|
|
|
usb_remove_hcd(hcd); |
|
err3: |
|
iounmap(hcd->regs); |
|
err2: |
|
release_mem_region(hcd->rsrc_start, hcd->rsrc_len); |
|
err1: |
|
usb_put_hcd(hcd); |
|
|
|
return retval; |
|
} |
|
|
|
|
|
|
|
/** |
|
* usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs |
|
* @hcd: USB Host Controller being removed |
|
* |
|
* Context: task context, might sleep |
|
* |
|
* Reverses the effect of usb_hcd_msp_probe(), first invoking |
|
* the HCD's stop() method. It is always called from a thread |
|
* context, normally "rmmod", "apmd", or something similar. |
|
* |
|
* may be called without controller electrically present |
|
* may be called with controller, bus, and devices active |
|
*/ |
|
static void usb_hcd_msp_remove(struct usb_hcd *hcd) |
|
{ |
|
usb_remove_hcd(hcd); |
|
iounmap(hcd->regs); |
|
release_mem_region(hcd->rsrc_start, hcd->rsrc_len); |
|
usb_put_hcd(hcd); |
|
} |
|
|
|
static const struct hc_driver ehci_msp_hc_driver = { |
|
.description = hcd_name, |
|
.product_desc = "PMC MSP EHCI", |
|
.hcd_priv_size = sizeof(struct ehci_hcd), |
|
|
|
/* |
|
* generic hardware linkage |
|
*/ |
|
.irq = ehci_irq, |
|
.flags = HCD_MEMORY | HCD_DMA | HCD_USB2 | HCD_BH, |
|
|
|
/* |
|
* basic lifecycle operations |
|
*/ |
|
.reset = ehci_msp_setup, |
|
.shutdown = ehci_shutdown, |
|
.start = ehci_run, |
|
.stop = ehci_stop, |
|
|
|
/* |
|
* 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 ehci_hcd_msp_drv_probe(struct platform_device *pdev) |
|
{ |
|
int ret; |
|
|
|
pr_debug("In ehci_hcd_msp_drv_probe"); |
|
|
|
if (usb_disabled()) |
|
return -ENODEV; |
|
|
|
gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO"); |
|
|
|
ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev); |
|
|
|
return ret; |
|
} |
|
|
|
static int ehci_hcd_msp_drv_remove(struct platform_device *pdev) |
|
{ |
|
struct usb_hcd *hcd = platform_get_drvdata(pdev); |
|
|
|
usb_hcd_msp_remove(hcd); |
|
|
|
/* free TWI GPIO USB_HOST_DEV pin */ |
|
gpio_free(MSP_PIN_USB0_HOST_DEV); |
|
|
|
return 0; |
|
} |
|
|
|
MODULE_ALIAS("pmcmsp-ehci"); |
|
|
|
static struct platform_driver ehci_hcd_msp_driver = { |
|
.probe = ehci_hcd_msp_drv_probe, |
|
.remove = ehci_hcd_msp_drv_remove, |
|
.driver = { |
|
.name = "pmcmsp-ehci", |
|
}, |
|
};
|
|
|