@@ -552,6 +552,14 @@ config USB_OHCI_EXYNOS
help
Enable support for the Samsung Exynos SOC's on-chip OHCI controller.
+config USB_OHCI_HCD_PS2
+ tristate "OHCI support for the Sony PlayStation 2"
+ depends on SONY_PS2
+ select GENERIC_ALLOCATOR
+ default y
+ help
+ Enable support for the Sony PlayStation 2 OHCI controller.
+
config USB_CNS3XXX_OHCI
bool "Cavium CNS3XXX OHCI Module (DEPRECATED)"
depends on ARCH_CNS3XXX
@@ -69,6 +69,7 @@ obj-$(CONFIG_USB_OHCI_HCD_S3C2410) += ohci-s3c2410.o
obj-$(CONFIG_USB_OHCI_HCD_LPC32XX) += ohci-nxp.o
obj-$(CONFIG_USB_OHCI_HCD_PXA27X) += ohci-pxa27x.o
obj-$(CONFIG_USB_OHCI_HCD_DAVINCI) += ohci-da8xx.o
+obj-$(CONFIG_USB_OHCI_HCD_PS2) += ohci-ps2.o
obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o
obj-$(CONFIG_USB_FHCI_HCD) += fhci.o
new file mode 100644
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PlayStation 2 USB 1.1 OHCI host controller (HCD)
+ *
+ * Copyright (C) 2017 Jürgen Urban
+ * Copyright (C) 2018 Fredrik Noring
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include <asm/mach-ps2/iop-heap.h>
+#include <asm/mach-ps2/iop-memory.h>
+#include <asm/mach-ps2/iop-registers.h>
+
+#include "ohci.h"
+
+#define DRIVER_DESC "PlayStation 2 USB OHCI host controller"
+#define DRV_NAME "ohci-ps2"
+
+/* Size allocated from IOP heap (maximum size of DMA memory). */
+#define DMA_BUFFER_SIZE (256 * 1024)
+
+#define hcd_to_priv(hcd) ((struct ps2_hcd *)(hcd_to_ohci(hcd)->priv))
+
+/**
+ * struct ps2_hcd - private device driver structure
+ * @iop_dma_addr: input/output processor (IOP) DMA buffer address
+ */
+struct ps2_hcd {
+ dma_addr_t iop_dma_addr;
+};
+
+static struct hc_driver __read_mostly ohci_ps2_hc_driver;
+
+static void ohci_ps2_enable(struct usb_hcd *hcd)
+{
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+
+ ohci_writel(ohci, 1, &ohci->regs->roothub.portstatus[11]);
+}
+
+static void ohci_ps2_disable(struct usb_hcd *hcd)
+{
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+
+ ohci_writel(ohci, 0, &ohci->regs->roothub.portstatus[11]);
+}
+
+static void ohci_ps2_start_hc(struct usb_hcd *hcd)
+{
+ iop_set_dma_dpcr2(IOP_DMA_DPCR2_OHCI);
+
+ outw(1, IOP_OHCI_BASE + 0x80);
+}
+
+static void ohci_ps2_stop_hc(struct usb_hcd *hcd)
+{
+ iop_clr_dma_dpcr2(IOP_DMA_DPCR2_OHCI);
+}
+
+static int ohci_ps2_reset(struct usb_hcd *hcd)
+{
+ int err;
+
+ ohci_ps2_start_hc(hcd);
+
+ err = ohci_setup(hcd);
+ if (err) {
+ ohci_ps2_stop_hc(hcd);
+ return err;
+ }
+
+ ohci_ps2_enable(hcd);
+
+ return 0;
+}
+
+static int iopheap_alloc_dma_buffer(struct platform_device *pdev, size_t size)
+{
+ struct device *dev = &pdev->dev;
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+ struct ps2_hcd *ps2priv = hcd_to_priv(hcd);
+ int err;
+
+ ps2priv->iop_dma_addr = iop_alloc(size);
+ if (!ps2priv->iop_dma_addr) {
+ dev_err(dev, "iop_alloc failed\n");
+ return -ENOMEM;
+ }
+
+ err = usb_hcd_setup_local_mem(hcd,
+ iop_bus_to_phys(ps2priv->iop_dma_addr),
+ ps2priv->iop_dma_addr, size);
+ if (err) {
+ dev_err(dev, "usb_hcd_setup_local_mem failed with %d\n", err);
+ iop_free(ps2priv->iop_dma_addr);
+ ps2priv->iop_dma_addr = 0;
+ return err;
+ }
+
+ return 0;
+}
+
+static void iopheap_free_dma_buffer(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+ struct ps2_hcd *ps2priv = hcd_to_priv(hcd);
+
+ if (!ps2priv->iop_dma_addr)
+ return;
+
+ iop_free(ps2priv->iop_dma_addr);
+ ps2priv->iop_dma_addr = 0;
+}
+
+static int ohci_hcd_ps2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *regs;
+ struct usb_hcd *hcd;
+ struct ps2_hcd *ps2priv;
+ int irq;
+ int err;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "platform_get_irq failed\n");
+ return irq;
+ }
+
+ hcd = usb_create_hcd(&ohci_ps2_hc_driver, dev, dev_name(dev));
+ if (!hcd) {
+ dev_err(dev, "usb_create_hcd failed\n");
+ return -ENOMEM;
+ }
+
+ ps2priv = hcd_to_priv(hcd);
+ memset(ps2priv, 0, sizeof(*ps2priv));
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!regs) {
+ dev_err(dev, "platform_get_resource 0 failed\n");
+ err = -ENOENT;
+ goto err_platform;
+ }
+
+ hcd->rsrc_start = regs->start;
+ hcd->rsrc_len = resource_size(regs);
+ hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
+ if (IS_ERR(hcd->regs)) {
+ err = PTR_ERR(hcd->regs);
+ dev_err(dev, "ioremap failed with %d\n", err);
+ goto err_ioremap;
+ }
+
+ err = iopheap_alloc_dma_buffer(pdev, DMA_BUFFER_SIZE);
+ if (err)
+ goto err_alloc_dma_buffer;
+
+ err = usb_add_hcd(hcd, irq, IRQF_SHARED);
+ if (err) {
+ dev_err(dev, "usb_add_hcd failed with %d\n", err);
+ goto err_add_hcd;
+ }
+
+ err = device_wakeup_enable(hcd->self.controller);
+ if (err) {
+ dev_err(dev, "device_wakeup_enable failed with %d\n", err);
+ goto err_wakeup;
+ }
+
+ return 0;
+
+err_wakeup:
+ usb_remove_hcd(hcd);
+err_add_hcd:
+ iopheap_free_dma_buffer(pdev);
+err_alloc_dma_buffer:
+ iounmap(hcd->regs);
+err_ioremap:
+err_platform:
+ usb_put_hcd(hcd);
+ return err;
+}
+
+static int ohci_hcd_ps2_remove(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+
+ usb_remove_hcd(hcd);
+
+ ohci_ps2_disable(hcd);
+ ohci_ps2_stop_hc(hcd);
+
+ iopheap_free_dma_buffer(pdev);
+ iounmap(hcd->regs);
+
+ usb_put_hcd(hcd);
+
+ return 0;
+}
+
+static struct platform_driver ohci_hcd_ps2_driver = {
+ .probe = ohci_hcd_ps2_probe,
+ .remove = ohci_hcd_ps2_remove,
+ .shutdown = usb_hcd_platform_shutdown,
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static const struct ohci_driver_overrides ps2_overrides __initconst = {
+ .reset = ohci_ps2_reset,
+ .product_desc = DRIVER_DESC,
+ .extra_priv_size = sizeof(struct ps2_hcd),
+};
+
+static int __init ohci_ps2_init(void)
+{
+ if (usb_disabled()) {
+ pr_err(DRV_NAME ": Initialization failed: USB is disabled\n");
+ return -ENODEV;
+ }
+
+ ohci_init_driver(&ohci_ps2_hc_driver, &ps2_overrides);
+
+ return platform_driver_register(&ohci_hcd_ps2_driver);
+}
+module_init(ohci_ps2_init);
+
+static void __exit ohci_ps2_exit(void)
+{
+ platform_driver_unregister(&ohci_hcd_ps2_driver);
+}
+module_exit(ohci_ps2_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Fredrik Noring");
+MODULE_AUTHOR("Jürgen Urban");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
Support the USB 1.1 OHCI for the PlayStation 2. The controller is connected to the input/output processor (IOP) which is separate from the main (R5900) processor that runs the kernel. IOP DMA memory and OHCI registers are mapped into kernel address space. OHCI interrupts are asserted on the IOP and forwarded to the kernel by an IOP IRQ relay module. Signed-off-by: Fredrik Noring <noring@nocrew.org> --- drivers/usb/host/Kconfig | 8 ++ drivers/usb/host/Makefile | 1 + drivers/usb/host/ohci-ps2.c | 245 ++++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+) create mode 100644 drivers/usb/host/ohci-ps2.c